PIC Specific RS232 routine

Rob Hamerling's 57.6kbps interrupt driven 16F628 test circuit and program

// ---------------------------------------------------------------------
// S628.c     Study/experiment with interrupt driven serial
//            communications with a PIC-equiped device as DCE.
//
// Author:    Rob Hamerling.
// Date:      January 2003.
// E-mail:    robh@hccnet.nl
// homepahe:  http://www.robh.nl
// ---------------------------------------------------------------------
//
//  Function: echo incoming datastream from 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).
//  - 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 'reversed' logic the signals are called
//   here CTSrev and RTSrev.
//
// -------------------------------------------------------------------------
//  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 |=  0x3FFF                      // all on initially
#pragma  config &= ~0x0080                      // LVP off -> RB4 I/O
#pragma  config FOSC=HS                         // 20MHz crystal
#pragma  config WDTE=off                        // watch dog disabled
#pragma  config ID=6280                         // firmware ID (optional)

#pragma  bit CTSrev  @ PORTA.2                  // CTS signal to DTE (PC)
#pragma  bit RTSrev  @ PORTB.0                  // RTS signal from DTE (PC)

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  TMR0COUNT   (OSCFREQ/8/16/1000)        // for 1 ms delay (OPTION = 4)

#define  BPSRATE     57600                      // desired speed
#define  BPSCLASS    TRUE                       // BRGH setting (high)
#define  BPSCOUNT    ((10*OSCFREQ/16/BPSRATE-5)/10 - 1)  // SPBRG (BRGH=1)
                                                // closest integer value

#define  XMTBUFSIZE  32                 // (power of 2) output buffer size
#define  RCVBUFSIZE  64                 // (power of 2) input buffer size
#define  DELTA       17                         // minimum free rcv buffer ..
                                                // .. space (PC UARTFiFo + 1)

char     xmtoffset;                             // offset next byte to xmit
char     putoffset;                             // offset last appl. out byte
char     rcvoffset;                             // offset next byte to receive
char     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
      xmtoffset = (xmtoffset + 1) & (XMTBUFSIZE - 1);   // update offset
      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?)
      x = RCREG;                                // read and discard byte
    else {                                      // data without errors
      rcvbuf[rcvoffset] = RCREG;                // move byte to rcv buffer
      x = (rcvoffset + 1) & (RCVBUFSIZE - 1);   // offset next byte
      if (x != getoffset)                       // buffer not yet full
        rcvoffset = x;                          // update offset,
                                                // (else discard byte,
                                                //  CTS flow control failed)

      if (CTSrev == FALSE) {                    // CTS true!
        if (rcvoffset > getoffset)              // circular buffer situation
          x = RCVBUFSIZE - rcvoffset + getoffset;  // free buffer space
        else                                    // other situation
          x = getoffset - rcvoffset;            // free buffer space
        if (x <= DELTA) {                       // buffer reaches 'full'
          CTSrev = TRUE;                        // drop CTS (CTS FALSE)
          }
        }
      }
    }

  /* Note: All other interrupts disabled, so no further checks needed */

  FSR = save_FSR;                               // restore FSR
  int_restore_registers                         // restore other

  }


// -------------------------------------------------------------------
//  Milliseconds delay by polling TMR0
//
//  See top of source for the calculation of TMR0COUNT,
//  depending on oscillator frequency and prescaling via OPTION.
// -------------------------------------------------------------------
static void msdelay(char millisec) {

  do  {
    TMR0 = 0;
    while (TMR0 < TMR0COUNT)                    // pause of 1 millisecond
      ;
    } while (--millisec > 0);                   // number of milliseconds
  }


// -----------------------------------------------
//  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) & (XMTBUFSIZE - 1);     // offset next char
    while (x == xmtoffset)                      // buffer full!
      ;                                         // spin until something xmit'd
    putoffset = x;                              // update offset
    TXIE = TRUE;                                // (re-)enable xmit interrupts
    }
  }


// ----------------------------------------------------------------
//  copy input bytes to caller
//     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
    getoffset = (getoffset + 1) & (RCVBUFSIZE - 1);   // update offset

    }                                             // to caller

  if (CTSrev == TRUE) {                         // CTS FALSE)
    if (rcvoffset > getoffset)                  // circular buffer situation
      x = RCVBUFSIZE - rcvoffset + getoffset;   // free buffer space
    else                                        // other situation
      x = getoffset - rcvoffset;                // free buffer space
    if (x >= DELTA) {                           // enough free space now
      CTSrev = FALSE;                           // rise CTS (CTS TRUE)
      }
    }

  return i;                                     // number of bytes returned
  }


// -------------------------------------------------------
//  Perform all required PIC setup
// -------------------------------------------------------
static void setup() {

  CMCON   = 0b0000.0111;                        // Comparator off
  CCP1CON = 0b0000.0000;                        // Capt/Comp/PWM off

  OPTION = 3;                                   // TMR0 prescaler 1:16
  INTCON = 0;                                   // all interrupt bits off
  PIR1   = 0;                                   //  ..

  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
  GIE    = TRUE;                                // globally enable interrupts

  }


// ===============================================================
//
//   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

  setup();                                      // init PIC

  for (;;) {                                    // forever
    CTSrev = TRUE;                              // CTS FALSE
    while (RTSrev == TRUE)                      // wait for rise of RTS (DTE)
      ;
    CTSrev = FALSE;                             // CTS TRUE

    xmtoffset = 0;                              // (re-)init ..
    putoffset = 0;                              //  .. input and ..
    rcvoffset = 0;                              //   .. output ..
    getoffset = 0;                              //    .. buffer offsets

    while (RTSrev == FALSE) {                   // RTS TRUE
      l = getdata(buffer, sizeof(buffer));      // get input
      if (l > 0)                                // something received
        putdata(buffer, l);                     // echo the input
      else                                      // nothing
        msdelay(10);                            // do 'low priority' work
      }

    }

  }

Interested:

Questions: