Decoding SENT protocol

Hi all :slight_smile:

Has anybody successfully decoded the SENT protocol using an Arduino Uno and/or Mega?

I promise I've read the discussions that I can find in this forum but all seem to close without any resolution. I'm not sure if that is because its so easy that everyone figured it out for themselves, or so complicated that everyone gave up, or its simply not possible.

My problem isn't the actual decoding, that's pretty easy. Using my Saleae Analyzer I can see and decode the data stream and I've written some code for the Uno that decodes the data just fine if I manually input the falling edge to falling edge durations.

My problem is accurately measuring the falling edge to falling edge durations in the first place. As the tick time in SENT is defined as 3uS < tick < 90uS I had hoped that the particular sensor that I need to decode would use a tick time close to the 90uS end of the spectrum, but alas its actually 3.15uS so using interrupt triggering just isn't accurate enough :frowning:

SENT is a fairly simple protocol incorporating a CRC, and its an extremely fast data stream, so I'm fairly confident that even if I could measure the edges to 1uS that would be accurate enough for my needs as I could simply ignore any data that fails the CRC. I'm not too bothered about the transmission speed, more the accuracy of the data.

Any advice / guidance / words of encouragement would be very gratefully received !

Gareth

https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf take a look at 15.6

If the micro doesn't have anything else to do, polling is much faster and has less latency than interrupts.

To time a transition on a pin you can use something like this (used to time codewheel transitions):

	//first pass synchronizes edges
	while( (PINC&(1<<PC5)) ==0);	//seeing white, wait till black
	while( (PINC&(1<<PC5)) !=0);	//seeing black, wait till white
       // TCNT1 is a 16 bit timer counter, running at 1 usec per tick
	then=TCNT1;						//now time one full revolution
	while( (PINC&(1<<PC5)) ==0);	//seeing white, wait till black
	while( (PINC&(1<<PC5)) !=0);	//seeing black, wait till white
	now=TCNT1;
	period=now-then;  //always works for unsigned ints

On AVR processors, those while loops each compile to two machine instructions: bit test and loop back.

Probably the sensors are rare and not many have tried it.

Could you try this if it is able to decode the bitstream?


// Connect data pin of sensor to D8 of the Arduino Uno/Nano/Mini

// Time for one tick (in ยตs)
constexpr uint8_t tick_time = 3;

constexpr auto serial_baud = 115200;

/************************************************************/

constexpr uint8_t tick_syn = 56;
constexpr uint32_t tick_offest = 12;

constexpr uint16_t cycl_tick = F_CPU / 1000000 * tick_time; // CPU cycles per tick

// Allow +/- 20% difference
constexpr uint16_t cycl_syn_min = cycl_tick * tick_syn * 4 / 5;
constexpr uint16_t cycl_syn_max = cycl_tick * tick_syn * 6 / 5;

constexpr auto LOOKUP_SIZE = 256;
constexpr auto LOOKUP_DIV = 4;

uint16_t cycl_syn;
uint16_t cycl_offset;
int8_t cycl_lookup[LOOKUP_SIZE];

constexpr uint8_t BUFFER_SIZE = 32;

volatile uint16_t buffer[BUFFER_SIZE];
volatile uint8_t buffer_write_pointer;
volatile uint8_t buffer_read_pointer;

uint16_t last_icr = 0;
volatile bool error = false;
ISR(TIMER1_CAPT_vect) {
  uint16_t value = ICR1;
  buffer[buffer_write_pointer] = value - last_icr;
  last_icr = value;

  buffer_write_pointer++;
  if (buffer_write_pointer >= BUFFER_SIZE) {
    buffer_write_pointer = 0;
  }
  if (buffer_write_pointer == buffer_read_pointer) {
    error = true;
  }
}

bool buffer_available() {
  return buffer_write_pointer != buffer_read_pointer;
}

uint16_t buffer_read() {
  while (!buffer_available()) {
    // wait for data
  }
  uint16_t ret = buffer[buffer_read_pointer];
  auto new_p = buffer_read_pointer + 1;
  if (new_p >= BUFFER_SIZE) {
    new_p = 0;
  }

  uint8_t oldSREG = SREG;
  cli();
  buffer_read_pointer = new_p;
  SREG = oldSREG;

  return ret;
}

uint16_t get_syn_cycl() {
  while (true) {
    uint16_t dx = buffer_read();
    if (dx >= cycl_syn_min && dx <= cycl_syn_max) {
      return dx;
    }
  }
}

