Passing Command-line arguments in C for Embedded Targets
Passing Command-line arguments in C for Embedded Targets
Passing command line arguments to your main function is a commonly used feature when it comes to programming Linux command line tools. For Embedded Systems that do not use an OS to run an application, often called bare-metal applications, we need to understand the mechanism in order to adapt. If we do so, we can easily pass arguments to our bare metal application.
Passing arguments can be used for on-target embedded system testing. If you want to learn more about this interesting topic, we recommend the blog articles Modern On-Target Embedded System Testing with CMake and CTest and Automated On-Target Testing with J-Run’s –args Feature from Erich Styger.
This Knowledge Base article will explain the command line argument mechanism and demonstrate three ways to pass arguments to bare metal applications using the Segger ecosystem.
Command-line arguments in C
Let's have a look at a simple example args.c on a Linux host first:
#include <stdio.h>
int main(int argc, char** argv) {
int Cnt = 0;
char *sArg;
printf("argc = %i\n", argc);
while (argc-- > 0) {
sArg = *argv++;
printf("argv[%i]: %s\n", Cnt, sArg);
Cnt++;
}
return 0;
}
If we try it out on out Linux command line it will simply work:
CID294:~/args$ gcc -o args args.c
CID294:~/args$ ./args foo baz --bar
argc = 4
argv[0]: ./args
argv[1]: foo
argv[2]: baz
argv[3]: --bar
CID294:~/args$
So obviously there is some OS inherent magic involved, converting the command string into variables reaching the application:
Command-line arguments in Embedded Systems
If we are using a bare-metal application on a target device, this magic is obviously not in place. There is no standard way of passing the command line to the startup code and thus there is no standardized startup code to demangle the symbols. Following examples use SEGGER J-Run, Semihosting and RTT.
Passing argc/argv to the target main function
The method mimicking the host implementation one-to-one, is writing the argc and argv contents directly to the target memory. However, this requires some dedicated target startup code and is thus toolchain specific. Also the necessary request is not covered by Semihosting standard. The argument passing mechanism looks like depicted:
Example using Segger Semihosting
Prepare an Embedded Studio project, select SEMIHOST Library I/O and define preprocessor macro FULL_LIBRARY during build. This will instruct the Segger startup to reserve space for the arguments, retrieve arguments from host using a Segger Semihosting extension and pass argc and argv to main.
J-Run will answer the custom Semihosting request and the arguments will be available via argc/argv in your application.
Code to receive the arguments on the target (as said, magic happens before main ;-):
#include <stdio.h>
int main(int argc, char** argv) {
int Cnt = 0;
char *sArg;
printf("argc = %i\n", argc);
while (argc-- > 0) {
sArg = *argv++;
printf("argv[%i]: %s\n", Cnt, sArg);
Cnt++;
}
return 0;
}
Passing the arguments to application
An alternative method, is sending the argument string to the target application on request. This is not as comfortable, but does not impose requirements to the toolchain.
Example using standard Semihosting
Enhance your custom project implementing Semihosting and issue a SYS_GET_CMDLINE command. The application has to reserve some space for the argument string, fetch the arguments using the Semihosting command and parse them.
J-Run will answer the SYS_GET_CMDLINE request and the arguments will be available as string in your application.
Start J-Run, passing the intended arguments:
C:\Work\JLink\Output\Debug_x64>JRun --wait ST_STM32F407_SemihostArgs_Example.elf --args "foo baz --verbose"
Code to receive the arguments on the target:
#include <stdio.h>
#include "SEGGER_SEMIHOST.h"
void main(void) {
char *sArg;
char aArg[128];
int iSize = 128;
printf("SYS_GET_CMDLINE:\n");
SEGGER_SEMIHOST_GetCmdLine(aArg, &sArg, &iSize);
printf("args = %s\n", sArg);
return 0;
}
Example using RTT
J-Run will establish a connection to target using RTT and parse the output from the target looking for dedicated wildcards. The application has to reserve some space for the argument string, fetch the arguments by sending the corresponding wildcard and parse them.
J-Run will answer the wildcard string and send the arguments to your application.
Start J-Run, passing the intended arguments:
C:\Work\JLink\Output\Debug_x64>JRun --wait ST_STM32F407_RTTArgs_Example.elf --args "foo baz --verbose"
Execute the code to receive the arguments on the target:
#include "SEGGER_RTT.h"
static int _JRUN_GetArgs(const char* buffer, const int size) {
int NumBytes;
SEGGER_RTT_printf(0, "*ARGS*\n");
NumBytes = 0;
do {
NumBytes += SEGGER_RTT_Read (0, (void*)&buffer[NumBytes], size - NumBytes);
} while ((NumBytes == 0 || buffer[NumBytes-1] != '\n') && (NumBytes < size));
return NumBytes;
}
int main(void) {
int Cnt = 0;
char acArgs[128] = {0};
SEGGER_RTT_Init();
_JRUN_GetArgs(acArgs, 128);
SEGGER_RTT_printf(0, "args = %s\n", acArgs);
return 0;
}
Defining Command-line arguments in other Segger Tools
In order to use above examples, your host tool needs to be able to support the selected method argument passing. The following table provides an overview which method is supported by which tool:
| Tool | SYS_GET_ARGS | SYS_GET_CMDLINE | RTT *ARGS* |
|---|---|---|---|
| Embedded Studio | + | + | - |
| Ozone | - | + | - |
| J-Run | + | + | + |
In J-Run, arguments can be passed by using the "--args"option
C:\Work\JLink\Output\Debug_x64>JRun --wait ST_STM32F407_SemihostArgs_Example.elf --args "foo baz --verbose"
In Embedded Studio, arguments can be added in the Project Options
In Ozone, arguments can be added by following Console Command
Project.ConfigSemihosting("TargetCmdLine="foo baz --verbose")