Unnexpected Behaviour From Software UART On ATtiny84A AVR - Page 1
Forum section: electronics / general

author: Kalcifer
author_rank: Regular Contributor
timestamp: May 05, 2021, 07:58:53 am
content: I am trying to implement a software UART for the ATtiny84A, as it does not come with a UART out of the box.The following is an example of the UART TX:Code:DDRB |= (1 << DDB0); // Set the pin on Port B0 to be an output for UART TX.PORTB |= (1 << PORTB0); // Default the pin to HIGH for the idle high of the UARTvoid uart_tx(uint8_t *transmit_data){uint8_t string_length = strlen(transmit_data);for (uint_8t character = 0; character < string_length; character++) // Separate the string into characters.{PORTB &=~ (1 << PORTB0); // Send a start bit by bringing the UART TX low.timer_delay(); // Extra function that generates a delay to generate the appropriate baudrate.for (uint8_t character_bit = 0; character_bit < 8; character_bit++) // Separate the character into bits.{if ((1 << character_bit) & transmit_data) //{PORTB |= (1 << PORTB0); // Transmit a logical onetimer_delay(); // Aforementioned delay} else {PORTB &=~ (1 << PORTB0); // Transmit a logical 0timer_delay(); // Aforementioned delay}}PORTB |= (1 << PORTB0); // Transmit a stop bit by bringing UART tx High.timer_delay(); // Aforementioned delay}}uart_tx("ab");What I would expect as an output isCode:0100001101 0010001101however, what I am actually getting is shown in the followinghttps://imgur.com/a/ShA933Twhich in terms of bits isCode:00001000011010010001101...Taken as a whole, it has little meaning, but upon closer inspection, parts of it are correct. What is wrong about it is the 000 inserted at the beginning, so a more accurate way of looking at it isCode:?IDLE...000 0100001101 0010001101^ start bit?More specifically: The last two frames are accurate, but mystery data is being inserted at the beginning.What is very strange, is that If I implement this in a regular C program, it works as I would expect:Code:#include <stdio.h>#include <string.h>void uart_tx(unsigned char *transmit_data){for (unsigned char character = 0; character < strlen(transmit_data); character++){printf("0");for (unsigned char character_bit = 0; character_bit < 8; character_bit++){if ((1 << character_bit) & transmit_data){printf("1");} else{printf("0");}}printf("1");printf(" ");}}uart_tx("ab");outputsCode:0100001101 0010001101as expected, so I am very perplexed as to what is going on here.EDIT:As requested, here is the code for the delay functionCode:// Initializing the timerTCCR0A|=(1 << WGM01);TIMSK0|=(1 << OCIE0A);OCR0A=52;// timer functionvoid timer_delay(void){TCNT0=0;// Reset the timeTCCR0B|=(1 << CS01); // start the timer with /8 prescaler.while (!(TIFR0 & (1 << OCF0A)));// Wait until the compare interrupt flag is setTIFR0&=~(1 << OCF0A);// Reset the Compare flagTCCR0B&=~(1 << CS01);// Stop the timer.}

author: Dabbot
author_rank: Regular Contributor
timestamp: May 05, 2021, 08:15:27 am
content: What happens if you call timer_delay() once at the very start of your function? Can we see the code for timer_delay() as well?

author: Dabbot
author_rank: Regular Contributor
timestamp: May 05, 2021, 08:50:51 am
content: I suggest leaving the timer running, and just using the interrupt flags in your delay routine. This means you will need to call timer_delay() once at the start of your function to 'sync' with the timer, but you can then remove the timer_delay() call proceeding the stop bit.Suggested change. Given the function is now only two lines, you can probably copy it straight into your UART TX function where required:Code:// Initializing the timerTCCR0A|=(1 << WGM01);TIMSK0|=(1 << OCIE0A);OCR0A=52;TCNT0=0;// Reset the timeTCCR0B|=(1 << CS01); // start the timer with /8 prescaler.// timer functionvoid timer_delay(void){TIFR0=(1 << OCF0A);// Reset the Compare flagwhile (!(TIFR0 & (1 << OCF0A)));// Wait until the compare interrupt flag is set}Edit: Per cv007's post re. clearing interrupt flags

