Jump to content

a simple Command Stack

Recommended Posts

I'm working on a project that is going to involve a device relaying out sensor data while also listening for incoming commands all over UART.  To keep it mostly asynchronous, I decided to write a simple command stack structure.


The idea came from the way you typically receive data over the UART...one character at a time.  The usual formula is to keep reading characters into a buffer until you hit a delimiter, then set a flag to tell the state machine to process the buffer as a command...however this approach blocks the UART, as you have to wait for the state machine to at least copy the command off somewhere before you can allow more data to be written, otherwise the command is corrupted.  Another situation would be a wireless uart, where (power wise) it is better to wake the radio up and send a burst of traffic then put it to sleep, instead of letting the data trickle out.


The stack is composed of a stack of buffers.  One buffer is considered the 'active' buffer, and is the one that is actively written to.  Once a full command is written to the active buffer, it is 'committed', which flags the buffer as ready to be processed and moves on to the next buffer in the stack to start writing again.  In this fashion, you can store a whole list of commands and process them how you see fit, when you see fit.  Each buffer also has an associated 'status' flag, that currently indicates when a buffer is 'ready' to be processed and when a buffer has been overflowed (so the processing code can handle the error), but this can be extended to add other features like command priority.


The basic stack structure looks like this:

typedef struct
	char buffer[STACK_LEN][BUFFER_LEN + 1];		//buffer data store.
	uint8_t status[STACK_LEN];                      // 0x1 = process status; 0x2 = overflow status
	uint8_t stack_index;				//which buffer are we writing to
	uint8_t buffer_index[STACK_LEN];		//which element of the buffer


the meat of the stack is a 2D char array.  The data is organized into  <STACK_LEN> buffers, each <BUFFER_LEN +1> long...the +1 is to allow the buffer to be silently zero terminated.  The idea is that this structure keeps track of itself so the processing code doesn't have to.


'stack_index' indicates which buffer should be written to.


'buffer_index' keeps track of which index in each buffer is the 'end' of the buffer.  Since this acts as a ring buffer, buffers are just overwritten.  Clearing the buffer just involves resetting buffer_index to 0. 


'status' keeps track of the status of each buffer.  Currently there are two implemented options:  the first bit is the 'process status' flag, which indicates if the buffer is ready to be read and processed by other code.  the second is an 'overflow' or 'error' flag.  As data are written to the buffers, if the buffer is overflowed, it just stops writing and sets the overflow flag.  This lets the processing code check and handle the condition gracefully.


To avoid posting a huge® wall of text, I will just cover the basic usage and leave the source up on github:  https://github.com/mjbeals/commandStack


To initialize everything, you just create a command_stack and call buffer_int():

command_stack test_stack;

This creates the stack and initializes it to default values (all zeros).


Writing data into the current active buffer can be char at a time or string at a time:

write_char(&test_stack, 'C');

write_string(&test_stack, "test");

Once a full command is written, it is time to commit it, which marks it as ready and rolls the stack to the next buffer


you can also advance the stack without setting it as 'ready' (in case there was an error, or the buffer should just be discarded) as well as modify the 'ready' status of any buffer


void flag_buffer(&test_stack, 0, 1);  //flag buffer 0 to READY

To read back out the buffers, I currently only have two methods written.  The first will just dump out the currently active buffer.  The second will dump out any arbitrary buffer.  Since ideally we just commit a bunch of commands, then let the state machine process those that are ready (FIFO style), there is also a function to find the oldest ready buffer.

char buffer[BUFFER_LEN+1];

//read the current 'active' buffer
uint8_t num_chars = read_current_buffer(&test_stack, buffer);

// find the index of the oldest 'ready' buffer and then read it
int next_index;

uint8_t isValid  = oldest_ready(&test_stack, &next_index);
uint8_t num_chars;

if (isValid)
   num_chars = read_buffer(&test_stack, buffer, next_index);

So using this framework, here is a simple uart interrupt handler:

#pragma vector = USCIAB0RX_VECTOR		
__interrupt void USCI0RX_ISR(void)
	char data = UCA0RXBUF;		//Copy from RX buffer, in doing so we ACK the interrupt as well

	//if it is a line term char, roll the stack
	if(data == 0x0D) commit_buffer(&rx_stack);

This will just sit there and fill the stack with commands delimited by 0x0D (it's what XBee radios use for their AT commands, which is the heart of this code).



then with a function like:

int8_t oldest_RX(char* buf)
	uint8_t index;

        // if there is no buffer flagged 'ready', return 0
	int8_t ready =  oldest_ready(&rx_stack, &index);
	if (ready == 0) return 0;

        //read the command off the buffer into 'buf' and mark the buffer processed
	read_buffer(&rx_stack, buf, (uint8_t) index);
	flag_buffer(&rx_stack, index, 0);
	return 1;

the state machine can just 

int rxstat = 0;
char buffer[BUFFER_LEN+1];
  rxstat = oldest_RX(buffer);   //if there is a buffer to process, rxstat = 1, command in buffer
//if there is an item on the stack to process

  if (rxstat)
     process_command(buffer);  //arbitrary function to process the command

The github repo also has sample UART code with this integrated.  A basic main loop to loop back received serial data is:

   char buffer[BUFFER_LEN+1];
   int rxstat = 0;
      rxstat = oldest_RX(buffer);
      //if there is an item on the stack to process
      if (rxstat)
         //queue up the incoming command back to the TX stack and send

It uses some of the new functions I wrote for the uart that I didn't discuss.  They are documented in source, but I may write them up later once I have a chance to refine them.


As it stands, the code works for the test cases I've run, but it is in very alpha stage still.  I'm going to be refining it and adding on to it over the next few weeks especially as I wrap up this project.  One of the first additions is for the 'advance_stack' function to roll to the next (oldest) buffer that does not have a 'ready flag'.


I'm completely open to comments, questions, criticism, etc.  I'm hoping to turn this into my 'go to' library for handling serial on the MSP's as I seem to find myself rewriting things from scratch each time I want to use a UART.




Link to post
Share on other sites
  • 2 weeks later...

Yeah, this is a fairly common approach.  Often it is called double buffering or XY buffering.  So, you're not doing anything crazy that isn't going to work :smile:


Personally, I prefer using packets for the command pipe (e.g. a UART).  The reason is that it is better for chip-to-chip communications, even though the tradeoff is that you will need something more than a dumb TTY.  we whipped-up a Qt-based console app for that.  If you want to use NDEF formatting, the app is called OTcom and you can probably find it online.

Link to post
Share on other sites

TTY is traditionally a character-based interface.  There are special characters that cause certain functions.  A packet-based interface uses packets, not characters, typically multiple bytes long and with some order/organization to them.  You cannot easily access a packet-based protocol directly on a TTY shell, which is a downside, but the upside is that it is a lot easier for the embedded device to implement.

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Create New...