Standalone Semihosting Host-Target Console with CI/CD Runner and CMSIS-DAP

NXP has released a new LinkServer software. It includes an interesting feature. The LinkServer test runner has been extended with a Semihosting console. This is not only very useful for on-target testing. With the Semihosting console, I have a bidirectional communication channel with the target. And I do not need any hardware pins or to run a debug session. All what I need is the CMSIS-DAP connection with the NXP LinkServer runner to have a command line shell:

Target Console with Semihosting
Target Console with Semihosting

Outline

Semihosting is a technology using the debug connection to communicate between the host and the embedded target. One usage is that the target can print messages to host, but it is not limited to that. The target use the file system on the host, or do other things (see Using Semihosting the direct Way). The other advantage is that it does not need any extra wires, except the ones used by the debug interface.

NXP provides a LinkServer ‘runner’ which is great for on-target testing (see On-Target Testing with LinkServer Runner and VS Code). It included semihosting to pass text arguments to the target. With the new v24.12.21 version, the semihosting has been extended. Now it supports bidirectional semihosting text communication. With this, it is easy to implement a console or command line interface to the running application.

Example session

Below is an example how to use it:

LinkServer run --mode semihost --send "help" lpc55s16 "Debug\LPC55S16_Semihosting.axf"

This will start the runner in semihosting mode, and sends the ‘help’ string to the application. The application (see GitHub) is running on the NXP LPC55S16 EVK:

LPC55S16 EVK
LPC55S16 EVK

The application runs a console/shell interface, and the ‘help’ command displays all the available commands:

'help' command output
‘help’ command output

Using the semihosting interface, I have now a bidirectional interface to the target, for example I can query the status:

'status' command output
‘status’ command output

In the next sections, I’ll show the basic building blocks.

Command Line Shell

Below is a simple bare-metal implementation of a command line shell:

 McuSemihost_DefaultShellBuffer[0] = '\0'; McuSemihost_WriteString0((unsigned char*)McuShell_CONFIG_PROMPT_STRING); for(;;) { int i = McuSemihost_ReadLine(McuSemihost_DefaultShellBuffer, sizeof(McuSemihost_DefaultShellBuffer), true); if (i>1 && (McuSemihost_DefaultShellBuffer[i-1]=='\r' || McuSemihost_DefaultShellBuffer[i-1]=='\n')) { McuSemihost_DefaultShellBuffer[i-1] = '\0'; /* remove line end character */ (void)McuShell_ParseWithCommandTableExt(McuSemihost_DefaultShellBuffer, &McuSemihost_stdio, CmdParserTable, true); McuSemihost_WriteString0((unsigned char*)McuShell_CONFIG_PROMPT_STRING); } } 

It initializes the input buffer, writes the command prompt and then waits for data coming from the semihosting interface. The data is read with the McuSemihost_ReadLine() function.

Reading a Line

The main work is done inside McuSemihost_ReadLine() which is part of the McuLib:

 int McuSemihost_ReadLine(unsigned char *buf, size_t bufSize, bool echo) { int c; /* character from semihosting */ int i = 0; /* cursor position in string */ if (bufSize<2) { return 0; /* buffer size needs room for at least two bytes: '\n' and '\0' */ } do { c = McuSemihost_SysReadC(); /* first call is blocking until user presses enter. Then it returns each character on each iteration, until '\n' */ if (echo) { McuSemihost_SysWriteC(c); /* echo the character */ } if (i<bufSize-1) { /* -1 for zero byte at the end */ buf[i++] = c; /* only store into buffer if there is enough space, but continue reading until '\n' */ } } while(c!='\n' && c!='\r'); buf[i] = '\0'; /* zero terminate buffer */ return McuShell_ProcessConsoleInput((char*)buf, bufSize); } 

That function reads in a line from the semihosting interface and returns the number of characters stored in the buffer. A line end is either a ‘\r’ or ‘\n’, and matches what gets entered on the host console. The optional ‘echo’ setting will write back the characters received to the host console.

The last call in the function McuShell_ProcessConsoleInput() is handling special characters.

Handling special Characters

The input data might include ‘\b’ or backspace characters. To deal with this, the following function updates the input text for this:

 int McuShell_ProcessConsoleInput(char *buf, size_t bufSize) { /* - convert '\r' at the end to '\n' * - handle '\b' backspace in input */ char *src, *dst; /* dst is the current cursor in the string, changed as well by '\b' */ size_t len; if (bufSize<2) { return 0; /* buffer size needs room for at least two bytes: '\n' and '\0' */ } src = (char*)buf; dst = (char*)buf; while(*src!='\0') { if (*src=='\r' && *(src+1)=='\0') { *src = '\n'; /* convert '\r' at the end to '\n' */ } if (*src=='\b') { /* backspace */ if (dst!=buf) { /* go back only if not at start */ dst--; /* move cursor backward */ } src++; continue; /* jump to while condition */ } *dst++ = *src++; /* copy char */ } *dst = '\0'; /* terminate resulting string */ len = McuUtility_strlen(buf); if (buf[len-1]!='\n') { /* no line ending? */ McuUtility_chcat((unsigned char*)buf, bufSize, '\n'); } return McuUtility_strlen(buf); } 

The resulting string processed, any backspaces resolved and returns the number of characters in the buffer.

That’s it: I can process the buffer now in the Command Line Shell code presented earlier.

New –args-mark Option

Another useful extension is the ”–args-mark’ command line argument for the LinkServer runner:

--args-mark TEXT Marker to wait for before sending input to application; if not specified the input is sent immediately after starting the application 

In the above example I have used, the LinkServer runner sends the argument text right away to the application. This works fine for buffered semihosting text. In the case of using the UART, the application must first initialize the UART. It also might to do other tasks before it can accept incoming data. In that case, the application first sends the –args-mark marker text (e.g. “start”) to the communication channel, and then receives the text from the runner.

Summary

The new bi-directional interface with the LinkServer Runner allows me to configure and run tests on-target in an interactive way. I can run a test in a manual mode. Using the semihosting interface, I can select a test. Or I can enter any kind of user input to the application without the need for extra hardware or pins.

The other use case is to use the LinkServer Runner with Semihosting as a generic interface to the target system. Instead using a UART connection, I can use the debug pins and implement a console or command line shell interface. Here again: no extra hardware pins are needed.

This makes it an ideal solution during development, as it still requires a debug probe attached. And the solution is very generic, and applicable to ARM Cortex-M MCUs with CMSIS-DAP debug probes.

Are you using semihosting, or consider it? Let me know in the comments!

Happy hosting 🙂

Links

4 thoughts on “Standalone Semihosting Host-Target Console with CI/CD Runner and CMSIS-DAP

    • Hi Rolf,
      yes, similar to RTT (which is what I usually prefer over semihosting, because faster). Both JRun and LinkServer runner are used in our test farm, as both have their unique features. What semihosting makes interesting for on-target testing is that we can use gcov/gprof and other tools which require file I/O, and this can be done with semihosting. JRun does have semihosting, but no file I/O or capabilities, see my ticket #60345038 from 25-Oct-2023. So we use LinkServer runner for the more advanced on-target testing, and using JRun for the test runs with RTT where speed is more of a priority.

      Like

      • Hi Erich,

        Good points.

        We will add semihosting to J-Run, so it can be used with RTT or semihosting, as well as file I/O capabilities, which do make a lot of sense.
        I guess we will also add similar or probably same command line options to make sure J-Run can be used the same way.
        Nice work!

        Like

What do you think?

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