3 Commits
Author SHA1 Message Date
gronod f47299b939 Cut over WROOM main to RTOS UI and stop-safe input
ci / test (push) Successful in 1m16s
ci / test (pull_request) Successful in 1m19s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 29s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Failing after 30s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Failing after 30s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 33s
input_task drops motor EN immediately on X while RUNNING, then queues
CMD_STOP. machine_task ticks at 50 Hz. startingMenu is not linked.
STATUS DONE. Notes=hw smoke pending.
2026-09-16 14:05:21 +00:00
gronod 51faa0fe59 Add app_ui screens driven by machine events
Program/step/armed/running/stopped/complete layouts on 20x4. mm:ss uses
a stack buffer. Temp row shows thermo+value or --.
2026-09-16 14:05:21 +00:00
gronod a19accf6a7 Add 2004 display and keypad HAL
Vendor Keypad 3.1.1 and LiquidCrystal_I2C for WROOM. 20x4 I2C display
and 5x4 keypad map match src/config.cpp. Stubs keep the S3 link.
2026-09-16 14:05:21 +00:00
23 changed files with 1688 additions and 57 deletions
+4
View File
@@ -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)
+197
View File
@@ -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;
}
+17
View File
@@ -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
+11
View File
@@ -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;
+22
View File
@@ -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
+16
View File
@@ -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) {}
+23
View File
@@ -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})
+44
View File
@@ -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;
}
+18
View File
@@ -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
+9
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+5
View File
@@ -3,3 +3,8 @@ Vendored Arduino libraries for P05 WROOM HAL only (not used on S3).
- AccelStepper 1.64 (waspinator)
- OneWire 2.3.8 (PaulStoffregen)
- 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)
+3 -3
View File
@@ -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 |
| 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 |
| 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 |
| 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.
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
+10 -16
View File
@@ -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)
STATUS: BLOCKED
STATUS: DONE
DEPENDS: P05
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
@@ -140,8 +130,12 @@ If no hardware: still land; Notes=`hw smoke pending`; do not skip the input dual
## DoD
- [ ] Esc during run always EN-off
- [ ] No malloc mm:ss
- [ ] No blocking alarm on UI/machine
- [ ] Legacy menus not in the IDF link
- [ ] STATUS→DONE
- [x] Esc during run always EN-off
- [x] No malloc mm:ss
- [x] No blocking alarm on UI/machine
- [x] Legacy menus not in the IDF link
- [x] STATUS→DONE
## Notes
Notes=`hw smoke pending`. Host ctest green. WROOM `idf.py` build attempted in sandbox without ESP-IDF installed.
+14 -2
View File
@@ -11,13 +11,25 @@ endif()
idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES ${BOARD_COMP} driver app_process app_machine
hal_motor hal_temp hal_audio)
REQUIRES ${BOARD_COMP} driver app_process app_machine app_ui
hal_motor hal_temp hal_audio hal_display hal_input)
if(IDF_TARGET STREQUAL "esp32")
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_BOARD_WROOM=1)
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)
if(AUTOFILM_MOTION_SMOKE)
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_MOTION_SMOKE=1)
+155 -36
View File
@@ -1,61 +1,182 @@
#include "board.h"
#include "app_process.h"
#include "app_machine.h"
#include "app_ui.h"
#include "hal_motor.h"
#include "hal_temp.h"
#include "hal_audio.h"
#include "hal_display.h"
#include "hal_input.h"
#include "ui_cmd.h"
#include <string.h>
#include <stdbool.h>
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_task_wdt.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#ifdef AUTOFILM_BOARD_WROOM
#include <Arduino.h>
#endif
#ifndef AUTOFILM_GIT_DESC
#define AUTOFILM_GIT_DESC "dev"
#endif
static const char *TAG = "app";
#ifdef AUTOFILM_BOARD_WROOM
static QueueHandle_t s_cmdq;
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)
{
(void)arg;
esp_task_wdt_add(NULL);
for (;;) {
autofilm_temp_tick();
float c = 0.0f;
bool ok = (hal_temp_read_c(&c) == ESP_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
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
initArduino();
@@ -81,19 +202,17 @@ void app_main(void)
app_machine_init();
#ifdef AUTOFILM_BOARD_WROOM
xTaskCreate(temp_task, "temp", 4096, NULL, 1, NULL);
#if defined(AUTOFILM_MOTION_SMOKE)
xTaskCreate(smoke_task, "smoke", 3072, NULL, 1, NULL);
#endif
hal_display_init();
hal_input_init();
app_ui_init();
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
while (1) {
#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));
}
/* app_main returns; scheduler keeps product tasks. No legacy startingMenu. */
}