Skip to content

Commit 48dc4ab

Browse files
initial commit
1 parent 63b3630 commit 48dc4ab

3 files changed

Lines changed: 210 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__pycache__/

__init__.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
## Modified from rgb_led_ws281x - original license below:
2+
## Copyright (C) 2023: hyp0dermik@gmail.com
3+
4+
##
5+
## This file is part of the libsigrokdecode project.
6+
##
7+
## Copyright (C) 2016 Vladimir Ermakov <vooon341@gmail.com>
8+
##
9+
## This program is free software; you can redistribute it and/or modify
10+
## it under the terms of the GNU General Public License as published by
11+
## the Free Software Foundation; either version 3 of the License, or
12+
## (at your option) any later version.
13+
##
14+
## This program is distributed in the hope that it will be useful,
15+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
## GNU General Public License for more details.
18+
##
19+
## You should have received a copy of the GNU General Public License
20+
## along with this program; if not, see <http://www.gnu.org/licenses/>.
21+
##
22+
23+
'''
24+
DShot protocol decoder.
25+
26+
Details:
27+
https://brushlesswhoop.com/dshot-and-bidirectional-dshot/
28+
'''
29+
30+
from .pd import Decoder

pd.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
## Modified from rgb_led_ws281x - original license below:
2+
## Copyright (C) 2023: hyp0dermik@gmail.com
3+
4+
##
5+
## This file is part of the libsigrokdecode project.
6+
##
7+
## Copyright (C) 2016 Vladimir Ermakov <vooon341@gmail.com>
8+
##
9+
## This program is free software; you can redistribute it and/or modify
10+
## it under the terms of the GNU General Public License as published by
11+
## the Free Software Foundation; either version 3 of the License, or
12+
## (at your option) any later version.
13+
##
14+
## This program is distributed in the hope that it will be useful,
15+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
## GNU General Public License for more details.
18+
##
19+
## You should have received a copy of the GNU General Public License
20+
## along with this program; if not, see <http://www.gnu.org/licenses/>.
21+
##
22+
23+
import sigrokdecode as srd
24+
from functools import reduce
25+
26+
27+
28+
class SamplerateError(Exception):
29+
pass
30+
31+
class Decoder(srd.Decoder):
32+
api_version = 3
33+
id = 'dshot'
34+
name = 'DShot'
35+
longname = 'DShot RC Hobby Motor Protcol Decoder'
36+
desc = 'DShot RC Hobby Motor Protcol Decoder'
37+
license = 'gplv3+'
38+
inputs = ['logic']
39+
outputs = []
40+
tags = ['Display', 'IC']
41+
channels = (
42+
{'id': 'din', 'name': 'DIN', 'desc': 'DIN data line'},
43+
)
44+
45+
options = (
46+
{'id': 'dshot_rate', 'desc': 'DShot Rate', 'default': '150','values': ('150', '300','600','1200')},
47+
{ 'id': 'bidir', 'desc': 'Bidirectional DShot','default': 'False', 'values': ('True', 'False')},
48+
{ 'id': 'log', 'desc': 'Write log file','default': 'no', 'values': ('yes', 'no')},
49+
)
50+
annotations = (
51+
('bit', 'Bit'),
52+
('cmd', 'Command'),
53+
('throttle', 'Throttle'),
54+
('dshot_errors', 'Throttle'),
55+
56+
)
57+
annotation_rows = (
58+
('bit', 'Bits', (0,)),
59+
('throttle', 'Throttle', (1,2,)),
60+
('dshot_errors', 'Dshot Errors', (3,)),
61+
)
62+
63+
dshot_period_lookup = {'150': 6.67e-6, '300': 3.33e-6,'600':1.67e-6,'1200':0.83e-6}
64+
65+
def __init__(self):
66+
self.reset()
67+
68+
def reset(self):
69+
self.samplerate = None
70+
self.oldpin = None
71+
self.ss_packet = None
72+
self.ss = None
73+
self.es = None
74+
self.bits = []
75+
self.inreset = False
76+
self.bidirectional = False
77+
self.dshot_period = 3.33e-6
78+
79+
80+
def start(self):
81+
self.bidirectional = True if self.options['bidir'] == 'True' else False
82+
self.dshot_period = self.dshot_period_lookup[self.options['dshot_rate']]
83+
print("start period",self.dshot_period)
84+
self.out_ann = self.register(srd.OUTPUT_ANN)
85+
86+
def metadata(self, key, value):
87+
if key == srd.SRD_CONF_SAMPLERATE:
88+
self.samplerate = value
89+
90+
def handle_bits(self, samplenum):
91+
if len(self.bits) == 16:
92+
# rgb = (grb & 0xff0000) >> 8 | (grb & 0x00ff00) << 8 | (grb & 0x0000ff)
93+
print(self.bits)
94+
throttle = int(reduce(lambda a, b: (a << 1) | b, self.bits[:11]))
95+
self.put(self.ss_packet, samplenum, self.out_ann,
96+
[2, ['%04d' % throttle]])
97+
self.bits = []
98+
self.ss_packet = None
99+
else:
100+
self.put(self.es, self.samplenum, self.out_ann,
101+
[1, ['ERROR', 'ERR', 'E']])
102+
103+
104+
def handle_bit(self):
105+
if self.ss and self.es:
106+
period = self.samplenum - self.ss
107+
duty = self.es - self.ss
108+
# Ideal duty for T0H: 33%, T1H: 66%.
109+
bit_ = (duty / period) > 0.5
110+
111+
self.put(self.ss, self.samplenum, self.out_ann,
112+
[0, ['%d' % bit_]])
113+
114+
self.bits.append(bit_)
115+
#self.handle_bits(self.samplenum)
116+
if self.ss_packet is None:
117+
self.ss_packet = self.samplenum
118+
self.ss = self.samplenum
119+
120+
def handle_notbit(self):
121+
self.inreset = False
122+
self.es = self.samplenum
123+
124+
def check_reset(self):
125+
# Decode last bit value.
126+
tH = (self.es - self.ss) / self.samplerate
127+
128+
# High if greater than half the period
129+
bit_ = True if tH >= (self.dshot_period/2) else False
130+
131+
self.bits.append(bit_)
132+
self.handle_bits(self.es)
133+
134+
self.put(self.ss, self.es, self.out_ann, [0, ['%d' % bit_]])
135+
# self.put(self.es, self.samplenum, self.out_ann,
136+
# [1, ['RESET', 'RST', 'R']])
137+
138+
self.inreset = True
139+
self.bits = []
140+
self.ss_packet = None
141+
self.ss = None
142+
def decode(self):
143+
if not self.samplerate:
144+
raise SamplerateError('Cannot decode without samplerate.')
145+
146+
while True:
147+
# TODO: Come up with more appropriate self.wait() conditions.
148+
(pin,) = self.wait()
149+
150+
if self.oldpin is None:
151+
self.oldpin = pin
152+
continue
153+
154+
# Check idle condition if longer is greater than 2x max period
155+
# TODO: Confirm this with spec
156+
if not self.bidirectional:
157+
if not self.inreset and not pin and self.es is not None and \
158+
self.ss is not None and \
159+
(self.samplenum - self.es) / self.samplerate > self.dshot_period*2:
160+
self.check_reset()
161+
if not self.oldpin and pin:
162+
# Rising edge
163+
self.handle_bit()
164+
elif self.oldpin and not pin:
165+
# Falling edge
166+
self.handle_notbit()
167+
else:
168+
if self.oldpin and not pin:
169+
# Falling edge.
170+
self.handle_bit()
171+
elif not self.oldpin and pin:
172+
# Rising edge.
173+
self.handle_notbit()
174+
175+
self.oldpin = pin
176+
177+
178+
179+

0 commit comments

Comments
 (0)