I have been asked to provide a command line shell example for a bare-metal (no RTOS) application, so here we go!
Having a way to communicate to the firmware on a board is essential for most of my projects: it is simply, incredibly helpful and easy to do (see “A Shell for the Freedom KL25Z Board“). This tutorial shows how to add a simple command line shell to the NXP Freedom board which then can be extended as necessary.
Outline
I’m using the Eclipse NXP Kinetis Design Studio v3 with a NXP FRDM (FRDM-KL25Z, as the most popular board). But any other IDE or board would be applicable too. I’m using Processor Expert to set up the shell (serial) communication interface, but you could use any kind of driver too. It is just that with Processor Expert it is very portable, generic and super easy to do :-). If you like, you can use the Kinetis SDK or anything else with the same concept.
Processor Expert Project
In this project I’m using Processor Expert without the Kinetis SDK. I’m using a few components from the McuOnEclipse project (see “McuOnEclipse Releases on SourceForge“).
In the first step, create a project with your IDE. I have created a project for the NXP Kinetis KL25Z with Processor Expert enabled.
Add the Utility and the Shell components to the project. The Shell component will ask you which serial interface to use: use the ‘Serial’ option:
This mean we are going to use the Serial (UART) on the board. The ‘Serial’ is a pre-configured ‘AsynchroSerial’ component.
The AS1 Aynchroserial shows an error because it is not configured yet for the UART I want to use. To use it with the UART to the OpenSDA circuit on the FRDM-KL25Z board, I configure it as below:
💡 If using another board, check your board schematics and change the UART and pin assignments.
The linkage between the Shell and the serial communication channel is a setting in the Shell component:
The Shell component offers many functions to read/write to the console:
Writing Text
I can write text to the shell or console that way:
void CLS1_SendStr(const uint8_t *str, CLS1_StdIO_OutErr_FctType io);
The first parameter is the string to write, and the second parameter is a standard I/O handle. The handles are call backs to read/write characters. The shell implements a default handler I can get with CLS1_GetStdio(). The following prints a ‘hello’ to the terminal:
CLS1_SendStr("Hello World!\r\n", CLS1_GetStdio()->stdOut);
If I put this into an into an endless loop inside main and add a delay of 1000 ms, it will print the messsage every second.
for(;;) { CLS1_SendStr("Hello World!\r\n", CLS1_GetStdio()->stdOut); WAIT1_Waitms(1000); }
Connect with a terminal program to the COM port of the board and you should see something like this:
Below is an example function which reads in some text and writes it back to the terminal:
static void ReadText(void) { uint8_t buffer[32], ch, i; for(;;) { CLS1_SendStr("Enter some text: ", CLS1_GetStdio()->stdOut); buffer[0] = '\0'; i = 0; do { if (CLS1_KeyPressed()) { CLS1_ReadChar(&ch); /* read the character */ if (ch!='\0') { /* received a character */ buffer[i++] = ch; /* store in buffer */ if (i>=sizeof(buffer)) { /* reached end of buffer? */ buffer[i] = '\0'; /* terminate string */ break; /* leave loop */ } if (ch=='\n') { /* line end? */ buffer[i] = '\0'; /* terminate string */ break; /* leave loop */ } } } } while(1); CLS1_SendStr("You entered: ", CLS1_GetStdio()->stdOut); CLS1_SendStr(buffer, CLS1_GetStdio()->stdOut); CLS1_SendStr("\r\n", CLS1_GetStdio()->stdOut); } }
Which then looks like this:
Shell Application
The above can be easily extended with a shell application which parses input and provides a command line menu. In this example I want the ability to set a variable of my application with a command line interface:
static int app_value = 0;
Next is a simple command line parser:
; if (UTIL1_strcmp((char*)cmd, CLS1_CMD_HELP)==0 || UTIL1_strcmp((char*)cmd, "app help")==0) { CLS1_SendHelpStr((unsigned char*)"app", (const unsigned char*)"Group of app commands\r\n", io->stdOut); CLS1_SendHelpStr((unsigned char*)" val ", (const unsigned char*)"Set value\r\n", io->stdOut); *handled = TRUE; } else if ((UTIL1_strcmp((char*)cmd, CLS1_CMD_STATUS)==0) || (UTIL1_strcmp((char*)cmd, "app status")==0)) { CLS1_SendStatusStr((unsigned char*)"app", (const unsigned char*)"\r\n", io->stdOut); UTIL1_Num32sToStr(buf, sizeof(buf), app_value); UTIL1_strcat(buf, sizeof(buf), "\r\n"); CLS1_SendStatusStr(" val", buf, io->stdOut); *handled = TRUE; } else if (UTIL1_strncmp((char*)cmd, "app val", sizeof("app val")-1)==0) { p = cmd+sizeof("app val")-1; res = UTIL1_xatoi(&p, &tmp); if (res==ERR_OK) { app_value = tmp; *handled = TRUE; } return res; } return res; }
It parses the incoming command (cmd). If the command has been recognized, it returns TRUE through handled.
The command handlers are listed in a table of function pointers with a NULL entry at the end:
static const CLS1_ParseCommandCallback CmdParserTable[] = { CLS1_ParseCommand, /* default shell parser */ ParseCommand, /* my own shell parser */ NULL /* Sentinel, must be last */ };
The main loop then is very simple:
static void Shell(void) { unsigned char buf[48]; app_value = 0; /* init value */ buf[0] = '\0'; /* init buffer */ (void)CLS1_ParseWithCommandTable((unsigned char*)CLS1_CMD_HELP, CLS1_GetStdio(), CmdParserTable); /* write help */ for(;;) { /* wait for input and parse it */ (void)CLS1_ReadAndParseWithCommandTable(buf, sizeof(buf), CLS1_GetStdio(), CmdParserTable); } }
Inside the endless loop, it reads from the standard input and passes the commands to the parsers in the table. Below is an example session:
Summary
It does not need much to have a command line interface to the application. It only needs little resources, but adds big user and debugging value to most projects. Most of the time it makes sense to run the application with an RTOS and run the shell as a dedicated task. But as shown here, it is easy in a bare metal environment too. The project created in this tutorial is available on GitHub here.
Happy Shelling 🙂
Links
- McuOnEclipse Processor Expert Components: https://mcuoneclipse.com/2014/10/21/mcuoneclipse-releases-on-sourceforge/
- Kinetis Design Studio: http://www.nxp.com/kds
- FRDM-KL25Z Board: http://www.nxp.com/FREEDOM
- Project of this tutorial on GitHub: https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/KDS/FRDM-KL25Z/FRDM-KL25Z_Shell_PEx
Pingback: A Shell for the Freedom KL25Z Board | MCU on Eclipse
This would be great if AsynchroSerial was an option for the KV series. Do you have any tips (or a guide) on what is needed to extend this support?
LikeLike
I think it should not be too hard to add the shell sources to a SDK project (with or without Processor Expert). To bad that Processor Expert is not included in KSDK v2.0, so not sure how much effort I should put into this 😦
LikeLike
Thank you! I’ll try to test it!!
LikeLike
[Try again – WordPress ate my comment!]
Hi Erich – trying to follow this on a clean KDS installation. I’ve picked up KinetisTools_09.02.2016.PEupd from SourceForge, Processor Expert -> Import Components. Even with filtering off, and after a restart, I can’t see any of your components in Components Library.
I saw an “updating supported processors” message when I did Import Components – so assume that worked.
Am I doing something wrong?
LikeLike
Are you showing all repositories? See https://mcuoneclipse.com/2015/07/06/processor-expert-component-repositories/
LikeLike
Interesting – initially, I imported into ‘My Components,’ where I could only see a component called ‘KinetisTools’. I tried creating a new repository, and importing again – this also has only ‘KinetisTools’ in in it. I double-click this, I get an error:
‘Creating referenced component’ has encountered a problem.
Error during adding referenced component/template “Unsupported-Utility/UNSUPPORTED”. Component is either missing or corrupted.
This is Kinetis Design Studio Version: 3.0.0
Eclipse Version: Luna SR2 (4.4.2)
LikeLike
*headdesk* I figured it – what I thought was the correct file (first one showing) on SourceForge wasn’t. Sorry. Now to get back to it…
LikeLike
Hi, Erich.
I do not see the Download Zip button in the GitHub. I am log in with my account GitHub.
Manuel.
LikeLike
Hi Manuel,
there should be a ‘Download Zip’ button. Here is the link for the download: https://github.com/ErichStyger/mcuoneclipse/archive/master.zip
LikeLike
Hi sir,
Wow amazing I tried for my KE-02Z controller and its amazingly working. Have you wrote any code for sending a file through Shell ? I saw your brilliant boot loader article where you have successfully sent your application file. If you have any article for sending a s19 file with the help of Shell, kindly share the link sir.
Looking forward for your reply sir
Thanks sir
Bala
LikeLike
Hi sir, sorry for being over anxious in sending another query before getting the reply for the current query. But sir, actually Hello world code worked fine.
But
int main(void)
{
PE_low_level_init();
static void ReadText(void) {
uint8_t buffer[32], ch, i;
for(;;) {
GPIO_PDD_TogglePortDataOutputMask(GPIOA_BASE_PTR, GPIO_PDD_PIN_21);
CLS1_SendStr(“Enter some text: “, CLS1_GetStdio()->stdOut);
buffer[0] = ‘\0′;
i = 0;
do {
if (CLS1_KeyPressed()) {
GPIO_PDD_TogglePortDataOutputMask(GPIOA_BASE_PTR, GPIO_PDD_PIN_21);
CLS1_ReadChar(&ch); /* read the character */
if (ch!=’\0’) { /* received a character */
buffer[i++] = ch; /* store in buffer */
if (i>=sizeof(buffer)) { /* reached end of buffer? */
buffer[i] = ‘\0′; /* terminate string */
break; /* leave loop */
}
if (ch==’\n’) { /* line end? */
buffer[i] = ‘\0’; /* terminate string */
break; /* leave loop */
}
}
}
} while(1);
GPIO_PDD_TogglePortDataOutputMask(GPIOA_BASE_PTR, GPIO_PDD_PIN_21);
CLS1_SendStr(“You entered: “, CLS1_GetStdio()->stdOut);
CLS1_SendStr(buffer, CLS1_GetStdio()->stdOut);
CLS1_SendStr(“\r\n”, CLS1_GetStdio()->stdOut);
}
}
This code when built throws error
../Sources/main.c:61:15: error: invalid storage class for function ‘ReadText’
static void ReadText(void) {
^
Thanks sir
Looking forward for your reply
LikeLike
Hi Bala,
you have the function ReadText() inside the function main(). This is not possible in C/C++. You have to write the function outside the main() function.
LikeLiked by 1 person
Hi Bala,
the current implementation of the Shell component only deals with ASCII data. Loading S19 files would be possible, but is currently not implemented.
Erich
LikeLike
Hi sir,
Oh thanks. Ill put it outside the main function and will try to debug the code. So S19 files are possible right. Do you have any articles to load S19 files. I want to get familiarized with this amazing SHELL component before starting to design a boot loader. That is why am asking you is there any specific articles to Load s19 files and see the output in Terminal.
Thanks sir
Looking forward for your reply.
Bala
LikeLike
Hey Bala,
I tried your shell example and for some reason it is just spilling out gibberish.
“[00][02][00]
———————————— —
f oo p ”
Have you seen this before?
The ‘Shell’ function is the only thing i have added to main.
Thanks for your time,
Jay
LikeLike
Make sure your baud settings are correct. Additionaly, in the AsynchroSerial components, have the interrupts enabled and specify buffers for 32 or more for RX and TX.
LikeLike
Erich, I tried adding the Shell component to a FRDM-KL26Z board today. At the step where it asks which component to use for the console/serial interface, it reported “New component [My Components/Serial] (unsupported on MKL26Z128VLH4)”. Is this a KSDK v1.3 vs v2 issue, and do you have any suggestions on how to resolve it ?
thanks,
Geoff
LikeLike
Hi Geoff,
yes, this is a KSDK issue: the SDK does not support the needed/required serial components.
Best if you do not use the SDK for that KL26Z project.
LikeLike
Hi Erich,
First of, thanks a lot for helping with all these concepts, not sure how I would learn without your blog.
I’m trying to implement this on my K64F board, and for some reason if I have interrupts enabled in AS1 (64bytes for Rx/Tx), nothing comes through to my terminal. On the other hand, disabling interrupts allows me to transfer everything through.
Here’s my code:
for(;;) {
CLS1_SendStr(“Hello World!\r\n”, CLS1_GetStdio()->stdOut);
LED1_Neg();
LED2_Neg();
WAIT1_Waitms(1000);
}
implemented in main() function
Any idea what I’m missing?
Thank you,
Kevin
LikeLike
Hi Kevin,
not sure what this is, except can you check that interrupts are really enabled? Check the PRIMASK register (registers view) if interrupts are really enabled (0)?
LikeLike
Hi Erich,
Thank you for your response!
I’ve checked primask register and it comes out as 0x0. I guess that is what it should be?
If I put a tiny code snippet:
CLS1_SendStr(“Hello World!\r\n”, CLS1_GetStdio()->stdOut);
Into void AS1_OnRxChar(void), it appears to respond to my terminal commands well. More so, if I stick CLS1_SendStr(…) into that interrupt routine, it also prints everything out. However, if I leave all events empty and put the CLS1_SendStr() into main(), nothing comes out to my terminal. Just to verify, in you guide you only programmed in main(), right?
Could this in any way be related to my machine running Windows 10?
LikeLike
Yes, a value of zero in PRIMASK means that interrupts are globally enabled.
LikeLike
Made a small mistake in my previous message. What I wanted to say is that both functions AS1_SendChar(); and CLS1_SendStr(); (AsynchroSerial and Shell components) work when putting them into events routine AS1_OnRxChar, but CLS1_SendStr(); does not work in main()
LikeLike
Hi Kevin,
really hard to tell. Maybe you can send me your project to the email address listed on https://mcuoneclipse.com/about/ and I try to have a look?
LikeLike
Hi Erich,
I just noticed, that after running PE_low_level_init(); line PRIMASK register becomes 0x1. Could this be an issue?
LikeLike
Yes, that means that interrupts are disabled. Then your UART driver does not work as it needs interrupts. I’ll have a look at the project you have sent.
LikeLike
Hi Kevin,
I had a look at your project: Interrupts are disabled because you are using the RTOS in your project. By default, interrupts are disabled in PE_Low_Level_Init() and are enabled when you start the scheduler with vTaskStartScheduler(). You can control this by the FreeRTOS setting ‘Disable Interrupts in Startup code’. Or enable interrupts with Cpu_EnableInt() (that method is disabled by default in the CPU component, so enable it first).
I hope this helps,
Erich
LikeLike
Hi Erich,
I am having some problems with this project.
I have a KE04A8VTG4 processor, and I repeated the steps in this blog post of yours up to the section that starts with Shell Application, and everything works perfectly. (Although I added the shell component first and the util component was added automatically) Oh, Also I am using the RTT_Terminal instead of the AS port, and I have the SeggerRTT component included.
At this point if I add another component, (LED should be easy, but it seems like even the HF component also makes it fail), I get an hard fault every time the ReadText function runs.
EVEN THOUGH I DIDN’T EVEN CALL the led component I added!!!
If I remove the LED component and delete generated code and rebuild and relaunch, it works again. And it seems to not be just the LED component, adding the HF component does the same thing.
Hope you can tell me what is wrong here! I have my project with LED and QUAD decoder and some IO pins all working if I don’t want to use the shell (or even just the RTT_terminal would be enough) Update: I just commented out the ReadText() function call, and the CLS1_SendStr(“Hello World”. CLS1_GTetStdio()->stdOut) call works with my LED blinking.
This is telling me the problem is in the CLS1_ReadChar(&ch), because that is also where the HF or unhandled interrupt was generated.
If instead of trying to add the LED at that point I want to go forward and continue with adding the Shell Application, the code you include above at the point
‘Next is a simple command line parser:’
begins with a semicolon and has too many close braces, so something is missing and I cannot drag and drop your code into my app anymore.
Brynn
LikeLike
I found the code that is missing from the above (by downloading your entire MCUonEclipse) –
the missing part is the beginning of parse command:
static uint8_t ParseCommand(const unsigned char *cmd, bool *handled, const CLS1_StdIOType *io) {
uint8_t res = ERR_OK;
int32_t tmp;
const uint8_t *p;
uint8_t buf[16] ;
LikeLike
Hi Erich,
Okay this code works perfectly, blinking my led and writing hello world onto the RTT_terminal,
and if I type a char into the RTT_terminal, it prints that it ‘read a char’:
static int position=0;
static unsigned char buff[64].ch
for(;;) {
LED1_Off();
if (CLS1_KeyPressed()) {
CLS1_ReadChar(&ch); /* read the character */
CLS1_SendStr(“read a char\r\n”, CLS1_GetStdio()->stdOut);
}
#if 0
XF1_xsprintf(buff,”char =0x%x Pos=%d\r\n”,ch,position);
CLS1_SendStr(buff, CLS1_GetStdio()->stdOut);
#endif
CLS1_SendStr(“Hello World \r\n”, CLS1_GetStdio()->stdOut);
WAIT1_Waitms(1000);
LED1_On();
WAIT1_Waitms(1000);
}
BUT, if I change the #if 0 to #if 1, it gets the to
hard fault interrupt
memcpy
_WriteNoCheck() at SEGGER_RTT.c
SEGGER_RTT_WriteNoLock()
SEGGER_RTT_Write()
RTT1_SendChar()
CLS1_SendChar()
CLS1_SendStr()
main()
My stack was only 0x50 in the CPU component, I upped it to 0x200 but it still failed in the same way.
Brynn
LikeLike
I have quickly tried it with a real UART (TWR-KL25Z, code on https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/KDS/FRDM-KL25Z/FRDM-KL25Z_Shell_PEx) and it works for me:
Hello World
char =0x0 Pos=0
Hello World
char =0x0 Pos=0
Hello World
char =0x0 Pos=0
Hello World
char =0x0 Pos=0
Hello World
char =0x0 Pos=0
Hello World
Not sure if RTT should make any difference, but I can try.
Erich
LikeLike
Hi Erich,
If you can try with the RTT that would be great. I only have the 16 pin package, and I was going to say that I didn’t have any pins left over for a UART, but I do have a SPI port on the external connector and they often share pins with the UART – I can probably wire something up. In fact, I might just change my whole communication protocol to use a UART instead of SPI
LikeLike
Nope, I can’t put a uart on the SPI pins that I used. Is there a bit-banged async serial component? I didn’t see one. I designed the board thinking I could use the RTT_terminal.
Brynn
LikeLike
No, I don’t have a bit-banged UART/async serial. I’m looking into trying it with RTT on the FRDM-KL25Z
LikeLike
I have now used the RTT terminal, and everything looks fine too:
Hello World
read a char
char =0x68 Pos=0
Hello World
read a char
char =0x65 Pos=0
Hello World
read a char
char =0x6c Pos=0
Hello World
read a char
char =0x70 Pos=0
Hello World
read a char
char =0xd Pos=0
Hello World
read a char
char =0xa Pos=0
Hello World
LikeLike
I am curious why your output always shows char = 0x0, I would expect it to give the ascii value of something you typed. (And don’t tell me you were typing ctrl-shift-@, which should generate a ascii 0x00 )
Brynn
LikeLike
I did not enter anything, and I had that code outside of the CLS1_KeyPressed() check, that’s why it printed always zero.
LikeLike
Thanks for checking it out. I still have problems with it – Do you think it might have something to do with the processor being the small KE04 with 8k flash and only 1k RAM?
LikeLike
Possible, but I don’t think so, I have used 8K/1K parts too (but not from the KE series).
LikeLike
when I removed the XF1_xsprintf, I was able to get it work. It then works if I build my output buffer with various UTIL cat functions.
I guess that means the stack was too small. Is there any kind of stack viewer in KDS?
Brynn
LikeLike
If using FreeRTOS, there is a stack viewer and checks for stack overflow. For bare metal there is a static stack analyzer described here: https://mcuoneclipse.com/2015/08/21/gnu-static-stack-usage-analysis/
LikeLike
Hello Erich,
First of all, I’d like to thank you because thanks to you and this page I’m finishing my Final Project for getting an electronical engineer degree, since a part of the project is developed in KDS, and I would be stucked without this page.
Second, I have 3 questions
1) I want to send a command with 4 numbers as parameters like: commandA 1,5,6,7
The way to do it is using strncmp for commandA and then the function ScanSeparatedNumbers?
2)Here: else if (UTIL1_strncmp((char*)cmd, “app val”, sizeof(“app val”)-1)==0) {
p = cmd+sizeof(“app val”)-1;
I don’t get what the -1 is for and what the second line does,
3) I have a warning that says that CLS component doesn’t support clock config 2, but this component supports all speed modes. Should I take care of it?
Thanks for everything
LikeLike
Hi Javier,
thank you for your kind words, and I wish you success with your degree!
1) Yes, you can use the ScanSeparatedNumbers() function.
2) sizeof(“app val”) returns the size of the object, which is for a string *including* the zero byte. the -1 is to not count the zero byte, so only to compare “app val”.
The second line advances the pointer p past “app val”, so points right after that. Again the -1 to compensate for the zero byte (termination byte) in “app val” string.
3) Yes, you can ignore that. or remove the different speed configurations in the CPU component.
I hope this helps?
Erich
LikeLike
Thanks, helped without a doubt
LikeLike