TPMS transcoder for Renault zoe2 with CC1101 and Arduino Nano

I tried to put a Clio TPMS sensor E24 10R-041328 in my front wheel but unfortunately, they are not seen by the zoe2 (Zoe2 original TPMS sensor: 407004CB0B).
The signals are very similar that's why I tried it.

In this project, I will transcode the clio signal to a Zoe signal.
If you want to do a similar transcoding, it is strongly recommended to have a SDR ( software-defined ) radio.
My material and software for SDR:

My material for transcoder:

Pictures of the sensors are on renault-zoe.forumpro.fr (in french)

The dataflow for this project:

More coming soon...

lots of part numbers, but none of them are definitive. As we all know Nano has become a generic term, there are many different ones available with different processors. It appears it is basically becoming a form factor. I looked up CC1101 and got several totally different things. With the information supplied I do not have a clue as to what you want or what is coming soon.

@gilshultz I update my first post with links to software and hardware and a dataflow diagram.
Any CC1101 and Arduino 3.3V which can use the SmartRC-CC1101-Driver-Lib should work: ESP8266, ESP32. For 5V Arduino (Nano, UNO, MEGA) as explained in the library , "A logic level converter is recommended for arduino. It also works well without. Use at your own risk."

Step 1 : Analyze the original Zoe2 TPMS radio signal
Run rtl_433 with command line in terminal(-R 90 is a filter for tpms Renault):
rtl_433 -R 90
Deflate the wheel or shake the sensor if unmounted. The terminal show:

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
time      : 2025-01-27 11:08:05
model     : Renault      type      : TPMS          id        : 101bb3
flags     : 31           pressure_kPa: 246.8 kPa   temperature_C: 14 C       Integrity : CRC

It is possible to have more information with:
rtl_433 -R90 -A

The terminal will return more informations and a link to
triq.org/encodedData . Click this link!


Modify he settings according to the picture: PCM, 52µs, 52µs and Bits return:
05 55 55 55 6A 56 66 59 65 9A 59 A5 A5 69 A5 65 55 59 69 5A 59 96 50 0
Copy and paste this to triq.net/bitbench: link at the top right corner.

Modify the settings:
Align 5556, Preamble, Show
Manchester (G.E Thomas) Show
You get the decoded signal for Zoe2 TPMS sensor:
c5 49 2c b3 1b 10 09 8c a4
According to rtl_433/src/devices/tpms_renault.c on github packet is:
F F/P PP TT II II II ?? ?? CC

  • F = flags, (seen: c0: 22% c8: 14% d0: 31% d8: 33%) maybe 110??T->31 for our sensor
  • P = Pressure, 10 bit 0.75 kPa->246.8kPa for our sensor
  • I = id, 24-bit little-endian -> ID: 101bb3 for our sensor
  • T = Temperature in C, offset -30->14°C for our sensor
  • ? = Unknown, mostly 0xffff -> 8c09 for our sensor and not ffff
  • C = Checksum, CRC-8 truncated poly 0x07 init 0x00->a4 for our sensor

The same analyze with the clio TPMS sensor gives:
c0 00 38 87 2e 10 ff ff bb
So
ID: 102e87
Unknown: ffff

Unknown is the différence between Zoe2 TPMS and Clio TPMS:

  • Alwaws ffff for clio
  • for Zoe2 : 9409 for stable pressure, 8C09 for pressure decrease and 9449 for ????

More coming soon...

Nice job!

Did you try to capture just with CC1101?

1 Like

Yes, this will be in next steps but SDR is much easier when you have no clue of what is in the packet.

Step 2: Send the Manchester encoded signal as a fake TPMS sensor
From step 1, Manchester encoded signal is:
05 55 55 55 6A 56 66 59 65 9A 59 A5 A5 69 A5 65 55 59 69 5A 59 96 50

The 0 at begining and end are important because they will generate the 250µs 0

ELECHOUSE_CC1101 library will be used.
From step 1, we know:

  • Modulation is FSK2
  • Frequency is 433.92MHz
  • Frequency deviation is 35
  • Packet size is 23 once Manchester encoded

According to CC1101 manual:


CC1101 can generate:

  • Preamble: our is 55 55
  • Synch word: our is 55 56
  • CRC calculation
  • Manchester encoding

But we can use none of them because:

  • CC1101 send lenght with Preamble/Synch Word
  • CRC is CRC16 and we need CRC 8
  • Manchester encoding will remove 0 at beginning/end
    That's why we use Manchester encoded data.

The ELECHOUSE_cc1101.SendData(buffer, len, 100) function do not allow to send data without lenght in packet (not in the Zoe2 TPMS packet format) so I use low level functions as workaround (maybe another CC1101 library would do a better job). See my post on ELECHOUSE_cc1101github issues

The important are:
ELECHOUSE_cc1101.setSyncMode(0) 0 = No preamble/sync -> we can use ours
ELECHOUSE_cc1101.setLengthConfig(0) 0 = Fixed packet length mode ->this is the only way to not send the packet length!

Here is the code for fake Zoe2 TPMS:

#include <ELECHOUSE_CC1101_SRC_DRV.h>

#define CC1101_FREQUENCY 433.92                                                                                                                                 //433.92  // Frequency for TPMS
uint8_t encodedData[] = {0x05,0x55,0x55,0x55,0x6A,0x56,0x66,0x59,0x65,0x9A,0x59,0xA5,0xA5,0x69,0xA5,0x65,0x55,0x59,0x69,0x5A,0x59,0x96,0x50};
int size = sizeof(encodedData);

