Implementing SENT Interface in MicroPython?
Implementing SENT Interface in MicroPython?
Hallo *,
I planned to implement a simulator for the SENT-Interface (Single Edge Nibble Transmission) in Micropython in an OLIMEX STM32-H405 board.
Unfortunately I found out, that the Micropython Main loop is too slow for the bit generation according to the SENT-Spec. I need at least a 9µs low-pulse generation possibility:
(yellow pattern is from a SENT-Sensor - blue pattern from my STM32-H405 board - simple toggle in the main loop)
With a Timer - faster but only periodically pattern are possible. But I need a programmable bit pattern generator with a smallest time unit of 3µs which I can programm freely. Would that be possible with MicroPython - may be with inline assembler?
I tried to toggle my pin simply in the Main-Loop. The measured Low-Time was not below 17µs. Also a Timer with a callback function showed this minimal possible low-time no matter which frequency I set.
Any hints?
kind regards
Mario
I planned to implement a simulator for the SENT-Interface (Single Edge Nibble Transmission) in Micropython in an OLIMEX STM32-H405 board.
Unfortunately I found out, that the Micropython Main loop is too slow for the bit generation according to the SENT-Spec. I need at least a 9µs low-pulse generation possibility:
(yellow pattern is from a SENT-Sensor - blue pattern from my STM32-H405 board - simple toggle in the main loop)
With a Timer - faster but only periodically pattern are possible. But I need a programmable bit pattern generator with a smallest time unit of 3µs which I can programm freely. Would that be possible with MicroPython - may be with inline assembler?
I tried to toggle my pin simply in the Main-Loop. The measured Low-Time was not below 17µs. Also a Timer with a callback function showed this minimal possible low-time no matter which frequency I set.
Any hints?
kind regards
Mario
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Implementing SENT Interface in MicroPython?
17µs is about what I'd expect. This doc explains how to optimise speed, but as you mentioned you may need to use the inline assembler. This can achieve very high performance. It is also possible to write native C modules, however the learning curve for these is similar to that of the assembler (in my opinion).
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: Implementing SENT Interface in MicroPython?
Hi pythoncoder,
thanx for the fast reply. So as a mentioned - inline C or assembler would be possible. So I try to implement the bit generator in C as a Timer callback. Is it shure, that the timer callback will be performed all 3µs when I install it like this:
tim = pyb.Timer(1)
tim.init(freq=333333)
tim.callback(tick)
The callback "tick" then would be an inline C-function with direct memory access to the bitfield to send und with direct GPIO access.
Writing in the bitfield to change the data - can be done from PYTHON side.
What do you think?
kind regards
Mario
thanx for the fast reply. So as a mentioned - inline C or assembler would be possible. So I try to implement the bit generator in C as a Timer callback. Is it shure, that the timer callback will be performed all 3µs when I install it like this:
tim = pyb.Timer(1)
tim.init(freq=333333)
tim.callback(tick)
The callback "tick" then would be an inline C-function with direct memory access to the bitfield to send und with direct GPIO access.
Writing in the bitfield to change the data - can be done from PYTHON side.
What do you think?
kind regards
Mario
Re: Implementing SENT Interface in MicroPython?
For test of a decoder I made a python script that creates and sends SENT messages. I used an ESP32 with it's RMT module for the test. Code attached. it includes proper CRC calculation, which was the hard part, given that almost no test data was available.
Code: Select all
from machine import Pin
from esp32 import RMT
# Constants
data_pin = const(13) # GPIO13
LOW = const(5) # Low pulse width in ticks
SYNC = const(56 - LOW) # SYNC Pulse width
OFFSET = const(12 - LOW) # Nibble offset
MAXTICK = const(272) # 56 + 8 * 27 ticks
crc4_tab = (0, 13, 7, 10, 14, 3, 9, 4, 1, 12, 6, 11, 15, 2, 8, 5)
p = Pin(data_pin, Pin.OUT)
def frame(msg, status, idle, pause):
'''
Generate the frame pulse train. Arguments:
msg: value to be sent as 3 byte bytes string
status: Status value
idle level: 0 for low, 1 for high
pause: minimal pause length. 0 for no pause
'''
# create the pulse sequence
crc = 5 # Magic start value
# calibration pulse
data = [LOW, SYNC]
# Status nibble
data.append(LOW)
data.append(status + OFFSET) # Status nibble
# Data nibbles
for v in msg:
nibble = (v >> 4) & 0xf
crc = crc4_tab[crc] ^ nibble
data.append(LOW)
data.append(nibble + OFFSET)
nibble = v & 0xf
crc = crc4_tab[crc] ^ nibble
data.append(LOW)
data.append(nibble + OFFSET)
crc = crc4_tab[crc] # padding nibble
# CRC nibble
data.append(LOW)
data.append(crc + OFFSET) # crc nibble
# Add a pause pulse, if requested
if pause == 1:
data.append(LOW)
data.append(LOW)
elif pause > 0:
pause = max(pause, 12) # Ensure minimal length of 12 ticks
data.append(LOW)
data.append((MAXTICK - sum(data)) + (pause - LOW)) # pause pulse
return data
def send_data(data, idle, tick):
'''
Send the data frame(s). Arguments:
data: list with the tick values
idle: Idle level of the data line
tick: tick time in us units
'''
# set up the interface
idle = 1 if idle else 0 # map idle value to 0 and 1
p.value(idle)
if idle: # if idle is high, the idle level has to be 1. Not supported
# by the main branch of MicroPython
rmt = RMT(0, pin=p, clock_div=8, idle=1) # 100 ns tick granularity, idle=1
else:
rmt = RMT(0, pin=p, clock_div=8) # 100 ns tick granularity, idle=0 (default)
tick = int(tick * 10 + 0.49) # scale to 100 ns granularity
# Mutiply items by the internal tick units
for i in range(len(data)):
data[i] *= tick
# send the pulses
rmt.write_pulses(data, start=1 - idle)
rmt.wait_done(timeout=sum(data)) # Wait for the end
rmt.deinit()
# send a single fast data frame with two data values
def fast(value1, value2=None, status=0, idle=1, pause=12, tick=3):
'''
Send a single data frame in fast data format. Arguments:
value1, value2: values to be sent; integer
status: Status value
idle level: 0 for low, 1 for high
pause: minimal pause length. 0 for no pause
tick: tick time in us units
'''
msg = bytearray(3)
msg[0] = ((value1 >> 4) & 0xff)
# msg[1] = ((value1 << 4) & 0xf0) | ((value2 >> 8) & 0x0f)
# msg[2] = value2 & 0xff
if value2 is None:
value2 = value1
# value2 with the nibbles in opposite order:
msg[1] = ((value1 << 4) & 0xf0) | (value2 & 0x0f)
msg[2] = (value2 & 0xf0) | ((value2 >> 8) & 0xf)
# value 2 is sent in regular order.
# msg[1] = ((value1 << 4) & 0xf0) | ((value2 >> 8) & 0x0f)
# msg[2] = value2 & 0xff
data = frame(msg, status, idle, pause)
send_data(data, idle, tick)
# send a single secure message frame
def secure(value, counter=0, status=0, idle=1, pause=12, tick=3):
'''
Send a single secure message frame. Arguments:
value: value to be sent; integer
counter: counter; integer
status: Status value
idle level: 0 for low, 1 for high
pause: minimal pause length. 0 for no pause
tick: tick time in us units
'''
msg = bytearray(3)
msg[0] = ((value >> 4) & 0xff)
msg[1] = ((value & 0x0f) << 4) | ((counter >> 4) & 0x0f)
msg[2] = ((counter & 0x0f) << 4) | ((~value >> 8) & 0x0f)
data = frame(msg, status, idle, pause)
send_data(data, idle, tick)
# send a single fast channel high speed frame
def highspeed(value, status=0, idle=1, pause=12, tick=2.7):
'''
Send a fast channel high speed frame. Arguments:
value: value to be sent; integer
status: Status value
idle level: 0 for low, 1 for high
pause: minimal pause length. 0 for no pause
tick: tick time in us units
'''
msg = bytearray(2)
msg[0] = ((value >> 5) & 0x70) | ((value >> 6 ) & 0x07)
msg[1] = ((value << 1) & 0x70) | (value & 0x07)
data = frame(msg, status, idle, pause)
send_data(data, idle, tick)
# send a short serial message encoded in a burst of 16 frames
def serial(value, id=0, idle=0, pause=12, tick=3, dummy=True):
'''
Send a short serial message with a burst of 16 frames. Arguments:
value: value to be sent; integer
id: ID; integer
idle level: 0 for low, 1 for high
pause: minimal pause length. 0 for no pause
tick: tick time in us units
dummy: Flag telling whether a additional trailing frame will be sent
'''
data = []
crc = 5 # Magic start value
crc = crc4_tab[crc] ^ (id & 0x0f)
crc = crc4_tab[crc] ^ ((value >> 4) & 0x0f)
crc = crc4_tab[crc] ^ (value & 0x0f)
crc = crc4_tab[crc] # padding nibble
value = ((id & 0x0f) << 12) | ((value & 0xff) << 4) | crc
for i in range(16):
if i == 0:
status = ((value >> 13) & 0x04) | 0x08
else:
status = ((value >> 13) & 0x04)
data += frame(chr(i).encode() + b"\x00\x00", status, idle, pause)
value <<= 1
# adding a dummy frame to cope with the SD2000x+ expectation
if dummy:
data += frame(b"\xff\x00\x00", 0, idle, pause)
send_data(data, idle, tick)
# send an enhanced serial message with 12 or 16 bit in a 18 frame burst
def enhanced(value, id=0, long=False, idle=0, pause=12, tick=3, dummy=True):
'''
Send an enhanced serial message with a burst of 18 frames. Arguments:
value: value to be sent; integer
id: ID; integer
long: True for 16 bit, False for 12 bit,
idle level: 0 for low, 1 for high
pause: minimal pause length. 0 for no pause
tick: tick time in us units
dummy: Flag telling whether am additional trailing frame will be sent
'''
# Pack the data into two bit fields for bit2 and bit3
if long:
bit3 = ((id & 0x0f) << 6) | ((value >> 11) & 0x1e) | 0x400
else:
bit3 = ((id & 0xf0) << 2) | ((id & 0x0f) << 1)
bit2 = value & 0xfff
# calculate the CRC bitwise
polynome = 0b011001 # (x^6 +) x^4 + x^3 + 1 (1 is x^0 -> lowest bit set, x^6 is implicit)
uprmask = 0b100000 # Masking the upper bit of the crc
bitmask = 0x800
crc = 0x15 # Magic start value
for i in range(15): # 24 data bits + 6 implicit zero bits
crc = ((crc << 1) | ((bit2 & bitmask) != 0)) ^ (polynome if (crc & uprmask) else 0)
crc = ((crc << 1) | ((bit3 & bitmask) != 0)) ^ (polynome if (crc & uprmask) else 0)
bitmask >>= 1
crc &= 0x3f
# add the crc to bit2 and "enhanced serial" flag to bit3
bit2 |= (crc << 12) # crc sixbit
bit3 |= 0x3f000 # ones sixbit
# create the frame sequence
data = []
for i in range(18):
status = ((bit2 >> 15) & 0x04) | ((bit3 >> 14) & 0x08)
data += frame(chr(i).encode() + b"\x00\x00", status, idle, pause)
bit2 <<= 1
bit3 <<= 1
# adding a dummy frame to cope with the SD2000x+ expectation
if dummy:
data += frame(b"\xff\x00\x00", 0, idle, pause)
send_data(data, idle, tick)
Re: Implementing SENT Interface in MicroPython?
Thanx Roberthh,
unfortunately my self-compiled OLIMEX-H405 port (STM32) has not yet the RMT module. May be I can add this?
But I think not - its an other processor...
But nevertheless thanx for the code - I have the same challange with the CRC
best regards
Mario
unfortunately my self-compiled OLIMEX-H405 port (STM32) has not yet the RMT module. May be I can add this?
But I think not - its an other processor...
But nevertheless thanx for the code - I have the same challange with the CRC
best regards
Mario
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Implementing SENT Interface in MicroPython?
RMT is an ESP32 hardware component so cannot be implemented on STM.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: Implementing SENT Interface in MicroPython?
Hi Peter,
I already found this out. What do you think about my implementation idea according the bit-generation:
"Is it shure, that the timer callback will be performed all 3µs when I install it like this:
tim = pyb.Timer(1)
tim.init(freq=333333)
tim.callback(tick)
The callback "tick" then would be an inline C-function with direct memory access to the bitfield to send und with direct GPIO access.
Writing in the bitfield to change the data - can be done from PYTHON side."
I first make a try with direkt GPIO access in MicroPython in the "tick" callback - may be it is still sufficiant...
best regards
Mario
I already found this out. What do you think about my implementation idea according the bit-generation:
"Is it shure, that the timer callback will be performed all 3µs when I install it like this:
tim = pyb.Timer(1)
tim.init(freq=333333)
tim.callback(tick)
The callback "tick" then would be an inline C-function with direct memory access to the bitfield to send und with direct GPIO access.
Writing in the bitfield to change the data - can be done from PYTHON side."
I first make a try with direkt GPIO access in MicroPython in the "tick" callback - may be it is still sufficiant...
best regards
Mario
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Implementing SENT Interface in MicroPython?
I have no idea whether this is feasible - I think you're breaking new ground here. It depends on whether there is a significant overhead in calling a C function which was specified in Python as an ISR. Otherwise you may need to figure out how, in C, to allocate a function to an interrupt. I've often wondered about doing this kind of thing, but I've yet to try it.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: Implementing SENT Interface in MicroPython?
I do not expect it to work at that frequency. The time to call a python function on a F407 is about 4 µs.
Re: Implementing SENT Interface in MicroPython?
I tried it one time again with direct register access of the GPIO in the ISR. The low-time goes to 15,20µs - which is not sufficiant.
The problem would be the slow ISR-call on Python level as you mentioned - I think. So it makes no difference if the ISR itselfes is native C or not.
So - I switch to complete C-Implementation...
May be I simply introduce a Python command/object in the STM port of the MicroPython interpreter? I built my image from source - so it would be possible. What do you think? Is it simple to make own Python-callable functions - completely embedded in the Interpreter with own C-Code?
I only need the Bit-Generator on C-Level. It needs to toggle a bit-array out with correct timing.
thanx for your hints so far...
The problem would be the slow ISR-call on Python level as you mentioned - I think. So it makes no difference if the ISR itselfes is native C or not.
So - I switch to complete C-Implementation...
May be I simply introduce a Python command/object in the STM port of the MicroPython interpreter? I built my image from source - so it would be possible. What do you think? Is it simple to make own Python-callable functions - completely embedded in the Interpreter with own C-Code?
I only need the Bit-Generator on C-Level. It needs to toggle a bit-array out with correct timing.
thanx for your hints so far...