ADS1256+FFT+卡尔曼滤波 F4
引脚初始化
void bsp_InitADS1256(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
#ifdef SOFT_SPI
/* 打开GPIO时钟 */
RCC_AHB1PeriphClockCmd(RCC_SCK | RCC_DIN | RCC_DOUT | RCC_CS | RCC_DRDY, ENABLE);
/* 配置几个推完输出IO */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出 /* 设为推挽输出口 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; /* IO口最大速度 */
GPIO_InitStructure.GPIO_Pin = PIN_SCK;
GPIO_Init(PORT_SCK, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = PIN_DIN;
GPIO_Init(PORT_DIN, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = PIN_CS;
GPIO_Init(PORT_CS, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = PIN_SYNC;
GPIO_Init(PORT_SYNC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = PIN_RST;
GPIO_Init(PORT_RST, &GPIO_InitStructure);
/* 配置GPIO为浮动输入模式(实际上CPU复位后就是输入状态) */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//普通输入模式
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//浮空
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = PIN_DOUT;
GPIO_Init(PORT_DOUT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = PIN_DRDY;
GPIO_Init(PORT_DRDY, &GPIO_InitStructure);
RST_0();
delay_ms(1);
SYNC_1();
RST_1();
delay_ms(100);
//
CS_1();
SCK_0(); /* SPI总线空闲时,钟线是低电平 */
DI_1();
#endif
//ADS1256_CfgADC(ADS1256_GAIN_1, ADS1256_1000SPS); /* 配置ADC参数: 增益1:1, 数据输出速率 1KHz */
}
#define RCC_SCK RCC_AHB1Periph_GPIOC
#define PORT_SCK GPIOC
#define PIN_SCK GPIO_Pin_11
#define RCC_DIN RCC_AHB1Periph_GPIOC
#define PORT_DIN GPIOC
#define PIN_DIN GPIO_Pin_10
#define RCC_CS RCC_AHB1Periph_GPIOC
#define PORT_CS GPIOC
#define PIN_CS GPIO_Pin_12
#define RCC_DRDY RCC_AHB1Periph_GPIOC
#define PORT_DRDY GPIOC
#define PIN_DRDY GPIO_Pin_3
#define RCC_DOUT RCC_AHB1Periph_GPIOC
#define PORT_DOUT GPIOC
#define PIN_DOUT GPIO_Pin_9
#define RCC_SYNC RCC_AHB1Periph_GPIOC
#define PORT_SYNC GPIOC
#define PIN_SYNC GPIO_Pin_8
#define RCC_RST RCC_AHB1Periph_GPIOC
#define PORT_RST GPIOC
#define PIN_RST GPIO_Pin_13
GPIO配置
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出 /* 设为推挽输出口 */
速率
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
浮空输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//普通输入模式
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//浮空
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
其余配置与例程相同
外部中断配置
/* Enable AFIO clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
/* Connect EXTI3 Line to PC3 pin */
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, GPIO_PinSource3);
采集数据
放ads1256.c
uint8_t ch_num;
int32_t adc[8];
int32_t volt[8];
uint8_t i;
int32_t iTemp;
char infoShowLCD[64];
void ADS_GetAllValue(void)
{
// if(g_tADS1256.ReadOver) //所有通道读取完毕
// {
g_tADS1256.ReadOver = 0;
for (i = 0; i < ch_num; i++) //数据提取与转换
{
adc[i] = ADS1256_GetAdc(i); /* 从全局缓冲区读取采样结果。 采样结果是在中断服务程序中读取的。*/
/* 4194303 == (2^24)/4, ADS1256芯片采集电压范围为正负2*Vref,2.5V只占其量程1/4,故4194303对应2.5V*/
volt[i] = ((int64_t)adc[i] * 2523200) / 4194303;
/*2509700,单位uV,为基准源芯片真实电压值。如需采集准确,需采得基准源芯片输出的准确电压值进行换算*/
/*基准源是U1左上角的铜点,测量时正接铜点,负接地*/
}
for (i = 0; i < ch_num; i++) //打印到串口
{
iTemp = volt[i]; /* uV */
if (iTemp < 0) //采集到负电压
{
iTemp = -iTemp;
//if(i==0)
//printf("%d=(-%d.%03d %03d V),\t\r\n", i, iTemp /1000000, (iTemp%1000000)/1000, iTemp%1000);//%6X adc[i]
}
else //采集到正电压
{
//if(i==0)
//printf("%d=( %d.%03d %03d V),\t\r\n", i, iTemp/1000000, (iTemp%1000000)/1000, iTemp%1000);//%6X adc[i]
}
}
for(i=0;i<4;i++)
{
switch(i)
{
case 0:
sprintf(infoShowLCD, "DIFCH%d:", i);
LCD_ShowString(0,20,10*15,16,16,( char*)infoShowLCD);
if(volt[i]<0)
{
volt[i] = -volt[i];
sprintf(infoShowLCD, "-%07duV", volt[i]);
LCD_ShowString(70,20,10*15,16,16,(char*)infoShowLCD);
}
else
{
sprintf(infoShowLCD, "+%07duV", volt[i]);
LCD_ShowString(70,20,10*15,16,16,(char*)infoShowLCD);
}
break;
case 1:
sprintf(infoShowLCD, "DIFCH%d:", i);
LCD_ShowString(0,40,10*15,16,16,( char*)infoShowLCD);
if(volt[i]<0)
{
volt[i] = -volt[i];
sprintf(infoShowLCD, "-%07duV", volt[i]);
LCD_ShowString(70,40,10*15,16,16,(char*)infoShowLCD);
}
else
{
sprintf(infoShowLCD, "+%07duV", volt[i]);
LCD_ShowString(70,40,10*15,16,16,(char*)infoShowLCD);
}
break;
case 2:
sprintf(infoShowLCD, "DIFCH%d:", i);
LCD_ShowString(0,60,10*15,16,16,( char*)infoShowLCD);
if(volt[i]<0)
{
volt[i] = -volt[i];
sprintf(infoShowLCD, "-%07duV", volt[i]);
LCD_ShowString(70,60,10*15,16,16,(char*)infoShowLCD);
}
else
{
sprintf(infoShowLCD, "+%07duV", volt[i]);
LCD_ShowString(70,60,10*15,16,16,(char*)infoShowLCD);
}
break;
case 3:
sprintf(infoShowLCD, "DIFCH%d:", i);
LCD_ShowString(0,80,10*15,16,16,( char*)infoShowLCD);
if(volt[i]<0)
{
volt[i] = -volt[i];
sprintf(infoShowLCD, "-%07duV", volt[i]);
LCD_ShowString(70,80,10*15,16,16,(char*)infoShowLCD);
}
else
{
sprintf(infoShowLCD, "+%07duV", volt[i]);
LCD_ShowString(70,80,10*15,16,16,(char*)infoShowLCD);
}
break;
}
}
// }
}
定时器3中断
float ADSValue_CH0[fft_adc_n] = {0}; //通道0的数据,用于FFT处理
void TIM3_IRQHandler(void)
{
static int i = 0;
static int adc_n = 0;
float temp;
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
if(g_tADS1256.ReadOver)
{
ADS_GetAllValue(); //采集4个通道的电压值
ADSValue_CH0[adc_n] = volt[0]; //存放通道0的数据
//ADSValue_CH0[adc_n] = ADS1256_GetAdc(0);
//printf("adc_n:%d\t%.0f\r\n",adc_n,ADSValue_CH0[adc_n]);
adc_n++;
if(adc_n == fft_adc_n)
{
adc_n = 0;
ad_flag = 1; //判断完成一次数据采集
}
}
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
main.c
int main(void)
{
u8 lcd_id[12]; //存放LCD ID字符串
uint8_t id;
delay_init(168); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
KEY_Init();
LCD_Init();//默认打印LCD,ID到串口
POINT_COLOR=RED;
AD985x_Init(AD9851,SERIAL); //初始化控制AD9851需要用到的IO口,设置为串行通讯
sprintf((char*)lcd_id,"LCD ID:%04X",lcddev.id);//将LCD ID打印到lcd_id数组。
POINT_COLOR=RED;
LCD_ShowString(70,0,200,16,16,(uint8_t*)"2019-C TEST");
delay_ms(1000); /* 等待上电稳定,等基准电压电路稳定, bsp_InitADS1256() 内部会进行自校准 */
bsp_InitADS1256(); /* 初始化配置ADS1256. PGA=1, DRATE=30KSPS, BUFEN=0, 输入正负5V */
id = ADS1256_ReadChipID();
if (id != 3)
printf("Error, ASD1256 Chip ID = 0x%X\r\n", id);
else
printf("Ok, ASD1256 Chip ID = 0x%X\r\n", id);
ADS1256_CfgADC(ADS1256_GAIN_1, ADS1256_2000SPS); /* 配置ADC参数: 增益1:1, 数据输出速率 100Hz */
ADS1256_StartScan(1); /*0表示单端8路,1表示差分4路 */
ch_num = 4;
TIM3_Int_Init(5000-1,168-1); /* 通道数 = 8*/
while(1)
{
getFreq();
//ADS_GetAllValue();
}
}
FFT
fft.h
#ifndef _FFT_H
#define _FFT_H
#include "sys.h"
#include "arm_math.h"
#define fft_adc_n 256
extern float fft_in_adc_data[fft_adc_n*2]; // FFT输入,实部是ADC值,虚部补0
extern float fft_out_adc_data[fft_adc_n]; // FFT输出
extern uint16_t fft_flag[fft_adc_n]; // 数据处理时暂时存放数据
extern int ad_flag; //数据采集完成标志位
float getFreq(void); //获取频率
#endif
fft.c
#include "fft.h"
#include "usart.h"
#include "ADS1256.h"
#include "arm_const_structs.h"
float fft_in_adc_data[fft_adc_n*2] = {0};
float fft_out_adc_data[fft_adc_n] = {0};
uint16_t fft_fft_flag[fft_adc_n] = {0};
int fft_i,fft_j;
int k = 1; // 找第k大值
float32_t maxValue = 0; // ADC最大值
uint32_t maxIndex = 0; // 最大值下标
float freq = 0; // 频率
int ad_flag = 0;
uint16_t fft_flag[fft_adc_n] = {0};
float getFreq(void)
{
if(ad_flag == 1)
{
printf("finish\r\n");
ad_flag = 0;
for(fft_i=0;fft_i<fft_adc_n;fft_i++)
{
fft_in_adc_data[fft_i*2] = (double)ADSValue_CH0[fft_i]/1000000; //实部
fft_in_adc_data[fft_i*2+1] = 0; //虚部
printf("%d\t%.7f\r\n",fft_i,fft_in_adc_data[fft_i*2]);
}
printf("\r\n");
arm_cfft_f32(&arm_cfft_sR_f32_len1024,fft_in_adc_data,0,1);
arm_cmplx_mag_f32(fft_in_adc_data,fft_out_adc_data,fft_adc_n);
fft_out_adc_data[0]/=256;
for(fft_i=1;fft_i<fft_adc_n;fft_i++)
fft_out_adc_data[i]/=128;
for(fft_i=0;fft_i<fft_adc_n;fft_i++)
{
printf("%.5f\t",fft_out_adc_data[fft_i]);
}
printf("\r\n");
//arm_max_f32(&fft_out_adc_data[1],fft_adc_n/2-1,&maxValue,&index);
/****查找最大值及其下标*********/
memset(fft_flag, 0, sizeof(fft_flag));
for (fft_i = 1; fft_i <= k; fft_i++)
{
maxValue = 0.0;
for (fft_j = 1; fft_j < fft_adc_n / 2; fft_j++)
{
if (fft_flag[fft_j] == 1)
break;
if (fft_out_adc_data[fft_j] > maxValue)
{
maxValue = fft_out_adc_data[fft_j];
maxIndex = fft_j;
}
}
fft_flag[maxIndex] = 1;
}
printf("index:%d\r\n",maxIndex);
printf("Vmax:%.2f\r\n",maxValue);
freq = (float)2000/fft_adc_n * (maxIndex) /2; //2000为采样率
printf("freq: %.3f",freq);
ad_flag = 0;
}
return freq;
}
问题总结
- (如果采集全部通道并进行数据处理)ADS1256采样速率慢,TIM3配置ARR为5000时才能实现数据不重复(PSC:168)
- 输入正弦波03V,实际采样0.8V2.1V;输入正弦波-2.5V2.5V,实际采样-1.351.35V
- 配置ARR为2,有2~3个数重复,频率不准
再改
中断
float ADSValue_CH0[fft_adc_n] = {0};
void TIM3_IRQHandler(void)
{
static int i = 0;
static int adc_n = 0;
float temp;
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
// if(LED1 == 1)
// AD985x_SetFre_Phase(500,0);
// else
// AD985x_SetFre_Phase(5000,0);
// LED1 = !LED1;
// if(g_tADS1256.ReadOver)
// {
//ADS_GetAllValue(); //采集4个通道的电压值
//ADSValue_CH0[adc_n] = volt[0]; //存放通道0的数据
ADSValue_CH0[adc_n] = ((int64_t)ADS1256_GetAdc(0)* 2523200) / 4194303;
//printf("adc_n:%d\t%.0f\r\n",adc_n,ADSValue_CH0[adc_n]);
adc_n++;
if(adc_n == fft_adc_n)
{
adc_n = 0;
ad_flag = 1;
}
// }
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
不进行全部通道的采集,直接调用采集数据函数
最后
采集函数不需要放在中断中
ADS1256_CfgADC(ADS1256_GAIN_1, ADS1256_2000SPS); /* 配置ADC参数: 增益1:1, 数据输出速率 100Hz */
初始化时已经设置了采样率ADS1256_2000SPS
因此只需放在while(1)
中即可
int main(void)
{
u8 lcd_id[12]; //存放LCD ID字符串
uint8_t id;
static int i = 0;
static int adc_n = 0;
KF_Struct KFS1;
KF_Struct_Init(&KFS1);
delay_init(168); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
KEY_Init();
LCD_Init();//默认打印LCD,ID到串口
POINT_COLOR=RED;
AD985x_Init(AD9851,SERIAL); //初始化控制AD9851需要用到的IO口,设置为串行通讯
sprintf((char*)lcd_id,"LCD ID:%04X",lcddev.id);//将LCD ID打印到lcd_id数组。
POINT_COLOR=RED;
LCD_ShowString(70,0,200,16,16,(uint8_t*)"2019-C TEST");
delay_ms(1000); /* 等待上电稳定,等基准电压电路稳定, bsp_InitADS1256() 内部会进行自校准 */
bsp_InitADS1256(); /* 初始化配置ADS1256. PGA=1, DRATE=30KSPS, BUFEN=0, 输入正负5V */
id = ADS1256_ReadChipID();
if (id != 3)
printf("Error, ASD1256 Chip ID = 0x%X\r\n", id);
else
printf("Ok, ASD1256 Chip ID = 0x%X\r\n", id);
ADS1256_CfgADC(ADS1256_GAIN_1, ADS1256_2000SPS); /* 配置ADC参数: 增益1:1, 数据输出速率 100Hz */
ADS1256_StartScan(0); /*0表示单端8路,1表示差分4路 */
ch_num = 1; /* 通道数 = 8*/
TIM3_Int_Init(1000-1,168-1);
while(1)
{
if(g_tADS1256.ReadOver)
{
ADS_GetAllValue();
ADSValue_CH0[adc_n] = volt[0]/1000000; //存放通道0的数据
//KMFilter(&KFS1,ADSValue_CH0[adc_n]);
printf("%.5f\n",ADSValue_CH0[adc_n]);
adc_n++;
if(adc_n == fft_adc_n)
{
adc_n = 0;
ad_flag = 1;
// for(i=0;i<fft_adc_n;i++)
// {
// ADSValue_CH0[i] = KMFilter(&KFS1,ADSValue_CH0[i]);
// }
}
}
//filter(&KFS1,ADSValue_CH0,fft_adc_n);
getFreq();
}
}
附:卡尔曼滤波
kalman.h
#ifndef _KALMAN_H
#define _KALMAN_H
typedef struct Kalman_Filter{
float x_last;
float P_now;
float P_last;
float K;
float R_cov;
float Q_cov;
float out;
}KF_Struct;
void KF_Struct_Init(KF_Struct* KFS); //初始化函数
float KMFilter(KF_Struct* KFS,float z); //对一个数据滤波
void filter(KF_Struct* KFS,float *data,int n); //对数组滤波
#endif
kalman.c
#include "kalman.h"
#include "usart.h"
void KF_Struct_Init(KF_Struct* KFS)
{
KFS->x_last =0;
KFS->P_now =0;
KFS->P_last =0.02;
KFS->K =0;
KFS->Q_cov =0.1;//过程激励噪声协方差,参数可调 //调大:实际占比多
KFS->R_cov =30;//测量噪声协方差,与仪器测量的性质有关,参数可调 //调大:预测占比多
KFS->out =0;
}
/*
* @brief 卡尔曼滤波器
* @param KFS:卡尔曼滤波器结构体指针
* @param z:测量仪器的输入量
* @return 当前时刻的最优估计值
*/
float KMFilter(KF_Struct* KFS,float z)
{
KFS->P_now = KFS->P_last + KFS->Q_cov;
KFS->K = KFS->P_now / (KFS->P_now + KFS->R_cov );
KFS->out = KFS->out + KFS->K * (z - KFS->out);
//printf("%.2f\t",z);
KFS->P_last = (1 - KFS->K)* KFS->P_now;
return KFS->out;
}
void filter(KF_Struct* KFS,float *data,int n)
{
static int i;
//KF_Struct KM;
for(i=0;i<n;i++)
{
data[i] = KMFilter(KFS,data[i]);
}
}
参考文章:手把手教你学-卡尔曼滤波(附代码)
参数调制方法
主要调节R_cov
和Q_cov
R_cov
调大,数据倾向实际采样
Q_cov
调大,数据倾向预测值
参考文章:卡尔曼滤波参数是如何影响滤波效果的?——KF第一篇笔记