建立 MAG160C 逆向工程交接仓库
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
/* Public API / session tests (no-libusb build path). */
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(void) {
|
||||
assert(mag160c_init(NULL) == MAG160C_ERR_INVALID_ARGUMENT);
|
||||
|
||||
mag160c_ctx_t *ctx = NULL;
|
||||
assert(mag160c_init(&ctx) == MAG160C_OK);
|
||||
assert(ctx != NULL);
|
||||
|
||||
mag160c_ir_t *ir = NULL;
|
||||
mag160c_error_t rc = mag160c_ir_open(ctx, &ir);
|
||||
|
||||
#if !MAG160C_HAS_LIBUSB
|
||||
assert(rc == MAG160C_ERR_NOT_SUPPORTED);
|
||||
assert(strstr(mag160c_last_error(), "libusb") != NULL);
|
||||
#else
|
||||
if (rc == MAG160C_OK) {
|
||||
assert(ir != NULL);
|
||||
mag160c_ir_info_t info;
|
||||
assert(mag160c_ir_get_info(ir, &info) == MAG160C_OK);
|
||||
assert(info.width == 160 || info.width != 0);
|
||||
mag160c_ir_close(ir);
|
||||
}
|
||||
#endif
|
||||
|
||||
mag160c_shutdown(ctx);
|
||||
|
||||
/* pure helpers must work regardless of transport */
|
||||
uint8_t out[64];
|
||||
size_t size = 0;
|
||||
assert(mag160c_tcm_rotate(5, out, sizeof(out), &size) == MAG160C_OK);
|
||||
assert(size == 11);
|
||||
|
||||
printf("test_api: all passed\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/* Tests for the display pipeline module (bad map, AGC, FFC scheduler, cal). */
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "mag160c/mag160c.h"
|
||||
#include "mag160c/mag160c_display.h"
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
#define CHECK(cond, msg) do { \
|
||||
if (!(cond)) { printf("FAIL: %s\n", msg); failures++; } \
|
||||
else { printf("ok: %s\n", msg); } \
|
||||
} while (0)
|
||||
|
||||
static void test_badmap(void) {
|
||||
/* 4x3 synthetic frame with two bad pixels and a 2-pixel cluster */
|
||||
enum { W = 4, H = 3 };
|
||||
mag160c_display_badmap_t m;
|
||||
mag160c_display_badmap_init(&m, W, H);
|
||||
m.temporal_thr = 50;
|
||||
|
||||
/* pixel (1,0): bright defect (value 5000 vs peak 100) - every frame */
|
||||
/* pixels (2,1),(3,1): temporal fluctuation cluster */
|
||||
uint16_t f2[H * W];
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
for (int p = 0; p < H * W; ++p) f2[p] = 100;
|
||||
f2[0 * W + 1] = 5000;
|
||||
f2[1 * W + 2] = 100 + (i % 2 ? 80 : 0);
|
||||
f2[1 * W + 3] = 100 + (i % 2 ? 80 : 0);
|
||||
mag160c_display_badmap_feed(&m, f2);
|
||||
}
|
||||
mag160c_error_t e = mag160c_display_badmap_finalize(&m);
|
||||
CHECK(e == MAG160C_OK, "badmap finalize ok");
|
||||
CHECK(m.ready == 1, "badmap ready");
|
||||
CHECK(m.bad[0 * W + 1] == 1, "bright defect detected");
|
||||
CHECK(m.bad[1 * W + 2] == 1 && m.bad[1 * W + 3] == 1, "temporal cluster detected");
|
||||
CHECK(m.bad_count == 3, "bad_count == 3");
|
||||
/* correction must fill bad pixels with neighbour means */
|
||||
for (int p = 0; p < H * W; ++p) f2[p] = 100;
|
||||
f2[0 * W + 1] = 5000;
|
||||
f2[1 * W + 2] = 180;
|
||||
f2[1 * W + 3] = 180;
|
||||
mag160c_display_badmap_correct(&m, f2);
|
||||
CHECK(f2[0 * W + 1] == 100, "bright defect corrected to neighbour mean");
|
||||
CHECK(f2[1 * W + 2] == 100, "cluster pixel corrected");
|
||||
CHECK(f2[1 * W + 3] == 100, "cluster pixel 2 corrected");
|
||||
CHECK(m.ref[1 * W + 2] == 100, "reference cluster filled");
|
||||
mag160c_display_badmap_destroy(&m);
|
||||
}
|
||||
|
||||
static void test_agc(void) {
|
||||
/* 1000 samples: 980 background 1000..1979, 10 hot outliers 40000+,
|
||||
* 10 zeros. Percentile [2,98] must exclude the outliers. */
|
||||
uint16_t frame[1000];
|
||||
for (int i = 0; i < 980; ++i) frame[i] = (uint16_t)(1000 + i % 980);
|
||||
for (int i = 0; i < 10; ++i) frame[980 + i] = (uint16_t)(40000 + i * 100);
|
||||
frame[990] = 0;
|
||||
frame[991] = 0;
|
||||
uint32_t lo = 0, hi = 0;
|
||||
mag160c_error_t e = mag160c_display_agc_range(frame, 1000, 2, 98, &lo, &hi);
|
||||
CHECK(e == MAG160C_OK, "agc_range ok");
|
||||
CHECK(hi < 40000, "agc excludes hot defect outliers");
|
||||
CHECK(lo >= 1000 && lo <= 1100, "agc low percentile sane");
|
||||
|
||||
int32_t diff[8] = {0, 5, 10, -10, 100, -100, 1000, -1000};
|
||||
uint32_t span = 0;
|
||||
e = mag160c_display_diff_span(diff, 8, 120, 4000, &span);
|
||||
CHECK(e == MAG160C_OK, "diff_span ok");
|
||||
CHECK(span > 200 && span <= 4000, "diff span adaptive");
|
||||
}
|
||||
|
||||
static void test_ffc(void) {
|
||||
mag160c_ffc_scheduler_t s;
|
||||
mag160c_ffc_scheduler_init(&s, 400, 9);
|
||||
int sent0 = 0, sent1 = 0;
|
||||
for (int f = 0; f < 900; ++f) {
|
||||
int32_t p = mag160c_ffc_scheduler_tick(&s);
|
||||
if (p == 0) sent0++;
|
||||
if (p == 1) sent1++;
|
||||
}
|
||||
CHECK(sent1 >= 2, "ffc scheduler sends FFC(1) (initial + pair)");
|
||||
CHECK(sent0 >= 2, "ffc scheduler sends FFC(0) periodically");
|
||||
}
|
||||
|
||||
static void test_ffc_trigger(void) {
|
||||
/* manual trigger: FFC(0) now, FFC(1) exactly `gap` frames later */
|
||||
mag160c_ffc_scheduler_t s;
|
||||
mag160c_ffc_scheduler_init(&s, 400, 9);
|
||||
int32_t p = mag160c_ffc_scheduler_trigger(&s);
|
||||
CHECK(p == 0, "trigger returns 0 (send FFC(0))");
|
||||
int frames_to_1 = 0;
|
||||
for (int f = 0; f < 20; ++f) {
|
||||
int32_t t = mag160c_ffc_scheduler_tick(&s);
|
||||
if (t == 1) {
|
||||
frames_to_1 = f + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK(frames_to_1 == 9, "FFC(1) comes exactly 9 frames after trigger");
|
||||
}
|
||||
|
||||
static void test_cal(void) {
|
||||
double a = 0, b = 0;
|
||||
mag160c_error_t e = mag160c_temp_calibrate_linear(11000, 0.0, 14000, 90.0, &a, &b);
|
||||
CHECK(e == MAG160C_OK, "linear cal ok");
|
||||
CHECK(a > 0.029 && a < 0.031, "slope ~0.03 C/count");
|
||||
CHECK(mag160c_temp_apply_linear(12500, a, b) > 44.9 &&
|
||||
mag160c_temp_apply_linear(12500, a, b) < 45.1, "apply midpoint 45C");
|
||||
e = mag160c_temp_calibrate_linear(11000, 0, 11000, 90, &a, &b);
|
||||
CHECK(e != MAG160C_OK, "identical counts rejected");
|
||||
}
|
||||
|
||||
static void test_ref_track(void) {
|
||||
/* hot object (1200 over bg) for 4500 frames must not be absorbed */
|
||||
enum { N = 8 };
|
||||
uint16_t ref[N], live[N];
|
||||
for (int i = 0; i < N; ++i) ref[i] = 11720;
|
||||
for (int i = 0; i < N; ++i) live[i] = 11720 + 1200;
|
||||
for (int f = 0; f < 4500; ++f)
|
||||
mag160c_display_ref_track(ref, live, N, 60, 32);
|
||||
int absorbed = 0;
|
||||
for (int i = 0; i < N; ++i) if (ref[i] != 11720) absorbed++;
|
||||
CHECK(absorbed == 0, "ref_track: static hot object not absorbed (no ghost)");
|
||||
|
||||
/* removal: diff ~0 */
|
||||
for (int i = 0; i < N; ++i) live[i] = 11720;
|
||||
long sum = 0;
|
||||
for (int i = 0; i < N; ++i) sum += (int)live[i] - (int)ref[i];
|
||||
CHECK(sum > -10 && sum < 10, "ref_track: no ghost after object removal");
|
||||
|
||||
/* slow drift still tracked */
|
||||
for (int f = 0; f < 2000; ++f) {
|
||||
for (int i = 0; i < N; ++i) live[i] = (uint16_t)(11723 + f % 10);
|
||||
mag160c_display_ref_track(ref, live, N, 60, 32);
|
||||
}
|
||||
long drift = 0;
|
||||
for (int i = 0; i < N; ++i) drift += (int)live[i] - (int)ref[i];
|
||||
CHECK(drift / N < 60, "ref_track: slow drift tracked");
|
||||
}
|
||||
|
||||
static void test_ref_heal(void) {
|
||||
enum { W2 = 160, H2 = 120 };
|
||||
enum { N = W2 * H2 };
|
||||
static uint16_t ref[N], live[N];
|
||||
static uint8_t fg[N];
|
||||
for (int i = 0; i < N; ++i) { ref[i] = 11720; live[i] = 11720; }
|
||||
|
||||
/* 40 isolated startup-noise pixels wrongly in the reference (+800) */
|
||||
int noise[40];
|
||||
for (int n = 0; n < 40; ++n) {
|
||||
int x = 10 + (n % 8) * 5, y = 10 + (n / 8) * 5;
|
||||
noise[n] = y * W2 + x;
|
||||
ref[noise[n]] = 11720 + 800;
|
||||
}
|
||||
for (int f = 0; f < 50; ++f)
|
||||
mag160c_display_ref_track_heal(ref, live, W2, H2, 60, 32, fg, 30, 2);
|
||||
int healed = 0;
|
||||
for (int n = 0; n < 40; ++n) if (ref[noise[n]] == 11720) healed++;
|
||||
CHECK(healed == 40, "heal: isolated startup noise pixels healed");
|
||||
|
||||
/* contiguous 60x40 hot object must stay frozen (not healed/absorbed) */
|
||||
for (int i = 0; i < N; ++i) { ref[i] = 11720; live[i] = 11720; fg[i] = 0; }
|
||||
for (int y = 40; y < 80; ++y)
|
||||
for (int x = 50; x < 110; ++x) live[y * W2 + x] = 11720 + 1200;
|
||||
for (int f = 0; f < 200; ++f)
|
||||
mag160c_display_ref_track_heal(ref, live, W2, H2, 60, 32, fg, 30, 2);
|
||||
int absorbed = 0;
|
||||
for (int y = 40; y < 80; ++y)
|
||||
for (int x = 50; x < 110; ++x)
|
||||
if (ref[y * W2 + x] > 11720 + 100) absorbed++;
|
||||
CHECK(absorbed == 0, "heal: contiguous object never absorbed/healed");
|
||||
}
|
||||
|
||||
static void test_nuc(void) {
|
||||
/* synthetic mura: background 11720 with a +5000 row-1 band and a
|
||||
* -3000 col-17 band; a hand (+1200 over a region) must survive NUC */
|
||||
enum { W2 = 160, H2 = 120 };
|
||||
enum { N = W2 * H2 };
|
||||
static uint16_t live[N], ref[N], out[N];
|
||||
for (int i = 0; i < N; ++i) {
|
||||
int x = i % W2, y = i / W2;
|
||||
uint16_t v = 11720;
|
||||
if (y == 1) v += 5000; /* mura row band */
|
||||
if (x == 17) v -= 3000; /* mura col band */
|
||||
ref[i] = v;
|
||||
live[i] = v;
|
||||
if (x >= 60 && x < 100 && y >= 40 && y < 80) live[i] += 1200; /* hand */
|
||||
}
|
||||
mag160c_error_t e = mag160c_display_nuc(live, ref, N, out);
|
||||
CHECK(e == MAG160C_OK, "nuc ok");
|
||||
/* mura removed: row1 and col17 pixels back near background */
|
||||
int r1 = out[1 * W2 + 50];
|
||||
int c17 = out[50 * W2 + 17];
|
||||
CHECK(r1 >= 11690 && r1 <= 11770, "nuc removes row mura band");
|
||||
CHECK(c17 >= 11690 && c17 <= 11770, "nuc removes col mura band");
|
||||
/* hand region still visible */
|
||||
int hand = out[50 * W2 + 80];
|
||||
CHECK(hand >= 11700 + 1100 && hand <= 11700 + 1300, "nuc keeps object");
|
||||
}
|
||||
|
||||
static void test_ref_rebase(void) {
|
||||
enum { N = 8 };
|
||||
uint16_t ref[N], live[N];
|
||||
for (int i = 0; i < N; ++i) { ref[i] = 11720; live[i] = 11720 + 800; }
|
||||
mag160c_error_t e = mag160c_display_ref_rebase(ref, live, N, 2000);
|
||||
CHECK(e == MAG160C_OK, "ref_rebase ok");
|
||||
long sum = 0;
|
||||
for (int i = 0; i < N; ++i) sum += (int)live[i] - (int)ref[i];
|
||||
CHECK(sum == 0, "ref_rebase removes global offset");
|
||||
/* single hot pixel (outlier) does not drag the median */
|
||||
live[0] = 11720 + 6000;
|
||||
mag160c_display_ref_rebase(ref, live, N, 2000);
|
||||
sum = 0;
|
||||
for (int i = 0; i < N; ++i) sum += (int)live[i] - (int)ref[i];
|
||||
CHECK(sum >= 0 && sum < 6000, "ref_rebase robust to single outlier");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_badmap();
|
||||
test_agc();
|
||||
test_ffc();
|
||||
test_ffc_trigger();
|
||||
test_ref_track();
|
||||
test_ref_heal();
|
||||
test_nuc();
|
||||
test_ref_rebase();
|
||||
test_cal();
|
||||
printf(failures ? "\nTEST_DISPLAY_FAILED (%d)\n" : "\nTEST_DISPLAY_PASSED\n",
|
||||
failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/* Frame parser tests against the recovered 0x1bb1b11b framing. */
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static void put32(uint8_t *p, uint32_t v) {
|
||||
p[0] = (uint8_t)v;
|
||||
p[1] = (uint8_t)(v >> 8);
|
||||
p[2] = (uint8_t)(v >> 16);
|
||||
p[3] = (uint8_t)(v >> 24);
|
||||
}
|
||||
|
||||
static void test_parse_valid(void) {
|
||||
uint8_t frame[0x40];
|
||||
memset(frame, 0xaa, sizeof(frame));
|
||||
put32(frame + 0x00, 0x1bb1b11b);
|
||||
put32(frame + 0x04, 42);
|
||||
put32(frame + 0x08, 4);
|
||||
put32(frame + 0x0c, 1);
|
||||
put32(frame + 0x10, 1000);
|
||||
frame[0x1c] = 0x34;
|
||||
frame[0x1d] = 0x12;
|
||||
put32(frame + 0x1c + 4, 0x1bb1b11c);
|
||||
|
||||
mag160c_frame_header_t h;
|
||||
const uint16_t *pixels = NULL;
|
||||
assert(mag160c_frame_parse(frame, sizeof(frame), &h, &pixels) == MAG160C_OK);
|
||||
assert(h.marker == 0x1bb1b11b);
|
||||
assert(h.frame_counter == 42);
|
||||
assert(h.data_length == 4);
|
||||
assert(h.frame_type == 1);
|
||||
assert(h.period_shutter == 1000);
|
||||
assert(h.trailing_marker == 0x1bb1b11c);
|
||||
assert(pixels[0] == 0x1234);
|
||||
}
|
||||
|
||||
static void test_parse_rejects(void) {
|
||||
uint8_t frame[0x40];
|
||||
memset(frame, 0, sizeof(frame));
|
||||
mag160c_frame_header_t h;
|
||||
const uint16_t *pixels = NULL;
|
||||
|
||||
memset(frame, 0, sizeof(frame));
|
||||
assert(mag160c_frame_parse(frame, sizeof(frame), &h, &pixels) ==
|
||||
MAG160C_ERR_BAD_FRAME);
|
||||
|
||||
put32(frame + 0x00, 0x1bb1b11b);
|
||||
put32(frame + 0x08, 4);
|
||||
put32(frame + 0x0c, 2); /* bad type */
|
||||
assert(mag160c_frame_parse(frame, sizeof(frame), &h, &pixels) ==
|
||||
MAG160C_ERR_BAD_FRAME);
|
||||
|
||||
put32(frame + 0x0c, 0);
|
||||
put32(frame + 0x1c + 4, 0xdeadbeef); /* bad trailer */
|
||||
assert(mag160c_frame_parse(frame, sizeof(frame), &h, &pixels) ==
|
||||
MAG160C_ERR_BAD_FRAME);
|
||||
}
|
||||
|
||||
static void test_stream_split(void) {
|
||||
uint8_t frame[0x40];
|
||||
memset(frame, 0xaa, sizeof(frame));
|
||||
put32(frame + 0x00, 0x1bb1b11b);
|
||||
put32(frame + 0x04, 7);
|
||||
put32(frame + 0x08, 4);
|
||||
put32(frame + 0x0c, 1);
|
||||
frame[0x1c] = 0x78;
|
||||
frame[0x1d] = 0x56;
|
||||
put32(frame + 0x1c + 4, 0x1bb1b11c);
|
||||
|
||||
mag160c_frame_stream_t s;
|
||||
mag160c_frame_stream_init(&s, 0x40);
|
||||
assert(s.buf != NULL);
|
||||
|
||||
uint8_t out[0x100];
|
||||
size_t len = 99;
|
||||
const size_t total = 0x38 + 4; /* 0x3c */
|
||||
|
||||
/* feed one byte at a time; the frame completes exactly at total bytes */
|
||||
for (size_t i = 0; i + 1 < total; ++i) {
|
||||
len = 99;
|
||||
assert(mag160c_frame_stream_push(&s, frame + i, 1, out, sizeof(out), &len) ==
|
||||
MAG160C_OK);
|
||||
assert(len == 0);
|
||||
}
|
||||
len = 99;
|
||||
assert(mag160c_frame_stream_push(&s, frame + total - 1, 1, out, sizeof(out),
|
||||
&len) == MAG160C_OK);
|
||||
assert(len == total);
|
||||
|
||||
mag160c_frame_header_t h;
|
||||
const uint16_t *pixels = NULL;
|
||||
assert(mag160c_frame_parse(out, len, &h, &pixels) == MAG160C_OK);
|
||||
assert(h.frame_counter == 7);
|
||||
assert(pixels[0] == 0x5678);
|
||||
|
||||
/* trailing 4 bytes of the 0x40-byte test frame must not form a frame */
|
||||
len = 99;
|
||||
assert(mag160c_frame_stream_push(&s, frame + total, 4, out, sizeof(out),
|
||||
&len) == MAG160C_OK);
|
||||
assert(len == 0);
|
||||
|
||||
mag160c_frame_stream_destroy(&s);
|
||||
}
|
||||
|
||||
static void test_stream_resync(void) {
|
||||
uint8_t frame[0x40];
|
||||
memset(frame, 0xaa, sizeof(frame));
|
||||
put32(frame + 0x00, 0x1bb1b11b);
|
||||
put32(frame + 0x08, 4);
|
||||
put32(frame + 0x0c, 0);
|
||||
frame[0x1c] = 0x11;
|
||||
frame[0x1d] = 0x22;
|
||||
put32(frame + 0x1c + 4, 0x1bb1b11c);
|
||||
|
||||
mag160c_frame_stream_t s;
|
||||
mag160c_frame_stream_init(&s, 0x40);
|
||||
|
||||
/* garbage prefix (4-byte aligned) then the frame */
|
||||
const uint8_t garbage[] = {0x00, 0xff, 0x12, 0x34};
|
||||
uint8_t out[0x100];
|
||||
size_t len = 0;
|
||||
assert(mag160c_frame_stream_push(&s, garbage, sizeof(garbage), out, sizeof(out),
|
||||
&len) == MAG160C_OK);
|
||||
assert(len == 0);
|
||||
assert(mag160c_frame_stream_push(&s, frame, sizeof(frame), out, sizeof(out),
|
||||
&len) == MAG160C_OK);
|
||||
assert(len == 0x38 + 4);
|
||||
mag160c_frame_stream_destroy(&s);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_parse_valid();
|
||||
test_parse_rejects();
|
||||
test_stream_split();
|
||||
test_stream_resync();
|
||||
printf("test_frame: all passed\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/* TCM framing tests (0x7e FTDICommand format). */
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static void test_rotate_frame(void) {
|
||||
/* vendor byte-exact: 7e 00 07 85 02 77 00 01 00 05 7f */
|
||||
const uint8_t expected[] = {0x7e, 0x00, 0x07, 0x85, 0x02, 0x77,
|
||||
0x00, 0x01, 0x00, 0x05, 0x7f};
|
||||
uint8_t out[64];
|
||||
size_t size = 0;
|
||||
assert(mag160c_tcm_rotate(5, out, sizeof(out), &size) == MAG160C_OK);
|
||||
assert(size == sizeof(expected));
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
assert(out[i] == expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_light_frame(void) {
|
||||
/* blink green: 7e 00 09 87 02 32 00 01 01 00 ff 00 35 */
|
||||
const uint8_t expected[] = {0x7e, 0x00, 0x09, 0x87, 0x02, 0x32, 0x00,
|
||||
0x01, 0x01, 0x00, 0xff, 0x00, 0x35};
|
||||
uint8_t out[64];
|
||||
size_t size = 0;
|
||||
assert(mag160c_tcm_light(MAG160C_TCM_LIGHT_GREEN, MAG160C_TCM_LIGHT_BLINK,
|
||||
out, sizeof(out), &size) == MAG160C_OK);
|
||||
assert(size == sizeof(expected));
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
assert(out[i] == expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_decode_roundtrip(void) {
|
||||
const uint8_t payload[] = {0x10, 0x20, 0x30};
|
||||
uint8_t enc[64];
|
||||
size_t enc_size = 0;
|
||||
assert(mag160c_tcm_encode(0xa1, 0xb2, 0xc3d4, payload, sizeof(payload),
|
||||
enc, sizeof(enc), &enc_size) == MAG160C_OK);
|
||||
|
||||
uint8_t main = 0, sub = 0, pl[16];
|
||||
uint16_t frame_id = 0;
|
||||
size_t pl_size = 0;
|
||||
assert(mag160c_tcm_decode(enc, enc_size, &main, &sub, &frame_id, pl,
|
||||
sizeof(pl), &pl_size) == MAG160C_OK);
|
||||
assert(main == 0xa1 && sub == 0xb2 && frame_id == 0xc3d4);
|
||||
assert(pl_size == 3 && pl[0] == 0x10 && pl[1] == 0x20 && pl[2] == 0x30);
|
||||
|
||||
enc[3] ^= 0xff; /* corrupt header checksum */
|
||||
assert(mag160c_tcm_decode(enc, enc_size, &main, &sub, &frame_id, pl,
|
||||
sizeof(pl), &pl_size) == MAG160C_ERR_CHECKSUM);
|
||||
}
|
||||
|
||||
static void test_rotate_clamp(void) {
|
||||
uint8_t out[64];
|
||||
size_t size = 0;
|
||||
assert(mag160c_tcm_rotate(-200, out, sizeof(out), &size) == MAG160C_OK);
|
||||
/* direction=1, magnitude=128 */
|
||||
assert(out[8] == 1 && out[9] == 128);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_rotate_frame();
|
||||
test_light_frame();
|
||||
test_decode_roundtrip();
|
||||
test_rotate_clamp();
|
||||
printf("test_tcm: all passed\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/* Temperature conversion tests against the recovered CFunctions math. */
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static void test_calibrate_band_select(void) {
|
||||
const uint16_t frame[4] = {1000, 2000, 3000, 4000};
|
||||
const int16_t thresh[4] = {5, 5, 5, 5};
|
||||
const uint16_t pwl[16] = {
|
||||
0, 100, 0, 100, 0, 100, 0, 100,
|
||||
0, 200, 0, 200, 0, 200, 0, 200,
|
||||
};
|
||||
mag160c_temp_tables_t t;
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.thresholds = thresh;
|
||||
t.pwl = pwl;
|
||||
t.pixel_count = 4;
|
||||
t.band_count = 2;
|
||||
|
||||
uint16_t out[4];
|
||||
mag160c_temp_calibrate(frame, &t, out);
|
||||
/* diff = frame>>1 = 500..2000 all > 5 -> band 1 -> 200 */
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
assert(out[i] == 200);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_calibrate_interp_clamp(void) {
|
||||
const uint16_t frame[2] = {2000, 0x8000};
|
||||
const uint16_t baseline[2] = {0, 0x100};
|
||||
const int16_t thresh[2] = {500, 500};
|
||||
const uint16_t pwl[8] = {
|
||||
0x1000, 1000, 0x1000, 1000,
|
||||
0x0100, 2000, 0x0100, 2000,
|
||||
};
|
||||
mag160c_temp_tables_t t;
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.thresholds = thresh;
|
||||
t.pwl = pwl;
|
||||
t.pixel_count = 2;
|
||||
t.band_count = 2;
|
||||
t.baseline = baseline;
|
||||
t.has_baseline = 1;
|
||||
|
||||
uint16_t out[2];
|
||||
mag160c_temp_calibrate(frame, &t, out);
|
||||
assert(out[0] == 2062); /* 2000 + (1000*256>>12) */
|
||||
assert(out[1] == 3016); /* 2000 + (16256*256>>12) */
|
||||
|
||||
/* negative diff: (int16)(100-200)>>1 = -50 -> 1000 + (-50*4096>>12) = 950 */
|
||||
const uint16_t f2[1] = {100};
|
||||
const uint16_t b2[1] = {200};
|
||||
mag160c_temp_tables_t t2;
|
||||
memset(&t2, 0, sizeof(t2));
|
||||
t2.thresholds = thresh;
|
||||
t2.pwl = pwl;
|
||||
t2.pixel_count = 1;
|
||||
t2.band_count = 2;
|
||||
t2.baseline = b2;
|
||||
t2.has_baseline = 1;
|
||||
uint16_t out2[1];
|
||||
mag160c_temp_calibrate(f2, &t2, out2);
|
||||
assert(out2[0] == 950);
|
||||
}
|
||||
|
||||
static void test_convert_response(void) {
|
||||
const uint16_t frame[4] = {100, 200, 300, 400};
|
||||
const uint16_t baseline[4] = {0, 0, 0, 0};
|
||||
mag160c_temp_gain_t cfg;
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
cfg.gain = 0;
|
||||
cfg.shift = 0;
|
||||
cfg.coeff = 4;
|
||||
cfg.baseline = baseline;
|
||||
|
||||
uint16_t out[4];
|
||||
uint32_t sat = mag160c_temp_convert_response(frame, 4, &cfg, out);
|
||||
/* base = (50000-0)>>0 = 50000 (< 0xffff, not clamped); v = 50000 + diff*4 */
|
||||
assert(sat == 0);
|
||||
assert(out[0] == 50400 && out[1] == 50800 && out[2] == 51200 && out[3] == 51600);
|
||||
|
||||
mag160c_temp_gain_t cfg2;
|
||||
memset(&cfg2, 0, sizeof(cfg2));
|
||||
cfg2.gain = 49900; /* base = (50000-49900)>>0 = 100 */
|
||||
cfg2.shift = 0;
|
||||
cfg2.coeff = 1;
|
||||
cfg2.baseline = NULL;
|
||||
sat = mag160c_temp_convert_response(frame, 4, &cfg2, out);
|
||||
assert(sat == 0);
|
||||
assert(out[0] == 200 && out[1] == 300 && out[2] == 400 && out[3] == 500);
|
||||
}
|
||||
|
||||
static void test_t2e(void) {
|
||||
/* index 0 = value where (v+0xc350)>>13 == 0 -> v = -0xc350 */
|
||||
assert(mag160c_temp_t2e_interp(-0xc350) == 1000);
|
||||
/* at -0xc350 + 8192, idx=1 -> 0x4d3 = 1235 */
|
||||
assert(mag160c_temp_t2e_interp(-0xc350 + 8192) == 0x4d3);
|
||||
/* monotonic curve sanity (avoid int32 overflow; vendor math wraps) */
|
||||
const int32_t a = mag160c_temp_t2e_interp(-0xc350);
|
||||
const int32_t b = mag160c_temp_t2e_interp(0x40000);
|
||||
assert(a < b);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_calibrate_band_select();
|
||||
test_calibrate_interp_clamp();
|
||||
test_convert_response();
|
||||
test_t2e();
|
||||
printf("test_temp: all passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user