SoftwareSerial не будет читать всю напечатанную строку при вызове readString()

У меня есть Arduino UNO и ESP32, которые должны взаимодействовать друг с другом с помощью SoftwareSerial. Проблема, с которой я сталкиваюсь, заключается в том, что когда я вызываю readString и распечатываю ее с помощью Serial, она не дает мне всего, что было распечатано с моего ESP32.

У меня Arduino RX Pin подключен к 4, а TX подключен к 5 У меня есть контакт ESP32 RX2, подключенный к 16, и контакт TX2, подключенный к 17, а GND подключен непосредственно к GND Arduino. Я не использую никаких резисторов, только 5 В чистой мощности

Код Arduino UNO:

// C-стандартная библиотека
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <SoftwareSerial.h>

#define RX_PIN 4
#define TX_PIN 5

SoftwareSerial ardSerial(RX_PIN, TX_PIN);

void setup(void) {
  // Получаем доступ к сети
  pinMode(RX_PIN, INPUT);
  pinMode(TX_PIN, OUTPUT);
  Serial.begin(9600);
  while(!Serial) {}
  ardSerial.begin(9600);
  while(!ardSerial) {}
}

void loop(void) {
  while (ardSerial.available() > 0) {
    String payload = ardSerial.readString();
    Serial.println(payload);
  }
  delay(1000);

}

Код ESP32

#include <stdlib.h>
#include <string.h>

#include <SoftwareSerial.h>

SoftwareSerial ardSerial;

void setup(void) {
  // Начинаем соединения
  Serial.begin(115200);
  while (!Serial) {}

  ardSerial.begin(9600, SWSERIAL_8N1, RX2_PIN, TX2_PIN);
  while (!ardSerial) {}
  return;
}

void loop(void) {
  {
    String spotData = "Huge JSON File that needs transferred";
    ardSerial.print(spotData);
  }

  delay(5000);
}

Ожидаемый результат:

  • Возможность получить каждый бит распечатанного файла JSON

Фактический результат:

  • Получить только половину файла JSON

, 👍0

Обсуждение

Насколько велик файл JSON? Сколько байт удалось извлечь Uno?, @Edgar Bonet

@EdgarBonet, это огромный файл JSON, не могу точно сказать, сколько байтов содержит JSON, поскольку он каждый раз случайный. Я беру его из Spotify API, если это вам что-то говорит. Он извлекает из начала JSON случайную часть ближе к середине, затем просто прерывает связь, пока JSON не будет отправлен снова., @Primitive

он помещается в оперативную память Uno?, @Juraj


2 ответа


Лучший ответ:

0

Я вижу 2 проблемы с вашим кодом:

  • Вы написали в комментариях, что файл JSON огромен. У Uno всего 2 КБ ОЗУ, а вы пытаетесь сразу весь файл привести в ОЗУ. Такой файл JSON от Spotify легко превысит 2 КБ (на самом деле у вас даже меньше, потому что фреймворк Arduino и SoftwareSerial также используют часть оперативной памяти). Таким образом, вы просто не сможете одновременно обрабатывать весь файл с помощью Uno.

  • Вы используете readString(). Эта функция, безусловно, проста, но также имеет некоторые подводные камни. Он использует класс String, использующий динамическое выделение памяти. Он выделяет больший массив символов в ОЗУ, если текущий массив недостаточно велик. Это означает, что вам потребуется до 2 версий вашей строки, помещающихся в ОЗУ при получении, оставляя только около 1 КБ. Также он будет считываться из интерфейса SoftwareSerial, пока не истечет время ожидания (я думаю, по умолчанию 1 с). Для создания надежной связи между двумя устройствами вы должны читать до тех пор, пока не дойдете до специального символа, например, с помощью readStringUntil(). Этот специальный символ часто представляет собой символ новой строки '\n'.

