
Miscellaneous PIC, AVR & other microcontroller stuff
Assorted neat microcontroller tips, tricks etc. New stuff will be added
at the top of the page
ICD2 isolator (PIC)
The 32 Word Bootloader (PIC)
Measuring battery voltage (Any Micro)
Tiny 9600 baud software Async Tx (PIC)
Compact I2C EEPROM code (PIC)
ARM LPC2103/uAlfat baudrate calculator
See also on other pages : Probe for in-circuit
PIC programming Which is Better, PIC or AVR?
ICD2 isolator
Microchip's ICD2 is a handy programmer, even when not using the in-circuit
debug facility, as it can be set up to automatically program a device after a successful
build, providing a very quick "assemble-burn-run" cycle time for small projects
where you can live without an emulator or debug facilities, e.g. on parts that don't
support ICD.
One problem however, is that the ICD2 loads the RB6/7 lines significantly, which can be an
issue for some designs.
To get around this I built this simple isolator, which plugs inline with the ICD2 cable,
and disconnects RB6/7 using a relay when ICD2 is not programming the device. The relay is
turned on when the MCLR line is either at 0V (PIC held in reset by ICD) or >8V
(Programming mode) A 3V relay was used to allow operation over the whole supply voltage
range.
Note that this is only suitable for use when the ICD2 is being used as a
simple programmer, not when using the ICD debug facilities.

