I needed non-blocking serial transmit for a project I'm working on and so I got the following code working tonight. Eventually it'll turn into a library but for now it's straight out of the incubator - all in one file with it's test program. The hardest part was understanding how to get ring_buffer working.
To make it work you have to poll the update() method in loop which means that nothing in your main loop can block either. A much more elegant solution would be to make it interrupt driven but I'm not ready to climb that mountain yet.
Use, comment, improve, or flame as desired. ;)
// ********************************************************************************
// begin future nbSerialTX library code
//nbSerialTx.h
/*
* nbSerialTX - non-blocking Serial Transmit library
* by tdc218@sbcglobal.net
* 2011-Oct-28
*
*/
#include "ring_buffer.h"
#include "usart.h"
#ifndef _NBSERIALTX_H_
#define _NBSERIALTX_H_
#define NBS_BLOCKONFULL 13
#define NBS_DISCARDONFULL 218
class nbSerialTx
{
public:
nbSerialTx(usart_dev *);
~nbSerialTx();
void init(char *, int);
void init(char *, int, int);
void update(void);
void nbputc(char);
void nbputs(char *);
int space(void);
private:
usart_reg_map *xregs;
int bufsize;
ring_buffer rb;
int blockflag;
};
#endif
// ********************************************************************************
// nbSerialTx.cpp
//#include "nbSerialTx.h"
nbSerialTx::nbSerialTx(usart_dev *device) {
this->xregs = device->regs;
this->bufsize = 0;
// this->rb = NULL; // compiler doesn't like this
blockflag = NBS_DISCARDONFULL;
}
nbSerialTx::~nbSerialTx() {
rb_reset(&rb);
this->xregs = (usart_reg_map *)NULL;
this->bufsize = 0;
// this->rb = NULL; // compiler doesn't like this
}
void nbSerialTx::init(char *buffer, int buffer_size) {
this->bufsize = buffer_size;
rb_init(&rb, buffer_size, (uint8 *)buffer);
}
void nbSerialTx::init(char *buffer, int buffer_size, int block) {
this->bufsize = buffer_size;
rb_init(&rb, buffer_size, (uint8 *)buffer);
blockflag = block;
}
void nbSerialTx::update(void) {
if (xregs->SR & USART_SR_TXE) // if uart transmitter is ready for another character
if (!(rb_is_empty(&rb))) // if there is a character to send
xregs->DR = rb_remove(&rb); // then pull it from the buffer and send it
}
//
// Send one character
// if blockflag is NBS_BLOCKONFULL then the function will block when the buffer gets full
// otherwise the character will be discarded if the buffer is full
//
void nbSerialTx::nbputc(char c)
{
if (blockflag == NBS_BLOCKONFULL)
while (!rb_safe_insert(&rb, (uint8)c)); // wait for buffer to be not full before inserting
else
(void)rb_safe_insert(&rb, (uint8)c); // will only insert if buffer is not full
}
//
// send a string
//
// If you want to print numbers or other fancy stuff, use sprintf, eg:
// #include <stdio.h>
// char buf[SIZE]; // you define SIZE to something larger than the longest string you're likely to run into
// sprintf(buf, "x=%04.3f address=%04x\n", float_var, uint16_var);
//
void nbSerialTx::nbputs(char *s)
{
char c;
while ((c = *s++))
nbputc(c);
}
/*
* space
* returns the number of bytes available in the buffer
*
*/
int nbSerialTx::space(void)
{
return this->bufsize - rb_full_count(&rb);
}
// end of future library code
// ********************************************************************************
// begin main
// Assumes a Gravitech SLCD-3 serial LCD on Serial 2
//
#include <stdio.h>
#define MAXMYS2BUF 128
nbSerialTx mys2 = nbSerialTx(USART2);
char mys2buf[MAXMYS2BUF]; // My serial 2 transmit buffer (wouldn't need this if we had malloc)
unsigned int sendMillis = 0; // store the last time serial test was sent
unsigned int sendInterval = 50; // interval at which to send serial text
unsigned int previousBlinkMillis = 0; // will store the last time the LED was updated
unsigned int Blinkinterval = 500; // interval at which to blink (in milliseconds)
void setup(void)
{
// Set up the built-in LED pin as output:
pinMode(BOARD_LED_PIN, OUTPUT);
// Seed the random # generator
pinMode(28, INPUT_ANALOG);
randomSeed(analogRead(28));
Serial2.begin(9600);
Serial2.print("?f"); // Gravitech SLCD-3 clear screen command
delay(100);
Serial2.print("?g"); // beep the LCD's beeper
delay(100);
Serial2.print("Begin test ");
Serial2.print(random(100), DEC); // print a random number so I can tell it really restarted
Serial2.print("?n"); // stupid LCD doesn't understand real newlines
// initialize
mys2.init(mys2buf, MAXMYS2BUF);
// send a string to the non-blocking routines
mys2.nbputs("Hello,world?n");
}
void loop(void)
{
unsigned int currentMillis;
static unsigned int variation = 0;
static char spbuf[32];
currentMillis = millis();
mys2.update(); // update the non-blocking serial transmit
//
// blink the LED for something to do
//
if (currentMillis - previousBlinkMillis >= Blinkinterval) {
// Save the last time you blinked the LED
previousBlinkMillis = currentMillis;
// If the LED is off, turn it on, and vice-versa:
toggleLED();
}
//
// spit out currentMillis to the serial port at intervals with some random variation
// The LED should still blink at the correct intervals
//
if ((currentMillis - sendMillis) >= (sendInterval + variation)) {
sendMillis = currentMillis;
variation = 10 - random(20);
sprintf(spbuf,"%08x?n", currentMillis);
mys2.nbputs(spbuf);
}
}