Monday, March 2, 2015

Generic UART for any microcontroller

Communicating with a micro-controller at runtime, is a very useful facility. It can be used for debugging, or to send commands etc. One of the easiest available protocol to communicate is UART. It requires a pin for transmit and a pin for receive, and the GROUND connection. There are many available programs that allow us to communicate using UART on the serial port, or using a usb-serial adapter. e.g. hyperterminal, minicom, etc.

However, not all microcontrollers have UART ports in built.

i was taking a look at the UART protocol, and it seemed pretty simple to implement.  So i gave it a go, and was zapped when it just worked the first time :).

Below is a little app that accepts 2 byte commands. Toggles LATD1 when it receives the command "TG", and echoes back the command after its done. Invert flag is provided. ( Inversion may be needed if we are not working directly with a real serial port). Tested with minicom @9600 baud, with hardware-flow-control OFF, so that it sends the chars we type.

It is tested on the PIC18F4550, will need modifications to run on other microcontrollers.

Its also available as a module to include at https://github.com/manojmo/pic_micro


/*
 * File:   main.c
 * Author: manoj
 * Writing to uart
 * Created on September 27, 2014, 6:01 PM
 */

#include 
#include 
#include 

// PLL with pre-scaler and post-scale options is a way to derive multiple
// freq from the same source, e.g. for low=6Mhz/high=48Mhz speed USB and for MCU clock
// Input to PLL has to be 4 Mhz and its output is 96 MHz
// So, for e.g. if we are using exernal 20 MHz osc, its o/p has to be
// divided by 5 to get 4 Mhz input for PLL
// PLLDIV is prescaler, o/p has to be 4MHz
// CPUDIV and USBDIV are postscalers, input is 96Mhz

#pragma config PLLDIV   = 5         // (20 MHz crystal on PICDEM FS USB board)
#pragma config CPUDIV   = OSC1_PLL2
#pragma config USBDIV   = 2         // Clock source from 96MHz PLL/2
#pragma config FOSC     = HSPLL_HS

#pragma config IESO = OFF
#pragma config WDT = OFF
#pragma config STVREN = ON
#pragma config LVP = ON
#pragma config BOR = ON
#pragma config MCLRE = ON
#pragma config PWRT = OFF
#pragma config PBADEN = OFF

// Effective CPU Freq, considering PLL and CPUDIV. Needed for the delay routines
#define _XTAL_FREQ 48000000

#define UART_BIT_TIME 104 // us. inverse gives the bit-rate
#define PIN_UART_TX LATD4 // output
#define PIN_UART_RX PORTDbits.RD5 // input

uint8_t UART_INVERTED = 0; // If inverted, low and high states will be inverted
uint8_t HIGH_STATE = 1;
uint8_t LOW_STATE = 0;

// flexible way to set some params.
void uart_init( uint8_t is_inverted){
    UART_INVERTED = is_inverted;
    if( is_inverted){
        HIGH_STATE = 0;
        LOW_STATE = 1;
    }
}

// start of transmission
void uart_start_tx(){
    PIN_UART_TX = HIGH_STATE;
}

// end of transmission
void uart_end_tx(){
    PIN_UART_TX = HIGH_STATE;
}

// write a char
void uart_write( char c){

    // start bit
    PIN_UART_TX = LOW_STATE;
    __delay_us( UART_BIT_TIME);

    uint8_t bit_pos = 0;
    // transmit bits, LSB first
    while (bit_pos < 8) {
        uint8_t currbit =  c & 0x01;
        PIN_UART_TX = UART_INVERTED ? ! currbit : currbit;
        __delay_us( UART_BIT_TIME);
        bit_pos++;
        c = c >> 1;
    }
    // stop bit
    PIN_UART_TX = HIGH_STATE;
    __delay_us( UART_BIT_TIME);
}

// Read specified number of chars and put them into holder array
void uart_read( uint8_t len, char holder[] ){
    uint8_t i;
    uint8_t bit_pos;

    // loop and read len number of chars.
    for( i=0; i< len; i++){

        // Wait for idle state to change, i.e. start bit
        while( PIN_UART_RX == HIGH_STATE);

        // start bit
        __delay_us( UART_BIT_TIME);

        // read bits, LSB first
        bit_pos = 0;
        char c = 0;
        while (bit_pos < 8) {
            uint8_t currbit = UART_INVERTED ? ! PIN_UART_RX : PIN_UART_RX;
            c = c | (currbit << bit_pos);
            __delay_us( UART_BIT_TIME);
            bit_pos++;
        }
        holder[i] = c;


        // stop bit
        __delay_us( UART_BIT_TIME);
    }
}

// write a string, optionally of specified len.
// if null terminated, len can be -1
void uart_writestr( char str[], int len ){
    int i;
    for( i=0; i< len || len == -1; i++){
        char curr = str[i];
        if( curr == '\0'){
            break;
        }
        uart_write( curr);
    }
}

// process the command we received
void process_cmd(char cmd[] ){
    if( strncmp( cmd, "TG", 2) == 0){
        LATD1 ^= 1;
    }
}

/*
 *
 */
int main(int argc, char** argv)
{
    TRISDbits.RD4 = 0; //TX pin set as output
    TRISDbits.RD5 = 1; //RX pin set as input
    TRISDbits.RD1 = 0; // command output

    uart_init(1); // invert
    uart_start_tx();
    char cmd[2]; // array to hold received command.
    while(1){
       uart_read( 2, cmd); // read a command, specified bytes long
       process_cmd( cmd);
       uart_writestr( cmd, 2); // echo it back
    }
}

No comments:

Post a Comment