Python 物联网入门指南(三)(2)https://developer.aliyun.com/article/1507200
准备工作
这个配方使用了前一个配方中使用的 RGB LED 套件;您还需要以下额外的物品:
- 面包板(半尺寸或更大)
- 2 x DuPont 母对公跳线
- 倾斜开关(适合滚珠类型)
- 1 x 470 欧姆电阻(R_Protect)
- 面包板线(实心线)
倾斜开关应添加到 RGB LED(如准备工作部分的多路复用彩色 LED配方中所述)。倾斜开关的接线如下:
!倾斜开关连接到 GPIO 输入(GPIO 引脚 24)和 Gnd(GPIO 引脚 6)
为了重现 POV 图像,您需要能够快速移动 LED 并来回倾斜开关。请注意倾斜开关安装在侧面倾斜,因此当向左移动时开关将打开。建议将硬件安装在一根木头或类似设备上。您甚至可以使用便携式 USB 电池组和 Wi-Fi dongle 来通过远程连接为树莓派供电和控制(有关详细信息,请参见第一章中的*通过网络远程连接树莓派使用 SSH(和 X11 转发)*配方):
!持续视觉硬件设置
您还需要已完成的rgbled.py
文件,我们将在如何操作…部分进一步扩展它。
如何操作…
- 创建一个名为
tilt.py
的脚本来报告倾斜开关的状态:
#!/usr/bin/python3 #tilt.py import RPi.GPIO as GPIO #HARDWARE SETUP # GPIO # 2[===========T=]26[=======]40 # 1[=============]25[=======]39 #Tilt Config TILT_SW = 24 def tilt_setup(): #Setup the wiring GPIO.setmode(GPIO.BOARD) #Setup Ports GPIO.setup(TILT_SW,GPIO.IN,pull_up_down=GPIO.PUD_UP) def tilt_moving(): #Report the state of the Tilt Switch return GPIO.input(TILT_SW) def main(): import time tilt_setup() while True: print("TILT %s"% (GPIO.input(TILT_SW))) time.sleep(0.1) if __name__=='__main__': try: main() finally: GPIO.cleanup() print("Closed Everything. END") #End
- 您可以通过直接运行以下命令来测试脚本:
sudo python3 tilt.py
- 将以下
rgbled_pov()
函数添加到我们之前创建的rgbled.py
脚本中;这将允许我们显示图像的单行:
def rgbled_pov(led_pattern,color,ontime): '''Disable all the LEDs and re-enable the LED pattern in the required color''' led_deactivate(LED,RGB) for led_num,col_num in enumerate(led_pattern): if col_num >= 1: led_activate(LED[led_num],color) time.sleep(ontime)
- 现在,我们将创建以下文件,名为
rgbledmessage.py
,以执行显示我们的消息所需的操作。首先,我们将导入所使用的模块:更新的rgbled
模块,新的tilt
模块和 Pythonos
模块。最初,我们将DEBUG
设置为True
,这样 Python 终端在脚本运行时将显示额外的信息:
#!/usr/bin/python3 # rgbledmessage.py import rgbled as RGBLED import tilt as TILT import os DEBUG = True
- 添加一个
readMessageFile()
函数来读取letters.txt
文件的内容,然后添加processFileContent()
来为每个字母生成一个 LED 模式的Python 字典:
def readMessageFile(filename): assert os.path.exists(filename), 'Cannot find the message file: %s' % (filename) try: with open(filename, 'r') as theFile: fileContent = theFile.readlines() except IOError: print("Unable to open %s" % (filename)) if DEBUG:print ("File Content START:") if DEBUG:print (fileContent) if DEBUG:print ("File Content END") dictionary = processFileContent(fileContent) return dictionary def processFileContent(content): letterIndex = [] #Will contain a list of letters stored in the file letterList = [] #Will contain a list of letter formats letterFormat = [] #Will contain the format of each letter firstLetter = True nextLetter = False LETTERDIC={} #Process each line that was in the file for line in content: # Ignore the # as comments if '#' in line: if DEBUG:print ("Comment: %s"%line) #Check for " in the line = index name elif '"' in line: nextLetter = True line = line.replace('"','') #Remove " characters LETTER=line.rstrip() if DEBUG:print ("Index: %s"%line) #Remaining lines are formatting codes else: #Skip firstLetter until complete if firstLetter: firstLetter = False nextLetter = False lastLetter = LETTER #Move to next letter if needed if nextLetter: nextLetter = False LETTERDIC[lastLetter]=letterFormat[:] letterFormat[:] = [] lastLetter = LETTER #Save the format data values = line.rstrip().split(' ') row = [] for val in values: row.append(int(val)) letterFormat.append(row) LETTERDIC[lastLetter]=letterFormat[:] #Show letter patterns for debugging if DEBUG:print ("LETTERDIC: %s" %LETTERDIC) if DEBUG:print ("C: %s"%LETTERDIC['C']) if DEBUG:print ("O: %s"%LETTERDIC['O']) return LETTERDIC
- 添加一个
createBuffer()
函数,它将把消息转换为每个字母的 LED 模式系列(假设该字母由letters.txt
文件定义):
def createBuffer(message,dictionary): buffer=[] for letter in message: try: letterPattern=dictionary[letter] except KeyError: if DEBUG:print("Unknown letter %s: use _"%letter) letterPattern=dictionary['_'] buffer=addLetter(letterPattern,buffer) if DEBUG:print("Buffer: %s"%buffer) return buffer def addLetter(letter,buffer): for row in letter: buffer.append(row) buffer.append([0,0,0,0,0]) buffer.append([0,0,0,0,0]) return buffer
- 接下来,我们定义一个
displayBuffer()
函数,使用rgbled
模块中的rgbled_pov()
函数来显示 LED 模式:
def displayBuffer(buffer): position=0 while(1): if(TILT.tilt_moving()==False): position=0 elif (position+1)<len(buffer): position+=1 if DEBUG:print("Pos:%s ROW:%s"%(position,buffer[position])) RGBLED.rgbled_pov(buffer[position],RGBLED.RGB_GREEN,0.001) RGBLED.rgbled_pov(buffer[position],RGBLED.RGB_BLUE,0.001)
- 最后,我们创建一个
main()
函数来执行所需的每个步骤: - 设置硬件组件(RGB LED 和倾斜开关)。
- 阅读
letters.txt
文件。 - 定义 LED 字母模式的字典。
- 生成一个缓冲区来表示所需的消息。
- 使用
rgbled
模块显示缓冲区,并使用tilt
模块进行控制:
def main(): RGBLED.led_setup() TILT.tilt_setup() dict=readMessageFile('letters.txt') buffer=createBuffer('_COOKBOOK_',dict) displayBuffer(buffer) if __name__=='__main__': try: main() finally: RGBLED.led_cleanup() print("Closed Everything. END") #End
- 创建以下文件,名为
letters.txt
,以定义显示示例'_COOKBOOK_'
消息所需的 LED 模式。请注意,此文件只需要为消息中的每个唯一字母或符号定义一个模式:
#COOKBOOK "C" 0 1 1 1 0 1 0 0 0 1 1 0 0 0 1 "O" 0 1 1 1 0 1 0 0 0 1 1 0 0 0 1 0 1 1 1 0 "K" 1 1 1 1 1 0 1 0 1 0 1 0 0 0 1 "B" 1 1 1 1 1 1 0 1 0 1 0 1 0 1 0 "_" 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
工作原理…
第一个函数“readMessageFile()”将打开并读取给定文件的内容。然后使用“processFileContent()”返回一个包含文件中定义的字母对应的 LED 图案的 Python 字典。处理文件时,会处理文件中的每一行,忽略包含“#”字符的任何行,并检查“”字符以指示接下来的 LED 图案的名称。处理文件后,我们得到一个包含 LED 图案的 Python 字典,其中包含'_'
、'C'
、'B'
、'K'
和'O'
字符。
'_': [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] 'C': [[0, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1]] 'B': [[1, 1, 1, 1, 1], [1, 0, 1, 0, 1], [0, 1, 0, 1, 0]] 'K': [[1, 1, 1, 1, 1], [0, 1, 0, 1, 0], [1, 0, 0, 0, 1]] 'O': [[0, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 1, 1, 0]]
现在我们有一系列可供选择的字母,我们可以使用“createBuffer()”函数创建 LED 图案序列。正如其名称所示,该函数将通过查找消息中的每个字母并逐行添加相关的图案来构建 LED 图案的缓冲区。如果在字典中找不到字母,则将使用空格代替。
最后,我们现在有一系列准备显示的 LED 图案。为了控制我们何时开始序列,我们将使用 TILT 模块并检查倾斜开关的状态:
当倾斜开关不移动时的位置(左)和移动时的位置(右)
倾斜开关由一个小滚珠封闭在一个空心绝缘圆柱体中组成;当球静止在圆柱体底部时,两个引脚之间的连接闭合。当球移动到圆柱体的另一端,远离引脚的接触时,倾斜开关打开:
倾斜开关电路,开关闭合和开关打开时
先前显示的倾斜开关电路将在开关闭合时将 GPIO 引脚 24 连接到地。然后,如果我们读取引脚,当它静止时将返回False
。通过将 GPIO 引脚设置为输入并启用内部上拉电阻,当倾斜开关打开时,它将报告True
。
如果倾斜开关是打开的(报告True
),那么我们将假设单位正在移动,并开始显示 LED 序列,每次显示 LED 图案的一行时递增当前位置。为了使图案更加丰富多彩(只是因为我们可以!),我们会用另一种颜色重复每一行。一旦“TILT.tilt_moving()”函数报告我们已经停止移动或者我们正在向相反方向移动,我们将重置当前位置,准备重新开始整个图案:
消息由 RGB LED 显示 - 在这里,我们一起使用绿色和蓝色
当 RGB LED 模块和倾斜开关来回移动时,我们应该看到消息在空中显示!
尝试尝试不同的颜色组合、速度和手臂挥动,看看你能产生什么效果。你甚至可以创建一个类似的设置,安装在车轮上,产生连续的 POV 效果。
第八章:感知和显示真实世界的数据
在本章中,我们将涵盖以下主题:
- 使用 I2C 总线的设备
- 使用模拟数字转换器读取模拟数据
- 记录和绘制数据
- 通过 I/O 扩展器扩展树莓派 GPIO
- 在 SQLite 数据库中捕获数据
- 查看来自您自己的 Web 服务器的数据
- 感知和发送数据到在线服务
介绍
在本章中,我们将学习如何收集来自现实世界的模拟数据并对其进行处理,以便在程序中显示、记录、绘制和共享数据,并利用这些数据。
我们将通过使用树莓派的 GPIO 连接来扩展树莓派的功能,与模拟数字转换器(ADC)、LCD 字母显示器和数字端口扩展器进行接口。
使用 I2C 总线的设备
树莓派可以支持多种高级协议,可以轻松连接各种设备。在本章中,我们将专注于最常见的总线,称为 I-squared-C(I²C)。它提供了一个用于通过两根导线与设备通信的中速总线。在本节中,我们将使用 I²C 与 8 位 ADC 进行接口。该设备将测量模拟信号,将其转换为 0 到 255 之间的相对值,并将该值作为数字信号(由 8 位表示)通过 I²C 总线发送到树莓派。
I²C 的优势可以总结如下:
- 即使在总线上有许多设备的情况下,也能保持低引脚/信号计数
- 适应不同从设备的需求
- 容易支持多个主设备
- 包括 ACK/NACK 功能以改进错误处理
准备工作
并非所有树莓派镜像都启用了 I²C 总线;因此,我们需要启用模块并安装一些支持工具。Raspbian 的新版本使用设备树来处理硬件外围设备和驱动程序。
为了使用 I²C 总线,我们需要在bootconfig.txt
文件中启用 ARM I²C。
您可以使用以下命令自动执行此操作:
sudo raspi-config
从菜单中选择高级选项,然后选择 I²C,如下截图所示。当询问时,选择是以启用接口,然后点击是以默认加载模块:
raspi-config 菜单
从菜单中选择 I2C,选择是以启用接口并默认加载模块。
raspi-config
程序通过修改/boot/config.txt
以包括dtparam=i2c_arm=on
来启用I2C_ARM
接口。另一种总线(I2C_VC)通常保留用于与树莓派 HAT 附加板进行接口(从板载存储器设备读取配置信息);但是,您也可以使用dtparam=i2c_vc=on
来启用此功能。
如果您愿意,您还可以使用raspi-config
列表启用 SPI,这是另一种类型的总线。
接下来,我们应该包括 I²C 模块在打开树莓派时加载,如下所示:
sudo nano /etc/modules
添加以下内容并保存(Ctrl + X, Y, Enter):
i2c-dev i2c-bcm2708
类似地,我们还可以通过添加spi-bcm2708
来启用 SPI 模块。
接下来,我们将安装一些工具,以便直接从命令行使用 I²C 设备,如下所示:
sudo apt-get update sudo apt-get install i2c-tools
最后,在连接硬件之前关闭树莓派,以便应用更改,如下所示:
sudo halt
您将需要一个 PCF8591 模块(这些的零售商在附录硬件和软件清单中列出)或者您可以单独获取 PCF8591 芯片并构建自己的电路(有关电路的详细信息,请参阅*还有更多…*部分):
来自 dx.com 的 PCF8591 ADC 和传感器模块
将 GND、VCC、SDA 和 SCL 引脚连接到树莓派的 GPIO 引脚头,如下所示:
树莓派 GPIO 引脚上的 I2C 连接您可以通过研究设备的数据表找出要发送/读取的消息以及用于控制设备的寄存器,使用相同的 I²C 工具/代码与其他 I²C 设备。
操作步骤…
i2cdetect
命令用于检测 I²C 设备(--y
选项跳过有关可能干扰连接到 I²C 总线的其他硬件的警告)。以下命令用于扫描两个总线:
sudo i2cdetect -y 0 sudo i2cdetect -y 1
- 根据您的树莓派板子版本,设备的地址应该在总线 0 上列出(适用于 Model B Rev1 板)或总线 1 上(适用于树莓派 2 和 3,以及树莓派 1 Model A 和 Model B Revision 2)。默认情况下,PCF8591 地址是
0x48
:
要使用的 I²C 总线号 | 总线 00 | 总线 11 |
树莓派 2 和 3 | HAT ID(I2C_VC) | GPIO(I2C_ARM) |
Model A 和 Model B Revision 2 | P5 | GPIO |
Model B Revision 1 | GPIO | N/A |
- 以下屏幕截图显示了
i2cdetect
的输出:
PCF8591 地址(48)在总线 1 上显示
如果没有列出任何内容,请关闭并仔细检查您的连接(来自www.dx.com的 ADC 模块在上电时会打开红色 LED)。
如果收到错误消息,指出/dev/i2c1
总线不存在,您可以执行以下检查:
- 确保
/etc/modprobe.d/raspi-blacklist.conf
文件为空(即模块未被列入黑名单),使用以下命令查看文件:
sudo nano /etc/modprobe.d/raspi-blacklist.conf
- 如果文件中有任何内容(例如
blacklist i2c-bcm2708
),请删除并保存 - 检查
/boot/config
,确保没有包含device_tree_param=
的行(这将禁用对新设备树配置的支持,并禁用对某些树莓派 HAT 附加板的支持) - 使用
lsmod
检查模块是否已加载,并查找i2c-bcm2708
和i2c_dev
- 使用检测到的总线号(
0
或1
)和设备地址(0x48
),使用i2cget
从设备读取(上电或通道更改后,您需要两次读取设备才能看到最新值),如下所示:
sudo i2cget -y 1 0x48 sudo i2cget -y 1 0x48
- 要从通道
1
读取(这是模块上的温度传感器),我们可以使用i2cset
将0x01
写入 PCF8591 控制寄存器。同样,使用两次读取来从通道1
获取新样本,如下所示:
sudo i2cset -y 1 0x48 0x01 sudo i2cget -y 1 0x48 sudo i2cget -y 1 0x48
- 要循环遍历每个输入通道,请使用
i2cset
将控制寄存器设置为0x04
,如下所示:
sudo i2cset -y 1 0x48 0x04
- 我们还可以使用以下命令控制 AOUT 引脚,将其完全打开(点亮 LED D1):
sudo i2cset -y 1 0x48 0x40 0xff
- 最后,我们可以使用以下命令将其完全关闭(关闭 LED D1):
sudo i2cset -y 1 0x48 0x40 0x00
工作原理…
设备上电后的第一次读取将返回0x80
,并且还将触发通道 0 的新样本。如果再次读取,它将返回先前读取的样本并生成新样本。每次读取都将是一个 8 位值(范围从0
到255
),表示电压到 VCC(在本例中为 0V 到 3.3V)。在www.dx.com模块上,通道 0 连接到光传感器,因此如果用手遮住模块并重新发送命令,您将观察到值的变化(较暗表示较高的值,较亮表示较低的值)。您会发现读数总是滞后一步;这是因为当它返回先前的样本时,它捕获了下一个样本。
我们使用以下命令指定要读取的特定通道:
sudo i2cset -y 1 0x48 0x01
这将更改要读取的通道为通道 1(在模块上标有AIN1)。请记住,您需要执行两次读取,然后才能从新选择的通道看到数据。以下表格显示了通道和引脚名称,以及哪些跳线连接器启用/禁用了每个传感器:
通道 | 0 | 1 | 2 | 3 |
引脚名称 | AIN0 | AIN1 | AIN2 | AIN3 |
传感器 | 光敏电阻 | 热敏电阻 | 外部引脚 | 电位器 |
跳线 | P5 | P4 | P6 |
接下来,我们通过设置控制寄存器的模拟输出使能标志(第 6 位)来控制 AOUT 引脚,并使用下一个值来设置模拟电压(0V-3.3V,0x00-0xFF),如下所示:
sudo i2cset -y 1 0x48 0x40 0xff
最后,可以将第 2 位(0x04
)设置为自动递增,并循环通过输入通道,如下所示:
sudo i2cset -y 1 0x48 0x04
每次运行i2cget -y 1 0x48
,下一个通道将被选择,从 AIN0 开始,然后从 AIN1 到 AIN3 再返回到 AIN0。
要理解如何设置值中的特定位,有助于查看数字的二进制表示。8 位值0x04
可以用二进制b0000 0100
来表示(0x
表示值以十六进制表示,b 表示二进制数)。
二进制数中的位从右到左进行计数,从 0 开始 - 即,MSB 7 6 5 4 3 2 1 0 LSB。
第 7 位被称为最高有效位(MSB),第 0 位被称为最低有效位(LSB)。因此,通过设置第 2 位,我们最终得到b0000 0100
(即0x04
)。
还有更多…
I²C 总线允许我们只使用少量线路轻松连接多个设备。PCF8591 芯片可用于将自己的传感器连接到模块或仅连接芯片。
使用多个 I2C 设备
I²C 总线上的所有命令都是针对特定的 I²C 设备的(许多设备可以选择将一些引脚设为高电平或低电平以选择附加地址,并允许多个设备存在于同一总线上)。每个设备必须具有唯一地址,以便一次只有一个设备会做出响应。PCF8591 的起始地址是0x48
,通过三个地址引脚可选择附加地址为0x4F
。这允许在同一总线上使用多达八个 PCF8591 设备。
如果决定使用位于 GPIO 引脚 27 和 28(或位于 Model A 和 Revision 2 Model B 设备的 P5 标头)的 I2C_VC 总线,则可能需要在 I²C 线和 3.3V 之间添加 1k8 欧姆的上拉电阻。这些电阻已经存在于 GPIO 连接器上的 I²C 总线上。但是,一些 I²C 模块,包括 PCF8591 模块,已经安装了自己的电阻,因此可以在没有额外电阻的情况下工作。
I2C 总线和电平转换
I²C 总线由两根线组成,一根数据线(SDA)和一根时钟线(SCL)。两根线都通过上拉电阻被被动地拉到 VCC(在树莓派上,这是 3.3V)。树莓派将通过每个周期将时钟线拉低来控制时钟,数据线可以被树莓派拉低以发送命令,或者被连接的设备拉低以回应数据:
树莓派 I²C 引脚包括 SDA 和 SCL 上的上拉电阻
由于从机设备只能将数据线拉到GND,因此设备可以由 3.3V 甚至 5V 供电,而不会有驱动 GPIO 引脚电压过高的风险(请记住,树莓派 GPIO 无法处理超过 3.3V 的电压)。只要设备的 I²C 总线能够识别逻辑最大值为 3.3V 而不是 5V,这应该可以工作。I²C 设备不能安装自己的上拉电阻,因为这会导致 GPIO 引脚被拉到 I²C 设备的供电电压。
请注意,本章中使用的 PCF8591 模块已安装了电阻;因此,我们只能使用VCC = 3V3。双向逻辑电平转换器可用于克服逻辑电平的任何问题。其中一种设备是Adafruit I²C 双向逻辑电平转换模块,如下图所示:
Adafruit I²C 双向逻辑电平转换模块
除了确保任何逻辑电压适合您使用的设备之外,它还将允许总线在更长的导线上延伸(电平转换器还将充当总线中继)。
仅使用 PCF8591 芯片或添加替代传感器
下图显示了 PCF8591 模块不带传感器的电路图:
PCF8591 模块的电路图,不带传感器附件
如您所见,除了传感器外,只有五个额外的元件。我们有一个电源滤波电容(C1)和一个带有限流电阻(R5)的电源指示 LED(D2),所有这些都是可选的。
请注意,该模块包括两个 10K 上拉电阻(R8 和 R9)用于 SCL 和 SDA 信号。但是,由于树莓派上的 GPIO I²C 连接也包括上拉电阻,因此模块上不需要这些电阻(并且可以被移除)。这也意味着我们应该只将该模块连接到 VCC = 3.3V(如果我们使用 5V,则 SCL 和 SDA 上的电压将约为 3.56V,这对于树莓派的 GPIO 引脚来说太高)。
PCF891 模块上的传感器都是电阻性的,因此模拟输入上的电压电平将随着传感器电阻的变化在 GND 和 VCC 之间变化:
电位分压电路。这提供了与传感器电阻成比例的电压。
该模块使用一种称为电位分压器的电路。顶部的电阻平衡了底部传感器提供的电阻,以提供介于VCC和GND之间的电压。
电位器的输出电压(V[out])可以计算如下:
R[t]和 R[b]分别是顶部和底部的电阻值,VCC 是供电电压。
模块中的电位器具有 10K 欧姆的电阻,根据调节器的位置在顶部和底部之间分割。因此,在中间,我们在每一侧都有 5K 欧姆和输出电压为 1.65V;四分之一的位置(顺时针),我们有 2.5K 欧姆和 7.5K 欧姆,产生 0.825V。
我没有显示 AOUT 电路,它是一个电阻和 LED。但是,正如您将发现的,LED 不适合指示模拟输出(除了显示开/关状态)。
对于更敏感的电路,您可以使用更复杂的电路,例如惠斯通电桥(它允许检测电阻的微小变化),或者您可以使用专用传感器,根据其读数输出模拟电压(例如TMP36温度传感器)。PCF891 还支持差分输入模式,其中一个通道的输入可以与另一个通道的输入进行比较(结果读数将是两者之间的差异)。
有关 PCF8591 芯片的更多信息,请参阅www.nxp.com/documents/data_sheet/PCF8591.pdf
上的数据表。
使用模拟数字转换器读取模拟数据
在命令行中使用的 I²C 工具(在上一节中使用)对于调试 I²C 设备非常有用,但对于 Python 来说并不实用,因为它们会很慢并且需要大量的开销。幸运的是,有几个 Python 库提供了 I²C 支持,允许有效地使用 I²C 与连接的设备进行通信并提供简单的操作。
我们将使用这样的库来创建我们自己的 Python 模块,它将允许我们快速轻松地从 ADC 设备获取数据并在我们的程序中使用它。该模块设计得非常灵活,可以在不影响其余示例的情况下放置其他硬件或数据源。
准备工作
要使用 Python 3 使用 I²C 总线,我们将使用Gordon Henderson 的 WiringPi2(有关更多详细信息,请参见wiringpi.com/
)。
安装wiringpi2
的最简单方法是使用 Python 3 的pip
。pip
是 Python 的软件包管理器,其工作方式类似于apt-get
。您希望安装的任何软件包都将从在线存储库自动下载并安装。
要安装pip
,请使用以下命令:
sudo apt-get install python3-dev python3-pip
然后,使用以下命令安装wiringpi2
:
sudo pip-3.2 install wiringpi2
安装完成后,您应该看到以下内容,表示成功:
成功安装 WiringPi2
您需要将 PCF8591 模块连接到树莓派的 I²C 连接上,就像之前使用的那样:
PCF8591 模块和引脚连接到树莓派 GPIO 连接器
如何做…
在下一节中,我们将编写一个脚本,以便我们可以收集数据,然后稍后在本章中使用。
创建以下脚本data_adc.py
,如下所示:
- 首先,导入我们将使用的模块并创建变量,如下所示:
#!/usr/bin/env python3 #data_adc.py import wiringpi2 import time DEBUG=False LIGHT=0;TEMP=1;EXT=2;POT=3 ADC_CH=[LIGHT,TEMP,EXT,POT] ADC_ADR=0x48 ADC_CYCLE=0x04 BUS_GAP=0.25 DATANAME=["0:Light","1:Temperature", "2:External","3:Potentiometer"]
- 创建
device
类并使用构造函数进行初始化,如下所示:
class device: # Constructor: def __init__(self,addr=ADC_ADR): self.NAME = DATANAME self.i2c = wiringpi2.I2C() self.devADC=self.i2c.setup(addr) pwrup = self.i2c.read(self.devADC) #flush powerup value if DEBUG==True and pwrup!=-1: print("ADC Ready") self.i2c.read(self.devADC) #flush first value time.sleep(BUS_GAP) self.i2c.write(self.devADC,ADC_CYCLE) time.sleep(BUS_GAP) self.i2c.read(self.devADC) #flush first value
- 在类中,定义一个函数以提供通道名称列表,如下所示:
def getName(self): return self.NAME
- 定义另一个函数(仍然作为类的一部分)以返回 ADC 通道的新样本集,如下所示:
def getNew(self): data=[] for ch in ADC_CH: time.sleep(BUS_GAP) data.append(self.i2c.read(self.devADC)) return data
- 最后,在设备类之后,创建一个测试函数来测试我们的新
device
类,如下所示。这只能在直接执行脚本时运行:
def main(): ADC = device(ADC_ADR) print (str(ADC.getName())) for i in range(10): dataValues = ADC.getNew() print (str(dataValues)) time.sleep(1) if __name__=='__main__': main() #End
您可以使用以下命令运行此模块的测试函数:
sudo python3 data_adc.py
工作原理…
我们首先导入wiringpi2
,以便稍后可以与我们的 I²C 设备通信。我们将创建一个类来包含控制 ADC 所需的功能。创建类时,我们可以初始化wiringpi2
,使其准备好使用 I²C 总线(使用wiringpi2.I2C()
),并使用芯片的总线地址设置一个通用 I²C 设备(使用self.i2c.setup(0x48)
)。
wiringpi2
还有一个专用类,可与 PCF8591 芯片一起使用;但是,在这种情况下,更有用的是使用标准 I²C 功能来说明如何使用wiringpi2
控制任何 I²C 设备。通过参考设备数据表,您可以使用类似的命令与任何连接的 I²C 设备进行通信(无论是否直接支持)。
与以前一样,我们执行设备读取并配置 ADC 以循环通过通道,但是我们使用wiringpi2
的I2C
对象的read
和write
函数,而不是i2cget
和i2cset
。初始化后,设备将准备好读取每个通道上的模拟信号。
该类还将有两个成员函数。第一个函数getName()
返回一个通道名称列表(我们可以用它来将数据与其来源进行关联),第二个函数getNew()
返回所有通道的新数据集。数据是使用i2c.read()
函数从 ADC 读取的,由于我们已经将其放入循环模式,每次读取都将来自下一个通道。
由于我们计划稍后重用此类,因此我们将使用if __name__
测试来允许我们定义在直接执行文件时要运行的代码。在我们的main()
函数中,我们创建 ADC,这是我们新设备类的一个实例。如果需要,我们可以选择选择非默认地址;否则,将使用芯片的默认地址。我们使用getName()
函数打印出通道的名称,然后我们可以从ADC
(使用getNew()
)收集数据并显示它们。
还有更多…
以下允许我们在data_adc.py
中定义设备类的另一个版本,以便可以在 ADC 模块的位置使用它。这将允许在本章的其余部分中尝试而无需任何特定的硬件。
无硬件收集模拟数据
如果您没有可用的 ADC 模块,则可以从树莓派内部获得大量可用数据,可以代替使用。
创建data_local.py
脚本如下:
#!/usr/bin/env python3 #data_local.py import subprocess from random import randint import time MEM_TOTAL=0 MEM_USED=1 MEM_FREE=2 MEM_OFFSET=7 DRIVE_USED=0 DRIVE_FREE=1 DRIVE_OFFSET=9 DEBUG=False DATANAME=["CPU_Load","System_Temp","CPU_Frequency", "Random","RAM_Total","RAM_Used","RAM_Free", "Drive_Used","Drive_Free"] def read_loadavg(): # function to read 1 minute load average from system uptime value = subprocess.check_output( ["awk '{print $1}' /proc/loadavg"], shell=True) return float(value) def read_systemp(): # function to read current system temperature value = subprocess.check_output( ["cat /sys/class/thermal/thermal_zone0/temp"], shell=True) return int(value) def read_cpu(): # function to read current clock frequency value = subprocess.check_output( ["cat /sys/devices/system/cpu/cpu0/cpufreq/"+ "scaling_cur_freq"], shell=True) return int(value) def read_rnd(): return randint(0,255) def read_mem(): # function to read RAM info value = subprocess.check_output(["free"], shell=True) memory=[] for val in value.split()[MEM_TOTAL+ MEM_OFFSET:MEM_FREE+ MEM_OFFSET+1]: memory.append(int(val)) return(memory) def read_drive(): # function to read drive info value = subprocess.check_output(["df"], shell=True) memory=[] for val in value.split()[DRIVE_USED+ DRIVE_OFFSET:DRIVE_FREE+ DRIVE_OFFSET+1]: memory.append(int(val)) return(memory) class device: # Constructor: def __init__(self,addr=0): self.NAME=DATANAME def getName(self): return self.NAME def getNew(self): data=[] data.append(read_loadavg()) data.append(read_systemp()) data.append(read_cpu()) data.append(read_rnd()) memory_ram = read_mem() data.append(memory_ram[MEM_TOTAL]) data.append(memory_ram[MEM_USED]) data.append(memory_ram[MEM_FREE]) memory_drive = read_drive() data.append(memory_drive[DRIVE_USED]) data.append(memory_drive[DRIVE_FREE]) return data def main(): LOCAL = device() print (str(LOCAL.getName())) for i in range(10): dataValues = LOCAL.getNew() print (str(dataValues)) time.sleep(1) if __name__=='__main__': main() #End
前面的脚本允许我们使用以下命令从树莓派中收集系统信息(subprocess
模块允许我们捕获结果并处理它们):
- CPU 速度:
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
- CPU 负载:
awk '{print $1}' /proc/loadavg
- 核心温度(乘以 1,000):
cat /sys/class/thermal/thermal_zone0/temp
- 驱动器信息:
df
- RAM 信息:
free
每个数据项都是使用其中一个函数进行采样的。在驱动和 RAM 信息的情况下,我们将响应拆分为一个列表(由空格分隔),并选择我们想要监视的项目(如可用内存和已用驱动器空间)。
这一切都打包成与data_adc.py
文件和device
类相同的方式运行(因此您可以选择在以下示例中使用data_adc
包括或data_local
包括,只需将data_adc
包括替换为data_local
)。
Python 物联网入门指南(三)(4)https://developer.aliyun.com/article/1507202