example: added dshot esc example based on new rmt driver

This commit is contained in:
morris 2022-04-07 13:14:25 +08:00
parent 0e19bc1463
commit f472551910
7 changed files with 364 additions and 0 deletions

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(dshot_esc)

View File

@ -0,0 +1,60 @@
| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- |
# RMT Infinite Loop Transmit Example -- Dshot ESC (Electronic Speed Controller)
(See the README.md file in the upper level 'examples' directory for more information about examples.)
RMT TX channel can transmit symbols in an infinite loop, where the loop is totally controlled by the hardware. This feature is useful for scenarios where a device needs continuous stimulus.
The [DShot](https://github.com/betaflight/betaflight/wiki/Dshot) is a digital protocol between flight controller (FC) and ESC, which is more resistant to electrical noise than traditional analog protocols. The DShot protocol requires the FC to encode throttle information into pulses with various durations and send out the pulses periodically. This is what an RMT TX channel can perfectly do.
## 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
* An ESC that supports DShot protocol (this example will take the **DShot300** as an example)
Connection :
```
BLDC DShot ESC 12V GND
+--------+ +---------------+ | | ESP
| | | | | | +----------------------+
| U +-----+ U P+ +----+ | | |
| | | | | | |
| V +-----+ V P- +--------+ | |
| | | | | |
| W +-----+ W SIG +----------+ DSHOT_ESC_GPIO_NUM |
| | | GND +----------+ GND |
+--------+ +---------------+ +----------------------+
```
The GPIO number used in this example can be changed according to your board, by the macro `DSHOT_ESC_GPIO_NUM` defined in the [source file](main/dshot_esc_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.
## Console Output
```
I (0) cpu_start: Starting scheduler on APP CPU.
I (182) example: Create RMT TX channel
I (182) gpio: GPIO[43]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (182) example: Install Dshot ESC encoder
I (182) example: Start RMT TX channel
I (182) example: Start ESC by sending zero throttle for a while...
I (3182) example: Set throttle to 1000, no telemetry
```
The BLDC motor will beep when the ESC receives a burst of initialization pulses. And then starts high-speed rotation at the throttle set in the code.
## 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 "dshot_esc_example_main.c" "dshot_esc_encoder.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,165 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "dshot_esc_encoder.h"
static const char *TAG = "dshot_encoder";
/**
* @brief Type of Dshot ESC frame
*/
typedef union {
struct {
uint16_t crc: 4; /*!< CRC checksum */
uint16_t telemetry: 1; /*!< Telemetry request */
uint16_t throttle: 11; /*!< Throttle value */
};
uint16_t val;
} dshot_esc_frame_t;
#ifndef __cplusplus
_Static_assert(sizeof(dshot_esc_frame_t) == 0x02, "Invalid size of dshot_esc_frame_t structure");
#endif
typedef struct {
rmt_encoder_t base;
rmt_encoder_t *bytes_encoder;
rmt_encoder_t *copy_encoder;
rmt_symbol_word_t dshot_delay_symbol;
int state;
} rmt_dshot_esc_encoder_t;
static void make_dshot_frame(dshot_esc_frame_t *frame, uint16_t throttle, bool telemetry)
{
frame->throttle = throttle;
frame->telemetry = telemetry;
uint16_t val = frame->val;
uint8_t crc = ((val ^ (val >> 4) ^ (val >> 8)) & 0xF0) >> 4;;
frame->crc = crc;
val = frame->val;
// change the endian
frame->val = ((val & 0xFF) << 8) | ((val & 0xFF00) >> 8);
}
static size_t rmt_encode_dshot_esc(rmt_encoder_t *encoder, rmt_channel_handle_t channel,
const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_dshot_esc_encoder_t *dshot_encoder = __containerof(encoder, rmt_dshot_esc_encoder_t, base);
rmt_encoder_handle_t bytes_encoder = dshot_encoder->bytes_encoder;
rmt_encoder_handle_t copy_encoder = dshot_encoder->copy_encoder;
rmt_encode_state_t session_state = 0;
rmt_encode_state_t state = 0;
size_t encoded_symbols = 0;
// convert user data into dshot frame
dshot_esc_throttle_t *throttle = (dshot_esc_throttle_t *)primary_data;
dshot_esc_frame_t frame = {};
make_dshot_frame(&frame, throttle->throttle, throttle->telemetry_req);
switch (dshot_encoder->state) {
case 0: // send the dshot frame
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &frame, sizeof(frame), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
dshot_encoder->state = 1; // switch to next state when current encoding session finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
// fall-through
case 1:
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &dshot_encoder->dshot_delay_symbol,
sizeof(rmt_symbol_word_t), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
state |= RMT_ENCODING_COMPLETE;
dshot_encoder->state = 0; // switch to next state when current encoding session finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
}
out:
*ret_state = state;
return encoded_symbols;
}
static esp_err_t rmt_del_dshot_encoder(rmt_encoder_t *encoder)
{
rmt_dshot_esc_encoder_t *dshot_encoder = __containerof(encoder, rmt_dshot_esc_encoder_t, base);
rmt_del_encoder(dshot_encoder->bytes_encoder);
rmt_del_encoder(dshot_encoder->copy_encoder);
free(dshot_encoder);
return ESP_OK;
}
static esp_err_t rmt_dshot_encoder_reset(rmt_encoder_t *encoder)
{
rmt_dshot_esc_encoder_t *dshot_encoder = __containerof(encoder, rmt_dshot_esc_encoder_t, base);
rmt_encoder_reset(dshot_encoder->bytes_encoder);
rmt_encoder_reset(dshot_encoder->copy_encoder);
dshot_encoder->state = 0;
return ESP_OK;
}
esp_err_t rmt_new_dshot_esc_encoder(const dshot_esc_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
esp_err_t ret = ESP_OK;
rmt_dshot_esc_encoder_t *dshot_encoder = NULL;
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
dshot_encoder = calloc(1, sizeof(rmt_dshot_esc_encoder_t));
ESP_GOTO_ON_FALSE(dshot_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for musical score encoder");
dshot_encoder->base.encode = rmt_encode_dshot_esc;
dshot_encoder->base.del = rmt_del_dshot_encoder;
dshot_encoder->base.reset = rmt_dshot_encoder_reset;
uint32_t delay_ticks = config->resolution / 1e6 * config->post_delay_us;
rmt_symbol_word_t dshot_delay_symbol = {
.level0 = 0,
.duration0 = delay_ticks / 2,
.level1 = 0,
.duration1 = delay_ticks / 2,
};
dshot_encoder->dshot_delay_symbol = dshot_delay_symbol;
// different dshot protocol have its own timing requirements,
float period_ticks = (float)config->resolution / config->baud_rate;
// 1 and 0 is represented by a 74.850% and 37.425% duty cycle respectively
unsigned int t1h_ticks = (unsigned int)(period_ticks * 0.7485);
unsigned int t1l_ticks = (unsigned int)(period_ticks - t1h_ticks);
unsigned int t0h_ticks = (unsigned int)(period_ticks * 0.37425);
unsigned int t0l_ticks = (unsigned int)(period_ticks - t0h_ticks);
rmt_bytes_encoder_config_t bytes_encoder_config = {
.bit0 = {
.level0 = 1,
.duration0 = t0h_ticks,
.level1 = 0,
.duration1 = t0l_ticks,
},
.bit1 = {
.level0 = 1,
.duration0 = t1h_ticks,
.level1 = 0,
.duration1 = t1l_ticks,
},
.flags.msb_first = 1,
};
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &dshot_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &dshot_encoder->copy_encoder), err, TAG, "create copy encoder failed");
*ret_encoder = &dshot_encoder->base;
return ESP_OK;
err:
if (dshot_encoder) {
if (dshot_encoder->bytes_encoder) {
rmt_del_encoder(dshot_encoder->bytes_encoder);
}
if (dshot_encoder->copy_encoder) {
rmt_del_encoder(dshot_encoder->copy_encoder);
}
free(dshot_encoder);
}
return ret;
}

View File

@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "driver/rmt_encoder.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Throttle representation in DShot protocol
*/
typedef struct {
uint16_t throttle; /*!< Throttle value */
bool telemetry_req; /*!< Telemetry request */
} dshot_esc_throttle_t;
/**
* @brief Type of Dshot ESC encoder configuration
*/
typedef struct {
uint32_t resolution; /*!< Encoder resolution, in Hz */
uint32_t baud_rate; /*!< Dshot protocol runs at several different baud rates, e.g. DSHOT300 = 300k baud rate */
uint32_t post_delay_us; /*!< Delay time after one Dshot frame, in microseconds */
} dshot_esc_encoder_config_t;
/**
* @brief Create RMT encoder for encoding Dshot ESC frame 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 Dshot ESC encoder
* - ESP_OK if creating encoder successfully
*/
esp_err_t rmt_new_dshot_esc_encoder(const dshot_esc_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,66 @@
/*
* 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 "esp_log.h"
#include "driver/rmt_tx.h"
#include "dshot_esc_encoder.h"
#define DSHOT_ESC_RESOLUTION_HZ 40000000 // 40MHz resolution, DSHot protocol needs a relative high resolution
#define DSHOT_ESC_GPIO_NUM 0
static const char *TAG = "example";
void app_main(void)
{
ESP_LOGI(TAG, "Create RMT TX channel");
rmt_channel_handle_t esc_chan = NULL;
rmt_tx_channel_config_t tx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // select a clock that can provide needed resolution
.gpio_num = DSHOT_ESC_GPIO_NUM,
.mem_block_symbols = 64,
.resolution_hz = DSHOT_ESC_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, &esc_chan));
ESP_LOGI(TAG, "Install Dshot ESC encoder");
rmt_encoder_handle_t dshot_encoder = NULL;
dshot_esc_encoder_config_t encoder_config = {
.resolution = DSHOT_ESC_RESOLUTION_HZ,
.baud_rate = 300000, // DSHOT300 protocol
.post_delay_us = 50, // extra delay between each frame
};
ESP_ERROR_CHECK(rmt_new_dshot_esc_encoder(&encoder_config, &dshot_encoder));
ESP_LOGI(TAG, "Enable RMT TX channel");
ESP_ERROR_CHECK(rmt_enable(esc_chan));
rmt_transmit_config_t tx_config = {
.loop_count = -1, // infinite loop
};
dshot_esc_throttle_t throttle = {
.throttle = 0,
.telemetry_req = false, // telemetry is not supported in this example
};
ESP_LOGI(TAG, "Start ESC by sending zero throttle for a while...");
ESP_ERROR_CHECK(rmt_transmit(esc_chan, dshot_encoder, &throttle, sizeof(throttle), &tx_config));
vTaskDelay(pdMS_TO_TICKS(5000));
ESP_LOGI(TAG, "Increase throttle, no telemetry");
for (uint16_t thro = 100; thro < 1000; thro += 10) {
throttle.throttle = thro;
ESP_ERROR_CHECK(rmt_transmit(esc_chan, dshot_encoder, &throttle, sizeof(throttle), &tx_config));
// the previous loop transfer is till undergoing, we need to stop it and restart,
// so that the new throttle can be updated on the output
ESP_ERROR_CHECK(rmt_disable(esc_chan));
ESP_ERROR_CHECK(rmt_enable(esc_chan));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

View File

@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32c3
@pytest.mark.generic
def test_dshot_esc_example(dut: Dut) -> None:
dut.expect_exact('example: Create RMT TX channel')
dut.expect_exact('example: Install Dshot ESC encoder')
dut.expect_exact('example: Enable RMT TX channel')
dut.expect_exact('example: Start ESC by sending zero throttle for a while...')
dut.expect_exact('example: Increase throttle, no telemetry')