Home ==> Micro beginner ==> 11. EEPROM mit LCD-Anzeige
ATtiny24

Lecture 11: EEPROM with LCD on an ATtiny24


New is in this lecture to read from and to write to the internal EEPROM and to convert binary numbers to decimal and to display those.

11.0 Overview

  1. Introduction to EEPROM
  2. Introduction to decimal conversion
  3. Hardware, components, Mounting
  4. An 8 bit counter counts power-ups
  5. A 16 bit counter

11.1 Introduction to EEPROM

11.1.1 An EEPROM as a permanent memory

The internal SRAM is rather short-lived, its content is deleted whenever the operating voltage gets lost. When the controller starts new, he has to initiate the SRAM's content to their default values. If you have had a lengthy procedure to set the SRAM's content to your very special setting, e.g. ten favoured wake-up times in a digital clock, you start all over again whenever the controller restarts. Not very convenient ...

The built-in EEPROM is not that short-lived. Its content remains unchanged even if the operating voltage is missing for longer times. Typical content where EEPROM to use for are such wake-up times, preselected time-outs of a stop watch, a preselected and adjustable temperature for your refrigerator or - like in our case - hidden power-up counters for printers or for other equipment.

EEPROMs are not designed to withstand millions of read and write cycles. ATMEL guarantees 100,000 erase and write cycles. EEPROM therefore should not be used like SRAM, with several write operations per second. With 3 ms duration per write cycle this is nevertheless the most ineffective way to store something temporarily.

The ATtiny24 has 128 byte EEPROM, which is sufficient for far more than ten favoured wake-up times. It has its own address space, starts at 0x0000 and ends at 0x007F.

11.1.2 To write to the EEPROM

Three different write procedures are possible with the EEPROM:
  1. to clear its content in total. This requires a programming tool like the built-in tools in the Studio. No, the content after clearing is not 0x00, it is 0xFF. A post-write of a binary one to a location that is already zero is not possible, unless you write the whole byte by controller action or you clear the whole EEPROM to 0xFF first.
  2. to write its content. This also requires a programming tool, and a hex file that holds addresses and content in a special encoding format. The write cycle has to be preceeded by an erase.
  3. to erase a byte (to 0xFF) and to program its content in two subsequent steps by controller action.
The first write process (clearing) is automatically performed if you program the flash memory. So whenever you transfer a new version of your software, the EEPROM content gets lost and has to be programmed new (to program the EEPROM hex file). Most of the more recent AVRs allow to set a fuse named "EESAVE", which prevents clearing the EEPROM content during flash memory write procedures. If this fuse is set, the EEPROM content has to be separately cleared and rewritten. Of course, the controller can write content at any time even with this fuse set.

To create and later write an EEPROM hex file, insert the following to your assembler source code (at whatever place in the code):

.ESEG ; Switch to EEPROM segment and EEPROM address counter
.ORG 0 ; Start at address 0 (or wherever you want)
.db 0,1,2,3,4 ; Write bytes
.db "01234" ; Write text in ASCII codes
.CSEG ; Switches back to the code segment

The directive ".ESEG" advices the assembler to assemble the following content to the EEPROM hex file. This file is named "SourceCodeName.eep", where SourceCodeName is the name of your assembler source code file in .asm format. The generated .eep file is in Intel hex format and can be read with any simple editor.

As the EEPROM is organized byte-wise, any number of bytes can be written to the ESEG until it is full (if you specified your device type in the .include directive). With the .ORG directive it is possible to write the specified content to any location of the EEPROM, even if other EEPROM space already holds other content (remember: it is not possible to overwrite zeros!).

Within the ESEG only ".DB"- and ".DW"-directives are accepted by the assembler.

In order to write the content of such an .eep file one uses its favoured programming tool (e.g. within the Studio), selects the EEPROM write section and selects the generated .eep file (in the Studio by clicking on the small square behind the file name with the "..." on it). By clicking on "WRITE" the content is written to the EEPROM.

The same section of the programming tools can be used to read the content of the EEPROM to a file. Which then is also in Intel hex format, so better have a look at those kind of files in case you ever need that information or if you want to verify the EEPROM's content.

11.1.3 To read EEPROM content in assembler

EECR Of course reading EEPROM content in assembler is done via ports. The following steps have to be absolved:
  1. Make sure that the EEPROM is not occupied by any write operation. This can be done by reading the EEPE bit in the EEPROM control port EECR. If that bit is zero the EEPROM is ready to be read.
  2. The address, from which content shall be read is to written to the ports EEARH (MSB) and EEARL (LSB). Even if the device has less or equal 256 EEPROM bytes, the MSB shall be cleared.
  3. The the Read Enable bit EERE in the control port is set. This blocks the controller for four clock cycles and transfers the data from the preselected address in the EEPROM to the data port "EEDR", from which the content can be read.
Subsequent read accesses need only to adjust the LSB of the read address (as long as the MSB is not different), there is no auto-increment implemented.

12.1.4 To write data to the EEPROM in assembler

