1

I'm working on command-handler library and I have a big problem with F() function. Example sketch for library uses quite a lot of RAM, because it has to print a lot of text over UART. As before, I've used F() to lower RAM usage. It worked on first!

Lib works with help of strtok function. Command fomar is:

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

Commands are separated with ; while command parameters(params) are separated with , Library works without any problems, it returns memory address(char*) of any command and every param.

When I try to compare parameter/command with strcmp it works only if all commands are in uppercase. Let's take LED command for example: It works only with Led, LED or any case where is letter d in uppercase. led won't work, and it will say that command le doesn't exist. Yes, le, it won't print led. But, after DURATION command, it somehow start working.

Memory for serial buffer is dynamicly allocaed in object constructor, in global scope. Why? All those problems are because F(). Can someone explain why? Here's the code and screenshot of serial monitor.

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

if (!strcmp(cmdParam, "LED"))
{
    // ONE WAY TO CHECK IF COMMAND HAS PARAM(S) - BEFORE GETTING PARAM(S) == BETTER WAY
    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);

    // SECOND WAY TO CHECK IF COMMAND HAS PARAM(S) - AFTER GETTING EVERY 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) // FIRST CALL
    {
        constrain[type] = input + strlen(input) - 1;
        next[type] = input;
    }
    else if (next[type] > constrain[type]) return (nullptr);

    found = strtok(next[type], &separator); // FOR SOME REASON IT DOES NOT WORK IF I PUT &delimiter[type] INSTEAD OF &separator
    last[type] = next[type];
    next[type] += strlen(found) + 1;

    return (found);
}

Screenshot of serail monitor

1 Answer 1

5

To compare with F() macro encased strings you have to use special forms of the string functions that are suffixed with _P. You also need to cast the string literals to the right type (PGM_P).

To do a case-insensitive comparison you should use strcasecmp instead of strcmp.

To put those together you end up with:

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

There is a problem here:

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

strtok takes a C string as a list of separators (must be NULL terminated). You're passing it the address of a single character. There may or may not (probably not) be a NULL in the next byte in memory, but everything else up to the first NULL found in memory will be taken as delimiter characters.

10
  • 1
    Are all these specific '_P' functions documented somewhere? I know a few but not all of them like strcasecmp_P. Commented Apr 18, 2020 at 20:41
  • 3
    @MichelKeijzers nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html
    – Majenko
    Commented Apr 18, 2020 at 20:51
  • No, I've used F() for Serial.print() and Serial.println(), not for ˙strcmp(). Why I should used it in strcmp()` then?.
    – Pararera
    Commented Apr 18, 2020 at 21:13
  • 1
    @SilvioCro Then there is something strange happening in the mysterious CMDHandler class's find method. If your string is not parsing correctly you should look at the parser, not the comparison structure.
    – Majenko
    Commented Apr 19, 2020 at 10:11
  • 1
    You have a critical mistake in your find method. See my edit above.
    – Majenko
    Commented Apr 19, 2020 at 17:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.