Home ==> Mikrobeginner ==> 10. LCD-Anzeige
ATtiny24

Lecture 10: A Lcd display on an ATtiny24


To operate an LCD on a controller we need some more pins than an ATtiny13 has. We therefore switch to the next larger pin package and to an ATtiny24. Two methods can be used to communicate with an LCD: one-way with fixed delay loops and two-way with reading and processing the busy flag.

10.0 Overview

  1. Introduction to LCD displays
  2. Introduction to ATtiny24
  3. Hardware, components and mounting
  4. Access using delays loops
  5. Access using the busy flag
  6. Display of own characters

10.1 Introduction to LCD displays

10.1.1 General on LCD displays

LCD above There is a large number of colors and types of LCDs available. Green, Yellow, gray and blue. Every taste can be met. There are text and graphics displays. This lecture works with text displays because they have a simple interface and programming mode and they cover nearly all applications.

Text displays work with a pixel matrix of 5 by 8, which is optimal for character recognition even from some distance.

The advantage of LCD displays over e.g. Seven-Segment-Displays is their low current consumption of 1 to 2 mA. For battery operation this is optimal. To have the same contrast 7-segment-displays use at least the 20-fold of current. Additionally, displaying text characters with 7-segment is horrible. And furthermore: to display 8, 16 or 24 characters on a 7-segment basis is a cabling and hardware grave. An LCD has a simple hardware interface, no matter how many characters have to be displayed.

LCD below Available are a number of displays covering a different number of characters and lines. Single line displays with eight characters are ideal for small measuring devices, more characters would be adverse. Two line LCDs with 8, 16 or 20 characters per line can display a large amount of information. If you need more, you select a four line LCD with 20 or 24 characters. Like we do in this course.

10.1.2 Interfaces of LCD displays

The pins of a LCD are more or less the same for all displays, nearly all use the same row of pins. A few others do not care about this norm, e.g. 18-pin devices.

LCD pins
Those pins mean the following:
PinNameDescription
1VSSOperating voltage, minus
2VDDOperating voltage, plus, 3 or 5 Volt
3VEEContrast regulation voltage
4RSRegister Select, 0=Control, 1=Data
5R/WRead/Write, 0=Write to LCD, 1=Read from LCD
6EEnable, 0=inactive, 1=active/Read/Write
7D0Data bit 0
8D1Data bit 1
9D2Data bit 2
10D3Data bit 3
11D4Data bit 4
12D5Data bit 5
13D6Data bit 6
14D7Data bit 7
15ABacklight Anode
16KBacklight Cathode

The connector with two rows is usable for flat cable. The data sheets for the display holds definitive information on the pin configuration.

The regulation voltage for contrast is adjusted with a trim potentiometer that divides the operating voltage. The potentiometer is adjusted to have the optimal display contrast. If this pin is tied to zero Volt or to the operating voltage, no characters can be seen on the display.

In most of the displays the character positions can be identified if the operating voltage is applied without any actions of the controller. In case of multiple lines single lines will not show character positions. With that one can see if the operating voltage is correctly attached, that the contrast regulation works fine and that the display is alive. If not, and if certain chips on the display get hot or even explode: your operating voltage has been applied reversely. Then transfer the display to your local electronics recycler.

8 bit interface

When the operating voltage is applied LCDs are going to the default of communication in 8 bit mode. Every read and write transfers eight data bits. All data bit inputs are by default on high level.

4 bit interface

With a special command LCDs can change to the 4 bit communication mode. In this mode only the four upper data bits D4 to D7 are used. Each write to the display now has to be made in two portions, each read includes two read cycles. First the upper four bits, then the lower four bits are read or written.

10.1.3 Controlling LCD devices

Init phase

The following procedure initializes a LCD, it should be executed after the operating voltage is stable. In all cases the RS pin has to be held low. Transfer of the data occurs by activating the E pin for at least 1 µs duration.

"x" means that this bit is ignored and that it can high or low. With that the initiation of the LCD is complete and we can output characters.

Data output

To send data to the LCD the RS input of the LCD must be set to high level and the R/W input to low level. The subsequently written characters occur one after the other on the LCD if Auto-Indent is selected (I=1). To change the address of the display the following numbers have to be written to the display with RS=0 and R/W=0 (if multiple lines N=1 are selected and the display has N characters per line):

10.1.4 Backlight of LCDs

LCD can be better read (especially in the dark) and are nicer if they have a backlight. To switch this on, the cathode pin is tied to the negative operating voltage and current is fed to the anode input. The current is specified in the data sheet for the LCD. For the 4 line LCD used here 70 mA are specified.

The following pictures demonstrate that above 5 mA no visible effect occurs.
2.1 mA
I = 2.1 mA, U = 2.9 V
7.4 mA
I = 7.4 mA, U = 3.4 V
12.0 mA
I = 12.0 mA, U = 3.8 V
18.3 mA
I = 18.3 mA, U = 4.3 V


The effect of the current for the battery life is higher than the visibility and the optical effect.

Home Top LCD ATtiny24 Hardware Wait mode Busy mode Characters


10.2 Introduction to the ATtiny24

ATtiny24 The ATtiny24 is a 14 pin controller type. As the multiple pin functions show, he not only has more pins but also more internal hardware than an ATtiny13. Port A is available with all eight bits, additionally three bits of Port B are accessible. If ISP is unnecessary the RESET input can be used for other purposes.

