Интересный баг в приёмнике на RP2040

Занимаясь некоторым извращением, а если быть точным, писал код в arduino ide под RP2040(планировался оптический приёмник в связке с с передатчиком Arduino nano на Atmega168p, но пока что увяз на самом процессе передачи). В чем суть возникшей проблемы:

  1. Система использует код Хэмминга(8,4) (никакой практической пользы в нём нет в данных условиях, просто спортивный интерес), и во время тестовых передач без его использования всё работает прекрасно
  2. Тут уже интереснее проблема, точнее первая вытекает отсюда, по абсолютно неизвестной мне причине декодирование кода Хэмминга происходит неверно(да и приём байт в целом), если в цикле заполнения буфера отсутствует вывод serial.

Конечно можно оставить всё как есть, ведь вроде как работает, но мне интересно почему прекращается адекватный приём в отсутствии связи с компом.

Скетчи ниже

Приёмник

#include <Arduino.h>

#define RECEIVEPIN 26
#define DIGRECEIVERPIN 11
#define THRESHHOLD 3320
#define BITRATE 400
#define BITPERIOD 1000/BITRATE
#define THFOBITPERIOD BITPERIOD*(3/4)
#define GET_BYTE(val, n) (((val) >> (8 * (n))) & 0xFF)

#define TEST

uint32_t _time;
uint32_t prevtime;

uint32_t fullTime = 0;       // 32-битное полное время
uint16_t lastTime16 = 0;     // последнее полученное 16-битное значение
bool firstPacket = true;     // флаг первого пакета

uint32_t FullTime(uint16_t currentTime16) {
    if (firstPacket) {
        fullTime = currentTime16;
        lastTime16 = currentTime16;
        firstPacket = false;
        return fullTime;
    }
    int16_t diff = currentTime16 - lastTime16;
    if (diff < 0) {
        diff += 65536;
    }
    fullTime += diff;
    lastTime16 = currentTime16;
    
    return fullTime;
}

void mig() {
  for(int i = 0; i < 10; ++i) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(200);
    digitalWrite(LED_BUILTIN, LOW);
    delay(200);
  }
}

void setup() 
{
  #ifdef TEST
    pinMode(DIGRECEIVERPIN,INPUT);
  #elif defined REAL
    pinMode(RECEIVERPIN,INPUT);
  #endif

  _time = 0;prevtime = 0;

  Serial.begin(115200);
  mig();
  
  while(digitalRead(DIGRECEIVERPIN)!=1)
  {
  }
  Serial.print(F("Receiver start work"));
  delay(THFOBITPERIOD);
}

void loop() 
{
    if(preambleSearch())
    {
        uint8_t buffer[10];
        for(int i = 0;i<10;++i)
        {
          uint8_t byte1 = receiveByte();uint8_t byte2 = receiveByte();
          buffer[i] = decode(byte1,byte2);
        }
        if(bufferp[8]==)
        uint16_t data[4];
        for(int i = 0;i<4;++i)
          data[i] = (buffer[i*2]<<8)|buffer[i*2+1];
        /*
        Serial.print(F("Transmitter time work: "));
        Serial.print(FullTime(data[0])/1000);
        Serial.print(F(" T = ")); 
        Serial.print(data[1]*0.01);
        Serial.print(F(" P = "));
        Serial.print(data[2]+90000);
        Serial.print(F(" H = "));
        Serial.print(data[3]);
        Serial.print('\n');*/
    }
}

bool preambleSearch()
{
  uint16_t preamble = 0;
  uint32_t bitbuffer = 0;
  while (preamble != 0x4845)
  {
    bitbuffer = (bitbuffer<<1)|digitalRead(DIGRECEIVERPIN);
    
    preamble = (decode(GET_BYTE(bitbuffer,3),GET_BYTE(bitbuffer,2))<<8)|decode(GET_BYTE(bitbuffer,1),GET_BYTE(bitbuffer,0));
    delay(BITPERIOD);
  }
  return true;
}

uint8_t receiveByte()
{
  uint8_t byte = 0;
  for(int i = 0;i < 8;++i)
    {
      byte = (byte << 1) | digitalRead(DIGRECEIVERPIN);
      delay(BITPERIOD);
    } 
  return byte;
}

