fix(sensor): 更新传感器去皮逻辑,改用中值滤波以提高噪声抑制效果
This commit is contained in:
+80
-21
@@ -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.0ms,20ms 帧周期内刚好用满
|
* 4 通道 = 19.4ms,20ms 帧周期内刚好用满
|
||||||
* 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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user