Последовательная связь - проблема strtok()

Я работаю над связью UART между ESP32 (отправитель) и платой ESP8266 NodeMCU (приемник).

Хост отправляет следующую строку символов:

Serial.println("2753192144$1&");

2753192144 — это полезная нагрузка. $1& — это трейлер пакета: $1 обозначает определенную опцию для получателя, а & — это символ завершения, указывающий на то, что пакет завершен.

На приемной стороне - вот что у меня:

#define MAX_REC_STR_SIZE 10
#define REC_TRAILER_SIZE 4 // $1& и терминатор строки
#define TRUE_TRAILER_SIZE 2 // размер опции на самом деле

void setup() {
  Serial.begin(9600);
}

void loop() {
    if(Serial.available()){
    char data [MAX_REC_STR_SIZE + TRUE_TRAILER_SIZE + 1];
    Serial.readBytesUntil('&', data, MAX_REC_STR_SIZE + REC_TRAILER_SIZE); // данные не содержат завершения &
    data[MAX_REC_STR_SIZE + TRUE_TRAILER_SIZE + 1] = 0; // добавление нулевого терминатора

    // Токенизация
    char *payload = strtok(data, "$"); // получение полезной нагрузки
    char opt = strtok(NULL, "$")[0]; // получаем option - 1 в нашем примере

    // Печать
    Serial.println(payload);
    Serial.println(opt);
  }
}

Этот код приводит к ошибке исключения стека в последовательном мониторе. Если я удалю последнюю строку, которая печатает opt, печать полезной нагрузки будет работать нормально, и я получу 2753192144 в последовательном выводе.

Я протестировал этот точный код в стандартном компиляторе C, за исключением того, что вместо части последовательного чтения я определил массив символов с помощью 2753192144$1& и использовал printf вместо Serial.println. Смотрите код ниже

#include <stdio.h>
#include <string.h>

int main()
{
    char data[] = "2753192144$1";

    char *payload = strtok(data, "$");
    char opt = strtok(NULL, "$")[0];
    printf("Payload is %s and opt is %c", payload, opt);

    return 0;
}

Этот код выводит

Payload is 2753192144 and opt is 1 

Это работает отлично. Есть идеи, почему скетч Arduino не работает?

, 👍1

Обсуждение

Есть несколько «удобных» функций, например readBytesUntil. У них есть тайм-аут. Вы можете использовать их один раз, когда вы новичок. Но после этого вам придется двигаться дальше и помещать символы в буфер, как только он станет доступен. По одному символу за раз для каждой итерации цикла arduino. Проверьте, достаточно ли данных в буфере, затем обработайте данные в буфере. Когда вы ожидаете 16 байт, создайте буфер из 40 байт и не допускайте его переполнения. Ваш код опасен во многих отношениях., @Jot

println отправляет два дополнительных символа, которые будут мешать фиксированному размеру сообщения. Также у вас есть буфер на 13 символов, но вы устанавливаете ограничение на readBytesUntil в 14 символов., @gre_gor

@gre_gor Ах да. Этот REC_TRAILER_SIZE в readBytesUntil() должен быть TRUE_TRAILER_SIZE, поскольку сам & не предназначен для чтения., @FShiwani

@Jot Да. Я заметил, что многие люди в других темах вручную считывают данные, и мне стало интересно, почему не так много людей используют эту функцию readBytesUntil()., @FShiwani


1 ответ


2
data[MAX_REC_STR_SIZE + TRUE_TRAILER_SIZE + 1] = 0; // добавление нулевого терминатора

Эта строка помещает 0 в байт, следующий за массивом.

Если вы определяете массив размером [x], у вас есть срезы от [0] до [x-1]. Если вы попытаетесь получить доступ к [x], вы измените другие элементы в стеке после массива.

Это называется переполнением буфера и является одной из наиболее распространенных причин сбоев программ.

Если вы хотите поместить 0 в конец массива, вам следует использовать

data[MAX_REC_STR_SIZE + TRUE_TRAILER_SIZE] = 0; // добавление нулевого терминатора

Вы также должны проверить, что вызовы strtok() действительно возвращают действительный указатель. Если входящая строка имеет неправильный формат и strtok() не может разделить строку, он вернет NULL. Если вы затем попытаетесь получить доступ к этому NULL-указателю, как если бы это был действительный массив, вы получите неприятный сбой.

,

Ах да. Моя ошибка, извините. Позвольте мне внести некоторые поправки в код, чтобы учесть все комментарии., @FShiwani

Я сделал это изменение. Ничего не изменил. Я добавил проверку, чтобы увидеть, возвращается ли NULL любой из двух команд strtok(). Код работает нормально. Я использую строку char opt = strtok(), не выполняя с ней никаких операций. Но даже проверка на NULL приводит к сбою программы., @FShiwani

Я распечатал strlen() данных, и это 12, так что это должно означать, что сюда входит 10-значная полезная нагрузка и $1. Не уверен, почему второй strtok() доставляет мне столько проблем. Технически, второй strtok() должен возвращать только 1 символ — '1'., @FShiwani

ОБНОВЛЕНИЕ: char opt = strtok(NULL, "$")[0]; на char *opt = strtok(NULL, "$"), похоже, как-то исправило это. Не совсем уверен, почему первое не сработало. Есть идеи?, @FShiwani

Может быть что-то странное в библиотеке C для ESP8266. Я не знаю, что она использует, но это точно не будет то же самое, что на вашем ПК..., @Majenko

Очень даже может быть. Это действительно странно. Печать как opt[0] тоже работает с моей новой строкой кода. По какой-то причине я просто не могу присвоить это так - char opt = strtok(NULL, "$")[0]; Спасибо за помощь. Я ценю это, @FShiwani