void setup() {
  Serial.begin(1000000);

  Serial.println("Initializing CC1101...");
  Serial.println("size : " + (String)size);

  if (ELECHOUSE_cc1101.getCC1101()) {  // Check the CC1101 Spi connection.
    Serial.println("Connection OK  !");
  } else {
    Serial.println("Connection Error  ?");
  }

  ELECHOUSE_cc1101.Init();                    // must be set to initialize the cc1101!
  ELECHOUSE_cc1101.setCCMode(1);              // set config for internal transmission mode.
  ELECHOUSE_cc1101.setModulation(0);          // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
  ELECHOUSE_cc1101.setMHZ(CC1101_FREQUENCY);  // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet.
  ELECHOUSE_cc1101.setDeviation(35);          //47.60// Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz.
  ELECHOUSE_cc1101.setChannel(0);             // Set the Channelnumber from 0 to 255. Default is cahnnel 0.
  ELECHOUSE_cc1101.setChsp(199.95);           // The channel spacing is multiplied by the channel number CHAN and added to the base frequency in kHz. Value from 25.39 to 405.45. Default is 199.95 kHz.
  ELECHOUSE_cc1101.setRxBW(812.50);           // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz.
  ELECHOUSE_cc1101.setDRate(19.23);           //18.5  // Set the Data Rate in kBaud. Value from 0.02 to 1621.83. Default is 99.97 kBaud!
  ELECHOUSE_cc1101.setPA(0);                  // Set TxPower. The following settings are possible depending on the frequency band.  (-30  -20  -15  -10  -6    0    5    7    10   11   12) Default is max!
  ELECHOUSE_cc1101.setSyncMode(0);            // Combined sync-word qualifier mode. 0 = No preamble/sync. 1 = 16 sync word bits detected. 2 = 16/16 sync word bits detected. 3 = 30/32 sync word bits detected. 4 = No preamble/sync, carrier-sense above threshold. 5 = 15/16 + carrier-sense above threshold. 6 = 16/16 + carrier-sense above threshold. 7 = 30/32 + carrier-sense above threshold.
  ELECHOUSE_cc1101.setSyncWord(0, 0);         //211,245// Set sync word. Must be the same for the transmitter and receiver. (Syncword high, Syncword low)
  ELECHOUSE_cc1101.setAdrChk(0);              // Controls address check configuration of received packages. 0 = No address check. 1 = Address check, no broadcast. 2 = Address check and 0 (0x00) broadcast. 3 = Address check and 0 (0x00) and 255 (0xFF) broadcast.
  ELECHOUSE_cc1101.setAddr(0);                // Address used for packet filtration. Optional broadcast addresses are 0 (0x00) and 255 (0xFF).
  ELECHOUSE_cc1101.setWhiteData(0);           // Turn data whitening on / off. 0 = Whitening off. 1 = Whitening on.
  ELECHOUSE_cc1101.setPktFormat(0);           // Format of RX and TX data. 0 = Normal mode, use FIFOs for RX and TX. 1 = Synchronous serial mode, Data in on GDO0 and data out on either of the GDOx pins. 2 = Random TX mode; sends random data using PN9 generator. Used for test. Works as normal mode, setting 0 (00), in RX. 3 = Asynchronous serial mode, Data in on GDO0 and data out on either of the GDOx pins.
  ELECHOUSE_cc1101.setLengthConfig(0);        // 0 = Fixed packet length mode. 1 = Variable packet length mode. 2 = Infinite packet length mode. 3 = Reserved
  ELECHOUSE_cc1101.setPacketLength(size);     // Indicates the packet length when fixed packet length mode is enabled. If variable packet length mode is used, this value indicates the maximum packet length allowed.
  ELECHOUSE_cc1101.setCrc(0);                 // 1 = CRC calculation in TX and CRC check in RX enabled. 0 = CRC disabled for TX and RX.
  ELECHOUSE_cc1101.setCRC_AF(0);              // Enable automatic flush of RX FIFO when CRC is not OK. This requires that only one packet is in the RXIFIFO and that packet length is limited to the RX FIFO size.
  ELECHOUSE_cc1101.setDcFilterOff(0);         // Disable digital DC blocking filter before demodulator. Only for data rates ≤ 250 kBaud The recommended IF frequency changes when the DC blocking is disabled. 1 = Disable (current optimized). 0 = Enable (better sensitivity).
  ELECHOUSE_cc1101.setManchester(0);          // Enables Manchester encoding/decoding. 0 = Disable. 1 = Enable.
  ELECHOUSE_cc1101.setFEC(0);                 // Enable Forward Error Correction (FEC) with interleaving for packet payload (Only supported for fixed packet length mode. 0 = Disable. 1 = Enable.
  ELECHOUSE_cc1101.setPRE(0);                 // Sets the minimum number of preamble bytes to be transmitted. Values: 0 : 2, 1 : 3, 2 : 4, 3 : 6, 4 : 8, 5 : 12, 6 : 16, 7 : 24
  ELECHOUSE_cc1101.setPQT(0);                 // Preamble quality estimator threshold. The preamble quality estimator increases an internal counter by one each time a bit is received that is different from the previous bit, and decreases the counter by 8 each time a bit is received that is the same as the last bit. A threshold of 4∙PQT for this counter is used to gate sync word detection. When PQT=0 a sync word is always accepted.
  ELECHOUSE_cc1101.setAppendStatus(0);        // When enabled, two status bytes will be appended to the payload of the packet. The status bytes contain RSSI and LQI values, as well as CRC OK.

  Serial.println("Tx Mode");
  Serial.println("CC1101 initialized.");
}

void loop() {
    // Send TPMS data
    ELECHOUSE_cc1101.SpiStrobe(CC1101_SFTX);  //flush TXfifo
    ELECHOUSE_cc1101.SpiWriteBurstReg(CC1101_TXFIFO, encodedData, size);  //write data to send to TXfifo
    ELECHOUSE_cc1101.SpiStrobe(CC1101_SIDLE); //set IDLE mode - necessary?
    ELECHOUSE_cc1101.SpiStrobe(CC1101_STX);  //start send
    Serial.print("TX bytes:");
    txbytes(); //wait for the end of transmission - can be remove if you wait more than transmission time!

    delay(10000);
  }

void txbytes() {//wait for end of transmission : FIFO size=0 cc1101.pdf p94
  int txbytes;
  do {
    txbytes = ELECHOUSE_cc1101.SpiReadStatus(CC1101_TXBYTES);
    Serial.print(txbytes);
    Serial.print(" ");
  } while (txbytes > 0);
  Serial.println();
}

Note:

  • ELECHOUSE_cc1101 github/wiring will explain you how to connect your CC1101 and Arduino
  • txbytes() function is not necessary here but will be usefull in next steps...
1 Like

Step 3: Encode the TPMS data
Good news:

  • The code step2 works on RTL-SDR with rtl_433 and generate exactly the same signal as the original sensor: triq.org validation performed!
  • Second good new, the code also works on the Zoe2. You need to long press on 0 while on the Zoe TPMS screen to learn the sensor if not seen since a long time.

Part of the code for that step comes from the gitHub issue Replicating Renault TPMS signal
But that original code has drawbacks:

  • It send the length in the packet
  • There is no 256 µs 0 at the beginning and end of packet

So it doesn't work on my Zoe.
The strategy is:

  1. Generate TPMS Data
  2. Generate CRC8
  3. Encode Manchester
  4. Shift 4 bits the data and add 0 at beginning and end
  5. Send the data with low level function to avoid send length

Here is the code for 2 wheels:

#include <ELECHOUSE_CC1101_SRC_DRV.h>

#define CC1101_FREQUENCY 433.91  //433.92  // Frequency for TPMS
unsigned int i=0;