This works very well, however if building it again I would use a 4 pole
relay, to allow :
1) MCLR to be disconnected from the target - this is useful when using parts that can use
the MCLR pin as an input, as ICD2 drives the MCLR pin. in the meantime, a 1K series
resistor does not seem to affect programming and did the job when I needed it.
2) Forcing of the target Vdd to 5V during programming for designs running
at 3V - this can speed up programming significantly as the Bulk Erase instruction can only
be used above 4.5V.
* Resident part only, not including setup code, local
taxes etc. your mileage may vary. CA residents add 8 words PIC tax.
Now that self-programming is available on some smaller PICs (e.g. 16F818),
I was thinking about ways of providing in-system firmware updates with the absolute
minimum of code overhead - a typical 'traditional' bootloder takes of the order of 128-256
words, depending on the communications requirements, which is a significant chunk on a 1K
part.
I was thinking particularly about a current design, but it would be applicable to any
system where the main application already has :
a) An external eprom or serial flash etc. memory big enough to hold a code image
b) Some communications link capable of reading/writing to the eeprom.
Instead of the traditional bootloader approach (where the loader handles the comms and the
programming, and has to be able to recover if the comms fail, you copy the code into
eeprom using the existing application code, verify it back, then call a small routine at
the top of memory to
copy the eeprom image to the flash. This has the advantage that because you already have
the comms code in your main application, the only code overhead is the actual
eeprom-to-flash copying code.
Another advantage for this method could be where the available
communication protocol is too complex to fit economically into a bootloader, e.g. TCP/IP,
USB, Zigbee etc. In this situation, the cost of adding an extra eeprom to allow code
updates may well be justifiable.
It eliminates the need for a bootloader to be able to recover from comms failure, as you
can write and verify the data before committing to the flash programming operation. The
only hazard would be a power failure during the copying process, a much less likely
occurrance.
Depending on how much of a risk you consider this is for your product (and how difficult
it would be to recover from a failure), you could trade off a bit of code space by having
the copy routine rewrite the reset vector to restart the process if interrupted, copying
the 'normal' vector back as the last operation, narrowing the window time in which a power
failure would be fatal to an insignificantly small few tens of milliseconds.
Alternatively (at the expense of a little more code) it could be set up to always boot
into the copy code, which would check the eeprom to look for a flag to signal a new image
to be loaded.
For devices like serial flash on hardware SPI, where one command can read a large number
of data bytes, the main application code could even initiate the eeprom read command,
eeprom address etc., so the copy code would just have to read the stream of bytes from the
SPI port, giving an extremely
low code overhead.
A limit on the sizing of the 'resident' eeprom copying routine is the 32 word row
erasability boundaries, so if the code can't be squeezed below 32 words, there is no
benefit in using any less than 64, so this could be used to add some of the various
'protection' options I outlined above, or to move the setup code into the resident area to
free space in the application area.
My first attempt put the resident part at 37 words, so i just HAD to do some squeezing to
get it down to one row, just to prove it could be done...!
The total code overhead for enabling the in-system reprogrammability came to 50 (decimal!)
words, 18 words setup within the application code and 32 for the redident copy code which
lives in the top row of program space.
Having all the setup within the application code area instead of the fixed 'resident'
section maximises the possibility for future code squeezing/sharing if things start to get
really tight, but care must be taken to ensure that it will work correctly in all
circumstances. Flash-program time for the 16F818 is about 0.7 seconds, so even without any
additional protection mechanisms, the 'potential disaster' window is pretty short.
Note that this technique is only seriously advantageous for systems that ALREADY HAVE a
suitable external eeprom/serial flash, and
communications system as part of the main application.
here's the code - obviously will need tweaking to suit other apps, but the 'get byte' is a
seperate routine and should be fairly easy to modify (once you understand the strange bits
done to save space..!)
; 16F818 eeprom-copy flash loader
; (C) Mike Harrison 2005
; copies from external ST M25Pxx serial flash, address 000000, LSB first
wordcount equ 070 ; variables in shared bank to allow access regardless of page bit
settings
rowcount equ 071
; setup code - resides in application area
copyflash ; copy ext serial flash to program memory. SPI already set up, ints disabled
; setup for SPI eeprom read
clrf intcon ; ensure ints off
movlw 3 ; serial flash read command
call sendspi ; uses spi routine which is already parts of main app code (asserts
/CS)
call sendspi_0 ; address bytes 2..0, sends 0
call sendspi_0
call sendspi_0
movlw 1<<rp1
movwf status ; ensure rp0 and irp clear
movlw sspbuf
movwf fsr ; use indf/fsr to allow access to sspbuf without page switching.
clrf eeadr-100 ; zero PIC flash address
clrf eeadrh-100
movlw loaderstart/d'32' ; number of rows to program
movwf rowcount
goto loaderstart
;end of application code
;------------------------------------------------------------------------------------------------
org 3e0
; start of resident loader section.
loaderstart
eraseloop ; erase and program one row of 32 bytes
movlw
(1<<EEPGD)+(1<<FREE)+(1<<WREN) ; select program memory, row erase, write
enable
call flash_write ; erase row
movwf wordcount ; 32 words to program ( value 32 returned by flash_write retlw)
wordloop
call get_ssp ; get LSbyte from
eeprom
movwf eedata-100
call get_ssp ; get MSbyte from
eeprom
movwf eedath-100
movlw (1<<EEPGD)+ (1<<WREN) ; program memory write
call flash_write
incf eeadr-100 ; increment flash address
skpnz
incf eeadrh-100 ; address msb
decfsz wordcount
goto wordloop
decfsz rowcount
goto eraseloop
goto force_pc ; exit address to force re-entry to PC mode
; the exit address would typically be a well-defined, non-movable address in the app.code,
e.g.0002
; an alternative would be to use "goto $" to loop infinitely and force a reset
when the watchdog
; times out.
get_ssp ; get byte from spi
movwf indf ; (fsr-> sspbuf), initiate SSP read
call sspdelay ; delay to allow spi to happen ( SPI clock is osc/4, so takes about 8
instruction times)
movf indf,w
return
flash_write ; do flash erase or write sequence, w = eecon1 value, assumes rp1 set
bsf status,rp0 ; page 3
movwf eecon1-180 ; select program memory, row erase, write enable
movlw 055 ; write sequence
movwf eecon2-180
movlw 0aa
movwf eecon2-180
bsf eecon1-180,wr ; start write/erase
sspdelay ; entry point used for SPI wait
delay
goto $+1 ; long NOP, used to extend ssp wait
delay.
clrwdt ; avoid watchdog spoiling
your day. placed here to maximise ssp delay time
; wont be executed after flash
write but will be when ssp is read
bcf status,rp0
retlw 020 ; number of words per row, used after erase
Measuring battery voltage on 'always-on' applications where Vbat is higher than the
micro's supply can be harder than it first appears.
You need to divide down the voltage to a range suitable for the ADC or comparator,
however you don't want to waste power in the divider.
You can't use a high-impedance divider as the ADC input pin leakage will cause errors.
Switching the divider is non-trivial due to the level-shifting requirements.
Here's a neat (and very cheap) solution, which simplifies the level-shift requirements
by making use of the fact that the divider only needs turning on for long enough to make a
measurement, and so uses a capacitor to do the level-shifting.
The 'OUT' pin is any output, and can ofen be shared with other functions. When it is
driven low, the divider is turned on for long enough to make a battery voltage measurement
using the ADC (or comparator with internal reference divider).
If size is really tight, the transistor can be replaced by a digital transistor, which
incorporates the top two resistors in the package.
Incidentally, for a voltage regulator for these 'always-on' type applications, I really
like the Holtek HT71xx parts (HT7150 for 5.0v. HT7133
for 3.3v etc.). Their quiescent current is a couple of microamps, they are cheap, and have
the unusual capability to accept inputs up to 24V.

The following is an extremely compact routine for sending asynchronous serial data at
9600 baud (with 4mhz osc). It uses only 16 instructions
and 2 registers. Its compactness makes it useful for temporarily adding to programs for
sending debug data etc. You can increase the
number of stop bits up to 6 if the receiver needs gaps between bytes.
The speed isn't exactly 9600, but is only 0.16% fast. The output polarity is inverted, for
direct feeding into an RS232 port. For true
data, swap the bsf and bcf instructions. Note the use of GOTO $+1 as a 2-cycle NOP - this
is also very useful for writing compact I2C code.
txbyte ; send byte in W at 9600 baud (4MHz osc), 8N1
movwf temp
movlw d'10' ; 8 data + 1 start + 1 stop (increase for more stop bits)
movwf cnt ; cnt is used as bit counter and delay loop counter
clc ;start bit
txloop
skpnc
_____
bcf serport,dout ; output bit = carry
skpc
bsf serport,dout
movlw 10
dloop ; delay
goto $+1 ; 2-cycle NOP in 1
instruction!
addwf cnt ; increment upper 4 bits
only
skpc
goto dloop ; loop sixteen times
rrf temp ; carry will be set
- shifted in as stop bit
decfsz cnt
goto txloop
retlw 0
22/Feb/2001
For many years I've been irritated by the relatively large amount of code needed to talk
to I2C EEPROMS, especially on c5x parts where stack depth is limited. I swore one day I'd
try to do some highly optimised code. I finally got around to it, and 30-odd sheets of
paper later, below is the result
It's primarily targetted at c5x applications, and was designed with the following criteria
& limitations in mind (based on several real applications) :
- Absolute minimum code size, stack usage and register usage, in that order.
- Must be able to read or write one *or more* bytes to a single small (<=256 byte)
- I2C eeprom, transferring data to/from an area of RAM pointed to by the FSR.
- I2C address of eeprom fixed at assemble time.
- Automatic retry if the eeprom is busy with a previous write.
- Correct I2C timing at 4MHZ.
- SDA and SCL will be on the same port, but no restrictions on which bits are used.
- No assumptions to be made on the state of SCL and/or SDA on entry - this is important
where pins are
shared with other funcions - SCL can often be re-used as the eeprom will ignore it as long
as SDA is stable.
- No jump table schemes (as used in Microchip's code in the 12CE518 data sheet) to allow
the code body to be
moved out of page zero if required
The following limitation of the current code is considered acceptable for the applications
targeted :
The number of bytes to read/write, and the RAM address to read/write are likely to be
fixed at assemble time, so it doesn't matter if strange offsets etc. are required to these
values, as this costs no code space, and can be handled easily with macros.
The code will read or write up to 14 bytes per call, although the write cache size of
small eeproms will usually limit writes to 8 bytes or
fewer. READ THE DATASHEET CAREFULLY to find the cache size for the device you are using,
and be aware that this can vary between different manufacturers of the same part, and part
revisions (e.g. Microchip 24LC01 vs. 24LC01B).
The current code body uses 68 words (including return). The code used to set-up addresses
etc. is not counted as this will be different in different applications. Three words can
be saved if 'fast mode' eeproms are used (e.g. 24LC01B at 5V). 4 registers are used,(plus
FSR), one of which is for the eeprom address, which is preserved and can be eliminated if
only one address will be used. No internal CALLs are used.
Explanatory notes - the following notes describe the more subtle aspects of the
code - you don't need to understand them if you just want to use the code 'as is', but you
will if you want to modify or optimise it further!
The code runs in two 'phases' - a write phase and a read phase, the latter only being used
for read operations.
Each phase begins with a start condition, followed by the control byte.
The write phase sends the control byte, the eeprom address, and for write operations, the
data to be written.
The read phase sends the control byte and reads the bytes from the eeprom.
The variable 'cnt' holds two counts, one per nibble, stored as negative numbers, i.e.
counting up to zero.
Bits 7-4 hold the number of bytes in the write phase
Bits 3-0 hold the number of bytes in the read phase
The flags byte is used as follows :
bit 0 'read' is set for reading bytes, cleared for writing
bit 1 'addr' is set after the eeprom address has been sent, to ensure it only gets sent
once.
bit 2 'rden' is a 'read pending' flag, which causes a switch to read mode after the second
control byte has been written
bits 7..5 are used as a bit counter for the byte send/recive section.
Using the same byte for flags and the bit count doesn't actually take any more code - the
extra cycles to increment the count
it by adding 20h are saved by not having to initialise the count - it's done when the
flags are set up.
When SCL is set high, if SDA is tri-state (input or '1' output), the SDA output register
bit may get set high
(which would prevent SDA going low) by read-modify-write bit operations on SCL. This
problem is avoided by clearing
both SDA and SCL bits together with ANDWF. This will not cause SDA glitches, as the only
time this clearing will
change the SDA output register state is when SDA is tri-stated.
FSR is incremented on every byte - there's no point doing it conditionally as all that's
needed to compensate
is an assembly-time offset.
Note that in a few cases you will need to add a clrwdt somewhere inside the retry loop,
depending on choice of
eeprom, WDT prescale setting, and the delay between a write and any subsequent read or
write attempt.
The worst-case write time of a 24LC01B is 10mS, and the PIC's worst-case undivided
watchdog period is 9mS.
Everyone's application is different, and so there is scope for further optimisation
depending on
particular requirements - here are a few suggestions :
If only one eeprom address is needed (e.g. a single parameter block), the eeadr register
can be replaced
with a literal.
If only single byte reads & writes are required, a couple of optimisations are
possible - the INCF FSR goes, and the
conditional write to INDF can be an unconditional write to the target register, as it
doesn't matter if it gets
written with rubbish before the actual data is read.
If the routine is called from several places, some of the set-up done in the macros could
be placed
at the head of the code, depending on what parameters are the same for all calls.
If the system timing is such that the eeprom will never be busy writing when an attempt to
access it again is made,
the 2 words of retry code can be omitted.
It may be possible to simplify some of the start/stop condition code if it is known that
other I/O activity
will not affect the state of the SCL/SDA porta and TRIS registers between calls.
The retry on busy could easily be changed to return immediately, for example by replacing
the
'goto retry_iic' with 'retlw 1'. The calling code could then check W to test for success.
Some of the delays (as noted in comments) can be omitted for 'fast mode' compatible
eeproms.
If power consumption is an issue, you may want to add a delay to the retry loop to reduce
the number of retries that will be attempted when the eeprom is busy writing.
A 10K pullup resistor is required from SDA to Vdd. No pullup is required on SCL as
open-drain SCL drive is not required for eeprom applications.
Note that this code will not work as-is with the 12CE51x devices, and although it could be
modified, it may not be especially optimal - the CE
devices don't (all?) have a write cache, so the multi-byte write capability will not be
useful in its current form.
;***********************************************************************
; compact I2C eeprom access code V1.0
; (C) Mike Harrison 1998
; example code for PIC12C508
;------------------------------------------------------- workspace
cnt equ 08 ; byte count register
flags equ 09 ; flags and bit count :
read equ 0 ; 1 to read bytes, 0 to write
addr equ 1 ; 0 to send eeprom address byte
rden equ 2 ; 1 to enable read next cycle
; b5-7 used as bit counter
temp equ 0a ; read/write data byte
eeadr equ 0b ; eeprom address
iiport equ gpio ; port address
sclbit equ 4 ; SCL port pin (no pullup
required)
sdabit equ 5 ; SDA port pin (10K pullup to
Vdd required)
lotris equ b'00101' ; TRIS setting with SCL and SDA outputs, other bits
as required by application
hitris equ lotris+(1<<sdabit)
; calling examples :
;to read 3 bytes from eeprom addr 10..12 to registers 14..16
; movlw 10
; movwf eeadr
; readee 3,14
;to write 5 bytes from registers 19..1d to eeprom address 0
; clrf eeadr
; writeee 5,19
;--------------------------------------------------- calling macros
; to simplify parameter set-up, you can call the code using the following macros
readee macro bytes,address
; usage : readee <no. of bytes to read>, <RAM address to read to>
movlw address-3 ; FSR offset due to unconditional increment
movwf fsr
movlw 0ef - bytes ; 2 writes (control, address) + n+1 reads (control,data)
call do_iic
endm
writeee macro bytes,address
; usage : writeee <no. of bytes to write>, <RAM address to write from>
movlw address-1
movwf fsr
movlw 0e0 - (bytes <<4) ; n+2 writes (control,address,data), no reads
call do_iic
endm
;-----------------------------------------------------------do_iic
do_iic ; read/write byte(s) to I2C EEPROM
; W & FSR to be setup as follows :
; read : EF - nbytes FSR = RAM address-1
; write : E0 - (nbytes<<4) FSR = RAM address-3
; eeadr holds eeprom address (preserved on exit)
; on exit, FSR points to the byte after the last one read/written
; nbytes can be up to 14, but eeprom write cache may limit this
movwf cnt
retry_iic
clrf flags ; initialise flags and bit count
phaseloop
movlw hitris
tris iiport ; ensure SDA high
bsf iiport,sclbit ; SCL high
bcf iiport,sdabit ; ensure SDA o/p reg low
movlw lotris
goto $+1 ; ensure
Tsu:sta - can be omitted in fast mode
tris iiport
; sda low - start
condition
movlw iiadr
; IIC control
byte (write)
btfsc flags,rden
movlw iiadr+1 ; .. or read control
byte if read pending
movwf temp ; IIC control byte
bcf iiport,sclbit ; scl low - code above ensures Thd:sta
byteloop ;
; start of byte read/write section
movlw lotris
btfss flags,read
; set SDA high
(tri-state) if reading
btfsc temp,7
; set SDA low
only if writing and data bit = 0
movlw hitris
; (sda o/p
register bit will be low)
tris iiport
goto $+1
; wait set-up time
bsf iiport,sclbit
; clock high (may set SDA o/p reg bit high)
clc
; used later - done here for timing
movlw 0ff^(1<<sclbit)^(1<<sdabit) ; mask to
clear SDA and SCL, " "
btfsc iiport,sdabit
; test SDA input
for read
sec
andwf iiport
; SCL low, SDA o/p reg bit low
rlf temp
; shift read data in or write data out
movlw 020
addwf flags
; increment bitcount in b5-7
skpc
goto byteloop
; do 8 bits
movlw 0f0
xorwf cnt,w
; last byte of read ? Z set if so
movlw lotris
; ack low if reading to send ack to eeprom
skpz
; ..but no ack on last byte of read
btfss flags,read
;
movlw hitris
; ack high for write to test ack from eeprom
tris iiport
bsf iiport,sclbit ; clock high to
get or send ack bit
goto $+1
; wait ack pull-up time
movlw 0ff^(1<<sclbit)^(1<<sdabit) ; SDA/SCL low
mask, done here to add delay
skpz
; last byte of read - skip retry
btfss iiport,sdabit ; read ack bit
state
goto no_retry_iic
goto retry_iic ;
retry if ack high (will be forced low on reads, except last byte)
no_retry _iic
andwf iiport
; set scl and sda o/p register bit low
;..................... end of byte read/write section
movf temp,w
btfsc flags,read
movwf indf
; store data if reading
movf indf,w ; get
write data
incf fsr ;
increment RAM pointer
btfss flags,addr
movf eeadr,w ;
load eeprom address if not disabled
movwf temp ; byte
to send next loop - address or data
bsf flags,addr ; disable address flag
btfsc flags,rden ; read mode pending?
bsf flags,read ; set read mode
movlw 010
addwf cnt ;
increment byte counter in B4..7
skpnz
goto done ;
both nibbles zero - all done
skpc
; c set if b7-4 now clear - write phase done
goto byteloop
bsf flags,rden ; set 'read pending' flag
swapf cnt ; load byte counter with read byte count
goto phaseloop ; do second phase of
command
done
; do stop condition
movlw lotris ;
(SDA o/p bit will be low)
tris iiport
; set SDA low
bsf iiport,sclbit ; scl high
goto $+1
; ensure Tsu:sto
goto $+1
; both these can be omitted for fast mode
movlw hitris
tris iiport ; sda
high
retlw 0
;---------------------------------------
LPC2103 / uALFAT baudrate calculator
The Philips LPC2103 (used in the GHI
uALFAT chip) has a rather complex baudrate calculator. Here's an Excel Spreadsheet to simplify finding the right value.
|