Ви не увійшли.
Дійсно, динамічно виділяти пам'ять під масив можна
Це не динамічне виділення в сенсі з купи, як malloc() або оператор new. Тут памʼять виділяється на стеку, як для звичайної локальної змінної. Компілятору нема різниці, чи віднімати від вказівника вершини стека константу, чи значення якого-небудь регістра.
Те ж саме можна зробити "класичним" методом:
char *array = alloca(strlen(str) + 1);
А якщо не вистачить пам'яті при роботі..
Проблема не стільки в тому, що памʼяті може не вистачити, а в тому, що нема можливості це перевірити навіть в рантаймі. При динамічному виділенні є можливіть перевірити успішність результата (і грамотно написаний код має це перевіряти). Хоча в ардуіно фреймворку це теж не дає 100% гарантії, бо хто знає, скільки байтів ще напхає в стек який-небуть обробник переривання умовної бібліотеки Wire. Може і для ардуіни є якісь реалізації runtime stack guard, але мені не зустрічались.
не умею программировать Ардуино, пользуюсь готовыми скетчами,
возникла проблема как объединить 6 в один,может ли кто помочь?
Ви хочете розібратись і навчитись, чи просто отримати готовий результат?
Якщо перше, то почніть з простої програми, поступово додаючи потрібні функції. Почитайте туторіали, подивіться навчальні відео, їх зараз мільйони. Якісь конкретні питання - пишіть, що саме незрозуміло.
Якщо друге, то шукайте виконавця, який зробить вам послугу за відповідну оплату.
Працює, не димить, не іскрить - це вже добре
Але перевірку контрольної суми я б таки додав. Хто зна, що там з UART'у може прийти. Так хоч якась валідація.
sprintf(fullSpeed, sizeof(fullSpeed), "%03d", speedKmh);
Звісно ж, тут snprintf. То я при цитуванні забув поправити.
snprintf(fullSpeed, sizeof(fullSpeed)-1
Це чому? snprintf в avr-libc нічим не відрізняється від інших реалізацій libc:
no more than n characters (including the trailing NUL character) will be converted to s
String nmeaSentence = ""; // Переменная для хранения NMEA-строки ... nmeaSentence += c; // Добавить символ к строке NMEA ... char nmeaCStr[nmea.length() + 1]; nmea.toCharArray(nmeaCStr, nmea.length() + 1); ...
Навіщо зберігати символи в String, якщо потім їх копіювати в char[] ?
if (fieldIndex == 1) {printFormattedTime(token);token = nullptr;} // выводим время 1 if (fieldIndex == 2) {printFormattedValid(token);token = nullptr;} // выводим валидность координат 2 if (fieldIndex == 7) {printFormattedSpeed(token);token = nullptr;}// выводим скорость 7 if (fieldIndex == 9) {printFormattedDate(token);token = nullptr;} // выводим дату 9 token = commaPos + 1;
Навіщо присвоювати token = nullptr, якщо відразу ж присвоюється token = commaPos + 1?
Раз на то пішло, то хоча б
switch (fieldIndex) {
case 1: printFormattedTime(token); break;
case 2: printFormattedValid(token); break;
case 7: printFormattedSpeed(token); break;
case 9: printFormattedDate(token); break;
default: break;
}
token = commaPos + 1;
float Kmh = atof(rawKnot) * 1.852; int speedKmh = (int)Kmh;
Якщо вже так хочеться float, навіщо проміжна змінна і C-style кастинг? Такий же результат дасть
int speedKmh = atof(rawKnot) * 1.852;
А краще
int speedKmh = round(atof(rawKnot) * 1.8523);
char fullSpeed[5]; sprintf(fullSpeed, "%03d", speedKmh);
У разі невалідних вхідних даних може бути переповнення буфера. Хоча б
sprintf(fullSpeed, sizeof(fullSpeed), "%03d", speedKmh);
Хоча у разі невалідних вхідних даних переповнення буфера буде ще на стадії отримання строки з UART.
Кілобайти то такоє.. Там мабуть і периферія трохи незвична, і тактування..
Та периферія здерта з STM32. Тільки ядро RISC-V.
Мені китайські stc8 сподобались. 8 ніг, обв'язки немає. Програмуються трохи по наркоманськи, ага.
Цікаво. Цих не доводилось щупати.
Трохи грався з CH32V. Наче нічо, але в "продакшн" їх брати поки б не ризикував. Документація мізерна, є питання щодо надійності в довгостроковій перспективі. Ну і треба розуміти, що кілобайт на AVR8 і кілобайт на RV32 - це дещо різний кілобайт.
Для використання attiny повинна бути дуже поважна причина
Колись була: ціна. Зараз, із китайськими CH32 по "гривні за відро", це майже не актуально.
rtos нахвалюють, і навіть під atmega воно є.
Так правду кажуть, мультипоточність - для тих, хто не зрозумів конечні автомати
Це зручно, але кожній задачі потрібен стек, як мінімум щоб зберігати контекст. На Attiny13 ОЗУ 64 байта, а контекст регістрів - 32 байта. На Attiny15 взагалі ОЗУ нема
А є приклади реалізації, щоб не по уродськи ?
Мені поки що не зустрічались. А самому розробляти - часу на це немає, та і для однієї людини воно розтягнеться на роки. Воно ж одне тягне за собою інше.
Від існуючих ардуінівських бібліотек доведеться відмовитись одразу, а кому воно потрібне без бібліотек? Найбільш розповсюджені ще якось більш-менш реалістично портувати під нову парадигму, але ж тисячі їх. Ще й уніфікація HAL'а для різних архітектур.
На виході генерувати код на C? Сучасні компілятори не дуже підходять для embedded з обмеженими ресурсами. Наприклад, з LTO практично неможливо передбачити, скільки стеку потрібно тій чи іншій задачі. Інші налаштування оптимізації - все, приїхали. Потрібно розробляти свій компілятор з блекджеком і повіями, та ще й щоб підтримував зоопарк цільових архітектур. Невдячна це справа.
Ну, мабуть існують задачі, які простіше описати на декларативній мові.
Мені здається, навпаки, для більшості задач, які намагаються вирішувати за допомогою ардуіни (в тому числі і тут на форумі), декларативна парадигма так і проситься. Але то таке, філософія.
Але csv - навряд чи цей випадок
Це поки самі value не містять в собі comma, які можуть бути не тільки екрановані, а і в складі літералів у одинарних або подвійних лапках, наприклад.
Я правильно розумію, що якщо на мові yacc описати правила для формату email, то можна отримати regexp?
Не regexp, а код, який реалізує алгоритм, еквівалентний regexp. Тільки у yacc можливостей на порядок більше, він може генерувати парсер для будь-якої Тьюринг-повної мови.
але як дебажити помилки? Тестами хіба що.
Тестами помилки тільки виявляються, як і скрізь. Зміни вносяться в опис граматики. Мабуть є якісь опції і для дебага. Я далеко не експерт в парсер-генераторах, тільки краєм вуха бачив.
Якщо ваша проблема вирішується через регекспи - значить у вас 2 проблеми
регексп для перевірки коректності email це кілька рядків.
Ви про цей вираз? Так і формат адреси відповідно до RFC 822 непростий. До того ж, такі вирази не пишуться вручну, а генеруються з простіших частин. Ви ж не звинувачуєте компілятор C++, що він генерує машинний код, який людині складно розібрати? Регулярний вираз - той же "машинний код" для КА.
А кінцеві автомати якось пройшли мимо. Учили, але як від діаграми перейти до коду я так і не врюхав.
"Звичайна" програма з розгалуженнями і циклами - це теж КА (як мінімум).
Але суть запропонованої мною реалізації в тому, що дані частково парсяться по мірі надходження. UART працює повільно відносно процесора, тому виконання парсинга "розмазуються" в часі.
Якщо потрібно витягати тільки певні токени у певних типах повідомлень, то можна допиляти парсер, щоб він по закінченню просто випльовував готову структуру з float'ами, чи що там ще потрібно.
як це взагалі працює? на вході опис даних на виході код С?
Саме так. Але це має сенс для парсинга нерегулярних синтаксисів. Для регулярних, типу як в NEMA, достатньо КА.
Якби в arduino core була підтримка регулярних виразів, весь той КА можна було би описати однією строкою. Хіба що обчислення контрольної суми довелось би реалізовувати окремо.
Кінцевий автомат воно концептуально. але ж букв дофіга
і ніхто крім автора не розуміє як воно працює
Букв там дофіга бо написано для ардуіни на недо-C++. Для документації продакшн коду можна діаграму намалювати. Або взагалі взяти парсер-генератор (yacc чи bison) і не морочити голову.
Та ось вам іще одна наївна реалізація
class nmea
{
void (nmea::*context_)(char);
uint8_t buf_idx_;
uint8_t tok_idx_;
uint8_t tok_start_;
uint8_t checksum_;
bool ready_;
char buf_[80];
uint8_t tokens_[80];
void update_checksum(uint8_t c)
{
checksum_ ^= c;
}
void skip()
{
context_ = &nmea::ctx_skip;
}
void reset()
{
context_ = &nmea::ctx_reset;
}
void append_buf(char c)
{
if (buf_idx_ >= sizeof(buf_)/sizeof(*buf_)) {
skip();
return;
}
buf_[buf_idx_++] = c;
}
void append_tok()
{
if (tok_idx_ < sizeof(tokens_)/sizeof(*tokens_)) {
tokens_[tok_idx_++] = tok_start_;
}
}
void ctx_reset(char c)
{
ready_ = false;
if (c != '$') {
return;
}
context_ = &nmea::ctx_tokens;
buf_idx_ = 0;
tok_idx_ = 0;
tok_start_ = 0;
checksum_ = 0;
}
void ctx_skip(char c)
{
if (c == '\n') {
context_ = &nmea::ctx_reset;
}
}
void ctx_tokens(char c)
{
if (c == '*') {
context_ = &nmea::ctx_checksum;
append_tok();
append_buf('\0');
tok_start_ = buf_idx_;
return;
}
if (c == '\n') {
reset();
return;
}
update_checksum(c);
if (c == ',') {
append_tok();
append_buf('\0');
tok_start_ = buf_idx_;
return;
}
append_buf(c);
}
void ctx_checksum(char c)
{
if (c == '\r') {
if (buf_idx_ - tok_start_ != 2) {
skip();
}
return;
}
if (c == '\n') {
if (buf_idx_ - tok_start_ != 2) {
reset();
return;
}
append_buf('\0');
char *endptr;
uint8_t checksum = strtoul(buf_ + tok_start_, &endptr, 16);
if (*endptr == '\0' && checksum == checksum_) {
ready_ = true;
}
reset();
return;
}
append_buf(c);
}
public:
nmea() : context_ { &nmea::ctx_reset } {};
bool parse(char c)
{
(this->*context_)(c);
return ready_;
}
const char *token(uint8_t idx) const
{
return (idx < tok_idx_) ? (buf_ + tokens_[idx]) : "";
}
}
Детально на баги не тестив, на даних з оригінального поста наче працює.
Приклад використання:
nmea nmea;
void loop()
{
for (int c; (c = SoftwareSerial.read()) != -1;) {
if (!nmea.parse(c)) {
continue;
}
const char *type = nmea.token(0);
Serial.print(F("Parsed: "));
Serial.print(type);
if (strcmp(type, "GPGGA") == 0) {
Serial.print(F(" time: "));
Serial.print(nmea.token(1));
Serial.print(F("; location: "));
Serial.print(nmea.token(2));
Serial.print(nmea.token(3));
Serial.print(nmea.token(4));
Serial.print(nmea.token(5));
}
Serial.println();
}
}
Оффтоп: баг з бекслешами на цьому форумі колись пофіксять?
отримую таке
Які поля з яких типів повідомлень вам потрібно витягти?
Або візьміть приклад з man strtok і переробіть під себе.
strtok() витягає тільки непусті токени. NMEA може містити пусті.
Взагалі, ефективна реалізація парсера залежить від того, чи потрібно парсити якість конкретні типи повідомлень, які токени з них потрібні, в якому форматі вони мають бути для зручності подальшої обробки і т.д.
Це класична задача для реалізації у вигляді конечного автомата.
Проблема в тому, що серія ком ( ,,, ) функцією sscanf обробляється не корректно ...
![]()
![]()
sscanf() - невдалий вибір для парсингу такого синтаксиса. Його тут можна застосувати для парсингу окремого токена, але для рядка в цілому він погано підходить.
Токени розділені комами, так шукайте символ коми. Або за допомогою strchr(), або своєю реалізацією, що не набагато складніше. Потім парсіть вміст між комами.
Навіщо копіювати кожний токен в окремий буфер? Він же і так уже присутній в памʼяті. Якщо для парсинга самого токена він має закінчуватись нульовим символом, заміняйте кому на нульовий символ.
Якщо не помиляюсь, максимальна довжина рядка NMEA - 82 символи. Тобто буфера довжиною 83 символи буде достатньо. Чому 88?
Взагалі, ви спочатку накопичуєте всю строку в буфер, потім намагаєтесь її парсити. Звісно, можна і так. Але, мені здається, простіше парсити дані по мірі надходження.
Короче. По живленню пролазить імпульсна завада.
Ага, і мабуть ще струмовий шунт між вхідним і вихідним мінусами.
Якщо на платі ардуіни нормальна AMS1117, то зовнішній регулятор викидаєте зовсім, на Vin подаєте +12В з блока живлення. З конденсаторами з боку ардуіни. Периферію живете з виходу +5В. Для живлення дуйчика див. попередні рекомендації.