Eight pins can be used as ADC0 to ADC7 inputs.

Behind OC0A and OC0B an 8 bit timer is working, behind OC1A and OC1B a 16 bit timer. All four pins can be used as clock or PWM outputs.

An external xtal can be attached to pins 2 and 3 and enabled by setting fuses.

All pins can be monitored for level changes and can trigger two PCINTs (0 .. 7, 8 .. 10/11).

Of course, via the pins USCK, MISO, MOSI and RESET ISP programming can be performed.

The ATtiny24 has an internal 8 Mcs/s RC oscillator. With the internal clock divider this is divided by eight if the DIV8 fuse is set (default), so that a clock of 1 Mcs/s results. By clearing the DIV8 fuse it can be set to higher clock speeds, by writing the CLKPR port lower speeds can be selected, as already shown for the ATtiny13.

If you run out of pins with an ATtiny13, can change to this type. The multiple functions per pin demonstrate how optimal controller selection should work: one has to have a clear idea about the hardware components that are necessary, what has to be available as an external pin, which portpins have to be accessible in a row (in our case the four data bits of the LCD). Conflicting multiple functions Of pins are then to be avoided. This leads us to optimal type selection. The C programmer knows only one criterion: has the controller enough flash memory to host all his libraries?

If the overview on the necessary hardware is lacking, one tends to overdimension the controller type. If you decide to use a smaller type, and if you need an additional OC0A or OC1B output pin then, not only a few changes in the interrupt vector table will result. It could well be that the whole hard- and software design has to be changed. That is a main issue when planning such a project: more consequent planning avoids later changes. If you start with an Arduino, your elefant in a porcellan shop prevents you from such hazzle of learning optimal design.

The theme multiple pin functions has several aspects. As we want to use ISP, the pins USCK, MOSU and MISO have to be available for that. As the LCD's E input allows to free the four data bits for other uses (just by holding E low and R/W on write), no conflict results when using those pins for ISP as well as as LCD data pins. The use of the USCK pin for measuring analogue voltages of for driving a motor would rise several questions.


Home Top LCD ATtiny24 Hardware Wait mode Busy mode Characters


10.3 Hardware, components and mounting

10.3.1 Scheme

Scheme LCD The scheme shows how the LCD is controlled by the ATtiny24:

10.3.2 Components

LCD display

Pin header on LCD First we solder a 16 pin pin header to the LCD so that this fits into the breadboard. If you use a different LCD header you'll have to develop a solution for that.

ATtiny24

ATtiny24 This is the 14 pin ATtiny24.

Socket 14 pin

Socket The controller fits into that 14 pin socket.

Trimmer

Trimmer 10k This trim potentiometer is for adjusting the contrast of the LCD. The two pins in the foreground of the right picture are the endpoints of the resistor layer, the middle pin in the background is the slider.

10.3.3 Mounting

Mounting This is how the Tiny24 and the LCD is mounted.

A hint to such breadboards: the contacts of the board are subject to ageing. Especially if you leave such mighty pin headers in the holes over a longer period of time. If you use a very simple breadboard without a metal surface below those contacts are driven out, contact stability is very small then. Short pins, as that of an IC socket, do not fit well then. If that concerns the operating voltage, it can ruin a controller. If you are suspicious that ageing might be a concern, let your measuring deviice test contact. And finally order your next breadboard.


Home Top LCD ATtiny24 Hardware Wait mode Busy mode Characters


10.4 Controlling a LCD with delay loops

10.4.1 Timing requirements and construction of delay loops

The procedure is to write a command or a character and then to delay further execution in a loop.

The ATtiny24 runs with an internal clock of 1 Mcs/s by default, each clock cycle requires one µs.

The following delay times are necessary to control an LCD: A 16 bit wait loop fits all those needs. The code formulation:

	ldi ZH,HIGH(n)
	ldi ZL,LOW(n)
LcdWtLoop:
	sbiw ZL,1
	brne LcdWtLoop

This delays execution by Therefore the formula is nClock = 2 + 4 * (n-1) + 3 = 4*n + 1 or n = (nClock - 1) / 4. There is some additional overhead when you use the delay loop in practice: The formulas then are
nClock = 4*n + 16 or nClock = 4*(n+4)

and
n = (nClock-4)/4.

If the delay time is 50,000 µs and the clock frequency is 1 Mcs/s, the number of clock cycles nClock = 50,000 and n is = 12,499. For 40 µs n is 9. So the 16 bit loop covers all LCD delay times.

The whole routine then is:

	; rcall wait50ms ; + 3 clocks
.set n = 12499 ; N for 50 ms delay
LcdWt50ms:
	push ZH ; + 2 clocks
	push ZL ; + 2 clocks
	ldi ZH,HIGH(n) ; + 1 clock
	ldi ZL,LOW(n) ; + 1 clock
	rjmp LcdWtN ; + 2 clocks
; In total: 11 clock cycles
; And the wait routine:
LcdWtN:
	sbiw ZL,1 ; + 2 clocks
	brne warte ; +1/2 clock(s)
	pop ZL ; + 2 clocks
	pop ZH ; + 2 clocks
	ret ; + 4 clocks
; In total: (n-1)*4 + 3 + 8 = 4*n + 7
; Complete: nClock = 4*n + 18 cycles

The complete formula to calculate n for 50 ms at 1 Mcs/s is
n = (50000-18+2)/4 = 12496

The adder "+2" is for rounding the division result.

