树莓派与PCF8591模数转换器的那些事儿


一、简介

  因为树莓派上没有内置的模数转换器(ADC),所以当我们需要用树莓派读取模拟量时需要外接一块具有模数转换功能的转换器(ADC),本文使用的是PCF8591中的HW-011,根据参考范例成功复现点亮led灯。

实验结果

二、硬件准备

1、PCF8591 * 1

  PCF8591是一个单片集成、单独供电、低功耗、8-bit CMOS数据获取器件。PCF8591具有4个模拟输入、1个模拟输出和1个串行I²C总线接口。PCF8591的3个地址引脚A0, A1和A2可用于硬件地址编程,允许在同一个I2C总线上接入8个PCF8591器件,而无需额外的硬件。在PCF8591器件上输入输出的地址、控制和数据信号都是通过双线双向I2C总线以串行的方式进行传输。
PCF8591

2、KY-011双色LED模块 * 1

  ky-011分为红黄两色,(图中左侧)标注‘-’的管脚接GND,中间的管脚控制亮红灯,(图中右侧)标注"S"的管脚控制亮黄灯。
在这里插入图片描述

3、树莓派4B * 1

在这里插入图片描述

4、硬件引脚连线

引脚连线
  因为后期实验接线较多,为了方便接线,故使用了GPIO扩展板,这个在某宝随便一搜就能买到,一般也就几块钱到十几块钱不等,建议大家多花点钱买个好点的,毕竟这种东西还是一分钱一分货。上图是创乐博制作的接线图,对应有三十多个实验,有一套完整的开发套件,目前网上搜到的教程也大多是参考于此。

树莓派扩展板 PCF8591
SCL SCL
SDA SDA
5V VCC
GND GND

  此处将用Y代替黄灯引脚,R代表红灯引脚,*代表什么都不接,当然这一块接黄接红都一样,就是一个模拟实验,通过PCF8591输出信号给Led,使其亮灯。另外J6短路帽不能取下,其余随便

PCF8591 KY-011 Led
AOUT Y
* R
GND GND

三、软件准备

  原理很简单,就是通过PCF8591获取蓝色电位器调节的输入电压,并将其转化为数字值在终端显示,然后又将其转化成模拟值从AOUT输出达到亮灯的目的。

  另外要想实现效果,得先打开树莓派的I2C接口,其在树莓派中默认是关闭的,在使用该传感器的时候,我们必须首先允许I2C总线通信。
在这里插入图片描述

1、PCF8591.py

  PCF8591.py可以单独运行实现led亮灯的效果。

#!/usr/bin/env python
#------------------------------------------------------
#
#       This is a program for PCF8591 Module. 
#
#       Warnng! The Analog input MUST NOT be over 3.3V!
#    
#       In this script, we use a poteniometer for analog
#   input, and a LED on AO for analog output. 
#
#       you can import this script to another by:
#   import PCF8591 as ADC
#   
#   ADC.Setup(Address)  # Check it by sudo i2cdetect -y -1
#   ADC.read(channal)   # Channal range from 0 to 3
#   ADC.write(Value)    # Value range from 0 to 255     
#
#------------------------------------------------------
#SMBus (System Management Bus,系统管理总线) 
import smbus   #在程序中导入“smbus”模块
import time

# for RPI version 1, use "bus = smbus.SMBus(1)"
# 0 代表 /dev/i2c-0, 1 代表 /dev/i2c-1 ,具体看使用的树莓派那个I2C来决定
bus = smbus.SMBus(1)         #创建一个smbus实例

#在树莓派上查询PCF8591的地址:“sudo i2cdetect -y 1”
def setup(Addr):
	global address
	address = Addr

def read(chn): #channel
	if chn == 0:
		bus.write_byte(address,0x40)   #发送一个控制字节到设备
	if chn == 1:
		bus.write_byte(address,0x41)
	if chn == 2:
		bus.write_byte(address,0x42)
	if chn == 3:
		bus.write_byte(address,0x43)
	bus.read_byte(address)         # 从设备读取单个字节,而不指定设备寄存器。
	return bus.read_byte(address)  #返回某通道输入的模拟值A/D转换后的数字值

def write(val):
	temp = val  # 将字符串值移动到temp
	temp = int(temp) # 将字符串改为整数类型
	# print temp to see on terminal else comment out
	bus.write_byte_data(address, 0x40, temp) 
    #写入字节数据,将数字值转化成模拟值从AOUT输出

if __name__ == "__main__":
	setup(0x48) 
 #在树莓派终端上使用命令“sudo i2cdetect -y 1”,查询出PCF8591的地址为0x48
	while True:
		print ('电位计   AIN0 = ', read(0))   #电位计模拟信号转化的数字值
		print ('光敏电阻 AIN1 = ', read(1))   #光敏电阻模拟信号转化的数字
        print ('热敏电阻 AIN2 = ', read(2))   #热敏电阻模拟信号转化的数字值
		tmp = read(0)
		tmp = tmp*(255-125)/255+125 
# 125以下LED不会亮,所以将“0-255”转换为“125-255”,调节亮度时灯不会熄灭
		write(tmp)
		time.sleep(2)

2、main.py

  导入PCF8591,只打印电位器电压大小A/D转换后的数字值。

#!/usr/bin/env python
import PCF8591 as ADC

def setup():
	ADC.setup(0x48)

def loop():
	while True:
		print ADC.read(0) 
  #打印电位计电压大小A/D转换后的数字值(从AIN0借口输入的)
  #范围是0~255,0时LED灯熄灭,255时灯最亮
		ADC.write(ADC.read(0)) 
  #将0通道输入的电位计电压数字值转化成模拟值从AOUT输出
  #给LED灯提供电源VCC输入

def destroy():
	ADC.write(0)

if __name__ == "__main__":
	try:
		setup()
		loop()
	except KeyboardInterrupt:
		destroy()

3、实验结果

  运行程序后,led灯被点亮,调节蓝色电位器,led灯的亮度出现变化。
实验结果
  Shell显示电位计、光敏电阻、热敏电阻的数字值。
实验结果2
  但由于写入的tmp加了125的缘故,始终保持一个较亮的情况,所以调节电位器,led灯也并不会有明显的明暗变化,大家测试的时候可以将其去掉试试。
在这里插入图片描述
  手动将其调节为0,100,200,led灯明暗情况:
手动调节

4、C程序

#include <wiringPi.h>
#include <pcf8591.h>
#include <stdio.h>
#include <time.h>

//PCF8591默认的I2C设备地址
#define Address 0x48

//模拟信号输入端的地址
#define BASE 0x40
#define A0 0x40
#define A1 0x41
#define A2 0x42
#define A3 0x43

//供电(mV)
#define POWER 5000

//函数声明
void ShowTime();
float AD_work(unsigned char channel);

int main(void)
{
    
    
	//初始化wiringPi设置
	wiringPiSetup();

	//设置pcf8591的器件地址
	pcf8591Setup(BASE, Address);

	float AD_val;

	while (1)
	{
    
    
		AD_val=AD_work(A0);//读取A0端口的电压值
		
		ShowTime(); //打印当前时间
		
		printf("A0 value: %fmV\n", AD_val); //打印A0引脚的输入电压
		//printf("asgydasg");
		
		delay(100);
	}
}

//显示系统时间
void ShowTime()
{
    
    
	time_t t;
	struct tm *p;
	int hour = 0, min = 0, sec = 0;
	time(&t);
	p = gmtime(&t);
	hour = 8 + p->tm_hour; //获取当地时间,与UTC时间相差8小时
	min = p->tm_min;
	sec = p->tm_sec;
	printf("\nNow time: %.2d:%.2d:%.2d\n", hour, min, sec);
}

float AD_work(unsigned char channel)
{
    
    
	float AD_val; //定义处理后的数值AD_val为浮点数
	unsigned char i;
	for (i = 0; i < 10; i++) 
		AD_val += analogRead(channel); //转换10次求平均值(提高精度)
	AD_val /= 10;
	AD_val = (AD_val * POWER)/ 255 ; //AD的参考电压是单片机上的5v,所以乘5即为实际电压值
	return AD_val;
}

在这里插入图片描述
实验结果:
C实验结果

四、知识准备

