Code:
// --------------------------------------------------------------------- // S628.c Study/experiment with interrupt driven serial // communications with a PIC-equiped device as DCE. // // Author: Rob Hamerling. // Date: February 2004. // E-mail: info@robh.nl // homepage: http://www.robh.nl // --------------------------------------------------------------------- // // Function: echo incoming datastream from DTE back to DTE // Features: // - Interrupt driven, high speed, full duplex data flow (57600 bps) // - Use of builtin USART in RS232 mode (8 bits, no parity). // - With relatively large receive buffer. // - Using CTS flow control (PC -> PIC). // PC-side should have set CTS output flow control enabled, // PC FiFo transmit load count may be set to 16 (max). // - While DTE inactive (RTS false) the PIC slumbers. It gives a 'being // alive' signal by slowly flashing the RTS light. It is waked-up by // RB0 (to which RTS is connected) // - Note: no RTS flow control (PIC -> PC)! // // Language support: CC5X compiler version 3.1 // // Hardware: PIC 16F628 or similar with UART, and MAX232. // // // --------------------------------------------------------------------- // // Simplified schematics: // COMx plug // PIC16F628 MAX232 RS232 DB9 DB25 // +-------------+ +-----------+ // | | | | // | RA2 (1)|-----|(11)---(14)|--->-- CTS --- 8 5 // | RB0 (6)|-----|(12)---(13)|---<-- RTS --- 7 4 // | | | | // | RB1 (7)|-----|(9)-----(8)|---<-- TxD --- 3 2 // | RB2 (8)|-----|(10)----(7)|--->-- RxD --- 2 3 // | | | | // | (5) | | (15) | // +------|------+ +----|------+ // +-----------------+----------- GND --- 5 7 // // +-< DTR --- 4 20 // Optional cable wraps: | // (maybe required by PC softw.) +-> DSR --- 6 6 // +-> DCD --- 1 8 // // --------------------------------------------------------------------- // Some basic PIC and RS232 knowledge will be needed to fully understand // the data flow and control signalling used in this program. // // The PIC ports use positive logic: // '1' is positive voltage, '0' is ground. // // This program uses positive logic for boolean variables: // the symbol TRUE for '1', the symbol FALSE for '0'. // // In the RS232 standard: // - Negative voltage ('mark') means OFF for control signals, and // indicates 1 (one) for a data signals (start-, data-, stop-bits). // - Positive voltage ('space') means ON for control signals and // 0 (zero) for start-, data- and stop-bits. // // Since the MAX232 is not only a level convertor (between TTL and RS232) // but also a signal inverter, you should be aware of the following: // - The inversion of PIC data-in and data-out by the MAX232 is required // to convert data-, start- and stop-bits to/from the corresponding // RS232 polarity. So nothing special has to be done in the program. // - For RTS and CTS the inversion by the MAX232 inversion is NOT desired, // and therefore the program uses inverted signaling for RTS and CTS: // 'FALSE' is used for ON and 'TRUE' for OFF with RTS/CTS signals! // As a reminder for this 'inversed' logic the signals are called // here CTSinv and RTSinv. // // ------------------------------------------------------------------------- // For other examples and useful learning material see also: // - MicroChip datasheets (for the PIC16F62X: DS30400C). // - Tony Kubek's example for the PIC16F876, // ASM, interrupt driven, no CTS flow control, single byte buffer. // - Fr. Thomas MacGhee's example for the PIC16C74, // ASM, not interrupt driven, but contains many educational notes. // ------------------------------------------------------------------------- #pragma chip PIC16F628 // target PIC #include <int16cxx.h> // interrupt support #pragma config &= ~0b11.1111.1111.1111 // all OFF #pragma config |= 0b11.1111.0110.0110 // x xx FOSC = HS // x WDT enabled // x x BOD enabled (forces /PWRTE) // x LVP disabled (makes RB4 free) // xxxxxxx no memory protection #pragma config ID = 0x6281 // firmware ID (optional) #pragma bit CTSinv @ PORTA.2 // CTS signal to DTE (PC) #pragma bit RTSinv @ PORTB.0 // RTS signal from DTE (PC) #pragma bit RTSled @ PORTB.3 // visual RTS signal typedef bit BOOL, BOOLEAN; // boolean variable type(s) #define FALSE 0 // PIC: off, low #define TRUE 1 // PIC: on, high #define OSCFREQ 20000000 // oscillator frequency #define TMR1COUNT (OSCFREQ/4/1000) // 16-bits count for 1 ms // (prescaler 1:1) #define BPSRATE 57600 // desired speed #define BPSCLASS TRUE // BRGH setting (high) #define BPSCOUNT ((10*OSCFREQ/16/BPSRATE-5)/10) // SPBRG (BRGH=1) // closest integer value #define XMTBUFSIZE 32 // output buffer size #define RCVBUFSIZE 64 // input buffer size #define DELTA 17 // minimum free rcv buffer .. // .. space (PC UARTFiFo + 1) int8 xmtoffset; // offset next byte to xmit int8 putoffset; // offset last appl. out byte int8 rcvoffset; // offset next byte to receive int8 getoffset; // offset last appl. in byte char xmtbuf[XMTBUFSIZE]; // circular output buffer bank1 char rcvbuf[RCVBUFSIZE]; // circular input buffer // located in RAM bank1! // ---------------------------- // Interrupt service routine // ---------------------------- #pragma origin 4 // hardware requirement extern interrupt isr(void) { char save_FSR; // FSR save byte char x; // intermediate byte value int_save_registers // save registers save_FSR = FSR; // save FSR if (TXIF == TRUE && TXIE == TRUE) { // RS232 transmit interrupt if (xmtoffset != putoffset) { // still data in xmit buffer x = xmtbuf[xmtoffset]; // next char to xmit if (++xmtoffset >= XMTBUFSIZE) // update offset xmtoffset = 0; // wrap if (xmtoffset == putoffset) // was this last byte? TXIE = FALSE; // disable xmit interrupts TXREG = x; // now actually xmit char } } if (RCIF == TRUE && RCIE == TRUE) { // RS232 receive interrupt if (OERR == TRUE) { // overrun, reset UART CREN = FALSE; // disable UART CREN = TRUE; // re-enable UART } // discard pending bytes else if (FERR == TRUE) { // framing error (break?) getoffset = 0; // flush buffers xmtoffset = 0; putoffset = 0; rcvoffset = 1; rcvbuf[0] = RCREG; // move byte to rcv buffer CTSinv = TRUE; // ensure CTS is true } else { // data without errors rcvbuf[rcvoffset] = RCREG; // move byte to rcv buffer x = rcvoffset + 1; // offset next byte if (x >= RCVBUFSIZE) // beyond buffer boundary x = 0; // wrap to begin if (x != getoffset) // buffer not yet full rcvoffset = x; // update offset, // (else discard byte, // CTS flow control failed) if (CTSinv == FALSE) { // CTS is TRUE x = getoffset - rcvoffset; // offset difference if (x <= 0) // wrapping effect x += RCVBUFSIZE; // wrapping correction if (x < DELTA) // buffer reaches 'full' CTSinv = TRUE; // make CTS FALSE } } } if (INTE == TRUE && INTF == TRUE) { // RB0 change interrupt // nothing to do, just .. INTF = FALSE; // .. wake-up from sleep } /* Note: Other interrupts disabled, so no further checks needed */ FSR = save_FSR; // restore FSR int_restore_registers // restore other } // ----------------------------------------------- // copy output bytes of caller // from: application buffer // to: interrupt controlled transmit buffer // // returns nothing // // notes: - initiates transmission (interrupt handler) // when not currently transmitting // - spin when transmission buffer full // (wait for free buffer space) // ----------------------------------------------- static void putdata(char *buffer, char bytesout) { char i; // counter(s) char x; // intermediate byte value for (i=0; i<bytesout; i++) { // all user data x = buffer[i]; // copy char xmtbuf[putoffset] = x; // .. to buffer x = putoffset + 1; // next char if (x >= XMTBUFSIZE) // beyond buffer boundary x = 0; while (x == xmtoffset) // buffer full! ; // spin until something xmit'd putoffset = x; // update offset TXIE = TRUE; // (re-)enable xmit interrupts } } // ---------------------------------------------------------------- // copy input bytes to callers buffer // from: interrupt controlled receive buffer // to: application buffer // returns: number of bytes actually stored in application buffer // // notes: - rise CTS when receive buffer has more than <DELTA>> // bytes free space after delivering data to caller. // ---------------------------------------------------------------- static char getdata(char *buffer, // application buffer char bufsize) { // size of appl. buffer char i, x; for (i=0; i<bufsize; i++) { // fill user buffer (max) if (getoffset == rcvoffset) // no more data break; x = rcvbuf[getoffset]; // copy char buffer[i] = x; // .. to user buffer if (++getoffset >= RCVBUFSIZE) // update offset getoffset = 0; } if (CTSinv == TRUE) { // (CTS is FALSE) x = getoffset - rcvoffset; // offset difference if (x <= 0) // wrapping effect x += RCVBUFSIZE; // wrapping correction if (x >= DELTA) // enough free space now CTSinv = FALSE; // (make CTS TRUE) } return i; // number of bytes returned } // ------------------------------------------------------- // Perform all required initial PIC setup // ------------------------------------------------------- static void setup() { CMCON = 0b0000.0111; // Comparator off CCP1CON = 0b0000.0000; // Capt/Comp/PWM off OPTION = 0b0000.1111; // WDT prescaler 1:128 T1CON = 0b0000.0001; // Timer1 enabled, presc 1:1 INTCON = 0; // all interrupt bits off PIR1 = 0; // .. INTEDG = 0; // int at falling edge RB0 // (= rising of RTS!) PORTA = 0; // all ports zero PORTB = 0; // .. TRISA = 0b0010.0000; // IN: RA5/MCLR TRISB = 0b0001.0011; // IN: RB0,1,4 PIE1 = 0; // disable all ext. interrupts BRGH = BPSCLASS; // baudrate class SPBRG = BPSCOUNT; // baudrate clock divisor TXEN = TRUE; // enable UART transmit SYNC = FALSE; // async mode RCIE = TRUE; // enable receive interrupts SPEN = TRUE; // enable UART CREN = TRUE; // enable UART receive PEIE = TRUE; // enable external interrupts INTE = TRUE; // RB0 (RTSinv) change GIE = TRUE; // globally enable interrupts } // ------------------------------------------------------------------- // Milliseconds delay by using TMR1 as 16-bits counter // // See top of source for the value of TMR1COUNT // ------------------------------------------------------------------- static void msdelay(char millisec) { uns16 usTimeCount; // value of Timer1 do { TMR1H = 0; // restart Timer1 .. TMR1L = 0; // .. counting do { usTimeCount = (uns16)TMR1H << 8; // take high byte value usTimeCount += TMR1L; // aad low byte value } while (usTimeCount < TMR1COUNT); // pause 1 millisecond } while (--millisec > 0); // number of milliseconds } // ------------------------------------------------------------------- // Keep PIC in slumbering state: sleep most of the time // // Flash a LED as visual 'being alive' signal // // ------------------------------------------------------------------- static void waitforRTS(void) { char ucOptionSave; // option reg at entry ucOptionSave = OPTION; // save OPTION register while (RTSinv == TRUE) { // waiting for RTS OPTION = 0b0000.1111; // WDT postscaler 1:128 sleep(); // wait for RB0 or Watchdog RTSled = TRUE; // RTS LED on OPTION = 0b0000.1001; // WDT postscaler 1:2 sleep(); // duration of RTS LED flash RTSled = FALSE; // RTS LED off } OPTION = ucOptionSave; // restore OPTION to original } // =============================================================== // // M A I N L I N E // // Initially CTS is set false and the program waits for // RTS to become true before activating the echo loop. // When RTS become true, CTS follows, which allows the // DTE to send data. The echo loop remains active as // long as RTS remains true. When RTS becomes false the // echo-loop is terminated and the PIC reset to its initial // state, waiting for RTS. // // =============================================================== extern void main(void) { char i, k, l; // counter(s) char buffer[20]; // local I/O buffer const char *welcome = "Echo from S628\n\r"; // welcome! setup(); // init PIC for (;;) { // forever RTSled = FALSE; // assume RTS false CTSinv = TRUE; // CTS FALSE waitforRTS(); // in slumbering state RTSled = TRUE; // show RTS status CTSinv = FALSE; // CTS TRUE xmtoffset = 0; // (re-)init .. putoffset = 0; // .. input and .. rcvoffset = 0; // .. output .. getoffset = 0; // .. buffer offsets for (i=0; welcome[i] != '\0'; i++) { // copy msg to I/O buffer k = welcome[i]; buffer[i] = k; } putdata(buffer, i); // send msg to DTE while (RTSinv == FALSE) { // RTS true l = getdata(buffer, sizeof(buffer)); // get input if (l > 0) // something received putdata(buffer, l); // echo the input else // nothing received msdelay(25); // do 'low priority' work clrwdt(); // reset watchdog } } }
Interested:
See:
Comments:
BUg on lineJames Newton replies: Ah! Very common and annoying error. X is being ASSIGNED the value of RCVBUFSIZE rather then being compaired to it. It should be "if (x==RCVBUFSIZE)" and the best coding style to avoid makeing this mistake is to always put the constant first. E.g. "if (RCVBUFSIZE==X)" becuase if you only put in one "=" the compiler will show you the error.
if (x = RCVBUFSIZE) // beyond buffer boundary
x = 0; // wrap to begin
note the missing = in the if statement drove me mad for we while!
Questions: