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);
}
1 Answer 1
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.
-
1Are all these specific '_P' functions documented somewhere? I know a few but not all of them like strcasecmp_P.Michel Keijzers– Michel Keijzers2020年04月18日 20:41:23 +00:00Commented Apr 18, 2020 at 20:41
-
3@MichelKeijzers nongnu.org/avr-libc/user-manual/group__avr__pgmspace.htmlMajenko– Majenko2020年04月18日 20:51:18 +00:00Commented Apr 18, 2020 at 20:51
-
No, I've used
F()
forSerial.print()
andSerial.println()
, not for ̇strcmp(). Why I should used it in
strcmp()` then?.Pararera– Pararera2020年04月18日 21:13:59 +00:00Commented Apr 18, 2020 at 21:13 -
1@SilvioCro Then there is something strange happening in the mysterious
CMDHandler
class'sfind
method. If your string is not parsing correctly you should look at the parser, not the comparison structure.Majenko– Majenko2020年04月19日 10:11:14 +00:00Commented Apr 19, 2020 at 10:11 -
1You have a critical mistake in your find method. See my edit above.Majenko– Majenko2020年04月19日 17:40:05 +00:00Commented Apr 19, 2020 at 17:40