Skip to content

Commit 8c2cf5f

Browse files
committed
Update Dshot timing and add eRPM telemetry support
Adjusted Dshot and Bidirectional Dshot PIO timing parameters for improved ESC compatibility. Added eRPM telemetry support for bidirectional Dshot outputs, including tracking and reporting eRPM values in the Out class and CLI output.
1 parent c4a9b5e commit 8c2cf5f

File tree

8 files changed

+60
-190
lines changed

8 files changed

+60
-190
lines changed

src/cli/cli.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ static void cli_pout() {
202202
for(int i=0;i<OUT_SIZE;i++) {
203203
if(out.getType(i)) {
204204
Serial.printf("%c%d%%:%1.0f\t", out.getType(i), i, 100*out.get(i));
205+
if(out.erpmEnabled[i]>=0) {
206+
Serial.printf("erpm%d:%d\t", i, out.erpm[i]);
207+
}
205208
}
206209
}
207210
}

src/hal/RP2040/Dshot/dshot_parallel.pio

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ SOFTWARE.
2626

2727
.program dshot_parallel
2828

29-
//A bit period (1667ns for Dshot600) is split in 8 equal parts. A zero-bit is encoded as 3H 5L (625ns high), and a one-bit is encoded as 6H 2L (1250ns high) - see: https://www.speedgoat.com/products/dshot
30-
//madflight v2.2.1: modified to zero: 5H 11L (520ns high), one: 12H 4L (1250ns high) as some ESC misinterpreted zeroes as ones
29+
//A bit period (1667ns for Dshot600) is split in 8 equal parts. A zero-bit is encoded as 3H 5L (625ns high), and a one-bit is encoded as 6H 2L (1250ns high) - see: https://brushlesswhoop.com/dshot-and-bidirectional-dshot/ https://blck.mn/2016/11/dshot-the-new-kid-on-the-block/ https://www.speedgoat.com/products/dshot
30+
//madflight v2.2.1: modified to zero: 1H 2L (556ns high), one: 2H 1L (1111ns high) as some ESC misinterpreted zeroes as ones
3131
.define public T1 5
32-
.define public T2 7
33-
.define public T3 4
32+
.define public T2 5
33+
.define public T3 5
3434

3535
.wrap_target
3636
out x, 8

src/hal/RP2040/Dshot/dshot_parallel.pio.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@
1717
#define dshot_parallel_pio_version 0
1818

1919
#define dshot_parallel_T1 5
20-
#define dshot_parallel_T2 7
21-
#define dshot_parallel_T3 4
20+
#define dshot_parallel_T2 5
21+
#define dshot_parallel_T3 5
2222

2323
static const uint16_t dshot_parallel_program_instructions[] = {
2424
// .wrap_target
2525
0x6028, // 0: out x, 8
2626
0xa40b, // 1: mov pins, !null [4]
27-
0xa601, // 2: mov pins, x [6]
28-
0xa203, // 3: mov pins, null [2]
27+
0xa401, // 2: mov pins, x [4]
28+
0xa303, // 3: mov pins, null [3]
2929
// .wrap
3030
};
3131

src/hal/RP2040/DshotBidir/DshotBidirPio.h

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ The ESC replies with a 21 bit reply, the reply bit rate is 25% higher (5/4) than
5858
See https://brushlesswhoop.com/dshot-and-bidirectional-dshot/ for full details on decoding.
5959
*/
6060

