examples: replace legacy timer group with gptimer

This commit is contained in:
morris
2022-01-02 16:19:06 +08:00
parent 02f6b83f47
commit 6bf3af7c8e
20 changed files with 523 additions and 1454 deletions

View File

@@ -2,7 +2,7 @@
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/common_components"
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/common_components/pid_ctrl"
"$ENV{IDF_PATH}/examples/peripherals/pcnt/rotary_encoder/components")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)

View File

@@ -0,0 +1,112 @@
| Supported Targets | ESP32 | ESP32-S3 |
| ----------------- | ----- | -------- |
# MCPWM Brushed DC Motor Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example mainly illustrates how to drive a brushed DC motor by generating two specific PWM signals. However the PWM signals from ESP32 can't drive motors directly as the motor usually consumes high current. So an H-bridge like [DRV8848](https://www.ti.com/product/DRV8848) should be used to provide the needed voltage and current for brushed DC motor. To measure the speed of motor, a photoelectric encoder is used to generate the "speed feedback" signals (e.g. a pair of quadrature signal). The example uses a simple PID control approach to keep the motor speed in a constant speed. The example provides a console command line interface for user to update the PID parameters according to actual situation.
## How to Use Example
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
### Hardware Required
* A development board with any Espressif SoC which features MCPWM and PCNT peripheral (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A USB cable for Power supply and programming
* A separate 12V power supply for brushed DC motor and H-bridge (the voltage depends on the motor model used in the example)
* A motor driving board to transfer pwm signal into driving signal
* A brushed DC motor, e.g. [25GA370](http://www.tronsunmotor.com/data/upload/file/201807/e03b98802b5c5390d6570939def525ba.pdf)
* A quadrature encoder to detect speed
Connection :
```
Power(12V)
|
v
+----------------+ +--------------------+
| | | H-Bridge |
| GND +<----------->| GND | +--------------+
| | | | | |
| GENA_GPIO_NUM +----PWM0A--->| IN_A OUT_A +----->| Brushed |
| | | | | DC |
| GENB_GPIO_NUM +----PWM0B--->| IN_B OUT_B +----->| Motor |
| | | | | |
| ESP | +--------------------+ | |
| | +------+-------+
| | |
| | +--------------------+ |
| VCC3.3 +------------>| VCC Encoder | |
| | | | |
| GND +<----------->| |<------------+
| | | |
|PHASEA_GPIO_NUM |<---PhaseA---+ C1 |
| | | |
|PHASEB_GPIO_NUM |<---PhaseB---+ C2 |
| | | |
+----------------+ +--------------------+
```
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
## Example Output
Run the example, you will see the following output log:
```
I (0) cpu_start: Starting scheduler on APP CPU.
configure mcpwm gpio
init mcpwm driver
init and start rotary encoder
init PID control block
init motor control timer
D (561) gptimer: new group (0) @0x3fce0a24
D (561) gptimer: new gptimer (0,0) at 0x3fce0964, resolution=1000000Hz
create motor control task
start motor control timer
D (571) gptimer: install interrupt service for timer (0,0)
install console command line
Type 'help' to get the list of commands.
Use UP/DOWN arrows to navigate through command history.
Press TAB when typing command name to auto-complete.
dc-motor>
dc-motor> help
help
Print the list of registered commands
pid [-p <kp>] [-i <ki>] [-d <kd>]
Set PID parameters
-p <kp> Set Kp value of PID
-i <ki> Set Ki value of PID
-d <kd> Set Kd value of PID
```
### Set PID parameters
* Command: `pid -p <double> -i <double> -d <double> -t <loc/inc>`
* 'p' - proportion value
* 'i' - integral value
* 'd' - differential value
* 't' - PID calculation type (locational or incremental).
```bash
mcpwm-motor> pid -p 0.8 -i 0.02 -d 0.1 -t inc
pid: kp = 0.800
pid: ki = 0.020
pid: kd = 0.100
pid: type = increment
```
## Troubleshooting
* Make sure your ESP board and H-bridge module have been connected to the same GND panel.
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "mcpwm_bdc_control_example_main.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,232 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gptimer.h"
#include "driver/mcpwm.h"
#include "rotary_encoder.h"
#include "pid_ctrl.h"
#include "esp_console.h"
#include "argtable3/argtable3.h"
// Enable this config, we will print debug formated string, which in return can be captured and parsed by Serial-Studio
#define SERIAL_STUDIO_DEBUG 0
#define BDC_MCPWM_UNIT 0
#define BDC_MCPWM_TIMER 0
#define BDC_MCPWM_GENA_GPIO_NUM 7
#define BDC_MCPWM_GENB_GPIO_NUM 15
#define BDC_MCPWM_FREQ_HZ 1500
#define BDC_ENCODER_PCNT_UNIT 0
#define BDC_ENCODER_PHASEA_GPIO_NUM 36
#define BDC_ENCODER_PHASEB_GPIO_NUM 35
#define BDC_PID_CALCULATION_PERIOD_US 10000
#define BDC_PID_FEEDBACK_QUEUE_LEN 10
static pid_ctrl_parameter_t pid_runtime_param = {
.kp = 0.6,
.ki = 0.3,
.kd = 0.12,
.cal_type = PID_CAL_TYPE_INCREMENTAL,
.max_output = 100,
.min_output = -100,
.max_integral = 1000,
.min_integral = -1000,
};
static bool pid_need_update = false;
static int expect_pulses = 300;
static int real_pulses;
typedef struct {
rotary_encoder_t *encoder;
QueueHandle_t pid_feedback_queue;
} motor_control_timer_context_t;
typedef struct {
QueueHandle_t pid_feedback_queue;
pid_ctrl_block_handle_t pid_ctrl;
} motor_control_task_context_t;
static void brushed_motor_set_duty(float duty_cycle)
{
/* motor moves in forward direction, with duty cycle = duty % */
if (duty_cycle > 0) {
mcpwm_set_signal_low(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_A);
mcpwm_set_duty(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_B, duty_cycle);
mcpwm_set_duty_type(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_B, MCPWM_DUTY_MODE_0);
}
/* motor moves in backward direction, with duty cycle = -duty % */
else {
mcpwm_set_signal_low(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_B);
mcpwm_set_duty(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_A, -duty_cycle);
mcpwm_set_duty_type(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_A, MCPWM_DUTY_MODE_0);
}
}
static bool motor_ctrl_timer_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *arg)
{
static int last_pulse_count = 0;
BaseType_t high_task_awoken = pdFALSE;
motor_control_timer_context_t *user_ctx = (motor_control_timer_context_t *)arg;
rotary_encoder_t *encoder = user_ctx->encoder;
int cur_pulse_count = encoder->get_counter_value(encoder);
int delta = cur_pulse_count - last_pulse_count;
last_pulse_count = cur_pulse_count;
xQueueSendFromISR(user_ctx->pid_feedback_queue, &delta, &high_task_awoken);
return high_task_awoken == pdTRUE;
}
static void bdc_ctrl_task(void *arg)
{
float duty_cycle = 0;
motor_control_task_context_t *user_ctx = (motor_control_task_context_t *)arg;
while (1) {
xQueueReceive(user_ctx->pid_feedback_queue, &real_pulses, portMAX_DELAY);
float error = expect_pulses - real_pulses;
pid_compute(user_ctx->pid_ctrl, error, &duty_cycle);
brushed_motor_set_duty(duty_cycle);
}
}
static struct {
struct arg_dbl *kp;
struct arg_dbl *ki;
struct arg_dbl *kd;
struct arg_end *end;
} pid_ctrl_args;
static int do_pid_ctrl_cmd(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&pid_ctrl_args);
if (nerrors != 0) {
arg_print_errors(stderr, pid_ctrl_args.end, argv[0]);
return 0;
}
if (pid_ctrl_args.kp->count) {
pid_runtime_param.kp = pid_ctrl_args.kp->dval[0];
}
if (pid_ctrl_args.ki->count) {
pid_runtime_param.ki = pid_ctrl_args.ki->dval[0];
}
if (pid_ctrl_args.kd->count) {
pid_runtime_param.kd = pid_ctrl_args.kd->dval[0];
}
pid_need_update = true;
return 0;
}
static void register_pid_console_command(void)
{
pid_ctrl_args.kp = arg_dbl0("p", NULL, "<kp>", "Set Kp value of PID");
pid_ctrl_args.ki = arg_dbl0("i", NULL, "<ki>", "Set Ki value of PID");
pid_ctrl_args.kd = arg_dbl0("d", NULL, "<kd>", "Set Kd value of PID");
pid_ctrl_args.end = arg_end(2);
const esp_console_cmd_t pid_ctrl_cmd = {
.command = "pid",
.help = "Set PID parameters",
.hint = NULL,
.func = &do_pid_ctrl_cmd,
.argtable = &pid_ctrl_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&pid_ctrl_cmd));
}
void app_main(void)
{
QueueHandle_t pid_fb_queue = xQueueCreate(BDC_PID_FEEDBACK_QUEUE_LEN, sizeof(int));
assert(pid_fb_queue);
printf("configure mcpwm gpio\r\n");
ESP_ERROR_CHECK(mcpwm_gpio_init(BDC_MCPWM_UNIT, MCPWM0A, BDC_MCPWM_GENA_GPIO_NUM));
ESP_ERROR_CHECK(mcpwm_gpio_init(BDC_MCPWM_UNIT, MCPWM0B, BDC_MCPWM_GENB_GPIO_NUM));
printf("init mcpwm driver\n");
mcpwm_config_t pwm_config = {
.frequency = BDC_MCPWM_FREQ_HZ,
.cmpr_a = 0,
.cmpr_b = 0,
.counter_mode = MCPWM_UP_COUNTER,
.duty_mode = MCPWM_DUTY_MODE_0,
};
ESP_ERROR_CHECK(mcpwm_init(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, &pwm_config));
printf("init and start rotary encoder\r\n");
rotary_encoder_config_t config = {
.dev = (rotary_encoder_dev_t)BDC_ENCODER_PCNT_UNIT,
.phase_a_gpio_num = BDC_ENCODER_PHASEA_GPIO_NUM,
.phase_b_gpio_num = BDC_ENCODER_PHASEB_GPIO_NUM,
};
rotary_encoder_t *speed_encoder = NULL;
ESP_ERROR_CHECK(rotary_encoder_new_ec11(&config, &speed_encoder));
ESP_ERROR_CHECK(speed_encoder->set_glitch_filter(speed_encoder, 1));
ESP_ERROR_CHECK(speed_encoder->start(speed_encoder));
printf("init PID control block\r\n");
pid_ctrl_block_handle_t pid_ctrl;
pid_ctrl_config_t pid_config = {
.init_param = pid_runtime_param,
};
ESP_ERROR_CHECK(pid_new_control_block(&pid_config, &pid_ctrl));
printf("init motor control timer\r\n");
gptimer_handle_t gptimer;
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_APB,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1000000, // 1MHz, 1 tick = 1us
};
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
printf("create motor control task\r\n");
static motor_control_task_context_t my_ctrl_task_ctx = {};
my_ctrl_task_ctx.pid_feedback_queue = pid_fb_queue;
my_ctrl_task_ctx.pid_ctrl = pid_ctrl;
xTaskCreate(bdc_ctrl_task, "bdc_ctrl_task", 4096, &my_ctrl_task_ctx, 5, NULL);
printf("start motor control timer\r\n");
static motor_control_timer_context_t my_timer_ctx = {};
my_timer_ctx.pid_feedback_queue = pid_fb_queue;
my_timer_ctx.encoder = speed_encoder;
gptimer_event_callbacks_t cbs = {
.on_alarm = motor_ctrl_timer_cb,
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, &my_timer_ctx));
gptimer_alarm_config_t alarm_config = {
.reload_count = 0,
.alarm_count = BDC_PID_CALCULATION_PERIOD_US,
.flags.auto_reload_on_alarm = true,
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
ESP_ERROR_CHECK(gptimer_start(gptimer));
printf("install console command line\r\n");
esp_console_repl_t *repl = NULL;
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
repl_config.prompt = "dc-motor>";
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
register_pid_console_command();
ESP_ERROR_CHECK(esp_console_start_repl(repl));
while (1) {
vTaskDelay(pdMS_TO_TICKS(100));
// the following logging format is according to the requirement of serial-studio
// also see the parser mapping file `serial-studio-proto-map.json` in the project folder
#if SERIAL_STUDIO_DEBUG
printf("/*%d*/\r\n", real_pulses);
#endif
if (pid_need_update) {
pid_update_parameters(pid_ctrl, &pid_runtime_param);
pid_need_update = false;
}
}
}

