diff options
-rw-r--r-- | lcd-async.c | 391 | ||||
-rw-r--r-- | lcd-async.h | 32 |
2 files changed, 423 insertions, 0 deletions
diff --git a/lcd-async.c b/lcd-async.c new file mode 100644 index 0000000..ab6f0c2 --- /dev/null +++ b/lcd-async.c @@ -0,0 +1,391 @@ +#include "LiquidCrystal.h" +#include "nrf_delay.h" +#include "nrf_error.h" +#include "nrf_gpio.h" +#include "app_timer.h" + +#include <string.h> +#include <inttypes.h> + +/* + * Arduino | LCD | nrf51 + * D4 | DB4 | P0.16 + * D5 | DB5 | P0.17 + * D6 | DB6 | P0.18 + * D7 | DB7 | P0.19 + * D8 | RS | P0.20, register select. 0=instruction (write), 0=busy (read), 1=data + * D9 | EN | P0.23, clock, data is clocked in/out on falling edge + * D10 | DB4 | P0.24 + */ + +enum liquid_crystal_cmd { + LIQUID_CRYSTAL_CMD_CLEAR = 0x01, + LIQUID_CRYSTAL_CMD_RETURN_HOME = 0x02, + LIQUID_CRYSTAL_CMD_ENTRY_MODE_SET = 0x04, + LIQUID_CRYSTAL_CMD_DISPLAY = 0x08, + LIQUID_CRYSTAL_CMD_SHIFT = 0x10, + LIQUID_CRYSTAL_CMD_FUNCTION_SET = 0x20, + LIQUID_CRYSTAL_CMD_SET_CGRAM_ADDDRESS = 0x40, + LIQUID_CRYSTAL_CMD_SET_DDRAM_ADDDRESS = 0x80, + + LIQUID_CRYSTAL_CMD_DELAY = 0x11, + LIQUID_CRYSTAL_CMD_INIT_1 = 0x12, + LIQUID_CRYSTAL_CMD_INIT_2 = 0x13, + LIQUID_CRYSTAL_CMD_INIT_3 = 0x14, +}; + +static app_timer_id_t timer_id; + +static struct { + uint8_t pin_db4; + uint8_t pin_db5; + uint8_t pin_db6; + uint8_t pin_db7; + uint8_t pin_rs; + uint8_t pin_en; +} data = { .pin_db4 = 16, .pin_db5 = 17, .pin_db6 = 18, .pin_db7 = 19, .pin_rs = + 20, .pin_en = 23, }; + +static void write_4(uint8_t value) { + nrf_gpio_pin_write(data.pin_db4, value & 0x01); + nrf_gpio_pin_write(data.pin_db5, value & 0x02); + nrf_gpio_pin_write(data.pin_db6, value & 0x04); + nrf_gpio_pin_write(data.pin_db7, value & 0x08); + + nrf_delay_us(10); + nrf_gpio_pin_set(data.pin_en); + nrf_delay_us(10); // 450ns is supposed to be sufficient + nrf_gpio_pin_clear(data.pin_en); +} + +static void write_value(uint8_t value, bool is_data, uint32_t delay) { + nrf_gpio_pin_write(data.pin_rs, is_data); + + write_4(value >> 4); + write_4(value); + + // something is going too fast. have to investigate. +// nrf_delay_us(delay); + nrf_delay_ms(delay); +} + +uint32_t liquid_crystal_write_char(char chr) { + write_value(chr, true, 50); + return NRF_SUCCESS; +} + +uint32_t liquid_crystal_write_string(char *chr) { + for (; *chr != '\0'; chr++) { + liquid_crystal_write_char(*chr); + } + return NRF_SUCCESS; +} + +uint32_t liquid_crystal_write_string_len(char *chr, size_t max_len) { + for (; *chr != '\0' && max_len > 0; chr++, max_len--) { + liquid_crystal_write_char(*chr); + } + return NRF_SUCCESS; +} + +uint32_t liquid_crystal_clear() { + // The documentation doesn't specify a value, but everything else is 37us + write_value(LIQUID_CRYSTAL_CMD_CLEAR, false, 50); + return NRF_SUCCESS; +} + +uint32_t liquid_crystal_return_home() { + write_value(LIQUID_CRYSTAL_CMD_RETURN_HOME, false, 2000); + return NRF_SUCCESS; +} + +static struct { + union { + uint8_t value; + struct { + bool shift :1; + bool increment :1; + int :6; + } fields __attribute__((packed)); + }; +}__attribute__((packed)) entry_mode_state; + +uint32_t liquid_crystal_entry_mode_set(bool increment, bool shift) { + /* + uint8_t value = LIQUID_CRYSTAL_CMD_ENTRY_MODE_SET; + + value |= increment ? 0x02 : 0x00; + value |= shift ? 0x01 : 0x00; + + write_value(value, false); + */ + + entry_mode_state.fields.increment = increment; + entry_mode_state.fields.shift = shift; + write_value(entry_mode_state.value, false, 50); + + return NRF_SUCCESS; +} + +static struct { + union { + uint8_t value; + struct { + bool blink :1; + bool cursor_on :1; + bool display_on :1; + int :5; + } fields __attribute__((packed)); + }; +}__attribute__((packed)) display_state; + +uint32_t liquid_crystal_display(bool display_on, bool cursor_on, bool blink) { + display_state.fields.display_on = display_on; + display_state.fields.cursor_on = cursor_on; + display_state.fields.blink = blink; + + write_value(display_state.value, false, 50); + + return NRF_SUCCESS; +} + +static struct { + union { + uint8_t value; + struct { + int :2; + bool many_dots :1; + bool two_line :1; + bool data_length :1; + int :3; + } fields __attribute__((packed)); + }; +}__attribute__((packed)) function_set_state; + +struct lcd_task { + struct lcd_task* next; + + enum liquid_crystal_cmd cmd; + uint32_t delay; +}; + +struct lcd_task lcd_tasks[10]; +struct lcd_task* next_lcd_task = NULL; +uint32_t ready_at_tick; + +static inline struct lcd_task* alloc_task(enum liquid_crystal_cmd cmd, uint32_t delay) { + for (int i = 0; i < sizeof(lcd_tasks); i++) { + struct lcd_task *t = &lcd_tasks[i]; + if (t->next == t) { + t->cmd = cmd; + t->delay = delay; + return t; + } + } + + return NULL; +} + +static inline struct lcd_task* pop_task() { + struct lcd_task* task = next_lcd_task; + if (task == NULL) { + return NULL; + } + + next_lcd_task = task->next; + task->next = NULL; + + return task; +} + +void add_task(struct lcd_task *task) { + if (next_lcd_task == NULL) { + next_lcd_task = task; + return; + } + + struct lcd_task *t = next_lcd_task; + + while(t->next != t) { + t = t->next; + } + + t->next = task; +} + +static uint32_t execute_task(); + +static uint32_t schedule_next() { + if (next_lcd_task == NULL) { + return NRF_SUCCESS; + } + + uint32_t now; + + uint32_t err = app_timer_cnt_get(&now); + if (err) { + return err; + } + + uint32_t time_until_ready = ready_at_tick - now; + + if (time_until_ready <= 0) { + return execute_task(); + } else { + ready_at_tick = now + next_lcd_task->delay; + return app_timer_start(timer_id, time_until_ready, NULL); + } +} + +static uint32_t execute_task() { + struct lcd_task *const task = pop_task(), + *new_task = NULL; + + if (task == NULL) { + return NRF_SUCCESS; + } + + switch(task->cmd) { + case LIQUID_CRYSTAL_CMD_DELAY: + // noop + break; + case LIQUID_CRYSTAL_CMD_INIT_1: + nrf_gpio_pin_clear(data.pin_rs); + nrf_gpio_pin_clear(data.pin_en); + + new_task = alloc_task(LIQUID_CRYSTAL_CMD_INIT_2, 50); + if (new_task == NULL) { + return NRF_ERROR_NO_MEM; + } + break; + case LIQUID_CRYSTAL_CMD_INIT_2: + new_task = alloc_task(LIQUID_CRYSTAL_CMD_INIT_3, 50); + if (new_task == NULL) { + return NRF_ERROR_NO_MEM; + } + break; + case LIQUID_CRYSTAL_CMD_INIT_3: + break; + default: + return NRF_ERROR_INTERNAL; + } + + if (new_task != NULL) { + add_task(new_task); + } + + return schedule_next(); +} + +static void timeout_handler() { + uint32_t err = execute_task(); + + if (err != NRF_SUCCESS) { + printf("lcd: error executing last task: %" PRIu32 "\r\n", err); + } +} + +uint32_t liquid_crystal_init(bool data_length, bool two_line, bool many_dots) { + for(int i = 0; i < sizeof(lcd_tasks); i++) { + lcd_tasks[i].next = &lcd_tasks[i]; + } + ready_at_tick = 0; + + nrf_gpio_cfg_output(data.pin_db4); + nrf_gpio_cfg_output(data.pin_db5); + nrf_gpio_cfg_output(data.pin_db6); + nrf_gpio_cfg_output(data.pin_db7); + nrf_gpio_cfg_output(data.pin_rs); + nrf_gpio_cfg_output(data.pin_en); + + uint32_t err = app_timer_create(&timer_id, APP_TIMER_MODE_SINGLE_SHOT, timeout_handler); + if (err) { + return err; + } + + display_state.value = LIQUID_CRYSTAL_CMD_DISPLAY; + entry_mode_state.value = LIQUID_CRYSTAL_CMD_ENTRY_MODE_SET; + function_set_state.value = LIQUID_CRYSTAL_CMD_FUNCTION_SET; + + function_set_state.fields.data_length = data_length; + function_set_state.fields.two_line = two_line; + function_set_state.fields.many_dots = many_dots; + +// for (int i = 0; i < 3; i++) { +// nrf_gpio_pin_set(data.pin_en); +// nrf_delay_ms(100); +// nrf_gpio_pin_clear(data.pin_en); +// nrf_delay_ms(100); +// } + + // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION! + // according to datasheet, we need at least 40ms after power rises above 2.7V + // before sending commands. Arduino can turn on way before 4.5V so we'll wait 50 + nrf_delay_ms(50); + + nrf_gpio_pin_clear(data.pin_rs); + nrf_gpio_pin_clear(data.pin_en); + +// struct lcd_task *task = alloc_task(LIQUID_CRYSTAL_CMD_INIT_1, 50000); +// add_task(task); + + write_4(0x03); + nrf_delay_us(4500); + + write_4(0x03); + nrf_delay_us(4500); + + write_4(0x03); + nrf_delay_us(150); + + write_4(0x02); + + write_value(function_set_state.value, false, 50); + + return schedule_next(); +} + +uint32_t liquid_crystal_reset() { +// liquid_crystal_clear(); +// liquid_crystal_return_home(); +// liquid_crystal_entry_mode_set(true, false); + + struct lcd_task *task = alloc_task(LIQUID_CRYSTAL_CMD_CLEAR, 50); + if (task == NULL) { + return NRF_ERROR_NO_MEM; + } + add_task(task); + + task = alloc_task(LIQUID_CRYSTAL_CMD_RETURN_HOME, 2000); + if (task == NULL) { + return NRF_ERROR_NO_MEM; + } + add_task(task); + + task = alloc_task(LIQUID_CRYSTAL_CMD_ENTRY_MODE_SET, 50); + if (task == NULL) { + return NRF_ERROR_NO_MEM; + } + + add_task(task); + + return NRF_SUCCESS; +} + +uint32_t liquid_crystal_set_cursor(int column, int row) { + uint8_t value = LIQUID_CRYSTAL_CMD_SET_DDRAM_ADDDRESS; + + if (row == 1) { + value += 0x40; + } else if (row == 2) { + value += 14; + } else if (row == 3) { + value += 54; + } + + value += column; + + write_value(value, false, 50); + + return NRF_SUCCESS; +} diff --git a/lcd-async.h b/lcd-async.h new file mode 100644 index 0000000..b0c4172 --- /dev/null +++ b/lcd-async.h @@ -0,0 +1,32 @@ +/* + * LiquidCrystal + */ + +#ifndef LIQUIDCRYSTAL_H_ +#define LIQUIDCRYSTAL_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> + +uint32_t liquid_crystal_init(bool data_length, bool two_line, bool many_dots); + +uint32_t liquid_crystal_reset(); + +uint32_t liquid_crystal_clear(); + +uint32_t liquid_crystal_return_home(); + +uint32_t liquid_crystal_entry_mode_set(bool increment, bool shift); + +uint32_t liquid_crystal_display(bool display_on, bool cursor_on, bool blink); + +uint32_t liquid_crystal_set_cursor(int column, int row); + +uint32_t liquid_crystal_write_char(char chr); + +uint32_t liquid_crystal_write_string(char *chr); + +uint32_t liquid_crystal_write_string_len(char *chr, size_t max_len); + +#endif |