/*! \file uartsw.c \brief Software Interrupt-driven UART function library. */ //***************************************************************************** // // File Name : 'uartsw.c' // Title : Software Interrupt-driven UART function library // Author : Pascal Stang - Copyright (C) 2002-2004 // Created : 7/20/2002 // Revised : 4/27/2004 // Version : 0.1 // Target MCU : Atmel AVR Series (intended for the ATmega16 and ATmega32) // Editor Tabs : 4 // // This code is distributed under the GNU Public License // which can be found at http://www.gnu.org/licenses/gpl.txt // //***************************************************************************** #include #include #include #include "global.h" #include "timer.h" #include "uartsw.h" // Program ROM constants // Global variables // uartsw transmit status and data variables static volatile u08 UartswTxBusy; static volatile u08 UartswTxData; static volatile u08 UartswTxBitNum; // baud rate common to transmit and receive static volatile u16 UartswBaudRateDiv; // uartsw receive status and data variables static volatile u08 UartswRxBusy; static volatile u08 UartswRxData; static volatile u08 UartswRxBitNum; // receive buffer static cBuffer uartswRxBuffer; ///< uartsw receive buffer // automatically allocate space in ram for each buffer static char uartswRxData[UARTSW_RX_BUFFER_SIZE]; // functions //! enable and initialize the software uart void uartswInit(void) { // initialize the buffers uartswInitBuffers(); // initialize the ports sbi(UARTSW_TX_DDR, UARTSW_TX_PIN); cbi(UARTSW_RX_DDR, UARTSW_RX_PIN); cbi(UARTSW_RX_PORT, UARTSW_RX_PIN); // initialize baud rate uartswSetBaudRate(9600); // setup the transmitter UartswTxBusy = FALSE; // disable OC1A interrupt cbi(TIMSK, OCIE1A); // attach TxBit service routine to OC1A timerAttach(TIMER1OUTCOMPAREA_INT, uartswTxBitService); // setup the receiver UartswRxBusy = FALSE; // disable OC1B interrupt cbi(TIMSK, OCIE1B); // attach RxBit service routine to OC1B timerAttach(TIMER1OUTCOMPAREB_INT, uartswRxBitService); // attach RxBit service routine to ICP timerAttach(TIMER1INPUTCAPTURE_INT, uartswRxBitService); // trigger on rising edge sbi(TCCR1B, ICES1); // enable ICP interrupt sbi(TIMSK, TICIE1); // turn on interrupts sei(); } //! create and initialize the uart buffers void uartswInitBuffers(void) { // initialize the UART receive buffer bufferInit(&uartswRxBuffer, uartswRxData, UARTSW_RX_BUFFER_SIZE); } //! turns off software UART void uartswOff(void) { // disable interrupts cbi(TIMSK, OCIE1A); cbi(TIMSK, OCIE1B); cbi(TIMSK, TICIE1); // detach the service routines timerDetach(TIMER1OUTCOMPAREA_INT); timerDetach(TIMER1OUTCOMPAREB_INT); timerDetach(TIMER1INPUTCAPTURE_INT); } void uartswSetBaudRate(u32 baudrate) { // set timer prescaler timer1SetPrescaler(TIMER_CLK_DIV1); // calculate division factor for requested baud rate, and set it UartswBaudRateDiv = (u16)((F_CPU+(baudrate/2L))/(baudrate*1L)); } //! returns the receive buffer structure cBuffer* uartswGetRxBuffer(void) { // return rx buffer pointer return &uartswRxBuffer; } void uartswSendByte(u08 data) { // wait until uart is ready while(UartswTxBusy); // set busy flag UartswTxBusy = TRUE; // save data UartswTxData = data; // set number of bits (+1 for stop bit) UartswTxBitNum = 9; // set the start bit #ifdef UARTSW_INVERT sbi(UARTSW_TX_PORT, UARTSW_TX_PIN); #else cbi(UARTSW_TX_PORT, UARTSW_TX_PIN); #endif // schedule the next bit outw(OCR1A, inw(TCNT1) + UartswBaudRateDiv); // enable OC1A interrupt sbi(TIMSK, OCIE1A); } //! gets a byte (if available) from the uart receive buffer u08 uartswReceiveByte(u08* rxData) { // make sure we have a receive buffer if(uartswRxBuffer.size) { // make sure we have data if(uartswRxBuffer.datalength) { // get byte from beginning of buffer *rxData = bufferGetFromFront(&uartswRxBuffer); return TRUE; } else { // no data return FALSE; } } else { // no buffer return FALSE; } } void uartswTxBitService(void) { if(UartswTxBitNum) { // there are bits still waiting to be transmitted if(UartswTxBitNum > 1) { // transmit data bits (inverted, LSB first) #ifdef UARTSW_INVERT if( !(UartswTxData & 0x01) ) #else if( (UartswTxData & 0x01) ) #endif sbi(UARTSW_TX_PORT, UARTSW_TX_PIN); else cbi(UARTSW_TX_PORT, UARTSW_TX_PIN); // shift bits down UartswTxData = UartswTxData>>1; } else { // transmit stop bit #ifdef UARTSW_INVERT cbi(UARTSW_TX_PORT, UARTSW_TX_PIN); #else sbi(UARTSW_TX_PORT, UARTSW_TX_PIN); #endif } // schedule the next bit outw(OCR1A, inw(OCR1A) + UartswBaudRateDiv); // count down UartswTxBitNum--; } else { // transmission is done // clear busy flag UartswTxBusy = FALSE; } } void uartswRxBitService(void) { // this function runs on either: // - a rising edge interrupt // - OC1B if(!UartswRxBusy) { // this is a start bit // disable ICP interrupt cbi(TIMSK, TICIE1); // schedule data bit sampling 1.5 bit periods from now outw(OCR1B, inw(TCNT1) + UartswBaudRateDiv + UartswBaudRateDiv/2); // clear OC1B interrupt flag sbi(TIFR, OCF1B); // enable OC1B interrupt sbi(TIMSK, OCIE1B); // set start bit flag UartswRxBusy = TRUE; // reset bit counter UartswRxBitNum = 0; // reset data UartswRxData = 0; } else { // start bit has already been received // we're in the data bits // shift data byte to make room for new bit UartswRxData = UartswRxData>>1; // sample the data line #ifdef UARTSW_INVERT if( !(inb(UARTSW_RX_PORTIN) & (1<= 8) { // save data in receive buffer bufferAddToEnd(&uartswRxBuffer, UartswRxData); // disable OC1B interrupt cbi(TIMSK, OCIE1B); // clear ICP interrupt flag sbi(TIFR, ICF1); // enable ICP interrupt sbi(TIMSK, TICIE1); // clear start bit flag UartswRxBusy = FALSE; } } } /* void uartswRxBitService(void) { u16 thisBitTime; u08 bitperiods; u08 i; // bit transition was detected // record bit's edge time thisBitTime = inw(ICR1); cbi(PORTB, 0); if(!UartswRxStartBit) { // this is a start bit // switch to falling-edge trigger cbi(TCCR1B, ICES1); // record bit time UartswRxBitTime = thisBitTime; // set start bit flag UartswRxStartBit = TRUE; // reset bit counter UartswRxBitNum = 0; // reset data UartswRxData = 0; } else { // start bit has already been received // we're in the data bits // how many bit periods since last edge? bitperiods = (thisBitTime - UartswRxBitTime + UartswBaudRateDiv/2)/UartswBaudRateDiv; // set last edge time UartswRxBitTime = thisBitTime; if(bitperiods > 10) { // switch to trigger on rising edge sbi(TCCR1B, ICES1); // clear start bit flag UartswRxStartBit = FALSE; } else { if( inb(TCCR1B) & (1< 8) { // save data in receive buffer bufferAddToEnd(&uartswRxBuffer, UartswRxData); // switch to trigger on rising edge sbi(TCCR1B, ICES1); // clear start bit flag UartswRxStartBit = FALSE; } } } // turn off debug LEDs delay(10); sbi(PORTB, 0); sbi(PORTB, 1); } */