Последовательная связь - проблема 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?
- Адаптер USB для UART на базе CH340G - Схемы распиновки
- Arduino UART (TX/RX) multidrop 1 master 50 slaves
Есть несколько «удобных» функций, например readBytesUntil. У них есть тайм-аут. Вы можете использовать их один раз, когда вы новичок. Но после этого вам придется двигаться дальше и помещать символы в буфер, как только он станет доступен. По одному символу за раз для каждой итерации цикла arduino. Проверьте, достаточно ли данных в буфере, затем обработайте данные в буфере. Когда вы ожидаете 16 байт, создайте буфер из 40 байт и не допускайте его переполнения. Ваш код опасен во многих отношениях., @Jot
printlnотправляет два дополнительных символа, которые будут мешать фиксированному размеру сообщения. Также у вас есть буфер на 13 символов, но вы устанавливаете ограничение наreadBytesUntilв 14 символов., @gre_gor@gre_gor Ах да. Этот REC_TRAILER_SIZE в readBytesUntil() должен быть TRUE_TRAILER_SIZE, поскольку сам & не предназначен для чтения., @FShiwani
@Jot Да. Я заметил, что многие люди в других темах вручную считывают данные, и мне стало интересно, почему не так много людей используют эту функцию readBytesUntil()., @FShiwani