Compare commits
47
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce9d729027 | ||
|
|
06acd84928 | ||
|
|
cfd047a0ac | ||
|
|
a3830a58cb | ||
|
|
3785f07189 | ||
|
|
37b772218b | ||
|
|
8f7db974fa | ||
|
|
02d226b949 | ||
|
|
d71e28c85d | ||
|
|
adff1d963b | ||
|
|
d84116c45b | ||
|
|
1ade64b2b2 | ||
|
|
20535ceb14 | ||
|
|
76ef75da29 | ||
|
|
d70df219f8 | ||
|
|
7e854e10f0 | ||
|
|
b6c385e09c | ||
|
|
a9f76ea058 | ||
|
|
c40c714028 | ||
|
|
f87ab0c65f | ||
|
|
b4230b2a0e | ||
|
|
04e1ed0f16 | ||
|
|
a33b4b0c74 | ||
|
|
73f3158b27 | ||
|
|
a3fe809359 | ||
|
|
637ea8cb6e | ||
|
|
84b60155dd | ||
|
|
7406114368 | ||
|
|
0b0a713098 | ||
|
|
9ea89ac467 | ||
|
|
5f1727da7d | ||
|
|
32af44a702 | ||
|
|
82f55af4b1 | ||
|
|
880f9a18c5 | ||
|
|
e04d0f0e7f | ||
|
|
114135fba0 | ||
|
|
4fe0648005 | ||
|
|
ce9af6382e | ||
|
|
d21f7ad4e4 | ||
|
|
f47299b939 | ||
|
|
51faa0fe59 | ||
|
|
a19accf6a7 | ||
|
|
2c61e3576f | ||
|
|
0b1d14d21a | ||
|
|
e245db175b | ||
|
|
57ca78a1e3 | ||
|
|
674d067872 |
@@ -19,7 +19,7 @@ jobs:
|
||||
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
|
||||
$SUDO apt-get install -y -q --no-install-recommends cmake gcc g++ make
|
||||
- name: Host unit tests
|
||||
if: hashFiles('tests/host/CMakeLists.txt') != ''
|
||||
run: |
|
||||
@@ -46,15 +46,15 @@ jobs:
|
||||
- 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 --no-install-recommends curl ca-certificates && \
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | $SUDO bash - && \
|
||||
$SUDO apt-get install -y --no-install-recommends nodejs
|
||||
$SUDO apt-get install -y -q --no-install-recommends nodejs
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build
|
||||
if: hashFiles('CMakeLists.txt') != ''
|
||||
run: |
|
||||
. $IDF_PATH/export.sh && \
|
||||
git config --global --add safe.directory '*' && \
|
||||
idf.py -B build/${{ matrix.board }} set-target ${{ matrix.target }} && \
|
||||
idf.py -B build/${{ matrix.board }} \
|
||||
@@ -62,10 +62,11 @@ jobs:
|
||||
-D AUTOFILM_BOARD=${{ matrix.board }} \
|
||||
build && \
|
||||
idf.py -B build/${{ matrix.board }} size | tee size-${{ matrix.board }}.txt && \
|
||||
cd build/${{ matrix.board }} && \
|
||||
python $IDF_PATH/components/esptool_py/esptool/esptool.py \
|
||||
--chip ${{ matrix.target }} merge_bin \
|
||||
-o autofilm-${{ matrix.board }}-${{ gitea.sha }}.bin \
|
||||
@build/${{ matrix.board }}/flash_args
|
||||
-o ../../autofilm-${{ matrix.board }}-${{ gitea.sha }}.bin \
|
||||
@flash_args
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: hashFiles('CMakeLists.txt') != ''
|
||||
with:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
build/
|
||||
build_host/
|
||||
sdkconfig
|
||||
sdkconfig.old
|
||||
managed_components/
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
if(NOT DEFINED AUTOFILM_BOARD)
|
||||
set(AUTOFILM_BOARD wroom CACHE STRING "wroom|jc4827w543")
|
||||
endif()
|
||||
if(IDF_TARGET STREQUAL "esp32s3" OR AUTOFILM_BOARD STREQUAL "jc4827w543")
|
||||
set(EXCLUDE_COMPONENTS board_wroom)
|
||||
set(AUTOFILM_BOARD jc4827w543 CACHE STRING "wroom|jc4827w543" FORCE)
|
||||
else()
|
||||
set(EXCLUDE_COMPONENTS board_jc4827w543)
|
||||
endif()
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(autofilm)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
idf_component_register(SRCS "app_machine.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES ui_cmd app_process)
|
||||
REQUIRES ui_cmd app_process
|
||||
PRIV_REQUIRES hal_motor hal_temp hal_audio freertos)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "app_machine.h"
|
||||
#include "app_process.h"
|
||||
#include "hal_motor.h"
|
||||
@@ -5,6 +8,7 @@
|
||||
#include "hal_audio.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define AGITATE_RPM 60u
|
||||
|
||||
@@ -16,20 +20,13 @@ static uint32_t s_deadline_ms;
|
||||
static bool s_have_deadline;
|
||||
static bool s_resume_pending;
|
||||
static bool s_auto_advance;
|
||||
static ui_evt_t s_q[UI_EVT_QUEUE_LEN];
|
||||
static uint8_t s_q_head;
|
||||
static uint8_t s_q_tail;
|
||||
static uint8_t s_q_count;
|
||||
static QueueHandle_t s_evtq;
|
||||
|
||||
static void emit(ui_evt_t ev)
|
||||
{
|
||||
if (s_q_count == UI_EVT_QUEUE_LEN) {
|
||||
s_q_head = (uint8_t)((s_q_head + 1u) % UI_EVT_QUEUE_LEN);
|
||||
s_q_count--;
|
||||
if (s_evtq != NULL) {
|
||||
xQueueSend(s_evtq, &ev, 0);
|
||||
}
|
||||
s_q[s_q_tail] = ev;
|
||||
s_q_tail = (uint8_t)((s_q_tail + 1u) % UI_EVT_QUEUE_LEN);
|
||||
s_q_count++;
|
||||
}
|
||||
|
||||
static void fill_common(ui_evt_t *ev)
|
||||
@@ -122,14 +119,13 @@ static void enter_armed(void)
|
||||
emit_id(EVT_STEP_ARMED);
|
||||
}
|
||||
|
||||
static void start_running(uint32_t now_ms)
|
||||
static void start_running(void)
|
||||
{
|
||||
const process_def_t *p = app_process_get(s_proc);
|
||||
uint16_t t_s = app_process_time_s(s_proc, s_step);
|
||||
s_remaining_ms = (uint32_t)t_s * 1000u;
|
||||
s_deadline_ms = now_ms + s_remaining_ms;
|
||||
s_have_deadline = true;
|
||||
s_resume_pending = false;
|
||||
s_have_deadline = false;
|
||||
s_resume_pending = true;
|
||||
|
||||
if (t_s == 0 || p == NULL) {
|
||||
hal_motor_enable(false);
|
||||
@@ -198,10 +194,14 @@ void app_machine_init(void)
|
||||
s_deadline_ms = 0;
|
||||
s_have_deadline = false;
|
||||
s_resume_pending = false;
|
||||
s_auto_advance = false;
|
||||
s_q_head = 0;
|
||||
s_q_tail = 0;
|
||||
s_q_count = 0;
|
||||
s_auto_advance = true;
|
||||
|
||||
if (s_evtq == NULL) {
|
||||
s_evtq = xQueueCreate(UI_EVT_QUEUE_LEN, sizeof(ui_evt_t));
|
||||
} else {
|
||||
xQueueReset(s_evtq);
|
||||
}
|
||||
|
||||
app_process_init();
|
||||
hal_motor_init();
|
||||
hal_temp_init();
|
||||
@@ -230,13 +230,13 @@ uint32_t app_machine_remaining_ms(void)
|
||||
|
||||
int app_machine_last_event(ui_evt_t *out)
|
||||
{
|
||||
if (s_q_count == 0 || out == NULL) {
|
||||
if (s_evtq == NULL || out == NULL) {
|
||||
return 0;
|
||||
}
|
||||
*out = s_q[s_q_head];
|
||||
s_q_head = (uint8_t)((s_q_head + 1u) % UI_EVT_QUEUE_LEN);
|
||||
s_q_count--;
|
||||
if (xQueueReceive(s_evtq, out, 0) == pdTRUE) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void app_machine_on_temp(float c, bool ok)
|
||||
@@ -336,7 +336,7 @@ void app_machine_handle_cmd(const ui_cmd_t *cmd)
|
||||
enter_armed();
|
||||
} else if (s_state == ST_ARMED) {
|
||||
apply_step_index(cmd->step_index);
|
||||
start_running(0);
|
||||
start_running();
|
||||
} else if (s_state == ST_COMPLETE) {
|
||||
arm_or_next_from_complete();
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -0,0 +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);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
set(srcs)
|
||||
set(priv_req driver)
|
||||
|
||||
if(IDF_TARGET STREQUAL "esp32")
|
||||
list(APPEND srcs wroom/hal_audio.c)
|
||||
list(APPEND priv_req board_wroom)
|
||||
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||
list(APPEND srcs s3/hal_audio.c)
|
||||
list(APPEND priv_req board_jc4827w543)
|
||||
else()
|
||||
list(APPEND srcs stub/hal_audio.c)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES driver
|
||||
PRIV_REQUIRES ${priv_req})
|
||||
@@ -0,0 +1,212 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/i2s_std.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "board.h"
|
||||
|
||||
#define SPK_SAMPLE_RATE 48000
|
||||
#define TONE_HZ 2000
|
||||
#define TONE_PERIOD_SMP (SPK_SAMPLE_RATE / TONE_HZ) /* 24 samples */
|
||||
#define CHUNK_MS 10
|
||||
#define CHUNK_FRAMES (SPK_SAMPLE_RATE / 1000 * CHUNK_MS) /* 480, multiple of period */
|
||||
#define TONE_AMPLITUDE 12000
|
||||
#define DMA_DESC_NUM 4
|
||||
#define DMA_FRAME_NUM 240
|
||||
|
||||
static const char *TAG = "audio";
|
||||
|
||||
enum {
|
||||
AUDIO_SHORT = 1,
|
||||
AUDIO_ALARM,
|
||||
AUDIO_CANCEL,
|
||||
};
|
||||
|
||||
static QueueHandle_t s_q;
|
||||
static TaskHandle_t s_task;
|
||||
static i2s_chan_handle_t s_tx;
|
||||
static bool s_chan_on;
|
||||
static bool s_inited;
|
||||
|
||||
static int16_t s_tone[CHUNK_FRAMES * 2];
|
||||
static int16_t s_silence[CHUNK_FRAMES * 2];
|
||||
|
||||
static void chan_on(void)
|
||||
{
|
||||
if (!s_chan_on && s_tx != NULL) {
|
||||
i2s_channel_enable(s_tx);
|
||||
s_chan_on = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void chan_off(void)
|
||||
{
|
||||
if (s_chan_on && s_tx != NULL) {
|
||||
size_t w = 0;
|
||||
i2s_channel_write(s_tx, s_silence, sizeof(s_silence), &w, 50);
|
||||
i2s_channel_disable(s_tx);
|
||||
s_chan_on = false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool drain_cancel(void)
|
||||
{
|
||||
int msg;
|
||||
while (xQueueReceive(s_q, &msg, 0) == pdTRUE) {
|
||||
if (msg == AUDIO_CANCEL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Write buf for ~ms wall time. DMA pacing makes each 10 ms write block
|
||||
* in this task only; the UI path never touches I2S. */
|
||||
static bool play_buf_ms(const int16_t *buf, size_t len, uint32_t ms)
|
||||
{
|
||||
TickType_t t0 = xTaskGetTickCount();
|
||||
while ((xTaskGetTickCount() - t0) < pdMS_TO_TICKS(ms)) {
|
||||
if (drain_cancel()) {
|
||||
return false;
|
||||
}
|
||||
size_t w = 0;
|
||||
if (i2s_channel_write(s_tx, buf, len, &w, portMAX_DELAY) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void play_short(void)
|
||||
{
|
||||
chan_on();
|
||||
play_buf_ms(s_tone, sizeof(s_tone), 100);
|
||||
chan_off();
|
||||
}
|
||||
|
||||
static void play_alarm(void)
|
||||
{
|
||||
chan_on();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (!play_buf_ms(s_tone, sizeof(s_tone), 500)) {
|
||||
break;
|
||||
}
|
||||
if (!play_buf_ms(s_silence, sizeof(s_silence), 250)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
chan_off();
|
||||
}
|
||||
|
||||
static void audio_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
int msg;
|
||||
for (;;) {
|
||||
if (xQueueReceive(s_q, &msg, portMAX_DELAY) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
if (msg == AUDIO_SHORT) {
|
||||
play_short();
|
||||
} else if (msg == AUDIO_ALARM) {
|
||||
play_alarm();
|
||||
} else if (msg == AUDIO_CANCEL) {
|
||||
chan_off();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void post(int msg)
|
||||
{
|
||||
if (s_q == NULL) {
|
||||
return;
|
||||
}
|
||||
xQueueSend(s_q, &msg, 0);
|
||||
}
|
||||
|
||||
void autofilm_audio_task(void *arg)
|
||||
{
|
||||
audio_task(arg);
|
||||
}
|
||||
|
||||
void hal_audio_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
}
|
||||
gpio_num_t bclk = board_pin_spk_bclk();
|
||||
gpio_num_t lrclk = board_pin_spk_lrclk();
|
||||
gpio_num_t din = board_pin_spk_din();
|
||||
if (bclk == GPIO_NUM_NC || lrclk == GPIO_NUM_NC || din == GPIO_NUM_NC) {
|
||||
ESP_LOGW(TAG, "no speaker pins on this board");
|
||||
s_inited = true;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < CHUNK_FRAMES; i++) {
|
||||
int16_t v = ((i % TONE_PERIOD_SMP) < (TONE_PERIOD_SMP / 2))
|
||||
? TONE_AMPLITUDE : -TONE_AMPLITUDE;
|
||||
s_tone[i * 2] = v;
|
||||
s_tone[i * 2 + 1] = v;
|
||||
}
|
||||
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
|
||||
chan_cfg.dma_desc_num = DMA_DESC_NUM;
|
||||
chan_cfg.dma_frame_num = DMA_FRAME_NUM;
|
||||
if (i2s_new_channel(&chan_cfg, &s_tx, NULL) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_new_channel failed");
|
||||
s_inited = true;
|
||||
return;
|
||||
}
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SPK_SAMPLE_RATE),
|
||||
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
|
||||
I2S_SLOT_MODE_STEREO),
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = bclk,
|
||||
.ws = lrclk,
|
||||
.dout = din,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
if (i2s_channel_init_std_mode(s_tx, &std_cfg) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_channel_init_std_mode failed");
|
||||
i2s_del_channel(s_tx);
|
||||
s_tx = NULL;
|
||||
s_inited = true;
|
||||
return;
|
||||
}
|
||||
s_q = xQueueCreate(8, sizeof(int));
|
||||
if (s_task == NULL) {
|
||||
xTaskCreate(audio_task, "audio", 3072, NULL, 2, &s_task);
|
||||
}
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "init i2s bclk=%d ws=%d dout=%d", bclk, lrclk, din);
|
||||
}
|
||||
|
||||
void hal_audio_beep_short(void)
|
||||
{
|
||||
post(AUDIO_SHORT);
|
||||
}
|
||||
|
||||
void hal_audio_alarm_complete(void)
|
||||
{
|
||||
post(AUDIO_ALARM);
|
||||
}
|
||||
|
||||
void hal_audio_alarm_cancel(void)
|
||||
{
|
||||
post(AUDIO_CANCEL);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
void hal_audio_init(void)
|
||||
{
|
||||
}
|
||||
|
||||
void hal_audio_beep_short(void)
|
||||
{
|
||||
}
|
||||
|
||||
void hal_audio_alarm_complete(void)
|
||||
{
|
||||
}
|
||||
|
||||
void hal_audio_alarm_cancel(void)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#define PIN_BEEP 25
|
||||
#define LEDC_TIMER LEDC_TIMER_0
|
||||
#define LEDC_MODE LEDC_LOW_SPEED_MODE
|
||||
#define LEDC_CHANNEL LEDC_CHANNEL_0
|
||||
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT
|
||||
#define LEDC_DUTY 4095
|
||||
|
||||
static const char *TAG = "audio";
|
||||
|
||||
enum {
|
||||
AUDIO_SHORT = 1,
|
||||
AUDIO_ALARM,
|
||||
AUDIO_CANCEL,
|
||||
};
|
||||
|
||||
static QueueHandle_t s_q;
|
||||
static TaskHandle_t s_task;
|
||||
static bool s_inited;
|
||||
|
||||
static void tone_on(uint32_t hz)
|
||||
{
|
||||
ledc_set_freq(LEDC_MODE, LEDC_TIMER, hz);
|
||||
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, LEDC_DUTY);
|
||||
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
|
||||
}
|
||||
|
||||
static void tone_off(void)
|
||||
{
|
||||
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 0);
|
||||
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
|
||||
}
|
||||
|
||||
static bool drain_cancel(void)
|
||||
{
|
||||
int msg;
|
||||
while (xQueueReceive(s_q, &msg, 0) == pdTRUE) {
|
||||
if (msg == AUDIO_CANCEL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void play_short(void)
|
||||
{
|
||||
tone_on(2000);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
tone_off();
|
||||
}
|
||||
|
||||
static void play_alarm(void)
|
||||
{
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (drain_cancel()) {
|
||||
tone_off();
|
||||
return;
|
||||
}
|
||||
tone_on(2000);
|
||||
TickType_t t0 = xTaskGetTickCount();
|
||||
while ((xTaskGetTickCount() - t0) < pdMS_TO_TICKS(500)) {
|
||||
if (drain_cancel()) {
|
||||
tone_off();
|
||||
return;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
tone_off();
|
||||
t0 = xTaskGetTickCount();
|
||||
while ((xTaskGetTickCount() - t0) < pdMS_TO_TICKS(250)) {
|
||||
if (drain_cancel()) {
|
||||
return;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void audio_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
int msg;
|
||||
for (;;) {
|
||||
if (xQueueReceive(s_q, &msg, portMAX_DELAY) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
if (msg == AUDIO_SHORT) {
|
||||
play_short();
|
||||
} else if (msg == AUDIO_ALARM) {
|
||||
play_alarm();
|
||||
} else if (msg == AUDIO_CANCEL) {
|
||||
tone_off();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void post(int msg)
|
||||
{
|
||||
if (s_q == NULL) {
|
||||
return;
|
||||
}
|
||||
xQueueSend(s_q, &msg, 0);
|
||||
}
|
||||
|
||||
void autofilm_audio_task(void *arg)
|
||||
{
|
||||
audio_task(arg);
|
||||
}
|
||||
|
||||
void hal_audio_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
}
|
||||
ledc_timer_config_t tcfg = {
|
||||
.speed_mode = LEDC_MODE,
|
||||
.duty_resolution = LEDC_DUTY_RES,
|
||||
.timer_num = LEDC_TIMER,
|
||||
.freq_hz = 2000,
|
||||
.clk_cfg = LEDC_AUTO_CLK,
|
||||
};
|
||||
ledc_timer_config(&tcfg);
|
||||
ledc_channel_config_t ccfg = {
|
||||
.gpio_num = PIN_BEEP,
|
||||
.speed_mode = LEDC_MODE,
|
||||
.channel = LEDC_CHANNEL,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.timer_sel = LEDC_TIMER,
|
||||
.duty = 0,
|
||||
.hpoint = 0,
|
||||
};
|
||||
ledc_channel_config(&ccfg);
|
||||
tone_off();
|
||||
s_q = xQueueCreate(8, sizeof(int));
|
||||
if (s_task == NULL) {
|
||||
xTaskCreate(audio_task, "audio", 3072, NULL, 2, &s_task);
|
||||
}
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "init beep pin=%d LEDC", PIN_BEEP);
|
||||
}
|
||||
|
||||
void hal_audio_beep_short(void)
|
||||
{
|
||||
post(AUDIO_SHORT);
|
||||
}
|
||||
|
||||
void hal_audio_alarm_complete(void)
|
||||
{
|
||||
post(AUDIO_ALARM);
|
||||
}
|
||||
|
||||
void hal_audio_alarm_cancel(void)
|
||||
{
|
||||
post(AUDIO_CANCEL);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
set(srcs)
|
||||
set(priv_inc)
|
||||
set(priv_req driver)
|
||||
|
||||
if(IDF_TARGET STREQUAL "esp32")
|
||||
list(APPEND srcs hal_display_lcd2004.c)
|
||||
list(APPEND priv_req board_wroom)
|
||||
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||
list(APPEND srcs hal_display_nv3041a.c)
|
||||
list(APPEND priv_req board_jc4827w543 esp_lcd)
|
||||
else()
|
||||
list(APPEND srcs stub/hal_display.c)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ${priv_inc}
|
||||
REQUIRES driver
|
||||
PRIV_REQUIRES ${priv_req})
|
||||
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
/* Public-domain 5x7 glyphs for ASCII 32..126. */
|
||||
static const uint8_t font5x7[95][7] = {
|
||||
{0,0,0,0,0,0,0},
|
||||
{0x04,0x04,0x04,0x04,0x04,0x00,0x04},
|
||||
{0x0A,0x0A,0x0A,0,0,0,0},
|
||||
{0x0A,0x0A,0x1F,0x0A,0x1F,0x0A,0x0A},
|
||||
{0x04,0x0F,0x14,0x0E,0x05,0x1E,0x04},
|
||||
{0x18,0x19,0x02,0x04,0x08,0x13,0x03},
|
||||
{0x08,0x14,0x14,0x08,0x15,0x12,0x0D},
|
||||
{0x04,0x04,0x08,0,0,0,0},
|
||||
{0x02,0x04,0x08,0x08,0x08,0x04,0x02},
|
||||
{0x08,0x04,0x02,0x02,0x02,0x04,0x08},
|
||||
{0x00,0x04,0x15,0x0E,0x15,0x04,0x00},
|
||||
{0x00,0x04,0x04,0x1F,0x04,0x04,0x00},
|
||||
{0,0,0,0,0,0x04,0x08},
|
||||
{0,0,0,0x1F,0,0,0},
|
||||
{0,0,0,0,0,0,0x04},
|
||||
{0x01,0x02,0x04,0x08,0x10,0,0},
|
||||
{0x0E,0x11,0x13,0x15,0x19,0x11,0x0E},
|
||||
{0x04,0x0C,0x04,0x04,0x04,0x04,0x0E},
|
||||
{0x0E,0x11,0x01,0x02,0x04,0x08,0x1F},
|
||||
{0x1F,0x02,0x04,0x02,0x01,0x11,0x0E},
|
||||
{0x02,0x06,0x0A,0x12,0x1F,0x02,0x02},
|
||||
{0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E},
|
||||
{0x06,0x08,0x10,0x1E,0x11,0x11,0x0E},
|
||||
{0x1F,0x01,0x02,0x04,0x08,0x08,0x08},
|
||||
{0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E},
|
||||
{0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C},
|
||||
{0,0x04,0,0,0x04,0,0},
|
||||
{0,0x04,0,0,0x04,0x04,0x08},
|
||||
{0x02,0x04,0x08,0x10,0x08,0x04,0x02},
|
||||
{0,0,0x1F,0,0x1F,0,0},
|
||||
{0x08,0x04,0x02,0x01,0x02,0x04,0x08},
|
||||
{0x0E,0x11,0x01,0x02,0x04,0x00,0x04},
|
||||
{0x0E,0x11,0x17,0x15,0x17,0x10,0x0E},
|
||||
{0x0E,0x11,0x11,0x1F,0x11,0x11,0x11},
|
||||
{0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E},
|
||||
{0x0E,0x11,0x10,0x10,0x10,0x11,0x0E},
|
||||
{0x1C,0x12,0x11,0x11,0x11,0x12,0x1C},
|
||||
{0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F},
|
||||
{0x1F,0x10,0x10,0x1E,0x10,0x10,0x10},
|
||||
{0x0E,0x11,0x10,0x17,0x11,0x11,0x0F},
|
||||
{0x11,0x11,0x11,0x1F,0x11,0x11,0x11},
|
||||
{0x0E,0x04,0x04,0x04,0x04,0x04,0x0E},
|
||||
{0x07,0x02,0x02,0x02,0x02,0x12,0x0C},
|
||||
{0x11,0x12,0x14,0x18,0x14,0x12,0x11},
|
||||
{0x10,0x10,0x10,0x10,0x10,0x10,0x1F},
|
||||
{0x11,0x1B,0x15,0x15,0x11,0x11,0x11},
|
||||
{0x11,0x19,0x15,0x13,0x11,0x11,0x11},
|
||||
{0x0E,0x11,0x11,0x11,0x11,0x11,0x0E},
|
||||
{0x1E,0x11,0x11,0x1E,0x10,0x10,0x10},
|
||||
{0x0E,0x11,0x11,0x11,0x15,0x12,0x0D},
|
||||
{0x1E,0x11,0x11,0x1E,0x14,0x12,0x11},
|
||||
{0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E},
|
||||
{0x1F,0x04,0x04,0x04,0x04,0x04,0x04},
|
||||
{0x11,0x11,0x11,0x11,0x11,0x11,0x0E},
|
||||
{0x11,0x11,0x11,0x11,0x11,0x0A,0x04},
|
||||
{0x11,0x11,0x11,0x15,0x15,0x1B,0x11},
|
||||
{0x11,0x11,0x0A,0x04,0x0A,0x11,0x11},
|
||||
{0x11,0x11,0x0A,0x04,0x04,0x04,0x04},
|
||||
{0x1F,0x01,0x02,0x04,0x08,0x10,0x1F},
|
||||
{0x0E,0x08,0x08,0x08,0x08,0x08,0x0E},
|
||||
{0x10,0x08,0x04,0x02,0x01,0,0},
|
||||
{0x0E,0x02,0x02,0x02,0x02,0x02,0x0E},
|
||||
{0x04,0x0A,0x11,0,0,0,0},
|
||||
{0,0,0,0,0,0,0x1F},
|
||||
{0x08,0x04,0x02,0,0,0,0},
|
||||
{0,0,0x0E,0x01,0x0F,0x11,0x0F},
|
||||
{0x10,0x10,0x1E,0x11,0x11,0x11,0x1E},
|
||||
{0,0,0x0E,0x10,0x10,0x11,0x0E},
|
||||
{0x01,0x01,0x0F,0x11,0x11,0x11,0x0F},
|
||||
{0,0,0x0E,0x11,0x1F,0x10,0x0E},
|
||||
{0x06,0x08,0x08,0x1E,0x08,0x08,0x08},
|
||||
{0,0,0x0F,0x11,0x11,0x0F,0x01,},
|
||||
{0x10,0x10,0x1E,0x11,0x11,0x11,0x11},
|
||||
{0x04,0,0x0C,0x04,0x04,0x04,0x0E},
|
||||
{0x02,0,0x06,0x02,0x02,0x02,0x12},
|
||||
{0x10,0x10,0x12,0x14,0x18,0x14,0x12},
|
||||
{0x0C,0x04,0x04,0x04,0x04,0x04,0x0E},
|
||||
{0,0,0x1A,0x15,0x15,0x15,0x15},
|
||||
{0,0,0x1E,0x11,0x11,0x11,0x11},
|
||||
{0,0,0x0E,0x11,0x11,0x11,0x0E},
|
||||
{0,0,0x1E,0x11,0x11,0x1E,0x10},
|
||||
{0,0,0x0F,0x11,0x11,0x0F,0x01},
|
||||
{0,0,0x16,0x19,0x10,0x10,0x10},
|
||||
{0,0,0x0F,0x10,0x0E,0x01,0x1E},
|
||||
{0x08,0x08,0x1E,0x08,0x08,0x08,0x06},
|
||||
{0,0,0x11,0x11,0x11,0x11,0x0F},
|
||||
{0,0,0x11,0x11,0x11,0x0A,0x04},
|
||||
{0,0,0x11,0x15,0x15,0x15,0x0A},
|
||||
{0,0,0x11,0x0A,0x04,0x0A,0x11},
|
||||
{0,0,0x11,0x11,0x0F,0x01,0x0E},
|
||||
{0,0,0x1F,0x02,0x04,0x08,0x1F},
|
||||
{0x02,0x04,0x04,0x08,0x04,0x04,0x02},
|
||||
{0x04,0x04,0x04,0x04,0x04,0x04,0x04},
|
||||
{0x08,0x04,0x04,0x02,0x04,0x04,0x08},
|
||||
{0x00,0x00,0x08,0x15,0x02,0x00,0x00},
|
||||
};
|
||||
@@ -0,0 +1,190 @@
|
||||
#include "hal_display.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "driver/i2c_master.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define LCD_ADDR 0x27
|
||||
#define LCD_COLS 20
|
||||
#define LCD_ROWS 4
|
||||
#define PIN_SDA 21
|
||||
#define PIN_SCL 22
|
||||
|
||||
#define LCD_RS 0x01
|
||||
#define LCD_EN 0x04
|
||||
#define LCD_BL 0x08
|
||||
|
||||
static const char *TAG = "display";
|
||||
|
||||
static i2c_master_bus_handle_t s_bus;
|
||||
static i2c_master_dev_handle_t s_dev;
|
||||
static bool s_inited;
|
||||
|
||||
static const uint8_t s_thermo[8] = {
|
||||
0b00100,
|
||||
0b01100,
|
||||
0b00100,
|
||||
0b01100,
|
||||
0b00100,
|
||||
0b01110,
|
||||
0b01110,
|
||||
0b01110,
|
||||
};
|
||||
|
||||
static const uint8_t s_row_off[4] = {0x00, 0x40, 0x14, 0x54};
|
||||
|
||||
static void i2c_write(uint8_t b)
|
||||
{
|
||||
if (s_dev == NULL) {
|
||||
return;
|
||||
}
|
||||
i2c_master_transmit(s_dev, &b, 1, 50);
|
||||
}
|
||||
|
||||
static void pulse(uint8_t data)
|
||||
{
|
||||
i2c_write((uint8_t)(data | LCD_EN | LCD_BL));
|
||||
esp_rom_delay_us(1);
|
||||
i2c_write((uint8_t)((data & (uint8_t)~LCD_EN) | LCD_BL));
|
||||
esp_rom_delay_us(50);
|
||||
}
|
||||
|
||||
static void write4(uint8_t nibble, uint8_t rs)
|
||||
{
|
||||
uint8_t data = (uint8_t)((nibble & 0xF0) | rs | LCD_BL);
|
||||
i2c_write(data);
|
||||
pulse(data);
|
||||
}
|
||||
|
||||
static void send(uint8_t value, uint8_t rs)
|
||||
{
|
||||
write4(value, rs);
|
||||
write4((uint8_t)(value << 4), rs);
|
||||
}
|
||||
|
||||
static void cmd(uint8_t c)
|
||||
{
|
||||
send(c, 0);
|
||||
if (c == 0x01 || c == 0x02) {
|
||||
vTaskDelay(pdMS_TO_TICKS(2));
|
||||
}
|
||||
}
|
||||
|
||||
static void data_byte(uint8_t d)
|
||||
{
|
||||
send(d, LCD_RS);
|
||||
}
|
||||
|
||||
static void set_ddram(uint8_t addr)
|
||||
{
|
||||
cmd((uint8_t)(0x80 | addr));
|
||||
}
|
||||
|
||||
void hal_display_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
}
|
||||
i2c_master_bus_config_t bus_cfg = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.sda_io_num = PIN_SDA,
|
||||
.scl_io_num = PIN_SCL,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.flags = {
|
||||
.enable_internal_pullup = true,
|
||||
},
|
||||
};
|
||||
if (i2c_new_master_bus(&bus_cfg, &s_bus) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2c bus failed");
|
||||
return;
|
||||
}
|
||||
i2c_device_config_t dev_cfg = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = LCD_ADDR,
|
||||
.scl_speed_hz = 100000,
|
||||
};
|
||||
if (i2c_master_bus_add_device(s_bus, &dev_cfg, &s_dev) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2c device failed");
|
||||
return;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
write4(0x30, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(5));
|
||||
write4(0x30, 0);
|
||||
esp_rom_delay_us(150);
|
||||
write4(0x30, 0);
|
||||
write4(0x20, 0);
|
||||
cmd(0x28); /* 4-bit, 2 line */
|
||||
cmd(0x08);
|
||||
cmd(0x01);
|
||||
cmd(0x06);
|
||||
cmd(0x0C);
|
||||
|
||||
cmd(0x40); /* CGRAM 0 */
|
||||
for (int i = 0; i < 8; i++) {
|
||||
data_byte(s_thermo[i]);
|
||||
}
|
||||
cmd(0x01);
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "init LCD 20x4 addr=0x27 sda=21 scl=22 IDF I2C");
|
||||
}
|
||||
|
||||
void hal_display_clear(void)
|
||||
{
|
||||
if (!s_inited) {
|
||||
return;
|
||||
}
|
||||
cmd(0x01);
|
||||
}
|
||||
|
||||
void hal_display_text(int col, int row, const char *s)
|
||||
{
|
||||
if (!s_inited || s == NULL) {
|
||||
return;
|
||||
}
|
||||
if (row < 0 || row >= LCD_ROWS) {
|
||||
return;
|
||||
}
|
||||
if (col < 0) {
|
||||
col = 0;
|
||||
}
|
||||
if (col >= LCD_COLS) {
|
||||
return;
|
||||
}
|
||||
set_ddram((uint8_t)(s_row_off[row] + col));
|
||||
int room = LCD_COLS - col;
|
||||
int n = 0;
|
||||
while (s[n] != '\0' && n < room) {
|
||||
data_byte((uint8_t)s[n]);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
void hal_display_glyph_thermo(int col, int row)
|
||||
{
|
||||
if (!s_inited) {
|
||||
return;
|
||||
}
|
||||
if (row < 0 || row >= LCD_ROWS || col < 0 || col >= LCD_COLS) {
|
||||
return;
|
||||
}
|
||||
set_ddram((uint8_t)(s_row_off[row] + col));
|
||||
data_byte(0);
|
||||
}
|
||||
|
||||
void hal_display_flush(void)
|
||||
{
|
||||
}
|
||||
|
||||
void hal_display_softkey(int slot, int slots, const char *label)
|
||||
{
|
||||
(void)slot;
|
||||
(void)slots;
|
||||
(void)label;
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
#include "hal_display.h"
|
||||
#include "font5x7.h"
|
||||
#include "board.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_lcd_panel_commands.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define LCD_HOST SPI2_HOST
|
||||
#define LCD_H HAL_DISPLAY_PX_H
|
||||
#define LCD_W HAL_DISPLAY_PX_W
|
||||
|
||||
#define COL_BG 0x18E3
|
||||
#define COL_FG 0xFFFF
|
||||
#define COL_ACC 0xF800
|
||||
#define COL_BTN 0x39E7
|
||||
#define COL_BTNT 0xFFFF
|
||||
|
||||
static const char *TAG = "disp-s3";
|
||||
|
||||
static esp_lcd_panel_io_handle_t s_io;
|
||||
static uint16_t *s_fb;
|
||||
static bool s_inited;
|
||||
static bool s_dirty;
|
||||
|
||||
static uint16_t swap565(uint16_t c)
|
||||
{
|
||||
return (uint16_t)((c >> 8) | (c << 8));
|
||||
}
|
||||
|
||||
static void fb_fill(uint16_t color)
|
||||
{
|
||||
if (s_fb == NULL) {
|
||||
return;
|
||||
}
|
||||
uint16_t c = swap565(color);
|
||||
for (int i = 0; i < LCD_W * LCD_H; i++) {
|
||||
s_fb[i] = c;
|
||||
}
|
||||
s_dirty = true;
|
||||
}
|
||||
|
||||
static void fb_pixel(int x, int y, uint16_t color)
|
||||
{
|
||||
if (s_fb == NULL || x < 0 || y < 0 || x >= LCD_W || y >= LCD_H) {
|
||||
return;
|
||||
}
|
||||
s_fb[y * LCD_W + x] = swap565(color);
|
||||
}
|
||||
|
||||
static void fb_fill_rect(int x, int y, int w, int h, uint16_t color)
|
||||
{
|
||||
if (s_fb == NULL) {
|
||||
return;
|
||||
}
|
||||
if (x < 0) {
|
||||
w += x;
|
||||
x = 0;
|
||||
}
|
||||
if (y < 0) {
|
||||
h += y;
|
||||
y = 0;
|
||||
}
|
||||
if (x + w > LCD_W) {
|
||||
w = LCD_W - x;
|
||||
}
|
||||
if (y + h > LCD_H) {
|
||||
h = LCD_H - y;
|
||||
}
|
||||
uint16_t c = swap565(color);
|
||||
for (int yy = 0; yy < h; yy++) {
|
||||
uint16_t *row = &s_fb[(y + yy) * LCD_W + x];
|
||||
for (int xx = 0; xx < w; xx++) {
|
||||
row[xx] = c;
|
||||
}
|
||||
}
|
||||
s_dirty = true;
|
||||
}
|
||||
|
||||
static void fb_char(int x, int y, char ch, uint16_t fg, int scale)
|
||||
{
|
||||
if (ch < 32 || ch > 126) {
|
||||
ch = '?';
|
||||
}
|
||||
const uint8_t *g = font5x7[ch - 32];
|
||||
for (int row = 0; row < 7; row++) {
|
||||
uint8_t bits = g[row];
|
||||
for (int col = 0; col < 5; col++) {
|
||||
if (bits & (0x10 >> col)) {
|
||||
fb_fill_rect(x + col * scale, y + row * scale, scale, scale, fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fb_str(int x, int y, const char *s, uint16_t fg, int scale)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
int cx = x;
|
||||
while (*s) {
|
||||
fb_char(cx, y, *s, fg, scale);
|
||||
cx += 6 * scale;
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
static void lcd_cmd(uint8_t cmd, const uint8_t *data, size_t len)
|
||||
{
|
||||
if (s_io == NULL) {
|
||||
return;
|
||||
}
|
||||
uint32_t packed = ((uint32_t)0x02 << 24) | ((uint32_t)cmd << 8);
|
||||
esp_lcd_panel_io_tx_param(s_io, packed, data, len);
|
||||
}
|
||||
|
||||
static void lcd_window(int x0, int y0, int x1, int y1)
|
||||
{
|
||||
uint8_t cas[4] = { (uint8_t)(x0 >> 8), (uint8_t)x0, (uint8_t)(x1 >> 8), (uint8_t)x1 };
|
||||
uint8_t ras[4] = { (uint8_t)(y0 >> 8), (uint8_t)y0, (uint8_t)(y1 >> 8), (uint8_t)y1 };
|
||||
lcd_cmd(LCD_CMD_CASET, cas, 4);
|
||||
lcd_cmd(LCD_CMD_RASET, ras, 4);
|
||||
}
|
||||
|
||||
static void lcd_init_panel(void)
|
||||
{
|
||||
/* Vendor bring-up for NV3041A-01 480x272. Commands are public datasheet/community. */
|
||||
static const uint8_t seq[][3] = {
|
||||
{0xFF, 0xA5, 1},
|
||||
{0x36, 0x00, 1},
|
||||
{0x3A, 0x01, 1},
|
||||
{0x41, 0x03, 1},
|
||||
{0x44, 0x15, 1},
|
||||
{0x45, 0x15, 1},
|
||||
{0x7D, 0x03, 1},
|
||||
{0xC1, 0xBB, 1},
|
||||
{0xC2, 0x05, 1},
|
||||
{0xC3, 0x10, 1},
|
||||
{0xC6, 0x3E, 1},
|
||||
{0xC7, 0x25, 1},
|
||||
{0xC8, 0x11, 1},
|
||||
{0xC9, 0x20, 1},
|
||||
{0x7A, 0x51, 1},
|
||||
{0x6F, 0x2D, 1},
|
||||
{0x78, 0x4B, 1},
|
||||
{0x70, 0x07, 1},
|
||||
{0x71, 0x05, 1},
|
||||
};
|
||||
lcd_cmd(LCD_CMD_SWRESET, NULL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(120));
|
||||
lcd_cmd(LCD_CMD_SLPOUT, NULL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(120));
|
||||
for (size_t i = 0; i < sizeof(seq) / sizeof(seq[0]); i++) {
|
||||
uint8_t d = seq[i][1];
|
||||
lcd_cmd(seq[i][0], &d, seq[i][2]);
|
||||
}
|
||||
lcd_cmd(LCD_CMD_DISPON, NULL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
}
|
||||
|
||||
void hal_display_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
}
|
||||
|
||||
gpio_config_t bl = {
|
||||
.pin_bit_mask = 1ULL << board_pin_lcd_bl(),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&bl);
|
||||
gpio_set_level(board_pin_lcd_bl(), 1);
|
||||
|
||||
spi_bus_config_t buscfg = {
|
||||
.sclk_io_num = board_pin_lcd_sck(),
|
||||
.data0_io_num = board_pin_lcd_d0(),
|
||||
.data1_io_num = board_pin_lcd_d1(),
|
||||
.data2_io_num = board_pin_lcd_d2(),
|
||||
.data3_io_num = board_pin_lcd_d3(),
|
||||
.max_transfer_sz = LCD_W * 40 * sizeof(uint16_t),
|
||||
};
|
||||
esp_err_t err = spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "spi_bus_initialize %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
esp_lcd_panel_io_spi_config_t io_config = {
|
||||
.cs_gpio_num = board_pin_lcd_cs(),
|
||||
.dc_gpio_num = -1,
|
||||
.spi_mode = 0,
|
||||
.pclk_hz = 40 * 1000 * 1000,
|
||||
.trans_queue_depth = 4,
|
||||
.lcd_cmd_bits = 32,
|
||||
.lcd_param_bits = 8,
|
||||
.flags = {
|
||||
.quad_mode = 1,
|
||||
},
|
||||
};
|
||||
err = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &s_io);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "panel_io %s", esp_err_to_name(err));
|
||||
s_io = NULL;
|
||||
} else {
|
||||
lcd_init_panel();
|
||||
}
|
||||
|
||||
s_fb = heap_caps_malloc((size_t)LCD_W * LCD_H * sizeof(uint16_t),
|
||||
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if (s_fb == NULL) {
|
||||
s_fb = heap_caps_malloc((size_t)LCD_W * LCD_H * sizeof(uint16_t), MALLOC_CAP_8BIT);
|
||||
}
|
||||
if (s_fb == NULL) {
|
||||
ESP_LOGE(TAG, "no framebuffer");
|
||||
}
|
||||
fb_fill(COL_BG);
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "NV3041A QSPI %dx%d CS=%d SCK=%d D0=%d",
|
||||
LCD_W, LCD_H, (int)board_pin_lcd_cs(), (int)board_pin_lcd_sck(),
|
||||
(int)board_pin_lcd_d0());
|
||||
}
|
||||
|
||||
void hal_display_clear(void)
|
||||
{
|
||||
fb_fill(COL_BG);
|
||||
}
|
||||
|
||||
void hal_display_text(int col, int row, const char *s)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
if (col < 0) {
|
||||
col = 0;
|
||||
}
|
||||
if (row < 0 || row >= HAL_DISPLAY_ROWS) {
|
||||
return;
|
||||
}
|
||||
int x = col * HAL_DISPLAY_CELL_W + 4;
|
||||
int y = 8 + row * HAL_DISPLAY_CELL_H;
|
||||
/* clip 20 columns */
|
||||
char buf[HAL_DISPLAY_COLS + 1];
|
||||
int n = 0;
|
||||
while (s[n] && n < HAL_DISPLAY_COLS - col && n < HAL_DISPLAY_COLS) {
|
||||
buf[n] = s[n];
|
||||
n++;
|
||||
}
|
||||
buf[n] = 0;
|
||||
fb_fill_rect(x, y, n * HAL_DISPLAY_CELL_W, HAL_DISPLAY_CELL_H - 4, COL_BG);
|
||||
fb_str(x, y + 6, buf, COL_FG, 3);
|
||||
}
|
||||
|
||||
void hal_display_glyph_thermo(int col, int row)
|
||||
{
|
||||
int x = col * HAL_DISPLAY_CELL_W + 8;
|
||||
int y = 8 + row * HAL_DISPLAY_CELL_H + 4;
|
||||
fb_fill_rect(x + 6, y, 6, 14, COL_ACC);
|
||||
fb_fill_rect(x + 3, y + 14, 12, 10, COL_ACC);
|
||||
}
|
||||
|
||||
void hal_display_softkey(int slot, int slots, const char *label)
|
||||
{
|
||||
if (slots < 1) {
|
||||
slots = 1;
|
||||
}
|
||||
if (slot < 0 || slot >= slots) {
|
||||
return;
|
||||
}
|
||||
int w = LCD_W / slots;
|
||||
int x = slot * w + 4;
|
||||
int y = LCD_H - HAL_DISPLAY_SOFTKEY_H + 4;
|
||||
int bw = w - 8;
|
||||
int bh = HAL_DISPLAY_SOFTKEY_H - 8;
|
||||
fb_fill_rect(x, y, bw, bh, COL_BTN);
|
||||
if (label != NULL) {
|
||||
int scale = 3;
|
||||
int tw = (int)strlen(label) * 6 * scale;
|
||||
int tx = x + (bw - tw) / 2;
|
||||
int ty = y + (bh - 7 * scale) / 2;
|
||||
if (tx < x) {
|
||||
tx = x + 4;
|
||||
}
|
||||
fb_str(tx, ty, label, COL_BTNT, scale);
|
||||
}
|
||||
}
|
||||
|
||||
void hal_display_flush(void)
|
||||
{
|
||||
if (!s_dirty || s_fb == NULL || s_io == NULL) {
|
||||
s_dirty = false;
|
||||
return;
|
||||
}
|
||||
lcd_window(0, 0, LCD_W - 1, LCD_H - 1);
|
||||
const int chunk_rows = 16;
|
||||
for (int y = 0; y < LCD_H; y += chunk_rows) {
|
||||
int rows = chunk_rows;
|
||||
if (y + rows > LCD_H) {
|
||||
rows = LCD_H - y;
|
||||
}
|
||||
uint32_t packed = ((uint32_t)0x32 << 24) | ((uint32_t)LCD_CMD_RAMWR << 8);
|
||||
esp_lcd_panel_io_tx_color(s_io, packed, &s_fb[y * LCD_W],
|
||||
(size_t)rows * LCD_W * sizeof(uint16_t));
|
||||
}
|
||||
s_dirty = false;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define HAL_DISPLAY_COLS 20
|
||||
#define HAL_DISPLAY_ROWS 4
|
||||
|
||||
/* Colour panel geometry (S3). 2004 ignores pixel APIs. */
|
||||
#define HAL_DISPLAY_PX_W 480
|
||||
#define HAL_DISPLAY_PX_H 272
|
||||
#define HAL_DISPLAY_CELL_W 24
|
||||
#define HAL_DISPLAY_CELL_H 32
|
||||
#define HAL_DISPLAY_SOFTKEY_H 48
|
||||
|
||||
void hal_display_init(void);
|
||||
void hal_display_clear(void);
|
||||
void hal_display_text(int col, int row, const char *s); /* clip to 20 cols */
|
||||
void hal_display_glyph_thermo(int col, int row);
|
||||
void hal_display_flush(void);
|
||||
|
||||
/* Softkey chrome; no-op on 2004. slot in [0, slots). */
|
||||
void hal_display_softkey(int slot, int slots, const char *label);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "hal_display.h"
|
||||
|
||||
void hal_display_init(void) {}
|
||||
void hal_display_clear(void) {}
|
||||
void hal_display_text(int col, int row, const char *s)
|
||||
{
|
||||
(void)col;
|
||||
(void)row;
|
||||
(void)s;
|
||||
}
|
||||
void hal_display_glyph_thermo(int col, int row)
|
||||
{
|
||||
(void)col;
|
||||
(void)row;
|
||||
}
|
||||
void hal_display_flush(void) {}
|
||||
|
||||
void hal_display_softkey(int slot, int slots, const char *label)
|
||||
{
|
||||
(void)slot;
|
||||
(void)slots;
|
||||
(void)label;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
set(srcs)
|
||||
set(priv_inc)
|
||||
set(priv_req driver)
|
||||
|
||||
if(IDF_TARGET STREQUAL "esp32")
|
||||
list(APPEND srcs hal_input_keypad.c)
|
||||
list(APPEND priv_req board_wroom)
|
||||
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||
list(APPEND srcs hal_input_gt911.c)
|
||||
list(APPEND priv_req board_jc4827w543)
|
||||
else()
|
||||
list(APPEND srcs stub/hal_input.c)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ${priv_inc}
|
||||
REQUIRES driver
|
||||
PRIV_REQUIRES ${priv_req})
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "hal_input.h"
|
||||
#include <string.h>
|
||||
#include "board.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define TP_I2C I2C_NUM_0
|
||||
#define TP_ADDR 0x5D
|
||||
#define GT_REG_STAT 0x814E
|
||||
#define GT_REG_PT0 0x8150
|
||||
|
||||
static const char *TAG = "gt911";
|
||||
|
||||
static bool s_have;
|
||||
static ui_raw_touch_t s_last;
|
||||
static uint8_t s_prev_stat;
|
||||
|
||||
static esp_err_t tp_write(uint16_t reg, const uint8_t *data, size_t n)
|
||||
{
|
||||
uint8_t buf[8];
|
||||
if (n + 2 > sizeof(buf)) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
buf[0] = (uint8_t)(reg >> 8);
|
||||
buf[1] = (uint8_t)reg;
|
||||
if (n && data) {
|
||||
memcpy(buf + 2, data, n);
|
||||
}
|
||||
return i2c_master_write_to_device(TP_I2C, TP_ADDR, buf, n + 2, pdMS_TO_TICKS(50));
|
||||
}
|
||||
|
||||
static esp_err_t tp_read(uint16_t reg, uint8_t *data, size_t n)
|
||||
{
|
||||
uint8_t addr[2] = { (uint8_t)(reg >> 8), (uint8_t)reg };
|
||||
return i2c_master_write_read_device(TP_I2C, TP_ADDR, addr, 2, data, n, pdMS_TO_TICKS(50));
|
||||
}
|
||||
|
||||
void hal_input_init(void)
|
||||
{
|
||||
gpio_config_t io = {
|
||||
.pin_bit_mask = (1ULL << board_pin_tp_rst()) | (1ULL << board_pin_tp_int()),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io);
|
||||
gpio_set_level(board_pin_tp_int(), 0);
|
||||
gpio_set_level(board_pin_tp_rst(), 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
gpio_set_level(board_pin_tp_rst(), 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
io.mode = GPIO_MODE_INPUT;
|
||||
io.pin_bit_mask = 1ULL << board_pin_tp_int();
|
||||
gpio_config(&io);
|
||||
|
||||
i2c_config_t conf = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = board_pin_tp_sda(),
|
||||
.scl_io_num = board_pin_tp_scl(),
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = 400000,
|
||||
};
|
||||
i2c_param_config(TP_I2C, &conf);
|
||||
i2c_driver_install(TP_I2C, conf.mode, 0, 0, 0);
|
||||
ESP_LOGI(TAG, "GT911 addr=0x5D SDA=%d SCL=%d RST=%d INT=%d",
|
||||
(int)board_pin_tp_sda(), (int)board_pin_tp_scl(),
|
||||
(int)board_pin_tp_rst(), (int)board_pin_tp_int());
|
||||
}
|
||||
|
||||
bool hal_input_pop_key(ui_raw_key_t *out)
|
||||
{
|
||||
(void)out;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hal_input_pop_touch(ui_raw_touch_t *out)
|
||||
{
|
||||
uint8_t stat = 0;
|
||||
if (tp_read(GT_REG_STAT, &stat, 1) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
if ((stat & 0x80) == 0) {
|
||||
return false;
|
||||
}
|
||||
uint8_t n = stat & 0x0F;
|
||||
uint8_t raw[8];
|
||||
if (n > 0 && tp_read(GT_REG_PT0, raw, 8) == ESP_OK) {
|
||||
int16_t x = (int16_t)(raw[0] | ((uint16_t)raw[1] << 8));
|
||||
int16_t y = (int16_t)(raw[2] | ((uint16_t)raw[3] << 8));
|
||||
uint8_t zero = 0;
|
||||
tp_write(GT_REG_STAT, &zero, 1);
|
||||
/* Edge: report once per contact start. */
|
||||
if (s_prev_stat == 0) {
|
||||
s_last.x = x;
|
||||
s_last.y = y;
|
||||
s_prev_stat = n;
|
||||
if (out) {
|
||||
*out = s_last;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
s_prev_stat = n;
|
||||
return false;
|
||||
}
|
||||
uint8_t zero = 0;
|
||||
tp_write(GT_REG_STAT, &zero, 1);
|
||||
s_prev_stat = 0;
|
||||
(void)s_have;
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#include "hal_input.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "input";
|
||||
|
||||
#define ROWS 5
|
||||
#define COLS 4
|
||||
|
||||
static const gpio_num_t s_row_pins[ROWS] = {19, 18, 5, 17, 16};
|
||||
static const gpio_num_t s_col_pins[COLS] = {15, 2, 0, 4};
|
||||
|
||||
static const char s_keys[ROWS][COLS] = {
|
||||
{'F', 'E', '#', '*'},
|
||||
{'1', '2', '3', 'U'},
|
||||
{'4', '5', '6', 'D'},
|
||||
{'7', '8', '9', 'X'},
|
||||
{'L', '0', 'R', 'E'}};
|
||||
|
||||
static bool s_inited;
|
||||
static char s_held;
|
||||
|
||||
static void cols_idle_high(void)
|
||||
{
|
||||
for (int c = 0; c < COLS; c++) {
|
||||
gpio_set_level(s_col_pins[c], 1);
|
||||
}
|
||||
}
|
||||
|
||||
static char scan_once(void)
|
||||
{
|
||||
for (int c = 0; c < COLS; c++) {
|
||||
cols_idle_high();
|
||||
gpio_set_level(s_col_pins[c], 0);
|
||||
for (int r = 0; r < ROWS; r++) {
|
||||
if (gpio_get_level(s_row_pins[r]) == 0) {
|
||||
cols_idle_high();
|
||||
return s_keys[r][c];
|
||||
}
|
||||
}
|
||||
}
|
||||
cols_idle_high();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hal_input_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
}
|
||||
for (int r = 0; r < ROWS; r++) {
|
||||
gpio_config_t io = {
|
||||
.pin_bit_mask = 1ULL << s_row_pins[r],
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io);
|
||||
}
|
||||
for (int c = 0; c < COLS; c++) {
|
||||
gpio_config_t io = {
|
||||
.pin_bit_mask = 1ULL << s_col_pins[c],
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io);
|
||||
gpio_set_level(s_col_pins[c], 1);
|
||||
}
|
||||
s_held = 0;
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "init 5x4 GPIO keypad map byte-identical to src/config.cpp");
|
||||
}
|
||||
|
||||
bool hal_input_pop_key(ui_raw_key_t *out)
|
||||
{
|
||||
char k = scan_once();
|
||||
if (k == 0) {
|
||||
s_held = 0;
|
||||
return false;
|
||||
}
|
||||
if (k == s_held) {
|
||||
return false;
|
||||
}
|
||||
s_held = k;
|
||||
if (out != NULL) {
|
||||
out->key = k;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hal_input_pop_touch(ui_raw_touch_t *out)
|
||||
{
|
||||
(void)out;
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char key;
|
||||
} ui_raw_key_t;
|
||||
|
||||
typedef struct {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
} ui_raw_touch_t;
|
||||
|
||||
void hal_input_init(void);
|
||||
bool hal_input_pop_key(ui_raw_key_t *out); /* nonblocking */
|
||||
bool hal_input_pop_touch(ui_raw_touch_t *out); /* nonblocking; S3 only */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "hal_input.h"
|
||||
|
||||
void hal_input_init(void) {}
|
||||
|
||||
bool hal_input_pop_key(ui_raw_key_t *out)
|
||||
{
|
||||
(void)out;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hal_input_pop_touch(ui_raw_touch_t *out)
|
||||
{
|
||||
(void)out;
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
set(srcs)
|
||||
set(priv_inc)
|
||||
set(priv_req driver)
|
||||
|
||||
if(IDF_TARGET STREQUAL "esp32")
|
||||
list(APPEND srcs hal_motor.c)
|
||||
list(APPEND priv_req board_wroom)
|
||||
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||
list(APPEND srcs hal_motor.c)
|
||||
list(APPEND priv_req board_jc4827w543)
|
||||
else()
|
||||
list(APPEND srcs stub/hal_motor.c)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ${priv_inc}
|
||||
REQUIRES driver
|
||||
PRIV_REQUIRES ${priv_req})
|
||||
@@ -0,0 +1,284 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/rmt_tx.h"
|
||||
#include "driver/rmt_encoder.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "board.h"
|
||||
#include "hal_motor.h"
|
||||
|
||||
#define STEPS_PER_REV 4800
|
||||
#define DEFAULT_RPM 60
|
||||
#define ACCEL 9600
|
||||
#define RMT_RES_HZ 1000000
|
||||
#define CRUISE_HZ ((DEFAULT_RPM * STEPS_PER_REV) / 60) /* 4800 */
|
||||
#define RAMP_STAGES 8
|
||||
#define BATCH 32
|
||||
|
||||
static const char *TAG = "motor";
|
||||
|
||||
static gpio_num_t s_pin_en = GPIO_NUM_NC;
|
||||
static gpio_num_t s_pin_dir = GPIO_NUM_NC;
|
||||
static gpio_num_t s_pin_step = GPIO_NUM_NC;
|
||||
static int s_en_disable_level = 1;
|
||||
static TaskHandle_t s_task;
|
||||
static atomic_uint s_stop_req;
|
||||
static float s_cw;
|
||||
static float s_ccw;
|
||||
static uint32_t s_rpm = DEFAULT_RPM;
|
||||
static bool s_enabled;
|
||||
static bool s_inited;
|
||||
static rmt_channel_handle_t s_chan;
|
||||
static rmt_encoder_handle_t s_enc;
|
||||
|
||||
static void en_disable(void)
|
||||
{
|
||||
if (s_pin_en != GPIO_NUM_NC) {
|
||||
gpio_set_level(s_pin_en, s_en_disable_level);
|
||||
}
|
||||
s_enabled = false;
|
||||
}
|
||||
|
||||
static void en_enable(void)
|
||||
{
|
||||
if (s_pin_en != GPIO_NUM_NC) {
|
||||
gpio_set_level(s_pin_en, !s_en_disable_level);
|
||||
}
|
||||
s_enabled = true;
|
||||
}
|
||||
|
||||
static void rmt_abort(void)
|
||||
{
|
||||
if (s_chan != NULL) {
|
||||
rmt_disable(s_chan);
|
||||
rmt_enable(s_chan);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t period_us_for_hz(uint32_t hz)
|
||||
{
|
||||
if (hz < 100) {
|
||||
hz = 100;
|
||||
}
|
||||
return RMT_RES_HZ / hz;
|
||||
}
|
||||
|
||||
static bool emit_steps(uint32_t n, uint32_t hz)
|
||||
{
|
||||
if (n == 0) {
|
||||
return true;
|
||||
}
|
||||
uint32_t period = period_us_for_hz(hz);
|
||||
uint32_t half = period / 2;
|
||||
if (half == 0) {
|
||||
half = 1;
|
||||
}
|
||||
rmt_symbol_word_t sym = {
|
||||
.duration0 = (uint16_t)half,
|
||||
.level0 = 1,
|
||||
.duration1 = (uint16_t)(period - half),
|
||||
.level1 = 0,
|
||||
};
|
||||
while (n > 0) {
|
||||
if (atomic_load(&s_stop_req) != 0) {
|
||||
rmt_abort();
|
||||
return false;
|
||||
}
|
||||
uint32_t chunk = n > BATCH ? BATCH : n;
|
||||
rmt_symbol_word_t burst[BATCH];
|
||||
for (uint32_t i = 0; i < chunk; i++) {
|
||||
burst[i] = sym;
|
||||
}
|
||||
rmt_transmit_config_t tcfg = {
|
||||
.loop_count = 0,
|
||||
};
|
||||
if (rmt_transmit(s_chan, s_enc, burst, chunk * sizeof(rmt_symbol_word_t), &tcfg) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
if (rmt_tx_wait_all_done(s_chan, 2000) != ESP_OK) {
|
||||
rmt_abort();
|
||||
return false;
|
||||
}
|
||||
n -= chunk;
|
||||
}
|
||||
return atomic_load(&s_stop_req) == 0;
|
||||
}
|
||||
|
||||
static bool run_move(long steps)
|
||||
{
|
||||
if (steps == 0) {
|
||||
return true;
|
||||
}
|
||||
int dir = (steps > 0) ? 1 : 0;
|
||||
if (steps < 0) {
|
||||
steps = -steps;
|
||||
}
|
||||
gpio_set_level(s_pin_dir, dir);
|
||||
|
||||
uint32_t cruise = (s_rpm * STEPS_PER_REV) / 60;
|
||||
if (cruise == 0) {
|
||||
cruise = CRUISE_HZ;
|
||||
}
|
||||
/* Short accel table: ACCEL ≈9600 steps/s^2 into cruise. */
|
||||
uint32_t remaining = (uint32_t)steps;
|
||||
uint32_t ramp = remaining / 8;
|
||||
if (ramp > 200) {
|
||||
ramp = 200;
|
||||
}
|
||||
if (ramp < RAMP_STAGES) {
|
||||
ramp = (remaining < RAMP_STAGES) ? remaining : RAMP_STAGES;
|
||||
}
|
||||
uint32_t start_hz = cruise / 4;
|
||||
if (start_hz < 200) {
|
||||
start_hz = 200;
|
||||
}
|
||||
for (int i = 0; i < RAMP_STAGES && remaining > 0; i++) {
|
||||
uint32_t n = ramp / RAMP_STAGES;
|
||||
if (n == 0) {
|
||||
n = 1;
|
||||
}
|
||||
if (n > remaining) {
|
||||
n = remaining;
|
||||
}
|
||||
uint32_t hz = start_hz + ((cruise - start_hz) * (uint32_t)(i + 1)) / RAMP_STAGES;
|
||||
if (!emit_steps(n, hz)) {
|
||||
return false;
|
||||
}
|
||||
remaining -= n;
|
||||
}
|
||||
if (remaining > 0) {
|
||||
if (!emit_steps(remaining, cruise)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void motor_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
for (;;) {
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
if (atomic_load(&s_stop_req) != 0) {
|
||||
rmt_abort();
|
||||
en_disable();
|
||||
continue;
|
||||
}
|
||||
en_enable();
|
||||
while (atomic_load(&s_stop_req) == 0) {
|
||||
if (!run_move((long)((float)STEPS_PER_REV * s_cw))) {
|
||||
break;
|
||||
}
|
||||
if (atomic_load(&s_stop_req) != 0) {
|
||||
break;
|
||||
}
|
||||
if (!run_move((long)(-((float)STEPS_PER_REV * s_ccw)))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
rmt_abort();
|
||||
en_disable();
|
||||
}
|
||||
}
|
||||
|
||||
void autofilm_motor_task(void *arg)
|
||||
{
|
||||
motor_task(arg);
|
||||
}
|
||||
|
||||
void hal_motor_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
}
|
||||
s_pin_en = board_pin_motor_en();
|
||||
s_pin_dir = board_pin_motor_dir();
|
||||
s_pin_step = board_pin_motor_step();
|
||||
s_en_disable_level = board_motor_en_disable_level();
|
||||
if (s_pin_en == GPIO_NUM_NC || s_pin_dir == GPIO_NUM_NC || s_pin_step == GPIO_NUM_NC) {
|
||||
ESP_LOGW(TAG, "no motor pins on this board");
|
||||
s_inited = true;
|
||||
return;
|
||||
}
|
||||
gpio_config_t io = {
|
||||
.pin_bit_mask = (1ULL << s_pin_en) | (1ULL << s_pin_dir),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io);
|
||||
gpio_set_level(s_pin_en, s_en_disable_level);
|
||||
gpio_set_level(s_pin_dir, 0);
|
||||
s_enabled = false;
|
||||
atomic_store(&s_stop_req, 0);
|
||||
|
||||
rmt_tx_channel_config_t txcfg = {
|
||||
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||
.gpio_num = s_pin_step,
|
||||
.mem_block_symbols = 64,
|
||||
.resolution_hz = RMT_RES_HZ,
|
||||
.trans_queue_depth = 4,
|
||||
};
|
||||
rmt_new_tx_channel(&txcfg, &s_chan);
|
||||
rmt_copy_encoder_config_t enc_cfg = {};
|
||||
rmt_new_copy_encoder(&enc_cfg, &s_enc);
|
||||
rmt_enable(s_chan);
|
||||
|
||||
if (s_task == NULL) {
|
||||
xTaskCreatePinnedToCore(motor_task, "motor", 4096, NULL,
|
||||
configMAX_PRIORITIES - 2, &s_task, 0);
|
||||
}
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "init en=%d step=%d dir=%d dis_lvl=%d",
|
||||
s_pin_en, s_pin_step, s_pin_dir, s_en_disable_level);
|
||||
}
|
||||
|
||||
void hal_motor_enable(bool on)
|
||||
{
|
||||
if (on) {
|
||||
en_enable();
|
||||
} else {
|
||||
en_disable();
|
||||
}
|
||||
}
|
||||
|
||||
void hal_motor_request_stop(void)
|
||||
{
|
||||
atomic_store(&s_stop_req, 1);
|
||||
en_disable();
|
||||
rmt_abort();
|
||||
if (s_task != NULL) {
|
||||
xTaskNotifyGive(s_task);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t hal_motor_agitate_start(float cw_revs, float ccw_revs, uint32_t rpm)
|
||||
{
|
||||
s_cw = cw_revs;
|
||||
s_ccw = ccw_revs;
|
||||
s_rpm = (rpm == 0) ? DEFAULT_RPM : rpm;
|
||||
atomic_store(&s_stop_req, 0);
|
||||
if (s_task != NULL) {
|
||||
xTaskNotifyGive(s_task);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void hal_motor_agitate_stop(void)
|
||||
{
|
||||
atomic_store(&s_stop_req, 1);
|
||||
rmt_abort();
|
||||
en_disable();
|
||||
}
|
||||
|
||||
bool hal_motor_is_enabled(void)
|
||||
{
|
||||
return s_enabled;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "hal_motor.h"
|
||||
|
||||
void hal_motor_init(void)
|
||||
{
|
||||
}
|
||||
|
||||
void hal_motor_enable(bool on)
|
||||
{
|
||||
(void)on;
|
||||
}
|
||||
|
||||
void hal_motor_request_stop(void)
|
||||
{
|
||||
}
|
||||
|
||||
esp_err_t hal_motor_agitate_start(float cw_revs, float ccw_revs, uint32_t rpm)
|
||||
{
|
||||
(void)cw_revs;
|
||||
(void)ccw_revs;
|
||||
(void)rpm;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void hal_motor_agitate_stop(void)
|
||||
{
|
||||
}
|
||||
|
||||
bool hal_motor_is_enabled(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
set(srcs)
|
||||
set(priv_inc)
|
||||
set(priv_req driver esp_timer)
|
||||
|
||||
if(IDF_TARGET STREQUAL "esp32")
|
||||
list(APPEND srcs hal_temp.c)
|
||||
list(APPEND priv_req board_wroom onewire_bus)
|
||||
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||
list(APPEND srcs hal_temp.c)
|
||||
list(APPEND priv_req board_jc4827w543 onewire_bus)
|
||||
else()
|
||||
list(APPEND srcs stub/hal_temp.c)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ${priv_inc}
|
||||
REQUIRES driver
|
||||
PRIV_REQUIRES ${priv_req})
|
||||
@@ -0,0 +1,157 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
#include "onewire_bus.h"
|
||||
#include "board.h"
|
||||
#include "hal_temp.h"
|
||||
|
||||
#define TEMP_OFFSET 0.4f
|
||||
#define CMD_SKIP_ROM 0xCC
|
||||
#define CMD_CONVERT_T 0x44
|
||||
#define CMD_READ_SCRATCH 0xBE
|
||||
|
||||
static const char *TAG = "temp";
|
||||
|
||||
#define TEMP_CONV_MS 750
|
||||
|
||||
typedef enum {
|
||||
T_START,
|
||||
T_WAIT,
|
||||
} temp_state_t;
|
||||
|
||||
static onewire_bus_handle_t s_bus;
|
||||
static bool s_inited;
|
||||
static float s_last_c;
|
||||
static bool s_last_ok;
|
||||
static temp_state_t s_state = T_START;
|
||||
static uint32_t s_deadline_ms;
|
||||
|
||||
static uint32_t now_ms(void)
|
||||
{
|
||||
return (uint32_t)(esp_timer_get_time() / 1000ULL);
|
||||
}
|
||||
|
||||
static bool scratch_valid(const uint8_t *sp)
|
||||
{
|
||||
/* DS18B20 CRC8 over first 8 bytes, poly 0x8C reflected. */
|
||||
uint8_t crc = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
uint8_t in = sp[i];
|
||||
for (int b = 0; b < 8; b++) {
|
||||
uint8_t mix = (crc ^ in) & 0x01;
|
||||
crc >>= 1;
|
||||
if (mix) {
|
||||
crc ^= 0x8C;
|
||||
}
|
||||
in >>= 1;
|
||||
}
|
||||
}
|
||||
return crc == sp[8];
|
||||
}
|
||||
|
||||
void hal_temp_tick(void)
|
||||
{
|
||||
if (s_bus == NULL) {
|
||||
s_last_ok = false;
|
||||
return;
|
||||
}
|
||||
uint32_t now = now_ms();
|
||||
if (s_state == T_START) {
|
||||
if (now < s_deadline_ms) {
|
||||
return;
|
||||
}
|
||||
if (onewire_bus_reset(s_bus) != ESP_OK) {
|
||||
s_last_ok = false;
|
||||
s_deadline_ms = now + TEMP_CONV_MS;
|
||||
return;
|
||||
}
|
||||
uint8_t conv[2] = {CMD_SKIP_ROM, CMD_CONVERT_T};
|
||||
if (onewire_bus_write_bytes(s_bus, conv, 2) != ESP_OK) {
|
||||
s_last_ok = false;
|
||||
s_deadline_ms = now + TEMP_CONV_MS;
|
||||
return;
|
||||
}
|
||||
s_deadline_ms = now + TEMP_CONV_MS;
|
||||
s_state = T_WAIT;
|
||||
return;
|
||||
}
|
||||
/* T_WAIT */
|
||||
if (now < s_deadline_ms) {
|
||||
return;
|
||||
}
|
||||
s_state = T_START;
|
||||
s_deadline_ms = now;
|
||||
if (onewire_bus_reset(s_bus) != ESP_OK) {
|
||||
s_last_ok = false;
|
||||
s_deadline_ms = now + TEMP_CONV_MS;
|
||||
return;
|
||||
}
|
||||
uint8_t rd[2] = {CMD_SKIP_ROM, CMD_READ_SCRATCH};
|
||||
if (onewire_bus_write_bytes(s_bus, rd, 2) != ESP_OK) {
|
||||
s_last_ok = false;
|
||||
s_deadline_ms = now + TEMP_CONV_MS;
|
||||
return;
|
||||
}
|
||||
uint8_t sp[9];
|
||||
memset(sp, 0, sizeof(sp));
|
||||
if (onewire_bus_read_bytes(s_bus, sp, sizeof(sp)) != ESP_OK) {
|
||||
s_last_ok = false;
|
||||
s_deadline_ms = now + TEMP_CONV_MS;
|
||||
return;
|
||||
}
|
||||
if (!scratch_valid(sp)) {
|
||||
s_last_ok = false;
|
||||
s_deadline_ms = now + TEMP_CONV_MS;
|
||||
return;
|
||||
}
|
||||
int16_t raw = (int16_t)((uint16_t)sp[0] | ((uint16_t)sp[1] << 8));
|
||||
float t = ((float)raw) / 16.0f + TEMP_OFFSET;
|
||||
s_last_c = t;
|
||||
s_last_ok = true;
|
||||
}
|
||||
|
||||
void hal_temp_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
return;
|
||||
}
|
||||
gpio_num_t pin = board_pin_temp();
|
||||
if (pin == GPIO_NUM_NC) {
|
||||
ESP_LOGW(TAG, "no temp pin on this board");
|
||||
s_inited = true;
|
||||
return;
|
||||
}
|
||||
onewire_bus_config_t bus_config = {
|
||||
.bus_gpio_num = pin,
|
||||
.flags = {
|
||||
.en_pull_up = true,
|
||||
},
|
||||
};
|
||||
onewire_bus_rmt_config_t rmt_config = {
|
||||
.max_rx_bytes = 10,
|
||||
};
|
||||
if (onewire_new_bus_rmt(&bus_config, &rmt_config, &s_bus) != ESP_OK) {
|
||||
s_bus = NULL;
|
||||
ESP_LOGE(TAG, "onewire_bus install failed pin=%d", pin);
|
||||
}
|
||||
s_last_ok = false;
|
||||
s_last_c = 0.0f;
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "init onewire_bus pin=%d offset=%.1f", pin, (double)TEMP_OFFSET);
|
||||
}
|
||||
|
||||
esp_err_t hal_temp_read_c(float *out)
|
||||
{
|
||||
if (!s_last_ok) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (out != NULL) {
|
||||
*out = s_last_c;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
espressif/onewire_bus: "^1.1.0"
|
||||
@@ -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 */
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "hal_temp.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
void hal_temp_init(void)
|
||||
{
|
||||
}
|
||||
|
||||
void hal_temp_tick(void)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(750));
|
||||
}
|
||||
|
||||
esp_err_t hal_temp_read_c(float *out)
|
||||
{
|
||||
(void)out;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
@@ -0,0 +1,679 @@
|
||||
// AccelStepper.cpp
|
||||
//
|
||||
// Copyright (C) 2009-2020 Mike McCauley
|
||||
// $Id: AccelStepper.cpp,v 1.24 2020/04/20 00:15:03 mikem Exp mikem $
|
||||
|
||||
#include "AccelStepper.h"
|
||||
|
||||
#if 0
|
||||
// Some debugging assistance
|
||||
void dump(uint8_t* p, int l)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < l; i++)
|
||||
{
|
||||
Serial.print(p[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println("");
|
||||
}
|
||||
#endif
|
||||
|
||||
void AccelStepper::moveTo(long absolute)
|
||||
{
|
||||
if (_targetPos != absolute)
|
||||
{
|
||||
_targetPos = absolute;
|
||||
computeNewSpeed();
|
||||
// compute new n?
|
||||
}
|
||||
}
|
||||
|
||||
void AccelStepper::move(long relative)
|
||||
{
|
||||
moveTo(_currentPos + relative);
|
||||
}
|
||||
|
||||
// Implements steps according to the current step interval
|
||||
// You must call this at least once per step
|
||||
// returns true if a step occurred
|
||||
boolean AccelStepper::runSpeed()
|
||||
{
|
||||
// Dont do anything unless we actually have a step interval
|
||||
if (!_stepInterval)
|
||||
return false;
|
||||
|
||||
unsigned long time = micros();
|
||||
if (time - _lastStepTime >= _stepInterval)
|
||||
{
|
||||
if (_direction == DIRECTION_CW)
|
||||
{
|
||||
// Clockwise
|
||||
_currentPos += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Anticlockwise
|
||||
_currentPos -= 1;
|
||||
}
|
||||
step(_currentPos);
|
||||
|
||||
_lastStepTime = time; // Caution: does not account for costs in step()
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
long AccelStepper::distanceToGo()
|
||||
{
|
||||
return _targetPos - _currentPos;
|
||||
}
|
||||
|
||||
long AccelStepper::targetPosition()
|
||||
{
|
||||
return _targetPos;
|
||||
}
|
||||
|
||||
long AccelStepper::currentPosition()
|
||||
{
|
||||
return _currentPos;
|
||||
}
|
||||
|
||||
// Useful during initialisations or after initial positioning
|
||||
// Sets speed to 0
|
||||
void AccelStepper::setCurrentPosition(long position)
|
||||
{
|
||||
_targetPos = _currentPos = position;
|
||||
_n = 0;
|
||||
_stepInterval = 0;
|
||||
_speed = 0.0;
|
||||
}
|
||||
|
||||
// Subclasses can override
|
||||
unsigned long AccelStepper::computeNewSpeed()
|
||||
{
|
||||
long distanceTo = distanceToGo(); // +ve is clockwise from curent location
|
||||
|
||||
long stepsToStop = (long)((_speed * _speed) / (2.0 * _acceleration)); // Equation 16
|
||||
|
||||
if (distanceTo == 0 && stepsToStop <= 1)
|
||||
{
|
||||
// We are at the target and its time to stop
|
||||
_stepInterval = 0;
|
||||
_speed = 0.0;
|
||||
_n = 0;
|
||||
return _stepInterval;
|
||||
}
|
||||
|
||||
if (distanceTo > 0)
|
||||
{
|
||||
// We are anticlockwise from the target
|
||||
// Need to go clockwise from here, maybe decelerate now
|
||||
if (_n > 0)
|
||||
{
|
||||
// Currently accelerating, need to decel now? Or maybe going the wrong way?
|
||||
if ((stepsToStop >= distanceTo) || _direction == DIRECTION_CCW)
|
||||
_n = -stepsToStop; // Start deceleration
|
||||
}
|
||||
else if (_n < 0)
|
||||
{
|
||||
// Currently decelerating, need to accel again?
|
||||
if ((stepsToStop < distanceTo) && _direction == DIRECTION_CW)
|
||||
_n = -_n; // Start accceleration
|
||||
}
|
||||
}
|
||||
else if (distanceTo < 0)
|
||||
{
|
||||
// We are clockwise from the target
|
||||
// Need to go anticlockwise from here, maybe decelerate
|
||||
if (_n > 0)
|
||||
{
|
||||
// Currently accelerating, need to decel now? Or maybe going the wrong way?
|
||||
if ((stepsToStop >= -distanceTo) || _direction == DIRECTION_CW)
|
||||
_n = -stepsToStop; // Start deceleration
|
||||
}
|
||||
else if (_n < 0)
|
||||
{
|
||||
// Currently decelerating, need to accel again?
|
||||
if ((stepsToStop < -distanceTo) && _direction == DIRECTION_CCW)
|
||||
_n = -_n; // Start accceleration
|
||||
}
|
||||
}
|
||||
|
||||
// Need to accelerate or decelerate
|
||||
if (_n == 0)
|
||||
{
|
||||
// First step from stopped
|
||||
_cn = _c0;
|
||||
_direction = (distanceTo > 0) ? DIRECTION_CW : DIRECTION_CCW;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Subsequent step. Works for accel (n is +_ve) and decel (n is -ve).
|
||||
_cn = _cn - ((2.0 * _cn) / ((4.0 * _n) + 1)); // Equation 13
|
||||
_cn = max(_cn, _cmin);
|
||||
}
|
||||
_n++;
|
||||
_stepInterval = _cn;
|
||||
_speed = 1000000.0 / _cn;
|
||||
if (_direction == DIRECTION_CCW)
|
||||
_speed = -_speed;
|
||||
|
||||
#if 0
|
||||
Serial.println(_speed);
|
||||
Serial.println(_acceleration);
|
||||
Serial.println(_cn);
|
||||
Serial.println(_c0);
|
||||
Serial.println(_n);
|
||||
Serial.println(_stepInterval);
|
||||
Serial.println(distanceTo);
|
||||
Serial.println(stepsToStop);
|
||||
Serial.println("-----");
|
||||
#endif
|
||||
return _stepInterval;
|
||||
}
|
||||
|
||||
// Run the motor to implement speed and acceleration in order to proceed to the target position
|
||||
// You must call this at least once per step, preferably in your main loop
|
||||
// If the motor is in the desired position, the cost is very small
|
||||
// returns true if the motor is still running to the target position.
|
||||
boolean AccelStepper::run()
|
||||
{
|
||||
if (runSpeed())
|
||||
computeNewSpeed();
|
||||
return _speed != 0.0 || distanceToGo() != 0;
|
||||
}
|
||||
|
||||
AccelStepper::AccelStepper(uint8_t interface, uint8_t pin1, uint8_t pin2, uint8_t pin3, uint8_t pin4, bool enable)
|
||||
{
|
||||
_interface = interface;
|
||||
_currentPos = 0;
|
||||
_targetPos = 0;
|
||||
_speed = 0.0;
|
||||
_maxSpeed = 0.0;
|
||||
_acceleration = 0.0;
|
||||
_sqrt_twoa = 1.0;
|
||||
_stepInterval = 0;
|
||||
_minPulseWidth = 1;
|
||||
_enablePin = 0xff;
|
||||
_lastStepTime = 0;
|
||||
_pin[0] = pin1;
|
||||
_pin[1] = pin2;
|
||||
_pin[2] = pin3;
|
||||
_pin[3] = pin4;
|
||||
_enableInverted = false;
|
||||
|
||||
// NEW
|
||||
_n = 0;
|
||||
_c0 = 0.0;
|
||||
_cn = 0.0;
|
||||
_cmin = 1.0;
|
||||
_direction = DIRECTION_CCW;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 4; i++)
|
||||
_pinInverted[i] = 0;
|
||||
if (enable)
|
||||
enableOutputs();
|
||||
// Some reasonable default
|
||||
setAcceleration(1);
|
||||
setMaxSpeed(1);
|
||||
}
|
||||
|
||||
AccelStepper::AccelStepper(void (*forward)(), void (*backward)())
|
||||
{
|
||||
_interface = 0;
|
||||
_currentPos = 0;
|
||||
_targetPos = 0;
|
||||
_speed = 0.0;
|
||||
_maxSpeed = 0.0;
|
||||
_acceleration = 0.0;
|
||||
_sqrt_twoa = 1.0;
|
||||
_stepInterval = 0;
|
||||
_minPulseWidth = 1;
|
||||
_enablePin = 0xff;
|
||||
_lastStepTime = 0;
|
||||
_pin[0] = 0;
|
||||
_pin[1] = 0;
|
||||
_pin[2] = 0;
|
||||
_pin[3] = 0;
|
||||
_forward = forward;
|
||||
_backward = backward;
|
||||
|
||||
// NEW
|
||||
_n = 0;
|
||||
_c0 = 0.0;
|
||||
_cn = 0.0;
|
||||
_cmin = 1.0;
|
||||
_direction = DIRECTION_CCW;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 4; i++)
|
||||
_pinInverted[i] = 0;
|
||||
// Some reasonable default
|
||||
setAcceleration(1);
|
||||
setMaxSpeed(1);
|
||||
}
|
||||
|
||||
void AccelStepper::setMaxSpeed(float speed)
|
||||
{
|
||||
if (speed < 0.0)
|
||||
speed = -speed;
|
||||
if (_maxSpeed != speed)
|
||||
{
|
||||
_maxSpeed = speed;
|
||||
_cmin = 1000000.0 / speed;
|
||||
// Recompute _n from current speed and adjust speed if accelerating or cruising
|
||||
if (_n > 0)
|
||||
{
|
||||
_n = (long)((_speed * _speed) / (2.0 * _acceleration)); // Equation 16
|
||||
computeNewSpeed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float AccelStepper::maxSpeed()
|
||||
{
|
||||
return _maxSpeed;
|
||||
}
|
||||
|
||||
void AccelStepper::setAcceleration(float acceleration)
|
||||
{
|
||||
if (acceleration == 0.0)
|
||||
return;
|
||||
if (acceleration < 0.0)
|
||||
acceleration = -acceleration;
|
||||
if (_acceleration != acceleration)
|
||||
{
|
||||
// Recompute _n per Equation 17
|
||||
_n = _n * (_acceleration / acceleration);
|
||||
// New c0 per Equation 7, with correction per Equation 15
|
||||
_c0 = 0.676 * sqrt(2.0 / acceleration) * 1000000.0; // Equation 15
|
||||
_acceleration = acceleration;
|
||||
computeNewSpeed();
|
||||
}
|
||||
}
|
||||
|
||||
float AccelStepper::acceleration()
|
||||
{
|
||||
return _acceleration;
|
||||
}
|
||||
|
||||
void AccelStepper::setSpeed(float speed)
|
||||
{
|
||||
if (speed == _speed)
|
||||
return;
|
||||
speed = constrain(speed, -_maxSpeed, _maxSpeed);
|
||||
if (speed == 0.0)
|
||||
_stepInterval = 0;
|
||||
else
|
||||
{
|
||||
_stepInterval = fabs(1000000.0 / speed);
|
||||
_direction = (speed > 0.0) ? DIRECTION_CW : DIRECTION_CCW;
|
||||
}
|
||||
_speed = speed;
|
||||
}
|
||||
|
||||
float AccelStepper::speed()
|
||||
{
|
||||
return _speed;
|
||||
}
|
||||
|
||||
// Subclasses can override
|
||||
void AccelStepper::step(long step)
|
||||
{
|
||||
switch (_interface)
|
||||
{
|
||||
case FUNCTION:
|
||||
step0(step);
|
||||
break;
|
||||
|
||||
case DRIVER:
|
||||
step1(step);
|
||||
break;
|
||||
|
||||
case FULL2WIRE:
|
||||
step2(step);
|
||||
break;
|
||||
|
||||
case FULL3WIRE:
|
||||
step3(step);
|
||||
break;
|
||||
|
||||
case FULL4WIRE:
|
||||
step4(step);
|
||||
break;
|
||||
|
||||
case HALF3WIRE:
|
||||
step6(step);
|
||||
break;
|
||||
|
||||
case HALF4WIRE:
|
||||
step8(step);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
long AccelStepper::stepForward()
|
||||
{
|
||||
// Clockwise
|
||||
_currentPos += 1;
|
||||
step(_currentPos);
|
||||
_lastStepTime = micros();
|
||||
return _currentPos;
|
||||
}
|
||||
|
||||
long AccelStepper::stepBackward()
|
||||
{
|
||||
// Counter-clockwise
|
||||
_currentPos -= 1;
|
||||
step(_currentPos);
|
||||
_lastStepTime = micros();
|
||||
return _currentPos;
|
||||
}
|
||||
|
||||
// You might want to override this to implement eg serial output
|
||||
// bit 0 of the mask corresponds to _pin[0]
|
||||
// bit 1 of the mask corresponds to _pin[1]
|
||||
// ....
|
||||
void AccelStepper::setOutputPins(uint8_t mask)
|
||||
{
|
||||
uint8_t numpins = 2;
|
||||
if (_interface == FULL4WIRE || _interface == HALF4WIRE)
|
||||
numpins = 4;
|
||||
else if (_interface == FULL3WIRE || _interface == HALF3WIRE)
|
||||
numpins = 3;
|
||||
uint8_t i;
|
||||
for (i = 0; i < numpins; i++)
|
||||
digitalWrite(_pin[i], (mask & (1 << i)) ? (HIGH ^ _pinInverted[i]) : (LOW ^ _pinInverted[i]));
|
||||
}
|
||||
|
||||
// 0 pin step function (ie for functional usage)
|
||||
void AccelStepper::step0(long step)
|
||||
{
|
||||
(void)(step); // Unused
|
||||
if (_speed > 0)
|
||||
_forward();
|
||||
else
|
||||
_backward();
|
||||
}
|
||||
|
||||
// 1 pin step function (ie for stepper drivers)
|
||||
// This is passed the current step number (0 to 7)
|
||||
// Subclasses can override
|
||||
void AccelStepper::step1(long step)
|
||||
{
|
||||
(void)(step); // Unused
|
||||
|
||||
// _pin[0] is step, _pin[1] is direction
|
||||
setOutputPins(_direction ? 0b10 : 0b00); // Set direction first else get rogue pulses
|
||||
setOutputPins(_direction ? 0b11 : 0b01); // step HIGH
|
||||
// Caution 200ns setup time
|
||||
// Delay the minimum allowed pulse width
|
||||
delayMicroseconds(_minPulseWidth);
|
||||
setOutputPins(_direction ? 0b10 : 0b00); // step LOW
|
||||
}
|
||||
|
||||
|
||||
// 2 pin step function
|
||||
// This is passed the current step number (0 to 7)
|
||||
// Subclasses can override
|
||||
void AccelStepper::step2(long step)
|
||||
{
|
||||
switch (step & 0x3)
|
||||
{
|
||||
case 0: /* 01 */
|
||||
setOutputPins(0b10);
|
||||
break;
|
||||
|
||||
case 1: /* 11 */
|
||||
setOutputPins(0b11);
|
||||
break;
|
||||
|
||||
case 2: /* 10 */
|
||||
setOutputPins(0b01);
|
||||
break;
|
||||
|
||||
case 3: /* 00 */
|
||||
setOutputPins(0b00);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 3 pin step function
|
||||
// This is passed the current step number (0 to 7)
|
||||
// Subclasses can override
|
||||
void AccelStepper::step3(long step)
|
||||
{
|
||||
switch (step % 3)
|
||||
{
|
||||
case 0: // 100
|
||||
setOutputPins(0b100);
|
||||
break;
|
||||
|
||||
case 1: // 001
|
||||
setOutputPins(0b001);
|
||||
break;
|
||||
|
||||
case 2: //010
|
||||
setOutputPins(0b010);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 4 pin step function for half stepper
|
||||
// This is passed the current step number (0 to 7)
|
||||
// Subclasses can override
|
||||
void AccelStepper::step4(long step)
|
||||
{
|
||||
switch (step & 0x3)
|
||||
{
|
||||
case 0: // 1010
|
||||
setOutputPins(0b0101);
|
||||
break;
|
||||
|
||||
case 1: // 0110
|
||||
setOutputPins(0b0110);
|
||||
break;
|
||||
|
||||
case 2: //0101
|
||||
setOutputPins(0b1010);
|
||||
break;
|
||||
|
||||
case 3: //1001
|
||||
setOutputPins(0b1001);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 3 pin half step function
|
||||
// This is passed the current step number (0 to 7)
|
||||
// Subclasses can override
|
||||
void AccelStepper::step6(long step)
|
||||
{
|
||||
switch (step % 6)
|
||||
{
|
||||
case 0: // 100
|
||||
setOutputPins(0b100);
|
||||
break;
|
||||
|
||||
case 1: // 101
|
||||
setOutputPins(0b101);
|
||||
break;
|
||||
|
||||
case 2: // 001
|
||||
setOutputPins(0b001);
|
||||
break;
|
||||
|
||||
case 3: // 011
|
||||
setOutputPins(0b011);
|
||||
break;
|
||||
|
||||
case 4: // 010
|
||||
setOutputPins(0b010);
|
||||
break;
|
||||
|
||||
case 5: // 011
|
||||
setOutputPins(0b110);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 4 pin half step function
|
||||
// This is passed the current step number (0 to 7)
|
||||
// Subclasses can override
|
||||
void AccelStepper::step8(long step)
|
||||
{
|
||||
switch (step & 0x7)
|
||||
{
|
||||
case 0: // 1000
|
||||
setOutputPins(0b0001);
|
||||
break;
|
||||
|
||||
case 1: // 1010
|
||||
setOutputPins(0b0101);
|
||||
break;
|
||||
|
||||
case 2: // 0010
|
||||
setOutputPins(0b0100);
|
||||
break;
|
||||
|
||||
case 3: // 0110
|
||||
setOutputPins(0b0110);
|
||||
break;
|
||||
|
||||
case 4: // 0100
|
||||
setOutputPins(0b0010);
|
||||
break;
|
||||
|
||||
case 5: //0101
|
||||
setOutputPins(0b1010);
|
||||
break;
|
||||
|
||||
case 6: // 0001
|
||||
setOutputPins(0b1000);
|
||||
break;
|
||||
|
||||
case 7: //1001
|
||||
setOutputPins(0b1001);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevents power consumption on the outputs
|
||||
void AccelStepper::disableOutputs()
|
||||
{
|
||||
if (! _interface) return;
|
||||
|
||||
setOutputPins(0); // Handles inversion automatically
|
||||
if (_enablePin != 0xff)
|
||||
{
|
||||
pinMode(_enablePin, OUTPUT);
|
||||
digitalWrite(_enablePin, LOW ^ _enableInverted);
|
||||
}
|
||||
}
|
||||
|
||||
void AccelStepper::enableOutputs()
|
||||
{
|
||||
if (! _interface)
|
||||
return;
|
||||
|
||||
pinMode(_pin[0], OUTPUT);
|
||||
pinMode(_pin[1], OUTPUT);
|
||||
if (_interface == FULL4WIRE || _interface == HALF4WIRE)
|
||||
{
|
||||
pinMode(_pin[2], OUTPUT);
|
||||
pinMode(_pin[3], OUTPUT);
|
||||
}
|
||||
else if (_interface == FULL3WIRE || _interface == HALF3WIRE)
|
||||
{
|
||||
pinMode(_pin[2], OUTPUT);
|
||||
}
|
||||
|
||||
if (_enablePin != 0xff)
|
||||
{
|
||||
pinMode(_enablePin, OUTPUT);
|
||||
digitalWrite(_enablePin, HIGH ^ _enableInverted);
|
||||
}
|
||||
}
|
||||
|
||||
void AccelStepper::setMinPulseWidth(unsigned int minWidth)
|
||||
{
|
||||
_minPulseWidth = minWidth;
|
||||
}
|
||||
|
||||
void AccelStepper::setEnablePin(uint8_t enablePin)
|
||||
{
|
||||
_enablePin = enablePin;
|
||||
|
||||
// This happens after construction, so init pin now.
|
||||
if (_enablePin != 0xff)
|
||||
{
|
||||
pinMode(_enablePin, OUTPUT);
|
||||
digitalWrite(_enablePin, HIGH ^ _enableInverted);
|
||||
}
|
||||
}
|
||||
|
||||
void AccelStepper::setPinsInverted(bool directionInvert, bool stepInvert, bool enableInvert)
|
||||
{
|
||||
_pinInverted[0] = stepInvert;
|
||||
_pinInverted[1] = directionInvert;
|
||||
_enableInverted = enableInvert;
|
||||
}
|
||||
|
||||
void AccelStepper::setPinsInverted(bool pin1Invert, bool pin2Invert, bool pin3Invert, bool pin4Invert, bool enableInvert)
|
||||
{
|
||||
_pinInverted[0] = pin1Invert;
|
||||
_pinInverted[1] = pin2Invert;
|
||||
_pinInverted[2] = pin3Invert;
|
||||
_pinInverted[3] = pin4Invert;
|
||||
_enableInverted = enableInvert;
|
||||
}
|
||||
|
||||
// Blocks until the target position is reached and stopped
|
||||
void AccelStepper::runToPosition()
|
||||
{
|
||||
while (run())
|
||||
YIELD; // Let system housekeeping occur
|
||||
}
|
||||
|
||||
boolean AccelStepper::runSpeedToPosition()
|
||||
{
|
||||
if (_targetPos == _currentPos)
|
||||
return false;
|
||||
if (_targetPos >_currentPos)
|
||||
_direction = DIRECTION_CW;
|
||||
else
|
||||
_direction = DIRECTION_CCW;
|
||||
return runSpeed();
|
||||
}
|
||||
|
||||
// Blocks until the new target position is reached
|
||||
void AccelStepper::runToNewPosition(long position)
|
||||
{
|
||||
moveTo(position);
|
||||
runToPosition();
|
||||
}
|
||||
|
||||
void AccelStepper::stop()
|
||||
{
|
||||
if (_speed != 0.0)
|
||||
{
|
||||
long stepsToStop = (long)((_speed * _speed) / (2.0 * _acceleration)) + 1; // Equation 16 (+integer rounding)
|
||||
if (_speed > 0)
|
||||
move(stepsToStop);
|
||||
else
|
||||
move(-stepsToStop);
|
||||
}
|
||||
}
|
||||
|
||||
bool AccelStepper::isRunning()
|
||||
{
|
||||
return !(_speed == 0.0 && _targetPos == _currentPos);
|
||||
}
|
||||
@@ -0,0 +1,783 @@
|
||||
// AccelStepper.h
|
||||
//
|
||||
/// \mainpage AccelStepper library for Arduino
|
||||
///
|
||||
/// This is the Arduino AccelStepper library.
|
||||
/// It provides an object-oriented interface for 2, 3 or 4 pin stepper motors and motor drivers.
|
||||
///
|
||||
/// The standard Arduino IDE includes the Stepper library
|
||||
/// (http://arduino.cc/en/Reference/Stepper) for stepper motors. It is
|
||||
/// perfectly adequate for simple, single motor applications.
|
||||
///
|
||||
/// AccelStepper significantly improves on the standard Arduino Stepper library in several ways:
|
||||
/// \li Supports acceleration and deceleration
|
||||
/// \li Supports multiple simultaneous steppers, with independent concurrent stepping on each stepper
|
||||
/// \li Most API functions never delay() or block (unless otherwise stated)
|
||||
/// \li Supports 2, 3 and 4 wire steppers, plus 3 and 4 wire half steppers.
|
||||
/// \li Supports alternate stepping functions to enable support of AFMotor (https://github.com/adafruit/Adafruit-Motor-Shield-library)
|
||||
/// \li Supports stepper drivers such as the Sparkfun EasyDriver (based on 3967 driver chip)
|
||||
/// \li Very slow speeds are supported
|
||||
/// \li Extensive API
|
||||
/// \li Subclass support
|
||||
///
|
||||
/// The latest version of this documentation can be downloaded from
|
||||
/// http://www.airspayce.com/mikem/arduino/AccelStepper
|
||||
/// The version of the package that this documentation refers to can be downloaded
|
||||
/// from http://www.airspayce.com/mikem/arduino/AccelStepper/AccelStepper-1.64.zip
|
||||
///
|
||||
/// Example Arduino programs are included to show the main modes of use.
|
||||
///
|
||||
/// You can also find online help and discussion at http://groups.google.com/group/accelstepper
|
||||
/// Please use that group for all questions and discussions on this topic.
|
||||
/// Do not contact the author directly, unless it is to discuss commercial licensing.
|
||||
/// Before asking a question or reporting a bug, please read
|
||||
/// - http://en.wikipedia.org/wiki/Wikipedia:Reference_desk/How_to_ask_a_software_question
|
||||
/// - http://www.catb.org/esr/faqs/smart-questions.html
|
||||
/// - http://www.chiark.greenend.org.uk/~shgtatham/bugs.html
|
||||
///
|
||||
/// Beginners to C++ and stepper motors in general may find this helpful:
|
||||
/// - https://hackaday.io/project/183279-accelstepper-the-missing-manual
|
||||
/// - https://hackaday.io/project/183713-using-the-arduino-accelstepper-library
|
||||
///
|
||||
/// Tested on Arduino Diecimila and Mega with arduino-0018 & arduino-0021
|
||||
/// on OpenSuSE 11.1 and avr-libc-1.6.1-1.15,
|
||||
/// cross-avr-binutils-2.19-9.1, cross-avr-gcc-4.1.3_20080612-26.5.
|
||||
/// Tested on Teensy http://www.pjrc.com/teensy including Teensy 3.1 built using Arduino IDE 1.0.5 with
|
||||
/// teensyduino addon 1.18 and later.
|
||||
///
|
||||
/// \par Installation
|
||||
///
|
||||
/// Install in the usual way: unzip the distribution zip file to the libraries
|
||||
/// sub-folder of your sketchbook.
|
||||
///
|
||||
/// \par Theory
|
||||
///
|
||||
/// This code uses speed calculations as described in
|
||||
/// "Generate stepper-motor speed profiles in real time" by David Austin
|
||||
/// http://fab.cba.mit.edu/classes/MIT/961.09/projects/i0/Stepper_Motor_Speed_Profile.pdf or
|
||||
/// http://www.embedded.com/design/mcus-processors-and-socs/4006438/Generate-stepper-motor-speed-profiles-in-real-time or
|
||||
/// http://web.archive.org/web/20140705143928/http://fab.cba.mit.edu/classes/MIT/961.09/projects/i0/Stepper_Motor_Speed_Profile.pdf
|
||||
/// with the exception that AccelStepper uses steps per second rather than radians per second
|
||||
/// (because we dont know the step angle of the motor)
|
||||
/// An initial step interval is calculated for the first step, based on the desired acceleration
|
||||
/// On subsequent steps, shorter step intervals are calculated based
|
||||
/// on the previous step until max speed is achieved.
|
||||
///
|
||||
/// \par Adafruit Motor Shield V2
|
||||
///
|
||||
/// The included examples AFMotor_* are for Adafruit Motor Shield V1 and do not work with Adafruit Motor Shield V2.
|
||||
/// See https://github.com/adafruit/Adafruit_Motor_Shield_V2_Library for examples that work with Adafruit Motor Shield V2.
|
||||
///
|
||||
/// \par Donations
|
||||
///
|
||||
/// This library is offered under a free GPL license for those who want to use it that way.
|
||||
/// We try hard to keep it up to date, fix bugs
|
||||
/// and to provide free support. If this library has helped you save time or money, please consider donating at
|
||||
/// http://www.airspayce.com or here:
|
||||
///
|
||||
/// \htmlonly <form action="https://www.paypal.com/cgi-bin/webscr" method="post"><input type="hidden" name="cmd" value="_donations" /> <input type="hidden" name="business" value="mikem@airspayce.com" /> <input type="hidden" name="lc" value="AU" /> <input type="hidden" name="item_name" value="Airspayce" /> <input type="hidden" name="item_number" value="AccelStepper" /> <input type="hidden" name="currency_code" value="USD" /> <input type="hidden" name="bn" value="PP-DonationsBF:btn_donateCC_LG.gif:NonHosted" /> <input type="image" alt="PayPal — The safer, easier way to pay online." name="submit" src="https://www.paypalobjects.com/en_AU/i/btn/btn_donateCC_LG.gif" /> <img alt="" src="https://www.paypalobjects.com/en_AU/i/scr/pixel.gif" width="1" height="1" border="0" /></form> \endhtmlonly
|
||||
///
|
||||
/// \par Trademarks
|
||||
///
|
||||
/// AccelStepper is a trademark of AirSpayce Pty Ltd. The AccelStepper mark was first used on April 26 2010 for
|
||||
/// international trade, and is used only in relation to motor control hardware and software.
|
||||
/// It is not to be confused with any other similar marks covering other goods and services.
|
||||
///
|
||||
/// \par Copyright
|
||||
///
|
||||
/// This software is Copyright (C) 2010-2021 Mike McCauley. Use is subject to license
|
||||
/// conditions. The main licensing options available are GPL V3 or Commercial:
|
||||
///
|
||||
/// \par Open Source Licensing GPL V3
|
||||
/// This is the appropriate option if you want to share the source code of your
|
||||
/// application with everyone you distribute it to, and you also want to give them
|
||||
/// the right to share who uses it. If you wish to use this software under Open
|
||||
/// Source Licensing, you must contribute all your source code to the open source
|
||||
/// community in accordance with the GPL Version 23 when your application is
|
||||
/// distributed. See https://www.gnu.org/licenses/gpl-3.0.html
|
||||
///
|
||||
/// \par Commercial Licensing
|
||||
/// This is the appropriate option if you are creating proprietary applications
|
||||
/// and you are not prepared to distribute and share the source code of your
|
||||
/// application. To purchase a commercial license, contact info@airspayce.com
|
||||
///
|
||||
/// \par Revision History
|
||||
/// \version 1.0 Initial release
|
||||
///
|
||||
/// \version 1.1 Added speed() function to get the current speed.
|
||||
/// \version 1.2 Added runSpeedToPosition() submitted by Gunnar Arndt.
|
||||
/// \version 1.3 Added support for stepper drivers (ie with Step and Direction inputs) with _pins == 1
|
||||
/// \version 1.4 Added functional contructor to support AFMotor, contributed by Limor, with example sketches.
|
||||
/// \version 1.5 Improvements contributed by Peter Mousley: Use of microsecond steps and other speed improvements
|
||||
/// to increase max stepping speed to about 4kHz. New option for user to set the min allowed pulse width.
|
||||
/// Added checks for already running at max speed and skip further calcs if so.
|
||||
/// \version 1.6 Fixed a problem with wrapping of microsecond stepping that could cause stepping to hang.
|
||||
/// Reported by Sandy Noble.
|
||||
/// Removed redundant _lastRunTime member.
|
||||
/// \version 1.7 Fixed a bug where setCurrentPosition() did not always work as expected.
|
||||
/// Reported by Peter Linhart.
|
||||
/// \version 1.8 Added support for 4 pin half-steppers, requested by Harvey Moon
|
||||
/// \version 1.9 setCurrentPosition() now also sets motor speed to 0.
|
||||
/// \version 1.10 Builds on Arduino 1.0
|
||||
/// \version 1.11 Improvments from Michael Ellison:
|
||||
/// Added optional enable line support for stepper drivers
|
||||
/// Added inversion for step/direction/enable lines for stepper drivers
|
||||
/// \version 1.12 Announce Google Group
|
||||
/// \version 1.13 Improvements to speed calculation. Cost of calculation is now less in the worst case,
|
||||
/// and more or less constant in all cases. This should result in slightly beter high speed performance, and
|
||||
/// reduce anomalous speed glitches when other steppers are accelerating.
|
||||
/// However, its hard to see how to replace the sqrt() required at the very first step from 0 speed.
|
||||
/// \version 1.14 Fixed a problem with compiling under arduino 0021 reported by EmbeddedMan
|
||||
/// \version 1.15 Fixed a problem with runSpeedToPosition which did not correctly handle
|
||||
/// running backwards to a smaller target position. Added examples
|
||||
/// \version 1.16 Fixed some cases in the code where abs() was used instead of fabs().
|
||||
/// \version 1.17 Added example ProportionalControl
|
||||
/// \version 1.18 Fixed a problem: If one calls the funcion runSpeed() when Speed is zero, it makes steps
|
||||
/// without counting. reported by Friedrich, Klappenbach.
|
||||
/// \version 1.19 Added MotorInterfaceType and symbolic names for the number of pins to use
|
||||
/// for the motor interface. Updated examples to suit.
|
||||
/// Replaced individual pin assignment variables _pin1, _pin2 etc with array _pin[4].
|
||||
/// _pins member changed to _interface.
|
||||
/// Added _pinInverted array to simplify pin inversion operations.
|
||||
/// Added new function setOutputPins() which sets the motor output pins.
|
||||
/// It can be overridden in order to provide, say, serial output instead of parallel output
|
||||
/// Some refactoring and code size reduction.
|
||||
/// \version 1.20 Improved documentation and examples to show need for correctly
|
||||
/// specifying AccelStepper::FULL4WIRE and friends.
|
||||
/// \version 1.21 Fixed a problem where desiredSpeed could compute the wrong step acceleration
|
||||
/// when _speed was small but non-zero. Reported by Brian Schmalz.
|
||||
/// Precompute sqrt_twoa to improve performance and max possible stepping speed
|
||||
/// \version 1.22 Added Bounce.pde example
|
||||
/// Fixed a problem where calling moveTo(), setMaxSpeed(), setAcceleration() more
|
||||
/// frequently than the step time, even
|
||||
/// with the same values, would interfere with speed calcs. Now a new speed is computed
|
||||
/// only if there was a change in the set value. Reported by Brian Schmalz.
|
||||
/// \version 1.23 Rewrite of the speed algorithms in line with
|
||||
/// http://fab.cba.mit.edu/classes/MIT/961.09/projects/i0/Stepper_Motor_Speed_Profile.pdf
|
||||
/// Now expect smoother and more linear accelerations and decelerations. The desiredSpeed()
|
||||
/// function was removed.
|
||||
/// \version 1.24 Fixed a problem introduced in 1.23: with runToPosition, which did never returned
|
||||
/// \version 1.25 Now ignore attempts to set acceleration to 0.0
|
||||
/// \version 1.26 Fixed a problem where certina combinations of speed and accelration could cause
|
||||
/// oscillation about the target position.
|
||||
/// \version 1.27 Added stop() function to stop as fast as possible with current acceleration parameters.
|
||||
/// Also added new Quickstop example showing its use.
|
||||
/// \version 1.28 Fixed another problem where certain combinations of speed and acceleration could cause
|
||||
/// oscillation about the target position.
|
||||
/// Added support for 3 wire full and half steppers such as Hard Disk Drive spindle.
|
||||
/// Contributed by Yuri Ivatchkovitch.
|
||||
/// \version 1.29 Fixed a problem that could cause a DRIVER stepper to continually step
|
||||
/// with some sketches. Reported by Vadim.
|
||||
/// \version 1.30 Fixed a problem that could cause stepper to back up a few steps at the end of
|
||||
/// accelerated travel with certain speeds. Reported and patched by jolo.
|
||||
/// \version 1.31 Updated author and distribution location details to airspayce.com
|
||||
/// \version 1.32 Fixed a problem with enableOutputs() and setEnablePin on Arduino Due that
|
||||
/// prevented the enable pin changing stae correctly. Reported by Duane Bishop.
|
||||
/// \version 1.33 Fixed an error in example AFMotor_ConstantSpeed.pde did not setMaxSpeed();
|
||||
/// Fixed a problem that caused incorrect pin sequencing of FULL3WIRE and HALF3WIRE.
|
||||
/// Unfortunately this meant changing the signature for all step*() functions.
|
||||
/// Added example MotorShield, showing how to use AdaFruit Motor Shield to control
|
||||
/// a 3 phase motor such as a HDD spindle motor (and without using the AFMotor library.
|
||||
/// \version 1.34 Added setPinsInverted(bool pin1Invert, bool pin2Invert, bool pin3Invert, bool pin4Invert, bool enableInvert)
|
||||
/// to allow inversion of 2, 3 and 4 wire stepper pins. Requested by Oleg.
|
||||
/// \version 1.35 Removed default args from setPinsInverted(bool, bool, bool, bool, bool) to prevent ambiguity with
|
||||
/// setPinsInverted(bool, bool, bool). Reported by Mac Mac.
|
||||
/// \version 1.36 Changed enableOutputs() and disableOutputs() to be virtual so can be overridden.
|
||||
/// Added new optional argument 'enable' to constructor, which allows you toi disable the
|
||||
/// automatic enabling of outputs at construction time. Suggested by Guido.
|
||||
/// \version 1.37 Fixed a problem with step1 that could cause a rogue step in the
|
||||
/// wrong direction (or not,
|
||||
/// depending on the setup-time requirements of the connected hardware).
|
||||
/// Reported by Mark Tillotson.
|
||||
/// \version 1.38 run() function incorrectly always returned true. Updated function and doc so it returns true
|
||||
/// if the motor is still running to the target position.
|
||||
/// \version 1.39 Updated typos in keywords.txt, courtesey Jon Magill.
|
||||
/// \version 1.40 Updated documentation, including testing on Teensy 3.1
|
||||
/// \version 1.41 Fixed an error in the acceleration calculations, resulting in acceleration of haldf the intended value
|
||||
/// \version 1.42 Improved support for FULL3WIRE and HALF3WIRE output pins. These changes were in Yuri's original
|
||||
/// contribution but did not make it into production.<br>
|
||||
/// \version 1.43 Added DualMotorShield example. Shows how to use AccelStepper to control 2 x 2 phase steppers using the
|
||||
/// Itead Studio Arduino Dual Stepper Motor Driver Shield model IM120417015.<br>
|
||||
/// \version 1.44 examples/DualMotorShield/DualMotorShield.ino examples/DualMotorShield/DualMotorShield.pde
|
||||
/// was missing from the distribution.<br>
|
||||
/// \version 1.45 Fixed a problem where if setAcceleration was not called, there was no default
|
||||
/// acceleration. Reported by Michael Newman.<br>
|
||||
/// \version 1.45 Fixed inaccuracy in acceleration rate by using Equation 15, suggested by Sebastian Gracki.<br>
|
||||
/// Performance improvements in runSpeed suggested by Jaakko Fagerlund.<br>
|
||||
/// \version 1.46 Fixed error in documentation for runToPosition().
|
||||
/// Reinstated time calculations in runSpeed() since new version is reported
|
||||
/// not to work correctly under some circumstances. Reported by Oleg V Gavva.<br>
|
||||
/// \version 1.48 2015-08-25
|
||||
/// Added new class MultiStepper that can manage multiple AccelSteppers,
|
||||
/// and cause them all to move
|
||||
/// to selected positions at such a (constant) speed that they all arrive at their
|
||||
/// target position at the same time. Suitable for X-Y flatbeds etc.<br>
|
||||
/// Added new method maxSpeed() to AccelStepper to return the currently configured maxSpeed.<br>
|
||||
/// \version 1.49 2016-01-02
|
||||
/// Testing with VID28 series instrument stepper motors and EasyDriver.
|
||||
/// OK, although with light pointers
|
||||
/// and slow speeds like 180 full steps per second the motor movement can be erratic,
|
||||
/// probably due to some mechanical resonance. Best to accelerate through this speed.<br>
|
||||
/// Added isRunning().<br>
|
||||
/// \version 1.50 2016-02-25
|
||||
/// AccelStepper::disableOutputs now sets the enable pion to OUTPUT mode if the enable pin is defined.
|
||||
/// Patch from Piet De Jong.<br>
|
||||
/// Added notes about the fact that AFMotor_* examples do not work with Adafruit Motor Shield V2.<br>
|
||||
/// \version 1.51 2016-03-24
|
||||
/// Fixed a problem reported by gregor: when resetting the stepper motor position using setCurrentPosition() the
|
||||
/// stepper speed is reset by setting _stepInterval to 0, but _speed is not
|
||||
/// reset. this results in the stepper motor not starting again when calling
|
||||
/// setSpeed() with the same speed the stepper was set to before.
|
||||
/// \version 1.52 2016-08-09
|
||||
/// Added MultiStepper to keywords.txt.
|
||||
/// Improvements to efficiency of AccelStepper::runSpeed() as suggested by David Grayson.
|
||||
/// Improvements to speed accuracy as suggested by David Grayson.
|
||||
/// \version 1.53 2016-08-14
|
||||
/// Backed out Improvements to speed accuracy from 1.52 as it did not work correctly.
|
||||
/// \version 1.54 2017-01-24
|
||||
/// Fixed some warnings about unused arguments.
|
||||
/// \version 1.55 2017-01-25
|
||||
/// Fixed another warning in MultiStepper.cpp
|
||||
/// \version 1.56 2017-02-03
|
||||
/// Fixed minor documentation error with DIRECTION_CCW and DIRECTION_CW. Reported by David Mutterer.
|
||||
/// Added link to Binpress commercial license purchasing.
|
||||
/// \version 1.57 2017-03-28
|
||||
/// _direction moved to protected at the request of Rudy Ercek.
|
||||
/// setMaxSpeed() and setAcceleration() now correct negative values to be positive.
|
||||
/// \version 1.58 2018-04-13
|
||||
/// Add initialisation for _enableInverted in constructor.
|
||||
/// \version 1.59 2018-08-28
|
||||
/// Update commercial licensing, remove binpress.
|
||||
/// \version 1.60 2020-03-07
|
||||
/// Release under GPL V3
|
||||
/// \version 1.61 2020-04-20
|
||||
/// Added yield() call in runToPosition(), so that platforms like esp8266 dont hang/crash
|
||||
/// during long runs.
|
||||
/// \version 1.62 2022-05-22
|
||||
/// Added link to AccelStepper - The Missing Manual.<br>
|
||||
/// Fixed a problem when setting the maxSpeed to 1.0 due to incomplete initialisation.
|
||||
/// Reported by Olivier Pécheux. <br>
|
||||
/// \version 1.63 2022-06-30
|
||||
/// Added virtual destructor at the request of Jan.<br>
|
||||
/// \version 1.64 2022-10-31
|
||||
/// Patch courtesy acwest: Changes to make AccelStepper more subclassable. These changes are
|
||||
/// largely oriented to implementing new step-scheduling algorithms.
|
||||
///
|
||||
/// \author Mike McCauley (mikem@airspayce.com) DO NOT CONTACT THE AUTHOR DIRECTLY: USE THE GOOGLE GROUP
|
||||
// Copyright (C) 2009-2020 Mike McCauley
|
||||
// $Id: AccelStepper.h,v 1.28 2020/04/20 00:15:03 mikem Exp mikem $
|
||||
|
||||
#ifndef AccelStepper_h
|
||||
#define AccelStepper_h
|
||||
|
||||
#include <stdlib.h>
|
||||
#if ARDUINO >= 100
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <WProgram.h>
|
||||
#include <wiring.h>
|
||||
#endif
|
||||
|
||||
// These defs cause trouble on some versions of Arduino
|
||||
#undef round
|
||||
|
||||
// Use the system yield() whenever possoible, since some platforms require it for housekeeping, especially
|
||||
// ESP8266
|
||||
#if (defined(ARDUINO) && ARDUINO >= 155) || defined(ESP8266)
|
||||
#define YIELD yield();
|
||||
#else
|
||||
#define YIELD
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
/// \class AccelStepper AccelStepper.h <AccelStepper.h>
|
||||
/// \brief Support for stepper motors with acceleration etc.
|
||||
///
|
||||
/// This defines a single 2 or 4 pin stepper motor, or stepper moter with fdriver chip, with optional
|
||||
/// acceleration, deceleration, absolute positioning commands etc. Multiple
|
||||
/// simultaneous steppers are supported, all moving
|
||||
/// at different speeds and accelerations.
|
||||
///
|
||||
/// \par Operation
|
||||
/// This module operates by computing a step time in microseconds. The step
|
||||
/// time is recomputed after each step and after speed and acceleration
|
||||
/// parameters are changed by the caller. The time of each step is recorded in
|
||||
/// microseconds. The run() function steps the motor once if a new step is due.
|
||||
/// The run() function must be called frequently until the motor is in the
|
||||
/// desired position, after which time run() will do nothing.
|
||||
///
|
||||
/// \par Positioning
|
||||
/// Positions are specified by a signed long integer. At
|
||||
/// construction time, the current position of the motor is consider to be 0. Positive
|
||||
/// positions are clockwise from the initial position; negative positions are
|
||||
/// anticlockwise. The current position can be altered for instance after
|
||||
/// initialization positioning.
|
||||
///
|
||||
/// \par Caveats
|
||||
/// This is an open loop controller: If the motor stalls or is oversped,
|
||||
/// AccelStepper will not have a correct
|
||||
/// idea of where the motor really is (since there is no feedback of the motor's
|
||||
/// real position. We only know where we _think_ it is, relative to the
|
||||
/// initial starting point).
|
||||
///
|
||||
/// \par Performance
|
||||
/// The fastest motor speed that can be reliably supported is about 4000 steps per
|
||||
/// second at a clock frequency of 16 MHz on Arduino such as Uno etc.
|
||||
/// Faster processors can support faster stepping speeds.
|
||||
/// However, any speed less than that
|
||||
/// down to very slow speeds (much less than one per second) are also supported,
|
||||
/// provided the run() function is called frequently enough to step the motor
|
||||
/// whenever required for the speed set.
|
||||
/// Calling setAcceleration() is expensive,
|
||||
/// since it requires a square root to be calculated.
|
||||
///
|
||||
/// Gregor Christandl reports that with an Arduino Due and a simple test program,
|
||||
/// he measured 43163 steps per second using runSpeed(),
|
||||
/// and 16214 steps per second using run();
|
||||
class AccelStepper
|
||||
{
|
||||
public:
|
||||
/// \brief Symbolic names for number of pins.
|
||||
/// Use this in the pins argument the AccelStepper constructor to
|
||||
/// provide a symbolic name for the number of pins
|
||||
/// to use.
|
||||
typedef enum
|
||||
{
|
||||
FUNCTION = 0, ///< Use the functional interface, implementing your own driver functions (internal use only)
|
||||
DRIVER = 1, ///< Stepper Driver, 2 driver pins required
|
||||
FULL2WIRE = 2, ///< 2 wire stepper, 2 motor pins required
|
||||
FULL3WIRE = 3, ///< 3 wire stepper, such as HDD spindle, 3 motor pins required
|
||||
FULL4WIRE = 4, ///< 4 wire full stepper, 4 motor pins required
|
||||
HALF3WIRE = 6, ///< 3 wire half stepper, such as HDD spindle, 3 motor pins required
|
||||
HALF4WIRE = 8 ///< 4 wire half stepper, 4 motor pins required
|
||||
} MotorInterfaceType;
|
||||
|
||||
/// Constructor. You can have multiple simultaneous steppers, all moving
|
||||
/// at different speeds and accelerations, provided you call their run()
|
||||
/// functions at frequent enough intervals. Current Position is set to 0, target
|
||||
/// position is set to 0. MaxSpeed and Acceleration default to 1.0.
|
||||
/// The motor pins will be initialised to OUTPUT mode during the
|
||||
/// constructor by a call to enableOutputs().
|
||||
/// \param[in] interface Number of pins to interface to. Integer values are
|
||||
/// supported, but it is preferred to use the \ref MotorInterfaceType symbolic names.
|
||||
/// AccelStepper::DRIVER (1) means a stepper driver (with Step and Direction pins).
|
||||
/// If an enable line is also needed, call setEnablePin() after construction.
|
||||
/// You may also invert the pins using setPinsInverted().
|
||||
/// Caution: DRIVER implements a blocking delay of minPulseWidth microseconds (default 1us) for each step.
|
||||
/// You can change this with setMinPulseWidth().
|
||||
/// AccelStepper::FULL2WIRE (2) means a 2 wire stepper (2 pins required).
|
||||
/// AccelStepper::FULL3WIRE (3) means a 3 wire stepper, such as HDD spindle (3 pins required).
|
||||
/// AccelStepper::FULL4WIRE (4) means a 4 wire stepper (4 pins required).
|
||||
/// AccelStepper::HALF3WIRE (6) means a 3 wire half stepper, such as HDD spindle (3 pins required)
|
||||
/// AccelStepper::HALF4WIRE (8) means a 4 wire half stepper (4 pins required)
|
||||
/// Defaults to AccelStepper::FULL4WIRE (4) pins.
|
||||
/// \param[in] pin1 Arduino digital pin number for motor pin 1. Defaults
|
||||
/// to pin 2. For a AccelStepper::DRIVER (interface==1),
|
||||
/// this is the Step input to the driver. Low to high transition means to step)
|
||||
/// \param[in] pin2 Arduino digital pin number for motor pin 2. Defaults
|
||||
/// to pin 3. For a AccelStepper::DRIVER (interface==1),
|
||||
/// this is the Direction input the driver. High means forward.
|
||||
/// \param[in] pin3 Arduino digital pin number for motor pin 3. Defaults
|
||||
/// to pin 4.
|
||||
/// \param[in] pin4 Arduino digital pin number for motor pin 4. Defaults
|
||||
/// to pin 5.
|
||||
/// \param[in] enable If this is true (the default), enableOutputs() will be called to enable
|
||||
/// the output pins at construction time.
|
||||
AccelStepper(uint8_t interface = AccelStepper::FULL4WIRE, uint8_t pin1 = 2, uint8_t pin2 = 3, uint8_t pin3 = 4, uint8_t pin4 = 5, bool enable = true);
|
||||
|
||||
/// Alternate Constructor which will call your own functions for forward and backward steps.
|
||||
/// You can have multiple simultaneous steppers, all moving
|
||||
/// at different speeds and accelerations, provided you call their run()
|
||||
/// functions at frequent enough intervals. Current Position is set to 0, target
|
||||
/// position is set to 0. MaxSpeed and Acceleration default to 1.0.
|
||||
/// Any motor initialization should happen before hand, no pins are used or initialized.
|
||||
/// \param[in] forward void-returning procedure that will make a forward step
|
||||
/// \param[in] backward void-returning procedure that will make a backward step
|
||||
AccelStepper(void (*forward)(), void (*backward)());
|
||||
|
||||
/// Set the target position. The run() function will try to move the motor (at most one step per call)
|
||||
/// from the current position to the target position set by the most
|
||||
/// recent call to this function. Caution: moveTo() also recalculates the speed for the next step.
|
||||
/// If you are trying to use constant speed movements, you should call setSpeed() after calling moveTo().
|
||||
/// \param[in] absolute The desired absolute position. Negative is
|
||||
/// anticlockwise from the 0 position.
|
||||
void moveTo(long absolute);
|
||||
|
||||
/// Set the target position relative to the current position.
|
||||
/// \param[in] relative The desired position relative to the current position. Negative is
|
||||
/// anticlockwise from the current position.
|
||||
void move(long relative);
|
||||
|
||||
/// Poll the motor and step it if a step is due, implementing
|
||||
/// accelerations and decelerations to achieve the target position. You must call this as
|
||||
/// frequently as possible, but at least once per minimum step time interval,
|
||||
/// preferably in your main loop. Note that each call to run() will make at most one step, and then only when a step is due,
|
||||
/// based on the current speed and the time since the last step.
|
||||
/// \return true if the motor is still running to the target position.
|
||||
boolean run();
|
||||
|
||||
/// Poll the motor and step it if a step is due, implementing a constant
|
||||
/// speed as set by the most recent call to setSpeed(). You must call this as
|
||||
/// frequently as possible, but at least once per step interval,
|
||||
/// \return true if the motor was stepped.
|
||||
boolean runSpeed();
|
||||
|
||||
/// Sets the maximum permitted speed. The run() function will accelerate
|
||||
/// up to the speed set by this function.
|
||||
/// Caution: the maximum speed achievable depends on your processor and clock speed.
|
||||
/// The default maxSpeed is 1.0 steps per second.
|
||||
/// \param[in] speed The desired maximum speed in steps per second. Must
|
||||
/// be > 0. Caution: Speeds that exceed the maximum speed supported by the processor may
|
||||
/// Result in non-linear accelerations and decelerations.
|
||||
void setMaxSpeed(float speed);
|
||||
|
||||
/// Returns the maximum speed configured for this stepper
|
||||
/// that was previously set by setMaxSpeed();
|
||||
/// \return The currently configured maximum speed
|
||||
float maxSpeed();
|
||||
|
||||
/// Sets the acceleration/deceleration rate.
|
||||
/// \param[in] acceleration The desired acceleration in steps per second
|
||||
/// per second. Must be > 0.0. This is an expensive call since it requires a square
|
||||
/// root to be calculated. Dont call more ofthen than needed
|
||||
void setAcceleration(float acceleration);
|
||||
|
||||
/// Returns the acceleration/deceleration rate configured for this stepper
|
||||
/// that was previously set by setAcceleration();
|
||||
/// \return The currently configured acceleration/deceleration
|
||||
float acceleration();
|
||||
|
||||
/// Sets the desired constant speed for use with runSpeed().
|
||||
/// \param[in] speed The desired constant speed in steps per
|
||||
/// second. Positive is clockwise. Speeds of more than 1000 steps per
|
||||
/// second are unreliable. Very slow speeds may be set (eg 0.00027777 for
|
||||
/// once per hour, approximately. Speed accuracy depends on the Arduino
|
||||
/// crystal. Jitter depends on how frequently you call the runSpeed() function.
|
||||
/// The speed will be limited by the current value of setMaxSpeed()
|
||||
void setSpeed(float speed);
|
||||
|
||||
/// The most recently set speed.
|
||||
/// \return the most recent speed in steps per second
|
||||
float speed();
|
||||
|
||||
/// The distance from the current position to the target position.
|
||||
/// \return the distance from the current position to the target position
|
||||
/// in steps. Positive is clockwise from the current position.
|
||||
long distanceToGo();
|
||||
|
||||
/// The most recently set target position.
|
||||
/// \return the target position
|
||||
/// in steps. Positive is clockwise from the 0 position.
|
||||
long targetPosition();
|
||||
|
||||
/// The current motor position.
|
||||
/// \return the current motor position
|
||||
/// in steps. Positive is clockwise from the 0 position.
|
||||
long currentPosition();
|
||||
|
||||
/// Resets the current position of the motor, so that wherever the motor
|
||||
/// happens to be right now is considered to be the new 0 position. Useful
|
||||
/// for setting a zero position on a stepper after an initial hardware
|
||||
/// positioning move.
|
||||
/// Has the side effect of setting the current motor speed to 0.
|
||||
/// \param[in] position The position in steps of wherever the motor
|
||||
/// happens to be right now.
|
||||
void setCurrentPosition(long position);
|
||||
|
||||
/// Moves the motor (with acceleration/deceleration)
|
||||
/// to the target position and blocks until it is at
|
||||
/// position. Dont use this in event loops, since it blocks.
|
||||
void runToPosition();
|
||||
|
||||
/// Executes runSpeed() unless the targetPosition is reached.
|
||||
/// This function needs to be called often just like runSpeed() or run().
|
||||
/// Will step the motor if a step is required at the currently selected
|
||||
/// speed unless the target position has been reached.
|
||||
/// Does not implement accelerations.
|
||||
/// \return true if it stepped
|
||||
boolean runSpeedToPosition();
|
||||
|
||||
/// Moves the motor (with acceleration/deceleration)
|
||||
/// to the new target position and blocks until it is at
|
||||
/// position. Dont use this in event loops, since it blocks.
|
||||
/// \param[in] position The new target position.
|
||||
void runToNewPosition(long position);
|
||||
|
||||
/// Sets a new target position that causes the stepper
|
||||
/// to stop as quickly as possible, using the current speed and acceleration parameters.
|
||||
void stop();
|
||||
|
||||
/// Disable motor pin outputs by setting them all LOW
|
||||
/// Depending on the design of your electronics this may turn off
|
||||
/// the power to the motor coils, saving power.
|
||||
/// This is useful to support Arduino low power modes: disable the outputs
|
||||
/// during sleep and then reenable with enableOutputs() before stepping
|
||||
/// again.
|
||||
/// If the enable Pin is defined, sets it to OUTPUT mode and clears the pin to disabled.
|
||||
virtual void disableOutputs();
|
||||
|
||||
/// Enable motor pin outputs by setting the motor pins to OUTPUT
|
||||
/// mode. Called automatically by the constructor.
|
||||
/// If the enable Pin is defined, sets it to OUTPUT mode and sets the pin to enabled.
|
||||
virtual void enableOutputs();
|
||||
|
||||
/// Sets the minimum pulse width allowed by the stepper driver. The minimum practical pulse width is
|
||||
/// approximately 20 microseconds. Times less than 20 microseconds
|
||||
/// will usually result in 20 microseconds or so.
|
||||
/// \param[in] minWidth The minimum pulse width in microseconds.
|
||||
void setMinPulseWidth(unsigned int minWidth);
|
||||
|
||||
/// Sets the enable pin number for stepper drivers.
|
||||
/// 0xFF indicates unused (default).
|
||||
/// Otherwise, if a pin is set, the pin will be turned on when
|
||||
/// enableOutputs() is called and switched off when disableOutputs()
|
||||
/// is called.
|
||||
/// \param[in] enablePin Arduino digital pin number for motor enable
|
||||
/// \sa setPinsInverted
|
||||
void setEnablePin(uint8_t enablePin = 0xff);
|
||||
|
||||
/// Sets the inversion for stepper driver pins
|
||||
/// \param[in] directionInvert True for inverted direction pin, false for non-inverted
|
||||
/// \param[in] stepInvert True for inverted step pin, false for non-inverted
|
||||
/// \param[in] enableInvert True for inverted enable pin, false (default) for non-inverted
|
||||
void setPinsInverted(bool directionInvert = false, bool stepInvert = false, bool enableInvert = false);
|
||||
|
||||
/// Sets the inversion for 2, 3 and 4 wire stepper pins
|
||||
/// \param[in] pin1Invert True for inverted pin1, false for non-inverted
|
||||
/// \param[in] pin2Invert True for inverted pin2, false for non-inverted
|
||||
/// \param[in] pin3Invert True for inverted pin3, false for non-inverted
|
||||
/// \param[in] pin4Invert True for inverted pin4, false for non-inverted
|
||||
/// \param[in] enableInvert True for inverted enable pin, false (default) for non-inverted
|
||||
void setPinsInverted(bool pin1Invert, bool pin2Invert, bool pin3Invert, bool pin4Invert, bool enableInvert);
|
||||
|
||||
/// Checks to see if the motor is currently running to a target
|
||||
/// \return true if the speed is not zero or not at the target position
|
||||
bool isRunning();
|
||||
|
||||
/// Virtual destructor to prevent warnings during delete
|
||||
virtual ~AccelStepper() {};
|
||||
protected:
|
||||
|
||||
/// \brief Direction indicator
|
||||
/// Symbolic names for the direction the motor is turning
|
||||
typedef enum
|
||||
{
|
||||
DIRECTION_CCW = 0, ///< Counter-Clockwise
|
||||
DIRECTION_CW = 1 ///< Clockwise
|
||||
} Direction;
|
||||
|
||||
/// Forces the library to compute a new instantaneous speed and set that as
|
||||
/// the current speed. It is called by
|
||||
/// the library:
|
||||
/// \li after each step
|
||||
/// \li after change to maxSpeed through setMaxSpeed()
|
||||
/// \li after change to acceleration through setAcceleration()
|
||||
/// \li after change to target position (relative or absolute) through
|
||||
/// move() or moveTo()
|
||||
/// \return the new step interval
|
||||
virtual unsigned long computeNewSpeed();
|
||||
|
||||
/// Low level function to set the motor output pins
|
||||
/// bit 0 of the mask corresponds to _pin[0]
|
||||
/// bit 1 of the mask corresponds to _pin[1]
|
||||
/// You can override this to impment, for example serial chip output insted of using the
|
||||
/// output pins directly
|
||||
virtual void setOutputPins(uint8_t mask);
|
||||
|
||||
/// Called to execute a step. Only called when a new step is
|
||||
/// required. Subclasses may override to implement new stepping
|
||||
/// interfaces. The default calls step1(), step2(), step4() or step8() depending on the
|
||||
/// number of pins defined for the stepper.
|
||||
/// \param[in] step The current step phase number (0 to 7)
|
||||
virtual void step(long step);
|
||||
|
||||
/// Called to execute a clockwise(+) step. Only called when a new step is
|
||||
/// required. This increments the _currentPos and calls step()
|
||||
/// \return the updated current position
|
||||
long stepForward();
|
||||
|
||||
/// Called to execute a counter-clockwise(-) step. Only called when a new step is
|
||||
/// required. This decrements the _currentPos and calls step()
|
||||
/// \return the updated current position
|
||||
long stepBackward();
|
||||
|
||||
/// Called to execute a step using stepper functions (pins = 0) Only called when a new step is
|
||||
/// required. Calls _forward() or _backward() to perform the step
|
||||
/// \param[in] step The current step phase number (0 to 7)
|
||||
virtual void step0(long step);
|
||||
|
||||
/// Called to execute a step on a stepper driver (ie where pins == 1). Only called when a new step is
|
||||
/// required. Subclasses may override to implement new stepping
|
||||
/// interfaces. The default sets or clears the outputs of Step pin1 to step,
|
||||
/// and sets the output of _pin2 to the desired direction. The Step pin (_pin1) is pulsed for 1 microsecond
|
||||
/// which is the minimum STEP pulse width for the 3967 driver.
|
||||
/// \param[in] step The current step phase number (0 to 7)
|
||||
virtual void step1(long step);
|
||||
|
||||
/// Called to execute a step on a 2 pin motor. Only called when a new step is
|
||||
/// required. Subclasses may override to implement new stepping
|
||||
/// interfaces. The default sets or clears the outputs of pin1 and pin2
|
||||
/// \param[in] step The current step phase number (0 to 7)
|
||||
virtual void step2(long step);
|
||||
|
||||
/// Called to execute a step on a 3 pin motor, such as HDD spindle. Only called when a new step is
|
||||
/// required. Subclasses may override to implement new stepping
|
||||
/// interfaces. The default sets or clears the outputs of pin1, pin2,
|
||||
/// pin3
|
||||
/// \param[in] step The current step phase number (0 to 7)
|
||||
virtual void step3(long step);
|
||||
|
||||
/// Called to execute a step on a 4 pin motor. Only called when a new step is
|
||||
/// required. Subclasses may override to implement new stepping
|
||||
/// interfaces. The default sets or clears the outputs of pin1, pin2,
|
||||
/// pin3, pin4.
|
||||
/// \param[in] step The current step phase number (0 to 7)
|
||||
virtual void step4(long step);
|
||||
|
||||
/// Called to execute a step on a 3 pin motor, such as HDD spindle. Only called when a new step is
|
||||
/// required. Subclasses may override to implement new stepping
|
||||
/// interfaces. The default sets or clears the outputs of pin1, pin2,
|
||||
/// pin3
|
||||
/// \param[in] step The current step phase number (0 to 7)
|
||||
virtual void step6(long step);
|
||||
|
||||
/// Called to execute a step on a 4 pin half-stepper motor. Only called when a new step is
|
||||
/// required. Subclasses may override to implement new stepping
|
||||
/// interfaces. The default sets or clears the outputs of pin1, pin2,
|
||||
/// pin3, pin4.
|
||||
/// \param[in] step The current step phase number (0 to 7)
|
||||
virtual void step8(long step);
|
||||
|
||||
/// Current direction motor is spinning in
|
||||
/// Protected because some peoples subclasses need it to be so
|
||||
boolean _direction; // 1 == CW
|
||||
|
||||
/// The current interval between steps in microseconds.
|
||||
/// 0 means the motor is currently stopped with _speed == 0
|
||||
unsigned long _stepInterval;
|
||||
|
||||
private:
|
||||
/// Number of pins on the stepper motor. Permits 2 or 4. 2 pins is a
|
||||
/// bipolar, and 4 pins is a unipolar.
|
||||
uint8_t _interface; // 0, 1, 2, 4, 8, See MotorInterfaceType
|
||||
|
||||
/// Arduino pin number assignments for the 2 or 4 pins required to interface to the
|
||||
/// stepper motor or driver
|
||||
uint8_t _pin[4];
|
||||
|
||||
/// Whether the _pins is inverted or not
|
||||
uint8_t _pinInverted[4];
|
||||
|
||||
/// The current absolution position in steps.
|
||||
long _currentPos; // Steps
|
||||
|
||||
/// The target position in steps. The AccelStepper library will move the
|
||||
/// motor from the _currentPos to the _targetPos, taking into account the
|
||||
/// max speed, acceleration and deceleration
|
||||
long _targetPos; // Steps
|
||||
|
||||
/// The current motos speed in steps per second
|
||||
/// Positive is clockwise
|
||||
float _speed; // Steps per second
|
||||
|
||||
/// The maximum permitted speed in steps per second. Must be > 0.
|
||||
float _maxSpeed;
|
||||
|
||||
/// The acceleration to use to accelerate or decelerate the motor in steps
|
||||
/// per second per second. Must be > 0
|
||||
float _acceleration;
|
||||
float _sqrt_twoa; // Precomputed sqrt(2*_acceleration)
|
||||
|
||||
/// The last step time in microseconds
|
||||
unsigned long _lastStepTime;
|
||||
|
||||
/// The minimum allowed pulse width in microseconds
|
||||
unsigned int _minPulseWidth;
|
||||
|
||||
/// Is the direction pin inverted?
|
||||
///bool _dirInverted; /// Moved to _pinInverted[1]
|
||||
|
||||
/// Is the step pin inverted?
|
||||
///bool _stepInverted; /// Moved to _pinInverted[0]
|
||||
|
||||
/// Is the enable pin inverted?
|
||||
bool _enableInverted;
|
||||
|
||||
/// Enable pin for stepper driver, or 0xFF if unused.
|
||||
uint8_t _enablePin;
|
||||
|
||||
/// The pointer to a forward-step procedure
|
||||
void (*_forward)();
|
||||
|
||||
/// The pointer to a backward-step procedure
|
||||
void (*_backward)();
|
||||
|
||||
/// The step counter for speed calculations
|
||||
long _n;
|
||||
|
||||
/// Initial step size in microseconds
|
||||
float _c0;
|
||||
|
||||
/// Last step size in microseconds
|
||||
float _cn;
|
||||
|
||||
/// Min step size in microseconds based on maxSpeed
|
||||
float _cmin; // at max speed
|
||||
|
||||
};
|
||||
|
||||
/// @example Random.pde
|
||||
/// Make a single stepper perform random changes in speed, position and acceleration
|
||||
|
||||
/// @example Overshoot.pde
|
||||
/// Check overshoot handling
|
||||
/// which sets a new target position and then waits until the stepper has
|
||||
/// achieved it. This is used for testing the handling of overshoots
|
||||
|
||||
/// @example MultipleSteppers.pde
|
||||
/// Shows how to multiple simultaneous steppers
|
||||
/// Runs one stepper forwards and backwards, accelerating and decelerating
|
||||
/// at the limits. Runs other steppers at the same time
|
||||
|
||||
/// @example ConstantSpeed.pde
|
||||
/// Shows how to run AccelStepper in the simplest,
|
||||
/// fixed speed mode with no accelerations
|
||||
|
||||
/// @example Blocking.pde
|
||||
/// Shows how to use the blocking call runToNewPosition
|
||||
/// Which sets a new target position and then waits until the stepper has
|
||||
/// achieved it.
|
||||
|
||||
/// @example AFMotor_MultiStepper.pde
|
||||
/// Control both Stepper motors at the same time with different speeds
|
||||
/// and accelerations.
|
||||
|
||||
/// @example AFMotor_ConstantSpeed.pde
|
||||
/// Shows how to run AccelStepper in the simplest,
|
||||
/// fixed speed mode with no accelerations
|
||||
|
||||
/// @example ProportionalControl.pde
|
||||
/// Make a single stepper follow the analog value read from a pot or whatever
|
||||
/// The stepper will move at a constant speed to each newly set posiiton,
|
||||
/// depending on the value of the pot.
|
||||
|
||||
/// @example Bounce.pde
|
||||
/// Make a single stepper bounce from one limit to another, observing
|
||||
/// accelrations at each end of travel
|
||||
|
||||
/// @example Quickstop.pde
|
||||
/// Check stop handling.
|
||||
/// Calls stop() while the stepper is travelling at full speed, causing
|
||||
/// the stepper to stop as quickly as possible, within the constraints of the
|
||||
/// current acceleration.
|
||||
|
||||
/// @example MotorShield.pde
|
||||
/// Shows how to use AccelStepper to control a 3-phase motor, such as a HDD spindle motor
|
||||
/// using the Adafruit Motor Shield http://www.ladyada.net/make/mshield/index.html.
|
||||
|
||||
/// @example DualMotorShield.pde
|
||||
/// Shows how to use AccelStepper to control 2 x 2 phase steppers using the
|
||||
/// Itead Studio Arduino Dual Stepper Motor Driver Shield
|
||||
/// model IM120417015
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,317 @@
|
||||
#ifndef DallasTemperature_h
|
||||
#define DallasTemperature_h
|
||||
|
||||
#define DALLASTEMPLIBVERSION "3.8.1" // To be deprecated -> TODO remove in 4.0.0
|
||||
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
// set to true to include code for new and delete operators
|
||||
#ifndef REQUIRESNEW
|
||||
#define REQUIRESNEW false
|
||||
#endif
|
||||
|
||||
// set to true to include code implementing alarm search functions
|
||||
#ifndef REQUIRESALARMS
|
||||
#define REQUIRESALARMS true
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#ifdef __STM32F1__
|
||||
#include <OneWireSTM.h>
|
||||
#else
|
||||
#include <OneWire.h>
|
||||
#endif
|
||||
|
||||
// Model IDs
|
||||
#define DS18S20MODEL 0x10 // also DS1820
|
||||
#define DS18B20MODEL 0x28 // also MAX31820
|
||||
#define DS1822MODEL 0x22
|
||||
#define DS1825MODEL 0x3B
|
||||
#define DS28EA00MODEL 0x42
|
||||
|
||||
// Error Codes
|
||||
#define DEVICE_DISCONNECTED_C -127
|
||||
#define DEVICE_DISCONNECTED_F -196.6
|
||||
#define DEVICE_DISCONNECTED_RAW -7040
|
||||
|
||||
// For readPowerSupply on oneWire bus
|
||||
// definition of nullptr for C++ < 11, using official workaround:
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf
|
||||
#if __cplusplus < 201103L
|
||||
const class
|
||||
{
|
||||
public:
|
||||
template <class T>
|
||||
operator T *() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
template <class C, class T>
|
||||
operator T C::*() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
void operator&() const;
|
||||
} nullptr = {};
|
||||
#endif
|
||||
|
||||
typedef uint8_t DeviceAddress[8];
|
||||
|
||||
class DallasTemperature {
|
||||
public:
|
||||
|
||||
DallasTemperature();
|
||||
DallasTemperature(OneWire*);
|
||||
DallasTemperature(OneWire*, uint8_t);
|
||||
|
||||
void setOneWire(OneWire*);
|
||||
|
||||
void setPullupPin(uint8_t);
|
||||
|
||||
// initialise bus
|
||||
void begin(void);
|
||||
|
||||
// returns the number of devices found on the bus
|
||||
uint8_t getDeviceCount(void);
|
||||
|
||||
// returns the number of DS18xxx Family devices on bus
|
||||
uint8_t getDS18Count(void);
|
||||
|
||||
// returns true if address is valid
|
||||
bool validAddress(const uint8_t*);
|
||||
|
||||
// returns true if address is of the family of sensors the lib supports.
|
||||
bool validFamily(const uint8_t* deviceAddress);
|
||||
|
||||
// finds an address at a given index on the bus
|
||||
bool getAddress(uint8_t*, uint8_t);
|
||||
|
||||
// attempt to determine if the device at the given address is connected to the bus
|
||||
bool isConnected(const uint8_t*);
|
||||
|
||||
// attempt to determine if the device at the given address is connected to the bus
|
||||
// also allows for updating the read scratchpad
|
||||
bool isConnected(const uint8_t*, uint8_t*);
|
||||
|
||||
// read device's scratchpad
|
||||
bool readScratchPad(const uint8_t*, uint8_t*);
|
||||
|
||||
// write device's scratchpad
|
||||
void writeScratchPad(const uint8_t*, const uint8_t*);
|
||||
|
||||
// read device's power requirements
|
||||
bool readPowerSupply(const uint8_t* deviceAddress = nullptr);
|
||||
|
||||
// get global resolution
|
||||
uint8_t getResolution();
|
||||
|
||||
// set global resolution to 9, 10, 11, or 12 bits
|
||||
void setResolution(uint8_t);
|
||||
|
||||
// returns the device resolution: 9, 10, 11, or 12 bits
|
||||
uint8_t getResolution(const uint8_t*);
|
||||
|
||||
// set resolution of a device to 9, 10, 11, or 12 bits
|
||||
bool setResolution(const uint8_t*, uint8_t,
|
||||
bool skipGlobalBitResolutionCalculation = false);
|
||||
|
||||
// sets/gets the waitForConversion flag
|
||||
void setWaitForConversion(bool);
|
||||
bool getWaitForConversion(void);
|
||||
|
||||
// sets/gets the checkForConversion flag
|
||||
void setCheckForConversion(bool);
|
||||
bool getCheckForConversion(void);
|
||||
|
||||
// sends command for all devices on the bus to perform a temperature conversion
|
||||
void requestTemperatures(void);
|
||||
|
||||
// sends command for one device to perform a temperature conversion by address
|
||||
bool requestTemperaturesByAddress(const uint8_t*);
|
||||
|
||||
// sends command for one device to perform a temperature conversion by index
|
||||
bool requestTemperaturesByIndex(uint8_t);
|
||||
|
||||
// returns temperature raw value (12 bit integer of 1/128 degrees C)
|
||||
int16_t getTemp(const uint8_t*);
|
||||
|
||||
// returns temperature in degrees C
|
||||
float getTempC(const uint8_t*);
|
||||
|
||||
// returns temperature in degrees F
|
||||
float getTempF(const uint8_t*);
|
||||
|
||||
// Get temperature for device index (slow)
|
||||
float getTempCByIndex(uint8_t);
|
||||
|
||||
// Get temperature for device index (slow)
|
||||
float getTempFByIndex(uint8_t);
|
||||
|
||||
// returns true if the bus requires parasite power
|
||||
bool isParasitePowerMode(void);
|
||||
|
||||
// Is a conversion complete on the wire? Only applies to the first sensor on the wire.
|
||||
bool isConversionComplete(void);
|
||||
|
||||
int16_t millisToWaitForConversion(uint8_t);
|
||||
|
||||
// Sends command to one device to save values from scratchpad to EEPROM by index
|
||||
// Returns true if no errors were encountered, false indicates failure
|
||||
bool saveScratchPadByIndex(uint8_t);
|
||||
|
||||
// Sends command to one or more devices to save values from scratchpad to EEPROM
|
||||
// Returns true if no errors were encountered, false indicates failure
|
||||
bool saveScratchPad(const uint8_t* = nullptr);
|
||||
|
||||
// Sends command to one device to recall values from EEPROM to scratchpad by index
|
||||
// Returns true if no errors were encountered, false indicates failure
|
||||
bool recallScratchPadByIndex(uint8_t);
|
||||
|
||||
// Sends command to one or more devices to recall values from EEPROM to scratchpad
|
||||
// Returns true if no errors were encountered, false indicates failure
|
||||
bool recallScratchPad(const uint8_t* = nullptr);
|
||||
|
||||
// Sets the autoSaveScratchPad flag
|
||||
void setAutoSaveScratchPad(bool);
|
||||
|
||||
// Gets the autoSaveScratchPad flag
|
||||
bool getAutoSaveScratchPad(void);
|
||||
|
||||
#if REQUIRESALARMS
|
||||
|
||||
typedef void AlarmHandler(const uint8_t*);
|
||||
|
||||
// sets the high alarm temperature for a device
|
||||
// accepts a int8_t. valid range is -55C - 125C
|
||||
void setHighAlarmTemp(const uint8_t*, int8_t);
|
||||
|
||||
// sets the low alarm temperature for a device
|
||||
// accepts a int8_t. valid range is -55C - 125C
|
||||
void setLowAlarmTemp(const uint8_t*, int8_t);
|
||||
|
||||
// returns a int8_t with the current high alarm temperature for a device
|
||||
// in the range -55C - 125C
|
||||
int8_t getHighAlarmTemp(const uint8_t*);
|
||||
|
||||
// returns a int8_t with the current low alarm temperature for a device
|
||||
// in the range -55C - 125C
|
||||
int8_t getLowAlarmTemp(const uint8_t*);
|
||||
|
||||
// resets internal variables used for the alarm search
|
||||
void resetAlarmSearch(void);
|
||||
|
||||
// search the wire for devices with active alarms
|
||||
bool alarmSearch(uint8_t*);
|
||||
|
||||
// returns true if ia specific device has an alarm
|
||||
bool hasAlarm(const uint8_t*);
|
||||
|
||||
// returns true if any device is reporting an alarm on the bus
|
||||
bool hasAlarm(void);
|
||||
|
||||
// runs the alarm handler for all devices returned by alarmSearch()
|
||||
void processAlarms(void);
|
||||
|
||||
// sets the alarm handler
|
||||
void setAlarmHandler(const AlarmHandler *);
|
||||
|
||||
// returns true if an AlarmHandler has been set
|
||||
bool hasAlarmHandler();
|
||||
|
||||
#endif
|
||||
|
||||
// if no alarm handler is used the two bytes can be used as user data
|
||||
// example of such usage is an ID.
|
||||
// note if device is not connected it will fail writing the data.
|
||||
// note if address cannot be found no error will be reported.
|
||||
// in short use carefully
|
||||
void setUserData(const uint8_t*, int16_t);
|
||||
void setUserDataByIndex(uint8_t, int16_t);
|
||||
int16_t getUserData(const uint8_t*);
|
||||
int16_t getUserDataByIndex(uint8_t);
|
||||
|
||||
// convert from Celsius to Fahrenheit
|
||||
static float toFahrenheit(float);
|
||||
|
||||
// convert from Fahrenheit to Celsius
|
||||
static float toCelsius(float);
|
||||
|
||||
// convert from raw to Celsius
|
||||
static float rawToCelsius(int16_t);
|
||||
|
||||
// convert from raw to Fahrenheit
|
||||
static float rawToFahrenheit(int16_t);
|
||||
|
||||
#if REQUIRESNEW
|
||||
|
||||
// initialize memory area
|
||||
void* operator new (unsigned int);
|
||||
|
||||
// delete memory reference
|
||||
void operator delete(void*);
|
||||
|
||||
#endif
|
||||
|
||||
private:
|
||||
typedef uint8_t ScratchPad[9];
|
||||
|
||||
// parasite power on or off
|
||||
bool parasite;
|
||||
|
||||
// external pullup
|
||||
bool useExternalPullup;
|
||||
uint8_t pullupPin;
|
||||
|
||||
// used to determine the delay amount needed to allow for the
|
||||
// temperature conversion to take place
|
||||
uint8_t bitResolution;
|
||||
|
||||
// used to requestTemperature with or without delay
|
||||
bool waitForConversion;
|
||||
|
||||
// used to requestTemperature to dynamically check if a conversion is complete
|
||||
bool checkForConversion;
|
||||
|
||||
// used to determine if values will be saved from scratchpad to EEPROM on every scratchpad write
|
||||
bool autoSaveScratchPad;
|
||||
|
||||
// count of devices on the bus
|
||||
uint8_t devices;
|
||||
|
||||
// count of DS18xxx Family devices on bus
|
||||
uint8_t ds18Count;
|
||||
|
||||
// Take a pointer to one wire instance
|
||||
OneWire* _wire;
|
||||
|
||||
// reads scratchpad and returns the raw temperature
|
||||
int16_t calculateTemperature(const uint8_t*, uint8_t*);
|
||||
|
||||
void blockTillConversionComplete(uint8_t);
|
||||
|
||||
// Returns true if all bytes of scratchPad are '\0'
|
||||
bool isAllZeros(const uint8_t* const scratchPad, const size_t length = 9);
|
||||
|
||||
// External pullup control
|
||||
void activateExternalPullup(void);
|
||||
void deactivateExternalPullup(void);
|
||||
|
||||
#if REQUIRESALARMS
|
||||
|
||||
// required for alarmSearch
|
||||
uint8_t alarmSearchAddress[8];
|
||||
int8_t alarmSearchJunction;
|
||||
uint8_t alarmSearchExhausted;
|
||||
|
||||
// the alarm handler function pointer
|
||||
AlarmHandler *_AlarmHandler;
|
||||
|
||||
#endif
|
||||
|
||||
};
|
||||
#endif
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
|| @file Key.cpp
|
||||
|| @version 1.0
|
||||
|| @author Mark Stanley
|
||||
|| @contact mstanley@technologist.com
|
||||
||
|
||||
|| @description
|
||||
|| | Key class provides an abstract definition of a key or button
|
||||
|| | and was initially designed to be used in conjunction with a
|
||||
|| | state-machine.
|
||||
|| #
|
||||
||
|
||||
|| @license
|
||||
|| | This library is free software; you can redistribute it and/or
|
||||
|| | modify it under the terms of the GNU Lesser General Public
|
||||
|| | License as published by the Free Software Foundation; version
|
||||
|| | 2.1 of the License.
|
||||
|| |
|
||||
|| | This library is distributed in the hope that it will be useful,
|
||||
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|| | Lesser General Public License for more details.
|
||||
|| |
|
||||
|| | You should have received a copy of the GNU Lesser General Public
|
||||
|| | License along with this library; if not, write to the Free Software
|
||||
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|| #
|
||||
||
|
||||
*/
|
||||
#include <Key.h>
|
||||
|
||||
|
||||
// default constructor
|
||||
Key::Key() {
|
||||
kchar = NO_KEY;
|
||||
kstate = IDLE;
|
||||
stateChanged = false;
|
||||
}
|
||||
|
||||
// constructor
|
||||
Key::Key(char userKeyChar) {
|
||||
kchar = userKeyChar;
|
||||
kcode = -1;
|
||||
kstate = IDLE;
|
||||
stateChanged = false;
|
||||
}
|
||||
|
||||
|
||||
void Key::key_update (char userKeyChar, KeyState userState, boolean userStatus) {
|
||||
kchar = userKeyChar;
|
||||
kstate = userState;
|
||||
stateChanged = userStatus;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|| @changelog
|
||||
|| | 1.0 2012-06-04 - Mark Stanley : Initial Release
|
||||
|| #
|
||||
*/
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
||
|
||||
|| @file Keypad.cpp
|
||||
|| @version 3.1
|
||||
|| @author Mark Stanley, Alexander Brevig
|
||||
|| @contact mstanley@technologist.com, alexanderbrevig@gmail.com
|
||||
||
|
||||
|| @description
|
||||
|| | This library provides a simple interface for using matrix
|
||||
|| | keypads. It supports multiple keypresses while maintaining
|
||||
|| | backwards compatibility with the old single key library.
|
||||
|| | It also supports user selectable pins and definable keymaps.
|
||||
|| #
|
||||
||
|
||||
|| @license
|
||||
|| | This library is free software; you can redistribute it and/or
|
||||
|| | modify it under the terms of the GNU Lesser General Public
|
||||
|| | License as published by the Free Software Foundation; version
|
||||
|| | 2.1 of the License.
|
||||
|| |
|
||||
|| | This library is distributed in the hope that it will be useful,
|
||||
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|| | Lesser General Public License for more details.
|
||||
|| |
|
||||
|| | You should have received a copy of the GNU Lesser General Public
|
||||
|| | License along with this library; if not, write to the Free Software
|
||||
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|| #
|
||||
||
|
||||
*/
|
||||
#include <Keypad.h>
|
||||
|
||||
// <<constructor>> Allows custom keymap, pin configuration, and keypad sizes.
|
||||
Keypad::Keypad(char *userKeymap, byte *row, byte *col, byte numRows, byte numCols) {
|
||||
rowPins = row;
|
||||
columnPins = col;
|
||||
sizeKpd.rows = numRows;
|
||||
sizeKpd.columns = numCols;
|
||||
|
||||
begin(userKeymap);
|
||||
|
||||
setDebounceTime(10);
|
||||
setHoldTime(500);
|
||||
keypadEventListener = 0;
|
||||
|
||||
startTime = 0;
|
||||
single_key = false;
|
||||
}
|
||||
|
||||
// Let the user define a keymap - assume the same row/column count as defined in constructor
|
||||
void Keypad::begin(char *userKeymap) {
|
||||
keymap = userKeymap;
|
||||
}
|
||||
|
||||
// Returns a single key only. Retained for backwards compatibility.
|
||||
char Keypad::getKey() {
|
||||
single_key = true;
|
||||
|
||||
if (getKeys() && key[0].stateChanged && (key[0].kstate==PRESSED))
|
||||
return key[0].kchar;
|
||||
|
||||
single_key = false;
|
||||
|
||||
return NO_KEY;
|
||||
}
|
||||
|
||||
// Populate the key list.
|
||||
bool Keypad::getKeys() {
|
||||
bool keyActivity = false;
|
||||
|
||||
// Limit how often the keypad is scanned. This makes the loop() run 10 times as fast.
|
||||
if ( (millis()-startTime)>debounceTime ) {
|
||||
scanKeys();
|
||||
keyActivity = updateList();
|
||||
startTime = millis();
|
||||
}
|
||||
|
||||
return keyActivity;
|
||||
}
|
||||
|
||||
// Private : Hardware scan
|
||||
void Keypad::scanKeys() {
|
||||
// Re-intialize the row pins. Allows sharing these pins with other hardware.
|
||||
for (byte r=0; r<sizeKpd.rows; r++) {
|
||||
pin_mode(rowPins[r],INPUT_PULLUP);
|
||||
}
|
||||
|
||||
// bitMap stores ALL the keys that are being pressed.
|
||||
for (byte c=0; c<sizeKpd.columns; c++) {
|
||||
pin_mode(columnPins[c],OUTPUT);
|
||||
pin_write(columnPins[c], LOW); // Begin column pulse output.
|
||||
for (byte r=0; r<sizeKpd.rows; r++) {
|
||||
bitWrite(bitMap[r], c, !pin_read(rowPins[r])); // keypress is active low so invert to high.
|
||||
}
|
||||
// Set pin to high impedance input. Effectively ends column pulse.
|
||||
pin_write(columnPins[c],HIGH);
|
||||
pin_mode(columnPins[c],INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
// Manage the list without rearranging the keys. Returns true if any keys on the list changed state.
|
||||
bool Keypad::updateList() {
|
||||
|
||||
bool anyActivity = false;
|
||||
|
||||
// Delete any IDLE keys
|
||||
for (byte i=0; i<LIST_MAX; i++) {
|
||||
if (key[i].kstate==IDLE) {
|
||||
key[i].kchar = NO_KEY;
|
||||
key[i].kcode = -1;
|
||||
key[i].stateChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new keys to empty slots in the key list.
|
||||
for (byte r=0; r<sizeKpd.rows; r++) {
|
||||
for (byte c=0; c<sizeKpd.columns; c++) {
|
||||
boolean button = bitRead(bitMap[r],c);
|
||||
char keyChar = keymap[r * sizeKpd.columns + c];
|
||||
int keyCode = r * sizeKpd.columns + c;
|
||||
int idx = findInList (keyCode);
|
||||
// Key is already on the list so set its next state.
|
||||
if (idx > -1) {
|
||||
nextKeyState(idx, button);
|
||||
}
|
||||
// Key is NOT on the list so add it.
|
||||
if ((idx == -1) && button) {
|
||||
for (byte i=0; i<LIST_MAX; i++) {
|
||||
if (key[i].kchar==NO_KEY) { // Find an empty slot or don't add key to list.
|
||||
key[i].kchar = keyChar;
|
||||
key[i].kcode = keyCode;
|
||||
key[i].kstate = IDLE; // Keys NOT on the list have an initial state of IDLE.
|
||||
nextKeyState (i, button);
|
||||
break; // Don't fill all the empty slots with the same key.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report if the user changed the state of any key.
|
||||
for (byte i=0; i<LIST_MAX; i++) {
|
||||
if (key[i].stateChanged) anyActivity = true;
|
||||
}
|
||||
|
||||
return anyActivity;
|
||||
}
|
||||
|
||||
// Private
|
||||
// This function is a state machine but is also used for debouncing the keys.
|
||||
void Keypad::nextKeyState(byte idx, boolean button) {
|
||||
key[idx].stateChanged = false;
|
||||
|
||||
switch (key[idx].kstate) {
|
||||
case IDLE:
|
||||
if (button==CLOSED) {
|
||||
transitionTo (idx, PRESSED);
|
||||
holdTimer = millis(); } // Get ready for next HOLD state.
|
||||
break;
|
||||
case PRESSED:
|
||||
if ((millis()-holdTimer)>holdTime) // Waiting for a key HOLD...
|
||||
transitionTo (idx, HOLD);
|
||||
else if (button==OPEN) // or for a key to be RELEASED.
|
||||
transitionTo (idx, RELEASED);
|
||||
break;
|
||||
case HOLD:
|
||||
if (button==OPEN)
|
||||
transitionTo (idx, RELEASED);
|
||||
break;
|
||||
case RELEASED:
|
||||
transitionTo (idx, IDLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// New in 2.1
|
||||
bool Keypad::isPressed(char keyChar) {
|
||||
for (byte i=0; i<LIST_MAX; i++) {
|
||||
if ( key[i].kchar == keyChar ) {
|
||||
if ( (key[i].kstate == PRESSED) && key[i].stateChanged )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // Not pressed.
|
||||
}
|
||||
|
||||
// Search by character for a key in the list of active keys.
|
||||
// Returns -1 if not found or the index into the list of active keys.
|
||||
int Keypad::findInList (char keyChar) {
|
||||
for (byte i=0; i<LIST_MAX; i++) {
|
||||
if (key[i].kchar == keyChar) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Search by code for a key in the list of active keys.
|
||||
// Returns -1 if not found or the index into the list of active keys.
|
||||
int Keypad::findInList (int keyCode) {
|
||||
for (byte i=0; i<LIST_MAX; i++) {
|
||||
if (key[i].kcode == keyCode) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// New in 2.0
|
||||
char Keypad::waitForKey() {
|
||||
char waitKey = NO_KEY;
|
||||
while( (waitKey = getKey()) == NO_KEY ); // Block everything while waiting for a keypress.
|
||||
return waitKey;
|
||||
}
|
||||
|
||||
// Backwards compatibility function.
|
||||
KeyState Keypad::getState() {
|
||||
return key[0].kstate;
|
||||
}
|
||||
|
||||
// The end user can test for any changes in state before deciding
|
||||
// if any variables, etc. needs to be updated in their code.
|
||||
bool Keypad::keyStateChanged() {
|
||||
return key[0].stateChanged;
|
||||
}
|
||||
|
||||
// The number of keys on the key list, key[LIST_MAX], equals the number
|
||||
// of bytes in the key list divided by the number of bytes in a Key object.
|
||||
byte Keypad::numKeys() {
|
||||
return sizeof(key)/sizeof(Key);
|
||||
}
|
||||
|
||||
// Minimum debounceTime is 1 mS. Any lower *will* slow down the loop().
|
||||
void Keypad::setDebounceTime(uint debounce) {
|
||||
debounce<1 ? debounceTime=1 : debounceTime=debounce;
|
||||
}
|
||||
|
||||
void Keypad::setHoldTime(uint hold) {
|
||||
holdTime = hold;
|
||||
}
|
||||
|
||||
void Keypad::addEventListener(void (*listener)(char)){
|
||||
keypadEventListener = listener;
|
||||
}
|
||||
|
||||
void Keypad::transitionTo(byte idx, KeyState nextState) {
|
||||
key[idx].kstate = nextState;
|
||||
key[idx].stateChanged = true;
|
||||
|
||||
// Sketch used the getKey() function.
|
||||
// Calls keypadEventListener only when the first key in slot 0 changes state.
|
||||
if (single_key) {
|
||||
if ( (keypadEventListener!=NULL) && (idx==0) ) {
|
||||
keypadEventListener(key[0].kchar);
|
||||
}
|
||||
}
|
||||
// Sketch used the getKeys() function.
|
||||
// Calls keypadEventListener on any key that changes state.
|
||||
else {
|
||||
if (keypadEventListener!=NULL) {
|
||||
keypadEventListener(key[idx].kchar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|| @changelog
|
||||
|| | 3.1 2013-01-15 - Mark Stanley : Fixed missing RELEASED & IDLE status when using a single key.
|
||||
|| | 3.0 2012-07-12 - Mark Stanley : Made library multi-keypress by default. (Backwards compatible)
|
||||
|| | 3.0 2012-07-12 - Mark Stanley : Modified pin functions to support Keypad_I2C
|
||||
|| | 3.0 2012-07-12 - Stanley & Young : Removed static variables. Fix for multiple keypad objects.
|
||||
|| | 3.0 2012-07-12 - Mark Stanley : Fixed bug that caused shorted pins when pressing multiple keys.
|
||||
|| | 2.0 2011-12-29 - Mark Stanley : Added waitForKey().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Added the public function keyStateChanged().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Added the private function scanKeys().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Moved the Finite State Machine into the function getKeyState().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Removed the member variable lastUdate. Not needed after rewrite.
|
||||
|| | 1.8 2011-11-21 - Mark Stanley : Added decision logic to compile WProgram.h or Arduino.h
|
||||
|| | 1.8 2009-07-08 - Alexander Brevig : No longer uses arrays
|
||||
|| | 1.7 2009-06-18 - Alexander Brevig : Every time a state changes the keypadEventListener will trigger, if set.
|
||||
|| | 1.7 2009-06-18 - Alexander Brevig : Added setDebounceTime. setHoldTime specifies the amount of
|
||||
|| | microseconds before a HOLD state triggers
|
||||
|| | 1.7 2009-06-18 - Alexander Brevig : Added transitionTo
|
||||
|| | 1.6 2009-06-15 - Alexander Brevig : Added getState() and state variable
|
||||
|| | 1.5 2009-05-19 - Alexander Brevig : Added setHoldTime()
|
||||
|| | 1.4 2009-05-15 - Alexander Brevig : Added addEventListener
|
||||
|| | 1.3 2009-05-12 - Alexander Brevig : Added lastUdate, in order to do simple debouncing
|
||||
|| | 1.2 2009-05-09 - Alexander Brevig : Changed getKey()
|
||||
|| | 1.1 2009-04-28 - Alexander Brevig : Modified API, and made variables private
|
||||
|| | 1.0 2007-XX-XX - Mark Stanley : Initial Release
|
||||
|| #
|
||||
*/
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
||
|
||||
|| @file Key.h
|
||||
|| @version 1.0
|
||||
|| @author Mark Stanley
|
||||
|| @contact mstanley@technologist.com
|
||||
||
|
||||
|| @description
|
||||
|| | Key class provides an abstract definition of a key or button
|
||||
|| | and was initially designed to be used in conjunction with a
|
||||
|| | state-machine.
|
||||
|| #
|
||||
||
|
||||
|| @license
|
||||
|| | This library is free software; you can redistribute it and/or
|
||||
|| | modify it under the terms of the GNU Lesser General Public
|
||||
|| | License as published by the Free Software Foundation; version
|
||||
|| | 2.1 of the License.
|
||||
|| |
|
||||
|| | This library is distributed in the hope that it will be useful,
|
||||
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|| | Lesser General Public License for more details.
|
||||
|| |
|
||||
|| | You should have received a copy of the GNU Lesser General Public
|
||||
|| | License along with this library; if not, write to the Free Software
|
||||
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|| #
|
||||
||
|
||||
*/
|
||||
|
||||
#ifndef Keypadlib_KEY_H_
|
||||
#define Keypadlib_KEY_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define OPEN LOW
|
||||
#define CLOSED HIGH
|
||||
|
||||
typedef unsigned int uint;
|
||||
typedef enum{ IDLE, PRESSED, HOLD, RELEASED } KeyState;
|
||||
|
||||
const char NO_KEY = '\0';
|
||||
|
||||
class Key {
|
||||
public:
|
||||
// members
|
||||
char kchar;
|
||||
int kcode;
|
||||
KeyState kstate;
|
||||
boolean stateChanged;
|
||||
|
||||
// methods
|
||||
Key();
|
||||
Key(char userKeyChar);
|
||||
void key_update(char userKeyChar, KeyState userState, boolean userStatus);
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
|| @changelog
|
||||
|| | 1.0 2012-06-04 - Mark Stanley : Initial Release
|
||||
|| #
|
||||
*/
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
||
|
||||
|| @file Keypad.h
|
||||
|| @version 3.1
|
||||
|| @author Mark Stanley, Alexander Brevig
|
||||
|| @contact mstanley@technologist.com, alexanderbrevig@gmail.com
|
||||
||
|
||||
|| @description
|
||||
|| | This library provides a simple interface for using matrix
|
||||
|| | keypads. It supports multiple keypresses while maintaining
|
||||
|| | backwards compatibility with the old single key library.
|
||||
|| | It also supports user selectable pins and definable keymaps.
|
||||
|| #
|
||||
||
|
||||
|| @license
|
||||
|| | This library is free software; you can redistribute it and/or
|
||||
|| | modify it under the terms of the GNU Lesser General Public
|
||||
|| | License as published by the Free Software Foundation; version
|
||||
|| | 2.1 of the License.
|
||||
|| |
|
||||
|| | This library is distributed in the hope that it will be useful,
|
||||
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|| | Lesser General Public License for more details.
|
||||
|| |
|
||||
|| | You should have received a copy of the GNU Lesser General Public
|
||||
|| | License along with this library; if not, write to the Free Software
|
||||
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|| #
|
||||
||
|
||||
*/
|
||||
|
||||
#ifndef KEYPAD_H
|
||||
#define KEYPAD_H
|
||||
|
||||
#include "Key.h"
|
||||
|
||||
// bperrybap - Thanks for a well reasoned argument and the following macro(s).
|
||||
// See http://arduino.cc/forum/index.php/topic,142041.msg1069480.html#msg1069480
|
||||
#ifndef INPUT_PULLUP
|
||||
#warning "Using pinMode() INPUT_PULLUP AVR emulation"
|
||||
#define INPUT_PULLUP 0x2
|
||||
#define pinMode(_pin, _mode) _mypinMode(_pin, _mode)
|
||||
#define _mypinMode(_pin, _mode) \
|
||||
do { \
|
||||
if(_mode == INPUT_PULLUP) \
|
||||
pinMode(_pin, INPUT); \
|
||||
digitalWrite(_pin, 1); \
|
||||
if(_mode != INPUT_PULLUP) \
|
||||
pinMode(_pin, _mode); \
|
||||
}while(0)
|
||||
#endif
|
||||
|
||||
|
||||
#define OPEN LOW
|
||||
#define CLOSED HIGH
|
||||
|
||||
typedef char KeypadEvent;
|
||||
typedef unsigned int uint;
|
||||
typedef unsigned long ulong;
|
||||
|
||||
// Made changes according to this post http://arduino.cc/forum/index.php?topic=58337.0
|
||||
// by Nick Gammon. Thanks for the input Nick. It actually saved 78 bytes for me. :)
|
||||
typedef struct {
|
||||
byte rows;
|
||||
byte columns;
|
||||
} KeypadSize;
|
||||
|
||||
#define LIST_MAX 10 // Max number of keys on the active list.
|
||||
#define MAPSIZE 10 // MAPSIZE is the number of rows (times 16 columns)
|
||||
#define makeKeymap(x) ((char*)x)
|
||||
|
||||
|
||||
//class Keypad : public Key, public HAL_obj {
|
||||
class Keypad : public Key {
|
||||
public:
|
||||
|
||||
Keypad(char *userKeymap, byte *row, byte *col, byte numRows, byte numCols);
|
||||
|
||||
virtual void pin_mode(byte pinNum, byte mode) { pinMode(pinNum, mode); }
|
||||
virtual void pin_write(byte pinNum, boolean level) { digitalWrite(pinNum, level); }
|
||||
virtual int pin_read(byte pinNum) { return digitalRead(pinNum); }
|
||||
|
||||
uint bitMap[MAPSIZE]; // 10 row x 16 column array of bits. Except Due which has 32 columns.
|
||||
Key key[LIST_MAX];
|
||||
unsigned long holdTimer;
|
||||
|
||||
char getKey();
|
||||
bool getKeys();
|
||||
KeyState getState();
|
||||
void begin(char *userKeymap);
|
||||
bool isPressed(char keyChar);
|
||||
void setDebounceTime(uint);
|
||||
void setHoldTime(uint);
|
||||
void addEventListener(void (*listener)(char));
|
||||
int findInList(char keyChar);
|
||||
int findInList(int keyCode);
|
||||
char waitForKey();
|
||||
bool keyStateChanged();
|
||||
byte numKeys();
|
||||
|
||||
private:
|
||||
unsigned long startTime;
|
||||
char *keymap;
|
||||
byte *rowPins;
|
||||
byte *columnPins;
|
||||
KeypadSize sizeKpd;
|
||||
uint debounceTime;
|
||||
uint holdTime;
|
||||
bool single_key;
|
||||
|
||||
void scanKeys();
|
||||
bool updateList();
|
||||
void nextKeyState(byte n, boolean button);
|
||||
void transitionTo(byte n, KeyState nextState);
|
||||
void (*keypadEventListener)(char);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
|| @changelog
|
||||
|| | 3.1 2013-01-15 - Mark Stanley : Fixed missing RELEASED & IDLE status when using a single key.
|
||||
|| | 3.0 2012-07-12 - Mark Stanley : Made library multi-keypress by default. (Backwards compatible)
|
||||
|| | 3.0 2012-07-12 - Mark Stanley : Modified pin functions to support Keypad_I2C
|
||||
|| | 3.0 2012-07-12 - Stanley & Young : Removed static variables. Fix for multiple keypad objects.
|
||||
|| | 3.0 2012-07-12 - Mark Stanley : Fixed bug that caused shorted pins when pressing multiple keys.
|
||||
|| | 2.0 2011-12-29 - Mark Stanley : Added waitForKey().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Added the public function keyStateChanged().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Added the private function scanKeys().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Moved the Finite State Machine into the function getKeyState().
|
||||
|| | 2.0 2011-12-23 - Mark Stanley : Removed the member variable lastUdate. Not needed after rewrite.
|
||||
|| | 1.8 2011-11-21 - Mark Stanley : Added test to determine which header file to compile,
|
||||
|| | WProgram.h or Arduino.h.
|
||||
|| | 1.8 2009-07-08 - Alexander Brevig : No longer uses arrays
|
||||
|| | 1.7 2009-06-18 - Alexander Brevig : This library is a Finite State Machine every time a state changes
|
||||
|| | the keypadEventListener will trigger, if set
|
||||
|| | 1.7 2009-06-18 - Alexander Brevig : Added setDebounceTime setHoldTime specifies the amount of
|
||||
|| | microseconds before a HOLD state triggers
|
||||
|| | 1.7 2009-06-18 - Alexander Brevig : Added transitionTo
|
||||
|| | 1.6 2009-06-15 - Alexander Brevig : Added getState() and state variable
|
||||
|| | 1.5 2009-05-19 - Alexander Brevig : Added setHoldTime()
|
||||
|| | 1.4 2009-05-15 - Alexander Brevig : Added addEventListener
|
||||
|| | 1.3 2009-05-12 - Alexander Brevig : Added lastUdate, in order to do simple debouncing
|
||||
|| | 1.2 2009-05-09 - Alexander Brevig : Changed getKey()
|
||||
|| | 1.1 2009-04-28 - Alexander Brevig : Modified API, and made variables private
|
||||
|| | 1.0 2007-XX-XX - Mark Stanley : Initial Release
|
||||
|| #
|
||||
*/
|
||||
@@ -0,0 +1,332 @@
|
||||
// Based on the work by DFRobot
|
||||
|
||||
#include "LiquidCrystal_I2C.h"
|
||||
#include <inttypes.h>
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#define printIIC(args) Wire.write(args)
|
||||
inline size_t LiquidCrystal_I2C::write(uint8_t value) {
|
||||
send(value, Rs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#else
|
||||
#include "WProgram.h"
|
||||
|
||||
#define printIIC(args) Wire.send(args)
|
||||
inline void LiquidCrystal_I2C::write(uint8_t value) {
|
||||
send(value, Rs);
|
||||
}
|
||||
|
||||
#endif
|
||||
#include "Wire.h"
|
||||
|
||||
|
||||
|
||||
// When the display powers up, it is configured as follows:
|
||||
//
|
||||
// 1. Display clear
|
||||
// 2. Function set:
|
||||
// DL = 1; 8-bit interface data
|
||||
// N = 0; 1-line display
|
||||
// F = 0; 5x8 dot character font
|
||||
// 3. Display on/off control:
|
||||
// D = 0; Display off
|
||||
// C = 0; Cursor off
|
||||
// B = 0; Blinking off
|
||||
// 4. Entry mode set:
|
||||
// I/D = 1; Increment by 1
|
||||
// S = 0; No shift
|
||||
//
|
||||
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
|
||||
// can't assume that its in that state when a sketch starts (and the
|
||||
// LiquidCrystal constructor is called).
|
||||
|
||||
LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows)
|
||||
{
|
||||
_Addr = lcd_Addr;
|
||||
_cols = lcd_cols;
|
||||
_rows = lcd_rows;
|
||||
_backlightval = LCD_NOBACKLIGHT;
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::oled_init(){
|
||||
_oled = true;
|
||||
init_priv();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::init(){
|
||||
init_priv();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::init_priv()
|
||||
{
|
||||
Wire.begin();
|
||||
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
|
||||
begin(_cols, _rows);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
|
||||
if (lines > 1) {
|
||||
_displayfunction |= LCD_2LINE;
|
||||
}
|
||||
_numlines = lines;
|
||||
|
||||
// for some 1 line displays you can select a 10 pixel high font
|
||||
if ((dotsize != 0) && (lines == 1)) {
|
||||
_displayfunction |= LCD_5x10DOTS;
|
||||
}
|
||||
|
||||
// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
|
||||
// according to datasheet, we need at least 40ms after power rises above 2.7V
|
||||
// before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
|
||||
delay(50);
|
||||
|
||||
// Now we pull both RS and R/W low to begin commands
|
||||
expanderWrite(_backlightval); // reset expanderand turn backlight off (Bit 8 =1)
|
||||
delay(1000);
|
||||
|
||||
//put the LCD into 4 bit mode
|
||||
// this is according to the hitachi HD44780 datasheet
|
||||
// figure 24, pg 46
|
||||
|
||||
// we start in 8bit mode, try to set 4 bit mode
|
||||
write4bits(0x03 << 4);
|
||||
delayMicroseconds(4500); // wait min 4.1ms
|
||||
|
||||
// second try
|
||||
write4bits(0x03 << 4);
|
||||
delayMicroseconds(4500); // wait min 4.1ms
|
||||
|
||||
// third go!
|
||||
write4bits(0x03 << 4);
|
||||
delayMicroseconds(150);
|
||||
|
||||
// finally, set to 4-bit interface
|
||||
write4bits(0x02 << 4);
|
||||
|
||||
|
||||
// set # lines, font size, etc.
|
||||
command(LCD_FUNCTIONSET | _displayfunction);
|
||||
|
||||
// turn the display on with no cursor or blinking default
|
||||
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
|
||||
display();
|
||||
|
||||
// clear it off
|
||||
clear();
|
||||
|
||||
// Initialize to default text direction (for roman languages)
|
||||
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
|
||||
|
||||
// set the entry mode
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
|
||||
home();
|
||||
|
||||
}
|
||||
|
||||
/********** high level commands, for the user! */
|
||||
void LiquidCrystal_I2C::clear(){
|
||||
command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero
|
||||
delayMicroseconds(2000); // this command takes a long time!
|
||||
if (_oled) setCursor(0,0);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::home(){
|
||||
command(LCD_RETURNHOME); // set cursor position to zero
|
||||
delayMicroseconds(2000); // this command takes a long time!
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row){
|
||||
int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
|
||||
if ( row > _numlines ) {
|
||||
row = _numlines-1; // we count rows starting w/0
|
||||
}
|
||||
command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
|
||||
}
|
||||
|
||||
// Turn the display on/off (quickly)
|
||||
void LiquidCrystal_I2C::noDisplay() {
|
||||
_displaycontrol &= ~LCD_DISPLAYON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
void LiquidCrystal_I2C::display() {
|
||||
_displaycontrol |= LCD_DISPLAYON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
|
||||
// Turns the underline cursor on/off
|
||||
void LiquidCrystal_I2C::noCursor() {
|
||||
_displaycontrol &= ~LCD_CURSORON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
void LiquidCrystal_I2C::cursor() {
|
||||
_displaycontrol |= LCD_CURSORON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
|
||||
// Turn on and off the blinking cursor
|
||||
void LiquidCrystal_I2C::noBlink() {
|
||||
_displaycontrol &= ~LCD_BLINKON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
void LiquidCrystal_I2C::blink() {
|
||||
_displaycontrol |= LCD_BLINKON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
|
||||
// These commands scroll the display without changing the RAM
|
||||
void LiquidCrystal_I2C::scrollDisplayLeft(void) {
|
||||
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
|
||||
}
|
||||
void LiquidCrystal_I2C::scrollDisplayRight(void) {
|
||||
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
|
||||
}
|
||||
|
||||
// This is for text that flows Left to Right
|
||||
void LiquidCrystal_I2C::leftToRight(void) {
|
||||
_displaymode |= LCD_ENTRYLEFT;
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
}
|
||||
|
||||
// This is for text that flows Right to Left
|
||||
void LiquidCrystal_I2C::rightToLeft(void) {
|
||||
_displaymode &= ~LCD_ENTRYLEFT;
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
}
|
||||
|
||||
// This will 'right justify' text from the cursor
|
||||
void LiquidCrystal_I2C::autoscroll(void) {
|
||||
_displaymode |= LCD_ENTRYSHIFTINCREMENT;
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
}
|
||||
|
||||
// This will 'left justify' text from the cursor
|
||||
void LiquidCrystal_I2C::noAutoscroll(void) {
|
||||
_displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
}
|
||||
|
||||
// Allows us to fill the first 8 CGRAM locations
|
||||
// with custom characters
|
||||
void LiquidCrystal_I2C::createChar(uint8_t location, uint8_t charmap[]) {
|
||||
location &= 0x7; // we only have 8 locations 0-7
|
||||
command(LCD_SETCGRAMADDR | (location << 3));
|
||||
for (int i=0; i<8; i++) {
|
||||
write(charmap[i]);
|
||||
}
|
||||
}
|
||||
|
||||
//createChar with PROGMEM input
|
||||
void LiquidCrystal_I2C::createChar(uint8_t location, const char *charmap) {
|
||||
location &= 0x7; // we only have 8 locations 0-7
|
||||
command(LCD_SETCGRAMADDR | (location << 3));
|
||||
for (int i=0; i<8; i++) {
|
||||
write(pgm_read_byte_near(charmap++));
|
||||
}
|
||||
}
|
||||
|
||||
// Turn the (optional) backlight off/on
|
||||
void LiquidCrystal_I2C::noBacklight(void) {
|
||||
_backlightval=LCD_NOBACKLIGHT;
|
||||
expanderWrite(0);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::backlight(void) {
|
||||
_backlightval=LCD_BACKLIGHT;
|
||||
expanderWrite(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********** mid level commands, for sending data/cmds */
|
||||
|
||||
inline void LiquidCrystal_I2C::command(uint8_t value) {
|
||||
send(value, 0);
|
||||
}
|
||||
|
||||
|
||||
/************ low level data pushing commands **********/
|
||||
|
||||
// write either command or data
|
||||
void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
|
||||
uint8_t highnib=value&0xf0;
|
||||
uint8_t lownib=(value<<4)&0xf0;
|
||||
write4bits((highnib)|mode);
|
||||
write4bits((lownib)|mode);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::write4bits(uint8_t value) {
|
||||
expanderWrite(value);
|
||||
pulseEnable(value);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::expanderWrite(uint8_t _data){
|
||||
Wire.beginTransmission(_Addr);
|
||||
printIIC((int)(_data) | _backlightval);
|
||||
Wire.endTransmission();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::pulseEnable(uint8_t _data){
|
||||
expanderWrite(_data | En); // En high
|
||||
delayMicroseconds(1); // enable pulse must be >450ns
|
||||
|
||||
expanderWrite(_data & ~En); // En low
|
||||
delayMicroseconds(50); // commands need > 37us to settle
|
||||
}
|
||||
|
||||
|
||||
// Alias functions
|
||||
|
||||
void LiquidCrystal_I2C::cursor_on(){
|
||||
cursor();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::cursor_off(){
|
||||
noCursor();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::blink_on(){
|
||||
blink();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::blink_off(){
|
||||
noBlink();
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::load_custom_character(uint8_t char_num, uint8_t *rows){
|
||||
createChar(char_num, rows);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::setBacklight(uint8_t new_val){
|
||||
if(new_val){
|
||||
backlight(); // turn backlight on
|
||||
}else{
|
||||
noBacklight(); // turn backlight off
|
||||
}
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::printstr(const char c[]){
|
||||
//This function is not identical to the function used for "real" I2C displays
|
||||
//it's here so the user sketch doesn't have to be changed
|
||||
print(c);
|
||||
}
|
||||
|
||||
|
||||
// unsupported API functions
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
void LiquidCrystal_I2C::off(){}
|
||||
void LiquidCrystal_I2C::on(){}
|
||||
void LiquidCrystal_I2C::setDelay (int cmdDelay,int charDelay) {}
|
||||
uint8_t LiquidCrystal_I2C::status(){return 0;}
|
||||
uint8_t LiquidCrystal_I2C::keypad (){return 0;}
|
||||
uint8_t LiquidCrystal_I2C::init_bargraph(uint8_t graphtype){return 0;}
|
||||
void LiquidCrystal_I2C::draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end){}
|
||||
void LiquidCrystal_I2C::draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_row_end){}
|
||||
void LiquidCrystal_I2C::setContrast(uint8_t new_val){}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
//YWROBOT
|
||||
#ifndef LiquidCrystal_I2C_h
|
||||
#define LiquidCrystal_I2C_h
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "Print.h"
|
||||
#include <Wire.h>
|
||||
|
||||
// commands
|
||||
#define LCD_CLEARDISPLAY 0x01
|
||||
#define LCD_RETURNHOME 0x02
|
||||
#define LCD_ENTRYMODESET 0x04
|
||||
#define LCD_DISPLAYCONTROL 0x08
|
||||
#define LCD_CURSORSHIFT 0x10
|
||||
#define LCD_FUNCTIONSET 0x20
|
||||
#define LCD_SETCGRAMADDR 0x40
|
||||
#define LCD_SETDDRAMADDR 0x80
|
||||
|
||||
// flags for display entry mode
|
||||
#define LCD_ENTRYRIGHT 0x00
|
||||
#define LCD_ENTRYLEFT 0x02
|
||||
#define LCD_ENTRYSHIFTINCREMENT 0x01
|
||||
#define LCD_ENTRYSHIFTDECREMENT 0x00
|
||||
|
||||
// flags for display on/off control
|
||||
#define LCD_DISPLAYON 0x04
|
||||
#define LCD_DISPLAYOFF 0x00
|
||||
#define LCD_CURSORON 0x02
|
||||
#define LCD_CURSOROFF 0x00
|
||||
#define LCD_BLINKON 0x01
|
||||
#define LCD_BLINKOFF 0x00
|
||||
|
||||
// flags for display/cursor shift
|
||||
#define LCD_DISPLAYMOVE 0x08
|
||||
#define LCD_CURSORMOVE 0x00
|
||||
#define LCD_MOVERIGHT 0x04
|
||||
#define LCD_MOVELEFT 0x00
|
||||
|
||||
// flags for function set
|
||||
#define LCD_8BITMODE 0x10
|
||||
#define LCD_4BITMODE 0x00
|
||||
#define LCD_2LINE 0x08
|
||||
#define LCD_1LINE 0x00
|
||||
#define LCD_5x10DOTS 0x04
|
||||
#define LCD_5x8DOTS 0x00
|
||||
|
||||
// flags for backlight control
|
||||
#define LCD_BACKLIGHT 0x08
|
||||
#define LCD_NOBACKLIGHT 0x00
|
||||
|
||||
#define En B00000100 // Enable bit
|
||||
#define Rw B00000010 // Read/Write bit
|
||||
#define Rs B00000001 // Register select bit
|
||||
|
||||
class LiquidCrystal_I2C : public Print {
|
||||
public:
|
||||
LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
|
||||
void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS );
|
||||
void clear();
|
||||
void home();
|
||||
void noDisplay();
|
||||
void display();
|
||||
void noBlink();
|
||||
void blink();
|
||||
void noCursor();
|
||||
void cursor();
|
||||
void scrollDisplayLeft();
|
||||
void scrollDisplayRight();
|
||||
void printLeft();
|
||||
void printRight();
|
||||
void leftToRight();
|
||||
void rightToLeft();
|
||||
void shiftIncrement();
|
||||
void shiftDecrement();
|
||||
void noBacklight();
|
||||
void backlight();
|
||||
void autoscroll();
|
||||
void noAutoscroll();
|
||||
void createChar(uint8_t, uint8_t[]);
|
||||
void createChar(uint8_t location, const char *charmap);
|
||||
// Example: const char bell[8] PROGMEM = {B00100,B01110,B01110,B01110,B11111,B00000,B00100,B00000};
|
||||
|
||||
void setCursor(uint8_t, uint8_t);
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
virtual size_t write(uint8_t);
|
||||
#else
|
||||
virtual void write(uint8_t);
|
||||
#endif
|
||||
void command(uint8_t);
|
||||
void init();
|
||||
void oled_init();
|
||||
|
||||
////compatibility API function aliases
|
||||
void blink_on(); // alias for blink()
|
||||
void blink_off(); // alias for noBlink()
|
||||
void cursor_on(); // alias for cursor()
|
||||
void cursor_off(); // alias for noCursor()
|
||||
void setBacklight(uint8_t new_val); // alias for backlight() and nobacklight()
|
||||
void load_custom_character(uint8_t char_num, uint8_t *rows); // alias for createChar()
|
||||
void printstr(const char[]);
|
||||
|
||||
////Unsupported API functions (not implemented in this library)
|
||||
uint8_t status();
|
||||
void setContrast(uint8_t new_val);
|
||||
uint8_t keypad();
|
||||
void setDelay(int,int);
|
||||
void on();
|
||||
void off();
|
||||
uint8_t init_bargraph(uint8_t graphtype);
|
||||
void draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
|
||||
void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
|
||||
|
||||
|
||||
private:
|
||||
void init_priv();
|
||||
void send(uint8_t, uint8_t);
|
||||
void write4bits(uint8_t);
|
||||
void expanderWrite(uint8_t);
|
||||
void pulseEnable(uint8_t);
|
||||
uint8_t _Addr;
|
||||
uint8_t _displayfunction;
|
||||
uint8_t _displaycontrol;
|
||||
uint8_t _displaymode;
|
||||
uint8_t _numlines;
|
||||
bool _oled = false;
|
||||
uint8_t _cols;
|
||||
uint8_t _rows;
|
||||
uint8_t _backlightval;
|
||||
};
|
||||
|
||||
#endif
|
||||
+603
@@ -0,0 +1,603 @@
|
||||
/*
|
||||
Copyright (c) 2007, Jim Studt (original old version - many contributors since)
|
||||
|
||||
The latest version of this library may be found at:
|
||||
http://www.pjrc.com/teensy/td_libs_OneWire.html
|
||||
|
||||
OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since
|
||||
January 2010.
|
||||
|
||||
DO NOT EMAIL for technical support, especially not for ESP chips!
|
||||
All project support questions must be posted on public forums
|
||||
relevant to the board or chips used. If using Arduino, post on
|
||||
Arduino's forum. If using ESP, post on the ESP community forums.
|
||||
There is ABSOLUTELY NO TECH SUPPORT BY PRIVATE EMAIL!
|
||||
|
||||
Github's issue tracker for OneWire should be used only to report
|
||||
specific bugs. DO NOT request project support via Github. All
|
||||
project and tech support questions must be posted on forums, not
|
||||
github issues. If you experience a problem and you are not
|
||||
absolutely sure it's an issue with the library, ask on a forum
|
||||
first. Only use github to report issues after experts have
|
||||
confirmed the issue is with OneWire rather than your project.
|
||||
|
||||
Back in 2010, OneWire was in need of many bug fixes, but had
|
||||
been abandoned the original author (Jim Studt). None of the known
|
||||
contributors were interested in maintaining OneWire. Paul typically
|
||||
works on OneWire every 6 to 12 months. Patches usually wait that
|
||||
long. If anyone is interested in more actively maintaining OneWire,
|
||||
please contact Paul (this is pretty much the only reason to use
|
||||
private email about OneWire).
|
||||
|
||||
OneWire is now very mature code. No changes other than adding
|
||||
definitions for newer hardware support are anticipated.
|
||||
|
||||
ESP32 mods authored by stickbreaker:
|
||||
@stickbreaker 30APR2018 add IRAM_ATTR to read_bit() write_bit() to solve ICache miss timing failure.
|
||||
thanks @everslick re: https://github.com/espressif/arduino-esp32/issues/1335
|
||||
Altered by garyd9 for clean merge with Paul Stoffregen's source
|
||||
|
||||
Version 2.3:
|
||||
Unknown chip fallback mode, Roger Clark
|
||||
Teensy-LC compatibility, Paul Stoffregen
|
||||
Search bug fix, Love Nystrom
|
||||
|
||||
Version 2.2:
|
||||
Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com
|
||||
Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030
|
||||
Fix DS18B20 example negative temperature
|
||||
Fix DS18B20 example's low res modes, Ken Butcher
|
||||
Improve reset timing, Mark Tillotson
|
||||
Add const qualifiers, Bertrik Sikken
|
||||
Add initial value input to crc16, Bertrik Sikken
|
||||
Add target_search() function, Scott Roberts
|
||||
|
||||
Version 2.1:
|
||||
Arduino 1.0 compatibility, Paul Stoffregen
|
||||
Improve temperature example, Paul Stoffregen
|
||||
DS250x_PROM example, Guillermo Lovato
|
||||
PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com
|
||||
Improvements from Glenn Trewitt:
|
||||
- crc16() now works
|
||||
- check_crc16() does all of calculation/checking work.
|
||||
- Added read_bytes() and write_bytes(), to reduce tedious loops.
|
||||
- Added ds2408 example.
|
||||
Delete very old, out-of-date readme file (info is here)
|
||||
|
||||
Version 2.0: Modifications by Paul Stoffregen, January 2010:
|
||||
http://www.pjrc.com/teensy/td_libs_OneWire.html
|
||||
Search fix from Robin James
|
||||
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
|
||||
Use direct optimized I/O in all cases
|
||||
Disable interrupts during timing critical sections
|
||||
(this solves many random communication errors)
|
||||
Disable interrupts during read-modify-write I/O
|
||||
Reduce RAM consumption by eliminating unnecessary
|
||||
variables and trimming many to 8 bits
|
||||
Optimize both crc8 - table version moved to flash
|
||||
|
||||
Modified to work with larger numbers of devices - avoids loop.
|
||||
Tested in Arduino 11 alpha with 12 sensors.
|
||||
26 Sept 2008 -- Robin James
|
||||
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
|
||||
|
||||
Updated to work with arduino-0008 and to include skip() as of
|
||||
2007/07/06. --RJL20
|
||||
|
||||
Modified to calculate the 8-bit CRC directly, avoiding the need for
|
||||
the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010
|
||||
-- Tom Pollard, Jan 23, 2008
|
||||
|
||||
Jim Studt's original library was modified by Josh Larios.
|
||||
|
||||
Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Much of the code was inspired by Derek Yerger's code, though I don't
|
||||
think much of that remains. In any event that was..
|
||||
(copyleft) 2006 by Derek Yerger - Free to distribute freely.
|
||||
|
||||
The CRC code was excerpted and inspired by the Dallas Semiconductor
|
||||
sample code bearing this copyright.
|
||||
//---------------------------------------------------------------------------
|
||||
// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// Except as contained in this notice, the name of Dallas Semiconductor
|
||||
// shall not be used except as stated in the Dallas Semiconductor
|
||||
// Branding Policy.
|
||||
//--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "OneWire.h"
|
||||
#include "util/OneWire_direct_gpio.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// due to the dual core esp32, a critical section works better than disabling interrupts
|
||||
# define noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux)
|
||||
# define interrupts() portEXIT_CRITICAL(&mux);}
|
||||
// for info on this, search "IRAM_ATTR" at https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/general-notes.html
|
||||
# define CRIT_TIMING IRAM_ATTR
|
||||
#else
|
||||
# define CRIT_TIMING
|
||||
#endif
|
||||
|
||||
|
||||
void OneWire::begin(uint8_t pin)
|
||||
{
|
||||
pinMode(pin, INPUT);
|
||||
bitmask = PIN_TO_BITMASK(pin);
|
||||
baseReg = PIN_TO_BASEREG(pin);
|
||||
#if ONEWIRE_SEARCH
|
||||
reset_search();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Perform the onewire reset function. We will wait up to 250uS for
|
||||
// the bus to come high, if it doesn't then it is broken or shorted
|
||||
// and we return a 0;
|
||||
//
|
||||
// Returns 1 if a device asserted a presence pulse, 0 otherwise.
|
||||
//
|
||||
uint8_t CRIT_TIMING OneWire::reset(void)
|
||||
{
|
||||
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||
__attribute__((unused)) volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
|
||||
uint8_t r;
|
||||
uint8_t retries = 125;
|
||||
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(reg, mask);
|
||||
interrupts();
|
||||
// wait until the wire is high... just in case
|
||||
do {
|
||||
if (--retries == 0) return 0;
|
||||
delayMicroseconds(2);
|
||||
} while ( !DIRECT_READ(reg, mask));
|
||||
|
||||
noInterrupts();
|
||||
DIRECT_WRITE_LOW(reg, mask);
|
||||
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
|
||||
interrupts();
|
||||
delayMicroseconds(480);
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(reg, mask); // allow it to float
|
||||
delayMicroseconds(70);
|
||||
r = !DIRECT_READ(reg, mask);
|
||||
interrupts();
|
||||
delayMicroseconds(410);
|
||||
return r;
|
||||
}
|
||||
|
||||
//
|
||||
// Write a bit. Port and bit is used to cut lookup time and provide
|
||||
// more certain timing.
|
||||
//
|
||||
void CRIT_TIMING OneWire::write_bit(uint8_t v)
|
||||
{
|
||||
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||
__attribute__((unused)) volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
|
||||
|
||||
if (v & 1) {
|
||||
noInterrupts();
|
||||
DIRECT_WRITE_LOW(reg, mask);
|
||||
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
|
||||
delayMicroseconds(10);
|
||||
DIRECT_WRITE_HIGH(reg, mask); // drive output high
|
||||
interrupts();
|
||||
delayMicroseconds(55);
|
||||
} else {
|
||||
noInterrupts();
|
||||
DIRECT_WRITE_LOW(reg, mask);
|
||||
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
|
||||
delayMicroseconds(65);
|
||||
DIRECT_WRITE_HIGH(reg, mask); // drive output high
|
||||
interrupts();
|
||||
delayMicroseconds(5);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Read a bit. Port and bit is used to cut lookup time and provide
|
||||
// more certain timing.
|
||||
//
|
||||
uint8_t CRIT_TIMING OneWire::read_bit(void)
|
||||
{
|
||||
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||
__attribute__((unused)) volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
|
||||
uint8_t r;
|
||||
|
||||
noInterrupts();
|
||||
DIRECT_MODE_OUTPUT(reg, mask);
|
||||
DIRECT_WRITE_LOW(reg, mask);
|
||||
delayMicroseconds(3);
|
||||
DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise
|
||||
delayMicroseconds(10);
|
||||
r = DIRECT_READ(reg, mask);
|
||||
interrupts();
|
||||
delayMicroseconds(53);
|
||||
return r;
|
||||
}
|
||||
|
||||
//
|
||||
// Write a byte. The writing code uses the active drivers to raise the
|
||||
// pin high, if you need power after the write (e.g. DS18S20 in
|
||||
// parasite power mode) then set 'power' to 1, otherwise the pin will
|
||||
// go tri-state at the end of the write to avoid heating in a short or
|
||||
// other mishap.
|
||||
//
|
||||
void OneWire::write(uint8_t v, uint8_t power /* = 0 */) {
|
||||
uint8_t bitMask;
|
||||
|
||||
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
|
||||
OneWire::write_bit( (bitMask & v)?1:0);
|
||||
}
|
||||
if ( !power) {
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(baseReg, bitmask);
|
||||
DIRECT_WRITE_LOW(baseReg, bitmask);
|
||||
interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
void OneWire::write_bytes(const uint8_t *buf, uint16_t count, bool power /* = 0 */) {
|
||||
for (uint16_t i = 0 ; i < count ; i++)
|
||||
write(buf[i]);
|
||||
if (!power) {
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(baseReg, bitmask);
|
||||
DIRECT_WRITE_LOW(baseReg, bitmask);
|
||||
interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Read a byte
|
||||
//
|
||||
uint8_t OneWire::read() {
|
||||
uint8_t bitMask;
|
||||
uint8_t r = 0;
|
||||
|
||||
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
|
||||
if ( OneWire::read_bit()) r |= bitMask;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
void OneWire::read_bytes(uint8_t *buf, uint16_t count) {
|
||||
for (uint16_t i = 0 ; i < count ; i++)
|
||||
buf[i] = read();
|
||||
}
|
||||
|
||||
//
|
||||
// Do a ROM select
|
||||
//
|
||||
void OneWire::select(const uint8_t rom[8])
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
write(0x55); // Choose ROM
|
||||
|
||||
for (i = 0; i < 8; i++) write(rom[i]);
|
||||
}
|
||||
|
||||
//
|
||||
// Do a ROM skip
|
||||
//
|
||||
void OneWire::skip()
|
||||
{
|
||||
write(0xCC); // Skip ROM
|
||||
}
|
||||
|
||||
void OneWire::depower()
|
||||
{
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(baseReg, bitmask);
|
||||
interrupts();
|
||||
}
|
||||
|
||||
#if ONEWIRE_SEARCH
|
||||
|
||||
//
|
||||
// You need to use this function to start a search again from the beginning.
|
||||
// You do not need to do it for the first search, though you could.
|
||||
//
|
||||
void OneWire::reset_search()
|
||||
{
|
||||
// reset the search state
|
||||
LastDiscrepancy = 0;
|
||||
LastDeviceFlag = false;
|
||||
LastFamilyDiscrepancy = 0;
|
||||
for(int i = 7; ; i--) {
|
||||
ROM_NO[i] = 0;
|
||||
if ( i == 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the search to find the device type 'family_code' on the next call
|
||||
// to search(*newAddr) if it is present.
|
||||
//
|
||||
void OneWire::target_search(uint8_t family_code)
|
||||
{
|
||||
// set the search state to find SearchFamily type devices
|
||||
ROM_NO[0] = family_code;
|
||||
for (uint8_t i = 1; i < 8; i++)
|
||||
ROM_NO[i] = 0;
|
||||
LastDiscrepancy = 64;
|
||||
LastFamilyDiscrepancy = 0;
|
||||
LastDeviceFlag = false;
|
||||
}
|
||||
|
||||
//
|
||||
// Perform a search. If this function returns a '1' then it has
|
||||
// enumerated the next device and you may retrieve the ROM from the
|
||||
// OneWire::address variable. If there are no devices, no further
|
||||
// devices, or something horrible happens in the middle of the
|
||||
// enumeration then a 0 is returned. If a new device is found then
|
||||
// its address is copied to newAddr. Use OneWire::reset_search() to
|
||||
// start over.
|
||||
//
|
||||
// --- Replaced by the one from the Dallas Semiconductor web site ---
|
||||
//--------------------------------------------------------------------------
|
||||
// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing
|
||||
// search state.
|
||||
// Return TRUE : device found, ROM number in ROM_NO buffer
|
||||
// FALSE : device not found, end of search
|
||||
//
|
||||
bool OneWire::search(uint8_t *newAddr, bool search_mode /* = true */)
|
||||
{
|
||||
uint8_t id_bit_number;
|
||||
uint8_t last_zero, rom_byte_number;
|
||||
bool search_result;
|
||||
uint8_t id_bit, cmp_id_bit;
|
||||
|
||||
unsigned char rom_byte_mask, search_direction;
|
||||
|
||||
// initialize for search
|
||||
id_bit_number = 1;
|
||||
last_zero = 0;
|
||||
rom_byte_number = 0;
|
||||
rom_byte_mask = 1;
|
||||
search_result = false;
|
||||
|
||||
// if the last call was not the last one
|
||||
if (!LastDeviceFlag) {
|
||||
// 1-Wire reset
|
||||
if (!reset()) {
|
||||
// reset the search
|
||||
LastDiscrepancy = 0;
|
||||
LastDeviceFlag = false;
|
||||
LastFamilyDiscrepancy = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// issue the search command
|
||||
if (search_mode == true) {
|
||||
write(0xF0); // NORMAL SEARCH
|
||||
} else {
|
||||
write(0xEC); // CONDITIONAL SEARCH
|
||||
}
|
||||
|
||||
// loop to do the search
|
||||
do
|
||||
{
|
||||
// read a bit and its complement
|
||||
id_bit = read_bit();
|
||||
cmp_id_bit = read_bit();
|
||||
|
||||
// check for no devices on 1-wire
|
||||
if ((id_bit == 1) && (cmp_id_bit == 1)) {
|
||||
break;
|
||||
} else {
|
||||
// all devices coupled have 0 or 1
|
||||
if (id_bit != cmp_id_bit) {
|
||||
search_direction = id_bit; // bit write value for search
|
||||
} else {
|
||||
// if this discrepancy if before the Last Discrepancy
|
||||
// on a previous next then pick the same as last time
|
||||
if (id_bit_number < LastDiscrepancy) {
|
||||
search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0);
|
||||
} else {
|
||||
// if equal to last pick 1, if not then pick 0
|
||||
search_direction = (id_bit_number == LastDiscrepancy);
|
||||
}
|
||||
// if 0 was picked then record its position in LastZero
|
||||
if (search_direction == 0) {
|
||||
last_zero = id_bit_number;
|
||||
|
||||
// check for Last discrepancy in family
|
||||
if (last_zero < 9)
|
||||
LastFamilyDiscrepancy = last_zero;
|
||||
}
|
||||
}
|
||||
|
||||
// set or clear the bit in the ROM byte rom_byte_number
|
||||
// with mask rom_byte_mask
|
||||
if (search_direction == 1)
|
||||
ROM_NO[rom_byte_number] |= rom_byte_mask;
|
||||
else
|
||||
ROM_NO[rom_byte_number] &= ~rom_byte_mask;
|
||||
|
||||
// serial number search direction write bit
|
||||
write_bit(search_direction);
|
||||
|
||||
// increment the byte counter id_bit_number
|
||||
// and shift the mask rom_byte_mask
|
||||
id_bit_number++;
|
||||
rom_byte_mask <<= 1;
|
||||
|
||||
// if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
|
||||
if (rom_byte_mask == 0) {
|
||||
rom_byte_number++;
|
||||
rom_byte_mask = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
while(rom_byte_number < 8); // loop until through all ROM bytes 0-7
|
||||
|
||||
// if the search was successful then
|
||||
if (!(id_bit_number < 65)) {
|
||||
// search successful so set LastDiscrepancy,LastDeviceFlag,search_result
|
||||
LastDiscrepancy = last_zero;
|
||||
|
||||
// check for last device
|
||||
if (LastDiscrepancy == 0) {
|
||||
LastDeviceFlag = true;
|
||||
}
|
||||
search_result = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if no device found then reset counters so next 'search' will be like a first
|
||||
if (!search_result || !ROM_NO[0]) {
|
||||
LastDiscrepancy = 0;
|
||||
LastDeviceFlag = false;
|
||||
LastFamilyDiscrepancy = 0;
|
||||
search_result = false;
|
||||
} else {
|
||||
for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i];
|
||||
}
|
||||
return search_result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if ONEWIRE_CRC
|
||||
// The 1-Wire CRC scheme is described in Maxim Application Note 27:
|
||||
// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products"
|
||||
//
|
||||
|
||||
#if ONEWIRE_CRC8_TABLE
|
||||
// Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0
|
||||
// Tiny 2x16 entry CRC table created by Arjen Lentz
|
||||
// See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table
|
||||
static const uint8_t PROGMEM dscrc2x16_table[] = {
|
||||
0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83,
|
||||
0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41,
|
||||
0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8,
|
||||
0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74
|
||||
};
|
||||
|
||||
// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM
|
||||
// and the registers. (Use tiny 2x16 entry CRC table)
|
||||
uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len)
|
||||
{
|
||||
uint8_t crc = 0;
|
||||
|
||||
while (len--) {
|
||||
crc = *addr++ ^ crc; // just re-using crc as intermediate
|
||||
crc = pgm_read_byte(dscrc2x16_table + (crc & 0x0f)) ^
|
||||
pgm_read_byte(dscrc2x16_table + 16 + ((crc >> 4) & 0x0f));
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
#else
|
||||
//
|
||||
// Compute a Dallas Semiconductor 8 bit CRC directly.
|
||||
// this is much slower, but a little smaller, than the lookup table.
|
||||
//
|
||||
uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len)
|
||||
{
|
||||
uint8_t crc = 0;
|
||||
|
||||
while (len--) {
|
||||
#if defined(__AVR__)
|
||||
crc = _crc_ibutton_update(crc, *addr++);
|
||||
#else
|
||||
uint8_t inbyte = *addr++;
|
||||
for (uint8_t i = 8; i; i--) {
|
||||
uint8_t mix = (crc ^ inbyte) & 0x01;
|
||||
crc >>= 1;
|
||||
if (mix) crc ^= 0x8C;
|
||||
inbyte >>= 1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ONEWIRE_CRC16
|
||||
bool OneWire::check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc)
|
||||
{
|
||||
crc = ~crc16(input, len, crc);
|
||||
return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1];
|
||||
}
|
||||
|
||||
uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc)
|
||||
{
|
||||
#if defined(__AVR__)
|
||||
for (uint16_t i = 0 ; i < len ; i++) {
|
||||
crc = _crc16_update(crc, input[i]);
|
||||
}
|
||||
#else
|
||||
static const uint8_t oddparity[16] =
|
||||
{ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 };
|
||||
|
||||
for (uint16_t i = 0 ; i < len ; i++) {
|
||||
// Even though we're just copying a byte from the input,
|
||||
// we'll be doing 16-bit computation with it.
|
||||
uint16_t cdata = input[i];
|
||||
cdata = (cdata ^ crc) & 0xff;
|
||||
crc >>= 8;
|
||||
|
||||
if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4])
|
||||
crc ^= 0xC001;
|
||||
|
||||
cdata <<= 6;
|
||||
crc ^= cdata;
|
||||
cdata <<= 1;
|
||||
crc ^= cdata;
|
||||
}
|
||||
#endif
|
||||
return crc;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
// undef defines for no particular reason
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
# undef noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux)
|
||||
# undef interrupts() portEXIT_CRITICAL(&mux);}
|
||||
#endif
|
||||
// for info on this, search "IRAM_ATTR" at https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/general-notes.html
|
||||
#undef CRIT_TIMING
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
#ifndef OneWire_h
|
||||
#define OneWire_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__AVR__)
|
||||
#include <util/crc16.h>
|
||||
#endif
|
||||
|
||||
#if ARDUINO >= 100
|
||||
#include <Arduino.h> // for delayMicroseconds, digitalPinToBitMask, etc
|
||||
#else
|
||||
#include "WProgram.h" // for delayMicroseconds
|
||||
#include "pins_arduino.h" // for digitalPinToBitMask, etc
|
||||
#endif
|
||||
|
||||
// You can exclude certain features from OneWire. In theory, this
|
||||
// might save some space. In practice, the compiler automatically
|
||||
// removes unused code (technically, the linker, using -fdata-sections
|
||||
// and -ffunction-sections when compiling, and Wl,--gc-sections
|
||||
// when linking), so most of these will not result in any code size
|
||||
// reduction. Well, unless you try to use the missing features
|
||||
// and redesign your program to not need them! ONEWIRE_CRC8_TABLE
|
||||
// is the exception, because it selects a fast but large algorithm
|
||||
// or a small but slow algorithm.
|
||||
|
||||
// you can exclude onewire_search by defining that to 0
|
||||
#ifndef ONEWIRE_SEARCH
|
||||
#define ONEWIRE_SEARCH 1
|
||||
#endif
|
||||
|
||||
// You can exclude CRC checks altogether by defining this to 0
|
||||
#ifndef ONEWIRE_CRC
|
||||
#define ONEWIRE_CRC 1
|
||||
#endif
|
||||
|
||||
// Select the table-lookup method of computing the 8-bit CRC
|
||||
// by setting this to 1. The lookup table enlarges code size by
|
||||
// about 250 bytes. It does NOT consume RAM (but did in very
|
||||
// old versions of OneWire). If you disable this, a slower
|
||||
// but very compact algorithm is used.
|
||||
#ifndef ONEWIRE_CRC8_TABLE
|
||||
#define ONEWIRE_CRC8_TABLE 1
|
||||
#endif
|
||||
|
||||
// You can allow 16-bit CRC checks by defining this to 1
|
||||
// (Note that ONEWIRE_CRC must also be 1.)
|
||||
#ifndef ONEWIRE_CRC16
|
||||
#define ONEWIRE_CRC16 1
|
||||
#endif
|
||||
|
||||
// Board-specific macros for direct GPIO
|
||||
#include "util/OneWire_direct_regtype.h"
|
||||
|
||||
class OneWire
|
||||
{
|
||||
private:
|
||||
IO_REG_TYPE bitmask;
|
||||
volatile IO_REG_TYPE *baseReg;
|
||||
|
||||
#if ONEWIRE_SEARCH
|
||||
// global search state
|
||||
unsigned char ROM_NO[8];
|
||||
uint8_t LastDiscrepancy;
|
||||
uint8_t LastFamilyDiscrepancy;
|
||||
bool LastDeviceFlag;
|
||||
#endif
|
||||
|
||||
public:
|
||||
OneWire() { }
|
||||
OneWire(uint8_t pin) { begin(pin); }
|
||||
void begin(uint8_t pin);
|
||||
|
||||
// Perform a 1-Wire reset cycle. Returns 1 if a device responds
|
||||
// with a presence pulse. Returns 0 if there is no device or the
|
||||
// bus is shorted or otherwise held low for more than 250uS
|
||||
uint8_t reset(void);
|
||||
|
||||
// Issue a 1-Wire rom select command, you do the reset first.
|
||||
void select(const uint8_t rom[8]);
|
||||
|
||||
// Issue a 1-Wire rom skip command, to address all on bus.
|
||||
void skip(void);
|
||||
|
||||
// Write a byte. If 'power' is one then the wire is held high at
|
||||
// the end for parasitically powered devices. You are responsible
|
||||
// for eventually depowering it by calling depower() or doing
|
||||
// another read or write.
|
||||
void write(uint8_t v, uint8_t power = 0);
|
||||
|
||||
void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0);
|
||||
|
||||
// Read a byte.
|
||||
uint8_t read(void);
|
||||
|
||||
void read_bytes(uint8_t *buf, uint16_t count);
|
||||
|
||||
// Write a bit. The bus is always left powered at the end, see
|
||||
// note in write() about that.
|
||||
void write_bit(uint8_t v);
|
||||
|
||||
// Read a bit.
|
||||
uint8_t read_bit(void);
|
||||
|
||||
// Stop forcing power onto the bus. You only need to do this if
|
||||
// you used the 'power' flag to write() or used a write_bit() call
|
||||
// and aren't about to do another read or write. You would rather
|
||||
// not leave this powered if you don't have to, just in case
|
||||
// someone shorts your bus.
|
||||
void depower(void);
|
||||
|
||||
#if ONEWIRE_SEARCH
|
||||
// Clear the search state so that if will start from the beginning again.
|
||||
void reset_search();
|
||||
|
||||
// Setup the search to find the device type 'family_code' on the next call
|
||||
// to search(*newAddr) if it is present.
|
||||
void target_search(uint8_t family_code);
|
||||
|
||||
// Look for the next device. Returns 1 if a new address has been
|
||||
// returned. A zero might mean that the bus is shorted, there are
|
||||
// no devices, or you have already retrieved all of them. It
|
||||
// might be a good idea to check the CRC to make sure you didn't
|
||||
// get garbage. The order is deterministic. You will always get
|
||||
// the same devices in the same order.
|
||||
bool search(uint8_t *newAddr, bool search_mode = true);
|
||||
#endif
|
||||
|
||||
#if ONEWIRE_CRC
|
||||
// Compute a Dallas Semiconductor 8 bit CRC, these are used in the
|
||||
// ROM and scratchpad registers.
|
||||
static uint8_t crc8(const uint8_t *addr, uint8_t len);
|
||||
|
||||
#if ONEWIRE_CRC16
|
||||
// Compute the 1-Wire CRC16 and compare it against the received CRC.
|
||||
// Example usage (reading a DS2408):
|
||||
// // Put everything in a buffer so we can compute the CRC easily.
|
||||
// uint8_t buf[13];
|
||||
// buf[0] = 0xF0; // Read PIO Registers
|
||||
// buf[1] = 0x88; // LSB address
|
||||
// buf[2] = 0x00; // MSB address
|
||||
// WriteBytes(net, buf, 3); // Write 3 cmd bytes
|
||||
// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16
|
||||
// if (!CheckCRC16(buf, 11, &buf[11])) {
|
||||
// // Handle error.
|
||||
// }
|
||||
//
|
||||
// @param input - Array of bytes to checksum.
|
||||
// @param len - How many bytes to use.
|
||||
// @param inverted_crc - The two CRC16 bytes in the received data.
|
||||
// This should just point into the received data,
|
||||
// *not* at a 16-bit integer.
|
||||
// @param crc - The crc starting value (optional)
|
||||
// @return True, iff the CRC matches.
|
||||
static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0);
|
||||
|
||||
// Compute a Dallas Semiconductor 16 bit CRC. This is required to check
|
||||
// the integrity of data received from many 1-Wire devices. Note that the
|
||||
// CRC computed here is *not* what you'll get from the 1-Wire network,
|
||||
// for two reasons:
|
||||
// 1) The CRC is transmitted bitwise inverted.
|
||||
// 2) Depending on the endian-ness of your processor, the binary
|
||||
// representation of the two-byte return value may have a different
|
||||
// byte order than the two bytes you get from 1-Wire.
|
||||
// @param input - Array of bytes to checksum.
|
||||
// @param len - How many bytes to use.
|
||||
// @param crc - The crc starting value (optional)
|
||||
// @return The CRC16, as defined by Dallas Semiconductor.
|
||||
static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0);
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
// Prevent this name from leaking into Arduino sketches
|
||||
#ifdef IO_REG_TYPE
|
||||
#undef IO_REG_TYPE
|
||||
#endif
|
||||
|
||||
#endif // __cplusplus
|
||||
#endif // OneWire_h
|
||||
@@ -0,0 +1,519 @@
|
||||
#ifndef OneWire_Direct_GPIO_h
|
||||
#define OneWire_Direct_GPIO_h
|
||||
|
||||
// This header should ONLY be included by OneWire.cpp. These defines are
|
||||
// meant to be private, used within OneWire.cpp, but not exposed to Arduino
|
||||
// sketches or other libraries which may include OneWire.h.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Platform specific I/O definitions
|
||||
|
||||
#if defined(__AVR__)
|
||||
#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin)))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint8_t
|
||||
#define IO_REG_BASE_ATTR asm("r30")
|
||||
#define IO_REG_MASK_ATTR
|
||||
#if defined(__AVR_ATmega4809__)
|
||||
#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*((base)-8)) &= ~(mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)-8)) |= (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*((base)-4)) &= ~(mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)-4)) |= (mask))
|
||||
#else
|
||||
#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask))
|
||||
#endif
|
||||
|
||||
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
|
||||
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
|
||||
#define PIN_TO_BITMASK(pin) (1)
|
||||
#define IO_REG_TYPE uint8_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR __attribute__ ((unused))
|
||||
#define DIRECT_READ(base, mask) (*((base)+512))
|
||||
#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0)
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1)
|
||||
#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1)
|
||||
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1)
|
||||
|
||||
#elif defined(__MKL26Z64__)
|
||||
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint8_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask))
|
||||
|
||||
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
|
||||
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) ((*((base)+2) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) (*((base)+1) &= ~(mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+1) |= (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) (*((base)+34) = (mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+33) = (mask))
|
||||
|
||||
#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__)
|
||||
// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due.
|
||||
// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268
|
||||
// If you have trouble with OneWire on Arduino Due, please check the
|
||||
// status of delayMicroseconds() before reporting a bug in OneWire!
|
||||
#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask))
|
||||
#ifndef PROGMEM
|
||||
#define PROGMEM
|
||||
#endif
|
||||
#ifndef pgm_read_byte
|
||||
#define pgm_read_byte(addr) (*(const uint8_t *)(addr))
|
||||
#endif
|
||||
|
||||
#elif defined(__PIC32MX__)
|
||||
#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin)))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
// Special note: I depend on the ESP community to maintain these definitions and
|
||||
// submit good pull requests. I can not answer any ESP questions or help you
|
||||
// resolve any problems related to ESP chips. Please do not contact me and please
|
||||
// DO NOT CREATE GITHUB ISSUES for ESP support. All ESP questions must be asked
|
||||
// on ESP community forums.
|
||||
#define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO)
|
||||
#define PIN_TO_BITMASK(pin) (1UL << (pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeInput(IO_REG_TYPE mask)
|
||||
{
|
||||
if(mask > 0x8000)
|
||||
{
|
||||
GP16FFS(GPFFS_GPIO(16));
|
||||
GPC16 = 0;
|
||||
GP16E &= ~1;
|
||||
}
|
||||
else
|
||||
{
|
||||
GPE &= ~(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeOutput(IO_REG_TYPE mask)
|
||||
{
|
||||
if(mask > 0x8000)
|
||||
{
|
||||
GP16FFS(GPFFS_GPIO(16));
|
||||
GPC16 = 0;
|
||||
GP16E |= 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
GPE |= (mask);
|
||||
}
|
||||
}
|
||||
static inline __attribute__((always_inline))
|
||||
bool directRead(IO_REG_TYPE mask)
|
||||
{
|
||||
if(mask > 0x8000)
|
||||
return GP16I & 0x01;
|
||||
else
|
||||
return ((GPI & (mask)) ? true : false);
|
||||
}
|
||||
|
||||
#define DIRECT_READ(base, mask) directRead(mask)
|
||||
#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask)
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask)
|
||||
#define DIRECT_WRITE_LOW(base, mask) (mask > 0x8000) ? GP16O &= ~1 : (GPOC = (mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) (mask > 0x8000) ? GP16O |= 1 : (GPOS = (mask))
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#include <driver/rtc_io.h>
|
||||
#include <soc/gpio_struct.h>
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
IO_REG_TYPE directRead(IO_REG_TYPE pin)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
return (GPIO.in.val >> pin) & 0x1;
|
||||
#else // plain ESP32
|
||||
if ( pin < 32 )
|
||||
return (GPIO.in >> pin) & 0x1;
|
||||
else if ( pin < 46 )
|
||||
return (GPIO.in1.val >> (pin - 32)) & 0x1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteLow(IO_REG_TYPE pin)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
GPIO.out_w1tc.val = ((uint32_t)1 << pin);
|
||||
#else // plain ESP32
|
||||
if ( pin < 32 )
|
||||
GPIO.out_w1tc = ((uint32_t)1 << pin);
|
||||
else if ( pin < 46 )
|
||||
GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32));
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteHigh(IO_REG_TYPE pin)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
GPIO.out_w1ts.val = ((uint32_t)1 << pin);
|
||||
#else // plain ESP32
|
||||
if ( pin < 32 )
|
||||
GPIO.out_w1ts = ((uint32_t)1 << pin);
|
||||
else if ( pin < 46 )
|
||||
GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32));
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeInput(IO_REG_TYPE pin)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
GPIO.enable_w1tc.val = ((uint32_t)1 << (pin));
|
||||
#else
|
||||
if ( digitalPinIsValid(pin) )
|
||||
{
|
||||
#if ESP_IDF_VERSION_MAJOR < 4 // IDF 3.x ESP32/PICO-D4
|
||||
uint32_t rtc_reg(rtc_gpio_desc[pin].reg);
|
||||
|
||||
if ( rtc_reg ) // RTC pins PULL settings
|
||||
{
|
||||
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux);
|
||||
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown);
|
||||
}
|
||||
#endif
|
||||
// Input
|
||||
if ( pin < 32 )
|
||||
GPIO.enable_w1tc = ((uint32_t)1 << pin);
|
||||
else
|
||||
GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeOutput(IO_REG_TYPE pin)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
GPIO.enable_w1ts.val = ((uint32_t)1 << (pin));
|
||||
#else
|
||||
if ( digitalPinIsValid(pin) && pin <= 33 ) // pins above 33 can be only inputs
|
||||
{
|
||||
#if ESP_IDF_VERSION_MAJOR < 4 // IDF 3.x ESP32/PICO-D4
|
||||
uint32_t rtc_reg(rtc_gpio_desc[pin].reg);
|
||||
|
||||
if ( rtc_reg ) // RTC pins PULL settings
|
||||
{
|
||||
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux);
|
||||
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown);
|
||||
}
|
||||
#endif
|
||||
// Output
|
||||
if ( pin < 32 )
|
||||
GPIO.enable_w1ts = ((uint32_t)1 << pin);
|
||||
else // already validated to pins <= 33
|
||||
GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#define DIRECT_READ(base, pin) directRead(pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin)
|
||||
#define DIRECT_MODE_INPUT(base, pin) directModeInput(pin)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(pin)
|
||||
// https://github.com/PaulStoffregen/OneWire/pull/47
|
||||
// https://github.com/stickbreaker/OneWire/commit/6eb7fc1c11a15b6ac8c60e5671cf36eb6829f82c
|
||||
#ifdef interrupts
|
||||
#undef interrupts
|
||||
#endif
|
||||
#ifdef noInterrupts
|
||||
#undef noInterrupts
|
||||
#endif
|
||||
#define noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux)
|
||||
#define interrupts() portEXIT_CRITICAL(&mux);}
|
||||
//#warning "ESP32 OneWire testing"
|
||||
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) ((uint32_t)digitalPinToPinName(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) digitalReadFast((PinName)pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) digitalWriteFast((PinName)pin, LOW)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) digitalWriteFast((PinName)pin, HIGH)
|
||||
#define DIRECT_MODE_INPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0))
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_OUTPUT_PP, GPIO_NOPULL, 0))
|
||||
|
||||
#elif defined(__SAMD21G18A__)
|
||||
#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask))
|
||||
|
||||
#elif defined(__ASR6501__)
|
||||
#define PIN_IN_PORT(pin) (pin % PIN_NUMBER_IN_PORT)
|
||||
#define PORT_FROM_PIN(pin) (pin / PIN_NUMBER_IN_PORT)
|
||||
#define PORT_OFFSET(port) (PORT_REG_SHFIT * port)
|
||||
#define PORT_ADDRESS(pin) (CYDEV_GPIO_BASE + PORT_OFFSET(PORT_FROM_PIN(pin)))
|
||||
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) CY_SYS_PINS_READ_PIN(PORT_ADDRESS(pin)+4, PIN_IN_PORT(pin))
|
||||
#define DIRECT_WRITE_LOW(base, pin) CY_SYS_PINS_CLEAR_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin))
|
||||
#define DIRECT_WRITE_HIGH(base, pin) CY_SYS_PINS_SET_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin))
|
||||
#define DIRECT_MODE_INPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_DIG_HIZ)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_STRONG)
|
||||
|
||||
#elif defined(RBL_NRF51822)
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin)
|
||||
#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin)
|
||||
|
||||
#elif defined(__arc__) /* Arduino101/Genuino101 specifics */
|
||||
|
||||
#include "scss_registers.h"
|
||||
#include "portable.h"
|
||||
#include "avr/pgmspace.h"
|
||||
|
||||
#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId)
|
||||
#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType)
|
||||
#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase)
|
||||
#define DIR_OFFSET_SS 0x01
|
||||
#define DIR_OFFSET_SOC 0x04
|
||||
#define EXT_PORT_OFFSET_SS 0x0A
|
||||
#define EXT_PORT_OFFSET_SOC 0x50
|
||||
|
||||
/* GPIO registers base address */
|
||||
#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase)
|
||||
#define PIN_TO_BITMASK(pin) pin
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
IO_REG_TYPE ret;
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS));
|
||||
} else {
|
||||
ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC);
|
||||
}
|
||||
return ((ret >> GPIO_ID(pin)) & 0x01);
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)),
|
||||
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
|
||||
} else {
|
||||
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)),
|
||||
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
|
||||
} else {
|
||||
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base);
|
||||
} else {
|
||||
MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base);
|
||||
} else {
|
||||
MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin));
|
||||
}
|
||||
}
|
||||
|
||||
#define DIRECT_READ(base, pin) directRead(base, pin)
|
||||
#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin)
|
||||
|
||||
#elif defined(__riscv)
|
||||
|
||||
/*
|
||||
* Tested on highfive1
|
||||
*
|
||||
* Stable results are achieved operating in the
|
||||
* two high speed modes of the highfive1. It
|
||||
* seems to be less reliable in slow mode.
|
||||
*/
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) digitalPinToBitMask(pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
IO_REG_TYPE directRead(IO_REG_TYPE mask)
|
||||
{
|
||||
return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeInput(IO_REG_TYPE mask)
|
||||
{
|
||||
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
|
||||
GPIO_REG(GPIO_IOF_EN) &= ~mask;
|
||||
|
||||
GPIO_REG(GPIO_INPUT_EN) |= mask;
|
||||
GPIO_REG(GPIO_OUTPUT_EN) &= ~mask;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeOutput(IO_REG_TYPE mask)
|
||||
{
|
||||
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
|
||||
GPIO_REG(GPIO_IOF_EN) &= ~mask;
|
||||
|
||||
GPIO_REG(GPIO_INPUT_EN) &= ~mask;
|
||||
GPIO_REG(GPIO_OUTPUT_EN) |= mask;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteLow(IO_REG_TYPE mask)
|
||||
{
|
||||
GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteHigh(IO_REG_TYPE mask)
|
||||
{
|
||||
GPIO_REG(GPIO_OUTPUT_VAL) |= mask;
|
||||
}
|
||||
|
||||
#define DIRECT_READ(base, mask) directRead(mask)
|
||||
#define DIRECT_WRITE_LOW(base, mask) directWriteLow(mask)
|
||||
#define DIRECT_WRITE_HIGH(base, mask) directWriteHigh(mask)
|
||||
#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask)
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask)
|
||||
|
||||
#elif defined(__MBED__)
|
||||
|
||||
#include "platform/mbed_critical.h"
|
||||
#include "DigitalInOut.h"
|
||||
#include <cmsis_os2.h>
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (new mbed::DigitalInOut(digitalPinToPinName(pin)))
|
||||
#define IO_REG_TYPE mbed::DigitalInOut*
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) (*pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) (*pin = 0)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) (*pin = 1)
|
||||
#define DIRECT_MODE_INPUT(base, pin) (pin->input())
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) (pin->output())
|
||||
#undef interrupts
|
||||
#undef noInterrupts
|
||||
#define noInterrupts() osThreadSetPriority(osThreadGetId(), osPriorityRealtime) //core_util_critical_section_enter()
|
||||
#define interrupts() osThreadSetPriority(osThreadGetId(), osPriorityNormal) //core_util_critical_section_exit()
|
||||
|
||||
#elif defined(ARDUINO_ARCH_MBED_RP2040)|| defined(ARDUINO_ARCH_RP2040)
|
||||
#define delayMicroseconds(time) busy_wait_us(time)
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (pin)
|
||||
#define IO_REG_TYPE unsigned int
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) digitalRead(pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH)
|
||||
#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT)
|
||||
#warning "OneWire. RP2040 in Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite."
|
||||
|
||||
#else
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (pin)
|
||||
#define IO_REG_TYPE unsigned int
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) digitalRead(pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH)
|
||||
#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT)
|
||||
#warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture."
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,59 @@
|
||||
#ifndef OneWire_Direct_RegType_h
|
||||
#define OneWire_Direct_RegType_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Platform specific I/O register type
|
||||
|
||||
#if defined(__AVR__)
|
||||
#define IO_REG_TYPE uint8_t
|
||||
|
||||
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
|
||||
#define IO_REG_TYPE uint8_t
|
||||
|
||||
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__MKL26Z64__)
|
||||
#define IO_REG_TYPE uint8_t
|
||||
|
||||
#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__PIC32MX__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_MASK_ATTR
|
||||
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__SAMD21G18A__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__ASR6501__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(RBL_NRF51822)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__arc__) /* Arduino101/Genuino101 specifics */
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__MBED__)
|
||||
#include "DigitalInOut.h"
|
||||
#define IO_REG_TYPE mbed::DigitalInOut*
|
||||
|
||||
#elif defined(__riscv)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#else
|
||||
#define IO_REG_TYPE unsigned int
|
||||
|
||||
#endif
|
||||
#endif
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
Vendored Arduino libraries for P05 WROOM HAL only (not used on S3).
|
||||
|
||||
- AccelStepper 1.64 (waspinator)
|
||||
- OneWire 2.3.8 (PaulStoffregen)
|
||||
- DallasTemperature 3.9.1 (milesburton; PIO listed 3.11.0 which is not tagged upstream)
|
||||
|
||||
P06 WROOM UI:
|
||||
|
||||
- Keypad 3.1.1 (chris--a / Mark Stanley)
|
||||
- LiquidCrystal_I2C (johnrickman / marcoschwartz 1.1.x API)
|
||||
+12
-1
@@ -20,7 +20,7 @@ Gitea `act_runner` must be registered against `git.i3omb.com` with labels that m
|
||||
|
||||
| Workflow `runs-on` | Typical runner label | Notes |
|
||||
| --- | --- | --- |
|
||||
| `ubuntu-latest` | `ubuntu-latest:docker://gitea/runner-images:ubuntu-latest` | Host tests, checkout, artifact upload |
|
||||
| `ubuntu-latest` | `ubuntu-latest:docker://gitea/runner-images:ubuntu-latest` | Host tests, checkout, artifact upload. Image has **no cmake/gcc** — the test job must `apt-get install` them. |
|
||||
| (job `container`) | Docker socket available on the runner | Firmware jobs use `espressif/idf:<tag>` as the job container |
|
||||
|
||||
Firmware builds need:
|
||||
@@ -155,6 +155,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install host toolchain
|
||||
if: hashFiles('tests/host/CMakeLists.txt') != ''
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
if command -v sudo >/dev/null 2>&1 && [ "$(id -u)" -ne 0 ]; then
|
||||
SUDO=sudo
|
||||
else
|
||||
SUDO=
|
||||
fi
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y --no-install-recommends cmake gcc g++ make
|
||||
- name: Host unit tests
|
||||
if: hashFiles('tests/host/CMakeLists.txt') != ''
|
||||
run: |
|
||||
|
||||
@@ -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,38 @@
|
||||
# Codebase Audit & Remediation Plan (develop branch)
|
||||
|
||||
After a thorough audit of the `develop` branch focusing on the recent modular ESP-IDF port, I've identified several critical defects primarily centered around FreeRTOS concurrency, system timers, and legacy feature regressions.
|
||||
|
||||
Here is the step-by-step remediation plan to address these issues. **No code has been changed yet.**
|
||||
|
||||
## 1. Fix Critical Step Timing Bug in `app_machine.c`
|
||||
**Defect:** `CMD_START_STEP` triggers `start_running(0)`, passing a hardcoded `0` for `now_ms`. The task then calculates `s_deadline_ms = 0 + remaining_ms`. When `app_machine_tick()` runs moments later, it reads the *real* system uptime (e.g., 200,000 ms). If the uptime is larger than the step duration, the step instantly completes.
|
||||
**Remediation Steps:**
|
||||
- Modify `start_running()` to remove the `now_ms` argument.
|
||||
- Instead of calculating `s_deadline_ms` immediately, configure the state machine to defer calculation: set `s_have_deadline = false;` and `s_resume_pending = true;`.
|
||||
- This safely delegates the deadline calculation to the next `app_machine_tick()` cycle, which naturally computes it using the true system `now_ms`.
|
||||
- Update `app_machine_handle_cmd()` to call `start_running()` without arguments.
|
||||
|
||||
## 2. Fix Event Queue Concurrency in `app_machine.c`
|
||||
**Defect:** The system uses a raw array `s_q` (with `s_q_head`, `s_q_tail`, and `s_q_count++`) to pass events from the machine to the UI. However, `emit()` is called by both the `machine_task` and the `temp_task` (via `app_machine_on_temp`), while `app_machine_last_event()` is read by the `ui_task`. These are non-atomic read-modify-write operations across three threads, which will inevitably corrupt the queue and crash the UI.
|
||||
**Remediation Steps:**
|
||||
- Replace the raw ring buffer variables (`s_q`, `s_q_head`, `s_q_tail`, `s_q_count`) with a standard FreeRTOS `QueueHandle_t s_evtq`.
|
||||
- In `app_machine_init()`, initialize the queue: `s_evtq = xQueueCreate(UI_EVT_QUEUE_LEN, sizeof(ui_evt_t));`.
|
||||
- Update `emit()` to use `xQueueSend(s_evtq, &ev, 0);`.
|
||||
- Update `app_machine_last_event()` to use `xQueueReceive(s_evtq, out, 0) == pdTRUE`.
|
||||
|
||||
## 3. Restore Auto-Advance Functionality in `app_machine.c`
|
||||
**Defect:** The legacy Arduino loop automatically chained processing steps together (`run == 1`). The new state machine includes a `maybe_auto_advance()` function guarded by `s_auto_advance`, but `s_auto_advance` is permanently hardcoded to `false` and never toggled. As a result, the machine halts after every single step.
|
||||
**Remediation Steps:**
|
||||
- Initialize `s_auto_advance = true` to match the legacy behavior of chaining steps automatically.
|
||||
- (Optional) Wire up a UI command (`CMD_TOGGLE_AUTO_ADVANCE`) to allow users to turn this off if manual pausing between steps is desired.
|
||||
|
||||
## 4. Fix Task Watchdog Initialization in `main.c`
|
||||
**Defect:** `ui_task`, `machine_task`, `input_task`, and `temp_task` all invoke `esp_task_wdt_add(NULL)`. However, the Task Watchdog Timer (TWDT) is never initialized in `app_main()`. Depending on the ESP-IDF version and `sdkconfig` defaults, this can cause silent failures or panic at boot.
|
||||
**Remediation Steps:**
|
||||
- In `app_main()`, invoke `esp_task_wdt_init(&wdt_config)` *before* creating the FreeRTOS tasks.
|
||||
- Ensure the WDT timeout is set generously (e.g., 3-5 seconds) to accommodate the 1.5-second blocking time in `hal_temp_tick()`.
|
||||
|
||||
## 5. Clean up `hal_temp_tick()` Timing (Low Priority)
|
||||
**Defect:** `hal_temp_tick()` blocks `temp_task` with multiple `vTaskDelay(pdMS_TO_TICKS(750))` calls for the DS18B20 conversion. While safely isolated from the UI, it forces the watchdog timeout to be artificially large and limits responsiveness if multiple sensors were ever added.
|
||||
**Remediation Steps:**
|
||||
- Refactor the 1-Wire sequence into a non-blocking state machine within `temp_task`, or simply keep it as-is but acknowledge the WDT requirement constraint outlined in step 4.
|
||||
@@ -0,0 +1,63 @@
|
||||
# AUDIT MEGAPLAN — develop-branch defect remediation
|
||||
|
||||
Audience: coding agent. One phase = one session. Do not start the next phase in the same session.
|
||||
|
||||
## Source
|
||||
|
||||
Implements `docs/audit_remediation_plan.md` — five defects found in the post-refactor `develop` tree: step deadline computed from a fake `now`, non-thread-safe UI event ring, auto-advance regression, implicit task-watchdog setup, and a blocking DS18B20 conversion in `hal_temp_tick`.
|
||||
|
||||
## Protocol (every session)
|
||||
|
||||
1. `git checkout feature/audit-remediation && git pull --ff-only`
|
||||
2. Read only: `AGENTS.md`, this file (status table), **the assigned phase file**, and the audit doc items it cites. Open `docs/CURRENT_STATE.md` / `docs/TARGET_ARCHITECTURE.md` / `docs/CICD.md` only if the phase `READ:` list says so.
|
||||
3. Execute `IN` only. Honour `OUT` and `FORBIDDEN`.
|
||||
4. Run `VERIFY` exactly. Do not push if any verify item fails. Local verify = host ctest; firmware builds run in Gitea CI on push (`feature/**` is a push trigger — see `.gitea/workflows/ci.yml`).
|
||||
5. Commits: messages listed in the phase. Imperative. One concern per commit.
|
||||
6. Push `origin feature/audit-remediation`. PR target is `develop`, not `main`.
|
||||
7. Set phase `STATUS:` to `DONE` in the phase file **and** this table. One-line `Notes` if you diverged (API name only — do not silently change behaviour).
|
||||
|
||||
If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase file with the exact error.
|
||||
|
||||
## Frozen constraints (never reinterpret)
|
||||
|
||||
- Recipe **values** (times s, CW, CCW, temp min/pref/max) for C41 E6 ECN-2 B&W Custom B&WREV = `components/app_process/app_process.c` / `docs/CURRENT_STATE.md`. Changing values requires explicit user order + doc update in the same commit.
|
||||
- Stop while running: Esc `'X'` (keypad r4c4) and touch STOP on S3 → immediate motor **EN disabled**, then Resume or ReturnToStepSelect. `hal_motor_request_stop` runs before any other work on the stop path.
|
||||
- UI never blocks on motor, OneWire, or audio.
|
||||
- No Arduino types in `ui_cmd`, `app_process`, `app_machine` public headers.
|
||||
- Watchdog stays **on** — A03 makes setup explicit, it does not disable anything. No `vTaskDelete` of long-lived workers.
|
||||
- Session time edits ±5 s are RAM overlays only. No NVS profiles. No pumps/valves/heaters.
|
||||
- **Auto-advance**: REFACTOR-MEGAPLAN froze `auto_advance` default `false` "unless a later phase says so" — **A02 is that phase** (audit item 3, owner-approved). Semantics = legacy `run==1` chain: auto-**ARM** the next step, never auto-run; the operator still presses Start per step.
|
||||
- Event queue semantics change (accepted): a full event queue now drops the **newest** event (`xQueueSend` timeout 0); the old ring overwrote the oldest. UI drains at 40 Hz vs ≤~1.3 Hz producers — overflow is not expected.
|
||||
|
||||
## Branch
|
||||
|
||||
`feature/audit-remediation` cut from `develop` → PRs into `develop`. (`feature/**` is required for the CI push trigger.)
|
||||
|
||||
## Working-tree note
|
||||
|
||||
The branch was cut with uncommitted `components/app_machine/app_machine.c` changes present: they implement audit items 1–2 **plus** the item-3 flag flip. A01 lands items 1–2 with `s_auto_advance` reverted to `false`; A02 lands the flag. Do not commit the whole dirty file in one go — split per concern (`git add -p` or re-apply in order).
|
||||
|
||||
## Status
|
||||
|
||||
| ID | File | Session goal | STATUS |
|
||||
| --- | --- | --- | --- |
|
||||
| A00 | `audit/A00-docs.md` | Land audit doc + megaplan + phase files | DONE |
|
||||
| A01 | `audit/A01-machine-timing-queue.md` | Deadline fix + FreeRTOS event queue + host queue shim | DONE |
|
||||
| A02 | `audit/A02-auto-advance.md` | Restore auto-arm after step complete + test rework | DONE |
|
||||
| A03 | `audit/A03-task-wdt.md` | Explicit TWDT init/reconfigure + `add()` failure logging | DONE |
|
||||
| A04 | `audit/A04-temp-nonblocking.md` | OPTIONAL: non-blocking DS18B20 conversion | DONE |
|
||||
|
||||
## Dependency
|
||||
|
||||
```
|
||||
A00 → A01 → A02 (same file; A02 builds on A01's queue + tests)
|
||||
A03 is independent of A01/A02 (main.c only) — run any time after A00
|
||||
A04 runs after A03 (shares temp_task in main.c); OPTIONAL — skipping is acceptable
|
||||
```
|
||||
|
||||
## Do not
|
||||
|
||||
- Expand scope: no `CMD_TOGGLE_AUTO_ADVANCE` (rejected by owner), no UI changes, no NVS, no pumps/heater, no recipe edits.
|
||||
- Commit `sdkconfig` (generated), `build/`, `build_host/`, tokens.
|
||||
- Batch unrelated fixes into a phase.
|
||||
- Reduce the TWDT timeout below the blocking bound while `hal_temp_tick` still blocks (~750 ms); A04 removes that constraint.
|
||||
@@ -0,0 +1,41 @@
|
||||
# INTEGRATION MEGAPLAN — AutoFilm-ESP32 S3 Hardware Integration
|
||||
|
||||
Audience: coding agent. One phase = one session. Do not start the next phase in the same session.
|
||||
|
||||
## Summary
|
||||
|
||||
This megaplan drives the hardware integration for the ESP32-S3 (JC4827W543) board, activating the stepper motor, DS18B20 temperature sensor, and I2S speaker using conflict-free, header-available GPIOs. To ensure success, these changes have been broken down from a single monolithic phase into a sequence of safe, isolated phases.
|
||||
|
||||
## Protocol (every session)
|
||||
|
||||
1. Ensure you are on the appropriate integration branch off `develop`.
|
||||
2. Read only: `AGENTS.md`, this file (status table), **the assigned phase file**. Open other docs only if the phase `READ:` list says so.
|
||||
3. Execute `IN` only. Honour `OUT` and `FORBIDDEN`.
|
||||
4. Run `VERIFY` exactly. Do not push if any verify item fails.
|
||||
5. Commits: messages listed in the phase. Imperative. No secret tokens.
|
||||
6. Push to origin. PR target is `develop`, not `main`.
|
||||
7. Set phase `STATUS:` to `DONE` in the phase file **and** this table. One-line `Notes` if you diverged (API name only — do not silently change behaviour).
|
||||
|
||||
If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase file with the exact error.
|
||||
|
||||
## Frozen constraints (never reinterpret)
|
||||
|
||||
- The motor EN/STEP/DIR, DS18B20, and I2S audio pins for the JC4827W543 are strictly defined in this plan and the pin audit doc.
|
||||
- **Constraints that eliminate S3 pins:**
|
||||
- QSPI display: 45, 47, 21, 48, 40, 39 + BL 1 — consumed
|
||||
- GT911: SDA 8, SCL 4, INT 3, RST 38 — consumed
|
||||
- Speaker I2S: 2 (LRCLK), 41 (DIN), 42 (BCLK) — consumed by onboard amp
|
||||
- TF slot: 10–13; USB: 19/20; UART0: 43/44; BOOT+LCD_TE: 0
|
||||
- Octal PSRAM: 33–37 off-limits
|
||||
- GPIO46 is INPUT-ONLY on ESP32-S3 — excluded from outputs entirely
|
||||
- UI never blocks on hardware operations (motor, OneWire, I2S).
|
||||
- Watchdog stays **on**. No `vTaskDelete` of long-lived workers.
|
||||
|
||||
## Status
|
||||
|
||||
| ID | File | Session goal | STATUS |
|
||||
| --- | --- | --- | --- |
|
||||
| I01 | `integration/I01-board-configs.md` | Board pin allocations & accessors | DONE |
|
||||
| I02 | `integration/I02-temp-fix.md` | Temp HAL shared & starvation fix | DONE |
|
||||
| I03 | `integration/I03-motor-hal.md` | Motor HAL shared across boards | DONE |
|
||||
| I04 | `integration/I04-audio-i2s.md` | I2S audio implementation for S3 | DONE |
|
||||
@@ -37,14 +37,22 @@ If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase fi
|
||||
| P02 | `refactor/P02-ui-cmd-process.md` | `ui_cmd` + `app_process` + golden tests | DONE |
|
||||
| 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 | BLOCKED |
|
||||
| P06 | `refactor/P06-ui-wroom-cutover.md` | 2004 UI + keypad + Stop/Resume cutover | TODO |
|
||||
| P07 | `refactor/P07-board-s3-ui.md` | JC4827W543 display+GT911+STOP | TODO |
|
||||
| P08 | `refactor/P08-idf-debt.md` | Replace Arduino drivers (RMT etc.) | TODO |
|
||||
| P05 | `refactor/P05-hal-wroom-motion.md` | WROOM motor/temp/audio HAL | DONE |
|
||||
| P06 | `refactor/P06-ui-wroom-cutover.md` | 2004 UI + keypad + Stop/Resume cutover | DONE |
|
||||
| P07 | `refactor/P07-board-s3-ui.md` | JC4827W543 display+GT911+STOP | DONE |
|
||||
| P08 | `refactor/P08-idf-debt.md` | Replace Arduino drivers (RMT etc.) | DONE |
|
||||
|
||||
P04 Notes: merge_bin uses `${{ gitea.sha }}` (not `github.sha`); runner unverified (no Actions run).
|
||||
|
||||
P05 Notes: BLOCKED — no app_machine + HAL headers from P03 (branch @ 415c13a). Not implemented.
|
||||
P05 Notes: Arduino-esp32 ~3.2.1 (3.1.x requires IDF <5.4) + IDF v5.4; AccelStepper 1.64; OneWire 2.3.8; DallasTemperature 3.9.1. AUTOFILM_MOTION_SMOKE default off. hw not flashed.
|
||||
|
||||
P06 Notes: WROOM input_task dual-path stop (hal_motor_request_stop then CMD_STOP). Legacy startingMenu unlinked from IDF. Notes=`hw smoke pending`.
|
||||
|
||||
P07 Notes: custom NV3041A QSPI + GT911; shared app_ui text grid + colour softkeys; EN=NC. idf.py not run here (no IDF_PATH). host ctest green. Hw unflashed.
|
||||
|
||||
P08 Notes: LEDC audio; RMT STEP + GPIO DIR/EN; onewire_bus DS18B20 +0.4; GPIO keypad; IDF I2C 2004. Arduino-as-component removed. third_party trees unlinked. host ctest green. idf.py not run here (no IDF_PATH). hw unflashed.
|
||||
|
||||
AUDIT A02 Notes: supersedes the frozen `auto_advance` default-false constraint — `s_auto_advance = true` restores the legacy auto-ARM chain (audit item 3, owner-approved); auto-ARM only, never auto-run.
|
||||
|
||||
## Dependency
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# A00 — land audit docs on the remediation branch
|
||||
|
||||
STATUS: DONE
|
||||
DEPENDS: —
|
||||
Notes: executed by the session that authored the megaplan.
|
||||
|
||||
**READ:** `AGENTS.md`, `docs/megaplans/AUDIT-MEGAPLAN.md`
|
||||
|
||||
**IN:**
|
||||
1. Create `feature/audit-remediation` from `develop`.
|
||||
2. Commit `docs/audit_remediation_plan.md`, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/megaplans/audit/*.md`, and the `build_host/` `.gitignore` line.
|
||||
3. Do **not** commit the `components/app_machine/app_machine.c` working-tree changes (A01/A02 own them) or `build_host/` artifacts.
|
||||
|
||||
**OUT:** all code changes.
|
||||
|
||||
**VERIFY:** `git log -1` shows the docs commit; `git status` shows only `app_machine.c` modified.
|
||||
|
||||
**COMMITS:**
|
||||
1. `Add audit remediation megaplan`
|
||||
|
||||
**DoD checkboxes:**
|
||||
- [x] Branch `feature/audit-remediation` exists, cut from `develop`.
|
||||
- [x] Audit doc + megaplan + phase files committed.
|
||||
- [x] `app_machine.c` left uncommitted for A01/A02.
|
||||
@@ -0,0 +1,44 @@
|
||||
# A01 — app_machine deadline fix + FreeRTOS event queue + host queue shim
|
||||
|
||||
STATUS: DONE
|
||||
DEPENDS: A00
|
||||
|
||||
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` items 1–2, `components/app_machine/app_machine.c`, `tests/host/CMakeLists.txt`
|
||||
|
||||
**Context:** the working tree already carries uncommitted fixes for items 1–2 **and** the item-3 `s_auto_advance = true` flip. This phase lands items 1–2 only; the flag stays `false` until A02. The queue change adds `#include "freertos/queue.h"` to `app_machine.c`, which the host test build cannot resolve — a shim is required.
|
||||
|
||||
**IN:**
|
||||
1. `app_machine.c` deadline fix (keep from working tree): `start_running(void)` takes no `now_ms`; sets `s_remaining_ms = time_s * 1000`, `s_have_deadline = false`, `s_resume_pending = true`. First `app_machine_tick` in `ST_RUNNING` computes `s_deadline_ms = now_ms + s_remaining_ms` from real uptime. `app_machine_handle_cmd` calls `start_running()` with no args.
|
||||
2. `app_machine.c` cleanup: revert `s_auto_advance` to `false`; restore `#define AGITATE_RPM 60u`; **remove** the duplicate `#define UI_EVT_QUEUE_LEN 16` (already defined via `ui_cmd.h`).
|
||||
3. `app_machine.c` event queue (keep from working tree): `static QueueHandle_t s_evtq`; `emit()` does `xQueueSend(s_evtq, &ev, 0)` when non-NULL; `app_machine_last_event()` does `xQueueReceive(s_evtq, out, 0) == pdTRUE`. In `app_machine_init`: `if (s_evtq == NULL) { s_evtq = xQueueCreate(UI_EVT_QUEUE_LEN, sizeof(ui_evt_t)); } else { xQueueReset(s_evtq); }` — host tests re-init repeatedly and stale events must not leak between inits.
|
||||
4. `components/app_machine/CMakeLists.txt`: add `freertos` to `PRIV_REQUIRES` (explicit dependency for `freertos/queue.h`).
|
||||
5. New host shim so `test_machine` keeps compiling the same `app_machine.c`:
|
||||
- `tests/host/freertos/FreeRTOS.h` — `BaseType_t`, `UBaseType_t`, `TickType_t`, `pdTRUE`, `pdFALSE`, `pdPASS`, `pdMS_TO_TICKS`.
|
||||
- `tests/host/freertos/queue.h` — `QueueHandle_t` + prototypes for `xQueueCreate`, `xQueueSend`, `xQueueReceive`, `xQueueReset`.
|
||||
- `tests/host/freertos/queue.c` — malloc'd ring buffer; `xQueueSend` returns `pdFALSE` (drop) when full; `xQueueReceive` returns `pdFALSE` when empty; `xQueueReset` clears.
|
||||
- `tests/host/CMakeLists.txt` — add `freertos/queue.c` to the `test_machine` sources (`tests/host` is already on its include path, so `"freertos/FreeRTOS.h"` resolves to the shim).
|
||||
6. Behaviour must be identical for host tests: all existing `test_machine.c` cases pass **unchanged** (auto_advance still `false`).
|
||||
|
||||
**OUT:** `s_auto_advance = true` (A02), TWDT setup (A03), `hal_temp` internals (A04), any UI change.
|
||||
|
||||
**FORBIDDEN:** committing the auto-advance flag; changing recipe values; Arduino types; deleting or weakening host test assertions.
|
||||
|
||||
**VERIFY:**
|
||||
```
|
||||
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
|
||||
```
|
||||
All green. Firmware builds run in CI on push.
|
||||
|
||||
**COMMITS** (order matters — every commit must compile on host):
|
||||
1. `Add FreeRTOS queue shim for host tests` (shim alone; old code doesn't include the headers yet — harmless)
|
||||
2. `Fix step deadline to derive from real uptime`
|
||||
3. `Replace machine event ring with FreeRTOS queue`
|
||||
|
||||
Stage hunks per concern (`git add -p` or re-apply in order) — the dirty file combines all three changes plus the A02 flag.
|
||||
|
||||
**DoD checkboxes:**
|
||||
- [x] `start_running` takes no `now_ms`; deadline set on the first `RUNNING` tick.
|
||||
- [x] `emit`/`app_machine_last_event` go through `s_evtq`; `app_machine_init` creates or resets the queue.
|
||||
- [x] `s_auto_advance` still `false`.
|
||||
- [x] Host ctest green with zero test changes.
|
||||
- [x] STATUS → DONE here and in the megaplan table.
|
||||
@@ -0,0 +1,43 @@
|
||||
# A02 — restore auto-advance (auto-arm next step after complete)
|
||||
|
||||
STATUS: DONE
|
||||
DEPENDS: A01
|
||||
Notes: `stop_disables_motor_and_resume` final assertion updated to `ST_ARMED` — the "unchanged" list cannot hold once auto-advance is on (resume-to-complete auto-arms step 1).
|
||||
|
||||
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` item 3, `docs/CURRENT_STATE.md` §Runtime behaviour, `components/app_machine/app_machine.c`, `tests/host/test_machine.c`
|
||||
|
||||
**Semantics (implement exactly):** legacy `run==1` in `src/menu.cpp` chained `startProcessing()`, which displayed `Ent:start Esc:quit` for the next step — i.e. **auto-ARM, never auto-run**. `maybe_auto_advance()` already implements this: on the completing tick it moves to `ST_ARMED` for `s_step+1` (emitting `EVT_STEP_ARMED`), or `ST_IDLE` + `EVT_PROCESS_IDLE` after the last step. `ST_COMPLETE` becomes transient inside the completing tick — the alarm (`hal_audio_alarm_complete`, ~7.5 s async, self-terminating) plays while the next step sits armed. That is acceptable and intentional: do **not** add `alarm_cancel` to `start_running` and do not block on the melody.
|
||||
|
||||
**IN:**
|
||||
1. `app_machine.c`: set `s_auto_advance = true` in `app_machine_init`. Nothing else in the state machine changes.
|
||||
2. Keep the `ST_COMPLETE` + `CMD_STOP` alarm-cancel branch in `app_machine_handle_cmd` — it becomes unreachable while auto-advance is on, but stays as a defensive path.
|
||||
3. Rework `tests/host/test_machine.c` for transient COMPLETE (Custom has 4 steps, ECN-2 RemJet is step 1, C41 Prewarm is step 0):
|
||||
- `arm_start_complete_custom_10s`: after `tick(10000)` expect `ST_ARMED` and `step_index == 1` (auto-armed next step); drop the `tick(10001)` "stays complete" assertion.
|
||||
- Replace `stop_during_complete_cancels_alarm` with `auto_advance_last_step_goes_idle`: select Custom, `cmd_arm(3)`, `cmd_start(3)`, `tick(0)`, `tick(10000)` → `ST_IDLE`; drain events and assert `EVT_PROCESS_IDLE` present.
|
||||
- `ecn2_remjet_zero_time`: after `tick(0)` expect `ST_ARMED` at step index 2.
|
||||
- `c41_clock`: after `tick(180000)` expect `ST_ARMED` at step index 1.
|
||||
- All other cases unchanged (`select_c41_step_view`, `adjust_does_not_mutate_const`, `stop_disables_motor_and_resume`, `return_from_stopped`, `stop_ignored_meaningless_in_idle`).
|
||||
- Note: `complete_step` still emits `EVT_STEP_COMPLETE` before `EVT_STEP_ARMED`/`EVT_PROCESS_IDLE`; tests may assert that ordering via `pop_ids` if convenient.
|
||||
4. `docs/megaplans/REFACTOR-MEGAPLAN.md`: add a one-line `Notes` entry recording that the `auto_advance` default-false freeze is superseded by AUDIT A02 (owner-approved via audit item 3). Do not rewrite the frozen-constraint text itself.
|
||||
|
||||
**OUT:** `CMD_TOGGLE_AUTO_ADVANCE` (rejected by owner), any UI/input changes, NVS, recipe values.
|
||||
|
||||
**FORBIDDEN:** auto-starting the next step's motor; removing the `ST_COMPLETE` STOP branch; changing recipe tables; editing the frozen-constraint lines of REFACTOR-MEGAPLAN.
|
||||
|
||||
**VERIFY:**
|
||||
```
|
||||
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
|
||||
```
|
||||
All green, including the new `auto_advance_last_step_goes_idle`. Firmware builds run in CI on push.
|
||||
|
||||
**COMMITS:**
|
||||
1. `Restore auto-advance to arm next step after step complete`
|
||||
2. `Update machine tests for auto-advance`
|
||||
|
||||
**DoD checkboxes:**
|
||||
- [x] `s_auto_advance = true`; auto-ARM only, never auto-run.
|
||||
- [x] Last step completes → `ST_IDLE` + `EVT_PROCESS_IDLE`.
|
||||
- [x] `ST_COMPLETE` + `CMD_STOP` branch retained.
|
||||
- [x] REFACTOR-MEGAPLAN supersede note added.
|
||||
- [x] Host ctest green.
|
||||
- [x] STATUS → DONE here and in the megaplan table.
|
||||
@@ -0,0 +1,60 @@
|
||||
# A03 — explicit task watchdog setup in app_main
|
||||
|
||||
STATUS: DONE
|
||||
DEPENDS: A00 (independent of A01/A02 — `main/main.c` only)
|
||||
|
||||
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` item 4, `main/main.c`, `sdkconfig.defaults`
|
||||
|
||||
**Context (audit correction):** the audit's "TWDT never initialized → panic at boot" is unlikely under the current config — `sdkconfig.defaults` sets `CONFIG_ESP_TASK_WDT_EN=y` + `CONFIG_ESP_TASK_WDT_TIMEOUT_S=10` + `CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y`, and IDF v5.4 auto-initialises the TWDT before `app_main`, so `esp_task_wdt_add(NULL)` in the tasks already succeeds. The real gaps: setup is implicit (breaks silently if the Kconfig default ever changes), and every `esp_task_wdt_add` return value is ignored. Make setup explicit and failures observable. **Keep the effective config identical** — 10 s already bounds the ~750 ms `hal_temp_tick` block; do not adopt the audit's 3–5 s suggestion.
|
||||
|
||||
**IN:**
|
||||
1. `main/main.c`, in `app_main` before the `xTaskCreate` block, under `#if CONFIG_ESP_TASK_WDT_EN`:
|
||||
```c
|
||||
esp_task_wdt_config_t wdt_cfg = {
|
||||
.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000,
|
||||
.idle_core_mask =
|
||||
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
|
||||
(1u << 0)
|
||||
#else
|
||||
0
|
||||
#endif
|
||||
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
|
||||
| (1u << 1)
|
||||
#endif
|
||||
,
|
||||
#if CONFIG_ESP_TASK_WDT_PANIC
|
||||
.trigger_panic = true,
|
||||
#else
|
||||
.trigger_panic = false,
|
||||
#endif
|
||||
};
|
||||
esp_err_t wdt_err = esp_task_wdt_init(&wdt_cfg);
|
||||
if (wdt_err == ESP_ERR_INVALID_STATE) {
|
||||
wdt_err = esp_task_wdt_reconfigure(&wdt_cfg); /* already auto-initialised */
|
||||
}
|
||||
if (wdt_err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "task wdt setup: %s", esp_err_to_name(wdt_err));
|
||||
}
|
||||
```
|
||||
Verify field names/signatures against `esp_task_wdt.h` in the pinned IDF (v5.4) and adjust the sketch only as needed to compile.
|
||||
2. In each of `input_task`, `machine_task`, `ui_task`, `temp_task`: capture `esp_err_t werr = esp_task_wdt_add(NULL);` and `ESP_LOGW(TAG, "... wdt add failed: %s", esp_err_to_name(werr))` on non-OK; continue regardless — the unconditional `esp_task_wdt_reset()` no-ops harmlessly if the task is not subscribed.
|
||||
|
||||
**OUT:** `app_machine`, `hal_temp` internals, UI code, `sdkconfig*` edits.
|
||||
|
||||
**FORBIDDEN:** disabling the TWDT or idle-task watch; `vTaskDelete`; shrinking the timeout; committing a generated `sdkconfig`.
|
||||
|
||||
**VERIFY:**
|
||||
```
|
||||
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
|
||||
```
|
||||
(unchanged — `main.c` is not host-compiled; run anyway to catch regressions). Firmware builds for both `esp32` and `esp32s3` run in CI on push — confirm both go green.
|
||||
|
||||
**COMMITS:**
|
||||
1. `Make task watchdog setup explicit in app_main`
|
||||
|
||||
**DoD checkboxes:**
|
||||
- [x] init-or-reconfigure runs before task creation.
|
||||
- [x] `esp_task_wdt_add` failures are logged in all four tasks.
|
||||
- [x] Effective config unchanged: 10 s timeout, CPU0 idle watched, no panic.
|
||||
- [x] Both CI firmware builds green.
|
||||
- [x] STATUS → DONE here and in the megaplan table.
|
||||
@@ -0,0 +1,37 @@
|
||||
# A04 — (OPTIONAL) non-blocking DS18B20 conversion in hal_temp
|
||||
|
||||
STATUS: DONE
|
||||
DEPENDS: A03 (shares `temp_task` in `main/main.c`)
|
||||
|
||||
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` item 5, `components/hal_temp/hal_temp.c`, `main/main.c`
|
||||
|
||||
**Context:** `hal_temp_tick()` currently blocks `temp_task` for ~750 ms per conversion (plus 750 ms on each error path). It is safely isolated from the UI, so this is a robustness/latency improvement only — the audit itself allows keeping the block if the WDT constraint is documented (A03 does that). Implement only if the phase session chooses to spend it.
|
||||
|
||||
**IN:**
|
||||
1. `components/hal_temp/hal_temp.c`: turn `hal_temp_tick()` into a two-state machine driven by a deadline (`esp_timer_get_time()` / 1000, or `xTaskGetTickCount()`):
|
||||
- `T_START`: `onewire_bus_reset` + `CMD_SKIP_ROM`/`CMD_CONVERT_T`; on success record `s_deadline_ms = now + 750` and move to `T_WAIT`. On any bus error: `s_last_ok = false`, set a ~750 ms retry deadline, stay in `T_START` (preserves the current retry cadence).
|
||||
- `T_WAIT`: if `now < s_deadline_ms` return immediately; else `onewire_bus_reset` + `CMD_SKIP_ROM`/`CMD_READ_SCRATCH`, read 9 bytes, CRC-check via existing `scratch_valid`, update `s_last_c`/`s_last_ok`, move back to `T_START` (next conversion starts on the next tick).
|
||||
- The `s_bus == NULL` path must also be non-blocking (`s_last_ok = false`, return).
|
||||
- Keep `TEMP_OFFSET`, `scratch_valid`, `hal_temp_init`, and `hal_temp_read_c` semantics unchanged.
|
||||
2. `main/main.c` `temp_task`: `hal_temp_tick` no longer blocks, so add `vTaskDelay(pdMS_TO_TICKS(100))` at the end of the loop (~10 Hz poll) so the task always sleeps. Calling `app_machine_on_temp` every loop is acceptable (16-deep event queue drained at 40 Hz by `ui_task`); keep it unconditional to keep the diff small.
|
||||
3. Host stub `components/hal_temp/stub/hal_temp.c` / `tests/host/stubs/hal_temp.c`: no signature change — leave as-is unless the implementation forces otherwise.
|
||||
|
||||
**OUT:** additional sensors, temperature alarms, heater control, `hal_temp_read_c` contract changes.
|
||||
|
||||
**FORBIDDEN:** changing `TEMP_OFFSET` or the CRC; any `vTaskDelay` ≥ 750 ms left inside `hal_temp_tick`; blocking the UI task; removing the watchdog.
|
||||
|
||||
**VERIFY:**
|
||||
```
|
||||
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
|
||||
```
|
||||
plus CI firmware builds for both targets.
|
||||
|
||||
**COMMITS:**
|
||||
1. `Make DS18B20 conversion non-blocking in hal_temp`
|
||||
|
||||
**DoD checkboxes:**
|
||||
- [x] No `vTaskDelay(750)` inside `hal_temp_tick`; conversion waits via deadline.
|
||||
- [x] `temp_task` always blocks (explicit 100 ms delay).
|
||||
- [x] `TEMP_OFFSET`, CRC, and read semantics unchanged.
|
||||
- [x] Host ctest green; both CI firmware builds green.
|
||||
- [x] STATUS → DONE here and in the megaplan table (or noted `SKIPPED` with reason).
|
||||
@@ -0,0 +1,23 @@
|
||||
STATUS: DONE
|
||||
DEPENDS: none
|
||||
|
||||
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `docs/JC4827W543_PIN_AUDIT_VERIFIED.md`, `components/board_jc4827w543/board.c`, `components/board_wroom/board.c`
|
||||
|
||||
**IN:**
|
||||
1. `board_jc4827w543`: add `board_pin_motor_step()` → 16, `board_pin_motor_dir()` → 15, `board_pin_temp()` → 17, `board_pin_spk_bclk()`, `board_pin_spk_lrclk()`, `board_pin_spk_din()` → 42, 2, 41; change `board_pin_motor_en()` → 7 (keep disable level 1 = active-LOW EN).
|
||||
2. `board_wroom`: add matching `board_pin_motor_step()` → 12, `board_pin_motor_dir()` → 14, `board_pin_temp()` → 13 so HAL impls can be shared.
|
||||
3. Update `docs/CURRENT_STATE.md` pin table note and `JC4827W543_PIN_AUDIT_VERIFIED.md` header section with the corrected labels if not already updated (P2: IO46/9/14/5, P3: IO6/7/15/16, P4: GND/3V3/17/18).
|
||||
|
||||
**OUT:** HAL implementations, business logic, NVS profiles.
|
||||
**FORBIDDEN:** GPIO46 for any output; stealing TF, QSPI, GT911, USB, or UART0 pins.
|
||||
|
||||
**VERIFY:** `idf.py build` succeeds for both `esp32` and `esp32s3` targets.
|
||||
|
||||
**COMMITS:**
|
||||
1. `Lock S3 header pins and introduce shared board pin accessors`
|
||||
2. `Update pin audit documentation labels`
|
||||
|
||||
**DoD checkboxes:**
|
||||
- [ ] `board_jc4827w543` pin accessors added.
|
||||
- [ ] `board_wroom` pin accessors added.
|
||||
- [ ] Build succeeds on both targets.
|
||||
@@ -0,0 +1,23 @@
|
||||
STATUS: DONE
|
||||
DEPENDS: I01
|
||||
Notes: Poll API named `hal_temp_tick` (declared in hal_temp.h). `idf.py build` for both targets verified via CI run 38616 (no local IDF). S3 flash watchdog check not run — no device attached to this machine.
|
||||
|
||||
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `main/main.c`, `components/hal_temp/*`
|
||||
|
||||
**IN:**
|
||||
1. `hal_temp`: Generalize `hal_temp.c` to use `board_pin_temp()` instead of hardcoded macros. Compile for both targets via CMake (`IDF_TARGET esp32|esp32s3` → shared source, `PRIV_REQUIRES board_wroom|board_jc4827w543`).
|
||||
2. `main.c` **bug fix**: `temp_task` currently calls `autofilm_temp_tick()` only `#ifdef AUTOFILM_BOARD_WROOM` and has **no `vTaskDelay`** — on S3 it spins at priority 2 and starves the idle task (task-WDT risk). Move the tick loop into a `hal_temp`-level poll (e.g. `hal_temp_tick()` weak per-board) or add `vTaskDelay(pdMS_TO_TICKS(750))` on the non-WROOM path so the task always blocks.
|
||||
|
||||
**OUT:** Motor logic, audio logic.
|
||||
**FORBIDDEN:** Removing the watchdog, deleting the temp task.
|
||||
|
||||
**VERIFY:** `idf.py build` succeeds for both `esp32` and `esp32s3`. Flash to S3 and verify no task watchdog panics occur in the console.
|
||||
|
||||
**COMMITS:**
|
||||
1. `Share onewire temp HAL across both boards`
|
||||
2. `Fix temp_task starvation bug on non-WROOM`
|
||||
|
||||
**DoD checkboxes:**
|
||||
- [x] `hal_temp` generalized to use board accessors.
|
||||
- [x] `temp_task` starvation bug fixed.
|
||||
- [x] Build succeeds on both targets.
|
||||
@@ -0,0 +1,21 @@
|
||||
STATUS: DONE
|
||||
DEPENDS: I01
|
||||
|
||||
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `components/hal_motor/*`
|
||||
|
||||
**IN:**
|
||||
1. `hal_motor`: Generalize `wroom/hal_motor.c` (already pure-IDF RMT) to read pins from `board.h`; compile for both targets via CMake (`IDF_TARGET esp32|esp32s3` → shared source, `PRIV_REQUIRES board_wroom|board_jc4827w543`).
|
||||
2. Preserve: EN HIGH at init + on stop, `stop_req` sampled inside step loop, immortal task, `hal_motor_request_stop` ISR-safe.
|
||||
|
||||
**OUT:** Temp logic, audio logic.
|
||||
**FORBIDDEN:** Blocking UI on motor operations.
|
||||
|
||||
**VERIFY:** `idf.py build` succeeds for both targets. Run host ctests if any apply to motor logic.
|
||||
|
||||
**COMMITS:**
|
||||
1. `Share RMT motor HAL across both boards`
|
||||
|
||||
**DoD checkboxes:**
|
||||
- [ ] `hal_motor` generalized to use board accessors.
|
||||
- [ ] EN HIGH initialization preserved.
|
||||
- [ ] Build succeeds on both targets.
|
||||
@@ -0,0 +1,23 @@
|
||||
STATUS: DONE
|
||||
DEPENDS: I01
|
||||
Notes: `idf.py build` for both targets verified via CI run 38684 (no local IDF).
|
||||
|
||||
**READ:** `docs/megaplans/INTEGRATION-MEGAPLAN.md`, `components/hal_audio/*`
|
||||
|
||||
**IN:**
|
||||
1. `hal_audio` S3: Implement new `s3/hal_audio.c` using `i2s_std` TX on pins 41/42/2 (via `board_pin_spk_*`) to the NS4168 amp.
|
||||
2. Generate 2 kHz square-wave bursts into a small DMA buffer.
|
||||
3. Ensure identical queue protocol (SHORT / 10×ALARM 500ms-on/250ms-off / CANCEL) and non-blocking semantics as the WROOM LEDC version.
|
||||
|
||||
**OUT:** Motor logic, Temp logic.
|
||||
**FORBIDDEN:** Blocking the UI on I2S writes.
|
||||
|
||||
**VERIFY:** `idf.py build` succeeds for both targets.
|
||||
|
||||
**COMMITS:**
|
||||
1. `Add I2S speaker audio for S3`
|
||||
|
||||
**DoD checkboxes:**
|
||||
- [x] `hal_audio` I2S implementation added for S3.
|
||||
- [x] Queue protocol and non-blocking semantics maintained.
|
||||
- [x] Build succeeds.
|
||||
@@ -1,18 +1,6 @@
|
||||
BLOCKED: P03 artifacts missing on branch refactor/esp-idf-modular-ui @ 415c13a.
|
||||
|
||||
Required and absent:
|
||||
- components/app_machine/ (include/app_machine.h, app_machine.c)
|
||||
- components/hal_motor/include/hal_motor.h
|
||||
- components/hal_temp/include/hal_temp.h
|
||||
- components/hal_audio/include/hal_audio.h
|
||||
|
||||
Megaplan table still lists P03 STATUS=TODO. Protocol: P05 requires P03 APIs.
|
||||
Do not invent or change public HAL/app_machine APIs. Do not implement P01–P04 in this session.
|
||||
No device HAL, tasks, Arduino-as-component, or main.c cutover landed.
|
||||
|
||||
# P05 — WROOM HAL motor / temp / audio
|
||||
|
||||
STATUS: BLOCKED
|
||||
STATUS: DONE
|
||||
DEPENDS: P03
|
||||
READ: this file, `include/config.h`, `src/motor.cpp`, `src/temperature.cpp`, `src/sound.cpp`, `components/hal_*/include/*.h`
|
||||
OUT: rewriting `app_ui` / deleting menus; S3 motor pins; disabling WDT; `vTaskDelete` motor
|
||||
@@ -100,8 +88,8 @@ Manual on hardware (if unavailable, Notes=`hw not flashed`; still land code):
|
||||
|
||||
## DoD
|
||||
|
||||
- [ ] EN HIGH at init and on stop
|
||||
- [ ] stop_req checked inside step loop
|
||||
- [ ] OneWire only in temp_task
|
||||
- [ ] tone/LEDC only in audio_task
|
||||
- [ ] STATUS→DONE
|
||||
- [x] EN HIGH at init and on stop
|
||||
- [x] stop_req checked inside step loop
|
||||
- [x] OneWire only in temp_task
|
||||
- [x] tone/LEDC only in audio_task
|
||||
- [x] STATUS→DONE
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# P06 — WROOM UI cutover (2004 + keypad + Stop/Resume)
|
||||
|
||||
STATUS: TODO
|
||||
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
|
||||
@@ -130,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,7 +1,13 @@
|
||||
# P07 — JC4827W543 display + GT911 + STOP
|
||||
|
||||
STATUS: TODO
|
||||
STATUS: DONE
|
||||
DEPENDS: P06
|
||||
|
||||
Custom-draw NV3041A + GT911 colour adapter for the five P06 screens.
|
||||
Touch STOP = `hal_motor_request_stop()` then CMD_STOP via the same key map as Esc.
|
||||
Motor EN remains GPIO_NUM_NC (no spare header pin documented). No STEP/DIR.
|
||||
No LVGL. Hw smoke: unflashed.
|
||||
|
||||
READ: this file, `docs/TARGET_ARCHITECTURE.md` dual-board + UI strategy, vendor pin notes below
|
||||
OUT: LVGL in `app_machine`; changing recipes; assigning WROOM STEP/DIR onto S3 without a pin table comment; pumps
|
||||
FORBIDDEN: `lv_*` types in `ui_cmd.h` / `app_machine.h`; blocking in flush_cb beyond DMA wait; copying WROOM GPIO map blindly
|
||||
@@ -52,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.
|
||||
|
||||
+33
-5
@@ -1,11 +1,39 @@
|
||||
if(NOT DEFINED AUTOFILM_BOARD)
|
||||
set(AUTOFILM_BOARD wroom CACHE STRING "wroom|jc4827w543")
|
||||
endif()
|
||||
if(AUTOFILM_BOARD STREQUAL "wroom")
|
||||
set(BOARD_COMP board_wroom)
|
||||
elseif(AUTOFILM_BOARD STREQUAL "jc4827w543")
|
||||
if(IDF_TARGET STREQUAL "esp32s3" OR AUTOFILM_BOARD STREQUAL "jc4827w543")
|
||||
set(BOARD_COMP board_jc4827w543)
|
||||
elseif(AUTOFILM_BOARD STREQUAL "wroom" OR IDF_TARGET STREQUAL "esp32")
|
||||
set(BOARD_COMP board_wroom)
|
||||
else()
|
||||
message(FATAL_ERROR "AUTOFILM_BOARD=${AUTOFILM_BOARD}")
|
||||
message(FATAL_ERROR "AUTOFILM_BOARD=${AUTOFILM_BOARD} IDF_TARGET=${IDF_TARGET}")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS "main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ${BOARD_COMP} driver app_process app_machine app_ui
|
||||
hal_motor hal_temp hal_audio hal_display hal_input
|
||||
PRIV_REQUIRES esp_timer)
|
||||
|
||||
if(IDF_TARGET STREQUAL "esp32")
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_BOARD_WROOM=1)
|
||||
elseif(IDF_TARGET STREQUAL "esp32s3")
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_BOARD_S3=1)
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND git describe --always --dirty
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE AUTOFILM_GIT_DESC
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
)
|
||||
if(NOT AUTOFILM_GIT_DESC)
|
||||
set(AUTOFILM_GIT_DESC "dev")
|
||||
endif()
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_GIT_DESC="${AUTOFILM_GIT_DESC}")
|
||||
|
||||
option(AUTOFILM_MOTION_SMOKE "5s auto-demo on WROOM (bring-up only)" OFF)
|
||||
if(AUTOFILM_MOTION_SMOKE)
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE AUTOFILM_MOTION_SMOKE=1)
|
||||
endif()
|
||||
idf_component_register(SRCS "main.c" INCLUDE_DIRS "." REQUIRES ${BOARD_COMP} driver)
|
||||
|
||||
+231
-3
@@ -1,13 +1,196 @@
|
||||
#include "board.h"
|
||||
#include "app_process.h"
|
||||
#include "app_machine.h"
|
||||
#include "app_ui.h"
|
||||
#include "hal_motor.h"
|
||||
#include "hal_temp.h"
|
||||
#include "hal_audio.h"
|
||||
#include "hal_display.h"
|
||||
#include "hal_input.h"
|
||||
#include "ui_cmd.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#ifndef AUTOFILM_GIT_DESC
|
||||
#define AUTOFILM_GIT_DESC "dev"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "app";
|
||||
|
||||
#if defined(AUTOFILM_BOARD_WROOM) || defined(AUTOFILM_BOARD_S3)
|
||||
static QueueHandle_t s_cmdq;
|
||||
|
||||
static void map_and_post_key(char key)
|
||||
{
|
||||
ui_cmd_t cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
machine_state_t st = app_machine_state();
|
||||
|
||||
if (key >= '1' && key <= '6') {
|
||||
cmd.id = CMD_SELECT_PROCESS;
|
||||
cmd.process_id = (uint8_t)(key - '1');
|
||||
xQueueSend(s_cmdq, &cmd, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case 'U':
|
||||
cmd.id = CMD_BROWSE_STEP;
|
||||
cmd.step_delta = -1;
|
||||
break;
|
||||
case 'D':
|
||||
cmd.id = CMD_BROWSE_STEP;
|
||||
cmd.step_delta = 1;
|
||||
break;
|
||||
case 'L':
|
||||
cmd.id = CMD_ADJUST_STEP_TIME;
|
||||
cmd.time_delta_s = -5;
|
||||
break;
|
||||
case 'R':
|
||||
cmd.id = CMD_ADJUST_STEP_TIME;
|
||||
cmd.time_delta_s = 5;
|
||||
break;
|
||||
case 'E':
|
||||
if (st == ST_STEP_SELECT) {
|
||||
cmd.id = CMD_ARM_STEP;
|
||||
cmd.step_index = app_machine_step_index();
|
||||
} else if (st == ST_ARMED) {
|
||||
cmd.id = CMD_START_STEP;
|
||||
} else if (st == ST_STOPPED) {
|
||||
cmd.id = CMD_RESUME;
|
||||
} else if (st == ST_COMPLETE) {
|
||||
cmd.id = CMD_ARM_STEP;
|
||||
cmd.step_index = app_machine_step_index();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'X':
|
||||
if (st == ST_ARMED) {
|
||||
cmd.id = CMD_CANCEL_ARMED;
|
||||
} else if (st == ST_RUNNING) {
|
||||
/* Dual-path stop: EN-off in this task, then CMD_STOP. */
|
||||
hal_motor_request_stop();
|
||||
cmd.id = CMD_STOP;
|
||||
} else if (st == ST_COMPLETE) {
|
||||
cmd.id = CMD_STOP;
|
||||
} else if (st == ST_STOPPED) {
|
||||
cmd.id = CMD_RETURN_TO_STEP_SELECT;
|
||||
} else {
|
||||
return; /* ignore IDLE */
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
xQueueSend(s_cmdq, &cmd, 0);
|
||||
}
|
||||
|
||||
static void input_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
esp_err_t werr = esp_task_wdt_add(NULL);
|
||||
if (werr != ESP_OK) {
|
||||
ESP_LOGW(TAG, "input wdt add failed: %s", esp_err_to_name(werr));
|
||||
}
|
||||
const TickType_t period = pdMS_TO_TICKS(40); /* 25 Hz */
|
||||
for (;;) {
|
||||
ui_raw_key_t raw;
|
||||
while (hal_input_pop_key(&raw)) {
|
||||
map_and_post_key(raw.key);
|
||||
}
|
||||
ui_raw_touch_t touch;
|
||||
while (hal_input_pop_touch(&touch)) {
|
||||
char key;
|
||||
if (app_ui_touch_to_key(touch.x, touch.y, &key)) {
|
||||
map_and_post_key(key);
|
||||
}
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
vTaskDelay(period);
|
||||
}
|
||||
}
|
||||
|
||||
static void machine_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
esp_err_t werr = esp_task_wdt_add(NULL);
|
||||
if (werr != ESP_OK) {
|
||||
ESP_LOGW(TAG, "machine wdt add failed: %s", esp_err_to_name(werr));
|
||||
}
|
||||
const TickType_t period = pdMS_TO_TICKS(20); /* 50 Hz */
|
||||
for (;;) {
|
||||
ui_cmd_t cmd;
|
||||
while (xQueueReceive(s_cmdq, &cmd, 0) == pdTRUE) {
|
||||
app_machine_handle_cmd(&cmd);
|
||||
}
|
||||
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000ULL);
|
||||
app_machine_tick(now_ms);
|
||||
esp_task_wdt_reset();
|
||||
vTaskDelay(period);
|
||||
}
|
||||
}
|
||||
|
||||
static void ui_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
esp_err_t werr = esp_task_wdt_add(NULL);
|
||||
if (werr != ESP_OK) {
|
||||
ESP_LOGW(TAG, "ui wdt add failed: %s", esp_err_to_name(werr));
|
||||
}
|
||||
|
||||
hal_display_clear();
|
||||
hal_display_text(0, 0, "AUTOFILM");
|
||||
hal_display_text(0, 1, AUTOFILM_GIT_DESC);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
|
||||
app_ui_init();
|
||||
app_ui_draw();
|
||||
|
||||
const TickType_t period = pdMS_TO_TICKS(25);
|
||||
for (;;) {
|
||||
ui_evt_t ev;
|
||||
while (app_machine_last_event(&ev) == 1) {
|
||||
app_ui_on_event(&ev);
|
||||
}
|
||||
/* Running remaining is read from the machine on each draw (>=4 Hz). */
|
||||
if (app_ui_is_dirty() || app_machine_state() == ST_RUNNING) {
|
||||
app_ui_draw();
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
vTaskDelay(period);
|
||||
}
|
||||
}
|
||||
|
||||
static void temp_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
esp_err_t werr = esp_task_wdt_add(NULL);
|
||||
if (werr != ESP_OK) {
|
||||
ESP_LOGW(TAG, "temp wdt add failed: %s", esp_err_to_name(werr));
|
||||
}
|
||||
for (;;) {
|
||||
hal_temp_tick();
|
||||
float c = 0.0f;
|
||||
bool ok = (hal_temp_read_c(&c) == ESP_OK);
|
||||
app_machine_on_temp(ok ? c : 0.0f, ok);
|
||||
esp_task_wdt_reset();
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "board=%s", board_name());
|
||||
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) {
|
||||
@@ -22,7 +205,52 @@ void app_main(void)
|
||||
gpio_set_level(en_pin, board_motor_en_disable_level());
|
||||
}
|
||||
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
app_process_init();
|
||||
hal_motor_init();
|
||||
hal_temp_init();
|
||||
hal_audio_init();
|
||||
app_machine_init();
|
||||
|
||||
#if defined(AUTOFILM_BOARD_WROOM) || defined(AUTOFILM_BOARD_S3)
|
||||
hal_display_init();
|
||||
hal_input_init();
|
||||
app_ui_init();
|
||||
|
||||
s_cmdq = xQueueCreate(UI_CMD_QUEUE_LEN, sizeof(ui_cmd_t));
|
||||
|
||||
#if CONFIG_ESP_TASK_WDT_EN
|
||||
esp_task_wdt_config_t wdt_cfg = {
|
||||
.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000,
|
||||
.idle_core_mask =
|
||||
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
|
||||
(1u << 0)
|
||||
#else
|
||||
0
|
||||
#endif
|
||||
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
|
||||
| (1u << 1)
|
||||
#endif
|
||||
,
|
||||
#if CONFIG_ESP_TASK_WDT_PANIC
|
||||
.trigger_panic = true,
|
||||
#else
|
||||
.trigger_panic = false,
|
||||
#endif
|
||||
};
|
||||
esp_err_t wdt_err = esp_task_wdt_init(&wdt_cfg);
|
||||
if (wdt_err == ESP_ERR_INVALID_STATE) {
|
||||
wdt_err = esp_task_wdt_reconfigure(&wdt_cfg); /* already auto-initialised */
|
||||
}
|
||||
if (wdt_err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "task wdt setup: %s", esp_err_to_name(wdt_err));
|
||||
}
|
||||
#endif
|
||||
|
||||
xTaskCreate(temp_task, "temp", 4096, NULL, 2, NULL);
|
||||
xTaskCreate(input_task, "input", 3072, NULL, 8, NULL);
|
||||
xTaskCreate(machine_task, "machine", 4096, NULL, 6, NULL);
|
||||
xTaskCreate(ui_task, "ui", 4096, NULL, 4, NULL);
|
||||
#endif
|
||||
|
||||
/* app_main returns; scheduler keeps product tasks. No legacy startingMenu. */
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ add_executable(test_machine
|
||||
stubs/hal_motor.c
|
||||
stubs/hal_temp.c
|
||||
stubs/hal_audio.c
|
||||
freertos/queue.c
|
||||
)
|
||||
|
||||
target_include_directories(test_machine PRIVATE ${HOST_INCLUDES})
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef long BaseType_t;
|
||||
typedef unsigned long UBaseType_t;
|
||||
typedef uint32_t TickType_t;
|
||||
|
||||
#define pdFALSE ((BaseType_t)0)
|
||||
#define pdTRUE ((BaseType_t)1)
|
||||
#define pdPASS pdTRUE
|
||||
#define pdFAIL pdFALSE
|
||||
|
||||
#define pdMS_TO_TICKS(xTimeInMs) ((TickType_t)(xTimeInMs))
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct QueueDefinition {
|
||||
UBaseType_t len;
|
||||
UBaseType_t item_size;
|
||||
UBaseType_t head;
|
||||
UBaseType_t tail;
|
||||
UBaseType_t count;
|
||||
uint8_t *storage;
|
||||
};
|
||||
|
||||
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize)
|
||||
{
|
||||
struct QueueDefinition *q = calloc(1, sizeof(*q));
|
||||
if (q == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
q->storage = malloc((size_t)uxQueueLength * uxItemSize);
|
||||
if (q->storage == NULL) {
|
||||
free(q);
|
||||
return NULL;
|
||||
}
|
||||
q->len = uxQueueLength;
|
||||
q->item_size = uxItemSize;
|
||||
return q;
|
||||
}
|
||||
|
||||
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait)
|
||||
{
|
||||
(void)xTicksToWait;
|
||||
if (xQueue == NULL || pvItemToQueue == NULL) {
|
||||
return pdFALSE;
|
||||
}
|
||||
if (xQueue->count == xQueue->len) {
|
||||
return pdFALSE;
|
||||
}
|
||||
memcpy(xQueue->storage + (size_t)xQueue->tail * xQueue->item_size,
|
||||
pvItemToQueue, xQueue->item_size);
|
||||
xQueue->tail = (xQueue->tail + 1u) % xQueue->len;
|
||||
xQueue->count++;
|
||||
return pdTRUE;
|
||||
}
|
||||
|
||||
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)
|
||||
{
|
||||
(void)xTicksToWait;
|
||||
if (xQueue == NULL || pvBuffer == NULL) {
|
||||
return pdFALSE;
|
||||
}
|
||||
if (xQueue->count == 0u) {
|
||||
return pdFALSE;
|
||||
}
|
||||
memcpy(pvBuffer, xQueue->storage + (size_t)xQueue->head * xQueue->item_size,
|
||||
xQueue->item_size);
|
||||
xQueue->head = (xQueue->head + 1u) % xQueue->len;
|
||||
xQueue->count--;
|
||||
return pdTRUE;
|
||||
}
|
||||
|
||||
BaseType_t xQueueReset(QueueHandle_t xQueue)
|
||||
{
|
||||
if (xQueue == NULL) {
|
||||
return pdFALSE;
|
||||
}
|
||||
xQueue->head = 0;
|
||||
xQueue->tail = 0;
|
||||
xQueue->count = 0;
|
||||
return pdTRUE;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
struct QueueDefinition;
|
||||
typedef struct QueueDefinition *QueueHandle_t;
|
||||
|
||||
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
|
||||
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
|
||||
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
|
||||
BaseType_t xQueueReset(QueueHandle_t xQueue);
|
||||
@@ -10,6 +10,10 @@ void hal_temp_init(void)
|
||||
stub_temp_c = 20.0f;
|
||||
}
|
||||
|
||||
void hal_temp_tick(void)
|
||||
{
|
||||
}
|
||||
|
||||
esp_err_t hal_temp_read_c(float *out)
|
||||
{
|
||||
if (stub_temp_fail) {
|
||||
|
||||
+17
-17
@@ -145,12 +145,10 @@ static void arm_start_complete_custom_10s(void)
|
||||
app_machine_tick(9999);
|
||||
expect_int((int)app_machine_state(), ST_RUNNING, "custom tick9999");
|
||||
app_machine_tick(10000);
|
||||
expect_int((int)app_machine_state(), ST_COMPLETE, "custom complete");
|
||||
expect_int((int)app_machine_state(), ST_ARMED, "custom auto-armed next");
|
||||
expect_int((int)app_machine_step_index(), 1, "custom auto-advance step");
|
||||
expect_int(stub_motor_enabled ? 1 : 0, 0, "custom motor off complete");
|
||||
expect_int(stub_alarm_count >= 1 ? 1 : 0, 1, "custom alarm");
|
||||
/* default auto_advance false */
|
||||
app_machine_tick(10001);
|
||||
expect_int((int)app_machine_state(), ST_COMPLETE, "auto_advance stays complete");
|
||||
}
|
||||
|
||||
static void stop_disables_motor_and_resume(void)
|
||||
@@ -182,7 +180,7 @@ static void stop_disables_motor_and_resume(void)
|
||||
expect_int(stub_beep_count >= beeps + 1 ? 1 : 0, 1, "resume beep");
|
||||
app_machine_tick(1000);
|
||||
app_machine_tick(1000 + rem);
|
||||
expect_int((int)app_machine_state(), ST_COMPLETE, "resume then complete");
|
||||
expect_int((int)app_machine_state(), ST_ARMED, "resume then auto-armed");
|
||||
}
|
||||
|
||||
static void return_from_stopped(void)
|
||||
@@ -208,23 +206,23 @@ static void return_from_stopped(void)
|
||||
expect_int((int)app_process_get(PROC_CUSTOM)->steps[0].time_s, 10, "const still 10");
|
||||
}
|
||||
|
||||
static void stop_during_complete_cancels_alarm(void)
|
||||
static void auto_advance_last_step_goes_idle(void)
|
||||
{
|
||||
app_machine_init();
|
||||
ui_cmd_t c = cmd_select(PROC_CUSTOM);
|
||||
app_machine_handle_cmd(&c);
|
||||
ui_cmd_t arm = cmd_arm(0);
|
||||
ui_cmd_t arm = cmd_arm(3);
|
||||
app_machine_handle_cmd(&arm);
|
||||
ui_cmd_t start = cmd_start(0);
|
||||
ui_cmd_t start = cmd_start(3);
|
||||
app_machine_handle_cmd(&start);
|
||||
app_machine_tick(0);
|
||||
app_machine_tick(10000);
|
||||
expect_int((int)app_machine_state(), ST_COMPLETE, "complete before stop");
|
||||
int cancels = stub_alarm_cancel_count;
|
||||
ui_cmd_t stop = cmd_id(CMD_STOP);
|
||||
app_machine_handle_cmd(&stop);
|
||||
expect_int((int)app_machine_state(), ST_STEP_SELECT, "stop from complete");
|
||||
expect_int(stub_alarm_cancel_count >= cancels + 1 ? 1 : 0, 1, "alarm cancel");
|
||||
expect_int((int)app_machine_state(), ST_IDLE, "last step goes idle");
|
||||
ui_evt_id_t ids[8];
|
||||
int n = pop_ids(ids, 8);
|
||||
if (!has_id(ids, n, EVT_PROCESS_IDLE)) {
|
||||
fail("last step idle event");
|
||||
}
|
||||
}
|
||||
|
||||
static void ecn2_remjet_zero_time(void)
|
||||
@@ -246,7 +244,8 @@ static void ecn2_remjet_zero_time(void)
|
||||
expect_int(stub_motor_enabled ? 1 : 0, 0, "remjet no enable");
|
||||
expect_int(stub_agitate_start_count, starts, "remjet no agitate");
|
||||
app_machine_tick(0);
|
||||
expect_int((int)app_machine_state(), ST_COMPLETE, "remjet complete next tick");
|
||||
expect_int((int)app_machine_state(), ST_ARMED, "remjet auto-armed next");
|
||||
expect_int((int)app_machine_step_index(), 2, "remjet auto-advance step");
|
||||
}
|
||||
|
||||
static void stop_ignored_meaningless_in_idle(void)
|
||||
@@ -275,7 +274,8 @@ static void c41_clock(void)
|
||||
app_machine_tick(179999);
|
||||
expect_int((int)app_machine_state(), ST_RUNNING, "c41 tick179999");
|
||||
app_machine_tick(180000);
|
||||
expect_int((int)app_machine_state(), ST_COMPLETE, "c41 tick180000");
|
||||
expect_int((int)app_machine_state(), ST_ARMED, "c41 auto-armed next");
|
||||
expect_int((int)app_machine_step_index(), 1, "c41 auto-advance step");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
@@ -285,7 +285,7 @@ int main(void)
|
||||
arm_start_complete_custom_10s();
|
||||
stop_disables_motor_and_resume();
|
||||
return_from_stopped();
|
||||
stop_during_complete_cancels_alarm();
|
||||
auto_advance_last_step_goes_idle();
|
||||
ecn2_remjet_zero_time();
|
||||
stop_ignored_meaningless_in_idle();
|
||||
c41_clock();
|
||||
|
||||
Reference in New Issue
Block a user