void setup() {
  Serial.begin(115200);
  Serial.println("Initializing CC1101...");

  if (ELECHOUSE_cc1101.getCC1101()) {  // Check the CC1101 Spi connection.
    Serial.println("Connection OK  !!!!!!!!!!!!!!!!!!!!!!!!!!!!");
  } else {
    Serial.println("Connection Error  ?????????????????????????");
  }

  ELECHOUSE_cc1101.Init();                    // must be set to initialize the cc1101!
  ELECHOUSE_cc1101.setCCMode(1);              // set config for internal transmission mode.
  ELECHOUSE_cc1101.setModulation(0);          // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
  ELECHOUSE_cc1101.setMHZ(CC1101_FREQUENCY);  // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet.
  ELECHOUSE_cc1101.setDeviation(35);          //47.60// Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz.
  ELECHOUSE_cc1101.setChannel(0);             // Set the Channelnumber from 0 to 255. Default is cahnnel 0.
  ELECHOUSE_cc1101.setChsp(199.95);           // The channel spacing is multiplied by the channel number CHAN and added to the base frequency in kHz. Value from 25.39 to 405.45. Default is 199.95 kHz.
  ELECHOUSE_cc1101.setRxBW(812.50);           // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz.
  ELECHOUSE_cc1101.setDRate(19.23);           //18.5  // Set the Data Rate in kBaud. Value from 0.02 to 1621.83. Default is 99.97 kBaud!
  ELECHOUSE_cc1101.setPA(10);                 // Set TxPower. The following settings are possible depending on the frequency band.  (-30  -20  -15  -10  -6    0    5    7    10   11   12) Default is max!
  ELECHOUSE_cc1101.setSyncMode(0);            // Combined sync-word qualifier mode. 0 = No preamble/sync. 1 = 16 sync word bits detected. 2 = 16/16 sync word bits detected. 3 = 30/32 sync word bits detected. 4 = No preamble/sync, carrier-sense above threshold. 5 = 15/16 + carrier-sense above threshold. 6 = 16/16 + carrier-sense above threshold. 7 = 30/32 + carrier-sense above threshold.
  ELECHOUSE_cc1101.setSyncWord(0, 0);         //211,245// Set sync word. Must be the same for the transmitter and receiver. (Syncword high, Syncword low)
  ELECHOUSE_cc1101.setAdrChk(0);              // Controls address check configuration of received packages. 0 = No address check. 1 = Address check, no broadcast. 2 = Address check and 0 (0x00) broadcast. 3 = Address check and 0 (0x00) and 255 (0xFF) broadcast.
  ELECHOUSE_cc1101.setAddr(0);                // Address used for packet filtration. Optional broadcast addresses are 0 (0x00) and 255 (0xFF).
  ELECHOUSE_cc1101.setWhiteData(0);           // Turn data whitening on / off. 0 = Whitening off. 1 = Whitening on.
  ELECHOUSE_cc1101.setPktFormat(0);           // Format of RX and TX data. 0 = Normal mode, use FIFOs for RX and TX. 1 = Synchronous serial mode, Data in on GDO0 and data out on either of the GDOx pins. 2 = Random TX mode; sends random data using PN9 generator. Used for test. Works as normal mode, setting 0 (00), in RX. 3 = Asynchronous serial mode, Data in on GDO0 and data out on either of the GDOx pins.
  ELECHOUSE_cc1101.setLengthConfig(0);        //!!!  // 0 = Fixed packet length mode. 1 = Variable packet length mode. 2 = Infinite packet length mode. 3 = Reserved
  ELECHOUSE_cc1101.setCrc(0);                 // 1 = CRC calculation in TX and CRC check in RX enabled. 0 = CRC disabled for TX and RX.
  ELECHOUSE_cc1101.setCRC_AF(0);              // Enable automatic flush of RX FIFO when CRC is not OK. This requires that only one packet is in the RXIFIFO and that packet length is limited to the RX FIFO size.
  ELECHOUSE_cc1101.setDcFilterOff(0);         // Disable digital DC blocking filter before demodulator. Only for data rates ≤ 250 kBaud The recommended IF frequency changes when the DC blocking is disabled. 1 = Disable (current optimized). 0 = Enable (better sensitivity).
  ELECHOUSE_cc1101.setManchester(0);          // Enables Manchester encoding/decoding. 0 = Disable. 1 = Enable.
  ELECHOUSE_cc1101.setFEC(0);                 // Enable Forward Error Correction (FEC) with interleaving for packet payload (Only supported for fixed packet length mode. 0 = Disable. 1 = Enable.
  ELECHOUSE_cc1101.setPRE(0);                 // Sets the minimum number of preamble bytes to be transmitted. Values: 0 : 2, 1 : 3, 2 : 4, 3 : 6, 4 : 8, 5 : 12, 6 : 16, 7 : 24
  ELECHOUSE_cc1101.setPQT(0);                 // Preamble quality estimator threshold. The preamble quality estimator increases an internal counter by one each time a bit is received that is different from the previous bit, and decreases the counter by 8 each time a bit is received that is the same as the last bit. A threshold of 4∙PQT for this counter is used to gate sync word detection. When PQT=0 a sync word is always accepted.
  ELECHOUSE_cc1101.setAppendStatus(0);        // When enabled, two status bytes will be appended to the payload of the packet. The status bytes contain RSSI and LQI values, as well as CRC OK.

  Serial.println("Tx Mode");

  Serial.println("CC1101 initialized.");
}

