Electronics > Microcontrollers
Unnexpected Behaviour From Software UART On ATtiny84A AVR
Kalcifer:
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 UART
void 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[character]) //
{
PORTB |= (1 << PORTB0); // Transmit a logical one
timer_delay(); // Aforementioned delay
} else {
PORTB &=~ (1 << PORTB0); // Transmit a logical 0
timer_delay(); // Aforementioned delay
}
}
PORTB |= (1 << PORTB0); // Transmit a stop bit by bringing UART tx High.
timer_delay(); // Aforementioned delay
}
}
uart_tx("ab");
--- End code ---
What I would expect as an output is
--- Code: ---0100001101 0010001101
--- End code ---
however, what I am actually getting is shown in the following
https://imgur.com/a/ShA933T
which in terms of bits is
--- Code: ---00001000011010010001101...
--- End code ---
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 is
--- Code: ---?IDLE...000 0100001101 0010001101
^ start bit?
--- End code ---
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[character])
{
printf("1");
} else
{
printf("0");
}
}
printf("1");
printf(" ");
}
}
uart_tx("ab");
--- End code ---
outputs
--- Code: ---0100001101 0010001101
--- End code ---
as expected, so I am very perplexed as to what is going on here.
EDIT:
As requested, here is the code for the delay function
--- Code: ---// Initializing the timer
TCCR0A |= (1 << WGM01);
TIMSK0 |= (1 << OCIE0A);
OCR0A = 52;
// timer function
void timer_delay(void)
{
TCNT0 = 0; // Reset the time
TCCR0B |= (1 << CS01); // start the timer with /8 prescaler.
while (!(TIFR0 & (1 << OCF0A))); // Wait until the compare interrupt flag is set
TIFR0 &=~ (1 << OCF0A); // Reset the Compare flag
TCCR0B &=~ (1 << CS01); // Stop the timer.
}
--- End code ---
Dabbot:
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?
Dabbot:
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 timer
TCCR0A |= (1 << WGM01);
TIMSK0 |= (1 << OCIE0A);
OCR0A = 52;
TCNT0 = 0; // Reset the time
TCCR0B |= (1 << CS01); // start the timer with /8 prescaler.
// timer function
void timer_delay(void)
{
TIFR0 = (1 << OCF0A); // Reset the Compare flag
while (!(TIFR0 & (1 << OCF0A))); // Wait until the compare interrupt flag is set
}
--- End code ---
Edit: Per cv007's post re. clearing interrupt flags
cv007:
TIFR0 &=~ (1 << OCF0A); // Reset the Compare flag
That 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
DavidAlfa:
--- Quote from: Kalcifer on May 05, 2021, 07:58:53 am ---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 UART
void 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[character]) //
{
PORTB |= (1 << PORTB0); // Transmit a logical one
timer_delay(); // Aforementioned delay
} else {
PORTB &=~ (1 << PORTB0); // Transmit a logical 0
timer_delay(); // Aforementioned delay
}
}
PORTB |= (1 << PORTB0); // Transmit a stop bit by bringing UART tx High.
timer_delay(); // Aforementioned delay
}
}
--- End code ---
--- End quote ---
Simplify things:
--- Code: ---
#define Wait() while(!(TIFR0 & (1 << OCF0A))); TIFR0 = (1 << OCF0A)
void uart_tx(uint8_t *data){
TCNT0 = 0; // Reset counter
TIFR0 = (1 << OCF0A); // Reset flag
TCCR0B |= (1 << CS01); // start timer
while(*data++){
PORTB &=~ (1 << PORTB0); // Start bit
Wait();
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 bit
Wait();
}
TCCR0B &=~ (1 << CS01); // Stop timer
}
--- End code ---
Remember that TX idle state is high.
Not ok in your waveform, set it high at boot.
Navigation
[0] Message Index
[#] Next page
Go to full version