1、PCF8591详解

  ​ PCF8591是一个8位模数转换器或8位数模转换器模块,这意味着每个引脚可以读取高达0 - 255的模拟值;该模块有四个模拟输入和一个模拟输出,同时适用于I2C通信;此外它需要2.5V - 6V电源电压并具有低待机电流,我们可以通过调节蓝色电位器的旋钮来控制输入电压。板上还有三个跳线。 J4连接选择热敏电阻接入电路,J5连接选择LDR /光敏电阻接入电路和J6连接选择0 - 5V可调电压接入电路。要访问这些电路,必须使用这些跳线的地址:J6为0x50,J5为0x60,J4为0x70。电路板上有两个LED,D1和D2 ,D1表示输出电压强度,D2表示电源电压强度。输出或电源电压越高,LED D1或D2的强度越高。
PCF8591

1.1、模块功能描述

  • 1 模块芯片采用 PCF8951
  • 2 模块支持外部 4 路电压输入采集(电压输入范围 0-5v)
  • 3 模块集成光敏电阻,可以通过 AD 采集环境光强精确数值
  • 4 模块集成热敏电阻,可以通过 AD 采集环境温度精确数值
  • 5 模块集成 1 路 0-5V 电压输入采集(通过蓝色电位器调节输入电压)
  • 6 模块带电源指示灯(对模块供电后指示灯会亮)
  • 7 模块带 DA 输出指示灯,当模块 DA 输出接口电压达到一定值,会点亮板上 DA 输出指示灯,电压越大,指示灯亮度越明显;

1.2、模块接口说明

本模块左边和右边分别外扩 2 路排针接口,分别说明如下:
pcf8591模块图
左边 :

  • AOUT :芯片 DA 输出接口
  • AIN0 :芯片模拟输入接口 0
  • AIN1 :芯片模拟输入接口 1
  • AIN2 :芯片模拟输入接口 2
  • AIN3 :芯片模拟输入接口 3

右边 :

  • SCL :IIC 时钟接口 接单片机 IO 口
  • SDA :IIC 数字接口 接单片机 IO 口
  • GND :模块地 外接地
  • VCC :电源接口 外接 3.3v-5v

模块共有 3 个短路帽,分别作用如下:
J4 :接上 J4 短路帽,选择热敏电阻接入电路
J5 :接上 J5 短路帽,选择光敏电阻接入电路
J6 :接上 J6 短路帽,选择 0-5V 可调电压接入电路

注:如果需要使用四路外部电压输入,请将 3 个红色短路帽都取下

1.3、模块原理图

PCF8591原理图
在这里插入图片描述

1.4、模块原理说明

  ​ PCF8591是AIN端口输入模拟电压,然后PCF8591将转换后的数字量通过I2C总线发送给单片机,或是单片机通过I2C总线给一个数字量,然后PCF8591通过AOUT端口将模拟电压输出。

第一字节:总线地址

  对于 PCF8591的使用,依旧是按照先寻地址,再读写相应寄存器的步骤,PCF8591芯片所能接收的地址包含固定部分和可编程部分。可编程部分必须根据地址引脚A0,A1,和A2来设置,在I2C总线协议中地址必须是起始条件后作为第一个字节发送,地址字节的最后一位用来设置对目标地址的读或写。地址字节格式如下所示:
地址字节
  PCF8591采用典型的I2C总线接口器件寻址方法,即总线地址由器件地址、引脚地址和方向位组成。飞利蒲公司规定A/D器件地址为1001,引脚地址为A2A1A0,其值由用户选择,因此I2C系统中最多可接2的三次方=8个具有I2C总线接口的A/D器件。地址的最后一位为方向位R/ W,当主控器对A/D器件进行读操作时为1,进行写操作时为0。总线操作时,由器件地址、引脚地址和方向位组成的从地址为主控器发送的第一字节。

  对于PCF8591来说,其default address为0x48(十六进制数),转换为二进制数是1001000,所以可以知道他的A0、A1、A2三个引脚值均为0。需要再提前强调一点,Python代码中所有0x十六进制数,只有0x48代表Address,也只有PCF拥有Address,其余所有十六进制数都不代表Address。

第二字节:控制字节
  第一个字节是地址字节,那么第二个字节就是控制字节,控制字节发送到PCF8591的控制寄存器中,用于控制器件的功能,控制字格式如下所示:
