The following files demonstrate how to use the multi-byte read and write protocol of the Microchip 24LC16B 16Kb serial RAM chip. The complete test program consists of four files. The main program is 24LC_TST.ASM, and is here used only to show examples of how the RAM driver is called. The driver itself is 24LC.ASM and is simply included into your program, typically at the end. There are two definition files called RKMACROS.INC and TIMER.INC which are from my project toolbox. The former defines the macros used while the latter computes at compile time the required internal timer time constants. Both can be left out after appropriate editing of the source files. The programs are written in Microchip MPASM assembler and can be included in an MPLAB project.
Starting from address zero, the demonstration program writes BYTES bytes to the chip, reads them back, checks they are the same, increments the address by BYTES bytes, then does the same again until the entire 16Kb have been written, read and checked. In my project I am reading and writing eight bytes at a time to a data base on this chip, so by default the constant BYTES is here set to 8. However it can be set to any value in the range 1 to 16.
Note that the 24LC16 writes multiple bytes by incrementing only the low 4 bits of the address in its internal counter. Thus if the low address was 0xC0 you could write 16 bytes, but if was 0x7 you could only write 9 bytes (ie 16-7=9) before the counter overflowed and you started writing somewhere else. This means you can only write multiple data groups in a controlled way, where you know the destination starting address. The following table might make this a little clearer:
Start Address (hex) | Possible Write Lengths | ||||||||||||
xxx0 | 1 thru 16
xxx1
| 1 thru 15
| xxx2
| 1 thru 14
| ...
| ...
| xxxD
| 1 thru 3
| xxxE
| 1 thru 2
| xxxF
| 1 only
| |
It is usually easier to write in multiples of 2 (1,2,4,8 and 16) based on the address you start from. Thus if you always write in groups of 4 from a starting address of zero, the overflow problem will never arise. Of course it will also never arise if you always write one byte at a time no matter where you start from, but this is none too efficient. With a little extra work, you could consider the 24LC16 as a 1024 sector disk, with the 16-byte buffer in data memory as a sector cache which you maintain just like a disk cache, along with dirty bits and all.
Data is written from a structure with two more bytes than BYTES:
1 | AAAAABBB | AAAAA = number of bytes, BBB = high 3 bits of address (ie the bank) 2
| CCCCCCCC
| low 8 bits of address (0-255)
| 3
| Byte[1]
| 1st byte of BYTES bytes
| 4
| Byte[2]
| 2nd byte of BYTES bytes
| ...
| ..
| ..
| BYTES+2
| Byte[BYTES]
| Last byte
| |
Reading is done to exactly the same type of structure, where the first 2 bytes define the address to be read. It can re-use the same address as the write structure if required.
If you connect an LED/resistor combination between RA2 and ground, it will blink briefly at the completion of a succesful write/read of the entire chip, and flash for 200mSecs for an error.
A zipped copy of these four files is available: | 24LC.ZIP |
Please refer all queries to Ron Kreymborg
NOLIST ;****************************************************************** ; ; A set of standard macros. ; ; Ron Kreymborg ; ;****************************************************************** intoff macro bcf intcon,gie btfsc intcon,gie goto $-2 endm bank0 macro bcf STATUS,RP0 endm bank1 macro bsf STATUS,RP0 endm dotris macro arg1, arg2 bsf STATUS,RP0 movlw arg1 movwf arg2 bcf STATUS,RP0 endm dodelay macro arg1 movlw arg1 call delay endm LIST
; PRESCL and TMRVAL computation NOLIST ; Take the processor clock frequency in Hz (clock), and ; the required time delay in uSecs (dusec), and compute ; a value for the prescaler (PRESCL) and another for ; TMR0 (TMRVAL). _cycle equ clock >> 2 _dfreq equ 1000000 / dusec _divr equ _cycle / _dfreq if (_divr < 512) error "Delay time too short. Assign PSA to WDT." endif _test set _divr / 256 _testB equ _divr % 256 if (_testB > 0) _test set _test + 1 endif if (_test > 1) _temp set 2 PRESCL set 0 endif if (_test > 2) _temp set 4 PRESCL set 1 endif if (_test > 4) _temp set 8 PRESCL set 2 endif if (_test > 8) _temp set 16 PRESCL set 3 endif if (_test > 16) _temp set 32 PRESCL set 4 endif if (_test > 32) _temp set 64 PRESCL set 5 endif if (_test > 64) _temp set 128 PRESCL set 6 endif if (_test > 128) _temp set 256 PRESCL set 7 endif if (_test > 255) error "Timer requirement is outside range" endif TMRVAL equ 256 - (_cycle / _temp / _dfreq) LIST
;****************************************************************** ; ; Test program for Microchip 24LC16B RAM chip ; ; Takes about 660mSecs to write then read 16Kbytes @ 16bytes a time. ; ;****************************************************************** title "Main Routine Test Program" list p=pic16f84,r=dec,n=80,x=off,st=off include p16f84.inc include rkmacros.inc errorlevel -302 ; no bank warnings errorlevel -305 ; no default dest warnings clock equ 10000000 ; my crystal frequency dusec equ 1000 ; required delay (in uSecs) include timer.inc ; (note include must be after ; previous two definitions) constant BYTES=8 ; number of bytes to transfer constant BLOCKSIZE=BYTES+2 ; Masks - maska equ b'11110000' ; RA0 and RA1 are used by 24LC16 driver ; RA2 and RA3 are free ; Data - CBLOCK 0xc ; start of data area dval ; delay cycle counter cntr ; general counter temp ; temporary storage w_data:BLOCKSIZE ; write data structure r_data:BLOCKSIZE ; read data structure lo_address ; low address (8 bits) hi_address ; high address (3 bits) spntr ; source data pointer dpntr ; destination data pointer ENDC ; Flags - ;**************************************************************** org 0 ; for future expansion goto start ; org 4 ; retfie ; ;**************************************************************** ; Main program begins start clrwdt ; setup 1mSec timer bsf STATUS,RP0 movlw b'11010000' | PRESCL ; prescaler to TMR0 movwf OPTION_REG bcf STATUS,RP0 dotris 0,PORTA ; PORTA all outputs for now clrf PORTA ; Call initialisation routines call preprw24 ; initialise the 24LC16B driver ; Start the test code mloop clrf hi_address ; start from zero in the ram clrf lo_address mn1 movlw BYTES*8 ; number of bytes addwf hi_address,w ; add in page address movwf w_data movwf r_data mn2 movf lo_address,w movwf w_data+1 ; put in write structure movwf r_data+1 ; and read movlw w_data+2 ; start address of data movwf FSR ; store for indirect movlw BYTES ; fill data values movwf cntr ; byte counter movlw 1 loop1 movwf INDF ; w to indirect incf FSR,f ; step pointer addlw 1 ; step w decfsz cntr,f goto loop1 ; for BYTES byte movlw BYTES ; zero the read area movwf cntr movlw r_data+2 ; start address of data movwf FSR loop2 clrf INDF incf FSR,f decfsz cntr,f goto loop2 ; Do the read/write test re_wrt movlw w_data ; address of write data structure call write24 ; go write it to eeprom btfss STATUS,z ; success? (ie zero return?) goto re_wrt ; no, re-write re_read movlw r_data ; address of read data structure call read24 ; go read the eeprom btfss STATUS,z ; success? (ie zero return?) goto re_read ; no, re-read ; now check they are the same check movlw BYTES movwf cntr movlw w_data+2 ; written data movwf spntr ; make write pointer movlw r_data+2 ; read data movwf dpntr ; make read pointer chk1: movf dpntr,w ; read pointer movwf FSR ; to FSR movf INDF,w ; get read data movwf temp ; save in temp incf dpntr,f ; step pointer movf spntr,w ; written pointer movwf FSR ; to FSR movf INDF,w ; compare with temp subwf temp,f ; same? btfss STATUS,z goto bad ; no match!! incf spntr,f ; step pointer decfsz cntr,f goto chk1 ; Phew! chk2 movlw BYTES ; step low address by BYTES addwf lo_address,f btfss STATUS,z ; skip if went to zero goto mn2 ; around again incf hi_address,f ; step high address to next page btfss hi_address,3 ; gone to 4? goto mn1 ; no bsf PORTA,2 ; blip a led on RA2 to say all is well dodelay 5 bcf PORTA,2 goto mloop bad bsf PORTA,2 ; long blink for error dodelay 200 bcf PORTA,2 ; copy to lcd goto chk2 ;**************************************************************** ; Delay routine using the internal timer. Will delay for W ; times the value of dusec in microseconds. delay movwf dval dy1 clrf TMR0 bcf INTCON,T0IF movlw TMRVAL ; set timer movwf TMR0 dy2 btfss INTCON,T0IF ; timer overflow yet? goto dy2 ; no decfsz dval,f ; yes, all cycles? goto dy1 ; no return ; yes, delay finished ;**************************************************************** ; Includes include <24lc.asm> end
;**************************************************************** ; ; Multi-byte Read and Write routines for the Microchip 24LC16B. ; ; Data is written from a structure: ; AAAAABBB AAAAA = number of bytes, ; BBB = high 3 bits of address (ie bank) ; CCCCCCCC low 8 bits of address (0-255) ; BYTE-1 1st byte of data ; BYTE-2 ; etc ; ; Reading is done to exactly the same type of structure, with ; only the first 2 bytes requiring definition. It can be at the ; same address as the write structure if required. ; ; A constant must be defined called BYTES that represents the ; number of bytes to transfer during a read or write (1-16). ; If the structure started at w_data, it could be prepared as: ; ; movlw BYTES*8 ; BYTES in hi 5 ; addwf hi_addr,w ; hi part of address in lo 3 ; movwf w_data,f ; movf lo_addr,w ; movwf w_data+1,f ; ; Note that the 24LC16 writes multiple bytes by incrementing ; only the low 4 bits of the address. Thus if the low address ; was 0xC0 you could write 16 bytes, but if was 0x7 you could ; only write 9 bytes (ie 16-7=9). This means you can only write ; data groups in multiples of 2 (1,2,4,8 and 16). ; ; Initialisation requires RA0 and RA1 be set up as outputs. ; The routines require a mask be defined calledthat ; defines how the other pins on porta are defined. For example, ; if RA3 is an output and RA2 an input, the mask would be: ; maska equ b'11110100' ; Note that RA0 and RA1 must be set to zero in this mask. ; ; As part of initialisation, call the following subroutine ; to set up RA0/RA1: ; call preprw24 ; set RA0/RA1 ; ; A call to write24 is preceeded by loading W with the address of ; the write data structure. It will return with zero in W and the ; Z flag set for success. An error is indicated by a 1 in W and ; the Z flag cleared. Only possible error is 24LC16 busy, so ; just retry, either immediately or at some later time. ; ; A call to read24 requires the same setup as write24. Errors are ; also the same and should also be retried. ; ; The following Special Function Registers are used or modified ; in some way: ; FSR ; STATUS ; PORTA, TRISA ; ; The routines use 3 levels of the stack in addition to the call. ; You can use an INCLUDE to integrate this routine with your program. ; ; Ron Kreymborg, February 1998 ; ;**************************************************************** maska equ b'11110000' ; RA0 and RA1 are used by read/write ; RA2 is CRO synch line ; RA3 is LED driver CBLOCK flags24 ; general flags cont24 ; control byte for 24LC16 cntr24 ; general counter bcntr24 ; byte counter ENDC ; Flags - rwbit24 equ 0 ; high for read ack24 equ 1 ; high for valid ack ; Port A defs - scl24 equ 0 ; SCL clock line sda24 equ 1 ; SDA data line ;**************************************************************** ; Write the data - enter with a pointer to the start of the write ; data structure in W. write24 call setup24 addlw 0 ; success? btfss STATUS,z ; (ie zero return?) goto write24c ; no, error write24a movf INDF,w ; get next byte movwf cont24 call sendb24 ; send it incf FSR,f ; step to next decfsz bcntr24,f goto write24a ; around again write24b movlw 0 ; no errors write24c call stop24 ; all done addlw 0 ; force zero flag return ;**************************************************************** ; Read the data - enter with a pointer to the start of the read ; data structure in W. read24 call setup24 ; send the two preamble bytes addlw 0 ; success? btfss STATUS,z ; (ie zero return?) goto write24c ; no, error call prestart24 call start24 ; start again bsf flags24,rwbit24 ; now set for reading decf FSR,f ; point back to structure start decf FSR,f call comm24 ; send hi address again incf FSR,f ; skip lo address, point to data area read24a movlw 8 ; read in 8 bits movwf cntr24 call sda_in24 ; set SDA for reading read24b bcf STATUS,c ; clear carry bsf PORTA,scl24 ; clock high btfsc PORTA,sda24 ; check sda bsf STATUS,c ; set carry if high bcf PORTA,scl24 ; clock low rlf INDF,f ; put carry into output byte decfsz cntr24,f goto read24b ; for all bits call outall24 ; make all outputs again bcf PORTA,sda24 ; ensure SDA low decf bcntr24,f ; all bytes? btfsc STATUS,z goto write24b ; yes, send stop read24d bsf PORTA,scl24 ; send ack unless last byte nop ; not really needed at 4MHZ bcf PORTA,scl24 ; clock low again incf FSR,f ; step to next place goto read24a ; get next byte ;**************************************************************** ; Common subroutines. These are the I2C routines. setup24 movwf FSR ; setup pointer bcf flags24,rwbit24 ; set for writing call start24 call comm24 ; send hi address btfsc flags24,ack24 ; continue only if valid ack retlw 1 ; return error movf INDF,w ; get low address byte movwf cont24 call sendb24 ; send it incf FSR,f ; step on to start of data retlw 0 comm24 bcf flags24,ack24 ; clear ack bit movf INDF,w ; get 1st byte of structure movwf cntr24 ; save temporarily rrf cntr24,f ; get byte count rrf cntr24,f rrf cntr24,w andlw 0x01f movwf bcntr24 ; make a counter movf INDF,w ; get 1st byte again addwf INDF,w ; shift left one bit andlw 0x0e ; mask to 3 bits btfsc flags24,rwbit24 ; reading or writing? addlw 1 ; set R/W bit if required addlw 0xa0 ; attention bits movwf cont24 ; put in control byte call sendb24 ; send control byte incf FSR,f ; step on to low address return ; Clock out the byte in cont24. sendb24 movlw 8 ; 8 bits movwf cntr24 sendb24a rlf cont24,f ; bit 7 into carry btfsc STATUS,c ; is it 1? bsf PORTA,sda24 ; yes, set SDA high bsf PORTA,scl24 ; clock high nop ; not really needed at 4MHZ bcf PORTA,scl24 ; clock low bcf PORTA,sda24 ; ensure SDA low decfsz cntr24,f goto sendb24a ; now check for an aknowledge call sda_in24 ; make SDA input now bsf PORTA,scl24 ; clock high btfsc PORTA,sda24 ; ack from 24LC16? bsf flags24,ack24 ; yes, set ack bit bcf PORTA,scl24 ; clock low ; bcf PORTA,sda24 ; ensure is low call outall24 ; SDA output again return start24 bcf PORTA,sda24 ; data low for start condition nop ; setup time bcf PORTA,scl24 ; now clock low return stop24 bsf PORTA,scl24 ; stop sequence nop bsf PORTA,sda24 return outall24 bsf STATUS,rp0 movlw maska ; SDA and SDL are outputs movwf TRISA bcf STATUS,rp0 return sda_in24 bsf STATUS,rp0 movlw maska + b'10' ; SDA is input for reading movwf TRISA bcf STATUS,rp0 return preprw24 call outall24 ; make SDA /SCL outputs prestart24 bsf PORTA,sda24 ; establish pre-start nop bsf PORTA,scl24 return
Code:
Questions: