millwood0 发表于 2013-8-18 06:55:07

Interrupt-drive software uart

I wrote a software uart for STM32F10x chips here: http://www.amobbs.com/thread-5542383-1-1.html. On a 24Mhz CM3, it is good up to about 50Kbps, without any optimization.

I mentioned that porting it to the code to other mcus should be fairly easy. Here are two examples of porting that piece of code to a PIC (16F684) and a 89C51.

First, porting it to a 16F684. 16F chips don't have vectored interrupts so it is more involved. But here it is:
//==============uartxisr.h========================
#ifndef _UARTXISR_H
#define _UARTXISR_H

//#include "stm32f10x_rcc.h"                                //we use rcc
//#include "stm32f10x_tim.h"                                //we use timer

//hardware configuration
//softuart pin definitions
#define UART_PORT                PORTC
#define UART_DDR                TRISC
#define UART_TX                        (1<<2)                //tx pin

#define UART_SET(tx)        IO_SET(UART_PORT, tx)
#define UART_CLR(tx)        IO_CLR(UART_PORT, tx)

//pick the timer to be used
//#define UART_TIM                TIM3
#define tmrx_init                tmr1_init
#define tmrx_act                tmr1_act
#include "tmr1.h"                                                //we use timer
//end hardware configuration

//insert into the isr
#define UARTX_TX_ISR()                                \
        if (TMR1IF) {                                        \
                TMR1H = tmr1_offset>>8;                \
                TMR1L = tmr1_offset/* & 0x00ff*/;        \
                TMR1IF = 0;                                        \
                _tmr1_isr_ptr();                        \
        }       

//uartx protocol
//1 start bit
//8 data bits
//1 stop bit
//lsb first
#define UART_SR(dat)                (0x0200 | ((dat) << 1) | 0x0000)        //form the uart shift register. 1 start bit (low), 8 data bits, 1 stop bit (high), lsb first

//global defines
//define baud rates
#define UART_BR_300                        300ul                //baudrate=300 - it overflows under 24Mhz F_CPU
#define UART_BR_600                        600ul                //baudrate=600
#define UART_BR_1200                1200ul                //baudrate=1200
#define UART_BR_2400                2400ul                //baudrate=2400
#define UART_BR_4800                4800ul                //baudrate=4800
#define UART_BR_9600                9600ul                //baudrate=9600
#define UART_BR_19200                19200ul                //baudrate=19200
#define UART_BR_38400                38400ul                //baudrate=38400
#define UART_BR_57600                57600ul                //baudrate=57600 - upper limit for 24Mhz F_CPU
#define UART_BR_115200                115200ul        //baudrate=115200


//global variables
//softuart_isr handler
//install this in the timer
void uartx_isr(void);

//reset softuart_isr
void uartx_init(unsigned long baud);

//transmit a string
void uartx_puts(unsigned char * str);

//if uart is busy, return 1
unsigned char uartx_busy(void);

#endif
We are using timer1 to generate the baud rate and PORTC.2 as the tx pin.
//================uartxisr.c==========================
#include <htc.h>                                        //we use picc
//#include <stm32f10x.h>
//#include "stm32f10x_gpio.h"
//#include "stm32f10x_tim.h"                        //we use timer
#include "gpio.h"                                        //we use own macros
#include "tmr1.h"                                        //we use tmr1 as baud rate generator
#include "uartxisr.h"                                //we use software uart

//hardware configuration
//end hardware configuration

//global defines

//global variables
static unsigned char *_UxTX_ptr;
static unsigned char _UxTX_BUSY=0;                //0=u1 transmission done, 1=u1 transmission in process
static unsigned short _UxTX_MASK=0;                //current bit being transmitted. 0=end of transmission for the current char
static unsigned short _UxTX_buffer;                //software shift register for the transmiter
unsigned short _Ux_OFFSET;                                //timer offset for uart baud rate


