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 digital_clock
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 <avr/cpufunc.h>
48#include <util/delay.h>
49
50//Project specific headers.
51#include "hd44780_lcd.h"
52#include "pin_defines.h" // Pinout for the display is defined here.
53
54/**
55 * Local copy of lcd_config_t pointer to store address of config object.
56 */
57static lcd_config_t *p_config_local;
58
59/**
60 * Bitmask used to manipulate data based on data length setting in config.
61 */
62static uint8_t data_length_bitmask = 0;
63
64// Base commands without flags applied.
65#define CLEAR_DISPLAY 0b00000001U
66#define CURSOR_HOME 0b00000010U
67#define ENTRY_MODE_SET 0b00000100U
68#define ON_OFF_CTRL 0b00001000U
69#define CURSOR_DISPLAY_SHIFT 0b00010000U
70#define FUNCTION_SET 0b00100000U
71
72// Bit positions for setting flags in base commands.
73#define DATA_LENGTH_BIT 4U
74#define DISPLAY_LINES_BIT 3U
75#define FONT_SIZE_BIT 2U
76#define MOVE_DIRECTION_BIT 1U
77#define DISPLAY_SHIFT_BIT 0U
78#define CURSOR_ENABLE_BIT 1U
79#define BLINK_ENABLE_BIT 0U
80#define ON_OFF_CTRL_BIT 2U
81#define RIGHT_LEFT_BIT 2U
82#define SHIFT_OR_CURSOR_BIT 3U
83#define BUSY_FLAG_BIT 7U
84
85// Flags for setting ON_OFF_CTRL_BIT.
86#define DISPLAY_ON true
87#define DISPLAY_OFF false
88
89// Delay time definitions.
90#define POWER_RAMP_DELAY_MS 100
91#define SHORT_INSTR_DELAY_MS 2
92#define LONG_INSTR_DELAY_MS 10
93#define SCROLL_DELAY_MS 400
94#define ENABLE_DELAY_US 2
95
96// Forward declarations
97void lcd_command(uint8_t data);
98void lcd_char(char character);
99void pulse_enable(void);
100
101
102/*
103 * Initialisation routine (run once at startup).
104 */
105void init_lcd(lcd_config_t *p_config)
106{
107 // copy address of config object pointer to a local pointer
108 p_config_local = p_config;
109
110 // Set data direction registers to output where applicable
111 LCD_CTRL_DDR |= (1U << LCD_RS) | (1U << LCD_EN);
112
113 // Test for bit mode and set appropriate bitmask
114 if (p_config_local->eight_bit_mode)
115 {
116 data_length_bitmask = 0xff; // 0b11111111
117 }
118 else
119 {
120 data_length_bitmask |= (1U << LCD_D7) | (1U << LCD_D6);
121 data_length_bitmask |= (1U << LCD_D5) | (1U << LCD_D4);
122 }
123
124 // Set relevant lcd data port direction to output
125 LCD_DATA_DDR |= data_length_bitmask;
126
127 // Delay to allow power ramp up in LCD controller
128 _delay_ms(POWER_RAMP_DELAY_MS);
129
130 // Begin initialising HD44780
131 uint8_t byte = 0;
132 byte |= FUNCTION_SET;
133 byte |= (p_config_local->eight_bit_mode << DATA_LENGTH_BIT);
134
135 for (uint8_t count = 0; count < 3; ++count)
136 {
137 lcd_command(byte);
138 _delay_ms(LONG_INSTR_DELAY_MS);
139 }
140
141 byte = 0;
142 byte |= FUNCTION_SET;
143 byte |= (p_config_local->eight_bit_mode << DATA_LENGTH_BIT);
144 byte |= (p_config_local->two_line_display << DISPLAY_LINES_BIT);
145 byte |= (p_config_local->five_by_ten_font << FONT_SIZE_BIT);
146 lcd_command(byte);
147
148 byte = 0;
149 byte |= ON_OFF_CTRL;
150 byte |= (DISPLAY_OFF << ON_OFF_CTRL_BIT);
151 byte |= (p_config_local->cursor_enable << CURSOR_ENABLE_BIT);
152 byte |= (p_config_local->blink_enable << BLINK_ENABLE_BIT);
153 lcd_command(byte);
154
155 byte = 0;
156 byte |= CLEAR_DISPLAY;
157 lcd_command(byte);
158
159 byte = 0;
160 byte |= ENTRY_MODE_SET;
161 byte |= (p_config_local->increment_counter << MOVE_DIRECTION_BIT);
162 byte |= (p_config_local->display_shift << DISPLAY_SHIFT_BIT);
163 lcd_command(byte);
164
165 byte = 0;
166 byte |= ON_OFF_CTRL;
167 byte |= (DISPLAY_ON << ON_OFF_CTRL_BIT);
168 byte |= (p_config_local->cursor_enable << CURSOR_ENABLE_BIT);
169 byte |= (p_config_local->blink_enable << BLINK_ENABLE_BIT);
170 lcd_command(byte);
172}
173
174
175/*
176 * Turn display off (config settings are retained).
177 */
179{
180 uint8_t byte = 0;
181 byte |= ON_OFF_CTRL; // Default is off
182 byte |= (p_config_local->cursor_enable << CURSOR_ENABLE_BIT);
183 byte |= (p_config_local->blink_enable << BLINK_ENABLE_BIT);
184 lcd_command(byte);
185}
186
187
188/*
189 * Turn display on.
190 */
192{
193 uint8_t byte = 0;
194 byte |= ON_OFF_CTRL;
195 byte |= (DISPLAY_ON << ON_OFF_CTRL_BIT);
196 byte |= (p_config_local->cursor_enable << CURSOR_ENABLE_BIT);
197 byte |= (p_config_local->blink_enable << BLINK_ENABLE_BIT);
198 lcd_command(byte);
199}
200
201
202/*
203 * Prints a string of characters to the display.
204 */
205void lcd_print_string(const char *str)
206{
207 for (uint8_t index = 0; str[index] != 0; ++index)
208 {
209 lcd_char(str[index]);
210 }
211}
212
213
214/*
215 * Prints an integer variable.
216 */
217void lcd_print_integer(int16_t number)
218{
219 if (number < 0)
220 {
221 lcd_char('-');
222 }
223
224 uint8_t count = 0;
225 uint8_t digit = 0;
226 char str[20];
227
228 while (number > 0)
229 {
230 digit = number % 10;
231 str[count] = (digit + '0');
232 ++count;
233 number /= 10;
234 }
235
236 while (count > 0)
237 {
238 lcd_char(str[count - 1]);
239 --count;
240 }
241}
242
243
244/*
245 * Sets cursor location using x and y coordinates.
246 */
247void lcd_set_cursor(uint8_t column, uint8_t row)
248{
249 uint8_t address = 0;
250
251 if (row == 0)
252 {
253 address = column;
254 }
255
256 else if (row == 1)
257 {
258 address = (column + 64);
259 }
260
261 address |= 0x80U; // Write 1 to MSB
262 lcd_command(address);
263}
264
265
266/*
267 * Writes space characters to all 32 sections of display (or 16 if in 1 line
268 * mode).
269 */
271{
272 lcd_set_cursor(0,0);
273 for (uint8_t count = 0; count < 16; ++count)
274 {
275 lcd_char(' ');
276 }
277
278 if (p_config_local->two_line_display)
279 {
280 lcd_set_cursor(0,1);
281 for (uint8_t count = 0; count < 16; ++count)
282 {
283 lcd_char(' ');
284 }
285 }
286}
287
288
289/*
290 * Edits config settings for lcd (lcd_config_t members must be changed first).
291 */
293{
294 uint8_t byte = 0;
295 byte |= ENTRY_MODE_SET;
296 byte |= (p_config_local->increment_counter << MOVE_DIRECTION_BIT);
297 byte |= (p_config_local->display_shift << DISPLAY_SHIFT_BIT);
298 lcd_command(byte);
299
300 // Also resends cursor and blink settings
302}
303
304
305/*
306 * Sets DDRAM address 0 in address counter. Also returns display from being
307 * shifted to original position. DDRAM contents remain unchanged.
308 */
310{
311 lcd_command(CURSOR_HOME);
312 _delay_ms(SHORT_INSTR_DELAY_MS);
313}
314
315
316/*
317 * Moves cursor left without changing DDRAM contents.
318 */
319void lcd_shift_cursor_left(uint8_t distance)
320{
321 uint8_t byte = 0;
322 byte |= CURSOR_DISPLAY_SHIFT;
323
324 for (uint8_t count = 0; count < distance; ++count)
325 {
326 lcd_command(byte);
327 }
328}
329
330
331/*
332 * Moves cursor right without changing DDRAM contents.
333 */
334void lcd_shift_cursor_right(uint8_t distance)
335{
336 uint8_t byte = 0;
337 byte |= CURSOR_DISPLAY_SHIFT;
338 byte |= (1U << RIGHT_LEFT_BIT);
339
340 for (uint8_t count = 0; count < distance; ++count)
341 {
342 lcd_command(byte);
343 }
344}
345
346
347/*
348 * Shifts display left without changing DDRAM contents.
349 */
350void lcd_shift_display_left(uint8_t distance, bool delay)
351{
352 uint8_t byte = 0U;
353 byte |= CURSOR_DISPLAY_SHIFT;
354 byte |= (1U << SHIFT_OR_CURSOR_BIT);
355
356 for (uint8_t count = 0; count < distance; ++count)
357 {
358 lcd_command(byte);
359 if (delay == true)
360 {
361 _delay_ms(SCROLL_DELAY_MS);
362 }
363 }
364}
365
366
367/*
368 * Shifts display right without changing DDRAM contents.
369 */
370void lcd_shift_display_right(uint8_t distance, bool delay)
371{
372 uint8_t byte = 0;
373 byte |= CURSOR_DISPLAY_SHIFT;
374 byte |= (1U << SHIFT_OR_CURSOR_BIT);
375 byte |= (1U << RIGHT_LEFT_BIT);
376
377 for (uint8_t count = 0; count < distance; ++count)
378 {
379 lcd_command(byte);
380 if (delay == true)
381 {
382 _delay_ms(SCROLL_DELAY_MS);
383 }
384 }
385}
386
387//===========================================================================//
388//---------------------------------------------------------------------------//
389// Private utility functions //
390//---------------------------------------------------------------------------//
391//===========================================================================//
392
393/*
394 * Sends a command to the display.
395 */
396void lcd_command(uint8_t data)
397{
398 LCD_CTRL_PORT &= ~(1U << LCD_RS);
399
400 if (p_config_local->eight_bit_mode)
401 {
402 LCD_DATA_PORT = data;
403 pulse_enable();
404 }
405 else
406 {
407 uint8_t byte = 0;
408
409 LCD_DATA_PORT &= ~(data_length_bitmask); // Clear relevant bits in port
410 byte = (data & 0b11110000); // Keep high 4 bits only
411 byte = (byte >> (7U - LCD_D7)); // Shift nibble into place
412 LCD_DATA_PORT |= byte; // Send first nibble to LCD
413 pulse_enable();
414
415 LCD_DATA_PORT &= ~(data_length_bitmask); // Clear relevant bits in port
416 byte = (data & 0b00001111); // Keep low 4 bits only
417 byte = (byte << (LCD_D7 - 3U)); // Shift nibble into place
418 LCD_DATA_PORT |= byte; // Send second nibble to LCD
419 pulse_enable();
420 }
421 _delay_ms(SHORT_INSTR_DELAY_MS);
422}
423
424
425/*
426 * Writes a single character to LCD.
427 */
428void lcd_char(char character)
429{
430 LCD_CTRL_PORT |= (1U << LCD_RS);
431
432 if (p_config_local->eight_bit_mode)
433 {
434 LCD_DATA_PORT = character;
435 pulse_enable();
436 }
437 else
438 {
439 uint8_t byte = 0;
440
441 LCD_DATA_PORT &= ~(data_length_bitmask); // Clear relevant bits in port
442 byte = (character & 0b11110000); // Keep high 4 bits only
443 byte = (byte >> (7U - LCD_D7)); // Shift nibble into place
444 LCD_DATA_PORT |= byte; // Send first nibble to LCD
445 pulse_enable();
446
447 LCD_DATA_PORT &= ~(data_length_bitmask); // Clear relevant bits in port
448 byte = (character & 0b00001111); // Keep low 4 bits only
449 byte = (byte << (LCD_D7 - 3U)); // Shift nibble into place
450 LCD_DATA_PORT |= byte; // Send second nibble to LCD
451 pulse_enable();
452 }
453 _delay_ms(SHORT_INSTR_DELAY_MS);
454}
455
456
457/*
458 * Pulse enable pin to latch data into register.
459 */
460void pulse_enable(void)
461{
462 LCD_CTRL_PORT |= (1U << LCD_EN);
463 _delay_us(ENABLE_DELAY_US);
464 LCD_CTRL_PORT &= ~(1U << LCD_EN);
465 _delay_us(ENABLE_DELAY_US);
466}
467
468/*** 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