PCF8591使用及Python控制

PCF8591使用及Python控制

INTRODUCTION

  大家一定对于PCF8591芯片,对于其中的A0、A1、A2、channel0-channel3、AIN0-AIN3、AOUT、SCL、SDA等等以及I2C协议有着很大的困惑。同时也没有完全理解Python代码中的0x40、0x41、0x42、0x43、0x48到底是什么,bus.write_byte(0x48, 0x40) bus.write_byte(0x48, 0x40, value) bus.read_byte(0x48)这些代码到底控制什么。

  这篇文章将帮助大家理清以上内容。

一、PCF8591与Raspberry Pi的关系

  做一个很简单的形象化,将整个系统看作一个进水/出水装置。
MASTER与SLAVER示意图
  装置有四根进水管,分别命名为AIN0, AIN1, AIN2, AIN3,还有唯一一根出水管,命名为AOUT。还有四个中转通道,分别命名为channel0, channel1, channel2, channel3,从四根进水管进来的水可以受控制地从某一个通道中流出,进入总管道,之后通过两根出水管道——SDA和SCL,进入一个水的加工处理储存设备。

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

二、PCF的地址与A0-A2引脚

  一个“MASTER”不可能只有一个“SLAVER”。所以对于“MASTER”来说,需要给“SLAVERS”编号,以便控制、命令。这个编号也就是PCF地址。地址的编码规则如下图:
ADDRESS编码规则
  可以看出,这是一个七位二进制数,前四位是fixed number,后三位分别对应PCF的三个引脚——A0, A1, A2。每一个引脚的值都可以是0或1,一共2^3=8种排列方式,也即一共8个可使用的地址。所以一个“MASTER”最多能同时控制8个“SLAVER”。对于PCF8591来说,其default address为0x48(十六进制数),转换为二进制数是1001000,所以可以知道他的A0、A1、A2三个引脚值均为0。

  需要再提前强调一点,Python代码中所有0x十六进制数,只有0x48代表Address,也只有PCF拥有Address,其余所有十六进制数都不代表Address。(这是一个天坑)

三、I2C协议与SDA SCL

  “MASTER”与“SLAVERS”之间需要有沟通交流,给“SLAVER”分配工作或是让“SLAVER”汇报工作,并告知“SLAVER”如何做。所以他们之间有一个特定的沟通方式,这个沟通方式就叫I^2 C通信协议(Inter-Integrated Circuit)。I^2 C总线需要两根线来实现连接于总线上的器件之间的信息传输,一根是SDA(Serial Data)串行数据线,另一根是SCL(Serial Clock)串行时钟线。

  下图是AD convert的I2C数据传输流程。
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确认数据收到。
chatting

四、Python代码控制读取/写入值

import smbus
import time

if __name__ == "__main__":
    bus=smbus.SMBus(1)
    bus.write_byte(0x48,0x42)
    bus.read_byte(0x48)
    while True:
        num= bus.read_byte(0x48)
        print(num)
        bus.write_byte_data(0x48,0x42,160)
        time.sleep(0.01)

  其实代码非常简单。之前参考文献给出的代码令人迷惑的原因主要是自定义了很多方法。现在用最简单的办法写出来,用到的核心方法只有三个:

bus.read_byte(ADDRESS)
bus.write_byte(ADDRESS, CONTROL_byte)
bus.write_byte_data(ADDRESS, CONTROL_byte, value)

  顾名思义,read_byte就是从PCF中读取一个byte的数据, write_byte就是向PCF中写入一个byte的数据,write_byte_data就是像PCF中写入data。

  有人就会问了,write_byte和write_byte_data有什么区别?

  还记得在PART1中强调的点吗?每次数据传输一定是由“MASTER”主动开启的,所以write_byte的目的是明确“MASTER”与哪个“SLAVER”对话,建立联系,并以一个“CONTROL byte”告知“SLAVER”如何工作,并不传输value。这也是为什么代码中的第一步是bus.read_byte(0x48)

  树莓派主动开启对话,与0x48地址对应的PCF8591建立联系,并通过一个CONTROL byte明确工作模式(Analog Input Mode)。

  第二个问题来了,CONTROL byte是什么?那不是个地址吗?

  这时候应该要想起PART2强调的内容(只有0x48是地址)。write_byte方法的第二个传参是一个CONTROL byte。
