PCF8591 的通信接口是 IIC协议,编程需要对 PCF8591 进行初始化。
PCF8591接线原理图:
- AIN0 ~ AIN3 : 模拟信号的4个输入端口
- A0 ~ A2 : 芯片地址低三位。
- VDD、GND : 电源、地。(电源电压2.5~6V)
- SDA、SCL : IIC总线数据、时钟线。
- OSC : 外部时钟输入端,内部时钟输出端。
- EXT : 内部、外部时钟选择线,使用内部时钟时EXT接地。
- AGND : 模拟信号地。
- VREF : 基准电源端。(内部转换使用)
- AOUT : D/A(数字转模拟信号)转换的模拟输出端。
PCF8591地址字节:
- 高4位 固定是 1001;
- 低三位 是 A2,A1,A0,在电路中我们直接接地;R/W作为读写功能。
PCF8591控制字节:
控制字节用于控制 PCF8591 的功能。控制寄存器的所有位在上电复位时被复位为 0 ;
其中
D3 位和 D7 位是固定的 0,是为芯片以后扩展功能用的。
D1、D0 :这两位是4路A/D通道编号,也就是 AIN0 ~ AIN3 。00 通道0,01 通道1,10 通道2,11 通道3。
D2:是自动增量标志位。设置为1时,模块在采集完通道0数据后继续采集通道1、通道2、通道3。设置单路采集时需要置0。
D5、D4:模拟量输入选择:00为四路单端输入、01为三路差分输入、10为单端与差分配合输入、11为两路差分输入。参考下图。
D6 :D/A是数字转模拟信号输出使能控制位(有效位为1)。我们用的一般是A/D转换,默认为0。
代码部分:
/***********************************主程序代码************************************/
/*****************************main.c 文件程序源代码******************************/
#include <reg52.h>
#include "LCD1602.h"
#include "IIC.h"
bit flag300ms = 1; //300ms 定时标志
unsigned char T0RH = 0; //T0 重载值的高字节
unsigned char T0RL = 0; //T0 重载值的低字节
void ConfigTimer0(unsigned int ms);
unsigned char GetADCValue(unsigned char chn);
void ValueToString(unsigned char *str, unsigned char val);
void main()
{
unsigned char val;
unsigned char str[10];
EA = 1; //开总中断
ConfigTimer0(10); //配置 T0 定时 10ms
LCD_Init(); //初始化液晶
LCD_ShowString(1, 2, "AIN0 AIN1 AIN3"); //显示通道指示
while (1)
{
if (flag300ms)
{
flag300ms = 0;
//显示通道 0 的电压
val = GetADCValue(0); //获取 ADC 通道 0 的转换值
ValueToString(str, val); //转为字符串格式的电压值
LCD_ShowString(2, 2, str); //显示到LCD1602上
//显示通道 1 的电压
val = GetADCValue(1);
ValueToString(str, val);
LCD_ShowString(2, 7, str);
//显示通道 3 的电压
val = GetADCValue(3);
ValueToString(str, val);
LCD_ShowString(2, 12, str);
}
}
}
/* 读取当前的 ADC 转换值,chn-ADC 通道号 0~3 */
unsigned char GetADCValue(unsigned char chn)
{
unsigned char val;
I2CStart();
if (!I2CWrite(0x48<<1)) //寻址 PCF8591,如未应答,则停止操作并返回 0
{
I2CStop();
return 0;
}
I2CWrite(0x40|chn); //写入控制字节,选择转换通道
I2CStart();
I2CWrite((0x48<<1)|0x01); //寻址 PCF8591,指定后续为读操作
I2CReadACK(); //先空读一个字节,提供采样转换时间
val = I2CReadNAK(); //读取刚刚转换完的值
I2CStop();
return val;
}
/* ADC 转换值转为实际电压值的字符串形式,str-字符串指针,val-AD 转换值 */
void ValueToString(unsigned char *str, unsigned char val)
{
val = (val*50) / 255; //电压值=转换结果*5.0V/255,式中的 50 隐含了一位十进制小数
str[0] = (val/10) + '0'; //整数位空字符
str[1] = '.'; //小数点
str[2] = (val%10) + '0'; //小数位空字符
str[3] = 'V'; //电压单位
str[4] = '\0'; //结束符
}
/* 配置并启动 T0,ms-T0 定时时间 */
void ConfigTimer0(unsigned int ms)
{
unsigned long tmp; //临时变量
tmp = 11059200 / 12; //定时器计数频率
tmp = (tmp * ms) / 1000; //计算所需的计数值
tmp = 65536 - tmp; //计算定时器重载值
tmp = tmp + 12; //补偿中断响应延时造成的误差
T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
T0RL = (unsigned char)tmp;
TMOD &= 0xF0; //清零 T0 的控制位
TMOD |= 0x01; //配置 T0 为模式 1
TH0 = T0RH; //加载 T0 重载值
TL0 = T0RL;
ET0 = 1; //使能 T0 中断
TR0 = 1; //启动 T0
}
/* T0 中断服务函数,执行 300ms 定时 */
void InterruptTimer0() interrupt 1
{
static unsigned char tmr300ms = 0;
TH0 = T0RH; //重新加载重载值
TL0 = T0RL;
tmr300ms++;
if (tmr300ms >= 30) //定时 300ms
{
tmr300ms = 0;
flag300ms = 1;
}
}
/***************************LCD1602.c 文件程序源代码*****************************/
/*****************************LCD1602显示代码************************/
#include <REGX52.H>
//注意:LCD1602只有2行16列 不能超出范围
//LCD_Init(); LCD初始化
//LCD_ShowChar(1,1,'A'); 1行1列显示一个字符
//LCD_ShowString(1,3,"hello"); 1行3列显示字符串
//LCD_ShowNum(1,9,123,3); 1行9列显示十进制数字 3位 显示数字超出位数最高位消失,多于则最高位补0
//LCD_ShowSignedNum(1,13,-66,2); 1行13列显示有符号十进制数字 2位 位数不包含符号
//LCD_ShowHexNum(2,1,0xA8,2); 显示十六进制数字 A8
//LCD_ShowBinNum(2,4,0xAA,8); 显示二进制数字 10101010
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0
//函数定义:
/**
* @brief LCD1602延时函数,12MHz调用可延时1ms
* @param 无
* @retval 无
*/
void LCD_Delay()
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
/**
* @brief LCD1602初始化函数
* @param 无
* @retval 无
*/
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
/**
* @brief 在LCD1602指定位置开始显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-32768~32767
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以十六进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFF
* @param Length 要显示数字的长度,范围:1~4
* @retval 无
*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
/**
* @brief 在LCD1602指定位置开始以二进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}
/**********************************IIC.c 文件程序源代码***************************************/
/******************************I2C.c 文件程序源代码******************************/
#include <reg52.h>
#include <intrins.h>
#define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}
sbit I2C_SCL = P3^7;
sbit I2C_SDA = P3^6;
/* 产生总线起始信号 */
void I2CStart()
{
I2C_SDA = 1; //首先确保 SDA、SCL 都是高电平
I2C_SCL = 1;
I2CDelay();
I2C_SDA = 0; //先拉低 SDA
I2CDelay();
I2C_SCL = 0; //再拉低 SCL
}
/* 产生总线停止信号 */
void I2CStop(){
I2C_SCL = 0; //首先确保 SDA、SCL 都是低电平
I2C_SDA = 0;
I2CDelay();
I2C_SCL = 1; //先拉高 SCL
I2CDelay();
I2C_SDA = 1; //再拉高 SDA
I2CDelay();
}
/* I2C 总线写操作,dat-待写入字节,返回值-从机应答位的值 */
bit I2CWrite(unsigned char dat)
{
bit ack; //用于暂存应答位的值
unsigned char mask; //用于探测字节内某一位值的掩码变量
for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
{
if ((mask&dat) == 0) //该位的值输出到 SDA 上
I2C_SDA = 0;
else
I2C_SDA = 1;
I2CDelay();
I2C_SCL = 1; //拉高 SCL
I2CDelay();
I2C_SCL = 0; //再拉低 SCL,完成一个位周期
}
I2C_SDA = 1; //8 位数据发送完后,主机释放 SDA,以检测从机应答
I2CDelay();
I2C_SCL = 1; //拉高 SCL
ack = I2C_SDA; //读取此时的 SDA 值,即为从机的应答值
I2CDelay();
I2C_SCL = 0; //再拉低 SCL 完成应答位,并保持住总线
//应答值取反以符合通常的逻辑:
//0=不存在或忙或写入失败,1=存在且空闲或写入成功
return (~ack);
}
/* I2C 总线读操作,并发送非应答信号,返回值-读到的字节 */
unsigned char I2CReadNAK()
{
unsigned char mask;
unsigned char dat;
I2C_SDA = 1; //首先确保主机释放 SDA
for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
{
I2CDelay();
I2C_SCL = 1; //拉高 SCL
if(I2C_SDA == 0) //读取 SDA 的值
dat &= ~mask; //为 0 时,dat 中对应位清零
else
dat |= mask; //为 1 时,dat 中对应位置 1
I2CDelay();
I2C_SCL = 0; //再拉低 SCL,以使从机发送出下一位
}
I2C_SDA = 1; //8 位数据发送完后,拉高 SDA,发送非应答信号
I2CDelay();
I2C_SCL = 1; //拉高 SCL
I2CDelay();
I2C_SCL = 0; //再拉低 SCL 完成非应答位,并保持住总线
return dat;
}
/* I2C 总线读操作,并发送应答信号,返回值-读到的字节 */
unsigned char I2CReadACK()
{
unsigned char mask;
unsigned char dat;
I2C_SDA = 1; //首先确保主机释放 SDA
for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
{
I2CDelay();
I2C_SCL = 1; //拉高 SCL
if(I2C_SDA == 0) //读取 SDA 的值
dat &= ~mask; //为 0 时,dat 中对应位清零
else
dat |= mask; //为 1 时,dat 中对应位置 1
I2CDelay();
I2C_SCL = 0; //再拉低 SCL,以使从机发送出下一位
}
I2C_SDA = 0; //8 位数据发送完后,拉低 SDA,发送应答信号
I2CDelay();
I2C_SCL = 1; //拉高 SCL
I2CDelay();
I2C_SCL = 0; //再拉低 SCL 完成应答位,并保持住总线
return dat;
}