Skip to content

Instantly share code, notes, and snippets.

@RickKimball
Last active December 12, 2015 10:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RickKimball/4758580 to your computer and use it in GitHub Desktop.
Save RickKimball/4758580 to your computer and use it in GitHub Desktop.
sw_serial_t - blocking software only cycle counting msp430 serial routines for Energia

readme.md - sw_serial_t template based UART code for Energia

Yet another software only serial class implementation? What makes this one different? C++ template based with asm routines to implement read and write. Works with any pin and port combination. You can mix and match, put read on P2.0 and write on P1.2. Small size even with Energia. Multiple instances allowed. Each instance uses about 12 bytes so you are only limited by the number of pins and memory on your chip. Big plus, doesn't use the timer. Read on if you this peaks your interest.

This code implements a C++ template based software only blocking serial UART class. It doesn't use the timer peripheral so it can be used with other code that might want to monopolize the timer(s). The read/write routines are based on oPossum's CCS msp430 asm based cycle counting serial routines. They have been generalized for use with Energia, C++ templates, msp430-gas abi asm, modified to support any port and pin combination, and will allow for multiple serial instances.

The code does use some instance data (~12 bytes) but you can have multiple sw_serial_t class instances. You might find this useful if you have have to interact with more than one serial device. This isn't a library so much as a header file you include in your project. The code weighs in at about 1200 bytes for a typical ASCII table print. A serial.print("Hello World\n") is only about 560 bytes. Useful things can even be done on the MSP430G2231 chip using this code.

I've successfully tested output to 230400 baud running the DCO clock at 16MHz. You won't be able to achieve that baud rate running at 1MHz. Note: I did calibrate my DCO clock to be to 16.00MHz before trying to run that fast.

I've not stressed the input side so I can't say what the limits are for input. Needless to say, it will all depend on the input and how you attempt to process the input data.

You might want to snag a couple of those $3 pl2303x usb modules from ebay. That is what I used to test at high speed.

The sw_serial_t template class inherits from the Stream base class. That means that all the normal print() methods are available for use as you would do with any other Serial class implementation. In addition, I've included the Streaming class from http://arduiniana.org/libraries/streaming/ This allows C++-style output with the "<<" operator.

See the example '.ino' files for information on how to actually instance the sw_serial_t template.

Some caveats:

1.) This code uses cycle counting. That means when you use the write() or read() methods, they disable the watchdog timer. Disabling the watchdog in Energia means the millis() count is going to be wrong. I guess an improvement would be to update the millis counter based on the baud rate but I haven't done that. You have been warned!

2.) This code implements blocking routines. If you do a read() it will block until a character arrives. If you do a write() it will block until the bits have been shifted out. I take care of disabling the watchdog timer. However, if you have timer interrupt routines they will break this code. You will have to make sure you disable the timer interrupts while doing serial IO or pick a time when the timer code isn't running.

3.) This code doesn't use a ring buffer. So, expect it to to drop characters if you receive a long stream of characters and you try to do too much in between the read() calls. If you need to do something with a stream of characters, receive each byte and put it in your own buffer then process the buffer when you detect a new line. Some simple solutions to dealing with blocking reads: a.) use a slower host BAUD rate. b.) add extra stop bits from the host side to give yourself more time between received bytes. c.) preface the stream of bytes with a length so your firmware will know how many bytes to receive before it tries to process them.

4.) The more features of the Print class you use the larger your code will grow. If you really only want multiple serial instances then just use the read() and write() methods for the smallest code. Avoid the print() methods and you will acheive the smallest implementation.

This code is implemented as templates and doesn't use the Arduino style pin number table runtime lookups. Pin information must be available at compile time so the asm routines can do the right thing. However, using templates reduces the size of this code and makes it more flexible. Look at the ssgpio.h for more information about the pin configuration. It is only setup for P1 and P2. I'm assuming people who want to use this are probably going to use it on the G series DIP chips. You will have to add entries for anything other than that.

-rick kimball