Возможное решение: Сначала следует поговорить о проблеме ограниченного объема оперативной памяти. Как указано выше, вы не можете обрабатывать весь файл сразу. Вы должны обрабатывать это кусками. Что бы вы ни хотели сделать с данными в конечном итоге, Uno потребуется некоторое время для обработки текущего фрагмента данных. Поэтому вам нужно приостановить передачу данных на ESP, пока Uno не будет готов принять больше. Тут можно пойти двумя путями:

  • Отправлять данные блоками предопределенного размера (достаточно маленькими для того, чтобы Uno мог сразу хранить их в своей ОЗУ) и ждать предопределенное время между каждым блоком, которого достаточно для того, чтобы Uno обработала все данные, которые он мог получить. тот кусок. Это проще всего запрограммировать, но обязательно потребуется больше времени на выполнение.
  • Позвольте Uno сигнализировать ESP о том, что его буфер заполнен, например, отправив специальный символ в ESP. Затем ESP должен прекратить отправку, пока не получит разрешение от Uno на продолжение передачи. Чтобы отправить без потери данных, Uno, вероятно, также должен отправить количество байтов, полученных в этом фрагменте. Таким образом, ESP точно знает, где в строке данных находится Uno в данный момент. Это немного сложнее, но все же достаточно просто и надежно.

Для обработки отправки фрагментами я бы использовал метод write(const uint8_t *buffer, size_t size) класса SoftwareSerial (на самом деле эта функция исходит из класса Print, от которого наследуется SoftwareSerial). Буфер — это указатель на первый элемент текущего фрагмента в массиве char с данными. Вы можете получить массив char с помощью spotData.c_str(), и вы можете получить указатель на n-й его элемент с помощью &(spotData.c_str ()[n]). Второй параметр — размер чанка в байтах. Он должен быть достаточно маленьким, чтобы данные поместились в ОЗУ UNO вместе со всей оперативной памятью, необходимой для обработки данных.

На Uno я бы читал побайтно. Что-то вроде этого:

char data[BUFFER_SIZE]="";
int pos=0;

void loop(){
    if(ardSerial.available()){
        data[pos] = ardSerial.read();
        if(pos == BUFFER_SIZE - 1){
            // Обработка блока данных
            pos = 0; // сброс позиции для повторного использования буфера
        } else {
            pos++;
        }
    }
}

Я пропустил некоторые части, чтобы продемонстрировать принцип. Также вам необходимо самостоятельно определить BUFFER_SIZE в зависимости от доступной оперативной памяти.

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


Помимо отправки и получения вам нужно будет подумать о том, как обрабатывать блоки данных. Это во многом зависит от того, что именно вы хотите делать с этими данными. Просто отправить их через Serial очень просто. Интерпретировать данные фрагментами сложнее.

,

1

Есть два способа реализовать синтаксический анализатор для таких языков, как JSON или XML:

  1. Пусть синтаксический анализатор проглотит весь документ сразу и создаст представление одних и тех же данных в памяти.

  2. Пусть синтаксический анализатор возвращает управление вызывающему объекту для каждого "события", которое он обнаруживает, где «событие» обычно является примитивным элементом данных, или начало или конец контейнера.

Парсеры первого типа намного проще в использовании, но это может быть дорого в использовании ОЗУ, если вы хотите разобрать большой документ. Второй синтаксические анализаторы сложнее в использовании, но они способны анализировать огромные документы, использующие минимальный объем памяти.

На Arduino обычно используется ArduinoJson, который представляет собой синтаксический анализатор первый вид, не идеальный для парсинга огромных документов на малой памяти. А быстрый поиск «управляемого событиями синтаксического анализатора C++ JSON» привел меня к JsonReader. я не пробовал, и не знаю, будет ли это работать на Arduino, но это показывает, что по крайней мере такие парсеры существуют для JSON в C++. Вы можете продолжить поиск по этому пути.

Однако, прежде чем вы углубитесь в это, я предлагаю вам дать ArduinoJson второй шанс. У ArduinoJson есть несколько приемов, которые могут помочь справиться с с большими документами:

  • Вы можете попросить его фильтровать данные по мере их чтения, чтобы только запоминайте то, что вас действительно интересует.

  • Вы можете десериализовать фрагменты и использовать данные, извлеченные из одного фрагмент за раз.

Оба метода объясняются в руководстве Как десериализовать очень большой документ?.

Кроме того, давайте не будем забывать об очевидном: вы не должны пытаться сохранить необработанный документ JSON в памяти. Вместо этого вы должны дать ArduinoJson ссылку на ваш поток (а именно ardSerial) и позвольте ему напрямую тянуть байты при разборе.

,