#276 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-10 22:39:21

jokeer пише:

Дійсно, динамічно виділяти пам'ять під масив можна wink

Це не динамічне виділення в сенсі з купи, як malloc() або оператор new. Тут памʼять виділяється на стеку, як для звичайної локальної змінної. Компілятору нема різниці, чи віднімати від вказівника вершини стека константу, чи значення якого-небудь регістра.
Те ж саме можна зробити "класичним" методом:

char *array = alloca(strlen(str) + 1);
jokeer пише:

А якщо не вистачить пам'яті при роботі..

Проблема не стільки в тому, що памʼяті може не вистачити, а в тому, що нема можливості це перевірити навіть в рантаймі. При динамічному виділенні є можливіть перевірити успішність результата (і грамотно написаний код має це перевіряти). Хоча в ардуіно фреймворку це теж не дає 100% гарантії, бо хто знає, скільки байтів ще напхає в стек який-небуть обробник переривання умовної бібліотеки Wire. Може і для ардуіни є якісь реалізації runtime stack guard, але мені не зустрічались.

#277 Re: Різне » слияние нескольких скетчей в один » 2025-04-10 18:43:15

val1 пише:

не умею программировать Ардуино, пользуюсь готовыми скетчами,
возникла проблема как объединить 6 в один,может ли кто помочь?

Ви хочете розібратись і навчитись, чи просто отримати готовий результат?

Якщо перше, то почніть з простої програми, поступово додаючи потрібні функції. Почитайте туторіали, подивіться навчальні відео, їх зараз мільйони. Якісь конкретні питання - пишіть, що саме незрозуміло.

Якщо друге, то шукайте виконавця, який зробить вам послугу за відповідну оплату.

#278 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-10 11:49:46

Працює, не димить, не іскрить - це вже добре cool
Але перевірку контрольної суми я б таки додав. Хто зна, що там з UART'у може прийти. Так хоч якась валідація.

dimich пише:

sprintf(fullSpeed, sizeof(fullSpeed), "%03d", speedKmh);

Звісно ж, тут snprintf. То я при цитуванні забув поправити.

#279 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-10 10:17:16

jokeer пише:

char nmeaCStr[nmea.length() + 1];
wtf? хіба так можна?

Можна в ISO C99, а з gcc можна і в C++. Але чи варто - залежить від контексту. В даному випадку навряд чи.

#280 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-10 10:12:53

jokeer пише:

snprintf(fullSpeed, sizeof(fullSpeed)-1

Це чому? snprintf в avr-libc нічим не відрізняється від інших реалізацій libc:

no more than n characters (including the trailing NUL character) will be converted to s

#281 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-09 23:54:14

renoshnik пише:
String nmeaSentence = ""; 		// Переменная для хранения NMEA-строки
...
nmeaSentence += c; 		// Добавить символ к строке NMEA
...
char nmeaCStr[nmea.length() + 1];
nmea.toCharArray(nmeaCStr, nmea.length() + 1);
...

Навіщо зберігати символи в String, якщо потім їх копіювати в char[] ?

renoshnik пише:
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;
renoshnik пише:
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);
renoshnik пише:
char fullSpeed[5];
sprintf(fullSpeed, "%03d", speedKmh);

У разі невалідних вхідних даних може бути переповнення буфера. Хоча б

sprintf(fullSpeed, sizeof(fullSpeed), "%03d", speedKmh);

Хоча у разі невалідних вхідних даних переповнення буфера буде ще на стадії отримання строки з UART.

#282 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-07 14:24:37

jokeer пише:

Кілобайти то такоє.. Там мабуть і периферія трохи незвична, і тактування..

Та периферія здерта з STM32. Тільки ядро RISC-V.

#283 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-07 13:12:04

jokeer пише:

Мені китайські stc8 сподобались. 8 ніг, обв'язки немає. Програмуються трохи по наркоманськи, ага.

