ESP32与MicroPython入门-02 基本外设操作

开发板介绍

在介绍 GPIO 的基本操作之前,有必要先介绍一下所使用的开发板,以便更好地了解其用途。

DOIT ESP32 DEVKIT V1 使用的是 ESP-WROOM-32 模块,其主要参数为:

参数名称 参数值
主频 240MHZ
Flash容量 4MB
RAM容量 512KB

开发板上的一些主要元器件功能如下:

开发板
  1. ESP-WROOM-32 模块,是整个开发板的核心
  2. CP2102 芯片,用于串口转 USB
  3. 3.3V 稳压器,使 ESP32 能正常工作
  4. EN(ENABLE) 按键,用于复位芯片
  5. BOOT 按键,用于引导芯片进入烧入状态

上一节中,使用 MicroPython 实现了 LED 灯闪烁效果,完整的代码为:

from machine import Pin
import time
led = Pin(2, Pin.OUT)
while True:
    led.value(0)
    time.sleep(0.5)
    led.value(1)
    time.sleep(0.5)

接下来通过详细分析该程序来介绍 MicroPython 操作 GPIO 的一般方法。

基本GPIO操作

GPIO 输出代码分析

在第一行中,导入了 machine 模块的 Pin 类:

from machine import Pin

machine 是MicroPython提供的一个与芯片的外设交互的接口模块,包括 GPIO 、ADC 、计时器等。而 Pin 类就是对 GPIO 封装。

第二行中,导入了标准库 time

import time

尽管 MicroPython 删除了很多标准库以及标准库里的函数,但还是有不少标准库可以正常使用。可以使用以下代码检查当前 MicroPython 支持的所有标准库:

>>> help('modules')
__main__          gc                ubinascii         urandom
_boot             inisetup          ubluetooth        ure
_onewire          machine           ucollections      urequests
_thread           math              ucryptolib        uselect
...
Plus any modules on the filesystem

接下来,以下代码用于初始化 GPIO 口:

led = Pin(2, Pin.OUT)

Pin 类的初始化方法中,有以下几个主要的参数:

  • id :指定 GPIO 的引脚号,可以是一个标识符数字,也可以是字符串形式的引脚名称
  • mode :引脚模式,可以为 Pin.OUT 常规输出、Pin.IN 输入,以及 Pin.OPEN_RAIN 开漏输出模式。
  • pull :如果设置为 Pin.PULL_UP ,为上拉模式;Pin.PULL_DOWN 为下拉模式;设置为 None 则为悬空模式。

当初始化完成后,随时都可以通过以下方法重新设置 GPIO 引脚功能:

  • .mode(mode=None) :更改工作模式。如果忽略参数,则可以查询当前引脚的工作模式
  • .pull(pull=None) :更改上下拉方式。如果忽略参数,则可以查询当前引脚的上下拉方式
  • .init(mode=None, pull=None) :根据给定的参数重新初始化引脚

GPIO 的输入输出,主要是通过类的 .value(x=None) 方法实现。该方法如果忽略参数,则可以读取当前引脚的数字逻辑电平( 0 或 1 )。如果是工作在输出模式,可以通过传入等价于 True 的值让引脚输出高电平,或传入等价于 False 的值让引脚输出低电平。

Pin 类还将其封装为方法 .on(self).off(self) ,能将一个引脚的电平快速置 1 或 0 。

实际上,Pin 类还通过重载 .__call__(self, x=None) 特殊方法,只需要像函数一样调用 Pin 对象就可以读写 GPIO 。

因此,借助 Python 的语法,循环还能写成如下更简洁的形式:

while True:
    led(not led())
    sleep(1)

GPIO 按键输入实例

有了以上对 GPIO 的介绍后,就可以很轻松编写出 GPIO 的按键输入程序了。

例如,与 ESP32 相连的电路为:

电路图

根据电路图,首先需要初始化 GPIO 引脚为:

