0.本节任务
了解PCF8591的使用方法,并利用PCF8591检测模拟电压信号(模数转换)。
1.工作原理
PCF8591简介
PCF8591的引脚和原理图连接
PCF8591是一种单芯片、单电源、低功耗的8位COMS数据采集器件,具有4个模拟输入、1个模拟输出和IIC总线。
PCF8591引脚说明
AINO~3是四个模拟输入,VSS为负电源,VDD为正电源,VREF为参考电压(蓝桥杯平台接VCC),EXT是内部/外部时钟的切换开关(为1时允许从OSC输入时钟信号,但是蓝桥杯平台接地了),OSC是外部时钟信号的输入端,A0~A2为IIC相关的硬件地址(蓝桥杯平台接地),AGND为模拟地(蓝桥杯平台接地),SCL和SDA为IIC的信号线。
蓝桥杯单片机平台的原理图
IIC内容详见,本节不再做介绍:
文章加载失败
PCF8591的设备地址
PCF8591的设备地址如下图所示,根据蓝桥杯给出原理图连接,写地址为0x90(1001 0000),读地址为0x91(1001 0001):
PCF8951的控制字节
PCF8951的控制字节如下图所示:
第0、1位控制AD转换的信道,可以选择四个端口的作为模拟信号的输入端,00为信道0,01为信道1,10为信道2,11为信道3;
第3位是自动增量标志位,若置1,每次检测完一个信道后会自动检测下一个信道;
第4位恒为0;
第5、6位控制模拟输入的模式,不同模式有不同数量的信道,一般用单端输入(00)较多;
第7位控制模拟信号输出/输入,AD转换置0,DA转换置1(有资料写只有DA转换必须置1,AD转换置0,1均可);
第8位恒为0;
蓝桥杯单片机中AIN3与Rb2可调电阻相连,所以一般可以使用0000 0011(0x03)(AD转换,单端输入,AIN3作为模拟输入的信道)作为控制字发送。
开始AD转换
通过datasheet和相关资料,PCF8951的开始AD转换分两步:
①.IIC开始->写PCF8951(0x90)->写控制字->IIC停止(第一步datasheet中似乎没有明确写出,它只说了DA转换需要写控制字???)
②.IIC开始->读PCF8951->PCF8951发送数据...->IIC停止
值得注意的是,第二部开始读PCF8951后,其发送的第一个数据是上一次转换的数据,如果是刚刚上电,则第一个数据是0x80.
datasheet中给出的A/D转换示意
AD转换单端输入特性如下图所示:
根据之前的原理图连接,VREF = VCC(5v),VAGND= 0,就可以算出Vlsb约等于0.0195v,假设模拟输入VAIN为3V,则对应横坐标属于154这个阶梯上,此时PCF8951发送的数据为0x9A.
2.程序设计
设定PCF8591为AD转换模式,并通过AIN3检测Rb2可变电阻上电压。
IIC驱动代码仍是官方给出的代码,我们只需要控制好读写地址和控制字就行:
#include "reg52.h"
#include "intrins.h"
#define DELAY_TIME 5
/** 定义I2C总线时钟线和数据线 */
sbit scl = P2^0;
sbit sda = P2^1;
/**
* @brief I2C总线中一些必要的延时
*
* @param[in] i - 延时时间调整.
* @return none
*/
void i2c_delay(unsigned char i)
{
do
{
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
}
while(i--);
}
/**
* @brief 产生I2C总线启动条件.
*
* @param[in] none
* @param[out] none
* @return none
*/
void i2c_start(void)
{
sda = 1;
scl = 1;
i2c_delay(DELAY_TIME);
sda = 0;
i2c_delay(DELAY_TIME);
scl = 0;
}
/**
* @brief 产生I2C总线停止条件
*
* @param[in] none
* @param[out] none.
* @return none
*/
void i2c_stop(void)
{
sda = 0;
scl = 1;
i2c_delay(DELAY_TIME);
sda = 1;
i2c_delay(DELAY_TIME);
}
/**
* @brief I2C发送一个字节的数据
*
* @param[in] byt - 待发送的字节
* @return none
*/
void i2c_sendbyte(unsigned char byt)
{
unsigned char i;
//
EA = 0; //关闭中断,避免因为中断而影响总写读写的时序,导致读写失败。
for(i=0; i<8; i++){
scl = 0;
i2c_delay(DELAY_TIME);
if(byt & 0x80){
sda = 1;
}
else{
sda = 0;
}
i2c_delay(DELAY_TIME);
scl = 1;
byt <<= 1;
i2c_delay(DELAY_TIME);
}
EA = 1;
//
scl = 0;
}
/**
* @brief 等待应答
*
* @param[in] none
* @param[out] none
* @return none
*/
unsigned char i2c_waitack(void)
{
unsigned char ackbit;
scl = 1;
i2c_delay(DELAY_TIME);
ackbit = sda; //while(sda); //wait ack
scl = 0;
i2c_delay(DELAY_TIME);
return ackbit;
}
/**
* @brief I2C接收一个字节数据
*
* @param[in] none
* @param[out] da
* @return da - 从I2C总线上接收到得数据
*/
unsigned char i2c_receivebyte(void)
{
unsigned char da;
unsigned char i;
//
EA = 0;
for(i=0;i<8;i++){
scl = 1;
i2c_delay(DELAY_TIME);
da <<= 1;
if(sda)
da |= 0x01;
scl = 0;
i2c_delay(DELAY_TIME);
}
EA = 1;
//
return da;
}
/**
* @brief 发送应答
*
* @param[in] ackbit - 设定是否发送应答
* @return - none
*/
void i2c_sendack(unsigned char ackbit)
{
scl = 0;
sda = ackbit; //0:发送应答信号;1:发送非应答信号
i2c_delay(DELAY_TIME);
scl = 1;
i2c_delay(DELAY_TIME);
scl = 0;
sda = 1;
i2c_delay(DELAY_TIME);
}
/**
* @brief 读写操作过程中一些必要的延时
*
* @param[in] i - 指定延时时间
* @return - none
*/
void operate_delay(unsigned char t)
{
unsigned char i;
while(t--){
for(i=0; i<112; i++);
}
}
初始化和读PCF8591:
#include "PCF8951_AD.h"
#include "STC15F2K60S2.H"
#include "intrins.h"
#include "HC138.h"
#include "LedBuzzerRelay.h"
#include "SMG.h"
#include "i2c.h"
//初始化PCF8951,AD转换,单端输入,AIN3作为模拟输入信道
void PCF8951Init(void)
{
i2c_start();
i2c_sendbyte(0x90);//写地址
i2c_waitack();
i2c_sendbyte(0x03);
i2c_waitack();
i2c_stop();
operate_delay(10);
}
unsigned char ReadPCF8951(void)
{
unsigned char da;
i2c_start();
i2c_sendbyte(0x91);//读地址
i2c_waitack();
da = i2c_receivebyte();
i2c_sendack(1);
i2c_stop();
return da;
}
主函数(这里只对读出来的数据显示了X上坐标,可以乘上分辨率得出实际电压):
#include "STC15F2K60S2.H"
#include "intrins.h"
#include "HC138.h"
#include "LedBuzzerRelay.h"
#include "SMG.h"
#include "i2c.h"
#include "PCF8951_AD.h"
void main(void)
{
unsigned char dat;
AllOff();
PCF8951Init();
//LEDOn();
while(1){
dat = ReadPCF8951();
DisplaySMG(0,dat/100);
DelaySMG();
DisplaySMG(1,(dat%100)/10);
DelaySMG();
DisplaySMG(2,dat%10);
DelaySMG();
}
}
|