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

asked Apr 18, 2020 at 20:28

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.

answered Apr 18, 2020 at 20:39
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 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?. 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. Commented Apr 19, 2020 at 10:11
  • 1
    You have a critical mistake in your find method. See my edit above. Commented Apr 19, 2020 at 17:40

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.