Skip to content

Commit ff6f5ba

Browse files
Refactor to modules/classes for unit testing (#5)
* initial - back to motor command bits barely recognized * motor bits displayed correctly, checksum passing failing on telem * tidyup, move telem data * telem bits rxed again, no reset * Split modules off into package for unit testing
1 parent 5f4aeaf commit ff6f5ba

4 files changed

Lines changed: 311 additions & 191 deletions

File tree

pd.py

Lines changed: 84 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -23,37 +23,25 @@
2323
import sigrokdecode as srd
2424
from functools import reduce
2525
from enum import Enum
26+
from dshot.protoDshot import DshotCmd, DshotTelem, BitDshot, DshotSettings, Bit_DshotTelem
27+
2628

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-
}
4529

4630
class SamplerateError(Exception):
4731
pass
4832

4933
class State(Enum):
50-
CMD = 1,
34+
RESET = 0
35+
CMD = 1
5136
TELEM = 2
5237

38+
5339
class State_Telem(Enum):
54-
START = 1,
40+
RESET = 0
41+
START = 1
5542
RECV = 2
5643

44+
5745
class Decoder(srd.Decoder):
5846
api_version = 3
5947
id = 'dshot'
@@ -106,200 +94,100 @@ def __init__(self):
10694

10795
def reset(self):
10896
self.state = State.CMD
109-
self.samplerate = None
110-
111-
self.debug = False
97+
self.state_telem = State_Telem.START
11298

99+
self.samplerate = None
113100
self.inreset = False
114-
self.bidirectional = False
115-
self.dshot_kbaud = 300e3
116-
self.dshot_period = 3.33e-6
117-
self.actual_period = None
118-
self.halfbitwidth = None
119101
self.currbit_ss = None
120102
self.currbit_es = None
121-
self.samples_after_motorcmd = None
122-
self.samples_pp = None
123103

124-
self.telem_start = None
125-
self.state_telem = State_Telem.START
126-
self.telem_baudrate_midpoint = 0
127-
self.edt_force = False
104+
self.dshot_cfg = DshotSettings()
105+
106+
self.debug = False
128107

129108
def start(self):
130-
self.bidirectional = True if self.options['bidir'] == 'True' else False
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
134-
self.samples_pp = int(self.samplerate*self.dshot_period)
135-
self.samples_after_motorcmd = self.samples_pp * 3
136-
self.samples_after_telempkt = self.samples_pp * 3
109+
self.dshot_cfg.bidirectional = True if self.options['bidir'] == 'True' else False
110+
self.dshot_cfg.edt_force = True if self.options['edt_force'] == 'True' else False
111+
self.dshot_cfg.dshot_kbaud = int(self.options['dshot_rate'])*1000
112+
self.dshot_cfg.samplerate = self.samplerate
113+
self.dshot_cfg.update()
137114

138115
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)
142116

143117
def metadata(self, key, value):
144118
if key == srd.SRD_CONF_SAMPLERATE:
145119
self.samplerate = value
146120