uint8_t decode(uint8_t byte1, uint8_t byte2) 
{
  return ((decodeHamming(byte1) << 4) | decodeHamming(byte2));
}

uint8_t decodeHamming(uint8_t sembyte) {
    uint8_t d0 = (sembyte >> 7) & 1;  // bit 7
    uint8_t d1 = (sembyte >> 6) & 1;  // bit 6
    uint8_t d2 = (sembyte >> 5) & 1;  // bit 5
    uint8_t d3 = (sembyte >> 4) & 1;  // bit 4
    uint8_t p0 = (sembyte >> 3) & 1;  // bit 3
    uint8_t p1 = (sembyte >> 2) & 1;  // bit 2
    uint8_t p2 = (sembyte >> 1) & 1;  // bit 1
    uint8_t parity = sembyte & 1;     // bit 0
    
    // Calculate syndrome bits
    uint8_t syn0 = d0 ^ d1 ^ d2 ^ p0;  // syn1[0]
    uint8_t syn1 = d1 ^ d2 ^ d3 ^ p1;  // syn1[1]
    uint8_t syn2 = d0 ^ d1 ^ d3 ^ p2;  // syn1[2]
    
    // Combine syndrome bits to get error position
    uint8_t error_pos = (syn0 << 2) | (syn1 << 1) | syn2;
    
    // If there's an error (syndrome != 0), correct it
    if (error_pos != 0) {
        // Note: error_pos is 1-indexed from right in original JS,
        // we need to convert to 0-indexed from left
        uint8_t bit_to_flip = 8 - error_pos;
        
        // Flip the erroneous bit
        switch (bit_to_flip) {
            case 7: d0 ^= 1; break;  // bit 7
            case 6: d1 ^= 1; break;  // bit 6
            case 5: d2 ^= 1; break;  // bit 5
            case 4: d3 ^= 1; break;  // bit 4
            case 3: p0 ^= 1; break;  // bit 3
            case 2: p1 ^= 1; break;  // bit 2
            case 1: p2 ^= 1; break;  // bit 1
            case 0: parity ^= 1; break; // bit 0
        }
    }
    
    // Extract the original 4-bit data
    uint8_t decSemByte = (d0 << 3) | (d1 << 2) | (d2 << 1) | d3;
    
    return decSemByte;
}

Передатчик

#include <GyverBME280.h>

#include <math.h>



#define LASER_PIN 9

#define MAXPACKETSIZE 25

#define BITRATE 400

#define BITPERIOD 1000/BITRATE

#define BITBLOCKSIZE BITRATE

#define BMEADRESS 0x76

#define PREAMBLE 0x4845

#define POSTAMBLE 0xAA55

#define CONSTANT 29.254

#define BAR 101325



#define TEST



GyverBME280 dataMaker;



uint16_t packet[6];



uint8_t HammingEncode(uint8_t sembyte);

void sendBlock(uint8_t *block);



const size_t SerialPrintf (const char *szFormat, ...) 

{

    va_list argptr;

    va_start(argptr, szFormat);

    char *szBuffer = 0;

    const size_t nBufferLength = vsnprintf(szBuffer, 0, szFormat, argptr) + 1;

    if (nBufferLength == 1) return 0;

    szBuffer = (char *) malloc(nBufferLength);

    if (! szBuffer) return - nBufferLength;

    vsnprintf(szBuffer, nBufferLength, szFormat, argptr);

    Serial.print(szBuffer);

    free(szBuffer);

    return nBufferLength - 1;

}



void setup() 

{

  Serial.begin(9600);



  pinMode(LASER_PIN,OUTPUT);



  packet[0] = PREAMBLE;

  packet[5] = POSTAMBLE;

  if (!dataMaker.begin(0x76)) Serial.println("Error!");

}



void loop() 

