2222
2323import sigrokdecode as srd
2424from functools import reduce
25-
26-
25+ from enum import Enum
26+
27+ gcr_tables = {
28+ "0b11001" : 0x0 ,
29+ "0b11011" : 0x1 ,
30+ "0b10010" : 0x2 ,
31+ "0b10011" : 0x3 ,
32+ "0b11101" : 0x4 ,
33+ "0b10101" : 0x5 ,
34+ "0b10110" : 0x6 ,
35+ "0b10111" : 0x7 ,
36+ "0b11010" : 0x8 ,
37+ "0b1001" : 0x9 ,
38+ "0b1010" : 0xa ,
39+ "0b1011" : 0xb ,
40+ "0b11110" : 0xc ,
41+ "0b1101" : 0xd ,
42+ "0b1110" : 0xe ,
43+ "0b1111" : 0xf
44+ }
2745
2846class SamplerateError (Exception ):
2947 pass
3048
49+ class State (Enum ):
50+ CMD = 1 ,
51+ TELEM = 2
52+
53+ class State_Telem (Enum ):
54+ START = 1 ,
55+ RECV = 2
56+
3157class Decoder (srd .Decoder ):
3258 api_version = 3
3359 id = 'dshot'
@@ -44,65 +70,83 @@ class Decoder(srd.Decoder):
4470
4571 options = (
4672 {'id' : 'dshot_rate' , 'desc' : 'DShot Rate' , 'default' : '150' ,'values' : ('150' , '300' ,'600' ,'1200' )},
47- { 'id' : 'bidir' , 'desc' : 'Bidirectional DShot' ,'default' : 'False ' , 'values' : ('True' , 'False' )},
73+ { 'id' : 'bidir' , 'desc' : 'Bidirectional DShot' ,'default' : 'True ' , 'values' : ('True' , 'False' )},
4874 { 'id' : 'log' , 'desc' : 'Write log file' ,'default' : 'no' , 'values' : ('yes' , 'no' )},
75+ {'id' : 'edt_force' , 'desc' : 'Force EDT as telem type' , 'default' : 'no' , 'values' : ('True' , 'False' )},
4976 )
5077 annotations = (
5178 ('bit' , 'Bit' ),
5279 ('cmd' , 'Command' ),
5380 ('throttle' , 'Throttle' ),
5481 ('checksum' , 'CRC' ),
5582 ('errors' , 'Errors' ),
83+ ('telem_bit' , 'Telem Bit' ),
84+ ('telem_erpm' , 'Telem ERPM' ),
85+ ('telem_edt' , 'Telem EDT' ),
86+ ('telem_errors' , 'Telem Errors' ),
87+ ('telem_error2' , 'Telem Errors2' ),
5688
5789 )
5890 annotation_rows = (
5991 ('bits' , 'Bits' , (0 ,)),
6092 ('dshot_data' , 'DShot Data' , (1 ,2 ,3 )),
6193 ('dshot_errors' , 'Dshot Errors' , (4 ,)),
94+ ('telem_bits' , 'Telem Bits' , (5 ,)),
95+ ('dshot_telem_erpm' , 'Dshot Telem ERPM' , (6 ,)),
96+ ('dshot_telem_edt' , 'Dshot Telem' , (7 ,)),
97+ ('dshot_telem_errors' , 'Dshot Errors' , (8 ,)),
98+ ('dshot_telem_errors2' , 'Dshot Errors' , (9 ,)),
6299 )
63100
64- dshot_period_lookup = {'150' : 6.67e-6 , '300' : 3.33e-6 ,'600' :1.67e-6 ,'1200' :0.83e-6 }
101+ #dshot_period_lookup = {'150': 6.67e-6, '300': 3.33e-6,'600':1.67e-6,'1200':0.83e-6}
102+
65103
66104 def __init__ (self ):
67105 self .reset ()
68106
69107 def reset (self ):
108+ self .state = State .CMD
70109 self .samplerate = None
71- # self.oldpin = None
72- # self.ss_packet = None
73- # self.ss = None
74- # self.es = None
75- # self.bits = []
110+
111+ self .debug = False
112+
76113 self .inreset = False
77114 self .bidirectional = False
115+ self .dshot_kbaud = 300e3
78116 self .dshot_period = 3.33e-6
79117 self .actual_period = None
80118 self .halfbitwidth = None
81119 self .currbit_ss = None
82120 self .currbit_es = None
83- self .samples_toreset = None
121+ self .samples_after_motorcmd = None
84122 self .samples_pp = None
85-
123+
124+ self .telem_start = None
125+ self .state_telem = State_Telem .START
126+ self .telem_baudrate_midpoint = 0
127+ self .edt_force = False
86128
87129 def start (self ):
88130 self .bidirectional = True if self .options ['bidir' ] == 'True' else False
89- self .dshot_period = self .dshot_period_lookup [self .options ['dshot_rate' ]]
131+ self .edt_force = True if self .options ['edt_force' ] == 'True' else False
132+ self .dshot_kbaud = int (self .options ['dshot_rate' ])* 1000
133+ self .dshot_period = 1 / self .dshot_kbaud
90134 self .samples_pp = int (self .samplerate * self .dshot_period )
91- self .samples_toreset = self .samples_pp * 3
92- # self.halfbitwidth = int(( self.samplerate / self.dshot_period) / 2.0)
93- #print("start period",self.dshot_period)
135+ self .samples_after_motorcmd = self .samples_pp * 3
136+ self .samples_after_telempkt = self .samples_pp * 3
137+
94138 self .out_ann = self .register (srd .OUTPUT_ANN )
139+ self .telem_baudrate_midpoint = int ((self .samplerate / (self .dshot_kbaud * (5 / 4 ))) / 2.0 )
140+ if self .debug :
141+ print ("telem_midpoint" ,self .telem_baudrate_midpoint )
95142
96143 def metadata (self , key , value ):
97144 if key == srd .SRD_CONF_SAMPLERATE :
98145 self .samplerate = value
99146
100- def handle_bits (self , results ):
147+ def handle_bits_dshot (self , results ):
101148 #ss, es, bit
102- #print(results)
103149 bits = [result [2 ] for result in results ]
104- # print(bits)
105-
106150
107151 if len (bits ) == 16 :
108152 dshot_value = int (reduce (lambda a , b : (a << 1 ) | b , bits [:11 ]))
@@ -138,18 +182,11 @@ def handle_bits(self, results):
138182 if not crc_ok :
139183 self .put (crc_startsample , results [15 ][1 ], self .out_ann ,
140184 [4 , ['CRC INVALID' ]])
141-
142-
143- self .bits = []
144- self .ss_packet = None
185+ return True
145186 else :
146- return
147- # self.put(results[0][0], results[-1::1][1], self.out_ann,
148- # [1, ['ERROR: INVALID PACKET LENGTH', 'ERR', 'E']])
149-
150-
151- def handle_bit (self , ss , es , nb_ss ):
187+ return False
152188
189+ def handle_bit_dshot (self , ss , es , nb_ss ):
153190 period = nb_ss - ss
154191 duty = es - ss
155192 # Ideal duty for T0H: 33%, T1H: 66%.
@@ -159,43 +196,171 @@ def handle_bit(self, ss, es, nb_ss):
159196 [0 , ['%d' % bit_ ]])
160197 return [ss ,nb_ss ,bit_ ]
161198
162-
199+ def handle_telem_bit (self ,matched ):
200+ # None to raise exception if no match
201+ result = None
202+
203+ # Low
204+ if matched == (True , False ):
205+ # 0 value
206+ result = 0
207+
208+ # High
209+ elif matched == (False , True ):
210+ # 1 value
211+ result = 1
212+ return result
213+
214+ def process_telem_erpm (self ,packet ,start ,end ):
215+ # Raw packet
216+ self .put (start ,
217+ end , self .out_ann ,
218+ [6 , ['%23s' % bin (packet )]])
219+ # XOR with next?
220+ packet &= 0x0FFFFF
221+ packet = (packet ^ (packet >> 1 ))
222+ self .put (start ,
223+ end , self .out_ann ,
224+ [7 , ['%23s' % bin (packet )]])
225+ # Undo GCR
226+ output = 0b0
227+
228+ nibbles = 4
229+ bitmask = 0b11111 << ((nibbles - 1 )* 5 )
230+
231+ for n in range (nibbles ):
232+ gcr_n = bitmask & packet
233+ ungcr = gcr_tables [bin (gcr_n >> (nibbles - (n + 1 )) * 5 )]
234+ output = (output << 4 ) | ungcr
235+ bitmask = (bitmask >> 5 )
236+
237+ # Compare CRC
238+ crc_received = output & 0xF
239+ output = (output >> 4 ) & 0xFFF
240+ crc_calc = ~ ((output ^ (output >> 4 ) ^ (output >> 8 ))) & 0x0F
241+
242+
243+
244+ self .put (end - ((self .telem_baudrate_midpoint * 2 )* 4 ),
245+ end , self .out_ann ,
246+ [7 , ['%23s' % ("RX CRC: " + hex (crc_received )+ " Calc CRC: " + hex (crc_calc ))]])
247+ if crc_calc != crc_received :
248+ self .put (end - ((self .telem_baudrate_midpoint * 2 ) * 4 ),
249+ end , self .out_ann ,
250+ [8 , ['%23s' % ("CRC ERROR!" )]])
251+ # The upper 12 bit contain the eperiod (1/erps) in the following bitwise encoding:
252+ #
253+ # e e e m m m m m m m m m
254+ #
255+ # The 9 bit value M needs to shifted left E times to get the period in micro seconds.
256+ # This gives a range of 1 us to 65408 us. Which translates to a min e-frequency of 15.29 hz or for 14 pole motors 3.82 hz.
257+ return
258+ def process_telem_edt (self ,packet ,start ,end ):
259+
260+ return
261+ def process_telem (self ,packet ,start ,end ):
262+ if self .edt_force :
263+ self .process_telem_edt (packet ,start ,end )
264+ else :
265+ self .process_telem_erpm (packet ,start ,end )
266+
267+ return
268+
269+
270+
271+
163272 def decode (self ):
164273 if not self .samplerate :
165274 raise SamplerateError ('Cannot decode without samplerate.' )
166275
167276 results = []
277+ telem = 0b0
278+ tlm_start = 0
168279 while True :
169- if not self .bidirectional :
170- pins = self .wait ([{0 : 'r' },{0 : 'f' },{'skip' :self .samples_toreset }])
171- else :
172- pins = self .wait ([{0 : 'f' },{0 : 'r' },{'skip' :self .samples_toreset }])
173-
174- if self .currbit_ss and self .currbit_es and self .matched [2 ]:
175- # Assume end of packet if have seen start and end of a potential bit but no further change within 3 periods
176- # TODO: Confirm wait period this works with spec
177- results += [self .handle_bit (self .currbit_ss ,self .currbit_es ,(self .currbit_ss + self .samples_pp ))]
178- self .currbit_ss = None
179- self .currbit_es = None
180-
181- # Pass results to decoder
182- self .handle_bits (results )
183- results = []
184-
185280
186- if self .matched [0 ] and not self .currbit_ss and not self .currbit_es :
187- # Start of bit
188- self .currbit_ss = self .samplenum
189- elif self .matched [1 ] and self .currbit_ss and not self .currbit_es :
190- # End of bit
191- self .currbit_es = self .samplenum
192- elif self .matched [0 ] and self .currbit_es and self .currbit_ss :
193- # Have complete bit, can handle bit now
194- result = [self .handle_bit (self .currbit_ss ,self .currbit_es ,self .samplenum )]
195- # print(result)
196- results += result
197- self .currbit_ss = self .samplenum
198- self .currbit_es = None
281+ match self .state :
282+ case State .CMD :
283+ if not self .bidirectional :
284+ pins = self .wait ([{0 : 'r' }, {0 : 'f' }, {'skip' : self .samples_after_motorcmd }])
285+ else :
286+ pins = self .wait ([{0 : 'f' }, {0 : 'r' }, {'skip' : self .samples_after_motorcmd }])
287+ #TODO: Increase skip to maximum time for effiency
288+ #TODO: Mark any changes in this time as errors? Option to reduce load?
289+
290+ if self .currbit_ss and self .currbit_es and self .matched [2 ]:
291+ # Assume end of packet if have seen start and end of a potential bit but no further change within 3 periods
292+ # TODO: Confirm wait period this works with spec
293+ results += [self .handle_bit_dshot (self .currbit_ss , self .currbit_es ,
294+ (self .currbit_ss + self .samples_pp ))]
295+ self .currbit_ss = None
296+ self .currbit_es = None
297+
298+ # Pass results to decoder
299+ result = self .handle_bits_dshot (results )
300+ if result and self .bidirectional :
301+ self .state = State .TELEM
302+ results = []
303+
304+ if self .matched [0 ] and not self .currbit_ss and not self .currbit_es :
305+ # Start of bit
306+ self .currbit_ss = self .samplenum
307+ elif self .matched [1 ] and self .currbit_ss and not self .currbit_es :
308+ # End of bit
309+ self .currbit_es = self .samplenum
310+ elif self .matched [0 ] and self .currbit_es and self .currbit_ss :
311+ # Have complete bit, can handle bit now
312+ result = [self .handle_bit_dshot (self .currbit_ss , self .currbit_es , self .samplenum )]
313+ # print(result)
314+ results += result
315+ self .currbit_ss = self .samplenum
316+ self .currbit_es = None
317+ case State .TELEM :
318+ match self .state_telem :
319+ case State_Telem .START :
320+ # First wait for falling edge (idle high)
321+ pins = self .wait ([{0 : 'f' }])
322+ # Save start pulse
323+ tlm_start = self .samplenum
324+ # Switch to receiving state
325+ self .state_telem = State_Telem .RECV
326+ # TODO: Check if still low after 1/8 bitlength for error det?
327+ case State_Telem .RECV :
328+ # First conditions skips half bit width and matches low
329+ # Second condition skips half bit width and matches high
330+ pins = self .wait ([{0 : 'l' , 'skip' : self .telem_baudrate_midpoint },
331+ {0 : 'h' , 'skip' : self .telem_baudrate_midpoint }])
332+
333+ # Append next bit
334+ curr_bit = self .handle_telem_bit (self .matched )
335+ self .put (self .samplenum - self .telem_baudrate_midpoint ,
336+ self .samplenum + self .telem_baudrate_midpoint ,
337+ self .out_ann ,
338+ [5 , ['%04d' % curr_bit ]])
339+ telem = telem | curr_bit
340+
341+ # Skip half bitwidth to end of bit
342+ pins = self .wait ([{'skip' : self .telem_baudrate_midpoint }])
343+
344+ if telem .bit_length () >= 20 - 1 :
345+ self .process_telem (telem ,tlm_start ,self .samplenum )
346+
347+ # Reset
348+ telem = 0b0
349+ self .state_telem = State_Telem .START
350+ self .state = State .CMD
351+ else :
352+ # Shift for next bit
353+ telem = telem << 1
354+ # If not mark as error
355+
356+ # Then skip x samples and sample
357+ # Repeat for 21 bits (TBC)
358+
359+ #TODO: What happens if it gets stuck in the wrong state?
360+
361+
362+
363+
199364
200365
201366
0 commit comments