Как разделить входящую строку?
Я отправляю список позиций сервоприводов через последовательное соединение на Arduino в следующем формате
1:90&2:80&3:180
Что будет проанализировано как:
servoId : Позиция & servoId : Позиция & servoId: позиция
Как мне разделить эти значения и преобразовать их в целое число?
@ValrikRobot, 👍71
Обсуждение12 ответов
Лучший ответ:
В отличие от других ответов, я бы предпочел держаться подальше от String
по следующим причинам:
- использование динамической памяти (что может быстро привести к фрагментации кучи и исчерпанию памяти)
- довольно медленный из-за операторов построения/уничтожения/присваивания
Во встроенной среде, такой как Arduino (даже для Mega с большим объемом SRAM), я бы предпочел использовать стандартные функции C:
strchr()
: поиск символа в строке C ( т.е.char *
)strtok()
: разбивает строку C на подстроки на основе символ-разделительatoi()
: преобразует строку C винтервал
Это приведет к следующему примеру кода:
// Расчет на основе максимального размера ввода, ожидаемого для одной команды
#define INPUT_SIZE 30
...
// Получить следующую команду из Serial (добавьте 1 для конечного 0)
char input[INPUT_SIZE + 1];
byte size = Serial.readBytes(input, INPUT_SIZE);
// Добавляем последний 0 в конце строки C
input[size] = 0;
// Чтение каждой пары команд
char* command = strtok(input, "&");
while (command != 0)
{
// Разделить команду на два значения
char* separator = strchr(command, ':');
if (separator != 0)
{
// На самом деле разделить строку на 2: заменить ':' на 0
*separator = 0;
int servoId = atoi(command);
++separator;
int position = atoi(separator);
// Делаем что-то с servoId и позицией
}
// Находим следующую команду во входной строке
command = strtok(0, "&");
}
Преимущество здесь в том, что не происходит динамического выделения памяти; вы даже можете объявить input
как локальную переменную внутри функции, которая будет читать команды и выполнять их; как только функция возвращается, размер, занимаемый input
(в стеке), восстанавливается.
Не думал о проблеме с памятью. отлично., @ValrikRobot
Отличный. Мой ответ был очень основан на «arduino» и использовал типичные функции arduino SDK, к которым новый пользователь мог бы быть более привычным, но этот ответ — это то, что следует сделать для «производственных» систем. В общем, постарайтесь избежать динамического выделения памяти во встраиваемых системах., @drodri
Вы можете использовать Stream.readStringUntil(terminator)
, передавая разные разделители для каждой части. .
В каждой части вы затем вызываете String.toInt
Вы можете сделать что-то вроде следующего, но примите во внимание несколько моментов:
Если вы используете readStringUntil()
, он будет ждать, пока не получит символ или время ожидания. Таким образом, с вашей текущей строкой последняя позиция будет длиться немного дольше, так как она должна ждать. Вы можете добавить завершающий &
, чтобы избежать этого тайм-аута. Вы можете легко проверить это поведение на своем мониторе, попробуйте отправить строку с дополнительным &
и без него, и вы увидите такую задержку времени ожидания.
На самом деле вам не нужен индекс сервопривода, вы можете просто отправить свою строку позиций и получить индекс сервопривода по позиции значения в строке, например: 90&80&180&
. Если вы используете сервоиндекс, возможно, вы захотите проверить его (преобразовать в int
, а затем сопоставить индекс цикла i), чтобы убедиться, что с вашим сообщением все в порядке.
Вы должны убедиться, что строка, возвращаемая из readStringUntil
, не пуста. Если время ожидания функции истекло, вы не получили достаточно данных, и поэтому любая попытка извлечь ваши значения int
приведет к странным результатам.
void setup() {
Serial.begin(9600);
}
void loop() {
for(int i=1; i<=3; i++) {
String servo = Serial.readStringUntil(':');
if(servo != ""){
//здесь вы можете проверить номер сервопривода
String pos = Serial.readStringUntil('&');
int int_pos=pos.toInt();
Serial.println("Pos");
Serial.println(int_pos);
}
}
}
Это кажется очень хорошим решением, спасибо. Пример прекрасно все объясняет, @ValrikRobot
Что, если бы у нас было неопределенное количество входов для сервоприводов? в моем примере их было 3. А что, если иногда их было больше или меньше. Можете ли вы предложить какое-либо предложение для обработки такого сценария, @ValrikRobot
Конечно: есть две возможности. 1. Отправьте сначала количество сервоприводов: 3:val1&val2&val3&, прочитайте этот номер перед запуском цикла. 2. Используйте другой терминатор, чтобы указать, что у вас больше нет сервоприводов, повторяйте, пока не найдете его: например, val1&val2&val3&#., @drodri
Рад, что это решение помогло вам, @ValrikRobot, не могли бы вы подтвердить ответ, если он был полезен?, @drodri
или вы можете просто удалить for, и тогда код будет работать каждый раз, когда вы отправляете команду., @Lesto
Эту функцию можно использовать для разделения строки на части в зависимости от символа-разделителя.
String xval = getValue(myString, ':', 0);
String yval = getValue(myString, ':', 1);
Serial.println("Y:" + yval);
Serial.print("X:" + xval);
Преобразовать строку в целое число
int xvalue = xvalue.toInt(xval);
int yvalue = yvalue.toInt(yval);
Этот фрагмент кода берет строку, разделяет ее на основе заданного символа и возвращает Элемент между разделительным символом
String getValue(String data, char separator, int index)
{
int found = 0;
int strIndex[] = { 0, -1 };
int maxIndex = data.length() - 1;
for (int i = 0; i <= maxIndex && found <= index; i++) {
if (data.charAt(i) == separator || i == maxIndex) {
found++;
strIndex[0] = strIndex[1] + 1;
strIndex[1] = (i == maxIndex) ? i+1 : i;
}
}
return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}
String getValue(String data, char separator, int index)
{
int maxIndex = data.length() - 1;
int j = 0;
String chunkVal = "";
for (int i = 0; i <= maxIndex && j <= index; i++)
{
chunkVal.concat(data[i]);
if (data[i] == separator)
{
j++;
if (j > index)
{
chunkVal.trim();
return chunkVal;
}
chunkVal = "";
}
else if ((i == maxIndex) && (j < index)) {
chunkVal = "";
return chunkVal;
}
}
}
Это было хорошо. Это было идеально!!!, @Arthur Alunts
См. пример: https://github.com/BenTommyE/Arduino_getStringPartByNr
// разбивает строку и возвращает индекс номера части, разделенный разделителем
String getStringPartByNr(String data, char separator, int index) {
int stringData = 0; //переменная для подсчета части данных nr
String dataPart = ""; //переменная для удаления возвращаемого текста
for(int i = 0; i<data.length()-1; i++) { //Проходим по тексту по одной букве за раз
if(data[i]==separator) {
//Подсчитываем, сколько раз символ-разделитель появляется в тексте
stringData++;
} else if(stringData==index) {
//получаем текст, если разделитель правильный
dataPart.concat(data[i]);
} else if(stringData>index) {
// возвращаем текст и останавливаемся, если появляется следующий разделитель - для экономии процессорного времени
return dataPart;
break;
}
}
// возвращаем текст, если это последняя часть
return dataPart;
}
Самое простое решение — использовать sscanf().
int id1, id2, id3;
int pos1, pos2, pos3;
char* buf = "1:90&2:80&3:180";
int n = sscanf(buf, "%d:%d&%d:%d&%d:%d", &id1, &pos1, &id2, &pos2, &id3, &pos3);
Serial.print(F("n="));
Serial.println(n);
Serial.print(F("id1="));
Serial.print(id1);
Serial.print(F(", pos1="));
Serial.println(pos1);
Serial.print(F("id2="));
Serial.print(id2);
Serial.print(F(", pos2="));
Serial.println(pos2);
Serial.print(F("id3="));
Serial.print(id3);
Serial.print(F(", pos3="));
Serial.println(pos3);
Это даст следующий результат:
n=6
id1=1, pos1=90
id2=2, pos2=80
id3=3, pos3=180
Ура!
Он не работает для serial.read()... есть идеи, почему? Я получаю следующую ошибку: неверное преобразование из 'int' в 'char*' [-fpermissive]
, @Alvaro
Вот метод Arduino для разделения строки в качестве ответа на вопрос "Как разделить строку на подстроку?" объявлен как дубликат текущего вопроса.
Целью решения является анализ ряда положений GPS, зарегистрированных в файле SD-карты. Вместо получения строки из Serial
строка считывается из файла.
Функция StringSplit()
анализирует строку sLine = "1.12345,4.56789,hello"
до 3 строк sParams[0]="1.12345"
, sParams[1]="4.56789"
& sParams[2]="привет"
.
String sInput
: строки ввода для анализа,char cDelim
: символ-разделитель между параметрами,String sParams[]
: выходной массив параметров,int iMaxParams
: максимальное количество параметров,- Вывод
int
: количество проанализированных параметров,
Эта функция основана на String::indexOf()
и String::substring()
:
int StringSplit(String sInput, char cDelim, String sParams[], int iMaxParams)
{
int iParamCount = 0;
int iPosDelim, iPosStart = 0;
do {
// Поиск разделителя с помощью indexOf()
iPosDelim = sInput.indexOf(cDelim,iPosStart);
if (iPosDelim > (iPosStart+1)) {
// Добавление нового параметра с помощью substring()
sParams[iParamCount] = sInput.substring(iPosStart,iPosDelim-1);
iParamCount++;
// Проверка количества параметров
if (iParamCount >= iMaxParams) {
return (iParamCount);
}
iPosStart = iPosDelim + 1;
}
} while (iPosDelim >= 0);
if (iParamCount < iMaxParams) {
// Добавляем последний параметр в конец строки
sParams[iParamCount] = sInput.substring(iPosStart);
iParamCount++;
}
return (iParamCount);
}
И использование очень простое:
String sParams[3];
int iCount, i;
String sLine;
// чтение строки из файла
sLine = readLine();
// анализировать, только если существует
if (sLine.length() > 0) {
// разбираем строку
iCount = StringSplit(sLine,',',sParams,3);
// печатаем извлеченные параметры
for(i=0;i<iCount;i++) {
Serial.print(sParams[i]);
}
Serial.println("");
}
char str[] = "1:90&2:80&3:180"; // тестовый образец последовательного ввода от сервопривода
int servoId;
int position;
char* p = str;
while (sscanf(p, "%d:%d", &servoId, &position) == 2)
{
// обрабатываем servoId, позиция здесь
//
while (*p && *p++ != '&'); // к следующей паре id/pos
}
jfpoilpret предоставил отличный ответ для синтаксического анализа последовательной команды на Arduino. Однако у Attiny85 нет двунаправленного последовательного порта — необходимо использовать SoftwareSerial. Вот как вы портируете тот же код для Attiny85
#include <SoftwareSerial.h>
// Расчет на основе максимального размера ввода, ожидаемого для одной команды
#define INPUT_SIZE 30
// Инициализировать SoftwareSerial
SoftwareSerial mySerial(3, 4); // RX=PB3, TX=PB4
// Параметр для получения последовательной команды (добавьте 1 для конечного 0)
char input[INPUT_SIZE + 1];
void setup() {
mySerial.begin(9600);
}
void loop() {
// Нам нужен этот счетчик для имитации Serial.readBytes, которого нет в SoftwareSerial
int key = 0;
// начинаем получать команду от Serial
while (mySerial.available()) {
delay(3); // Задержка для заполнения буфера, без этого код на Attiny85 почему-то становится нестабильным
// Не читать больше символов, чем определено
if (key < INPUT_SIZE && mySerial.available()) {
input[key] = mySerial.read();
key += 1;
}
}
if (key > 0) {
// Добавляем последний 0 в конце строки C
input[key] = 0;
// Чтение каждой пары команд
char* command = strtok(input, "&");
while (command != 0)
{
// Разделить команду на два значения
char* separator = strchr(command, ':');
if (separator != 0)
{
// На самом деле разбить строку на 2: заменить ':' на 0
*separator = 0;
int servoId = atoi(command);
++separator;
int position = atoi(separator);
}
// Находим следующую команду во входной строке
command = strtok(0, "&");
}
}
}
Схема Attiny85 для номеров контактов
Скетч компилируется в:
Sketch uses 2244 bytes (27%) of program storage space. Maximum is 8192 bytes.
Global variables use 161 bytes (31%) of dynamic memory, leaving 351 bytes for local variables. Maximum is 512 bytes.
Так что для остального кода достаточно места и памяти
Как читать из серийного номера на ATtiny85, на самом деле не является частью вопроса., @gre_gor
Извините за отклонение от вопроса, но сообщество и ресурсы, доступные для Attiny, намного меньше, чем для Arduino. Такие люди, как я, ищущие ответы, используют ключевое слово «Arduino» и иногда попадают в очень сложные ситуации, поскольку реализация кода Arduino на Attiny не всегда тривиальна. Пришлось преобразовать исходный код для работы на Attiny, проверил его работу и решил поделиться им., @goodevil
Этот сайт в формате вопросов и ответов. Ответы должны отвечать на вопрос. Ваш просто добавляет что-то, что не связано с ним., @gre_gor
void setup() {
Serial.begin(9600);
char str[] ="1:90&2:80";
char * pch;
pch = strtok(str,"&");
printf ("%s\n",pch);
pch = strtok(NULL,"&"); //pch=следующее значение
printf ("%s\n",pch);
}
void loop(){}
Это не ответ на ваш вопрос, но может быть кому-то полезен. Если ваша строка имеет определенный префикс, вы можете просто использовать startsWith
и substring
. Например
void loop ()
if(Serial.available()){
command = Serial.readStringUntil('\n');
Serial.println("Received command: " + command);
if(command.startsWith("height")) {
Serial.println(command.substring(7));
}
}
}
А затем отправьте height 10
, что позволит извлечь 10
.
- Arduino Serial.ReadString() проблема
- чтение нескольких целочисленных значений arduino mega на других arduino mega
- Serial1.ReadString() на arduino mega возвращает пустую строку
- Не удалось удалить символ T из передатчика Arduino
- Как вывести несколько переменных в строке?
- форматирование строк в Arduino для вывода
- Очень простая операция Arduino Uno Serial.readString()
- Arduino Преобразование std:string в String
у меня раб (arduino uno) отправляет строку через последовательный порт 30; 12.4; 1 и 1 основная (esp8266) строка получения я хочу, чтобы в мастере были отдельные данные, например 30 12,4 1 и сохранить на микро сд карту, @majid mahmoudi