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);
/*
* 每通道每帧采 17 次取值。3750 SPS 下:
* 首次切 MUX 建立 0.44ms + 后续 16 次各 0.27ms = 4.76ms/通道
* 4 通道 = 19.0ms20ms 帧周期内刚好用满
* 17 次均值提供 √17 ≈ 4.12× 噪声抑制 (≈ 2.04 bit)
* 每通道每帧采 17 次取值。3750 SPS 下:
* 17 次各 0.27ms = 4.86ms/通道
* 4 通道 = 19.4ms20ms 帧周期内刚好用满
* 中值滤波对脉冲/尖峰干扰的抑制力强于均值
*/
#define AVG_COUNT 17
#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 atomic_t tare_requested;
@@ -47,6 +47,16 @@ static atomic_t tare_requested;
static K_THREAD_STACK_DEFINE(sensor_stack, SENSOR_STACK_SIZE);
static struct k_thread sensor_thread;
/**
* @brief 对整型数组做原地升序排序。
*
* @param arr 待排序的数据缓冲区;这里要求调用方传入可写数组,
* 因为去皮流程需要直接在采样缓冲区上重排以减少额外拷贝。
* @param n 数组元素个数;显式传入长度是为了让该内部工具函数只依赖调用现场,
* 避免隐式假设固定采样数后影响后续维护。
*
* @return 无返回值。
*/
static void sort_array(int32_t *arr, int n) {
for (int i = 0; i < n - 1; i++) {
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) {
LOG_INF("Taring...");
int32_t sorted_buf[AVG_COUNT];
@@ -80,25 +97,42 @@ static void do_tare(void) {
memset(filtered, 0, sizeof(filtered));
}
/**
* @brief 完成一帧四通道采集,并写入去皮后的滤波结果。
*
* @param 无。
*
* @return 无返回值。
*/
static void acquire_cycle(void) {
for (int ch = 0; ch < 4; ch++) {
int64_t sum = 0;
int32_t samples[AVG_COUNT];
for (int ch = 0; ch < 4; ch++) {
ads1256_write_reg(ADS1256_REG_MUX, mux_channels[ch]);
ads1256_sync_wakeup();
for (int k = 0; k < AVG_COUNT; k++) {
ads1256_wait_drdy(50);
sum += ads1256_read_data();
samples[k] = ads1256_read_data();
}
int32_t avg = (int32_t)(sum / AVG_COUNT);
avg -= sensor_offsets[ch];
if (avg > -DEADZONE_THRESHOLD && avg < DEADZONE_THRESHOLD) avg = 0;
filtered[ch] = avg;
sort_array(samples, AVG_COUNT);
int32_t median = samples[AVG_COUNT / 2] - sensor_offsets[ch];
if (median > -DEADZONE_THRESHOLD && median < DEADZONE_THRESHOLD) median = 0;
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) {
float forces[4];
float total = 0.0f;
@@ -126,6 +160,15 @@ static bool compute_cop(float *out_x, float *out_y, float *out_force) {
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) {
(void)p1;
(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));
int64_t t2 = k_uptime_get();
/* 调试日志(5 Hz */
if (++debug_log_divider >= 10) {
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(
"FR=%.2f BR=%.2f BL=%.2f FL=%.2f | total=%.2f kg | cop=(%.1f,%.1f) mm | %s", (double)w[0], (double)w[1],
(double)w[2], (double)w[3], (double)force, (double)cop_x, (double)cop_y, valid ? "VALID" : "low");
LOG_INF(
"timing: acq=%lld ble=%lld total=%lld ms/frame", (long long)(t1 - t0), (long long)(t2 - t1),
(long long)frame_ms);
"FR=%.2f BR=%.2f BL=%.2f FL=%.2f | total=%.2f kg | cop=(%.1f,%.1f) mm | %s", (double)filtered[0],
(double)filtered[1], (double)filtered[2], (double)filtered[3], (double)force, (double)cop_x,
(double)cop_y, valid ? "VALID" : "low");
LOG_INF("timing: acq=%lld total=%lld ms/frame", (long long)(t1 - t0), (long long)frame_ms);
}
}
}
/**
* @brief 初始化传感器采集链路并启动后台采集线程。
*
* @param 无。
*
* @retval 0 初始化成功,传感器线程已经启动并完成一次初始去皮。
* @retval 负值或其他错误码 ADS1256 初始化失败
*/
int sensor_init(void) {
int err = ads1256_init();
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(filtered, 0, sizeof(filtered));
@@ -202,6 +254,13 @@ int sensor_init(void) {
return 0;
}
/**
* @brief 请求后台线程在下一帧边界执行去皮。
*
* @param 无。
*
* @return 无返回值。
*/
void sensor_perform_tare(void) {
atomic_set(&tare_requested, 1);
}