Saturday 15 February 2014

AVR USART Queue Snippet

This snippet defines queue buffers to be used with the USART RX/TX for the Atmel AVR ATmega 8/32, it uses interruptions to release the MCU from continuous polling. This was based on AVR306 app note source code from Atmel.

Defines & Includes:
#define F_CPU 16000000
#define BAUD 9600

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>

Source:
#define USART_RX_SIZE   64  // Receive queue max size
#define USART_TX_SIZE   64  // Transmit queue max size

#define USART_RX_MASK ( USART_RX_SIZE - 1 )
#define USART_TX_MASK ( USART_TX_SIZE - 1 )

static uint8_t USART_RxBuf  [USART_RX_SIZE];
static uint8_t USART_TxBuf  [USART_TX_SIZE];

int USART_Tx_File(char ch, FILE *stream);
static FILE USART_StdOut = FDEV_SETUP_STREAM(USART_Tx_File, NULL, _FDEV_SETUP_WRITE);

static volatile uint8_t USART_RxHead;
static volatile uint8_t USART_RxTail;
static volatile uint8_t USART_TxHead;
static volatile uint8_t USART_TxTail;

void (*USART_RxCallback)(uint8_t ch) = NULL;

void USART_Init(long baudrate){
    long baud_setting = (F_CPU / 8 / baudrate - 1) / 2;

    memset(USART_RxBuf,0,USART_RX_SIZE);
    memset(USART_TxBuf,0,USART_TX_SIZE);

    // Set STDOUT to custom file stream.
    stdout = &USART_StdOut;

    UBRRH = (baud_setting >> 8);
    UBRRL = baud_setting;

    // Don't use U2X
    UCSRA &= ~(1 << U2X);
    // Enable TX(TXEN), Enable RX(RXEN), Enable RX Interrupt(RXCIE)
    UCSRB =  (1<<TXEN) | (1<<RXEN) | (1<<RXCIE);
     // Asyncronous, Write to  UCSRC(URSEL), 8 bit
    UCSRC = (1<<URSEL) | (3<<UCSZ0);

    USART_RxTail = 0;
    USART_RxHead = 0;
    USART_TxTail = 0;
    USART_TxHead = 0;
}

void USART_Tx(uint8_t data){
    uint8_t tmphead;
    tmphead = ( USART_TxHead + 1 ) & USART_TX_MASK;
    // Wait while full
    while ( tmphead == USART_TxTail ){};
    USART_TxBuf[tmphead] = data;
    USART_TxHead = tmphead;
    // If the UART ain't busy sending bytes, send the first byte.
    if((UCSRB & (1<<TXCIE)) != (1<<TXCIE)){
        // Enable Tx interrupt for the following bytes
        UCSRB |= (1<<TXCIE);
         // Emulate the TX interruption for the first character
        uint8_t tmptail = ( USART_TxTail + 1 ) & USART_TX_MASK;
        USART_TxTail = tmptail;
        UDR = USART_TxBuf[tmptail];
    }
}

int USART_Tx_File(char ch, FILE *stream){
    USART_Tx(ch);
    return 0;
}

uint8_t USART_RxByte(void){
    uint8_t tmptail;
    while ( USART_RxHead == USART_RxTail ){};
    tmptail = ( USART_RxTail + 1 ) & USART_RX_MASK;
    USART_RxTail = tmptail;
    return USART_RxBuf[tmptail];
}

ISR(USART_RXC_vect){
    uint8_t tmphead;
    uint8_t data = UDR;
    tmphead = ( USART_RxHead + 1 ) & USART_RX_MASK;
    USART_RxHead = tmphead;
    if ( tmphead != USART_RxTail )
        USART_RxBuf[tmphead] = data;
    if( USART_RxCallback != NULL )
        USART_RxCallback(data);
}

