ATtiny Gets A Tiny Software UART

Modern microcontroller platforms spoil us with their performance and expansive spec sheets. These days it’s not uncommon to be developing for a cheap micro that has a clock rate well in excess of 100MHz, with all manner of peripherals baked in. DACs, WiFi, you name it – it’s in there, with a bunch of libraries to boot. It wasn’t always this way, and sometimes you would even find yourself lacking hardware serial support. In these cases, the bitbanged software UART is your friend, and [MarcelMG] decided to document just how it’s done.

The amateur programmer’s first recourse may be to use delays to properly time the output data stream. This has the drawback of wasting processor cycles and doesn’t let the microcontroller do much else useful. Instead, [Marcel] discusses the proper way to do things, through the use of interrupt service routines and hardware timers.

[Marcel]’s implementation is for the ATtiny24A, though it should be easily portable to other AVR8 processors. Taking up just 2 bytes of RAM and 276 bytes of program space, it’s compact – which is key on resource-limited 8-bit devices. The code is available on Github if you fancy trying it out yourself.

It’s a technique that is more than familiar to the old hands, but useful to those new to the art. It can be particularly useful if you need to get data out of a legacy platform with limited options. As times change, it’s important to pass on the techniques of yesteryear to the new generation. Of course, if things are really tight, you can even do a half-duplex UART on a single pin.

 

22 thoughts on “ATtiny Gets A Tiny Software UART

  1. The USI hardware on ATTiny’s is actually really good for UART functionality. For writing of course it’s super simple. You can queue up to 16 bits and let it run, preferably loading up another 8 bits each time the buffer empties.

    Reading is a bit more difficult because the signal is asynchronous. You can’t sample once per baud rate, you’d risk getting a sample time right around transition and get the occasional double bit or missed bit. Typical hardware samples around 16 times per baud period. It then adjusts the sample window when it sees a clock edge. You can get away with 4 (or even 3) samples per baud period and get reliable communication.

    So for a bi-directional uart, you need to have 4 bits of buffer space for each bit on the channel. You end up servicing it on average every 2 channels bits, but since there are two USI buffers you can have up to 4 channel bits interrupt latency and still not drop things.

    Another way to do things is sample on start bit. This is a little more tricky but can save a lot of processor time and code complexity. When the line goes low, it’s a start bit. You then wait 1.5 bit periods and then take 8 samples, once per bit period. And then of course setup for the next start bit. If things get out of sync with this method it can be very difficult to resync if the bitstream is steady.

    1. That’s true, but the disadvantage of the USI hardware is that it’s tied to specific pins. But if you’re okay with using the specific pins you’re right, USI is probably the way to go.

  2. Very nice, Marcel. Cycle counting and bit-banging a serial interface can be quite fun. I posted an example on PICLIST many years ago for interrupt driven full-duplex 9600 baud on a PIC16F819 or PIC12F683 (8-MHz INTOSC) with 16 byte RX and TX circular buffers. The timer driven interrupt fired at three times the bit rate (every 59 instruction cycles) and used 34 or 35 cycles (about 50% ‘overhead’). The ISR driver, Init, Put, and Get routines used ~189 words of program memory.

  3. its a good example of one of the bad points of arduino. by wrapping all the hardware into a couple of general purpose features you generally are not aware of how powerful that hardware can actually be. sure you can delay(), or pulseout() all you want. but you dont really learn how such low level systems actually work. also the attiny has a usi which can be configured to do half-duplex asynchronous serial, why dont you use it?

    1. I actually think of this as an advantage of Arduino and other “high” level uC frameworks. You get lots of convenience, but you can always drop it and seamlessly work with the lower level stuff when you need. Of course, if you have the attitude of “meh I don’t care how any of this Arduino magic works” it can make you a shitty programmer, but it can also enable a skilled user to focus on the parts they care about.

      1. im generally pro arduino but this is one of the areas where it is weak. but i see a lot of people do something in a round about way when the chip already has that feature built in.

    2. I tried to do it mainly for educational purposes from the ground up. I think is just makes you feel proud when you did something completely from scratch (and it works!) vs. just using someone’s library. :)

      Another advantage of this approach is that you can use any GPIO pin, no only the one tied to the USI.

    1. You are missing the point here. Your code use idle loop for delay wasting these cycles. Your code is caught in the rx and tx procedures until the end of byte transmission.
      [MarcelMG] point is to avoid that using a timer and interrupt.

Leave a Reply to Nicolas Pitre Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.