The directive ".set" defines a variable, which can be changed later on (e.g. .set N = 1246 for 5 ms and .set N = 6 for 40 µs). The directive .equ does not allow to change a constant, .set does.

10.4.2 Task

On the LCD a message with four lines (alternative: one, two or three lines) with 20 (alternative: eight, sixteen, twentifour) characters chall be displayed. Execution times shall be controlled with delay loops.

10.4.3 Program

This is the program, this is the source code for download. It is a linear program because initiation and writing to the LCD does not require interrupts. In fact, the necessary delay loops would not fit to a interrupt service routine. That is why all LCD read and write operations to/from the LCD has to be done in the main program loop and outside of interrupt service routines.

;
; ****************************************************
; * LCD-Display 4*20 on an ATtiny24, 4 bit interface *
; * (C)2017 by www.avr-asm-tutorial.net              *
; ****************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ------------- Hardware ----------------
;
;                ________
;               /        |
;     + 5 V o--|VCC   GND|--o 0 V
;              |         |
;    LCD-RS o--|PB0      |--o
;              |         |
;           o--|         |--o
;              |         |
;     RESET o--|RES      |--o
;              |         |
;     LCD-E o--|PB2      |--o
;              |         |
;    LCD-D7 o--|PA7   PA4|--o LCD-D4
;              |         |
;    LCD-D6 o--|PA6   PA5|--o LCD-D5
;              |_________|
;
;
; ----------- Ports, Port pins -----------
; 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
; 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 mLcdDR   = 0xF0   ; LCD data port direction mask
;
; ------- Registers ----------------------
; free: R0 .. R15
.def rmp = R16 ; Multi purpose register
.def rmo = R17 ; Second multi purpose register
.def rLine = R18 ; Line counter LCD
; free: R19 .. R29
; used: R31:R30, ZH:ZL, for counting and pointing
;
; ------- Constants --------------------
.equ Clock = 1000000 ; Default clock frequency
;
; -------- Program start, init ----------
.CSEG ; Code Segment
.ORG 0 ; Start at 0
	; Init stack for subroutines
	ldi rmp,LOW(RAMEND) ; Init stack
	out SPL,rmp ; to stack pointer
	; Init port outputs
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS) ; LCD
	out pLcdCR,rmp ; control port outputs
	clr rmp ; Outputs off
	out pLcdCO,rmp ; to control port
	ldi rmp,mLcdDR ; Data port output mask
	out pLcdDR,rmp ; to output
	; Init LCD
	rcall LcdInit
	; Output text on LCD
	ldi ZH,HIGH(2*Texttable)
	ldi ZL,LOW(2*Texttable)
	rcall LcdText ; Text in flash from ZH:ZL
	; Sleep enable
	ldi rmp,1<<SE
	out MCUCR,rmp
Loop:
	sleep ; to sleep
	rjmp Loop
;
; Output text on LCD
Texttable:
.db "LCD display ATtiny24",0x0D,0xFF ; Line 1
.db "avr-asm-tutorial.net",0x0D,0xFF ; Line 2
.db " Test program 4 bit",0x0D ; Line 3
.db "  with wait loops",0x00 ; Line 4
;
; --------- LCD control Init ----------
LcdInit:
	; Wait 50 ms until LCD has initiated
	rcall Wait50ms ; Delay by 50 ms
	; Set to 8 bit mode (three times)
	ldi rmp,0x30 ; 8 bit mode
	rcall LcdC8Byte ; Write in 8 bit mode
	rcall Wait5ms ; Wait 5 ms
	ldi rmp,0x30 ; Second repeat
	rcall LcdC8Byte
	rcall Wait5ms
	ldi rmp,0x30 ; Third repeat
	rcall LcdC8Byte
	rcall Wait5ms
	; Switch to 4 bit mode
	ldi rmp,0x20 ; 4 bit mode
	rcall LcdC8Byte
	rcall Wait5ms
	; Function set LCD
	ldi rmp,0x28 ; 4 bit mode, 4 lines, 5*7
	rcall LcdC4Byte
	rcall Wait5ms
	ldi rmp,0x0F ; Display on, blink
	rcall LcdC4Byte
	rcall Wait5ms
	ldi rmp,0x01 ; Clear display
	rcall LcdC4Byte
	rcall Wait5ms
	ldi rmp,0x06 ; Autoindent
	rcall LcdC4Byte
	rjmp Wait40us
;
; Output of text on the LCD
;   Z points to text in flash memory
LcdText:
	clr rLine ; Line counter
LcdText1:
	lpm rmp,Z+ ; Read character from flash
	cpi rmp,0 ; End of Output?
	breq LcdTextRet ; no
	cpi rmp,0xFF ; Dummy character?
	breq LcdText1 ; yes, next character
	cpi rmp,0x0D ; Line change?
	brne LcdText2 ; No line change
	inc rLine ; Next Zeile
	mov rmp,rLine ; Select line
	rcall LcdLineSet ; Set output line
	rjmp LcdText1 ; Continue with characters
LcdText2: ; Write character
	rcall LcdD4Byte ; Character out
	rcall Wait40us ; Wait
	rjmp LcdText1 ; Go on
LcdTextRet:
	ret ; Ready
;
; Sets the output cursor to the line start
; of the selected line
;   Line: rmp 0 to 3 
LcdLineSet:
	cpi rmp,1 ; Line 2?
	brcs LcdLineSet1 ; no, to line 1
	breq LcdLineSet2 ; yes, to line 2
	cpi rmp,2 ; Line 3?
	breq LcdLineSet3 ; yes, to line 3
	rjmp LcdLineSet4 ; no, to line 4
LcdLineSet1:
	ldi rmp,0x80 ; Line 1
	rjmp LcdLineSetRmp
LcdLineSet2:
	ldi rmp,0xC0 ; Line 2
	rjmp LcdLineSetRmp
LcdLineSet3:
	ldi rmp,0x80+20 ; Line 3
	rjmp LcdLineSetRmp
LcdLineSet4:
	ldi rmp,0xC0+20 ; Line 4
	rjmp LcdLineSetRmp
LcdLineSetRmp:
	rcall LcdC4Byte ; Output to LCD control port
	rjmp Wait40us
;
; Data byte write in 4 bit mode
;   Data byte in rmp
LcdD4Byte:
	sbi pLcdCO,bLcdCORS ; Set RS bit
	rjmp Lcd4Byte ; Write byte in rmp
;
; Control write in 4 bit mode
;   Data in rmp
LcdC4Byte:
	cbi pLcdCO,bLcdCORS ; Clear RS bit
; Write byte in 4 bit mode
Lcd4Byte:
	push rmp ; rmp to stack
	andi rmp,0xF0 ; Clear lower nibble
	in rmo,pLcdDI ; Read LCD data port
	andi rmo,0x0F ; Clear upper nibble
	or rmp,rmo ; OR upper and lower nibble
	out pLcdDO,rmp ; Write 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
	pop rmp ; rmp back from stack
	andi rmp,0x0F ; Clear upper nibble
	swap rmp ; Exchange upper/lower nibble
	or rmp,rmo ; OR upper and lower nibble
	out pLcdDO,rmp ; to LCD data
	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 ; Complete
;
; Control byte output in 8 bit mode
;   Data byte in rmp
LcdC8Byte:
	cbi pLcdCO,bLcdCORS ; Clear LCD-RS
	andi rmp,0xF0 ; Clear lower nibble
	in rmo,pLcdDI ; Read data bus port
	andi rmo,0x0F ; Clear upper nibble
	or rmp,rmo ; OR upper and lower nibble
	out pLcdDO,rmp ; Write to LCD data port
	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 for 50 ms
.equ c50ms = 50000
.equ n50ms = (c50ms-18+2)/4
;   rcall: + 3
	push ZH ; + 2
	push ZL ; + 2
	ldi ZH,HIGH(n50ms) ; + 1
	ldi ZL,LOW(n50ms) ; + 1
	rjmp LcdWait ; + 2, total = 11
Wait5ms: ; Wait for 5 ms
.equ c5ms = 5000
.equ n5ms = (c5ms-18+2)/4
	push ZH
	push ZL
	ldi ZH,HIGH(n5ms)
	ldi ZL,LOW(n5ms)
	rjmp LcdWait
Wait100us: ; Wait for 100us
.equ c100us = 100
.equ n100us = (c100us-18+2)/4
	push ZH
	push ZL
	ldi ZH,HIGH(n100us)
	ldi ZL,LOW(n100us)
	rjmp LcdWait
Wait40us: ; Wait for 40us
.equ c40us = 40
.equ n40us = (c40us-18+2)/4
	push ZH
	push ZL
	ldi ZH,HIGH(n40us)
	ldi ZL,LOW(n40us)
	rjmp LcdWait
; Wait routine Z delay loops
LcdWait: ; Wait loop, Clock cycles = 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
;

This program utilizes one new instruction. SWAP register exchanges the lower and upper four bits (nibbles) of the register. This is much faster than shifting the register four times to the left or to the right.

Home Top LCD ATtiny24 Hardware Wait mode Busy mode Characters


10.5 Control of the LCD in busy mode

10.5.1 Reading the busy flag of the LCD

Scheme with R/W For reading the busy flag the LCD data bus has to be put to Write mode by setting the LCD-R/W input to one. Of course the ATtiny's data bus bits must change direction first. The LCD-R/W input requires an additional port pin of the ATtiny24, for which PB1 has to be connected.

10.5.2 Task

The task is the same as before (text output on the LCD), instead of wait cycles the busy flag of the LCD shall be utilized.

10.5.3 Program

This is the program, the source code is here.

;
; ***********************************************************
; * LCD display 4*20 on ATtiny24, 4 bit interface with busy *
; * (C)2017 by http://www.avr-asm-tutorial.net              *
; ***********************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ------------- Hardware ----------------
;
;                ________
;               /        |
;     + 5 V o--|VCC   GND|--o 0 V
;              |         |
;    LCD-RS o--|PB0      |--o
;              |         |
;   LCD-R/W o--|PB1      |--o
;              |         |
;     RESET o--|RES      |--o
;              |         |
;     LCD-E o--|PB2      |--o
;              |         |
;    LCD-D7 o--|PA7   PA4|--o LCD-D4
;              |         |
;    LCD-D6 o--|PA6   PA5|--o LCD-D5
;              |_________|
;
;
; ----------- Ports, port pins -----------
; 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 mask write
.equ mLcdDRR  = 0x00   ; LCD data port mask read
;
; ------- Registers ----------------------
; free: R0 .. R15
.def rmp = R16 ; Multi purpose register
.def rmo = R17 ; Additional multi purpose register
.def rLine = R18 ; Line counter LCD
.def rLese = R19 ; Read result from LCD data port
; free: R20 .. R29
; used: R31:R30, ZH:ZL, for counting and as pointer 
;
; ------- Constants --------------------
.equ Clock = 1000000 ; Default clock frequency
;
; ---- Program start, Initiation -------
.CSEG ; Code Segment
.ORG 0 ; Start at 0
	; Init stack for subroutines
	ldi rmp,LOW(RAMEND) ; Init stack
	out SPL,rmp ; to stack pointer
	; Init ports and port pins
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; to LCD control port direction
	clr rmp ; Clear outputs
	out pLcdCO,rmp ; to LCD control port
	ldi rmp,mLcdDRW ; Data port output mask write
	out pLcdDR,rmp ; to data port direction port
	; Init LCD
	rcall LcdInit
	; Text output on LCD
	ldi ZH,HIGH(2*Texttable)
	ldi ZL,LOW(2*Texttable)
	rcall LcdText ; Text from ZH:ZL in flash
	; Sleep enable
	ldi rmp,1<<SE
	out MCUCR,rmp
