智能称体脂称实现(代码与基本数据处理篇)
(本文均出于个人理解而写,仅用于学习和交流,某些过程可能不一定正确,希望各位提出意见进行交流,共同进步)
AFE4300的配置是比较简单的,从配置到处理基本的数据,主要有3个方面:SPI配置,AFE4300配置,基本的数据处理。(由于当时板子没做成一块,用STM32产生1M时钟再用杜邦线连接时干扰较大,于是时钟没有用STM32产生)
SPI配置与接口封装
STM32使用库函数来进行开发可以加快开发进度,为了方便,我们也使用了STM32的库函数,按照AFE4300的SPI配置说明,其初始化函数可参考如下:
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );//PORTB时钟使能
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );//SPI2时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15上拉
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行同步时钟的空闲状态为低电平,看手册
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //???串行同步时钟的第二个跳变沿(上升或下降)数据被采样 (两个都可以试试)
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外设
SPI2_ReadWriteByte(0xff);//启动传输
}
另外有代码进行其他一些设置,为了方便使用SPI进行读写,在STM32的SPI读写函数的基础上,我们又封装了两个函数,spiWrite和spiRead。spiWrite函数用于写寄存器,其调用方法非常简单,函数可参考如下:
/**
*spiWrite - 写AFE4300寄存器
*@spiAddr:寄存器地址
*@spiData:16位的数据
*/
void spiWrite (unsigned char spiAddr, unsigned short spiData)
{//先发送地址,再发送数据高8,低8
SPI_AFE4300_CS = 1;
SPI_AFE4300_CS = 0; //使能器件
SPI2_ReadWriteByte(spiAddr);
SPI2_ReadWriteByte(spiData >> 8);// Load MSB write data
SPI2_ReadWriteByte(spiData); // Load LSB write data
SPI_AFE4300_CS = 1;
}
spiRead函数用于读寄存器,代码可参考如下:/**
*spiRead - 读取AFE4300寄存器
*@spiAddr:寄存器地址
*返回unsigned short 类型的数据(16位)
*/
unsigned short spiRead(unsigned char spiAddr)
{
unsigned short spiData;
SPI_AFE4300_CS = 1;
SPI_AFE4300_CS = 0; //使能器件
spiData = SPI2_ReadWriteByte(0x20 | spiAddr); //发送读取状态寄存器命令,返回读取到的值 现在这个是没用的
spiData = (SPI2_ReadWriteByte(0x00)) << 8; // Read MSB data
spiData |= SPI2_ReadWriteByte(0x00);// Read LSB data
SPI_AFE4300_CS = 1; //取消片选
spiWrite (spiAddr, spiData); // Writeback read data due to feature bug on the BCM device
return spiData; // Return SPI read data
}
有了这两个接口,对于AFE4300的配置就更加的方便和简洁。AFE4300配置
AFE4300的配置完全按照其datasheet所写进行初始化。其中复位引脚RST(53)低电平复位,高电平正常操作,在复位完成之后,即可开始对AFE4300进行初始化。以IQ模式为例,其初始化可参考下面的一小段代码:
// Reset AFE4300
GPIO_ResetBits(GPIOG,GPIO_Pin_14);
delay_ms(1);
GPIO_SetBits(GPIOG,GPIO_Pin_14);
//0x01 ADC_CONTROL_REGISTER1 这里很多位都是配置不同功能的
spiWrite(0x01,0x4170); //860SPS
//DEVICE_CONTROL1 第0位和第2位 和电源相关
//开BMP还是体重
spiWrite(0x02,0x0000);//空的寄存器
spiWrite(0x03,0xFFFF); //空的寄存器
spiWrite(0x09,0x6006);
//设置DAC频率 250k
spiWrite(0x0E,0x00FF);
//开一个电流的通道 0:+ 1:-
spiWrite(0x0A,0x0408);
//开一个电压测量通道 0:+ 1:-
spiWrite(0x0B,0x0408);
//分频 IQ_DEMOD_CLK BCM_DAC_FREQ
// 1 1M 250K !
// 2 500K 125K !
// 4 250K 62.5K
// 8 125K 31.25K
// 16 62.5K 15.625K
// 32 31.25K 7.8125K
//开启IQ模式
spiWrite(0x0C,0x0800);
//IQ_DEMOD_CLK_DIV_FAC:1分频
spiWrite(0x0F,0x0000);
配置好了之后即可进行ADC的读取。
基本的数据处理
我们测量人体的阻抗得到的是一个复阻抗,如下面的公式所述由IQ调制解调的原理,最终可以得到I分量和Q分量如下:
进而有
我们测量所得到的是I分量和Q分量的值,因此通过上述公式可以算出阻抗Z,但是还有一个系数K为止,芯片有几个引脚是用来连接校准电阻的,但是由于干扰的情况不同,K并不是唯一的,即K也是一个变量,会随着外界的环境而变。我们可以通过分别开启IQ通道读取IQ分量:
spiWrite(0x10,0x0063); //开启I通道
afe4300Data_new = spiRead(0);//读ADC
spiWrite(0x10,0x0065); //开启Q通道
afe4300Data_new = spiRead(0);//读ADC
但是,直接读取ADC的值的时候,数据的波动是非常大的,因此需要采取滤波,至于采取什么滤波算法,因为之前接触到一些传感器的滤波都是使用卡尔曼滤波,出于学习的目的,我也是使用了卡尔曼滤波,卡尔曼滤波的相关学习可参考我的另一篇博客项目应用中的卡尔曼滤波。使用了卡尔曼滤波后数据变得比较稳定,但是K值怎么确定呢?我的做法是采用最小二乘法进行数据拟合。我们制作了一排电阻,其阻值由小到大,然后使用AFE4300对这些电阻进行测量,研究其测量值与真实值之间的关系,将结果使用matlab进行处理,代码如下(包含测试数据):
function AFE4300_Data
clc;
clear;
close all;
%a,b,保存实际测得的数据
x = [0.782586286 4.660827093 16.670862 20.89953036 27.18909184 32.8900304 45.89909348 57.44171326 84.29415621 102.9043944];%测量值 频率
y = [19.7 39.5 98.8 119.1 149.1 177.7 239.7 296.0 423.0 512.0];%实际值
hold on
h1 = plot(x,y)%画出实际的曲线
legend([h1(1)], '实际曲线');%显示格式
p = polyfit(x,y,1)%进行线性拟合 p保存的系数从高到低
xlabel('测量值');
ylabel('真实值');
figure
t=0:200;
s = 4.8128*t + 18.0136;
hold on
h2 = plot(t,s,'r+')
s = polyval(p,t);%画出拟合后的曲线
h3 = plot(t,s,'b')
xlabel('测量值');
ylabel('真实值');
legend([h2(1) h3(1)], '算式拟合','拟合曲线');%显示格式
可以看到结果呈现很好的线性,因此可以得到K值,K值算出来后,可用已经得到的K值,带回去测量那一排电阻,得到的结果与真实值很接近,因此这种做法应该是可行的。按照这种方法可以得到人体的阻抗,当然这只是第一步,由阻抗在推算出其他的东西需要做非常多的工作,TI的工程师说有很多公司有很多的paper在研究这个。具体要自己再去看相关的论文。