Those pins mean the following:
Pin | Name | Description |
---|
1 | VSS | Operating voltage, minus |
2 | VDD | Operating voltage, plus, 3 or 5 Volt |
3 | VEE | Contrast regulation voltage |
4 | RS | Register Select, 0=Control, 1=Data |
5 | R/W | Read/Write, 0=Write to LCD, 1=Read from LCD |
6 | E | Enable, 0=inactive, 1=active/Read/Write |
7 | D0 | Data bit 0 |
8 | D1 | Data bit 1 |
9 | D2 | Data bit 2 |
10 | D3 | Data bit 3 |
11 | D4 | Data bit 4 |
12 | D5 | Data bit 5 |
13 | D6 | Data bit 6 |
14 | D7 | Data bit 7 |
15 | A | Backlight Anode |
16 | K | Backlight 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.
- Following the switching on of the operating voltage of the display
the LCD controller needs some time to start up. Before this has been
finished no communication is possible. The time over which this internal
initiation takes place varies from display to display. 50 ms is in
any case sufficient.
- After waiting for start-up the Function Set is executed. The binary
code for that is 0b001L.NFxx. The single bits in that mean:
- L=1 if an 8 bit interface is attached.
- N defines the number of lines, N=0 is a single line, N=1 defines
multiple lines.
- F selects the character format, 0 means a 5-by-8 character set.
- If a 4 bit interface is attached and selected, then sending of
0b0011.xxxx for three times in a row switches the LCD safely to the eight
bit mode. After that the switching to four bit mode with 0b0010.xxxx is
performed in 8 bit mode (no splitting to upper and lower nibble). After
having done this, the N and F can be set by sending 0b0010.NFxx, now in
4 bit split mode (0b0010 first, then 0bNFxx).
- Switching modes requires slightly longer than 1 ms, waiting for
5 ms between those commands is sufficient.
- The following commands require approximately 40 µs. From
now on the busy flag can be read from the LCD alternatively. To do that
the data port that drive the LCDs data lines has to be configured as
input (clear the direction bits), the R/W input of the LCD has to be set
high. By activating the E input of the LCD this outputs its busy flag on
bit 7. In 4 bit mode a second activation of E is necessary. If the busy
flag is zero, the previous command has been executed and the LCD is
listening for the next command.
- The following command is 0b0000.DCBx.
- D=0 switches the LCD off, D=1 on.
- C=0 switches the output cursor off, C=1 on.
- B=0 switches blinking at the cursor position off, B=1 on.
- The command 0b0000.0001 clears the display.
- The command 0b0000.00IS switches the autoincrement of the display
address on (I=1) or off (I=0). S=1 shifts the content of the display,
S=0 overwrites the content.
- With the command 0b0000.001x the address is set to character 1 on line 1
and any shifting is cleared (if S=1). This command requires more than
1 ms, 5 ms are sufficient.
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):
- Line 1: 0x80 + (Column - 1),
- Line 2: 0xC0 + (Column - 1),
- Line 3: 0x80 + N + (Column - 1),
- Line 4: 0xC0 + N + (Column -1).
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.
 I = 2.1 mA, U = 2.9 V |
 I = 7.4 mA, U = 3.4 V |
 I = 12.0 mA, U = 3.8 V |
 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.
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.
10.3.1 Scheme
The scheme shows how the LCD is controlled by the ATtiny24:
- The pins PB0 (LCD-RS) and PB2 (LCD-E) drive the control pins of the
LCD, the data bus (upper nibble, nibble = 4 bits) is controlled by
the upper nibble of port A. The R/W input of the LCD is tied to low,
the LCD is only used to write to it and not to read from it.
- The ISP is attached to the respective pins, which are also used
as LCD data bus.
- The backlight of the LCD is driven via a 220 Ω resistor
with a small but sufficient current.
10.3.2 Components
LCD display
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
This is the 14 pin ATtiny24.
Socket 14 pin
The controller fits into that 14 pin socket.
Trimmer
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
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.
10.3.4 Alternative mounting: The Attiny24 LCD experimental board
In order to ease mounting for this and the following experiments you can also
use the ATtiny24 LCD experimental board described on the page
here. All the necessary
components (ATtiny24, LCD, trim potentiometer, etc.) are mounted on a small
single-sided PCB. The board is equipped with a six-pin ISP interface to
program the ATtiny24 and another six-pin plug to interface the PCB with the
breadboard.
All software provided here can be used without any changes on this board.
The experimental board can be used with any type of LCD in respect to sizes
(lines, characters per line). A software tool (as assembler include file)
to accommodate source code to those different sizes and two demonstrative
appliances of this include code round up this versatile experimental board,
so that it can be used for lots of own experiments, too.
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:
- Initiation: 50 ms,
- Clearing the display: 5 ms,
- Character write, simple control commands: 40 µs.
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
- two clock cycles for the LDI instructions,
- four clock cycles multiplied by (n-1),
- three clock cycles for the last loop run.
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:
- three clock cycles for an RCALL instruction to jump to the loop,
- four clock cycles for a RET instruction at the end,
- if the register pair Z has to be saved first and restored after
using it, two PUSH and two POP with two clock cycles each (8 clock
cycles in total) are added to the total.
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
; | |
; (LCD-RW) 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
.equ bLcdCORW = PORTB1 ; LCD RW pin output
.equ bLcdCRRW = DDB1 ; LCD RW 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)|(1<<bLcdCRRW) ; 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 a few new instructions. 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.
Further new is RCALL distance. This jumps to a relative
location given by the distance parameter and returns back to the origin if
a ret instruction is executed because it has pushed the original program
counter to the stack prior to jumping. The distance is calculated by the
assembler, if a label to be called specifies the parameter. The distance
is limited, in larger devices a wide call CALL label can
be used instead. This requires two instruction words and longer execution
times.
The instructions PUSH register and POP
save a register on the stack or restore this from the stack and adjust the
stack pointer accordingly. A POP always yields the last pushed byte.
10.4.4 Simulating the wait routines
Here it makes some sense to test the wait routines with the simulator
avr_sim.
For that we place calls to the waitroutines behind the stack
pointer init,
rcall Wait50ms
rcall Wait5ms
rcall Wait100us
rcall Wait40us
nop
place breakpoints to all of those lines and clear the stopwatch
after every line executed.
The routine is rather exact, only one clock cycle longer than
calculated.
The same with the 5 ms loop.
And also for 100 µs.
And, it is getting boring now, all the same for the 40 µs
loop. It seems that the formula has to be corrected for one
cycle.
10.5.1 Reading the busy flag of the LCD
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.
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:
- 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.
- 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.
- For all eight lines of the characters one pair of address and data
are to be written.
10.6.2 Software for character design
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.
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:
- Between the LCD's init code and the text output to the LCD a new
routine has to be added that writes the new characters to the LCD.
- The text output routine for the characters has to be changed. To
write the character null the previous end character that signalled
the table end has to be changed. The character 0xFE is a good
selection for this.
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
;
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).
An additional hint that might be useful: There is a useful versatile LCD include
file available that masters all variations (4-/8-bit interface, wait/busy mode,
special characters) in one include file. That can be configured from within the
main source code by setting constants. The routine is described
here, the include file can be downloaded
here as assembler include file.
©2017 by http://www.avr-asm-tutorial.net