33 Commits
Author SHA1 Message Date
gronod d70df219f8 Mark I04 status DONE
ci / test (pull_request) Successful in 1m16s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Successful in 4m4s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Successful in 4m5s
ci / test (push) Successful in 1m13s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m15s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m19s
2026-09-16 22:28:17 +01:00
gronod 7e854e10f0 Add I2S speaker audio for S3
ci / test (push) Successful in 1m14s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m26s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m28s
2026-09-16 22:20:53 +01:00
gronod b6c385e09c Mark I03 status DONE
ci / test (pull_request) Successful in 1m14s
ci / test (push) Successful in 1m17s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Successful in 3m48s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Successful in 3m57s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m4s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m8s
2026-09-16 21:58:26 +01:00
gronod a9f76ea058 Share RMT motor HAL across both boards
ci / test (push) Successful in 1m18s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m30s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m39s
2026-09-16 21:51:32 +01:00
gronod c40c714028 Mark I02 status DONE
ci / test (pull_request) Successful in 1m13s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Successful in 4m39s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Successful in 4m41s
ci / test (push) Successful in 1m8s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m9s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m15s
2026-09-16 21:31:03 +01:00
gronod f87ab0c65f Fix temp_task starvation bug on non-WROOM
ci / test (push) Successful in 1m9s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m9s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m10s
2026-09-16 21:21:12 +01:00
gronod b4230b2a0e Share onewire temp HAL across both boards 2026-09-16 21:20:18 +01:00
gronod 04e1ed0f16 Merge pull request 'I01 board pin allocations & accessors' (#12) from feature/integration-I01 into develop
ci / test (push) Successful in 1m14s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m46s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m48s
Merge I01 board pin allocations & accessors into develop
2026-09-16 21:15:46 +01:00
gronod a33b4b0c74 Mark I01 status DONE
ci / test (push) Successful in 1m4s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 3m56s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 3m57s
ci / test (pull_request) Successful in 1m22s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Successful in 4m42s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Successful in 4m47s
2026-09-16 21:05:57 +01:00
gronod 73f3158b27 Update pin audit documentation labels
ci / test (push) Successful in 1m3s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 3m58s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m1s
2026-09-16 20:43:25 +01:00
gronod a3fe809359 Lock S3 header pins and introduce shared board pin accessors 2026-09-16 20:43:24 +01:00
gronod 637ea8cb6e Update .gitea/workflows/ci.yml
ci / test (pull_request) Successful in 1m4s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Successful in 8m1s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Successful in 8m4s
ci / test (push) Successful in 1m11s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m57s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m57s
2026-09-16 16:45:58 +01:00
gronod 84b60155dd Update components/app_ui/app_ui.c
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Canceled after 0s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Canceled after 0s
ci / test (push) Canceled after 15s
ci / test (pull_request) Successful in 1m15s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Failing after 6m1s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Failing after 6m5s
2026-09-16 16:34:14 +01:00
gronod 7406114368 Update main/CMakeLists.txt
ci / test (push) Successful in 1m4s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Canceled after 23s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Canceled after 23s
ci / test (pull_request) Successful in 1m7s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Failing after 5m41s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Failing after 5m45s
2026-09-16 16:14:53 +01:00
gronod 0b0a713098 Update .gitea/workflows/ci.yml
ci / test (push) Successful in 1m14s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 6m46s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 6m46s
2026-09-16 15:54:32 +01:00
gronod 9ea89ac467 Update .gitea/workflows/ci.yml
ci / test (push) Successful in 54s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 1m39s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 1m39s
2026-09-16 15:50:13 +01:00
gronod 5f1727da7d Update .gitea/workflows/ci.yml
ci / test (push) Successful in 1m3s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Canceled after 4m17s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Canceled after 4m17s
2026-09-16 15:44:22 +01:00
gronod 32af44a702 Drop espressif/arduino-esp32 from IDF firmware
ci / test (pull_request) Successful in 1m22s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Failing after 33s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Failing after 37s
ci / test (push) Successful in 1m19s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 30s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 32s
2026-09-16 14:34:25 +00:00
gronod 82f55af4b1 Replace LiquidCrystal_I2C with IDF I2C HD44780 HAL 2026-09-16 14:33:57 +00:00
gronod 880f9a18c5 Replace Keypad lib with GPIO matrix scan HAL 2026-09-16 14:33:20 +00:00
gronod e04d0f0e7f Replace Dallas/OneWire with onewire_bus temp HAL 2026-09-16 14:33:07 +00:00
gronod 114135fba0 Replace AccelStepper with RMT step pulse HAL 2026-09-16 14:32:46 +00:00
gronod 4fe0648005 Replace Arduino tone with LEDC audio HAL 2026-09-16 14:32:12 +00:00
gronod ce9af6382e Map colour screens and STOP hit-test to existing commands
ci / test (pull_request) Successful in 1m15s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Failing after 30s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Failing after 34s
ci / test (push) Successful in 1m18s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 30s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 29s
Same five screens via text grid plus colour softkeys. Touch STOP
follows Esc: request_stop then CMD_STOP. P07 STATUS DONE.
2026-09-16 14:18:35 +00:00
gronod d21f7ad4e4 Add NV3041A and GT911 HAL for jc4827w543 2026-09-16 14:17:47 +00:00
gronod f47299b939 Cut over WROOM main to RTOS UI and stop-safe input
ci / test (push) Successful in 1m16s
ci / test (pull_request) Successful in 1m19s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 29s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Failing after 30s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Failing after 30s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 33s
input_task drops motor EN immediately on X while RUNNING, then queues
CMD_STOP. machine_task ticks at 50 Hz. startingMenu is not linked.
STATUS DONE. Notes=hw smoke pending.
2026-09-16 14:05:21 +00:00
gronod 51faa0fe59 Add app_ui screens driven by machine events
Program/step/armed/running/stopped/complete layouts on 20x4. mm:ss uses
a stack buffer. Temp row shows thermo+value or --.
2026-09-16 14:05:21 +00:00
gronod a19accf6a7 Add 2004 display and keypad HAL
Vendor Keypad 3.1.1 and LiquidCrystal_I2C for WROOM. 20x4 I2C display
and 5x4 keypad map match src/config.cpp. Stubs keep the S3 link.
2026-09-16 14:05:21 +00:00
gronod 2c61e3576f Mark P07 BLOCKED pending P06 app_ui cutover
ci / test (push) Successful in 2m58s
ci / test (pull_request) Successful in 2m59s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 42s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Failing after 46s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 47s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Failing after 47s
P07 requires the P06 five-screen contract. app_ui, hal_display, and
hal_input are absent; no S3 colour adapter landed.
2026-09-16 14:01:17 +00:00
gronod 0b1d14d21a Add temp and audio tasks off the UI path
ci / test (push) Successful in 2m10s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 1m19s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 1m19s
ci / test (pull_request) Successful in 1m58s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Failing after 9m11s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Failing after 9m12s
2026-09-16 13:07:51 +00:00
gronod e245db175b Add Arduino-as-component and WROOM motor HAL with cooperative stop 2026-09-16 13:06:44 +00:00
gronod 57ca78a1e3 Mark P06 BLOCKED pending P05 device HAL and tasks
ci / test (push) Successful in 28s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 53s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 53s
P06 requires WROOM motor/temp/audio implementations and tasks.
Branch has HAL headers and host stubs only. Stop without implementing P01–P05.
2026-09-16 12:36:32 +00:00
gronod 674d067872 Install cmake and gcc in Gitea host-test job
ci / test (push) Successful in 32s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 51s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 51s
gitea/runner-images:ubuntu-latest has no cmake; test job exited 127
before firmware matrix could run.
2026-09-16 12:31:37 +00:00
69 changed files with 8301 additions and 56 deletions
+7 -6
View File
@@ -19,7 +19,7 @@ jobs:
export DEBIAN_FRONTEND=noninteractive && \ export DEBIAN_FRONTEND=noninteractive && \
if command -v sudo >/dev/null 2>&1 && [ "$(id -u)" -ne 0 ]; then SUDO=sudo; else SUDO=; fi && \ 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 update && \
$SUDO apt-get install -y --no-install-recommends cmake gcc g++ make $SUDO apt-get install -y -q --no-install-recommends cmake gcc g++ make
- name: Host unit tests - name: Host unit tests
if: hashFiles('tests/host/CMakeLists.txt') != '' if: hashFiles('tests/host/CMakeLists.txt') != ''
run: | run: |
@@ -46,15 +46,15 @@ jobs:
- name: Install Node.js - name: Install Node.js
run: | run: |
export DEBIAN_FRONTEND=noninteractive && \ 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 && \ 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 update && \
$SUDO apt-get install -y --no-install-recommends curl ca-certificates && \ $SUDO apt-get install -y -q --no-install-recommends nodejs
curl -fsSL https://deb.nodesource.com/setup_20.x | $SUDO bash - && \
$SUDO apt-get install -y --no-install-recommends nodejs
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Build - name: Build
if: hashFiles('CMakeLists.txt') != '' if: hashFiles('CMakeLists.txt') != ''
run: | run: |
. $IDF_PATH/export.sh && \
git config --global --add safe.directory '*' && \ git config --global --add safe.directory '*' && \
idf.py -B build/${{ matrix.board }} set-target ${{ matrix.target }} && \ idf.py -B build/${{ matrix.board }} set-target ${{ matrix.target }} && \
idf.py -B build/${{ matrix.board }} \ idf.py -B build/${{ matrix.board }} \
@@ -62,10 +62,11 @@ jobs:
-D AUTOFILM_BOARD=${{ matrix.board }} \ -D AUTOFILM_BOARD=${{ matrix.board }} \
build && \ build && \
idf.py -B build/${{ matrix.board }} size | tee size-${{ matrix.board }}.txt && \ 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 \ python $IDF_PATH/components/esptool_py/esptool/esptool.py \
--chip ${{ matrix.target }} merge_bin \ --chip ${{ matrix.target }} merge_bin \
-o autofilm-${{ matrix.board }}-${{ gitea.sha }}.bin \ -o ../../autofilm-${{ matrix.board }}-${{ gitea.sha }}.bin \
@build/${{ matrix.board }}/flash_args @flash_args
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
if: hashFiles('CMakeLists.txt') != '' if: hashFiles('CMakeLists.txt') != ''
with: with:
+9
View File
@@ -1,3 +1,12 @@
cmake_minimum_required(VERSION 3.16) 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) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(autofilm) project(autofilm)
+2 -1
View File
@@ -1,3 +1,4 @@
idf_component_register(SRCS "app_machine.c" idf_component_register(SRCS "app_machine.c"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES ui_cmd app_process) REQUIRES ui_cmd app_process
PRIV_REQUIRES hal_motor hal_temp hal_audio)
+4
View File
@@ -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)
+200
View File
@@ -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;
}
+99
View File
@@ -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;
}
}
+20
View File
@@ -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
+11
View File
@@ -0,0 +1,11 @@
#pragma once
/* Screen ids used by app_ui. Layouts are 20x4 WROOM strings. */
typedef enum {
UI_SCREEN_PROGRAM_SELECT = 0,
UI_SCREEN_STEP_SELECT,
UI_SCREEN_ARMED,
UI_SCREEN_RUNNING,
UI_SCREEN_STOPPED,
UI_SCREEN_COMPLETE
} app_ui_screen_t;
+54 -1
View File
@@ -1,5 +1,37 @@
#include "board.h" #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) const char *board_name(void)
{ {
return "jc4827w543"; return "jc4827w543";
@@ -7,10 +39,31 @@ const char *board_name(void)
gpio_num_t board_pin_motor_en(void) gpio_num_t board_pin_motor_en(void)
{ {
return GPIO_NUM_NC; return (gpio_num_t)7;
} }
int board_motor_en_disable_level(void) int board_motor_en_disable_level(void)
{ {
return 1; 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; }
@@ -1,5 +1,25 @@
#pragma once #pragma once
#include "driver/gpio.h" #include "driver/gpio.h"
const char *board_name(void); const char *board_name(void);
gpio_num_t board_pin_motor_en(void); gpio_num_t board_pin_motor_en(void);
int board_motor_en_disable_level(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);
+4
View File
@@ -14,3 +14,7 @@ int board_motor_en_disable_level(void)
{ {
return 1; return 1;
} }
gpio_num_t board_pin_motor_step(void) { return (gpio_num_t)12; }
gpio_num_t board_pin_motor_dir(void) { return (gpio_num_t)14; }
gpio_num_t board_pin_temp(void) { return (gpio_num_t)13; }
+1
View File
@@ -0,0 +1 @@
dependencies: {}
+3
View File
@@ -3,3 +3,6 @@
const char *board_name(void); const char *board_name(void);
gpio_num_t board_pin_motor_en(void); gpio_num_t board_pin_motor_en(void);
int board_motor_en_disable_level(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);
+17
View File
@@ -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})
+212
View File
@@ -0,0 +1,212 @@
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "driver/i2s_std.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "board.h"
#define SPK_SAMPLE_RATE 48000
#define TONE_HZ 2000
#define TONE_PERIOD_SMP (SPK_SAMPLE_RATE / TONE_HZ) /* 24 samples */
#define CHUNK_MS 10
#define CHUNK_FRAMES (SPK_SAMPLE_RATE / 1000 * CHUNK_MS) /* 480, multiple of period */
#define TONE_AMPLITUDE 12000
#define DMA_DESC_NUM 4
#define DMA_FRAME_NUM 240
static const char *TAG = "audio";
enum {
AUDIO_SHORT = 1,
AUDIO_ALARM,
AUDIO_CANCEL,
};
static QueueHandle_t s_q;
static TaskHandle_t s_task;
static i2s_chan_handle_t s_tx;
static bool s_chan_on;
static bool s_inited;
static int16_t s_tone[CHUNK_FRAMES * 2];
static int16_t s_silence[CHUNK_FRAMES * 2];
static void chan_on(void)
{
if (!s_chan_on && s_tx != NULL) {
i2s_channel_enable(s_tx);
s_chan_on = true;
}
}
static void chan_off(void)
{
if (s_chan_on && s_tx != NULL) {
size_t w = 0;
i2s_channel_write(s_tx, s_silence, sizeof(s_silence), &w, 50);
i2s_channel_disable(s_tx);
s_chan_on = false;
}
}
static bool drain_cancel(void)
{
int msg;
while (xQueueReceive(s_q, &msg, 0) == pdTRUE) {
if (msg == AUDIO_CANCEL) {
return true;
}
}
return false;
}
/* Write buf for ~ms wall time. DMA pacing makes each 10 ms write block
* in this task only; the UI path never touches I2S. */
static bool play_buf_ms(const int16_t *buf, size_t len, uint32_t ms)
{
TickType_t t0 = xTaskGetTickCount();
while ((xTaskGetTickCount() - t0) < pdMS_TO_TICKS(ms)) {
if (drain_cancel()) {
return false;
}
size_t w = 0;
if (i2s_channel_write(s_tx, buf, len, &w, portMAX_DELAY) != ESP_OK) {
return false;
}
}
return true;
}
static void play_short(void)
{
chan_on();
play_buf_ms(s_tone, sizeof(s_tone), 100);
chan_off();
}
static void play_alarm(void)
{
chan_on();
for (int i = 0; i < 10; i++) {
if (!play_buf_ms(s_tone, sizeof(s_tone), 500)) {
break;
}
if (!play_buf_ms(s_silence, sizeof(s_silence), 250)) {
break;
}
}
chan_off();
}
static void audio_task(void *arg)
{
(void)arg;
int msg;
for (;;) {
if (xQueueReceive(s_q, &msg, portMAX_DELAY) != pdTRUE) {
continue;
}
if (msg == AUDIO_SHORT) {
play_short();
} else if (msg == AUDIO_ALARM) {
play_alarm();
} else if (msg == AUDIO_CANCEL) {
chan_off();
}
}
}
static void post(int msg)
{
if (s_q == NULL) {
return;
}
xQueueSend(s_q, &msg, 0);
}
void autofilm_audio_task(void *arg)
{
audio_task(arg);
}
void hal_audio_init(void)
{
if (s_inited) {
return;
}
gpio_num_t bclk = board_pin_spk_bclk();
gpio_num_t lrclk = board_pin_spk_lrclk();
gpio_num_t din = board_pin_spk_din();
if (bclk == GPIO_NUM_NC || lrclk == GPIO_NUM_NC || din == GPIO_NUM_NC) {
ESP_LOGW(TAG, "no speaker pins on this board");
s_inited = true;
return;
}
for (int i = 0; i < CHUNK_FRAMES; i++) {
int16_t v = ((i % TONE_PERIOD_SMP) < (TONE_PERIOD_SMP / 2))
? TONE_AMPLITUDE : -TONE_AMPLITUDE;
s_tone[i * 2] = v;
s_tone[i * 2 + 1] = v;
}
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
chan_cfg.dma_desc_num = DMA_DESC_NUM;
chan_cfg.dma_frame_num = DMA_FRAME_NUM;
if (i2s_new_channel(&chan_cfg, &s_tx, NULL) != ESP_OK) {
ESP_LOGE(TAG, "i2s_new_channel failed");
s_inited = true;
return;
}
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SPK_SAMPLE_RATE),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
I2S_SLOT_MODE_STEREO),
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = bclk,
.ws = lrclk,
.dout = din,
.din = I2S_GPIO_UNUSED,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
};
if (i2s_channel_init_std_mode(s_tx, &std_cfg) != ESP_OK) {
ESP_LOGE(TAG, "i2s_channel_init_std_mode failed");
i2s_del_channel(s_tx);
s_tx = NULL;
s_inited = true;
return;
}
s_q = xQueueCreate(8, sizeof(int));
if (s_task == NULL) {
xTaskCreate(audio_task, "audio", 3072, NULL, 2, &s_task);
}
s_inited = true;
ESP_LOGI(TAG, "init i2s bclk=%d ws=%d dout=%d", bclk, lrclk, din);
}
void hal_audio_beep_short(void)
{
post(AUDIO_SHORT);
}
void hal_audio_alarm_complete(void)
{
post(AUDIO_ALARM);
}
void hal_audio_alarm_cancel(void)
{
post(AUDIO_CANCEL);
}
+15
View File
@@ -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)
{
}
+163
View File
@@ -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);
}
+19
View File
@@ -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})
+100
View File
@@ -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
+23
View File
@@ -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;
}
+19
View File
@@ -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})
+116
View File
@@ -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;
}
+99
View File
@@ -0,0 +1,99 @@
#include "hal_input.h"
#include "driver/gpio.h"
#include "esp_log.h"
static const char *TAG = "input";
#define ROWS 5
#define COLS 4
static const gpio_num_t s_row_pins[ROWS] = {19, 18, 5, 17, 16};
static const gpio_num_t s_col_pins[COLS] = {15, 2, 0, 4};
static const char s_keys[ROWS][COLS] = {
{'F', 'E', '#', '*'},
{'1', '2', '3', 'U'},
{'4', '5', '6', 'D'},
{'7', '8', '9', 'X'},
{'L', '0', 'R', 'E'}};
static bool s_inited;
static char s_held;
static void cols_idle_high(void)
{
for (int c = 0; c < COLS; c++) {
gpio_set_level(s_col_pins[c], 1);
}
}
static char scan_once(void)
{
for (int c = 0; c < COLS; c++) {
cols_idle_high();
gpio_set_level(s_col_pins[c], 0);
for (int r = 0; r < ROWS; r++) {
if (gpio_get_level(s_row_pins[r]) == 0) {
cols_idle_high();
return s_keys[r][c];
}
}
}
cols_idle_high();
return 0;
}
void hal_input_init(void)
{
if (s_inited) {
return;
}
for (int r = 0; r < ROWS; r++) {
gpio_config_t io = {
.pin_bit_mask = 1ULL << s_row_pins[r],
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io);
}
for (int c = 0; c < COLS; c++) {
gpio_config_t io = {
.pin_bit_mask = 1ULL << s_col_pins[c],
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io);
gpio_set_level(s_col_pins[c], 1);
}
s_held = 0;
s_inited = true;
ESP_LOGI(TAG, "init 5x4 GPIO keypad map byte-identical to src/config.cpp");
}
bool hal_input_pop_key(ui_raw_key_t *out)
{
char k = scan_once();
if (k == 0) {
s_held = 0;
return false;
}
if (k == s_held) {
return false;
}
s_held = k;
if (out != NULL) {
out->key = k;
}
return true;
}
bool hal_input_pop_touch(ui_raw_touch_t *out)
{
(void)out;
return false;
}
+25
View File
@@ -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
+15
View File
@@ -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;
}
+19
View File
@@ -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})
+284
View File
@@ -0,0 +1,284 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include "esp_err.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_encoder.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "board.h"
#include "hal_motor.h"
#define STEPS_PER_REV 4800
#define DEFAULT_RPM 60
#define ACCEL 9600
#define RMT_RES_HZ 1000000
#define CRUISE_HZ ((DEFAULT_RPM * STEPS_PER_REV) / 60) /* 4800 */
#define RAMP_STAGES 8
#define BATCH 32
static const char *TAG = "motor";
static gpio_num_t s_pin_en = GPIO_NUM_NC;
static gpio_num_t s_pin_dir = GPIO_NUM_NC;
static gpio_num_t s_pin_step = GPIO_NUM_NC;
static int s_en_disable_level = 1;
static TaskHandle_t s_task;
static atomic_uint s_stop_req;
static float s_cw;
static float s_ccw;
static uint32_t s_rpm = DEFAULT_RPM;
static bool s_enabled;
static bool s_inited;
static rmt_channel_handle_t s_chan;
static rmt_encoder_handle_t s_enc;
static void en_disable(void)
{
if (s_pin_en != GPIO_NUM_NC) {
gpio_set_level(s_pin_en, s_en_disable_level);
}
s_enabled = false;
}
static void en_enable(void)
{
if (s_pin_en != GPIO_NUM_NC) {
gpio_set_level(s_pin_en, !s_en_disable_level);
}
s_enabled = true;
}
static void rmt_abort(void)
{
if (s_chan != NULL) {
rmt_disable(s_chan);
rmt_enable(s_chan);
}
}
static uint32_t period_us_for_hz(uint32_t hz)
{
if (hz < 100) {
hz = 100;
}
return RMT_RES_HZ / hz;
}
static bool emit_steps(uint32_t n, uint32_t hz)
{
if (n == 0) {
return true;
}
uint32_t period = period_us_for_hz(hz);
uint32_t half = period / 2;
if (half == 0) {
half = 1;
}
rmt_symbol_word_t sym = {
.duration0 = (uint16_t)half,
.level0 = 1,
.duration1 = (uint16_t)(period - half),
.level1 = 0,
};
while (n > 0) {
if (atomic_load(&s_stop_req) != 0) {
rmt_abort();
return false;
}
uint32_t chunk = n > BATCH ? BATCH : n;
rmt_symbol_word_t burst[BATCH];
for (uint32_t i = 0; i < chunk; i++) {
burst[i] = sym;
}
rmt_transmit_config_t tcfg = {
.loop_count = 0,
};
if (rmt_transmit(s_chan, s_enc, burst, chunk * sizeof(rmt_symbol_word_t), &tcfg) != ESP_OK) {
return false;
}
if (rmt_tx_wait_all_done(s_chan, 2000) != ESP_OK) {
rmt_abort();
return false;
}
n -= chunk;
}
return atomic_load(&s_stop_req) == 0;
}
static bool run_move(long steps)
{
if (steps == 0) {
return true;
}
int dir = (steps > 0) ? 1 : 0;
if (steps < 0) {
steps = -steps;
}
gpio_set_level(s_pin_dir, dir);
uint32_t cruise = (s_rpm * STEPS_PER_REV) / 60;
if (cruise == 0) {
cruise = CRUISE_HZ;
}
/* Short accel table: ACCEL ≈9600 steps/s^2 into cruise. */
uint32_t remaining = (uint32_t)steps;
uint32_t ramp = remaining / 8;
if (ramp > 200) {
ramp = 200;
}
if (ramp < RAMP_STAGES) {
ramp = (remaining < RAMP_STAGES) ? remaining : RAMP_STAGES;
}
uint32_t start_hz = cruise / 4;
if (start_hz < 200) {
start_hz = 200;
}
for (int i = 0; i < RAMP_STAGES && remaining > 0; i++) {
uint32_t n = ramp / RAMP_STAGES;
if (n == 0) {
n = 1;
}
if (n > remaining) {
n = remaining;
}
uint32_t hz = start_hz + ((cruise - start_hz) * (uint32_t)(i + 1)) / RAMP_STAGES;
if (!emit_steps(n, hz)) {
return false;
}
remaining -= n;
}
if (remaining > 0) {
if (!emit_steps(remaining, cruise)) {
return false;
}
}
return true;
}
static void motor_task(void *arg)
{
(void)arg;
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (atomic_load(&s_stop_req) != 0) {
rmt_abort();
en_disable();
continue;
}
en_enable();
while (atomic_load(&s_stop_req) == 0) {
if (!run_move((long)((float)STEPS_PER_REV * s_cw))) {
break;
}
if (atomic_load(&s_stop_req) != 0) {
break;
}
if (!run_move((long)(-((float)STEPS_PER_REV * s_ccw)))) {
break;
}
}
rmt_abort();
en_disable();
}
}
void autofilm_motor_task(void *arg)
{
motor_task(arg);
}
void hal_motor_init(void)
{
if (s_inited) {
return;
}
s_pin_en = board_pin_motor_en();
s_pin_dir = board_pin_motor_dir();
s_pin_step = board_pin_motor_step();
s_en_disable_level = board_motor_en_disable_level();
if (s_pin_en == GPIO_NUM_NC || s_pin_dir == GPIO_NUM_NC || s_pin_step == GPIO_NUM_NC) {
ESP_LOGW(TAG, "no motor pins on this board");
s_inited = true;
return;
}
gpio_config_t io = {
.pin_bit_mask = (1ULL << s_pin_en) | (1ULL << s_pin_dir),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io);
gpio_set_level(s_pin_en, s_en_disable_level);
gpio_set_level(s_pin_dir, 0);
s_enabled = false;
atomic_store(&s_stop_req, 0);
rmt_tx_channel_config_t txcfg = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.gpio_num = s_pin_step,
.mem_block_symbols = 64,
.resolution_hz = RMT_RES_HZ,
.trans_queue_depth = 4,
};
rmt_new_tx_channel(&txcfg, &s_chan);
rmt_copy_encoder_config_t enc_cfg = {};
rmt_new_copy_encoder(&enc_cfg, &s_enc);
rmt_enable(s_chan);
if (s_task == NULL) {
xTaskCreatePinnedToCore(motor_task, "motor", 4096, NULL,
configMAX_PRIORITIES - 2, &s_task, 0);
}
s_inited = true;
ESP_LOGI(TAG, "init en=%d step=%d dir=%d dis_lvl=%d",
s_pin_en, s_pin_step, s_pin_dir, s_en_disable_level);
}
void hal_motor_enable(bool on)
{
if (on) {
en_enable();
} else {
en_disable();
}
}
void hal_motor_request_stop(void)
{
atomic_store(&s_stop_req, 1);
en_disable();
rmt_abort();
if (s_task != NULL) {
xTaskNotifyGive(s_task);
}
}
esp_err_t hal_motor_agitate_start(float cw_revs, float ccw_revs, uint32_t rpm)
{
s_cw = cw_revs;
s_ccw = ccw_revs;
s_rpm = (rpm == 0) ? DEFAULT_RPM : rpm;
atomic_store(&s_stop_req, 0);
if (s_task != NULL) {
xTaskNotifyGive(s_task);
}
return ESP_OK;
}
void hal_motor_agitate_stop(void)
{
atomic_store(&s_stop_req, 1);
rmt_abort();
en_disable();
}
bool hal_motor_is_enabled(void)
{
return s_enabled;
}
+31
View File
@@ -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;
}
+19
View File
@@ -0,0 +1,19 @@
set(srcs)
set(priv_inc)
set(priv_req driver)
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})
+127
View File
@@ -0,0 +1,127 @@
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.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";
static onewire_bus_handle_t s_bus;
static bool s_inited;
static float s_last_c;
static bool s_last_ok;
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;
vTaskDelay(pdMS_TO_TICKS(750));
return;
}
if (onewire_bus_reset(s_bus) != ESP_OK) {
s_last_ok = false;
vTaskDelay(pdMS_TO_TICKS(750));
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;
vTaskDelay(pdMS_TO_TICKS(750));
return;
}
vTaskDelay(pdMS_TO_TICKS(750));
if (onewire_bus_reset(s_bus) != ESP_OK) {
s_last_ok = false;
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;
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;
return;
}
if (!scratch_valid(sp)) {
s_last_ok = false;
return;
}
int16_t raw = (int16_t)((uint16_t)sp[0] | ((uint16_t)sp[1] << 8));
float t = ((float)raw) / 16.0f + TEMP_OFFSET;
s_last_c = t;
s_last_ok = true;
}
void hal_temp_init(void)
{
if (s_inited) {
return;
}
gpio_num_t pin = board_pin_temp();
if (pin == GPIO_NUM_NC) {
ESP_LOGW(TAG, "no temp pin on this board");
s_inited = true;
return;
}
onewire_bus_config_t bus_config = {
.bus_gpio_num = pin,
.flags = {
.en_pull_up = true,
},
};
onewire_bus_rmt_config_t rmt_config = {
.max_rx_bytes = 10,
};
if (onewire_new_bus_rmt(&bus_config, &rmt_config, &s_bus) != ESP_OK) {
s_bus = NULL;
ESP_LOGE(TAG, "onewire_bus install failed pin=%d", pin);
}
s_last_ok = false;
s_last_c = 0.0f;
s_inited = true;
ESP_LOGI(TAG, "init onewire_bus pin=%d offset=%.1f", pin, (double)TEMP_OFFSET);
}
esp_err_t hal_temp_read_c(float *out)
{
if (!s_last_ok) {
return ESP_FAIL;
}
if (out != NULL) {
*out = s_last_c;
}
return ESP_OK;
}
+2
View File
@@ -0,0 +1,2 @@
dependencies:
espressif/onewire_bus: "^1.1.0"
+1
View File
@@ -7,4 +7,5 @@
#endif #endif
void hal_temp_init(void); 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 */ esp_err_t hal_temp_read_c(float *out); /* ESP_FAIL → disconnected */
+19
View File
@@ -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;
}
+679
View File
@@ -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
View File
@@ -0,0 +1,61 @@
/*
|| @file Key.cpp
|| @version 1.0
|| @author Mark Stanley
|| @contact mstanley@technologist.com
||
|| @description
|| | Key class provides an abstract definition of a key or button
|| | and was initially designed to be used in conjunction with a
|| | state-machine.
|| #
||
|| @license
|| | This library is free software; you can redistribute it and/or
|| | modify it under the terms of the GNU Lesser General Public
|| | License as published by the Free Software Foundation; version
|| | 2.1 of the License.
|| |
|| | This library is distributed in the hope that it will be useful,
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|| | Lesser General Public License for more details.
|| |
|| | You should have received a copy of the GNU Lesser General Public
|| | License along with this library; if not, write to the Free Software
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|| #
||
*/
#include <Key.h>
// default constructor
Key::Key() {
kchar = NO_KEY;
kstate = IDLE;
stateChanged = false;
}
// constructor
Key::Key(char userKeyChar) {
kchar = userKeyChar;
kcode = -1;
kstate = IDLE;
stateChanged = false;
}
void Key::key_update (char userKeyChar, KeyState userState, boolean userStatus) {
kchar = userKeyChar;
kstate = userState;
stateChanged = userStatus;
}
/*
|| @changelog
|| | 1.0 2012-06-04 - Mark Stanley : Initial Release
|| #
*/
+293
View File
@@ -0,0 +1,293 @@
/*
||
|| @file Keypad.cpp
|| @version 3.1
|| @author Mark Stanley, Alexander Brevig
|| @contact mstanley@technologist.com, alexanderbrevig@gmail.com
||
|| @description
|| | This library provides a simple interface for using matrix
|| | keypads. It supports multiple keypresses while maintaining
|| | backwards compatibility with the old single key library.
|| | It also supports user selectable pins and definable keymaps.
|| #
||
|| @license
|| | This library is free software; you can redistribute it and/or
|| | modify it under the terms of the GNU Lesser General Public
|| | License as published by the Free Software Foundation; version
|| | 2.1 of the License.
|| |
|| | This library is distributed in the hope that it will be useful,
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|| | Lesser General Public License for more details.
|| |
|| | You should have received a copy of the GNU Lesser General Public
|| | License along with this library; if not, write to the Free Software
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|| #
||
*/
#include <Keypad.h>
// <<constructor>> Allows custom keymap, pin configuration, and keypad sizes.
Keypad::Keypad(char *userKeymap, byte *row, byte *col, byte numRows, byte numCols) {
rowPins = row;
columnPins = col;
sizeKpd.rows = numRows;
sizeKpd.columns = numCols;
begin(userKeymap);
setDebounceTime(10);
setHoldTime(500);
keypadEventListener = 0;
startTime = 0;
single_key = false;
}
// Let the user define a keymap - assume the same row/column count as defined in constructor
void Keypad::begin(char *userKeymap) {
keymap = userKeymap;
}
// Returns a single key only. Retained for backwards compatibility.
char Keypad::getKey() {
single_key = true;
if (getKeys() && key[0].stateChanged && (key[0].kstate==PRESSED))
return key[0].kchar;
single_key = false;
return NO_KEY;
}
// Populate the key list.
bool Keypad::getKeys() {
bool keyActivity = false;
// Limit how often the keypad is scanned. This makes the loop() run 10 times as fast.
if ( (millis()-startTime)>debounceTime ) {
scanKeys();
keyActivity = updateList();
startTime = millis();
}
return keyActivity;
}
// Private : Hardware scan
void Keypad::scanKeys() {
// Re-intialize the row pins. Allows sharing these pins with other hardware.
for (byte r=0; r<sizeKpd.rows; r++) {
pin_mode(rowPins[r],INPUT_PULLUP);
}
// bitMap stores ALL the keys that are being pressed.
for (byte c=0; c<sizeKpd.columns; c++) {
pin_mode(columnPins[c],OUTPUT);
pin_write(columnPins[c], LOW); // Begin column pulse output.
for (byte r=0; r<sizeKpd.rows; r++) {
bitWrite(bitMap[r], c, !pin_read(rowPins[r])); // keypress is active low so invert to high.
}
// Set pin to high impedance input. Effectively ends column pulse.
pin_write(columnPins[c],HIGH);
pin_mode(columnPins[c],INPUT);
}
}
// Manage the list without rearranging the keys. Returns true if any keys on the list changed state.
bool Keypad::updateList() {
bool anyActivity = false;
// Delete any IDLE keys
for (byte i=0; i<LIST_MAX; i++) {
if (key[i].kstate==IDLE) {
key[i].kchar = NO_KEY;
key[i].kcode = -1;
key[i].stateChanged = false;
}
}
// Add new keys to empty slots in the key list.
for (byte r=0; r<sizeKpd.rows; r++) {
for (byte c=0; c<sizeKpd.columns; c++) {
boolean button = bitRead(bitMap[r],c);
char keyChar = keymap[r * sizeKpd.columns + c];
int keyCode = r * sizeKpd.columns + c;
int idx = findInList (keyCode);
// Key is already on the list so set its next state.
if (idx > -1) {
nextKeyState(idx, button);
}
// Key is NOT on the list so add it.
if ((idx == -1) && button) {
for (byte i=0; i<LIST_MAX; i++) {
if (key[i].kchar==NO_KEY) { // Find an empty slot or don't add key to list.
key[i].kchar = keyChar;
key[i].kcode = keyCode;
key[i].kstate = IDLE; // Keys NOT on the list have an initial state of IDLE.
nextKeyState (i, button);
break; // Don't fill all the empty slots with the same key.
}
}
}
}
}
// Report if the user changed the state of any key.
for (byte i=0; i<LIST_MAX; i++) {
if (key[i].stateChanged) anyActivity = true;
}
return anyActivity;
}
// Private
// This function is a state machine but is also used for debouncing the keys.
void Keypad::nextKeyState(byte idx, boolean button) {
key[idx].stateChanged = false;
switch (key[idx].kstate) {
case IDLE:
if (button==CLOSED) {
transitionTo (idx, PRESSED);
holdTimer = millis(); } // Get ready for next HOLD state.
break;
case PRESSED:
if ((millis()-holdTimer)>holdTime) // Waiting for a key HOLD...
transitionTo (idx, HOLD);
else if (button==OPEN) // or for a key to be RELEASED.
transitionTo (idx, RELEASED);
break;
case HOLD:
if (button==OPEN)
transitionTo (idx, RELEASED);
break;
case RELEASED:
transitionTo (idx, IDLE);
break;
}
}
// New in 2.1
bool Keypad::isPressed(char keyChar) {
for (byte i=0; i<LIST_MAX; i++) {
if ( key[i].kchar == keyChar ) {
if ( (key[i].kstate == PRESSED) && key[i].stateChanged )
return true;
}
}
return false; // Not pressed.
}
// Search by character for a key in the list of active keys.
// Returns -1 if not found or the index into the list of active keys.
int Keypad::findInList (char keyChar) {
for (byte i=0; i<LIST_MAX; i++) {
if (key[i].kchar == keyChar) {
return i;
}
}
return -1;
}
// Search by code for a key in the list of active keys.
// Returns -1 if not found or the index into the list of active keys.
int Keypad::findInList (int keyCode) {
for (byte i=0; i<LIST_MAX; i++) {
if (key[i].kcode == keyCode) {
return i;
}
}
return -1;
}
// New in 2.0
char Keypad::waitForKey() {
char waitKey = NO_KEY;
while( (waitKey = getKey()) == NO_KEY ); // Block everything while waiting for a keypress.
return waitKey;
}
// Backwards compatibility function.
KeyState Keypad::getState() {
return key[0].kstate;
}
// The end user can test for any changes in state before deciding
// if any variables, etc. needs to be updated in their code.
bool Keypad::keyStateChanged() {
return key[0].stateChanged;
}
// The number of keys on the key list, key[LIST_MAX], equals the number
// of bytes in the key list divided by the number of bytes in a Key object.
byte Keypad::numKeys() {
return sizeof(key)/sizeof(Key);
}
// Minimum debounceTime is 1 mS. Any lower *will* slow down the loop().
void Keypad::setDebounceTime(uint debounce) {
debounce<1 ? debounceTime=1 : debounceTime=debounce;
}
void Keypad::setHoldTime(uint hold) {
holdTime = hold;
}
void Keypad::addEventListener(void (*listener)(char)){
keypadEventListener = listener;
}
void Keypad::transitionTo(byte idx, KeyState nextState) {
key[idx].kstate = nextState;
key[idx].stateChanged = true;
// Sketch used the getKey() function.
// Calls keypadEventListener only when the first key in slot 0 changes state.
if (single_key) {
if ( (keypadEventListener!=NULL) && (idx==0) ) {
keypadEventListener(key[0].kchar);
}
}
// Sketch used the getKeys() function.
// Calls keypadEventListener on any key that changes state.
else {
if (keypadEventListener!=NULL) {
keypadEventListener(key[idx].kchar);
}
}
}
/*
|| @changelog
|| | 3.1 2013-01-15 - Mark Stanley : Fixed missing RELEASED & IDLE status when using a single key.
|| | 3.0 2012-07-12 - Mark Stanley : Made library multi-keypress by default. (Backwards compatible)
|| | 3.0 2012-07-12 - Mark Stanley : Modified pin functions to support Keypad_I2C
|| | 3.0 2012-07-12 - Stanley & Young : Removed static variables. Fix for multiple keypad objects.
|| | 3.0 2012-07-12 - Mark Stanley : Fixed bug that caused shorted pins when pressing multiple keys.
|| | 2.0 2011-12-29 - Mark Stanley : Added waitForKey().
|| | 2.0 2011-12-23 - Mark Stanley : Added the public function keyStateChanged().
|| | 2.0 2011-12-23 - Mark Stanley : Added the private function scanKeys().
|| | 2.0 2011-12-23 - Mark Stanley : Moved the Finite State Machine into the function getKeyState().
|| | 2.0 2011-12-23 - Mark Stanley : Removed the member variable lastUdate. Not needed after rewrite.
|| | 1.8 2011-11-21 - Mark Stanley : Added decision logic to compile WProgram.h or Arduino.h
|| | 1.8 2009-07-08 - Alexander Brevig : No longer uses arrays
|| | 1.7 2009-06-18 - Alexander Brevig : Every time a state changes the keypadEventListener will trigger, if set.
|| | 1.7 2009-06-18 - Alexander Brevig : Added setDebounceTime. setHoldTime specifies the amount of
|| | microseconds before a HOLD state triggers
|| | 1.7 2009-06-18 - Alexander Brevig : Added transitionTo
|| | 1.6 2009-06-15 - Alexander Brevig : Added getState() and state variable
|| | 1.5 2009-05-19 - Alexander Brevig : Added setHoldTime()
|| | 1.4 2009-05-15 - Alexander Brevig : Added addEventListener
|| | 1.3 2009-05-12 - Alexander Brevig : Added lastUdate, in order to do simple debouncing
|| | 1.2 2009-05-09 - Alexander Brevig : Changed getKey()
|| | 1.1 2009-04-28 - Alexander Brevig : Modified API, and made variables private
|| | 1.0 2007-XX-XX - Mark Stanley : Initial Release
|| #
*/
+68
View File
@@ -0,0 +1,68 @@
/*
||
|| @file Key.h
|| @version 1.0
|| @author Mark Stanley
|| @contact mstanley@technologist.com
||
|| @description
|| | Key class provides an abstract definition of a key or button
|| | and was initially designed to be used in conjunction with a
|| | state-machine.
|| #
||
|| @license
|| | This library is free software; you can redistribute it and/or
|| | modify it under the terms of the GNU Lesser General Public
|| | License as published by the Free Software Foundation; version
|| | 2.1 of the License.
|| |
|| | This library is distributed in the hope that it will be useful,
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|| | Lesser General Public License for more details.
|| |
|| | You should have received a copy of the GNU Lesser General Public
|| | License along with this library; if not, write to the Free Software
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|| #
||
*/
#ifndef Keypadlib_KEY_H_
#define Keypadlib_KEY_H_
#include <Arduino.h>
#define OPEN LOW
#define CLOSED HIGH
typedef unsigned int uint;
typedef enum{ IDLE, PRESSED, HOLD, RELEASED } KeyState;
const char NO_KEY = '\0';
class Key {
public:
// members
char kchar;
int kcode;
KeyState kstate;
boolean stateChanged;
// methods
Key();
Key(char userKeyChar);
void key_update(char userKeyChar, KeyState userState, boolean userStatus);
private:
};
#endif
/*
|| @changelog
|| | 1.0 2012-06-04 - Mark Stanley : Initial Release
|| #
*/
+149
View File
@@ -0,0 +1,149 @@
/*
||
|| @file Keypad.h
|| @version 3.1
|| @author Mark Stanley, Alexander Brevig
|| @contact mstanley@technologist.com, alexanderbrevig@gmail.com
||
|| @description
|| | This library provides a simple interface for using matrix
|| | keypads. It supports multiple keypresses while maintaining
|| | backwards compatibility with the old single key library.
|| | It also supports user selectable pins and definable keymaps.
|| #
||
|| @license
|| | This library is free software; you can redistribute it and/or
|| | modify it under the terms of the GNU Lesser General Public
|| | License as published by the Free Software Foundation; version
|| | 2.1 of the License.
|| |
|| | This library is distributed in the hope that it will be useful,
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|| | Lesser General Public License for more details.
|| |
|| | You should have received a copy of the GNU Lesser General Public
|| | License along with this library; if not, write to the Free Software
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|| #
||
*/
#ifndef KEYPAD_H
#define KEYPAD_H
#include "Key.h"
// bperrybap - Thanks for a well reasoned argument and the following macro(s).
// See http://arduino.cc/forum/index.php/topic,142041.msg1069480.html#msg1069480
#ifndef INPUT_PULLUP
#warning "Using pinMode() INPUT_PULLUP AVR emulation"
#define INPUT_PULLUP 0x2
#define pinMode(_pin, _mode) _mypinMode(_pin, _mode)
#define _mypinMode(_pin, _mode) \
do { \
if(_mode == INPUT_PULLUP) \
pinMode(_pin, INPUT); \
digitalWrite(_pin, 1); \
if(_mode != INPUT_PULLUP) \
pinMode(_pin, _mode); \
}while(0)
#endif
#define OPEN LOW
#define CLOSED HIGH
typedef char KeypadEvent;
typedef unsigned int uint;
typedef unsigned long ulong;
// Made changes according to this post http://arduino.cc/forum/index.php?topic=58337.0
// by Nick Gammon. Thanks for the input Nick. It actually saved 78 bytes for me. :)
typedef struct {
byte rows;
byte columns;
} KeypadSize;
#define LIST_MAX 10 // Max number of keys on the active list.
#define MAPSIZE 10 // MAPSIZE is the number of rows (times 16 columns)
#define makeKeymap(x) ((char*)x)
//class Keypad : public Key, public HAL_obj {
class Keypad : public Key {
public:
Keypad(char *userKeymap, byte *row, byte *col, byte numRows, byte numCols);
virtual void pin_mode(byte pinNum, byte mode) { pinMode(pinNum, mode); }
virtual void pin_write(byte pinNum, boolean level) { digitalWrite(pinNum, level); }
virtual int pin_read(byte pinNum) { return digitalRead(pinNum); }
uint bitMap[MAPSIZE]; // 10 row x 16 column array of bits. Except Due which has 32 columns.
Key key[LIST_MAX];
unsigned long holdTimer;
char getKey();
bool getKeys();
KeyState getState();
void begin(char *userKeymap);
bool isPressed(char keyChar);
void setDebounceTime(uint);
void setHoldTime(uint);
void addEventListener(void (*listener)(char));
int findInList(char keyChar);
int findInList(int keyCode);
char waitForKey();
bool keyStateChanged();
byte numKeys();
private:
unsigned long startTime;
char *keymap;
byte *rowPins;
byte *columnPins;
KeypadSize sizeKpd;
uint debounceTime;
uint holdTime;
bool single_key;
void scanKeys();
bool updateList();
void nextKeyState(byte n, boolean button);
void transitionTo(byte n, KeyState nextState);
void (*keypadEventListener)(char);
};
#endif
/*
|| @changelog
|| | 3.1 2013-01-15 - Mark Stanley : Fixed missing RELEASED & IDLE status when using a single key.
|| | 3.0 2012-07-12 - Mark Stanley : Made library multi-keypress by default. (Backwards compatible)
|| | 3.0 2012-07-12 - Mark Stanley : Modified pin functions to support Keypad_I2C
|| | 3.0 2012-07-12 - Stanley & Young : Removed static variables. Fix for multiple keypad objects.
|| | 3.0 2012-07-12 - Mark Stanley : Fixed bug that caused shorted pins when pressing multiple keys.
|| | 2.0 2011-12-29 - Mark Stanley : Added waitForKey().
|| | 2.0 2011-12-23 - Mark Stanley : Added the public function keyStateChanged().
|| | 2.0 2011-12-23 - Mark Stanley : Added the private function scanKeys().
|| | 2.0 2011-12-23 - Mark Stanley : Moved the Finite State Machine into the function getKeyState().
|| | 2.0 2011-12-23 - Mark Stanley : Removed the member variable lastUdate. Not needed after rewrite.
|| | 1.8 2011-11-21 - Mark Stanley : Added test to determine which header file to compile,
|| | WProgram.h or Arduino.h.
|| | 1.8 2009-07-08 - Alexander Brevig : No longer uses arrays
|| | 1.7 2009-06-18 - Alexander Brevig : This library is a Finite State Machine every time a state changes
|| | the keypadEventListener will trigger, if set
|| | 1.7 2009-06-18 - Alexander Brevig : Added setDebounceTime setHoldTime specifies the amount of
|| | microseconds before a HOLD state triggers
|| | 1.7 2009-06-18 - Alexander Brevig : Added transitionTo
|| | 1.6 2009-06-15 - Alexander Brevig : Added getState() and state variable
|| | 1.5 2009-05-19 - Alexander Brevig : Added setHoldTime()
|| | 1.4 2009-05-15 - Alexander Brevig : Added addEventListener
|| | 1.3 2009-05-12 - Alexander Brevig : Added lastUdate, in order to do simple debouncing
|| | 1.2 2009-05-09 - Alexander Brevig : Changed getKey()
|| | 1.1 2009-04-28 - Alexander Brevig : Modified API, and made variables private
|| | 1.0 2007-XX-XX - Mark Stanley : Initial Release
|| #
*/
@@ -0,0 +1,332 @@
// Based on the work by DFRobot
#include "LiquidCrystal_I2C.h"
#include <inttypes.h>
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#define printIIC(args) Wire.write(args)
inline size_t LiquidCrystal_I2C::write(uint8_t value) {
send(value, Rs);
return 1;
}
#else
#include "WProgram.h"
#define printIIC(args) Wire.send(args)
inline void LiquidCrystal_I2C::write(uint8_t value) {
send(value, Rs);
}
#endif
#include "Wire.h"
// When the display powers up, it is configured as follows:
//
// 1. Display clear
// 2. Function set:
// DL = 1; 8-bit interface data
// N = 0; 1-line display
// F = 0; 5x8 dot character font
// 3. Display on/off control:
// D = 0; Display off
// C = 0; Cursor off
// B = 0; Blinking off
// 4. Entry mode set:
// I/D = 1; Increment by 1
// S = 0; No shift
//
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
// can't assume that its in that state when a sketch starts (and the
// LiquidCrystal constructor is called).
LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows)
{
_Addr = lcd_Addr;
_cols = lcd_cols;
_rows = lcd_rows;
_backlightval = LCD_NOBACKLIGHT;
}
void LiquidCrystal_I2C::oled_init(){
_oled = true;
init_priv();
}
void LiquidCrystal_I2C::init(){
init_priv();
}
void LiquidCrystal_I2C::init_priv()
{
Wire.begin();
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
begin(_cols, _rows);
}
void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
if (lines > 1) {
_displayfunction |= LCD_2LINE;
}
_numlines = lines;
// for some 1 line displays you can select a 10 pixel high font
if ((dotsize != 0) && (lines == 1)) {
_displayfunction |= LCD_5x10DOTS;
}
// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
// according to datasheet, we need at least 40ms after power rises above 2.7V
// before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
delay(50);
// Now we pull both RS and R/W low to begin commands
expanderWrite(_backlightval); // reset expanderand turn backlight off (Bit 8 =1)
delay(1000);
//put the LCD into 4 bit mode
// this is according to the hitachi HD44780 datasheet
// figure 24, pg 46
// we start in 8bit mode, try to set 4 bit mode
write4bits(0x03 << 4);
delayMicroseconds(4500); // wait min 4.1ms
// second try
write4bits(0x03 << 4);
delayMicroseconds(4500); // wait min 4.1ms
// third go!
write4bits(0x03 << 4);
delayMicroseconds(150);
// finally, set to 4-bit interface
write4bits(0x02 << 4);
// set # lines, font size, etc.
command(LCD_FUNCTIONSET | _displayfunction);
// turn the display on with no cursor or blinking default
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
display();
// clear it off
clear();
// Initialize to default text direction (for roman languages)
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
// set the entry mode
command(LCD_ENTRYMODESET | _displaymode);
home();
}
/********** high level commands, for the user! */
void LiquidCrystal_I2C::clear(){
command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero
delayMicroseconds(2000); // this command takes a long time!
if (_oled) setCursor(0,0);
}
void LiquidCrystal_I2C::home(){
command(LCD_RETURNHOME); // set cursor position to zero
delayMicroseconds(2000); // this command takes a long time!
}
void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row){
int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
if ( row > _numlines ) {
row = _numlines-1; // we count rows starting w/0
}
command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
}
// Turn the display on/off (quickly)
void LiquidCrystal_I2C::noDisplay() {
_displaycontrol &= ~LCD_DISPLAYON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal_I2C::display() {
_displaycontrol |= LCD_DISPLAYON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
// Turns the underline cursor on/off
void LiquidCrystal_I2C::noCursor() {
_displaycontrol &= ~LCD_CURSORON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal_I2C::cursor() {
_displaycontrol |= LCD_CURSORON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
// Turn on and off the blinking cursor
void LiquidCrystal_I2C::noBlink() {
_displaycontrol &= ~LCD_BLINKON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal_I2C::blink() {
_displaycontrol |= LCD_BLINKON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
// These commands scroll the display without changing the RAM
void LiquidCrystal_I2C::scrollDisplayLeft(void) {
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}
void LiquidCrystal_I2C::scrollDisplayRight(void) {
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}
// This is for text that flows Left to Right
void LiquidCrystal_I2C::leftToRight(void) {
_displaymode |= LCD_ENTRYLEFT;
command(LCD_ENTRYMODESET | _displaymode);
}
// This is for text that flows Right to Left
void LiquidCrystal_I2C::rightToLeft(void) {
_displaymode &= ~LCD_ENTRYLEFT;
command(LCD_ENTRYMODESET | _displaymode);
}
// This will 'right justify' text from the cursor
void LiquidCrystal_I2C::autoscroll(void) {
_displaymode |= LCD_ENTRYSHIFTINCREMENT;
command(LCD_ENTRYMODESET | _displaymode);
}
// This will 'left justify' text from the cursor
void LiquidCrystal_I2C::noAutoscroll(void) {
_displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
command(LCD_ENTRYMODESET | _displaymode);
}
// Allows us to fill the first 8 CGRAM locations
// with custom characters
void LiquidCrystal_I2C::createChar(uint8_t location, uint8_t charmap[]) {
location &= 0x7; // we only have 8 locations 0-7
command(LCD_SETCGRAMADDR | (location << 3));
for (int i=0; i<8; i++) {
write(charmap[i]);
}
}
//createChar with PROGMEM input
void LiquidCrystal_I2C::createChar(uint8_t location, const char *charmap) {
location &= 0x7; // we only have 8 locations 0-7
command(LCD_SETCGRAMADDR | (location << 3));
for (int i=0; i<8; i++) {
write(pgm_read_byte_near(charmap++));
}
}
// Turn the (optional) backlight off/on
void LiquidCrystal_I2C::noBacklight(void) {
_backlightval=LCD_NOBACKLIGHT;
expanderWrite(0);
}
void LiquidCrystal_I2C::backlight(void) {
_backlightval=LCD_BACKLIGHT;
expanderWrite(0);
}
/*********** mid level commands, for sending data/cmds */
inline void LiquidCrystal_I2C::command(uint8_t value) {
send(value, 0);
}
/************ low level data pushing commands **********/
// write either command or data
void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
uint8_t highnib=value&0xf0;
uint8_t lownib=(value<<4)&0xf0;
write4bits((highnib)|mode);
write4bits((lownib)|mode);
}
void LiquidCrystal_I2C::write4bits(uint8_t value) {
expanderWrite(value);
pulseEnable(value);
}
void LiquidCrystal_I2C::expanderWrite(uint8_t _data){
Wire.beginTransmission(_Addr);
printIIC((int)(_data) | _backlightval);
Wire.endTransmission();
}
void LiquidCrystal_I2C::pulseEnable(uint8_t _data){
expanderWrite(_data | En); // En high
delayMicroseconds(1); // enable pulse must be >450ns
expanderWrite(_data & ~En); // En low
delayMicroseconds(50); // commands need > 37us to settle
}
// Alias functions
void LiquidCrystal_I2C::cursor_on(){
cursor();
}
void LiquidCrystal_I2C::cursor_off(){
noCursor();
}
void LiquidCrystal_I2C::blink_on(){
blink();
}
void LiquidCrystal_I2C::blink_off(){
noBlink();
}
void LiquidCrystal_I2C::load_custom_character(uint8_t char_num, uint8_t *rows){
createChar(char_num, rows);
}
void LiquidCrystal_I2C::setBacklight(uint8_t new_val){
if(new_val){
backlight(); // turn backlight on
}else{
noBacklight(); // turn backlight off
}
}
void LiquidCrystal_I2C::printstr(const char c[]){
//This function is not identical to the function used for "real" I2C displays
//it's here so the user sketch doesn't have to be changed
print(c);
}
// unsupported API functions
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
void LiquidCrystal_I2C::off(){}
void LiquidCrystal_I2C::on(){}
void LiquidCrystal_I2C::setDelay (int cmdDelay,int charDelay) {}
uint8_t LiquidCrystal_I2C::status(){return 0;}
uint8_t LiquidCrystal_I2C::keypad (){return 0;}
uint8_t LiquidCrystal_I2C::init_bargraph(uint8_t graphtype){return 0;}
void LiquidCrystal_I2C::draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end){}
void LiquidCrystal_I2C::draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_row_end){}
void LiquidCrystal_I2C::setContrast(uint8_t new_val){}
#pragma GCC diagnostic pop
@@ -0,0 +1,131 @@
//YWROBOT
#ifndef LiquidCrystal_I2C_h
#define LiquidCrystal_I2C_h
#include <inttypes.h>
#include "Print.h"
#include <Wire.h>
// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80
// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00
// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00
// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00
// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00
// flags for backlight control
#define LCD_BACKLIGHT 0x08
#define LCD_NOBACKLIGHT 0x00
#define En B00000100 // Enable bit
#define Rw B00000010 // Read/Write bit
#define Rs B00000001 // Register select bit
class LiquidCrystal_I2C : public Print {
public:
LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS );
void clear();
void home();
void noDisplay();
void display();
void noBlink();
void blink();
void noCursor();
void cursor();
void scrollDisplayLeft();
void scrollDisplayRight();
void printLeft();
void printRight();
void leftToRight();
void rightToLeft();
void shiftIncrement();
void shiftDecrement();
void noBacklight();
void backlight();
void autoscroll();
void noAutoscroll();
void createChar(uint8_t, uint8_t[]);
void createChar(uint8_t location, const char *charmap);
// Example: const char bell[8] PROGMEM = {B00100,B01110,B01110,B01110,B11111,B00000,B00100,B00000};
void setCursor(uint8_t, uint8_t);
#if defined(ARDUINO) && ARDUINO >= 100
virtual size_t write(uint8_t);
#else
virtual void write(uint8_t);
#endif
void command(uint8_t);
void init();
void oled_init();
////compatibility API function aliases
void blink_on(); // alias for blink()
void blink_off(); // alias for noBlink()
void cursor_on(); // alias for cursor()
void cursor_off(); // alias for noCursor()
void setBacklight(uint8_t new_val); // alias for backlight() and nobacklight()
void load_custom_character(uint8_t char_num, uint8_t *rows); // alias for createChar()
void printstr(const char[]);
////Unsupported API functions (not implemented in this library)
uint8_t status();
void setContrast(uint8_t new_val);
uint8_t keypad();
void setDelay(int,int);
void on();
void off();
uint8_t init_bargraph(uint8_t graphtype);
void draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
private:
void init_priv();
void send(uint8_t, uint8_t);
void write4bits(uint8_t);
void expanderWrite(uint8_t);
void pulseEnable(uint8_t);
uint8_t _Addr;
uint8_t _displayfunction;
uint8_t _displaycontrol;
uint8_t _displaymode;
uint8_t _numlines;
bool _oled = false;
uint8_t _cols;
uint8_t _rows;
uint8_t _backlightval;
};
#endif
+603
View File
@@ -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
View File
@@ -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
+10
View File
@@ -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)
+12 -1
View File
@@ -20,7 +20,7 @@ Gitea `act_runner` must be registered against `git.i3omb.com` with labels that m
| Workflow `runs-on` | Typical runner label | Notes | | Workflow `runs-on` | Typical runner label | Notes |
| --- | --- | --- | | --- | --- | --- |
| `ubuntu-latest` | `ubuntu-latest:docker://gitea/runner-images:ubuntu-latest` | Host tests, checkout, artifact upload | | `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 | | (job `container`) | Docker socket available on the runner | Firmware jobs use `espressif/idf:<tag>` as the job container |
Firmware builds need: Firmware builds need:
@@ -155,6 +155,17 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - 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 - name: Host unit tests
if: hashFiles('tests/host/CMakeLists.txt') != '' if: hashFiles('tests/host/CMakeLists.txt') != ''
run: | run: |
+2
View File
@@ -44,6 +44,8 @@ From `include/config.h` and `src/config.cpp`:
Motor constants: `STEPS_PER_REV = 4800`, `RPM = 60`, acceleration `9600`. Enable is driven HIGH at boot (disabled). 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): Keypad map (5×4):
``` ```
+174
View File
@@ -0,0 +1,174 @@
# JC4827W543 pin audit
Comparison of the GPIO assignments in this tree against the vendor
documentation at <https://github.com/lsdlsd88/JC4827W543> (GUITION
factory docs: spec PDF, IO pin distribution xlsx, Arduino demos).
Board variants: `JC4827W543N` (no touch), `...R` (resistive XPT2046),
`...C` (capacitive GT911). Ours is the **C** model.
## 1. Vendor-documented pin allocation (authoritative)
From `5-IO pin distribution/4.3 inches IO pin distribution.xlsx`,
cross-checked against the demo sketches
(`1-Demo/Demo_Arduino/3_3-*` and `4_*`):
| GPIO | Vendor function | Notes |
| --- | --- | --- |
| 0 | BOOT button | also wired to `LCD_TE` |
| 1 | `BL_CTRL` | LCD backlight |
| 2 | `SPECK_LRCLK` | onboard speaker amp I2S WS |
| 3 | `CTP_INT` / `RTP_IRQ` | touch interrupt (both models) |
| 4 | `CTP_SCL` | GT911 I2C SCL |
| 5 | free | |
| 6 | free | |
| 7 | free | |
| 8 | `CTP_SDA` | GT911 I2C SDA |
| 9 | free | |
| 10 | `TF_CS` | microSD slot |
| 11 | `RTP_DIN` / `TF_MISO` | shared SPI: resistive touch / SD |
| 12 | `RTP_CLK` / `TF_CLK` | shared SPI |
| 13 | `RTP_DIO` / `TF_MOSI` | shared SPI |
| 14 | free | |
| 15 | free | exposed on header (P2/P3 area) |
| 16 | free | exposed on header; used by vendor LED demo |
| 17 | `U1TXD` | UART1 header (P5), usable if UART1 unused |
| 18 | `U1RXD` | UART1 header (P5), usable if UART1 unused |
| 19 | `USB+` | USB D+ |
| 20 | `USB-` | USB D- |
| 21 | `LCD_A0` | QSPI data 0 |
| 22–25 | — | do not exist on ESP32-S3 |
| 26–34 | — | flash/PSRAM or non-existent |
| 35 | not available | octal PSRAM (xlsx marks explicitly) |
| 36, 37 | free per xlsx | **but** consumed by octal PSRAM on N8R8/R8 modules — treat as unavailable until module variant confirmed |
| 38 | `RTP_CS` (resistive model) | on the **C** model this is the GT911 reset line — see §3 |
| 39 | `LCD_A3` | QSPI data 3 |
| 40 | `LCD_A2` | QSPI data 2 |
| 41 | `SPECK_DIN` | speaker amp I2S data in |
| 42 | `SPECK_BCLK` | speaker amp I2S BCLK |
| 43 | `U0TXD` | console |
| 44 | `U0RXD` | console |
| 45 | `LCD_CS` | QSPI chip select |
| 46 | free per xlsx | strapping pin (`LOG`); output-only-ish, use with care |
| 47 | `LCD_CLK` | QSPI clock |
| 48 | `LCD_A1` | QSPI data 1 |
The board carries an onboard I2S speaker amplifier (datasheets for
NS4168 / AX9835 ship in `4-Driver_IC_Data_Sheet`), a `Speak` connector
(P7), `BAT` connector (P6), TF slot, UART0 console, UART1 header and a
scattering of free-IO headers (P2–P5: GPIO 15, 16, 17, 18 visible on
the structure diagram). Vendor demos drive LEDs on GPIO 16/17 and a
DHT11 on GPIO 27 — note GPIO 27 is **not** in the vendor pin table,
so that demo value is suspect/generic.
## 2. What the code assigns today
### `board_jc4827w543` (`components/board_jc4827w543/board.c`)
| Signal | Code GPIO | Vendor | Match |
| --- | --- | --- | --- |
| LCD CS | 45 | 45 `LCD_CS` | yes |
| LCD SCK | 47 | 47 `LCD_CLK` | yes |
| LCD D0 | 21 | 21 `LCD_A0` | yes |
| LCD D1 | 48 | 48 `LCD_A1` | yes |
| LCD D2 | 40 | 40 `LCD_A2` | yes |
| LCD D3 | 39 | 39 `LCD_A3` | yes |
| LCD BL | 1 | 1 `BL_CTRL` | yes |
| TP SDA | 8 | 8 `CTP_SDA` | yes |
| TP SCL | 4 | 4 `CTP_SCL` | yes |
| TP INT | 3 | 3 `CTP_INT` | yes |
| TP RST | 38 | 38 (demo: `TOUCH_RES 38`) | yes — see §3 |
| Motor EN | `GPIO_NUM_NC` | n/a | deliberately unassigned |
### `hal_display_nv3041a.c`
Driver **NV3041A** over QSPI via `esp_lcd` SPI host, 480×272, quad
mode, 40 MHz. Matches the vendor demo
(`Arduino_ESP32QSPI(45,47,21,48,40,39)` + `Arduino_NV3041A`,
`Arduino_Canvas(480,272)`).
Caveat: the spec sheet PDF lists the driver chip as "ST3401A" — that
string appears nowhere else; every demo and the burn files use
NV3041A. Treat the PDF label as a typo.
### `hal_input_gt911.c`
GT911 at I2C addr `0x5D` (`GT911_SLAVE_ADDRESS1` in the vendor
`touch.h`), I2C_NUM_0 @ 400 kHz on SDA 8 / SCL 4. Reset sequence
(drive INT low, pulse RST, release INT to input) selects the 0x5D
address per the GT911 datasheet — matches the vendor demo's
`TOUCH_RES 38` / `TOUCH_INT 3` wiring.
### `board_wroom` + `hal_*` (classic ESP32, unchanged)
| Signal | GPIO | Source |
| --- | --- | --- |
| Stepper STEP | 12 | `config.h`, `hal_motor.c` |
| Stepper DIR | 14 | `config.h`, `hal_motor.c` |
| Stepper EN (active LOW) | 27 | `config.h`, `hal_motor.c`, `board_wroom` |
| DS18B20 | 13 | `config.h`, `hal_temp.c` |
| Beeper | 25 | `config.cpp`, `hal_audio.c` |
| LCD 2004 I2C | SDA 21 / SCL 22, addr 0x27 | `config.cpp`, `hal_display_lcd2004.c` |
| Keypad rows | 19, 18, 5, 17, 16 | `config.cpp`, `hal_input_keypad.c` |
| Keypad cols | 15, 2, 0, 4 | `config.cpp`, `hal_input_keypad.c` |
Self-consistent with `docs/CURRENT_STATE.md`. Not related to the S3
panel.
## 3. Findings / discrepancies
1. **Display and touch pins are all correct.** Every QSPI, backlight
and GT911 pin in `board_jc4827w543/board.c` matches both the
vendor xlsx and the vendor Arduino demos. No changes needed.
2. **TP_RST on GPIO 38 is undocumented for the C model.** The xlsx
labels IO38 `RTP_CS` (its resistive-touch function). The capacitive
demos (`LvglWidgets/touch.h`) set `TOUCH_RES 38`, so on the C
variant IO38 is clearly the GT911 reset. Our code is right, but the
vendor table alone would not tell you that — the demo code is the
authority here.
3. **Motor EN/STEP/DIR are unassigned on the S3 board** (`GPIO_NUM_NC`
in `board.c`). Correct call — but usable free pins do exist.
Candidates from the vendor table + headers:
- Safest (plain free, on headers): **15, 16** and UART1 header
**17, 18** if UART1 is unused.
- Free in xlsx but not obviously headered: **5, 6, 7, 9, 14**.
- **Avoid**: 0 (BOOT/LCD_TE), 1–4/8 (LCD+touch+speaker), 10–13
(TF/RTP SPI), 19/20 (USB), 21 (LCD D0), 35–37 (octal PSRAM),
38–48 (used/strapping), 43/44 (console).
- GPIO 46 is xlsx-free but is a strapping pin — last resort only.
- Octal PSRAM (`sdkconfig.s3`: `CONFIG_SPIRAM_MODE_OCT=y`) means
33–37 are off-limits even though the xlsx only flags 35.
4. **Audio on S3 should use I2S, not a beeper GPIO.** The board has a
speaker amp on IO2 (LRCLK), IO41 (DIN), IO42 (BCLK) — P7 `Speak`
connector. `hal_audio` currently builds the LEDC buzzer only for
esp32 and a stub elsewhere; an S3 implementation should drive
I2S on those pins rather than allocating a GPIO.
5. **DS18B20 on S3 needs one free GPIO** (any of §3's candidates; it
only needs input+open-drain drive). Hal currently stubs temp on
non-esp32.
6. **Reserved/bus-shared notes**: GPIO 0 carries `LCD_TE` in addition
to BOOT — do not use it for anything else. TF card SPI (10–13) is
shared with resistive-touch signals on R models; on our C model
it's purely the SD slot.
7. **No conflicts found** between the code's S3 selections and the
vendor map — every consumed pin is one the vendor assigns to that
same function.
## Suggested motor wiring (for when S3 motor support lands)
| Signal | Suggested GPIO | Rationale |
| --- | --- | --- |
| STEP | 16 | free, on header, no boot function |
| DIR | 15 | free, on header |
| EN (active LOW) | 17 or 18 | UART1 header; free if UART1 unused |
| DS18B20 | 9 or 14 | free, any GPIO works |
If UART1 is wanted for something else, use 5/6/7 for EN/temp instead.
Keep motor EN's disable-first behaviour (drive inactive at boot before
enabling output mode) whatever pins are chosen.
+14
View File
@@ -0,0 +1,14 @@
# JC4827W543 pin audit - ERRATA
*Based on verification against the official vendor repository at <https://github.com/lsdlsd88/JC4827W543>.*
**No errors were found in the original `JC4827W543_PIN_AUDIT.md` document.**
### Verification Notes:
- **Vendor IO pin distribution:** The `4.3 inches IO pin distribution.xlsx` file perfectly matches the pinout matrix in the audit document (e.g., IO45 for LCD_CS, IO47 for LCD_CLK, etc., and SPECK_LRCLK / SPECK_DIN / SPECK_BCLK correctly documented as I2S lines).
- **Display Configuration:** `Arduino_ESP32QSPI` code in `LvglWidgets.ino` verifies the QSPI display pins as `CS:45, SCK:47, D0:21, D1:48, D2:40, D3:39` matching the audit exactly.
- **Touch Controller:** Capacitive GT911 configuration uses `TOUCH_SDA = 8`, `TOUCH_SCL = 4`, `TOUCH_RES = 38`, `TOUCH_INT = 3` which aligns with the undocumented `RTP_CS (38)` being effectively co-opted for capacitive reset, confirming the finding in the original document.
- **Demos:** The DHT11 demo at `1-Demo/Demo_Arduino/4_9_WIFI Web Servers DHT11` indeed uses `GPIO 27`, and the `4_7_WIFI Web Servers LED` demo uses `GPIO 16/17`. These assignments are generic demo choices as noted in the original document and correctly do not conflict with the board specifications.
- **Proposed GPIOs:** The suggested GPIOs for motor control (`16`, `15`, `17`, `18`, `9`, `14`, `5`, `6`, `7`) are correctly identified as free based on the schematic and code.
The original document is factually sound and can be completely trusted as the authoritative reference for this project.
+184
View File
@@ -0,0 +1,184 @@
# JC4827W543 pin audit
Comparison of the GPIO assignments in this tree against the vendor
documentation at <https://github.com/lsdlsd88/JC4827W543> (GUITION
factory docs: spec PDF, IO pin distribution xlsx, Arduino demos).
Board variants: `JC4827W543N` (no touch), `...R` (resistive XPT2046),
`...C` (capacitive GT911). Ours is the **C** model.
## 1. Vendor-documented pin allocation (authoritative)
From `5-IO pin distribution/4.3 inches IO pin distribution.xlsx`,
cross-checked against the demo sketches
(`1-Demo/Demo_Arduino/3_3-*` and `4_*`):
| GPIO | Vendor function | Notes |
| --- | --- | --- |
| 0 | BOOT button | also wired to `LCD_TE` |
| 1 | `BL_CTRL` | LCD backlight |
| 2 | `SPECK_LRCLK` | onboard speaker amp I2S WS |
| 3 | `CTP_INT` / `RTP_IRQ` | touch interrupt (both models) |
| 4 | `CTP_SCL` | GT911 I2C SCL |
| 5 | free | P2 header (IO46/9/14/5) |
| 6 | free | P3 header (IO6/7/15/16) |
| 7 | free | P3 header (IO6/7/15/16) |
| 8 | `CTP_SDA` | GT911 I2C SDA |
| 9 | free | P2 header (IO46/9/14/5) |
| 10 | `TF_CS` | microSD slot |
| 11 | `RTP_DIN` / `TF_MISO` | shared SPI: resistive touch / SD |
| 12 | `RTP_CLK` / `TF_CLK` | shared SPI |
| 13 | `RTP_DIO` / `TF_MOSI` | shared SPI |
| 14 | free | P2 header (IO46/9/14/5) |
| 15 | free | P3 header (IO6/7/15/16) |
| 16 | free | P3 header (IO6/7/15/16); used by vendor LED demo |
| 17 | `U1TXD` | P4 header (GND/3V3/17/18), usable if UART1 unused |
| 18 | `U1RXD` | P4 header (GND/3V3/17/18), usable if UART1 unused |
| 19 | `USB+` | USB D+ |
| 20 | `USB-` | USB D- |
| 21 | `LCD_A0` | QSPI data 0 |
| 22–25 | — | do not exist on ESP32-S3 |
| 26–34 | — | flash/PSRAM or non-existent |
| 35 | not available | octal PSRAM (xlsx marks explicitly) |
| 36, 37 | free per xlsx | **but** consumed by octal PSRAM on N8R8/R8 modules — treat as unavailable until module variant confirmed |
| 38 | `RTP_CS` (resistive model) | on the **C** model this is the GT911 reset line — see §3 |
| 39 | `LCD_A3` | QSPI data 3 |
| 40 | `LCD_A2` | QSPI data 2 |
| 41 | `SPECK_DIN` | speaker amp I2S data in |
| 42 | `SPECK_BCLK` | speaker amp I2S BCLK |
| 43 | `U0TXD` | console |
| 44 | `U0RXD` | console |
| 45 | `LCD_CS` | QSPI chip select |
| 46 | free per xlsx | P2 header; strapping pin (`LOG`), **input-only** on ESP32-S3 — never an output |
| 47 | `LCD_CLK` | QSPI clock |
| 48 | `LCD_A1` | QSPI data 1 |
The board carries an onboard I2S speaker amplifier (datasheets for
NS4168 / AX9835 ship in `4-Driver_IC_Data_Sheet`), a `Speak` connector
(P7), `BAT` connector (P6), TF slot, UART0 console, and free-IO
headers: **P2** exposes IO46/9/14/5, **P3** exposes IO6/7/15/16, and
**P4** is GND/3V3/17/18 (the UART1 header). Vendor demos drive LEDs on GPIO 16/17 and a
DHT11 on GPIO 27 — note GPIO 27 is **not** in the vendor pin table,
so that demo value is suspect/generic.
## 2. What the code assigns today
### `board_jc4827w543` (`components/board_jc4827w543/board.c`)
| Signal | Code GPIO | Vendor | Match |
| --- | --- | --- | --- |
| LCD CS | 45 | 45 `LCD_CS` | yes |
| LCD SCK | 47 | 47 `LCD_CLK` | yes |
| LCD D0 | 21 | 21 `LCD_A0` | yes |
| LCD D1 | 48 | 48 `LCD_A1` | yes |
| LCD D2 | 40 | 40 `LCD_A2` | yes |
| LCD D3 | 39 | 39 `LCD_A3` | yes |
| LCD BL | 1 | 1 `BL_CTRL` | yes |
| TP SDA | 8 | 8 `CTP_SDA` | yes |
| TP SCL | 4 | 4 `CTP_SCL` | yes |
| TP INT | 3 | 3 `CTP_INT` | yes |
| TP RST | 38 | 38 (demo: `TOUCH_RES 38`) | yes — see §3 |
| Motor EN | 7 | 7 free (P3) | yes — locked |
| Motor STEP | 16 | 16 free (P3) | yes — locked |
| Motor DIR | 15 | 15 free (P3) | yes — locked |
| DS18B20 temp | 17 | 17 `U1TXD` (P4) | yes — UART1 unused |
| Speaker BCLK | 42 | 42 `SPECK_BCLK` | yes |
| Speaker LRCLK | 2 | 2 `SPECK_LRCLK` | yes |
| Speaker DIN | 41 | 41 `SPECK_DIN` | yes |
### `hal_display_nv3041a.c`
Driver **NV3041A** over QSPI via `esp_lcd` SPI host, 480×272, quad
mode, 40 MHz. Matches the vendor demo
(`Arduino_ESP32QSPI(45,47,21,48,40,39)` + `Arduino_NV3041A`,
`Arduino_Canvas(480,272)`).
Caveat: the spec sheet PDF lists the driver chip as "ST3401A" — that
string appears nowhere else; every demo and the burn files use
NV3041A. Treat the PDF label as a typo.
### `hal_input_gt911.c`
GT911 at I2C addr `0x5D` (`GT911_SLAVE_ADDRESS1` in the vendor
`touch.h`), I2C_NUM_0 @ 400 kHz on SDA 8 / SCL 4. Reset sequence
(drive INT low, pulse RST, release INT to input) selects the 0x5D
address per the GT911 datasheet — matches the vendor demo's
`TOUCH_RES 38` / `TOUCH_INT 3` wiring.
### `board_wroom` + `hal_*` (classic ESP32, unchanged)
| Signal | GPIO | Source |
| --- | --- | --- |
| Stepper STEP | 12 | `config.h`, `hal_motor.c` |
| Stepper DIR | 14 | `config.h`, `hal_motor.c` |
| Stepper EN (active LOW) | 27 | `config.h`, `hal_motor.c`, `board_wroom` |
| DS18B20 | 13 | `config.h`, `hal_temp.c` |
| Beeper | 25 | `config.cpp`, `hal_audio.c` |
| LCD 2004 I2C | SDA 21 / SCL 22, addr 0x27 | `config.cpp`, `hal_display_lcd2004.c` |
| Keypad rows | 19, 18, 5, 17, 16 | `config.cpp`, `hal_input_keypad.c` |
| Keypad cols | 15, 2, 0, 4 | `config.cpp`, `hal_input_keypad.c` |
Self-consistent with `docs/CURRENT_STATE.md`. Not related to the S3
panel.
## 3. Findings / discrepancies
1. **Display and touch pins are all correct.** Every QSPI, backlight
and GT911 pin in `board_jc4827w543/board.c` matches both the
vendor xlsx and the vendor Arduino demos. No changes needed.
2. **TP_RST on GPIO 38 is undocumented for the C model.** The xlsx
labels IO38 `RTP_CS` (its resistive-touch function). The capacitive
demos (`LvglWidgets/touch.h`) set `TOUCH_RES 38`, so on the C
variant IO38 is clearly the GT911 reset. Our code is right, but the
vendor table alone would not tell you that — the demo code is the
authority here.
3. **Motor EN/STEP/DIR and DS18B20 are now locked on the S3 board.**
Header labels confirmed: P2 exposes IO46/9/14/5, P3 exposes
IO6/7/15/16, P4 is GND/3V3/17/18 (the UART1 header).
- Motor STEP **16**, DIR **15**, EN **7** (active LOW) — all on P3;
IO6 stays spare.
- DS18B20 on **17** — P4 puts GND, 3V3 and data on one header;
UART1 is unused.
- Speaker amp I2S stays on the vendor pins: BCLK **42**,
LRCLK **2**, DIN **41** (P7 `Speak`).
- **Avoid**: 0 (BOOT/LCD_TE), 1–4/8 (LCD+touch+speaker), 10–13
(TF/RTP SPI), 19/20 (USB), 21 (LCD D0), 35–37 (octal PSRAM),
38–48 (used/strapping), 43/44 (console).
- GPIO 46 is xlsx-free and on P2 but is **input-only** on the
ESP32-S3 — never use it for outputs.
- Octal PSRAM (`sdkconfig.s3`: `CONFIG_SPIRAM_MODE_OCT=y`) means
33–37 are off-limits even though the xlsx only flags 35.
4. **Audio on S3 should use I2S, not a beeper GPIO.** The board has a
speaker amp on IO2 (LRCLK), IO41 (DIN), IO42 (BCLK) — P7 `Speak`
connector. `hal_audio` currently builds the LEDC buzzer only for
esp32 and a stub elsewhere; an S3 implementation should drive
I2S on those pins rather than allocating a GPIO.
5. **DS18B20 on S3 needs one free GPIO** (any of §3's candidates; it
only needs input+open-drain drive). Hal currently stubs temp on
non-esp32.
6. **Reserved/bus-shared notes**: GPIO 0 carries `LCD_TE` in addition
to BOOT — do not use it for anything else. TF card SPI (10–13) is
shared with resistive-touch signals on R models; on our C model
it's purely the SD slot.
7. **No conflicts found** between the code's S3 selections and the
vendor map — every consumed pin is one the vendor assigns to that
same function.
## Motor / temp / speaker wiring as locked (phase I01)
| Signal | GPIO | Rationale |
| --- | --- | --- |
| STEP | 16 | free, P3 header, no boot function |
| DIR | 15 | free, P3 header |
| EN (active LOW) | 7 | free, P3 header |
| DS18B20 | 17 | P4 header carries GND/3V3/17/18 — one connector for the sensor |
| Speaker BCLK / LRCLK / DIN | 42 / 2 / 41 | onboard amp wired to P7 `Speak` |
Keep motor EN's disable-first behaviour (drive inactive at boot before
enabling output mode).
+41
View File
@@ -0,0 +1,41 @@
# INTEGRATION MEGAPLAN — AutoFilm-ESP32 S3 Hardware Integration
Audience: coding agent. One phase = one session. Do not start the next phase in the same session.
## Summary
This megaplan drives the hardware integration for the ESP32-S3 (JC4827W543) board, activating the stepper motor, DS18B20 temperature sensor, and I2S speaker using conflict-free, header-available GPIOs. To ensure success, these changes have been broken down from a single monolithic phase into a sequence of safe, isolated phases.
## Protocol (every session)
1. Ensure you are on the appropriate integration branch off `develop`.
2. Read only: `AGENTS.md`, this file (status table), **the assigned phase file**. Open other docs only if the phase `READ:` list says so.
3. Execute `IN` only. Honour `OUT` and `FORBIDDEN`.
4. Run `VERIFY` exactly. Do not push if any verify item fails.
5. Commits: messages listed in the phase. Imperative. No secret tokens.
6. Push to origin. PR target is `develop`, not `main`.
7. Set phase `STATUS:` to `DONE` in the phase file **and** this table. One-line `Notes` if you diverged (API name only — do not silently change behaviour).
If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase file with the exact error.
## Frozen constraints (never reinterpret)
- The motor EN/STEP/DIR, DS18B20, and I2S audio pins for the JC4827W543 are strictly defined in this plan and the pin audit doc.
- **Constraints that eliminate S3 pins:**
- QSPI display: 45, 47, 21, 48, 40, 39 + BL 1 — consumed
- GT911: SDA 8, SCL 4, INT 3, RST 38 — consumed
- Speaker I2S: 2 (LRCLK), 41 (DIN), 42 (BCLK) — consumed by onboard amp
- TF slot: 10–13; USB: 19/20; UART0: 43/44; BOOT+LCD_TE: 0
- Octal PSRAM: 33–37 off-limits
- GPIO46 is INPUT-ONLY on ESP32-S3 — excluded from outputs entirely
- UI never blocks on hardware operations (motor, OneWire, I2S).
- Watchdog stays **on**. No `vTaskDelete` of long-lived workers.
## Status
| ID | File | Session goal | STATUS |
| --- | --- | --- | --- |
| I01 | `integration/I01-board-configs.md` | Board pin allocations & accessors | DONE |
| I02 | `integration/I02-temp-fix.md` | Temp HAL shared & starvation fix | DONE |
| I03 | `integration/I03-motor-hal.md` | Motor HAL shared across boards | DONE |
| I04 | `integration/I04-audio-i2s.md` | I2S audio implementation for S3 | DONE |
+11 -5
View File
@@ -37,14 +37,20 @@ If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase fi
| P02 | `refactor/P02-ui-cmd-process.md` | `ui_cmd` + `app_process` + golden tests | DONE | | 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 | | P03 | `refactor/P03-machine-host.md` | `app_machine` + stub HAL + SM tests | DONE |
| P04 | `refactor/P04-gitea-ci.md` | `.gitea/workflows/ci.yml` live | DONE | | P04 | `refactor/P04-gitea-ci.md` | `.gitea/workflows/ci.yml` live | DONE |
| P05 | `refactor/P05-hal-wroom-motion.md` | WROOM motor/temp/audio HAL | BLOCKED | | 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 | TODO | | 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 | TODO | | P07 | `refactor/P07-board-s3-ui.md` | JC4827W543 display+GT911+STOP | DONE |
| P08 | `refactor/P08-idf-debt.md` | Replace Arduino drivers (RMT etc.) | TODO | | P08 | `refactor/P08-idf-debt.md` | Replace Arduino drivers (RMT etc.) | DONE |
P04 Notes: merge_bin uses `${{ gitea.sha }}` (not `github.sha`); runner unverified (no Actions run). P04 Notes: merge_bin uses `${{ gitea.sha }}` (not `github.sha`); runner unverified (no Actions run).
P05 Notes: BLOCKED — no app_machine + HAL headers from P03 (branch @ 415c13a). Not implemented. 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.
## Dependency ## Dependency
@@ -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.
@@ -1,18 +1,6 @@
BLOCKED: P03 artifacts missing on branch refactor/esp-idf-modular-ui @ 415c13a.
Required and absent:
- components/app_machine/ (include/app_machine.h, app_machine.c)
- components/hal_motor/include/hal_motor.h
- components/hal_temp/include/hal_temp.h
- components/hal_audio/include/hal_audio.h
Megaplan table still lists P03 STATUS=TODO. Protocol: P05 requires P03 APIs.
Do not invent or change public HAL/app_machine APIs. Do not implement P01–P04 in this session.
No device HAL, tasks, Arduino-as-component, or main.c cutover landed.
# P05 — WROOM HAL motor / temp / audio # P05 — WROOM HAL motor / temp / audio
STATUS: BLOCKED STATUS: DONE
DEPENDS: P03 DEPENDS: P03
READ: this file, `include/config.h`, `src/motor.cpp`, `src/temperature.cpp`, `src/sound.cpp`, `components/hal_*/include/*.h` 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 OUT: rewriting `app_ui` / deleting menus; S3 motor pins; disabling WDT; `vTaskDelete` motor
@@ -100,8 +88,8 @@ Manual on hardware (if unavailable, Notes=`hw not flashed`; still land code):
## DoD ## DoD
- [ ] EN HIGH at init and on stop - [x] EN HIGH at init and on stop
- [ ] stop_req checked inside step loop - [x] stop_req checked inside step loop
- [ ] OneWire only in temp_task - [x] OneWire only in temp_task
- [ ] tone/LEDC only in audio_task - [x] tone/LEDC only in audio_task
- [ ] STATUS→DONE - [x] STATUS→DONE
@@ -1,6 +1,6 @@
# P06 — WROOM UI cutover (2004 + keypad + Stop/Resume) # P06 — WROOM UI cutover (2004 + keypad + Stop/Resume)
STATUS: TODO STATUS: DONE
DEPENDS: P05 DEPENDS: P05
READ: this file, `src/menu.cpp`, `src/display.cpp`, `src/config.cpp`, `docs/TARGET_ARCHITECTURE.md` (screens + stop rules) READ: this file, `src/menu.cpp`, `src/display.cpp`, `src/config.cpp`, `docs/TARGET_ARCHITECTURE.md` (screens + stop rules)
OUT: S3 LVGL, NVS profiles, keeping blocking `getEntEscInput` as the live path OUT: S3 LVGL, NVS profiles, keeping blocking `getEntEscInput` as the live path
@@ -130,8 +130,12 @@ If no hardware: still land; Notes=`hw smoke pending`; do not skip the input dual
## DoD ## DoD
- [ ] Esc during run always EN-off - [x] Esc during run always EN-off
- [ ] No malloc mm:ss - [x] No malloc mm:ss
- [ ] No blocking alarm on UI/machine - [x] No blocking alarm on UI/machine
- [ ] Legacy menus not in the IDF link - [x] Legacy menus not in the IDF link
- [ ] STATUS→DONE - [x] STATUS→DONE
## Notes
Notes=`hw smoke pending`. Host ctest green. WROOM `idf.py` build attempted in sandbox without ESP-IDF installed.
+11 -5
View File
@@ -1,7 +1,13 @@
# P07 — JC4827W543 display + GT911 + STOP # P07 — JC4827W543 display + GT911 + STOP
STATUS: TODO STATUS: DONE
DEPENDS: P06 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 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 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 FORBIDDEN: `lv_*` types in `ui_cmd.h` / `app_machine.h`; blocking in flush_cb beyond DMA wait; copying WROOM GPIO map blindly
@@ -52,7 +58,7 @@ Hardware S3: ProgramSelect visible, tap process, Running shows name/remaining/te
## DoD ## DoD
- [ ] Same commands as keypad - [x] Same commands as keypad
- [ ] No lv types in machine/process/cmd headers - [x] No lv types in machine/process/cmd headers
- [ ] WROOM firmware unchanged in behaviour - [x] WROOM firmware unchanged in behaviour
- [ ] STATUS→DONE - [x] STATUS→DONE
+6 -4
View File
@@ -1,6 +1,6 @@
# P08 — Arduino debt burn-down # P08 — Arduino debt burn-down
STATUS: TODO STATUS: DONE
DEPENDS: P06 (behaviour parity). P07 optional. DEPENDS: P06 (behaviour parity). P07 optional.
READ: this file, `components/hal_motor/**`, HAL headers from P03 READ: this file, `components/hal_motor/**`, HAL headers from P03
OUT: recipe edits; UI redesign; LVGL-by-default OUT: recipe edits; UI redesign; LVGL-by-default
@@ -35,6 +35,8 @@ Per numbered item above, e.g. `Replace AccelStepper with RMT step pulse HAL`.
## DoD ## DoD
- [ ] `rg -n "Arduino.h|AccelStepper|LiquidCrystal|DallasTemperature|#include <Keypad.h>|#include <OneWire.h>"` empty in `main/` `components/` - [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/`)
- [ ] Stop still EN-off first - [x] Stop still EN-off first (`hal_motor_request_stop` EN HIGH then RMT abort)
- [ ] STATUS→DONE - [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.
+33 -5
View File
@@ -1,11 +1,39 @@
if(NOT DEFINED AUTOFILM_BOARD) if(NOT DEFINED AUTOFILM_BOARD)
set(AUTOFILM_BOARD wroom CACHE STRING "wroom|jc4827w543") set(AUTOFILM_BOARD wroom CACHE STRING "wroom|jc4827w543")
endif() endif()
if(AUTOFILM_BOARD STREQUAL "wroom") if(IDF_TARGET STREQUAL "esp32s3" OR AUTOFILM_BOARD STREQUAL "jc4827w543")
set(BOARD_COMP board_wroom)
elseif(AUTOFILM_BOARD STREQUAL "jc4827w543")
set(BOARD_COMP board_jc4827w543) set(BOARD_COMP board_jc4827w543)
elseif(AUTOFILM_BOARD STREQUAL "wroom" OR IDF_TARGET STREQUAL "esp32")
set(BOARD_COMP board_wroom)
else() else()
message(FATAL_ERROR "AUTOFILM_BOARD=${AUTOFILM_BOARD}") 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() endif()
idf_component_register(SRCS "main.c" INCLUDE_DIRS "." REQUIRES ${BOARD_COMP} driver)
+191 -4
View File
@@ -1,13 +1,183 @@
#include "board.h" #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_log.h"
#include "esp_timer.h"
#include "esp_task_wdt.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/queue.h"
#ifndef AUTOFILM_GIT_DESC
#define AUTOFILM_GIT_DESC "dev"
#endif
static const char *TAG = "app"; 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_task_wdt_add(NULL);
const TickType_t period = pdMS_TO_TICKS(40); /* 25 Hz */
for (;;) {
ui_raw_key_t raw;
while (hal_input_pop_key(&raw)) {
map_and_post_key(raw.key);
}
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_task_wdt_add(NULL);
const TickType_t period = pdMS_TO_TICKS(20); /* 50 Hz */
for (;;) {
ui_cmd_t cmd;
while (xQueueReceive(s_cmdq, &cmd, 0) == pdTRUE) {
app_machine_handle_cmd(&cmd);
}
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000ULL);
app_machine_tick(now_ms);
esp_task_wdt_reset();
vTaskDelay(period);
}
}
static void ui_task(void *arg)
{
(void)arg;
esp_task_wdt_add(NULL);
hal_display_clear();
hal_display_text(0, 0, "AUTOFILM");
hal_display_text(0, 1, AUTOFILM_GIT_DESC);
vTaskDelay(pdMS_TO_TICKS(1000));
app_ui_init();
app_ui_draw();
const TickType_t period = pdMS_TO_TICKS(25);
for (;;) {
ui_evt_t ev;
while (app_machine_last_event(&ev) == 1) {
app_ui_on_event(&ev);
}
/* Running remaining is read from the machine on each draw (>=4 Hz). */
if (app_ui_is_dirty() || app_machine_state() == ST_RUNNING) {
app_ui_draw();
}
esp_task_wdt_reset();
vTaskDelay(period);
}
}
static void temp_task(void *arg)
{
(void)arg;
esp_task_wdt_add(NULL);
for (;;) {
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();
}
}
#endif
void app_main(void) void app_main(void)
{ {
ESP_LOGI(TAG, "board=%s", board_name()); ESP_LOGI(TAG, "board=%s desc=%s", board_name(), AUTOFILM_GIT_DESC);
gpio_num_t en_pin = board_pin_motor_en(); gpio_num_t en_pin = board_pin_motor_en();
if (en_pin != GPIO_NUM_NC) { if (en_pin != GPIO_NUM_NC) {
@@ -22,7 +192,24 @@ void app_main(void)
gpio_set_level(en_pin, board_motor_en_disable_level()); gpio_set_level(en_pin, board_motor_en_disable_level());
} }
while (1) { app_process_init();
vTaskDelay(pdMS_TO_TICKS(1000)); 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));
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. */
} }
+4
View File
@@ -10,6 +10,10 @@ void hal_temp_init(void)
stub_temp_c = 20.0f; stub_temp_c = 20.0f;
} }
void hal_temp_tick(void)
{
}
esp_err_t hal_temp_read_c(float *out) esp_err_t hal_temp_read_c(float *out)
{ {
if (stub_temp_fail) { if (stub_temp_fail) {