Since ATtiny13 does not have hardware USART/UART in some cases we’re forced to use Software UART. In this example project a simple bit-banging UART has been presented. Compiled for both TX and RX it uses just 248 bytes of flash. Default serial configuration is 8/N/1 format at 19200 baud (lowest error rates during tests). It can be easily changed to other standard RS-232 baudrate. Note that it is a great solution for a simple debug/logging but to other purposes I would recommend using an AVRs with a built-in UART. The code is on Github, click here.
Edit: To make a simpler integration with external projects I have created a little library:
https://github.com/lpodkalicki/attiny13-software-uart-library
Parts Required
- ATtiny13 – i.e. MBAVR-1 development board
Circuit Diagram
Serial communications tools
1) TTL Serial Adapter (UART <-> USB converter)
2) Serial terminal
I usually use CuteCom – a graphical serial port communications program, similar to Minicom.
Or python scripts, i.e.:
#!/usr/bin/env python import serial import time class Client: def __init__(self, port="/dev/ttyUSB0"): self.port = serial.Serial( port=port, baudrate=19200, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS) def loop(self): while True: req = "%s\r\n" % raw_input("client$ ") n = self.port.write(req) print "Sent %d bytes" % n time.sleep(0.01) rep = self.port.readline() print "Received %d bytes, rep=\"%s\"" % (len(rep), rep.replace("\r\n", "")) if __name__ == '__main__': Client().loop()
Firmware
This code is written in C and can be compiled using the avr-gcc. More details on how compile this project is here.
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#define UART_RX_ENABLED (1) // Enable UART RX
#define UART_TX_ENABLED (1) // Enable UART TX
#ifndef F_CPU
# define F_CPU (1200000UL) // 1.2 MHz
#endif /* !F_CPU */
#if defined(UART_TX_ENABLED) && !defined(UART_TX)
# define UART_TX PB3 // Use PB3 as TX pin
#endif /* !UART_TX */
#if defined(UART_RX_ENABLED) && !defined(UART_RX)
# define UART_RX PB4 // Use PB4 as RX pin
#endif /* !UART_RX */
#if (defined(UART_TX_ENABLED) || defined(UART_RX_ENABLED)) && !defined(UART_BAUDRATE)
# define UART_BAUDRATE (19200)
#endif /* !UART_BAUDRATE */
#define TXDELAY (int)(((F_CPU/UART_BAUDRATE)-7 +1.5)/3)
#define RXDELAY (int)(((F_CPU/UART_BAUDRATE)-5 +1.5)/3)
#define RXDELAY2 (int)((RXDELAY*1.5)-2.5)
#define RXROUNDED (((F_CPU/UART_BAUDRATE)-5 +2)/3)
#if RXROUNDED > 127
# error Low baud rates unsupported - use higher UART_BAUDRATE
#endif
static char uart_getc();
static void uart_putc(char c);
static void uart_puts(const char *s);
int
main(void)
{
char c, *p, buff[16];
uart_puts("Hello World!\n");
/* loop */
while (1) {
p = buff;
while((c = uart_getc()) != '\n' && (p - buff) < 16) {
*(p++) = c;
}
*p = 0;
_delay_ms(10);
uart_puts(buff);
}
}
char
uart_getc(void)
{
#ifdef UART_RX_ENABLED
char c;
uint8_t sreg;
sreg = SREG;
cli();
PORTB &= ~(1 << UART_RX);
DDRB &= ~(1 << UART_RX);
__asm volatile(
" ldi r18, %[rxdelay2] \n\t" // 1.5 bit delay
" ldi %0, 0x80 \n\t" // bit shift counter
"WaitStart: \n\t"
" sbic %[uart_port]-2, %[uart_pin] \n\t" // wait for start edge
" rjmp WaitStart \n\t"
"RxBit: \n\t"
// 6 cycle loop + delay - total = 5 + 3*r22
// delay (3 cycle * r18) -1 and clear carry with subi
" subi r18, 1 \n\t"
" brne RxBit \n\t"
" ldi r18, %[rxdelay] \n\t"
" sbic %[uart_port]-2, %[uart_pin] \n\t" // check UART PIN
" sec \n\t"
" ror %0 \n\t"
" brcc RxBit \n\t"
"StopBit: \n\t"
" dec r18 \n\t"
" brne StopBit \n\t"
: "=r" (c)
: [uart_port] "I" (_SFR_IO_ADDR(PORTB)),
[uart_pin] "I" (UART_RX),
[rxdelay] "I" (RXDELAY),
[rxdelay2] "I" (RXDELAY2)
: "r0","r18","r19"
);
SREG = sreg;
return c;
#else
return (-1);
#endif /* !UART_RX_ENABLED */
}
void
uart_putc(char c)
{
#ifdef UART_TX_ENABLED
uint8_t sreg;
sreg = SREG;
cli();
PORTB |= 1 << UART_TX;
DDRB |= 1 << UART_TX;
__asm volatile(
" cbi %[uart_port], %[uart_pin] \n\t" // start bit
" in r0, %[uart_port] \n\t"
" ldi r30, 3 \n\t" // stop bit + idle state
" ldi r28, %[txdelay] \n\t"
"TxLoop: \n\t"
// 8 cycle loop + delay - total = 7 + 3*r22
" mov r29, r28 \n\t"
"TxDelay: \n\t"
// delay (3 cycle * delayCount) - 1
" dec r29 \n\t"
" brne TxDelay \n\t"
" bst %[ch], 0 \n\t"
" bld r0, %[uart_pin] \n\t"
" lsr r30 \n\t"
" ror %[ch] \n\t"
" out %[uart_port], r0 \n\t"
" brne TxLoop \n\t"
:
: [uart_port] "I" (_SFR_IO_ADDR(PORTB)),
[uart_pin] "I" (UART_TX),
[txdelay] "I" (TXDELAY),
[ch] "r" (c)
: "r0","r28","r29","r30"
);
SREG = sreg;
#endif /* !UART_TX_ENABLED */
}
void
uart_puts(const char *s)
{
while (*s) uart_putc(*(s++));
}
for those who couldnt get it working, maybe due to the clock / baud, this might work for you:
https://github.com/Dolphin101546015/ATtiny13A-UART
it worked for me to allow bluetooth communication
Very nice!
/LP
Hello! Thank you for this awesome code!
How to implement Serial.available with this code?
When I compile it. Error message shown error: r28 cannot be used in asm here.
And for r29
The asm code looks familiar. 🙂
Ralph, thanks for emailing me with the optimization hint! Will try it 🙂
Hi Łukasz , I am so thankful to you for this solution. Simple and works right away. I have been struggling with debugging my ATTiny13 but not anymore.
Thank you so much for this! Keep up the good work!
Hi Vijay, I hope to put here a lot more entries about ATtiny13, soon. Thanks!
I experienced some problems while trying the IR receiver demo where I was not able to have a UART communication. So I tried this part and it works for me out of the box. When I change the demo here to FUSE_L=0x7A, FUSE_H=0xFF, F_CPU=9600000, -DUART_BAUDRATE=57600 (taken from the IR project), I get some compile errors:
main.c:70:2: warning: asm operand 4 probably doesn’t match constraints
__asm volatile(
^
main.c:70:2: error: impossible constraint in ‘asm’
I’m now confused as the code looks identical when comparing these projects. I would be glad if I could get a helping hand ….
When I switch to 115200, the example works again. Do you have an explanation for this behaviour?
I guess it’s a problem with internal clock which is not so stable.
Works right out of the box 🙂 thank you very much Łukasz.
Great. 🙂
Hi, I like your blog and I share your addiction to such a tiny but powerful devices as attiny13. There no too much resources in internet and your blog is real gem for me. So, first of all – thank you for the great work you did.
I have no too much experience in AVRs and may be I’m doing something wrong but I’ve played a little bit with your code and noticed couple of issues. When I’ve added just an additional if-else branch in main() inside while loop, the “Hello World!” stopped working, which is pretty weird. Seems like memory or registers corruption (it might be that assembly code somehow interfere with code generated by gcc?)
Also, it seems me excessive to do DDRB |= 1 << UART_TX each time when you need to send character (and may be PORTB |= 1 << UART_TX as well). Would it be more efficient to take it out of the function and perform only once in kind of "setup()"?
Thanks, Alex. Comments like that give me a power for making next projects.
Issue with program hangs was probably caused by incorrect condition inside a loop (Boma-fix). It has been updated on page after you reported concerns about that bug. Yes, you can achieve one-time initialization of I/O pins by refactoring the code. Note that these redundant instructions cost is two clock-cycles while additional setup function will increase the size of program space. This compromise between CPU speed and program space depend on project requirements. You can choose what fits you best.
I’ve compiled the code from the repo and uploaded to an attiny13 using an arduino mini. The hex is successfully uploaded without error, but when I connect the uart adapter to the attiny and open the serial monitor there is no response. Why?
Hi, I must admit that I never tried upload hex files using an Arduino and I cant help with what could go wrong. I always recommend to check twice if there is a bug related to wiring or mistake in firmware code.
There’s error in main(). There should be (p – buff) < 16.
Fixed. Thank you for reporting that bug.
Fixed where? In the page it still with bug.
Good eyes! Thanks. Fix has been applied to repo. Now page is updated, too.