控制字节
简略图:
在这里插入图片描述
其中:

  • D1、D0 两位是A/D通道编号:00通道0,01通道1,10通道2,11通道3
  • D2 自动增量选择(0为禁止自动增量,1为允许自动增量),如果允许自动增量,则在每次A/D转换后,通道编号会自动递增。
  • D3 特征位:固定值为:0。
  • D5、D4 模拟量输入选择:00为四路单端输入、01为三路差分输入、10为两路单端与一路差分输入、11为两路差分输入。
  • D6 使能模拟输出AOUT有效(1为有效,0为无效)。
  • D7 特征位:固定值为:0。

  编程中遇到的“bus.write_byte(address,0x40) ” 语句就是发送控制字“0x40”,40就代表控制字“0100 0000”,主要表示模拟输出有效,四路单端输入,禁止自动增量,A/D通道为0。
在这里插入图片描述

  当系统为A/D转换时,模拟输出允许为0。模拟量输入选择位取值由输入方式决定:四路单端输入时取00,三路差分输入时取01,单端与差分输入时取10,二路差分输入时取11。最低两位时通道编号位,当对0通道的模拟信号进行A/D转换时取00,当对1通道的模拟信号进行A/D转换时取01,当对2通道的模拟信号进行A/D转换时取10,当对3通道的模拟信号进行A/D转换时取11。

  在进行数据操作时,首先是主控器发出起始信号,然后发出读寻址字节,被控器做出应答后,主控器从被控器读出第一个数据字节,主控器发出应答,主控器从被控器读出第二个数据字节,主控器发出应答…一直到主控器从被控器中读出第n个数据字节,主控器发出非应答信号,最后主控器发出停止信号。

1.5、D/A转换

在这里插入图片描述
  发送给PCF8591的第三个字节被存储到DAC数据寄存器,并使用片上D/A转换器转化成对应的模拟电压。
转换公式
  我们可以通过转换公式,将AD转换后的数字量转换为对应的电压值,在数码管或液晶上显示。

#define fun(x) (int)(5*x/255.0*100+0.5)             //数字电压x转换为模拟电压的公式

1.6、A/D转换

在这里插入图片描述
  A/D转换器采用逐次逼近转换技术,在A/D转换周期将临时使用片上D/A转换器和高增益比较器,一个A/D转换周期总是开始于发送一个有效读模式地址给PCF8591之后,A/D转换周期在应答时钟脉冲的后沿被触发,并在传输前一次转换结果时执行。

1.7、PCF8591与Raspberry Pi的关系

在这里插入图片描述
  将整个系统看作一个进水/出水装置,装置有四根进水管,分别命名为AIN0, AIN1, AIN2, AIN3,还有唯一一根出水管,命名为AOUT。还有四个中转通道,分别命名为channel0, channel1, channel2, channel3,从四根进水管进来的水可以受控制地从某一个通道中流出,进入总管道,之后通过两根出水管道——SDA和SCL,进入一个水的加工处理储存设备。

  Raspberry PI是“MASTER”,PCF是“SLAVER”,他们之间是主从关系。如果主器件要发送数据给从器件,则会启动数据传输,并发送数据至从器件,最后终止数据传输;如果主器件想要接受从器件发来的数据,同样也是发出命令启动数据传输,接收从器件发来的数据,最后终止接收过程。强调一点:每次数据传输都是由“MASTER”主动开启的

1.8、I2C协议通信

在这里插入图片描述
   I2C就是一种沟通方式,所以这个数据传输流程,就可以理解为在I2C这种特定沟通方式下的一次沟通。讲的更通俗一点,就是一次聊天对话。

  聊天的第一步,是拿出手机打开微信。(对应START condition,当SDA和SCL两条线的电平同时满足某一特定条件时,传输开始。)

  打开微信后需要找一个联系人才能聊天,我们之前说“MASTER”有很多“SLAVERS”,这一步便是“MASTER”选择与某一个“SLAVER”对话。(对应ADDRESS。)

  第三步,“MASTER”确定这次对话的目的的给“SLAVER”分配活儿干,还是让“SLAVER”干完活后汇报。(对应R/W,R means read,W means write,控制这次数据传输的目的是树莓派写入一个值到PCF中,还是树莓派从PCF中读取一个值。)
可以看到有很多ACK。ACK means Acknowledge character,即确认字符,意思是数据接收者告诉数据传输者,“已收到”。

  之后便是数据传输,每传输一个byte的数据,都会反馈一个ACK确认数据收到。

五、主要参考资料

猜你喜欢

转载自blog.csdn.net/qq_41071754/article/details/114689787