/*
* aciitable - example of using the blocking sw_serial_t template
*/
#include "streaming.h" /* pull in insertion operator overloads */
#include "sw_serial.h"
// typedefs for software only rx/tx using software pin settings * J3 default jumper settings
// this is a good setting for the g2452 and g2231
sw_serial_t<9600, SS_P1_2, SS_NO_PIN> swserial; // HW jumper version TX=P1.2, RX=NOT CONNECTED
void setup() {
swserial.begin(9600); // speed doesn't matter set above in template
swserial.print(" ------ ASCII TABLE -----\n");
}
int thisByte=' ';
void loop() {
swserial << "'" << _BYTE(thisByte) << "'"
<< " dec=" << _DEC(thisByte)
<< ", hex=" << _HEX(thisByte)
<< ", bin=" << _BIN(thisByte)
<< endl;
thisByte++;
if ( thisByte > '~' ) {
while(1);
}
}
/*
* example.ino - example of using the blocking sw_serial_t template
*/
#include "streaming.h" /* pull in insertion operator overloads */
#include "sw_serial.h"
sw_serial_t<9600, SS_P1_2, SS_P1_1> swserial; // HW jumper version TX=P1.2, RX=P1.1
sw_serial_t<4000, SS_P2_6> xmitonly; // sample of how to use xmit only and not mess with rx pin
void setup() {
swserial.begin(9600);
swserial.print(" ------ ASCII TABLE -----\n");
xmitonly.begin(4000);
P1DIR |= BIT4; P1SEL |= BIT4;
}
int thisByte=' ';
void loop() {
swserial << "'" << _BYTE(thisByte) << "'"
<< " dec=" << _DEC(thisByte)
<< ", hex=" << _HEX(thisByte)
<< ", bin=" << _BIN(thisByte)
<< endl;
xmitonly.print("U"); // spew U on second P2.6
thisByte++;
if ( thisByte > '~' ) {
thisByte=' ';
}
delay(100);
}
/*
* hello_world - simple example using sw_serial_t
*
* size: ~580 bytes
*/
#include "streaming.h" /* pull in insertion operator overloads */
#include "sw_serial.h"
sw_serial_t<9600, SS_P1_2, SS_NO_PIN> swserial; // HW jumper version TX=P1.2, RX=NOT CONNECTED
void setup() {
swserial.begin(9600); // speed doesn't matter set above in template
swserial << "Hello world!" << endl;
}
void loop() {
}
/*
* ssgpio.h - minimal template abstraction for port and pins to support sw_serial_t
*
* Author: rick@kimballsoftware.com
* Date: 02-12-2013
* Version: 0.001
*/
#ifndef _GPIO_H_
#define _GPIO_H_
typedef volatile uint8_t & u8_SFR; /* 8 bit unsigned Special Function Register reference */
typedef const volatile uint8_t & u8_CSFR; /* 8 bit unsigned Constant SFR reference */
struct PORT_P1 {
static u8_CSFR pin() { return P1IN; }
static u8_SFR pout() { return P1OUT; }
static u8_SFR pdir() { return P1DIR; }
static u8_SFR psel() { return P1SEL; }
#ifdef P1SEL2_
static u8_SFR psel2() { return P1SEL2; }
#else
static u8_SFR psel2() { return P1SEL; }
#endif
};
struct PORT_P2 {
static u8_CSFR pin() { return P2IN; }
static u8_SFR pout() { return P2OUT; }
static u8_SFR pdir() { return P2DIR; }
static u8_SFR psel() { return P2SEL; }
#ifdef P2SEL2_
static u8_SFR psel2() { return P2SEL2; }
#else
static u8_SFR psel2() { return P2SEL; }
#endif
};
template <uint8_t mask, typename PORT>
struct PIN_DEF {
static const uint8_t pin_mask = mask;
static u8_CSFR pin() { return PORT::pin(); }
static u8_SFR pout() { return PORT::pout(); }
static u8_SFR pdir() { return PORT::pdir(); }
static u8_SFR psel() { return PORT::psel(); }
static u8_SFR psel2() { return PORT::psel2(); }
};
typedef PIN_DEF<0,PORT_P1> SS_NO_PIN;
typedef PIN_DEF<BIT0,PORT_P1> SS_P1_0;
typedef PIN_DEF<BIT1,PORT_P1> SS_P1_1;
typedef PIN_DEF<BIT2,PORT_P1> SS_P1_2;
typedef PIN_DEF<BIT3,PORT_P1> SS_P1_3;
typedef PIN_DEF<BIT4,PORT_P1> SS_P1_4;
typedef PIN_DEF<BIT5,PORT_P1> SS_P1_5;
typedef PIN_DEF<BIT6,PORT_P1> SS_P1_6;
typedef PIN_DEF<BIT7,PORT_P1> SS_P1_7;
typedef PIN_DEF<BIT0,PORT_P2> SS_P2_0;
typedef PIN_DEF<BIT1,PORT_P2> SS_P2_1;
typedef PIN_DEF<BIT2,PORT_P2> SS_P2_2;
typedef PIN_DEF<BIT3,PORT_P2> SS_P2_3;
typedef PIN_DEF<BIT4,PORT_P2> SS_P2_4;
typedef PIN_DEF<BIT5,PORT_P2> SS_P2_5;
typedef PIN_DEF<BIT6,PORT_P2> SS_P2_6;
typedef PIN_DEF<BIT7,PORT_P2> SS_P2_7;
#endif
/*
Streaming.h - Arduino library for supporting the << streaming operator
Copyright (c) 2010-2012 Mikal Hart. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
02-11-2013 - Modified for Energia rick@kimballsoftware.com
*/
#ifndef ARDUINO_STREAMING
#define ARDUINO_STREAMING
#include "Energia.h"
#define STREAMING_LIBRARY_VERSION 5
// Generic template
template<class T>
inline Print &operator <<(Print &stream, T arg) { stream.print(arg); return stream; }
struct _BASED {
long val;
int base;
_BASED(long v, int b): val(v), base(b) {}
};
#if ARDUINO >= 100
struct _BYTE_CODE {
byte val;
_BYTE_CODE(byte v) : val(v) {}
};
#define _BYTE(a) _BYTE_CODE(a)
inline Print &operator <<(Print &obj, const _BYTE_CODE &arg) { obj.write(arg.val); return obj; }
#else
#define _BYTE(a) _BASED(a, BYTE)
#endif
#define _HEX(a) _BASED(a, HEX)
#define _DEC(a) _BASED(a, DEC)
#define _OCT(a) _BASED(a, OCT)
#define _BIN(a) _BASED(a, BIN)
// Specialization for class _BASED
// Thanks to Arduino forum user Ben Combee who suggested this
// clever technique to allow for expressions like
// Serial << _HEX(a);
inline Print &operator <<(Print &obj, const _BASED &arg) { obj.print(arg.val, arg.base); return obj; }
#if ARDUINO >= 18
// Specialization for class _FLOAT
// Thanks to Michael Margolis for suggesting a way
// to accommodate Arduino 0018's floating point precision
// feature like this:
// Serial << _FLOAT(gps_latitude, 6); // 6 digits of precision
struct _FLOAT
{
float val;
int digits;
_FLOAT(double v, int d): val(v), digits(d) {}
};
inline Print &operator <<(Print &obj, const _FLOAT &arg)
{ obj.print(arg.val, arg.digits); return obj; }
#endif
// Specialization for enum _EndLineCode
// Thanks to Arduino forum user Paul V. who suggested this
// clever technique to allow for expressions like
// Serial << "Hello!" << endl;
enum _EndLineCode { endl };
inline Print &operator <<(Print &obj, _EndLineCode arg)
{ obj.println(); return obj; }
#endif
/*
* sw_serial_t - blocking bit bang cycle counting UART routines with Print support
*
* Author: rick@kimballsoftare.com
* Date: Nov-12-2012
* Version: 0.001
*
* Acknowledgments:
* Inspiration for cycle counting RX/TX routines from Kevin Timmerman.
* see: http://forum.43oh.com/topic/1284-software-async-serial-txrx-without-timer/
* Serial API inspired by Arduino.
*/
#ifndef _SW_SERIAL_H
#define _SW_SERIAL_H
#include "ssgpio.h"
template <uint32_t BAUD, typename TXPIN, typename RXPIN=SS_NO_PIN, uint32_t MCLK_HZ=F_CPU>
class sw_serial_t : public Stream
{
//--------------------------------------------------------------------------------
private:
template <uint16_t DUR, uint16_t DUR_HALF>
struct BITRATE {
enum {
dur=DUR,
dur_half=DUR_HALF
};
};
enum { LOOP1_OVERHEAD=16, LOOP2_OVERHEAD=32 }; /* see asm code */
static const BITRATE<((MCLK_HZ/BAUD)-LOOP1_OVERHEAD) << 1,(MCLK_HZ/BAUD)-LOOP2_OVERHEAD> bit_rate;
int _read(); /* asm read routine */
void _write(const unsigned c); /* asm write routine */
//--------------------------------------------------------------------------------
public:
/**
* begin() - initialize TX/RX pins
*/
void begin(uint32_t baud=BAUD) {
if ( TXPIN::pin_mask ) { /* only modify real pins SS_NO_PIN */
TXPIN::pout() |= TXPIN::pin_mask;
TXPIN::pdir() |= TXPIN::pin_mask;
TXPIN::psel() &= ~TXPIN::pin_mask;
TXPIN::psel2() &= ~TXPIN::pin_mask;
}
if ( RXPIN::pin_mask ) {
RXPIN::pdir() &= ~RXPIN::pin_mask;
}
}
/*
* end() - set TX pin back to default
*/
void end(void) {
if ( TXPIN::pin_mask ) {
TXPIN::pout() &= ~TXPIN::pin_mask;
TXPIN::pdir() &= ~TXPIN::pin_mask;
}
}
/*
* write() - provide our implementation for virutal Print::write() class
*/
virtual size_t write(uint8_t c) {
disableWatchDog();
_write(c);
enableWatchDog();
return 1;
}
/*
* read() - provide our implementation for virtual Print::read() class
*/
virtual int read(void) {
disableWatchDog();
int c = _read();
enableWatchDog();
return c;
}
virtual void flush(void) {}
virtual int peek(void) { return -1; }
virtual int available(void) { return 0; }
using Print::write;
};
/*
* serial_base_sw_t<>::_write() - write one byte to the TX pin
*/
template <uint32_t BAUD, typename TXPIN, typename RXPIN, uint32_t MCLK_HZ>
void sw_serial_t<BAUD, TXPIN, RXPIN, MCLK_HZ>::_write(unsigned c)
{
__asm__ (
" bis #0x0300, %[c] ; Add Stop bit(s) to tx char\n"
" mov %[bit_dur], r14 ; Bit duration\n"
" mov %[tx_bit_mask], r12 ; Serial output bitmask\n"
" jmp 3f ; Send start bit...\n"
"1:\n"
" mov r14, r13 ; Get bit duration\n"
"2:\n"
" nop ; 4 cycle loop\n"
" sub #8, r13\n"
" jc 2b\n"
" subc r13, r0 ; 0 to 3 cycle delay\n"
" nop ; 3\n"
" nop ; 2\n"
" nop ; 1\n"
" rra %[c] ; Get bit to tx, test for zero\n"
" jc 4f ; If high...\n"
"3:\n"
" bic.b r12, %[PXOUT] ; Send zero bit\n"
" jmp 1b ; Next bit...\n"
"4:\n"
" bis.b r12, %[PXOUT] ; Send one bit\n"
" jnz 1b ; If tx data is not zero, then there are more bits to send...\n"
: /* return value */
: /* external variables */
[c] "r" (c),
[bit_dur] "i" (bit_rate.dur),
[half_dur] "i" (bit_rate.dur_half),
[tx_bit_mask] "i" (TXPIN::pin_mask), /* #BIT2 */
[PXOUT] "m" (TXPIN::pout()) /* &P1OUT */
: /* _write() clobbers these registers */
"r14", "r13", "r12"
);
return;
}
/*
* serial_base_sw_t<>::_read() - read one byte from RX pin
*/
template <uint32_t BAUD, typename TXPIN, typename RXPIN, uint32_t MCLK_HZ>
int sw_serial_t<BAUD, TXPIN, RXPIN, MCLK_HZ>::_read()
{
register unsigned result;
__asm__ (
" mov %[bit_dur], r14 ; Bit duration\n"
" mov %[rx_bit_mask], r13 ; Input bitmask\n"
" mov #0x01FF, %[rc] ; 9 bits - 8 data + stop\n"
"1: ; Wait for start bit\n"
" mov.b %[PXIN], r12 ; Get serial input\n"
" and r13, r12 ; Mask and test bit\n"
" jc 1b ; Wait for low...\n"
" mov %[half_dur], r13 ; Wait for 1/2 bit time\n"
"3:\n"
" nop ; Bit delay\n"
" sub #8, r13 ; 1/2 bit adjust\n"
" jc 3b\n"
" subc r13, r0 ; 0 to 3 cycle delay\n"
" nop ; 3\n"
" nop ; 2\n"
" nop ; 1\n"
" mov.b %[PXIN], r12 ; Get serial input\n"
" and %[rx_bit_mask], r12 ;\n"
" rrc %[rc] ; Shift in a bit\n"
" mov r14, r13 ; Setup bit timer\n"
" jc 3b ; Next bit...\n"
" rla %[rc] ; Move stop bit to carry\n"
" swpb %[rc] ; Move rx byte to lower byte, start bit in msb\n"
"4:\n"
: /* return value */
[rc] "=r" (result)
: /* external variables */
[bit_dur] "i" (bit_rate.dur),
[half_dur] "i" (bit_rate.dur_half),
[rx_bit_mask] "i" (RXPIN::pin_mask), /* #BIT1, */
[PXIN] "m" (RXPIN::pin()) /* &P1IN, */
: /* _read() clobbers these registers */
"r14", "r13", "r12"
);
return result;
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment