上传数据
This commit is contained in:
parent
042af6036e
commit
003d96fdca
@ -2,5 +2,8 @@
|
||||
"wifi": {
|
||||
"ssid": "Ti",
|
||||
"password": "94544549"
|
||||
},
|
||||
"backend": {
|
||||
"url": "http://192.168.5.18:3000/api/data"
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,9 @@ void connectWiFi();
|
||||
// 初始化系统
|
||||
void initSystem(Adafruit_ST7735& tft);
|
||||
|
||||
// 获取后端接口 URL(来自 settings.json)
|
||||
const String& getBackendUrl();
|
||||
|
||||
} // namespace init
|
||||
} // namespace esp32
|
||||
|
||||
|
||||
@ -66,7 +66,8 @@ void readAll(int16_t* data8) {
|
||||
}
|
||||
|
||||
// 读取8个16位(RD/SC 由 SPI SCLK 提供)
|
||||
SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
|
||||
// 提升串行读取速率以缩短读出时间(与 TFT 共用 SPI,使用事务隔离)
|
||||
SPI.beginTransaction(SPISettings(16000000, MSBFIRST, SPI_MODE0));
|
||||
digitalWrite(PIN_AD_CS, LOW);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
uint16_t raw = SPI.transfer16(0x0000);
|
||||
|
||||
13
src/init.cpp
13
src/init.cpp
@ -10,6 +10,7 @@ namespace init {
|
||||
// WiFi 配置变量
|
||||
static String wifiSSID = "";
|
||||
static String wifiPassword = "";
|
||||
static String backendUrl = "";
|
||||
|
||||
// TFT 显示器引用 (在 initSystem 中设置)
|
||||
static Adafruit_ST7735* tftPtr = nullptr;
|
||||
@ -53,6 +54,15 @@ bool loadSettings() {
|
||||
wifiSSID = doc["wifi"]["ssid"].as<String>();
|
||||
wifiPassword = doc["wifi"]["password"].as<String>();
|
||||
|
||||
// 读取后端接口(可选)
|
||||
if (doc.containsKey("backend")) {
|
||||
backendUrl = doc["backend"]["url"].as<String>();
|
||||
}
|
||||
if (backendUrl.isEmpty()) {
|
||||
// 回退:保持为空或设为默认
|
||||
backendUrl = "http://nf.xn--876a.net/api/data";
|
||||
}
|
||||
|
||||
tftLog("Config loaded:", GREEN);
|
||||
tftPtr->print(" SSID: ");
|
||||
tftLog(wifiSSID.c_str(), WHITE);
|
||||
@ -103,5 +113,8 @@ void initSystem(Adafruit_ST7735& tft) {
|
||||
}
|
||||
}
|
||||
|
||||
// 提供后端 URL 给外部使用
|
||||
const String& getBackendUrl() { return backendUrl; }
|
||||
|
||||
} // namespace init
|
||||
} // namespace esp32
|
||||
|
||||
455
src/main.cpp
455
src/main.cpp
@ -1,11 +1,18 @@
|
||||
#include <Adafruit_GFX.h>
|
||||
#include <Adafruit_ST7735.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <SPI.h>
|
||||
#include <U8g2_for_Adafruit_GFX.h>
|
||||
#include <WiFi.h>
|
||||
#include <math.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/task.h>
|
||||
#include "ad7606.h"
|
||||
#include "init.h"
|
||||
#include "ui.h"
|
||||
#include "utils.h"
|
||||
#include "ad7606.h"
|
||||
|
||||
U8G2_FOR_ADAFRUIT_GFX u8g2; // 给 Adafruit_GFX 套一层 U8g2 的“文字引擎”
|
||||
using namespace esp32;
|
||||
@ -22,7 +29,7 @@ Adafruit_ST7735 tft(TFT_CS, TFT_DC, TFT_RST);
|
||||
using namespace esp32::utils;
|
||||
using namespace esp32::ui;
|
||||
|
||||
const int BTN_LEFT = 12; // 左按钮
|
||||
const int BTN_LEFT = 9; // 左按钮
|
||||
const int BTN_RIGHT = 11; // 右按钮
|
||||
const int BTN_UP = 10; // 上按钮
|
||||
|
||||
@ -31,14 +38,37 @@ const int BTN_UP = 10; // 上按钮
|
||||
// 应用状态
|
||||
struct AppState {
|
||||
// 环形缓冲:宽度=屏幕宽;时间窗口=3秒
|
||||
static const int GraphW = 128; // 与屏幕宽度一致
|
||||
static const int GraphW = 128; // 与屏幕宽度一致
|
||||
int16_t samples[GraphW];
|
||||
int writeIdx = 0; // 下一个写入位置
|
||||
int sampleCount = 0; // 已写入样本(<=GraphW)
|
||||
int16_t lastRaw = 0; // 最新原始码
|
||||
uint32_t lastSampleMs = 0; // 上一次采样时间
|
||||
static const uint32_t WindowMs = 3000; // 3 秒窗口
|
||||
static const uint32_t SampleIntervalMs = WindowMs / GraphW; // 约 23ms
|
||||
int writeIdx = 0; // 下一个写入位置
|
||||
int sampleCount = 0; // 已写入样本(<=GraphW)
|
||||
int16_t lastRaw = 0; // 最新原始码
|
||||
uint32_t lastSampleMs = 0; // 上一次采样时间
|
||||
static const uint32_t WindowMs = 3000; // 3 秒窗口
|
||||
static const uint32_t SampleIntervalMs = WindowMs / GraphW; // 约 23ms
|
||||
|
||||
// 标定/拟合:mN = a*code + b(至少2点生效)
|
||||
float target_mN[4]; // 预设档位:0, 3.6, 7.2, 10.8
|
||||
float capturedCode[4]; // 各档记录到的原始码
|
||||
bool captured[4]; // 是否已记录
|
||||
int presetIdx = 0; // 当前要记录的档位索引
|
||||
int capturedCount = 0; // 已记录的档位数
|
||||
bool fitReady = false; // 已具备>=2点
|
||||
float fitA = 0.0f; // mN = a*code + b
|
||||
float fitB = 0.0f;
|
||||
|
||||
// 录制
|
||||
static const int RecCap = 16384;
|
||||
int16_t recBuf[RecCap];
|
||||
int recLen = 0;
|
||||
bool recActive = false;
|
||||
uint32_t recStartMs = 0;
|
||||
uint32_t recEndMs = 0;
|
||||
|
||||
// 上传状态提示
|
||||
char statusMsg[64];
|
||||
uint32_t statusUntilMs = 0;
|
||||
uint16_t statusColor = WHITE;
|
||||
};
|
||||
|
||||
static UI appUI;
|
||||
@ -55,23 +85,129 @@ static const int ICON_W = 48; // 图标宽度
|
||||
static const int ICON_H = 48; // 图标高度
|
||||
static const int SPACING = 72; // 图标中心间距(略大于宽度,保证边缘留白)
|
||||
static const int CENTER_X = (SCREEN_W - ICON_W) / 2; // 中心图标左上角 X
|
||||
static const int BASE_Y = 40; // 图标顶端 Y(上方留空间给标题)
|
||||
static const int BASE_Y = 40; // 图标顶端 Y(上方留空间给标题)
|
||||
static const int LABEL_MARGIN = 12; // 图标与文字竖向间距
|
||||
|
||||
static const int CHAR_W = 14;
|
||||
static const int CHAR_H = 14;
|
||||
|
||||
// 采样队列:将高速采样与 UI 渲染解耦
|
||||
static QueueHandle_t g_sampleQ = nullptr; // 队列元素:int16_t(CH0 原始码)
|
||||
|
||||
// 采样任务:固定频率触发 AD7606 读取,推入队列
|
||||
static void samplerTask(void* param) {
|
||||
// 目标采样率(可按需调高/调低)。示例:1000Hz
|
||||
const TickType_t period = pdMS_TO_TICKS(1);
|
||||
TickType_t last = xTaskGetTickCount();
|
||||
for (;;) {
|
||||
int16_t s = ad7606::readCH0();
|
||||
if (g_sampleQ) {
|
||||
// 队列满则丢弃最旧样本:先试一次无阻塞发送,失败则腾挪1个旧样本
|
||||
if (xQueueSendToBack(g_sampleQ, &s, 0) != pdTRUE) {
|
||||
int16_t dummy;
|
||||
xQueueReceive(g_sampleQ, &dummy, 0);
|
||||
xQueueSendToBack(g_sampleQ, &s, 0);
|
||||
}
|
||||
}
|
||||
vTaskDelayUntil(&last, period);
|
||||
}
|
||||
}
|
||||
|
||||
// 工具:推入一条新样本(原码)
|
||||
static inline void pushSample(AppState& s, int16_t code, uint32_t nowMs) {
|
||||
// 控制写入速率,保持一屏=3秒
|
||||
if (nowMs - s.lastSampleMs < AppState::SampleIntervalMs) return;
|
||||
if (nowMs - s.lastSampleMs < AppState::SampleIntervalMs)
|
||||
return;
|
||||
s.lastSampleMs = nowMs;
|
||||
s.samples[s.writeIdx] = code;
|
||||
s.writeIdx = (s.writeIdx + 1) % AppState::GraphW;
|
||||
if (s.sampleCount < AppState::GraphW) s.sampleCount++;
|
||||
if (s.sampleCount < AppState::GraphW)
|
||||
s.sampleCount++;
|
||||
s.lastRaw = code;
|
||||
}
|
||||
|
||||
// 发送录制数据到服务器(JSON)
|
||||
static void postRecording(AppState& s) {
|
||||
if (s.recLen <= 0) {
|
||||
s.statusColor = YELLOW;
|
||||
snprintf(s.statusMsg, sizeof(s.statusMsg), "无数据可上传");
|
||||
s.statusUntilMs = millis() + 2000;
|
||||
return;
|
||||
}
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
s.statusColor = RED;
|
||||
snprintf(s.statusMsg, sizeof(s.statusMsg), "未连接WiFi");
|
||||
s.statusUntilMs = millis() + 2000;
|
||||
return;
|
||||
}
|
||||
|
||||
String payload;
|
||||
// 估算预留空间:每个数最多6字符+逗号
|
||||
payload.reserve(32 + s.recLen * 7 + 48);
|
||||
payload += "{";
|
||||
payload += "\"code\":[";
|
||||
for (int i = 0; i < s.recLen; ++i) {
|
||||
payload += String((int)s.recBuf[i]);
|
||||
if (i + 1 < s.recLen)
|
||||
payload += ",";
|
||||
}
|
||||
payload += "]";
|
||||
// 附带录制起止时间(毫秒)
|
||||
payload += ",\"startTime\":";
|
||||
payload += String(s.recStartMs);
|
||||
payload += ",\"endTime\":";
|
||||
payload += String(s.recEndMs);
|
||||
if (s.fitReady) {
|
||||
payload += ",\"fit\":{\"a\":";
|
||||
payload += String(s.fitA, 6);
|
||||
payload += ",\"b\":";
|
||||
payload += String(s.fitB, 6);
|
||||
payload += "}";
|
||||
}
|
||||
payload += "}";
|
||||
printf("上传数据:%s\n", payload.c_str());
|
||||
HTTPClient http;
|
||||
http.begin(esp32::init::getBackendUrl());
|
||||
http.addHeader("Content-Type", "application/json");
|
||||
http.setTimeout(10000);
|
||||
int code = http.POST(payload);
|
||||
String resp = http.getString();
|
||||
http.end();
|
||||
|
||||
if (code > 0) {
|
||||
// 尝试解析 { success: true, id, timestamp }
|
||||
JsonDocument doc;
|
||||
DeserializationError err = deserializeJson(doc, resp);
|
||||
bool ok = false;
|
||||
String idStr = "";
|
||||
String tsStr = "";
|
||||
if (!err) {
|
||||
ok = doc["success"].as<bool>();
|
||||
idStr = doc["id"].as<String>();
|
||||
tsStr = doc["timestamp"].as<String>();
|
||||
} else {
|
||||
ok = resp.indexOf("\"success\":true") >= 0; // 兜底
|
||||
}
|
||||
if (ok) {
|
||||
s.statusColor = GREEN;
|
||||
if (idStr.length())
|
||||
snprintf(s.statusMsg, sizeof(s.statusMsg), "上传成功\nID:%s",
|
||||
idStr.c_str());
|
||||
else
|
||||
snprintf(s.statusMsg, sizeof(s.statusMsg), "上传成功");
|
||||
s.statusUntilMs = millis() + 3000;
|
||||
} else {
|
||||
s.statusColor = RED;
|
||||
snprintf(s.statusMsg, sizeof(s.statusMsg), "上传失败(%d)", code);
|
||||
s.statusUntilMs = millis() + 3000;
|
||||
}
|
||||
} else {
|
||||
s.statusColor = RED;
|
||||
snprintf(s.statusMsg, sizeof(s.statusMsg), "HTTP错误(%d)", code);
|
||||
s.statusUntilMs = millis() + 3000;
|
||||
}
|
||||
}
|
||||
|
||||
// 根据状态输出显示列表
|
||||
static void buildScene(void* ctx, DisplayList& out) {
|
||||
auto* st = static_cast<AppState*>(ctx);
|
||||
@ -79,44 +215,176 @@ static void buildScene(void* ctx, DisplayList& out) {
|
||||
// 背景整屏清除
|
||||
out.addFillRect(1, 0, 0, SCREEN_W, SCREEN_H, BLACK);
|
||||
|
||||
// 显示当前原始码(居中顶部,去掉电压)
|
||||
char line[48];
|
||||
snprintf(line, sizeof(line), "CH0: %d", st->lastRaw);
|
||||
// 第一行:显示原始码 并在拟合可用时附加 mN
|
||||
char line[64];
|
||||
if (st->fitReady) {
|
||||
float currM = st->fitA * (float)st->lastRaw + st->fitB;
|
||||
snprintf(line, sizeof(line), "%d it %.3f mN", st->lastRaw, currM);
|
||||
} else {
|
||||
snprintf(line, sizeof(line), "%d it", st->lastRaw);
|
||||
}
|
||||
out.addText(2, SCREEN_W / 2, 10, WHITE, line, true);
|
||||
// 右上角录制提示:红色圆点(空心两层加粗)+ 录制点数
|
||||
if (st->recActive) {
|
||||
int cx = SCREEN_W - 30;
|
||||
int cy = SCREEN_H - 6;
|
||||
int r = 4;
|
||||
out.addCircle(5, cx, cy, r, RED);
|
||||
out.addCircle(6, cx, cy, r - 1, RED);
|
||||
char recCnt[16];
|
||||
snprintf(recCnt, sizeof(recCnt), "%d", st->recLen);
|
||||
uint16_t cntColor = esp32::utils::rgbTo565(0xFFCCCC);
|
||||
out.addText(7, cx + 8, cy - 4, cntColor, recCnt, false);
|
||||
}
|
||||
|
||||
// 第二行:格式如 "[0*] 3.6 7.2 10.8",[]表示当前选择,*表示该档已记录
|
||||
char info[64];
|
||||
info[0] = '\0';
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
char valStr[12];
|
||||
float tgt = st->target_mN[i];
|
||||
if (fabsf(tgt) < 0.05f) {
|
||||
snprintf(valStr, sizeof(valStr), "0");
|
||||
} else {
|
||||
snprintf(valStr, sizeof(valStr), "%.1f", tgt);
|
||||
}
|
||||
char seg[16];
|
||||
if (i == st->presetIdx) {
|
||||
if (st->captured[i])
|
||||
snprintf(seg, sizeof(seg), "[%s*]", valStr);
|
||||
else
|
||||
snprintf(seg, sizeof(seg), "[%s]", valStr);
|
||||
} else {
|
||||
if (st->captured[i])
|
||||
snprintf(seg, sizeof(seg), "%s*", valStr);
|
||||
else
|
||||
snprintf(seg, sizeof(seg), "%s", valStr);
|
||||
}
|
||||
size_t cur = strlen(info);
|
||||
if (cur + strlen(seg) + 2 < sizeof(info)) {
|
||||
if (cur > 0)
|
||||
strcat(info, " ");
|
||||
strcat(info, seg);
|
||||
}
|
||||
}
|
||||
uint16_t infoColor = esp32::utils::rgbTo565(0xFFD060);
|
||||
out.addText(3, SCREEN_W / 2, 22, infoColor, info, true);
|
||||
|
||||
// 图形区域:最大化剩余高度,无边框
|
||||
const int topY = 24; // 标题高度
|
||||
const int graphH = SCREEN_H - topY; // 剩余高度
|
||||
const int gw = AppState::GraphW; // 128
|
||||
const int topY = 40; // 标题高度(两行信息)
|
||||
const int graphH = SCREEN_H - topY - 4; // 剩余高度
|
||||
const int gw = AppState::GraphW; // 128
|
||||
const int gx = 0;
|
||||
|
||||
if (st->sampleCount == 0) return; // 尚无数据
|
||||
if (st->sampleCount == 0)
|
||||
return; // 尚无数据
|
||||
|
||||
// 动态范围(min-200, max+200)
|
||||
int16_t minV = st->samples[0];
|
||||
int16_t maxV = st->samples[0];
|
||||
for (int i = 1; i < st->sampleCount; ++i) {
|
||||
int16_t v = st->samples[i];
|
||||
if (v < minV) minV = v;
|
||||
if (v > maxV) maxV = v;
|
||||
// 动态范围(原始码模式:min-200/max+200;拟合后:mN 加适度留白)
|
||||
bool useFit = st->fitReady;
|
||||
float fMin, fMax, fSpan, fPad;
|
||||
if (!useFit) {
|
||||
int16_t minV = st->samples[0];
|
||||
int16_t maxV = st->samples[0];
|
||||
for (int i = 1; i < st->sampleCount; ++i) {
|
||||
int16_t v = st->samples[i];
|
||||
if (v < minV)
|
||||
minV = v;
|
||||
if (v > maxV)
|
||||
maxV = v;
|
||||
}
|
||||
int32_t rangeMin = (int32_t)minV - 200;
|
||||
int32_t rangeMax = (int32_t)maxV + 200;
|
||||
if (rangeMin < -32768)
|
||||
rangeMin = -32768;
|
||||
if (rangeMax > 32767)
|
||||
rangeMax = 32767;
|
||||
fMin = (float)rangeMin;
|
||||
fMax = (float)rangeMax;
|
||||
fSpan = fMax - fMin;
|
||||
if (fSpan <= 0.0f)
|
||||
fSpan = 1.0f;
|
||||
} else {
|
||||
// 将样本转换为 mN 后求范围
|
||||
float minM = st->fitA * (float)st->samples[0] + st->fitB;
|
||||
float maxM = minM;
|
||||
for (int i = 1; i < st->sampleCount; ++i) {
|
||||
float m = st->fitA * (float)st->samples[i] + st->fitB;
|
||||
if (m < minM)
|
||||
minM = m;
|
||||
if (m > maxM)
|
||||
maxM = m;
|
||||
}
|
||||
// mN 下给一个小的固定留白(0.2mN)
|
||||
fMin = minM - 0.2f;
|
||||
fMax = maxM + 0.2f;
|
||||
if (fMin > fMax) {
|
||||
float t = fMin;
|
||||
fMin = fMax;
|
||||
fMax = t;
|
||||
}
|
||||
fSpan = fMax - fMin;
|
||||
if (fSpan <= 1e-6f)
|
||||
fSpan = 1.0f; // 避免除0
|
||||
}
|
||||
int32_t rangeMin = (int32_t)minV - 200;
|
||||
int32_t rangeMax = (int32_t)maxV + 200;
|
||||
if (rangeMin < -32768) rangeMin = -32768;
|
||||
if (rangeMax > 32767) rangeMax = 32767;
|
||||
int32_t span = rangeMax - rangeMin;
|
||||
if (span <= 0) span = 1;
|
||||
|
||||
auto mapY = [&](int16_t code) -> int {
|
||||
float rel = (float)((int32_t)code - rangeMin) / (float)span; // 0..1
|
||||
if (rel < 0.0f) rel = 0.0f; if (rel > 1.0f) rel = 1.0f;
|
||||
float y = (1.0f - rel) * (graphH - 1); // 倒置
|
||||
float val = useFit ? (st->fitA * (float)code + st->fitB) : (float)code;
|
||||
float rel = (val - fMin) / fSpan; // 0..1
|
||||
if (rel < 0.0f)
|
||||
rel = 0.0f;
|
||||
if (rel > 1.0f)
|
||||
rel = 1.0f;
|
||||
float y = (1.0f - rel) * (graphH - 1); // 倒置
|
||||
int iy = topY + (int)(y + 0.5f);
|
||||
if (iy < topY) iy = topY;
|
||||
if (iy > topY + graphH - 1) iy = topY + graphH - 1;
|
||||
if (iy < topY)
|
||||
iy = topY;
|
||||
if (iy > topY + graphH - 1)
|
||||
iy = topY + graphH - 1;
|
||||
return iy;
|
||||
};
|
||||
|
||||
// 绘制 Y 轴刻度与网格
|
||||
uint16_t gridColor = esp32::utils::rgbTo565(0x303030);
|
||||
uint16_t tickColor = esp32::utils::rgbTo565(0x606060);
|
||||
const int ticks = 4; // 分成4段,5条刻度线
|
||||
for (int i = 0; i <= ticks; ++i) {
|
||||
float val = fMin + (fSpan * i) / ticks;
|
||||
// 为了绘制刻度线,构造一个虚拟 code 值逆映射并用 mapY 仅为获取 y 像素
|
||||
int yPix;
|
||||
if (useFit) {
|
||||
// 近似:在当前 y 下对应的 val 直接用反推像素到值,再直接求 y 像素
|
||||
float rel = (val - fMin) / fSpan;
|
||||
if (rel < 0)
|
||||
rel = 0;
|
||||
if (rel > 1)
|
||||
rel = 1;
|
||||
float y = (1.0f - rel) * (graphH - 1);
|
||||
yPix = topY + (int)(y + 0.5f);
|
||||
} else {
|
||||
// 同上,直接由范围转 y 像素
|
||||
float rel = (val - fMin) / fSpan;
|
||||
if (rel < 0)
|
||||
rel = 0;
|
||||
if (rel > 1)
|
||||
rel = 1;
|
||||
float y = (1.0f - rel) * (graphH - 1);
|
||||
yPix = topY + (int)(y + 0.5f);
|
||||
}
|
||||
// 网格横线
|
||||
out.addLine(20 + i, 0, yPix, SCREEN_W - 1, yPix, gridColor);
|
||||
// 左侧短刻度
|
||||
out.addLine(40 + i, 0, yPix, 3, yPix, tickColor);
|
||||
|
||||
// 标签
|
||||
char lab[20];
|
||||
if (useFit) {
|
||||
snprintf(lab, sizeof(lab), "%.1f", val);
|
||||
} else {
|
||||
snprintf(lab, sizeof(lab), "%d", (int)val);
|
||||
}
|
||||
out.addText(60 + i, 6, yPix - 6, tickColor, lab, false);
|
||||
}
|
||||
|
||||
int prevY = mapY(st->samples[(st->writeIdx) % gw]);
|
||||
for (int x = 1; x < gw; ++x) {
|
||||
int idx = (st->writeIdx + x) % gw;
|
||||
@ -124,6 +392,13 @@ static void buildScene(void* ctx, DisplayList& out) {
|
||||
out.addLine(100 + x, gx + x - 1, prevY, gx + x, y, GREEN);
|
||||
prevY = y;
|
||||
}
|
||||
|
||||
// 底部上传状态提示(临时显示)
|
||||
uint32_t nowMs = millis();
|
||||
if (st->statusUntilMs > nowMs && st->statusMsg[0] != '\0') {
|
||||
out.addText(400, SCREEN_W / 2, SCREEN_H - 10, st->statusColor,
|
||||
st->statusMsg, true);
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
@ -144,8 +419,8 @@ void setup() {
|
||||
// ★ 关键:把 u8g2 挂到 tft 上
|
||||
u8g2.begin(tft);
|
||||
// 连接到 Adafruit_GFX :contentReference[oaicite:2]{index=2}
|
||||
u8g2.setFontMode(1); // 透明背景(默认也是 1)
|
||||
u8g2.setFontDirection(0); // 正常从左到右
|
||||
u8g2.setFontMode(1); // 透明背景(默认也是 1)
|
||||
u8g2.setFontDirection(0); // 正常从左到右
|
||||
u8g2.setForegroundColor(WHITE); // 文字颜色
|
||||
|
||||
// 使用更小的中文字体
|
||||
@ -171,10 +446,31 @@ void setup() {
|
||||
// 初始化 AD7606(与 TFT 共用 SPI)
|
||||
ad7606::init();
|
||||
|
||||
// 创建采样队列与任务(将采样放到另一核,降低 UI 阻塞影响)
|
||||
g_sampleQ = xQueueCreate(2048, sizeof(int16_t));
|
||||
// 在 Core 0 上跑采样任务,优先级略高于默认
|
||||
xTaskCreatePinnedToCore(samplerTask, "sampler", 4096, nullptr, 3, nullptr, 0);
|
||||
|
||||
// 清空波形缓存并初始化计时
|
||||
for (int i = 0; i < AppState::GraphW; ++i) app.samples[i] = 0;
|
||||
for (int i = 0; i < AppState::GraphW; ++i)
|
||||
app.samples[i] = 0;
|
||||
app.sampleCount = 0;
|
||||
app.lastSampleMs = millis();
|
||||
|
||||
// 初始化标定目标
|
||||
app.target_mN[0] = 0.0f;
|
||||
app.target_mN[1] = 3.6f;
|
||||
app.target_mN[2] = 7.2f;
|
||||
app.target_mN[3] = 10.8f;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
app.captured[i] = false;
|
||||
app.capturedCode[i] = 0.0f;
|
||||
}
|
||||
app.presetIdx = 0;
|
||||
app.capturedCount = 0;
|
||||
app.fitReady = false;
|
||||
app.fitA = 0.0f;
|
||||
app.fitB = 0.0f;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
@ -182,14 +478,81 @@ void loop() {
|
||||
bool rightPressed = digitalRead(BTN_RIGHT) == HIGH;
|
||||
bool upPressed = digitalRead(BTN_UP) == HIGH;
|
||||
|
||||
// 读取 AD7606 CH0 原始码并按速率写入缓冲
|
||||
// 从采样队列取尽样本:
|
||||
// - 显示:按时间节流到 128 点/3秒(pushSample 内已限速)
|
||||
// - 录制:不节流,全部写入 recBuf
|
||||
uint32_t now = millis();
|
||||
int16_t ch0 = ad7606::readCH0();
|
||||
pushSample(app, ch0, now);
|
||||
int drained = 0;
|
||||
int16_t ch0;
|
||||
while (g_sampleQ && xQueueReceive(g_sampleQ, &ch0, 0) == pdTRUE) {
|
||||
drained++;
|
||||
app.lastRaw = ch0;
|
||||
// 显示按时间节流
|
||||
pushSample(app, ch0, now);
|
||||
now = millis();
|
||||
// 录制不节流
|
||||
if (app.recActive && app.recLen < AppState::RecCap) {
|
||||
app.recBuf[app.recLen++] = ch0;
|
||||
}
|
||||
}
|
||||
|
||||
// BTN_RIGHT:记录当前电压到当前档位,并循环到下一档
|
||||
if (rightPressed && !prevRight) {
|
||||
float c = (float)app.lastRaw;
|
||||
int idx = app.presetIdx;
|
||||
app.capturedCode[idx] = c;
|
||||
if (!app.captured[idx]) {
|
||||
app.captured[idx] = true;
|
||||
app.capturedCount++;
|
||||
}
|
||||
|
||||
// 计算线性拟合(使用已记录点的最小二乘)
|
||||
if (app.capturedCount >= 2) {
|
||||
double sx = 0, sy = 0, sxx = 0, sxy = 0;
|
||||
int n = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (!app.captured[i])
|
||||
continue;
|
||||
double x = app.capturedCode[i];
|
||||
double y = app.target_mN[i];
|
||||
sx += x;
|
||||
sy += y;
|
||||
sxx += x * x;
|
||||
sxy += x * y;
|
||||
n++;
|
||||
}
|
||||
double denom = (n * sxx - sx * sx);
|
||||
if (n >= 2 && fabs(denom) > 1e-9) {
|
||||
app.fitA = (float)((n * sxy - sx * sy) / denom);
|
||||
app.fitB = (float)((sy - app.fitA * sx) / n);
|
||||
app.fitReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 下一个档位
|
||||
app.presetIdx = (app.presetIdx + 1) % 4;
|
||||
}
|
||||
|
||||
// 左键:开始/停止录制,停止后上传
|
||||
if (leftPressed && !prevLeft) {
|
||||
if (!app.recActive) {
|
||||
app.recActive = true;
|
||||
app.recLen = 0;
|
||||
app.recStartMs = millis();
|
||||
app.recEndMs = 0;
|
||||
} else {
|
||||
app.recActive = false;
|
||||
app.recEndMs = millis();
|
||||
postRecording(app);
|
||||
}
|
||||
}
|
||||
// printf("l:%s, r:%s, u:%s\n", leftPressed ? "Y" : "N", rightPressed ? "Y"
|
||||
// : "N", upPressed ? "Y" : "N"); 驱动 UI 帧刷新
|
||||
|
||||
// 驱动 UI 帧刷新
|
||||
appUI.tick(millis());
|
||||
|
||||
|
||||
delay(16); // 简单节流 ~60FPS
|
||||
prevLeft = leftPressed;
|
||||
prevRight = rightPressed;
|
||||
prevUp = upPressed;
|
||||
// delay(16); // 简单节流 ~60FPS
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user