Writing EEPROM content goes as follows:
  1. First it has to be checked that previous write cycles are finished (EEPE in port EECR is zero).
  2. By writing the EECR to all zero it has to be assured that EERE as well as EEPM1 and EEPM0 are clear. EEPM1 and EEPM0 specify the write mode, 00 is the sequence "Erase" and subsequent "Write".
  3. MSB and LSB of the write address are written to the address ports EEARH and EEARL.
  4. The byte that is to be written to the EEPROM is written to the data port EEDR.
  5. The EEMPE bit in the EEPROM control port is set. This bit deletes itself after four clock cycles (so nothing else shall be executed in between this and the next instruction, no interrupt shall occur and be handled).
  6. Within these four clock cycles set the EEPE bit in EECR. The Erase&Write cycle now last for 3.2 ms, at the end the EEPE bit clears itself.
After starting the programming sequence (not before!) setting the EERIE bit in EECR enables to trigger the EE_RDY interrupt. Following the last write operation the EERIE bit has to be cleared, otherwise the ready-to-program interrupt would cause a permanent interrupt and blocks the controller from doing something else.

Home Top EEPROM Decimal conversion Hardware Byte counter Word counter


11.2 Introduction to decimal conversion of binaries

Because we have a LCD now, we would like to display human-readable numbers, which are of course decimal. So we have to learn now to convert binaries.

11.2.1 The most primitive (and most lengthy) version

8 bit numbers in binary are between 000 and 255 in decimal. So we can start with "CPI rNmbr,200" if the first digit is a 2 (if carry is not set after CPI). If yes we place a "2" to the first decimal digit. If not, we compare with decimal 100 to find out, if it is a "1". Otherwise it is a "0", or with suppressing leading zeros a blank character. We can send the 2, 1 or the blank in ASCII to the LCD.

After we subtracted 200 or 100 from the binary number (if those were the case) we are between 0 and 99. Now we can in the same manner look downwards if the number is larger than 90, 80, 70, etc. to identify the next digit. A very lengthy process, but controllers are dump and the will execute whatever been told, no matter how intelligent the source code is.

Beware to write a blank to the LCD if this second digit is zero: it would be confusing to see for example "2 8".

If we have identified the second digit now and reduced the number down to the last digit 0 to 9, we can just add an ASCII-0 (decimal 48) and we are complete. But do not try "ADDI rNmbr,48", it won't assemble. Only SUBI is correct, so we can use an old trick by formulating "SUBI rNmbr,-48". As we learned in school "minus minus is plus". So who cares if the AVRs do not know an ADDI instruction?

11.2.2 The improved version

Instead of comparing we can subtract 100 (or 10 for the second digit) on and on until a carry occurs and to count the number of times this did not lead to a carry bit set. That goes like this:

; The binary number is in R0
	ldi R16,100 ; R16 is the decimal digit
	clr R1 ; R1 is counter
Count1:
	cp R0,R16 ; Compare with 100
	brcs Digit1 ; Overflow, digit complete
	sub R0,R16 ; Subtract 100
	inc R1 ; Increase counter
	rjmp Count1 ; Go on comparing/subtracting
Digit1:
	ldi R16,'0' ; ASCII-Zero
	add R16,R1 ; Add to counter
	; Here: output digit 1
	ldi R16,10 ; Now the 10s
	clr R1 ; Again start at zero
Count2:
	cp R0,R16 ; Compare with 10
	brcs Digit2 ; Overflow, digit complete
	sub R0,R16 ; Subtract 10
	inc R1 ; Increase counter
	rjmp Count2 ; Go on comparing/subtracting
Digit2:
	ldi R16,'0' ; ASCII-Zero
	add R16,R1 ; Add counter
	; Here: output digit 2
	ldi R16,'0' ; ASCII-Zero
	add R16,R0 ; Add remaining rest
	; Here: output digit 3

New is the instruction CP register, register. This subracts the second register from the first one temporarily, sets the flags in SREG and leaves the first register as it was.

The two parts marked red are identical. If you are short in flash memory you can formulate it as a subroutine and call it with RCALL.

11.2.3 The 16 bit version

If we have a binary with 16 bits length, the following changes: This goes like this (now comparision, subtracting and conversion to ASCII in a subroutine):

; The binary to be converted is in R1:R0
	ldi ZH,HIGH(10000) ; Ten thousands
	ldi ZL,LOW(10000)
	rcall Count
	; Digit 1 output
	ldi ZH,HIGH(1000) ; Thousends
	ldi ZL,LOW(1000)
	rcall Count
	; Digit 2 output
	ldi ZH,HIGH(100) ; Hundreds
	ldi ZL,LOW(100)
	rcall Count
	; Digit 3 output
	ldi ZL,LOW(10) ; Tens
	rcall Count
	; Digit 4 output
	ldi R16,'0'
	add R16,R0
	; Digit 5 output
	; fertig
; Subroutine
Count:
	clr R16 ; R16 is counter
Count1:
	sub R0,ZL ; Subtract LSB
	sbc R1,ZH ; Subtract MSB with carry
	brcs Count2 ; Overflow during subtract
	inc R16 ; no overflow
	rjmp Count1 ; continue subtracting
Count2:
	add R0,ZL ; Add LSB to revert last subtract
	adc R1,ZH ; Add MSB and carry
	subi R16,-'0' ; Add ASCII-Zero
	ret ; Return with result in R16

New instructions here are ADC and SBC register,register. This adds resp. subtracts the second register from the first and adds/subtract one if the carry flag is set. Overflows from previous LSB operations to the MSB of the 16 bit value can be handled with that.

