Skip to content

Instantly share code, notes, and snippets.

Created October 18, 2023 03:14
Show Gist options
  • Save mostthingsweb/c6fc6e82ff49f663349956a2fdd9b57e to your computer and use it in GitHub Desktop.
Save mostthingsweb/c6fc6e82ff49f663349956a2fdd9b57e to your computer and use it in GitHub Desktop.
Si4431 packet parsing script
0 R Device Type 0 0 0 dt[4] dt[3] dt[2] dt[1] dt[0] 111
1 R Device Version 0 0 0 vc[4] vc[3] vc[2] vc[1] vc[0] 06h
2 R Device Status ffovfl ffunfl rxffem headerr reserved reserved cps[1] cps[0] -
3 R Interrupt Status 1 ifferr itxffafull itxffaem irxffafull iext ipksent ipkvalid icrcerror -
4 R Interrupt Status 2 iswdet ipreaval ipreainval irssi iwut ilbd ichiprdy ipor -
5 R/W Interrupt Enable 1 enfferr entxffafull entxffaem enrxffafull enext enpksent enpkvalid encrcerror 00h
6 R/W Interrupt Enable 2 enswdet enpreaval enpreainval enrssi enwut enlbd enchiprdy enpor 03h
7 R/W Operating & Function Control 1 swres enlbd enwt x32ksel txon rxon pllon xton 01h
8 R/W Operating & Function Control 2 antdiv[2] antdiv[1] antdiv[0] rxmpk autotx enldm ffclrrx ffclrtx 00h
9 R/W Crystal Oscillator Load Capacitance xtalshft xlc[6] xlc[5] xlc[4] xlc[3] xlc[2] xlc[1] xlc[0] 7Fh
0A R/W Microcontroller Output Clock Reserved Reserved clkt[1] clkt[0] enlfc mclk[2] mclk[1] mclk[0] 06h
0B R/W GPIO0 Configuration gpio0drv[1] gpio0drv[0] pup0 gpio0[4] gpio0[3] gpio0[2] gpio0[1] gpio0[0] 00h
0C R/W GPIO1 Configuration gpio1drv[1] gpio1drv[0] pup1 gpio1[4] gpio1[3] gpio1[2] gpio1[1] gpio1[0] 00h
0D R/W GPIO2 Configuration gpio2drv[1] gpio2drv[0] pup2 gpio2[4] gpio2[3] gpio2[2] gpio2[1] gpio2[0] 00h
0E R/W I/O Port Configuration Reserved extitst[2] extitst[1] extitst[0] itsdo dio2 dio1 dio0 00h
0F R/W ADC Configuration adcstart/adc-done adcsel[2] adcsel[1] adcsel[0] adcref[1] adcref[0] adcgain[1] adcgain[0] 00h
10 R/W ADC Sensor Amplifier Offset Reserved Reserved Reserved Reserved adcoffs[3] adcoffs[2] adcoffs[1] adcoffs[0] 00h
11 R ADC Value adc[7] adc[6] adc[5] adc[4] adc[3] adc[2] adc[1] adc[0] -
12 R/W Temperature Sensor Control tsrange[1] tsrange[0] entsoffs entstrim tstrim[3] tstrim[2] tstrim[1] tstrim[0] 20h
13 R/W Temperature Value Offset tvoffs[7] tvoffs[6] tvoffs[5] tvoffs[4] tvoffs[3] tvoffs[2] tvoffs[1] tvoffs[0] 00h
14 R/W Wake-Up Timer Period 1 Reserved Reserved Reserved wtr[4] wtr[3] wtr[2] wtr[1] wtr[0] 03h
15 R/W Wake-Up Timer Period 2 wtm[15] wtm[14] wtm[13] wtm[12] wtm[11] wtm[10] wtm[9] wtm[8] 00h
16 R/W Wake-Up Timer Period 3 wtm[7] wtm[6] wtm[5] wtm[4] wtm[3] wtm[2] wtm[1] wtm[0] 01h
17 R Wake-Up Timer Value 1 wtv[15] wtv[14] wtv[13] wtv[12] wtv[11] wtv[10] wtv[9] wtv[8] -
18 R Wake-Up Timer Value 2 wtv[7] wtv[6] wtv[5] wtv[4] wtv[3] wtv[2] wtv[1] wtv[0] -
19 R/W Low-Duty Cycle Mode Duration ldc[7] ldc[6] ldc[5] ldc[4] ldc[3] ldc[2] ldc[1] ldc[0] 00h
1A R/W Low Battery Detector Threshold Reserved Reserved Reserved lbdt[4] lbdt[3] lbdt[2] lbdt[1] lbdt[0] 14h
1B R Battery Voltage Level 0 0 0 vbat[4] vbat[3] vbat[2] vbat[1] vbat[0] -
1C R/W IF Filter Bandwidth dwn3_bypass ndec[2] ndec[1] ndec[0] filset[3] filset[2] filset[1] filset[0] 01h
1D R/W AFC Loop Gearshift Override afcbd enafc afcgearh[2] afcgearh[1] afcgearh[0] 1p5 bypass matap ph0size 40h
1E R/W AFC Timing Control swait_timer[1] swait_timer[0] shwait[2] shwait[1] shwait[0] anwait[2] anwait[1] anwait[0] 0Ah
1F R/W Clock Recovery Gearshift Override Reserved Reserved crfast[2] crfast[1] crfast[0] crslow[2] crslow[1] crslow[0] 03h
20 R/W Clock Recovery Oversampling Ratio rxosr[7] rxosr[6] rxosr[5] rxosr[4] rxosr[3] rxosr[2] rxosr[1] rxosr[0] 64h
21 R/W Clock Recovery Offset 2 rxosr[10] rxosr[9] rxosr[8] stallctrl ncoff[19] ncoff[18] ncoff[17] ncoff[16] 01h
22 R/W Clock Recovery Offset 1 ncoff[15] ncoff[14] ncoff[13] ncoff[12] ncoff[11] ncoff[10] ncoff[9] ncoff[8] 47h
23 R/W Clock Recovery Offset 0 ncoff[7] ncoff[6] ncoff[5] ncoff[4] ncoff[3] ncoff[2] ncoff[1] ncoff[0] AEh
24 R/W Clock Recovery Timing Loop Gain 1 Reserved Reserved Reserved rxncocomp crgain2x crgain[10] crgain[9] crgain[8] 02h
25 R/W Clock Recovery Timing Loop Gain 0 crgain[7] crgain[6] crgain[5] crgain[4] crgain[3] crgain[2] crgain[1] crgain[0] 8Fh
26 R Received Signal Strength Indicator rssi[7] rssi[6] rssi[5] rssi[4] rssi[3] rssi[2] rssi[1] rssi[0] -
27 R/W RSSI Threshold for Clear Channel Indicator rssith[7] rssith[6] rssith[5] rssith[4] rssith[3] rssith[2] rssith[1] rssith[0] 1Eh
28 R Antenna Diversity Register 1 adrssi1[7] adrssia[6] adrssia[5] adrssia[4] adrssia[3] adrssia[2] adrssia[1] adrssia[0] -
29 R Antenna Diversity Register 2 adrssib[7] adrssib[6] adrssib[5] adrssib[4] adrssib[3] adrssib[2] adrssib[1] adrssib[0] -
2A R/W AFC Limiter Afclim[7] Afclim[6] Afclim[5] Afclim[4] Afclim[3] Afclim[2] Afclim[1] Afclim[0] 00h
2B R AFC Correction Read afc_corr[9] afc_corr[8] afc_corr[7] afc_corr[6] afc_corr[5] afc_corr[4] afc_corr[3] afc_corr[2] 00h
2C R/W OOK Counter Value 1 afc_corr[9] afc_corr[9] ookfrzen peakdeten madeten ookcnt[10] ookcnt[9] ookcnt[8] 18h
2D R/W OOK Counter Value 2 ookcnt[7] ookcnt[6] ookcnt[5] ookcnt[4] ookcnt[3] ookcnt[2] ookcnt[1] ookcnt[0] BCh
2E R/W Slicer Peak Hold Reserved attack[2] attack[1] attack[0] decay[3] decay[2] decay[1] decay[0] 26h
2F Reserved
30 R/W Data Access Control enpacrx lsbfrst crcdonly skip2ph enpactx encrc crc[1] crc[0] 8Dh
31 R EzMAC status 0 rxcrc1 pksrch pkrx pkvalid crcerror pktx pksent -
32 R/W Header Control 1 bcen[3] bcen[2] bcen[1] bcen[0] hdch[3] hdch[2] hdch[1] hdch[0] 0Ch
33 R/W Header Control 2 skipsyn hdlen[2] hdlen[1] hdlen[0] fixpklen synclen[1] synclen[0] prealen[8] 22h
34 R/W Preamble Length prealen[7] prealen[6] prealen[5] prealen[4] prealen[3] prealen[2] prealen[1] prealen[0] 08h
35 R/W Preamble Detection Control preath[4] preath[3] preath[2] preath[1] preath[0] rssi_off[2] rssi_off[1] rssi_off[0] 2Ah
36 R/W Sync Word 3 sync[31] sync[30] sync[29] sync[28] sync[27] sync[26] sync[25] sync[24] 2Dh
37 R/W Sync Word 2 sync[23] sync[22] sync[21] sync[20] sync[19] sync[18] sync[17] sync[16] D4h
38 R/W Sync Word 1 sync[15] sync[14] sync[13] sync[12] sync[11] sync[10] sync[9] sync[8] 00h
39 R/W Sync Word 0 sync[7] sync[6] sync[5] sync[4] sync[3] sync[2] sync[1] sync[0] 00h
3A R/W Transmit Header 3 txhd[31] txhd[30] txhd[29] txhd[28] txhd[27] txhd[26] txhd[25] txhd[24] 00h
3B R/W Transmit Header 2 txhd[23] txhd[22] txhd[21] txhd[20] txhd[19] txhd[18] txhd[17] txhd[16] 00h
3C R/W Transmit Header 1 txhd[15] txhd[14] txhd[13] txhd[12] txhd[11] txhd[10] txhd[9] txhd[8] 00h
3D R/W Transmit Header 0 txhd[7] txhd[6] txhd[5] txhd[4] txhd[3] txhd[2] txhd[1] txhd[0] 00h
3E R/W Transmit Packet Length pklen[7] pklen[6] pklen[5] pklen[4] pklen[3] pklen[2] pklen[1] pklen[0] 00h
3F R/W Check Header 3 chhd[31] chhd[30] chhd[29] chhd[28] chhd[27] chhd[26] chhd[25] chhd[24] 00h
40 R/W Check Header 2 chhd[23] chhd[22] chhd[21] chhd[20] chhd[19] chhd[18] chhd[17] chhd[16] 00h
41 R/W Check Header 1 chhd[15] chhd[14] chhd[13] chhd[12] chhd[11] chhd[10] chhd[9] chhd[8] 00h
42 R/W Check Header 0 chhd[7] chhd[6] chhd[5] chhd[4] chhd[3] chhd[2] chhd[1] chhd[0] 00h
43 R/W Header Enable 3 hden[31] hden[30] hden[29] hden[28] hden[27] hden[26] hden[25] hden[24] FFh
44 R/W Header Enable 2 hden[23] hden[22] hden[21] hden[20] hden[19] hden[18] hden[17] hden[16] FFh
45 R/W Header Enable 1 hden[15] hden[14] hden[13] hden[12] hden[11] hden[10] hden[9] hden[8] FFh
46 R/W Header Enable 0 hden[7] hden[6] hden[5] hden[4] hden[3] hden[2] hden[1] hden[0] FFh
47 R Received Header 3 rxhd[31] rxhd[30] rxhd[29] rxhd[28] rxhd[27] rxhd[26] rxhd[25] rxhd[24] -
48 R Received Header 2 rxhd[23] rxhd[22] rxhd[21] rxhd[20] rxhd[19] rxhd[18] rxhd[17] rxhd[16] -
49 R Received Header 1 rxhd[15] rxhd[14] rxhd[13] rxhd[12] rxhd[11] rxhd[10] rxhd[9] rxhd[8] -
4A R Received Header 0 rxhd[7] rxhd[6] rxhd[5] rxhd[4] rxhd[3] rxhd[2] rxhd[1] rxhd[0] -
4B R Received Packet Length rxplen[7] rxplen[6] rxplen[5] rxplen[4] rxplen[3] rxplen[2] rxplen[1] rxplen[0] -
4C-4E Reserved
4F R/W ADC8 Control Reserved Reserved adc8[5] adc8[4] adc8[3] adc8[2] adc8[1] adc8[0] 10h
50-5F Reserved
60 R/W Channel Filter Coefficient Address Inv_pre_th[3] Inv_pre_th[2] Inv_pre_th[1] Inv_pre_th[0] chfiladd[3] chfiladd[2] chfiladd[1] chfiladd[0] 00h
61 Reserved
62 R/W Crystal Oscillator/Control Test pwst[2] pwst[1] pwst[0] clkhyst enbias2x enamp2x bufovr enbuf 24h
63-68 Reserved
69 R/W AGC Override 1 Reserved sgi agcen lnagain pga3 pga2 pga1 pga0 20h
6A-6C Reserved
6D R/W TX Power Reserved Reserved Reserved Reserved Ina_sw txpow[2] txpow[1] txpow[0] 18h
6E R/W TX Data Rate 1 txdr[15] txdr[14] txdr[13] txdr[12] txdr[11] txdr[10] txdr[9] txdr[8] 0Ah
6F R/W TX Data Rate 0 txdr[7] txdr[6] txdr[5] txdr[4] txdr[3] txdr[2] txdr[1] txdr[0] 3Dh
70 R/W Modulation Mode Control 1 Reserved Reserved txdtrtscale enphpwdn manppol enmaninv enmanch enwhite 0Ch
71 R/W Modulation Mode Control 2 trclk[1] trclk[0] dtmod[1] dtmod[0] eninv fd[8] modtyp[1] modtyp[0] 00h
72 R/W Frequency Deviation fd[7] fd[6] fd[5] fd[4] fd[3] fd[2] fd[1] fd[0] 20h
73 R/W Frequency Offset 1 fo[7] fo[6] fo[5] fo[4] fo[3] fo[2] fo[1] fo[0] 00h
74 R/W Frequency Offset 2 Reserved Reserved Reserved Reserved Reserved Reserved fo[9] fo[8] 00h
75 R/W Frequency Band Select Reserved sbsel hbsel fb[4] fb[3] fb[2] fb[1] fb[0] 75h
76 R/W Nominal Carrier Frequency 1 fc[15] fc[14] fc[13] fc[12] fc[11] fc[10] fc[9] fc[8] BBh
77 R/W Nominal Carrier Frequency 0 fc[7] fc[6] fc[5] fc[4] fc[3] fc[2] fc[1] fc[0] 80h
78 Reserved
79 R/W Frequency Hopping Channel Select fhch[7] fhch[6] fhch[5] fhch[4] fhch[3] fhch[2] fhch[1] fhch[0] 00h
7A R/W Frequency Hopping Step Size fhs[7] fhs[6] fhs[5] fhs[4] fhs[3] fhs[2] fhs[1] fhs[0] 00h
7B Reserved
7C R/W TX FIFO Control 1 Reserved Reserved txafthr[5] txafthr[4] txafthr[3] txafthr[2] txafthr[1] txafthr[0] 37h
7D R/W TX FIFO Control 2 Reserved Reserved txaethr[5] txaethr[4] txaethr[3] txaethr[2] txaethr[1] txaethr[0] 04h
7E R/W RX FIFO Control Reserved Reserved rxafthr[5] rxafthr[4] rxafthr[3] rxafthr[2] rxafthr[1] rxafthr[0] 37h
7F R/W FIFO Access fifod[7] fifod[6] fifod[5] fifod[4] fifod[3] fifod[2] fifod[1] fifod[0] -
#!/usr/bin/env python2
import re
from enum import Enum
from collections import Iterable
from itertools import islice, izip
from decimal import Decimal
from bitarray import bitarray
from colorama import Fore, Back, Style
from math import modf
import sys
REGISTER_FILE = "datasheet-registers.txt"
#LOGIC_FILE = "startup-channel-9345.txt"
#LOGIC_FILE = "8050.txt"
class RegisterAccess(Enum):
Reserved = 0
ReadOnly = 1
ReadWrite = 2
def from_str(cls, str):
if str == "R":
return cls.ReadOnly
elif str == "R/W":
return cls.ReadWrite
return cls.Reserved
class Bit:
def __init__(self, name=None, bitfield_index=None):
self._name = name
self._bitfield_index = bitfield_index
def name(self):
return self._name
def is_reserved(self):
return not
def isPartOfBitfield(self):
return self._bitfield_index is not None
def __str__(self):
if self.isPartOfBitfield:
return "{0}[{1}]".format(, self._bitfield_index)
return "Reserved"
class Register:
def __init__(self, address, desc, access=RegisterAccess.Reserved, default=None, bits=[]):
self._address = "0x{:02X}".format(address)
self._access = access
self._desc = desc
self._bits = bits
self._data = None
if default:
self._data = default
def decode(self, t):
assert len(list(izip(self.bits, == 8
for i, (b, d) in enumerate(izip(self.bits,
if b.is_reserved:
sys.stdout.write("{0}={1}".format(str(b), 1 if d else 0))
if i < 7:
sys.stdout.write(", ")
def address(self):
return self._address
def desc(self):
return self._desc
def bits(self):
return self._bits
def data(self):
return self._data
def __repr__(self):
return str(self._address)
class RegisterMap:
def __init__(self, registers=None):
self._registers = {r.address:r for r in registers}
def add(self, reg):
assert not reg.address in self._registers
self._registers[reg.address] = reg
def for_t(self, t):
address = "0x{:02X}".format(t.address)
return self._registers[address]
def decode(self, t):
def from_register_file(cls, filename):
registers = []
with open(filename, "r") as fp:
for line in fp:
fields = [f.strip() for f in line.strip().split("\t")]
# Handle reserved registers (including ranges of reserved registers, e.g. 4C-4E)
if fields[1] == "Reserved":
if "-" in fields[0]:
low_address = int(fields[0][0:2], 16)
high_address = int(fields[0][3:5], 16)
low_address = int(fields[0], 16)
high_address = low_address
# Iterate over the range of addresses covered by this line (in base 10)
for address in range(low_address, high_address + 1):
registers.append(Register(address, fields[1]))
# Otherwise, handle an unreserved register
address = int(fields[0], 16)
access = RegisterAccess.from_str(fields[1])
desc = fields[2]
bits = []
for i in range(3, 11):
if fields[i] == "Reserved":
# Match the bit name, and optional a bitfield index
match = re.match(r"([^\[]+)(?:\[(\d+)\])?", fields[i])
assert match
# Default value
default_raw = fields[11]
default = None
if not "-" in default_raw:
if "h" in default_raw:
# It's hex
default = bitarray(format(int(default_raw.translate(None, "h"), 16), "b"))
# It's binary
default = bitarray(default_raw)
# Pad to a full byte. e.g. 111 becomes 00000111
for _ in range(0, 8 - len(default)):
default.insert(0, 0)
registers.append(Register(address, desc, access=access, bits=bits, default=default))
return cls(registers)
class Transaction:
def __init__(self, time_sec, address, is_write, data):
self._time_sec = time_sec
self._address = address
self._is_write = is_write
self._data = data
def time_sec(self):
return self._time_sec
def address(self):
return self._address
def is_write(self):
return self._is_write
def data(self):
return self._data
def from_capture_line(cls, line):
fields = [f.strip() for f in re.split(r"[,:;]", line)]
# 'fields' contains something like:
# ['2.002989291666667', 'SPI', 'MOSI', '0b 0000 0011 1111 1111', 'MISO', '0b 0000 0000 0010 0000']
time_sec = Decimal(fields[0])
# Split the MOSI data field by whitespace. We end up with 5 fields, with the first being useless (0b).
assert fields[1] == "SPI" and fields[2] == "MOSI"
mosi = bitarray("".join(fields[3].split()[1:]))
is_write = mosi[0]
address = int(mosi[1:8].to01(), 2)
if is_write:
data = mosi[8:16]
# Split the MISO data field by whitespace. We end up with 5 fields, with the first three being useless - the first is (0b), while the
# second and third are all garbage zeros. The actual data the slave responds with doesn't start until the master has finished transmitting the address, obviously.
assert fields[4] == "MISO"
if not is_write:
data = bitarray("".join(fields[5].split()[3:]))
return cls(time_sec, address, is_write, data)
def transaction_bursts(transactions, threshold=0.01):
last_time = None
burst_start = None
ret = []
for t in transactions:
if not burst_start:
burst_start = t.time_sec
# If this transaction occurred at least 'threshold' after the previous packet,
# declare the burst over and return it. This packet begins the next burst.
if last_time and (t.time_sec - last_time > threshold):
yield (burst_start, ret)
ret = []
burst_start = t.time_sec
last_time = t.time_sec
if ret:
yield (burst_start, ret)
import sys
def main():
registers = RegisterMap.from_register_file(REGISTER_FILE)
# Now time to parse the capture file
transactions = []
with open(sys.argv[1], "r") as fp:
# Skip the header line
for line in islice(fp, 1, None):
# Divide the transactions into bursts, i.e. clusters of transactions without long pauses in between.
# Note: This is NOT the same concept of "bursts" as described in the datasheet.
for b in transaction_bursts(transactions):
burst_start = b[0]
print Style.BRIGHT + Back.GREEN + "Burst beginning at {0}s, length {1} packet(s)".format(burst_start, len(b[1])) + Style.RESET_ALL
for t in b[1]:
print "{0} {1}".format(Back.RED + "write" + Style.RESET_ALL if t.is_write else Back.BLUE + "read" + Style.RESET_ALL, registers.for_t(t).desc)
print "\n"
if __name__ == "__main__":
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment