/*
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 ) */