View File

@@ -0,0 +1,22 @@
{
"fe": "*/",
"fs": "/*",
"g": [
{
"d": [
{
"g": true,
"max": 100,
"min": 0,
"t": "pulses within 10ms",
"u": "",
"v": "%1",
"w": ""
}
],
"t": "Encoder Feedback",
}
],
"s": ",",
"t": "Brushed DC Motor Speed"
}

View File

@@ -1,246 +0,0 @@
| Supported Targets | ESP32 | ESP32-S3 |
| ----------------- | ----- | -------- |
# MCPWM Brushed DC Motor Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example mainly illustrates how to drive a brushed DC motor by generating two specific PWM signals. This example assumes an [L298N](https://www.st.com/content/st_com/en/products/motor-drivers/brushed-dc-motor-drivers/l298.html) H-bridge driver is used to provide the needed voltage and current for brushed DC motor. This example also implements a motor control command console such that users can configure and control the motors at run time using console commands.
## How to Use Example
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
### Hardware Required
* A development board with any Espressif SoC which features MCPWM and PCNT peripheral (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A USB cable for Power supply and programming
* A separate 12V power supply for brushed DC motor and H-bridge (the voltage depends on the motor model used in the example)
* A motor driving board to transfer pwm signal into driving signal
* A brushed DC motor, e.g. [25GA370](http://www.tronsunmotor.com/data/upload/file/201807/e03b98802b5c5390d6570939def525ba.pdf)
* A quadrature encoder to detect speed
Connection :
```
Power(12V)
|
v
+----------------+ +--------------------+
| | | H-Bridge |
| GND +------------>| | +--------------+
| | | | | |
| GPIO15 +----PWM0A--->| IN_A OUT_A +----->| Brushed |
| | | | | DC |
| GPIO16 +----PWM0B--->| IN_A OUT_B +----->| Motor |
| | | | | |
| ESP | +--------------------+ | |
| | +------+-------+
| | |
| | +--------------------+ |
| VCC3.3 +------------>| Encoder | |
| | | | |
| GND +------------>| |<------------+
| | | |
| GPIO18 |<---PhaseA---+ C1 |
| | | |
| GPIO19 |<---PhaseB---+ C2 |
| | | |
+----------------+ +--------------------+
```
NOTE: If some other GPIO pins (e.g., 13/14) are chosen as the PCNT encoder pins, flashing might fail while the wires are connected. If this occurs, please try disconnecting the power supply of the encoder while flashing.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
## Example Output
Run the example, you will see the following output log:
```bash
...
Testing brushed motor with PID...
initializing mcpwm gpio...
Configuring Initial Parameters of mcpwm...
Type 'help' to get the list of commands.
Use UP/DOWN arrows to navigate through command history.
Press TAB when typing command name to auto-complete.
=================================================================
| Example of Motor Control |
| |
| 1. Try 'help', check all supported commands |
| 2. Try 'config' to set control period or pwm frequency |
| 3. Try 'pid' to configure pid paremeters |
| 4. Try 'expt' to set expectation value and mode |
| 5. Try 'motor' to start motor in several seconds or stop it |
| |
=================================================================
Default configuration are shown as follows.
You can input 'config -s' to check current status.
-----------------------------------------------------------------
Current Configuration Status
Configuration
Period = 10 ms PID = enabled
PID - Increment
Kp = 0.800 Ki = 0.000 Kd = 0.100
Expectation - Triangle
init = 30.000 max = 50.000 min = -50.000 pace = 1.000
MCPWM
Frequency = 1000 Hz
Motor
Running seconds = 10
-----------------------------------------------------------------
mcpwm-motor>
```
### Check all supported commands and their usages
* Command: `help`
```bash
mcpwm-motor> help
help
Print the list of registered commands
config config -s
Enable or disable PID and set motor control period
--pid=<y|n> Enable or disable PID algorithm
-T, --period=<ms> Set motor control period
-s, --show Show current configurations
expt expt -i <duty> -m <fixed/tri/rect> -p <double> --max <duty> --min -50<duty>
Set initial value, limitation and wave mode of expectation. Both dynamic and
static mode are available
--max=<duty> Max limitation for dynamic expectation
--min=<duty> Min limitation for dynamic expectation
-p, --pace=<double> The increasing pace of expectation every 50ms
-i, --init=<duty> Initial expectation. Usually between -100~100
-m, --mode=<fixed/tri/rect> Select static or dynamic expectation wave mode. 'fixed' for static, 'tri' for triangle, 'rect' for rectangle
pid pid -p <double> -i <double> -d <double> -t <loc/inc>
Set parameters and type for PID algorithm
-p, --kp=<double> Set Kp value for PID
-i, --ki=<double> Set Ki value for PID
-d, --kd=<double> Set Kd value for PID
-t, --type=<loc/inc> Select locational PID or incremental PID
motor motor -u 10
Start or stop the motor
-u, --start=<seconds> Set running seconds for motor, set '0' to keep motor running
-d, --stop Stop the motor
```
### Check status
* Command: `config -s`
```bash
mcpwm-motor> config -s
-----------------------------------------------------------------
Current Configuration Status
Configuration
Period = 10 ms PID = enabled
PID - Increment
Kp = 0.800 Ki = 0.000 Kd = 0.100
Expectation - Triangle
init = 30.000 max = 50.000 min = -50.000 pace = -1.000
MCPWM
Frequency = 1000 Hz
Motor
Running seconds = 10
-----------------------------------------------------------------
```
### Enable or disable PID
* Command: `config --pid <y/n>`
* 'y' - enable PID
* 'n' - disable PID
```bash
mcpwm-motor> config --pid n
config: pid disabled
mcpwm-motor> config --pid y
config: pid enabled
```
### Set PID parameters
* Command: `pid -p <double> -i <double> -d <double> -t <loc/inc>`
* 'p' - proportion value
* 'i' - integral value
* 'd' - differential value
* 't' - PID calculation type (locational or incremental).
```bash
mcpwm-motor> pid -p 0.8 -i 0.02 -d 0.1 -t inc
pid: kp = 0.800
pid: ki = 0.020
pid: kd = 0.100
pid: type = increment
```
### Set expectation parameters
* Command: `expt -i <duty> -m <fixed/tri/rect> -p <double> --max <duty> --min <duty>`
* 'i' - initial duty if you set mode 'fixed'
* 'm' - expectation mode. 'fixed' means the expectation will never change, 'tri' means the expectation will changes with trigonometric wave, 'rect' means the expectation will changes with rectangular wave
* 'p' - the setp size of expectation changed in every 50ms, it can adjust the expectation changing speed
* 'max' - the maximum limitation of expectation
* 'min' - the minimum limitation of expectation
```bash
mcpwm-motor> expt -i 40 -m rect -p 1.5 --max 80 --min -60
expt: init = 40.000
expt: max = 80.000
expt: min = -60.000
expt: pace = 1.500
expt: mode = rectangle
```
### Start or stop motor
* Command: `motor -u <sec>`
* Command: `motor -d`
* 'u' - start the motor in <sec> seconds, if <sec> is 0, the motor won't stop until 'motor -d' is inputed
* 'd' - stop the motor right now
```bash
mcpwm-motor> motor -u 10
motor: motor starts to run in 10 seconds
mcpwm-motor> 1
2
3
4
5
6
7
8
9
10
Time up: motor stoped
```
## Troubleshooting
* Make sure your ESP board and H-bridge module have been connected to the same GND panel.
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@@ -1,5 +0,0 @@
set(COMPONENT_SRCS "motor_ctrl_timer.c")
idf_component_register(SRCS "${COMPONENT_SRCS}"
INCLUDE_DIRS .
PRIV_REQUIRES "driver")

View File

@@ -1,141 +0,0 @@
/* To set the control period for DC motor Timer
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "motor_ctrl_timer.h"
#include "esp_check.h"
#define MOTOR_CTRL_TIMER_DIVIDER (16) // Hardware timer clock divider
#define MOTOR_CTRL_TIMER_SCALE (TIMER_BASE_CLK / MOTOR_CTRL_TIMER_DIVIDER) // convert counter value to seconds
#define MOTOR_CONTROL_TIMER_GROUP TIMER_GROUP_0
#define MOTOR_CONTROL_TIMER_ID TIMER_0
static const char *TAG = "motor_ctrl_timer";
/**
* @brief Callback function of timer intterupt
*
* @param args The parameter transmited to callback function from timer_isr_callback_add. Args here is for timer_info.
* @return
* - True Do task yield at the end of ISR
* - False Not do task yield at the end of ISR
*/
static bool IRAM_ATTR motor_ctrl_timer_isr_callback(void *args)
{
BaseType_t high_task_awoken = pdFALSE;
motor_ctrl_timer_info_t *info = (motor_ctrl_timer_info_t *) args;
info->pulse_info.pulse_cnt = info->pulse_info.get_pulse_callback(info->pulse_info.callback_args);
/* Now just send the event data back to the main program task */
xQueueSendFromISR(info->timer_evt_que, info, &high_task_awoken);
return high_task_awoken == pdTRUE; // return whether we need to yield at the end of ISR
}
/**
* @brief Initialize the motor control timer
*
* @param timer_info the secondary pointer of motor_ctrl_timer_info_t
* @param evt_que timer event queue
* @param ctrl_period_ms motor control period
* @param pulse_info quadrature encoder pulse information
* @return
* - ESP_OK: Motor control timer initialized successfully
* - ESP_FAIL: motor control timer failed to initialize because of other errors
*/
esp_err_t motor_ctrl_new_timer(motor_ctrl_timer_info_t **timer_info,
QueueHandle_t evt_que,
unsigned int ctrl_period_ms,
pulse_info_t pulse_info)
{
esp_err_t ret = ESP_FAIL;
/* Select and initialize basic parameters of the timer */
timer_config_t config = {
.divider = MOTOR_CTRL_TIMER_DIVIDER,
.counter_dir = TIMER_COUNT_UP,
.counter_en = TIMER_PAUSE,
.alarm_en = TIMER_ALARM_EN,
.auto_reload = true,
}; // default clock source is APB
ret = timer_init(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID, &config);
ESP_RETURN_ON_ERROR(ret, TAG, "timer init failed\n");
/* Timer's counter will initially start from value below.
Since auto_reload is set, this value will be automatically reload on alarm */
timer_set_counter_value(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID, 0);
/* Configure the alarm value and the interrupt on alarm. */
timer_set_alarm_value(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID, ctrl_period_ms * MOTOR_CTRL_TIMER_SCALE / 1000);
timer_enable_intr(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID);
/* Check the pointers */
ESP_GOTO_ON_FALSE(evt_que, ESP_ERR_INVALID_ARG, err, TAG, "timer event queue handler is NULL\n");
ESP_GOTO_ON_FALSE(timer_info, ESP_ERR_INVALID_ARG, err, TAG, "timer info structure pointer is NULL\n");
/* Alloc and config the infomation structure for this file */
*timer_info = calloc(1, sizeof(motor_ctrl_timer_info_t));
ESP_GOTO_ON_FALSE(*timer_info, ESP_ERR_NO_MEM, err, TAG, "timer_info calloc failed\n");
(*timer_info)->timer_group = MOTOR_CONTROL_TIMER_GROUP;
(*timer_info)->timer_idx = MOTOR_CONTROL_TIMER_ID;
(*timer_info)->timer_evt_que = evt_que;
(*timer_info)->ctrl_period_ms = ctrl_period_ms;
(*timer_info)->pulse_info = pulse_info;
timer_isr_callback_add(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID, motor_ctrl_timer_isr_callback, *timer_info, 0);
return ret;
err:
timer_deinit(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID);
return ret;
}
/**
* @brief Set timer alarm period
*
* @param period Timer alarm period
* @return
* - void
*/
void motor_ctrl_timer_set_period(unsigned int period)
{
timer_set_alarm_value(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID, period * MOTOR_CTRL_TIMER_SCALE / 1000);
}
/**
* @brief Start the timer
*/
void motor_ctrl_timer_start(void)
{
/* start the timer */
timer_start(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID);
}
/**
* @brief Pause the timer and clear the counting value
*/
void motor_ctrl_timer_stop(void)
{
/* stop the timer */
timer_pause(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID);
timer_set_counter_value(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID, 0);
}
/**
* @brief Deinitialize the timer
*
* @param timer_info the secondary pointer of motor_ctrl_timer_info_t, the memory will be freed
*/
void motor_ctrl_timer_deinit(motor_ctrl_timer_info_t **timer_info)
{
if (*timer_info != NULL) {
timer_deinit(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID);
free(*timer_info);
*timer_info = NULL;
}
}

View File

@@ -1,78 +0,0 @@
/* To set the control period for DC motor Timer
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "driver/timer.h"
typedef struct {
int (*get_pulse_callback)(void *);
void *callback_args;
int pulse_cnt;
} pulse_info_t;
typedef struct {
timer_group_t timer_group; /* Timer Group number */
timer_idx_t timer_idx; /* Timer ID */
unsigned int ctrl_period_ms; /* Motor control period, unit in ms */
QueueHandle_t timer_evt_que; /* The queue of timer events */
pulse_info_t pulse_info;
} motor_ctrl_timer_info_t;
/**
* @brief Initialize the motor control timer
*
* @param timer_info the secondary pointer of motor_ctrl_timer_info_t
* @param evt_que timer event queue
* @param ctrl_period_ms motor control period
* @param pulse_info quadrature encoder pulse information
* @return
* - ESP_OK: Motor control timer initialized successfully
* - ESP_FAIL: motor control timer failed to initialize because of other errors
*/
esp_err_t motor_ctrl_new_timer(motor_ctrl_timer_info_t **timer_info,
QueueHandle_t evt_que,
unsigned int ctrl_period_ms,
pulse_info_t pulse_info);
/**
* @brief Set timer alarm period
*
* @param period Timer alarm period
*/
void motor_ctrl_timer_set_period(unsigned int period);
/**
* @brief Start the timer
*/
void motor_ctrl_timer_start(void);
/**
* @brief Pause the timer and clear the counting value
*/
void motor_ctrl_timer_stop(void);
/**
* @brief Deinitialize the timer
*
* @param timer_info the secondary pointer of motor_ctrl_timer_info_t, the memory will be freed
*/
void motor_ctrl_timer_deinit(motor_ctrl_timer_info_t **timer_info);
#ifdef __cplusplus
}
#endif