author: cv007
author_rank: Frequent Contributor
timestamp: May 05, 2021, 10:09:55 am
content: TIFR0 &=~ (1 << OCF0A); // Reset the Compare flagThat does not clear the flag. You write a 1 to the bit to clear it-TIFR0 = 1<<OCF0A;I would just use the builtin delay cycles function to get delays. Also split up things into smaller functions so its easier to read and understand.https://godbolt.org/z/es8aEoM7o

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 05, 2021, 08:46:12 pm
content: Quote from: Kalcifer on May 05, 2021, 07:58:53 amSimplify things:Code:#define Wait() while(!(TIFR0 & (1 << OCF0A))); TIFR0 = (1 << OCF0A)void uart_tx(uint8_t *data){TCNT0 = 0; // Reset counterTIFR0 = (1 << OCF0A); // Reset flagTCCR0B |= (1 << CS01); // start timerwhile(*data++){PORTB &=~ (1 << PORTB0); // Start bitWait();for (uint8_t bit = 0; bit < 8; bit++){ // Character into bits.if ((1 << bit) & *data){PORTB |= (1 << PORTB0); // Transmit a logical 1}else {PORTB &=~ (1 << PORTB0); // Transmit a logical 0}Wait();}PORTB |= (1 << PORTB0); // Stop bitWait();}TCCR0B&=~(1 << CS01);// Stop timer}Remember that TX idle state is high.Not ok in your waveform, set it high at boot.

author: Kalcifer
author_rank: Regular Contributor
timestamp: May 05, 2021, 09:51:28 pm
content: Quote from: cv007 on May 05, 2021, 10:09:55 amYou are correct; the problem ended up being that I was not clearing the interrupt flag. Sort of counter intuitive to set the interrupt flag to clear it! Nevertheless, problem solved. Thank you!

author: mikerj
author_rank: Super Contributor
timestamp: May 06, 2021, 07:54:21 am
content: Quote from: Kalcifer on May 05, 2021, 09:51:28 pmIt is counterintuitive at first, but this is very common and done for good reason. A simple register write is atomic, whereas a read-modify-write operation (which you were attempting) is not. If another interrupt changes the register halfway through a R-M-W operation then you can end up with nasty problems that are very hard to track down.

author: Dabbot
author_rank: Regular Contributor
timestamp: May 06, 2021, 08:20:29 am
content: Quote from: mikerj on May 06, 2021, 07:54:21 amWould that have been the case in this instance? Looking at the line in question, and ignoring the operation it is attempting to perform is incorrect anyway, I'd assume the compiler would ultimately use AND or ANDI with the provided literal to perform the update on the register:TIFR0 &=~ (1 << OCF0A)

author: mikerj
author_rank: Super Contributor
timestamp: May 06, 2021, 12:04:10 pm
content: Quote from: Dabbot on May 06, 2021, 08:20:29 amIf TIFR0 was in the 0-1F address range then the compiler could have optimised this as a CBI instruction which is atomic, but it's not in that range and that is a specific corner case anyway.In the general case AND is a read-modify-write operation. At the assembly level you'd have to read the interrupt status register into one of the working registers, AND the working register with an immediate value (or the value held in another working register) and then write the working register value back to the interrupt status register. Anything that changes the interrupt status register after the first step is going to cause problems.By putting the bit masking into the hardware for this register it solves the problem.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: May 17, 2021, 04:29:28 am
content: Quote from: Kalcifer on May 05, 2021, 07:58:53 amThat is a very bad reason to be working on a software UART.Almost any uC above 60ct is going to have one (or more) UART's these days. You really have to search to find one without a UART.I've been tinkering with AVR's since the AT90S2313, and I never bothered to work with the tinies. There are just too many incompatibilities with the Mega's, and that makes it annoying for me, because I would start mixing which chip can do what. Also Atmel used to change things for very silly reasons. If it has 3 16 bit timers they're probably all different. You want to swap two timers, and then it's missing a few bits you need for some function. Yuch, really annoying. And why even bother with putting in 8-bit timers? Shave 30 flipflops of the design?Also other things such as inventing another programming interface every three years I found quite annoying.I once was very tempted to buy the AVR Dragon, but the thing has a serious design fault which caused it to blow itself up if the USB input voltage is a bit low. Few years later I was tempted to buy a newer programmer from Atmel, but it had very fragile connectors to connect it to a development board, with multiple reports of the thing breaking off.Lately I'm experimenting a bit with STM32. When reading the datasheet, you get a description of a timer, U(s)ART or I2C peripheral , and then a small note that says "Oh, there are 5 of these in this chip".