61-
#define DSHOT_DMA_BUF_SIZE (256 / 4)
61+
#define DSHOT_DMA_BUF_SIZE (256 / 4) // buffer size in 32 bit words, one sample per byte
6262
//NOTE: dma buffer could be smaller, which will give faster rates but with risk of missing replies...
6363
//dma sampling period for 256 samples with 5.33x oversampling is: 256 / 5.33 * 4/5 Tbit = 39 Tbit
6464
//total period is: 16 Tbit (TX) + 2 Tbit (delay) + 39 Tbit (RX) + 10 Tbit (extra) = 67 Tbit
@@ -100,7 +100,7 @@ class DshotBidirPio {
100100
this->rate_khz = rate_khz;
101101
this->interval_us = (16 + 2 + 39 + 10) * 1000 / rate_khz; //16 (tx) + 2 (delay) + 39 (dma) + 10 (extra) -- FIXME: calculate dma delay based on DSHOT_DMA_BUF_SIZE and OSR
102102

103-
for(int i=0;i<DSHOT_DMA_BUF_SIZE;i++) dma_buf[i] = 0xffffffff; //init to high level, i.e. "no bidir reply"
103+
for(int i = 0; i < DSHOT_DMA_BUF_SIZE; i++) dma_buf[i] = 0xffffffff; //init to high level, i.e. "no bidir reply"
104104

105105
for(int i = 0 ; i < pin_count; i++) {
106106
gpio_pull_up(pin + i);
@@ -126,12 +126,12 @@ class DshotBidirPio {
126126
channel_config_set_read_increment(&c, false);
127127
channel_config_set_dreq(&c, pio_get_dreq(pio, sm, false));
128128
dma_channel_configure(
129-
dma_ch, // Channel to be configured
130-
&c, // The configuration we just created
131-
NULL, // The initial write address - set when starting dma
132-
&(pio->rxf[sm]), // The read address
129+
dma_ch, // Channel to be configured
130+
&c, // The configuration we just created
131+
NULL, // The initial write address - set when starting dma
132+
&(pio->rxf[sm]), // The read address
133133
DSHOT_DMA_BUF_SIZE, // Number of transfers; in this case each is 1 byte.
134-
false // do not start
134+
false // do not start
135135
);
136136

137137
setup_done = true;
@@ -182,15 +182,15 @@ class DshotBidirPio {
182182
frames[ch] = f;
183183
}
184184

185-
//convert frames into fifo stream
185+
//convert frames into fifo stream (16 bytes, first byte is first frame bit for 8 channels)
186186
uint32_t d[4] = {};
187-
for(int ch = 0; ch < pin_count; ch++) {
188-
for(int bit = 0; bit < 16; bit++) {
187+
for(int bit = 15; bit >= 0; bit--) {
188+
uint32_t word = (15 - bit) / 4;
189+
uint32_t shift = ((15 - bit) % 4) * 8;
190+
for(int ch=0; ch < pin_count; ch++) {
191+
uint32_t chmask = (1u << ch);
189192
if( frames[ch] & (1u << bit) ) {
190-
uint byte = 15 - bit; //byte in fifo stream (MSB of frame is sent first)
191-
uint32_t word = byte / 4; //32bit word in fifo stream
192-
uint32_t shift = (byte % 4) * 8; //shift in word (LSB of fifo is sent first)
193-
d[word] |= (1u << shift);
193+
d[word] |= (chmask << shift);
194194
}
195195
}
196196
}
@@ -222,8 +222,8 @@ class DshotBidirPio {
222222
int read_erpm(uint8_t channel) {
223223
uint32_t tlm_val;
224224
int tlm_type = read_telem(channel, &tlm_val);
225-
if(tlm_type<0) return tlm_type; //no data
226-
if(tlm_type>0) return -100 - tlm_type; //data received, but not erpm
225+
if(tlm_type < 0) return tlm_type; //no data
226+
if(tlm_type > 0) return -100 - tlm_type; //data received, but not erpm
227227
return tlm_val;
228228
}
229229

@@ -247,51 +247,50 @@ class DshotBidirPio {
247247
if(dma_channel_is_busy(dma_ch)) return -3;
248248

249249
uint8_t *byte_buf = (uint8_t *)dma_buf;
250-
uint8_t mask = 1<<channel;
250+
uint8_t mask = 1 << channel;
251251

252-
/*
253-
for(int i=0;i<DSHOT_DMA_BUF_SIZE*4;i++) {
252+
/* print dma buffer
253+
Serial.printf("ch%d:", channel);
254+
for(int i = 0; i < DSHOT_DMA_BUF_SIZE * 4; i++) {
254255
uint8_t bit = (byte_buf[i] & mask ? 0 : 1); //bit value
255256
Serial.print(bit ? '1' : '-');
256257
}
257-
Serial.println();
258-
*/
258+
Serial.println();
259+
//*/
259260

260261
//over sampling rates * 100
261262
int osr100 = (dshot_bidir_T1 + dshot_bidir_T2 + dshot_bidir_T3) * 100 * 32 / 40 / dshot_bidir_TRX;
262263
int osr100off = osr100 * 6 / 10; //should be 50% in theory, but 60% boosts shorter pulses a bit which appears to help decoding
263-
//Serial.printf("osr100=%d ", osr100);
264264

265265
uint8_t bit_last = 0;
266266
int pulse_len = 0;
267267
int bit_cnt = -1;
268268
uint32_t reply = 0;
269269

270-
for(int i=0;i<DSHOT_DMA_BUF_SIZE*4;i++) {
270+
for(int i = 0; i < DSHOT_DMA_BUF_SIZE * 4; i++) {
271271
pulse_len++;
272272
uint8_t bit = (byte_buf[i] & mask ? 0 : 1); //bit value
273273
if(bit != bit_last) {
274274
int bits = (pulse_len * 100 + osr100off) / osr100;
275275
if(bits == 0) bits = 1; //accept short pulses as single bit
276-
//Serial.printf("bit=%d len=%d bits=%d reply=%X bit_cnt=%d\n", bit_last, pulse_len, bits, reply, bit_cnt);
277276
if(bit_cnt < 0) {
278277
bit_cnt = 0;
279278
}else{
280-
while(bit_cnt<21 && bits > 0) {
279+
while(bit_cnt < 21 && bits > 0) {
281280
reply = (reply << 1) | bit_last; //store bits
282281
bit_cnt++;
283282
bits--;
284283
}
285284
}
286285
pulse_len = 0;
287286
bit_last = bit;
288-
//Serial.printf("reply=%X bit_cnt=%d\n", reply, bit_cnt);
289-
}
290-
if(bit_cnt>=21) break;
287+
}
288+
if(bit_cnt >= 21) break;
291289
}
292290

293-
if(bit_cnt<21) reply <<= 21 - bit_cnt; //append zeroes
291+
if(bit_cnt < 21) reply <<= 21 - bit_cnt; //append zeroes
294292

293+
//Serial.printf("ch=%d reply=%X\n", channel, reply);
295294
*tlm_val = 0;
296295
return decode_telem(reply, tlm_val);
297296
}

src/hal/RP2040/DshotBidir/dshot_bidir.pio

Lines changed: 10 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,12 @@ SOFTWARE.
3333

3434
; one TX bit is 40 cycles, one RX bit is 32 cycles
3535

36-
.define public T1 15
37-
.define public T2 15
38-
.define public T3 10
36+
//A bit period (1667ns for Dshot600) is split in 8 equal parts. A zero-bit is encoded as 3H 5L (625ns high), and a one-bit is encoded as 6H 2L (1250ns high) - see: https://brushlesswhoop.com/dshot-and-bidirectional-dshot/ https://blck.mn/2016/11/dshot-the-new-kid-on-the-block/ https://www.speedgoat.com/products/dshot
37+
//madflight v2.2.1: modified to zero: 1H 2L (556ns high), one: 2H 1L (1111ns high) as some ESC misinterpreted zeroes as ones (new timing is approximate, kept 40 cycle TX bit timing)
38+
39+
.define public T1 13
40+
.define public T2 14
41+
.define public T3 13
3942

4043
//.define public TRX 8 ; 4x oversampling (response is 21 bits -> 81 samples)
4144
.define public TRX 6 ; 5.33x oversampling (response is 21 bits -> 112 samples)
@@ -46,22 +49,18 @@ txloop:
4649
mov pins, null [T1-1]
4750
mov pins, x [T2-1]
4851
mov pins, !null [T3-3]
49-
jmp !osre txloop ; loop until ORS is empty
52+
jmp !osre txloop ; loop until OSR FIFO is empty
5053

51-
; delay [31] to partially bridge gap between sender - receiver (nominal gap is 30 us, actual anything between 20 ~ 40 us)
54+
; Note: next instructions have delay [31] to partially bridge gap between sender - receiver (nominal gap is 30 us, measured between 20 ~ 40 us)
5255

5356
mov osr, null [31] ; (*1) Fill OSR with zeroes
54-
out pindirs, 32 [31] ; (*1) Set pins to input with pullup (and empty osr)
57+
out pindirs, 32 [31] ; (*1) Set pins to input with pullup (and empty osr by pulling 32 bits)
5558

5659
.wrap_target
5760
in pins, 8 [TRX-1] ; keep on sampling 8 channel bytes until reset by C program
5861
.wrap
5962

60-
; REDUCING NUMBER OF INSTRUCTIONS FROM 8 to 7 (version 1)
61-
62-
; (*1) with PIO version 1 we can optimize "mov osr, null; out pindirs, 32" to "mov pindirs, null"
63-
64-
63+
; (*1) Note: with PIO version 1 we can optimize "mov osr, null; out pindirs, 32" to "mov pindirs, null"
6564

6665

6766
% c-sdk {
@@ -91,74 +90,3 @@ static inline void dshot_bidir_program_init(PIO pio, uint sm, uint offset, uint
9190
pio_sm_clear_fifos(pio, sm); //just to be sure
9291
}
9392
%}
94-
95-
96-
97-
;=================================================================================================
98-
; 1-8 channel Bidirectional DSHOT, needs blocking/isr/dma to bytes with 8-channel reply samples from fifo
99-
;=================================================================================================
100-
101-
.program dshot_bidir8
102-
103-
; one TX bit is 40 cycles, one RX bit is 32 cycles
104-
105-
.define public T1 15
106-
.define public T2 15
107-
.define public T3 10
108-
109-
//.define public TRX 8 ; 4x oversampling (response is 21 bits -> 81 samples)
110-
.define public TRX 6 ; 5.33x oversampling (response is 21 bits -> 112 samples)
111-
//.define public TRX 5 ; 6.4x oversampling (response is 21 bits -> 135 samples)
112-
113-
114-
txloop:
115-
out x, 8 ; "out x" is needed to stall here and keep pins high when fifo is empty (I.e. can't use "out pins 8" instead of "out x 8; mov pins x")
116-
mov pins, null [T1-1]
117-
mov pins, x [T2-1]
118-
mov pins, !null [T3-3]
119-
jmp !osre txloop ; loop until ORS is empty
120-
121-
; delay [31] to partially bridge gap between sender - receiver (nominal gap is 30 us, actual anything between 20 ~ 40 us)
122-
123-
mov osr, null [31] ; (*1) Fill OSR with zeroes
124-
out pindirs, 32 [31] ; (*1) Set pins to input with pullup (and empty osr)
125-
126-
; NOTE: we can't wait for a pin, as we would have to monitor 8 pins, C program discards samples until first 0
127-
128-
.wrap_target
129-
in pins 8 [TRX-1] ; keep on sampling until reset by C program
130-
.wrap
131-
132-
133-
; REDUCING NUMBER OF INSTRUCTIONS FROM 7 to 6 (version 1 only)
134-
135-
; (*1) with PIO version 1 we can optimize "mov osr, null; out pindirs, 32" to "mov pindirs, null"
136-
137-
% c-sdk {
138-
#include "hardware/clocks.h"
139-
140-
// init the sm in disabled state, ready for loading tx fifo.
141-
static inline void dshot_bidir8_program_init(PIO pio, uint sm, uint offset, uint pin_base, uint pin_count, float freq) {
142-
pio_sm_set_enabled(pio, sm, false); //disable sm
143-
144-
for(uint i=pin_base; i<pin_base+pin_count; i++) {
145-
pio_gpio_init(pio, i);
146-
}
147-
pio_sm_set_consecutive_pindirs(pio, sm, pin_base, pin_count, true); //set output
148-
149-
pio_sm_config c = dshot_bidir8_program_get_default_config(offset);
150-
sm_config_set_out_shift(&c, true, true, 32); //autopull
151-
sm_config_set_out_pins(&c, pin_base, pin_count);
152-
153-
sm_config_set_in_shift(&c, true, true, 32); //autopush
154-
sm_config_set_in_pins(&c, pin_base);
155-
156-
int cycles_per_bit = dshot_bidir8_T1 + dshot_bidir8_T2 + dshot_bidir8_T3;
157-
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
158-
sm_config_set_clkdiv(&c, div);
159-
160-
pio_sm_init(pio, sm, offset, &c);
161-
162-
pio_sm_clear_fifos(pio, sm); //just to be sure
163-
}
164-
%}

src/hal/RP2040/DshotBidir/dshot_bidir.pio.h

Lines changed: 6 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@
1616
#define dshot_bidir_wrap 7
1717
#define dshot_bidir_pio_version 0
1818

19-
#define dshot_bidir_T1 15
20-
#define dshot_bidir_T2 15
21-
#define dshot_bidir_T3 10
19+
#define dshot_bidir_T1 13
20+
#define dshot_bidir_T2 14
21+
#define dshot_bidir_T3 13
2222
#define dshot_bidir_TRX 6
2323

2424
static const uint16_t dshot_bidir_program_instructions[] = {
2525
0x6028, // 0: out x, 8
26-
0xae03, // 1: mov pins, null [14]
27-
0xae01, // 2: mov pins, x [14]
28-
0xa70b, // 3: mov pins, !null [7]
26+
0xac03, // 1: mov pins, null [12]
27+
0xad01, // 2: mov pins, x [13]
28+
0xaa0b, // 3: mov pins, !null [10]
2929
0x00e0, // 4: jmp !osre, 0
3030
0xbfe3, // 5: mov osr, null [31]
3131
0x7f80, // 6: out pindirs, 32 [31]
@@ -73,68 +73,3 @@ static inline void dshot_bidir_program_init(PIO pio, uint sm, uint offset, uint
7373

7474
#endif
7575

76-
// ------------ //
77-
// dshot_bidir8 //
78-
// ------------ //
79-
80-
#define dshot_bidir8_wrap_target 7
81-
#define dshot_bidir8_wrap 7
82-
#define dshot_bidir8_pio_version 0
83-
84-
#define dshot_bidir8_T1 15
85-
#define dshot_bidir8_T2 15
86-
#define dshot_bidir8_T3 10
87-
#define dshot_bidir8_TRX 6
88-
89-
static const uint16_t dshot_bidir8_program_instructions[] = {
90-
0x6028, // 0: out x, 8
91-
0xae03, // 1: mov pins, null [14]
92-
0xae01, // 2: mov pins, x [14]
93-
0xa70b, // 3: mov pins, !null [7]
94-
0x00e0, // 4: jmp !osre, 0
95-
0xbfe3, // 5: mov osr, null [31]
96-
0x7f80, // 6: out pindirs, 32 [31]
97-
// .wrap_target
98-
0x4508, // 7: in pins, 8 [5]
99-
// .wrap
100-
};
101-
102-
#if !PICO_NO_HARDWARE
103-
static const struct pio_program dshot_bidir8_program = {
104-
.instructions = dshot_bidir8_program_instructions,
105-
.length = 8,
106-
.origin = -1,
107-
.pio_version = 0,
108-
#if PICO_PIO_VERSION > 0
109-
.used_gpio_ranges = 0x0
110-
#endif
111-
};
112-
113-
static inline pio_sm_config dshot_bidir8_program_get_default_config(uint offset) {
114-
pio_sm_config c = pio_get_default_sm_config();
115-
sm_config_set_wrap(&c, offset + dshot_bidir8_wrap_target, offset + dshot_bidir8_wrap);
116-
return c;
117-
}
118-
119-
#include "hardware/clocks.h"
120-
// init the sm in disabled state, ready for loading tx fifo.
121-
static inline void dshot_bidir8_program_init(PIO pio, uint sm, uint offset, uint pin_base, uint pin_count, float freq) {
122-
pio_sm_set_enabled(pio, sm, false); //disable sm
123-
for(uint i=pin_base; i<pin_base+pin_count; i++) {
124-
pio_gpio_init(pio, i);
125-
}
126-
pio_sm_set_consecutive_pindirs(pio, sm, pin_base, pin_count, true); //set output
127-
pio_sm_config c = dshot_bidir8_program_get_default_config(offset);
128-
sm_config_set_out_shift(&c, true, true, 32); //autopull
129-
sm_config_set_out_pins(&c, pin_base, pin_count);
130-
sm_config_set_in_shift(&c, true, true, 32); //autopush
131-
sm_config_set_in_pins(&c, pin_base);
132-
int cycles_per_bit = dshot_bidir8_T1 + dshot_bidir8_T2 + dshot_bidir8_T3;
133-
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
134-
sm_config_set_clkdiv(&c, div);
135-
pio_sm_init(pio, sm, offset, &c);
136-
pio_sm_clear_fifos(pio, sm); //just to be sure
137-
}
138-
139-
#endif
140-

0 commit comments

Comments
 (0)