Loop:
	sleep ; sleep
	rjmp Loop
;
; Text for LCD
Texttable:
.db "LCD display ATtiny24",0x0D,0xFF ; Line 1
.db "avr-asm-tutorial.net",0x0D,0xFF ; Line 2
.db " Testprogram 4 bit",0x0D,0xFF ; Line 3
.db "with busy flag read",0x00 ; Line 4
;
; --------- LCD control init ----------
LcdInit:
	; Wait 50 ms until LCD is available
	rcall Wait50ms ; Wait time 50 ms
	; Set LCD to 8 bit mode
	ldi rmp,0x30 ; 8 bit mode
	rcall LcdC8Byte ; Write to LCD in 8 bit mode
	rcall Wait5ms ; Wait 5 ms
	ldi rmp,0x30 ; Second repeat
	rcall LcdC8Byte
	rcall Wait5ms
	ldi rmp,0x30 ; Third repeat
	rcall LcdC8Byte
	rcall Wait5ms
	; Switch to 4 bit mode
	ldi rmp,0x20 ; To 4 bit mode
	rcall LcdC8Byte
	rcall Wait5ms
	; Function set LCD
	ldi rmp,0x28 ; 4 bit mode, 4 lines, 5*7
	rcall LcdC4Byte ; Byte to LCD control
	ldi rmp,0x0F ; Display on, blink
	rcall LcdC4Byte ; Byte to LCD control
	ldi rmp,0x01 ; Clear Display
	rcall LcdC4Byte ; Byte to LCD control
	ldi rmp,0x06 ; Autoindent
	rjmp LcdC4Byte ; Byte to LCD control
;
; Output of text on the LCD
;   Z points to text in flash
LcdText:
	clr rLine ; Line counter = 0
LcdText1:
	lpm rmp,Z+ ; read character from flash
	cpi rmp,0 ; End of text?
	breq LcdTextRet ; no
	cpi rmp,0xFF ; Dummy character?
	breq LcdText1 ; yes, continue
	cpi rmp,0x0D ; Line change?
	brne LcdText2 ; No line change
	inc rLine ; Next line
	mov rmp,rLine ; Select line
	rcall LcdLineSet ; Set line
	rjmp LcdText1 ; continue with characters
LcdText2: ; Write character
	rcall LcdD4Byte ; Write character to LCD
	rjmp LcdText1 ; Continue
LcdTextRet:
	ret ; Done
;
; Sets the output address to the line start of the
; selected line
;   Line: rmp 0 to 3 
LcdLineSet:
	cpi rmp,1 ; Line 2?
	brcs LcdLineSet1 ; no, to line 1
	breq LcdLineSet2 ; yes, to line 2
	cpi rmp,2 ; Line 3?
	breq LcdLineSet3 ; yes, to line 3
	rjmp LcdLineSet4 ; no, to line 4
LcdLineSet1:
	ldi rmp,0x80 ; Line 1
	rjmp LcdLineSetRmp
LcdLineSet2:
	ldi rmp,0xC0 ; Line 2
	rjmp LcdLineSetRmp
LcdLineSet3:
	ldi rmp,0x80+20 ; Line 3
	rjmp LcdLineSetRmp
LcdLineSet4:
	ldi rmp,0xC0+20 ; Line 4
	rjmp LcdLineSetRmp
LcdLineSetRmp:
	rjmp LcdC4Byte ; Control byte to LCD
;
; Data output to LCD in 4 bit mode
;   Data in rmp
LcdD4Byte:
	rcall LcdBusy ; Wait until busy = low
	sbi pLcdCO,bLcdCORS ; Set LCD-RS
	rjmp Lcd4Byte ; Write byte to LCD
;
; Control output to LCD in 4 bit mode
;   Data in rmp
LcdC4Byte:
	rcall LcdBusy ; Wait bis busy = Null
	cbi pLcdCO,bLcdCORS ; Clear LCD-RS