//softuart_isr handler
//install this in the timer
void uartx_isr(void) {
        //IO_FLP(UART_PORT, UART_TX);        //flip the pin - for debugging
        if (_UxTX_MASK!=0x0400) {                                //current char isn't fully transmitted
                if (_UxTX_MASK & _UxTX_buffer) UART_SET(UART_TX);        //send '1'
                else UART_CLR(UART_TX);                //send '0'
                _UxTX_MASK = _UxTX_MASK << 1;        //shift to the next bit
        } else {                                                //current char has been fully transmitted
                if (*_UxTX_ptr)        {                        //current char is not a null char
                        _UxTX_ptr+=1;                        //increment to the next character
                        _UxTX_MASK = 0x0001;        //1 start bit, 8 data bits, 1 stop bits = 10 bits
                        _UxTX_buffer = UART_SR(*_UxTX_ptr);        //form the buffer to be transmitted
                } else {                                        //current char is a null char -> end of transmission
                        _UxTX_BUSY = 0;                        //uartx no longer busy
                        /* TIM IT enable */
                        //TIM_ITConfig(UART_TIM, TIM_IT_Update, DISABLE);        //don't start the isr yet
                        TMR1IE = 0;                                //disable tmr1 interrupt
                }
        }
}

//reset softuart_isr
void uartx_init(unsigned long baud) {
        //set up the pin
        IO_SET(UART_PORT, UART_TX);                //tx idles high
        IO_OUT(UART_DDR, UART_TX);                //tx as output

        //set up the baud rate generator
        tmrx_init(0, F_CPU / baud);                //set up timer
        tmrx_act(uartx_isr);                        //install the handler
        //clear the bits
        //TIM_ClearITPendingBit(UART_TIM, TIM_IT_Update);
        TMR1IF = 0;
        /* TIM IT enable */
        //TIM_ITConfig(UART_TIM, TIM_IT_Update, DISABLE);        //don't start the isr yet
        TMR1IE = 1;

        _UxTX_BUSY = 0;                                        //uartx not busy
}

//transmit a string
void uartx_puts(unsigned char * str) {
        _UxTX_BUSY = 1;                                        //uartx is busy
        _UxTX_ptr = str;
        _UxTX_MASK = 0x0001;                        //1 start bit, 8 data bits, 1 stop bits = 10 bits
        _UxTX_buffer = UART_SR(*_UxTX_ptr);        //form the buffer to be transmitted

        //clear the bits
        //TIM_ClearITPendingBit(UART_TIM, TIM_IT_Update);
        TMR1IF = 1;                        //force loading of offsets in the isr
        /* TIM IT enable */
        //TIM_ITConfig(UART_TIM, TIM_IT_Update, ENABLE);        //enable the transmission
        TMR1IE = 1;
}

//if uart is busy, return 1
unsigned char uartx_busy(void) {
        return _UxTX_BUSY;
}

Three routines are involved: uartx_init() initialize the module and sets the baud rate; uartx_puts() sends a string, and uartx_busy() tests if the uartx module is busy.

You can see great similarity between this code base and the one for STM32F.

here is the application code:
//==============main.c===================
#include <htc.h>                                                //we use picc
#include "config.h"                                                //configuration words
#include "gpio.h"
#include "delay.h"                                                //we use software delays
#include "tmr1.h"                                                //we use timer1 for uartxisr
#include "uartxisr.h"                                        //we use software uart isr

//hardware configuration
#define LED_PORT                        PORTC
#define LED_DDR                                TRISC
#define LED                                        (1<<0)
#define LED_DLY                                100                        //delay, in ms
//end hardware configuration

//global defines

//global variables
unsigned char uRAM[]="16F684 UARTXISR...\n\r";        //test string to be sent

//isr
void interrupt isr(void) {
        UARTX_TX_ISR();                                                //uartx transmitter isr
}

//reset the mcu
void mcu_init(void) {
        ANSEL = 0x00;                                                //all pins digital
        CMCON0= 0x07;                                                //comparators off
       
        IO_CLR(LED_PORT, LED);                                //clear led
        IO_OUT(LED_DDR, LED);                                //led as output
}

int main(void) {
       
        mcu_init();                                                        //initialize the mcu
        uartx_init(UART_BR_1200);                        //set the baud rate
        ei();                                                                //enable interrupt
        while (1) {
                if (!uartx_busy()) {                        //uart is not busy
                        uartx_puts(uRAM);                        //send the string
                }
                IO_FLP(LED_PORT, LED);                        //flip led
                //delay_ms(LED_DLY);                                //waste some time
        }
}

