PIC Assembler Modules
For Non-Commercial Use
Only
I would appreciate to be credited within your project, if you
use any of the source code below. If you have an interesting
project going on, I'll be glad about feedback.
The software below comes with no guarantee or warranty
except for my good intentions. Further the use of this code
implies that the user has a fundamental understanding of
electronics and its risks. I'm not responsible for any harm or
damage, caused by inproper use of any of the code below.
Any commercial use of parts or all of this code requires the
permission of the
author.
Table of Contents [Toc]
General
Standard Modules
Dot Matrix LCD Modules
LCD Display Driver
Routines
LCD Display Conversion
Routines
RS232 Modules
Notes on Modules
General
Recommendations
Known Limitations of
MPLAB IDE
Technical Hints
General [Toc] [Top]
Modules are source code blocks, which can be included in the
MAIN PROGRAM by simply adding the command line:
#include "C:/ (...) m_bank.asm"
The code will be inline expanded. All you need is to specify the
necessary registers in front of the include statement in the main
program, e.g.
LCDtris equ TRISB
LCDport equ PORTB
CONSTANT BASE = 0x0C ; 16F84 base address of user file registers
#include "..\m_bank.asm"
#include "..\m_wait.asm"
#include "..\m_lcd.asm"
|
Standard Modules [Toc] [Top]
Assembler include files: General macros to ease assembler
handling, e.g. macros BANK0, BANK1
| m_bank.asm |
V2.00 (17.08.2004)
Bank & page handling: Bank0, Bank1,...
Branch macros (to simplify 'IF THEN ELSE' queries):
BEQ val: branch on equal w and val
BNE val: branch on not equal w and val
BREG val: branch on equal or greater w than val
...
|
| m_wait.asm |
V1.02 (20.08.2004)
Parameterizable wait function, which performs a "busy"
wait.
Implemented standard delay (@ 4 MHz):
WAIT 0x01 is equal to 1 unit == 1.02 ms
The assigned standard prescaler for TMR0 is PRESCstd =
b'00000001'
Relationship between TMR0 prescaler and unit delay (WAIT
0x01):
b'00000000' == 1:2 ==> 0.512 ms
b'00000001' == 1:4 ==> 1.02 ms (standard)
b'00000111' == 1:256 ==> 65.3 ms
Declarations needed in main program:
CONSTANT BASE = 0x0C ; 16F84 base address of user file registers
#include "..\m_bank.asm"
#include "..\m_wait.asm"
; Call of implemented procedures with:
; WAIT 0x01 ; standard delay, 1.02 ms
; WAITX d'16',d'7' ; 1.045 s @ 4 MHz, extended with specific prescaler
|
|
| m_beep.asm |
V1.00 (16.02.2003)
Beep function on parameterizable output port.
Declarations needed in main program:
#define BEEPport PORTA,0x00
#define BEEPtris TRISA,0x00
CONSTANT BASE = 0x0C ; 16F84 base address of user file registers
#include "..\m_bank.asm"
#include "..\m_beep.asm"
; Call of implemented procedures with:
; BEEPinit ; initialization to set output port
; BEEP 0xFF,0x02
|
|
For demo-programs or complete
applications, refer to 'Projects' [click here]
Dot Matrix LCD Modules [Toc] [Top]
Assembler include files: Display drivers for dot matrix LCD
displays. (Hitachi HD44780 compatibles)
|

