原文:
zh.annas-archive.org/md5/729fe5cbaba4b57748ef3646477e357a译者:飞龙
第五章:处理数字输入、轮询和中断
在本章中,我们将使用数字输入,以便在处理 HTTP 请求的同时让用户与板交互。我们将:
-
理解上拉电阻和下拉电阻之间的区别,以便连接按钮
-
将按钮与数字输入引脚连接
-
使用轮询检查
mraa和wiring-x86库中的按钮状态 -
在运行 RESTful API 的同时结合轮询读取数字输入
-
编写代码,确保在提供电子组件和 API 的共享功能时保持一致性
-
使用中断和
mraa库检测按下的按钮 -
理解轮询和中断在检测数字输入变化之间的差异、优势和权衡
理解按钮和上拉电阻
我们使用 RESTful API 控制红、绿、蓝 LED 的亮度级别。然后,我们将三个 LED 替换为单个 RGB LED,并使用相同的 RESTful API 生成不同颜色的灯光。现在,我们希望用户能够通过面包板上添加的两个按钮来改变三个组件的亮度级别:
-
一个按钮用于关闭所有颜色,即设置所有颜色亮度级别为 0
-
一个按钮用于将所有颜色设置为最大亮度级别,即设置所有颜色亮度级别为 255
当用户按下按钮,也称为微动开关时,它就像一根电线,因此,它允许电流通过其融入的电路。当按钮未按下时,其融入的电路被中断。因此,每当用户释放按钮时,电路都会被中断。显然,我们不希望在用户按下按钮时短路连接,因此,我们将分析不同的可能方法来安全地将按钮连接到英特尔 Galileo Gen 2 板上。
以下图片显示了我们可以将按钮连接到英特尔 Galileo Gen 2 板的一种方法,并使用 GPIO 引脚号0作为输入以确定按钮是否被按下。该示例的 Fritzing 文件为iot_fritzing_chapter_05_01.fzz,以下图片是面包板视图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_05_01.jpg
以下图片显示了用符号表示的电子组件的电路图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_05_02.jpg
如前图所示,板子符号上标记为D0/RX的 GPIO 引脚连接到一个 120Ω电阻,公差为 5%(棕色红棕色金色),并连接到IOREF引脚。我们已经知道,标记为IOREF的引脚为我们提供 IOREF 电压,即在我们的实际配置中为 5V。由于我们可能希望在将来使用其他电压配置,我们始终可以使用 IOREF 引脚而不是专门使用5V或3V3引脚。板子符号上标记为D0/RX的 GPIO 引脚也连接到S1按钮,通过 120Ω电阻和GND(地)连接。
小贴士
该配置被称为分压器,120Ω电阻被称为上拉电阻。
拉高电阻在按下S1按钮时限制电流。由于拉高电阻的作用,如果我们按下S1按钮,我们将在标记为D0/RX的 GPIO 引脚上读取低值(0V)。当我们释放 S1 按钮时,我们将读取高值,即 IOREF 电压(在我们的实际配置中为 5V)。
由于我们在按钮按下时读取到低值,所以情况可能有些令人困惑。然而,我们可以编写面向对象的代码来封装按钮的行为,并使用更容易理解的状态来隔离上拉电阻的工作方式。
还可以使用下拉电阻。我们可以将 120Ω电阻连接到地,将其从上拉电阻转换为下拉电阻。以下图片显示了如何使用下拉电阻将按钮连接到英特尔 Galileo Gen 2 板,并使用 GPIO 引脚号0作为输入来确定按钮是否被按下。该示例的 Fritzing 文件为iot_fritzing_chapter_05_02.fzz,以下图片是面包板视图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_05_03.jpg
下图显示了用符号表示的电子元件的原理图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_05_04.jpg
如前图所示,在本例中,板子符号上标记为D0/RX的 GPIO 引脚连接到S1按钮和IOREF引脚。S1 按钮的另一个引脚连接到 120Ω电阻,该电阻连接到GND(地)。
小贴士
在此配置中,120Ω电阻被称为下拉电阻。
拉低电阻在按下S1按钮时限制电流。由于拉低电阻的作用,如果我们按下S1按钮,我们将在标记为D0/RX的 GPIO 引脚上读取高值,即 IOREF 电压(在我们的实际配置中为 5V)。当我们释放S1按钮时,我们将读取低值(0V)。因此,拉低电阻与我们在使用上拉电阻时读取的相反值一起工作。
使用按钮连接数字输入引脚
现在,我们将使用以下引脚连接两个按钮,并将使用上拉电阻:
-
引脚 1(标记为 D1/TX)用于连接关闭三种颜色的按钮
-
引脚 0(标记为 D0/RX)用于连接设置三种颜色到最大亮度级别的按钮
在完成必要的布线后,我们将编写 Python 代码来检查每个按钮是否被按下,同时保持我们的 RESTful API 正常工作。这样,我们将使用户能够通过按钮和 RESTful API 与 RGB LED 交互。为了使用此示例,我们需要以下额外的组件:
-
两个带两个引脚的按钮
-
两个 120Ω 电阻,公差为 5%(棕色 红色 棕色 金色)
以下图显示了连接到面包板上的组件、必要的布线和从英特尔 Galileo Gen 2 板到面包板的布线。示例的 Fritzing 文件是 iot_fritzing_chapter_05_03.fzz,以下图片是面包板视图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_05_05.jpg
以下图片显示了用符号表示的电子组件的原理图。
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_05_06.jpg
如前图所示,我们添加了两个按钮(S1 和 S2)和两个 120Ω 上拉电阻(R4 和 R5)。板符号中标记为 D0/RX 的 GPIO 引脚连接到 S2 按钮,R4 电阻是其上拉电阻。板符号中标记为 D1/TX 的 GPIO 引脚连接到 S1 按钮,R5 电阻是其上拉电阻。这样,当 S2 按钮被按下时,GPIO 引脚编号 0 将为低电平,当 S1 按钮被按下时,GPIO 引脚编号 1 将为低电平。S1 按钮位于面包板的左侧,而 S2 按钮位于右侧。
现在,是时候将组件插入面包板并完成所有必要的布线了。在添加或从板上的引脚移除任何电线之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从英特尔 Galileo Gen 2 板上拔掉电源。
使用数字输入和 mraa 库读取按钮状态
我们将创建一个新的 PushButton 类来表示连接到我们的板上的按钮,该按钮可以使用上拉或下拉电阻。以下行显示了与 mraa 库一起工作的新 PushButton 类的代码。示例的代码文件是 iot_python_chapter_05_01.py。
import mraa
import time
from datetime import date
class PushButton:
def __init__(self, pin, pull_up=True):
self.pin = pin
self.pull_up = pull_up
self.gpio = mraa.Gpio(pin)
self.gpio.dir(mraa.DIR_IN)
@property
def is_pressed(self):
push_button_status = self.gpio.read()
if self.pull_up:
# Pull-up resistor connected
return push_button_status == 0
else:
# Pull-down resistor connected
return push_button_status == 1
@property
def is_released(self):
return not self.is_pressed
在创建PushButton类的实例时,我们必须指定按钮连接的引脚号,作为pin必需参数。如果我们没有指定其他值,可选的pull_up参数将为True,实例将像按钮连接了上拉电阻一样工作。如果我们使用下拉电阻,我们必须在pull_up参数中传递False。构造函数,即__init__方法,使用接收到的pin作为其pin参数创建一个新的mraa.Gpio实例,将其引用保存到gpio属性中,并调用其dir方法将引脚配置为输入引脚(mraa.DIR_IN)。
该类定义了以下两个属性:
-
is_pressed:调用相关mraa.Gpio实例的read方法从引脚获取值并将其保存到push_button_status变量中。如果按钮连接了上拉电阻(self.pull_up为True),则代码将返回True,表示如果push_button_status中的值为0(低值),则按钮被按下。如果按钮连接了下拉电阻(self.pull_up为False),则代码将返回True,表示如果push_button_status中的值为1(高值),则按钮被按下。 -
is_released:返回is_pressed属性的相反结果。
现在,我们可以编写使用新的PushButton类创建每个按钮实例的代码,并轻松检查它们是否被按下。新类处理按钮是否连接了上拉或下拉电阻,因此我们只需检查is_pressed或is_released属性值,无需担心它们连接的具体细节。
我们将在稍后集成考虑两个按钮状态的代码到我们的 RESTful API 中。首先,我们将通过一个简单的示例将两个按钮隔离出来,以了解我们如何读取它们的状态。在这种情况下,我们将使用轮询,即一个循环,将检查按钮是否被按下。如果按钮被按下,我们希望代码在控制台输出中打印一条消息,指示正在按下的特定按钮。
以下行显示了执行先前解释操作的 Python 代码。示例的代码文件为iot_python_chapter_05_01.py。
if __name__ == "__main__":
s1_push_button = PushButton(1)
s2_push_button = PushButton(0)
while True:
# Check whether the S1 pushbutton is pressed
if s1_push_button.is_pressed:
print("You are pressing S1.")
# Check whether the S2 pushbutton is pressed
if s2_push_button.is_pressed:
print("You are pressing S2.")
# Sleep 500 milliseconds (0.5 seconds)
time.sleep(0.5)
前两行创建了之前编写的PushButton类的两个实例。S1按钮连接到 GPIO 引脚 1,S2按钮连接到 GPIO 引脚 0。在这两种情况下,代码没有为pull_up参数指定值。因此,构造函数,即__init__方法,将使用此参数的默认值True,并将实例配置为与上拉电阻连接的按钮。我们需要在创建两个实例时注意这一点,然后,我们使用包含实例的变量的名称:s1_push_button和s2_push_button。
然后,代码将无限循环运行,即直到你通过按下Ctrl + C或停止进程的按钮来中断执行,如果你使用具有远程开发功能的 Python IDE 来运行代码在你的板上。
while循环内的第一行检查名为s1_push_button的PushButton实例的is_pressed属性的值是否为True。True值表示此时按钮被按下,因此代码会在控制台输出中打印一条消息,表明 S1 按钮正在被按下。while循环内的后续行对名为s2_push_button的PushButton实例执行相同的程序。
在我们检查了两个按钮的状态之后,调用time.sleep函数,并将0.5作为第二个参数的值,将执行延迟 500 毫秒,即 0.5 秒。
以下行将启动示例;不要忘记你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux。
python iot_python_chapter_05_01.py
在运行示例之后,执行以下操作:
-
按下 S1 按钮 1 秒钟
-
按下 S2 按钮 1 秒钟
-
同时按下 S1 和 S2 按钮 1 秒钟
由于之前的操作,你将看到以下输出:
You are pressing S1.
You are pressing S2.
You are pressing S1.
You are pressing S2.
在这种情况下,我们正在使用轮询读取数字输入。mraa库还允许我们使用中断,并用 Python 声明中断处理程序。这样,每当用户按下按钮时,事件会生成中断,mraa库会调用指定的中断处理程序。如果你曾经从事基于事件的编程,你可以考虑事件和事件处理程序而不是中断和中断处理程序,这样你将很容易理解事情是如何工作的。
中断处理程序在不同的线程中运行,你可以为它们编写的代码有很多限制。例如,你无法在中断处理程序中使用基本类型。因此,在这种情况下,使用中断并不合适,而轮询由于我们必须在用户按下任意一个按钮时执行的任务,使得事情变得更容易。
与之前示例中的轮询读取数字输入相比,用于相同任务的轮询具有以下优点:
-
代码易于理解和阅读
-
流程易于理解,我们不必担心回调中运行的代码。
-
我们可以编写所有必要的代码来执行按钮按下时的动作,而不必担心与中断回调相关的特定限制。
-
我们不必担心多线程中运行的代码。
然而,与使用中断进行相同任务相比,使用轮询读取数字输入有以下缺点:
-
如果我们没有按住按钮特定的时间,代码可能无法检测到按钮被按下。
-
如果我们长时间按住按钮,代码将表现得好像按钮被多次按下。有时,我们不希望这种情况发生。
-
与中断触发的事件相比,循环消耗的资源更多,我们可能无法为其他任务提供这些资源。
在这种情况下,我们希望用户至少按住任意一个按钮半秒钟,因此我们不需要中断的优势。然而,我们将在本章后面使用中断。
读取按钮状态和运行 RESTful API。
现在,我们将集成检查两个按钮状态的代码到我们的 RESTful API 中。我们希望能够向 RESTful API 发出 HTTP 请求,并且我们也希望能够使用我们添加到面包板上的两个按钮。
我们必须让 Tornado 运行一个周期性回调,并在该回调中编写检查两个按钮状态的代码。我们将使用我们在上一章中创建最后一个版本的 RESTful API 时编写的代码,使用mraa库,并将此代码作为基准来添加新功能。示例代码文件为iot_python_chapter_04_03.py。
我们将向现有的BoardInteraction类添加两个类属性和三个类方法。示例代码文件为iot_python_chapter_05_02.py。
class BoardInteraction:
# The Red LED is connected to pin ~6
red_led = AnalogLed(6, 'Red')
# The Green LED is connected to Pin ~5
green_led = AnalogLed(5, 'Green')
# The Blue LED is connected to Pin ~3
blue_led = AnalogLed(3, 'Blue')
# The push button to reset colors
reset_push_button = PushButton(1)
# The push button to set colors to their maximum brightness
max_brightness_push_button = PushButton(0)
@classmethod
def set_min_brightness(cls):
cls.red_led.set_brightness(0)
cls.green_led.set_brightness(0)
cls.blue_led.set_brightness(0)
@classmethod
def set_max_brightness(cls):
cls.red_led.set_brightness(255)
cls.green_led.set_brightness(255)
cls.blue_led.set_brightness(255)
@classmethod
def check_push_buttons_callback(cls):
# Check whether the reset push button is pressed
if cls.reset_push_button.is_pressed:
print("You are pressing the reset pushbutton.")
cls.set_min_brightness()
# Check whether the maximum brightness push button is pressed
if cls.max_brightness_push_button.is_pressed:
print("You are pressing the maximum brightness pushbutton.")
cls.set_max_brightness()
之前的代码向BoardInteraction类添加了两个类属性:reset_push_button和max_brightness_push_button。reset_push_button类属性是一个PushButton实例,其pin属性设置为1。这样,该实例可以检查连接到 GPIO 引脚 1 的按钮的状态。max_brightness_push_button类属性是一个PushButton实例,其pin属性设置为0,因此,该实例可以检查连接到 GPIO 引脚 0 的按钮的状态。此外,之前的代码还向BoardInteraction类添加了以下类方法:
-
set_min_brightness: 使用0作为参数调用set_brightness方法,针对保存在red_led、green_led和blue_led类属性中的三个AnalogLed实例。这样,RGB LED 的三个组件将被关闭。 -
set_max_brightness:调用set_brightness方法,将255作为参数传递给保存在red_led、green_led和blue_led类属性中的三个AnalogLed实例。这样,RGB LED 的三个组件将以最大亮度级别打开。 -
check_push_buttons_callback:首先,通过评估代表复位按钮的PushButton实例的is_pressed属性值来检查复位按钮是否被按下。即,cls.reset_push_button。如果属性的值为True,代码将打印一条消息表明您正在按下复位按钮,并调用之前描述的cls.set_min_brightness类方法来关闭 RGB LED 的三个组件。然后,代码检查最大亮度按钮是否被按下,通过评估代表最大亮度按钮的PushButton实例的is_pressed属性值,即cls.max_brightness_push_button。如果属性的值为True,代码将打印一条消息表明您正在按下最大亮度按钮,并调用之前描述的cls.set_max_brightness类方法,以最大亮度级别打开 RGB LED 的三个组件。
小贴士
在 Python 中,在类方法标题之前添加@classmethod装饰器是必要的,以声明类方法。实例方法接收self作为第一个参数,但类方法接收当前类作为第一个参数,参数名称通常称为cls。在前面的代码中,我们使用cls来访问BoardInteraction类的类属性和类方法。
以下几行展示了我们必须添加到现有代码中的新类,以便通过 HTTP 请求设置最小和最大亮度。我们希望能够在我们的 RESTful API 中拥有与通过按钮可以控制的相同功能。代码添加了以下两个类:PutMinBrightnessHandler和PutMaxBrightnessHandler。示例代码文件为iot_python_chapter_05_02.py。
class PutMinBrightnessHandler(tornado.web.RequestHandler):
def put(self):
BoardInteraction.set_min_brightness()
response = dict(
red=BoardInteraction.red_led.brightness_value,
green=BoardInteraction.green_led.brightness_value,
blue=BoardInteraction.blue_led.brightness_value)
self.write(response)
class PutMaxBrightnessHandler(tornado.web.RequestHandler):
def put(self):
BoardInteraction.set_max_brightness()
response = dict(
red=BoardInteraction.red_led.brightness_value,
green=BoardInteraction.green_led.brightness_value,
blue=BoardInteraction.blue_led.brightness_value)
self.write(response)
代码声明了以下两个tornado.web.RequestHandler的子类:
-
PutMinBrightnessHandler:定义了调用BoardInteraction类的set_min_brightness类方法的put方法。然后,代码返回一个响应,其中包含已转换为连接到 RGB LED 红、绿、蓝阳极的 PWM 引脚输出占空比百分比的最低亮度级别。 -
PutMaxBrightnessHandler:定义了调用BoardInteraction类的set_max_brightness类方法的put方法。然后,代码返回一个响应,其中包含已转换为连接到 RGB LED 红、绿、蓝阳极的 PWM 引脚输出占空比百分比的最高亮度级别。
现在,有必要将高亮行添加到创建名为application的tornado.web.Application类实例的代码中,该实例包含构成 Web 应用程序的请求处理器列表,即正则表达式和tornado.web.RequestHandler子类的元组。示例代码文件为iot_python_chapter_05_02.py。
application = tornado.web.Application([
(r"/putredbrightness/([0-9]+)", PutRedBrightnessHandler),
(r"/putgreenbrightness/([0-9]+)", PutGreenBrightnessHandler),
(r"/putbluebrightness/([0-9]+)", PutBlueBrightnessHandler),
(r"/putrgbbrightness/r([0-9]+)g([0-9]+)b([0-9]+)",
PutRGBBrightnessHandler),
(r"/putminbrightness", PutMinBrightnessHandler),
(r"/putmaxbrightness", PutMaxBrightnessHandler),
(r"/getredbrightness", GetRedBrightnessHandler),
(r"/getgreenbrightness", GetGreenBrightnessHandler),
(r"/getbluebrightness", GetBlueBrightnessHandler),
(r"/version", VersionHandler)])
如我们之前的示例所示,代码创建了一个名为application的tornado.web.Application类实例,其中包含构成 Web 应用程序的请求处理器列表,即正则表达式和tornado.web.RequestHandler子类的元组。
最后,有必要将__main__方法替换为一个新的方法,因为我们想要运行一个周期性回调来检查两个按钮中的任何一个是否被按下。示例代码文件为iot_python_chapter_05_02.py。
if __name__ == "__main__":
print("Listening at port 8888")
application.listen(8888)
ioloop = tornado.ioloop.IOLoop.instance()
periodic_callback = tornado.ioloop.PeriodicCallback(BoardInteraction.check_push_buttons_callback, 500, ioloop)
periodic_callback.start()
ioloop.start()
如前例所示,__main__方法调用application.listen方法来为应用程序构建一个 HTTP 服务器,该服务器在端口号8888上定义了规则。然后,代码检索全局IOLoop实例并将其保存到ioloop局部变量中。我们必须使用该实例作为创建名为periodic_callback的tornado.ioloop.PeriodicCallback实例的一个参数。
PeriodicCallback实例允许我们安排一个指定的回调定期被调用。在这种情况下,我们指定BoardInteraction.check_push_buttons_callback类方法作为每 500 毫秒将被调用的回调。这样,我们指示 Tornado 每 500 毫秒运行一次BoardInteraction.check_push_buttons_callback类方法。如果该方法执行时间超过 500 毫秒,Tornado 将跳过后续调用以回到预定时间表。在代码创建PeriodicCallback实例后,下一行调用其start方法。
最后,调用ioloop.start()启动了使用application.listen创建的服务器。这样,Web 应用程序将处理接收到的请求,并且还会运行一个回调来检查按钮是否被按下。
以下行将启动 HTTP 服务器和我们的新版本 RESTful API。别忘了你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux。
python iot_python_chapter_05_02.py
运行示例后,按下设置颜色为最大亮度的按钮一秒钟。RGB LED 将显示白光,你将看到以下输出:
You are pressing the maximum brightness pushbutton.
Red LED connected to PWM Pin #6 set to brightness 255.
Green LED connected to PWM Pin #5 set to brightness 255.
Blue LED connected to PWM Pin #3 set to brightness 255.
现在,按下设置颜色为最小亮度的按钮一秒钟。RGB LED 将关闭,你将看到以下输出:
You are pressing the reset pushbutton.
Red LED connected to PWM Pin #6 set to brightness 0.
Green LED connected to PWM Pin #5 set to brightness 0.
Blue LED connected to PWM Pin #3 set to brightness 0.
使用新的 RESTful API,我们可以组合以下 HTTP 动词和请求 URL:
PUT http://192.168.1.107:8888/putmaxbrightness
之前的请求路径将与之前添加的元组 (regexp, request_class) 匹配 (r"/putmaxbrightness", PutMaxBrightnessHandler),Tornado 将调用 PutMaxBrightnessHandler.put 方法。RGB LED 将显示白色光,就像你按下最大亮度按钮时发生的那样。以下行显示了 HTTP 服务器对三个 LED 设置的亮度级别的响应:
{
"blue": 255,
"green": 255,
"red": 255
}
以下 HTTP 动词和请求 URL 将关闭 RGB LED,就像我们按下设置颜色为最小亮度按钮时发生的那样:
PUT http://192.168.1.107:8888/putminbrightness
以下行显示了 HTTP 服务器对三个 LED 设置的亮度级别的响应:
{
"blue": 0,
"green": 0,
"red": 0
}
现在,按下设置颜色为最大亮度按钮一秒钟。RGB LED 将显示白色光。然后,以下三个 HTTP 动词和请求 URL 将检索每种颜色的亮度级别。所有请求都将返回 255 作为当前值。我们使用按钮设置亮度级别,但代码的效果与调用 API 来更改颜色相同。我们保持了应用程序的一致性。
GET http://192.168.1.107:8888/getredbrightness
GET http://192.168.1.107:8888/getgreenbrightness
GET http://192.168.1.107:8888/getbluebrightness
如果我们使用 HTTPie,以下命令将完成工作:
http –b GET http://192.168.1.107:8888/getredbrightness
http –b GET http://192.168.1.107:8888/getgreenbrightness
http –b GET http://192.168.1.107:8888/getbluebrightness
以下行显示了三个请求的响应:
{
"red": 255
}
{
"green": 255
}
{
"blue": 255
}
我们创建了可以在 API 调用和用户按下按钮时使用的方法。我们可以处理 HTTP 请求并在用户按下按钮时运行操作。当我们使用 Tornado 构建我们的 RESTful API 时,我们必须创建和配置一个 PeriodicCallback 实例,以便每 500 毫秒检查按钮是否被按下。
小贴士
当我们添加可以通过按钮或其他与板交互的电子组件控制的特性时,考虑一致性非常重要。在这种情况下,我们确保当用户按下按钮并更改三种颜色的亮度值时,通过 API 调用读取的亮度值与设置的值完全一致。我们使用面向对象的代码和相同的方法,因此保持一致性很容易。
使用 wiring-x86 库读取数字输入
到目前为止,我们一直在使用 mraa 库读取数字输入。然而,在第一章中,我们也安装了 wiring-x86 库。我们可以修改几行面向对象的代码,用 wiring-x86 库替换 mraa 库来检查按钮是否被按下。
当我们使用 wiring-x86 库创建我们 RESTful API 的最后一个版本时,我们将使用上一章编写的代码,并将此代码作为基准来添加新功能。示例代码文件为 iot_python_chapter_04_04.py。
首先,我们将创建一个PushButton类的新版本来表示连接到我们的板上的按钮,该按钮可以使用上拉或下拉电阻。以下行显示了与wiring-x86库一起工作的新PushButton类的代码。示例代码文件为iot_python_chapter_05_03.py。
from wiringx86 import GPIOGalileoGen2 as GPIO
class PushButton:
def __init__(self, pin, pull_up=True):
self.pin = pin
self.pull_up = pull_up
self.gpio = Board.gpio
pin_mode = self.gpio.INPUT_PULLUP if pull_up else self.gpio.INPUT_PULLDOWN
self.gpio.pinMode(pin, pin_mode)
@property
def is_pressed(self):
push_button_status = self.gpio.digitalRead(self.pin)
if self.pull_up:
# Pull-up resistor connected
return push_button_status == 0
else:
# Pull-down resistor connected
return push_button_status == 1
@property
def is_released(self):
return not self.is_pressed
我们只需要从PushButton类的先前代码中更改几行,即与mraa库一起工作的版本。与wiring-x86库交互的新行在之前的代码中突出显示。构造函数,即__init__方法接收与mraa库一起工作的PushButton类的相同参数。在这种情况下,此方法将Board.gpio类属性的一个引用保存到self.gpio中。然后,代码根据pull_up参数的值确定pin_mode局部变量的值。如果pull_up是true,则值将是self.gpio.INPUT_PULLUP,否则是self.gpio.INPUT_PULLDOWN。最后,构造函数使用接收到的pin作为其pin参数和pin_mode作为其模式参数调用self.gpio.pinMode方法。这样,我们配置引脚为具有适当的上拉或下拉电阻的数字输入引脚。所有的PushButton实例都将保存对创建GPIO类实例的同一Board.gpio类属性的引用,特别是wiringx86.GPIOGalileoGen2类,其debug参数设置为False以避免不必要的低级通信调试信息。
is_pressed属性调用 GPIO 实例(self.gpio)的digitalRead方法来检索配置为数字输入的引脚的数字值。self.pin属性指定analogRead方法调用的pin值。is_pressed属性和PushButton类的其余代码与使用mraa库的版本保持相同。
然后,我们需要对之前的例子中进行的相同编辑进行修改,以创建BoardInteraction类的新版本,添加PutMinBrightnessHandler和PutMaxBrightnessHandler类,创建tornado.web.Application实例以及创建和配置PeriodicCallback实例的新版本__main__方法。因此,我们 RESTful API 的其余代码与之前示例中使用的代码保持相同。没有必要对代码的其余部分进行修改,因为它将自动与新PushButton类一起工作,并且其构造函数或其属性的参数没有发生变化。
以下行将启动 HTTP 服务器和与wiring-x86库一起工作的我们新的 RESTful API 版本。不要忘记,你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux,正如前一章所述。
python iot_python_chapter_05_03.py
小贴士
我们可以按下按钮,然后发出我们在上一个示例中发出的相同 HTTP 请求,以检查我们是否可以使用wiring-x86库实现完全相同的结果。
使用中断检测按下按钮
之前,我们分析了与之前示例中轮询读取数字输入相比,使用中断执行相同任务的优缺点。如果我们长时间按下任何一个按钮,代码的行为就像按钮被多次按下一样。现在,我们不希望这种情况发生,因此我们将使用中断而不是轮询来检测按钮是否被按下。
在我们开始编辑代码之前,有必要修改我们现有的接线。问题是并非所有的 GPIO 引脚都支持中断。实际上,编号为 0 和 1 的引脚不支持中断,而我们的按钮连接到了这些引脚上。在第一章中,理解和设置基础物联网硬件,当我们学习到英特尔 Galileo Gen 2 板上的 I/O 引脚时,我们了解到带有波浪线符号(~)作为编号前缀的引脚可以用作 PWM 输出引脚。事实上,带有波浪线符号(~)作为编号前缀的引脚也支持中断。
因此,我们可以将连接到关闭三个颜色的复位按钮的线从引脚1移到引脚**11**,并将连接到将三个颜色设置为最大亮度的按钮的线从引脚**0**移到引脚**10**。
以下图显示了连接到面包板的组件、必要的接线以及从英特尔 Galileo Gen 2 板到面包板的接线。该示例的 Fritzing 文件为iot_fritzing_chapter_05_04.fzz,以下图片是面包板视图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_05_07.jpg
以下图片显示了用符号表示的电子元件的电路图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_05_08.jpg
板上符号中标记为D10 PWM/SS的 GPIO 引脚连接到S2按钮,R4电阻是其上拉电阻。板上符号中标记为D11 PWM/MOSI的 GPIO 引脚连接到S1按钮,R5电阻是其上拉电阻。这样,当S2按钮被按下时,GPIO 引脚编号 10 将是低电平,当S1按钮被按下时,GPIO 引脚编号 11 将是低电平。
提示
当按钮被按下时,信号将从高电平降至低电平,因此,我们对当信号降至低电平时产生的中断感兴趣,因为这表明按钮已被按下。如果用户持续按下按钮,信号不会多次下降,GPIO 引脚将保持在低电平。因此,当我们观察从高电平降至低电平的过程中,即使用户长时间按下按钮,也只会触发一次中断,我们不会对中断处理代码进行多次调用。
请记住,S1按钮位于面包板的左侧,而S2按钮位于右侧。现在,是时候对连线进行更改了。在从板上的引脚上拔掉任何电线之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 灯熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。完成连线更改后,我们将编写 Python 代码来检测用户按下按钮时使用中断而不是轮询。
当我们使用mraa库创建我们 RESTful API 的最后一个版本时,我们将使用之前示例中编写的代码,并将此代码作为基准来添加新功能。示例代码文件为iot_python_chapter_05_02.py。
我们将创建一个新的PushButtonWithInterrupt类来表示连接到我们板上的按钮,该按钮可以使用上拉或下拉电阻,并将指定当按钮被按下时需要调用的回调,即中断处理程序。当按钮被按下时,将发生中断,指定的回调将作为中断处理程序执行。以下行显示了与mraa库一起工作的新PushButtonWithInterrupt类的代码。示例代码文件为iot_python_chapter_05_04.py。
import mraa
import time
from datetime import date
class PushButtonWithInterrupt:
def __init__(self, pin, pyfunc, args, pull_up=True):
self.pin = pin
self.pull_up = pull_up
self.gpio = mraa.Gpio(pin)
self.gpio.dir(mraa.DIR_IN)
mode = mraa.EDGE_FALLING if pull_up else mraa.EDGE_RISING
result = self.gpio.isr(mode, pyfunc, args)
if result != mraa.SUCCESS:
raise Exception("I could not configure ISR on pin {0}".format(pin))
def __del__(self):
self.gpio.isrExit()
在创建PushButtonWithInterrupt类的实例时,我们必须指定以下参数:
-
在
pin参数中,按钮连接到的引脚号 -
当中断被触发时将被调用的函数,即中断处理函数,在
pyfunc参数中 -
将传递给中断处理函数的参数,在
args参数中
如果我们没有指定额外的值,可选的pull_up参数将为True,实例将像按钮连接上拉电阻一样工作。如果我们使用下拉电阻,我们必须在pull_up参数中传递False。
构造函数,即 __init__ 方法,创建一个新的 mraa.Gpio 实例,将接收到的 pin 作为其 pin 参数,将其引用保存到 gpio 属性中,并调用其 dir 方法来配置引脚为输入引脚(mraa.DIR_IN)。然后,代码根据 pull_up 参数的值确定 mode 局部变量的值。如果 pull_up 为 true,则值将为 mraa.EDGE_FALLING 和 mraa.EDGE_RISING;否则。mode 局部变量持有将触发中断的边缘模式。当我们使用上拉电阻并且用户按下按钮时,信号将从高电平下降到低电平,因此我们希望边缘下降场景触发中断,以指示按钮已被按下。
然后,代码使用接收到的 pin 作为其 pin 参数,局部变量 mode 作为其 mode 参数,以及接收到的 pyfunc 和 args 作为其 pyfunc 和 args 参数来调用 self.gpio.isr 方法。这样,我们设置了一个回调,当引脚值改变(即按钮被按下)时将被调用。因为我们之前已经确定了 mode 局部变量的适当值,所以我们将根据上拉或下拉电阻的使用配置适当的边缘模式,以便在按钮被按下时触发中断。如前所述,并非所有 GPIO 引脚都支持中断,因此有必要检查调用 self.gpio.isr 方法的返回结果。如果之前通过调用 self.gpio.isr 方法已经将中断处理程序设置到引脚上,则不会返回 mraa.SUCCESS 值。
PushButtonWithInterrupt 类还声明了一个 __del__ 方法,该方法将在 Python 从内存中删除此类的实例之前被调用,即当对象变得不可访问并被垃圾回收机制删除时。该方法只是调用 self.gpio.isrExit 方法来移除与引脚关联的中断处理程序。
我们将替换现有 BoardInteraction 类中的两个类属性。我们将不再使用 PushButton 实例,而是使用 PushButtonWithInterrupt 实例。类中声明的类方法与作为基准使用的代码中保持相同,但它们不包括在下述行中。示例代码的文件名为 iot_python_chapter_05_04.py。
class BoardInteraction:
# The Red LED is connected to pin ~6
red_led = AnalogLed(6, 'Red')
# The Green LED is connected to Pin ~5
green_led = AnalogLed(5, 'Green')
# The Blue LED is connected to Pin ~3
blue_led = AnalogLed(3, 'Blue')
# The push button to reset colors
reset_push_button = PushButtonWithInterrupt(11, set_min_brightness_callback, set_min_brightness_callback)
# The push button to set colors to their maximum brightness
max_brightness_push_button = PushButtonWithInterrupt(10, set_max_brightness_callback, set_max_brightness_callback)
突出的代码行声明了BoardInteraction类的两个类属性:reset_push_button和max_brightness_push_button。reset_push_button类属性是一个PushButtonWithInterrupt的实例,其pin属性设置为11,中断处理程序设置为稍后我们将声明的set_min_brightness_callback函数。这样,该实例将在用户按下连接到 GPIO 引脚编号 11 的按钮时调用set_min_brightness_callback函数进行所有必要的配置。max_brightness_push_button类属性是一个PushButtonWithInterrupt的实例,其pin属性设置为10,因此,将在用户按下连接到 GPIO 引脚编号 10 的按钮时调用set_max_brightness_callback函数进行所有必要的配置。
现在,有必要声明当中断被触发时将被调用的函数:set_min_brightness_callback和set_max_brightness_callback。请注意,这些函数被声明为函数,而不是任何类的成员方法。
def set_max_brightness_callback(args):
print("You have pressed the maximum brightness pushbutton.")
BoardInteraction.set_max_brightness()
def set_min_brightness_callback(args):
print("You have pressed the reset pushbutton.")
BoardInteraction.set_min_brightness()
在前面的代码中声明的两个函数会打印一条消息,指示已按下特定按钮,并调用BoardInteraction.set_max_brightness或BoardInteraction.set_min_brightness类方法。我们已经从之前的示例中知道了这些类方法,并且我们没有对它们进行任何更改。
最后,有必要用一个新的方法替换__main__方法,因为我们不再需要运行周期性回调了。现在,我们的PushButtonWithInterrupt实例配置了当按下按钮时将被调用的中断处理程序。示例的代码文件是iot_python_chapter_05_04.py。
if __name__ == "__main__":
print("Listening at port 8888")
application.listen(8888)
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.start()
当__main__方法开始运行时,BoardInteraction类已经执行了创建两个PushButtonWithInterrupt实例的代码,因此,每当按下按钮时,中断处理程序都会运行。__main__方法只是构建并启动 HTTP 服务器。
以下行将启动 HTTP 服务器和我们的新版本 RESTful API。别忘了你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux。
python iot_python_chapter_05_04.py
运行示例后,按下设置颜色为最大亮度的按钮 5 秒钟。RGB LED 将显示白色光,你将看到以下输出:
You are pressing the maximum brightness pushbutton.
Red LED connected to PWM Pin #6 set to brightness 255.
Green LED connected to PWM Pin #5 set to brightness 255.
Blue LED connected to PWM Pin #3 set to brightness 255.
你按下了按钮 5 秒钟,但输出显示的消息表明你只按了一次按钮。当你按下按钮时,GPIO 引脚编号 10 的信号从高变低一次,因此触发了mraa.EDGE_FALLING中断,并执行了配置的中断处理程序(set_max_brightness_callback)。你继续按住按钮,但信号保持在低值,因此没有再次触发中断。
小贴士
显然,当你只想在按下按钮一次(即使按得很久)时运行代码,中断处理程序的用法提供了轮询所难以实现的必要精度。
现在,按下设置颜色为最低亮度的按钮 10 秒钟。RGB LED 将关闭,你将看到以下输出:
You are pressing the reset pushbutton.
Red LED connected to PWM Pin #6 set to brightness 0.
Green LED connected to PWM Pin #5 set to brightness 0.
Blue LED connected to PWM Pin #3 set to brightness 0.
就像其他按钮一样,你按下了按钮很多秒,但显示的消息表明你只按了一次按钮。当你按下按钮时,GPIO 引脚 11 的信号从高电平变为低电平一次,因此,mraa.EDGE_FALLING中断被触发,配置的中断处理程序(set_min_brightness_callback)被执行。
小贴士
我们可以发出与之前示例中相同的 HTTP 请求,以检查我们是否可以使用与使用中断处理程序运行 HTTP 服务器的新代码实现完全相同的结果。
我们可以处理 HTTP 请求,并在用户按下按钮时运行中断处理程序。与之前版本相比,我们提高了准确性,因为代码表现得好像用户长时间按住按钮时按钮被按了很多次。此外,我们还移除了周期性回调。
小贴士
每当我们需要读取数字输入时,我们可以根据我们项目中的具体需求在轮询或中断处理程序之间进行选择。有时,中断处理程序是最佳解决方案,但在其他情况下轮询更为合适。非常重要的一点是,wiring-x86库不允许我们使用中断处理程序来处理数字输入,因此,如果我们决定使用它们,我们必须使用mraa库。
测试你的知识
-
由于在按钮上使用了上拉电阻,当按钮按下连接到 GPIO 引脚时,我们将读取以下值:
-
低值(0V)。
-
高值,即 IOREF 电压。
-
电压值在 1V 到 3.3V 之间。
-
-
由于在按钮上使用了上拉电阻,当按钮释放连接到 GPIO 引脚时,我们将读取以下值:
-
低值(0V)。
-
高值,即 IOREF 电压。
-
电压值在 1V 到 3.3V 之间。
-
-
如果我们通过轮询读取连接到 GPIO 引脚的按钮状态,循环每 0.5 秒运行一次,并且用户持续按下按钮 3 秒钟:
-
代码将表现得好像按钮被按了多次。
-
代码将表现得好像按钮被按了一次。
-
代码将表现得好像按钮从未被按下。
-
-
我们有一个中断处理程序,用于按钮,中断边缘模式设置为
mraa.EDGE_FALLING,按钮通过上拉电阻连接。如果用户持续按下按钮 3 秒钟:-
代码将表现得好像按钮被按了多次。
-
代码将表现得好像按钮只被按下了一次。
-
代码将表现得好像按钮从未被按下。
-
-
在英特尔 Galileo Gen 2 板上,带有以下符号作为前缀的引脚可以在
mraa库中配置为数字输入的中断处理程序:-
哈希符号(#)。
-
美元符号($)。
-
波浪符号(~)。
-
摘要
在本章中,我们了解了上拉和下拉电阻的区别,以及如何使用mraa和wiring-x86库读取按钮的状态。我们了解了使用轮询读取按钮状态与使用中断和中断处理程序工作的区别。
我们创建了统一的代码,允许用户使用面包板上的按钮或 HTTP 请求执行相同的操作。我们将响应按钮状态变化的代码与使用 Tornado Web 服务器构建的 RESTful API 相结合。与前面的章节一样,我们利用了 Python 的面向对象特性,并创建了类来封装按钮和必要的配置,使用mraa和wiring-x86库。我们的代码易于阅读和理解,并且我们可以轻松切换底层低级库。
现在我们能够以不同的方式和配置读取数字输入,这使得用户在设备处理 HTTP 请求的同时能够与之交互,我们可以利用板上的更复杂的通信功能,并利用其存储功能,这些是下一章的主题。
第六章. 使用模拟输入和本地存储
在本章中,我们将使用模拟输入将来自真实环境的定量值转换为定性值,我们将使用这些值来触发动作。我们将:
-
理解模拟输入的工作原理
-
了解模拟数字转换器分辨率的影响
-
使用模拟引脚和
mraa库测量电压 -
在分压器中包含光敏电阻,并将模拟输入引脚与电压源连接
-
将可变电阻转换为电压源
-
使用模拟输入和
mraa库确定黑暗程度 -
当环境光线变化时触发动作
-
使用 wiring-x86 库控制模拟输入
-
使用不同的本地存储选项来记录事件
理解模拟输入
在第一章 理解和设置基础物联网硬件 中,我们了解到英特尔 Galileo Gen 2 板提供了从A0到A5编号的六个模拟输入引脚,位于板的前面板的右下角。可以测量从 0V(地)到配置的IOREF跳线位置(默认为 5V)的值,该板为模拟数字转换器提供 12 位的分辨率。因此,我们可以检测到 4096 个不同的值(2¹² = 4096),或 4096 个单位,其值从 0 到 4095(包含),其中 0 代表 0V,4095 表示 5V。
小贴士
如果你有其他 Arduino 板的经验,你必须考虑到英特尔 Galileo Gen 2 板不使用标记为AREF的引脚。在其他 Arduino 板上,你可以使用此引脚来设置模拟数字转换过程的模拟参考电压。当我们使用英特尔 Galileo Gen 2 板时,模拟引脚的最大值始终将由IOREF跳线位置(5V 或 3.3V)控制,并且无法为模拟输入使用任何外部参考。在我们的所有示例中,我们将使用IOREF跳线的默认位置,因此最大值始终为 5V。
我们只需要应用一个线性函数将模拟引脚读取的原始值转换为输入电压值。如果我们使用 12 位的分辨率,检测到的值将具有最小差异或步长为 5V / 4095 = 0.001220012 V,大约为 1.22 mV(毫伏)或 1.22E-03 V。我们只需要将模拟引脚读取的原始值乘以 5,然后除以 4095。
下面的图表显示了从模拟引脚读取的值在横轴(x-轴)上,以及它在纵轴(y-轴)上表示的相应浮点电压值。
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_06_01.jpg
之前图表的方程式是 y = x / 4095 * 5,具体来说 voltage_value = analog_pin_read_value / 4095 * 5。我们可以在我们的 Python 解释器中运行以下代码来查看输出,其中包括使用公式从模拟引脚读取的每个原始值(从0到4095,包括)可以生成的所有电压值。
for analog_pin_read_value in range(0, 4096):
print(analog_pin_read_value / 4095.0 * 5.0)
提示
我们也可以使用较低的分辨率,例如 10 位分辨率,我们就能检测到更少的不同的值,具体是 1024 个不同的值(2¹⁰ = 1024),或 1024 个单位,从0到1023(包括)。在这种情况下,值的最小差异或步长将是 5V / 1023 = 0.004887585V,大约是 4.89mV(毫伏)或 4.89E-03 V。如果我们决定使用这个较低的分辨率,我们只需将模拟引脚读取的原始值乘以五,然后除以 1023。
使用电压源连接模拟输入引脚
理解如何从模拟引脚读取值并将这些值映射回电压值的最简单方法是通过一个非常简单的例子来操作。我们将连接一个电源到模拟输入引脚之一,具体来说是一个串联两个 AA 或 AAA 1.25 V 可充电电池的电池组。也可以使用串联的两个 AA 或 AAA 1.5 V 标准电池。请注意,两个可充电电池串联时的最大电压将是 2.5 V(1.25 V * 2),而两个标准电池串联时的最大电压将是 3 V(1.5 V * 2)。
我们将使用标记为A0的模拟引脚连接到电池组的正极(+)。别忘了电池组的正极(+)连接到电池的乳头。完成必要的接线后,我们将编写 Python 代码来测量电池组的电压。这样,我们将读取将模拟值转换为数字表示的结果,并将其映射到电压值。为了使用这个例子,我们需要以下部件:
-
两个 AA 或 AAA 1.25 V 可充电电池或两个 AA 或 AAA 1.5 V 标准电池。
-
一个合适的电池夹,用于将两个选定的电池串联并简化接线。例如,如果你使用两个 AA 1.25 可充电电池,你需要一个 2 x AA 电池夹。
-
一个 2200Ω(2k2Ω)的 5%容差(红红红金)电阻。
以下图像显示了电池夹、连接到面包板的电阻、必要的接线以及从英特尔 Galileo Gen 2 板到面包板的接线。该示例的 Fritzing 文件为iot_fritzing_chapter_06_01.fzz,以下图像是面包板视图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_06_02.jpg
以下图示显示了用符号表示电子组件的电路图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_06_03.jpg
如前图所示,板符号上标记为 A0 的模拟输入引脚通过电阻连接到电源的正极。电源的负极连接到地。
现在,是时候进行所有必要的接线了。在添加或移除任何线从板上的引脚之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
使用模拟输入和 mraa 库测量电压
我们将创建一个新的 VoltageInput 类来表示连接到我们的板上的电压源,具体来说,是连接到模拟输入引脚。以下行显示了与 mraa 库一起工作的新 VoltageInput 类的代码。示例代码文件为 iot_python_chapter_06_01.py。
import mraa
import time
class VoltageInput:
def __init__(self, analog_pin):
self.analog_pin = analog_pin
self.aio = mraa.Aio(analog_pin)
# Configure ADC resolution to 12 bits (0 to 4095)
self.aio.setBit(12)
@property
def voltage(self):
raw_value = self.aio.read()
return raw_value / 4095.0 * 5.0
在创建 VoltageInput 类的实例时,我们必须指定电压源连接到的模拟引脚编号,analog_pin 是必需的参数。构造函数,即 __init__ 方法,使用接收到的 analog_pin 作为其 pin 参数创建一个新的 mraa.Aio 实例,将其引用保存到 aio 属性中,并调用其 setBit 方法来配置模拟数字转换器的分辨率为 12 位,即提供 4096 个可能的值来表示从 0 到 5V。
该类定义了一个 voltage 属性,它调用相关 mraa.Aio 实例(self.aio)的 read 方法来从模拟引脚检索原始值,并将其保存到 raw_value 变量中。然后,代码返回将 raw_value 除以 4095 并乘以 5 的结果。这样,该属性返回从读取函数返回的原始值转换的电压值。
现在,我们可以编写使用新的 VoltageInput 类来创建电池包实例并轻松检索电压值的代码。新类执行必要的计算,将读取的值映射到电压值,因此我们只需检查 voltage 属性的值,无需担心关于模拟数字转换器和其分辨率的详细信息。
现在,我们将编写一个循环,每秒检索一次电压值。示例代码文件为 iot_python_chapter_06_01.py。
if __name__ == "__main__":
v0 = VoltageInput(0)
while True:
print("Voltage at pin A0: {0}".format(v0.voltage))
# Sleep 1 second
time.sleep(2)
第一行创建了一个之前编码的 VoltageInput 类的实例,其中 analog_pin 参数的值为 0。这样,该实例将读取标记为 A0 的引脚上的模拟值,该引脚通过电阻连接到电源的正极。
然后,代码将无限循环运行,也就是说,直到你通过按Ctrl + C或按下停止过程的按钮来中断执行。该循环每两秒打印一次A0引脚的电压值。以下是在使用两个电量略有下降的可充电电池执行代码时生成的示例输出行:
Voltage at pin A0: 2.47130647131
将光敏电阻连接到模拟输入引脚
现在,我们将使用光敏电阻,也就是光传感器,具体来说,这是一种电子元件,它提供了一个可变电阻,该电阻的阻值会根据入射光的强度而变化。随着入射光强度的增加,光敏电阻的阻值减小,反之亦然。
小贴士
光敏电阻也被称为LDR(即光敏电阻)或光电管。请注意,光敏电阻并不是最佳的光感测元件,但当我们没有达到一秒的延迟问题时,它们在轻松确定我们是否处于黑暗环境中时非常有用。
我们无法使用我们的板子测量电阻值。然而,我们可以读取电压值,因此,我们将使用一个电压分压器配置,其中包括光敏电阻作为其两个电阻之一。当光敏电阻接收到大量光线时,电压分压器将输出高电压值;当光敏电阻处于暗区,即接收到少量或没有光线时,它将输出低电压值。
在之前的例子中,我们学习了如何从模拟引脚读取值并将这些值映射回电压值。我们将使用这些知识来确定何时变暗。一旦我们理解了传感器的工作原理,我们将对光条件的变化做出反应,并记录特定场景的数据。
我们将使用标有A0的模拟引脚来连接包含光敏电阻的电压分压器的正极(+)。完成必要的接线后,我们将编写 Python 代码来确定我们是否处于黑暗环境中。这样,我们将读取将电阻值转换为电压的结果,然后将这个模拟值转换为它的数字表示。正如我们在之前的例子中学到的,我们将读取的数字值映射到电压值,然后我们将这个电压值映射到黑暗测量值。听起来很复杂,但实际上比听起来容易得多。我们需要以下部分来处理这个例子:
-
光敏电阻
-
一个 10,000Ω(10kΩ)的电阻,公差为 5%(棕色 黑色 橙色 金色)
以下图表显示了连接到面包板的光敏电阻和电阻,必要的布线和从英特尔 Galileo Gen 2 板到面包板的布线。该示例的 Fritzing 文件为iot_fritzing_chapter_06_02.fzz,以下图片是面包板视图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_06_04.jpg
以下图片显示了用符号表示的电子组件的电路图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_06_05.jpg
如前图所示,板符号上标记为A0的 GPIO 引脚连接到由名为LDR1的光敏电阻和 5%公差为 10kΩ的电阻R1构建的分压器。LDR1光敏电阻连接到IOREF引脚。我们已经知道标记为IOREF的引脚为我们提供 IOREF 电压,即在我们的实际配置中为 5V。R1电阻连接到GND(地)。
现在,是时候进行所有必要的布线了。在添加或从板上的引脚移除任何电线之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从英特尔 Galileo Gen 2 板上拔掉电源。
使用模拟输入和 mraa 库确定黑暗程度
我们将创建一个新的DarknessSensor类来表示包含在分压器中并连接到我们的板上的光敏电阻,具体来说,是连接到模拟输入引脚。因为我们已经编写了读取和转换模拟输入的代码,所以我们将使用之前创建的VoltageInput类。以下行显示了与mraa库一起工作的新DarknessSensor类的代码。该示例的代码文件为iot_python_chapter_06_02.py。
import mraa
import time
class DarknessSensor:
# Light level descriptions
light_extremely_dark = "extremely dark"
light_very_dark = "very dark"
light_dark = "just dark"
light_no_need_for_a_flashlight = \
"there is no need for a flashlight"
# Maximum voltages that determine the light level
extremely_dark_max_voltage = 2.0
very_dark_max_voltage = 3.0
dark_max_voltage = 4.0
def __init__(self, analog_pin):
self.voltage_input = VoltageInput(analog_pin)
self.voltage = 0.0
self.ambient_light = self.__class__.light_extremely_dark
self.measure_light()
def measure_light(self):
self.voltage = self.voltage_input.voltage
if self.voltage < self.__class__.extremely_dark_max_voltage:
self.ambient_light = self.__class__.light_extremely_dark
elif self.voltage < self.__class__.very_dark_max_voltage:
self.ambient_light = self.__class__.light_very_dark
elif self.voltage < self.__class__.dark_max_voltage:
self.ambient_light = self.__class__.light_dark
else:
self.ambient_light = self.__class__.light_no_need_for_a_flashlight
当我们在analog_pin必需参数中创建DarknessSensor类的实例时,我们必须指定连接到包含光敏电阻的分压器的模拟引脚编号。构造函数,即__init__方法,使用接收到的analog_pin作为其analog_pin参数创建一个新的VoltageInput实例,并将其引用保存在voltage_input属性中。然后,构造函数创建并初始化两个属性:voltage和ambient_light。最后,构造函数调用measure_light方法。
该类定义了一个measure_light方法,该方法通过检查self.voltage_input.voltage属性在voltage属性(self.voltage)中检索到的电压值来保存电压值。这样,代码可以检查存储在电压属性中的值是否低于确定光级的三个最大电压值,并为ambient_light属性(self.ambient_light)设置适当的值。
该类定义了以下三个类属性,这些属性确定了确定每个光级的最大电压值:
-
extremely_dark_max_voltage:如果检索到的电压低于 2V,这意味着环境非常暗 -
very_dark_max_voltage: 如果检索到的电压低于 3V,这意味着环境非常暗 -
dark_max_voltage。如果检索到的电压低于 4V,这意味着环境只是暗
小贴士
这些值是为特定光敏电阻和环境条件配置的。你可能需要根据包含在分压器中的光敏电阻检索到的电压值设置不同的值。一旦运行示例,你可以检查电压值并对之前解释过的类属性中存储的电压值进行必要的调整。记住,当入射光增加时,电压值会更高,即更接近 5V。因此,最暗的环境,测量的电压越低。
我们的目标是将一个定量值,特别是电压值,转换为定性值,即一个能够解释真实环境中真实情况的值。该类定义了以下四个类属性,指定光级描述并确定在调用measure_light方法后电压值将被转换为四个光级中的哪一个:
-
light_extremely_dark -
light_very_dark -
light_dark -
light_no_need_for_a_flashlight
现在,我们可以编写使用新的DarkSensor类创建分压器中包含的光敏电阻实例的代码,并轻松打印光条件描述。新类使用之前创建的VoltageInput类进行必要的计算,将读取的值映射到电压值,然后将其转换为定性值,为我们提供光条件描述。现在,我们将编写一个循环,每两秒检查一次光条件是否改变。示例的代码文件是iot_python_chapter_06_02.py。
if __name__ == "__main__":
darkness_sensor = DarknessSensor(0)
last_ambient_light = ""
while True:
darkness_sensor.measure_light()
new_ambient_light = darkness_sensor.ambient_light
if new_ambient_light != last_ambient_light:
# The ambient light value changed
last_ambient_light = new_ambient_light
print("Darkness level: {0}".format(new_ambient_light))
# Sleep 2 seconds
time.sleep(2)
第一行创建了一个之前编写的DarknessSensor类的实例,将0作为analog_pin参数的值,并将实例保存在darkness_sensor局部变量中。这样,该实例将使用VoltageInput类的实例从标记为A0的引脚读取模拟值。然后,代码将last_ambient_light局部变量初始化为空字符串。
然后,代码将无限循环运行,即直到你通过按Ctrl + C或按下停止过程的按钮来中断执行。在这种情况下,如果你使用具有远程开发功能的 Python IDE 运行代码在你的板上,循环将调用darkness_sensor.measure_light方法来检索当前的光线条件,并将更新的darkness_sensor.ambient_light值保存在new_ambient_light局部变量中。然后,代码检查new_ambient_light值是否与last_ambient_light不同。如果它们不同,这意味着环境光线已经改变,因此,它将last_ambient_light的值设置为new_ambient_light,并打印存储在new_ambient_light中的环境光线描述。
当环境光线从最后打印的值变化时,循环打印环境光线描述,并且每两秒检查一次环境光线。以下行将启动示例。别忘了你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux。
python iot_python_chapter_06_02.py
在运行示例之后,执行以下操作:
-
使用智能手机或手电筒在光敏电阻上诱导光线
-
用你的手在光敏电阻上产生阴影
-
减少环境中的光线,但不是最小值,只是让它稍微暗一些
-
将环境中的光线减少到最小,一个完全没有光线的完全黑暗环境
前述动作的结果,你应该看到以下输出:
Darkness level: there is no need for a flashlight
Darkness level: just dark
Darkness level: very dark
Darkness level: extremely dark
环境光线变化时的触发动作
在之前的示例中,我们使用 PWM 来设置 RGB LED 的红色、绿色和蓝色组件的亮度级别。现在,我们将添加一个 RGB LED,并将基于光敏电阻检测到的环境光线为其三个组件设置亮度级别。我们将像在第四章中处理此组件的示例那样布线 RGB LED,使用 RESTful API 和脉宽调制。我们将使用以下 PWM 输出引脚:
-
将**~6**引脚连接到红色 LED 的正极引脚
-
将**~5**引脚连接到绿色 LED 的正极引脚
-
将**~3**引脚连接到蓝色 LED 的正极引脚。
我们需要以下额外的部件来使用此示例:
-
一个常见的共阴极 5mm RGB LED
-
三个 270Ω、5%容差的电阻(红紫棕金)
以下图显示了连接到面包板的组件,必要的布线和从 Intel Galileo Gen 2 板到面包板的布线。示例的 Fritzing 文件为iot_fritzing_chapter_06_03.fzz,以下图片是面包板视图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_06_06.jpg
以下图片显示了用符号表示的电子组件的原理图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_06_07.jpg
如前图所示,板上的符号中标记为D3 PWM、D5 PWM和D6 PWM的三个具有 PWM 功能的 GPIO 引脚连接到一个 270Ω电阻,该电阻连接到每个 LED 颜色的阳极引脚,而公共阴极连接到地。
现在,是时候将组件插入面包板并完成所有必要的布线了。在添加或移除任何线之前,别忘了关闭 Yocto Linux,等待所有板载 LED 熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
我们将添加代表连接到我们的板上的 LED 的AnalogLed类的代码,该 LED 的亮度级别可以从 0 到 255。我们在第四章中创建了此类,使用 RESTful API 和脉宽调制,示例代码文件为iot_python_chapter_04_02.py。
我们将创建一个新的BoardInteraction类来创建我们的DarknessSensor类的一个实例以及 RGB LED 每个组件的一个实例,以便轻松控制它们的亮度级别。以下行显示了BoardInteraction类的代码。示例代码文件为iot_python_chapter_06_03.py:
class BoardInteraction:
# The photoresistor included in the voltage divider
# is connected to analog PIN A0
darkness_sensor = DarknessSensor(0)
# The Red LED is connected to GPIO pin ~6
red_led = AnalogLed(6, 'Red')
# The Green LED is connected to GPIO Pin ~5
green_led = AnalogLed(5, 'Green')
# The Blue LED is connected to GPIO Pin ~3
blue_led = AnalogLed(3, 'Blue')
@classmethod
def set_rgb_led_brightness(cls, brightness_level):
cls.red_led.set_brightness(brightness_level)
cls.green_led.set_brightness(brightness_level)
cls.blue_led.set_brightness(brightness_level)
@classmethod
def update_leds_brightness(cls):
if cls.darkness_sensor.ambient_light == DarknessSensor.light_extremely_dark:
cls.set_rgb_led_brightness(255)
elif cls.darkness_sensor.ambient_light == DarknessSensor.light_very_dark:
cls.set_rgb_led_brightness(128)
elif cls.darkness_sensor.ambient_light == DarknessSensor.light_dark:
cls.set_rgb_led_brightness(64)
else:
cls.set_rgb_led_brightness(0)
BoardInteraction类声明了四个类属性:darkness_sensor、red_led、green_led和blue_led。第一个类属性保存了DarknessSensor类的新实例,最后三个类属性保存了之前导入的AnalogLed类的新实例,分别代表连接到引脚**6**、**5和~3**的红、绿、蓝 LED。然后,BoardInteraction类声明了以下两个类方法:
-
set_rgb_led_brightness:将brightness_level参数接收到的相同亮度级别设置为 RGB LED 的三个组件。 -
update_leds_brightness:根据 DarknessSensor 实例(cls.darkness_sensor)的ambient_light值设置 RGB LED 的三个组件的亮度级别。如果非常暗,亮度级别将为 255。如果很暗,亮度级别将为 128。如果暗,亮度级别将为 64。否则,RGB LED 将完全关闭。
现在,我们可以编写一个代码,使用新的BoardInteraction类来测量环境光线并根据获取的值设置 RGB LED 的亮度。正如我们之前的示例一样,我们只有在环境光线值从当前值变化时才会进行更改。我们将编写一个循环,每两秒检查一次光线条件是否发生变化。示例代码文件为iot_python_chapter_06_03.py。
last_ambient_light = ""
while True:
BoardInteraction.darkness_sensor.measure_light()
new_ambient_light = BoardInteraction.darkness_sensor.ambient_light
if new_ambient_light != last_ambient_light:
# The ambient light value changed
last_ambient_light = new_ambient_light
print("Darkness level: {0}".format(new_ambient_light))
BoardInteraction.update_leds_brightness()
# Sleep 2 seconds
time.sleep(2)
第一行初始化last_ambient_light局部变量为空字符串。然后,代码无限循环运行,即直到你中断执行。循环调用BoardInteraction.darkness_sensor.measure_light方法来检索当前光线条件,并将更新的BoardInteraction.darkness_sensor.ambient_light值保存到new_ambient_light局部变量中。然后,代码检查new_ambient_light值是否与last_ambient_light不同。如果它们不同,这意味着环境光线已经改变,因此,它将last_ambient_light的值设置为new_ambient_light,打印存储在new_ambient_light中的环境光线描述,并调用BoardInteraction.update_leds_brightness方法根据环境光线设置 RGB LED 的亮度。
以下行将开始示例。别忘了你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux。
python iot_python_chapter_06_03.py
在运行示例之后,执行以下操作,你将看到 RGB LED 的亮度级别按以下方式变化:
-
使用智能手机或手电筒在光敏电阻上产生光线。RGB LED 将保持关闭。
-
用你的手在光敏电阻上产生阴影。RGB LED 将以昏暗的灯光打开。
-
减少环境中的光线,但不是最小,只是让它变得有点暗。RGB LED 将增加其亮度。
-
将环境中的光线减少到最小,一个完全没有光线的完全黑暗环境。RGB LED 将增加其亮度到最大级别。
-
使用智能手机或手电筒再次在光敏电阻上产生光线。RGB LED 将关闭。
由于之前的操作,你应该看到以下输出:
Darkness level: there is no need for a flashlight
Red LED connected to PWM Pin #6 set to brightness 0.
Green LED connected to PWM Pin #5 set to brightness 0.
Blue LED connected to PWM Pin #3 set to brightness 0.
Darkness level: just dark
Red LED connected to PWM Pin #6 set to brightness 64.
Green LED connected to PWM Pin #5 set to brightness 64.
Blue LED connected to PWM Pin #3 set to brightness 64.
Darkness level: very dark
Red LED connected to PWM Pin #6 set to brightness 128.
Green LED connected to PWM Pin #5 set to brightness 128.
Blue LED connected to PWM Pin #3 set to brightness 128.
Darkness level: extremely dark
Red LED connected to PWM Pin #6 set to brightness 255.
Green LED connected to PWM Pin #5 set to brightness 255.
Blue LED connected to PWM Pin #3 set to brightness 255.
Darkness level: there is no need for a flashlight
Red LED connected to PWM Pin #6 set to brightness 0.
Green LED connected to PWM Pin #5 set to brightness 0.
Blue LED connected to PWM Pin #3 set to brightness 0.
我们编写了易于阅读和理解的面向对象 Python 代码。借助mraa库,我们能够在环境光线变化时轻松触发动作。当环境光线改变时,我们可以控制 RGB LED 的亮度。我们使用模拟输入来确定环境光线水平,并使用 PWM 生成模拟输出以控制 RGB LED 的亮度级别。
使用 wiring-x86 库控制模拟输入
到目前为止,我们一直在使用mraa库来处理模拟输入并检索环境光线水平。然而,我们也在之前的示例中使用了wiring-x86库。我们只需更改几行面向对象的代码,就可以用wiring-x86库替换mraa库来读取模拟值。
首先,我们必须用与wiring-x86库兼容的版本替换AnalogLed类的代码。我们在第四章中创建了此版本,使用 RESTful API 和脉冲宽度调制,示例代码文件为iot_python_chapter_04_04.py。当我们获取AnalogLed类的代码时,我们也将获得Board类。
以下行显示了与wiring-x86库兼容而不是使用mraa的VoltageInput类的新版本。示例代码文件为iot_python_chapter_06_04.py。
from wiringx86 import GPIOGalileoGen2 as GPIO
class VoltageInput:
initial_analog_pin_number = 14
def __init__(self, analog_pin):
self.analog_pin = analog_pin
self.gpio = Board.gpio
self.gpio.pinMode(
analog_pin + self.__class__.initial_analog_pin_number,
self.gpio.ANALOG_INPUT)
@property
def voltage(self):
raw_value = self.gpio.analogRead(
self.analog_pin +
self.__class__.initial_analog_pin_number)
return raw_value / 1023.0 * 5.0
我们创建了一个新的VoltageInput类版本,该版本声明了一个initial_analog_pin_number类属性,并将其设置为14。wiring-x86库使用 Arduino 兼容的数字来引用模拟输入引脚或 ADC 引脚。因此,模拟输入引脚0被称为14,模拟输入引脚1被称为15,依此类推。由于我们不希望修改代码的其他部分,我们使用类属性来指定必须加到接收到的analog_pin值上以将其转换为wiring-x86模拟引脚编号的数字。
构造函数,即__init__方法,将Board.gpio类属性的引用保存到self.gpio中,并使用接收到的analog_pin和initial_analog_pin_number类属性中指定的值作为其pin参数,以及self.gpio.ANALOG_INPUT作为其mode参数调用其pinMode方法。这样,我们配置引脚为模拟输入引脚,将模拟输入引脚编号转换为wiring-x86兼容的模拟输入引脚编号。wiring-x86库在 GPIO 和模拟 I/O 引脚之间没有区别,我们可以通过Board.gpio类属性来管理它们。
所有的VoltageInput实例都将保存对创建GPIO类实例的同一Board.gpio类属性的引用,特别是具有debug参数设置为False的wiringx86.GPIOGalileoGen2类,以避免为低级通信提供不必要的调试信息。
该类定义了一个voltage属性,该属性调用 GPIO 实例(self.gpio)的analogRead方法以从模拟引脚获取原始值,并将其保存到raw_value变量中。self.analog_pin属性加上initial_analog_pin_number类属性中指定的值指定了analogRead方法调用中的pin值。然后,代码返回raw_value除以1023再乘以5的结果。这样,该属性返回电压值,该值是从analogRead函数返回的原始值转换而来的。
小贴士
不幸的是,wiring-x86库不支持模拟数字转换器的 12 位分辨率。该库使用固定的 10 位分辨率,因此我们只能检测到 1024 个不同的值(2¹⁰ = 1024),或 1024 个单位,其值范围从 0 到 1023(包含),其中 0 代表 0V,1023 代表 5V。因此,我们必须在voltage属性中将原始值除以1023而不是4095。
代码的其余部分与之前示例中使用的相同。不需要对DarknessSensor类、BoardInteraction类或主循环进行更改,因为它们将自动与新VoltageInput类一起工作,并且其构造函数或voltage属性的参数没有发生变化。
以下行将启动与wiring-x86库一起工作的示例的新版本:
python iot_python_chapter_06_04.py
小贴士
我们可以在之前示例中对光敏电阻上的入射光进行相同的更改,以检查我们是否可以使用wiring-x86库实现完全相同的结果。唯一的区别将是检索到的电压值的精度,因为我们在这个情况下使用的是模拟数字转换器的 10 位分辨率。
在本地存储中记录日志
Python 提供了一个强大且灵活的日志 API,由标准库模块提供。我们可以使用日志模块来跟踪在板上运行我们的物联网应用程序时发生的事件,并通过利用本地存储选项将它们保存到日志文件中。
现在,我们将对我们之前使用mraa库工作的示例的最后版本进行更改,以记录从环境光传感器读取的电压值。我们只想在环境光发生变化时记录新的电压值,即当BoardInteraction.darkness_sensor.ambient_light的值发生变化时。我们将使用之前的代码作为基准来添加新的日志功能。示例的代码文件为iot_python_chapter_06_03.py。
我们将替换__main__方法。以下行展示了添加了日志功能的新版本。新的代码行被突出显示,示例的代码文件为iot_python_chapter_06_05.py。
import logging
if __name__ == "__main__":
logging.basicConfig(
filename="iot_python_chapter_06_05.log",
level=logging.INFO,
format="%(asctime)s %(message)s",
datefmt="%m/%d/%Y %I:%M:%S %p")
logging.info("Application started")
last_ambient_light = ""
last_voltage = 0.0
while True:
BoardInteraction.darkness_sensor.measure_light()
new_ambient_light = BoardInteraction.darkness_sensor.ambient_light
if new_ambient_light != last_ambient_light:
# The ambient light value changed
logging.info(
"Ambient light value changed from {0} to {1}".format(
last_voltage, BoardInteraction.darkness_sensor.voltage))
last_ambient_light = new_ambient_light
last_voltage = BoardInteraction.darkness_sensor.voltage
print("Darkness level: {0}".format(new_ambient_light))
BoardInteraction.update_leds_brightness()
# Sleep 2 seconds
time.sleep(2)
第一行调用logging.basicConfig方法来对日志系统进行基本配置。fileName参数指定了我们要用于日志记录的文件名"iot_python_chapter_06_05.log"。由于我们没有为fileMode参数指定值,因此使用默认的'a'模式,连续运行的消息将被追加到指定的日志文件名,即文件不会被覆盖。
小贴士
我们在fileName参数中未指定任何路径,因此,日志文件将在 Python 脚本运行的同一文件夹中创建,即/home/root文件夹。在这种情况下,日志文件将使用启动 Yocto Linux 分发的 microSD 卡上的可用存储空间。
format参数指定了"%(asctime)s %(message)s",因为我们希望存储日期和时间,然后是消息。datefmt参数指定了"%m/%d/%Y %I:%M:%S %p"作为我们希望用于包含在所有附加到日志的行前面的日期和时间格式的日期和时间。我们想要一个简短的日期(月/日/年),后面跟着一个简短的时间(小时/分钟/秒 AM/PM)。我们只想将信息日志记录到文件中,因此,level参数指定了logging.INFO,将根日志记录器级别设置为该值。
下一行调用logging.info方法来记录第一个事件:开始执行的应用程序。在进入循环之前,代码声明了一个新的last_voltage局部变量并将其初始化为0.0。我们希望在环境光改变时记录前一次电压和新的电压值,因此,将最后一次电压保存在新变量中是必要的。当环境光改变时,对logging.info方法的调用将记录从前一次电压到新电压值的转换。然而,非常重要的一点是要注意,当此方法第一次被调用时,前一次电压将等于0.0。下一行将BoardInteraction.darkness_sensor.voltage的值保存到last_voltage变量中。
以下行将启动新版本的示例,该示例将创建iot_python_chapter_06_05.log文件:
python iot_python_chapter_06_05.py
让 Python 脚本运行几分钟,并对光敏电阻上的入射光进行多次更改。这样,你将在日志文件中生成许多行。然后,你可以使用你喜欢的 SFTP 客户端从/home/root下载日志文件并阅读它。
以下行显示了在执行应用程序后生成的日志文件中的某些示例行:
03/08/2016 04:54:46 PM Application started
03/08/2016 04:54:46 PM Ambient light value changed from 0.0 to 4.01953601954
03/08/2016 04:55:20 PM Ambient light value changed from 4.01953601954 to 3.91208791209
03/08/2016 04:55:26 PM Ambient light value changed from 3.91208791209 to 2.49572649573
03/08/2016 04:55:30 PM Ambient light value changed from 2.49572649573 to 3.40903540904
03/08/2016 04:55:34 PM Ambient light value changed from 3.40903540904 to 2.19291819292
03/08/2016 04:55:38 PM Ambient light value changed from 2.19291819292 to 3.83394383394
03/08/2016 04:55:42 PM Ambient light value changed from 3.83394383394 to 4.0
03/08/2016 04:55:48 PM Ambient light value changed from 4.0 to 3.40903540904
03/08/2016 04:55:50 PM Ambient light value changed from 3.40903540904 to 2.89133089133
03/08/2016 04:55:56 PM Ambient light value changed from 2.89133089133 to 3.88278388278
03/08/2016 04:55:58 PM Ambient light value changed from 3.88278388278 to 4.69841269841
03/08/2016 04:56:00 PM Ambient light value changed from 4.69841269841 to 3.93650793651
处理 USB 附加存储
记录与传感器相关事件的日志文件可能会迅速增长,因此,将日志文件存储在 microSD 存储空间可能会成为一个问题。我们可以处理高达 32 GB 的 microSD 卡。因此,一个选项是在更大的 microSD 卡上创建 Yocto Linux 镜像,并继续使用单个存储空间。这将需要我们从默认镜像中扩展分区。另一个选项是利用云服务,只需在我们的本地存储中保留受限制的日志。然而,我们将在稍后处理这个选项。现在,我们想要探索我们使用本地存储的附加选项。
如我们在第一章中学习到的,理解和设置基础物联网硬件,英特尔 Galileo Gen 2 板提供了一个标记为USB HOST的 USB 2.0 主机连接器。我们可以使用此连接器插入 USB 闪存驱动器以进行额外存储,并将日志文件保存到新存储中。
在插入任何 USB 闪存驱动器之前,请在 SSH 终端中运行以下命令以列出分区表:
fdisk -l
以下行显示了由上一个命令生成的输出示例。您的输出可能不同,因为它取决于您用于引导 Yocto Linux 的微 SD 卡。请注意,/dev/mmcblk0磁盘标识了微 SD 卡,并且您有两个分区:/dev/mmcblk0p1和/dev/mmcblk0p2。
Disk /dev/mmcblk0: 7.2 GiB, 7746879488 bytes, 15130624 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x000a69e4
Device Boot Start End Blocks Id System
/dev/mmcblk0p1 * 2048 106495 52224 83 Linux
/dev/mmcblk0p2 106496 2768895 1331200 83 Linux
现在,我们将把一个 USB 闪存驱动器插入到板载的 USB 2.0 主机连接器,我们将运行必要的命令来挂载它,然后我们将修改代码以将日志保存到 USB 闪存驱动器内的一个文件夹中。您需要一个与 USB 2.0 兼容的预格式化 USB 闪存驱动器来运行此示例。
以下图片显示了连接到板载 USB 2.0 主机连接器的 USB 闪存驱动器,标记为USB HOST。插入 USB 闪存驱动器后,请等待几秒钟。
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_06_08.jpg
Yocto Linux 将在/dev文件夹中添加一个新的块设备。在 SSH 终端中运行以下命令以列出分区表:
fdisk -l
以下行显示了由上一个命令生成的输出示例。您的输出可能不同,因为它取决于您使用的 USB 驱动器和微 SD 卡。将输出与您在插入 USB 闪存驱动器之前执行相同命令时生成的输出进行比较。附加的行提供了有关 USB 闪存驱动器、其磁盘名称和其分区的信息。突出显示的行显示了 USB 分区的详细信息,标识为/dev/sda磁盘和 FAT32 分区/dev/sda1。我们将使用此分区名称作为我们下一步操作之一。
Disk /dev/mmcblk0: 7.2 GiB, 7746879488 bytes, 15130624 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x000a69e4
Device Boot Start End Blocks Id System
/dev/mmcblk0p1 * 2048 106495 52224 83 Linux
/dev/mmcblk0p2 106496 2768895 1331200 83 Linux
Disk /dev/sda: 3.8 GiB, 4026531840 bytes, 7864320 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x02bb0a1a
Device Boot Start End Blocks Id System
/dev/sda1 * 64 7864319 3932128 b W95 FAT32
现在,有必要创建一个挂载点。我们必须在/media文件夹中创建一个新的子文件夹。我们将使用usb作为子文件夹的名称,因此我们将挂载驱动器的文件夹将是/media/usb。运行以下命令以创建文件夹:
mkdir /media/usb
运行以下命令在最近创建的/media/usb文件夹中挂载分区。在之前的步骤中,我们检索了分区名称,它被命名为/dev/sda1。您的分区名称可能不同,因此您只需将/dev/sda1替换为您在执行列出磁盘及其分区的 fdisk 命令时列出的分区名称即可。
mount /dev/sda1 /media/usb
现在,我们可以通过/media/usb文件夹访问 USB 闪存驱动器的内容,也就是说,无论何时我们在该文件夹中创建文件夹或文件,我们都是在向 USB 闪存驱动器分区写入。
运行以下命令以创建一个新的/media/usb/log文件夹,我们将在此文件夹中存储我们的物联网应用的日志:
mkdir /media/usb/log
现在,我们将更改在__main__方法中调用logging.basicConfig方法时传递给文件名参数的值。我们想在/media/usb/log文件夹中保存日志文件。这样,我们将它存储在 USB 闪存驱动器的log文件夹中。我们将使用之前的代码作为基准来更改日志文件名及其路径。示例代码文件是iot_python_chapter_06_05.py。
以下行显示了调用logging.basicConfig方法的新代码和示例代码文件iot_python_chapter_06_06.py。其余的代码与我们在前面的示例中使用的一样。
import logging
if __name__ == "__main__":
logging.basicConfig(
filename="/media/usb/log/iot_python_chapter_06_06.log",
level=logging.INFO,
format="%(asctime)s %(message)s",
datefmt="%m/%d/%Y %I:%M:%S %p")
以下行将启动新版本的示例,该示例将在/media/usb/log文件夹中创建iot_python_chapter_06_06.log文件:
python iot_python_chapter_06_06.py
保持 Python 脚本运行几分钟,并对光敏电阻上的环境光进行多次更改。这样,你将在日志文件中生成多行。然后,你可以使用你喜欢的 SFTP 客户端从/media/usb/log下载日志文件并阅读它。然而,别忘了在你的 SFTP 客户端中返回到home/root文件夹,因为这是上传 Python 脚本的那个文件夹。
如果你需要将 USB 闪存驱动器拔出以连接到另一台计算机或设备,首先你必须中断 Python 脚本的执行,然后,你必须运行以下命令来卸载分区。在前面的步骤中,我们检索了分区名称,它被命名为/dev/sda1。你的分区名称可能不同,因此,你只需将/dev/sda1替换为你执行列出磁盘及其分区的fdisk命令时列出的分区名称。请小心,并确保你在运行 Yocto Linux 上的 shell 的终端上运行此命令。在执行之前,请确保你看到root@galileo:~#提示符。如果你在运行 Linux 或 OS X 的计算机上运行此命令,你可能会卸载你的一个驱动器。
umount /dev/sda1
现在,你可以从 USB 2.0 主机连接器中拔出 USB 闪存驱动器。
测试你的知识
-
Intel Galileo Gen 2 板为模拟数字转换器提供以下分辨率:
-
32 位。
-
64 位。
-
12 位。
-
-
模拟引脚允许我们检测的最大值:
-
4096 个不同的值,值的范围从 0 到 4095(包含)。
-
16384 个不同的值,值的范围从 0 到 16383(包含)。
-
256 个不同的值,值的范围从 0 到 255(包含)。
-
-
我们可以通过调用
mraa.Aio实例的以下方法来配置我们想要使用的位数作为分辨率:-
setADCResolution。 -
setBit。 -
setResolutionBits。
-
-
对
mraa.Aio实例的read方法的调用返回:-
基于为实例配置的分辨率位数的一个原始单位数。
-
一个从原始单位数自动转换的电压值。
-
一个以欧姆(Ω)为单位的电阻值。
-
-
我们可以使用模拟引脚来读取:
-
电阻值。
-
电流值。
-
电压值。
-
摘要
在本章中,我们学习了如何使用模拟输入来测量电压值。我们理解了模拟数字转换器不同位分辨率的影响,并编写了将读取的原始单位转换为电压值的代码。
我们使用模拟引脚和 mraa 以及 wiring-x86 库来测量电压。我们能够将可变电阻转换成电压源,并使其能够通过模拟输入、光敏电阻和分压器来测量黑暗程度。
与前几章一样,我们继续利用 Python 的面向对象特性,并创建了类来封装电压输入、光敏传感器以及必要的配置,使用 mraa 和 wiring-x86 库。我们的代码易于阅读和理解,并且我们可以轻松切换底层低级库。
当环境光线变化时,我们触发了动作,并且能够同时处理模拟输入和模拟输出。最后,我们通过利用 Python 标准库中包含的日志功能来注册事件。我们还学会了利用英特尔 Galileo Gen 2 板中包含的 USB 2.0 主机连接器来插入 USB 闪存驱动器并将其用作额外的存储。
现在我们能够以不同的方式和配置读取模拟输入,这使得我们的物联网设备能够读取由环境变化产生的模拟值,我们可以与更多种类的传感器一起工作,从现实世界获取数据,这是下一章的主题。
第七章. 使用传感器从现实世界获取数据
在本章中,我们将与各种传感器一起工作,以从现实世界获取数据。我们将涵盖以下主题:
-
理解传感器及其连接类型
-
学习在选择传感器时必须考虑的最重要事项
-
利用
upm库与多种不同的传感器一起工作 -
使用加速度计测量适当的加速度或 g 力的幅度和方向
-
使用三轴模拟加速度计
-
使用与 I²C 总线兼容的数字加速度计
-
使用
mraa库和 I²C 总线控制数字加速度计 -
使用模拟传感器测量环境温度
-
使用与 I²C 总线兼容的数字温度和湿度传感器
理解传感器及其连接类型
在第六章,使用模拟输入和本地存储,我们使用了一个包含在分压器中的光敏电阻,并将其连接到模拟输入引脚。我们能够测量环境光,并确定不同的暗度级别,并改变 RGB LED 的亮度级别。光敏电阻,也称为LDR(Light-Dependent Resistor的缩写)或光电池,是一种传感器。我们只需要将其包含在分压器中,就可以通过环境光改变光敏电阻的电阻值。这些电阻值的变动将在我们的模拟引脚中产生电压值的变动。因此,我们与一个电子组件配置一起工作,该配置生成一个模拟传感器,能够将环境光的变动转换为电压值。
有大量的传感器使我们能够从现实世界获取数据并将其转换为我们可以通过 Intel Galileo Gen 2 板上的不同通信端口收集的模拟或数字值,并用 Python 和不同的库进行处理。当我们使用光敏电阻来测量环境光时,我们将配置连接到模拟引脚,并使用mraa库然后是wiring-x86库来利用模数转换器来获取值。
在第二章,在 Intel Galileo Gen 2 上使用 Python,我们安装了最新可用的upm库。这个库为传感器和执行器提供了高级接口。每次我们与传感器一起工作时,通常都方便检查upm库是否支持它,因为高级接口可以为我们节省大量时间,并使我们从传感器获取值以及进行必要的单位转换变得更加容易。
在本章中,我们将利用具有许多不同传感器的upm库。然而,我们必须考虑到,有时upm库中为特定传感器提供的功能可能不足以满足需求,我们可能需要编写自己的底层代码,使用mraa或wiring-x86库与传感器进行交互。正如我们稍后将要分析的,根据连接类型,当传感器在upm库中不受支持时,只有mraa会为我们提供所有必要的功能。
显然,在选择传感器时,我们必须首先考虑我们想要测量的内容,例如温度。然而,这并不是我们选择特定传感器时唯一需要考虑的因素。当我们选择传感器时,我们必须考虑它们的功能、测量范围、精度以及连接类型等众多因素。以下列表列举了我们必须考虑的最重要的因素及其解释:
-
与英特尔 Galileo Gen 2 板和我们所使用的电压供应(5V 或 3.3V)的兼容性:有时,我们必须将多个传感器连接到板上,因此检查我们选择的传感器是否都能与板上的电压配置兼容是很重要的。一些传感器只有在我们有特定设置的情况下才能与板一起工作。
-
功耗:我们必须考虑到一些传感器具有不同的工作模式。例如,一些传感器具有高性能模式,需要比正常模式更多的电力。由于我们可能会将多个传感器连接到板上,因此考虑所有传感器连接到板上并使用我们将在其中使用的模式时的整体功耗也很重要。此外,一些传感器在我们不使用它们时,会切换到省电模式。
-
连接类型:为了决定最方便的连接类型,我们需要回答几个问题。我们是否有必要的连接、通信或接口端口?它们是否可用?连接类型和我们需要的距离是否会影响测量值的准确性?此外,当我们为我们的板选择第一个传感器时,所有连接可能都是可用的,但随着我们添加更多传感器,情况会发生变化,这可能会迫使选择具有不同连接类型的传感器。让我们考虑以下情况,我们已经在 6 个不同的位置测量环境光线。我们有 6 个光电电阻,通过 6 个分压器配置连接,并连接到 6 个可用的模拟输入引脚,因此我们没有额外的模拟引脚可用。如果我们必须添加温度传感器,我们不能添加需要模拟输入引脚的模拟传感器,因为它们都已经连接到光线传感器。在这种情况下,我们必须使用可以连接到 I²C 或 SPI 总线的数字温度传感器。另一个选择是使用可以连接到 UART 端口的数字温度传感器。我们将在稍后深入探讨传感器的不同连接类型。
-
测量范围:传感器的规格表明了它们的测量范围。例如,测量环境温度的温度传感器可以有一个测量范围从-40ºF 到 185ºF(相当于-40ºC 到 85ºC)。如果我们需要测量可以达到 90ºC 的环境温度,我们必须选择一个具有更高上限范围的温度传感器。例如,另一个测量环境温度的传感器提供的测量范围是-40ºF 到 257ºF(相当于-40ºC 到 125ºC),将适合这项工作。
-
灵敏度和精度:每个传感器都是灵敏的,可能提供不同的可配置精度级别。我们必须确保传感器提供的精度符合我们的需求。随着测量值的改变,考虑灵敏度,也称为测量分辨率,是很重要的。例如,如果我们必须测量温度,并且必须能够根据我们使用的单位确定至少 2ºF 或 1ºC 的变化,我们必须确保传感器能够提供所需灵敏度。
小贴士
当我们开始选择合适的传感器时,分析测量范围、灵敏度和精度时注意单位非常重要。一个典型的例子是温度传感器,它可以以摄氏度(ºC)或华氏度(ºF)来表示数值。
-
延迟: 确定我们能够等待传感器收集新值的时间以及它是否能够在这么短的时间内提供真实的新值非常重要。当我们在真实环境或测量对象中测量的测量值发生变化时,传感器需要一些时间才能提供新的测量值。有时,这些可能是微秒,但在其他情况下,它们可以是毫秒甚至秒。这取决于传感器,我们在选择适合我们项目的传感器时必须考虑这一点。例如,我们可能需要一个温度传感器,每秒允许我们测量 2 个温度值,因此,我们必须与延迟低于 500 毫秒(0.5 秒)的传感器合作以达到我们的目标。具体来说,我们可以选择延迟为 200 毫秒的温度传感器。不幸的是,有时我们必须深入研究数据表来检查某些传感器及其使用的电子组件的延迟值。
-
工作范围和特殊环境要求: 考虑传感器的工作范围非常重要。有时,传感器必须在工作于特定环境条件下,而这些条件可能不适合所有可用的传感器。以下是一些粗略的环境要求示例:高抗冲击性、防水、极高温度和非常高的湿度水平。
-
尺寸: 传感器具有不同的尺寸。有时只有特定的尺寸适合我们的项目。
-
协议、upm 库的支持和 Python 绑定: 我们最终将使用 Python 代码处理从传感器获取的数据,因此,确保我们可以在 Python 中使用传感器非常重要。在某些情况下,我们不希望编写底层代码,并确保传感器在
upm库中得到支持。在其他情况下,我们必须确保我们有必要的 Python 库来处理某些数字传感器使用的协议。例如,许多使用 UART 端口的温度传感器使用 MODBUS 串行通信协议。如果它们在upm库中没有得到支持,我们必须使用特定的 Python 库来通过 MODBUS 串行通信协议建立通信,这可能需要我们在没有先前使用此协议的经验的情况下进行额外的工作。 -
成本: 显然,我们必须考虑传感器的成本。可能符合我们所有要求的最佳传感器非常昂贵,我们可能决定使用功能较少或精度较低但成本较低的另一种传感器。我们有大量具有令人印象深刻的功能且与英特尔 Galileo Gen 2 板兼容的廉价传感器。然而,我们始终必须考虑每个传感器的成本,以便根据我们的需求和预算进行选择。
我们可以将传感器或模块连接到英特尔 Galileo Gen 2 板上,可以使用以下连接类型。列表列出了制造商通常用来描述模块连接类型的缩写及其解释:
-
AIO:该模块需要一个或多个模拟输入引脚。需要模拟输入引脚的传感器被称为模拟传感器。
-
GPIO:该模块需要一个或多个 GPIO 引脚。
-
I²C:该模块需要两根线连接到两个 I²C 总线线:SCL(代表 Serial CLock)和 SDA(代表 Serial DAta)。只要每个设备都有一个不同的 I²C 地址,我们就可以将许多设备连接到这个总线。
-
SPI:该模块需要三根线连接到三个 SPI 总线线:MISO(代表 Master In Slave Out)、MOSI(代表 Master Out Slave In)和 SCK(代表 Serial Clock)。
-
UART:该模块使用串行连接(RX/TX),因此需要两根线连接到 UART 端口的两个引脚:TX->1 和 RX<-0。UART 端口代表 通用异步接收/发送器。
与 I²C 总线、SPI 总线或 UART 端口一起工作的模块被称为 数字传感器,因为它们使用数字接口。一些模块将其中一个总线或 UART 端口与 GPIO 引脚结合使用。
我们已经使用 mraa 和 wiring-x86 库处理了模拟输入和模拟数字转换器。我们还使用这些库处理了配置为输入引脚的 GPIO 引脚。然而,我们还没有处理 I²C 总线、SPI 总线或 UART 端口。
mraa 库提供了以下类,允许我们与之前提到的串行总线和 UART 端口一起工作:
-
mraa.I2c:该类表示一个 I²C 总线主设备(板),可以通过选择它们的地址与多个 I²C 总线从设备进行通信。我们可以创建许多此类实例来与多个从设备进行交互。该类允许我们将数据写入和从连接到 I²C 总线的从设备读取数据。 -
Mraa.Spi:该类表示 SPI 总线和其芯片选择。该类允许我们将数据写入和从连接到 SPI 总线的设备读取数据。 -
mraa.UART:该类表示 UART 端口,并允许我们配置、向 UART 端口发送数据并从 UART 端口接收数据。
提示
我们可以使用之前解释过的由 mraa 库提供的类来与任何数字模块进行交互。然而,这需要我们花一些时间阅读模块的数据表,了解它们的工作模式,编写代码将数据写入和从适当的总线或 UART 端口读取。每个模块都有自己的 API,我们必须通过串行总线或 UART 端口来组合请求和处理响应。
首先,我们将利用每个模块的upm库。在少数情况下,我们还将使用mraa库中的适当类来了解如何使用低级接口与传感器交互。这样,如果我们必须与upm库不支持模块一起工作,我们可以分析数据表提供的信息,并编写代码与模块交互。
与加速度计一起工作
加速度计使我们能够测量加速度或 g 力的幅度和方向。平板电脑和智能手机使用加速度计根据我们握持设备的方向自动在纵向和横向模式之间切换。此外,内置的加速度计允许我们通过在设备的不同方向上用不同强度的微小动作来控制应用程序。
加速度计使我们能够通过测量由于重力产生的加速度来检测物体相对于地球表面的方向。此外,当我们想要检测物体开始或停止移动时,加速度计非常有用。加速度计还能够检测振动和物体下落。
小贴士
加速度计通常以 g 力为单位测量加速度,缩写为g。重要的是要避免由单位名称中包含的力这个词引起的混淆,因为我们测量的是加速度而不是力。一些加速度计使用每秒平方米(m/s²)作为它们的测量单位而不是 g 力。
现在,大多数加速度计能够测量三个轴的加速度,被称为三轴加速度计或三轴加速度计。一个三轴加速度计可以测量x、y和z轴的加速度。如果我们想测量小的加速度或振动,使用小范围的三轴加速度计会更方便,因为它们提供了必要的灵敏度。
将模拟加速度计连接到模拟输入引脚
理解加速度计工作原理的最简单方法是在一个简单的例子中使用它。现在,我们将使用一个具有从-3g 到+3g 的全量程传感范围的模拟三轴加速度计。这种加速度计需要三个模拟输入引脚,每个测量轴一个。加速度计根据每个轴测量的加速度提供电压级别。
我们将使用标记为A0、A1和A2的三个模拟引脚来连接模拟加速度计断开板的正电压输出。完成必要的布线后,我们将编写 Python 代码来测量和显示三个轴(x、y 和 z)的加速度。这样,我们将读取将模拟值转换为其数字表示的结果,并将其映射到加速度值。
我们需要一个 SparkFun 三轴加速度计扩展板 ADXL335 来与这个示例一起使用。以下 URL 提供了有关此扩展板的详细信息:www.sparkfun.com/products/9269。该扩展板集成了来自 Analog Devices 的 ADXL335 加速度计传感器。
提示
供应给扩展板的电源应在 1.8VDC 至 3.6VDC 之间,因此我们将使用标记为3V3的电源引脚作为电源,以确保我们提供 3.3V,并且我们永远不会向扩展板提供5V。
还可以使用 Seeedstudio Grove 3 轴模拟加速度计与这个示例一起工作。以下 URL 提供了有关此模块的详细信息:www.seeedstudio.com/depot/Grove-3Axis-Analog-Accelerometer-p-1086.html。如果您使用此模块,您可以使用标记为3V3或5V的电源引脚作为电源,因为扩展板能够与 3V 至 5V 的电压供应一起工作。全量程与 SparkFun 扩展板相同,并且两者使用相同的加速度计传感器。布线对两个模块都是兼容的。
以下图示显示了 SparkFun 三轴加速度计扩展板 ADXL335、必要的布线以及从 Intel Galileo Gen 2 板到面包板的布线。示例的 Fritzing 文件为iot_fritzing_chapter_07_01.fzz,以下图片是面包板视图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_07_01.jpg
以下图片显示了用电子元件表示的符号的原理图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_07_02.jpg
如前图所示,我们有以下连接:
-
标记为A0的模拟输入引脚连接到标记为X(在扩展板符号中为XOUT)的加速度计输出引脚
-
标记为A1的模拟输入引脚连接到标记为Y(在扩展板符号中为YOUT)的加速度计输出引脚
-
标记为A2的模拟输入引脚连接到标记为Z(在扩展板符号中为ZOUT)的加速度计输出引脚
-
标记为3V3的电源引脚连接到标记为VCC的加速度计电源引脚
-
标记为GND的接地引脚连接到标记为GND的加速度计接地引脚
现在,是时候进行所有必要的布线了。在添加或移除任何线从板上的引脚之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从 Intel Galileo Gen 2 板上拔掉电源供应。确保你使用粗线,这样你就可以在不意外拔掉电缆的情况下将加速度计扩展板移动到不同的方向。
使用模拟加速度计测量三轴加速度
upm 库在 pyupm_adxl335 模块中包括对三个轴模拟加速度计扩展板的支撑。在此模块中声明的 ADXL335 类代表连接到我们板上的三个轴模拟加速度计。该类使得校准加速度计并将从模拟输入读取的原始值转换为以 g 单位表示的值变得容易。
我们将创建一个新的 Accelerometer 类来表示加速度计,并使我们能够更容易地检索加速度值,而无需担心与 ADXL335 类的实例一起工作时必要的类型转换。我们将使用 ADXL335 类与加速度计交互。以下行显示了与 upm 库(特别是 pyupm_adxl335 模块)一起工作的新 Accelerometer 类的代码。示例代码文件为 iot_python_chapter_07_01.py。
import pyupm_adxl335 as upmAdxl335
import time
class Accelerometer:
def __init__(self, pinX, pinY, pinZ):
self.accelerometer = upmAdxl335.ADXL335(
pinX, pinY, pinZ)
self.accelerometer.calibrate()
self.x_acceleration_fp = upmAdxl335.new_floatPointer()
self.y_acceleration_fp = upmAdxl335.new_floatPointer()
self.z_acceleration_fp = upmAdxl335.new_floatPointer()
self.x_acceleration = 0.0
self.y_acceleration = 0.0
self.z_acceleration = 0.0
def calibrate(self):
self.accelerometer.calibrate()
def measure_acceleration(self):
# Retrieve the acceleration values for the three axis
self.accelerometer.acceleration(
self.x_acceleration_fp,
self.y_acceleration_fp,
self.z_acceleration_fp)
self.x_acceleration = upmAdxl335.floatPointer_value(
self.x_acceleration_fp)
self.y_acceleration = upmAdxl335.floatPointer_value(
self.y_acceleration_fp)
self.z_acceleration = upmAdxl335.floatPointer_value(
self.z_acceleration_fp)
在创建 Accelerometer 类的实例时,我们必须指定每个轴引脚连接到的模拟引脚编号,所需的 pinX、pinY 和 pinZ 参数。构造函数,即 __init__ 方法,使用接收到的 pinX、pinY 和 pinZ 参数创建一个新的 upmAdxl335.ADXL335 实例,并将其引用保存到 accelerometer 属性中。
upmAdxl335.ADXL335 实例需要使用浮点指针来检索三个轴的加速度值。因此,构造函数通过调用 upmAdxl335.new_floatPointer() 将三个 float * 类型(浮点指针)的对象保存到以下三个属性中。
-
x_acceleration_fp -
y_acceleration_fp -
z_acceleration_fp
最后,构造函数创建并初始化三个属性为 0.0:x_acceleration、y_acceleration 和 z_acceleration。构造函数执行后,我们必须校准加速度计,然后,我们将准备好检索三个轴的加速度值:x、y 和 z。
该类定义了以下两个方法:
-
calibrate:调用self.accelerometer的校准方法来校准模拟加速度计。 -
measure_acceleration:检索三个轴的加速度值并将它们保存到以下三个属性中:x_acceleration、y_acceleration和z_acceleration。加速度值以重力加速度(g)表示。首先,代码调用self.accelerometer的acceleration方法,并将三个float *类型的对象作为参数。该方法读取从三个模拟引脚获取的原始值,将它们转换为适当的重力加速度(g)值,并使用更新后的值更改接收到的float*类型对象的浮点值。然后,代码调用upmAdxl335.floatPointer_value方法从float*类型的对象中检索浮点值,并更新三个属性:x_acceleration、y_acceleration和z_acceleration。
现在,我们将编写一个循环,该循环将运行校准,每 500 毫秒检索并显示三个轴的加速度值(以 g 力表示),即每秒两次。示例的代码文件为iot_python_chapter_07_01.py。
if __name__ == "__main__":
# The accelerometer is connected to analog pins A0, A1 and A2
# A0 -> x
# A1 -> y
# A2 -> z
accelerometer = Accelerometer(0, 1, 2)
# Calibrate the accelerometer
accelerometer.calibrate()
while True:
accelerometer.measure_acceleration()
print("Acceleration for x: {0}g".format(accelerometer.x_acceleration))
print("Acceleration for y: {0}g".format(accelerometer.y_acceleration))
print("Acceleration for z: {0}g".format(accelerometer.z_acceleration))
# Sleep 0.5 seconds (500 milliseconds)
time.sleep(0.5)
第一行创建了一个之前编写的Accelerometer类的实例,其中pinX、pinY和pinZ的值分别为0、1和2。这样,实例将从标有A0、A1和A2的引脚读取模拟值。然后,代码调用Accelerometer实例的calibrate方法来校准模拟加速度计。
小贴士
校准测量传感器静止时的x、y和z轴值,然后,传感器使用这些值作为零值,即作为基线。此模拟传感器的默认灵敏度是 0.25V/g。
然后,代码将无限循环运行,即直到你通过按Ctrl + C或停止按钮中断执行,如果你使用具有远程开发功能的 Python IDE 来在板上运行代码。循环调用measure_acceleration方法来更新加速度值,然后以 g 力(g)的形式打印它们。
以下行将启动示例。别忘了你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux。在开始示例之前,请确保加速度计扩展板位于稳定且不震动的表面上。这样,校准才能正常工作。
python iot_python_chapter_07_01.py
运行示例后,执行以下操作:
-
以不同的方向对加速度计扩展板进行小幅度移动
-
在特定方向上对加速度计扩展板进行大幅度移动
-
将加速度计扩展板放置在稳定且不震动的表面上
由于之前的操作,你将看到三个轴测量的不同加速度值。以下行显示了当我们对扩展板进行大幅度移动时生成的某些示例输出行:
Acceleration for x: 0.0g
Acceleration for y: 0.4296875g
Acceleration for z: 0.0g
Acceleration for x: 0.0g
Acceleration for y: 0.52734375g
Acceleration for z: 0.0g
Acceleration for x: 0.0g
Acceleration for y: 0.60546875g
Acceleration for z: 0.0g
Acceleration for x: 0.01953125g
Acceleration for y: 0.68359375g
Acceleration for z: 0.0g
将数字加速度计连接到 I²C 总线
数字加速度计通常比模拟加速度计提供更好的精度、更高的分辨率和更高的灵敏度。现在,我们将使用一个从-16g 到+16g 的全量程数字 3 轴加速度计。我们将使用一个使用 I²C 总线来允许板与加速度计通信的扩展板。
我们将使用标有SDA和SCL的两个引脚将 I²C 总线的数据线和时钟线连接到数字加速度计扩展板上的相应引脚。完成必要的布线后,我们将编写 Python 代码来测量和显示三个轴(x、y和z)的加速度。这样,我们将读取通过 I²C 总线发送到加速度计的命令的结果,读取响应并将它们解码为以 g 力(g)表示的适当的加速度值。
我们需要 SparkFun 三轴加速度计扩展板 ADXL345 来与这个示例一起工作。以下网址提供了关于此扩展板的详细信息:www.sparkfun.com/products/9836。该扩展板集成了来自 Analog Devices 的 ADXL345 数字加速度计传感器,并为 SPI 和 I²C 总线提供支持。在这种情况下,我们将仅使用 I²C 总线。
小贴士
供应给扩展板的电源应在 2.0VDC 至 3.6VDC 之间,因此,我们必须使用标有3V3的电源引脚作为电源,以确保我们提供 3.3V,并且我们永远不会向扩展板提供5V。
也可以使用 Seeedstudio Grove 3 轴数字加速度计与这个示例一起工作。以下网址提供了关于此模块的详细信息:www.seeedstudio.com/depot/Grove-3Axis-Digital-Accelerometer16g-p-1156.html。如果您使用此模块,可以使用标有3V3或5V的电源引脚作为电源,因为扩展板能够与 3V 至 5V 的电压供应一起工作。全量程与 SparkFun 扩展板相同,并且两者都使用相同的加速度计传感器。这两款模块的布线是兼容的。
小贴士
Seeedstudio Grove 3 轴数字加速度计已准备好使用电缆插入 Grove 底板。Grove 底板是一块可以插入您的 Intel Galileo Gen 2 板的板子,它提供了数字、模拟和 I²C 端口,您可以使用适当的电缆轻松地将 Grove 传感器连接到底层的 Intel Galileo Gen 2 板。在我们的示例中,我们不会使用 Grove 底板,我们将继续使用布线连接每个不同的传感器。但是,如果您决定使用 Grove 底板与 Grove 传感器结合使用,您将获得相同的结果。我们将在下一个示例中使用的其他 Grove 传感器也将准备好与 Grove 底板一起工作。Grove 底板的最新版本是 V2,您可以在以下网址中获取更多关于它的信息:www.seeedstudio.com/depot/Base-Shield-V2-p-1378.html
以下图示展示了 Seeedstudio Grove 3 轴数字加速度计扩展板 ADXL345,必要的布线以及从 Intel Galileo Gen 2 板到面包板的布线。示例的 Fritzing 文件为iot_fritzing_chapter_07_02.fzz,以下图片是面包板视图。
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_07_03.jpg
以下图片显示了用符号表示电子组件的原理图。
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_07_04.jpg
如前图所示,我们有以下连接:
-
SDA引脚连接到标有SDA的加速度计引脚。这样,我们将数字加速度计连接到 I²C 总线的串行数据线。Intel Galileo Gen 2 板上的SDA引脚连接到标有A4的模拟输入引脚,因此,板上的符号使用A4/SDA标签。标有SDA的引脚与标有A4的引脚位于不同的位置,但它们在内部是连接的。
-
SCL引脚连接到标有SCL的加速度计引脚。这样,我们将数字加速度计连接到 I²C 总线的串行时钟线。Intel Galileo Gen 2 板上的SCL引脚连接到标有A5的模拟输入引脚,因此,板上的符号使用A5/SCL标签。标有SCL的引脚与标有A5的引脚位于不同的位置,但它们在内部是连接的。
-
标有5V的电源引脚连接到标有VCC的加速度计电源引脚。如果你使用 SparkFun 三轴加速度计扩展板 ADXL345,标有3V3的电源引脚连接到加速度计的电源引脚VCC。
-
标有GND的接地引脚连接到标有GND的加速度计接地引脚。
现在,是时候进行所有必要的接线了。不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并在从板上的引脚添加或移除任何电线之前,从 Intel Galileo Gen 2 板上拔掉电源。就像你使用模拟加速度计时做的那样,确保你使用粗线,这样你就可以在不意外拔掉电缆的情况下将加速度计扩展板移动到不同的方向。
使用数字加速度计测量三个轴的加速度
upm库在pyupm_adxl345模块中包括了对三个轴数字加速度计扩展板 ADXL345 的支持。在此模块中声明的Adxl345类代表基于 ADXL345 传感器的三个轴数字加速度计,连接到我们的板上。该类使得初始化传感器、通过 I²C 总线更新和检索三个轴的加速度值变得容易。该类在幕后与mraa.I²C类一起工作,与传感器通信,即向 ADXL345 传感器写入数据并从中读取数据,该传感器作为连接到 I²C 总线的从设备。
小贴士
不幸的是,upm库中的每个模块都不遵循我们应期望的 Python 代码的相同命名约定。例如,在我们之前的例子中,那个类名是ADXL335,使用大写字母,而在这个例子中,类名是Adxl345。
我们将创建Accelerometer类的新版本来表示加速度计,并使我们能够更容易地检索加速度值,而无需在处理Adxl345类的实例时担心特定的方法和数组。我们将使用Adxl345类与加速度计交互。以下行显示了与upm库一起工作的新Accelerometer类的代码,特别是与pyupm_adxl345模块一起工作。示例代码文件为iot_python_chapter_07_02.py。
import pyupm_adxl345 as upmAdxl345
import time
class Accelerometer:
def __init__(self, bus):
self.accelerometer = upmAdxl345.Adxl345(bus)
self.x_acceleration = 0.0
self.y_acceleration = 0.0
self.z_acceleration = 0.0
def measure_acceleration(self):
# Update the acceleration values for the three axis
self.accelerometer.update()
# Retrieve the acceleration values for the three axis
acceleration_array = \
self.accelerometer.getAcceleration()
self.x_acceleration = acceleration_array[0]
self.y_acceleration = acceleration_array[1]
self.z_acceleration = acceleration_array[2]
在创建Accelerometer类的实例时,我们需要指定数字加速度计连接的 I²C 总线编号,这需要在bus必需参数中指定。构造函数,即__init__方法,使用接收到的bus参数创建一个新的upmAdxl345.Adxl345实例,并将其引用保存在accelerometer属性中。
upmAdxl345.Adxl345实例需要与三个轴的加速度值检索的浮点指针数组一起工作。我们希望使用易于理解的属性,因此构造函数创建并初始化了三个属性,其值为0.0:x_acceleration、y_acceleration和z_acceleration。构造函数执行后,我们有一个初始化的数字加速度计,可以检索三个轴的加速度值:x、y和z。
该类定义了一个measure_acceleration方法,该方法更新传感器中三个轴的加速度值,从传感器检索这些加速度值,并将其最终保存在以下三个属性中:x_acceleration、y_acceleration和z_acceleration。加速度值以 g 力(g)表示。
首先,measure_acceleration方法内的代码调用self.accelerometer的update方法,请求传感器更新读取值。然后,代码调用self.accelerometer的getAcceleration方法,检索三个轴的加速度值,并将返回的数组保存在局部变量acceleration_array中。数组中的第一个元素包含 x 轴的加速度值,第二个为 y 轴,第三个为 z 轴。因此,代码使用acceleration_array数组中的值更新以下三个属性:x_acceleration、y_acceleration和z_acceleration。这样,我们可以通过访问适当的属性而不是处理可能导致混淆的数组元素来轻松访问每个加速度值。
现在,我们将编写一个循环,该循环将每 500 毫秒运行一次校准,检索并显示三个轴的加速度值(以 g 力g表示),即每秒两次。示例代码文件为iot_python_chapter_07_02.py。
if __name__ == "__main__":
accelerometer = Accelerometer(0)
while True:
accelerometer.measure_acceleration()
print("Acceleration for x: {:5.2f}g".
format(accelerometer.x_acceleration))
print("Acceleration for y: {:5.2f}g".
format(accelerometer.y_acceleration))
print("Acceleration for z: {:5.2f}g".
format(accelerometer.z_acceleration))
# Sleep 0.5 seconds (500 milliseconds)
time.sleep(0.5)
第一行创建了一个之前编写的Accelerometer类的实例,其中bus参数的值为0。mraa.I2c类识别出我们用编号0连接加速度计的 I²C 总线。这样,该实例将通过 I²C 总线与数字加速度计建立通信。英特尔 Galileo Gen 2 板是总线上的主设备,而数字加速度计,以及连接到该总线的任何其他设备,都充当从设备。
然后,代码运行一个无限循环,调用measure_acceleration方法来更新加速度值,然后以 g 力(g)为单位打印它们。
以下行将开始示例:
python iot_python_chapter_07_02.py
在运行示例之后,执行与上一个示例中相同的操作。这些操作的结果,您将看到三个轴测量的不同加速度值。以下行展示了当我们用分线板进行小幅度移动时生成的某些示例输出行:
Acceleration for x: 0.000g
Acceleration for y: 0.056g
Acceleration for z: 0.000g
Acceleration for x: 0.000g
Acceleration for y: 0.088g
Acceleration for z: 0.000g
Acceleration for x: 0.000g
Acceleration for y: 0.872g
Acceleration for z: 0.056g
使用 mraa 库通过 I²C 总线控制数字加速度计
有时,upm库中针对特定传感器的功能可能不包括其所有可能的用法和配置。我们之前示例中使用的upmAdxl345.Adxl345类就是这种情况的一个例子。此类不允许我们配置加速度计所需的比例,而传感器支持以下四个可选测量范围:±2g、±4g、±8g 和±16g。如果我们想使用upm模块中未包含的特定功能,我们可以使用适当的mraa类与传感器交互,在这种情况下,我们可以使用mraa.I2c通过 I²C 总线控制数字加速度计。
我们将使用 upm 模块的 C++源代码作为基准来编写我们自己的 Python 代码,该代码通过mraa.I2c类通过 I²C 总线控制加速度计。C++源代码文件是adxl1345.cxx,可以在以下 GitHub URL 中找到:github.com/intel-iot-devkit/upm/blob/master/src/adxl345/adxl345.cxx。由于我们使用 C++源代码作为基准,我们将使用相同的命名约定(大写字母)来声明#define中的常量,但我们将它们转换为类属性。
以下行展示了用于与mraa.I2c类实例通信以与数字加速度计交互的新Adxl1345类的代码。示例的代码文件为iot_python_chapter_07_03.py。
class Adxl345:
# Read buffer length
READ_BUFFER_LENGTH = 6
# I2C address for the ADXL345 accelerometer
ADXL345_I2C_ADDR = 0x53
ADXL345_ID = 0x00
# Control registers
ADXL345_OFSX = 0x1E
ADXL345_OFSY = 0x1F
ADXL345_OFSZ = 0x20
ADXL345_TAP_THRESH = 0x1D
ADXL345_TAP_DUR = 0x21
ADXL345_TAP_LATENCY = 0x22
ADXL345_ACT_THRESH = 0x24
ADXL345_INACT_THRESH = 0x25
ADXL345_INACT_TIME = 0x26
ADXL345_INACT_ACT_CTL = 0x27
ADXL345_FALL_THRESH = 0x28
ADXL345_FALL_TIME = 0x29
ADXL345_TAP_AXES = 0x2A
ADXL345_ACT_TAP_STATUS = 0x2B
# Interrupt registers
ADXL345_INT_ENABLE = 0x2E
ADXL345_INT_MAP = 0x2F
ADXL345_INT_SOURCE = 0x30
# Data registers (read only)
ADXL345_XOUT_L = 0x32
ADXL345_XOUT_H = 0x33
ADXL345_YOUT_L = 0x34
ADXL345_YOUT_H = 0x35
ADXL345_ZOUT_L = 0x36
ADXL345_ZOUT_H = 0x37
DATA_REG_SIZE = 6
# Data and power management
ADXL345_BW_RATE = 0x2C
ADXL345_POWER_CTL = 0x2D
ADXL345_DATA_FORMAT = 0x31
ADXL345_FIFO_CTL = 0x38
ADXL345_FIFO_STATUS = 0x39
# Useful values
ADXL345_POWER_ON = 0x08
ADXL345_AUTO_SLP = 0x30
ADXL345_STANDBY = 0x00
# Scales and resolution
ADXL345_FULL_RES = 0x08
ADXL345_10BIT = 0x00
ADXL345_2G = 0x00
ADXL345_4G = 0x01
ADXL345_8G = 0x02
ADXL345_16G = 0x03
def __init__(self, bus):
# Init bus and reset chip
self.i2c = mraa.I2c(bus)
# Set the slave to talk to
if self.i2c.address(self.__class__.ADXL345_I2C_ADDR) != mraa.SUCCESS:
raise Exception("i2c.address() failed")
message = bytearray(
[self.__class__.ADXL345_POWER_CTL,
self.__class__.ADXL345_POWER_ON])
if self.i2c.write(message) != mraa.SUCCESS:
raise Exception("i2c.write() control register failed")
if self.i2c.address(self.__class__.ADXL345_I2C_ADDR) != mraa.SUCCESS:
raise Exception("i2c.address() failed")
message = bytearray(
[self.__class__.ADXL345_DATA_FORMAT,
self.__class__.ADXL345_16G | self.__class__.ADXL345_FULL_RES])
if self.i2c.write(message) != mraa.SUCCESS:
raise Exception("i2c.write() mode register failed")
# 2.5V sensitivity is 256 LSB/g = 0.00390625 g/bit
# 3.3V x and y sensitivity is 265 LSB/g = 0.003773584 g/bit, z is the same
self.x_offset = 0.003773584
self.y_offset = 0.003773584
self.z_offset = 0.00390625
self.x_acceleration = 0.0
self.y_acceleration = 0.0
self.z_acceleration = 0.0
self.update()
def update(self):
# Set the slave to talk to
self.i2c.address(self.__class__.ADXL345_I2C_ADDR)
self.i2c.writeByte(self.__class__.ADXL345_XOUT_L)
self.i2c.address(self.__class__.ADXL345_I2C_ADDR)
xyz_raw_acceleration = self.i2c.read(self.__class__.DATA_REG_SIZE)
x_raw_acceleration = (xyz_raw_acceleration[1] << 8) | xyz_raw_acceleration[0]
y_raw_acceleration = (xyz_raw_acceleration[3] << 8) | xyz_raw_acceleration[2]
z_raw_acceleration = (xyz_raw_acceleration[5] << 8) | xyz_raw_acceleration[4]
self.x_acceleration = x_raw_acceleration * self.x_offset
self.y_acceleration = y_raw_acceleration * self.y_offset
self.z_acceleration = z_raw_acceleration * self.z_offset
首先,该类声明了许多常量,这使得我们更容易理解通过 I²C 总线与加速度计交互的代码。例如,ADXL345_I2C_ADDR常量指定了 I²C 总线中 ADXL345 加速度计的地址,十六进制为 53(0x53)。如果我们只是在代码中看到0x53,我们不会理解它是传感器的 I²C 总线地址。我们导入了 C++版本中定义的所有常量,以便在需要添加初始版本中未包含的额外功能时,我们拥有所有必要的值。制造商提供的数据表提供了必要的细节,以了解每个寄存器的地址以及命令在 I²C 总线中的工作方式。
在创建Adxl345类的实例时,我们必须指定数字加速度计连接的 I²C 总线编号,该编号作为bus必需参数。构造函数,即__init__方法,使用接收到的bus参数创建一个新的mraa.I2c实例,并将其引用保存在i2c属性中。
self.i2c = mraa.I2c(bus)
在 I²C 总线中执行任何读取或写入操作之前,调用mraa.I2c实例的address方法以指示我们想要与之通信的从设备是一个好习惯。在这种情况下,从设备的地址由ADXL345_I2C_ADDR常量指定。
if self.i2c.address(self.__class__.ADXL345_I2C_ADDR) != mraa.SUCCESS:
raise Exception("i2c.address() failed")
然后,代码通过创建一个包含我们想要写入从设备的两个十六进制值的bytearray来构建一条消息:ADXL345_POWER_CTL和ADXL345_POWER_ON。我们可以将这条消息解读为“将开启写入电源控制寄存器”。使用此消息调用mraa.I2c实例的write方法将开启加速度计。
message = bytearray(
[self.__class__.ADXL345_POWER_CTL,
self.__class__.ADXL345_POWER_ON])
if self.i2c.write(message) != mraa.SUCCESS:
raise Exception("i2c.write() control register failed")
我们声明了以下与分辨率相关的常量:
-
ADXL345_FULL_RES:以全分辨率工作,其中分辨率随着 g 范围的增加而增加到 13 位分辨率 -
ADXL345_10BIT:以固定的 10 位分辨率工作
我们声明了以下与量纲相关的常量:
-
ADXL345_2G:将 g 范围设置为±2g -
ADXL345_4G:将 g 范围设置为±4g -
ADXL345_8G:将 g 范围设置为±8g -
ADXL345_16G:将 g 范围设置为±16g
在对传感器配置所需的分辨率和量纲进行另一次写入之前,代码对mraa.I2c实例的address方法进行了另一次调用。代码通过创建一个包含我们想要写入从设备的两个十六进制值的bytearray来构建另一条消息:ADXL345_DATA_FORMAT以及应用位或运算符(|)后的ADXL345_16G和ADXL345_FULL_RES的结果。我们可以将这条消息解读为“将±16g 和全分辨率写入数据格式寄存器”。有必要将所需的分辨率和范围组合成一个单字节值,因此我们必须使用位或运算符(|)。
if self.i2c.address(self.__class__.ADXL345_I2C_ADDR) != mraa.SUCCESS:
raise Exception("i2c.address() failed")
message = bytearray(
[self.__class__.ADXL345_DATA_FORMAT,
self.__class__.ADXL345_16G | self.__class__.ADXL345_FULL_RES])
if self.i2c.write(message) != mraa.SUCCESS:
raise Exception("i2c.write() mode register failed")
调用mraa.I2c实例的write方法并传递此消息将配置加速度计以±16g 的范围工作,并具有完整的分辨率。由于我们有权访问此调用,我们可以修改代码以更改所需的分辨率或加速度测量的比例。例如,以下组成消息的行将配置更改,使加速度计能够以±4g 的范围工作:
message = bytearray(
[self.__class__.ADXL345_DATA_FORMAT,
self.__class__.ADXL345_4G | self.__class__.ADXL345_FULL_RES])
然后,代码声明了 x、y 和 z 的偏移量属性,这是将来自加速度计的原始加速度值转换为以 g 表示的适当值所必需的。我们希望使用易于理解的属性,因此构造函数创建并初始化了三个属性,其值为0.0:x_acceleration、y_acceleration和z_acceleration。最后,构造函数调用update方法以从加速度计检索第一个值。
update方法对mraa.I2c实例的address方法进行调用,然后调用其writeByte方法,将ADXL345_XOUT_L作为其参数,即我们想要读取的第一个数据寄存器。
self.i2c.address(self.__class__.ADXL345_I2C_ADDR)
self.i2c.writeByte(self.__class__.ADXL345_XOUT_L)
加速度计的值存储在六个数据寄存器中。每个轴有两个字节:低字节(八个最低有效位)和高字节(八个最高有效位),因此我们可以通过单个 I²C 读取操作读取六个字节,从 x 轴的第一个字节地址开始。然后,我们必须将每对字节组合成一个单一值。对mraa.I2c实例的read方法的调用将DATA_REG_SIZE常量作为参数传递,以指示我们想要读取六个字节,并且代码将结果bytearray保存到xyz_raw_acceleration局部变量中。
self.i2c.address(self.__class__.ADXL345_I2C_ADDR)
xyz_raw_acceleration = self.i2c.read(self.__class__.DATA_REG_SIZE)
然后,代码将低字节和高字节组合成一个单一值,为从加速度计检索的每个原始加速度字节对保存到三个局部变量中:x_raw_acceleration、y_raw_acceleration和z_raw_acceleration。代码使用二进制左移(<<)位运算符将高字节(八个最高有效位)向左移动 8 位,并将右侧的新位设置为 0。然后,它应用二进制或(|)来构建整个字(两个字节)。x_raw_acceleration值是将高字节和低字节组合起来构成一个双字节字的結果。
xyz_raw_acceleration数组中的第一个元素(xyz_raw_acceleration[0])包括 x 原始加速度的低字节,而xyz_raw_acceleration数组中的第二个元素(xyz_raw_acceleration[1])包括 x 原始加速度的高字节。因此,有必要向高字节(xyz_raw_acceleration[1])添加 8 个二进制零,并用低字节(xyz_raw_acceleration[0])替换这些零。对于 y 和 z 原始加速度字节也要做同样的事情。
x_raw_acceleration = (xyz_raw_acceleration[1] << 8) | xyz_raw_acceleration[0]
y_raw_acceleration = (xyz_raw_acceleration[3] << 8) | xyz_raw_acceleration[2]
z_raw_acceleration = (xyz_raw_acceleration[5] << 8) | xyz_raw_acceleration[4]
最后,我们需要将每个值乘以构造函数中定义的偏移量,以获得以 g 为单位的 x、y 和 z 的适当值,并将它们保存到三个属性中:x_acceleration、y_acceleration 和 z_acceleration。
self.x_acceleration = x_raw_acceleration * self.x_offset
self.y_acceleration = y_raw_acceleration * self.y_offset
self.z_acceleration = z_raw_acceleration * self.z_offset
现在,我们有一个完全用 Python 编写的表示 ADXL345 加速度计的类,我们可以进行任何必要的更改,以对加速度计进行不同的配置。
我们只需要创建 Accelerometer 类的新版本,使用最近创建的 Adxl345 类而不是 pyupm_adxl345.Adxl345 类。以下行显示了新 Accelerometer 类的代码。示例的代码文件为 iot_python_chapter_07_03.py。
class Accelerometer:
def __init__(self, bus):
self.accelerometer = Adxl345(bus)
self.x_acceleration = 0.0
self.y_acceleration = 0.0
self.z_acceleration = 0.0
def measure_acceleration(self):
# Update the acceleration values for the three axis
self.accelerometer.update()
self.x_acceleration = self.accelerometer.x_acceleration
self.y_acceleration = self.accelerometer.y_acceleration
self.z_acceleration = self.accelerometer.z_acceleration
现在,我们可以使用之前示例中用于 __main__ 方法的相同代码,并执行相同的操作来检查从加速度计获取的值。
小贴士
编写与 I²C 总线和特定传感器交互的代码需要很大的努力,因为我们必须从制造商的数据表中读取详细的规格。有时,如果我们不编写自己的代码,可能无法使用传感器中包含的所有功能。在其他情况下,upm 库中包含的功能可能足以满足我们的项目需求。
连接模拟温度传感器
在第六章 使用模拟输入和本地存储 中,我们使用了一个包含在分压器中的光敏电阻,并将其连接到模拟输入引脚。我们可以使用类似的配置,并用热敏电阻替换光敏电阻来测量环境温度。热敏电阻会随着温度变化而改变其电阻值,因此,我们可以将电阻变化转换为电压值变化。
我们还可以使用一个包含热敏电阻的模拟传感器扩展板,该热敏电阻配置为为我们提供一个模拟引脚的电压级别,这些电压级别代表温度值。在这种情况下,我们将使用 upm 库支持的模拟温度传感器来测量环境温度。
我们将使用标有 A0 的模拟引脚来连接模拟加速度计扩展板的电压输出。完成必要的接线后,我们将编写 Python 代码来测量并显示环境温度,单位为摄氏度 (ºC) 和华氏度 (ºF)。这样,我们将读取将模拟值转换为其数字表示的结果,并将其映射到适当的测量单位中的温度值。
我们需要一个 Seeedstudio Grove 温度传感器来配合这个示例。以下网址提供了关于此模块的详细信息:www.seeedstudio.com/depot/Grove-Temperature-Sensor-p-774.html。以下图显示了传感器扩展板、必要的布线以及从 Intel Galileo Gen 2 板到面包板的布线。示例的 Fritzing 文件为 iot_fritzing_chapter_07_04.fzz,以下图片是面包板视图。不要忘记,您也可以选择使用 Grove 基础板将此传感器连接到 Intel Galileo Gen 2 板。
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_07_05.jpg
以下图片显示了用符号表示的电子元件的电路图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_07_06.jpg
如前图所示,我们有以下连接:
-
标记为A0的模拟输入引脚连接到标记为SIG的温度输出引脚(在扩展板符号中为0)
-
标记为3V3的电源引脚连接到温度传感器电源引脚标记为VCC
-
标记为GND的接地引脚连接到温度传感器接地引脚标记为GND
现在,是时候进行所有必要的布线了。在添加或移除任何线缆之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 灯熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
使用模拟传感器测量环境温度
upm 库在 pyupm_grove 模块中包括对 Grove 模拟温度传感器扩展板的支持。在此模块中声明的 GroveTemp 类代表连接到我们板上的模拟温度传感器。该类使得从模拟输入读取的原始值转换为以摄氏度(ºC)表示的值变得简单。
我们将创建一个新的 TemperatureSensor 类来表示温度传感器,并使我们更容易检索环境温度值,而无需担心与 GroveTemp 类实例一起工作时必要的单位转换。我们将使用 GroveTemp 类与模拟温度传感器交互。以下行显示了与 upm 库一起工作的新 TemperatureSensor 类的代码,特别是与 pyupm_grove 模块一起。示例的代码文件为 iot_python_chapter_07_04.py。
import pyupm_grove as upmGrove
import time
class TemperatureSensor:
def __init__(self, analog_pin):
self.temperature_sensor = upmGrove.GroveTemp(analog_pin)
self.temperature_celsius = 0.0
self.temperature_fahrenheit = 0.0
def measure_temperature(self):
# Retrieve the temperature expressed in Celsius degrees
temperature_celsius = self.temperature_sensor.value()
self.temperature_celsius = temperature_celsius
self.temperature_fahrenheit = \
(temperature_celsius * 9.0 / 5.0) + 32.0
当我们创建 TemperatureSensor 类的实例并在 analog_pin 必需参数中指定传感器连接的模拟引脚时,我们必须指定该引脚。构造函数,即 __init__ 方法,创建一个新的 upmGrove.GroveTemp 实例,并使用接收到的 analog_pin 参数,将其引用保存在 temperature_sensor 属性中。最后,构造函数实例创建并初始化两个属性,值为 0.0:temperature_celsius 和 temperature_fahrenheit。
该类定义了一个 measure_temperature 方法,通过调用 self.temperature_sensor 的值方法来获取当前环境温度(单位为摄氏度,ºC),并将该值保存在局部变量 temperature_celsius 中。下一行将值赋给 temperature_celsius 属性。最后,代码将摄氏度(ºC)测量的温度转换为等效的华氏度(ºF)值。该公式易于阅读,因为它只需要将摄氏度(ºC)测量的温度乘以 9,然后将结果除以 5 并加上 32。这样,TemperatureSensor 类就更新了两个属性,即传感器测量的环境温度(单位为摄氏度,ºC)和华氏度(ºF)。
现在,我们将编写一个循环,每 10 秒检索并显示环境温度,单位为摄氏度(ºC)和华氏度(ºF)。该示例的代码文件为 iot_python_chapter_07_04.py。
if __name__ == "__main__":
# The temperature sensor is connected to analog pin A0
temperature_sensor = TemperatureSensor(0)
while True:
temperature_sensor.measure_temperature()
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_sensor.temperature_fahrenheit))
# Sleep 10 seconds (10000 milliseconds)
time.sleep(10)
第一行创建了一个之前编写的 TemperatureSensor 类的实例,analog_pin 参数的值为 0。这样,该实例将从标有 A0 的引脚读取模拟值。然后,代码运行一个无限循环,调用 measure_temperature 方法来更新环境温度值,然后打印它们,单位为摄氏度(ºC)和华氏度(ºF)。
以下行将启动示例:
python iot_python_chapter_07_04.py
运行示例后,打开空调或加热系统以产生环境温度的变化,你将看到测量温度在几分钟后的变化。以下行显示了部分示例输出:
Ambient temperature in degrees Celsius: 13
Ambient temperature in degrees Fahrenheit: 55.4
Ambient temperature in degrees Celsius: 14
Ambient temperature in degrees Fahrenheit: 57.2
Ambient temperature in degrees Celsius: 15
Ambient temperature in degrees Fahrenheit: 59
Ambient temperature in degrees Celsius: 16
Ambient temperature in degrees Fahrenheit: 60.8
将数字温度和湿度传感器连接到 I²C 总线
现在,我们将使用一个多功能数字传感器,它将为我们提供温度和相对湿度信息。我们将使用一个使用 I²C 总线进行通信的电路板,以便英特尔 Galileo Gen 2 板与传感器通信。当不需要在极端条件下测量温度和湿度时,该传感器很有用。我们不能在厄特纳火山顶部使用此传感器,以防我们在与火山相关的科研项目中工作。
我们将使用标有SDA和SCL的两个引脚将 I²C 总线的数据线和时钟线连接到数字温度和湿度引脚扩展板上的相应引脚。完成必要的布线后,我们将编写 Python 代码来测量、显示环境温度和相对湿度。这样,我们将通过 I²C 总线发送命令到传感器,读取响应,并将它们解码为以适当单位表示的环境温度和相对湿度。
我们需要一款 SeeedStudio Grove 温度与湿度传感器(高精度且迷你)的引脚扩展板来与这个示例一起使用。以下网址提供了关于这款引脚扩展板的详细信息:www.seeedstudio.com/depot/Grove-TemperatureHumidity-Sensor-HighAccuracy-Mini-p-1921.html。该引脚扩展板集成了 TH02 数字湿度温度传感器,并支持 I²C 总线。
以下图表显示了数字温度、湿度引脚扩展板、必要的布线以及从 Intel Galileo Gen 2 板到面包板的布线。该示例的 Fritzing 文件为iot_fritzing_chapter_07_05.fzz,以下图片是面包板视图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_07_07.jpg
以下图片显示了带有电子元件符号的电路图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_07_08.jpg
如前图所示,我们有以下连接:
-
SDA引脚连接到标有SDA的引脚扩展板。这样,我们将数字温度和湿度传感器连接到 I²C 总线的串行数据线。
-
SCL引脚连接到标有SCL的引脚扩展板。这样,我们将数字温度和湿度传感器连接到 I²C 总线的串行时钟线。
-
标有3V3的电源引脚连接到标有VCC的引脚扩展板电源引脚。
-
标有GND的接地引脚连接到标有GND的引脚扩展板接地引脚。
现在,是时候进行所有必要的布线了。在添加或移除任何线从板上的引脚之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
使用数字传感器测量温度和湿度
upm 库在 pyupm_th02 模块中包含了支持使用 TH02 传感器的数字温度和湿度扩展板。在此模块中声明的 TH02 类代表一个使用 TH02 传感器的数字温度和湿度传感器,该传感器连接到我们的板上。该类使得初始化传感器并通过 I²C 总线检索温度和湿度值变得简单。该类在幕后与 mraa.I2c 类协同工作,与传感器通信,即向 TH02 传感器写入数据并从该传感器读取数据,该传感器作为连接到 I²C 总线的从设备。
我们将创建一个新的 TemperatureAndHumiditySensor 类来表示温度和湿度传感器,并使我们在使用 TH02 类的实例时更容易检索温度和湿度值。我们将使用 TH02 类与传感器交互。以下行显示了与 upm 库(特别是 pyupm_th02 模块)一起工作的新 TemperatureSensor 类的代码。示例的代码文件为 iot_python_chapter_07_05.py。
import pyupm_th02 as upmTh02
import time
class TemperatureAndHumiditySensor:
def __init__(self, bus):
self.th02_sensor = upmTh02.TH02(bus)
self.temperature_celsius = 0.0
self.temperature_fahrenheit = 0.0
self.humidity = 0.0
def measure_temperature_and_humidity(self):
# Retrieve the temperature expressed in Celsius degrees
temperature_celsius = self.th02_sensor.getTemperature()
self.temperature_celsius = temperature_celsius
self.temperature_fahrenheit = \
(temperature_celsius * 9.0 / 5.0) + 32.0
# Retrieve the humidity
self.humidity = self.th02_sensor.getHumidity()
当我们在 TemperatureAndHumiditySensor 类的 bus 必需参数中创建实例时,我们必须指定数字温度和湿度传感器连接到的 I²C 总线编号。构造函数,即 __init__ 方法,使用接收到的 bus 参数创建一个新的 upmTh02.TH02 实例,并将它的引用保存在 th02_sensor 属性中。
小贴士
TH02 传感器的数据表指定了一个将原始读取温度转换为摄氏度(ºC)的公式,因此,通过阅读数据表,我们可能会认为 upmTh02.TH02 实例将提供一个华氏度(ºF)的值。然而,事实并非如此。upmTh02.TH02 实例将华氏度(ºF)转换为摄氏度(ºC),并为我们提供一个后者的测量单位值。因此,如果我们想以华氏度(ºF)显示值,我们必须将摄氏度(ºC)转换为华氏度(ºF)。不幸的是,了解这种情况的唯一方法是通过查看 upm 模块的 C++ 源代码,因为关于代码使用的测量单位没有文档说明。
我们希望使用易于理解的属性,因此构造函数创建了三个属性并初始化为 0.0:temperature_celsius、temperature_fahrenheit 和 humidity。构造函数执行后,我们有一个初始化的数字温度和湿度传感器,可以检索值。
该类定义了一个 measure_temperature_and_humidity 方法,该方法更新传感器中的环境温度和湿度值,检索这些值,并将它们最终保存到以下三个属性中:temperature_celsius、temperature_fahrenheit 和 humidity。
首先,measure_temperature_and_humidity方法中的代码调用self.th02_sensor的getTemperature方法,请求传感器检索温度值。该方法返回转换为摄氏度(ºC)的读取值,代码将其保存到temperature_celsius局部变量中。代码将值保存在具有相同名称的属性中,并将转换为华氏度(ºF)的值保存在temperature_fahrenheit属性中。最后,代码调用self.th02_sensor的getHumidity方法,请求传感器检索湿度值并将其保存在humidity属性中。
现在,我们将编写一个循环,每 10 秒检索并显示以摄氏度(ºC)和华氏度表示的温度值,以及湿度值。示例的代码文件是iot_python_chapter_07_05.py。
if __name__ == "__main__":
temperature_and_humidity_sensor = \
TemperatureAndHumiditySensor(0)
while True:
temperature_and_humidity_sensor.\
measure_temperature_and_humidity()
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_and_humidity_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_and_humidity_sensor.temperature_fahrenheit))
print("Ambient humidity: {0}%".
format(temperature_and_humidity_sensor.humidity))
# Sleep 10 seconds (10000 milliseconds)
time.sleep(10)
第一行创建了一个之前编写的TemperatureAndHumiditySensor类的实例,bus参数的值为0。这样,该实例将通过 I²C 总线与数字加速度计建立通信。正如我们在之前与 I²C 总线连接的传感器示例中发生的那样,英特尔 Galileo Gen 2 板是总线上的主设备,而数字温度和湿度传感器则作为从设备。
然后,代码运行一个无限循环,调用measure_temperature_and_humidity方法来更新以两种单位和湿度表示的温度值。
以下行将开始示例:
python iot_python_chapter_07_05.py
在运行示例后,打开空调或供暖系统,以产生环境温度和湿度的变化。
Ambient temperature in degrees Celsius: 24
Ambient temperature in degrees Fahrenheit: 73.4
Ambient humidity: 48%
测试你的知识
-
以下哪个传感器允许我们测量正加速度的大小和方向?
-
一个温度传感器。
-
一个加速度计。
-
一个光传感器。
-
-
以下哪个缩写定义了一个具有传感器的模块的连接类型是模拟的:
-
AIO。
-
I2C。
-
UART。
-
-
我们需要多少根线来将设备连接到 I²C 总线:
-
1`。
-
2`。
-
3`。
-
-
我们需要多少根线来将设备连接到 SPI 总线:
-
1`。
-
2`。
-
3`。
-
-
以下哪个不是 I²C 总线的连接:
-
MISO。
-
SDA。
-
SCL。
-
摘要
在本章中,我们学习了传感器及其连接类型。我们了解到在选择传感器时需要考虑许多重要事项,并且它们使我们能够轻松地从现实世界测量不同的变量。我们学习了考虑测量单位的重要性,因为传感器总是以特定的单位提供测量值,我们必须考虑这些单位。
我们编写了利用upm库中包含的模块和类来简化我们开始使用模拟和数字传感器的代码。此外,我们还编写了通过 I²C 总线与数字加速度计交互的代码,因为我们想利用传感器提供的额外功能,但这些功能并未包含在upm库模块中。
我们测量了合加速度或 g 力的幅度和方向、环境温度和湿度。与前面的章节一样,我们继续利用 Python 的面向对象特性,并创建了类来封装upm和mraa库中的传感器和必要的配置。我们的代码易于阅读和理解,并且我们可以轻松地隐藏底层细节。
现在我们能够通过传感器从现实世界获取数据,我们将使我们的物联网设备通过不同的执行器和屏蔽器执行动作,这是下一章的主题。
第八章. 显示信息并执行操作
在本章中,我们将使用各种扩展板和执行器,通过编写 Python 代码来显示数据和执行操作。我们将:
-
理解 LCD 显示屏及其连接类型
-
学习在选择 LCD 显示屏时必须考虑的最重要事项
-
利用 LCD 显示屏和执行器利用
upm库 -
使用与 I²C 总线兼容的 RGB 背光 LCD 显示屏
-
在 16x2 LCD 屏幕上显示和更新文本
-
使用与 I²C 总线兼容的 OLED 显示屏
-
在 96x96 点阵 OLED 显示屏上显示和更新文本
-
将标准伺服电机连接到 PWM 进行控制
-
使用伺服电机和轴显示值
理解 LCD 显示屏及其连接类型
有时,我们的物联网设备必须通过连接到英特尔 Galileo Gen 2 板的任何设备向用户提供信息。我们可以使用不同类型的电子组件、屏蔽或扩展板来实现这一目标。
例如,我们可以使用简单的 LED 灯来提供可以用颜色表示的信息。例如,一个亮起的红色 LED 可以表示连接到板上的温度传感器检测到环境温度高于 80 华氏度(ºF)或 26.66 摄氏度(ºC)。一个亮起的蓝色 LED 可以表示我们的温度传感器检测到环境温度低于 40 华氏度(ºF)或 4.44 摄氏度(ºC)。一个亮起的红色 LED 可以表示温度介于这两个值之间。这三个 LED 灯使我们能够向用户提供有价值的信息。
我们也可以使用单个 RGB LED 并利用脉冲宽度调制(PWM)来根据测量的环境温度值改变其颜色,正如我们在第四章中学习的,使用 RESTful API 和脉冲宽度调制。
然而,有时颜色不足以向用户提供详细和准确的信息。例如,有时我们希望用百分比值显示湿度水平,而几个 LED 灯不足以表示从 0 到 100%的数字。如果我们想要能够显示 1%的步进值,我们就需要 100 个 LED 灯。我们没有 100 个 GPIO 引脚,因此我们需要一个带有 100 个 LED 灯和数字接口(如 I²C 总线)的屏蔽或扩展板,以便我们可以发送指示要打开的 LED 灯数量的命令。
在这些情况下,一个允许我们打印特定数量字符的液晶屏幕可能是一个合适的解决方案。例如,在一个允许我们每行显示 16 个字符的液晶屏幕上,有 2 行 16 个字符,称为 16x2 液晶模块,我们可以在第一行显示温度,在第二行显示湿度水平。以下表格显示了每一行文本和值的示例,考虑到我们有 16 列和 2 行字符。
| T | e | m | p | . | 4 | 0 | . | 2 | F | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| H | u | m | i | d | i | t | y | 8 | 0 | % |
16x2 液晶模块为每个值提供清晰的描述,包括浮点值和度量单位。因此,我们将使用 16x2 液晶模块作为示例。以下图片显示了 16x2 液晶屏幕中每个字符位置的示例:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_08_01.jpg
有不同功能的液晶模块,我们必须考虑我们在第七章“使用传感器从现实世界获取数据”中分析传感器时学到的大量内容。以下列表列举了在选择液晶模块时我们必须考虑的最重要的因素及其描述。由于我们在学习传感器时分析了这些内容中的许多,因此我们不会重复常见项目的描述。
-
与英特尔 Galileo Gen 2 板和我们所使用的电压供应(5V 或 3.3V)的兼容性。
-
功耗。
-
连接类型:一些液晶显示器消耗太多的引脚,因此,检查它们所需的全部引脚非常重要。液晶显示器最常见的连接类型是 I²C 总线、SPI 总线和 UART 端口。然而,一些液晶显示器需要总线或端口与额外的 GPIO 引脚结合使用。
-
工作范围和特殊环境要求。
-
尺寸:液晶显示器有不同的尺寸。有时只有特定的尺寸适合我们的项目。
-
列数和行数:根据我们必须显示的文本,我们将选择具有适当列数和行数的液晶显示器,以便显示字符。
-
响应时间:确定我们能够等待液晶显示器显示替换正在显示的文本或清除显示器的新内容的时间非常重要。
-
协议,upm 库中的支持以及 Python 绑定。
-
支持的字符集和内置字体:一些液晶显示器支持用户自定义字符,因此,它们允许我们配置和显示自定义字符。检查液晶显示器是否支持我们必须显示的文本的语言中的字符也很重要。
-
背光颜色、文本颜色和对比度级别:一些 LCD 显示器允许我们更改背光颜色,而另一些则具有固定的背光颜色。RGB 背光使我们能够结合红色、绿色和蓝色组件来确定所需的背光颜色。此外,始终需要考虑对比度级别是否适合您需要显示信息的照明条件。
-
成本。
将 LCD RGB 背光连接到 I²C 总线
在我们的第七章“使用传感器从现实世界获取数据”的最后一个示例中,我们使用了一个多功能数字传感器,为我们提供了温度和相对湿度信息。我们使用了一个使用 I²C 总线允许 Intel Galileo Gen 2 板与传感器通信的扩展板。现在,我们将添加一个带有 16x2 LCD RGB 背光的扩展板,以便我们可以用文本和数字显示测量的温度和湿度值。
LCD RGB 背光扩展板也将连接到与温度和湿度数字传感器相同的 I²C 总线。只要它们的 I²C 地址不同,我们就可以在 Intel Galileo Gen 2 板上将许多从设备连接到 I²C 总线。实际上,LCD RGB 背光扩展板有两个 I²C 地址:一个用于 LCD 显示器,另一个用于背光。
我们需要以下部分来使用这个示例:
-
一块 SeeedStudio Grove 温度和湿度传感器(高精度和迷你)扩展板。以下网址提供了有关此扩展板的详细信息:
www.seeedstudio.com/depot/Grove-TemperatureHumidity-Sensor-HighAccuracy-Mini-p-1921.html。 -
一块 SeeedStudio Grove LCD RGB 背光扩展板。以下网址提供了有关此扩展板的详细信息:
www.seeedstudio.com/depot/Grove-LCD-RGB-Backlight-p-1643.html。
以下图表显示了数字温度和湿度扩展板、LCD RGB 背光扩展板、必要的连接以及从 Intel Galileo Gen 2 板到面包板的连接。该示例的 Fritzing 文件为iot_fritzing_chapter_08_01.fzz,以下图像是面包板视图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_08_02.jpg
以下图像显示了用符号表示的电子元件的原理图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_08_03.jpg
如前一个原理图所示,我们有以下连接:
-
SDA引脚连接到标记为SDA的扩展板引脚。这样,我们将数字温度和湿度传感器以及 LCD 背光连接到 I²C 总线的串行数据线。
-
SCL 引脚连接到标有 SCL 的扩展板引脚。这样,我们可以将数字温度和湿度传感器以及 LCD 背光连接到 I²C 总线的串行时钟线上。
-
标有 3V3 的电源引脚连接到标有 VCC 的数字温度和湿度传感器扩展板电源引脚。
-
标有 5V 的电源引脚连接到标有 VCC 的 LCD 背光扩展板电源引脚。
-
标有 GND 的地线连接到标有 GND 的扩展板引脚。
现在,是时候进行所有必要的接线了。在从板上的引脚添加或移除任何电线之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 灯熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
在 LCD 显示器上显示文本
upm 库在 pyupm_i2clcd 模块中包括对 16x2 LCD RGB 背光扩展板的支持。在此模块中声明的 Jhd1313m1 类代表一个 16x2 LCD 显示屏及其 RGB 背光,连接到我们的板上。该类使得设置 RGB 背光的颜色组件、清除 LCD 显示、指定光标位置以及通过 I²C 总线写入文本变得简单。该类在幕后与 mraa.I2c 类一起工作,以与 RGB 背光和 LCD 显示进行通信。这两个设备作为连接到 I²C 总线的从设备,因此,它们在这个总线中都有特定的地址。
我们将使用上一章中编写的代码来读取传感器的温度和湿度值,并将此代码作为基准来添加新功能。示例代码文件为 iot_python_chapter_07_05.py。
我们将创建一个 Lcd 类来表示 16x2 LCD RGB 背光,并使我们在设置背景颜色和在不担心 Jhd1313m1 类实例的具体方法的情况下写入两行文本时更加容易。我们将使用 Jhd1313m1 类与 LCD 及其 RGB 背光进行交互。以下行显示了与 upm 库一起工作的新 Lcd 类的代码,特别是与 pyupm_i2clcd 模块一起工作。示例代码文件为 iot_python_chapter_08_01.py。
import pyupm_th02 as upmTh02
import pyupm_i2clcd as upmLcd
import time
class Lcd:
# The I2C address for the LCD display
lcd_i2c_address = 0x3E
# The I2C address for the RBG backlight
rgb_i2c_address = 0x62
def __init__(self, bus, red, green, blue):
self.lcd = upmLcd.Jhd1313m1(
bus,
self.__class__.lcd_i2c_address,
self.__class__.rgb_i2c_address)
self.lcd.clear()
self.set_background_color(red, green, blue)
def set_background_color(self, red, green, blue):
self.lcd.setColor(red, green, blue)
def print_line_1(self, message):
self.lcd.setCursor(0, 0)
self.lcd.write(message)
def print_line_2(self, message):
self.lcd.setCursor(1, 0)
self.lcd.write(message)
Lcd 类声明了两个类属性:lcd_i2c_address 和 rgb_i2c_address。第一个类属性定义了 LCD 显示的 I²C 地址,即当光标位于特定行和列时,将处理定位光标和写入文本的命令的地址。该地址为十六进制的 3E(即 0x3E)。如果我们只是在代码中看到 0x3E,我们不会理解它是一个 LCD 显示的 I²C 总线地址。第二个类属性定义了 RGB 背光的 I²C 地址,即处理设置背光红色、绿色和蓝色组件的命令的地址。该地址为十六进制的 62(即 0x62)。如果我们只是在代码中看到 0x62,我们不会理解它是一个 RGB 背光的 I²C 总线地址。这些类属性使得代码更容易阅读。
当我们在 bus 必要参数中创建 Lcd 类的实例时,我们必须指定连接到 16x2 LCD 和 RGB 背光的 I²C 总线编号。此外,还需要指定红色、绿色和蓝色颜色组件的值来配置 RGB 背光的背景颜色。构造函数,即 __init__ 方法,使用接收到的 bus 参数以及 lcd_i2c_address 和 rgb_i2c_address 类属性创建一个新的 upmLcd.Jhd1313m1 实例,并将新实例的引用保存在 lcd 属性中。然后,代码调用新实例的 clear 方法来清除 LCD 屏幕。最后,代码调用 set_background_color 方法,并使用作为参数接收的红色、绿色和蓝色值来配置 RGB 背光的背景颜色。
该类声明了 set_background_color 方法,该方法使用接收到的 red、green 和 blue 值调用 lcd.setColor 方法。在底层,upmLcd.Jhd1313m1 实例将通过 I²C 总线将数据写入地址等于 rgb_i2c_address 类属性的从设备,以指定每个颜色组件的期望值。我们只是创建了一个特定方法来遵循 Python 命名约定,并使使用我们的类的最终代码更容易阅读。
该类定义了以下两个额外的方法,以便于在 LCD 显示的第一行和第二行打印文本:
-
print_line_1 -
print_line_2
print_line_1方法调用upmLcd.Jhd1313m1实例(self.lcd)的setCursor方法,其中row和column参数的值均为0,以将光标定位在第一行和第一列。然后,调用upmLcd.Jhd1313m1实例(self.lcd)的写入方法,将message作为参数传递,在 LCD 显示屏中打印接收到的字符串。在底层,upmLcd.Jhd1313m1实例将通过 I²C 总线将数据写入地址等于lcd_i2c_address类属性的从设备,以指定光标所需的位置,然后从我们定位光标的位置开始写入指定的文本。第一行用 0 标识,但我们将其命名为print_line_1,因为这使我们更容易理解我们正在在 LCD 屏幕的第一行写入消息。
print_line_2方法与print_line_1方法具有相同的两行代码,只是对setCursor方法的调用指定了1作为行参数的值。这样,该方法将在 LCD 屏幕的第二行打印消息。
现在,我们将创建一个名为TemperatureAndHumidityLcd的之前编写的Lcd类的子类。该子类将专门化Lcd类,使我们能够轻松地在 LCD 屏幕的第一行打印华氏度表示的温度值,并在第二行打印百分比表示的湿度值。以下行显示了新TemperatureAndHumidityLcd类的代码。示例的代码文件是iot_python_chapter_08_01.py。
class TemperatureAndHumidityLcd(Lcd):
def print_temperature(self, temperature_fahrenheit):
self.print_line_1("Temp. {:5.2f}F".format(temperature_fahrenheit))
def print_humidity(self, humidity):
self.print_line_2("Humidity {0}%".format(humidity))
新的类(TemperatureAndHumidityLcd)向其超类(Lcd)添加了以下两个方法:
-
print_temperature:调用print_line_1方法,并使用在temperature_fahrenheit参数中接收到的格式化文本来显示温度值。 -
print_humidity:调用print_line_2方法,并使用在humidity参数中接收到的格式化文本来显示湿度水平。
现在,我们将编写一个循环,每 10 秒在 LCD 屏幕上显示环境温度(华氏度,ºF)和湿度值。示例的代码文件是iot_python_chapter_08_01.py。
if __name__ == "__main__":
temperature_and_humidity_sensor = \
TemperatureAndHumiditySensor(0)
lcd = TemperatureAndHumidityLcd(0, 0, 0, 128)
while True:
temperature_and_humidity_sensor.\
measure_temperature_and_humidity()
lcd.print_temperature(
temperature_and_humidity_sensor.temperature_fahrenheit)
lcd.print_humidity(
temperature_and_humidity_sensor.humidity)
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_and_humidity_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_and_humidity_sensor.temperature_fahrenheit))
print("Ambient humidity: {0}".
format(temperature_and_humidity_sensor.humidity))
# Sleep 10 seconds (10000 milliseconds)
time.sleep(10)
突出的行显示了与上一个版本相比对__main__方法所做的更改。第一行突出显示了使用0作为bus参数的值、0用于red和green以及128用于blue来设置背景颜色为浅蓝色的新实例TemperatureAndHumidityLcd类的代码。该代码将此实例的引用保存到lcd局部变量中。这样,该实例将通过 I²C 总线与 LCD 屏幕和 RGB 背光建立通信。RGB 背光将显示浅蓝色背景。
然后,代码运行一个无限循环,并使用突出显示的行调用lcd.print_temperature方法,并使用temperature_and_humidity_sensor.temperature_fahrenheit作为参数,即以华氏度(ºF)表示的测量温度。这样,代码在 LCD 屏幕的第一行显示这个温度值。
下一条突出显示的行调用lcd.print_humidity方法,并使用temperature_and_humidity_sensor.humidity作为参数,即以百分比表示的测量湿度。这样,代码在 LCD 屏幕的第二行显示这个湿度值。
以下行将启动示例:
python iot_python_chapter_08_01.py
在运行示例之后,打开空调或加热系统,以产生环境温度和湿度的变化。LCD 屏幕将显示温度和湿度,并且每 10 秒刷新一次。
将 OLED 点阵连接到 I²C 总线
当我们需要通过 I²C 或 SPI 总线在外部屏幕上显示内容时,LCD 显示屏并不是唯一的选择。还有一些 OLED 点阵,允许我们控制特定数量的点。在 OLED 点阵中,我们可以控制每个点,而不是控制每个字符空间。其中一些是灰度的,而另一些是 RGB 的。
OLED 点阵的关键优势在于我们可以显示任何类型的图形,而不仅仅是文本。实际上,我们可以将任何类型的图形和图像与文本混合。Grove OLED Display 0.96"是一个 16 灰度 96-by-96 点阵 OLED 显示屏模块的例子,它支持 I²C 总线。以下 URL 提供了关于这块扩展板的详细信息:www.seeedstudio.com/depot/Grove-OLED-Display-096-p-824.html。Xadow RGB OLED 96x24 是一个 RGB 彩色 96-by-64 点阵 OLED 显示屏模块的例子,它支持 SPI 总线。以下 URL 提供了关于这块扩展板的详细信息:www.seeedstudio.com/depot/Xadow-RGB-OLED-96x64-p-2125.html。
小贴士
另一个选择是与 TFT LCD 点阵或显示屏一起工作。其中一些包括触摸检测的支持。
现在,我们将用一块带有 16 灰度 96-by-96 点阵的 OLED 显示屏的 16x2 LCD RGB 背光板来替换扩展板,这块显示屏也支持 I²C 总线,我们将使用这块新屏幕以不同的配置显示类似值。这些接线与之前的扩展板兼容。
如同我们之前的示例,点阵 OLED 也将连接到与温度和湿度数字传感器相同的 I²C 总线。由于点阵 OLED 的 I²C 地址与温度和湿度数字传感器使用的地址不同,因此我们不需要将两个设备连接到同一个 I²C 总线上。
我们需要以下额外的部件来使用此示例:一个 SeeedStudio Grove OLED 显示屏 0.96 英寸,16 灰度 96x96 点阵 OLED 显示模块。96x96 点阵 OLED 显示屏为我们提供了控制 9,216 个点,即像素的机会。然而,在这种情况下,我们只想使用 OLED 显示屏来显示类似于我们之前示例中的文本,但布局不同。
如果我们使用默认的 8x8 字符框,我们就有 12 列(96/8)和 12 行(96/8)用于字符。以下表格显示了每行文本及其值的示例。
| 温 | 度 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| 法 | 雷 | 尼 | 哈恩 | 海 | 特 | 海 | 伊 | 特 | |||
| 4 | 0 | . | 2 | ||||||||
| 细 | 节 | ||||||||||
| 4 | . | 5 | 5 | ||||||||
| 湿 | 度 | ||||||||||
| 级 | 别 | ||||||||||
| 8 | 0 | % | |||||||||
能够使用 12 列和 12 行的字符,使我们能够为每个值提供非常清晰的描述。此外,我们能够显示以华氏度和摄氏度表示的温度值。以下图片显示了带有 8x8 字符框的 96x96 点阵 OLED 显示模块中每个字符的位置示例。
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_08_04.jpg
在我们将 LCD 屏幕扩展板替换为 OLED 模块后,我们将有以下连接:
-
SDA引脚连接到标记为SDA的扩展板引脚。这样,我们将数字温度和湿度传感器以及 OLED 模块连接到 I²C 总线的串行数据线。
-
SCL引脚连接到标记为SCL的扩展板引脚。这样,我们将数字温度和湿度传感器以及 OLED 模块连接到 I²C 总线的串行时钟线。
-
标记为3V3的电源引脚连接到数字温度和湿度传感器扩展板的标记为VCC的电源引脚。
-
标记为5V的电源引脚连接到 OLED 模块的标记为VCC的电源引脚。
-
标记为GND的地线连接到标记为GND的扩展板引脚。
现在,是时候进行所有必要的接线了。在添加或从板上的引脚移除任何电线之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
在 OLED 显示屏上显示文本
upm库在pyupm_i2clcd模块中包括了对 SeeedStudio Grove OLED 显示屏 0.96 英寸、16 灰度 96x96 点阵 OLED 显示屏模块的支持。由于该 OLED 显示屏使用 SSD1327 驱动集成电路,因此在此模块中声明的SSD1327类代表一个 96x96 点阵 OLED 显示屏,连接到我们的板上。该类使得清除 OLED 屏幕、绘制位图图像、指定光标位置和通过 I²C 总线写入文本变得容易。该类在幕后与mraa.I2c类一起工作,以与 OLED 显示屏通信。
我们将创建一个新的Oled类,该类将代表 96x96 点阵 OLED 显示屏,并使用其默认的 8x8 字符框来显示文本。我们将使用SSD1327类与 OLED 显示屏进行交互。以下行显示了与upm库一起工作的新Oled类的代码,特别是与pyupm_i2clcd模块及其SSD1327类。示例代码文件为iot_python_chapter_08_02.py:
class Oled:
# The I2C address for the OLED display
oled_i2c_address = 0x3C
def __init__(self, bus, red, green, blue):
self.oled = upmLcd.SSD1327(
bus,
self.__class__.oled_i2c_address)
self.oled.clear()
def print_line(self, row, message):
self.oled.setCursor(row, 0)
self.oled.setGrayLevel(12)
self.oled.write(message)
Oled类声明了oled_i2c_address类属性,该属性定义了 OLED 显示屏的 I²C 地址,即一旦光标定位在特定的行和列,将处理定位光标和写入文本的命令的地址。该地址为十六进制的3C(0x3C)。
在创建Oled类的实例时,我们必须指定 OLED 显示屏所连接的 I²C 总线编号,该参数在bus必需参数中。构造函数,即__init__方法,使用接收到的bus参数和oled_i2c_address类属性创建一个新的upmLcd. SSD1327实例,并将新实例的引用保存在oled属性中。最后,代码调用新实例的clear方法以清除 OLED 屏幕。
该类声明了print_line方法,以便于在特定行上打印文本。代码调用upmLcd.SSD1327实例(self.oled)的setCursor方法,将接收到的row值作为row参数的值,并将0作为column参数的值,以将光标定位在指定的行和第一列。然后,调用setGrayLevel和write方法,将upmLcd.SSD1327实例(self.oled)的message接收作为参数的参数,以默认的 8x8 字符框和灰度设置为 12 打印接收到的字符串到 OLED 显示屏。在幕后,upmLcd.SSD1327实例将通过 I²C 总线将数据写入地址等于oled_i2c_address类属性的从设备,以指定光标所需的位置,然后从我们定位光标的位置开始写入指定的文本。
现在,我们将创建一个名为TemperatureAndHumidityOled的子类,该子类基于之前编写的Oled类。这个子类将专门化Oled类,使我们能够轻松地打印华氏度表示的温度值、摄氏度表示的温度值以及百分比表示的湿度值。我们将使用之前解释过的文本布局。以下行显示了新TemperatureAndHumidityOled类的代码。示例的代码文件是iot_python_chapter_08_02.py。
class TemperatureAndHumidityOled(Oled):
def print_temperature(self, temperature_fahrenheit, temperature_celsius):self.oled.clear()
self.print_line(0, "Temperature")
self.print_line(2, "Fahrenheit")
self.print_line(3, "{:5.2f}".format(temperature_fahrenheit))
self.print_line(5, "Celsius")
self.print_line(6, "{:5.2f}".format(temperature_celsius))
def print_humidity(self, humidity):
self.print_line(8, "Humidity")
self.print_line(9, "Level")
self.print_line(10, "{0}%".format(humidity))
新的类(TemperatureAndHumidityOled)向其超类(Oled)添加了以下两个方法:
-
print_temperature: 多次调用print_line方法来显示接收到的温度参数,以华氏度(ºF)和摄氏度(ºC)的形式 -
print_humidity: 多次调用print_line方法来显示接收到的湿度参数
小贴士
在这种情况下,我们刷新许多行来更改仅几个值。由于我们将每 10 秒运行一个循环,所以这不会成为问题。然而,在其他情况下,如果我们想在更短的时间内更新值,我们可以编写优化后的代码,该代码仅清除单行并更新该行中的特定值。
现在,我们将编写一个循环,每 10 秒在 OLED 屏幕上显示以华氏度(ºF)和摄氏度(ºC)表示的环境温度以及以百分比表示的湿度值。示例的代码文件是iot_python_chapter_08_02.py。
if __name__ == "__main__":
temperature_and_humidity_sensor = \
TemperatureAndHumiditySensor(0)
oled = TemperatureAndHumidityOled(0)
while True:
temperature_and_humidity_sensor.\
measure_temperature_and_humidity()
oled.print_temperature(
temperature_and_humidity_sensor.temperature_fahrenheit,
temperature_and_humidity_sensor.temperature_celsius)
oled.print_humidity(
temperature_and_humidity_sensor.humidity)
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_and_humidity_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_and_humidity_sensor.temperature_fahrenheit))
print("Ambient humidity: {0}".
format(temperature_and_humidity_sensor.humidity))
# Sleep 10 seconds (10000 milliseconds)
time.sleep(10)
高亮行显示了与上一个版本相比在__main__方法中做出的更改。第一条高亮行使用0作为bus参数的值创建了一个之前编写的TemperatureAndHumidityOled类的实例。代码将此实例的引用保存在oled局部变量中。这样,实例将通过 I²C 总线与 OLED 屏幕建立通信。
然后,代码无限循环运行,高亮行调用oled.print_temperature方法,并使用temperature_and_humidity_sensor.temperature_fahrenheit和temperature_and_humidity_sensor.temperature_celsius作为参数。这样,代码在 OLED 屏幕的第一行显示这两个温度值。
下一条高亮行调用了oled.print_humidity方法,并使用temperature_and_humidity_sensor.humidity。这样,代码通过多行来显示这个湿度值在 OLED 屏幕的底部。
以下行将启动示例:
python iot_python_chapter_08_02.py
运行示例后,打开空调或加热系统以产生环境温度和湿度的变化。OLED 屏幕将显示温度和湿度,并且每 10 秒刷新一次。
连接伺服电机
到目前为止,我们一直在使用传感器从现实世界获取数据,并在液晶显示屏和 OLED 显示屏上显示信息。然而,物联网设备并不仅限于感应和显示数据,它们还可以移动物体。我们可以将不同的组件、屏蔽或分线板连接到我们的英特尔 Galileo Gen 2 板上,并编写 Python 代码来移动连接到板上的物体。
标准伺服电机对于精确控制轴并使其在 0 到 180 度之间的各种角度定位非常有用。在第四章中,使用 RESTful API 和脉冲宽度调制,我们使用了脉冲宽度调制,简称 PWM,来控制 LED 和 RGB LED 的亮度。我们还可以使用 PWM 来控制标准模拟伺服电机,并使其轴在特定角度定位。
小贴士
标准伺服电机是包含齿轮和反馈控制回路电路的直流电机,它提供了精确的位置定位。它们非常适合齿轮转向、机器人手臂和腿部,以及其他需要精确定位的应用。标准伺服电机不需要电机驱动器。
显然,不是所有的伺服电机都具有相同的特性,我们在为我们的项目选择特定的伺服电机时必须考虑许多因素。这取决于我们需要定位什么,精度,所需的扭矩,最佳伺服电机旋转速度等因素。在这种情况下,我们将专注于使用 PWM 定位标准伺服电机。然而,你不能用同一个伺服电机旋转比你需要旋转的重型机械臂更轻的塑料件。对于每个任务,有必要研究合适的伺服电机。
现在,我们将把标准高灵敏度微型伺服电机连接到我们的现有项目中,并将旋转轴以华氏度数显示测量的温度。轴将允许我们在半圆形的量角器上显示测量的温度,该量角器以度为单位测量角度,并将显示从 0 到 180 度的角度数值。伺服电机与轴和量角器的组合将允许我们通过移动部件显示温度。然后,我们可以创建自己的量角器,带有可以添加颜色、特定阈值和许多其他视觉效果的刻度,使温度测量更有趣。具体来说,我们可以创建一个仪表盘图表、速度计或半圆形甜甜圈,即一个饼图和甜甜圈的组合,在单个图表中显示不同的温度值。以下图片显示了我们可以与伺服电机和轴一起使用的半圆形量角器示例。
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_08_05.jpg
我们需要以下附加部件来使用此示例:SeeedStudio Grove 伺服或 EMAX 9g ES08A 高灵敏度迷你伺服。以下网址提供了关于这些伺服的详细信息:www.seeedstudio.com/depot/Grove-Servo-p-1241.html 和 www.seeedstudio.com/depot/EMAX-9g-ES08A-High-Sensitive-Mini-Servo-p-760.html。
下图显示了数字温度和湿度扩展板、LCD RGB 背光扩展板、迷你伺服、必要的布线以及从 Intel Galileo Gen 2 板到面包板的布线。示例的 Fritzing 文件为iot_fritzing_chapter_08_03.fzz,以下图片是面包板视图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_08_06.jpg
下图显示了用符号表示的电子组件的原理图:
https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/iot-py/img/B05042_08_07.jpg
如前图所示,我们在现有项目中添加了以下附加连接:
-
在电路符号中标有5V的电源针与标有**+**的伺服针相连。伺服通常使用红色电线进行此连接。
-
在电路符号中标有D3 PWM的具有 PWM 功能的 GPIO 针与标有PULSE的伺服针相连。伺服通常使用黄色电线进行此连接。
-
在电路符号中标有GND的接地针与标有**-**的伺服针相连。伺服通常使用黑色电线进行此连接。
现在,是时候进行所有必要的布线了。在添加或从板上的引脚移除任何电线之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
使用伺服电机定位轴以指示值
我们可以使用mraa.Pwm类来控制标有**~3**的具有 PWM 功能的 GPIO 针上的 PWM,正如我们在第四章中学习的,使用 RESTful API 和脉宽调制。然而,这需要我们阅读伺服的详细规格。upm库在pyupm_servo模块中包括了对 SeeedStudio Grove 伺服或 EMAX 9g ES08A 高灵敏度迷你伺服的支持。在此模块中声明的ES08A类代表连接到我们板上的两种提到的伺服器中的任何一个。
该类使得设置伺服轴所需的角度并使用角度而不是占空比和其他 PWM 细节进行工作变得容易。该类在幕后与mraa.Pwm类一起工作以配置 PWM 并根据轴所需的角度控制占空比。
我们将使用之前示例中编写的代码,并将此代码作为添加新功能的基准。示例代码文件为iot_python_chapter_08_02.py。
我们将创建一个TemperatureServo类来表示伺服器,并使我们能够根据华氏度表示的温度将轴定位在有效角度(从 0 到 180 度)内。我们将使用ES08A类与伺服器交互。以下行显示了与upm库一起工作的新TemperatureServo类的代码,特别是与pyupm_servo模块一起工作。示例的代码文件是iot_python_chapter_08_03.py。
import pyupm_th02 as upmTh02
import pyupm_i2clcd as upmLcd
import pyupm_servo as upmServo
import time
class TemperatureServo:
def __init__(self, pin):
self.servo = upmServo.ES08A(pin)
self.servo.setAngle(0)
def print_temperature(self, temperature_fahrenheit):
angle = temperature_fahrenheit
if angle < 0:
angle = 0
elif angle > 180:
angle = 180
self.servo.setAngle(angle)
在创建TemperatureServo类的实例时,我们必须指定与伺服器连接的引脚号,作为pin必需参数。构造函数,即__init__方法,使用接收到的pin作为其pin参数创建一个新的upmServo.ES08A实例,将其引用保存在servo属性中,并调用其setAngle方法,将0作为angle必需参数的值。这样,底层代码将根据angle参数中接收到的值配置 PWM 启用 GPIO 引脚的输出占空比,以将轴定位在所需的角位置。在这种情况下,我们希望轴定位在 0 度。
该类定义了一个print_temperature方法,该方法接收一个以华氏度(ºF)表示的温度值,作为temperature_fahrenheit参数。代码定义了一个angle局部变量,确保所需的轴角度在有效范围内:从 0 到 180 度(包括)。如果temperature_fahrenheit参数中接收到的值低于0,则angle值将为0。如果temperature_fahrenheit参数中接收到的值大于180,则angle值将为180。然后,代码使用angle作为参数调用upmServo.ES08A实例(self.servo)的setAngle方法。在底层,upmServo.ES08A实例将根据angle参数中接收到的值配置 PWM 启用 GPIO 引脚的输出占空比,以将轴定位在所需的角位置。这样,只要温度值在 0 到 180 华氏度(ºF)之间,轴将定位在与其接收到的华氏度(ºF)温度相同的角位置。
如果温度太低(低于 0 华氏度),则轴将保持在 0 度角度。如果温度高于 180 华氏度,则轴将保持在 180 度角度。
现在,我们将修改我们的主循环,每 10 秒显示以华氏度(ºF)表示的环境温度,并用轴表示。示例的代码文件是iot_python_chapter_08_03.py。
if __name__ == "__main__":
temperature_and_humidity_sensor = \
TemperatureAndHumiditySensor(0)
oled = TemperatureAndHumidityOled(0)
temperature_servo = TemperatureServo(3)
while True:
temperature_and_humidity_sensor.\
measure_temperature_and_humidity()
oled.print_temperature(
temperature_and_humidity_sensor.temperature_fahrenheit,
temperature_and_humidity_sensor.temperature_celsius)
oled.print_humidity(
temperature_and_humidity_sensor.humidity)
temperature_servo.print_temperature(
temperature_and_humidity_sensor.temperature_fahrenheit)
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_and_humidity_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_and_humidity_sensor.temperature_fahrenheit))
print("Ambient humidity: {0}".
format(temperature_and_humidity_sensor.humidity))
# Sleep 10 seconds (10000 milliseconds)
time.sleep(10)
突出的行显示了与上一个版本相比对__main__方法所做的更改。第一条突出显示的行使用3作为pin参数的值创建了一个先前编码的TemperatureServo类的实例。代码将此实例的引用保存在temperature_servo局部变量中。这样,该实例将为 3 号引脚配置 PWM 并将轴定位在0度。
然后,代码无限循环运行,突出显示的行调用temperature_servo.print_temperature方法,并将temperature_and_humidity_sensor.temperature_fahrenheit作为参数。这样,代码使轴指向温度计中的温度值。
以下行将开始示例。
python iot_python_chapter_08_03.py
在运行示例后,打开空调或供暖系统并生成环境温度的变化。您将注意到轴每 10 秒开始移动以反映温度的变化。
测试你的知识
-
英特尔 Galileo Gen 2 板可以作为 I²C 总线主控器,并允许我们:
-
只要它们的 I²C 地址不同,就可以将许多从设备连接到 I²C 总线上。
-
只要它们的 I²C 地址相同,就可以将许多从设备连接到 I²C 总线上。
-
只要它们的 I²C 地址不同,就可以将最多两个从设备连接到 I²C 总线上。
-
-
一个 16x2 液晶模块允许我们显示:
-
每行 16 个字符,共两行。
-
每行 16 个字符,每个字符 2 个。
-
每行 16 个字符,每个字符 3 个。
-
-
一个 16 灰度 96x96 点阵 OLED 显示器模块允许我们控制:
-
每行 96 个字符,共 96 行。
-
一行有 96 个点或 96 个字符,具体取决于我们如何配置 OLED 显示器。
-
9,216 个点(96*96)。
-
-
一个 16 灰度 96x96 点阵 OLED 显示器,带有 8x8 字符框,允许我们显示:
-
每行 96 个字符,共 96 行:96 列和 96 行。
-
每行 16 个字符,共 16 行:16 列和 16 行。
-
每行 12 个字符,共 12 行:12 列和 12 行。
-
-
标准伺服允许我们:
-
在 OLED 显示器上显示文本。
-
将轴定位在各个特定角度。
-
通过指定所需的纬度和经度来将轴移动到特定位置。
-
摘要
在本章中,我们学习了我们可以通过 I²C 总线连接到我们的板上的不同显示器。我们使用了一个液晶显示器、一个 RGB 背光,然后将其替换为 OLED 点阵。
我们编写了利用upm库中包含的模块和类来简化我们与 LCD 和 OLED 显示屏以及在其上显示文本的代码。此外,我们还编写了与模拟伺服电机交互的代码。我们不是编写自己的代码来根据轴的期望位置设置输出占空比,而是利用了upm库中的一个特定模块和一个类。我们可以控制轴,以便创建一个仪表图表来显示通过传感器获取的温度值。我们的 Python 代码可以使物体移动。
现在我们能够将数据展示在黑板上并使用伺服电机,我们将把我们的物联网设备连接到整个世界,并使用云服务,这正是下一章的主题。
797

被折叠的 条评论
为什么被折叠?