; Output byte in 4 bit mode with busy
Lcd4Byte:
	push rmp ; Save rmp on stack
	andi rmp,0xF0 ; Clear lower nibble
	in rmo,pLcdDI ; Read data bus port
	andi rmo,0x0F ; Clear upper nibble
	or rmp,rmo ; Combine lower and upper nibble
	out pLcdDO,rmp ; to data bus of the LCD
	nop ; Wait one cycle
	sbi pLcdCO,bLcdCOE ; Activate LCD-Enable
	nop ; Wait one cycle
	cbi pLcdCO,bLcdCOE ; Clear LCD-Enable
	pop rmp ; Restore rmp from stack
	andi rmp,0x0F ; Clear upper nibble
	swap rmp ; Exchange nibbles
	or rmp,rmo ; Combine lower and upper nibble
	out pLcdDO,rmp ; Write to data bus LCD
	nop ; Wait one cycle
	sbi pLcdCO,bLcdCOE ; Activate LCD-Enable
	nop ; Wait one cycle
	cbi pLcdCO,bLcdCOE ; Clear LCD-Enable
	ret ; Done
;
; Wait until busy clear
LcdBusy:
	push rmp ; Save rmp
	ldi rmp,mLcdDRR ; Read mask
	out pLcdDR,rmp ; to direction port
	cbi pLcdCO,bLcdCORS ; Clear LCD-RS
	sbi pLcdCO,bLcdCORW ; Set LCD-R/W
LcdBusy1:
	sbi pLcdCO,bLcdCOE ; Set LCD-Enable
	nop ; Wait one cycle
	in rLese,pLcdDI ; Read upper nibble
	cbi pLcdCO,bLcdCOE ; Clear LCD-Enable
	andi rLese,0xF0 ; Clear lower nibble
	sbi pLcdCO,bLcdCOE ; Set LCD-Enable
	nop ; Wait one cycle
	in rmp,pLcdDI ; Read lower nibble
	cbi pLcdCO,bLcdCOE ; Clear LCD-Enable
	andi rmp,0xF0 ; Clear lower nibble
	swap rmp ; Exchange upper/lower nibble
	or rLese,rmp ; Combine upper and lower nibble
	sbrc rLese,7 ; Skip if Busy=0
	rjmp LcdBusy1 ; Repeat until busy=0
	cbi pLcdCO,bLcdCORW ; Clear LCD-R/W
	ldi rmp,mLcdDRW ; Load write mask
	out pLcdDR,rmp ; to direction port
	pop rmp ; Restore rmp
	ret ; Return
;
; LCD control output in 8 bit mode
;   Data in rmp
LcdC8Byte:
	cbi pLcdCO,bLcdCORS ; Clear LCD-RS
	andi rmp,0xF0 ; Clear lower nibble
	in rmo,pLcdDI ; Read data port
	andi rmo,0x0F ; Clear upper nibble
	or rmp,rmo ; Combine lower and upper nibble
	out pLcdDO,rmp ; Write to LCD data port
	nop ; Wait one cycle
	sbi pLcdCO,bLcdCOE ; Activate LCD-Enable
	nop ; Wait one 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, total = 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 loops
LcdWait: ; Wait loop, nClocks = 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
;

No new instructions are used here.

Home Top LCD ATtiny24 Hardware Wait mode Busy mode Characters


10.6 To design new characters

10.6.1 The character generator in LCDs

All LCDs offer the opportunity to define own characters, but only those between 0 and 7. Those are designed by default (remember: memories cannot be empty) but their content can be overwritten.

Overwriting works like this:
  1. A byte of the type 0b01NNNZZZ is send to the control port of the LCD (with LCD-RS = 0). Therein NNN is the number of the character to be overwritten (0 bis 7). ZZZ is the line of the character, o is the most upper line, 7 the most lower line.
  2. Following that a byte of the type 0b000BBBBB is send to the data port of the LCD, with LCD-RS = 1. The Bs stand for the pixels of that line, with the most significant B as the leftmost pixel.
  3. For all eight lines of the characters one pair of address and data are to be written.

10.6.2 Software for character design

Charactergenerator To ease the design of such characters one can use a spread sheet.

Comfortable character design goes like this: open either the OpenOffice spreadsheet or the M$ Office Excel spreadsheet. To display a pixel in white, write a one to the cell, in background color write a zero there. The address and data in decimal format are generated from this on the right side. To better remind yourself what character what is fill in the description below those characters.

Assembler table

From that the spreadsheet generator constructs an assembler table. It starts with the address of the character, followed by a null byte (remember: every line must have an even number of bytes). Then the eight data lines of the character follow. If all designed characters are defined (there could well be less than eight), a zero byte follows to signal the end. The table can be selected in total, copied with Ctrl-C and inserted to the source code with Strg-V.

10.6.3 Task

Define the arrow characters listed in the above graphic and display those on line 4 of the LCD.

10.6.4 The program

The spreadsheet calculation

The spreadsheet is available in OpenOffice format and as in Excel-Format.

Changes to the previous program

The previous program can be used as a basis. The following changes are required:

The code

Here is the program code, also available as source code in asm format.

