AD-DA芯片基本知识
ADC:模拟信号转化为数字信号,特定电压转换为二进制
DAC:数字信号转化为模拟信号,通过二进制让芯片输出特定的电压

电路原理

AIN:电路输入引脚,可外界电压进行采集
A:器件地址
AOUT:DA输出口,输出电压
vref:参考电压
RD1:光敏电阻,光照越强,电阻越低;光照越弱,电阻越高(但有分压电阻,所以光照越弱,AIN1口 的电压越低,光照越强,AIN1口的电压越高)
Rb2:旋转电位器,最低0V,最高5V
器件地址:1001 000(R/W):0x90代表51向PCF8591写数据。0x91代表51读PCF8591的数据
PS:AIN2接的是差分放大电路,是需要接外接电源的,不考
AT24C02器件地址:1010 000(R/W):0xa0代表51向AT24C02写数据。0xa0代表51读AT24C02的数据
芯片特性

单电源供电
工作电压2.5V~6V
低待机电流
IIC通讯接口
器件地址可以定义(A0~A2)
4路模拟量输入口(AD),可以单端输入,也可以差分输入
通道选择可以自动增量
模拟输入范围是Vss~Vdd,蓝桥杯板载0V~5V
片内跟踪保持电路
8位逐次逼近型AD转换器
模拟输出的乘法DAC
ADC采样编程
类似DS18B20,发送相信的控制数据,让器件干嘛干嘛
PCF8591控制字

采样通道0控制字:0100 0000(0x40)
采样通道1(光敏电阻)控制字:0100 0001(0x41)
采样通道3(旋转电位器)控制字:0100 0003(0x43)
类似EEPROM,51向PCF8591写控制字(选择通道),51读PCF8591发来的数据