One further branching instruction used here for the first time is BRCS distance. It branches in case that the carry flag was set by preceeding instructions and adds the given distance to the program counter. Distances can be forward (distance positive) or backwards (distance negative). The assembler calculates those distances from the given label that is used to mark the branching target, but can also be specified manually with a constant value (e.g. brcs +3 or brcs -15). All SREG bits can be used to branch if set (BRxS) or cleared (BRxC). Note that the Z bit in SREG equals E in the branch instruction, using its original name Z for that leads to an error message of the assembler.

The problem is solved, but leading zeros occur as 0 and not as blank. This problem is resolved in the two software examples of this lecture.

Now it is easy to even convert 24 or 32 bit numbers (or even 40 bits, like in one of the upcoming lectures) to decimal.

Home Top EEPROM Decimal conversion Hardware Byte counter Word counter


11.3 Task, hardware, components and mounting

11.3.1 Task

The following task has to be solved: on every reset and if the operating voltage is applied a counter has to be increased and the previous and current state has to written to the LCD.

11.3.2 Scheme

Scheme To not having to switch off the operating voltage during the debugging phase we install a reset key.

All components are well known, mounting is also simple.


Home Top EEPROM Decimal conversion Hardware Byte counter Word counter


11.4 Byte counter

11.4.1 The LCD routines as include file

The routines to init and to write to the LCD can be used in general whenever a LCD is part of the program. In that case we can formulate those routines in a way that they can be used in different applications. We can write those routines to a different file, add a header that documents the properties and the routines provided and save this with the extension *.inc in the same folder like where the .asm is located. In that case we can add a directive in the source code that includes these source code routines, such as .include "filename.inc". If the file to be included is stored in a different, the filename must include the path information. Assembling then is continued in this include file and finally returns back to the main code source. Note that the included routines appear at the place where the .include directive has been called.

This is the perfect 4 bit busy LCD include file, here in source code format. Please note that files with the extension .inc in some cases are blocked by anti-virus software from downloading. In our case the .inc file is not dangerous.

;
; *************************************************
; * Include Code for a 4 bit busy access to a LCD *
; * with base routines                            *
; * (C)2017 by www.avr-asm-tutorial.net           *
; *************************************************
;
; -------- Used registers -------------------
; rmp, rmo, rLine, rRead, R0
;
; -------- Routinen ------------------------------
; Routine  Provides     Call parameters      Registers
; -------  ------------ -------------------- ---------
; LcdInit  Init LCD     Non                  rmp,rmo
;                                            rRead
; LcdChars Generate own ZH:ZL Character      rmp,rmo
;          characters   table                rRead,R0
; LcdText  Output the   ZH:ZL Text table     rmp,rmo
;          text in the  0D=Next line         rLine
;          flash table  FF=Ignore (Dummy)    rRead
;          in line 1    FE=End of table
;          aus        
; LcdTextC Output the   (see LcdText)        (see LcdText)
;          text in the
;          flash table
;          on the cur-
;          rent position
; LcdSRam  Output the   XH:XL: points to     rmp,rmo
;          text in SRAM position in SRAM     rRead
;                       ZH:ZL: Lcd position
;                       rmp: Number of
;                       characters
; LcdPos   Set output   ZH: Line 0..3        rmp,rmo
;          position     ZL: Column 0..19     rRead
; LcdLine  Set line,    rmp: Line 0..3       rmp,rmo
;          Column = 0                        rRead
; LcdLineN Set line     N: Line 1..4         rmp,rmo
;          Column = 0                        rRead
; 
; ---------Ports and portpins of the LCD ------------
; LCD control port
.equ pLcdCO   = PORTB  ; LCD control port output
.equ pLcdCR   = DDRB   ; LCD control port direction 
.equ bLcdCOE  = PORTB2 ; LCD enable pin output
.equ bLcdCRE  = DDB2   ; LCD enable pin direction
.equ bLcdCORS = PORTB0 ; LCD RS pin output
.equ bLcdCRRS = DDB0   ; LCD RS pin direction
.equ bLcdCORW = PORTB1 ; LCD R/W pin output
.equ bLcdCRRW = DDB1   ; LCD R/W pin direction
; LCD data port
.equ pLcdDO   = PORTA  ; LCD data port output
.equ pLcdDI   = PINA   ; LCD data port input
.equ pLcdDR   = DDRA   ; LCD data port direction
.equ mLcdDRW  = 0xF0   ; LCD data port write mask
.equ mLcdDRR  = 0x0F   ; LCD data port read mask
;
; --------- LCD access init ----------
LcdInit:
	; Wait for 50 ms until LCD is ready
	rcall Wait50ms ; Inactive delay 50 ms
	; Switch to 8 bit mode (three times)
	ldi rmp,0x30 ; 8 bit mode
	rcall LcdC8Byte ; Write byte in 8 bit mode
	rcall Wait5ms ; Warte 5 ms
	ldi rmp,0x30
	rcall LcdC8Byte
	rcall Wait5ms
	ldi rmp,0x30
	rcall LcdC8Byte
	rcall Wait5ms
	; Switch to 4 bit mode
	ldi rmp,0x20 ; Switch to 4 bit mode
	rcall LcdC8Byte ; Write byte in 8 bit mode
	rcall Wait5ms
	; Function set LCD
	ldi rmp,0x28 ; 4 bit mode, 4 lines, 5*7
	rcall LcdC4Byte ; Byte in 4 bit mode to LCD
	ldi rmp,0x0F ; Display on, blink
	rcall LcdC4Byte ; Byte in 4 bit mode to LCD
	ldi rmp,0x01 ; Clear display
	rcall LcdC4Byte ; Byte in 4 bit mode to LCD
	ldi rmp,0x06 ; Autoindent
	rjmp LcdC4Byte ; Byte in 4 bit mode to LCD