It is fairly simple: sets up the uart to send a string at 1200bps, and then flip a pin (PORTC.0).

Here is the output:



ronic 发表于 2013-8-18 08:52:46

good, i raise you

millwood0 发表于 2013-8-18 08:52:49

本帖最后由 millwood0 于 2013-8-18 08:53 编辑

Now, on to 89C51. It is actually a lot easier as the chip has a vectored interrupt controller.
#ifndef _UARTXISR_H
#define _UARTXISR_H

//#include "stm32f10x_rcc.h"                                //we use rcc
//#include "stm32f10x_tim.h"                                //we use timer

//hardware configuration
//softuart pin definitions
#define UART_PORT                P2
#define UART_DDR                P2
#define UART_TX                        (1<<2)                //tx pin

#define UART_SET(tx)        IO_SET(UART_PORT, tx)
#define UART_CLR(tx)        IO_CLR(UART_PORT, tx)

//pick the timer to be used
//#define UART_TIM                TIM3
#define tmrx_init                tmr1_init
#define tmrx_act                tmr1_act
#include "tmr1.h"                                                //we use timer
//end hardware configuration

//insert into the isr
#define UARTX_TX_ISR()                                \
        //if (TMR1IF) {                                        \
                TMR1H = tmr1_offset>>8;                \
                TMR1L = tmr1_offset/* & 0x00ff*/;        \
        //        TMR1IF = 0;                                        \
        //        _tmr1_isr_ptr();                        \
        //}       

//uartx protocol
//1 start bit
//8 data bits
//1 stop bit
//lsb first
#define UART_SR(dat)                (0x0200 | ((dat) << 1) | 0x0000)        //form the uart shift register. 1 start bit (low), 8 data bits, 1 stop bit (high), lsb first

//global defines
//define baud rates
#define UART_BR_300                        300ul                //baudrate=300 - it overflows under 24Mhz F_CPU
#define UART_BR_600                        600ul                //baudrate=600
#define UART_BR_1200                1200ul                //baudrate=1200
#define UART_BR_2400                2400ul                //baudrate=2400
#define UART_BR_4800                4800ul                //baudrate=4800
#define UART_BR_9600                9600ul                //baudrate=9600
#define UART_BR_19200                19200ul                //baudrate=19200
#define UART_BR_38400                38400ul                //baudrate=38400
#define UART_BR_57600                57600ul                //baudrate=57600 - upper limit for 24Mhz F_CPU
#define UART_BR_115200                115200ul        //baudrate=115200


//global variables
//softuart_isr handler
//install this in the timer
void uartx_isr(void);

//reset softuart_isr
void uartx_init(unsigned long baud);

//transmit a string
void uartx_puts(unsigned char * str);

//if uart is busy, return 1
unsigned char uartx_busy(void);

#endif
the corresponding .c file
//===============uartxisr.c===================
//#include <htc.h>                                        //we use picc
#include <regx51.h>                                        //we use keil c51
//#include <stm32f10x.h>
//#include "stm32f10x_gpio.h"
//#include "stm32f10x_tim.h"                        //we use timer
#include "gpio.h"                                        //we use own macros
#include "tmr1.h"                                        //we use tmr1 as baud rate generator
#include "uartxisr.h"                                //we use software uart

//hardware configuration
//end hardware configuration

//global defines

//global variables
static unsigned char *_UxTX_ptr;
static unsigned char _UxTX_BUSY=0;                //0=u1 transmission done, 1=u1 transmission in process
static unsigned short _UxTX_MASK=0;                //current bit being transmitted. 0=end of transmission for the current char
static unsigned short _UxTX_buffer;                //software shift register for the transmiter
unsigned short _Ux_OFFSET;                                //timer offset for uart baud rate


