这里介绍Ti公司的一款低功耗高精度的电容传感器芯片FDC2214,这里我们主要讲的是其简单配置及其使用。
以下大多数的图片都来自于FDC2214的芯片手册,本人只是用来讲解,不做它用。(如有更多需要,自行前往Ti官网进行下载)
首先我们先来看一下芯片的主要特性:
从芯片手册上可知,其供电电压为2.7V到3.6V,谐振频率从10kHz to 10MHz,数据位数为28位,即精度为1/2^28,FDC2214有4个采集通道,其与MCU的通信方式为IIC。
这里我们用软件IIC来实现与FDC2214的通信。
其次我们来看一下芯片的引脚图:
通过芯片手册我们可知写时序为:
读时序为:
读完整个芯片手册我们发现,其从机的地址说的并不是很清楚,这里通过测试发现其从机地址(FDC2214为从机)如下
当ADDR为低时:0x2A,这里二进制表示为0010 1010,这里我们需要移除最高位,即为010 1010_,这里当最后一位为低时表示向FDC2214写入数据,为高时,是读出数据。
及ADDR = L:0x54为写,0x55为读;
同理当ADDR = H:0x56为写,0x57为读;
即通信的原理框图如下(以ADDR为低为例):
(1) 写时序:
(2)读时序:
了解完时序之后,我们就可以开始进行寄存器的配置,这里芯片手册上已经给出该怎么配置,如下图:
这里不细说每个寄存器是干什么的,如有需要,可以参照芯片手册,这里,我们需要改一个寄存器的配置,就是地址为0x1A的寄存器,
该寄存器的值如下:
这里我们需要配置第9位,将其配置为0,使用内部晶振,那么0x1A寄存器的值改为 0x1401。
这里我们开始写程序,
这里IIC的程序,我们使用的是正点原子的库,其库函数如下:
//IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7
}
//
void IIC_Start(void)
{
SDA_OUT();
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0;
}
//
void IIC_Stop(void)
{
SDA_OUT();
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;
delay_us(4);
}
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN();
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;
return 0;
}
//Set up ACK
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//Set up NACK(NO ACK)
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//IIC Send Byte
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;
for(t=0;t<8;t++)
{
//IIC_SDA=(txd&0x80)>>7;
if((txd&0x80)>>7)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//ack = 0 send Nack ack = 1 send ACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();
else
IIC_Ack();
return receive;
}
void IIC_Init(void)函数是初始化 SCL,SDA两个引脚;
void IIC_Start(void) 函数是产生START信号;
void IIC_Stop(void)函数是产生STOP信号;
u8 IIC_Wait_Ack(void)函数是接收从机发送的ACK;
void IIC_Ack(void)函数是主机读完数据后产生应答信号;
void IIC_NAck(void) 函数是主机读完数据后不产生应答信号;
void IIC_Send_Byte(u8 txd) 函数是发送一个字节的数据;
u8 IIC_Read_Byte(unsigned char ack)函数是接收一个字节的数据;
这里我们还需要配置与FDC2214通信的函数,向FDC2214寄存器写的函数为
//这里 FDC_Address_W =0x54
void FDC_write_reg(u8 addr,u16 value) //addr 为寄存器地址,value为需要写入的寄存器数据
{
IIC_Start(); //产生START信号
IIC_Send_Byte(FDC_Address_W); //发送从机地址和写信号
IIC_Wait_Ack(); //等待ACK
IIC_Send_Byte(addr); //发送需要写入的寄存器地址
IIC_Wait_Ack(); //等待ACK
IIC_Send_Byte(value>>8); //发送高8位数据
IIC_Wait_Ack(); //等待ACK
IIC_Send_Byte(value&0xFF); //发送低8位数据
IIC_Wait_Ack(); //等待ACK
IIC_Stop(); //产生STOP信号
delay_ms(1);
}
从FDC2214读16位数据的函数如下:
//FDC_Address_W =0x54
//FDC_Address_R =0x55
//Receive_Date[] 为接收数据的数组
//C_Data 为一个16位无符号的整型
u16 FDC_read_reg(u8 addr)
{
IIC_Start(); //产生START信号
IIC_Send_Byte(FDC_Address_W); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(addr); //发送需要读的寄存器的地址
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(FDC_Address_R); //发送读命令
IIC_Wait_Ack();
Receive_Date[0]=IIC_Read_Byte(1); //读高8位
Receive_Date[1]=IIC_Read_Byte(0); //读低8位
IIC_Stop(); //产生STOP信号
C_Data=(Receive_Date[0]<<8)+ Receive_Date[1];
return C_Data;
}
从FDC2214读高8位数据的函数如下:
u8 FDC_read_reg_high(u8 addr)
{
IIC_Start();
IIC_Send_Byte(FDC_Address_W);
IIC_Wait_Ack();
IIC_Send_Byte(addr);
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(FDC_Address_R);
IIC_Wait_Ack();
Receive_Date[0]=IIC_Read_Byte(1);
Receive_Date[1]=IIC_Read_Byte(0);
IIC_Stop();
return Receive_Date[0];
}
从FDC2214读低8位数据的函数如下:
u8 FDC_read_reg_low(u8 addr)
{
IIC_Start();
IIC_Send_Byte(FDC_Address_W);
IIC_Wait_Ack();
IIC_Send_Byte(addr);
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(FDC_Address_R);
IIC_Wait_Ack();
Receive_Date[0]=IIC_Read_Byte(1);
Receive_Date[1]=IIC_Read_Byte(0);
IIC_Stop();
return Receive_Date[1];
}
FDC2214初始化的函数如下:
void FDC_Start(void)
{
FDC_write_reg(0x08,0x8329); //(CHx_RCOUNT*16)/55M ==9.76ms,,每10ms左右可以读一次值
FDC_write_reg(0x09,0x8329);
FDC_write_reg(0x0A,0x8329);
FDC_write_reg(0x0B,0x8329);
FDC_write_reg(0x10,0x000A); //设置4个通道最小稳定时间
FDC_write_reg(0x11,0x000A);
FDC_write_reg(0x12,0x000A);
FDC_write_reg(0x13,0x000A);
FDC_write_reg(0x14,0x1001); //时钟除以1,设置传感器频率在0.01M到8.5M之间
FDC_write_reg(0x15,0x1001);
FDC_write_reg(0x16,0x1001);
FDC_write_reg(0x17,0x1001);
FDC_write_reg(0x19,0x0000); //不设置中断标志位
FDC_write_reg(0x1B,0xC20D);//使能0,1,2,3通道,且带宽设置为10M
FDC_write_reg(0x1E,0x8000); //设置4个通道的驱动电流
FDC_write_reg(0x1F,0x8000);
FDC_write_reg(0x20,0x8000);
FDC_write_reg(0x21,0x8000);
FDC_write_reg(0x1A,0x1401); //使能FDC2214,且取内部时钟为参考时钟
}
其头文件的配置如下:
#ifndef __FDC2214_H
#define __FDC2214_H
#include <sys.h>
//IO口方向
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
//IO操作函数
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
#define READ_SDA PBin(7) //SDA输入
//IIC函数
void IIC_Init(void);
void IIC_Start(void);
void IIC_Stop(void);
void IIC_Send_Byte(u8 txd);
u8 IIC_Read_Byte(unsigned char ack);
u8 IIC_Wait_Ack(void);
void IIC_Ack(void);
void IIC_NAck(void);
void FDC_write_reg(u8 addr,u16 value);
void FDC_Start(void);
u16 FDC_read_reg(u8 addr);
u8 FDC_read_reg_high(u8 addr);
u8 FDC_read_reg_low(u8 addr);
#endif
这里为了判断我们的程序和FDC2214是没有问题,先读取相应的ID,这里有两个ID可以供我们读取:
这里我们在主函数中需要读出相应的ID,来确定FDC2214和我们IIC的程序没有问题。
其主函数如下:
int main()
{
u8 lcd_id[12];
u16 C_date=0;
u16 ID=0;
u16 C_CH0_data=0;
u16 C_CH0_data_low=0;
u32 C_CHO_data_final=0;
u16 C_CH1_data=0;
u16 C_CH1_data_low=0;
u32 C_CH1_data_final=0;
u16 C_CH2_data=0;
u16 C_CH2_data_low=0;
u32 C_CH2_data_final=0;
u16 C_CH3_data=0;
u16 C_CH3_data_low=0;
u32 C_CH3_data_final=0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init(); //初始化延时函数
LCD_Init(); //初始化LCD显示
uart_init(115200); //初始化串口
IIC_Init(); //初始化 IIC
POINT_COLOR=BLUE;
sprintf((char*)lcd_id,"LCD ID:%04X",lcddev.id);
LCD_Clear(WHITE);
POINT_COLOR=BLUE;
LCD_ShowString(30,70,240,16,24,"C_Chan_0:");
LCD_ShowString(30,100,240,16,24,"C_Chan_1:");
LCD_ShowString(30,130,240,16,24,"C_Chan_2:");
LCD_ShowString(30,160,240,16,24,"C_Chan_3:");
LCD_ShowString(30,190,240,16,24,"DRDY:");
delay_ms(10);
FDC_Start(); //FDC初始化
while(1)
{
LCD_ShowString(30,40,240,16,24,"Successful!");
delay_ms(500);
C_date = FDC_read_reg(0x7E); //读取ID
ID = FDC_read_reg(0x7F); //读取ID
if(C_date!=0xFF00 && ID!=0xFF00) //显示ID
{
LCD_ShowxNum(139,350,ID,5,24,0);
LCD_ShowxNum(139,380,C_date,5,24,0);
}
delay_ms(100);
}
}
FDC2214的数值是28位的,这里我们需要读两个寄存器的值(以CH0为例)分别是0x00,和0x0A:
从芯片手册可以知道(以CH0为例),0x01寄存器中为数据的低16位,0x00寄存器为数据的高12位,将两个数据合起来,即为相应的数据,其程序如下:
if(C_date==0x5449 && ID==0x3055) //当为相应ID时才读取寄存器的值
{
//读通道0数值
C_CH0_data = FDC_read_reg(0x00)&0xFFF;
C_CH0_data_low = FDC_read_reg(0x01);
C_CHO_data_final =((C_CH0_data<<16)+C_CH0_data_low);
//读通道1数值
delay_ms(50);
C_CH1_data = FDC_read_reg(0x02)&0xFFF;
C_CH1_data_low = FDC_read_reg(0x03);
C_CH1_data_final =((C_CH1_data<<16)+C_CH1_data_low);
//读通道2数值
delay_ms(50);
C_CH2_data=FDC_read_reg(0x04)&0xFFF;
C_CH2_data_low = FDC_read_reg(0x05);
C_CH2_data_final =((C_CH2_data<<16)+C_CH2_data_low);
//读通道3数值
delay_ms(50);
C_CH3_data=FDC_read_reg(0x06)&0xFFF;
C_CH3_data_low = FDC_read_reg(0x07);
C_CH3_data_final =((C_CH3_data<<16)+C_CH3_data_low);
//显示相应通道读出来的值
LCD_ShowxNum(139,70,C_CHO_data_final,9,24,0);
LCD_ShowxNum(139,100,C_CH1_data_final,9,24,0);
LCD_ShowxNum(139,130,C_CH2_data_final,9,24,0);
LCD_ShowxNum(139,160,C_CH3_data_final,9,24,0);
}
读取完数值之后,其电容的计算公式以可在芯片手册找到,如下:
这里我们可以知道其电容的计算就是根据LC谐振频率的变化来计算。
这里,CHx_FIN_SEL和f_REFx可以从下图得知,DATAx即为我们读出的数值,这里就可以算出电容值。
这里我们如果发现ID读得不对,可以将SCL和SDA通过电阻(本人用的4.7K)上拉,这样就可以读取正确的ID值。