目录
3.1PCF8591介绍
3.1.1、PCF8591简介
PCF8591是单片、单电源低功耗8位CMOS数据采集器件,具有4个模拟输入:用做AD转换通道本单片机只用到了通道1与通道3(光敏电阻与可调电阻)、一个输出(用作DA转换通道)和一个串行I2C总线接口(与单片机进行通信)。3个地址引脚A0、A1和A2用于编程硬件地址,允许将最多8个器件连接至I2C,总线而不需要额外硬件。器件的地址、控制和数据通过两线双向I2C总线传输。器件功能包括多路复用模拟输入、片上跟踪和保持功能、8位模数转换和8位数模拟转换。最大转换速率取决于I2C总线的最高速率。
上图的各个引脚说明如下:
- AIN0~AIN3:模拟信号输入端。
- A0~A3:引脚地址端。
- VDD、Vss:电源端。 (2.5~6V)
- SDA、SCL:IC 总线的数据线、时钟线。
- OSC:外部时钟输入端,内部时钟
- EXT:内部、外部时钟选择线,使用内部时钟时 EXT 接地。
- AGND:模拟信号地。
- AOUT:D/A 转换输出端。
- VREF:基准电源端。
PCF8591与光敏电阻可调电阻电路图如下:
由此图我们可以得到A0,A1,A2的引脚已经全部接地所以它的值全部为0,寻址时,只需要写是读还是写即可。
3.1.2、PCF8951地址
I2C 总线系统中的每一片PCF8591通过发送有效地址到该器件来激活(I2C总线片选)。该地址包括固定部分和可编程部分。可编程部分必须根据地址引脚A0、A1和A2来设置。在I2C总线协议中地址必须是起始条件后作为第一个字节发送。地址字节的最后一位是用于设置以后数据传输方向的读/写位。(具体地址段如下图)
具体控制指令格式如下:
- 高四位在出厂之前就已经确定,属于不可修改部分
- A0~A2是可编程部分,通过电路将对应的引脚拉高/拉低来确定,但蓝桥杯单片机将这三位全部接地,所以这三位全部为0。
- 第八位是读写控制位,为1时表示通信方向为,PCF写数据,单片机读数据。为0时则相反,所以对于地址的输入只有两种:单片机读PCF写(0x91),单片机写PCF读(0x90)。
3.1.3、PCF控制字
在通信寻址成功之后(片选+读/写),就是对PCF写控制指令,设置芯片的工作模式,其格式如下所示:
其中D1、D0两位是A/D通道编号:00通道0,01 通道1,10通道2,11 通道3;D2 自动增益选择(有效位为1);D5、D4模拟量输入选择:00为四路单数入、01为三路差分输入、10为单端与差分配合输入、11 为模拟输出允许有效。
当系统为A/D转换时,模拟输出允许为0。模拟量输入选择位取值由输入方式决定:四路单端输入时取00,三路差分输入时取01,单端与差分输入时取10,二路差分输入时取11。最低两位时通道编号位,当对0通道的模拟信号进行A/D转换时取00,当对1通道的模拟信号进行A/D转换时取 01,当对2通道的模拟信号进行A/D转换时取10,当对3通道的模拟信号进行A/D转换时取 11。
3.1.4、PCF内部框图学习:
其内部框图如下:
根据PCF的两种操作来对PCF进行学习:
AD转换:
AD转换采用逐次逼近型的工作原理:当通道输入的模拟电压到达逐次逼近寄存器时,PCF内部拥有一个DAC模块,其内部是通过加权电阻网络实现模数转换,可以将逐次逼近寄存器的值转换为对应的模拟电压值,将其电压值再与待测电压相比较(在比较器中进行比较),比较结果控制逐次逼近寄存器中存储的值,直到DAC输出的电压与外部通道输入的电压近似相等,DAC输入的数据就是外部电压的编码数据了。为了最快找到未知编码的电压,通常使用二分法进行查找。且使用二分法查找未知电压的编码的好处在于:每次选择比较的值恰好为对应二进制数字的每一位的权数,判断过程相当于,从高位到底位依次判断为1还是为0的过程(每次对比是比找的数大还是小)。要找到未知编码的电压,8位ADC需要判断8次,12位ADC需要判断12次。转换结束后,ADC的输入数据就是未知电压的编码,通过I2C数据线进行数据传输。
具体转换流程如下:
【1】 主机发送开始通信信号
【2】 主机寻址,与PCF8591通信(方向:单片机-> PCF8591)
【3】 等待PCF8591应答,
【4】 应答成功,发送控制指令(AD转换,单端与差分配合输入,通道为channel)
【5】 等待PCF8591应答,
【6】 应答成功,主机结束通信
【8】 等待电压转换完成(如果I2C通信速率慢,且没有这一步的话容易读不出来电压值)
【7】 主机开始通信
【8】 主机寻址,与PCF8591通信(方向: PCF8591->单片机)
【9】 等待PCF8591应答,
【10】 PCF8591发送AD转换后的数字量
【11】 主机读取数据,并且不应答
【12】 主机结束通信
【13】 主机对读取的数据进行处理,
具体代码如下:
unsigned char PCF8591_AD_Read(unsigned char Channel){
unsigned char dat;
IIC_Start(); //主机通信
IIC_SendByte(PCF8591W); //PCF8591的写设备地址
IIC_WaitAck(); //等待应答
IIC_SendByte(Channel); //发送控制字(要读取的通道)
IIC_WaitAck(); //等待应答
IIC_Stop(); //结束通信
Delayxms(10);//等待电压转换完成
IIC_Start(); //主机开始通信
IIC_SendByte(PCF8591R); //PCF8591的读设备地址
IIC_WaitAck(); //等待应答
dat = IIC_RecByte(); //读出AD采样数据
IIC_SendAck(1); //产生非应答信号
IIC_Stop(); //结束通信
return dat;
}
DA转换:
单片机将写入的数据发送到PCF8591设备的第三个字节存储在DAC数据请求器中,并使用片上D/A转换器转换为相应的模拟电压。具体过程与AD像类似不再过多阐述,其具体的工作流程如下:
【1】 主机开始通信信号
【2】 主机寻址,与PCF8591通信(方向:单片机-> PCF8591)
【3】 等待PCF8591应答,
【4】 应答成功,发送控制指令(DA转换)
【5】 等待PCF8591应答,
【6】 应答成功,单片机继续发送DA转换的数字量
【7】 等待PCF8591应答,
【8】 应答成功,PCF8591进行DA转换
【9】 主机结束通信
具体代码如下:
void PCF8591_AD_Write(unsigned char dat){//DA转换,单片机写入
I2C_Start();//开始通信
I2C_SendByte(PCF8591W);//写入写地址
I2C_waitACK();//等待应答
I2C_SendByte(PCF8591_Write_Control); // 写入允许DA转换
I2C_waitACK();//等待应答
I2C_SendByte(dat);//发送DA转换的值
I2C_waitACK();//等待应带
I2C_Stop();//结束通信
}
3.2进阶实验
- 将IIC总线的底层驱动代码文件正确移植到工程中。
- 光敏电阻rd1接到PCF8591的ANI1通道,可调电阻rd3接到PCF8591的ANI3通道。
- 将J5的23脚短接,把S4、S5和S6设置为独立按键。
- 系统上电后,循环采样PCF8591的AIN1通道,并将采 样结果换算成电压值,电压结果保留2位小数,并根据电压值 控制8个LED灯的开关。
- 灯光的控制分为五个等级:
等级1:5V <= 光照电压(可调电阻电压) < 4V,L1点亮。
等级2:4V <= 光照电压 (可调电阻电压)< 3V,L1至L2点亮。
等级3:3V <= 光照电压 (可调电阻电压)< 2V,L1至L4点亮。
等级4:2V <= 光照电压 (可调电阻电压)< 1V,L1至L6点亮。
等级5:1V <= 光照电压 (可调电阻电压)< 0V,L1至L8点亮。
- 灯光控制等级和光敏电压实时显示在数码管上,在数码管靠左端的四位,第一位显示通道口,剩下三位显示当前灯光的控制等级,在数码管靠右端的三位,显示光敏电阻(可调电阻的电压)的当前电压。
- 按下S6按键,切换数码管显示的电压,初始化为光敏电阻的电压。
- 按下S5按键,将当前灯光的控制等级和光敏电阻的采样数据保存到24C02存储器的 0x01 和0x02 地址单元中。
- 按下S4按键时,读取存储在24C02中的历史数据并显示在数码管上,再次按下时,回到切换电阻电压的界面。
题目解析:
这个看起来要求很多其实细分的话就四个模块:LED灯控制函数,按键控制函数,I2C总线协议的综合运用,数码管的使用,其中最主要的是I2C总线协议的使用和按键控制,这两个在蓝桥杯比赛中也是重难点,按键的循环嵌套非常考验编程技巧,希望大家和我共同加油努力!!!!。
3.3参考代码:
前言:
由于我的板子学校给的好像有点拉,所以我的板子对于I2C协议适配度有点问题,为此d了有点久的代码,所以本代码只做参考,哈哈哈
具体代码部分:
main.c
#include "System.h"
void main(){
Init_System(); //系统初始化
Timer0_Init();//定时器扫描按键
while(1){
Key_Control();//按键按下改变通道
LED_Control();//根据电压设置的照明控制
Display_SMG();//数码管显示函数
}
}
I2C_System.c(这里的两个模块我的IIC协议用的两个(单片机学校给的不是新版有点不适配底层驱动,我d了两天bug我确定是IIC协议传输速率的问题(因为其他网上的代码也过不了),所以我这样用了,新板子应该不会出现我这种情况(不给我新板子,我拿什么拿奖,八嘎学校!!!!)))
#include "I2C_System.h"
void AT24C02_Write(unsigned char address,unsigned char Byte){
I2C_Start();//开始通信
I2C_SendByte(SlaveAddrW);//发送写地址
I2C_waitACK();//等待应答
I2C_SendByte(address);//发送数据要写入的地址
I2C_waitACK();//等待应答
I2C_SendByte(Byte);//在指定的地址中写入数据
I2C_waitACK();//等待应答
I2C_Stop();//结束通信
}
unsigned char AT24C02_Read(unsigned char address){
unsigned char Byte= 0;
I2C_Start();//开始通信
I2C_SendByte(SlaveAddrW);//写入写标志
I2C_waitACK();//等待应答
I2C_SendByte(address);//写入要写的地址
I2C_waitACK();//等待应答
I2C_Start();//重启开始通信
I2C_SendByte(SlaveAddrR);//写入读标志
I2C_waitACK();//等待应答
Byte = I2C_ReceiveByte();//读取内容
I2C_SendACK(1);//发送停止信号
I2C_Stop();//结束通信
return Byte;
}
unsigned int PCF8591_AD_Read(unsigned char Channel){
unsigned int real_dat;
unsigned char dat;
float value;
IIC_Start();
IIC_SendByte(PCF8591W); //PCF8591的写设备地址
IIC_WaitAck();
IIC_SendByte(Channel);
IIC_WaitAck();
IIC_Stop();
// Delayxms(10);
Display_SMG();
IIC_Start();
IIC_SendByte(PCF8591R); //PCF8591的读设备地址
IIC_WaitAck();
dat = IIC_RecByte(); //读出AD采样数据
IIC_SendAck(1); //产生非应答信号
IIC_Stop();
//数字规格化处理
value = dat *(5.0 / 255);
real_dat = value/100;
real_dat = (real_dat * 100) + ( value * 100 - real_dat * 100);
return real_dat;
}
/*
void PCF8591_AD_Write(unsigned char dat){
I2C_Start();//开始通信
I2C_SendByte(PCF8591W);//写入写地址
I2C_waitACK();//等待应答
I2C_SendByte(PCF8591_Write_Control); // 写入允许DA转换
I2C_waitACK();//等待应答
I2C_SendByte(dat);//发送DA转换的值
I2C_waitACK();//等待应带
I2C_Stop();//结束通信
}*/
System.c
#include "System.h"
uchar flag = 0;//改变显示模式
uchar lastkey,nowkey;//获取键值
uint value=0;//获取AD通道的值
uchar display_24C02_level = 0;//读取当前的照明等级
uchar display_24C02_AIN = 0;//读取当前的电阻通道
uint display_24C02_value = 0;//读取当前的电阻电压
uchar lastflag = 0;//区分读取历史电压与当前电压
uchar level;//照明等级
/*0 1 2 3 4 5 6 7 8 9 */
uchar code NixieTube_nodot[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,
/* A B C D E F H L N P */
0x88,0x83,0xC6,0xA1,0x86,0x8E,0x89,0xC7,0xC8,0x8C,
/* U - ' ' */
0xC1,0xBF,0xFF};//无小数点
uchar code NixieTube_havedot[]={0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10};//有小数点
void Delay(uchar xms) //@12MHz延时函数
{
unsigned char data i, j;
while(xms--){
i = 12;
j = 190;
do
{
while (--j);
} while (--i);
}
}
void HC138_Select(uchar choice){//锁存器选择函数
switch(choice){
case 0: P2 = (P2 & 0x1f) | 0x00; break;
case 4: P2 = (P2 & 0x1f) | 0x80; break;
case 5: P2 = (P2 & 0x1f) | 0xa0; break;
case 6: P2 = (P2 & 0x1f) | 0xc0; break;
case 7: P2 = (P2 & 0x1f) | 0xe0; break;
}
}
void Init_System(void){//系统初始化
HC138_Select(4);
P2 = 0xFF;
HC138_Select(5);
P2 = 0x00;
HC138_Select(6);
P2 = 0xFF;
HC138_Select(7);
P2 = 0xFF;
}
void SMG_TranslateBit(uchar pos,uchar value){//数码管显示一位函数
HC138_Select(7);
P0 = 0xFF;
HC138_Select(6);
P0 = 0x01<<pos;
HC138_Select(7);
P0 = value;
Delay(2);
}
void LED_Control(void){//根据电压值获取照明的亮度
if(lastflag == 0){
if(flag == 0){
if(value>=0 && value < 100){
HC138_Select(4);
P0 = 0x00;//L1到L8全亮
level = 5;
}
if(value>=100 && value < 200){
HC138_Select(4);// L1至L6亮
P0 = 0xC0;
level = 4;
}
if(value>=200 && value < 300){
HC138_Select(4);
P0 = 0xF0; // L1到L4全亮
level = 3;
}
if(value>=300 && value < 400){
HC138_Select(4);
P0 = 0xFC; // L1到L2全亮
level = 2;
}
if(value>=400 && value < 500){
HC138_Select(4);
P0 = 0xFE; // L1亮
level = 1;
}
}
if(flag == 1){
if(value>=0 && value < 100){
HC138_Select(4);
P0 = 0x00;//L1到L8全亮
level = 5;
}
if(value>=100 && value < 200){
HC138_Select(4);// L1至L6亮
P0 = 0xC0;
level = 4;
}
if(value>=200 && value < 300){
HC138_Select(4);
P0 = 0xF0; // L1到L4全亮
level = 3;
}
if(value>=300 && value < 400){
HC138_Select(4);
P0 = 0xFC; // L1到L2全亮
level = 2;
}
if(value>=400 && value < 500){
HC138_Select(4);
P0 = 0xFE; // L1亮
level = 1;
}
}
}
else if(lastflag == 3){
HC138_Select(4);
P0 = 0xFF;
}
}
void Mode_Control(){//电压选择显示函数
if(flag == 0){
value = PCF8591_AD_Read(0x41);
}
else if(flag == 1){
value = PCF8591_AD_Read(0x43);
}
}
void Save_to_24C02()
{
if(flag == 0)
{AT24C02_Write(0x00,0x01);}//保存通道
else if(flag == 1)
{AT24C02_Write(0x00,0x03);}
Display_SMG();
AT24C02_Write(0x01, level);//保存照明等级
Display_SMG();
AT24C02_Write(0x02,value);//保存获取的的电压值
Display_SMG();
}
void Read_from_24C02()//读取保存在AT24C02中的信息
{
display_24C02_AIN = AT24C02_Read(0x00);//读保存的AD通道
display_24C02_level = AT24C02_Read(0x01);//读保存的照明等级
display_24C02_value = (int)AT24C02_Read(0x02); //读保存的电压
}
void Key_Control(void){//按键控制
Mode_Control();
lastkey = nowkey;
nowkey = Key_Read();
if(lastkey == 6){
if(lastkey == 6){
if(flag == 0){flag = 1; }
else if(flag == 1) {flag = 0;}
}
}
if(lastkey == 5){//保存电压值
if(lastkey == 5)
Save_to_24C02();
}
if(lastkey == 4){ //读出电压值
if(lastkey == 4){
if(lastflag == 0){
Read_from_24C02();
lastflag = 3;
}
else if(lastflag == 3){
lastflag = 0;
}
}
}
}
void Display_SMG(){//数码管显示函数
if(lastflag == 0){
if(flag == 0){
SMG_TranslateBit(0,NixieTube_nodot[1]);
}
else{
SMG_TranslateBit(0,NixieTube_nodot[3]);
}
SMG_TranslateBit(1,NixieTube_nodot[21]);
SMG_TranslateBit(2,NixieTube_nodot[level]);
SMG_TranslateBit(3,NixieTube_nodot[21]);
SMG_TranslateBit(5,NixieTube_havedot[value/100]);
SMG_TranslateBit(6,NixieTube_nodot[value/10%10]);
SMG_TranslateBit(7,NixieTube_nodot[value%10]);
}
else if(lastflag == 3){
SMG_TranslateBit(0,NixieTube_nodot[display_24C02_AIN]);
SMG_TranslateBit(1,NixieTube_nodot[21]);
SMG_TranslateBit(2,NixieTube_nodot[display_24C02_level]);
SMG_TranslateBit(3,NixieTube_nodot[21]);
SMG_TranslateBit(5,NixieTube_havedot[display_24C02_value/100]);
SMG_TranslateBit(6,NixieTube_nodot[display_24C02_value/10%10]);
SMG_TranslateBit(7,NixieTube_nodot[display_24C02_value%10]);
}
}
Key.c
#include "key.h"
#include "I2C_System.h"
void Timer0_Init(){//定时器初始化
TMOD = 0x00;
TL0 = 0x18;
TH0 = 0x66;
TR0 = 1;
EA = 1;
ET0 = 1;
PT0 = 0;
}
unsigned char Key_Scan(void){//读取按键键值
uchar KeyNumber = 0;
if(Key6==0){ if(Key6==0)KeyNumber = 6;}
if(Key5==0){ if(Key5==0)KeyNumber = 5;}
if(Key4==0){ if(Key4==0)KeyNumber = 4;}
return KeyNumber;
}
unsigned char Key_Read(void){//获取按键键值
static uchar Laststate,nowstate;
uchar Keynum;
Laststate = nowstate;
nowstate = Key_Scan();
if(Laststate == 6 && nowstate == 0) Keynum = 6;
if(Laststate == 5 && nowstate == 0) Keynum = 5;
if(Laststate == 4 && nowstate == 0) Keynum = 4;
return Keynum;
}
void Timer0_handel(void) interrupt 1{
static uint T0Count = 0;
T0Count++;
if(T0Count>=20){
T0Count = 0;
Key_Read();//用定时器扫描按键
}
}