From cc7992bd934b565befcdb7e1a4e68dac0577e79e Mon Sep 17 00:00:00 2001 From: Alexander Rosenberg Date: Fri, 23 Feb 2024 22:56:10 -0800 Subject: [PATCH] Create LCD driver --- .gitignore | 2 + Makefile | 13 +++-- lcd.c | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lcd.h | 107 ++++++++++++++++++++++++++++++++++++++++ main.c | 30 ++++++++++++ util.c | 29 +++++++++++ util.h | 33 +++++++++++++ 7 files changed, 350 insertions(+), 4 deletions(-) create mode 100644 .gitignore create mode 100644 lcd.c create mode 100644 lcd.h create mode 100644 main.c create mode 100644 util.c create mode 100644 util.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..401433f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +rpi4b-temp-humidity \ No newline at end of file diff --git a/Makefile b/Makefile index 22fc18a..fc4bfac 100644 --- a/Makefile +++ b/Makefile @@ -2,15 +2,20 @@ CC=clang CFLAGS=-std=c99 LD=clang LDFLAGS=-lgpio -OBJS=main.o lcd.o +OBJS=main.o util.o lcd.o +OUTFILE=rpi4b-temp-humidity + +${OUTFILE}: ${OBJS} + ${LD} ${LDFLAGS} -o $@ ${OBJS} main.o lcd.o: lcd.h - -rpi4b-temp-himidity: ${OBJS} - ${LD} ${LDFLAGS} -o $@ ${OBJS} +main.o util.o lcd.o: util.h .c.o: ${CC} ${CFLAGS} -c $< +clean: + rm -f ${OUTFILE} ${OBJS} + .SUFFIXES: .c .o .PHONY: clean diff --git a/lcd.c b/lcd.c new file mode 100644 index 0000000..599a133 --- /dev/null +++ b/lcd.c @@ -0,0 +1,140 @@ +/* + * lcd.c - Subroutines for communicating with HD44780U based LCD panels + * Copyright (C) 2024 Alexander Rosenberg + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. See the LICENSE file for more information. + * + * This file was created with help from the document located here: + * https://www.sparkfun.com/datasheets/LCD/HD44780.pdf + */ +#include "lcd.h" +#include "util.h" + +#include + +static void lcd_set_read(LCD *lcd, bool is_read, bool force) { + if (force || lcd->is_read != is_read) { + gpio_config_t cfg = {.g_flags = is_read ? GPIO_PIN_INPUT + : GPIO_PIN_OUTPUT}; + cfg.g_pin = lcd->d0; + gpio_pin_set_flags(lcd->handle, &cfg); + cfg.g_pin = lcd->d1; + gpio_pin_set_flags(lcd->handle, &cfg); + cfg.g_pin = lcd->d2; + gpio_pin_set_flags(lcd->handle, &cfg); + cfg.g_pin = lcd->d3; + gpio_pin_set_flags(lcd->handle, &cfg); + cfg.g_pin = lcd->d4; + gpio_pin_set_flags(lcd->handle, &cfg); + cfg.g_pin = lcd->d5; + gpio_pin_set_flags(lcd->handle, &cfg); + cfg.g_pin = lcd->d6; + gpio_pin_set_flags(lcd->handle, &cfg); + cfg.g_pin = lcd->d7; + gpio_pin_set_flags(lcd->handle, &cfg); + lcd->is_read = is_read; + } +} + +static void lcd_pulse_enable(LCD *lcd) { + gpio_pin_high(lcd->handle, lcd->en); + usleep(1); // min of 1000ns + gpio_pin_low(lcd->handle, lcd->en); +} + +LCD *lcd_open(gpio_handle_t handle, int rs, int rw, int en, int d0, int d1, + int d2, int d3, int d4, int d5, int d6, int d7) { + LCD *lcd = malloc_checked(sizeof(LCD)); + lcd->handle = handle; + lcd->rs = rs; + lcd->rw = rw; + lcd->en = en; + lcd->d0 = d0; + lcd->d1 = d1; + lcd->d2 = d2; + lcd->d3 = d3; + lcd->d4 = d4; + lcd->d5 = d5; + lcd->d6 = d6; + lcd->d7 = d7; + lcd_set_read(lcd, false, true); + lcd_clear(lcd); + lcd_call(lcd, 0, 0, 0, 0, 1, 1, 1, 0, 0); // 5x8 font, 2 lines, 8bit + lcd_entry_mode(lcd, LCD_INCREMENT, LCD_CURSOR_MOVE); + lcd_display_control(lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, LCD_DISPLAY_ON); + return lcd; +} + +void lcd_close(LCD *lcd) { + free(lcd); +} + +void lcd_call(LCD *lcd, int rs, int d0, int d1, int d2, int d3, int d4, int d5, + int d6, int d7) { + while(lcd_is_busy(lcd)) { + usleep(5); + } + lcd_set_read(lcd, false, false); + gpio_pin_low(lcd->handle, lcd->en); + gpio_pin_low(lcd->handle, lcd->rw); + gpio_pin_set(lcd->handle, lcd->rs, rs); + gpio_pin_set(lcd->handle, lcd->d0, d0); + gpio_pin_set(lcd->handle, lcd->d1, d1); + gpio_pin_set(lcd->handle, lcd->d2, d2); + gpio_pin_set(lcd->handle, lcd->d3, d3); + gpio_pin_set(lcd->handle, lcd->d4, d4); + gpio_pin_set(lcd->handle, lcd->d5, d5); + gpio_pin_set(lcd->handle, lcd->d6, d6); + gpio_pin_set(lcd->handle, lcd->d7, d7); + lcd_pulse_enable(lcd); +} + +void lcd_write_char(LCD *lcd, char c) { + lcd_call(lcd, 1, c & 1, (c >> 1) & 1, (c >> 2) & 1, (c >> 3) & 1, (c >> 4) & 1, + (c >> 5) & 1, (c >> 6) & 1, (c >> 7) & 1); +} + +size_t lcd_write_string(LCD *lcd, const char *str) { + size_t count; + for (count = 0; *str; ++str, ++count) { + lcd_write_char(lcd, *str); + } + return count; +} + +void lcd_move_to(LCD *lcd, int line, int col) { + lcd_call(lcd, 0, col & 1, (col >> 1) & 1, (col >> 2) & 1, + (col >> 3) & 1, (col >> 4) & 1, (col >> 5) & 1, line, 1); +} + +void lcd_clear(LCD *lcd) { + lcd_call(lcd, 0, 1, 0, 0, 0, 0, 0, 0, 0); +} + +void lcd_display_control(LCD *lcd, int b, int c, int d) { + lcd_call(lcd, 0, b, c, d, 1, 0, 0, 0, 0); +} + +void lcd_entry_mode(LCD *lcd, int id, int s) { + lcd_call(lcd, 0, s, id, 1, 0, 0, 0, 0, 0); +} + +void lcd_cursor_shift(LCD *lcd, int sc, int rl) { + lcd_call(lcd, 0, 0, 0, rl, sc, 1, 0, 0, 0); +} + +bool lcd_is_busy(LCD *lcd) { + lcd_set_read(lcd, true, false); + gpio_pin_low(lcd->handle, lcd->rs); + gpio_pin_high(lcd->handle, lcd->rw); + gpio_pin_high(lcd->handle, lcd->en); + usleep(1); + gpio_value_t busy = gpio_pin_get(lcd->handle, lcd->d7); + gpio_pin_low(lcd->handle, lcd->en); + usleep(1); + return busy; + +} diff --git a/lcd.h b/lcd.h new file mode 100644 index 0000000..03fd977 --- /dev/null +++ b/lcd.h @@ -0,0 +1,107 @@ +/* + * lcd.h - Subroutines for communicating with HD44780U based LCD panels + * Copyright (C) 2024 Alexander Rosenberg + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. See the LICENSE file for more information. + */ +#ifndef INCLUDED_LCD_H +#define INCLUDED_LCD_H + +#include +#include +#include + +typedef struct { + gpio_handle_t handle; + // pin numbers + int rs; // register select + int rw; // read-write + int en; // enable + int d0; + int d1; + int d2; + int d3; + int d4; + int d5; + int d6; + int d7; + + // internal use + bool is_read; +} LCD; + +/* + * Allocate and initialize a new LCD object and return a pointer to it. HANDLE + * is a gpio_handle_t, each of the other arguments corresponds to the GPIO pin + * number that the given controller pin is connected to: "register select", + * "read-write", "enable", and data pins 0-7. + * Return: the newly created object. + */ +LCD *lcd_open(gpio_handle_t handle, int rs, int rw, int en, int d0, int d1, + int d2, int d3, int d4, int d5, int d6, int d7); +/* + * Close LCD by freeing any resources associated with it. + */ +void lcd_close(LCD *lcd); +/* + * Call a single instruction on LCD. Each argument is the value of that pin. + */ +void lcd_call(LCD *lcd, int rs, int d0, int d1, int d2, int d3, int d4, int d5, + int d6, int d7); +/* + * Write character C to LCD. + */ +void lcd_write_char(LCD *lcd, char c); + +/* + * Write STR to LCD. + * Return: the number of characters written. + */ +size_t lcd_write_string(LCD *lcd, const char *str); + +/* + * Move the cursor to COL on LINE, which both start from 0. + */ +void lcd_move_to(LCD *lcd, int line, int col); + +/* + * Clear LCD and return the cursor to the top right. + */ +void lcd_clear(LCD *lcd); + +#define LCD_DISPLAY_ON 1 +#define LCD_DISPLAY_OFF 0 +#define LCD_CURSOR_ON 1 +#define LCD_CURSOR_OFF 0 +#define LCD_CURSOR_BLINK 1 +#define LCD_CURSOR_NO_BLINK 0 +/* + * Control B, cursor blink, C, cursor visibility, and D, display power for LCD. + */ +void lcd_display_control(LCD *lcd, int b, int c, int d); + +#define LCD_INCREMENT 1 +#define LCD_DECREMENT 0 +#define LCD_DISPLAY_SHIFT 1 +#define LCD_CURSOR_MOVE 0 +#define LCD_SHIFT_RIGHT 1 +#define LCD_SHIFT_LEFT 0 +/* + * Control shift and increment for LCD. + */ +void lcd_entry_mode(LCD *lcd, int id, int s); + +/* + * Control direction and shift/move for cursor for LCD. + */ +void lcd_cursor_shift(LCD *lcd, int sc, int rl); + +/* + * Return: weather the busy flag is set for LCD. + */ +bool lcd_is_busy(LCD *lcd); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..48791a7 --- /dev/null +++ b/main.c @@ -0,0 +1,30 @@ +#include "lcd.h" + +#include +#include + +#define RS 2 +#define RW 3 +#define EN 4 +#define D0 17 +#define D1 27 +#define D2 22 +#define D3 10 +#define D4 9 +#define D5 14 +#define D6 15 +#define D7 18 + +int main(int argc, const char **argv) { + gpio_handle_t handle = gpio_open(0); + if (handle == GPIO_INVALID_HANDLE) { + errx(1, "count not open GPIO handle!"); + } + LCD *lcd = lcd_open(handle, RS, RW, EN, D0, D1, D2, D3, D4, D5, D6, D7); + lcd_write_string(lcd, "This is a test"); + lcd_move_to(lcd, 1, 0); + lcd_write_string(lcd, "FreeBSD!"); + lcd_close(lcd); + gpio_close(handle); + return 0; +} diff --git a/util.c b/util.c new file mode 100644 index 0000000..8994062 --- /dev/null +++ b/util.c @@ -0,0 +1,29 @@ +/* + * util.c - Utility functions + * Copyright (C) 2024 Alexander Rosenberg + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. See the LICENSE file for more information. + */ +#include "util.h" + +#include + +struct GlobalFlags GLOBAL_FLAGS = { + .config_path = NULL, + .gpio_path = NULL, +}; + +void *malloc_checked(size_t n) { + return realloc_checked(NULL, n); +} + +void *realloc_checked(void *old_ptr, size_t n) { + void *ptr = realloc(old_ptr, n); + if (n && !ptr) { + errx(1, "out of memory!"); + } + return ptr; +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..d0989ba --- /dev/null +++ b/util.h @@ -0,0 +1,33 @@ +/* + * util.h - Utility functions + * Copyright (C) 2024 Alexander Rosenberg + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. See the LICENSE file for more information. + */ +#ifndef INCLUDED_UTIL_H +#define INCLUDED_UTIL_H + +#include + +struct GlobalFlags { + const char *config_path; // path to config file + const char *gpio_path; // path of GPIO device file to use +}; +extern struct GlobalFlags GLOBAL_FLAGS; + +/* + * Like malloc(3), except that if allocation fails, an error will be written to + * standard error and abort(3) called + */ +void *malloc_checked(size_t n); + +/* + * Like realloc(3), except that if allocation fails, an error will be written to + * standard error and abort(3) called + */ +void *realloc_checked(void *old_ptr, size_t n); + +#endif