Цікаво. Цих не доводилось щупати.
Трохи грався з CH32V. Наче нічо, але в "продакшн" їх брати поки б не ризикував. Документація мізерна, є питання щодо надійності в довгостроковій перспективі. Ну і треба розуміти, що кілобайт на AVR8 і кілобайт на RV32 - це дещо різний кілобайт.

#284 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-07 12:29:36

jokeer пише:

Для використання attiny повинна бути дуже поважна причина wink

Колись була: ціна. Зараз, із китайськими CH32 по "гривні за відро", це майже не актуально.

#285 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-07 11:04:37

jokeer пише:

rtos нахвалюють, і навіть під atmega воно є.

Так правду кажуть, мультипоточність - для тих, хто не зрозумів конечні автомати smile
Це зручно, але кожній задачі потрібен стек, як мінімум щоб зберігати контекст. На Attiny13 ОЗУ 64 байта, а контекст регістрів - 32 байта. На Attiny15 взагалі ОЗУ нема smile

#286 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-07 10:15:00

jokeer пише:

А є приклади реалізації, щоб не по уродськи ? wink

Мені поки що не зустрічались. А самому розробляти - часу на це немає, та і для однієї людини воно розтягнеться на роки. Воно ж одне тягне за собою інше.
Від існуючих ардуінівських бібліотек доведеться відмовитись одразу, а кому воно потрібне без бібліотек? Найбільш розповсюджені ще якось більш-менш реалістично портувати під нову парадигму, але ж тисячі їх. Ще й уніфікація HAL'а для різних архітектур.
На виході генерувати код на C? Сучасні компілятори не дуже підходять для embedded з обмеженими ресурсами. Наприклад, з LTO практично неможливо передбачити, скільки стеку потрібно тій чи іншій задачі. Інші налаштування оптимізації - все, приїхали. Потрібно розробляти свій компілятор з блекджеком і повіями, та ще й щоб підтримував зоопарк цільових архітектур. Невдячна це справа.

#287 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-06 16:13:14

jokeer пише:

Ну, мабуть існують задачі, які простіше описати на декларативній мові.

Мені здається, навпаки, для більшості задач, які намагаються вирішувати за допомогою ардуіни (в тому числі і тут на форумі), декларативна парадигма так і проситься. Але то таке, філософія.

jokeer пише:

Але csv - навряд чи цей випадок smile

Це поки самі value не містять в собі comma, які можуть бути не тільки екрановані, а і в складі літералів у одинарних або подвійних лапках, наприклад.

#288 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-06 10:46:15

jokeer пише:

Я правильно розумію, що якщо на мові yacc описати правила для формату email, то можна отримати regexp?

Не regexp, а код, який реалізує алгоритм, еквівалентний regexp. Тільки у yacc можливостей на порядок більше, він може генерувати парсер для будь-якої Тьюринг-повної мови.

jokeer пише:

але як дебажити помилки? Тестами хіба що.

Тестами помилки тільки виявляються, як і скрізь. Зміни вносяться в опис граматики. Мабуть є якісь опції і для дебага. Я далеко не експерт в парсер-генераторах, тільки краєм вуха бачив.

#289 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-06 09:01:09

jokeer пише:

Якщо ваша проблема вирішується через регекспи - значить у вас 2 проблеми wink регексп для перевірки коректності email це кілька рядків.

Ви про цей вираз? Так і формат адреси відповідно до RFC 822 непростий. До того ж, такі вирази не пишуться вручну, а генеруються з простіших частин. Ви ж не звинувачуєте компілятор C++, що він генерує машинний код, який людині складно розібрати? Регулярний вираз - той же "машинний код" для КА.

jokeer пише:

А кінцеві автомати якось пройшли мимо. Учили, але як від діаграми перейти до коду я так і не врюхав.

"Звичайна" програма з розгалуженнями і циклами - це теж КА (як мінімум).

#290 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-05 22:01:43

