Python 物联网入门指南(三)(3)

本文涉及的产品
数据可视化DataV,5个大屏 1个月
可视分析地图(DataV-Atlas),3 个项目,100M 存储空间
简介: Python 物联网入门指南(三)

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文件,我们将在如何操作…部分进一步扩展它。

如何操作…

  1. 创建一个名为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 
  1. 您可以通过直接运行以下命令来测试脚本:
sudo python3 tilt.py
  1. 将以下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) 
  1. 现在,我们将创建以下文件,名为rgbledmessage.py,以执行显示我们的消息所需的操作。首先,我们将导入所使用的模块:更新的rgbled模块,新的tilt模块和 Python os模块。最初,我们将DEBUG设置为True,这样 Python 终端在脚本运行时将显示额外的信息:
#!/usr/bin/python3 
# rgbledmessage.py 
import rgbled as RGBLED 
import tilt as TILT 
import os 
DEBUG = True 
  1. 添加一个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
  1. 添加一个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 
  1. 接下来,我们定义一个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) 
  1. 最后,我们创建一个main()函数来执行所需的每个步骤:
  2. 设置硬件组件(RGB LED 和倾斜开关)。
  3. 阅读letters.txt文件。
  4. 定义 LED 字母模式的字典。
  5. 生成一个缓冲区来表示所需的消息。
  6. 使用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 
  1. 创建以下文件,名为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 设备。

操作步骤…

  1. i2cdetect命令用于检测 I²C 设备(--y选项跳过有关可能干扰连接到 I²C 总线的其他硬件的警告)。以下命令用于扫描两个总线:
sudo i2cdetect -y 0
sudo i2cdetect -y 1 
  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
  1. 以下屏幕截图显示了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-bcm2708i2c_dev
  1. 使用检测到的总线号(01)和设备地址(0x48),使用i2cget从设备读取(上电或通道更改后,您需要两次读取设备才能看到最新值),如下所示:
sudo i2cget -y 1 0x48
sudo i2cget -y 1 0x48 
  1. 要从通道1读取(这是模块上的温度传感器),我们可以使用i2cset0x01写入 PCF8591 控制寄存器。同样,使用两次读取来从通道1获取新样本,如下所示:
sudo i2cset -y 1 0x48 0x01
sudo i2cget -y 1 0x48
sudo i2cget -y 1 0x48
  1. 要循环遍历每个输入通道,请使用i2cset将控制寄存器设置为0x04,如下所示:
sudo i2cset -y 1 0x48 0x04
  1. 我们还可以使用以下命令控制 AOUT 引脚,将其完全打开(点亮 LED D1):
sudo i2cset -y 1 0x48 0x40 0xff 
  1. 最后,我们可以使用以下命令将其完全关闭(关闭 LED D1):
sudo i2cset -y 1 0x48 0x40 0x00  

工作原理…

设备上电后的第一次读取将返回0x80,并且还将触发通道 0 的新样本。如果再次读取,它将返回先前读取的样本并生成新样本。每次读取都将是一个 8 位值(范围从0255),表示电压到 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 之间变化:

电位分压电路。这提供了与传感器电阻成比例的电压。

该模块使用一种称为电位分压器的电路。顶部的电阻平衡了底部传感器提供的电阻,以提供介于VCCGND之间的电压。

