fix(sensor): 更新传感器去皮逻辑,改用中值滤波以提高噪声抑制效果

This commit is contained in:
2026-05-11 15:25:02 +08:00
parent 353f9aca13
commit cb758045f8
+80 -21
View File
@@ -11,10 +11,10 @@
LOG_MODULE_REGISTER(sensor, LOG_LEVEL_INF); LOG_MODULE_REGISTER(sensor, LOG_LEVEL_INF);
/* /*
* 每通道每帧采 17 次取值。3750 SPS 下: * 每通道每帧采 17 次取值。3750 SPS 下:
* 首次切 MUX 建立 0.44ms + 后续 16 次各 0.27ms = 4.76ms/通道 * 17 次各 0.27ms = 4.86ms/通道
* 4 通道 = 19.0ms20ms 帧周期内刚好用满 * 4 通道 = 19.4ms20ms 帧周期内刚好用满
* 17 次均值提供 √17 ≈ 4.12× 噪声抑制 (≈ 2.04 bit) * 中值滤波对脉冲/尖峰干扰的抑制力强于均值
*/ */
#define AVG_COUNT 17 #define AVG_COUNT 17
#define DEADZONE_THRESHOLD 250 #define DEADZONE_THRESHOLD 250
@@ -37,7 +37,7 @@ static const float sensor_y[4] = {
}; };
/* --- 内部状态 --- */ /* --- 内部状态 --- */
static int32_t sensor_offsets[4]; static int32_t sensor_offsets[4]; // 去皮的零点偏移值
static int32_t filtered[4]; static int32_t filtered[4];
static atomic_t tare_requested; static atomic_t tare_requested;
@@ -47,6 +47,16 @@ static atomic_t tare_requested;
static K_THREAD_STACK_DEFINE(sensor_stack, SENSOR_STACK_SIZE); static K_THREAD_STACK_DEFINE(sensor_stack, SENSOR_STACK_SIZE);
static struct k_thread sensor_thread; static struct k_thread sensor_thread;
/**
* @brief 对整型数组做原地升序排序。
*
* @param arr 待排序的数据缓冲区;这里要求调用方传入可写数组,
* 因为去皮流程需要直接在采样缓冲区上重排以减少额外拷贝。
* @param n 数组元素个数;显式传入长度是为了让该内部工具函数只依赖调用现场,
* 避免隐式假设固定采样数后影响后续维护。
*
* @return 无返回值。
*/
static void sort_array(int32_t *arr, int n) { static void sort_array(int32_t *arr, int n) {
for (int i = 0; i < n - 1; i++) { for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) { for (int j = 0; j < n - i - 1; j++) {
@@ -59,6 +69,13 @@ static void sort_array(int32_t *arr, int n) {
} }
} }
/**
* @brief 执行一次四路传感器去皮,更新每路零点偏移。
*
* @param 无。
*
* @return 无返回值。
*/
static void do_tare(void) { static void do_tare(void) {
LOG_INF("Taring..."); LOG_INF("Taring...");
int32_t sorted_buf[AVG_COUNT]; int32_t sorted_buf[AVG_COUNT];
@@ -80,25 +97,42 @@ static void do_tare(void) {
memset(filtered, 0, sizeof(filtered)); memset(filtered, 0, sizeof(filtered));
} }
/**
* @brief 完成一帧四通道采集,并写入去皮后的滤波结果。
*
* @param 无。
*
* @return 无返回值。
*/
static void acquire_cycle(void) { static void acquire_cycle(void) {
for (int ch = 0; ch < 4; ch++) { int32_t samples[AVG_COUNT];
int64_t sum = 0;
for (int ch = 0; ch < 4; ch++) {
ads1256_write_reg(ADS1256_REG_MUX, mux_channels[ch]); ads1256_write_reg(ADS1256_REG_MUX, mux_channels[ch]);
ads1256_sync_wakeup(); ads1256_sync_wakeup();
for (int k = 0; k < AVG_COUNT; k++) { for (int k = 0; k < AVG_COUNT; k++) {
ads1256_wait_drdy(50); ads1256_wait_drdy(50);
sum += ads1256_read_data(); samples[k] = ads1256_read_data();
} }
int32_t avg = (int32_t)(sum / AVG_COUNT); sort_array(samples, AVG_COUNT);
avg -= sensor_offsets[ch]; int32_t median = samples[AVG_COUNT / 2] - sensor_offsets[ch];
if (avg > -DEADZONE_THRESHOLD && avg < DEADZONE_THRESHOLD) avg = 0; if (median > -DEADZONE_THRESHOLD && median < DEADZONE_THRESHOLD) median = 0;
filtered[ch] = avg; filtered[ch] = median;
} }
} }
/**
* @brief 根据四路受力结果计算压力中心(CoP)和总力。
*
* @param[out] out_x 输出 CoP 的 X 坐标,单位由板级几何参数决定;
* @param[out] out_y 输出 CoP 的 Y 坐标;与 @p out_x 配套写回,
* @param[out] out_force 输出总力值;
*
* @retval true 总力高于有效阈值,当前 CoP 可用于对外发布。
* @retval false 总力不足,CoP 被强制归零以避免在几乎无载荷时放大数值噪声。
*/
static bool compute_cop(float *out_x, float *out_y, float *out_force) { static bool compute_cop(float *out_x, float *out_y, float *out_force) {
float forces[4]; float forces[4];
float total = 0.0f; float total = 0.0f;
@@ -126,6 +160,15 @@ static bool compute_cop(float *out_x, float *out_y, float *out_force) {
return true; return true;
} }
/**
* @brief 传感器后台线程主循环,负责采集、去皮处理、CoP 计算和蓝牙发送。
*
* @param p1 Zephyr 线程入口预留参数,当前未使用;
* @param p2 Zephyr 线程入口预留参数,当前未使用;
* @param p3 Zephyr 线程入口预留参数,当前未使用;
*
* @return 无返回值。
*/
static void sensor_thread_fn(void *p1, void *p2, void *p3) { static void sensor_thread_fn(void *p1, void *p2, void *p3) {
(void)p1; (void)p1;
(void)p2; (void)p2;
@@ -167,28 +210,37 @@ static void sensor_thread_fn(void *p1, void *p2, void *p3) {
/* 发送 */ /* 发送 */
ble_transport_send(pkt.bytes, sizeof(pkt.bytes)); ble_transport_send(pkt.bytes, sizeof(pkt.bytes));
int64_t t2 = k_uptime_get();
/* 调试日志(5 Hz */ /* 调试日志(5 Hz */
if (++debug_log_divider >= 10) { if (++debug_log_divider >= 10) {
debug_log_divider = 0; debug_log_divider = 0;
float w[4];
for (int i = 0; i < 4; i++)
w[i] = (float)filtered[i] * ADC_TO_FORCE_SCALE;
LOG_INF( LOG_INF(
"FR=%.2f BR=%.2f BL=%.2f FL=%.2f | total=%.2f kg | cop=(%.1f,%.1f) mm | %s", (double)w[0], (double)w[1], "FR=%.2f BR=%.2f BL=%.2f FL=%.2f | total=%.2f kg | cop=(%.1f,%.1f) mm | %s", (double)filtered[0],
(double)w[2], (double)w[3], (double)force, (double)cop_x, (double)cop_y, valid ? "VALID" : "low"); (double)filtered[1], (double)filtered[2], (double)filtered[3], (double)force, (double)cop_x,
LOG_INF( (double)cop_y, valid ? "VALID" : "low");
"timing: acq=%lld ble=%lld total=%lld ms/frame", (long long)(t1 - t0), (long long)(t2 - t1), LOG_INF("timing: acq=%lld total=%lld ms/frame", (long long)(t1 - t0), (long long)frame_ms);
(long long)frame_ms);
} }
} }
} }
/**
* @brief 初始化传感器采集链路并启动后台采集线程。
*
* @param 无。
*
* @retval 0 初始化成功,传感器线程已经启动并完成一次初始去皮。
* @retval 负值或其他错误码 ADS1256 初始化失败
*/
int sensor_init(void) { int sensor_init(void) {
int err = ads1256_init(); int err = ads1256_init();
if (err) return err; if (err) return err;
/* SELFCAL 后 ADC 模拟链路需几个转换周期才能完全建立,空读排空 pipeline */
for (int i = 0; i < 4; i++) {
ads1256_wait_drdy(50);
(void)ads1256_read_data();
}
memset(sensor_offsets, 0, sizeof(sensor_offsets)); memset(sensor_offsets, 0, sizeof(sensor_offsets));
memset(filtered, 0, sizeof(filtered)); memset(filtered, 0, sizeof(filtered));
@@ -202,6 +254,13 @@ int sensor_init(void) {
return 0; return 0;
} }
/**
* @brief 请求后台线程在下一帧边界执行去皮。
*
* @param 无。
*
* @return 无返回值。
*/
void sensor_perform_tare(void) { void sensor_perform_tare(void) {
atomic_set(&tare_requested, 1); atomic_set(&tare_requested, 1);
} }