View File

@@ -1,5 +0,0 @@
set(COMPONENT_SRCS "mcpwm_brushed_dc_control_example.c"
"cmd_mcpwm_motor.c")
idf_component_register(SRCS "${COMPONENT_SRCS}"
INCLUDE_DIRS "./")

View File

@@ -1,308 +0,0 @@
/* cmd_mcpwm_motor.h
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "argtable3/argtable3.h"
#include "esp_console.h"
#include "esp_log.h"
#include "mcpwm_brushed_dc_control_example.h"
#define MOTOR_CTRL_CMD_CHECK(ins) if(arg_parse(argc, argv, (void **)&ins)){ \
arg_print_errors(stderr, ins.end, argv[0]); \
return 0;}
static mcpwm_motor_control_t *mc;
extern SemaphoreHandle_t g_motor_mux;
static struct {
struct arg_str *pid_flag;
struct arg_int *period;
struct arg_lit *show;
struct arg_end *end;
} motor_ctrl_config_args;
static struct {
struct arg_dbl *max;
struct arg_dbl *min;
struct arg_dbl *pace;
struct arg_dbl *init;
struct arg_str *mode;
struct arg_end *end;
} motor_ctrl_expt_args;
static struct {
struct arg_dbl *kp;
struct arg_dbl *ki;
struct arg_dbl *kd;
struct arg_str *type;
struct arg_end *end;
} motor_ctrl_pid_args;
static struct {
struct arg_int *start;
struct arg_lit *stop;
struct arg_end *end;
} motor_ctrl_motor_args;
static void print_current_status(void)
{
printf("\n -----------------------------------------------------------------\n");
printf(" Current Configuration Status \n\n");
printf(" Configuration\n Period = %d ms\tPID = %s\n\n",
mc->cfg.ctrl_period, mc->cfg.pid_enable ? "enabled" : "disabled");
printf(" PID - %s\n Kp = %.3f\tKi = %.3f\tKd = %.3f\n\n",
(mc->pid_param.cal_type == PID_CAL_TYPE_POSITIONAL) ? "Location" : "Increment",
mc->pid_param.kp, mc->pid_param.ki, mc->pid_param.kd);
printf(" Expectation - %s\n init = %.3f\tmax = %.3f\tmin = %.3f\tpace = %.3f\n\n",
mc->cfg.expt_mode ? (mc->cfg.expt_mode == MOTOR_CTRL_MODE_TRIANGLE ? "Triangle" : "Rectangle") : "Fixed",
mc->cfg.expt_init, mc->cfg.expt_max, mc->cfg.expt_min, mc->cfg.expt_pace);
printf(" MCPWM\n Frequency = %d Hz\n\n", mc->cfg.pwm_freq);
printf(" Motor\n Running seconds = %d\n", mc->cfg.running_sec);
printf(" -----------------------------------------------------------------\n\n");
}
static int do_motor_ctrl_config_cmd(int argc, char **argv)
{
MOTOR_CTRL_CMD_CHECK(motor_ctrl_config_args);
xSemaphoreTake(g_motor_mux, portMAX_DELAY);
if (motor_ctrl_config_args.pid_flag->count) {
if (!strcmp(*motor_ctrl_config_args.pid_flag->sval, "n") ||
!strcmp(*motor_ctrl_config_args.pid_flag->sval, "no")) {
mc->cfg.pid_enable = false;
printf("config: pid disabled\n");
} else {
mc->cfg.pid_enable = true;
printf("config: pid enabled\n");
}
}
if (motor_ctrl_config_args.period->count) {
mc->cfg.ctrl_period = motor_ctrl_config_args.period->ival[0];
motor_ctrl_timer_set_period(mc->cfg.ctrl_period);
printf("config: control period = mc->cfg.ctrl_period\n");
}
if (motor_ctrl_config_args.show->count) {
print_current_status();
}
xSemaphoreGive(g_motor_mux);
return 0;
}
static int do_motor_ctrl_expt_cmd(int argc, char **argv)
{
MOTOR_CTRL_CMD_CHECK(motor_ctrl_expt_args);
xSemaphoreTake(g_motor_mux, portMAX_DELAY);
if (motor_ctrl_expt_args.init->count) {
mc->cfg.expt_init = motor_ctrl_expt_args.init->dval[0];
printf("expt: init = %.3f\n", mc->cfg.expt_init);
}
if (motor_ctrl_expt_args.max->count) {
mc->cfg.expt_max = motor_ctrl_expt_args.max->dval[0];
printf("expt: max = %.3f\n", mc->cfg.expt_max);
}
if (motor_ctrl_expt_args.min->count) {
mc->cfg.expt_min = motor_ctrl_expt_args.min->dval[0];
printf("expt: min = %.3f\n", mc->cfg.expt_min);
}
if (motor_ctrl_expt_args.pace->count) {
mc->cfg.expt_pace = motor_ctrl_expt_args.pace->dval[0];
printf("expt: pace = %.3f\n", mc->cfg.expt_pace);
}
if (motor_ctrl_expt_args.mode->count) {
if (!strcmp(*motor_ctrl_expt_args.mode->sval, "fixed")) {
mc->cfg.expt_mode = MOTOR_CTRL_MODE_FIXED;
printf("expt: mode = fixed\n");
} else if (!strcmp(*motor_ctrl_expt_args.mode->sval, "tri")) {
mc->cfg.expt_mode = MOTOR_CTRL_MODE_TRIANGLE;
printf("expt: mode = triangle\n");
} else if (!strcmp(*motor_ctrl_expt_args.mode->sval, "rect")) {
mc->cfg.expt_mode = MOTOR_CTRL_MODE_RECTANGLE;
printf("expt: mode = rectangle\n");
} else {
mc->cfg.expt_mode = MOTOR_CTRL_MODE_TRIANGLE;
printf("expt: mode = triangle\n");
}
}
xSemaphoreGive(g_motor_mux);
return 0;
}
static int do_motor_ctrl_pid_cmd(int argc, char **argv)
{
int ret = 0;
MOTOR_CTRL_CMD_CHECK(motor_ctrl_pid_args);
xSemaphoreTake(g_motor_mux, portMAX_DELAY);
if (motor_ctrl_pid_args.kp->count) {
mc->pid_param.kp = motor_ctrl_pid_args.kp->dval[0];
printf("pid: kp = %.3f\n", mc->pid_param.kp);
}
if (motor_ctrl_pid_args.ki->count) {
mc->pid_param.ki = motor_ctrl_pid_args.ki->dval[0];
printf("pid: ki = %.3f\n", mc->pid_param.ki);
}
if (motor_ctrl_pid_args.kd->count) {
mc->pid_param.kd = motor_ctrl_pid_args.kd->dval[0];
printf("pid: kd = %.3f\n", mc->pid_param.kd);
}
if (motor_ctrl_pid_args.type->count) {
if (!strcmp(motor_ctrl_pid_args.type->sval[0], "loc")) {
mc->pid_param.cal_type = PID_CAL_TYPE_POSITIONAL;
printf("pid: type = positional\n");
} else if (!strcmp(motor_ctrl_pid_args.type->sval[0], "inc")) {
mc->pid_param.cal_type = PID_CAL_TYPE_INCREMENTAL;
printf("pid: type = incremental\n");
} else {
printf("Invalid pid type:%s\n", motor_ctrl_pid_args.type->sval[0]);
ret = 1;
}
}
pid_update_parameters(mc->pid, &mc->pid_param);
xSemaphoreGive(g_motor_mux);
return ret;
}
static int do_motor_ctrl_motor_cmd(int argc, char **argv)
{
MOTOR_CTRL_CMD_CHECK(motor_ctrl_motor_args);
xSemaphoreTake(g_motor_mux, portMAX_DELAY);
if (motor_ctrl_motor_args.start->count) {
mc->cfg.running_sec = motor_ctrl_motor_args.start->ival[0];
// Start the motor
brushed_motor_start(mc);
mc->cfg.running_sec ?
printf("motor: motor starts to run in %d seconds\n", mc->cfg.running_sec) :
printf("motor: motor starts to run, input 'motor -d' to stop it\n");
}
if (motor_ctrl_motor_args.stop->count) {
// Stop the motor
brushed_motor_stop(mc);
printf("motor: motor stoped\n");
}
xSemaphoreGive(g_motor_mux);
return 0;
}
static void register_motor_ctrl_config(void)
{
motor_ctrl_config_args.pid_flag = arg_str0(NULL, "pid", "<y|n>", "Enable or disable PID algorithm");
motor_ctrl_config_args.period = arg_int0("T", "period", "<ms>", "Set motor control period");
motor_ctrl_config_args.show = arg_lit0("s", "show", "Show current configurations");
motor_ctrl_config_args.end = arg_end(2);
const esp_console_cmd_t motor_ctrl_cfg_cmd = {
.command = "config",
.help = "Enable or disable PID and set motor control period",
.hint = "config -s",
.func = &do_motor_ctrl_config_cmd,
.argtable = &motor_ctrl_config_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&motor_ctrl_cfg_cmd));
}
static void register_motor_ctrl_expt(void)
{
motor_ctrl_expt_args.init = arg_dbl0("i", "init", "<duty>", "Initial expectation. Usually between -100~100");
motor_ctrl_expt_args.max = arg_dbl0(NULL, "max", "<duty>", "Max limitation for dynamic expectation");
motor_ctrl_expt_args.min = arg_dbl0(NULL, "min", "<duty>", "Min limitation for dynamic expectation");
motor_ctrl_expt_args.pace = arg_dbl0("p", "pace", "<double>", "The increasing pace of expectation every 50ms");
motor_ctrl_expt_args.mode = arg_str0("m", "mode", "<fixed/tri/rect>",
"Select static or dynamic expectation wave mode. 'fixed' for static, 'tri' for triangle, 'rect' for rectangle");
motor_ctrl_expt_args.end = arg_end(2);
const esp_console_cmd_t motor_ctrl_expt_cmd = {
.command = "expt",
.help = "Set initial value, limitation and wave mode of expectation. Both dynamic and static mode are available",
.hint = "expt -i <duty> -m <fixed/tri/rect> -p <double> --max <duty> --min <duty>",
.func = &do_motor_ctrl_expt_cmd,
.argtable = &motor_ctrl_expt_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&motor_ctrl_expt_cmd));
}
static void register_motor_ctrl_pid(void)
{
motor_ctrl_pid_args.kp = arg_dbl0("p", "kp", "<double>", "Set Kp value for PID");
motor_ctrl_pid_args.ki = arg_dbl0("i", "ki", "<double>", "Set Ki value for PID");
motor_ctrl_pid_args.kd = arg_dbl0("d", "kd", "<double>", "Set Kd value for PID");
motor_ctrl_pid_args.type = arg_str0("t", "type", "<loc/inc>", "Select locational PID or incremental PID");
motor_ctrl_pid_args.end = arg_end(2);
const esp_console_cmd_t motor_ctrl_pid_cmd = {
.command = "pid",
.help = "Set parameters and type for PID algorithm",
.hint = "pid -p <double> -i <double> -d <double> -t <loc/inc>",
.func = &do_motor_ctrl_pid_cmd,
.argtable = &motor_ctrl_pid_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&motor_ctrl_pid_cmd));
}
static void register_motor_ctrl_motor(void)
{
motor_ctrl_motor_args.start = arg_int0("u", "start", "<seconds>", "Set running seconds for motor, set '0' to keep motor running");
motor_ctrl_motor_args.stop = arg_lit0("d", "stop", "Stop the motor");
motor_ctrl_motor_args.end = arg_end(2);
const esp_console_cmd_t motor_ctrl_motor_cmd = {
.command = "motor",
.help = "Start or stop the motor",
.hint = "motor -u 10",
.func = &do_motor_ctrl_motor_cmd,
.argtable = &motor_ctrl_motor_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&motor_ctrl_motor_cmd));
}
void cmd_mcpwm_motor_init(mcpwm_motor_control_t *motor_ctrl)
{
mc = motor_ctrl;
esp_console_repl_t *repl = NULL;
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
repl_config.prompt = "mcpwm-motor>";
// install console REPL environment
#if CONFIG_ESP_CONSOLE_UART
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
#elif CONFIG_ESP_CONSOLE_USB_CDC
esp_console_dev_usb_cdc_config_t cdc_config = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_cdc(&cdc_config, &repl_config, &repl));
#endif
register_motor_ctrl_config();
register_motor_ctrl_expt();
register_motor_ctrl_pid();
register_motor_ctrl_motor();
printf("\n =================================================================\n");
printf(" | Example of Motor Control |\n");
printf(" | |\n");
printf(" | 1. Try 'help', check all supported commands |\n");
printf(" | 2. Try 'config' to set control period or pwm frequency |\n");
printf(" | 3. Try 'pid' to configure pid paremeters |\n");
printf(" | 4. Try 'expt' to set expectation value and mode |\n");
printf(" | 5. Try 'motor' to start motor in several seconds or stop it |\n");
printf(" | |\n");
printf(" =================================================================\n\n");
printf("Default configuration are shown as follows.\nYou can input 'config -s' to check current status.");
print_current_status();
// start console REPL
ESP_ERROR_CHECK(esp_console_start_repl(repl));
}

View File

@@ -1,310 +0,0 @@
/* brushed dc motor control example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/*
* This example will show you how to use MCPWM module to control brushed dc motor.
* This code is tested with L298 motor driver.
* User may need to make changes according to the motor driver they use.
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_attr.h"
#include "driver/mcpwm.h"
#include "soc/mcpwm_periph.h"
#include "driver/pcnt.h"
#include "mcpwm_brushed_dc_control_example.h"
#define MOTOR_CTRL_MCPWM_UNIT MCPWM_UNIT_0
#define MOTOR_CTRL_MCPWM_TIMER MCPWM_TIMER_0
/* The global infomation structure */
static mcpwm_motor_control_t motor_ctrl;
SemaphoreHandle_t g_motor_mux;
/**
* @brief Initialize the gpio as mcpwm output
*/
static void mcpwm_example_gpio_initialize(void)
{
printf("initializing mcpwm gpio...\n");
mcpwm_gpio_init(MOTOR_CTRL_MCPWM_UNIT, MCPWM0A, GPIO_PWM0A_OUT);
mcpwm_gpio_init(MOTOR_CTRL_MCPWM_UNIT, MCPWM0B, GPIO_PWM0B_OUT);
}
/**
* @brief set motor moves speed and direction with duty cycle = duty %
*/
void brushed_motor_set_duty(float duty_cycle)
{
/* motor moves in forward direction, with duty cycle = duty % */
if (duty_cycle > 0) {
mcpwm_set_signal_low(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, MCPWM_OPR_A);
mcpwm_set_duty(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, MCPWM_OPR_B, duty_cycle);
mcpwm_set_duty_type(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, MCPWM_OPR_B, MCPWM_DUTY_MODE_0); //call this each time, if operator was previously in low/high state
}
/* motor moves in backward direction, with duty cycle = -duty % */
else {
mcpwm_set_signal_low(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, MCPWM_OPR_B);
mcpwm_set_duty(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, MCPWM_OPR_A, -duty_cycle);
mcpwm_set_duty_type(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, MCPWM_OPR_A, MCPWM_DUTY_MODE_0); //call this each time, if operator was previously in low/high state
}
}
/**
* @brief start motor
*
* @param mc mcpwm_motor_control_t pointer
*/
void brushed_motor_start(mcpwm_motor_control_t *mc)
{
motor_ctrl_timer_start();
mc->sec_cnt = 0;
mc->start_flag = true;
}
/**
* @brief stop motor
*
* @param mc mcpwm_motor_control_t pointer
*/
void brushed_motor_stop(mcpwm_motor_control_t *mc)
{
mc->expt = 0;
mc->sec_cnt = 0;
mc->start_flag = false;
motor_ctrl_timer_stop();
brushed_motor_set_duty(0);
}
/**
* @brief The callback function of timer interrupt
* @note This callback is called by timer interrupt callback. It need to offer the PCNT pulse in one control period for PID calculation
* @param args the rotary_encoder_t pointer, it is given by timer interrupt callback
* @return
* - int: the PCNT pulse in one control period
*/
static int pcnt_get_pulse_callback(void *args)
{
/* Record the last count value */
static unsigned int last_pulse = 0;
/* Get the encoder from args */
rotary_encoder_t *encoder = (rotary_encoder_t *)args;
/* Get the value current count value */
unsigned int temp = encoder->get_counter_value(encoder);
/* Calculate the pulse count in one control period */
unsigned int ret = temp - last_pulse;
/* Update last count value */
last_pulse = temp;
return (int)ret;
}
/**
* @brief Initialize the PCNT rotaty encoder
*/
static void motor_ctrl_default_init(void)
{
motor_ctrl.cfg.pid_enable = true;
motor_ctrl.pid_param.kp = 0.8;
motor_ctrl.pid_param.ki = 0.0;
motor_ctrl.pid_param.kd = 0.1;
motor_ctrl.pid_param.cal_type = PID_CAL_TYPE_INCREMENTAL;
motor_ctrl.pid_param.max_output = 100;
motor_ctrl.pid_param.min_output = -100;
motor_ctrl.pid_param.max_integral = 1000;
motor_ctrl.pid_param.min_integral = -1000;
motor_ctrl.cfg.expt_init = 30;
motor_ctrl.cfg.expt_mode = MOTOR_CTRL_MODE_TRIANGLE;
motor_ctrl.cfg.expt_max = 50;
motor_ctrl.cfg.expt_min = -50;
motor_ctrl.cfg.expt_pace = 1.0;
motor_ctrl.cfg.pwm_freq = 1000;
motor_ctrl.cfg.running_sec = 10;
motor_ctrl.cfg.ctrl_period = 10;
}
/**
* @brief Initialize the PCNT rotaty encoder
*/
static void motor_ctrl_pcnt_rotary_encoder_init(void)
{
/* Rotary encoder underlying device is represented by a PCNT unit in this example */
uint32_t pcnt_unit = 0;
/* Create rotary encoder instance */
rotary_encoder_config_t config = ROTARY_ENCODER_DEFAULT_CONFIG(
(rotary_encoder_dev_t)pcnt_unit,
GPIO_PCNT_PINA, GPIO_PCNT_PINB);
ESP_ERROR_CHECK(rotary_encoder_new_ec11(&config, &motor_ctrl.encoder));
/* Filter out glitch (1us) */
ESP_ERROR_CHECK(motor_ctrl.encoder->set_glitch_filter(motor_ctrl.encoder, 1));
/* Start encoder */
ESP_ERROR_CHECK(motor_ctrl.encoder->start(motor_ctrl.encoder));
pcnt_counter_clear((pcnt_unit_t)pcnt_unit);
}
/**
* @brief Initialize the MCPWM
*/
static void motor_ctrl_mcpwm_init(void)
{
/* mcpwm gpio initialization */
mcpwm_example_gpio_initialize();
/* initial mcpwm configuration */
printf("Configuring Initial Parameters of mcpwm...\n");
mcpwm_config_t pwm_config;
pwm_config.frequency = motor_ctrl.cfg.pwm_freq; //frequency = 1kHz,
pwm_config.cmpr_a = 0; //initial duty cycle of PWMxA = 0
pwm_config.cmpr_b = 0; //initial duty cycle of PWMxb = 0
pwm_config.counter_mode = MCPWM_UP_COUNTER; //up counting mode
pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
mcpwm_init(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, &pwm_config); //Configure PWM0A & PWM0B with above settings
}
/**
* @brief Initialize the timer
*/
static void motor_ctrl_timer_init(void)
{
/* Initialize timer alarm event queue */
motor_ctrl.timer_evt_que = xQueueCreate(10, sizeof(motor_ctrl_timer_info_t));
/* Set PCNT rotary encoder handler and pulse getting callback function */
pulse_info_t pulse_info = {.callback_args = motor_ctrl.encoder,
.get_pulse_callback = pcnt_get_pulse_callback
};
motor_ctrl_new_timer(&motor_ctrl.timer_info, motor_ctrl.timer_evt_que, motor_ctrl.cfg.ctrl_period, pulse_info);
}
/**
* @brief the top initialization function in this example
*/
static void motor_ctrl_init_all(void)
{
/* 1. Set default configurations */
motor_ctrl_default_init();
/* 2.rotary encoder initialization */
motor_ctrl_pcnt_rotary_encoder_init();
/* 3.MCPWM initialization */
motor_ctrl_mcpwm_init();
/* 4.pid_ctrl initialization */
pid_ctrl_config_t pid_config = {
.init_param = motor_ctrl.pid_param,
};
pid_new_control_block(&pid_config, &motor_ctrl.pid);
/* 5.Timer initialization */
motor_ctrl_timer_init();
}
/**
* @brief Motor control thread
*
* @param arg Information pointer transmitted by task creating function
*/
static void mcpwm_brushed_motor_ctrl_thread(void *arg)
{
motor_ctrl_timer_info_t recv_info;
while (1) {
/* Wait for recieving information of timer interrupt from timer event queque */
xQueueReceive(motor_ctrl.timer_evt_que, &recv_info, portMAX_DELAY);
/* Get the pcnt pulse during one control period */
motor_ctrl.pulse_in_one_period = recv_info.pulse_info.pulse_cnt;
if (motor_ctrl.cfg.pid_enable) {
/* Calculate the output by PID algorithm according to the pulse. Pid_output here is the duty of MCPWM */
motor_ctrl.error = motor_ctrl.expt - motor_ctrl.pulse_in_one_period;
pid_compute(motor_ctrl.pid, motor_ctrl.error, &motor_ctrl.pid_output);
} else {
motor_ctrl.pid_output = motor_ctrl.expt;
}
/* Set the MCPWM duty */
brushed_motor_set_duty(motor_ctrl.pid_output);
}
}
/**
* @brief Motor control thread
*
* @param arg Information pointer transmitted by task creating function
*/
static void mcpwm_brushed_motor_expt_thread(void *arg)
{
float cnt = 0;
while (1) {
xSemaphoreTake(g_motor_mux, portMAX_DELAY);
switch (motor_ctrl.cfg.expt_mode) {
/* Static expectation */
case MOTOR_CTRL_MODE_FIXED:
motor_ctrl.expt = motor_ctrl.cfg.expt_init;
break;
/* Dynamic expectation: triangle wave */
case MOTOR_CTRL_MODE_TRIANGLE:
motor_ctrl.expt += motor_ctrl.cfg.expt_pace;
motor_ctrl.cfg.expt_pace = (motor_ctrl.expt > motor_ctrl.cfg.expt_max - 0.0001 ||
motor_ctrl.expt < motor_ctrl.cfg.expt_min + 0.0001) ?
- motor_ctrl.cfg.expt_pace : motor_ctrl.cfg.expt_pace;
break;
/* Dynamic expectation: rectangle wave */
case MOTOR_CTRL_MODE_RECTANGLE:
cnt += motor_ctrl.cfg.expt_pace;
if (cnt > motor_ctrl.cfg.expt_max - 0.0001) {
motor_ctrl.cfg.expt_pace = -motor_ctrl.cfg.expt_pace;
motor_ctrl.expt = motor_ctrl.cfg.expt_min;
}
if (cnt < motor_ctrl.cfg.expt_min - 0.0001) {
motor_ctrl.cfg.expt_pace = -motor_ctrl.cfg.expt_pace;
motor_ctrl.expt = motor_ctrl.cfg.expt_max;
}
break;
default:
motor_ctrl.expt = motor_ctrl.cfg.expt_init;
break;
}
xSemaphoreGive(g_motor_mux);
/* Motor automatic stop judgement */
if (motor_ctrl.start_flag) {
motor_ctrl.sec_cnt++;
/* Show the seconds count */
if ((motor_ctrl.sec_cnt + 1) % 20 == 0) {
printf("%d\n", (motor_ctrl.sec_cnt + 1) / 20);
}
/* Stop motor if time up */
if (motor_ctrl.sec_cnt > 20 * motor_ctrl.cfg.running_sec && motor_ctrl.cfg.running_sec != 0) {
brushed_motor_stop(&motor_ctrl);
printf("\nTime up: motor stoped\n");
}
}
/* Delay 50ms */
vTaskDelay(50 / portTICK_PERIOD_MS);
}
}
/**
* @brief The main entry of this example
*/
void app_main(void)
{
printf("Testing brushed motor with PID...\n");
/* Create semaphore */
g_motor_mux = xSemaphoreCreateMutex();
/* Initialize peripherals and modules */
motor_ctrl_init_all();
/* Initialize the console */
cmd_mcpwm_motor_init(&motor_ctrl);
/* Motor control thread */
xTaskCreate(mcpwm_brushed_motor_ctrl_thread, "mcpwm_brushed_motor_ctrl_thread", 4096, NULL, 3, NULL);
/* Motor expectation wave generate thread */
xTaskCreate(mcpwm_brushed_motor_expt_thread, "mcpwm_brushed_motor_expt_thread", 4096, NULL, 5, NULL);
}

