DMA to GPIO for generating dshot signal

Weskrauser
Posts: 9
Joined: Thu Sep 07, 2017 8:39 pm

DMA to GPIO for generating dshot signal

Postby Weskrauser » Thu Sep 07, 2017 9:05 pm

Hello everyone,

I'm new to esp32 and I can not find simple examples that could help me to generate a dshot signal using DMA to GPIO transfers.

The dshot protocol is digital protocol to comunicate with ESC used to drive quadcopter motors for example:

Image

And some technical infos about this protocol:

https://blck.mn/2016/11/dshot-the-new-kid-on-the-block/

DMA transfers seem to be the way to go to generate this signal for 4 motors.

With my understanding of DMA to GPIO process, the idea is to use a timer that would trig 3 DMA channels to operate this scheme:

Image

I only found some examples involving I2S transfers using DMA but I do not have any idea of how to do DMA to GPIO transfer of a bits array to GPIO registers (GPIO_OUT_W1TS_REG ?).

Any help would be apreciated because I'm lost :lol: , thanks in advance!

ESP_igrr
Posts: 1515
Joined: Tue Dec 01, 2015 8:37 am

Re: DMA to GPIO for generating dshot signal

Postby ESP_igrr » Fri Sep 08, 2017 1:26 am

Looks like a job for RMT peripheral!

Weskrauser
Posts: 9
Joined: Thu Sep 07, 2017 8:39 pm

Re: DMA to GPIO for generating dshot signal

Postby Weskrauser » Sat Sep 09, 2017 10:16 am

Hi ESP_igrr,

thx for your reply! It looks like a great feature that I didn't know... perhaps because of its name^^. I'll try too investigate this way!

Weskrauser
Posts: 9
Joined: Thu Sep 07, 2017 8:39 pm

Re: DMA to GPIO for generating dshot signal

Postby Weskrauser » Sun Sep 17, 2017 3:34 pm

Hello,

I used RMT to generate the signal and the result is strange:

-Signal frames are generated precisly and correctly -> Good :D
-Unexplicable 35us delay between frames -> Not good :shock:
-Huge delay of 140 ms that seems like a reset or something like that... -> Not good at all :oops:

Here is the code (I use arduino IDE):

Code: Select all

void setup() {

  config.rmt_mode = RMT_MODE_TX;
  config.channel = RMT_CHANNEL_0;
  config.gpio_num = (gpio_num_t)0;
  config.mem_block_num = 1;
  config.tx_config.loop_en = 0;
  config.tx_config.carrier_en = 0;
  config.tx_config.idle_output_en = 1;
  config.tx_config.idle_level = (rmt_idle_level_t)0;
  config.tx_config.carrier_duty_percent = 50;
  config.tx_config.carrier_freq_hz = 1000000;
  config.tx_config.carrier_level = (rmt_carrier_level_t)1;
  config.clk_div = 2;

  rmt_config(&config);
  rmt_driver_install(config.channel, 0, 0);


}

Code: Select all

void loop() {


  /* Computing Signal Timing to feed RMT */


  
  rmt_write_items(config.channel, Times_Array,
                  16, /* Number of items */
                  1 /* wait till done */);

delayMicroseconds(1);

}
Here is the result with dezooming progressively:

With a 5us division, we see the signal is OK:
DshotOK.jpg
DshotOK.jpg (105.94 KiB) Viewed 5066 times
With 20us division, we see that we have 35us between frames. I expected less than 10 (delay of 1us and the computing takes 7us). It can be reduced to 20 with setting wait_tx_done to 0 for rmt_write_items :
Dshot_Reset.jpg
Dshot_Reset.jpg (88.43 KiB) Viewed 5066 times

With 200ms division, we see a huge delay of 144ms! :
Dshot_delay_between_frames.jpg
Dshot_delay_between_frames.jpg (105.14 KiB) Viewed 5066 times

So the questions I have this time are:
-What is causing that delay between frames and the huge delay of 140ms?
-Is it OK if I put wait_tx_done to 0 for rmt_write_items?
-What is the impact of carrier_freq_hz for RMT? (I tried to change it without visible impact)

Thanks in advance!
Last edited by Weskrauser on Sun Sep 24, 2017 9:18 am, edited 1 time in total.

ESP_igrr
Posts: 1515
Joined: Tue Dec 01, 2015 8:37 am

Re: DMA to GPIO for generating dshot signal

Postby ESP_igrr » Sun Sep 17, 2017 8:35 pm