void loop() {
  byte tpmsData[9];      // Actual data
  byte encodedData[18];  // Manchester encoded data (2x the size)
  byte shiftedData[23];  // qhifted+preamble Manchester encoded data (Manchester size+5)

  // Generate the TPMS data payload
  i++;
  if (i%2==0){
  generateTPMSData(tpmsData, 0x101bb3, 14, 248, 0x31);  // Example values}
  }else{
  generateTPMSData(tpmsData, 0x102647, 14, 260, 0x31);  // Example values
  }

                  //array, ID, temperature ,pression, piles

  // Encode data using Manchester encoding
  encodeManchester(encodedData, tpmsData,9);

  //shift tpms data
  shift(shiftedData, encodedData, 18);

  // Print data for debugging
  Serial.println("TPMS DATA:");
  for (int i = 0; i < 9; i++) {
    Serial.print(tpmsData[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

  // Print data for debugging
  Serial.println("ENCODED TPMS DATA:");
  for (int i = 0; i < 18; i++) {
    Serial.print(encodedData[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

  // Print data for debugging
  Serial.println("SHIFTED TPMS DATA:");
  for (int i = 0; i < 23; i++) {
    char s[80];
    snprintf(s, sizeof(s), "%02x ", shiftedData[i]);
    Serial.write(s);
  }
  Serial.println();

  // Send TPMS data
  ELECHOUSE_cc1101.setPacketLength(23);                               // Indicates the packet length when fixed packet length mode is enabled. If variable packet length mode is used, this value indicates the maximum packet length allowed.
  ELECHOUSE_cc1101.SpiStrobe(CC1101_SFTX);                              //flush TXfifo
  ELECHOUSE_cc1101.SpiWriteBurstReg(CC1101_TXFIFO, shiftedData, 23);  //write data to send
  ELECHOUSE_cc1101.SpiStrobe(CC1101_SIDLE);
  ELECHOUSE_cc1101.SpiStrobe(CC1101_STX);  //start send
  Serial.print("TX bytes:");
  txbytes();  //wait for the end of transmission
  Serial.println("TPMS signal sent!");
  delay(10000);
}
//end loop !!!!!!!!!!!!!!!!!!!!!!!!!




void generateTPMSData(uint8_t *data, uint32_t id, uint8_t temperature, uint16_t pressure, uint8_t battery) {
  uint16_t press;
  press = (pressure * 4) / 3;
  data[0] = ((battery << 2) | (press >> 8)) & 0xFF;
  data[1] = (press)&0xFF;
  data[2] = (temperature + 30) & 0xFF;  // Offset by 30 as per tpms_renault.c
  data[3] = id & 0xFF;
  data[4] = (id >> 8) & 0xFF;
  data[5] = (id >> 16) & 0xFF;
  data[6] = 0x09;                       //0x09;
  data[7] = 0x94;                       //0x94 degonfle:8C   ;
  data[8] = crc8(data, 8, 0x07, 0x00);  // Calculate CRC8 checksum with polynomial 0x07 and initial value 0x00=CRC-8-ATM (CRC-8-CCITT)
}

void encodeManchester(uint8_t *encoded, const uint8_t *data, size_t len) {
  size_t bitIndex = 0;
  for (size_t i = 0; i < len; i++) {
    for (int bit = 7; bit >= 0; bit--) {
      if (data[i] & (1 << bit)) {
        // Bit is 1, Manchester encoding is 10
        encoded[bitIndex / 8] |= (1 << (7 - (bitIndex % 8)));
        bitIndex++;
        encoded[bitIndex / 8] &= ~(1 << (7 - (bitIndex % 8)));
      } else {
        // Bit is 0, Manchester encoding is 01
        encoded[bitIndex / 8] &= ~(1 << (7 - (bitIndex % 8)));
        bitIndex++;
        encoded[bitIndex / 8] |= (1 << (7 - (bitIndex % 8)));
      }
      bitIndex++;
    }
  }
}

void shift(uint8_t *shifted, const uint8_t *data, size_t len) {  //add preamble 05 55 55 6. , shift data 4 bits right and add 0 at end
  shifted[0] = 0x05;
  shifted[1] = 0x55;
  shifted[2] = 0x55;
  shifted[3] = 0x55;
  shifted[4] = 0x60 | (data[0] & 0xf0) >> 4;
  for (int i = 1; i < len; i++) {  //shit 4 bits
    shifted[i + 4] = (data[i - 1] & 0x0f) << 4 | (data[i] & 0xf0) >> 4;
  }
  shifted[len + 4] = (data[len] & 0x0f) << 4;  //last hex and 0 at end
}

uint8_t crc8(const uint8_t *data, size_t len, uint8_t polynomial, uint8_t initial) {
  uint8_t crc = initial;
  while (len--) {
    crc ^= *data++;
    for (uint8_t i = 0; i < 8; i++) {
      if (crc & 0x80)
        crc = (crc << 1) ^ polynomial;
      else
        crc <<= 1;
    }
  }
  return crc;
}

void state() {  //voir Main Radio Control State Machine State sur cc1101.pdf p93 & p50
  int state;
  do {
    state = 0x0f & ELECHOUSE_cc1101.SpiReadStatus(CC1101_MARCSTATE);
    Serial.print(state);
    Serial.print(" ");
  } while (state != 1);
  Serial.println();
}

void txbytes() {  //voir status sur cc1101.pdf p94
  int txbytes;
  do {
    txbytes = ELECHOUSE_cc1101.SpiReadStatus(CC1101_TXBYTES);
    Serial.print(txbytes);
    Serial.print(" ");
  } while (txbytes > 0);
  Serial.println();
}

That code works for my 2 front wheels!

Step 4: Receive TPMS data with CC1101
A made a mistake in my previous post. I am not using a nano 3.3V but a nano V3.3 (old bootloader): this is a 5V arduino! Previous post edited are now correct!
Here is a picture of my hardware wired:

We have here the same problem for receive:

  • We cannot use ELECHOUSE_cc1101.ReceiveData(buffer) because it is supposed to contain length: low level functions will be used.
  • CC1101 hardware Manchester decoding cannot be used

But good news: Synch Word can be used. This will be 55 56 for us. The sensor send 55 55 55 56 but the CC1101 cannot detect that 32 bit Synch word so only last 16 bits wil be used!

Important change in the config between send and receive are:
Packet length is 18 because we read after synch word: ELECHOUSE_cc1101.setPacketLength(18)
ELECHOUSE_cc1101.setSyncMode(1) and ELECHOUSE_cc1101.setSyncWord(0x55, 0x56); because me use synch word

The strategy for read data is:

  1. Wait for data
  2. Verify packet lenght
  3. Read data
  4. Decode manchester
  5. Verifiy manchester validity
  6. Verify crc
  7. Print data: flag, pressure, temperature, ID, unknown

Here is the receive code which work for any renault TPMS your CC1101 can reveive:

#include <ELECHOUSE_CC1101_SRC_DRV.h>

#define PacketLength 18
byte buffer[PacketLength];
byte decoded[PacketLength / 2];

void setup() {

  Serial.begin(115200);

  if (ELECHOUSE_cc1101.getCC1101()) {  // Check the CC1101 Spi connection.
    Serial.println("Connection OK");
  } else {
    Serial.println("Connection Error");
  }

  ELECHOUSE_cc1101.Init();                   // must be set to initialize the cc1101!
  ELECHOUSE_cc1101.setCCMode(1);             // set config for internal transmission mode.
  ELECHOUSE_cc1101.setModulation(0);         // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
  ELECHOUSE_cc1101.setMHZ(433.92);           // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet.
  ELECHOUSE_cc1101.setDeviation(35);         // Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz.
  ELECHOUSE_cc1101.setRxBW(812.50);          // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz.
  ELECHOUSE_cc1101.setDRate(19.23);          // Set the Data Rate in kBaud. Value from 0.02 to 1621.83. Default is 99.97 kBaud!
  ELECHOUSE_cc1101.setPA(10);                // Set TxPower. The following settings are possible depending on the frequency band.  (-30  -20  -15  -10  -6    0    5    7    10   11   12) Default is max!
  ELECHOUSE_cc1101.setSyncMode(1);           // Combined sync-word qualifier mode. 0 = No preamble/sync. 1 = 16 sync word bits detected. 2 = 16/16 sync word bits detected. 3 = 30/32 sync word bits detected. 4 = No preamble/sync, carrier-sense above threshold. 5 = 15/16 + carrier-sense above threshold. 6 = 16/16 + carrier-sense above threshold. 7 = 30/32 + carrier-sense above threshold.
  ELECHOUSE_cc1101.setSyncWord(0x55, 0x56);  // Set sync word. Must be the same for the transmitter and receiver. (Syncword high, Syncword low)
  ELECHOUSE_cc1101.setAdrChk(0);             // Controls address check configuration of received packages. 0 = No address check. 1 = Address check, no broadcast. 2 = Address check and 0 (0x00) broadcast. 3 = Address check and 0 (0x00) and 255 (0xFF) broadcast.
  ELECHOUSE_cc1101.setWhiteData(0);          // Turn data whitening on / off. 0 = Whitening off. 1 = Whitening on.
  ELECHOUSE_cc1101.setPktFormat(0);          // Format of RX and TX data. 0 = Normal mode, use FIFOs for RX and TX. 1 = Synchronous serial mode, Data in on GDO0 and data out on either of the GDOx pins. 2 = Random TX mode; sends random data using PN9 generator. Used for test. Works as normal mode, setting 0 (00), in RX. 3 = Asynchronous serial mode, Data in on GDO0 and data out on either of the GDOx pins.
  ELECHOUSE_cc1101.setLengthConfig(0);       // 0 = Fixed packet length mode. 1 = Variable packet length mode. 2 = Infinite packet length mode. 3 = Reserved
  ELECHOUSE_cc1101.setPacketLength(18);      //!!// Indicates the packet length when fixed packet length mode is enabled. If variable packet length mode is used, this value indicates the maximum packet length allowed.
  ELECHOUSE_cc1101.setCrc(0);                // 1 = CRC calculation in TX and CRC check in RX enabled. 0 = CRC disabled for TX and RX.
  ELECHOUSE_cc1101.setCRC_AF(0);             // Enable automatic flush of RX FIFO when CRC is not OK. This requires that only one packet is in the RXIFIFO and that packet length is limited to the RX FIFO size.
  ELECHOUSE_cc1101.setDcFilterOff(0);        // Disable digital DC blocking filter before demodulator. Only for data rates ≤ 250 kBaud The recommended IF frequency changes when the DC blocking is disabled. 1 = Disable (current optimized). 0 = Enable (better sensitivity).
  ELECHOUSE_cc1101.setManchester(0);         // Enables Manchester encoding/decoding. 0 = Disable. 1 = Enable.
  ELECHOUSE_cc1101.setFEC(0);                // Enable Forward Error Correction (FEC) with interleaving for packet payload (Only supported for fixed packet length mode. 0 = Disable. 1 = Enable.
  ELECHOUSE_cc1101.setPRE(0);                // Sets the minimum number of preamble bytes to be transmitted. Values: 0 : 2, 1 : 3, 2 : 4, 3 : 6, 4 : 8, 5 : 12, 6 : 16, 7 : 24
  ELECHOUSE_cc1101.setPQT(0);                // Preamble quality estimator threshold. The preamble quality estimator increases an internal counter by one each time a bit is received that is different from the previous bit, and decreases the counter by 8 each time a bit is received that is the same as the last bit. A threshold of 4∙PQT for this counter is used to gate sync word detection. When PQT=0 a sync word is always accepted.
  ELECHOUSE_cc1101.setAppendStatus(0);       // When enabled, two status bytes will be appended to the payload of the packet. The status bytes contain RSSI and LQI values, as well as CRC OK.

  //Start RX
  ELECHOUSE_cc1101.SpiStrobe(CC1101_SFRX);  //flush fifo
  ELECHOUSE_cc1101.SpiStrobe(CC1101_SRX);   //enable rx
  Serial.println("Rx Mode");
}

void loop() {
  byte len = ELECHOUSE_cc1101.SpiReadStatus(CC1101_RXBYTES);
  if (len > 0) {
    long t = millis();
    long t2;
    do {
      len = ELECHOUSE_cc1101.SpiReadStatus(CC1101_RXBYTES);
      t2 = millis();
    } while (len < PacketLength & ((t2 - t) < 20));  //20ms timeout
    Serial.println();

    Serial.print("len :");
    Serial.print(len);
    Serial.print(" temps : ");
    Serial.print(t2 - t);
    Serial.print(" ms");

    if (len == PacketLength) {
      ELECHOUSE_cc1101.SpiReadBurstReg(CC1101_RXFIFO, buffer, PacketLength);  //read rx FIFO
      ELECHOUSE_cc1101.SpiStrobe(CC1101_SFRX);                                //flush fifo
      ELECHOUSE_cc1101.SpiStrobe(CC1101_SRX);                                 //enable rx

      //Print received in bytes format.
      Serial.print(" Data: ");
      for (int i = 0; i < PacketLength; i++) {
        if (buffer[i] < 16) Serial.print("0");
        Serial.print(buffer[i], HEX);
        Serial.print(" ");
      }
      Serial.println();

      int nbdecoded = manchesterDecode(buffer, decoded, PacketLength);
      Serial.print("Nb decoded :");
      Serial.print(nbdecoded);

      if (nbdecoded == 9) {  //manchester valide
        //Print decoded in bytes format.
        Serial.print(" Decoded: ");
        for (int i = 0; i < nbdecoded; i++) {
          if (decoded[i] < 16) Serial.print("0");
          Serial.print(decoded[i], HEX);
          Serial.print(" ");
        }

        byte crc = crc8(decoded, 8, 0x07, 0x00);
        Serial.print(" CRC: ");
        Serial.print(crc, HEX);

        if (crc == decoded[8]) {
          Serial.println(" OK");  //crc ok

          //Print readings
          char flags_str[10];
          snprintf(flags_str, sizeof(flags_str), "FLAG : %02x", decoded[0] >> 2);
          Serial.write(flags_str);

          float pressure_raw = (decoded[0] & 0x03) << 8 | decoded[1];
          float pressure_kpa = pressure_raw * 0.75;
          Serial.print(" PRESSURE : ");
          Serial.print(pressure_kpa);

          char id_str[40];
          snprintf(id_str, sizeof(id_str), " Temp : %+d°C ID : %02X%02X%02X CODE : %02X%02X", decoded[2] - 30, decoded[5], decoded[4], decoded[3], decoded[7], decoded[6]);
          Serial.write(id_str);
          Serial.println();
        } else {
          Serial.println(" CRC erreur!!!");
        }

      } else {  //nbdecoded <> 9
        Serial.println(" erreur de decodage Manchester");
      }
    } else {                                    //len <> PacketLength
      ELECHOUSE_cc1101.SpiStrobe(CC1101_SFRX);  //flush fifo
      ELECHOUSE_cc1101.SpiStrobe(CC1101_SRX);   //enable rx
    }
  }
}
//end loop

uint8_t crc8(const uint8_t *data, size_t len, uint8_t polynomial, uint8_t initial) {
  uint8_t crc = initial;
  while (len--) {
    crc ^= *data++;
    for (uint8_t i = 0; i < 8; i++) {
      if (crc & 0x80)
        crc = (crc << 1) ^ polynomial;
      else
        crc <<= 1;
    }
  }
  return crc;
}

byte manchesterDecode(const byte *manchesterData, byte *decodedData, size_t length) {
  size_t bitCount = length * 8;       // Chaque byte contient 8 bits
  if (bitCount % 2 != 0) return 255;  // Vérifie si la longueur est valide

  byte bitIndex = 0;
  byte byteIndex = 0;
  decodedData[byteIndex] = 0;

  for (size_t i = 0; i < bitCount; i += 2) {
    byte first = (manchesterData[i / 8] >> (7 - (i % 8))) & 0x01;
    byte second = (manchesterData[(i + 1) / 8] >> (7 - ((i + 1) % 8))) & 0x01;

    if (first == 0 && second == 1) {
      decodedData[byteIndex] <<= 1;
      decodedData[byteIndex] |= 0;  // 0 logique
    } else if (first == 1 && second == 0) {
      decodedData[byteIndex] <<= 1;
      decodedData[byteIndex] |= 1;  // 1 logique
    } else {
      return 255;  // Séquence invalide
    }

    bitIndex++;
    if (bitIndex == 8) {
      bitIndex = 0;
      byteIndex++;
      if (byteIndex < length / 2) {  // Évite de dépasser la taille du tableau
        decodedData[byteIndex] = 0;
      }
    }
  }
  return byteIndex;  // Nombre d'octets décodés
}

Step 5 - last step: Receive, Transcode and Send

In this step, Send and receive code are merged.

Here is the new strategy:

  1. Wait for data
  2. Verify packet length
  3. Read data
  4. Decode manchester
  5. Verifiy manchester validity
  6. Verify crc
  7. Verify that ID is one of the wheel on car
  8. Transcode data
  9. Generate CRC8
  10. Encode Manchester
  11. Shift data
  12. Send data twice

The full process is 53 ms for recognised ID, one sending/receive is 9.6ms

The code in loop is very long with a lot a printings at each step but this is very useful to understand if each encoding/decoding behave properly.

Data are no fully decoded before transcoding: this is not usefull because only ID, unknown and CRC need to be overwrited.

I need to send data twice because CC1101 do not set Sync Mode to 0 the first time. I do not know why: a CC1101 bug or I didn't understand the CC1101 data sheet. Maybe a CC1101 specialist could explain. Maybe a ELECHOUSE_cc1101.S.reset could solve this.

Here is the full code:


#include <ELECHOUSE_CC1101_SRC_DRV.h>

#define CC1101_FREQUENCY 433.92              //433.92  // Frequency for TPMS
#define PacketLength 18                      //receive
uint32_t IDfrom[] = { 0x102e87, 0x0fd7bb };  //left,right
uint32_t IDto[] = { 0x101bb3, 0x102647 };    //left,right
int count = 0;

byte buffer[] = { 0xA5, 0x55, 0x55, 0x55, 0x5A, 0x69, 0x95, 0x6A, 0x59, 0xA9, 0x56, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x59 };  //for test
byte decoded[9];
byte encodedData[18];  // Manchester encoded data (2x the size)
byte shiftedData[23];  // shifted+preamble Manchester encoded data (Manchester size+5)

void setup() {
  Serial.begin(115200);
  Serial.println("Initializing CC1101...");

  if (ELECHOUSE_cc1101.getCC1101()) {  // Check the CC1101 Spi connection.
    Serial.println("Connection OK  !!!!!!!!!!!!!!!!!!!!!!!!!!!!");
  } else {
    Serial.println("Connection Error  ?????????????????????????");
  }

  ELECHOUSE_cc1101.Init();                    // must be set to initialize the cc1101!
  ELECHOUSE_cc1101.setCCMode(1);              // set config for internal transmission mode.
  ELECHOUSE_cc1101.setModulation(0);          // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
  ELECHOUSE_cc1101.setMHZ(CC1101_FREQUENCY);  // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet.
  ELECHOUSE_cc1101.setDeviation(35);          //47.60// Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz.
  ELECHOUSE_cc1101.setChannel(0);             // Set the Channelnumber from 0 to 255. Default is cahnnel 0.
  ELECHOUSE_cc1101.setChsp(199.95);           // The channel spacing is multiplied by the channel number CHAN and added to the base frequency in kHz. Value from 25.39 to 405.45. Default is 199.95 kHz.
  ELECHOUSE_cc1101.setRxBW(812.50);           // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz.
  ELECHOUSE_cc1101.setDRate(19.23);           //18.5  // Set the Data Rate in kBaud. Value from 0.02 to 1621.83. Default is 99.97 kBaud!
  ELECHOUSE_cc1101.setPA(10);                 // Set TxPower. The following settings are possible depending on the frequency band.  (-30  -20  -15  -10  -6    0    5    7    10   11   12) Default is max!
  ELECHOUSE_cc1101.setSyncWord(0x55, 0x56);   // Set sync word. Must be the same for the transmitter and receiver. (Syncword high, Syncword low)
  ELECHOUSE_cc1101.setAdrChk(0);              // Controls address check configuration of received packages. 0 = No address check. 1 = Address check, no broadcast. 2 = Address check and 0 (0x00) broadcast. 3 = Address check and 0 (0x00) and 255 (0xFF) broadcast.
  ELECHOUSE_cc1101.setAddr(0);                // Address used for packet filtration. Optional broadcast addresses are 0 (0x00) and 255 (0xFF).
  ELECHOUSE_cc1101.setWhiteData(0);           // Turn data whitening on / off. 0 = Whitening off. 1 = Whitening on.
  ELECHOUSE_cc1101.setPktFormat(0);           // Format of RX and TX data. 0 = Normal mode, use FIFOs for RX and TX. 1 = Synchronous serial mode, Data in on GDO0 and data out on either of the GDOx pins. 2 = Random TX mode; sends random data using PN9 generator. Used for test. Works as normal mode, setting 0 (00), in RX. 3 = Asynchronous serial mode, Data in on GDO0 and data out on either of the GDOx pins.
  ELECHOUSE_cc1101.setLengthConfig(0);        //!!!  // 0 = Fixed packet length mode. 1 = Variable packet length mode. 2 = Infinite packet length mode. 3 = Reserved
  ELECHOUSE_cc1101.setCrc(0);                 // 1 = CRC calculation in TX and CRC check in RX enabled. 0 = CRC disabled for TX and RX.
  ELECHOUSE_cc1101.setCRC_AF(0);              // Enable automatic flush of RX FIFO when CRC is not OK. This requires that only one packet is in the RXIFIFO and that packet length is limited to the RX FIFO size.
  ELECHOUSE_cc1101.setDcFilterOff(0);         // Disable digital DC blocking filter before demodulator. Only for data rates ≤ 250 kBaud The recommended IF frequency changes when the DC blocking is disabled. 1 = Disable (current optimized). 0 = Enable (better sensitivity).
  ELECHOUSE_cc1101.setManchester(0);          // Enables Manchester encoding/decoding. 0 = Disable. 1 = Enable.
  ELECHOUSE_cc1101.setFEC(0);                 // Enable Forward Error Correction (FEC) with interleaving for packet payload (Only supported for fixed packet length mode. 0 = Disable. 1 = Enable.
  ELECHOUSE_cc1101.setPRE(0);                 // Sets the minimum number of preamble bytes to be transmitted. Values: 0 : 2, 1 : 3, 2 : 4, 3 : 6, 4 : 8, 5 : 12, 6 : 16, 7 : 24
  ELECHOUSE_cc1101.setPQT(0);                 // Preamble quality estimator threshold. The preamble quality estimator increases an internal counter by one each time a bit is received that is different from the previous bit, and decreases the counter by 8 each time a bit is received that is the same as the last bit. A threshold of 4∙PQT for this counter is used to gate sync word detection. When PQT=0 a sync word is always accepted.
  ELECHOUSE_cc1101.setAppendStatus(0);        // When enabled, two status bytes will be appended to the payload of the packet. The status bytes contain RSSI and LQI values, as well as CRC OK.

  Serial.println("CC1101 initialized.");

  //Start RX
  ELECHOUSE_cc1101.setPacketLength(18);
  ELECHOUSE_cc1101.setSyncMode(1);          //sync on 0x5556
  ELECHOUSE_cc1101.SpiStrobe(CC1101_SFRX);  //flush fifo
  ELECHOUSE_cc1101.SpiStrobe(CC1101_SRX);   //enable rx
  Serial.println("Rx Mode");
}

void loop() {
  byte len = ELECHOUSE_cc1101.SpiReadStatus(CC1101_RXBYTES);  //!!!!!!!!!!!!!!!!!

  if (len > 0) {
    long t = millis();
    long t2;
    do {
      len = ELECHOUSE_cc1101.SpiReadStatus(CC1101_RXBYTES);
      t2 = millis();
    } while (len < PacketLength & ((t2 - t) < 20));  //20ms timeout
    Serial.println();

    Serial.print("len :");
    Serial.print(len);
    Serial.print(" temps : ");
    Serial.print(t2 - t);
    Serial.print(" ms");

    if (len == PacketLength) {
      ELECHOUSE_cc1101.SpiReadBurstReg(CC1101_RXFIFO, buffer, PacketLength);  //read rx FIFO !!!!!!!!!!!!!!!!!!!!!

      //Print received in bytes format.
      Serial.print(" Data: ");
      for (int i = 0; i < PacketLength; i++) {
        if (buffer[i] < 16) Serial.print("0");
        Serial.print(buffer[i], HEX);
        Serial.print(" ");
      }
      Serial.println();

      int nbdecoded = manchesterDecode(buffer, decoded, PacketLength);
      Serial.print("Nb decoded :");
      Serial.print(nbdecoded);

      if (nbdecoded == 9) {  //manchester valide

        //Print decoded in bytes format.
        Serial.print(" Decoded: ");
        for (int i = 0; i < nbdecoded; i++) {
          if (decoded[i] < 16) Serial.print("0");
          Serial.print(decoded[i], HEX);
          Serial.print(" ");
        }

        byte crc = crc8(decoded, 8, 0x07, 0x00);
        Serial.print(" CRC: ");
        Serial.print(crc, HEX);

        if (crc == decoded[8]) { 

          //Print readings
          Serial.println(" OK !!!!!!");  //crc ok

          Serial.print(" FLAG : ");
          Serial.print(decoded[0] >> 2, HEX);

          float pressure_raw = (decoded[0] & 0x0003) << 8 | (decoded[1] & 0x00ff);
          float pressure_kpa = pressure_raw * 0.75;
          Serial.print(" PRESSURE : ");
          Serial.print(pressure_kpa);

          Serial.print(" TEMP : ");
          Serial.print(decoded[2] - 30);

          Serial.print(" ID : ");
          if (decoded[5] < 16) Serial.print("0");
          Serial.print(decoded[5], HEX);
          if (decoded[4] < 16) Serial.print("0");
          Serial.print(decoded[4], HEX);
          if (decoded[3] < 16) Serial.print("0");
          Serial.print(decoded[3], HEX);

          Serial.print(" CODE : ");
          if (decoded[7] < 16) Serial.print("0");
          Serial.print(decoded[7], HEX);
          if (decoded[6] < 16) Serial.print("0");
          Serial.print(decoded[6], HEX);

          //Verify ID
          byte j = 127;
          byte ID3 = IDfrom[0] & 0xFF;
          byte ID4 = (IDfrom[0] >> 8) & 0xFF;
          byte ID5 = (IDfrom[0] >> 16) & 0xFF;
          if ((decoded[3] == ID3) && (decoded[4] == ID4) && (decoded[5] == ID5)) j = 0;
          ID3 = IDfrom[1] & 0xFF;
          ID4 = (IDfrom[1] >> 8) & 0xFF;
          ID5 = (IDfrom[1] >> 16) & 0xFF;
          if ((decoded[3] == ID3) && (decoded[4] == ID4) && (decoded[5] == ID5)) j = 1;
          Serial.print("Index : ");
          Serial.println(j);
          if (j < 2) {  //valid ID from

            //Send data -----------------------------------------------

            //transcode data
            decoded[3] = IDto[j] & 0xFF;
            decoded[4] = (IDto[j] >> 8) & 0xFF;
            decoded[5] = (IDto[j] >> 16) & 0xFF;
            decoded[6] = 0x09;                          //0x09;
            decoded[7] = 0x94;                          //0x94 degonfle:8C   ;
            decoded[8] = crc8(decoded, 8, 0x07, 0x00);  // Calculate CRC8 checksum with polynomial 0x07 and initial value 0x00=CRC-8-ATM (CRC-8-CCITT)

            // Re-Encode data using Manchester encoding
            encodeManchester(encodedData, decoded, 9);

            //shift tpms data
            shift(shiftedData, encodedData, 18);

            // Print data
            Serial.println("Transcode DATA:");
            for (int k = 0; k < 9; k++) {
              Serial.print(decoded[k], HEX);
              Serial.print(" ");
            }
            Serial.println();

            // Print data
            Serial.println("ENCODED TPMS DATA:");
            for (int i = 0; i < 18; i++) {
              Serial.print(encodedData[i], HEX);
              Serial.print(" ");
            }
            Serial.println();


            // Print data
            Serial.println("SHIFTED TPMS DATA:");
            for (int i = 0; i < 23; i++) {
              char s[80];
              snprintf(s, sizeof(s), "%02x ", shiftedData[i]);
              Serial.write(s);
            }
            Serial.println();

            // Send TPMS data
            ELECHOUSE_cc1101.setSyncMode(0);
            ELECHOUSE_cc1101.setPacketLength(23);  // Indicates the packet length when fixed packet length mode is enabled. If variable packet length mode is used, this value indicates the maximum packet length allowed.
            ELECHOUSE_cc1101.SpiStrobe(CC1101_SIDLE);
            ELECHOUSE_cc1101.SpiStrobe(CC1101_SFTX);                            //flush TXfifo
            ELECHOUSE_cc1101.SpiWriteBurstReg(CC1101_TXFIFO, shiftedData, 23);  //write data to send
            ELECHOUSE_cc1101.SpiStrobe(CC1101_STX);                             //start send
            Serial.print("TX bytes:");
            txbytes();  //wait for the end of transmission
            //send again because first send do not set syncmode=0
            //ELECHOUSE_cc1101.S.reset could be tested as an quickest alternative
            ELECHOUSE_cc1101.SpiStrobe(CC1101_SFTX);                            //flush TXfifo
            ELECHOUSE_cc1101.SpiWriteBurstReg(CC1101_TXFIFO, shiftedData, 23);  //write data to send
            ELECHOUSE_cc1101.SpiStrobe(CC1101_STX);                             //start send
            Serial.print("TX bytes:");
            txbytes();  //wait for the end of transmission
            count++;
            Serial.println("TPMS sent count: " + (String)count);
            //End send data---------------------------------------------

          } else {  //no valide ID from
            Serial.println("No valid ID !!!!!!!!!!!!!");
          }
        } else {
          Serial.println(" CRC erreur!!!");
        }
      } else {  //nbdecoded <> 9
        Serial.println(" erreur de decodage Manchester");
      }
    } else {  //len <> PacketLength
      Serial.println(" len <> PacketLength");
    }
    ELECHOUSE_cc1101.setSyncWord(0x55, 0x56);
    ELECHOUSE_cc1101.setSyncMode(1);  //sync on 0x5556, 1 = 16 sync word bits detected.
    ELECHOUSE_cc1101.setPacketLength(18);
    ELECHOUSE_cc1101.SpiStrobe(CC1101_SFRX);  //flush rx fifo
    ELECHOUSE_cc1101.SpiStrobe(CC1101_SIDLE);
    ELECHOUSE_cc1101.SpiStrobe(CC1101_SRX);  //enable rx
    Serial.println("Rx Mode: " + (String)(millis() - t) + " ms");
  }  //end if (len > 0)
}

void encodeManchester(uint8_t *encoded, const uint8_t *data, size_t len) {
  size_t bitIndex = 0;
  for (size_t i = 0; i < len; i++) {
    for (int bit = 7; bit >= 0; bit--) {
      if (data[i] & (1 << bit)) {
        // Bit is 1, Manchester encoding is 10
        encoded[bitIndex / 8] |= (1 << (7 - (bitIndex % 8)));
        bitIndex++;
        encoded[bitIndex / 8] &= ~(1 << (7 - (bitIndex % 8)));
      } else {
        // Bit is 0, Manchester encoding is 01
        encoded[bitIndex / 8] &= ~(1 << (7 - (bitIndex % 8)));
        bitIndex++;
        encoded[bitIndex / 8] |= (1 << (7 - (bitIndex % 8)));
      }
      bitIndex++;
    }
  }
}

byte manchesterDecode(const byte *manchesterData, byte *decodedData, size_t length) {
  size_t bitCount = length * 8;       // Chaque byte contient 8 bits
  if (bitCount % 2 != 0) return 255;  // Vérifie si la longueur est valide

  byte bitIndex = 0;
  byte byteIndex = 0;
  decodedData[byteIndex] = 0;

  for (size_t i = 0; i < bitCount; i += 2) {
    byte first = (manchesterData[i / 8] >> (7 - (i % 8))) & 0x01;
    byte second = (manchesterData[(i + 1) / 8] >> (7 - ((i + 1) % 8))) & 0x01;

    if (first == 0 && second == 1) {
      decodedData[byteIndex] <<= 1;
      decodedData[byteIndex] |= 0;  // 0 logique
    } else if (first == 1 && second == 0) {
      decodedData[byteIndex] <<= 1;
      decodedData[byteIndex] |= 1;  // 1 logique
    } else {
      return 254;  // Séquence invalide
    }

    bitIndex++;
    if (bitIndex == 8) {
      bitIndex = 0;
      byteIndex++;
      if (byteIndex < length / 2) {  // Évite de dépasser la taille du tableau
        decodedData[byteIndex] = 0;
      }
    }
  }
  return byteIndex;  // Nombre d'octets décodés
}

void shift(uint8_t *shifted, const uint8_t *data, size_t len) {  //add preamble 05 55 55 6. , shift data 4 bits right and add 0 at end
  shifted[0] = 0x05;
  shifted[1] = 0x55;
  shifted[2] = 0x55;
  shifted[3] = 0x55;
  shifted[4] = 0x60 | (data[0] & 0xf0) >> 4;
  for (int i = 1; i < len; i++) {  //shit 4 bits
    shifted[i + 4] = (data[i - 1] & 0x0f) << 4 | (data[i] & 0xf0) >> 4;
  }
  shifted[len + 4] = (data[len - 1] & 0x0f) << 4;  //last hex and 0 at end
}

uint8_t crc8(const uint8_t *data, size_t len, uint8_t polynomial, uint8_t initial) {
  uint8_t crc = initial;
  while (len--) {
    crc ^= *data++;
    for (uint8_t i = 0; i < 8; i++) {
      if (crc & 0x80)
        crc = (crc << 1) ^ polynomial;
      else
        crc <<= 1;
    }
  }
  return crc;
}

void state() {  //voir Main Radio Control State Machine State sur cc1101.pdf p93 & p50
  int state;
  do {
    state = 0x0f & ELECHOUSE_cc1101.SpiReadStatus(CC1101_MARCSTATE);
    Serial.print(state);
    Serial.print(" ");
  } while (state != 1);
  Serial.println();
}

void txbytes() {  //voir status sur cc1101.pdf p94
  int txbytes;
  do {
    txbytes = ELECHOUSE_cc1101.SpiReadStatus(CC1101_TXBYTES);
    Serial.print(txbytes);
    Serial.print(" ");
  } while (txbytes > 0);
  Serial.println();
}

The code can certainly be optimised.
A further step would be a code able to transcode any TPMS but that's a huge job.
I hope that my posts will help you to decode/encode/transcode your own TPMS: only one step could be usefull to you if your project is for example:

  • Fake TPMS sender
  • Show TPMS data on LCD
  • TPMS alarm buzzer

Hi,

Have you been able to solve problem with sending twice the package? Any improvements?

I'm also trying to write my own tpms signal simulator but as for now it seems the generated signal by cc1101 is much different than genuine signal from continental sensors.

Btw, do you know which company is delivering sensor for Renault?

Have you been able to solve problem with sending twice the package?

No, I still need to send twice the package. This should be a CC1101 bug.

Btw, do you know which company is delivering sensor for Renault?

No. You can find pictures of the sensors here: renault-zoe.forumpro.fr
Can you post your rtl_433 decoded data.

Well, Unfortunately I was not able to generate signal which would be accepted by my car or by tpms activator tools (used to make tpms sensors active). I looked on this signal and it seems to be very different from the signal generated by true tpms sensors...