;
; OUtput text in flash to LCD
;   Z points to text in flash
LcdText:
	rcall LcdLine1 ; Set LCD to line 1
LcdTextC: ; Continue text output on current position
	clr rLine ; Clear line counter
LcdText1:
	lpm rmp,Z+ ; Read character from flash
	cpi rmp,0xFE ; End of text?
	breq LcdTextRet ; yes
	cpi rmp,0xFF ; Dummy character?
	breq LcdText1 ; yes, next character
	cpi rmp,0x0D ; Line change?
	brne LcdText2 ; No line change
	inc rLine ; Next line
	mov rmp,rLine ; Set line
	rcall LcdLine ; Change line
	rjmp LcdText1 ; Continue with caharacters
LcdText2: ; Output character
	rcall LcdD4Byte ; Output character
	rjmp LcdText1 ; and continue
LcdTextRet:
	ret ; Ready
;
; Output of text in SRAM to the LCD
;   ZH = Line 0..3; ZL = Column 0..19,
;   XH:XL = Address in SRAM, rmp: Number of chars
LcdSram:
	mov R0,rmp ; Copy number of characters
	rcall LcdPos ; Set LCD position to ZH:ZL
LcdSram1:
	ld rmp,X+ ; Read byte from SRAM
	rcall LcdD4Byte ; Output on LCD
	dec R0 ; Counter downwards
	brne LcdSram1 ; Continue with characters
	ret ; Done
;
; Set LCD output cursor to the line start
; of the selected line
;   Line: rmp 0 .. 3 
LcdLine:
	cpi rmp,1 ; Line 2?
	brcs LcdLine1 ; to line 1
	breq LcdLine2 ; to line 2
	cpi rmp,2 ; Line 3?
	breq LcdLine3 ; to line 3
	rjmp LcdLine4 ; to line 4
LcdLine1:
	ldi rmp,0x80 ; Line 1
	rjmp LcdC4Byte ; Output control byte
LcdLine2:
	ldi rmp,0xC0 ; Line 2
	rjmp LcdC4Byte ; Output control byte
LcdLine3:
	ldi rmp,0x80+20 ; Line 3
	rjmp LcdC4Byte ; Output control byte
LcdLine4:
	ldi rmp,0xC0+20 ; Line 4
	rjmp LcdC4Byte ; Output control byte
;
; Set the LCD output cursor to the position
; in ZH:ZL, ZH is line (0..3), ZL is column
LcdPos:
	ldi rmp,0x80 ; Set line 1
	cpi ZH,1 ; Line 2?
	brcs LcdPos1 ; Line = 1
	ldi rmp,0xC0 ; Set line 2
	breq LcdPos1 ; Line = 2
	ldi rmp,0x80+20 ; Set line 3
	cpi ZH,2 ; Line 3?
	breq LcdPos1 ; Line = 3
	ldi rmp,0xC0+20 ; Line = 4
LcdPos1:
	add rmp,ZL ; Add column
	rjmp LcdC4Byte ; Output control byte
;
; Define own character
LcdChars:
	lpm ; Read character address
	tst R0 ; Zero?
	breq LcdChars2 ; Done
	adiw ZL,2 ; Overread dummy byte
	ldi rLine,8
LcdChars1:
	mov rmp,R0 ; Address set
	rcall LcdC4Byte ; to LCD
	lpm rmp,Z+ ; Read data
	rcall LcdD4Byte ; Output to LCD
	inc R0 ; Increase adsress
	dec rLine ; Count down
	brne LcdChars1 ; Further bytes
	rjmp LcdChars ; Next character
LcdChars2:
	ret ; Done
;
; Data byte output in 4 bit mode
;   Data in rmp
LcdD4Byte:
	rcall LcdBusy ; Wait until busy = 0
	sbi pLcdCO,bLcdCORS ; Set RS bit
	rjmp Lcd4Byte ; Output byte in rmp
;
; Control byte output in 4 bit mode
;   Data in rmp
LcdC4Byte:
	rcall LcdBusy ; Wait until busy = 0
	cbi pLcdCO,bLcdCORS ; Clear RS bit
; Output byte in 4 bit mode with busy
Lcd4Byte:
	push rmp ; Save rmp on stack
	andi rmp,0xF0 ; Clear lower nibble
	in rmo,pLcdDO ; Read data input port
	andi rmo,0x0F ; Clear upper nibble
	or rmp,rmo ; Combine lower and upper nibble
	out pLcdDO,rmp ; to data port LCD
	nop ; Wait one clock cycle
	sbi pLcdCO,bLcdCOE ; Activate LCD enable
	nop ; Wait one clock cycle
	cbi pLcdCO,bLcdCOE ; Clear LCD enable
	pop rmp ; Restore rmp
	andi rmp,0x0F ; Clear upper nibble
	swap rmp ; Exchange nibble
	or rmp,rmo ; Combine upper and lower nibble
	out pLcdDO,rmp ; to data port LCD
	nop ; Wait for one clock cycle
	sbi pLcdCO,bLcdCOE ; Activate LCD enable
	nop ; Wait for one clock cycle
	cbi pLcdCO,bLcdCOE ; Clear LCD enable
	ret ; Done
