You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
zavlaha-kzk/src/lcd.c

434 lines
8.6 KiB

#include <stdbool.h>
#include <stdint.h>
#include <pico/stdlib.h>
#include "lcd.h"
#include "pinout.h"
#define LCD_D7 PIN_LCD_D7
#define LCD_D6 PIN_LCD_D6
#define LCD_D5 PIN_LCD_D5
#define LCD_D4 PIN_LCD_D4
#define LCD_RS PIN_LCD_RS
#define LCD_RW PIN_LCD_RW
#define LCD_E PIN_LCD_E
#define set_pin(num, val) gpio_put((num), (val))
#define get_pin(num) gpio_get((num))
#define pin_high(num) set_pin((num), 1)
#define pin_low(num) set_pin((num), 0)
#define get_bit(a, pos) (((a) >> (pos)) & 1)
#define as_output(pin) gpio_set_dir((pin), 1)
#define as_input_pu(pin) do { gpio_set_dir((pin), 0); gpio_pull_up((pin)); } while (0)
#define _delay_ms(ms) sleep_ms((ms))
#define _delay_us(us) sleep_us((us))
#define _delay_ns(ns) sleep_us(1 + ((ns) / 1000)) // TODO
// Start address of rows
const uint8_t LCD_ROW_ADDR[] = {0x00, 0x40, 0x14, 0x54};
// these are for the lcdbuf library
#include "lcd/lcdbuf.h"
/** Clear the entire screen (CGRAM can be left unchanged, but they will be written anew if needed) */
void LcdBuffer_IO_Clear() {
lcd_clear();
}
/** Write character data at position */
void LcdBuffer_IO_WriteAt(lcdbuf_pos_t row, lcdbuf_pos_t col, const uint8_t *buf, lcdbuf_count_t len)
{
lcd_xy(col, row);
lcd_putsn(buf, len);
}
/** Write CGRAM data. Data is always 8 bytes long. */
void LcdBuffer_IO_WriteCGRAM(uint8_t position, const uint8_t* data)
{
lcd_glyph(position, data);
}
void LcdBuffer_IO_SetCursorPos(lcdbuf_pos_t cursor_row, lcdbuf_pos_t cursor_col)
{
lcd_xy(cursor_col, cursor_row);
}
void LcdBuffer_IO_SetCursorStyle(uint8_t cursor_style)
{
lcd_set_cursor(cursor_style & 0b11);
}
// Internal prototypes
void _lcd_mode_r();
void _lcd_mode_w();
void _lcd_clk();
void _lcd_wait_bf();
void _lcd_write_byte(uint8_t bb);
uint8_t _lcd_read_byte();
void lcd_command(uint8_t bb);
// Write utilities
#define _lcd_write_low(bb) _lcd_write_nibble((bb) & 0x0F)
#define _lcd_write_high(bb) _lcd_write_nibble(((bb) & 0xF0) >> 4)
#define _lcd_write_nibble(nib) \
gpio_put_masked((1 << PIN_LCD_D7) | (1 << PIN_LCD_D6) | (1 << PIN_LCD_D5) | (1 << PIN_LCD_D4), (nib) << PIN_LCD_D4)
//#define _lcd_write_nibble(nib) do { \
// set_pin(LCD_D7, get_bit((nib), 3)); \
// set_pin(LCD_D6, get_bit((nib), 2)); \
// set_pin(LCD_D5, get_bit((nib), 1)); \
// set_pin(LCD_D4, get_bit((nib), 0)); \
//} while(0)
// --- Commands ---
// Clear screen (reset)
#define LCD_CLEAR 0b00000001
// Move cursor to (0,0), unshift...
#define LCD_HOME 0b00000010
// Set mode: Increment + NoShift
#define LCD_MODE_INC 0b00000110
// Set mode: Increment + Shift
#define LCD_MODE_INC_SHIFT 0b00000111
// Set mode: Decrement + NoShift
#define LCD_MODE_DEC 0b00000100
// Set mode: Decrement + Shift
#define LCD_MODE_DEC_SHIFT 0b00000101
// Disable display (data remains untouched)
#define LCD_DISABLE 0b00001000
// Disable cursor
#define LCD_CURSOR_NONE 0b00001100
// Set cursor to still underscore
#define LCD_CURSOR_BAR 0b00001110
// Set cursor to blinking block
#define LCD_CURSOR_BLINK 0b00001101
// Set cursor to both of the above at once
#define LCD_CURSOR_BOTH (LCD_CURSOR_BAR | LCD_CURSOR_BLINK)
// Move cursor
#define LCD_MOVE_LEFT 0b00010000
#define LCD_MOVE_RIGHT 0b00010100
// Shift display
#define LCD_SHIFT_LEFT 0b00011000
#define LCD_SHIFT_RIGHT 0b00011100
// Set iface to 5x7 font, 1-line
#define LCD_IFACE_4BIT_1LINE 0b00100000
#define LCD_IFACE_8BIT_1LINE 0b00110000
// Set iface to 5x7 font, 2-line
#define LCD_IFACE_4BIT_2LINE 0b00101000
#define LCD_IFACE_8BIT_2LINE 0b00111000
// 0 W, 1 R
static bool lcd_mode;
static struct {
uint8_t x;
uint8_t y;
} lcd_pos;
static enum {
TEXT = 0,
CG = 1
} lcd_addrtype;
static uint8_t lcd_old_cursor = CURSOR_NONE;
static bool lcd_enabled = false;
/** Initialize the display */
void lcd_init()
{
// configure pins as output
as_output(LCD_E);
as_output(LCD_RW);
as_output(LCD_RS);
lcd_mode = 1; // force data pins to output
_lcd_mode_w();
// Magic sequence to invoke Cthulhu (or enter 4-bit mode)
_delay_ms(16);
_lcd_write_nibble(0b0011);
_lcd_clk();
_delay_ms(5);
_lcd_clk();
_delay_ms(5);
_lcd_clk();
_delay_ms(5);
_lcd_write_nibble(0b0010);
_lcd_clk();
_delay_us(100);
// Configure the display
lcd_command(LCD_IFACE_4BIT_2LINE);
lcd_command(LCD_DISABLE);
lcd_command(LCD_CLEAR);
lcd_command(LCD_MODE_INC);
// mark as enabled
lcd_enable();
lcd_pos.x = 0;
lcd_pos.y = 0;
lcd_addrtype = TEXT;
}
/** Send a pulse on the ENABLE line */
void _lcd_clk()
{
pin_high(LCD_E);
_delay_ns(450);
pin_low(LCD_E);
_delay_ns(450);
}
/** Enter READ mode */
void _lcd_mode_r()
{
if (lcd_mode == 1) { return; } // already in R mode
pin_high(LCD_RW);
as_input_pu(LCD_D7);
as_input_pu(LCD_D6);
as_input_pu(LCD_D5);
as_input_pu(LCD_D4);
lcd_mode = 1;
}
/** Enter WRITE mode */
void _lcd_mode_w()
{
if (lcd_mode == 0) { return; } // already in W mode
pin_low(LCD_RW);
as_output(LCD_D7);
as_output(LCD_D6);
as_output(LCD_D5);
as_output(LCD_D4);
lcd_mode = 0;
}
/** Read a byte */
uint8_t _lcd_read_byte()
{
_lcd_mode_r();
uint8_t res = 0;
_lcd_clk();
res = (get_pin(LCD_D7) << 7) | (get_pin(LCD_D6) << 6) | (get_pin(LCD_D5) << 5) | (get_pin(LCD_D4) << 4);
_lcd_clk();
res |= (get_pin(LCD_D7) << 3) | (get_pin(LCD_D6) << 2) | (get_pin(LCD_D5) << 1) | (get_pin(LCD_D4) << 0);
return res;
}
/** Write an instruction byte */
void lcd_command(uint8_t bb)
{
_lcd_wait_bf();
pin_low(LCD_RS); // select instruction register
_lcd_write_byte(bb); // send instruction byte
}
/** Write a data byte */
void lcd_write(uint8_t bb)
{
if (lcd_addrtype == TEXT) {
if (bb == '\r') {
// CR
lcd_pos.x = 0;
lcd_xy(lcd_pos.x, lcd_pos.y);
return;
}
if (bb == '\n') {
// LF
lcd_pos.y++;
lcd_xy(lcd_pos.x, lcd_pos.y);
return;
}
lcd_pos.x++;
}
_lcd_wait_bf();
pin_high(LCD_RS); // select data register
_lcd_write_byte(bb); // send data byte
}
/** Read BF & Address */
uint8_t lcd_read_bf_addr()
{
pin_low(LCD_RS);
return _lcd_read_byte();
}
/** Read CGRAM or DDRAM */
uint8_t lcd_read()
{
if (lcd_addrtype == TEXT) { lcd_pos.x++; }
pin_high(LCD_RS);
return _lcd_read_byte();
}
/** Write a byte using the 4-bit interface */
void _lcd_write_byte(uint8_t bb)
{
_lcd_mode_w(); // enter W mode
_lcd_write_high(bb);
_lcd_clk();
_lcd_write_low(bb);
_lcd_clk();
}
/** Wait until the device is ready */
void _lcd_wait_bf()
{
uint8_t d = 0;
while (d++ < 200 && lcd_read_bf_addr() & (1 << 7))
_delay_us(1);
}
/** Send a string to LCD */
void lcd_puts(const char *str_p)
{
char c;
while ((c = *str_p++)) {
lcd_putc(c);
}
}
/** Send a string to LCD, N characters long */
void lcd_putsn(const char *str_p, size_t len)
{
while(len--) {
lcd_putc(*str_p++);
}
}
/** Sedn a char to LCD */
void lcd_putc(const char c)
{
lcd_write(c);
}
/** Set cursor position */
void lcd_xy(const uint8_t x, const uint8_t y)
{
lcd_pos.x = x;
lcd_pos.y = y;
lcd_addr(LCD_ROW_ADDR[y] + (x));
}
/** Set LCD cursor. If not enabled, only remember it. */
void lcd_set_cursor(uint8_t type)
{
lcd_old_cursor = (type & CURSOR_BOTH);
if (lcd_enabled) { lcd_command(LCD_CURSOR_NONE | lcd_old_cursor); }
}
/** Display display (preserving cursor) */
void lcd_disable()
{
lcd_command(LCD_DISABLE);
lcd_enabled = false;
}
/** Enable display (restoring cursor) */
void lcd_enable()
{
lcd_enabled = true;
lcd_set_cursor(lcd_old_cursor);
}
/** Go home */
void lcd_home()
{
lcd_command(LCD_HOME);
lcd_pos.x = 0;
lcd_pos.y = 0;
lcd_addrtype = TEXT;
}
/** Clear the screen */
void lcd_clear()
{
lcd_command(LCD_CLEAR);
lcd_pos.x = 0;
lcd_pos.y = 0;
lcd_addrtype = TEXT;
_delay_ms(1); // it tends to lose the first character otherwise!
}
/** Define a glyph */
void lcd_glyph(const uint8_t index, const uint8_t *array)
{
lcd_addr_cg(index * 8);
for (uint8_t i = 0; i < 8; ++i) {
lcd_write(array[i]);
}
// restore previous position
lcd_xy(lcd_pos.x, lcd_pos.y);
lcd_addrtype = TEXT;
}
/** Set address in CGRAM */
void lcd_addr_cg(const uint8_t acg)
{
lcd_addrtype = CG;
lcd_command(0b01000000 | ((acg) & 0b00111111));
}
/** Set address in DDRAM */
void lcd_addr(const uint8_t add)
{
lcd_addrtype = TEXT;
lcd_command(0b10000000 | ((add) & 0b01111111));
}