Back to top

Discover How to Build a Clean and Simple Command Line Interface in an RTOS Environment

WEBINAR RECORDING ON-DEMAND

Join us for an insightful webinar where Peifang Zhou, a Senior Embedded Software Designer with 30 years of experience, shares his expertise on designing a clean and simple command line interface (CLI) in an RTOS environment. This presentation, also featured at the Embedded Online Conference 2024, delves into the architectural design and practical implementation of an effective CLI. The webinar includes a detailed discussion on the three main functional blocks: terminal utility program, UART ISR, and console task, and demonstrates the implementation on a low-cost STM32 Nucleo board.

  • Speaker: Peifang Zhou, Senior Embedded Software Designer, Fidus Systems
  • Date Published: May 3, 2024
  • Duration: 10 Minutes

Key Topics Covered:

  • Object-Oriented CLI Design: Transition from traditional character-based CLI to an object-oriented approach for cleaner, simpler design.
  • Modular Functional Blocks: Break down CLI functionalities into distinct blocks for enhanced scalability and maintainability.
  • Unidirectional Data Flow: Simplify design by ensuring data flows in a single direction, improving clarity and predictability.
  • Single Responsibility Principle: Assign clear, single responsibilities to each functional block to prevent convoluted implementations.
  • Practical Implementation: Step-by-step guide to implementing the CLI on an STM32 Nucleo board with live coding examples.

Additional Resources

  • Learn more about Fidus’ embedded software expertise here.
  • Stay at the forefront of technology by participating in our upcoming series of webinars that cover innovative topics in the tech industry. Visit our webinar series page for more information and to register.
  • Learn how FPGA-based prototyping can accelerate time-to-market for embedded systems, providing flexibility and cost efficiency during the design phase.
  • Understand the key principles and techniques for improving signal integrity in high-speed PCB designs. This resource offers practical tips and real-world examples.
  • Explore how AMD Versal ACAP technology can be leveraged for next-generation embedded system designs, offering unmatched performance and flexibility.

Webinar Transcript

0:00 Introduction

Hello, everyone, and welcome to the workshop on building a clean and simple command line interface in an RTOS environment. Without any further delay, let’s jump right into it with today’s agenda.

0:17 Agenda

We will start off with a brief introduction to highlight the importance of command line interface embedded software development. We will then move on to the architectural design of the proposed command line interface. It is clean, simple, and quite different from what you have seen before. We will take a look at the questions that you might have in the chat window and address them right away. Understanding the architecture upfront is essential. Once we have a good grasp of the underlying architecture, its implementation becomes straightforward.

1:17 Design and Implementation

After you have seen the design and its implementation, we step back and reflect on what makes these clean and simple. I’ll share my insights along the line of design patterns. The main purpose of my presentation is to enable you to do clean and simple design in your real-world projects. We will have a live Q&A afterwards, and I look forward to a lively discussion on the CLI design in particular and doing clean and software design in general.

1:27 Importance of CLI

CLI plays a crucial role in interacting with embedded systems. They allow developers and users to communicate with the system through text-based commands, whether it’s configuring settings, diagnosing issues, or monitoring system behavior. Well-designed CLIs can significantly enhance the usability and maintainability of an embedded application. Here’s a slide from one of last year’s presentations, and it captured the essence of the fundamental role that CLI plays in developing embedded applications.

2:07 CLI Requirements

To set up the context for our CLI design, let’s first look at the CLI requirements as shown here. It’s not intended to be all-inclusive, just highlighting the key ones: 115200-8-N-1​rate, text-based commands and responses including error handling, use of a prompt to enter new commands, etc. It will be really nice to have command auto-completion and access to a command history list. We will see them in action during the implementation walkthrough.

2:43 Key Differences

