Tour : click picture below for previous

Back to Mike's Electric Stuff

Click picture below for next


wpe149.jpg (163094 bytes)

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.

Save0003.gif (10870 bytes)

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. 


The 32-word* Bootloader

* 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

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.

Save0004.gif (7085 bytes)


Tiny 9600 baud async TX 

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


Compact I2C EEPROM code

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.


wpe149.jpg (163094 bytes)


Click picture above for Previous

Back to Mike's Electric Stuff

Click picture above for next