led = Pin(4, Pin.OUT)
key = Pin(5, Pin.IN, Pin.PULL_UP)

由于按钮按下时,GPIO5 的输入变为低电平 0 ,因此这里将 GPIO5 初始化设置为上拉模式,在按钮未按下时其值恒为 1 。

以下程序,当按钮按下时,LED 灯也同步亮起;当按钮松开时,LED 灯同步熄灭:

while True:
    led.value(key.value())
    time.sleep(0.1)

对程序稍加改动,就可以实现通过按键每次按下控制 LED 亮起与熄灭翻转:

while True:
    if not key.value():
        led.value(not led.value())
    time.sleep(0.1)

外设操作:PWM和ADC

ESP32 提供了许多外设,如 ADC 、UART 、I2C 、SPI 、CAN 等,这些外设不一定都会用到,因此这里先以比较常用且比较简单的 PWM 和 ADC 为例作为介绍,剩余的只有当需要时再作介绍。

PWM

PWM(Pulse-Width Modulation, 脉冲宽度调制),是一种利用数字信号有无来控制某些模拟电路程度的方式。

PWM 可以这样浅显地理解:某些元件(例如 LED )的工作程度(例如 LED 灯的亮度)是根据功率来确定的,而功率又是根据电压的有效值来决定的。因此,如果让一个数字信号在很短的周期内快速变化,那么得到的电压有效值将介于两者之间,并且高电平持续的时间越长,有效值将越大。

下图展示了 PWM 的概念。PWM 中有一个很重要的概念为占空比,指一个周期内高电平占用时间的比例:

PWM概念

可以这样认为,占空比越高,电压利用率也就越高,有效值就越大。

实现 PWM 效果非常容易,machine 模块包含了 PWM 类,可以快速生成 PWM 。首先引入该类:

from machine import PWM

以下代码将一个 GPIO 引脚包装为 PWM 输出:

led = Pin(2, Pin.OUT)
led_pwm = PWM(led)

PWM 类的 .freq(freq=None).duty(duty=None) 分别用于设置 PWM 的频率和占空比。如果忽略参数,则可以查询当前所用的频率和占空比。频率的设置范围为 0-78125 ,单位为 Hz ;而占空比的设置范围为 0-1023 ,它与 1023 的比值(或者说 0 到该值的范围占 0-1023 的范围比例)则是实际的占空比。这两个值都不能超过这个范围,否则实际结果会被约束到合法范围内。

这里先设置好工作频率:

FREQUENCY = 5000
led_pwm = PWM(Pin(2), freq=FREQUENCY)

这里让占空比不断变化,以达到 LED 的亮度逐渐变化的效果,对应的代码为:

while True:
    for duty in range(0, 1024, 4):
        led_pwm.duty(duty)
        time.sleep(0.01)

运行该代码,可以看到开发板上的蓝色 LED 灯从熄灭开始逐渐变亮,达到最大亮度后再熄灭逐渐变亮,以此循环。

如果要关闭 PWM 效果,可以调用其 .deinit() 方法。

ADC

ADC(Analog to Digital Converter, 模拟/数字信号转换)用于处理模拟信号。模拟信号指的是介于 0~Vcc 的电压值,此时该信号不能简单地使用 0 或 1 表示,而是要通过一定方式将其转换为其它可读的值。

和大多数单片机一样,这种功能只有部分引脚具备。下图列出了 DOIT ESP32 DEVKIT V1 的所有 GPIO 口支持的复用功能。需要注意的是,该图所对应的开发板为 30 个引脚,而某些版本的开发板是 36 个引脚的,可能会多出一些功能。但不管怎么说,相同序号的引脚功能是基本相同的。

e

注意 GPIO 口和引脚序号是不同步的。

使用 ADC 和使用 PWM 的方式类似,首先都需要将一个引脚变为 ADC 功能:

from machine import Pin, ADC
adc = ADC(Pin(32))