Okay, what’s the key difference between the CLI design here and the ones you’ve seen before? The nature of transmitting a command character by character over a serial interface lends itself naturally to a character-based design. The crux of our clean and simple design is to move away from this traditional character-based thinking and treat each console command as an immutable object. The shift from character to object leads to a much cleaner and simpler design. Simply put, the design presented here is not character-based; it is object-oriented. The keyword is object.

Design Approach

The second part of our design is the standard one: divide and conquer. We will distribute CLI functionalities among a series of functional blocks and watch the movement of a command object from one functional block to the next. In this setup, we will have three functional blocks.

Functional Blocks

The first one is a terminal utility program running on your host computer. Our choice is Termite as it meets the requirements of building command objects and supports command line auto-completion and history. Its sole functionality is to assemble characters to build a command object. I know this sounds more and more like C++: it is indeed the constructor of any command object in the sense that no character will come out of this functional block until the command object is fully assembled. Once again, think about objects, not individual characters.

The second functional block is your ISR. The interrupt service routine for the serial port has the sole functionality of assembling command objects from serialized data over the console interface. So, it is a De-serializer. Once a command object is reassembled, it will notify a console task of the availability of such a command object via a semaphore.

The third functional block is a console task. It acts as a dispatcher to find and run the appropriate handler function, which in turn notifies the task designated to process the command by depositing a message in the task mailbox.

Block Diagram Overview

Here’s a block diagram to illustrate both control flow and data flow in terms of how a console command is handled as an object. Once a user enters L, E, D, space O, N, and presses the Enter key, the command object, as shown here, is assembled and sent out by Termite to your board over a serial interface. The UART ISR will reassemble the command object, as shown here, and notify the console task using a semaphore. The console task could have forwarded the entire command object directly to the LED task that is responsible for turning on, off, or toggling the LED.

Command Parsing

After one more task is added to your system to handle a different command, it will become obvious that they all need to look at the verb within the current command object and act on it. So, we will factor out this common functionality of word parsing and give it to the console task. Note that parsing does not alter the command object itself; it merely changes its representation from “LED ON” to standard argc argv format. The console task then finds and calls a handler function, which in turn deposits a message into the mailbox of the LED task.

Implementation Walkthrough

We will come back to this block diagram and share a lot of light on how to do clean and simple designs. For the implementation walkthrough, we’ll do it in a step-by-step fashion.

  1. Step 1: The first step is to set up LED, UART, FreeRTOS and printf() as a sanity test to make sure that the demo board we’re going to use is functional.
  2. Step 2: The second step is to set up the UART ISR as a Deserializer and add a console task with a simple echo-back functionality.
  3. Step 3: After that, we will set up command object parsing in the console task.
  4. Step 4: Finally, we will set up the command handler functionality in the console task and an LED task responsible for turning the LED on, off, or toggling it.

Demo Setup

Here’s our demo setup. We have a Windows computer at one end and an STM32 Nucleo board at the other end of a serial interface. ASCII-based commands and responses will fly back and forth between the Windows computer and the STM32 Nucleo board. Now, we have seen the power of clean and simple CLI design.

General Design Principles

If we step back and ask ourselves a question: how are we to make clean and simple designs in general? There are many aspects we could look at. My insight is along the lines of a design pattern. There are two key ingredients.

  1. Unidirectional Data Flow: Once your data flows in both ways, it will immediately introduce complexity.
  2. Single Responsibility: If each individual functional block or C++ class acts as a kitchen sink for everything, we make its implementation convoluted and messy.

Conclusion and Q&A

With this note, we’ve come to the end of our presentation, and now we can start our Q&A.

Speaker

Peifang Zhou, Senior Embedded Software Designer

Peifang Zhou, a Senior Embedded Software Designer with 30 years of experience, is renowned for his expertise in C/C++, Intel/Xilinx SoC FPGA, MCU, and RTOS. Throughout his career, Peifang has consistently demonstrated a deep understanding of embedded software design principles, focusing on creating efficient, maintainable, and scalable solutions. His extensive experience and innovative approach have made him a sought-after speaker and thought leader in the field of embedded systems.