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.
		
		
		
		
		
			
		
			
				
					
					
						
							396 lines
						
					
					
						
							7.6 KiB
						
					
					
				
			
		
		
	
	
							396 lines
						
					
					
						
							7.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};
 | |
| 
 | |
| // 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
 | |
| bool _lcd_mode;
 | |
| 
 | |
| struct {
 | |
|   uint8_t x;
 | |
|   uint8_t y;
 | |
| } _pos;
 | |
| 
 | |
| enum {
 | |
|   TEXT = 0,
 | |
|   CG = 1
 | |
| } _addrtype;
 | |
| 
 | |
| 
 | |
| /** 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();
 | |
| 
 | |
|     _pos.x = 0;
 | |
|     _pos.y = 0;
 | |
|     _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 (_addrtype == TEXT) {
 | |
|         if (bb == '\r') {
 | |
|             // CR
 | |
|             _pos.x = 0;
 | |
|             lcd_xy(_pos.x, _pos.y);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (bb == '\n') {
 | |
|             // LF
 | |
|             _pos.y++;
 | |
|             lcd_xy(_pos.x, _pos.y);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         _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 (_addrtype == TEXT) { _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(char *str_p)
 | |
| {
 | |
|     char c;
 | |
|     while ((c = *str_p++)) {
 | |
|         lcd_putc(c);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /** 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)
 | |
| {
 | |
|     _pos.x = x;
 | |
|     _pos.y = y;
 | |
|     lcd_addr(LCD_ROW_ADDR[y] + (x));
 | |
| }
 | |
| 
 | |
| 
 | |
| uint8_t _lcd_old_cursor = CURSOR_NONE;
 | |
| bool _lcd_enabled = false;
 | |
| 
 | |
| /** Set LCD cursor. If not enabled, only remember it. */
 | |
| void lcd_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_cursor(_lcd_old_cursor);
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Go home */
 | |
| void lcd_home()
 | |
| {
 | |
|     lcd_command(LCD_HOME);
 | |
|     _pos.x = 0;
 | |
|     _pos.y = 0;
 | |
|     _addrtype = TEXT;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Clear the screen */
 | |
| void lcd_clear()
 | |
| {
 | |
|     lcd_command(LCD_CLEAR);
 | |
|     _pos.x = 0;
 | |
|     _pos.y = 0;
 | |
|     _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(_pos.x, _pos.y);
 | |
|     _addrtype = TEXT;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Set address in CGRAM */
 | |
| void lcd_addr_cg(const uint8_t acg)
 | |
| {
 | |
|     _addrtype = CG;
 | |
|     lcd_command(0b01000000 | ((acg) & 0b00111111));
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Set address in DDRAM */
 | |
| void lcd_addr(const uint8_t add)
 | |
| {
 | |
|     _addrtype = TEXT;
 | |
|     lcd_command(0b10000000 | ((add) & 0b01111111));
 | |
| }
 | |
| 
 |