Compare commits
61
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce9d729027 | ||
|
|
06acd84928 | ||
|
|
cfd047a0ac | ||
|
|
a3830a58cb | ||
|
|
3785f07189 | ||
|
|
37b772218b | ||
|
|
8f7db974fa | ||
|
|
02d226b949 | ||
|
|
d71e28c85d | ||
|
|
adff1d963b | ||
|
|
d84116c45b | ||
|
|
1ade64b2b2 | ||
|
|
20535ceb14 | ||
|
|
76ef75da29 | ||
|
|
d70df219f8 | ||
|
|
7e854e10f0 | ||
|
|
b6c385e09c | ||
|
|
a9f76ea058 | ||
|
|
c40c714028 | ||
|
|
f87ab0c65f | ||
|
|
b4230b2a0e | ||
|
|
04e1ed0f16 | ||
|
|
a33b4b0c74 | ||
|
|
73f3158b27 | ||
|
|
a3fe809359 | ||
|
|
637ea8cb6e | ||
|
|
84b60155dd | ||
|
|
7406114368 | ||
|
|
0b0a713098 | ||
|
|
9ea89ac467 | ||
|
|
5f1727da7d | ||
|
|
32af44a702 | ||
|
|
82f55af4b1 | ||
|
|
880f9a18c5 | ||
|
|
e04d0f0e7f | ||
|
|
114135fba0 | ||
|
|
4fe0648005 | ||
|
|
ce9af6382e | ||
|
|
d21f7ad4e4 | ||
|
|
f47299b939 | ||
|
|
51faa0fe59 | ||
|
|
a19accf6a7 | ||
|
|
2c61e3576f | ||
|
|
0b1d14d21a | ||
|
|
e245db175b | ||
|
|
57ca78a1e3 | ||
|
|
674d067872 | ||
|
|
8f5b41ebba | ||
|
|
c047d5d96f | ||
|
|
461771cc02 | ||
|
|
9b2c6bf3ee | ||
|
|
415c13a605 | ||
|
|
d8397778d5 | ||
|
|
255db87881 | ||
|
|
179efb54cd | ||
|
|
01cc76b8b1 | ||
|
|
a9298ae521 | ||
|
|
2458a61625 | ||
|
|
d8fed43b42 | ||
|
|
d6a33996ce | ||
|
|
9558e58e43 |
@@ -0,0 +1,82 @@
|
|||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [develop]
|
||||||
|
push:
|
||||||
|
branches: [develop, "refactor/**", "feature/**"]
|
||||||
|
tags: ["v*"]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- 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 -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 && \
|
||||||
|
ctest --test-dir build/host --output-on-failure
|
||||||
|
|
||||||
|
firmware:
|
||||||
|
needs: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: docker.io/espressif/idf:v5.4
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- board: wroom
|
||||||
|
target: esp32
|
||||||
|
sdkconfig: sdkconfig.wroom
|
||||||
|
- board: jc4827w543
|
||||||
|
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: |
|
||||||
|
. $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 && \
|
||||||
|
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 \
|
||||||
|
@flash_args
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: hashFiles('CMakeLists.txt') != ''
|
||||||
|
with:
|
||||||
|
name: firmware-${{ matrix.board }}
|
||||||
|
path: |
|
||||||
|
autofilm-${{ matrix.board }}-*.bin
|
||||||
|
build/${{ matrix.board }}/*.bin
|
||||||
|
build/${{ matrix.board }}/bootloader/bootloader.bin
|
||||||
|
build/${{ matrix.board }}/partition_table/partition-table.bin
|
||||||
|
build/${{ matrix.board }}/flasher_args.json
|
||||||
|
build/${{ matrix.board }}/sdkconfig
|
||||||
|
size-${{ matrix.board }}.txt
|
||||||
|
|
||||||
@@ -3,3 +3,9 @@
|
|||||||
.vscode/c_cpp_properties.json
|
.vscode/c_cpp_properties.json
|
||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
.vscode/ipch
|
.vscode/ipch
|
||||||
|
build/
|
||||||
|
build_host/
|
||||||
|
sdkconfig
|
||||||
|
sdkconfig.old
|
||||||
|
managed_components/
|
||||||
|
dependencies.lock
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# Agent conventions — AutoFilm-ESP32
|
||||||
|
|
||||||
|
Instructions for humans and coding agents working in this repository.
|
||||||
|
|
||||||
|
## Product constraints
|
||||||
|
|
||||||
|
- Default temperatures, step times, and CW/CCW rotation counts for C41, E6, ECN-2, B&W, Custom, and B&WREV **do not change** unless a commit message explicitly says so and the recipe table in `docs/CURRENT_STATE.md` is updated in the same change.
|
||||||
|
- The machine **must always be stoppable** while a step is running: keypad Esc (`'X'`, 4 across / 4 down) and, on the S3 panel, a STOP control. Stop drops motor enable immediately, then offers Resume or return to step select.
|
||||||
|
- UI must not block on motor motion, OneWire conversion, or melody playback.
|
||||||
|
|
||||||
|
## Branch and docs
|
||||||
|
|
||||||
|
- Integration branch is **`develop`**. Feature and refactor work is done on a branch cut from `develop`, then opened as a PR **into `develop`**.
|
||||||
|
- Do not PR this work into `main`. `main` is not the day-to-day integration line.
|
||||||
|
- Active work branch for this refactor: `refactor/esp-idf-modular-ui` (rebase onto `develop` if the branch still points at `main`).
|
||||||
|
- Architecture intent: `docs/CURRENT_STATE.md`, `docs/TARGET_ARCHITECTURE.md`, `docs/CICD.md`.
|
||||||
|
- Implementation sessions: `docs/megaplans/REFACTOR-MEGAPLAN.md` then exactly one `docs/megaplans/refactor/Pxx-*.md`. Do not start the next phase in the same session.
|
||||||
|
- Do not commit secrets (Git tokens, Wi-Fi passwords, `sdkconfig` with provisioned keys).
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
| Path | Role |
|
||||||
|
| --- | --- |
|
||||||
|
| `src/`, `include/` | Legacy Arduino sources; shrink, do not grow, except bugfixes needed for the port |
|
||||||
|
| `main/` | IDF entry |
|
||||||
|
| `components/app_*` | Application: process table, machine, UI |
|
||||||
|
| `components/ui_cmd` | Command/event types only |
|
||||||
|
| `components/hal_*` | Hardware adapters |
|
||||||
|
| `components/board_*` | Pin maps and board bring-up |
|
||||||
|
| `docs/` | Design notes |
|
||||||
|
| `.gitea/workflows/` | Gitea Actions (tests + firmware builds) |
|
||||||
|
|
||||||
|
New behaviour goes into `components/`, not into larger `menu.cpp` files.
|
||||||
|
|
||||||
|
## Framework policy
|
||||||
|
|
||||||
|
- Build system is **ESP-IDF**. PlatformIO + Arduino remains only until the IDF skeleton compiles both boards.
|
||||||
|
- **Arduino-as-component** is allowed as a bridge for AccelStepper, Keypad, LiquidCrystal_I2C, OneWire, DallasTemperature.
|
||||||
|
- Treat Arduino as debt:
|
||||||
|
- No Arduino types (`String`, `Keypad`, `AccelStepper`, `LiquidCrystal_I2C`) in headers of `app_machine`, `app_process`, or `ui_cmd`.
|
||||||
|
- Do not add new Arduino library dependencies.
|
||||||
|
- Prefer IDF drivers (GPIO, RMT, I2C, LEDC, `esp_timer`, `esp_lcd`) for new work.
|
||||||
|
- Two firmware flavours: `board_wroom` and `board_jc4827w543`. Gate pins and peripherals with board CMake, not `#ifdef` sprinkled through the state machine.
|
||||||
|
|
||||||
|
## Concurrency
|
||||||
|
|
||||||
|
- One responsibility per task: input, UI, machine, motor, temperature, audio.
|
||||||
|
- Cross-task data: FreeRTOS queues, task notifications, atomics. No unsynchronised use of `lcd` / `stepper` / `sensors`.
|
||||||
|
- ISR / stop path: set a flag, disable motor EN, `xQueueSendFromISR` / `xTaskNotifyFromISR`. No heap, no `delay`, no LCD from ISR.
|
||||||
|
- Do not `vTaskDelete` worker tasks after startup. Do not disable the task watchdog to “make it work”.
|
||||||
|
- `vTaskDelay` / `esp_timer` instead of busy-wait `millis()` loops.
|
||||||
|
|
||||||
|
## UI / machine split
|
||||||
|
|
||||||
|
- UI emits commands; machine emits events. See `docs/TARGET_ARCHITECTURE.md`.
|
||||||
|
- Touch and keypad are input adapters. They produce the same commands.
|
||||||
|
- Display adapters render events. Machine code must compile with no display driver linked (except in tests that stub HAL).
|
||||||
|
|
||||||
|
## Code style
|
||||||
|
|
||||||
|
- C11 for new IDF components; C++17 only where an existing Arduino library forces it, isolated in `hal_*`.
|
||||||
|
- `snake_case` for new functions and files; existing camelCase stays until that file is rewritten.
|
||||||
|
- 4-space indent to match the current tree.
|
||||||
|
- No raw `new` / `malloc` for per-frame strings. No `String` concatenation on the control path.
|
||||||
|
- Log with `ESP_LOGx` tags (`machine`, `motor`, `ui`, `temp`, `input`), not `Serial.print`.
|
||||||
|
- Errors are `esp_err_t` across HAL boundaries.
|
||||||
|
|
||||||
|
## Recipes
|
||||||
|
|
||||||
|
- Recipe tables live in `app_process` (or today’s `devSequence.cpp` until moved).
|
||||||
|
- Session time nudges (±5 s) are RAM overlays, not edits of the const defaults.
|
||||||
|
- Persistent custom profiles are out of scope until designed; do not half-add NVS.
|
||||||
|
|
||||||
|
## Hardware notes
|
||||||
|
|
||||||
|
- WROOM pins are defined in the legacy `config` headers; do not silently reuse them on the S3 panel.
|
||||||
|
- JC4827W543C: NV3041A 480×272 QSPI, GT911 on I²C, limited free IO. Confirm any new GPIO against the board pin table before assigning STEP/DIR/EN/1-Wire.
|
||||||
|
- Motor enable is active LOW on the current WROOM wiring. Safe state is disabled.
|
||||||
|
|
||||||
|
## Commits and review
|
||||||
|
|
||||||
|
- Cut work from `develop`. Open PRs against `develop`.
|
||||||
|
- Imperative commit subjects (`Add machine stop notify`, not `Added`).
|
||||||
|
- One concern per commit when practical (HAL wrap ≠ UI rewrite ≠ recipe move).
|
||||||
|
- Do not reformat whole legacy files in the same commit as a behaviour change.
|
||||||
|
- If defaults would change, fail the change unless the user explicitly asked.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
- Prefer host-side tests for `app_process` lookup and the machine state machine with a stub motor (`tests/host`, CMake + CTest).
|
||||||
|
- On-target smoke: select process, start a short Custom step, Esc mid-step, confirm EN high and Resume works.
|
||||||
|
- CI is Gitea Actions: host tests must pass before either firmware image is built. See `docs/CICD.md`.
|
||||||
|
- After `tests/host` exists, run it before push. After the IDF skeleton exists, a local `idf.py` build for the board you touched is expected.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
if(NOT DEFINED AUTOFILM_BOARD)
|
||||||
|
set(AUTOFILM_BOARD wroom CACHE STRING "wroom|jc4827w543")
|
||||||
|
endif()
|
||||||
|
if(IDF_TARGET STREQUAL "esp32s3" OR AUTOFILM_BOARD STREQUAL "jc4827w543")
|
||||||
|
set(EXCLUDE_COMPONENTS board_wroom)
|
||||||
|
set(AUTOFILM_BOARD jc4827w543 CACHE STRING "wroom|jc4827w543" FORCE)
|
||||||
|
else()
|
||||||
|
set(EXCLUDE_COMPONENTS board_jc4827w543)
|
||||||
|
endif()
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(autofilm)
|
||||||
@@ -0,0 +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 freertos)
|
||||||
@@ -0,0 +1,425 @@
|
|||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
|
#include "app_machine.h"
|
||||||
|
#include "app_process.h"
|
||||||
|
#include "hal_motor.h"
|
||||||
|
#include "hal_temp.h"
|
||||||
|
#include "hal_audio.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#define AGITATE_RPM 60u
|
||||||
|
|
||||||
|
static machine_state_t s_state;
|
||||||
|
static uint8_t s_proc;
|
||||||
|
static uint8_t s_step;
|
||||||
|
static uint32_t s_remaining_ms;
|
||||||
|
static uint32_t s_deadline_ms;
|
||||||
|
static bool s_have_deadline;
|
||||||
|
static bool s_resume_pending;
|
||||||
|
static bool s_auto_advance;
|
||||||
|
static QueueHandle_t s_evtq;
|
||||||
|
|
||||||
|
static void emit(ui_evt_t ev)
|
||||||
|
{
|
||||||
|
if (s_evtq != NULL) {
|
||||||
|
xQueueSend(s_evtq, &ev, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fill_common(ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
const process_def_t *p = app_process_get(s_proc);
|
||||||
|
memset(ev, 0, sizeof(*ev));
|
||||||
|
ev->process_id = s_proc;
|
||||||
|
ev->step_index = s_step;
|
||||||
|
ev->step_count = (p != NULL) ? p->step_count : 0;
|
||||||
|
ev->time_s = app_process_time_s(s_proc, s_step);
|
||||||
|
ev->remaining_ms = s_remaining_ms;
|
||||||
|
if (p != NULL && s_step < p->step_count) {
|
||||||
|
ev->cw_revs = p->steps[s_step].cw_revs;
|
||||||
|
ev->ccw_revs = p->steps[s_step].ccw_revs;
|
||||||
|
ev->temp_min_c = p->steps[s_step].temp_min_c;
|
||||||
|
ev->temp_pref_c = p->steps[s_step].temp_pref_c;
|
||||||
|
ev->temp_max_c = p->steps[s_step].temp_max_c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emit_id(ui_evt_id_t id)
|
||||||
|
{
|
||||||
|
ui_evt_t ev;
|
||||||
|
fill_common(&ev);
|
||||||
|
ev.id = id;
|
||||||
|
emit(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emit_fault(int16_t code)
|
||||||
|
{
|
||||||
|
ui_evt_t ev;
|
||||||
|
fill_common(&ev);
|
||||||
|
ev.id = EVT_FAULT;
|
||||||
|
ev.fault_code = code;
|
||||||
|
emit(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void apply_step_index(uint8_t step)
|
||||||
|
{
|
||||||
|
const process_def_t *p = app_process_get(s_proc);
|
||||||
|
if (p == NULL || p->step_count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (step >= p->step_count) {
|
||||||
|
step = (uint8_t)(p->step_count - 1u);
|
||||||
|
}
|
||||||
|
s_step = step;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void replace_session_time_s(uint16_t time_s)
|
||||||
|
{
|
||||||
|
uint16_t cur = app_process_time_s(s_proc, s_step);
|
||||||
|
int32_t left = (int32_t)time_s - (int32_t)cur;
|
||||||
|
while (left != 0) {
|
||||||
|
int8_t chunk = (left > 127) ? 127 : (left < -128) ? -128 : (int8_t)left;
|
||||||
|
if (app_process_adjust_time(s_proc, s_step, chunk) != ESP_OK) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint16_t now = app_process_time_s(s_proc, s_step);
|
||||||
|
int32_t next_left = (int32_t)time_s - (int32_t)now;
|
||||||
|
if (next_left == left) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
left = next_left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void go_step_select(void)
|
||||||
|
{
|
||||||
|
s_state = ST_STEP_SELECT;
|
||||||
|
s_have_deadline = false;
|
||||||
|
s_resume_pending = false;
|
||||||
|
emit_id(EVT_STEP_VIEW);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void go_idle(void)
|
||||||
|
{
|
||||||
|
s_state = ST_IDLE;
|
||||||
|
s_have_deadline = false;
|
||||||
|
s_resume_pending = false;
|
||||||
|
s_remaining_ms = 0;
|
||||||
|
emit_id(EVT_PROCESS_IDLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void enter_armed(void)
|
||||||
|
{
|
||||||
|
s_state = ST_ARMED;
|
||||||
|
s_have_deadline = false;
|
||||||
|
s_resume_pending = false;
|
||||||
|
emit_id(EVT_STEP_ARMED);
|
||||||
|
}
|
||||||
|
|
||||||
|
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_have_deadline = false;
|
||||||
|
s_resume_pending = true;
|
||||||
|
|
||||||
|
if (t_s == 0 || p == NULL) {
|
||||||
|
hal_motor_enable(false);
|
||||||
|
} else {
|
||||||
|
(void)hal_motor_agitate_start(p->steps[s_step].cw_revs,
|
||||||
|
p->steps[s_step].ccw_revs,
|
||||||
|
AGITATE_RPM);
|
||||||
|
hal_motor_enable(true);
|
||||||
|
}
|
||||||
|
s_state = ST_RUNNING;
|
||||||
|
emit_id(EVT_STEP_STARTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void complete_step(void)
|
||||||
|
{
|
||||||
|
hal_motor_agitate_stop();
|
||||||
|
hal_motor_enable(false);
|
||||||
|
s_remaining_ms = 0;
|
||||||
|
s_have_deadline = false;
|
||||||
|
s_resume_pending = false;
|
||||||
|
s_state = ST_COMPLETE;
|
||||||
|
hal_audio_alarm_complete();
|
||||||
|
emit_id(EVT_STEP_COMPLETE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void maybe_auto_advance(uint32_t now_ms)
|
||||||
|
{
|
||||||
|
(void)now_ms;
|
||||||
|
if (!s_auto_advance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const process_def_t *p = app_process_get(s_proc);
|
||||||
|
if (p == NULL) {
|
||||||
|
go_idle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((uint16_t)s_step + 1u < p->step_count) {
|
||||||
|
s_step++;
|
||||||
|
enter_armed();
|
||||||
|
} else {
|
||||||
|
go_idle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void arm_or_next_from_complete(void)
|
||||||
|
{
|
||||||
|
const process_def_t *p = app_process_get(s_proc);
|
||||||
|
if (p == NULL) {
|
||||||
|
go_idle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((uint16_t)s_step + 1u < p->step_count) {
|
||||||
|
s_step++;
|
||||||
|
enter_armed();
|
||||||
|
} else {
|
||||||
|
go_idle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_machine_init(void)
|
||||||
|
{
|
||||||
|
s_state = ST_IDLE;
|
||||||
|
s_proc = 0;
|
||||||
|
s_step = 0;
|
||||||
|
s_remaining_ms = 0;
|
||||||
|
s_deadline_ms = 0;
|
||||||
|
s_have_deadline = false;
|
||||||
|
s_resume_pending = false;
|
||||||
|
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();
|
||||||
|
hal_audio_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
machine_state_t app_machine_state(void)
|
||||||
|
{
|
||||||
|
return s_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t app_machine_process_id(void)
|
||||||
|
{
|
||||||
|
return s_proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t app_machine_step_index(void)
|
||||||
|
{
|
||||||
|
return s_step;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t app_machine_remaining_ms(void)
|
||||||
|
{
|
||||||
|
return s_remaining_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
int app_machine_last_event(ui_evt_t *out)
|
||||||
|
{
|
||||||
|
if (s_evtq == NULL || out == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (xQueueReceive(s_evtq, out, 0) == pdTRUE) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_machine_on_temp(float c, bool ok)
|
||||||
|
{
|
||||||
|
ui_evt_t ev;
|
||||||
|
fill_common(&ev);
|
||||||
|
ev.id = EVT_TEMP_UPDATED;
|
||||||
|
ev.temp_c = c;
|
||||||
|
ev.temp_ok = ok;
|
||||||
|
emit(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_machine_tick(uint32_t now_ms)
|
||||||
|
{
|
||||||
|
if (s_state == ST_RUNNING && s_resume_pending) {
|
||||||
|
s_deadline_ms = now_ms + s_remaining_ms;
|
||||||
|
s_have_deadline = true;
|
||||||
|
s_resume_pending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s_state == ST_RUNNING && s_have_deadline) {
|
||||||
|
if (now_ms >= s_deadline_ms) {
|
||||||
|
s_remaining_ms = 0;
|
||||||
|
complete_step();
|
||||||
|
maybe_auto_advance(now_ms);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_remaining_ms = s_deadline_ms - now_ms;
|
||||||
|
emit_id(EVT_STEP_PROGRESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_machine_handle_cmd(const ui_cmd_t *cmd)
|
||||||
|
{
|
||||||
|
if (cmd == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cmd->id) {
|
||||||
|
case CMD_SELECT_PROCESS:
|
||||||
|
if (app_process_get(cmd->process_id) == NULL) {
|
||||||
|
emit_fault(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_proc = cmd->process_id;
|
||||||
|
s_step = 0;
|
||||||
|
s_remaining_ms = 0;
|
||||||
|
s_state = ST_STEP_SELECT;
|
||||||
|
emit_id(EVT_PROCESS_SELECTED);
|
||||||
|
emit_id(EVT_STEP_VIEW);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case CMD_BROWSE_STEP:
|
||||||
|
if (s_state != ST_STEP_SELECT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const process_def_t *p = app_process_get(s_proc);
|
||||||
|
if (p == NULL || p->step_count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int32_t next = (int32_t)s_step + (int32_t)cmd->step_delta;
|
||||||
|
if (next < 0) {
|
||||||
|
next = 0;
|
||||||
|
}
|
||||||
|
if (next >= (int32_t)p->step_count) {
|
||||||
|
next = (int32_t)p->step_count - 1;
|
||||||
|
}
|
||||||
|
s_step = (uint8_t)next;
|
||||||
|
emit_id(EVT_STEP_VIEW);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
case CMD_ADJUST_STEP_TIME:
|
||||||
|
if (s_state != ST_STEP_SELECT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (app_process_adjust_time(s_proc, s_step, cmd->time_delta_s) == ESP_OK) {
|
||||||
|
emit_id(EVT_STEP_VIEW);
|
||||||
|
} else {
|
||||||
|
emit_fault(1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
case CMD_ARM_STEP:
|
||||||
|
if (s_state == ST_STEP_SELECT) {
|
||||||
|
apply_step_index(cmd->step_index);
|
||||||
|
enter_armed();
|
||||||
|
} else if (s_state == ST_COMPLETE) {
|
||||||
|
arm_or_next_from_complete();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
case CMD_START_STEP:
|
||||||
|
if (s_state == ST_STEP_SELECT) {
|
||||||
|
apply_step_index(cmd->step_index);
|
||||||
|
enter_armed();
|
||||||
|
} else if (s_state == ST_ARMED) {
|
||||||
|
apply_step_index(cmd->step_index);
|
||||||
|
start_running();
|
||||||
|
} else if (s_state == ST_COMPLETE) {
|
||||||
|
arm_or_next_from_complete();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
case CMD_CANCEL_ARMED:
|
||||||
|
if (s_state == ST_ARMED) {
|
||||||
|
hal_motor_enable(false);
|
||||||
|
go_step_select();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
case CMD_STOP:
|
||||||
|
if (s_state == ST_IDLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s_state == ST_RUNNING) {
|
||||||
|
/* request_stop before any other work */
|
||||||
|
hal_motor_request_stop();
|
||||||
|
hal_motor_enable(false);
|
||||||
|
if (s_have_deadline && !s_resume_pending) {
|
||||||
|
/* remaining already updated by last tick; if never ticked, keep start remaining */
|
||||||
|
}
|
||||||
|
hal_audio_beep_short();
|
||||||
|
s_state = ST_STOPPED;
|
||||||
|
s_have_deadline = false;
|
||||||
|
s_resume_pending = false;
|
||||||
|
emit_id(EVT_STEP_STOPPED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s_state == ST_ARMED) {
|
||||||
|
hal_motor_enable(false);
|
||||||
|
go_step_select();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s_state == ST_STOPPED) {
|
||||||
|
{
|
||||||
|
uint16_t secs = (uint16_t)((s_remaining_ms + 999u) / 1000u);
|
||||||
|
replace_session_time_s(secs);
|
||||||
|
}
|
||||||
|
hal_motor_enable(false);
|
||||||
|
go_step_select();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s_state == ST_COMPLETE) {
|
||||||
|
hal_audio_alarm_cancel();
|
||||||
|
hal_audio_beep_short();
|
||||||
|
go_step_select();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
case CMD_RESUME:
|
||||||
|
if (s_state == ST_STOPPED) {
|
||||||
|
const process_def_t *p = app_process_get(s_proc);
|
||||||
|
hal_audio_beep_short();
|
||||||
|
s_resume_pending = true;
|
||||||
|
s_have_deadline = false;
|
||||||
|
if (p != NULL && s_remaining_ms > 0) {
|
||||||
|
(void)hal_motor_agitate_start(p->steps[s_step].cw_revs,
|
||||||
|
p->steps[s_step].ccw_revs,
|
||||||
|
AGITATE_RPM);
|
||||||
|
hal_motor_enable(true);
|
||||||
|
} else {
|
||||||
|
hal_motor_enable(false);
|
||||||
|
}
|
||||||
|
s_state = ST_RUNNING;
|
||||||
|
emit_id(EVT_STEP_RESUMED);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
case CMD_RETURN_TO_STEP_SELECT:
|
||||||
|
if (s_state == ST_STOPPED) {
|
||||||
|
uint16_t secs = (uint16_t)((s_remaining_ms + 999u) / 1000u);
|
||||||
|
replace_session_time_s(secs);
|
||||||
|
hal_motor_enable(false);
|
||||||
|
go_step_select();
|
||||||
|
} else if (s_state == ST_COMPLETE) {
|
||||||
|
go_step_select();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "ui_cmd.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ST_IDLE = 0,
|
||||||
|
ST_STEP_SELECT,
|
||||||
|
ST_ARMED,
|
||||||
|
ST_RUNNING,
|
||||||
|
ST_STOPPED,
|
||||||
|
ST_COMPLETE
|
||||||
|
} machine_state_t;
|
||||||
|
|
||||||
|
void app_machine_init(void);
|
||||||
|
machine_state_t app_machine_state(void);
|
||||||
|
void app_machine_handle_cmd(const ui_cmd_t *cmd);
|
||||||
|
void app_machine_tick(uint32_t now_ms); /* drive timers; tests call this */
|
||||||
|
void app_machine_on_temp(float c, bool ok);
|
||||||
|
int app_machine_last_event(ui_evt_t *out); /* pop 1 event; 0 empty, 1 ok */
|
||||||
|
uint8_t app_machine_process_id(void);
|
||||||
|
uint8_t app_machine_step_index(void);
|
||||||
|
uint32_t app_machine_remaining_ms(void);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
idf_component_register(SRCS "app_process.c"
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
REQUIRES ui_cmd)
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
#include "app_process.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static const process_step_t s_c41_steps[] = {
|
||||||
|
{"Prewarm", 180, 1.f, 1.f, 37.8f, 38.f, 38.2f},
|
||||||
|
{"Developer", 195, 5.5f, 5.f, 37.8f, 38.f, 38.2f},
|
||||||
|
{"Bleach", 45, 5.5f, 5.f, 32.f, 38.f, 38.2f},
|
||||||
|
{"Fix", 180, 5.5f, 5.f, 32.f, 38.f, 38.2f},
|
||||||
|
{"Rinse 1", 60, 3.5f, 3.f, 32.f, 38.f, 38.2f},
|
||||||
|
{"Rinse 2", 60, 3.5f, 3.f, 32.f, 38.f, 38.2f},
|
||||||
|
{"Fin Rinse", 30, 1.f, 1.f, 32.f, 38.f, 38.2f},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const process_step_t s_e6_steps[] = {
|
||||||
|
{"Preheat", 180, 1.f, 1.f, 37.5f, 38.f, 38.5f},
|
||||||
|
{"FirstDev", 360, 5.5f, 5.f, 37.7f, 38.f, 38.3f},
|
||||||
|
{"Wash 1", 120, 3.5f, 3.f, 33.0f, 38.f, 38.0f},
|
||||||
|
{"Reversal", 120, 5.f, 5.5f, 37.7f, 38.f, 38.3f},
|
||||||
|
{"ColorDev", 360, 5.5f, 5.f, 37.0f, 38.f, 39.0f},
|
||||||
|
{"PreBleach", 120, 5.5f, 5.5f, 37.f, 38.f, 38.f},
|
||||||
|
{"Bleach", 360, 5.5f, 5.5f, 37.5f, 38.f, 38.5f},
|
||||||
|
{"Fixer", 240, 5.5f, 5.5f, 37.5f, 38.f, 38.5f},
|
||||||
|
{"Wash 2", 120, 3.5f, 3.f, 33.0f, 38.f, 38.5f},
|
||||||
|
{"Wash 3", 120, 3.5f, 3.f, 33.0f, 38.f, 38.5f},
|
||||||
|
{"Wash 4", 120, 3.5f, 3.f, 33.0f, 38.f, 38.5f},
|
||||||
|
{"Fin Rinse", 30, 3.5f, 3.f, 19.0f, 20.f, 21.0f},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const process_step_t s_ecn2_steps[] = {
|
||||||
|
{"Prebath", 180, 1.f, 1.f, 27.f, 38.f, 38.f},
|
||||||
|
{"RemJet", 0, 0.f, 0.f, 0.f, 0.f, 0.f},
|
||||||
|
{"Developer", 210, 5.5f, 5.f, 40.8f, 41.f, 41.2f},
|
||||||
|
{"Stop Bath", 60, 3.5f, 3.f, 27.f, 38.f, 38.f},
|
||||||
|
{"Wash", 180, 3.5f, 3.f, 27.f, 38.f, 38.f},
|
||||||
|
{"Bleach", 150, 5.5f, 5.f, 27.f, 38.f, 38.f},
|
||||||
|
{"Fixer", 120, 5.5f, 5.f, 27.f, 38.f, 38.f},
|
||||||
|
{"Wash 2", 300, 3.5f, 3.f, 27.f, 38.f, 38.f},
|
||||||
|
{"Fin Rinse", 120, 1.f, 1.f, 27.f, 38.f, 38.f},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const process_step_t s_bw_steps[] = {
|
||||||
|
{"Developer", 510, 5.5f, 5.f, 19.f, 20.f, 21.f},
|
||||||
|
{"Stop", 30, 3.5f, 3.f, 19.f, 20.f, 21.f},
|
||||||
|
{"Fix", 300, 3.5f, 3.f, 19.f, 20.f, 21.f},
|
||||||
|
{"Rinse 1", 60, 3.5f, 3.f, 19.f, 20.f, 21.f},
|
||||||
|
{"Rinse 2", 90, 3.5f, 3.f, 19.f, 20.f, 21.f},
|
||||||
|
{"Rinse 3", 120, 3.5f, 3.f, 19.f, 20.f, 21.f},
|
||||||
|
{"Fin Rinse", 30, 1.f, 1.f, 19.f, 20.f, 21.f},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const process_step_t s_custom_steps[] = {
|
||||||
|
{"Developer", 10, 5.5f, 5.f, 19.f, 20.f, 21.f},
|
||||||
|
{"Stop", 10, 3.5f, 3.f, 19.f, 20.f, 21.f},
|
||||||
|
{"Fix", 10, 3.5f, 3.f, 19.f, 20.f, 21.f},
|
||||||
|
{"Rinse", 10, 3.5f, 3.f, 19.f, 20.f, 21.f},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const process_step_t s_bwrev_steps[] = {
|
||||||
|
{"FirstDev", 720, 5.5f, 5.f, 19.5f, 20.f, 20.5f},
|
||||||
|
{"Wash 1", 300, 3.5f, 3.f, 15.5f, 20.f, 22.5f},
|
||||||
|
{"Bleach", 300, 5.5f, 5.f, 19.5f, 20.f, 22.5f},
|
||||||
|
{"Wash 2", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f},
|
||||||
|
{"Clearing", 120, 5.5f, 5.f, 19.5f, 20.f, 22.5f},
|
||||||
|
{"Wash 3", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f},
|
||||||
|
{"Reversal", 120, 5.5f, 5.f, 19.5f, 20.f, 22.5f},
|
||||||
|
{"SecondDev", 360, 5.5f, 5.f, 19.5f, 20.f, 22.5f},
|
||||||
|
{"Wash 4", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f},
|
||||||
|
{"Fix", 300, 5.5f, 5.f, 19.5f, 20.f, 22.5f},
|
||||||
|
{"Wash 5", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f},
|
||||||
|
{"Fin Rinse", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const process_def_t s_processes[PROCESS_COUNT] = {
|
||||||
|
{"C41", 7, s_c41_steps},
|
||||||
|
{"E6", 12, s_e6_steps},
|
||||||
|
{"ECN-2", 9, s_ecn2_steps},
|
||||||
|
{"B&W", 7, s_bw_steps},
|
||||||
|
{"Custom", 4, s_custom_steps},
|
||||||
|
{"B&WREV", 12, s_bwrev_steps},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int16_t s_time_delta_s[PROCESS_COUNT][PROCESS_MAX_STEPS];
|
||||||
|
|
||||||
|
void app_process_init(void)
|
||||||
|
{
|
||||||
|
memset(s_time_delta_s, 0, sizeof(s_time_delta_s));
|
||||||
|
}
|
||||||
|
|
||||||
|
const process_def_t *app_process_get(uint8_t id)
|
||||||
|
{
|
||||||
|
if (id >= PROCESS_COUNT) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return &s_processes[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
const process_def_t *app_process_find_name(const char *name)
|
||||||
|
{
|
||||||
|
if (name == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for (uint8_t i = 0; i < PROCESS_COUNT; i++) {
|
||||||
|
if (strcmp(s_processes[i].name, name) == 0) {
|
||||||
|
return &s_processes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t app_process_time_s(uint8_t id, uint8_t step)
|
||||||
|
{
|
||||||
|
if (id >= PROCESS_COUNT) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const process_def_t *p = &s_processes[id];
|
||||||
|
if (step >= p->step_count) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int32_t t = (int32_t)p->steps[step].time_s + (int32_t)s_time_delta_s[id][step];
|
||||||
|
if (t < 0) {
|
||||||
|
t = 0;
|
||||||
|
}
|
||||||
|
if (t > 36000) {
|
||||||
|
t = 36000;
|
||||||
|
}
|
||||||
|
return (uint16_t)t;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t app_process_adjust_time(uint8_t id, uint8_t step, int8_t delta_s)
|
||||||
|
{
|
||||||
|
if (id >= PROCESS_COUNT) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
if (step >= s_processes[id].step_count || step >= PROCESS_MAX_STEPS) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
s_time_delta_s[id][step] = (int16_t)(s_time_delta_s[id][step] + delta_s);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_process_reset_session(uint8_t id)
|
||||||
|
{
|
||||||
|
if (id >= PROCESS_COUNT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memset(s_time_delta_s[id], 0, sizeof(s_time_delta_s[id]));
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#ifdef AUTOFILM_HOST
|
||||||
|
#include "esp_host_compat.h"
|
||||||
|
#else
|
||||||
|
#include "esp_err.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define PROCESS_COUNT 6
|
||||||
|
#define PROCESS_MAX_STEPS 20
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROC_C41 = 0,
|
||||||
|
PROC_E6 = 1,
|
||||||
|
PROC_ECN2 = 2,
|
||||||
|
PROC_BW = 3,
|
||||||
|
PROC_CUSTOM = 4,
|
||||||
|
PROC_BWREV = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
|
uint16_t time_s;
|
||||||
|
float cw_revs, ccw_revs;
|
||||||
|
float temp_min_c, temp_pref_c, temp_max_c;
|
||||||
|
} process_step_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
|
uint8_t step_count;
|
||||||
|
const process_step_t *steps;
|
||||||
|
} process_def_t;
|
||||||
|
|
||||||
|
void app_process_init(void); /* zero session deltas */
|
||||||
|
const process_def_t *app_process_get(uint8_t id);
|
||||||
|
const process_def_t *app_process_find_name(const char *name);
|
||||||
|
uint16_t app_process_time_s(uint8_t id, uint8_t step); /* default + session delta, min 0 */
|
||||||
|
esp_err_t app_process_adjust_time(uint8_t id, uint8_t step, int8_t delta_s);
|
||||||
|
void app_process_reset_session(uint8_t id);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
idf_component_register(SRCS "app_ui.c" "app_ui_color.c"
|
||||||
|
INCLUDE_DIRS "include" "."
|
||||||
|
REQUIRES ui_cmd app_machine app_process
|
||||||
|
PRIV_REQUIRES hal_display)
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
#include "app_ui.h"
|
||||||
|
#include "screens.h"
|
||||||
|
#include "app_machine.h"
|
||||||
|
#include "app_process.h"
|
||||||
|
#include "hal_display.h"
|
||||||
|
|
||||||
|
void app_ui_draw_color_chrome(machine_state_t st);
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static ui_evt_t s_last;
|
||||||
|
static bool s_have;
|
||||||
|
static bool s_dirty;
|
||||||
|
|
||||||
|
static void fmt_mmss(char *buf, size_t n, uint32_t total_s)
|
||||||
|
{
|
||||||
|
unsigned m = (unsigned)(total_s / 60u);
|
||||||
|
unsigned s = (unsigned)(total_s % 60u);
|
||||||
|
if (m > 99u) {
|
||||||
|
m = 99u;
|
||||||
|
}
|
||||||
|
snprintf(buf, n, "%02u:%02u", m, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_temp_row3(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
/* row3 col13: thermo + tt.tC or -- */
|
||||||
|
char tbuf[8];
|
||||||
|
if (ev != NULL && ev->temp_ok) {
|
||||||
|
snprintf(tbuf, sizeof(tbuf), "%4.1fC", (double)ev->temp_c);
|
||||||
|
hal_display_glyph_thermo(13, 3);
|
||||||
|
hal_display_text(14, 3, tbuf);
|
||||||
|
} else {
|
||||||
|
hal_display_text(13, 3, " --");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *step_name(uint8_t proc, uint8_t step)
|
||||||
|
{
|
||||||
|
const process_def_t *p = app_process_get(proc);
|
||||||
|
if (p == NULL || step >= p->step_count) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return p->steps[step].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_program_select(void)
|
||||||
|
{
|
||||||
|
hal_display_clear();
|
||||||
|
hal_display_text(0, 0, "Select Programme:");
|
||||||
|
hal_display_text(0, 1, "1. C41 4. ECN-2");
|
||||||
|
hal_display_text(0, 2, "2. E6 5. Custom");
|
||||||
|
hal_display_text(0, 3, "3. B&W 6. B&W Rev");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_step_select(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
char line[32];
|
||||||
|
char mm[8];
|
||||||
|
uint16_t ts = ev->time_s;
|
||||||
|
fmt_mmss(mm, sizeof(mm), ts);
|
||||||
|
int pref = (int)(ev->temp_pref_c + (ev->temp_pref_c >= 0 ? 0.5f : -0.5f));
|
||||||
|
hal_display_clear();
|
||||||
|
hal_display_text(0, 0, "Step Time Temp");
|
||||||
|
snprintf(line, sizeof(line), "%-9.9s %s %2dC", step_name(ev->process_id, ev->step_index), mm, pref);
|
||||||
|
hal_display_text(0, 1, line);
|
||||||
|
hal_display_text(0, 2, "Scroll / Esc / Ent");
|
||||||
|
draw_temp_row3(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_armed(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
char line[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));
|
||||||
|
hal_display_clear();
|
||||||
|
snprintf(line, sizeof(line), "%-10.10s %s %2dC", step_name(ev->process_id, ev->step_index), mm, pref);
|
||||||
|
hal_display_text(0, 0, line);
|
||||||
|
hal_display_text(0, 1, "Ent:start Esc:quit");
|
||||||
|
draw_temp_row3(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_running(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
char line[32];
|
||||||
|
char mm[8];
|
||||||
|
uint32_t rem_s = (ev->remaining_ms + 999u) / 1000u;
|
||||||
|
fmt_mmss(mm, sizeof(mm), rem_s);
|
||||||
|
hal_display_clear();
|
||||||
|
snprintf(line, sizeof(line), "%-20.20s", step_name(ev->process_id, ev->step_index));
|
||||||
|
hal_display_text(0, 0, line);
|
||||||
|
snprintf(line, sizeof(line), "Remaining: %s", mm);
|
||||||
|
hal_display_text(0, 1, line);
|
||||||
|
draw_temp_row3(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_stopped(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
char line[32];
|
||||||
|
char mm[8];
|
||||||
|
uint32_t rem_s = (ev->remaining_ms + 999u) / 1000u;
|
||||||
|
fmt_mmss(mm, sizeof(mm), rem_s);
|
||||||
|
hal_display_clear();
|
||||||
|
snprintf(line, sizeof(line), "%-10.10s %s", step_name(ev->process_id, ev->step_index), mm);
|
||||||
|
hal_display_text(0, 0, line);
|
||||||
|
hal_display_text(0, 1, "E:resume X:back");
|
||||||
|
draw_temp_row3(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_complete(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
char line[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);
|
||||||
|
hal_display_text(0, 1, "E:next X:back");
|
||||||
|
draw_temp_row3(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_ui_init(void)
|
||||||
|
{
|
||||||
|
memset(&s_last, 0, sizeof(s_last));
|
||||||
|
s_have = false;
|
||||||
|
s_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_ui_on_event(const ui_evt_t *ev)
|
||||||
|
{
|
||||||
|
if (ev == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ev->id == EVT_TEMP_UPDATED && s_have) {
|
||||||
|
s_last.temp_c = ev->temp_c;
|
||||||
|
s_last.temp_ok = ev->temp_ok;
|
||||||
|
s_dirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ev->id == EVT_STEP_PROGRESS && s_have) {
|
||||||
|
s_last.remaining_ms = ev->remaining_ms;
|
||||||
|
s_last.id = ev->id;
|
||||||
|
s_dirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_last = *ev;
|
||||||
|
s_have = true;
|
||||||
|
s_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool app_ui_is_dirty(void)
|
||||||
|
{
|
||||||
|
return s_dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_ui_draw(void)
|
||||||
|
{
|
||||||
|
machine_state_t st = app_machine_state();
|
||||||
|
ui_evt_t ev = s_last;
|
||||||
|
if (!s_have) {
|
||||||
|
ev.temp_ok = false;
|
||||||
|
}
|
||||||
|
ev.remaining_ms = app_machine_remaining_ms();
|
||||||
|
ev.process_id = app_machine_process_id();
|
||||||
|
ev.step_index = app_machine_step_index();
|
||||||
|
if (app_process_get(ev.process_id) != NULL) {
|
||||||
|
ev.time_s = app_process_time_s(ev.process_id, ev.step_index);
|
||||||
|
const process_def_t *p = app_process_get(ev.process_id);
|
||||||
|
if (p != NULL && ev.step_index < p->step_count) {
|
||||||
|
ev.temp_pref_c = p->steps[ev.step_index].temp_pref_c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (st) {
|
||||||
|
case ST_IDLE:
|
||||||
|
draw_program_select();
|
||||||
|
break;
|
||||||
|
case ST_STEP_SELECT:
|
||||||
|
draw_step_select(&ev);
|
||||||
|
break;
|
||||||
|
case ST_ARMED:
|
||||||
|
draw_armed(&ev);
|
||||||
|
break;
|
||||||
|
case ST_RUNNING:
|
||||||
|
draw_running(&ev);
|
||||||
|
break;
|
||||||
|
case ST_STOPPED:
|
||||||
|
draw_stopped(&ev);
|
||||||
|
break;
|
||||||
|
case ST_COMPLETE:
|
||||||
|
draw_complete(&ev);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
draw_program_select();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
app_ui_draw_color_chrome(st);
|
||||||
|
hal_display_flush();
|
||||||
|
s_dirty = false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#include "app_ui.h"
|
||||||
|
#include "app_machine.h"
|
||||||
|
#include "hal_display.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
bool app_ui_touch_to_key(int x, int y, char *out_key)
|
||||||
|
{
|
||||||
|
if (out_key == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
machine_state_t st = app_machine_state();
|
||||||
|
const int w = HAL_DISPLAY_PX_W;
|
||||||
|
const int h = HAL_DISPLAY_PX_H;
|
||||||
|
const int sk = HAL_DISPLAY_SOFTKEY_H;
|
||||||
|
|
||||||
|
if (x < 0 || y < 0 || x >= w || y >= h) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st == ST_RUNNING || st == ST_COMPLETE) {
|
||||||
|
if (y >= h - sk) {
|
||||||
|
*out_key = 'X';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st == ST_STOPPED) {
|
||||||
|
if (y >= h - sk) {
|
||||||
|
*out_key = (x < w / 2) ? 'E' : 'X';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st == ST_IDLE) {
|
||||||
|
/* 2x3 process grid matching ProgramSelect labels. */
|
||||||
|
int col = (x < w / 2) ? 0 : 1;
|
||||||
|
int row = y / (h / 3);
|
||||||
|
if (row > 2) {
|
||||||
|
row = 2;
|
||||||
|
}
|
||||||
|
/* left: 1,2,3 right: 4,5,6 */
|
||||||
|
int id = row + col * 3;
|
||||||
|
*out_key = (char)('1' + id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st == ST_ARMED) {
|
||||||
|
if (y >= h - sk) {
|
||||||
|
*out_key = (x < w / 2) ? 'E' : 'X';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*out_key = 'E';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* STEP_SELECT: strips for U/D/L/R, body Ent */
|
||||||
|
if (y < 40) {
|
||||||
|
*out_key = 'U';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (y >= h - sk) {
|
||||||
|
*out_key = 'D';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (x < 80) {
|
||||||
|
*out_key = 'L';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (x >= w - 80) {
|
||||||
|
*out_key = 'R';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*out_key = 'E';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_ui_draw_color_chrome(machine_state_t st)
|
||||||
|
{
|
||||||
|
switch (st) {
|
||||||
|
case ST_RUNNING:
|
||||||
|
case ST_COMPLETE:
|
||||||
|
hal_display_softkey(0, 1, "STOP");
|
||||||
|
break;
|
||||||
|
case ST_STOPPED:
|
||||||
|
hal_display_softkey(0, 2, "RESUME");
|
||||||
|
hal_display_softkey(1, 2, "BACK");
|
||||||
|
break;
|
||||||
|
case ST_ARMED:
|
||||||
|
hal_display_softkey(0, 2, "START");
|
||||||
|
hal_display_softkey(1, 2, "QUIT");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_cmd.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void app_ui_init(void);
|
||||||
|
void app_ui_on_event(const ui_evt_t *ev);
|
||||||
|
void app_ui_draw(void);
|
||||||
|
bool app_ui_is_dirty(void);
|
||||||
|
|
||||||
|
/* Map colour-panel touch to the same raw keys as the WROOM keypad. */
|
||||||
|
bool app_ui_touch_to_key(int x, int y, char *out_key);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/* Screen ids used by app_ui. Layouts are 20x4 WROOM strings. */
|
||||||
|
typedef enum {
|
||||||
|
UI_SCREEN_PROGRAM_SELECT = 0,
|
||||||
|
UI_SCREEN_STEP_SELECT,
|
||||||
|
UI_SCREEN_ARMED,
|
||||||
|
UI_SCREEN_RUNNING,
|
||||||
|
UI_SCREEN_STOPPED,
|
||||||
|
UI_SCREEN_COMPLETE
|
||||||
|
} app_ui_screen_t;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
idf_component_register(SRCS "board.c" INCLUDE_DIRS "include" REQUIRES driver)
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
#include "board.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* JC4827W543C pin lock (community 480x272 NV3041A + GT911).
|
||||||
|
*
|
||||||
|
* Display NV3041A QSPI:
|
||||||
|
* CS 45
|
||||||
|
* SCK 47
|
||||||
|
* D0 21
|
||||||
|
* D1 48
|
||||||
|
* D2 40
|
||||||
|
* D3 39
|
||||||
|
* BL 1
|
||||||
|
*
|
||||||
|
* GT911 I2C:
|
||||||
|
* SDA 8
|
||||||
|
* SCL 4
|
||||||
|
* INT 3
|
||||||
|
* RST 38
|
||||||
|
* addr 0x5D
|
||||||
|
*
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
return "jc4827w543";
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_num_t board_pin_motor_en(void)
|
||||||
|
{
|
||||||
|
return (gpio_num_t)7;
|
||||||
|
}
|
||||||
|
|
||||||
|
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; }
|
||||||
|
gpio_num_t board_pin_lcd_d1(void) { return (gpio_num_t)48; }
|
||||||
|
gpio_num_t board_pin_lcd_d2(void) { return (gpio_num_t)40; }
|
||||||
|
gpio_num_t board_pin_lcd_d3(void) { return (gpio_num_t)39; }
|
||||||
|
gpio_num_t board_pin_lcd_bl(void) { return (gpio_num_t)1; }
|
||||||
|
|
||||||
|
gpio_num_t board_pin_tp_sda(void) { return (gpio_num_t)8; }
|
||||||
|
gpio_num_t board_pin_tp_scl(void) { return (gpio_num_t)4; }
|
||||||
|
gpio_num_t board_pin_tp_int(void) { return (gpio_num_t)3; }
|
||||||
|
gpio_num_t board_pin_tp_rst(void) { return (gpio_num_t)38; }
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
|
||||||
|
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);
|
||||||
|
gpio_num_t board_pin_lcd_d0(void);
|
||||||
|
gpio_num_t board_pin_lcd_d1(void);
|
||||||
|
gpio_num_t board_pin_lcd_d2(void);
|
||||||
|
gpio_num_t board_pin_lcd_d3(void);
|
||||||
|
gpio_num_t board_pin_lcd_bl(void);
|
||||||
|
|
||||||
|
gpio_num_t board_pin_tp_sda(void);
|
||||||
|
gpio_num_t board_pin_tp_scl(void);
|
||||||
|
gpio_num_t board_pin_tp_int(void);
|
||||||
|
gpio_num_t board_pin_tp_rst(void);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
idf_component_register(SRCS "board.c" INCLUDE_DIRS "include" REQUIRES driver)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#include "board.h"
|
||||||
|
|
||||||
|
const char *board_name(void)
|
||||||
|
{
|
||||||
|
return "wroom";
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_num_t board_pin_motor_en(void)
|
||||||
|
{
|
||||||
|
return (gpio_num_t)27;
|
||||||
|
}
|
||||||
|
|
||||||
|
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; }
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dependencies: {}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
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);
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
set(srcs)
|
||||||
|
set(priv_req driver)
|
||||||
|
|
||||||
|
if(IDF_TARGET STREQUAL "esp32")
|
||||||
|
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()
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${srcs}
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
REQUIRES driver
|
||||||
|
PRIV_REQUIRES ${priv_req})
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void hal_audio_init(void);
|
||||||
|
void hal_audio_beep_short(void);
|
||||||
|
void hal_audio_alarm_complete(void);
|
||||||
|
void hal_audio_alarm_cancel(void);
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
void hal_audio_init(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_audio_beep_short(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_audio_alarm_complete(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_audio_alarm_cancel(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "driver/ledc.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
|
#define PIN_BEEP 25
|
||||||
|
#define LEDC_TIMER LEDC_TIMER_0
|
||||||
|
#define LEDC_MODE LEDC_LOW_SPEED_MODE
|
||||||
|
#define LEDC_CHANNEL LEDC_CHANNEL_0
|
||||||
|
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT
|
||||||
|
#define LEDC_DUTY 4095
|
||||||
|
|
||||||
|
static const char *TAG = "audio";
|
||||||
|
|
||||||
|
enum {
|
||||||
|
AUDIO_SHORT = 1,
|
||||||
|
AUDIO_ALARM,
|
||||||
|
AUDIO_CANCEL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static QueueHandle_t s_q;
|
||||||
|
static TaskHandle_t s_task;
|
||||||
|
static bool s_inited;
|
||||||
|
|
||||||
|
static void tone_on(uint32_t hz)
|
||||||
|
{
|
||||||
|
ledc_set_freq(LEDC_MODE, LEDC_TIMER, hz);
|
||||||
|
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, LEDC_DUTY);
|
||||||
|
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tone_off(void)
|
||||||
|
{
|
||||||
|
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 0);
|
||||||
|
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool drain_cancel(void)
|
||||||
|
{
|
||||||
|
int msg;
|
||||||
|
while (xQueueReceive(s_q, &msg, 0) == pdTRUE) {
|
||||||
|
if (msg == AUDIO_CANCEL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void play_short(void)
|
||||||
|
{
|
||||||
|
tone_on(2000);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
tone_off();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void play_alarm(void)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
if (drain_cancel()) {
|
||||||
|
tone_off();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tone_on(2000);
|
||||||
|
TickType_t t0 = xTaskGetTickCount();
|
||||||
|
while ((xTaskGetTickCount() - t0) < pdMS_TO_TICKS(500)) {
|
||||||
|
if (drain_cancel()) {
|
||||||
|
tone_off();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
}
|
||||||
|
tone_off();
|
||||||
|
t0 = xTaskGetTickCount();
|
||||||
|
while ((xTaskGetTickCount() - t0) < pdMS_TO_TICKS(250)) {
|
||||||
|
if (drain_cancel()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
tone_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;
|
||||||
|
}
|
||||||
|
ledc_timer_config_t tcfg = {
|
||||||
|
.speed_mode = LEDC_MODE,
|
||||||
|
.duty_resolution = LEDC_DUTY_RES,
|
||||||
|
.timer_num = LEDC_TIMER,
|
||||||
|
.freq_hz = 2000,
|
||||||
|
.clk_cfg = LEDC_AUTO_CLK,
|
||||||
|
};
|
||||||
|
ledc_timer_config(&tcfg);
|
||||||
|
ledc_channel_config_t ccfg = {
|
||||||
|
.gpio_num = PIN_BEEP,
|
||||||
|
.speed_mode = LEDC_MODE,
|
||||||
|
.channel = LEDC_CHANNEL,
|
||||||
|
.intr_type = LEDC_INTR_DISABLE,
|
||||||
|
.timer_sel = LEDC_TIMER,
|
||||||
|
.duty = 0,
|
||||||
|
.hpoint = 0,
|
||||||
|
};
|
||||||
|
ledc_channel_config(&ccfg);
|
||||||
|
tone_off();
|
||||||
|
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 beep pin=%d LEDC", PIN_BEEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
set(srcs)
|
||||||
|
set(priv_inc)
|
||||||
|
set(priv_req driver)
|
||||||
|
|
||||||
|
if(IDF_TARGET STREQUAL "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)
|
||||||
|
else()
|
||||||
|
list(APPEND srcs stub/hal_display.c)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${srcs}
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
PRIV_INCLUDE_DIRS ${priv_inc}
|
||||||
|
REQUIRES driver
|
||||||
|
PRIV_REQUIRES ${priv_req})
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
/* Public-domain 5x7 glyphs for ASCII 32..126. */
|
||||||
|
static const uint8_t font5x7[95][7] = {
|
||||||
|
{0,0,0,0,0,0,0},
|
||||||
|
{0x04,0x04,0x04,0x04,0x04,0x00,0x04},
|
||||||
|
{0x0A,0x0A,0x0A,0,0,0,0},
|
||||||
|
{0x0A,0x0A,0x1F,0x0A,0x1F,0x0A,0x0A},
|
||||||
|
{0x04,0x0F,0x14,0x0E,0x05,0x1E,0x04},
|
||||||
|
{0x18,0x19,0x02,0x04,0x08,0x13,0x03},
|
||||||
|
{0x08,0x14,0x14,0x08,0x15,0x12,0x0D},
|
||||||
|
{0x04,0x04,0x08,0,0,0,0},
|
||||||
|
{0x02,0x04,0x08,0x08,0x08,0x04,0x02},
|
||||||
|
{0x08,0x04,0x02,0x02,0x02,0x04,0x08},
|
||||||
|
{0x00,0x04,0x15,0x0E,0x15,0x04,0x00},
|
||||||
|
{0x00,0x04,0x04,0x1F,0x04,0x04,0x00},
|
||||||
|
{0,0,0,0,0,0x04,0x08},
|
||||||
|
{0,0,0,0x1F,0,0,0},
|
||||||
|
{0,0,0,0,0,0,0x04},
|
||||||
|
{0x01,0x02,0x04,0x08,0x10,0,0},
|
||||||
|
{0x0E,0x11,0x13,0x15,0x19,0x11,0x0E},
|
||||||
|
{0x04,0x0C,0x04,0x04,0x04,0x04,0x0E},
|
||||||
|
{0x0E,0x11,0x01,0x02,0x04,0x08,0x1F},
|
||||||
|
{0x1F,0x02,0x04,0x02,0x01,0x11,0x0E},
|
||||||
|
{0x02,0x06,0x0A,0x12,0x1F,0x02,0x02},
|
||||||
|
{0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E},
|
||||||
|
{0x06,0x08,0x10,0x1E,0x11,0x11,0x0E},
|
||||||
|
{0x1F,0x01,0x02,0x04,0x08,0x08,0x08},
|
||||||
|
{0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E},
|
||||||
|
{0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C},
|
||||||
|
{0,0x04,0,0,0x04,0,0},
|
||||||
|
{0,0x04,0,0,0x04,0x04,0x08},
|
||||||
|
{0x02,0x04,0x08,0x10,0x08,0x04,0x02},
|
||||||
|
{0,0,0x1F,0,0x1F,0,0},
|
||||||
|
{0x08,0x04,0x02,0x01,0x02,0x04,0x08},
|
||||||
|
{0x0E,0x11,0x01,0x02,0x04,0x00,0x04},
|
||||||
|
{0x0E,0x11,0x17,0x15,0x17,0x10,0x0E},
|
||||||
|
{0x0E,0x11,0x11,0x1F,0x11,0x11,0x11},
|
||||||
|
{0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E},
|
||||||
|
{0x0E,0x11,0x10,0x10,0x10,0x11,0x0E},
|
||||||
|
{0x1C,0x12,0x11,0x11,0x11,0x12,0x1C},
|
||||||
|
{0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F},
|
||||||
|
{0x1F,0x10,0x10,0x1E,0x10,0x10,0x10},
|
||||||
|
{0x0E,0x11,0x10,0x17,0x11,0x11,0x0F},
|
||||||
|
{0x11,0x11,0x11,0x1F,0x11,0x11,0x11},
|
||||||
|
{0x0E,0x04,0x04,0x04,0x04,0x04,0x0E},
|
||||||
|
{0x07,0x02,0x02,0x02,0x02,0x12,0x0C},
|
||||||
|
{0x11,0x12,0x14,0x18,0x14,0x12,0x11},
|
||||||
|
{0x10,0x10,0x10,0x10,0x10,0x10,0x1F},
|
||||||
|
{0x11,0x1B,0x15,0x15,0x11,0x11,0x11},
|
||||||
|
{0x11,0x19,0x15,0x13,0x11,0x11,0x11},
|
||||||
|
{0x0E,0x11,0x11,0x11,0x11,0x11,0x0E},
|
||||||
|
{0x1E,0x11,0x11,0x1E,0x10,0x10,0x10},
|
||||||
|
{0x0E,0x11,0x11,0x11,0x15,0x12,0x0D},
|
||||||
|
{0x1E,0x11,0x11,0x1E,0x14,0x12,0x11},
|
||||||
|
{0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E},
|
||||||
|
{0x1F,0x04,0x04,0x04,0x04,0x04,0x04},
|
||||||
|
{0x11,0x11,0x11,0x11,0x11,0x11,0x0E},
|
||||||
|
{0x11,0x11,0x11,0x11,0x11,0x0A,0x04},
|
||||||
|
{0x11,0x11,0x11,0x15,0x15,0x1B,0x11},
|
||||||
|
{0x11,0x11,0x0A,0x04,0x0A,0x11,0x11},
|
||||||
|
{0x11,0x11,0x0A,0x04,0x04,0x04,0x04},
|
||||||
|
{0x1F,0x01,0x02,0x04,0x08,0x10,0x1F},
|
||||||
|
{0x0E,0x08,0x08,0x08,0x08,0x08,0x0E},
|
||||||
|
{0x10,0x08,0x04,0x02,0x01,0,0},
|
||||||
|
{0x0E,0x02,0x02,0x02,0x02,0x02,0x0E},
|
||||||
|
{0x04,0x0A,0x11,0,0,0,0},
|
||||||
|
{0,0,0,0,0,0,0x1F},
|
||||||
|
{0x08,0x04,0x02,0,0,0,0},
|
||||||
|
{0,0,0x0E,0x01,0x0F,0x11,0x0F},
|
||||||
|
{0x10,0x10,0x1E,0x11,0x11,0x11,0x1E},
|
||||||
|
{0,0,0x0E,0x10,0x10,0x11,0x0E},
|
||||||
|
{0x01,0x01,0x0F,0x11,0x11,0x11,0x0F},
|
||||||
|
{0,0,0x0E,0x11,0x1F,0x10,0x0E},
|
||||||
|
{0x06,0x08,0x08,0x1E,0x08,0x08,0x08},
|
||||||
|
{0,0,0x0F,0x11,0x11,0x0F,0x01,},
|
||||||
|
{0x10,0x10,0x1E,0x11,0x11,0x11,0x11},
|
||||||
|
{0x04,0,0x0C,0x04,0x04,0x04,0x0E},
|
||||||
|
{0x02,0,0x06,0x02,0x02,0x02,0x12},
|
||||||
|
{0x10,0x10,0x12,0x14,0x18,0x14,0x12},
|
||||||
|
{0x0C,0x04,0x04,0x04,0x04,0x04,0x0E},
|
||||||
|
{0,0,0x1A,0x15,0x15,0x15,0x15},
|
||||||
|
{0,0,0x1E,0x11,0x11,0x11,0x11},
|
||||||
|
{0,0,0x0E,0x11,0x11,0x11,0x0E},
|
||||||
|
{0,0,0x1E,0x11,0x11,0x1E,0x10},
|
||||||
|
{0,0,0x0F,0x11,0x11,0x0F,0x01},
|
||||||
|
{0,0,0x16,0x19,0x10,0x10,0x10},
|
||||||
|
{0,0,0x0F,0x10,0x0E,0x01,0x1E},
|
||||||
|
{0x08,0x08,0x1E,0x08,0x08,0x08,0x06},
|
||||||
|
{0,0,0x11,0x11,0x11,0x11,0x0F},
|
||||||
|
{0,0,0x11,0x11,0x11,0x0A,0x04},
|
||||||
|
{0,0,0x11,0x15,0x15,0x15,0x0A},
|
||||||
|
{0,0,0x11,0x0A,0x04,0x0A,0x11},
|
||||||
|
{0,0,0x11,0x11,0x0F,0x01,0x0E},
|
||||||
|
{0,0,0x1F,0x02,0x04,0x08,0x1F},
|
||||||
|
{0x02,0x04,0x04,0x08,0x04,0x04,0x02},
|
||||||
|
{0x04,0x04,0x04,0x04,0x04,0x04,0x04},
|
||||||
|
{0x08,0x04,0x04,0x02,0x04,0x04,0x08},
|
||||||
|
{0x00,0x00,0x08,0x15,0x02,0x00,0x00},
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
#include "hal_display.h"
|
||||||
|
#include "font5x7.h"
|
||||||
|
#include "board.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/spi_master.h"
|
||||||
|
#include "esp_heap_caps.h"
|
||||||
|
#include "esp_lcd_panel_io.h"
|
||||||
|
#include "esp_lcd_panel_commands.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
#define LCD_HOST SPI2_HOST
|
||||||
|
#define LCD_H HAL_DISPLAY_PX_H
|
||||||
|
#define LCD_W HAL_DISPLAY_PX_W
|
||||||
|
|
||||||
|
#define COL_BG 0x18E3
|
||||||
|
#define COL_FG 0xFFFF
|
||||||
|
#define COL_ACC 0xF800
|
||||||
|
#define COL_BTN 0x39E7
|
||||||
|
#define COL_BTNT 0xFFFF
|
||||||
|
|
||||||
|
static const char *TAG = "disp-s3";
|
||||||
|
|
||||||
|
static esp_lcd_panel_io_handle_t s_io;
|
||||||
|
static uint16_t *s_fb;
|
||||||
|
static bool s_inited;
|
||||||
|
static bool s_dirty;
|
||||||
|
|
||||||
|
static uint16_t swap565(uint16_t c)
|
||||||
|
{
|
||||||
|
return (uint16_t)((c >> 8) | (c << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fb_fill(uint16_t color)
|
||||||
|
{
|
||||||
|
if (s_fb == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint16_t c = swap565(color);
|
||||||
|
for (int i = 0; i < LCD_W * LCD_H; i++) {
|
||||||
|
s_fb[i] = c;
|
||||||
|
}
|
||||||
|
s_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fb_pixel(int x, int y, uint16_t color)
|
||||||
|
{
|
||||||
|
if (s_fb == NULL || x < 0 || y < 0 || x >= LCD_W || y >= LCD_H) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_fb[y * LCD_W + x] = swap565(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fb_fill_rect(int x, int y, int w, int h, uint16_t color)
|
||||||
|
{
|
||||||
|
if (s_fb == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (x < 0) {
|
||||||
|
w += x;
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
if (y < 0) {
|
||||||
|
h += y;
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
if (x + w > LCD_W) {
|
||||||
|
w = LCD_W - x;
|
||||||
|
}
|
||||||
|
if (y + h > LCD_H) {
|
||||||
|
h = LCD_H - y;
|
||||||
|
}
|
||||||
|
uint16_t c = swap565(color);
|
||||||
|
for (int yy = 0; yy < h; yy++) {
|
||||||
|
uint16_t *row = &s_fb[(y + yy) * LCD_W + x];
|
||||||
|
for (int xx = 0; xx < w; xx++) {
|
||||||
|
row[xx] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fb_char(int x, int y, char ch, uint16_t fg, int scale)
|
||||||
|
{
|
||||||
|
if (ch < 32 || ch > 126) {
|
||||||
|
ch = '?';
|
||||||
|
}
|
||||||
|
const uint8_t *g = font5x7[ch - 32];
|
||||||
|
for (int row = 0; row < 7; row++) {
|
||||||
|
uint8_t bits = g[row];
|
||||||
|
for (int col = 0; col < 5; col++) {
|
||||||
|
if (bits & (0x10 >> col)) {
|
||||||
|
fb_fill_rect(x + col * scale, y + row * scale, scale, scale, fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fb_str(int x, int y, const char *s, uint16_t fg, int scale)
|
||||||
|
{
|
||||||
|
if (s == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int cx = x;
|
||||||
|
while (*s) {
|
||||||
|
fb_char(cx, y, *s, fg, scale);
|
||||||
|
cx += 6 * scale;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lcd_cmd(uint8_t cmd, const uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
if (s_io == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint32_t packed = ((uint32_t)0x02 << 24) | ((uint32_t)cmd << 8);
|
||||||
|
esp_lcd_panel_io_tx_param(s_io, packed, data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lcd_window(int x0, int y0, int x1, int y1)
|
||||||
|
{
|
||||||
|
uint8_t cas[4] = { (uint8_t)(x0 >> 8), (uint8_t)x0, (uint8_t)(x1 >> 8), (uint8_t)x1 };
|
||||||
|
uint8_t ras[4] = { (uint8_t)(y0 >> 8), (uint8_t)y0, (uint8_t)(y1 >> 8), (uint8_t)y1 };
|
||||||
|
lcd_cmd(LCD_CMD_CASET, cas, 4);
|
||||||
|
lcd_cmd(LCD_CMD_RASET, ras, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lcd_init_panel(void)
|
||||||
|
{
|
||||||
|
/* Vendor bring-up for NV3041A-01 480x272. Commands are public datasheet/community. */
|
||||||
|
static const uint8_t seq[][3] = {
|
||||||
|
{0xFF, 0xA5, 1},
|
||||||
|
{0x36, 0x00, 1},
|
||||||
|
{0x3A, 0x01, 1},
|
||||||
|
{0x41, 0x03, 1},
|
||||||
|
{0x44, 0x15, 1},
|
||||||
|
{0x45, 0x15, 1},
|
||||||
|
{0x7D, 0x03, 1},
|
||||||
|
{0xC1, 0xBB, 1},
|
||||||
|
{0xC2, 0x05, 1},
|
||||||
|
{0xC3, 0x10, 1},
|
||||||
|
{0xC6, 0x3E, 1},
|
||||||
|
{0xC7, 0x25, 1},
|
||||||
|
{0xC8, 0x11, 1},
|
||||||
|
{0xC9, 0x20, 1},
|
||||||
|
{0x7A, 0x51, 1},
|
||||||
|
{0x6F, 0x2D, 1},
|
||||||
|
{0x78, 0x4B, 1},
|
||||||
|
{0x70, 0x07, 1},
|
||||||
|
{0x71, 0x05, 1},
|
||||||
|
};
|
||||||
|
lcd_cmd(LCD_CMD_SWRESET, NULL, 0);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(120));
|
||||||
|
lcd_cmd(LCD_CMD_SLPOUT, NULL, 0);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(120));
|
||||||
|
for (size_t i = 0; i < sizeof(seq) / sizeof(seq[0]); i++) {
|
||||||
|
uint8_t d = seq[i][1];
|
||||||
|
lcd_cmd(seq[i][0], &d, seq[i][2]);
|
||||||
|
}
|
||||||
|
lcd_cmd(LCD_CMD_DISPON, NULL, 0);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(20));
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_display_init(void)
|
||||||
|
{
|
||||||
|
if (s_inited) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_config_t bl = {
|
||||||
|
.pin_bit_mask = 1ULL << board_pin_lcd_bl(),
|
||||||
|
.mode = GPIO_MODE_OUTPUT,
|
||||||
|
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.intr_type = GPIO_INTR_DISABLE,
|
||||||
|
};
|
||||||
|
gpio_config(&bl);
|
||||||
|
gpio_set_level(board_pin_lcd_bl(), 1);
|
||||||
|
|
||||||
|
spi_bus_config_t buscfg = {
|
||||||
|
.sclk_io_num = board_pin_lcd_sck(),
|
||||||
|
.data0_io_num = board_pin_lcd_d0(),
|
||||||
|
.data1_io_num = board_pin_lcd_d1(),
|
||||||
|
.data2_io_num = board_pin_lcd_d2(),
|
||||||
|
.data3_io_num = board_pin_lcd_d3(),
|
||||||
|
.max_transfer_sz = LCD_W * 40 * sizeof(uint16_t),
|
||||||
|
};
|
||||||
|
esp_err_t err = spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "spi_bus_initialize %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_lcd_panel_io_spi_config_t io_config = {
|
||||||
|
.cs_gpio_num = board_pin_lcd_cs(),
|
||||||
|
.dc_gpio_num = -1,
|
||||||
|
.spi_mode = 0,
|
||||||
|
.pclk_hz = 40 * 1000 * 1000,
|
||||||
|
.trans_queue_depth = 4,
|
||||||
|
.lcd_cmd_bits = 32,
|
||||||
|
.lcd_param_bits = 8,
|
||||||
|
.flags = {
|
||||||
|
.quad_mode = 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
err = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &s_io);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "panel_io %s", esp_err_to_name(err));
|
||||||
|
s_io = NULL;
|
||||||
|
} else {
|
||||||
|
lcd_init_panel();
|
||||||
|
}
|
||||||
|
|
||||||
|
s_fb = heap_caps_malloc((size_t)LCD_W * LCD_H * sizeof(uint16_t),
|
||||||
|
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||||
|
if (s_fb == NULL) {
|
||||||
|
s_fb = heap_caps_malloc((size_t)LCD_W * LCD_H * sizeof(uint16_t), MALLOC_CAP_8BIT);
|
||||||
|
}
|
||||||
|
if (s_fb == NULL) {
|
||||||
|
ESP_LOGE(TAG, "no framebuffer");
|
||||||
|
}
|
||||||
|
fb_fill(COL_BG);
|
||||||
|
s_inited = true;
|
||||||
|
ESP_LOGI(TAG, "NV3041A QSPI %dx%d CS=%d SCK=%d D0=%d",
|
||||||
|
LCD_W, LCD_H, (int)board_pin_lcd_cs(), (int)board_pin_lcd_sck(),
|
||||||
|
(int)board_pin_lcd_d0());
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_display_clear(void)
|
||||||
|
{
|
||||||
|
fb_fill(COL_BG);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_display_text(int col, int row, const char *s)
|
||||||
|
{
|
||||||
|
if (s == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (col < 0) {
|
||||||
|
col = 0;
|
||||||
|
}
|
||||||
|
if (row < 0 || row >= HAL_DISPLAY_ROWS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int x = col * HAL_DISPLAY_CELL_W + 4;
|
||||||
|
int y = 8 + row * HAL_DISPLAY_CELL_H;
|
||||||
|
/* clip 20 columns */
|
||||||
|
char buf[HAL_DISPLAY_COLS + 1];
|
||||||
|
int n = 0;
|
||||||
|
while (s[n] && n < HAL_DISPLAY_COLS - col && n < HAL_DISPLAY_COLS) {
|
||||||
|
buf[n] = s[n];
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
buf[n] = 0;
|
||||||
|
fb_fill_rect(x, y, n * HAL_DISPLAY_CELL_W, HAL_DISPLAY_CELL_H - 4, COL_BG);
|
||||||
|
fb_str(x, y + 6, buf, COL_FG, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_display_glyph_thermo(int col, int row)
|
||||||
|
{
|
||||||
|
int x = col * HAL_DISPLAY_CELL_W + 8;
|
||||||
|
int y = 8 + row * HAL_DISPLAY_CELL_H + 4;
|
||||||
|
fb_fill_rect(x + 6, y, 6, 14, COL_ACC);
|
||||||
|
fb_fill_rect(x + 3, y + 14, 12, 10, COL_ACC);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_display_softkey(int slot, int slots, const char *label)
|
||||||
|
{
|
||||||
|
if (slots < 1) {
|
||||||
|
slots = 1;
|
||||||
|
}
|
||||||
|
if (slot < 0 || slot >= slots) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int w = LCD_W / slots;
|
||||||
|
int x = slot * w + 4;
|
||||||
|
int y = LCD_H - HAL_DISPLAY_SOFTKEY_H + 4;
|
||||||
|
int bw = w - 8;
|
||||||
|
int bh = HAL_DISPLAY_SOFTKEY_H - 8;
|
||||||
|
fb_fill_rect(x, y, bw, bh, COL_BTN);
|
||||||
|
if (label != NULL) {
|
||||||
|
int scale = 3;
|
||||||
|
int tw = (int)strlen(label) * 6 * scale;
|
||||||
|
int tx = x + (bw - tw) / 2;
|
||||||
|
int ty = y + (bh - 7 * scale) / 2;
|
||||||
|
if (tx < x) {
|
||||||
|
tx = x + 4;
|
||||||
|
}
|
||||||
|
fb_str(tx, ty, label, COL_BTNT, scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_display_flush(void)
|
||||||
|
{
|
||||||
|
if (!s_dirty || s_fb == NULL || s_io == NULL) {
|
||||||
|
s_dirty = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lcd_window(0, 0, LCD_W - 1, LCD_H - 1);
|
||||||
|
const int chunk_rows = 16;
|
||||||
|
for (int y = 0; y < LCD_H; y += chunk_rows) {
|
||||||
|
int rows = chunk_rows;
|
||||||
|
if (y + rows > LCD_H) {
|
||||||
|
rows = LCD_H - y;
|
||||||
|
}
|
||||||
|
uint32_t packed = ((uint32_t)0x32 << 24) | ((uint32_t)LCD_CMD_RAMWR << 8);
|
||||||
|
esp_lcd_panel_io_tx_color(s_io, packed, &s_fb[y * LCD_W],
|
||||||
|
(size_t)rows * LCD_W * sizeof(uint16_t));
|
||||||
|
}
|
||||||
|
s_dirty = false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define HAL_DISPLAY_COLS 20
|
||||||
|
#define HAL_DISPLAY_ROWS 4
|
||||||
|
|
||||||
|
/* Colour panel geometry (S3). 2004 ignores pixel APIs. */
|
||||||
|
#define HAL_DISPLAY_PX_W 480
|
||||||
|
#define HAL_DISPLAY_PX_H 272
|
||||||
|
#define HAL_DISPLAY_CELL_W 24
|
||||||
|
#define HAL_DISPLAY_CELL_H 32
|
||||||
|
#define HAL_DISPLAY_SOFTKEY_H 48
|
||||||
|
|
||||||
|
void hal_display_init(void);
|
||||||
|
void hal_display_clear(void);
|
||||||
|
void hal_display_text(int col, int row, const char *s); /* clip to 20 cols */
|
||||||
|
void hal_display_glyph_thermo(int col, int row);
|
||||||
|
void hal_display_flush(void);
|
||||||
|
|
||||||
|
/* Softkey chrome; no-op on 2004. slot in [0, slots). */
|
||||||
|
void hal_display_softkey(int slot, int slots, const char *label);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#include "hal_display.h"
|
||||||
|
|
||||||
|
void hal_display_init(void) {}
|
||||||
|
void hal_display_clear(void) {}
|
||||||
|
void hal_display_text(int col, int row, const char *s)
|
||||||
|
{
|
||||||
|
(void)col;
|
||||||
|
(void)row;
|
||||||
|
(void)s;
|
||||||
|
}
|
||||||
|
void hal_display_glyph_thermo(int col, int row)
|
||||||
|
{
|
||||||
|
(void)col;
|
||||||
|
(void)row;
|
||||||
|
}
|
||||||
|
void hal_display_flush(void) {}
|
||||||
|
|
||||||
|
void hal_display_softkey(int slot, int slots, const char *label)
|
||||||
|
{
|
||||||
|
(void)slot;
|
||||||
|
(void)slots;
|
||||||
|
(void)label;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
set(srcs)
|
||||||
|
set(priv_inc)
|
||||||
|
set(priv_req driver)
|
||||||
|
|
||||||
|
if(IDF_TARGET STREQUAL "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)
|
||||||
|
else()
|
||||||
|
list(APPEND srcs stub/hal_input.c)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${srcs}
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
PRIV_INCLUDE_DIRS ${priv_inc}
|
||||||
|
REQUIRES driver
|
||||||
|
PRIV_REQUIRES ${priv_req})
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
#include "hal_input.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include "board.h"
|
||||||
|
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/i2c.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
#define TP_I2C I2C_NUM_0
|
||||||
|
#define TP_ADDR 0x5D
|
||||||
|
#define GT_REG_STAT 0x814E
|
||||||
|
#define GT_REG_PT0 0x8150
|
||||||
|
|
||||||
|
static const char *TAG = "gt911";
|
||||||
|
|
||||||
|
static bool s_have;
|
||||||
|
static ui_raw_touch_t s_last;
|
||||||
|
static uint8_t s_prev_stat;
|
||||||
|
|
||||||
|
static esp_err_t tp_write(uint16_t reg, const uint8_t *data, size_t n)
|
||||||
|
{
|
||||||
|
uint8_t buf[8];
|
||||||
|
if (n + 2 > sizeof(buf)) {
|
||||||
|
return ESP_ERR_INVALID_SIZE;
|
||||||
|
}
|
||||||
|
buf[0] = (uint8_t)(reg >> 8);
|
||||||
|
buf[1] = (uint8_t)reg;
|
||||||
|
if (n && data) {
|
||||||
|
memcpy(buf + 2, data, n);
|
||||||
|
}
|
||||||
|
return i2c_master_write_to_device(TP_I2C, TP_ADDR, buf, n + 2, pdMS_TO_TICKS(50));
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t tp_read(uint16_t reg, uint8_t *data, size_t n)
|
||||||
|
{
|
||||||
|
uint8_t addr[2] = { (uint8_t)(reg >> 8), (uint8_t)reg };
|
||||||
|
return i2c_master_write_read_device(TP_I2C, TP_ADDR, addr, 2, data, n, pdMS_TO_TICKS(50));
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_input_init(void)
|
||||||
|
{
|
||||||
|
gpio_config_t io = {
|
||||||
|
.pin_bit_mask = (1ULL << board_pin_tp_rst()) | (1ULL << board_pin_tp_int()),
|
||||||
|
.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(board_pin_tp_int(), 0);
|
||||||
|
gpio_set_level(board_pin_tp_rst(), 0);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
gpio_set_level(board_pin_tp_rst(), 1);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(20));
|
||||||
|
io.mode = GPIO_MODE_INPUT;
|
||||||
|
io.pin_bit_mask = 1ULL << board_pin_tp_int();
|
||||||
|
gpio_config(&io);
|
||||||
|
|
||||||
|
i2c_config_t conf = {
|
||||||
|
.mode = I2C_MODE_MASTER,
|
||||||
|
.sda_io_num = board_pin_tp_sda(),
|
||||||
|
.scl_io_num = board_pin_tp_scl(),
|
||||||
|
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||||
|
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||||
|
.master.clk_speed = 400000,
|
||||||
|
};
|
||||||
|
i2c_param_config(TP_I2C, &conf);
|
||||||
|
i2c_driver_install(TP_I2C, conf.mode, 0, 0, 0);
|
||||||
|
ESP_LOGI(TAG, "GT911 addr=0x5D SDA=%d SCL=%d RST=%d INT=%d",
|
||||||
|
(int)board_pin_tp_sda(), (int)board_pin_tp_scl(),
|
||||||
|
(int)board_pin_tp_rst(), (int)board_pin_tp_int());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hal_input_pop_key(ui_raw_key_t *out)
|
||||||
|
{
|
||||||
|
(void)out;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hal_input_pop_touch(ui_raw_touch_t *out)
|
||||||
|
{
|
||||||
|
uint8_t stat = 0;
|
||||||
|
if (tp_read(GT_REG_STAT, &stat, 1) != ESP_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((stat & 0x80) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint8_t n = stat & 0x0F;
|
||||||
|
uint8_t raw[8];
|
||||||
|
if (n > 0 && tp_read(GT_REG_PT0, raw, 8) == ESP_OK) {
|
||||||
|
int16_t x = (int16_t)(raw[0] | ((uint16_t)raw[1] << 8));
|
||||||
|
int16_t y = (int16_t)(raw[2] | ((uint16_t)raw[3] << 8));
|
||||||
|
uint8_t zero = 0;
|
||||||
|
tp_write(GT_REG_STAT, &zero, 1);
|
||||||
|
/* Edge: report once per contact start. */
|
||||||
|
if (s_prev_stat == 0) {
|
||||||
|
s_last.x = x;
|
||||||
|
s_last.y = y;
|
||||||
|
s_prev_stat = n;
|
||||||
|
if (out) {
|
||||||
|
*out = s_last;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
s_prev_stat = n;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint8_t zero = 0;
|
||||||
|
tp_write(GT_REG_STAT, &zero, 1);
|
||||||
|
s_prev_stat = 0;
|
||||||
|
(void)s_have;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char key;
|
||||||
|
} ui_raw_key_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
} ui_raw_touch_t;
|
||||||
|
|
||||||
|
void hal_input_init(void);
|
||||||
|
bool hal_input_pop_key(ui_raw_key_t *out); /* nonblocking */
|
||||||
|
bool hal_input_pop_touch(ui_raw_touch_t *out); /* nonblocking; S3 only */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#include "hal_input.h"
|
||||||
|
|
||||||
|
void hal_input_init(void) {}
|
||||||
|
|
||||||
|
bool hal_input_pop_key(ui_raw_key_t *out)
|
||||||
|
{
|
||||||
|
(void)out;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hal_input_pop_touch(ui_raw_touch_t *out)
|
||||||
|
{
|
||||||
|
(void)out;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
set(srcs)
|
||||||
|
set(priv_inc)
|
||||||
|
set(priv_req driver)
|
||||||
|
|
||||||
|
if(IDF_TARGET STREQUAL "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()
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${srcs}
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
PRIV_INCLUDE_DIRS ${priv_inc}
|
||||||
|
REQUIRES driver
|
||||||
|
PRIV_REQUIRES ${priv_req})
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#ifdef AUTOFILM_HOST
|
||||||
|
#include "esp_host_compat.h"
|
||||||
|
#else
|
||||||
|
#include "esp_err.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void hal_motor_init(void);
|
||||||
|
void hal_motor_enable(bool on); /* on=false → EN disabled (HIGH on wroom) */
|
||||||
|
void hal_motor_request_stop(void); /* must be safe to call anytime; sets flag + enable false */
|
||||||
|
esp_err_t hal_motor_agitate_start(float cw_revs, float ccw_revs, uint32_t rpm);
|
||||||
|
void hal_motor_agitate_stop(void); /* leave disabled */
|
||||||
|
bool hal_motor_is_enabled(void);
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#include "hal_motor.h"
|
||||||
|
|
||||||
|
void hal_motor_init(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_motor_enable(bool on)
|
||||||
|
{
|
||||||
|
(void)on;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_motor_request_stop(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t hal_motor_agitate_start(float cw_revs, float ccw_revs, uint32_t rpm)
|
||||||
|
{
|
||||||
|
(void)cw_revs;
|
||||||
|
(void)ccw_revs;
|
||||||
|
(void)rpm;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_motor_agitate_stop(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hal_motor_is_enabled(void)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
set(srcs)
|
||||||
|
set(priv_inc)
|
||||||
|
set(priv_req driver esp_timer)
|
||||||
|
|
||||||
|
if(IDF_TARGET STREQUAL "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()
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${srcs}
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
PRIV_INCLUDE_DIRS ${priv_inc}
|
||||||
|
REQUIRES driver
|
||||||
|
PRIV_REQUIRES ${priv_req})
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
dependencies:
|
||||||
|
espressif/onewire_bus: "^1.1.0"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef AUTOFILM_HOST
|
||||||
|
#include "esp_host_compat.h"
|
||||||
|
#else
|
||||||
|
#include "esp_err.h"
|
||||||
|
#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 */
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#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;
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
@@ -0,0 +1,679 @@
|
|||||||
|
// AccelStepper.cpp
|
||||||
|
//
|
||||||
|
// Copyright (C) 2009-2020 Mike McCauley
|
||||||
|
// $Id: AccelStepper.cpp,v 1.24 2020/04/20 00:15:03 mikem Exp mikem $
|
||||||
|
|
||||||
|
#include "AccelStepper.h"
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// Some debugging assistance
|
||||||
|
void dump(uint8_t* p, int l)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < l; i++)
|
||||||
|
{
|
||||||
|
Serial.print(p[i], HEX);
|
||||||
|
Serial.print(" ");
|
||||||
|
}
|
||||||
|
Serial.println("");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void AccelStepper::moveTo(long absolute)
|
||||||
|
{
|
||||||
|
if (_targetPos != absolute)
|
||||||
|
{
|
||||||
|
_targetPos = absolute;
|
||||||
|
computeNewSpeed();
|
||||||
|
// compute new n?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccelStepper::move(long relative)
|
||||||
|
{
|
||||||
|
moveTo(_currentPos + relative);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements steps according to the current step interval
|
||||||
|
// You must call this at least once per step
|
||||||
|
// returns true if a step occurred
|
||||||
|
boolean AccelStepper::runSpeed()
|
||||||
|
{
|
||||||
|
// Dont do anything unless we actually have a step interval
|
||||||
|
if (!_stepInterval)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
unsigned long time = micros();
|
||||||
|
if (time - _lastStepTime >= _stepInterval)
|
||||||
|
{
|
||||||
|
if (_direction == DIRECTION_CW)
|
||||||
|
{
|
||||||
|
// Clockwise
|
||||||
|
_currentPos += 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Anticlockwise
|
||||||
|
_currentPos -= 1;
|
||||||
|
}
|
||||||
|
step(_currentPos);
|
||||||
|
|
||||||
|
_lastStepTime = time; // Caution: does not account for costs in step()
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long AccelStepper::distanceToGo()
|
||||||
|
{
|
||||||
|
return _targetPos - _currentPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
long AccelStepper::targetPosition()
|
||||||
|
{
|
||||||
|
return _targetPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
long AccelStepper::currentPosition()
|
||||||
|
{
|
||||||
|
return _currentPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Useful during initialisations or after initial positioning
|
||||||
|
// Sets speed to 0
|
||||||
|
void AccelStepper::setCurrentPosition(long position)
|
||||||
|
{
|
||||||
|
_targetPos = _currentPos = position;
|
||||||
|
_n = 0;
|
||||||
|
_stepInterval = 0;
|
||||||
|
_speed = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subclasses can override
|
||||||
|
unsigned long AccelStepper::computeNewSpeed()
|
||||||
|
{
|
||||||
|
long distanceTo = distanceToGo(); // +ve is clockwise from curent location
|
||||||
|
|
||||||
|
long stepsToStop = (long)((_speed * _speed) / (2.0 * _acceleration)); // Equation 16
|
||||||
|
|
||||||
|
if (distanceTo == 0 && stepsToStop <= 1)
|
||||||
|
{
|
||||||
|
// We are at the target and its time to stop
|
||||||
|
_stepInterval = 0;
|
||||||
|
_speed = 0.0;
|
||||||
|
_n = 0;
|
||||||
|
return _stepInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distanceTo > 0)
|
||||||
|
{
|
||||||
|
// We are anticlockwise from the target
|
||||||
|
// Need to go clockwise from here, maybe decelerate now
|
||||||
|
if (_n > 0)
|
||||||
|
{
|
||||||
|
// Currently accelerating, need to decel now? Or maybe going the wrong way?
|
||||||
|
if ((stepsToStop >= distanceTo) || _direction == DIRECTION_CCW)
|
||||||
|
_n = -stepsToStop; // Start deceleration
|
||||||
|
}
|
||||||
|
else if (_n < 0)
|
||||||
|
{
|
||||||
|
// Currently decelerating, need to accel again?
|
||||||
|
if ((stepsToStop < distanceTo) && _direction == DIRECTION_CW)
|
||||||
|
_n = -_n; // Start accceleration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (distanceTo < 0)
|
||||||
|
{
|
||||||
|
// We are clockwise from the target
|
||||||
|
// Need to go anticlockwise from here, maybe decelerate
|
||||||
|
if (_n > 0)
|
||||||
|
{
|
||||||
|
// Currently accelerating, need to decel now? Or maybe going the wrong way?
|
||||||
|
if ((stepsToStop >= -distanceTo) || _direction == DIRECTION_CW)
|
||||||
|
_n = -stepsToStop; // Start deceleration
|
||||||
|
}
|
||||||
|
else if (_n < 0)
|
||||||
|
{
|
||||||
|
// Currently decelerating, need to accel again?
|
||||||
|
if ((stepsToStop < -distanceTo) && _direction == DIRECTION_CCW)
|
||||||
|
_n = -_n; // Start accceleration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to accelerate or decelerate
|
||||||
|
if (_n == 0)
|
||||||
|
{
|
||||||
|
// First step from stopped
|
||||||
|
_cn = _c0;
|
||||||
|
_direction = (distanceTo > 0) ? DIRECTION_CW : DIRECTION_CCW;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Subsequent step. Works for accel (n is +_ve) and decel (n is -ve).
|
||||||
|
_cn = _cn - ((2.0 * _cn) / ((4.0 * _n) + 1)); // Equation 13
|
||||||
|
_cn = max(_cn, _cmin);
|
||||||
|
}
|
||||||
|
_n++;
|
||||||
|
_stepInterval = _cn;
|
||||||
|
_speed = 1000000.0 / _cn;
|
||||||
|
if (_direction == DIRECTION_CCW)
|
||||||
|
_speed = -_speed;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
Serial.println(_speed);
|
||||||
|
Serial.println(_acceleration);
|
||||||
|
Serial.println(_cn);
|
||||||
|
Serial.println(_c0);
|
||||||
|
Serial.println(_n);
|
||||||
|
Serial.println(_stepInterval);
|
||||||
|
Serial.println(distanceTo);
|
||||||
|
Serial.println(stepsToStop);
|
||||||
|
Serial.println("-----");
|
||||||
|
#endif
|
||||||
|
return _stepInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the motor to implement speed and acceleration in order to proceed to the target position
|
||||||
|
// You must call this at least once per step, preferably in your main loop
|
||||||
|
// If the motor is in the desired position, the cost is very small
|
||||||
|
// returns true if the motor is still running to the target position.
|
||||||
|
boolean AccelStepper::run()
|
||||||
|
{
|
||||||
|
if (runSpeed())
|
||||||
|
computeNewSpeed();
|
||||||
|
return _speed != 0.0 || distanceToGo() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
AccelStepper::AccelStepper(uint8_t interface, uint8_t pin1, uint8_t pin2, uint8_t pin3, uint8_t pin4, bool enable)
|
||||||
|
{
|
||||||
|
_interface = interface;
|
||||||
|
_currentPos = 0;
|
||||||
|
_targetPos = 0;
|
||||||
|
_speed = 0.0;
|
||||||
|
_maxSpeed = 0.0;
|
||||||
|
_acceleration = 0.0;
|
||||||
|
_sqrt_twoa = 1.0;
|
||||||
|
_stepInterval = 0;
|
||||||
|
_minPulseWidth = 1;
|
||||||
|
_enablePin = 0xff;
|
||||||
|
_lastStepTime = 0;
|
||||||
|
_pin[0] = pin1;
|
||||||
|
_pin[1] = pin2;
|
||||||
|
_pin[2] = pin3;
|
||||||
|
_pin[3] = pin4;
|
||||||
|
_enableInverted = false;
|
||||||
|
|
||||||
|
// NEW
|
||||||
|
_n = 0;
|
||||||
|
_c0 = 0.0;
|
||||||
|
_cn = 0.0;
|
||||||
|
_cmin = 1.0;
|
||||||
|
_direction = DIRECTION_CCW;
|
||||||
|
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 4; i++)
|
||||||
|
_pinInverted[i] = 0;
|
||||||
|
if (enable)
|
||||||
|
enableOutputs();
|
||||||
|
// Some reasonable default
|
||||||
|
setAcceleration(1);
|
||||||
|
setMaxSpeed(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
AccelStepper::AccelStepper(void (*forward)(), void (*backward)())
|
||||||
|
{
|
||||||
|
_interface = 0;
|
||||||
|
_currentPos = 0;
|
||||||
|
_targetPos = 0;
|
||||||
|
_speed = 0.0;
|
||||||
|
_maxSpeed = 0.0;
|
||||||
|
_acceleration = 0.0;
|
||||||
|
_sqrt_twoa = 1.0;
|
||||||
|
_stepInterval = 0;
|
||||||
|
_minPulseWidth = 1;
|
||||||
|
_enablePin = 0xff;
|
||||||
|
_lastStepTime = 0;
|
||||||
|
_pin[0] = 0;
|
||||||
|
_pin[1] = 0;
|
||||||
|
_pin[2] = 0;
|
||||||
|
_pin[3] = 0;
|
||||||
|
_forward = forward;
|
||||||
|
_backward = backward;
|
||||||
|
|
||||||
|
// NEW
|
||||||
|
_n = 0;
|
||||||
|
_c0 = 0.0;
|
||||||
|
_cn = 0.0;
|
||||||
|
_cmin = 1.0;
|
||||||
|
_direction = DIRECTION_CCW;
|
||||||
|
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 4; i++)
|
||||||
|
_pinInverted[i] = 0;
|
||||||
|
// Some reasonable default
|
||||||
|
setAcceleration(1);
|
||||||
|
setMaxSpeed(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccelStepper::setMaxSpeed(float speed)
|
||||||
|
{
|
||||||
|
if (speed < 0.0)
|
||||||
|
speed = -speed;
|
||||||
|
if (_maxSpeed != speed)
|
||||||
|
{
|
||||||
|
_maxSpeed = speed;
|
||||||
|
_cmin = 1000000.0 / speed;
|
||||||
|
// Recompute _n from current speed and adjust speed if accelerating or cruising
|
||||||
|
if (_n > 0)
|
||||||
|
{
|
||||||
|
_n = (long)((_speed * _speed) / (2.0 * _acceleration)); // Equation 16
|
||||||
|
computeNewSpeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float AccelStepper::maxSpeed()
|
||||||
|
{
|
||||||
|
return _maxSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccelStepper::setAcceleration(float acceleration)
|
||||||
|
{
|
||||||
|
if (acceleration == 0.0)
|
||||||
|
return;
|
||||||
|
if (acceleration < 0.0)
|
||||||
|
acceleration = -acceleration;
|
||||||
|
if (_acceleration != acceleration)
|
||||||
|
{
|
||||||
|
// Recompute _n per Equation 17
|
||||||
|
_n = _n * (_acceleration / acceleration);
|
||||||
|
// New c0 per Equation 7, with correction per Equation 15
|
||||||
|
_c0 = 0.676 * sqrt(2.0 / acceleration) * 1000000.0; // Equation 15
|
||||||
|
_acceleration = acceleration;
|
||||||
|
computeNewSpeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float AccelStepper::acceleration()
|
||||||
|
{
|
||||||
|
return _acceleration;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccelStepper::setSpeed(float speed)
|
||||||
|
{
|
||||||
|
if (speed == _speed)
|
||||||
|
return;
|
||||||
|
speed = constrain(speed, -_maxSpeed, _maxSpeed);
|
||||||
|
if (speed == 0.0)
|
||||||
|
_stepInterval = 0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_stepInterval = fabs(1000000.0 / speed);
|
||||||
|
_direction = (speed > 0.0) ? DIRECTION_CW : DIRECTION_CCW;
|
||||||
|
}
|
||||||
|
_speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
float AccelStepper::speed()
|
||||||
|
{
|
||||||
|
return _speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subclasses can override
|
||||||
|
void AccelStepper::step(long step)
|
||||||
|
{
|
||||||
|
switch (_interface)
|
||||||
|
{
|
||||||
|
case FUNCTION:
|
||||||
|
step0(step);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DRIVER:
|
||||||
|
step1(step);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FULL2WIRE:
|
||||||
|
step2(step);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FULL3WIRE:
|
||||||
|
step3(step);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FULL4WIRE:
|
||||||
|
step4(step);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HALF3WIRE:
|
||||||
|
step6(step);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HALF4WIRE:
|
||||||
|
step8(step);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long AccelStepper::stepForward()
|
||||||
|
{
|
||||||
|
// Clockwise
|
||||||
|
_currentPos += 1;
|
||||||
|
step(_currentPos);
|
||||||
|
_lastStepTime = micros();
|
||||||
|
return _currentPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
long AccelStepper::stepBackward()
|
||||||
|
{
|
||||||
|
// Counter-clockwise
|
||||||
|
_currentPos -= 1;
|
||||||
|
step(_currentPos);
|
||||||
|
_lastStepTime = micros();
|
||||||
|
return _currentPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// You might want to override this to implement eg serial output
|
||||||
|
// bit 0 of the mask corresponds to _pin[0]
|
||||||
|
// bit 1 of the mask corresponds to _pin[1]
|
||||||
|
// ....
|
||||||
|
void AccelStepper::setOutputPins(uint8_t mask)
|
||||||
|
{
|
||||||
|
uint8_t numpins = 2;
|
||||||
|
if (_interface == FULL4WIRE || _interface == HALF4WIRE)
|
||||||
|
numpins = 4;
|
||||||
|
else if (_interface == FULL3WIRE || _interface == HALF3WIRE)
|
||||||
|
numpins = 3;
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < numpins; i++)
|
||||||
|
digitalWrite(_pin[i], (mask & (1 << i)) ? (HIGH ^ _pinInverted[i]) : (LOW ^ _pinInverted[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 pin step function (ie for functional usage)
|
||||||
|
void AccelStepper::step0(long step)
|
||||||
|
{
|
||||||
|
(void)(step); // Unused
|
||||||
|
if (_speed > 0)
|
||||||
|
_forward();
|
||||||
|
else
|
||||||
|
_backward();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 pin step function (ie for stepper drivers)
|
||||||
|
// This is passed the current step number (0 to 7)
|
||||||
|
// Subclasses can override
|
||||||
|
void AccelStepper::step1(long step)
|
||||||
|
{
|
||||||
|
(void)(step); // Unused
|
||||||
|
|
||||||
|
// _pin[0] is step, _pin[1] is direction
|
||||||
|
setOutputPins(_direction ? 0b10 : 0b00); // Set direction first else get rogue pulses
|
||||||
|
setOutputPins(_direction ? 0b11 : 0b01); // step HIGH
|
||||||
|
// Caution 200ns setup time
|
||||||
|
// Delay the minimum allowed pulse width
|
||||||
|
delayMicroseconds(_minPulseWidth);
|
||||||
|
setOutputPins(_direction ? 0b10 : 0b00); // step LOW
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 2 pin step function
|
||||||
|
// This is passed the current step number (0 to 7)
|
||||||
|
// Subclasses can override
|
||||||
|
void AccelStepper::step2(long step)
|
||||||
|
{
|
||||||
|
switch (step & 0x3)
|
||||||
|
{
|
||||||
|
case 0: /* 01 */
|
||||||
|
setOutputPins(0b10);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: /* 11 */
|
||||||
|
setOutputPins(0b11);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: /* 10 */
|
||||||
|
setOutputPins(0b01);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: /* 00 */
|
||||||
|
setOutputPins(0b00);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3 pin step function
|
||||||
|
// This is passed the current step number (0 to 7)
|
||||||
|
// Subclasses can override
|
||||||
|
void AccelStepper::step3(long step)
|
||||||
|
{
|
||||||
|
switch (step % 3)
|
||||||
|
{
|
||||||
|
case 0: // 100
|
||||||
|
setOutputPins(0b100);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // 001
|
||||||
|
setOutputPins(0b001);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: //010
|
||||||
|
setOutputPins(0b010);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4 pin step function for half stepper
|
||||||
|
// This is passed the current step number (0 to 7)
|
||||||
|
// Subclasses can override
|
||||||
|
void AccelStepper::step4(long step)
|
||||||
|
{
|
||||||
|
switch (step & 0x3)
|
||||||
|
{
|
||||||
|
case 0: // 1010
|
||||||
|
setOutputPins(0b0101);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // 0110
|
||||||
|
setOutputPins(0b0110);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: //0101
|
||||||
|
setOutputPins(0b1010);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: //1001
|
||||||
|
setOutputPins(0b1001);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3 pin half step function
|
||||||
|
// This is passed the current step number (0 to 7)
|
||||||
|
// Subclasses can override
|
||||||
|
void AccelStepper::step6(long step)
|
||||||
|
{
|
||||||
|
switch (step % 6)
|
||||||
|
{
|
||||||
|
case 0: // 100
|
||||||
|
setOutputPins(0b100);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // 101
|
||||||
|
setOutputPins(0b101);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // 001
|
||||||
|
setOutputPins(0b001);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: // 011
|
||||||
|
setOutputPins(0b011);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4: // 010
|
||||||
|
setOutputPins(0b010);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5: // 011
|
||||||
|
setOutputPins(0b110);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4 pin half step function
|
||||||
|
// This is passed the current step number (0 to 7)
|
||||||
|
// Subclasses can override
|
||||||
|
void AccelStepper::step8(long step)
|
||||||
|
{
|
||||||
|
switch (step & 0x7)
|
||||||
|
{
|
||||||
|
case 0: // 1000
|
||||||
|
setOutputPins(0b0001);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // 1010
|
||||||
|
setOutputPins(0b0101);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // 0010
|
||||||
|
setOutputPins(0b0100);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: // 0110
|
||||||
|
setOutputPins(0b0110);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4: // 0100
|
||||||
|
setOutputPins(0b0010);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5: //0101
|
||||||
|
setOutputPins(0b1010);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6: // 0001
|
||||||
|
setOutputPins(0b1000);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7: //1001
|
||||||
|
setOutputPins(0b1001);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevents power consumption on the outputs
|
||||||
|
void AccelStepper::disableOutputs()
|
||||||
|
{
|
||||||
|
if (! _interface) return;
|
||||||
|
|
||||||
|
setOutputPins(0); // Handles inversion automatically
|
||||||
|
if (_enablePin != 0xff)
|
||||||
|
{
|
||||||
|
pinMode(_enablePin, OUTPUT);
|
||||||
|
digitalWrite(_enablePin, LOW ^ _enableInverted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccelStepper::enableOutputs()
|
||||||
|
{
|
||||||
|
if (! _interface)
|
||||||
|
return;
|
||||||
|
|
||||||
|
pinMode(_pin[0], OUTPUT);
|
||||||
|
pinMode(_pin[1], OUTPUT);
|
||||||
|
if (_interface == FULL4WIRE || _interface == HALF4WIRE)
|
||||||
|
{
|
||||||
|
pinMode(_pin[2], OUTPUT);
|
||||||
|
pinMode(_pin[3], OUTPUT);
|
||||||
|
}
|
||||||
|
else if (_interface == FULL3WIRE || _interface == HALF3WIRE)
|
||||||
|
{
|
||||||
|
pinMode(_pin[2], OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_enablePin != 0xff)
|
||||||
|
{
|
||||||
|
pinMode(_enablePin, OUTPUT);
|
||||||
|
digitalWrite(_enablePin, HIGH ^ _enableInverted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccelStepper::setMinPulseWidth(unsigned int minWidth)
|
||||||
|
{
|
||||||
|
_minPulseWidth = minWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccelStepper::setEnablePin(uint8_t enablePin)
|
||||||
|
{
|
||||||
|
_enablePin = enablePin;
|
||||||
|
|
||||||
|
// This happens after construction, so init pin now.
|
||||||
|
if (_enablePin != 0xff)
|
||||||
|
{
|
||||||
|
pinMode(_enablePin, OUTPUT);
|
||||||
|
digitalWrite(_enablePin, HIGH ^ _enableInverted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccelStepper::setPinsInverted(bool directionInvert, bool stepInvert, bool enableInvert)
|
||||||
|
{
|
||||||
|
_pinInverted[0] = stepInvert;
|
||||||
|
_pinInverted[1] = directionInvert;
|
||||||
|
_enableInverted = enableInvert;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccelStepper::setPinsInverted(bool pin1Invert, bool pin2Invert, bool pin3Invert, bool pin4Invert, bool enableInvert)
|
||||||
|
{
|
||||||
|
_pinInverted[0] = pin1Invert;
|
||||||
|
_pinInverted[1] = pin2Invert;
|
||||||
|
_pinInverted[2] = pin3Invert;
|
||||||
|
_pinInverted[3] = pin4Invert;
|
||||||
|
_enableInverted = enableInvert;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocks until the target position is reached and stopped
|
||||||
|
void AccelStepper::runToPosition()
|
||||||
|
{
|
||||||
|
while (run())
|
||||||
|
YIELD; // Let system housekeeping occur
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean AccelStepper::runSpeedToPosition()
|
||||||
|
{
|
||||||
|
if (_targetPos == _currentPos)
|
||||||
|
return false;
|
||||||
|
if (_targetPos >_currentPos)
|
||||||
|
_direction = DIRECTION_CW;
|
||||||
|
else
|
||||||
|
_direction = DIRECTION_CCW;
|
||||||
|
return runSpeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocks until the new target position is reached
|
||||||
|
void AccelStepper::runToNewPosition(long position)
|
||||||
|
{
|
||||||
|
moveTo(position);
|
||||||
|
runToPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccelStepper::stop()
|
||||||
|
{
|
||||||
|
if (_speed != 0.0)
|
||||||
|
{
|
||||||
|
long stepsToStop = (long)((_speed * _speed) / (2.0 * _acceleration)) + 1; // Equation 16 (+integer rounding)
|
||||||
|
if (_speed > 0)
|
||||||
|
move(stepsToStop);
|
||||||
|
else
|
||||||
|
move(-stepsToStop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AccelStepper::isRunning()
|
||||||
|
{
|
||||||
|
return !(_speed == 0.0 && _targetPos == _currentPos);
|
||||||
|
}
|
||||||
@@ -0,0 +1,783 @@
|
|||||||
|
// AccelStepper.h
|
||||||
|
//
|
||||||
|
/// \mainpage AccelStepper library for Arduino
|
||||||
|
///
|
||||||
|
/// This is the Arduino AccelStepper library.
|
||||||
|
/// It provides an object-oriented interface for 2, 3 or 4 pin stepper motors and motor drivers.
|
||||||
|
///
|
||||||
|
/// The standard Arduino IDE includes the Stepper library
|
||||||
|
/// (http://arduino.cc/en/Reference/Stepper) for stepper motors. It is
|
||||||
|
/// perfectly adequate for simple, single motor applications.
|
||||||
|
///
|
||||||
|
/// AccelStepper significantly improves on the standard Arduino Stepper library in several ways:
|
||||||
|
/// \li Supports acceleration and deceleration
|
||||||
|
/// \li Supports multiple simultaneous steppers, with independent concurrent stepping on each stepper
|
||||||
|
/// \li Most API functions never delay() or block (unless otherwise stated)
|
||||||
|
/// \li Supports 2, 3 and 4 wire steppers, plus 3 and 4 wire half steppers.
|
||||||
|
/// \li Supports alternate stepping functions to enable support of AFMotor (https://github.com/adafruit/Adafruit-Motor-Shield-library)
|
||||||
|
/// \li Supports stepper drivers such as the Sparkfun EasyDriver (based on 3967 driver chip)
|
||||||
|
/// \li Very slow speeds are supported
|
||||||
|
/// \li Extensive API
|
||||||
|
/// \li Subclass support
|
||||||
|
///
|
||||||
|
/// The latest version of this documentation can be downloaded from
|
||||||
|
/// http://www.airspayce.com/mikem/arduino/AccelStepper
|
||||||
|
/// The version of the package that this documentation refers to can be downloaded
|
||||||
|
/// from http://www.airspayce.com/mikem/arduino/AccelStepper/AccelStepper-1.64.zip
|
||||||
|
///
|
||||||
|
/// Example Arduino programs are included to show the main modes of use.
|
||||||
|
///
|
||||||
|
/// You can also find online help and discussion at http://groups.google.com/group/accelstepper
|
||||||
|
/// Please use that group for all questions and discussions on this topic.
|
||||||
|
/// Do not contact the author directly, unless it is to discuss commercial licensing.
|
||||||
|
/// Before asking a question or reporting a bug, please read
|
||||||
|
/// - http://en.wikipedia.org/wiki/Wikipedia:Reference_desk/How_to_ask_a_software_question
|
||||||
|
/// - http://www.catb.org/esr/faqs/smart-questions.html
|
||||||
|
/// - http://www.chiark.greenend.org.uk/~shgtatham/bugs.html
|
||||||
|
///
|
||||||
|
/// Beginners to C++ and stepper motors in general may find this helpful:
|
||||||
|
/// - https://hackaday.io/project/183279-accelstepper-the-missing-manual
|
||||||
|
/// - https://hackaday.io/project/183713-using-the-arduino-accelstepper-library
|
||||||
|
///
|
||||||
|
/// Tested on Arduino Diecimila and Mega with arduino-0018 & arduino-0021
|
||||||
|
/// on OpenSuSE 11.1 and avr-libc-1.6.1-1.15,
|
||||||
|
/// cross-avr-binutils-2.19-9.1, cross-avr-gcc-4.1.3_20080612-26.5.
|
||||||
|
/// Tested on Teensy http://www.pjrc.com/teensy including Teensy 3.1 built using Arduino IDE 1.0.5 with
|
||||||
|
/// teensyduino addon 1.18 and later.
|
||||||
|
///
|
||||||
|
/// \par Installation
|
||||||
|
///
|
||||||
|
/// Install in the usual way: unzip the distribution zip file to the libraries
|
||||||
|
/// sub-folder of your sketchbook.
|
||||||
|
///
|
||||||
|
/// \par Theory
|
||||||
|
///
|
||||||
|
/// This code uses speed calculations as described in
|
||||||
|
/// "Generate stepper-motor speed profiles in real time" by David Austin
|
||||||
|
/// http://fab.cba.mit.edu/classes/MIT/961.09/projects/i0/Stepper_Motor_Speed_Profile.pdf or
|
||||||
|
/// http://www.embedded.com/design/mcus-processors-and-socs/4006438/Generate-stepper-motor-speed-profiles-in-real-time or
|
||||||
|
/// http://web.archive.org/web/20140705143928/http://fab.cba.mit.edu/classes/MIT/961.09/projects/i0/Stepper_Motor_Speed_Profile.pdf
|
||||||
|
/// with the exception that AccelStepper uses steps per second rather than radians per second
|
||||||
|
/// (because we dont know the step angle of the motor)
|
||||||
|
/// An initial step interval is calculated for the first step, based on the desired acceleration
|
||||||
|
/// On subsequent steps, shorter step intervals are calculated based
|
||||||
|
/// on the previous step until max speed is achieved.
|
||||||
|
///
|
||||||
|
/// \par Adafruit Motor Shield V2
|
||||||
|
///
|
||||||
|
/// The included examples AFMotor_* are for Adafruit Motor Shield V1 and do not work with Adafruit Motor Shield V2.
|
||||||
|
/// See https://github.com/adafruit/Adafruit_Motor_Shield_V2_Library for examples that work with Adafruit Motor Shield V2.
|
||||||
|
///
|
||||||
|
/// \par Donations
|
||||||
|
///
|
||||||
|
/// This library is offered under a free GPL license for those who want to use it that way.
|
||||||
|
/// We try hard to keep it up to date, fix bugs
|
||||||
|
/// and to provide free support. If this library has helped you save time or money, please consider donating at
|
||||||
|
/// http://www.airspayce.com or here:
|
||||||
|
///
|
||||||
|
/// \htmlonly <form action="https://www.paypal.com/cgi-bin/webscr" method="post"><input type="hidden" name="cmd" value="_donations" /> <input type="hidden" name="business" value="mikem@airspayce.com" /> <input type="hidden" name="lc" value="AU" /> <input type="hidden" name="item_name" value="Airspayce" /> <input type="hidden" name="item_number" value="AccelStepper" /> <input type="hidden" name="currency_code" value="USD" /> <input type="hidden" name="bn" value="PP-DonationsBF:btn_donateCC_LG.gif:NonHosted" /> <input type="image" alt="PayPal — The safer, easier way to pay online." name="submit" src="https://www.paypalobjects.com/en_AU/i/btn/btn_donateCC_LG.gif" /> <img alt="" src="https://www.paypalobjects.com/en_AU/i/scr/pixel.gif" width="1" height="1" border="0" /></form> \endhtmlonly
|
||||||
|
///
|
||||||
|
/// \par Trademarks
|
||||||
|
///
|
||||||
|
/// AccelStepper is a trademark of AirSpayce Pty Ltd. The AccelStepper mark was first used on April 26 2010 for
|
||||||
|
/// international trade, and is used only in relation to motor control hardware and software.
|
||||||
|
/// It is not to be confused with any other similar marks covering other goods and services.
|
||||||
|
///
|
||||||
|
/// \par Copyright
|
||||||
|
///
|
||||||
|
/// This software is Copyright (C) 2010-2021 Mike McCauley. Use is subject to license
|
||||||
|
/// conditions. The main licensing options available are GPL V3 or Commercial:
|
||||||
|
///
|
||||||
|
/// \par Open Source Licensing GPL V3
|
||||||
|
/// This is the appropriate option if you want to share the source code of your
|
||||||
|
/// application with everyone you distribute it to, and you also want to give them
|
||||||
|
/// the right to share who uses it. If you wish to use this software under Open
|
||||||
|
/// Source Licensing, you must contribute all your source code to the open source
|
||||||
|
/// community in accordance with the GPL Version 23 when your application is
|
||||||
|
/// distributed. See https://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
///
|
||||||
|
/// \par Commercial Licensing
|
||||||
|
/// This is the appropriate option if you are creating proprietary applications
|
||||||
|
/// and you are not prepared to distribute and share the source code of your
|
||||||
|
/// application. To purchase a commercial license, contact info@airspayce.com
|
||||||
|
///
|
||||||
|
/// \par Revision History
|
||||||
|
/// \version 1.0 Initial release
|
||||||
|
///
|
||||||
|
/// \version 1.1 Added speed() function to get the current speed.
|
||||||
|
/// \version 1.2 Added runSpeedToPosition() submitted by Gunnar Arndt.
|
||||||
|
/// \version 1.3 Added support for stepper drivers (ie with Step and Direction inputs) with _pins == 1
|
||||||
|
/// \version 1.4 Added functional contructor to support AFMotor, contributed by Limor, with example sketches.
|
||||||
|
/// \version 1.5 Improvements contributed by Peter Mousley: Use of microsecond steps and other speed improvements
|
||||||
|
/// to increase max stepping speed to about 4kHz. New option for user to set the min allowed pulse width.
|
||||||
|
/// Added checks for already running at max speed and skip further calcs if so.
|
||||||
|
/// \version 1.6 Fixed a problem with wrapping of microsecond stepping that could cause stepping to hang.
|
||||||
|
/// Reported by Sandy Noble.
|
||||||
|
/// Removed redundant _lastRunTime member.
|
||||||
|
/// \version 1.7 Fixed a bug where setCurrentPosition() did not always work as expected.
|
||||||
|
/// Reported by Peter Linhart.
|
||||||
|
/// \version 1.8 Added support for 4 pin half-steppers, requested by Harvey Moon
|
||||||
|
/// \version 1.9 setCurrentPosition() now also sets motor speed to 0.
|
||||||
|
/// \version 1.10 Builds on Arduino 1.0
|
||||||
|
/// \version 1.11 Improvments from Michael Ellison:
|
||||||
|
/// Added optional enable line support for stepper drivers
|
||||||
|
/// Added inversion for step/direction/enable lines for stepper drivers
|
||||||
|
/// \version 1.12 Announce Google Group
|
||||||
|
/// \version 1.13 Improvements to speed calculation. Cost of calculation is now less in the worst case,
|
||||||
|
/// and more or less constant in all cases. This should result in slightly beter high speed performance, and
|
||||||
|
/// reduce anomalous speed glitches when other steppers are accelerating.
|
||||||
|
/// However, its hard to see how to replace the sqrt() required at the very first step from 0 speed.
|
||||||
|
/// \version 1.14 Fixed a problem with compiling under arduino 0021 reported by EmbeddedMan
|
||||||
|
/// \version 1.15 Fixed a problem with runSpeedToPosition which did not correctly handle
|
||||||
|
/// running backwards to a smaller target position. Added examples
|
||||||
|
/// \version 1.16 Fixed some cases in the code where abs() was used instead of fabs().
|
||||||
|
/// \version 1.17 Added example ProportionalControl
|
||||||
|
/// \version 1.18 Fixed a problem: If one calls the funcion runSpeed() when Speed is zero, it makes steps
|
||||||
|
/// without counting. reported by Friedrich, Klappenbach.
|
||||||
|
/// \version 1.19 Added MotorInterfaceType and symbolic names for the number of pins to use
|
||||||
|
/// for the motor interface. Updated examples to suit.
|
||||||
|
/// Replaced individual pin assignment variables _pin1, _pin2 etc with array _pin[4].
|
||||||
|
/// _pins member changed to _interface.
|
||||||
|
/// Added _pinInverted array to simplify pin inversion operations.
|
||||||
|
/// Added new function setOutputPins() which sets the motor output pins.
|
||||||
|
/// It can be overridden in order to provide, say, serial output instead of parallel output
|
||||||
|
/// Some refactoring and code size reduction.
|
||||||
|
/// \version 1.20 Improved documentation and examples to show need for correctly
|
||||||
|
/// specifying AccelStepper::FULL4WIRE and friends.
|
||||||
|
/// \version 1.21 Fixed a problem where desiredSpeed could compute the wrong step acceleration
|
||||||
|
/// when _speed was small but non-zero. Reported by Brian Schmalz.
|
||||||
|
/// Precompute sqrt_twoa to improve performance and max possible stepping speed
|
||||||
|
/// \version 1.22 Added Bounce.pde example
|
||||||
|
/// Fixed a problem where calling moveTo(), setMaxSpeed(), setAcceleration() more
|
||||||
|
/// frequently than the step time, even
|
||||||
|
/// with the same values, would interfere with speed calcs. Now a new speed is computed
|
||||||
|
/// only if there was a change in the set value. Reported by Brian Schmalz.
|
||||||
|
/// \version 1.23 Rewrite of the speed algorithms in line with
|
||||||
|
/// http://fab.cba.mit.edu/classes/MIT/961.09/projects/i0/Stepper_Motor_Speed_Profile.pdf
|
||||||
|
/// Now expect smoother and more linear accelerations and decelerations. The desiredSpeed()
|
||||||
|
/// function was removed.
|
||||||
|
/// \version 1.24 Fixed a problem introduced in 1.23: with runToPosition, which did never returned
|
||||||
|
/// \version 1.25 Now ignore attempts to set acceleration to 0.0
|
||||||
|
/// \version 1.26 Fixed a problem where certina combinations of speed and accelration could cause
|
||||||
|
/// oscillation about the target position.
|
||||||
|
/// \version 1.27 Added stop() function to stop as fast as possible with current acceleration parameters.
|
||||||
|
/// Also added new Quickstop example showing its use.
|
||||||
|
/// \version 1.28 Fixed another problem where certain combinations of speed and acceleration could cause
|
||||||
|
/// oscillation about the target position.
|
||||||
|
/// Added support for 3 wire full and half steppers such as Hard Disk Drive spindle.
|
||||||
|
/// Contributed by Yuri Ivatchkovitch.
|
||||||
|
/// \version 1.29 Fixed a problem that could cause a DRIVER stepper to continually step
|
||||||
|
/// with some sketches. Reported by Vadim.
|
||||||
|
/// \version 1.30 Fixed a problem that could cause stepper to back up a few steps at the end of
|
||||||
|
/// accelerated travel with certain speeds. Reported and patched by jolo.
|
||||||
|
/// \version 1.31 Updated author and distribution location details to airspayce.com
|
||||||
|
/// \version 1.32 Fixed a problem with enableOutputs() and setEnablePin on Arduino Due that
|
||||||
|
/// prevented the enable pin changing stae correctly. Reported by Duane Bishop.
|
||||||
|
/// \version 1.33 Fixed an error in example AFMotor_ConstantSpeed.pde did not setMaxSpeed();
|
||||||
|
/// Fixed a problem that caused incorrect pin sequencing of FULL3WIRE and HALF3WIRE.
|
||||||
|
/// Unfortunately this meant changing the signature for all step*() functions.
|
||||||
|
/// Added example MotorShield, showing how to use AdaFruit Motor Shield to control
|
||||||
|
/// a 3 phase motor such as a HDD spindle motor (and without using the AFMotor library.
|
||||||
|
/// \version 1.34 Added setPinsInverted(bool pin1Invert, bool pin2Invert, bool pin3Invert, bool pin4Invert, bool enableInvert)
|
||||||
|
/// to allow inversion of 2, 3 and 4 wire stepper pins. Requested by Oleg.
|
||||||
|
/// \version 1.35 Removed default args from setPinsInverted(bool, bool, bool, bool, bool) to prevent ambiguity with
|
||||||
|
/// setPinsInverted(bool, bool, bool). Reported by Mac Mac.
|
||||||
|
/// \version 1.36 Changed enableOutputs() and disableOutputs() to be virtual so can be overridden.
|
||||||
|
/// Added new optional argument 'enable' to constructor, which allows you toi disable the
|
||||||
|
/// automatic enabling of outputs at construction time. Suggested by Guido.
|
||||||
|
/// \version 1.37 Fixed a problem with step1 that could cause a rogue step in the
|
||||||
|
/// wrong direction (or not,
|
||||||
|
/// depending on the setup-time requirements of the connected hardware).
|
||||||
|
/// Reported by Mark Tillotson.
|
||||||
|
/// \version 1.38 run() function incorrectly always returned true. Updated function and doc so it returns true
|
||||||
|
/// if the motor is still running to the target position.
|
||||||
|
/// \version 1.39 Updated typos in keywords.txt, courtesey Jon Magill.
|
||||||
|
/// \version 1.40 Updated documentation, including testing on Teensy 3.1
|
||||||
|
/// \version 1.41 Fixed an error in the acceleration calculations, resulting in acceleration of haldf the intended value
|
||||||
|
/// \version 1.42 Improved support for FULL3WIRE and HALF3WIRE output pins. These changes were in Yuri's original
|
||||||
|
/// contribution but did not make it into production.<br>
|
||||||
|
/// \version 1.43 Added DualMotorShield example. Shows how to use AccelStepper to control 2 x 2 phase steppers using the
|
||||||
|
/// Itead Studio Arduino Dual Stepper Motor Driver Shield model IM120417015.<br>
|
||||||
|
/// \version 1.44 examples/DualMotorShield/DualMotorShield.ino examples/DualMotorShield/DualMotorShield.pde
|
||||||
|
/// was missing from the distribution.<br>
|
||||||
|
/// \version 1.45 Fixed a problem where if setAcceleration was not called, there was no default
|
||||||
|
/// acceleration. Reported by Michael Newman.<br>
|
||||||
|
/// \version 1.45 Fixed inaccuracy in acceleration rate by using Equation 15, suggested by Sebastian Gracki.<br>
|
||||||
|
/// Performance improvements in runSpeed suggested by Jaakko Fagerlund.<br>
|
||||||
|
/// \version 1.46 Fixed error in documentation for runToPosition().
|
||||||
|
/// Reinstated time calculations in runSpeed() since new version is reported
|
||||||
|
/// not to work correctly under some circumstances. Reported by Oleg V Gavva.<br>
|
||||||
|
/// \version 1.48 2015-08-25
|
||||||
|
/// Added new class MultiStepper that can manage multiple AccelSteppers,
|
||||||
|
/// and cause them all to move
|
||||||
|
/// to selected positions at such a (constant) speed that they all arrive at their
|
||||||
|
/// target position at the same time. Suitable for X-Y flatbeds etc.<br>
|
||||||
|
/// Added new method maxSpeed() to AccelStepper to return the currently configured maxSpeed.<br>
|
||||||
|
/// \version 1.49 2016-01-02
|
||||||
|
/// Testing with VID28 series instrument stepper motors and EasyDriver.
|
||||||
|
/// OK, although with light pointers
|
||||||
|
/// and slow speeds like 180 full steps per second the motor movement can be erratic,
|
||||||
|
/// probably due to some mechanical resonance. Best to accelerate through this speed.<br>
|
||||||
|
/// Added isRunning().<br>
|
||||||
|
/// \version 1.50 2016-02-25
|
||||||
|
/// AccelStepper::disableOutputs now sets the enable pion to OUTPUT mode if the enable pin is defined.
|
||||||
|
/// Patch from Piet De Jong.<br>
|
||||||
|
/// Added notes about the fact that AFMotor_* examples do not work with Adafruit Motor Shield V2.<br>
|
||||||
|
/// \version 1.51 2016-03-24
|
||||||
|
/// Fixed a problem reported by gregor: when resetting the stepper motor position using setCurrentPosition() the
|
||||||
|
/// stepper speed is reset by setting _stepInterval to 0, but _speed is not
|
||||||
|
/// reset. this results in the stepper motor not starting again when calling
|
||||||
|
/// setSpeed() with the same speed the stepper was set to before.
|
||||||
|
/// \version 1.52 2016-08-09
|
||||||
|
/// Added MultiStepper to keywords.txt.
|
||||||
|
/// Improvements to efficiency of AccelStepper::runSpeed() as suggested by David Grayson.
|
||||||
|
/// Improvements to speed accuracy as suggested by David Grayson.
|
||||||
|
/// \version 1.53 2016-08-14
|
||||||
|
/// Backed out Improvements to speed accuracy from 1.52 as it did not work correctly.
|
||||||
|
/// \version 1.54 2017-01-24
|
||||||
|
/// Fixed some warnings about unused arguments.
|
||||||
|
/// \version 1.55 2017-01-25
|
||||||
|
/// Fixed another warning in MultiStepper.cpp
|
||||||
|
/// \version 1.56 2017-02-03
|
||||||
|
/// Fixed minor documentation error with DIRECTION_CCW and DIRECTION_CW. Reported by David Mutterer.
|
||||||
|
/// Added link to Binpress commercial license purchasing.
|
||||||
|
/// \version 1.57 2017-03-28
|
||||||
|
/// _direction moved to protected at the request of Rudy Ercek.
|
||||||
|
/// setMaxSpeed() and setAcceleration() now correct negative values to be positive.
|
||||||
|
/// \version 1.58 2018-04-13
|
||||||
|
/// Add initialisation for _enableInverted in constructor.
|
||||||
|
/// \version 1.59 2018-08-28
|
||||||
|
/// Update commercial licensing, remove binpress.
|
||||||
|
/// \version 1.60 2020-03-07
|
||||||
|
/// Release under GPL V3
|
||||||
|
/// \version 1.61 2020-04-20
|
||||||
|
/// Added yield() call in runToPosition(), so that platforms like esp8266 dont hang/crash
|
||||||
|
/// during long runs.
|
||||||
|
/// \version 1.62 2022-05-22
|
||||||
|
/// Added link to AccelStepper - The Missing Manual.<br>
|
||||||
|
/// Fixed a problem when setting the maxSpeed to 1.0 due to incomplete initialisation.
|
||||||
|
/// Reported by Olivier Pécheux. <br>
|
||||||
|
/// \version 1.63 2022-06-30
|
||||||
|
/// Added virtual destructor at the request of Jan.<br>
|
||||||
|
/// \version 1.64 2022-10-31
|
||||||
|
/// Patch courtesy acwest: Changes to make AccelStepper more subclassable. These changes are
|
||||||
|
/// largely oriented to implementing new step-scheduling algorithms.
|
||||||
|
///
|
||||||
|
/// \author Mike McCauley (mikem@airspayce.com) DO NOT CONTACT THE AUTHOR DIRECTLY: USE THE GOOGLE GROUP
|
||||||
|
// Copyright (C) 2009-2020 Mike McCauley
|
||||||
|
// $Id: AccelStepper.h,v 1.28 2020/04/20 00:15:03 mikem Exp mikem $
|
||||||
|
|
||||||
|
#ifndef AccelStepper_h
|
||||||
|
#define AccelStepper_h
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#if ARDUINO >= 100
|
||||||
|
#include <Arduino.h>
|
||||||
|
#else
|
||||||
|
#include <WProgram.h>
|
||||||
|
#include <wiring.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// These defs cause trouble on some versions of Arduino
|
||||||
|
#undef round
|
||||||
|
|
||||||
|
// Use the system yield() whenever possoible, since some platforms require it for housekeeping, especially
|
||||||
|
// ESP8266
|
||||||
|
#if (defined(ARDUINO) && ARDUINO >= 155) || defined(ESP8266)
|
||||||
|
#define YIELD yield();
|
||||||
|
#else
|
||||||
|
#define YIELD
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
/// \class AccelStepper AccelStepper.h <AccelStepper.h>
|
||||||
|
/// \brief Support for stepper motors with acceleration etc.
|
||||||
|
///
|
||||||
|
/// This defines a single 2 or 4 pin stepper motor, or stepper moter with fdriver chip, with optional
|
||||||
|
/// acceleration, deceleration, absolute positioning commands etc. Multiple
|
||||||
|
/// simultaneous steppers are supported, all moving
|
||||||
|
/// at different speeds and accelerations.
|
||||||
|
///
|
||||||
|
/// \par Operation
|
||||||
|
/// This module operates by computing a step time in microseconds. The step
|
||||||
|
/// time is recomputed after each step and after speed and acceleration
|
||||||
|
/// parameters are changed by the caller. The time of each step is recorded in
|
||||||
|
/// microseconds. The run() function steps the motor once if a new step is due.
|
||||||
|
/// The run() function must be called frequently until the motor is in the
|
||||||
|
/// desired position, after which time run() will do nothing.
|
||||||
|
///
|
||||||
|
/// \par Positioning
|
||||||
|
/// Positions are specified by a signed long integer. At
|
||||||
|
/// construction time, the current position of the motor is consider to be 0. Positive
|
||||||
|
/// positions are clockwise from the initial position; negative positions are
|
||||||
|
/// anticlockwise. The current position can be altered for instance after
|
||||||
|
/// initialization positioning.
|
||||||
|
///
|
||||||
|
/// \par Caveats
|
||||||
|
/// This is an open loop controller: If the motor stalls or is oversped,
|
||||||
|
/// AccelStepper will not have a correct
|
||||||
|
/// idea of where the motor really is (since there is no feedback of the motor's
|
||||||
|
/// real position. We only know where we _think_ it is, relative to the
|
||||||
|
/// initial starting point).
|
||||||
|
///
|
||||||
|
/// \par Performance
|
||||||
|
/// The fastest motor speed that can be reliably supported is about 4000 steps per
|
||||||
|
/// second at a clock frequency of 16 MHz on Arduino such as Uno etc.
|
||||||
|
/// Faster processors can support faster stepping speeds.
|
||||||
|
/// However, any speed less than that
|
||||||
|
/// down to very slow speeds (much less than one per second) are also supported,
|
||||||
|
/// provided the run() function is called frequently enough to step the motor
|
||||||
|
/// whenever required for the speed set.
|
||||||
|
/// Calling setAcceleration() is expensive,
|
||||||
|
/// since it requires a square root to be calculated.
|
||||||
|
///
|
||||||
|
/// Gregor Christandl reports that with an Arduino Due and a simple test program,
|
||||||
|
/// he measured 43163 steps per second using runSpeed(),
|
||||||
|
/// and 16214 steps per second using run();
|
||||||
|
class AccelStepper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// \brief Symbolic names for number of pins.
|
||||||
|
/// Use this in the pins argument the AccelStepper constructor to
|
||||||
|
/// provide a symbolic name for the number of pins
|
||||||
|
/// to use.
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
FUNCTION = 0, ///< Use the functional interface, implementing your own driver functions (internal use only)
|
||||||
|
DRIVER = 1, ///< Stepper Driver, 2 driver pins required
|
||||||
|
FULL2WIRE = 2, ///< 2 wire stepper, 2 motor pins required
|
||||||
|
FULL3WIRE = 3, ///< 3 wire stepper, such as HDD spindle, 3 motor pins required
|
||||||
|
FULL4WIRE = 4, ///< 4 wire full stepper, 4 motor pins required
|
||||||
|
HALF3WIRE = 6, ///< 3 wire half stepper, such as HDD spindle, 3 motor pins required
|
||||||
|
HALF4WIRE = 8 ///< 4 wire half stepper, 4 motor pins required
|
||||||
|
} MotorInterfaceType;
|
||||||
|
|
||||||
|
/// Constructor. You can have multiple simultaneous steppers, all moving
|
||||||
|
/// at different speeds and accelerations, provided you call their run()
|
||||||
|
/// functions at frequent enough intervals. Current Position is set to 0, target
|
||||||
|
/// position is set to 0. MaxSpeed and Acceleration default to 1.0.
|
||||||
|
/// The motor pins will be initialised to OUTPUT mode during the
|
||||||
|
/// constructor by a call to enableOutputs().
|
||||||
|
/// \param[in] interface Number of pins to interface to. Integer values are
|
||||||
|
/// supported, but it is preferred to use the \ref MotorInterfaceType symbolic names.
|
||||||
|
/// AccelStepper::DRIVER (1) means a stepper driver (with Step and Direction pins).
|
||||||
|
/// If an enable line is also needed, call setEnablePin() after construction.
|
||||||
|
/// You may also invert the pins using setPinsInverted().
|
||||||
|
/// Caution: DRIVER implements a blocking delay of minPulseWidth microseconds (default 1us) for each step.
|
||||||
|
/// You can change this with setMinPulseWidth().
|
||||||
|
/// AccelStepper::FULL2WIRE (2) means a 2 wire stepper (2 pins required).
|
||||||
|
/// AccelStepper::FULL3WIRE (3) means a 3 wire stepper, such as HDD spindle (3 pins required).
|
||||||
|
/// AccelStepper::FULL4WIRE (4) means a 4 wire stepper (4 pins required).
|
||||||
|
/// AccelStepper::HALF3WIRE (6) means a 3 wire half stepper, such as HDD spindle (3 pins required)
|
||||||
|
/// AccelStepper::HALF4WIRE (8) means a 4 wire half stepper (4 pins required)
|
||||||
|
/// Defaults to AccelStepper::FULL4WIRE (4) pins.
|
||||||
|
/// \param[in] pin1 Arduino digital pin number for motor pin 1. Defaults
|
||||||
|
/// to pin 2. For a AccelStepper::DRIVER (interface==1),
|
||||||
|
/// this is the Step input to the driver. Low to high transition means to step)
|
||||||
|
/// \param[in] pin2 Arduino digital pin number for motor pin 2. Defaults
|
||||||
|
/// to pin 3. For a AccelStepper::DRIVER (interface==1),
|
||||||
|
/// this is the Direction input the driver. High means forward.
|
||||||
|
/// \param[in] pin3 Arduino digital pin number for motor pin 3. Defaults
|
||||||
|
/// to pin 4.
|
||||||
|
/// \param[in] pin4 Arduino digital pin number for motor pin 4. Defaults
|
||||||
|
/// to pin 5.
|
||||||
|
/// \param[in] enable If this is true (the default), enableOutputs() will be called to enable
|
||||||
|
/// the output pins at construction time.
|
||||||
|
AccelStepper(uint8_t interface = AccelStepper::FULL4WIRE, uint8_t pin1 = 2, uint8_t pin2 = 3, uint8_t pin3 = 4, uint8_t pin4 = 5, bool enable = true);
|
||||||
|
|
||||||
|
/// Alternate Constructor which will call your own functions for forward and backward steps.
|
||||||
|
/// You can have multiple simultaneous steppers, all moving
|
||||||
|
/// at different speeds and accelerations, provided you call their run()
|
||||||
|
/// functions at frequent enough intervals. Current Position is set to 0, target
|
||||||
|
/// position is set to 0. MaxSpeed and Acceleration default to 1.0.
|
||||||
|
/// Any motor initialization should happen before hand, no pins are used or initialized.
|
||||||
|
/// \param[in] forward void-returning procedure that will make a forward step
|
||||||
|
/// \param[in] backward void-returning procedure that will make a backward step
|
||||||
|
AccelStepper(void (*forward)(), void (*backward)());
|
||||||
|
|
||||||
|
/// Set the target position. The run() function will try to move the motor (at most one step per call)
|
||||||
|
/// from the current position to the target position set by the most
|
||||||
|
/// recent call to this function. Caution: moveTo() also recalculates the speed for the next step.
|
||||||
|
/// If you are trying to use constant speed movements, you should call setSpeed() after calling moveTo().
|
||||||
|
/// \param[in] absolute The desired absolute position. Negative is
|
||||||
|
/// anticlockwise from the 0 position.
|
||||||
|
void moveTo(long absolute);
|
||||||
|
|
||||||
|
/// Set the target position relative to the current position.
|
||||||
|
/// \param[in] relative The desired position relative to the current position. Negative is
|
||||||
|
/// anticlockwise from the current position.
|
||||||
|
void move(long relative);
|
||||||
|
|
||||||
|
/// Poll the motor and step it if a step is due, implementing
|
||||||
|
/// accelerations and decelerations to achieve the target position. You must call this as
|
||||||
|
/// frequently as possible, but at least once per minimum step time interval,
|
||||||
|
/// preferably in your main loop. Note that each call to run() will make at most one step, and then only when a step is due,
|
||||||
|
/// based on the current speed and the time since the last step.
|
||||||
|
/// \return true if the motor is still running to the target position.
|
||||||
|
boolean run();
|
||||||
|
|
||||||
|
/// Poll the motor and step it if a step is due, implementing a constant
|
||||||
|
/// speed as set by the most recent call to setSpeed(). You must call this as
|
||||||
|
/// frequently as possible, but at least once per step interval,
|
||||||
|
/// \return true if the motor was stepped.
|
||||||
|
boolean runSpeed();
|
||||||
|
|
||||||
|
/// Sets the maximum permitted speed. The run() function will accelerate
|
||||||
|
/// up to the speed set by this function.
|
||||||
|
/// Caution: the maximum speed achievable depends on your processor and clock speed.
|
||||||
|
/// The default maxSpeed is 1.0 steps per second.
|
||||||
|
/// \param[in] speed The desired maximum speed in steps per second. Must
|
||||||
|
/// be > 0. Caution: Speeds that exceed the maximum speed supported by the processor may
|
||||||
|
/// Result in non-linear accelerations and decelerations.
|
||||||
|
void setMaxSpeed(float speed);
|
||||||
|
|
||||||
|
/// Returns the maximum speed configured for this stepper
|
||||||
|
/// that was previously set by setMaxSpeed();
|
||||||
|
/// \return The currently configured maximum speed
|
||||||
|
float maxSpeed();
|
||||||
|
|
||||||
|
/// Sets the acceleration/deceleration rate.
|
||||||
|
/// \param[in] acceleration The desired acceleration in steps per second
|
||||||
|
/// per second. Must be > 0.0. This is an expensive call since it requires a square
|
||||||
|
/// root to be calculated. Dont call more ofthen than needed
|
||||||
|
void setAcceleration(float acceleration);
|
||||||
|
|
||||||
|
/// Returns the acceleration/deceleration rate configured for this stepper
|
||||||
|
/// that was previously set by setAcceleration();
|
||||||
|
/// \return The currently configured acceleration/deceleration
|
||||||
|
float acceleration();
|
||||||
|
|
||||||
|
/// Sets the desired constant speed for use with runSpeed().
|
||||||
|
/// \param[in] speed The desired constant speed in steps per
|
||||||
|
/// second. Positive is clockwise. Speeds of more than 1000 steps per
|
||||||
|
/// second are unreliable. Very slow speeds may be set (eg 0.00027777 for
|
||||||
|
/// once per hour, approximately. Speed accuracy depends on the Arduino
|
||||||
|
/// crystal. Jitter depends on how frequently you call the runSpeed() function.
|
||||||
|
/// The speed will be limited by the current value of setMaxSpeed()
|
||||||
|
void setSpeed(float speed);
|
||||||
|
|
||||||
|
/// The most recently set speed.
|
||||||
|
/// \return the most recent speed in steps per second
|
||||||
|
float speed();
|
||||||
|
|
||||||
|
/// The distance from the current position to the target position.
|
||||||
|
/// \return the distance from the current position to the target position
|
||||||
|
/// in steps. Positive is clockwise from the current position.
|
||||||
|
long distanceToGo();
|
||||||
|
|
||||||
|
/// The most recently set target position.
|
||||||
|
/// \return the target position
|
||||||
|
/// in steps. Positive is clockwise from the 0 position.
|
||||||
|
long targetPosition();
|
||||||
|
|
||||||
|
/// The current motor position.
|
||||||
|
/// \return the current motor position
|
||||||
|
/// in steps. Positive is clockwise from the 0 position.
|
||||||
|
long currentPosition();
|
||||||
|
|
||||||
|
/// Resets the current position of the motor, so that wherever the motor
|
||||||
|
/// happens to be right now is considered to be the new 0 position. Useful
|
||||||
|
/// for setting a zero position on a stepper after an initial hardware
|
||||||
|
/// positioning move.
|
||||||
|
/// Has the side effect of setting the current motor speed to 0.
|
||||||
|
/// \param[in] position The position in steps of wherever the motor
|
||||||
|
/// happens to be right now.
|
||||||
|
void setCurrentPosition(long position);
|
||||||
|
|
||||||
|
/// Moves the motor (with acceleration/deceleration)
|
||||||
|
/// to the target position and blocks until it is at
|
||||||
|
/// position. Dont use this in event loops, since it blocks.
|
||||||
|
void runToPosition();
|
||||||
|
|
||||||
|
/// Executes runSpeed() unless the targetPosition is reached.
|
||||||
|
/// This function needs to be called often just like runSpeed() or run().
|
||||||
|
/// Will step the motor if a step is required at the currently selected
|
||||||
|
/// speed unless the target position has been reached.
|
||||||
|
/// Does not implement accelerations.
|
||||||
|
/// \return true if it stepped
|
||||||
|
boolean runSpeedToPosition();
|
||||||
|
|
||||||
|
/// Moves the motor (with acceleration/deceleration)
|
||||||
|
/// to the new target position and blocks until it is at
|
||||||
|
/// position. Dont use this in event loops, since it blocks.
|
||||||
|
/// \param[in] position The new target position.
|
||||||
|
void runToNewPosition(long position);
|
||||||
|
|
||||||
|
/// Sets a new target position that causes the stepper
|
||||||
|
/// to stop as quickly as possible, using the current speed and acceleration parameters.
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
/// Disable motor pin outputs by setting them all LOW
|
||||||
|
/// Depending on the design of your electronics this may turn off
|
||||||
|
/// the power to the motor coils, saving power.
|
||||||
|
/// This is useful to support Arduino low power modes: disable the outputs
|
||||||
|
/// during sleep and then reenable with enableOutputs() before stepping
|
||||||
|
/// again.
|
||||||
|
/// If the enable Pin is defined, sets it to OUTPUT mode and clears the pin to disabled.
|
||||||
|
virtual void disableOutputs();
|
||||||
|
|
||||||
|
/// Enable motor pin outputs by setting the motor pins to OUTPUT
|
||||||
|
/// mode. Called automatically by the constructor.
|
||||||
|
/// If the enable Pin is defined, sets it to OUTPUT mode and sets the pin to enabled.
|
||||||
|
virtual void enableOutputs();
|
||||||
|
|
||||||
|
/// Sets the minimum pulse width allowed by the stepper driver. The minimum practical pulse width is
|
||||||
|
/// approximately 20 microseconds. Times less than 20 microseconds
|
||||||
|
/// will usually result in 20 microseconds or so.
|
||||||
|
/// \param[in] minWidth The minimum pulse width in microseconds.
|
||||||
|
void setMinPulseWidth(unsigned int minWidth);
|
||||||
|
|
||||||
|
/// Sets the enable pin number for stepper drivers.
|
||||||
|
/// 0xFF indicates unused (default).
|
||||||
|
/// Otherwise, if a pin is set, the pin will be turned on when
|
||||||
|
/// enableOutputs() is called and switched off when disableOutputs()
|
||||||
|
/// is called.
|
||||||
|
/// \param[in] enablePin Arduino digital pin number for motor enable
|
||||||
|
/// \sa setPinsInverted
|
||||||
|
void setEnablePin(uint8_t enablePin = 0xff);
|
||||||
|
|
||||||
|
/// Sets the inversion for stepper driver pins
|
||||||
|
/// \param[in] directionInvert True for inverted direction pin, false for non-inverted
|
||||||
|
/// \param[in] stepInvert True for inverted step pin, false for non-inverted
|
||||||
|
/// \param[in] enableInvert True for inverted enable pin, false (default) for non-inverted
|
||||||
|
void setPinsInverted(bool directionInvert = false, bool stepInvert = false, bool enableInvert = false);
|
||||||
|
|
||||||
|
/// Sets the inversion for 2, 3 and 4 wire stepper pins
|
||||||
|
/// \param[in] pin1Invert True for inverted pin1, false for non-inverted
|
||||||
|
/// \param[in] pin2Invert True for inverted pin2, false for non-inverted
|
||||||
|
/// \param[in] pin3Invert True for inverted pin3, false for non-inverted
|
||||||
|
/// \param[in] pin4Invert True for inverted pin4, false for non-inverted
|
||||||
|
/// \param[in] enableInvert True for inverted enable pin, false (default) for non-inverted
|
||||||
|
void setPinsInverted(bool pin1Invert, bool pin2Invert, bool pin3Invert, bool pin4Invert, bool enableInvert);
|
||||||
|
|
||||||
|
/// Checks to see if the motor is currently running to a target
|
||||||
|
/// \return true if the speed is not zero or not at the target position
|
||||||
|
bool isRunning();
|
||||||
|
|
||||||
|
/// Virtual destructor to prevent warnings during delete
|
||||||
|
virtual ~AccelStepper() {};
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/// \brief Direction indicator
|
||||||
|
/// Symbolic names for the direction the motor is turning
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
DIRECTION_CCW = 0, ///< Counter-Clockwise
|
||||||
|
DIRECTION_CW = 1 ///< Clockwise
|
||||||
|
} Direction;
|
||||||
|
|
||||||
|
/// Forces the library to compute a new instantaneous speed and set that as
|
||||||
|
/// the current speed. It is called by
|
||||||
|
/// the library:
|
||||||
|
/// \li after each step
|
||||||
|
/// \li after change to maxSpeed through setMaxSpeed()
|
||||||
|
/// \li after change to acceleration through setAcceleration()
|
||||||
|
/// \li after change to target position (relative or absolute) through
|
||||||
|
/// move() or moveTo()
|
||||||
|
/// \return the new step interval
|
||||||
|
virtual unsigned long computeNewSpeed();
|
||||||
|
|
||||||
|
/// Low level function to set the motor output pins
|
||||||
|
/// bit 0 of the mask corresponds to _pin[0]
|
||||||
|
/// bit 1 of the mask corresponds to _pin[1]
|
||||||
|
/// You can override this to impment, for example serial chip output insted of using the
|
||||||
|
/// output pins directly
|
||||||
|
virtual void setOutputPins(uint8_t mask);
|
||||||
|
|
||||||
|
/// Called to execute a step. Only called when a new step is
|
||||||
|
/// required. Subclasses may override to implement new stepping
|
||||||
|
/// interfaces. The default calls step1(), step2(), step4() or step8() depending on the
|
||||||
|
/// number of pins defined for the stepper.
|
||||||
|
/// \param[in] step The current step phase number (0 to 7)
|
||||||
|
virtual void step(long step);
|
||||||
|
|
||||||
|
/// Called to execute a clockwise(+) step. Only called when a new step is
|
||||||
|
/// required. This increments the _currentPos and calls step()
|
||||||
|
/// \return the updated current position
|
||||||
|
long stepForward();
|
||||||
|
|
||||||
|
/// Called to execute a counter-clockwise(-) step. Only called when a new step is
|
||||||
|
/// required. This decrements the _currentPos and calls step()
|
||||||
|
/// \return the updated current position
|
||||||
|
long stepBackward();
|
||||||
|
|
||||||
|
/// Called to execute a step using stepper functions (pins = 0) Only called when a new step is
|
||||||
|
/// required. Calls _forward() or _backward() to perform the step
|
||||||
|
/// \param[in] step The current step phase number (0 to 7)
|
||||||
|
virtual void step0(long step);
|
||||||
|
|
||||||
|
/// Called to execute a step on a stepper driver (ie where pins == 1). Only called when a new step is
|
||||||
|
/// required. Subclasses may override to implement new stepping
|
||||||
|
/// interfaces. The default sets or clears the outputs of Step pin1 to step,
|
||||||
|
/// and sets the output of _pin2 to the desired direction. The Step pin (_pin1) is pulsed for 1 microsecond
|
||||||
|
/// which is the minimum STEP pulse width for the 3967 driver.
|
||||||
|
/// \param[in] step The current step phase number (0 to 7)
|
||||||
|
virtual void step1(long step);
|
||||||
|
|
||||||
|
/// Called to execute a step on a 2 pin motor. Only called when a new step is
|
||||||
|
/// required. Subclasses may override to implement new stepping
|
||||||
|
/// interfaces. The default sets or clears the outputs of pin1 and pin2
|
||||||
|
/// \param[in] step The current step phase number (0 to 7)
|
||||||
|
virtual void step2(long step);
|
||||||
|
|
||||||
|
/// Called to execute a step on a 3 pin motor, such as HDD spindle. Only called when a new step is
|
||||||
|
/// required. Subclasses may override to implement new stepping
|
||||||
|
/// interfaces. The default sets or clears the outputs of pin1, pin2,
|
||||||
|
/// pin3
|
||||||
|
/// \param[in] step The current step phase number (0 to 7)
|
||||||
|
virtual void step3(long step);
|
||||||
|
|
||||||
|
/// Called to execute a step on a 4 pin motor. Only called when a new step is
|
||||||
|
/// required. Subclasses may override to implement new stepping
|
||||||
|
/// interfaces. The default sets or clears the outputs of pin1, pin2,
|
||||||
|
/// pin3, pin4.
|
||||||
|
/// \param[in] step The current step phase number (0 to 7)
|
||||||
|
virtual void step4(long step);
|
||||||
|
|
||||||
|
/// Called to execute a step on a 3 pin motor, such as HDD spindle. Only called when a new step is
|
||||||
|
/// required. Subclasses may override to implement new stepping
|
||||||
|
/// interfaces. The default sets or clears the outputs of pin1, pin2,
|
||||||
|
/// pin3
|
||||||
|
/// \param[in] step The current step phase number (0 to 7)
|
||||||
|
virtual void step6(long step);
|
||||||
|
|
||||||
|
/// Called to execute a step on a 4 pin half-stepper motor. Only called when a new step is
|
||||||
|
/// required. Subclasses may override to implement new stepping
|
||||||
|
/// interfaces. The default sets or clears the outputs of pin1, pin2,
|
||||||
|
/// pin3, pin4.
|
||||||
|
/// \param[in] step The current step phase number (0 to 7)
|
||||||
|
virtual void step8(long step);
|
||||||
|
|
||||||
|
/// Current direction motor is spinning in
|
||||||
|
/// Protected because some peoples subclasses need it to be so
|
||||||
|
boolean _direction; // 1 == CW
|
||||||
|
|
||||||
|
/// The current interval between steps in microseconds.
|
||||||
|
/// 0 means the motor is currently stopped with _speed == 0
|
||||||
|
unsigned long _stepInterval;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Number of pins on the stepper motor. Permits 2 or 4. 2 pins is a
|
||||||
|
/// bipolar, and 4 pins is a unipolar.
|
||||||
|
uint8_t _interface; // 0, 1, 2, 4, 8, See MotorInterfaceType
|
||||||
|
|
||||||
|
/// Arduino pin number assignments for the 2 or 4 pins required to interface to the
|
||||||
|
/// stepper motor or driver
|
||||||
|
uint8_t _pin[4];
|
||||||
|
|
||||||
|
/// Whether the _pins is inverted or not
|
||||||
|
uint8_t _pinInverted[4];
|
||||||
|
|
||||||
|
/// The current absolution position in steps.
|
||||||
|
long _currentPos; // Steps
|
||||||
|
|
||||||
|
/// The target position in steps. The AccelStepper library will move the
|
||||||
|
/// motor from the _currentPos to the _targetPos, taking into account the
|
||||||
|
/// max speed, acceleration and deceleration
|
||||||
|
long _targetPos; // Steps
|
||||||
|
|
||||||
|
/// The current motos speed in steps per second
|
||||||
|
/// Positive is clockwise
|
||||||
|
float _speed; // Steps per second
|
||||||
|
|
||||||
|
/// The maximum permitted speed in steps per second. Must be > 0.
|
||||||
|
float _maxSpeed;
|
||||||
|
|
||||||
|
/// The acceleration to use to accelerate or decelerate the motor in steps
|
||||||
|
/// per second per second. Must be > 0
|
||||||
|
float _acceleration;
|
||||||
|
float _sqrt_twoa; // Precomputed sqrt(2*_acceleration)
|
||||||
|
|
||||||
|
/// The last step time in microseconds
|
||||||
|
unsigned long _lastStepTime;
|
||||||
|
|
||||||
|
/// The minimum allowed pulse width in microseconds
|
||||||
|
unsigned int _minPulseWidth;
|
||||||
|
|
||||||
|
/// Is the direction pin inverted?
|
||||||
|
///bool _dirInverted; /// Moved to _pinInverted[1]
|
||||||
|
|
||||||
|
/// Is the step pin inverted?
|
||||||
|
///bool _stepInverted; /// Moved to _pinInverted[0]
|
||||||
|
|
||||||
|
/// Is the enable pin inverted?
|
||||||
|
bool _enableInverted;
|
||||||
|
|
||||||
|
/// Enable pin for stepper driver, or 0xFF if unused.
|
||||||
|
uint8_t _enablePin;
|
||||||
|
|
||||||
|
/// The pointer to a forward-step procedure
|
||||||
|
void (*_forward)();
|
||||||
|
|
||||||
|
/// The pointer to a backward-step procedure
|
||||||
|
void (*_backward)();
|
||||||
|
|
||||||
|
/// The step counter for speed calculations
|
||||||
|
long _n;
|
||||||
|
|
||||||
|
/// Initial step size in microseconds
|
||||||
|
float _c0;
|
||||||
|
|
||||||
|
/// Last step size in microseconds
|
||||||
|
float _cn;
|
||||||
|
|
||||||
|
/// Min step size in microseconds based on maxSpeed
|
||||||
|
float _cmin; // at max speed
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @example Random.pde
|
||||||
|
/// Make a single stepper perform random changes in speed, position and acceleration
|
||||||
|
|
||||||
|
/// @example Overshoot.pde
|
||||||
|
/// Check overshoot handling
|
||||||
|
/// which sets a new target position and then waits until the stepper has
|
||||||
|
/// achieved it. This is used for testing the handling of overshoots
|
||||||
|
|
||||||
|
/// @example MultipleSteppers.pde
|
||||||
|
/// Shows how to multiple simultaneous steppers
|
||||||
|
/// Runs one stepper forwards and backwards, accelerating and decelerating
|
||||||
|
/// at the limits. Runs other steppers at the same time
|
||||||
|
|
||||||
|
/// @example ConstantSpeed.pde
|
||||||
|
/// Shows how to run AccelStepper in the simplest,
|
||||||
|
/// fixed speed mode with no accelerations
|
||||||
|
|
||||||
|
/// @example Blocking.pde
|
||||||
|
/// Shows how to use the blocking call runToNewPosition
|
||||||
|
/// Which sets a new target position and then waits until the stepper has
|
||||||
|
/// achieved it.
|
||||||
|
|
||||||
|
/// @example AFMotor_MultiStepper.pde
|
||||||
|
/// Control both Stepper motors at the same time with different speeds
|
||||||
|
/// and accelerations.
|
||||||
|
|
||||||
|
/// @example AFMotor_ConstantSpeed.pde
|
||||||
|
/// Shows how to run AccelStepper in the simplest,
|
||||||
|
/// fixed speed mode with no accelerations
|
||||||
|
|
||||||
|
/// @example ProportionalControl.pde
|
||||||
|
/// Make a single stepper follow the analog value read from a pot or whatever
|
||||||
|
/// The stepper will move at a constant speed to each newly set posiiton,
|
||||||
|
/// depending on the value of the pot.
|
||||||
|
|
||||||
|
/// @example Bounce.pde
|
||||||
|
/// Make a single stepper bounce from one limit to another, observing
|
||||||
|
/// accelrations at each end of travel
|
||||||
|
|
||||||
|
/// @example Quickstop.pde
|
||||||
|
/// Check stop handling.
|
||||||
|
/// Calls stop() while the stepper is travelling at full speed, causing
|
||||||
|
/// the stepper to stop as quickly as possible, within the constraints of the
|
||||||
|
/// current acceleration.
|
||||||
|
|
||||||
|
/// @example MotorShield.pde
|
||||||
|
/// Shows how to use AccelStepper to control a 3-phase motor, such as a HDD spindle motor
|
||||||
|
/// using the Adafruit Motor Shield http://www.ladyada.net/make/mshield/index.html.
|
||||||
|
|
||||||
|
/// @example DualMotorShield.pde
|
||||||
|
/// Shows how to use AccelStepper to control 2 x 2 phase steppers using the
|
||||||
|
/// Itead Studio Arduino Dual Stepper Motor Driver Shield
|
||||||
|
/// model IM120417015
|
||||||
|
|
||||||
|
#endif
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,317 @@
|
|||||||
|
#ifndef DallasTemperature_h
|
||||||
|
#define DallasTemperature_h
|
||||||
|
|
||||||
|
#define DALLASTEMPLIBVERSION "3.8.1" // To be deprecated -> TODO remove in 4.0.0
|
||||||
|
|
||||||
|
// This library is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU Lesser General Public
|
||||||
|
// License as published by the Free Software Foundation; either
|
||||||
|
// version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
// set to true to include code for new and delete operators
|
||||||
|
#ifndef REQUIRESNEW
|
||||||
|
#define REQUIRESNEW false
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// set to true to include code implementing alarm search functions
|
||||||
|
#ifndef REQUIRESALARMS
|
||||||
|
#define REQUIRESALARMS true
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#ifdef __STM32F1__
|
||||||
|
#include <OneWireSTM.h>
|
||||||
|
#else
|
||||||
|
#include <OneWire.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Model IDs
|
||||||
|
#define DS18S20MODEL 0x10 // also DS1820
|
||||||
|
#define DS18B20MODEL 0x28 // also MAX31820
|
||||||
|
#define DS1822MODEL 0x22
|
||||||
|
#define DS1825MODEL 0x3B
|
||||||
|
#define DS28EA00MODEL 0x42
|
||||||
|
|
||||||
|
// Error Codes
|
||||||
|
#define DEVICE_DISCONNECTED_C -127
|
||||||
|
#define DEVICE_DISCONNECTED_F -196.6
|
||||||
|
#define DEVICE_DISCONNECTED_RAW -7040
|
||||||
|
|
||||||
|
// For readPowerSupply on oneWire bus
|
||||||
|
// definition of nullptr for C++ < 11, using official workaround:
|
||||||
|
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf
|
||||||
|
#if __cplusplus < 201103L
|
||||||
|
const class
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template <class T>
|
||||||
|
operator T *() const
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
template <class C, class T>
|
||||||
|
operator T C::*() const
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void operator&() const;
|
||||||
|
} nullptr = {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef uint8_t DeviceAddress[8];
|
||||||
|
|
||||||
|
class DallasTemperature {
|
||||||
|
public:
|
||||||
|
|
||||||
|
DallasTemperature();
|
||||||
|
DallasTemperature(OneWire*);
|
||||||
|
DallasTemperature(OneWire*, uint8_t);
|
||||||
|
|
||||||
|
void setOneWire(OneWire*);
|
||||||
|
|
||||||
|
void setPullupPin(uint8_t);
|
||||||
|
|
||||||
|
// initialise bus
|
||||||
|
void begin(void);
|
||||||
|
|
||||||
|
// returns the number of devices found on the bus
|
||||||
|
uint8_t getDeviceCount(void);
|
||||||
|
|
||||||
|
// returns the number of DS18xxx Family devices on bus
|
||||||
|
uint8_t getDS18Count(void);
|
||||||
|
|
||||||
|
// returns true if address is valid
|
||||||
|
bool validAddress(const uint8_t*);
|
||||||
|
|
||||||
|
// returns true if address is of the family of sensors the lib supports.
|
||||||
|
bool validFamily(const uint8_t* deviceAddress);
|
||||||
|
|
||||||
|
// finds an address at a given index on the bus
|
||||||
|
bool getAddress(uint8_t*, uint8_t);
|
||||||
|
|
||||||
|
// attempt to determine if the device at the given address is connected to the bus
|
||||||
|
bool isConnected(const uint8_t*);
|
||||||
|
|
||||||
|
// attempt to determine if the device at the given address is connected to the bus
|
||||||
|
// also allows for updating the read scratchpad
|
||||||
|
bool isConnected(const uint8_t*, uint8_t*);
|
||||||
|
|
||||||
|
// read device's scratchpad
|
||||||
|
bool readScratchPad(const uint8_t*, uint8_t*);
|
||||||
|
|
||||||
|
// write device's scratchpad
|
||||||
|
void writeScratchPad(const uint8_t*, const uint8_t*);
|
||||||
|
|
||||||
|
// read device's power requirements
|
||||||
|
bool readPowerSupply(const uint8_t* deviceAddress = nullptr);
|
||||||
|
|
||||||
|
// get global resolution
|
||||||
|
uint8_t getResolution();
|
||||||
|
|
||||||
|
// set global resolution to 9, 10, 11, or 12 bits
|
||||||
|
void setResolution(uint8_t);
|
||||||
|
|
||||||
|
// returns the device resolution: 9, 10, 11, or 12 bits
|
||||||
|
uint8_t getResolution(const uint8_t*);
|
||||||
|
|
||||||
|
// set resolution of a device to 9, 10, 11, or 12 bits
|
||||||
|
bool setResolution(const uint8_t*, uint8_t,
|
||||||
|
bool skipGlobalBitResolutionCalculation = false);
|
||||||
|
|
||||||
|
// sets/gets the waitForConversion flag
|
||||||
|
void setWaitForConversion(bool);
|
||||||
|
bool getWaitForConversion(void);
|
||||||
|
|
||||||
|
// sets/gets the checkForConversion flag
|
||||||
|
void setCheckForConversion(bool);
|
||||||
|
bool getCheckForConversion(void);
|
||||||
|
|
||||||
|
// sends command for all devices on the bus to perform a temperature conversion
|
||||||
|
void requestTemperatures(void);
|
||||||
|
|
||||||
|
// sends command for one device to perform a temperature conversion by address
|
||||||
|
bool requestTemperaturesByAddress(const uint8_t*);
|
||||||
|
|
||||||
|
// sends command for one device to perform a temperature conversion by index
|
||||||
|
bool requestTemperaturesByIndex(uint8_t);
|
||||||
|
|
||||||
|
// returns temperature raw value (12 bit integer of 1/128 degrees C)
|
||||||
|
int16_t getTemp(const uint8_t*);
|
||||||
|
|
||||||
|
// returns temperature in degrees C
|
||||||
|
float getTempC(const uint8_t*);
|
||||||
|
|
||||||
|
// returns temperature in degrees F
|
||||||
|
float getTempF(const uint8_t*);
|
||||||
|
|
||||||
|
// Get temperature for device index (slow)
|
||||||
|
float getTempCByIndex(uint8_t);
|
||||||
|
|
||||||
|
// Get temperature for device index (slow)
|
||||||
|
float getTempFByIndex(uint8_t);
|
||||||
|
|
||||||
|
// returns true if the bus requires parasite power
|
||||||
|
bool isParasitePowerMode(void);
|
||||||
|
|
||||||
|
// Is a conversion complete on the wire? Only applies to the first sensor on the wire.
|
||||||
|
bool isConversionComplete(void);
|
||||||
|
|
||||||
|
int16_t millisToWaitForConversion(uint8_t);
|
||||||
|
|
||||||
|
// Sends command to one device to save values from scratchpad to EEPROM by index
|
||||||
|
// Returns true if no errors were encountered, false indicates failure
|
||||||
|
bool saveScratchPadByIndex(uint8_t);
|
||||||
|
|
||||||
|
// Sends command to one or more devices to save values from scratchpad to EEPROM
|
||||||
|
// Returns true if no errors were encountered, false indicates failure
|
||||||
|
bool saveScratchPad(const uint8_t* = nullptr);
|
||||||
|
|
||||||
|
// Sends command to one device to recall values from EEPROM to scratchpad by index
|
||||||
|
// Returns true if no errors were encountered, false indicates failure
|
||||||
|
bool recallScratchPadByIndex(uint8_t);
|
||||||
|
|
||||||
|
// Sends command to one or more devices to recall values from EEPROM to scratchpad
|
||||||
|
// Returns true if no errors were encountered, false indicates failure
|
||||||
|
bool recallScratchPad(const uint8_t* = nullptr);
|
||||||
|
|
||||||
|
// Sets the autoSaveScratchPad flag
|
||||||
|
void setAutoSaveScratchPad(bool);
|
||||||
|
|
||||||
|
// Gets the autoSaveScratchPad flag
|
||||||
|
bool getAutoSaveScratchPad(void);
|
||||||
|
|
||||||
|
#if REQUIRESALARMS
|
||||||
|
|
||||||
|
typedef void AlarmHandler(const uint8_t*);
|
||||||
|
|
||||||
|
// sets the high alarm temperature for a device
|
||||||
|
// accepts a int8_t. valid range is -55C - 125C
|
||||||
|
void setHighAlarmTemp(const uint8_t*, int8_t);
|
||||||
|
|
||||||
|
// sets the low alarm temperature for a device
|
||||||
|
// accepts a int8_t. valid range is -55C - 125C
|
||||||
|
void setLowAlarmTemp(const uint8_t*, int8_t);
|
||||||
|
|
||||||
|
// returns a int8_t with the current high alarm temperature for a device
|
||||||
|
// in the range -55C - 125C
|
||||||
|
int8_t getHighAlarmTemp(const uint8_t*);
|
||||||
|
|
||||||
|
// returns a int8_t with the current low alarm temperature for a device
|
||||||
|
// in the range -55C - 125C
|
||||||
|
int8_t getLowAlarmTemp(const uint8_t*);
|
||||||
|
|
||||||
|
// resets internal variables used for the alarm search
|
||||||
|
void resetAlarmSearch(void);
|
||||||
|
|
||||||
|
// search the wire for devices with active alarms
|
||||||
|
bool alarmSearch(uint8_t*);
|
||||||
|
|
||||||
|
// returns true if ia specific device has an alarm
|
||||||
|
bool hasAlarm(const uint8_t*);
|
||||||
|
|
||||||
|
// returns true if any device is reporting an alarm on the bus
|
||||||
|
bool hasAlarm(void);
|
||||||
|
|
||||||
|
// runs the alarm handler for all devices returned by alarmSearch()
|
||||||
|
void processAlarms(void);
|
||||||
|
|
||||||
|
// sets the alarm handler
|
||||||
|
void setAlarmHandler(const AlarmHandler *);
|
||||||
|
|
||||||
|
// returns true if an AlarmHandler has been set
|
||||||
|
bool hasAlarmHandler();
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// if no alarm handler is used the two bytes can be used as user data
|
||||||
|
// example of such usage is an ID.
|
||||||
|
// note if device is not connected it will fail writing the data.
|
||||||
|
// note if address cannot be found no error will be reported.
|
||||||
|
// in short use carefully
|
||||||
|
void setUserData(const uint8_t*, int16_t);
|
||||||
|
void setUserDataByIndex(uint8_t, int16_t);
|
||||||
|
int16_t getUserData(const uint8_t*);
|
||||||
|
int16_t getUserDataByIndex(uint8_t);
|
||||||
|
|
||||||
|
// convert from Celsius to Fahrenheit
|
||||||
|
static float toFahrenheit(float);
|
||||||
|
|
||||||
|
// convert from Fahrenheit to Celsius
|
||||||
|
static float toCelsius(float);
|
||||||
|
|
||||||
|
// convert from raw to Celsius
|
||||||
|
static float rawToCelsius(int16_t);
|
||||||
|
|
||||||
|
// convert from raw to Fahrenheit
|
||||||
|
static float rawToFahrenheit(int16_t);
|
||||||
|
|
||||||
|
#if REQUIRESNEW
|
||||||
|
|
||||||
|
// initialize memory area
|
||||||
|
void* operator new (unsigned int);
|
||||||
|
|
||||||
|
// delete memory reference
|
||||||
|
void operator delete(void*);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
typedef uint8_t ScratchPad[9];
|
||||||
|
|
||||||
|
// parasite power on or off
|
||||||
|
bool parasite;
|
||||||
|
|
||||||
|
// external pullup
|
||||||
|
bool useExternalPullup;
|
||||||
|
uint8_t pullupPin;
|
||||||
|
|
||||||
|
// used to determine the delay amount needed to allow for the
|
||||||
|
// temperature conversion to take place
|
||||||
|
uint8_t bitResolution;
|
||||||
|
|
||||||
|
// used to requestTemperature with or without delay
|
||||||
|
bool waitForConversion;
|
||||||
|
|
||||||
|
// used to requestTemperature to dynamically check if a conversion is complete
|
||||||
|
bool checkForConversion;
|
||||||
|
|
||||||
|
// used to determine if values will be saved from scratchpad to EEPROM on every scratchpad write
|
||||||
|
bool autoSaveScratchPad;
|
||||||
|
|
||||||
|
// count of devices on the bus
|
||||||
|
uint8_t devices;
|
||||||
|
|
||||||
|
// count of DS18xxx Family devices on bus
|
||||||
|
uint8_t ds18Count;
|
||||||
|
|
||||||
|
// Take a pointer to one wire instance
|
||||||
|
OneWire* _wire;
|
||||||
|
|
||||||
|
// reads scratchpad and returns the raw temperature
|
||||||
|
int16_t calculateTemperature(const uint8_t*, uint8_t*);
|
||||||
|
|
||||||
|
void blockTillConversionComplete(uint8_t);
|
||||||
|
|
||||||
|
// Returns true if all bytes of scratchPad are '\0'
|
||||||
|
bool isAllZeros(const uint8_t* const scratchPad, const size_t length = 9);
|
||||||
|
|
||||||
|
// External pullup control
|
||||||
|
void activateExternalPullup(void);
|
||||||
|
void deactivateExternalPullup(void);
|
||||||
|
|
||||||
|
#if REQUIRESALARMS
|
||||||
|
|
||||||
|
// required for alarmSearch
|
||||||
|
uint8_t alarmSearchAddress[8];
|
||||||
|
int8_t alarmSearchJunction;
|
||||||
|
uint8_t alarmSearchExhausted;
|
||||||
|
|
||||||
|
// the alarm handler function pointer
|
||||||
|
AlarmHandler *_AlarmHandler;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
};
|
||||||
|
#endif
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
|| @file Key.cpp
|
||||||
|
|| @version 1.0
|
||||||
|
|| @author Mark Stanley
|
||||||
|
|| @contact mstanley@technologist.com
|
||||||
|
||
|
||||||
|
|| @description
|
||||||
|
|| | Key class provides an abstract definition of a key or button
|
||||||
|
|| | and was initially designed to be used in conjunction with a
|
||||||
|
|| | state-machine.
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
|| @license
|
||||||
|
|| | This library is free software; you can redistribute it and/or
|
||||||
|
|| | modify it under the terms of the GNU Lesser General Public
|
||||||
|
|| | License as published by the Free Software Foundation; version
|
||||||
|
|| | 2.1 of the License.
|
||||||
|
|| |
|
||||||
|
|| | This library is distributed in the hope that it will be useful,
|
||||||
|
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
|| | Lesser General Public License for more details.
|
||||||
|
|| |
|
||||||
|
|| | You should have received a copy of the GNU Lesser General Public
|
||||||
|
|| | License along with this library; if not, write to the Free Software
|
||||||
|
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
*/
|
||||||
|
#include <Key.h>
|
||||||
|
|
||||||
|
|
||||||
|
// default constructor
|
||||||
|
Key::Key() {
|
||||||
|
kchar = NO_KEY;
|
||||||
|
kstate = IDLE;
|
||||||
|
stateChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructor
|
||||||
|
Key::Key(char userKeyChar) {
|
||||||
|
kchar = userKeyChar;
|
||||||
|
kcode = -1;
|
||||||
|
kstate = IDLE;
|
||||||
|
stateChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Key::key_update (char userKeyChar, KeyState userState, boolean userStatus) {
|
||||||
|
kchar = userKeyChar;
|
||||||
|
kstate = userState;
|
||||||
|
stateChanged = userStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|| @changelog
|
||||||
|
|| | 1.0 2012-06-04 - Mark Stanley : Initial Release
|
||||||
|
|| #
|
||||||
|
*/
|
||||||
+293
@@ -0,0 +1,293 @@
|
|||||||
|
/*
|
||||||
|
||
|
||||||
|
|| @file Keypad.cpp
|
||||||
|
|| @version 3.1
|
||||||
|
|| @author Mark Stanley, Alexander Brevig
|
||||||
|
|| @contact mstanley@technologist.com, alexanderbrevig@gmail.com
|
||||||
|
||
|
||||||
|
|| @description
|
||||||
|
|| | This library provides a simple interface for using matrix
|
||||||
|
|| | keypads. It supports multiple keypresses while maintaining
|
||||||
|
|| | backwards compatibility with the old single key library.
|
||||||
|
|| | It also supports user selectable pins and definable keymaps.
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
|| @license
|
||||||
|
|| | This library is free software; you can redistribute it and/or
|
||||||
|
|| | modify it under the terms of the GNU Lesser General Public
|
||||||
|
|| | License as published by the Free Software Foundation; version
|
||||||
|
|| | 2.1 of the License.
|
||||||
|
|| |
|
||||||
|
|| | This library is distributed in the hope that it will be useful,
|
||||||
|
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
|| | Lesser General Public License for more details.
|
||||||
|
|| |
|
||||||
|
|| | You should have received a copy of the GNU Lesser General Public
|
||||||
|
|| | License along with this library; if not, write to the Free Software
|
||||||
|
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
*/
|
||||||
|
#include <Keypad.h>
|
||||||
|
|
||||||
|
// <<constructor>> Allows custom keymap, pin configuration, and keypad sizes.
|
||||||
|
Keypad::Keypad(char *userKeymap, byte *row, byte *col, byte numRows, byte numCols) {
|
||||||
|
rowPins = row;
|
||||||
|
columnPins = col;
|
||||||
|
sizeKpd.rows = numRows;
|
||||||
|
sizeKpd.columns = numCols;
|
||||||
|
|
||||||
|
begin(userKeymap);
|
||||||
|
|
||||||
|
setDebounceTime(10);
|
||||||
|
setHoldTime(500);
|
||||||
|
keypadEventListener = 0;
|
||||||
|
|
||||||
|
startTime = 0;
|
||||||
|
single_key = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let the user define a keymap - assume the same row/column count as defined in constructor
|
||||||
|
void Keypad::begin(char *userKeymap) {
|
||||||
|
keymap = userKeymap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a single key only. Retained for backwards compatibility.
|
||||||
|
char Keypad::getKey() {
|
||||||
|
single_key = true;
|
||||||
|
|
||||||
|
if (getKeys() && key[0].stateChanged && (key[0].kstate==PRESSED))
|
||||||
|
return key[0].kchar;
|
||||||
|
|
||||||
|
single_key = false;
|
||||||
|
|
||||||
|
return NO_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the key list.
|
||||||
|
bool Keypad::getKeys() {
|
||||||
|
bool keyActivity = false;
|
||||||
|
|
||||||
|
// Limit how often the keypad is scanned. This makes the loop() run 10 times as fast.
|
||||||
|
if ( (millis()-startTime)>debounceTime ) {
|
||||||
|
scanKeys();
|
||||||
|
keyActivity = updateList();
|
||||||
|
startTime = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private : Hardware scan
|
||||||
|
void Keypad::scanKeys() {
|
||||||
|
// Re-intialize the row pins. Allows sharing these pins with other hardware.
|
||||||
|
for (byte r=0; r<sizeKpd.rows; r++) {
|
||||||
|
pin_mode(rowPins[r],INPUT_PULLUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitMap stores ALL the keys that are being pressed.
|
||||||
|
for (byte c=0; c<sizeKpd.columns; c++) {
|
||||||
|
pin_mode(columnPins[c],OUTPUT);
|
||||||
|
pin_write(columnPins[c], LOW); // Begin column pulse output.
|
||||||
|
for (byte r=0; r<sizeKpd.rows; r++) {
|
||||||
|
bitWrite(bitMap[r], c, !pin_read(rowPins[r])); // keypress is active low so invert to high.
|
||||||
|
}
|
||||||
|
// Set pin to high impedance input. Effectively ends column pulse.
|
||||||
|
pin_write(columnPins[c],HIGH);
|
||||||
|
pin_mode(columnPins[c],INPUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manage the list without rearranging the keys. Returns true if any keys on the list changed state.
|
||||||
|
bool Keypad::updateList() {
|
||||||
|
|
||||||
|
bool anyActivity = false;
|
||||||
|
|
||||||
|
// Delete any IDLE keys
|
||||||
|
for (byte i=0; i<LIST_MAX; i++) {
|
||||||
|
if (key[i].kstate==IDLE) {
|
||||||
|
key[i].kchar = NO_KEY;
|
||||||
|
key[i].kcode = -1;
|
||||||
|
key[i].stateChanged = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new keys to empty slots in the key list.
|
||||||
|
for (byte r=0; r<sizeKpd.rows; r++) {
|
||||||
|
for (byte c=0; c<sizeKpd.columns; c++) {
|
||||||
|
boolean button = bitRead(bitMap[r],c);
|
||||||
|
char keyChar = keymap[r * sizeKpd.columns + c];
|
||||||
|
int keyCode = r * sizeKpd.columns + c;
|
||||||
|
int idx = findInList (keyCode);
|
||||||
|
// Key is already on the list so set its next state.
|
||||||
|
if (idx > -1) {
|
||||||
|
nextKeyState(idx, button);
|
||||||
|
}
|
||||||
|
// Key is NOT on the list so add it.
|
||||||
|
if ((idx == -1) && button) {
|
||||||
|
for (byte i=0; i<LIST_MAX; i++) {
|
||||||
|
if (key[i].kchar==NO_KEY) { // Find an empty slot or don't add key to list.
|
||||||
|
key[i].kchar = keyChar;
|
||||||
|
key[i].kcode = keyCode;
|
||||||
|
key[i].kstate = IDLE; // Keys NOT on the list have an initial state of IDLE.
|
||||||
|
nextKeyState (i, button);
|
||||||
|
break; // Don't fill all the empty slots with the same key.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report if the user changed the state of any key.
|
||||||
|
for (byte i=0; i<LIST_MAX; i++) {
|
||||||
|
if (key[i].stateChanged) anyActivity = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private
|
||||||
|
// This function is a state machine but is also used for debouncing the keys.
|
||||||
|
void Keypad::nextKeyState(byte idx, boolean button) {
|
||||||
|
key[idx].stateChanged = false;
|
||||||
|
|
||||||
|
switch (key[idx].kstate) {
|
||||||
|
case IDLE:
|
||||||
|
if (button==CLOSED) {
|
||||||
|
transitionTo (idx, PRESSED);
|
||||||
|
holdTimer = millis(); } // Get ready for next HOLD state.
|
||||||
|
break;
|
||||||
|
case PRESSED:
|
||||||
|
if ((millis()-holdTimer)>holdTime) // Waiting for a key HOLD...
|
||||||
|
transitionTo (idx, HOLD);
|
||||||
|
else if (button==OPEN) // or for a key to be RELEASED.
|
||||||
|
transitionTo (idx, RELEASED);
|
||||||
|
break;
|
||||||
|
case HOLD:
|
||||||
|
if (button==OPEN)
|
||||||
|
transitionTo (idx, RELEASED);
|
||||||
|
break;
|
||||||
|
case RELEASED:
|
||||||
|
transitionTo (idx, IDLE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New in 2.1
|
||||||
|
bool Keypad::isPressed(char keyChar) {
|
||||||
|
for (byte i=0; i<LIST_MAX; i++) {
|
||||||
|
if ( key[i].kchar == keyChar ) {
|
||||||
|
if ( (key[i].kstate == PRESSED) && key[i].stateChanged )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false; // Not pressed.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search by character for a key in the list of active keys.
|
||||||
|
// Returns -1 if not found or the index into the list of active keys.
|
||||||
|
int Keypad::findInList (char keyChar) {
|
||||||
|
for (byte i=0; i<LIST_MAX; i++) {
|
||||||
|
if (key[i].kchar == keyChar) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search by code for a key in the list of active keys.
|
||||||
|
// Returns -1 if not found or the index into the list of active keys.
|
||||||
|
int Keypad::findInList (int keyCode) {
|
||||||
|
for (byte i=0; i<LIST_MAX; i++) {
|
||||||
|
if (key[i].kcode == keyCode) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New in 2.0
|
||||||
|
char Keypad::waitForKey() {
|
||||||
|
char waitKey = NO_KEY;
|
||||||
|
while( (waitKey = getKey()) == NO_KEY ); // Block everything while waiting for a keypress.
|
||||||
|
return waitKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backwards compatibility function.
|
||||||
|
KeyState Keypad::getState() {
|
||||||
|
return key[0].kstate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The end user can test for any changes in state before deciding
|
||||||
|
// if any variables, etc. needs to be updated in their code.
|
||||||
|
bool Keypad::keyStateChanged() {
|
||||||
|
return key[0].stateChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The number of keys on the key list, key[LIST_MAX], equals the number
|
||||||
|
// of bytes in the key list divided by the number of bytes in a Key object.
|
||||||
|
byte Keypad::numKeys() {
|
||||||
|
return sizeof(key)/sizeof(Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimum debounceTime is 1 mS. Any lower *will* slow down the loop().
|
||||||
|
void Keypad::setDebounceTime(uint debounce) {
|
||||||
|
debounce<1 ? debounceTime=1 : debounceTime=debounce;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keypad::setHoldTime(uint hold) {
|
||||||
|
holdTime = hold;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keypad::addEventListener(void (*listener)(char)){
|
||||||
|
keypadEventListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keypad::transitionTo(byte idx, KeyState nextState) {
|
||||||
|
key[idx].kstate = nextState;
|
||||||
|
key[idx].stateChanged = true;
|
||||||
|
|
||||||
|
// Sketch used the getKey() function.
|
||||||
|
// Calls keypadEventListener only when the first key in slot 0 changes state.
|
||||||
|
if (single_key) {
|
||||||
|
if ( (keypadEventListener!=NULL) && (idx==0) ) {
|
||||||
|
keypadEventListener(key[0].kchar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sketch used the getKeys() function.
|
||||||
|
// Calls keypadEventListener on any key that changes state.
|
||||||
|
else {
|
||||||
|
if (keypadEventListener!=NULL) {
|
||||||
|
keypadEventListener(key[idx].kchar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|| @changelog
|
||||||
|
|| | 3.1 2013-01-15 - Mark Stanley : Fixed missing RELEASED & IDLE status when using a single key.
|
||||||
|
|| | 3.0 2012-07-12 - Mark Stanley : Made library multi-keypress by default. (Backwards compatible)
|
||||||
|
|| | 3.0 2012-07-12 - Mark Stanley : Modified pin functions to support Keypad_I2C
|
||||||
|
|| | 3.0 2012-07-12 - Stanley & Young : Removed static variables. Fix for multiple keypad objects.
|
||||||
|
|| | 3.0 2012-07-12 - Mark Stanley : Fixed bug that caused shorted pins when pressing multiple keys.
|
||||||
|
|| | 2.0 2011-12-29 - Mark Stanley : Added waitForKey().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Added the public function keyStateChanged().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Added the private function scanKeys().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Moved the Finite State Machine into the function getKeyState().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Removed the member variable lastUdate. Not needed after rewrite.
|
||||||
|
|| | 1.8 2011-11-21 - Mark Stanley : Added decision logic to compile WProgram.h or Arduino.h
|
||||||
|
|| | 1.8 2009-07-08 - Alexander Brevig : No longer uses arrays
|
||||||
|
|| | 1.7 2009-06-18 - Alexander Brevig : Every time a state changes the keypadEventListener will trigger, if set.
|
||||||
|
|| | 1.7 2009-06-18 - Alexander Brevig : Added setDebounceTime. setHoldTime specifies the amount of
|
||||||
|
|| | microseconds before a HOLD state triggers
|
||||||
|
|| | 1.7 2009-06-18 - Alexander Brevig : Added transitionTo
|
||||||
|
|| | 1.6 2009-06-15 - Alexander Brevig : Added getState() and state variable
|
||||||
|
|| | 1.5 2009-05-19 - Alexander Brevig : Added setHoldTime()
|
||||||
|
|| | 1.4 2009-05-15 - Alexander Brevig : Added addEventListener
|
||||||
|
|| | 1.3 2009-05-12 - Alexander Brevig : Added lastUdate, in order to do simple debouncing
|
||||||
|
|| | 1.2 2009-05-09 - Alexander Brevig : Changed getKey()
|
||||||
|
|| | 1.1 2009-04-28 - Alexander Brevig : Modified API, and made variables private
|
||||||
|
|| | 1.0 2007-XX-XX - Mark Stanley : Initial Release
|
||||||
|
|| #
|
||||||
|
*/
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
||
|
||||||
|
|| @file Key.h
|
||||||
|
|| @version 1.0
|
||||||
|
|| @author Mark Stanley
|
||||||
|
|| @contact mstanley@technologist.com
|
||||||
|
||
|
||||||
|
|| @description
|
||||||
|
|| | Key class provides an abstract definition of a key or button
|
||||||
|
|| | and was initially designed to be used in conjunction with a
|
||||||
|
|| | state-machine.
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
|| @license
|
||||||
|
|| | This library is free software; you can redistribute it and/or
|
||||||
|
|| | modify it under the terms of the GNU Lesser General Public
|
||||||
|
|| | License as published by the Free Software Foundation; version
|
||||||
|
|| | 2.1 of the License.
|
||||||
|
|| |
|
||||||
|
|| | This library is distributed in the hope that it will be useful,
|
||||||
|
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
|| | Lesser General Public License for more details.
|
||||||
|
|| |
|
||||||
|
|| | You should have received a copy of the GNU Lesser General Public
|
||||||
|
|| | License along with this library; if not, write to the Free Software
|
||||||
|
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef Keypadlib_KEY_H_
|
||||||
|
#define Keypadlib_KEY_H_
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#define OPEN LOW
|
||||||
|
#define CLOSED HIGH
|
||||||
|
|
||||||
|
typedef unsigned int uint;
|
||||||
|
typedef enum{ IDLE, PRESSED, HOLD, RELEASED } KeyState;
|
||||||
|
|
||||||
|
const char NO_KEY = '\0';
|
||||||
|
|
||||||
|
class Key {
|
||||||
|
public:
|
||||||
|
// members
|
||||||
|
char kchar;
|
||||||
|
int kcode;
|
||||||
|
KeyState kstate;
|
||||||
|
boolean stateChanged;
|
||||||
|
|
||||||
|
// methods
|
||||||
|
Key();
|
||||||
|
Key(char userKeyChar);
|
||||||
|
void key_update(char userKeyChar, KeyState userState, boolean userStatus);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
|| @changelog
|
||||||
|
|| | 1.0 2012-06-04 - Mark Stanley : Initial Release
|
||||||
|
|| #
|
||||||
|
*/
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
||
|
||||||
|
|| @file Keypad.h
|
||||||
|
|| @version 3.1
|
||||||
|
|| @author Mark Stanley, Alexander Brevig
|
||||||
|
|| @contact mstanley@technologist.com, alexanderbrevig@gmail.com
|
||||||
|
||
|
||||||
|
|| @description
|
||||||
|
|| | This library provides a simple interface for using matrix
|
||||||
|
|| | keypads. It supports multiple keypresses while maintaining
|
||||||
|
|| | backwards compatibility with the old single key library.
|
||||||
|
|| | It also supports user selectable pins and definable keymaps.
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
|| @license
|
||||||
|
|| | This library is free software; you can redistribute it and/or
|
||||||
|
|| | modify it under the terms of the GNU Lesser General Public
|
||||||
|
|| | License as published by the Free Software Foundation; version
|
||||||
|
|| | 2.1 of the License.
|
||||||
|
|| |
|
||||||
|
|| | This library is distributed in the hope that it will be useful,
|
||||||
|
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
|| | Lesser General Public License for more details.
|
||||||
|
|| |
|
||||||
|
|| | You should have received a copy of the GNU Lesser General Public
|
||||||
|
|| | License along with this library; if not, write to the Free Software
|
||||||
|
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|| #
|
||||||
|
||
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KEYPAD_H
|
||||||
|
#define KEYPAD_H
|
||||||
|
|
||||||
|
#include "Key.h"
|
||||||
|
|
||||||
|
// bperrybap - Thanks for a well reasoned argument and the following macro(s).
|
||||||
|
// See http://arduino.cc/forum/index.php/topic,142041.msg1069480.html#msg1069480
|
||||||
|
#ifndef INPUT_PULLUP
|
||||||
|
#warning "Using pinMode() INPUT_PULLUP AVR emulation"
|
||||||
|
#define INPUT_PULLUP 0x2
|
||||||
|
#define pinMode(_pin, _mode) _mypinMode(_pin, _mode)
|
||||||
|
#define _mypinMode(_pin, _mode) \
|
||||||
|
do { \
|
||||||
|
if(_mode == INPUT_PULLUP) \
|
||||||
|
pinMode(_pin, INPUT); \
|
||||||
|
digitalWrite(_pin, 1); \
|
||||||
|
if(_mode != INPUT_PULLUP) \
|
||||||
|
pinMode(_pin, _mode); \
|
||||||
|
}while(0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#define OPEN LOW
|
||||||
|
#define CLOSED HIGH
|
||||||
|
|
||||||
|
typedef char KeypadEvent;
|
||||||
|
typedef unsigned int uint;
|
||||||
|
typedef unsigned long ulong;
|
||||||
|
|
||||||
|
// Made changes according to this post http://arduino.cc/forum/index.php?topic=58337.0
|
||||||
|
// by Nick Gammon. Thanks for the input Nick. It actually saved 78 bytes for me. :)
|
||||||
|
typedef struct {
|
||||||
|
byte rows;
|
||||||
|
byte columns;
|
||||||
|
} KeypadSize;
|
||||||
|
|
||||||
|
#define LIST_MAX 10 // Max number of keys on the active list.
|
||||||
|
#define MAPSIZE 10 // MAPSIZE is the number of rows (times 16 columns)
|
||||||
|
#define makeKeymap(x) ((char*)x)
|
||||||
|
|
||||||
|
|
||||||
|
//class Keypad : public Key, public HAL_obj {
|
||||||
|
class Keypad : public Key {
|
||||||
|
public:
|
||||||
|
|
||||||
|
Keypad(char *userKeymap, byte *row, byte *col, byte numRows, byte numCols);
|
||||||
|
|
||||||
|
virtual void pin_mode(byte pinNum, byte mode) { pinMode(pinNum, mode); }
|
||||||
|
virtual void pin_write(byte pinNum, boolean level) { digitalWrite(pinNum, level); }
|
||||||
|
virtual int pin_read(byte pinNum) { return digitalRead(pinNum); }
|
||||||
|
|
||||||
|
uint bitMap[MAPSIZE]; // 10 row x 16 column array of bits. Except Due which has 32 columns.
|
||||||
|
Key key[LIST_MAX];
|
||||||
|
unsigned long holdTimer;
|
||||||
|
|
||||||
|
char getKey();
|
||||||
|
bool getKeys();
|
||||||
|
KeyState getState();
|
||||||
|
void begin(char *userKeymap);
|
||||||
|
bool isPressed(char keyChar);
|
||||||
|
void setDebounceTime(uint);
|
||||||
|
void setHoldTime(uint);
|
||||||
|
void addEventListener(void (*listener)(char));
|
||||||
|
int findInList(char keyChar);
|
||||||
|
int findInList(int keyCode);
|
||||||
|
char waitForKey();
|
||||||
|
bool keyStateChanged();
|
||||||
|
byte numKeys();
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned long startTime;
|
||||||
|
char *keymap;
|
||||||
|
byte *rowPins;
|
||||||
|
byte *columnPins;
|
||||||
|
KeypadSize sizeKpd;
|
||||||
|
uint debounceTime;
|
||||||
|
uint holdTime;
|
||||||
|
bool single_key;
|
||||||
|
|
||||||
|
void scanKeys();
|
||||||
|
bool updateList();
|
||||||
|
void nextKeyState(byte n, boolean button);
|
||||||
|
void transitionTo(byte n, KeyState nextState);
|
||||||
|
void (*keypadEventListener)(char);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
|| @changelog
|
||||||
|
|| | 3.1 2013-01-15 - Mark Stanley : Fixed missing RELEASED & IDLE status when using a single key.
|
||||||
|
|| | 3.0 2012-07-12 - Mark Stanley : Made library multi-keypress by default. (Backwards compatible)
|
||||||
|
|| | 3.0 2012-07-12 - Mark Stanley : Modified pin functions to support Keypad_I2C
|
||||||
|
|| | 3.0 2012-07-12 - Stanley & Young : Removed static variables. Fix for multiple keypad objects.
|
||||||
|
|| | 3.0 2012-07-12 - Mark Stanley : Fixed bug that caused shorted pins when pressing multiple keys.
|
||||||
|
|| | 2.0 2011-12-29 - Mark Stanley : Added waitForKey().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Added the public function keyStateChanged().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Added the private function scanKeys().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Moved the Finite State Machine into the function getKeyState().
|
||||||
|
|| | 2.0 2011-12-23 - Mark Stanley : Removed the member variable lastUdate. Not needed after rewrite.
|
||||||
|
|| | 1.8 2011-11-21 - Mark Stanley : Added test to determine which header file to compile,
|
||||||
|
|| | WProgram.h or Arduino.h.
|
||||||
|
|| | 1.8 2009-07-08 - Alexander Brevig : No longer uses arrays
|
||||||
|
|| | 1.7 2009-06-18 - Alexander Brevig : This library is a Finite State Machine every time a state changes
|
||||||
|
|| | the keypadEventListener will trigger, if set
|
||||||
|
|| | 1.7 2009-06-18 - Alexander Brevig : Added setDebounceTime setHoldTime specifies the amount of
|
||||||
|
|| | microseconds before a HOLD state triggers
|
||||||
|
|| | 1.7 2009-06-18 - Alexander Brevig : Added transitionTo
|
||||||
|
|| | 1.6 2009-06-15 - Alexander Brevig : Added getState() and state variable
|
||||||
|
|| | 1.5 2009-05-19 - Alexander Brevig : Added setHoldTime()
|
||||||
|
|| | 1.4 2009-05-15 - Alexander Brevig : Added addEventListener
|
||||||
|
|| | 1.3 2009-05-12 - Alexander Brevig : Added lastUdate, in order to do simple debouncing
|
||||||
|
|| | 1.2 2009-05-09 - Alexander Brevig : Changed getKey()
|
||||||
|
|| | 1.1 2009-04-28 - Alexander Brevig : Modified API, and made variables private
|
||||||
|
|| | 1.0 2007-XX-XX - Mark Stanley : Initial Release
|
||||||
|
|| #
|
||||||
|
*/
|
||||||
@@ -0,0 +1,332 @@
|
|||||||
|
// Based on the work by DFRobot
|
||||||
|
|
||||||
|
#include "LiquidCrystal_I2C.h"
|
||||||
|
#include <inttypes.h>
|
||||||
|
#if defined(ARDUINO) && ARDUINO >= 100
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
#define printIIC(args) Wire.write(args)
|
||||||
|
inline size_t LiquidCrystal_I2C::write(uint8_t value) {
|
||||||
|
send(value, Rs);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
#include "WProgram.h"
|
||||||
|
|
||||||
|
#define printIIC(args) Wire.send(args)
|
||||||
|
inline void LiquidCrystal_I2C::write(uint8_t value) {
|
||||||
|
send(value, Rs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#include "Wire.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// When the display powers up, it is configured as follows:
|
||||||
|
//
|
||||||
|
// 1. Display clear
|
||||||
|
// 2. Function set:
|
||||||
|
// DL = 1; 8-bit interface data
|
||||||
|
// N = 0; 1-line display
|
||||||
|
// F = 0; 5x8 dot character font
|
||||||
|
// 3. Display on/off control:
|
||||||
|
// D = 0; Display off
|
||||||
|
// C = 0; Cursor off
|
||||||
|
// B = 0; Blinking off
|
||||||
|
// 4. Entry mode set:
|
||||||
|
// I/D = 1; Increment by 1
|
||||||
|
// S = 0; No shift
|
||||||
|
//
|
||||||
|
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
|
||||||
|
// can't assume that its in that state when a sketch starts (and the
|
||||||
|
// LiquidCrystal constructor is called).
|
||||||
|
|
||||||
|
LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows)
|
||||||
|
{
|
||||||
|
_Addr = lcd_Addr;
|
||||||
|
_cols = lcd_cols;
|
||||||
|
_rows = lcd_rows;
|
||||||
|
_backlightval = LCD_NOBACKLIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::oled_init(){
|
||||||
|
_oled = true;
|
||||||
|
init_priv();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::init(){
|
||||||
|
init_priv();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::init_priv()
|
||||||
|
{
|
||||||
|
Wire.begin();
|
||||||
|
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
|
||||||
|
begin(_cols, _rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
|
||||||
|
if (lines > 1) {
|
||||||
|
_displayfunction |= LCD_2LINE;
|
||||||
|
}
|
||||||
|
_numlines = lines;
|
||||||
|
|
||||||
|
// for some 1 line displays you can select a 10 pixel high font
|
||||||
|
if ((dotsize != 0) && (lines == 1)) {
|
||||||
|
_displayfunction |= LCD_5x10DOTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
|
||||||
|
// according to datasheet, we need at least 40ms after power rises above 2.7V
|
||||||
|
// before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
|
||||||
|
delay(50);
|
||||||
|
|
||||||
|
// Now we pull both RS and R/W low to begin commands
|
||||||
|
expanderWrite(_backlightval); // reset expanderand turn backlight off (Bit 8 =1)
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
//put the LCD into 4 bit mode
|
||||||
|
// this is according to the hitachi HD44780 datasheet
|
||||||
|
// figure 24, pg 46
|
||||||
|
|
||||||
|
// we start in 8bit mode, try to set 4 bit mode
|
||||||
|
write4bits(0x03 << 4);
|
||||||
|
delayMicroseconds(4500); // wait min 4.1ms
|
||||||
|
|
||||||
|
// second try
|
||||||
|
write4bits(0x03 << 4);
|
||||||
|
delayMicroseconds(4500); // wait min 4.1ms
|
||||||
|
|
||||||
|
// third go!
|
||||||
|
write4bits(0x03 << 4);
|
||||||
|
delayMicroseconds(150);
|
||||||
|
|
||||||
|
// finally, set to 4-bit interface
|
||||||
|
write4bits(0x02 << 4);
|
||||||
|
|
||||||
|
|
||||||
|
// set # lines, font size, etc.
|
||||||
|
command(LCD_FUNCTIONSET | _displayfunction);
|
||||||
|
|
||||||
|
// turn the display on with no cursor or blinking default
|
||||||
|
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
|
||||||
|
display();
|
||||||
|
|
||||||
|
// clear it off
|
||||||
|
clear();
|
||||||
|
|
||||||
|
// Initialize to default text direction (for roman languages)
|
||||||
|
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
|
||||||
|
|
||||||
|
// set the entry mode
|
||||||
|
command(LCD_ENTRYMODESET | _displaymode);
|
||||||
|
|
||||||
|
home();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/********** high level commands, for the user! */
|
||||||
|
void LiquidCrystal_I2C::clear(){
|
||||||
|
command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero
|
||||||
|
delayMicroseconds(2000); // this command takes a long time!
|
||||||
|
if (_oled) setCursor(0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::home(){
|
||||||
|
command(LCD_RETURNHOME); // set cursor position to zero
|
||||||
|
delayMicroseconds(2000); // this command takes a long time!
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row){
|
||||||
|
int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
|
||||||
|
if ( row > _numlines ) {
|
||||||
|
row = _numlines-1; // we count rows starting w/0
|
||||||
|
}
|
||||||
|
command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn the display on/off (quickly)
|
||||||
|
void LiquidCrystal_I2C::noDisplay() {
|
||||||
|
_displaycontrol &= ~LCD_DISPLAYON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
void LiquidCrystal_I2C::display() {
|
||||||
|
_displaycontrol |= LCD_DISPLAYON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turns the underline cursor on/off
|
||||||
|
void LiquidCrystal_I2C::noCursor() {
|
||||||
|
_displaycontrol &= ~LCD_CURSORON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
void LiquidCrystal_I2C::cursor() {
|
||||||
|
_displaycontrol |= LCD_CURSORON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn on and off the blinking cursor
|
||||||
|
void LiquidCrystal_I2C::noBlink() {
|
||||||
|
_displaycontrol &= ~LCD_BLINKON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
void LiquidCrystal_I2C::blink() {
|
||||||
|
_displaycontrol |= LCD_BLINKON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
|
||||||
|
// These commands scroll the display without changing the RAM
|
||||||
|
void LiquidCrystal_I2C::scrollDisplayLeft(void) {
|
||||||
|
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
|
||||||
|
}
|
||||||
|
void LiquidCrystal_I2C::scrollDisplayRight(void) {
|
||||||
|
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is for text that flows Left to Right
|
||||||
|
void LiquidCrystal_I2C::leftToRight(void) {
|
||||||
|
_displaymode |= LCD_ENTRYLEFT;
|
||||||
|
command(LCD_ENTRYMODESET | _displaymode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is for text that flows Right to Left
|
||||||
|
void LiquidCrystal_I2C::rightToLeft(void) {
|
||||||
|
_displaymode &= ~LCD_ENTRYLEFT;
|
||||||
|
command(LCD_ENTRYMODESET | _displaymode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will 'right justify' text from the cursor
|
||||||
|
void LiquidCrystal_I2C::autoscroll(void) {
|
||||||
|
_displaymode |= LCD_ENTRYSHIFTINCREMENT;
|
||||||
|
command(LCD_ENTRYMODESET | _displaymode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will 'left justify' text from the cursor
|
||||||
|
void LiquidCrystal_I2C::noAutoscroll(void) {
|
||||||
|
_displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
|
||||||
|
command(LCD_ENTRYMODESET | _displaymode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows us to fill the first 8 CGRAM locations
|
||||||
|
// with custom characters
|
||||||
|
void LiquidCrystal_I2C::createChar(uint8_t location, uint8_t charmap[]) {
|
||||||
|
location &= 0x7; // we only have 8 locations 0-7
|
||||||
|
command(LCD_SETCGRAMADDR | (location << 3));
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
write(charmap[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//createChar with PROGMEM input
|
||||||
|
void LiquidCrystal_I2C::createChar(uint8_t location, const char *charmap) {
|
||||||
|
location &= 0x7; // we only have 8 locations 0-7
|
||||||
|
command(LCD_SETCGRAMADDR | (location << 3));
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
write(pgm_read_byte_near(charmap++));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn the (optional) backlight off/on
|
||||||
|
void LiquidCrystal_I2C::noBacklight(void) {
|
||||||
|
_backlightval=LCD_NOBACKLIGHT;
|
||||||
|
expanderWrite(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::backlight(void) {
|
||||||
|
_backlightval=LCD_BACKLIGHT;
|
||||||
|
expanderWrite(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*********** mid level commands, for sending data/cmds */
|
||||||
|
|
||||||
|
inline void LiquidCrystal_I2C::command(uint8_t value) {
|
||||||
|
send(value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/************ low level data pushing commands **********/
|
||||||
|
|
||||||
|
// write either command or data
|
||||||
|
void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
|
||||||
|
uint8_t highnib=value&0xf0;
|
||||||
|
uint8_t lownib=(value<<4)&0xf0;
|
||||||
|
write4bits((highnib)|mode);
|
||||||
|
write4bits((lownib)|mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::write4bits(uint8_t value) {
|
||||||
|
expanderWrite(value);
|
||||||
|
pulseEnable(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::expanderWrite(uint8_t _data){
|
||||||
|
Wire.beginTransmission(_Addr);
|
||||||
|
printIIC((int)(_data) | _backlightval);
|
||||||
|
Wire.endTransmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::pulseEnable(uint8_t _data){
|
||||||
|
expanderWrite(_data | En); // En high
|
||||||
|
delayMicroseconds(1); // enable pulse must be >450ns
|
||||||
|
|
||||||
|
expanderWrite(_data & ~En); // En low
|
||||||
|
delayMicroseconds(50); // commands need > 37us to settle
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Alias functions
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::cursor_on(){
|
||||||
|
cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::cursor_off(){
|
||||||
|
noCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::blink_on(){
|
||||||
|
blink();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::blink_off(){
|
||||||
|
noBlink();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::load_custom_character(uint8_t char_num, uint8_t *rows){
|
||||||
|
createChar(char_num, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::setBacklight(uint8_t new_val){
|
||||||
|
if(new_val){
|
||||||
|
backlight(); // turn backlight on
|
||||||
|
}else{
|
||||||
|
noBacklight(); // turn backlight off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::printstr(const char c[]){
|
||||||
|
//This function is not identical to the function used for "real" I2C displays
|
||||||
|
//it's here so the user sketch doesn't have to be changed
|
||||||
|
print(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// unsupported API functions
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
|
void LiquidCrystal_I2C::off(){}
|
||||||
|
void LiquidCrystal_I2C::on(){}
|
||||||
|
void LiquidCrystal_I2C::setDelay (int cmdDelay,int charDelay) {}
|
||||||
|
uint8_t LiquidCrystal_I2C::status(){return 0;}
|
||||||
|
uint8_t LiquidCrystal_I2C::keypad (){return 0;}
|
||||||
|
uint8_t LiquidCrystal_I2C::init_bargraph(uint8_t graphtype){return 0;}
|
||||||
|
void LiquidCrystal_I2C::draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end){}
|
||||||
|
void LiquidCrystal_I2C::draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_row_end){}
|
||||||
|
void LiquidCrystal_I2C::setContrast(uint8_t new_val){}
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
//YWROBOT
|
||||||
|
#ifndef LiquidCrystal_I2C_h
|
||||||
|
#define LiquidCrystal_I2C_h
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "Print.h"
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
|
// commands
|
||||||
|
#define LCD_CLEARDISPLAY 0x01
|
||||||
|
#define LCD_RETURNHOME 0x02
|
||||||
|
#define LCD_ENTRYMODESET 0x04
|
||||||
|
#define LCD_DISPLAYCONTROL 0x08
|
||||||
|
#define LCD_CURSORSHIFT 0x10
|
||||||
|
#define LCD_FUNCTIONSET 0x20
|
||||||
|
#define LCD_SETCGRAMADDR 0x40
|
||||||
|
#define LCD_SETDDRAMADDR 0x80
|
||||||
|
|
||||||
|
// flags for display entry mode
|
||||||
|
#define LCD_ENTRYRIGHT 0x00
|
||||||
|
#define LCD_ENTRYLEFT 0x02
|
||||||
|
#define LCD_ENTRYSHIFTINCREMENT 0x01
|
||||||
|
#define LCD_ENTRYSHIFTDECREMENT 0x00
|
||||||
|
|
||||||
|
// flags for display on/off control
|
||||||
|
#define LCD_DISPLAYON 0x04
|
||||||
|
#define LCD_DISPLAYOFF 0x00
|
||||||
|
#define LCD_CURSORON 0x02
|
||||||
|
#define LCD_CURSOROFF 0x00
|
||||||
|
#define LCD_BLINKON 0x01
|
||||||
|
#define LCD_BLINKOFF 0x00
|
||||||
|
|
||||||
|
// flags for display/cursor shift
|
||||||
|
#define LCD_DISPLAYMOVE 0x08
|
||||||
|
#define LCD_CURSORMOVE 0x00
|
||||||
|
#define LCD_MOVERIGHT 0x04
|
||||||
|
#define LCD_MOVELEFT 0x00
|
||||||
|
|
||||||
|
// flags for function set
|
||||||
|
#define LCD_8BITMODE 0x10
|
||||||
|
#define LCD_4BITMODE 0x00
|
||||||
|
#define LCD_2LINE 0x08
|
||||||
|
#define LCD_1LINE 0x00
|
||||||
|
#define LCD_5x10DOTS 0x04
|
||||||
|
#define LCD_5x8DOTS 0x00
|
||||||
|
|
||||||
|
// flags for backlight control
|
||||||
|
#define LCD_BACKLIGHT 0x08
|
||||||
|
#define LCD_NOBACKLIGHT 0x00
|
||||||
|
|
||||||
|
#define En B00000100 // Enable bit
|
||||||
|
#define Rw B00000010 // Read/Write bit
|
||||||
|
#define Rs B00000001 // Register select bit
|
||||||
|
|
||||||
|
class LiquidCrystal_I2C : public Print {
|
||||||
|
public:
|
||||||
|
LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
|
||||||
|
void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS );
|
||||||
|
void clear();
|
||||||
|
void home();
|
||||||
|
void noDisplay();
|
||||||
|
void display();
|
||||||
|
void noBlink();
|
||||||
|
void blink();
|
||||||
|
void noCursor();
|
||||||
|
void cursor();
|
||||||
|
void scrollDisplayLeft();
|
||||||
|
void scrollDisplayRight();
|
||||||
|
void printLeft();
|
||||||
|
void printRight();
|
||||||
|
void leftToRight();
|
||||||
|
void rightToLeft();
|
||||||
|
void shiftIncrement();
|
||||||
|
void shiftDecrement();
|
||||||
|
void noBacklight();
|
||||||
|
void backlight();
|
||||||
|
void autoscroll();
|
||||||
|
void noAutoscroll();
|
||||||
|
void createChar(uint8_t, uint8_t[]);
|
||||||
|
void createChar(uint8_t location, const char *charmap);
|
||||||
|
// Example: const char bell[8] PROGMEM = {B00100,B01110,B01110,B01110,B11111,B00000,B00100,B00000};
|
||||||
|
|
||||||
|
void setCursor(uint8_t, uint8_t);
|
||||||
|
#if defined(ARDUINO) && ARDUINO >= 100
|
||||||
|
virtual size_t write(uint8_t);
|
||||||
|
#else
|
||||||
|
virtual void write(uint8_t);
|
||||||
|
#endif
|
||||||
|
void command(uint8_t);
|
||||||
|
void init();
|
||||||
|
void oled_init();
|
||||||
|
|
||||||
|
////compatibility API function aliases
|
||||||
|
void blink_on(); // alias for blink()
|
||||||
|
void blink_off(); // alias for noBlink()
|
||||||
|
void cursor_on(); // alias for cursor()
|
||||||
|
void cursor_off(); // alias for noCursor()
|
||||||
|
void setBacklight(uint8_t new_val); // alias for backlight() and nobacklight()
|
||||||
|
void load_custom_character(uint8_t char_num, uint8_t *rows); // alias for createChar()
|
||||||
|
void printstr(const char[]);
|
||||||
|
|
||||||
|
////Unsupported API functions (not implemented in this library)
|
||||||
|
uint8_t status();
|
||||||
|
void setContrast(uint8_t new_val);
|
||||||
|
uint8_t keypad();
|
||||||
|
void setDelay(int,int);
|
||||||
|
void on();
|
||||||
|
void off();
|
||||||
|
uint8_t init_bargraph(uint8_t graphtype);
|
||||||
|
void draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
|
||||||
|
void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
void init_priv();
|
||||||
|
void send(uint8_t, uint8_t);
|
||||||
|
void write4bits(uint8_t);
|
||||||
|
void expanderWrite(uint8_t);
|
||||||
|
void pulseEnable(uint8_t);
|
||||||
|
uint8_t _Addr;
|
||||||
|
uint8_t _displayfunction;
|
||||||
|
uint8_t _displaycontrol;
|
||||||
|
uint8_t _displaymode;
|
||||||
|
uint8_t _numlines;
|
||||||
|
bool _oled = false;
|
||||||
|
uint8_t _cols;
|
||||||
|
uint8_t _rows;
|
||||||
|
uint8_t _backlightval;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
+603
@@ -0,0 +1,603 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2007, Jim Studt (original old version - many contributors since)
|
||||||
|
|
||||||
|
The latest version of this library may be found at:
|
||||||
|
http://www.pjrc.com/teensy/td_libs_OneWire.html
|
||||||
|
|
||||||
|
OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since
|
||||||
|
January 2010.
|
||||||
|
|
||||||
|
DO NOT EMAIL for technical support, especially not for ESP chips!
|
||||||
|
All project support questions must be posted on public forums
|
||||||
|
relevant to the board or chips used. If using Arduino, post on
|
||||||
|
Arduino's forum. If using ESP, post on the ESP community forums.
|
||||||
|
There is ABSOLUTELY NO TECH SUPPORT BY PRIVATE EMAIL!
|
||||||
|
|
||||||
|
Github's issue tracker for OneWire should be used only to report
|
||||||
|
specific bugs. DO NOT request project support via Github. All
|
||||||
|
project and tech support questions must be posted on forums, not
|
||||||
|
github issues. If you experience a problem and you are not
|
||||||
|
absolutely sure it's an issue with the library, ask on a forum
|
||||||
|
first. Only use github to report issues after experts have
|
||||||
|
confirmed the issue is with OneWire rather than your project.
|
||||||
|
|
||||||
|
Back in 2010, OneWire was in need of many bug fixes, but had
|
||||||
|
been abandoned the original author (Jim Studt). None of the known
|
||||||
|
contributors were interested in maintaining OneWire. Paul typically
|
||||||
|
works on OneWire every 6 to 12 months. Patches usually wait that
|
||||||
|
long. If anyone is interested in more actively maintaining OneWire,
|
||||||
|
please contact Paul (this is pretty much the only reason to use
|
||||||
|
private email about OneWire).
|
||||||
|
|
||||||
|
OneWire is now very mature code. No changes other than adding
|
||||||
|
definitions for newer hardware support are anticipated.
|
||||||
|
|
||||||
|
ESP32 mods authored by stickbreaker:
|
||||||
|
@stickbreaker 30APR2018 add IRAM_ATTR to read_bit() write_bit() to solve ICache miss timing failure.
|
||||||
|
thanks @everslick re: https://github.com/espressif/arduino-esp32/issues/1335
|
||||||
|
Altered by garyd9 for clean merge with Paul Stoffregen's source
|
||||||
|
|
||||||
|
Version 2.3:
|
||||||
|
Unknown chip fallback mode, Roger Clark
|
||||||
|
Teensy-LC compatibility, Paul Stoffregen
|
||||||
|
Search bug fix, Love Nystrom
|
||||||
|
|
||||||
|
Version 2.2:
|
||||||
|
Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com
|
||||||
|
Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030
|
||||||
|
Fix DS18B20 example negative temperature
|
||||||
|
Fix DS18B20 example's low res modes, Ken Butcher
|
||||||
|
Improve reset timing, Mark Tillotson
|
||||||
|
Add const qualifiers, Bertrik Sikken
|
||||||
|
Add initial value input to crc16, Bertrik Sikken
|
||||||
|
Add target_search() function, Scott Roberts
|
||||||
|
|
||||||
|
Version 2.1:
|
||||||
|
Arduino 1.0 compatibility, Paul Stoffregen
|
||||||
|
Improve temperature example, Paul Stoffregen
|
||||||
|
DS250x_PROM example, Guillermo Lovato
|
||||||
|
PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com
|
||||||
|
Improvements from Glenn Trewitt:
|
||||||
|
- crc16() now works
|
||||||
|
- check_crc16() does all of calculation/checking work.
|
||||||
|
- Added read_bytes() and write_bytes(), to reduce tedious loops.
|
||||||
|
- Added ds2408 example.
|
||||||
|
Delete very old, out-of-date readme file (info is here)
|
||||||
|
|
||||||
|
Version 2.0: Modifications by Paul Stoffregen, January 2010:
|
||||||
|
http://www.pjrc.com/teensy/td_libs_OneWire.html
|
||||||
|
Search fix from Robin James
|
||||||
|
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
|
||||||
|
Use direct optimized I/O in all cases
|
||||||
|
Disable interrupts during timing critical sections
|
||||||
|
(this solves many random communication errors)
|
||||||
|
Disable interrupts during read-modify-write I/O
|
||||||
|
Reduce RAM consumption by eliminating unnecessary
|
||||||
|
variables and trimming many to 8 bits
|
||||||
|
Optimize both crc8 - table version moved to flash
|
||||||
|
|
||||||
|
Modified to work with larger numbers of devices - avoids loop.
|
||||||
|
Tested in Arduino 11 alpha with 12 sensors.
|
||||||
|
26 Sept 2008 -- Robin James
|
||||||
|
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
|
||||||
|
|
||||||
|
Updated to work with arduino-0008 and to include skip() as of
|
||||||
|
2007/07/06. --RJL20
|
||||||
|
|
||||||
|
Modified to calculate the 8-bit CRC directly, avoiding the need for
|
||||||
|
the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010
|
||||||
|
-- Tom Pollard, Jan 23, 2008
|
||||||
|
|
||||||
|
Jim Studt's original library was modified by Josh Larios.
|
||||||
|
|
||||||
|
Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
Much of the code was inspired by Derek Yerger's code, though I don't
|
||||||
|
think much of that remains. In any event that was..
|
||||||
|
(copyleft) 2006 by Derek Yerger - Free to distribute freely.
|
||||||
|
|
||||||
|
The CRC code was excerpted and inspired by the Dallas Semiconductor
|
||||||
|
sample code bearing this copyright.
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
// copy of this software and associated documentation files (the "Software"),
|
||||||
|
// to deal in the Software without restriction, including without limitation
|
||||||
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
// and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
// Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included
|
||||||
|
// in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||||
|
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
// OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
//
|
||||||
|
// Except as contained in this notice, the name of Dallas Semiconductor
|
||||||
|
// shall not be used except as stated in the Dallas Semiconductor
|
||||||
|
// Branding Policy.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "OneWire.h"
|
||||||
|
#include "util/OneWire_direct_gpio.h"
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
// due to the dual core esp32, a critical section works better than disabling interrupts
|
||||||
|
# define noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux)
|
||||||
|
# define interrupts() portEXIT_CRITICAL(&mux);}
|
||||||
|
// for info on this, search "IRAM_ATTR" at https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/general-notes.html
|
||||||
|
# define CRIT_TIMING IRAM_ATTR
|
||||||
|
#else
|
||||||
|
# define CRIT_TIMING
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
void OneWire::begin(uint8_t pin)
|
||||||
|
{
|
||||||
|
pinMode(pin, INPUT);
|
||||||
|
bitmask = PIN_TO_BITMASK(pin);
|
||||||
|
baseReg = PIN_TO_BASEREG(pin);
|
||||||
|
#if ONEWIRE_SEARCH
|
||||||
|
reset_search();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Perform the onewire reset function. We will wait up to 250uS for
|
||||||
|
// the bus to come high, if it doesn't then it is broken or shorted
|
||||||
|
// and we return a 0;
|
||||||
|
//
|
||||||
|
// Returns 1 if a device asserted a presence pulse, 0 otherwise.
|
||||||
|
//
|
||||||
|
uint8_t CRIT_TIMING OneWire::reset(void)
|
||||||
|
{
|
||||||
|
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||||
|
__attribute__((unused)) volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
|
||||||
|
uint8_t r;
|
||||||
|
uint8_t retries = 125;
|
||||||
|
|
||||||
|
noInterrupts();
|
||||||
|
DIRECT_MODE_INPUT(reg, mask);
|
||||||
|
interrupts();
|
||||||
|
// wait until the wire is high... just in case
|
||||||
|
do {
|
||||||
|
if (--retries == 0) return 0;
|
||||||
|
delayMicroseconds(2);
|
||||||
|
} while ( !DIRECT_READ(reg, mask));
|
||||||
|
|
||||||
|
noInterrupts();
|
||||||
|
DIRECT_WRITE_LOW(reg, mask);
|
||||||
|
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
|
||||||
|
interrupts();
|
||||||
|
delayMicroseconds(480);
|
||||||
|
noInterrupts();
|
||||||
|
DIRECT_MODE_INPUT(reg, mask); // allow it to float
|
||||||
|
delayMicroseconds(70);
|
||||||
|
r = !DIRECT_READ(reg, mask);
|
||||||
|
interrupts();
|
||||||
|
delayMicroseconds(410);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Write a bit. Port and bit is used to cut lookup time and provide
|
||||||
|
// more certain timing.
|
||||||
|
//
|
||||||
|
void CRIT_TIMING OneWire::write_bit(uint8_t v)
|
||||||
|
{
|
||||||
|
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||||
|
__attribute__((unused)) volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
|
||||||
|
|
||||||
|
if (v & 1) {
|
||||||
|
noInterrupts();
|
||||||
|
DIRECT_WRITE_LOW(reg, mask);
|
||||||
|
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
|
||||||
|
delayMicroseconds(10);
|
||||||
|
DIRECT_WRITE_HIGH(reg, mask); // drive output high
|
||||||
|
interrupts();
|
||||||
|
delayMicroseconds(55);
|
||||||
|
} else {
|
||||||
|
noInterrupts();
|
||||||
|
DIRECT_WRITE_LOW(reg, mask);
|
||||||
|
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
|
||||||
|
delayMicroseconds(65);
|
||||||
|
DIRECT_WRITE_HIGH(reg, mask); // drive output high
|
||||||
|
interrupts();
|
||||||
|
delayMicroseconds(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Read a bit. Port and bit is used to cut lookup time and provide
|
||||||
|
// more certain timing.
|
||||||
|
//
|
||||||
|
uint8_t CRIT_TIMING OneWire::read_bit(void)
|
||||||
|
{
|
||||||
|
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||||
|
__attribute__((unused)) volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
|
||||||
|
uint8_t r;
|
||||||
|
|
||||||
|
noInterrupts();
|
||||||
|
DIRECT_MODE_OUTPUT(reg, mask);
|
||||||
|
DIRECT_WRITE_LOW(reg, mask);
|
||||||
|
delayMicroseconds(3);
|
||||||
|
DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise
|
||||||
|
delayMicroseconds(10);
|
||||||
|
r = DIRECT_READ(reg, mask);
|
||||||
|
interrupts();
|
||||||
|
delayMicroseconds(53);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Write a byte. The writing code uses the active drivers to raise the
|
||||||
|
// pin high, if you need power after the write (e.g. DS18S20 in
|
||||||
|
// parasite power mode) then set 'power' to 1, otherwise the pin will
|
||||||
|
// go tri-state at the end of the write to avoid heating in a short or
|
||||||
|
// other mishap.
|
||||||
|
//
|
||||||
|
void OneWire::write(uint8_t v, uint8_t power /* = 0 */) {
|
||||||
|
uint8_t bitMask;
|
||||||
|
|
||||||
|
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
|
||||||
|
OneWire::write_bit( (bitMask & v)?1:0);
|
||||||
|
}
|
||||||
|
if ( !power) {
|
||||||
|
noInterrupts();
|
||||||
|
DIRECT_MODE_INPUT(baseReg, bitmask);
|
||||||
|
DIRECT_WRITE_LOW(baseReg, bitmask);
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OneWire::write_bytes(const uint8_t *buf, uint16_t count, bool power /* = 0 */) {
|
||||||
|
for (uint16_t i = 0 ; i < count ; i++)
|
||||||
|
write(buf[i]);
|
||||||
|
if (!power) {
|
||||||
|
noInterrupts();
|
||||||
|
DIRECT_MODE_INPUT(baseReg, bitmask);
|
||||||
|
DIRECT_WRITE_LOW(baseReg, bitmask);
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Read a byte
|
||||||
|
//
|
||||||
|
uint8_t OneWire::read() {
|
||||||
|
uint8_t bitMask;
|
||||||
|
uint8_t r = 0;
|
||||||
|
|
||||||
|
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
|
||||||
|
if ( OneWire::read_bit()) r |= bitMask;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OneWire::read_bytes(uint8_t *buf, uint16_t count) {
|
||||||
|
for (uint16_t i = 0 ; i < count ; i++)
|
||||||
|
buf[i] = read();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Do a ROM select
|
||||||
|
//
|
||||||
|
void OneWire::select(const uint8_t rom[8])
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
write(0x55); // Choose ROM
|
||||||
|
|
||||||
|
for (i = 0; i < 8; i++) write(rom[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Do a ROM skip
|
||||||
|
//
|
||||||
|
void OneWire::skip()
|
||||||
|
{
|
||||||
|
write(0xCC); // Skip ROM
|
||||||
|
}
|
||||||
|
|
||||||
|
void OneWire::depower()
|
||||||
|
{
|
||||||
|
noInterrupts();
|
||||||
|
DIRECT_MODE_INPUT(baseReg, bitmask);
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ONEWIRE_SEARCH
|
||||||
|
|
||||||
|
//
|
||||||
|
// You need to use this function to start a search again from the beginning.
|
||||||
|
// You do not need to do it for the first search, though you could.
|
||||||
|
//
|
||||||
|
void OneWire::reset_search()
|
||||||
|
{
|
||||||
|
// reset the search state
|
||||||
|
LastDiscrepancy = 0;
|
||||||
|
LastDeviceFlag = false;
|
||||||
|
LastFamilyDiscrepancy = 0;
|
||||||
|
for(int i = 7; ; i--) {
|
||||||
|
ROM_NO[i] = 0;
|
||||||
|
if ( i == 0) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the search to find the device type 'family_code' on the next call
|
||||||
|
// to search(*newAddr) if it is present.
|
||||||
|
//
|
||||||
|
void OneWire::target_search(uint8_t family_code)
|
||||||
|
{
|
||||||
|
// set the search state to find SearchFamily type devices
|
||||||
|
ROM_NO[0] = family_code;
|
||||||
|
for (uint8_t i = 1; i < 8; i++)
|
||||||
|
ROM_NO[i] = 0;
|
||||||
|
LastDiscrepancy = 64;
|
||||||
|
LastFamilyDiscrepancy = 0;
|
||||||
|
LastDeviceFlag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Perform a search. If this function returns a '1' then it has
|
||||||
|
// enumerated the next device and you may retrieve the ROM from the
|
||||||
|
// OneWire::address variable. If there are no devices, no further
|
||||||
|
// devices, or something horrible happens in the middle of the
|
||||||
|
// enumeration then a 0 is returned. If a new device is found then
|
||||||
|
// its address is copied to newAddr. Use OneWire::reset_search() to
|
||||||
|
// start over.
|
||||||
|
//
|
||||||
|
// --- Replaced by the one from the Dallas Semiconductor web site ---
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing
|
||||||
|
// search state.
|
||||||
|
// Return TRUE : device found, ROM number in ROM_NO buffer
|
||||||
|
// FALSE : device not found, end of search
|
||||||
|
//
|
||||||
|
bool OneWire::search(uint8_t *newAddr, bool search_mode /* = true */)
|
||||||
|
{
|
||||||
|
uint8_t id_bit_number;
|
||||||
|
uint8_t last_zero, rom_byte_number;
|
||||||
|
bool search_result;
|
||||||
|
uint8_t id_bit, cmp_id_bit;
|
||||||
|
|
||||||
|
unsigned char rom_byte_mask, search_direction;
|
||||||
|
|
||||||
|
// initialize for search
|
||||||
|
id_bit_number = 1;
|
||||||
|
last_zero = 0;
|
||||||
|
rom_byte_number = 0;
|
||||||
|
rom_byte_mask = 1;
|
||||||
|
search_result = false;
|
||||||
|
|
||||||
|
// if the last call was not the last one
|
||||||
|
if (!LastDeviceFlag) {
|
||||||
|
// 1-Wire reset
|
||||||
|
if (!reset()) {
|
||||||
|
// reset the search
|
||||||
|
LastDiscrepancy = 0;
|
||||||
|
LastDeviceFlag = false;
|
||||||
|
LastFamilyDiscrepancy = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// issue the search command
|
||||||
|
if (search_mode == true) {
|
||||||
|
write(0xF0); // NORMAL SEARCH
|
||||||
|
} else {
|
||||||
|
write(0xEC); // CONDITIONAL SEARCH
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop to do the search
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// read a bit and its complement
|
||||||
|
id_bit = read_bit();
|
||||||
|
cmp_id_bit = read_bit();
|
||||||
|
|
||||||
|
// check for no devices on 1-wire
|
||||||
|
if ((id_bit == 1) && (cmp_id_bit == 1)) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// all devices coupled have 0 or 1
|
||||||
|
if (id_bit != cmp_id_bit) {
|
||||||
|
search_direction = id_bit; // bit write value for search
|
||||||
|
} else {
|
||||||
|
// if this discrepancy if before the Last Discrepancy
|
||||||
|
// on a previous next then pick the same as last time
|
||||||
|
if (id_bit_number < LastDiscrepancy) {
|
||||||
|
search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0);
|
||||||
|
} else {
|
||||||
|
// if equal to last pick 1, if not then pick 0
|
||||||
|
search_direction = (id_bit_number == LastDiscrepancy);
|
||||||
|
}
|
||||||
|
// if 0 was picked then record its position in LastZero
|
||||||
|
if (search_direction == 0) {
|
||||||
|
last_zero = id_bit_number;
|
||||||
|
|
||||||
|
// check for Last discrepancy in family
|
||||||
|
if (last_zero < 9)
|
||||||
|
LastFamilyDiscrepancy = last_zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set or clear the bit in the ROM byte rom_byte_number
|
||||||
|
// with mask rom_byte_mask
|
||||||
|
if (search_direction == 1)
|
||||||
|
ROM_NO[rom_byte_number] |= rom_byte_mask;
|
||||||
|
else
|
||||||
|
ROM_NO[rom_byte_number] &= ~rom_byte_mask;
|
||||||
|
|
||||||
|
// serial number search direction write bit
|
||||||
|
write_bit(search_direction);
|
||||||
|
|
||||||
|
// increment the byte counter id_bit_number
|
||||||
|
// and shift the mask rom_byte_mask
|
||||||
|
id_bit_number++;
|
||||||
|
rom_byte_mask <<= 1;
|
||||||
|
|
||||||
|
// if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
|
||||||
|
if (rom_byte_mask == 0) {
|
||||||
|
rom_byte_number++;
|
||||||
|
rom_byte_mask = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while(rom_byte_number < 8); // loop until through all ROM bytes 0-7
|
||||||
|
|
||||||
|
// if the search was successful then
|
||||||
|
if (!(id_bit_number < 65)) {
|
||||||
|
// search successful so set LastDiscrepancy,LastDeviceFlag,search_result
|
||||||
|
LastDiscrepancy = last_zero;
|
||||||
|
|
||||||
|
// check for last device
|
||||||
|
if (LastDiscrepancy == 0) {
|
||||||
|
LastDeviceFlag = true;
|
||||||
|
}
|
||||||
|
search_result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no device found then reset counters so next 'search' will be like a first
|
||||||
|
if (!search_result || !ROM_NO[0]) {
|
||||||
|
LastDiscrepancy = 0;
|
||||||
|
LastDeviceFlag = false;
|
||||||
|
LastFamilyDiscrepancy = 0;
|
||||||
|
search_result = false;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i];
|
||||||
|
}
|
||||||
|
return search_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ONEWIRE_CRC
|
||||||
|
// The 1-Wire CRC scheme is described in Maxim Application Note 27:
|
||||||
|
// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products"
|
||||||
|
//
|
||||||
|
|
||||||
|
#if ONEWIRE_CRC8_TABLE
|
||||||
|
// Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0
|
||||||
|
// Tiny 2x16 entry CRC table created by Arjen Lentz
|
||||||
|
// See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table
|
||||||
|
static const uint8_t PROGMEM dscrc2x16_table[] = {
|
||||||
|
0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83,
|
||||||
|
0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41,
|
||||||
|
0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8,
|
||||||
|
0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM
|
||||||
|
// and the registers. (Use tiny 2x16 entry CRC table)
|
||||||
|
uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len)
|
||||||
|
{
|
||||||
|
uint8_t crc = 0;
|
||||||
|
|
||||||
|
while (len--) {
|
||||||
|
crc = *addr++ ^ crc; // just re-using crc as intermediate
|
||||||
|
crc = pgm_read_byte(dscrc2x16_table + (crc & 0x0f)) ^
|
||||||
|
pgm_read_byte(dscrc2x16_table + 16 + ((crc >> 4) & 0x0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
//
|
||||||
|
// Compute a Dallas Semiconductor 8 bit CRC directly.
|
||||||
|
// this is much slower, but a little smaller, than the lookup table.
|
||||||
|
//
|
||||||
|
uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len)
|
||||||
|
{
|
||||||
|
uint8_t crc = 0;
|
||||||
|
|
||||||
|
while (len--) {
|
||||||
|
#if defined(__AVR__)
|
||||||
|
crc = _crc_ibutton_update(crc, *addr++);
|
||||||
|
#else
|
||||||
|
uint8_t inbyte = *addr++;
|
||||||
|
for (uint8_t i = 8; i; i--) {
|
||||||
|
uint8_t mix = (crc ^ inbyte) & 0x01;
|
||||||
|
crc >>= 1;
|
||||||
|
if (mix) crc ^= 0x8C;
|
||||||
|
inbyte >>= 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ONEWIRE_CRC16
|
||||||
|
bool OneWire::check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc)
|
||||||
|
{
|
||||||
|
crc = ~crc16(input, len, crc);
|
||||||
|
return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc)
|
||||||
|
{
|
||||||
|
#if defined(__AVR__)
|
||||||
|
for (uint16_t i = 0 ; i < len ; i++) {
|
||||||
|
crc = _crc16_update(crc, input[i]);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static const uint8_t oddparity[16] =
|
||||||
|
{ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 };
|
||||||
|
|
||||||
|
for (uint16_t i = 0 ; i < len ; i++) {
|
||||||
|
// Even though we're just copying a byte from the input,
|
||||||
|
// we'll be doing 16-bit computation with it.
|
||||||
|
uint16_t cdata = input[i];
|
||||||
|
cdata = (cdata ^ crc) & 0xff;
|
||||||
|
crc >>= 8;
|
||||||
|
|
||||||
|
if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4])
|
||||||
|
crc ^= 0xC001;
|
||||||
|
|
||||||
|
cdata <<= 6;
|
||||||
|
crc ^= cdata;
|
||||||
|
cdata <<= 1;
|
||||||
|
crc ^= cdata;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// undef defines for no particular reason
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
# undef noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux)
|
||||||
|
# undef interrupts() portEXIT_CRITICAL(&mux);}
|
||||||
|
#endif
|
||||||
|
// for info on this, search "IRAM_ATTR" at https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/general-notes.html
|
||||||
|
#undef CRIT_TIMING
|
||||||
+182
@@ -0,0 +1,182 @@
|
|||||||
|
#ifndef OneWire_h
|
||||||
|
#define OneWire_h
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#if defined(__AVR__)
|
||||||
|
#include <util/crc16.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ARDUINO >= 100
|
||||||
|
#include <Arduino.h> // for delayMicroseconds, digitalPinToBitMask, etc
|
||||||
|
#else
|
||||||
|
#include "WProgram.h" // for delayMicroseconds
|
||||||
|
#include "pins_arduino.h" // for digitalPinToBitMask, etc
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// You can exclude certain features from OneWire. In theory, this
|
||||||
|
// might save some space. In practice, the compiler automatically
|
||||||
|
// removes unused code (technically, the linker, using -fdata-sections
|
||||||
|
// and -ffunction-sections when compiling, and Wl,--gc-sections
|
||||||
|
// when linking), so most of these will not result in any code size
|
||||||
|
// reduction. Well, unless you try to use the missing features
|
||||||
|
// and redesign your program to not need them! ONEWIRE_CRC8_TABLE
|
||||||
|
// is the exception, because it selects a fast but large algorithm
|
||||||
|
// or a small but slow algorithm.
|
||||||
|
|
||||||
|
// you can exclude onewire_search by defining that to 0
|
||||||
|
#ifndef ONEWIRE_SEARCH
|
||||||
|
#define ONEWIRE_SEARCH 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// You can exclude CRC checks altogether by defining this to 0
|
||||||
|
#ifndef ONEWIRE_CRC
|
||||||
|
#define ONEWIRE_CRC 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Select the table-lookup method of computing the 8-bit CRC
|
||||||
|
// by setting this to 1. The lookup table enlarges code size by
|
||||||
|
// about 250 bytes. It does NOT consume RAM (but did in very
|
||||||
|
// old versions of OneWire). If you disable this, a slower
|
||||||
|
// but very compact algorithm is used.
|
||||||
|
#ifndef ONEWIRE_CRC8_TABLE
|
||||||
|
#define ONEWIRE_CRC8_TABLE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// You can allow 16-bit CRC checks by defining this to 1
|
||||||
|
// (Note that ONEWIRE_CRC must also be 1.)
|
||||||
|
#ifndef ONEWIRE_CRC16
|
||||||
|
#define ONEWIRE_CRC16 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Board-specific macros for direct GPIO
|
||||||
|
#include "util/OneWire_direct_regtype.h"
|
||||||
|
|
||||||
|
class OneWire
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
IO_REG_TYPE bitmask;
|
||||||
|
volatile IO_REG_TYPE *baseReg;
|
||||||
|
|
||||||
|
#if ONEWIRE_SEARCH
|
||||||
|
// global search state
|
||||||
|
unsigned char ROM_NO[8];
|
||||||
|
uint8_t LastDiscrepancy;
|
||||||
|
uint8_t LastFamilyDiscrepancy;
|
||||||
|
bool LastDeviceFlag;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
OneWire() { }
|
||||||
|
OneWire(uint8_t pin) { begin(pin); }
|
||||||
|
void begin(uint8_t pin);
|
||||||
|
|
||||||
|
// Perform a 1-Wire reset cycle. Returns 1 if a device responds
|
||||||
|
// with a presence pulse. Returns 0 if there is no device or the
|
||||||
|
// bus is shorted or otherwise held low for more than 250uS
|
||||||
|
uint8_t reset(void);
|
||||||
|
|
||||||
|
// Issue a 1-Wire rom select command, you do the reset first.
|
||||||
|
void select(const uint8_t rom[8]);
|
||||||
|
|
||||||
|
// Issue a 1-Wire rom skip command, to address all on bus.
|
||||||
|
void skip(void);
|
||||||
|
|
||||||
|
// Write a byte. If 'power' is one then the wire is held high at
|
||||||
|
// the end for parasitically powered devices. You are responsible
|
||||||
|
// for eventually depowering it by calling depower() or doing
|
||||||
|
// another read or write.
|
||||||
|
void write(uint8_t v, uint8_t power = 0);
|
||||||
|
|
||||||
|
void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0);
|
||||||
|
|
||||||
|
// Read a byte.
|
||||||
|
uint8_t read(void);
|
||||||
|
|
||||||
|
void read_bytes(uint8_t *buf, uint16_t count);
|
||||||
|
|
||||||
|
// Write a bit. The bus is always left powered at the end, see
|
||||||
|
// note in write() about that.
|
||||||
|
void write_bit(uint8_t v);
|
||||||
|
|
||||||
|
// Read a bit.
|
||||||
|
uint8_t read_bit(void);
|
||||||
|
|
||||||
|
// Stop forcing power onto the bus. You only need to do this if
|
||||||
|
// you used the 'power' flag to write() or used a write_bit() call
|
||||||
|
// and aren't about to do another read or write. You would rather
|
||||||
|
// not leave this powered if you don't have to, just in case
|
||||||
|
// someone shorts your bus.
|
||||||
|
void depower(void);
|
||||||
|
|
||||||
|
#if ONEWIRE_SEARCH
|
||||||
|
// Clear the search state so that if will start from the beginning again.
|
||||||
|
void reset_search();
|
||||||
|
|
||||||
|
// Setup the search to find the device type 'family_code' on the next call
|
||||||
|
// to search(*newAddr) if it is present.
|
||||||
|
void target_search(uint8_t family_code);
|
||||||
|
|
||||||
|
// Look for the next device. Returns 1 if a new address has been
|
||||||
|
// returned. A zero might mean that the bus is shorted, there are
|
||||||
|
// no devices, or you have already retrieved all of them. It
|
||||||
|
// might be a good idea to check the CRC to make sure you didn't
|
||||||
|
// get garbage. The order is deterministic. You will always get
|
||||||
|
// the same devices in the same order.
|
||||||
|
bool search(uint8_t *newAddr, bool search_mode = true);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ONEWIRE_CRC
|
||||||
|
// Compute a Dallas Semiconductor 8 bit CRC, these are used in the
|
||||||
|
// ROM and scratchpad registers.
|
||||||
|
static uint8_t crc8(const uint8_t *addr, uint8_t len);
|
||||||
|
|
||||||
|
#if ONEWIRE_CRC16
|
||||||
|
// Compute the 1-Wire CRC16 and compare it against the received CRC.
|
||||||
|
// Example usage (reading a DS2408):
|
||||||
|
// // Put everything in a buffer so we can compute the CRC easily.
|
||||||
|
// uint8_t buf[13];
|
||||||
|
// buf[0] = 0xF0; // Read PIO Registers
|
||||||
|
// buf[1] = 0x88; // LSB address
|
||||||
|
// buf[2] = 0x00; // MSB address
|
||||||
|
// WriteBytes(net, buf, 3); // Write 3 cmd bytes
|
||||||
|
// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16
|
||||||
|
// if (!CheckCRC16(buf, 11, &buf[11])) {
|
||||||
|
// // Handle error.
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @param input - Array of bytes to checksum.
|
||||||
|
// @param len - How many bytes to use.
|
||||||
|
// @param inverted_crc - The two CRC16 bytes in the received data.
|
||||||
|
// This should just point into the received data,
|
||||||
|
// *not* at a 16-bit integer.
|
||||||
|
// @param crc - The crc starting value (optional)
|
||||||
|
// @return True, iff the CRC matches.
|
||||||
|
static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0);
|
||||||
|
|
||||||
|
// Compute a Dallas Semiconductor 16 bit CRC. This is required to check
|
||||||
|
// the integrity of data received from many 1-Wire devices. Note that the
|
||||||
|
// CRC computed here is *not* what you'll get from the 1-Wire network,
|
||||||
|
// for two reasons:
|
||||||
|
// 1) The CRC is transmitted bitwise inverted.
|
||||||
|
// 2) Depending on the endian-ness of your processor, the binary
|
||||||
|
// representation of the two-byte return value may have a different
|
||||||
|
// byte order than the two bytes you get from 1-Wire.
|
||||||
|
// @param input - Array of bytes to checksum.
|
||||||
|
// @param len - How many bytes to use.
|
||||||
|
// @param crc - The crc starting value (optional)
|
||||||
|
// @return The CRC16, as defined by Dallas Semiconductor.
|
||||||
|
static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prevent this name from leaking into Arduino sketches
|
||||||
|
#ifdef IO_REG_TYPE
|
||||||
|
#undef IO_REG_TYPE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // __cplusplus
|
||||||
|
#endif // OneWire_h
|
||||||
@@ -0,0 +1,519 @@
|
|||||||
|
#ifndef OneWire_Direct_GPIO_h
|
||||||
|
#define OneWire_Direct_GPIO_h
|
||||||
|
|
||||||
|
// This header should ONLY be included by OneWire.cpp. These defines are
|
||||||
|
// meant to be private, used within OneWire.cpp, but not exposed to Arduino
|
||||||
|
// sketches or other libraries which may include OneWire.h.
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Platform specific I/O definitions
|
||||||
|
|
||||||
|
#if defined(__AVR__)
|
||||||
|
#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin)))
|
||||||
|
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||||
|
#define IO_REG_TYPE uint8_t
|
||||||
|
#define IO_REG_BASE_ATTR asm("r30")
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
#if defined(__AVR_ATmega4809__)
|
||||||
|
#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0)
|
||||||
|
#define DIRECT_MODE_INPUT(base, mask) ((*((base)-8)) &= ~(mask))
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)-8)) |= (mask))
|
||||||
|
#define DIRECT_WRITE_LOW(base, mask) ((*((base)-4)) &= ~(mask))
|
||||||
|
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)-4)) |= (mask))
|
||||||
|
#else
|
||||||
|
#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0)
|
||||||
|
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask))
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask))
|
||||||
|
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask))
|
||||||
|
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
|
||||||
|
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
|
||||||
|
#define PIN_TO_BITMASK(pin) (1)
|
||||||
|
#define IO_REG_TYPE uint8_t
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR __attribute__ ((unused))
|
||||||
|
#define DIRECT_READ(base, mask) (*((base)+512))
|
||||||
|
#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0)
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1)
|
||||||
|
#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1)
|
||||||
|
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1)
|
||||||
|
|
||||||
|
#elif defined(__MKL26Z64__)
|
||||||
|
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
|
||||||
|
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||||
|
#define IO_REG_TYPE uint8_t
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0)
|
||||||
|
#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask))
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask))
|
||||||
|
#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask))
|
||||||
|
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask))
|
||||||
|
|
||||||
|
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
|
||||||
|
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
|
||||||
|
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
#define DIRECT_READ(base, mask) ((*((base)+2) & (mask)) ? 1 : 0)
|
||||||
|
#define DIRECT_MODE_INPUT(base, mask) (*((base)+1) &= ~(mask))
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+1) |= (mask))
|
||||||
|
#define DIRECT_WRITE_LOW(base, mask) (*((base)+34) = (mask))
|
||||||
|
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+33) = (mask))
|
||||||
|
|
||||||
|
#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__)
|
||||||
|
// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due.
|
||||||
|
// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268
|
||||||
|
// If you have trouble with OneWire on Arduino Due, please check the
|
||||||
|
// status of delayMicroseconds() before reporting a bug in OneWire!
|
||||||
|
#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER))
|
||||||
|
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0)
|
||||||
|
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask))
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask))
|
||||||
|
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask))
|
||||||
|
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask))
|
||||||
|
#ifndef PROGMEM
|
||||||
|
#define PROGMEM
|
||||||
|
#endif
|
||||||
|
#ifndef pgm_read_byte
|
||||||
|
#define pgm_read_byte(addr) (*(const uint8_t *)(addr))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#elif defined(__PIC32MX__)
|
||||||
|
#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin)))
|
||||||
|
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10
|
||||||
|
#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04
|
||||||
|
#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24
|
||||||
|
#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28
|
||||||
|
|
||||||
|
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||||
|
// Special note: I depend on the ESP community to maintain these definitions and
|
||||||
|
// submit good pull requests. I can not answer any ESP questions or help you
|
||||||
|
// resolve any problems related to ESP chips. Please do not contact me and please
|
||||||
|
// DO NOT CREATE GITHUB ISSUES for ESP support. All ESP questions must be asked
|
||||||
|
// on ESP community forums.
|
||||||
|
#define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO)
|
||||||
|
#define PIN_TO_BITMASK(pin) (1UL << (pin))
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directModeInput(IO_REG_TYPE mask)
|
||||||
|
{
|
||||||
|
if(mask > 0x8000)
|
||||||
|
{
|
||||||
|
GP16FFS(GPFFS_GPIO(16));
|
||||||
|
GPC16 = 0;
|
||||||
|
GP16E &= ~1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GPE &= ~(mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directModeOutput(IO_REG_TYPE mask)
|
||||||
|
{
|
||||||
|
if(mask > 0x8000)
|
||||||
|
{
|
||||||
|
GP16FFS(GPFFS_GPIO(16));
|
||||||
|
GPC16 = 0;
|
||||||
|
GP16E |= 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GPE |= (mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
bool directRead(IO_REG_TYPE mask)
|
||||||
|
{
|
||||||
|
if(mask > 0x8000)
|
||||||
|
return GP16I & 0x01;
|
||||||
|
else
|
||||||
|
return ((GPI & (mask)) ? true : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DIRECT_READ(base, mask) directRead(mask)
|
||||||
|
#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask)
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask)
|
||||||
|
#define DIRECT_WRITE_LOW(base, mask) (mask > 0x8000) ? GP16O &= ~1 : (GPOC = (mask))
|
||||||
|
#define DIRECT_WRITE_HIGH(base, mask) (mask > 0x8000) ? GP16O |= 1 : (GPOS = (mask))
|
||||||
|
|
||||||
|
#elif defined(ARDUINO_ARCH_ESP32)
|
||||||
|
#include <driver/rtc_io.h>
|
||||||
|
#include <soc/gpio_struct.h>
|
||||||
|
#define PIN_TO_BASEREG(pin) (0)
|
||||||
|
#define PIN_TO_BITMASK(pin) (pin)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
IO_REG_TYPE directRead(IO_REG_TYPE pin)
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
return (GPIO.in.val >> pin) & 0x1;
|
||||||
|
#else // plain ESP32
|
||||||
|
if ( pin < 32 )
|
||||||
|
return (GPIO.in >> pin) & 0x1;
|
||||||
|
else if ( pin < 46 )
|
||||||
|
return (GPIO.in1.val >> (pin - 32)) & 0x1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directWriteLow(IO_REG_TYPE pin)
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
GPIO.out_w1tc.val = ((uint32_t)1 << pin);
|
||||||
|
#else // plain ESP32
|
||||||
|
if ( pin < 32 )
|
||||||
|
GPIO.out_w1tc = ((uint32_t)1 << pin);
|
||||||
|
else if ( pin < 46 )
|
||||||
|
GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directWriteHigh(IO_REG_TYPE pin)
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
GPIO.out_w1ts.val = ((uint32_t)1 << pin);
|
||||||
|
#else // plain ESP32
|
||||||
|
if ( pin < 32 )
|
||||||
|
GPIO.out_w1ts = ((uint32_t)1 << pin);
|
||||||
|
else if ( pin < 46 )
|
||||||
|
GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directModeInput(IO_REG_TYPE pin)
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
GPIO.enable_w1tc.val = ((uint32_t)1 << (pin));
|
||||||
|
#else
|
||||||
|
if ( digitalPinIsValid(pin) )
|
||||||
|
{
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 4 // IDF 3.x ESP32/PICO-D4
|
||||||
|
uint32_t rtc_reg(rtc_gpio_desc[pin].reg);
|
||||||
|
|
||||||
|
if ( rtc_reg ) // RTC pins PULL settings
|
||||||
|
{
|
||||||
|
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux);
|
||||||
|
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// Input
|
||||||
|
if ( pin < 32 )
|
||||||
|
GPIO.enable_w1tc = ((uint32_t)1 << pin);
|
||||||
|
else
|
||||||
|
GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directModeOutput(IO_REG_TYPE pin)
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
GPIO.enable_w1ts.val = ((uint32_t)1 << (pin));
|
||||||
|
#else
|
||||||
|
if ( digitalPinIsValid(pin) && pin <= 33 ) // pins above 33 can be only inputs
|
||||||
|
{
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 4 // IDF 3.x ESP32/PICO-D4
|
||||||
|
uint32_t rtc_reg(rtc_gpio_desc[pin].reg);
|
||||||
|
|
||||||
|
if ( rtc_reg ) // RTC pins PULL settings
|
||||||
|
{
|
||||||
|
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux);
|
||||||
|
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// Output
|
||||||
|
if ( pin < 32 )
|
||||||
|
GPIO.enable_w1ts = ((uint32_t)1 << pin);
|
||||||
|
else // already validated to pins <= 33
|
||||||
|
GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DIRECT_READ(base, pin) directRead(pin)
|
||||||
|
#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin)
|
||||||
|
#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin)
|
||||||
|
#define DIRECT_MODE_INPUT(base, pin) directModeInput(pin)
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(pin)
|
||||||
|
// https://github.com/PaulStoffregen/OneWire/pull/47
|
||||||
|
// https://github.com/stickbreaker/OneWire/commit/6eb7fc1c11a15b6ac8c60e5671cf36eb6829f82c
|
||||||
|
#ifdef interrupts
|
||||||
|
#undef interrupts
|
||||||
|
#endif
|
||||||
|
#ifdef noInterrupts
|
||||||
|
#undef noInterrupts
|
||||||
|
#endif
|
||||||
|
#define noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux)
|
||||||
|
#define interrupts() portEXIT_CRITICAL(&mux);}
|
||||||
|
//#warning "ESP32 OneWire testing"
|
||||||
|
|
||||||
|
#elif defined(ARDUINO_ARCH_STM32)
|
||||||
|
#define PIN_TO_BASEREG(pin) (0)
|
||||||
|
#define PIN_TO_BITMASK(pin) ((uint32_t)digitalPinToPinName(pin))
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
#define DIRECT_READ(base, pin) digitalReadFast((PinName)pin)
|
||||||
|
#define DIRECT_WRITE_LOW(base, pin) digitalWriteFast((PinName)pin, LOW)
|
||||||
|
#define DIRECT_WRITE_HIGH(base, pin) digitalWriteFast((PinName)pin, HIGH)
|
||||||
|
#define DIRECT_MODE_INPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0))
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_OUTPUT_PP, GPIO_NOPULL, 0))
|
||||||
|
|
||||||
|
#elif defined(__SAMD21G18A__)
|
||||||
|
#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin))
|
||||||
|
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0)
|
||||||
|
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask))
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask))
|
||||||
|
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask))
|
||||||
|
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask))
|
||||||
|
|
||||||
|
#elif defined(__ASR6501__)
|
||||||
|
#define PIN_IN_PORT(pin) (pin % PIN_NUMBER_IN_PORT)
|
||||||
|
#define PORT_FROM_PIN(pin) (pin / PIN_NUMBER_IN_PORT)
|
||||||
|
#define PORT_OFFSET(port) (PORT_REG_SHFIT * port)
|
||||||
|
#define PORT_ADDRESS(pin) (CYDEV_GPIO_BASE + PORT_OFFSET(PORT_FROM_PIN(pin)))
|
||||||
|
|
||||||
|
#define PIN_TO_BASEREG(pin) (0)
|
||||||
|
#define PIN_TO_BITMASK(pin) (pin)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
#define DIRECT_READ(base, pin) CY_SYS_PINS_READ_PIN(PORT_ADDRESS(pin)+4, PIN_IN_PORT(pin))
|
||||||
|
#define DIRECT_WRITE_LOW(base, pin) CY_SYS_PINS_CLEAR_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin))
|
||||||
|
#define DIRECT_WRITE_HIGH(base, pin) CY_SYS_PINS_SET_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin))
|
||||||
|
#define DIRECT_MODE_INPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_DIG_HIZ)
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_STRONG)
|
||||||
|
|
||||||
|
#elif defined(RBL_NRF51822)
|
||||||
|
#define PIN_TO_BASEREG(pin) (0)
|
||||||
|
#define PIN_TO_BITMASK(pin) (pin)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin)
|
||||||
|
#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin)
|
||||||
|
#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin)
|
||||||
|
#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL)
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin)
|
||||||
|
|
||||||
|
#elif defined(__arc__) /* Arduino101/Genuino101 specifics */
|
||||||
|
|
||||||
|
#include "scss_registers.h"
|
||||||
|
#include "portable.h"
|
||||||
|
#include "avr/pgmspace.h"
|
||||||
|
|
||||||
|
#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId)
|
||||||
|
#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType)
|
||||||
|
#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase)
|
||||||
|
#define DIR_OFFSET_SS 0x01
|
||||||
|
#define DIR_OFFSET_SOC 0x04
|
||||||
|
#define EXT_PORT_OFFSET_SS 0x0A
|
||||||
|
#define EXT_PORT_OFFSET_SOC 0x50
|
||||||
|
|
||||||
|
/* GPIO registers base address */
|
||||||
|
#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase)
|
||||||
|
#define PIN_TO_BITMASK(pin) pin
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||||
|
{
|
||||||
|
IO_REG_TYPE ret;
|
||||||
|
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||||
|
ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS));
|
||||||
|
} else {
|
||||||
|
ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC);
|
||||||
|
}
|
||||||
|
return ((ret >> GPIO_ID(pin)) & 0x01);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||||
|
{
|
||||||
|
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||||
|
WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)),
|
||||||
|
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
|
||||||
|
} else {
|
||||||
|
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||||
|
{
|
||||||
|
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||||
|
WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)),
|
||||||
|
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
|
||||||
|
} else {
|
||||||
|
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||||
|
{
|
||||||
|
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||||
|
WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base);
|
||||||
|
} else {
|
||||||
|
MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||||
|
{
|
||||||
|
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||||
|
WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base);
|
||||||
|
} else {
|
||||||
|
MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DIRECT_READ(base, pin) directRead(base, pin)
|
||||||
|
#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin)
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin)
|
||||||
|
#define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin)
|
||||||
|
#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin)
|
||||||
|
|
||||||
|
#elif defined(__riscv)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tested on highfive1
|
||||||
|
*
|
||||||
|
* Stable results are achieved operating in the
|
||||||
|
* two high speed modes of the highfive1. It
|
||||||
|
* seems to be less reliable in slow mode.
|
||||||
|
*/
|
||||||
|
#define PIN_TO_BASEREG(pin) (0)
|
||||||
|
#define PIN_TO_BITMASK(pin) digitalPinToBitMask(pin)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
IO_REG_TYPE directRead(IO_REG_TYPE mask)
|
||||||
|
{
|
||||||
|
return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directModeInput(IO_REG_TYPE mask)
|
||||||
|
{
|
||||||
|
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
|
||||||
|
GPIO_REG(GPIO_IOF_EN) &= ~mask;
|
||||||
|
|
||||||
|
GPIO_REG(GPIO_INPUT_EN) |= mask;
|
||||||
|
GPIO_REG(GPIO_OUTPUT_EN) &= ~mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directModeOutput(IO_REG_TYPE mask)
|
||||||
|
{
|
||||||
|
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
|
||||||
|
GPIO_REG(GPIO_IOF_EN) &= ~mask;
|
||||||
|
|
||||||
|
GPIO_REG(GPIO_INPUT_EN) &= ~mask;
|
||||||
|
GPIO_REG(GPIO_OUTPUT_EN) |= mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directWriteLow(IO_REG_TYPE mask)
|
||||||
|
{
|
||||||
|
GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void directWriteHigh(IO_REG_TYPE mask)
|
||||||
|
{
|
||||||
|
GPIO_REG(GPIO_OUTPUT_VAL) |= mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DIRECT_READ(base, mask) directRead(mask)
|
||||||
|
#define DIRECT_WRITE_LOW(base, mask) directWriteLow(mask)
|
||||||
|
#define DIRECT_WRITE_HIGH(base, mask) directWriteHigh(mask)
|
||||||
|
#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask)
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask)
|
||||||
|
|
||||||
|
#elif defined(__MBED__)
|
||||||
|
|
||||||
|
#include "platform/mbed_critical.h"
|
||||||
|
#include "DigitalInOut.h"
|
||||||
|
#include <cmsis_os2.h>
|
||||||
|
#define PIN_TO_BASEREG(pin) (0)
|
||||||
|
#define PIN_TO_BITMASK(pin) (new mbed::DigitalInOut(digitalPinToPinName(pin)))
|
||||||
|
#define IO_REG_TYPE mbed::DigitalInOut*
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
#define DIRECT_READ(base, pin) (*pin)
|
||||||
|
#define DIRECT_WRITE_LOW(base, pin) (*pin = 0)
|
||||||
|
#define DIRECT_WRITE_HIGH(base, pin) (*pin = 1)
|
||||||
|
#define DIRECT_MODE_INPUT(base, pin) (pin->input())
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, pin) (pin->output())
|
||||||
|
#undef interrupts
|
||||||
|
#undef noInterrupts
|
||||||
|
#define noInterrupts() osThreadSetPriority(osThreadGetId(), osPriorityRealtime) //core_util_critical_section_enter()
|
||||||
|
#define interrupts() osThreadSetPriority(osThreadGetId(), osPriorityNormal) //core_util_critical_section_exit()
|
||||||
|
|
||||||
|
#elif defined(ARDUINO_ARCH_MBED_RP2040)|| defined(ARDUINO_ARCH_RP2040)
|
||||||
|
#define delayMicroseconds(time) busy_wait_us(time)
|
||||||
|
#define PIN_TO_BASEREG(pin) (0)
|
||||||
|
#define PIN_TO_BITMASK(pin) (pin)
|
||||||
|
#define IO_REG_TYPE unsigned int
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
#define DIRECT_READ(base, pin) digitalRead(pin)
|
||||||
|
#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW)
|
||||||
|
#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH)
|
||||||
|
#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT)
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT)
|
||||||
|
#warning "OneWire. RP2040 in Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite."
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define PIN_TO_BASEREG(pin) (0)
|
||||||
|
#define PIN_TO_BITMASK(pin) (pin)
|
||||||
|
#define IO_REG_TYPE unsigned int
|
||||||
|
#define IO_REG_BASE_ATTR
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
#define DIRECT_READ(base, pin) digitalRead(pin)
|
||||||
|
#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW)
|
||||||
|
#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH)
|
||||||
|
#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT)
|
||||||
|
#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT)
|
||||||
|
#warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture."
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#ifndef OneWire_Direct_RegType_h
|
||||||
|
#define OneWire_Direct_RegType_h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Platform specific I/O register type
|
||||||
|
|
||||||
|
#if defined(__AVR__)
|
||||||
|
#define IO_REG_TYPE uint8_t
|
||||||
|
|
||||||
|
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
|
||||||
|
#define IO_REG_TYPE uint8_t
|
||||||
|
|
||||||
|
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
|
||||||
|
#elif defined(__MKL26Z64__)
|
||||||
|
#define IO_REG_TYPE uint8_t
|
||||||
|
|
||||||
|
#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
|
||||||
|
#elif defined(__PIC32MX__)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
|
||||||
|
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
|
||||||
|
#elif defined(ARDUINO_ARCH_ESP32)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
#define IO_REG_MASK_ATTR
|
||||||
|
|
||||||
|
#elif defined(ARDUINO_ARCH_STM32)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
|
||||||
|
#elif defined(__SAMD21G18A__)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
|
||||||
|
#elif defined(__ASR6501__)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
|
||||||
|
#elif defined(RBL_NRF51822)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
|
||||||
|
#elif defined(__arc__) /* Arduino101/Genuino101 specifics */
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
|
||||||
|
#elif defined(__MBED__)
|
||||||
|
#include "DigitalInOut.h"
|
||||||
|
#define IO_REG_TYPE mbed::DigitalInOut*
|
||||||
|
|
||||||
|
#elif defined(__riscv)
|
||||||
|
#define IO_REG_TYPE uint32_t
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define IO_REG_TYPE unsigned int
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
Vendored Arduino libraries for P05 WROOM HAL only (not used on S3).
|
||||||
|
|
||||||
|
- AccelStepper 1.64 (waspinator)
|
||||||
|
- OneWire 2.3.8 (PaulStoffregen)
|
||||||
|
- DallasTemperature 3.9.1 (milesburton; PIO listed 3.11.0 which is not tagged upstream)
|
||||||
|
|
||||||
|
P06 WROOM UI:
|
||||||
|
|
||||||
|
- Keypad 3.1.1 (chris--a / Mark Stanley)
|
||||||
|
- LiquidCrystal_I2C (johnrickman / marcoschwartz 1.1.x API)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
idf_component_register(INCLUDE_DIRS "include")
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#define UI_CMD_QUEUE_LEN 8
|
||||||
|
#define UI_EVT_QUEUE_LEN 16
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CMD_SELECT_PROCESS = 1,
|
||||||
|
CMD_BROWSE_STEP = 2,
|
||||||
|
CMD_ADJUST_STEP_TIME = 3,
|
||||||
|
CMD_ARM_STEP = 4,
|
||||||
|
CMD_START_STEP = 5,
|
||||||
|
CMD_STOP = 6,
|
||||||
|
CMD_RESUME = 7,
|
||||||
|
CMD_RETURN_TO_STEP_SELECT = 8,
|
||||||
|
CMD_CANCEL_ARMED = 9,
|
||||||
|
CMD_PUMP_SET = 100,
|
||||||
|
CMD_VALVE_SET = 101,
|
||||||
|
CMD_RESERVOIR_SELECT = 102,
|
||||||
|
CMD_DRAIN_START = 103,
|
||||||
|
CMD_DRAIN_STOP = 104,
|
||||||
|
CMD_HEATER_SETPOINT = 105
|
||||||
|
} ui_cmd_id_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EVT_PROCESS_SELECTED = 1,
|
||||||
|
EVT_STEP_VIEW = 2,
|
||||||
|
EVT_STEP_ARMED = 3,
|
||||||
|
EVT_STEP_STARTED = 4,
|
||||||
|
EVT_STEP_PROGRESS = 5,
|
||||||
|
EVT_STEP_COMPLETE = 6,
|
||||||
|
EVT_STEP_STOPPED = 7,
|
||||||
|
EVT_STEP_RESUMED = 8,
|
||||||
|
EVT_PROCESS_IDLE = 9,
|
||||||
|
EVT_TEMP_UPDATED = 10,
|
||||||
|
EVT_FAULT = 11
|
||||||
|
} ui_evt_id_t;
|
||||||
|
|
||||||
|
typedef enum { MOTION_IDLE = 0, MOTION_CW = 1, MOTION_CCW = 2 } motion_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ui_cmd_id_t id;
|
||||||
|
uint8_t process_id;
|
||||||
|
int8_t step_delta; /* BrowseStep */
|
||||||
|
int8_t time_delta_s; /* AdjustStepTime, typically ±5 */
|
||||||
|
uint8_t step_index; /* Arm/Start */
|
||||||
|
} ui_cmd_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ui_evt_id_t id;
|
||||||
|
uint8_t process_id;
|
||||||
|
uint8_t step_index;
|
||||||
|
uint8_t step_count;
|
||||||
|
uint16_t time_s;
|
||||||
|
uint32_t remaining_ms;
|
||||||
|
float temp_c; /* NAN if disconnected */
|
||||||
|
bool temp_ok;
|
||||||
|
float temp_min_c, temp_pref_c, temp_max_c;
|
||||||
|
float cw_revs, ccw_revs;
|
||||||
|
motion_t motion;
|
||||||
|
int16_t fault_code;
|
||||||
|
} ui_evt_t;
|
||||||
+254
@@ -0,0 +1,254 @@
|
|||||||
|
# CI/CD — Gitea Actions
|
||||||
|
|
||||||
|
Status: planned on `refactor/esp-idf-modular-ui`. Implementation follows the IDF skeleton
|
||||||
|
(migration step 2 in `docs/TARGET_ARCHITECTURE.md`). Workflows live in **`.gitea/workflows/`**
|
||||||
|
(Gitea Actions, GitHub-compatible YAML). PRs target **`develop`**.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Every change that lands on `develop` must:
|
||||||
|
|
||||||
|
1. Run **host unit tests** for recipe tables and the machine state machine (no hardware).
|
||||||
|
2. **Build two firmware images**: `board_wroom` (ESP32 classic) and `board_jc4827w543` (ESP32-S3).
|
||||||
|
3. Publish flashable artifacts (app binary, bootloader, partition table, merged image, size report).
|
||||||
|
|
||||||
|
Legacy PlatformIO (`platformio.ini`) is not a CI target.
|
||||||
|
|
||||||
|
## Runner requirements
|
||||||
|
|
||||||
|
Gitea `act_runner` must be registered against `git.i3omb.com` with labels that match `runs-on`.
|
||||||
|
|
||||||
|
| Workflow `runs-on` | Typical runner label | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `ubuntu-latest` | `ubuntu-latest:docker://gitea/runner-images:ubuntu-latest` | Host tests, checkout, artifact upload. Image has **no cmake/gcc** — the test job must `apt-get install` them. |
|
||||||
|
| (job `container`) | Docker socket available on the runner | Firmware jobs use `espressif/idf:<tag>` as the job container |
|
||||||
|
|
||||||
|
Firmware builds need:
|
||||||
|
|
||||||
|
- Docker on the runner (job-level `container:`)
|
||||||
|
- Enough disk for the IDF image (~several GB) and two `build/` trees
|
||||||
|
- Outbound pull of `docker.io/espressif/idf` (or a mirror on the Gitea registry)
|
||||||
|
|
||||||
|
If the instance cannot pull Docker Hub, mirror `espressif/idf:v5.4` into the Gitea container registry and point `container.image` at that.
|
||||||
|
|
||||||
|
Do not depend on `espressif/esp-idf-ci-action` or other GitHub-hosted actions for the compile step. Those often assume GitHub path layout. Drive `idf.py` inside `espressif/idf` instead. `actions/checkout` and `actions/upload-artifact` (or Gitea’s equivalents) are fine.
|
||||||
|
|
||||||
|
## Triggers
|
||||||
|
|
||||||
|
| Event | What runs |
|
||||||
|
| --- | --- |
|
||||||
|
| `pull_request` into `develop` | tests + both firmware builds (required check) |
|
||||||
|
| `push` to `develop` | tests + both firmware builds; keep artifacts |
|
||||||
|
| `push` to `refactor/**` and `feature/**` | same, so the branch is green before the PR |
|
||||||
|
| `push` tags `v*` | tests + builds + attach binaries to the Gitea release |
|
||||||
|
| `workflow_dispatch` | manual rebuild |
|
||||||
|
|
||||||
|
`main` is not a CI integration branch for this refactor.
|
||||||
|
|
||||||
|
## Jobs
|
||||||
|
|
||||||
|
```
|
||||||
|
test (host) ──► firmware [wroom / jc4827w543] ──► (tag only) release
|
||||||
|
```
|
||||||
|
|
||||||
|
Firmware must `needs: test`. A failing recipe or state-machine test must not produce a binary.
|
||||||
|
|
||||||
|
### 1. `test` — host unit tests
|
||||||
|
|
||||||
|
Runs on `ubuntu-latest` **without** the IDF container so the gate is fast.
|
||||||
|
|
||||||
|
- Toolchain: CMake + GCC (C11), CTest.
|
||||||
|
- Tree: `tests/host/` linking `components/app_process` and `components/app_machine` against **stub HAL** (`tests/host/stubs/`: motor, temp, audio, display no-ops; queues replaced with a tiny fake or a vendored FreeRTOS POSIX port if the machine task is exercised).
|
||||||
|
- Framework: Unity (can be vendored) or plain `assert` + CTest. Prefer Unity so on-target tests later share the same style.
|
||||||
|
- Must cover:
|
||||||
|
- `find` / lookup of all six process names
|
||||||
|
- frozen default times, temps, CW/CCW for every step (values from `docs/CURRENT_STATE.md`)
|
||||||
|
- session ±5 s overlay does not mutate the const table
|
||||||
|
- state machine: Idle → Select → Arm → Start → Stop (EN disabled) → Resume → Complete → Return
|
||||||
|
- `Stop` is accepted in `StepRunning` and during a complete-alarm
|
||||||
|
- Recipe-value tests fail the build if defaults change without an explicit doc update.
|
||||||
|
|
||||||
|
Command sketch:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -S tests/host -B build/host
|
||||||
|
cmake --build build/host
|
||||||
|
ctest --test-dir build/host --output-on-failure
|
||||||
|
```
|
||||||
|
|
||||||
|
Until `tests/host` exists, the workflow file is not merged as a required check — or the job is `if`’d on path existence. Do not ship a red pipeline on docs-only commits after the workflow is enabled; use:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Host tests
|
||||||
|
if: hashFiles('tests/host/CMakeLists.txt') != ''
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `firmware` — matrix build
|
||||||
|
|
||||||
|
Job container: `espressif/idf:v5.4` (pin the tag; bump in one place when IDF is upgraded).
|
||||||
|
|
||||||
|
Matrix:
|
||||||
|
|
||||||
|
| Name | `IDF_TARGET` | Board cmake/sdkconfig |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `wroom` | `esp32` | `-DAUTOFILM_BOARD=wroom` + `sdkconfig.defaults` + `sdkconfig.wroom` |
|
||||||
|
| `jc4827w543` | `esp32s3` | `-DAUTOFILM_BOARD=jc4827w543` + `sdkconfig.defaults` + `sdkconfig.s3` |
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
1. Checkout (full history not required).
|
||||||
|
2. `source /opt/esp/idf/export.sh` (already true in the official image entrypoint; use `idf.py` directly).
|
||||||
|
3. `idf.py set-target ${IDF_TARGET}` then
|
||||||
|
`idf.py @sdkconfig.defaults @sdkconfig.<board> -DAUTOFILM_BOARD=<board> build`
|
||||||
|
4. `idf.py size` (fail the job only if the binary will not fit flash; log the report either way).
|
||||||
|
5. Merge a single flash image:
|
||||||
|
`esptool.py --chip ${IDF_TARGET} merge_bin -o autofilm-${board}-${GIT_SHA}.bin @build/flash_args`
|
||||||
|
6. Upload artifacts (see below).
|
||||||
|
|
||||||
|
S3 build must enable octal PSRAM in `sdkconfig.s3`. WROOM must not.
|
||||||
|
|
||||||
|
Arduino-as-component is pulled by the IDF CMake on the WROOM image first; the S3 image may share it until debt burn-down. Cache `managed_components/` by lock hash when a `idf_component.yml` exists.
|
||||||
|
|
||||||
|
### 3. `release` (tags only)
|
||||||
|
|
||||||
|
`needs: [test, firmware]`. Download both artifact sets and attach to the Gitea release for tag `v*`:
|
||||||
|
|
||||||
|
- `autofilm-wroom-<version>.bin` (merged)
|
||||||
|
- `autofilm-jc4827w543-<version>.bin` (merged)
|
||||||
|
- raw `app.bin` / `bootloader.bin` / `partition-table.bin` per board
|
||||||
|
- `sdkconfig` snapshot per board
|
||||||
|
- `size.txt` per board
|
||||||
|
|
||||||
|
Version string is the git tag. CI also stamps `version` in firmware via `-DAUTOFILM_GIT_DESC=$(git describe --always --dirty)`.
|
||||||
|
|
||||||
|
## Artifacts
|
||||||
|
|
||||||
|
| File | Why |
|
||||||
|
| --- | --- |
|
||||||
|
| `autofilm-<board>-<sha>.bin` | Merged image for `esptool write_flash 0x0` |
|
||||||
|
| `build/autofilm.bin` (app) | OTA / partial flash |
|
||||||
|
| `build/bootloader/bootloader.bin` | First-time flash |
|
||||||
|
| `build/partition_table/partition-table.bin` | First-time flash |
|
||||||
|
| `build/flasher_args.json` | Exact offsets |
|
||||||
|
| `size.txt` / `size.json` | Flash/RAM budget |
|
||||||
|
| `sdkconfig` | Repro the build flags |
|
||||||
|
|
||||||
|
Retention: 14 days on branch builds, keep release attachments. Artifact names must include board + short SHA so WROOM and S3 cannot overwrite each other.
|
||||||
|
|
||||||
|
## Example workflow (target shape)
|
||||||
|
|
||||||
|
Path: `.gitea/workflows/ci.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [develop]
|
||||||
|
push:
|
||||||
|
branches: [develop, "refactor/**", "feature/**"]
|
||||||
|
tags: ["v*"]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- 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
|
||||||
|
- name: Host unit tests
|
||||||
|
if: hashFiles('tests/host/CMakeLists.txt') != ''
|
||||||
|
run: |
|
||||||
|
cmake -S tests/host -B build/host
|
||||||
|
cmake --build build/host
|
||||||
|
ctest --test-dir build/host --output-on-failure
|
||||||
|
|
||||||
|
firmware:
|
||||||
|
needs: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: docker.io/espressif/idf:v5.4
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- board: wroom
|
||||||
|
target: esp32
|
||||||
|
sdkconfig: sdkconfig.wroom
|
||||||
|
- board: jc4827w543
|
||||||
|
target: esp32s3
|
||||||
|
sdkconfig: sdkconfig.s3
|
||||||
|
steps:
|
||||||
|
- 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.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
|
||||||
|
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
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: hashFiles('CMakeLists.txt') != ''
|
||||||
|
with:
|
||||||
|
name: firmware-${{ matrix.board }}
|
||||||
|
path: |
|
||||||
|
autofilm-${{ matrix.board }}-*.bin
|
||||||
|
build/${{ matrix.board }}/*.bin
|
||||||
|
build/${{ matrix.board }}/bootloader/bootloader.bin
|
||||||
|
build/${{ matrix.board }}/partition_table/partition-table.bin
|
||||||
|
build/${{ matrix.board }}/flasher_args.json
|
||||||
|
build/${{ matrix.board }}/sdkconfig
|
||||||
|
size-${{ matrix.board }}.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Context substitutions: Gitea accepts both `github.*` and `gitea.*` in many versions; prefer `gitea.sha` / `gitea.ref` and fall back if the runner is older.
|
||||||
|
|
||||||
|
`hashFiles` guards keep docs-only stages of this branch from going red before the IDF tree exists.
|
||||||
|
|
||||||
|
## Required status on PRs
|
||||||
|
|
||||||
|
Once the IDF skeleton compiles, mark on the `develop` branch protection (Gitea repo settings):
|
||||||
|
|
||||||
|
- `test`
|
||||||
|
- `firmware (wroom)`
|
||||||
|
- `firmware (jc4827w543)`
|
||||||
|
|
||||||
|
No merge to `develop` with a red firmware matrix.
|
||||||
|
|
||||||
|
## Caching (optional, second pass)
|
||||||
|
|
||||||
|
- Cache `~/.cache/pip` and IDF tools if not using the pre-baked `espressif/idf` image.
|
||||||
|
- Cache `managed_components/` keyed on `idf_component.yml` + `idf.lock`.
|
||||||
|
- Do not cache `build/` across boards or targets.
|
||||||
|
|
||||||
|
## Secrets
|
||||||
|
|
||||||
|
CI must not need device tokens or Wi-Fi credentials. Firmware builds are offline-configurable. If a later job publishes to a hardware farm, use Gitea secrets — never commit them.
|
||||||
|
|
||||||
|
## Local equivalent
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
|
||||||
|
|
||||||
|
docker run --rm -v "$PWD":/project -w /project espressif/idf:v5.4 \
|
||||||
|
bash -lc 'idf.py -B build/wroom set-target esp32 && idf.py -B build/wroom -D AUTOFILM_BOARD=wroom build'
|
||||||
|
```
|
||||||
|
|
||||||
|
Agents must run the host tests before pushing once `tests/host` exists.
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
# AutoFilm-ESP32 — Current State
|
||||||
|
|
||||||
|
Snapshot of the pre-refactor PlatformIO Arduino tree (`9186d84`).
|
||||||
|
Integration going forward is **`develop`**; this refactor lands via PR from `refactor/esp-idf-modular-ui` into `develop`.
|
||||||
|
This document describes the tree **as it existed at that snapshot**, before the ESP-IDF modular refactor. Recipe *values* (times, temperatures, rotation counts) are treated as frozen; this document does not propose changing them.
|
||||||
|
|
||||||
|
## What the product is
|
||||||
|
|
||||||
|
AutoFilm-ESP32 is a Kindermann-style rotary film processor controller. The operator picks a process (C41, E6, B&W, ECN-2, Custom, B&WREV), walks the chemical steps, starts a timed agitation cycle, and is prompted to pour / drain between steps. Temperature is *measured* only; closed-loop bath heat is an external sous-vide.
|
||||||
|
|
||||||
|
## Toolchain and layout
|
||||||
|
|
||||||
|
| Item | Today |
|
||||||
|
| --- | --- |
|
||||||
|
| Build | PlatformIO `platformio.ini`, env `esp32dev` |
|
||||||
|
| Framework | Arduino on Espressif32 |
|
||||||
|
| Target | Classic ESP32-WROOM (`board = esp32dev`) |
|
||||||
|
| Layout | `src/*.cpp`, `include/*.h`, Arduino-style `setup()` / `loop()` |
|
||||||
|
| Entry | `src/AutoFilmESP32.cpp` |
|
||||||
|
|
||||||
|
Arduino libraries (all treated as future debt):
|
||||||
|
|
||||||
|
- `AccelStepper` — STEP/DIR stepper
|
||||||
|
- `LiquidCrystal_I2C` — 20×4 character LCD
|
||||||
|
- `Keypad` — matrix scan
|
||||||
|
- `OneWire` + `DallasTemperature` — DS18B20
|
||||||
|
|
||||||
|
There is **no** ESP-IDF `CMakeLists.txt`, no `sdkconfig`, no component split, no CI matrix.
|
||||||
|
|
||||||
|
## Hardware as wired in this tree
|
||||||
|
|
||||||
|
From `include/config.h` and `src/config.cpp`:
|
||||||
|
|
||||||
|
| Function | GPIO / bus |
|
||||||
|
| --- | --- |
|
||||||
|
| Stepper STEP (pulse) | 12 |
|
||||||
|
| Stepper DIR | 14 |
|
||||||
|
| Stepper EN (active LOW) | 27 |
|
||||||
|
| DS18B20 data | 13 |
|
||||||
|
| Beeper | 25 |
|
||||||
|
| LCD I²C | SDA 21, SCL 22, address `0x27`, 20×4 |
|
||||||
|
| Keypad rows | 19, 18, 5, 17, 16 |
|
||||||
|
| Keypad cols | 15, 2, 0, 4 |
|
||||||
|
|
||||||
|
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):
|
||||||
|
|
||||||
|
```
|
||||||
|
F E # *
|
||||||
|
1 2 3 U
|
||||||
|
4 5 6 D
|
||||||
|
7 8 9 X ← 4 across, 4 down = Esc ('X')
|
||||||
|
L 0 R E
|
||||||
|
```
|
||||||
|
|
||||||
|
Keys `1`–`6` select programmes. `U`/`D` browse steps. `L`/`R` nudge step time ±5 s. `E` confirm. `X` escape.
|
||||||
|
|
||||||
|
## Module map
|
||||||
|
|
||||||
|
```
|
||||||
|
AutoFilmESP32.cpp setup/loop, LCD splash, watchdog off
|
||||||
|
config.cpp / .h globals, pin map, library objects
|
||||||
|
devSequence.cpp/.h six compiled-in recipes
|
||||||
|
menu.cpp / .h blocking menus + startProcessing()
|
||||||
|
display.cpp / .h starting menu, headings, mm:ss helper
|
||||||
|
temperature.cpp/.h DS18B20 read + LCD write
|
||||||
|
motor.cpp / .h FreeRTOS agitation task
|
||||||
|
sound.cpp / .h blocking tone() tunes
|
||||||
|
watchdog.h disables TWDT and idle-task WDT
|
||||||
|
```
|
||||||
|
|
||||||
|
There is no HAL. UI, recipes, and actuators share globals (`devPgm`, `run`, `lcd`, `stepper`, `keypad`, task handles).
|
||||||
|
|
||||||
|
## Recipe data
|
||||||
|
|
||||||
|
`struct devSequence` (`include/devSequence.h`):
|
||||||
|
|
||||||
|
- `processName[7]`
|
||||||
|
- `cycles` — number of steps
|
||||||
|
- `processTime[20]` — seconds
|
||||||
|
- `processCycleName[20][10]`
|
||||||
|
- `processCycle[2][20]` — `[0]` CW revolutions, `[1]` CCW revolutions
|
||||||
|
- `processTemp[3][20]` — `[0]` min, `[1]` preferred, `[2]` max °C
|
||||||
|
|
||||||
|
`NUM_DEV_SEQUENCES = 6`. Lookup is linear `strcmp` in `findSequenceByName()`.
|
||||||
|
|
||||||
|
### Compiled defaults (do not change values)
|
||||||
|
|
||||||
|
**C41** — 7 steps, ~38 °C developer:
|
||||||
|
|
||||||
|
| Step | Time s | Name | CW | CCW | Min | Pref | Max |
|
||||||
|
| --- | ---: | --- | ---: | ---: | ---: | ---: | ---: |
|
||||||
|
| 0 | 180 | Prewarm | 1 | 1 | 37.8 | 38 | 38.2 |
|
||||||
|
| 1 | 195 | Developer | 5.5 | 5 | 37.8 | 38 | 38.2 |
|
||||||
|
| 2 | 45 | Bleach | 5.5 | 5 | 32 | 38 | 38.2 |
|
||||||
|
| 3 | 180 | Fix | 5.5 | 5 | 32 | 38 | 38.2 |
|
||||||
|
| 4 | 60 | Rinse 1 | 3.5 | 3 | 32 | 38 | 38.2 |
|
||||||
|
| 5 | 60 | Rinse 2 | 3.5 | 3 | 32 | 38 | 38.2 |
|
||||||
|
| 6 | 30 | Fin Rinse | 1 | 1 | 32 | 38 | 38.2 |
|
||||||
|
|
||||||
|
**E6** — 12 steps (Preheat … Fin Rinse). FirstDev 360 s at 38 °C; ColorDev 360 s; Fin Rinse 30 s at 19–21 °C.
|
||||||
|
|
||||||
|
**ECN-2** — 9 steps. RemJet is a 0 s / 0 rotation placeholder (manual). Developer 210 s at 40.8–41.2 °C.
|
||||||
|
|
||||||
|
**B&W** — 7 steps. Developer 510 s at 19–21 °C.
|
||||||
|
|
||||||
|
**Custom** — 4×10 s stub (Developer / Stop / Fix / Rinse).
|
||||||
|
|
||||||
|
**B&WREV** — 12 steps. FirstDev 720 s; SecondDev 360 s; wash steps cooler (min 15.5 °C).
|
||||||
|
|
||||||
|
Left/right time edits mutate the in-RAM struct. They are **not** written to flash. Process names longer than 6 characters would not fit `processName[7]`.
|
||||||
|
|
||||||
|
## Runtime behaviour
|
||||||
|
|
||||||
|
1. `setup()`: Serial 115200, **watchdogs disabled**, I²C + LCD init, custom thermometer glyph, motor disabled, splash `AUTOFILM` + `V0.1.1 20240701` for 1 s.
|
||||||
|
2. `loop()` calls `startingMenu()` and never returns to a real idle loop while a programme is selected.
|
||||||
|
3. `startingMenu()` draws 1–6 and **busy-waits** until `devPgm != ""`.
|
||||||
|
4. `startDev()` walks `cycles`. If `run == 1` it chains `startProcessing()` for the next step (auto-advance after a completed step). Otherwise it shows Step / Time / Temp and waits for U/D/E/X/L/R.
|
||||||
|
5. `startProcessing()`:
|
||||||
|
- Shows step name, duration, preferred temp, `Ent:start Esc:quit`.
|
||||||
|
- Esc (`X`) here aborts **before** motion and returns 0.
|
||||||
|
- On Enter, records `processStartTime` / `processTimeMillis`, heap-allocates `MotorTaskParams`, and `xTaskCreatePinnedToCore(runMotorTask, …, core 0)`.
|
||||||
|
- Then **blocks** in `while (millis() < end) { update remaining; delay(410); readTemperature(); }`.
|
||||||
|
- Deletes the motor task, drives EN HIGH, plays a **blocking 10-beep alarm** (~7.5 s), frees params.
|
||||||
|
|
||||||
|
`runMotorTask` enables the driver and loops forever: CW `cwRotations` revs, then CCW `ccwRotations` revs, using `stepper.run()` busy-wait on each move. `processEndTime` is passed in and **never read**. The task does not check a stop flag. It only ends when the UI task `vTaskDelete`s it.
|
||||||
|
|
||||||
|
Temperature: `sensors.requestTemperatures()` + `getTempCByIndex(0) + 0.4 °C` offset, written to LCD row 3. Called from the UI wait loops. `updateTempDisplay(void *)` exists as a one-shot task that also talks to the LCD and then deletes itself; it is unused from `startProcessing()`.
|
||||||
|
|
||||||
|
Sound: `tone()` + `delay()` on the caller thread. `playTune()` is unused by the main flow.
|
||||||
|
|
||||||
|
## Concurrency as it stands
|
||||||
|
|
||||||
|
| Thread | Role | Problem |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Arduino `loopTask` | All UI, all input, all timing, temp, sound | Blocks for seconds to minutes |
|
||||||
|
| `MotorTask` (core 0) | Agitation | No cooperative stop; killed from outside |
|
||||||
|
| Idle 0/1 | — | Removed from TWDT |
|
||||||
|
|
||||||
|
LCD, `stepper`, and `sensors` are used without mutexes. That is safe only because the UI thread does not command the stepper while the motor task runs — except `vTaskDelete` can interrupt `stepper.run()` mid-pulse.
|
||||||
|
|
||||||
|
## Defects and hazards relevant to the refactor
|
||||||
|
|
||||||
|
1. **No stop while a step is running.** Esc is honoured only on the pre-start prompt. The run loop does not read the keypad. This violates the product rule that the machine must always stop on Esc.
|
||||||
|
2. **`vTaskDelete` of a live motor task** can leave STEP/DIR in an undefined state; EN is dropped only *after* delete.
|
||||||
|
3. **Watchdog deliberately off** (`include/watchdog.h`). A hung `stepper.run()` or OneWire slot will wedge the chip with no recovery.
|
||||||
|
4. **Busy-wait input** (`while (true)` + `keypad.getKey()`). `millis() % 1000 == 0` is not a 1 Hz tick; it is a one-millisecond race and can skip or repeat temp reads.
|
||||||
|
5. **`secondsToMinutesSeconds()` `malloc`s 6 bytes and never frees.** Called from the run loop ~twice per second.
|
||||||
|
6. **`startDev()` shadows the global** with `int run = 1;` on Enter, so the auto-advance path is inconsistent with the `run` flag used after `startProcessing()` returns.
|
||||||
|
7. **OneWire conversion blocks the UI** (typically 750 ms at 12-bit). Combined with `delay(410)` the remaining-time display stutters.
|
||||||
|
8. **Alarm after a step blocks everything** for several seconds; Esc cannot interrupt it.
|
||||||
|
9. **No input abstraction.** Touch cannot be added without rewriting `menu.cpp`.
|
||||||
|
10. **Board pins are compile-time literals.** The Guition S3 panel reuses many of these GPIOs for QSPI / GT911; the current map cannot be copied across.
|
||||||
|
|
||||||
|
## What is *not* in the tree
|
||||||
|
|
||||||
|
- ESP-IDF project or dual-firmware CI
|
||||||
|
- Touch / colour LCD support
|
||||||
|
- Pause / resume of a live step
|
||||||
|
- Persistent profiles (README already lists this as future)
|
||||||
|
- Pumps, valves, reservoirs, drain actuators
|
||||||
|
- Closed-loop heater control
|
||||||
|
- Unit tests beyond PlatformIO’s empty `test/README`
|
||||||
|
|
||||||
|
## README vs code
|
||||||
|
|
||||||
|
`README.MD` matches the hardware story (ESP32, NEMA-17, DS18B20, 2004 I²C, Kindermann tank) and the pour-start-drain loop. It still talks about opening the project in Arduino IDE / PlatformIO and does not mention FreeRTOS, the S3 panel, or stop-during-run.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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).
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
# AutoFilm-ESP32 — Target Architecture
|
||||||
|
|
||||||
|
Status: proposed on `refactor/esp-idf-modular-ui`, for review via PR into **`develop`**.
|
||||||
|
Does not change default process times, temperatures, or rotation patterns.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
1. Rebuild as an **ESP-IDF** project that still links **Arduino-as-component** so AccelStepper, Keypad, LiquidCrystal_I2C, OneWire, and DallasTemperature keep working in the first cut.
|
||||||
|
2. Produce **two firmware images in CI**: classic ESP32-WROOM (2004 + keypad) and Guition **JC4827W543C** (ESP32-S3-WROOM-1-N4R8, NV3041A 480×272 QSPI, GT911 capacitive touch).
|
||||||
|
3. Split **UI / input** from **mechatronics** so a touch panel can replace the character LCD without rewriting agitation or recipes.
|
||||||
|
4. Use FreeRTOS tasks, queues, and interrupts so the UI never blocks on motor moves, OneWire conversions, or tones.
|
||||||
|
5. **Always stop motion on Esc** (keypad position 4 across / 4 down, key `'X'`) and, later, on a touch STOP control. Same command path for both.
|
||||||
|
6. Keep Arduino libraries isolated behind HAL files; replacing them with native IDF drivers is planned debt, not this document’s implementation phase.
|
||||||
|
7. Leave room for pumps, valves, reservoirs, and drain without breaking the command/event contract.
|
||||||
|
|
||||||
|
Arduino-as-component is **explicit technical debt**. New modules must not add Arduino types to public headers.
|
||||||
|
|
||||||
|
## Non-goals (this refactor)
|
||||||
|
|
||||||
|
- Changing C41 / E6 / ECN-2 / B&W / Custom / B&WREV default numbers.
|
||||||
|
- A real Custom-programme editor (session ±5 s only; NVS profiles later).
|
||||||
|
- Closed-loop bath heating.
|
||||||
|
- Implementing pumps/valves now — only the extension points.
|
||||||
|
|
||||||
|
## Dual board model
|
||||||
|
|
||||||
|
| | `board_wroom` | `board_jc4827w543` |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| SoC | ESP32-WROOM (classic) | ESP32-S3-WROOM-1-N4R8 (4 MB flash, 8 MB octal PSRAM) |
|
||||||
|
| UI | 20×4 I²C LCD `0x27` + 5×4 keypad | 4.3" IPS 480×272 NV3041A QSPI + GT911 I²C |
|
||||||
|
| Touch | none | GT911, typical pins SDA 8, SCL 4, INT 3, RST 38, addr `0x5D` |
|
||||||
|
| Display bus | I²C 21/22 | QSPI CS 45, SCK 47, D0–D3 21/48/40/39, backlight GPIO 1 |
|
||||||
|
| Mechatronics | current pin map | **remapped**; panel consumes most GPIOs, ~10 usable extras + headers |
|
||||||
|
|
||||||
|
Pin maps live only in board components. Application code sees `hal_motor_enable()`, not `GPIO_NUM_27`.
|
||||||
|
|
||||||
|
CI builds:
|
||||||
|
|
||||||
|
```
|
||||||
|
idf.py -D AUTOFILM_BOARD=wroom @sdkconfig.defaults @sdkconfig.wroom
|
||||||
|
idf.py -D AUTOFILM_BOARD=jc4827w543 @sdkconfig.defaults @sdkconfig.s3
|
||||||
|
```
|
||||||
|
|
||||||
|
Automated by Gitea Actions — see `docs/CICD.md`. Both images are required checks on PRs into `develop`.
|
||||||
|
|
||||||
|
WROOM remains the machine that already exists. S3 firmware is the future operator panel; mechatronics may stay on the WROOM or move when the S3 I/O map is proven. The **same** `app_machine` binary interface is compiled for both.
|
||||||
|
|
||||||
|
Vendor references for the S3 panel (not copied into this repo): [lsdlsd88/JC4827W543](https://github.com/lsdlsd88/JC4827W543), [profi-max board notes](https://github.com/profi-max/JC4827W543_4.3inch_ESP32S3_board) — NV3041A + GT911, 480×272.
|
||||||
|
|
||||||
|
## Proposed tree
|
||||||
|
|
||||||
|
Existing `src/` and `include/` stay until each file is moved. New code lands here:
|
||||||
|
|
||||||
|
```
|
||||||
|
AGENTS.md
|
||||||
|
docs/
|
||||||
|
CURRENT_STATE.md
|
||||||
|
TARGET_ARCHITECTURE.md
|
||||||
|
main/ # idf app: NVS, task spawn, board select
|
||||||
|
components/
|
||||||
|
app_process/ # recipe table + lookup + session time overrides
|
||||||
|
app_machine/ # process state machine (no display types)
|
||||||
|
app_ui/ # screens + command emission (no stepper types)
|
||||||
|
ui_cmd/ # command / event structs only
|
||||||
|
hal_motor/
|
||||||
|
hal_temp/
|
||||||
|
hal_input/ # keypad + (later) GT911 → ui_event
|
||||||
|
hal_display/ # 2004 adapter; later NV3041A adapter
|
||||||
|
hal_audio/
|
||||||
|
board_wroom/
|
||||||
|
board_jc4827w543/
|
||||||
|
managed_components/ # Arduino-esp32 component, IDF drivers
|
||||||
|
```
|
||||||
|
|
||||||
|
Public headers of `app_*` and `ui_cmd` are C (or `extern "C"`) and Arduino-free.
|
||||||
|
|
||||||
|
## Command / event contract
|
||||||
|
|
||||||
|
UI never starts the motor. UI posts commands. Machine never writes pixels. Machine posts events.
|
||||||
|
|
||||||
|
### Commands (`ui → machine`)
|
||||||
|
|
||||||
|
| Command | Now | Later |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `SelectProcess(id)` | keypad 1–6 | touch tile |
|
||||||
|
| `BrowseStep(delta)` | U/D | swipe / list |
|
||||||
|
| `AdjustStepTime(step, delta_s)` | L/R ±5 | stepper buttons |
|
||||||
|
| `ArmStep(step)` | show Ent/Esc prompt | confirm sheet |
|
||||||
|
| `StartStep(step)` | Enter | START |
|
||||||
|
| `Stop` | Esc during run | STOP |
|
||||||
|
| `Resume` | after Stop | RESUME |
|
||||||
|
| `ReturnToStepSelect` | after Stop | BACK |
|
||||||
|
| `CancelArmed` | Esc before start | cancel |
|
||||||
|
|
||||||
|
Reserved, not implemented:
|
||||||
|
|
||||||
|
`PumpSet`, `ValveSet`, `ReservoirSelect`, `DrainStart`, `DrainStop`, `HeaterSetpoint`.
|
||||||
|
|
||||||
|
Adding an actuator is a new command + HAL + machine state. It must not require `app_ui` to include motor headers.
|
||||||
|
|
||||||
|
### Events (`machine → ui`)
|
||||||
|
|
||||||
|
| Event | Payload |
|
||||||
|
| --- | --- |
|
||||||
|
| `ProcessSelected` | process id, name, step count |
|
||||||
|
| `StepView` | index, name, time_s, temp pref/min/max, cw, ccw |
|
||||||
|
| `StepArmed` | same |
|
||||||
|
| `StepStarted` | start tick, duration_ms |
|
||||||
|
| `StepProgress` | remaining_ms, temp_c, motion {cw\|ccw\|idle} |
|
||||||
|
| `StepComplete` | index |
|
||||||
|
| `StepStopped` | remaining_ms, index |
|
||||||
|
| `StepResumed` | remaining_ms |
|
||||||
|
| `ProcessIdle` | — |
|
||||||
|
| `TempUpdated` | °C or disconnected |
|
||||||
|
| `Fault` | code, message |
|
||||||
|
|
||||||
|
Required on-screen fields while running: **temperature, step name, time remaining**. Motion direction is optional. STOP / RESUME / BACK are controls, not telemetry.
|
||||||
|
|
||||||
|
### Transport
|
||||||
|
|
||||||
|
One FreeRTOS queue each way (or a tiny pub/sub wrapping `xQueueSend`). Bounded structs, no `String`. ISR context may only `xQueueSendFromISR` a `Stop` or raw key/touch sample.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────┐ cmd_q ┌──────────────┐ actuator calls ┌──────────┐
|
||||||
|
│ app_ui │ ─────────► │ app_machine │ ─────────────────► │ hal_* │
|
||||||
|
│ + hal_in │ ◄───────── │ + app_process│ ◄── temp / done │ │
|
||||||
|
└────────────┘ evt_q └──────────────┘ └──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## State machine
|
||||||
|
|
||||||
|
```
|
||||||
|
Idle → ProcessSelected → StepSelect ⇄ StepArmed
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
StepRunning ◄── Resume
|
||||||
|
│ Stop
|
||||||
|
▼
|
||||||
|
StepStopped → StepSelect
|
||||||
|
│ timer done
|
||||||
|
▼
|
||||||
|
StepComplete → StepSelect (or auto-advance policy later)
|
||||||
|
```
|
||||||
|
|
||||||
|
`run == 1` auto-chaining from the current `startDev()` is preserved as a **machine policy flag** (`auto_advance`), not as UI blocking. Default: after `StepComplete`, arm the next step and wait for Start (safer with pour/drain). If we must match today’s “immediately start the next step after the alarm”, that is one flag, not a second code path.
|
||||||
|
|
||||||
|
### Stop / resume (product rule)
|
||||||
|
|
||||||
|
- **Stop** = immediately end the agitation routine, **disable the motor driver**, keep the step and remaining time, show Resume or Return to step select.
|
||||||
|
- **Resume** = continue the **same step** for the remaining time with the same CW/CCW pattern. Short beep.
|
||||||
|
- **Return** = leave the step stopped, motor disabled, back to step list. Remaining time for that step stays at whatever was left (session override), unless we later decide to snap back to nominal — default is keep remaining.
|
||||||
|
- Esc **before** Start stays “cancel arm”, not Stop.
|
||||||
|
- Short beep on Stop and on Resume. The current 10-beep end-of-step alarm becomes a non-blocking audio request on `StepComplete`.
|
||||||
|
|
||||||
|
Esc must work in `StepRunning`, `StepStopped` (as Back), and during the complete-alarm.
|
||||||
|
|
||||||
|
## Tasks, cores, interrupts
|
||||||
|
|
||||||
|
Watchdogs stay **on**. Every long-lived task calls `esp_task_wdt_reset()` or is not on the TWDT.
|
||||||
|
|
||||||
|
| Task | Priority (relative) | Work |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `input_task` | high | Keypad scan ~20–50 Hz; GT911 read on INT or poll. Debounce. Map to commands. |
|
||||||
|
| `ui_task` | mid | Consume events, draw dirty regions only. Never `delay` for motor or OneWire. |
|
||||||
|
| `machine_task` | mid-high | State machine, timers via `vTaskDelayUntil` / `esp_timer`. |
|
||||||
|
| `motor_task` | high | Agitation pattern. Waits on notifications, not `vTaskDelete`. |
|
||||||
|
| `temp_task` | low | DS18B20: kick conversion, wait conversion time, publish `TempUpdated`. |
|
||||||
|
| `audio_task` | low | Short beeps / alarm pattern from a queue. |
|
||||||
|
|
||||||
|
Arduino `loop()` becomes a thin idle (or is not used). `setup()` only starts IDF tasks.
|
||||||
|
|
||||||
|
### Stop path (must be low latency)
|
||||||
|
|
||||||
|
1. Keypad Esc or GT911 hit-test on STOP is recognized in `input_task` or, for the matrix Esc line if we later wire a dedicated GPIO, in a GPIO ISR.
|
||||||
|
2. `Stop` is queued to `machine_task` **and** `hal_motor_request_stop()` runs:
|
||||||
|
- set an atomic `stop_req`
|
||||||
|
- drive **EN HIGH immediately** (driver off)
|
||||||
|
- `xTaskNotify` the motor task to abandon the current move
|
||||||
|
3. Motor task leaves `stepper.run()` at the next loop check (or aborts the RMT transaction in the IDF driver) and parks.
|
||||||
|
4. Machine publishes `StepStopped` with remaining time. UI shows Resume / Back.
|
||||||
|
|
||||||
|
EN-off is the safety action. It does not need a new motor-driver IC.
|
||||||
|
|
||||||
|
### Question 8, resolved: RMT / MCPWM vs AccelStepper
|
||||||
|
|
||||||
|
No extra power electronics. The existing STEP/DIR driver stays.
|
||||||
|
|
||||||
|
Today AccelStepper **busy-polls** `stepper.run()` on a CPU core to generate step edges. That works, but:
|
||||||
|
|
||||||
|
- Stop latency is “next poll of `distanceToGo`”, and `vTaskDelete` is unsafe mid-pulse.
|
||||||
|
- The core cannot sleep; jitter depends on competing work.
|
||||||
|
|
||||||
|
ESP32 **RMT** (or MCPWM + a step counter) can emit the pulse train in hardware. The CPU only queues “N steps this direction”. Stop then is: abort the RMT TX + drop EN, which is cleaner.
|
||||||
|
|
||||||
|
**Phase 1:** keep AccelStepper behind `hal_motor`, but add `stop_req` checks inside the inner `run()` loop and never `vTaskDelete` the task.
|
||||||
|
**Phase 2 (debt burn-down):** replace AccelStepper with an IDF RMT stepper. Same `hal_motor` API.
|
||||||
|
|
||||||
|
## HAL sketches
|
||||||
|
|
||||||
|
```c
|
||||||
|
void hal_motor_init(void);
|
||||||
|
void hal_motor_enable(bool on);
|
||||||
|
void hal_motor_request_stop(void); // ISR-safe: EN off + flag
|
||||||
|
esp_err_t hal_motor_move_revs(float revs, int dir, uint32_t rpm);
|
||||||
|
bool hal_motor_is_busy(void);
|
||||||
|
|
||||||
|
void hal_temp_init(void);
|
||||||
|
esp_err_t hal_temp_read_c(float *out); // blocking OK — temp_task only
|
||||||
|
|
||||||
|
typedef enum { UI_KEY, UI_TOUCH } ui_src_t;
|
||||||
|
void hal_input_init(void);
|
||||||
|
bool hal_input_pop(ui_raw_event_t *ev);
|
||||||
|
|
||||||
|
void hal_display_init(void);
|
||||||
|
void hal_display_clear(void);
|
||||||
|
void hal_display_text(int col, int row, const char *s); // 2004 mapping
|
||||||
|
void hal_display_flush(void); // no-op on 2004
|
||||||
|
|
||||||
|
void hal_audio_beep_short(void);
|
||||||
|
void hal_audio_alarm_complete(void);
|
||||||
|
```
|
||||||
|
|
||||||
|
Colour UI implements a richer draw API *or* LVGL flush, still inside `hal_display` / `app_ui`, never inside `app_machine`.
|
||||||
|
|
||||||
|
## UI strategy: 2004 now, touch without a rewrite
|
||||||
|
|
||||||
|
`app_ui` is a set of **screens** (`ProgramSelect`, `StepSelect`, `Armed`, `Running`, `Stopped`) that read the last event snapshot and emit commands.
|
||||||
|
|
||||||
|
- **WROOM adapter:** screens print 20×4 strings. Input adapter translates `'1'`…`'X'` into commands.
|
||||||
|
- **S3 adapter:** same screens, different renderer.
|
||||||
|
|
||||||
|
### LVGL vs custom (recommendation)
|
||||||
|
|
||||||
|
The JC4827W543 community stack is Arduino_GFX + LVGL + TAMC_GT911 / TouchLib. LVGL will render a pretty 480×272 panel and is already demoed on this exact board.
|
||||||
|
|
||||||
|
For AutoFilm the visible surface is small: six programmes, a step list, one run view (name / remaining / temp), Stop / Resume / Back. That is a few dirty-rectangle updates per second.
|
||||||
|
|
||||||
|
**Recommendation: custom screens first, LVGL optional later.**
|
||||||
|
|
||||||
|
- Custom keeps the UI task tiny, avoids 4 MB-flash + PSRAM fight with LVGL v8/v9 buffers, and does not force Arduino_GFX deeper into the tree.
|
||||||
|
- LVGL widgets are worth it when we have graphs, profile editors, or multi-language layouts. Those are out of scope.
|
||||||
|
- If a first S3 prototype is faster with vendor LVGL demos, isolate LVGL in `board_jc4827w543` + `app_ui` and **do not** put `lv_*` types in `ui_cmd` or `app_machine`.
|
||||||
|
- Either way: LVGL `flush_cb` and touch read run on `ui_task` / `input_task` only; no `lv_timer_handler()` on `machine_task`.
|
||||||
|
|
||||||
|
Touch STOP is a hit-test that emits the same `Stop` command as Esc.
|
||||||
|
|
||||||
|
## Process data
|
||||||
|
|
||||||
|
Keep the six recipes and their numeric defaults. The storage shape may change.
|
||||||
|
|
||||||
|
Proposed:
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
|
uint8_t step_count;
|
||||||
|
const process_step_t *steps; /* flash / const */
|
||||||
|
} process_def_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
|
uint16_t time_s;
|
||||||
|
float cw_revs;
|
||||||
|
float ccw_revs;
|
||||||
|
float temp_min_c;
|
||||||
|
float temp_pref_c;
|
||||||
|
float temp_max_c;
|
||||||
|
} process_step_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
Session overlay: `int16_t time_delta_s[step]` in RAM (L/R ±5). Not persisted. NVS profile slot is a later increment behind `app_process` (`process_save_custom()`, etc.). Custom remains the 4×10 s stub.
|
||||||
|
|
||||||
|
`processName[7]` becomes a pointer; `"B&WREV"` no longer depends on a 6-char cap.
|
||||||
|
|
||||||
|
## Audio
|
||||||
|
|
||||||
|
`tone()` + `delay()` moves into `audio_task`. Machine sends `AUDIO_BEEP_SHORT` or `AUDIO_ALARM_COMPLETE`. Stop must pre-empt an in-progress alarm.
|
||||||
|
|
||||||
|
## Safety and timing
|
||||||
|
|
||||||
|
- Motor EN is safe-state HIGH (disabled) at boot and on any `Fault` or `Stop`.
|
||||||
|
- Step deadline is `esp_timer` one-shot, not a `millis()` busy loop.
|
||||||
|
- Temp offset `+0.4 °C` stays a board/config constant.
|
||||||
|
- Re-enable TWDT; conversion wait lives in `temp_task`.
|
||||||
|
- No `vTaskDelete` of worker tasks after init. Tasks block on queues/notifications for life.
|
||||||
|
|
||||||
|
## Migration plan
|
||||||
|
|
||||||
|
Executable session phases: `docs/megaplans/REFACTOR-MEGAPLAN.md`.
|
||||||
|
|
||||||
|
1. **Docs + conventions** (P00, this branch).
|
||||||
|
2. **IDF skeleton** (P01) Arduino-free, two sdkconfigs.
|
||||||
|
3. **`ui_cmd` + `app_process` + golden tests** (P02).
|
||||||
|
4. **`app_machine` + stub HAL** (P03). Host tests become the gate.
|
||||||
|
5. **Gitea Actions** (P04) `.gitea/workflows/ci.yml`.
|
||||||
|
6. **WROOM motion HAL** (P05) then **UI cutover** (P06) including Stop/Resume.
|
||||||
|
7. **S3 display+touch** (P07).
|
||||||
|
8. **Arduino debt burn-down** (P08).
|
||||||
|
|
||||||
|
## Acceptance checks for the first behavioural cut (WROOM)
|
||||||
|
|
||||||
|
- Defaults for all six processes match `docs/CURRENT_STATE.md` tables.
|
||||||
|
- Esc during a running step disables the motor within one step-pulse loop iteration and shows Resume / Back.
|
||||||
|
- Resume continues remaining time; Back returns to step select.
|
||||||
|
- Short beep on Stop and Resume; complete-alarm does not block Esc.
|
||||||
|
- Temperature updates while running without stalling remaining-time display.
|
||||||
|
- UI remains navigable if the DS18B20 is disconnected (`--` as today).
|
||||||
|
- Watchdog no longer disabled as a matter of policy.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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 |
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# REFACTOR MEGAPLAN — AutoFilm-ESP32 IDF modular UI
|
||||||
|
|
||||||
|
Audience: coding agent. One phase = one session. Do not start the next phase in the same session.
|
||||||
|
|
||||||
|
## Protocol (every session)
|
||||||
|
|
||||||
|
1. `git checkout refactor/esp-idf-modular-ui && git pull --ff-only`
|
||||||
|
2. Read only: `AGENTS.md`, this file (status table), **the assigned phase file**. Open `docs/TARGET_ARCHITECTURE.md` / `docs/CURRENT_STATE.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.
|
||||||
|
5. Commits: messages listed in the phase. Imperative. No secret tokens.
|
||||||
|
6. Push `origin refactor/esp-idf-modular-ui`. 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 = `src/devSequence.cpp` / `docs/CURRENT_STATE.md`. Changing values requires explicit user order + doc update same commit.
|
||||||
|
- Stop while running: Esc `'X'` (keypad r4c4) and later touch STOP. Immediate motor **EN HIGH** (disabled). Then Resume or ReturnToStepSelect. Short beep Stop and Resume.
|
||||||
|
- UI never blocks on motor, OneWire, or `tone()`.
|
||||||
|
- No Arduino types in `ui_cmd`, `app_process`, `app_machine` public headers.
|
||||||
|
- Watchdog stays **on**. No `vTaskDelete` of long-lived workers. No `vTaskDelete` of motor task.
|
||||||
|
- Session time edits ±5 s RAM only. No NVS profiles. No Custom editor. No pumps/valves/heaters (command IDs reserved only).
|
||||||
|
- `auto_advance` default **false** (after StepComplete wait for Start). Do not restore silent auto-chain unless a later phase says so.
|
||||||
|
|
||||||
|
## Branch
|
||||||
|
|
||||||
|
`refactor/esp-idf-modular-ui` → PR → `develop`. Rebase onto `develop` if requested; do not retarget `main`.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
| ID | File | Session goal | STATUS |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| P00 | `refactor/P00-docs-lock.md` | Architecture docs already on branch | DONE |
|
||||||
|
| P01 | `refactor/P01-idf-skeleton.md` | Dual-board IDF project compiles | BLOCKED |
|
||||||
|
| P02 | `refactor/P02-ui-cmd-process.md` | `ui_cmd` + `app_process` + golden tests | DONE |
|
||||||
|
| P03 | `refactor/P03-machine-host.md` | `app_machine` + stub HAL + SM tests | DONE |
|
||||||
|
| P04 | `refactor/P04-gitea-ci.md` | `.gitea/workflows/ci.yml` live | DONE |
|
||||||
|
| 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.) | DONE |
|
||||||
|
|
||||||
|
P04 Notes: merge_bin uses `${{ gitea.sha }}` (not `github.sha`); runner unverified (no Actions run).
|
||||||
|
|
||||||
|
P05 Notes: Arduino-esp32 ~3.2.1 (3.1.x requires IDF <5.4) + IDF v5.4; AccelStepper 1.64; OneWire 2.3.8; DallasTemperature 3.9.1. AUTOFILM_MOTION_SMOKE default off. hw not flashed.
|
||||||
|
|
||||||
|
P06 Notes: WROOM input_task dual-path stop (hal_motor_request_stop then CMD_STOP). Legacy startingMenu unlinked from IDF. Notes=`hw smoke pending`.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
```
|
||||||
|
P00 → P01 → P02 → P03 → P04
|
||||||
|
↘ P05 → P06 → P07 → P08
|
||||||
|
P04 may start after P01 (hashFiles guards) but must not be required-on-develop until P03 VERIFY is green.
|
||||||
|
P05 requires P03 APIs. P06 requires P05. P07 requires P06 screen contract. P08 requires P06 behaviour-identical.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tree (end state; create only what the current phase lists)
|
||||||
|
|
||||||
|
```
|
||||||
|
main/ IDF entry, spawn tasks
|
||||||
|
components/ui_cmd/ cmds/events C structs
|
||||||
|
components/app_process/ const recipes + session overlay
|
||||||
|
components/app_machine/ state machine
|
||||||
|
components/app_ui/ screens → commands
|
||||||
|
components/hal_{motor,temp,input,display,audio}/
|
||||||
|
components/board_{wroom,jc4827w543}/
|
||||||
|
tests/host/ CMake+CTest, stub HAL
|
||||||
|
.gitea/workflows/ci.yml
|
||||||
|
sdkconfig.defaults sdkconfig.wroom sdkconfig.s3
|
||||||
|
src/ include/ legacy; shrink after P06
|
||||||
|
```
|
||||||
|
|
||||||
|
## IDF pin
|
||||||
|
|
||||||
|
`espressif/idf:v5.4`. Bump only in P04/CICD together.
|
||||||
|
|
||||||
|
## Do not
|
||||||
|
|
||||||
|
- Expand scope (LVGL, NVS profiles, pumps, heater, PlatformIO CI).
|
||||||
|
- Disable TWDT.
|
||||||
|
- Add Arduino libraries beyond AccelStepper, Keypad, LiquidCrystal_I2C, OneWire, DallasTemperature (P05–P07 only).
|
||||||
|
- Commit `sdkconfig` (generated), `build/`, tokens.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# P00 — Docs lock
|
||||||
|
|
||||||
|
STATUS: DONE
|
||||||
|
SESSION: already executed (commits 9558e58, d6a3399, d8fed43)
|
||||||
|
DEPENDS: none
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
Present on `refactor/esp-idf-modular-ui`:
|
||||||
|
|
||||||
|
- `AGENTS.md`
|
||||||
|
- `docs/CURRENT_STATE.md`
|
||||||
|
- `docs/TARGET_ARCHITECTURE.md`
|
||||||
|
- `docs/CICD.md`
|
||||||
|
|
||||||
|
Do not rewrite those unless a later phase `Notes` a real API drift. Executing agents start at **P01**.
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
# P01 — IDF dual-board skeleton
|
||||||
|
|
||||||
|
STATUS: BLOCKED
|
||||||
|
BLOCKED: no IDF (IDF_PATH unset; no idf.py; no docker.io/espressif/idf:v5.4). Sources landed; builds not run; no binaries faked.
|
||||||
|
DEPENDS: P00
|
||||||
|
READ: `AGENTS.md`, `docs/megaplans/REFACTOR-MEGAPLAN.md`, this file
|
||||||
|
OUT: recipes, UI, motor, Arduino-as-component, Gitea workflow, touching `src/` behaviour
|
||||||
|
FORBIDDEN: `esp_task_wdt_deinit`, Arduino `#include`, new libraries, changing recipe files
|
||||||
|
|
||||||
|
## IN
|
||||||
|
|
||||||
|
Create a compileable ESP-IDF v5.4 app for two boards. App: init GPIO motor-EN as disabled (HIGH), log board name, idle loop `vTaskDelay`, TWDT on. No LCD yet.
|
||||||
|
|
||||||
|
## FILES (create)
|
||||||
|
|
||||||
|
```
|
||||||
|
CMakeLists.txt
|
||||||
|
main/CMakeLists.txt
|
||||||
|
main/idf_component.yml # empty dependencies: {} (no Arduino yet)
|
||||||
|
main/main.c
|
||||||
|
components/board_wroom/CMakeLists.txt
|
||||||
|
components/board_wroom/include/board.h
|
||||||
|
components/board_wroom/board.c
|
||||||
|
components/board_jc4827w543/CMakeLists.txt
|
||||||
|
components/board_jc4827w543/include/board.h
|
||||||
|
components/board_jc4827w543/board.c
|
||||||
|
sdkconfig.defaults
|
||||||
|
sdkconfig.wroom
|
||||||
|
sdkconfig.s3
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `.gitignore` append:
|
||||||
|
|
||||||
|
```
|
||||||
|
build/
|
||||||
|
sdkconfig
|
||||||
|
sdkconfig.old
|
||||||
|
managed_components/
|
||||||
|
dependencies.lock
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not delete `platformio.ini` / `src/` / `include/`.
|
||||||
|
|
||||||
|
## Board CMake
|
||||||
|
|
||||||
|
Exactly one board component is compiled.
|
||||||
|
|
||||||
|
`main/CMakeLists.txt`:
|
||||||
|
|
||||||
|
```
|
||||||
|
if(NOT DEFINED AUTOFILM_BOARD)
|
||||||
|
set(AUTOFILM_BOARD wroom CACHE STRING "wroom|jc4827w543")
|
||||||
|
endif()
|
||||||
|
if(AUTOFILM_BOARD STREQUAL "wroom")
|
||||||
|
set(BOARD_COMP board_wroom)
|
||||||
|
elseif(AUTOFILM_BOARD STREQUAL "jc4827w543")
|
||||||
|
set(BOARD_COMP board_jc4827w543)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "AUTOFILM_BOARD=${AUTOFILM_BOARD}")
|
||||||
|
endif()
|
||||||
|
idf_component_register(SRCS "main.c" INCLUDE_DIRS "." REQUIRES ${BOARD_COMP} driver)
|
||||||
|
```
|
||||||
|
|
||||||
|
Both `board.h` **identical API**:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#pragma once
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
const char *board_name(void);
|
||||||
|
gpio_num_t board_pin_motor_en(void); /* WROOM: 27. S3: GPIO_NUM_NC until P07 maps real spare IO */
|
||||||
|
int board_motor_en_disable_level(void); /* 1 = HIGH means disabled */
|
||||||
|
```
|
||||||
|
|
||||||
|
`board_wroom`: `name="wroom"`, EN=27, disable_level=1.
|
||||||
|
`board_jc4827w543`: `name="jc4827w543"`, EN=`GPIO_NUM_NC`, disable_level=1. If EN is NC, `main.c` skips gpio config.
|
||||||
|
|
||||||
|
## main.c contract
|
||||||
|
|
||||||
|
- `ESP_LOGI("app", "board=%s", board_name());`
|
||||||
|
- if EN valid: `gpio_set_direction`, `gpio_set_level(pin, disable_level)` **before** any other actuator
|
||||||
|
- do **not** call `disableWatchdogTimers` or `esp_task_wdt_deinit`
|
||||||
|
- loop: `vTaskDelay(pdMS_TO_TICKS(1000))`
|
||||||
|
|
||||||
|
## sdkconfig (minimal)
|
||||||
|
|
||||||
|
`sdkconfig.defaults`:
|
||||||
|
|
||||||
|
```
|
||||||
|
CONFIG_ESP_TASK_WDT_EN=y
|
||||||
|
CONFIG_ESP_TASK_WDT_TIMEOUT_S=10
|
||||||
|
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y
|
||||||
|
```
|
||||||
|
|
||||||
|
`sdkconfig.wroom`:
|
||||||
|
|
||||||
|
```
|
||||||
|
CONFIG_IDF_TARGET="esp32"
|
||||||
|
```
|
||||||
|
|
||||||
|
`sdkconfig.s3`:
|
||||||
|
|
||||||
|
```
|
||||||
|
CONFIG_IDF_TARGET="esp32s3"
|
||||||
|
CONFIG_SPIRAM=y
|
||||||
|
CONFIG_SPIRAM_MODE_OCT=y
|
||||||
|
CONFIG_SPIRAM_SPEED_80M=y
|
||||||
|
```
|
||||||
|
|
||||||
|
If a Kconfig name fails on v5.4, fix to the v5.4 equivalent; do not drop PSRAM on S3.
|
||||||
|
|
||||||
|
## VERIFY
|
||||||
|
|
||||||
|
Need `IDF_PATH` or docker `espressif/idf:v5.4`.
|
||||||
|
|
||||||
|
```
|
||||||
|
idf.py -B /tmp/af-wroom -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.wroom" -D AUTOFILM_BOARD=wroom set-target esp32 build
|
||||||
|
idf.py -B /tmp/af-s3 -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.s3" -D AUTOFILM_BOARD=jc4827w543 set-target esp32s3 build
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass = both exit 0. If no IDF in environment: document `BLOCKED: no IDF` and still land the files; do not fake binaries.
|
||||||
|
|
||||||
|
## COMMITS
|
||||||
|
|
||||||
|
1. `Add ESP-IDF skeleton for wroom and jc4827w543 boards`
|
||||||
|
2. (if needed) `Ignore IDF build products`
|
||||||
|
|
||||||
|
## DoD
|
||||||
|
|
||||||
|
- [ ] Both CMake board comps export the same `board.h` API
|
||||||
|
- [ ] Motor EN disabled at boot on wroom
|
||||||
|
- [ ] TWDT not disabled
|
||||||
|
- [ ] Legacy Arduino tree untouched
|
||||||
|
- [ ] STATUS→DONE
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
# P02 — ui_cmd + app_process + golden tests
|
||||||
|
|
||||||
|
STATUS: DONE
|
||||||
|
DEPENDS: P01
|
||||||
|
READ: this file, `src/devSequence.cpp` (values only), `docs/megaplans/REFACTOR-MEGAPLAN.md`
|
||||||
|
OUT: FreeRTOS tasks, HAL, display, Arduino, CI yaml, machine states
|
||||||
|
FORBIDDEN: mutate recipe numbers; `String`; Arduino headers in new public `.h`
|
||||||
|
|
||||||
|
## IN
|
||||||
|
|
||||||
|
C components + host tests that lock recipe bytes. No firmware behaviour change yet (`main.c` may log process count).
|
||||||
|
|
||||||
|
## FILES
|
||||||
|
|
||||||
|
```
|
||||||
|
components/ui_cmd/CMakeLists.txt
|
||||||
|
components/ui_cmd/include/ui_cmd.h
|
||||||
|
components/app_process/CMakeLists.txt
|
||||||
|
components/app_process/include/app_process.h
|
||||||
|
components/app_process/app_process.c
|
||||||
|
tests/host/CMakeLists.txt
|
||||||
|
tests/host/test_process.c
|
||||||
|
tests/host/main.c # if Unity/CTest needs a runner; else add_executable per test
|
||||||
|
```
|
||||||
|
|
||||||
|
Host CMake **must not** require `IDF_PATH`. Link `app_process` sources directly. Provide `esp_err_t` typedef if needed:
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef int esp_err_t;
|
||||||
|
#define ESP_OK 0
|
||||||
|
#define ESP_ERR_NOT_FOUND 0x105
|
||||||
|
#define ESP_ERR_INVALID_ARG 0x102
|
||||||
|
```
|
||||||
|
|
||||||
|
in `tests/host/esp_host_compat.h` and include it only in host builds.
|
||||||
|
|
||||||
|
## ui_cmd.h (lock)
|
||||||
|
|
||||||
|
Packed, no pointers in the union payload except none — use ids.
|
||||||
|
|
||||||
|
```c
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#define UI_CMD_QUEUE_LEN 8
|
||||||
|
#define UI_EVT_QUEUE_LEN 16
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CMD_SELECT_PROCESS = 1,
|
||||||
|
CMD_BROWSE_STEP = 2,
|
||||||
|
CMD_ADJUST_STEP_TIME = 3,
|
||||||
|
CMD_ARM_STEP = 4,
|
||||||
|
CMD_START_STEP = 5,
|
||||||
|
CMD_STOP = 6,
|
||||||
|
CMD_RESUME = 7,
|
||||||
|
CMD_RETURN_TO_STEP_SELECT = 8,
|
||||||
|
CMD_CANCEL_ARMED = 9,
|
||||||
|
CMD_PUMP_SET = 100,
|
||||||
|
CMD_VALVE_SET = 101,
|
||||||
|
CMD_RESERVOIR_SELECT = 102,
|
||||||
|
CMD_DRAIN_START = 103,
|
||||||
|
CMD_DRAIN_STOP = 104,
|
||||||
|
CMD_HEATER_SETPOINT = 105
|
||||||
|
} ui_cmd_id_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EVT_PROCESS_SELECTED = 1,
|
||||||
|
EVT_STEP_VIEW = 2,
|
||||||
|
EVT_STEP_ARMED = 3,
|
||||||
|
EVT_STEP_STARTED = 4,
|
||||||
|
EVT_STEP_PROGRESS = 5,
|
||||||
|
EVT_STEP_COMPLETE = 6,
|
||||||
|
EVT_STEP_STOPPED = 7,
|
||||||
|
EVT_STEP_RESUMED = 8,
|
||||||
|
EVT_PROCESS_IDLE = 9,
|
||||||
|
EVT_TEMP_UPDATED = 10,
|
||||||
|
EVT_FAULT = 11
|
||||||
|
} ui_evt_id_t;
|
||||||
|
|
||||||
|
typedef enum { MOTION_IDLE = 0, MOTION_CW = 1, MOTION_CCW = 2 } motion_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ui_cmd_id_t id;
|
||||||
|
uint8_t process_id;
|
||||||
|
int8_t step_delta; /* BrowseStep */
|
||||||
|
int8_t time_delta_s; /* AdjustStepTime, typically ±5 */
|
||||||
|
uint8_t step_index; /* Arm/Start */
|
||||||
|
} ui_cmd_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ui_evt_id_t id;
|
||||||
|
uint8_t process_id;
|
||||||
|
uint8_t step_index;
|
||||||
|
uint8_t step_count;
|
||||||
|
uint16_t time_s;
|
||||||
|
uint32_t remaining_ms;
|
||||||
|
float temp_c; /* NAN if disconnected */
|
||||||
|
bool temp_ok;
|
||||||
|
float temp_min_c, temp_pref_c, temp_max_c;
|
||||||
|
float cw_revs, ccw_revs;
|
||||||
|
motion_t motion;
|
||||||
|
int16_t fault_code;
|
||||||
|
} ui_evt_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
P03+ fill unused fields with 0 / NAN. Do not add `char name[n]` to the event if it forces copies; UI looks up names via `app_process` by id+index.
|
||||||
|
|
||||||
|
## app_process.h (lock)
|
||||||
|
|
||||||
|
```c
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "esp_err.h" /* or host compat */
|
||||||
|
|
||||||
|
#define PROCESS_COUNT 6
|
||||||
|
#define PROCESS_MAX_STEPS 20
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROC_C41 = 0,
|
||||||
|
PROC_E6 = 1,
|
||||||
|
PROC_ECN2 = 2,
|
||||||
|
PROC_BW = 3,
|
||||||
|
PROC_CUSTOM = 4,
|
||||||
|
PROC_BWREV = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
|
uint16_t time_s;
|
||||||
|
float cw_revs, ccw_revs;
|
||||||
|
float temp_min_c, temp_pref_c, temp_max_c;
|
||||||
|
} process_step_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
|
uint8_t step_count;
|
||||||
|
const process_step_t *steps;
|
||||||
|
} process_def_t;
|
||||||
|
|
||||||
|
void app_process_init(void); /* zero session deltas */
|
||||||
|
const process_def_t *app_process_get(uint8_t id);
|
||||||
|
const process_def_t *app_process_find_name(const char *name);
|
||||||
|
uint16_t app_process_time_s(uint8_t id, uint8_t step); /* default + session delta, min 0 */
|
||||||
|
esp_err_t app_process_adjust_time(uint8_t id, uint8_t step, int8_t delta_s);
|
||||||
|
void app_process_reset_session(uint8_t id);
|
||||||
|
```
|
||||||
|
|
||||||
|
Session overlay: `static int16_t time_delta_s[PROCESS_COUNT][PROCESS_MAX_STEPS]`. `adjust_time` adds delta; does **not** write `process_step_t.time_s`. Clamp resulting time to `[0, 36000]`.
|
||||||
|
|
||||||
|
## Golden table (assert exact)
|
||||||
|
|
||||||
|
Copy from `src/devSequence.cpp`. Names `strcmp`. Floats: compare with `fabsf(a-b)<1e-4` except integers as exact.
|
||||||
|
|
||||||
|
**C41** `name="C41"` count=7
|
||||||
|
|
||||||
|
| i | name | t | cw | ccw | min | pref | max |
|
||||||
|
|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
| 0 | Prewarm | 180 | 1 | 1 | 37.8 | 38 | 38.2 |
|
||||||
|
| 1 | Developer | 195 | 5.5 | 5 | 37.8 | 38 | 38.2 |
|
||||||
|
| 2 | Bleach | 45 | 5.5 | 5 | 32 | 38 | 38.2 |
|
||||||
|
| 3 | Fix | 180 | 5.5 | 5 | 32 | 38 | 38.2 |
|
||||||
|
| 4 | Rinse 1 | 60 | 3.5 | 3 | 32 | 38 | 38.2 |
|
||||||
|
| 5 | Rinse 2 | 60 | 3.5 | 3 | 32 | 38 | 38.2 |
|
||||||
|
| 6 | Fin Rinse | 30 | 1 | 1 | 32 | 38 | 38.2 |
|
||||||
|
|
||||||
|
**E6** `name="E6"` 12: times `{180,360,120,120,360,120,360,240,120,120,120,30}` names `Preheat,FirstDev,Wash 1,Reversal,ColorDev,PreBleach,Bleach,Fixer,Wash 2,Wash 3,Wash 4,Fin Rinse` cw `{1,5.5,3.5,5,5.5,5.5,5.5,5.5,3.5,3.5,3.5,3.5}` ccw `{1,5,3,5.5,5,5.5,5.5,5.5,3,3,3,3}` min `{37.5,37.7,33.0,37.7,37.0,37,37.5,37.5,33.0,33.0,33.0,19.0}` pref `{38,38,38,38,38,38,38,38,38,38,38,20}` max `{38.5,38.3,38.0,38.3,39.0,38,38.5,38.5,38.5,38.5,38.5,21.0}`
|
||||||
|
|
||||||
|
**ECN-2** `name="ECN-2"` 9: t `{180,0,210,60,180,150,120,300,120}` names `Prebath,RemJet,Developer,Stop Bath,Wash,Bleach,Fixer,Wash 2,Fin Rinse` cw `{1,0,5.5,3.5,3.5,5.5,5.5,3.5,1}` ccw `{1,0,5,3,3,5,5,3,1}` min `{27,0,40.8,27,27,27,27,27,27}` pref `{38,0,41,38,38,38,38,38,38}` max `{38,0,41.2,38,38,38,38,38,38}`
|
||||||
|
|
||||||
|
**B&W** `name="B&W"` 7: t `{510,30,300,60,90,120,30}` names `Developer,Stop,Fix,Rinse 1,Rinse 2,Rinse 3,Fin Rinse` cw `{5.5,3.5,3.5,3.5,3.5,3.5,1}` ccw `{5,3,3,3,3,3,1}` min 19 pref 20 max 21 all.
|
||||||
|
|
||||||
|
**Custom** `name="Custom"` 4: t 10, names `Developer,Stop,Fix,Rinse` cw `{5.5,3.5,3.5,3.5}` ccw `{5,3,3,3}` min 19 pref 20 max 21.
|
||||||
|
|
||||||
|
**B&WREV** `name="B&WREV"` 12: t `{720,300,300,60,120,60,120,360,60,300,60,60}` names `FirstDev,Wash 1,Bleach,Wash 2,Clearing,Wash 3,Reversal,SecondDev,Wash 4,Fix,Wash 5,Fin Rinse` cw `{5.5,3.5,5.5,3.5,5.5,3.5,5.5,5.5,3.5,5.5,3.5,3.5}` ccw `{5,3,5,3,5,3,5,5,3,5,3,3}` min `{19.5,15.5,19.5,15.5,19.5,15.5,19.5,19.5,15.5,19.5,15.5,15.5}` pref 20 all max `{20.5,22.5,22.5,22.5,22.5,22.5,22.5,22.5,22.5,22.5,22.5,22.5}`
|
||||||
|
|
||||||
|
Tests also: `find_name("nope")==NULL`; `adjust_time(C41,1,+5)` → 200, const table still 195; `reset_session` restores 195.
|
||||||
|
|
||||||
|
## VERIFY
|
||||||
|
|
||||||
|
```
|
||||||
|
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
|
||||||
|
```
|
||||||
|
|
||||||
|
All golden tests pass.
|
||||||
|
|
||||||
|
## COMMITS
|
||||||
|
|
||||||
|
1. `Add ui_cmd and app_process with frozen recipe tables`
|
||||||
|
2. `Add host tests for process lookup and session time overlay`
|
||||||
|
|
||||||
|
## DoD
|
||||||
|
|
||||||
|
- [x] Headers Arduino-free
|
||||||
|
- [x] Every cell in golden table asserted
|
||||||
|
- [x] Overlay does not mutate const steps
|
||||||
|
- [x] STATUS→DONE
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
# P03 — app_machine + stub HAL + state tests
|
||||||
|
|
||||||
|
STATUS: DONE
|
||||||
|
DEPENDS: P02
|
||||||
|
READ: this file, `docs/megaplans/REFACTOR-MEGAPLAN.md`, `components/ui_cmd/include/ui_cmd.h`
|
||||||
|
OUT: real GPIO, Arduino, LCD, Gitea yaml, deleting `src/menu.cpp`
|
||||||
|
FORBIDDEN: `vTaskDelete`; blocking `delay` in machine logic; auto_advance default true
|
||||||
|
|
||||||
|
## IN
|
||||||
|
|
||||||
|
Synchronous state machine callable from host tests **without FreeRTOS**. On-target, P06 wraps it in `machine_task`. Same `.c` file compiled twice.
|
||||||
|
|
||||||
|
## FILES
|
||||||
|
|
||||||
|
```
|
||||||
|
components/app_machine/CMakeLists.txt
|
||||||
|
components/app_machine/include/app_machine.h
|
||||||
|
components/app_machine/app_machine.c
|
||||||
|
components/hal_motor/include/hal_motor.h
|
||||||
|
components/hal_temp/include/hal_temp.h
|
||||||
|
components/hal_audio/include/hal_audio.h
|
||||||
|
tests/host/stubs/hal_motor.c
|
||||||
|
tests/host/stubs/hal_temp.c
|
||||||
|
tests/host/stubs/hal_audio.c
|
||||||
|
tests/host/test_machine.c
|
||||||
|
```
|
||||||
|
|
||||||
|
Host CMake adds stubs + `app_machine.c` + `app_process.c`. Define `AUTOFILM_HOST=1` for clock injection.
|
||||||
|
|
||||||
|
## HAL headers (lock; P05 implements for device)
|
||||||
|
|
||||||
|
`hal_motor.h`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
void hal_motor_init(void);
|
||||||
|
void hal_motor_enable(bool on); /* on=false → EN disabled (HIGH on wroom) */
|
||||||
|
void hal_motor_request_stop(void); /* must be safe to call anytime; sets flag + enable false */
|
||||||
|
esp_err_t hal_motor_agitate_start(float cw_revs, float ccw_revs, uint32_t rpm);
|
||||||
|
void hal_motor_agitate_stop(void); /* leave disabled */
|
||||||
|
bool hal_motor_is_enabled(void);
|
||||||
|
```
|
||||||
|
|
||||||
|
`hal_temp.h`: `void hal_temp_init(void); esp_err_t hal_temp_read_c(float *out);` /* ESP_FAIL → disconnected */
|
||||||
|
|
||||||
|
`hal_audio.h`: `void hal_audio_init(void); void hal_audio_beep_short(void); void hal_audio_alarm_complete(void); void hal_audio_alarm_cancel(void);`
|
||||||
|
|
||||||
|
Host stubs: record last call in globals `stub_motor_enabled`, `stub_stop_count`, `stub_beep_count`, `stub_alarm_count`, `stub_alarm_cancel_count`. `hal_temp_read_c` returns 20.0 ESP_OK unless test sets `stub_temp_fail`.
|
||||||
|
|
||||||
|
## app_machine.h (lock)
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef enum {
|
||||||
|
ST_IDLE = 0,
|
||||||
|
ST_STEP_SELECT,
|
||||||
|
ST_ARMED,
|
||||||
|
ST_RUNNING,
|
||||||
|
ST_STOPPED,
|
||||||
|
ST_COMPLETE
|
||||||
|
} machine_state_t;
|
||||||
|
|
||||||
|
void app_machine_init(void);
|
||||||
|
machine_state_t app_machine_state(void);
|
||||||
|
void app_machine_handle_cmd(const ui_cmd_t *cmd);
|
||||||
|
void app_machine_tick(uint32_t now_ms); /* drive timers; tests call this */
|
||||||
|
void app_machine_on_temp(float c, bool ok);
|
||||||
|
int app_machine_last_event(ui_evt_t *out); /* pop 1 event; 0 empty, 1 ok */
|
||||||
|
uint8_t app_machine_process_id(void);
|
||||||
|
uint8_t app_machine_step_index(void);
|
||||||
|
uint32_t app_machine_remaining_ms(void);
|
||||||
|
```
|
||||||
|
|
||||||
|
Internal event ring size `UI_EVT_QUEUE_LEN`.
|
||||||
|
|
||||||
|
## Transitions (implement exactly)
|
||||||
|
|
||||||
|
| from | cmd/tick | to | side effects |
|
||||||
|
|---|---|---|---|
|
||||||
|
| IDLE | SELECT_PROCESS valid id | STEP_SELECT | evt PROCESS_SELECTED + STEP_VIEW(0) |
|
||||||
|
| STEP_SELECT | BROWSE_STEP +1/-1 clamp | STEP_SELECT | STEP_VIEW |
|
||||||
|
| STEP_SELECT | ADJUST_STEP_TIME ±5 | STEP_SELECT | overlay; STEP_VIEW |
|
||||||
|
| STEP_SELECT | ARM_STEP or START_STEP | ARMED | STEP_ARMED (START from select still arms first — do not start motion until START in ARMED, except if cmd is START_STEP from ARMED) |
|
||||||
|
| ARMED | START_STEP | RUNNING | motor enable+agitate_start; remaining=time_s*1000; deadline=now+remaining; STEP_STARTED |
|
||||||
|
| ARMED | CANCEL_ARMED or STOP | STEP_SELECT | motor disabled; STEP_VIEW |
|
||||||
|
| RUNNING | tick now>=deadline | COMPLETE | agitate_stop; enable false; alarm_complete; STEP_COMPLETE |
|
||||||
|
| RUNNING | STOP | STOPPED | **request_stop + enable false first**; beep_short; remaining=deadline-now; STEP_STOPPED |
|
||||||
|
| STOPPED | RESUME | RUNNING | beep_short; deadline=now+remaining; agitate_start; STEP_RESUMED |
|
||||||
|
| STOPPED | RETURN_TO_STEP_SELECT or STOP | STEP_SELECT | enable false; remaining stored in overlay as ceil(remaining/1000) replacing session time for that step; STEP_VIEW |
|
||||||
|
| COMPLETE | START_STEP / ARM_STEP next | ARMED of next if any else IDLE PROCESS_IDLE | |
|
||||||
|
| COMPLETE | RETURN_TO_STEP_SELECT | STEP_SELECT | |
|
||||||
|
| COMPLETE | STOP | STEP_SELECT | alarm_cancel; beep_short |
|
||||||
|
| * | STOP in RUNNING | (above) | STOP must work even if alarm would be playing — in COMPLETE STOP cancels alarm |
|
||||||
|
| * | invalid id | stay | EVT_FAULT code=1 |
|
||||||
|
|
||||||
|
START_STEP from STEP_SELECT: treat as ARM then if you want one-key start, **still require second START** (matches Ent on armed screen). Keypad `E` on step list currently starts processing after a prompt — two-step is correct.
|
||||||
|
|
||||||
|
`auto_advance`: `static bool auto_advance=false`. If true, COMPLETE tick auto ARM next. Tests assert default false: after COMPLETE, state stays COMPLETE until cmd.
|
||||||
|
|
||||||
|
RPM for agitate_start: 60.
|
||||||
|
|
||||||
|
0-second steps (ECN-2 RemJet): START → immediately COMPLETE on next tick (no motor enable, or enable false). Tests cover this.
|
||||||
|
|
||||||
|
## Clock
|
||||||
|
|
||||||
|
`app_machine_tick(now_ms)` monotonic. Tests: init, SELECT C41, ARM 0, START, tick(0) start, tick(179999) still RUNNING, tick(180000) COMPLETE. Custom 10s similar.
|
||||||
|
|
||||||
|
STOP at t=1000 on Custom step0: remaining_ms ≈ 9000±tick, `stub_motor_enabled==false`, `stub_stop_count>=1`. RESUME then tick +9000 → COMPLETE.
|
||||||
|
|
||||||
|
## VERIFY
|
||||||
|
|
||||||
|
```
|
||||||
|
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
|
||||||
|
```
|
||||||
|
|
||||||
|
Required cases in `test_machine.c` names:
|
||||||
|
|
||||||
|
- `select_c41_step_view`
|
||||||
|
- `adjust_does_not_mutate_const`
|
||||||
|
- `arm_start_complete_custom_10s`
|
||||||
|
- `stop_disables_motor_and_resume`
|
||||||
|
- `return_from_stopped`
|
||||||
|
- `stop_during_complete_cancels_alarm`
|
||||||
|
- `ecn2_remjet_zero_time`
|
||||||
|
- `stop_ignored_meaningless_in_idle` (IDLE+STOP → stay IDLE, no motor calls required)
|
||||||
|
|
||||||
|
## COMMITS
|
||||||
|
|
||||||
|
1. `Add HAL headers and host stubs for motor temp audio`
|
||||||
|
2. `Add app_machine state machine with stop and resume`
|
||||||
|
3. `Add host tests for machine stop resume and complete`
|
||||||
|
|
||||||
|
## DoD
|
||||||
|
|
||||||
|
- [x] STOP path calls `hal_motor_request_stop` before any other work
|
||||||
|
- [x] default auto_advance false
|
||||||
|
- [x] host tests cover list above
|
||||||
|
- [x] STATUS→DONE
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# P04 — Gitea Actions CI
|
||||||
|
|
||||||
|
STATUS: DONE
|
||||||
|
Notes: used ${{ gitea.sha }} in merge_bin output name (CICD example); github.sha not substituted. Runner unverified (no Actions run). No required-check flip (P01 BLOCKED; images not confirmed compiling). Release-attach job skipped.
|
||||||
|
DEPENDS: P01 (required), P02/P03 (tests job becomes real when `tests/host/CMakeLists.txt` exists)
|
||||||
|
READ: this file, `docs/CICD.md`
|
||||||
|
OUT: changing firmware sources; enabling branch protection yourself if you lack repo admin — then note BLOCKED for humans
|
||||||
|
FORBIDDEN: GitHub-only `espressif/esp-idf-ci-action` as the compile driver; secrets in yaml; required-check flip if firmware still does not compile
|
||||||
|
|
||||||
|
## IN
|
||||||
|
|
||||||
|
Add `.gitea/workflows/ci.yml` per `docs/CICD.md` example. Use `hashFiles` guards:
|
||||||
|
|
||||||
|
- test job runs cmake/ctest only if `tests/host/CMakeLists.txt` exists
|
||||||
|
- firmware job runs idf.py only if root `CMakeLists.txt` exists (P01)
|
||||||
|
|
||||||
|
Container: `docker.io/espressif/idf:v5.4`. `fail-fast: false`. `needs: test` on firmware.
|
||||||
|
|
||||||
|
Artifact names: `firmware-${{ matrix.board }}` including merged bin, bootloader, partition-table, flasher_args, sdkconfig, size txt.
|
||||||
|
|
||||||
|
Gitea context: try `${{ gitea.sha }}`; if runner only has `github.sha`, use that. Document which in Notes.
|
||||||
|
|
||||||
|
Triggers exactly:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [develop]
|
||||||
|
push:
|
||||||
|
branches: [develop, "refactor/**", "feature/**"]
|
||||||
|
tags: ["v*"]
|
||||||
|
workflow_dispatch:
|
||||||
|
```
|
||||||
|
|
||||||
|
`runs-on: ubuntu-latest`.
|
||||||
|
|
||||||
|
Firmware matrix: wroom/esp32/sdkconfig.wroom and jc4827w543/esp32s3/sdkconfig.s3.
|
||||||
|
|
||||||
|
`git config --global --add safe.directory '*'` inside container before idf.py.
|
||||||
|
|
||||||
|
Do **not** add a release-attach job unless tag attach API is already used on this Gitea; skip release job in v1.
|
||||||
|
|
||||||
|
## VERIFY
|
||||||
|
|
||||||
|
- yaml parses (python `-c 'import yaml; yaml.safe_load(open(".gitea/workflows/ci.yml"))'` if PyYAML present, else visual: valid YAML, two-space indent)
|
||||||
|
- If Gitea runner visible: push and confirm jobs queued. If not: still land file; Notes=`runner not verified`
|
||||||
|
|
||||||
|
## COMMITS
|
||||||
|
|
||||||
|
1. `Add Gitea Actions workflow for host tests and dual firmware`
|
||||||
|
|
||||||
|
## DoD
|
||||||
|
|
||||||
|
- [ ] Path `.gitea/workflows/ci.yml`
|
||||||
|
- [ ] Guards prevent red on missing tests/host before P02
|
||||||
|
- [ ] STATUS→DONE
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# P05 — WROOM HAL motor / temp / audio
|
||||||
|
|
||||||
|
STATUS: DONE
|
||||||
|
DEPENDS: P03
|
||||||
|
READ: this file, `include/config.h`, `src/motor.cpp`, `src/temperature.cpp`, `src/sound.cpp`, `components/hal_*/include/*.h`
|
||||||
|
OUT: rewriting `app_ui` / deleting menus; S3 motor pins; disabling WDT; `vTaskDelete` motor
|
||||||
|
FORBIDDEN: new Arduino libraries; `tone()` on machine task; blocking OneWire on UI task
|
||||||
|
|
||||||
|
## IN
|
||||||
|
|
||||||
|
Device implementations of `hal_motor`, `hal_temp`, `hal_audio` for `board_wroom`. Arduino-as-component **first allowed here**.
|
||||||
|
|
||||||
|
Keep AccelStepper behind `hal_motor`. Inner step loop **must** sample `stop_req` every `stepper.run()` and exit. Motor task is immortal: wait on notification for next `agitate_start`.
|
||||||
|
|
||||||
|
## Arduino component
|
||||||
|
|
||||||
|
`main/idf_component.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
espressif/arduino-esp32: "~3.1.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
If 3.1 conflicts with IDF 5.4, pin the combo that compiles; Notes the versions. Only WROOM firmware enables Arduino this phase; S3 may compile without motor SRCS via `if(AUTOFILM_BOARD STREQUAL wroom)`.
|
||||||
|
|
||||||
|
AccelStepper: add as managed component **or** vendor `components/third_party/AccelStepper` (upstream 1.64). Prefer `idf_component.yml` git dep if it builds; else vendor the .cpp/.h used today.
|
||||||
|
|
||||||
|
OneWire + DallasTemperature: same policy. Keypad/LCD **not** this phase.
|
||||||
|
|
||||||
|
## Pins (wroom only, from config)
|
||||||
|
|
||||||
|
| fn | gpio |
|
||||||
|
|---|---|
|
||||||
|
| STEP | 12 |
|
||||||
|
| DIR | 14 |
|
||||||
|
| EN active-low | 27 |
|
||||||
|
| DS18B20 | 13 |
|
||||||
|
| beep | 25 |
|
||||||
|
|
||||||
|
`STEPS_PER_REV=4800` `RPM=60` `ACCEL=9600` `TEMP_OFFSET=0.4f`
|
||||||
|
|
||||||
|
## motor_task
|
||||||
|
|
||||||
|
Priority high, core 0. Loop:
|
||||||
|
|
||||||
|
```
|
||||||
|
wait notify
|
||||||
|
while !stop_req && agitating:
|
||||||
|
move CW cw_revs (run loop checks stop_req)
|
||||||
|
if stop_req break
|
||||||
|
move CCW ccw_revs
|
||||||
|
hal_motor_enable(false)
|
||||||
|
```
|
||||||
|
|
||||||
|
`hal_motor_request_stop`: atomic stop_req=1; `gpio_set_level(EN,1)` **immediately**; notify. ISR-safe: no heap, no stepper API.
|
||||||
|
|
||||||
|
Never `vTaskDelete`.
|
||||||
|
|
||||||
|
## temp_task
|
||||||
|
|
||||||
|
Low prio. Period: request conversion, `vTaskDelay(750ms)`, read, add 0.4, `app_machine_on_temp`. On disconnect `ok=false`. Do not write LCD.
|
||||||
|
|
||||||
|
## audio_task
|
||||||
|
|
||||||
|
Queue of `{SHORT, ALARM, CANCEL}`. SHORT: one 2 kHz 80–120 ms pulse (LEDC or Arduino `tone` **in this task only**). ALARM: 10× (2000 Hz 500 ms on / 250 ms off) **abort immediately on CANCEL or STOP queued**. Machine never `delay`s for sound.
|
||||||
|
|
||||||
|
## main.c (wroom)
|
||||||
|
|
||||||
|
`hal_*_init`, `app_process_init`, `app_machine_init`, create motor/temp/audio tasks. Do not start UI yet (P06). Optional: 1 Hz log remaining if RUNNING for bring-up.
|
||||||
|
|
||||||
|
S3 build: stub motor/temp/audio weak symbols or board-local no-ops so P01 S3 still links.
|
||||||
|
|
||||||
|
## VERIFY
|
||||||
|
|
||||||
|
Host tests still pass.
|
||||||
|
|
||||||
|
WROOM `idf.py` build pass.
|
||||||
|
|
||||||
|
Manual on hardware (if unavailable, Notes=`hw not flashed`; still land code):
|
||||||
|
|
||||||
|
1. Custom start (will need P06 for keypad — this phase may expose a 5 s auto-demo **behind `#ifdef AUTOFILM_MOTION_SMOKE` default off**). Do not enable smoke in production sdkconfig.
|
||||||
|
2. Logic-analyzer optional. Minimum: compile + review EN init HIGH.
|
||||||
|
|
||||||
|
## COMMITS
|
||||||
|
|
||||||
|
1. `Add Arduino-as-component and WROOM motor HAL with cooperative stop`
|
||||||
|
2. `Add temp and audio tasks off the UI path`
|
||||||
|
|
||||||
|
## DoD
|
||||||
|
|
||||||
|
- [x] EN HIGH at init and on stop
|
||||||
|
- [x] stop_req checked inside step loop
|
||||||
|
- [x] OneWire only in temp_task
|
||||||
|
- [x] tone/LEDC only in audio_task
|
||||||
|
- [x] STATUS→DONE
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
# P06 — WROOM UI cutover (2004 + keypad + Stop/Resume)
|
||||||
|
|
||||||
|
STATUS: DONE
|
||||||
|
DEPENDS: P05
|
||||||
|
READ: this file, `src/menu.cpp`, `src/display.cpp`, `src/config.cpp`, `docs/TARGET_ARCHITECTURE.md` (screens + stop rules)
|
||||||
|
OUT: S3 LVGL, NVS profiles, keeping blocking `getEntEscInput` as the live path
|
||||||
|
FORBIDDEN: `while(true)` keypad wait in machine; `malloc` for mm:ss; `vTaskDelete`; WDT off
|
||||||
|
|
||||||
|
## IN
|
||||||
|
|
||||||
|
Replace live control path with `input_task` + `ui_task` + `app_ui` screens on 20×4. Legacy `src/*.cpp` may remain unlinked. `main` no longer calls `startingMenu()`.
|
||||||
|
|
||||||
|
After this phase the WROOM firmware **is** the product.
|
||||||
|
|
||||||
|
## FILES
|
||||||
|
|
||||||
|
```
|
||||||
|
components/hal_display/include/hal_display.h
|
||||||
|
components/hal_display/hal_display_lcd2004.cpp
|
||||||
|
components/hal_input/include/hal_input.h
|
||||||
|
components/hal_input/hal_input_keypad.cpp
|
||||||
|
components/app_ui/include/app_ui.h
|
||||||
|
components/app_ui/app_ui.c
|
||||||
|
components/app_ui/screens.h
|
||||||
|
```
|
||||||
|
|
||||||
|
C++ only in hal_display / hal_input.
|
||||||
|
|
||||||
|
## hal_display.h
|
||||||
|
|
||||||
|
```c
|
||||||
|
void hal_display_init(void);
|
||||||
|
void hal_display_clear(void);
|
||||||
|
void hal_display_text(int col, int row, const char *s); /* clip to 20 cols */
|
||||||
|
void hal_display_glyph_thermo(int col, int row);
|
||||||
|
void hal_display_flush(void); /* no-op */
|
||||||
|
```
|
||||||
|
|
||||||
|
LCD: I2C 21/22 addr 0x27, 20x4, createChar thermometer from `config.cpp` bytes.
|
||||||
|
|
||||||
|
mm:ss: stack buffer `char[6]`, `snprintf "%02d:%02d"`. Never malloc.
|
||||||
|
|
||||||
|
## hal_input.h
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct { char key; } ui_raw_key_t;
|
||||||
|
void hal_input_init(void);
|
||||||
|
bool hal_input_pop_key(ui_raw_key_t *out); /* nonblocking */
|
||||||
|
```
|
||||||
|
|
||||||
|
Keypad map **byte-identical** to `src/config.cpp`. Scan in `input_task` 25 Hz. Debounce via Keypad lib.
|
||||||
|
|
||||||
|
Map:
|
||||||
|
|
||||||
|
| key | cmd |
|
||||||
|
|---|---|
|
||||||
|
| 1..6 | SELECT_PROCESS id 0..5 |
|
||||||
|
| U/D | BROWSE_STEP ±1 |
|
||||||
|
| L/R | ADJUST_STEP_TIME ±5 |
|
||||||
|
| E | ARM if STEP_SELECT; START if ARMED; RESUME if STOPPED; ARM next if COMPLETE |
|
||||||
|
| X | CANCEL_ARMED if ARMED; STOP if RUNNING or COMPLETE; RETURN_TO_STEP_SELECT if STOPPED; ignore IDLE |
|
||||||
|
|
||||||
|
**X in RUNNING:** `hal_motor_request_stop()` **in input_task immediately**, then queue CMD_STOP. Dual-path required.
|
||||||
|
|
||||||
|
## Screens (20×4)
|
||||||
|
|
||||||
|
`ProgramSelect` (IDLE):
|
||||||
|
|
||||||
|
```
|
||||||
|
Select Programme:
|
||||||
|
1. C41 4. ECN-2
|
||||||
|
2. E6 5. Custom
|
||||||
|
3. B&W 6. B&W Rev
|
||||||
|
```
|
||||||
|
|
||||||
|
`StepSelect`:
|
||||||
|
|
||||||
|
```
|
||||||
|
Step Time Temp
|
||||||
|
<name> mm:ss nnC
|
||||||
|
Scroll / Esc / Ent
|
||||||
|
🌡 tt.tC row3 col13 thermo+temp or --
|
||||||
|
```
|
||||||
|
|
||||||
|
`Armed`: name, mm:ss, pref C, `Ent:start Esc:quit`, temp row3
|
||||||
|
|
||||||
|
`Running`: name row0, `Remaining: mm:ss` row1, temp row3. Update remaining ≥4 Hz from events, **not** by blocking. No `delay(410)`.
|
||||||
|
|
||||||
|
`Stopped`: name, remaining, `E:resume X:back`, temp row3
|
||||||
|
|
||||||
|
`Complete`: name `Done`, then alarm (nonblocking). `E` next / `X` back.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
| task | prio |
|
||||||
|
|---|---|
|
||||||
|
| input_task | high |
|
||||||
|
| machine_task | mid-high; `handle_cmd` + `tick(esp_timer_get_time/1000)` 50 Hz |
|
||||||
|
| ui_task | mid; drain events, dirty draw |
|
||||||
|
| motor/temp/audio | as P05 |
|
||||||
|
|
||||||
|
TWDT: register tasks that run long loops or rely on idle.
|
||||||
|
|
||||||
|
## Cutover
|
||||||
|
|
||||||
|
`main.c`: init display/input, splash `AUTOFILM` + git describe 1 s **via vTaskDelay not busy Arduino delay in ui_task after scheduler start**. Unlink `src/AutoFilmESP32.cpp` from IDF (leave files on disk).
|
||||||
|
|
||||||
|
`platformio.ini` may stay; do not promise PIO still builds.
|
||||||
|
|
||||||
|
## VERIFY
|
||||||
|
|
||||||
|
Host tests pass.
|
||||||
|
|
||||||
|
WROOM idf build pass.
|
||||||
|
|
||||||
|
Hardware smoke:
|
||||||
|
|
||||||
|
1. Select Custom, Ent to arm, Ent to start 10 s step, remaining counts, temp updates
|
||||||
|
2. Mid-step X: motor stops at once, Stopped screen, E resumes remaining, X returns to list
|
||||||
|
3. Disconnect DS18B20: `--` still navigable
|
||||||
|
4. Complete alarm: X cancels and returns; UI not frozen 7.5 s
|
||||||
|
|
||||||
|
If no hardware: still land; Notes=`hw smoke pending`; do not skip the input dual-path stop.
|
||||||
|
|
||||||
|
## COMMITS
|
||||||
|
|
||||||
|
1. `Add 2004 display and keypad HAL`
|
||||||
|
2. `Add app_ui screens driven by machine events`
|
||||||
|
3. `Cut over WROOM main to RTOS UI and stop-safe input`
|
||||||
|
|
||||||
|
## DoD
|
||||||
|
|
||||||
|
- [x] Esc during run always EN-off
|
||||||
|
- [x] No malloc mm:ss
|
||||||
|
- [x] No blocking alarm on UI/machine
|
||||||
|
- [x] Legacy menus not in the IDF link
|
||||||
|
- [x] STATUS→DONE
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Notes=`hw smoke pending`. Host ctest green. WROOM `idf.py` build attempted in sandbox without ESP-IDF installed.
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# P07 — JC4827W543 display + GT911 + STOP
|
||||||
|
|
||||||
|
STATUS: DONE
|
||||||
|
DEPENDS: P06
|
||||||
|
|
||||||
|
Custom-draw NV3041A + GT911 colour adapter for the five P06 screens.
|
||||||
|
Touch STOP = `hal_motor_request_stop()` then CMD_STOP via the same key map as Esc.
|
||||||
|
Motor EN remains GPIO_NUM_NC (no spare header pin documented). No STEP/DIR.
|
||||||
|
No LVGL. Hw smoke: unflashed.
|
||||||
|
|
||||||
|
READ: this file, `docs/TARGET_ARCHITECTURE.md` dual-board + UI strategy, vendor pin notes below
|
||||||
|
OUT: LVGL in `app_machine`; changing recipes; assigning WROOM STEP/DIR onto S3 without a pin table comment; pumps
|
||||||
|
FORBIDDEN: `lv_*` types in `ui_cmd.h` / `app_machine.h`; blocking in flush_cb beyond DMA wait; copying WROOM GPIO map blindly
|
||||||
|
|
||||||
|
## IN
|
||||||
|
|
||||||
|
S3 firmware shows the **same five screens** via a colour adapter. Touch STOP = same path as Esc (`hal_motor_request_stop` + CMD_STOP). Custom draw first (no LVGL unless bring-up is otherwise blocked — if LVGL used, confine to `hal_display` + `app_ui` board files).
|
||||||
|
|
||||||
|
Mechatronics on S3: keep motor EN `GPIO_NUM_NC` **unless** a spare pin is documented in `board.c` comments with the header pin number. Do not invent STEP/DIR. S3 image is operator-panel-capable; agitation may remain on WROOM until pins proven.
|
||||||
|
|
||||||
|
## Pins (lock from community JC4827W543C)
|
||||||
|
|
||||||
|
Display NV3041A QSPI: CS 45, SCK 47, D0 21, D1 48, D2 40, D3 39, BL 1, 480×272.
|
||||||
|
|
||||||
|
GT911 I2C: SDA 8, SCL 4, INT 3, RST 38, addr 0x5D. Reset: RST low 200 ms then high; INT as input.
|
||||||
|
|
||||||
|
PSRAM octal 80 MHz already in `sdkconfig.s3`.
|
||||||
|
|
||||||
|
Free IO is scarce. Do not steal QSPI/GT911 pins for motor.
|
||||||
|
|
||||||
|
## FILES
|
||||||
|
|
||||||
|
```
|
||||||
|
components/board_jc4827w543/board.c # update comments/pins
|
||||||
|
components/hal_display/hal_display_nv3041a.c # or .cpp
|
||||||
|
components/hal_input/hal_input_gt911.c
|
||||||
|
components/app_ui/app_ui_color.c # or ifdefs in app_ui.c
|
||||||
|
```
|
||||||
|
|
||||||
|
`hal_display_text` still works (glyph blit at cell grid 24×8 or similar) so `app_ui.c` can stay shared. Prefer: `app_ui` calls abstract `ui_draw_program_select()` implemented per board.
|
||||||
|
|
||||||
|
STOP hit-test: rectangle bottom of Running/Stopped/Complete screens, label `STOP`. On touch inside: `hal_motor_request_stop()` then CMD_STOP (same as keypad X). Resume/Back buttons on Stopped.
|
||||||
|
|
||||||
|
## VERIFY
|
||||||
|
|
||||||
|
S3 `idf.py` build pass.
|
||||||
|
|
||||||
|
WROOM build still pass (do not regress P06).
|
||||||
|
|
||||||
|
Host tests pass.
|
||||||
|
|
||||||
|
Hardware S3: ProgramSelect visible, tap process, Running shows name/remaining/temp, STOP stops if motor wired else still transitions UI to Stopped.
|
||||||
|
|
||||||
|
## COMMITS
|
||||||
|
|
||||||
|
1. `Add NV3041A and GT911 HAL for jc4827w543`
|
||||||
|
2. `Map colour screens and STOP hit-test to existing commands`
|
||||||
|
|
||||||
|
## DoD
|
||||||
|
|
||||||
|
- [x] Same commands as keypad
|
||||||
|
- [x] No lv types in machine/process/cmd headers
|
||||||
|
- [x] WROOM firmware unchanged in behaviour
|
||||||
|
- [x] STATUS→DONE
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# P08 — Arduino debt burn-down
|
||||||
|
|
||||||
|
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
|
||||||
|
FORBIDDEN: behaviour change of times/stop/resume; dropping cooperative stop
|
||||||
|
|
||||||
|
## IN
|
||||||
|
|
||||||
|
Replace Arduino libs with IDF peripherals **behind the same HAL**. Remove Arduino-as-component from the S3 image first, then WROOM.
|
||||||
|
|
||||||
|
Order (one commit each, tests+builds green after each):
|
||||||
|
|
||||||
|
1. **Audio:** LEDC / `ledc_timer` tones; delete `tone()`.
|
||||||
|
2. **Motor:** RMT TX pulse train for STEP, GPIO DIR/EN. `hal_motor_request_stop` aborts RMT + EN HIGH. Delete AccelStepper. Same 4800 steps/rev, 60 RPM, accel ≈9600 (RMT may use a short ramp table).
|
||||||
|
3. **Temp:** RMT or `onewire_bus` IDF component; keep +0.4 °C offset. Delete Dallas/OneWire Arduino.
|
||||||
|
4. **Keypad:** GPIO matrix scan in `hal_input` (no Keypad lib). Same keymap.
|
||||||
|
5. **LCD 2004:** IDF I2C + HD44780 nibble commands (or `esp_lcd` panel io i2c). Delete LiquidCrystal_I2C.
|
||||||
|
6. Drop `espressif/arduino-esp32` from `idf_component.yml` when nothing includes Arduino.h.
|
||||||
|
|
||||||
|
Do not change `hal_*` public headers except adding RMT-specific internals in `.c`.
|
||||||
|
|
||||||
|
## VERIFY
|
||||||
|
|
||||||
|
Host tests pass (stubs unchanged).
|
||||||
|
|
||||||
|
Both boards build without Arduino component.
|
||||||
|
|
||||||
|
Hardware WROOM: Custom 10 s, Esc mid-step EN off, Resume remaining, temp display, beep Stop/Resume, complete alarm cancellable.
|
||||||
|
|
||||||
|
## COMMITS
|
||||||
|
|
||||||
|
Per numbered item above, e.g. `Replace AccelStepper with RMT step pulse HAL`.
|
||||||
|
|
||||||
|
## DoD
|
||||||
|
|
||||||
|
- [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.
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
if(NOT DEFINED AUTOFILM_BOARD)
|
||||||
|
set(AUTOFILM_BOARD wroom CACHE STRING "wroom|jc4827w543")
|
||||||
|
endif()
|
||||||
|
if(IDF_TARGET STREQUAL "esp32s3" OR AUTOFILM_BOARD STREQUAL "jc4827w543")
|
||||||
|
set(BOARD_COMP board_jc4827w543)
|
||||||
|
elseif(AUTOFILM_BOARD STREQUAL "wroom" OR IDF_TARGET STREQUAL "esp32")
|
||||||
|
set(BOARD_COMP board_wroom)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "AUTOFILM_BOARD=${AUTOFILM_BOARD} IDF_TARGET=${IDF_TARGET}")
|
||||||
|
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
|
||||||
|
PRIV_REQUIRES esp_timer)
|
||||||
|
|
||||||
|
if(IDF_TARGET STREQUAL "esp32")
|
||||||
|
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_BOARD_WROOM=1)
|
||||||
|
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||||
|
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_BOARD_S3=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
execute_process(
|
||||||
|
COMMAND git describe --always --dirty
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
OUTPUT_VARIABLE AUTOFILM_GIT_DESC
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
ERROR_QUIET
|
||||||
|
)
|
||||||
|
if(NOT AUTOFILM_GIT_DESC)
|
||||||
|
set(AUTOFILM_GIT_DESC "dev")
|
||||||
|
endif()
|
||||||
|
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_GIT_DESC="${AUTOFILM_GIT_DESC}")
|
||||||
|
|
||||||
|
option(AUTOFILM_MOTION_SMOKE "5s auto-demo on WROOM (bring-up only)" OFF)
|
||||||
|
if(AUTOFILM_MOTION_SMOKE)
|
||||||
|
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_MOTION_SMOKE=1)
|
||||||
|
endif()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dependencies: {}
|
||||||
+256
@@ -0,0 +1,256 @@
|
|||||||
|
#include "board.h"
|
||||||
|
#include "app_process.h"
|
||||||
|
#include "app_machine.h"
|
||||||
|
#include "app_ui.h"
|
||||||
|
#include "hal_motor.h"
|
||||||
|
#include "hal_temp.h"
|
||||||
|
#include "hal_audio.h"
|
||||||
|
#include "hal_display.h"
|
||||||
|
#include "hal_input.h"
|
||||||
|
#include "ui_cmd.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "esp_task_wdt.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
|
#ifndef AUTOFILM_GIT_DESC
|
||||||
|
#define AUTOFILM_GIT_DESC "dev"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const char *TAG = "app";
|
||||||
|
|
||||||
|
#if defined(AUTOFILM_BOARD_WROOM) || defined(AUTOFILM_BOARD_S3)
|
||||||
|
static QueueHandle_t s_cmdq;
|
||||||
|
|
||||||
|
static void map_and_post_key(char key)
|
||||||
|
{
|
||||||
|
ui_cmd_t cmd;
|
||||||
|
memset(&cmd, 0, sizeof(cmd));
|
||||||
|
machine_state_t st = app_machine_state();
|
||||||
|
|
||||||
|
if (key >= '1' && key <= '6') {
|
||||||
|
cmd.id = CMD_SELECT_PROCESS;
|
||||||
|
cmd.process_id = (uint8_t)(key - '1');
|
||||||
|
xQueueSend(s_cmdq, &cmd, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 'U':
|
||||||
|
cmd.id = CMD_BROWSE_STEP;
|
||||||
|
cmd.step_delta = -1;
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
cmd.id = CMD_BROWSE_STEP;
|
||||||
|
cmd.step_delta = 1;
|
||||||
|
break;
|
||||||
|
case 'L':
|
||||||
|
cmd.id = CMD_ADJUST_STEP_TIME;
|
||||||
|
cmd.time_delta_s = -5;
|
||||||
|
break;
|
||||||
|
case 'R':
|
||||||
|
cmd.id = CMD_ADJUST_STEP_TIME;
|
||||||
|
cmd.time_delta_s = 5;
|
||||||
|
break;
|
||||||
|
case 'E':
|
||||||
|
if (st == ST_STEP_SELECT) {
|
||||||
|
cmd.id = CMD_ARM_STEP;
|
||||||
|
cmd.step_index = app_machine_step_index();
|
||||||
|
} else if (st == ST_ARMED) {
|
||||||
|
cmd.id = CMD_START_STEP;
|
||||||
|
} else if (st == ST_STOPPED) {
|
||||||
|
cmd.id = CMD_RESUME;
|
||||||
|
} else if (st == ST_COMPLETE) {
|
||||||
|
cmd.id = CMD_ARM_STEP;
|
||||||
|
cmd.step_index = app_machine_step_index();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'X':
|
||||||
|
if (st == ST_ARMED) {
|
||||||
|
cmd.id = CMD_CANCEL_ARMED;
|
||||||
|
} else if (st == ST_RUNNING) {
|
||||||
|
/* Dual-path stop: EN-off in this task, then CMD_STOP. */
|
||||||
|
hal_motor_request_stop();
|
||||||
|
cmd.id = CMD_STOP;
|
||||||
|
} else if (st == ST_COMPLETE) {
|
||||||
|
cmd.id = CMD_STOP;
|
||||||
|
} else if (st == ST_STOPPED) {
|
||||||
|
cmd.id = CMD_RETURN_TO_STEP_SELECT;
|
||||||
|
} else {
|
||||||
|
return; /* ignore IDLE */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xQueueSend(s_cmdq, &cmd, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void input_task(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
esp_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;
|
||||||
|
while (hal_input_pop_key(&raw)) {
|
||||||
|
map_and_post_key(raw.key);
|
||||||
|
}
|
||||||
|
ui_raw_touch_t touch;
|
||||||
|
while (hal_input_pop_touch(&touch)) {
|
||||||
|
char key;
|
||||||
|
if (app_ui_touch_to_key(touch.x, touch.y, &key)) {
|
||||||
|
map_and_post_key(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
vTaskDelay(period);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void machine_task(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
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;
|
||||||
|
while (xQueueReceive(s_cmdq, &cmd, 0) == pdTRUE) {
|
||||||
|
app_machine_handle_cmd(&cmd);
|
||||||
|
}
|
||||||
|
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000ULL);
|
||||||
|
app_machine_tick(now_ms);
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
vTaskDelay(period);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_task(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
esp_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");
|
||||||
|
hal_display_text(0, 1, AUTOFILM_GIT_DESC);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
|
||||||
|
app_ui_init();
|
||||||
|
app_ui_draw();
|
||||||
|
|
||||||
|
const TickType_t period = pdMS_TO_TICKS(25);
|
||||||
|
for (;;) {
|
||||||
|
ui_evt_t ev;
|
||||||
|
while (app_machine_last_event(&ev) == 1) {
|
||||||
|
app_ui_on_event(&ev);
|
||||||
|
}
|
||||||
|
/* Running remaining is read from the machine on each draw (>=4 Hz). */
|
||||||
|
if (app_ui_is_dirty() || app_machine_state() == ST_RUNNING) {
|
||||||
|
app_ui_draw();
|
||||||
|
}
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
vTaskDelay(period);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void temp_task(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
esp_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 (;;) {
|
||||||
|
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
|
||||||
|
|
||||||
|
void app_main(void)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "board=%s desc=%s", board_name(), AUTOFILM_GIT_DESC);
|
||||||
|
|
||||||
|
gpio_num_t en_pin = board_pin_motor_en();
|
||||||
|
if (en_pin != GPIO_NUM_NC) {
|
||||||
|
gpio_config_t io = {
|
||||||
|
.pin_bit_mask = 1ULL << en_pin,
|
||||||
|
.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(en_pin, board_motor_en_disable_level());
|
||||||
|
}
|
||||||
|
|
||||||
|
app_process_init();
|
||||||
|
hal_motor_init();
|
||||||
|
hal_temp_init();
|
||||||
|
hal_audio_init();
|
||||||
|
app_machine_init();
|
||||||
|
|
||||||
|
#if defined(AUTOFILM_BOARD_WROOM) || defined(AUTOFILM_BOARD_S3)
|
||||||
|
hal_display_init();
|
||||||
|
hal_input_init();
|
||||||
|
app_ui_init();
|
||||||
|
|
||||||
|
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);
|
||||||
|
xTaskCreate(ui_task, "ui", 4096, NULL, 4, NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* app_main returns; scheduler keeps product tasks. No legacy startingMenu. */
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
CONFIG_ESP_TASK_WDT_EN=y
|
||||||
|
CONFIG_ESP_TASK_WDT_TIMEOUT_S=10
|
||||||
|
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
CONFIG_IDF_TARGET="esp32s3"
|
||||||
|
CONFIG_SPIRAM=y
|
||||||
|
CONFIG_SPIRAM_MODE_OCT=y
|
||||||
|
CONFIG_SPIRAM_SPEED_80M=y
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
CONFIG_IDF_TARGET="esp32"
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
project(autofilm_host_tests C)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
set(HOST_INCLUDES
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../components/app_process/include
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../components/app_machine/include
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../components/ui_cmd/include
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../components/hal_motor/include
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../components/hal_temp/include
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../components/hal_audio/include
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(test_process
|
||||||
|
test_process.c
|
||||||
|
main.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../components/app_process/app_process.c
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(test_process PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../components/app_process/include
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../components/ui_cmd/include
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(test_process PRIVATE AUTOFILM_HOST=1)
|
||||||
|
target_compile_features(test_process PRIVATE c_std_11)
|
||||||
|
target_link_libraries(test_process PRIVATE m)
|
||||||
|
add_test(NAME test_process COMMAND test_process)
|
||||||
|
|
||||||
|
add_executable(test_machine
|
||||||
|
test_machine.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../components/app_process/app_process.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../components/app_machine/app_machine.c
|
||||||
|
stubs/hal_motor.c
|
||||||
|
stubs/hal_temp.c
|
||||||
|
stubs/hal_audio.c
|
||||||
|
freertos/queue.c
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(test_machine PRIVATE ${HOST_INCLUDES})
|
||||||
|
target_compile_definitions(test_machine PRIVATE AUTOFILM_HOST=1)
|
||||||
|
target_compile_features(test_machine PRIVATE c_std_11)
|
||||||
|
target_link_libraries(test_machine PRIVATE m)
|
||||||
|
add_test(NAME test_machine COMMAND test_machine)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef int esp_err_t;
|
||||||
|
#define ESP_OK 0
|
||||||
|
#define ESP_FAIL -1
|
||||||
|
#define ESP_ERR_NOT_FOUND 0x105
|
||||||
|
#define ESP_ERR_INVALID_ARG 0x102
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user