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