ISR(USART_TXC_vect){
    uint8_t tmptail;
    if ( USART_TxHead != USART_TxTail ){
        tmptail = ( USART_TxTail + 1 ) & USART_TX_MASK;
        USART_TxTail = tmptail;
        UDR = USART_TxBuf[tmptail];
    }
    else{
        // Empty queue, disable interrupt.
        UCSRB &= ~(1<<TXCIE);  
    }
}

Updated: 2014/12/20, changed to use stdout and removed malloc usage.

Friday 14 February 2014

AVR I2C Master Snippet

This snippet defines functions to read/write bytes from i2c with an Atmel AVR ATmega 8/32, this can be used with EEPROM(24C02), IO Expander(PFC8574), Clock & Calendar(PCF8583) and other kind of slave i2c devides. This is based on Ronald Willem Besinga i2c tutorial, extended to execute page read/write operations.

#define I2C_START       0
#define I2C_STOP        1
#define I2C_DATA_NACK   2
#define I2C_DATA_ACK    3

function UART_Log(const char * str, ...){    
     // Dummy log
}

void I2C_Init(uint32_t clock)
{
     // initialize TWI clock: 100 kHz clock, TWPS = 0 => prescaler = 1
     TWSR = 0;                         /* no prescaler */
     TWBR = ((F_CPU/clock)-16)/2;  /* must be > 10 for stable operation */
}

unsigned char I2C_Transmit(uint8_t type) {
    switch(type) {
        case I2C_START:     // Send start condition
            TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
            break;
        case I2C_DATA_NACK: // Send data
            TWCR = (1 << TWINT) | (1 << TWEN);
            break;
        case I2C_DATA_ACK:  // Send data with acknowledge.
            TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
            break;
        case I2C_STOP:      // Send stop condition
            TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
            return 0;
    }

    // Wait for TWINT flag set in TWCR Register
    while (!(TWCR & (1 << TWINT)));

    // Return TWI Status Register, mask the prescaler bits (TWPS1,TWPS0)
    return (TWSR & 0xF8);
}

uint8_t I2C_Start(uint8_t dev_id, uint8_t dev_addr, uint8_t addr){
    uint8_t s;

    // First step, send start
    s = I2C_Transmit(I2C_START);

    if (s == TW_MT_ARB_LOST || (s != TW_START && s != TW_REP_START)){
        USART_Log("Error(Start): %x\r\n",s);
        goto failure;
    }

    // Second step, set device id and device address
    TWDR = (dev_id & 0xF0) | ((dev_addr << 1) & 0x0E) | TW_WRITE;

    s = I2C_Transmit(I2C_DATA_ACK);

    if (s == TW_MT_ARB_LOST || s != TW_MT_SLA_ACK || s == TW_MT_SLA_NACK){
        USART_Log("Error(Device Id/Address): %x\r\n",s);
        goto failure;
    }

    // Third step, set memory address
    TWDR = addr;

    s = I2C_Transmit(I2C_DATA_ACK);

    if (s != TW_MT_DATA_ACK){
        USART_Log("Error(EEPROM Address): %x\r\n",s);
        goto failure;
    }

    return 0;

    failure:return 1;
}

uint8_t I2C_Random_Read(uint8_t dev_id,
    uint8_t dev_addr,
    uint8_t addr, uint8_t *data)
{
    uint8_t ret = 0;

    // First step, setup i2c and addresses (dummy write)
    if(I2C_Start(dev_id,dev_addr,addr))
        goto exit;

    // Second step, start again
    uint8_t s = I2C_Transmit(I2C_START);

    if (s == TW_MT_ARB_LOST || (s != TW_START && s != TW_REP_START)){
        USART_Log("Error(Start again): %x\r\n",s);
        goto exit;
    }

    // Third step, set device id and device address
    TWDR = (dev_id & 0xF0) | ((dev_addr << 1) & 0x0E) | TW_READ;

    s = I2C_Transmit(I2C_DATA_ACK);

    if (s == TW_MR_ARB_LOST || s != TW_MR_SLA_ACK || s == TW_MR_SLA_NACK){
        USART_Log("Error(Device address 2): %x\r\n",s);
        goto exit;
    }

    // Forth step, read data
    s = I2C_Transmit(I2C_DATA_NACK);

    if (s != TW_MR_DATA_NACK){
        USART_Log("Error(Data reception NACK): %x\r\n",s);
        goto exit;
    }

    *data = TWDR;

    ret = 1;

exit:    I2C_Transmit(I2C_STOP);
    return ret;
}