;
; Wait until busy zero
LcdBusy:
	push rmp ; Save rmp
	in rmp,pLcdDR ; Read LCD data bus
	andi rmp,mLcdDRR ; Apply read mask
	out pLcdDR,rmp ; To direction port
	cbi pLcdCO,bLcdCORS ; Clear RS
	sbi pLcdCO,bLcdCORW ; Set R/W
LcdBusy1:
	sbi pLcdCO,bLcdCOE ; Set LCD enable
	nop ; Wait for one clock cycle
	in rRead,pLcdDI ; Read upper nibble
	cbi pLcdCO,bLcdCOE ; Clear LCD enable
	andi rRead,0xF0 ; Clear lower nibble
	sbi pLcdCO,bLcdCOE ; Set LCD enable
	nop ; Wait for one clock cycle
	in rmp,pLcdDI ; Read lower nibble
	cbi pLcdCO,bLcdCOE ; Clear LCD enable
	andi rmp,0xF0 ; Clear lower nibble
	swap rmp ; Exchange nibbles
	or rRead,rmp ; Combine upper and lower nibble
	sbrc rRead,7 ; Skip if busy=0
	rjmp LcdBusy1 ; Repeat until busy=0
	cbi pLcdCO,bLcdCORW ; Clear R/W
	in rmp,pLcdDR ; Read direction
	ori rmp,mLcdDRW ; Set write bit mask
	out pLcdDR,rmp ; to direction port
	pop rmp ; Restore rmp
	ret ; Done
;
; Control byte output in 8 bit mode
;   Data in rmp
LcdC8Byte:
	cbi pLcdCO,bLcdCORS ; Clear RS bit
	andi rmp,0xF0 ; Clear lower nibble
	in rmo,pLcdDO ; Read data input port
	andi rmo,0x0F ; Clear upper nibble
	or rmp,rmo ; Combine lower and upper nibble
	out pLcdDO,rmp ; to data port LCD
	nop ; Wait for one clock cycle
	sbi pLcdCO,bLcdCOE ; Activate LCD enable
	nop ; Wait for one clock cycle
	cbi pLcdCO,bLcdCOE ; Clear LCD enable
	ret ; Done
;
; ------ Wait routines ------------------
Wait50ms: ; Wait routine 50 ms
.equ c50ms = 50000
.equ n50ms = (c50ms-18)/4
;   rcall: + 3
	push ZH ; + 2
	push ZL ; + 2
	ldi ZH,HIGH(n50ms) ; + 1
	ldi ZL,LOW(n50ms) ; + 1
	rjmp LcdWait ; + 2, gesamt = 11
Wait5ms: ; Wait routine 5 ms
.equ c5ms = 5000
.equ n5ms = (c5ms-18)/4
	push ZH
	push ZL
	ldi ZH,HIGH(n5ms)
	ldi ZL,LOW(n5ms)
	rjmp LcdWait
; Wait routine Z cycles
LcdWait: ; Wait loop, Clock = 4*(n-1)+11 = 4*n + 7
	sbiw ZL,1 ; + 2
	brne LcdWait ; + 1 / 2
	pop ZL ; + 2
	pop ZH ; +2
	ret ; + 4, Total=4*n+18
;
; End of include
;

With this include file the following program will be much shorter.

The include source uses the instruction OR register,register to combine the current lower nibble that was read, and masked with the ANDI instruction, with the desired upper nibble. The OR sets all bits that are either set in the first or in the second register and stores the result in the first register.

11.4.2 The program

The program for solving the task is as follows, the source code is here. Remember that the include routine is required.

;
; *********************************************************
; * Read and write EEPROM of an ATtiny24 and a 4 line LCD *
; * (C)2017 by http://www.avr-asm-tutorial.net            *
; *********************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ------- Ports, port bits ---------------
; (All LCD-Ports are defined in the include routine)
;
; ------- Registers ----------------------
; Use: R0 locally by the character definition routine
; free: R1 .. R15
.def rmp = R16 ; Multi purpose register
.def rmo = R17 ; Additional multi purpose register
.def rLine = R18 ; Line counter LCD
.def rRead = R19 ; Read result from LCD data port
.def rEep = R20 ; Data byte for EEPROM counter
; free R21 .. R25
; Used: XH:XL R27:R26 pointer 
; free: YH:YL R29:R28
; Used: R31:R30, ZH:ZL, for counting and LCD
;
; ------- Constants --------------------
.equ Clock = 1000000 ; Default clock frequency
.equ EepAddress = 0 ; Read and write address EEPROM
;
; ------- SRAM --------------------------
.DSEG ; Assemble to data segment
.ORG 0x0060 ; To SRAM start address
Number: ; Convert byte to ASCII text
.BYTE 3 ; Requires three characters
;
; -------- EEPROM segment -----------
.ESEG ; Assemble to EEPROM segment
.ORG EepAddress
.db 0 ; Init the EEPROM counter
;
; -------- Program start, init ----------
.CSEG ; Code Segment
.ORG 0 ; Start at 0
	; Stack init for subroutine calls
	ldi rmp,LOW(RAMEND) ; Init stack
	out SPL,rmp ; To stack pointer
	; Init port outputs for LCD operation
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; Control port outputs
	clr rmp ; Clear outputs
	out pLcdCO,rmp ; To control port
	ldi rmp,mLcdDRW ; Data port output mask write
	out pLcdDR,rmp ; To direction port
	; Init LCD
	rcall LcdInit
	; Output text on LCD
	ldi ZH,HIGH(2*TextTable)
	ldi ZL,LOW(2*TextTable)
	rcall LcdText ; Output text at ZH:ZL
	; Read byte from EEPROM and output in line 3
	rcall EepRead
	inc rEep ; Increase EEPROM byte
	rcall EepWrite ; Write to EEPROM
	; Sleep enable
	ldi rmp,1<<SE ; Sleep mode idle
	out MCUCR,rmp ; to master control port
