Sunday 14 September 2014

AVR CLI Parser Snippet

This snippet can be used to provide a basic CLI (Command Line Interface) support to the ATmega, parsing the arguments in a single line of text transmitted via USART or other serial interface.

With this it's possible to change parameters in the running program without flashing the MCU.

Source:

#define CLI_MAX_ARGS    8       // Max arguments
#define CLI_MAX_LENGTH  8       // Max characters per argument

#define CLI_MAX_LENGTH_PAD      CLI_MAX_LENGTH+1
#define CLI_ERROR_MAX_ARGS      -1
#define CLI_ERROR_MAX_LENGTH    -2

struct CLI_Parser_Context{
    uint8_t currentArg;
    uint8_t currentMode;
    uint8_t currentSize;
    char args[CLI_MAX_ARGS][CLI_MAX_LENGTH_PAD];
};

void CLI_Parser_Clear(struct CLI_Parser_Context * ctx){
    ctx->currentArg = 0;
    ctx->currentMode = 0;
    ctx->currentSize = 0;
    for(uint8_t i = 0;i < CLI_MAX_ARGS;i++)
        memset(ctx->args[i],0,CLI_MAX_LENGTH_PAD);
}

int8_t CLI_Parse_Character(struct CLI_Parser_Context * ctx, char ch)
{
    if(ctx->currentArg >= CLI_MAX_ARGS)
        return CLI_ERROR_MAX_ARGS;
  
    if(ch == ' ' || ch == '\t'){
        if(ctx->currentMode == 0){
            ctx->currentArg++;
            ctx->currentSize = 0;
            ctx->currentMode = 1;
        }
    }
    else{
        if(ctx->currentSize >= CLI_MAX_LENGTH)
            return CLI_ERROR_MAX_LENGTH;
        ctx->args[ctx->currentArg][ctx->currentSize++] = ch;
        ctx->args[ctx->currentArg][ctx->currentSize] = '\0';
        ctx->currentMode = 0;
    }
  
    return ctx->currentArg;
}

Sample:
#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>

#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);  
    }
}

#define CLI_MAX_ARGS    8       // Max arguments
#define CLI_MAX_LENGTH  8       // Max characters per argument

#define CLI_MAX_LENGTH_PAD      CLI_MAX_LENGTH+1
#define CLI_ERROR_MAX_ARGS      -1
#define CLI_ERROR_MAX_LENGTH    -2

struct CLI_Parser_Context{
    uint8_t currentArg;
    uint8_t currentMode;
    uint8_t currentSize;
    char args[CLI_MAX_ARGS][CLI_MAX_LENGTH_PAD];
};

void CLI_Parser_Clear(struct CLI_Parser_Context * ctx){
    ctx->currentArg = 0;
    ctx->currentMode = 0;
    ctx->currentSize = 0;
    for(uint8_t i = 0;i < CLI_MAX_ARGS;i++)
        memset(ctx->args[i],0,CLI_MAX_LENGTH_PAD);
}

int8_t CLI_Parse_Character(struct CLI_Parser_Context * ctx, char ch)
{
    if(ctx->currentArg >= CLI_MAX_ARGS)
        return CLI_ERROR_MAX_ARGS;
  
    if(ch == ' ' || ch == '\t'){
        if(ctx->currentMode == 0){
            ctx->currentArg++;
            ctx->currentSize = 0;
            ctx->currentMode = 1;
        }
    }
    else{
        if(ctx->currentSize >= CLI_MAX_LENGTH)
            return CLI_ERROR_MAX_LENGTH;
        ctx->args[ctx->currentArg][ctx->currentSize++] = ch;
        ctx->args[ctx->currentArg][ctx->currentSize] = '\0';
        ctx->currentMode = 0;
    }
  
    return ctx->currentArg;
}

struct CLI_Parser_Context cli_context;

void cli_led(uint8_t argc, char argv[][CLI_MAX_LENGTH_PAD]);

void InputCallback(uint8_t ch){
    if(ch == '\r'){      
        USART_Tx('\n');
        USART_Tx('\r');

        if(strcmp(cli_context.args[0],"led") == 0){
            cli_led(cli_context.currentArg+1,cli_context.args);
        }
      
        CLI_Parser_Clear(&cli_context);
    }
    else{
        int8_t flag = CLI_Parse_Character(&cli_context,ch);
        if(flag < 0){
            printf("CLI_Parse Error : %d\n\r",flag);
            CLI_Parser_Clear(&cli_context);
        }
        else
            USART_Tx(ch);
    }
}

void cli_led(uint8_t argc, char argv[][CLI_MAX_LENGTH_PAD]){
    if(argc == 2){
        if(strcmp(argv[1],"on") == 0){
            PORTB |= (1 << PB1);
            printf("Led is ON\n\r");
        }
        else if(strcmp(argv[1],"off") == 0){
            PORTB &= ~(1 << PB1);
            printf("Led is OFF\n\r");
        }
    }
}

int main(void)
{
    CLI_Parser_Clear(&cli_context);
    USART_RxCallback = InputCallback;
    USART_Init(BAUD);
    DDRB |= (1<<PB1);
    sei();
    printf("CLI Led Switch, Build %s at %s\n\r",__DATE__,__TIME__);
    while(1){}
    return 0;
}

Usage:
1) Declare parser context and init/clear:
struct CLI_Parser_Context cli_context;
CLI_Parser_Clear(&cli_context);

2) Parse received character:
char rxChar = ...
CLI_Parse_Character(&cli_context, rxChar);

3) On line end character (CR or LF), check the arguments and clear the context:
for(uint8_t i = 0;i<cli_context.currentArg+1;i++)
  printf("%s\n\r",cli_context.args[i]);
CLI_Parse_Clear(&cli_context); 

Updated: 2014/12/20, changed to avoid using malloc.

Monday 4 August 2014

Caravela Portuguesa

Some time ago I modelled a low poly model of a Portuguese Caravel, but due to lack of detailed references it seemed poor, eventually I reworked the model and added more detail after a trip to the national navy museum. Armed with more photos and patience, the following model was accomplished with 32000 Triangles and 5 textures (+bumpmaps generated on demand in Blender). The wood textured was generated in Blender (for the wood seams) and GIMP (contrast, color correction, levels and seamless texture generation). It can be downloaded here under the CC license.



The previous model was also updated with a few tweaks and it's available to download.


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

Tuesday 24 December 2013

Postal de natal 2013

Esta é a minha entrada para a competição de natal de 2013 do forum Blender-PT de postais de natal, é algo simples sem elementos 3D, ré-imaginando a cena toda em 2.5D no Blender, Inkscape foi usado para adicionar o texto.

Bem podia ter usado uma fonte mais natalícia, ups :)