Having the firmware image to decompile allowed me to find the settings for the radio. How close was I?

Spirit Library?

I had guessed that they may well be using the STM SPIRIT1 library and after some looking I found that this was the case. It took a little while but eventually I found the data structures thet would let me know how the radio should be configured.


This is defined in the SPIRIT_Radio.h header for the library.

 * @brief  SPIRIT Radio Init structure definition
typedef struct
    int16_t           nXtalOffsetPpm;
    uint32_t          lFrequencyBase;
    uint32_t          nChannelSpace; 
    uint8_t           cChannelNumber;
    ModulationSelect  xModulationSelect;
    uint32_t          lDatarate;
    uint32_t          lFreqDev;
    uint32_t          lBandwidth;
} SRadioInit;
SRadioInit structure definition.

The structure is passed to the init function for the radio.

uint8_t SpiritRadioInit(SRadioInit* pxSRadioInitStruct);

Finding the function did take some looking but by working backwars from the SPI references I was able to find the functions that interacted with the Spirit radio and from there found the SpiritRadioInit function. In fact I found two of them!

It turns out there are 2 seperate sets of setup routines for the radio, each with their own init structures.

2000007c 00 00           int16_t   0h                      nXtalOffsetPpm
2000007e 00 00           uint16_t  0h                      spacer0
20000080 40 ae bf 33     uint32_t  33BFAE40h               lFrequencyBase
20000084 20 4e 00 00     uint32_t  4E20h                   nChannelSpace 
20000088 00              uint8_t   '\0'                    cChannelNumber
20000089 50              Modulati  GFSK_BT05               xModulationS
2000008a 00 00           uint16_t  0h                      spacer1
2000008c 50 c3 00 00     uint32_t  C350h                   lDatarate
20000090 20 4e 00 00     uint32_t  4E20h                   lFreqDev
20000094 a0 86 01 00     uint32_t  186A0h                  lBandwidth
Detected SRadioInit structure in SRAM.

The spacer0 and spacer1 fields were needed as the alignment wasn't correct without them, so I guess the compiler has aligned things?

The frequency is 868200000 - exactly what I had settled on using. The data rate is also an exact match at 50,000.

I had the modulation as GFSK_BT1 so I was close :-)

Packet Settings

Having found the basic radio setup, next I wanted to find the settings for the packets. I had guessed these would be Basic and so I should be looking for a PktBasicInit structure.

 * @brief  SPIRIT Basic Packet Init structure definition. This structure allows users to set the main options
 *         for the Basic packet.
typedef struct

  BasicPreambleLength           xPreambleLength;
  BasicSyncLength               xSyncLength;
  uint32_t                      lSyncWords;
  BasicFixVarLength             xFixVarLength;
  uint8_t                       cPktLengthWidth;
  BasicCrcMode                  xCrcMode;
  BasicControlLength            xControlLength;
  SpiritFunctionalState         xAddressField;
  SpiritFunctionalState         xFec;
  SpiritFunctionalState         xDataWhitening;
PktBasicInit structure definition.

Again, this is used by the init function. Once I had the SPI calls it was possible to work backwards and again find the function.

void SpiritPktBasicInit(PktBasicInit* pxPktBasicInit);

This led to the discovery of 2 init structures.

2000006c 20              BasicPre  PKT_PREAMBLE_LENGTH_05 
2000006d 06              BasicSyn  PKT_SYNC_LENGTH_4BYTES  xSyncLength
2000006e 00 00           uint16_t  0h                      spacer0
20000070 50 52 47 5a     uint32_t  5A475250h               lSyncWords
20000074 01              BasicFix  PKT_LENGTH_VAR          xFixVarLength
20000075 07              uint8_t   '\a'                    cPktLengthWi
20000076 80              BasicCrc  PKT_CRC_MODE_24BITS     xCrcMode
20000077 04              BasicCon  PKT_CONTROL_LENGTH_4BY  
20000078 01              SpiritFu  S_ENABLE                xAddressField
20000079 01              SpiritFu  S_ENABLE                xFec
2000007a 01              SpiritFu  S_ENABLE                xDataWhitening
Detected PktBasicInit structure in SRAM

Again I found that the byte alignment wasn't an exact match and the decompiled code expected the spacer bytes to be present.

Here I found there were quite a few things I had got wrong. The 3 byte CRC was a surprise and the FEC being nabled wasn't something I had considered. Also the use of an address field with 4 bytes of control data wasn't something I had correctly detected.

Following Up

After making the changes to the radio and some slight updates to the module and test script, I grabbed some more packets.

06:46:01 - messsage of 19 bytes
  Packet from ff, control bytes fc 00 34 06 [ RSSI 112, SQI 32 ]
  1: 05 ff 00 c5 c5 91 d8 d6 55 81 1b 81 85 e9 07 16 4c 84 c9 
06:46:10 - messsage of 19 bytes
  Packet from ff, control bytes c6 00 3e 06 [ RSSI 92, SQI 32 ]
  2: 05 ff 00 32 81 90 54 b1 5f 01 84 d2 80 44 20 c3 76 33 8c 
06:46:12 - messsage of 19 bytes
  Packet from ff, control bytes 53 00 0f 06 [ RSSI 94, SQI 32 ]
  3: 05 ff 00 b4 30 06 92 44 5a 31 7b 8c 1b 70 7c e6 71 5c 8d 
06:46:41 - messsage of 19 bytes
  Packet from ff, control bytes fc 00 35 06 [ RSSI 112, SQI 32 ]
  4: 05 ff 00 62 d5 dc d6 93 b5 ec 3c fc 94 02 39 7a e4 57 6a 
06:46:50 - messsage of 19 bytes
  Packet from ff, control bytes c6 00 3f 06 [ RSSI 92, SQI 32 ]
  5: 05 ff 00 ea c0 bf 5d a0 71 67 82 79 8e 67 10 b3 43 6e e1 
First 5 messages receieved.

All the messages are 19 bytes and all come from the same address, 0xff. The control bytes all have a very consistent pattern.

fc 00 34 06
             c6 00 3e 06
                           53 00 0f 06
fc 00 35 06
             c6 00 3f 06
                           53 00 10 06
fc 00 36 06
             c6 00 00 06
                           53 00 11 06
fc 00 37 06
             c6 00 01 06
                           53 00 12 06
Control bytes from messages received, spaced to allow patterns to be seen.

As we have 3 devices, each could relate to one and so the first number may actually be an device id? The third number appears to be a counter that wraps at 0x3F.

The 19 byte content is a little strangely sized. There is a lot of reference in the decompilation to AES like functionality, including a seeming key generation. The code appears to work on 16 byte blocks, which makes sense for AES so perhaps the CRC bytes are still attached and the messages are 16 bytes of payload plus 3 bytes of CRC?


The decompiled code is labyrinthal as it deals with button presses, LCD, networking and also tries to sleep as much as possible to preserve batteries. Some avenues of discovery work well and I have found a lot of the HAL library code which has allowed me to gain insights into what is being done, but much of the remaining code remains a list of maths operations on seemingly random addresses. It's impressive just how quickly it all starts to make sense with the appropriate structures added and referenced, but without an external reference it's a case of deductions.

I'm also conscious  that my aim isn't to fully reverse engineer the firmware for the device, just try and understand the networking such that I can replicate it without the device. Sadly things appear more interwoven than I would have liked.

If anyone has suggestions for places to find help and strategies to use to speed things up then I'd love to hear from you :-)