Loop:
	sleep ; go to sleep
	rjmp Loop
;
; Output text to LCD
TextTable:
.db "LCD display ATtiny24",0x0D,0xFF ; Line 1
.db "avr-asm-tutorial.net",0x0D,0xFF ; Line 2
.db "Previous count =    ",0x0D,0xFF ; Line 3
.db "New count      =    ",0xFE,0xFF ; Line 4
;
; Read EEPROM
EepRead:
  ; Wait until EEPROM is ready
	sbic EECR, EEPE ; Check EEPROM Program Enable bit
	rjmp EepRead ; not yet done
	; Set EEPROM read address
	ldi rmp,HIGH(EepAddress) ; MSB to address port
	out EEARH,rmp ; ATtiny24/44 has less than 257 byte EEPROM
	ldi rmp,LOW(EepAddress) ; LSB address EEPROM
	out EEARL,rmp ; to LSB address port
	; Set EERE bit in EEPROM control port
	sbi EECR,EERE ; Read enable
	; Read byte from data port
	in rEep,EEDR
	; Convert number read to decimal ASCII
	mov R0,rEep ; Copy number to R0
	ldi XH,HIGH(Number) ; SRAM buffer pointer for result, MSB
	ldi XL,LOW(Number) ; dto., LSB
	rcall Decimal ; Convert to decimal ASCII
	ldi XH,HIGH(Number) ; SRAM buffer pointer for output, MSB
	ldi XL,LOW(Number) ; dto., LSB
	ldi ZH,2 ; To  line 3
	ldi ZL,17 ; in column 17
	ldi rmp,3 ; Three characters
	rcall LcdSram ; Call include routine SRAM out
	ret
;
; Write to EEPROM
EepWrite:
	; Wait until EEPROM ready
	sbic EECR, EEPE ; Check Program Enable bit in control port
	rjmp EepWrite ; not yet ready
	; Set EEPROM write mode
	ldi rmp,(0<<EEPM1)|(0<<EEPM0) ; Erase and write
	out EECR,rmp ; To EEPROM control port
	; Address to Address port
	ldi rmp,HIGH(EepAddress) ; MSB
	out EEARH,rmp ; For ATtiny24/44 not necessary
	ldi rmp,LOW(EepAddress) ; LSB
	out EEARL,rmp ; to address port
	; Byte to be written to data port
	out EEDR,rEep ; To the data port
	; Set Memory Program enable EEMPE
	sbi EECR,EEMPE ; Enable write
	; Write to EEPROM with EEPE = One
	sbi EECR,EEPE ; Start write, duration 3.4 ms
	; Write new counter to LCD
	mov R0,rEep ; Copy number to R0
	ldi XH,HIGH(Number) ; SRAM buffer pointer for result, MSB
	ldi XL,LOW(Number) ; dto. LSB
	rcall Decimal ; Convert to decimal ASCII
	ldi XH,HIGH(Number) ; SRAM buffer pointer for output, MSB
	ldi XL,LOW(Number) ; dto., LSB
	ldi ZH,3 ; To line 4
	ldi ZL,17 ; To column 17
	ldi rmp,3 ; Three characters
	rcall LcdSram ; Call included routine
	ret
;
; Convert R0 to a decimal number in ASCII code
;   XH:XL = Position in SRAM
Decimal:
	set ; Set T-Flagge for leading zeroes
	ldi rmp,100 ; Decimal 100
	clr R1 ; R1 is result counter
Decimal100:
	cp R0,rmp ; Smaller than 100?
	brcs Decimal2 ; yes
	sub R0,rmp ; Subtract 100
	inc R1 ; Increase result counter
	rjmp Decimal100 ; Continue 100s
Decimal2: ; Blank leading zeroes
	tst R1 ; Result = 0?
	brne Decimal3 ; No
	ldi rmp,' ' ; Blank
	rjmp Decimal4 ; To SRAM buffer
Decimal3: ; No leading zero
	clt ; Clear flag
	ldi rmp,0x30 ; ASCII-0
	add rmp,R1 ; Add 100s result
Decimal4: ; Store result in SRAM
	st X+,rmp ; Write to SRAM
	; Check 10s
	ldi rmp,10 ; Decimal 10
	clr R1 ; Clear result
Decimal10: ; 10s loop
	cp R0,rmp ; Compare with 10
	brcs Decimal5 ; Already complete
	sub R0,rmp ; Subtract 10
	inc R1 ; next 10
	rjmp Decimal10 ; Continue 10s
Decimal5: ; 10s are complete
	brtc Decimal6 ; Leading zero flag is off
	tst R1 ; Leading zero?
	brne Decimal6 ; No
	ldi rmp,' ' ; Blank
	rjmp Decimal7 ; To buffer write
Decimal6:
	ldi rmp,'0' ; ASCII-0
	add rmp,R1 ; Add result
