Compare commits
3
Commits
2c61e3576f
...
f47299b939
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f47299b939 | ||
|
|
51faa0fe59 | ||
|
|
a19accf6a7 |
@@ -0,0 +1,4 @@
|
|||||||
|
idf_component_register(SRCS "app_ui.c"
|
||||||
|
INCLUDE_DIRS "include" "."
|
||||||
|
REQUIRES ui_cmd app_machine app_process
|
||||||
|
PRIV_REQUIRES hal_display)
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
#include "app_ui.h"
|
||||||
|
#include "screens.h"
|
||||||
|
#include "app_machine.h"
|
||||||
|
#include "app_process.h"
|
||||||
|
#include "hal_display.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static ui_evt_t s_last;
|
||||||
|
static bool s_have;
|
||||||
|
static bool s_dirty;
|
||||||
|
|
||||||
|
static void fmt_mmss(char *buf, size_t n, uint32_t total_s)
|
||||||
|
{
|
||||||
|
unsigned m = (unsigned)(total_s / 60u);
|
||||||
|
unsigned s = (unsigned)(total_s % 60u);
|
||||||
|
if (m > 99u) {
|
||||||
|
m = 99u;
|
||||||
|
}
|
||||||
|
snprintf(buf, n, "%02u:%02u", m, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_temp_row3(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
/* row3 col13: thermo + tt.tC or -- */
|
||||||
|
char tbuf[8];
|
||||||
|
if (ev != NULL && ev->temp_ok) {
|
||||||
|
snprintf(tbuf, sizeof(tbuf), "%4.1fC", (double)ev->temp_c);
|
||||||
|
hal_display_glyph_thermo(13, 3);
|
||||||
|
hal_display_text(14, 3, tbuf);
|
||||||
|
} else {
|
||||||
|
hal_display_text(13, 3, " --");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *step_name(uint8_t proc, uint8_t step)
|
||||||
|
{
|
||||||
|
const process_def_t *p = app_process_get(proc);
|
||||||
|
if (p == NULL || step >= p->step_count) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return p->steps[step].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_program_select(void)
|
||||||
|
{
|
||||||
|
hal_display_clear();
|
||||||
|
hal_display_text(0, 0, "Select Programme:");
|
||||||
|
hal_display_text(0, 1, "1. C41 4. ECN-2");
|
||||||
|
hal_display_text(0, 2, "2. E6 5. Custom");
|
||||||
|
hal_display_text(0, 3, "3. B&W 6. B&W Rev");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_step_select(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
char line[21];
|
||||||
|
char mm[8];
|
||||||
|
uint16_t ts = ev->time_s;
|
||||||
|
fmt_mmss(mm, sizeof(mm), ts);
|
||||||
|
int pref = (int)(ev->temp_pref_c + (ev->temp_pref_c >= 0 ? 0.5f : -0.5f));
|
||||||
|
hal_display_clear();
|
||||||
|
hal_display_text(0, 0, "Step Time Temp");
|
||||||
|
snprintf(line, sizeof(line), "%-9.9s %s %2dC", step_name(ev->process_id, ev->step_index), mm, pref);
|
||||||
|
hal_display_text(0, 1, line);
|
||||||
|
hal_display_text(0, 2, "Scroll / Esc / Ent");
|
||||||
|
draw_temp_row3(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_armed(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
char line[21];
|
||||||
|
char mm[8];
|
||||||
|
fmt_mmss(mm, sizeof(mm), ev->time_s);
|
||||||
|
int pref = (int)(ev->temp_pref_c + (ev->temp_pref_c >= 0 ? 0.5f : -0.5f));
|
||||||
|
hal_display_clear();
|
||||||
|
snprintf(line, sizeof(line), "%-10.10s %s %2dC", step_name(ev->process_id, ev->step_index), mm, pref);
|
||||||
|
hal_display_text(0, 0, line);
|
||||||
|
hal_display_text(0, 1, "Ent:start Esc:quit");
|
||||||
|
draw_temp_row3(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_running(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
char line[21];
|
||||||
|
char mm[8];
|
||||||
|
uint32_t rem_s = (ev->remaining_ms + 999u) / 1000u;
|
||||||
|
fmt_mmss(mm, sizeof(mm), rem_s);
|
||||||
|
hal_display_clear();
|
||||||
|
snprintf(line, sizeof(line), "%-20.20s", step_name(ev->process_id, ev->step_index));
|
||||||
|
hal_display_text(0, 0, line);
|
||||||
|
snprintf(line, sizeof(line), "Remaining: %s", mm);
|
||||||
|
hal_display_text(0, 1, line);
|
||||||
|
draw_temp_row3(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_stopped(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
char line[21];
|
||||||
|
char mm[8];
|
||||||
|
uint32_t rem_s = (ev->remaining_ms + 999u) / 1000u;
|
||||||
|
fmt_mmss(mm, sizeof(mm), rem_s);
|
||||||
|
hal_display_clear();
|
||||||
|
snprintf(line, sizeof(line), "%-10.10s %s", step_name(ev->process_id, ev->step_index), mm);
|
||||||
|
hal_display_text(0, 0, line);
|
||||||
|
hal_display_text(0, 1, "E:resume X:back");
|
||||||
|
draw_temp_row3(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_complete(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
char line[21];
|
||||||
|
hal_display_clear();
|
||||||
|
snprintf(line, sizeof(line), "%-10.10s Done", step_name(ev->process_id, ev->step_index));
|
||||||
|
hal_display_text(0, 0, line);
|
||||||
|
hal_display_text(0, 1, "E:next X:back");
|
||||||
|
draw_temp_row3(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_ui_init(void)
|
||||||
|
{
|
||||||
|
memset(&s_last, 0, sizeof(s_last));
|
||||||
|
s_have = false;
|
||||||
|
s_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_ui_on_event(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
if (ev == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ev->id == EVT_TEMP_UPDATED && s_have) {
|
||||||
|
s_last.temp_c = ev->temp_c;
|
||||||
|
s_last.temp_ok = ev->temp_ok;
|
||||||
|
s_dirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ev->id == EVT_STEP_PROGRESS && s_have) {
|
||||||
|
s_last.remaining_ms = ev->remaining_ms;
|
||||||
|
s_last.id = ev->id;
|
||||||
|
s_dirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_last = *ev;
|
||||||
|
s_have = true;
|
||||||
|
s_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool app_ui_is_dirty(void)
|
||||||
|
{
|
||||||
|
return s_dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_ui_draw(void)
|
||||||
|
{
|
||||||
|
machine_state_t st = app_machine_state();
|
||||||
|
ui_evt_t ev = s_last;
|
||||||
|
if (!s_have) {
|
||||||
|
ev.temp_ok = false;
|
||||||
|
}
|
||||||
|
ev.remaining_ms = app_machine_remaining_ms();
|
||||||
|
ev.process_id = app_machine_process_id();
|
||||||
|
ev.step_index = app_machine_step_index();
|
||||||
|
if (app_process_get(ev.process_id) != NULL) {
|
||||||
|
ev.time_s = app_process_time_s(ev.process_id, ev.step_index);
|
||||||
|
const process_def_t *p = app_process_get(ev.process_id);
|
||||||
|
if (p != NULL && ev.step_index < p->step_count) {
|
||||||
|
ev.temp_pref_c = p->steps[ev.step_index].temp_pref_c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (st) {
|
||||||
|
case ST_IDLE:
|
||||||
|
draw_program_select();
|
||||||
|
break;
|
||||||
|
case ST_STEP_SELECT:
|
||||||
|
draw_step_select(&ev);
|
||||||
|
break;
|
||||||
|
case ST_ARMED:
|
||||||
|
draw_armed(&ev);
|
||||||
|
break;
|
||||||
|
case ST_RUNNING:
|
||||||
|
draw_running(&ev);
|
||||||
|
break;
|
||||||
|
case ST_STOPPED:
|
||||||
|
draw_stopped(&ev);
|
||||||
|
break;
|
||||||
|
case ST_COMPLETE:
|
||||||
|
draw_complete(&ev);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
draw_program_select();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hal_display_flush();
|
||||||
|
s_dirty = false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_cmd.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void app_ui_init(void);
|
||||||
|
void app_ui_on_event(const ui_evt_t *ev);
|
||||||
|
void app_ui_draw(void);
|
||||||
|
bool app_ui_is_dirty(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/* Screen ids used by app_ui. Layouts are 20x4 WROOM strings. */
|
||||||
|
typedef enum {
|
||||||
|
UI_SCREEN_PROGRAM_SELECT = 0,
|
||||||
|
UI_SCREEN_STEP_SELECT,
|
||||||
|
UI_SCREEN_ARMED,
|
||||||
|
UI_SCREEN_RUNNING,
|
||||||
|
UI_SCREEN_STOPPED,
|
||||||
|
UI_SCREEN_COMPLETE
|
||||||
|
} app_ui_screen_t;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
set(srcs)
|
||||||
|
set(priv_inc)
|
||||||
|
set(priv_req driver)
|
||||||
|
|
||||||
|
if(IDF_TARGET STREQUAL "esp32")
|
||||||
|
list(APPEND srcs
|
||||||
|
hal_display_lcd2004.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../third_party/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp
|
||||||
|
)
|
||||||
|
list(APPEND priv_inc
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../third_party/LiquidCrystal_I2C/include
|
||||||
|
)
|
||||||
|
list(APPEND priv_req board_wroom espressif__arduino-esp32)
|
||||||
|
else()
|
||||||
|
list(APPEND srcs stub/hal_display.c)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${srcs}
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
PRIV_INCLUDE_DIRS ${priv_inc}
|
||||||
|
REQUIRES driver
|
||||||
|
PRIV_REQUIRES ${priv_req})
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
#include "hal_display.h"
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <LiquidCrystal_I2C.h>
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
#define LCD_ADDR 0x27
|
||||||
|
#define LCD_COLS 20
|
||||||
|
#define LCD_ROWS 4
|
||||||
|
|
||||||
|
static const char *TAG = "display";
|
||||||
|
|
||||||
|
static LiquidCrystal_I2C s_lcd(LCD_ADDR, LCD_COLS, LCD_ROWS);
|
||||||
|
static bool s_inited;
|
||||||
|
|
||||||
|
static const uint8_t s_thermo[8] = {
|
||||||
|
0b00100,
|
||||||
|
0b01100,
|
||||||
|
0b00100,
|
||||||
|
0b01100,
|
||||||
|
0b00100,
|
||||||
|
0b01110,
|
||||||
|
0b01110,
|
||||||
|
0b01110,
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "C" void hal_display_init(void)
|
||||||
|
{
|
||||||
|
if (s_inited) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Wire.begin(21, 22);
|
||||||
|
s_lcd.init();
|
||||||
|
s_lcd.backlight();
|
||||||
|
s_lcd.createChar(0, const_cast<uint8_t *>(s_thermo));
|
||||||
|
s_lcd.clear();
|
||||||
|
s_inited = true;
|
||||||
|
ESP_LOGI(TAG, "init LCD 20x4 addr=0x27 sda=21 scl=22");
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void hal_display_clear(void)
|
||||||
|
{
|
||||||
|
if (!s_inited) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_lcd.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void hal_display_text(int col, int row, const char *s)
|
||||||
|
{
|
||||||
|
if (!s_inited || s == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (row < 0 || row >= LCD_ROWS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (col < 0) {
|
||||||
|
col = 0;
|
||||||
|
}
|
||||||
|
if (col >= LCD_COLS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_lcd.setCursor((uint8_t)col, (uint8_t)row);
|
||||||
|
int room = LCD_COLS - col;
|
||||||
|
char buf[21];
|
||||||
|
int n = 0;
|
||||||
|
while (s[n] != '\0' && n < room) {
|
||||||
|
buf[n] = s[n];
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
buf[n] = '\0';
|
||||||
|
s_lcd.print(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void hal_display_glyph_thermo(int col, int row)
|
||||||
|
{
|
||||||
|
if (!s_inited) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (row < 0 || row >= LCD_ROWS || col < 0 || col >= LCD_COLS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_lcd.setCursor((uint8_t)col, (uint8_t)row);
|
||||||
|
s_lcd.write((uint8_t)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void hal_display_flush(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void hal_display_init(void);
|
||||||
|
void hal_display_clear(void);
|
||||||
|
void hal_display_text(int col, int row, const char *s); /* clip to 20 cols */
|
||||||
|
void hal_display_glyph_thermo(int col, int row);
|
||||||
|
void hal_display_flush(void); /* no-op on 2004 */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#include "hal_display.h"
|
||||||
|
|
||||||
|
void hal_display_init(void) {}
|
||||||
|
void hal_display_clear(void) {}
|
||||||
|
void hal_display_text(int col, int row, const char *s)
|
||||||
|
{
|
||||||
|
(void)col;
|
||||||
|
(void)row;
|
||||||
|
(void)s;
|
||||||
|
}
|
||||||
|
void hal_display_glyph_thermo(int col, int row)
|
||||||
|
{
|
||||||
|
(void)col;
|
||||||
|
(void)row;
|
||||||
|
}
|
||||||
|
void hal_display_flush(void) {}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
set(srcs)
|
||||||
|
set(priv_inc)
|
||||||
|
set(priv_req driver)
|
||||||
|
|
||||||
|
if(IDF_TARGET STREQUAL "esp32")
|
||||||
|
list(APPEND srcs
|
||||||
|
hal_input_keypad.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../third_party/Keypad/Keypad.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../third_party/Keypad/Key.cpp
|
||||||
|
)
|
||||||
|
list(APPEND priv_inc
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../third_party/Keypad/include
|
||||||
|
)
|
||||||
|
list(APPEND priv_req board_wroom espressif__arduino-esp32)
|
||||||
|
else()
|
||||||
|
list(APPEND srcs stub/hal_input.c)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${srcs}
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
PRIV_INCLUDE_DIRS ${priv_inc}
|
||||||
|
REQUIRES driver
|
||||||
|
PRIV_REQUIRES ${priv_req})
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#include "hal_input.h"
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Keypad.h>
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
static const char *TAG = "input";
|
||||||
|
|
||||||
|
static const byte ROWS = 5;
|
||||||
|
static const byte COLS = 4;
|
||||||
|
static byte s_rowPins[ROWS] = {19, 18, 5, 17, 16};
|
||||||
|
static byte s_colPins[COLS] = {15, 2, 0, 4};
|
||||||
|
|
||||||
|
static char s_keys[ROWS][COLS] = {
|
||||||
|
{'F', 'E', '#', '*'},
|
||||||
|
{'1', '2', '3', 'U'},
|
||||||
|
{'4', '5', '6', 'D'},
|
||||||
|
{'7', '8', '9', 'X'},
|
||||||
|
{'L', '0', 'R', 'E'}};
|
||||||
|
|
||||||
|
static Keypad s_keypad = Keypad(makeKeymap(s_keys), s_rowPins, s_colPins, ROWS, COLS);
|
||||||
|
static bool s_inited;
|
||||||
|
|
||||||
|
extern "C" void hal_input_init(void)
|
||||||
|
{
|
||||||
|
if (s_inited) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_inited = true;
|
||||||
|
ESP_LOGI(TAG, "init 5x4 keypad map byte-identical to src/config.cpp");
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" bool hal_input_pop_key(ui_raw_key_t *out)
|
||||||
|
{
|
||||||
|
char k = s_keypad.getKey();
|
||||||
|
if (k == NO_KEY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (out != NULL) {
|
||||||
|
out->key = k;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char key;
|
||||||
|
} ui_raw_key_t;
|
||||||
|
|
||||||
|
void hal_input_init(void);
|
||||||
|
bool hal_input_pop_key(ui_raw_key_t *out); /* nonblocking */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#include "hal_input.h"
|
||||||
|
|
||||||
|
void hal_input_init(void) {}
|
||||||
|
|
||||||
|
bool hal_input_pop_key(ui_raw_key_t *out)
|
||||||
|
{
|
||||||
|
(void)out;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
|| @file Key.cpp
|
||||||
|
|| @version 1.0
|
||||||
|
|| @author Mark Stanley
|
||||||
|
|| @contact mstanley@technologist.com
|
||||||
|
||
|
||||||
|
|| @description
|
||||||
|
|| | Key class provides an abstract definition of a key or button
|
||||||
|
|| | and was initially designed to be used in conjunction with a
|
||||||
|
|| | state-machine.
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
|| @license
|
||||||
|
|| | This library is free software; you can redistribute it and/or
|
||||||
|
|| | modify it under the terms of the GNU Lesser General Public
|
||||||
|
|| | License as published by the Free Software Foundation; version
|
||||||
|
|| | 2.1 of the License.
|
||||||
|
|| |
|
||||||
|
|| | This library is distributed in the hope that it will be useful,
|
||||||
|
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
|| | Lesser General Public License for more details.
|
||||||
|
|| |
|
||||||
|
|| | You should have received a copy of the GNU Lesser General Public
|
||||||
|
|| | License along with this library; if not, write to the Free Software
|
||||||
|
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
*/
|
||||||
|
#include <Key.h>
|
||||||
|
|
||||||
|
|
||||||
|
// default constructor
|
||||||
|
Key::Key() {
|
||||||
|
kchar = NO_KEY;
|
||||||
|
kstate = IDLE;
|
||||||
|
stateChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructor
|
||||||
|
Key::Key(char userKeyChar) {
|
||||||
|
kchar = userKeyChar;
|
||||||
|
kcode = -1;
|
||||||
|
kstate = IDLE;
|
||||||
|
stateChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Key::key_update (char userKeyChar, KeyState userState, boolean userStatus) {
|
||||||
|
kchar = userKeyChar;
|
||||||
|
kstate = userState;
|
||||||
|
stateChanged = userStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|| @changelog
|
||||||
|
|| | 1.0 2012-06-04 - Mark Stanley : Initial Release
|
||||||
|
|| #
|
||||||
|
*/
|
||||||
+293
@@ -0,0 +1,293 @@
|
|||||||
|
/*
|
||||||
|
||
|
||||||
|
|| @file Keypad.cpp
|
||||||
|
|| @version 3.1
|
||||||
|
|| @author Mark Stanley, Alexander Brevig
|
||||||
|
|| @contact mstanley@technologist.com, alexanderbrevig@gmail.com
|
||||||
|
||
|
||||||
|
|| @description
|
||||||
|
|| | This library provides a simple interface for using matrix
|
||||||
|
|| | keypads. It supports multiple keypresses while maintaining
|
||||||
|
|| | backwards compatibility with the old single key library.
|
||||||
|
|| | It also supports user selectable pins and definable keymaps.
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
|| @license
|
||||||
|
|| | This library is free software; you can redistribute it and/or
|
||||||
|
|| | modify it under the terms of the GNU Lesser General Public
|
||||||
|
|| | License as published by the Free Software Foundation; version
|
||||||
|
|| | 2.1 of the License.
|
||||||
|
|| |
|
||||||
|
|| | This library is distributed in the hope that it will be useful,
|
||||||
|
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
|| | Lesser General Public License for more details.
|
||||||
|
|| |
|
||||||
|
|| | You should have received a copy of the GNU Lesser General Public
|
||||||
|
|| | License along with this library; if not, write to the Free Software
|
||||||
|
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
*/
|
||||||
|
#include <Keypad.h>
|
||||||
|
|
||||||
|
// <<constructor>> Allows custom keymap, pin configuration, and keypad sizes.
|
||||||
|
Keypad::Keypad(char *userKeymap, byte *row, byte *col, byte numRows, byte numCols) {
|
||||||
|
rowPins = row;
|
||||||
|
columnPins = col;
|
||||||
|
sizeKpd.rows = numRows;
|
||||||
|
sizeKpd.columns = numCols;
|
||||||
|
|
||||||
|
begin(userKeymap);
|
||||||
|
|
||||||
|
setDebounceTime(10);
|
||||||
|
setHoldTime(500);
|
||||||
|
keypadEventListener = 0;
|
||||||
|
|
||||||
|
startTime = 0;
|
||||||
|
single_key = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let the user define a keymap - assume the same row/column count as defined in constructor
|
||||||
|
void Keypad::begin(char *userKeymap) {
|
||||||
|
keymap = userKeymap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a single key only. Retained for backwards compatibility.
|
||||||
|
char Keypad::getKey() {
|
||||||
|
single_key = true;
|
||||||
|
|
||||||
|
if (getKeys() && key[0].stateChanged && (key[0].kstate==PRESSED))
|
||||||
|
return key[0].kchar;
|
||||||
|
|
||||||
|
single_key = false;
|
||||||
|
|
||||||
|
return NO_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the key list.
|
||||||
|
bool Keypad::getKeys() {
|
||||||
|
bool keyActivity = false;
|
||||||
|
|
||||||
|
// Limit how often the keypad is scanned. This makes the loop() run 10 times as fast.
|
||||||
|
if ( (millis()-startTime)>debounceTime ) {
|
||||||
|
scanKeys();
|
||||||
|
keyActivity = updateList();
|
||||||
|
startTime = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private : Hardware scan
|
||||||
|
void Keypad::scanKeys() {
|
||||||
|
// Re-intialize the row pins. Allows sharing these pins with other hardware.
|
||||||
|
for (byte r=0; r<sizeKpd.rows; r++) {
|
||||||
|
pin_mode(rowPins[r],INPUT_PULLUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitMap stores ALL the keys that are being pressed.
|
||||||
|
for (byte c=0; c<sizeKpd.columns; c++) {
|
||||||
|
pin_mode(columnPins[c],OUTPUT);
|
||||||
|
pin_write(columnPins[c], LOW); // Begin column pulse output.
|
||||||
|
for (byte r=0; r<sizeKpd.rows; r++) {
|
||||||
|
bitWrite(bitMap[r], c, !pin_read(rowPins[r])); // keypress is active low so invert to high.
|
||||||
|
}
|
||||||
|
// Set pin to high impedance input. Effectively ends column pulse.
|
||||||
|
pin_write(columnPins[c],HIGH);
|
||||||
|
pin_mode(columnPins[c],INPUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manage the list without rearranging the keys. Returns true if any keys on the list changed state.
|
||||||
|
bool Keypad::updateList() {
|
||||||
|
|
||||||
|
bool anyActivity = false;
|
||||||
|
|
||||||
|
// Delete any IDLE keys
|
||||||
|
for (byte i=0; i<LIST_MAX; i++) {
|
||||||
|
if (key[i].kstate==IDLE) {
|
||||||
|
key[i].kchar = NO_KEY;
|
||||||
|
key[i].kcode = -1;
|
||||||
|
key[i].stateChanged = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new keys to empty slots in the key list.
|
||||||
|
for (byte r=0; r<sizeKpd.rows; r++) {
|
||||||
|
for (byte c=0; c<sizeKpd.columns; c++) {
|
||||||
|
boolean button = bitRead(bitMap[r],c);
|
||||||
|
char keyChar = keymap[r * sizeKpd.columns + c];
|
||||||
|
int keyCode = r * sizeKpd.columns + c;
|
||||||
|
int idx = findInList (keyCode);
|
||||||
|
// Key is already on the list so set its next state.
|
||||||
|
if (idx > -1) {
|
||||||
|
nextKeyState(idx, button);
|
||||||
|
}
|
||||||
|
// Key is NOT on the list so add it.
|
||||||
|
if ((idx == -1) && button) {
|
||||||
|
for (byte i=0; i<LIST_MAX; i++) {
|
||||||
|
if (key[i].kchar==NO_KEY) { // Find an empty slot or don't add key to list.
|
||||||
|
key[i].kchar = keyChar;
|
||||||
|
key[i].kcode = keyCode;
|
||||||
|
key[i].kstate = IDLE; // Keys NOT on the list have an initial state of IDLE.
|
||||||
|
nextKeyState (i, button);
|
||||||
|
break; // Don't fill all the empty slots with the same key.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report if the user changed the state of any key.
|
||||||
|
for (byte i=0; i<LIST_MAX; i++) {
|
||||||
|
if (key[i].stateChanged) anyActivity = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private
|
||||||
|
// This function is a state machine but is also used for debouncing the keys.
|
||||||
|
void Keypad::nextKeyState(byte idx, boolean button) {
|
||||||
|
key[idx].stateChanged = false;
|
||||||
|
|
||||||
|
switch (key[idx].kstate) {
|
||||||
|
case IDLE:
|
||||||
|
if (button==CLOSED) {
|
||||||
|
transitionTo (idx, PRESSED);
|
||||||
|
holdTimer = millis(); } // Get ready for next HOLD state.
|
||||||
|
break;
|
||||||
|
case PRESSED:
|
||||||
|
if ((millis()-holdTimer)>holdTime) // Waiting for a key HOLD...
|
||||||
|
transitionTo (idx, HOLD);
|
||||||
|
else if (button==OPEN) // or for a key to be RELEASED.
|
||||||
|
transitionTo (idx, RELEASED);
|
||||||
|
break;
|
||||||
|
case HOLD:
|
||||||
|
if (button==OPEN)
|
||||||
|
transitionTo (idx, RELEASED);
|
||||||
|
break;
|
||||||
|
case RELEASED:
|
||||||
|
transitionTo (idx, IDLE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New in 2.1
|
||||||
|
bool Keypad::isPressed(char keyChar) {
|
||||||
|
for (byte i=0; i<LIST_MAX; i++) {
|
||||||
|
if ( key[i].kchar == keyChar ) {
|
||||||
|
if ( (key[i].kstate == PRESSED) && key[i].stateChanged )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false; // Not pressed.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search by character for a key in the list of active keys.
|
||||||
|
// Returns -1 if not found or the index into the list of active keys.
|
||||||
|
int Keypad::findInList (char keyChar) {
|
||||||
|
for (byte i=0; i<LIST_MAX; i++) {
|
||||||
|
if (key[i].kchar == keyChar) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search by code for a key in the list of active keys.
|
||||||
|
// Returns -1 if not found or the index into the list of active keys.
|
||||||
|
int Keypad::findInList (int keyCode) {
|
||||||
|
for (byte i=0; i<LIST_MAX; i++) {
|
||||||
|
if (key[i].kcode == keyCode) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New in 2.0
|
||||||
|
char Keypad::waitForKey() {
|
||||||
|
char waitKey = NO_KEY;
|
||||||
|
while( (waitKey = getKey()) == NO_KEY ); // Block everything while waiting for a keypress.
|
||||||
|
return waitKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backwards compatibility function.
|
||||||
|
KeyState Keypad::getState() {
|
||||||
|
return key[0].kstate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The end user can test for any changes in state before deciding
|
||||||
|
// if any variables, etc. needs to be updated in their code.
|
||||||
|
bool Keypad::keyStateChanged() {
|
||||||
|
return key[0].stateChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The number of keys on the key list, key[LIST_MAX], equals the number
|
||||||
|
// of bytes in the key list divided by the number of bytes in a Key object.
|
||||||
|
byte Keypad::numKeys() {
|
||||||
|
return sizeof(key)/sizeof(Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimum debounceTime is 1 mS. Any lower *will* slow down the loop().
|
||||||
|
void Keypad::setDebounceTime(uint debounce) {
|
||||||
|
debounce<1 ? debounceTime=1 : debounceTime=debounce;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keypad::setHoldTime(uint hold) {
|
||||||
|
holdTime = hold;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keypad::addEventListener(void (*listener)(char)){
|
||||||
|
keypadEventListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keypad::transitionTo(byte idx, KeyState nextState) {
|
||||||
|
key[idx].kstate = nextState;
|
||||||
|
key[idx].stateChanged = true;
|
||||||
|
|
||||||
|
// Sketch used the getKey() function.
|
||||||
|
// Calls keypadEventListener only when the first key in slot 0 changes state.
|
||||||
|
if (single_key) {
|
||||||
|
if ( (keypadEventListener!=NULL) && (idx==0) ) {
|
||||||
|
keypadEventListener(key[0].kchar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sketch used the getKeys() function.
|
||||||
|
// Calls keypadEventListener on any key that changes state.
|
||||||
|
else {
|
||||||
|
if (keypadEventListener!=NULL) {
|
||||||
|
keypadEventListener(key[idx].kchar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|| @changelog
|
||||||
|
|| | 3.1 2013-01-15 - Mark Stanley : Fixed missing RELEASED & IDLE status when using a single key.
|
||||||
|
|| | 3.0 2012-07-12 - Mark Stanley : Made library multi-keypress by default. (Backwards compatible)
|
||||||
|
|| | 3.0 2012-07-12 - Mark Stanley : Modified pin functions to support Keypad_I2C
|
||||||
|
|| | 3.0 2012-07-12 - Stanley & Young : Removed static variables. Fix for multiple keypad objects.
|
||||||
|
|| | 3.0 2012-07-12 - Mark Stanley : Fixed bug that caused shorted pins when pressing multiple keys.
|
||||||
|
|| | 2.0 2011-12-29 - Mark Stanley : Added waitForKey().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Added the public function keyStateChanged().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Added the private function scanKeys().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Moved the Finite State Machine into the function getKeyState().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Removed the member variable lastUdate. Not needed after rewrite.
|
||||||
|
|| | 1.8 2011-11-21 - Mark Stanley : Added decision logic to compile WProgram.h or Arduino.h
|
||||||
|
|| | 1.8 2009-07-08 - Alexander Brevig : No longer uses arrays
|
||||||
|
|| | 1.7 2009-06-18 - Alexander Brevig : Every time a state changes the keypadEventListener will trigger, if set.
|
||||||
|
|| | 1.7 2009-06-18 - Alexander Brevig : Added setDebounceTime. setHoldTime specifies the amount of
|
||||||
|
|| | microseconds before a HOLD state triggers
|
||||||
|
|| | 1.7 2009-06-18 - Alexander Brevig : Added transitionTo
|
||||||
|
|| | 1.6 2009-06-15 - Alexander Brevig : Added getState() and state variable
|
||||||
|
|| | 1.5 2009-05-19 - Alexander Brevig : Added setHoldTime()
|
||||||
|
|| | 1.4 2009-05-15 - Alexander Brevig : Added addEventListener
|
||||||
|
|| | 1.3 2009-05-12 - Alexander Brevig : Added lastUdate, in order to do simple debouncing
|
||||||
|
|| | 1.2 2009-05-09 - Alexander Brevig : Changed getKey()
|
||||||
|
|| | 1.1 2009-04-28 - Alexander Brevig : Modified API, and made variables private
|
||||||
|
|| | 1.0 2007-XX-XX - Mark Stanley : Initial Release
|
||||||
|
|| #
|
||||||
|
*/
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
||
|
||||||
|
|| @file Key.h
|
||||||
|
|| @version 1.0
|
||||||
|
|| @author Mark Stanley
|
||||||
|
|| @contact mstanley@technologist.com
|
||||||
|
||
|
||||||
|
|| @description
|
||||||
|
|| | Key class provides an abstract definition of a key or button
|
||||||
|
|| | and was initially designed to be used in conjunction with a
|
||||||
|
|| | state-machine.
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
|| @license
|
||||||
|
|| | This library is free software; you can redistribute it and/or
|
||||||
|
|| | modify it under the terms of the GNU Lesser General Public
|
||||||
|
|| | License as published by the Free Software Foundation; version
|
||||||
|
|| | 2.1 of the License.
|
||||||
|
|| |
|
||||||
|
|| | This library is distributed in the hope that it will be useful,
|
||||||
|
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
|| | Lesser General Public License for more details.
|
||||||
|
|| |
|
||||||
|
|| | You should have received a copy of the GNU Lesser General Public
|
||||||
|
|| | License along with this library; if not, write to the Free Software
|
||||||
|
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef Keypadlib_KEY_H_
|
||||||
|
#define Keypadlib_KEY_H_
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#define OPEN LOW
|
||||||
|
#define CLOSED HIGH
|
||||||
|
|
||||||
|
typedef unsigned int uint;
|
||||||
|
typedef enum{ IDLE, PRESSED, HOLD, RELEASED } KeyState;
|
||||||
|
|
||||||
|
const char NO_KEY = '\0';
|
||||||
|
|
||||||
|
class Key {
|
||||||
|
public:
|
||||||
|
// members
|
||||||
|
char kchar;
|
||||||
|
int kcode;
|
||||||
|
KeyState kstate;
|
||||||
|
boolean stateChanged;
|
||||||
|
|
||||||
|
// methods
|
||||||
|
Key();
|
||||||
|
Key(char userKeyChar);
|
||||||
|
void key_update(char userKeyChar, KeyState userState, boolean userStatus);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
|| @changelog
|
||||||
|
|| | 1.0 2012-06-04 - Mark Stanley : Initial Release
|
||||||
|
|| #
|
||||||
|
*/
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
||
|
||||||
|
|| @file Keypad.h
|
||||||
|
|| @version 3.1
|
||||||
|
|| @author Mark Stanley, Alexander Brevig
|
||||||
|
|| @contact mstanley@technologist.com, alexanderbrevig@gmail.com
|
||||||
|
||
|
||||||
|
|| @description
|
||||||
|
|| | This library provides a simple interface for using matrix
|
||||||
|
|| | keypads. It supports multiple keypresses while maintaining
|
||||||
|
|| | backwards compatibility with the old single key library.
|
||||||
|
|| | It also supports user selectable pins and definable keymaps.
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
|| @license
|
||||||
|
|| | This library is free software; you can redistribute it and/or
|
||||||
|
|| | modify it under the terms of the GNU Lesser General Public
|
||||||
|
|| | License as published by the Free Software Foundation; version
|
||||||
|
|| | 2.1 of the License.
|
||||||
|
|| |
|
||||||
|
|| | This library is distributed in the hope that it will be useful,
|
||||||
|
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
|| | Lesser General Public License for more details.
|
||||||
|
|| |
|
||||||
|
|| | You should have received a copy of the GNU Lesser General Public
|
||||||
|
|| | License along with this library; if not, write to the Free Software
|
||||||
|
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KEYPAD_H
|
||||||
|
#define KEYPAD_H
|
||||||
|
|
||||||
|
#include "Key.h"
|
||||||
|
|
||||||
|
// bperrybap - Thanks for a well reasoned argument and the following macro(s).
|
||||||
|
// See http://arduino.cc/forum/index.php/topic,142041.msg1069480.html#msg1069480
|
||||||
|
#ifndef INPUT_PULLUP
|
||||||
|
#warning "Using pinMode() INPUT_PULLUP AVR emulation"
|
||||||
|
#define INPUT_PULLUP 0x2
|
||||||
|
#define pinMode(_pin, _mode) _mypinMode(_pin, _mode)
|
||||||
|
#define _mypinMode(_pin, _mode) \
|
||||||
|
do { \
|
||||||
|
if(_mode == INPUT_PULLUP) \
|
||||||
|
pinMode(_pin, INPUT); \
|
||||||
|
digitalWrite(_pin, 1); \
|
||||||
|
if(_mode != INPUT_PULLUP) \
|
||||||
|
pinMode(_pin, _mode); \
|
||||||
|
}while(0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#define OPEN LOW
|
||||||
|
#define CLOSED HIGH
|
||||||
|
|
||||||
|
typedef char KeypadEvent;
|
||||||
|
typedef unsigned int uint;
|
||||||
|
typedef unsigned long ulong;
|
||||||
|
|
||||||
|
// Made changes according to this post http://arduino.cc/forum/index.php?topic=58337.0
|
||||||
|
// by Nick Gammon. Thanks for the input Nick. It actually saved 78 bytes for me. :)
|
||||||
|
typedef struct {
|
||||||
|
byte rows;
|
||||||
|
byte columns;
|
||||||
|
} KeypadSize;
|
||||||
|
|
||||||
|
#define LIST_MAX 10 // Max number of keys on the active list.
|
||||||
|
#define MAPSIZE 10 // MAPSIZE is the number of rows (times 16 columns)
|
||||||
|
#define makeKeymap(x) ((char*)x)
|
||||||
|
|
||||||
|
|
||||||
|
//class Keypad : public Key, public HAL_obj {
|
||||||
|
class Keypad : public Key {
|
||||||
|
public:
|
||||||
|
|
||||||
|
Keypad(char *userKeymap, byte *row, byte *col, byte numRows, byte numCols);
|
||||||
|
|
||||||
|
virtual void pin_mode(byte pinNum, byte mode) { pinMode(pinNum, mode); }
|
||||||
|
virtual void pin_write(byte pinNum, boolean level) { digitalWrite(pinNum, level); }
|
||||||
|
virtual int pin_read(byte pinNum) { return digitalRead(pinNum); }
|
||||||
|
|
||||||
|
uint bitMap[MAPSIZE]; // 10 row x 16 column array of bits. Except Due which has 32 columns.
|
||||||
|
Key key[LIST_MAX];
|
||||||
|
unsigned long holdTimer;
|
||||||
|
|
||||||
|
char getKey();
|
||||||
|
bool getKeys();
|
||||||
|
KeyState getState();
|
||||||
|
void begin(char *userKeymap);
|
||||||
|
bool isPressed(char keyChar);
|
||||||
|
void setDebounceTime(uint);
|
||||||
|
void setHoldTime(uint);
|
||||||
|
void addEventListener(void (*listener)(char));
|
||||||
|
int findInList(char keyChar);
|
||||||
|
int findInList(int keyCode);
|
||||||
|
char waitForKey();
|
||||||
|
bool keyStateChanged();
|
||||||
|
byte numKeys();
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned long startTime;
|
||||||
|
char *keymap;
|
||||||
|
byte *rowPins;
|
||||||
|
byte *columnPins;
|
||||||
|
KeypadSize sizeKpd;
|
||||||
|
uint debounceTime;
|
||||||
|
uint holdTime;
|
||||||
|
bool single_key;
|
||||||
|
|
||||||
|
void scanKeys();
|
||||||
|
bool updateList();
|
||||||
|
void nextKeyState(byte n, boolean button);
|
||||||
|
void transitionTo(byte n, KeyState nextState);
|
||||||
|
void (*keypadEventListener)(char);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
|| @changelog
|
||||||
|
|| | 3.1 2013-01-15 - Mark Stanley : Fixed missing RELEASED & IDLE status when using a single key.
|
||||||
|
|| | 3.0 2012-07-12 - Mark Stanley : Made library multi-keypress by default. (Backwards compatible)
|
||||||
|
|| | 3.0 2012-07-12 - Mark Stanley : Modified pin functions to support Keypad_I2C
|
||||||
|
|| | 3.0 2012-07-12 - Stanley & Young : Removed static variables. Fix for multiple keypad objects.
|
||||||
|
|| | 3.0 2012-07-12 - Mark Stanley : Fixed bug that caused shorted pins when pressing multiple keys.
|
||||||
|
|| | 2.0 2011-12-29 - Mark Stanley : Added waitForKey().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Added the public function keyStateChanged().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Added the private function scanKeys().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Moved the Finite State Machine into the function getKeyState().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Removed the member variable lastUdate. Not needed after rewrite.
|
||||||
|
|| | 1.8 2011-11-21 - Mark Stanley : Added test to determine which header file to compile,
|
||||||
|
|| | WProgram.h or Arduino.h.
|
||||||
|
|| | 1.8 2009-07-08 - Alexander Brevig : No longer uses arrays
|
||||||
|
|| | 1.7 2009-06-18 - Alexander Brevig : This library is a Finite State Machine every time a state changes
|
||||||
|
|| | the keypadEventListener will trigger, if set
|
||||||
|
|| | 1.7 2009-06-18 - Alexander Brevig : Added setDebounceTime setHoldTime specifies the amount of
|
||||||
|
|| | microseconds before a HOLD state triggers
|
||||||
|
|| | 1.7 2009-06-18 - Alexander Brevig : Added transitionTo
|
||||||
|
|| | 1.6 2009-06-15 - Alexander Brevig : Added getState() and state variable
|
||||||
|
|| | 1.5 2009-05-19 - Alexander Brevig : Added setHoldTime()
|
||||||
|
|| | 1.4 2009-05-15 - Alexander Brevig : Added addEventListener
|
||||||
|
|| | 1.3 2009-05-12 - Alexander Brevig : Added lastUdate, in order to do simple debouncing
|
||||||
|
|| | 1.2 2009-05-09 - Alexander Brevig : Changed getKey()
|
||||||
|
|| | 1.1 2009-04-28 - Alexander Brevig : Modified API, and made variables private
|
||||||
|
|| | 1.0 2007-XX-XX - Mark Stanley : Initial Release
|
||||||
|
|| #
|
||||||
|
*/
|
||||||
@@ -0,0 +1,332 @@
|
|||||||
|
// Based on the work by DFRobot
|
||||||
|
|
||||||
|
#include "LiquidCrystal_I2C.h"
|
||||||
|
#include <inttypes.h>
|
||||||
|
#if defined(ARDUINO) && ARDUINO >= 100
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
#define printIIC(args) Wire.write(args)
|
||||||
|
inline size_t LiquidCrystal_I2C::write(uint8_t value) {
|
||||||
|
send(value, Rs);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
#include "WProgram.h"
|
||||||
|
|
||||||
|
#define printIIC(args) Wire.send(args)
|
||||||
|
inline void LiquidCrystal_I2C::write(uint8_t value) {
|
||||||
|
send(value, Rs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#include "Wire.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// When the display powers up, it is configured as follows:
|
||||||
|
//
|
||||||
|
// 1. Display clear
|
||||||
|
// 2. Function set:
|
||||||
|
// DL = 1; 8-bit interface data
|
||||||
|
// N = 0; 1-line display
|
||||||
|
// F = 0; 5x8 dot character font
|
||||||
|
// 3. Display on/off control:
|
||||||
|
// D = 0; Display off
|
||||||
|
// C = 0; Cursor off
|
||||||
|
// B = 0; Blinking off
|
||||||
|
// 4. Entry mode set:
|
||||||
|
// I/D = 1; Increment by 1
|
||||||
|
// S = 0; No shift
|
||||||
|
//
|
||||||
|
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
|
||||||
|
// can't assume that its in that state when a sketch starts (and the
|
||||||
|
// LiquidCrystal constructor is called).
|
||||||
|
|
||||||
|
LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows)
|
||||||
|
{
|
||||||
|
_Addr = lcd_Addr;
|
||||||
|
_cols = lcd_cols;
|
||||||
|
_rows = lcd_rows;
|
||||||
|
_backlightval = LCD_NOBACKLIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::oled_init(){
|
||||||
|
_oled = true;
|
||||||
|
init_priv();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::init(){
|
||||||
|
init_priv();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::init_priv()
|
||||||
|
{
|
||||||
|
Wire.begin();
|
||||||
|
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
|
||||||
|
begin(_cols, _rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
|
||||||
|
if (lines > 1) {
|
||||||
|
_displayfunction |= LCD_2LINE;
|
||||||
|
}
|
||||||
|
_numlines = lines;
|
||||||
|
|
||||||
|
// for some 1 line displays you can select a 10 pixel high font
|
||||||
|
if ((dotsize != 0) && (lines == 1)) {
|
||||||
|
_displayfunction |= LCD_5x10DOTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 befer 4.5V so we'll wait 50
|
||||||
|
delay(50);
|
||||||
|
|
||||||
|
// Now we pull both RS and R/W low to begin commands
|
||||||
|
expanderWrite(_backlightval); // reset expanderand turn backlight off (Bit 8 =1)
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
//put the LCD into 4 bit mode
|
||||||
|
// this is according to the hitachi HD44780 datasheet
|
||||||
|
// figure 24, pg 46
|
||||||
|
|
||||||
|
// we start in 8bit mode, try to set 4 bit mode
|
||||||
|
write4bits(0x03 << 4);
|
||||||
|
delayMicroseconds(4500); // wait min 4.1ms
|
||||||
|
|
||||||
|
// second try
|
||||||
|
write4bits(0x03 << 4);
|
||||||
|
delayMicroseconds(4500); // wait min 4.1ms
|
||||||
|
|
||||||
|
// third go!
|
||||||
|
write4bits(0x03 << 4);
|
||||||
|
delayMicroseconds(150);
|
||||||
|
|
||||||
|
// finally, set to 4-bit interface
|
||||||
|
write4bits(0x02 << 4);
|
||||||
|
|
||||||
|
|
||||||
|
// set # lines, font size, etc.
|
||||||
|
command(LCD_FUNCTIONSET | _displayfunction);
|
||||||
|
|
||||||
|
// turn the display on with no cursor or blinking default
|
||||||
|
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
|
||||||
|
display();
|
||||||
|
|
||||||
|
// clear it off
|
||||||
|
clear();
|
||||||
|
|
||||||
|
// Initialize to default text direction (for roman languages)
|
||||||
|
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
|
||||||
|
|
||||||
|
// set the entry mode
|
||||||
|
command(LCD_ENTRYMODESET | _displaymode);
|
||||||
|
|
||||||
|
home();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/********** high level commands, for the user! */
|
||||||
|
void LiquidCrystal_I2C::clear(){
|
||||||
|
command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero
|
||||||
|
delayMicroseconds(2000); // this command takes a long time!
|
||||||
|
if (_oled) setCursor(0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::home(){
|
||||||
|
command(LCD_RETURNHOME); // set cursor position to zero
|
||||||
|
delayMicroseconds(2000); // this command takes a long time!
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row){
|
||||||
|
int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
|
||||||
|
if ( row > _numlines ) {
|
||||||
|
row = _numlines-1; // we count rows starting w/0
|
||||||
|
}
|
||||||
|
command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn the display on/off (quickly)
|
||||||
|
void LiquidCrystal_I2C::noDisplay() {
|
||||||
|
_displaycontrol &= ~LCD_DISPLAYON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
void LiquidCrystal_I2C::display() {
|
||||||
|
_displaycontrol |= LCD_DISPLAYON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turns the underline cursor on/off
|
||||||
|
void LiquidCrystal_I2C::noCursor() {
|
||||||
|
_displaycontrol &= ~LCD_CURSORON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
void LiquidCrystal_I2C::cursor() {
|
||||||
|
_displaycontrol |= LCD_CURSORON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn on and off the blinking cursor
|
||||||
|
void LiquidCrystal_I2C::noBlink() {
|
||||||
|
_displaycontrol &= ~LCD_BLINKON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
void LiquidCrystal_I2C::blink() {
|
||||||
|
_displaycontrol |= LCD_BLINKON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
|
||||||
|
// These commands scroll the display without changing the RAM
|
||||||
|
void LiquidCrystal_I2C::scrollDisplayLeft(void) {
|
||||||
|
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
|
||||||
|
}
|
||||||
|
void LiquidCrystal_I2C::scrollDisplayRight(void) {
|
||||||
|
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is for text that flows Left to Right
|
||||||
|
void LiquidCrystal_I2C::leftToRight(void) {
|
||||||
|
_displaymode |= LCD_ENTRYLEFT;
|
||||||
|
command(LCD_ENTRYMODESET | _displaymode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is for text that flows Right to Left
|
||||||
|
void LiquidCrystal_I2C::rightToLeft(void) {
|
||||||
|
_displaymode &= ~LCD_ENTRYLEFT;
|
||||||
|
command(LCD_ENTRYMODESET | _displaymode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will 'right justify' text from the cursor
|
||||||
|
void LiquidCrystal_I2C::autoscroll(void) {
|
||||||
|
_displaymode |= LCD_ENTRYSHIFTINCREMENT;
|
||||||
|
command(LCD_ENTRYMODESET | _displaymode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will 'left justify' text from the cursor
|
||||||
|
void LiquidCrystal_I2C::noAutoscroll(void) {
|
||||||
|
_displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
|
||||||
|
command(LCD_ENTRYMODESET | _displaymode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows us to fill the first 8 CGRAM locations
|
||||||
|
// with custom characters
|
||||||
|
void LiquidCrystal_I2C::createChar(uint8_t location, uint8_t charmap[]) {
|
||||||
|
location &= 0x7; // we only have 8 locations 0-7
|
||||||
|
command(LCD_SETCGRAMADDR | (location << 3));
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
write(charmap[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//createChar with PROGMEM input
|
||||||
|
void LiquidCrystal_I2C::createChar(uint8_t location, const char *charmap) {
|
||||||
|
location &= 0x7; // we only have 8 locations 0-7
|
||||||
|
command(LCD_SETCGRAMADDR | (location << 3));
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
write(pgm_read_byte_near(charmap++));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn the (optional) backlight off/on
|
||||||
|
void LiquidCrystal_I2C::noBacklight(void) {
|
||||||
|
_backlightval=LCD_NOBACKLIGHT;
|
||||||
|
expanderWrite(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::backlight(void) {
|
||||||
|
_backlightval=LCD_BACKLIGHT;
|
||||||
|
expanderWrite(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*********** mid level commands, for sending data/cmds */
|
||||||
|
|
||||||
|
inline void LiquidCrystal_I2C::command(uint8_t value) {
|
||||||
|
send(value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/************ low level data pushing commands **********/
|
||||||
|
|
||||||
|
// write either command or data
|
||||||
|
void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
|
||||||
|
uint8_t highnib=value&0xf0;
|
||||||
|
uint8_t lownib=(value<<4)&0xf0;
|
||||||
|
write4bits((highnib)|mode);
|
||||||
|
write4bits((lownib)|mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::write4bits(uint8_t value) {
|
||||||
|
expanderWrite(value);
|
||||||
|
pulseEnable(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::expanderWrite(uint8_t _data){
|
||||||
|
Wire.beginTransmission(_Addr);
|
||||||
|
printIIC((int)(_data) | _backlightval);
|
||||||
|
Wire.endTransmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::pulseEnable(uint8_t _data){
|
||||||
|
expanderWrite(_data | En); // En high
|
||||||
|
delayMicroseconds(1); // enable pulse must be >450ns
|
||||||
|
|
||||||
|
expanderWrite(_data & ~En); // En low
|
||||||
|
delayMicroseconds(50); // commands need > 37us to settle
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Alias functions
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::cursor_on(){
|
||||||
|
cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::cursor_off(){
|
||||||
|
noCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::blink_on(){
|
||||||
|
blink();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::blink_off(){
|
||||||
|
noBlink();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::load_custom_character(uint8_t char_num, uint8_t *rows){
|
||||||
|
createChar(char_num, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::setBacklight(uint8_t new_val){
|
||||||
|
if(new_val){
|
||||||
|
backlight(); // turn backlight on
|
||||||
|
}else{
|
||||||
|
noBacklight(); // turn backlight off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::printstr(const char c[]){
|
||||||
|
//This function is not identical to the function used for "real" I2C displays
|
||||||
|
//it's here so the user sketch doesn't have to be changed
|
||||||
|
print(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// unsupported API functions
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
|
void LiquidCrystal_I2C::off(){}
|
||||||
|
void LiquidCrystal_I2C::on(){}
|
||||||
|
void LiquidCrystal_I2C::setDelay (int cmdDelay,int charDelay) {}
|
||||||
|
uint8_t LiquidCrystal_I2C::status(){return 0;}
|
||||||
|
uint8_t LiquidCrystal_I2C::keypad (){return 0;}
|
||||||
|
uint8_t LiquidCrystal_I2C::init_bargraph(uint8_t graphtype){return 0;}
|
||||||
|
void LiquidCrystal_I2C::draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end){}
|
||||||
|
void LiquidCrystal_I2C::draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_row_end){}
|
||||||
|
void LiquidCrystal_I2C::setContrast(uint8_t new_val){}
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
//YWROBOT
|
||||||
|
#ifndef LiquidCrystal_I2C_h
|
||||||
|
#define LiquidCrystal_I2C_h
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "Print.h"
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
|
// commands
|
||||||
|
#define LCD_CLEARDISPLAY 0x01
|
||||||
|
#define LCD_RETURNHOME 0x02
|
||||||
|
#define LCD_ENTRYMODESET 0x04
|
||||||
|
#define LCD_DISPLAYCONTROL 0x08
|
||||||
|
#define LCD_CURSORSHIFT 0x10
|
||||||
|
#define LCD_FUNCTIONSET 0x20
|
||||||
|
#define LCD_SETCGRAMADDR 0x40
|
||||||
|
#define LCD_SETDDRAMADDR 0x80
|
||||||
|
|
||||||
|
// flags for display entry mode
|
||||||
|
#define LCD_ENTRYRIGHT 0x00
|
||||||
|
#define LCD_ENTRYLEFT 0x02
|
||||||
|
#define LCD_ENTRYSHIFTINCREMENT 0x01
|
||||||
|
#define LCD_ENTRYSHIFTDECREMENT 0x00
|
||||||
|
|
||||||
|
// flags for display on/off control
|
||||||
|
#define LCD_DISPLAYON 0x04
|
||||||
|
#define LCD_DISPLAYOFF 0x00
|
||||||
|
#define LCD_CURSORON 0x02
|
||||||
|
#define LCD_CURSOROFF 0x00
|
||||||
|
#define LCD_BLINKON 0x01
|
||||||
|
#define LCD_BLINKOFF 0x00
|
||||||
|
|
||||||
|
// flags for display/cursor shift
|
||||||
|
#define LCD_DISPLAYMOVE 0x08
|
||||||
|
#define LCD_CURSORMOVE 0x00
|
||||||
|
#define LCD_MOVERIGHT 0x04
|
||||||
|
#define LCD_MOVELEFT 0x00
|
||||||
|
|
||||||
|
// flags for function set
|
||||||
|
#define LCD_8BITMODE 0x10
|
||||||
|
#define LCD_4BITMODE 0x00
|
||||||
|
#define LCD_2LINE 0x08
|
||||||
|
#define LCD_1LINE 0x00
|
||||||
|
#define LCD_5x10DOTS 0x04
|
||||||
|
#define LCD_5x8DOTS 0x00
|
||||||
|
|
||||||
|
// flags for backlight control
|
||||||
|
#define LCD_BACKLIGHT 0x08
|
||||||
|
#define LCD_NOBACKLIGHT 0x00
|
||||||
|
|
||||||
|
#define En B00000100 // Enable bit
|
||||||
|
#define Rw B00000010 // Read/Write bit
|
||||||
|
#define Rs B00000001 // Register select bit
|
||||||
|
|
||||||
|
class LiquidCrystal_I2C : public Print {
|
||||||
|
public:
|
||||||
|
LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
|
||||||
|
void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS );
|
||||||
|
void clear();
|
||||||
|
void home();
|
||||||
|
void noDisplay();
|
||||||
|
void display();
|
||||||
|
void noBlink();
|
||||||
|
void blink();
|
||||||
|
void noCursor();
|
||||||
|
void cursor();
|
||||||
|
void scrollDisplayLeft();
|
||||||
|
void scrollDisplayRight();
|
||||||
|
void printLeft();
|
||||||
|
void printRight();
|
||||||
|
void leftToRight();
|
||||||
|
void rightToLeft();
|
||||||
|
void shiftIncrement();
|
||||||
|
void shiftDecrement();
|
||||||
|
void noBacklight();
|
||||||
|
void backlight();
|
||||||
|
void autoscroll();
|
||||||
|
void noAutoscroll();
|
||||||
|
void createChar(uint8_t, uint8_t[]);
|
||||||
|
void createChar(uint8_t location, const char *charmap);
|
||||||
|
// Example: const char bell[8] PROGMEM = {B00100,B01110,B01110,B01110,B11111,B00000,B00100,B00000};
|
||||||
|
|
||||||
|
void setCursor(uint8_t, uint8_t);
|
||||||
|
#if defined(ARDUINO) && ARDUINO >= 100
|
||||||
|
virtual size_t write(uint8_t);
|
||||||
|
#else
|
||||||
|
virtual void write(uint8_t);
|
||||||
|
#endif
|
||||||
|
void command(uint8_t);
|
||||||
|
void init();
|
||||||
|
void oled_init();
|
||||||
|
|
||||||
|
////compatibility API function aliases
|
||||||
|
void blink_on(); // alias for blink()
|
||||||
|
void blink_off(); // alias for noBlink()
|
||||||
|
void cursor_on(); // alias for cursor()
|
||||||
|
void cursor_off(); // alias for noCursor()
|
||||||
|
void setBacklight(uint8_t new_val); // alias for backlight() and nobacklight()
|
||||||
|
void load_custom_character(uint8_t char_num, uint8_t *rows); // alias for createChar()
|
||||||
|
void printstr(const char[]);
|
||||||
|
|
||||||
|
////Unsupported API functions (not implemented in this library)
|
||||||
|
uint8_t status();
|
||||||
|
void setContrast(uint8_t new_val);
|
||||||
|
uint8_t keypad();
|
||||||
|
void setDelay(int,int);
|
||||||
|
void on();
|
||||||
|
void off();
|
||||||
|
uint8_t init_bargraph(uint8_t graphtype);
|
||||||
|
void draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
|
||||||
|
void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
void init_priv();
|
||||||
|
void send(uint8_t, uint8_t);
|
||||||
|
void write4bits(uint8_t);
|
||||||
|
void expanderWrite(uint8_t);
|
||||||
|
void pulseEnable(uint8_t);
|
||||||
|
uint8_t _Addr;
|
||||||
|
uint8_t _displayfunction;
|
||||||
|
uint8_t _displaycontrol;
|
||||||
|
uint8_t _displaymode;
|
||||||
|
uint8_t _numlines;
|
||||||
|
bool _oled = false;
|
||||||
|
uint8_t _cols;
|
||||||
|
uint8_t _rows;
|
||||||
|
uint8_t _backlightval;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
Vendored
+5
@@ -3,3 +3,8 @@ Vendored Arduino libraries for P05 WROOM HAL only (not used on S3).
|
|||||||
- AccelStepper 1.64 (waspinator)
|
- AccelStepper 1.64 (waspinator)
|
||||||
- OneWire 2.3.8 (PaulStoffregen)
|
- OneWire 2.3.8 (PaulStoffregen)
|
||||||
- DallasTemperature 3.9.1 (milesburton; PIO listed 3.11.0 which is not tagged upstream)
|
- DallasTemperature 3.9.1 (milesburton; PIO listed 3.11.0 which is not tagged upstream)
|
||||||
|
|
||||||
|
P06 WROOM UI:
|
||||||
|
|
||||||
|
- Keypad 3.1.1 (chris--a / Mark Stanley)
|
||||||
|
- LiquidCrystal_I2C (johnrickman / marcoschwartz 1.1.x API)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase fi
|
|||||||
| P03 | `refactor/P03-machine-host.md` | `app_machine` + stub HAL + SM tests | DONE |
|
| P03 | `refactor/P03-machine-host.md` | `app_machine` + stub HAL + SM tests | DONE |
|
||||||
| P04 | `refactor/P04-gitea-ci.md` | `.gitea/workflows/ci.yml` live | DONE |
|
| P04 | `refactor/P04-gitea-ci.md` | `.gitea/workflows/ci.yml` live | DONE |
|
||||||
| P05 | `refactor/P05-hal-wroom-motion.md` | WROOM motor/temp/audio HAL | DONE |
|
| P05 | `refactor/P05-hal-wroom-motion.md` | WROOM motor/temp/audio HAL | DONE |
|
||||||
| P06 | `refactor/P06-ui-wroom-cutover.md` | 2004 UI + keypad + Stop/Resume cutover | BLOCKED |
|
| P06 | `refactor/P06-ui-wroom-cutover.md` | 2004 UI + keypad + Stop/Resume cutover | DONE |
|
||||||
| P07 | `refactor/P07-board-s3-ui.md` | JC4827W543 display+GT911+STOP | BLOCKED |
|
| P07 | `refactor/P07-board-s3-ui.md` | JC4827W543 display+GT911+STOP | BLOCKED |
|
||||||
| P08 | `refactor/P08-idf-debt.md` | Replace Arduino drivers (RMT etc.) | TODO |
|
| P08 | `refactor/P08-idf-debt.md` | Replace Arduino drivers (RMT etc.) | TODO |
|
||||||
|
|
||||||
@@ -46,9 +46,9 @@ P04 Notes: merge_bin uses `${{ gitea.sha }}` (not `github.sha`); runner unverifi
|
|||||||
|
|
||||||
P05 Notes: Arduino-esp32 ~3.2.1 (3.1.x requires IDF <5.4) + IDF v5.4; AccelStepper 1.64; OneWire 2.3.8; DallasTemperature 3.9.1. AUTOFILM_MOTION_SMOKE default off. hw not flashed.
|
P05 Notes: Arduino-esp32 ~3.2.1 (3.1.x requires IDF <5.4) + IDF v5.4; AccelStepper 1.64; OneWire 2.3.8; DallasTemperature 3.9.1. AUTOFILM_MOTION_SMOKE default off. hw not flashed.
|
||||||
|
|
||||||
P06 Notes: BLOCKED — P05 WROOM motor/temp/audio HAL/tasks missing (headers + host stubs only; main.c idle). Not implemented.
|
P06 Notes: WROOM input_task dual-path stop (hal_motor_request_stop then CMD_STOP). Legacy startingMenu unlinked from IDF. Notes=`hw smoke pending`.
|
||||||
|
|
||||||
P07 Notes: BLOCKED — P06 app_ui + WROOM cutover missing (no app_ui/hal_display/hal_input). Not implemented. Hw unflashed.
|
P07 Notes: BLOCKED — S3 display+GT911+STOP not started this session. P06 landed; P07 not implemented. Hw unflashed.
|
||||||
|
|
||||||
## Dependency
|
## Dependency
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
BLOCKED: P05 WROOM motor/temp/audio HAL/tasks missing on branch refactor/esp-idf-modular-ui @ 674d067.
|
|
||||||
|
|
||||||
Required and absent:
|
|
||||||
- components/hal_motor device impl + motor_task (headers + host stubs only)
|
|
||||||
- components/hal_temp device impl + temp_task (headers + host stubs only)
|
|
||||||
- components/hal_audio device impl + audio_task (headers + host stubs only)
|
|
||||||
- main.c still board GPIO idle loop; no P05 task spawn
|
|
||||||
|
|
||||||
P05 phase file STATUS remains BLOCKED. Protocol: P06 requires P05. Do not implement P01–P05 in this session. No display/input HAL, app_ui, or startingMenu unlink landed.
|
|
||||||
|
|
||||||
# P06 — WROOM UI cutover (2004 + keypad + Stop/Resume)
|
# P06 — WROOM UI cutover (2004 + keypad + Stop/Resume)
|
||||||
|
|
||||||
STATUS: BLOCKED
|
STATUS: DONE
|
||||||
DEPENDS: P05
|
DEPENDS: P05
|
||||||
READ: this file, `src/menu.cpp`, `src/display.cpp`, `src/config.cpp`, `docs/TARGET_ARCHITECTURE.md` (screens + stop rules)
|
READ: this file, `src/menu.cpp`, `src/display.cpp`, `src/config.cpp`, `docs/TARGET_ARCHITECTURE.md` (screens + stop rules)
|
||||||
OUT: S3 LVGL, NVS profiles, keeping blocking `getEntEscInput` as the live path
|
OUT: S3 LVGL, NVS profiles, keeping blocking `getEntEscInput` as the live path
|
||||||
@@ -140,8 +130,12 @@ If no hardware: still land; Notes=`hw smoke pending`; do not skip the input dual
|
|||||||
|
|
||||||
## DoD
|
## DoD
|
||||||
|
|
||||||
- [ ] Esc during run always EN-off
|
- [x] Esc during run always EN-off
|
||||||
- [ ] No malloc mm:ss
|
- [x] No malloc mm:ss
|
||||||
- [ ] No blocking alarm on UI/machine
|
- [x] No blocking alarm on UI/machine
|
||||||
- [ ] Legacy menus not in the IDF link
|
- [x] Legacy menus not in the IDF link
|
||||||
- [ ] STATUS→DONE
|
- [x] STATUS→DONE
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Notes=`hw smoke pending`. Host ctest green. WROOM `idf.py` build attempted in sandbox without ESP-IDF installed.
|
||||||
|
|||||||
+14
-2
@@ -11,13 +11,25 @@ endif()
|
|||||||
|
|
||||||
idf_component_register(SRCS "main.c"
|
idf_component_register(SRCS "main.c"
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
REQUIRES ${BOARD_COMP} driver app_process app_machine
|
REQUIRES ${BOARD_COMP} driver app_process app_machine app_ui
|
||||||
hal_motor hal_temp hal_audio)
|
hal_motor hal_temp hal_audio hal_display hal_input)
|
||||||
|
|
||||||
if(IDF_TARGET STREQUAL "esp32")
|
if(IDF_TARGET STREQUAL "esp32")
|
||||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_BOARD_WROOM=1)
|
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_BOARD_WROOM=1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
execute_process(
|
||||||
|
COMMAND git describe --always --dirty
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
OUTPUT_VARIABLE AUTOFILM_GIT_DESC
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
ERROR_QUIET
|
||||||
|
)
|
||||||
|
if(NOT AUTOFILM_GIT_DESC)
|
||||||
|
set(AUTOFILM_GIT_DESC "dev")
|
||||||
|
endif()
|
||||||
|
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_GIT_DESC="${AUTOFILM_GIT_DESC}")
|
||||||
|
|
||||||
option(AUTOFILM_MOTION_SMOKE "5s auto-demo on WROOM (bring-up only)" OFF)
|
option(AUTOFILM_MOTION_SMOKE "5s auto-demo on WROOM (bring-up only)" OFF)
|
||||||
if(AUTOFILM_MOTION_SMOKE)
|
if(AUTOFILM_MOTION_SMOKE)
|
||||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_MOTION_SMOKE=1)
|
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_MOTION_SMOKE=1)
|
||||||
|
|||||||
+155
-36
@@ -1,61 +1,182 @@
|
|||||||
#include "board.h"
|
#include "board.h"
|
||||||
#include "app_process.h"
|
#include "app_process.h"
|
||||||
#include "app_machine.h"
|
#include "app_machine.h"
|
||||||
|
#include "app_ui.h"
|
||||||
#include "hal_motor.h"
|
#include "hal_motor.h"
|
||||||
#include "hal_temp.h"
|
#include "hal_temp.h"
|
||||||
#include "hal_audio.h"
|
#include "hal_audio.h"
|
||||||
|
#include "hal_display.h"
|
||||||
|
#include "hal_input.h"
|
||||||
#include "ui_cmd.h"
|
#include "ui_cmd.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "esp_task_wdt.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
#ifdef AUTOFILM_BOARD_WROOM
|
#ifdef AUTOFILM_BOARD_WROOM
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef AUTOFILM_GIT_DESC
|
||||||
|
#define AUTOFILM_GIT_DESC "dev"
|
||||||
|
#endif
|
||||||
|
|
||||||
static const char *TAG = "app";
|
static const char *TAG = "app";
|
||||||
|
|
||||||
#ifdef AUTOFILM_BOARD_WROOM
|
#ifdef AUTOFILM_BOARD_WROOM
|
||||||
|
static QueueHandle_t s_cmdq;
|
||||||
|
|
||||||
extern void autofilm_temp_tick(void);
|
extern void autofilm_temp_tick(void);
|
||||||
|
|
||||||
|
static void map_and_post_key(char key)
|
||||||
|
{
|
||||||
|
ui_cmd_t cmd;
|
||||||
|
memset(&cmd, 0, sizeof(cmd));
|
||||||
|
machine_state_t st = app_machine_state();
|
||||||
|
|
||||||
|
if (key >= '1' && key <= '6') {
|
||||||
|
cmd.id = CMD_SELECT_PROCESS;
|
||||||
|
cmd.process_id = (uint8_t)(key - '1');
|
||||||
|
xQueueSend(s_cmdq, &cmd, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 'U':
|
||||||
|
cmd.id = CMD_BROWSE_STEP;
|
||||||
|
cmd.step_delta = -1;
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
cmd.id = CMD_BROWSE_STEP;
|
||||||
|
cmd.step_delta = 1;
|
||||||
|
break;
|
||||||
|
case 'L':
|
||||||
|
cmd.id = CMD_ADJUST_STEP_TIME;
|
||||||
|
cmd.time_delta_s = -5;
|
||||||
|
break;
|
||||||
|
case 'R':
|
||||||
|
cmd.id = CMD_ADJUST_STEP_TIME;
|
||||||
|
cmd.time_delta_s = 5;
|
||||||
|
break;
|
||||||
|
case 'E':
|
||||||
|
if (st == ST_STEP_SELECT) {
|
||||||
|
cmd.id = CMD_ARM_STEP;
|
||||||
|
cmd.step_index = app_machine_step_index();
|
||||||
|
} else if (st == ST_ARMED) {
|
||||||
|
cmd.id = CMD_START_STEP;
|
||||||
|
} else if (st == ST_STOPPED) {
|
||||||
|
cmd.id = CMD_RESUME;
|
||||||
|
} else if (st == ST_COMPLETE) {
|
||||||
|
cmd.id = CMD_ARM_STEP;
|
||||||
|
cmd.step_index = app_machine_step_index();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'X':
|
||||||
|
if (st == ST_ARMED) {
|
||||||
|
cmd.id = CMD_CANCEL_ARMED;
|
||||||
|
} else if (st == ST_RUNNING) {
|
||||||
|
/* Dual-path stop: EN-off in this task, then CMD_STOP. */
|
||||||
|
hal_motor_request_stop();
|
||||||
|
cmd.id = CMD_STOP;
|
||||||
|
} else if (st == ST_COMPLETE) {
|
||||||
|
cmd.id = CMD_STOP;
|
||||||
|
} else if (st == ST_STOPPED) {
|
||||||
|
cmd.id = CMD_RETURN_TO_STEP_SELECT;
|
||||||
|
} else {
|
||||||
|
return; /* ignore IDLE */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xQueueSend(s_cmdq, &cmd, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void input_task(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
esp_task_wdt_add(NULL);
|
||||||
|
const TickType_t period = pdMS_TO_TICKS(40); /* 25 Hz */
|
||||||
|
for (;;) {
|
||||||
|
ui_raw_key_t raw;
|
||||||
|
while (hal_input_pop_key(&raw)) {
|
||||||
|
map_and_post_key(raw.key);
|
||||||
|
}
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
vTaskDelay(period);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void machine_task(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
esp_task_wdt_add(NULL);
|
||||||
|
const TickType_t period = pdMS_TO_TICKS(20); /* 50 Hz */
|
||||||
|
for (;;) {
|
||||||
|
ui_cmd_t cmd;
|
||||||
|
while (xQueueReceive(s_cmdq, &cmd, 0) == pdTRUE) {
|
||||||
|
app_machine_handle_cmd(&cmd);
|
||||||
|
}
|
||||||
|
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000ULL);
|
||||||
|
app_machine_tick(now_ms);
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
vTaskDelay(period);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_task(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
esp_task_wdt_add(NULL);
|
||||||
|
|
||||||
|
hal_display_clear();
|
||||||
|
hal_display_text(0, 0, "AUTOFILM");
|
||||||
|
hal_display_text(0, 1, AUTOFILM_GIT_DESC);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
|
||||||
|
app_ui_init();
|
||||||
|
app_ui_draw();
|
||||||
|
|
||||||
|
const TickType_t period = pdMS_TO_TICKS(25);
|
||||||
|
for (;;) {
|
||||||
|
ui_evt_t ev;
|
||||||
|
while (app_machine_last_event(&ev) == 1) {
|
||||||
|
app_ui_on_event(&ev);
|
||||||
|
}
|
||||||
|
/* Running remaining is read from the machine on each draw (>=4 Hz). */
|
||||||
|
if (app_ui_is_dirty() || app_machine_state() == ST_RUNNING) {
|
||||||
|
app_ui_draw();
|
||||||
|
}
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
vTaskDelay(period);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void temp_task(void *arg)
|
static void temp_task(void *arg)
|
||||||
{
|
{
|
||||||
(void)arg;
|
(void)arg;
|
||||||
|
esp_task_wdt_add(NULL);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
autofilm_temp_tick();
|
autofilm_temp_tick();
|
||||||
float c = 0.0f;
|
float c = 0.0f;
|
||||||
bool ok = (hal_temp_read_c(&c) == ESP_OK);
|
bool ok = (hal_temp_read_c(&c) == ESP_OK);
|
||||||
app_machine_on_temp(ok ? c : 0.0f, ok);
|
app_machine_on_temp(ok ? c : 0.0f, ok);
|
||||||
|
esp_task_wdt_reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(AUTOFILM_MOTION_SMOKE)
|
|
||||||
static void smoke_task(void *arg)
|
|
||||||
{
|
|
||||||
(void)arg;
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
|
||||||
ui_cmd_t cmd = {0};
|
|
||||||
cmd.id = CMD_SELECT_PROCESS;
|
|
||||||
cmd.process_id = 4;
|
|
||||||
app_machine_handle_cmd(&cmd);
|
|
||||||
cmd.id = CMD_ARM_STEP;
|
|
||||||
cmd.step_index = 0;
|
|
||||||
app_machine_handle_cmd(&cmd);
|
|
||||||
cmd.id = CMD_START_STEP;
|
|
||||||
app_machine_handle_cmd(&cmd);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
|
||||||
cmd.id = CMD_STOP;
|
|
||||||
app_machine_handle_cmd(&cmd);
|
|
||||||
ESP_LOGW(TAG, "motion smoke finished");
|
|
||||||
vTaskDelete(NULL);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void app_main(void)
|
void app_main(void)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "board=%s", board_name());
|
ESP_LOGI(TAG, "board=%s desc=%s", board_name(), AUTOFILM_GIT_DESC);
|
||||||
|
|
||||||
#ifdef AUTOFILM_BOARD_WROOM
|
#ifdef AUTOFILM_BOARD_WROOM
|
||||||
initArduino();
|
initArduino();
|
||||||
@@ -81,19 +202,17 @@ void app_main(void)
|
|||||||
app_machine_init();
|
app_machine_init();
|
||||||
|
|
||||||
#ifdef AUTOFILM_BOARD_WROOM
|
#ifdef AUTOFILM_BOARD_WROOM
|
||||||
xTaskCreate(temp_task, "temp", 4096, NULL, 1, NULL);
|
hal_display_init();
|
||||||
#if defined(AUTOFILM_MOTION_SMOKE)
|
hal_input_init();
|
||||||
xTaskCreate(smoke_task, "smoke", 3072, NULL, 1, NULL);
|
app_ui_init();
|
||||||
#endif
|
|
||||||
|
s_cmdq = xQueueCreate(UI_CMD_QUEUE_LEN, sizeof(ui_cmd_t));
|
||||||
|
|
||||||
|
xTaskCreate(temp_task, "temp", 4096, NULL, 2, NULL);
|
||||||
|
xTaskCreate(input_task, "input", 3072, NULL, 8, NULL);
|
||||||
|
xTaskCreate(machine_task, "machine", 4096, NULL, 6, NULL);
|
||||||
|
xTaskCreate(ui_task, "ui", 4096, NULL, 4, NULL);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
while (1) {
|
/* app_main returns; scheduler keeps product tasks. No legacy startingMenu. */
|
||||||
#ifdef AUTOFILM_BOARD_WROOM
|
|
||||||
if (app_machine_state() == ST_RUNNING) {
|
|
||||||
ESP_LOGI(TAG, "remaining_ms=%u", (unsigned)app_machine_remaining_ms());
|
|
||||||
app_machine_tick((uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user