void wait_for_syn() {
  while (true) {
    uint16_t dx = buffer_read();
    auto t = dx > cycl_syn ? dx - cycl_syn : cycl_syn - dx;
    if ( t < 10) {
      return;
    }
  }
}

void setup() {
  Serial.begin(serial_baud);
  Serial.print("cycl_tick = ");
  Serial.println(cycl_tick);

  TCCR1A = 0;
  TCCR1B = 1;
  TCCR1C = 0;

  TIMSK1 = (1 << ICIE1);

  cycl_syn = get_syn_cycl();
  cycl_offset = cycl_syn * (tick_offest * 2 - 1) / tick_syn / 2; // cycl_tick * 11.5

  for (uint16_t c = 0; c < LOOKUP_SIZE; c++) {
    int8_t value = c * tick_syn * LOOKUP_DIV / cycl_syn;
    if ( value >= 16) {
      value = -1;
    }
    cycl_lookup[c] = value;
  }

  auto cycl_syn1 = get_syn_cycl();
  auto dx = cycl_syn > cycl_syn1 ? cycl_syn - cycl_syn1 : cycl_syn1 - cycl_syn;

  Serial.print("cycl_syn = ");
  Serial.println(cycl_syn);

  Serial.print("cycl_syn ok? ");
  Serial.println(dx < 10 ? "yes" : "no");

  Serial.print("cycl_offset = ");
  Serial.println(cycl_offset);

  if (LOOKUP_SIZE < (cycl_syn * 17 / tick_syn / LOOKUP_DIV)) {
    Serial.println("Lookup table would exceed expected size!");
    while (true) {
      ;
    }
  }

  Serial.println("Lookup table:");
  for (int x = 0; x < 16; x++) {
    Serial.print(x * 16, HEX);
    Serial.print(": ");
    for (int y = 0; y < 16; y++) {
      Serial.print(cycl_lookup[x * 16 + y], HEX);
      Serial.print(" ");
    }
    Serial.println("");
  }
  Serial.println("************************");
  Serial.flush();
  error = false;
}

constexpr char toHex[] = "0123456789ABCDEF";
void loop() {
  constexpr auto FRAME_SIZE = 8;
  char frame[FRAME_SIZE + 1];

  wait_for_syn();
  for (int c = 0; c < FRAME_SIZE; c++) {
    auto dx = buffer_read();
    dx = (dx - cycl_offset) / LOOKUP_DIV;
    if (dx > LOOKUP_SIZE || cycl_lookup[dx] < 0) {
      frame[c] = '!';
    } else {
      frame[c] = toHex[cycl_lookup[dx]];
    }
  }
  Serial.write(frame, FRAME_SIZE);
  Serial.println("");
  if (error) {
    Serial.println("Buffer overflow");
    while (true) {
      ;
    }
  }
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.

Code works well with a few changes:

// added these definitions to the top
#define INVERTED_SIGNAL 1  // added since the signal from my device was inverted for some odd reason
#define NOISE_CANCELER_ENABLE 1 // likely not necessary, but cant hurt

...

// increased the serial speed, likely not necessary for slow SENT speeds, but required for 3ยตs ticks
constexpr auto serial_baud = 250000;  // used to be 115200, increased to outpace the SENT bus, otherwise the buffer overflows quickly

...

// implemented the new #defines at the top
TCCR1B = 1 | INVERTED_SIGNAL<<6 | NOISE_CANCELER_ENABLE<<7; // used to be just '1'

...

// replaced the following line
dx = (dx - cycl_offset) / LOOKUP_DIV;
// with
dx = (dx - cycl_offset - 12 * cycl_tick) / LOOKUP_DIV
// this removes the 12 tick constant off each pulse, since the pulses are 12-27 ticks long and the lookup table is configured for looking up 0-15 ticks

115200 is a little on the slow side leaving no time for printing anything else than the decoded nibbles. Maybe I also got lucky with my sensor which also sends padding.

I would not multiply by 12 but rather by 11.5 to allow for jitter of the sensor.
https://europe1.discourse-cdn.com/arduino/original/4X/9/2/b/92bcdf8ef5b3a1d77bab2cf767ea8cb38313d2ee.png

Also I wonder why cycl_offset does not do the job here.

I probably should move forward with my library (GitHub - BlinxFox/sent: Arduino example for decoding the bitstream of a sent (Single Edge Nibble Transmission) sensor) and bring it into the library manager

1 Like