/* * Serial port driver for 16Cxx mid-range PIC devices * (Interrupt-driven) * * Copyright (C) 1997 HI-TECH Software. * Author: Jeremy Bennett * * Comments: * * The major limiting factor in how fast you can go is * the need to sample the RxData line at several times the * desired baud rate when looking for a start bit. 8 times is * used here - less may be possible. With 8 times sampling, the * maximum baud rate that can be reliably achieved with a 4MHz * PIC is 1200 baud. This results in about a 100uS interrupt interval, * with about 30uS used in the interrupt service routine. * * Of course once the start bit has been recognised, the interrupt * interval could be dropped back to the baud rate, but since it's * pretty axiomatic that a PIC will wait for characters much more * than it will actually receive them, this wouldn't help much, and * would make for additional complication. * * Another approach would be to use the external interrupt pin for the serial input, * and start timing from the interrupt. This would have to be carefully done * to allow simultaneous transmission, though. * * This module compiles to about 125 words on a PIC16C84. * */ #include #include /************************************* * Tunable parameters */ /* Transmit and Receive port bits */ static volatile bit TxData @ (unsigned)&PORTA*8+3; /* bit3 in port A */ static volatile bit RxData @ (unsigned)&PORTA*8+2; /* bit2 in port A */ #define XTAL 20000000 /* Crystal frequency (Hz). */ #define BRATE 1200 /* Baud rate. */ #define RX_OVERSAMPLE 8 /* Amount of oversampling the receiver does. Must be a power of two */ /* * Don't change anything else ************************************/ #define TIMER_VALUE XTAL / (4 * BRATE * RX_OVERSAMPLE) #define TRANSMIT_NUM_BITS 13 // 1 start bit + 8 data bits + 2 stop bits + safe. #if (RX_OVERSAMPLE-1)&RX_OVERSAMPLE #error RX_OVERSAMPLE_value must be a power of 2 #endif // Receiver states. enum receiver_state { RS_HAVE_NOTHING, RS_WAIT_HALF_A_BIT, RS_HAVE_STARTBIT, RS_WAIT_FOR_STOP = RS_HAVE_STARTBIT+8 }; static unsigned char sendbuffer; // Where the character to sent is stored. static unsigned char receivebuffer; // Where the character is stored as it is received. static bit receivebufferfull; // 1 = receivebuffer is full. static unsigned char send_bitno; static unsigned char receivestate; // Initial state of the receiver (0). static unsigned char skipoversamples; // Used to skip receive samples. static unsigned char rxshift; static bit tx_next_bit; /** * init_uart * * Initialises the serial port: * * Sets up the I/O directions for the appropriate PortA pins; * Sets up Timer0. * * */ void init_uart(void) { receivestate = RS_HAVE_NOTHING; skipoversamples = 1; // check each interrupt for start bit TRISA = 0x17; // Set up I/O direction. TRISB = 0xFE; /* Set up the timer. */ T0CS = 0; // Set timer mode for Timer0. TMR0 = (2-TIMER_VALUE); // +2 as timer stops for 2 cycles // when writing to TMR0 T0IE = 1; // Enable the Timer0 interrupt. GIE = 1; } void putch(char c) { while(send_bitno) continue; tx_next_bit = 0; sendbuffer = c; send_bitno = TRANSMIT_NUM_BITS*RX_OVERSAMPLE; } char getch(void) { while(!receivebufferfull) continue; receivebufferfull = 0; return receivebuffer; } bit kbhit(void) { return receivebufferfull; } /** * serial_isr * * Transmits and receives characters which have been * "putch"ed and "getch"ed. * * This ISR runs BRATE * RX_OVERSAMPLE times per second. * * */ interrupt void serial_isr(void) { // Reset Timer0 value // This is added to TMR0 because there is a delay to get to the isr. PORTB |= 1; TMR0 += -TIMER_VALUE + 4; // +2 as timer stops for 2 cycles when writing to TMR0 +2 for tweak T0IF = 0; /*** RECEIVE ***/ if( --skipoversamples == 0) { skipoversamples++; // check next time switch(receivestate) { case RS_HAVE_NOTHING: /* Check for start bit of a received char. */ if(!RxData){ skipoversamples = RX_OVERSAMPLE/2; receivestate++; } break; case RS_WAIT_HALF_A_BIT: if(!RxData) { // valid start bit skipoversamples = RX_OVERSAMPLE; receivestate++; } else receivestate = RS_HAVE_NOTHING; break; // case RS_HAVE_STARTBIT: and subsequent values default: rxshift = (rxshift >> 1) | (RxData << 7); skipoversamples = RX_OVERSAMPLE; receivestate++; break; case RS_WAIT_FOR_STOP: receivebuffer = rxshift; receivebufferfull = 1; receivestate = RS_HAVE_NOTHING; break; } } /*** TRANSMIT ***/ /* This will be called every RX_OVERSAMPLEth time * (because the RECEIVE needs to over-sample the incoming * data). */ if(send_bitno) { if((send_bitno & (RX_OVERSAMPLE-1)) == 0) { TxData = tx_next_bit; // Send next bit. tx_next_bit = sendbuffer & 1; sendbuffer = (sendbuffer >> 1) | 0x80; } send_bitno--; } PORTB &= ~1; }