Decimal7:
	st X+,rmp ; Store 10s in SRAM
	; 1s
	ldi rmp,'0' ; ASCII-0
	add rmp,R0 ; Add rest
	st X+,rmp ; Store in SRAM
	ret
;
; Include Lcd4Busy routines
.include "Lcd4Busy.inc"
;
; End of source code
;

Do not forget to burn the EEPROM content in .eep after programming the flash code.

Result This is the result of the program (the German version). Looks perfect.


Home Top EEPROM Decimal conversion Hardware Byte counter Word counter


11.5 Word counter

11.5.1 Task

The previous program shall be changed, so that after reaching a maximum counter value the chip shall discontinue to work (planned obsolescence). To being able to define higher limits a 16 bit counter shall be used. On reaching the limit, line 4 of the LCD shall display a message.

11.5.2 The program for that task

This is the program, the source code is here. It requires the LCD routines.

;
; *********************************************************
; * To read and write an EEPROM word and a message on LCD *
; * (C)2017 by http://www.avr-asm-tutorial.net            *
; *********************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ------- Ports, port bits ---------------
; (All LCD ports are defined in the include routine)
;
; ------- Registers ----------------------
; Used: R0 to R3 locally by character definition and decimal conversion
; free: R4 .. R15
.def rmp = R16 ; Multi purpose register
.def rmo = R17 ; Additional multi purpose register
.def rLine = R18 ; Line counter LCD
.def rRead = R19 ; Read result from LCD data port
.def rEepH = R20 ; MSB data for EEPROM counter
.def rEepL = R21 ; dto., LSB
; free: R22 .. R25
; Used: XH:XL R27:R26 Pointer
; free: YH:YL R29:R28
; Used: R31:R30, ZH:ZL, fuer counting and LCD
;
; ------- Constants --------------------
.equ Clock = 1000000 ; Default clock frequency
.equ EepAddress = 0 ; Read and write address EEPROM
.equ Obsolescence = 3 ; Obsolescence criterion 1..65535
;
; ------- SRAM --------------------------
.DSEG ; Assemble to data segment
.ORG 0x0060 ; to SRAM start
Number: ; Conversion word to decimal ASCII
.BYTE 5 ; needs five characters
;
; -------- EEPROM init value -----------
.ESEG ; Assemble EEPROM segment
.ORG EepAddress
.db LOW(32767),HIGH(32767) ; Init EEPROM counter
;
; -------- Program start, init ----------
.CSEG ; Assemble to code Segment
.ORG 0 ; Start at 0
	; Stack init for subroutine calls
	ldi rmp,LOW(RAMEND) ; Init Stack
	out SPL,rmp ; to stack pointer
	; Init port outputs
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; to control port directions
	clr rmp ; Clear outputs
	out pLcdCO,rmp ; to control port output
	ldi rmp,mLcdDRW ; Data port output mask write
	out pLcdDR,rmp ; to direction port
	; Init LCD
	rcall LcdInit ; Call included init routine
	; Output text on LCD
	ldi ZH,HIGH(2*TextTable)
	ldi ZL,LOW(2*TextTable)
	rcall LcdText ; Output flash text at ZH:ZL
	; Read word from EEPROM und display in line 3
	rcall EepReadW
	inc rEepL ; Increase EEPROM LSB
	brne MsbOk ; No overflow
	inc rEepH ; Overflow to MSB
MsbOk:
	; Check obsolescence criterion
	rcall ChkObsolescence; Ende-Meldung ausgeben
	brcc Obsolet ; System is obsolete
	rcall EepWriteW ; Write to EEPROM and to line 4
Obsolet:
	; Sleep enable
	ldi rmp,1<<SE ; Sleep mode idle
	out MCUCR,rmp ; to master control port
Loop:
	sleep ; go to sleep
	rjmp Loop
;
; Output text for LCD
TextTable:
.db "LCD display ATtiny24",0x0D,0xFF ; Line 1
.db "avr-asm-tutorial.net",0x0D,0xFF ; Line 2
.db "Previous     =      ",0x0D,0xFF ; Line 3
.db "New count    =      ",0xFE,0xFF ; Line 4
;
; Obsolenz prufen
ChkObsolescence:
	cpi rEepL,LOW(Obsolescence) ; Compare with constant
	ldi rmp,HIGH(Obsolescence)
	cpc rEepH,rmp ; With overflow from LSB compare
	brcs ChkObsolescenceRet
	; End of service reached
	rcall LcdLine4 ; To line 4
	ldi ZH,HIGH(2*ObsoletText) ; Text message
	ldi ZL,LOW(2*ObsoletText)
	rcall LcdTextC ; Output text on LCD
	clc ; Clear carry flag
ChkObsolescenceRet:
	ret
