Python-微控制器编程教程-全-

Python 微控制器编程教程(全)

原文:Programming Microcontrollers with Python

协议:CC BY-NC-SA 4.0

一、做好准备

您已经决定使用 Python 学习微控制器。尽管直接投入到开发很酷的东西是很好的,但是有几个步骤你必须首先做好准备。如果你有软件开发的经验,你可能习惯于打开一个编辑器,然后马上输入代码。然而,微控制器开发比这要复杂一些。毕竟,你将为一台单独的计算机编写程序,所以有更多的东西需要设置。微控制器开发环境不仅包括软件,还包括一个完整的硬件生态系统,必须理解这个生态系统才能释放这些器件的所有魅力。

在这一章中,我们将着眼于建立一个开发环境。本章结束时,您将准备好选择微控制器板,并拥有开始使用 CircuitPython 进行开发所需的软件工具和硬件设备。我们开始吧!

微控制器简介

在我们的现代世界,计算机无处不在。它们变得更小,更容易使用,并且更加融入我们的日常生活。几年前,要连接互联网和查看电子邮件,你必须坐在一个大型桌面设备前。今天,你可以用一台手掌大小的电脑做同样的事情,把世界上所有的信息都放在你的指尖。

科技进步给我们带来了智能手机和平板电脑,也让数十亿人拥有了电脑。然而,这些手持设备并不是唯一存在的计算机。如果我告诉你,与静静地围绕在我们周围的另一种计算设备相比,数十亿部正在使用的智能手机相形见绌,会怎么样?事实上,现在在你的房间里可能有半打或更多这样的设备。这些微型计算机被称为微控制器,或MCU,它们在我们身边随处可见。

微控制器是小型、智能、可编程的计算机,我们用它来执行重复性的任务,或者需要某种程度的智能控制而不需要人工干预的任务。微控制器设备的计算能力只是智能手机或平板电脑等设备的一小部分,但它们有很多很多用途。你周围的任何设备,只要一按下按钮就会做出反应,显示信息,或者在出错时发出声音,都很可能是由微控制器控制的。从计算器到电视到微波炉和洗碗机,你能想到的几乎每一个家用设备都包含一个微控制器。

如果你熟悉计算机,你肯定听说过中央处理器(CPU)或微处理器:计算机背后的大脑。微处理器让计算机执行许多功能。某一天你可能用电脑来打文件,下一天你可能用电脑来播放你最喜欢的电影,等等。微处理器有许多支持电路,包括存储器和输入输出芯片,使它能够执行所有这些任务。这些通常是容纳微处理器的主板的一部分。

微控制器将微处理器工作所需的所有支持芯片放在一个芯片上。这就是为什么微控制器被称为单片计算机。微控制器仍然像微处理器一样工作,但它被设计成只执行一项任务,并且尽可能高效地执行。因此,微控制器芯片包含完成任务所需的最小处理能力。

固件、内存和时钟速度

像通用计算机一样,微控制器通过运行程序来工作。因为微控制器执行有限的功能,所以为它们编写的程序不会经常改变。因此,为微控制器编写的程序被称为固件。该固件通常存储为二进制文件(具有. bin 文件扩展名)或十六进制文件,后者是文本表示的二进制文件(具有. hex 文件扩展名);该文件包含写入 flash 的存储器内容,因此可以在微控制器上运行。一种叫做编程器的特殊设备将这个二进制文件加载到微控制器的存储器中。

存储器由两部分组成:主存储器(有时称为程序存储器)和随机存取存储器(ram,有时称为数据存储器)。主存储器是非易失性的,而 RAM 是易失性的。这意味着当微控制器断电时,RAM 中包含的信息将会消失,而主存储器中的信息将会保留。因此,主存储器也被称为只读存储器(ROM)。它的内容被设计成主要被读取,而 RAM 被设计成在运行时由用户改变其内容。

在通用计算机系统中,通常一个程序在执行前被装入 ram。微控制器系统有点不同。在微控制器中,固件直接从 rom 中执行,RAM 用于存储帮助运行固件的临时信息(这些通常是运行时变量,可以包含堆栈或堆,以及特殊的内存结构)。

有些微控制器器件可以在出厂时就将 ROM 内容编程到器件中,一旦装入器件,最终用户就无法更改。这些被称为一次性可编程(OTP)设备,它们更便宜,更容易制造。不过,通常 ROM 是由闪存制成的,这意味着它可以在制造后进行编程和更改。

闪存可让您对设备进行数千次编程,这对于学习和产品原型制作非常有用。如果固件程序中出现错误,flash 还允许您通过更新来修复错误,这一过程称为修补。更新通常采取空中下载(OTA)更新的形式,通过无线连接改变 ROM 的内容。OTA 更新对于物联网(IoT)设备来说很常见。或者,您可以使用电缆将设备连接到电脑来更新固件。

微控制器根据运行设备的时钟速度执行其固件程序中的指令。速度是以赫兹或每秒钟的周期数来衡量的。时钟速度越快,设备执行指令就越快。微控制器的时钟速度通常在 1 MHz 左右(尽管对于极低成本、低功耗的应用,时钟速度可以低至 32.768 kHz,对于高速系统,时钟速度最高可达 1 GHz。)

8 位与 32 位

我们将在本书中使用的设备非常强大。它们由 32 位微处理器内核组成,这意味着微控制器可以处理的数据位数由 32 个寄存器组成(寄存器是微控制器内数据的小存储单元;我们将在下一章详细讨论这一点)。然而,在很长一段时间里,8 位设备统治着微控制器市场。

历史上,32 位设备价格昂贵且难以编程。由于技术的进步,32 位设备的成本已经下降到可以与 8 位设备竞争的程度,但对成本极其敏感的应用除外。与此同时,由于现在有无数的工具可以使用,32 位设备已经变得非常容易编程和控制。虽然 8 位微控制器仍然存在并具有相关性,但它们的许多市场份额正在被 32 位设备取代。

对于初学者来说,32 位设备是一个特别有用的学习工具,因为它们包含更多的内存和更大的内存寻址能力。这允许更高层次的抽象,意味着在没有完全理解其内部工作原理的情况下,通常更容易对 32 位设备进行编程。相比之下,由于 8 位设备的处理能力和内存较小,因此您需要更深入地了解设备的内部,以便更好地管理内存资源和编写有效的程序。

微控制器编程语言

在本节中,我们将了解一些可用于微控制器编程的语言。虽然有几个选项可供选择,但微控制器对构成现代软件开发环境的拥挤不堪的编程语言大多持抵制态度。从历史上看,微控制器领域一直由 C 编程语言主导。这是因为微控制器传统上只有几个字节的内存,并且以几十兆赫的时钟速度运行,而 C 特别适合在内存受限的系统上工作。然而现在,你可以找到拥有运行频率高达千兆赫的多核微控制器设备,并拥有数兆字节的内存,为其他语言的使用开辟了空间。

汇编语言

曾经有一段时间,微控制器完全是用汇编语言编程的。今天,汇编语言是为内存有限的设备和程序员需要充分发挥微控制器性能的情况而保留的。汇编语言在这些情况下很有用,因为许多汇编指令在微控制器上直接翻译成机器指令。这意味着指令执行的开销更少,使得用汇编语言编写的代码段更快。虽然汇编语言速度很快,但在设备上执行一个简单的任务需要大量指令。汇编语言的另一个限制因素是,对于你正在编程的每一个设备,你必须学习那个设备的汇编语言。鉴于这些限制,除了小众场合,这种语言的受欢迎程度已经下降。

C

长期以来,c 语言一直是嵌入式领域的首选语言。它可以在内存受限的设备上运行,比如微控制器。C 给了我们有效控制底层硬件的能力——一条 C 指令翻译成几条汇编语言指令——而且它可以和大多数应用程序的汇编语言速度相匹配。由于 C 语言已经使用了很长时间,所以许多代码都经过了实战测试,并被证明适用于它们的应用程序。c 用户可以访问包含有用信息和代码片段的大型代码库。然而,这种语言需要对硬件有很好的理解,初学者很难掌握。

C++

随着时间的推移,嵌入式设备变得越来越强大,一些微控制器制造商和软件供应商开始为他们的设备添加 C++支持。C++正在专业嵌入式领域慢慢获得牵引力。然而,在业余爱好者领域,C++在 Arduino 平台上得到了广泛的应用。然而,C++是一门庞大而难学的语言。许多使 C++在通用计算应用中比 C 更有效的特性,有时不能在资源受限的微控制器设备上实现。这是因为虽然 C++在大多数应用程序中的性能可以与 C 相媲美,但 C++倾向于使用更多的内存,这是一种宝贵的资源,通常在微控制器设备上并不丰富。因此,C++是为高端设备保留的。

基本的

在 21 世纪初,如果一个初学者开始接触微控制器,并且不喜欢汇编,BASIC 是可以使用的编程语言。BASIC 代表初学者的通用符号指令代码,是一种易于使用的编程语言。微控制器芯片上通常有一个基本的解释程序来运行指令。

BASIC 最终不受欢迎,因为运行它的主板相对于它们的能力来说花费了很多钱。此外,运行 BASIC 解释程序会降低芯片速度,并在已经受限的 8 位设备上占用太多资源。此外,最流行的基本设备的工具和软件都是闭源的,所以人们不能制造他们自己的基本设备。当像 Arduino 这样的开源替代品出现时,像 BASIC Stamp 这样的设备就不再受欢迎了。

Rust 编程语言相对于 C(几乎有半个世纪的历史)来说是新的,它被设计用来颠覆 C 和 C++对系统编程的控制,包括嵌入式编程。随着微控制器变得更加强大,并发性(一次执行多个进程的能力)等因素开始变得重要,Rust 相对于 C 的优势开始显现。Rust 更适合并发性,因为它可以处理数据竞争,即两个设备试图同时访问内存中的同一位置。

虽然 Rust 可能会取代 C,但业界没有理由在短期内采用它。嵌入式软件被称为固件是有原因的:它不会经常改变,而且许多已经写好的代码没有理由改变成一种新的语言。c 工作并且有许多已建立的工具链和设备,并且有许多熟练的开发人员熟悉这种语言。然而,已经有工具允许 Rust 用于微控制器,随着时间的推移,Rust 可能会在嵌入式市场获得一些份额。

计算机编程语言

Python 相对来说是嵌入式领域的新手,它可能会成为该领域的主要参与者。Python 比 C 语言简单得多,是当今最流行的编程语言之一。虽然对于初学者来说,BASIC 比 C 容易,但是 Python 比 BASIC 有几个优点,这使它更适合作为嵌入式语言使用。值得注意的是,虽然流行的基本微控制器是闭源的,但 Python 是开源的,如果您愿意,可以在您的定制设备上运行 Python。Python 文件也可以被编译成更小的文件,这样你就可以创建紧凑的、节省内存的程序。

很多人说 Python 这样的解释语言不适合微控制器的局限性。这可能曾经是真的,但随着今天更强大的设备,微控制器完全有可能运行解释语言,而不会像旧的基本设备那样遇到速度限制。对于非常省时的计算,也称为实时计算,解释语言仍然不适合。然而,Python 应该可以满足大多数微控制器项目的速度要求。

虽然 Python 在微控制器上运行时没有 C 语言快或高效,但它的易用性使它值得一试,尤其是如果你现在开始接触微控制器的话。此外,您可以用 C 扩展 Python 代码,这意味着您可以利用现有的 C 代码库,这些代码库经过了几十年的考验和改进。

存在于通用计算机上的 Python 解释器不能直接在具有相同可用特征的微控制器上实现。这是因为标准 Python 解释器是一个大型程序,它依赖于操作系统提供的功能,尤其是内存和硬件接口功能,而这在微控制器设备上是不存在的。语言解释器的两种修改形式,MicroPython 和 CircuitPython,在标准 Python 解释器和嵌入式空间之间架起了一座桥梁。其中,MicroPython 更面向专业开发人员,领先于 CircuitPython。由 Adafruit 开发的 CircuitPython 使用起来更简单,使其成为初学者的优秀学习工具以及专业人士的平台。CircuitPython 用户友好的主要特点是,在微控制器上运行程序之前,您不需要编译程序。一旦你保存了一个程序,它就会运行并执行。

CircuitPython 有望在更多资源受限的设备上使用,并有望在可预见的未来保持良好的支持。由于这些原因,我们将在整本书中使用它。

选择开发板

为了完成本书中的项目,您需要一个带有微控制器的开发板,可以运行 CircuitPython。开发板包含一个微控制器以及为开发板供电并使其启动和运行所需的连接。开发板允许您对微控制器进行使用、编程和原型制作,而无需担心硬件设置。

在撰写本文时,CircuitPython 支持超过 140 种电路板,并且这个列表还在不断增长。你可以在 CircuitPython 网站这里查看名单: https://circuitpython.org/downloads 。这些兼容的板中的任何一个都可以和这本书一起工作。您也可以选择创建自己的支持 CircuitPython 的定制设备,这个过程我将在本章末尾讨论。然而,对于初学者来说,使用预制的 CircuitPython 兼容板总是更好的选择。它将确保硬件正常工作,并允许您更快地开始为您的设备编写软件。

在本节中,我们将了解一些可以运行 CircuitPython 的预配置设备。尽管许多公司提供能够运行 CircuitPython 的微控制器板,但 Adafruit 设备拥有最好的支持,因为它们首创了 CircuitPython 语言,并拥有一个围绕 circuit python 构建的完整生态系统及其开发环境。我们将看看他们提供的一些电路板,以及其他制造商提供的可以与 CircuitPython 一起使用的流行电路板。这个列表并不详尽,但是这里介绍的板将与本书中讨论的例子兼容。

阿达水果 M0 地铁快线

我们要看的第一块广告牌是 Adafruit Metro M0 快车,如图 1-1 所示。该板由 SAMD21G18A 微控制器供电,是配合本书示例使用的理想选择。该板还具有 Arduino 外形;这意味着它可以与现有的 Arduino 盾牌一起使用。因此,强大的 Arduino 生态系统可以很容易地用 Python 中的 Arduino shields 进行原型设计。SAMD21G18A 代表运行 CircuitPython 所需的“理想最小值”。它的特性允许它运行解释器没有任何停顿。SAMD21G18A 具有 48 MHz 时钟、256KB 闪存和 32KB RAM。(相比之下,由 ATmega328 微控制器供电的电路板,如 Arduino Uno,其闪存和 RAM 分别减少了 8 倍和 16 倍。)M0 地铁快车还有 2MB 的闪存,可以用来存储程序和其他文件。与 SAMD21G18A 相比,您可以使用更少的内存或更少的处理能力来运行 CircuitPython 解释器,但体验可能并不完美。

SAMD21G18A 微控制器是首批支持 CircuitPython 的器件之一,围绕该器件构建的电路板通常是首批获得最新版本解释器的器件。特别是,M0 地铁快线是第一个使用 CircuitPython 设计的 Adafruit 地铁板。它被认为是运行 CircuitPython 的标准板,它将能够体面地运行本书中的程序。

img/511128_1_En_1_Fig1_HTML.jpg

图 1-1

阿达果地铁 M0 快车[信用:阿达果,adafruit.com]

阿达果羽毛 M0 快递

如果你想要一个更简约的开发方法,你可以从 Adafruit 获得羽毛 M0 快车,如图 1-2 所示。因为它也是围绕 SAMD21G18A 处理器构建的,所以该板具有 Metro M0 Express 的所有功能:相同的 48 MHz 时钟、256KB 闪存和 32KB RAM。它还拥有相同的板载 2MB 闪存。然而,它比 Metro 更紧凑,并且少了 5 个 I/O 引脚——20 个而不是 25 个。

这种板的一个很酷的特点是前面的小原型区域,这是 M0 地铁快车所没有的。当你已经熟悉了 M0 地铁快线,并且你想要一个板嵌入到你自己的项目中,那么你可以使用这个更小、更便宜的板。还有 Adafruit 的 QT Py 板,如果您的项目需要更小的板,它甚至比 M0 Express 板更紧凑。

img/511128_1_En_1_Fig2_HTML.jpg

图 1-2

阿达果羽毛 M0 快递[信用:阿达果,adafruit.com]

阿达水果 M4 地铁快线

如果你需要比 M0 地铁快线更多的魅力,那么你可以搭乘 M4 地铁快线,如图 1-3 所示。该板由 SAMD51J19 微控制器供电,其性能优于之前讨论的基于 SAMD21G18A 的器件。在 120 MHz 时,它的运行速度是基于 SAMD21G18A 的主板的两倍多,它具有 512KB 的闪存、192KB 的 RAM 和额外的 2MB 板载闪存。

这些增强功能为数字信号处理(DSP)和浮点处理应用提供了更好的支持。如果您希望将您的主板用于音频处理等应用,或者如果您正在寻求更好的安全功能或总体性能改进,那么这是一款不错的主板。然而,性能的提高是有代价的。M4 地铁快线的处理器将比之前讨论的主板消耗更多的能量。不过,根据您的应用,这可能不是一个很大的因素,因为电路板仍然是高能效的。

img/511128_1_En_1_Fig3_HTML.jpg

图 1-3

阿达果 M4 地铁快车[信用:阿达果,adafruit.com]

Adafruit Grand Central M4 高速公路

我们可以用来和这本书一起工作的下一块板是阿达果大中央 M4 快车,如图 1-4 所示。与 Metro M0 相比,该板非常强大,因为它具有 ATSAMD51P20 微控制器。它的运行速度快了 2.5 倍,并且该板的 RAM 大小是 Metro M0 的 8 倍。事实上,大中央车站的内存量相当于整个 M0 地铁的闪存量(让它沉下去一点)。Grand Central 还具有 1MB 的闪存,通过 QSPI 芯片的 8MB 板载闪存,以及内置的 SD 卡插座。该板的 I/O 几乎是 M0 地铁上的三倍。当您有一个大型应用程序,并且在一个更基本的主板上耗尽了空间(内存或 I/O)时,这个主板是一个很好的迁移途径。

Grand Central 绝对可以用来运行本书中的所有示例,当您准备好开始自己的更复杂的 CircuitPython 项目时,像这样使用 ATSAMD51 微控制器的电路板是理想的选择。由于其充足的资源,机器学习和视频游戏编程等应用可以通过该板完成。

img/511128_1_En_1_Fig4_HTML.jpg

图 1-4

阿达果大中央 M4 快递[信用:阿达果,adafruit.com]

Arduino Zero

Arduino Uno 无疑是有史以来最受欢迎的微控制器板之一,至今仍被广泛使用。它向我们展示了 8 位微控制器的强大功能。然而,世界在变,32 位微控制器已经接管了很多曾经由 8 位设备控制的细分市场。Arduino Uno 平台有许多限制,因此 Arduino Zero 的创建是为了将 32 位开发带入 Arduino 世界。图 1-5 中的 Arduino Zero 向 Arduino 平台的用户提供 32 位嵌入式计算迈出了一步。

Arduino Zero 由 SAMD21G18A 微控制器供电,该微控制器与 Metro M0 和 Feather M0 电路板中使用的微控制器相同。虽然它缺少 Adafruit Metro 主板上的板载闪存,但 Arduino 仍然可以运行相当大的程序,并将与本书中提供的所有示例一起工作。与前面讨论的其他开发板不同,虽然开源(是的,所有硬件原理图和源代码都是可用的)特定于一个供应商,但 Arduino Zero 有许多开发板的克隆版本,您可以在其上运行 CircuitPython。这些复制品通常比制造商提供的官方主板便宜。

img/511128_1_En_1_Fig5_HTML.jpg

图 1-5

Arduino Zero[鸣谢:adafruit.com 阿达果]

STM32F746ZG 核子

基于 STM32 的电路板也可以运行本书中讨论的例子。例如,STM32F746ZG Nucleo 已经过本书中所有示例的测试,运行良好。这个板子,如图 1-6 所示,绝对是个庞然大物。它采用 STM32F746ZG 微控制器,其处理器的性能优于前面讨论的任何一种板。该板运行在惊人的 216 MHz,有 320KB 的内存和 1MB 的闪存。

STM32F746ZG 主要面向之前有微控制器经验的人。虽然它在 CircuitPython 上运行良好,但它并不像该系列中的其他主板那样开箱即用。尽管如此,该板的速度和性能使其值得用于通过 CircuitPython 执行计算密集型任务,如解码 MP3 文件或使用以太网,或者当您需要大量 I/O 时。

如果你想要更多的果汁,你可以考虑购买 STM32H743 Nucleo,它有一个运行频率高达 400 MHz 的微控制器。凭借 1MB 的内存和 2MB 的闪存,它可以轻松地与 CircuitPython 配合使用。这个板也不像其他一些板那样对初学者友好,而且它带有一些陷阱。例如,如果没有自己的支持,微控制器上的一些外来外设可能无法在 CircuitPython 中使用。其中包括 HDMI、DFSDM 和运算放大器等外设。此外,由于该板的工作频率为 400 MHz,功耗很大,因此不适合电池供电的应用。新的微控制器用户最好先从一块 Metro 板开始,然后再使用这块板。

img/511128_1_En_1_Fig6_HTML.jpg

图 1-6

STM32F746ZG Nucleo

设备比较

表 1-1 比较了可用于跟随本书的电路板的特性。选择合适的微控制器可能是一项艰巨的任务。在我看来,最好的微控制器是 Adafruit Grand Central M4,因为它功能强大,并得到 CircuitPython 的良好支持。然而,前面讨论的任何板都可以满足我们的目的。

表 1-1

设备比较

|

设备

|

时钟速度

|

随机存取存储

|

闪光

|

板载 SPI

闪存

|
| --- | --- | --- | --- | --- |
| 米-0 | 48 兆赫 | 32KB | 256 千字节 | 2MB |
| 羽毛 M0 | 48 兆赫 | 32KB | 256KB | 2MB |
| M4 公尺 | 120 兆赫 | 192KB | 512KB | 2MB |
| 大 M4 中心 | 120 兆赫 | 265KB | 1MB | 8MB |
| Arduino 零号 | 48 兆赫 | 32KB | 256KB | 没有人 |
| STM32F746ZG 核子 | 216 兆赫 | 320KB | 1MB | 没有人 |

组件列表

微控制器是我们电路的核心。然而,本书中的项目也需要支持组件和设备。表 1-2 列出了本书中需要用到的所有设备和组件。不要担心:每一部分都会在出现时详细讨论。为了方便起见,表中为每个商品提供了两个产品编号,一个来自 Amazon,一个来自另一个供应商。但是,欢迎您货比三家,寻找符合商品描述的其他选项。

表 1-2

组件列表

|

项目

|

|

小贩

|

产品编号

|
| --- | --- | --- | --- |
| 各种电阻器套件 | one | 贾梅克亚马逊 ASIN | Two million two hundred and seventeen thousand five hundred and elevenB07L851T3V |
| 10K 试验板微调电位计 | Two | 阿达果亚马逊 ASIN | Three hundred and fifty-sixB07S69443J |
| 220 μF 电容电解的 | one | 阿达果亚马逊 ASIN | Two thousand one hundred and ninety-twob07 sbw 1 和 17 战斗机 |
| 0.1 μF 陶瓷电容 | one | 阿达果亚马逊 ASIN | Seven hundred and fifty-threeB07RB4W4MR |
| 5 毫米红色指示灯 | one | 阿达果亚马逊 ASIN | Two hundred and ninety-nineB077X95F7C |
| 触摸开关按钮 | one | 阿达果亚马逊 ASIN | One thousand one hundred and nineteenB01GN79QF8 战斗机 |
| 10 lux 50–100 kohm 光敏电阻 | 至少 2 个 | 阿达果亚马逊 ASIN | One hundred and sixty-oneB01N7V536K |
| TMP36 温度传感器 | one | 阿达果亚马逊 ASIN | One hundred and sixty-fiveb11 GH 32 突击步枪 |
| MPU6050 加速度计和陀螺仪 | one | 阿达果亚马逊 ASIN | Three thousand eight hundred and eighty-sixB00LP25V1A |
| 4 通道逻辑电平转换器 | Two | 阿达果亚马逊 ASIN | Seven hundred and fifty-sevenB07LG646VS |
| CP2102 或 CP2104 USB-UART 转换器 | one | 阿达果亚马逊 ASIN | Three thousand three hundred and nineB07D6LLX19 战斗机 |
| 诺基亚 5110 GLCD | one | 阿达果亚马逊 ASIN | Three hundred and thirty-eight-= ytet-伊甸园字幕组=-翻译 |
| 固态盘 1306 | one | 阿达果亚马逊 ASIN | Three hundred and twenty-sixb076pdvfqd |
| 6V 爱好 DC 电机尺寸 130 | one | 阿达果亚马逊 ASIN | Seven hundred and elevenB07BHHP2BT |
| 微型伺服电机 MG90S 或 SG92R | one | 阿达果亚马逊 ASIN | One hundred and sixty-nineB07NV476P7 |
| 5V 500mA 步进电机 | one | 贾梅克电子公司亚马逊 ASIN | Two hundred and thirty-seven thousand eight hundred and twenty-fiveB00B88DHQ2 突击步枪 |
| ULN2003 达林顿阵列 | one | 数码电子亚马逊 ASIN | 497-2344-5-NDB07YSS1MQL |
| L293D 四路半 H 驱动器 | one | 阿达果亚马逊 ASIN | Eight hundred and sevenb200 nay 2 欧元 |
| DHT11 温度和湿度传感器 | one | 阿达果亚马逊 ASIN | Three hundred and eighty-sixb11 有 8 个 ZMWK |
| 公共阳极 RGB LED(最好是漫射的) | one | 阿达果亚马逊 ASIN | One hundred and fifty-nineb 0194 和 6 兆瓦 2 |
| HC-SR04 超声波传感器 | one | 阿达果亚马逊 ASIN | Three thousand nine hundred and forty-twoB004U8TOE6 |
| 压电扬声器 | one | 阿达果亚马逊 ASIN | One hundred and sixtyb21 gj 5 bs |
| 1N4001 通用硅二极管 | one | 阿达果亚马逊 ASIN | Seven hundred and fifty-fiveB0087YKE02 战斗机 |
| 400 连接点无焊试验板 | Two | 阿达果亚马逊 ASIN | Sixty-fourb7pcie 9 代 |
| 跳线男性对男性 | 至少 20 个 | 阿达果亚马逊 ASIN | Four thousand four hundred and eighty-twob07 会计年度第 7 季第 1 集 |
| 实验室工作台电源 | one | 亚马逊 ASIN 各种制造商 | B081SFKW2R |

关于电源,一个高质量的 1 安培墙上适配器可以作为通过这本书的最低要求。(避免使用便宜的 1A 适配器,因为很多时候它们很吵,会干扰正常的电路操作。)然而,如果你打算长期使用微控制器,一个更坚固的实验室电源是一个不错的投资。这在使用电机时尤其重要,因为电机在启动时会消耗大量功率。标准是使用 30V 5A 的实验台电源,如表 1-2 中建议的电源。这些电源几乎可以处理你扔给它们的任何东西。

管理部门编辑器

现在我们已经讨论了硬件,我们可以设置本书中练习所需的软件。使用 CircuitPython 对设备编程的最佳方式是使用 Mu 编辑器,它与 Windows、macOS 和 Linux 机器兼容。你可以从 https://codewith.mu/ 下载编辑器。

下载并安装了 Mu 编辑器后,用 USB 线把你的 CircuitPython 设备连接到你的电脑上(没错,就是这么简单!)并开辟 Mu。你会看到一个类似图 1-7 的窗口。

img/511128_1_En_1_Fig7_HTML.jpg

图 1-7

管理部门编辑器

如果您的 CircuitPython 设备已连接,您将能够读取设备上的文件(该文件是核心 CircuitPython 的一部分)。一旦 CircuitPython 出现在连接的设备上,一旦插入设备,code.py 文件就会出现在设备上。如果您选择的电路板不包含 CircuitPython,您可以在 https://learn.adafruit.com/installing-circuitpython-on-samd21-boards/overview 遵循本指南。点击编辑器上的加载按钮,如图 1-8 所示。

img/511128_1_En_1_Fig8_HTML.jpg

图 1-8

管理部门中的加载按钮

将出现一个对话框。Mu 将默认为文件的目录,但如果没有,则导航到您的 CircuitPython 设备的驱动器(CircuitPython 就像 USB 闪存驱动器一样显示)。你会看到一个 code.py 文件,如图 1-9 所示。。py 扩展名表示这是一个 Python 代码文件。

img/511128_1_En_1_Fig9_HTML.jpg

图 1-9

在管理部门中加载 code.py 文件

打开文件,您会看到它包含一行代码:print('Hello world!')。当一台普通的计算机运行print命令时,括号中包含的消息显示在终端中,在这个窗口中,您可以输入命令并查看代码的输出。但是,您的微控制器没有显示功能或内置终端窗口。幸运的是,CircuitPython 设备默认允许通过串行通信协议与主机通信。Mu 有一个称为串行监视器的特性,允许您查看微控制器的程序输出。它还会提醒您程序中可能存在的任何错误。

加载完代码文件后,点击 Mu 中的串口按钮,打开串口连接,如图 1-10 所示。

img/511128_1_En_1_Fig10_HTML.jpg

图 1-10

管理部门中的串行按钮

单击该按钮后,编辑器下方会弹出一个小的终端窗口。这将显示微控制器的串行输出。它看起来应该类似于图 1-11 。

img/511128_1_En_1_Fig11_HTML.jpg

图 1-11

串行输出窗口

点击编辑器中的保存按钮,您将会看到print语句已经执行并且正在串行输出窗口中运行。您的输出应该类似于图 1-12 。

img/511128_1_En_1_Fig12_HTML.jpg

图 1-12

在串行输出窗口中运行 code.py

串行终端有一些有用的特性。按 ctrl-C(或 Mac 的 command-C)将打开一个读取-评估-打印循环(REPL),由屏幕上的>>>符号指示,如图 1-13 底部所示。

img/511128_1_En_1_Fig13_HTML.jpg

图 1-13

在串行终端中打开电路 Python REPL

REPL 允许你逐行输入你的 Python 代码,并让你的微控制器执行它。当你在学习如何用一种新语言编程时,有时你只想快速尝试。REPL 是有用的,因为它为您提供了一种方便测试代码片断的方法,而无需编写整个程序。当您打开 REPL 时,微控制器停止执行它正在运行的程序。要离开 REPL 并让程序重新运行,请按 ctrl-D

其他串行通信工具

除了 Mu 的串行终端之外,还有其他工具允许您通过串行连接与开发板通信。如果您使用的是定制板,或者 Mu 在检测板时遇到问题,这些工具将会非常有用。在这些情况下,您有时会看到如图 1-14 所示的错误信息。当 Mu 找不到它认为可以与串行端口一起工作的连接板时,就会出现错误。

img/511128_1_En_1_Fig14_HTML.jpg

图 1-14

Mu 找不到连接的板时的错误消息

如果你用的是 macOS 或者 Unix 电脑,可以看看 CoolTerm 之类的程序,可以在 http://freeware.the-meiers.org/ 下载。在 www.mac-usb-serial.com/docs/tutorials/access-serial-port-on-mac-with-coolterm.html# 可以找到不错的 macOS 用户 CoolTerm 教程。请注意,在较新版本的 macOS 上,运行该程序可能会出现问题,因为开发者未经验证。为了解决这个问题,打开终端并输入sudo spctl –master-disable,这将允许你运行从任何地方下载的应用程序。

如果 Windows 用户在 Mu 内部使用串行终端时遇到问题,他们也可以使用名为 PuTTY 的程序与开发板建立串行连接。可在 www.chiark.greenend.org.uk/~sgtatham/putty/ 下载。PuTTY 允许计算机通过 Telnet 和 SSH 等协议相互通信。PuTTY 还可以与连接到计算机 COM 端口的虚拟设备通信。虽然我们使用 USB 电缆将 CircuitPython 设备连接到计算机,但该设备显示为计算机的串行端口。当 USB 设备被模拟为串行 COM 端口时,我们称之为虚拟 COM 端口(VCP),PuTTY 等程序允许我们从这个 VCP 读取信息,就像它是一个实际的串行端口一样。

下载并安装 PuTTY 后,在 Windows 计算机上,转到控制面板并打开设备管理器。在“端口”下查找您的设备,如图 1-15 所示。记下与您的主板相关的端口号。

img/511128_1_En_1_Fig15_HTML.jpg

图 1-15

COM 端口设备

现在,您可以使用 PuTTY 来打开与您的板的串行通信。为此,当您打开 PuTTY 时,使用单选按钮将连接类型设置为 Serial 。在串行线框中输入端口(在本例中为 COM9 ,将速度设置为 115200 ,然后按打开。如图 1-16 所示。

img/511128_1_En_1_Fig16_HTML.jpg

图 1-16

用 PuTTY 打开串行通信

PuTTY 允许您与微控制器交互,就像您从 Mu 中的串行终端与它交互一样。一旦在 PuTTY 和微控制器之间建立了串行连接,您将看到如图 1-17 所示的输出。PuTTY 还允许您访问 REPL。

img/511128_1_En_1_Fig17_HTML.jpg

图 1-17

PuTTY 中的成功连接

本节中概述的选项都是在 Mu 无法与您的主板建立串行连接时的替代选项。但是,如果您可以从 Mu 内部连接您的板,我建议您这样做,因为这是一种更方便的方法。

建立自己的董事会

Note

这一部分是为已经有使用微控制器经验的读者准备的,比如 Arduino 平台,但是如果你刚刚得到一个 Arduino Zero 并且想继续学习,那么你可以这样做!

开源的一个很好的特点是,你可以构建自己的设备副本,而不用担心任何后果。例如,CircuitPython 项目源于 MicroPython 项目,并拥有非常宽松的 MIT 许可证。这个许可意味着你可以用 CircuitPython 做任何你想做的事情,包括编译源代码和自己运行。也可以自己搭建硬件开发板,用 CircuitPython 编程。

虽然不建议刚接触微控制器的人创建定制板,但这一选项为经验丰富的用户提供了许多好处。建立自己的董事会给你很大的灵活性,尤其是如果你想开发自己的商业产品。制作自己的设备也比购买预制电路板便宜得多。例如,如果你正在设计一个成品,你不会将整个 Arduino Zero 或 Metro M0 板放入设备中。相反,你会有一个围绕标准微控制器设计的印刷电路板(PCB)。

如果您希望设置自己的 CircuitPython 板,最好选择 Microchip Technology 的 SAMD21G18A 微控制器。这个微控制器得到了 CircuitPython 的良好支持,也是最容易上手的。有两种方法可以将 CircuitPython 加载到您自己的板上。第一种是使用 Arduino IDE,第二种是使用 Atmel Studio。我将把重点放在 Arduino IDE 方法上,因为它更简单,更容易设置。此外,Atmel Studio 只能在 Windows 设备上运行,因此 Arduino IDE 方法可能是在 macOS 或 Linux 机器上工作的唯一方法。

你需要做的第一件事是从 Arduino IDE 中加载 Arduino Zero bootloader。最好使用 Atmel-ICE 编程器来完成这项工作。一旦你将你的 ISP 连接到你的目标,打开 Arduino IDE,进入工具编程器,选择 Atmel-ICE ,如图 1-18 所示。确保您有一个 LED 连接到引脚 PA17,并且设置了复位电路。

img/511128_1_En_1_Fig18_HTML.jpg

图 1-18

在 Arduino IDE 中选择程序员

选择好编程器后,将设置到 Arduino Zero(编程端口),如图 1-19 。

img/511128_1_En_1_Fig19_HTML.jpg

图 1-19

在 Arduino IDE 中选择电路板

然后选择刻录引导程序刻录引导程序,如图 1-20 所示。

img/511128_1_En_1_Fig20_HTML.jpg

图 1-20

在 Arduino IDE 中刻录引导程序

烧完引导程序后,通过选择文件示例基础知识闪现来运行一个示例草图。确保连接到 PA17 的 LED 在闪烁。

下一步是加载 USB 闪存格式(UF2)引导程序。这个引导程序允许微控制器显示为大容量存储设备,因此您可以在板上加载和运行 CircuitPython。

接下来,前往 CircuitPython 网站,在 https://circuitpython.org/board/arduino_zero/ 抓取 Arduino Zero 的 UF2 文件。该页面应类似于图 1-21 中的页面。点击 Download .UF2 Now 按钮,记下你保存文件的位置。

img/511128_1_En_1_Fig21_HTML.jpg

图 1-21

下载 CircuitPython UF2 文件

一旦你下载了这个文件,去 https://github.com/adafruit/uf2-samdx1/releases 下载最新版本的草图来更新 Zero 的引导程序。你会看到类似图 1-22 的东西。该文件的名称应该类似于update-boot loader-zero-v3-10-0 . ino,尽管版本号可能不同。

img/511128_1_En_1_Fig22_HTML.jpg

图 1-22

下载 Arduino 草图

打开草图并运行它。完成后,将 USB 电缆连接到您的微控制器。引脚 34 为 D+,引脚 33 为 D-。确保同时连接设备上的接地连接。微控制器现在应该在您的计算机上显示为可移动存储设备,内容如图 1-23 所示。

img/511128_1_En_1_Fig23_HTML.jpg

图 1-23

UF2 引导成功后的 CircuitPython 设备

将我们之前从 CircuitPython 网站下载的 UF2 文件复制到设备上。复制完成后,设备将显示为 CircuitPython 设备,如图 1-24 所示。

img/511128_1_En_1_Fig24_HTML.jpg

图 1-24

CircuitPython 在设备上运行时应该存在的文件

您的设备现在可以运行 CircuitPython 了。在撰写本文时,Mu 编辑器无法识别以这种方式安装了 CircuitPython 的设备,因此您需要使用 PuTTY 在 Windows 或终端上查看设备的输出,如果您使用的是 macOS 设备,则需要使用 CoolTerm。

作为参考,图 1-25 显示了基于 Arduino Zero 的器件的引脚排列,相当于我们刚刚设置的定制 SAMD21G18A 器件。如果你使用的是定制设备,你需要参考这张图片。它显示了所有的数字引脚、模拟引脚、通信和编程接口。

img/511128_1_En_1_Fig25_HTML.jpg

图 1-25

基于 Arduino Zero 的定制设备的引脚排列

结论

在这一章中,我们讲述了微控制器的基础知识,并了解了我们需要什么来设置我们的开发环境。一旦你完成了本章中的步骤,你的环境应该已经准备好用 Python 编程你的微控制器了。然而,对电子学有一个基本的了解将帮助你更有效地使用微控制器来构建你自己的设备。我们将在下一章讨论这个话题。

二、电子学入门

在上一章中,我们介绍了微控制器的基础知识以及如何建立开发环境。然而,要真正使用微控制器,我们需要将它们与外界连接。这需要对电子学有一个基本的了解。虽然不可能在一章中完全涵盖这个庞大而复杂的领域,更不用说一整本书了,但本章介绍了电子学原理,并概述了您将在微控制器中使用的一些元件。

电子学可以分为两个主要领域:模拟电子学和数字电子学。模拟电子学涉及电路中的元件可以有几种状态的电子学;因此,我们说模拟电子学处理的是连续电路。另一方面,数字电路有两种状态,因此数字电子学处理的是分立电路。在本章中,我将讨论这两个问题。

电就是电子的流动。为了理解电子,让我们谈一点原子。据说原子是物质存在的最小部分,这一切都很好,除了它还不止于此。即使在原子内部,也存在无数的亚原子粒子,它们被描述为粒子动物园。我们有质子、中子、电子、夸克、介子,这个清单可以一直列下去。

亚原子物理学可能需要一生的时间来研究。我们将跳过这一复杂性,把原子描述为由三种主要粒子组成。这些是质子、中子和电子。据说质子带正电荷,中子不带电荷,电子带负电荷。

现在我们的头脑中有了这些粒子,人们会认为它们的大小都一样。然而,事实并非如此。质子和中子比电子大得多。关于这些粒子,还需要记住的是,它们在原子中不是随机分布的。中子和质子在原子中心聚集在一起,电子围绕着它。

这种电子流称为电流。这些电子流动的原因很简单:导体本身由原子构成。当我们施加一个电压时,这是导体两点之间的电位差,它使这些电子流动,这就是所谓的电流。现在,记住导体的原子已经有电子了;因此,当你让更多的电子流过导体时,它会导致一个单独的原子向其邻居“扔出”电子,这个过程以光速继续进行许多许多次。

所以,让我们为那些仍然困惑的人回顾一下。原子是由质子、中子和电子组成的。这些电子可以流过允许它们流动的叫做导体的材料,而不能流过不允许它们流动的绝缘体。当这些电子流动时,它们产生电流,它们流动是因为导体两端存在电位差,也称为电压。允许电流通过的物质被称为导体,而不允许电流通过的物质被称为绝缘体。为了流动,电流需要一个闭合的路径或回路,称为电路

一种叫做电源的设备两端有一个电势差,可以让电流流动。在上面的一段中,我们谈到了电压,它是导致电流流过物体的电位差。然而,这种电位差并不是来自它本身;它来自一种能产生电力的装置,我们需要这种电力来驱动电子设备。电池和蓄电池就是两种这样的电源。

在这个时代出生的任何人都肯定遇到过这种或那种形式的电池。你想过电池是什么吗?我的意思是,你知道在你的手机和你身边的小玩意里有一个。然而,你想过什么是电池吗?

电池是一种我们用来发电的装置,由一组电池组成。虽然我们把所有能提供电能的东西都叫做电池,但事实并非如此。电池是产生能量的单一单位。当我们收集了一些细胞时,就形成了一个电池。

原电池是这样一种电池,其中发生的产生电的化学反应不容易逆转。你更换过多少次家里钟表和遥控器的电池?一旦原电池耗尽,就意味着要被丢弃。

你用来给玩具和小玩意供电的碱性电池通常是原电池。当 AA 或 AAA 电池用完时,你可以扔掉它,根据预期用途放入一个新的。

如果我们观察细胞,我们会看到两个标记。电源的正极叫做阳极,你会看到它标有一个小小的十字符号(+)。你的电源的负极端被称为阴极,你会看到它标有一个小破折号(-)。这种阴极有时被称为接地。

虽然原电池有其用途,但我们向“绿色”的转变以及电池技术的进步使我们倾向于使用能量耗尽后可以充电的电池。我们称这些细胞为次级细胞。

二次电池是一种产生电能的化学反应可以逆转的电池。想一想;如果化学反应产生了电,增加电不应该能够逆转产生电的化学反应吗?

这是二次电池工作的前提。由于它的电池化学性质,它可以充电。这些类型的电池在我们的手机和平板电脑等现代电子设备中很常见。

我们将在整本书中使用的电子元件具有不同的物理属性,允许电子以特定的方式在其中流动。当我们使用这些组件时,我们所做的就是利用这些属性来产生某些想要的效果。在这一章中,我将依次讨论每个组件,以展示它如何影响电流。

电线

电线是电流在电路中流动的媒介。电线由包围导电金属芯的绝缘体组成。这种芯可以是实心的或绞合的。实芯焊丝由芯部的一片金属组成。图 2-1 显示了绝缘下面的实芯电线。

img/511128_1_En_2_Fig1_HTML.jpg

图 2-1

实芯线

绞合芯线由许多捆在一起的实心线组成。图 2-2 向我们展示了绞合芯线的样子。

img/511128_1_En_2_Fig2_HTML.jpg

图 2-2

绞合芯线

绞合线和实芯线都有它们的应用。在抗腐蚀方面,实芯焊丝非常坚固,对于可能发生腐蚀的应用,它们是首选的焊丝类型。另一方面,多股绞合线更柔软,因此用于电线可能会有很多弯曲的应用中。实芯线在大量弯曲下更容易断裂,不适合这种应用。

随着您获得更多经验,您将能够确定哪种电线适合哪种应用。有时,成本或电流处理能力等其他因素可能会决定您将在应用中使用哪种类型的电线。

关于电线,需要了解的另一件重要事情是它们的尺寸。我们用来指代导线尺寸的名称是导线的规格。美国线规(AWG)是大多数人在处理电线尺寸时使用的标准。

一些总是让初学者困惑的事情与电线的尺寸和分配给他们规格的数字有关。在 AWG 系统中,数字越大,导线越细。这意味着 12 号线(家庭布线的常用尺寸)比电子工程中常见的 22 号线粗。

更粗的线通常意味着更大的处理能力。当比较导线时,在 DC 电压下,单芯导线通常比具有相同横截面积的相应绞合导线提供更高的额定电流。原因很简单:归结起来就是热。

导线处理电流的能力取决于当电流流过导线时它能多好地消除产生的热量。单芯导线必须将热量从导线散发到外部,这比绞合导线要好。想想绞线中间的那一股。热量必须从该中心线股散发,并依靠其他线股将热量传导到电线外部,这导致散热较差。

试验板

当我们开始使用电子元件时,我们需要一个表面来构建电路。我们称这个表面为试验板。试验板是一块由称为插座的小孔组成的板,我们可以在那里连接我们的组件。图 2-3 显示了一个试验板。

img/511128_1_En_2_Fig3_HTML.jpg

图 2-3

一块试验板

试验板的插座按组排列。电路板的两侧是垂直的插座带,称为电源端子、电源轨或电源总线。这些都标有一个加号“+”用于指示电源的正极连接位置,一个负号“-”用于指示电路上的接地连接位置。在试验板内部,单个+或–列中的所有插座都用金属线连接,允许电流在插座之间流动。电源轨如图 2-4 所示。

img/511128_1_En_2_Fig4_HTML.jpg

图 2-4

试验板的电源轨用红线突出显示

试验板的中心由成行排列的插座条组成。试验板的这一部分称为原型制作区域。每排有两组五个插座,中间有一条沟隔开。每一排插座都有一个编号;在我们的示例试验板上,它们从 1 到 30 编号。一行中的每个插座也有指定的字母。在试验板内部,每组五个插座用一根金属线连接,如图 2-5 中突出显示的。中央沟槽用于支撑集成电路元件,这将在本章后面讨论。

img/511128_1_En_2_Fig5_HTML.jpg

图 2-5

原型区域,前几行插座用红色突出显示

公对公跳线,如图 2-6 所示,其连接器可以轻松插入试验板的插座中。

img/511128_1_En_2_Fig6_HTML.png

图 2-6

跳线

这些跳线用于将不同的插座连接在一起,为电流流经试验板上的元件提供路径。

电子原理图

描述电子学时,习惯上用一个原理图来表示你正在处理的电路。想象我们有一个连接到电池的电机,如图 2-7 所示。

img/511128_1_En_2_Fig7_HTML.jpg

图 2-7

一个简单的电路,特点是电机连接到电池

可以用电路元件的特殊电子表示来替换电路元件的图形表示。这种表示叫做示意图。在我们的第一个电路中,我们将电池连接到马达上。图 2-8 向我们展示了如何将这个电路重新绘制成图形。这个电路的图示叫做电路图。在电路图中,我们用图形来表示我们的电路。

img/511128_1_En_2_Fig8_HTML.jpg

图 2-8

我们电路的图示

图 2-8 中的电路可以用特殊符号重新绘制,以表示组成电路的元件,如图 2-9 所示。这是因为虽然电路图很棒,但随着我们的电路变得越来越复杂,充分使用电路图来表示我们的电路变得越来越困难。图 2-9 中的图表使用示意符号来表示我们的电路。原理图使用抽象的图形符号来表示电路中的元件,而不是图形表示。

img/511128_1_En_2_Fig9_HTML.jpg

图 2-9

使用原理图符号显示的相同电路

然而,这些术语并不是一成不变的,因为有些人在某些情况下将电路图称为示意图。然而,你应该知道,我们如何表示我们的电路存在差异。在本书中,我使用原理图,因为它们比电路图更清晰、更简洁。另外,一旦你理解了原理图,你应该可以毫无问题地理解电路图。

我们在电子产品中使用的每个元件都有自己的标准化示意图符号来表示。以下是我们迄今为止使用的组件的符号。

导线用直线表示,如图 2-10 所示。试验板上成行成列的连接孔可视为导线,因为它们允许电路内的元件连接。

img/511128_1_En_2_Fig10_HTML.jpg

图 2-10

导线原理图符号

如图 2-11 所示,一个单元由一条短线和一条长线表示,它们相互平行,导线从两侧伸出。

img/511128_1_En_2_Fig11_HTML.jpg

图 2-11

单元示意图符号

电池的示意符号是几个电池连接在一起,如图 2-12 所示。

img/511128_1_En_2_Fig12_HTML.jpg

图 2-12

电池示意图符号

电机也有自己的示意符号,它是一个内部带有大写字母 M 的圆圈,如图 2-13 所示。

img/511128_1_En_2_Fig13_HTML.jpg

图 2-13

电机示意图符号

随着我们的进展,将会逐渐引入更多的示意性符号。

无源元件

我们现在准备讨论所谓的无源元件。它们被称为无源,因为它们不需要任何外部电源来运行。这些装置能够消耗、储存和释放电能。无源元件包括电阻电容电感

电阻

电阻器是一种阻止电子流通过的装置。这需要限制流入某些电路元件的电流。如果我们将太多的电流注入某些元件,它们就会被损坏,因此,我们使用电阻来防止这种情况发生。电阻也是一个特别重要的分压电路的一部分。分压器将较大的电压转换成较小的电压,这在构建某些电路时很有用,我们将在本书后面看到。

我们用欧姆这个单位来测量物质的电阻。仔细想想,所有电阻都是电路中电压和电流之间的关系。本质上,它是一种物质上的电压与通过它的电流之比。这种关系形成了所谓的欧姆定律。

我们谈论电路不能不讨论欧姆定律。一个叫格奥尔格·欧姆的家伙发展了一个定律,在这个定律中,他发现了电压、电阻和电流之间的关系。

  • 电压=电流*电阻

这种关系简单却强大!为了说明这一点,让我们假设有一个 5 伏的电压流过你的电路,一个电阻的值为 1k。

我们如何计算电流?我们简单地重新排列欧姆定律,得到如下结果:

  • 电流=电压/电阻

因此,我们可以计算出电路中的电流为 5v / 1000 欧姆= 0.5 毫安。

在图 2-14 中,我们可以看到一些电阻的样子。

img/511128_1_En_2_Fig14_HTML.jpg

图 2-14

一些抵抗组织:阿达果,adafruit.com

电阻器上印有彩色条带,用以指示电阻值。第一段表示电阻的第一位数字,第二段表示第二位数字,第三段表示零的数量。第四个频段称为电阻的容差水平,它告诉我们电阻可能超出或低于原始值的百分比。我们从左到右阅读这些电阻颜色代码;然后根据带的颜色,我们根据电阻的带数来赋值。我们通常可以通过寻找电阻上的金或银容差带来判断哪个带是第一带;公差带将是最右边的带,因此我们从最左边的带开始确定值。通常,公差带和值带之间会有一个小的间隙,但情况并非总是如此。表 2-1 列出了我们可以用来确定电阻值的颜色。

表 2-1

电阻器颜色代码

|

颜色

|

数字

|
| --- | --- |
| 黑色 | Zero |
| 褐色的 | one |
| 红色 | Two |
| 柑橘 | three |
| 黄色 | four |
| 格林(姓氏);绿色的 | five |
| 蓝色 | six |
| 紫罗兰 | seven |
| 灰色的 | eight |
| 白色的 | nine |

公差带可以是棕色,表示 1%的公差,金色,表示 5%的公差,银色,表示 10%的公差,或者根本没有条纹,表示 20%的公差。还有其他指示其他容差级别的波段,但这些是您最常遇到的波段。

电阻是一种性能良好的元件。电阻是罪魁祸首的电路很少会有问题。如果一个电路中的电阻出现故障,要么是因为设计选择不当,要么是因为另一个元件的故障导致该电阻出现故障。

电阻的示意符号是一条锯齿线,如图 2-15 所示。

img/511128_1_En_2_Fig15_HTML.jpg

图 2-15

电阻器原理图符号

电容器

电容器负责在电路中储存电能。它们最常见的用途之一是与电阻一起用于滤波电路。电子设备中的滤波器用于允许特定频率的电信号通过,同时衰减或减少其他频率的电信号。一些类型的滤波器甚至能够放大或修改它们允许通过的频率信号。

滤波器电路有许多重要的应用;一个这样的应用是在音频信号中,当您想要创建一个双向分频电路,并将所有高频信号导向高音扬声器,同时衰减低频信号。

电容器的特性取决于制造它们的材料;我们称它们是由电介质制成的材料。电介质将决定电容器是极化的还是非极化的。极化电容器在电路中必须以某种方式连接;否则,它将被损坏并存在火灾危险。无极性电容器可以在电路中以任何一种方式连接都没有问题。

电容器所能容纳的电荷量称为电容,用法拉来度量。一法拉是很大的存储量,因此,我们通常使用少量电容,通常为微法拉(μF)和皮法拉(pF),分别为百万分之一法拉和万亿分之一法拉。

使用电容器时,需要注意的一些事项是它们的温度范围和工作电压。这些值通常印在设备的主体上。温度范围代表电容器正常工作的最低和最高温度,而工作电压是可以馈入电容器的最大电压。重要的是不要超过工作电压,因为这将导致设备损坏。当电容器发生故障时,它们会燃烧,并有能力带走其他电路元件。

如果电容器上没有电压或温度额定值,最好查阅器件的数据手册。这是一份列出您正在使用的设备的所有特性的文件,包括安全工作电压和推荐的工作温度。

极化电容器

你可能遇到的最常见的极化电容器是铝电解电容器,如图 2-16 所示。与大多数非极化电容器相比,这些电容器具有较大的电容。

img/511128_1_En_2_Fig16_HTML.jpg

图 2-16

一种铝电解电容器

由于其结构,铝电解电容器存在一个称为漏电流的问题。漏电流的产生是因为电容器中的绝缘体不完美,因此确实有一些电流流过。为了解决这个问题,人们发明了另一种电容,称为钽电解电容,它的漏电流比铝制电容低得多。

非极化电容器

陶瓷电容是迄今为止最常见的非极化电容。图 2-17 显示了陶瓷电容器的样子。

img/511128_1_En_2_Fig17_HTML.jpg

图 2-17

陶瓷电容器

陶瓷电容器比铝电解电容器提供更高的工作电压,尽管它们的电容要低得多。通常,您会根据自己的电容要求选择其中之一。如果你需要一个大电容,那么你可以使用电解电容;然而,如果你想要一个小电容,你会倾向于使用陶瓷电容。

如果你观察陶瓷电容器,你会发现上面印着三个数字。前两位数字代表电容值,最后一位数字给出乘数。我们所做的就是取前两位数,加上乘数中指定的零的个数。这给出了以皮法为单位的电容。如果您没有看到任何第三位数字,那么这就是皮法值。因此,如果你看到一个电容只写着“10”,那么这个电容的值就是 10 皮法。

例如,图 2-17 中列出的电容器的标记为 104。因此,该值将为 10 + 0000,即 100 000 pF 或 0.1uF。

电容器示意图符号

图 2-18 向我们展示了如何用示意符号表示极化电容器,图 2-19 向我们展示了如何表示非极化电容器。

img/511128_1_En_2_Fig19_HTML.jpg

图 2-19

非极化电容器示意图符号

img/511128_1_En_2_Fig18_HTML.jpg

图 2-18

极化电容器示意图符号

极化电容器总是有一根导线,旁边有一个加号“+”。非极化电容器没有任何极化标记。

感应器

电感是一个特别重要的元件,我认为很多人把它看得过于复杂。你可以利用微积分和其他高等数学来了解电感,或者你可以从电感是什么的角度来看待电感,它只是另一个可以使用的元件。不要担心;在本书中,我们将以最实用的方式介绍电感及其用途。

在深入研究电感之前,我们必须了解电磁学。在生活中的某个时刻,我们可能会遇到磁铁。

磁铁是展示磁性的材料。也就是说,磁体是具有磁场特性的材料。当电流通过导体时,就会产生磁场。如果我们有一根有电流流过的导线,磁场会很弱。如果将导线缠绕成线圈,就会产生更强的磁场,这就形成了电磁铁的基础。

一圈线圈会产生磁场。然而,这仍然是一个非常薄弱的领域。我们可以通过增加线圈的匝数来增加磁场的强度。

为了大幅增加磁场强度,我们将一种导磁材料放入线圈核心。这种材料通常是铁。当我们这样做时,我们现在得到了一个可以电子控制的强磁体。带有导磁磁芯的线圈称为螺线管。

我们刚刚学习了电容器,它们可以储存电荷,我们还学习了电磁学,即当电场穿过导体时,导体会产生磁场。

电感器是储存电荷的装置。我们刚才谈到的盘绕在一起的导线是一个电感器,因此,你有时可能会听到人们把电感器称为线圈。

电感和电容的区别在于,电感以磁场的形式储存电荷。就电感器的工作而言,你可以把电感器当作一个在磁场中储存电荷的黑匣子来设计许多电路。

电感缩写为(L ),测量单位为亨利(H)。

电感有各种不同用途的封装。这些电感可以是屏蔽的,也可以是未屏蔽的。记得我们说过电磁学吗?感应器的设计会产生磁场。该磁场会对周围的电子电路产生不良影响。我们可以通过屏蔽的方式来设计电感,从而将干扰对环境的影响降至最低。

电感用于阻挡电力线上的高频噪声;为此,我们把这样使用的电感器叫做扼流圈。这在逆变器和电机控制电路等电源电路中很常见。这些扼流圈可以用来处理相当大的电流。它们也用于设计类似于电容器的电子滤波器。

长期以来,“空芯”电感器被广泛使用。这些电感器是通过简单地将一根实心线缠绕成线圈而形成的。尽管这些仍然有其应用,但这种电感器有其局限性。只需添加一个铁芯供电线缠绕,就可以在更小的封装中获得更大的电感。随着时间的推移,其他核心材料和设计变得普遍。因此,现代电感器看起来不仅仅是一个中间有铁条的线圈。图 2-20 显示了一种你可能在现代电子产品中遇到的电感,左边是它的原理图符号。

img/511128_1_En_2_Fig20_HTML.jpg

图 2-20

电感器及其原理图符号

技术进步如此之快,以至于出现了一种叫做平面电感器的小型电感器。考虑到它们的小尺寸,它们具有非常高的电流处理能力。

半导体

在上一节中,我们讨论的电阻、电容和电感被称为无源模拟电路元件。还有一类电路元件称为有源电路元件。我们将从半导体开始讨论有源模拟电路元件。我之前提到了半导体,并告诉您我们将在稍后讨论它们。本节是对这一承诺的履行。

我们了解到,虽然银和铜等一些物质是良导体,但橡胶和玻璃等其他物质是绝缘体。然而,有一类特殊的材料被称为半导体。

在电子学中,元件中最常用的两种半导体材料是锗(Ge)和硅(Si)。尽管近年来市场上出现的一些其他半导体元件是由氮化镓(GaN)和碳化硅(SiC)制成的。

半导体的导电性比绝缘体好,但比导体差。另一种思考半导体的方式是,它们的电阻比绝缘体小,但比导体大。然而,这些器件仅在特殊条件下表现出这些性质,例如温度或添加其他材料。

前面提到的半导体不是最好的导体。这些半导体的导电性可以通过添加某些杂质来提高,从而使它们成为 n 型半导体或 p 型半导体。添加这些杂质的过程称为掺杂。当这些 p 型和 n 型半导体结合在一起时,就形成了所谓的 pn 结。

当你在一个电路的两点之间施加 DC 电压进行某项操作时,这就是所谓的偏置。当你施加的电压可以流过 pn 结时,这就是所谓的正向偏置。你可以通过在 pn 结上连接一个电源来实现正向偏置,这样电源的正端连接到 pn 结的正端,负端连接到 pn 结的负端。

然而,如果施加电压时,正端连接到 pn 结的负端,负端连接到 pn 结的正端,就会产生反向偏置。当反向偏置 pn 结时,它的电阻很大,不允许电压流过。

然而,当反向偏置 pn 结时,你应该知道有极少量的电流流过。还有,当你给 pn 结施加电压时,它会取一定量以上的电压;对于硅来说,它是 0.7 伏,即电流开始在电路中流动时的电压降。这种导致电流快速增加的电压称为拐点电压。如果对 pn 结施加大的反向电压,它就会击穿。结击穿时的电压称为反向电压。峰值反向电压是一个术语,人们用来指 pn 结被损坏前可以施加的最大反向电压量。

二极管

我们要看的第一个半导体器件是二极管。二极管只允许电流单向流动。当二极管以那个方向连接并且电流可以流动时,就说它正向偏置。当它以另一个方向连接时,二极管不允许电流流动,这被称为反向偏置。二极管由设备阴极上的条带来识别。图 2-21 显示了二极管的样子。

img/511128_1_En_2_Fig21_HTML.png

图 2-21

二极管

二极管的示意符号是一个三角形,尖端有一条线,如图 2-22 所示。

img/511128_1_En_2_Fig22_HTML.jpg

图 2-22

二极管示意图符号

二极管正向连接时电阻可以忽略不计,反向连接时电阻非常高。有信号二极管,用于小电流电路,和功率二极管,可以处理更高的电压和电流。

发光二极管

发光二极管(LED) 是一种特殊类型的二极管,在正向偏置时会发光。LED 可以产生可见光、红外光或紫外光。图 2-23 显示了发光二极管的样子。

img/511128_1_En_2_Fig23_HTML.jpg

图 2-23

发光二极管

LED 示意符号与二极管符号相似,只是其周围有一个圆圈,如图 2-24 所示。

img/511128_1_En_2_Fig24_HTML.jpg

图 2-24

LED 示意符号

像所有二极管一样,led 也有一个电压降。这个电压降取决于制造 LED 的材料类型。

一个 LED 上有两条引线。这些导线中较长的称为阳极。这是你连接正电压的地方。较短的导线称为阴极。这是你接地的地方。如果我们观察塑料外壳内的 LED,您会发现其中一条引线在外壳内看起来更大更平。这也可以用来识别阴极。此外,在圆形 led 上,塑料外壳本身有一个平边;这是另一种识别 LED 的方法。所有这些标记的原因是 led 不喜欢以错误的方式连接。我们将在以后学习如何使用 LED 时讨论这一点。

晶体管

晶体管是有史以来最重要的设备之一。它是计算机革命得以发生的核心。

晶体管是通过将 p 型或 n 型材料放在一对相反类型的材料之间而形成的。因此,当你有两个 N 型半导体,中间有一个 P 型半导体时,你得到一个 N-P-N 晶体管。如果你有两个 P 型半导体,中间有一个 N 型半导体,你将得到一个 P-N-P 晶体管。这个 NPN 或 PNP 结可以看作是两个背靠背连接的二极管,一个正向偏置,另一个反向偏置。晶体管的名字来源于这样一个事实,即它将信号从低电阻二极管传输到高电阻二极管。事实上,晶体管是“转移电阻器”的简称

到目前为止,我们一直在讨论的器件有一个共同点:它们都是双端器件。然而,晶体管有三个端子:基极、集电极和发射极。它们分别由字母 B、C 和 E 表示。图 2-25 显示了这些晶体管引脚的样子。

img/511128_1_En_2_Fig25_HTML.jpg

图 2-25

晶体管图

当电流流入晶体管的基极时,从集电极流向发射极的电流比流入基极的电流大。基极流过的电流越多,从集电极流到发射极的电流也就越多。我们在图 2-26 中看到一个晶体管。

img/511128_1_En_2_Fig26_HTML.png

图 2-26

物理晶体管

NPN 晶体管示意符号如图 2-27 所示。该符号是一个圆形,三个引脚集电极、基极和发射极分别标记为 C、B 和 E。NPN 晶体管发射极上的箭头指向远离基极的方向。

img/511128_1_En_2_Fig27_HTML.jpg

图 2-27

NPN 晶体管图

PNP 晶体管原理图符号如图 2-28 所示。PNP 晶体管看起来像 NPN 晶体管,发射极指向基极。

img/511128_1_En_2_Fig28_HTML.jpg

图 2-28

PNP 晶体管图

p 型和 n 型导体相遇点称为结。发射极和基极相遇的结称为发射极二极管。请记住,晶体管可以被认为是两个背靠背的二极管。集电极和基极相遇的结称为集电极二极管。发射极二极管正向偏置,允许电流流过,集电极二极管反向偏置,不允许电流流过。在我们前进的时候,请记住这一点。由于这种构造晶体管的方法,这里讨论的晶体管类型被称为双极结型晶体管或 BJT。

金属氧化物半导体场效应晶体管

金属氧化物半导体场效应晶体管(MOSFET) 像压控电阻器(VCR)一样工作。这些录像机有一个输入端口和两个输出端口;输入端口电压控制输出端口之间的电阻。电阻随着输入到器件的电压非线性变化。这一特性允许输入端口有效地开启或关闭 MOSFET。当 MOSFET 导通时,它的电阻很低,只有几分之一欧姆。查看 MOSFET 的数据手册时,您会看到它的 RDS(On)值,即器件饱和时的漏源电阻。当最大量的栅极电压施加到器件上时,MOSFET 处于饱和状态,这使得 RDS(On)非常小,允许最大漏极电流流过 MOSFET。较低的 RDS(On)值意味着器件的运行温度更低,效率更高。

我们可以在图 2-29 中看到一个 MOSFET。这是一种 TO-220 封装,对于 MOSFETs 来说并不少见。

img/511128_1_En_2_Fig29_HTML.jpg

图 2-29

金属氧化物半导体场效应晶体管

正如 BJT 有两种类型一样,MOSFETs 也有两种类型。图 2-30 显示了两者的示意符号。左边是 N 沟道 MOSFET,类似于 NPN BJT,右边是 P 沟道 MOSFET,类似于 PNP BJT。

img/511128_1_En_2_Fig30_HTML.jpg

图 2-30

N 沟道和 P 沟道 MOSFETs 的原理图符号

需要考虑的一个重要因素是栅极阈值电压,它表示将电流导入漏极所需的最小电压。将注意力转移到图 2-31 上。

像晶体管一样,MOSFETs 也有三个引脚。它们被标记为漏极、栅极和源极。它们分别相当于晶体管的集电极、基极和发射极引脚。通常,在大多数应用中,你可以用 MOSFET 代替晶体管。

img/511128_1_En_2_Fig31_HTML.jpg

图 2-31

晶体管和 MOSFET 并排

使用 MOSFET 的一些注意事项是确保不要超过栅极到源极的电压,否则会损坏 MOSFET。此外,当切换感性负载(我们称之为电感器件)时,要确保使用反激二极管(连接在器件两端的二极管)来保护 MOSFET。这是我们将在稍后研究如何在我们的设备中使用电机时更详细讨论的内容;现在请记住,当您使用 MOSFETs 时,如果您使用的器件充当继电器或电机等电感,您将需要使用二极管来保护该器件。

集成电路

到目前为止,我们看到的器件被称为分立元件,因为每个器件只有一个功能。相比之下,集成电路(IC) 是一种将几个分立元件放入一个设备中的元件。IC 在紧凑的封装中提供了许多有用的功能。通过将我们之前看到的元件组合成不同的电路配置,我们可以得到该电路的理想功能。我们可以把分立器件小型化,做成集成电路,而不是用分立元件来实现这个功能。例如,正如我们在上一章中所了解的,微控制器是一种将许多不同电路功能集成到一个封装中的集成电路。我们将在书中进一步了解不同类型的 IC。

图 2-32 显示了集成电路的样子。顶部通常有一个凹口,表示哪个引脚是器件上的第一个引脚。如果你没有看到一个凹口,那么你会看到一个点,这将起到同样的作用。

img/511128_1_En_2_Fig32_HTML.jpg

图 2-32

集成电路块

图 2-32 所示的 IC 类型是双列直插式封装(DIP) IC。这些 ic 通常有 4 到 40 个引脚,通常引脚均匀分布在容纳所有器件的塑料外壳的两侧。引脚间距通常为 0.1 英寸,这使得 DIP ICs 非常适合插入试验板。

虽然 DIP 技术非常适合原型制作,但今天你会发现大多数 IC 都是作为表面贴装技术(SMT)器件制造的,这种器件是为机器组装而制造的,可以更容易地以更低的成本制造电子产品。对于初学者来说,SMT 设备很难使用,尽管有一些适配器可以让它们适应试验板。

数字逻辑

既然我们已经讲述了基本的模拟电子学,我们可以看看数字电子学了。模拟电路元件,如晶体管和二极管,可以不同的方式组合成数字逻辑电路。所有数字电子设备的基本构件是逻辑门;因此,我们对数字逻辑的讨论从对逻辑门的讨论开始。逻辑门是一种电路配置,它接收输入并输出高或低状态。这些高和低状态对应于电压电平。在传统的数字电子设备中,我们认为高电压通常为 1.8、3.3 或 5 伏,低电压通常为 0 伏。这些高电压和低电压可能会有所不同,但对于大多数应用,这些都是我们将考虑的电压水平。本质上,高电压就是我们所说的逻辑电平“1”,低电压就是逻辑电平“0”。这就是所谓的二进制系统。二进制系统只用 0 和 1 两个数字来表示它们所有的信息。数字逻辑电路在其输入端使用这些逻辑电平,并产生二进制输出。对于我们在本节中所研究的数字逻辑电路,这种输出通常是一个二进制数字,我们称之为位。

逻辑门通常有两个输入和一个输出。尽管可以用任意数量的输入来构造门,但为了简单起见,我们将坚持使用两个输入。逻辑门是可以连接到微控制器的分立元件(我们将在下一节讨论这种类型),当这样使用时,它们被称为胶合逻辑,因为它们允许不同的逻辑电路通过桥接接口连接在一起工作。它们也是电路中较大的部分,我们将在本书后面的章节中看到。在这一节,我们将看看一些常见的逻辑门。

我们要看的第一个逻辑门是与门。与门比较两个二进制值。如果两个值都是逻辑 1 或高,则结果是逻辑 1,输出将是高。如果输入端的任何值为逻辑 0 或低电平,则门的输出将为低电平。图 2-33 显示了与门的符号。

img/511128_1_En_2_Fig33_HTML.jpg

图 2-33

与门

一个或门比较两个二进制值。如果任一值为逻辑 1 或高,则结果为逻辑 1,输出将为高。如果两个值都是逻辑 1,那么输出也将是高电平。如果输入端的两个值都是逻辑 0 或低电平,那么门的输出将是低电平。图 2-34 显示了或门的符号。

img/511128_1_En_2_Fig34_HTML.jpg

图 2-34

或门

非门是数字电子世界中经常遇到的另一种门。它将输入的反义词作为输出。如果输入低,则输出高,如果输入高,则输出低。图 2-35 为非门的示意符号。

img/511128_1_En_2_Fig35_HTML.jpg

图 2-35

非门

另一个重要的门是异或门。XOR 代表异或。如果输入具有相反的值,异或门将输出高值,否则输出将为低。换句话说,只有当一个或另一个输入(而不是两个)为高时,它的输出才会为高。XOR 门可以使用这种独特的能力来执行许多不同的任务,例如奇偶校验(用于确保数据正确传输),XOR 门的一个特点是任何数字与其自身进行 XOR 运算都会产生一个 0,用于清除机器代码中的寄存器(我们将在后面了解更多),并可以在微处理器中使用,以帮助进行更有效的加法。此外,如果您对同一个数字进行两次 XOR 运算(我们执行一次 XOR 运算,取其输出值,然后再次进行 XOR 运算),您将得到原始值!异或门符号如图 2-36 所示。

img/511128_1_En_2_Fig36_HTML.jpg

图 2-36

异或门

我们要看的最后一个门是缓冲门。缓冲门接收输入信号,并且不反转输出;如果你输入一个逻辑高,你得到一个高输出,如果你输入一个低输入,你得到一个低输出。缓冲门用于放大数字信号。如果在输入端提供逻辑信号的器件无法向目标提供足够的吸电流(提供电流)或源电流(接收电流)(稍后我们将在讨论微控制器的输入和输出时看到这一点),则使用缓冲栅极来增强器件输入端的驱动能力。图 2-37 为缓冲器的示意符号。

img/511128_1_En_2_Fig37_HTML.jpg

图 2-37

缓冲器

我们将会看到,所有这些逻辑门都有其应用。

逻辑电平转换

使用数字逻辑时,有时需要将一个逻辑电平(如 1.8 伏、3.3 伏或 5 伏)转换为另一个。这是因为不同的设备在不同的电压下运行。在数字电路的早期,系统通常在 5 伏电压下运行。由于 5 伏是微处理器等智能设备出现时的标准电压,因此它们也使用 5 伏逻辑进行操作。因此,许多外设(我们稍后将了解微控制器上的特殊电路)和模块都是为 5 伏系统设计的。

然而,近年来,行业趋势已转向使用 3.3 伏逻辑电平或甚至 1.8 伏。由于 5 伏系统已经存在多年,有时有必要将较低的逻辑电平与使用 5 伏逻辑的模块或其它 ic 结合起来,因为它们可能更容易获得,或者设计团队可能对这种器件的鲁棒性有经验。在这种情况下,可能需要执行逻辑电平转换,以便从一种电压转换到另一种电压。请注意,如果您使用的是具有 3.3 伏逻辑电平的 5 伏外设,则无需进行逻辑电平转换就可以与器件接口。然而,当使用具有 5 伏逻辑电平的 3.3 伏逻辑器件时,转换器是必要的。不过,为了安全起见,当您跨不同的逻辑电平域工作时,可以使用逻辑电平转换器。为此,您通常会使用专用 IC。也可以使用如图 2-38 所示的特殊逻辑电平转换 PCB。这个逻辑电平转换器可以看作是几个缓冲逻辑门,它们不改变信息,只是修改输入或输出端的驱动电流。

img/511128_1_En_2_Fig38_HTML.jpg

图 2-38

逻辑电平转换器

触发器

既然我们已经检查了分立逻辑门,我们可以转向构成计算机存储器一部分的触发器。触发器是最小的存储单元。触发器可以以高或低逻辑状态的形式存储单个数据位。在数字电子技术中,有组合逻辑电路和时序逻辑电路。组合逻辑电路的输入一旦改变,其输出也随之改变;以与门为例,一旦门的输入改变;输出将响应输入而立即改变。触发器可以用作计算机存储器的原因是它属于时序逻辑电路的范畴。这种电路只有在你有意改变输出状态时才会改变,不管你在输入端做了什么。该输出通常不仅响应于当前输入,还响应于设备的先前输入而改变。触发器是时序逻辑电路的基本构件。

钟控 RS 触发器就是这样一种时序触发器电路。钟控 RS 触发器依赖施加于输入端的时钟脉冲来改变状态。JK 触发器清除了与 S 和 R 都为逻辑 1 所导致的无效条件相关的一些不一致。因此,JK 触发器是一种常用于数字设计的流行触发器。在图 2-39 中,我们看到一个 JK 触发器。

img/511128_1_En_2_Fig39_HTML.jpg

图 2-39

JK 人字拖

我们看到的触发器上 J 和 K 引脚(输入引脚)之间的三角形引脚是时钟引脚;时钟引脚接受时钟输入。时钟可以被认为是在高和低逻辑电平状态之间交替的逻辑电平信号。时钟由低变高的状态称为时钟的上升沿,时钟由高变低的状态称为时钟的下降沿。我们还有输出引脚 Q 和!Q(不是 Q)。如果 J 和 K 处于逻辑高电平状态,并且我们施加一个时钟信号,那么 Q 和!q 会改变状态。如果 J 和 K 都处于低逻辑电平状态,并且我们施加一个时钟脉冲,那么输出将没有变化。

JK 触发器被称为通用触发器,它们用于移位寄存器、脉宽调制(PWM)电路和计数器等设备,以及我们将在本书中了解的其他类型的电路。

寄存器和移位寄存器

触发器构成了计算存储器基本单元的基础。我们用来指代计算机内存基本单元的另一个术语是寄存器。寄存器用于存储和操作计算机中的数据。寄存器利用触发器来存储数据,因此我们可以轻松地对其进行读写。为了便于理解,可以把寄存器想象成一个存储一位数据的黑盒,这个黑盒内部使用一个触发器来完成这个任务。

你会遇到的一种寄存器是移位寄存器。移位寄存器可以存储 4 位或 8 位的序列。移位寄存器具有一系列触发器,这些触发器以这样的方式连接,即当一个时钟脉冲施加到器件时,位从一个触发器移动到下一个触发器。

为了实现这一点,移位寄存器通常有一串 D 触发器。D 触发器可以认为是只有一个输入的 JK 触发器,这使得触发器更容易使用;然而,它需要更多的逻辑电路来实现它的实现。在一个移位寄存器中,我们称每个 D 触发器为一个锁存器。从最严格的意义上来说,锁存器和触发器的区别在于锁存器是电平触发的,而触发器是边沿触发的。这意味着锁存器是异步的,输入一改变逻辑电平,锁存器就改变输入,而触发器依赖时钟信号的状态(利用时钟的上升沿或下降沿)来改变输入。

我们说,当某样东西对器件输入端的电压电平转换做出响应时,它就是电平触发的,电压电平可以是高逻辑电平,也可以是低逻辑电平。另一方面,边沿触发器件响应时钟信号的边沿,它可以由时钟信号的上升沿或下降沿触发。

在包含锁存器的移位寄存器中,一个锁存器的输出连接到另一个锁存器的输入,数据可以串行(一次一位)或并行(所有位同时)馈入移位寄存器。

由于这种安排,我们可以有各种各样的移位寄存器

  • 串行输入串行输出(SISO)

  • 串行输入并行输出(SIPO)

  • 平行输入平行输出(PIPO)

  • 并行输入串行输出(PISO)

  • 双向移位寄存器

每种类型的移位寄存器都有自己的应用。如今,许多移位寄存器都集成在 IC 中,因此不经常作为分立元件使用。

多路复用器和多路分离器

一个多路复用器是一个数字电路,它将两个或多个输入线路组合成一个输出。这有时被称为复用。图 2-40 有多路复用电路。

img/511128_1_En_2_Fig40_HTML.jpg

图 2-40

多路复用器

多路复用器有两个标记为 0 和 1 或 A 和 B 的输入,另一个输入线称为 SEL0,还有一个输出线 y。根据输入线的值,多路复用器为输出分配一个值。如果选择器线为低电平,则线 1 将在输出端。如果选择器线为高电平,输出将反映线 0 上的值。

一个解复用器做与复用器相反的事情。它将单个输入转换成许多不同的输出。解复用器如图 2-41 所示。

img/511128_1_En_2_Fig41_HTML.jpg

图 2-41

多路分解器

解复用器具有输入 A 或 F 和两个输出 0 和 1 或 A 和 B,以及选择器线 SEL0。输入线上的二进制模式将被转换为输出线上的值。当选择线为低时,A 处的输入将在线 0 上,当选择线为高时,多路复用器将 A 处的输入分配给线 1。

结论

在本章中,我们讲述了基本的电子学,重点是一些常用的实际元件。我们研究了无源模拟器件,以及半导体、二极管、晶体管和 MOSFETs。我们还看了一些数字电子元件。掌握了数字电子学的基础知识,当你以后使用微控制器时,你会更好地理解它们。我希望在这一章中,你将会学到电子学的概念是建立在彼此的基础上的。尽管二极管和晶体管是模拟元件,但它们可以组合起来形成数字构建模块,构成微控制器;这里学到的知识现在看起来很抽象,但我可以保证,当我们了解微控制器上的各种外设时,我们就会明白这些信息有多有用。这里的信息将为您的微控制器之旅服务。

三、嵌入式系统概述

虽然我很希望你开始写代码,但我们必须谈一谈,这样你才能理解你的代码是如何构建的。在这一章中,我们将介绍一点嵌入式系统的理论,这将为你理解本书的其余部分提供背景知识。在上一章中,我们概述了一些基本的电子学,在本章中,我们将嵌入式系统作为一个整体来看,以便更好地理解微控制器在整个事物中的位置。的确,光是这一章的内容,本身就能占据一本书的篇幅!这一章一定会很长,并且会给你一些贯穿你整个职业生涯的原则。

嵌入式系统概述

我们将从一开始就开始我们的旅程,讨论什么是嵌入式系统。嵌入式系统是为一个目的或一个功能而设计的系统,它被期望以高度的可靠性和最少的用户干预来执行。嵌入式系统的定义特征是它们通常是资源受限的。这意味着它们通常没有千兆字节的 RAM、无尽的电力和常规计算系统的计算能力。

这些系统依次由两个主要的子系统组成,一个是由主处理设备和相关电子设备组成的硬件组件,另一个是在硬件配置上运行的软件系统。

嵌入式系统有时需要在其他计算系统会失效的恶劣环境中执行其功能,例如在深空或海底。一些嵌入式系统预计在单个电池上运行数年,因此节能设计对大多数嵌入式系统至关重要。

大多数嵌入式系统都是为实时操作而设计的,这意味着系统的延迟必须最小。在某些情况下,如果不在要求的规格范围内,这种延迟可能会导致受伤或死亡。

微控制器与应用处理器

在我们进一步讨论嵌入式系统之前,我认为有一个重要的区别是必须要做的。您将在嵌入式设计中遇到的处理设备类型之间的区别。你会遇到基于微控制器的系统和基于应用处理器的其他系统。

微控制器(MCU)是一种独立的设备,将处理器、内存和支持设备集成到一个封装中。微控制器通常用于实时应用或要求高容量、低成本、低功耗或三者兼备的应用。

智能手机行业的诞生催生了应用处理器。应用处理器可以被认为是“类固醇处理器”,因为这些设备通常与许多 CPU 内核、图形处理单元(GPU)、高速缓存、多媒体编解码器、通信控制器和许多其他好东西一起封装在一个封装中。

两者之间的主要区别在于它们的预期应用。虽然微控制器意味着深度嵌入式应用,其中最复杂的应用需要实时操作系统(RTOS),但应用处理器通常至少具有 RTOS,更常见的是运行通用操作系统,如 Linux。应用处理器也可用于平板电脑或智能手机等通用计算系统。

嵌入式系统结构

尽管嵌入式系统的结构在不断变化,但大多数系统仍然在设计中使用经典的结构。嵌入式系统的经典结构如图 3-1 所示,由四个主要组件组成。嵌入式系统的主要硬件组件之一是与通常称为固件的软件程序协作的微控制器。固件是一种软件程序,最终用户不得更改。还有一些类型的输入和输出设备与这种微控制器-固件双核通信。虽然这种类型的配置不会很快出现,但重要的是要承认它不是构建系统的唯一方式。

img/511128_1_En_3_Fig1_HTML.jpg

图 3-1

嵌入式系统的经典结构

现在的趋势如图 3-2 所示,运行基于 Linux 操作系统的通用应用处理器系统与微控制器设备相结合,以执行实时处理功能。这可以被组合在一个系统级封装(SiP)中,该系统级封装将所有芯片包含在一个封装中或者在板上,其中应用处理器可以是位于“实时”处理器附近的系统级模块,该“实时”处理器通常运行实时操作系统(RTOS)或者裸机循环执行系统。

img/511128_1_En_3_Fig2_HTML.jpg

图 3-2

嵌入式系统的趋势结构

请记住,这些不是嵌入式系统的唯一结构,因为有时可编程逻辑结构可能存在于系统的某个地方。不管系统的结构如何,它都会从系统的输入/输出(I/O)端口接收一些输入,执行一些处理,然后通过一些输出设备向用户提供一些有用的输出。

具有经典结构的嵌入式系统的典型例子是计算器。计算器将通过键盘接收输入,然后嵌入式处理器将执行计算,输出将显示在屏幕上。类似地,大多数“哑”消费电器、办公自动化设备,甚至车辆的子系统都遵循这种“经典”结构。这是本书关注的焦点,因为它是构建其他结构的“基础”。

曾经为非常高端的系统保留的“更新”结构开始更频繁地出现。随着计算能力的提高,芯片成本不断下降,加上市场对“智能”设备的需求,导致了应用和实时处理器结构的这种耦合。机器人是利用这种结构的嵌入式系统的最好例子。一个微控制器执行实时处理,控制电机和读取传感器,然后与运行 Linux 的应用处理器通信,后者使用该数据来执行定位和映射,并通过某种串行协议与微控制器通信来控制平台的移动速度。

智能设备也具有这种结构,使用运行 Linux 的应用处理器来获得丰富的用户体验,它与底层微控制器或操作实时子系统的几个微控制器通信。

硬件系统

正如我们之前所讨论的,嵌入式系统的组件由硬件和软件组成,它们协同工作来执行某些任务。不管制造商是谁,硬件肯定会包括处理器、内存和 I/O 端口。

处理器通常是微控制器,在一个封装中包含中央处理器(CPU)内核、内存和 I/O 引脚。目前常用的微控制器内核有 ARM、PIC 和 AVR 架构。RISC-V 是另一种 CPU 核心架构,由于其设计的开源性质,有一天可能会流行起来。许多制造商,如许多 CircuitPython 板中使用的 SAM 微控制器的制造商 Microchip Technology,也将在封装中包括外设,以简化开发时间。

包装上的存储器分为两类,即程序存储器和数据存储器。程序存储器通常是基于闪存或 FRAM 的非易失性存储系统,通常大小从几千字节到几兆字节。程序存储器用于存储将由 CPU 内核执行的程序。另一方面,数据存储器是用于存储微控制器在运行时使用的数据的存储器,通常是几十或几百千字节的 SRAM。

I/O 引脚是拼图的第三块。它们允许 CPU 内核与系统外部的设备通信,例如传感器和其他集成电路。系统所需的 I/O 数量因应用程序而异。

最后,硬件设备上还包括外设。这些外围设备可以执行各种功能,例如系统支持、通信、数据转换、安全功能或者调试和诊断能力。一些较新的微控制器还具有核心独立外设(CIP ),无需 CPU 干预即可执行功能。诸如直接存储器存取(DMA)之类的一些外设可能需要 CPU 的初始干预,但是随后将执行它们的其余功能,而无需 CPU 的进一步干预。

在主处理器的外部,硬件系统可以包括其他设备,例如传感器、用户接口设备或某种类型的可编程逻辑设备。这些共同构成了嵌入式系统的硬件组件。

软件系统

软件负责使硬件变得有用。运行在嵌入式系统上的软件被称为固件,因为它不是为系统用户而设计的。然而,这并不是一成不变的,因为更新和升级可能会在部署后进行。现代物联网设备尤其如此,在这个消费者期待“最新最棒”的世界里也是如此。一般的理解是固件不应该被改变。

嵌入式系统中的软件通常分为两类。您可以设计一个裸机循环执行系统,也可以让一个实时操作系统运行该系统。两者的区别在于处理任务的方式。任务可以被认为是固件程序的最小单位。

在一个循环执行系统中,只有一个任务采取所谓无限循环的形式。在这样的系统中,程序会有一个主入口点,然后循环执行系统必须执行的一系列动作。大多数嵌入式设计都利用这种类型的系统。它实现起来很简单,并且在您不需要太多系统的时候使用。这是我们将在本书的开头部分使用的系统类型。

在基于 RTOS 的系统中,有许多任务需要执行。由于硬件系统的资源有限,因此需要一个调度程序来管理任务访问资源的方式。内核根据任务的优先级管理每个任务如何利用系统的硬件资源。RTOSs 通常被认为是一个高级主题,因此将在本书的高级部分讨论。

固件开发的一个主要方面是它必须被设计成模块化的。拥有模块化软件非常重要,因为它允许您在一个处理器家族中的多个成员之间,甚至在多个架构之间利用现有的代码库和知识。跨平台使用代码的能力被称为可移植性。从本质上讲,模块化和可移植性是协同工作的,我们可以在图 3-3 中看到这一点。

img/511128_1_En_3_Fig3_HTML.jpg

图 3-3

模块化和可移植性在好的软件设计中一起工作

这一点很重要,因为在对正确的软件开发过程进行初始投资后,新系统开发所需的技能和时间可以显著减少。有时,由于成本或设计要求,您可能需要升级或降级您设计的处理器,并且您将感谢您将您的软件设计为可移植的。

工具链

开发嵌入式系统时要考虑的一个重要部分是工具链。工具链是指用于创建嵌入式系统的工具,包括编译器和程序员/调试器。

先说编译器。我们编写软件的嵌入式设备称为主机,通常是 PC、MAC 或 Linux 机器,我们为其编写设备的设备称为目标。嵌入式工具链中的编译器在业内被称为交叉编译器。交叉编译器是一种特殊的编译器,它在主机上运行,但在链接器的帮助下创建最终的可执行程序,该程序是为目标程序设计的。

嵌入式工具链的第二部分是将交叉编译器创建的程序加载到设备上的机制。加载是一个过程,通过这个过程,称为程序员的设备将可执行映像放入目标的非易失性存储器中。这通常通过编程器经由主机和目标之间的 USB 或网络链路连接来实现。许多程序员也有调试能力,允许你验证一个程序是否正常工作。出于这个原因,许多程序员被称为在线调试器(ICD ),它允许您在不从电路中移除设备的情况下调试设备。

联合测试行动小组(JTAG)是一个流行的调试和测试嵌入式硬件的行业标准。

像 CircuitPython 这样的开发环境的好处是,通过将解释器放在芯片上,我们可以消除与使用传统开发环境相关的大多数常规细微差别。由于集成了引导加载程序,我们只需将程序放到芯片上,设备就会为我们解释它。

软件测试

嵌入式软件通常被忽视的一部分是测试。测试确保嵌入式软件按预期运行;这些测试被称为功能测试。还有性能测试,它主要确定软件是否满足“SSS”要求(速度、稳定性和可伸缩性)。性能测试本质上是质量测试。故障播种是我们可以执行的另一个常见测试,在该测试中,我们将故障引入系统,并确定它如何响应。我们这样做是为了确保我们的异常处理代码正常工作。

为了帮助测试,许多供应商提供了模拟器,这是一种在您将程序部署到实际硬件之前确保程序正常工作的方法。由于嵌入式硬件的限制,有时 LED 可能是帮助测试的最佳工具,因为它提供了程序按预期工作的视觉反馈。

嵌入式软件架构

为了保持固件的可移植性,开发人员的趋势是为他们的固件生态系统使用所谓的分层架构。分层架构的作用是将如何控制实际硬件的具体驱动程序细节与应用程序本身分开。甚至制造商也将这种类型的设计强加给开发者,所有微控制器供应商都提供某种硬件抽象层(HAL ),该硬件抽象层在软件设计中通过分层架构使用可重用固件的原理。

您将使用的分层应用程序的类型将取决于您的设计的复杂性。您将会遇到的典型分层软件配置通常有 2 到 7 层,我们现在来看一下。

这些层甚至可能没有这里所描述的布局,因为一旦它们被组合到框架中(稍后会有更多介绍),它可能看起来不完全像这种严格的分层方法。一些制造商设计的框架在框架的同一层上有几层。然而,我在这里提供的信息将阐明嵌入式软件是如何构造的。让我们一次检查一层。

驱动程序层

驱动程序层具有特定于 CPU 内核的代码,并且是软件架构中唯一通常不可重用的部分。这是因为,即使在器件系列中,也可能有针对该器件的特殊外设说明。有时,芯片缺陷是存在的,为了确保开发人员仍然可以使用为其编写驱动程序的外设,有必要采取变通办法。

好的司机有四个主要功能。它们执行外设的初始化,配置外设,执行运行时控制,并正确关闭驱动程序。

有时,对于简单的系统,应用层是建立在驱动程序层之上的。因此,在这一点之后,可能不需要其他层,一旦驱动程序层就位,应用层就可以位于任何其他层之上。

这意味着所有后续层都可以放在驱动程序层之上,但在应用层之下。因此,在我看来,驱动程序层是软件架构中最重要的一层。很多开发后期出现的问题都可以追溯到这一层。这一层必须尽可能坚固耐用。

如果您想知道什么是应用层,应用层是包含您希望系统执行的任务的程序。这是根据系统需求实现程序的地方。我们在图 3-4 中看到了这种结构。

img/511128_1_En_3_Fig4_HTML.jpg

图 3-4

驱动程序层构建在硬件层之上

硬件抽象层

下一层是硬件抽象层(HAL)。HAL 位于驱动程序层和应用层之间。

HAL 为驱动程序层增加了模块性,允许开发者在不了解硬件细节的情况下使用特定的硬件特性。这提供了一致性,可以跨设备系列使用,甚至独立于 CPU 核心架构。许多微控制器供应商现在都提供了 HAL,可用于提高开发人员的工作效率。设备制造商通常投入大量资源来提供一个好的 HAL,因为它允许开发者尽可能容易地使用他们的设备。

图 3-5 允许我们消除该结构。

img/511128_1_En_3_Fig5_HTML.jpg

图 3-5

硬件抽象层(HAL)的布局

主板支持包(BSP)

一些架构在 HAL 之上有一个板支持包(BSP ),提供了更高层次的抽象。制造商通常为他们的产品提供开发板。制造商将提供一个板支持包,以便开发人员可以测试并确保一切正常工作。

由于电路板的布局,可能需要特殊的软件配置来确保电路板正常工作。有时,当您与设计公司签订合同来帮助产品开发时,他们可能还会提供 BSP,特别是当希望董事会运行操作系统来确保一切顺利运行时。当你计划迭代未来电路板的设计时,BSP 是很好的选择。如图 3-6 所示。

img/511128_1_En_3_Fig6_HTML.jpg

图 3-6

放置板支持包(BSP)

如果你正在开发一个基于 Linux 的系统,那么主板支持包是必不可少的。BSP 将配置通信总线、时钟、内存以及操作系统正常工作所需的任何其他相关外围设备。BSP 通常还包括一个 bootloader,它是一个在应用程序代码运行之前运行的小程序。

中间件

有一层可以让您的生活变得更加轻松,这一层通常是应用程序实现之前的最顶层,它就是中间件层。中间件通常位于 HAL(或者 BSP,如果该层存在的话)和应用程序之间。中间件将板支持包的软件组件与应用层连接起来。请记住,BSP 是可选的,所以很多时候你会发现中间件直接将 HAL 与应用层连接起来。

中间件很重要,因为它通常包含用于高级外设和复杂协议的软件。好的供应商提供中间件来确保他们的客户使用他们的产品。编写一个设备驱动程序可能需要几个小时到几天,而中间件的开发需要几个星期到几个月的时间。我们在图 3-7 中看到了新的结构。

img/511128_1_En_3_Fig7_HTML.jpg

图 3-7

中间件布局

软件框架

虽然分层体系结构有利于对软件的结构有一个总体的了解,但是嵌入式软件体系结构可以以不同的方式配置。这些层的互操作性的具体配置和布局被称为软件框架。框架提供的是简化开发的驱动程序和中间件库的特定配置。我们在图 3-8 中看到了这一切的全貌。

img/511128_1_En_3_Fig8_HTML.jpg

图 3-8

示例软件框架模型

虽然图 3-8 中的框架模型并不代表所有或任何特定的实现,但是它可以让你理解一个软件框架是如何构建的。软件框架是模块化的,注重可扩展性。它没有一种结构化的方法,也不一定具有前面提到的严格的分层方法。本质上,框架提供了构建应用程序的“脚手架”。通常不希望你修改框架。然而,在实践中,您可能会遇到框架中的错误,这可能需要修改。请记住,大多数框架都有许可证,可能会限制更改或要求您发布您所做的任何更改。在做任何修改之前,最好先看看您打算使用的框架的许可协议。

编码发生器

行业趋势表明,软件框架的下一个层次是代码生成器。代码生成器目前是嵌入式软件开发的最后前沿。代码生成器所做的通常是使用一个图形化的配置环境,它详细描述了您的应用程序中需要的软件框架的组件。生成您需要的组件,不需要的组件不会添加到项目中。代码生成器生成代码结构、目录、引导加载程序和最终应用程序所需的任何其他组件。

平台

一起工作的硬件和软件的组合被称为平台。该平台包括硬件、开发工具(包括工具链和 ide)、软件框架,有时还包括代码生成器。平台可以大大减少开发时间。我们可以用来学习嵌入式系统的一个平台是 Arduino 平台。Arduino 平台是由板、屏蔽、引导装载程序、库和开发环境组成的整个生态系统。类似地,CircuitPython 遵循这个概念,结合了硬件、软件和开发工具。图 3-9 显示了这种关系。

img/511128_1_En_3_Fig9_HTML.jpg

图 3-9

平台包含的三元组

嵌入式系统限制

嵌入式系统在设计上有各种各样的限制。最常见的限制是成本、性能和能源预算。我们将在下面的小节中讨论每一个问题。

费用

首先,嵌入式系统最关键的一个方面是成本。成本是嵌入式设计的主要驱动因素之一。由于大多数嵌入式系统被设计成消费类产品,这些产品将被大量生产,并且基本上是“一次性的”,因此设计中的组件成本至关重要。有时候,从长远来看,一个设计的几分钟会产生巨大的影响。

嵌入式设计中使用的组件列表称为物料清单(BOM)。在讨论成本时,你会碰到一个术语叫非经常性工程(NRE)成本。NRE 成本是指将产品投入生产的初始成本。像研究、原型、产品设计和文档都属于这一类。

设计嵌入式系统时需要考虑的另一个因素是您将使用的器件类型。有从零售商处或直接从制造商处购买的商业现货(COTS)零件。还有一些专用集成电路(ASICs)是为你在任何地方都找不到的特定目的而设计的。

虽然有些人可能不同意我的观点,但在 COTS 解决方案和 ASIC 之间存在一个中间点,ASIC 是现场可编程器件的一种混合解决方案。现场可编程门阵列(FPGAs)、可编程逻辑器件(PLD)和现场可编程模拟阵列(FPAAs)等器件可以“现货”购买,但需要设计人员确定器件的具体功能,因为这些器件在购买时并未设计具体功能。

然而,有时嵌入式设计的成本是不可避免的。如果你是为需要特殊组件的航空航天或深空应用而设计,你可能无法降低成本。例如,在深空探测中,抗辐射组件(rad-hard)可能需要几十万美元。在这里,可靠性和质量比成本更重要。一般来说,你通常需要在价格和质量之间做出平衡。

表演

性能是嵌入式系统设计的一个重要方面。当我们谈论性能时,我们谈论的是系统在合理的时间内执行其功能的能力。合理的定义将在设计的背景下。对于电动牙刷,合理的时间可能是几秒,而气囊展开系统将有十分之几秒来执行其功能。硬件和软件组件都可能影响系统的性能。

在硬件方面,处理器架构和时钟频率会对性能产生影响。运行在 400 MHz 的 32 位微控制器比运行在 4 MHz 的 8 位微控制器具有更好的性能。这两者是相辅相成的,选择合适的架构和合适的频率是一项需要时间学习的技能。

硅缺陷也会对性能产生影响。在许多情况下,CPU 内核或外设会出现问题,这是由制造商的设计错误引起的。这种错误被制造到产品中的结果导致硅缺陷。这些问题会对性能产生严重影响,尤其是在设计过程中未被发现的情况下。有时有必要查看您正在使用的芯片器件的勘误表,通常可以在数据手册中找到。

在软件方面,抽象和算法复杂性以及任务细节都会影响性能。尽管软件开发的当前趋势是在应用程序中有大量的抽象和复杂算法的实现,但重要的是要记住,抽象层越多,需要的 CPU 时间周期就越多。为此,有时从应用层,有时可能需要直接调用较低层次的驱动程序层,如果你需要一定水平的性能。保持算法简单也可以大大减少开发时间。总的来说,在设计你的算法和软件时,记住“KISS”(保持简单,愚蠢)。

任务细节指的是调度程序如何为系统中的任务分配优先级,以及任务之间如何通信会极大地影响系统的性能。根据您的调度程序如何向您的系统分配任务,如果资源分配有错误,它会影响您的最终应用程序。任务的互通也会影响绩效;如果有信息需要在任务之间传递,那么必须确保任务之间的通信没有错误。

能量预算

我把嵌入式系统约束的这个方面留到最后,因为所有嵌入式系统都有能源预算。即使是用市电供电的电器也有能源预算,因为它们在设计时必须考虑能效。嵌入式系统通常依靠电池运行,因此,它们必须尽可能节能。您将使用的电源类型也会影响设备的重量和尺寸。我说尺寸是因为电池和一些电源可能需要特殊冷却,这会增加系统的总重量和尺寸。

制定电池预算是确保您的系统不会消耗太多电力的好方法,尽管大多数设计师都是事后才考虑电力。我甚至可以说,在您开发应用需求之后,甚至在您选择处理器之前,就应该确定您的电池预算。问一个问题,系统将使用多少功率?确定预算后,您可以专注于选择处理器。

您还应该查看可用的处理器节能模式。这个重要的特性意味着你的产品运行几天和几个月的区别。阅读处理器的数据表,确定处理器的睡眠模式。通常,HAL 和驱动程序层将提供选择睡眠模式类型的功能。

拥有专用电源监控电路、通过 I/O 引脚为外部设备供电,以及使用更节能的现代设备,都是降低设备功耗的好方法。

嵌入式系统分类

既然我们对嵌入式系统的硬件和软件组件以及限制有了很好的了解,我们就可以专注于理解嵌入式系统是如何分类的。嵌入式系统可以分为小型、中型和高性能系统。

小规模系统

小型嵌入式系统采用单个微控制器设计,通常是 8 位、16 位或低功能 32 位微控制器。在硬件方面,单个微控制器控制整个系统。这种系统构成了当今世界嵌入式系统的主体。这些类型的嵌入式系统通常限制最多,通常要求极低的成本、低性能和超低功耗。它们有几千字节的程序存储器和几千字节的数据存储器。

这个市场由 8 位架构的设备主导,也可能包括 4 位架构的设备(是的,4 位)。制造商一直试图为这个市场设计 32 位设备,32 位正在逐渐蚕食 8 位微控制器在这个领域的份额。这类设备通常需要在单个电池上运行数年。此类别中的“高端”设备也可能是低功耗 16 位设备。尤其是较新的基于 ARM 的设备越来越能够满足这些标准。

在软件方面,小规模系统通常运行具有循环执行系统的裸机,并且在某些情况下可能实现协作调度器。这些设备通常具有两层软件架构,汇编代码甚至可能用于开发这些设备的应用程序。这些是我们将在本书中重点介绍的系统类型,因为您的大多数设计任务都属于这一类别。我们在图 3-10 中看到了这些系统的结构。

img/511128_1_En_3_Fig10_HTML.jpg

图 3-10

小规模系统

中型系统

中等规模的嵌入式系统可能只有一个处理设备,也可能有多个设备。它们可以将 8 位微控制器和 16 位或 32 位处理设备与几百千字节的数据存储器和高达大约 2 兆字节的程序存储器结合起来。一些供应商还为这一类别的设备提供 16 位和 32 位多核设备。

软件可能由运行 RTOS 的主处理器组成,尽管其他处理器可能是裸机小型系统。汇编语言可以在这一级使用,但可能只在小规模的子系统上使用。它们可能包括网络功能,用于“中档”嵌入式设计。这种设计如图 3-11 所示。

img/511128_1_En_3_Fig11_HTML.jpg

图 3-11

中等规模系统

高性能系统

高性能嵌入式系统可能有一个或多个处理设备。它们可能包含多个高端处理器和 FPGAs,有些可能需要数字信号处理器(DSP)作为其硬件设计的一部分。它们可以被设计成具有运行 RTOSss 的多个处理器子系统,RTOS 本身由几个子处理器系统组成。一些组件甚至可能由运行完整操作系统的应用处理器组成。这些是嵌入式系统所能得到的最昂贵和高性能的。

这种系统的软件设计通常需要几个具有不同专业领域的开发人员来设计。开发工具和调试器也可能需要昂贵的许可费用(尤其是在使用高端 FPGAs 的情况下)。可能还需要购买制造和工具供应商支持的额外费用。

分布式嵌入式系统

中型和高性能系统本身可以归类为分布式嵌入式系统。分布式嵌入式系统由不同的子系统组成,每个子系统都是一个更大系统的节点。这些系统可以具有不同的通信模型,可以是 TCP/IP、can、LIN 等,并且是存在于有线网络(例如汽车和飞机中的那些)以及无线网络(例如物联网)中的系统类型。

这种系统需要有经验的架构师来设计,因为需要协调系统事件,异步系统的设计至关重要。全分布式嵌入式系统没有“主”处理器;所有节点都独立运行,是一个独立的系统。

这些分布式系统允许模块化设计,这允许安全关键部件与非安全关键部件分离。安全关键系统是这样一种系统,如果该系统发生故障,将会导致严重的人身伤害或死亡,或者导致重大的经济损失。因此,例如,在汽车中,拥有分布式系统将允许控制车辆防抱死系统的设备与信息娱乐系统分离。

通常,分布式嵌入式系统是为大型系统保留的,如航空航天、汽车和军事工业中的系统。然而,最近发生的事情是,随着物联网(一种分布式嵌入式系统)的引入,开发人员需要了解分布式嵌入式系统。

开发嵌入式产品的七个步骤

开发嵌入式产品有七个步骤,即创意、需求规格、功能设计、快速原型制作、测试、保护和推向市场。

第一步:创意

第一步可能是最重要的,那就是提出你的想法。这可能是这个过程中最难的一步。最重要的是你的想法必须有一个预定的目标市场,最重要的是你的产品必须解决一些问题。很多时候,客户会提出这个想法。

步骤 2:需求规格

流程的第二步是设计需求规格。这一步很重要,因为你必须对你将要设计的东西有一个清晰的方向和焦点。如果你有一个客户,你的客户想要一个特性,然后又不想要,这种情况并不罕见,这导致了客户和开发人员之间的分歧。拥有一份清晰的需求规格文档将有助于作为客户需求的证明文件,否则你可能会发现自己不断地添加新功能,直到超出预算并落后于上市时间。确保在此步骤中包含一个框图,显示系统将如何组成。

第三步:功能设计

有了需求之后,下一步是进行功能设计。需求规格说明描述了我们将要设计的东西,而功能设计是我们将如何着手构建它。功能设计将包括制作系统模型和进行验证。这是使用统一建模语言(UML)等工具对系统建模的阶段。您还应该有功能块,这些功能块进一步细分需求部分中的块,以包括更多的细节。

第四步:快速原型制作

传统上,嵌入式系统开发遵循几个阶段或一系列步骤,并使用几个开发过程。这些包括常见的软件架构,如瀑布、V-Cycle 和基于螺旋的开发方法。大多数书和课程都会教这个。然而,在行业内,发展趋势是朝着快速成型。快速原型所做的是利用现有的高级硬件和软件工具来实现你的产品。快速原型制作利用平台、代码生成器和硬件模块来开发产品。您通常会对设计执行迭代,编写软件和测试,直到您完成原型设计,而不是遵循一系列增量步骤。

这样做的目的是以更快的速度给你一个原型,并提供更快的上市时间(TTM),这是指从提出想法到销售你的产品的时间。快速原型制作节省了大量时间,就好像你必须重新设计一个原型一样;它包括简单地对模块进行“即插即用”和重写软件。

传统上,你会在试验板上设计一个电路,然后为原型旋转 PCB 板,得到某种类型的外壳,修改它,然后你就有了你的原型。这一过程既昂贵又耗时。

借助 Arduino 等平台和 3D 打印等技术,有可能让您的产品在外观和功能上非常接近最终设计,因为您可以在几小时或几天内迭代设计,而不是几周或几个月。

第五步:测试

即使您将在原型阶段执行测试,在产品创建之后执行测试也是很重要的。测试是一个复杂的过程,可以跨越几本书的信息。出于这个原因,我已经包括了一些基本的测试,您可以执行这些测试来确保您有一个功能合理的系统。当涉及到测试时,总是有扩展的空间。

您将执行硬件测试,包括查找系统中的缺陷和错误。硬件缺陷是实际原型和设计之间的差异。当有缺陷的系统运行时会发生错误,这可能导致硬件系统的故障。

在软件方面,执行软件性能测试很重要,重点是速度和稳定性,有时是可伸缩性。原型完成并且重构软件之后,您必须执行单元测试。单元测试是分离软件的类和功能组件,并将其与系统的其余部分隔离开来进行测试的过程。您将执行数据测试和场景测试。

在数据测试中,您可以在广泛的输入值范围内测试隔离函数。在基于场景的测试中,您既要检查软件处于典型用例中的普通用例场景,也要检查与普通用例不一致的场景。对系统的这种测试会使系统中的任何错误变得可见。在这种类型的测试中,创造力是必不可少的,因为你永远不知道你的用户会做什么。尝试你能想到的任何事情。在极端情况下测试系统。执行基本的篡改,看看会发生什么。你会很高兴你做了。

您还必须执行交互测试。交互测试是一种测试硬件和软件系统互操作性的方法。您可以执行这种类型的测试的方法是执行故障播种,并查看系统的每个组件如何对该故障做出反应。尽管这些测试方法简单且不完整,但是简单地执行这些测试就可以让您领先于那些不执行这些基本测试就发布产品的开发人员。我见过无数失败的产品,它们本可以在产品上市前通过执行这些简单的步骤而受益。

步骤 6:保护您的系统

不用考虑系统安全性的日子已经一去不复返了。随着嵌入式系统上的软件变得越来越复杂,有必要引入安全措施来保护您的嵌入式系统。

没有连接到网络的小型系统通常需要实施较少的安全措施。对于此类系统,设置一些锁定位或熔丝位并启用代码保护可能就足够了。您还可以在软件中引入某种类型的 ID 认证方法,防止固件未经验证就运行。用环氧树脂涂覆您的重要组件也是保护您的系统免受恶意攻击的好方法。

虽然上述方法有利于保护未连接到网络的嵌入式系统,但是嵌入式系统还有一个额外的安全隐患,那就是联网。随着物联网(IoT)的日益普及,需要将更多设备连接到互联网。事实上,物联网寻求将一切连接到互联网。

针对连接的嵌入式系统的一些常见攻击是广播风暴、重放攻击和端口探测。广播风暴攻击是指通过网络链路向最初广播数据的节点重新广播数据,从而导致网络故障的过程。重放攻击包括破坏加密或使用不安全的网络来拦截在网络上传输的数据,然后延迟其传输或稍后重新发送。端口探测是在网络上寻找开放端口以利用漏洞的过程。

黑客利用嵌入式系统安全性的另一种常见方式是使用上述攻击之一来访问您的嵌入式系统,对您的设备重新编程,然后将其与其他嵌入式设备一起使用来发起分布式拒绝服务攻击(DDoS)。如果您引入了我们安全讨论第一部分中提到的 ID 认证,它将防止黑客对您的设备重新编程,并防止 DDoS 攻击。

不可能为您的系统提供所有级别的安全性。最强大的设备是人类的大脑。因为人们是如此有创造力,如果一个黑客想找到进入你的系统的方法,他们会找到这样做的方法。你所能做的就是通过采取上述措施来降低与你的产品相关的风险。通常用于基于 CitcuitPython 的电路板的 SAMD 微控制器制造商 Microchip Technology 也提供有助于网络安全的解决方案,并为物联网设备提供使用案例。微芯片 ATECC608A 等设备提供了可用于保护您设备的算法和安全协议。他们为两家最大的云提供商提供了使用案例,这是为您的网络嵌入式系统增加安全性的良好开端。

第七步:推向市场

创建嵌入式设备的最后一步是将其推向市场。这是最长和最详细的步骤。在下面的步骤中,您创建了一个工程原型。将设备推向市场的步骤太多了,本书无法一一介绍。因此,如果你没有这方面的经验,我建议你使用专注于产品开发的工程公司。

如果你没有经验,我建议你使用工程公司的原因是,这个行业是一个鲨鱼吃鲨鱼的世界。组件供应商和外壳制造商,尤其是那些不在美国的供应商和制造商,会窃取你的设计,给你假的组件,甚至可能拿走你的钱,却不提供你所购买的产品。这些工程公司将与经过验证的供应商和设计师建立联系,并利用他们的专业知识帮助你将产品推向市场。

结论

在这一章中,我们研究了软件开发的所有方面,包括嵌入式系统的概述、嵌入式软件架构、嵌入式系统的分类以及开发嵌入式产品的步骤。本章包含的信息将在您的嵌入式职业生涯中为您服务。在使用 CircuitPython 开发自己的微控制器产品时,以及在使用复杂工具和更高性能系统时,这里学到的技术和术语将对您有所帮助。

四、Python 编程

Python 是一门庞大而复杂的语言,我不希望在一章中涵盖它。有太多的函数、结构和编程构造需要详细说明,以使您成为一名优秀的 Python 用户。然而,我将尝试应用 80/20 法则,也称为帕累托法则。因为我们学习 Python 的目的是用 CircuitPython 编写微控制器,所以我可以省略很多东西,但仍然可以让你理解这本书。

出于这个原因,我们将学习你需要知道的 20%的语言,以处理你可能用 CircuitPython 做的 80%的任务。因此,这一章将呈现核心语言的一个子集,简要地涵盖该语言最重要的方面。如果你有使用 Python 编程的经验,你可能会认为我应该添加其中的一个。不过,我在这里介绍的子集已经足够了,例如,任何来自 Arduino 或 C 背景的人都可以轻松掌握。

编写 Python 程序

Python 程序通常是在带有“.”的文本文件中编写的。py”扩展名。Python 有两个版本,一个是 Python 2,它是 Python 的传统版本,另一个是 Python 3,它是当前版本,得到了很好的支持。出于大多数目的,我们将关注 Python 3,因为这是 CircuitPython 中使用的版本。如果您觉得有必要运行本章中介绍的任何程序,您可以使用 Mu 编辑器。

为此,只需点击如图 4-1 所示的模式按钮。

img/511128_1_En_4_Fig1_HTML.jpg

图 4-1

模式按钮

点击这个按钮后,应该会出现一个窗口;从对话框中,选择图 4-2 中的“Python 3”选项,启动一个新的 Python 程序。

img/511128_1_En_4_Fig2_HTML.jpg

图 4-2

Python 3 选项

一旦它被选中,一个窗口打开;我们在文本区输入我们的程序,并按下图 4-3 中描述的运行按钮。

img/511128_1_En_4_Fig3_HTML.jpg

图 4-3

运行按钮

我们的程序将运行,并在位于 IDE 底部的控制台窗口中查找输出。我们的程序完成后,我们可以点击停止按钮,如图 4-4 所示。

img/511128_1_En_4_Fig4_HTML.jpg

图 4-4

停止按钮

现在你知道了如何运行 Python 程序,我们可以看看如何使用 Python 语言了。

首先看一下 Python 程序的典型结构,以此开始我们对 Python 编程的讨论是很重要的。清单 4-1 展示了我们将在本书中使用的一个基本 Python 程序的例子。我们使用 import 语句在程序中引入模块,这样我们就可以使用其中包含的方法。我们还将有一个主循环,称为无限期运行的超级循环。哈希符号“#”后的单词称为注释。这些被 Python 解释器忽略了,但是允许我们向自己和其他程序员解释我们的代码在做什么。

# import math modules
import math

# main super loop
while(True):
    # call method from our imported module
    print (math.sqrt(4))

Listing 4-1Basic Python Program

在 Python 中,我们访问的方法是代码块,只有在被点符号调用时才会运行。我们的数学模块有一个我们想要使用的名为平方根的方法,所以我们称之为“math.sqrt”方法。还有一个打印声明。print 语句用于将东西输出到我们的显示器,我们可以用它从程序中获取信息。所有 Python 程序都将保持这种基本结构,你将在后面的章节中看到。

空白

需要注意的是 Python 程序中的空白。当我们谈论空白时,我们真正谈论的是不可见的字符,比如制表符、空格、换行符等等。

一些编程语言会忽略空白,但是在 Python 中,空白扮演了一个特殊的角色。空白用来构建你的 Python 程序。虽然许多编程语言使用花括号来表示代码块的开始和结束位置,但在 Python 中,空格用于表示程序中的代码块。注意空格,因为它在 Python 代码中特别重要。这本书里的程序相当短,没有太多的缩进层次;然而,你仍然应该注意程序中的空白。

评论

注释是 Python 程序中的文本,是为了程序员的利益而存在的,因为它们被解释器忽略了。良好的编程实践要求您包含足够多的注释,以便其他程序员在阅读您的代码时能够意识到程序在做什么。注释还会提醒你你的代码做了什么,因为将来你可能需要更新或维护你的代码。

虽然评论是必要的,但重要的是不要滥用评论和放置太多。就像生活中的任何事情一样,保持平衡很重要,所以使用评论,但不要过度使用。

用 Python 写注释有两种方法。有一个单行注释占据了一行,它是通过使用散列符号来完成的。解释器会忽略放在这个符号后面的任何内容。这是在清单 4-2 中完成的。

# a single line comment

Listing 4-2Single-Line Comment

还有跨越多行的注释,用于注释块。这些使用包含在两组三重引号中的注释。清单 4-3 向我们展示了这些多行注释的样子。

"""
a multiline block comment
That spans multiple lines
"""

Listing 4-3Multiline Comment

每种注释类型都有它们的用途,并且它依赖于您所维护的代码库或者您所工作的团队,这将决定您将使用哪种注释风格。

变量和常数

当编写程序时,我们需要某种方法来存储信息并在以后检索它。我们用变量来做这件事。变量允许我们给一个内存位置指定一个名字,在那个位置存储数据,然后在以后检索它。我们在 Python 中赋予变量的名称必须符合某些参数。有效的 Python 变量可以是数字和字符的组合,也统称为字母数字字符。这意味着在创建 Python 程序时,只能使用字母、数字和下划线。清单 4-4 展示了有效 Python 变量名的例子。

Foo
dove
_topshot
RickyTicky99

Listing 4-4Example Valid Python Variable Names

虽然在变量名中使用数字是有效的,但是不能以数字作为变量名的开头。变量名中也不能有空格,并且不能在变量名中使用关键字(作为语言的一部分保留的词)。清单 4-5 向我们展示了一些无效的变量名。

20Foo
dove bird
$upermaniac
for

Listing 4-5Example Invalid Python Variable Names

为了确保您不使用任何保留的关键字作为变量名,表 4-1 显示了我们不能使用的变量名,按字母顺序排序。

表 4-1

Python 中的保留关键字

| 和 | 艾列弗 | 如果 | 或者 | 产量 | | 如同 | 其他 | 进口 | 及格 |   | | 维护 | 除...之外 | 在 | 上升 |   | | 破裂 | 最后 | 存在 | 返回 |   | | 班级 | 错误的 | 希腊字母的第 11 个 | 真实的 |   | | 继续 | 为 | 非局部的 | 尝试 |   | | 极好的 | 从 | 没有人 | 随着 |   | | 是吗 | 全球的 | 不 | 正在… |   |

当变量在程序执行过程中不能改变时,我们称这些变量为常量。在 Python 中,我们通常用大写字母来声明变量,并将它们放在 constants.py 文件中。

在程序中使用变量之前,我们必须声明它。清单 4-6 向我们展示了如何声明一个变量。

# declaring a variable
myVar = None

Listing 4-6Declaring a Variable

在我们声明一个变量后,我们可以给它赋值。给变量赋值的过程称为变量的初始化。清单 4-7 展示了我们如何初始化一个变量。

# initializing a variable
myVar = 10

Listing 4-7Initializing a Variable

如清单 4-8 所示,我们可以同时声明和初始化一个变量。

# declaring and initializing a variable
anotherVar = 10

Listing 4-8Declaring and Initializing a Variable

数据类型

变量可以属于 Python 语言中几种数据类型中的一种。变量的类型决定了变量中可以存储什么类型的数据以及它占用多少内存。我们不需要告诉 Python 一个变量是整数(一个数字)还是字符串(比如单词),因为 Python 会自动处理这些赋值。表 4-2 中给出了我们程序中最常用的四种数据类型及其用法。

表 4-2

常见数据类型

|

数据类型

|

描述

|
| --- | --- |
| int(整数) | 这是一个整数或数字,如 10、20、100、4000 |
| 浮动(浮动) | 这是一个浮点数或带小数点的数字,例如 4.5、60.9、300.03、1000.908 |
| 字符串 | 字符串是引号(双引号或单引号)之间的一组字符,例如,“Andy”或“Maggie” |
| 布尔值(布尔值) | 可以表示真或假的数据类型 |

要查看变量的类型,只需使用“type(variableName)”就可以看到关键字的类型。

经营者

Python 提供了执行许多逻辑和数学运算的工具。为了实现这一点,Python 有很多可以使用的操作符。这些运算符可以是算术、关系、逻辑、按位和赋值运算符。表 4-3 列出了一些常见的运算符,并提供了它们的用法示例。这个表并不详尽,但它确实让您了解了可用的内容。

表 4-3

常见 Python 运算符

|

操作员

|

类型

|

描述

|

使用

|
| --- | --- | --- | --- |
| + | 算术 | 将两个操作数相加 | X + Y |
| - | 算术 | 从第一个操作数中减去第二个操作数 | X - Y |
| * | 算术 | 将两个操作数相乘 | X * Y |
| / | 算术 | 将分子除以分母 | X / Y |
| % | 算术 | 模运算符给出除法运算的余数 | X % Y |
| ** | 算术 | 这是指数运算符,执行幂运算 | X**Y |
| == | 比较 | 如果两个操作数相等,那么 Python 会将条件评估为真 | A==B |
| != | 比较 | 如果两个操作数不相等,Python 会将条件评估为真 | 答!= B |
| > | 比较 | 如果左边的操作数大于右边的操作数,则条件为真 | A > B |
| < | 比较 | 如果左边的操作数小于右边的操作数,则条件为真 | A < B |

列表

任何编程语言的一个重要组成部分是它支持的数据结构的类型。列表是 Python 编程语言的一种基础数据结构。列表存储已排序的项目集合,并且列表可以更改。清单 4-9 向我们展示了 Python 中的一个列表。

# python list
myList = ["boy", 15, 1.2]

Listing 4-9Example List in Python

在 Python 中,列表中的项目被分配一个从 0 开始的索引。我们可以通过使用清单 4-10 中的代码来获得一个列表的索引。

# get index of list item
myList = ["red", "orange", "green"]
print(thislist[1])

Listing 4-10Get Index of List Item

请记住,当我们向前移动时,列表中的元素从零开始。

元组

我们在上一节中了解到,列表可以存储数据类型的集合。但是,有时您需要另一种方法来组织数据。对于这样的场景,我们可以使用元组。

在 Python 中,一个元组结构可以被认为是一个列表;然而,我们不能改变元组中包含的元素。清单 4-11 向我们展示了如何使用元组。

# tuple example
myTuple=("red", "orange", "green")
print(myTuple)

Listing 4-11Python Tuple

我们可以像访问列表一样访问元组中条目的索引。清单 4-12 展示了我们如何做到这一点。

# access tuple item index
myTuple = ("red", "orange", "green")
print(myTuple[0])

Listing 4-12Access Items of Tuple

如果语句

if 语句用于在你的程序中做决定(参见清单 4-13 )。为此,该语句检查布尔表达式是否为真。如果表达式为真,将执行该语句。

if (x > y):
    doSomething()

Listing 4-13if Statement

else 语句

else 语句用于补充 if 语句并创建一个决策块。当 if 语句中的布尔语句评估为 false 时,else 语句执行。清单 4-14 向我们展示了 else 语句是如何运行的。

if (x > y):
   doSomething()

else:
  doSomethingElse()

Listing 4-14else Statement

elif 语句

很多时候,在我们的程序中,我们可能需要在我们的决策块中测试不止两个条件。为了测试几个条件,我们必须使用 elif 语句来测试这些多个条件。清单 4-15 给了我们一个 else if 语句的例子。

if (x > y):
   doSomething()

elif (x == y):
  doSomethingElse()

else:
   doTheOtherThing()

Listing 4-15else if Statement

有几分地

有时,如果 if 语句足够短,并且您只有一条语句要执行,您可以将它放在一行中,如清单 4-16 所示。

# single line if statement
if x == y: print("its equal")

Listing 4-16One-Line if

for 循环

有时,我们需要执行一段代码指定的次数。为了帮助实现这一点,我们使用了一个与范围函数相结合的 for 循环。清单 4-17 给出了一个使用范围函数的 for 循环的例子。

# print 0 to 9
for x in range(10):
  print(x)

Listing 4-17for Loop

请记住,Python 从 0 开始计数,因此当前面的语句运行时,它将在控制台中打印从 0 到 9 的数字,而不是 10。

while 循环

虽然带有 range 函数的 for 循环允许我们运行一个代码块一定的次数,但有时我们不确定我们需要执行一个代码块多少次。在这种情况下,我们使用一个 while 循环,该循环在为循环执行指定的条件保持为真的情况下执行。这是本书中许多程序的基础。清单 4-18 向我们展示了 while 循环的样子。

while (True):
   runForever()

Listing 4-18while Loop

while 循环在许多嵌入式应用程序中用于保持程序无限期运行,当它这样做时,我们称之为超级循环。

功能

有时,在您的 Python 程序中,您可能会发现自己不得不一遍又一遍地运行相同的代码块。Python 中有一个名为 function 的特性,可以让您调用自己编写的代码块并返回一个值,而不是多次键入同一个代码块。这些函数可以接受输入的参数,然后对它们做一些事情。如果你需要从一个函数中返回值,你可以使用 return 语句。

例如,让我们编写一个函数,它将接受两个变量并对其中一个进行乘方,然后调用它三次来演示这是如何工作的,如清单 4-19 所示。

def addVar(a, b):
     print (a**b)

addVar(3, 6)
addVar(2, 8)
addVar(1, 9)

Listing 4-19Functions

λ函数

有时,在我们的程序中,我们可以创建一个 lambda 函数。Lambda 函数允许我们编写不需要命名的小函数。清单 4-20 向我们展示了如何用一个小的 lambda 函数替换前面的函数。

x = lambda a, b : a**b
print (x(2, 5))

Listing 4-20Lambda Functions

异常处理

有时,我们的部分代码不能按预期工作,尽管它可能遵循程序的所有语法规则,但 Python 解释器执行的语句可能仍然无效。例如,如果我们试图打印一个不存在的变量,尽管用 print 函数打印一个变量在语法上是正确的,但由于变量尚不存在,我们会得到一个错误。当我们“尝试”执行语句失败时,程序不会崩溃,我们可以通过将代码块放在 except 语句下来做其他事情。清单 4-21 向我们展示了异常处理是如何工作的。

 try:
  print(undefVar)
except:
  print("Causes Exception")

Listing 4-21Exception Handling

面向对象编程

Python 是众所周知的面向对象编程语言。这意味着我们可以创建一些特殊的代码,作为我们创建其他代码的蓝图。我们称这个特殊的蓝图代码为类。这个蓝图的正式名称是对象构造函数,我们用它来创建类的实例。我们也可以拥有存在于一个类中的函数,当我们这样做时,它们被称为方法。

在 Python 中,类中有一个特殊的方法,每次我们使用它来创建一个名为 init()方法的对象时,都会调用这个方法。在 Python 中,我们可以在用“self”调用之前修改类的实例;使用 self,我们可以访问 Python 类的属性和方法,这些属性和方法将在对象初始化后对其执行。

一个对象是一个类的实例,它从这个类中获取它的属性,并用来模拟现实世界中的事物。想想一辆车;两辆汽车可能有相同的品牌和型号,但颜色不同。除了颜色属性不同之外,它们在功能上是相同的。在 Python 中,也是这样——有时,我们需要相同的对象,但需要对其中一个进行稍微不同的修改;在这种情况下,我们将创建一个类,然后我们可以分别修改该类的两个实例。清单 4-22 向我们展示了 Python 中的类是什么样的。

# create the car class
class AwesomeCar:
    # our special method that lets us
    # initialize attributes of the class
    def __init__(self, color):
        # these remain the same
        self.make = "Tayaba"
        self.model = "Nimbus"
        # the colors change
        self.color = color

# create instance of our car class
Car1 = AwesomeCar("Red")

# create another instance of our car class
Car2 = AwesomeCar("Blue")

# print car attributes
# prints {'make': 'Tayaba', 'model': 'Nimbus', 'color': 'Red'}
print(Car1.__dict__)

# print car attributes
# prints {'make': 'Tayaba', 'model': 'Nimbus', 'color': 'Blue'}
print(Car2.__dict__)

Listing 4-22Python Class

我们可以用(dict)打印该类的所有属性,该属性本身就是对象拥有的属性,包含为对象本身定义的所有属性。

在 Python 中,我们将在整本书中使用许多类及其方法;《objects》中的这一小段碰撞部分足以让你看完这本书的其余部分。

随机和时间

有时,我们需要生成随机数,因此,Python 提供了一个名为 random 模块的模块来实现这一点。我们还有一个模块叫做睡眠模块,它允许我们的代码等待一段时间。清单 4-23 展示了我们如何一起使用这两个模块。

# our random module
import random

# the time module
import time

# super loop
while (True):
    # create a random number between 1 and 10
    x = random.randint(1, 10)

    # print the number
    print(x)

    # once per second
    time.sleep(1)

Listing 4-23Using Random and Time

Python 诉 circuitpython 案

使用最广泛的 Python 发行版是我们所知的 CPython。CPython 是 Python 编程语言的“黄金标准”实现,因为它被称为该语言的参考实现。CircuitPython 旨在兼容 CPython 然而,由于微控制器内存不足的明显原因以及为了便于使用,一些在 CPython 中可用的库可能在 CircuitPython 中不可用。然而,来自核心语言的大多数东西都可以工作,用 CircuitPython 编写的代码在 CPython 中是有效的,但反过来就不一定了。然而,核心语言就在那里,你可以用 CircuitPython 编写许多令人惊叹的程序。

我的 Python 程序如何运行?

如果您不熟悉微控制器和编程,您可能会想知道 Python 程序是如何从您的编辑器(如 Mu)进入微控制器执行的。因此,在这一节中,我给出了运行 Python 程序时会发生什么的高级概述。

Python 是一种被称为解释语言的语言,这意味着当程序运行时,它获取我们源代码中的每条语句,并将其转换为我们的微控制器可以理解的机器语言。这与编译器不同,编译器在程序运行之前将编写的代码转换成机器代码。

Python 程序以三种状态运行,它们是

  • 初始化

  • 汇编

  • 执行

在我们开始研究 Python 程序是如何运行的之前,我们必须记住 Python 解释器是用 C 编写的,因此它仅仅是一个用 C 代码运行的程序。在初始阶段,当我们编写程序并将其加载到 C 程序中时,程序会寻找 Py_main 程序,这是主程序中的主要高级函数。然后调用处理程序初始化等的其他支持函数。

在这个阶段之后,我们进入编译阶段,在这个阶段,称为解析树生成和抽象语法树(AST)生成的事情发生,以帮助生成字节码。这些字节码指令不是机器码;相反,它们独立于我们正在使用的平台。然后字节码被优化,我们得到了需要运行的目标代码。在 CircuitPython 中,我们可以选择在程序运行之前将代码转换成字节码,这样的文件会被赋予一个“.”。mpy”扩展名。这些文件可以像普通文件一样导入,并且更紧凑,从而更有效地使用内存。

最后,我们的程序在 Python 虚拟机(PVM)中执行。虚拟机是运行这些字节码指令的解释程序中的一个功能。它只是一个大的超级循环,一个接一个地读取我们的字节码指令并执行它们。

结论

在这一章中,我们简要地看了一下 Python 和这种语言的一些特性。一旦你阅读了这一章,你应该对 Python 编程语言有一个基本的了解。编程是一件需要你长时间学习的事情,因此我们写代码是很重要的。只有通过编写代码,你才能学会你正在使用的语言,因此,在下一章,我们将看看如何使用 Python 来控制我们的微控制器硬件。

五、数字控制

至此,我们已经了解了足够多的电子学、嵌入式系统架构和编程知识,可以开始使用物理微控制器器件了。现在,我们将学习毫无疑问的最重要的操作的基础:输入和输出。在每一个微控制器系统的核心,你至少会有一个输出被执行,许多系统也接受输入。

在本章中,您将开始了解如何控制和操作物理传感器和执行器。操纵物理 I/O 设备有一种满足感,这是在屏幕上推动像素无法比拟的。我希望这一章将向你介绍使用硬件的乐趣,并激起你进一步尝试微控制器开发的欲望。

I/O 引脚

微控制器设备附有物理引脚。这些引脚有一个有价值的用途:它们允许微控制器设备与外界接口。正如我们之前所讨论的,微控制器系统通常接受某种输入,执行某种处理,然后提供一个输出来操作某种外部外设或设备。这是输入输出控制的前提。

一般来说,你可以从一个微控制器控制的输入和输出引脚的数量上了解它。微控制器上更多的引脚意味着设备通常会包含更多的外设和强大的 CPU 内核。微控制器通常至少有六个 I/O 引脚。有时,他们可以有数百个。

过去,微控制器通常采用 DIP 封装。在使用它之前,你需要将它连接到一个编程器上,并设置好正确的连接和电源配置。虽然这在专业环境中可能是好的,但对于制造商和刚刚开始使用微控制器的人来说,这太复杂了。开发板的出现简化了爱好者的过程。该板带有一个处于已知工作状态的微控制器,允许用户立即专注于构建电路和连接外部设备,并专注于编写软件。

微控制器硬件上的输出和输入

在底层硬件上,微控制器通常使用所谓的端口来执行输出。在其最简单的形式中,端口可以被认为是允许微控制器与外界接口的一组引脚。

端口通常排列成引脚组;典型地,大约 8 或 16 个引脚构成一个端口,并且这可以高达 32 个引脚。您可能想知道为什么我们首先需要在端口中分组微控制器引脚。你必须知道,在微控制器架构中,端口只不过是寄存器。因此,端口中的引脚数量与微控制器的架构之间存在一定的相关性。因此,8 位微控制器的端口通常有 8 个引脚,16 位微控制器的端口有 16 个引脚。然而,这并不是一成不变的;例如,您可能有一个 32 位微控制器,它的端口为 16 位宽(有 16 个引脚),甚至一个端口只有 4 个引脚。作为微控制器设备的用户,您可以简单地将此视为制造商的设计决策。您还应该知道,由于端口是一个寄存器,该端口上的每个引脚都是寄存器中的一个位。端口有一个名称,如 PORTA 或 PORTB,每个端口内的引脚都有编号。假设我们有一个假设的微控制器,它有两个端口,PORTA 和 PORTB,如图 5-1 所示。

img/511128_1_En_5_Fig1_HTML.jpg

图 5-1

一个假想的有两个端口的微控制器

PORTA 上的所有引脚都被命名为“AX”,其中“X”是与该端口相关的一个位。例如,A1 引脚指 PORTA 的第一位。像“A1”这样的缩写名称便于识别和标记,当您为微控制器编写代码时,如果需要访问某个特定的引脚,它们也会有所帮助。

图 5-1 中 PORTB 上的引脚遵循与 PORTA 不同的命名惯例:“PBX”而非“BX”不同的制造商遵循不同的端口引脚命名惯例。有些人会用“A1”和“D1”这样的名字,而有些人会用“PA1”和“PD1”这样的名字只有命名约定发生了变化。针本身的工作方式是一样的。实际上,微控制器的管脚号从 0 开始是很常见的。因此,PORTA 中的第一个引脚通常被称为 A0 或 PA0。

为简单起见,微控制器上的每个引脚最好都有一个专用功能。例如,我们可以想象我们假设的设备上的 PORTA 是一个输出端口,这意味着端口上的引脚只能将数据带出微控制器。类似地,我们可以想象 PORTB 是一个仅输入端口,只能接受来自外部世界的数据和进入微控制器的数据。

然而,如果我们有一个微控制器必须与许多输出设备接口的应用,比如控制 15 盏灯,会发生什么呢?或者我们可以让微控制器控制一个有 12 个输入的键盘?在这两种情况下,如果引脚仅用于一个目的,我们可用于输入或输出的引脚数量将会受到限制。因此,通常根据电路设计者的判断将微控制器上的引脚配置为输入或输出。

为此,微控制器内部必须有某种机制,允许端口上的引脚用作输入或输出。因此,在许多微控制器中,通常有一个寄存器可以控制数据进出引脚的方向。该寄存器通过软件编程,并由微控制器编程器控制。

当以这种方式使用时,端口可以简单地配置为输入或输出,端口引脚处于数字状态。当处于数字状态时,引脚可以多种方式配置;通常,我们说它有三种状态。作为输出,它可以配置为输出高电平,此时引脚为输出,向负载提供电流。该引脚也可以是输出低电平,此时该引脚仍配置为输出引脚,但不向负载提供电流。该引脚也可以配置为输入。

不仅可以控制引脚方向的内部电路很常见,而且很多时候微控制器还有其他内部外设和模块也依赖这些引脚来执行输入和输出功能。这些包括像串行接口,使用内部定时器或模拟功能。这些被称为引脚的替代功能,因制造商而异。

这是必要的,因为虽然大多数微控制器包括许多功能和板载外设,但大多数应用在任何时候都只需要使用其中的一两个外设。因此,为了确保微控制器上的每个外设都不需要引脚,这种内部电路将确保板载功能可以在有限的 I/O 引脚之间共享(还记得我们之前谈到的多路复用器吗?这是微控制器内设备的应用)。因此,通常会看到微控制器更像图 5-2 中的图。

img/511128_1_En_5_Fig2_HTML.jpg

图 5-2

更现实的微控制器

图 5-2 中的微控制器更准确地描述了微控制器的样子,考虑到大多数引脚都包含替代功能。如您所见,有多个端口:PORTA、PORTB、PORTC 和 PORTD。这些端口中的每一个都有各种功能的引脚。所有微控制器上的 VDD 引脚和 VSS 引脚都不与任何 I/O 端口相关联,因为它们为器件供电。一个有经验的设计师将能够看到一个类似的图表,并意识到哪些引脚用于何种目的。例如,我们知道引脚 PA0 最有可能用于数字输入和输出。然而,标签 AN0 也贴在销上。这意味着该引脚也可以用于模拟输入功能。第三个标签 INT0 表示我们也可以将该引脚用于外部中断功能。

随着您获得更多设计经验并更加熟悉不同的微控制器系列,您将能够查看电路图上的端口,并知道哪些引脚具有哪些替代功能。即使有这样的经验,你也不能总是说出一枚别针的用途。例如,看看针 PC3。得益于替代的 AN3 标签,你可能认为它可以用于数字和模拟功能,但 CMP 标签是什么意思呢?这些问题最好通过您所用器件的数据手册来回答。例如,数据手册可能会显示此引脚仅用于输入,不能用于任何数字输出功能。请务必准备一份器件数据手册,以回答此类问题。

深入了解微控制器 I/O

您可能想知道微控制器如何使用引脚执行输入和输出。虽然其工作原理背后的数字电子器件可能会变得非常复杂,但我们可以通过一些基本的电路结构来了解最底层发生了什么。

对于输出引脚,参见图 5-3 中的电路图。

img/511128_1_En_5_Fig3_HTML.jpg

图 5-3

MCU 输出电路

在这种情况下,我们有一个触发器或其他受 CPU 控制的锁存电路。你还记得我们谈过人字拖吗?这是触发器在微控制器中的一个应用。记得我们说过触发器一次只能存储一位数据。嗯,当我们的引脚设置为输出时,我们可以输出高值(逻辑 1)或输出低值(逻辑 0)。触发器用于存储我们想要的位(高或低)。锁存电路的输出通过缓冲器。该缓冲电路允许更大的驱动强度,并为微控制器内部器件提供更好的保护,防止器件外部的电气干扰。这里也没有显示保护二极管,它们与缓冲器一起保护微控制器。然后,缓冲电路的输出连接到微控制器上的物理引脚。

同样,我们可以通过查看图 5-4 中的电路图来了解微控制器上的输入是如何工作的。

img/511128_1_En_5_Fig4_HTML.jpg

图 5-4

MCU 输入电路

这种情况下,该引脚连接到具有使能功能的缓冲器。当 CPU 操纵缓冲器的使能引脚时(当然,这是基于程序代码中的指令),可以读取引脚的电压电平,然后 CPU 可以将该信息用于任何目的。

当然,在真实的 MCU 中,电路可能会比这更复杂,因为微控制器上的引脚必须同时充当输入和输出。这称为双向控制,它允许微控制器使用单个引脚进行输入和输出。

当使用微控制器 I/O 引脚时,我们对输入和输出电路的观察使我们想到了另一个考虑因素:微控制器端口的吸电流或源电流能力。源电流是指微控制器向负载提供电流的能力。也就是说,电流将从微控制器引脚流过负载,然后流向地。图 5-5 描绘了源电流的样子。

img/511128_1_En_5_Fig5_HTML.jpg

图 5-5

MCU 源电流

微控制器 I/O 引脚也可以吸收电流。这种情况下,电流从电源流出,经过负载,流入 I/O 引脚。图 5-6 显示了吸电流的样子。

img/511128_1_En_5_Fig6_HTML.jpg

图 5-6

MCU 吸电流

使用输出:点亮 LED

现在我们对微控制器端口有了一些了解,我们可以看看如何使用输出来执行功能:在本例中,点亮 LED。很像打印“你好,世界!”是学习新编码语言的传统第一步,操作 LED 是使用新微控制器的传统第一步。它允许您验证您有一个工作程序,并且您的所有连接都是准确的。在本节中,我们将探讨如何构建一个物理电路来连接微控制器和 LED。然后我们将介绍如何使用 CircuitPython 来控制微控制器的输出并使 LED 亮起。

LED 控制

回想一下第二章,led 是一种特殊的二极管,当电流通过时会发光。为了驱动 LED,你需要电压流入 LED。虽然微控制器可以向 LED 提供稳定的电压,但也必须调节电流以避免损坏它,因为 LED 具有最大额定电流,您不得超过该电流。要调节流经 LED 的电流,可以使用一个电阻。

使用 LED 还会产生电压降。例如,典型的红色 LED 将下降大约 2 伏。然而,不同的 LED 颜色会降低不同的电压,并且电压降也会因制造商而异。最好查阅所用器件的数据手册,了解 LED 的具体规格。

知道了电压降,就可以计算出流经 LED 的输出电流。如果微控制器输出 5 伏电压,那么 LED 产生 2 伏电压降,串联电阻上的电压将为 3 伏。如果您希望流经 LED 的最大电流为 10 mA(您可以在数据手册中找到实际数字),那么您可以使用欧姆定律来计算所需的电阻。

用欧姆定律,电压除以电流。这将是 3 V / 0.01 A,产生 300 欧姆,这是保护 LED 所需的最小电阻。在这种情况下,470 欧姆的电阻是一个不错的选择。请记住,每个电阻都有一个容差水平,可以使实际电阻值小于预期值,因此使用您计算的精确电阻值是有风险的。如果你想接近最大亮度,那么 330 欧姆的电阻就足够了。

构建电路

完成计算后,您就可以设计一个电路,让微控制器控制一个 LED。该电路将类似于图 5-7 所示的原理图。

img/511128_1_En_5_Fig7_HTML.jpg

图 5-7

MCU 电路 LED

我们还使用一个 1k 电阻来限制从器件流出的电流量;请记住,使用较高阻值的电阻没有问题,如果您想要更高的电源效率,这是可取的,因为较高的电阻会消耗较少的电流。

您可以按照以下步骤来设置这个简单的电路。确保在给设备加电之前连接好电路。

  1. 将微控制器连接到电源,确保微控制器正常供电;如果您使用的是开发板,那么您的 USB 连接将足以为设备供电。如果您希望连接外部电源,请将电源的 Vout 连接到主板的 Vin 输入端(电源输出电压的值通常为 7.5 至 12;这取决于您使用的电路板,因此请务必查阅电路板的用户手册以了解输入电压范围),并将地连接到微控制器电路板的接地引脚。

  2. 将一根跳线从微控制器上的 A1 引脚连接到试验板原型制作区域的一个插座上。

  3. 将电阻的一根引线连接到步骤 2 中使用的试验板同一行中的另一个插座。

  4. 将电阻的另一端连接到试验板另一排的插座上。

  5. 将 LED 的长引线连接到步骤 4 中使用的试验板同一行中的另一个插座。

  6. 将 LED 的短引线连接到试验板另一排的插座上。

  7. 将跳线连接到步骤 6 中使用的试验板同一行中的不同插座。

  8. 将跨接导线的另一端接地。

当你完成连接电路时,它看起来应该类似于图 5-8 。

img/511128_1_En_5_Fig8_HTML.jpg

图 5-8

试验板上的 MCU LED 电路

用 CircuitPython 点亮 LED

既然我们已经介绍了构建物理 LED 电路,我们可以看看如何使用 CircuitPython 来控制 LED。我们将要编写的程序将打开 LED。打开 Mu 编辑器,将程序输入到您的“code.py”文件中,如清单 5-1 所示。

# import pin constants for board we are using
1 import board

# import pin control
2 import digitalio

# create object for pin we are using
3 led = digitalio.DigitalInOut(board.A1)

# set the pin to output
4 led.direction = digitalio.Direction.OUTPUT

# turn the LED on
5 while True:
    led.value = True

Listing 5-1Controlling the LED

在这段代码中,我们做的第一件事是导入控制电路板所需的 CircuitPython 库。CircuitPython 提供了许多库来执行数字 I/O。在 1 ,我们导入了board模块,该模块包含所用电路板的引脚常数。导入该模块后,我们可以通过名称来引用管脚,例如,A1。在 2 ,我们导入digitalio模块。它包含了所有的类,这些类提供了对我们将要使用的输入和输出函数的访问。此外,该模块允许我们设置引脚方向,以确定我们是将数据读入引脚还是将数据写入引脚。

导入必要的库后,在 3 我们创建一个正在使用的 pin 对象的实例。这个实例在内存中有自己的位置,这样做允许我们访问我们导入的“digitalio”模块中存在的所有方法。这是普通的 Python 语法,向我们展示了我们常规的 Python 能力可以在 CircuitPython 程序中使用。然后,我们使用该实例来设置 LED 在 4 处的方向。在这种情况下,我们将方向设置为输出。

至此,一切都设置好了,所以我们现在可以打开 LED 了。在 5 ,我们创建了一个在嵌入式系统世界中通常被称为超级循环的东西。任何属于while True语句的指令都将被反复执行,直到程序被告知停止。在这种情况下,我们将led对象的value设置为True,这意味着 LED 将会亮起。

我们使用超级循环是因为没有操作系统为我们做任何控制。while True线确保我们一遍又一遍地执行我们的 LED on 行动。如果你省略了这一行,那么 LED 将在短时间内点亮一次(短到我们看不见)。几乎所有你为你的微控制器编写的程序都会包含这样一个超级循环。

点击管理部门编辑器中的保存按钮;这应该会将更改保存到“code.py”文件中,您将会看到连接到引脚 A1 的 LED 已经亮起。既然您已经成功控制了数字输出,请随意尝试一下。你能让 LED 受 A0 脚控制吗?还是 pin A2?要做到这一点,您只需将您的电路重新连接到不同的引脚,并在我们创建引脚对象时更改引脚号。

如果你的电路不工作,确保并测试你的连接;我的意思是确保你的电线被正确地插入指定的孔中。检查,检查,再检查。

LED 闪烁

打开 LED 灯很有趣,但有点无聊。如果我们使用电阻将 LED 直接连接到电源,我们会有相同的效果。智能控制的妙处在于我们可以智能地做事。

例如,您可以对您的微控制器进行编程,使其不仅开启 LED,还能以您选择的时间间隔开启 LED。这个程序可以让你验证你的时钟设置是否正确,因为你可以看到 LED 闪烁的频率。如果 LED 以不正确的频率闪烁,您需要确保选择了正确的主板,或者可能需要检查您的 USB 和电路连接。您已经建立了物理电路。您所需要做的就是用几行代码更新您的代码。您可以使用清单 5-2 中给出的程序。

# import pin constants for board we are using
import board

# import pin control
import digitalio

1 # import time library
import time

# create object for pin we are using
led = digitalio.DigitalInOut(board.A1)

# set the pin to output
led.direction = digitalio.Direction.OUTPUT

# super loop
while True:
    # turn the LED on
  2 led.value = True

    # sleep for a second
  3 time.sleep(1)

    # turn the LED off
  4 led.value = False

    # sleep for a second
  5 time.sleep(1)

Listing 5-2Blinking the LED

在这个程序中,除了导入boarddigitalio模块,正如我们在清单 5-1 中所做的,我们还在 1 导入了time库。该库包含允许微控制器遵循时间相关指令的函数。特别是,我们将使用time库的sleep方法。它告诉微控制器等待一定的时间,以秒为单位。

在启动了清单 5-1 中的led pin 对象后,我们进入一个带有while True语句的超级循环,就像之前一样。这一次,循环包含了更多的指令。在 2 ,我们打开 LED,就像清单 5-1 中一样。在 3 ,我们通过在sleep方法中传递 1 秒的值来告诉微控制器等待。在 4 ,我们通过将led对象的value设置为False来关闭 LED。然后我们在 5 再次使用sleep再延迟一秒钟,然后重新开始循环。

点击 Mu 编辑器中的“Save”按钮,您将观察到连接到引脚 A1 的 LED 每秒闪烁一次。如果您想让 LED 闪烁得更快,您可以向sleep方法传递一个更小的值,比如 0.5 秒。如果您希望 LED 运行得更慢,您可以使用更长的值,如 2 秒。

使用输入:添加按钮

下次我们与我们的评估板合作时,我们将关注数字输入。为此,我们将在电路中添加一个按钮。在微控制器上,输入是输出的补充,它们共同构成了两个最基本的微控制器操作。如果使用 LED 就像嵌入式开发中的“Hello World”允许你在屏幕上打印一些东西,那么阅读按钮就相当于学习从键盘上阅读输入。我们的输入项目将读取一个引脚上按钮的状态,然后根据读取的状态,我们将切换连接到另一个 I/O 引脚的 LED。

上拉电阻与下拉电阻

在我们开始构建电路之前,需要注意的是,按钮与微控制器引脚有不同的连接方式。可以使用上拉或下拉电路配置来实现。上拉配置的原理图如图 5-9 所示。

img/511128_1_En_5_Fig9_HTML.jpg

图 5-9

上拉配置

在上拉配置中,我们使用一个高阻值电阻(通常在 4.7 到 10k 之间)将按钮的一端拉至 VDD(也就是说,通过电阻将 I/O 引脚连接至 VDD)。该电阻器确保当按钮被按下时,电路中几乎没有电流流出。

图 5-10 显示了下拉配置中按钮的示意图。

img/511128_1_En_5_Fig10_HTML.jpg

图 5-10

下拉配置

上拉电阻将引脚上拉至高值,而下拉电阻将引脚下拉至逻辑低值,也就是说,我们通过该电阻将 I/O 引脚接地。要在下拉配置中连接开关,开关必须置于电源电压和微控制器引脚之间。

使用上拉配置时,电路将为低电平有效,而使用下拉配置时,电路将为高电平有效。一般来说,您将使用带有上拉电阻的按钮,大多数微控制器在其引脚上提供上拉电阻。然而,这两种配置都可以工作。根据您的电路应用,这种或那种方法可能更好。

在这一部分的后面,我们将讨论如何使用具有上拉和下拉电路配置的微控制器。不过,对于本书中的其它电路,我将只使用上拉配置。如果您愿意,可以按照如下所示的下拉模型修改本书中的任何电路,以使用下拉配置,而不会对电路工作产生任何不利影响。

开关去抖

使用一种开关时,处理微控制器输入时需要考虑的另一个方面是开关去抖。没有一个设备是完美的,一个设备的活动部件越多,与之相关的问题就越多。当机械按钮开关必须连接到微控制器电路时,也是如此。

由于其结构,当您按下按钮开关时,微控制器可能会记录开关的多次按下。这种效果类似于你丢球时发生的情况。球不会立即停下来,而是弹起一段时间。同样,当你按下一个开关时,它没有一个固定的闭合。当开关闭合时,会发生一系列的打开和闭合。

由于这种反弹效应,微控制器有可能会在开关应该闭合时将其记录为断开。为了解决这个问题,您可以实施一种称为开关去抖的技术。这可以使用硬件或软件来完成。

对于硬件去抖,我们可以使用触发器(latch),如图 5-11 所示。触发器的作用相当于一个去抖开关,因为一旦 Q 被输入设置为高电平状态,Q 的输出端将不再发生变化,只要状态发生变化,触发器就会锁存输入值。还有其他硬件去抖方法,例如使用施密特触发器;然而,触发器方法简单而有效。

img/511128_1_En_5_Fig11_HTML.jpg

图 5-11

触发器去反跳

除了添加硬件,您还可以在代码中构建一个去抖动效果。有几种软件去抖方法,包括使用计数器和软件移位寄存器。在本节的后面,我们将使用一个简单的软件延迟来使按钮开关去抖。

带 MCU 原理图的输入(上拉)

我们可以通过构建如图 5-12 所示的程序来构建一个简单的按钮电路。在本电路中,我们使用上拉配置的开关。

img/511128_1_En_5_Fig12_HTML.jpg

图 5-12

带上拉开关的 MCU

按钮上拉电路连接提示

这个电路很简单,很难搞砸。如果您的电路不工作,请确保并测试您的连接。检查,检查,再检查。确保在给设备加电之前连接好电路。

以下是连接电路的推荐步骤:

  1. 按照示意图,按照我们在上一节中所做的那样连接 LED。为方便起见,您可能需要将一根跳线从开发板上的 3.3v 电压输出插座连接到正极供电轨,另一根跳线从接地连接连接到接地供电轨。为了简化布局并更好地利用电路板,可以将两个正供电轨和两个负供电轨连接在一起。

  2. 将开关放置在电路板中央的凹槽上,确保开关两侧的两个引脚都插入到试验板原型制作区域的插孔中。

  3. 将开关的一端连接到同一插座中电阻的一端,将电阻的另一端连接到正极供电轨。不需要使用跳线,因为电阻器可以直接连接到 LED。

  4. 使用跨接导线将开关的另一端接地。

  5. 从开关和电阻器之间的交叉点连接一根跳线,连接到 MCU 上的 A5 引脚。为此,确保开关引线、电阻器引线和跨接导线都在同一插孔内。

当你完成连接电路时,它应该看起来如图 5-13 所示。

img/511128_1_En_5_Fig13_HTML.png

图 5-13

试验板上的 MCU 上拉电路

用 CircuitPython 程序上拉按钮

连接好电路后,我们可以编写一个程序,让 MCU 在上拉电路配置中使用按钮。编辑 code.py 文件,使其类似于清单 5-3 。

# import pin constants for board we are using
import board

# import pin control
import digitalio

# import time library
import time

# create object for LED we are using
1 led = digitalio.DigitalInOut(board.A1)

# create object for the switch we are using
            switch = digitalio.DigitalInOut(board.A5)

# set the LED to output
led.direction = digitalio.Direction.OUTPUT

# set the switch to input
            switch.direction = digitalio.Direction.INPUT

# super loop
2 while True:
       # if our pin goes low
         if switch.value == False:
       # wait 100 ms to see if switch is still on
               3 time.sleep(0.1)

        # if the switch is still on
          4 if switch.value == False:
            # turn the LED on 5
             led.value = True

    else:
        # turn the LED off 6
        led.value = False

Listing 5-3Reading a Pushbutton with MCU Pull-Up

保存文件,观察会发生什么。当您按下按钮时,LED 将亮起,当您松开按钮时,LED 将停止亮起。

你知道这里发生了什么吗?让我们看一下这个程序。我们做我们通常的导入,并为我们在 1 使用的 LED 和开关设置对象。一旦我们有了这些设置,在 1 之后,我们将 LED 的方向设置为输出,将开关的方向设置为输入。我们的超级循环就是一切发生的地方。

2 的超级循环中,我们使用条件 if/else 语句来测试我们的程序,看看按钮是否被按下。如果按钮被按下,那么我们在 3 等待 100 毫秒。如果 100 毫秒过去后,按钮在 4 仍被按下,那么我们在 5 打开 LED。为了完成这个任务,我们使用一个嵌套的 if 语句。检查按钮,等待,然后再次检查按钮,这就是我们所说的开关去抖。在 6 ,我们关闭 LED,这是程序的默认状态。

当我们看条件,我们看到,如果按钮是低,然后我们打开 LED。如果您对此感到困惑,请记住,上拉电阻会将引脚设置为默认高电平状态,因此我们的 MCU 会不断读取引脚上的高电平状态。本质上,由于我们有一个连接到引脚的上拉电阻,因此引脚将保持高电平,直到 3.3 伏的 VCC。当我们按下按钮时,针脚会被拉低至地面。我们也可以在下拉状态下使用按钮,我们将在下一节学习。

带 MCU 原理图的输入(下拉)

我们可以通过构建如图 5-14 所示的程序来构建一个简单的按钮电路。在本电路中,我们使用下拉配置的开关。

img/511128_1_En_5_Fig14_HTML.jpg

图 5-14

带下拉开关的 MCU

按钮下拉电路连接提示

如果您的电路不工作,请确保并测试您的连接。检查,检查,再检查。确保在给设备加电之前连接好电路。

以下是连接电路的推荐步骤:

  1. 按照我们在上一节中所做的那样连接 LED。为方便起见,您可能需要将一根跳线从开发板上的 3.3v 电压输出插座连接到正极供电轨,另一根跳线从接地连接连接到接地供电轨。为了简化布局并更好地利用电路板,可以将两个正供电轨和两个负供电轨连接在一起。

  2. 将开关放置在电路板中央的凹槽上,确保开关两侧的两个引脚都插入到试验板原型制作区域的插孔中。

  3. 将开关的一端连接到电阻器的一端,确保它们在试验板上的同一个插座中,并将电阻器的另一端连接到接地轨;这里不需要使用跳线,因为电阻足够长。

  4. 使用跨接线将开关的另一端连接到正极供电轨。

  5. 从开关和电阻器之间的交叉点连接一根跳线,连接到 MCU 上的 A5 引脚。

当你完成连接电路时,它应该看起来如图 5-15 所示。

img/511128_1_En_5_Fig15_HTML.jpg

图 5-15

试验板上的 MCU 下拉电路

用 CircuitPython 程序进行按钮下拉

连接好电路后,我们可以编写一个程序,让 MCU 在上拉电路配置中使用按钮。一旦你完成了你的电路,你就可以连接你的电路板并编辑 code.py 文件,使其类似于清单 5-4 。

# import pin constants for board we are using
import board

# import pin control
import digitalio

# import time library
import time

# create object for LED we are using
led = digitalio.DigitalInOut(board.A1)

# create object for the switch we are using
switch = digitalio.DigitalInOut(board.A5)

# set the LED to output
led.direction = digitalio.Direction.OUTPUT

# set the switch to input
switch.direction = digitalio.Direction.INPUT

# super loop
while True:
    # if our pin goes high
   1 if switch.value == True:
        # wait 100 ms to see if switch is still on
   2    time.sleep(0.1)

        # if the switch is still on
   3if switch.value == True:
            # turn the LED on
            4led.value = True

    else:
        # turn the LED off
        led.value = False

Listing 5-4Reading a Pushbutton with MCU Pull-Down

保存文件,观察会发生什么。您将获得与使用连接有按钮的 MCU 作为上拉电阻时相同的效果。当您按下按钮时,LED 将亮起,当您松开按钮时,LED 将停止亮起。

您将看到的唯一变化是检查按钮的条件语句。当我们在 1 处查看条件时,我们看到如果按钮为高,我们在 2 处等待 100 毫秒,然后在 3 处,一旦开关仍被按下,然后在 4 处,我们打开 LED。我们必须记住,由于我们有一个下拉电阻连接到引脚,因此引脚将保持低电平至地。当我们按下按钮时,插脚将从电源获得电流。

您可以尝试使用不同的引脚来连接按钮。不要害怕实验,不断尝试新事物。记住这是你学习的唯一途径;尝试交换引脚,尝试将您的“真”条件更改为假,并观察它将产生的效果(LED 将一直亮着,直到您按下按钮,然后它将熄灭)。这就是你学习如何使用你的微控制器所需要做的。

结论

在本章中,我们讨论了许多与微控制器的输入和输出相关的信息。我们学习了微控制器端口以及如何将它们用作输入和输出。我们讨论了如何使用输出来控制 led,以及如何使用输入来控制上拉和下拉状态下的按钮。下一章,我们将扩展到数字信号之外,看看如何执行模拟输入和输出,并利用它与模拟器件接口。

六、数据变换

微控制器的主要优势之一是能够数字化我们熟悉的模拟信息,如温度、湿度和光强。我们可以获取这些模拟传感器读数,并将其转换为数字形式,作为我们可以分析和处理的信号。将模拟信号转换为数字信号是微控制器设备的标志,因为许多利用微控制器的智能设备应用将监控传感器,然后根据该数据执行一些操作。模数转换是任何对微控制器感兴趣的人的基本话题。

在这一章中,我们先来看看模数转换,然后看看我们可以通过这种方法获取数据的一些传感器。我们将使用的传感器包括电位计、光敏电阻和温度传感器。

模数转换

任何像样的微控制器都会配备模数转换模块。模数转换 (ADC)模块将提供给它们的信息转换成微控制器可以处理的二进制表示。

我们的物理世界本质上是模拟的。拿你手机的录音系统来说:你对着手机的麦克风说话,接收到的信号是模拟的。然后,麦克风必须将您声音中的模拟信息转换为微控制器可以解释的数字信号。其中一个应用是语音识别系统,设备中的微控制器需要监听“唤醒词”。“唤醒词”是可以用来引起计算设备注意的单词或短语。微控制器需要将你的声音转换成数字形式,这样它就可以搜索单词,从而将设备从睡眠中唤醒。大多数智能手机还具有过滤背景噪音或放大语音信号的功能。使用微控制器的 ADC 模块,您可以轻松滤除进入麦克风的音频中的噪声。这种类型的滤波器虽然不像我们在第二章中学习的硬件滤波器,但实际上是一种在软件中运行的数字滤波器。然而,它们的功能与您之前学习的过滤器相似。

我们在第二章中了解到,模拟电路被称为连续电路;因此,它们产生的信号本质上是连续的。当我们说一个信号是连续的时,我们的意思是它的特征是随着时间的变化在信号中的点之间平滑过渡。正如我们所知,由数字电路产生的数字信号具有离散性。出于我们的目的,这些可以被认为是连续信号的数字表示。

让我们先仔细看看什么是信号。信号可以被认为是我们用来提取信息的能量。在微控制器电子设备中,信号采用传感器产生的电压或电流形式。传感器产生的信号通常不是我们可以直接获取信息的形式,因为我们缺乏处理传感器信息的能力。微控制器需要为我们读取传感器,然后通过某种显示器以我们可以理解的方式传递信息。微控制器也可以获取这些信息并执行一些操作。例如,数控烤箱可能有一个热电偶传感器来读取烤箱内的温度。当烤箱内的温度达到预设温度时,微控制器需要关闭加热器。这只是可能使用 ADC 模块的一种应用。

ADC 硬件

有几种方法可以执行模数转换,其中最常用的是使用逐次逼近型 ADC 电路。逐次逼近型 ADC 使用各种电路元件。逐次逼近电路使用的这些组件包括将数字信号转换成模拟信号的数模转换器(DAC)。它还使用一个比较器、一个采样保持(S & H)电路和一个控制电路,我们一会儿会讲到。逐次逼近型 ADC 电路看起来有点像图 6-1 。

img/511128_1_En_6_Fig1_HTML.jpg

图 6-1

逐次逼近电路

该电路首先从微控制器引脚获取输入。由于典型微控制器上只有一个 ADC 模块,使用多路复用器电路,我们可以将电压从几个引脚路由到微控制器内的 ADC 模块。这是因为一个模块一次只能读取一个引脚,很多时候,我们可能需要我们的微控制器来读取几个传感器。

为确保 ADC 正常工作,需要拍摄微控制器引脚电压的快照。由于传感器会持续输出数据,ADC 电路需要时间来执行转换,因此需要拍摄该快照。该快照是在微控制器调用 ADC 模块执行转换时拍摄的。简单来说,使用电容是因为,正如我们之前所学,电容可以存储电压。电容器将被充电到与我们想要读取其电压的引脚相同的电压值。例如,如果一个传感器输出 3.7 伏,然后快速输出 3.9 伏,然后 4.0 伏,我们需要一些序列来有效地数字化所有这些读数。这是因为单独的 ADC 模块无法执行并行转换。ADC 模块首先需要获取 3.7 伏电压,将其转换为数字形式,然后是 3.9 伏电压,依此类推。

电容充电的电压将是我们在微控制器上执行逐次逼近程序的电压。我们将 ADC 模块上带电容的前端称为采样保持(S&H)电路。

一旦 S&H 电路上有电压,与 ADC 电路一起工作的比较器就开始工作。这两个电路的结合将持续将输入端的电压减半。当我们说输入端的电压时,我们实际上指的是快照电压。该过程将继续,直到我们达到这些电路的组合所允许的最接近输入值。

在讨论 ADC 的时候,经常出现的一个词就是分辨率。让我们仔细看看什么是决心。ADC 模块有一系列值可用来表示转换后的信号。该值表示为模块输出的二进制位数。本质上,引起比特变化的最小电压就是我们所说的分辨率。根据 ADC 的分辨率,ADC 模块将具有一定数量的电压阶跃,可用于表示测量的电压。电压步进数越多,ADC 模块的分辨率越高。

为了输出信息,ADC 电路将利用来自比较器和 DAC 的转换值,将模块输出的二进制数的最高有效位(MSB)处理到最低有效位(LSB)。这是为了尽可能匹配输入端读取的电压值,并以二进制形式表示。该输出值当然受到 ADC 模块内 DAC 分辨率的限制。这是因为电压可以被削减的次数取决于 DAC 的分辨率。该值随后从 ADC 模块输出,以便 CPU 读取和处理。

为应用选择微控制器时,有时 ADC 的分辨率可能是选择器件的决定性因素。例如,读取温度或光传感器所需的分辨率可能不那么重要。然而,如果我们需要建立一个用于语音识别的电路,您将需要高分辨率来拥有一个功能电路。微控制器上的 ADC 电路通常具有约 8 至 16 位的分辨率。对于大多数传感器读数应用,这就足够了,如果需要更高的分辨率,例如在专业医疗应用中,可以使用外部 ADC IC。

深入了解 ADC

现在,我们来看看讨论 ADC 时可能会遇到的一些额外术语。在讨论 ADC 时,我们首先需要讨论的是采样。如前所述,为了让 ADC 模块有时间处理读取值,必须拍摄电压快照。采样是指我们在一段时间内可以拍摄的快照数量。当我们谈到模数转换中的采样时,我们指的是 ADC 模块复制正在分析的信号并创建尽可能接近原始信号的数字等效物的能力。数模转换器能够捕获的样本越多,原始信号的数字表示就越精确。

你可能会遇到的一个难题是,试图确定我们需要采集的样本的最佳数量,以便恰当地表示我们正在采样的信号。如果我们接受很多信号,那么我们的模块将会很慢,可能没有我们的应用所需要的响应时间。另一方面,如果我们取的样本太少,我们会有一个快速的转换;然而,我们可能没有原始信号的精确表示。称为奈奎斯特采样速率的特殊采样速率是用于确定最小采样时间的基准。

奈奎斯特采样速率是指最小采样速率至少是我们试图采样的信号最高频率的两倍。例如,如果信号的最高频率预计为 10 kHz,则最低采样速率必须为 20 kHz。换句话说,采样保持电路必须每秒拍摄 20,000 张快照才能充分测量信号。

ADC 模块还有一个数据采样速率,通常用 ADC 模块能够测量的每秒样本数(SPS)来衡量。通常,我们会看到制造商以每秒千个样本(kSPS)或每秒百万个样本(MSPS)来指定采样速率。

ADC 转换的另一个方面是量化。量化是指将 ADC 的输入电压值映射到 ADC 能够产生的输出值的过程。例如,一个 10 位 ADC 模块将具有从 0 到 1023 的步长。我们称这个从 0 到 1023 的值为 ADC 的量化电平。8 位 ADC 的量化级别为 0 至 255。这种量化是对等于我们试图在引脚上测量的电压的值进行舍入的名称。这意味着,在最严格的意义上,我们可以将分辨率重新定义为两个相邻量化级别之间的度量距离。

电位计

我们要研究的第一个配合 ADC 模块使用的器件是电位计。当我们观察现实世界中的嵌入式器件时,你会发现许多器件中都有电位计。例如,立体声系统和扬声器系统上的音量旋钮使用电位计,以允许用户调整这些设备上的音量。这些设备都使用电位计电路来正常工作。在图 6-2 中,我们看到了电位计的样子。

img/511128_1_En_6_Fig2_HTML.jpg

图 6-2

电位计

电位计可能有不同的形状因素;但是,如果我们观察它的机械结构,我们会发现它们都是三针装置,在顶部有一个转动机构,以便进行调整。有时,您可能会看到一个看起来像电位计的器件,上面有五个或更多的引脚。这不是电位计,而是旋转编码器,功能不同。

电位计的三个引脚中,一个连接到供电轨,另一个连接到地电源,第三个连接到微控制器的输入引脚。当您调节电位计上的旋钮时,您所做的是将模拟输入上的电压从 0 伏特改变到 VDD。这通常可以在 0 伏到 3.3 或 5 伏的范围内。

发生这种情况的原因是电位计可以被认为是一种分压器电路。分压器是一种电路,它利用两个电阻串联时存在的特性,将较大的电压转换成较小的电压。在图 6-3 中,我们看到了这个分压器的布局。

img/511128_1_En_6_Fig3_HTML.jpg

图 6-3

该分压器

如图 6-3 所示的分压器将输出 VCC 上电压的大约一半。因此,如果 VCC 是 5 伏,那么分压器的输出将是 2.5 伏。分压器的输出被确定为在两个电阻的中点测得的电压。这就是分压器的作用。它们输出输入端输入电压的一小部分。分压器的输出不是任意的,而是由组成分压器电路的电阻值决定的。一旦我们知道了电阻值,我们就可以使用分压器公式来计算输出。当我们需要使用电阻获得特定的电压输出时,这个公式非常有用。

为了计算分压器的输出,我们使用以下公式

想要=葡萄酒(R2/(R1 + R2)

我们知道图 6-3 中的分压电路将提供一半的输入电压;然而,如果我们想计算同样的数学,我们可以使用我们的分压公式。如果我们想计算 Vout,可以使用公式并输入 Vin、R1 和 R2 的值。进行计算时,我们将 R1 作为顶部电阻,R2 作为底部电阻,我们得到

vot = 5v(1/(1+1)= 5v x 0.5 = 2.5

电位计起电阻器的作用,并表现出电阻。然而,它可以被认为是一个完全不同的设备,因此它有自己的示意性符号。我们在图 6-4 中看到电位计的示意符号。

img/511128_1_En_6_Fig4_HTML.jpg

图 6-4

电位计示意图符号

电位计的示意符号由一个指向电阻器的箭头组成。这意味着电阻是可调的。有时,您会看到如图 6-5 所示的较小版本的电位计。

img/511128_1_En_6_Fig5_HTML.jpg

图 6-5

微调电位器

这种电位计被称为微调电位计或微调电位计。功能与普通电位器相同;但是,它被设计为隐藏在最终产品中,并由制造商或服务人员进行调整,而不是由产品的最终用户进行调整。这些很容易识别,因为常规电位计的调节旋钮从器件中伸出,必须用螺丝刀调节才能改变它们的值。

CircuitPython 中的模数转换

CircuitPython 提供了使用 ADC 的库。我们将为模数转换工作的库是

  • board–board 模块包含我们正在使用的电路板的引脚常量。

  • analog io–这是一个包含所有类的模块,提供对我们正在使用的模拟输入和输出功能的访问。该模块允许我们从模拟引脚读取模拟数据。

  • 时间–时间库包含允许微控制器使用时间相关功能的函数。睡眠方法是我们用来帮助微控制器计时的方法。

Note

有些主板可能不支持某些功能,如模拟输入功能,尤其是当它们的固件处于测试阶段时;因此,最好查看您所用主板的发行说明。

带 MCU 的 ADC 原理图

电位计可以连接到我们的 MCU,如图 6-6 所示。

img/511128_1_En_6_Fig6_HTML.jpg

图 6-6

带电位计的 MCU

ADC 电路连接技巧

以下是连接电路的推荐步骤:

  1. 将一根跳线从微控制器上的 A0 引脚连接到试验板原型制作区域的一个插座。

  2. 将电阻器的中心引线连接到步骤 1 中使用的试验板同一行中的另一个插座。

  3. 用一根跳线将它从电位计的一根引线连接到 VCC。

  4. 用跨接线将电位计的最后一根引线接地。

当你完成电路连接后,它看起来应该类似于图 6-7 。

img/511128_1_En_6_Fig7_HTML.jpg

图 6-7

带电位计试验板的 MCU

一旦你有了你的电路设置,我们运行如清单 6-1 所示的程序。记得检查你的连接,确保它们符合你的原理图,否则你有损坏电路板电路元件的危险。出于这个原因,我总是建议您在关闭电源的情况下连接电路,然后在编写测试程序之前打开电源。

带电位计程序的 CircuitPython

现在我们已经连接了物理电路,我们可以看看如何使用 CircuitPython 读取电位计。在 Mu 编辑器中编辑您的 code.py 文件,以反映清单 6-1 。

# import time module
import time

# import the board module
import board

# import our analog read function
1 from analogio import AnalogIn

# read the A0 pin on our board
2 analog_in = AnalogIn(board.A0)

# get the voltage level from our potentiometer
3  def get_voltage(pin):
    return (pin.value * 3.3) / 65536

# print the voltage we read

4  while True:
    print((get_voltage(analog_in),))
    time.sleep(0.1)

Listing 6-1MCU with Potentiometer Program

我们做的第一件事是导入控制电路板所需的 CircuitPython 库。这些是我们通常进口的时间和董事会图书馆。在(1)中,我们从analogio模块中导入AnalogIn对象。这是允许我们执行模拟功能的库。我们程序(2)的下一步是创建一个 p in 对象,它将允许我们读取正在使用的特定模拟引脚。在这种情况下,我们使用电路板上的引脚 A0,并将其命名为analog_in

沿着(3)的程序,我们有一个函数,允许我们从电位计读取电压电平。该函数get_voltage将引脚作为输入,然后执行计算以返回正确的电位计读数。在我们的超级循环中的(4)处,我们使用get_voltage函数读取我们的模拟输入引脚,并将该读取电压连续打印到我们的串行控制台。一旦一切运行正常,在你的串行控制台中,你应该看到如图 6-8 所示的输出。

img/511128_1_En_6_Fig8_HTML.jpg

图 6-8

带电位计输出的 MCU

当我们调整电位计的输入时,我们看到输出从 0 伏变为 3.3 伏左右,该值被打印到串行端子上。

光敏电阻

由于这种读取模拟传感器的能力,我们现在能够使用的一种传感器是光敏电阻。光敏电阻是一种电阻器,它根据照射到其上的光量来改变电阻。当没有光照射在光敏电阻上时,它具有极高的电阻,大约为几十万欧姆。然而,当光照射到光敏电阻上时,电阻下降到几百欧姆。为了创建一个读取光敏电阻的程序,我们需要创建一个分压电路。这个分压电路将由光敏电阻和常规电阻组成。然后,微控制器将读取电路的输出,以确定光敏电阻的亮度变化。

带 mcu 原理图的光探测器

我们如图 6-9 所示连接电路。光敏电阻连接到引脚 A1,LED 连接到引脚 A2。

img/511128_1_En_6_Fig9_HTML.jpg

图 6-9

带摄像头的单片机

光电探测器电路连接提示

以下是连接电路的推荐步骤:

  1. 将一根跳线从微控制器上的 A2 引脚连接到试验板原型制作区域的插座,再连接到 LED 的一根引线。

  2. 用跨接导线将 LED 的另一根导线接地。

  3. 用一根跳线将光敏电阻的一根引线连接到 VCC。

  4. 将光敏电阻的另一根引线连接到 10k 电阻器的一根引线上。

  5. 用一根跨接导线将 10k 电阻的另一根导线接地。

  6. 在电阻器和光敏电阻的交叉点与微控制器上的引脚 A1 之间连接一根跳线。

当你完成连接电路时,它应该看起来如图 6-10 所示。

img/511128_1_En_6_Fig10_HTML.jpg

图 6-10

带照片显示试验板的 mcu

现在我们有了电路设置,我们可以写一些代码了!

带 CircuitPython 程序的光敏电阻

在 Mu 编辑器中编辑您的 code.py,使其类似于清单 6-2 。

# import the board module
import board

# import time library
import time

# import our analog read function
from analogio import AnalogIn

# import pin control
import digitalio

# set an adjust value
1 adjustValue = 2000

# create object for pin we are using
2  led = digitalio.DigitalInOut(board.A2)

# set the pin to output

3 led.direction = digitalio.Direction.OUTPUT

# read the A1 pin on our board
4 photoresistor = AnalogIn(board.A1)

# set an ambient light value
5 ambientLightValue = photoresistor.value

# release the pin for other use
6 photoresistor.deinit()

# print the voltage we read
7 while True:
    # read the photoresistor
    photoresistor = AnalogIn(board.A1)
    # if bright turn the LED off
    if (photoresistor.value > ambientLightValue - 2000):
        led.value = False
    # turn the LED on
    else:
        led.value = True

    # release the pin for other use
    photoresistor.deinit()

Listing 6-2MCU with Photoresistor Program

这个程序做了很多事情。让我们看看会发生什么。在程序的顶部,我们导入我们的电路板、时间、模拟和数字库。在(1)中,我们创建了一个adjustValue变量,用于帮助读取光敏电阻。我们的下一步是在(2)处创建一个表示真实世界 LED 的对象,并将其设置为(3)处的输出。然后,我们创建一个对象来表示(4)处的光敏电阻。在(5)中,我们从光电管中读取一个读数并存储该值。这是当前存在的环境光的值。

注意,在读取数据后,我们必须使用(6)中的deinit方法来允许我们的 pin 在程序的其他部分使用。如果我们不这样做,那么我们就不能在超级循环中使用 pin。

在(7)的超级循环中,我们不断读取光敏电阻值。有一个条件,检查是否有黑暗的 LED。如果有黑暗,LED 就打开,有光亮的时候,我们就把 LED 关掉。

保存程序并运行它。你会观察到,用手盖住光敏电阻,LED 就亮了;然而,当 LED 处于环境亮度或更亮时,LED 将不被点亮。

温度传感器

现在我们可以使用模拟输入的一种传感器是温度传感器。我们将使用的温度传感器是 TMP36 温度传感器,它使用电压作为输出,以摄氏度为单位提供温度读数。TMP36 如图 6-11 所示。

img/511128_1_En_6_Fig11_HTML.jpg

图 6-11

TMP36 温度传感器认证:adafruit.com ada fruit

TMP36 温度传感器器件有三个引脚。一个是连接到正电源的 VCC 引脚。还有一个连接到地的 GND 引脚和一个由微控制器读取的模拟电压输出引脚。根据 VCC 的电压值,微控制器可以进行一些计算来确定我们正在读取的温度。

该传感器的工作电压范围为 2.7 至 5.5v,是一款非常通用的传感器,可以与所有配有 ADC 模块的普通微控制器配合使用。它能够与基于 CircuitPython 的 3.3 伏 MCU 配合使用,该电压在器件的电压范围内。温度传感器可以测量从-50 摄氏度到 125 摄氏度的温度,这对于您将要承担的大多数项目来说都是一个很好的范围。

带 MCU 原理图的温度传感器

我们如图 6-12 所示连接电路。我们的 TMP36 连接到 A0 模拟输入引脚。使用 CircuitPython MCU 时,我们需要电容 C1 和电阻 R1 来获得传感器的精确读数。这是由于高速读取传感器的设备的基本配置。

img/511128_1_En_6_Fig12_HTML.jpg

图 6-12

带 MCU 原理图的温度传感器

温度传感器电路连接提示

以下是连接电路的推荐步骤:

  1. 将一根跳线从 TMP36 温度传感器的 VCC 引脚连接到试验板的正供电轨。

  2. 用一根跳线将传感器的接地引脚连接到试验板的接地轨。

  3. 将 47k 电阻器的一根引线连接到 TMP36 传感器的输出引脚,并将电阻器的另一根引线连接到传感器的接地引脚。

  4. 将电容的一根引线连接到接地引脚,另一根引线连接到 TMP36 传感器的输出引脚。

  5. 在温度传感器的输出引脚和运行 CircuitPython 的 MCU 上的引脚 A0 之间连接一根跳线。

当您完成电路连接时,它看起来应该类似于图 6-13 。

img/511128_1_En_6_Fig13_HTML.jpg

图 6-13

试验板上集成 MCU 的温度传感器

带 CircuitPython 程序的温度传感器

在 Mu 编辑器中编辑您的 code.py,使其类似于清单 6-3 。这个例子由 Adafruit Industries 提供的用于读取传感器的例子修改而来。

# import the board module

import board

# import the time module
import time

# import module for reading analog input
import analogio

# sensor connected to pin A0
1 TMP36_PIN = board.A0

# function for reading the temperature sensor
2 def tmp36_temperature_C(analogin):
    # convert the voltage to a temperature
    millivolts = analogin.value * (analogin.reference_voltage * 1000 / 65535)
    return (millivolts - 500) / 10

# create instance of analog object for sensor
3 tmp36 = analogio.AnalogIn(TMP36_PIN)

# super loop

4 while True:
    # read temperature in Celsius
    temp_C = tmp36_temperature_C(tmp36)
    # use Celsius to get Fahrenheit value
    temp_F = (temp_C * 9/5) + 32

    # print our temperature
    print("Temperature: {}C {}F".format(temp_C, temp_F))

    # every second
    time.sleep(1.0)

Listing 6-3MCU with Temperature Sensor Program

在这个程序中,我们做了我们通常的导入工作来启动和运行开发板。在(1)中,我们设置了连接到模拟输入引脚 A0 的温度传感器。在(2)中,我们有一个读取温度传感器并进行电压到温度转换的函数。在(3)中,我们为温度传感器创建一个模拟对象的实例。在主程序中,我们在(4)处运行一个超级循环,读取传感器数据并每秒钟将其打印到输出控制台。如果您查看您的串行控制台,您应该会得到类似于我在图 6-14 中得到的输出。

img/511128_1_En_6_Fig14_HTML.jpg

图 6-14

温度传感器输出

结论

在本章中,我们讨论了模数转换的基础知识。我们研究了微控制器 ADC 电路在硬件中的工作原理,在学习这些电路工作原理的过程中,我们发现了电位计、光敏电阻和分压器的工作原理。我们还学习了如何将 CircuitPython 用于模拟输入。利用这些信息,我们能够读取温度传感器并将信息输出到串行终端。有了这里获得的知识,数百个传感器现在由你支配。

七、通信协议

在上一章中,我们讨论了如何利用微控制器读取传感器,即利用微控制器上的模数转换器。然而,当我们开始使用串行通信时,可以与微控制器接口的器件种类会急剧增加。在本章中,我们将了解 UART、SPI 和 I2C 的串行通信协议。理解这些协议将扩展我们微控制器的接口能力。

微控制器通信

在我们开始研究微控制器上的具体通信协议之前,我们先讨论一下微控制器的一般通信。在微控制器系统中,有两种类型的通信。有串行通信和并行通信。并行通信系统利用多条信号线来传输数据。并行通信速度很快,是老式计算系统中传输数据的首选方法。这是因为这些旧设备中的处理器速度不够快,无法处理与串行通信相关的开销。当我们谈到开销时,我们指的是软件开销,即 CPU 花费在做额外工作上的额外时间。

在串行通信中,数据以比特流的形式通过比并行系统少得多的通信线路发送。虽然串行通信比并行通信慢,但如果数据传输速率相等,现代硬件的速度足以消除对并行系统的需求。这并不是说并行通信已经过时。例如,像显示器这样的设备受益于使用并行通信获得的更高的传输速率。

如今,并行通信被保留用于与彼此距离较近的设备进行通信。这是因为通过并行总线传输数据需要大量额外的硬件。例如,组成传输线的电线需要使用额外的资源。因此,每当我们远距离传输数据时,我们倾向于使用串行通信。

如今存在无数的串行通信方法。有些很简单,而有些则采用复杂的协议,这些协议足够强大,可用于航空航天和汽车应用。

USART 通信公司

在本节深入探讨之前,我们应该花点时间讨论一下串行通信。串行通信有两种方式:异步或同步。

异步通信以比特流的形式发送数据。这个比特流通常包括三个部分。在数据流的开头是一个起始位,表示数据开始传输的时间。最后还有一个停止位,指示数据停止传输的时间。在起始位和停止位之间,有一个数据包,其中包含我们要传输的数据。数据包是我们给一个字节的格式化数据起的名字。发送开始和停止位的需要增加了一点软件开销,这限制了传输的速度。然而,这些起始和停止位对于实现通信设备之间的同步是必要的。

同步串行通信消除了与发送起始和停止位相关的开销,因此需要较少的软件开销。这是通过使用时钟来同步数据传输来实现的。然而,这种方法确实需要额外的线路来传送时钟信号。因此,虽然它确实提高了数据传输效率,但它需要额外的硬件才能正常运行。

到目前为止,在我们的整个项目中,我们一直在利用 USART 通信形式的串行通信,而没有意识到这一点。USART 是一种串行通信协议,代表通用同步异步收发器。每次我们在机器上使用 PuTTY 与 CircuitPython 微控制器对话时,我们都在利用 USART 的强大功能。

虽然它是一种能够同步使用的协议,但这些功能很少被利用,而且在基于微控制器的系统中,绝大多数 UART 通信都是异步使用的。因此,USART 通常被称为 UART(通用异步收发器),缩写中省略了同步位。因此,我们将把重点放在异步通信方法上。

异步 UART 有两条通信线路,分别连接到发送(TX)引脚和接收(RX)引脚。这些引脚在发送数据的发送器和接收数据的接收器之间共享。为了成功实现数据通信,器件之间还必须有公共接地连接。

由于没有时钟线来帮助通过异步 UART 传输数据,接收器和发送器必须就数据传输速率达成一致。这种数据传输速率称为波特率。波特率衡量每秒传输的位数。大多数低级通信接口使用 9600 波特的波特率。CircuitPython UART 接口的波特率约为 115,200。

UART 模块可以在三种通信模式下工作,即单工、半双工和全双工通信方式。为了理解这些是如何工作的,假设我们有一个 UART 通信系统,如图 7-1 所示。

img/511128_1_En_7_Fig1_HTML.jpg

图 7-1

UART 通信系统

如果数据从器件 1 单向传输到器件 2,这称为单工通信。如果数据从器件 1 传输到器件 2,然后从器件 2 传输到器件 1,但不是同时传输,则这称为半双工通信。如果数据从器件 1 传输到器件 2,然后同时从器件 2 传输到器件 1,这称为全双工通信。

更深入地了解 UART

我们之前在 UART 环境中讨论的比特流称为数据帧。数据帧是我们给单个数据传输单元起的名字。如果我们观察 UART 数据帧,会发现它由 1 个起始位、5 至 9 个数据位、0 至 1 个奇偶校验位和 1 至 2 个停止位组成。当我们将所有这些放在一起时,UART 数据帧类似于图 7-2 所示的排列。

img/511128_1_En_7_Fig2_HTML.jpg

图 7-2

UART 数据帧

我们知道,起始位的目的是指示数据传输的开始,而停止位指示数据传输何时停止。但是,您可能想知道奇偶校验位的用途是什么。使用奇偶校验位原因是为了帮助错误检测。

异步 UART 有可能发生干扰。当我们谈到干扰时,我们的意思是某些因素可能导致数据损坏和数据传输中的错误。奇偶校验位用于帮助这种情况。奇偶校验位被添加到数据包的停止位之前。添加该奇偶校验位使得数据帧中 1 的数量为偶数或奇数。当数据包到达目的地时,如果数据帧与预期的偶数值或奇数值不同,我们就知道传输中有错误。利用这些信息,可以重新发送或丢弃数据包。

CircuitPython 中的 UART

在 CircuitPython 中,可以使用板载硬件外设进行串行通信,也可以通过称为位碰撞的过程使用软件例程来执行串行通信。由于使用硬件外设更有效,这是我们将用来控制串行设备的方法。

CircuitPython 提供“busio”库,支持 UART、SPI 和 I2C 通信的硬件外设。

这些是我们将用来与 CircuitPython MCU 进行 UART 通信的模块:

  • board–board 模块包含我们正在使用的电路板的引脚常量。

  • 时间–时间库包含允许微控制器使用时间相关功能的函数。睡眠方法是我们用来帮助微控制器计时的方法。

  • busio——这个库包含允许我们使用 CircuitPython 工具来控制硬件的函数。

带 MCU 原理图的 USB-UART

我们连接微控制器,如图 7-3 所示。我们可以使用 USB 转 UART 转换器,将微控制器连接到计算机。

img/511128_1_En_7_Fig3_HTML.jpg

图 7-3

带 UART 的 MCU

带 USB-UART 电路连接提示的 MCU

以下是连接电路的推荐步骤:

  1. 首先,在进行串行通信连接时,最好保持电路断电,因为错误的连接会损坏计算机的 USB 端口。

  2. 用跳线将 MCU 连接到 USB 转 UART 模块,如下所示。用第一根跳线将 USB-UART 模块上的 RX 引脚连接到微控制器的 TX 引脚,并将 USB-UART 模块的 TX 引脚连接到微控制器的 RX 引脚。

  3. 如果你的 USB-UART 模块是 5 伏的,你必须通过一个逻辑电平转换器将 MCU 连接到模块。为此,将 USB-UART 模块的 RX 和 TX 引脚连接到逻辑电平转换器的 HL 引脚,并将微控制器的 RX 和 TX 引脚连接到逻辑电平转换器的 LV 引脚。

  4. 用跳线将模块上的接地引脚连接到试验板接地轨的接地引脚。

  5. 连接好所有东西后,仔细检查你的连接,然后给你的电路通电。

当你完成连接电路时,它应该看起来如图 7-4 所示。

img/511128_1_En_7_Fig4_HTML.jpg

图 7-4

MCU 连接到 USB-UART 试验板

UART 与 CircuitPython 程序

一旦您的电路被连接,在 Mu 编辑器中编辑您的 code.py 文件以反映清单 7-1 。

# import the board module
import board

# import time library
import time

# import library for working with UART
   1   import busio

#setup uart 9600 baudrate, 8 bits, no parity, 1 stop
   2   uart = busio.UART(board.TX, board.RX, baudrate=9600,
                  bits=8, parity=None,
                  stop=1, receiver_buffer_size=64)

   3   while True:
    # read up to 64 bytes of data
    dataRead = uart.read(64)

    # once we got data print it
    if dataRead is not None:
       print("I got data")

Listing 7-1Our Program

我们的程序是一个简单的程序,用于验证 UART 模块是否工作。首先,我们导入开发板和时间库来启动和运行开发板。在(1)中,我们导入了 busio 库来使用 UART。在(2)处,UART 模块随后设置为板的默认 RX 和 TX 引脚。我们还将 UART 配置为 9600 波特值和 8 位数据,并且不执行任何奇偶校验。此外,我们设置了 1 个停止位,并为接收器设置了 64 字节的缓冲区大小。这些设置是标准 UART 值。在(3)中,我们有一个无限循环,它从 UART 模块读取数据,然后让我们知道 UART 模块何时接收到数据。

当您保存文件和程序运行时,您可以开始与您的电路进行交互。如果您通过 USB-UART 模块的串行连接输入数据,您的串行终端将显示消息“我有数据”。

SPI 通信

我们接下来要讨论的串行通信协议是 SPI。SPI 代表串行外设接口。SPI 是微控制器使用的另一种串行通信方法。与可以同步或异步操作的 USART 不同,SPI 是一种仅同步协议。同步使得 SPI 成为一种快速协议,因为对模块速度的限制通常取决于模块的硬件限制。SPI 使用时钟来保持器件同步,该模块可以以通信时钟允许的最快速度运行。

SPI 对相互交互的器件采用主从关系。产生时钟的器件称为主器件,与之通信的另一个器件称为从器件。微控制器上的典型 SPI 模块可以在主机模式和从机模式下工作。

SPI 有四条线路。第一行叫做主出从入(MOSI)。该线路从主机向从机发送数据。另一条线路从从机向主机发送数据;我们称之为主入从出(MISO)。还有一条时钟线(SCK ),负责传送时钟信号,使器件保持同步。最后,还有一个从机选择(SS)线路,也称为片选(CS)线路。这条线有特殊的用途。您可以看到,SPI 总线可以支持从机选择线路允许的从机数量。当主机选择器件的从机选择线时,从机将知道必须选择该线。

你可以在图 7-5 中看到典型的 SPI 总线连接。

img/511128_1_En_7_Fig5_HTML.jpg

图 7-5

SPI 总线

深入 SPI

查看图 7-6 中的图像,这样您就可以了解 SPI 通信的内部情况。

img/511128_1_En_7_Fig6_HTML.jpg

图 7-6

SPI 基本框图

SPI 利用几个移位寄存器将并行数据转换成串行数据,然后通过 SPI 总线传输。回想一下,我们说过主机生成时钟。该时钟用作模块内所有移位寄存器的输入,进而让主机控制传输速度。

为了传输数据,SPI 总线的工作原理如下。从机选择线从高电平变为低电平,开始传输。然后根据时钟的上升沿或下降沿传输数据。我们称之为时钟阶段。根据用户选择的时钟极性,SPI 模块将知道何时工作。时钟极性是时钟线默认状态的名称,在我们称之为空闲状态期间,它可以是高电平或低电平。在该阶段结束时,从机选择线从低电平变为高电平。

SPI 电路连接

  1. 用一根跳线将 MCU 上的 MOSI 引脚连接到 MISO 引脚。

SPI 与 CircuitPython 程序

在将一根跳线从 MISO 引脚连接到 MOSI 引脚后,打开 Mu 编辑器并编辑您的程序,使其类似于清单 7-2 。

# import the board module
import board

# import time library
import time

# import library for working with SPI
1   import busio

# setup SPI
2   spi = busio.SPI(board.SCK, board.MOSI, board.MISO)

# lock spi bus
3   while not spi.try_lock():
    pass

# super loop
4   while True:
    # print numbers 0 to 8 via SPI bus
    for x in range(48, 57, 1):
        # buffer for send
        tx = chr(x)

        # buffer for receive
        rx = bytearray(1)

        # SPI RX_TX

        spi.write_readinto(tx, rx)

        # print sent and received data
        print("tx: " + str(tx))
        print("rx: " + str(rx))

        # sleep for 500 ms
        time.sleep(0.5)

Listing 7-2Our SPI Loopback Program

我们使用的 SPI 程序就是所谓的回送程序。回送程序是发送者发送和接收它已经发送的消息的程序。该回送程序可以用来验证 SPI 总线的工作情况。

在(1)中,我们使用 busio 库来处理 SPI 硬件外设。在(2)中,我们通过创建实例来设置 SPI 模块,同时设置 SCK、MOSI 和 MISO 引脚。在(3)中,我们需要锁定 SPI 总线才能使用。这是 CircuitPython 的要求之一,在您希望使用总线的任何时候都必须这样做。在(4)的超级循环中,我们使用 SPI 模块打印数字 0 到 8 来传输字符。发送完字符后,我们使用 SPI 模块读取已发送的字符。一旦字符被传输,我们每 500 毫秒将发送和传输的字符打印到串行终端。

I2C 通信公司

我们要看的最后一个通信协议是 I2C(集成电路间)协议。I2C 是一种串行协议,最近变得非常流行。你看,I2C 只需要两条通信线路。它们是串行时钟线(SCL)和串行数据线(SDA)。像 SPI 一样,I2C 也有一个主机,负责控制与之通信的总线和从机。在这方面,它与 SPI 的不同之处在于,I2C 总线上的任何器件都可以在任何时间点成为主机。看看图 7-7 你就知道 I2C 公共汽车是什么样子了。

img/511128_1_En_7_Fig7_HTML.jpg

图 7-7

I2C 公共汽车

正如您所观察到的,主设备不需要使用从设备选择线来与总线上的 I2C 设备通信。这是因为 I2C 总线上的每个设备都被分配了一个我们可以用来读取它们的地址。该地址长度为 7 位,有效地址范围从 0 到 127,最多允许 128 个设备连接到总线。

由于我们缺少从机选择线,因此只有总线上的主机才能发起通信,这一点尤为重要。如果两个设备试图同时发起通信,就可能发生总线上的冲突。I2C 总线上的两条线被称为开漏引脚。开漏引脚需要一个上拉电阻才能输出高电平。因此,在 I2C 总线上,需要一个 4.7k 的上拉电阻来保证总线正常工作。

深入 I2C

当时钟线为低电平时,I2C 通过让数据线改变状态(高电平或低电平)来工作。当时钟线为高电平时,使用启动和停止信号读取数据,以实现无错误的数据传输。起始和停止条件都是通过保持时钟线为高电平,同时改变数据线上的电平来实现的。

像在所有串行通信协议中一样,数据在 I2C 中是通过使用包来传输的。为确保从机与主机通信,数据包中会发送一个特殊的应答位,称为应答位。当与接收器通信时,发送设备将释放 SDA 线。如果线路被拉低,那么我们将得到一个确认“ACK ”,因为我们知道器件已准备好通信。如果没有,我们将得到一个不承认“NACK”,我们知道设备不能正常通信。

图 7-8 向我们展示了 I2C 传输序列的样子。

img/511128_1_En_7_Fig8_HTML.jpg

图 7-8

I2C 传输

最近进入嵌入式设备领域的一种新总线是系统管理总线(SMBus)。SMBus 主要与 I2C 兼容,只是在功耗、超时要求和最大总线速度等方面有所改进。

CircuitPython 中的 I2C 支持

CircuitPython 提供了一个库,用于处理微控制器上的 I2C 模块。我们使用与 UART 和 SPI 模块相同的模块,即 busio 模块。

MPU6050

我们将用来测试 CircuitPython 的器件是 MPU6050。该器件在一个封装中集成了加速度计和陀螺仪,这使得它可以方便地用于传感器融合和机器人应用等领域。

设备内的陀螺仪测量角速度。这意味着陀螺仪测量旋转物体的角度变化率。另一种说法是,陀螺仪本质上是测量某个物体相对于三个轴的旋转,这三个轴是 X、Y 和 Z 轴。

设备中的加速度计测量物体速度变化的速率(实质上是作用在物体上的力)。这通常用重力或米/秒(或米/秒的平方)来衡量。

MPU6050 还包括一个板载温度传感器,我们可以用它来读取温度数据。

由于器件采用 SMD 封装,因此在使用器件时,通常在分线板上使用。分线板是一种特殊电路板的名称,通过提供外部焊盘,我们可以轻松连接到 MPU6050 等 SMD 器件。我们看到一个这样的 MPU6050 分线板,如图 7-9 所示。

img/511128_1_En_7_Fig9_HTML.jpg

图 7-9

MPU6050 早餐信贷:Adafruit,adafruit.com

来自加速度计和陀螺仪的数据可以在我们称之为传感器融合的过程中结合起来。当我们融合来自加速度计和陀螺仪的传感器数据时,我们得到的就是所谓的惯性测量单元(IMU)。IMU 通常使用软件过滤器将这些数据融合在一起。这种类型的过滤器本质上是对时间点上的一些数据进行采样的数学运算。有许多方法可以实现这种滤波,包括互补滤波器、卡尔曼滤波器和 Madgwick 滤波器。每种过滤器都有其优点和缺点,您可以根据自己的应用来决定。本书不涉及传感器融合;但是,如果您愿意,您可以使用各种过滤器对我们从 MPU6050 读取的数据进行研究。

带 MCU 原理图的 I2C

我们将 MPU6050 连接到 MCU,如图 7-10 所示。我们必须确保包括上拉电阻。请注意,1k 和 10k 之间的值将适用于 4.7k,这是一个很好的最佳值。

img/511128_1_En_7_Fig10_HTML.jpg

图 7-10

MCU 连接到 MPU6050 原理图

I2C 电路连接提示

以下是连接电路的推荐步骤:

  1. 首先,我们将每个电阻的一条引线连接到 VCC。

  2. 如下连接每个电阻的另一根引线。将第一个电阻的自由端连接到 SCL 线,将另一个电阻的自由端连接到 SDA 线。

  3. 接下来,在 MPU6050 的 SCL 和 SDA 引脚与微控制器的 SCL 和 SDA 引脚之间连接一条跳线。

  4. 将 MPU6050 上的 VCC 引脚连接到试验板上的 VCC 轨,将 GND 引脚连接到试验板的 GND 轨。

当你完成连接电路时,它应该看起来如图 7-11 所示。

img/511128_1_En_7_Fig11_HTML.jpg

图 7-11

MCU 连接到 MPU6050 试验板

I2C 与 CircuitPython 程序

打开 Mu 编辑器并编辑您的程序,使其类似于清单 7-3 。

# import the board module
import board

# import time library
import time

# import library for working with I2C
import busio

# setup the I2C module
1   i2c = busio.I2C(board.SCL, board.SDA)

# lock the I2C bus
2   while not i2c.try_lock():
    pass

# super loop

3   while True:
    # scan for addresses on the bus
    print("I2C addresses found:", [hex(device_address)
                                   for device_address in i2c.scan()])

    # every two seconds
    time.sleep(2)

Listing 7-3Our I2C Test Program

我们编写的程序将确保传感器设置正确,并且通过 I2C 总线进行通信。我们执行通常的导入来让微控制器运行。在(1)中,我们为 I2C 通信进行了设置,创建了我们可以使用的 I2C 总线的实例,具有设备的物理 I2C 引脚的参数。在(2)中,我们锁定了总线的实例,因为这是 CircuitPython 所要求的。在(3)的主循环中,我们一直扫描 I2C 总线,并将连接的设备打印到我们的串行控制台。

一旦传感器正确连接,您应该看到连接到总线的 MPU6050(地址为 0x68)将被打印到我们的串行控制台。

添加库

检测到传感器在那里是好的;然而,我们也可能想要读取传感器数据。为此,我们必须添加已创建的库,以便轻松连接传感器。为设备添加库很简单。首先,请确保从 Adafruit 网站下载适用于您正在使用的设备的库包,网址是:

https://circuitpython.org/libraries

一旦你下载了你的库,解压它。打开连接到计算机的“CIRCUITPY”驱动器,并打开 lib 文件夹。将库包中的以下文件放入“CIRCUITPY”驱动器的 lib 文件夹中,以使用本节内容:

  • adafruit_register 文件夹

  • adafruit_bus_device 文件夹

  • adafruit_mpu6050.mpy 文件

一旦添加了这些文件,您就可以继续下一部分了。

MPU6050 与 CircuitPython 程序

现在我们已经导入了库,我们将能够读取 MPU6050。我们将读取温度、陀螺仪和加速度计数据,并将其打印到串行控制台。打开管理部门编辑器并编辑文件,使其类似于清单 7-4 。

# import the board module
import board

# import time library
import time

# import library for working with SPI
import busio

#import library for working with the MPU6050
1   import adafruit_mpu6050

# setup I2C

2   i2c = busio.I2C(board.SCL, board.SDA)

# create instance of MPU6050
3   mpu = adafruit_mpu6050.MPU6050(i2c)

#super loop
4   while True:
    #print accelerometer data
    print("Acceleration: X:%.2f, Y: %.2f, Z: %.2f m/s²" % (mpu.acceleration))

    # print gyroscope data
    print("Gyro X:%.2f, Y: %.2f, Z: %.2f degrees/s" % (mpu.gyro))

    # print temperature data
    print("Temperature: %.2f C" % mpu.temperature)

    # print space
    print("")

    # every second
    time.sleep(1)

Listing 7-4Reading Information from the MPU6050

在程序中,我们导入我们的库,以便与电路板和 I2C 模块一起工作。在(1)中,我们导入了用于使用 MPU6050 传感器的库。在(2)中,我们为要使用的微控制器上的管脚设置了 I2C 模块。在(3)中,我们创建了一个我们想要使用的 MPU6050 模块的实例。在我们的超级循环 at (4)中,我们使用我们库中可用的方法,每秒钟将加速度计、陀螺仪和温度数据等信息打印到控制台。

结论

在本章中,我们学习了如何在 CircuitPython 中使用串行通信。我们讨论了 UART、SPI 和 I2C 的使用。我们还学习了如何使用 MPU6050 传感器并从器件中读取陀螺仪、加速度计和温度数据。今天有如此多的传感器使用本章中涵盖的所有协议,你将能够感知和操纵来自我们物理世界的几乎任何数据。

八、显示界面

到目前为止,我们所有的电路都是在没有使用任何显示器的情况下构建的。我们可以使用 LED 或蜂鸣器等设备获得传感器数据的反馈,但要真正可视化传感器数据,我们需要一个显示器。虽然到目前为止我们一直使用的串行控制台是一种可视化信息的好方法,但您需要将您的 MCU 连接到您的计算机才能从中获取任何信息。然而,大多数嵌入式系统(计算器、手表等)都有一个显示器,用来给用户提供信息。在本节中,我们将通过学习显示器为制作这类设备打下基础。

该液晶显示器

我们将了解的第一种显示器是液晶显示器或 LCD。液晶显示器是由一种叫做液晶的特殊晶体制成的,这种晶体对电起反应。LCD 的工作原理是液晶被夹在玻璃层之间形成网格。这些陷印点中的每一个都称为一个像素。当电流通过它们时,它们具有能够阻挡光通过的效果。这种对光的阻挡使显示器变暗,并允许我们制作我们识别为信息的图案。这是单色显示器工作的前提。我们可以在图 8-1 中看到这种单色 LCD 的样子。

img/511128_1_En_8_Fig1_HTML.jpg

图 8-1

单色液晶显示器鸣谢:adafruit.com 阿达弗洛

虽然单色 LCD 在过去很受欢迎,但如今我们的产品中设计全彩色 LCD 更为常见。我们说它们是单色的,因为它们只有一种不同的色调,通常是黑色。也有彩色液晶显示器,其工作原理与单色不同。

在我们讨论彩色液晶显示器之前,让我们花点时间来讨论颜色。在光的领域里,有两种颜色。我们称之为加色和减色。

加色有红、绿、蓝三原色,我们称之为 RGB。如果我们有一个黑色的背景(没有光线),我们在这个黑色的背景上加上加色的组合,我们可以得到各种各样的颜色。所有的加色混合会给你白色。

然而,如果我们有白色背景(白光存在),我们可以通过添加颜色来去除部分白光。某些被称为减色法的颜色可以去除光线中的其他颜色。我们可以添加青色(去除红色)、洋红色(去除绿色)和黄色(去除蓝色)来获得无数种颜色。这些颜色被表示为 CMY 或(-R,-G,-B)。当我们把所有的减色法结合起来,我们就得到黑色。

有没有想过为什么我们在打印机上使用 CMY 的颜色?这是因为如果我们有一张白纸(白色背景),我们从其中去除某些光线成分,我们就可以创建出我们在印刷媒体中使用的光谱。然而,混合所有的 CMY 颜色不会产生完全的黑色。这就是为什么我们的打印机必须使用黑色墨盒的原因。在印刷过程中,当我们使用黑色的 CMY 时,它被称为 CMYK,其中 K 代表黑色成分。

现在我们了解了颜色,我们可以继续讨论显示器了。

彩色显示器的像素由红色、绿色和蓝色(RGB)成分组成。像素可以阻挡它们必须表现的光的某些成分,这些被称为透射型液晶显示器。然而,也有显示器发出这些颜色,我们称之为发射型液晶显示器。

为了控制这些像素,我们使用了一个显示控制器,它可以处理有效操作 LCD 所需的所有细粒度控制。显示控制器本身是一个基于微处理器的设备,可以与我们的 MCU 通信,以控制显示器上的像素。如果我们没有显示控制器,那么我们将不得不编写代码来控制 LCD 上的每个像素,这将增加很多复杂性和软件开销。

使用 GLCD

我们将从如何连接单色 GLCD 开始我们的显示界面。虽然单色显示器通常使用并行接口,但如今,控制这些 LCD 通常使用串行通信接口。我们将使用的第一种显示器基于 PCD8544 显示驱动器,使用 SPI 通信协议。

基于 PCD8544 的液晶显示器很受欢迎,因为它们曾经是诺基亚 5110 手机的一部分,因此,它们有时被称为诺基亚 5110 显示器。这些显示器不仅能显示字母数字字符,还能显示图形和位图图像。当 LCD 可以显示图形和字母数字字符时,这种显示器有时被称为图形液晶显示器(GLCD)。

显示器具有 84x48 单色像素。图 8-2 向我们展示了 PCD8544 显示器的样子。

img/511128_1_En_8_Fig2_HTML.jpg

图 8-2

基于 PCD8544 的 LCD 认证:adafruit.com Adafruit

尽管图 8-2 中的显示器安装在绿色 PCB 上,但有时也经常发现显示器印刷在功能相同的红色 PCB 上。显示器有八个引脚,其功能如下:

  • VCC-连接 VCC。

  • GND-连接到地面。

  • SCE–我们的串行芯片使能引脚,用于在低电平有效时选择显示器。

  • RST–此引脚在拉低时复位 LCD。

  • d/C–这是数据和命令引脚,用于告诉 LCD 我们是否正在向 LCD 发送数据或命令。

  • MOSI–我们的主机输出从机输入引脚用于 SPI 通信。

  • SCLK–这是用于 SPI 通信的串行时钟线。

  • LED–LED 或 LIGHT 引脚为显示器上的背光供电。

单色 GLCD 示意图

我们现在可以将基于 PCD8544 的 LCD 连接到我们的显示器,如图 8-3 所示。

img/511128_1_En_8_Fig3_HTML.jpg

图 8-3

带 MCU 的 PCD8544

请务必检查与 SPI MOSI 和 SPI SCLK 线相关的主板引脚排列。一旦你接通了电路,我们就可以进入下一步了。

  1. 用一根跳线将显示器的 GND 引脚连接到试验板的 GND 轨。

  2. 将 LED 引脚连接到微控制器上的 D10 引脚。

  3. 用一根跳线将显示器上的 VCC 引脚连接到试验板上的 VCC 导轨。

  4. 将 LCD 时钟引脚连接到微控制器的引脚 D13。

  5. 将 DIN 引脚连接到微控制器的 D11 引脚。

  6. 用一根跳线将 LCD 上的 DC 引脚连接到微控制器的 D6 引脚。

  7. 将 LCD 上的 CE 引脚连接到微控制器的 D5 引脚。

  8. 最后,将 LCD RST 引脚连接到微控制器的 D9 引脚。

带 CircuitPython 的 PCD8544

PCD8544 器件有一个为 CircuitPython 编写的库。要在 CircuitPython 中使用 LCD,我们必须将 Adafruit 库包中的以下文件添加到微控制器上的 lib 文件夹中:

  • -=伊甸园美剧 http://sfile . ydy . com =-荣誉出品本字幕仅供学习交流,严禁用于商业途径

  • -= ytet-伊甸园字幕组=-翻译

  • adafruit_bus_device 文件夹

一旦你将它们添加到你的 lib 文件夹中,我们就可以编写清单 8-1 中给出的程序了。

# import the board module
import board

# import time library
import time

# import library for working with SPI
import busio

# import library for digital I/O
import digitalio

# import the LCD library
(1) import adafruit_pcd8544

(2) # Initialize SPI bus
spi = busio.SPI(board.SCK, MOSI=board.MOSI)

       #initialize the control pins
dc = digitalio.DigitalInOut(board.D6)
cs = digitalio.DigitalInOut(board.D5)
reset = digitalio.DigitalInOut(board.D9)

# create instance of display
(3) display = adafruit_pcd8544.PCD8544(spi, dc, cs, reset)

(4) # set bias and contrast
display.bias = 4
display.contrast = 60

(5) # Turn on the Backlight LED
backlight = digitalio.DigitalInOut(board.D10)
backlight.switch_to_output()
backlight.value = True

# we'll draw from corner to corner, lets define all the pair coordinates here
(6) corners = (
    (0, 0),
    (0, display.height - 1),
    (display.width - 1, 0),
    (display.width - 1, display.height - 1),
)

(7) #draw some graphics
       display.fill(0)
for corner_from in corners

:
    for corner_to in corners:
        display.line(corner_from[0], corner_from[1], corner_to[0], corner_to[1], 1)
display.show()
time.sleep(2)

(8) # draw some graphics
display.fill(0)
w_delta = display.width / 10
h_delta = display.height / 10
for i in range(11):
    display.rect(0, 0, int(w_delta * i), int(h_delta * i), 1)
display.show()
time.sleep(2)

(9) # draw text
display.fill(0)
display.text("hello world", 0, 0, 1)
display.show()

(10) #super loop
while True:
    # invert display
    display.invert = True

    time.sleep(0.5)

    # remove invert
    display.invert = False
    time.sleep(0.5)

Listing 8-1PCD8544 with CircuitPython

我们的程序工作如下。首先,我们执行常规导入来设置电路板并使其运行。在(1)中,我们导入了用于处理 LCD 的库。在(2)中,我们初始化 SPI 总线,然后初始化控制引脚。At (3)是我们创建将要使用的实际模块的实例的地方。然后,我们继续设置选项,以控制(4)处显示的偏差和对比度。下一步是打开背光 LED,我们在(5)中这样做。

我们的下一步是展示 LCD 的图形功能。在(6)处,我们在显示器上画角,在(7)和(8)处画一些漂亮的图形效果,在(9)处,我们在 LCD 上写一些文字。在(10)的超级循环中,我们演示了库反转功能。

解决纷争

在撰写本文时,如果您试图按原样运行该程序,可能会出现错误。你会看到一个抱怨字体的输出,如图 8-4 所示。

img/511128_1_En_8_Fig4_HTML.jpg

图 8-4

找不到字体错误

解决方法是将字体文件“font5x8.bin”放在 CIRCUITPY 驱动器的根文件夹中,如图 8-5 所示。

img/511128_1_En_8_Fig5_HTML.jpg

图 8-5

放置字体文件

放置字体文件后,再次运行程序,您将看到文本输出到您的显示器上。程序输出的图形之一将如图 8-6 所示。

img/511128_1_En_8_Fig6_HTML.jpg

图 8-6

试验板上的电路

现在,我们的单色 LCD 可以与 CircuitPython 配合使用。在下一节中,我们将看看如何扩展我们已经知道的内容。

帧缓冲区

为了更有效地更新显示输出,我们可以导入一个名为“adafruit_framebuf.mpy”的库,它提供了 framebuffer 功能。帧缓冲区是用来存放我们将要输出到内存中的数据帧的名称。framebuffer 不仅可以用于 LCD,还可以用于任何输出设备,包括串行终端。查看清单 8-2 ,我们使用帧缓冲区将数据输出到串行终端。

# import the frame buffer library
(1) import adafruit_framebuf

print("framebuf test will draw to the REPL")

(2) WIDTH = 32
HEIGHT = 8

(3) buffer = bytearray(round(WIDTH * HEIGHT / 8))
fb = adafruit_framebuf.FrameBuffer(
    buffer, WIDTH, HEIGHT, buf_format=adafruit_framebuf.MVLSB

)

(4) # Ascii printer for very small framebufs!
def print_buffer(the_fb):
    print("." * (the_fb.width + 2))
    for y in range(the_fb.height):
        print(".", end="")
        for x in range(the_fb.width):
            if fb.pixel(x, y):
                print("*", end="")
            else:
                print(" ", end="")
        print(".")
    print("." * (the_fb.width + 2))

(5) # Small function to clear the buffer
def clear_buffer():
    for i, _ in enumerate(buffer):
        buffer[i] = 0

(6) print("Shapes test: ")
fb.pixel(3, 5, True)
fb.rect(0, 0, fb.width, fb.height, True)
fb.line(1, 1, fb.width - 2, fb.height - 2, True)
fb.fill_rect(25, 2, 2, 2, True)
print_buffer(fb)

(7) print("Text test: ")
# empty
fb.fill_rect(0, 0, WIDTH, HEIGHT, False)

# write some text
fb.text("hello", 0, 0, True)
print_buffer(fb)
clear_buffer()

# write some larger text

fb.text("hello", 8, 0, True, size=2)
print_buffer(fb)

Listing 8-2Framebuffer with CircuitPython

清单 8-2 中的 framebuffer 示例是 CircuitPython 库包中提供的示例,它演示了我们如何使用 CircuitPython framebuffer 输出到显示器。首先,我们在(1)导入帧缓冲库。在(2)中,我们设置缓冲区的尺寸,然后在(3)中创建缓冲区。我们的下一步是创建一个打印缓冲区的函数,我们在(4)中完成。在(5)中,我们有一个清除缓冲区的函数。在(6)中,我们将形状打印到串行终端,在(7)中,我们打印一些文本,然后是一些更大的文本。

当您运行该程序时,您会发现在写入您的串行终端的 ASCII 代码中有文本和形状。

有机发光二极管(Organic Light Emitting Diode 的缩写)

液晶显示器是一项伟大的技术,已经为技术世界服务了很长时间。然而,另一种显示技术已经在技术世界中流行起来。这是基于有机发光二极管(有机发光二极管)的显示器。

有机发光二极管显示器产生丰富清晰的色彩,并且比传统的 LCD 显示器具有更宽的视角。它们还具有更快的响应时间。与液晶显示器相比,有机发光二极管显示器很容易辨别。由于显示器提供了更高的对比度,颜色更加清晰。与 LCD 显示器相比,有机发光二极管显示器在其结构中具有额外的层。这些层由允许它们发光的有机物质构成。将有机发光二极管想象成一个三明治,如图 8-7 所示。

img/511128_1_En_8_Fig7_HTML.jpg

图 8-7

基本有机发光二极管结构

图 8-7 中的简化三明治很好地代表了有机发光二极管的样子。它由位于阳极和阴极材料之间的有机层组成。然后将它们放在由玻璃或塑料制成的底层之间,我们称之为基底。有机层本身由两层组成,即发射层和导电层。有机发光二极管通过使电流从阴极层穿过有机层到达阴极层来发光。这种电流导致了光的发射。由于有机发光二极管能够发出自己的光,所以不需要像大多数 LCD 显示器那样的背光。

与 LCD 一样,有机发光二极管显示器也需要一个驱动程序来轻松控制有机发光二极管,在下一节中,我们将探讨如何将显示器与我们自己的微控制器电路接口。

使用有机发光二极管

目前市场上有许多显示器;然而,有时与驱动程序接口可能是一个问题。出于这个原因,我们将考虑使用一个带有驱动程序的有机发光二极管,该驱动程序有一个我们可以轻松控制的可用库。

我们将使用的有机发光二极管显示器是基于 SSD1306 的有机发光二极管,具有 128 x 64 像素,屏幕尺寸为 1.44 英寸。该显示器的许多版本可以同时使用 SPI 和 I2C,例如图 8-8 中 Adafruit 提供的版本。

img/511128_1_En_8_Fig8_HTML.jpg

图 8-8

基于 SSD1306 的 LCD 认证:adafruit.com Adafruit

如图 8-9 所示,我们将在 I2C 模式下使用显示器,因为有许多低成本的 I2C 专用版本。

img/511128_1_En_8_Fig9_HTML.jpg

图 8-9

仅基于 SSD1306 的液晶 I2C

显示器的 I2C 模式使用四条线,分别是 VCC、GND、SCL 和 SDA。我们的 MCU 只使用 SCL 和 SDA 线,只需要两条 I/O 线来驱动有机发光二极管。

img/511128_1_En_8_Fig10_HTML.jpg

图 8-10

内置 SSD1306 的 MCU 有机发光二极管原理图

带有机发光二极管原理图的 MCU

原理图包括将我们的有机发光二极管连接到 I2C 总线,如图 8-10 所示。一些版本的显示器包括 I2C 总线的上拉电阻;我们仍将包括它们,以防您的显示器版本不包括上拉电阻。

我们将显示器连接到微控制器,如下所示:

  1. 使用跳线将有机发光二极管上的 GND 引脚连接到试验板上的 GND 引脚。

  2. 用一根跳线将有机发光二极管的 VCC 引脚连接到试验板上的 VCC 轨。

  3. 将有机发光二极管上的 SDA 引脚连接到微控制器的 SDA 引脚。

  4. 将有机发光二极管的 SCL 引脚连接到微控制器的 SCL 引脚。

  5. 拿起电阻,按如下方式连接。将每个电阻的一根引线连接到试验板的 VCC 轨。取出自由引脚,将一端分别连接到有机发光二极管的 SDA 和 SCL 引脚。

CircuitPython 与有机发光二极管程序

在我们的程序中,我们使用 Adafruit 提供的示例在显示器上制作弹跳球的动画。清单 8-3 中给出了程序。

# usual imports
import board
import busio

(1) # import library for working with SSD1306
import adafruit_ssd1306

# Create the I2C interface.
i2c = busio.I2C(board.SCL, board.SDA)

(2) # Create the SSD1306 OLED class.
# The first two parameters are the pixel width and pixel height.  Change these
# to the right size for your display!
oled = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c)

(3) # Helper function to draw a circle from a given position with a given radius
# This is an implementation of the midpoint circle algorithm,
# see https://en.wikipedia.org/wiki/Midpoint_circle_algorithm#C_example for details
def draw_circle(xpos0, ypos0, rad, col=1):
    x = rad - 1
    y = 0
    dx = 1
    dy = 1
    err = dx - (rad << 1)
    while x >= y

:
        oled.pixel(xpos0 + x, ypos0 + y, col)
        oled.pixel(xpos0 + y, ypos0 + x, col)
        oled.pixel(xpos0 - y, ypos0 + x, col)
        oled.pixel(xpos0 - x, ypos0 + y, col)
        oled.pixel(xpos0 - x, ypos0 - y, col)
        oled.pixel(xpos0 - y, ypos0 - x, col)
        oled.pixel(xpos0 + y, ypos0 - x, col)
        oled.pixel(xpos0 + x, ypos0 - y, col)
        if err <= 0:
            y += 1
            err += dy
            dy += 2
        if err > 0:
            x -= 1
            dx += 2
            err += dx - (rad << 1)

(4) # initial center of the circle
center_x = 63
center_y = 15
# how fast does it move in each direction
x_inc = 1
y_inc = 1
# what is the starting radius of the circle
radius = 8

# start with a blank screen
oled.fill(0)
# we just blanked the framebuffer. to push the framebuffer onto the display, we call show()
oled.show()
(5) while True

:
    # undraw the previous circle
    draw_circle(center_x, center_y, radius, col=0)

    # if bouncing off right
    if center_x + radius >= oled.width:
        # start moving to the left
        x_inc = -1
    # if bouncing off left
    elif center_x - radius < 0:
        # start moving to the right
        x_inc = 1

    # if bouncing off top
    if center_y + radius >= oled.height:
        # start moving down
        y_inc = -1
    # if bouncing off bottom
    elif center_y - radius < 0:
        # start moving up

        y_inc = 1

    # go more in the current direction
    center_x += x_inc
    center_y += y_inc

    # draw the new circle
    draw_circle(center_x, center_y, radius)
    # show all the changes we just made
    oled.show()

Listing 8-3Bouncing Ball Program

在我们的程序中,我们创建了一个弹跳球。在(1)中执行我们通常的导入后,我们导入库以使用 SSD1306 显示器。在(2)中,我们创建了一个可以操作的 SSD1306 类的实例。由于球本质上是一个实心圆,在(3)中,我们有一个画圆的函数。在(4)中,我们设置球的参数,包括它的尺寸和速度。在(5)处,无限循环将弹跳球吸引到显示器。如果您查看 Adafruit 库包,您会看到其他可以用来处理显示的示例程序。

结论

在这一章中,我们看了界面显示。我们研究了如何同时使用液晶显示器(LCD)和有机发光二极管(有机发光二极管)。在这个过程中,我们了解了 LCD 和 GLCDs 的工作原理,并了解了如何使用 Adafruit 缓冲库向串行控制台输出信息。有了使用显示器的能力,您现在可以显示与计算机无关的信息。使用传感器的能力与显示信息的能力相结合,涵盖了许多嵌入式系统的功能。

九、控制 DC 执行器

微控制器可以用来控制致动器设备。致动器是一种负责运动的装置。致动器分为两组,即机械致动器和机电致动器。在这一章中,我们将研究控制一些重要的机电 DC 执行器。学会控制执行器后,整个世界向你敞开。自动锁,机器人,数控机床更容易理解,以后你就有能力造这种设备了。

DC 汽车公司

您将使用的第一种类型的致动器是 DC 马达。DC 电机被用于很多设备,从玩具,如遥控汽车,到电器和工具,如无绳电钻。

当电势差(电压)施加在它们的端子上时,这些电机通过旋转动作运行。

有两种类型的 DC 电机,有刷 DC 电机和无刷 DC 电机。无刷 DC 电机比有刷 DC 电机具有更好的热特性和更高的效率。然而,有刷 DC 电机可以以更简单的方式驱动,从而降低系统成本。

有刷 DC 电机是你可能会在自己的项目中使用的类型,尽管在本章的后面我们还会看到一种称为步进电机的无刷 DC 电机。

图 9-1 显示了玩具和其他简单设备中常见的典型有刷 DC 电机。

img/511128_1_En_9_Fig1_HTML.jpg

图 9-1

有刷 DC 电机

由于其机械结构,有刷 DC 电机的寿命比无刷电机短。这是因为有刷 DC 电机有一个被称为电刷的部件,它会随着时间的推移而磨损。

驾驶 DC 汽车

有刷 DC 电机应该很好开;我的意思是,如果你把一个 DC 发动机连接到电池上,如图 9-2 所示,它应该会运行。

img/511128_1_En_9_Fig2_HTML.jpg

图 9-2

带电池的 DC 发动机

然而,这种简单的方法缺乏智能控制,并且电机将仅在一个方向上旋转。如果我们想让马达在智能控制下反方向旋转,那就不仅仅需要把 DC 马达和电池连接起来。使用 MCU 驱动电机的方式如图 9-3 所示。

img/511128_1_En_9_Fig3_HTML.jpg

图 9-3

用单片机驱动 DC 电机

电路运行的前提很简单。来自微控制器的信号将使晶体管打开或关闭。根据晶体管的状态,电流将流过电机,使其旋转。我们必须记住,电机是一个感性负载。因此,D1 是一个缓冲二极管,保护其他电路元件免受电机产生的感应尖峰的影响。如果您确实需要对振动电机等小型电机进行简单的开/关控制,那么您可以使用图 9-4 中的电路。

img/511128_1_En_9_Fig4_HTML.jpg

图 9-4

一种实用的开关控制电路

这里,在图 9-4 中,我们有一个如图 9-3 所示版本的实际电路。为了驱动电机,我们使用 2N2222A 晶体管,它可以处理高达 800 mA 的电流,足以驱动电机。虽然 2N3904 经常被宣传为 2N2222A 的替代品,但在本电路中并不合适,因为 2N3904 的电流处理能力仅为 200 mA。如果你想驱动需要简单开/关控制的小电机,这个电路是很好的。如图 9-5 所示,用于触觉反馈的振动电机或用于玩具等的标准 130 DC 电机是理想的选择。

img/511128_1_En_9_Fig5_HTML.jpg

图 9-5

标准 130 adafruit.com DC 汽车信贷公司

虽然简单的开/关控制确实有其应用,但如果我们真的想要一个智能电机控制,我们需要像方向和速度控制这样的东西。这些属于脉宽调制的范畴,我们将在下一节讨论。

脉宽灯

如果你真的想用微控制器来控制电机的速度,那么你必须考虑一种叫做脉宽调制(PWM)的东西。在讲 PWM 之前,我们先来看一下图 9-6 中的方波。

img/511128_1_En_9_Fig6_HTML.jpg

图 9-6

方波

在你的方波上,你有一个我们称之为高电平时间的时间和一个我们称之为低电平时间的时间。如果这个方波是由微控制器产生的,我们可以将高电平时间设为 3.3 伏,低电平时间设为 0 伏。这种脉冲重复的频率称为波的频率,我们用赫兹(Hz)来测量。波的周期是频率的倒数,指的是周期重复一次所需的时间。

由于波的周期是频率的倒数,随着波形频率的增加,波的周期将同时减小。

PWM 的一个重要方面是占空比。众所周知,数字信号可以是高电平或低电平。波形的高电平时间称为占空比,通常用百分比表示。例如,如果一个波一半时间为高电平,一半时间为低电平,则可以说它的占空比为 50%。

图 9-7 显示了我们可以在波形上识别占空比的图表。

img/511128_1_En_9_Fig7_HTML.jpg

图 9-7

占空比

通过调整波形的占空比,我们可以有效地调整输出的电压水平。这是一种强大的技术。使用 PWM,我们可以控制发光二极管的亮度和控制电机的速度。

电路中的 PWM python

如今,几乎每个微控制器都提供了支持 PWM 的模块,CircuitPython 提供了用于控制这些 PWM 模块的库。大多数支持 CircuitPython 的 MCU 都有 PWM 引脚,在输出引脚旁边印有一个小小的波浪号“~”。为了在 CircuitPython 中使用 PWM,我们使用以下库:

  • 电路板–我们需要电路板库来指示微控制器的特定引脚。

  • 时间–时间库提供了处理基于时间的活动的功能。

  • pulse io–pulse io 库是 CircuitPython 中 PWM 用法的核心。该库为支持 PWM 的引脚提供了函数。

使用 CircuitPython 程序进行 PWM

我们可以使用 PWM 模块来淡化 LED。我们可以使用 PWM 来淡化电路板上的 LED。清单 9-1 中有一个程序,我们可以用它来淡化连接到 PWM 引脚的 LED,在本例中是引脚 D13。大多数支持 CircuitPython 的电路板都有一个 LED 连接到此引脚;如果此引脚上没有 LED,或者您使用的是定制板,则可以用一个 1k 电阻将 LED 连接到引脚 D13。

# import time functions
import time

# import our board specific pins
import board

# library for using PWM
(1) import pulseio

# setup the pwm using Pin13, with a frequency of 5000 Hz
(2) pwmLed = pulseio.PWMOut(board.D13, frequency=5000)

(3) while True:
    for i in range(100):
        # PWM LED up and down
        if i < 50:
            # below 50 pwm up
            pwmLed.duty_cycle = int(i * 2 * 65535 / 100)
        else

:
            # more than 50 pwm down
            pwmLed.duty_cycle = 65535 - int((i - 50) * 2 * 65535 / 100)
        # slow it down so we can see
        time.sleep(0.05)

Listing 9-1The PWM Program

当你运行程序时,你会看到 LED 变得非常亮,然后逐渐消失,变得暗淡。该程序的工作原理如下。在(1)中,我们导入了用于使用 PWM 的“pulseio”库。在(2)中,我们在引脚上创建一个 PWM 模块实例,并将频率设置为 5000 赫兹。在(3)中,我们有一个主程序循环,在该循环中,我们递增然后递减 PWM 占空比一个指定的周期。这导致 LED 具有褪色效果。

控制电机速度

如果你想控制 DC 电机,标准的方法是用 PWM 控制。如果我们提供电机运行所需的最大电压,那么电机将全速运行。然而,如果我们通过使用 PWM 调整电机的占空比来快速打开和关闭电机,我们将能够控制电机的有效或平均速度。

我们可以创建如图 9-8 所示的硬件连接来控制电机速度。

img/511128_1_En_9_Fig8_HTML.jpg

图 9-8

电机控制的连接

这与我们在本章前面讨论的电路相同。我们只需将“MCU”替换为您选择的引脚和一个晶体管,该晶体管可以处理您想要控制的电机的电流。为了控制电机速度,我们可以使用 MCU 的 PWM 来改变电机的速度,使用晶体管来做重物提升。然而,有一种更好的方法,我们将在下一节中探讨。

H 桥

如果你需要简单的开/关控制或者只是调整电机的转速,用晶体管驱动电机是很好的选择。然而,在有些情况下,你不仅希望调整电机的速度,还需要控制旋转的方向。移动机器人就是一个很好的例子。在移动机器人中,机器人不仅需要向前行驶,还需要反向行驶。要做到这一点,您可以使用一种称为 H 桥的电路配置,如图 9-9 所示。

img/511128_1_En_9_Fig9_HTML.jpg

图 9-9

H 桥

该电路将如下工作。当 S3 和 S2 关闭时,电机将正向旋转,当 S1 和 S4 关闭时,电机将反向旋转。使用 H 桥时,避免在驱动电机时产生短路非常重要。如果左边的两个开关(S1 和 S2)或右边的两个开关(S3 和 S4)都闭合,那么您将创建从源到地的短路。如果使用 H 桥 IC,通常会包括热关断功能,以防止这些器件被损坏。但是,您不应该依赖这种保护机制来保护您的 IC。

通常,当您需要高电流处理能力时(在这种情况下大于 5A),通常用 MOSFETs 构建 H 桥。然而,对于大多数低于 5A 的应用,您可以使用 H 桥 IC。有可能获得处理 5A 以上电流的 IC 驱动器。然而,由于功耗水平以及电机可能需要的峰值电流,构建分立 H 桥通常更经济。

可能有些人不同意我的观点,但随着半导体技术的进步,为大多数应用构建自己的分立驱动器将不再必要。

对于低电流应用(< 5A),通常使用 H 桥驱动器 IC。两种常见的 H 桥 IC 是 L293D 和 SN754410NE 四路半 H 驱动器。这些驱动程序非常相似,在大多数应用中可以互换使用。

与 L293D 相比,SN754410NE 具有更高的连续电流输出和更高的峰值电流。图 9-10 突出了这两种驱动器的特征差异。

img/511128_1_En_9_Fig10_HTML.jpg

图 9-10

L293D 与 SN754410NE 特性的关系

在图 9-11 中,我们看到了两个设备的物理布局。L293D 在左边,SN754410NE 在右边。

img/511128_1_En_9_Fig11_HTML.jpg

图 9-11

L293D 与 SN754410NE 物理封装

图 9-11 中的封装是 PDIP-16 封装,它们的引脚相互兼容。芯片的内部逻辑和电机电源都有 VCC 电源。VCC1 为芯片的内部逻辑供电,连接到 5v,VCC2 连接到电机电源,通常为 9–12v。

左马达连接到针脚 1Y 和 2Y,右马达连接到针脚 3Y 和 4Y。引脚 1A 和 2A 是左逻辑引脚,3A 和 4A 引脚是右逻辑引脚。1,2 EN 使能左驱动器,3,4 EN 使能右驱动器。

驾驶员基本上有三种被驱动状态,即前进、后退和制动。在正向状态下,我们将一个开关设为高电平,另一个设为低电平。对于反向旋转,我们反转引脚上的逻辑电平。为了创建制动场景,我们将两个逻辑电平引脚都设为低电平状态。

带 MCU 原理图的 h 桥

我们可以使用逻辑电平转换器将微控制器连接到 L293D,因为 L293D 需要 5v 逻辑,我们有运行 CircuitPython 的 3.3v 器件。如图 9-12 所示连接原理图。

img/511128_1_En_9_Fig12_HTML.jpg

图 9-12

带 H 桥的 CircuitPython MCU

  1. 将微控制器的引脚 D12 和 D13 连接到逻辑电平转换器的 LV1 和 LV2。

  2. 将逻辑电平转换器上的 GND 引脚连接到试验板上的接地轨。

  3. 将逻辑电平转换器的 HV1 引脚连接到 H 桥 ic 的 INPUT1 引脚,将 HV2 引脚连接到 INPUT2 引脚。

  4. 将电机的一个引脚连接到输出 1,另一个引脚连接到输出 2。

  5. H 桥的使能引脚连接到 MCU 的 A1。

  6. 将 H 桥 IC 的正极引脚连接到电源轨,将 GND 引脚连接到接地轨。

连接的电路应如图 9-13 所示。注意你使用的电机的大小很重要,因为越大的电机使用的电流越大;因此,你必须确保额外的电容器放置在电源轨上,如图 9-13 所示。如果你不放置这些额外的电容,那么微控制器将复位,你将有一个意外的操作。H 桥运行时,无需连接驱动器右侧的接地连接,但如果您愿意,也可以将其连接起来作为预防措施。

img/511128_1_En_9_Fig13_HTML.jpg

图 9-13

试验板上集成 MCU 的 h 桥

h 桥与 CircuitPython 程序

我们将编写一个程序,使用 PWM 来限制由 L293D 电机驱动器驱动的电机的速度,这将允许我们控制电机的方向。我们将打开 Mu 编辑器并创建清单 9-2 中的程序。

# import time functions
import time

# import our board specific pins
import board

# library for using PWM
(1) import pulseio

# library for working with digital output
import digitalio

# create instance of enable pin
(2) en = digitalio.DigitalInOut(board.A1)

# set the enable pin to output
en.direction = digitalio.Direction.OUTPUT

# start with enable false
en.value = False

# setup the pwm using Pin13, with a frequency of 5000 Hz
# steup the pwm using Pin12, with a frequecny of 5000 Hz
(3) in1 = pulseio.PWMOut(board.D13, frequency=5000, duty_cycle=0)
in2 = pulseio.PWMOut(board.D12, frequency=5000, duty_cycle=0)

# turn in forward direction
(4) def forward():
    print("forward")
    en.value = True
    in1.duty_cycle = 20000
    in2.duty_cycle = 0
    time.sleep(3)

# reverse direction
(5) def reverse():
    print("reverse")
    en.value = True
    in1.duty_cycle = 0
    in2.duty_cycle = 20000
    time.sleep(3)

# stop motors
(6) def stop():
    print("stop")
    en.value = False
    in1.duty_cycle = 0
    in2.duty_cycle = 0
    time.sleep(2)

# super loop

(7) while True:
    # forward
    forward()

    # stop before transition
    stop()

    # reverse
    reverse()

    # stop before transition
    stop()

Listing 9-2Using the H-Bridge

在程序中,我们做我们通常的导入,在(1)中,pulseio 库被导入用于处理 PWM 引脚。在(2)处,设置使能引脚,并设置其方向和状态。在(3)处,设置 PWM 实例,用于连接 H 桥。在(4)、(5)和(6)中,创建了允许电机正转、反转和停止的功能。正向功能允许电机正向旋转,反向功能允许电机反向旋转,停止功能阻止电机移动。

在(7)的超级循环中,我们正向旋转电机 3 秒钟;请注意,在我们转换到另一个方向之前,我们停止电机 2 秒钟。在那之后,我们反转马达的方向,这样无限地继续下去。

伺服电机

在上一节中,我们介绍了有刷 DC 电机以及如何驱动它们。另一个常见的 DC 致动器你可能会遇到的是 DC 伺服电机。这些伺服电机是 R/C(无线电控制)伺服电机,因为它们旨在用于与遥控器(通常是模型飞机)相关的爱好应用。

这些电机不需要外部电机驱动器。这是因为它们是独立的,具有带控制电路的 DC 电机和集成到设备中的齿轮系。

这些电机有三根电线,如图 9-14 所示。一根导线向伺服系统供电,另一根导线接地,最后一根导线用于向电机发送控制信号。电源线通常为红色,接地线为棕色或黑色,控制信号为白色或橙色。

图 9-14 中的伺服系统是 MG90D,这是一个很好的标准微伺服系统,可以在您自己的项目中使用。

有两种类型的伺服,这是连续旋转和标准伺服电机。连续旋转伺服电机可以旋转完整的 360 度,而标准伺服范围从 0 到 180 度。

为了控制伺服电机,我们需要在信号线上发送一个脉冲。对于连续旋转的伺服系统,脉冲的长度将决定伺服系统旋转的速度。在标准伺服电机中,脉冲长度将决定伺服旋转的位置。

脉冲长度将由制造商指定。然而,对于典型的伺服系统,1 ms 的脉冲宽度将使电机转到 0 度位置。当提供 1.5 毫秒的脉冲宽度时,它将使电机转向 90 度位置。最后,当我们提供一个 2 毫秒的脉冲,电机将转向 180 度的位置。

img/511128_1_En_9_Fig14_HTML.jpg

图 9-14

遥控伺服电机

伺服电机也需要发送一个信号来保持其位置一段时间。如果不发送该信号,电机的运行可能会变得非常不规则,出现跳动。

CircuitPython 中的伺服电机

控制一个伺服系统很简单,但对初学者来说可能会令人望而生畏。为了使用伺服功能,我们必须使用 PWM 来控制脉冲。幸运的是,CircuitPython 提供了我们可以用来控制伺服系统的库。要使用伺服电机,我们需要

  • pulseio 库——这将允许我们使用 PWM 来控制伺服电机。

  • 这个库包含了我们控制伺服电机所需要的函数。该库还提供了一些与刷 DC 和步进电机工作的函数。要使用这个库,我们需要将它复制到微控制器上的 lib 文件夹中。

带 MCU 原理图的伺服电机

我们需要使用逻辑电平转换器将微控制器连接到伺服系统,因为伺服系统运行在 5 伏电压下,但我们的微控制器是 3.3 伏器件。该示意图如图 9-15 所示。

img/511128_1_En_9_Fig15_HTML.jpg

图 9-15

带伺服原理图的 MCU

我们按如下方式连接电路:

  1. 将伺服电机的 V+引脚连接到电源轨。

  2. 将伺服电机的 GND 引脚连接到试验板的 GND 轨道。

  3. 信号引脚连接到逻辑电平转换器的 HV3 引脚。

  4. 将逻辑电平转换器的 LV3 连接到微控制器的 A2 引脚。

  5. 将逻辑电平转换器的 GND 引脚连接到试验板上的接地轨。

在图 9-16 中,我们看到了电路的试验版。

img/511128_1_En_9_Fig16_HTML.jpg

图 9-16

带伺服试验板的 MCU

带 CircuitPython 程序的伺服电机

为了控制伺服电机,我们将使用 adafruit_motor 库函数,该示例将电机扫过 180 度圆弧。清单 9-3 中给出了程序。

# import the time library
import time

# import the board pins
import board

# import library for working with PWM
import pulseio

# import library for working with servo
(1) from adafruit_motor import servo

# create a PWMOut object on Pin A2.
(2) pwm = pulseio.PWMOut(board.A2, duty_cycle=2 ** 15, frequency=50)

# Create a servo object, my_servo and set the min and max pulse
(3) my_servo = servo.Servo(pwm, min_pulse = 500, max_pulse = 2800)

(4) while True:
    for angle in range(0, 180, 5):  # 0 - 180 degrees, 5 degrees at a time.
        my_servo.angle = angle
        time.sleep(0.05)
    for angle in range(180, 0, -5): # 180 - 0 degrees, 5 degrees at a time.
        my_servo.angle = angle
        time.sleep(0.05)

Listing 9-3Using the Servo Motor Program

在我们的程序中,我们执行通常的导入以及 pulseio 库来处理 PWM。在(1)中,我们导入了 adafruit_motor 库,以允许我们控制伺服电机。在(2)中,我们在引脚 A2 上创建了一个 PWM 实例。在(3)中,我们可以操作的伺服对象被创建,并且我们设置使伺服操作所需的脉冲宽度。在(4)的主程序循环中,我们首先从 0 度到 180 度扫描伺服范围,然后从 180 度到 0 度。

在 my_servo 对象中随意调整最小和最大脉冲;该程序已配置为与 MG90S 电机或同等产品一起工作。

步进电机

我们要看的最后一种电机是步进电机。步进电机是一种无刷 DC 电机,这当然使它在某些应用中优于有刷 DC 电机。步进电机非常适合位置控制,这使得它们在 CNC 机器、3D 打印机和绘图机中无处不在。步进电机还具有很高的保持转矩,这使得它们特别适合这种应用。然而,步进电机的缺点是,由于它们的结构,它们通常不如有刷 DC 电机运行得快。

步进电机有两种,双极步进电机和单极步进电机。不管是哪一种,步进电机都是通过给围绕中心转子的线圈通电来工作的,中心转子上有我们称之为磁极的永磁齿。线圈被称为定子。这些定子是电磁体,按顺序极化和去极化,使电机能够步进旋转。这些阶梯旋转一定的角度,我们称之为阶梯角。

步距角取决于步进电机内定子极和转子齿的数量。定子分阶段通电。定子的这些相是连续接通和断开的。这样做会产生一个磁场,使转子转动,从而产生步进动作。

双极步进电机由两个绕组和一个电机电枢组成,如图 9-17 所示。

img/511128_1_En_9_Fig17_HTML.jpg

图 9-17

双极步进电机

双极步进电机有四根电线。您通常会参考数据手册来确定哪两根电线属于哪个绕组对。电线通常用颜色标记,以便于识别。如果没有数据表,可以将万用表设在导通档。使用此功能,您可以检查连续性,以确定哪对电线属于同一对。

图 9-18 中所示的 42BYGHM809 是一种常见的双极步进电机。

img/511128_1_En_9_Fig18_HTML.jpg

图 9-18

42building m809 步进电机

还有单极步进电机。这些电机可采用五引脚、六引脚或八引脚配置。由于六引脚配置最为常见,这就是我们要研究的品种。六针单极步进电机的原理图如图 9-19 所示。

img/511128_1_En_9_Fig19_HTML.jpg

图 9-19

六引线单极步进电机

单极步进电机每相有一个带中心抽头的绕组。这意味着电流只沿一个方向流过线圈。这与电流双向流过线圈的双极步进电机形成对比。

我们将使用的六线步进电机是 Sinotech 25BY4801,它代表一种普通的小型步进电机,如图 9-20 所示。

img/511128_1_En_9_Fig20_HTML.jpg

图 9-20

Sinotech 25BY4801 六线步进电机

如果需要,您可以通过简单地忽略中心抽头,将六引线单极步进电机作为双极电机来驱动。

有三种方式可以用来驱动步进电机,它们是波形驱动模式、全驱动模式和半驱动模式。

在波形驱动模式下,我们一次给电机上的每个定子线圈通电一次。这样做的结果是,它给了我们更少的输出扭矩,但也减少了电机消耗的功率。

在全驱动模式下,我们一次激励两个定子,为我们提供更大的输出扭矩,这也伴随着更大的电流消耗。

还有一种半驱动步进模式,交替激励一个相位,然后两个相位。它用于使电机的角度分辨率加倍(增加步数)。不过不要担心;我们将只关注波浪驱动和全驱动模式,这两种模式对于初学者来说更容易理解。

CircuitPython 中的步进电机

adafruit_motor 库包含用于单极和双极步进电机的函数。然而,如果我们手动驱动电机,那么理解发生了什么会更有用。因此,我们将不使用 adafruit_motor 库函数。

带 MCU 原理图的步进电机

为了控制电机,我们将使用 ULN2003 IC,它可以处理步进电机的高电流要求。ULN2003 在一个封装中包含七个达林顿晶体管,在最高 40v 的电压下,每个驱动器可以处理高达 500 mA 的电流。这对于驱动我们的马达来说绰绰有余。该驱动器还包括抑制二极管,这使得它很适合驱动电感负载,如我们的步进电机。这些抑制二极管提供额外的电路保护。示意图如图 9-21 所示。

img/511128_1_En_9_Fig21_HTML.jpg

图 9-21

步进电机控制

我们可以按如下方式连接电路:

  1. 将逻辑电平转换器的 GND 引脚接地。

  2. 将逻辑电平转换器的 LV4 引脚连接到 D13 引脚。

  3. 将逻辑电平转换器的 LV3 引脚连接到 D12 引脚。

  4. 将逻辑电平转换器的 LV2 引脚连接到引脚 D11,将 LV1 引脚连接到引脚 D10。

  5. 在逻辑电平转换器上,将引脚 HV4、HV3、HV2 和 HV1 分别连接到 ULN2003 的引脚 1B、2B、3B 和 4B。

  6. 将 ULN2003 的 COM 引脚连接到 VCC 引脚。

  7. 将步进电机的中心抽头 2 和 5 连接到 VCC。

  8. 将步进电机的电线 1 和 3 分别连接到 ULN2003 的引脚 4C 和 3C。

  9. 将步进电机的导线 4 和 6 分别连接到 ULN2003 的引脚 1C 和 2C。

由于 ULN2003 在 5v 逻辑下比在 3.3v 逻辑下输出更多的电流,我们使用逻辑电平转换器将来自 MCU 的 3.3v 信号转换为供 ULN2003 使用的 5v 信号。

步进电机与 CircuitPython 程序

我们现在可以编写一个程序,使用波形驱动和全驱动模式来控制步进电机。清单 9-4 中给出了程序。该程序在紧凑的 Python 代码方面效率不高,但我觉得它对初学者来说更容易理解。

# import pin constants for board we are using
import board

# import pin control
import digitalio

# import time
import time

(1) # create objects for pins we are using
WHT = digitalio.DigitalInOut(board.D10)
BLK = digitalio.DigitalInOut(board.D11)
YEL = digitalio.DigitalInOut(board.D12)
RED = digitalio.DigitalInOut(board.D13)

(2) # set the pins to output
WHT.direction = digitalio.Direction.OUTPUT
BLK.direction = digitalio.Direction.OUTPUT
YEL.direction = digitalio.Direction.OUTPUT
RED.direction = digitalio.Direction.OUTPUT

(3) # super loop
while True:
    for i in range(24):
        if i < 12:

            # phase 1
            # 1000
            RED.value = True    # A
            BLK.value = False   # B
            YEL.value = False   # C
            WHT.value = False   # D
            time.sleep(0.1)

            # phase 2
            # 0100

            RED.value = False    # A
            BLK.value = True     # B
            YEL.value = False    # C
            WHT.value = False    # D
            time.sleep(0.1)

            # phase 3
            # 0010
            RED.value = False    # A
            BLK.value = False    # B
            YEL.value = True     # C
            WHT.value = False    # D
            time.sleep(0.1)

            # phase 4
            0001
            RED.value = False   # A
            BLK.value = False   # B
            YEL.value = False   # C
            WHT.value = True    # D
            time.sleep(0.1)

            time.sleep(0.5)

        else:
            # phase 4
            # 1001
            RED.value = True    # A
            BLK.value = False   # B
            YEL.value = False   # C
            WHT.value = True    # D
            time.sleep(0.1)

            # phase 3
            # 0011

            RED.value = False   # A
            BLK.value = False   # B
            YEL.value = True    # C
            WHT.value = True    # D
            time.sleep(0.1)

            # phase 2
            # 0110
            RED.value = False   # A
            BLK.value = True    # B
            YEL.value = True    # C
            WHT.value = False   # D
            time.sleep(0.1)

            # phase 1
            # 1100
            RED.value = True    # A
            BLK.value = True    # B
            YEL.value = False   # C
            WHT.value = False   # D
            time.sleep(0.1)

            time.sleep(0.5)

Listing 9-4Using the Stepper Motor Program

在我们的程序(1)中,我们为正在使用的管脚创建对象,然后在(2)中,我们将管脚设置为输出管脚。这些接点是根据它们的导线颜色命名的,以使连接更容易理解。在我们的主循环中的(3)处,我们使用波形驱动模式向前旋转电机。然后,当我们达到 360 度旋转时,我们使用全驱动模式使电机回到其起始位置。

在我们的超级循环中,当“I”变量达到 12 时,步进电机将完成一次完整的旋转。由于电机的步进角度为 7.5 度,电机中有四个相位,每次变量增加,我们就步进四次,直到 7.5 度,也就是 30 度。12 次计数后,我们将旋转 360 度。

如果您愿意,可以测量电流消耗,您会发现全驱动模式比波形驱动模式消耗的电流更少。在我的测试中,全驱动模式的电流消耗约为 520 毫安,而波形驱动模式的电流消耗约为 325 毫安。

结论

在这一章中,我们讨论了各种 DC 致动器。我们研究了有刷和无刷 DC 电机,包括有刷 DC 电机、步进电机和伺服电机。我们讨论了这些电机的特性和用途,以及如何将它们与基于 Python 的 MCU 进行接口。通过学习这些主题,我们了解了 H 桥以及驱动 DC 电机的各种方法,并学习了控制步进电机的程序。如果您需要更多信息,我建议您查看特定电机驱动器的制造商数据表,包括 L293D 和 SN754410NE。机械臂、移动机器人,甚至无人机的设计和控制现在都可以用你在本章学到的知识来尝试。

十、Python MCU 接口

恭喜你!如果到目前为止,您已经掌握了使用微控制器和 Python 所需的基础知识。我们已经走了很长的路,但我们还没有完成。在本章中,我们将了解如何使用运行 CircuitPython 的微控制器与一些常见传感器接口。传感器接口的主题可以涵盖整个体积。然而,当您继续构建自己的项目时,可能会用到一些传感器;在这一章中,我们将介绍您可能希望在项目中使用的传感器。

RGB LED 指示灯

如果你认为发光二极管很棒,那么我有一个传感器会让你大吃一惊。有时,你不能决定在你的项目中使用的 LED 灯的颜色。在这种情况下,我们需要使用一个封装中包含三个 LED 的 LED。这是红色、绿色和蓝色或 RGB LED。RGB LED 如图 10-1 所示。

img/511128_1_En_10_Fig1_HTML.jpg

图 10-1

RGB LED 认证:Adafruit.com ada fruit

正如我们在显示器一章中所学的,使用红色、绿色和蓝色,我们将能够产生任何颜色的光。RGB LEDs 在同一个封装中有一个红色、一个绿色和一个蓝色 LED。使用 RGB LEDs,我们将能够生产出我们能想到的几乎任何颜色的 LED。这使得它们非常适用于指示器之类的东西。我们可以用一个 LED 来改变它的颜色,而不是用多个 LED 来传递信息。

RGB LED 具有四个引脚,并且 LED 可以是公共阳极或公共阴极。如果我们观察 RGB LED,我们会发现一个引脚比其他引脚长。这个长针可以连接到我们电源的阳极或阴极。

RGB LED 和 MCU 原理图

我们如图 10-2 所示连接电路。我们的 RGB LED 连接到引脚 D10、D11 和 D12。该原理图假设使用公共阴极 RGB LED。如果你使用一个普通的阳极 LED,最长的管脚将连接到 VCC 而不是地。

img/511128_1_En_10_Fig2_HTML.jpg

图 10-2

RGB LED 和 MCU 原理图

请注意我们是如何使用三个电阻的,因为封装中的每个 LED 仍必须被视为一个单独的器件。

RGB LED 电路连接提示

以下是连接电路的推荐步骤:

  1. 根据您的版本,将 RGB LED 的长公共引脚连接到 VCC 或地。

  2. 使用 1k 电阻将其余三个短引脚分别连接到引脚 D10、D11 和 D12。

当你完成连接你的电路时,它看起来应该如图 10-3 所示。

img/511128_1_En_10_Fig3_HTML.jpg

图 10-3

试验板上带 MCU 的 RGB LED

我们需要的图书馆

这些是我们需要添加到 lib 文件夹中的库:

  • adafruit_rgbled 函数

  • 简单的

RGB LED 与 CircuitPython 程序

然后我们可以在 Mu 编辑器中打开我们的 code.py 文件,这样它就类似于清单 10-1 。

# import board library
import board

# import time library
import time

# import library for RGB led
(1) import adafruit_rgbled

# setup pin constants
(2) RED_LED = board.D10
GREEN_LED = board.D11
BLUE_LED = board.D12

# create a RGB LED object

# invert pwm = false if common cathode
#              true common anode
(3) rgbLed = adafruit_rgbled.RGBLED(RED_LED, GREEN_LED, BLUE_LED, invert_pwm=False)

(4) while True:
    # turn on red
    rgbLed.color = (128, 0, 0)
    time.sleep(1)

    # turn on green
    rgbLed.color = (0, 128, 0)
    time.sleep(1)

    # turn on blue
    rgbLed.color = (0, 0, 128)
    time.sleep(1)

    # mix 1
    rgbLed.color = (100, 0, 204)
    time.sleep(1)

    # mix 2
    rgbLed.color = (90, 20, 0)
    time.sleep(1)

Listing 10-1MCU with RGB LED Program

我们导入模块来设置我们的开发板以供使用。在(1)中,我们导入了 adafruit_rgbled 库,这将允许我们控制 led。完成这些后,在(2)中,我们为代表每个 LED 的引脚设置常数。完成后,我们在(3)处创建一个 RGB LED 对象。在我们的主循环(4)中,这个对象用于我们一次打开一个单独的 LED,红色,然后绿色,然后蓝色各一秒钟。然后我们练习混合颜色值。

HC-SR04 战斗机

有时候,无论什么原因,你都需要测量距离。例如,如果你正在建造一个基于微控制器的移动机器人,你将需要能够控制机器人行驶的方向。使用一个传感器,测量机器人与我们使用的物体之间的距离,我们可以创造一个半智能机器人。我们有两种方法可以测量距离,那就是利用光和声音。使用光是困难的,因为它会受到环境中环境光的干扰。有弹性光传感器,如基于激光雷达的传感器;然而,将它们集成到项目中是很昂贵的。一个不错的低成本解决方案是使用声音。最常用的声音传感器是 HC-SR04 传感器,它使用超声波来测量距离。该传感器如图 10-4 所示。

img/511128_1_En_10_Fig4_HTML.jpg

图 10-4

HC-SR04 超声波传感器:adafruit.com ada fruit

该传感器有四个引脚,分别是 VCC、GND、触发引脚和回波引脚。该装置有两个超声波传感器。其中一个传感器发射超声波脉冲,另一个设备监听发射的脉冲。该传感器可以测量从 2 厘米到 400 厘米的距离。

传感器的工作原理是,触发针(Trig)用于从其中一个传感器传输脉冲。当接收到反射信号时,echo 引脚变为高电平。根据设备在接收(回波)引脚上检测到信号所需的时间长度,我们可以确定被测物体的距离。

HC-SR04,带 MCU 原理图

电路连接如图 10-5 所示。由于 HC-SR04 是一个 5 伏器件,我们需要使用一个逻辑电平转换器将其与我们的 CircuitPython MCU 接口。trigger 引脚通过逻辑电平转换器连接到 MCU 的 D5 引脚,echo 引脚通过逻辑电平转换器连接到 MCU 的 D6 引脚。

img/511128_1_En_10_Fig5_HTML.jpg

图 10-5

带 MCU 原理图的温度传感器

HC-SR04 电路连接提示

以下是连接电路的推荐步骤:

  1. 将 HC-SR04 传感器的 VCC 引脚连接到正极供电轨。

  2. 将传感器的接地引脚接地。

  3. 将 HC-SR04 的 Trig 引脚连接到 HV4,将 echo 引脚连接到 HV3。

  4. 将逻辑电平转换器的 LV4 引脚连接到微控制器的 D5 引脚,LV3 引脚连接到微控制器的 D6 引脚。

  5. 将逻辑电平转换器的 GND 引脚连接到试验板的负供电轨。

当你完成电路连接时,它应该看起来如图 10-6 所示。

img/511128_1_En_10_Fig6_HTML.jpg

图 10-6

试验板上带 MCU 的 HC-SR04

我们需要的图书馆

这些是我们需要添加到 lib 文件夹中的库:

  • 开源软件国际化之简体中文组

HC-SR04 带 CircuitPython 程序

在管理单元编辑器中编辑您的 code.py,使其类似于清单 10-2 。这个例子由 Adafruit Industries 提供的用于读取传感器的例子修改而来。

# import time library
import time

# import board library
import board

# import HCSR04 sensor
(1) import adafruit_hcsr04

# create instance of our HCSR04 object
(2) sonar = adafruit_hcsr04.HCSR04(trigger_pin=board.D5, echo_pin=board.D6)

# super loop
(3) while True:
    # try to get the distance
    try:
        print((sonar.distance,))

    # else tell us it failed
    except RuntimeError:
        print("Fail!")

    # wait 0.1s
    time.sleep(0.1)

Listing 10-2MCU with Temperature Sensor Program

在程序中,我们执行通常的导入,在(1)中,我们导入了允许我们使用 HC-SR04 传感器的库。在(2)中,我们创建了一个可以操作的 HCSR04 对象的实例,位于 D5 和 D6 引脚上。在主超级循环中的(3)处,我们有一个 try catch 语句,用于尝试读取传感器,如果失败,我们会告诉用户发生了错误。我们在图 10-7 中看到串行控制台的输出。

img/511128_1_En_10_Fig7_HTML.jpg

图 10-7

HC-SR04 传感器输出

当我们把手靠近传感器时,距离读数变小,当我们把手拿开时,我们观察到距离读数变大。

压电扬声器

如果你曾经使用过微波炉或自动取款机,你一定会听到这些设备发出的电子哔哔声。有时,当我们需要提醒用户一些事情时,我们不仅可以使用 led 发出的光,还可以使用声音。产生声音的经典方法是使用压电扬声器,也称为压电蜂鸣器或压电扬声器。一个这样的扬声器如图 10-8 所示。

img/511128_1_En_10_Fig8_HTML.jpg

图 10-8

压电扬声器鸣谢:adafruit.com Adafruit

压电扬声器由一个微小的金属板组成,我们称之为压电元件。当我们向压电元件施加方波时,它就会振动,并产生可听见的声音。我们可以利用这种效应创建一个程序,允许我们向设备发送不同频率的波,以创建不同的声音。

带 MCU 的压电原理图

我们如图 10-9 所示连接电路。压电扬声器有两个引脚。我们将压电片上的正极引脚连接到 D5 引脚,另一引脚接地。压电的正极引脚通常在蜂鸣器上写有一个小的“+”号。

img/511128_1_En_10_Fig9_HTML.jpg

图 10-9

带 MCU 原理图的温度传感器

压电电路连接提示

以下是连接电路的推荐步骤:

  1. 将压电扬声器的接地引脚接地。

  2. 将压电的正极引脚连接到引脚 D5。

当你完成电路连接时,它应该看起来如图 10-10 所示。

img/511128_1_En_10_Fig10_HTML.jpg

图 10-10

试验板上集成 MCU 的压电传感器

我们需要的图书馆

这些是我们需要添加到 lib 文件夹中的库:

  • 简单的

Piezo 和 CircuitPython 程序

在管理单元编辑器中编辑您的 code.py,使其类似于清单 10-3 。这个例子是由 Adafruit Industries 提供的用于创建声音的例子修改而来的。

# import board
import board

# import simple io library
import simpleio

# Define pin connected to piezo buzzer.
(1) PIEZO_PIN = board.D5

# Define a list of tones/music notes to play.
(2) TONE_FREQ = [ 262,  # C4
              294,  # D4
              330,  # E4
              349,  # F4
              392,  # G4
              440,  # A4
              494 ] # B4

# super loop

(3) while True:
    # Play tones going from start to end of list.
    for i in range(len(TONE_FREQ)):
        simpleio.tone(PIEZO_PIN, TONE_FREQ[i], duration=0.5)

    # Then play tones going from end to start of list.
    for i in range(len(TONE_FREQ)-1, -1, -1):
        simpleio.tone(PIEZO_PIN, TONE_FREQ[i], duration=0.5)

Listing 10-3MCU with Piezo Program

在程序中,我们做我们通常的导入;然后在(1)我们设置压电钉 D5。在(2)中,我们定义了要演奏的音符列表。使用程序中的注释列表,在(3)处的超级循环中,我们对它们进行迭代。一旦程序正常工作,你会听到从你的扬声器传来的音符。

DHT11

在模拟接口部分,我们讨论了温度传感器的使用。但是,有一种流行的二合一传感器可以测量温度和湿度,这就是 DHT11 传感器。DHT11 如图 10-11 所示。

img/511128_1_En_10_Fig11_HTML.jpg

图 10-11

DHT11 温度和湿度传感器:adafruit.com 阿达弗洛

该器件有四个引脚。一个引脚是 VCC,另一个引脚是接地引脚。还有一个输出引脚,用于从传感器读取数据。

带 MCU 原理图的 DHT11

我们如图 10-12 所示连接电路。我们将 DHT11 的输出连接到输入引脚 D10。为了正常工作,我们需要一个连接到 DHT11 输出引脚的上拉电阻。

img/511128_1_En_10_Fig12_HTML.jpg

图 10-12

带 MCU 原理图的 DHT11 传感器

DHT11 传感器电路连接提示

以下是连接电路的推荐步骤:

  1. 将 DHT11 的 VCC 引脚连接到正电源轨。

  2. 将传感器的接地引脚接地。

  3. 将 1k 电阻从输出引脚连接到 VCC 引脚。

  4. 在 DHT11 传感器的输出引脚和运行 CircuitPython 的 MCU 上的 D10 引脚之间连接一根跳线。

当你完成电路连接时,它应该看起来如图 10-13 所示。

img/511128_1_En_10_Fig13_HTML.jpg

图 10-13

试验板上集成 MCU 的温度传感器

我们需要的图书馆

这些是我们需要添加到 lib 文件夹中的库:

  • adafruit_dht 函数

带 CircuitPython 程序的 DHT11 传感器

在管理单元编辑器中编辑您的 code.py,使其类似于清单 10-4 。这个例子由 Adafruit Industries 提供的用于读取传感器的例子修改而来。

# import board
import board

# import time
import time

# import busio
import busio

# import library for working with sensor
(1) import adafruit_dht

# connect the DHT11 to pin10
(2) dht = adafruit_dht.DHT11(board.D10)

(3) while True:
    try:
        # read the temperature and humidity
        temperature = dht.temperature
        humidity = dht.humidity

        # print the read temepratue and humidity
        print("Temp: {:.1f} *C \t Humidity: {}%".format(temperature, humidity))

    except RuntimeError as e:
        # if dosent work print error
        print("Reading from DHT failure: ", e.args)

    # print every second

    time.sleep(1)

Listing 10-4MCU with DHT11 Sensor Program

在程序中,我们做我们通常的导入;然后,在(1)中,我们导入库以使用 DHT11 传感器。在(2)中,我们在引脚 10 上创建了 DHT11 传感器的实例。在(3)的主循环中,我们读取传感器的温度和湿度,并将其打印到控制台。输出如图 10-14 所示。

img/511128_1_En_10_Fig14_HTML.jpg

图 10-14

DHT11 传感器输出

传感器将以稳定的速率输出数据,并且由于我们的 try catch,如果程序失败,它将继续运行并将数据输出到控制台。

请注意,尽管本例使用了 DHT11 传感器,但也可以使用 DHT22 而不会有任何问题,因为“adafruit_dht”库支持这两种器件。要在创建传感器实例时使用该传感器,只需将 DHT11 改为 DHT22。

结论

在本章中,我们讨论了使用基于 MicroPython 的微控制器与一些常见传感器进行接口。我们考虑使用 RGB LEDs、超声波传感器、声音、温度和湿度传感器。本章学到的知识将允许你构建一些非常有趣的嵌入式系统。

恭喜你!你已经读完了整本书。如果您已经做到了这一步,那么您就已经为在微控制器上使用 CircuitPython 打下了坚实的基础。不要停在那里!你仍然可以做很多事情来增加你的知识。检查 Adafruit 库包,并从那里运行代码示例。继续修修补补!

posted @ 2024-08-09 17:43  绝不原创的飞龙  阅读(33)  评论(0编辑  收藏  举报