/*
This file is linked with the documentation ! */
//---------------------------------------------------------------------------
// File: C:\pic\GPSDO\UART_PIC16F1783.c
// Author: Wolfgang Buescher, DL4YHF
// Date: 2015-12-25
// Purpose: Simple UART functions for PIC17(L)F178x, without ISR .
// Development System : Microchip MPLAB IDE v8.85,
// later also "MPLAB X" because "MPLAB" debugger is severely bugged,
// and XC8 C Compiler ("Free Mode") V1.35 .
//
//
//---------------------------------------------------------------------------
#include "switches.h" // project specific 'compiler' switches & options
// (it seems impossible to define the include files in MLPAB-X,
// and pass those settings on to the "custom translator" CC5X.
// So, like it or not, everything (*.c, *.h) had to be dumped
// into the stupid 'project directory'. What a mess. )
// Include an awful lot of compiler-specific junk ... see gpsdo_pic_main.c
#ifdef __BORLANDC__ // compiling with Borland C ? Use WB's "PIC emulator" ..
# include "pic_emulator/xc.h"
#elif (defined __XC8) // compiling for PIC, using Microchip's "XC8" compiler ?
# include "xc.h"
# include "stdint.h"
# include "stdlib.h" // stuff like itoa() [beware, non-standardized argument sequence]
#else // neither Borland, nor XC8 ...
# error "Your compiler is not supported here yet. Please add support yourself. But don't waste your time with CC5X."
#endif // using BORLAND C, Microchip's "XC8", or what else ?
#include "uart_pic.h" // header for THIS module ("UART functions for PIC" by DL4YHF)
char UART_sz9Temp[10]; // temporary string, used to convert integer to string, etc
//---------------------------------------------------------------------------
void UART_Init(void) // Open the serial port, primarily used for debugging .
{ // Forget about antique, bloated, "peripheral libraries" !
// The PIC16F1782/3 datasheet explains how to get the UART running
// on page 313, Ch. 27.1.1.7, "Asynchronous Transmission Set-Up" .
// Don't miss TABLE 27-1, "SUMMARY OF REGISTERS ASSOCIATED WITH ASYNCHRONOUS TRANSMISSION" .
// in DS40001579E on page 314, with links to each register description.
// Note: The UART pin port direction (TXD, RXD) have already been initialized,
// including the APFCON settings to have TXD on RB6, and RXD on RB7 .
// > 1. Initialize the SPBRGH, SPBRGL register pair and
// the BRGH and BRG16 bits to achieve the desired baud rate.
// > Setting the SCKP bit to '1' will invert the transmit data
// > resulting in low true idle and data bit .
// Unfortunately there's no equivalent polarity control for RX,
// so when using an RS232 level shifter (MAX232..), do NOT invert !
// At least, the PIC can *talk to* the PC's RS232 this way,
// without any active component in between.
#if( TXD_INVERT_POLARITY )
BAUDCON= 0b00011000; // BAUD RATE CONTROL REGISTER (DS40001579E page 322) ..
#else // ||||||||
BAUDCON= 0b00001000; // similar, if a MAX232 or similar INVERTING level-converter is in use
#endif // ||||||||
// ||||||||______ b0 : ABDEN (Auto-Baud Detect; 0=no automatic baudrate detection)
// |||||||_______ b1 : WUE (Wake-up enable bit)
// ||||||________ b2 : n.c.
// |||||_________ b3 : BRG16 : 0=8-bit baudrate generator, 1=16-bit baudrate generator
// ||||__________ b4 : SCKP (sync clock polarity, in ASYNC mode:
// ||| 0=non-inverted TX data, 1=inverted .. details above)
// |||___________ b5 : n.c.
// ||____________ b6 : RCIDL (Receive Idle flag)
// |_____________ b7 : ABDOVF (auto-baud detect overflow)
TXSTA = 0b00000100; // 8-bit, async, BRGH=0, INITIAL VALUE, TX still diabled !
// ||||||||______ b0 : TX9D (9th bit of tx data, not used here)
// |||||||_______ b1 : TRMT (transmit shift register status)
// ||||||________ b2 : BRGH (High Baud Rate Select bit, 1=high speed)
// |||||_________ b3 : SENDB (0=don't send BREAK)
// ||||__________ b4 : SYNC (0=async="UART", 1=sync)
// |||___________ b5 : TXEN (transmit enable bit)
// ||____________ b6 : TX9 (0 = 8-bit transmissions)
// |_____________ b7 : CSRC (Clock Source Select bit, don't care in ASYNC mode)
RCSTA = 0b00010000; // initial value: SPEN disabled before setting the baudrate; 8-bit, CREN (DS40001579E page 321)
// ||||||||______ b0 : RX9D (9th bit of rx data)
// |||||||_______ b1 : OERR (Overrun Error bit)
// ||||||________ b2 : FERR (Framing Error bit)
// |||||_________ b3 : ADDEN (Address Detect Enable bit, don't care in 8-bit mode)
// ||||__________ b4 : CREN (Continuous Receive enable bit, 1=enable rx)
// |||___________ b5 : SREN (Single Receive enable bit, don't care in ASYNC mode)
// ||____________ b6 : RX9 (9-bit receive enable bit)
// |_____________ b7 : SPEN (serial port enable bit)
// Fortunately, "C" permits 16-bit access. Combines "SPBRGH:SPBRGL" from the datasheet.
// NOTE: FORGET ABOUT DS40001579E page 323 "Example 27-1" ! For higher baudrate,
// baudrate = Fosc / ( 64 * (SPBRG+1) ) is unusable .
// > It may be advantageous to use the high baud rate (BRGH = 1),
// > or the 16-bit BRG (BRG16 = 1) to reduce the baud rate error.
// Thus, "Example 27-1" is the WORST example, even though presented FIRST.
// Much better suited for higher bitrates ("baudrates") :
// Configuration with "SYNC=0, BRG16=1, BRGH=1" : Baudrate = Fosc/(4*(SPBRG+1)) .
// Example : Fosc = 40 MHz (slightly violating the spec; permitted Fosc <= 32 MHz)
// Wanted: 115.2 kBit/sec
// -> SPBRG = (Fosc / Bitrate) / 4 - 1 = (40000000/115200) / 4 - 1 = 85.8 .
// -> rounded to SPBRG = 86, resulting REAL bitrate:
// Fbit = Fosc / ( 4 * (SPBRG+1) ) = 114.9 kBit/sec . Should be ok.
#if( SWI_UART_BAUDRATE==115200) // 115200 bits/second (ANY serial port should support this)
SPBRG = 86; // baudrate divisor, 16 bit, details above
#elif( SWI_UART_BAUDRATE==1000000) // 1000000 bits/second for higher sampling rates, but who supports this ?
// Wanted: 1000.0 kBit/sec -> SPBRG = (Fosc / Bitrate) / 4 - 1 = (40MHz/1MHz) / 4 - 1 = 39 .
SPBRG = 39; // baudrate divisor for 1 MBit/sec. Didn't work with 'Prolific'.
#elif( SWI_UART_BAUDRATE==460800) // 460800 = 115200 * 4 should be easier (with 'UART crystals')..
// Wanted: 460.8 kBit/sec -> SPBRG = (Fosc/Bitrate)/4-1 = (40MHz/460.8kHz)/4-1 = 20.7 .
// Using 21 (nearest int) -> Fbit = 40MHz / (4*(21+1)) = 454545 bit/sec; 1.4 % off .
SPBRG = 21; // baudrate divisor for 460.8kBit/sec. Ok with 'Prolific'.
#elif( SWI_UART_BAUDRATE==500000) // 500 instead of 460.8 kBit should be enough for fs_out = 20 kHz..
// Wanted: 500.0 kBit/sec -> SPBRG = (Fosc/Bitrate)/4-1 = (40MHz/500.0kHz)/4-1 = 19 [exact].
SPBRG = 19; // baudrate divisor for 500 kBit/sec. Failed with 'Prolific', no problem with FTDI.
#else
# error "Please add support for the new baudrate HERE !"
#endif
// > 2. Enable the asynchronous serial port by clearing
// the SYNC bit and setting the SPEN bit (.. etc, see DS40001579E page 320)
RCSTAbits.SPEN = 1; // serial port enable
TXSTAbits.TXEN = 1; // transmit enable
PIE1bits.TXIE = 0; // disable USART transmit interrupt (we use stupid busy-spinning, but that's ok. KISS.)
PIE1bits.RCIE = 0; // disable USART receive interrupt as well ! (whatever the "C" stands for..)
// > 8. Load 8-bit data into the TXREG register.
// > This will start the transmission.
} // end UART_Init()
//---------------------------------------------------------------------------
void UART_SendChar( char c )
// forget about putch() / printf() ! This is a PIC with microscopic ROM !
{
// > The TRMT bit is set when the TSR register is empty and is
// > cleared when a character is transferred to the TSR register from the TXREG (..)
while( ! TXSTAbits.TRMT ) // Wait until the transmit-register can accept another character
{
}
TXREG = c; // UART transmit register (at least in PIC16F1783)
} // end UART_SendChar()
void UART_SendCrNl(void)
{ UART_SendChar( '\r' );
UART_SendChar( '\n' );
}
//---------------------------------------------------------------------------
void UART_SendString( const char *cp )
// forget about putch() / printf() ! This is a PIC with microscopic ROM !
{
// > The TRMT bit is set when the TSR register is empty and is
// > cleared when a character is transferred to the TSR register from the TXREG (..)
while( *cp ) // Wait until the transmit-register can accept another character
{ UART_SendChar( *cp++ );
// Wonder what XC8 produces from this, when after each compilation it says:
// > You have compiled in FREE mode.
// > Using Omnicient Code Generation that is available in PRO mode,
// > you could have produced up to 60% smaller and 400% faster code. ?
// ; while( *cp ) ...
// 0x74A: MOVF cp, W ; <--------------------- while-loop
// 0x74B: MOVWF FSR0 ; |
// 0x74C: MOVF 0x72, W ; |
// 0x74D: MOVWF FSR0H ; |
// 0x74E: MOVIW FSR0++ ; |
// 0x74F: BTFSC STATUS, 0x2 ; -- |
// 0x750: RETURN ; | |
// 0x751: MOVF 0xF1(cp??), W ; <- |
// 0x752: MOVWF FSR0 ; MOVWF 0x84 ? |
// 0x753: MOVF 0x72, W ; MOVF 0xF2, W ? |
// 0x754: MOVWF FSR0H ; MOVWF 0x85 ? |
// 0x755: MOVF INDF0, W ; MOVWF 0x80, W ? |
// 0x756: MOVLP 0x7 ; PCLATH := 7; |
// 0x757: CALL 0x737 ; -> UART_SendChar( w ) |
// 0x758: MOVLP 0x7 ; |
// 0x759: MOVLW 0x1 ; |
// 0x75A: ADDWF cp, F ; |
// 0x75B: MOVLW 0x0 ; |
// 0x75C: ADDWFC 0x72, F ; |
// 0x75D: GOTO 0x74A ; ----------------------
}
} // end UART_SendString()
//---------------------------------------------------------------------------
void UART_SendDecimal( short i16 ) // sends i16 as decimal string.
{ // Note: itoa() is NON STANDARD ! ! Microchip declares it as
// > extern char * itoa(char * buf, int val, int base);
// while many others use an incompatible, non-intuitive argument list:
// > extern char * itoa(int val, char * buf, int base);
#if(0) && (defined __XC8) // compiling for PIC, using Microchip's "XC8" compiler ?
UART_SendString( itoa( UART_sz9Temp, i16, 10/*base*/) );
// program size using itoa : 2150 code memory words with XC8 "free".
// program size without itoa : 2042 code memory words with XC8 "free".
#else // do NOT use itoa() ...
// For most other PIC C compilers, we don't want to depend on itoa() & co,
// so -as usual- roll our own. Here: integer-to-string conversion..
// Principle: Convert integer to decimal, using the temp buffer to reverse the digits later.
uint8_t i;
# ifdef __CC5X__
uint8_t bTemp;
if( i16 < 0 )
{
UART_SendChar( '-' );
i16 = -i16;
}
// Convert integer to decimal string, ending with the MOST SIGNIFICANT digit:
i = 0;
do
{ // ex: UART_sz9Temp[i++] = '0' + (i16 % 10);
// ex: UART_sz9Temp[i] = '0' + (u16 % 10); // still too complex for CC5X !
bTemp = (uint16_t)i16 % 10;
// > Error[2] : Sign problems, please typecast one operand to unsigned (uns8)
bTemp += '0';
UART_sz9Temp[i] = bTemp;
// > Error[1] C:\pic\GPSDO\UART_PIC16F1783.C 211 : Unable to generate code
// > (The C syntax is correct. However, CC5X is unable to generate code.
// > The workaround is often to split the code into simpler statements,
// > using an extra variable to store temporary results. Sometimes it is
// > enough to change the sequence of operations)
i++;
i16 = (uint16_t)i16 / (uint16_t)10;
// > Error[2] : Sign problems, please typecast one operand to unsigned (uns8)
} while( i16>0 );
// Print the string in reversed order, because the most significant digit was converted LAST:
while( i>0 ) // i = number of decimal digits from the above loop
{ --i; // don't use UART_sz9Temp[--i] .. that would be asking too much for CC5X !
UART_SendChar( UART_sz9Temp[i] );
}
# else // here the non-brain-damaged implementation :
if( i16 < 0 )
{ UART_SendChar( '-' );
i16 = -i16;
}
// Convert integer to decimal string, ending with the MOST SIGNIFICANT digit:
i = 0;
do
{ UART_sz9Temp[i++] = '0' + (i16 % 10); // XC8 has no problems with this, CC5X surrenders
i16 /= 10;
} while( i16>0 );
// Print the string in reversed order, because the most significant digit was converted LAST:
while( i>0 ) // i = number of decimal digits from the above loop
{ UART_SendChar( UART_sz9Temp[--i] );
}
# endif // __CC5X__ ?
#endif // don't use itoa() ?
} // end UART_SendDecimal()
//---------------------------------------------------------------------------
uint8_t UART_ReadChar(void) // Reads the next character from the UART's FIFO.
{ // Returns 0x00 when 'nothing received'. Thus only for ASCII data.
// Details about reception from the "EUSART" in DS40001579E, page 315 .
// > The RCIF interrupt flag bit will be set when there is an
// > unread character in the FIFO, regardless of the state of
// > interrupt enable bits.
if( PIR1bits.RCIF )
{ return RCREG;
}
// Arrived here: Nothing received. On this occasion, check for an RX-overflow,
// because (from DS40001579E, page 315ff) :
// > If the receive FIFO is overrun, no additional characters will be received
// > until the overrun condition is cleared. (..) The error must be cleared
// > by either clearing the CREN bit of the RCSTA register or by resetting
// > the EUSART by clearing the SPEN bit of the RCSTA register.
// WB: Decided to try the less radical method (do not disturb transmission):
if( RCSTAbits.OERR ) // 'Overrun Error bit' in the 'Receive Status' set ?
{ RCSTAbits.CREN = 0; // "clear error by clearing the CREN bit".. but :
// This actually DISABLES THE RECEIVER (in asynchronous mode) !
NOP(); // alias _nop() alias __nop() ... holy shit
RCSTAbits.CREN = 1; // re-enable reception in async mode
}
return 0x00;
} // end UART_ReadChar()
#if( RXD_INVERT_POLARITY ) // invert the polarity of received data by software ?
//---------------------------------------------------------------------------
uint8_t UART_InvertRcvdChar( uint8_t bRcvdChar )
{
switch( bRcvdChar ) // try to convert "garbage" into the original character
{ // This kind-of "look-up table" was made by simply examining
// the 'garbage', printed as decimal code in WinPic's terminal.
// THIS ONLY WORKS WITH A SUFFICIENT GAP BETWEEN CHARACTERS !
// Note: Sorting this table by case-values, and filling the
// the gaps with "return 0x00" did not motivate the compiler
// to use a computed jump into a list of "retlw"s -> Eeek...
// case 0: return 0x00;
// case 1: return 0x00;
// case 2: return 0x00;
case 3: return ' ';
case 4: return 'p';
case 5: return 'P';
case 6: return '0'; // digit zero
case 8: return 'x';
case 9: return 'h';
case 10: return 'X';
case 11: return 'H';
case 12: return '8';
// case 13: return 0x00;
// case 14: return 0x00;
// case 15: return 0x00;
// case 16: return 0x00;
case 17: return 't';
case 18: return 'l'; // "L" lower case
case 19: return 'd';
// case 20: return 0x00;
case 21: return 'T';
case 22: return 'L';
case 23: return 'D';
case 25: return '4';
// case 26: return 0x00;
// case 27: return 0x00;
// case 28: return 0x00;
// case 29: return 0x00;
// case 30: return 0x00;
// case 31: return 0x00;
// case 32: return 0x00;
case 33: return 'z';
case 34: return 'v';
case 35: return 'r';
case 36: return 'n';
case 37: return 'j';
case 38: return 'f';
case 39: return 'b';
// case 40: return 0x00;
case 41: return 'Z';
case 42: return 'V';
case 43: return 'R';
case 44: return 'N';
case 45: return 'J';
case 46: return 'F';
case 47: return 'B';
// case 48: return 0x00;
// case 49: return 0x00;
case 50: return '6';
case 51: return '2';
case 67: return 'y';
case 68: return 'w';
case 69: return 'u';
case 70: return 's';
case 71: return 'q';
case 72: return 'o';
case 73: return 'm';
case 74: return 'k';
case 75: return 'i';
case 76: return 'g';
case 77: return 'e';
case 78: return 'c';
case 79: return 'a';
// case 80: return 0x00;
// case 81: return 0x00;
// case 82: return 0x00;
case 83: return 'Y';
case 84: return 'W';
case 85: return 'U';
case 86: return 'S';
case 87: return 'Q';
case 88: return 'O';
case 89: return 'M';
case 90: return 'K';
case 91: return 'I';
case 92: return 'G';
case 93: return 'E';
case 94: return 'C';
case 95: return 'A';
case 99: return '9';
case 100: return '7';
case 101: return '5';
case 102: return '3';
case 103: return '1'; // digit one
# if(0) // (1)=normal compilation, (0)=test
default: return 0x00; // discard anything else
# else
default: return bRcvdChar; // pass-through anything else (unchanged)
# endif
}
} // end UART_InvertRcvdChar()
#endif // RXD_INVERT_POLARITY ?
/*
EOF ( UART_PIC16F1783.c )
*/