Sometimes, it’s nice to see an example of an implementation that is, otherwise, strewn throughout pages of a datasheet. I decided to pull some serial code out of a debugging file and provide it as an example to anyone who is having trouble getting their Microchip EUSART to perform correctly. The example is thoroughly documented and I am providing a makefile that will build the example using GPUtils. Note that the example is using a Cygwin (read unix) installation of GPUtils, but simply changing the paths to the executable files defined in the makefile will allow building on Windows as well. Additionally, this example should be easily portable to MPLab and MPASM by changing the configuration bit section in main.asm.
This example was written for the PIC16F1828 device, but is pin-compatible with a number of 20-pin devices. With a few changes it will work with a PIC16F690, for example. It was written specifically to demonstrate how to produce serial output with simple macro insertions such as this
_SerialSendTableL TESTTEXT _serialSendByteL ' ' _serialSendBitsL b'10101010' _SerialSendTableL CRLF
The meat of the example is contained within the serialoutput.asm file. The UART is configured for 57.6k baud at 8MHz, 8 data bits and one stop bit with no parity. It contains a few macros that will allow the caller to output a byte, a binary representation of a byte, and string values defined in a program data table (DT).
;----------------------------------------------------------------------------- ; File: serialoutput.inc ; Date: 02/24/2017 ; Purpose: ; ERRORLEVEL -202 ;............................................................................. ; #include "project.inc" ;............................................................................. ;Declare data for serial helpers ; .serialoutput.data udata serialLoop res .2 ;We'll use these bytes for loop count serialSendByte res .1 ;Temporary for shifting literal args serialTableHi res .1 ;Table address high byte serialTableLo res .1 ;Table address low byte ;============================================================================= ;Begin Code section ; .serialoutput.code code ;============================================================================== ; Function: ConfigureSerial ; Purpose: This function will configure a PIC16F1828 UART for transmission ; at a data rate of 57.6k baud. All related pins must be set to ; digital I/O before this function is called. ; ConfigureSerial: banksel TRISB ;Select tristate bank and movlw b'01000000' ; load work reg with mask iorwf TRISB, f ;Or work reg with tristate for input banksel TXSTA ;Select transmitter status register bcf TXSTA, SYNC ;Enable Async communication bsf TXSTA, BRGH ;Enable high resolution BRG bcf TXSTA, TX9D ;Disable 9 bit transmission banksel BAUDCON ;Select baud rate control register bsf BAUDCON, BRG16 ;Enable 16-bit BRG value bcf BAUDCON, WUE ; banksel RCSTA ;bank select status and control reg bsf RCSTA, SPEN ;Serial port enable bit banksel SPBRGL ;Select low byte of 16-bit baud rate movlw .34 ;57.6k baud @8MHz is .34 (datasheet) movwf SPBRGL ; move this value to LSB banksel SPBRGH ;Select MSB bank of BRG value clrf SPBRGH ; set to 0 banksel TXSTA ;Select status TX status again and bsf TXSTA, TXEN ; enable transmitter return global ConfigureSerial ;------------------------------------------------------------------------------ ; Function: _serialSendByteW ; Purpose: Sends a single byte to the peripheral to be transmitted over ; the TX pin. The byte to be sent should be in the working ; register. ; _serialSendByteW macro local ___txWait ___txWait banksel PIR1 ;Select bank btfss PIR1, TXIF ;Test for buffer clear to send next goto ___txWait ; Block waiting to send banksel TXREG ;Select TX buffer register and movwf TXREG ; move work register value to TXREG endm ;------------------------------------------------------------------------------ ; Function: _serialSendByteL ; Purpose: Sends a single byte to the peripheral to be transmitted over ; the TX pin. The argument provided is a literal value ; _serialSendByteL macro byte local ___txWait ___txWait banksel PIR1 ;Select bank btfss PIR1, TXIF ;Test for buffer clear to send next goto ___txWait ; Block waiting to send movlw byte ;Load literal to working register banksel TXREG ;Select TX buffer register and movwf TXREG ; move work register value to TXREG endm ;------------------------------------------------------------------------------ ; Function: _serialSendBitsL ; Purpose: ; _serialSendBitsL macro literal local ___bitLoop, ___sendMark, ___sendSpace, ___doneSend banksel serialSendByte ;Select temp register bank movlw literal ;Load literal value to work register movwf serialSendByte ; Then store in file reg for shifting movlw .8 ;Loop counter for 8 bits banksel serialLoop ;Bank select movwf serialLoop ; and move literal 8 into counter reg ___bitLoop rlf serialSendByte,f ;Rotate first bit to carry flag btfss STATUS, C ;Test LSb for mark or space goto ___sendSpace ; Jump to space if not set goto ___sendMark ; Jump to mark if set ___sendMark _serialSendByteL 0x31 ;Send '1' byte goto ___doneSend ;Jump to loop check ___sendSpace _serialSendByteL 0x30 ;Send '1' byte ___doneSend banksel serialLoop ;Select bank for loop counter decfsz serialLoop, f ; Decrement and see if we're done goto ___bitLoop ; Nope, keep shifting and sending... endm ;------------------------------------------------------------------------------ ; Function: _SerialSendTableL ; Scope: Public ; Purpose: Writes a table to the serial device ; _SerialSendTableL macro table banksel serialTableHi ;Select Bank movlw HIGH table ;Get the high bits of the table addr movwf serialTableHi ;Store them in our file register movlw LOW table ;Get the low bits movwf serialTableLo ;And store those call SerialWriteTable ;Call our function to send table data endm ;============================================================================== ; Function: SerialLogWriteTable ; Scope: Private ; Purpose: This function is used by the _SerialLogTableL macro. Don't call ; directly. ; SerialWriteTable: call SerialTableLookup ;Call the lookup function to get next iorlw 0x00 ;Check to see if it's a null btfsc STATUS, Z ;If zero set, then we're done goto ___SerialWriteTable_end _serialSendByteW ;Send the next letter goto SerialWriteTable ;Loop back to function start ___SerialWriteTable_end return global SerialWriteTable ;============================================================================== ; Function: SerialLogTableLookup ; Scope: Private ; Purpose: This function is used by the SerialLogWriteTable function. ; Don't call directly. ; SerialTableLookup: banksel serialTableHi ;Select bank movf serialTableHi,w ;Load hi bits to accumulator movwf PCLATH ;put this in the PCLATH register movf serialTableLo,w ;Load the low bits to accumulator incf serialTableLo,f ;Increment low data register for next btfsc STATUS,Z ;Skip the hi bank inc if no zero flag incf serialTableHi,f ;The next low addr crosses a page, movwf PCL ;Jump to table, return is implicit global SerialTableLookup
I would talk about the file more, but it’s commented to death. If anyone would like a walkthrough of this file, simply leave a comment. The main.asm file that drives the example contains some boiler-plate initialization and then calls each macro in the include file:
;----------------------------------------------------------------------------- ; File: main.asm ; Date: 02/17/2017 ; Purpose: ; ERRORLEVEL -202 ERRORLEVEL -205 CONFIG FOSC = INTOSC CONFIG WDTE = OFF CONFIG PWRTE = ON CONFIG MCLRE = OFF CONFIG BOREN = OFF CONFIG IESO = OFF CONFIG PLLEN = OFF ;............................................................................. ;Processor include ; #include "p16f1828.inc" #include "serialoutput.inc" ;............................................................................. ;Declare data local to this module. We just need a loop variable. ; .main.data udata loop res .1 ;----------------------------------------------------------------------------- ;Jump to start label at reset PC ; .romStart code 0x00 goto Start ;----------------------------------------------------------------------------- ;Jump to interrupt handler at interrupt vector ; .romInterrupt code 0x04 goto Interrupt ;============================================================================= ;Begin Code section ; .main.code code ;============================================================================= ;Declare program data tables for serial output ; CRLF dt "\r\n",0 TESTTEXT dt "Outputting Byte",0 ;============================================================================= ;Begin processing from 0x00 ; Start: ;....................................................................... ;Disable peripheral interrupts and global ; banksel INTCON bcf INTCON, GIE bcf INTCON, PEIE ;....................................................................... ;Oscillator configuration ; banksel OSCCON movlw b'01110010' ;8MHz oscillator frequency movwf OSCCON banksel OSCSTAT ___Main_oscwait btfss OSCSTAT, HFIOFR ;Wait for high freq oscillator goto ___Main_oscwait ___Main_oscstable btfss OSCSTAT, HFIOFS ;Wait for HFO stable goto ___Main_oscstable ;....................................................................... ;Disable analog banksel ANSELA clrf ANSELA clrf ANSELB clrf ANSELC ;....................................................................... banksel ADCON0 bcf ADCON0,ADON ;Disable A2D converters ;........................................................................ ;Disable weak pullups ; banksel OPTION_REG bcf OPTION_REG, 7 ;....................................................................... ;Call serial configuration function defined in serialout.inc ;1 call ConfigureSerial ;....................................................................... ;Set PORTA:5 as an output ; banksel TRISC ;Bank select for tristate config bcf TRISC, 7 ;Trigger pin output ;============================================================================== ; Function: Main ; Purpose: ; Main: ;....................................................................... ; Use PORTC:7 to trigger our oscilloscope ; banksel PORTC bsf PORTC, 7 nop nop bcf PORTC, 7 ;....................................................................... ; Test the macros ; _SerialSendTableL TESTTEXT _serialSendByteL ' ' _serialSendBitsL b'10101010' _SerialSendTableL CRLF ;....................................................................... ; Sleep for a short period ; banksel loop movlw 0xff movwf loop ___Main_sleep nop nop nop decfsz loop, f goto ___Main_sleep goto Main ;Loop back to main ;============================================================================== ; Function: Interrupt ; Purpose: ; Interrupt: retfie ;Return from interrupt handler end ;Stop assembly
Assembling these files will produce continuous output on RB7 (pin 10/TX). Note that I also use RC7 as a trigger so that I can show you this:
And, if you’re using a RS-232 to TTL device, such as a Maxim Integrated MAX233, you can send this output to a serial port:
Wiring your microcontroller to a PC serial port through an RS-232 to TTL line driver such as the MAX233A is very straight-forward:
Source code available for download here: Simple Serial Example