F() мешает программе

Я работаю над библиотекой обработчиков команд, и у меня возникла большая проблема с функцией F(). Пример скетча для библиотеки использует довольно много оперативной памяти, потому что ему приходится печатать много текста по UART. Как и прежде, я использовал F(), чтобы уменьшить использование оперативной памяти. Сначала сработало!

Lib работает с помощью функции strtok. Команда fomar:

cmd1,..,paramN;..;cmdN,..,paramN

Команды разделены ;, а параметры команды (params) разделены , Библиотека работает без проблем, возвращает адрес памяти (char*) любой команды и каждого параметра.

Когда я пытаюсь сравнить параметр/команду с strcmp, это работает, только если все команды в верхнем регистре. Возьмем, к примеру, команду LED: Он работает только с Led, LED или любым случаем, где буква d находится в верхнем регистре. led не будет работать, и будет сказано, что команда le не существует. Да, le, он не будет печатать led. Но после команды DURATION он как-то заработал.

Память для последовательного буфера динамически выделяется в конструкторе объекта в глобальной области видимости. Почему? Все эти проблемы связаны с тем, что F(). Может кто-нибудь объяснить, почему? Вот код и скриншот последовательного монитора.

void execCmd(const char *set)
{
char *cmdParam = CMDHandler.find(set, CMD_PARAM);

if (!strcmp(cmdParam, "LED"))
{
    // ОДИН СПОСОБ ПРОВЕРИТЬ, ИМЕЕТ ЛИ КОМАНДА ПАРАМЕТРЫ - ПЕРЕД ПОЛУЧЕНИЕМ ПАРАМЕТРОВ == ЛУЧШИЙ СПОСОБ
    if (!CMDHandler.count(CMDHandler.getNext(CMD_PARAM), CMD_PARAM))
    {       
        Serial.print(F("->> Command LED: Current LED status is "));
        Serial.println(digitalRead(LED_PIN));
        return;     
    }

    cmdParam = CMDHandler.find(nullptr, CMD_PARAM);
    LEDStatus = 0;
    uint8_t status = atoi(cmdParam);

    digitalWrite(LED_PIN, status);
    Serial.print(F("->> Command led: LED Status is "));
    Serial.println(status, DEC);
}
else if (!strcmp(cmdParam, "LOOP"))
{
    cmdParam = CMDHandler.find(nullptr, CMD_PARAM);

    // ВТОРОЙ СПОСОБ ПРОВЕРИТЬ, ИМЕЕТ ЛИ КОМАНДА ПАРАМЕТРЫ - ПОСЛЕ ПОЛУЧЕНИЯ КАЖДОГО ПАРАМЕТРА
    if (cmdParam == nullptr)
    {
        Serial.println(F("->> Command LOOP: Expected one parameter!"));
        return;
    }   

    uint8_t status = atoi(cmdParam);
    if (status)
    {
        LEDStatus = 2;
        digitalWrite(LED_PIN, HIGH);
        Serial.println(F("->> Command LOOP: LED Blink Loop is on!"));
        delay(blinkDuration);
    }
    else
    {
        LEDStatus = 0;
        digitalWrite(LED_PIN, LOW);
        Serial.println(F("->> Command LOOP: LED Blink Loop is off!"));          
    }   
}
else if (!strcmp(cmdParam, "DURATION"))
{
    if (!CMDHandler.count(CMDHandler.getNext(CMD_PARAM), CMD_PARAM))
    {
        Serial.print(F("->> Command DURATION: Current blink duration is "));
        Serial.print(blinkDuration, DEC);
        Serial.println("ms");
        return;     
    }

    cmdParam = CMDHandler.find(nullptr, CMD_PARAM);
    blinkDuration = atoi(cmdParam);

    Serial.print(F("->> Command DURATION: New blink duration is "));    
    Serial.print(blinkDuration, DEC);
    Serial.println("ms!");  
}
else if (!strcmp(cmdParam, "HELP"))
{
    Serial.println(F("------ HELP ------\n-> LED [0/1] - Turns off/on LED on pin 13\n-> LOOP [0/1] - Stops/starts blink loop with LED on pin 13\n-> DURATION [X] - Changes duration of LED loop blink. Recommended values is 50-500ms\n"));
}
else
{
    Serial.print(F("->> Command "));
    Serial.print(cmdParam);
    Serial.println(F(" does not exist!"));
}

char *CMDHand::find(const char *input, uint8_t const type)
{
    char *found = nullptr;
    char separator = delimiter[type];

    if (input != nullptr) // ПЕРВЫЙ ВЫЗОВ
    {
        constrain[type] = input + strlen(input) - 1;
        next[type] = input;
    }
    else if (next[type] > constrain[type]) return (nullptr);

    found = strtok(next[type], &separator); // ПО НЕКОТОРЫМ ПРИЧИНАМ ЭТО НЕ РАБОТАЕТ, ЕСЛИ Я ПОСТАВЛЯЮ &delimiter[type] ВМЕСТО &separator
    last[type] = next[type];
    next[type] += strlen(found) + 1;

    return (found);
}

Снимок экрана монитора serail

, 👍1


1 ответ


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

5

Для сравнения со строками, заключенными в макрос F(), необходимо использовать специальные формы строковых функций, которые имеют суффикс _P. Вам также необходимо привести строковые литералы к правильному типу (PGM_P).

Для сравнения без учета регистра следует использовать strcasecmp вместо strcmp.

Чтобы собрать их вместе, вы получите следующее:

if (strcasecmp_P(cmdParam, (PGM_P)F("help")) == 0) {
    ...
}

Здесь возникла проблема:

found = strtok(next[type], &separator)

strtok принимает строку C как список разделителей (должен заканчиваться NULL). Вы передаете ему адрес одного символа. В следующем байте в памяти может быть или не быть (вероятно, нет) NULL, но все остальное до первого найденного в памяти NULL будет восприниматься как символы-разделители.

,

Все эти конкретные функции «_P» где-то задокументированы? Я знаю несколько, но не всем из них нравится strcasecmp_P., @Michel Keijzers

@MichelKeijzers https://nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html, @Majenko

Нет, я использовал F() для Serial.print() и Serial.println(), а не для ˙strcmp(). Почему тогда я должен использовать его в strcmp()`?, @Pararera

По тем же причинам, что и в Serial.print. Для экономии оперативной памяти., @Majenko

Большое спасибо (я уже проголосовал за ваш ответ)., @Michel Keijzers

@Majenko извините, но нет. Это не работает так, как должно. led не работает, потому что каким-то образом led становится le (я знаю, потому что там написано, что command le не существует). светодиод работает. Я не знаю, почему буква d (строчная d) вызывает все эти проблемы. Независимо от того, какую команду я набираю, если в ней есть d, это вызовет проблемы. Без F() в Serial.print/ln все работает нормально. Почему?, @Pararera

@SilvioCro Тогда в методе find загадочного класса CMDHandler происходит что-то странное. Если ваша строка не анализируется правильно, вы должны смотреть на синтаксический анализатор, а не на структуру сравнения., @Majenko

@Majenko добавил метод «найти». Код работает нормально, когда я не использую F(), даже если я пытался использовать его как программу на C++. Похоже, что F() переопределяет часть буфера для Serial, поскольку, если я печатаю текст, используя метод next из find, я могу увидеть часть текста, которую я не писал., @Pararera

У вас есть критическая ошибка в вашем методе поиска. См. мое редактирование выше., @Majenko

Большое спасибо, теперь это работает., @Pararera