;
; **************************************************
; * LCD display 4*20 on ATtiny24/4 bit/busy/arrows *
; * (C)2017 by http://www.avr-asm-tutorial.net     *
; **************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ------------- Hardware ----------------
;
;                ________
;               /        |
;     + 5 V o--|VCC   GND|--o 0 V
;              |         |
;    LCD-RS o--|PB0      |--o
;              |         |
;   LCD-R/W o--|PB1      |--o
;              |         |
;     RESET o--|RES      |--o
;              |         |
;     LCD-E o--|PB2      |--o
;              |         |
;    LCD-D7 o--|PA7   PA4|--o LCD-D4
;              |         |
;    LCD-D6 o--|PA6   PA5|--o LCD-D5
;              |_________|
;
;
; ----------- Ports, port pins -----------
; 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 mask write
.equ mLcdDRR  = 0x00   ; LCD data port mask read
;
; ------- Registers ----------------------
; free: R0 .. R15
.def rmp = R16 ; Multi purpose register
.def rmo = R17 ; Additional multi purpose register
.def rLine = R18 ; Line counter LCD
.def rLese = R19 ; Read result from LCD data port
.def rAddr = R20 ; Line address LCD character
; free: R21 .. R29
; used: R31:R30, ZH:ZL, for counting and as pointer 
;
; ------- Constants --------------------
.equ Clock = 1000000 ; Default clock frequency
;
; ---- Program start, Initiation -------
.CSEG ; Code Segment
.ORG 0 ; Start at 0
	; Init stack for subroutines
	ldi rmp,LOW(RAMEND) ; Init stack
	out SPL,rmp ; to stack pointer
	; Init ports and port pins
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; to LCD control port direction
	clr rmp ; Clear outputs
	out pLcdCO,rmp ; to LCD control port
	ldi rmp,mLcdDRW ; Data port output mask write
	out pLcdDR,rmp ; to data port direction port
	; Init LCD
	rcall LcdInit
	; Define characters
	rcall LcdChars ; New characters
	; Text output on LCD
	ldi ZH,HIGH(2*Texttable)
	ldi ZL,LOW(2*Texttable)
	rcall LcdText ; Text from ZH:ZL in flash
	; Sleep enable
	ldi rmp,1<<SE
	out MCUCR,rmp
Loop:
	sleep ; sleep
	rjmp Loop
;
; Output text on LCD
Texttable:
.db "LCD display ATtiny24",0x0D,0xFF ; Line 1
.db "avr-asm-tutorial.net",0x0D,0xFF ; Line 2
.db "Own characters here:",0x0D,0xFF ; Line 3
.db " ",0x00," ",0x01," ",0x02," ",0x03 ; Line 4 left
.db " ",0x04," ",0x05," ",0x06," ",0x07 ; Line 4 right
.db 0xFE,0xFE ; End of text
;
; --------- LCD control init ----------
LcdInit:
	; Wait 50 ms until LCD is available
	rcall Wait50ms ; Wait time 50 ms
	; Set LCD to 8 bit mode
	ldi rmp,0x30 ; 8 bit mode
	rcall LcdC8Byte ; Write to LCD in 8 bit mode
	rcall Wait5ms ; Wait 5 ms
	ldi rmp,0x30 ; Second repeat
	rcall LcdC8Byte
	rcall Wait5ms
	ldi rmp,0x30 ; Third repeat
	rcall LcdC8Byte
	rcall Wait5ms
	; Switch to 4 bit mode
	ldi rmp,0x20 ; To 4 bit mode
	rcall LcdC8Byte
	rcall Wait5ms
	; Function set LCD
	ldi rmp,0x28 ; 4 bit mode, 4 lines, 5*7
	rcall LcdC4Byte ; Byte to LCD control
	ldi rmp,0x0F ; Display on, blink
	rcall LcdC4Byte ; Byte to LCD control
	ldi rmp,0x01 ; Clear Display
	rcall LcdC4Byte ; Byte to LCD control
	ldi rmp,0x06 ; Autoindent
	rjmp LcdC4Byte ; Byte to LCD control
;
; Define own characters
LcdChars:
	ldi ZH,HIGH(2*Codechars) ; Z to character code table
	ldi ZL,LOW(2*Codechars)
LcdChars1:
	lpm rAddr,Z ; Read character address
	tst rAddr ; Check zero (end of table)
	breq LcdChars3 ; Done
	adiw ZL,2 ; to first data byte
	ldi rLine,8
LcdChars2:
	mov rmp,rAddr ; Write Address
	rcall LcdC4Byte ; to LCD
	lpm rmp,Z+ ; Read line pixels
	rcall LcdD4Byte ; Write to LCD
	inc rAddr ; Increase address
	dec rLine ; Count downwards
	brne LcdChars2 ; Further pixel bytes
	rjmp LcdChars1 ; Next character
LcdChars3:
	ret ; Done

; Table of code characters
Codechars:
.db 64,0,0,12,6,31,6,12,0,0 ; Z = 0, Arrow right
.db 72,0,0,6,12,31,12,6,0,0 ; Z = 1, Arrow left
.db 80,0,4,14,31,21,4,4,4,0 ; Z = 2, Arrow up
.db 88,0,4,4,4,21,31,14,4,0 ; Z = 3, Arrow down
.db 96,0,0,15,3,5,9,16,0,0 ; Z = 4, Arrow right up
.db 104,0,0,16,9,5,3,15,0,0 ; Z = 5, Arrow right down
.db 112,0,0,1,18,20,24,30,0,0 ; Z = 6, Arrow left down
.db 120,0,0,30,24,20,18,1,0,0 ; Z = 7, Arrow left up
.db 0,0 ; End of table
;
; Output of text on the LCD
;   Z points to text in flash
LcdText:
	clr rLine ; Line counter = 0
LcdText1:
	lpm rmp,Z+ ; read character from flash
	cpi rmp,0xFE ; End of text?
	breq LcdTextRet ; no
	cpi rmp,0xFF ; Dummy character?
	breq LcdText1 ; yes, continue
	cpi rmp,0x0D ; Line change?
	brne LcdText2 ; No line change
	inc rLine ; Next line
	mov rmp,rLine ; Select line
	rcall LcdLineSet ; Set line
	rjmp LcdText1 ; continue with characters