Dot Matrix LCD Display
4 lines x 20 characters
|
Dot Matrix LCD Display 2 lines x 40
characters
|
LCD Display Driver Routines
[Toc] [Top]
Download the PDF schematic to illustrate the connectivity
of both 'classes' of LCD driver routines:
- Modules m_lcd.asm, m_lcdx.asm,
m_lcd_bf.asm, m_lcdxbf.asm: working only on entire
ports ([1..7], without [0]),
e.g. PortB[1..7] on PIC16F84,
or PortB[1..7], PortC[1..7], PortD[1..7] on PIC16F77
- Modules m_lcde.asm, m_lcde_bf.asm,
m_lcdexbf.asm: working on separate, customizable
ports,
e.g. PortB[0..2] for control lines & PortA[0..3] for data
lines on PIC16F84, PIC16F7x,
or PortC[5..7] for control lines & PortD[0..3] for data
lines on PIC16F77, or ...
Note that the data lines have to be on the low nibble of the
port.
| m_lcd.asm |
V2.06 (26.12.2004)
7 wires, 4 bit LCD interface
Based on timing constraints, no busy flag check. So if LCD
fails, system is still running stable. Higher reliability,
because less critical system components.
No portable code for higher clock frequencies.
R/W connection to LCD supported for compatibility to
m_lcd_bf.asm, but can be put to GND at LCD side =>
You'll get one interrupt pin more on the processor
side.
Schematic of LCD connection (PDF)
Declarations needed in main program:
LCDtris equ TRISB ; could also be PORTC, PORTD on 16F77
LCDport equ PORTB
; LCD data ports D4-D7 are on LCDport,0x1-0x4
; LCD_EN is on LCDport,0x05 ; Enable
; LCD_RW is on LCDport,0x06 ; Read/Write
; LCD_RS is on LCDport,0x07 ; Register Select
CONSTANT BASE = 0x0C ; 16F84 base address of user file registers
#include "..\m_bank.asm"
#include "..\m_wait.asm"
#include "..\m_lcd.asm"
|
|
| m_lcdx.asm |
V2.26 (26.12.2004)
7 wires, 4 bit LCD interface
Extended m_lcd.asm with the ability to define your own
characters (max. 8) in macro LCDspecialChars. Based on
timing constraints, specifications as m_lcd.asm.
Schematic of LCD connection (PDF)
Declarations needed in main program:
LCDtris equ TRISB ; could also be PORTC, PORTD on 16F77
LCDport equ PORTB
; LCD data ports D4-D7 are on LCDport,0x1-0x4
; LCD_EN is on LCDport,0x05 ; Enable
; LCD_RW is on LCDport,0x06 ; Read/Write
; LCD_RS is on LCDport,0x07 ; Register Select
CONSTANT BASE = 0x0C ; 16F84 base address of user file registers
; complete/replace macro LCDspecialChars to define your own characters
#include "..\m_bank.asm"
#include "..\m_wait.asm"
#include "..\m_lcdx.asm"
|
|
| m_lcd_bf.asm |
V3.09 (26.12.2004)
7 wires, 4 bit LCD interface
Extended m_lcd.asm which reads busy flag of LCD
(bi-directional communication between controller and
LCD)
R/W connection to LCD needed for hand shaking.
Clock independent program code, successfully tested up to
10 MHz on PIC 16F84 and up to 20 MHz on PIC 16C74A!
Gives the best performance, but system fails, if LCD
(-connection) fails, because processor is waiting for ready
signal from LCD to send next character. => Deadlock, but
should not occur under normal circumstances!
Solution:
Connect an auxilliary 10k resistor from DB7 to GND, so if
the LCD is disconnected, the microprocessor will read an
inactive busy flag. (That's needed because of the internal
weak pull ups of the microprocessor ports. Maybe also
needed if you switch them off.)
Schematic of LCD connection (PDF)
Declarations needed in main program:
LCDtris equ TRISB ; could also be PORTC, PORTD on 16F77
LCDport equ PORTB
; LCD data ports D4-D7 are on LCDport,0x1-0x4
; LCD_EN is on LCDport,0x05 ; Enable
; LCD_RW is on LCDport,0x06 ; Read/Write
; LCD_RS is on LCDport,0x07 ; Register Select
CONSTANT BASE = 0x0C ; 16F84 base address of user file registers
FLAGreg equ BASE+d'7' ; general flag register
#define LCDbusy FLAGreg,0x00 ; LCD busy flag declared within flag register
#define LCDcflag FLAGreg,0x01
#include "..\m_bank.asm"
#include "..\m_wait.asm"
#include "..\m_lcd_bf.asm"
|
|
| m_lcdxbf.asm |
V3.29 (26.12.2004)
7 wires, 4 bit LCD interface
Basically the same as m_lcd_bf.asm, but with the ability to
define your own characters (max. 8) in macro
LCDspecialChars. Bi-directional communication, reads busy
flag of LCD.
Schematic of LCD connection (PDF)
|
| m_lcde.asm |
V2.12e (17.08.2004)
7 wires, 4 bit LCD interface
Basically as m_lcd.asm, but with the ability to
independently configure LCD control and data lines, i.e.
LCD data on low nibble of any port, LCD command lines on
any port bits (even different port).
Schematic of LCD connection (PDF)
Declarations needed in main program:
LCDtris equ TRISA ; LCD data on low nibble of portA
LCDport equ PORTA
#define LCD_ENtris TRISB,0x01 ; EN on portB,1
#define LCD_EN PORTB,0x01
#define LCD_RStris TRISB,0x02 ; RS on portB,2
#define LCD_RS PORTB,0x02
#define LCD_RWtris TRISB,0x03 ; RW on portB,3
#define LCD_RW PORTB,0x03
CONSTANT BASE = 0x0C ; 16F84 base address of user file registers
#include "..\m_bank.asm"
#include "..\m_wait.asm"
#include "..\m_lcde.asm"
|
|
| m_lcde_bf.asm |
V4.03e (17.08.2004)
7 wires, 4 bit LCD interface
Extended m_lcde.asm which reads busy flag of LCD
(bi-directional communication between controller and
LCD)
Schematic of LCD connection (PDF)
Declarations needed in main program:
LCDtris equ TRISA ; LCD data on low nibble of portA
LCDport equ PORTA
#define LCD_ENtris TRISB,0x01 ; EN on portB,1
#define LCD_EN PORTB,0x01
#define LCD_RStris TRISB,0x02 ; RS on portB,2
#define LCD_RS PORTB,0x02
#define LCD_RWtris TRISB,0x03 ; RW on portB,3
#define LCD_RW PORTB,0x03
CONSTANT BASE = 0x0C ; 16F84 base address of user file registers
FLAGreg equ BASE+d'7' ; general flag register
#define LCDbusy FLAGreg,0x06 ; LCD busy flag declared within flag register
#define LCDcflag FLAGreg,0x07 ; LCD command/data flag
#include "..\m_bank.asm"
#include "..\m_wait.asm"
#include "..\m_lcde_bf.asm"
|
|
| m_lcdexbf.asm |
V4.23e (17.08.2004)
7 wires, 4 bit LCD interface
Basically the same as m_lcde_bf.asm, but with the ability
to define your own characters (max. 8) in macro
LCDspecialChars. Bi-directional communication, reads busy
flag of LCD.
Schematic of LCD connection (PDF)
|
For demo-programs or complete
applications, refer to 'Projects' [click here]
LCD Display Conversion Routines
[Toc] [Top]
| m_lcdv08.asm |
V1.02 (20.08.2004)
8 bit binary to decimal conversion routine for LCD output,
stringent for any numeric display output or
interaction.
Declarations needed in main program:
CONSTANT BASE = 0x0C ; 16F84 base address of user file registers
FLAGreg equ BASE+d'4' ; general flag register
LO equ BASE+d'5'
LO_TEMP set BASE+d'6'
#define BCflag FLAGreg,0x00 ; blank checker for preceding zeros
#include "..\m_bank.asm"
#include "..\m_wait.asm"
#include "..\m_lcd.asm"
#include "..\m_lcdv08.asm"
; Call of implemented procedure with:
LCDval_08 ; result is stored in register LO
|
|
| m_lcdv16.asm |
V1.02 (20.08.2004)
16 bit binary to decimal conversion routine for LCD output,
stringent for any numeric display output or
interaction.
Declarations needed in main program:
CONSTANT BASE = 0x0C ; 16F84 base address of user file registers
FLAGreg equ BASE+d'4' ; general flag register
LO equ BASE+d'5'
HI equ BASE+d'6'
LO_TEMP set BASE+d'7'
HI_TEMP set BASE+d'8'
#define BCflag FLAGreg,0x00 ; blank checker for preceding zeros
#include "..\m_bank.asm"
#include "..\m_wait.asm"
#include "..\m_lcd.asm"
#include "..\m_lcdv16.asm"
; Call of implemented procedure with:
LCDval_16 ; result is stored in registers HI, LO
|
|
| m_lcdb08.asm |
V1.00 (20.08.2004)
8 bit binary LCD output routine for debugging of registers
and bitstreams, e.g. to visualize the binary output of an
A/D converter to check the magnitude of its LSB toggling
due to digital quantization and/or noise.
Declarations needed in main program:
CONSTANT BASE = 0x0C ; 16F84 base address of user file registers
b08_cnt equ BASE+d'4' ; counter
LO equ BASE+d'5'
LO_TEMP set BASE+d'6'
#include "..\m_bank.asm"
#include "..\m_wait.asm"
#include "..\m_lcd.asm"
#include "..\m_lcdb08.asm"
; Call of implemented procedure with:
LCDbin_08 ; value in register LO, output to LCD
|
|
| m_lcdb16.asm |
V1.00 (20.08.2004)
16 bit binary LCD output routine for debugging of registers
and bitstreams, e.g. to visualize the binary output of an
A/D converter to check the magnitude of its LSB toggling
due to digital quantization and/or noise.
Declarations needed in main program:
CONSTANT BASE = 0x0C ; 16F84 base address of user file registers
b16_cnt equ BASE+d'4' ; counter
LO equ BASE+d'5'
HI equ BASE+d'6'
LO_TEMP set BASE+d'7'
HI_TEMP set BASE+d'8'
#include "..\m_bank.asm"
#include "..\m_wait.asm"
#include "..\m_lcd.asm"
#include "..\m_lcdb16.asm"
; Call of implemented procedure with:
LCDbin_16 ; values in register HI, LO, output to LCD
|
|
For demo-programs or complete
applications, refer to 'Projects' [click here]
RS232 Modules [Toc] [Top]
For external interrupts, such as the RB0/INT pin or PORTB
change interrupt, the latency will be three to four instruction
cycles. The exact latency depends on when the interrupt occurs.
The latency is the same for both one and two cycle
instructions.
(=> see Microchip PIC16/17 Microcontroller Databook.)
For those, who are not familiar with interfacing a PIC to the
RS232 using a MAX232 : RS232-Interface.pdf
(9.7 kB)
| m_rs024.asm |
V1.02 (11.04.2004)
Completely software handled RS232 Interface for interrupt
featured PICs (PIC16C84, PIC16F84,...).
Specifications: 2400 baud, 8 bit, no parity, 1 stopbit
(@ 4 MHz / 1 MIPS)
Declarations needed in main program:
#define TXport PORTA,0x00 ; RS232 output port, could be
#define TXtris TRISA,0x00 ; any active push/pull port
; RS232 input port is RB0, because of its own interrupt flag
CONSTANT BASE = 0x0C ; 16F84 base address of user file registers
TXD equ BASE+d'7' ; TX data register, for transmission
RXD equ BASE+d'8' ; RX data register, for storage
#include "..\m_bank.asm"
#include "..\m_rs024.asm"
; Call of implemented procedures with:
; RS232init ; initialization
; SEND 'c' ; sends character 'c'
; SENDw ; sends content of working register
; RECEIVE ; macro in ISR: receive from RS232,
; store in register RXD
|
|
| m_rs048.asm |
V1.02 (11.04.2004)
Functionality as m_rs024.asm
Specifications: 4800 baud, 8 bit, no parity, 1 stopbit
(@ 4 MHz / 1 MIPS) |
| m_rs096.asm |
V1.02 (11.04.2004)
Functionality as m_rs024.asm
Specifications: 9600 baud, 8 bit, no parity, 1 stopbit
(@ 4 MHz / 1 MIPS) |
| m_rs192.asm |
V1.02 (11.04.2004)
Functionality as m_rs024.asm
Specifications: 19200 baud, 8 bit, no parity, 1 stopbit
(@ 4 MHz / 1 MIPS) |
| m_rs7n1.asm |
V1.02 (11.04.2004)
Functionality as m_rs024.asm
Developed for an old and heavy matrix needle printer with
RS232 Interface.
Specifications: 9600 baud, 7 bit, no parity, 1 stopbit
(@ 4 MHz / 1 MIPS) |
For demo-programs or complete
applications, refer to 'Projects' [click
here]
Notes on Modules [Toc] [Top]
- Code is optimized for 1. small memory, and 2. optimal
execution time.
- If extern sources are called from within modules, set
absolute pathes. Please refer to Known Limitations of MPLAB IDE
below.
- Declare and define the required registers and statements in
the MAIN.
- Declarations of constants and registers need to be listed
in the MAIN before the '#include' statement of the modules,
since most modules are referencing specific constants and
registers (therefore they need to be defined at that time).
General Recommendations
[Toc] [Top]
- Manage the used registers in a clear structured way.
- For prototyping, it is recommended to introduce temporarily
dedicated registers for each new code segment. At the end, when
everything runs properly, you can start to optimize the design
in order to share as much registers as possible to save memory
space. Otherwise, you run the risk of getting data consistency
problems - bugs, which are really hard to solve!
- Provide dedicated registers for interrupt handling! Do not
call subroutines from the interrupt service routine (ISR),
which are executed during normal operation. If so, ensure to
perform a complete context save of all registers being used
within this subroutine.
- Interrupt flags have to be cleared by software before
re-enabling the 'global interrupt enable bit' (GIE) and exiting
the ISR. Otherwise, recursive ISR calling occurs.
- At the beginning of the ISR, disable the 'global interrupt
enable bit' (GIE). In case different interrupt sources have to
be served, it may be necessary to disable also the related
'interrupt source enable bit' (e.g. INTF of RB0/INT pin).
See next recommendation
- On ISR exit, make sure to reset the interrupt flag of the
source, which triggered the interrupt. But perform re-enabling
of the corresponding 'interrupt source enable bit' at the end
of the corresponding service routine:
- If the interrupt service is entirely completed within the
ISR, re-enabling is suitable at the end of the ISR. This
applies only, if disabling of the interrupt source has been
carried out at the beginning of the ISR.
- If parts of the interrupt service are carried out during
normal operation, re-enabling the interrupt source is most
suitable at the end of the external code part. In this case,
disabling the specific interrupt source at the beginning of the
ISR is stringent, otherwise we face lost of data or a system
crash due to return address stack overflow. This method owns a
further advantage: A subsequent interrupt already having been
triggered before re-activation and originating from the same
source is not omitted and will immediately be served. (This is
because the interrupt flag bit gets still set, when the
interrupt is temporarily disabled. When unmasking this
interrupt source, the interrupt is immediately triggered.)
Known Limitations of MPLAB
IDE [Toc]
[Top]
- Previously MPLAB worked with the old MS DOS 8.3 name
convention (so set paths with old notation).
With the recent MPLAB versions beyond 6.30 (I use currently
6.40 and PICstart Plus), it seems they accept also longer file
names. That's while my include files sometimes have strange
names. There is also an upper limitation on the amount of
characters in an absolute file name including path.
- '#include' - commands in the MAIN could be relative
(..\..\xyz.asm) or absolute paths (C:\abc\xyz.asm).
- '#include' - commands in extern source have to be absolute
paths (if extern source is called with relative path).
Technical Hints [Toc] [Top]
- If you use 'set' instead of 'equ', you can assign different
labels to the same register. This is very useful, if you need
temporary registers in different modules. But be careful that
you do not write and access the same register alternating in 2
different procedures. This results in nice bugs or possibly
endless loops.
- 'equ' is exclusive, i.e. it accepts only one label for each
register.
- If you want to create a 16 bit assembler look-up table very
quickly, have a look at the
Automatic Table Generator for MPLAB
Assembler.
- To specify exact periods (e.g. with busy wait module
m_wait.asm), I use quite often the MPLAB stopwatch. This is a
very convenient cycle-accurate timer, which takes both 1 cycle
(e.g. incf) and 2 cycle instructions (e.g. goto) as well as
special instruction mnemonics (e.g. bnc, subcf) into
account.
I just let the simulator run to the wait statement or loop to
be measured, reset the stopwatch, and let it again simulate
until after the wait statement:
|

The Stopwatch
having been reset at 'bsf TXport' and run to the 'nop'
statement. The latency of the 'WAITX d73,d2' statement
within this setup (4 MHz clock, PIC16F84) turns out to
be approximately 150 ms.
|
Last updated: 15.01.2006
[Toc] [Top]