mirror of https://github.com/joan2937/pigpio
added an example of using the pigpio library to implement the SENT interface.
SENT is an automotive standard single wire interface for robust communication with sensors in a car, but the standard can be used anywhere. It has error detection including CRC checking. There is a write up about it on the Blog: https://surfncircuits.com/2020/11/27/implementing-a-single-edge-nibble-transmission-sent-protocol-in-python-for-the-raspberry-pi-zero/
This commit is contained in:
parent
11ee6f19d8
commit
35aa210556
|
@ -0,0 +1,28 @@
|
||||||
|
# Python Class for Reading Single Edge Nibble Transmission (SENT) using the Raspberry Pi
|
||||||
|
|
||||||
|
A full description of this Python script is described at [www.surfncircuits.com](https://surfncircuits.com) in the blog entry: [Implementing a Single Edge Nibble Transmission (SENT) protocol in Python for the Raspberry Pi Zero](https://surfncircuits.com/?p=3725)
|
||||||
|
|
||||||
|
|
||||||
|
This python library will read a Raspberry Pi GPIO pin connected. Start the pigpiod daemon with one microsecond sampling to read SENT transmissions with three microsecond tick times.
|
||||||
|
|
||||||
|
## To start the daemon on Raspberry Pi
|
||||||
|
- sudo pigpiod -s 1
|
||||||
|
|
||||||
|
## SENT packet frame summary
|
||||||
|
|
||||||
|
- Sync Pulse: 56 ticks
|
||||||
|
- 4 bit Status and Message Pulse: 17-32 ticks
|
||||||
|
- 4 bit (9:12) Data1 Field: 17-32 ticks
|
||||||
|
- 4 bit (5:8) Data1 Field: 17-32 ticks
|
||||||
|
- 4 bit (1:4) Data1 Field: 17-32 ticks
|
||||||
|
- 4 bit (9-12) Data2 Field: 17-32 ticks
|
||||||
|
- 4 bit (5-8) Data2 Field: 17-32 ticks
|
||||||
|
- 4 bit (1-4) Data2 Field: 17-32 ticks
|
||||||
|
- 4 bit CRC: 17-32 ticks
|
||||||
|
|
||||||
|
|
||||||
|
## requirements
|
||||||
|
[pigpiod](http://abyz.me.uk/rpi/pigpio/) library required
|
||||||
|
|
||||||
|
## To run the script for a signal attached to GPIO BCD 18 (pin 12)
|
||||||
|
- python3 sent_READ.py
|
|
@ -0,0 +1,322 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# read_PWM.py
|
||||||
|
# Public Domain by mark smith, www.surfncircuits.com
|
||||||
|
# blog:https://surfncircuits.com/2020/11/27/implementing-a-single-edge-nibble-transmission-sent-protocol-in-python-for-the-raspberry-pi-zero/
|
||||||
|
|
||||||
|
import time
|
||||||
|
import pigpio # http://abyz.co.uk/rpi/pigpio/python.html
|
||||||
|
import threading
|
||||||
|
|
||||||
|
class SENTReader:
|
||||||
|
"""
|
||||||
|
A class to read short Format SENT frames
|
||||||
|
(see the LX3302A datasheet for a SENT reference from Microchip)
|
||||||
|
(also using sent transmission mode where )
|
||||||
|
from wikiPedia: The SAE J2716 SENT (Single Edge Nibble Transmission) protocol
|
||||||
|
is a point-to-point scheme for transmitting signal values
|
||||||
|
from a sensor to a controller. It is intended to allow for
|
||||||
|
transmission of high resolution data with a low system cost.
|
||||||
|
|
||||||
|
Short sensor format:
|
||||||
|
The first is the SYNC pulse (56 ticks)
|
||||||
|
first Nibble : Status (4 bits)
|
||||||
|
2nd NIbble : DAta1 (4 bits)
|
||||||
|
3nd Nibble : Data2 (4 bits)
|
||||||
|
4th Nibble : Data3 (4 bits)
|
||||||
|
5th Nibble : Data1 (4 bits)
|
||||||
|
6th Nibble : Data2 (4 bits)
|
||||||
|
7th Nibble : Data3 (4 bits)
|
||||||
|
8th Nibble : CRC (4 bits)
|
||||||
|
"""
|
||||||
|
def __init__(self, pi, gpio, Mode = 0):
|
||||||
|
"""
|
||||||
|
Instantiate with the Pi and gpio of the SENT signal
|
||||||
|
to monitor.
|
||||||
|
SENT mode = A0: Microchip LX3302A where the two 12 bit data values are identical. there are other modes
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.pi = pi
|
||||||
|
self.gpio = gpio
|
||||||
|
self.SENTMode = Mode
|
||||||
|
|
||||||
|
# the time that pulse goes high
|
||||||
|
self._high_tick = 0
|
||||||
|
# the period of the low tick
|
||||||
|
self._low_tick = 0
|
||||||
|
# the period of the pulse (total data)
|
||||||
|
self._period = 0
|
||||||
|
# the time the item was low during the period
|
||||||
|
self._low = 0
|
||||||
|
# the time the output was high during the period
|
||||||
|
self._high = 0
|
||||||
|
# setting initial value to 100
|
||||||
|
self.syncTick = 100
|
||||||
|
|
||||||
|
|
||||||
|
#keep track of the periods
|
||||||
|
self.syncWidth = 0
|
||||||
|
self.status = 0
|
||||||
|
self.data1 = 0
|
||||||
|
self.data2 = 0
|
||||||
|
self.data3 = 0
|
||||||
|
self.data4 = 0
|
||||||
|
self.data5 = 0
|
||||||
|
self.data6 = 0
|
||||||
|
self.crc = 0
|
||||||
|
#initize the sent frame . Need to use hex for data
|
||||||
|
#self.frame = [0,0,0,'0x0','0x0','0x0','0x0','0x0','0x0',0]
|
||||||
|
self.frame = [0,0,0,0,0,0,0,0,0,0]
|
||||||
|
self.syncFound = False
|
||||||
|
self.frameComplete = False
|
||||||
|
self.nibble = 0
|
||||||
|
self.numberFrames = 0
|
||||||
|
self.SampleStopped = False
|
||||||
|
|
||||||
|
self.pi.set_mode(gpio, pigpio.INPUT)
|
||||||
|
|
||||||
|
#self._cb = pi.callback(gpio, pigpio.EITHER_EDGE, self._cbf)
|
||||||
|
#sleep enougth to start reading SENT
|
||||||
|
#time.sleep(0.05)
|
||||||
|
|
||||||
|
#start thread to sample the SENT property
|
||||||
|
# this is needed for piGPIO sample of 1us and sensing the 3us
|
||||||
|
self.OutputSampleThread = threading.Thread(target = self.SampleCallBack)
|
||||||
|
self.OutputSampleThread.daemon = True
|
||||||
|
self.OutputSampleThread.start()
|
||||||
|
|
||||||
|
#give time for thread to start capturing data
|
||||||
|
time.sleep(.05)
|
||||||
|
|
||||||
|
|
||||||
|
def SampleCallBack(self):
|
||||||
|
|
||||||
|
# this will run in a loop and sample the SENT path
|
||||||
|
# this sampling is required when 1us sample rate for SENT 3us tick time
|
||||||
|
while True:
|
||||||
|
|
||||||
|
self.SampleStopped = False
|
||||||
|
self._cb = self.pi.callback(self.gpio, pigpio.EITHER_EDGE, self._cbf)
|
||||||
|
# wait until sample stopped
|
||||||
|
while self.SampleStopped == False:
|
||||||
|
#do nothing
|
||||||
|
time.sleep(.001)
|
||||||
|
|
||||||
|
# gives the callback time to cancel so we can start again.
|
||||||
|
time.sleep(0.20)
|
||||||
|
|
||||||
|
def _cbf(self, gpio, level, tick):
|
||||||
|
# depending on the system state set the tick times.
|
||||||
|
# first look for sync pulse. this is found when duty ratio >90
|
||||||
|
#print(pgio)
|
||||||
|
#print("inside _cpf")
|
||||||
|
#print(tick)
|
||||||
|
if self.syncFound == False:
|
||||||
|
if level == 1:
|
||||||
|
self._high_tick = tick
|
||||||
|
self._low = pigpio.tickDiff(self._low_tick,tick)
|
||||||
|
elif level == 0:
|
||||||
|
# this may be a syncpulse if the duty is 51/56
|
||||||
|
self._period = pigpio.tickDiff(self._low_tick,tick)
|
||||||
|
# not reset the self._low_tick
|
||||||
|
self._low_tick = tick
|
||||||
|
self._high = pigpio.tickDiff(self._high_tick,tick)
|
||||||
|
# sync pulse is detected by finding duty ratio. 51/56
|
||||||
|
# but also filter if period is > 90us*56 = 5040
|
||||||
|
if (100*self._high/self._period) > 87 and (self._period<5100):
|
||||||
|
self.syncFound = True
|
||||||
|
self.syncWidth = self._high
|
||||||
|
self.syncPeriod = self._period
|
||||||
|
#self.syncTick = round(self.syncPeriod/56.0,2)
|
||||||
|
self.syncTick = self.syncPeriod
|
||||||
|
# reset the nibble to zero
|
||||||
|
self.nibble = 0
|
||||||
|
self.SampleStopped = False
|
||||||
|
else:
|
||||||
|
# now look for the nibble information for each nibble (8 Nibbles)
|
||||||
|
if level == 1:
|
||||||
|
self._high_tick = tick
|
||||||
|
self._low = pigpio.tickDiff(self._low_tick,tick)
|
||||||
|
elif level == 0:
|
||||||
|
# This will be a data nibble
|
||||||
|
self._period = pigpio.tickDiff(self._low_tick,tick)
|
||||||
|
# not reset the self._low_tick
|
||||||
|
self._low_tick = tick
|
||||||
|
self._high = pigpio.tickDiff(self._high_tick,tick)
|
||||||
|
self.nibble = self.nibble + 1
|
||||||
|
if self.nibble == 1:
|
||||||
|
self.status = self._period
|
||||||
|
elif self.nibble == 2:
|
||||||
|
#self.data1 = hex(int(round(self._period / self.syncTick)-12))
|
||||||
|
self.data1 = self._period
|
||||||
|
elif self.nibble == 3:
|
||||||
|
self.data2 = self._period
|
||||||
|
elif self.nibble == 4:
|
||||||
|
self.data3 = self._period
|
||||||
|
elif self.nibble == 5:
|
||||||
|
self.data4 = self._period
|
||||||
|
elif self.nibble == 6:
|
||||||
|
self.data5 = self._period
|
||||||
|
elif self.nibble == 7:
|
||||||
|
self.data6 = self._period
|
||||||
|
elif self.nibble == 8:
|
||||||
|
self.crc = self._period
|
||||||
|
# now send all to the SENT Frame
|
||||||
|
self.frame = [self.syncPeriod,self.syncTick,self.status,self.data1,self.data2,self.data3,self.data4,self.data5,self.data6,self.crc]
|
||||||
|
self.syncFound = False
|
||||||
|
self.nibble = 0
|
||||||
|
self.numberFrames += 1
|
||||||
|
if self.numberFrames > 2:
|
||||||
|
self.cancel()
|
||||||
|
self.SampleStopped = True
|
||||||
|
self.numberFrames = 0
|
||||||
|
|
||||||
|
def ConvertData(self,tickdata,tickTime):
|
||||||
|
if tickdata == 0:
|
||||||
|
t = '0x0'
|
||||||
|
else:
|
||||||
|
t = hex(int(round(tickdata / tickTime)-12))
|
||||||
|
if t[0] =='-':
|
||||||
|
t='0x0'
|
||||||
|
return t
|
||||||
|
|
||||||
|
def SENTData(self):
|
||||||
|
# check that data1 = Data2 if they are not equal return fault = True
|
||||||
|
# will check the CRC code for faults. if fault, return = true
|
||||||
|
# returns status, data1, data2, crc, fault
|
||||||
|
#self._cb = self.pi.callback(self.gpio, pigpio.EITHER_EDGE, self._cbf)
|
||||||
|
#time.sleep(0.1)
|
||||||
|
fault = False
|
||||||
|
SentFrame = self.frame[:]
|
||||||
|
SENTTick = round(SentFrame[1]/56.0,2)
|
||||||
|
|
||||||
|
# the greatest SYNC sync is 90us. So trip a fault if this occurs
|
||||||
|
if SENTTick > 90:
|
||||||
|
fault = True
|
||||||
|
|
||||||
|
#print(SentFrame)
|
||||||
|
# convert SentFrame to HEX Format including the status and Crc bits
|
||||||
|
for x in range (2,10):
|
||||||
|
SentFrame[x] = self.ConvertData(SentFrame[x],SENTTick)
|
||||||
|
SENTCrc = SentFrame[9]
|
||||||
|
SENTStatus = SentFrame[2]
|
||||||
|
SENTPeriod = SentFrame[0]
|
||||||
|
#print(SentFrame)
|
||||||
|
# combine the datafield nibbles
|
||||||
|
datanibble = '0x'
|
||||||
|
datanibble2 = '0x'
|
||||||
|
for x in range (3,6):
|
||||||
|
datanibble = datanibble + str((SentFrame[x]))[2:]
|
||||||
|
for x in range (6,9):
|
||||||
|
datanibble2 = datanibble2 + str((SentFrame[x]))[2:]
|
||||||
|
# if using SENT mode 0, then data nibbles should be equal
|
||||||
|
#if self.SENTMode == 0 :
|
||||||
|
# if datanibble != datanibble2:
|
||||||
|
# fault = True
|
||||||
|
# if datanibble or datanibble2 == 0 then fault = true
|
||||||
|
if (int(datanibble,16) == 0) or (int(datanibble2,16) ==0):
|
||||||
|
fault = True
|
||||||
|
# if datanibble or datanibble2 > FFF (4096) then fault = True
|
||||||
|
if ( (int(datanibble,16) > 0xFFF) or (int(datanibble2,16) > 0xFFF)):
|
||||||
|
fault = True
|
||||||
|
#print(datanibble)
|
||||||
|
# CRC checking
|
||||||
|
# converting the datanibble values to a binary bit string.
|
||||||
|
# remove the first two characters. Not needed for crcCheck
|
||||||
|
InputBitString = bin(int((datanibble + datanibble2[2:]),16))[2:]
|
||||||
|
# converting Crcvalue to bin but remove the first two characters 0b
|
||||||
|
# format is set to remove the leading 0b, 4 charactors long
|
||||||
|
crcBitValue = format(int(str(SENTCrc),16),'04b')
|
||||||
|
#checking the crcValue
|
||||||
|
# polybitstring is 1*X^4+1*X^3+1*x^2+0*X+1 = '11101'
|
||||||
|
if self.crcCheck(InputBitString,'11101',crcBitValue) == False:
|
||||||
|
fault = True
|
||||||
|
|
||||||
|
# converter to decimnal
|
||||||
|
returnData = int(datanibble,16)
|
||||||
|
returnData2 = int(datanibble2,16)
|
||||||
|
#returns both Data values and if there is a FAULT
|
||||||
|
return (SENTStatus, returnData, returnData2,SENTTick, SENTCrc, fault, SENTPeriod)
|
||||||
|
|
||||||
|
def tick(self):
|
||||||
|
status, data1, data2, ticktime, crc, errors, syncPulse = self.SENTData()
|
||||||
|
return ticktime
|
||||||
|
|
||||||
|
def crcNibble(self):
|
||||||
|
status, data1, data2, ticktime, crc, errors, syncPulse = self.SENTData()
|
||||||
|
return crc
|
||||||
|
|
||||||
|
def dataField1(self):
|
||||||
|
status, data1, data2, ticktime, crc, errors, syncPulse = self.SENTData()
|
||||||
|
return data1
|
||||||
|
|
||||||
|
def dataField2(self):
|
||||||
|
status, data1, data2, ticktime, crc, errors, syncPulse = self.SENTData()
|
||||||
|
return data2
|
||||||
|
|
||||||
|
def statusNibble(self):
|
||||||
|
status, data1, data2, ticktime, crc, errors, syncPulse = self.SENTData()
|
||||||
|
return status
|
||||||
|
|
||||||
|
def syncPulse(self):
|
||||||
|
status, data1, data2, ticktime, crc, errors, syncPulse = self.SENTData()
|
||||||
|
return syncPulse
|
||||||
|
|
||||||
|
def errorFrame(self):
|
||||||
|
status, data1, data2, ticktime, crc, errors, syncPulse = self.SENTData()
|
||||||
|
return errors
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self._cb.cancel()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.OutputSampleThread.stop()
|
||||||
|
|
||||||
|
def crcCheck(self, InputBitString, PolyBitString, crcValue ):
|
||||||
|
# the input string will be a binary string all 6 nibbles of the SENT data
|
||||||
|
# the seed value ( = '0101) is appended to the input string. Do not use zeros for SENT protocal
|
||||||
|
# this uses the SENT CRC recommended implementation.
|
||||||
|
checkOK = False
|
||||||
|
|
||||||
|
LenPolyBitString = len(PolyBitString)
|
||||||
|
PolyBitString = PolyBitString.lstrip('0')
|
||||||
|
LenInput = len(InputBitString)
|
||||||
|
InputPaddedArray = list(InputBitString + '0101')
|
||||||
|
while '1' in InputPaddedArray[:LenInput]:
|
||||||
|
cur_shift = InputPaddedArray.index('1')
|
||||||
|
for i in range(len(PolyBitString)):
|
||||||
|
InputPaddedArray[cur_shift + i] = str(int(PolyBitString[i] != InputPaddedArray[cur_shift + i]))
|
||||||
|
|
||||||
|
if (InputPaddedArray[LenInput:] == list(crcValue)):
|
||||||
|
checkOK = True
|
||||||
|
|
||||||
|
return checkOK
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
import time
|
||||||
|
import pigpio
|
||||||
|
import read_SENT
|
||||||
|
|
||||||
|
SENT_GPIO = 18
|
||||||
|
RUN_TIME = 6000000000.0
|
||||||
|
SAMPLE_TIME = 0.1
|
||||||
|
|
||||||
|
pi = pigpio.pi()
|
||||||
|
|
||||||
|
p = read_SENT.SENTReader(pi, SENT_GPIO)
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
while (time.time() - start) < RUN_TIME:
|
||||||
|
|
||||||
|
time.sleep(SAMPLE_TIME)
|
||||||
|
|
||||||
|
status, data1, data2, ticktime, crc, errors, syncPulse = p.SENTData()
|
||||||
|
print("Sent Status= %s - 12-bit DATA 1= %4.0f - DATA 2= %4.0f - tickTime(uS)= %4.2f - CRC= %s - Errors= %s - PERIOD = %s" % (status,data1,data2,ticktime,crc,errors,syncPulse))
|
||||||
|
print("Sent Stat2s= %s - 12-bit DATA 1= %4.0f - DATA 2= %4.0f - tickTime(uS)= %4.2f - CRC= %s - Errors= %s - PERIOD = %s" % (p.statusNibble(),p.dataField1(),p.dataField2(),p.tick(),p.crcNibble(),p.errorFrame(),p.syncPulse()))
|
||||||
|
|
||||||
|
# stop the thread in SENTReader
|
||||||
|
p.stop()
|
||||||
|
# clear the pi object instance
|
||||||
|
pi.stop()
|
Loading…
Reference in New Issue