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