Author SHA1 Message Date
gronod ce9d729027 Tick A04 DoD checkbox after CI green
ci / test (push) Successful in 1m11s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 3m51s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 3m53s
2026-09-17 10:08:33 +01:00
gronod 06acd84928 Add esp_timer to hal_temp requires
ci / test (push) Successful in 2m6s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 3m42s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 3m44s
2026-09-17 09:58:29 +01:00
gronod cfd047a0ac Mark A04 status DONE
ci / test (push) Successful in 1m24s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 7m23s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 7m24s
2026-09-17 09:47:01 +01:00
gronod a3830a58cb Make DS18B20 conversion non-blocking in hal_temp 2026-09-17 09:46:32 +01:00
gronod 3785f07189 Mark A03 status DONE
ci / test (push) Successful in 1m6s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 3m55s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m0s
2026-09-17 07:58:32 +01:00
gronod 37b772218b Make task watchdog setup explicit in app_main
ci / test (push) Successful in 1m5s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m36s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m40s
2026-09-17 07:49:20 +01:00
gronod 8f7db974fa Mark A02 status DONE
ci / test (push) Successful in 1m10s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 3m32s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 3m42s
2026-09-17 07:30:48 +01:00
gronod 02d226b949 Update machine tests for auto-advance 2026-09-17 07:30:19 +01:00
gronod d71e28c85d Restore auto-advance to arm next step after step complete 2026-09-17 07:30:19 +01:00
gronod adff1d963b Mark A01 status DONE
ci / test (push) Successful in 1m7s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m0s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m8s
2026-09-17 07:24:09 +01:00
gronod d84116c45b Replace machine event ring with FreeRTOS queue 2026-09-17 07:23:23 +01:00
gronod 1ade64b2b2 Fix step deadline to derive from real uptime 2026-09-17 07:22:57 +01:00
gronod 20535ceb14 Add FreeRTOS queue shim for host tests 2026-09-17 07:22:17 +01:00
gronod 76ef75da29 Add audit remediation megaplan
ci / test (push) Successful in 1m35s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 7m31s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 7m32s
2026-09-17 06:43:54 +01:00
gronod d70df219f8 Mark I04 status DONE
ci / test (pull_request) Successful in 1m16s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Successful in 4m4s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Successful in 4m5s
ci / test (push) Successful in 1m13s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m15s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m19s
2026-09-16 22:28:17 +01:00
gronod 7e854e10f0 Add I2S speaker audio for S3
ci / test (push) Successful in 1m14s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m26s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m28s
2026-09-16 22:20:53 +01:00
gronod b6c385e09c Mark I03 status DONE
ci / test (pull_request) Successful in 1m14s
ci / test (push) Successful in 1m17s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Successful in 3m48s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Successful in 3m57s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m4s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m8s
2026-09-16 21:58:26 +01:00
gronod a9f76ea058 Share RMT motor HAL across both boards
ci / test (push) Successful in 1m18s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m30s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m39s
2026-09-16 21:51:32 +01:00
gronod c40c714028 Mark I02 status DONE
ci / test (pull_request) Successful in 1m13s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Successful in 4m39s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Successful in 4m41s
ci / test (push) Successful in 1m8s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m9s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m15s
2026-09-16 21:31:03 +01:00
gronod f87ab0c65f Fix temp_task starvation bug on non-WROOM
ci / test (push) Successful in 1m9s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m9s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m10s
2026-09-16 21:21:12 +01:00
gronod b4230b2a0e Share onewire temp HAL across both boards 2026-09-16 21:20:18 +01:00
gronod 04e1ed0f16 Merge pull request 'I01 board pin allocations & accessors' (#12) from feature/integration-I01 into develop
ci / test (push) Successful in 1m14s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m46s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m48s
Merge I01 board pin allocations & accessors into develop
2026-09-16 21:15:46 +01:00
gronod a33b4b0c74 Mark I01 status DONE
ci / test (push) Successful in 1m4s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 3m56s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 3m57s
ci / test (pull_request) Successful in 1m22s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Successful in 4m42s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Successful in 4m47s
2026-09-16 21:05:57 +01:00
gronod 73f3158b27 Update pin audit documentation labels
ci / test (push) Successful in 1m3s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 3m58s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m1s
2026-09-16 20:43:25 +01:00
gronod a3fe809359 Lock S3 header pins and introduce shared board pin accessors 2026-09-16 20:43:24 +01:00
gronod 637ea8cb6e Update .gitea/workflows/ci.yml
ci / test (pull_request) Successful in 1m4s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Successful in 8m1s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Successful in 8m4s
ci / test (push) Successful in 1m11s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m57s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m57s
2026-09-16 16:45:58 +01:00
gronod 84b60155dd Update components/app_ui/app_ui.c
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Canceled after 0s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Canceled after 0s
ci / test (push) Canceled after 15s
ci / test (pull_request) Successful in 1m15s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Failing after 6m1s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Failing after 6m5s
2026-09-16 16:34:14 +01:00
gronod 7406114368 Update main/CMakeLists.txt
ci / test (push) Successful in 1m4s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Canceled after 23s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Canceled after 23s
ci / test (pull_request) Successful in 1m7s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Failing after 5m41s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Failing after 5m45s
2026-09-16 16:14:53 +01:00
gronod 0b0a713098 Update .gitea/workflows/ci.yml
ci / test (push) Successful in 1m14s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 6m46s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 6m46s
2026-09-16 15:54:32 +01:00
gronod 9ea89ac467 Update .gitea/workflows/ci.yml
ci / test (push) Successful in 54s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 1m39s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 1m39s
2026-09-16 15:50:13 +01:00
gronod 5f1727da7d Update .gitea/workflows/ci.yml
ci / test (push) Successful in 1m3s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Canceled after 4m17s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Canceled after 4m17s
2026-09-16 15:44:22 +01:00
gronod 32af44a702 Drop espressif/arduino-esp32 from IDF firmware
ci / test (pull_request) Successful in 1m22s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Failing after 33s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Failing after 37s
ci / test (push) Successful in 1m19s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 30s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 32s
2026-09-16 14:34:25 +00:00
gronod 82f55af4b1 Replace LiquidCrystal_I2C with IDF I2C HD44780 HAL 2026-09-16 14:33:57 +00:00
gronod 880f9a18c5 Replace Keypad lib with GPIO matrix scan HAL 2026-09-16 14:33:20 +00:00
gronod e04d0f0e7f Replace Dallas/OneWire with onewire_bus temp HAL 2026-09-16 14:33:07 +00:00
gronod 114135fba0 Replace AccelStepper with RMT step pulse HAL 2026-09-16 14:32:46 +00:00
gronod 4fe0648005 Replace Arduino tone with LEDC audio HAL 2026-09-16 14:32:12 +00:00
54 changed files with 2057 additions and 502 deletions
+22 -16
View File
@@ -16,19 +16,15 @@ jobs:
- name: Install host toolchain
if: hashFiles('tests/host/CMakeLists.txt') != ''
run: |
export DEBIAN_FRONTEND=noninteractive
if command -v sudo >/dev/null 2>&1 && [ "$(id -u)" -ne 0 ]; then
SUDO=sudo
else
SUDO=
fi
$SUDO apt-get update
$SUDO apt-get install -y --no-install-recommends cmake gcc g++ make
export DEBIAN_FRONTEND=noninteractive && \
if command -v sudo >/dev/null 2>&1 && [ "$(id -u)" -ne 0 ]; then SUDO=sudo; else SUDO=; fi && \
$SUDO apt-get update && \
$SUDO apt-get install -y -q --no-install-recommends cmake gcc g++ make
- name: Host unit tests
if: hashFiles('tests/host/CMakeLists.txt') != ''
run: |
cmake -S tests/host -B build/host
cmake --build build/host
cmake -S tests/host -B build/host && \
cmake --build build/host && \
ctest --test-dir build/host --output-on-failure
firmware:
@@ -47,21 +43,30 @@ jobs:
target: esp32s3
sdkconfig: sdkconfig.s3
steps:
- name: Install Node.js
run: |
export DEBIAN_FRONTEND=noninteractive && \
export NEEDRESTART_MODE=a && \
if command -v sudo >/dev/null 2>&1 && [ "$(id -u)" -ne 0 ]; then SUDO=sudo; else SUDO=; fi && \
$SUDO apt-get update && \
$SUDO apt-get install -y -q --no-install-recommends nodejs
- uses: actions/checkout@v4
- name: Build
if: hashFiles('CMakeLists.txt') != ''
run: |
git config --global --add safe.directory '*'
idf.py -B build/${{ matrix.board }} set-target ${{ matrix.target }}
. $IDF_PATH/export.sh && \
git config --global --add safe.directory '*' && \
idf.py -B build/${{ matrix.board }} set-target ${{ matrix.target }} && \
idf.py -B build/${{ matrix.board }} \
-D SDKCONFIG_DEFAULTS="sdkconfig.defaults;${{ matrix.sdkconfig }}" \
-D AUTOFILM_BOARD=${{ matrix.board }} \
build
idf.py -B build/${{ matrix.board }} size | tee size-${{ matrix.board }}.txt
build && \
idf.py -B build/${{ matrix.board }} size | tee size-${{ matrix.board }}.txt && \
cd build/${{ matrix.board }} && \
python $IDF_PATH/components/esptool_py/esptool/esptool.py \
--chip ${{ matrix.target }} merge_bin \
-o autofilm-${{ matrix.board }}-${{ gitea.sha }}.bin \
@build/${{ matrix.board }}/flash_args
-o ../../autofilm-${{ matrix.board }}-${{ gitea.sha }}.bin \
@flash_args
- uses: actions/upload-artifact@v3
if: hashFiles('CMakeLists.txt') != ''
with:
@@ -74,3 +79,4 @@ jobs:
build/${{ matrix.board }}/flasher_args.json
build/${{ matrix.board }}/sdkconfig
size-${{ matrix.board }}.txt
+1
View File
@@ -4,6 +4,7 @@
.vscode/launch.json
.vscode/ipch
build/
build_host/
sdkconfig
sdkconfig.old
managed_components/
+1 -1
View File
@@ -1,4 +1,4 @@
idf_component_register(SRCS "app_machine.c"
INCLUDE_DIRS "include"
REQUIRES ui_cmd app_process
PRIV_REQUIRES hal_motor hal_temp hal_audio)
PRIV_REQUIRES hal_motor hal_temp hal_audio freertos)
+23 -23
View File
@@ -1,3 +1,6 @@
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "app_machine.h"
#include "app_process.h"
#include "hal_motor.h"
@@ -5,6 +8,7 @@
#include "hal_audio.h"
#include <string.h>
#include <stddef.h>
#define AGITATE_RPM 60u
@@ -16,20 +20,13 @@ static uint32_t s_deadline_ms;
static bool s_have_deadline;
static bool s_resume_pending;
static bool s_auto_advance;
static ui_evt_t s_q[UI_EVT_QUEUE_LEN];
static uint8_t s_q_head;
static uint8_t s_q_tail;
static uint8_t s_q_count;
static QueueHandle_t s_evtq;
static void emit(ui_evt_t ev)
{
if (s_q_count == UI_EVT_QUEUE_LEN) {
s_q_head = (uint8_t)((s_q_head + 1u) % UI_EVT_QUEUE_LEN);
s_q_count--;
if (s_evtq != NULL) {
xQueueSend(s_evtq, &ev, 0);
}
s_q[s_q_tail] = ev;
s_q_tail = (uint8_t)((s_q_tail + 1u) % UI_EVT_QUEUE_LEN);
s_q_count++;
}
static void fill_common(ui_evt_t *ev)
@@ -122,14 +119,13 @@ static void enter_armed(void)
emit_id(EVT_STEP_ARMED);
}
static void start_running(uint32_t now_ms)
static void start_running(void)
{
const process_def_t *p = app_process_get(s_proc);
uint16_t t_s = app_process_time_s(s_proc, s_step);
s_remaining_ms = (uint32_t)t_s * 1000u;
s_deadline_ms = now_ms + s_remaining_ms;
s_have_deadline = true;
s_resume_pending = false;
s_have_deadline = false;
s_resume_pending = true;
if (t_s == 0 || p == NULL) {
hal_motor_enable(false);
@@ -198,10 +194,14 @@ void app_machine_init(void)
s_deadline_ms = 0;
s_have_deadline = false;
s_resume_pending = false;
s_auto_advance = false;
s_q_head = 0;
s_q_tail = 0;
s_q_count = 0;
s_auto_advance = true;
if (s_evtq == NULL) {
s_evtq = xQueueCreate(UI_EVT_QUEUE_LEN, sizeof(ui_evt_t));
} else {
xQueueReset(s_evtq);
}
app_process_init();
hal_motor_init();
hal_temp_init();
@@ -230,13 +230,13 @@ uint32_t app_machine_remaining_ms(void)
int app_machine_last_event(ui_evt_t *out)
{
if (s_q_count == 0 || out == NULL) {
if (s_evtq == NULL || out == NULL) {
return 0;
}
*out = s_q[s_q_head];
s_q_head = (uint8_t)((s_q_head + 1u) % UI_EVT_QUEUE_LEN);
s_q_count--;
if (xQueueReceive(s_evtq, out, 0) == pdTRUE) {
return 1;
}
return 0;
}
void app_machine_on_temp(float c, bool ok)
@@ -336,7 +336,7 @@ void app_machine_handle_cmd(const ui_cmd_t *cmd)
enter_armed();
} else if (s_state == ST_ARMED) {
apply_step_index(cmd->step_index);
start_running(0);
start_running();
} else if (s_state == ST_COMPLETE) {
arm_or_next_from_complete();
}
+5 -5
View File
@@ -56,7 +56,7 @@ static void draw_program_select(void)
static void draw_step_select(const ui_evt_t *ev)
{
char line[21];
char line[32];
char mm[8];
uint16_t ts = ev->time_s;
fmt_mmss(mm, sizeof(mm), ts);
@@ -71,7 +71,7 @@ static void draw_step_select(const ui_evt_t *ev)
static void draw_armed(const ui_evt_t *ev)
{
char line[21];
char line[32];
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));
@@ -84,7 +84,7 @@ static void draw_armed(const ui_evt_t *ev)
static void draw_running(const ui_evt_t *ev)
{
char line[21];
char line[32];
char mm[8];
uint32_t rem_s = (ev->remaining_ms + 999u) / 1000u;
fmt_mmss(mm, sizeof(mm), rem_s);
@@ -98,7 +98,7 @@ static void draw_running(const ui_evt_t *ev)
static void draw_stopped(const ui_evt_t *ev)
{
char line[21];
char line[32];
char mm[8];
uint32_t rem_s = (ev->remaining_ms + 999u) / 1000u;
fmt_mmss(mm, sizeof(mm), rem_s);
@@ -111,7 +111,7 @@ static void draw_stopped(const ui_evt_t *ev)
static void draw_complete(const ui_evt_t *ev)
{
char line[21];
char line[32];
hal_display_clear();
snprintf(line, sizeof(line), "%-10.10s Done", step_name(ev->process_id, ev->step_index));
hal_display_text(0, 0, line);
+20 -5
View File
@@ -19,10 +19,17 @@
* RST 38
* addr 0x5D
*
* Motor:
* EN GPIO_NUM_NC — no spare header pin documented for EN.
* STEP/DIR not assigned. Do not steal QSPI or GT911 pins.
* Agitation may remain on WROOM until a spare is proven.
* Motor (P3 header IO6/7/15/16):
* EN 7 active LOW (disable level 1)
* STEP 16
* DIR 15
*
* DS18B20 temp: 17 (P4 header GND/3V3/17/18, UART1 unused)
*
* Speaker I2S (onboard amp, P7 Speak connector):
* BCLK 42
* LRCLK 2
* DIN 41
*/
const char *board_name(void)
@@ -32,7 +39,7 @@ const char *board_name(void)
gpio_num_t board_pin_motor_en(void)
{
return GPIO_NUM_NC;
return (gpio_num_t)7;
}
int board_motor_en_disable_level(void)
@@ -40,6 +47,14 @@ int board_motor_en_disable_level(void)
return 1;
}
gpio_num_t board_pin_motor_step(void) { return (gpio_num_t)16; }
gpio_num_t board_pin_motor_dir(void) { return (gpio_num_t)15; }
gpio_num_t board_pin_temp(void) { return (gpio_num_t)17; }
gpio_num_t board_pin_spk_bclk(void) { return (gpio_num_t)42; }
gpio_num_t board_pin_spk_lrclk(void) { return (gpio_num_t)2; }
gpio_num_t board_pin_spk_din(void) { return (gpio_num_t)41; }
gpio_num_t board_pin_lcd_cs(void) { return (gpio_num_t)45; }
gpio_num_t board_pin_lcd_sck(void) { return (gpio_num_t)47; }
gpio_num_t board_pin_lcd_d0(void) { return (gpio_num_t)21; }
@@ -4,6 +4,12 @@
const char *board_name(void);
gpio_num_t board_pin_motor_en(void);
int board_motor_en_disable_level(void);
gpio_num_t board_pin_motor_step(void);
gpio_num_t board_pin_motor_dir(void);
gpio_num_t board_pin_temp(void);
gpio_num_t board_pin_spk_bclk(void);
gpio_num_t board_pin_spk_lrclk(void);
gpio_num_t board_pin_spk_din(void);
gpio_num_t board_pin_lcd_cs(void);
gpio_num_t board_pin_lcd_sck(void);
+4
View File
@@ -14,3 +14,7 @@ int board_motor_en_disable_level(void)
{
return 1;
}
gpio_num_t board_pin_motor_step(void) { return (gpio_num_t)12; }
gpio_num_t board_pin_motor_dir(void) { return (gpio_num_t)14; }
gpio_num_t board_pin_temp(void) { return (gpio_num_t)13; }
+1 -2
View File
@@ -1,2 +1 @@
dependencies:
espressif/arduino-esp32: "~3.2.1"
dependencies: {}
+3
View File
@@ -3,3 +3,6 @@
const char *board_name(void);
gpio_num_t board_pin_motor_en(void);
int board_motor_en_disable_level(void);
gpio_num_t board_pin_motor_step(void);
gpio_num_t board_pin_motor_dir(void);
gpio_num_t board_pin_temp(void);
+4 -1
View File
@@ -2,8 +2,11 @@ set(srcs)
set(priv_req driver)
if(IDF_TARGET STREQUAL "esp32")
list(APPEND srcs wroom/hal_audio.cpp)
list(APPEND srcs wroom/hal_audio.c)
list(APPEND priv_req board_wroom)
elseif(IDF_TARGET STREQUAL "esp32s3")
list(APPEND srcs s3/hal_audio.c)
list(APPEND priv_req board_jc4827w543)
else()
list(APPEND srcs stub/hal_audio.c)
endif()
+212
View File
@@ -0,0 +1,212 @@
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "driver/i2s_std.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "board.h"
#define SPK_SAMPLE_RATE 48000
#define TONE_HZ 2000
#define TONE_PERIOD_SMP (SPK_SAMPLE_RATE / TONE_HZ) /* 24 samples */
#define CHUNK_MS 10
#define CHUNK_FRAMES (SPK_SAMPLE_RATE / 1000 * CHUNK_MS) /* 480, multiple of period */
#define TONE_AMPLITUDE 12000
#define DMA_DESC_NUM 4
#define DMA_FRAME_NUM 240
static const char *TAG = "audio";
enum {
AUDIO_SHORT = 1,
AUDIO_ALARM,
AUDIO_CANCEL,
};
static QueueHandle_t s_q;
static TaskHandle_t s_task;
static i2s_chan_handle_t s_tx;
static bool s_chan_on;
static bool s_inited;
static int16_t s_tone[CHUNK_FRAMES * 2];
static int16_t s_silence[CHUNK_FRAMES * 2];
static void chan_on(void)
{
if (!s_chan_on && s_tx != NULL) {
i2s_channel_enable(s_tx);
s_chan_on = true;
}
}
static void chan_off(void)
{
if (s_chan_on && s_tx != NULL) {
size_t w = 0;
i2s_channel_write(s_tx, s_silence, sizeof(s_silence), &w, 50);
i2s_channel_disable(s_tx);
s_chan_on = false;
}
}
static bool drain_cancel(void)
{
int msg;
while (xQueueReceive(s_q, &msg, 0) == pdTRUE) {
if (msg == AUDIO_CANCEL) {
return true;
}
}
return false;
}
/* Write buf for ~ms wall time. DMA pacing makes each 10 ms write block
* in this task only; the UI path never touches I2S. */
static bool play_buf_ms(const int16_t *buf, size_t len, uint32_t ms)
{
TickType_t t0 = xTaskGetTickCount();
while ((xTaskGetTickCount() - t0) < pdMS_TO_TICKS(ms)) {
if (drain_cancel()) {
return false;
}
size_t w = 0;
if (i2s_channel_write(s_tx, buf, len, &w, portMAX_DELAY) != ESP_OK) {
return false;
}
}
return true;
}
static void play_short(void)
{
chan_on();
play_buf_ms(s_tone, sizeof(s_tone), 100);
chan_off();
}
static void play_alarm(void)
{
chan_on();
for (int i = 0; i < 10; i++) {
if (!play_buf_ms(s_tone, sizeof(s_tone), 500)) {
break;
}
if (!play_buf_ms(s_silence, sizeof(s_silence), 250)) {
break;
}
}
chan_off();
}
static void audio_task(void *arg)
{
(void)arg;
int msg;
for (;;) {
if (xQueueReceive(s_q, &msg, portMAX_DELAY) != pdTRUE) {
continue;
}
if (msg == AUDIO_SHORT) {
play_short();
} else if (msg == AUDIO_ALARM) {
play_alarm();
} else if (msg == AUDIO_CANCEL) {
chan_off();
}
}
}
static void post(int msg)
{
if (s_q == NULL) {
return;
}
xQueueSend(s_q, &msg, 0);
}
void autofilm_audio_task(void *arg)
{
audio_task(arg);
}
void hal_audio_init(void)
{
if (s_inited) {
return;
}
gpio_num_t bclk = board_pin_spk_bclk();
gpio_num_t lrclk = board_pin_spk_lrclk();
gpio_num_t din = board_pin_spk_din();
if (bclk == GPIO_NUM_NC || lrclk == GPIO_NUM_NC || din == GPIO_NUM_NC) {
ESP_LOGW(TAG, "no speaker pins on this board");
s_inited = true;
return;
}
for (int i = 0; i < CHUNK_FRAMES; i++) {
int16_t v = ((i % TONE_PERIOD_SMP) < (TONE_PERIOD_SMP / 2))
? TONE_AMPLITUDE : -TONE_AMPLITUDE;
s_tone[i * 2] = v;
s_tone[i * 2 + 1] = v;
}
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
chan_cfg.dma_desc_num = DMA_DESC_NUM;
chan_cfg.dma_frame_num = DMA_FRAME_NUM;
if (i2s_new_channel(&chan_cfg, &s_tx, NULL) != ESP_OK) {
ESP_LOGE(TAG, "i2s_new_channel failed");
s_inited = true;
return;
}
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SPK_SAMPLE_RATE),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
I2S_SLOT_MODE_STEREO),
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = bclk,
.ws = lrclk,
.dout = din,
.din = I2S_GPIO_UNUSED,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
};
if (i2s_channel_init_std_mode(s_tx, &std_cfg) != ESP_OK) {
ESP_LOGE(TAG, "i2s_channel_init_std_mode failed");
i2s_del_channel(s_tx);
s_tx = NULL;
s_inited = true;
return;
}
s_q = xQueueCreate(8, sizeof(int));
if (s_task == NULL) {
xTaskCreate(audio_task, "audio", 3072, NULL, 2, &s_task);
}
s_inited = true;
ESP_LOGI(TAG, "init i2s bclk=%d ws=%d dout=%d", bclk, lrclk, din);
}
void hal_audio_beep_short(void)
{
post(AUDIO_SHORT);
}
void hal_audio_alarm_complete(void)
{
post(AUDIO_ALARM);
}
void hal_audio_alarm_cancel(void)
{
post(AUDIO_CANCEL);
}
@@ -1,4 +1,5 @@
#include <stdint.h>
#include <stdbool.h>
#include "esp_log.h"
#include "driver/ledc.h"
@@ -109,12 +110,12 @@ static void post(int msg)
xQueueSend(s_q, &msg, 0);
}
extern "C" void autofilm_audio_task(void *arg)
void autofilm_audio_task(void *arg)
{
audio_task(arg);
}
extern "C" void hal_audio_init(void)
void hal_audio_init(void)
{
if (s_inited) {
return;
@@ -146,17 +147,17 @@ extern "C" void hal_audio_init(void)
ESP_LOGI(TAG, "init beep pin=%d LEDC", PIN_BEEP);
}
extern "C" void hal_audio_beep_short(void)
void hal_audio_beep_short(void)
{
post(AUDIO_SHORT);
}
extern "C" void hal_audio_alarm_complete(void)
void hal_audio_alarm_complete(void)
{
post(AUDIO_ALARM);
}
extern "C" void hal_audio_alarm_cancel(void)
void hal_audio_alarm_cancel(void)
{
post(AUDIO_CANCEL);
}
+2 -8
View File
@@ -3,14 +3,8 @@ 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)
list(APPEND srcs hal_display_lcd2004.c)
list(APPEND priv_req board_wroom)
elseif(IDF_TARGET STREQUAL "esp32s3")
list(APPEND srcs hal_display_nv3041a.c)
list(APPEND priv_req board_jc4827w543 esp_lcd)
@@ -0,0 +1,190 @@
#include "hal_display.h"
#include <string.h>
#include "driver/i2c_master.h"
#include "esp_rom_sys.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define LCD_ADDR 0x27
#define LCD_COLS 20
#define LCD_ROWS 4
#define PIN_SDA 21
#define PIN_SCL 22
#define LCD_RS 0x01
#define LCD_EN 0x04
#define LCD_BL 0x08
static const char *TAG = "display";
static i2c_master_bus_handle_t s_bus;
static i2c_master_dev_handle_t s_dev;
static bool s_inited;
static const uint8_t s_thermo[8] = {
0b00100,
0b01100,
0b00100,
0b01100,
0b00100,
0b01110,
0b01110,
0b01110,
};
static const uint8_t s_row_off[4] = {0x00, 0x40, 0x14, 0x54};
static void i2c_write(uint8_t b)
{
if (s_dev == NULL) {
return;
}
i2c_master_transmit(s_dev, &b, 1, 50);
}
static void pulse(uint8_t data)
{
i2c_write((uint8_t)(data | LCD_EN | LCD_BL));
esp_rom_delay_us(1);
i2c_write((uint8_t)((data & (uint8_t)~LCD_EN) | LCD_BL));
esp_rom_delay_us(50);
}
static void write4(uint8_t nibble, uint8_t rs)
{
uint8_t data = (uint8_t)((nibble & 0xF0) | rs | LCD_BL);
i2c_write(data);
pulse(data);
}
static void send(uint8_t value, uint8_t rs)
{
write4(value, rs);
write4((uint8_t)(value << 4), rs);
}
static void cmd(uint8_t c)
{
send(c, 0);
if (c == 0x01 || c == 0x02) {
vTaskDelay(pdMS_TO_TICKS(2));
}
}
static void data_byte(uint8_t d)
{
send(d, LCD_RS);
}
static void set_ddram(uint8_t addr)
{
cmd((uint8_t)(0x80 | addr));
}
void hal_display_init(void)
{
if (s_inited) {
return;
}
i2c_master_bus_config_t bus_cfg = {
.i2c_port = I2C_NUM_0,
.sda_io_num = PIN_SDA,
.scl_io_num = PIN_SCL,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.flags = {
.enable_internal_pullup = true,
},
};
if (i2c_new_master_bus(&bus_cfg, &s_bus) != ESP_OK) {
ESP_LOGE(TAG, "i2c bus failed");
return;
}
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = LCD_ADDR,
.scl_speed_hz = 100000,
};
if (i2c_master_bus_add_device(s_bus, &dev_cfg, &s_dev) != ESP_OK) {
ESP_LOGE(TAG, "i2c device failed");
return;
}
vTaskDelay(pdMS_TO_TICKS(50));
write4(0x30, 0);
vTaskDelay(pdMS_TO_TICKS(5));
write4(0x30, 0);
esp_rom_delay_us(150);
write4(0x30, 0);
write4(0x20, 0);
cmd(0x28); /* 4-bit, 2 line */
cmd(0x08);
cmd(0x01);
cmd(0x06);
cmd(0x0C);
cmd(0x40); /* CGRAM 0 */
for (int i = 0; i < 8; i++) {
data_byte(s_thermo[i]);
}
cmd(0x01);
s_inited = true;
ESP_LOGI(TAG, "init LCD 20x4 addr=0x27 sda=21 scl=22 IDF I2C");
}
void hal_display_clear(void)
{
if (!s_inited) {
return;
}
cmd(0x01);
}
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;
}
set_ddram((uint8_t)(s_row_off[row] + col));
int room = LCD_COLS - col;
int n = 0;
while (s[n] != '\0' && n < room) {
data_byte((uint8_t)s[n]);
n++;
}
}
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;
}
set_ddram((uint8_t)(s_row_off[row] + col));
data_byte(0);
}
void hal_display_flush(void)
{
}
void hal_display_softkey(int slot, int slots, const char *label)
{
(void)slot;
(void)slots;
(void)label;
}
@@ -1,98 +0,0 @@
#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)
{
}
extern "C" void hal_display_softkey(int slot, int slots, const char *label)
{
(void)slot;
(void)slots;
(void)label;
}
+2 -9
View File
@@ -3,15 +3,8 @@ 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)
list(APPEND srcs hal_input_keypad.c)
list(APPEND priv_req board_wroom)
elseif(IDF_TARGET STREQUAL "esp32s3")
list(APPEND srcs hal_input_gt911.c)
list(APPEND priv_req board_jc4827w543)
+99
View File
@@ -0,0 +1,99 @@
#include "hal_input.h"
#include "driver/gpio.h"
#include "esp_log.h"
static const char *TAG = "input";
#define ROWS 5
#define COLS 4
static const gpio_num_t s_row_pins[ROWS] = {19, 18, 5, 17, 16};
static const gpio_num_t s_col_pins[COLS] = {15, 2, 0, 4};
static const char s_keys[ROWS][COLS] = {
{'F', 'E', '#', '*'},
{'1', '2', '3', 'U'},
{'4', '5', '6', 'D'},
{'7', '8', '9', 'X'},
{'L', '0', 'R', 'E'}};
static bool s_inited;
static char s_held;
static void cols_idle_high(void)
{
for (int c = 0; c < COLS; c++) {
gpio_set_level(s_col_pins[c], 1);
}
}
static char scan_once(void)
{
for (int c = 0; c < COLS; c++) {
cols_idle_high();
gpio_set_level(s_col_pins[c], 0);
for (int r = 0; r < ROWS; r++) {
if (gpio_get_level(s_row_pins[r]) == 0) {
cols_idle_high();
return s_keys[r][c];
}
}
}
cols_idle_high();
return 0;
}
void hal_input_init(void)
{
if (s_inited) {
return;
}
for (int r = 0; r < ROWS; r++) {
gpio_config_t io = {
.pin_bit_mask = 1ULL << s_row_pins[r],
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io);
}
for (int c = 0; c < COLS; c++) {
gpio_config_t io = {
.pin_bit_mask = 1ULL << s_col_pins[c],
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io);
gpio_set_level(s_col_pins[c], 1);
}
s_held = 0;
s_inited = true;
ESP_LOGI(TAG, "init 5x4 GPIO keypad map byte-identical to src/config.cpp");
}
bool hal_input_pop_key(ui_raw_key_t *out)
{
char k = scan_once();
if (k == 0) {
s_held = 0;
return false;
}
if (k == s_held) {
return false;
}
s_held = k;
if (out != NULL) {
out->key = k;
}
return true;
}
bool hal_input_pop_touch(ui_raw_touch_t *out)
{
(void)out;
return false;
}
-50
View File
@@ -1,50 +0,0 @@
#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;
}
extern "C" bool hal_input_pop_touch(ui_raw_touch_t *out)
{
(void)out;
return false;
}
+5 -8
View File
@@ -3,14 +3,11 @@ set(priv_inc)
set(priv_req driver)
if(IDF_TARGET STREQUAL "esp32")
list(APPEND srcs
wroom/hal_motor.cpp
${CMAKE_CURRENT_LIST_DIR}/../third_party/AccelStepper/AccelStepper.cpp
)
list(APPEND priv_inc
${CMAKE_CURRENT_LIST_DIR}/../third_party/AccelStepper/include
)
list(APPEND priv_req board_wroom espressif__arduino-esp32)
list(APPEND srcs hal_motor.c)
list(APPEND priv_req board_wroom)
elseif(IDF_TARGET STREQUAL "esp32s3")
list(APPEND srcs hal_motor.c)
list(APPEND priv_req board_jc4827w543)
else()
list(APPEND srcs stub/hal_motor.c)
endif()
+284
View File
@@ -0,0 +1,284 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include "esp_err.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_encoder.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "board.h"
#include "hal_motor.h"
#define STEPS_PER_REV 4800
#define DEFAULT_RPM 60
#define ACCEL 9600
#define RMT_RES_HZ 1000000
#define CRUISE_HZ ((DEFAULT_RPM * STEPS_PER_REV) / 60) /* 4800 */
#define RAMP_STAGES 8
#define BATCH 32
static const char *TAG = "motor";
static gpio_num_t s_pin_en = GPIO_NUM_NC;
static gpio_num_t s_pin_dir = GPIO_NUM_NC;
static gpio_num_t s_pin_step = GPIO_NUM_NC;
static int s_en_disable_level = 1;
static TaskHandle_t s_task;
static atomic_uint s_stop_req;
static float s_cw;
static float s_ccw;
static uint32_t s_rpm = DEFAULT_RPM;
static bool s_enabled;
static bool s_inited;
static rmt_channel_handle_t s_chan;
static rmt_encoder_handle_t s_enc;
static void en_disable(void)
{
if (s_pin_en != GPIO_NUM_NC) {
gpio_set_level(s_pin_en, s_en_disable_level);
}
s_enabled = false;
}
static void en_enable(void)
{
if (s_pin_en != GPIO_NUM_NC) {
gpio_set_level(s_pin_en, !s_en_disable_level);
}
s_enabled = true;
}
static void rmt_abort(void)
{
if (s_chan != NULL) {
rmt_disable(s_chan);
rmt_enable(s_chan);
}
}
static uint32_t period_us_for_hz(uint32_t hz)
{
if (hz < 100) {
hz = 100;
}
return RMT_RES_HZ / hz;
}
static bool emit_steps(uint32_t n, uint32_t hz)
{
if (n == 0) {
return true;
}
uint32_t period = period_us_for_hz(hz);
uint32_t half = period / 2;
if (half == 0) {
half = 1;
}
rmt_symbol_word_t sym = {
.duration0 = (uint16_t)half,
.level0 = 1,
.duration1 = (uint16_t)(period - half),
.level1 = 0,
};
while (n > 0) {
if (atomic_load(&s_stop_req) != 0) {
rmt_abort();
return false;
}
uint32_t chunk = n > BATCH ? BATCH : n;
rmt_symbol_word_t burst[BATCH];
for (uint32_t i = 0; i < chunk; i++) {
burst[i] = sym;
}
rmt_transmit_config_t tcfg = {
.loop_count = 0,
};
if (rmt_transmit(s_chan, s_enc, burst, chunk * sizeof(rmt_symbol_word_t), &tcfg) != ESP_OK) {
return false;
}
if (rmt_tx_wait_all_done(s_chan, 2000) != ESP_OK) {
rmt_abort();
return false;
}
n -= chunk;
}
return atomic_load(&s_stop_req) == 0;
}
static bool run_move(long steps)
{
if (steps == 0) {
return true;
}
int dir = (steps > 0) ? 1 : 0;
if (steps < 0) {
steps = -steps;
}
gpio_set_level(s_pin_dir, dir);
uint32_t cruise = (s_rpm * STEPS_PER_REV) / 60;
if (cruise == 0) {
cruise = CRUISE_HZ;
}
/* Short accel table: ACCEL ≈9600 steps/s^2 into cruise. */
uint32_t remaining = (uint32_t)steps;
uint32_t ramp = remaining / 8;
if (ramp > 200) {
ramp = 200;
}
if (ramp < RAMP_STAGES) {
ramp = (remaining < RAMP_STAGES) ? remaining : RAMP_STAGES;
}
uint32_t start_hz = cruise / 4;
if (start_hz < 200) {
start_hz = 200;
}
for (int i = 0; i < RAMP_STAGES && remaining > 0; i++) {
uint32_t n = ramp / RAMP_STAGES;
if (n == 0) {
n = 1;
}
if (n > remaining) {
n = remaining;
}
uint32_t hz = start_hz + ((cruise - start_hz) * (uint32_t)(i + 1)) / RAMP_STAGES;
if (!emit_steps(n, hz)) {
return false;
}
remaining -= n;
}
if (remaining > 0) {
if (!emit_steps(remaining, cruise)) {
return false;
}
}
return true;
}
static void motor_task(void *arg)
{
(void)arg;
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (atomic_load(&s_stop_req) != 0) {
rmt_abort();
en_disable();
continue;
}
en_enable();
while (atomic_load(&s_stop_req) == 0) {
if (!run_move((long)((float)STEPS_PER_REV * s_cw))) {
break;
}
if (atomic_load(&s_stop_req) != 0) {
break;
}
if (!run_move((long)(-((float)STEPS_PER_REV * s_ccw)))) {
break;
}
}
rmt_abort();
en_disable();
}
}
void autofilm_motor_task(void *arg)
{
motor_task(arg);
}
void hal_motor_init(void)
{
if (s_inited) {
return;
}
s_pin_en = board_pin_motor_en();
s_pin_dir = board_pin_motor_dir();
s_pin_step = board_pin_motor_step();
s_en_disable_level = board_motor_en_disable_level();
if (s_pin_en == GPIO_NUM_NC || s_pin_dir == GPIO_NUM_NC || s_pin_step == GPIO_NUM_NC) {
ESP_LOGW(TAG, "no motor pins on this board");
s_inited = true;
return;
}
gpio_config_t io = {
.pin_bit_mask = (1ULL << s_pin_en) | (1ULL << s_pin_dir),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io);
gpio_set_level(s_pin_en, s_en_disable_level);
gpio_set_level(s_pin_dir, 0);
s_enabled = false;
atomic_store(&s_stop_req, 0);
rmt_tx_channel_config_t txcfg = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.gpio_num = s_pin_step,
.mem_block_symbols = 64,
.resolution_hz = RMT_RES_HZ,
.trans_queue_depth = 4,
};
rmt_new_tx_channel(&txcfg, &s_chan);
rmt_copy_encoder_config_t enc_cfg = {};
rmt_new_copy_encoder(&enc_cfg, &s_enc);
rmt_enable(s_chan);
if (s_task == NULL) {
xTaskCreatePinnedToCore(motor_task, "motor", 4096, NULL,
configMAX_PRIORITIES - 2, &s_task, 0);
}
s_inited = true;
ESP_LOGI(TAG, "init en=%d step=%d dir=%d dis_lvl=%d",
s_pin_en, s_pin_step, s_pin_dir, s_en_disable_level);
}
void hal_motor_enable(bool on)
{
if (on) {
en_enable();
} else {
en_disable();
}
}
void hal_motor_request_stop(void)
{
atomic_store(&s_stop_req, 1);
en_disable();
rmt_abort();
if (s_task != NULL) {
xTaskNotifyGive(s_task);
}
}
esp_err_t hal_motor_agitate_start(float cw_revs, float ccw_revs, uint32_t rpm)
{
s_cw = cw_revs;
s_ccw = ccw_revs;
s_rpm = (rpm == 0) ? DEFAULT_RPM : rpm;
atomic_store(&s_stop_req, 0);
if (s_task != NULL) {
xTaskNotifyGive(s_task);
}
return ESP_OK;
}
void hal_motor_agitate_stop(void)
{
atomic_store(&s_stop_req, 1);
rmt_abort();
en_disable();
}
bool hal_motor_is_enabled(void)
{
return s_enabled;
}
-153
View File
@@ -1,153 +0,0 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include "esp_err.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <Arduino.h>
#include <AccelStepper.h>
#define PIN_STEP 12
#define PIN_DIR 14
#define PIN_EN 27
#define STEPS_PER_REV 4800
#define DEFAULT_RPM 60
#define ACCEL 9600
static const char *TAG = "motor";
static AccelStepper s_stepper(AccelStepper::DRIVER, PIN_STEP, PIN_DIR);
static TaskHandle_t s_task;
static atomic_uint s_stop_req;
static float s_cw;
static float s_ccw;
static uint32_t s_rpm = DEFAULT_RPM;
static bool s_enabled;
static bool s_inited;
static void en_disable(void)
{
gpio_set_level((gpio_num_t)PIN_EN, 1);
s_enabled = false;
}
static void en_enable(void)
{
gpio_set_level((gpio_num_t)PIN_EN, 0);
s_enabled = true;
}
static void run_move(long steps)
{
s_stepper.setCurrentPosition(0);
s_stepper.moveTo(steps);
while (s_stepper.distanceToGo() != 0) {
if (atomic_load(&s_stop_req) != 0) {
break;
}
s_stepper.run();
}
}
static void motor_task(void *arg)
{
(void)arg;
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (atomic_load(&s_stop_req) != 0) {
en_disable();
continue;
}
float speed = ((float)s_rpm * (float)STEPS_PER_REV) / 60.0f;
s_stepper.setMaxSpeed(speed);
s_stepper.setAcceleration((float)ACCEL);
en_enable();
while (atomic_load(&s_stop_req) == 0) {
run_move((long)((float)STEPS_PER_REV * s_cw));
if (atomic_load(&s_stop_req) != 0) {
break;
}
run_move((long)(-((float)STEPS_PER_REV * s_ccw)));
}
en_disable();
}
}
extern "C" void autofilm_motor_task(void *arg)
{
motor_task(arg);
}
extern "C" void hal_motor_init(void)
{
if (s_inited) {
return;
}
gpio_config_t io = {
.pin_bit_mask = (1ULL << PIN_EN) | (1ULL << PIN_STEP) | (1ULL << PIN_DIR),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io);
gpio_set_level((gpio_num_t)PIN_EN, 1);
gpio_set_level((gpio_num_t)PIN_STEP, 0);
gpio_set_level((gpio_num_t)PIN_DIR, 0);
s_enabled = false;
atomic_store(&s_stop_req, 0);
s_stepper.setMaxSpeed(((float)DEFAULT_RPM * (float)STEPS_PER_REV) / 60.0f);
s_stepper.setAcceleration((float)ACCEL);
if (s_task == NULL) {
xTaskCreatePinnedToCore(motor_task, "motor", 4096, NULL,
configMAX_PRIORITIES - 2, &s_task, 0);
}
s_inited = true;
ESP_LOGI(TAG, "init EN=HIGH step=%d dir=%d", PIN_STEP, PIN_DIR);
}
extern "C" void hal_motor_enable(bool on)
{
if (on) {
en_enable();
} else {
en_disable();
}
}
extern "C" void hal_motor_request_stop(void)
{
atomic_store(&s_stop_req, 1);
gpio_set_level((gpio_num_t)PIN_EN, 1);
s_enabled = false;
if (s_task != NULL) {
xTaskNotifyGive(s_task);
}
}
extern "C" esp_err_t hal_motor_agitate_start(float cw_revs, float ccw_revs, uint32_t rpm)
{
s_cw = cw_revs;
s_ccw = ccw_revs;
s_rpm = (rpm == 0) ? DEFAULT_RPM : rpm;
atomic_store(&s_stop_req, 0);
if (s_task != NULL) {
xTaskNotifyGive(s_task);
}
return ESP_OK;
}
extern "C" void hal_motor_agitate_stop(void)
{
atomic_store(&s_stop_req, 1);
en_disable();
}
extern "C" bool hal_motor_is_enabled(void)
{
return s_enabled;
}
+6 -11
View File
@@ -1,18 +1,13 @@
set(srcs)
set(priv_inc)
set(priv_req driver)
set(priv_req driver esp_timer)
if(IDF_TARGET STREQUAL "esp32")
list(APPEND srcs
wroom/hal_temp.cpp
${CMAKE_CURRENT_LIST_DIR}/../third_party/OneWire/OneWire.cpp
${CMAKE_CURRENT_LIST_DIR}/../third_party/DallasTemperature/DallasTemperature.cpp
)
list(APPEND priv_inc
${CMAKE_CURRENT_LIST_DIR}/../third_party/OneWire/include
${CMAKE_CURRENT_LIST_DIR}/../third_party/DallasTemperature/include
)
list(APPEND priv_req board_wroom espressif__arduino-esp32)
list(APPEND srcs hal_temp.c)
list(APPEND priv_req board_wroom onewire_bus)
elseif(IDF_TARGET STREQUAL "esp32s3")
list(APPEND srcs hal_temp.c)
list(APPEND priv_req board_jc4827w543 onewire_bus)
else()
list(APPEND srcs stub/hal_temp.c)
endif()
+157
View File
@@ -0,0 +1,157 @@
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "onewire_bus.h"
#include "board.h"
#include "hal_temp.h"
#define TEMP_OFFSET 0.4f
#define CMD_SKIP_ROM 0xCC
#define CMD_CONVERT_T 0x44
#define CMD_READ_SCRATCH 0xBE
static const char *TAG = "temp";
#define TEMP_CONV_MS 750
typedef enum {
T_START,
T_WAIT,
} temp_state_t;
static onewire_bus_handle_t s_bus;
static bool s_inited;
static float s_last_c;
static bool s_last_ok;
static temp_state_t s_state = T_START;
static uint32_t s_deadline_ms;
static uint32_t now_ms(void)
{
return (uint32_t)(esp_timer_get_time() / 1000ULL);
}
static bool scratch_valid(const uint8_t *sp)
{
/* DS18B20 CRC8 over first 8 bytes, poly 0x8C reflected. */
uint8_t crc = 0;
for (int i = 0; i < 8; i++) {
uint8_t in = sp[i];
for (int b = 0; b < 8; b++) {
uint8_t mix = (crc ^ in) & 0x01;
crc >>= 1;
if (mix) {
crc ^= 0x8C;
}
in >>= 1;
}
}
return crc == sp[8];
}
void hal_temp_tick(void)
{
if (s_bus == NULL) {
s_last_ok = false;
return;
}
uint32_t now = now_ms();
if (s_state == T_START) {
if (now < s_deadline_ms) {
return;
}
if (onewire_bus_reset(s_bus) != ESP_OK) {
s_last_ok = false;
s_deadline_ms = now + TEMP_CONV_MS;
return;
}
uint8_t conv[2] = {CMD_SKIP_ROM, CMD_CONVERT_T};
if (onewire_bus_write_bytes(s_bus, conv, 2) != ESP_OK) {
s_last_ok = false;
s_deadline_ms = now + TEMP_CONV_MS;
return;
}
s_deadline_ms = now + TEMP_CONV_MS;
s_state = T_WAIT;
return;
}
/* T_WAIT */
if (now < s_deadline_ms) {
return;
}
s_state = T_START;
s_deadline_ms = now;
if (onewire_bus_reset(s_bus) != ESP_OK) {
s_last_ok = false;
s_deadline_ms = now + TEMP_CONV_MS;
return;
}
uint8_t rd[2] = {CMD_SKIP_ROM, CMD_READ_SCRATCH};
if (onewire_bus_write_bytes(s_bus, rd, 2) != ESP_OK) {
s_last_ok = false;
s_deadline_ms = now + TEMP_CONV_MS;
return;
}
uint8_t sp[9];
memset(sp, 0, sizeof(sp));
if (onewire_bus_read_bytes(s_bus, sp, sizeof(sp)) != ESP_OK) {
s_last_ok = false;
s_deadline_ms = now + TEMP_CONV_MS;
return;
}
if (!scratch_valid(sp)) {
s_last_ok = false;
s_deadline_ms = now + TEMP_CONV_MS;
return;
}
int16_t raw = (int16_t)((uint16_t)sp[0] | ((uint16_t)sp[1] << 8));
float t = ((float)raw) / 16.0f + TEMP_OFFSET;
s_last_c = t;
s_last_ok = true;
}
void hal_temp_init(void)
{
if (s_inited) {
return;
}
gpio_num_t pin = board_pin_temp();
if (pin == GPIO_NUM_NC) {
ESP_LOGW(TAG, "no temp pin on this board");
s_inited = true;
return;
}
onewire_bus_config_t bus_config = {
.bus_gpio_num = pin,
.flags = {
.en_pull_up = true,
},
};
onewire_bus_rmt_config_t rmt_config = {
.max_rx_bytes = 10,
};
if (onewire_new_bus_rmt(&bus_config, &rmt_config, &s_bus) != ESP_OK) {
s_bus = NULL;
ESP_LOGE(TAG, "onewire_bus install failed pin=%d", pin);
}
s_last_ok = false;
s_last_c = 0.0f;
s_inited = true;
ESP_LOGI(TAG, "init onewire_bus pin=%d offset=%.1f", pin, (double)TEMP_OFFSET);
}
esp_err_t hal_temp_read_c(float *out)
{
if (!s_last_ok) {
return ESP_FAIL;
}
if (out != NULL) {
*out = s_last_c;
}
return ESP_OK;
}
+2
View File
@@ -0,0 +1,2 @@
dependencies:
espressif/onewire_bus: "^1.1.0"
+1
View File
@@ -7,4 +7,5 @@
#endif
void hal_temp_init(void);
void hal_temp_tick(void); /* blocking conversion poll; blocks >=750 ms */
esp_err_t hal_temp_read_c(float *out); /* ESP_FAIL → disconnected */
+8
View File
@@ -1,9 +1,17 @@
#include "hal_temp.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void hal_temp_init(void)
{
}
void hal_temp_tick(void)
{
vTaskDelay(pdMS_TO_TICKS(750));
}
esp_err_t hal_temp_read_c(float *out)
{
(void)out;
-66
View File
@@ -1,66 +0,0 @@
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <Arduino.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#define PIN_DS 13
#define TEMP_OFFSET 0.4f
static const char *TAG = "temp";
static OneWire s_wire(PIN_DS);
static DallasTemperature s_sensors(&s_wire);
static bool s_inited;
static float s_last_c;
static bool s_last_ok;
extern "C" void autofilm_temp_tick(void)
{
s_sensors.requestTemperatures();
vTaskDelay(pdMS_TO_TICKS(750));
float t = s_sensors.getTempCByIndex(0);
bool ok = (t != DEVICE_DISCONNECTED_C);
if (ok) {
t += TEMP_OFFSET;
}
s_last_c = ok ? t : 0.0f;
s_last_ok = ok;
}
extern "C" void autofilm_temp_task(void *arg)
{
(void)arg;
for (;;) {
autofilm_temp_tick();
}
}
extern "C" void hal_temp_init(void)
{
if (s_inited) {
return;
}
s_sensors.begin();
s_last_ok = false;
s_last_c = 0.0f;
s_inited = true;
ESP_LOGI(TAG, "init OneWire pin=%d offset=%.1f", PIN_DS, (double)TEMP_OFFSET);
}
extern "C" esp_err_t hal_temp_read_c(float *out)
{
if (!s_last_ok) {
return ESP_FAIL;
}
if (out != NULL) {
*out = s_last_c;
}
return ESP_OK;
}
+2
View File
@@ -44,6 +44,8 @@ From `include/config.h` and `src/config.cpp`:
Motor constants: `STEPS_PER_REV = 4800`, `RPM = 60`, acceleration `9600`. Enable is driven HIGH at boot (disabled).
Note — S3 panel (JC4827W543C) pin lock via `board_jc4827w543` accessors: motor EN 7 / STEP 16 / DIR 15 on header P3 (IO6/7/15/16), DS18B20 on 17 on header P4 (GND/3V3/17/18, UART1 unused), speaker amp I2S BCLK 42 / LRCLK 2 / DIN 41 (P7 Speak). Header P2 carries IO46/9/14/5 and is left unused (IO46 is input-only).
Keypad map (5×4):
```
+174
View File
@@ -0,0 +1,174 @@
# JC4827W543 pin audit
Comparison of the GPIO assignments in this tree against the vendor
documentation at <https://github.com/lsdlsd88/JC4827W543> (GUITION
factory docs: spec PDF, IO pin distribution xlsx, Arduino demos).
Board variants: `JC4827W543N` (no touch), `...R` (resistive XPT2046),
`...C` (capacitive GT911). Ours is the **C** model.
## 1. Vendor-documented pin allocation (authoritative)
From `5-IO pin distribution/4.3 inches IO pin distribution.xlsx`,
cross-checked against the demo sketches
(`1-Demo/Demo_Arduino/3_3-*` and `4_*`):
| GPIO | Vendor function | Notes |
| --- | --- | --- |
| 0 | BOOT button | also wired to `LCD_TE` |
| 1 | `BL_CTRL` | LCD backlight |
| 2 | `SPECK_LRCLK` | onboard speaker amp I2S WS |
| 3 | `CTP_INT` / `RTP_IRQ` | touch interrupt (both models) |
| 4 | `CTP_SCL` | GT911 I2C SCL |
| 5 | free | |
| 6 | free | |
| 7 | free | |
| 8 | `CTP_SDA` | GT911 I2C SDA |
| 9 | free | |
| 10 | `TF_CS` | microSD slot |
| 11 | `RTP_DIN` / `TF_MISO` | shared SPI: resistive touch / SD |
| 12 | `RTP_CLK` / `TF_CLK` | shared SPI |
| 13 | `RTP_DIO` / `TF_MOSI` | shared SPI |
| 14 | free | |
| 15 | free | exposed on header (P2/P3 area) |
| 16 | free | exposed on header; used by vendor LED demo |
| 17 | `U1TXD` | UART1 header (P5), usable if UART1 unused |
| 18 | `U1RXD` | UART1 header (P5), usable if UART1 unused |
| 19 | `USB+` | USB D+ |
| 20 | `USB-` | USB D- |
| 21 | `LCD_A0` | QSPI data 0 |
| 22–25 | — | do not exist on ESP32-S3 |
| 26–34 | — | flash/PSRAM or non-existent |
| 35 | not available | octal PSRAM (xlsx marks explicitly) |
| 36, 37 | free per xlsx | **but** consumed by octal PSRAM on N8R8/R8 modules — treat as unavailable until module variant confirmed |
| 38 | `RTP_CS` (resistive model) | on the **C** model this is the GT911 reset line — see §3 |
| 39 | `LCD_A3` | QSPI data 3 |
| 40 | `LCD_A2` | QSPI data 2 |
| 41 | `SPECK_DIN` | speaker amp I2S data in |
| 42 | `SPECK_BCLK` | speaker amp I2S BCLK |
| 43 | `U0TXD` | console |
| 44 | `U0RXD` | console |
| 45 | `LCD_CS` | QSPI chip select |
| 46 | free per xlsx | strapping pin (`LOG`); output-only-ish, use with care |
| 47 | `LCD_CLK` | QSPI clock |
| 48 | `LCD_A1` | QSPI data 1 |
The board carries an onboard I2S speaker amplifier (datasheets for
NS4168 / AX9835 ship in `4-Driver_IC_Data_Sheet`), a `Speak` connector
(P7), `BAT` connector (P6), TF slot, UART0 console, UART1 header and a
scattering of free-IO headers (P2–P5: GPIO 15, 16, 17, 18 visible on
the structure diagram). Vendor demos drive LEDs on GPIO 16/17 and a
DHT11 on GPIO 27 — note GPIO 27 is **not** in the vendor pin table,
so that demo value is suspect/generic.
## 2. What the code assigns today
### `board_jc4827w543` (`components/board_jc4827w543/board.c`)
| Signal | Code GPIO | Vendor | Match |
| --- | --- | --- | --- |
| LCD CS | 45 | 45 `LCD_CS` | yes |
| LCD SCK | 47 | 47 `LCD_CLK` | yes |
| LCD D0 | 21 | 21 `LCD_A0` | yes |
| LCD D1 | 48 | 48 `LCD_A1` | yes |
| LCD D2 | 40 | 40 `LCD_A2` | yes |
| LCD D3 | 39 | 39 `LCD_A3` | yes |
| LCD BL | 1 | 1 `BL_CTRL` | yes |
| TP SDA | 8 | 8 `CTP_SDA` | yes |
| TP SCL | 4 | 4 `CTP_SCL` | yes |
| TP INT | 3 | 3 `CTP_INT` | yes |
| TP RST | 38 | 38 (demo: `TOUCH_RES 38`) | yes — see §3 |
| Motor EN | `GPIO_NUM_NC` | n/a | deliberately unassigned |
### `hal_display_nv3041a.c`
Driver **NV3041A** over QSPI via `esp_lcd` SPI host, 480×272, quad
mode, 40 MHz. Matches the vendor demo
(`Arduino_ESP32QSPI(45,47,21,48,40,39)` + `Arduino_NV3041A`,
`Arduino_Canvas(480,272)`).
Caveat: the spec sheet PDF lists the driver chip as "ST3401A" — that
string appears nowhere else; every demo and the burn files use
NV3041A. Treat the PDF label as a typo.
### `hal_input_gt911.c`
GT911 at I2C addr `0x5D` (`GT911_SLAVE_ADDRESS1` in the vendor
`touch.h`), I2C_NUM_0 @ 400 kHz on SDA 8 / SCL 4. Reset sequence
(drive INT low, pulse RST, release INT to input) selects the 0x5D
address per the GT911 datasheet — matches the vendor demo's
`TOUCH_RES 38` / `TOUCH_INT 3` wiring.
### `board_wroom` + `hal_*` (classic ESP32, unchanged)
| Signal | GPIO | Source |
| --- | --- | --- |
| Stepper STEP | 12 | `config.h`, `hal_motor.c` |
| Stepper DIR | 14 | `config.h`, `hal_motor.c` |
| Stepper EN (active LOW) | 27 | `config.h`, `hal_motor.c`, `board_wroom` |
| DS18B20 | 13 | `config.h`, `hal_temp.c` |
| Beeper | 25 | `config.cpp`, `hal_audio.c` |
| LCD 2004 I2C | SDA 21 / SCL 22, addr 0x27 | `config.cpp`, `hal_display_lcd2004.c` |
| Keypad rows | 19, 18, 5, 17, 16 | `config.cpp`, `hal_input_keypad.c` |
| Keypad cols | 15, 2, 0, 4 | `config.cpp`, `hal_input_keypad.c` |
Self-consistent with `docs/CURRENT_STATE.md`. Not related to the S3
panel.
## 3. Findings / discrepancies
1. **Display and touch pins are all correct.** Every QSPI, backlight
and GT911 pin in `board_jc4827w543/board.c` matches both the
vendor xlsx and the vendor Arduino demos. No changes needed.
2. **TP_RST on GPIO 38 is undocumented for the C model.** The xlsx
labels IO38 `RTP_CS` (its resistive-touch function). The capacitive
demos (`LvglWidgets/touch.h`) set `TOUCH_RES 38`, so on the C
variant IO38 is clearly the GT911 reset. Our code is right, but the
vendor table alone would not tell you that — the demo code is the
authority here.
3. **Motor EN/STEP/DIR are unassigned on the S3 board** (`GPIO_NUM_NC`
in `board.c`). Correct call — but usable free pins do exist.
Candidates from the vendor table + headers:
- Safest (plain free, on headers): **15, 16** and UART1 header
**17, 18** if UART1 is unused.
- Free in xlsx but not obviously headered: **5, 6, 7, 9, 14**.
- **Avoid**: 0 (BOOT/LCD_TE), 1–4/8 (LCD+touch+speaker), 10–13
(TF/RTP SPI), 19/20 (USB), 21 (LCD D0), 35–37 (octal PSRAM),
38–48 (used/strapping), 43/44 (console).
- GPIO 46 is xlsx-free but is a strapping pin — last resort only.
- Octal PSRAM (`sdkconfig.s3`: `CONFIG_SPIRAM_MODE_OCT=y`) means
33–37 are off-limits even though the xlsx only flags 35.
4. **Audio on S3 should use I2S, not a beeper GPIO.** The board has a
speaker amp on IO2 (LRCLK), IO41 (DIN), IO42 (BCLK) — P7 `Speak`
connector. `hal_audio` currently builds the LEDC buzzer only for
esp32 and a stub elsewhere; an S3 implementation should drive
I2S on those pins rather than allocating a GPIO.
5. **DS18B20 on S3 needs one free GPIO** (any of §3's candidates; it
only needs input+open-drain drive). Hal currently stubs temp on
non-esp32.
6. **Reserved/bus-shared notes**: GPIO 0 carries `LCD_TE` in addition
to BOOT — do not use it for anything else. TF card SPI (10–13) is
shared with resistive-touch signals on R models; on our C model
it's purely the SD slot.
7. **No conflicts found** between the code's S3 selections and the
vendor map — every consumed pin is one the vendor assigns to that
same function.
## Suggested motor wiring (for when S3 motor support lands)
| Signal | Suggested GPIO | Rationale |
| --- | --- | --- |
| STEP | 16 | free, on header, no boot function |
| DIR | 15 | free, on header |
| EN (active LOW) | 17 or 18 | UART1 header; free if UART1 unused |
| DS18B20 | 9 or 14 | free, any GPIO works |
If UART1 is wanted for something else, use 5/6/7 for EN/temp instead.
Keep motor EN's disable-first behaviour (drive inactive at boot before
enabling output mode) whatever pins are chosen.
+14
View File
@@ -0,0 +1,14 @@
# JC4827W543 pin audit - ERRATA
*Based on verification against the official vendor repository at <https://github.com/lsdlsd88/JC4827W543>.*
**No errors were found in the original `JC4827W543_PIN_AUDIT.md` document.**
### Verification Notes:
- **Vendor IO pin distribution:** The `4.3 inches IO pin distribution.xlsx` file perfectly matches the pinout matrix in the audit document (e.g., IO45 for LCD_CS, IO47 for LCD_CLK, etc., and SPECK_LRCLK / SPECK_DIN / SPECK_BCLK correctly documented as I2S lines).
- **Display Configuration:** `Arduino_ESP32QSPI` code in `LvglWidgets.ino` verifies the QSPI display pins as `CS:45, SCK:47, D0:21, D1:48, D2:40, D3:39` matching the audit exactly.
- **Touch Controller:** Capacitive GT911 configuration uses `TOUCH_SDA = 8`, `TOUCH_SCL = 4`, `TOUCH_RES = 38`, `TOUCH_INT = 3` which aligns with the undocumented `RTP_CS (38)` being effectively co-opted for capacitive reset, confirming the finding in the original document.
- **Demos:** The DHT11 demo at `1-Demo/Demo_Arduino/4_9_WIFI Web Servers DHT11` indeed uses `GPIO 27`, and the `4_7_WIFI Web Servers LED` demo uses `GPIO 16/17`. These assignments are generic demo choices as noted in the original document and correctly do not conflict with the board specifications.
- **Proposed GPIOs:** The suggested GPIOs for motor control (`16`, `15`, `17`, `18`, `9`, `14`, `5`, `6`, `7`) are correctly identified as free based on the schematic and code.
The original document is factually sound and can be completely trusted as the authoritative reference for this project.
+184
View File
@@ -0,0 +1,184 @@
# JC4827W543 pin audit
Comparison of the GPIO assignments in this tree against the vendor
documentation at <https://github.com/lsdlsd88/JC4827W543> (GUITION
factory docs: spec PDF, IO pin distribution xlsx, Arduino demos).
Board variants: `JC4827W543N` (no touch), `...R` (resistive XPT2046),
`...C` (capacitive GT911). Ours is the **C** model.
## 1. Vendor-documented pin allocation (authoritative)
From `5-IO pin distribution/4.3 inches IO pin distribution.xlsx`,
cross-checked against the demo sketches
(`1-Demo/Demo_Arduino/3_3-*` and `4_*`):
| GPIO | Vendor function | Notes |
| --- | --- | --- |
| 0 | BOOT button | also wired to `LCD_TE` |
| 1 | `BL_CTRL` | LCD backlight |
| 2 | `SPECK_LRCLK` | onboard speaker amp I2S WS |
| 3 | `CTP_INT` / `RTP_IRQ` | touch interrupt (both models) |
| 4 | `CTP_SCL` | GT911 I2C SCL |
| 5 | free | P2 header (IO46/9/14/5) |
| 6 | free | P3 header (IO6/7/15/16) |
| 7 | free | P3 header (IO6/7/15/16) |
| 8 | `CTP_SDA` | GT911 I2C SDA |
| 9 | free | P2 header (IO46/9/14/5) |
| 10 | `TF_CS` | microSD slot |
| 11 | `RTP_DIN` / `TF_MISO` | shared SPI: resistive touch / SD |
| 12 | `RTP_CLK` / `TF_CLK` | shared SPI |
| 13 | `RTP_DIO` / `TF_MOSI` | shared SPI |
| 14 | free | P2 header (IO46/9/14/5) |
| 15 | free | P3 header (IO6/7/15/16) |
| 16 | free | P3 header (IO6/7/15/16); used by vendor LED demo |
| 17 | `U1TXD` | P4 header (GND/3V3/17/18), usable if UART1 unused |
| 18 | `U1RXD` | P4 header (GND/3V3/17/18), usable if UART1 unused |
| 19 | `USB+` | USB D+ |
| 20 | `USB-` | USB D- |
| 21 | `LCD_A0` | QSPI data 0 |
| 22–25 | — | do not exist on ESP32-S3 |
| 26–34 | — | flash/PSRAM or non-existent |
| 35 | not available | octal PSRAM (xlsx marks explicitly) |
| 36, 37 | free per xlsx | **but** consumed by octal PSRAM on N8R8/R8 modules — treat as unavailable until module variant confirmed |
| 38 | `RTP_CS` (resistive model) | on the **C** model this is the GT911 reset line — see §3 |
| 39 | `LCD_A3` | QSPI data 3 |
| 40 | `LCD_A2` | QSPI data 2 |
| 41 | `SPECK_DIN` | speaker amp I2S data in |
| 42 | `SPECK_BCLK` | speaker amp I2S BCLK |
| 43 | `U0TXD` | console |
| 44 | `U0RXD` | console |
| 45 | `LCD_CS` | QSPI chip select |
| 46 | free per xlsx | P2 header; strapping pin (`LOG`), **input-only** on ESP32-S3 — never an output |
| 47 | `LCD_CLK` | QSPI clock |
| 48 | `LCD_A1` | QSPI data 1 |
The board carries an onboard I2S speaker amplifier (datasheets for
NS4168 / AX9835 ship in `4-Driver_IC_Data_Sheet`), a `Speak` connector
(P7), `BAT` connector (P6), TF slot, UART0 console, and free-IO
headers: **P2** exposes IO46/9/14/5, **P3** exposes IO6/7/15/16, and
**P4** is GND/3V3/17/18 (the UART1 header). Vendor demos drive LEDs on GPIO 16/17 and a
DHT11 on GPIO 27 — note GPIO 27 is **not** in the vendor pin table,
so that demo value is suspect/generic.
## 2. What the code assigns today
### `board_jc4827w543` (`components/board_jc4827w543/board.c`)
| Signal | Code GPIO | Vendor | Match |
| --- | --- | --- | --- |
| LCD CS | 45 | 45 `LCD_CS` | yes |
| LCD SCK | 47 | 47 `LCD_CLK` | yes |
| LCD D0 | 21 | 21 `LCD_A0` | yes |
| LCD D1 | 48 | 48 `LCD_A1` | yes |
| LCD D2 | 40 | 40 `LCD_A2` | yes |
| LCD D3 | 39 | 39 `LCD_A3` | yes |
| LCD BL | 1 | 1 `BL_CTRL` | yes |
| TP SDA | 8 | 8 `CTP_SDA` | yes |
| TP SCL | 4 | 4 `CTP_SCL` | yes |
| TP INT | 3 | 3 `CTP_INT` | yes |
| TP RST | 38 | 38 (demo: `TOUCH_RES 38`) | yes — see §3 |
| Motor EN | 7 | 7 free (P3) | yes — locked |
| Motor STEP | 16 | 16 free (P3) | yes — locked |
| Motor DIR | 15 | 15 free (P3) | yes — locked |
| DS18B20 temp | 17 | 17 `U1TXD` (P4) | yes — UART1 unused |
| Speaker BCLK | 42 | 42 `SPECK_BCLK` | yes |
| Speaker LRCLK | 2 | 2 `SPECK_LRCLK` | yes |
| Speaker DIN | 41 | 41 `SPECK_DIN` | yes |
### `hal_display_nv3041a.c`
Driver **NV3041A** over QSPI via `esp_lcd` SPI host, 480×272, quad
mode, 40 MHz. Matches the vendor demo
(`Arduino_ESP32QSPI(45,47,21,48,40,39)` + `Arduino_NV3041A`,
`Arduino_Canvas(480,272)`).
Caveat: the spec sheet PDF lists the driver chip as "ST3401A" — that
string appears nowhere else; every demo and the burn files use
NV3041A. Treat the PDF label as a typo.
### `hal_input_gt911.c`
GT911 at I2C addr `0x5D` (`GT911_SLAVE_ADDRESS1` in the vendor
`touch.h`), I2C_NUM_0 @ 400 kHz on SDA 8 / SCL 4. Reset sequence
(drive INT low, pulse RST, release INT to input) selects the 0x5D
address per the GT911 datasheet — matches the vendor demo's
`TOUCH_RES 38` / `TOUCH_INT 3` wiring.
### `board_wroom` + `hal_*` (classic ESP32, unchanged)
| Signal | GPIO | Source |
| --- | --- | --- |
| Stepper STEP | 12 | `config.h`, `hal_motor.c` |
| Stepper DIR | 14 | `config.h`, `hal_motor.c` |
| Stepper EN (active LOW) | 27 | `config.h`, `hal_motor.c`, `board_wroom` |
| DS18B20 | 13 | `config.h`, `hal_temp.c` |
| Beeper | 25 | `config.cpp`, `hal_audio.c` |
| LCD 2004 I2C | SDA 21 / SCL 22, addr 0x27 | `config.cpp`, `hal_display_lcd2004.c` |
| Keypad rows | 19, 18, 5, 17, 16 | `config.cpp`, `hal_input_keypad.c` |
| Keypad cols | 15, 2, 0, 4 | `config.cpp`, `hal_input_keypad.c` |
Self-consistent with `docs/CURRENT_STATE.md`. Not related to the S3
panel.
## 3. Findings / discrepancies
1. **Display and touch pins are all correct.** Every QSPI, backlight
and GT911 pin in `board_jc4827w543/board.c` matches both the
vendor xlsx and the vendor Arduino demos. No changes needed.
2. **TP_RST on GPIO 38 is undocumented for the C model.** The xlsx
labels IO38 `RTP_CS` (its resistive-touch function). The capacitive
demos (`LvglWidgets/touch.h`) set `TOUCH_RES 38`, so on the C
variant IO38 is clearly the GT911 reset. Our code is right, but the
vendor table alone would not tell you that — the demo code is the
authority here.
3. **Motor EN/STEP/DIR and DS18B20 are now locked on the S3 board.**
Header labels confirmed: P2 exposes IO46/9/14/5, P3 exposes
IO6/7/15/16, P4 is GND/3V3/17/18 (the UART1 header).
- Motor STEP **16**, DIR **15**, EN **7** (active LOW) — all on P3;
IO6 stays spare.
- DS18B20 on **17** — P4 puts GND, 3V3 and data on one header;
UART1 is unused.
- Speaker amp I2S stays on the vendor pins: BCLK **42**,
LRCLK **2**, DIN **41** (P7 `Speak`).
- **Avoid**: 0 (BOOT/LCD_TE), 1–4/8 (LCD+touch+speaker), 10–13
(TF/RTP SPI), 19/20 (USB), 21 (LCD D0), 35–37 (octal PSRAM),
38–48 (used/strapping), 43/44 (console).
- GPIO 46 is xlsx-free and on P2 but is **input-only** on the
ESP32-S3 — never use it for outputs.
- Octal PSRAM (`sdkconfig.s3`: `CONFIG_SPIRAM_MODE_OCT=y`) means
33–37 are off-limits even though the xlsx only flags 35.
4. **Audio on S3 should use I2S, not a beeper GPIO.** The board has a
speaker amp on IO2 (LRCLK), IO41 (DIN), IO42 (BCLK) — P7 `Speak`
connector. `hal_audio` currently builds the LEDC buzzer only for
esp32 and a stub elsewhere; an S3 implementation should drive
I2S on those pins rather than allocating a GPIO.
5. **DS18B20 on S3 needs one free GPIO** (any of §3's candidates; it
only needs input+open-drain drive). Hal currently stubs temp on
non-esp32.
6. **Reserved/bus-shared notes**: GPIO 0 carries `LCD_TE` in addition
to BOOT — do not use it for anything else. TF card SPI (10–13) is
shared with resistive-touch signals on R models; on our C model
it's purely the SD slot.
7. **No conflicts found** between the code's S3 selections and the
vendor map — every consumed pin is one the vendor assigns to that
same function.
## Motor / temp / speaker wiring as locked (phase I01)
| Signal | GPIO | Rationale |
| --- | --- | --- |
| STEP | 16 | free, P3 header, no boot function |
| DIR | 15 | free, P3 header |
| EN (active LOW) | 7 | free, P3 header |
| DS18B20 | 17 | P4 header carries GND/3V3/17/18 — one connector for the sensor |
| Speaker BCLK / LRCLK / DIN | 42 / 2 / 41 | onboard amp wired to P7 `Speak` |
Keep motor EN's disable-first behaviour (drive inactive at boot before
enabling output mode).
+38
View File
@@ -0,0 +1,38 @@
# Codebase Audit & Remediation Plan (develop branch)
After a thorough audit of the `develop` branch focusing on the recent modular ESP-IDF port, I've identified several critical defects primarily centered around FreeRTOS concurrency, system timers, and legacy feature regressions.
Here is the step-by-step remediation plan to address these issues. **No code has been changed yet.**
## 1. Fix Critical Step Timing Bug in `app_machine.c`
**Defect:** `CMD_START_STEP` triggers `start_running(0)`, passing a hardcoded `0` for `now_ms`. The task then calculates `s_deadline_ms = 0 + remaining_ms`. When `app_machine_tick()` runs moments later, it reads the *real* system uptime (e.g., 200,000 ms). If the uptime is larger than the step duration, the step instantly completes.
**Remediation Steps:**
- Modify `start_running()` to remove the `now_ms` argument.
- Instead of calculating `s_deadline_ms` immediately, configure the state machine to defer calculation: set `s_have_deadline = false;` and `s_resume_pending = true;`.
- This safely delegates the deadline calculation to the next `app_machine_tick()` cycle, which naturally computes it using the true system `now_ms`.
- Update `app_machine_handle_cmd()` to call `start_running()` without arguments.
## 2. Fix Event Queue Concurrency in `app_machine.c`
**Defect:** The system uses a raw array `s_q` (with `s_q_head`, `s_q_tail`, and `s_q_count++`) to pass events from the machine to the UI. However, `emit()` is called by both the `machine_task` and the `temp_task` (via `app_machine_on_temp`), while `app_machine_last_event()` is read by the `ui_task`. These are non-atomic read-modify-write operations across three threads, which will inevitably corrupt the queue and crash the UI.
**Remediation Steps:**
- Replace the raw ring buffer variables (`s_q`, `s_q_head`, `s_q_tail`, `s_q_count`) with a standard FreeRTOS `QueueHandle_t s_evtq`.
- In `app_machine_init()`, initialize the queue: `s_evtq = xQueueCreate(UI_EVT_QUEUE_LEN, sizeof(ui_evt_t));`.
- Update `emit()` to use `xQueueSend(s_evtq, &ev, 0);`.
- Update `app_machine_last_event()` to use `xQueueReceive(s_evtq, out, 0) == pdTRUE`.
## 3. Restore Auto-Advance Functionality in `app_machine.c`
**Defect:** The legacy Arduino loop automatically chained processing steps together (`run == 1`). The new state machine includes a `maybe_auto_advance()` function guarded by `s_auto_advance`, but `s_auto_advance` is permanently hardcoded to `false` and never toggled. As a result, the machine halts after every single step.
**Remediation Steps:**
- Initialize `s_auto_advance = true` to match the legacy behavior of chaining steps automatically.
- (Optional) Wire up a UI command (`CMD_TOGGLE_AUTO_ADVANCE`) to allow users to turn this off if manual pausing between steps is desired.
## 4. Fix Task Watchdog Initialization in `main.c`
**Defect:** `ui_task`, `machine_task`, `input_task`, and `temp_task` all invoke `esp_task_wdt_add(NULL)`. However, the Task Watchdog Timer (TWDT) is never initialized in `app_main()`. Depending on the ESP-IDF version and `sdkconfig` defaults, this can cause silent failures or panic at boot.
**Remediation Steps:**
- In `app_main()`, invoke `esp_task_wdt_init(&wdt_config)` *before* creating the FreeRTOS tasks.
- Ensure the WDT timeout is set generously (e.g., 3-5 seconds) to accommodate the 1.5-second blocking time in `hal_temp_tick()`.
## 5. Clean up `hal_temp_tick()` Timing (Low Priority)
**Defect:** `hal_temp_tick()` blocks `temp_task` with multiple `vTaskDelay(pdMS_TO_TICKS(750))` calls for the DS18B20 conversion. While safely isolated from the UI, it forces the watchdog timeout to be artificially large and limits responsiveness if multiple sensors were ever added.
**Remediation Steps:**
- Refactor the 1-Wire sequence into a non-blocking state machine within `temp_task`, or simply keep it as-is but acknowledge the WDT requirement constraint outlined in step 4.
+63
View File
@@ -0,0 +1,63 @@
# AUDIT MEGAPLAN — develop-branch defect remediation
Audience: coding agent. One phase = one session. Do not start the next phase in the same session.
## Source
Implements `docs/audit_remediation_plan.md` — five defects found in the post-refactor `develop` tree: step deadline computed from a fake `now`, non-thread-safe UI event ring, auto-advance regression, implicit task-watchdog setup, and a blocking DS18B20 conversion in `hal_temp_tick`.
## Protocol (every session)
1. `git checkout feature/audit-remediation && git pull --ff-only`
2. Read only: `AGENTS.md`, this file (status table), **the assigned phase file**, and the audit doc items it cites. Open `docs/CURRENT_STATE.md` / `docs/TARGET_ARCHITECTURE.md` / `docs/CICD.md` only if the phase `READ:` list says so.
3. Execute `IN` only. Honour `OUT` and `FORBIDDEN`.
4. Run `VERIFY` exactly. Do not push if any verify item fails. Local verify = host ctest; firmware builds run in Gitea CI on push (`feature/**` is a push trigger — see `.gitea/workflows/ci.yml`).
5. Commits: messages listed in the phase. Imperative. One concern per commit.
6. Push `origin feature/audit-remediation`. PR target is `develop`, not `main`.
7. Set phase `STATUS:` to `DONE` in the phase file **and** this table. One-line `Notes` if you diverged (API name only — do not silently change behaviour).
If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase file with the exact error.
## Frozen constraints (never reinterpret)
- Recipe **values** (times s, CW, CCW, temp min/pref/max) for C41 E6 ECN-2 B&W Custom B&WREV = `components/app_process/app_process.c` / `docs/CURRENT_STATE.md`. Changing values requires explicit user order + doc update in the same commit.
- Stop while running: Esc `'X'` (keypad r4c4) and touch STOP on S3 → immediate motor **EN disabled**, then Resume or ReturnToStepSelect. `hal_motor_request_stop` runs before any other work on the stop path.
- UI never blocks on motor, OneWire, or audio.
- No Arduino types in `ui_cmd`, `app_process`, `app_machine` public headers.
- Watchdog stays **on** — A03 makes setup explicit, it does not disable anything. No `vTaskDelete` of long-lived workers.
- Session time edits ±5 s are RAM overlays only. No NVS profiles. No pumps/valves/heaters.
- **Auto-advance**: REFACTOR-MEGAPLAN froze `auto_advance` default `false` "unless a later phase says so" — **A02 is that phase** (audit item 3, owner-approved). Semantics = legacy `run==1` chain: auto-**ARM** the next step, never auto-run; the operator still presses Start per step.
- Event queue semantics change (accepted): a full event queue now drops the **newest** event (`xQueueSend` timeout 0); the old ring overwrote the oldest. UI drains at 40 Hz vs ≤~1.3 Hz producers — overflow is not expected.
## Branch
`feature/audit-remediation` cut from `develop` → PRs into `develop`. (`feature/**` is required for the CI push trigger.)
## Working-tree note
The branch was cut with uncommitted `components/app_machine/app_machine.c` changes present: they implement audit items 1–2 **plus** the item-3 flag flip. A01 lands items 1–2 with `s_auto_advance` reverted to `false`; A02 lands the flag. Do not commit the whole dirty file in one go — split per concern (`git add -p` or re-apply in order).
## Status
| ID | File | Session goal | STATUS |
| --- | --- | --- | --- |
| A00 | `audit/A00-docs.md` | Land audit doc + megaplan + phase files | DONE |
| A01 | `audit/A01-machine-timing-queue.md` | Deadline fix + FreeRTOS event queue + host queue shim | DONE |
| A02 | `audit/A02-auto-advance.md` | Restore auto-arm after step complete + test rework | DONE |
| A03 | `audit/A03-task-wdt.md` | Explicit TWDT init/reconfigure + `add()` failure logging | DONE |
| A04 | `audit/A04-temp-nonblocking.md` | OPTIONAL: non-blocking DS18B20 conversion | DONE |
## Dependency
```
A00 → A01 → A02 (same file; A02 builds on A01's queue + tests)
A03 is independent of A01/A02 (main.c only) — run any time after A00
A04 runs after A03 (shares temp_task in main.c); OPTIONAL — skipping is acceptable
```
## Do not
- Expand scope: no `CMD_TOGGLE_AUTO_ADVANCE` (rejected by owner), no UI changes, no NVS, no pumps/heater, no recipe edits.
- Commit `sdkconfig` (generated), `build/`, `build_host/`, tokens.
- Batch unrelated fixes into a phase.
- Reduce the TWDT timeout below the blocking bound while `hal_temp_tick` still blocks (~750 ms); A04 removes that constraint.
+41
View File
@@ -0,0 +1,41 @@
# INTEGRATION MEGAPLAN — AutoFilm-ESP32 S3 Hardware Integration
Audience: coding agent. One phase = one session. Do not start the next phase in the same session.
## Summary
This megaplan drives the hardware integration for the ESP32-S3 (JC4827W543) board, activating the stepper motor, DS18B20 temperature sensor, and I2S speaker using conflict-free, header-available GPIOs. To ensure success, these changes have been broken down from a single monolithic phase into a sequence of safe, isolated phases.
## Protocol (every session)
1. Ensure you are on the appropriate integration branch off `develop`.
2. Read only: `AGENTS.md`, this file (status table), **the assigned phase file**. Open other docs only if the phase `READ:` list says so.
3. Execute `IN` only. Honour `OUT` and `FORBIDDEN`.
4. Run `VERIFY` exactly. Do not push if any verify item fails.
5. Commits: messages listed in the phase. Imperative. No secret tokens.
6. Push to origin. PR target is `develop`, not `main`.
7. Set phase `STATUS:` to `DONE` in the phase file **and** this table. One-line `Notes` if you diverged (API name only — do not silently change behaviour).
If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase file with the exact error.
## Frozen constraints (never reinterpret)
- The motor EN/STEP/DIR, DS18B20, and I2S audio pins for the JC4827W543 are strictly defined in this plan and the pin audit doc.
- **Constraints that eliminate S3 pins:**
- QSPI display: 45, 47, 21, 48, 40, 39 + BL 1 — consumed
- GT911: SDA 8, SCL 4, INT 3, RST 38 — consumed
- Speaker I2S: 2 (LRCLK), 41 (DIN), 42 (BCLK) — consumed by onboard amp
- TF slot: 10–13; USB: 19/20; UART0: 43/44; BOOT+LCD_TE: 0
- Octal PSRAM: 33–37 off-limits
- GPIO46 is INPUT-ONLY on ESP32-S3 — excluded from outputs entirely
- UI never blocks on hardware operations (motor, OneWire, I2S).
- Watchdog stays **on**. No `vTaskDelete` of long-lived workers.
## Status
| ID | File | Session goal | STATUS |
| --- | --- | --- | --- |
| I01 | `integration/I01-board-configs.md` | Board pin allocations & accessors | DONE |
| I02 | `integration/I02-temp-fix.md` | Temp HAL shared & starvation fix | DONE |
| I03 | `integration/I03-motor-hal.md` | Motor HAL shared across boards | DONE |
| I04 | `integration/I04-audio-i2s.md` | I2S audio implementation for S3 | DONE |
+5 -1
View File
@@ -40,7 +40,7 @@ If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase fi
| 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 | DONE |
| P07 | `refactor/P07-board-s3-ui.md` | JC4827W543 display+GT911+STOP | DONE |
| P08 | `refactor/P08-idf-debt.md` | Replace Arduino drivers (RMT etc.) | TODO |
| P08 | `refactor/P08-idf-debt.md` | Replace Arduino drivers (RMT etc.) | DONE |
P04 Notes: merge_bin uses `${{ gitea.sha }}` (not `github.sha`); runner unverified (no Actions run).
@@ -50,6 +50,10 @@ P06 Notes: WROOM input_task dual-path stop (hal_motor_request_stop then CMD_STOP
P07 Notes: custom NV3041A QSPI + GT911; shared app_ui text grid + colour softkeys; EN=NC. idf.py not run here (no IDF_PATH). host ctest green. Hw unflashed.
P08 Notes: LEDC audio; RMT STEP + GPIO DIR/EN; onewire_bus DS18B20 +0.4; GPIO keypad; IDF I2C 2004. Arduino-as-component removed. third_party trees unlinked. host ctest green. idf.py not run here (no IDF_PATH). hw unflashed.
AUDIT A02 Notes: supersedes the frozen `auto_advance` default-false constraint — `s_auto_advance = true` restores the legacy auto-ARM chain (audit item 3, owner-approved); auto-ARM only, never auto-run.
## Dependency
```
+24
View File
@@ -0,0 +1,24 @@
# A00 — land audit docs on the remediation branch
STATUS: DONE
DEPENDS: —
Notes: executed by the session that authored the megaplan.
**READ:** `AGENTS.md`, `docs/megaplans/AUDIT-MEGAPLAN.md`
**IN:**
1. Create `feature/audit-remediation` from `develop`.
2. Commit `docs/audit_remediation_plan.md`, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/megaplans/audit/*.md`, and the `build_host/` `.gitignore` line.
3. Do **not** commit the `components/app_machine/app_machine.c` working-tree changes (A01/A02 own them) or `build_host/` artifacts.
**OUT:** all code changes.
**VERIFY:** `git log -1` shows the docs commit; `git status` shows only `app_machine.c` modified.
**COMMITS:**
1. `Add audit remediation megaplan`
**DoD checkboxes:**
- [x] Branch `feature/audit-remediation` exists, cut from `develop`.
- [x] Audit doc + megaplan + phase files committed.
- [x] `app_machine.c` left uncommitted for A01/A02.
@@ -0,0 +1,44 @@
# A01 — app_machine deadline fix + FreeRTOS event queue + host queue shim
STATUS: DONE
DEPENDS: A00
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` items 1–2, `components/app_machine/app_machine.c`, `tests/host/CMakeLists.txt`
**Context:** the working tree already carries uncommitted fixes for items 1–2 **and** the item-3 `s_auto_advance = true` flip. This phase lands items 1–2 only; the flag stays `false` until A02. The queue change adds `#include "freertos/queue.h"` to `app_machine.c`, which the host test build cannot resolve — a shim is required.
**IN:**
1. `app_machine.c` deadline fix (keep from working tree): `start_running(void)` takes no `now_ms`; sets `s_remaining_ms = time_s * 1000`, `s_have_deadline = false`, `s_resume_pending = true`. First `app_machine_tick` in `ST_RUNNING` computes `s_deadline_ms = now_ms + s_remaining_ms` from real uptime. `app_machine_handle_cmd` calls `start_running()` with no args.
2. `app_machine.c` cleanup: revert `s_auto_advance` to `false`; restore `#define AGITATE_RPM 60u`; **remove** the duplicate `#define UI_EVT_QUEUE_LEN 16` (already defined via `ui_cmd.h`).
3. `app_machine.c` event queue (keep from working tree): `static QueueHandle_t s_evtq`; `emit()` does `xQueueSend(s_evtq, &ev, 0)` when non-NULL; `app_machine_last_event()` does `xQueueReceive(s_evtq, out, 0) == pdTRUE`. In `app_machine_init`: `if (s_evtq == NULL) { s_evtq = xQueueCreate(UI_EVT_QUEUE_LEN, sizeof(ui_evt_t)); } else { xQueueReset(s_evtq); }` — host tests re-init repeatedly and stale events must not leak between inits.
4. `components/app_machine/CMakeLists.txt`: add `freertos` to `PRIV_REQUIRES` (explicit dependency for `freertos/queue.h`).
5. New host shim so `test_machine` keeps compiling the same `app_machine.c`:
- `tests/host/freertos/FreeRTOS.h` — `BaseType_t`, `UBaseType_t`, `TickType_t`, `pdTRUE`, `pdFALSE`, `pdPASS`, `pdMS_TO_TICKS`.
- `tests/host/freertos/queue.h` — `QueueHandle_t` + prototypes for `xQueueCreate`, `xQueueSend`, `xQueueReceive`, `xQueueReset`.
- `tests/host/freertos/queue.c` — malloc'd ring buffer; `xQueueSend` returns `pdFALSE` (drop) when full; `xQueueReceive` returns `pdFALSE` when empty; `xQueueReset` clears.
- `tests/host/CMakeLists.txt` — add `freertos/queue.c` to the `test_machine` sources (`tests/host` is already on its include path, so `"freertos/FreeRTOS.h"` resolves to the shim).
6. Behaviour must be identical for host tests: all existing `test_machine.c` cases pass **unchanged** (auto_advance still `false`).
**OUT:** `s_auto_advance = true` (A02), TWDT setup (A03), `hal_temp` internals (A04), any UI change.
**FORBIDDEN:** committing the auto-advance flag; changing recipe values; Arduino types; deleting or weakening host test assertions.
**VERIFY:**
```
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
```
All green. Firmware builds run in CI on push.
**COMMITS** (order matters — every commit must compile on host):
1. `Add FreeRTOS queue shim for host tests` (shim alone; old code doesn't include the headers yet — harmless)
2. `Fix step deadline to derive from real uptime`
3. `Replace machine event ring with FreeRTOS queue`
Stage hunks per concern (`git add -p` or re-apply in order) — the dirty file combines all three changes plus the A02 flag.
**DoD checkboxes:**
- [x] `start_running` takes no `now_ms`; deadline set on the first `RUNNING` tick.
- [x] `emit`/`app_machine_last_event` go through `s_evtq`; `app_machine_init` creates or resets the queue.
- [x] `s_auto_advance` still `false`.
- [x] Host ctest green with zero test changes.
- [x] STATUS → DONE here and in the megaplan table.
+43
View File
@@ -0,0 +1,43 @@
# A02 — restore auto-advance (auto-arm next step after complete)
STATUS: DONE
DEPENDS: A01
Notes: `stop_disables_motor_and_resume` final assertion updated to `ST_ARMED` — the "unchanged" list cannot hold once auto-advance is on (resume-to-complete auto-arms step 1).
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` item 3, `docs/CURRENT_STATE.md` §Runtime behaviour, `components/app_machine/app_machine.c`, `tests/host/test_machine.c`
**Semantics (implement exactly):** legacy `run==1` in `src/menu.cpp` chained `startProcessing()`, which displayed `Ent:start Esc:quit` for the next step — i.e. **auto-ARM, never auto-run**. `maybe_auto_advance()` already implements this: on the completing tick it moves to `ST_ARMED` for `s_step+1` (emitting `EVT_STEP_ARMED`), or `ST_IDLE` + `EVT_PROCESS_IDLE` after the last step. `ST_COMPLETE` becomes transient inside the completing tick — the alarm (`hal_audio_alarm_complete`, ~7.5 s async, self-terminating) plays while the next step sits armed. That is acceptable and intentional: do **not** add `alarm_cancel` to `start_running` and do not block on the melody.
**IN:**
1. `app_machine.c`: set `s_auto_advance = true` in `app_machine_init`. Nothing else in the state machine changes.
2. Keep the `ST_COMPLETE` + `CMD_STOP` alarm-cancel branch in `app_machine_handle_cmd` — it becomes unreachable while auto-advance is on, but stays as a defensive path.
3. Rework `tests/host/test_machine.c` for transient COMPLETE (Custom has 4 steps, ECN-2 RemJet is step 1, C41 Prewarm is step 0):
- `arm_start_complete_custom_10s`: after `tick(10000)` expect `ST_ARMED` and `step_index == 1` (auto-armed next step); drop the `tick(10001)` "stays complete" assertion.
- Replace `stop_during_complete_cancels_alarm` with `auto_advance_last_step_goes_idle`: select Custom, `cmd_arm(3)`, `cmd_start(3)`, `tick(0)`, `tick(10000)` → `ST_IDLE`; drain events and assert `EVT_PROCESS_IDLE` present.
- `ecn2_remjet_zero_time`: after `tick(0)` expect `ST_ARMED` at step index 2.
- `c41_clock`: after `tick(180000)` expect `ST_ARMED` at step index 1.
- All other cases unchanged (`select_c41_step_view`, `adjust_does_not_mutate_const`, `stop_disables_motor_and_resume`, `return_from_stopped`, `stop_ignored_meaningless_in_idle`).
- Note: `complete_step` still emits `EVT_STEP_COMPLETE` before `EVT_STEP_ARMED`/`EVT_PROCESS_IDLE`; tests may assert that ordering via `pop_ids` if convenient.
4. `docs/megaplans/REFACTOR-MEGAPLAN.md`: add a one-line `Notes` entry recording that the `auto_advance` default-false freeze is superseded by AUDIT A02 (owner-approved via audit item 3). Do not rewrite the frozen-constraint text itself.
**OUT:** `CMD_TOGGLE_AUTO_ADVANCE` (rejected by owner), any UI/input changes, NVS, recipe values.
**FORBIDDEN:** auto-starting the next step's motor; removing the `ST_COMPLETE` STOP branch; changing recipe tables; editing the frozen-constraint lines of REFACTOR-MEGAPLAN.
**VERIFY:**
```
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
```
All green, including the new `auto_advance_last_step_goes_idle`. Firmware builds run in CI on push.
**COMMITS:**
1. `Restore auto-advance to arm next step after step complete`
2. `Update machine tests for auto-advance`
**DoD checkboxes:**
- [x] `s_auto_advance = true`; auto-ARM only, never auto-run.
- [x] Last step completes → `ST_IDLE` + `EVT_PROCESS_IDLE`.
- [x] `ST_COMPLETE` + `CMD_STOP` branch retained.
- [x] REFACTOR-MEGAPLAN supersede note added.
- [x] Host ctest green.
- [x] STATUS → DONE here and in the megaplan table.
+60
View File
@@ -0,0 +1,60 @@
# A03 — explicit task watchdog setup in app_main
STATUS: DONE
DEPENDS: A00 (independent of A01/A02 — `main/main.c` only)
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` item 4, `main/main.c`, `sdkconfig.defaults`
**Context (audit correction):** the audit's "TWDT never initialized → panic at boot" is unlikely under the current config — `sdkconfig.defaults` sets `CONFIG_ESP_TASK_WDT_EN=y` + `CONFIG_ESP_TASK_WDT_TIMEOUT_S=10` + `CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y`, and IDF v5.4 auto-initialises the TWDT before `app_main`, so `esp_task_wdt_add(NULL)` in the tasks already succeeds. The real gaps: setup is implicit (breaks silently if the Kconfig default ever changes), and every `esp_task_wdt_add` return value is ignored. Make setup explicit and failures observable. **Keep the effective config identical** — 10 s already bounds the ~750 ms `hal_temp_tick` block; do not adopt the audit's 3–5 s suggestion.
**IN:**
1. `main/main.c`, in `app_main` before the `xTaskCreate` block, under `#if CONFIG_ESP_TASK_WDT_EN`:
```c
esp_task_wdt_config_t wdt_cfg = {
.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000,
.idle_core_mask =
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
(1u << 0)
#else
0
#endif
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
| (1u << 1)
#endif
,
#if CONFIG_ESP_TASK_WDT_PANIC
.trigger_panic = true,
#else
.trigger_panic = false,
#endif
};
esp_err_t wdt_err = esp_task_wdt_init(&wdt_cfg);
if (wdt_err == ESP_ERR_INVALID_STATE) {
wdt_err = esp_task_wdt_reconfigure(&wdt_cfg); /* already auto-initialised */
}
if (wdt_err != ESP_OK) {
ESP_LOGW(TAG, "task wdt setup: %s", esp_err_to_name(wdt_err));
}
```
Verify field names/signatures against `esp_task_wdt.h` in the pinned IDF (v5.4) and adjust the sketch only as needed to compile.
2. In each of `input_task`, `machine_task`, `ui_task`, `temp_task`: capture `esp_err_t werr = esp_task_wdt_add(NULL);` and `ESP_LOGW(TAG, "... wdt add failed: %s", esp_err_to_name(werr))` on non-OK; continue regardless — the unconditional `esp_task_wdt_reset()` no-ops harmlessly if the task is not subscribed.
**OUT:** `app_machine`, `hal_temp` internals, UI code, `sdkconfig*` edits.
**FORBIDDEN:** disabling the TWDT or idle-task watch; `vTaskDelete`; shrinking the timeout; committing a generated `sdkconfig`.
**VERIFY:**
```
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
```
(unchanged — `main.c` is not host-compiled; run anyway to catch regressions). Firmware builds for both `esp32` and `esp32s3` run in CI on push — confirm both go green.
**COMMITS:**
1. `Make task watchdog setup explicit in app_main`
**DoD checkboxes:**
- [x] init-or-reconfigure runs before task creation.
- [x] `esp_task_wdt_add` failures are logged in all four tasks.
- [x] Effective config unchanged: 10 s timeout, CPU0 idle watched, no panic.
- [x] Both CI firmware builds green.
- [x] STATUS → DONE here and in the megaplan table.
@@ -0,0 +1,37 @@
# A04 — (OPTIONAL) non-blocking DS18B20 conversion in hal_temp
STATUS: DONE
DEPENDS: A03 (shares `temp_task` in `main/main.c`)
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` item 5, `components/hal_temp/hal_temp.c`, `main/main.c`
**Context:** `hal_temp_tick()` currently blocks `temp_task` for ~750 ms per conversion (plus 750 ms on each error path). It is safely isolated from the UI, so this is a robustness/latency improvement only — the audit itself allows keeping the block if the WDT constraint is documented (A03 does that). Implement only if the phase session chooses to spend it.
**IN:**
1. `components/hal_temp/hal_temp.c`: turn `hal_temp_tick()` into a two-state machine driven by a deadline (`esp_timer_get_time()` / 1000, or `xTaskGetTickCount()`):
- `T_START`: `onewire_bus_reset` + `CMD_SKIP_ROM`/`CMD_CONVERT_T`; on success record `s_deadline_ms = now + 750` and move to `T_WAIT`. On any bus error: `s_last_ok = false`, set a ~750 ms retry deadline, stay in `T_START` (preserves the current retry cadence).
- `T_WAIT`: if `now < s_deadline_ms` return immediately; else `onewire_bus_reset` + `CMD_SKIP_ROM`/`CMD_READ_SCRATCH`, read 9 bytes, CRC-check via existing `scratch_valid`, update `s_last_c`/`s_last_ok`, move back to `T_START` (next conversion starts on the next tick).
- The `s_bus == NULL` path must also be non-blocking (`s_last_ok = false`, return).
- Keep `TEMP_OFFSET`, `scratch_valid`, `hal_temp_init`, and `hal_temp_read_c` semantics unchanged.
2. `main/main.c` `temp_task`: `hal_temp_tick` no longer blocks, so add `vTaskDelay(pdMS_TO_TICKS(100))` at the end of the loop (~10 Hz poll) so the task always sleeps. Calling `app_machine_on_temp` every loop is acceptable (16-deep event queue drained at 40 Hz by `ui_task`); keep it unconditional to keep the diff small.
3. Host stub `components/hal_temp/stub/hal_temp.c` / `tests/host/stubs/hal_temp.c`: no signature change — leave as-is unless the implementation forces otherwise.
**OUT:** additional sensors, temperature alarms, heater control, `hal_temp_read_c` contract changes.
**FORBIDDEN:** changing `TEMP_OFFSET` or the CRC; any `vTaskDelay` ≥ 750 ms left inside `hal_temp_tick`; blocking the UI task; removing the watchdog.
**VERIFY:**
```
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
```
plus CI firmware builds for both targets.
**COMMITS:**
1. `Make DS18B20 conversion non-blocking in hal_temp`
**DoD checkboxes:**
- [x] No `vTaskDelay(750)` inside `hal_temp_tick`; conversion waits via deadline.
- [x] `temp_task` always blocks (explicit 100 ms delay).
- [x] `TEMP_OFFSET`, CRC, and read semantics unchanged.
- [x] Host ctest green; both CI firmware builds green.
- [x] STATUS → DONE here and in the megaplan table (or noted `SKIPPED` with reason).
@@ -0,0 +1,23 @@
STATUS: DONE
DEPENDS: none
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `docs/JC4827W543_PIN_AUDIT_VERIFIED.md`, `components/board_jc4827w543/board.c`, `components/board_wroom/board.c`
**IN:**
1. `board_jc4827w543`: add `board_pin_motor_step()` → 16, `board_pin_motor_dir()` → 15, `board_pin_temp()` → 17, `board_pin_spk_bclk()`, `board_pin_spk_lrclk()`, `board_pin_spk_din()` → 42, 2, 41; change `board_pin_motor_en()` → 7 (keep disable level 1 = active-LOW EN).
2. `board_wroom`: add matching `board_pin_motor_step()` → 12, `board_pin_motor_dir()` → 14, `board_pin_temp()` → 13 so HAL impls can be shared.
3. Update `docs/CURRENT_STATE.md` pin table note and `JC4827W543_PIN_AUDIT_VERIFIED.md` header section with the corrected labels if not already updated (P2: IO46/9/14/5, P3: IO6/7/15/16, P4: GND/3V3/17/18).
**OUT:** HAL implementations, business logic, NVS profiles.
**FORBIDDEN:** GPIO46 for any output; stealing TF, QSPI, GT911, USB, or UART0 pins.
**VERIFY:** `idf.py build` succeeds for both `esp32` and `esp32s3` targets.
**COMMITS:**
1. `Lock S3 header pins and introduce shared board pin accessors`
2. `Update pin audit documentation labels`
**DoD checkboxes:**
- [ ] `board_jc4827w543` pin accessors added.
- [ ] `board_wroom` pin accessors added.
- [ ] Build succeeds on both targets.
@@ -0,0 +1,23 @@
STATUS: DONE
DEPENDS: I01
Notes: Poll API named `hal_temp_tick` (declared in hal_temp.h). `idf.py build` for both targets verified via CI run 38616 (no local IDF). S3 flash watchdog check not run — no device attached to this machine.
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `main/main.c`, `components/hal_temp/*`
**IN:**
1. `hal_temp`: Generalize `hal_temp.c` to use `board_pin_temp()` instead of hardcoded macros. Compile for both targets via CMake (`IDF_TARGET esp32|esp32s3` → shared source, `PRIV_REQUIRES board_wroom|board_jc4827w543`).
2. `main.c` **bug fix**: `temp_task` currently calls `autofilm_temp_tick()` only `#ifdef AUTOFILM_BOARD_WROOM` and has **no `vTaskDelay`** — on S3 it spins at priority 2 and starves the idle task (task-WDT risk). Move the tick loop into a `hal_temp`-level poll (e.g. `hal_temp_tick()` weak per-board) or add `vTaskDelay(pdMS_TO_TICKS(750))` on the non-WROOM path so the task always blocks.
**OUT:** Motor logic, audio logic.
**FORBIDDEN:** Removing the watchdog, deleting the temp task.
**VERIFY:** `idf.py build` succeeds for both `esp32` and `esp32s3`. Flash to S3 and verify no task watchdog panics occur in the console.
**COMMITS:**
1. `Share onewire temp HAL across both boards`
2. `Fix temp_task starvation bug on non-WROOM`
**DoD checkboxes:**
- [x] `hal_temp` generalized to use board accessors.
- [x] `temp_task` starvation bug fixed.
- [x] Build succeeds on both targets.
@@ -0,0 +1,21 @@
STATUS: DONE
DEPENDS: I01
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `components/hal_motor/*`
**IN:**
1. `hal_motor`: Generalize `wroom/hal_motor.c` (already pure-IDF RMT) to read pins from `board.h`; compile for both targets via CMake (`IDF_TARGET esp32|esp32s3` → shared source, `PRIV_REQUIRES board_wroom|board_jc4827w543`).
2. Preserve: EN HIGH at init + on stop, `stop_req` sampled inside step loop, immortal task, `hal_motor_request_stop` ISR-safe.
**OUT:** Temp logic, audio logic.
**FORBIDDEN:** Blocking UI on motor operations.
**VERIFY:** `idf.py build` succeeds for both targets. Run host ctests if any apply to motor logic.
**COMMITS:**
1. `Share RMT motor HAL across both boards`
**DoD checkboxes:**
- [ ] `hal_motor` generalized to use board accessors.
- [ ] EN HIGH initialization preserved.
- [ ] Build succeeds on both targets.
@@ -0,0 +1,23 @@
STATUS: DONE
DEPENDS: I01
Notes: `idf.py build` for both targets verified via CI run 38684 (no local IDF).
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `components/hal_audio/*`
**IN:**
1. `hal_audio` S3: Implement new `s3/hal_audio.c` using `i2s_std` TX on pins 41/42/2 (via `board_pin_spk_*`) to the NS4168 amp.
2. Generate 2 kHz square-wave bursts into a small DMA buffer.
3. Ensure identical queue protocol (SHORT / 10×ALARM 500ms-on/250ms-off / CANCEL) and non-blocking semantics as the WROOM LEDC version.
**OUT:** Motor logic, Temp logic.
**FORBIDDEN:** Blocking the UI on I2S writes.
**VERIFY:** `idf.py build` succeeds for both targets.
**COMMITS:**
1. `Add I2S speaker audio for S3`
**DoD checkboxes:**
- [x] `hal_audio` I2S implementation added for S3.
- [x] Queue protocol and non-blocking semantics maintained.
- [x] Build succeeds.
+6 -4
View File
@@ -1,6 +1,6 @@
# P08 — Arduino debt burn-down
STATUS: TODO
STATUS: DONE
DEPENDS: P06 (behaviour parity). P07 optional.
READ: this file, `components/hal_motor/**`, HAL headers from P03
OUT: recipe edits; UI redesign; LVGL-by-default
@@ -35,6 +35,8 @@ Per numbered item above, e.g. `Replace AccelStepper with RMT step pulse HAL`.
## DoD
- [ ] `rg -n "Arduino.h|AccelStepper|LiquidCrystal|DallasTemperature|#include <Keypad.h>|#include <OneWire.h>"` empty in `main/` `components/`
- [ ] Stop still EN-off first
- [ ] STATUS→DONE
- [x] `rg -n "Arduino.h|AccelStepper|LiquidCrystal|DallasTemperature|#include <Keypad.h>|#include <OneWire.h>"` empty in `main/` and live `components/hal_*` / `main` (vendor copies remain unlinked under `components/third_party/`)
- [x] Stop still EN-off first (`hal_motor_request_stop` EN HIGH then RMT abort)
- [x] STATUS→DONE
Notes: audio was already LEDC; converted to C. Motor RMT copy-encoder batches + short accel table. Temp `espressif/onewire_bus` + skip-ROM scratchpad, +0.4 °C. Keypad GPIO scan, same 5x4 map. LCD PCF8574 nibble HD44780 on I2C0. Dropped `espressif/arduino-esp32` and `initArduino()`. Host ctest green. Local `idf.py` not run (no IDF_PATH). Hw unflashed.
+2 -1
View File
@@ -12,7 +12,8 @@ endif()
idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES ${BOARD_COMP} driver app_process app_machine app_ui
hal_motor hal_temp hal_audio hal_display hal_input)
hal_motor hal_temp hal_audio hal_display hal_input
PRIV_REQUIRES esp_timer)
if(IDF_TARGET STREQUAL "esp32")
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_BOARD_WROOM=1)
+46 -17
View File
@@ -19,10 +19,6 @@
#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
@@ -32,8 +28,6 @@ static const char *TAG = "app";
#if defined(AUTOFILM_BOARD_WROOM) || defined(AUTOFILM_BOARD_S3)
static QueueHandle_t s_cmdq;
extern void autofilm_temp_tick(void);
static void map_and_post_key(char key)
{
ui_cmd_t cmd;
@@ -103,7 +97,10 @@ static void map_and_post_key(char key)
static void input_task(void *arg)
{
(void)arg;
esp_task_wdt_add(NULL);
esp_err_t werr = esp_task_wdt_add(NULL);
if (werr != ESP_OK) {
ESP_LOGW(TAG, "input wdt add failed: %s", esp_err_to_name(werr));
}
const TickType_t period = pdMS_TO_TICKS(40); /* 25 Hz */
for (;;) {
ui_raw_key_t raw;
@@ -125,7 +122,10 @@ static void input_task(void *arg)
static void machine_task(void *arg)
{
(void)arg;
esp_task_wdt_add(NULL);
esp_err_t werr = esp_task_wdt_add(NULL);
if (werr != ESP_OK) {
ESP_LOGW(TAG, "machine wdt add failed: %s", esp_err_to_name(werr));
}
const TickType_t period = pdMS_TO_TICKS(20); /* 50 Hz */
for (;;) {
ui_cmd_t cmd;
@@ -142,7 +142,10 @@ static void machine_task(void *arg)
static void ui_task(void *arg)
{
(void)arg;
esp_task_wdt_add(NULL);
esp_err_t werr = esp_task_wdt_add(NULL);
if (werr != ESP_OK) {
ESP_LOGW(TAG, "ui wdt add failed: %s", esp_err_to_name(werr));
}
hal_display_clear();
hal_display_text(0, 0, "AUTOFILM");
@@ -170,15 +173,17 @@ static void ui_task(void *arg)
static void temp_task(void *arg)
{
(void)arg;
esp_task_wdt_add(NULL);
esp_err_t werr = esp_task_wdt_add(NULL);
if (werr != ESP_OK) {
ESP_LOGW(TAG, "temp wdt add failed: %s", esp_err_to_name(werr));
}
for (;;) {
#ifdef AUTOFILM_BOARD_WROOM
autofilm_temp_tick();
#endif
hal_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();
vTaskDelay(pdMS_TO_TICKS(100));
}
}
#endif
@@ -187,10 +192,6 @@ void app_main(void)
{
ESP_LOGI(TAG, "board=%s desc=%s", board_name(), AUTOFILM_GIT_DESC);
#ifdef AUTOFILM_BOARD_WROOM
initArduino();
#endif
gpio_num_t en_pin = board_pin_motor_en();
if (en_pin != GPIO_NUM_NC) {
gpio_config_t io = {
@@ -217,6 +218,34 @@ void app_main(void)
s_cmdq = xQueueCreate(UI_CMD_QUEUE_LEN, sizeof(ui_cmd_t));
#if CONFIG_ESP_TASK_WDT_EN
esp_task_wdt_config_t wdt_cfg = {
.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000,
.idle_core_mask =
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
(1u << 0)
#else
0
#endif
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
| (1u << 1)
#endif
,
#if CONFIG_ESP_TASK_WDT_PANIC
.trigger_panic = true,
#else
.trigger_panic = false,
#endif
};
esp_err_t wdt_err = esp_task_wdt_init(&wdt_cfg);
if (wdt_err == ESP_ERR_INVALID_STATE) {
wdt_err = esp_task_wdt_reconfigure(&wdt_cfg); /* already auto-initialised */
}
if (wdt_err != ESP_OK) {
ESP_LOGW(TAG, "task wdt setup: %s", esp_err_to_name(wdt_err));
}
#endif
xTaskCreate(temp_task, "temp", 4096, NULL, 2, NULL);
xTaskCreate(input_task, "input", 3072, NULL, 8, NULL);
xTaskCreate(machine_task, "machine", 4096, NULL, 6, NULL);
+1
View File
@@ -37,6 +37,7 @@ add_executable(test_machine
stubs/hal_motor.c
stubs/hal_temp.c
stubs/hal_audio.c
freertos/queue.c
)
target_include_directories(test_machine PRIVATE ${HOST_INCLUDES})
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <stdint.h>
typedef long BaseType_t;
typedef unsigned long UBaseType_t;
typedef uint32_t TickType_t;
#define pdFALSE ((BaseType_t)0)
#define pdTRUE ((BaseType_t)1)
#define pdPASS pdTRUE
#define pdFAIL pdFALSE
#define pdMS_TO_TICKS(xTimeInMs) ((TickType_t)(xTimeInMs))
+72
View File
@@ -0,0 +1,72 @@
#include "freertos/queue.h"
#include <stdlib.h>
#include <string.h>
struct QueueDefinition {
UBaseType_t len;
UBaseType_t item_size;
UBaseType_t head;
UBaseType_t tail;
UBaseType_t count;
uint8_t *storage;
};
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize)
{
struct QueueDefinition *q = calloc(1, sizeof(*q));
if (q == NULL) {
return NULL;
}
q->storage = malloc((size_t)uxQueueLength * uxItemSize);
if (q->storage == NULL) {
free(q);
return NULL;
}
q->len = uxQueueLength;
q->item_size = uxItemSize;
return q;
}
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait)
{
(void)xTicksToWait;
if (xQueue == NULL || pvItemToQueue == NULL) {
return pdFALSE;
}
if (xQueue->count == xQueue->len) {
return pdFALSE;
}
memcpy(xQueue->storage + (size_t)xQueue->tail * xQueue->item_size,
pvItemToQueue, xQueue->item_size);
xQueue->tail = (xQueue->tail + 1u) % xQueue->len;
xQueue->count++;
return pdTRUE;
}
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)
{
(void)xTicksToWait;
if (xQueue == NULL || pvBuffer == NULL) {
return pdFALSE;
}
if (xQueue->count == 0u) {
return pdFALSE;
}
memcpy(pvBuffer, xQueue->storage + (size_t)xQueue->head * xQueue->item_size,
xQueue->item_size);
xQueue->head = (xQueue->head + 1u) % xQueue->len;
xQueue->count--;
return pdTRUE;
}
BaseType_t xQueueReset(QueueHandle_t xQueue)
{
if (xQueue == NULL) {
return pdFALSE;
}
xQueue->head = 0;
xQueue->tail = 0;
xQueue->count = 0;
return pdTRUE;
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "freertos/FreeRTOS.h"
struct QueueDefinition;
typedef struct QueueDefinition *QueueHandle_t;
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
BaseType_t xQueueReset(QueueHandle_t xQueue);
+4
View File
@@ -10,6 +10,10 @@ void hal_temp_init(void)
stub_temp_c = 20.0f;
}
void hal_temp_tick(void)
{
}
esp_err_t hal_temp_read_c(float *out)
{
if (stub_temp_fail) {
+17 -17
View File
@@ -145,12 +145,10 @@ static void arm_start_complete_custom_10s(void)
app_machine_tick(9999);
expect_int((int)app_machine_state(), ST_RUNNING, "custom tick9999");
app_machine_tick(10000);
expect_int((int)app_machine_state(), ST_COMPLETE, "custom complete");
expect_int((int)app_machine_state(), ST_ARMED, "custom auto-armed next");
expect_int((int)app_machine_step_index(), 1, "custom auto-advance step");
expect_int(stub_motor_enabled ? 1 : 0, 0, "custom motor off complete");
expect_int(stub_alarm_count >= 1 ? 1 : 0, 1, "custom alarm");
/* default auto_advance false */
app_machine_tick(10001);
expect_int((int)app_machine_state(), ST_COMPLETE, "auto_advance stays complete");
}
static void stop_disables_motor_and_resume(void)
@@ -182,7 +180,7 @@ static void stop_disables_motor_and_resume(void)
expect_int(stub_beep_count >= beeps + 1 ? 1 : 0, 1, "resume beep");
app_machine_tick(1000);
app_machine_tick(1000 + rem);
expect_int((int)app_machine_state(), ST_COMPLETE, "resume then complete");
expect_int((int)app_machine_state(), ST_ARMED, "resume then auto-armed");
}
static void return_from_stopped(void)
@@ -208,23 +206,23 @@ static void return_from_stopped(void)
expect_int((int)app_process_get(PROC_CUSTOM)->steps[0].time_s, 10, "const still 10");
}
static void stop_during_complete_cancels_alarm(void)
static void auto_advance_last_step_goes_idle(void)
{
app_machine_init();
ui_cmd_t c = cmd_select(PROC_CUSTOM);
app_machine_handle_cmd(&c);
ui_cmd_t arm = cmd_arm(0);
ui_cmd_t arm = cmd_arm(3);
app_machine_handle_cmd(&arm);
ui_cmd_t start = cmd_start(0);
ui_cmd_t start = cmd_start(3);
app_machine_handle_cmd(&start);
app_machine_tick(0);
app_machine_tick(10000);
expect_int((int)app_machine_state(), ST_COMPLETE, "complete before stop");
int cancels = stub_alarm_cancel_count;
ui_cmd_t stop = cmd_id(CMD_STOP);
app_machine_handle_cmd(&stop);
expect_int((int)app_machine_state(), ST_STEP_SELECT, "stop from complete");
expect_int(stub_alarm_cancel_count >= cancels + 1 ? 1 : 0, 1, "alarm cancel");
expect_int((int)app_machine_state(), ST_IDLE, "last step goes idle");
ui_evt_id_t ids[8];
int n = pop_ids(ids, 8);
if (!has_id(ids, n, EVT_PROCESS_IDLE)) {
fail("last step idle event");
}
}
static void ecn2_remjet_zero_time(void)
@@ -246,7 +244,8 @@ static void ecn2_remjet_zero_time(void)
expect_int(stub_motor_enabled ? 1 : 0, 0, "remjet no enable");
expect_int(stub_agitate_start_count, starts, "remjet no agitate");
app_machine_tick(0);
expect_int((int)app_machine_state(), ST_COMPLETE, "remjet complete next tick");
expect_int((int)app_machine_state(), ST_ARMED, "remjet auto-armed next");
expect_int((int)app_machine_step_index(), 2, "remjet auto-advance step");
}
static void stop_ignored_meaningless_in_idle(void)
@@ -275,7 +274,8 @@ static void c41_clock(void)
app_machine_tick(179999);
expect_int((int)app_machine_state(), ST_RUNNING, "c41 tick179999");
app_machine_tick(180000);
expect_int((int)app_machine_state(), ST_COMPLETE, "c41 tick180000");
expect_int((int)app_machine_state(), ST_ARMED, "c41 auto-armed next");
expect_int((int)app_machine_step_index(), 1, "c41 auto-advance step");
}
int main(void)
@@ -285,7 +285,7 @@ int main(void)
arm_start_complete_custom_10s();
stop_disables_motor_and_resume();
return_from_stopped();
stop_during_complete_cancels_alarm();
auto_advance_last_step_goes_idle();
ecn2_remjet_zero_time();
stop_ignored_meaningless_in_idle();
c41_clock();