新增平台无关渲染模块 mag160c_render + Linux 参考程序 + 移植评估文档

- csdk/include/mag160c/mag160c_render.h + csdk/src/mag160c_render.c:
  官方渲染管线(快门提取/FFC 状态机/ref 采集/表重建/NUC/盲元/窗口/
  LUT(0x8c)/gray/2x/调色板)封装为无平台依赖 API,FFC 命令经回调发出
- csdk/tools/mag160c_linux_demo.c:Linux 风格参考程序(libusb + render
  API + 显示后端抽象 fbdev/DRM/LVGL),Windows 可编译验证(BMP 输出)
- 修复帧组装:第二段 bulk 读 38428B(像素+尾),尾部含快门字
  实测 shutter=31492 sel=2 stats 正常
- docs/linux_port_plan.md:芯片选型(推荐 T113-S3 QFP128 非 BGA ~20元)、
  动画库(LVGL+rlottie 参考 LiThermal)、显示方案、接口抽象设计
- csdk/README 增加 Linux 移植章节
This commit is contained in:
ZXCLI
2026-08-14 17:08:38 +08:00
parent df6c6dd444
commit 5c22bfe78c
6 changed files with 1154 additions and 2 deletions
+217
View File
@@ -0,0 +1,217 @@
/* mag160c_linux_demo.c - Linux-oriented reference app.
*
* Uses the platform-independent mag160c_render API. On Windows this
* builds/runs too (no GDI): output is written to thermal.bmp every 30
* frames. On Linux the same code path is used; only the display
* backend differs (fbdev/DRM/LVGL - see DISPLAY_BACKEND below).
*
* Build (Windows, same toolchain as demo3):
* gcc -O2 -w -DMAG160C_STATIC -I"csdk\third_party\libusb\win64"
* -I"csdk\include" -I"csdk\src" -o build-artifacts\mag160c_linux_demo.exe
* csdk\tools\mag160c_linux_demo.c csdk\src\mag160c_render.c
* csdk\src\mag160c_ir.c csdk\src\mag160c_frame.c csdk\src\mag160c_temp.c
* csdk\src\mag160c_tcm.c csdk\src\mag160c_error.c csdk\src\mag160c_display.c
* csdk\third_party\libusb\win64\libusb-1.0.x64.a
* Build (Linux):
* gcc -O2 -Wall csdk/tools/mag160c_linux_demo.c csdk/src/mag160c_render.c
* csdk/src/mag160c_ir.c csdk/src/mag160c_frame.c csdk/src/mag160c_temp.c
* csdk/src/mag160c_tcm.c csdk/src/mag160c_error.c csdk/src/mag160c_display.c
* -lusb-1.0 -lpthread -lm -o mag160c_linux_demo
*/
#define _WIN32_WINNT 0x0601
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#ifdef _WIN32
#include <windows.h>
#define SLEEP_MS(ms) Sleep(ms)
#else
#include <unistd.h>
#include <pthread.h>
#define SLEEP_MS(ms) usleep((ms) * 1000)
#endif
#include <libusb.h>
#include "mag160c/mag160c.h"
#include "mag160c/mag160c_render.h"
#include "mag160c/mag160c_display.h"
#define W 160
#define H 120
#define OUT_W 320
#define OUT_H 240
static libusb_context *g_ctx;
static libusb_device_handle *g_h;
static int sendcmd(unsigned magic, unsigned param, int len) {
unsigned char cmd[8] = {0};
cmd[0] = (unsigned char)(magic);
cmd[1] = (unsigned char)(magic >> 8);
cmd[2] = (unsigned char)(magic >> 16);
cmd[3] = (unsigned char)(magic >> 24);
if (len >= 8) {
cmd[4] = (unsigned char)(param);
cmd[5] = (unsigned char)(param >> 8);
cmd[6] = (unsigned char)(param >> 16);
cmd[7] = (unsigned char)(param >> 24);
}
int xfer = 0;
if (libusb_bulk_transfer(g_h, 0x03, cmd, len, &xfer, 2000)) return -1;
unsigned char resp[0x1000];
if (libusb_bulk_transfer(g_h, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
return 0;
}
static void ffc_cb(void *user, int param) {
(void)user;
if (g_h) sendcmd(0x6bb6b672, (unsigned)param, 8);
}
/* ---- display backend abstraction ----
* Windows: no-op (terminal stats). Linux: implement one of:
* A. fbdev: open("/dev/fb0"), mmap, memcpy RGB (convert to RGB565)
* B. DRM/KMS: drmModeSetCrtc + dumb buffer
* C. LVGL: lv_img_set_src + lv_obj_invalidate (best for UI/animation)
*/
static void display_rgb(const uint8_t *rgb, int w, int h) {
(void)rgb; (void)w; (void)h;
/* Linux example (fbdev RGB565):
* static int fd = -1; static unsigned char *fb; static int fbw, fbh;
* ... open/mmap once, then:
* for (y) for (x) { u16 v = ((rgb[y*w*3+x*3]>>3)<<11) |
* ((rgb[y*w*3+x*3+1]>>2)<<5) |
* (rgb[y*w*3+x*3+2]>>3);
* *(u16*)(fb + y*fbw*2 + x*2) = v; }
*/
}
static void save_bmp(const char *path, const uint8_t *rgb, int w, int h) {
uint32_t sz = 54 + (uint32_t)w * h * 3;
uint8_t bmp[54];
memset(bmp, 0, 54);
bmp[0] = 'B'; bmp[1] = 'M';
bmp[2] = (uint8_t)sz; bmp[3] = (uint8_t)(sz >> 8);
bmp[4] = (uint8_t)(sz >> 16); bmp[5] = (uint8_t)(sz >> 24);
bmp[10] = 54; bmp[14] = 40;
bmp[18] = (uint8_t)w; bmp[19] = (uint8_t)(w >> 8);
bmp[20] = (uint8_t)(w >> 16); bmp[21] = (uint8_t)(w >> 24);
bmp[22] = (uint8_t)h; bmp[23] = (uint8_t)(h >> 8);
bmp[24] = (uint8_t)(h >> 16); bmp[25] = (uint8_t)(h >> 24);
bmp[26] = 1; bmp[28] = 24;
FILE *f = fopen(path, "wb");
if (!f) return;
fwrite(bmp, 1, 54, f);
for (int y = h - 1; y >= 0; --y)
fwrite(rgb + (size_t)y * w * 3, 1, (size_t)w * 3, f);
fclose(f);
}
int main(int argc, char **argv) {
const char *ddt = argc > 1 ? argv[1] : "mag160c_official.ddt";
libusb_init(&g_ctx);
mag160c_render_cfg_t cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.width = W;
cfg.height = H;
cfg.ddt_path = ddt;
cfg.ffc_period = 1800;
cfg.ffc_drift = 250;
cfg.startup_force_ffc = 1;
cfg.warm_frames = 20;
cfg.cdf_pivot_75 = 1;
cfg.ffc_cb = ffc_cb;
mag160c_render_t *render = NULL;
if (mag160c_render_init(&render, &cfg) != MAG160C_OK) {
fprintf(stderr, "render init failed (need %s)\n", ddt);
return 1;
}
int ok = 0;
for (int attempt = 0; attempt < 4 && !ok; ++attempt) {
if (attempt > 0) {
if (g_h) libusb_reset_device(g_h);
if (g_h) { libusb_close(g_h); g_h = NULL; }
SLEEP_MS(2500);
}
libusb_device **list = NULL;
ssize_t cnt = libusb_get_device_list(g_ctx, &list);
for (ssize_t i = 0; i < cnt && !g_h; ++i) {
struct libusb_device_descriptor d;
libusb_get_device_descriptor(list[i], &d);
if (d.idVendor == 0x833c) libusb_open(list[i], &g_h);
}
libusb_free_device_list(list, 1);
if (!g_h) continue;
libusb_set_configuration(g_h, 2);
libusb_set_configuration(g_h, 1);
if (libusb_claim_interface(g_h, 0) != 0) { libusb_close(g_h); g_h = NULL; continue; }
if (sendcmd(0x6bb6b66b, 0, 4)) continue;
if (sendcmd(0x6bb6b66c, 0, 4)) continue;
if (sendcmd(0x6bb6b66f, 0, 4)) continue;
if (sendcmd(0x6bb6b672, 0, 8)) continue;
SLEEP_MS(100);
if (sendcmd(0x6bb6b672, 0, 8)) continue;
SLEEP_MS(300);
if (sendcmd(0x6bb6b673, 0, 4)) continue;
SLEEP_MS(700);
ok = 1;
}
if (!ok) {
fprintf(stderr, "camera init failed\n");
mag160c_render_destroy(render);
libusb_exit(g_ctx);
return 1;
}
static uint8_t rgb[OUT_W * OUT_H * 3];
static uint8_t frame[40000];
unsigned prev = 0xffffffff;
unsigned long nframe = 0;
for (;;) {
uint8_t hdr[64];
int xfer = 0;
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 500) || xfer < 28)
continue;
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) |
((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24);
if (m != 0x1bb1b11b) continue;
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
if (c == prev) continue;
prev = c;
if (libusb_bulk_transfer(g_h, 0x81, frame, sizeof(frame), &xfer, 500) || xfer < 38400)
continue;
nframe++;
/* assemble the full frame (28B header + pixels + tail) for the
* renderer: the second bulk read carries 38400 px + 28B tail */
static uint8_t full[28 + 40000];
memcpy(full, hdr, 28);
memcpy(full + 28, frame, 38400);
if (xfer > 38400)
memcpy(full + 28 + 38400, frame + 38400, (size_t)(xfer - 38400));
/* feed the platform-independent pipeline */
mag160c_error_t rc = mag160c_render_frame(render, full, 1, rgb);
if (rc != MAG160C_OK) continue; /* FFC window / startup: frozen */
display_rgb(rgb, OUT_W, OUT_H);
if (nframe % 30 == 0) {
char path[64];
snprintf(path, sizeof(path), "thermal_%05lu.bmp", nframe);
save_bmp(path, rgb, OUT_W, OUT_H);
int lo, hi, fmin, fmax, mean, std;
mag160c_render_get_stats(render, &lo, &hi, &fmin, &fmax, &mean, &std);
printf("f=%lu shutter=%d sel=%d win=[%d,%d] stats=(%d..%d m%d s%d) ref=%d\n",
nframe, mag160c_render_get_shutter(render),
mag160c_render_get_endpoint(render), lo, hi,
fmin, fmax, mean, std, mag160c_render_has_reference(render));
fflush(stdout);
}
}
mag160c_render_destroy(render);
libusb_close(g_h);
libusb_exit(g_ctx);
return 0;
}