{

      setpacketPayload();

      uint8_t buffer[(MAXPACKETSIZE-1)/2];

      buffer[0] = 0x48;

      buffer[1] = 0x45;

      buffer[((MAXPACKETSIZE-1)/2)-2] = 0xAA;

      buffer[((MAXPACKETSIZE-1)/2)-1] = 0x55;

      for(int i = 1;i<6;++i)

      {

        buffer[i*2] = (packet[i]>>8) & 0xFF;

        buffer[i*2+1] = packet[i] & 0xFF;



      }

      uint8_t encoded_packet[24];

      for(int i = 0;i<12;++i)

      {

        encoded_packet[i*2] = HammingEncode((buffer[i]>>4)&0x0F);

        encoded_packet[i*2+1] = HammingEncode(buffer[i]&0x0F);

      }

      Serial.print(encoded_packet[22],HEX);Serial.print(encoded_packet[23],HEX);

      Serial.println();

      sendBlock(encoded_packet);

}



void setpacketPayload()

{

  packet[1] = uint16_t(millis());

  packet[2] = uint16_t(dataMaker.readTemperature() * 100);

  packet[3] = uint16_t(dataMaker.readPressure()-90000);

  packet[4] = uint16_t(CONSTANT*(dataMaker.readTemperature()+273)*log(BAR/dataMaker.readPressure()));

}



uint8_t HammingEncode(uint8_t sembyte) 

{

    uint8_t d0 = (sembyte >> 3) & 1;  // bit 3

    uint8_t d1 = (sembyte >> 2) & 1;  // bit 2

    uint8_t d2 = (sembyte >> 1) & 1;  // bit 1

    uint8_t d3 = (sembyte >> 0) & 1;  // bit 0

    

    // Calculate Hamming parity bits

    uint8_t p0 = d0 ^ d1 ^ d2;  // d[4]

    uint8_t p1 = d1 ^ d2 ^ d3;  // d[5]

    uint8_t p2 = d0 ^ d1 ^ d3;  // d[6]

    uint8_t parity = d0 ^ d1 ^ d2 ^ d3 ^ p0 ^ p1 ^ p2;

    uint8_t byte = 0;

    byte = (byte << 1) | (d0 & 1);

    byte = (byte << 1) | (d1 & 1);

    byte = (byte << 1) | (d2 & 1);

    byte = (byte << 1) | (d3 & 1);

    byte = (byte << 1) | (p0 & 1);

    byte = (byte << 1) | (p1 & 1);

    byte = (byte << 1) | (p2 & 1);

    byte = (byte << 1) | (parity & 1);

    

    return byte;

}



void sendBlock(uint8_t *block)

{

  Serial.println(F("Send block"));

  digitalWrite(LASER_PIN, 1);

  for(int i = 0;i<24;++i)

    sendByte(block[i]);

}



void sendByte(uint8_t byte)

{

  for(int i = 7;i>=0;--i)

  {

    digitalWrite(LASER_PIN, ((byte>>i)&1));  // Сдвиг вправо

    delay(BITPERIOD);

  }

}

Грешу на то что это всё Arduino ide, но хочется услышать и другие предположения

код, похоже, вы вставили неверно:

Причина такого поведения всегда одна - в том месте где у вас Сериал, нужна задержка. А вот зачем она там и почему без нее не работает - это уже отдельный разговор.

1 лайк

Заменимте Serial на delay.
Проблема осталась или исчезла?

Это замечание наводит на грустные мысли о вашей квалификации…

2 лайка

Проблема с подбором самого делея, хотя в общем то, и при замене на него ничего не работает (выбирал следующие значения 1,2,5; И в микросекундах 400,600,800;)

