Последовательная связь - проблема 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 не работает?
@FShiwani, 👍1
Обсуждение1 ответ
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
- В чем разница между библиотеками Software Serial? Какая из них совместима с Arduino Nano?
- Как использовать Serial.setTimeout()
- Отправить структуру через Serial
- Может ли Arduino Leonardo одновременно использовать USB-порт и последовательные контакты RX TX?
- Последовательная связь между несколькими устройствами (или ардуино)
- как отправить данные в Adafruit Bluefruit Feather (32u4) через Bluetooth?
- Arduino UART (TX/RX) multidrop 1 master 50 slaves
- Когда дело доходит до связи UART-RS485, в чем разница между модулем "MAX485" и модулем "HW-0519"?
Есть несколько «удобных» функций, например readBytesUntil. У них есть тайм-аут. Вы можете использовать их один раз, когда вы новичок. Но после этого вам придется двигаться дальше и помещать символы в буфер, как только он станет доступен. По одному символу за раз для каждой итерации цикла arduino. Проверьте, достаточно ли данных в буфере, затем обработайте данные в буфере. Когда вы ожидаете 16 байт, создайте буфер из 40 байт и не допускайте его переполнения. Ваш код опасен во многих отношениях., @Jot
println
отправляет два дополнительных символа, которые будут мешать фиксированному размеру сообщения. Также у вас есть буфер на 13 символов, но вы устанавливаете ограничение наreadBytesUntil
в 14 символов., @gre_gor@gre_gor Ах да. Этот REC_TRAILER_SIZE в readBytesUntil() должен быть TRUE_TRAILER_SIZE, поскольку сам & не предназначен для чтения., @FShiwani
@Jot Да. Я заметил, что многие люди в других темах вручную считывают данные, и мне стало интересно, почему не так много людей используют эту функцию readBytesUntil()., @FShiwani