View full example on Github

Abstract

This is a basic terminal example implemented on an Arduino. The purpose is to provide an easy way to be able to GET/SET various settings/registers and execute functions from a terminal program such as RealTerm, Tera Term, or Putty. The basic commands implemented are as follows:

Command Description
Ver

Prints the firmware version to the console.
SettingA

First settings register. Accepts one argument between 0 and 10.
SettingB

Second settings register. Accepts one argument between 0 and 10.
TaskA

First task
TaskB

Second task

 

Settings

No argument being sent is a GET command. Which means that the Arduino will respond to the terminal with whatever value is currently in memory for that setting.

One argument for the setting will overwrite the current value and print one of the responses below.

Response Description
NR

Not Recognized
A

Accepted
E

Error

 

Tasks

Sending the TASK command will toggle to that task and process whatever code you have in the processTasks() function. By toggling, I mean if the current state is IDLE and you send TaskA then it will begin processing TaskA. If the current state is TaskA and you send TaskA then it will return to IDLE. You can switch directly from TaskA to TaskB without returning to IDLE.

Typically, this state machine will be controlled by some timer so it’ll update every 10ms or 100ms but to keep this example simple it’s simply called in the main loop.

Breakdown of the Code

Check the github repo for the full files.

Setup and Main Loop

Our setup is simple. For this example we’re just initializing our state machine and starting our serial. For this example we’re just responding over serial and not performing any other action.

In our main loop we’re constantly checking for incoming data and then processing that incoming data whenever we raise a flag. We’re also executing our state machine.

Incoming Serial Parsing

Our incoming data is handled in two functions. The first being processIncomingSerial() which fills our buffer and processPacket() which deconstructs that packet.

To build our buffer we’re checking to see if there’s any data to read, reading it, and adding it to our buffer with incrementing our index. To detect if a complete packet has arrived we’re looking for some delimiter. For this example I chose the carriage return character 0x0D since it always appears when you hit the enter key. When that value is observed we stuff that position in the buffer with NULL (0x00) to help our next function.

Processing the packet relies on the strtok function. This accepts two arguments. The first being our string that we’re deconstructing and what we’re using for a delimiter. This searches our string until NULL is found, hence us stuffing it in the last entry instead of the carriage return in the previous function. The strtok function is static so if you call it again with NULL as your input string it’ll look for the next entry instead of starting from the beginning. So if we’re expecting a SPACE between the commands and the data being sent then SPACE will be our delimiter. Here I’m just calling it three times to capture the input command and then two arguments. If we’ve reached the end of the string (NULL character) then the strtok calls will just return NULL themselves. Based on what command you sent to the Arduino will determine which handler function is used. Any additional arguments sent will just be ignored.

One additional thing to note is the equalsIgnoreCase. As the name implies it ignores case of the input so it could be lowercase, UPPERCASE, or CaMeLcAsE. Using a couple of if/else statements here is fine if you just have a couple but you could create a list of these and cycle through it to determine which handler function to call. For this example I kept it as simple as possible.

Handler Functions

For the handlers for the settings, I’m just sending all the available arguments. If the argument is NULL then we’re assuming the user wants to retrieve the data so it’s sent down. If it’s not then we convert that string to an int, check it, and then change the global variable. If it’s within bounds we’ll respond with an ACCEPT and if it isn’t then we’ll respond with an ERROR

The tasks are very similar. There’s no arguments for these so we adjust the current state. The below function is for TASK_A so if we’re not in that state we change to it and if we are we return to an idle state.

Tasks and States

Our states are defined globally and just have the two for the tasks and an idle.

The state machine for this is called in the main loop constantly. Typically, this will be driven by a timer but for brevity and learning purposes it’s just called every loop. The state machine here is just a case statement. To observe the different states you can uncomment the debug code in the processTasks() function.

Leave a Reply

Your email address will not be published. Required fields are marked *