Thank you very much.
Despite the fact my need is now old stories, regarding the lake of pertinent answer, i really appreciate that not only you find a solution to my question, but you took the time to reply here.
As far as im not much into mcu dev since few years now, i really wanted to express to you my gratitude.
Now, may be will take the time to catch up with your solution and try to test it on my old ESC/bldc motor.
Thanks a lot again
[HELP] RMT single wire bidirectional communication (dshot telemetry)
-
PurpleLilac
- Posts: 1
- Joined: Tue Apr 29, 2025 8:02 am
Re: [HELP] RMT single wire bidirectional communication (dshot telemetry)
Hello,Have you found a way yet?
I'm also very interested in using bidirectional DShot in one of my projects, but am failing to do it in a clean manner. Two not so good ways:
1) Use the telemetry wire, the telemetry frame (115200 baud, Serial 8N1) limits you to roughly 1kHz of signal rate, but for your purpose this should be ok (notice: slower erpm updates, no GCR encoding, ...)
2) Use a 1kOhm resistor between two GPIOs. One is the output, one is the input. Now connect the ESC signal wire to the input pin. The input pin will see this: dshot signal.png. From there, I think you know where to go (GCR decoding etc.)https://brushlesswhoop.com/dshot-and-bi ... nal-dshot/
But if you have found a cleaner (software) solution by using one pin as in- and output, I would be very interested to know, how you did it.
For me, this will be a long going project, so even if you find a solution in a year or more, please reply![]()
I am also trying to capture the ESC signal and i have been successful in that regard using an STM32. However, I have trouble in deciphering the captured data into eRPM. In the picture you provided and in my own testing, there seems to be only 20 bits coming back from the ESC, instead of 21. Maybe I'm missing something, but how would one go about decoding the signal from ESC?
Re: [HELP] RMT single wire bidirectional communication (dshot telemetry)
Hi
I am also searching for bidirectional Dshot implementation option for ESP32-S3 and studying the thing found that in IDF5.5 there were flags .io_loop_back and .io_od_mode for rmt_tx_channel_config_t and rmt_rx_channel_config_t what (in theory) solves exactly the problem of bidirectional communication via single pin. But in 6.0 version I do not see these flags for config_t structures. Does anybody knows if they have been removed for a reason?
I am also searching for bidirectional Dshot implementation option for ESP32-S3 and studying the thing found that in IDF5.5 there were flags .io_loop_back and .io_od_mode for rmt_tx_channel_config_t and rmt_rx_channel_config_t what (in theory) solves exactly the problem of bidirectional communication via single pin. But in 6.0 version I do not see these flags for config_t structures. Does anybody knows if they have been removed for a reason?
-
MicroController
- Posts: 2667
- Joined: Mon Oct 17, 2022 7:38 pm
- Location: Europe, Germany
Re: [HELP] RMT single wire bidirectional communication (dshot telemetry)
You should be able to just initialize TX&RX on the same pin, then set that pin to OD via gpio_od_enable().
Re: [HELP] RMT single wire bidirectional communication (dshot telemetry)
Hm, ok, thanks, will look into this
Re: [HELP] RMT single wire bidirectional communication (dshot telemetry)
Hi
I am struggling to make bidirectional dshot working, somewhere close but cannot figure out trivial case - when there is no response from ESC (just steady high level line after TX is finished).
Below is the code with general logic:
1) sending some data (with enabled end of TX interrupt callback)
2) when TX interrupt occures launch rmt_receive function (with with enabled end of RX interrupt callback)
3) when RX interrurt occures save received symbols and notify main task that something is received
I observe the following weird behaviour
- if .signal_range_max_ns is small (1000) analyser shows that RX interrupt occures before TX that does not make sense for me
- if .signal_range_max_ns is larger (5000) every 2nd cycle I receive an error rmt_receive(399): channel not in enable state (while other cycle behaves reasonable).
Looks like some conflict with RMT FSM but I am out of ideas
I am struggling to make bidirectional dshot working, somewhere close but cannot figure out trivial case - when there is no response from ESC (just steady high level line after TX is finished).
Below is the code with general logic:
1) sending some data (with enabled end of TX interrupt callback)
2) when TX interrupt occures launch rmt_receive function (with with enabled end of RX interrupt callback)
3) when RX interrurt occures save received symbols and notify main task that something is received
I observe the following weird behaviour
- if .signal_range_max_ns is small (1000) analyser shows that RX interrupt occures before TX that does not make sense for me
- if .signal_range_max_ns is larger (5000) every 2nd cycle I receive an error rmt_receive(399): channel not in enable state (while other cycle behaves reasonable).
Looks like some conflict with RMT FSM but I am out of ideas
Code: Select all
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_rx.h"
#include "hal/gpio_ll.h"
#include "esp_log.h"
#include "driver/gpio.h"
#define DSHOT_GPIO 4
#define RMT_BASE_CLK_HZ 40000000
#define RX_BUFFER_SIZE 48
#define NUMBER_OF_BLDC_POLES 14
#define LED_TX 17
#define LED_RX 18
#define LED_GEN 8
static rmt_channel_handle_t tx_chan = NULL;
static rmt_channel_handle_t rx_chan = NULL;
static rmt_encoder_handle_t copy_encoder = NULL;
static rmt_symbol_word_t tx_buffer[17];
static rmt_symbol_word_t rx_buffer[RX_BUFFER_SIZE];
static TaskHandle_t main_task_handle = NULL;
static volatile uint8_t is_timeout = 0;
static volatile uint8_t is_buffer_full;
static const rmt_receive_config_t rx_trans_config = {
.signal_range_min_ns = 500,
.signal_range_max_ns = 5000, //1000
};
static const rmt_transmit_config_t tx_trans_config = {
.loop_count = 0,
.flags.eot_level = 1, // high EOT level
};
void print_binary32(uint32_t num) {
for (int i = 31; i >= 0; i--) {
int bit = (num >> i) & 1;
putchar(bit ? '1' : '0');
if (i % 8 == 0) {
putchar(' ');
}
}
putchar('\n');
}
//TX callback
static bool IRAM_ATTR dshot_tx_done_callback(rmt_channel_handle_t tx_chan, const rmt_tx_done_event_data_t *edata, void *user_ctx) {
rmt_channel_handle_t rx_chan = (rmt_channel_handle_t)user_ctx;
//activating receiver
rmt_receive(rx_chan, rx_buffer, sizeof(rx_buffer), &rx_trans_config);
gpio_set_level(LED_TX, 1);
return false;
}
//RX callback
static bool IRAM_ATTR test_rx_done_callback(rmt_channel_handle_t rx_chan, const rmt_rx_done_event_data_t *edata, void *user_ctx) {
uint32_t rx_interrupt_info = edata->num_symbols;
//determining interrupt reason
if (edata->flags.is_last) rx_interrupt_info |= 0x80000000; //static level timeout flag
if (edata->num_symbols >= RX_BUFFER_SIZE) rx_interrupt_info |= 0x40000000; //RX buffer overwhelming
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
gpio_set_level(LED_RX, 1);
xTaskGenericNotifyFromISR(main_task_handle, 0, rx_interrupt_info, eSetValueWithOverwrite, NULL, &xHigherPriorityTaskWoken);
return xHigherPriorityTaskWoken == pdTRUE;
}
// BiDShot300 TX packet (16 bits + 1 Reset marker)
void prepare_test_packet(uint16_t throttle) {
uint16_t packet = throttle << 4;
uint16_t crc = (~(throttle ^ (throttle >> 4) ^ (throttle >> 8))) & 0x0F;
packet |= crc;
const uint32_t bit_total = 133;
const uint32_t bit1_low = 100;
const uint32_t bit0_low = 40;
for (int i = 0; i < 16; i++) {
uint16_t bit = (packet << i) & 0x8000;
if (bit) {
tx_buffer[i] = (rmt_symbol_word_t) {
.level0 = 0, .duration0 = bit1_low,
.level1 = 1, .duration1 = bit_total - bit1_low
};
} else {
tx_buffer[i] = (rmt_symbol_word_t) {
.level0 = 0, .duration0 = bit0_low,
.level1 = 1, .duration1 = bit_total - bit0_low
};
}
}
}
void bidishot_hw_init() {
gpio_reset_pin(LED_TX);
gpio_set_direction(LED_TX, GPIO_MODE_OUTPUT);
gpio_set_level(LED_TX, 0);
gpio_reset_pin(LED_RX);
gpio_set_direction(LED_RX, GPIO_MODE_OUTPUT);
gpio_set_level(LED_RX, 0);
gpio_reset_pin(LED_GEN);
gpio_set_direction(LED_GEN, GPIO_MODE_OUTPUT);
gpio_set_level(LED_GEN, 0);
//RX channel parameters
rmt_rx_channel_config_t rx_config = {
.gpio_num = DSHOT_GPIO,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = RMT_BASE_CLK_HZ,
.mem_block_symbols = 48,
};
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_config, &rx_chan));
//RX callback registration
rmt_rx_event_callbacks_t cbs = {
.on_recv_done = test_rx_done_callback,
};
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_chan, &cbs, NULL));
//RX channel enabing
ESP_ERROR_CHECK(rmt_enable(rx_chan));
//TX channel parameters (same pin)
rmt_tx_channel_config_t tx_config = {
.gpio_num = DSHOT_GPIO,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = RMT_BASE_CLK_HZ,
.mem_block_symbols = 48,
.trans_queue_depth = 1,
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_config, &tx_chan));
//TX callback registration
rmt_tx_event_callbacks_t tx_cbs = {
.on_trans_done = dshot_tx_done_callback,
};
ESP_ERROR_CHECK(rmt_tx_register_event_callbacks(tx_chan, &tx_cbs, rx_chan));
//creating copy encoder
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_ERROR_CHECK(rmt_new_copy_encoder(©_encoder_config, ©_encoder));
//enable open drain
gpio_ll_od_enable(&GPIO, DSHOT_GPIO);
//TX channel enabing
ESP_ERROR_CHECK(rmt_enable(tx_chan));
}
void dshot_test(void) {
main_task_handle = xTaskGetCurrentTaskHandle();
bidishot_hw_init();
while (1) {
//preparing any packet to send
prepare_test_packet(0);
//sending
ESP_ERROR_CHECK(rmt_transmit(tx_chan, copy_encoder, tx_buffer, 16 * sizeof(rmt_symbol_word_t), &tx_trans_config));
//waiting for interrupt from RX
uint32_t data_from_interrupt = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(2));
gpio_set_level(LED_GEN, 1);
print_binary32(data_from_interrupt);
gpio_set_level(LED_TX, 0);
gpio_set_level(LED_RX, 0);
gpio_set_level(LED_GEN,0);
vTaskDelay(pdMS_TO_TICKS(5));
}
}Re: [HELP] RMT single wire bidirectional communication (dshot telemetry)
Guys, I have narrowed down the issue, below is a very simple code
The logic is as follows:
1) configuring RX channel
2) activating rmt_receive()
3) waiting for interrupt (which sould happen after .signal_range_max_ns)
4) printing results of interrupt
5) waiting and go to 2)
It should work but works only one time, after first iteration rmt_receive() returns back errors rmt_receive(399): channel not in enable state. Interesting that if RX actually receives something - it works as should, errors are returned only when nothing is received. What am I missing here? Working with S3 and 6.0.0
The logic is as follows:
1) configuring RX channel
2) activating rmt_receive()
3) waiting for interrupt (which sould happen after .signal_range_max_ns)
4) printing results of interrupt
5) waiting and go to 2)
Code: Select all
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/rmt_rx.h"
#include "hal/gpio_ll.h"
#include "driver/gpio.h"
#define DSHOT_GPIO 4
#define RMT_BASE_CLK_HZ 40000000
#define RX_BUFFER_SIZE 48
#define LED_RX 18
static rmt_channel_handle_t rx_chan = NULL;
static rmt_symbol_word_t rx_buffer[RX_BUFFER_SIZE];
static TaskHandle_t main_task_handle = NULL;
static const rmt_receive_config_t rx_trans_config = {
.signal_range_min_ns = 500,
.signal_range_max_ns =60000,
};
//RX ISR
static bool IRAM_ATTR test_rx_done_callback(rmt_channel_handle_t rx_chan, const rmt_rx_done_event_data_t *edata, void *user_ctx) {
uint32_t rx_interrupt_info = edata->num_symbols;
//checking the cause of interrupt
if (edata->flags.is_last) rx_interrupt_info |= 0x80000000; //timeout flag
if (edata->num_symbols >= RX_BUFFER_SIZE) rx_interrupt_info |= 0x40000000; //overflow flag
gpio_set_level(LED_RX, 1); //to control that interrupt actually occurs
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTaskGenericNotifyFromISR(main_task_handle, 0, rx_interrupt_info, eSetValueWithOverwrite, NULL, &xHigherPriorityTaskWoken);
return xHigherPriorityTaskWoken == pdTRUE;
}
void dshot_test(void) {
main_task_handle = xTaskGetCurrentTaskHandle();
gpio_reset_pin(LED_RX);
gpio_set_direction(LED_RX, GPIO_MODE_OUTPUT);
gpio_set_level(LED_RX, 0);
//oped drain pin configuration
gpio_ll_od_enable(&GPIO, DSHOT_GPIO);
//RX settings
rmt_rx_channel_config_t rx_config = {
.gpio_num = DSHOT_GPIO,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = RMT_BASE_CLK_HZ,
.mem_block_symbols = 48,
};
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_config, &rx_chan));
ESP_ERROR_CHECK(rmt_enable(rx_chan));
//callback registration
rmt_rx_event_callbacks_t cbs = {
.on_recv_done = test_rx_done_callback,
};
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_chan, &cbs, NULL));
while (1) {
//activation receive
rmt_receive(rx_chan, rx_buffer, sizeof(rx_buffer), &rx_trans_config);
//waiting for interrupt from RX
uint32_t data_from_interrupt = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(2));
printf("%lu\n", data_from_interrupt);
gpio_set_level(LED_RX, 0);;
vTaskDelay(pdMS_TO_TICKS(10));
}
}
It should work but works only one time, after first iteration rmt_receive() returns back errors rmt_receive(399): channel not in enable state. Interesting that if RX actually receives something - it works as should, errors are returned only when nothing is received. What am I missing here? Working with S3 and 6.0.0
Who is online
Users browsing this forum: No registered users and 0 guests