В общем-то правильное замечание, я школьник, но подразумевалось что могут быть проблемы совместимости RP2040 и фреймворка ide. Потому что фреймворк вроде как сохраняет обратную совместимость с функциями из pico API, но при их использовании уходит в бесконечный перезапуск (во всяком случае, имею только такой опыт?

Это мало. Попробуй 200 мс.

1 лайк

Про то что деление целочисленное не забыли? К ,примеру 3/4 = 0 это так и задумано?

Могу ошибаться, смотрю бегло, ,но не увидел нормальной синхронизации приёмника с принимаемым сигналом. Только в сетапе, но этого мало, т.к. тайминги со временем могут “уплывать”

Всё равно не работает

Проблема не совсем в этом, потому что без помехоустойчивого кодирования всё работает хорошо. Синхронизацию конечно добавил на приём каждого нового пакета, но по большому сути дела не меняет.

Сначала думал что проблема в повышенной вероятности получения ошибки связанная с избыточностью кода, но проблема точно не в этом, ведь при добавлении serial всё работает

Чутка подкорректировал скетч приёмника, но проблема никуда ни ушла

#include <Arduino.h>




#define RECEIVEPIN 26

#define DIGRECEIVERPIN 11

#define THRESHHOLD 3320

#define BITRATE 400

#define BITPERIOD 1000/BITRATE

#define THFOBITPERIOD BITPERIOD*0.75

#define GET_BYTE(val, n) (((val) >> (8 * (n))) & 0xFF)




#define TEST




uint32_t _time;

uint32_t prevtime;




uint32_t fullTime = 0;       // 32-битное полное время

uint16_t lastTime16 = 0;     // последнее полученное 16-битное значение

bool firstPacket = true;     // флаг первого пакета




uint32_t FullTime(uint16_t currentTime16) {

    if (firstPacket) {

        fullTime = currentTime16;

        lastTime16 = currentTime16;

        firstPacket = false;

        return fullTime;

    }

    int16_t diff = currentTime16 - lastTime16;

    if (diff < 0) {

        diff += 65536;

    }

    fullTime += diff;

    lastTime16 = currentTime16;

    

    return fullTime;

}




void mig() {

  for(int i = 0; i < 10; ++i) {

    digitalWrite(LED_BUILTIN, HIGH);

    delay(200);

    digitalWrite(LED_BUILTIN, LOW);

    delay(200);

  }

}




void setup() 

{

  pinMode(DIGRECEIVERPIN,INPUT);




  _time = 0;prevtime = 0;




  Serial.begin(115200);

  mig();

  Serial.print(F("Receiver start work"));

  while(digitalRead(11)!=1){}

  delayMicroseconds(THFOBITPERIOD*1000);

}




void loop() 

{

        preambleSearch();

        uint8_t buffer[20];

        for(int i = 0;i<10;++i)

        {

          uint8_t byte1 = receiveByte();uint8_t byte2 = receiveByte();

          buffer[i*2] = byte1; buffer[i*2+1] = byte2; 

          //delay(200);

         Serial.print(byte1,HEX); Serial.print(' '); Serial.print(byte2,HEX);Serial.print(' ');

        }

        Serial.println();

        bool firstByte = decode(buffer[16],buffer[17]) == 0xAA;

        bool secondByte = decode(buffer[18],buffer[19]) == 0x55;

        if(firstByte && secondByte)

        {

        uint8_t bufferr[8];

        for(int i = 0;i<8;++i)

          bufferr[i]=decode(buffer[i*2],buffer[i*2+1]);

        uint16_t data[4];

        for(int i = 0;i<4;++i)

          data[i] = (bufferr[i*2]<<8)|bufferr[i*2+1];

        

        Serial.print(F("Transmitter time work: "));

        Serial.print(FullTime(data[0])/1000);

        Serial.print(F(" T = ")); 

        Serial.print(data[1]*0.01);

        Serial.print(F(" P = "));

        Serial.print(data[2]+90000);

        Serial.print(F(" H = "));

        Serial.print(data[3]);

        Serial.print('\n');}

}




bool preambleSearch()

{

  uint16_t preamble = 0;

  uint32_t bitbuffer = 0;

  while(digitalRead(11)!=1){}

  delayMicroseconds(THFOBITPERIOD*1000);

  while (preamble != 0x4845)

  {

    bitbuffer = (bitbuffer<<1)|digitalRead(DIGRECEIVERPIN);

    preamble = (decode(GET_BYTE(bitbuffer,3),GET_BYTE(bitbuffer,2))<<8)|decode(GET_BYTE(bitbuffer,1),GET_BYTE(bitbuffer,0));

    delayMicroseconds(BITPERIOD*1000);

  }

  return true;

}



uint8_t receiveByte()

{

  uint8_t byte = 0;

  for(int i = 0;i < 8;++i)

    {

      byte = (byte << 1) | digitalRead(DIGRECEIVERPIN);

      delayMicroseconds(BITPERIOD*1000);

    } 

  return byte;

}



uint8_t decode(uint8_t byte1, uint8_t byte2) 

{

  return ((decodeHamming(byte1) << 4) | decodeHamming(byte2));

}

uint8_t decodeHamming(uint8_t sembyte) {

    uint8_t d0 = (sembyte >> 7) & 1; 

    uint8_t d1 = (sembyte >> 6) & 1; 

    uint8_t d2 = (sembyte >> 5) & 1;

    uint8_t d3 = (sembyte >> 4) & 1; 

    uint8_t p0 = (sembyte >> 3) & 1;

    uint8_t p1 = (sembyte >> 2) & 1; 

    uint8_t p2 = (sembyte >> 1) & 1;

    uint8_t parity = sembyte & 1;

    uint8_t syn0 = d0 ^ d1 ^ d2 ^ p0;  // syn1[0]

    uint8_t syn1 = d1 ^ d2 ^ d3 ^ p1;  // syn1[1]

    uint8_t syn2 = d0 ^ d1 ^ d3 ^ p2;  // syn1[2]

    uint8_t error_pos = (syn0 << 2) | (syn1 << 1) | syn2;

    if (error_pos != 0) {

        uint8_t bit_to_flip = 8 - error_pos;

        switch (bit_to_flip) {

            case 7: d0 ^= 1; break;  // bit 7

            case 6: d1 ^= 1; break;  // bit 6

            case 5: d2 ^= 1; break;  // bit 5

            case 4: d3 ^= 1; break;  // bit 4

            case 3: p0 ^= 1; break;  // bit 3

            case 2: p1 ^= 1; break;  // bit 2

            case 1: p2 ^= 1; break;  // bit 1

            case 0: parity ^= 1; break; // bit 0

        }

    }

    uint8_t decSemByte = (d0 << 3) | (d1 << 2) | (d2 << 1) | d3;

    return decSemByte;

}

И скетч передатчика

#include <GyverBME280.h>

#include <math.h>


#define LASER_PIN 9

#define MAXPACKETSIZE 25

#define BITRATE 400

#define BITPERIOD 1000/BITRATE

#define BITBLOCKSIZE BITRATE

#define BMEADRESS 0x76

#define PREAMBLE 0x4845

#define POSTAMBLE 0xAA55

#define CONSTANT 29.254

#define BAR 101325




#define TEST




GyverBME280 dataMaker;


uint16_t packet[6];


uint8_t HammingEncode(uint8_t sembyte);

void sendBlock(uint8_t *block);


const size_t SerialPrintf (const char *szFormat, ...) 

{

    va_list argptr;

    va_start(argptr, szFormat);

    char *szBuffer = 0;

    const size_t nBufferLength = vsnprintf(szBuffer, 0, szFormat, argptr) + 1;

    if (nBufferLength == 1) return 0;

    szBuffer = (char *) malloc(nBufferLength);

    if (! szBuffer) return - nBufferLength;

    vsnprintf(szBuffer, nBufferLength, szFormat, argptr);

    Serial.print(szBuffer);

    free(szBuffer);

    return nBufferLength - 1;

}




void setup() 

{

  Serial.begin(9600);

  pinMode(LASER_PIN,OUTPUT);


  packet[0] = PREAMBLE;

  packet[5] = POSTAMBLE;

  if (!dataMaker.begin(0x76)) Serial.println("Error!");

}




void loop() 

{

      setpacketPayload();

      uint8_t buffer[(MAXPACKETSIZE-1)/2];

      buffer[0] = 0x48;

      buffer[1] = 0x45;

      buffer[((MAXPACKETSIZE-1)/2)-2] = 0xAA;

      buffer[((MAXPACKETSIZE-1)/2)-1] = 0x55;

      for(int i = 1;i<6;++i)

      {

        buffer[i*2] = (packet[i]>>8) & 0xFF;

        buffer[i*2+1] = packet[i] & 0xFF;




      }

      uint8_t encoded_packet[24];

      for(int i = 0;i<12;++i)

      {

        encoded_packet[i*2] = HammingEncode((buffer[i]>>4)&0x0F);

        encoded_packet[i*2+1] = HammingEncode(buffer[i]&0x0F);

      }

      sendBlock(encoded_packet);

      delayMicroseconds(BITPERIOD*1000);

}




void setpacketPayload()

{

  packet[1] = uint16_t(millis());

  packet[2] = uint16_t(dataMaker.readTemperature() * 100);

  packet[3] = uint16_t(dataMaker.readPressure()-90000);

  packet[4] = uint16_t(CONSTANT*(dataMaker.readTemperature()+273)*log(BAR/dataMaker.readPressure()));

}




uint8_t HammingEncode(uint8_t sembyte) 

{

    uint8_t d0 = (sembyte >> 3) & 1;

    uint8_t d1 = (sembyte >> 2) & 1;

    uint8_t d2 = (sembyte >> 1) & 1;

    uint8_t d3 = (sembyte >> 0) & 1;

    uint8_t p0 = d0 ^ d1 ^ d2;

    uint8_t p1 = d1 ^ d2 ^ d3;

    uint8_t p2 = d0 ^ d1 ^ d3;

    uint8_t parity = d0 ^ d1 ^ d2 ^ d3 ^ p0 ^ p1 ^ p2;

    uint8_t byte = 0;

    byte = (byte << 1) | (d0 & 1);

    byte = (byte << 1) | (d1 & 1);

    byte = (byte << 1) | (d2 & 1);

    byte = (byte << 1) | (d3 & 1);

    byte = (byte << 1) | (p0 & 1);

    byte = (byte << 1) | (p1 & 1);

    byte = (byte << 1) | (p2 & 1);

    byte = (byte << 1) | (parity & 1);

    

    return byte;

}




void sendBlock(uint8_t *block)

{

  digitalWrite(LASER_PIN, 1);

  for(int i = 0;i<24;++i)

  {

    sendByte(block[i]);

    Serial.print(block[i],HEX);Serial.print(' ');

  }

  Serial.println();

}




void sendByte(uint8_t byte)

{

  for(int i = 7;i>=0;--i)

  {

    digitalWrite(LASER_PIN, ((byte>>i)&1));  // Сдвиг вправо

    delayMicroseconds(BITPERIOD*1000);

  }

}

Вопрос решён. Надо было добавить задржку на 1/4 BITPERIOD и всё заработало, однако вопрос причины всё ещё стоит открытым

Вы сами себе противоречите:

Поставить правильный диагноз - начало решения…))

