单片机是一个典型的数字系统。数字系统只能对输入的数字信号进行处理,但是在工业检测系统和日常生活中的许多物理量都是模拟量,这些模拟量可以通过传感器变成与之对应的数字量便于处理和显示。
A/D和D/A的基本概念
A/D是模拟量到数字量的转换,依靠的是模数转换器(Analog to Digital Converter),简称ADC。D/A是数字量到模拟量的转换,依靠的是数模转换器(Digital to Analog Converter),简称DAC。本项目中主要以A/D为例。
模拟量是指变量在一定范围内连续变化的量,在任意时刻都有相应的值与之对应。比如天气温度值,在每时每刻都有温度值,也称之为连续变化的量。相对应的是数字量,数字量只有在特定的时间才有相对应的值,由于有一定的间隔,不是连续的,也称之为离散。ADC就是把连续的信号用离散的数字表达出来。
A/D的主要指标
在选取和使用A/D的时候,依靠什么指标来判断很重要。由于AD的种类很多,分为积分型、逐次逼近型、并行/串行比较型等多种类型,同时指标也比较多,介绍常用的三种。
1、ADC的位数
一个n位的ADC表示这个ADC共有2的n次方个刻度。8位的ADC,输出的是从0~255一共256个数字量,也就是2的8次方个数据刻度。
2、基准源
基准源,也叫基准电压,是ADC的一个重要指标,要想把输入ADC的信号测量准确,那么基准源首先要准,基准源的偏差会直接导致转换结果的偏差。
3、分辨率
分辨率是数字量变化一个最小刻度时,模拟信号的变化量,定义为满刻度量程与2n-1的比值。假定5.10V的电压系统,使用8位的ADC进行测量,那么相当于0~255一共256个刻度把5.10V平均分成了255份,那么分辨率就是5.10/255 = 0.02V。
PCF8591模块硬件应用说明
PCF8591是Philips公司的产品,是一个单电源低功耗的8位CMOS数据采集器件,具有4路模拟输入,1路模拟输出和一个串行I2C总线接口用来与单片机通信,该模块从某宝采购,如图1所示,该模块共有8个引脚,在本项目中接左侧的SCL、SDA、GND、VCC即可。
具有16引脚的芯片即为PCF8591,原理图如图2所示。该芯片允许最多8个器件连接到I2C总线而不需要额外的片选电路。器件的地址、控制以及数据都是通过I2C总线来传输,PCF8591的ADC是逐次逼近型,转换速度取决于I2C的通信速率。由于I2C速度的限制,所以PCF8591是低速的AD和DA集成,主要应用在一些转换速度要求不高,希望成本较低的场合,比如电池供电设备,测量电池的供电电压。
图2 PCF8591芯片连接原理图
图2中引脚1、2、3、4是4路模拟输入,对应的实物是图1中的右侧引脚,通过丝印层即可观察得到。5、6、7引脚对应A0、A1、A2,是I2C总线的硬件地址,用于编程硬件地址,8脚是数字地GND,9脚和10脚是I2C总线的SDA和SCL。12脚是时钟选择引脚,如果接高电平表示用外部时钟输入,接低电平则用内部时钟,电路用的是内部时钟,因此12脚直接接GND,同时11脚悬空。13脚是模拟地AGND,在实际开发中,如果有比较复杂的模拟电路,那么AGND部分在布局布线上要特别处理,而且和GND的连接也有多种方式。在板子上没有复杂的模拟部分电路,所以把AGND和GND接到一起。14脚是基准源,15脚是DAC的模拟输出,16脚是供电电源VCC。
14脚Vref基准电压的提供有两种方法。一是采用简易的原则,直接接到VCC上去,但是由于VCC会受到整个线路的用电功耗情况影响,相对来说并不是很准确,通常用于精度要求不高的场合。方法二是使用专门的基准电压器件,比如TL431,它可以提供一个精度很高的2.5V的电压基准,本项目中采用方法一。
** PCF8591模块使用说明(淘宝购买)**
模块共有3个黑色短路帽,如图1 PCF8591模块所示,通过丝印层观察可见J4、J5、J6,相应的原理图如图3所示,分别作用如下:
P5接上P5短路帽,选择光敏电阻接入电路,AN0通路;
P6接上P6短路帽,选择0-5V可调电压接入电路,AN3通路;
由于在本实验中读取该3路的具体值,所以实验中并未取下短路帽。
图3 模块引脚连接方式
这里需要注意的是AN3虽然测的是+5V的值,但是对于AD来说,只要输入信号超过Vref基准源,它得到的始终都是最大值,即255,也就是说它实际上无法测量超过其Vref的电压信号。需要注意的是,所有输入信号的电压值都不能超过VCC,即+5V,否则可能会损坏ADC芯片。
注:如果需要使用四路外部电压输入,请将3个红色短路帽都取下。
模块功能描述(资料中带有)
1、模块支持外部4路电压输入采集(电压输入范围 0-5v);
2、模块集成光敏电阻,可以通过AD采集环境光强精确数值;
3、模块集成热敏电阻,可以通过AD采集环境温度精确数值;
4、模块集成1路0-5V电压输入采集(通过蓝色电位器调节输入电压);
5、模块带电源指示灯(对模块供电后指示灯会亮);
6、模块带DA输出指示灯,当模块DA输出接口电压达到一定值,会点亮板上DA输出指示灯,电压越大,指示灯亮度越明显;
7、模块PCB尺寸:3.6cm*2.3cm;
8、标准双面板,板厚1.6mm,布局美观大方,四周设有通孔,孔径为:3mm,方便固定。
** PCF8591的软件编程**
PCF8591的通信接口是I2C。单片机对PCF8591进行初始化,一共发送三个字节即可。第一个字节是器件地址字节,其中7位代表地址,1位代表读写方向(最低位),“0”表示主机向从机写数据,“1”表示主机向从机读数据。地址高4位固定是0b1001,低三位是A2,A1,A0,这三位电路上都接了GND,因此也就是0b000,如图4所示。
图4 PCF8591地址字节
在程序中有这么一段代码,0x48是由高四位和低3位组成的,即0b1001000,由于读写位在第0位,所以需要整体左移一位,程序中if用于判断是否存在该器件,如果不存在则I2CWrite函数返回1,则执行I2CStop();return 0;这两条语句,return函数会结束当前函数;反之,返回0,略过if语句,继续执行下面的语句。
if(I2CWrite(0x48< < 1))
{
I2CStop();
return 0;
}
发送到PCF8591的第二个字节将被存储在控制寄存器,用于控制PCF8591的功能。其中第3位和第7位是固定的0,另外6位各自有各自的作用,如图5所示。
图5 PCF8591 控制字节
控制字节的第6位是DA使能位,这一位置1表示DA输出引脚使能,会产生模拟电压输出功能。第4位和第5位可以实现把PCF8591的4路模拟输入配置成单端模式和差分模式,这里只需要知道这两位是配置AD输入方式的控制位即可,如图6所示,本项目中采用“00”模式。
图6 PCF8591模拟输入配置方式
控制字节的第2位是自动增量控制位,自动增量的意思就是,比如一共有4个通道,当全部使用的时候,读完了通道0,下一次再读,会自动进入通道1进行读取,不需要我们指定下一个通道,由于A/D每次读到的数据,都是上一次的转换结果,所以在使用自动增量功能的时候,要特别注意,当前读到的是上一个通道的值!
控制字节的第0位和第1位就是通道选择位,00、01、10、11代表了从0到3的一共4个通道选择。
发送给PCF8591的第三个字节D/A数据寄存器,表示D/A模拟输出的电压值。如果仅仅使用A/D功能的话,就可以不发送第三个字节。
I2C总线与通讯时序的介绍
在项目六中接触到了第一种通信协议----UART异步串行通信,本项目中学习第二种通信协议----I2C。I2C总线是由PHILIPS公司开发的两线式串行总线,多用于连接微处理器及其外围芯片。I2C总线的主要特点是接口方式简单,两条线可以挂多个参与通信的器件,即多机模式,而且任何一个器件都可以作为主机,当然同一时刻只能有一个主机。I2C属于同步通信,SCL时钟线负责收发双方的时钟节拍,SDA数据线负责传输数据。I2C的发送方和接收方都以SCL这个时钟节拍为基准进行数据的发送和接收。在本项目中,I2C用于单片机和PCF8591之间的通信。
** I2C寻址模式**
I2C通信在字节级的传输中,也有固定的时序要求。I2C通信的起始信号(Start)后,首先要发送一个从机的地址,这个地址一共有7位,紧跟着的第8位是数据方向位(R/W),“0”表示接下来要发送数据(写),“1”表示接下来是请求数据(读)。
当发送完了这7位地址和1位方向后,如果发送的这个地址确实存在,那么这个地址的器件应该回应一个ACK(拉低SDA即输出“0”),如果不存在,就没“人”回应ACK(SDA将保持高电平即“1”)。ACK类似在打电话的时候,当拨通电话,接听方捡起电话肯定要回一个“喂”,这就是告诉拨电话的人,这边有人了。同理,这个第九位ACK实际上起到的就是这样一个作用。
在前面提到PCF8591的7位地址中高4位固定是0b1001,紧接低三位是A2,A1,A0,这三位电路上都接了GND,因此也就是0b000,因此PCF8591的7位地址实际上是二进制的0b1001000,也就是0x48。
I2C时序认识
I2C总线是由时钟总线SCL和数据总线SDA两条线构成,所有器件的SCL都连到一起,所有SDA都连到一起。I2C总线是开漏引脚并联的结构,因此外部要添加上拉电阻。对于开漏电路外部加上拉电阻,就组成了线“与”的关系。总线上线“与”的关系就是说,所有接入的器件保持高电平,这条线才是高电平,而任何一个器件输出一个低电平,那这条线就会保持低电平,因此可以做到任何一个器件都可以拉低电平,也就是任何一个器件都可以作为主机,如图2所示,添加了R8和R9两个上拉电阻。
I2C在通信过程中有起始信号、数据传输和停止信号,如图7所示。
图7 I2C通信流程解析
I2C分为起始信号、数据传输部分、停止信号。其中数据传输部分,可以一次通信过程传输很多个字节,字节数是不受限制的,而每个字节的数据最后也跟了一位,这一位叫做应答位,通常用ACK表示应答,NACK表示非应答。
起始信号:UART通信是从一直持续的高电平出现一个低电平标志起始位;而I2C通信的起始信号的定义是SCL为高电平期间,SDA由高电平向低电平变化产生一个下降沿,表示起始信号,如图7中的Start部分所示,相应的代码如下所示。
void I2CStart()
{
I2C_SDA = 1; //首先确保 SDA、SCL都是高电平
I2C_SCL = 1;
Delay();
I2C_SDA = 0; //先拉低 SDA
Delay();
I2C_SCL = 0; //再拉低 SCL
}
根据程序的时序图较易理解,程序中使用了Delay()函数,那么Delay()延时多少时间?在程序定义中可以看到。
#define Delay() { nop (); nop (); nop (); nop (); nop ();}
一个_nop_()表示大概是一个机器周期,约为5us,为什么是这个值?根据PCF8591操作手册要求,如图8所示,需要持续tHD;STA 的时间,结合图9,tHD;STA 的最小值为4us,没有最大值,从图9中还可以看到有的需要持续5us,所以统一方便定义Delay()的时间为5个_nop_()。
图8 I2C总线时间限制图
图9 具体时间分布图
数据传输:I2C通信是高位在前,低位在后。I2C不像UART有固定波特率,但是有时序要求:当SCL在低电平的时候,SDA允许变化,也就是说,发送方必须先保持SCL是低电平,才可以改变数据线SDA,输出要发送的当前数据的一位;而当SCL在高电平的时候,SDA绝对不可以变化,因为这个时候,接收方要来读取当前SDA的电平信号是0还是1,因此要保证SDA的稳定,如图7中的每一位数据的变化,都是在SCL的低电平位置。8位数据位后边跟着的是一位应答位。
数据传输又分为两种:主机向从机写数据和主机向从机读取数据,再次强调下一般来说单片机为主机,从机为24C02、PCF8591等具备I2C协议的专用芯片。
1、当读数据的时候,从设备每发送完8个数据位,如果主机继续读下一个字节,主机应该回答“ACK”以提示从机准备下一个数据,如果主机不希望读取更多字节,主机应该回答“NACK”以提示从机设备准备接收Stop信号。
2、当写数据的时候,主机每发送完8个数据位,从机设备如果还要一个字节应该回答“ACK”,从机设备如果不接受更多的字节应该回答“NACK”,主机当收到“NACK”或者一定时间之后没收到任何数据将视为超时,此时主机放弃数据传送。
3、无论是读数据还是写数据,都是主机动作!
根据读操作特点编写以下程序,I2CReadACKORNOT函数中的参数为1表示继续读下一字节,根据函数可知此时回答的是“ACK”;反之,为非1时,主机回应了“NACK”。不同于UART协议,I2C传输数据从高位开始,程序中巧妙地设置了BitCnt的值为0x80,对应的二进制为0b1000 0000,如果此时从机传给主机的值为0,那么“dat &= ~BitCnt”后,dat的最高位为0,如果从机传给主机的值为1,那么“dat |= BitCnt t”后,dat的最高位为1。一次循环后BitCnt>>=1,此时BitCnt的值为0x40,对应的二进制为0b0100 0000。通过此方式,依次读取出从机传给主机的数据,最后函数返回dat值!
unsigned char I2CReadACKORNOT(bit cnt)
{
unsigned char BitCnt;
unsigned char dat;
I2C_SDA = 1; //首先确保主机释放SDA
for (BitCnt=0x80; BitCnt!=0; BitCnt > >=1) //从高位到低位依次进行
{
Delay();
I2C_SCL = 1; //拉高SCL
if(I2C_SDA == 0) //读取SDA的值
dat &= ~BitCnt; //为0时,dat中对应位清零
else
dat |= BitCnt; //为1时,dat中对应位置1
Delay();
I2C_SCL = 0; //再拉低SCL,以使从机发送出下一位
}
if(cnt)
I2C_SDA = 0; //8位数据发送完后,拉低SDA,发送应答信号
else
I2C_SDA = 1;
Delay();
I2C_SCL = 1; //拉高SCL
Delay();
I2C_SCL = 0; //再拉低SCL完成应答位,并保持住总线
return dat;
}
对于写操作类似,不在此重复叙述。
停止信号:I2C通信停止信号的定义是SCL为高电平期间,SDA由低电平向高电平变化产生一个上升沿,表示结束信号,如图7中的Stop部分所示,相应的代码如下所示。
void I2CStop()
{
I2C_SCL = 0; //首先确保SDA、SCL都是低电平
I2C_SDA = 0;
Delay();
I2C_SCL = 1; //先拉高 SCL
Delay();
I2C_SDA = 1; //再拉高 SDA
Delay();
}
实现现象:
采集PCF8591模块的3路信号。
评论
查看更多