Thursday, November 14, 2013

ATtiny Software Serial

ATtiny software serial transmit and receive, based the Arduino SoftwareSerial library, and around 1K compiled. That's what I've been working on. Why?

Spy photo :) of ultra compact eeZeeISP AVR programmer
I'm designing a low cost, really small AVR Programmer, that's why. It's based on an ATtiny84A and requires SPI which claims the USI peripheral, leaving no hardware serial.

Here are the details on TinySoftSerial.

Overview

The current Arduino SoftwareSerial library is based on the NewSoftSerial library by Mikal Hart. A few years ago, NewSoftSerial was a dramatic improvement over then-incumbent SoftwareSerial, which is why the new code is part of the Arduino 1.0.x distribution.

Multiple serial listeners are supported at baud rates from 300 to 115200 though errors above 38400 at 16MHz may be too high and lower rates too slow. Overall, it's a really neat library for typical communications of 9600 - 38400.

So you may see why, when I needed serial receive (the hard part), I didn't really feel like reinventing the wheel. I need the library for other projects, too. But I needed something small and with only one software serial instance.

What I Did

  • Converted to C
  • Removed multiple instance capability
  • Converted to native PORTx/PINx/DDRx register references
  • Hardcoded to use PA0 for RX and PA1 for TX
  • Shaved a few bytes here and there,
    • e.g., using a mask instead of modulo for circular buffer**
  • Adapted interrupt register references to ATTiny84A: GIMSK, PCMSK0
  • Hardcoded 16MHz timings (for now; I may restore the other frequencies later)
  • Fixed "Error: register r24, r26, r28 or r30 required" - details
  • A few other things I can't remember
The result is pretty small. The following is for the library with a simple main() that echoes characters you type:

avr-size --format=berkeley -t TinySoftSerial.elf
   text    data     bss     dec     hex filename
   1008       2     204    1214     4be TinySoftSerial.elf
   1008       2     204    1214     4be (TOTALS)
Finished building: sizedummy

** If you're curious about using a mask instead of modulo. Let's say you have a buffer of 64 bytes (or any number 2n), aka 0x40 or 26. Indexes range from 0x00 to 0x3f or 000000 to 111111. After you increment your index, simply AND it with 0x3f (2n-1). Suppose the index is 0x3f, the maximum and you increment it to 0x40. Now AND with 0x3f and you get 0x00  (%01000000 & 111111) . This is faster and uses fewer assembly instructions to implement than modulo or an if statement. Here's what it looks like in C:

#define MAXBUF 64
#define BUFMASK (MAXBUF-1)
...
char buf[MAXBUF];
...
i = (i+1) & BUFMASK;

The Code

I bet you just want the code. Ok, here it is.

Source Code

Porting to ATtiny85, Etc.

It shouldn't be too hard to port this to ATtiny85. You could port to ATtiny2313 although it has hardware serial separate from the USI. The library is too big for ATtiny13 to do anything useful. I don't use any other ATtinys but basically anything that has 2K flash or higher should be a go.

Saleae Logic Analyzer: STK500 V2 protocol

5 comments:

  1. Wow, you could almost use this to build something like Sparkfun's OpenLog. But cheaper, hopefully a lot cheaper. The OpenLog is an SD card with a ATmega328 for serial logging, and sells for $25. I can attach a 328 to a SD card reader for a lot less than that, and I have. But an ATtiny84 makes things even smaller and cheaper. Memory could be a little tight though.

    ReplyDelete
  2. Hi there.
    Assuming that I use an ATtiny84 with 8 MHz internal clock, how should I modify the timing param?
    Thanks a lot.

    ReplyDelete
    Replies
    1. This worked for me on an ATTiny44 @8 MHz internal:
      ```
      // 8MHz
      static const DELAY_TABLE PROGMEM table[] = {
      // baud rxcenter rxintra rxstop tx
      { 38400, 1, 17, 17, 12, },
      { 31250, 10, 37, 37, 33, },
      { 28800, 25, 57, 57, 54, },
      { 19200, 31, 70, 70, 68, },
      { 14400, 34, 77, 77, 74, },
      { 9600, 54, 117, 117, 114, },
      { 4800, 74, 156, 156, 153, },
      { 2400, 114, 236, 236, 233, },
      { 1200, 233, 474, 474, 471, },
      { 600, 471, 950, 950, 947, },
      { 300, 947, 1902, 1902, 1899, }
      };
      ```
      (just shifted down everything by 2 lines)

      Delete
    2. I found timings for 1, 8, and 16mhz here: https://github.com/sludin/attiny_software_serial/blob/master/SoftwareSerial.c

      Delete
  3. This worked for me on an ATTiny44 @8 MHz internal:
    ```
    // 8MHz
    static const DELAY_TABLE PROGMEM table[] = {
    // baud rxcenter rxintra rxstop tx
    { 38400, 1, 17, 17, 12, },
    { 31250, 10, 37, 37, 33, },
    { 28800, 25, 57, 57, 54, },
    { 19200, 31, 70, 70, 68, },
    { 14400, 34, 77, 77, 74, },
    { 9600, 54, 117, 117, 114, },
    { 4800, 74, 156, 156, 153, },
    { 2400, 114, 236, 236, 233, },
    { 1200, 233, 474, 474, 471, },
    { 600, 471, 950, 950, 947, },
    { 300, 947, 1902, 1902, 1899, }
    };
    ```

    ReplyDelete

Note: Only a member of this blog may post a comment.