example: update stepper motor example with new rmt driver

This commit is contained in:
morris 2022-04-07 13:14:01 +08:00
parent b3c1480d9c
commit 0e19bc1463
17 changed files with 443 additions and 985 deletions

View File

@ -1,92 +0,0 @@
| Supported Targets | ESP32-S2 | ESP32-C3 | ESP32-S3 |
| ----------------- | -------- | -------- | -------- |
# RMT Transmit Loop Example -- Step Motor controller
(See the README.md file in the upper level 'examples' directory for more information about examples.)
RMT peripheral can send customized RMT items in a loop, which means we can use it to generate a configurable length of periodic signal, with accurate number of pulses.
This example will show how to control an A4988 based step motor driver to step accurately with simple APIs, based on the RMT loop feature. The example also implements a [Smoothstep](https://en.wikipedia.org/wiki/Smoothstep) feature which works out of the box.
## How to Use Example
### Hardware Required
* Recommend running this example on development board with SOC chip that support loop auto-stop feature by hardware (e.g. ESP32-S3)
* A USB cable for Power supply and programming
* A 4-wire (A+, A-, B+, B-) step motor
* An A4988 module
Connection :
```
+----------------+ +--------------------+ +--------------+
| | | A4988 | | 4-wire |
| GND +-------------+ GND | | Step |
| | | | | Motor |
| 5V +-------------+ VDD 1B +------+ A2 |
| | | | | |
| GPIO18 +------------>+ DIRECTION 1A +------+ A1 |
| | | | | |
| ESP GPIO17 +------------>+ STEP 2A +------+ B1 |
| | | | | |
| GPIO16 +------------>+ SLEEP 2B +------+ B2 |
| | | | +--------------+
| GPIO15 +------------>+ RESET VMOT +-------------------+
| | | | |
| GPIO7 +------------>+ MS3 GND +----------+ |
| | | | | |
| GPIO6 +------------>+ MS2 | | |
| | | | | |
| GPIO5 +------------>+ MS1 | +---+--------+-----+
| | | | | GND +12V |
| GPIO4 +------------>+ ENABLE | | POWER SUPPLY |
+----------------+ +--------------------+ +------------------+
```
IO mapping on ESP side can be changed in `step_motor_main.c`:
```c
// GPIO configuration
#define STEP_MOTOR_DIRECTION_PIN GPIO_NUM_18
#define STEP_MOTOR_STEP_PIN GPIO_NUM_17
#define STEP_MOTOR_SLEEP_PIN GPIO_NUM_16
#define STEP_MOTOR_RESET_PIN GPIO_NUM_15
#define STEP_MOTOR_MS3_PIN GPIO_NUM_7
#define STEP_MOTOR_MS2_PIN GPIO_NUM_6
#define STEP_MOTOR_MS1_PIN GPIO_NUM_5
#define STEP_MOTOR_ENABLE_PIN GPIO_NUM_4
```
### 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://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
I (344) step_motor: init
I (344) step_motor: set_step
I (1354) step_motor: step 10 @ 1000/s
I (2364) step_motor: step 100 @ 1000/s
I (3464) step_motor: step 1000 @ 1200/s
I (5294) step_motor: step 5000 @ 1400/s
I (9864) step_motor: smoothstep start 5000 steps @ 500~1400/s
I (14454) step_motor: smoothstep finish
I (15454) step_motor: continuous running for 5s
I (20454) step_motor: stop
I (21504) step_motor: deinit
```
Motor should move as output indicates.
## Troubleshooting
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,10 +0,0 @@
set(component_srcs "src/step_motor.c"
"src/step_motor_rmt.c"
"src/step_motor_driver_io_a4988.c"
)
idf_component_register(SRCS "${component_srcs}"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS ""
PRIV_REQUIRES "driver"
REQUIRES "")

View File

@ -1,134 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#pragma once
#include <stdbool.h>
#include "driver/rmt.h"
#include "hal/rmt_types.h"
#include "esp_err.h"
#include "step_motor_driver_io.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Type of step motor interface
*/
typedef struct step_motor_s step_motor_t;
typedef step_motor_t *step_motor_handle_t;
/**
* @brief Declaration of step motor interface
*
*/
struct step_motor_s {
esp_err_t (*init)(step_motor_t *handle);
esp_err_t (*deinit)(step_motor_t *handle);
esp_err_t (*step)(step_motor_t *handle, uint32_t n, uint32_t speed);
esp_err_t (*smooth_step)(step_motor_t *handle, uint32_t n, uint32_t speed_steps, uint32_t speed_min, uint32_t speed_max);
esp_err_t (*set_step)(step_motor_t *handle, uint16_t microstep, bool direction);
// TODO: other API like sleep, enable_output, reset
};
/**
* @brief Initialize step motor driver
*
* @param handle driver handle
* @return
* - ESP_OK: successfully initialized
* - ESP_ERR_INVALID_ARG: wrong parameter
*/
esp_err_t step_motor_init(step_motor_t *handle);
/**
* @brief Deinitialize driver
*
* @param handle driver handle
* @return
* - ESP_OK: Stop playing successfully
*/
esp_err_t step_motor_deinit(step_motor_t *handle);
/**
* @brief Move n small steps.
*
* @note Will block until finish if n is finite steps. But will immediately return if n is UINT32_MAX.
*
* @param handle driver handle
* @param n step count, UINT32_MAX for unlimited, 0 to stop
* @param speed steps per second
* @return
* - ESP_OK: Recycle memory successfully
*/
esp_err_t step_motor_step(step_motor_t *handle, uint32_t n, uint32_t speed);
/**
* @brief Move n small steps. Always blocking and take smooth arguments
*
* ^ speed (steps/s)
* | ********************* <---- speed_max
* | * | | *
* | * | | *
* | * | | *
* | * | | *
* | * speed | n-speed_steps*2 | speed *
* | * steps | | steps * <---- speed_min
* | | |
* +-------------------------------------------------------------------> timestamp (s)
*
* @param handle driver handle
* @param n steps
* @param speed_steps number of sample points during speed smoothing
* @param speed_min minimal speed, steps per seconds
* @param speed_max maximum speed, steps per seconds
* @note may consume lots of ram depending on speed_steps with current implementation (1000 will lead to 8kb of ram usage)
* @return
* - ESP_OK: Recycle memory successfully
*/
esp_err_t step_motor_smooth_step(step_motor_t *handle, uint32_t n, uint32_t speed_steps, uint32_t speed_min, uint32_t speed_max);
/**
* @brief Set microstep resolution
*
* @param handle driver handle
* @param step_config microstep resolution
* @param direction rotating direction
* @return
* - ESP_OK: Recycle memory successfully
*/
esp_err_t step_motor_set_step(step_motor_t *handle, uint16_t microstep, bool direction);
// TODO: move out of this header to rmt one (like step_motor_rmt.h)
/**
* @brief Create step motor instance based on RMT driver
*
* @param[in] io_driver step motor low part driver
* @param[out] ret_handle returned handle of step motor instance
* @return
* - ESP_OK: create step motor instance successfully
* - ESP_ERR_INVALID_ARG: wrong parameter
* - ESP_ERR_NO_MEM: no memory to allocate instance
*/
esp_err_t step_motor_create_rmt(step_motor_driver_io_t *io_driver, const rmt_config_t *rmt_conf, step_motor_handle_t *ret_handle);
/**
* @brief Delete step motor instance that previously created
*
* @param[in] handle step motor instance to be deleted
* @return
* - ESP_OK: create step motor instance successfully
* - ESP_ERR_INVALID_ARG: wrong parameter
*/
esp_err_t step_motor_delete_rmt(step_motor_handle_t handle);
#ifdef __cplusplus
}
#endif

View File

@ -1,75 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#pragma once
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct step_motor_driver_io_s step_motor_driver_io_t;
typedef step_motor_driver_io_t *step_motor_driver_io_handle_t;
typedef enum {
STEP_MOTOR_DIRECTION_NEGATIVE = 0, STEP_MOTOR_DIRECTION_POSITIVE
} step_direction;
/**
* @brief init low part of driver
* GPIO configuration, Bus initializing...
*/
typedef esp_err_t (*step_motor_driver_io_init)(step_motor_driver_io_t *handle);
/**
* @brief set rotation direction
*/
typedef esp_err_t (*step_motor_driver_io_set_direction)(step_motor_driver_io_t *handle, step_direction direction);
/**
* @brief enable/disable sleep mode if supported
*/
typedef esp_err_t (*step_motor_driver_io_enable_sleep)(step_motor_driver_io_t *handle, bool enabled);
/**
* @brief enable/disable output if supported
*/
typedef esp_err_t (*step_motor_driver_io_enable_output)(step_motor_driver_io_t *handle, bool enabled);
/**
* @brief set microstep configuration if supported.
* param microstep is treated as denominator. a input of 16 means 1/16 step
* should return ESP_ERR_NOT_SUPPORTED if not supported
*/
typedef esp_err_t (*step_motor_driver_io_set_microstep)(step_motor_driver_io_t *handle, uint16_t microstep);
/**
* @brief reset low part of driver
*/
typedef esp_err_t (*step_motor_driver_io_reset)(step_motor_driver_io_t *handle);
/**
* @brief deinit low part of driver
*/
typedef esp_err_t (*step_motor_driver_io_deinit)(step_motor_driver_io_t *handle);
/**
* @brief Driver IC specified control logic
*
* leave callback pointer NULL if action is not supported
*/
struct step_motor_driver_io_s {
step_motor_driver_io_init init; /*!< callback to init low part driver */
step_motor_driver_io_set_direction set_direction; /*!< callback to set rotate direction */
step_motor_driver_io_enable_sleep enable_sleep; /*!< callback to enable sleep mode */
step_motor_driver_io_enable_output enable_output; /*!< callback to enable output */
step_motor_driver_io_set_microstep set_microstep; /*!< callback to set microstep configuration */
bool step_triggered_edge; /*!< true if step is triggered by positive edge, otherwise false */
uint32_t pulse_low_period_us; /*!< minimum low level pulse width on step pin */
uint32_t pulse_high_period_us; /*!< minimum high level pulse width on step pin */
step_motor_driver_io_reset trigger_reset; /*!< callback to trigger a reset on low part driver */
step_motor_driver_io_deinit deinit; /*!< callback to deinit low part driver */
};
#ifdef __cplusplus
}
#endif

View File

@ -1,49 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#pragma once
#include "esp_err.h"
#include "step_motor_driver_io.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief A4988 configuration
*/
typedef struct step_motor_io_a4988_conf_s {
gpio_num_t direction_pin;
gpio_num_t sleep_pin;
gpio_num_t reset_pin;
gpio_num_t ms3_pin;
gpio_num_t ms2_pin;
gpio_num_t ms1_pin;
gpio_num_t enable_pin;
} step_motor_io_a4988_conf_t;
/**
* @brief A4988 low part driver handle
*/
typedef struct step_motor_driver_io_a4988_s {
step_motor_driver_io_t base;
step_motor_io_a4988_conf_t conf;
} step_motor_driver_io_a4988_t;
/**
* @brief create an A4988 driver handle
*/
esp_err_t step_motor_new_a4988_io_driver(const step_motor_io_a4988_conf_t *conf, step_motor_driver_io_handle_t *handle);
/**
* @brief delete an A4988 driver handle
*/
esp_err_t step_motor_delete_a4988_io_driver(step_motor_driver_io_handle_t handle);
#ifdef __cplusplus
}
#endif

View File

@ -1,32 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "step_motor.h"
esp_err_t step_motor_init(step_motor_t *handle)
{
return handle->init(handle);
}
esp_err_t step_motor_deinit(step_motor_t *handle)
{
return handle->deinit(handle);
}
esp_err_t step_motor_step(step_motor_t *handle, uint32_t n, uint32_t speed)
{
return handle->step(handle, n, speed);
}
esp_err_t step_motor_smooth_step(step_motor_t *handle, uint32_t n, uint32_t speed_steps, uint32_t speed_min, uint32_t speed_max)
{
return handle->smooth_step(handle, n, speed_steps, speed_min, speed_max);
}
esp_err_t step_motor_set_step(step_motor_t *handle, uint16_t microstep, bool direction)
{
return handle->set_step(handle, microstep, direction);
}

View File

@ -1,174 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <string.h>
#include "hal/gpio_types.h"
#include "driver/gpio.h"
#include "esp_check.h"
#include "step_motor_driver_io_a4988.h"
static const char *TAG = "A4988_IO";
#define A4988_RESPONSE_DELAY_MS 10
static esp_err_t a4988_init(step_motor_driver_io_t *handle)
{
step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base);
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
//bit mask of the pins that you want to set,e.g.GPIO18/19
io_conf.pin_bit_mask = BIT64(a4988_motor->conf.direction_pin) |
BIT64(a4988_motor->conf.sleep_pin) |
BIT64(a4988_motor->conf.reset_pin) |
BIT64(a4988_motor->conf.ms3_pin) |
BIT64(a4988_motor->conf.ms2_pin) |
BIT64(a4988_motor->conf.ms1_pin) |
BIT64(a4988_motor->conf.enable_pin);
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
ESP_ERROR_CHECK(gpio_config(&io_conf));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.direction_pin, 0));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.sleep_pin, 0)); // default sleep
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.reset_pin, 0)); // keep reset
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms3_pin, 0)); // 1/1 phase
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms2_pin, 0));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms1_pin, 0));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.enable_pin, 1)); // disable by default
vTaskDelay(pdMS_TO_TICKS(A4988_RESPONSE_DELAY_MS));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.reset_pin, 1));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.sleep_pin, 1));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.enable_pin, 0));
vTaskDelay(pdMS_TO_TICKS(A4988_RESPONSE_DELAY_MS));
return ESP_OK;
}
static esp_err_t a4988_set_direction(step_motor_driver_io_t *handle, step_direction direction)
{
step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base);
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.direction_pin, direction));
return ESP_OK;
}
static esp_err_t a4988_enable_sleep(step_motor_driver_io_t *handle, bool enabled)
{
step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base);
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.sleep_pin, enabled));
return ESP_OK;
}
static esp_err_t a4988_enable_output(step_motor_driver_io_t *handle, bool enabled)
{
step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base);
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.enable_pin, enabled));
return ESP_OK;
}
static esp_err_t a4988_set_microstep(step_motor_driver_io_t *handle, uint16_t microstep)
{
step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base);
switch (microstep) {
case 1:
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms3_pin, 0));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms2_pin, 0));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms1_pin, 0));
break;
case 2:
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms3_pin, 0));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms2_pin, 0));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms1_pin, 1));
break;
case 4:
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms3_pin, 0));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms2_pin, 1));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms1_pin, 0));
break;
case 8:
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms3_pin, 0));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms2_pin, 1));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms1_pin, 1));
break;
case 16:
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms3_pin, 1));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms2_pin, 1));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms1_pin, 1));
break;
default:
return ESP_ERR_NOT_SUPPORTED;
}
return ESP_OK;
}
static esp_err_t a4988_reset(step_motor_driver_io_t *handle)
{
step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base);
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.reset_pin, 0));
vTaskDelay(pdMS_TO_TICKS(A4988_RESPONSE_DELAY_MS));
ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.reset_pin, 1));
return ESP_OK;
}
static esp_err_t a4988_deinit(step_motor_driver_io_t *handle)
{
step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base);
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = BIT64(a4988_motor->conf.direction_pin) |
BIT64(a4988_motor->conf.sleep_pin) |
BIT64(a4988_motor->conf.reset_pin) |
BIT64(a4988_motor->conf.ms3_pin) |
BIT64(a4988_motor->conf.ms2_pin) |
BIT64(a4988_motor->conf.ms1_pin) |
BIT64(a4988_motor->conf.enable_pin);
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
ESP_ERROR_CHECK(gpio_config(&io_conf));
return ESP_OK;
}
esp_err_t step_motor_new_a4988_io_driver(const step_motor_io_a4988_conf_t *conf, step_motor_driver_io_handle_t *handle)
{
esp_err_t ret = ESP_OK;
step_motor_driver_io_a4988_t *a4988 = NULL;
ESP_GOTO_ON_FALSE(conf, ESP_ERR_INVALID_ARG, err, TAG, "configuration can't be null");
ESP_GOTO_ON_FALSE(handle, ESP_ERR_INVALID_ARG, err, TAG, "can't assign handle to null");
a4988 = calloc(1, sizeof(step_motor_driver_io_a4988_t));
ESP_GOTO_ON_FALSE(a4988, ESP_ERR_NO_MEM, err, TAG, "allocate context memory failed");
memcpy(&a4988->conf, conf, sizeof(step_motor_io_a4988_conf_t));
a4988->base.init = a4988_init;
a4988->base.deinit = a4988_deinit;
a4988->base.set_direction = a4988_set_direction;
a4988->base.set_microstep = a4988_set_microstep;
a4988->base.enable_sleep = a4988_enable_sleep;
a4988->base.enable_output = a4988_enable_output;
a4988->base.trigger_reset = a4988_reset;
a4988->base.step_triggered_edge = 1;
a4988->base.pulse_high_period_us = 1;
a4988->base.pulse_low_period_us = 1;
*handle = &(a4988->base);
return ESP_OK;
err:
if (a4988) {
free(a4988);
}
return ret;
}
esp_err_t step_motor_delete_a4988_io_driver(step_motor_driver_io_handle_t handle)
{
ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_STATE, TAG, "empty handle");
step_motor_driver_io_a4988_t *a4988 = __containerof(handle, step_motor_driver_io_a4988_t, base);
free(a4988);
return ESP_OK;
}

View File

@ -1,325 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
/*
* This file contains an implementation of step motor middleware based on rmt peripheral
*/
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_check.h"
#include "driver/rmt.h"
#include "step_motor.h"
static const char *TAG = "RMT_STEP_MOTOR";
typedef enum {
STOPPED = 0,
SMOOTH_SPEED_UP,
SMOOTH_KEEP_SPEED,
SMOOTH_SLOW_DOWN,
UNLIMITED_LOOP,
LIMITED_LOOP,
} rmt_step_motor_running_status;
typedef struct {
step_motor_t base;
step_motor_driver_io_t *io_driver;
rmt_channel_t rmt_ch;
rmt_step_motor_running_status status;
rmt_item32_t rmt_items_loop;
uint32_t rmt_items_loop_count;
rmt_item32_t *rmt_items_speedup;
rmt_item32_t *rmt_items_speeddown;
uint32_t rmt_items_smoothstep_count;
SemaphoreHandle_t notify_semphr;
} rmt_step_motor_t;
static inline float helper_smootherstep_clamp(float x, float lowerlimit, float upperlimit)
{
if (x < lowerlimit) {
x = lowerlimit;
}
if (x > upperlimit) {
x = upperlimit;
}
return x;
}
// smoothstep formula
// see https://en.wikipedia.org/wiki/Smoothstep
static float helper_smootherstep(float edge0, float edge1, float x)
{
// Scale, and clamp x to 0..1 range
x = helper_smootherstep_clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
// Evaluate polynomial
return x * x * x * (x * (x * 6 - 15) + 10) * (edge1 - edge0) + edge0;
}
static uint16_t helper_speed_to_duration(uint16_t speed)
{
return (uint16_t) round(1.0 * 1000 * 1000 / speed);
}
static esp_err_t helper_fill_rmt_items(rmt_item32_t *items, uint32_t speed, const step_motor_driver_io_t *io_driver)
{
items->duration1 = io_driver->step_triggered_edge ? io_driver->pulse_high_period_us : io_driver->pulse_low_period_us;
items->level1 = io_driver->step_triggered_edge;
items->level0 = !io_driver->step_triggered_edge;
uint32_t delay_period = helper_speed_to_duration(speed);
if (delay_period <= (io_driver->step_triggered_edge ? io_driver->pulse_low_period_us : io_driver->pulse_high_period_us)) {
ESP_LOGW(TAG, "maximum rate reached, driver will generate another possible highest rate instead");
items->duration0 = io_driver->step_triggered_edge ? io_driver->pulse_low_period_us : io_driver->pulse_high_period_us;
} else {
items->duration0 = delay_period;
}
return ESP_OK;
}
static esp_err_t rmt_step_motor_init(step_motor_t *motor)
{
rmt_step_motor_t *rmt_handle = __containerof(motor, rmt_step_motor_t, base);
step_motor_driver_io_t *io_driver = rmt_handle->io_driver;
if (io_driver->init) {
return io_driver->init(io_driver);
}
return ESP_OK;
}
static esp_err_t rmt_step_motor_deinit(step_motor_t *motor)
{
rmt_step_motor_t *rmt_handle = __containerof(motor, rmt_step_motor_t, base);
step_motor_driver_io_t *io_driver = rmt_handle->io_driver;
if (io_driver->deinit) {
return io_driver->deinit(io_driver);
}
return ESP_OK;
}
// assume n != 0 and speed is within considerable range
static esp_err_t rmt_step_motor_step_impl(step_motor_t *motor, uint32_t n, uint32_t speed)
{
rmt_step_motor_t *rmt_handle = __containerof(motor, rmt_step_motor_t, base);
ESP_ERROR_CHECK(rmt_set_tx_loop_mode(rmt_handle->rmt_ch, true));
ESP_ERROR_CHECK(rmt_enable_tx_loop_autostop(rmt_handle->rmt_ch, true));
rmt_handle->rmt_items_loop_count = n;
if ((rmt_handle->rmt_items_loop_count) > 1023) {
(rmt_handle->rmt_items_loop_count) -= 1023;
ESP_ERROR_CHECK(rmt_set_tx_loop_count(rmt_handle->rmt_ch, 1023));
} else {
ESP_ERROR_CHECK(rmt_set_tx_loop_count(rmt_handle->rmt_ch, rmt_handle->rmt_items_loop_count));
rmt_handle->rmt_items_loop_count = 0;
}
helper_fill_rmt_items(&rmt_handle->rmt_items_loop, speed, rmt_handle->io_driver);
rmt_handle->status = LIMITED_LOOP;
rmt_write_items(rmt_handle->rmt_ch, &rmt_handle->rmt_items_loop, 1, false);
xSemaphoreTake(rmt_handle->notify_semphr, portMAX_DELAY);
return ESP_OK;
}
static esp_err_t rmt_step_motor_step(step_motor_t *handle, uint32_t n, uint32_t speed)
{
rmt_step_motor_t *rmt_handle = __containerof(handle, rmt_step_motor_t, base);
ESP_ERROR_CHECK(rmt_tx_stop(rmt_handle->rmt_ch));
if (n == UINT32_MAX) { // forever loop, non-blocking
ESP_ERROR_CHECK(rmt_set_tx_loop_count(rmt_handle->rmt_ch, 0));
ESP_ERROR_CHECK(rmt_enable_tx_loop_autostop(rmt_handle->rmt_ch, false));
ESP_ERROR_CHECK(rmt_set_tx_loop_mode(rmt_handle->rmt_ch, true));
helper_fill_rmt_items(&rmt_handle->rmt_items_loop, speed, rmt_handle->io_driver);
rmt_handle->status = UNLIMITED_LOOP;
ESP_ERROR_CHECK(rmt_write_items(rmt_handle->rmt_ch, &rmt_handle->rmt_items_loop, 1, false));
return ESP_OK;
} else if (n == 0) { // break the forever loop
rmt_handle->status = STOPPED;
ESP_ERROR_CHECK(rmt_tx_stop(rmt_handle->rmt_ch));
ESP_ERROR_CHECK(rmt_set_tx_loop_mode(rmt_handle->rmt_ch, false));
return ESP_OK;
} else { // normally move n steps
ESP_RETURN_ON_FALSE(helper_speed_to_duration(speed) > 1, ESP_ERR_INVALID_ARG, TAG,
"speed too fast");
return rmt_step_motor_step_impl(handle, n, speed);
}
}
static esp_err_t rmt_step_motor_smoothstep(step_motor_t *handle, uint32_t n, uint32_t speed_steps, uint32_t speed_min,
uint32_t speed_max)
{
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE(speed_min <= speed_max, ESP_ERR_INVALID_ARG, TAG, "max speed lower than min speed");
ESP_RETURN_ON_FALSE(n > speed_steps * 2, ESP_ERR_INVALID_ARG, TAG, "too few steps. consider lower speed_steps");
ESP_RETURN_ON_FALSE(helper_speed_to_duration(speed_min) < 1 << 15, ESP_ERR_INVALID_ARG, TAG, "min speed too low");
ESP_RETURN_ON_FALSE(helper_speed_to_duration(speed_max) > 1, ESP_ERR_INVALID_ARG, TAG, "max speed too high");
rmt_step_motor_t *rmt_handle = __containerof(handle, rmt_step_motor_t, base);
rmt_handle->rmt_items_speedup = malloc(sizeof(rmt_item32_t) * speed_steps);
ESP_RETURN_ON_FALSE(rmt_handle->rmt_items_speedup != NULL, ESP_ERR_NO_MEM, TAG,
"failed to allocate rmt_items_speedup");
rmt_handle->rmt_items_speeddown = malloc(sizeof(rmt_item32_t) * speed_steps);
ESP_GOTO_ON_FALSE(rmt_handle->rmt_items_speeddown != NULL, ESP_ERR_NO_MEM, err_free_speedup, TAG,
"failed to allocate rmt_items_speeddown");
ESP_GOTO_ON_ERROR(rmt_tx_stop(rmt_handle->rmt_ch), err_free_speeddown, TAG, "failed to stop rmt tx");
// prepare speed tables
for (int i = 0; i < speed_steps; ++i) {
helper_fill_rmt_items(&rmt_handle->rmt_items_speedup[i],
(uint16_t)helper_smootherstep(
(float)speed_min,
(float)speed_max,
(float)speed_min + ( (float)i / (float)speed_steps) * (float)(speed_max - speed_min))
, rmt_handle->io_driver
);
}
for (int i = 0; i < speed_steps; ++i) {
helper_fill_rmt_items(&rmt_handle->rmt_items_speeddown[i],
speed_max + speed_min - (uint16_t)helper_smootherstep(
(float)speed_min,
(float)speed_max,
(float)speed_min + ((float) i / (float)speed_steps) * (float)(speed_max - speed_min)
)
, rmt_handle->io_driver
);
}
rmt_handle->rmt_items_smoothstep_count = speed_steps;
// prepare continuous phase rmt payload
helper_fill_rmt_items(&rmt_handle->rmt_items_loop, speed_max, rmt_handle->io_driver);
rmt_handle->rmt_items_loop_count = n - speed_steps * 2;
// set status to be checked inside ISR
rmt_handle->status = SMOOTH_SPEED_UP;
// start transmitting
ESP_ERROR_CHECK(rmt_write_items(rmt_handle->rmt_ch, rmt_handle->rmt_items_speedup, speed_steps, false));
// waiting for transfer done
xSemaphoreTake(rmt_handle->notify_semphr, portMAX_DELAY);
err_free_speeddown:
free(rmt_handle->rmt_items_speeddown);
err_free_speedup:
free(rmt_handle->rmt_items_speedup);
return ret;
}
static esp_err_t rmt_step_motor_set_step(step_motor_t *handle, uint16_t microstep, bool direction)
{
rmt_step_motor_t *rmt_handle = __containerof(handle, rmt_step_motor_t, base);
step_motor_driver_io_t *io_driver = rmt_handle->io_driver;
if (io_driver->set_direction) {
ESP_ERROR_CHECK(io_driver->set_direction(io_driver, direction));
}
if (io_driver->set_microstep) {
ESP_ERROR_CHECK(io_driver->set_microstep(io_driver, microstep));
}
// at least 200ns delay as described in datasheet
esp_rom_delay_us(1);
return ESP_OK;
}
static IRAM_ATTR void rmt_tx_loop_intr(rmt_channel_t channel, void *args)
{
rmt_step_motor_t *rmt_step_motor = (rmt_step_motor_t *) args;
// smoothstep speedup stage finished
if (rmt_step_motor->status == SMOOTH_SPEED_UP) {
rmt_step_motor->status = SMOOTH_KEEP_SPEED;
rmt_set_tx_loop_mode(rmt_step_motor->rmt_ch, true);
rmt_enable_tx_loop_autostop(rmt_step_motor->rmt_ch, true);
rmt_set_tx_intr_en(rmt_step_motor->rmt_ch, 0);
// continue and configure loop count
}
if (rmt_step_motor->status == SMOOTH_KEEP_SPEED || rmt_step_motor->status == LIMITED_LOOP) {
// loop count not 0, continuing looping
if ((rmt_step_motor->rmt_items_loop_count) != 0) {
if ((rmt_step_motor->rmt_items_loop_count) > 1023) {
(rmt_step_motor->rmt_items_loop_count) -= 1023;
rmt_set_tx_loop_count(rmt_step_motor->rmt_ch, 1023);
} else {
rmt_set_tx_loop_count(rmt_step_motor->rmt_ch, rmt_step_motor->rmt_items_loop_count);
rmt_step_motor->rmt_items_loop_count = 0;
}
rmt_write_items(rmt_step_motor->rmt_ch, &rmt_step_motor->rmt_items_loop, 1, false);
return;
}
}
// smoothstep keep speed stage finished
if (rmt_step_motor->status == SMOOTH_KEEP_SPEED) {
rmt_step_motor->status = SMOOTH_SLOW_DOWN;
rmt_set_tx_loop_mode(rmt_step_motor->rmt_ch, false);
rmt_enable_tx_loop_autostop(rmt_step_motor->rmt_ch, false);
rmt_set_tx_intr_en(rmt_step_motor->rmt_ch, 1);
rmt_write_items(rmt_step_motor->rmt_ch, rmt_step_motor->rmt_items_speeddown, rmt_step_motor->rmt_items_smoothstep_count, false);
return;
}
if (rmt_step_motor->status == LIMITED_LOOP || rmt_step_motor->status == SMOOTH_SLOW_DOWN) {
rmt_step_motor->status = STOPPED;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(rmt_step_motor->notify_semphr, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
}
esp_err_t step_motor_create_rmt(step_motor_driver_io_t *io_driver, const rmt_config_t *rmt_conf, step_motor_handle_t *ret_handle)
{
esp_err_t ret = ESP_OK;
rmt_step_motor_t *rmt_step_motor = NULL;
ESP_RETURN_ON_ERROR(rmt_config(rmt_conf), TAG, "Failed to configure RMT");
ESP_RETURN_ON_ERROR(rmt_driver_install(rmt_conf->channel, 0, 0), TAG, "Failed to install RMT driver");
ESP_GOTO_ON_FALSE(io_driver, ESP_ERR_INVALID_ARG, err, TAG, "configuration can't be null");
ESP_GOTO_ON_FALSE(ret_handle, ESP_ERR_INVALID_ARG, err, TAG, "can't assign handle to null");
rmt_step_motor = calloc(1, sizeof(rmt_step_motor_t));
ESP_GOTO_ON_FALSE(rmt_step_motor, ESP_ERR_NO_MEM, err, TAG, "allocate context memory failed");
rmt_step_motor->rmt_ch = rmt_conf->channel;
rmt_step_motor->notify_semphr = xSemaphoreCreateBinary();
ESP_GOTO_ON_FALSE(rmt_step_motor, ESP_ERR_NO_MEM, err, TAG, "allocate semaphore memory failed");
rmt_step_motor->io_driver = io_driver;
// register tx end callback function, which got invoked when tx loop comes to the end
rmt_register_tx_end_callback(rmt_tx_loop_intr, rmt_step_motor);
rmt_step_motor->base.init = rmt_step_motor_init;
rmt_step_motor->base.deinit = rmt_step_motor_deinit;
rmt_step_motor->base.step = rmt_step_motor_step;
rmt_step_motor->base.set_step = rmt_step_motor_set_step;
rmt_step_motor->base.smooth_step = rmt_step_motor_smoothstep;
*ret_handle = &(rmt_step_motor->base);
return ESP_OK;
err:
if (rmt_step_motor) {
if (rmt_step_motor->notify_semphr) {
vSemaphoreDelete(rmt_step_motor->notify_semphr);
}
free(rmt_step_motor);
}
return ret;
}
esp_err_t step_motor_delete_rmt(step_motor_handle_t handle)
{
ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_STATE, TAG, "empty handle");
rmt_step_motor_t *rmt_handle = __containerof(handle, rmt_step_motor_t, base);
ESP_RETURN_ON_ERROR(rmt_driver_uninstall(rmt_handle->rmt_ch), TAG, "Failed to uninstall RMT driver");
vSemaphoreDelete(rmt_handle->notify_semphr);
free(rmt_handle);
return ESP_OK;
}

View File

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

View File

@ -1,92 +0,0 @@
/* RMT example -- step motor */
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/rmt.h"
#include "step_motor.h"
#include "step_motor_driver_io_a4988.h"
// GPIO configuration
#define STEP_MOTOR_DIRECTION_PIN GPIO_NUM_18
#define STEP_MOTOR_STEP_PIN GPIO_NUM_17
#define STEP_MOTOR_SLEEP_PIN GPIO_NUM_16
#define STEP_MOTOR_RESET_PIN GPIO_NUM_15
#define STEP_MOTOR_MS3_PIN GPIO_NUM_7
#define STEP_MOTOR_MS2_PIN GPIO_NUM_6
#define STEP_MOTOR_MS1_PIN GPIO_NUM_5
#define STEP_MOTOR_ENABLE_PIN GPIO_NUM_4
#define RMT_TX_CHANNEL RMT_CHANNEL_0
static const char *TAG = "step_motor";
void app_main(void)
{
// Apply default RMT configuration
rmt_config_t dev_config = RMT_DEFAULT_CONFIG_TX(STEP_MOTOR_STEP_PIN, RMT_TX_CHANNEL);
step_motor_io_a4988_conf_t a4988_conf = {
.direction_pin = STEP_MOTOR_DIRECTION_PIN,
.sleep_pin = STEP_MOTOR_SLEEP_PIN,
.reset_pin = STEP_MOTOR_RESET_PIN,
.ms3_pin = STEP_MOTOR_MS3_PIN,
.ms2_pin = STEP_MOTOR_MS2_PIN,
.ms1_pin = STEP_MOTOR_MS1_PIN,
.enable_pin = STEP_MOTOR_ENABLE_PIN,
};
// Install low part driver
step_motor_driver_io_t *a4988_io;
ESP_ERROR_CHECK(step_motor_new_a4988_io_driver(&a4988_conf, &a4988_io));
// Install rmt driver
step_motor_t *motor = NULL;
ESP_ERROR_CHECK(step_motor_create_rmt(a4988_io, &dev_config, &motor));
step_motor_init(motor);
ESP_LOGI(TAG, "init");
ESP_LOGI(TAG, "set_step");
// configure Microstep to Full Step
step_motor_set_step(motor, 1, STEP_MOTOR_DIRECTION_POSITIVE);
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI(TAG, "step 10 @ 1000/s");
step_motor_step(motor, 10, 1000);
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI(TAG, "step 100 @ 1000/s");
step_motor_step(motor, 100, 1000);
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI(TAG, "step 1000 @ 1200/s");
step_motor_step(motor, 1000, 1200);
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI(TAG, "step 5000 @ 1400/s");
step_motor_step(motor, 5000, 1400);
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI(TAG, "smoothstep start 5000 steps @ 500~1400/s");
step_motor_smooth_step(motor, 5000, 1000, 500, 1400);
ESP_LOGI(TAG, "smoothstep finish");
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI(TAG, "continuous running for 5s");
step_motor_step(motor, UINT32_MAX, 1000);
vTaskDelay(pdMS_TO_TICKS(5000));
ESP_LOGI(TAG, "stop");
step_motor_step(motor, 0, 1000);
vTaskDelay(pdMS_TO_TICKS(1000));
step_motor_deinit(motor);
ESP_LOGI(TAG, "deinit");
ESP_ERROR_CHECK(step_motor_delete_rmt(motor));
ESP_ERROR_CHECK(step_motor_delete_a4988_io_driver(a4988_io));
}

View File

@ -0,0 +1,79 @@
| Supported Targets | ESP32-S3 |
| ----------------- | -------- |
# RMT Based Stepper Motor Smooth Controller
(See the README.md file in the upper level 'examples' directory for more information about examples.)
One RMT TX channel can use different encoders in sequence, which is useful to generate waveforms that have obvious multiple stages.
This example shows how to drive a stepper motor with a **STEP/DIR** interfaced controller (e.g. [DRV8825](https://www.ti.com/lit/ds/symlink/drv8825.pdf)) in a [smooth](https://en.wikipedia.org/wiki/Smoothstep) way. To smoothly drive a stepper motor, there're three phases: **Acceleration**, **Uniform** and **Deceleration**. Accordingly, this example implements two encoders so that RMT channel can generate the waveforms with different characteristics:
* `curve_encoder` is to encode the **Acceleration** and **Deceleration** phase
* `uniform_encoder` is to encode the ***Uniform** phase
## How to Use Example
### Hardware Required
* A development board with any supported Espressif SOC chip (see `Supported Targets` table above)
* A USB cable for Power supply and programming
* A two-phase four-wire stepper motor
* A DRV8825 stepper motor controller
Connection :
```
+---------------------------+ +--------------------+ +--------------+
| ESP Board | | DRV8825 | | 4-wire |
| GND +-------------+ GND | | Step |
| | | | | Motor |
| 3V3 +-------------+ VDD A+ +------+ A+ |
| | | | | |
| STEP_MOTOR_GPIO_DIR +------------>+ DIR A- +------+ A- |
| | | | | |
| STEP_MOTOR_GPIO_STEP +------------>+ STEP B- +------+ B- |
| | | | | |
| | 3V3----+ nSLEEP B+ +------+ B+ |
| | | | +--------------+
| | 3V3----+ nRST VM +-------------------+
| | | | |
| | 3V3|GND----+ M2 GND +----------+ |
| | | | | |
| | 3V3|GND----+ M1 | | |
| | | | | |
| | 3V3|GND----+ M0 | +---+--------+-----+
| | | | | GND +12V |
| STEP_MOTOR_GPIO_EN +------------>+ nEN | | POWER SUPPLY |
+---------------------------+ +--------------------+ +------------------+
```
The GPIO number used in this example can be changed according to your board, by the macro `STEP_MOTOR_GPIO_EN`, `STEP_MOTOR_GPIO_DIR` and `STEP_MOTOR_GPIO_STEP` defined in the [source file](main/stepper_motor_example_main.c).
### 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://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
I (0) cpu_start: Starting scheduler on APP CPU.
I (325) example: Initialize EN + DIR GPIO
I (325) gpio: GPIO[16]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (335) gpio: GPIO[17]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (345) example: Create RMT TX channel
I (365) example: Set spin direction
I (365) example: Enable step motor
I (375) example: Create motor encoders
I (405) example: Start RMT channel
I (405) example: Spin motor for 6000 steps: 500 accel + 5000 uniform + 500 decel
```
## Troubleshooting
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 "stepper_motor_example_main.c" "stepper_motor_encoder.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,181 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "stepper_motor_encoder.h"
static const char *TAG = "stepper_motor_encoder";
static float convert_to_smooth_freq(uint32_t freq1, uint32_t freq2, uint32_t freqx)
{
float normalize_x = ((float)(freqx - freq1)) / (freq2 - freq1);
// third-order "smoothstep" function: https://en.wikipedia.org/wiki/Smoothstep
float smooth_x = normalize_x * normalize_x * (3 - 2 * normalize_x);
return smooth_x * (freq2 - freq1) + freq1;
}
typedef struct {
rmt_encoder_t base;
rmt_encoder_handle_t copy_encoder;
uint32_t sample_points;
struct {
uint32_t is_accel_curve: 1;
} flags;
rmt_symbol_word_t curve_table[];
} rmt_stepper_curve_encoder_t;
static size_t rmt_encode_stepper_motor_curve(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_stepper_curve_encoder_t *motor_encoder = __containerof(encoder, rmt_stepper_curve_encoder_t, base);
rmt_encoder_handle_t copy_encoder = motor_encoder->copy_encoder;
rmt_encode_state_t session_state = 0;
uint32_t points_num = *(uint32_t *)primary_data;
size_t encoded_symbols = 0;
if (motor_encoder->flags.is_accel_curve) {
encoded_symbols = copy_encoder->encode(copy_encoder, channel, &motor_encoder->curve_table[0],
points_num * sizeof(rmt_symbol_word_t), &session_state);
} else {
encoded_symbols = copy_encoder->encode(copy_encoder, channel, &motor_encoder->curve_table[0] + motor_encoder->sample_points - points_num,
points_num * sizeof(rmt_symbol_word_t), &session_state);
}
*ret_state = session_state;
return encoded_symbols;
}
static esp_err_t rmt_del_stepper_motor_curve_encoder(rmt_encoder_t *encoder)
{
rmt_stepper_curve_encoder_t *motor_encoder = __containerof(encoder, rmt_stepper_curve_encoder_t, base);
rmt_del_encoder(motor_encoder->copy_encoder);
free(motor_encoder);
return ESP_OK;
}
static esp_err_t rmt_reset_stepper_motor_curve_encoder(rmt_encoder_t *encoder)
{
rmt_stepper_curve_encoder_t *motor_encoder = __containerof(encoder, rmt_stepper_curve_encoder_t, base);
rmt_encoder_reset(motor_encoder->copy_encoder);
return ESP_OK;
}
esp_err_t rmt_new_stepper_motor_curve_encoder(const stepper_motor_curve_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
esp_err_t ret = ESP_OK;
rmt_stepper_curve_encoder_t *step_encoder = NULL;
float smooth_freq;
uint32_t symbol_duration;
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid arguments");
ESP_GOTO_ON_FALSE(config->sample_points, ESP_ERR_INVALID_ARG, err, TAG, "sample points number can't be zero");
ESP_GOTO_ON_FALSE(config->start_freq_hz != config->end_freq_hz, ESP_ERR_INVALID_ARG, err, TAG, "start freq can't equal to end freq");
step_encoder = calloc(1, sizeof(rmt_stepper_curve_encoder_t) + config->sample_points * sizeof(rmt_symbol_word_t));
ESP_GOTO_ON_FALSE(step_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for stepper curve encoder");
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &step_encoder->copy_encoder), err, TAG, "create copy encoder failed");
bool is_accel_curve = config->start_freq_hz < config->end_freq_hz;
// prepare the curve table, in RMT symbol format
if (is_accel_curve) {
uint32_t curve_step = (config->end_freq_hz - config->start_freq_hz) / (config->sample_points - 1);
for (uint32_t i = 0; i < config->sample_points; i++) {
smooth_freq = convert_to_smooth_freq(config->start_freq_hz, config->end_freq_hz, config->start_freq_hz + curve_step * i);
symbol_duration = config->resolution / smooth_freq / 2;
step_encoder->curve_table[i].level0 = 0;
step_encoder->curve_table[i].duration0 = symbol_duration;
step_encoder->curve_table[i].level1 = 1;
step_encoder->curve_table[i].duration1 = symbol_duration;
}
} else {
uint32_t curve_step = (config->start_freq_hz - config->end_freq_hz) / (config->sample_points - 1);
for (uint32_t i = 0; i < config->sample_points; i++) {
smooth_freq = convert_to_smooth_freq(config->end_freq_hz, config->start_freq_hz, config->end_freq_hz + curve_step * i);
symbol_duration = config->resolution / smooth_freq / 2;
step_encoder->curve_table[config->sample_points - i - 1].level0 = 0;
step_encoder->curve_table[config->sample_points - i - 1].duration0 = symbol_duration;
step_encoder->curve_table[config->sample_points - i - 1].level1 = 1;
step_encoder->curve_table[config->sample_points - i - 1].duration1 = symbol_duration;
}
}
step_encoder->sample_points = config->sample_points;
step_encoder->flags.is_accel_curve = is_accel_curve;
step_encoder->base.del = rmt_del_stepper_motor_curve_encoder;
step_encoder->base.encode = rmt_encode_stepper_motor_curve;
step_encoder->base.reset = rmt_reset_stepper_motor_curve_encoder;
*ret_encoder = &(step_encoder->base);
return ESP_OK;
err:
if (step_encoder) {
if (step_encoder->copy_encoder) {
rmt_del_encoder(step_encoder->copy_encoder);
}
free(step_encoder);
}
return ret;
}
typedef struct {
rmt_encoder_t base;
rmt_encoder_handle_t copy_encoder;
uint32_t resolution;
} rmt_stepper_uniform_encoder_t;
static size_t rmt_encode_stepper_motor_uniform(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_stepper_uniform_encoder_t *motor_encoder = __containerof(encoder, rmt_stepper_uniform_encoder_t, base);
rmt_encoder_handle_t copy_encoder = motor_encoder->copy_encoder;
rmt_encode_state_t session_state = 0;
uint32_t target_freq_hz = *(uint32_t *)primary_data;
uint32_t symbol_duration = motor_encoder->resolution / target_freq_hz / 2;
rmt_symbol_word_t freq_sample = {
.level0 = 0,
.duration0 = symbol_duration,
.level1 = 1,
.duration1 = symbol_duration,
};
size_t encoded_symbols = copy_encoder->encode(copy_encoder, channel, &freq_sample, sizeof(freq_sample), &session_state);
*ret_state = session_state;
return encoded_symbols;
}
static esp_err_t rmt_del_stepper_motor_uniform_encoder(rmt_encoder_t *encoder)
{
rmt_stepper_uniform_encoder_t *motor_encoder = __containerof(encoder, rmt_stepper_uniform_encoder_t, base);
rmt_del_encoder(motor_encoder->copy_encoder);
free(motor_encoder);
return ESP_OK;
}
static esp_err_t rmt_reset_stepper_motor_uniform(rmt_encoder_t *encoder)
{
rmt_stepper_uniform_encoder_t *motor_encoder = __containerof(encoder, rmt_stepper_uniform_encoder_t, base);
rmt_encoder_reset(motor_encoder->copy_encoder);
return ESP_OK;
}
esp_err_t rmt_new_stepper_motor_uniform_encoder(const stepper_motor_uniform_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
esp_err_t ret = ESP_OK;
rmt_stepper_uniform_encoder_t *step_encoder = NULL;
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid arguments");
step_encoder = calloc(1, sizeof(rmt_stepper_uniform_encoder_t));
ESP_GOTO_ON_FALSE(step_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for stepper uniform encoder");
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &step_encoder->copy_encoder), err, TAG, "create copy encoder failed");
step_encoder->resolution = config->resolution;
step_encoder->base.del = rmt_del_stepper_motor_uniform_encoder;
step_encoder->base.encode = rmt_encode_stepper_motor_uniform;
step_encoder->base.reset = rmt_reset_stepper_motor_uniform;
*ret_encoder = &(step_encoder->base);
return ESP_OK;
err:
if (step_encoder) {
if (step_encoder->copy_encoder) {
rmt_del_encoder(step_encoder->copy_encoder);
}
free(step_encoder);
}
return ret;
}

