Sunday 19 February 2023

USI - Universal Serial Interface - UART send data.

USI - Universal Serial Interface

The USI provides the basic hardware implementation required for serial communication and can be used to implement three-wire communication (SPI) or two-wire communication (UART/I2C). This blog explains how to implement UART using USI, in ATtiny85 microcontroller. The ATtiny85 supports half-duplex UART, meaning it can perform either transfer or reception at a time.

The basic block diagram for the USI interface on the ATtiny85 microcontroller is shown below.

Fig-1: Block diagram of USI

In the diagram above, the three output pins are used for different purposes based on the type of communication that has been selected.

Pin Name Use in Use of pin.
DO 3-wire mode DO (Data Out) - Data output only.
SDA/DI 2-wire mode SDA (Serial Data) - Data input (open drain) and output
3-wire mode DI (Data In) - Data input open drain
SCL/USCK 2-wire mode SCL - Serial Clock
3-wire mode USCK - USI Clock


Elements used for USI UART implementation for sending data.

Name Use
USIDR
USI Data Register
Used to send/receive serial data from/to the micro controller.
USIBR
USI Buffer Register
Used to buffer the incoming data (Read-only). Not used while sending data
USICR
USI Control Register
Used for interrupt enable, select wire mode, select clock source and clock strobe.
USISR
USI Status Register
Contain interrupt status, line status and counter value.
4-Bit Counter This counter is part of USISR and increments for each USI clock cycle. It is useful for counting how many bits are transferred during serial communication.
USI Clock Source The clock source can be external, a Timer/Counter0 Compare Match, or by software using USICLK or USITC strobe bits.

UART - Universal Asynchronous Receiver Transmitter

UART is a form of serial communication where data is transmitted 1 bit at a time. There is no clock shared between transmitter and receiver, also the receiver will understand eac
h frames by using start and stop bits and hence it’s called Asynchronous. Both transmitter and receiver need to be operated in the same speed for accurate data transmission. This speed is called Baud rate and it’s expressed in bits per second (bps). The UART frame composed of the following bits.
  • 1 start bit
  • 5 to 9 data bits
  • 1 parity bit
  • 1 or 2 stop bits
Since there is no negotiation between transmitter and receiver before the actual communication start, it’s mandatory to configure both side with same baud rate and frame structure.

During idle conditions in UART communication, the output pin remains high, establishing a baseline for communication. To begin transmitting data, the output pin is set low for one bit time, indicating the start of a data frame. Subsequently, the data is transmitted bit by bit, starting with the least significant bit (LSB) first. In USI, the Most Significant Bit (MSB) of the data register (USIDR) is sent first, necessitating the reversal of the data before setting it in the register. Following the transmission of the data bits, the parity bit, if applicable, is sent to provide error checking. Finally, the output pin is held high for one or two bit times, depending on the frame structure, to signal the end of the data frame.

The most efficient way to reverse a byte is
uint8_t reverse_byte(uint8_t byte) {
    byte = ((byte & 0x55) << 1) | ((byte & 0xAA) >> 1);
    byte = ((byte & 0x33) << 2) | ((byte & 0xCC) >> 2);
    byte = ((byte & 0x0F) << 4) | ((byte & 0xF0) >> 4);
    return byte;
}




Configure USI as UART for transfer data.


Pin Selection
We have two options for choosing the serial output pin. We can either use DO pin by selecting 3-wire mode or SDA by selecting 2-wire mode. In this post we are going to use 3-wire mode. So take DO(PB1) for sending data out.
void setup_usi_uart() {
    DDRB |= (1 << DATA_OUT_PIN);      // Set data out port as output
    PORTB |= (1 << DATA_OUT_PIN);     // Set the output high in idle
    sei();                            // Enable global interrupt
}

Clock Selection
The clock can be sourced externally via the USCK pin, Timer/Counter0 compare match, or via software. Of these options, we have chosen the simplest method, Timer/Counter0 compare match, as no additional external circuitry is required, and it requires fewer lines of code compared to the software options.
In this option, each bit will be transferred out upon matching the Timer/Counter0 counter register value with the OCR0A. The value which is to be set in the OCR0A is based on the baud rate.

Baud Rate Selection
Number of clocks required for 1 bit transfer can be calculated by using the following equation.
No. of clocks = F_CPUBaud Rate

The baud rate should be chosen in such a way that, the drift between the counter value that we have calculated and the nearest whole number should not be more than 0.1. Since the counter register is 8-bit wide, if the calculated value is more than 255, we will need to use the prescaler of 8 and set value/8 in the OCR0A register.

Eg. F_CPUBaud Rate = 10000002400 = 416.666

Since 416.6666 is greater than 255, a prescaler should be used to bring the value down to 52.08. We then take the nearest integer, which is 52, and use it as the value for OCR0A. It's important to note that the maximum drift value of 0.1 mentioned here may not be completely accurate, as it was determined by trial-and-error method.

The 4-bit Counter
The 4-bit counter of USI is useful for sending the required number of bits from USIDR. This is achieved by enabling the counter overflow interrupt (USIOE) in USICR. The counter increments for every USI clock and the width of counter is 4 bit. Therefore, the value to be set in this register for sending n bits is 16 - n.

Combining all together
Here is the procedure to implement firmware for sending data using UART protocol using USI. The USI DR is 8-bit long, but for sending 8-bit data with 1 parity and 2 stop bits, we
 will require 12 bits. In this case the first 8-bit need to send first and then send the remaining for bits
  1. Initial setup:
    • Select the data out pin as GPIO output.
    • Enable global interrupt.
  2. Prepare setup and send a single byte (d).
    • Select CTC mode and prescaler for Timer/Counter0 and set the counter value as 0.
    • Select OCR0A value based on the prescaler and baud rate.
    • Enable counter overflow interrupt in USICR.
    • Select wire mode in USICR.
    • Select the clock source as Timer/Counter0 compare match.
    • Reverse the byte (rd).
    • Write 0 in MSB of USIDR (UART start condition).
    • Write the first 7 bits from bit 1 onwards.
    • Clear the overflow interrupt flag by writing 1 to USIOIF of USISR.
    • Write 16 - 8 = 8 in the 4-bit counter.
  3. Wait for ISR and send the remaining data.
    • Write the remaining data (rd) to the MSB of USIDR.
    • Write parity bit after data bits in USIDR.
    • Finally, write one or two bits of 1s as stop bits according to the required number of stop bits in USIDR.
    • Write the value (16 - (no. of remaining data) - (no. parity bits) - (no. stop bits)) to 4-bit counter.
  4. Wait for the second ISR - cleanup.
    • Disable USI.
    • Set the output pin as high as the line is idle.
    • Clear the interrupt flag.
Note: Step 3 and 4 are the same ISR, so it's required to distinguish both ISR by using the appropriate flag.
ISR (USI_OVF_vect) {
    if( PROC_BUFF1 == line_status ) {
        USIDR = 0x7F | (data_buff << 7);        // remaining bit + stop bits
        USISR = (1 << USIOIF) |                 // Clear USI overflow interrupt flag
                (16 - 2);                       // Set USI counter to send last data bit + 1 stop bits
        line_status = PROC_BUFF2;
    } else {
        PORTB |= (1 << DATA_OUT_PIN);           // Ensure output is high
        USICR = 0;                              // Disable USI.
        USISR |= (1 << USIOIF);                 // clear interrupt flag
        line_status = AVAILABLE;
    }
}

Source code
Here is the link to the complete source code: USI - UART send data