analog input mode
  这是官方文档对于CONTROL byte的解释。8位二进制码,其中第一位和第五位都是fixed number—0,可被编写的部分是第二位——代表是否开启AOUT输出口,0表示不开启,1表示开启,只有该位是1的时候,DA才会工作,数字信号被转为模拟信号从AOUT端口输出;第三四位——代表四种不同的analog input mode,用以明确AIN端口与channel之间的数据关系。
?
  在第一幅图中有一个大大的问号,analog input mode便是明确这个问号。有两种对应关系:一种是single-ended,即一对一。例如在00模式下,从channel0中读到的数据就是AIN0口输入的模拟量经过AD转换后的数字值,00模式的四个AIN端口都是分别一一对应于一个channel;在10模式下,AIN0和AIN1是一一对应channel0和channel1。另外一种对应关系是differential,即差分。例如在01模式下,channel0的值是AIN0与AIN3的差值;在10模式下,channel2的值是AIN2与AIN3的差值。

  CONTROL byte的第七八位是AD channel number,用来控制从哪个channel读取数据。第六位是auto-increment,值为1时,每次读取数据都会自动切换channel,第一次读channel0的数据,下一次读到的就是channel1的数据,以此类推。

  在Python代码中,CONTROL byte是一个十六进制数,转换为二进制后就是八位二进制。控制方法为:确定CONTROL byte中可编码位上你想要的取值,得到一个八位二进制数,转为十六进制数,输入Python方法。

  讲到这里后就几乎结束了大部分的内容。继续代码示例,read_byte,读值过程不需要CONTROL byte,所以传参只有ADDRESS。

  可以看到,在真正读取想要的num之前,还有一次空读。这是因为PCF8591在接到读取命令后,同时进行上一次的转换数据的传输及本次的数据转换,所以第一次空读的数据是一个不确定的数,第一次的空读只是为了启动下一次正常的读取。

bus.write_byte_data(0x48,0x42,160)

  write_byte_data这个方法再多解释一下,写入的值会直接作为一个digital value,经过转换后以电压形式的模拟量从AOUT口输出。我们使用的PCF8591是一个8bits的AD/DA转换器,8bits的意思是AD/DA共有 2^8=256个刻度,所以传输的digital值范围是0-255。举个例子,你希望从AOUT口输出一个3.13V的电压给LED灯,应该写入的值为3.13v/5v*255=160(这里ADC接5v的基准电压),但是实际上用电压表测得此时LED灯两端电压为2.64v,与理想值有一定误差。

五、Something More

  我几乎尝试了所有的analog input mode,将麦克风接入不同的AIN口,选择不同的channel,用示波器/电压表+python画出的图进行了很多很多实验,在差分模式下还是有很多难以解释的试验现象,但是基本可以肯定整个原理是没有错的。

  补充一点,single-ended模式下,AD转换的模拟量就是该AIN口与GND的电压;differential模式下,AD转换的模拟量则是两个AIN口之间的电压差,所以如果只使用一个AIN口且还想要用差分模式,那么另一个AIN口要记着接地。

六、Learning Experience

本次超过30个小时的学习过程得到的经验:
①永远不要认为你之前的理解都是对的。发现整个理解过程出现错误,有时 候有必要推倒重建。
②网上资料参差不齐,错误百出,绝对不能盲目相信。
③实验要清楚记录条件及现象,不然会因为没记清现象/条件而做很多次重复实验。
④在学习一个全新的知识系统的过程中会有很多信息涌入,往往在查找资料解决问题1的时候会发现问题2,然后注意力转移到问题2上,之后又会发现新的问题,进入一个死循环。所以需要适时停止,思考一下你是谁,你在哪儿,你要干什么。
⑤把问题记录下来,然后接着探索。很有可能在第二天的时候你回看第一天的问题,会发出感叹:我怎么会问这么愚蠢的问题。
⑥队友很重要。讨论/止损/提问/复盘/总结。

我的第一篇CSDN博客,希望讲的清楚。希望大家喜欢!!

猜你喜欢

转载自blog.csdn.net/m0_47262669/article/details/109140960