View File

@@ -1,99 +0,0 @@
/* cmd_mcpwm_motor.h
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#include "rotary_encoder.h"
#include "motor_ctrl_timer.h"
#include "pid_ctrl.h"
#ifdef __cplusplus
extern "C" {
#endif
#define GPIO_PWM0A_OUT 15 //Set GPIO 15 as PWM0A
#define GPIO_PWM0B_OUT 16 //Set GPIO 16 as PWM0B
#define GPIO_PCNT_PINA 18 //Set GPIO 18 as phaseA/C1
#define GPIO_PCNT_PINB 19 //Set GPIO 19 as phaseB/C2
typedef enum {
MOTOR_CTRL_MODE_FIXED = 0,
MOTOR_CTRL_MODE_TRIANGLE,
MOTOR_CTRL_MODE_RECTANGLE
} expect_mode_t;
typedef struct {
/* Handles */
rotary_encoder_t *encoder; // PCNT rotary encoder handler
motor_ctrl_timer_info_t *timer_info; // Timer infomation handler
pid_ctrl_block_handle_t pid; // PID algoritm handler
pid_ctrl_parameter_t pid_param; // PID parameters
QueueHandle_t timer_evt_que; // Timer event queue handler
/* Control visualization */
int pulse_in_one_period; // PCNT pulse in one control period
float error; // The error between the expectation(expt) and actual value (pulse_in_one_period)
float expt; // The expectation
float pid_output; // PID algorithm output
/* Status */
unsigned int sec_cnt; // Seconds count
bool start_flag; // Motor start flag
/* Configurations */
struct {
/* PID configuration */
bool pid_enable; // PID enable flag
/* Expectation configuration */
float expt_init; // Initial expectation
float expt_max; // Max expectation in dynamic mode
float expt_min; // Min expectation in dynamic mode
float expt_pace; // The expection pace. It can change expectation wave period
expect_mode_t expt_mode; // Expectation wave mode (MOTOR_CTRL_EXPT_FIXED/MOTOR_CTRL_EXPT_TRIANGLE/MOTOR_CTRL_EXPT_RECTANGLE)
/* Other configurations */
unsigned int ctrl_period; // Control period
unsigned int pwm_freq; // MCPWM output frequency
unsigned int running_sec; // Motor running seconds
} cfg; // Configurations that should be initialized for this example
} mcpwm_motor_control_t;
/**
* @brief Set pwm duty to drive the motor
*
* @param duty_cycle PWM duty cycle (100~-100), the motor will go backward if the duty is set to a negative value
*/
void brushed_motor_set_duty(float duty_cycle);
/**
* @brief start motor
*
* @param mc mcpwm_motor_control_t pointer
*/
void brushed_motor_start(mcpwm_motor_control_t *mc);
/**
* @brief stop motor
*
* @param mc mcpwm_motor_control_t pointer
*/
void brushed_motor_stop(mcpwm_motor_control_t *mc);
/**
* @brief Initialize the motor control console
*
* @param motor_ctrl The top infomation struct of this example
*/
extern void cmd_mcpwm_motor_init(mcpwm_motor_control_t *motor_ctrl);
#ifdef __cplusplus
}
#endif