147-
def handle_bits_dshot(self, results):
148-
#ss, es, bit
149-
bits = [result[2] for result in results]
150-
151-
if len(bits) == 16:
152-
dshot_value = int(reduce(lambda a, b: (a << 1) | b, bits[:11]))
153-
telem_request = bits[11]
154-
received_crc = int(reduce(lambda a, b: (a << 1) | b, bits[12:]))
155-
156-
value_tocrc = int(reduce(lambda a, b: (a << 1) | b, bits[:12]))
157-
158-
if self.bidirectional:
159-
calculated_crc = int((~(value_tocrc ^ (value_tocrc >> 4) ^ (value_tocrc >> 8)))&0x0F)
160-
else:
161-
calculated_crc = int(((value_tocrc ^ (value_tocrc >> 4) ^ (value_tocrc >> 8)))&0x0F)
162-
163-
if received_crc == calculated_crc:
164-
crc_ok = True
165-
else:
166-
crc_ok = False
167-
168-
# TODO: Align this correctly
169-
crc_startsample = results[12][0]
170-
171-
# Split annotation based on value type
172-
if dshot_value < 48:
173-
# Command
174-
self.put(results[0][0], crc_startsample, self.out_ann,
175-
[1, ['%04d' % dshot_value]])
176-
else:
177-
# Throttle
178-
self.put(results[0][0], crc_startsample, self.out_ann,
179-
[2, ['%04d' % dshot_value]])
180-
181-
self.put(crc_startsample, results[15][1], self.out_ann, [3, ['Calc CRC: '+('%04d' % calculated_crc)+' TXed CRC:'+('%04d' % received_crc)]])
182-
if not crc_ok:
183-
self.put(crc_startsample, results[15][1], self.out_ann,
184-
[4, ['CRC INVALID']])
185-
return True
186-
else:
187-
return False
188-
189-
def handle_bit_dshot(self, ss, es, nb_ss):
190-
period = nb_ss - ss
191-
duty = es - ss
192-
# Ideal duty for T0H: 33%, T1H: 66%.
193-
bit_ = (duty / period) > 0.5
194-
195-
self.put(ss, nb_ss, self.out_ann,
196-
[0, ['%d' % bit_]])
197-
return [ss,nb_ss,bit_]
198-
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)
266121

267-
return
268122

123+
def display_dshot(self,dshot):
124+
crc_startsample = dshot.results[12].ss
125+
126+
# Split annotation based on value type
127+
if dshot.dshot_value < 48:
128+
# Command
129+
self.put(dshot.results[0].ss, crc_startsample, self.out_ann,
130+
[1, ['%04d' % dshot.dshot_value]])
131+
else:
132+
# Throttle
133+
self.put(dshot.results[0].ss, crc_startsample, self.out_ann,
134+
[2, ['%04d' % dshot.dshot_value]])
135+
136+
self.put(crc_startsample, dshot.results[15].es, self.out_ann,
137+
[3, ['Calc CRC: ' + ('%04d' % dshot.crc_calc) + ' TXed CRC:' + ('%04d' % dshot.crc_recv)]])
138+
if not dshot.crc_ok:
139+
self.put(crc_startsample, dshot.results[15].es, self.out_ann,
140+
[4, ['CRC INVALID']])
269141

270142

143+
def complete_DshotBit(self, *args):
144+
bitseq = BitDshot(*args)
145+
146+
self.put(bitseq.ss, bitseq.es, self.out_ann,
147+
[0, ['%d' % bool(bitseq)]])
148+
return [bitseq]
271149

272150
def decode(self):
273151
if not self.samplerate:
274152
raise SamplerateError('Cannot decode without samplerate.')
275-
153+
154+
dshot_value = DshotCmd(self.dshot_cfg)
155+
telem_value = DshotTelem(self.dshot_cfg)
276156
results = []
277157
telem = 0b0
278158
tlm_start = 0
159+
160+
#bitseq = BitDshot()
279161
while True:
280162

281163
match self.state:
282164
case State.CMD:
283-
if not self.bidirectional:
284-
pins = self.wait([{0: 'r'}, {0: 'f'}, {'skip': self.samples_after_motorcmd}])
165+
if not self.dshot_cfg.bidirectional:
166+
pins = self.wait([{0: 'r'}, {0: 'f'}, {'skip': self.dshot_cfg.samples_after_motorcmd}])
285167
else:
286-
pins = self.wait([{0: 'f'}, {0: 'r'}, {'skip': self.samples_after_motorcmd}])
168+
pins = self.wait([{0: 'f'}, {0: 'r'}, {'skip': self.dshot_cfg.samples_after_motorcmd}])
287169
#TODO: Increase skip to maximum time for effiency
288170
#TODO: Mark any changes in this time as errors? Option to reduce load?
289171

