Compare commits
28
Commits
2c61e3576f
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d70df219f8 | ||
|
|
7e854e10f0 | ||
|
|
b6c385e09c | ||
|
|
a9f76ea058 | ||
|
|
c40c714028 | ||
|
|
f87ab0c65f | ||
|
|
b4230b2a0e | ||
|
|
04e1ed0f16 | ||
|
|
a33b4b0c74 | ||
|
|
73f3158b27 | ||
|
|
a3fe809359 | ||
|
|
637ea8cb6e | ||
|
|
84b60155dd | ||
|
|
7406114368 | ||
|
|
0b0a713098 | ||
|
|
9ea89ac467 | ||
|
|
5f1727da7d | ||
|
|
32af44a702 | ||
|
|
82f55af4b1 | ||
|
|
880f9a18c5 | ||
|
|
e04d0f0e7f | ||
|
|
114135fba0 | ||
|
|
4fe0648005 | ||
|
|
ce9af6382e | ||
|
|
d21f7ad4e4 | ||
|
|
f47299b939 | ||
|
|
51faa0fe59 | ||
|
|
a19accf6a7 |
+22
-16
@@ -16,19 +16,15 @@ jobs:
|
||||
- name: Install host toolchain
|
||||
if: hashFiles('tests/host/CMakeLists.txt') != ''
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
if command -v sudo >/dev/null 2>&1 && [ "$(id -u)" -ne 0 ]; then
|
||||
SUDO=sudo
|
||||
else
|
||||
SUDO=
|
||||
fi
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y --no-install-recommends cmake gcc g++ make
|
||||
export DEBIAN_FRONTEND=noninteractive && \
|
||||
if command -v sudo >/dev/null 2>&1 && [ "$(id -u)" -ne 0 ]; then SUDO=sudo; else SUDO=; fi && \
|
||||
$SUDO apt-get update && \
|
||||
$SUDO apt-get install -y -q --no-install-recommends cmake gcc g++ make
|
||||
- name: Host unit tests
|
||||
if: hashFiles('tests/host/CMakeLists.txt') != ''
|
||||
run: |
|
||||
cmake -S tests/host -B build/host
|
||||
cmake --build build/host
|
||||
cmake -S tests/host -B build/host && \
|
||||
cmake --build build/host && \
|
||||
ctest --test-dir build/host --output-on-failure
|
||||
|
||||
firmware:
|
||||
@@ -47,21 +43,30 @@ jobs:
|
||||
target: esp32s3
|
||||
sdkconfig: sdkconfig.s3
|
||||
steps:
|
||||
- name: Install Node.js
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive && \
|
||||
export NEEDRESTART_MODE=a && \
|
||||
if command -v sudo >/dev/null 2>&1 && [ "$(id -u)" -ne 0 ]; then SUDO=sudo; else SUDO=; fi && \
|
||||
$SUDO apt-get update && \
|
||||
$SUDO apt-get install -y -q --no-install-recommends nodejs
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build
|
||||
if: hashFiles('CMakeLists.txt') != ''
|
||||
run: |
|
||||
git config --global --add safe.directory '*'
|
||||
idf.py -B build/${{ matrix.board }} set-target ${{ matrix.target }}
|
||||
. $IDF_PATH/export.sh && \
|
||||
git config --global --add safe.directory '*' && \
|
||||
idf.py -B build/${{ matrix.board }} set-target ${{ matrix.target }} && \
|
||||
idf.py -B build/${{ matrix.board }} \
|
||||
-D SDKCONFIG_DEFAULTS="sdkconfig.defaults;${{ matrix.sdkconfig }}" \
|
||||
-D AUTOFILM_BOARD=${{ matrix.board }} \
|
||||
build
|
||||
idf.py -B build/${{ matrix.board }} size | tee size-${{ matrix.board }}.txt
|
||||
build && \
|
||||
idf.py -B build/${{ matrix.board }} size | tee size-${{ matrix.board }}.txt && \
|
||||
cd build/${{ matrix.board }} && \
|
||||
python $IDF_PATH/components/esptool_py/esptool/esptool.py \
|
||||
--chip ${{ matrix.target }} merge_bin \
|
||||
-o autofilm-${{ matrix.board }}-${{ gitea.sha }}.bin \
|
||||
@build/${{ matrix.board }}/flash_args
|
||||
-o ../../autofilm-${{ matrix.board }}-${{ gitea.sha }}.bin \
|
||||
@flash_args
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: hashFiles('CMakeLists.txt') != ''
|
||||
with:
|
||||
@@ -74,3 +79,4 @@ jobs:
|
||||
build/${{ matrix.board }}/flasher_args.json
|
||||
build/${{ matrix.board }}/sdkconfig
|
||||
size-${{ matrix.board }}.txt
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "app_ui.c" "app_ui_color.c"
|
||||
INCLUDE_DIRS "include" "."
|
||||
REQUIRES ui_cmd app_machine app_process
|
||||
PRIV_REQUIRES hal_display)
|
||||
@@ -0,0 +1,200 @@
|
||||
#include "app_ui.h"
|
||||
#include "screens.h"
|
||||
#include "app_machine.h"
|
||||
#include "app_process.h"
|
||||
#include "hal_display.h"
|
||||
|
||||
void app_ui_draw_color_chrome(machine_state_t st);
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static ui_evt_t s_last;
|
||||
static bool s_have;
|
||||
static bool s_dirty;
|
||||
|
||||
static void fmt_mmss(char *buf, size_t n, uint32_t total_s)
|
||||
{
|
||||
unsigned m = (unsigned)(total_s / 60u);
|
||||
unsigned s = (unsigned)(total_s % 60u);
|
||||
if (m > 99u) {
|
||||
m = 99u;
|
||||
}
|
||||
snprintf(buf, n, "%02u:%02u", m, s);
|
||||
}
|
||||
|
||||
static void draw_temp_row3(const ui_evt_t *ev)
|
||||
{
|
||||
/* row3 col13: thermo + tt.tC or -- */
|
||||
char tbuf[8];
|
||||
if (ev != NULL && ev->temp_ok) {
|
||||
snprintf(tbuf, sizeof(tbuf), "%4.1fC", (double)ev->temp_c);
|
||||
hal_display_glyph_thermo(13, 3);
|
||||
hal_display_text(14, 3, tbuf);
|
||||
} else {
|
||||
hal_display_text(13, 3, " --");
|
||||
}
|
||||
}
|
||||
|
||||
static const char *step_name(uint8_t proc, uint8_t step)
|
||||
{
|
||||
const process_def_t *p = app_process_get(proc);
|
||||
if (p == NULL || step >= p->step_count) {
|
||||
return "";
|
||||
}
|
||||
return p->steps[step].name;
|
||||
}
|
||||
|
||||
static void draw_program_select(void)
|
||||
{
|
||||
hal_display_clear();
|
||||
hal_display_text(0, 0, "Select Programme:");
|
||||
hal_display_text(0, 1, "1. C41 4. ECN-2");
|
||||
hal_display_text(0, 2, "2. E6 5. Custom");
|
||||
hal_display_text(0, 3, "3. B&W 6. B&W Rev");
|
||||
}
|
||||
|
||||
static void draw_step_select(const ui_evt_t *ev)
|
||||
{
|
||||
char line[32];
|
||||
char mm[8];
|
||||
uint16_t ts = ev->time_s;
|
||||
fmt_mmss(mm, sizeof(mm), ts);
|
||||
int pref = (int)(ev->temp_pref_c + (ev->temp_pref_c >= 0 ? 0.5f : -0.5f));
|
||||
hal_display_clear();
|
||||
hal_display_text(0, 0, "Step Time Temp");
|
||||
snprintf(line, sizeof(line), "%-9.9s %s %2dC", step_name(ev->process_id, ev->step_index), mm, pref);
|
||||
hal_display_text(0, 1, line);
|
||||
hal_display_text(0, 2, "Scroll / Esc / Ent");
|
||||
draw_temp_row3(ev);
|
||||
}
|
||||
|
||||
static void draw_armed(const ui_evt_t *ev)
|
||||
{
|
||||
char line[32];
|
||||
char mm[8];
|
||||
fmt_mmss(mm, sizeof(mm), ev->time_s);
|
||||
int pref = (int)(ev->temp_pref_c + (ev->temp_pref_c >= 0 ? 0.5f : -0.5f));
|
||||
hal_display_clear();
|
||||
snprintf(line, sizeof(line), "%-10.10s %s %2dC", step_name(ev->process_id, ev->step_index), mm, pref);
|
||||
hal_display_text(0, 0, line);
|
||||
hal_display_text(0, 1, "Ent:start Esc:quit");
|
||||
draw_temp_row3(ev);
|
||||
}
|
||||
|
||||
static void draw_running(const ui_evt_t *ev)
|
||||
{
|
||||
char line[32];
|
||||
char mm[8];
|
||||
uint32_t rem_s = (ev->remaining_ms + 999u) / 1000u;
|
||||
fmt_mmss(mm, sizeof(mm), rem_s);
|
||||
hal_display_clear();
|
||||
snprintf(line, sizeof(line), "%-20.20s", step_name(ev->process_id, ev->step_index));
|
||||
hal_display_text(0, 0, line);
|
||||
snprintf(line, sizeof(line), "Remaining: %s", mm);
|
||||
hal_display_text(0, 1, line);
|
||||
draw_temp_row3(ev);
|
||||
}
|
||||
|
||||
static void draw_stopped(const ui_evt_t *ev)
|
||||
{
|
||||
char line[32];
|
||||
char mm[8];
|
||||
uint32_t rem_s = (ev->remaining_ms + 999u) / 1000u;
|
||||
fmt_mmss(mm, sizeof(mm), rem_s);
|
||||
hal_display_clear();
|
||||
snprintf(line, sizeof(line), "%-10.10s %s", step_name(ev->process_id, ev->step_index), mm);
|
||||
hal_display_text(0, 0, line);
|
||||
hal_display_text(0, 1, "E:resume X:back");
|
||||
draw_temp_row3(ev);
|
||||
}
|
||||
|
||||
static void draw_complete(const ui_evt_t *ev)
|
||||
{
|
||||
char line[32];
|
||||
hal_display_clear();
|
||||
snprintf(line, sizeof(line), "%-10.10s Done", step_name(ev->process_id, ev->step_index));
|
||||
hal_display_text(0, 0, line);
|
||||
hal_display_text(0, 1, "E:next X:back");
|
||||
draw_temp_row3(ev);
|
||||
}
|
||||
|
||||
void app_ui_init(void)
|
||||
{
|
||||
memset(&s_last, 0, sizeof(s_last));
|
||||
s_have = false;
|
||||
s_dirty = true;
|
||||
}
|
||||
|
||||
void app_ui_on_event(const ui_evt_t *ev)
|
||||
{
|
||||
if (ev == NULL) {
|
||||
return;
|
||||
}
|
||||
if (ev->id == EVT_TEMP_UPDATED && s_have) {
|
||||
s_last.temp_c = ev->temp_c;
|
||||
s_last.temp_ok = ev->temp_ok;
|
||||
s_dirty = true;
|
||||
return;
|
||||
}
|
||||
if (ev->id == EVT_STEP_PROGRESS && s_have) {
|
||||
s_last.remaining_ms = ev->remaining_ms;
|
||||
s_last.id = ev->id;
|
||||
s_dirty = true;
|
||||
return;
|
||||
}
|
||||
s_last = *ev;
|
||||
s_have = true;
|
||||
s_dirty = true;
|
||||
}
|
||||
|
||||
bool app_ui_is_dirty(void)
|
||||
{
|
||||
return s_dirty;
|
||||
}
|
||||
|
||||
void app_ui_draw(void)
|
||||
{
|
||||
machine_state_t st = app_machine_state();
|
||||
ui_evt_t ev = s_last;
|
||||
if (!s_have) {
|
||||
ev.temp_ok = false;
|
||||
}
|
||||
ev.remaining_ms = app_machine_remaining_ms();
|
||||
ev.process_id = app_machine_process_id();
|
||||
ev.step_index = app_machine_step_index();
|
||||
if (app_process_get(ev.process_id) != NULL) {
|
||||
ev.time_s = app_process_time_s(ev.process_id, ev.step_index);
|
||||
const process_def_t *p = app_process_get(ev.process_id);
|
||||
if (p != NULL && ev.step_index < p->step_count) {
|
||||
ev.temp_pref_c = p->steps[ev.step_index].temp_pref_c;
|
||||
}
|
||||
}
|
||||
|
||||
switch (st) {
|
||||
case ST_IDLE:
|
||||
draw_program_select();
|
||||
break;
|
||||
case ST_STEP_SELECT:
|
||||
draw_step_select(&ev);
|
||||
break;
|
||||
case ST_ARMED:
|
||||
draw_armed(&ev);
|
||||
break;
|
||||
case ST_RUNNING:
|
||||
draw_running(&ev);
|
||||
break;
|
||||
case ST_STOPPED:
|
||||
draw_stopped(&ev);
|
||||
break;
|
||||
case ST_COMPLETE:
|
||||
draw_complete(&ev);
|
||||
break;
|
||||
default:
|
||||
draw_program_select();
|
||||
break;
|
||||
}
|
||||
app_ui_draw_color_chrome(st);
|
||||
hal_display_flush();
|
||||
s_dirty = false;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#include "app_ui.h"
|
||||
#include "app_machine.h"
|
||||
#include "hal_display.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
bool app_ui_touch_to_key(int x, int y, char *out_key)
|
||||
{
|
||||
if (out_key == NULL) {
|
||||
return false;
|
||||
}
|
||||
machine_state_t st = app_machine_state();
|
||||
const int w = HAL_DISPLAY_PX_W;
|
||||
const int h = HAL_DISPLAY_PX_H;
|
||||
const int sk = HAL_DISPLAY_SOFTKEY_H;
|
||||
|
||||
if (x < 0 || y < 0 || x >= w || y >= h) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (st == ST_RUNNING || st == ST_COMPLETE) {
|
||||
if (y >= h - sk) {
|
||||
*out_key = 'X';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (st == ST_STOPPED) {
|
||||
if (y >= h - sk) {
|
||||
*out_key = (x < w / 2) ? 'E' : 'X';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (st == ST_IDLE) {
|
||||
/* 2x3 process grid matching ProgramSelect labels. */
|
||||
int col = (x < w / 2) ? 0 : 1;
|
||||
int row = y / (h / 3);
|
||||
if (row > 2) {
|
||||
row = 2;
|
||||
}
|
||||
/* left: 1,2,3 right: 4,5,6 */
|
||||
int id = row + col * 3;
|
||||
*out_key = (char)('1' + id);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (st == ST_ARMED) {
|
||||
if (y >= h - sk) {
|
||||
*out_key = (x < w / 2) ? 'E' : 'X';
|
||||
return true;
|
||||
}
|
||||
*out_key = 'E';
|
||||
return true;
|
||||
}
|
||||
|
||||
/* STEP_SELECT: strips for U/D/L/R, body Ent */
|
||||
if (y < 40) {
|
||||
*out_key = 'U';
|
||||
return true;
|
||||
}
|
||||
if (y >= h - sk) {
|
||||
*out_key = 'D';
|
||||
return true;
|
||||
}
|
||||
if (x < 80) {
|
||||
*out_key = 'L';
|
||||
return true;
|
||||
}
|
||||
if (x >= w - 80) {
|
||||
*out_key = 'R';
|
||||
return true;
|
||||
}
|
||||
*out_key = 'E';
|
||||
return true;
|
||||
}
|
||||
|
||||
void app_ui_draw_color_chrome(machine_state_t st)
|
||||
{
|
||||
switch (st) {
|
||||
case ST_RUNNING:
|
||||
case ST_COMPLETE:
|
||||
hal_display_softkey(0, 1, "STOP");
|
||||
break;
|
||||
case ST_STOPPED:
|
||||
hal_display_softkey(0, 2, "RESUME");
|
||||
hal_display_softkey(1, 2, "BACK");
|
||||
break;
|
||||
case ST_ARMED:
|
||||
hal_display_softkey(0, 2, "START");
|
||||
hal_display_softkey(1, 2, "QUIT");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_cmd.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void app_ui_init(void);
|
||||
void app_ui_on_event(const ui_evt_t *ev);
|
||||
void app_ui_draw(void);
|
||||
bool app_ui_is_dirty(void);
|
||||
|
||||
/* Map colour-panel touch to the same raw keys as the WROOM keypad. */
|
||||
bool app_ui_touch_to_key(int x, int y, char *out_key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
/* Screen ids used by app_ui. Layouts are 20x4 WROOM strings. */
|
||||
typedef enum {
|
||||
UI_SCREEN_PROGRAM_SELECT = 0,
|
||||
UI_SCREEN_STEP_SELECT,
|
||||
UI_SCREEN_ARMED,
|
||||
UI_SCREEN_RUNNING,
|
||||
UI_SCREEN_STOPPED,
|
||||
UI_SCREEN_COMPLETE
|
||||
} app_ui_screen_t;
|
||||
@@ -1,5 +1,37 @@
|
||||
#include "board.h"
|
||||
|
||||
/*
|
||||
* JC4827W543C pin lock (community 480x272 NV3041A + GT911).
|
||||
*
|
||||
* Display NV3041A QSPI:
|
||||
* CS 45
|
||||
* SCK 47
|
||||
* D0 21
|
||||
* D1 48
|
||||
* D2 40
|
||||
* D3 39
|
||||
* BL 1
|
||||
*
|
||||
* GT911 I2C:
|
||||
* SDA 8
|
||||
* SCL 4
|
||||
* INT 3
|
||||
* RST 38
|
||||
* addr 0x5D
|
||||
*
|
||||
* Motor (P3 header IO6/7/15/16):
|
||||
* EN 7 active LOW (disable level 1)
|
||||
* STEP 16
|
||||
* DIR 15
|
||||
*
|
||||
* DS18B20 temp: 17 (P4 header GND/3V3/17/18, UART1 unused)
|
||||
*
|
||||
* Speaker I2S (onboard amp, P7 Speak connector):
|
||||
* BCLK 42
|
||||
* LRCLK 2
|
||||
* DIN 41
|
||||
*/
|
||||
|
||||
const char *board_name(void)
|
||||
{
|
||||
return "jc4827w543";
|
||||
@@ -7,10 +39,31 @@ const char *board_name(void)
|
||||
|
||||
gpio_num_t board_pin_motor_en(void)
|
||||
{
|
||||
return GPIO_NUM_NC;
|
||||
return (gpio_num_t)7;
|
||||
}
|
||||
|
||||
int board_motor_en_disable_level(void)
|
||||
{
|
||||
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
|
||||
#include "driver/gpio.h"
|
||||
|
||||
const char *board_name(void);
|
||||
gpio_num_t board_pin_motor_en(void);
|
||||
int board_motor_en_disable_level(void);
|
||||
gpio_num_t board_pin_motor_step(void);
|
||||
gpio_num_t board_pin_motor_dir(void);
|
||||
gpio_num_t board_pin_temp(void);
|
||||
gpio_num_t board_pin_spk_bclk(void);
|
||||
gpio_num_t board_pin_spk_lrclk(void);
|
||||
gpio_num_t board_pin_spk_din(void);
|
||||
|
||||
gpio_num_t board_pin_lcd_cs(void);
|
||||
gpio_num_t board_pin_lcd_sck(void);
|
||||
gpio_num_t board_pin_lcd_d0(void);
|
||||
gpio_num_t board_pin_lcd_d1(void);
|
||||
gpio_num_t board_pin_lcd_d2(void);
|
||||
gpio_num_t board_pin_lcd_d3(void);
|
||||
gpio_num_t board_pin_lcd_bl(void);
|
||||
|
||||
gpio_num_t board_pin_tp_sda(void);
|
||||
gpio_num_t board_pin_tp_scl(void);
|
||||
gpio_num_t board_pin_tp_int(void);
|
||||
gpio_num_t board_pin_tp_rst(void);
|
||||
|
||||
@@ -14,3 +14,7 @@ int board_motor_en_disable_level(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
gpio_num_t board_pin_motor_step(void) { return (gpio_num_t)12; }
|
||||
gpio_num_t board_pin_motor_dir(void) { return (gpio_num_t)14; }
|
||||
gpio_num_t board_pin_temp(void) { return (gpio_num_t)13; }
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
dependencies:
|
||||
espressif/arduino-esp32: "~3.2.1"
|
||||
dependencies: {}
|
||||
|
||||
@@ -3,3 +3,6 @@
|
||||
const char *board_name(void);
|
||||
gpio_num_t board_pin_motor_en(void);
|
||||
int board_motor_en_disable_level(void);
|
||||
gpio_num_t board_pin_motor_step(void);
|
||||
gpio_num_t board_pin_motor_dir(void);
|
||||
gpio_num_t board_pin_temp(void);
|
||||
|
||||
@@ -2,8 +2,11 @@ set(srcs)
|
||||
set(priv_req driver)
|
||||
|
||||
if(IDF_TARGET STREQUAL "esp32")
|
||||
list(APPEND srcs wroom/hal_audio.cpp)
|
||||
list(APPEND srcs wroom/hal_audio.c)
|
||||
list(APPEND priv_req board_wroom)
|
||||
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||
list(APPEND srcs s3/hal_audio.c)
|
||||
list(APPEND priv_req board_jc4827w543)
|
||||
else()
|
||||
list(APPEND srcs stub/hal_audio.c)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/i2s_std.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "board.h"
|
||||
|
||||
#define SPK_SAMPLE_RATE 48000
|
||||
#define TONE_HZ 2000
|
||||
#define TONE_PERIOD_SMP (SPK_SAMPLE_RATE / TONE_HZ) /* 24 samples */
|
||||
#define CHUNK_MS 10
|
||||
#define CHUNK_FRAMES (SPK_SAMPLE_RATE / 1000 * CHUNK_MS) /* 480, multiple of period */
|
||||
#define TONE_AMPLITUDE 12000
|
||||
#define DMA_DESC_NUM 4
|
||||
#define DMA_FRAME_NUM 240
|
||||
|
||||
static const char *TAG = "audio";
|
||||
|
||||
enum {
|
||||
AUDIO_SHORT = 1,
|
||||
AUDIO_ALARM,
|
||||
AUDIO_CANCEL,
|
||||
};
|
||||
|
||||
static QueueHandle_t s_q;
|
||||
static TaskHandle_t s_task;
|
||||
static i2s_chan_handle_t s_tx;
|
||||
static bool s_chan_on;
|
||||
static bool s_inited;
|
||||
|
||||
static int16_t s_tone[CHUNK_FRAMES * 2];
|
||||
static int16_t s_silence[CHUNK_FRAMES * 2];
|
||||
|
||||
static void chan_on(void)
|
||||
{
|
||||
if (!s_chan_on && s_tx != NULL) {
|
||||
i2s_channel_enable(s_tx);
|
||||
s_chan_on = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void chan_off(void)
|
||||
{
|
||||
if (s_chan_on && s_tx != NULL) {
|
||||
size_t w = 0;
|
||||
i2s_channel_write(s_tx, s_silence, sizeof(s_silence), &w, 50);
|
||||
i2s_channel_disable(s_tx);
|
||||
s_chan_on = false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool drain_cancel(void)
|
||||
{
|
||||
int msg;
|
||||
while (xQueueReceive(s_q, &msg, 0) == pdTRUE) {
|
||||
if (msg == AUDIO_CANCEL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Write buf for ~ms wall time. DMA pacing makes each 10 ms write block
|
||||
* in this task only; the UI path never touches I2S. */
|
||||
static bool play_buf_ms(const int16_t *buf, size_t len, uint32_t ms)
|
||||
{
|
||||
TickType_t t0 = xTaskGetTickCount();
|
||||
while ((xTaskGetTickCount() - t0) < pdMS_TO_TICKS(ms)) {
|
||||
if (drain_cancel()) {
|
||||
return false;
|
||||
}
|
||||
size_t w = 0;
|
||||
if (i2s_channel_write(s_tx, buf, len, &w, portMAX_DELAY) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void play_short(void)
|
||||
{
|
||||
chan_on();
|
||||
play_buf_ms(s_tone, sizeof(s_tone), 100);
|
||||
chan_off();
|
||||
}
|
||||
|
||||
static void play_alarm(void)
|
||||
{
|
||||
chan_on();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (!play_buf_ms(s_tone, sizeof(s_tone), 500)) {
|
||||
break;
|
||||
}
|
||||
if (!play_buf_ms(s_silence, sizeof(s_silence), 250)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
chan_off();
|
||||
}
|
||||
|
||||
static void audio_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
int msg;
|
||||
for (;;) {
|
||||
if (xQueueReceive(s_q, &msg, portMAX_DELAY) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
if (msg == AUDIO_SHORT) {
|
||||
play_short();
|
||||
} else if (msg == AUDIO_ALARM) {
|
||||
play_alarm();
|
||||
} else if (msg == AUDIO_CANCEL) {
|
||||
chan_off();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void post(int msg)
|
||||
{
|
||||
if (s_q == NULL) {
|
||||
return;
|
||||
}
|
||||
xQueueSend(s_q, &msg, 0);
|
||||
}
|
||||
|
||||
void autofilm_audio_task(void *arg)
|
||||
{
|
||||
audio_task(arg);
|
||||
}
|
||||
|
||||
void hal_audio_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
}
|
||||
gpio_num_t bclk = board_pin_spk_bclk();
|
||||
gpio_num_t lrclk = board_pin_spk_lrclk();
|
||||
gpio_num_t din = board_pin_spk_din();
|
||||
if (bclk == GPIO_NUM_NC || lrclk == GPIO_NUM_NC || din == GPIO_NUM_NC) {
|
||||
ESP_LOGW(TAG, "no speaker pins on this board");
|
||||
s_inited = true;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < CHUNK_FRAMES; i++) {
|
||||
int16_t v = ((i % TONE_PERIOD_SMP) < (TONE_PERIOD_SMP / 2))
|
||||
? TONE_AMPLITUDE : -TONE_AMPLITUDE;
|
||||
s_tone[i * 2] = v;
|
||||
s_tone[i * 2 + 1] = v;
|
||||
}
|
||||
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
|
||||
chan_cfg.dma_desc_num = DMA_DESC_NUM;
|
||||
chan_cfg.dma_frame_num = DMA_FRAME_NUM;
|
||||
if (i2s_new_channel(&chan_cfg, &s_tx, NULL) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_new_channel failed");
|
||||
s_inited = true;
|
||||
return;
|
||||
}
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SPK_SAMPLE_RATE),
|
||||
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
|
||||
I2S_SLOT_MODE_STEREO),
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = bclk,
|
||||
.ws = lrclk,
|
||||
.dout = din,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
if (i2s_channel_init_std_mode(s_tx, &std_cfg) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_channel_init_std_mode failed");
|
||||
i2s_del_channel(s_tx);
|
||||
s_tx = NULL;
|
||||
s_inited = true;
|
||||
return;
|
||||
}
|
||||
s_q = xQueueCreate(8, sizeof(int));
|
||||
if (s_task == NULL) {
|
||||
xTaskCreate(audio_task, "audio", 3072, NULL, 2, &s_task);
|
||||
}
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "init i2s bclk=%d ws=%d dout=%d", bclk, lrclk, din);
|
||||
}
|
||||
|
||||
void hal_audio_beep_short(void)
|
||||
{
|
||||
post(AUDIO_SHORT);
|
||||
}
|
||||
|
||||
void hal_audio_alarm_complete(void)
|
||||
{
|
||||
post(AUDIO_ALARM);
|
||||
}
|
||||
|
||||
void hal_audio_alarm_cancel(void)
|
||||
{
|
||||
post(AUDIO_CANCEL);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "driver/ledc.h"
|
||||
@@ -109,12 +110,12 @@ static void post(int msg)
|
||||
xQueueSend(s_q, &msg, 0);
|
||||
}
|
||||
|
||||
extern "C" void autofilm_audio_task(void *arg)
|
||||
void autofilm_audio_task(void *arg)
|
||||
{
|
||||
audio_task(arg);
|
||||
}
|
||||
|
||||
extern "C" void hal_audio_init(void)
|
||||
void hal_audio_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
@@ -146,17 +147,17 @@ extern "C" void hal_audio_init(void)
|
||||
ESP_LOGI(TAG, "init beep pin=%d LEDC", PIN_BEEP);
|
||||
}
|
||||
|
||||
extern "C" void hal_audio_beep_short(void)
|
||||
void hal_audio_beep_short(void)
|
||||
{
|
||||
post(AUDIO_SHORT);
|
||||
}
|
||||
|
||||
extern "C" void hal_audio_alarm_complete(void)
|
||||
void hal_audio_alarm_complete(void)
|
||||
{
|
||||
post(AUDIO_ALARM);
|
||||
}
|
||||
|
||||
extern "C" void hal_audio_alarm_cancel(void)
|
||||
void hal_audio_alarm_cancel(void)
|
||||
{
|
||||
post(AUDIO_CANCEL);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
set(srcs)
|
||||
set(priv_inc)
|
||||
set(priv_req driver)
|
||||
|
||||
if(IDF_TARGET STREQUAL "esp32")
|
||||
list(APPEND srcs hal_display_lcd2004.c)
|
||||
list(APPEND priv_req board_wroom)
|
||||
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||
list(APPEND srcs hal_display_nv3041a.c)
|
||||
list(APPEND priv_req board_jc4827w543 esp_lcd)
|
||||
else()
|
||||
list(APPEND srcs stub/hal_display.c)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ${priv_inc}
|
||||
REQUIRES driver
|
||||
PRIV_REQUIRES ${priv_req})
|
||||
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
/* Public-domain 5x7 glyphs for ASCII 32..126. */
|
||||
static const uint8_t font5x7[95][7] = {
|
||||
{0,0,0,0,0,0,0},
|
||||
{0x04,0x04,0x04,0x04,0x04,0x00,0x04},
|
||||
{0x0A,0x0A,0x0A,0,0,0,0},
|
||||
{0x0A,0x0A,0x1F,0x0A,0x1F,0x0A,0x0A},
|
||||
{0x04,0x0F,0x14,0x0E,0x05,0x1E,0x04},
|
||||
{0x18,0x19,0x02,0x04,0x08,0x13,0x03},
|
||||
{0x08,0x14,0x14,0x08,0x15,0x12,0x0D},
|
||||
{0x04,0x04,0x08,0,0,0,0},
|
||||
{0x02,0x04,0x08,0x08,0x08,0x04,0x02},
|
||||
{0x08,0x04,0x02,0x02,0x02,0x04,0x08},
|
||||
{0x00,0x04,0x15,0x0E,0x15,0x04,0x00},
|
||||
{0x00,0x04,0x04,0x1F,0x04,0x04,0x00},
|
||||
{0,0,0,0,0,0x04,0x08},
|
||||
{0,0,0,0x1F,0,0,0},
|
||||
{0,0,0,0,0,0,0x04},
|
||||
{0x01,0x02,0x04,0x08,0x10,0,0},
|
||||
{0x0E,0x11,0x13,0x15,0x19,0x11,0x0E},
|
||||
{0x04,0x0C,0x04,0x04,0x04,0x04,0x0E},
|
||||
{0x0E,0x11,0x01,0x02,0x04,0x08,0x1F},
|
||||
{0x1F,0x02,0x04,0x02,0x01,0x11,0x0E},
|
||||
{0x02,0x06,0x0A,0x12,0x1F,0x02,0x02},
|
||||
{0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E},
|
||||
{0x06,0x08,0x10,0x1E,0x11,0x11,0x0E},
|
||||
{0x1F,0x01,0x02,0x04,0x08,0x08,0x08},
|
||||
{0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E},
|
||||
{0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C},
|
||||
{0,0x04,0,0,0x04,0,0},
|
||||
{0,0x04,0,0,0x04,0x04,0x08},
|
||||
{0x02,0x04,0x08,0x10,0x08,0x04,0x02},
|
||||
{0,0,0x1F,0,0x1F,0,0},
|
||||
{0x08,0x04,0x02,0x01,0x02,0x04,0x08},
|
||||
{0x0E,0x11,0x01,0x02,0x04,0x00,0x04},
|
||||
{0x0E,0x11,0x17,0x15,0x17,0x10,0x0E},
|
||||
{0x0E,0x11,0x11,0x1F,0x11,0x11,0x11},
|
||||
{0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E},
|
||||
{0x0E,0x11,0x10,0x10,0x10,0x11,0x0E},
|
||||
{0x1C,0x12,0x11,0x11,0x11,0x12,0x1C},
|
||||
{0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F},
|
||||
{0x1F,0x10,0x10,0x1E,0x10,0x10,0x10},
|
||||
{0x0E,0x11,0x10,0x17,0x11,0x11,0x0F},
|
||||
{0x11,0x11,0x11,0x1F,0x11,0x11,0x11},
|
||||
{0x0E,0x04,0x04,0x04,0x04,0x04,0x0E},
|
||||
{0x07,0x02,0x02,0x02,0x02,0x12,0x0C},
|
||||
{0x11,0x12,0x14,0x18,0x14,0x12,0x11},
|
||||
{0x10,0x10,0x10,0x10,0x10,0x10,0x1F},
|
||||
{0x11,0x1B,0x15,0x15,0x11,0x11,0x11},
|
||||
{0x11,0x19,0x15,0x13,0x11,0x11,0x11},
|
||||
{0x0E,0x11,0x11,0x11,0x11,0x11,0x0E},
|
||||
{0x1E,0x11,0x11,0x1E,0x10,0x10,0x10},
|
||||
{0x0E,0x11,0x11,0x11,0x15,0x12,0x0D},
|
||||
{0x1E,0x11,0x11,0x1E,0x14,0x12,0x11},
|
||||
{0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E},
|
||||
{0x1F,0x04,0x04,0x04,0x04,0x04,0x04},
|
||||
{0x11,0x11,0x11,0x11,0x11,0x11,0x0E},
|
||||
{0x11,0x11,0x11,0x11,0x11,0x0A,0x04},
|
||||
{0x11,0x11,0x11,0x15,0x15,0x1B,0x11},
|
||||
{0x11,0x11,0x0A,0x04,0x0A,0x11,0x11},
|
||||
{0x11,0x11,0x0A,0x04,0x04,0x04,0x04},
|
||||
{0x1F,0x01,0x02,0x04,0x08,0x10,0x1F},
|
||||
{0x0E,0x08,0x08,0x08,0x08,0x08,0x0E},
|
||||
{0x10,0x08,0x04,0x02,0x01,0,0},
|
||||
{0x0E,0x02,0x02,0x02,0x02,0x02,0x0E},
|
||||
{0x04,0x0A,0x11,0,0,0,0},
|
||||
{0,0,0,0,0,0,0x1F},
|
||||
{0x08,0x04,0x02,0,0,0,0},
|
||||
{0,0,0x0E,0x01,0x0F,0x11,0x0F},
|
||||
{0x10,0x10,0x1E,0x11,0x11,0x11,0x1E},
|
||||
{0,0,0x0E,0x10,0x10,0x11,0x0E},
|
||||
{0x01,0x01,0x0F,0x11,0x11,0x11,0x0F},
|
||||
{0,0,0x0E,0x11,0x1F,0x10,0x0E},
|
||||
{0x06,0x08,0x08,0x1E,0x08,0x08,0x08},
|
||||
{0,0,0x0F,0x11,0x11,0x0F,0x01,},
|
||||
{0x10,0x10,0x1E,0x11,0x11,0x11,0x11},
|
||||
{0x04,0,0x0C,0x04,0x04,0x04,0x0E},
|
||||
{0x02,0,0x06,0x02,0x02,0x02,0x12},
|
||||
{0x10,0x10,0x12,0x14,0x18,0x14,0x12},
|
||||
{0x0C,0x04,0x04,0x04,0x04,0x04,0x0E},
|
||||
{0,0,0x1A,0x15,0x15,0x15,0x15},
|
||||
{0,0,0x1E,0x11,0x11,0x11,0x11},
|
||||
{0,0,0x0E,0x11,0x11,0x11,0x0E},
|
||||
{0,0,0x1E,0x11,0x11,0x1E,0x10},
|
||||
{0,0,0x0F,0x11,0x11,0x0F,0x01},
|
||||
{0,0,0x16,0x19,0x10,0x10,0x10},
|
||||
{0,0,0x0F,0x10,0x0E,0x01,0x1E},
|
||||
{0x08,0x08,0x1E,0x08,0x08,0x08,0x06},
|
||||
{0,0,0x11,0x11,0x11,0x11,0x0F},
|
||||
{0,0,0x11,0x11,0x11,0x0A,0x04},
|
||||
{0,0,0x11,0x15,0x15,0x15,0x0A},
|
||||
{0,0,0x11,0x0A,0x04,0x0A,0x11},
|
||||
{0,0,0x11,0x11,0x0F,0x01,0x0E},
|
||||
{0,0,0x1F,0x02,0x04,0x08,0x1F},
|
||||
{0x02,0x04,0x04,0x08,0x04,0x04,0x02},
|
||||
{0x04,0x04,0x04,0x04,0x04,0x04,0x04},
|
||||
{0x08,0x04,0x04,0x02,0x04,0x04,0x08},
|
||||
{0x00,0x00,0x08,0x15,0x02,0x00,0x00},
|
||||
};
|
||||
@@ -0,0 +1,190 @@
|
||||
#include "hal_display.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "driver/i2c_master.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define LCD_ADDR 0x27
|
||||
#define LCD_COLS 20
|
||||
#define LCD_ROWS 4
|
||||
#define PIN_SDA 21
|
||||
#define PIN_SCL 22
|
||||
|
||||
#define LCD_RS 0x01
|
||||
#define LCD_EN 0x04
|
||||
#define LCD_BL 0x08
|
||||
|
||||
static const char *TAG = "display";
|
||||
|
||||
static i2c_master_bus_handle_t s_bus;
|
||||
static i2c_master_dev_handle_t s_dev;
|
||||
static bool s_inited;
|
||||
|
||||
static const uint8_t s_thermo[8] = {
|
||||
0b00100,
|
||||
0b01100,
|
||||
0b00100,
|
||||
0b01100,
|
||||
0b00100,
|
||||
0b01110,
|
||||
0b01110,
|
||||
0b01110,
|
||||
};
|
||||
|
||||
static const uint8_t s_row_off[4] = {0x00, 0x40, 0x14, 0x54};
|
||||
|
||||
static void i2c_write(uint8_t b)
|
||||
{
|
||||
if (s_dev == NULL) {
|
||||
return;
|
||||
}
|
||||
i2c_master_transmit(s_dev, &b, 1, 50);
|
||||
}
|
||||
|
||||
static void pulse(uint8_t data)
|
||||
{
|
||||
i2c_write((uint8_t)(data | LCD_EN | LCD_BL));
|
||||
esp_rom_delay_us(1);
|
||||
i2c_write((uint8_t)((data & (uint8_t)~LCD_EN) | LCD_BL));
|
||||
esp_rom_delay_us(50);
|
||||
}
|
||||
|
||||
static void write4(uint8_t nibble, uint8_t rs)
|
||||
{
|
||||
uint8_t data = (uint8_t)((nibble & 0xF0) | rs | LCD_BL);
|
||||
i2c_write(data);
|
||||
pulse(data);
|
||||
}
|
||||
|
||||
static void send(uint8_t value, uint8_t rs)
|
||||
{
|
||||
write4(value, rs);
|
||||
write4((uint8_t)(value << 4), rs);
|
||||
}
|
||||
|
||||
static void cmd(uint8_t c)
|
||||
{
|
||||
send(c, 0);
|
||||
if (c == 0x01 || c == 0x02) {
|
||||
vTaskDelay(pdMS_TO_TICKS(2));
|
||||
}
|
||||
}
|
||||
|
||||
static void data_byte(uint8_t d)
|
||||
{
|
||||
send(d, LCD_RS);
|
||||
}
|
||||
|
||||
static void set_ddram(uint8_t addr)
|
||||
{
|
||||
cmd((uint8_t)(0x80 | addr));
|
||||
}
|
||||
|
||||
void hal_display_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
}
|
||||
i2c_master_bus_config_t bus_cfg = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.sda_io_num = PIN_SDA,
|
||||
.scl_io_num = PIN_SCL,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.flags = {
|
||||
.enable_internal_pullup = true,
|
||||
},
|
||||
};
|
||||
if (i2c_new_master_bus(&bus_cfg, &s_bus) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2c bus failed");
|
||||
return;
|
||||
}
|
||||
i2c_device_config_t dev_cfg = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = LCD_ADDR,
|
||||
.scl_speed_hz = 100000,
|
||||
};
|
||||
if (i2c_master_bus_add_device(s_bus, &dev_cfg, &s_dev) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2c device failed");
|
||||
return;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
write4(0x30, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(5));
|
||||
write4(0x30, 0);
|
||||
esp_rom_delay_us(150);
|
||||
write4(0x30, 0);
|
||||
write4(0x20, 0);
|
||||
cmd(0x28); /* 4-bit, 2 line */
|
||||
cmd(0x08);
|
||||
cmd(0x01);
|
||||
cmd(0x06);
|
||||
cmd(0x0C);
|
||||
|
||||
cmd(0x40); /* CGRAM 0 */
|
||||
for (int i = 0; i < 8; i++) {
|
||||
data_byte(s_thermo[i]);
|
||||
}
|
||||
cmd(0x01);
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "init LCD 20x4 addr=0x27 sda=21 scl=22 IDF I2C");
|
||||
}
|
||||
|
||||
void hal_display_clear(void)
|
||||
{
|
||||
if (!s_inited) {
|
||||
return;
|
||||
}
|
||||
cmd(0x01);
|
||||
}
|
||||
|
||||
void hal_display_text(int col, int row, const char *s)
|
||||
{
|
||||
if (!s_inited || s == NULL) {
|
||||
return;
|
||||
}
|
||||
if (row < 0 || row >= LCD_ROWS) {
|
||||
return;
|
||||
}
|
||||
if (col < 0) {
|
||||
col = 0;
|
||||
}
|
||||
if (col >= LCD_COLS) {
|
||||
return;
|
||||
}
|
||||
set_ddram((uint8_t)(s_row_off[row] + col));
|
||||
int room = LCD_COLS - col;
|
||||
int n = 0;
|
||||
while (s[n] != '\0' && n < room) {
|
||||
data_byte((uint8_t)s[n]);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
void hal_display_glyph_thermo(int col, int row)
|
||||
{
|
||||
if (!s_inited) {
|
||||
return;
|
||||
}
|
||||
if (row < 0 || row >= LCD_ROWS || col < 0 || col >= LCD_COLS) {
|
||||
return;
|
||||
}
|
||||
set_ddram((uint8_t)(s_row_off[row] + col));
|
||||
data_byte(0);
|
||||
}
|
||||
|
||||
void hal_display_flush(void)
|
||||
{
|
||||
}
|
||||
|
||||
void hal_display_softkey(int slot, int slots, const char *label)
|
||||
{
|
||||
(void)slot;
|
||||
(void)slots;
|
||||
(void)label;
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
#include "hal_display.h"
|
||||
#include "font5x7.h"
|
||||
#include "board.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_lcd_panel_commands.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define LCD_HOST SPI2_HOST
|
||||
#define LCD_H HAL_DISPLAY_PX_H
|
||||
#define LCD_W HAL_DISPLAY_PX_W
|
||||
|
||||
#define COL_BG 0x18E3
|
||||
#define COL_FG 0xFFFF
|
||||
#define COL_ACC 0xF800
|
||||
#define COL_BTN 0x39E7
|
||||
#define COL_BTNT 0xFFFF
|
||||
|
||||
static const char *TAG = "disp-s3";
|
||||
|
||||
static esp_lcd_panel_io_handle_t s_io;
|
||||
static uint16_t *s_fb;
|
||||
static bool s_inited;
|
||||
static bool s_dirty;
|
||||
|
||||
static uint16_t swap565(uint16_t c)
|
||||
{
|
||||
return (uint16_t)((c >> 8) | (c << 8));
|
||||
}
|
||||
|
||||
static void fb_fill(uint16_t color)
|
||||
{
|
||||
if (s_fb == NULL) {
|
||||
return;
|
||||
}
|
||||
uint16_t c = swap565(color);
|
||||
for (int i = 0; i < LCD_W * LCD_H; i++) {
|
||||
s_fb[i] = c;
|
||||
}
|
||||
s_dirty = true;
|
||||
}
|
||||
|
||||
static void fb_pixel(int x, int y, uint16_t color)
|
||||
{
|
||||
if (s_fb == NULL || x < 0 || y < 0 || x >= LCD_W || y >= LCD_H) {
|
||||
return;
|
||||
}
|
||||
s_fb[y * LCD_W + x] = swap565(color);
|
||||
}
|
||||
|
||||
static void fb_fill_rect(int x, int y, int w, int h, uint16_t color)
|
||||
{
|
||||
if (s_fb == NULL) {
|
||||
return;
|
||||
}
|
||||
if (x < 0) {
|
||||
w += x;
|
||||
x = 0;
|
||||
}
|
||||
if (y < 0) {
|
||||
h += y;
|
||||
y = 0;
|
||||
}
|
||||
if (x + w > LCD_W) {
|
||||
w = LCD_W - x;
|
||||
}
|
||||
if (y + h > LCD_H) {
|
||||
h = LCD_H - y;
|
||||
}
|
||||
uint16_t c = swap565(color);
|
||||
for (int yy = 0; yy < h; yy++) {
|
||||
uint16_t *row = &s_fb[(y + yy) * LCD_W + x];
|
||||
for (int xx = 0; xx < w; xx++) {
|
||||
row[xx] = c;
|
||||
}
|
||||
}
|
||||
s_dirty = true;
|
||||
}
|
||||
|
||||
static void fb_char(int x, int y, char ch, uint16_t fg, int scale)
|
||||
{
|
||||
if (ch < 32 || ch > 126) {
|
||||
ch = '?';
|
||||
}
|
||||
const uint8_t *g = font5x7[ch - 32];
|
||||
for (int row = 0; row < 7; row++) {
|
||||
uint8_t bits = g[row];
|
||||
for (int col = 0; col < 5; col++) {
|
||||
if (bits & (0x10 >> col)) {
|
||||
fb_fill_rect(x + col * scale, y + row * scale, scale, scale, fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fb_str(int x, int y, const char *s, uint16_t fg, int scale)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
int cx = x;
|
||||
while (*s) {
|
||||
fb_char(cx, y, *s, fg, scale);
|
||||
cx += 6 * scale;
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
static void lcd_cmd(uint8_t cmd, const uint8_t *data, size_t len)
|
||||
{
|
||||
if (s_io == NULL) {
|
||||
return;
|
||||
}
|
||||
uint32_t packed = ((uint32_t)0x02 << 24) | ((uint32_t)cmd << 8);
|
||||
esp_lcd_panel_io_tx_param(s_io, packed, data, len);
|
||||
}
|
||||
|
||||
static void lcd_window(int x0, int y0, int x1, int y1)
|
||||
{
|
||||
uint8_t cas[4] = { (uint8_t)(x0 >> 8), (uint8_t)x0, (uint8_t)(x1 >> 8), (uint8_t)x1 };
|
||||
uint8_t ras[4] = { (uint8_t)(y0 >> 8), (uint8_t)y0, (uint8_t)(y1 >> 8), (uint8_t)y1 };
|
||||
lcd_cmd(LCD_CMD_CASET, cas, 4);
|
||||
lcd_cmd(LCD_CMD_RASET, ras, 4);
|
||||
}
|
||||
|
||||
static void lcd_init_panel(void)
|
||||
{
|
||||
/* Vendor bring-up for NV3041A-01 480x272. Commands are public datasheet/community. */
|
||||
static const uint8_t seq[][3] = {
|
||||
{0xFF, 0xA5, 1},
|
||||
{0x36, 0x00, 1},
|
||||
{0x3A, 0x01, 1},
|
||||
{0x41, 0x03, 1},
|
||||
{0x44, 0x15, 1},
|
||||
{0x45, 0x15, 1},
|
||||
{0x7D, 0x03, 1},
|
||||
{0xC1, 0xBB, 1},
|
||||
{0xC2, 0x05, 1},
|
||||
{0xC3, 0x10, 1},
|
||||
{0xC6, 0x3E, 1},
|
||||
{0xC7, 0x25, 1},
|
||||
{0xC8, 0x11, 1},
|
||||
{0xC9, 0x20, 1},
|
||||
{0x7A, 0x51, 1},
|
||||
{0x6F, 0x2D, 1},
|
||||
{0x78, 0x4B, 1},
|
||||
{0x70, 0x07, 1},
|
||||
{0x71, 0x05, 1},
|
||||
};
|
||||
lcd_cmd(LCD_CMD_SWRESET, NULL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(120));
|
||||
lcd_cmd(LCD_CMD_SLPOUT, NULL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(120));
|
||||
for (size_t i = 0; i < sizeof(seq) / sizeof(seq[0]); i++) {
|
||||
uint8_t d = seq[i][1];
|
||||
lcd_cmd(seq[i][0], &d, seq[i][2]);
|
||||
}
|
||||
lcd_cmd(LCD_CMD_DISPON, NULL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
}
|
||||
|
||||
void hal_display_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
}
|
||||
|
||||
gpio_config_t bl = {
|
||||
.pin_bit_mask = 1ULL << board_pin_lcd_bl(),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&bl);
|
||||
gpio_set_level(board_pin_lcd_bl(), 1);
|
||||
|
||||
spi_bus_config_t buscfg = {
|
||||
.sclk_io_num = board_pin_lcd_sck(),
|
||||
.data0_io_num = board_pin_lcd_d0(),
|
||||
.data1_io_num = board_pin_lcd_d1(),
|
||||
.data2_io_num = board_pin_lcd_d2(),
|
||||
.data3_io_num = board_pin_lcd_d3(),
|
||||
.max_transfer_sz = LCD_W * 40 * sizeof(uint16_t),
|
||||
};
|
||||
esp_err_t err = spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "spi_bus_initialize %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
esp_lcd_panel_io_spi_config_t io_config = {
|
||||
.cs_gpio_num = board_pin_lcd_cs(),
|
||||
.dc_gpio_num = -1,
|
||||
.spi_mode = 0,
|
||||
.pclk_hz = 40 * 1000 * 1000,
|
||||
.trans_queue_depth = 4,
|
||||
.lcd_cmd_bits = 32,
|
||||
.lcd_param_bits = 8,
|
||||
.flags = {
|
||||
.quad_mode = 1,
|
||||
},
|
||||
};
|
||||
err = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &s_io);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "panel_io %s", esp_err_to_name(err));
|
||||
s_io = NULL;
|
||||
} else {
|
||||
lcd_init_panel();
|
||||
}
|
||||
|
||||
s_fb = heap_caps_malloc((size_t)LCD_W * LCD_H * sizeof(uint16_t),
|
||||
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if (s_fb == NULL) {
|
||||
s_fb = heap_caps_malloc((size_t)LCD_W * LCD_H * sizeof(uint16_t), MALLOC_CAP_8BIT);
|
||||
}
|
||||
if (s_fb == NULL) {
|
||||
ESP_LOGE(TAG, "no framebuffer");
|
||||
}
|
||||
fb_fill(COL_BG);
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "NV3041A QSPI %dx%d CS=%d SCK=%d D0=%d",
|
||||
LCD_W, LCD_H, (int)board_pin_lcd_cs(), (int)board_pin_lcd_sck(),
|
||||
(int)board_pin_lcd_d0());
|
||||
}
|
||||
|
||||
void hal_display_clear(void)
|
||||
{
|
||||
fb_fill(COL_BG);
|
||||
}
|
||||
|
||||
void hal_display_text(int col, int row, const char *s)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
if (col < 0) {
|
||||
col = 0;
|
||||
}
|
||||
if (row < 0 || row >= HAL_DISPLAY_ROWS) {
|
||||
return;
|
||||
}
|
||||
int x = col * HAL_DISPLAY_CELL_W + 4;
|
||||
int y = 8 + row * HAL_DISPLAY_CELL_H;
|
||||
/* clip 20 columns */
|
||||
char buf[HAL_DISPLAY_COLS + 1];
|
||||
int n = 0;
|
||||
while (s[n] && n < HAL_DISPLAY_COLS - col && n < HAL_DISPLAY_COLS) {
|
||||
buf[n] = s[n];
|
||||
n++;
|
||||
}
|
||||
buf[n] = 0;
|
||||
fb_fill_rect(x, y, n * HAL_DISPLAY_CELL_W, HAL_DISPLAY_CELL_H - 4, COL_BG);
|
||||
fb_str(x, y + 6, buf, COL_FG, 3);
|
||||
}
|
||||
|
||||
void hal_display_glyph_thermo(int col, int row)
|
||||
{
|
||||
int x = col * HAL_DISPLAY_CELL_W + 8;
|
||||
int y = 8 + row * HAL_DISPLAY_CELL_H + 4;
|
||||
fb_fill_rect(x + 6, y, 6, 14, COL_ACC);
|
||||
fb_fill_rect(x + 3, y + 14, 12, 10, COL_ACC);
|
||||
}
|
||||
|
||||
void hal_display_softkey(int slot, int slots, const char *label)
|
||||
{
|
||||
if (slots < 1) {
|
||||
slots = 1;
|
||||
}
|
||||
if (slot < 0 || slot >= slots) {
|
||||
return;
|
||||
}
|
||||
int w = LCD_W / slots;
|
||||
int x = slot * w + 4;
|
||||
int y = LCD_H - HAL_DISPLAY_SOFTKEY_H + 4;
|
||||
int bw = w - 8;
|
||||
int bh = HAL_DISPLAY_SOFTKEY_H - 8;
|
||||
fb_fill_rect(x, y, bw, bh, COL_BTN);
|
||||
if (label != NULL) {
|
||||
int scale = 3;
|
||||
int tw = (int)strlen(label) * 6 * scale;
|
||||
int tx = x + (bw - tw) / 2;
|
||||
int ty = y + (bh - 7 * scale) / 2;
|
||||
if (tx < x) {
|
||||
tx = x + 4;
|
||||
}
|
||||
fb_str(tx, ty, label, COL_BTNT, scale);
|
||||
}
|
||||
}
|
||||
|
||||
void hal_display_flush(void)
|
||||
{
|
||||
if (!s_dirty || s_fb == NULL || s_io == NULL) {
|
||||
s_dirty = false;
|
||||
return;
|
||||
}
|
||||
lcd_window(0, 0, LCD_W - 1, LCD_H - 1);
|
||||
const int chunk_rows = 16;
|
||||
for (int y = 0; y < LCD_H; y += chunk_rows) {
|
||||
int rows = chunk_rows;
|
||||
if (y + rows > LCD_H) {
|
||||
rows = LCD_H - y;
|
||||
}
|
||||
uint32_t packed = ((uint32_t)0x32 << 24) | ((uint32_t)LCD_CMD_RAMWR << 8);
|
||||
esp_lcd_panel_io_tx_color(s_io, packed, &s_fb[y * LCD_W],
|
||||
(size_t)rows * LCD_W * sizeof(uint16_t));
|
||||
}
|
||||
s_dirty = false;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define HAL_DISPLAY_COLS 20
|
||||
#define HAL_DISPLAY_ROWS 4
|
||||
|
||||
/* Colour panel geometry (S3). 2004 ignores pixel APIs. */
|
||||
#define HAL_DISPLAY_PX_W 480
|
||||
#define HAL_DISPLAY_PX_H 272
|
||||
#define HAL_DISPLAY_CELL_W 24
|
||||
#define HAL_DISPLAY_CELL_H 32
|
||||
#define HAL_DISPLAY_SOFTKEY_H 48
|
||||
|
||||
void hal_display_init(void);
|
||||
void hal_display_clear(void);
|
||||
void hal_display_text(int col, int row, const char *s); /* clip to 20 cols */
|
||||
void hal_display_glyph_thermo(int col, int row);
|
||||
void hal_display_flush(void);
|
||||
|
||||
/* Softkey chrome; no-op on 2004. slot in [0, slots). */
|
||||
void hal_display_softkey(int slot, int slots, const char *label);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "hal_display.h"
|
||||
|
||||
void hal_display_init(void) {}
|
||||
void hal_display_clear(void) {}
|
||||
void hal_display_text(int col, int row, const char *s)
|
||||
{
|
||||
(void)col;
|
||||
(void)row;
|
||||
(void)s;
|
||||
}
|
||||
void hal_display_glyph_thermo(int col, int row)
|
||||
{
|
||||
(void)col;
|
||||
(void)row;
|
||||
}
|
||||
void hal_display_flush(void) {}
|
||||
|
||||
void hal_display_softkey(int slot, int slots, const char *label)
|
||||
{
|
||||
(void)slot;
|
||||
(void)slots;
|
||||
(void)label;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
set(srcs)
|
||||
set(priv_inc)
|
||||
set(priv_req driver)
|
||||
|
||||
if(IDF_TARGET STREQUAL "esp32")
|
||||
list(APPEND srcs hal_input_keypad.c)
|
||||
list(APPEND priv_req board_wroom)
|
||||
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||
list(APPEND srcs hal_input_gt911.c)
|
||||
list(APPEND priv_req board_jc4827w543)
|
||||
else()
|
||||
list(APPEND srcs stub/hal_input.c)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ${priv_inc}
|
||||
REQUIRES driver
|
||||
PRIV_REQUIRES ${priv_req})
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "hal_input.h"
|
||||
#include <string.h>
|
||||
#include "board.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define TP_I2C I2C_NUM_0
|
||||
#define TP_ADDR 0x5D
|
||||
#define GT_REG_STAT 0x814E
|
||||
#define GT_REG_PT0 0x8150
|
||||
|
||||
static const char *TAG = "gt911";
|
||||
|
||||
static bool s_have;
|
||||
static ui_raw_touch_t s_last;
|
||||
static uint8_t s_prev_stat;
|
||||
|
||||
static esp_err_t tp_write(uint16_t reg, const uint8_t *data, size_t n)
|
||||
{
|
||||
uint8_t buf[8];
|
||||
if (n + 2 > sizeof(buf)) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
buf[0] = (uint8_t)(reg >> 8);
|
||||
buf[1] = (uint8_t)reg;
|
||||
if (n && data) {
|
||||
memcpy(buf + 2, data, n);
|
||||
}
|
||||
return i2c_master_write_to_device(TP_I2C, TP_ADDR, buf, n + 2, pdMS_TO_TICKS(50));
|
||||
}
|
||||
|
||||
static esp_err_t tp_read(uint16_t reg, uint8_t *data, size_t n)
|
||||
{
|
||||
uint8_t addr[2] = { (uint8_t)(reg >> 8), (uint8_t)reg };
|
||||
return i2c_master_write_read_device(TP_I2C, TP_ADDR, addr, 2, data, n, pdMS_TO_TICKS(50));
|
||||
}
|
||||
|
||||
void hal_input_init(void)
|
||||
{
|
||||
gpio_config_t io = {
|
||||
.pin_bit_mask = (1ULL << board_pin_tp_rst()) | (1ULL << board_pin_tp_int()),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io);
|
||||
gpio_set_level(board_pin_tp_int(), 0);
|
||||
gpio_set_level(board_pin_tp_rst(), 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
gpio_set_level(board_pin_tp_rst(), 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
io.mode = GPIO_MODE_INPUT;
|
||||
io.pin_bit_mask = 1ULL << board_pin_tp_int();
|
||||
gpio_config(&io);
|
||||
|
||||
i2c_config_t conf = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = board_pin_tp_sda(),
|
||||
.scl_io_num = board_pin_tp_scl(),
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = 400000,
|
||||
};
|
||||
i2c_param_config(TP_I2C, &conf);
|
||||
i2c_driver_install(TP_I2C, conf.mode, 0, 0, 0);
|
||||
ESP_LOGI(TAG, "GT911 addr=0x5D SDA=%d SCL=%d RST=%d INT=%d",
|
||||
(int)board_pin_tp_sda(), (int)board_pin_tp_scl(),
|
||||
(int)board_pin_tp_rst(), (int)board_pin_tp_int());
|
||||
}
|
||||
|
||||
bool hal_input_pop_key(ui_raw_key_t *out)
|
||||
{
|
||||
(void)out;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hal_input_pop_touch(ui_raw_touch_t *out)
|
||||
{
|
||||
uint8_t stat = 0;
|
||||
if (tp_read(GT_REG_STAT, &stat, 1) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
if ((stat & 0x80) == 0) {
|
||||
return false;
|
||||
}
|
||||
uint8_t n = stat & 0x0F;
|
||||
uint8_t raw[8];
|
||||
if (n > 0 && tp_read(GT_REG_PT0, raw, 8) == ESP_OK) {
|
||||
int16_t x = (int16_t)(raw[0] | ((uint16_t)raw[1] << 8));
|
||||
int16_t y = (int16_t)(raw[2] | ((uint16_t)raw[3] << 8));
|
||||
uint8_t zero = 0;
|
||||
tp_write(GT_REG_STAT, &zero, 1);
|
||||
/* Edge: report once per contact start. */
|
||||
if (s_prev_stat == 0) {
|
||||
s_last.x = x;
|
||||
s_last.y = y;
|
||||
s_prev_stat = n;
|
||||
if (out) {
|
||||
*out = s_last;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
s_prev_stat = n;
|
||||
return false;
|
||||
}
|
||||
uint8_t zero = 0;
|
||||
tp_write(GT_REG_STAT, &zero, 1);
|
||||
s_prev_stat = 0;
|
||||
(void)s_have;
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#include "hal_input.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "input";
|
||||
|
||||
#define ROWS 5
|
||||
#define COLS 4
|
||||
|
||||
static const gpio_num_t s_row_pins[ROWS] = {19, 18, 5, 17, 16};
|
||||
static const gpio_num_t s_col_pins[COLS] = {15, 2, 0, 4};
|
||||
|
||||
static const char s_keys[ROWS][COLS] = {
|
||||
{'F', 'E', '#', '*'},
|
||||
{'1', '2', '3', 'U'},
|
||||
{'4', '5', '6', 'D'},
|
||||
{'7', '8', '9', 'X'},
|
||||
{'L', '0', 'R', 'E'}};
|
||||
|
||||
static bool s_inited;
|
||||
static char s_held;
|
||||
|
||||
static void cols_idle_high(void)
|
||||
{
|
||||
for (int c = 0; c < COLS; c++) {
|
||||
gpio_set_level(s_col_pins[c], 1);
|
||||
}
|
||||
}
|
||||
|
||||
static char scan_once(void)
|
||||
{
|
||||
for (int c = 0; c < COLS; c++) {
|
||||
cols_idle_high();
|
||||
gpio_set_level(s_col_pins[c], 0);
|
||||
for (int r = 0; r < ROWS; r++) {
|
||||
if (gpio_get_level(s_row_pins[r]) == 0) {
|
||||
cols_idle_high();
|
||||
return s_keys[r][c];
|
||||
}
|
||||
}
|
||||
}
|
||||
cols_idle_high();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hal_input_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
}
|
||||
for (int r = 0; r < ROWS; r++) {
|
||||
gpio_config_t io = {
|
||||
.pin_bit_mask = 1ULL << s_row_pins[r],
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io);
|
||||
}
|
||||
for (int c = 0; c < COLS; c++) {
|
||||
gpio_config_t io = {
|
||||
.pin_bit_mask = 1ULL << s_col_pins[c],
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io);
|
||||
gpio_set_level(s_col_pins[c], 1);
|
||||
}
|
||||
s_held = 0;
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "init 5x4 GPIO keypad map byte-identical to src/config.cpp");
|
||||
}
|
||||
|
||||
bool hal_input_pop_key(ui_raw_key_t *out)
|
||||
{
|
||||
char k = scan_once();
|
||||
if (k == 0) {
|
||||
s_held = 0;
|
||||
return false;
|
||||
}
|
||||
if (k == s_held) {
|
||||
return false;
|
||||
}
|
||||
s_held = k;
|
||||
if (out != NULL) {
|
||||
out->key = k;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hal_input_pop_touch(ui_raw_touch_t *out)
|
||||
{
|
||||
(void)out;
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char key;
|
||||
} ui_raw_key_t;
|
||||
|
||||
typedef struct {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
} ui_raw_touch_t;
|
||||
|
||||
void hal_input_init(void);
|
||||
bool hal_input_pop_key(ui_raw_key_t *out); /* nonblocking */
|
||||
bool hal_input_pop_touch(ui_raw_touch_t *out); /* nonblocking; S3 only */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "hal_input.h"
|
||||
|
||||
void hal_input_init(void) {}
|
||||
|
||||
bool hal_input_pop_key(ui_raw_key_t *out)
|
||||
{
|
||||
(void)out;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hal_input_pop_touch(ui_raw_touch_t *out)
|
||||
{
|
||||
(void)out;
|
||||
return false;
|
||||
}
|
||||
@@ -3,14 +3,11 @@ set(priv_inc)
|
||||
set(priv_req driver)
|
||||
|
||||
if(IDF_TARGET STREQUAL "esp32")
|
||||
list(APPEND srcs
|
||||
wroom/hal_motor.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/../third_party/AccelStepper/AccelStepper.cpp
|
||||
)
|
||||
list(APPEND priv_inc
|
||||
${CMAKE_CURRENT_LIST_DIR}/../third_party/AccelStepper/include
|
||||
)
|
||||
list(APPEND priv_req board_wroom espressif__arduino-esp32)
|
||||
list(APPEND srcs hal_motor.c)
|
||||
list(APPEND priv_req board_wroom)
|
||||
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||
list(APPEND srcs hal_motor.c)
|
||||
list(APPEND priv_req board_jc4827w543)
|
||||
else()
|
||||
list(APPEND srcs stub/hal_motor.c)
|
||||
endif()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <AccelStepper.h>
|
||||
|
||||
#define PIN_STEP 12
|
||||
#define PIN_DIR 14
|
||||
#define PIN_EN 27
|
||||
#define STEPS_PER_REV 4800
|
||||
#define DEFAULT_RPM 60
|
||||
#define ACCEL 9600
|
||||
|
||||
static const char *TAG = "motor";
|
||||
|
||||
static AccelStepper s_stepper(AccelStepper::DRIVER, PIN_STEP, PIN_DIR);
|
||||
static TaskHandle_t s_task;
|
||||
static atomic_uint s_stop_req;
|
||||
static float s_cw;
|
||||
static float s_ccw;
|
||||
static uint32_t s_rpm = DEFAULT_RPM;
|
||||
static bool s_enabled;
|
||||
static bool s_inited;
|
||||
|
||||
static void en_disable(void)
|
||||
{
|
||||
gpio_set_level((gpio_num_t)PIN_EN, 1);
|
||||
s_enabled = false;
|
||||
}
|
||||
|
||||
static void en_enable(void)
|
||||
{
|
||||
gpio_set_level((gpio_num_t)PIN_EN, 0);
|
||||
s_enabled = true;
|
||||
}
|
||||
|
||||
static void run_move(long steps)
|
||||
{
|
||||
s_stepper.setCurrentPosition(0);
|
||||
s_stepper.moveTo(steps);
|
||||
while (s_stepper.distanceToGo() != 0) {
|
||||
if (atomic_load(&s_stop_req) != 0) {
|
||||
break;
|
||||
}
|
||||
s_stepper.run();
|
||||
}
|
||||
}
|
||||
|
||||
static void motor_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
for (;;) {
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
if (atomic_load(&s_stop_req) != 0) {
|
||||
en_disable();
|
||||
continue;
|
||||
}
|
||||
float speed = ((float)s_rpm * (float)STEPS_PER_REV) / 60.0f;
|
||||
s_stepper.setMaxSpeed(speed);
|
||||
s_stepper.setAcceleration((float)ACCEL);
|
||||
en_enable();
|
||||
while (atomic_load(&s_stop_req) == 0) {
|
||||
run_move((long)((float)STEPS_PER_REV * s_cw));
|
||||
if (atomic_load(&s_stop_req) != 0) {
|
||||
break;
|
||||
}
|
||||
run_move((long)(-((float)STEPS_PER_REV * s_ccw)));
|
||||
}
|
||||
en_disable();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void autofilm_motor_task(void *arg)
|
||||
{
|
||||
motor_task(arg);
|
||||
}
|
||||
|
||||
extern "C" void hal_motor_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
}
|
||||
gpio_config_t io = {
|
||||
.pin_bit_mask = (1ULL << PIN_EN) | (1ULL << PIN_STEP) | (1ULL << PIN_DIR),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io);
|
||||
gpio_set_level((gpio_num_t)PIN_EN, 1);
|
||||
gpio_set_level((gpio_num_t)PIN_STEP, 0);
|
||||
gpio_set_level((gpio_num_t)PIN_DIR, 0);
|
||||
s_enabled = false;
|
||||
atomic_store(&s_stop_req, 0);
|
||||
s_stepper.setMaxSpeed(((float)DEFAULT_RPM * (float)STEPS_PER_REV) / 60.0f);
|
||||
s_stepper.setAcceleration((float)ACCEL);
|
||||
if (s_task == NULL) {
|
||||
xTaskCreatePinnedToCore(motor_task, "motor", 4096, NULL,
|
||||
configMAX_PRIORITIES - 2, &s_task, 0);
|
||||
}
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "init EN=HIGH step=%d dir=%d", PIN_STEP, PIN_DIR);
|
||||
}
|
||||
|
||||
extern "C" void hal_motor_enable(bool on)
|
||||
{
|
||||
if (on) {
|
||||
en_enable();
|
||||
} else {
|
||||
en_disable();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void hal_motor_request_stop(void)
|
||||
{
|
||||
atomic_store(&s_stop_req, 1);
|
||||
gpio_set_level((gpio_num_t)PIN_EN, 1);
|
||||
s_enabled = false;
|
||||
if (s_task != NULL) {
|
||||
xTaskNotifyGive(s_task);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" esp_err_t hal_motor_agitate_start(float cw_revs, float ccw_revs, uint32_t rpm)
|
||||
{
|
||||
s_cw = cw_revs;
|
||||
s_ccw = ccw_revs;
|
||||
s_rpm = (rpm == 0) ? DEFAULT_RPM : rpm;
|
||||
atomic_store(&s_stop_req, 0);
|
||||
if (s_task != NULL) {
|
||||
xTaskNotifyGive(s_task);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
extern "C" void hal_motor_agitate_stop(void)
|
||||
{
|
||||
atomic_store(&s_stop_req, 1);
|
||||
en_disable();
|
||||
}
|
||||
|
||||
extern "C" bool hal_motor_is_enabled(void)
|
||||
{
|
||||
return s_enabled;
|
||||
}
|
||||
@@ -3,16 +3,11 @@ set(priv_inc)
|
||||
set(priv_req driver)
|
||||
|
||||
if(IDF_TARGET STREQUAL "esp32")
|
||||
list(APPEND srcs
|
||||
wroom/hal_temp.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/../third_party/OneWire/OneWire.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/../third_party/DallasTemperature/DallasTemperature.cpp
|
||||
)
|
||||
list(APPEND priv_inc
|
||||
${CMAKE_CURRENT_LIST_DIR}/../third_party/OneWire/include
|
||||
${CMAKE_CURRENT_LIST_DIR}/../third_party/DallasTemperature/include
|
||||
)
|
||||
list(APPEND priv_req board_wroom espressif__arduino-esp32)
|
||||
list(APPEND srcs hal_temp.c)
|
||||
list(APPEND priv_req board_wroom onewire_bus)
|
||||
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||
list(APPEND srcs hal_temp.c)
|
||||
list(APPEND priv_req board_jc4827w543 onewire_bus)
|
||||
else()
|
||||
list(APPEND srcs stub/hal_temp.c)
|
||||
endif()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
espressif/onewire_bus: "^1.1.0"
|
||||
@@ -7,4 +7,5 @@
|
||||
#endif
|
||||
|
||||
void hal_temp_init(void);
|
||||
void hal_temp_tick(void); /* blocking conversion poll; blocks >=750 ms */
|
||||
esp_err_t hal_temp_read_c(float *out); /* ESP_FAIL → disconnected */
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
#include "hal_temp.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
void hal_temp_init(void)
|
||||
{
|
||||
}
|
||||
|
||||
void hal_temp_tick(void)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(750));
|
||||
}
|
||||
|
||||
esp_err_t hal_temp_read_c(float *out)
|
||||
{
|
||||
(void)out;
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <OneWire.h>
|
||||
#include <DallasTemperature.h>
|
||||
|
||||
#define PIN_DS 13
|
||||
#define TEMP_OFFSET 0.4f
|
||||
|
||||
static const char *TAG = "temp";
|
||||
|
||||
static OneWire s_wire(PIN_DS);
|
||||
static DallasTemperature s_sensors(&s_wire);
|
||||
static bool s_inited;
|
||||
static float s_last_c;
|
||||
static bool s_last_ok;
|
||||
|
||||
extern "C" void autofilm_temp_tick(void)
|
||||
{
|
||||
s_sensors.requestTemperatures();
|
||||
vTaskDelay(pdMS_TO_TICKS(750));
|
||||
float t = s_sensors.getTempCByIndex(0);
|
||||
bool ok = (t != DEVICE_DISCONNECTED_C);
|
||||
if (ok) {
|
||||
t += TEMP_OFFSET;
|
||||
}
|
||||
s_last_c = ok ? t : 0.0f;
|
||||
s_last_ok = ok;
|
||||
}
|
||||
|
||||
extern "C" void autofilm_temp_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
for (;;) {
|
||||
autofilm_temp_tick();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void hal_temp_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
}
|
||||
s_sensors.begin();
|
||||
s_last_ok = false;
|
||||
s_last_c = 0.0f;
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "init OneWire pin=%d offset=%.1f", PIN_DS, (double)TEMP_OFFSET);
|
||||
}
|
||||
|
||||
extern "C" esp_err_t hal_temp_read_c(float *out)
|
||||
{
|
||||
if (!s_last_ok) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (out != NULL) {
|
||||
*out = s_last_c;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
|| @file Key.cpp
|
||||
|| @version 1.0
|
||||
|| @author Mark Stanley
|
||||
|| @contact mstanley@technologist.com
|
||||
||
|
||||
|| @description
|
||||
|| | Key class provides an abstract definition of a key or button
|
||||
|| | and was initially designed to be used in conjunction with a
|
||||
|| | state-machine.
|
||||
|| #
|
||||
||
|
||||
|| @license
|
||||
|| | This library is free software; you can redistribute it and/or
|
||||
|| | modify it under the terms of the GNU Lesser General Public
|
||||
|| | License as published by the Free Software Foundation; version
|
||||
|| | 2.1 of the License.
|
||||
|| |
|
||||
|| | This library is distributed in the hope that it will be useful,
|
||||
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|| | Lesser General Public License for more details.
|
||||
|| |
|
||||
|| | You should have received a copy of the GNU Lesser General Public
|
||||
|| | License along with this library; if not, write to the Free Software
|
||||
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|| #
|
||||
||
|
||||
*/
|
||||
#include <Key.h>
|
||||
|
||||
|
||||
// default constructor
|
||||
Key::Key() {
|
||||
kchar = NO_KEY;
|
||||
kstate = IDLE;
|
||||
stateChanged = false;
|
||||
}
|
||||
|
||||
// constructor
|
||||
Key::Key(char userKeyChar) {
|
||||
kchar = userKeyChar;
|
||||
kcode = -1;
|
||||
kstate = IDLE;
|
||||
stateChanged = false;
|
||||
}
|
||||
|
||||
|
||||
void Key::key_update (char userKeyChar, KeyState userState, boolean userStatus) {
|
||||
kchar = userKeyChar;
|
||||
kstate = userState;
|
||||
stateChanged = userStatus;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|| @changelog
|
||||
|| | 1.0 2012-06-04 - Mark Stanley : Initial Release
|
||||
|| #
|
||||
*/
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
||
|
||||
|| @file Keypad.cpp
|
||||
|| @version 3.1
|
||||
|| @author Mark Stanley, Alexander Brevig
|
||||
|| @contact mstanley@technologist.com, alexanderbrevig@gmail.com
|
||||
||
|
||||
|| @description
|
||||
|| | This library provides a simple interface for using matrix
|
||||
|| | keypads. It supports multiple keypresses while maintaining
|
||||
|| | backwards compatibility with the old single key library.
|
||||
|| | It also supports user selectable pins and definable keymaps.
|
||||
|| #
|
||||
||
|
||||
|| @license
|
||||
|| | This library is free software; you can redistribute it and/or
|
||||
|| | modify it under the terms of the GNU Lesser General Public
|
||||
|| | License as published by the Free Software Foundation; version
|
||||
|| | 2.1 of the License.
|
||||
|| |
|
||||
|| | This library is distributed in the hope that it will be useful,
|
||||
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|| | Lesser General Public License for more details.
|
||||
|| |
|
||||
|| | You should have received a copy of the GNU Lesser General Public
|
||||
|| | License along with this library; if not, write to the Free Software
|
||||
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|| #
|
||||
||
|
||||
*/
|
||||
#include <Keypad.h>
|
||||
|
||||
// <<constructor>> Allows custom keymap, pin configuration, and keypad sizes.
|
||||
Keypad::Keypad(char *userKeymap, byte *row, byte *col, byte numRows, byte numCols) {
|
||||
rowPins = row;
|
||||
columnPins = col;
|
||||
sizeKpd.rows = numRows;
|
||||
sizeKpd.columns = numCols;
|
||||
|
||||
begin(userKeymap);
|
||||
|
||||
setDebounceTime(10);
|
||||
setHoldTime(500);
|
||||
keypadEventListener = 0;
|
||||
|
||||
startTime = 0;
|
||||
single_key = false;
|
||||
}
|
||||
|
||||
// Let the user define a keymap - assume the same row/column count as defined in constructor
|
||||
void Keypad::begin(char *userKeymap) {
|
||||
keymap = userKeymap;
|
||||
}
|
||||
|
||||
// Returns a single key only. Retained for backwards compatibility.
|
||||
char Keypad::getKey() {
|
||||
single_key = true;
|
||||
|
||||
if (getKeys() && key[0].stateChanged && (key[0].kstate==PRESSED))
|
||||
return key[0].kchar;
|
||||
|
||||
single_key = false;
|
||||
|
||||
return NO_KEY;
|
||||
}
|
||||
|
||||
// Populate the key list.
|
||||
bool Keypad::getKeys() {
|
||||
bool keyActivity = false;
|
||||
|
||||
// Limit how often the keypad is scanned. This makes the loop() run 10 times as fast.
|
||||
if ( (millis()-startTime)>debounceTime ) {
|
||||
scanKeys();
|
||||
keyActivity = updateList();
|
||||
startTime = millis();
|
||||
}
|
||||
|
||||
return keyActivity;
|
||||
}
|
||||
|
||||
// Private : Hardware scan
|
||||
void Keypad::scanKeys() {
|
||||
// Re-intialize the row pins. Allows sharing these pins with other hardware.
|
||||
for (byte r=0; r<sizeKpd.rows; r++) {
|
||||
pin_mode(rowPins[r],INPUT_PULLUP);
|
||||
}
|
||||
|
||||
// bitMap stores ALL the keys that are being pressed.
|
||||
for (byte c=0; c<sizeKpd.columns; c++) {
|
||||
pin_mode(columnPins[c],OUTPUT);
|
||||
pin_write(columnPins[c], LOW); // Begin column pulse output.
|
||||
for (byte r=0; r<sizeKpd.rows; r++) {
|
||||
bitWrite(bitMap[r], c, !pin_read(rowPins[r])); // keypress is active low so invert to high.
|
||||
}
|
||||
// Set pin to high impedance input. Effectively ends column pulse.
|
||||
pin_write(columnPins[c],HIGH);
|
||||
pin_mode(columnPins[c],INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
// Manage the list without rearranging the keys. Returns true if any keys on the list changed state.
|
||||
bool Keypad::updateList() {
|
||||
|
||||
bool anyActivity = false;
|
||||
|
||||
// Delete any IDLE keys
|
||||
for (byte i=0; i<LIST_MAX; i++) {
|
||||
if (key[i].kstate==IDLE) {
|
||||
key[i].kchar = NO_KEY;
|
||||
key[i].kcode = -1;
|
||||
key[i].stateChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new keys to empty slots in the key list.
|
||||
for (byte r=0; r<sizeKpd.rows; r++) {
|
||||
for (byte c=0; c<sizeKpd.columns; c++) {
|
||||
boolean button = bitRead(bitMap[r],c);
|
||||
char keyChar = keymap[r * sizeKpd.columns + c];
|
||||
int keyCode = r * sizeKpd.columns + c;
|
||||
int idx = findInList (keyCode);
|
||||
// Key is already on the list so set its next state.
|
||||
if (idx > -1) {
|
||||
nextKeyState(idx, button);
|
||||
}
|
||||
// Key is NOT on the list so add it.
|
||||
if ((idx == -1) && button) {
|
||||
for (byte i=0; i<LIST_MAX; i++) {
|
||||
if (key[i].kchar==NO_KEY) { // Find an empty slot or don't add key to list.
|
||||
key[i].kchar = keyChar;
|
||||
key[i].kcode = keyCode;
|
||||
key[i].kstate = IDLE; // Keys NOT on the list have an initial state of IDLE.
|
||||
nextKeyState (i, button);
|
||||
break; // Don't fill all the empty slots with the same key.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report if the user changed the state of any key.
|
||||
for (byte i=0; i<LIST_MAX; i++) {
|
||||
if (key[i].stateChanged) anyActivity = true;
|
||||
}
|
||||
|
||||
return anyActivity;
|
||||
}
|
||||
|
||||
// Private
|
||||
// This function is a state machine but is also used for debouncing the keys.
|
||||
void Keypad::nextKeyState(byte idx, boolean button) {
|
||||
key[idx].stateChanged = false;
|
||||
|
||||
switch (key[idx].kstate) {
|
||||
case IDLE:
|
||||
if (button==CLOSED) {
|
||||
transitionTo (idx, PRESSED);
|
||||
holdTimer = millis(); } // Get ready for next HOLD state.
|
||||
break;
|
||||
case PRESSED:
|
||||
if ((millis()-holdTimer)>holdTime) // Waiting for a key HOLD...
|
||||
transitionTo (idx, HOLD);
|
||||
else if (button==OPEN) // or for a key to be RELEASED.
|
||||
transitionTo (idx, RELEASED);
|
||||
break;
|
||||
case HOLD:
|
||||
if (button==OPEN)
|
||||
transitionTo (idx, RELEASED);
|
||||
break;
|
||||
case RELEASED:
|
||||
transitionTo (idx, IDLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// New in 2.1
|
||||
bool Keypad::isPressed(char keyChar) {
|
||||
for (byte i=0; i<LIST_MAX; i++) {
|
||||
if ( key[i].kchar == keyChar ) {
|
||||
if ( (key[i].kstate == PRESSED) && key[i].stateChanged )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // Not pressed.
|
||||
}
|
||||
|
||||
// Search by character for a key in the list of active keys.
|
||||
// Returns -1 if not found or the index into the list of active keys.
|
||||
int Keypad::findInList (char keyChar) {
|
||||
for (byte i=0; i<LIST_MAX; i++) {
|
||||
if (key[i].kchar == keyChar) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Search by code for a key in the list of active keys.
|
||||
// Returns -1 if not found or the index into the list of active keys.
|
||||
int Keypad::findInList (int keyCode) {
|
||||
for (byte i=0; i<LIST_MAX; i++) {
|
||||
if (key[i].kcode == keyCode) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// New in 2.0
|
||||
char Keypad::waitForKey() {
|
||||
char waitKey = NO_KEY;
|
||||
while( (waitKey = getKey()) == NO_KEY ); // Block everything while waiting for a keypress.
|
||||
return waitKey;
|
||||
}
|
||||
|
||||
// Backwards compatibility function.
|
||||
KeyState Keypad::getState() {
|
||||
return key[0].kstate;
|
||||
}
|
||||
|
||||
// The end user can test for any changes in state before deciding
|
||||
// if any variables, etc. needs to be updated in their code.
|
||||
bool Keypad::keyStateChanged() {
|
||||
return key[0].stateChanged;
|
||||
}
|
||||
|
||||
// The number of keys on the key list, key[LIST_MAX], equals the number
|
||||
// of bytes in the key list divided by the number of bytes in a Key object.
|
||||
byte Keypad::numKeys() {
|
||||
return sizeof(key)/sizeof(Key);
|
||||
}
|
||||
|
||||
// Minimum debounceTime is 1 mS. Any lower *will* slow down the loop().
|
||||
void Keypad::setDebounceTime(uint debounce) {
|
||||
debounce<1 ? debounceTime=1 : debounceTime=debounce;
|
||||
}
|
||||
|
||||
void Keypad::setHoldTime(uint hold) {
|
||||
holdTime = hold;
|
||||
}
|
||||
|
||||
void Keypad::addEventListener(void (*listener)(char)){
|
||||
keypadEventListener = listener;
|
||||
}
|
||||
|
||||
void Keypad::transitionTo(byte idx, KeyState nextState) {
|
||||
key[idx].kstate = nextState;
|
||||
key[idx].stateChanged = true;
|
||||
|
||||
// Sketch used the getKey() function.
|
||||
// Calls keypadEventListener only when the first key in slot 0 changes state.
|
||||
if (single_key) {
|
||||
if ( (keypadEventListener!=NULL) && (idx==0) ) {
|
||||
keypadEventListener(key[0].kchar);
|
||||
}
|
||||
}
|
||||
// Sketch used the getKeys() function.
|
||||
// Calls keypadEventListener on any key that changes state.
|
||||
else {
|
||||
if (keypadEventListener!=NULL) {
|
||||
keypadEventListener(key[idx].kchar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|| @changelog
|
||||
|| | 3.1 2013-01-15 - Mark Stanley : Fixed missing RELEASED & IDLE status when using a single key.
|
||||
|| | 3.0 2012-07-12 - Mark Stanley : Made library multi-keypress by default. (Backwards compatible)
|
||||
|| | 3.0 2012-07-12 - Mark Stanley : Modified pin functions to support Keypad_I2C
|
||||
|| | 3.0 2012-07-12 - Stanley & Young : Removed static variables. Fix for multiple keypad objects.
|
||||
|| | 3.0 2012-07-12 - Mark Stanley : Fixed bug that caused shorted pins when pressing multiple keys.
|
||||
|| | 2.0 2011-12-29 - Mark Stanley : Added waitForKey().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Added the public function keyStateChanged().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Added the private function scanKeys().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Moved the Finite State Machine into the function getKeyState().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Removed the member variable lastUdate. Not needed after rewrite.
|
||||
|| | 1.8 2011-11-21 - Mark Stanley : Added decision logic to compile WProgram.h or Arduino.h
|
||||
|| | 1.8 2009-07-08 - Alexander Brevig : No longer uses arrays
|
||||
|| | 1.7 2009-06-18 - Alexander Brevig : Every time a state changes the keypadEventListener will trigger, if set.
|
||||
|| | 1.7 2009-06-18 - Alexander Brevig : Added setDebounceTime. setHoldTime specifies the amount of
|
||||
|| | microseconds before a HOLD state triggers
|
||||
|| | 1.7 2009-06-18 - Alexander Brevig : Added transitionTo
|
||||
|| | 1.6 2009-06-15 - Alexander Brevig : Added getState() and state variable
|
||||
|| | 1.5 2009-05-19 - Alexander Brevig : Added setHoldTime()
|
||||
|| | 1.4 2009-05-15 - Alexander Brevig : Added addEventListener
|
||||
|| | 1.3 2009-05-12 - Alexander Brevig : Added lastUdate, in order to do simple debouncing
|
||||
|| | 1.2 2009-05-09 - Alexander Brevig : Changed getKey()
|
||||
|| | 1.1 2009-04-28 - Alexander Brevig : Modified API, and made variables private
|
||||
|| | 1.0 2007-XX-XX - Mark Stanley : Initial Release
|
||||
|| #
|
||||
*/
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
||
|
||||
|| @file Key.h
|
||||
|| @version 1.0
|
||||
|| @author Mark Stanley
|
||||
|| @contact mstanley@technologist.com
|
||||
||
|
||||
|| @description
|
||||
|| | Key class provides an abstract definition of a key or button
|
||||
|| | and was initially designed to be used in conjunction with a
|
||||
|| | state-machine.
|
||||
|| #
|
||||
||
|
||||
|| @license
|
||||
|| | This library is free software; you can redistribute it and/or
|
||||
|| | modify it under the terms of the GNU Lesser General Public
|
||||
|| | License as published by the Free Software Foundation; version
|
||||
|| | 2.1 of the License.
|
||||
|| |
|
||||
|| | This library is distributed in the hope that it will be useful,
|
||||
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|| | Lesser General Public License for more details.
|
||||
|| |
|
||||
|| | You should have received a copy of the GNU Lesser General Public
|
||||
|| | License along with this library; if not, write to the Free Software
|
||||
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|| #
|
||||
||
|
||||
*/
|
||||
|
||||
#ifndef Keypadlib_KEY_H_
|
||||
#define Keypadlib_KEY_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define OPEN LOW
|
||||
#define CLOSED HIGH
|
||||
|
||||
typedef unsigned int uint;
|
||||
typedef enum{ IDLE, PRESSED, HOLD, RELEASED } KeyState;
|
||||
|
||||
const char NO_KEY = '\0';
|
||||
|
||||
class Key {
|
||||
public:
|
||||
// members
|
||||
char kchar;
|
||||
int kcode;
|
||||
KeyState kstate;
|
||||
boolean stateChanged;
|
||||
|
||||
// methods
|
||||
Key();
|
||||
Key(char userKeyChar);
|
||||
void key_update(char userKeyChar, KeyState userState, boolean userStatus);
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
|| @changelog
|
||||
|| | 1.0 2012-06-04 - Mark Stanley : Initial Release
|
||||
|| #
|
||||
*/
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
||
|
||||
|| @file Keypad.h
|
||||
|| @version 3.1
|
||||
|| @author Mark Stanley, Alexander Brevig
|
||||
|| @contact mstanley@technologist.com, alexanderbrevig@gmail.com
|
||||
||
|
||||
|| @description
|
||||
|| | This library provides a simple interface for using matrix
|
||||
|| | keypads. It supports multiple keypresses while maintaining
|
||||
|| | backwards compatibility with the old single key library.
|
||||
|| | It also supports user selectable pins and definable keymaps.
|
||||
|| #
|
||||
||
|
||||
|| @license
|
||||
|| | This library is free software; you can redistribute it and/or
|
||||
|| | modify it under the terms of the GNU Lesser General Public
|
||||
|| | License as published by the Free Software Foundation; version
|
||||
|| | 2.1 of the License.
|
||||
|| |
|
||||
|| | This library is distributed in the hope that it will be useful,
|
||||
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|| | Lesser General Public License for more details.
|
||||
|| |
|
||||
|| | You should have received a copy of the GNU Lesser General Public
|
||||
|| | License along with this library; if not, write to the Free Software
|
||||
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|| #
|
||||
||
|
||||
*/
|
||||
|
||||
#ifndef KEYPAD_H
|
||||
#define KEYPAD_H
|
||||
|
||||
#include "Key.h"
|
||||
|
||||
// bperrybap - Thanks for a well reasoned argument and the following macro(s).
|
||||
// See http://arduino.cc/forum/index.php/topic,142041.msg1069480.html#msg1069480
|
||||
#ifndef INPUT_PULLUP
|
||||
#warning "Using pinMode() INPUT_PULLUP AVR emulation"
|
||||
#define INPUT_PULLUP 0x2
|
||||
#define pinMode(_pin, _mode) _mypinMode(_pin, _mode)
|
||||
#define _mypinMode(_pin, _mode) \
|
||||
do { \
|
||||
if(_mode == INPUT_PULLUP) \
|
||||
pinMode(_pin, INPUT); \
|
||||
digitalWrite(_pin, 1); \
|
||||
if(_mode != INPUT_PULLUP) \
|
||||
pinMode(_pin, _mode); \
|
||||
}while(0)
|
||||
#endif
|
||||
|
||||
|
||||
#define OPEN LOW
|
||||
#define CLOSED HIGH
|
||||
|
||||
typedef char KeypadEvent;
|
||||
typedef unsigned int uint;
|
||||
typedef unsigned long ulong;
|
||||
|
||||
// Made changes according to this post http://arduino.cc/forum/index.php?topic=58337.0
|
||||
// by Nick Gammon. Thanks for the input Nick. It actually saved 78 bytes for me. :)
|
||||
typedef struct {
|
||||
byte rows;
|
||||
byte columns;
|
||||
} KeypadSize;
|
||||
|
||||
#define LIST_MAX 10 // Max number of keys on the active list.
|
||||
#define MAPSIZE 10 // MAPSIZE is the number of rows (times 16 columns)
|
||||
#define makeKeymap(x) ((char*)x)
|
||||
|
||||
|
||||
//class Keypad : public Key, public HAL_obj {
|
||||
class Keypad : public Key {
|
||||
public:
|
||||
|
||||
Keypad(char *userKeymap, byte *row, byte *col, byte numRows, byte numCols);
|
||||
|
||||
virtual void pin_mode(byte pinNum, byte mode) { pinMode(pinNum, mode); }
|
||||
virtual void pin_write(byte pinNum, boolean level) { digitalWrite(pinNum, level); }
|
||||
virtual int pin_read(byte pinNum) { return digitalRead(pinNum); }
|
||||
|
||||
uint bitMap[MAPSIZE]; // 10 row x 16 column array of bits. Except Due which has 32 columns.
|
||||
Key key[LIST_MAX];
|
||||
unsigned long holdTimer;
|
||||
|
||||
char getKey();
|
||||
bool getKeys();
|
||||
KeyState getState();
|
||||
void begin(char *userKeymap);
|
||||
bool isPressed(char keyChar);
|
||||
void setDebounceTime(uint);
|
||||
void setHoldTime(uint);
|
||||
void addEventListener(void (*listener)(char));
|
||||
int findInList(char keyChar);
|
||||
int findInList(int keyCode);
|
||||
char waitForKey();
|
||||
bool keyStateChanged();
|
||||
byte numKeys();
|
||||
|
||||
private:
|
||||
unsigned long startTime;
|
||||
char *keymap;
|
||||
byte *rowPins;
|
||||
byte *columnPins;
|
||||
KeypadSize sizeKpd;
|
||||
uint debounceTime;
|
||||
uint holdTime;
|
||||
bool single_key;
|
||||
|
||||
void scanKeys();
|
||||
bool updateList();
|
||||
void nextKeyState(byte n, boolean button);
|
||||
void transitionTo(byte n, KeyState nextState);
|
||||
void (*keypadEventListener)(char);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
|| @changelog
|
||||
|| | 3.1 2013-01-15 - Mark Stanley : Fixed missing RELEASED & IDLE status when using a single key.
|
||||
|| | 3.0 2012-07-12 - Mark Stanley : Made library multi-keypress by default. (Backwards compatible)
|
||||
|| | 3.0 2012-07-12 - Mark Stanley : Modified pin functions to support Keypad_I2C
|
||||
|| | 3.0 2012-07-12 - Stanley & Young : Removed static variables. Fix for multiple keypad objects.
|
||||
|| | 3.0 2012-07-12 - Mark Stanley : Fixed bug that caused shorted pins when pressing multiple keys.
|
||||
|| | 2.0 2011-12-29 - Mark Stanley : Added waitForKey().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Added the public function keyStateChanged().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Added the private function scanKeys().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Moved the Finite State Machine into the function getKeyState().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Removed the member variable lastUdate. Not needed after rewrite.
|
||||
|| | 1.8 2011-11-21 - Mark Stanley : Added test to determine which header file to compile,
|
||||
|| | WProgram.h or Arduino.h.
|
||||
|| | 1.8 2009-07-08 - Alexander Brevig : No longer uses arrays
|
||||
|| | 1.7 2009-06-18 - Alexander Brevig : This library is a Finite State Machine every time a state changes
|
||||
|| | the keypadEventListener will trigger, if set
|
||||
|| | 1.7 2009-06-18 - Alexander Brevig : Added setDebounceTime setHoldTime specifies the amount of
|
||||
|| | microseconds before a HOLD state triggers
|
||||
|| | 1.7 2009-06-18 - Alexander Brevig : Added transitionTo
|
||||
|| | 1.6 2009-06-15 - Alexander Brevig : Added getState() and state variable
|
||||
|| | 1.5 2009-05-19 - Alexander Brevig : Added setHoldTime()
|
||||
|| | 1.4 2009-05-15 - Alexander Brevig : Added addEventListener
|
||||
|| | 1.3 2009-05-12 - Alexander Brevig : Added lastUdate, in order to do simple debouncing
|
||||
|| | 1.2 2009-05-09 - Alexander Brevig : Changed getKey()
|
||||
|| | 1.1 2009-04-28 - Alexander Brevig : Modified API, and made variables private
|
||||
|| | 1.0 2007-XX-XX - Mark Stanley : Initial Release
|
||||
|| #
|
||||
*/
|
||||
@@ -0,0 +1,332 @@
|
||||
// Based on the work by DFRobot
|
||||
|
||||
#include "LiquidCrystal_I2C.h"
|
||||
#include <inttypes.h>
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#define printIIC(args) Wire.write(args)
|
||||
inline size_t LiquidCrystal_I2C::write(uint8_t value) {
|
||||
send(value, Rs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#else
|
||||
#include "WProgram.h"
|
||||
|
||||
#define printIIC(args) Wire.send(args)
|
||||
inline void LiquidCrystal_I2C::write(uint8_t value) {
|
||||
send(value, Rs);
|
||||
}
|
||||
|
||||
#endif
|
||||
#include "Wire.h"
|
||||
|
||||
|
||||
|
||||
// When the display powers up, it is configured as follows:
|
||||
//
|
||||
// 1. Display clear
|
||||
// 2. Function set:
|
||||
// DL = 1; 8-bit interface data
|
||||
// N = 0; 1-line display
|
||||
// F = 0; 5x8 dot character font
|
||||
// 3. Display on/off control:
|
||||
// D = 0; Display off
|
||||
// C = 0; Cursor off
|
||||
// B = 0; Blinking off
|
||||
// 4. Entry mode set:
|
||||
// I/D = 1; Increment by 1
|
||||
// S = 0; No shift
|
||||
//
|
||||
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
|
||||
// can't assume that its in that state when a sketch starts (and the
|
||||
// LiquidCrystal constructor is called).
|
||||
|
||||
LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows)
|
||||
{
|
||||
_Addr = lcd_Addr;
|
||||
_cols = lcd_cols;
|
||||
_rows = lcd_rows;
|
||||
_backlightval = LCD_NOBACKLIGHT;
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::oled_init(){
|
||||
_oled = true;
|
||||
init_priv();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::init(){
|
||||
init_priv();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::init_priv()
|
||||
{
|
||||
Wire.begin();
|
||||
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
|
||||
begin(_cols, _rows);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
|
||||
if (lines > 1) {
|
||||
_displayfunction |= LCD_2LINE;
|
||||
}
|
||||
_numlines = lines;
|
||||
|
||||
// for some 1 line displays you can select a 10 pixel high font
|
||||
if ((dotsize != 0) && (lines == 1)) {
|
||||
_displayfunction |= LCD_5x10DOTS;
|
||||
}
|
||||
|
||||
// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
|
||||
// according to datasheet, we need at least 40ms after power rises above 2.7V
|
||||
// before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
|
||||
delay(50);
|
||||
|
||||
// Now we pull both RS and R/W low to begin commands
|
||||
expanderWrite(_backlightval); // reset expanderand turn backlight off (Bit 8 =1)
|
||||
delay(1000);
|
||||
|
||||
//put the LCD into 4 bit mode
|
||||
// this is according to the hitachi HD44780 datasheet
|
||||
// figure 24, pg 46
|
||||
|
||||
// we start in 8bit mode, try to set 4 bit mode
|
||||
write4bits(0x03 << 4);
|
||||
delayMicroseconds(4500); // wait min 4.1ms
|
||||
|
||||
// second try
|
||||
write4bits(0x03 << 4);
|
||||
delayMicroseconds(4500); // wait min 4.1ms
|
||||
|
||||
// third go!
|
||||
write4bits(0x03 << 4);
|
||||
delayMicroseconds(150);
|
||||
|
||||
// finally, set to 4-bit interface
|
||||
write4bits(0x02 << 4);
|
||||
|
||||
|
||||
// set # lines, font size, etc.
|
||||
command(LCD_FUNCTIONSET | _displayfunction);
|
||||
|
||||
// turn the display on with no cursor or blinking default
|
||||
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
|
||||
display();
|
||||
|
||||
// clear it off
|
||||
clear();
|
||||
|
||||
// Initialize to default text direction (for roman languages)
|
||||
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
|
||||
|
||||
// set the entry mode
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
|
||||
home();
|
||||
|
||||
}
|
||||
|
||||
/********** high level commands, for the user! */
|
||||
void LiquidCrystal_I2C::clear(){
|
||||
command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero
|
||||
delayMicroseconds(2000); // this command takes a long time!
|
||||
if (_oled) setCursor(0,0);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::home(){
|
||||
command(LCD_RETURNHOME); // set cursor position to zero
|
||||
delayMicroseconds(2000); // this command takes a long time!
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row){
|
||||
int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
|
||||
if ( row > _numlines ) {
|
||||
row = _numlines-1; // we count rows starting w/0
|
||||
}
|
||||
command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
|
||||
}
|
||||
|
||||
// Turn the display on/off (quickly)
|
||||
void LiquidCrystal_I2C::noDisplay() {
|
||||
_displaycontrol &= ~LCD_DISPLAYON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
void LiquidCrystal_I2C::display() {
|
||||
_displaycontrol |= LCD_DISPLAYON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
|
||||
// Turns the underline cursor on/off
|
||||
void LiquidCrystal_I2C::noCursor() {
|
||||
_displaycontrol &= ~LCD_CURSORON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
void LiquidCrystal_I2C::cursor() {
|
||||
_displaycontrol |= LCD_CURSORON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
|
||||
// Turn on and off the blinking cursor
|
||||
void LiquidCrystal_I2C::noBlink() {
|
||||
_displaycontrol &= ~LCD_BLINKON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
void LiquidCrystal_I2C::blink() {
|
||||
_displaycontrol |= LCD_BLINKON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
|
||||
// These commands scroll the display without changing the RAM
|
||||
void LiquidCrystal_I2C::scrollDisplayLeft(void) {
|
||||
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
|
||||
}
|
||||
void LiquidCrystal_I2C::scrollDisplayRight(void) {
|
||||
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
|
||||
}
|
||||
|
||||
// This is for text that flows Left to Right
|
||||
void LiquidCrystal_I2C::leftToRight(void) {
|
||||
_displaymode |= LCD_ENTRYLEFT;
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
}
|
||||
|
||||
// This is for text that flows Right to Left
|
||||
void LiquidCrystal_I2C::rightToLeft(void) {
|
||||
_displaymode &= ~LCD_ENTRYLEFT;
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
}
|
||||
|
||||
// This will 'right justify' text from the cursor
|
||||
void LiquidCrystal_I2C::autoscroll(void) {
|
||||
_displaymode |= LCD_ENTRYSHIFTINCREMENT;
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
}
|
||||
|
||||
// This will 'left justify' text from the cursor
|
||||
void LiquidCrystal_I2C::noAutoscroll(void) {
|
||||
_displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
}
|
||||
|
||||
// Allows us to fill the first 8 CGRAM locations
|
||||
// with custom characters
|
||||
void LiquidCrystal_I2C::createChar(uint8_t location, uint8_t charmap[]) {
|
||||
location &= 0x7; // we only have 8 locations 0-7
|
||||
command(LCD_SETCGRAMADDR | (location << 3));
|
||||
for (int i=0; i<8; i++) {
|
||||
write(charmap[i]);
|
||||
}
|
||||
}
|
||||
|
||||
//createChar with PROGMEM input
|
||||
void LiquidCrystal_I2C::createChar(uint8_t location, const char *charmap) {
|
||||
location &= 0x7; // we only have 8 locations 0-7
|
||||
command(LCD_SETCGRAMADDR | (location << 3));
|
||||
for (int i=0; i<8; i++) {
|
||||
write(pgm_read_byte_near(charmap++));
|
||||
}
|
||||
}
|
||||
|
||||
// Turn the (optional) backlight off/on
|
||||
void LiquidCrystal_I2C::noBacklight(void) {
|
||||
_backlightval=LCD_NOBACKLIGHT;
|
||||
expanderWrite(0);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::backlight(void) {
|
||||
_backlightval=LCD_BACKLIGHT;
|
||||
expanderWrite(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********** mid level commands, for sending data/cmds */
|
||||
|
||||
inline void LiquidCrystal_I2C::command(uint8_t value) {
|
||||
send(value, 0);
|
||||
}
|
||||
|
||||
|
||||
/************ low level data pushing commands **********/
|
||||
|
||||
// write either command or data
|
||||
void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
|
||||
uint8_t highnib=value&0xf0;
|
||||
uint8_t lownib=(value<<4)&0xf0;
|
||||
write4bits((highnib)|mode);
|
||||
write4bits((lownib)|mode);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::write4bits(uint8_t value) {
|
||||
expanderWrite(value);
|
||||
pulseEnable(value);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::expanderWrite(uint8_t _data){
|
||||
Wire.beginTransmission(_Addr);
|
||||
printIIC((int)(_data) | _backlightval);
|
||||
Wire.endTransmission();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::pulseEnable(uint8_t _data){
|
||||
expanderWrite(_data | En); // En high
|
||||
delayMicroseconds(1); // enable pulse must be >450ns
|
||||
|
||||
expanderWrite(_data & ~En); // En low
|
||||
delayMicroseconds(50); // commands need > 37us to settle
|
||||
}
|
||||
|
||||
|
||||
// Alias functions
|
||||
|
||||
void LiquidCrystal_I2C::cursor_on(){
|
||||
cursor();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::cursor_off(){
|
||||
noCursor();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::blink_on(){
|
||||
blink();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::blink_off(){
|
||||
noBlink();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::load_custom_character(uint8_t char_num, uint8_t *rows){
|
||||
createChar(char_num, rows);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::setBacklight(uint8_t new_val){
|
||||
if(new_val){
|
||||
backlight(); // turn backlight on
|
||||
}else{
|
||||
noBacklight(); // turn backlight off
|
||||
}
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::printstr(const char c[]){
|
||||
//This function is not identical to the function used for "real" I2C displays
|
||||
//it's here so the user sketch doesn't have to be changed
|
||||
print(c);
|
||||
}
|
||||
|
||||
|
||||
// unsupported API functions
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
void LiquidCrystal_I2C::off(){}
|
||||
void LiquidCrystal_I2C::on(){}
|
||||
void LiquidCrystal_I2C::setDelay (int cmdDelay,int charDelay) {}
|
||||
uint8_t LiquidCrystal_I2C::status(){return 0;}
|
||||
uint8_t LiquidCrystal_I2C::keypad (){return 0;}
|
||||
uint8_t LiquidCrystal_I2C::init_bargraph(uint8_t graphtype){return 0;}
|
||||
void LiquidCrystal_I2C::draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end){}
|
||||
void LiquidCrystal_I2C::draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_row_end){}
|
||||
void LiquidCrystal_I2C::setContrast(uint8_t new_val){}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
//YWROBOT
|
||||
#ifndef LiquidCrystal_I2C_h
|
||||
#define LiquidCrystal_I2C_h
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "Print.h"
|
||||
#include <Wire.h>
|
||||
|
||||
// commands
|
||||
#define LCD_CLEARDISPLAY 0x01
|
||||
#define LCD_RETURNHOME 0x02
|
||||
#define LCD_ENTRYMODESET 0x04
|
||||
#define LCD_DISPLAYCONTROL 0x08
|
||||
#define LCD_CURSORSHIFT 0x10
|
||||
#define LCD_FUNCTIONSET 0x20
|
||||
#define LCD_SETCGRAMADDR 0x40
|
||||
#define LCD_SETDDRAMADDR 0x80
|
||||
|
||||
// flags for display entry mode
|
||||
#define LCD_ENTRYRIGHT 0x00
|
||||
#define LCD_ENTRYLEFT 0x02
|
||||
#define LCD_ENTRYSHIFTINCREMENT 0x01
|
||||
#define LCD_ENTRYSHIFTDECREMENT 0x00
|
||||
|
||||
// flags for display on/off control
|
||||
#define LCD_DISPLAYON 0x04
|
||||
#define LCD_DISPLAYOFF 0x00
|
||||
#define LCD_CURSORON 0x02
|
||||
#define LCD_CURSOROFF 0x00
|
||||
#define LCD_BLINKON 0x01
|
||||
#define LCD_BLINKOFF 0x00
|
||||
|
||||
// flags for display/cursor shift
|
||||
#define LCD_DISPLAYMOVE 0x08
|
||||
#define LCD_CURSORMOVE 0x00
|
||||
#define LCD_MOVERIGHT 0x04
|
||||
#define LCD_MOVELEFT 0x00
|
||||
|
||||
// flags for function set
|
||||
#define LCD_8BITMODE 0x10
|
||||
#define LCD_4BITMODE 0x00
|
||||
#define LCD_2LINE 0x08
|
||||
#define LCD_1LINE 0x00
|
||||
#define LCD_5x10DOTS 0x04
|
||||
#define LCD_5x8DOTS 0x00
|
||||
|
||||
// flags for backlight control
|
||||
#define LCD_BACKLIGHT 0x08
|
||||
#define LCD_NOBACKLIGHT 0x00
|
||||
|
||||
#define En B00000100 // Enable bit
|
||||
#define Rw B00000010 // Read/Write bit
|
||||
#define Rs B00000001 // Register select bit
|
||||
|
||||
class LiquidCrystal_I2C : public Print {
|
||||
public:
|
||||
LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
|
||||
void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS );
|
||||
void clear();
|
||||
void home();
|
||||
void noDisplay();
|
||||
void display();
|
||||
void noBlink();
|
||||
void blink();
|
||||
void noCursor();
|
||||
void cursor();
|
||||
void scrollDisplayLeft();
|
||||
void scrollDisplayRight();
|
||||
void printLeft();
|
||||
void printRight();
|
||||
void leftToRight();
|
||||
void rightToLeft();
|
||||
void shiftIncrement();
|
||||
void shiftDecrement();
|
||||
void noBacklight();
|
||||
void backlight();
|
||||
void autoscroll();
|
||||
void noAutoscroll();
|
||||
void createChar(uint8_t, uint8_t[]);
|
||||
void createChar(uint8_t location, const char *charmap);
|
||||
// Example: const char bell[8] PROGMEM = {B00100,B01110,B01110,B01110,B11111,B00000,B00100,B00000};
|
||||
|
||||
void setCursor(uint8_t, uint8_t);
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
virtual size_t write(uint8_t);
|
||||
#else
|
||||
virtual void write(uint8_t);
|
||||
#endif
|
||||
void command(uint8_t);
|
||||
void init();
|
||||
void oled_init();
|
||||
|
||||
////compatibility API function aliases
|
||||
void blink_on(); // alias for blink()
|
||||
void blink_off(); // alias for noBlink()
|
||||
void cursor_on(); // alias for cursor()
|
||||
void cursor_off(); // alias for noCursor()
|
||||
void setBacklight(uint8_t new_val); // alias for backlight() and nobacklight()
|
||||
void load_custom_character(uint8_t char_num, uint8_t *rows); // alias for createChar()
|
||||
void printstr(const char[]);
|
||||
|
||||
////Unsupported API functions (not implemented in this library)
|
||||
uint8_t status();
|
||||
void setContrast(uint8_t new_val);
|
||||
uint8_t keypad();
|
||||
void setDelay(int,int);
|
||||
void on();
|
||||
void off();
|
||||
uint8_t init_bargraph(uint8_t graphtype);
|
||||
void draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
|
||||
void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
|
||||
|
||||
|
||||
private:
|
||||
void init_priv();
|
||||
void send(uint8_t, uint8_t);
|
||||
void write4bits(uint8_t);
|
||||
void expanderWrite(uint8_t);
|
||||
void pulseEnable(uint8_t);
|
||||
uint8_t _Addr;
|
||||
uint8_t _displayfunction;
|
||||
uint8_t _displaycontrol;
|
||||
uint8_t _displaymode;
|
||||
uint8_t _numlines;
|
||||
bool _oled = false;
|
||||
uint8_t _cols;
|
||||
uint8_t _rows;
|
||||
uint8_t _backlightval;
|
||||
};
|
||||
|
||||
#endif
|
||||
Vendored
+5
@@ -3,3 +3,8 @@ Vendored Arduino libraries for P05 WROOM HAL only (not used on S3).
|
||||
- AccelStepper 1.64 (waspinator)
|
||||
- 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)
|
||||
|
||||
@@ -44,6 +44,8 @@ From `include/config.h` and `src/config.cpp`:
|
||||
|
||||
Motor constants: `STEPS_PER_REV = 4800`, `RPM = 60`, acceleration `9600`. Enable is driven HIGH at boot (disabled).
|
||||
|
||||
Note — S3 panel (JC4827W543C) pin lock via `board_jc4827w543` accessors: motor EN 7 / STEP 16 / DIR 15 on header P3 (IO6/7/15/16), DS18B20 on 17 on header P4 (GND/3V3/17/18, UART1 unused), speaker amp I2S BCLK 42 / LRCLK 2 / DIN 41 (P7 Speak). Header P2 carries IO46/9/14/5 and is left unused (IO46 is input-only).
|
||||
|
||||
Keypad map (5×4):
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
# JC4827W543 pin audit
|
||||
|
||||
Comparison of the GPIO assignments in this tree against the vendor
|
||||
documentation at <https://github.com/lsdlsd88/JC4827W543> (GUITION
|
||||
factory docs: spec PDF, IO pin distribution xlsx, Arduino demos).
|
||||
|
||||
Board variants: `JC4827W543N` (no touch), `...R` (resistive XPT2046),
|
||||
`...C` (capacitive GT911). Ours is the **C** model.
|
||||
|
||||
## 1. Vendor-documented pin allocation (authoritative)
|
||||
|
||||
From `5-IO pin distribution/4.3 inches IO pin distribution.xlsx`,
|
||||
cross-checked against the demo sketches
|
||||
(`1-Demo/Demo_Arduino/3_3-*` and `4_*`):
|
||||
|
||||
| GPIO | Vendor function | Notes |
|
||||
| --- | --- | --- |
|
||||
| 0 | BOOT button | also wired to `LCD_TE` |
|
||||
| 1 | `BL_CTRL` | LCD backlight |
|
||||
| 2 | `SPECK_LRCLK` | onboard speaker amp I2S WS |
|
||||
| 3 | `CTP_INT` / `RTP_IRQ` | touch interrupt (both models) |
|
||||
| 4 | `CTP_SCL` | GT911 I2C SCL |
|
||||
| 5 | free | |
|
||||
| 6 | free | |
|
||||
| 7 | free | |
|
||||
| 8 | `CTP_SDA` | GT911 I2C SDA |
|
||||
| 9 | free | |
|
||||
| 10 | `TF_CS` | microSD slot |
|
||||
| 11 | `RTP_DIN` / `TF_MISO` | shared SPI: resistive touch / SD |
|
||||
| 12 | `RTP_CLK` / `TF_CLK` | shared SPI |
|
||||
| 13 | `RTP_DIO` / `TF_MOSI` | shared SPI |
|
||||
| 14 | free | |
|
||||
| 15 | free | exposed on header (P2/P3 area) |
|
||||
| 16 | free | exposed on header; used by vendor LED demo |
|
||||
| 17 | `U1TXD` | UART1 header (P5), usable if UART1 unused |
|
||||
| 18 | `U1RXD` | UART1 header (P5), usable if UART1 unused |
|
||||
| 19 | `USB+` | USB D+ |
|
||||
| 20 | `USB-` | USB D- |
|
||||
| 21 | `LCD_A0` | QSPI data 0 |
|
||||
| 22–25 | — | do not exist on ESP32-S3 |
|
||||
| 26–34 | — | flash/PSRAM or non-existent |
|
||||
| 35 | not available | octal PSRAM (xlsx marks explicitly) |
|
||||
| 36, 37 | free per xlsx | **but** consumed by octal PSRAM on N8R8/R8 modules — treat as unavailable until module variant confirmed |
|
||||
| 38 | `RTP_CS` (resistive model) | on the **C** model this is the GT911 reset line — see §3 |
|
||||
| 39 | `LCD_A3` | QSPI data 3 |
|
||||
| 40 | `LCD_A2` | QSPI data 2 |
|
||||
| 41 | `SPECK_DIN` | speaker amp I2S data in |
|
||||
| 42 | `SPECK_BCLK` | speaker amp I2S BCLK |
|
||||
| 43 | `U0TXD` | console |
|
||||
| 44 | `U0RXD` | console |
|
||||
| 45 | `LCD_CS` | QSPI chip select |
|
||||
| 46 | free per xlsx | strapping pin (`LOG`); output-only-ish, use with care |
|
||||
| 47 | `LCD_CLK` | QSPI clock |
|
||||
| 48 | `LCD_A1` | QSPI data 1 |
|
||||
|
||||
The board carries an onboard I2S speaker amplifier (datasheets for
|
||||
NS4168 / AX9835 ship in `4-Driver_IC_Data_Sheet`), a `Speak` connector
|
||||
(P7), `BAT` connector (P6), TF slot, UART0 console, UART1 header and a
|
||||
scattering of free-IO headers (P2–P5: GPIO 15, 16, 17, 18 visible on
|
||||
the structure diagram). Vendor demos drive LEDs on GPIO 16/17 and a
|
||||
DHT11 on GPIO 27 — note GPIO 27 is **not** in the vendor pin table,
|
||||
so that demo value is suspect/generic.
|
||||
|
||||
## 2. What the code assigns today
|
||||
|
||||
### `board_jc4827w543` (`components/board_jc4827w543/board.c`)
|
||||
|
||||
| Signal | Code GPIO | Vendor | Match |
|
||||
| --- | --- | --- | --- |
|
||||
| LCD CS | 45 | 45 `LCD_CS` | yes |
|
||||
| LCD SCK | 47 | 47 `LCD_CLK` | yes |
|
||||
| LCD D0 | 21 | 21 `LCD_A0` | yes |
|
||||
| LCD D1 | 48 | 48 `LCD_A1` | yes |
|
||||
| LCD D2 | 40 | 40 `LCD_A2` | yes |
|
||||
| LCD D3 | 39 | 39 `LCD_A3` | yes |
|
||||
| LCD BL | 1 | 1 `BL_CTRL` | yes |
|
||||
| TP SDA | 8 | 8 `CTP_SDA` | yes |
|
||||
| TP SCL | 4 | 4 `CTP_SCL` | yes |
|
||||
| TP INT | 3 | 3 `CTP_INT` | yes |
|
||||
| TP RST | 38 | 38 (demo: `TOUCH_RES 38`) | yes — see §3 |
|
||||
| Motor EN | `GPIO_NUM_NC` | n/a | deliberately unassigned |
|
||||
|
||||
### `hal_display_nv3041a.c`
|
||||
|
||||
Driver **NV3041A** over QSPI via `esp_lcd` SPI host, 480×272, quad
|
||||
mode, 40 MHz. Matches the vendor demo
|
||||
(`Arduino_ESP32QSPI(45,47,21,48,40,39)` + `Arduino_NV3041A`,
|
||||
`Arduino_Canvas(480,272)`).
|
||||
|
||||
Caveat: the spec sheet PDF lists the driver chip as "ST3401A" — that
|
||||
string appears nowhere else; every demo and the burn files use
|
||||
NV3041A. Treat the PDF label as a typo.
|
||||
|
||||
### `hal_input_gt911.c`
|
||||
|
||||
GT911 at I2C addr `0x5D` (`GT911_SLAVE_ADDRESS1` in the vendor
|
||||
`touch.h`), I2C_NUM_0 @ 400 kHz on SDA 8 / SCL 4. Reset sequence
|
||||
(drive INT low, pulse RST, release INT to input) selects the 0x5D
|
||||
address per the GT911 datasheet — matches the vendor demo's
|
||||
`TOUCH_RES 38` / `TOUCH_INT 3` wiring.
|
||||
|
||||
### `board_wroom` + `hal_*` (classic ESP32, unchanged)
|
||||
|
||||
| Signal | GPIO | Source |
|
||||
| --- | --- | --- |
|
||||
| Stepper STEP | 12 | `config.h`, `hal_motor.c` |
|
||||
| Stepper DIR | 14 | `config.h`, `hal_motor.c` |
|
||||
| Stepper EN (active LOW) | 27 | `config.h`, `hal_motor.c`, `board_wroom` |
|
||||
| DS18B20 | 13 | `config.h`, `hal_temp.c` |
|
||||
| Beeper | 25 | `config.cpp`, `hal_audio.c` |
|
||||
| LCD 2004 I2C | SDA 21 / SCL 22, addr 0x27 | `config.cpp`, `hal_display_lcd2004.c` |
|
||||
| Keypad rows | 19, 18, 5, 17, 16 | `config.cpp`, `hal_input_keypad.c` |
|
||||
| Keypad cols | 15, 2, 0, 4 | `config.cpp`, `hal_input_keypad.c` |
|
||||
|
||||
Self-consistent with `docs/CURRENT_STATE.md`. Not related to the S3
|
||||
panel.
|
||||
|
||||
## 3. Findings / discrepancies
|
||||
|
||||
1. **Display and touch pins are all correct.** Every QSPI, backlight
|
||||
and GT911 pin in `board_jc4827w543/board.c` matches both the
|
||||
vendor xlsx and the vendor Arduino demos. No changes needed.
|
||||
|
||||
2. **TP_RST on GPIO 38 is undocumented for the C model.** The xlsx
|
||||
labels IO38 `RTP_CS` (its resistive-touch function). The capacitive
|
||||
demos (`LvglWidgets/touch.h`) set `TOUCH_RES 38`, so on the C
|
||||
variant IO38 is clearly the GT911 reset. Our code is right, but the
|
||||
vendor table alone would not tell you that — the demo code is the
|
||||
authority here.
|
||||
|
||||
3. **Motor EN/STEP/DIR are unassigned on the S3 board** (`GPIO_NUM_NC`
|
||||
in `board.c`). Correct call — but usable free pins do exist.
|
||||
Candidates from the vendor table + headers:
|
||||
- Safest (plain free, on headers): **15, 16** and UART1 header
|
||||
**17, 18** if UART1 is unused.
|
||||
- Free in xlsx but not obviously headered: **5, 6, 7, 9, 14**.
|
||||
- **Avoid**: 0 (BOOT/LCD_TE), 1–4/8 (LCD+touch+speaker), 10–13
|
||||
(TF/RTP SPI), 19/20 (USB), 21 (LCD D0), 35–37 (octal PSRAM),
|
||||
38–48 (used/strapping), 43/44 (console).
|
||||
- GPIO 46 is xlsx-free but is a strapping pin — last resort only.
|
||||
- Octal PSRAM (`sdkconfig.s3`: `CONFIG_SPIRAM_MODE_OCT=y`) means
|
||||
33–37 are off-limits even though the xlsx only flags 35.
|
||||
|
||||
4. **Audio on S3 should use I2S, not a beeper GPIO.** The board has a
|
||||
speaker amp on IO2 (LRCLK), IO41 (DIN), IO42 (BCLK) — P7 `Speak`
|
||||
connector. `hal_audio` currently builds the LEDC buzzer only for
|
||||
esp32 and a stub elsewhere; an S3 implementation should drive
|
||||
I2S on those pins rather than allocating a GPIO.
|
||||
|
||||
5. **DS18B20 on S3 needs one free GPIO** (any of §3's candidates; it
|
||||
only needs input+open-drain drive). Hal currently stubs temp on
|
||||
non-esp32.
|
||||
|
||||
6. **Reserved/bus-shared notes**: GPIO 0 carries `LCD_TE` in addition
|
||||
to BOOT — do not use it for anything else. TF card SPI (10–13) is
|
||||
shared with resistive-touch signals on R models; on our C model
|
||||
it's purely the SD slot.
|
||||
|
||||
7. **No conflicts found** between the code's S3 selections and the
|
||||
vendor map — every consumed pin is one the vendor assigns to that
|
||||
same function.
|
||||
|
||||
## Suggested motor wiring (for when S3 motor support lands)
|
||||
|
||||
| Signal | Suggested GPIO | Rationale |
|
||||
| --- | --- | --- |
|
||||
| STEP | 16 | free, on header, no boot function |
|
||||
| DIR | 15 | free, on header |
|
||||
| EN (active LOW) | 17 or 18 | UART1 header; free if UART1 unused |
|
||||
| DS18B20 | 9 or 14 | free, any GPIO works |
|
||||
|
||||
If UART1 is wanted for something else, use 5/6/7 for EN/temp instead.
|
||||
Keep motor EN's disable-first behaviour (drive inactive at boot before
|
||||
enabling output mode) whatever pins are chosen.
|
||||
@@ -0,0 +1,14 @@
|
||||
# JC4827W543 pin audit - ERRATA
|
||||
|
||||
*Based on verification against the official vendor repository at <https://github.com/lsdlsd88/JC4827W543>.*
|
||||
|
||||
**No errors were found in the original `JC4827W543_PIN_AUDIT.md` document.**
|
||||
|
||||
### Verification Notes:
|
||||
- **Vendor IO pin distribution:** The `4.3 inches IO pin distribution.xlsx` file perfectly matches the pinout matrix in the audit document (e.g., IO45 for LCD_CS, IO47 for LCD_CLK, etc., and SPECK_LRCLK / SPECK_DIN / SPECK_BCLK correctly documented as I2S lines).
|
||||
- **Display Configuration:** `Arduino_ESP32QSPI` code in `LvglWidgets.ino` verifies the QSPI display pins as `CS:45, SCK:47, D0:21, D1:48, D2:40, D3:39` matching the audit exactly.
|
||||
- **Touch Controller:** Capacitive GT911 configuration uses `TOUCH_SDA = 8`, `TOUCH_SCL = 4`, `TOUCH_RES = 38`, `TOUCH_INT = 3` which aligns with the undocumented `RTP_CS (38)` being effectively co-opted for capacitive reset, confirming the finding in the original document.
|
||||
- **Demos:** The DHT11 demo at `1-Demo/Demo_Arduino/4_9_WIFI Web Servers DHT11` indeed uses `GPIO 27`, and the `4_7_WIFI Web Servers LED` demo uses `GPIO 16/17`. These assignments are generic demo choices as noted in the original document and correctly do not conflict with the board specifications.
|
||||
- **Proposed GPIOs:** The suggested GPIOs for motor control (`16`, `15`, `17`, `18`, `9`, `14`, `5`, `6`, `7`) are correctly identified as free based on the schematic and code.
|
||||
|
||||
The original document is factually sound and can be completely trusted as the authoritative reference for this project.
|
||||
@@ -0,0 +1,184 @@
|
||||
# JC4827W543 pin audit
|
||||
|
||||
Comparison of the GPIO assignments in this tree against the vendor
|
||||
documentation at <https://github.com/lsdlsd88/JC4827W543> (GUITION
|
||||
factory docs: spec PDF, IO pin distribution xlsx, Arduino demos).
|
||||
|
||||
Board variants: `JC4827W543N` (no touch), `...R` (resistive XPT2046),
|
||||
`...C` (capacitive GT911). Ours is the **C** model.
|
||||
|
||||
## 1. Vendor-documented pin allocation (authoritative)
|
||||
|
||||
From `5-IO pin distribution/4.3 inches IO pin distribution.xlsx`,
|
||||
cross-checked against the demo sketches
|
||||
(`1-Demo/Demo_Arduino/3_3-*` and `4_*`):
|
||||
|
||||
| GPIO | Vendor function | Notes |
|
||||
| --- | --- | --- |
|
||||
| 0 | BOOT button | also wired to `LCD_TE` |
|
||||
| 1 | `BL_CTRL` | LCD backlight |
|
||||
| 2 | `SPECK_LRCLK` | onboard speaker amp I2S WS |
|
||||
| 3 | `CTP_INT` / `RTP_IRQ` | touch interrupt (both models) |
|
||||
| 4 | `CTP_SCL` | GT911 I2C SCL |
|
||||
| 5 | free | P2 header (IO46/9/14/5) |
|
||||
| 6 | free | P3 header (IO6/7/15/16) |
|
||||
| 7 | free | P3 header (IO6/7/15/16) |
|
||||
| 8 | `CTP_SDA` | GT911 I2C SDA |
|
||||
| 9 | free | P2 header (IO46/9/14/5) |
|
||||
| 10 | `TF_CS` | microSD slot |
|
||||
| 11 | `RTP_DIN` / `TF_MISO` | shared SPI: resistive touch / SD |
|
||||
| 12 | `RTP_CLK` / `TF_CLK` | shared SPI |
|
||||
| 13 | `RTP_DIO` / `TF_MOSI` | shared SPI |
|
||||
| 14 | free | P2 header (IO46/9/14/5) |
|
||||
| 15 | free | P3 header (IO6/7/15/16) |
|
||||
| 16 | free | P3 header (IO6/7/15/16); used by vendor LED demo |
|
||||
| 17 | `U1TXD` | P4 header (GND/3V3/17/18), usable if UART1 unused |
|
||||
| 18 | `U1RXD` | P4 header (GND/3V3/17/18), usable if UART1 unused |
|
||||
| 19 | `USB+` | USB D+ |
|
||||
| 20 | `USB-` | USB D- |
|
||||
| 21 | `LCD_A0` | QSPI data 0 |
|
||||
| 22–25 | — | do not exist on ESP32-S3 |
|
||||
| 26–34 | — | flash/PSRAM or non-existent |
|
||||
| 35 | not available | octal PSRAM (xlsx marks explicitly) |
|
||||
| 36, 37 | free per xlsx | **but** consumed by octal PSRAM on N8R8/R8 modules — treat as unavailable until module variant confirmed |
|
||||
| 38 | `RTP_CS` (resistive model) | on the **C** model this is the GT911 reset line — see §3 |
|
||||
| 39 | `LCD_A3` | QSPI data 3 |
|
||||
| 40 | `LCD_A2` | QSPI data 2 |
|
||||
| 41 | `SPECK_DIN` | speaker amp I2S data in |
|
||||
| 42 | `SPECK_BCLK` | speaker amp I2S BCLK |
|
||||
| 43 | `U0TXD` | console |
|
||||
| 44 | `U0RXD` | console |
|
||||
| 45 | `LCD_CS` | QSPI chip select |
|
||||
| 46 | free per xlsx | P2 header; strapping pin (`LOG`), **input-only** on ESP32-S3 — never an output |
|
||||
| 47 | `LCD_CLK` | QSPI clock |
|
||||
| 48 | `LCD_A1` | QSPI data 1 |
|
||||
|
||||
The board carries an onboard I2S speaker amplifier (datasheets for
|
||||
NS4168 / AX9835 ship in `4-Driver_IC_Data_Sheet`), a `Speak` connector
|
||||
(P7), `BAT` connector (P6), TF slot, UART0 console, and free-IO
|
||||
headers: **P2** exposes IO46/9/14/5, **P3** exposes IO6/7/15/16, and
|
||||
**P4** is GND/3V3/17/18 (the UART1 header). Vendor demos drive LEDs on GPIO 16/17 and a
|
||||
DHT11 on GPIO 27 — note GPIO 27 is **not** in the vendor pin table,
|
||||
so that demo value is suspect/generic.
|
||||
|
||||
## 2. What the code assigns today
|
||||
|
||||
### `board_jc4827w543` (`components/board_jc4827w543/board.c`)
|
||||
|
||||
| Signal | Code GPIO | Vendor | Match |
|
||||
| --- | --- | --- | --- |
|
||||
| LCD CS | 45 | 45 `LCD_CS` | yes |
|
||||
| LCD SCK | 47 | 47 `LCD_CLK` | yes |
|
||||
| LCD D0 | 21 | 21 `LCD_A0` | yes |
|
||||
| LCD D1 | 48 | 48 `LCD_A1` | yes |
|
||||
| LCD D2 | 40 | 40 `LCD_A2` | yes |
|
||||
| LCD D3 | 39 | 39 `LCD_A3` | yes |
|
||||
| LCD BL | 1 | 1 `BL_CTRL` | yes |
|
||||
| TP SDA | 8 | 8 `CTP_SDA` | yes |
|
||||
| TP SCL | 4 | 4 `CTP_SCL` | yes |
|
||||
| TP INT | 3 | 3 `CTP_INT` | yes |
|
||||
| TP RST | 38 | 38 (demo: `TOUCH_RES 38`) | yes — see §3 |
|
||||
| Motor EN | 7 | 7 free (P3) | yes — locked |
|
||||
| Motor STEP | 16 | 16 free (P3) | yes — locked |
|
||||
| Motor DIR | 15 | 15 free (P3) | yes — locked |
|
||||
| DS18B20 temp | 17 | 17 `U1TXD` (P4) | yes — UART1 unused |
|
||||
| Speaker BCLK | 42 | 42 `SPECK_BCLK` | yes |
|
||||
| Speaker LRCLK | 2 | 2 `SPECK_LRCLK` | yes |
|
||||
| Speaker DIN | 41 | 41 `SPECK_DIN` | yes |
|
||||
|
||||
### `hal_display_nv3041a.c`
|
||||
|
||||
Driver **NV3041A** over QSPI via `esp_lcd` SPI host, 480×272, quad
|
||||
mode, 40 MHz. Matches the vendor demo
|
||||
(`Arduino_ESP32QSPI(45,47,21,48,40,39)` + `Arduino_NV3041A`,
|
||||
`Arduino_Canvas(480,272)`).
|
||||
|
||||
Caveat: the spec sheet PDF lists the driver chip as "ST3401A" — that
|
||||
string appears nowhere else; every demo and the burn files use
|
||||
NV3041A. Treat the PDF label as a typo.
|
||||
|
||||
### `hal_input_gt911.c`
|
||||
|
||||
GT911 at I2C addr `0x5D` (`GT911_SLAVE_ADDRESS1` in the vendor
|
||||
`touch.h`), I2C_NUM_0 @ 400 kHz on SDA 8 / SCL 4. Reset sequence
|
||||
(drive INT low, pulse RST, release INT to input) selects the 0x5D
|
||||
address per the GT911 datasheet — matches the vendor demo's
|
||||
`TOUCH_RES 38` / `TOUCH_INT 3` wiring.
|
||||
|
||||
### `board_wroom` + `hal_*` (classic ESP32, unchanged)
|
||||
|
||||
| Signal | GPIO | Source |
|
||||
| --- | --- | --- |
|
||||
| Stepper STEP | 12 | `config.h`, `hal_motor.c` |
|
||||
| Stepper DIR | 14 | `config.h`, `hal_motor.c` |
|
||||
| Stepper EN (active LOW) | 27 | `config.h`, `hal_motor.c`, `board_wroom` |
|
||||
| DS18B20 | 13 | `config.h`, `hal_temp.c` |
|
||||
| Beeper | 25 | `config.cpp`, `hal_audio.c` |
|
||||
| LCD 2004 I2C | SDA 21 / SCL 22, addr 0x27 | `config.cpp`, `hal_display_lcd2004.c` |
|
||||
| Keypad rows | 19, 18, 5, 17, 16 | `config.cpp`, `hal_input_keypad.c` |
|
||||
| Keypad cols | 15, 2, 0, 4 | `config.cpp`, `hal_input_keypad.c` |
|
||||
|
||||
Self-consistent with `docs/CURRENT_STATE.md`. Not related to the S3
|
||||
panel.
|
||||
|
||||
## 3. Findings / discrepancies
|
||||
|
||||
1. **Display and touch pins are all correct.** Every QSPI, backlight
|
||||
and GT911 pin in `board_jc4827w543/board.c` matches both the
|
||||
vendor xlsx and the vendor Arduino demos. No changes needed.
|
||||
|
||||
2. **TP_RST on GPIO 38 is undocumented for the C model.** The xlsx
|
||||
labels IO38 `RTP_CS` (its resistive-touch function). The capacitive
|
||||
demos (`LvglWidgets/touch.h`) set `TOUCH_RES 38`, so on the C
|
||||
variant IO38 is clearly the GT911 reset. Our code is right, but the
|
||||
vendor table alone would not tell you that — the demo code is the
|
||||
authority here.
|
||||
|
||||
3. **Motor EN/STEP/DIR and DS18B20 are now locked on the S3 board.**
|
||||
Header labels confirmed: P2 exposes IO46/9/14/5, P3 exposes
|
||||
IO6/7/15/16, P4 is GND/3V3/17/18 (the UART1 header).
|
||||
- Motor STEP **16**, DIR **15**, EN **7** (active LOW) — all on P3;
|
||||
IO6 stays spare.
|
||||
- DS18B20 on **17** — P4 puts GND, 3V3 and data on one header;
|
||||
UART1 is unused.
|
||||
- Speaker amp I2S stays on the vendor pins: BCLK **42**,
|
||||
LRCLK **2**, DIN **41** (P7 `Speak`).
|
||||
- **Avoid**: 0 (BOOT/LCD_TE), 1–4/8 (LCD+touch+speaker), 10–13
|
||||
(TF/RTP SPI), 19/20 (USB), 21 (LCD D0), 35–37 (octal PSRAM),
|
||||
38–48 (used/strapping), 43/44 (console).
|
||||
- GPIO 46 is xlsx-free and on P2 but is **input-only** on the
|
||||
ESP32-S3 — never use it for outputs.
|
||||
- Octal PSRAM (`sdkconfig.s3`: `CONFIG_SPIRAM_MODE_OCT=y`) means
|
||||
33–37 are off-limits even though the xlsx only flags 35.
|
||||
|
||||
4. **Audio on S3 should use I2S, not a beeper GPIO.** The board has a
|
||||
speaker amp on IO2 (LRCLK), IO41 (DIN), IO42 (BCLK) — P7 `Speak`
|
||||
connector. `hal_audio` currently builds the LEDC buzzer only for
|
||||
esp32 and a stub elsewhere; an S3 implementation should drive
|
||||
I2S on those pins rather than allocating a GPIO.
|
||||
|
||||
5. **DS18B20 on S3 needs one free GPIO** (any of §3's candidates; it
|
||||
only needs input+open-drain drive). Hal currently stubs temp on
|
||||
non-esp32.
|
||||
|
||||
6. **Reserved/bus-shared notes**: GPIO 0 carries `LCD_TE` in addition
|
||||
to BOOT — do not use it for anything else. TF card SPI (10–13) is
|
||||
shared with resistive-touch signals on R models; on our C model
|
||||
it's purely the SD slot.
|
||||
|
||||
7. **No conflicts found** between the code's S3 selections and the
|
||||
vendor map — every consumed pin is one the vendor assigns to that
|
||||
same function.
|
||||
|
||||
## Motor / temp / speaker wiring as locked (phase I01)
|
||||
|
||||
| Signal | GPIO | Rationale |
|
||||
| --- | --- | --- |
|
||||
| STEP | 16 | free, P3 header, no boot function |
|
||||
| DIR | 15 | free, P3 header |
|
||||
| EN (active LOW) | 7 | free, P3 header |
|
||||
| DS18B20 | 17 | P4 header carries GND/3V3/17/18 — one connector for the sensor |
|
||||
| Speaker BCLK / LRCLK / DIN | 42 / 2 / 41 | onboard amp wired to P7 `Speak` |
|
||||
|
||||
Keep motor EN's disable-first behaviour (drive inactive at boot before
|
||||
enabling output mode).
|
||||
@@ -0,0 +1,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 |
|
||||
@@ -38,17 +38,19 @@ If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase fi
|
||||
| P03 | `refactor/P03-machine-host.md` | `app_machine` + stub HAL + SM tests | DONE |
|
||||
| P04 | `refactor/P04-gitea-ci.md` | `.gitea/workflows/ci.yml` live | DONE |
|
||||
| P05 | `refactor/P05-hal-wroom-motion.md` | WROOM motor/temp/audio HAL | DONE |
|
||||
| P06 | `refactor/P06-ui-wroom-cutover.md` | 2004 UI + keypad + Stop/Resume cutover | BLOCKED |
|
||||
| P07 | `refactor/P07-board-s3-ui.md` | JC4827W543 display+GT911+STOP | BLOCKED |
|
||||
| P08 | `refactor/P08-idf-debt.md` | Replace Arduino drivers (RMT etc.) | 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 | DONE |
|
||||
| P08 | `refactor/P08-idf-debt.md` | Replace Arduino drivers (RMT etc.) | DONE |
|
||||
|
||||
P04 Notes: merge_bin uses `${{ gitea.sha }}` (not `github.sha`); runner unverified (no Actions run).
|
||||
|
||||
P05 Notes: Arduino-esp32 ~3.2.1 (3.1.x requires IDF <5.4) + IDF v5.4; AccelStepper 1.64; OneWire 2.3.8; DallasTemperature 3.9.1. AUTOFILM_MOTION_SMOKE default off. hw not flashed.
|
||||
|
||||
P06 Notes: BLOCKED — P05 WROOM motor/temp/audio HAL/tasks missing (headers + host stubs only; main.c idle). Not implemented.
|
||||
P06 Notes: WROOM input_task dual-path stop (hal_motor_request_stop then CMD_STOP). Legacy startingMenu unlinked from IDF. Notes=`hw smoke pending`.
|
||||
|
||||
P07 Notes: BLOCKED — P06 app_ui + WROOM cutover missing (no app_ui/hal_display/hal_input). Not implemented. Hw unflashed.
|
||||
P07 Notes: 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
|
||||
|
||||
|
||||
@@ -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,16 +1,6 @@
|
||||
BLOCKED: P05 WROOM motor/temp/audio HAL/tasks missing on branch refactor/esp-idf-modular-ui @ 674d067.
|
||||
|
||||
Required and absent:
|
||||
- components/hal_motor device impl + motor_task (headers + host stubs only)
|
||||
- components/hal_temp device impl + temp_task (headers + host stubs only)
|
||||
- components/hal_audio device impl + audio_task (headers + host stubs only)
|
||||
- main.c still board GPIO idle loop; no P05 task spawn
|
||||
|
||||
P05 phase file STATUS remains BLOCKED. Protocol: P06 requires P05. Do not implement P01–P05 in this session. No display/input HAL, app_ui, or startingMenu unlink landed.
|
||||
|
||||
# P06 — WROOM UI cutover (2004 + keypad + Stop/Resume)
|
||||
|
||||
STATUS: BLOCKED
|
||||
STATUS: DONE
|
||||
DEPENDS: P05
|
||||
READ: this file, `src/menu.cpp`, `src/display.cpp`, `src/config.cpp`, `docs/TARGET_ARCHITECTURE.md` (screens + stop rules)
|
||||
OUT: S3 LVGL, NVS profiles, keeping blocking `getEntEscInput` as the live path
|
||||
@@ -140,8 +130,12 @@ If no hardware: still land; Notes=`hw smoke pending`; do not skip the input dual
|
||||
|
||||
## DoD
|
||||
|
||||
- [ ] Esc during run always EN-off
|
||||
- [ ] No malloc mm:ss
|
||||
- [ ] No blocking alarm on UI/machine
|
||||
- [ ] Legacy menus not in the IDF link
|
||||
- [ ] STATUS→DONE
|
||||
- [x] Esc during run always EN-off
|
||||
- [x] No malloc mm:ss
|
||||
- [x] No blocking alarm on UI/machine
|
||||
- [x] Legacy menus not in the IDF link
|
||||
- [x] STATUS→DONE
|
||||
|
||||
## Notes
|
||||
|
||||
Notes=`hw smoke pending`. Host ctest green. WROOM `idf.py` build attempted in sandbox without ESP-IDF installed.
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
# P07 — JC4827W543 display + GT911 + STOP
|
||||
|
||||
STATUS: BLOCKED
|
||||
STATUS: DONE
|
||||
DEPENDS: P06
|
||||
|
||||
BLOCKED: P06 `app_ui` + WROOM cutover missing on `refactor/esp-idf-modular-ui` @ `0b1d14d`.
|
||||
|
||||
Required and absent:
|
||||
- `components/app_ui` (five-screen contract P07 must colour-adapt)
|
||||
- `components/hal_display` / `hal_display_text` cell grid
|
||||
- `components/hal_input`
|
||||
- WROOM 2004 + keypad Stop/Resume cutover (`ui_task` / `input_task`)
|
||||
|
||||
P06 STATUS remains BLOCKED. Protocol: P07 requires P06 screen contract.
|
||||
Do not implement P01–P06 in this session. No NV3041A/GT911 HAL, colour adapter, or STOP hit-test landed.
|
||||
VERIFY not run (nothing to build for this phase). Hw smoke: unflashed.
|
||||
Custom-draw NV3041A + GT911 colour adapter for the five P06 screens.
|
||||
Touch STOP = `hal_motor_request_stop()` then CMD_STOP via the same key map as Esc.
|
||||
Motor EN remains GPIO_NUM_NC (no spare header pin documented). No STEP/DIR.
|
||||
No LVGL. Hw smoke: unflashed.
|
||||
|
||||
READ: this file, `docs/TARGET_ARCHITECTURE.md` dual-board + UI strategy, vendor pin notes below
|
||||
OUT: LVGL in `app_machine`; changing recipes; assigning WROOM STEP/DIR onto S3 without a pin table comment; pumps
|
||||
@@ -65,7 +58,7 @@ Hardware S3: ProgramSelect visible, tap process, Running shows name/remaining/te
|
||||
|
||||
## DoD
|
||||
|
||||
- [ ] Same commands as keypad
|
||||
- [ ] No lv types in machine/process/cmd headers
|
||||
- [ ] WROOM firmware unchanged in behaviour
|
||||
- [ ] STATUS→DONE
|
||||
- [x] Same commands as keypad
|
||||
- [x] No lv types in machine/process/cmd headers
|
||||
- [x] WROOM firmware unchanged in behaviour
|
||||
- [x] STATUS→DONE
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# P08 — Arduino debt burn-down
|
||||
|
||||
STATUS: TODO
|
||||
STATUS: DONE
|
||||
DEPENDS: P06 (behaviour parity). P07 optional.
|
||||
READ: this file, `components/hal_motor/**`, HAL headers from P03
|
||||
OUT: recipe edits; UI redesign; LVGL-by-default
|
||||
@@ -35,6 +35,8 @@ Per numbered item above, e.g. `Replace AccelStepper with RMT step pulse HAL`.
|
||||
|
||||
## DoD
|
||||
|
||||
- [ ] `rg -n "Arduino.h|AccelStepper|LiquidCrystal|DallasTemperature|#include <Keypad.h>|#include <OneWire.h>"` empty in `main/` `components/`
|
||||
- [ ] Stop still EN-off first
|
||||
- [ ] STATUS→DONE
|
||||
- [x] `rg -n "Arduino.h|AccelStepper|LiquidCrystal|DallasTemperature|#include <Keypad.h>|#include <OneWire.h>"` empty in `main/` and live `components/hal_*` / `main` (vendor copies remain unlinked under `components/third_party/`)
|
||||
- [x] Stop still EN-off first (`hal_motor_request_stop` EN HIGH then RMT abort)
|
||||
- [x] STATUS→DONE
|
||||
|
||||
Notes: audio was already LEDC; converted to C. Motor RMT copy-encoder batches + short accel table. Temp `espressif/onewire_bus` + skip-ROM scratchpad, +0.4 °C. Keypad GPIO scan, same 5x4 map. LCD PCF8574 nibble HD44780 on I2C0. Dropped `espressif/arduino-esp32` and `initArduino()`. Host ctest green. Local `idf.py` not run (no IDF_PATH). Hw unflashed.
|
||||
|
||||
+17
-2
@@ -11,13 +11,28 @@ endif()
|
||||
|
||||
idf_component_register(SRCS "main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ${BOARD_COMP} driver app_process app_machine
|
||||
hal_motor hal_temp hal_audio)
|
||||
REQUIRES ${BOARD_COMP} driver app_process app_machine app_ui
|
||||
hal_motor hal_temp hal_audio hal_display hal_input
|
||||
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)
|
||||
|
||||
+162
-46
@@ -1,65 +1,183 @@
|
||||
#include "board.h"
|
||||
#include "app_process.h"
|
||||
#include "app_machine.h"
|
||||
#include "app_ui.h"
|
||||
#include "hal_motor.h"
|
||||
#include "hal_temp.h"
|
||||
#include "hal_audio.h"
|
||||
#include "hal_display.h"
|
||||
#include "hal_input.h"
|
||||
#include "ui_cmd.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#ifdef AUTOFILM_BOARD_WROOM
|
||||
#include <Arduino.h>
|
||||
#ifndef AUTOFILM_GIT_DESC
|
||||
#define AUTOFILM_GIT_DESC "dev"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "app";
|
||||
|
||||
#ifdef AUTOFILM_BOARD_WROOM
|
||||
extern void autofilm_temp_tick(void);
|
||||
#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 (;;) {
|
||||
autofilm_temp_tick();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(AUTOFILM_MOTION_SMOKE)
|
||||
static void smoke_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
ui_cmd_t cmd = {0};
|
||||
cmd.id = CMD_SELECT_PROCESS;
|
||||
cmd.process_id = 4;
|
||||
app_machine_handle_cmd(&cmd);
|
||||
cmd.id = CMD_ARM_STEP;
|
||||
cmd.step_index = 0;
|
||||
app_machine_handle_cmd(&cmd);
|
||||
cmd.id = CMD_START_STEP;
|
||||
app_machine_handle_cmd(&cmd);
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
cmd.id = CMD_STOP;
|
||||
app_machine_handle_cmd(&cmd);
|
||||
ESP_LOGW(TAG, "motion smoke finished");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "board=%s", board_name());
|
||||
|
||||
#ifdef AUTOFILM_BOARD_WROOM
|
||||
initArduino();
|
||||
#endif
|
||||
ESP_LOGI(TAG, "board=%s desc=%s", board_name(), AUTOFILM_GIT_DESC);
|
||||
|
||||
gpio_num_t en_pin = board_pin_motor_en();
|
||||
if (en_pin != GPIO_NUM_NC) {
|
||||
@@ -80,20 +198,18 @@ void app_main(void)
|
||||
hal_audio_init();
|
||||
app_machine_init();
|
||||
|
||||
#ifdef AUTOFILM_BOARD_WROOM
|
||||
xTaskCreate(temp_task, "temp", 4096, NULL, 1, NULL);
|
||||
#if defined(AUTOFILM_MOTION_SMOKE)
|
||||
xTaskCreate(smoke_task, "smoke", 3072, NULL, 1, NULL);
|
||||
#endif
|
||||
#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
|
||||
|
||||
while (1) {
|
||||
#ifdef AUTOFILM_BOARD_WROOM
|
||||
if (app_machine_state() == ST_RUNNING) {
|
||||
ESP_LOGI(TAG, "remaining_ms=%u", (unsigned)app_machine_remaining_ms());
|
||||
app_machine_tick((uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS));
|
||||
}
|
||||
#endif
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
/* app_main returns; scheduler keeps product tasks. No legacy startingMenu. */
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ void hal_temp_init(void)
|
||||
stub_temp_c = 20.0f;
|
||||
}
|
||||
|
||||
void hal_temp_tick(void)
|
||||
{
|
||||
}
|
||||
|
||||
esp_err_t hal_temp_read_c(float *out)
|
||||
{
|
||||
if (stub_temp_fail) {
|
||||
|
||||
Reference in New Issue
Block a user