电位器的输出电压(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 的pippip是 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,如下所示:

  1. 首先,导入我们将使用的模块并创建变量,如下所示:
#!/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"] 
  1. 创建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 
  1. 在类中,定义一个函数以提供通道名称列表,如下所示:
def getName(self): 
  return self.NAME
  1. 定义另一个函数(仍然作为类的一部分)以返回 ADC 通道的新样本集,如下所示:
def getNew(self): 
  data=[] 
  for ch in ADC_CH: 
    time.sleep(BUS_GAP) 
    data.append(self.i2c.read(self.devADC)) 
  return data 
  1. 最后,在设备类之后,创建一个测试函数来测试我们的新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 以循环通过通道,但是我们使用wiringpi2I2C对象的readwrite函数,而不是i2cgeti2cset。初始化后,设备将准备好读取每个通道上的模拟信号。

该类还将有两个成员函数。第一个函数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

相关实践学习
钉钉群中如何接收IoT温控器数据告警通知
本实验主要介绍如何将温控器设备以MQTT协议接入IoT物联网平台,通过云产品流转到函数计算FC,调用钉钉群机器人API,实时推送温湿度消息到钉钉群。
阿里云AIoT物联网开发实战
本课程将由物联网专家带你熟悉阿里云AIoT物联网领域全套云产品,7天轻松搭建基于Arduino的端到端物联网场景应用。 开始学习前,请先开通下方两个云产品,让学习更流畅: IoT物联网平台:https://iot.console.aliyun.com/ LinkWAN物联网络管理平台:https://linkwan.console.aliyun.com/service-open
相关文章
|
12天前
|
安全 数据处理 开发者
Python中的多线程编程:从入门到精通
本文将深入探讨Python中的多线程编程,包括其基本原理、应用场景、实现方法以及常见问题和解决方案。通过本文的学习,读者将对Python多线程编程有一个全面的认识,能够在实际项目中灵活运用。
|
20天前
|
Python
【python从入门到精通】-- 第五战:函数大总结
【python从入门到精通】-- 第五战:函数大总结
56 0
|
7天前
|
数据采集 机器学习/深度学习 人工智能
Python编程入门:从基础到实战
【10月更文挑战第24天】本文将带你进入Python的世界,从最基础的语法开始,逐步深入到实际的项目应用。我们将一起探索Python的强大功能和灵活性,无论你是编程新手还是有经验的开发者,都能在这篇文章中找到有价值的内容。让我们一起开启Python的奇妙之旅吧!
|
9天前
|
数据采集 存储 数据库
Python中实现简单爬虫的入门指南
【10月更文挑战第22天】本文将带你进入Python爬虫的世界,从基础概念到实战操作,一步步指导你如何使用Python编写一个简单的网络爬虫。我们将不展示代码示例,而是通过详细的步骤描述和逻辑讲解,帮助你理解爬虫的工作原理和开发过程。无论你是编程新手还是有一定经验的开发者,这篇文章都将为你打开一扇通往数据收集新世界的大门。
|
7天前
|
测试技术 开发者 Python
探索Python中的装饰器:从入门到实践
【10月更文挑战第24天】 在Python的世界里,装饰器是一个既神秘又强大的工具。它们就像是程序的“隐形斗篷”,能在不改变原有代码结构的情况下,增加新的功能。本篇文章将带你走进装饰器的世界,从基础概念出发,通过实际例子,逐步深入到装饰器的高级应用,让你的代码更加优雅和高效。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开一扇通往高效编程的大门。
|
9天前
|
存储 人工智能 数据挖掘
Python编程入门:构建你的第一个程序
【10月更文挑战第22天】编程,这个听起来高深莫测的词汇,实际上就像搭积木一样简单有趣。本文将带你走进Python的世界,用最浅显的语言和实例,让你轻松掌握编写第一个Python程序的方法。无论你是编程新手还是希望了解Python的爱好者,这篇文章都将是你的理想起点。让我们一起开始这段奇妙的编程之旅吧!
14 3
|
8天前
|
机器学习/深度学习 人工智能 算法
机器学习基础:使用Python和Scikit-learn入门
机器学习基础:使用Python和Scikit-learn入门
20 1
|
10天前
|
存储 程序员 开发者
Python编程入门:从零开始掌握基础语法
【10月更文挑战第21天】本文将带你走进Python的世界,通过浅显易懂的语言和实例,让你快速了解并掌握Python的基础语法。无论你是编程新手还是想学习一门新的编程语言,这篇文章都将是你的不二之选。我们将一起探索变量、数据类型、运算符、控制结构、函数等基本概念,并通过实际代码示例加深理解。准备好了吗?让我们开始吧!
|
14天前
|
存储 算法 Python
【10月更文挑战第16天】「Mac上学Python 27」小学奥数篇13 - 动态规划入门
本篇将通过 Python 和 Cangjie 双语介绍动态规划的基本概念,并解决一个经典问题:斐波那契数列。学生将学习如何使用动态规划优化递归计算,并掌握编程中的重要算法思想。
77 3
|
16天前
|
设计模式 开发者 Python
Python编程中的设计模式:从入门到精通####
【10月更文挑战第14天】 本文旨在为Python开发者提供一个关于设计模式的全面指南,通过深入浅出的方式解析常见的设计模式,帮助读者在实际项目中灵活运用这些模式以提升代码质量和可维护性。文章首先概述了设计模式的基本概念和重要性,接着逐一介绍了几种常用的设计模式,并通过具体的Python代码示例展示了它们的实际应用。无论您是Python初学者还是经验丰富的开发者,都能从本文中获得有价值的见解和实用的技巧。 ####