Как разбить входящую строку?
Я отправляю список позиций сервоприводов через последовательное соединение с arduino в следующем формате
1: 90 & Amp; 2: 80 & амп; 3: 180
, который будет анализироваться как:
servoId: Position & servoId: Position & servoId: Position
Как бы разбить эти значения и преобразовать их в целое число?
10 ответов
В противоположность другим ответам, я предпочел бы держаться подальше от String
по следующим причинам:
- использование динамической памяти (что может быстро привести к фрагментации кучи и исчерпанию памяти )
- довольно медленно из-за операторов построения /уничтожения /назначения
Во встроенной среде, такой как Arduino (даже для Mega с большим количеством SRAM), я бы предпочел использовать стандартные функции C :
-
strchr ()
: поиск символа в строке C (т.е.char *
) -
strtok ()
: разбивает строку C на подстроки на основе символа разделителя -
atoi ()
: преобразует строку C в кодint
Это приведет к следующему образцу кода:
//Рассчитать на основе максимального размера ввода, ожидаемого для одной команды
#define INPUT_SIZE 30
...
//Получить следующую команду из Serial (добавить 1 для окончательного 0)
char input [INPUT_SIZE + 1];
размер байта = Serial.readBytes (вход, INPUT_SIZE);
//Добавить окончательный 0 для завершения строки C
input [size] = 0;
//Прочитайте каждую пару команд
char * command = strtok (input, "&");
while (command! = 0)
{
//Разделить команду на два значения
char * separator = strchr (команда, ':');
if (разделитель! = 0)
{
//Собственно разделим строку на 2: заменим ':' на 0
* separator = 0;
int servoId = atoi (команда);
++ сепаратор;
int position = atoi (разделитель);
//Делаем что-то с сервоприводом и позицией
}
//Найти следующую команду в строке ввода
command = strtok (0, "&");
}
Преимущество в том, что динамическое распределение памяти не происходит; вы можете даже объявить input
как локальную переменную внутри функции, которая будет читать команды и выполнять их; как только функция возвращается, размер, занимаемый input
(в стеке), восстанавливается.
Эта функция может использоваться для разделения строки на части на основе разделительного символа.
Строка xval = getValue (myString, ':', 0);
String yval = getValue (myString, ':', 1);
Serial.println ("Y:" + yval);
Serial.print ("X:" + xval);
Преобразовать строку в int
int xvalue = stringToNumber (xval);
int yvalue = stringToNumber (yval);
Этот кусок кода принимает строку и отделяет ее на основе заданного символа и возвращает Элемент между разделительным символом
String getValue (данные String, разделитель символов, индекс int)
{
int found = 0;
int strIndex [] = {0, -1};
int maxIndex = data.length () - 1;
для (int i = 0; i <= maxIndex & <= lt; = index; i ++) {
if (data.charAt (i) == separator || i == maxIndex) {
найдено ++;
strIndex [0] = strIndex [1] + 1;
strIndex [1] = (i == maxIndex)? i + 1: i;
}
}
return found> индекс ? data.substring (strIndex [0], strIndex [1]): "";
}
Вы можете сделать что-то вроде следующего, но, пожалуйста, учтите несколько вещей:
Если вы используете readStringUntil ()
, он будет ждать, пока он не получит символ или таймауты. Таким образом, с вашей текущей строкой последняя позиция будет длиться немного дольше, так как она должна ждать. Вы можете добавить конечный &
, чтобы избежать этого тайм-аута. Вы можете легко проверить это поведение на своем мониторе, попробуйте отправить строку с дополнительным &
и вы увидите такую задержку ожидания.
Вам действительно не нужен индекс сервомашинки, вы можете просто отправить свою строку позиций и получить индекс сервопривода по позиции значения в строке, например: 90 & 80 & 180 &
, Если вы используете индекс сервопривода, возможно, вы хотите его проверить (преобразовать в int
), а затем сопоставить индекс цикла i), чтобы убедиться, что с вашим сообщением ничего не получилось.
Вы должны проверить, что возвращаемая строка из readStringUntil
не пуста. Если тайм-ауты функции не получили достаточного количества данных, и поэтому любая попытка извлечь ваши значения int
приведет к появлению странных результатов.
void setup () {
Serial.begin (9600);
}
void loop () {
для (int i = 1; i <= 3; i ++) {
String servo = Serial.readStringUntil (':');
if (servo! = "") {
//здесь вы можете проверить номер сервопривода
String pos = Serial.readStringUntil ('&');
int int_pos = pos.toInt ();
Serial.println ( "Позы");
Serial.println (int_pos);
}
}
}
Вы можете использовать Stream.readStringUntil (terminator)
прохождение другого терминатора для каждой части .
В каждой части вы вызываете String.toInt
См. пример: https://github.com/BenTommyE/Arduino_getStringPartByNr
//разделение строки и возвращение раздела nr index split разделителем
String getStringPartByNr (данные String, разделитель символов, индекс int) {
int stringData = 0; //переменная для подсчета части данных nr
String dataPart = ""; //переменная для удаления возвращаемого текста
for (int i = 0; i <data.length () - 1; i ++) {//Прогулка по тексту по одной букве за раз
if (data [i] == separator) {
//Подсчитайте количество символов разделителя в тексте
StringData ++;
} else if (stringData == index) {
//получить текст, когда разделитель является rignt one
dataPart.concat (данные [I]);
} else if (stringData> index) {
//возвращаем текст и останавливаем, если появляется следующий разделитель - чтобы сохранить время CPU
return dataPart;
ломать;
}
}
//вернуть текст, если это последняя часть
return dataPart;
}
String getValue (данные String, разделитель символов, индекс int)
{
int maxIndex = data.length () - 1;
int j = 0;
Строка chunkVal = "";
для (int i = 0; i <= maxIndex & j <= index; i ++)
{
chunkVal.concat (данные [I]);
if (data [i] == separator)
{
j ++;
если (j> индекс)
{
chunkVal.trim ();
return chunkVal;
}
chunkVal = "";
}
else if ((i = maxIndex) & & (j <index)) {
chunkVal = "";
return chunkVal;
}
}
}
Самое простое решение - использовать 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 (Р ( "п ="));
Serial.println (п);
Serial.print (Р ( "ID1 ="));
Serial.print (ID1);
Serial.print (F (", pos1 ="));
Serial.println (Pos1);
Serial.print (Р ( "ID2 ="));
Serial.print (ID2);
Serial.print (F (", pos2 ="));
Serial.println (pos2);
Serial.print (Р ( "id3 ="));
Serial.print (id3);
Serial.print (F (", pos3 ="));
Serial.println (POS3);
Это даст следующий результат:
п = 6
id1 = 1, pos1 = 90
id2 = 2, pos2 = 80
id3 = 3, pos3 = 180
Ура!
char str [] = "1: 90 & 2: 80 & 3: 180"; //тестовый пример последовательного ввода из сервопривода
int servoId;
int позиция;
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
//Параметр для получения команды Serial (добавить 1 для окончательного 0)
char input [INPUT_SIZE + 1];
void setup () {
mySerial.begin (9600);
}
void loop () {
//Нам нужен этот счетчик для имитации Serial.readBytes, которого нет в SoftwareSerial
int key = 0;
//Начать прием команды из Serial
while (mySerial.available ()) {
задержки (3); //Задержка для заполнения буфера, код становится неустойчивым на Attiny85 без этого по какой-либо причине
//Не читайте больше символов, чем определено
if (ключ <INPUT_SIZE & & mySerial.available ()) {
input [key] = mySerial.read ();
ключ + = 1;
}
}
if (ключ> 0) {
//Добавить окончательный 0 для завершения строки C
input [key] = 0;
//Прочитайте каждую пару команд
char * command = strtok (input, "&");
while (command! = 0)
{
//Разделить команду на два значения
char * separator = strchr (команда, ':');
if (разделитель! = 0)
{
//Собственно разделим строку на 2: заменим ':' на 0
* separator = 0;
int servoId = atoi (команда);
++ сепаратор;
int position = atoi (разделитель);
}
//Найти следующую команду в строке ввода
command = strtok (0, "&");
}
}
}
Схемы Attiny85 для номеров контактов
Эскиз компилируется в:
В Sketch используется 2244 байт (27%) пространства для хранения программ. Максимум 8192 байта.
Глобальные переменные используют 161 байт (31%) динамической памяти, оставляя 351 байт для локальных переменных. Максимум - 512 байт.
Таким образом, для остальной части кода достаточно места и памяти.
Вот Arduino метод разделить строку как ответ на вопрос « Как разбить строку в подстроке? », объявленную как дубликат данного вопроса.
Цель решения - проанализировать ряд позиций GPS , зарегистрированных в файле SD-карты . Вместо того, чтобы иметь строку, полученную из Serial
, строка читается из файла.
Функция StringSplit ()
анализирует строку sLine = "1.12345,4.56789, привет"
до 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;
делать {
//Поиск разделителя с помощью 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);
//распечатать извлеченные параметры
для (i = 0; i <iCount; i ++) {
Serial.print (sParams [I]);
}
Serial.println ( "");
}