В Сериале синхронизация по каждому байту.

Не совсем понял где противоречу сам себе. Хотя конечно я изначально допустил ошибку в вопросе, ведь вчерашняя успешная работа программы зависела как оказывается от фазы Луны, а сегодня баг воспроизводился и на чистой передаче. Но всё равно, спасибо вам за помощь :slight_smile: Главное что нашёл решение, причину будет не так сложно найти

:smiley: точно.

1 лайк

точнее это не решение, а “костыль”

или, что вероятнее, просто забить на нее.
Этим и отличается “костыль” от настоящего решения

Причина то проста - у тебя передатчик тоже печатает на экран, и создает задержку между байтами. А если ты убираешь задержку в приемнике, то случается underflow :slight_smile: - читаешь быстрее, чем приходят данные.

1 лайк

Удивительно для меня другое - то, что этот код вообще работает. Никакой синхронизации нет, но передача - stream (байтовая). Так не работает. Или пакетную передачу делать и копить битики пока пакет не соберется и его процессить потом или сделать какую-то синхронизацию. Старт-стоп биты какие-нибудь.

костыль он и есть… что с него взять

Чуть изменит что в программе - и опять код превратится в тыкву

1 лайк

Причина не в этом, в отсутствие сериала в передатчике баг воспроизводится, и решается абсолютно таким же как было сказано “костылём”.

Использование детерминированной синхропоследовательности, тоже один из видов синхронной передачи. Синхронизируется приёмник с передатчиком по фронту первого единичного бита, после сдвигается на 3/4 и синхронизируется уже на этом значении. Вариант который вы предлагаете уже будет UARTом для самых маленьких

Не фанат оставлять костыли, тем более когда задача интересна в первую очередь мне самому