You may want to let the RMT keep transmitting same data until you decide to change it. That is called "loop mode" in RMT driver:
http://esp-idf.readthedocs.io/en/latest ... channel_tb

That would be equivalent to saying "keep sending this until i change the data". Then you only need to call rmt_write_items when new data is available from the application. I can't immediately tell what happens during these 140ms delay periods, it could be that the main task (i.e. the task in which "loop" runs) is being preempted by something else. Do you have something else in your application, or is it just this rmt_write_items call?

Regarding carrier frequency: it is used only if you set carrier_en to "true":
http://esp-idf.readthedocs.io/en/latest ... arrier_enE

When carrier is enabled, the signal will be modulated at the given carrier frequency and duty cycle. This feature is mostly used when transmitting and receiving IR remote control signals, which are modulated at ~40kHz.

Weskrauser
Posts: 9
Joined: Thu Sep 07, 2017 8:39 pm

Re: DMA to GPIO for generating dshot signal

Postby Weskrauser » Sun Sep 24, 2017 9:58 am

Hello,

thx again for your answer ESP_igrr, I tried the loop mode, it now works a lot better without the 144ms delay and with a correct delay of 6us between frames which is excellent. This is exacttly what I need for my use!

I've got only one problem remaining:

-With random frequency, frames longer than 16 bits are visibles. Perhaps a kind of concatenation of several parts of 16 bits frames (it can be 24 or more bits). Maybe I 'm not calling rmt_write_items at the right time or something like that. The frequency of these longer frames depends on the delayMicroseconds I use in my loop() fonction so it seems like a synchronization problem:
Dshot_NOK_Frame_Com.jpg
Dshot_NOK_Frame_Com.jpg (169.71 KiB) Viewed 5012 times
...more in detail:
Frames_longer_than expected.jpg
Frames_longer_than expected.jpg (133.17 KiB) Viewed 5012 times


I posted the code I use. The only thing it does is converting the throttle 1983 into a compatible 16 bit dshot frame. It then compute an timing array for RMT and write it:

Part1 with loop and init:

Code: Select all

// DSHOT600 mais peut-être modifié pour DSHOT1200

#include <driver/rmt.h>


/*RMT definition*/
#define DIVIDER    2 /* Diviseur du timer*/
#define DURATION  12.5 /* flash 80MHz => minimum time unit */

#define PULSE_T0H (  625 / (DURATION * DIVIDER));
#define PULSE_T1H (  1250 / (DURATION * DIVIDER));
#define PULSE_T0L (  1045 / (DURATION * DIVIDER));
#define PULSE_T1L (  420 / (DURATION * DIVIDER));

#define PULSE_T0H_RAW 625;
#define PULSE_T1H_RAW 1250;
#define PULSE_T0L_RAW 1045;
#define PULSE_T1L_RAW 420;

rmt_config_t config;

uint16_t Throttle = 1983;
uint16_t Throttlecut = Throttle;
uint8_t Decalcut = 0;
byte Telem = 0;
uint8_t Checksum = 0;
uint16_t ThrottleTelem = 0;
uint16_t DshotBinFrame = 0;

long t_now = 0;

void setup() {

  
  config.rmt_mode = RMT_MODE_TX;
  config.channel = RMT_CHANNEL_0;
  config.gpio_num = (gpio_num_t)0;
  config.mem_block_num = 1;
  config.tx_config.loop_en = 0;
  config.tx_config.carrier_en = 0;
  config.tx_config.idle_output_en = 1;
  config.tx_config.idle_level = (rmt_idle_level_t)0;
  config.tx_config.carrier_duty_percent = 50;
  config.tx_config.carrier_freq_hz = 10000;
  config.tx_config.carrier_level = (rmt_carrier_level_t)1;
  config.clk_div = 2;

  rmt_config(&config);
  rmt_driver_install(config.channel, 0, 0);

  rmt_set_tx_loop_mode((rmt_channel_t)0, 1);
}

void loop() {

  ThrottleTelem = ComputeThrottleTelem(Throttle , Telem);
  Checksum = ComputeDshotChecksum(ThrottleTelem);
  DshotBinFrame = ComputeFullDshotFrame(ThrottleTelem , Checksum);


  rmt_item32_t *Times_Array = ComputeDshotBuffer_RMT_ESP32(DshotBinFrame);

delayMicroseconds(25);

  rmt_write_items(config.channel, Times_Array,
                  16, /* Number of items */
                  1 /* wait till done */);
  rmt_wait_tx_done(config.channel);
  free(Times_Array);

  

}
Part2 with functions that compute timings array for RMT_write

