Electronics > Microcontrollers

Unnexpected Behaviour From Software UART On ATtiny84A AVR

(1/2) > >>

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

There was an error while thanking
Thanking...
Go to full version