View File

@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "driver/rmt_encoder.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Stepper motor curve encoder configuration
*/
typedef struct {
uint32_t resolution; // Encoder resolution, in Hz
uint32_t sample_points; // Sample points used for deceleration phase
uint32_t start_freq_hz; // Start frequency on the curve, in Hz
uint32_t end_freq_hz; // End frequency on the curve, in Hz
} stepper_motor_curve_encoder_config_t;
/**
* @brief Stepper motor uniform encoder configuration
*/
typedef struct {
uint32_t resolution; // Encoder resolution, in Hz
} stepper_motor_uniform_encoder_config_t;
/**
* @brief Create stepper motor curve encoder
*
* @param[in] config Encoder configuration
* @param[out] ret_encoder Returned encoder handle
* @return
* - ESP_ERR_INVALID_ARG for any invalid arguments
* - ESP_ERR_NO_MEM out of memory when creating step motor encoder
* - ESP_OK if creating encoder successfully
*/
esp_err_t rmt_new_stepper_motor_curve_encoder(const stepper_motor_curve_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
/**
* @brief Create RMT encoder for encoding step motor uniform phase into RMT symbols
*
* @param[in] config Encoder configuration
* @param[out] ret_encoder Returned encoder handle
* @return
* - ESP_ERR_INVALID_ARG for any invalid arguments
* - ESP_ERR_NO_MEM out of memory when creating step motor encoder
* - ESP_OK if creating encoder successfully
*/
esp_err_t rmt_new_stepper_motor_uniform_encoder(const stepper_motor_uniform_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,106 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/rmt_tx.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "stepper_motor_encoder.h"
///////////////////////////////Change the following configurations according to your board//////////////////////////////
#define STEP_MOTOR_GPIO_EN 16
#define STEP_MOTOR_GPIO_DIR 17
#define STEP_MOTOR_GPIO_STEP 18
#define STEP_MOTOR_ENABLE_LEVEL 0 // DRV8825 is enabled on low level
#define STEP_MOTOR_SPIN_DIR_CLOCKWISE 0
#define STEP_MOTOR_SPIN_DIR_COUNTERCLOCKWISE !STEP_MOTOR_SPIN_DIR_CLOCKWISE
#define STEP_MOTOR_RESOLUTION_HZ 1000000 // 1MHz resolution
static const char *TAG = "example";
void app_main(void)
{
ESP_LOGI(TAG, "Initialize EN + DIR GPIO");
gpio_config_t en_dir_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.intr_type = GPIO_INTR_DISABLE,
.pin_bit_mask = 1ULL << STEP_MOTOR_GPIO_DIR | 1ULL << STEP_MOTOR_GPIO_EN,
};
ESP_ERROR_CHECK(gpio_config(&en_dir_gpio_config));
ESP_LOGI(TAG, "Create RMT TX channel");
rmt_channel_handle_t motor_chan = NULL;
rmt_tx_channel_config_t tx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // select clock source
.gpio_num = STEP_MOTOR_GPIO_STEP,
.mem_block_symbols = 64,
.resolution_hz = STEP_MOTOR_RESOLUTION_HZ,
.trans_queue_depth = 10, // set the number of transactions that can be pending in the background
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &motor_chan));
ESP_LOGI(TAG, "Set spin direction");
gpio_set_level(STEP_MOTOR_GPIO_DIR, STEP_MOTOR_SPIN_DIR_CLOCKWISE);
ESP_LOGI(TAG, "Enable step motor");
gpio_set_level(STEP_MOTOR_GPIO_EN, STEP_MOTOR_ENABLE_LEVEL);
ESP_LOGI(TAG, "Create motor encoders");
stepper_motor_curve_encoder_config_t accel_encoder_config = {
.resolution = STEP_MOTOR_RESOLUTION_HZ,
.sample_points = 500,
.start_freq_hz = 500,
.end_freq_hz = 1500,
};
rmt_encoder_handle_t accel_motor_encoder = NULL;
ESP_ERROR_CHECK(rmt_new_stepper_motor_curve_encoder(&accel_encoder_config, &accel_motor_encoder));
stepper_motor_uniform_encoder_config_t uniform_encoder_config = {
.resolution = STEP_MOTOR_RESOLUTION_HZ,
};
rmt_encoder_handle_t uniform_motor_encoder = NULL;
ESP_ERROR_CHECK(rmt_new_stepper_motor_uniform_encoder(&uniform_encoder_config, &uniform_motor_encoder));
stepper_motor_curve_encoder_config_t decel_encoder_config = {
.resolution = STEP_MOTOR_RESOLUTION_HZ,
.sample_points = 500,
.start_freq_hz = 1500,
.end_freq_hz = 500,
};
rmt_encoder_handle_t decel_motor_encoder = NULL;
ESP_ERROR_CHECK(rmt_new_stepper_motor_curve_encoder(&decel_encoder_config, &decel_motor_encoder));
ESP_LOGI(TAG, "Enable RMT channel");
ESP_ERROR_CHECK(rmt_enable(motor_chan));
ESP_LOGI(TAG, "Spin motor for 6000 steps: 500 accel + 5000 uniform + 500 decel");
rmt_transmit_config_t tx_config = {
.loop_count = 0,
};
const static uint32_t accel_samples = 500;
const static uint32_t uniform_speed_hz = 1500;
const static uint32_t decel_samples = 500;
while (1) {
// acceleration phase
tx_config.loop_count = 0;
ESP_ERROR_CHECK(rmt_transmit(motor_chan, accel_motor_encoder, &accel_samples, sizeof(accel_samples), &tx_config));
// uniform phase
tx_config.loop_count = 5000;
ESP_ERROR_CHECK(rmt_transmit(motor_chan, uniform_motor_encoder, &uniform_speed_hz, sizeof(uniform_speed_hz), &tx_config));
// deceleration phase
tx_config.loop_count = 0;
ESP_ERROR_CHECK(rmt_transmit(motor_chan, decel_motor_encoder, &decel_samples, sizeof(decel_samples), &tx_config));
// wait all transactions finished
ESP_ERROR_CHECK(rmt_tx_wait_all_done(motor_chan, -1));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

View File

@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32s3
@pytest.mark.generic
def test_ir_nec_example(dut: Dut) -> None:
dut.expect_exact('example: Initialize EN + DIR GPIO')
dut.expect_exact('example: Create RMT TX channel')
dut.expect_exact('example: Set spin direction')
dut.expect_exact('example: Enable step motor')
dut.expect_exact('example: Create motor encoders')
dut.expect_exact('example: Enable RMT channel')
dut.expect_exact('example: Spin motor for 6000 steps: 500 accel + 5000 uniform + 500 decel')