Але суть запропонованої мною реалізації в тому, що дані частково парсяться по мірі надходження. UART працює повільно відносно процесора, тому виконання парсинга "розмазуються" в часі.

Якщо потрібно витягати тільки певні токени у певних типах повідомлень, то можна допиляти парсер, щоб він по закінченню просто випльовував готову структуру з float'ами, чи що там ще потрібно.

#291 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-05 21:51:30

jokeer пише:

як це взагалі працює?  на вході опис даних на виході код С?

Саме так. Але це має сенс для парсинга нерегулярних синтаксисів. Для регулярних, типу як в NEMA, достатньо КА.
Якби в arduino core була підтримка регулярних виразів, весь той КА можна було би описати однією строкою. Хіба що обчислення контрольної суми довелось би реалізовувати окремо.

#292 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-05 21:15:54

jokeer пише:

Кінцевий автомат воно концептуально. але ж букв дофіга wink і ніхто крім автора не розуміє як воно працює wink

Букв там дофіга бо написано для ардуіни на недо-C++. Для документації продакшн коду можна діаграму намалювати. Або взагалі взяти парсер-генератор (yacc чи bison) і не морочити голову.

#293 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-05 19:45:30

Та ось вам іще одна наївна реалізація

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();
    }
}

Оффтоп: баг з бекслешами на цьому форумі колись пофіксять?

#294 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-05 15:20:33

renoshnik пише:

отримую таке

Які поля з яких типів повідомлень вам потрібно витягти?

#295 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-05 13:48:49

jokeer пише:

Або візьміть приклад з man strtok і переробіть під себе.

strtok() витягає тільки непусті токени. NMEA може містити пусті.

#296 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-05 13:45:16

Взагалі, ефективна реалізація парсера залежить від того, чи потрібно парсити якість конкретні типи повідомлень, які токени з них потрібні, в якому форматі вони мають бути для зручності подальшої обробки і т.д.
Це класична задача для реалізації у вигляді конечного автомата.

#297 Re: Програмування Arduino » парсинг NMEA **проблема » 2025-04-05 11:03:17

renoshnik пише:

Проблема в тому, що серія ком ( ,,, ) функцією sscanf обробляється не корректно ...  sad sad sad

sscanf() - невдалий вибір для парсингу такого синтаксиса. Його тут можна застосувати для парсингу окремого токена, але для рядка в цілому він погано підходить.

Токени розділені комами, так шукайте символ коми. Або за допомогою strchr(), або своєю реалізацією, що не набагато складніше. Потім парсіть вміст між комами.

Навіщо копіювати кожний токен в окремий буфер? Він же і так уже присутній в памʼяті. Якщо для парсинга самого токена він має закінчуватись нульовим символом, заміняйте кому на нульовий символ. 

Якщо не помиляюсь, максимальна довжина рядка NMEA - 82 символи. Тобто буфера довжиною 83 символи буде достатньо. Чому 88?

Взагалі, ви спочатку накопичуєте всю строку в буфер, потім намагаєтесь її парсити. Звісно, можна і так. Але, мені здається, простіше парсити дані по мірі надходження.

#298 Re: Різне » 3D моделювання » 2025-04-04 22:08:34

Мені зайшов OpenSCAD. Пересічній людині може і не буде простою, але для розробників саме то, імхо.

#299 Re: Апаратні питання » Сушилка для філаменту на Arduino. » 2025-04-04 12:46:50

jokeR пише:

Короче. По живленню пролазить імпульсна завада.

Ага, і мабуть ще струмовий шунт між вхідним і вихідним мінусами.

#300 Re: Апаратні питання » Сушилка для філаменту на Arduino. » 2025-04-04 12:44:54

Якщо на платі ардуіни нормальна AMS1117, то зовнішній регулятор викидаєте зовсім, на Vin подаєте +12В з блока живлення. З конденсаторами з боку ардуіни. Периферію живете з виходу +5В. Для живлення дуйчика див. попередні рекомендації.

Підвал форуму