Code: Select all

/* Compute Dshot checksum */
uint8_t ComputeDshotChecksum(uint16_t ThrottleTelem) {      //Coupe la valeur de 12 bits en 3 parties de 4 bits et les "somme" avec XOR.

  uint8_t Checksum = 0;

  for (int i = 0; i < 3; i++) {

    Decalcut = 12 - (i + 1) * 4;                           // On commence par la partie de 4 bits coté bit dominant, donc on décale de 8, 4 et 0 bits
    Throttlecut =  (ThrottleTelem >> Decalcut) & 0b1111;   // Maintenant on ne garde que les 4 premiers chiffres de l'uint8
    Checksum ^= Throttlecut;                               // On somme avec XOR


  }

  return Checksum;
}




/* Compute Complete ThrottleTelem Value */

uint16_t ComputeThrottleTelem(uint16_t Throttle , byte Telem) {      //Rassemble le throttle et le bit de telem en une valeur de 12 bit

  uint16_t ThrottleTelem = 0;

  ThrottleTelem = (Throttle << 1) + Telem;

  return ThrottleTelem;

}






/* Compute Full Dshot frame */

uint16_t ComputeFullDshotFrame(uint16_t ThrottleTelem , uint8_t Checksum) {      //Concatene 11 bits throttle, 1 bit telem, et 4 bits de checksum

  uint16_t DshotBinFrame = 0;

  DshotBinFrame = (ThrottleTelem << 4) + (Checksum & 0b1111);

  return DshotBinFrame;

}






// Fonction utilisée générant le véritable standard pour esp32
rmt_item32_t *ComputeDshotBuffer_RMT_ESP32(uint16_t DshotBinFrame) {     // Put timings in a table to use for RMT (1 = High during 1250ns, 0 = High during 625ns). Fonction qui renvoit un pointeur qui pointe vers le tableau cf http://www.dinduks.com/cpp-retourner-un-tableau-dans-une-fonction-ou-methode/;

  rmt_item32_t *Times_Array = new rmt_item32_t[16];                    // Allocation dynamique c++ ~ malloc, calloc

  for (int i = 0; i < 16 ; i++) {

    if ( ((DshotBinFrame >> i) & 0b1) == 1 ) {        //Prend les bits du signal entier un par un par décalage. Si ce bit vaut un, on met les durées du timing haut, sinon timing bas en fonction du divider rmt

      Times_Array[(15 - i)].duration0 = PULSE_T1H;
      Times_Array[(15 - i)].level0 = 1;
      Times_Array[(15 - i)].duration1 = PULSE_T1L;
      Times_Array[(15 - i) ].level1 = 0;

    }
    else {

      Times_Array[(15 - i)].duration0 = PULSE_T0H;
      Times_Array[(15 - i)].level0 = 1;
      Times_Array[(15 - i)].duration1 = PULSE_T0L;
      Times_Array[(15 - i) ].level1 = 0;

    }

  }

  Times_Array[15].duration0 = PULSE_T0H;
      Times_Array[15].level0 = 1;
      Times_Array[15].duration1 = 250;
      Times_Array[15].level1 = 0;

  return Times_Array;

}
In fact, I would like to use the loop mode like you proposed and to inject a new updated value "at the right time" not to disturb the frame that will be updated at the next TX ;-)

Thanks in advance

aallman
Posts: 1
Joined: Tue Apr 16, 2019 12:43 pm

Re: DMA to GPIO for generating dshot signal

Postby aallman » Tue Apr 16, 2019 12:52 pm

Were you able to get this working with the tx loop enabled?

I've been following a similar path using the rmt feature for dshot signals but I'm running into some troubles getting 4 ESCs to arm properly. One servo seems to work just fine using rmt tx at 1ms intervals, but maybe the timing is marginal and breaks with more channels. I'm planning on trying the loop mode next to see if that gives more consistent results.

Jye___
Posts: 3
Joined: Sun May 05, 2019 11:07 pm

Re: DMA to GPIO for generating dshot signal

Postby Jye___ » Tue May 07, 2019 2:54 am

How did you go getting this working?

Im also looking at dshot with RMT but would also like to receive the dshot telemetry. My post below has working code that sends dshot and Im having trouble figuring out the receiving component.

http://bbs.esp32.com/viewtopic.php?f=19 ... 6d97af393e

Who is online

Users browsing this forum: No registered users and 4 guests