建立 MAG160C 逆向工程交接仓库

This commit is contained in:
ZXCLI
2026-08-11 19:08:44 +08:00
commit 8409b27ba3
3135 changed files with 534408 additions and 0 deletions
+556
View File
@@ -0,0 +1,556 @@
/*
* MAG160C display pipeline - pure C, no OS/USB dependencies.
*
* Implements the pipeline validated on hardware (Windows demo v3) and
* informed by open-source thermal SDKs (SeekThermal/OpenThermal, FLIR
* Lepton AGC, MLX90640):
*
* frame -> bad pixel correction -> diff/NUC -> AGC -> LUT -> RGB
*
* 1. Bad pixel detection (Seek-style histogram peak deviation + temporal
* min/max) with topological-order 4-neighbour fill.
* 2. AGC: percentile stretch (FLIR LINEAR clip, 2%/98%) or adaptive diff
* stretch with deadband.
* 3. Ironbow pseudo-color LUT (FLIR-style anchors).
* 4. FFC scheduler replicating the official demo cadence recovered from
* analysis/captures/libusb0_trace.txt: FFC(1) after the ~10th frame
* (stream -> type=0), then every ffc_period frames send FFC(0) and
* ffc_gap frames later FFC(1). Verified: 1400+ type=0 frames, 15 fps,
* zero stalls.
* 5. Two-point linear temperature calibration (counts -> degC).
*/
#include "mag160c_internal.h"
#include "mag160c/mag160c_display.h"
#include <stdlib.h>
#include <string.h>
/* ------------------------------------------------------------------ */
/* bad pixel map */
void mag160c_display_badmap_init(mag160c_display_badmap_t *m, uint32_t w,
uint32_t h) {
if (m == NULL) {
return;
}
memset(m, 0, sizeof(*m));
m->width = w;
m->height = h;
m->pixels = w * h;
m->temporal_thr = 400; /* min/max fluctuation (counts) */
m->hist_min_dev = 200; /* histogram peak min deviation (counts) */
m->bad = (uint8_t *)calloc(m->pixels, 1);
m->order_x = (uint32_t *)malloc(m->pixels * sizeof(uint32_t));
m->order_y = (uint32_t *)malloc(m->pixels * sizeof(uint32_t));
m->ref_sum = (uint64_t *)calloc(m->pixels, sizeof(uint64_t));
m->ref_min = (uint16_t *)malloc(m->pixels * sizeof(uint16_t));
m->ref_max = (uint16_t *)malloc(m->pixels * sizeof(uint16_t));
m->ref = (uint16_t *)calloc(m->pixels, sizeof(uint16_t));
}
void mag160c_display_badmap_destroy(mag160c_display_badmap_t *m) {
if (m == NULL) {
return;
}
free(m->bad);
free(m->order_x);
free(m->order_y);
free(m->ref_sum);
free(m->ref_min);
free(m->ref_max);
free(m->ref);
memset(m, 0, sizeof(*m));
}
static int has_valid_neighbor(const uint8_t *bad, uint32_t w, uint32_t h,
uint32_t x, uint32_t y) {
return (x > 0 && !bad[y * w + x - 1]) ||
(x + 1 < w && !bad[y * w + x + 1]) ||
(y > 0 && !bad[(y - 1) * w + x]) ||
(y + 1 < h && !bad[(y + 1) * w + x]);
}
/* neighbour mean of frame at (x,y), skipping bad pixels; 0 when none. */
static uint32_t neighbor_mean(const uint16_t *fr, const uint8_t *bad,
uint32_t w, uint32_t h, uint32_t x, uint32_t y) {
uint64_t sum = 0;
uint32_t n = 0;
if (x > 0 && !bad[y * w + x - 1]) { sum += fr[y * w + x - 1]; n++; }
if (x + 1 < w && !bad[y * w + x + 1]) { sum += fr[y * w + x + 1]; n++; }
if (y > 0 && !bad[(y - 1) * w + x]) { sum += fr[(y - 1) * w + x]; n++; }
if (y + 1 < h && !bad[(y + 1) * w + x]) { sum += fr[(y + 1) * w + x]; n++; }
return n ? (uint32_t)(sum / n) : 0;
}
/* topological fill order so clusters are filled edge-first */
static void build_fill_order(mag160c_display_badmap_t *m) {
const uint32_t w = m->width, h = m->height, n = m->pixels;
uint8_t *work = (uint8_t *)malloc(n);
if (work == NULL) {
return;
}
memcpy(work, m->bad, n);
uint32_t remain = 0;
for (uint32_t i = 0; i < n; ++i) {
if (work[i]) remain++;
}
uint32_t len = 0;
while (remain > 0) {
uint32_t progress = 0;
for (uint32_t y = 0; y < h; ++y) {
for (uint32_t x = 0; x < w; ++x) {
if (!work[y * w + x]) continue;
if (!has_valid_neighbor(work, w, h, x, y)) continue;
m->order_x[len] = x;
m->order_y[len] = y;
len++;
work[y * w + x] = 0;
remain--;
progress++;
}
}
if (progress == 0) { /* fully isolated: force-fill in order */
for (uint32_t y = 0; y < h && progress == 0; ++y) {
for (uint32_t x = 0; x < w && progress == 0; ++x) {
if (!work[y * w + x]) continue;
m->order_x[len] = x;
m->order_y[len] = y;
len++;
work[y * w + x] = 0;
remain--;
progress++;
}
}
}
}
m->order_len = len;
free(work);
}
/* threshold = histPeak - (frameMax - histPeak) (Seek method).
* Guarded: a pixel is only flagged when it is also at least hist_min_dev
* counts above the scene peak, so a lone extreme outlier cannot push the
* threshold below the whole background. */
static uint32_t histogram_peak_threshold(const uint16_t *fr, uint32_t n,
uint32_t min_dev) {
uint32_t *hist = (uint32_t *)calloc(65536, sizeof(uint32_t));
if (hist == NULL) {
return 0xffffu;
}
uint32_t peakv = 0, peakc = 0, maxv = 0;
for (uint32_t i = 0; i < n; ++i) {
uint32_t v = fr[i];
if (++hist[v] > peakc) {
peakc = hist[v];
peakv = v;
}
if (v > maxv) maxv = v;
}
free(hist);
if (maxv <= peakv) {
return 0xffffu;
}
int64_t t = (int64_t)peakv - ((int64_t)maxv - (int64_t)peakv);
uint64_t guard = (uint64_t)peakv + min_dev;
return t > (int64_t)guard ? (uint32_t)t : (uint32_t)guard;
}
/* Feed frames for reference statistics (call ~30 times at idle scene). */
mag160c_error_t mag160c_display_badmap_feed(mag160c_display_badmap_t *m,
const uint16_t *frame) {
if (m == NULL || frame == NULL || m->bad == NULL) {
mag160c_set_error("mag160c_display_badmap_feed: null argument");
return MAG160C_ERR_INVALID_ARGUMENT;
}
const uint32_t n = m->pixels;
for (uint32_t i = 0; i < n; ++i) {
uint16_t v = frame[i];
m->ref_sum[i] += v;
if (m->feed_count == 0) {
m->ref_min[i] = m->ref_max[i] = v;
} else {
if (v < m->ref_min[i]) m->ref_min[i] = v;
if (v > m->ref_max[i]) m->ref_max[i] = v;
}
}
m->feed_count++;
return MAG160C_OK;
}
/* Finalize: averages, temporal + histogram bad pixel detection, fill. */
mag160c_error_t mag160c_display_badmap_finalize(mag160c_display_badmap_t *m) {
if (m == NULL || m->bad == NULL) {
mag160c_set_error("mag160c_display_badmap_finalize: null argument");
return MAG160C_ERR_INVALID_ARGUMENT;
}
if (m->feed_count == 0) {
mag160c_set_error("mag160c_display_badmap_finalize: no frames fed");
return MAG160C_ERR_NOT_READY;
}
const uint32_t n = m->pixels;
for (uint32_t i = 0; i < n; ++i) {
m->ref[i] = (uint16_t)(m->ref_sum[i] / m->feed_count);
m->bad[i] = 0;
}
uint32_t nb = 0;
/* temporal detection */
for (uint32_t i = 0; i < n; ++i) {
if ((uint32_t)m->ref_max[i] - (uint32_t)m->ref_min[i] > m->temporal_thr) {
m->bad[i] = 1;
nb++;
}
}
/* histogram peak deviation (bright defects) */
uint32_t thr = histogram_peak_threshold(m->ref, n, m->hist_min_dev);
for (uint32_t i = 0; i < n; ++i) {
if (!m->bad[i] && (uint32_t)m->ref[i] > thr) {
m->bad[i] = 1;
nb++;
}
}
m->bad_count = nb;
build_fill_order(m);
/* fill the reference image itself */
uint8_t *work = (uint8_t *)malloc(n);
if (work == NULL) {
return MAG160C_ERR_NO_MEMORY;
}
memcpy(work, m->bad, n);
for (uint32_t i = 0; i < m->order_len; ++i) {
uint32_t x = m->order_x[i], y = m->order_y[i];
uint32_t v = neighbor_mean(m->ref, work, m->width, m->height, x, y);
if (v > 0) {
m->ref[y * m->width + x] = (uint16_t)v;
work[y * m->width + x] = 0;
}
}
free(work);
m->ready = 1;
mag160c_clear_error();
return MAG160C_OK;
}
/* Correct one frame in place using the stored topological order. */
mag160c_error_t mag160c_display_badmap_correct(const mag160c_display_badmap_t *m,
uint16_t *frame) {
if (m == NULL || frame == NULL || !m->ready) {
mag160c_set_error("mag160c_display_badmap_correct: not ready");
return MAG160C_ERR_NOT_READY;
}
for (uint32_t i = 0; i < m->order_len; ++i) {
uint32_t x = m->order_x[i], y = m->order_y[i];
uint32_t v = neighbor_mean(frame, m->bad, m->width, m->height, x, y);
if (v > 0) frame[y * m->width + x] = (uint16_t)v;
}
mag160c_clear_error();
return MAG160C_OK;
}
/* MOG-style per-pixel reference tracking (anti-ghost):
* - |d| < thr : background pixel, slowly track drift (ref += d/alpha)
* - |d| >= thr : foreground (object), reference FROZEN - a static object
* is never absorbed, so moving it away leaves no ghost.
* Pass ref=null to just classify (not needed by callers today). */
void mag160c_display_ref_track(uint16_t *ref, const uint16_t *live,
uint32_t n, int32_t thr, int32_t alpha) {
if (ref == NULL || live == NULL || thr <= 0 || alpha <= 0) {
return;
}
for (uint32_t i = 0; i < n; ++i) {
int32_t d = (int32_t)live[i] - (int32_t)ref[i];
if (d > -thr && d < thr) {
ref[i] = (uint16_t)((int32_t)ref[i] + d / alpha);
}
}
}
/* MOG tracking with self-healing (anti-ghost + anti-startup-noise):
* fg_count[] counts consecutive foreground frames per pixel. A pixel stuck
* as *isolated* foreground (fewer than min_nbr foreground 8-neighbors) for
* more than heal_frames is a reference error (startup noise baked into the
* reference, bad pixel) rather than a real object (objects are contiguous)
* - it is reset to the live value so noise cannot persist forever. */
void mag160c_display_ref_track_heal(uint16_t *ref, const uint16_t *live,
uint32_t w, uint32_t h,
int32_t thr, int32_t alpha,
uint8_t *fg_count, uint32_t heal_frames,
uint32_t min_nbr) {
if (ref == NULL || live == NULL || w == 0 || h == 0 ||
thr <= 0 || alpha <= 0 || fg_count == NULL) {
return;
}
if (heal_frames == 0) heal_frames = 1;
const uint32_t n = w * h;
for (uint32_t i = 0; i < n; ++i) {
int32_t d = (int32_t)live[i] - (int32_t)ref[i];
if (d > -thr && d < thr) {
ref[i] = (uint16_t)((int32_t)ref[i] + d / alpha);
fg_count[i] = 0;
} else {
if (fg_count[i] < 255) fg_count[i]++;
if (fg_count[i] >= heal_frames) {
uint32_t x = i % w, y = i / w;
uint32_t nbr_fg = 0;
for (int32_t dy = -1; dy <= 1; ++dy) {
for (int32_t dx = -1; dx <= 1; ++dx) {
if (dx == 0 && dy == 0) continue;
int32_t xx = (int32_t)x + dx, yy = (int32_t)y + dy;
if (xx < 0 || (uint32_t)xx >= w ||
yy < 0 || (uint32_t)yy >= h) {
continue;
}
uint32_t nbr = (uint32_t)yy * w + (uint32_t)xx;
int32_t dn = (int32_t)live[nbr] - (int32_t)ref[nbr];
if (dn > thr || dn < -thr) nbr_fg++;
}
}
if (nbr_fg < min_nbr) {
ref[i] = live[i]; /* heal reference error */
fg_count[i] = 0;
}
}
}
}
}
/* Flat-field (NUC) correction: out[i] = live[i] - (ref[i] - mean(ref)).
* Removes the fixed sensor mura (measured row span 15704 -> 401 counts on
* the MAG160C), so the absolute temperature image is smooth. The reference
* is the fixed pattern only - it must NOT track the scene (see
* mag160c_display_ref_track_heal for the freeze/self-heal model and the
* object-free guard at capture time), otherwise objects ghost. */
mag160c_error_t mag160c_display_nuc(const uint16_t *live, const uint16_t *ref,
uint32_t n, uint16_t *out) {
if (live == NULL || ref == NULL || out == NULL || n == 0) {
mag160c_set_error("mag160c_display_nuc: null argument");
return MAG160C_ERR_INVALID_ARGUMENT;
}
uint64_t s = 0;
for (uint32_t i = 0; i < n; ++i) s += ref[i];
const int32_t mean = (int32_t)(s / n);
for (uint32_t i = 0; i < n; ++i) {
int64_t v = (int64_t)live[i] - ((int64_t)ref[i] - mean);
if (v < 0) v = 0;
if (v > 65535) v = 65535;
out[i] = (uint16_t)v;
}
mag160c_clear_error();
return MAG160C_OK;
}
/* Re-align the reference after FFC(1): the type=0 baseline shifts (measured
* up to +1000 counts). Applies the median of (live-ref) clamped to
* max_shift as a global offset, robust to a small object in the scene. */
mag160c_error_t mag160c_display_ref_rebase(uint16_t *ref, const uint16_t *live,
uint32_t n, int32_t max_shift) {
if (ref == NULL || live == NULL || n == 0) {
mag160c_set_error("mag160c_display_ref_rebase: null argument");
return MAG160C_ERR_INVALID_ARGUMENT;
}
int32_t *d = (int32_t *)malloc(n * sizeof(int32_t));
if (d == NULL) {
return MAG160C_ERR_NO_MEMORY;
}
for (uint32_t i = 0; i < n; ++i) {
int32_t v = (int32_t)live[i] - (int32_t)ref[i];
if (v > 4000) v = 4000;
if (v < -4000) v = -4000;
d[i] = v;
}
/* median */
for (uint32_t a = 1; a < n; ++a) {
int32_t key = d[a];
uint32_t b = a;
while (b > 0 && d[b - 1] > key) {
d[b] = d[b - 1];
b--;
}
d[b] = key;
}
int32_t off = d[n / 2];
if (off > max_shift) off = max_shift;
if (off < -max_shift) off = -max_shift;
free(d);
if (off != 0) {
for (uint32_t i = 0; i < n; ++i) {
int64_t v = (int64_t)ref[i] + off;
if (v < 0) v = 0;
if (v > 65535) v = 65535;
ref[i] = (uint16_t)v;
}
}
mag160c_clear_error();
return MAG160C_OK;
}
/* ------------------------------------------------------------------ */
/* AGC */
/* Percentile stretch: find [lo_pct, hi_pct] value range of frame. */
mag160c_error_t mag160c_display_agc_range(const uint16_t *frame, uint32_t n,
uint32_t lo_pct, uint32_t hi_pct,
uint32_t *out_lo, uint32_t *out_hi) {
if (frame == NULL || out_lo == NULL || out_hi == NULL) {
mag160c_set_error("mag160c_display_agc_range: null argument");
return MAG160C_ERR_INVALID_ARGUMENT;
}
if (lo_pct >= hi_pct || hi_pct > 100) {
mag160c_set_error("mag160c_display_agc_range: bad percentiles");
return MAG160C_ERR_INVALID_ARGUMENT;
}
uint32_t hist[65536];
memset(hist, 0, sizeof(hist));
uint32_t nz = 0;
for (uint32_t i = 0; i < n; ++i) {
if (frame[i] == 0) continue;
hist[frame[i]]++;
nz++;
}
if (nz == 0) {
*out_lo = 0;
*out_hi = 65535;
return MAG160C_OK;
}
uint64_t acc = 0, lo = (uint64_t)nz * lo_pct / 100, hi = (uint64_t)nz * hi_pct / 100;
uint32_t mn = 0, mx = 0;
for (uint32_t k = 0; k < 65536; ++k) {
acc += hist[k];
if (acc >= lo && mn == 0) mn = k;
if (acc >= hi) {
mx = k;
break;
}
}
if (mx <= mn + 50) {
mn = 0;
mx = 65535;
}
*out_lo = mn;
*out_hi = mx;
mag160c_clear_error();
return MAG160C_OK;
}
/* Adaptive diff AGC: span = clamp(2*mean|d|, min_span, max_span). */
mag160c_error_t mag160c_display_diff_span(const int32_t *diff, uint32_t n,
uint32_t min_span, uint32_t max_span,
uint32_t *out_span) {
if (diff == NULL || out_span == NULL) {
mag160c_set_error("mag160c_display_diff_span: null argument");
return MAG160C_ERR_INVALID_ARGUMENT;
}
uint64_t sum = 0;
uint32_t cnt = 0;
for (uint32_t i = 0; i < n; ++i) {
int32_t d = diff[i];
if (d < 0) d = -d;
if (d > 2) {
sum += (uint64_t)d;
cnt++;
}
}
uint64_t span = cnt ? 2 * sum / cnt : 0;
if (span < min_span) span = min_span;
if (span > max_span) span = max_span;
*out_span = (uint32_t)span;
mag160c_clear_error();
return MAG160C_OK;
}
/* ------------------------------------------------------------------ */
/* pseudo color */
/* FLIR-style ironbow anchors, v in [0,255] */
void mag160c_display_ironbow(uint32_t v, uint8_t *r, uint8_t *g, uint8_t *b) {
static const uint8_t anchors[7][3] = {
{0, 0, 0}, {23, 0, 60}, {93, 0, 128}, {170, 31, 83},
{226, 117, 29}, {249, 196, 66}, {255, 255, 220}};
double f = (v & 0xff) / 255.0 * 6.0;
uint32_t i = (uint32_t)f;
if (i > 5) i = 5;
double t = f - i;
*r = (uint8_t)(anchors[i][0] + t * (anchors[i + 1][0] - anchors[i][0]));
*g = (uint8_t)(anchors[i][1] + t * (anchors[i + 1][1] - anchors[i][1]));
*b = (uint8_t)(anchors[i][2] + t * (anchors[i + 1][2] - anchors[i][2]));
}
/* ------------------------------------------------------------------ */
/* FFC scheduler (official demo cadence) */
void mag160c_ffc_scheduler_init(mag160c_ffc_scheduler_t *s, uint32_t period,
uint32_t gap) {
if (s == NULL) {
return;
}
memset(s, 0, sizeof(*s));
s->period = period ? period : 400;
s->gap = gap ? gap : 9;
}
/* Call once per complete frame. Returns:
* -1 : nothing to send
* >=0: FFC param to send (0 or 1), then call again after the response. */
int32_t mag160c_ffc_scheduler_tick(mag160c_ffc_scheduler_t *s) {
if (s == NULL) {
return -1;
}
s->frames++;
if (!s->started) {
/* official: FFC(1) after ~10 complete frames switches to type=0 */
if (s->frames >= 10) {
s->started = 1;
s->frames = 0;
return 1;
}
return -1;
}
if (s->wait1 && s->frames >= s->gap) {
s->wait1 = 0;
s->frames = 0;
return 1;
}
if (!s->wait1 && s->frames >= s->period) {
s->wait1 = 1;
s->frames = 0;
return 0;
}
return -1;
}
/* Force an immediate FFC pair: returns 0 (send FFC(0) now); the following
* tick() calls will then return 1 (send FFC(1)) after `gap` frames. This
* is the official manual-FFC behavior (FFC(0) then 9 frames later FFC(1)). */
int32_t mag160c_ffc_scheduler_trigger(mag160c_ffc_scheduler_t *s) {
if (s == NULL) {
return -1;
}
s->started = 1;
s->wait1 = 1;
s->frames = 0;
return 0;
}
/* ------------------------------------------------------------------ */
/* two-point temperature calibration */
/* counts = a * temp + b ; solve from two known points */
mag160c_error_t mag160c_temp_calibrate_linear(double counts0, double temp0,
double counts1, double temp1,
double *out_a, double *out_b) {
if (out_a == NULL || out_b == NULL) {
mag160c_set_error("mag160c_temp_calibrate_linear: null argument");
return MAG160C_ERR_INVALID_ARGUMENT;
}
if (counts1 == counts0) {
mag160c_set_error("mag160c_temp_calibrate_linear: identical counts");
return MAG160C_ERR_INVALID_ARGUMENT;
}
*out_a = (temp1 - temp0) / (counts1 - counts0);
*out_b = temp0 - *out_a * counts0;
mag160c_clear_error();
return MAG160C_OK;
}
double mag160c_temp_apply_linear(double counts, double a, double b) {
return a * counts + b;
}
+43
View File
@@ -0,0 +1,43 @@
#include "mag160c_internal.h"
#if defined(_WIN32) && !defined(__GNUC__)
# define MAG160C_TLS __declspec(thread)
#else
# define MAG160C_TLS __thread
#endif
static MAG160C_TLS char g_last_error[512] = {0};
void mag160c_set_error(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vsnprintf(g_last_error, sizeof(g_last_error), fmt, ap);
va_end(ap);
}
void mag160c_clear_error(void) {
g_last_error[0] = '\0';
}
const char *mag160c_last_error(void) {
return g_last_error;
}
const char *mag160c_error_name(mag160c_error_t code) {
switch (code) {
case MAG160C_OK: return "MAG160C_OK";
case MAG160C_ERR_INVALID_ARGUMENT: return "MAG160C_ERR_INVALID_ARGUMENT";
case MAG160C_ERR_NO_MEMORY: return "MAG160C_ERR_NO_MEMORY";
case MAG160C_ERR_NOT_READY: return "MAG160C_ERR_NOT_READY";
case MAG160C_ERR_NOT_INITIALIZED: return "MAG160C_ERR_NOT_INITIALIZED";
case MAG160C_ERR_NOT_OPEN: return "MAG160C_ERR_NOT_OPEN";
case MAG160C_ERR_NOT_SUPPORTED: return "MAG160C_ERR_NOT_SUPPORTED";
case MAG160C_ERR_FAILED: return "MAG160C_ERR_FAILED";
case MAG160C_ERR_USB: return "MAG160C_ERR_USB";
case MAG160C_ERR_TIMEOUT: return "MAG160C_ERR_TIMEOUT";
case MAG160C_ERR_CHECKSUM: return "MAG160C_ERR_CHECKSUM";
case MAG160C_ERR_BAD_FRAME: return "MAG160C_ERR_BAD_FRAME";
case MAG160C_ERR_INTERNAL: return "MAG160C_ERR_INTERNAL";
default: return "MAG160C_ERR_UNKNOWN";
}
}
+180
View File
@@ -0,0 +1,180 @@
/*
* Frame stream parsing, recovered from libmagcore.so.2.1.1 reader thread
* (0x170a4). The camera streams frames on EP IN 0x81:
*
* +0x00 u32 0x1BB1B11B leading marker
* +0x04 u32 frame counter
* +0x08 u32 data length (bytes of pixel data)
* +0x0c u32 frame type (0 = response, 1 = raw)
* +0x10 u32 period/shutter
* +0x14..+0x1b reserved
* +0x1c ... pixel data (uint16 LE)
* +0x1c+len u32 0x1BB1B11C trailing marker
* total frame = 0x38 + len
*
* The SDK keeps a ring buffer of 2*len+0x470 bytes, appends every bulk read,
* searches 4-byte aligned for the marker, and validates length + trailing
* marker before handing the frame to the dispatcher.
*/
#include "mag160c_internal.h"
#include <stdlib.h>
static uint32_t rd32(const uint8_t *p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) |
((uint32_t)p[3] << 24);
}
mag160c_error_t mag160c_frame_parse(const uint8_t *data, size_t size,
mag160c_frame_header_t *out_hdr,
const uint16_t **out_pixels) {
if (data == NULL || out_hdr == NULL || out_pixels == NULL) {
mag160c_set_error("mag160c_frame_parse: null argument");
return MAG160C_ERR_INVALID_ARGUMENT;
}
if (size < MAG160C_FRAME_DATA_OFFSET + 4) {
return MAG160C_ERR_BAD_FRAME;
}
if (rd32(data) != MAG160C_FRAME_MARKER) {
return MAG160C_ERR_BAD_FRAME;
}
mag160c_frame_header_t h;
h.marker = MAG160C_FRAME_MARKER;
h.frame_counter = rd32(data + 4);
h.data_length = rd32(data + 8);
h.frame_type = rd32(data + 12);
h.period_shutter = rd32(data + 16);
h.trailing_marker = 0;
if (h.frame_type > 1) {
return MAG160C_ERR_BAD_FRAME;
}
if (size < (size_t)MAG160C_FRAME_OVERHEAD + h.data_length) {
return MAG160C_ERR_BAD_FRAME;
}
const size_t trailing = (size_t)MAG160C_FRAME_DATA_OFFSET + h.data_length;
h.trailing_marker = rd32(data + trailing);
if (h.trailing_marker != MAG160C_FRAME_TRAILING_MARKER) {
return MAG160C_ERR_BAD_FRAME;
}
*out_hdr = h;
*out_pixels = (const uint16_t *)(data + MAG160C_FRAME_DATA_OFFSET);
mag160c_clear_error();
return MAG160C_OK;
}
/*
* Streaming assembler matching the vendor reader thread. Feed every bulk
* read from EP 0x81 into mag160c_frame_stream_push; when a complete valid
* frame is buffered it is copied to out_frame (capacity out_cap) and the
* function returns MAG160C_OK with *out_len set. Returns MAG160C_OK with
* *out_len == 0 when more data is needed.
*/
void mag160c_frame_stream_init(mag160c_frame_stream_t *s, size_t max_frame_len) {
if (s == NULL) {
return;
}
s->buf = NULL;
s->cap = max_frame_len * 2 + 0x470;
s->len = 0;
s->aligned = 0;
s->buf = (uint8_t *)malloc(s->cap);
}
void mag160c_frame_stream_destroy(mag160c_frame_stream_t *s) {
if (s == NULL) {
return;
}
free(s->buf);
s->buf = NULL;
s->len = 0;
s->cap = 0;
}
static void align_to_marker(mag160c_frame_stream_t *s) {
/* vendor: scan 4-byte aligned for 0x1bb1b11b, memmove tail to base */
size_t i = 0;
for (; i + 4 <= s->len; i += 4) {
if (rd32(s->buf + i) == MAG160C_FRAME_MARKER) {
break;
}
}
if (i == s->len) {
s->len = 0;
return;
}
if (i != 0) {
memmove(s->buf, s->buf + i, s->len - i);
s->len -= i;
}
s->aligned = 1;
}
mag160c_error_t mag160c_frame_stream_push(mag160c_frame_stream_t *s, const uint8_t *data,
size_t size, uint8_t *out_frame, size_t out_cap,
size_t *out_len) {
if (out_len != NULL) {
*out_len = 0;
}
if (s == NULL || s->buf == NULL || data == NULL || out_frame == NULL ||
out_len == NULL) {
mag160c_set_error("mag160c_frame_stream_push: null argument");
return MAG160C_ERR_INVALID_ARGUMENT;
}
if (s->cap == 0 || size > s->cap - s->len) {
s->len = 0;
s->aligned = 0;
}
memcpy(s->buf + s->len, data, size);
s->len += size;
if (!s->aligned) {
align_to_marker(s);
if (!s->aligned) {
return MAG160C_OK;
}
}
/* complete frame requires 0x38 + data_length bytes */
if (s->len < MAG160C_FRAME_OVERHEAD) {
return MAG160C_OK;
}
const uint32_t len = rd32(s->buf + 8);
if (len > s->cap - MAG160C_FRAME_OVERHEAD) {
s->len = 0; /* bogus length; resync */
return MAG160C_OK;
}
if (s->len < (size_t)MAG160C_FRAME_OVERHEAD + len) {
return MAG160C_OK;
}
mag160c_frame_header_t h;
const uint16_t *pixels;
mag160c_error_t rc = mag160c_frame_parse(s->buf, s->len, &h, &pixels);
if (rc != MAG160C_OK) {
/* leading marker present but trailer/length invalid: resync */
s->len = 0;
s->aligned = 0;
return MAG160C_OK;
}
const size_t total = (size_t)MAG160C_FRAME_OVERHEAD + h.data_length;
if (total > out_cap) {
mag160c_set_error("mag160c_frame_stream_push: output buffer too small");
return MAG160C_ERR_INVALID_ARGUMENT;
}
memcpy(out_frame, s->buf, total);
*out_len = total;
/* consume frame (vendor: advance and re-scan) */
memmove(s->buf, s->buf + total, s->len - total);
s->len -= total;
if (s->len == 0) {
s->aligned = 0;
}
return MAG160C_OK;
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef MAG160C_INTERNAL_H
#define MAG160C_INTERNAL_H
#include "mag160c/mag160c.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
void mag160c_set_error(const char *fmt, ...);
void mag160c_clear_error(void);
#ifdef __cplusplus
}
#endif
#endif
+598
View File
@@ -0,0 +1,598 @@
/*
* IR camera session, recovered from libmagcore.so.2.1.1:
*
* Link: enumerate VID 0x833C -> open -> set_auto_detach(1) ->
* set_configuration(2) -> claim_interface(0)
* Commands: write 8-byte {u32 magic, u32 param} on EP 0x03, then read up to
* 0x1000 bytes on EP 0x82 with 2000 ms timeout; success requires > 3
* bytes. Response magic 0x5BB5B55B carries the camera info block.
* Start: allocate 2*len+0x470 read buffer, create reader thread (EP 0x81)
* + dispatcher thread, sleep 50 ms, send 0x6BB6B673.
* Stop: send 0x6BB6B674, join threads.
* FFC: 0x6BB6B672 (+param).
*
* 2026-08-10 hardware: commands are 4-byte (magic only); only FFC is 8-byte
* {magic, param}. FFC(1) switches the stream to type=0 frames, FFC(0) back
* to type=1. The official demo alternates FFC(0)/FFC(1) (FFC(1) exactly 9
* frames after FFC(0)) to keep the type=0 stream alive; verified 1400+
* type=0 frames at 15 fps with zero stalls using the mag160c_ffc_scheduler.
*/
#include "mag160c_internal.h"
#include "mag160c/mag160c_display.h"
#include <stdlib.h>
#include <time.h>
#if MAG160C_HAS_LIBUSB
#include <libusb.h>
#endif
#if MAG160C_HAS_THREADS
#include <pthread.h>
#endif
struct mag160c_ctx_t {
int dummy;
};
struct mag160c_ir_t {
#if MAG160C_HAS_LIBUSB
libusb_context *usb; /* kept alive for the lifetime of the handle */
libusb_device_handle *handle;
#endif
uint32_t pid;
uint32_t width;
uint32_t height;
uint32_t frame_len; /* expected frame data length (bytes) */
uint32_t ffc_mode;
uint32_t info[8]; /* 0x5bb5b55b block cache */
int linked;
int running;
int prepared;
#if MAG160C_HAS_THREADS
pthread_t reader_thread;
pthread_t dispatch_thread;
int threads_up;
#endif
mag160c_frame_cb_t frame_cb;
void *frame_cb_user;
/* optional FFC scheduler: replicated official cadence keeps the type=0
* stream alive (verified 1400+ frames). When set, the reader thread
* issues FFC commands after complete frames as the scheduler dictates. */
mag160c_ffc_scheduler_t *ffc_sched;
int ffc_sched_owned;
};
mag160c_error_t mag160c_init(mag160c_ctx_t **out_ctx) {
if (out_ctx == NULL) {
mag160c_set_error("mag160c_init: out_ctx null");
return MAG160C_ERR_INVALID_ARGUMENT;
}
*out_ctx = (mag160c_ctx_t *)calloc(1, sizeof(mag160c_ctx_t));
if (*out_ctx == NULL) {
mag160c_set_error("mag160c_init: allocation failed");
return MAG160C_ERR_NO_MEMORY;
}
mag160c_clear_error();
return MAG160C_OK;
}
void mag160c_shutdown(mag160c_ctx_t *ctx) {
free(ctx);
}
#if MAG160C_HAS_LIBUSB
/* ---- command channel: EP 0x03 write + EP 0x82 read ---------------------- */
static mag160c_error_t cmd_transfer(mag160c_ir_t *ir, const uint8_t *cmd, size_t cmd_len) {
int transferred = 0;
int rc = libusb_bulk_transfer(ir->handle, MAG160C_IR_EP_CMD_OUT,
(uint8_t *)cmd, (int)cmd_len, &transferred, 500);
if (rc != 0 || (size_t)transferred != cmd_len) {
mag160c_set_error("cmd: write EP 0x03 failed (%s)", libusb_error_name(rc));
return MAG160C_ERR_USB;
}
uint8_t resp[0x1000];
rc = libusb_bulk_transfer(ir->handle, MAG160C_IR_EP_CMD_IN, resp, sizeof(resp),
&transferred, 2000);
if (rc != 0) {
mag160c_set_error("cmd: read EP 0x82 failed (%s)", libusb_error_name(rc));
return MAG160C_ERR_USB;
}
if (transferred <= 3) {
mag160c_set_error("cmd: short response (%d bytes)", transferred);
return MAG160C_ERR_BAD_FRAME;
}
const uint32_t magic = (uint32_t)resp[0] | ((uint32_t)resp[1] << 8) |
((uint32_t)resp[2] << 16) | ((uint32_t)resp[3] << 24);
const int payload_len = transferred - 4;
switch (magic) {
case MAG160C_MAG_RSP_INFO_0:
/* 0x5bb5b55b carries the camera info block (pid, width@+0x10,
height@+0x14); the 0x5bb5b55c block has a different layout and
must NOT be parsed as info */
if (payload_len >= (int)sizeof(ir->info)) {
memcpy(ir->info, resp + 4, sizeof(ir->info));
ir->width = ir->info[4]; /* +0x10 */
ir->height = ir->info[5]; /* +0x14 */
ir->frame_len = ir->width * ir->height * 2;
}
break;
default:
break;
}
mag160c_clear_error();
return MAG160C_OK;
}
static mag160c_error_t send_cmd32(mag160c_ir_t *ir, uint32_t magic, uint32_t param) {
uint8_t cmd[8];
cmd[0] = (uint8_t)(magic);
cmd[1] = (uint8_t)(magic >> 8);
cmd[2] = (uint8_t)(magic >> 16);
cmd[3] = (uint8_t)(magic >> 24);
cmd[4] = (uint8_t)(param);
cmd[5] = (uint8_t)(param >> 8);
cmd[6] = (uint8_t)(param >> 16);
cmd[7] = (uint8_t)(param >> 24);
return cmd_transfer(ir, cmd, sizeof(cmd));
}
/* Verified on hardware (WinUSB, VID 0x833C PID 0x0001):
* most commands are 4 bytes (magic only); only FFC (0x6bb6b672) carries an
* 8-byte {magic, param} payload. The official demo sends 66b/66c/66f as
* 4-byte packets and FFC before START, then FFC(0/1 alternating) after every
* ~10 frames. */
static mag160c_error_t send_cmd4(mag160c_ir_t *ir, uint32_t magic) {
uint8_t cmd[4];
cmd[0] = (uint8_t)(magic);
cmd[1] = (uint8_t)(magic >> 8);
cmd[2] = (uint8_t)(magic >> 16);
cmd[3] = (uint8_t)(magic >> 24);
return cmd_transfer(ir, cmd, sizeof(cmd));
}
/* ---- frame reader thread (recovered 0x170a4) ----------------------------- */
typedef struct reader_ctx_t {
mag160c_ir_t *ir;
uint8_t *buf; /* 2*frame_len + 0x470 */
size_t buf_cap;
size_t buf_len;
size_t aligned;
} reader_ctx_t;
static void reader_align(reader_ctx_t *r) {
size_t i = 0;
for (; i + 4 <= r->buf_len; i += 4) {
if (r->buf[i] == 0x1b && r->buf[i + 1] == 0xb1 &&
r->buf[i + 2] == 0xb1 && r->buf[i + 3] == 0x1b) {
break;
}
}
if (i == r->buf_len) {
r->buf_len = 0;
return;
}
if (i != 0) {
memmove(r->buf, r->buf + i, r->buf_len - i);
r->buf_len -= i;
}
r->aligned = 1;
}
static void *reader_thread_fn(void *arg) {
reader_ctx_t *r = (reader_ctx_t *)arg;
mag160c_ir_t *ir = r->ir;
uint8_t tmp[0x8000];
while (ir->running) {
int transferred = 0;
int rc = libusb_bulk_transfer(ir->handle, MAG160C_IR_EP_STREAM_IN, tmp,
(int)sizeof(tmp), &transferred, 500);
if (rc != 0 || transferred <= 0) {
continue;
}
if ((size_t)transferred > r->buf_cap - r->buf_len) {
r->buf_len = 0; /* buffer full: drop and resync */
r->aligned = 0;
}
memcpy(r->buf + r->buf_len, tmp, (size_t)transferred);
r->buf_len += (size_t)transferred;
if (!r->aligned) {
reader_align(r);
}
/* complete frame = 0x38 + data_length */
while (r->aligned && r->buf_len >= MAG160C_FRAME_OVERHEAD) {
const uint32_t len = (uint32_t)r->buf[8] | ((uint32_t)r->buf[9] << 8) |
((uint32_t)r->buf[10] << 16) | ((uint32_t)r->buf[11] << 24);
const size_t total = (size_t)MAG160C_FRAME_OVERHEAD + len;
if (len > r->buf_cap) {
r->buf_len = 0;
r->aligned = 0;
break;
}
if (r->buf_len < total) {
break;
}
const size_t trail = (size_t)MAG160C_FRAME_DATA_OFFSET + len;
if (r->buf[trail] == 0x1c && r->buf[trail + 1] == 0xb1 &&
r->buf[trail + 2] == 0xb1 && r->buf[trail + 3] == 0x1b) {
/* valid frame: notify dispatcher via callback */
if (ir->frame_cb != NULL) {
const uint32_t idx = (uint32_t)r->buf[4] | ((uint32_t)r->buf[5] << 8) |
((uint32_t)r->buf[6] << 16) |
((uint32_t)r->buf[7] << 24);
ir->frame_cb(idx, r->buf, total, ir->frame_cb_user);
}
/* official FFC cadence: tick after every complete frame and
* send the requested FFC command (0/1) immediately */
if (ir->ffc_sched != NULL) {
const int32_t param = mag160c_ffc_scheduler_tick(ir->ffc_sched);
if (param >= 0) {
(void)send_cmd32(ir, MAG160C_MAG_CMD_FFC, (uint32_t)param);
}
}
memmove(r->buf, r->buf + total, r->buf_len - total);
r->buf_len -= total;
if (r->buf_len == 0) {
r->aligned = 0;
}
} else {
r->buf_len = 0;
r->aligned = 0;
break;
}
}
}
return NULL;
}
#endif /* MAG160C_HAS_LIBUSB */
mag160c_error_t mag160c_ir_open(mag160c_ctx_t *ctx, mag160c_ir_t **out_ir) {
if (ctx == NULL || out_ir == NULL) {
mag160c_set_error("mag160c_ir_open: null argument");
return MAG160C_ERR_INVALID_ARGUMENT;
}
*out_ir = NULL;
#if !MAG160C_HAS_LIBUSB
mag160c_set_error("mag160c_ir_open: built without libusb support");
return MAG160C_ERR_NOT_SUPPORTED;
#else
mag160c_ir_t *ir = (mag160c_ir_t *)calloc(1, sizeof(mag160c_ir_t));
if (ir == NULL) {
mag160c_set_error("mag160c_ir_open: allocation failed");
return MAG160C_ERR_NO_MEMORY;
}
ir->pid = 0;
ir->linked = 0;
libusb_context *usb = NULL;
int rc = libusb_init(&usb);
if (rc != 0) {
mag160c_set_error("mag160c_ir_open: libusb_init failed");
free(ir);
return MAG160C_ERR_USB;
}
libusb_device **list = NULL;
const ssize_t count = libusb_get_device_list(usb, &list);
libusb_device_handle *handle = NULL;
for (ssize_t i = 0; i < count && handle == NULL; ++i) {
struct libusb_device_descriptor desc;
if (libusb_get_device_descriptor(list[i], &desc) != 0) {
continue;
}
if (desc.idVendor != MAG160C_IR_VENDOR_ID) {
continue;
}
if (libusb_open(list[i], &handle) != 0) {
continue;
}
libusb_set_auto_detach_kernel_driver(handle, 1);
/* vendor tries config 2 first, then falls back to config 1
(libmagcore 0x1856a -> 0x1865b); this unit has only config 1 */
if (libusb_set_configuration(handle, 2) != 0) {
if (libusb_set_configuration(handle, 1) != 0) {
libusb_close(handle);
handle = NULL;
continue;
}
}
if (libusb_claim_interface(handle, MAG160C_IR_INTERFACE_NUMBER) != 0) {
libusb_close(handle);
handle = NULL;
continue;
}
ir->pid = desc.idProduct;
}
libusb_free_device_list(list, 1);
if (handle == NULL) {
libusb_exit(usb);
mag160c_set_error("mag160c_ir_open: no device with VID 0x%04x",
MAG160C_IR_VENDOR_ID);
free(ir);
return MAG160C_ERR_NOT_OPEN;
}
ir->usb = usb; /* keep context alive; released in mag160c_ir_close */
ir->handle = handle;
ir->linked = 1;
/* query camera info: verified on hardware the sequence is
0x6bb6b66b (4B), 0x6bb6b66c (4B), 0x6bb6b66f (4B) */
(void)send_cmd4(ir, MAG160C_MAG_CMD_PREPARE1);
(void)send_cmd4(ir, MAG160C_MAG_CMD_PREPARE2);
(void)send_cmd4(ir, MAG160C_MAG_CMD_GET_INFO);
*out_ir = ir;
mag160c_clear_error();
return MAG160C_OK;
#endif
}
void mag160c_ir_close(mag160c_ir_t *ir) {
if (ir == NULL) {
return;
}
if (ir->running) {
(void)mag160c_ir_stop(ir);
}
#if MAG160C_HAS_LIBUSB
if (ir->handle != NULL) {
libusb_release_interface(ir->handle, MAG160C_IR_INTERFACE_NUMBER);
libusb_close(ir->handle);
ir->handle = NULL;
}
if (ir->usb != NULL) {
libusb_exit(ir->usb);
ir->usb = NULL;
}
#endif
if (ir->ffc_sched_owned && ir->ffc_sched != NULL) {
free(ir->ffc_sched);
ir->ffc_sched = NULL;
}
free(ir);
}
mag160c_error_t mag160c_ir_is_linked(mag160c_ir_t *ir) {
if (ir == NULL) {
mag160c_set_error("mag160c_ir_is_linked: ir null");
return MAG160C_ERR_INVALID_ARGUMENT;
}
return ir->linked ? MAG160C_OK : MAG160C_ERR_NOT_OPEN;
}
mag160c_error_t mag160c_ir_get_info(mag160c_ir_t *ir, mag160c_ir_info_t *out_info) {
if (ir == NULL || out_info == NULL) {
mag160c_set_error("mag160c_ir_get_info: null argument");
return MAG160C_ERR_INVALID_ARGUMENT;
}
memset(out_info, 0, sizeof(*out_info));
out_info->width = ir->width ? ir->width : 160;
out_info->height = ir->height ? ir->height : 120;
out_info->fpa_width = out_info->width;
out_info->fpa_height = out_info->height;
out_info->pid = ir->pid;
out_info->serial_lo = ir->info[1]; /* +0x08 */
out_info->serial_hi = ir->info[2]; /* +0x10 upper */
out_info->max_fps = 25;
snprintf(out_info->name, sizeof(out_info->name), "MAG-IR-0x%04x", ir->pid);
mag160c_clear_error();
return MAG160C_OK;
}
mag160c_error_t mag160c_ir_set_frame_callback(mag160c_ir_t *ir, mag160c_frame_cb_t cb,
void *user) {
if (ir == NULL) {
mag160c_set_error("mag160c_ir_set_frame_callback: ir null");
return MAG160C_ERR_INVALID_ARGUMENT;
}
ir->frame_cb = cb;
ir->frame_cb_user = user;
return MAG160C_OK;
}
mag160c_error_t mag160c_ir_prepare(mag160c_ir_t *ir) {
if (ir == NULL || !ir->linked) {
mag160c_set_error("mag160c_ir_prepare: not linked");
return MAG160C_ERR_NOT_OPEN;
}
#if !MAG160C_HAS_LIBUSB
(void)ir;
return MAG160C_ERR_NOT_SUPPORTED;
#else
mag160c_error_t e = send_cmd4(ir, MAG160C_MAG_CMD_PREPARE1);
if (e != MAG160C_OK) {
return e;
}
e = send_cmd4(ir, MAG160C_MAG_CMD_PREPARE2);
if (e != MAG160C_OK) {
return e;
}
ir->prepared = 1;
return MAG160C_OK;
#endif
}
mag160c_error_t mag160c_ir_start(mag160c_ir_t *ir) {
if (ir == NULL || !ir->linked) {
mag160c_set_error("mag160c_ir_start: not linked");
return MAG160C_ERR_NOT_OPEN;
}
if (ir->running) {
mag160c_set_error("mag160c_ir_start: already running");
return MAG160C_ERR_NOT_READY;
}
#if !MAG160C_HAS_LIBUSB
(void)ir;
return MAG160C_ERR_NOT_SUPPORTED;
#elif !MAG160C_HAS_THREADS
(void)ir;
mag160c_set_error("mag160c_ir_start: built without thread support");
return MAG160C_ERR_NOT_SUPPORTED;
#else
if (ir->frame_len == 0) {
ir->frame_len = 160 * 120 * 2; /* 38400 default */
}
reader_ctx_t *r = (reader_ctx_t *)calloc(1, sizeof(reader_ctx_t));
if (r == NULL) {
mag160c_set_error("mag160c_ir_start: allocation failed");
return MAG160C_ERR_NO_MEMORY;
}
r->ir = ir;
r->buf_cap = ir->frame_len * 2 + 0x470;
r->buf = (uint8_t *)malloc(r->buf_cap);
if (r->buf == NULL) {
free(r);
mag160c_set_error("mag160c_ir_start: allocation failed");
return MAG160C_ERR_NO_MEMORY;
}
ir->running = 1;
if (pthread_create(&ir->reader_thread, NULL, reader_thread_fn, r) != 0) {
ir->running = 0;
free(r->buf);
free(r);
mag160c_set_error("mag160c_ir_start: pthread_create failed");
return MAG160C_ERR_INTERNAL;
}
ir->threads_up = 1;
/* vendor sleeps 50 ms before issuing START */
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 50 * 1000 * 1000;
nanosleep(&ts, NULL);
/* verified on hardware: FFC(0) twice before START switches the unit into
the live imaging mode (type=0 frames); the official demo does this */
(void)send_cmd32(ir, MAG160C_MAG_CMD_FFC, 0);
(void)send_cmd32(ir, MAG160C_MAG_CMD_FFC, 0);
ts.tv_nsec = 300 * 1000 * 1000;
nanosleep(&ts, NULL);
mag160c_error_t e = send_cmd4(ir, MAG160C_MAG_CMD_START);
if (e != MAG160C_OK) {
ir->running = 0;
pthread_join(ir->reader_thread, NULL);
ir->threads_up = 0;
free(r->buf);
free(r);
return e;
}
return MAG160C_OK;
#endif
}
mag160c_error_t mag160c_ir_stop(mag160c_ir_t *ir) {
if (ir == NULL || !ir->linked) {
mag160c_set_error("mag160c_ir_stop: not linked");
return MAG160C_ERR_NOT_OPEN;
}
#if !MAG160C_HAS_LIBUSB
(void)ir;
return MAG160C_ERR_NOT_SUPPORTED;
#else
if (ir->running) {
ir->running = 0;
(void)send_cmd4(ir, MAG160C_MAG_CMD_STOP);
#if MAG160C_HAS_THREADS
if (ir->threads_up) {
pthread_join(ir->reader_thread, NULL);
ir->threads_up = 0;
}
#endif
}
return MAG160C_OK;
#endif
}
mag160c_error_t mag160c_ir_trigger_ffc(mag160c_ir_t *ir, uint32_t param) {
if (ir == NULL || !ir->linked) {
mag160c_set_error("mag160c_ir_trigger_ffc: not linked");
return MAG160C_ERR_NOT_OPEN;
}
#if !MAG160C_HAS_LIBUSB
(void)ir;
(void)param;
return MAG160C_ERR_NOT_SUPPORTED;
#else
return send_cmd32(ir, MAG160C_MAG_CMD_FFC, param);
#endif
}
mag160c_error_t mag160c_ir_set_ffc_mode(mag160c_ir_t *ir, uint32_t mode) {
if (ir == NULL || !ir->linked) {
mag160c_set_error("mag160c_ir_set_ffc_mode: not linked");
return MAG160C_ERR_NOT_OPEN;
}
ir->ffc_mode = mode; /* ffc mode stored locally; affects start behavior */
return MAG160C_OK;
}
/* Attach an FFC scheduler. The scheduler is ticked after every complete
* frame in the reader thread; when it returns 0/1 the corresponding FFC
* command is sent (official demo cadence keeps the type=0 stream alive).
* If sched is NULL, the scheduler is disabled. A stack-allocated scheduler
* must outlive the stream; pass own=1 to let the IR session free it. */
mag160c_error_t mag160c_ir_set_ffc_scheduler(mag160c_ir_t *ir,
mag160c_ffc_scheduler_t *sched,
int own) {
if (ir == NULL || !ir->linked) {
mag160c_set_error("mag160c_ir_set_ffc_scheduler: not linked");
return MAG160C_ERR_NOT_OPEN;
}
if (ir->ffc_sched_owned && ir->ffc_sched != NULL) {
free(ir->ffc_sched);
}
ir->ffc_sched = sched;
ir->ffc_sched_owned = own;
mag160c_clear_error();
return MAG160C_OK;
}
mag160c_error_t mag160c_ir_reset(mag160c_ir_t *ir) {
if (ir == NULL || !ir->linked) {
mag160c_set_error("mag160c_ir_reset: not linked");
return MAG160C_ERR_NOT_OPEN;
}
#if !MAG160C_HAS_LIBUSB
(void)ir;
return MAG160C_ERR_NOT_SUPPORTED;
#else
return send_cmd32(ir, MAG160C_MAG_CMD_FFC, 0);
#endif
}
mag160c_error_t mag160c_ir_read_temperature(mag160c_ir_t *ir, uint32_t x, uint32_t y,
int32_t *out_temp) {
(void)x;
(void)y;
if (ir == NULL || out_temp == NULL) {
mag160c_set_error("mag160c_ir_read_temperature: null argument");
return MAG160C_ERR_INVALID_ARGUMENT;
}
*out_temp = 0;
if (!ir->linked) {
return MAG160C_ERR_NOT_OPEN;
}
/* requires the host pipeline; temperature map is maintained by the
* frame callback (mag160c_temp_calibrate). Without a live frame the
* vendor SDK returns 0xe4ae0001. */
mag160c_set_error("mag160c_ir_read_temperature: no temperature map available "
"(feed frames via mag160c_temp_calibrate first)");
return MAG160C_ERR_NOT_READY;
}
+69
View File
@@ -0,0 +1,69 @@
/* Official MAG160C gray LUT: 1024 entries read from CoreSDKLib dev+0x4284c.
* gray = LUT[((nuc - lo) * 0xFFC00000 / (hi - lo)) >> 22], 32-bit truncating math.
* lo = max(min, mean - 312); hi = min(max, mean + 312) (X=624, window on NUC counts). */
static const unsigned char mag160c_official_lut1024[1024] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 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, 1, 2, 2,
2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5,
5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 8,
8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11,
11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 14, 14,
14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 17, 17,
17, 17, 17, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 20, 20, 20,
20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23,
24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 27,
27, 27, 27, 27, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 30, 30,
30, 30, 30, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 33, 33, 33,
33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 35, 36, 36, 36, 36, 36,
37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 40,
40, 40, 40, 40, 41, 41, 41, 41, 41, 42, 42, 42, 42, 42, 43, 43,
43, 43, 43, 44, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46,
46, 47, 47, 47, 47, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 50,
50, 50, 50, 51, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53,
53, 54, 54, 54, 54, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 57,
57, 57, 57, 58, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60, 60,
60, 61, 61, 61, 61, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 64,
64, 64, 64, 65, 65, 65, 65, 65, 66, 66, 66, 66, 67, 67, 67, 67,
67, 68, 68, 68, 68, 69, 69, 69, 69, 69, 70, 70, 70, 70, 70, 71,
71, 71, 71, 72, 72, 72, 72, 72, 73, 73, 73, 73, 74, 74, 74, 74,
74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 77, 78, 78,
78, 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 81, 82,
82, 82, 82, 83, 83, 83, 83, 84, 84, 84, 84, 84, 85, 85, 85, 85,
86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 88, 89, 89, 89,
89, 90, 90, 90, 90, 91, 91, 91, 91, 92, 92, 92, 92, 92, 93, 93,
93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, 96, 96, 97,
97, 97, 97, 98, 98, 98, 98, 99, 99, 99, 99, 99,100,100,100,100,
101,101,101,101,102,102,102,102,103,103,103,103,103,104,104,104,
104,105,105,105,105,106,106,106,106,107,107,107,107,108,108,108,
108,109,109,109,109,110,110,110,110,111,111,111,111,112,112,112,
112,113,113,113,113,114,114,114,114,115,115,115,115,116,116,116,
116,117,117,117,117,118,118,118,118,119,119,119,119,120,120,120,
120,121,121,121,121,122,122,122,122,123,123,123,123,124,124,124,
124,125,125,125,125,126,126,126,126,127,127,127,127,128,128,128,
128,129,129,129,129,130,130,130,130,131,131,131,131,132,132,132,
132,133,133,133,133,134,134,134,134,135,135,135,135,136,136,136,
136,137,137,137,137,138,138,138,138,139,139,139,139,140,140,140,
140,141,141,141,141,142,142,142,142,143,143,143,143,144,144,144,
145,145,145,145,146,146,146,146,147,147,147,147,148,148,148,148,
149,149,149,150,150,150,150,151,151,151,151,152,152,152,152,153,
153,153,154,154,154,154,155,155,155,155,156,156,156,156,157,157,
157,158,158,158,158,159,159,159,159,160,160,160,160,161,161,161,
162,162,162,162,163,163,163,163,164,164,164,164,165,165,165,165,
166,166,166,167,167,167,167,168,168,168,168,169,169,169,169,170,
170,170,171,171,171,171,172,172,172,172,173,173,173,173,174,174,
174,175,175,175,175,176,176,176,176,177,177,177,177,178,178,178,
179,179,179,179,180,180,180,181,181,181,181,182,182,182,183,183,
183,183,184,184,184,185,185,185,185,186,186,186,186,187,187,187,
188,188,188,188,189,189,189,190,190,190,190,191,191,191,192,192,
192,192,193,193,193,194,194,194,194,195,195,195,195,196,196,196,
197,197,197,197,198,198,198,199,199,199,199,200,200,200,201,201,
201,201,202,202,202,203,203,203,203,204,204,204,204,205,205,205,
};
+266
View File
@@ -0,0 +1,266 @@
/* Official MAG160C display palette (256 RGB), recovered from vendor
* render frames. Background (gray 0-14) is dark red (74,0,0);
* ramp: red -> magenta -> purple -> blue-violet. */
#ifndef MAG160C_OFFICIAL_PALETTE_H
#define MAG160C_OFFICIAL_PALETTE_H
static const unsigned char mag160c_official_palette[256][3] = {
{ 74, 0, 0},
{ 74, 0, 0},
{ 74, 0, 0},
{ 74, 0, 0},
{ 74, 0, 0},
{ 74, 0, 0},
{ 74, 0, 0},
{ 74, 0, 0},
{ 74, 0, 0},
{ 74, 0, 0},
{ 74, 0, 0},
{ 74, 0, 0},
{ 74, 0, 0},
{ 74, 0, 0},
{ 74, 0, 0},
{ 42, 0, 0},
{ 47, 0, 0},
{ 52, 0, 0},
{ 55, 0, 0},
{ 57, 0, 0},
{ 59, 0, 0},
{ 62, 0, 0},
{ 64, 0, 0},
{ 67, 0, 0},
{ 70, 0, 0},
{ 72, 0, 0},
{ 75, 0, 0},
{ 77, 0, 0},
{ 80, 0, 0},
{ 82, 0, 0},
{ 85, 0, 0},
{ 88, 0, 0},
{ 90, 0, 0},
{ 92, 0, 0},
{ 95, 0, 0},
{ 98, 0, 0},
{100, 0, 0},
{103, 0, 0},
{105, 0, 0},
{108, 0, 0},
{110, 0, 0},
{113, 0, 0},
{115, 0, 0},
{117, 0, 0},
{117, 0, 1},
{117, 0, 2},
{117, 0, 4},
{118, 0, 5},
{118, 0, 6},
{118, 0, 7},
{118, 0, 8},
{119, 0, 10},
{119, 0, 11},
{119, 0, 13},
{119, 0, 14},
{120, 0, 15},
{120, 0, 16},
{120, 0, 17},
{120, 0, 19},
{120, 0, 20},
{121, 0, 21},
{121, 0, 22},
{121, 0, 23},
{121, 0, 25},
{122, 0, 26},
{122, 0, 28},
{122, 0, 29},
{122, 0, 30},
{123, 0, 31},
{123, 0, 32},
{123, 0, 34},
{123, 0, 35},
{124, 0, 37},
{124, 0, 38},
{124, 0, 39},
{124, 0, 40},
{124, 0, 41},
{125, 0, 43},
{125, 0, 44},
{125, 0, 45},
{125, 0, 46},
{126, 0, 48},
{126, 0, 49},
{126, 0, 50},
{126, 0, 51},
{127, 0, 52},
{127, 0, 54},
{127, 0, 55},
{127, 0, 57},
{128, 0, 58},
{128, 0, 59},
{128, 0, 60},
{128, 0, 61},
{129, 0, 63},
{129, 0, 64},
{130, 0, 66},
{130, 0, 66},
{130, 0, 67},
{130, 0, 69},
{130, 0, 70},
{131, 0, 72},
{131, 0, 73},
{131, 0, 74},
{131, 0, 75},
{132, 0, 76},
{132, 0, 78},
{132, 0, 79},
{132, 0, 81},
{133, 0, 82},
{133, 0, 83},
{133, 0, 84},
{133, 0, 85},
{134, 0, 87},
{134, 0, 88},
{134, 0, 90},
{134, 0, 91},
{134, 0, 91},
{135, 0, 93},
{135, 0, 94},
{135, 0, 95},
{135, 0, 96},
{136, 0, 98},
{136, 0, 99},
{136, 0, 100},
{136, 0, 102},
{137, 0, 103},
{137, 0, 104},
{137, 0, 105},
{137, 0, 107},
{138, 0, 108},
{138, 0, 109},
{138, 0, 111},
{138, 0, 112},
{139, 0, 113},
{139, 0, 114},
{139, 0, 116},
{139, 0, 117},
{139, 0, 118},
{140, 0, 119},
{140, 0, 120},
{140, 0, 122},
{140, 0, 123},
{141, 0, 124},
{141, 0, 126},
{142, 0, 127},
{142, 0, 128},
{142, 0, 129},
{142, 0, 131},
{143, 0, 132},
{143, 0, 133},
{143, 0, 135},
{143, 0, 136},
{144, 0, 137},
{144, 0, 138},
{144, 0, 140},
{144, 0, 141},
{144, 0, 141},
{145, 0, 143},
{145, 0, 144},
{145, 0, 146},
{145, 0, 147},
{146, 0, 148},
{146, 0, 149},
{146, 0, 150},
{146, 0, 152},
{147, 0, 153},
{147, 0, 155},
{147, 0, 156},
{147, 0, 157},
{148, 0, 158},
{148, 0, 159},
{148, 0, 161},
{148, 0, 162},
{149, 0, 164},
{149, 0, 165},
{149, 0, 166},
{119, 25, 195},
{ 90, 50, 224},
{113, 30, 202},
{143, 5, 174},
{150, 0, 170},
{150, 0, 171},
{132, 15, 189},
{106, 39, 215},
{ 95, 48, 226},
{ 97, 48, 225},
{ 98, 47, 224},
{100, 45, 222},
{101, 44, 222},
{100, 45, 223},
{100, 45, 223},
{102, 44, 222},
{107, 39, 217},
{129, 19, 195},
{149, 0, 175},
{129, 19, 196},
{109, 38, 216},
{107, 39, 218},
{106, 40, 219},
{107, 39, 218},
{109, 37, 217},
{124, 24, 202},
{143, 6, 183},
{139, 10, 188},
{122, 26, 205},
{113, 34, 214},
{112, 35, 215},
{112, 35, 215},
{114, 33, 213},
{116, 31, 212},
{117, 31, 212},
{118, 30, 211},
{119, 29, 210},
{120, 28, 209},
{121, 27, 209},
{122, 27, 209},
{122, 26, 208},
{123, 25, 208},
{135, 14, 196},
{149, 2, 182},
{142, 9, 189},
{129, 20, 202},
{126, 23, 205},
{127, 22, 205},
{129, 20, 204},
{131, 19, 202},
{131, 18, 202},
{130, 19, 203},
{133, 16, 200},
{144, 6, 189},
{149, 2, 184},
{141, 10, 193},
{134, 16, 200},
{135, 15, 200},
{136, 14, 199},
{137, 14, 199},
{137, 13, 198},
{138, 12, 197},
{139, 12, 197},
{140, 11, 196},
{141, 10, 196},
{142, 9, 195},
{143, 8, 194},
{146, 5, 191},
{150, 1, 187},
{151, 1, 187},
{147, 4, 190},
{146, 5, 192},
{147, 5, 192},
{148, 4, 191},
{149, 3, 190},
{150, 2, 189},
{151, 1, 189},
{152, 0, 188},
{153, 0, 188},
{154, 0, 188},
};
#endif
+259
View File
@@ -0,0 +1,259 @@
/* Official MAG160C palette: 256 x 4 bytes (B, G, R, 0). Extracted from CoreSDKLib dev+0xb18. */
static const unsigned char mag160c_official_palette256[256][4] = {
{ 0, 0, 0, 0}, /* 0 */
{ 5, 0, 0, 0}, /* 1 */
{ 10, 0, 0, 0}, /* 2 */
{ 15, 0, 0, 0}, /* 3 */
{ 21, 0, 0, 0}, /* 4 */
{ 26, 0, 0, 0}, /* 5 */
{ 31, 0, 0, 0}, /* 6 */
{ 37, 0, 0, 0}, /* 7 */
{ 42, 0, 0, 0}, /* 8 */
{ 47, 0, 0, 0}, /* 9 */
{ 53, 0, 0, 0}, /* 10 */
{ 58, 0, 0, 0}, /* 11 */
{ 63, 0, 0, 0}, /* 12 */
{ 69, 0, 0, 0}, /* 13 */
{ 74, 0, 0, 0}, /* 14 */
{ 79, 0, 0, 0}, /* 15 */
{ 85, 0, 0, 0}, /* 16 */
{ 90, 0, 0, 0}, /* 17 */
{ 95, 0, 0, 0}, /* 18 */
{101, 0, 0, 0}, /* 19 */
{106, 0, 0, 0}, /* 20 */
{111, 0, 0, 0}, /* 21 */
{117, 0, 0, 0}, /* 22 */
{117, 0, 2, 0}, /* 23 */
{118, 0, 5, 0}, /* 24 */
{118, 0, 7, 0}, /* 25 */
{119, 0, 10, 0}, /* 26 */
{119, 0, 13, 0}, /* 27 */
{120, 0, 15, 0}, /* 28 */
{120, 0, 18, 0}, /* 29 */
{121, 0, 21, 0}, /* 30 */
{121, 0, 23, 0}, /* 31 */
{122, 0, 26, 0}, /* 32 */
{122, 0, 29, 0}, /* 33 */
{123, 0, 31, 0}, /* 34 */
{123, 0, 34, 0}, /* 35 */
{124, 0, 37, 0}, /* 36 */
{124, 0, 39, 0}, /* 37 */
{125, 0, 42, 0}, /* 38 */
{125, 0, 45, 0}, /* 39 */
{126, 0, 47, 0}, /* 40 */
{126, 0, 50, 0}, /* 41 */
{127, 0, 52, 0}, /* 42 */
{127, 0, 55, 0}, /* 43 */
{128, 0, 58, 0}, /* 44 */
{128, 0, 60, 0}, /* 45 */
{129, 0, 63, 0}, /* 46 */
{130, 0, 66, 0}, /* 47 */
{130, 0, 68, 0}, /* 48 */
{131, 0, 71, 0}, /* 49 */
{131, 0, 74, 0}, /* 50 */
{132, 0, 76, 0}, /* 51 */
{132, 0, 79, 0}, /* 52 */
{133, 0, 82, 0}, /* 53 */
{133, 0, 84, 0}, /* 54 */
{134, 0, 87, 0}, /* 55 */
{134, 0, 90, 0}, /* 56 */
{135, 0, 92, 0}, /* 57 */
{135, 0, 95, 0}, /* 58 */
{136, 0, 97, 0}, /* 59 */
{136, 0, 100, 0}, /* 60 */
{137, 0, 103, 0}, /* 61 */
{137, 0, 105, 0}, /* 62 */
{138, 0, 108, 0}, /* 63 */
{138, 0, 111, 0}, /* 64 */
{139, 0, 113, 0}, /* 65 */
{139, 0, 116, 0}, /* 66 */
{140, 0, 119, 0}, /* 67 */
{140, 0, 121, 0}, /* 68 */
{141, 0, 124, 0}, /* 69 */
{142, 0, 127, 0}, /* 70 */
{142, 0, 129, 0}, /* 71 */
{143, 0, 132, 0}, /* 72 */
{143, 0, 135, 0}, /* 73 */
{144, 0, 137, 0}, /* 74 */
{144, 0, 140, 0}, /* 75 */
{145, 0, 142, 0}, /* 76 */
{145, 0, 145, 0}, /* 77 */
{146, 0, 148, 0}, /* 78 */
{146, 0, 150, 0}, /* 79 */
{147, 0, 153, 0}, /* 80 */
{147, 0, 156, 0}, /* 81 */
{148, 0, 158, 0}, /* 82 */
{148, 0, 161, 0}, /* 83 */
{149, 0, 164, 0}, /* 84 */
{149, 0, 166, 0}, /* 85 */
{150, 0, 169, 0}, /* 86 */
{150, 0, 172, 0}, /* 87 */
{151, 0, 174, 0}, /* 88 */
{151, 0, 177, 0}, /* 89 */
{152, 0, 180, 0}, /* 90 */
{152, 0, 182, 0}, /* 91 */
{153, 0, 185, 0}, /* 92 */
{154, 0, 188, 0}, /* 93 */
{152, 1, 189, 0}, /* 94 */
{150, 3, 190, 0}, /* 95 */
{148, 5, 192, 0}, /* 96 */
{146, 6, 193, 0}, /* 97 */
{144, 8, 194, 0}, /* 98 */
{142, 10, 196, 0}, /* 99 */
{140, 12, 197, 0}, /* 100 */
{138, 13, 198, 0}, /* 101 */
{136, 15, 200, 0}, /* 102 */
{134, 17, 201, 0}, /* 103 */
{132, 18, 202, 0}, /* 104 */
{130, 20, 204, 0}, /* 105 */
{128, 22, 205, 0}, /* 106 */
{126, 24, 206, 0}, /* 107 */
{124, 25, 208, 0}, /* 108 */
{122, 27, 209, 0}, /* 109 */
{120, 29, 210, 0}, /* 110 */
{118, 31, 212, 0}, /* 111 */
{116, 32, 213, 0}, /* 112 */
{114, 34, 214, 0}, /* 113 */
{112, 36, 216, 0}, /* 114 */
{110, 37, 217, 0}, /* 115 */
{108, 39, 218, 0}, /* 116 */
{106, 41, 220, 0}, /* 117 */
{104, 43, 221, 0}, /* 118 */
{102, 44, 222, 0}, /* 119 */
{100, 46, 224, 0}, /* 120 */
{ 98, 48, 225, 0}, /* 121 */
{ 95, 49, 227, 0}, /* 122 */
{ 91, 51, 227, 0}, /* 123 */
{ 87, 53, 228, 0}, /* 124 */
{ 82, 55, 229, 0}, /* 125 */
{ 78, 56, 230, 0}, /* 126 */
{ 73, 58, 231, 0}, /* 127 */
{ 69, 60, 232, 0}, /* 128 */
{ 64, 62, 233, 0}, /* 129 */
{ 60, 63, 234, 0}, /* 130 */
{ 55, 65, 235, 0}, /* 131 */
{ 51, 67, 236, 0}, /* 132 */
{ 47, 68, 237, 0}, /* 133 */
{ 42, 70, 238, 0}, /* 134 */
{ 38, 72, 239, 0}, /* 135 */
{ 33, 74, 240, 0}, /* 136 */
{ 29, 75, 241, 0}, /* 137 */
{ 24, 77, 241, 0}, /* 138 */
{ 20, 79, 242, 0}, /* 139 */
{ 15, 80, 243, 0}, /* 140 */
{ 15, 82, 244, 0}, /* 141 */
{ 15, 84, 245, 0}, /* 142 */
{ 15, 86, 246, 0}, /* 143 */
{ 15, 87, 247, 0}, /* 144 */
{ 15, 89, 248, 0}, /* 145 */
{ 15, 91, 249, 0}, /* 146 */
{ 15, 93, 250, 0}, /* 147 */
{ 15, 94, 251, 0}, /* 148 */
{ 15, 96, 252, 0}, /* 149 */
{ 15, 98, 253, 0}, /* 150 */
{ 15, 99, 254, 0}, /* 151 */
{ 15, 101, 255, 0}, /* 152 */
{ 15, 103, 255, 0}, /* 153 */
{ 15, 105, 255, 0}, /* 154 */
{ 15, 106, 255, 0}, /* 155 */
{ 15, 108, 255, 0}, /* 156 */
{ 15, 110, 255, 0}, /* 157 */
{ 15, 111, 255, 0}, /* 158 */
{ 15, 113, 255, 0}, /* 159 */
{ 15, 115, 255, 0}, /* 160 */
{ 15, 117, 255, 0}, /* 161 */
{ 15, 118, 255, 0}, /* 162 */
{ 15, 120, 255, 0}, /* 163 */
{ 15, 122, 255, 0}, /* 164 */
{ 15, 124, 255, 0}, /* 165 */
{ 15, 125, 255, 0}, /* 166 */
{ 15, 127, 255, 0}, /* 167 */
{ 15, 129, 255, 0}, /* 168 */
{ 15, 130, 255, 0}, /* 169 */
{ 15, 132, 255, 0}, /* 170 */
{ 15, 134, 255, 0}, /* 171 */
{ 15, 136, 255, 0}, /* 172 */
{ 15, 137, 255, 0}, /* 173 */
{ 15, 139, 255, 0}, /* 174 */
{ 15, 141, 255, 0}, /* 175 */
{ 15, 143, 255, 0}, /* 176 */
{ 15, 144, 255, 0}, /* 177 */
{ 15, 146, 255, 0}, /* 178 */
{ 15, 148, 255, 0}, /* 179 */
{ 15, 149, 255, 0}, /* 180 */
{ 15, 151, 255, 0}, /* 181 */
{ 15, 153, 255, 0}, /* 182 */
{ 15, 155, 255, 0}, /* 183 */
{ 15, 156, 255, 0}, /* 184 */
{ 15, 158, 255, 0}, /* 185 */
{ 15, 160, 255, 0}, /* 186 */
{ 15, 161, 255, 0}, /* 187 */
{ 15, 163, 255, 0}, /* 188 */
{ 15, 165, 255, 0}, /* 189 */
{ 15, 167, 255, 0}, /* 190 */
{ 15, 168, 255, 0}, /* 191 */
{ 15, 170, 255, 0}, /* 192 */
{ 15, 172, 255, 0}, /* 193 */
{ 15, 174, 255, 0}, /* 194 */
{ 15, 175, 255, 0}, /* 195 */
{ 15, 177, 255, 0}, /* 196 */
{ 15, 179, 255, 0}, /* 197 */
{ 15, 180, 255, 0}, /* 198 */
{ 15, 182, 255, 0}, /* 199 */
{ 15, 184, 255, 0}, /* 200 */
{ 15, 186, 255, 0}, /* 201 */
{ 15, 187, 255, 0}, /* 202 */
{ 15, 189, 255, 0}, /* 203 */
{ 15, 191, 255, 0}, /* 204 */
{ 15, 192, 255, 0}, /* 205 */
{ 15, 194, 255, 0}, /* 206 */
{ 15, 196, 255, 0}, /* 207 */
{ 15, 198, 255, 0}, /* 208 */
{ 15, 199, 255, 0}, /* 209 */
{ 15, 201, 255, 0}, /* 210 */
{ 15, 203, 255, 0}, /* 211 */
{ 15, 205, 255, 0}, /* 212 */
{ 15, 206, 255, 0}, /* 213 */
{ 15, 208, 255, 0}, /* 214 */
{ 15, 210, 255, 0}, /* 215 */
{ 15, 211, 255, 0}, /* 216 */
{ 15, 213, 255, 0}, /* 217 */
{ 15, 215, 255, 0}, /* 218 */
{ 15, 217, 255, 0}, /* 219 */
{ 15, 218, 255, 0}, /* 220 */
{ 15, 220, 255, 0}, /* 221 */
{ 15, 222, 255, 0}, /* 222 */
{ 15, 223, 255, 0}, /* 223 */
{ 22, 225, 255, 0}, /* 224 */
{ 30, 227, 255, 0}, /* 225 */
{ 37, 229, 255, 0}, /* 226 */
{ 45, 230, 255, 0}, /* 227 */
{ 52, 232, 255, 0}, /* 228 */
{ 60, 234, 255, 0}, /* 229 */
{ 67, 236, 255, 0}, /* 230 */
{ 75, 237, 255, 0}, /* 231 */
{ 82, 239, 255, 0}, /* 232 */
{ 90, 241, 255, 0}, /* 233 */
{ 97, 242, 255, 0}, /* 234 */
{105, 244, 255, 0}, /* 235 */
{112, 246, 255, 0}, /* 236 */
{120, 248, 255, 0}, /* 237 */
{127, 249, 255, 0}, /* 238 */
{135, 251, 255, 0}, /* 239 */
{142, 253, 255, 0}, /* 240 */
{150, 255, 255, 0}, /* 241 */
{157, 255, 255, 0}, /* 242 */
{165, 255, 255, 0}, /* 243 */
{172, 255, 255, 0}, /* 244 */
{180, 255, 255, 0}, /* 245 */
{187, 255, 255, 0}, /* 246 */
{195, 255, 255, 0}, /* 247 */
{202, 255, 255, 0}, /* 248 */
{210, 255, 255, 0}, /* 249 */
{217, 255, 255, 0}, /* 250 */
{225, 255, 255, 0}, /* 251 */
{232, 255, 255, 0}, /* 252 */
{240, 255, 255, 0}, /* 253 */
{247, 255, 255, 0}, /* 254 */
{255, 255, 255, 0}, /* 255 */
};
+90
View File
@@ -0,0 +1,90 @@
/* Official MAG160C T2E table (646 int32 entries), extracted from
* CoreSDKLib.dll .rdata 0x5cde0. temp = slope[i]*diff>>12 + (i<<12) - 0x249f0 */
#ifndef MAG160C_OFFICIAL_T2E_H
#define MAG160C_OFFICIAL_T2E_H
static const int32_t mag160c_official_t2e[646] = {
51, 70, 94, 125, 162, 208, 264, 331,
410, 503, 612, 737, 881, 1045, 1231, 1440,
1675, 1937, 2227, 2547, 2899, 3285, 3705, 4162,
4658, 5192, 5768, 6386, 7047, 7754, 8506, 9305,
10153, 11050, 11997, 12995, 14045, 15147, 16303, 17513,
18778, 20098, 21473, 22905, 24393, 25938, 27540, 29199,
30916, 32690, 34522, 36412, 38360, 40366, 42430, 44552,
46731, 48968, 51262, 53613, 56022, 58487, 61008, 63586,
66220, 68910, 71654, 74454, 77308, 80217, 83179, 86195,
89263, 92385, 95558, 98783, 102059, 105387, 108764, 112191,
115668, 119194, 122768, 126391, 130060, 133777, 137541, 141350,
145205, 149105, 153050, 157039, 161072, 165147, 169266, 173426,
177629, 181873, 186157, 190482, 194847, 199251, 203694, 208175,
212695, 217252, 221847, 226478, 231145, 235849, 240587, 245361,
250169, 255012, 259888, 264797, 269740, 274715, 279722, 284761,
289831, 294933, 300064, 305227, 310419, 315640, 320891, 326170,
331478, 336814, 342178, 347569, 352988, 358433, 363904, 369402,
374925, 380474, 386048, 391647, 397271, 402919, 408591, 414286,
420005, 425747, 431512, 437300, 443109, 448941, 454795, 460670,
466566, 472484, 478422, 484381, 490360, 496359, 502378, 508416,
514474, 520551, 526647, 532761, 538894, 545045, 551215, 557402,
563606, 569828, 576068, 582324, 588597, 594887, 601193, 607516,
613854, 620209, 626579, 632965, 639366, 645782, 652213, 658660,
665120, 671596, 678086, 684589, 691108, 697639, 704185, 710744,
717317, 723903, 730502, 737114, 743740, 750377, 757028, 763691,
770366, 777053, 783753, 790464, 797187, 803922, 810669, 817427,
824196, 830977, 837768, 844571, 851384, 858209, 865043, 871889,
878745, 885611, 892487, 899374, 906270, 913177, 920093, 927019,
933954, 940899, 947854, 954818, 961791, 968773, 975764, 982764,
989773, 996791, 1003818, 1010853, 1017897, 1024949, 1032009, 1039078,
1046155, 1053240, 1060333, 1067435, 1074544, 1081661, 1088785, 1095918,
1103058, 1110205, 1117360, 1124523, 1131692, 1138869, 1146053, 1153245,
1160443, 1167648, 1174860, 1182080, 1189305, 1196538, 1203777, 1211023,
1218276, 1225535, 1232800, 1240072, 1247350, 1254635, 1261925, 1269222,
1276525, 1283834, 1291149, 1298470, 1305797, 1313129, 1320468, 1327812,
1335162, 1342517, 1349878, 1357245, 1364617, 1371995, 1379378, 1386766,
1394160, 1401559, 1408963, 1416372, 1423786, 1431206, 1438631, 1446060,
1453495, 1460934, 1468379, 1475828, 1483282, 1490741, 1498204, 1505672,
1513145, 1520622, 1528104, 1535591, 1543082, 1550578, 1558077, 1565582,
1573090, 1580603, 1588121, 1595642, 1603168, 1610698, 1618232, 1625770,
1633312, 1640859, 1648409, 1655963, 1663522, 1671084, 1678650, 1686220,
1693794, 1701371, 1708953, 1716538, 1724127, 1731719, 1739315, 1746915,
1754519, 1762126, 1769736, 1777350, 1784968, 1792589, 1800214, 1807842,
1815473, 1823108, 1830746, 1838387, 1846032, 1853680, 1861331, 1868986,
1876643, 1884304, 1891968, 1899635, 1907306, 1914979, 1922655, 1930335,
1938017, 1945703, 1953391, 1961082, 1968777, 1976474, 1984174, 1991877,
1999583, 2007292, 2015003, 2022718, 2030435, 2038155, 2045877, 2053602,
2061330, 2069061, 2076794, 2084530, 2092269, 2100010, 2107754, 2115500,
2123249, 2131001, 2138755, 2146511, 2154270, 2162031, 2169795, 2177562,
2185330, 2193101, 2200875, 2208651, 2216429, 2224210, 2231993, 2239778,
2247565, 2255355, 2263147, 2270941, 2278738, 2286537, 2294338, 2302141,
2309946, 2317754, 2325563, 2333375, 2341189, 2349005, 2356823, 2364643,
2372465, 2380290, 2388116, 2395944, 2403775, 2411607, 2419441, 2427278,
2435116, 2442956, 2450798, 2458642, 2466488, 2474336, 2482186, 2490038,
2497891, 2505747, 2513604, 2521463, 2529324, 2537187, 2545051, 2552918,
2560786, 2568656, 2576527, 2584401, 2592276, 2600153, 2608031, 2615911,
2623793, 2631677, 2639562, 2647449, 2655338, 2663228, 2671120, 2679014,
2686909, 2694806, 2702704, 2710604, 2718505, 2726408, 2734313, 2742219,
2750127, 2758036, 2765947, 2773859, 2781773, 2789688, 2797605, 2805524,
2813443, 2821364, 2829287, 2837211, 2845137, 2853064, 2860992, 2868922,
2876853, 2884786, 2892720, 2900655, 2908592, 2916530, 2924470, 2932411,
2940353, 2948296, 2956241, 2964187, 2972135, 2980084, 2988034, 2995985,
3003938, 3011892, 3019847, 3027803, 3035761, 3043720, 3051680, 3059642,
3067605, 3075569, 3083534, 3091500, 3099468, 3107436, 3115406, 3123378,
3131350, 3139323, 3147298, 3155274, 3163251, 3171229, 3179208, 3187189,
3195170, 3203153, 3211137, 3219121, 3227107, 3235095, 3243083, 3251072,
3259062, 3267054, 3275046, 3283040, 3291035, 3299030, 3307027, 3315025,
3323024, 3331023, 3339024, 3347026, 3355029, 3363033, 3371038, 3379044,
3387051, 3395059, 3403068, 3411078, 3419089, 3427101, 3435113, 3443127,
3451142, 3459158, 3467174, 3475192, 3483211, 3491230, 3499250, 3507272,
3515294, 3523317, 3531341, 3539366, 3547392, 3555419, 3563447, 3571475,
3579505, 3587535, 3595566, 3603598, 3611631, 3619665, 3627700, 3635735,
3643772, 3651809, 3659847, 3667886, 3675926, 3683966, 3692008, 3700050,
3708093, 3716137, 3724182, 3732227, 3740273, 3748321, 3756368, 3764417,
3772467, 3780517, 3788568, 3796620, 3804672, 3812726, 3820780, 3828835,
3836891, 3844947, 3853004, 3861062, 3869121, 3877180, 3885240, 3893301,
3901363, 3909425, 3917488, 3925552, 3933617, 3941682, 3949748, 3957814,
3965882, 3973950, 3982019, 3990088, 3998158, 4006229, 4014301, 4022373,
4030446, 4038519, 4046594, 4054669, 4062744, 4070820, 4078897, 4086975,
4095053, 4103132, 4111212, 4119292, 4127373, 4135454, 4143536, 4151619,
4159703, 4167787, 4175871, 4183957, 4192042, 4200129,
};
#endif
+111
View File
@@ -0,0 +1,111 @@
/* T2E / E2TAccQ10 calibration tables recovered from libcoresdk.so (ARM64). */
/* T2E @ 0x402010, E2TAccQ10 @ 0x40245c; vendor symbols T2E / E2TAccQ10. */
/* Generated from analysis/t2e_table.json. */
#ifndef MAG160C_TABLES_H
#define MAG160C_TABLES_H
#include <stdint.h>
#define MAG160C_TEMP_CURVE_ENTRIES 0x112u
static const uint32_t mag160c_t2e[MAG160C_TEMP_CURVE_ENTRIES] = {
0x000003e8, 0x000004d3, 0x000005e1, 0x00000714, 0x0000086e, 0x000009f1,
0x00000b9e, 0x00000d76, 0x00000f7b, 0x000011ae, 0x0000140e, 0x0000169d,
0x0000195c, 0x00001c49, 0x00001f67, 0x000022b3, 0x0000262f, 0x000029db,
0x00002db5, 0x000031bd, 0x000035f4, 0x00003a58, 0x00003ee9, 0x000043a7,
0x00004890, 0x00004da4, 0x000052e2, 0x0000584a, 0x00005ddb, 0x00006394,
0x00006974, 0x00006f7a, 0x000075a6, 0x00007bf7, 0x0000826c, 0x00008904,
0x00008fbf, 0x0000969b, 0x00009d98, 0x0000a4b6, 0x0000abf3, 0x0000b34e,
0x0000bac8, 0x0000c25f, 0x0000ca12, 0x0000d1e2, 0x0000d9cc, 0x0000e1d1,
0x0000e9f0, 0x0000f229, 0x0000fa7a, 0x000102e3, 0x00010b64, 0x000113fb,
0x00011ca9, 0x0001256d, 0x00012e46, 0x00013734, 0x00014036, 0x0001494c,
0x00015276, 0x00015bb2, 0x00016501, 0x00016e62, 0x000177d4, 0x00018158,
0x00018aec, 0x00019490, 0x00019e45, 0x0001a809, 0x0001b1dd, 0x0001bbbf,
0x0001c5b0, 0x0001cfaf, 0x0001d9bc, 0x0001e3d7, 0x0001edff, 0x0001f833,
0x00020275, 0x00020cc3, 0x0002171d, 0x00022183, 0x00022bf5, 0x00023672,
0x000240fa, 0x00024b8c, 0x0002562a, 0x000260d2, 0x00026b84, 0x00027640,
0x00028106, 0x00028bd5, 0x000296ae, 0x0002a190, 0x0002ac7b, 0x0002b76f,
0x0002c26b, 0x0002cd70, 0x0002d87d, 0x0002e392, 0x0002eeaf, 0x0002f9d4,
0x00030500, 0x00031034, 0x00031b6f, 0x000326b1, 0x000331fb, 0x00033d4b,
0x000348a2, 0x00035400, 0x00035f64, 0x00036acf, 0x00037640, 0x000381b7,
0x00038d34, 0x000398b7, 0x0003a440, 0x0003afcf, 0x0003bb63, 0x0003c6fd,
0x0003d29c, 0x0003de40, 0x0003e9ea, 0x0003f599, 0x0004014d, 0x00040d05,
0x000418c3, 0x00042486, 0x0004304d, 0x00043c18, 0x000447e9, 0x000453bd,
0x00045f96, 0x00046b74, 0x00047756, 0x0004833b, 0x00048f25, 0x00049b13,
0x0004a705, 0x0004b2fb, 0x0004bef4, 0x0004caf2, 0x0004d6f3, 0x0004e2f8,
0x0004ef00, 0x0004fb0c, 0x0005071b, 0x0005132e, 0x00051f44, 0x00052b5d,
0x0005377a, 0x0005439a, 0x00054fbd, 0x00055be3, 0x0005680c, 0x00057439,
0x00058068, 0x00058c9a, 0x000598cf, 0x0005a507, 0x0005b142, 0x0005bd7f,
0x0005c9bf, 0x0005d602, 0x0005e247, 0x0005ee90, 0x0005fada, 0x00060727,
0x00061377, 0x00061fc9, 0x00062c1e, 0x00063875, 0x000644ce, 0x00065129,
0x00065d87, 0x000669e7, 0x0006764a, 0x000682ae, 0x00068f15, 0x00069b7e,
0x0006a7e9, 0x0006b456, 0x0006c0c5, 0x0006cd36, 0x0006d9a9, 0x0006e61e,
0x0006f295, 0x0006ff0e, 0x00070b89, 0x00071805, 0x00072484, 0x00073104,
0x00073d86, 0x00074a0a, 0x0007568f, 0x00076317, 0x00076fa0, 0x00077c2a,
0x000788b7, 0x00079545, 0x0007a1d4, 0x0007ae66, 0x0007baf8, 0x0007c78d,
0x0007d423, 0x0007e0ba, 0x0007ed53, 0x0007f9ed, 0x00080689, 0x00081327,
0x00081fc5, 0x00082c65, 0x00083907, 0x000845aa, 0x0008524e, 0x00085ef4,
0x00086b9b, 0x00087843, 0x000884ed, 0x00089197, 0x00089e44, 0x0008aaf1,
0x0008b7a0, 0x0008c44f, 0x0008d100, 0x0008ddb3, 0x0008ea66, 0x0008f71b,
0x000903d0, 0x00091087, 0x00091d3f, 0x000929f9, 0x000936b3, 0x0009436e,
0x0009502b, 0x00095ce8, 0x000969a7, 0x00097666, 0x00098327, 0x00098fe9,
0x00099cab, 0x0009a96f, 0x0009b634, 0x0009c2f9, 0x0009cfc0, 0x0009dc88,
0x0009e950, 0x0009f61a, 0x000a02e4, 0x000a0faf, 0x000a1c7c, 0x000a2949,
0x000a3617, 0x000a42e6, 0x000a4fb5, 0x000a5c86, 0x000a6958, 0x000a762a,
0x000a82fd, 0x000a8fd1, 0x000a9ca6, 0x000aa97c, 0x000ab652, 0x000ac329,
0x000ad001, 0x000adcda, 0x000ae9b4, 0x000af68e, 0x000b0369, 0x000b1045,
0x000b1d22, 0x000b29ff, 0x000b36dd, 0x000b43bc,
};
static const uint32_t mag160c_e2t_acc_q10[MAG160C_TEMP_CURVE_ENTRIES] = {
0x00008b70, 0x0000795d, 0x00006abc, 0x00005eb5, 0x000054ac, 0x00004c62,
0x0000456c, 0x00003f62, 0x00003a34, 0x000035e5, 0x00003207, 0x00002e9d,
0x00002bc0, 0x00002910, 0x000026d3, 0x000024bc, 0x000022dc, 0x0000213c,
0x00001fc0, 0x00001e5e, 0x00001d27, 0x00001c08, 0x00001afe, 0x00001a12,
0x00001935, 0x0000186b, 0x000017ad, 0x000016ff, 0x0000165e, 0x000015ca,
0x00001540, 0x000014bd, 0x00001444, 0x000013d3, 0x0000136a, 0x00001305,
0x000012a9, 0x00001251, 0x000011fc, 0x000011af, 0x00001167, 0x0000111f,
0x000010dd, 0x000010a0, 0x00001062, 0x0000102c, 0x00000ff6, 0x00000fc3,
0x00000f91, 0x00000f64, 0x00000f38, 0x00000f0d, 0x00000ee7, 0x00000ebf,
0x00000e9a, 0x00000e78, 0x00000e56, 0x00000e36, 0x00000e16, 0x00000df8,
0x00000ddc, 0x00000dc0, 0x00000da6, 0x00000d8d, 0x00000d74, 0x00000d5d,
0x00000d47, 0x00000d30, 0x00000d1b, 0x00000d06, 0x00000cf4, 0x00000ce0,
0x00000cce, 0x00000cbc, 0x00000cab, 0x00000c9a, 0x00000c8c, 0x00000c7a,
0x00000c6c, 0x00000c5e, 0x00000c4f, 0x00000c41, 0x00000c34, 0x00000c28,
0x00000c1c, 0x00000c0e, 0x00000c03, 0x00000bf8, 0x00000bed, 0x00000be2,
0x00000bd8, 0x00000bcd, 0x00000bc3, 0x00000bb9, 0x00000bb0, 0x00000ba7,
0x00000b9e, 0x00000b95, 0x00000b8d, 0x00000b85, 0x00000b7c, 0x00000b75,
0x00000b6d, 0x00000b66, 0x00000b5f, 0x00000b57, 0x00000b51, 0x00000b4a,
0x00000b43, 0x00000b3d, 0x00000b36, 0x00000b30, 0x00000b2a, 0x00000b24,
0x00000b1e, 0x00000b19, 0x00000b13, 0x00000b0e, 0x00000b08, 0x00000b04,
0x00000aff, 0x00000af9, 0x00000af5, 0x00000af0, 0x00000aec, 0x00000ae7,
0x00000ae2, 0x00000ade, 0x00000adb, 0x00000ad5, 0x00000ad2, 0x00000ace,
0x00000ac9, 0x00000ac6, 0x00000ac3, 0x00000abe, 0x00000abb, 0x00000ab7,
0x00000ab4, 0x00000ab1, 0x00000aac, 0x00000aaa, 0x00000aa6, 0x00000aa4,
0x00000aa0, 0x00000a9d, 0x00000a9a, 0x00000a97, 0x00000a95, 0x00000a91,
0x00000a8f, 0x00000a8c, 0x00000a89, 0x00000a87, 0x00000a83, 0x00000a82,
0x00000a7f, 0x00000a7c, 0x00000a7a, 0x00000a77, 0x00000a76, 0x00000a73,
0x00000a70, 0x00000a6f, 0x00000a6b, 0x00000a6a, 0x00000a68, 0x00000a65,
0x00000a64, 0x00000a61, 0x00000a5f, 0x00000a5e, 0x00000a5c, 0x00000a5a,
0x00000a58, 0x00000a55, 0x00000a55, 0x00000a52, 0x00000a50, 0x00000a4f,
0x00000a4d, 0x00000a4b, 0x00000a4a, 0x00000a48, 0x00000a46, 0x00000a45,
0x00000a43, 0x00000a42, 0x00000a41, 0x00000a3e, 0x00000a3d, 0x00000a3c,
0x00000a3a, 0x00000a39, 0x00000a37, 0x00000a36, 0x00000a35, 0x00000a33,
0x00000a32, 0x00000a31, 0x00000a2f, 0x00000a2f, 0x00000a2c, 0x00000a2c,
0x00000a2b, 0x00000a29, 0x00000a28, 0x00000a27, 0x00000a25, 0x00000a25,
0x00000a23, 0x00000a22, 0x00000a21, 0x00000a20, 0x00000a1f, 0x00000a1e,
0x00000a1d, 0x00000a1b, 0x00000a1b, 0x00000a19, 0x00000a19, 0x00000a17,
0x00000a17, 0x00000a16, 0x00000a14, 0x00000a14, 0x00000a13, 0x00000a13,
0x00000a11, 0x00000a10, 0x00000a0f, 0x00000a0f, 0x00000a0e, 0x00000a0c,
0x00000a0c, 0x00000a0b, 0x00000a0b, 0x00000a09, 0x00000a08, 0x00000a08,
0x00000a07, 0x00000a06, 0x00000a06, 0x00000a05, 0x00000a04, 0x00000a04,
0x00000a02, 0x00000a02, 0x00000a01, 0x00000a00, 0x00000a00, 0x000009ff,
0x000009fe, 0x000009fe, 0x000009fd, 0x000009fc, 0x000009fc, 0x000009fb,
0x000009fa, 0x000009fa, 0x000009f9, 0x000009f9, 0x000009f8, 0x000009f7,
0x000009f7, 0x000009f6, 0x000009f6, 0x000009f5, 0x000009f4, 0x000009f3,
0x000009f3, 0x000009f3, 0x000009f2, 0x000009f1,
};
#endif
+161
View File
@@ -0,0 +1,161 @@
/*
* TCM board FTDICommand framing, recovered from Android Java bytecode
* (com.elotouch.ftdi.FTDICommand) and cross-validated on Windows/Linux:
*
* byte[0] = 0x7e
* byte[1..2] = big-endian body length (payload_len + 5)
* byte[3] = checksum over bytes 0..2
* byte[4] = main command
* byte[5] = sub command
* byte[6..7] = frame id (big-endian)
* byte[8..] = payload
* last byte = checksum over body bytes (index 4 .. n-2)
*
* Command table (main 0x02): rotate 0x77 [dir, magnitude],
* light 0x31/0x32/0x33 [on, r, g, b], get-version 0x04, begin 0x05,
* complete 0x06, transmit 0x07.
*/
#include "mag160c_internal.h"
#define TCM_HEADER 0x7e
#define TCM_MAX_PAYLOAD 0xffffu - 5u
static uint8_t checksum(const uint8_t *data, size_t n) {
uint8_t c = 0;
for (size_t i = 0; i < n; ++i) {
c = (uint8_t)(c + data[i]); /* additive mod 256, per FTDICommand */
}
return c;
}
mag160c_error_t mag160c_tcm_encode(uint8_t main_cmd, uint8_t sub_cmd, uint16_t frame_id,
const uint8_t *payload, size_t payload_size,
uint8_t *out, size_t out_cap, size_t *out_size) {
if (out_size == NULL) {
return MAG160C_ERR_INVALID_ARGUMENT;
}
if (payload_size > TCM_MAX_PAYLOAD || (payload_size && payload == NULL)) {
*out_size = 0;
mag160c_set_error("mag160c_tcm_encode: bad payload");
return MAG160C_ERR_INVALID_ARGUMENT;
}
const size_t body_len = payload_size + 5;
const size_t total = body_len + 4;
if (out_cap < total) {
*out_size = 0;
mag160c_set_error("mag160c_tcm_encode: output buffer too small");
return MAG160C_ERR_INVALID_ARGUMENT;
}
uint8_t head[4];
head[0] = TCM_HEADER;
head[1] = (uint8_t)(body_len >> 8);
head[2] = (uint8_t)(body_len & 0xff);
head[3] = checksum(head, 3);
size_t pos = 0;
for (size_t i = 0; i < 4; ++i) {
out[pos++] = head[i];
}
out[pos++] = main_cmd;
out[pos++] = sub_cmd;
out[pos++] = (uint8_t)(frame_id >> 8);
out[pos++] = (uint8_t)(frame_id & 0xff);
if (payload_size) {
memcpy(out + pos, payload, payload_size);
pos += payload_size;
}
out[pos++] = checksum(out + 4, body_len - 1);
*out_size = total;
mag160c_clear_error();
return MAG160C_OK;
}
mag160c_error_t mag160c_tcm_decode(const uint8_t *data, size_t size,
uint8_t *out_main, uint8_t *out_sub,
uint16_t *out_frame_id,
uint8_t *out_payload, size_t out_payload_cap,
size_t *out_payload_size) {
if (data == NULL || out_main == NULL || out_sub == NULL || out_frame_id == NULL ||
out_payload_size == NULL) {
return MAG160C_ERR_INVALID_ARGUMENT;
}
*out_payload_size = 0;
if (size < 5 || data[0] != TCM_HEADER) {
return MAG160C_ERR_BAD_FRAME;
}
if (data[3] != checksum(data, 3)) {
mag160c_set_error("mag160c_tcm_decode: header checksum mismatch");
return MAG160C_ERR_CHECKSUM;
}
const size_t body_len = ((size_t)data[1] << 8) | data[2];
if (body_len < 5 || body_len + 4 > size) {
return MAG160C_ERR_BAD_FRAME;
}
const uint8_t *body = data + 4;
if (body[body_len - 1] != checksum(body, body_len - 1)) {
mag160c_set_error("mag160c_tcm_decode: body checksum mismatch");
return MAG160C_ERR_CHECKSUM;
}
*out_main = body[0];
*out_sub = body[1];
*out_frame_id = (uint16_t)(((uint16_t)body[2] << 8) | body[3]);
const size_t payload_size = body_len - 5;
if (out_payload != NULL) {
if (out_payload_cap < payload_size) {
mag160c_set_error("mag160c_tcm_decode: payload buffer too small");
return MAG160C_ERR_INVALID_ARGUMENT;
}
memcpy(out_payload, body + 4, payload_size);
}
*out_payload_size = payload_size;
mag160c_clear_error();
return MAG160C_OK;
}
mag160c_error_t mag160c_tcm_rotate(int angle, uint8_t *out, size_t out_cap,
size_t *out_size) {
/* vendor: clamp to [-128, 128]; direction 0 = positive, 1 = negative */
if (angle < -128) {
angle = -128;
} else if (angle > 128) {
angle = 128;
}
uint8_t payload[2];
if (angle >= 0) {
payload[0] = 0;
payload[1] = (uint8_t)angle;
} else {
payload[0] = 1;
payload[1] = (uint8_t)(-angle);
}
return mag160c_tcm_encode(0x02, 0x77, 0x0001, payload, sizeof(payload),
out, out_cap, out_size);
}
mag160c_error_t mag160c_tcm_light(mag160c_tcm_light_color_t color,
mag160c_tcm_light_mode_t mode,
uint8_t *out, size_t out_cap, size_t *out_size) {
if (color > MAG160C_TCM_LIGHT_YELLOW || mode > MAG160C_TCM_LIGHT_BREATH) {
mag160c_set_error("mag160c_tcm_light: invalid color/mode");
return MAG160C_ERR_INVALID_ARGUMENT;
}
/* sub 0x31 steady, 0x32 blink, 0x33 breath; payload [on, r, g, b] */
uint8_t payload[4] = {0x01, 0x00, 0x00, 0x00};
switch (color) {
case MAG160C_TCM_LIGHT_OFF: payload[0] = 0x00; break;
case MAG160C_TCM_LIGHT_RED: payload[1] = 0xff; break;
case MAG160C_TCM_LIGHT_GREEN: payload[2] = 0xff; break;
case MAG160C_TCM_LIGHT_BLUE: payload[3] = 0xff; break;
case MAG160C_TCM_LIGHT_YELLOW: payload[1] = 0xff; payload[2] = 0xff; break;
default: break;
}
return mag160c_tcm_encode(0x02, (uint8_t)(0x31 + mode), 0x0001, payload,
sizeof(payload), out, out_cap, out_size);
}
+123
View File
@@ -0,0 +1,123 @@
/*
* Temperature conversion, recovered from libcoresdk.so (ARM64) exported
* CFunctions class:
*
* Calibration(this+0x6a41c):
* diff = (int16)(frame[p] - baseline[p]) >> 1
* band = first i in [0, bands-1) with diff <= threshold[p][i]
* (no match -> band = bands-1)
* v = pwl[(band*pixels + p)*2 + 1] + ((diff * pwl[(band*pixels + p)*2]) >> 12)
* out[p] = clamp(v, 0, 0xffff)
*
* ConvertResponse2Temperature(this+0x73124):
* base = clamp((0xc350 - gain) >> shift, 0, 0xffff)
* v = base + (frame[p] - baseline[p]) * coeff
* saturated count > 0x101 -> coeff -= 1 (auto gain reduction)
*
* T2E / E2TAccQ10 tables are the static vendor calibration curve.
*/
#include "mag160c_internal.h"
#include "mag160c_tables.h"
void mag160c_temp_calibrate(const uint16_t *frame,
const mag160c_temp_tables_t *tables,
uint16_t *out) {
if (frame == NULL || tables == NULL || out == NULL || tables->pixel_count == 0) {
return;
}
const uint32_t pixels = tables->pixel_count;
const uint32_t bands = tables->band_count == 0 ? 1 : tables->band_count;
const uint32_t search = bands - 1;
for (uint32_t p = 0; p < pixels; ++p) {
int32_t diff;
if (tables->has_baseline && tables->baseline != NULL) {
diff = (int32_t)(int16_t)(frame[p] - tables->baseline[p]);
} else {
diff = (int32_t)frame[p];
}
diff >>= 1;
uint32_t band = 0;
if (search > 0 && tables->thresholds != NULL) {
for (uint32_t i = 0; i < search; ++i) {
if (diff <= tables->thresholds[p * search + i]) {
band = i;
break;
}
band = i + 1;
}
}
const size_t entry = ((size_t)band * pixels + p) * 2;
const int32_t coeff = tables->pwl != NULL ? tables->pwl[entry] : 0;
const int32_t offset = tables->pwl != NULL ? tables->pwl[entry + 1] : 0;
int32_t v = offset + ((diff * coeff) >> 12);
if (v < 0) {
v = 0;
} else if (v > 0xffff) {
v = 0xffff;
}
out[p] = (uint16_t)v;
}
}
uint32_t mag160c_temp_convert_response(const uint16_t *frame, uint32_t pixel_count,
const mag160c_temp_gain_t *cfg, uint16_t *out) {
if (frame == NULL || cfg == NULL || out == NULL || pixel_count == 0) {
return 0;
}
const uint32_t shift = cfg->shift & 31;
int32_t base = (int32_t)(0xc350 - cfg->gain);
base >>= shift;
if (base < 0) {
base = 0;
} else if (base > 0xffff) {
base = 0xffff;
}
int32_t coeff = cfg->coeff;
uint32_t saturated = 0;
for (uint32_t p = 0; p < pixel_count; ++p) {
int32_t diff = (int32_t)frame[p];
if (cfg->baseline != NULL) {
diff -= (int32_t)cfg->baseline[p];
}
int32_t v = base + diff * coeff;
if (v >= 0x10000) {
++saturated;
v = 0xffff;
} else {
saturated += (uint32_t)(v >> 31);
if (v < 0) {
v = 0;
}
}
out[p] = (uint16_t)v;
}
return saturated;
}
/*
* T2E piecewise-linear evaluation with Q13 band selection:
* idx = clamp((value + 0xc350) >> 13, 0, 0x111)
* result = T2E[idx] + ((value - idx*8192) * (T2E[idx+1]-T2E[idx]) + 8191) >> 13
* Matches the vendor interp in ReviseTemperature/CorrectTemperature.
*/
int32_t mag160c_temp_t2e_interp(int32_t value) {
int32_t v = value + 0xc350;
int32_t idx = v >> 13;
if (idx < 0) {
idx = 0;
} else if (idx > (int32_t)MAG160C_TEMP_CURVE_ENTRIES - 2) {
idx = (int32_t)MAG160C_TEMP_CURVE_ENTRIES - 2;
}
const int32_t t0 = (int32_t)mag160c_t2e[idx];
const int32_t t1 = (int32_t)mag160c_t2e[idx + 1];
int32_t d = v - (idx << 13);
int32_t interp = (d * (t1 - t0)) >> 13;
if (interp < 0 && (d * (t1 - t0)) & 0x1fff) {
interp += 1; /* round half away from zero, matching asr+csel pattern */
}
return t0 + interp;
}