默认情况下,ADC 只能测量 1v 左右范围内的电压。如果要测量更大范围,可以使用 .atten(attenuation) 设置 ADC 输入的衰减量,以获取更大的电压测量范围。可以设置以下衰减值:

  • ADC.ATTN_0DB :不衰减,最大输入电压为 1.1v(默认值)
  • ADC.ATTN_2_5DB :2.5dB 衰减,最大输入电压约为 1.5v
  • ADC.ATTN_6DB :6dB 衰减,最大输入电压约为 2.2v
  • ADC.ATTN_11DB :11dB 衰减,最大输入电压约为 3.9v

因此,如果要测量 0-3.3v 变化的电压,需要使用以下代码:

adc.atten(ADC.ATTN_11DB)

在适当的时候,调用 ADC 类的 .read() 方法就可以获取当前的电压。电压使用一个 12 位的值来描述,因此如果当读取的值为 4095 时,达到最大输入电压。其余值可以根据比例换算成相应的电压。

注意,ESP32 的 ADC 引脚可以分为两组,GPIO 32-39 是第一组,GPIO 0, 2, 4, 12-15 和 25-27 是第二组。第二组引脚在有些时候(如使用 WiFi 功能时)不能作为 ADC 功能使用。

ESP32高级功能

触摸检测

这算是 ESP32 提供的一个非常有意思的功能了。ESP32 的某些引脚具备一种能力,可以检测引脚是否被人体触摸。

ESP32 有 10 个 GPIO 口可用作电容触摸输入,它们分别为:0、2、4、12、13、14、15、27、32、33。在 30 个引脚的开发板中,没有 0 号 GPIO ,这点需要注意。

使用电容触摸检测功能非常简单,只需要将一个 GPIO 引脚转化成触摸口,然后读入电容值即可。在有无触摸情况下,读入的值差别甚大。对应的代码为:

from machine import Pin, TouchPad
import time

touch = TouchPad(Pin(4))

while True:
    print(touch.read())
    time.sleep(0.5)

运行代码,观察输出结果如下:

809
684
671
809
109
62
56
43
57
808
825

当被触摸时,读取的值变为几十,而未触摸时的值则为几百。当然,具体的值可能会随着环境的改变而不一定,但是只要捕捉到很大的差别就可以判断是否被触摸。

中断

MicroPython 可以直接操作中断功能,通过某个信号暂停当前程序的运转并执行特定的功能。下面以外部中断为例简单介绍外部中断的使用方法。

除了 GPIO6 ~ GPIO11 之外的 GPIO 引脚都有这样一种功能,在电平发生跳变的瞬间,发出一个中断信号,并强制跳转执行某些代码。

要使用外部中断,首先需要初始化一个 GPIO 引脚,将其作为输入模式:

key = Pin(23, Pin.IN, Pin.PULL_DOWN)

为了在发送中断时执行某些功能,需要定义一个函数,这个函数要有这样一个参数,当发送外部中断时,传入产生中断的中断源:

def interrupt_handler(source):
    print(f'interrupt caused by {source}')

然后,通过调用 GPIO 引脚的 .irq(trigger, handler) 方法,可以将其变成一个中断源。trigger 表示触发方式,可以为上升沿 Pin.IRQ_RISING 即低电平向高电平跳变的瞬间,或下降沿 Pin.IRQ_FALLING 即高电平向低电平跳变的瞬间。handler 应该传入一个中断处理函数。

例如:

key.irq(trigger=Pin.IRQ_RISING, handler=interrupt_handler)

则与按键相连的 GPIO 口在从低电平向高电平跳变的瞬间,会调用 interrupt_handler() 函数,执行其中的代码,在终端中打印 "interrupt caused by Pin(23)"

参考资料/延伸阅读

https://docs.micropython.org/en/latest/esp32/quickref.html#
MicroPython 官方文档

京ICP备2021034974号
contact me by hello@frozencandles.fun