290172
if self.currbit_ss and self.currbit_es and self.matched[2]:
291173
# Assume end of packet if have seen start and end of a potential bit but no further change within 3 periods
292174
# 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))]
175+
176+
args = self.currbit_ss, self.currbit_es, (self.currbit_ss + self.dshot_cfg.samples_pp)
177+
results += self.complete_DshotBit(*args)
295178
self.currbit_ss = None
296179
self.currbit_es = None
297-
180+
#print(results)
298181
# Pass results to decoder
299-
result = self.handle_bits_dshot(results)
300-
if result and self.bidirectional:
182+
183+
result = dshot_value.handle_bits_dshot(results)
184+
if result:
185+
self.display_dshot(dshot_value)
186+
if result and self.dshot_cfg.bidirectional:
301187
self.state = State.TELEM
188+
302189
results = []
190+
#dshot_value = DshotCmd(self.dshot_cfg)
303191

304192
if self.matched[0] and not self.currbit_ss and not self.currbit_es:
305193
# Start of bit
@@ -309,13 +197,17 @@ def decode(self):
309197
self.currbit_es = self.samplenum
310198
elif self.matched[0] and self.currbit_es and self.currbit_ss:
311199
# 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
200+
args = self.currbit_ss, self.currbit_es, self.samplenum
201+
results += self.complete_DshotBit(*args)
202+
315203
self.currbit_ss = self.samplenum
316204
self.currbit_es = None
317205
case State.TELEM:
318206
match self.state_telem:
207+
case State_Telem.RESET:
208+
telem = 0b0
209+
self.state_telem = State_Telem.START
210+
319211
case State_Telem.START:
320212
# First wait for falling edge (idle high)
321213
pins = self.wait([{0: 'f'}])
@@ -327,35 +219,36 @@ def decode(self):
327219
case State_Telem.RECV:
328220
# First conditions skips half bit width and matches low
329221
# 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}])
222+
pins = self.wait([{0: 'l', 'skip': self.dshot_cfg.telem_baudrate_midpoint},
223+
{0: 'h', 'skip': self.dshot_cfg.telem_baudrate_midpoint}])
332224

333225
# 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,
226+
args = (self.samplenum - self.dshot_cfg.telem_baudrate_midpoint), self.samplenum, (self.samplenum + self.dshot_cfg.telem_baudrate_midpoint)
227+
curr_bit = Bit_DshotTelem(*args,self.matched)
228+
self.put(curr_bit.ss,curr_bit.es,
337229
self.out_ann,
338-
[5, ['%04d' % curr_bit]])
339-
telem = telem | curr_bit
230+
[5, ['%04d' % curr_bit.bit_]])
231+
232+
telem_value.add_bit(curr_bit)
233+
340234

341235
# Skip half bitwidth to end of bit
342-
pins = self.wait([{'skip': self.telem_baudrate_midpoint}])
236+
pins = self.wait([{'skip': self.dshot_cfg.telem_baudrate_midpoint}])
343237

344-
if telem.bit_length() >= 20-1:
345-
self.process_telem(telem,tlm_start,self.samplenum)
238+
if telem_value.bits.bit_length() >= 20:
239+
telem_value.process_telem()
346240

347241
# Reset
348-
telem = 0b0
349-
self.state_telem = State_Telem.START
242+
self.state_telem = State_Telem.RESET
243+
# Except Dshot packet next
350244
self.state = State.CMD
351-
else:
352-
# Shift for next bit
353-
telem = telem << 1
245+
354246
# If not mark as error
355247

356248
# Then skip x samples and sample
357249
# Repeat for 21 bits (TBC)
358250

251+
359252
#TODO: What happens if it gets stuck in the wrong state?
360253

361254

protoDshot/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .dshot_bits import *
2+
from .protocols_motor import *

0 commit comments

Comments
 (0)