This commit is contained in:
2026-03-15 09:01:02 +08:00
parent a87625df90
commit ccac60a1e9
11 changed files with 2001 additions and 616 deletions
+3 -2
View File
@@ -2,6 +2,7 @@
#include "ch32v30x_dvp.h"
#include "eth_driver.h"
#include "string.h"
#include "qdx_port.h"
__attribute__((aligned(4))) uint8_t DMA_LineBuf0[BYTES_PER_LINE];
__attribute__((aligned(4))) uint8_t DMA_LineBuf1[BYTES_PER_LINE];
@@ -63,10 +64,10 @@ void DVP_Init(void)
DVP->CR1 = RB_DVP_DMA_EN; /* DMA on, CM=0 continuous, no reset bits */
DVP->CR0 |= RB_DVP_ENABLE;
printf("[DVP] Init done CR0=0x%02x CR1=0x%02x ROW=%d COL=%d\r\n",
DBG_INIT("DVP CR0=0x%02x CR1=0x%02x ROW=%d COL=%d\r\n",
(int)(DVP->CR0 & 0xFF), (int)(DVP->CR1 & 0xFF),
(int)DVP->ROW_NUM, (int)DVP->COL_NUM);
printf("[DVP] DMA_BUF0=0x%08x DMA_BUF1=0x%08x\r\n",
DBG_INIT("DVP DMA_BUF0=0x%08x DMA_BUF1=0x%08x\r\n",
(int)DVP->DMA_BUF0, (int)DVP->DMA_BUF1);
}
+27 -26
View File
@@ -1,6 +1,7 @@
#include "mini212g2.h"
#include "debug.h"
#include "string.h"
#include "qdx_port.h"
#if SENSOR_UART_ENABLE
@@ -165,7 +166,7 @@ int Mini212G2_SendCmd(const uint8_t *cmd, uint8_t len)
if (n == ACK_LEN && memcmp(resp, ACK_PATTERN, ACK_LEN) == 0)
return 0;
printf("[Sensor] resp(%d):", n);
DBG_INIT("Sensor resp(%d):", n);
for (int j = 0; j < n; j++) printf(" %02x", resp[j]);
printf("\r\n");
return -1;
@@ -176,18 +177,18 @@ int Mini212G2_SendCmd(const uint8_t *cmd, uint8_t len)
int Mini212G2_Init(void)
{
#if !SENSOR_UART_ENABLE
printf("[Sensor] UART disabled, assuming pre-configured via USB\r\n");
printf("[Sensor] Expected: CMOS output, CMOS8(MSB), Y16, 30Hz\r\n");
DBG_INIT("Sensor: UART disabled, pre-configured via USB\r\n");
DBG_INIT("Sensor: CMOS output, CMOS8(MSB), Y16, 30Hz\r\n");
return 0;
#else
int ok = 0, fail = 0;
Sensor_UART_Init();
printf("[Sensor] UART init %d, baud=%d\r\n",
DBG_INIT("Sensor: UART%d baud=%d\r\n",
(int)(SENSOR_UART == USART3 ? 3 : 2), (int)SENSOR_UART_BAUD);
/* Sensor needs several seconds to boot after power-on */
printf("[Sensor] Waiting 3s for sensor boot...\r\n");
DBG_INIT("Sensor: Waiting 3s for boot...\r\n");
Delay_Ms(3000);
/* === Communication test: send status query === */
@@ -204,51 +205,51 @@ int Mini212G2_Init(void)
}
Delay_Ms(200);
printf("[Sensor] Configuring CMOS/DVP output...\r\n");
DBG_INIT("Sensor: Configuring CMOS/DVP output...\r\n");
/* 1. Shutter compensation */
if (Mini212G2_SendCmd(CMD_SHUTTER, sizeof(CMD_SHUTTER)) == 0)
{ ok++; printf("[Sensor] Shutter OK\r\n"); }
{ ok++; DBG_INIT("Sensor: Shutter OK\r\n"); }
else
{ fail++; printf("[Sensor] Shutter FAIL\r\n"); }
{ fail++; DBG_ERR("Sensor: Shutter FAIL\r\n"); }
Delay_Ms(200);
/* 2. Digital port → CMOS */
if (Mini212G2_SendCmd(CMD_DIGITAL_CMOS, sizeof(CMD_DIGITAL_CMOS)) == 0)
{ ok++; printf("[Sensor] Digital->CMOS OK\r\n"); }
{ ok++; DBG_INIT("Sensor: Digital->CMOS OK\r\n"); }
else
{ fail++; printf("[Sensor] Digital->CMOS FAIL\r\n"); }
{ fail++; DBG_ERR("Sensor: Digital->CMOS FAIL\r\n"); }
Delay_Ms(200);
/* 3. CMOS interface → 8-bit MSB */
if (Mini212G2_SendCmd(CMD_CMOS8_MSB, sizeof(CMD_CMOS8_MSB)) == 0)
{ ok++; printf("[Sensor] CMOS8(MSB) OK\r\n"); }
{ ok++; DBG_INIT("Sensor: CMOS8(MSB) OK\r\n"); }
else
{ fail++; printf("[Sensor] CMOS8(MSB) FAIL\r\n"); }
{ fail++; DBG_ERR("Sensor: CMOS8(MSB) FAIL\r\n"); }
Delay_Ms(200);
/* 4. CMOS content → Y16 (raw 16-bit thermal) */
if (Mini212G2_SendCmd(CMD_CONTENT_Y16, sizeof(CMD_CONTENT_Y16)) == 0)
{ ok++; printf("[Sensor] Y16 OK\r\n"); }
{ ok++; DBG_INIT("Sensor: Y16 OK\r\n"); }
else
{ fail++; printf("[Sensor] Y16 FAIL\r\n"); }
{ fail++; DBG_ERR("Sensor: Y16 FAIL\r\n"); }
Delay_Ms(200);
/* 5. Frame rate → 30Hz */
if (Mini212G2_SendCmd(CMD_FPS_30HZ, sizeof(CMD_FPS_30HZ)) == 0)
{ ok++; printf("[Sensor] 30Hz OK\r\n"); }
{ ok++; DBG_INIT("Sensor: 30Hz OK\r\n"); }
else
{ fail++; printf("[Sensor] 30Hz FAIL\r\n"); }
{ fail++; DBG_ERR("Sensor: 30Hz FAIL\r\n"); }
Delay_Ms(200);
/* 6. Save settings to flash */
if (Mini212G2_SendCmd(CMD_SAVE, sizeof(CMD_SAVE)) == 0)
{ ok++; printf("[Sensor] Save OK\r\n"); }
{ ok++; DBG_INIT("Sensor: Save OK\r\n"); }
else
{ fail++; printf("[Sensor] Save FAIL\r\n"); }
{ fail++; DBG_ERR("Sensor: Save FAIL\r\n"); }
Delay_Ms(500);
printf("[Sensor] Config result: %d ok, %d fail\r\n", ok, fail);
DBG_INIT("Sensor: %d ok, %d fail\r\n", ok, fail);
sensor_init_ok = (uint8_t)ok;
sensor_init_fail = (uint8_t)fail;
@@ -268,7 +269,7 @@ void Mini212G2_PrintDigitalVideoStatus(void)
int n = Sensor_ReadResp(resp, sizeof(resp), 500);
if (n < 24) {
printf("[Sensor] Query failed (got %d bytes)\r\n", n);
DBG_ERR("Sensor: Query failed (got %d bytes)\r\n", n);
return;
}
@@ -292,16 +293,16 @@ void Mini212G2_PrintDigitalVideoStatus(void)
uint8_t fps = resp[9];
uint8_t clk_phase = resp[11];
printf("[Sensor] Digital Video Status:\r\n");
printf(" Port: %s (%d)\r\n",
DBG_INIT("Sensor Digital Video Status:\r\n");
DBG_INIT(" Port: %s (%d)\r\n",
(port_type < 10) ? port_names[port_type] : "?", (int)port_type);
printf(" Content: %s (%d)\r\n",
DBG_INIT(" Content: %s (%d)\r\n",
(content < 6) ? content_names[content] : "?", (int)content);
printf(" Interface: %s (%d)\r\n",
DBG_INIT(" Interface: %s (%d)\r\n",
(iface < 3) ? iface_names[iface] : "?", (int)iface);
printf(" FPS: %s (%d)\r\n",
DBG_INIT(" FPS: %s (%d)\r\n",
(fps < 4) ? fps_names[fps] : "?", (int)fps);
printf(" ClkPhase: %s (%d)\r\n",
DBG_INIT(" ClkPhase: %s (%d)\r\n",
(clk_phase == 0) ? "Rising" : "Falling", (int)clk_phase);
}
+18 -18
View File
@@ -176,7 +176,7 @@ void qdx_port_sock_recv_notify(uint8_t sockid)
total += len;
if (err != WCHNET_ERR_SUCCESS) break;
}
DBG_PORT("recv_notify sock%d: %u bytes, ring=%u\r\n",
DBG_HB("recv_notify sock%d: %u bytes, ring=%u\r\n",
sockid, (unsigned)total, (unsigned)ring_available(&ctx->rx_ring));
/* Wake blocking recv thread */
xSemaphoreGive(ctx->rx_sem);
@@ -187,7 +187,7 @@ void qdx_port_sock_connect_notify(uint8_t sockid)
SocketCtx_t *ctx = find_ctx_by_wchnet_id(sockid);
if (!ctx) return;
ctx->connected = 1;
DBG_PORT("connect_notify sock%d\r\n", sockid);
DBG_NET("connect_notify sock%d\r\n", sockid);
xSemaphoreGive(ctx->connect_sem);
}
@@ -196,7 +196,7 @@ void qdx_port_sock_disconnect_notify(uint8_t sockid)
SocketCtx_t *ctx = find_ctx_by_wchnet_id(sockid);
if (!ctx) return;
ctx->connected = 0;
DBG_PORT("disconnect_notify sock%d\r\n", sockid);
DBG_NET("disconnect_notify sock%d\r\n", sockid);
/* Wake recv thread so it can detect disconnect */
xSemaphoreGive(ctx->rx_sem);
}
@@ -209,7 +209,7 @@ void qdx_port_init(void)
{
memset(g_sock_ctx, 0, sizeof(g_sock_ctx));
g_wchnet_mutex = xSemaphoreCreateMutex();
DBG_PORT("init done, mutex=%p\r\n", g_wchnet_mutex);
DBG_INIT("qdx_port init done, mutex=%p\r\n", g_wchnet_mutex);
}
/* ============================================================
@@ -268,7 +268,7 @@ int8_t qdx_port_thread_create(const char *name, qdx_thread_entry_t entry,
BaseType_t ret = xTaskCreate((TaskFunction_t)entry, name,
(uint16_t)stack_words, arg,
(UBaseType_t)priority, NULL);
DBG_PORT("thread_create \"%s\" stack=%d pri=%d -> %s\r\n",
DBG_INIT("thread_create \"%s\" stack=%d pri=%d -> %s\r\n",
name, (int)stack_words, (int)priority, (ret == pdPASS) ? "OK" : "FAIL");
return (ret == pdPASS) ? 0 : -1;
}
@@ -281,15 +281,15 @@ qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port)
{
uint8_t dest_ip[4];
if (parse_ip(ip, dest_ip) != 0) {
DBG_PORT("bad IP \"%s\"\r\n", ip);
DBG_ERR("bad IP \"%s\"\r\n", ip);
return NULL;
}
DBG_PORT("connecting to %s:%d\r\n", ip, port);
DBG_NET("connecting to %s:%d\r\n", ip, port);
SocketCtx_t *ctx = alloc_sock_ctx();
if (!ctx) {
DBG_PORT("no free SocketCtx\r\n");
DBG_ERR("no free SocketCtx\r\n");
return NULL;
}
@@ -300,7 +300,7 @@ qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port)
sock_inf.DesPort = port;
memcpy(sock_inf.IPAddr, dest_ip, 4);
DBG_PORT("SOCK_INF: proto=%d dst=%d.%d.%d.%d:%d\r\n",
DBG_NET("SOCK_INF: proto=%d dst=%d.%d.%d.%d:%d\r\n",
sock_inf.ProtoType,
sock_inf.IPAddr[0], sock_inf.IPAddr[1],
sock_inf.IPAddr[2], sock_inf.IPAddr[3],
@@ -313,11 +313,11 @@ qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port)
xSemaphoreGive(g_wchnet_mutex);
if (err != WCHNET_ERR_SUCCESS) {
DBG_PORT("SocketCreat fail %02X\r\n", err);
DBG_ERR("SocketCreat fail %02X\r\n", err);
free_sock_ctx(ctx);
return NULL;
}
DBG_PORT("SocketCreat OK, wchnet_id=%d\r\n", wchnet_id);
DBG_NET("SocketCreat OK, wchnet_id=%d\r\n", wchnet_id);
ctx->wchnet_sock_id = wchnet_id;
@@ -330,12 +330,12 @@ qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port)
xSemaphoreGive(g_wchnet_mutex);
if (err != WCHNET_ERR_SUCCESS) {
DBG_PORT("SocketConnect fail %02X\r\n", err);
DBG_ERR("SocketConnect fail %02X\r\n", err);
WCHNET_SocketClose(wchnet_id, TCP_CLOSE_RST);
free_sock_ctx(ctx);
return NULL;
}
DBG_PORT("SocketConnect OK (err=0x%02X), waiting connect_sem (5s)...\r\n", err);
DBG_NET("SocketConnect OK (err=0x%02X), waiting connect_sem (5s)...\r\n", err);
/* Block until SINT_STAT_CONNECT or 5s timeout */
uint32_t t0 = xTaskGetTickCount();
@@ -343,14 +343,14 @@ qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port)
uint32_t elapsed = (xTaskGetTickCount() - t0) * portTICK_PERIOD_MS;
if (sem_ret != pdTRUE) {
DBG_PORT("connect_sem TIMEOUT after %d ms -> %d.%d.%d.%d:%d\r\n",
DBG_ERR("connect_sem TIMEOUT after %d ms -> %d.%d.%d.%d:%d\r\n",
(int)elapsed, dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], port);
DBG_PORT(" ctx->connected=%d wchnet_id=%d\r\n", ctx->connected, wchnet_id);
DBG_ERR(" ctx->connected=%d wchnet_id=%d\r\n", ctx->connected, wchnet_id);
WCHNET_SocketClose(wchnet_id, TCP_CLOSE_RST);
free_sock_ctx(ctx);
return NULL;
}
DBG_PORT("connect_sem got after %d ms, connected=%d\r\n", (int)elapsed, ctx->connected);
DBG_NET("connect_sem got after %d ms, connected=%d\r\n", (int)elapsed, ctx->connected);
if (!ctx->connected) {
WCHNET_SocketClose(wchnet_id, TCP_CLOSE_RST);
@@ -362,7 +362,7 @@ qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port)
WCHNET_SocketSetKeepLive(wchnet_id, ENABLE);
#endif
DBG_PORT("connected sock %d -> %d.%d.%d.%d:%d\r\n",
DBG_NET("connected sock %d -> %d.%d.%d.%d:%d\r\n",
wchnet_id, dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], port);
return (qdx_socket_t)ctx;
}
@@ -388,7 +388,7 @@ int32_t qdx_port_tcp_send(qdx_socket_t sock, const uint8_t *data, uint32_t len)
if (err != WCHNET_ERR_SUCCESS && send_len == 0) {
/* WCHNET send buffer full — yield so wchnet task can flush */
if (++retries > 50) {
DBG_PORT("send fail after retries, err=0x%02X\r\n", err);
DBG_ERR("send fail after retries, err=0x%02X\r\n", err);
return -1;
}
vTaskDelay(pdMS_TO_TICKS(2));
+38 -19
View File
@@ -18,29 +18,48 @@ extern "C" {
#endif
/* ============================================================
* Debug Print Macros (set to 0 to disable, 1 to enable)
* Multi-Level Debug Print System
*
* Verbosity levels (set DBG_LEVEL to control output volume):
* 0 = NONE — all debug prints disabled
* 1 = ERR — errors only
* 2 = BRIEF — + server config / network events (recommended)
* 3 = NORMAL — + trigger / data / init events (default)
* 4 = VERBOSE — + heartbeat / detailed internals
*
* Category macros — each prints when DBG_LEVEL >= its threshold:
* DBG_ERR (>=1) Errors, HardFault, CRC fail, bad frames
* DBG_CFG (>=2) Server-received config (Config2D/1D/Common, DevID, ACK, DetResult)
* DBG_NET (>=2) TCP connect/disconnect, PHY change, socket events
* DBG_TRIG (>=3) Trigger events (ext/int), burst start/complete
* DBG_DATA (>=3) Frame send results (2D/1D/TEMP_REQ)
* DBG_INIT (>=3) Boot-time init messages (sensor, DVP, WCHNET)
* DBG_HB (>=4) Heartbeat prints (high-frequency, debug only)
* ============================================================ */
#define QDX_DEBUG_PORT 1 /* qdx_port.c: socket/mutex/thread ops */
#define QDX_DEBUG_LOGIC 1 /* qdx_tcp_logic.c: protocol/TLV parsing */
#define QDX_DEBUG_APP 1 /* main.c: application-level events */
#define DBG_LEVEL_NONE 0
#define DBG_LEVEL_ERR 1
#define DBG_LEVEL_BRIEF 2
#define DBG_LEVEL_NORMAL 3
#define DBG_LEVEL_VERBOSE 4
#if QDX_DEBUG_PORT
#define DBG_PORT(fmt, ...) printf("[PORT] " fmt, ##__VA_ARGS__)
#else
#define DBG_PORT(fmt, ...) ((void)0)
#endif
/* >>> Change this single value to control output volume <<< */
#define DBG_LEVEL DBG_LEVEL_NORMAL
#if QDX_DEBUG_LOGIC
#define DBG_LOGIC(fmt, ...) printf("[LOGIC] " fmt, ##__VA_ARGS__)
#else
#define DBG_LOGIC(fmt, ...) ((void)0)
#endif
#define DBG_PRINT_(threshold, tag, fmt, ...) \
do { if (DBG_LEVEL >= (threshold)) printf(tag fmt, ##__VA_ARGS__); } while(0)
#if QDX_DEBUG_APP
#define DBG_APP(fmt, ...) printf("[APP] " fmt, ##__VA_ARGS__)
#else
#define DBG_APP(fmt, ...) ((void)0)
#endif
#define DBG_ERR(fmt, ...) DBG_PRINT_(DBG_LEVEL_ERR, "[ERR] ", fmt, ##__VA_ARGS__)
#define DBG_CFG(fmt, ...) DBG_PRINT_(DBG_LEVEL_BRIEF, "[CFG] ", fmt, ##__VA_ARGS__)
#define DBG_NET(fmt, ...) DBG_PRINT_(DBG_LEVEL_BRIEF, "[NET] ", fmt, ##__VA_ARGS__)
#define DBG_TRIG(fmt, ...) DBG_PRINT_(DBG_LEVEL_NORMAL, "[TRIG] ", fmt, ##__VA_ARGS__)
#define DBG_DATA(fmt, ...) DBG_PRINT_(DBG_LEVEL_NORMAL, "[DATA] ", fmt, ##__VA_ARGS__)
#define DBG_INIT(fmt, ...) DBG_PRINT_(DBG_LEVEL_NORMAL, "[INIT] ", fmt, ##__VA_ARGS__)
#define DBG_HB(fmt, ...) DBG_PRINT_(DBG_LEVEL_VERBOSE, "[HB] ", fmt, ##__VA_ARGS__)
/* Legacy compatibility aliases — avoid using in new code */
#define DBG_PORT(fmt, ...) DBG_INIT(fmt, ##__VA_ARGS__)
#define DBG_LOGIC(fmt, ...) DBG_CFG(fmt, ##__VA_ARGS__)
#define DBG_APP(fmt, ...) DBG_DATA(fmt, ##__VA_ARGS__)
/* ============================================================
* Time & Delay
@@ -109,7 +109,7 @@ int8_t Preprocess_Execute(const RawImageBuffer_t *input,
tgt_w = (tgt_w + 1) / 2;
tgt_h = (tgt_h + 1) / 2;
}
DBG_PORT("PP: target clamped %dx%d -> %dx%d (buf=%d)\r\n",
DBG_INIT("PP: target clamped %dx%d -> %dx%d (buf=%d)\r\n",
(int)orig_w, (int)orig_h, (int)tgt_w, (int)tgt_h, (int)max_payload);
}
@@ -69,7 +69,7 @@ static void tcp_stream_init(TcpStreamCtx_t *ctx, const char *label) {
}
static void tcp_stream_disconnect(TcpStreamCtx_t *ctx) {
DBG_LOGIC("!! [%s] Disconnected\r\n", ctx->label);
DBG_NET("[%s] Disconnected\r\n", ctx->label);
ctx->is_connected = 0;
if (ctx->sock) {
qdx_port_tcp_close(ctx->sock);
@@ -79,10 +79,10 @@ static void tcp_stream_disconnect(TcpStreamCtx_t *ctx) {
static int8_t tcp_stream_connect(TcpStreamCtx_t *ctx, const char *ip,
uint16_t port) {
DBG_LOGIC("[%s] connecting %s:%d...\r\n", ctx->label, ip, port);
DBG_NET("[%s] connecting %s:%d...\r\n", ctx->label, ip, port);
ctx->sock = qdx_port_tcp_connect(ip, port);
if (ctx->sock == NULL) {
DBG_LOGIC("[%s] connect FAILED\r\n", ctx->label);
DBG_ERR("[%s] connect FAILED\r\n", ctx->label);
return -1;
}
@@ -90,7 +90,7 @@ static int8_t tcp_stream_connect(TcpStreamCtx_t *ctx, const char *ip,
ctx->last_activity_ms = qdx_port_get_tick_ms();
ctx->last_heartbeat_ms = ctx->last_activity_ms;
ctx->recv_len = 0;
DBG_LOGIC(">> [%s] Connected to %s:%d\r\n", ctx->label, ip, port);
DBG_NET("[%s] Connected to %s:%d\r\n", ctx->label, ip, port);
return 0;
}
@@ -121,7 +121,7 @@ static int32_t tcp_send_frame(TcpStreamCtx_t *ctx, uint8_t msg_class,
}
static void tcp_send_handshake(TcpStreamCtx_t *ctx) {
DBG_LOGIC(">> [%s] Handshake (DevID=%d)\r\n", ctx->label, (int)g_TcpLogic.dev_id);
DBG_NET("[%s] Handshake (DevID=%d)\r\n", ctx->label, (int)g_TcpLogic.dev_id);
uint8_t payload[54];
memset(payload, 0, sizeof(payload));
qdx_write_u16_le(payload + 0, 0x0200);
@@ -250,17 +250,17 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet,
uint8_t cfg_updated = 0;
DBG_LOGIC("[%s] TLV pkt: Seq=%d, PayloadLen=%d\r\n", ctx->label,
DBG_HB("[%s] TLV pkt: Seq=%d Len=%d\r\n", ctx->label,
hdr_seq, payload_len);
while (parsed_len <= payload_len - 3) {
uint8_t type = packet[offset];
uint16_t len = qdx_read_u16_le(packet + offset + 1);
DBG_LOGIC("[%s] TLV: Type=0x%02X, Len=%d\r\n", ctx->label, type, len);
DBG_HB("[%s] TLV: Type=0x%02X Len=%d\r\n", ctx->label, type, len);
if (parsed_len + 3 + len > payload_len) {
DBG_LOGIC("[%s] ! TLV truncated (need %d, have %d)\r\n",
DBG_ERR("[%s] TLV truncated (need %d, have %d)\r\n",
ctx->label, len, payload_len - parsed_len - 3);
break; /* Malformed */
}
@@ -278,16 +278,16 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet,
uint16_t ack_seq = qdx_read_u16_le(value);
uint8_t status = value[2];
uint16_t err_code = (len >= 5) ? qdx_read_u16_le(value + 3) : 0;
DBG_LOGIC("<< [%s] ACK: seq=%d status=%d err=0x%04x\r\n",
ctx->label, (int)ack_seq, (int)status, (int)err_code);
DBG_CFG("<< ACK: seq=%d status=%d err=0x%04x\r\n",
(int)ack_seq, (int)status, (int)err_code);
}
break;
}
case TYPE_DEVID_ASSIGN: {
if (len >= sizeof(DevIDAssignment_t)) {
uint16_t new_id = qdx_read_u16_le(value);
DBG_LOGIC("<< [%s] DevID assigned: %d -> %d\r\n",
ctx->label, (int)g_TcpLogic.dev_id, (int)new_id);
DBG_CFG("<< DevID assigned: %d -> %d\r\n",
(int)g_TcpLogic.dev_id, (int)new_id);
g_TcpLogic.pending_new_dev_id = new_id;
tcp_send_ack(ctx, hdr_seq, 0, 0);
}
@@ -300,8 +300,7 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet,
g_TcpLogic.has_valid_config = 1;
cfg_updated = 1;
qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
DBG_LOGIC("<< [%s] ConfigCommon: Mode=%d Tag=%d Strict=%d\r\n",
ctx->label,
DBG_CFG("<< ConfigCommon: Mode=%d Tag=%d Strict=%d\r\n",
(int)g_TcpLogic.cached_common.WorkMode,
(int)g_TcpLogic.cached_common.ConfigTag,
(int)g_TcpLogic.cached_common.StrictnessLevel);
@@ -315,10 +314,9 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet,
g_TcpLogic.has_valid_config = 1;
cfg_updated = 1;
qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
DBG_LOGIC("<< [%s] Config2D: En=%d %dx%d Tgt=%dx%d Fps=%d "
DBG_CFG("<< Config2D: En=%d %dx%d Tgt=%dx%d Fps=%d "
"Trig=%d Burst=%d Intv=%d Thresh=%d "
"ROI(%d,%d,%d,%d) NGio=%d\r\n",
ctx->label,
(int)g_TcpLogic.cached_cfg2d.Enabled,
(int)g_TcpLogic.cached_cfg2d.Width,
(int)g_TcpLogic.cached_cfg2d.Height,
@@ -335,8 +333,8 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet,
(int)g_TcpLogic.cached_cfg2d.TriggerRoiH,
(int)g_TcpLogic.cached_cfg2d.NGioDelay);
} else {
DBG_LOGIC("<< [%s] Config2D bad len=%d (need %d)\r\n",
ctx->label, len, (int)sizeof(Config2D_t));
DBG_ERR("<< Config2D bad len=%d (need %d)\r\n",
len, (int)sizeof(Config2D_t));
}
break;
}
@@ -347,9 +345,8 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet,
g_TcpLogic.has_valid_config = 1;
cfg_updated = 1;
qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
DBG_LOGIC("<< [%s] Config1D: En=%d RunMode=%d TrigType=%d "
DBG_CFG("<< Config1D: En=%d RunMode=%d TrigType=%d "
"BufSz=%d TempLim=%d NGio=%d\r\n",
ctx->label,
(int)g_TcpLogic.cached_cfg1d.Enabled,
(int)g_TcpLogic.cached_cfg1d.RunMode,
(int)g_TcpLogic.cached_cfg1d.TriggerType,
@@ -360,13 +357,13 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet,
break;
}
case TYPE_TEMP_FRAME: {
DBG_LOGIC("<< [%s] TempFrame request (len=%d)\r\n", ctx->label, (int)len);
DBG_CFG("<< TempFrame request (len=%d)\r\n", (int)len);
if (g_TcpLogic.temp_req_cb) {
uint8_t is2d = 0;
if (len >= 18) {
is2d = value[18];
}
DBG_LOGIC(" -> callback: is2D=%d\r\n", (int)is2d);
DBG_CFG(" -> callback: is2D=%d\r\n", (int)is2d);
g_TcpLogic.temp_req_cb(is2d);
}
break;
@@ -375,8 +372,8 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet,
if (len >= sizeof(DetectionResult_t)) {
uint32_t frame_num = qdx_read_u32_le(value);
uint8_t result_status = value[4];
DBG_LOGIC("<< [%s] DetectionResult: frm=%d result=%d\r\n",
ctx->label, (int)frame_num, (int)result_status);
DBG_CFG("<< DetectionResult: frm=%d result=%d\r\n",
(int)frame_num, (int)result_status);
if (g_TcpLogic.detect_cb)
g_TcpLogic.detect_cb(frame_num, result_status);
}
@@ -386,8 +383,7 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet,
/* Server echoes handshake info — safe to ignore */
break;
default:
DBG_LOGIC("<< [%s] Unknown TLV type=0x%02x len=%d\r\n",
ctx->label, (int)type, (int)len);
DBG_ERR("Unknown TLV type=0x%02x len=%d\r\n", (int)type, (int)len);
break;
}
@@ -396,7 +392,7 @@ static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet,
}
if (cfg_updated && g_TcpLogic.config_cb && g_TcpLogic.has_valid_config) {
DBG_LOGIC("<< [%s] Config updated -> notify app\r\n", ctx->label);
DBG_CFG("Config updated -> notify app\r\n");
qdx_port_mutex_lock(g_TcpLogic.config_mutex);
g_TcpLogic.config_cb(&g_TcpLogic.cached_common, &g_TcpLogic.cached_cfg2d,
&g_TcpLogic.cached_cfg1d);
@@ -438,7 +434,7 @@ static void tcp_process_rx_buffer(TcpStreamCtx_t *ctx) {
uint16_t length = qdx_read_u16_le(ctx->recv_buffer + 3);
if (version != PROTO_VERSION || length < HEADER_SIZE + CRC_SIZE) {
DBG_LOGIC("[%s] bad header: ver=0x%02X(exp 0x%02X) len=%d\r\n",
DBG_ERR("[%s] bad header: ver=0x%02X(exp 0x%02X) len=%d\r\n",
ctx->label, version, PROTO_VERSION, length);
memmove(ctx->recv_buffer, ctx->recv_buffer + 2, ctx->recv_len - 2);
ctx->recv_len -= 2;
@@ -446,7 +442,7 @@ static void tcp_process_rx_buffer(TcpStreamCtx_t *ctx) {
}
if (length > sizeof(ctx->recv_buffer)) {
DBG_LOGIC("[%s] frame too large: %d > %d\r\n", ctx->label,
DBG_ERR("[%s] frame too large: %d > %d\r\n", ctx->label,
length, (int)sizeof(ctx->recv_buffer));
ctx->recv_len = 0;
break;
@@ -464,7 +460,7 @@ static void tcp_process_rx_buffer(TcpStreamCtx_t *ctx) {
/* 3. Dispatch */
parse_and_dispatch_tlv(ctx, ctx->recv_buffer, length);
} else {
DBG_LOGIC("[%s] CRC fail: calc=0x%04X recv=0x%04X len=%d\r\n",
DBG_ERR("[%s] CRC fail: calc=0x%04X recv=0x%04X len=%d\r\n",
ctx->label, calculated_crc, received_crc, length);
}
@@ -642,6 +638,22 @@ void TcpLogic_RegisterTempFrameRequestCallback(TempFrameRequestCallback_t cb) {
g_TcpLogic.temp_req_cb = cb;
}
void TcpLogic_InjectTestConfig(const ConfigCommon_t *common,
const Config2D_t *cfg2d,
const Config1D_t *cfg1d) {
qdx_port_mutex_lock(g_TcpLogic.config_mutex);
if (common) memcpy(&g_TcpLogic.cached_common, common, sizeof(*common));
if (cfg2d) memcpy(&g_TcpLogic.cached_cfg2d, cfg2d, sizeof(*cfg2d));
if (cfg1d) memcpy(&g_TcpLogic.cached_cfg1d, cfg1d, sizeof(*cfg1d));
g_TcpLogic.has_valid_config = 1;
qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
if (g_TcpLogic.config_cb) {
g_TcpLogic.config_cb(&g_TcpLogic.cached_common,
&g_TcpLogic.cached_cfg2d,
&g_TcpLogic.cached_cfg1d);
}
}
/* ============================================================
* Zero-Copy Frame Building & Fragmentation for Temperature Data
* ============================================================ */
@@ -658,7 +670,7 @@ TcpLogic_BuildAndSendTemperatureFrame(TcpTxBuffer_t *io_buffer,
g_TcpLogic.frame_count++;
DBG_LOGIC(">> [Data] TempFrame #%d: %dx%d type=%d is2D=%d payload=%d\r\n",
DBG_DATA(">> TempFrame #%d: %dx%d type=%d is2D=%d payload=%d\r\n",
(int)processMeta->FrameNumber,
(int)processMeta->ValidWidth, (int)processMeta->ValidHeight,
(int)frameType, (int)is2D, (int)io_buffer->ValidPayloadLen);
@@ -725,7 +737,7 @@ TcpLogic_BuildAndSendTemperatureFrame(TcpTxBuffer_t *io_buffer,
uint32_t frag_count =
(total_tlv_len + MAX_FRAGMENT_PAYLOAD - 1) / MAX_FRAGMENT_PAYLOAD;
DBG_LOGIC(">> [Data] Fragmented: %d frags, total=%d\r\n",
DBG_DATA(">> Fragmented: %d frags, total=%d\r\n",
(int)frag_count, (int)total_tlv_len);
for (uint32_t i = 0; i < frag_count; i++) {
@@ -105,6 +105,16 @@ typedef void (*TempFrameRequestCallback_t)(uint8_t is2dRequest);
*/
void TcpLogic_RegisterTempFrameRequestCallback(TempFrameRequestCallback_t cb);
/**
* @brief Inject configuration directly (for test mode without server).
*
* Sets the internal config cache and fires the config callback.
* Only non-NULL parameters are updated.
*/
void TcpLogic_InjectTestConfig(const ConfigCommon_t *common,
const Config2D_t *cfg2d,
const Config1D_t *cfg1d);
#ifdef __cplusplus
}
#endif
+582 -232
View File
File diff suppressed because it is too large Load Diff
+709
View File
@@ -0,0 +1,709 @@
#include "ch32v30x.h"
#include "string.h"
#include "eth_driver.h"
#include "dvp.h"
#include "mini212g2.h"
#include "qdx_port.h"
#include "qdx_preprocess.h"
#include "qdx_tcp_logic.h"
#include "FreeRTOS.h"
#include "task.h"
/* ============================================================
* TEST MODE: Generate simulated sensor data without real hardware.
* Set to 1 to enable test pattern generation (no sensor/DVP needed).
* Set to 0 for normal operation with real Mini212G2 sensor.
* ============================================================ */
#define TEST_PATTERN_MODE 1
/* ============================================================
* Feature Test Switches — set to 1 to enable, 0 to disable.
* Allows testing each feature independently.
* ============================================================ */
#define TEST_ENABLE_HEARTBEAT 1 /* Periodic heartbeat debug print task */
#define TEST_ENABLE_TRIGGER 0 /* Internal trigger + burst upload */
#define TEST_ENABLE_NG_GPIO 0 /* NG GPIO pulse output on detection */
#define TEST_ENABLE_TEMP_REQ 1 /* On-demand frame upload from server */
#define TEST_ENABLE_TCP_SEND 1 /* TCP data channel sending */
/* Default burst parameters — used when server hasn't sent config yet */
#define DEFAULT_BURST_COUNT 3
#define DEFAULT_BURST_INTERVAL_MS 200
#define MAX_TCP_PAYLOAD_SIZE 10240
uint8_t g_TxNetBuffer_A_Mem[MAX_TCP_PAYLOAD_SIZE];
uint8_t g_TxNetBuffer_B_Mem[MAX_TCP_PAYLOAD_SIZE];
TcpTxBuffer_t g_TxNetBuffer_A = {
.pBuffer = g_TxNetBuffer_A_Mem,
.TotalCapacity = MAX_TCP_PAYLOAD_SIZE,
.HeadOffset = 64,
.ValidPayloadLen = 0
};
TcpTxBuffer_t g_TxNetBuffer_B = {
.pBuffer = g_TxNetBuffer_B_Mem,
.TotalCapacity = MAX_TCP_PAYLOAD_SIZE,
.HeadOffset = 64,
.ValidPayloadLen = 0
};
void OnConfigUpdate(const ConfigCommon_t *common, const Config2D_t *cfg2d, const Config1D_t *cfg1d)
{
Preprocess_Settings_Change(cfg2d, cfg1d, common);
}
extern volatile uint32_t sys_tick_ms;
#if TEST_ENABLE_NG_GPIO
/* NG output GPIO: PA8 push-pull, active-high when NG detected */
#define NG_GPIO_PORT GPIOA
#define NG_GPIO_PIN GPIO_Pin_8
#define NG_GPIO_CLK RCC_APB2Periph_GPIOA
#define NG_PULSE_MS 200 /* default NG pulse width */
static volatile uint32_t g_ng_off_time = 0;
static void NG_GPIO_Init(void)
{
GPIO_InitTypeDef gpio = {0};
RCC_APB2PeriphClockCmd(NG_GPIO_CLK, ENABLE);
gpio.GPIO_Pin = NG_GPIO_PIN;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
gpio.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(NG_GPIO_PORT, &gpio);
GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN);
}
#endif /* TEST_ENABLE_NG_GPIO */
#if TEST_ENABLE_TEMP_REQ
/* Flag set by TempFrameRequest callback; consumed by business task */
static volatile uint8_t g_temp_req_pending = 0;
static volatile uint8_t g_temp_req_is2d = 1;
void OnTempFrameRequest(uint8_t is2dRequest)
{
g_temp_req_is2d = is2dRequest;
g_temp_req_pending = 1;
DBG_APP("TempFrameReq is2d=%d\r\n", (int)is2dRequest);
}
#endif /* TEST_ENABLE_TEMP_REQ */
#if TEST_ENABLE_NG_GPIO
void OnDetectionResult(uint32_t frameNumber, uint8_t resultStatus)
{
(void)frameNumber;
/* resultStatus: 0 = NG, 1 = OK */
if (resultStatus == 0) {
ConfigCommon_t tmp_common;
Config2D_t tmp_cfg2d;
Config1D_t tmp_cfg1d;
uint32_t pulse_ms = NG_PULSE_MS; /* fallback default */
if (TcpLogic_GetLatestConfig(&tmp_common, &tmp_cfg2d, &tmp_cfg1d) == 0
&& tmp_cfg2d.NGioDelay > 0) {
pulse_ms = tmp_cfg2d.NGioDelay;
}
GPIO_SetBits(NG_GPIO_PORT, NG_GPIO_PIN);
g_ng_off_time = sys_tick_ms + pulse_ms;
}
}
#endif /* TEST_ENABLE_NG_GPIO */
#define KEEPALIVE_ENABLE 1
/* ============================================================
* FLASH / SRAM 分配配置 (Option Bytes RAM_CODE_MOD[2:0])
* 修改后需复位才生效
* ============================================================ */
typedef enum {
FLASH_192_SRAM_128 = 0, /* 00x 默认 */
FLASH_224_SRAM_96, /* 01x */
FLASH_256_SRAM_64, /* 10x */
FLASH_128_SRAM_192, /* 110 */
FLASH_288_SRAM_32 /* 111 */
} FLASH_SRAM_DEFIN;
static void Config_Flash_SRAM(FLASH_SRAM_DEFIN mode)
{
uint8_t UserByte = FLASH_GetUserOptionByte() & 0xFF;
uint8_t newByte = UserByte & ~0xE0; /* clear bits [7:5] */
switch (mode) {
case FLASH_192_SRAM_128: break; /* 000 */
case FLASH_224_SRAM_96: newByte |= 0x40; break; /* 010 */
case FLASH_256_SRAM_64: newByte |= 0x80; break; /* 100 */
case FLASH_128_SRAM_192: newByte |= 0xC0; break; /* 110 */
case FLASH_288_SRAM_32: newByte |= 0xE0; break; /* 111 */
default: return;
}
if (newByte == UserByte) return; /* already configured */
FLASH_Unlock();
FLASH_ProgramOptionByteData(0x1FFFF802, newByte);
FLASH_Lock();
printf("Flash/SRAM config changed to %d, resetting...\r\n", mode);
NVIC_SystemReset();
}
u8 MACAddr[6];
u8 IPAddr[4] = {192, 168, 7, 10};
u8 GWIPAddr[4] = {192, 168, 7, 1};
u8 IPMask[4] = {255, 255, 255, 0};
u8 DESIP[4] = {192, 168, 7, 50};
u16 desport = 5512;
u16 srcport = 5511;
u8 SocketId;
u8 SocketRecvBuf[WCHNET_MAX_SOCKET_NUM][RECE_BUF_LEN];
void mStopIfError(u8 iError)
{
if (iError == WCHNET_ERR_SUCCESS) return;
printf("Error: %02X\r\n", (u16)iError);
}
void TIM2_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure = {0};
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Period = WCHNETTIMERPERIOD * 1000 - 1;
TIM_TimeBaseStructure.TIM_Prescaler = SystemCoreClock / 1000000 - 1;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
NVIC_EnableIRQ(TIM2_IRQn);
}
/* qdx_port.c notification hooks */
extern void qdx_port_sock_recv_notify(uint8_t sockid);
extern void qdx_port_sock_connect_notify(uint8_t sockid);
extern void qdx_port_sock_disconnect_notify(uint8_t sockid);
extern void qdx_port_init(void);
extern void qdx_port_net_lock(void);
extern void qdx_port_net_unlock(void);
void WCHNET_HandleSockInt(u8 socketid, u8 intstat)
{
DBG_APP("SockInt: id=%d stat=0x%02X\r\n", socketid, intstat);
if (intstat & SINT_STAT_RECV)
{
qdx_port_sock_recv_notify(socketid);
}
if (intstat & SINT_STAT_CONNECT)
{
WCHNET_ModifyRecvBuf(socketid, (u32)SocketRecvBuf[socketid], RECE_BUF_LEN);
qdx_port_sock_connect_notify(socketid);
DBG_APP("TCP Connected, socket %d\r\n", socketid);
}
if (intstat & SINT_STAT_DISCONNECT)
{
qdx_port_sock_disconnect_notify(socketid);
DBG_APP("TCP Disconnected, socket %d\r\n", socketid);
}
if (intstat & SINT_STAT_TIM_OUT)
{
qdx_port_sock_disconnect_notify(socketid);
DBG_APP("TCP Timeout, socket %d\r\n", socketid);
}
}
void WCHNET_HandleGlobalInt(void)
{
u8 intstat;
u16 i;
u8 socketint;
intstat = WCHNET_GetGlobalInt();
DBG_APP("GlobalInt: 0x%02X\r\n", intstat);
if (intstat & GINT_STAT_UNREACH) DBG_APP("GINT_STAT_UNREACH\r\n");
if (intstat & GINT_STAT_IP_CONFLI) DBG_APP("GINT_STAT_IP_CONFLI\r\n");
if (intstat & GINT_STAT_PHY_CHANGE) {
i = WCHNET_GetPHYStatus();
DBG_APP("PHY_CHANGE: status=0x%04X %s\r\n", i,
(i & PHY_Linked_Status) ? "LINK_UP" : "LINK_DOWN");
}
if (intstat & GINT_STAT_SOCKET) {
for (i = 0; i < WCHNET_MAX_SOCKET_NUM; i++) {
socketint = WCHNET_GetSocketInt(i);
if (socketint) WCHNET_HandleSockInt(i, socketint);
}
}
}
#if TEST_ENABLE_HEARTBEAT
/* ============================================================
* RTOS Task: Heartbeat (periodic print for debug)
* ============================================================ */
static void task_heartbeat_entry(void *pvParameters)
{
(void)pvParameters;
uint32_t cnt = 0;
while (1)
{
#if TEST_PATTERN_MODE
printf("[HB] %d tick=%d TEST_MODE frm=%d\r\n",
(int)cnt++, (int)xTaskGetTickCount(),
(int)dvp_frame_count);
#else
printf("[HB] %d tick=%d dvp_frm=%d row_irq=%d\r\n",
(int)cnt++, (int)xTaskGetTickCount(),
(int)dvp_frame_count, (int)dvp_row_irq_cnt);
#endif
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
#endif /* TEST_ENABLE_HEARTBEAT */
/* ============================================================
* RTOS Task: WCHNET protocol stack driver (highest priority)
* ============================================================ */
static void task_wchnet_entry(void *pvParameters)
{
(void)pvParameters;
while (1)
{
qdx_port_net_lock();
WCHNET_MainTask();
if (WCHNET_QueryGlobalInt())
WCHNET_HandleGlobalInt();
qdx_port_net_unlock();
vTaskDelay(pdMS_TO_TICKS(5));
}
}
/* ============================================================
* RTOS Task: Business logic (DVP + preprocess + send)
* ============================================================ */
#if TEST_ENABLE_TRIGGER
/* ============================================================
* Burst capture state machine
* ============================================================ */
static uint8_t burst_active = 0; /* 1 = currently in burst */
static uint8_t burst_remaining = 0; /* frames left to capture */
static uint32_t burst_next_time_ms = 0; /* next burst frame time */
#endif /* TEST_ENABLE_TRIGGER */
#if TEST_PATTERN_MODE
/* ============================================================
* Test Pattern Generator
* Generates simulated 256x192 Y16 thermal images at ~10 FPS.
* Pattern cycles through several types so all pipeline paths
* can be exercised.
*
* Pattern 0: Gradient (25.00~45.00°C) — baseline, no trigger
* Pattern 1: Hot center spot (90.00°C) — should trigger alarm
* Pattern 2: Uniform warm (35.00°C) — no trigger
* Pattern 3: Checkerboard with hot cells — tests ROI search
* ============================================================ */
#define TEST_FPS_DELAY_MS 100 /* ~10 FPS */
#define TEMP_RAW(deg_c) ((uint16_t)((deg_c) * 10)) /* e.g. 35.0°C → 350, 0.1°C/LSB */
static void test_fill_gradient(uint16_t *buf, uint16_t w, uint16_t h)
{
/* Diagonal gradient: 25°C at top-left → 45°C at bottom-right
* Both X and Y contribute so 1D center-row also shows variation */
for (uint16_t y = 0; y < h; y++) {
for (uint16_t x = 0; x < w; x++) {
uint32_t pos = (uint32_t)y * w + (uint32_t)x * h; /* combined weight */
uint16_t temp = TEMP_RAW(25.0) + (uint16_t)(pos * TEMP_RAW(20.0) / ((uint32_t)w * h));
buf[y * w + x] = temp;
}
}
}
static void test_fill_hotspot(uint16_t *buf, uint16_t w, uint16_t h)
{
/* Background 30°C with slight X variation, center 32x32 block at 90°C */
for (uint16_t y = 0; y < h; y++)
for (uint16_t x = 0; x < w; x++)
buf[y * w + x] = TEMP_RAW(28.0) + (uint16_t)((uint32_t)x * TEMP_RAW(4.0) / w);
uint16_t cx = w / 2, cy = h / 2;
for (uint16_t y = cy - 16; y < cy + 16; y++)
for (uint16_t x = cx - 16; x < cx + 16; x++)
buf[y * w + x] = TEMP_RAW(90.0);
}
static void test_fill_uniform(uint16_t *buf, uint16_t w, uint16_t h)
{
for (uint16_t y = 0; y < h; y++)
for (uint16_t x = 0; x < w; x++)
buf[y * w + x] = TEMP_RAW(35.0);
}
static void test_fill_checker(uint16_t *buf, uint16_t w, uint16_t h)
{
/* 32x32 checkerboard: alternating 30°C and 85°C blocks */
for (uint16_t y = 0; y < h; y++)
for (uint16_t x = 0; x < w; x++) {
uint8_t cell = ((y / 32) + (x / 32)) & 1;
buf[y * w + x] = cell ? TEMP_RAW(85.0) : TEMP_RAW(28.0);
}
}
static void task_test_pattern_entry(void *pvParameters)
{
(void)pvParameters;
uint32_t frame_num = 0;
uint8_t pattern_idx = 0;
/* Cycle: gradient x20, hotspot x5, uniform x10, checker x5 */
const uint8_t pattern_seq[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,
2,2,2,2,2,2,2,2,2,2,
3,3,3,3,3};
const uint8_t seq_len = sizeof(pattern_seq);
while (1)
{
uint16_t *buf = (uint16_t *)FrameBuffer;
switch (pattern_seq[pattern_idx % seq_len]) {
case 0: test_fill_gradient(buf, SENSOR_WIDTH, SENSOR_HEIGHT); break;
case 1: test_fill_hotspot(buf, SENSOR_WIDTH, SENSOR_HEIGHT); break;
case 2: test_fill_uniform(buf, SENSOR_WIDTH, SENSOR_HEIGHT); break;
case 3: test_fill_checker(buf, SENSOR_WIDTH, SENSOR_HEIGHT); break;
}
pattern_idx++;
if (pattern_idx >= seq_len) pattern_idx = 0;
frame_num++;
dvp_frame_count = frame_num;
Ready_Frame_Count = frame_num;
Frame_Ready_Flag = 1;
vTaskDelay(pdMS_TO_TICKS(TEST_FPS_DELAY_MS));
}
}
#endif /* TEST_PATTERN_MODE */
#if TEST_ENABLE_TEMP_REQ
/* ============================================================
* 1D mode: build and send a single 1D temperature frame from
* the current raw image. Scans the center row and packs
* TempPoint1D_t (2B time_offset + 2B temp, little-endian).
* ============================================================ */
#define TEST_1D_POINTS 30 /* 1D test: sample 30 points from center row */
static void send_1d_frame_from_raw(const RawImageBuffer_t *raw, TcpTxBuffer_t *tx_buf)
{
uint16_t w = raw->Width;
uint16_t h = raw->Height;
uint16_t *src = raw->pData;
uint16_t row = h / 2; /* center row */
tx_buf->ValidPayloadLen = 0;
uint8_t *dest = tx_buf->pBuffer + tx_buf->HeadOffset;
uint32_t capacity = tx_buf->TotalCapacity - tx_buf->HeadOffset;
uint16_t points = TEST_1D_POINTS;
if (points > w) points = w;
if ((uint32_t)points * 4 > capacity)
points = (uint16_t)(capacity / 4);
int16_t min_t = 32767, max_t = -32768;
int32_t sum_t = 0;
for (uint16_t i = 0; i < points; i++) {
/* Evenly sample across the full row width */
uint16_t col = (uint16_t)((uint32_t)i * (w - 1) / (points > 1 ? points - 1 : 1));
uint16_t temp = src[row * w + col];
uint16_t time_offset = (uint16_t)((uint32_t)i * 600 / (points > 1 ? points - 1 : 1));
int16_t t = (int16_t)temp;
if (t < min_t) min_t = t;
if (t > max_t) max_t = t;
sum_t += t;
dest[0] = (uint8_t)(time_offset & 0xFF);
dest[1] = (uint8_t)((time_offset >> 8) & 0xFF);
dest[2] = (uint8_t)(temp & 0xFF);
dest[3] = (uint8_t)((temp >> 8) & 0xFF);
dest += 4;
}
tx_buf->ValidPayloadLen = (uint32_t)points * 4;
PreprocessResult_t meta;
memset(&meta, 0, sizeof(meta));
meta.pValidData = tx_buf->pBuffer + tx_buf->HeadOffset;
meta.DataLength = tx_buf->ValidPayloadLen;
meta.ValidWidth = points;
meta.ValidHeight = 1;
meta.MinTemp = min_t;
meta.MaxTemp = max_t;
meta.AvgTemp = (int16_t)(sum_t / (points > 0 ? points : 1));
meta.RoiTemp = meta.AvgTemp;
meta.FrameNumber = raw->FrameNumber;
TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 0 /* IS_1D */);
}
#endif /* TEST_ENABLE_TEMP_REQ */
static void task_business_entry(void *pvParameters)
{
(void)pvParameters;
static uint8_t use_buffer_A = 1;
while (1)
{
#if TEST_ENABLE_NG_GPIO
/* NG pulse off check */
if (g_ng_off_time && sys_tick_ms >= g_ng_off_time) {
GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN);
g_ng_off_time = 0;
}
#endif
#if !TEST_PATTERN_MODE
DVP_Task();
#endif
#if TEST_ENABLE_TEMP_REQ
/* Handle on-demand frame request from server */
if (g_temp_req_pending)
{
g_temp_req_pending = 0;
/* 检查对应模式是否已由服务器使能 */
ConfigCommon_t tc; Config2D_t t2; Config1D_t t1;
uint8_t mode_enabled = 0;
if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0) {
if (g_temp_req_is2d && t2.Enabled)
mode_enabled = 1;
else if (!g_temp_req_is2d && t1.Enabled)
mode_enabled = 1;
}
if (!mode_enabled) {
DBG_APP("TEMP_REQ ignored: %s not enabled\r\n",
g_temp_req_is2d ? "2D" : "1D");
} else {
RawImageBuffer_t raw_img;
raw_img.pData = (uint16_t *)FrameBuffer;
raw_img.Width = SENSOR_WIDTH;
raw_img.Height = SENSOR_HEIGHT;
raw_img.FrameNumber = Ready_Frame_Count;
TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B;
use_buffer_A = !use_buffer_A;
tx_buf->ValidPayloadLen = 0;
if (g_temp_req_is2d) {
PreprocessResult_t meta;
int8_t pp_ret = Preprocess_Execute(&raw_img, tx_buf, &meta);
if (pp_ret == 0) {
int8_t tx_ret = TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x02 /* REQUEST */, 1);
DBG_APP("TEMP_REQ SEND 2D frm=%d %dx%d ret=%d\r\n",
(int)meta.FrameNumber, (int)meta.ValidWidth,
(int)meta.ValidHeight, (int)tx_ret);
} else {
DBG_APP("TEMP_REQ PP fail ret=%d\r\n", (int)pp_ret);
}
} else {
send_1d_frame_from_raw(&raw_img, tx_buf);
DBG_APP("TEMP_REQ SEND 1D frm=%d\r\n", (int)raw_img.FrameNumber);
}
} /* mode_enabled */
}
#endif /* TEST_ENABLE_TEMP_REQ */
if (Frame_Ready_Flag)
{
Frame_Ready_Flag = 0;
RawImageBuffer_t raw_img;
raw_img.pData = (uint16_t *)FrameBuffer;
raw_img.Width = SENSOR_WIDTH;
raw_img.Height = SENSOR_HEIGHT;
raw_img.FrameNumber = Ready_Frame_Count;
#if TEST_ENABLE_TRIGGER
/* 触发逻辑仅在服务器下发 Config2D 且 Enabled=1 后才激活 */
{
ConfigCommon_t tc; Config2D_t t2; Config1D_t t1;
uint8_t trigger_armed = 0;
if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0 && t2.Enabled)
trigger_armed = 1;
if (trigger_armed)
{
if (burst_active)
{
/* Burst mode: wait for interval, then capture & send */
if (sys_tick_ms >= burst_next_time_ms)
{
PreprocessResult_t meta;
TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B;
use_buffer_A = !use_buffer_A;
tx_buf->ValidPayloadLen = 0;
if (Preprocess_Execute(&raw_img, tx_buf, &meta) == 0)
{
TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 1);
}
burst_remaining--;
if (burst_remaining == 0) {
burst_active = 0;
DBG_APP("Burst complete\r\n");
} else {
ConfigCommon_t tc; Config2D_t t2; Config1D_t t1;
uint16_t interval_ms = DEFAULT_BURST_INTERVAL_MS;
if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0
&& t2.TriggerInternalIntervalMs > 0)
interval_ms = t2.TriggerInternalIntervalMs;
burst_next_time_ms = sys_tick_ms + interval_ms;
}
}
}
else if (Preprocess_CheckInternalTrigger2D(&raw_img) == 1)
{
/* Trigger hit! Start burst: this frame is frame #0 */
DBG_APP("TRIGGER frm=%d\r\n", (int)raw_img.FrameNumber);
PreprocessResult_t meta;
TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B;
use_buffer_A = !use_buffer_A;
tx_buf->ValidPayloadLen = 0;
int8_t pp_ret = Preprocess_Execute(&raw_img, tx_buf, &meta);
if (pp_ret == 0)
{
int8_t tx_ret = TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 1);
DBG_APP("SEND frm=%d %dx%d ret=%d\r\n",
(int)meta.FrameNumber, (int)meta.ValidWidth,
(int)meta.ValidHeight, (int)tx_ret);
} else {
DBG_APP("PP fail ret=%d\r\n", (int)pp_ret);
}
/* Determine burst count from config (fall back to defaults) */
ConfigCommon_t tc; Config2D_t t2; Config1D_t t1;
uint8_t total_burst = DEFAULT_BURST_COUNT;
uint16_t interval_ms = DEFAULT_BURST_INTERVAL_MS;
if (TcpLogic_GetLatestConfig(&tc, &t2, &t1) == 0) {
if (t2.TriggerBurstCount >= 1)
total_burst = t2.TriggerBurstCount;
if (t2.TriggerInternalIntervalMs > 0)
interval_ms = t2.TriggerInternalIntervalMs;
}
if (total_burst > 1) {
burst_active = 1;
burst_remaining = total_burst - 1; /* frame #0 already sent */
burst_next_time_ms = sys_tick_ms + interval_ms;
DBG_APP("Burst start: %d frames, interval=%d ms\r\n",
(int)total_burst, (int)interval_ms);
}
}
} /* if trigger_armed */
} /* trigger scope */
#endif /* TEST_ENABLE_TRIGGER */
}
else
{
vTaskDelay(pdMS_TO_TICKS(2));
}
}
}
int main(void)
{
u8 i;
SystemCoreClockUpdate();
Delay_Init();
USART_Printf_Init(921600);
printf("TCPClient Test\r\nSystemClk:%d\r\n", SystemCoreClock);
printf("=== Feature Switches ===\r\n");
printf(" PATTERN=%d TRIGGER=%d NG_GPIO=%d TEMP_REQ=%d TCP_SEND=%d HB=%d\r\n",
TEST_PATTERN_MODE, TEST_ENABLE_TRIGGER, TEST_ENABLE_NG_GPIO,
TEST_ENABLE_TEMP_REQ, TEST_ENABLE_TCP_SEND, TEST_ENABLE_HEARTBEAT);
printf("UserByte: %02x\r\n", FLASH_GetUserOptionByte() & 0xFF);
Config_Flash_SRAM(FLASH_128_SRAM_192);
printf("net version:%x\n", WCHNET_GetVer());
if (WCHNET_LIB_VER != WCHNET_GetVer()) printf("version error.\n");
WCHNET_GetMacAddr(MACAddr);
printf("mac addr:");
for (i = 0; i < 6; i++) printf("%x ", MACAddr[i]);
printf("\n");
#if TEST_PATTERN_MODE
printf("=== TEST PATTERN MODE === No sensor/DVP hardware needed\r\n");
/* Skip Mini212G2_Init() and DVP_Init() */
#else
/* 模组已通过 USB 预配置,无需 MCU UART 配置,直接初始化 DVP */
DVP_Init();
#endif
TIM2_Init();
#if TEST_ENABLE_NG_GPIO
NG_GPIO_Init();
#endif
i = ETH_LibInit(IPAddr, GWIPAddr, IPMask, MACAddr);
mStopIfError(i);
if (i == WCHNET_ERR_SUCCESS) printf("WCHNET_LibInit Success\r\n");
#if KEEPALIVE_ENABLE
{
struct _KEEP_CFG cfg = {20000, 15000, 9};
WCHNET_ConfigKeepLive(&cfg);
}
#endif
qdx_port_init();
Preprocess_Init(SENSOR_WIDTH, SENSOR_HEIGHT);
#if TEST_PATTERN_MODE
/* Set default preprocess config so trigger fires with test patterns.
* Without this, all Config2D_t fields are 0 and trigger never fires
* (roi_w==0 → immediate return 0). */
{
Config2D_t test_cfg2d;
memset(&test_cfg2d, 0, sizeof(test_cfg2d));
test_cfg2d.TargetWidth = 64; /* ROI: 64x64 = 8KB, fits in 10KB buffer */
test_cfg2d.TargetHeight = 64;
test_cfg2d.TriggerRoiX = 0; /* Full-frame trigger area */
test_cfg2d.TriggerRoiY = 0;
test_cfg2d.TriggerRoiW = SENSOR_WIDTH;
test_cfg2d.TriggerRoiH = SENSOR_HEIGHT;
test_cfg2d.TriggerCondition = 1; /* Max temperature */
test_cfg2d.TriggerTemperatureThreshold = 800; /* 80.0°C (0.1°C/LSB) */
test_cfg2d.TriggerBurstCount = 3;
test_cfg2d.TriggerInternalIntervalMs = 200;
test_cfg2d.NGioDelay = 200;
Preprocess_Settings_Change(&test_cfg2d, NULL, NULL);
printf("Test default config loaded: trigger thresh=800 ROI=full burst=3\r\n");
}
#endif
TcpLogic_Init(MACAddr, NULL);
TcpLogic_RegisterConfigCallback(OnConfigUpdate);
#if TEST_ENABLE_NG_GPIO
TcpLogic_RegisterDetectionCallback(OnDetectionResult);
#endif
#if TEST_ENABLE_TEMP_REQ
TcpLogic_RegisterTempFrameRequestCallback(OnTempFrameRequest);
#endif
DBG_APP("TcpLogic_Start...\r\n");
TcpLogic_Start();
DBG_APP("Creating RTOS tasks...\r\n");
xTaskCreate(task_wchnet_entry, "wchnet", 512, NULL, 6, NULL);
#if TEST_ENABLE_TCP_SEND
xTaskCreate(task_business_entry, "business", 512, NULL, 5, NULL);
#endif
#if TEST_ENABLE_HEARTBEAT
xTaskCreate(task_heartbeat_entry, "hb", 256, NULL, 3, NULL);
#endif
#if TEST_PATTERN_MODE
xTaskCreate(task_test_pattern_entry, "testpat", 256, NULL, 4, NULL);
#endif
DBG_APP("Starting scheduler\r\n");
vTaskStartScheduler();
/* Should never reach here */
while (1) {}
}