//读取PCF8591
u8 ucRead_ADC(u8 ctrl_byte) //ctrl_byte:通道选择控制字
{
u8 adc_val;
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(ctrl_byte);
IIC_WaitAck();
IIC_Start();
IIC_SendByte(0x91);
IIC_WaitAck();
adc_val=IIC_RecByte();
IIC_SendAck(1);
IIC_Stop();
return adc_val;
}
//利用PCF8591采集不同通道的电压
#include "system.h"
HexToBin led_ctrl,uln_ctrl;
void vSystem_Init(void)
{
vDevice_Ctrl(0xa0,0); /* 关闭蜂鸣器、继电器*/
led_ctrl.hex=0xff;
vDevice_Ctrl(0x80,led_ctrl.hex); /* 关闭LED*/
}
//ADC采样
u8 cnt_adc;
u8 ch0,ch1,ch3;
void vADC_Process()
{
if(cnt_adc>=10)
{
cnt_adc=0;
EA = 0; /*关闭中断,中断会影响IIC的通讯,所以数码管显示会闪烁
关闭中断来避免这种影响是不太好的,更好的方法请看下一节*/
ch1 = ucRead_ADC(0x41); /*读取ADC转换结果耗时0.75ms ,所以不要放在中断里面
中断讲究快进快出*/
EA = 1;
}
}
//数码管操作函数
void vSMG_Process()
{
smg_buf[0]=smg_code[ch1/100];//显示的数据是0~255,要想显示电压值还要再除以51
smg_buf[1]=smg_code[ch1/10%10];//因为0~255与0~5有51的一个倍数
smg_buf[2]=smg_code[ch1%10];
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();
vTimer2_Init();
while(1)
{
vSMG_Process();
vADC_Process();
}
}
//中断服务程序
void vTimer2_ISR() interrupt 12 //中断入口
{
cnt_adc++;
vSMG_Display();
}
中断干扰IIC通讯
原因:中断函数里对P2口进行了字节赋值,导致IIC时序出错。中断函数里执行的是smg_display(), 里面有对P2的整体赋值,导致P20、P21拉低
解决办法:对锁存器-译码器进行操作时,只改变P27 P26 P25的值。修改锁存器-译码器控制函数如下
void vDevice_Ctrl(unsigned char p2data,unsigned char p0data)
{
P0 = p0data;
P2 = P2 & 0x1f | p2data;
P2 = P2 & 0x1f;
}
读取通道相反问题
有时候同时读取两个通道的电压,但是读取出来的通道电压是相反的。只读取一个通道的值是没有问题的
原因:每次A/D转换读到的数值都是上一次转换的结果
解决办法:直接读两次。读第二次再进行变量赋值。当然也可以直接把变量赋值对换啦,但是这样很容 易把自己搞晕😵
//利用PCF8591采集不同通道的电压
#include "system.h"
HexToBin led_ctrl,uln_ctrl;
void vSystem_Init(void)
{
vDevice_Ctrl(0xa0,0); /* 关闭蜂鸣器、继电器*/
led_ctrl.hex=0xff;
vDevice_Ctrl(0x80,led_ctrl.hex); /* 关闭LED*/
}
//ADC采样
u8 cnt_adc;
u8 ch0,ch1,ch3;
void vADC_Process()
{
if(cnt_adc>=10)
{
cnt_adc=0;
ucRead_ADC(0x41);
ch1 = ucRead_ADC(0x41);
ucRead_ADC(0x43);
ch3 = ucRead_ADC(0x43);
}
}
//数码管操作函数
void vSMG_Process()
{
smg_buf[0]=smg_code[ch1/100];//显示的数据是0~255,要想显示电压值还要再除以51
smg_buf[1]=smg_code[ch1/10%10];//因为0~255与0~5有51的一个倍数
smg_buf[2]=smg_code[ch1%10];
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();
vTimer2_Init();
while(1)
{
vSMG_Process();
vADC_Process();
}
}
//中断服务程序
void vTimer2_ISR() interrupt 12 //中断入口
{
cnt_adc++;
vSMG_Display();
}
ADC值的转换
由于PCF8591是一个8位的ADC,所以读取到的值范围是0~255,但是比赛一般不会要求直接返回0~255的数据,一般要求返回的是电压,所以还需要进行数据的转换。
将AD值(0~255)转换为实际电压(0~5),先成100,再除以51。先乘100的原因是把直接除51时的小数转换成整数,方便在数码管上显示。
将AD值转换为0~99的数值,直接除2.57
将AD值转换成区间范围,比如1~51->1 51~102->2.........
//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; //区间范围,除51.1是为了防止255显示区间范围6
}
}
DAC输出电压

//输出DAC
void vWrite_DAC(u8 dat)
{
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(0x40);//通道选择无所谓,只要前面是0x4,启用DAC
IIC_WaitAck();
IIC_SendByte(dat);
IIC_WaitAck();
IIC_Stop();
}
主函数
#include "system.h"
HexToBin led_ctrl,uln_ctrl;
void vSystem_Init(void)
{
vDevice_Ctrl(0xa0,0); /* 关闭蜂鸣器、继电器*/
led_ctrl.hex=0xff;
vDevice_Ctrl(0x80,led_ctrl.hex); /* 关闭LED*/
}
//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;
}
}
//DAC输出
u8 cnt_dac;
void vDAC_Process()
{
static u8 dac_val=0;
if(cnt_dac>=2)
{
cnt_dac=0;
vWrite_DAC(dac_val++);
}
}
//数码管操作函数
void vSMG_Process()
{
smg_buf[0]=smg_code[ch3_volt/100]|0x80; //旋转电位器的电压值
smg_buf[1]=smg_code[ch3_volt/10%10];
smg_buf[2]=smg_code[ch3_volt%10];
smg_buf[3]=0x00;
smg_buf[4]=smg_code[ch3_0_99/10];
smg_buf[5]=smg_code[ch3_0_99%10];
smg_buf[6]=0x00;
smg_buf[7]=smg_code[ch3_1_5];
}
void main(void)
{
vSystem_Init();
vTimer2_Init();
while(1)
{
vSMG_Process();
vADC_Process();
vDAC_Process();
}
}
//中断服务程序
void vTimer2_ISR() interrupt 12 //中断入口
{
cnt_adc++;
cnt_dac++;
vSMG_Display();
}