Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Tutorial: Bare-Metal Shell for Kinetis

DZone's Guide to

Tutorial: Bare-Metal Shell for Kinetis

Erich Styger presents a neat bare-metal command line shell example app for Kinetis.

· IoT Zone
Free Resource

Build an open IoT platform with Red Hat—keep it flexible with open source software.

I have been asked to provide a command line (shell) example for a bare-metal (no Real-t Operating System) 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  shell to the NXP Freedom board, which then can be extended as necessary.

Console Application

Figure 1: Console Application

Outline

I’m using the Eclipse NXP Kinetis Design Studio v3 with an 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

Figure 2: 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

Figure 3: 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

Figure 4: 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

Figure 5: UART Configuration

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

Figure 6: Console Interface in Shell

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

Shell Functions

Figure 7: 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 message 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

Figure 8: 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

Figure 9: 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

Figure 10: 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!

Download Red Hat’s blueprint for building an open IoT platform—open source from cloud to gateways to devices.

Topics:
eclipse ,xml

Published at DZone with permission of Erich Styger, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}