Tutorial: Bare-Metal Shell for Kinetis

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.

Console Application

Console Application

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.

Project Created

Project Created

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:

Serial Interface for Shell

Serial Interface for Shell

This mean we are going to use the Serial (UART) on the board. The ‘Serial’ is a pre-configured ‘AsynchroSerial’ component.

Components Added

Components Added

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:

UART Configuration

UART Configuration

💡 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:

Console Interface in Shell

Console Interface in Shell

The Shell component offers many functions to read/write to the console:

Shell Functions

Shell Functions

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:

Hello World

Hello World

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:

Session

Session

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:

Demo Application

Demo Application

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

45 thoughts on “Tutorial: Bare-Metal Shell for Kinetis

  1. Pingback: A Shell for the Freedom KL25Z Board | MCU on Eclipse

  2. 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?

    Like

    • 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 😦

      Like

  3. [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?

    Like

  4. 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

    Like

    • 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

      Like

    • 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

      Like

      • 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

        Like

  5. 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

    Like

  6. 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

    Like

  7. 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

    Like

    • 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)?

      Like

      • 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?

        Like

      • 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()

        Like

      • Hi Erich,

        I just noticed, that after running PE_low_level_init(); line PRIMASK register becomes 0x1. Could this be an issue?

        Like

        • 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.

          Like

        • 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

          Like

  8. 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

    Like

  9. 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] ;

    Like

  10. 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

    Like

  11. 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

    Like

    • 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

      Like

      • 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

        Like

  12. 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

    Like

  13. 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?

    Like

  14. 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

    Like

    • 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

      Like

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.