uint8_t I2C_Sequencial_Read(uint8_t dev_id,
    uint8_t dev_addr,
    uint8_t addr, uint8_t * data, uint8_t size)
{
    uint8_t ret = 0;

    // First step, setup i2c and addresses (dummy write)
    if(I2C_Start(dev_id,dev_addr,addr))
        goto exit;

    // Second step, start again
    uint8_t s = I2C_Transmit(I2C_START);

    if (s == TW_MT_ARB_LOST || (s != TW_START && s != TW_REP_START)){
        USART_Log("Error(Start again): %x\r\n",s);
        goto exit;
    }

    // Third step, set device id and device address
    TWDR = (dev_id & 0xF0) | ((dev_addr << 1) & 0x0E) | TW_READ;

    s = I2C_Transmit(I2C_DATA_ACK);

    if (s == TW_MR_ARB_LOST || s != TW_MR_SLA_ACK || s == TW_MR_SLA_NACK){
        USART_Log("Error(Device address 2): %x\r\n",s);
        goto exit;
    }

    for(uint8_t i = 0;i < size;i++){
        if(i == size - 1){
            // Fifth step, read and finish with NACK
            s = I2C_Transmit(I2C_DATA_NACK);
            if (s != TW_MR_DATA_NACK){
                USART_Log("Error(Data reception NACK): %x\r\n",s);
                break;
            }
        }else{
            // Forth step, read data with ACK
            s = I2C_Transmit(I2C_DATA_ACK);
            if (s != TW_MR_DATA_ACK){
                USART_Log("Error(Data reception ACK): %x\r\n",s);
                break;
            }
        }

        data[i] = TWDR;
        ret += 1;
    }

exit:    I2C_Transmit(I2C_STOP);
    return ret;
}

uint8_t I2C_Byte_Write(uint8_t dev_id,
    uint8_t dev_addr,
    uint8_t addr, uint8_t data)
{
    uint8_t ret = 0;

    // First step, setup i2c and addresses
    if(I2C_Start(dev_id,dev_addr,addr))
        goto exit;

    // Second step, write byte into memory
    TWDR = data;

    uint8_t s = I2C_Transmit(I2C_DATA_ACK);

    if (s != TW_MT_DATA_ACK){
        USART_Log("Error(Data dispatch): %x\r\n",s);
        goto exit;
    }

    ret = 1;

exit:    I2C_Transmit(I2C_STOP);
    return ret;
}

uint8_t I2C_Page_Write(uint8_t dev_id,
    uint8_t dev_addr,
    uint8_t addr, uint8_t * data, uint8_t size)
{
    uint8_t ret = 0;

    // First step, setup i2c and addresses
    if(I2C_Start(dev_id,dev_addr,addr))
        goto exit;

    // Second step, write byte into memory
    for(uint8_t i = 0;i < size;i++){
        TWDR = data[i];

        uint8_t s = I2C_Transmit(I2C_DATA_ACK);

        if (s != TW_MT_DATA_ACK){
            USART_Log("Error(Data dispatch): %x\r\n",s);
            break;
        }

        ret ++;
    }

exit:    I2C_Transmit(I2C_STOP);
    return ret;
}

Usage Example:
#define DEV_24C02 0xA4
#define SCL_CLOCK 100000L
I2C_Init(SCL_CLOCK);
I2C_Byte_Write(DEV_24C02,0,1,0xF0);
uint8_t val;
I2C_Random_Read(DEV_24C02,0,1,&val);

Use I2C_Page_Write and I2C_Sequencial_Read for more than one byte operations, otherwise i2c communication may start to fail.

Last update: 20/08/2014