LcdText2: ; Write character
	rcall LcdD4Byte ; Write character to LCD
	rjmp LcdText1 ; Continue
LcdTextRet:
	ret ; Done
;
; Sets the output address to the line start of the
; selected line
;   Line: rmp 0 to 3 
LcdLineSet:
	cpi rmp,1 ; Line 2?
	brcs LcdLineSet1 ; no, to line 1
	breq LcdLineSet2 ; yes, to line 2
	cpi rmp,2 ; Line 3?
	breq LcdLineSet3 ; yes, to line 3
	rjmp LcdLineSet4 ; no, to line 4
LcdLineSet1:
	ldi rmp,0x80 ; Line 1
	rjmp LcdLineSetRmp
LcdLineSet2:
	ldi rmp,0xC0 ; Line 2
	rjmp LcdLineSetRmp
LcdLineSet3:
	ldi rmp,0x80+20 ; Line 3
	rjmp LcdLineSetRmp
LcdLineSet4:
	ldi rmp,0xC0+20 ; Line 4
	rjmp LcdLineSetRmp
LcdLineSetRmp:
	rjmp LcdC4Byte ; Control byte to LCD
;
; Data output to LCD in 4 bit mode
;   Data in rmp
LcdD4Byte:
	rcall LcdBusy ; Wait until busy = low
	sbi pLcdCO,bLcdCORS ; Set LCD-RS
	rjmp Lcd4Byte ; Write byte to LCD
;
; Control output to LCD in 4 bit mode
;   Data in rmp
LcdC4Byte:
	rcall LcdBusy ; Wait bis busy = Null
	cbi pLcdCO,bLcdCORS ; Clear LCD-RS
; Output byte in 4 bit mode with busy
Lcd4Byte:
	push rmp ; Save rmp on stack
	andi rmp,0xF0 ; Clear lower nibble
	in rmo,pLcdDI ; Read data bus port
	andi rmo,0x0F ; Clear upper nibble
	or rmp,rmo ; Combine lower and upper nibble
	out pLcdDO,rmp ; to data bus of the LCD
	nop ; Wait one cycle
	sbi pLcdCO,bLcdCOE ; Activate LCD-Enable
	nop ; Wait one cycle
	cbi pLcdCO,bLcdCOE ; Clear LCD-Enable
	pop rmp ; Restore rmp from stack
	andi rmp,0x0F ; Clear upper nibble
	swap rmp ; Exchange nibbles
	or rmp,rmo ; Combine lower and upper nibble
	out pLcdDO,rmp ; Write to data bus LCD
	nop ; Wait one cycle
	sbi pLcdCO,bLcdCOE ; Activate LCD-Enable
	nop ; Wait one cycle
	cbi pLcdCO,bLcdCOE ; Clear LCD-Enable
	ret ; Done
;
; Wait until busy clear
LcdBusy:
	push rmp ; Save rmp
	ldi rmp,mLcdDRR ; Read mask
	out pLcdDR,rmp ; to direction port
	cbi pLcdCO,bLcdCORS ; Clear LCD-RS
	sbi pLcdCO,bLcdCORW ; Set LCD-R/W
LcdBusy1:
	sbi pLcdCO,bLcdCOE ; Set LCD-Enable
	nop ; Wait one cycle
	in rLese,pLcdDI ; Read upper nibble
	cbi pLcdCO,bLcdCOE ; Clear LCD-Enable
	andi rLese,0xF0 ; Clear lower nibble
	sbi pLcdCO,bLcdCOE ; Set LCD-Enable
	nop ; Wait one cycle
	in rmp,pLcdDI ; Read lower nibble
	cbi pLcdCO,bLcdCOE ; Clear LCD-Enable
	andi rmp,0xF0 ; Clear lower nibble
	swap rmp ; Exchange upper/lower nibble
	or rLese,rmp ; Combine upper and lower nibble
	sbrc rLese,7 ; Skip if Busy=0
	rjmp LcdBusy1 ; Repeat until busy=0
	cbi pLcdCO,bLcdCORW ; Clear LCD-R/W
	ldi rmp,mLcdDRW ; Load write mask
	out pLcdDR,rmp ; to direction port
	pop rmp ; Restore rmp
	ret ; Return
;
; LCD control output in 8 bit mode
;   Data in rmp
LcdC8Byte:
	cbi pLcdCO,bLcdCORS ; Clear LCD-RS
	andi rmp,0xF0 ; Clear lower nibble
	in rmo,pLcdDI ; Read data port
	andi rmo,0x0F ; Clear upper nibble
	or rmp,rmo ; Combine lower and upper nibble
	out pLcdDO,rmp ; Write to LCD data port
	nop ; Wait one cycle
	sbi pLcdCO,bLcdCOE ; Activate LCD-Enable
	nop ; Wait one 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, total = 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 loops
LcdWait: ; Wait loop, nClocks = 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
;

Character output This is the result of that all (the German version): all nice arrows in line 4.

Here instead of CPI register,0 the instruction TST register was used, if the register is zero the Z flag is set, otherwise cleared.

New is ADIW register,N. This adds N to the register pair wordwise, and is only available for the LSB of the double registers (R24, R26, R28 and R30).

Home Top LCD ATtiny24 Hardware Wait mode Busy mode Characters


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