文章目录
- 一、前言
- 二、PCF8591
- 1.介绍
- 2.原理图
- 3.引脚介绍
- 三、仿真图
- 1.未仿真时
- 2.仿真时
- 四、仿真程序
- main.c
- IIC.c
- 五、总结
一、前言
`AT89C52是一款经典的8051系列单片机,它通常不包含内置的模数转换器(ADC)或数字模拟转换器(DAC)功能。如果你需要使用ADC功能来进行模拟信号的采集,你可以通过外部连接ADC芯片来实现。ADC芯片将模拟信号转换为数字信号,然后可以通过AT89C52的GPIO端口进行读取和处理。
二、PCF8591
1.介绍
PCF8591是一个单片集成、单独供电、低功耗、8bit CMOS的AD/DA芯片。它具有4个模拟输入、1个模拟输出和1个串行IIC总线接口。PCF8591的3个地址引脚A0,A1和A2可以用于硬件地址编程,允许在同个IIC总线上接入8个PCF8591器件,而无需额外的硬件。
主要性能指标:
- 单电源供电,工作电压:2.5V-6V
- 通过IiC总线串行输入/输出
- PCF8591通过3个硬件地址引脚寻址
- PCF8591的采样率由IIC总线速率决定
- 4个模拟输入可编程为单端型或差分输入
- 自动增量频道选择
- PCF8591的模拟电压范围从VSS到VDD
- PCF8591内置跟踪保持电路 8-bit 逐次逼近A/D转换器
- 通过1路模拟输出实现DAC增益
2.原理图
这里的原理图用的是YL-40 AD模块的原理图。
- 模块支持外部4路电压输入采集(电压输入范围0-5V)
- 模块集成光敏电阻,可以通过AD采集环境光强精确数值
- 模块集成热敏电阻,可以通过AD采集环境温度精确数值
- 模块集成1路0-5V电压输入采集(通过蓝色电位器调节接入电压)
- 模块带电源指示灯(对模块供电后指示灯会亮)
- 模块带DA输出指示灯,当模块DA输出接口电压达到一定值,会点亮板上DA输出指示灯,电压越大,指示灯亮度越明显
3.引脚介绍
左边:
AOUT:芯片DA输出接口
AIN0:芯片模拟输入接口0,地址0x40
AIN1:芯片模拟输入接口1,地址0x41
AIN2 :芯片模拟输入接口2,地址0x42
AIN3 :芯片模拟输入接口3,地址0x43
右边:
SCL:IIC时钟接口,接单片机IO口
SDA:IIC数据接口,接单片机IO口
GND:模拟地 ,外接地
VCC:电源接口,外接3.3V-5V
三、仿真图
1.未仿真时
注意:这里的AT89C52的晶振为11.0592MHZ
2.仿真时
这里上电一段时间,AD才能正确采集出来数值,然后DAC也是一段时间过后才有电压输出。
4路ADC换算公式如图所示:ADC数值 = 2^8 * (Vacc / Vcc ) = 255 * (电阻分压 / 3.3V)
(255是因为是8bit)
四、仿真程序
main.c
void main(void)
{
UsartInit(); // 串口初始化
printf("Uart Test OK\r\n");
while(1)
{
/********以下AD-DA处理*************/
switch(AD_CHANNEL)
{
case 0: ISendByte(PCF8591,0x41);
D[0]=IRcvByte(PCF8591); //ADC0 模数转换1 光敏电阻
break;
case 1: ISendByte(PCF8591,0x42);
D[1]=IRcvByte(PCF8591); //ADC1 模数转换2 热敏电阻
break;
case 2: ISendByte(PCF8591,0x43);
D[2]=IRcvByte(PCF8591); //ADC2 模数转换3 悬空
break;
case 3: ISendByte(PCF8591,0x40);
D[3]=IRcvByte(PCF8591); //ADC3 模数转换4 可调0-5v
break;
case 4: DACconversion(PCF8591,0x40, 150); //DAC 数模转换
break;
}
if(++AD_CHANNEL>4) AD_CHANNEL=0;
/********以下将AD的值通过串口发送出去*************/
printf("D[0]=%d\r\n",D[0]);
delay(20);
printf("D[1]=%d\r\n",D[1]);
delay(20);
printf("D[2]=%d\r\n",D[2]);
delay(20);
printf("D[3]=%d\r\n",D[3]);
delay(20);
}
}
void delay(uchar i)
{
uchar j,k;
for(j=i;j>0;j--)
for(k=125;k>0;k--);
}
/*******************************************************************
DAC 变换, 转化函数
*******************************************************************/
bit DACconversion(unsigned char sla,unsigned char c, unsigned char Val)
{
Start_I2c(); //启动总线
SendByte(sla); //发送器件地址
if(ack==0)return(0);
SendByte(c); //发送控制字节
if(ack==0)return(0);
SendByte(Val); //发送DAC的数值
if(ack==0)return(0);
Stop_I2c(); //结束总线
return(1);
}
/*******************************************************************
ADC发送字节[命令]数据函数
*******************************************************************/
bit ISendByte(unsigned char sla,unsigned char c)
{
Start_I2c(); //启动总线
SendByte(sla); //发送器件地址
if(ack==0)return(0);
SendByte(c); //发送数据
if(ack==0)return(0);
Stop_I2c(); //结束总线
return(1);
}
/*******************************************************************
ADC读字节数据函数
*******************************************************************/
unsigned char IRcvByte(unsigned char sla)
{ unsigned char c;
Start_I2c(); //启动总线
SendByte(sla+1); //发送器件地址
if(ack==0)return(0);
c=RcvByte(); //读取数据0
Ack_I2c(1); //发送非就答位
Stop_I2c(); //结束总线
return(c);
}
/*******************************************************************************
* 函数名 :UsartInit()
* 函数功能 :设置串口
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void UsartInit()
{
TMOD=0x20; //即0010 0000,定时器/计数器1,工作方式2
TH1=0xfd; //设置波特率为9600
TL1=0xfd;
TR1=1; //启动定时器/计数器1
SCON=0x50; //SM0=0;SM1=1;REN=1;0101 0000.串口工作方式1,允许串行控制
PCON=0x00; //设置SMOD=0
IE=0x90; //EA=1;ES=1;CPU允许中断,串行允许中断
TI=1; //中断请求标志位,直接使用printf必须加入此句才能实现发送
}
/*******************************************************************************
* 函数名 : Usart() interrupt 4
* 函数功能 : 串口通信中断函数
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void Usart() interrupt 4
{
if (RI) //接收到新字节
{
RI = 0; //清零接收中断标志位
rx_buf = SBUF;
}
}
IIC.c
#define NOP() _nop_() /* 定义空指令 */
#define _Nop() _nop_() /*定义空指令*/
sbit SCL=P2^0; //I2C 时钟
sbit SDA=P2^1; //I2C 数据
bit ack; /*应答标志位*/
/*******************************************************************
起动总线函数
函数原型: void Start_I2c();
功能: 启动I2C总线,即发送I2C起始条件.
********************************************************************/
void Start_I2c()
{
SDA=1; /*发送起始条件的数据信号*/
_Nop();
SCL=1;
_Nop(); /*起始条件建立时间大于4.7us,延时*/
_Nop();
_Nop();
_Nop();
_Nop();
SDA=0; /*发送起始信号*/
_Nop(); /* 起始条件锁定时间大于4μs*/
_Nop();
_Nop();
_Nop();
_Nop();
SCL=0; /*钳住I2C总线,准备发送或接收数据 */
_Nop();
_Nop();
}
/*******************************************************************
结束总线函数
函数原型: void Stop_I2c();
功能: 结束I2C总线,即发送I2C结束条件.
********************************************************************/
void Stop_I2c()
{
SDA=0; /*发送结束条件的数据信号*/
_Nop(); /*发送结束条件的时钟信号*/
SCL=1; /*结束条件建立时间大于4μs*/
_Nop();
_Nop();
_Nop();
_Nop();
_Nop();
SDA=1; /*发送I2C总线结束信号*/
_Nop();
_Nop();
_Nop();
_Nop();
}
/*******************************************************************
字节数据发送函数
函数原型: void SendByte(UCHAR c);
功能: 将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对
此状态位进行操作.(不应答或非应答都使ack=0)
发送数据正常,ack=1; ack=0表示被控器无应答或损坏。
********************************************************************/
void SendByte(unsigned char c)
{
unsigned char BitCnt;
for(BitCnt=0;BitCnt<8;BitCnt++) /*要传送的数据长度为8位*/
{
if((c<<BitCnt)&0x80)SDA=1; /*判断发送位*/
else SDA=0;
_Nop();
SCL=1; /*置时钟线为高,通知被控器开始接收数据位*/
_Nop();
_Nop(); /*保证时钟高电平周期大于4μs*/
_Nop();
_Nop();
_Nop();
SCL=0;
}
_Nop();
_Nop();
SDA=1; /*8位发送完后释放数据线,准备接收应答位*/
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop();
_Nop();
if(SDA==1)ack=0;
else ack=1; /*判断是否接收到应答信号*/
SCL=0;
_Nop();
_Nop();
}
/*******************************************************************
字节数据接收函数
函数原型: UCHAR RcvByte();
功能: 用来接收从器件传来的数据,并判断总线错误(不发应答信号),
发完后请用应答函数应答从机。
********************************************************************/
unsigned char RcvByte()
{
unsigned char retc;
unsigned char BitCnt;
retc=0;
SDA=1; /*置数据线为输入方式*/
for(BitCnt=0;BitCnt<8;BitCnt++)
{
_Nop();
SCL=0; /*置时钟线为低,准备接收数据位*/
_Nop();
_Nop(); /*时钟低电平周期大于4.7μs*/
_Nop();
_Nop();
_Nop();
SCL=1; /*置时钟线为高使数据线上数据有效*/
_Nop();
_Nop();
retc=retc<<1;
if(SDA==1)retc=retc+1; /*读数据位,接收的数据位放入retc中 */
_Nop();
_Nop();
}
SCL=0;
_Nop();
_Nop();
return(retc);
}
/********************************************************************
应答子函数
函数原型: void Ack_I2c(bit a);
功能: 主控器进行应答信号(可以是应答或非应答信号,由位参数a决定)
********************************************************************/
void Ack_I2c(bit a)
{
if(a==0)SDA=0; /*在此发出应答或非应答信号 */
else SDA=1;
_Nop();
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop(); /*时钟低电平周期大于4μs*/
_Nop();
_Nop();
_Nop();
SCL=0; /*清时钟线,钳住I2C总线以便继续接收*/
_Nop();
_Nop();
}
五、总结
今天主要讲了基于51单片机的AD/DA转换的串口通信proteus仿真。
感谢你的观看!