AVRly - AVR Development Resources
hd44780_lcd.c
Go to the documentation of this file.
1/******************************************************************************
2 @copyright Copyright © 2022 by Jason Duffy.
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
21******************************************************************************/
22
23/**
24 * @file hd44780_lcd.c
25 * @ingroup hd44780_lcd
26 * @author Jason Duffy
27 * @date 1st March 2022
28 * @brief Driver for the HD44780 based 16x2 liquid crystal display.
29 * @bug No known bugs.
30 *
31 * This file provides the basic low-level functionality for the ubiquitous 16x2
32 * display. Please note that it uses long blocking waits in the initialisation
33 * routine (100ms), and short blockign waits in other utility functions
34 * (2uS - 2mS). This was done to simplify the code as the busy flag read
35 * introduced pitfalls. This driver currently only allows for a single display
36 * to be used, but this might be improved on at a later date.
37 *
38 * NOTE: Values used in bitwise operations MUST be of unsigned type.
39 *
40 * This driver was written using the datasheet for the HITACHI HD44780U LCD
41 * driver chip, which can be found here:
42 * @see http://www.datasheet-pdf.com/PDF/HD44780U-Datasheet-Hitachi-1109874
43 */
44
45// System utility headers. These are installed along with AVR Crosspack.
46#include <avr/io.h>
47#include <util/delay.h>
48
49//Project specific headers.
50#include "hd44780_lcd.h"
51#include "pin_defines.h" // Pinout for the display is defined here.
52
53/**
54 * Local copy of lcd_config_t pointer to store address of config object.
55 */
56static lcd_config_t *p_config_local;
57
58/**
59 * Bitmask used to manipulate data based on data length setting in config.
60 */
61static uint8_t data_length_bitmask = 0;
62
63// Base commands without flags applied.
64#define CLEAR_DISPLAY 0b00000001U
65#define CURSOR_HOME 0b00000010U
66#define ENTRY_MODE_SET 0b00000100U
67#define ON_OFF_CTRL 0b00001000U
68#define CURSOR_DISPLAY_SHIFT 0b00010000U
69#define FUNCTION_SET 0b00100000U
70
71// Bit positions for setting flags in base commands.
72#define DATA_LENGTH_BIT 4U
73#define DISPLAY_LINES_BIT 3U
74#define FONT_SIZE_BIT 2U
75#define MOVE_DIRECTION_BIT 1U
76#define DISPLAY_SHIFT_BIT 0U
77#define CURSOR_ENABLE_BIT 1U
78#define BLINK_ENABLE_BIT 0U
79#define ON_OFF_CTRL_BIT 2U
80#define RIGHT_LEFT_BIT 2U
81#define SHIFT_OR_CURSOR_BIT 3U
82#define BUSY_FLAG_BIT 7U // Not currently used in this program.
83
84// Flags for setting ON_OFF_CTRL_BIT.
85#define DISPLAY_ON true
86#define DISPLAY_OFF false
87
88// Delay time definitions.
89#define POWER_RAMP_DELAY_MS 100
90#define SHORT_INSTR_DELAY_MS 2
91#define LONG_INSTR_DELAY_MS 10
92#define SCROLL_DELAY_MS 400
93#define ENABLE_DELAY_US 2
94
95// Forward declarations
96void lcd_command(uint8_t data);
97void lcd_char(char character);
98void pulse_enable(void);
99
100
101/*
102 * Initialisation routine (run once at startup).
103 */
104void init_lcd(lcd_config_t *p_config)
105{
106 // copy address of config object pointer to a local pointer
107 p_config_local = p_config;
108
109 // Set data direction registers to output where applicable
110 LCD_CTRL_DDR |= (1U << LCD_RS) | (1U << LCD_EN);
111
112 // Test for bit mode and set appropriate bitmask
113 if (p_config_local->eight_bit_mode)
114 {
115 data_length_bitmask = 0xff; // 0b11111111
116 }
117 else
118 {
119 data_length_bitmask |= (1U << LCD_D7) | (1U << LCD_D6);
120 data_length_bitmask |= (1U << LCD_D5) | (1U << LCD_D4);
121 }
122
123 // Set relevant lcd data port direction to output
124 LCD_DATA_DDR |= data_length_bitmask;
125
126 // Delay to allow power ramp up in LCD controller
127 _delay_ms(POWER_RAMP_DELAY_MS);
128
129 // Begin initialising HD44780
130 uint8_t byte = 0;
131 byte |= FUNCTION_SET;
132 byte |= (p_config_local->eight_bit_mode << DATA_LENGTH_BIT);
133
134 for (uint8_t count = 0; count < 3; ++count)
135 {
136 lcd_command(byte);
137 _delay_ms(LONG_INSTR_DELAY_MS);
138 }
139
140 byte = 0;
141 byte |= FUNCTION_SET;
142 byte |= (p_config_local->eight_bit_mode << DATA_LENGTH_BIT);
143 byte |= (p_config_local->two_line_display << DISPLAY_LINES_BIT);
144 byte |= (p_config_local->five_by_ten_font << FONT_SIZE_BIT);
145 lcd_command(byte);
146
147 byte = 0;
148 byte |= ON_OFF_CTRL;
149 byte |= (DISPLAY_OFF << ON_OFF_CTRL_BIT);
150 byte |= (p_config_local->cursor_enable << CURSOR_ENABLE_BIT);
151 byte |= (p_config_local->blink_enable << BLINK_ENABLE_BIT);
152 lcd_command(byte);
153
154 byte = 0;
155 byte |= CLEAR_DISPLAY;
156 lcd_command(byte);
157
158 byte = 0;
159 byte |= ENTRY_MODE_SET;
160 byte |= (p_config_local->increment_counter << MOVE_DIRECTION_BIT);
161 byte |= (p_config_local->display_shift << DISPLAY_SHIFT_BIT);
162 lcd_command(byte);
163
164 byte = 0;
165 byte |= ON_OFF_CTRL;
166 byte |= (DISPLAY_ON << ON_OFF_CTRL_BIT);
167 byte |= (p_config_local->cursor_enable << CURSOR_ENABLE_BIT);
168 byte |= (p_config_local->blink_enable << BLINK_ENABLE_BIT);
169 lcd_command(byte);
171}
172
173
174/*
175 * Turn display off (config settings are retained).
176 */
178{
179 uint8_t byte = 0;
180 byte |= ON_OFF_CTRL; // Default is off
181 byte |= (p_config_local->cursor_enable << CURSOR_ENABLE_BIT);
182 byte |= (p_config_local->blink_enable << BLINK_ENABLE_BIT);
183 lcd_command(byte);
184}
185
186
187/*
188 * Turn display on.
189 */
191{
192 uint8_t byte = 0;
193 byte |= ON_OFF_CTRL;
194 byte |= (DISPLAY_ON << ON_OFF_CTRL_BIT);
195 byte |= (p_config_local->cursor_enable << CURSOR_ENABLE_BIT);
196 byte |= (p_config_local->blink_enable << BLINK_ENABLE_BIT);
197 lcd_command(byte);
198}
199
200
201/*
202 * Prints a string of characters to the display.
203 */
204void lcd_print_string(const char *str)
205{
206 for (uint8_t index = 0; str[index] != 0; ++index)
207 {
208 lcd_char(str[index]);
209 }
210}
211
212
213/*
214 * Prints an integer variable.
215 */
216void lcd_print_integer(int16_t number)
217{
218 if (number < 0)
219 {
220 lcd_char('-');
221 }
222
223 uint8_t count = 0;
224 uint8_t digit = 0;
225 char str[20];
226
227 while (number > 0)
228 {
229 digit = number % 10;
230 str[count] = (digit + '0');
231 ++count;
232 number /= 10;
233 }
234
235 while (count > 0)
236 {
237 lcd_char(str[count - 1]);
238 --count;
239 }
240}
241
242
243/*
244 * Sets cursor location using x and y coordinates.
245 */
246void lcd_set_cursor(uint8_t column, uint8_t row)
247{
248 uint8_t address = 0;
249
250 if (row == 0)
251 {
252 address = column;
253 }
254
255 else if (row == 1)
256 {
257 address = (column + 64);
258 }
259
260 address |= 0x80U; // Write 1 to MSB
261 lcd_command(address);
262}
263
264
265/*
266 * Writes space characters to all 32 sections of display (or 16 if in 1 line
267 * mode).
268 */
270{
271 lcd_set_cursor(0,0);
272 for (uint8_t count = 0; count < 16; ++count)
273 {
274 lcd_char(' ');
275 }
276
277 if (p_config_local->two_line_display)
278 {
279 lcd_set_cursor(0,1);
280 for (uint8_t count = 0; count < 16; ++count)
281 {
282 lcd_char(' ');
283 }
284 }
285}
286
287
288/*
289 * Edits config settings for lcd (lcd_config_t members must be changed first).
290 */
292{
293 uint8_t byte = 0;
294 byte |= ENTRY_MODE_SET;
295 byte |= (p_config_local->increment_counter << MOVE_DIRECTION_BIT);
296 byte |= (p_config_local->display_shift << DISPLAY_SHIFT_BIT);
297 lcd_command(byte);
298
299 // Also resends cursor and blink settings
301}
302
303
304/*
305 * Sets DDRAM address 0 in address counter. Also returns display from being
306 * shifted to original position. DDRAM contents remain unchanged.
307 */
309{
310 lcd_command(CURSOR_HOME);
311 _delay_ms(SHORT_INSTR_DELAY_MS);
312}
313
314
315/*
316 * Moves cursor left without changing DDRAM contents.
317 */
318void lcd_shift_cursor_left(uint8_t distance)
319{
320 uint8_t byte = 0;
321 byte |= CURSOR_DISPLAY_SHIFT;
322
323 for (uint8_t count = 0; count < distance; ++count)
324 {
325 lcd_command(byte);
326 }
327}
328
329
330/*
331 * Moves cursor right without changing DDRAM contents.
332 */
333void lcd_shift_cursor_right(uint8_t distance)
334{
335 uint8_t byte = 0;
336 byte |= CURSOR_DISPLAY_SHIFT;
337 byte |= (1U << RIGHT_LEFT_BIT);
338
339 for (uint8_t count = 0; count < distance; ++count)
340 {
341 lcd_command(byte);
342 }
343}
344
345
346/*
347 * Shifts display left without changing DDRAM contents.
348 */
349void lcd_shift_display_left(uint8_t distance, bool delay)
350{
351 uint8_t byte = 0U;
352 byte |= CURSOR_DISPLAY_SHIFT;
353 byte |= (1U << SHIFT_OR_CURSOR_BIT);
354
355 for (uint8_t count = 0; count < distance; ++count)
356 {
357 lcd_command(byte);
358 if (delay == true)
359 {
360 _delay_ms(SCROLL_DELAY_MS);
361 }
362 }
363}
364
365
366/*
367 * Shifts display right without changing DDRAM contents.
368 */
369void lcd_shift_display_right(uint8_t distance, bool delay)
370{
371 uint8_t byte = 0;
372 byte |= CURSOR_DISPLAY_SHIFT;
373 byte |= (1U << SHIFT_OR_CURSOR_BIT);
374 byte |= (1U << RIGHT_LEFT_BIT);
375
376 for (uint8_t count = 0; count < distance; ++count)
377 {
378 lcd_command(byte);
379 if (delay == true)
380 {
381 _delay_ms(SCROLL_DELAY_MS);
382 }
383 }
384}
385
386//===========================================================================//
387//---------------------------------------------------------------------------//
388// Private utility functions //
389//---------------------------------------------------------------------------//
390//===========================================================================//
391
392/*
393 * Sends a command to the display.
394 */
395void lcd_command(uint8_t data)
396{
397 LCD_CTRL_PORT &= ~(1U << LCD_RS);
398
399 if (p_config_local->eight_bit_mode)
400 {
401 LCD_DATA_PORT = data;
402 pulse_enable();
403 }
404 else
405 {
406 uint8_t byte = 0;
407
408 LCD_DATA_PORT &= ~(data_length_bitmask); // Clear relevant bits in port
409 byte = (data & 0b11110000); // Keep high 4 bits only
410 byte = (byte >> (7U - LCD_D7)); // Shift nibble into place
411 LCD_DATA_PORT |= byte; // Send first nibble to LCD
412 pulse_enable();
413
414 LCD_DATA_PORT &= ~(data_length_bitmask); // Clear relevant bits in port
415 byte = (data & 0b00001111); // Keep low 4 bits only
416 byte = (byte << (LCD_D7 - 3U)); // Shift nibble into place
417 LCD_DATA_PORT |= byte; // Send second nibble to LCD
418 pulse_enable();
419 }
420 _delay_ms(SHORT_INSTR_DELAY_MS);
421}
422
423
424/*
425 * Writes a single character to LCD.
426 */
427void lcd_char(char character)
428{
429 LCD_CTRL_PORT |= (1U << LCD_RS);
430
431 if (p_config_local->eight_bit_mode)
432 {
433 LCD_DATA_PORT = character;
434 pulse_enable();
435 }
436 else
437 {
438 uint8_t byte = 0;
439
440 LCD_DATA_PORT &= ~(data_length_bitmask); // Clear relevant bits in port
441 byte = (character & 0b11110000); // Keep high 4 bits only
442 byte = (byte >> (7U - LCD_D7)); // Shift nibble into place
443 LCD_DATA_PORT |= byte; // Send first nibble to LCD
444 pulse_enable();
445
446 LCD_DATA_PORT &= ~(data_length_bitmask); // Clear relevant bits in port
447 byte = (character & 0b00001111); // Keep low 4 bits only
448 byte = (byte << (LCD_D7 - 3U)); // Shift nibble into place
449 LCD_DATA_PORT |= byte; // Send second nibble to LCD
450 pulse_enable();
451 }
452 _delay_ms(SHORT_INSTR_DELAY_MS);
453}
454
455
456/*
457 * Pulse enable pin to latch data into register.
458 */
459void pulse_enable(void)
460{
461 LCD_CTRL_PORT |= (1U << LCD_EN);
462 _delay_us(ENABLE_DELAY_US);
463 LCD_CTRL_PORT &= ~(1U << LCD_EN);
464 _delay_us(ENABLE_DELAY_US);
465}
466
467/*** end of file ***/
void lcd_shift_cursor_left(uint8_t distance)
Moves cursor left without changing DDRAM contents.
Definition: hd44780_lcd.c:319
void lcd_print_string(const char *str)
Prints a string of characters to the display.
Definition: hd44780_lcd.c:205
void lcd_display_off(void)
Turn display off (config settings are retained).
Definition: hd44780_lcd.c:178
void lcd_print_integer(int16_t number)
Prints an integer variable.
Definition: hd44780_lcd.c:217
void lcd_shift_display_right(uint8_t distance, bool delay)
Shifts display right without changing DDRAM contents.
Definition: hd44780_lcd.c:370
void init_lcd(lcd_config_t *p_config)
Initialisation routine (run once at startup).
Definition: hd44780_lcd.c:105
void lcd_display_on(void)
Turn display on.
Definition: hd44780_lcd.c:191
void lcd_fast_clear(void)
Writes space characters to all 32 sections of display (or 16 if in 1 line mode).
Definition: hd44780_lcd.c:270
void lcd_shift_display_left(uint8_t distance, bool delay)
Shifts display left without changing DDRAM contents.
Definition: hd44780_lcd.c:350
void lcd_reconfigure(void)
Edits config settings on the display (lcd_config_t members must be changed first).
Definition: hd44780_lcd.c:292
void lcd_return_home(void)
Sets DDRAM address 0 in address counter.
Definition: hd44780_lcd.c:309
void lcd_shift_cursor_right(uint8_t distance)
Moves cursor right without changing DDRAM contents.
Definition: hd44780_lcd.c:334
void lcd_set_cursor(uint8_t column, uint8_t row)
Sets cursor location using x and y coordinates.
Definition: hd44780_lcd.c:247
Driver for the HD44780 based 16x2 liquid crystal display.
Definitions for pin mapping (for CCS81 gas sensor)
Configuration struct, to be instantiated and values assigned before passing it's address into and cal...
Definition: hd44780_lcd.h:57
bool display_shift
true = display shift, false = cursor shift.
Definition: hd44780_lcd.h:62
bool cursor_enable
true = enabled, false = disabled.
Definition: hd44780_lcd.h:63
bool eight_bit_mode
true = 8 bit mode, false = 4 bit mode.
Definition: hd44780_lcd.h:58
bool increment_counter
true = increment, false = decrement.
Definition: hd44780_lcd.h:61
bool blink_enable
true = enabled, false = disabled.
Definition: hd44780_lcd.h:64
bool five_by_ten_font
true = 5x10 dots, false = 5x8 dots.
Definition: hd44780_lcd.h:60
bool two_line_display
true = 2 lines, false = 1 line.
Definition: hd44780_lcd.h:59