Python 物联网入门指南(四)(2)https://developer.aliyun.com/article/1507228
GPIO 回调函数
让我们回顾一下在 GPIO 示例中使用函数的一些用途。函数可以用来处理与树莓派的 GPIO 引脚相关的特定事件。例如,gpiozero
库提供了在按钮按下或释放时调用函数的能力:
from gpiozero import Button def button_pressed(): print("button pressed") def button_released(): print("button released") #button is interfaced to GPIO 2 button = Button(2) button.when_pressed = button_pressed button.when_released = button_released while True: pass
在这个例子中,我们使用库的 GPIO 类的when_pressed
和when_released
属性。当按钮被按下时,执行函数button_pressed
。同样,当按钮被释放时,执行函数button_released
。我们使用while
循环来避免退出程序并继续监听按钮事件。使用pass
关键字来避免错误,当执行pass
关键字时什么也不会发生。
能够为不同事件执行不同函数的能力在家庭自动化等应用中非常有用。例如,可以用来在天黑时打开灯,反之亦然。
Python 中的直流电机控制
在本节中,我们将讨论使用树莓派 Zero 进行电机控制。为什么要讨论电机控制?随着我们在本书中不同主题的进展,我们将最终构建一个移动机器人。因此,我们需要讨论使用 Python 编写代码来控制树莓派上的电机。
为了控制电机,我们需要一个H 桥电机驱动器(讨论 H 桥超出了我们的范围。有几种资源可供 H 桥电机驱动器使用:www.mcmanis.com/chuck/robotics/tutorial/h-bridge/
)。有几种专为树莓派设计的电机驱动器套件。在本节中,我们将使用以下套件:www.pololu.com/product/2753
。
Pololu产品页面还提供了如何连接电机的说明。让我们开始编写一些 Python 代码来操作电机:
from gpiozero import Motor from gpiozero import OutputDevice import time motor_1_direction = OutputDevice(13) motor_2_direction = OutputDevice(12) motor = Motor(5, 6) motor_1_direction.on() motor_2_direction.on() motor.forward() time.sleep(10) motor.stop() motor_1_direction.off() motor_2_direction.off()
树莓派基于电机控制
为了控制电机,让我们声明引脚、电机的速度引脚和方向引脚。根据电机驱动器的文档,电机分别由 GPIO 引脚 12、13 和 5、6 控制。
from gpiozero import Motor from gpiozero import OutputDevice import time motor_1_direction = OutputDevice(13) motor_2_direction = OutputDevice(12) motor = Motor(5, 6)
控制电机就像使用on()
方法打开电机,使用forward()
方法向前移动电机一样简单:
motor.forward()
同样,通过调用reverse()
方法可以改变电机方向。通过以下方式可以停止电机:
motor.stop()
读者的一些迷你项目挑战
以下是一些迷你项目挑战给我们的读者:
- 在本章中,我们讨论了树莓派的输入接口和电机控制。想象一个项目,我们可以驱动一个移动机器人,该机器人从触须开关读取输入并操作移动机器人。结合限位开关和电机,是否可能构建一个沿墙行驶的机器人?
- 在本章中,我们讨论了如何控制直流电机。我们如何使用树莓派控制步进电机?
- 如何使用树莓派 Zero 接口运动传感器来控制家里的灯?
总结
在本章中,我们讨论了条件语句以及条件语句在 Python 中的应用。我们还讨论了 Python 中的函数,将参数传递给函数,从函数返回值以及 Python 程序中变量的作用域。我们讨论了回调函数和 Python 中的电机控制。
第十二章:通信接口
到目前为止,我们已经讨论了 Python 中的循环、条件语句和函数。我们还讨论了与树莓派接口的输出设备和简单的数字输入设备。
在本章中,我们将讨论以下通信接口:
- UART - 串行端口
- 串行外围接口
- I²C 接口
我们将使用不同的传感器/电子元件来演示在 Python 中编写这些接口的代码。我们留给您选择一个您喜欢的组件来探索这些通信接口。
UART - 串行端口
通用异步收发器(UART),即串行端口,是一种通信接口,数据以位的形式从传感器串行传输到主机计算机。使用串行端口是最古老的通信协议之一。它用于数据记录,微控制器从传感器收集数据并通过串行端口传输数据。还有一些传感器以串行通信的形式响应传入的命令传输数据。
我们不会深入讨论串行端口通信的理论(网络上有大量理论可供参考,网址为en.wikipedia.org/wiki/Universal_asynchronous_receiver/transmitter
)。我们将讨论使用串行端口与树莓派接口不同的传感器。
树莓派 Zero 的 UART 端口
通常,UART 端口由接收器(Rx)和发送器(Tx)引脚组成,用于接收和发送数据。树莓派的 GPIO 引脚带有 UART 端口。 GPIO 引脚 14(Tx引脚)和 15(Rx引脚)用作树莓派的 UART 端口:
GPIO 引脚 14 和 15 是 UART 引脚(图片来源:https://www.rs-online.com/designspark/introducing-the-raspberry-pi-b-plus)
设置树莓派 Zero 串行端口
为了使用串行端口与传感器通信,串行端口登录/控制台需要被禁用。在Raspbian操作系统镜像中,默认情况下启用此功能,因为它可以方便调试。
串行端口登录可以通过raspi-config
禁用:
- 启动终端并运行此命令:
sudo raspi-config
- 从
raspi-config
的主菜单中选择高级选项:
从 raspi-config 菜单中选择高级选项
- 从下拉菜单中选择 A8 串行选项:
从下拉菜单中选择 A8 串行
- 禁用串行登录:
禁用串行登录
- 完成配置并在最后重新启动:
保存配置并重新启动
示例 1 - 将二氧化碳传感器与树莓派连接
我们将使用 K30 二氧化碳传感器(其文档可在此处找到,co2meters.com/Documentation/Datasheets/DS30-01%20-%20K30.pdf
)。它的范围是 0-10,000 ppm,传感器通过串行端口以响应来自树莓派的特定命令提供二氧化碳浓度读数。
以下图显示了树莓派和 K30 二氧化碳传感器之间的连接:
与树莓派连接的 K30 二氧化碳传感器
传感器的接收器(Rx)引脚连接到树莓派 Zero 的发送器(Tx-GPIO 14(UART_TXD))引脚(前图中的黄色线)。传感器的发送器(Tx)引脚连接到树莓派 Zero 的接收器(Rx-GPIO 15(UART_RXD))引脚(前图中的绿色线)。
为了给传感器供电,传感器的 G+引脚(前图中的红线)连接到树莓派 Zero 的5V引脚。传感器的 G0 引脚连接到树莓派 Zero 的GND引脚(前图中的黑线)。
通常,串行端口通信是通过指定波特率、帧中的位数、停止位和流控来初始化的。
用于串行端口通信的 Python 代码
我们将使用pySerial库(pyserial.readthedocs.io/en/latest/shortintro.html#opening-serial-ports
)来接口二氧化碳传感器:
- 根据传感器的文档,可以通过以波特率 9600、无奇偶校验、8 位和 1 个停止位初始化串行端口来读取传感器输出。 GPIO 串行端口为
ttyAMA0
。与传感器进行接口的第一步是初始化串行端口通信:
import serial ser = serial.Serial("/dev/ttyAMA0")
- 根据传感器文档(
co2meters.com/Documentation/Other/SenseAirCommGuide.zip
),传感器对二氧化碳浓度的以下命令做出响应:
从传感器数据表中借用的读取二氧化碳浓度的命令
- 命令可以如下传输到传感器:
ser.write(bytearray([0xFE, 0x44, 0x00, 0x08, 0x02, 0x9F, 0x25]))
- 传感器以 7 个字节的响应做出响应,可以如下读取:
resp = ser.read(7)
- 传感器的响应格式如下:
二氧化碳传感器响应
- 根据数据表,传感器数据大小为 2 个字节。每个字节可用于存储 0 和 255 的值。两个字节可用于存储高达 65,535 的值(255 * 255)。二氧化碳浓度可以根据消息计算如下:
high = resp[3] low = resp[4] co2 = (high*256) + low
- 把它全部放在一起:
import serial import time import array ser = serial.Serial("/dev/ttyAMA0") print("Serial Connected!") ser.flushInput() time.sleep(1) while True: ser.write(bytearray([0xFE, 0x44, 0x00, 0x08, 0x02, 0x9F, 0x25])) # wait for sensor to respond time.sleep(.01) resp = ser.read(7) high = resp[3] low = resp[4] co2 = (high*256) + low print() print() print("Co2 = " + str(co2)) time.sleep(1)
- 将代码保存到文件并尝试执行它。
I2C 通信
I²C(Inter-Integrated Circuit)通信是一种串行通信类型,允许将多个传感器接口到计算机。 I²C 通信由时钟和数据线两根线组成。树莓派 Zero 的 I²C 通信的时钟和数据引脚分别为GPIO 3(SCL)和GPIO 2(SDA)。为了在同一总线上与多个传感器通信,通常通过 I²C 协议通信的传感器/执行器通常通过它们的 7 位地址进行寻址。可以有两个或更多树莓派板与同一 I²C 总线上的同一传感器进行通信。这使得可以在树莓派周围构建传感器网络。
I²C 通信线是开漏线路;因此,它们使用电阻上拉,如下图所示:
I²C 设置
让我们通过一个示例来回顾一下 I²C 通信。
示例 2 - PiGlow
PiGlow是树莓派的一个附加硬件,由 18 个 LED 与SN3218芯片接口。该芯片允许通过 I²C 接口控制 LED。芯片的 7 位地址为0x54
。
为了接口附加硬件,SCL引脚连接到GPIO 3,SDA引脚连接到GPIO 2;地线引脚和电源引脚分别连接到附加硬件的对应引脚。
PiGlow 附带了一个抽象 I²C 通信的库:github.com/pimoroni/piglow
。
尽管该库是对 I²C 接口的封装,但我们建议阅读代码以了解操作 LED 的内部机制:
PiGlow 叠放在 Raspberry Pi 上
安装库
PiGlow 库可以通过从命令行终端运行以下命令来安装:
curl get.pimoroni.com/piglow | bash
示例
安装完成后,切换到示例文件夹(/home/pi/Pimoroni/piglow
)并运行其中一个示例:
python3 bar.py
它应该运行闪烁灯效果,如下图所示:
PiGlow 上的闪烁灯
同样,还有库可以使用 I²C 通信与实时时钟、LCD 显示器等进行通信。如果你有兴趣编写自己的接口,提供 I²C 通信与传感器/输出设备的细节,请查看本书附带网站上的一些示例。
示例 3 - 用于树莓派的 Sensorian 附加硬件
Sensorian是为树莓派设计的附加硬件。这个附加硬件配备了不同类型的传感器,包括光传感器、气压计、加速度计、LCD 显示器接口、闪存存储器、电容触摸传感器和实时时钟。
这个附加硬件上的传感器足以学习本章讨论的所有通信接口的使用方法:
堆叠在树莓派 Zero 上的 Sensorian 硬件
在本节中,我们将讨论一个示例,我们将使用 I²C 接口通过树莓派 Zero 测量环境光水平。附加硬件板上的传感器是APDS-9300传感器(www.avagotech.com/docs/AV02-1077EN)。
用于光传感器的 I2C 驱动程序
传感器硬件的驱动程序可从 GitHub 存储库中获取(github.com/sensorian/sensorian-firmware.git
)。让我们从命令行终端克隆存储库:
git clone https://github.com/sensorian/sensorian-firmware.git
让我们使用驱动程序(位于 ~/sensorian-firmware/Drivers_Python/APDS-9300
文件夹中)从传感器的两个 ADC 通道读取值:
import time import APDS9300 as LuxSens import sys AmbientLight = LuxSens.APDS9300() while True: time.sleep(1) channel1 = AmbientLight.readChannel(1) channel2 = AmbientLight.readChannel(0) Lux = AmbientLight.getLuxLevel(channel1,channel2) print("Lux output: %d." % Lux)
有了两个通道的 ADC 值,驱动程序可以使用以下公式(从传感器数据表中检索)计算环境光值:
使用 ADC 值计算的环境光水平
这个计算是由属性getLuxLevel
执行的。在正常照明条件下,环境光水平(以勒克斯为单位)约为2
。当我们用手掌遮住光传感器时,测得的输出为0
。这个传感器可以用来测量环境光,并相应地调整房间照明。
挑战
我们讨论了使用光传感器测量环境光水平。我们如何利用光输出(环境光水平)来控制房间照明?
SPI 接口
还有一种名为串行外围接口(SPI)的串行通信接口。必须通过raspi-config
启用此接口(这类似于在本章前面启用串行端口接口)。使用 SPI 接口类似于 I²C 接口和串行端口。
通常,SPI 接口由时钟线、数据输入、数据输出和从机选择(SS)线组成。与 I²C 通信不同(在那里我们可以连接多个主机),在同一总线上可以有一个主机(树莓派 Zero),但可以有多个从机。SS引脚用于选择树莓派 Zero 正在读取/写入数据的特定传感器,当同一总线上连接了多个传感器时。
示例 4 - 写入外部存储器芯片
让我们查看一个示例,我们将通过 SPI 接口向 Sensorian 附加硬件上的闪存存储器写入数据。SPI 接口和存储器芯片的驱动程序可从同一 GitHub 存储库中获取。
由于我们已经下载了驱动程序,让我们查看一下驱动程序中提供的示例:
import sys import time import S25FL204K as Memory
让我们初始化并将消息hello
写入存储器:
Flash_memory = Memory.S25FL204K() Flash_memory.writeStatusRegister(0x00) message = "hello" flash_memory.writeArray(0x000000,list(message), message.len())
现在,让我们尝试读取刚刚写入外部存储器的数据:
data = flash_memory.readArray(0x000000, message.len()) print("Data Read from memory: ") print(''.join(data))
本章提供了代码示例,可通过下载获得(memory_test.py
)。
我们成功地演示了使用 SPI 读/写外部存储器芯片。
向读者提出挑战
在这里的图中,有一个 LED 灯带(www.adafruit.com/product/306
)与树莓派附加硬件的 SPI 接口相连,使用了 Adafruit Cobbler(www.adafruit.com/product/914
)。我们提供了一个线索,说明如何将 LED 灯带与树莓派 Zero 相连。我们希望看到您能否自己找到将 LED 灯带与树莓派 Zero 相连的解决方案。请参考本书网站获取答案。
LED 灯带与树莓派 Zero 的 Adafruit Cobbler 接口
总结
在本章中,我们讨论了树莓派 Zero 上可用的不同通信接口。这些接口包括 I²C、SPI 和 UART。我们将在我们的最终项目中使用这些接口。我们使用了二氧化碳传感器、LED 驱动器和传感器平台来讨论这些接口。在下一章中,我们将讨论面向对象编程及其独特的优势。我们将通过一个例子讨论面向对象编程的必要性。面向对象编程在您需要编写自己的驱动程序来控制机器人的组件或编写传感器的接口库的情况下尤其有帮助。
第十三章:Python 中的数据类型和面向对象编程
在本章中,我们将讨论 Python 中的数据类型和面向对象编程(OOP)。我们将讨论 Python 中的列表、字典、元组和集合等数据类型。我们还将讨论 OOP,它的必要性以及如何在树莓派基于项目中编写面向对象的代码(例如,使用 OOP 来控制家用电器)。我们将讨论在树莓派 Zero 项目中使用 OOP。
列表
在 Python 中,列表是一种数据类型(其文档在此处可用,docs.python.org/3.4/tutorial/datastructures.html#
),可用于按顺序存储元素。
本章讨论的主题如果不在实践中使用很难理解。任何使用此符号表示的示例:>>>
都可以使用 Python 解释器进行测试。
列表可以包含字符串、对象(在本章中详细讨论)或数字等。例如,以下是列表的示例:
>>> sequence = [1, 2, 3, 4, 5, 6] >>> example_list = ['apple', 'orange', 1.0, 2.0, 3]
在前面的一系列示例中,sequence
列表包含介于1
和6
之间的数字,而example_list
列表包含字符串、整数和浮点数的组合。列表用方括号([]
)表示。项目可以用逗号分隔添加到列表中:
>>> type(sequence) <class 'list'>
由于列表是有序元素的序列,可以通过使用for
循环遍历列表元素来获取列表的元素,如下所示:
for item in sequence: print("The number is ", item)
输出如下:
The number is 1 The number is 2 The number is 3 The number is 4 The number is 5 The number is 6
由于 Python 的循环可以遍历一系列元素,它会获取每个元素并将其赋值给item
。然后将该项打印到控制台上。
可以在列表上执行的操作
在 Python 中,可以使用dir()
方法检索数据类型的属性。例如,可以检索sequence
列表的可用属性如下:
>>> dir(sequence) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
这些属性使得可以在列表上执行不同的操作。让我们详细讨论每个属性。
向列表添加元素:
可以使用append()
方法添加元素:
>>> sequence.append(7) >>> sequence [1, 2, 3, 4, 5, 6, 7]
从列表中删除元素:
remove()
方法找到元素的第一个实例(传递一个参数)并将其从列表中删除。让我们考虑以下示例:
- 示例 1:
>>> sequence = [1, 1, 2, 3, 4, 7, 5, 6, 7] >>> sequence.remove(7) >>> sequence [1, 1, 2, 3, 4, 5, 6, 7]
- 示例 2:
>>> sequence.remove(1) >>> sequence [1, 2, 3, 4, 5, 6, 7]
- 示例 3:
>>> sequence.remove(1) >>> sequence [2, 3, 4, 5, 6, 7]
检索元素的索引
index()
方法返回列表中元素的位置:
>>> index_list = [1, 2, 3, 4, 5, 6, 7] >>> index_list.index(5) 4
在这个例子中,该方法返回元素5
的索引。由于 Python 使用从 0 开始的索引,因此元素5
的索引为4
:
random_list = [2, 2, 4, 5, 5, 5, 6, 7, 7, 8] >>> random_list.index(5) 3
在这个例子中,该方法返回元素的第一个实例的位置。元素5
位于第三个位置。
Python 物联网入门指南(四)(4)https://developer.aliyun.com/article/1507230