//softuart_isr handler
//install this in the timer
void uartx_isr(void) {
        //IO_FLP(UART_PORT, UART_TX);        //flip the pin - for debugging
        //load the offset
        UARTX_TX_ISR();                                        //realod the offset
        if (_UxTX_MASK!=0x0400) {                                //current char isn't fully transmitted
                if (_UxTX_MASK & _UxTX_buffer) UART_SET(UART_TX);        //send '1'
                else UART_CLR(UART_TX);                //send '0'
                _UxTX_MASK = _UxTX_MASK << 1;        //shift to the next bit
        } else {                                                //current char has been fully transmitted
                if (*_UxTX_ptr)        {                        //current char is not a null char
                        _UxTX_ptr+=1;                        //increment to the next character
                        _UxTX_MASK = 0x0001;        //1 start bit, 8 data bits, 1 stop bits = 10 bits
                        _UxTX_buffer = UART_SR(*_UxTX_ptr);        //form the buffer to be transmitted
                } else {                                        //current char is a null char -> end of transmission
                        _UxTX_BUSY = 0;                        //uartx no longer busy
                        /* TIM IT enable */
                        //TIM_ITConfig(UART_TIM, TIM_IT_Update, DISABLE);        //don't start the isr yet
                        //TMR1IE = 0;                                //disable tmr1 interrupt
                        ET1 = 0;                                //turn off tmr1 interrupt
                }
        }
}

//reset softuart_isr
void uartx_init(unsigned long baud) {
        //set up the pin
        IO_SET(UART_PORT, UART_TX);                //tx idles high
        IO_OUT(UART_DDR, UART_TX);                //tx as output

        //set up the baud rate generator
        tmrx_init(0, F_CPU / baud);                //set up timer
        tmrx_act(uartx_isr);                        //install the handler
        //clear the bits
        //TIM_ClearITPendingBit(UART_TIM, TIM_IT_Update);
        //TMR1IF = 0;
        TF1 = 0;
        /* TIM IT enable */
        //TIM_ITConfig(UART_TIM, TIM_IT_Update, DISABLE);        //don't start the isr yet
        //TMR1IE = 1;
        ET1 = 1;

        _UxTX_BUSY = 0;                                        //uartx not busy
}

//transmit a string
void uartx_puts(unsigned char * str) {
        _UxTX_BUSY = 1;                                        //uartx is busy
        _UxTX_ptr = str;
        _UxTX_MASK = 0x0001;                        //1 start bit, 8 data bits, 1 stop bits = 10 bits
        _UxTX_buffer = UART_SR(*_UxTX_ptr);        //form the buffer to be transmitted

        //clear the bits
        //TIM_ClearITPendingBit(UART_TIM, TIM_IT_Update);
        //TMR1IF = 1;                        //force loading of offsets in the isr
        TF1 = 1;
        /* TIM IT enable */
        //TIM_ITConfig(UART_TIM, TIM_IT_Update, ENABLE);        //enable the transmission
        //TMR1IE = 1;
        ET1 = 1;
}

//if uart is busy, return 1
unsigned char uartx_busy(void) {
        return _UxTX_BUSY;
}
The calling convention is the same:
//==============main.c=================
#include <regx51.h>                                                        //we use keil c51
#include "gpio.h"
#include "delay.h"                                                        //we use software delay
#include "tmr1.h"                                                        //we use timer
#include "uartxisr.h"                                                //we use software uartx

//hardware configuration
#define LED_PORT                P2
#define LED_DDR                        P2
#define LED                                (1<<0)
#define LED_DLY                        100
//end hardware configuration

//global defines

//global variables
unsigned char uRAM[]="89C51 UARTX_ISR...\n\r";

void mcu_init(void) {
        IO_CLR(LED_PORT, LED);                                        //clear led
        IO_OUT(LED_DDR, LED);                                        //led as output
}

int main(void) {
        mcu_init();                                                                //reset the mcu
        uartx_init(UART_BR_1200);                                //set the baud rate
        ei();                                                                        //enable interrupt
        while (1) {
                if (!uartx_busy()) {                                //uartx is not busy
                        uartx_puts(uRAM);                                //send the string
                }
                IO_FLP(LED_PORT, LED);                                //flip led
                //delay_ms(LED_DLY);                                //waste some time
        }
}
The output:

页: [1]
查看完整版本: Interrupt-drive software uart