; Text of the message
ObsoletText:
.db "* Maximum reached! *",0xFE,0xFE
;
; Read EEPROM word
EepReadW:
  ; Wait until EEPROM is ready
	sbic EECR, EEPE ; Check EEPROM Program Enable bit
	rjmp EepReadW ; not yet ready
	; EEPROM read address
	ldi rmp,HIGH(EepAddress) ; MSB to address port
	out EEARH,rmp ; ATtiny24/44 has less than 257 Byte EEPROM
	ldi rmp,LOW(EepAddress) ; LSB
	out EEARL,rmp ; to LSB address port
	; Set EERE bit in EEPROM control port
	sbi EECR,EERE
	; Read byte from data port
	in rEepL,EEDR ; read result to LSB
	cbi EECR,EERE ; Read bit off
	ldi rmp,LOW(EepAddress+1) ; Address next byte
	out EEARL,rmp ; to LSB address port
	sbi EECR,EERE ; Set Read Enable bit
	in rEepH,EEDR ; Read MSB from data port
	; Convert binary read to decimal ASCII
	mov R0,rEepL ; Copy counter to R1:R0
	mov R1,rEepH
	ldi XH,HIGH(Number) ; SRAM buffer for result, MSB
	ldi XL,LOW(Number) ; dto. LSB
	rcall DecimalW ; Convert word to decimal ASCII
	ldi XH,HIGH(Number) ; SRAM buffer pointer for output, MSB
	ldi XL,LOW(Number) ; dto., LSB
	ldi ZH,2 ; In lin 3
	ldi ZL,15 ; Column 16
	ldi rmp,5 ; Five characters
	rcall LcdSram ; Call SRAM output to LCD include routine
	ret
;
; Write to EEPROM
EepWriteW:
	; Wait until EEPROM ready
	sbic EECR,EEPE ; Check Program Enable bit in control port
	rjmp EepWriteW ; not yet ready
	; Set EEPROM write mode
	ldi rmp,(0<<EEPM1)|(0<<EEPM0) ; Erase and write
	out EECR,rmp ; to EEPROM control port
	; Address to address ports
	ldi rmp,HIGH(EepAddress) ; MSB address
	out EEARH,rmp ; ATtiny24/44 has less than 257 bytes
	ldi rmp,LOW(EepAddress) ; LSB address
	out EEARL,rmp
	; Byte to be written to data port, LSB
	out EEDR,rEepL ; into data port
	; Set Memory Program enable bit EEMPE
	sbi EECR,EEMPE ; Enable writing
	; Write EEPROM with EEPE = one
	sbi EECR,EEPE ; Start writing, duration 3.4 ms
EepWrite1:
	sbic EECR,EEPE ; Wait until ready written
	rjmp EepWrite1 ; not yet ready
	ldi rmp,LOW(EepAddress+1) ; Write MSB counter address
	out EEARL,rmp
	; MSB to write into data port
	out EEDR,rEepH ; Write MSB to data port
	; Set Memory Program enable bit EEMPE
	sbi EECR,EEMPE ; Enable write
	; Write to EEPROM with EEPE set
	sbi EECR,EEPE ; Start write
	; Display new content on LCD
	mov R0,rEepL ; Copy count to R1:R0
	mov R1,rEepH
	ldi XH,HIGH(Number) ; SRAM buffer pointer result, MSB
	ldi XL,LOW(Number) ; dto. LSB
	rcall DecimalW ; Convert to decimal ASCII
	ldi XH,HIGH(Number) ; SRAM buffer pointer for output, MSB
	ldi XL,LOW(Number) ; dto., LSB
	ldi ZH,3 ; To line 4
	ldi ZL,15 ; In column 17
	ldi rmp,5 ; Five characters
	rcall LcdSram ; Call include routine output SRAM to LCD
	ret
;
; Convert R1:R0 to decimal ASCII
;   XH:XL = Position in SRAM
;   uses R3:R2, ZH:ZL
DecimalW:
	set ; Set T flag for leading zero suppression
	ldi ZH,HIGH(2*DecTab) ; Z is pointer to decimal table
	ldi ZL,LOW(2*DecTab)
DecimalW1:
	lpm R2,Z+ ; Read LSB from decimal table
	tst R2 ; Is LSB zero?
	breq DecimalWOnes ; yes, finished
	lpm R3,Z+ ; Read MSB from decimal table
	clr rmp ; rmp is counter
DecimalW2:
	sub R0,R2 ; Subtract LSB
	sbc R1,R3 ; Subtract MSB and carry from LSB
	brcs DecimalW3 ; Too far
	inc rmp ; Increase digit counter
	rjmp DecimalW2 ; Go on subtracting
DecimalW3:
	add R0,R2 ; Get back one subtraction
	adc R1,R3
	brtc DecimalW6 ; No suppression of leading zeroes
	tst rmp ; Another leading zero?
	brne DecimalW5 ; no
	ldi rmp,' ' ; Output a blank
	rjmp DecimalW7 ; Direct output
DecimalW5:
	clt ; Not zero, clear T flag
DecimalW6:
	subi rmp,-'0' ; Add ASCII-0
DecimalW7:
	st X+,rmp ; Store in SRAM buffer
	rjmp DecimalW1 ; Next decimal digit
DecimalWOnes:
	ldi rmp,'0' ; ASCII-0
	add rmp,R0 ; Add ones
	st X+,rmp ; Store in SRAM
	ret
;
; Decimal table
DecTab:
.dw 10000 ; Ten thousands
.dw 1000 ; Thousends
.dw 100 ; Hundreds
.dw 10  ; Tens
.dw 0   ; Table end
;
; Include Lcd4Busy routines
.include "Lcd4Busy.inc"
;
; End of source code
;

New is the instruction CPC register, register. It compares the first register with the second one, as increased by the carry flag. Flags are resulting similiarly as with the instruction SBC.

Home Top EEPROM Decimal conversion Hardware Byte counter Word counter


©2017 by http://www.avr-asm-tutorial.net