EEPROM和PCF8951这两个外设都是基于I2C通信与单片机通信的,这里就不展开对I2C协议的讲解,感兴趣可以看看其他的大佬对于I2C的讲解,这里我们讲解如何用I2c来通信。I2C的底层代码会在比赛时提供,我们只需在底层文件中加EEPROM和PCF8951有关代码。
AT24C02
基础知识
AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息
EEPROM有2k存储空间,可以存256个字节,可支持一次写入8个字节,一个内存地址可写入一个字节
代码部分
1.写AT24C02函数
1)编程思路
可以根据芯片手册写出写AT24C02函数
步骤:
1.写操作 IIC_SendByte(0xa0);
2.确定要写入的地址 IIC_SendByte(addr);
3.写入数据 IIC_SendByte(dat);
每发送一个字节数据时要启动I2C总线,同理发完数据时,也要关闭I2C总线。每次发送数据时I2C都要等待一次应答。
//写AT24C02
void vWrite_EEPROM(u8 add, u8 dat)
{
IIC_Start(); //I2C总线启动
IIC_SendByte(0xa0); //AT24C02硬件地址,最低位为读写控制位,0-写。1-读
IIC_WaitAck(); //I2C等待应答
IIC_SendByte(add); //I2C发送要存储数据地址
IIC_WaitAck(); //I2C等待应答
IIC_SendByte(dat); //I2C发送要存储的数据
IIC_WaitAck(); //I2C等待应答
IIC_Stop(); //I2C总线停止发送
vDelay_Ms(5); //延时5ms,延长两次写的间隔,不然写入数据会出错,也可能损坏EEPROM
}
2)在main中如何调用函数
//开机次数统计
u8 start_times;
void vEEPROM_Process()
{
start_times = ucRead_EEPROM(0x20); /*AT24C02的地址范围是0x00~0xff*/
vWrite_EEPROM(0x20,++start_times);
}
//数码管操作函数
void vSMG_Process()
{
smg_buf[0]=smg_code[start_times/10];
smg_buf[1]=smg_code[start_times%10];
smg_buf[2]=0x00;
smg_buf[3]=0x00;
smg_buf[4]=0x00;
smg_buf[5]=0x00;
smg_buf[6]=0x00;
smg_buf[7]=0x00;
}
void main(void)
{
vSystem_Init();
vEEPROM_Process();
vTimer2_Init();
while(1)
{
vSMG_Process();
}
}
//中断服务程序
void vTimer2_ISR() interrupt 12 //中断入口
{
vSMG_Display();
}
2.读ATC24C02函数
在数据手册中同样也可以找到读ATC24C02函数的一个流程
步骤:
1.写操作 IIC_SendByte(0xa0);
2.确定要写入的地址 IIC_SendByte(addr);
3.写入数据 IIC_SendByte(dat);
读数据操作与写时类似,但是在接收I2C数据时要发送应答1,表示成功接收到数据
//读AT24C02
u8 ucRead_EEPROM(u8 add)
{
u8 dat; //定义接收数据变量
IIC_Start(); //I2C总线启动
IIC_SendByte(0xa0); //AT24C02硬件地址,写
IIC_WaitAck(); /I2C等待应答
IIC_SendByte(add); //I2C发送要读取数据地址
IIC_WaitAck(); //I2C等待应答
IIC_Start(); //发送重复起始信号,以便从写模式切换到读模式
IIC_SendByte(0xa1); //AT24C02硬件地址,读
IIC_WaitAck(); //I2C等待应答
dat=IIC_RecByte(); //从I2C总线接收一个字节的数据
IIC_SendAck(1); //发送应答信号,表示已经成功接收到数据,1表示发送应答
IIC_Stop(); I2C总线停止发送
return dat;
}
3.小技巧
如果我们要读写不同数据或者多位数据时该如何做
//EEPROM处理函数
u16 u16_write = 1234;
u16 u16_read = 0;
float float_write = 3.1415;
float float_read = 0;
s8 minus_write = -23;
s8 minus_read = 0;
s16 minus_s16_write = -1234;
s16 minus_s16_read = 0;
u8 str_write[11]={"hello world"};
u8 str_read[11];
void vEEPROM_Process()
{
u8 i = 0;
/***************写入u16类型的数据**************/
vWrite_EEPROM(0x00,u16_write>>8);
vWrite_EEPROM(0x01,u16_write);
u16_read = (ucRead_EEPROM(0x00)<<8)+ucRead_EEPROM(0x01);
/***************写入float类型的数据,保留小数点后四位**************/
vWrite_EEPROM(0x03,(u16)(float_write*1000)/256);
vWrite_EEPROM(0x04,(u16)(float_write*1000)%256);
float_read = (ucRead_EEPROM(0x03)*256+ucRead_EEPROM(0x04))/1000.0f;
/***************写入负数,在数字电路中的存储形式是补码**************/
/***************例如-23,其最高位为符号位1(表示负数),23的二进制10111,反码1110 1000,补码1110 1001 -> 0xE9**************/
// if(minus_write<0)
// {
// vWrite_EEPROM(0x06,'-');
// vWrite_EEPROM(0x07,-minus_write);
// }
// if(ucRead_EEPROM(0x06)=='-')
// {
// minus_read = 0 - ucRead_EEPROM(0x07);
// }
vWrite_EEPROM(0x06,minus_write);
minus_read = ucRead_EEPROM(0x06);
vWrite_EEPROM(0x08,minus_s16_write>>8); //取出高8位
vWrite_EEPROM(0x09,minus_s16_write); //取出低8位
minus_s16_read = (ucRead_EEPROM(0x08)<<8)+ucRead_EEPROM(0x09);
/***************写入字符串,字符以ASCII码存储**************/
for(i=0;i<sizeof(str_write);i++)
{
vWrite_EEPROM(0x10+i,str_write[i]);
}
for(i=0;i<sizeof(str_write);i++)
{
str_read[i]=ucRead_EEPROM(0x10+i);
}
}
其实关于读写AT24C02方法还有好几种这里就不一一举例,如果感兴趣的话可以看其他大佬的一个方法
PCF8951
AD-DA芯片基本知识
ADC:模拟信号转化为数字信号,特定电压转换为二进制
DAC:数字信号转化为模拟信号,通过二进制让芯片输出特定的电压
在蓝桥杯所给I2C.c底层文件中修改添加读取ADC和DAC转换函数
ADC—DAC编程
1.读取ADC函数流程
//读ADC函数
u8 Read_ADC(u8 chnl)
{
u8 ADC_Val = 0; //接受读取对应通道的值
I2CStart(); //I2c初始化
I2CSendByte(0x90); //51向PCF8591写数据
I2CWaitAck(); //等待应答
I2CSendByte(chnl); //读取chnl通道
I2CWaitAck(); /等待应答
I2CStart(); //I2c初始化
I2CSendByte(0x91); //PCF8591向51写数据
I2CWaitAck(); //等待应答
ADC_Val = I2CReceiveByte(); //返回读取的值
I2CSendAck(1); //发送非应答
I2CStop(); //总线停止发送数据
return ADC_Val; /返回数据
}
1)ADC值通道
采样通道0控制字:0100 0000(0x40)
采样通道1(光敏电阻)控制字:0100 0001(0x41)
采样通道3(旋转电位器)控制字:0100 0003(0x43)
2)ADC值的转换
由于PCF8591是一个8位的ADC,所以读取到的值范围是0~255,但是比赛一般不会要求直接返回0~255的数据,一般要求返回的是电压,所以需要进行数据的转换。
将AD值(0~255)转换为实际电压(0~5),先成100,再除以51。先乘100的原因是把直接除51时的小数转换成整数,方便在数码管上显示。
将AD值转换为0~99的数值,直接除2.57
//ADC采样
u8 cnt_adc;
u8 ch0,ch1,ch3;
u16 ch3_volt;
u8 ch3_0_99,ch3_1_5;
void vADC_Process()
{
if(cnt_adc>=10)
{
cnt_adc=0;
ucRead_ADC(0x41);
ch1 = ucRead_ADC(0x41); //光敏电阻的AD值 - 通道1
ucRead_ADC(0x43);
ch3 = ucRead_ADC(0x43); //旋转电位器的AD值 - 通道3
ch3_volt = ch3*100/51; // 0~255 --> 0~500
ch3_0_99 = ch3/2.57f; // 0~255 --> 0~99
ch3_1_5 = ch3/51.1f + 1;
}
}
3)在main主函数如何调用
u8 ADC_count; //在定时器中刷新读取
u8 ch1; //获取对应通道的值
/*PCF8951操作函数*/
void ADCorDAC_Loop()
{
if(ADC_count >= 10) // 10ms中刷新读取一次
{
ADC_count = 0; //每次读取刷新重置ADC_count 为0
ch1 = Read_ADC(0x41); //获取0x41通道光敏电阻的值
}
}
void T2Init() interrupt 12
{
Count_Key++;
T_Count++;
Nixie_Display();
ADC_count++; //在定时器二中计时10ms
}
最后在mian,while中不断调取 ADCOrDAC_Loop函数。
2.写DAC流程
//写DAC
void Wirte_DAC(u8 Data)
{
I2CStart(); //I2c初始化
I2CSendByte(0x90); //51向PCF8591写数据
I2CWaitAck(); //等待应答
I2CSendByte(0x40); //通道选择无所谓,只要前面是0x4,启用DAC
I2CWaitAck(); //等待应答
I2CSendByte(Data); //发送数据
I2CWaitAck(); //等待应答
I2CStop(); //总线停止发送
}
在main中调用使用DAC函数
u8 DAC_count;
/*PCF8951操作函数*/
void ADCorDAC_Loop()
{
if(DAC_count >= 2) //在定时器中计时2ms
{
static u8 dac_val=0;
DAC_count = 0; //每次读取刷新时间到了,将DAC_count清零
Wirte_DAC(dac_val++); //输出DAC值
}
}
void T2Init() interrupt 12
{
Count_Key++;
T_Count++;
Nixie_Display();
DAC_count++; //在定时器2中计时刷新
}
最后在mian,while中不断调取 ADCOrDAC_Loop函数。