目录
一、前言
在物联网、嵌入式或其他电子类项目开发中,我们常常会遇到误差。这些误差无法避免的出现,且呈非线性,其在没个真实值下的误差大小可能都不一样。
下面我举个例子:我采集到了一组电压值数据,该数据经实际测量比较与实际值有很大差距,且该误差随着实际值的增大而增大,但非线性误差。
数据如下:
测量值 | 实际值 | 误差值 |
---|---|---|
13 | 39 | 26 |
180 | 225 | 45 |
363 | 415 | 52 |
613 | 685 | 72 |
3160 | 3310 | 150 |
二、拟合函数
面对上述示例中的传感器值,是不是头大?那么类似这样的传感器还有很多,比如电容式水位传感器等等。那么面对这种传感器,我们常规的解决办法是给他分出来区间,分别对每个区间加上其该区间的误差值,这样就可以实现由测量值到真实值的补偿效果。
但这种方法也有弊端,比如过于麻烦和浪费事件,需要测量记录并计算,选出认为应该划分的区间,然后再在程序内实现分段补偿。
其实面对这种情况可以用数学方法实现,如果我们能得到测量值X与实际值Y的函数模型,是不是就能对曲线上的任意一点求出其实际值。这就是数据拟合。什么是拟合?拟合并非是要完全符合我们测量值和实际值的变化曲线,而是尽可能的缩小各个点测量值和实际值的误差,因此我们使用特征点拟合出的拟合函数并非一定经过特征点。
数据拟合的方法有很多,可以使用MATLAB等进行,同样的,我们也可以使用在线拟合工具:曲线拟合工具
我们将特征点输入进入,可以帮助我们生成拟合图像和拟合函数。
由示例生成的拟合函数如下(经简化后):
三、程序实现
根据该函数,我们编程单片机程序,编程中一点要考虑芯片的计算能力,对数据做一定简化变形处理。
我使用的基本工程是伦茨ST17H66 ADC中断采集文章中的资源工程。
下载地址为:simpleBlePeripheral-NewTask-ADC.zip资源-CSDN文库
#include "MyTask.h"
uint8 Mytask_id;
//拟合函数
//F(x) = -0.000012471615527228155*x*x+1.078975041723953*x+24.975432160612712
#include "math.h"
__ATTR_SECTION_SRAM__ double calc_val(int mv)
{
if(mv == 0)
return 0;
double part1 = -124.71615527 * (mv /100.0) * (mv / 100.0);
double part2 = 107.89750417 * mv;
part1 = part1 / 1000.0;
part2 = part2 / 100.0;
return (part1 + part2 + 24.975432);
}
adc_Cfg_t adc_cfg =
{
.channel = ADC_BIT(ADC_CH1N_P11),
.is_continue_mode = FALSE,// 连续模式选择,采集完一次,是否继续再次采集
.is_differential_mode = 0x00,//全0单端、否则差分
.is_high_resolution = 0x00,//0代表attenuation模式,1代表bypass模式
};
static void adc_evt(adc_Evt_t* pev)
{
float val = hal_adc_value_cal(adc_cfg.channel,pev->data,pev->size,0,0);
int mv = (int)(val *1000);
mv = calc_val(mv);
uint8 data[30]={'\0'};
sprintf((char *)data,"Val= %dmv",mv);
LOG("\n%s\n",data);
osal_start_timerEx(Mytask_id,MyTask_StartADC_EVT,1000);//再次采集
}
void MyTask_Init( uint8 task_id ){
//保存任务id
Mytask_id = task_id;
//初始化完成事件
osal_set_event(task_id,MyTask_INIT_EVT);
}
static void adcMeasureTask( void )
{
int ret;
bool batt_mode = FALSE;
uint8_t batt_ch = ADC_CH3P_P20;
GPIO_Pin_e pin;
if(FALSE == batt_mode)// 正常采样
{
ret = hal_adc_config_channel(adc_cfg, adc_evt);
}
else
{
if((((1 << batt_ch) & adc_cfg.channel) == 0) || (adc_cfg.is_differential_mode != 0x00))
return;
pin = s_pinmap[batt_ch];
hal_gpio_cfg_analog_io(pin,Bit_DISABLE);
hal_gpio_write(pin, 1);
ret = hal_adc_config_channel(adc_cfg, adc_evt);
hal_gpio_cfg_analog_io(pin,Bit_DISABLE);
}
if(ret)
{
LOG("ret = %d\n",ret);
return;
}
hal_adc_start(INTERRUPT_MODE);/// 启动采样
}
uint16 MyTask_ProcessEvent( uint8 task_id, uint16 events ){
if(events & MyTask_INIT_EVT)
{
adcMeasureTask();
return events ^ MyTask_INIT_EVT;
}
else if(events & MyTask_StartADC_EVT)
{
adcMeasureTask();
//LOG("Satrt ADC...\n");
return events ^ MyTask_StartADC_EVT;
}
else if(events & MyTask_StopADC_EVT)
{
//LOG("Stop ADC...\n");
return events ^ MyTask_StopADC_EVT;
}
return 0;
}
四、结果分析
通过上述程序,在单片机上运行后,误差大幅缩小,经测试数据误差小于5个单位。可以说是非常准确了。