View File

@@ -14,7 +14,7 @@
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "driver/dac.h"
#include "driver/timer.h"
#include "driver/gptimer.h"
#include "esp_log.h"
/* The timer ISR has an execution time of 5.5 micro-seconds(us).
@@ -22,21 +22,14 @@
7 us is a safe interval that will not trigger the watchdog. No need to customize it.
*/
#define WITH_RELOAD 1
#define TIMER_INTR_US 7 // Execution time of each ISR interval in micro-seconds
#define TIMER_DIVIDER 16
#define POINT_ARR_LEN 200 // Length of points array
#define AMP_DAC 255 // Amplitude of DAC voltage. If it's more than 256 will causes dac_output_voltage() output 0.
#define VDD 3300 // VDD is 3.3V, 3300mV
#define CONST_PERIOD_2_PI 6.2832
#define SEC_TO_MICRO_SEC(x) ((x) / 1000 / 1000) // Convert second to micro-second
#define UNUSED_PARAM __attribute__((unused)) // A const period parameter which equals 2 * pai, used to calculate raw dac output value.
#define TIMER_TICKS (TIMER_BASE_CLK / TIMER_DIVIDER) // TIMER_BASE_CLK = APB_CLK = 80MHz
#define ALARM_VAL_US SEC_TO_MICRO_SEC(TIMER_INTR_US * TIMER_TICKS) // Alarm value in micro-seconds
#define OUTPUT_POINT_NUM (int)(1000000 / (TIMER_INTR_US * FREQ) + 0.5) // The number of output wave points.
#define DAC_CHAN CONFIG_EXAMPLE_DAC_CHANNEL // DAC_CHANNEL_1 (GPIO25) by default
#define FREQ CONFIG_EXAMPLE_WAVE_FREQUENCY // 3kHz by default
#define OUTPUT_POINT_NUM (int)(1000000 / (TIMER_INTR_US * FREQ) + 0.5) // The number of output wave points.
_Static_assert(OUTPUT_POINT_NUM <= POINT_ARR_LEN, "The CONFIG_EXAMPLE_WAVE_FREQUENCY is too low and using too long buffer.");
@@ -47,60 +40,33 @@ static const char *TAG = "wave_gen";
static int g_index = 0;
/* Timer interrupt service routine */
static void IRAM_ATTR timer0_ISR(void *ptr)
static bool IRAM_ATTR on_timer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);
int *head = (int*)ptr;
int *head = (int *)user_data;
/* DAC output ISR has an execution time of 4.4 us*/
if (g_index >= OUTPUT_POINT_NUM) g_index = 0;
if (g_index >= OUTPUT_POINT_NUM) {
g_index = 0;
}
dac_output_voltage(DAC_CHAN, *(head + g_index));
g_index++;
return false;
}
/* Timer group0 TIMER_0 initialization */
static void example_timer_init(int timer_idx, bool auto_reload)
static void prepare_data(int pnt_num)
{
esp_err_t ret;
timer_config_t config = {
.divider = TIMER_DIVIDER,
.counter_dir = TIMER_COUNT_UP,
.counter_en = TIMER_PAUSE,
.alarm_en = TIMER_ALARM_EN,
.intr_type = TIMER_INTR_LEVEL,
.auto_reload = auto_reload,
};
ret = timer_init(TIMER_GROUP_0, timer_idx, &config);
ESP_ERROR_CHECK(ret);
ret = timer_set_counter_value(TIMER_GROUP_0, timer_idx, 0x00000000ULL);
ESP_ERROR_CHECK(ret);
ret = timer_set_alarm_value(TIMER_GROUP_0, timer_idx, ALARM_VAL_US);
ESP_ERROR_CHECK(ret);
ret = timer_enable_intr(TIMER_GROUP_0, TIMER_0);
ESP_ERROR_CHECK(ret);
/* Register an ISR handler */
timer_isr_register(TIMER_GROUP_0, timer_idx, timer0_ISR, (void *)raw_val, 0, NULL);
}
static void prepare_data(int pnt_num)
{
timer_pause(TIMER_GROUP_0, TIMER_0);
for (int i = 0; i < pnt_num; i ++) {
#ifdef CONFIG_EXAMPLE_WAVEFORM_SINE
raw_val[i] = (int)((sin( i * CONST_PERIOD_2_PI / pnt_num) + 1) * (double)(AMP_DAC)/2 + 0.5);
#elif CONFIG_EXAMPLE_WAVEFORM_TRIANGLE
raw_val[i] = (i > (pnt_num/2)) ? (2 * AMP_DAC * (pnt_num - i) / pnt_num) : (2 * AMP_DAC * i / pnt_num);
#elif CONFIG_EXAMPLE_WAVEFORM_SAWTOOTH
raw_val[i] = (i == pnt_num) ? 0 : (i * AMP_DAC / pnt_num);
#elif CONFIG_EXAMPLE_WAVEFORM_SQUARE
raw_val[i] = (i < (pnt_num/2)) ? AMP_DAC : 0;
#endif
#ifdef CONFIG_EXAMPLE_WAVEFORM_SINE
raw_val[i] = (int)((sin( i * CONST_PERIOD_2_PI / pnt_num) + 1) * (double)(AMP_DAC) / 2 + 0.5);
#elif CONFIG_EXAMPLE_WAVEFORM_TRIANGLE
raw_val[i] = (i > (pnt_num / 2)) ? (2 * AMP_DAC * (pnt_num - i) / pnt_num) : (2 * AMP_DAC * i / pnt_num);
#elif CONFIG_EXAMPLE_WAVEFORM_SAWTOOTH
raw_val[i] = (i == pnt_num) ? 0 : (i * AMP_DAC / pnt_num);
#elif CONFIG_EXAMPLE_WAVEFORM_SQUARE
raw_val[i] = (i < (pnt_num / 2)) ? AMP_DAC : 0;
#endif
volt_val[i] = (int)(VDD * raw_val[i] / (float)AMP_DAC);
}
timer_start(TIMER_GROUP_0, TIMER_0);
}
static void log_info(void)
@@ -111,15 +77,15 @@ static void log_info(void)
} else {
ESP_LOGI(TAG, "GPIO:%d", GPIO_NUM_26);
}
#ifdef CONFIG_EXAMPLE_WAVEFORM_SINE
ESP_LOGI(TAG, "Waveform: SINE");
#elif CONFIG_EXAMPLE_WAVEFORM_TRIANGLE
ESP_LOGI(TAG, "Waveform: TRIANGLE");
#elif CONFIG_EXAMPLE_WAVEFORM_SAWTOOTH
ESP_LOGI(TAG, "Waveform: SAWTOOTH");
#elif CONFIG_EXAMPLE_WAVEFORM_SQUARE
ESP_LOGI(TAG, "Waveform: SQUARE");
#endif
#ifdef CONFIG_EXAMPLE_WAVEFORM_SINE
ESP_LOGI(TAG, "Waveform: SINE");
#elif CONFIG_EXAMPLE_WAVEFORM_TRIANGLE
ESP_LOGI(TAG, "Waveform: TRIANGLE");
#elif CONFIG_EXAMPLE_WAVEFORM_SAWTOOTH
ESP_LOGI(TAG, "Waveform: SAWTOOTH");
#elif CONFIG_EXAMPLE_WAVEFORM_SQUARE
ESP_LOGI(TAG, "Waveform: SQUARE");
#endif
ESP_LOGI(TAG, "Frequency(Hz): %d", FREQ);
ESP_LOGI(TAG, "Output points num: %d\n", OUTPUT_POINT_NUM);
@@ -127,23 +93,38 @@ static void log_info(void)
void app_main(void)
{
esp_err_t ret;
example_timer_init(TIMER_0, WITH_RELOAD);
ret = dac_output_enable(DAC_CHAN);
ESP_ERROR_CHECK(ret);
g_index = 0;
gptimer_handle_t gptimer = NULL;
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_APB,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1000000, // 1MHz, 1 tick = 1us
};
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
ESP_ERROR_CHECK(dac_output_enable(DAC_CHAN));
log_info();
g_index = 0;
prepare_data(OUTPUT_POINT_NUM);
while(1) {
gptimer_alarm_config_t alarm_config = {
.reload_count = 0,
.alarm_count = TIMER_INTR_US,
.flags.auto_reload_on_alarm = true,
};
gptimer_event_callbacks_t cbs = {
.on_alarm = on_timer_alarm_cb,
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, raw_val));
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
ESP_ERROR_CHECK(gptimer_start(gptimer));
while (1) {
vTaskDelay(10);
#if CONFIG_EXAMPLE_LOG_VOLTAGE
if (g_index < OUTPUT_POINT_NUM) {
ESP_LOGI(TAG, "Output voltage(mV): %d", volt_val[g_index]);
ESP_LOGD(TAG, "g_index: %d\n", g_index);
}
#endif
#if CONFIG_EXAMPLE_LOG_VOLTAGE
if (g_index < OUTPUT_POINT_NUM) {
ESP_LOGI(TAG, "Output voltage(mV): %d", volt_val[g_index]);
ESP_LOGD(TAG, "g_index: %d\n", g_index);
}
#endif
}
}