mbeals 74 Posted July 1, 2013 Share Posted July 1, 2013 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 }command_stack; 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; buffer_init(&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 commit_buffer(&test_stack); 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 advance_stack(&test_stack); 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 write_char(&rx_stack,data); //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]; while(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; while(1) { 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 enque_buffer(buffer); process_tx(1); } } 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. Automate 1 Quote Link to post Share on other sites
jpnorair 340 Posted July 9, 2013 Share Posted July 9, 2013 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 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. Quote Link to post Share on other sites
mbeals 74 Posted July 9, 2013 Author Share Posted July 9, 2013 What do you mean by using packets for the command pipe? can you elaborate? Quote Link to post Share on other sites
jpnorair 340 Posted July 10, 2013 Share Posted July 10, 2013 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. Quote Link to post Share on other sites
mbeals 74 Posted July 10, 2013 Author Share Posted July 10, 2013 Oh I'm following you now. That's exactly what I'm going to do for the actual project. It makes no sense to convert numeric data to ASCII, only to convert it back on the other end. Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.