# ESP编程文档

# 内部文件系统

您的设备包含一个文件系统。此文件系统使用FAT格式且在MicroPython固件后储存在Flash中。

# 创建并读取文件

ESP32中的MicroPython支持使用内置open()函数以标准方式访问Python中的文件。

尝试以下方法来创建文件:

f = open('data.txt', 'w')
f.write('some data')
f.close()

然后您即可使用以下指令读回此新文件的内容:

f = open('data.txt')
print(f.read())
f.close()

注意

打开文件的默认模式为只读模式下的文本文件。 指定第二个参数为 'wb' 可以二进制模式打开写入, 'rb' 可以二进制模式读取。

# 列出文件及其他

os 模块可用于对文件系统的控制。首先引入模块:

>>> import os

然后尝试列出文件系统的内容:

>>> os.listdir()
['boot.py', 'main.py', 'www', 'param.json']

您可创建目录:

>>> os.mkdir('dir')

删除文件:

>>> os.remove('data.txt')

# 网络编程 TCP sockets

大多数互联网的基本组件即为TCP socket。 这些sockets提供了相连的网络设备间的可靠字节流。 本教程的此章节将演示在不同情况下如何使用TCP socket。

# Star Wars Asciimation

最简单的事情就是从网上下载数据。 在这种情况下,我们将使用由blinkenlights.nl网站提供的 Star Wars Asciimation设备。 其使用端口23上的远程登录协议来向任何与之相连的设备传输流数据。 使用方法很简单,因为并不需要您进行鉴别(给定一个用户名或密码), 您可直接开始下载数据。

首先,确保我们的socket模块可用:

>>> import socket

然后获取服务端的IP地址:

>>> addr_info = socket.getaddrinfo("towel.blinkenlights.nl", 23)

实际上,getaddrinfo 函数会返回一个地址列表, 且每个地址的信息都超出我们的需要。 我们只想获取首个有效地址和服务端的IP地址和端口。 使用以下指令来完成这一步骤。

>>> addr = addr_info[0][-1]

若您在提示框中输入 addr_infoaddr ,您看到的就是其中包含的信息。

使用IP地址,我们可以制作一个socket并连接到服务端:

>>> s = socket.socket()
>>> s.connect(addr)

我们已建立连接,所以可下载和显示数据:

>>> while True:
...     data = s.recv(500)
...     print(str(data, 'utf8'), end='')
...

# HTTP GET 请求

下一个示例演示如何下载网页。HTTP端口使用端口80, 下载前,您首先需要发送”GET”请求。在该请求中,您应指定检索的页面。

我们来定义一个可下载和打印URL的函数:

def http_get(url):
    _, _, host, path = url.split('/', 3)
    addr = socket.getaddrinfo(host, 80)[0][-1]
    s = socket.socket()
    s.connect(addr)
    s.send(bytes('GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n' % (path, host), 'utf8'))
    while True:
        data = s.recv(100)
        if data:
            print(str(data, 'utf8'), end='')
        else:
            break
    s.close()

确保您在运行此函数之前输入socket模块。然后您可尝试:

>>> http_get('http://micropython.org/ks/test.html')

此指令应检索页面并将HTML打印到控制台。

# GPIO编程

将您的板与真实世界相连、并控制其他器件的方式即通过GPIO引脚。 并非所有引脚都可用,多数情况下,可用引脚为:
0, 2, 4, 5, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33, 34, 35, 36,39.

GPIO0 和 GPIO2 为strapping引脚,这类引脚可影响启动。

GPIO1和GPIO3分别为REPL UART TX和RX。

GPIO6-11通常用于SPI Flash。

GPIO34-39 只能设置为输入模式,且无软件上拉和下拉函数。

GPIO37-38连接到一个用作ADC PRE_AMP的电容。

# Strapping 引脚

芯片系统重置(电源复位、RTC监控复位和欠压复位)的过程中, strapping引脚的锁存器将电压等级作为"0"或"1"的strapping位取样, 并保持这些数据直至芯片断电或关闭。 Strapping 配置设备的启动模式、VDD_SDIO的运行电压和其他初始系统设置。

内置 LDO 电压 (VDD_SDIO) +————————+————+———-+———-+ | Pin | Default | 3.3V | 1.8V | +========================+============+==========+==========+ | GPIO12 MTDI | Pull-down | 0 | 1 | +————————+————+———-+———-+

启动模式 +————————+————+———-+———-+ | Pin | Default | SPI Boot | Download Boot | +========================+============+==========+==========+ | GPIO0 | Pull-up | 1 | 0 | +————————+————+———-+———-+ | GPIO2 | Pull-down |Don’t-care| 0 | +————————+————+———-+———-+

因此我们建议只将其用于输出,或确保启动时下拉(上拉)不受影响。

引脚在 machine 模块中, 确保首先 import 了machine 模块。然后使用一下代码创建一个Pin对象:

>>> pin = machine.Pin(4)

此处,"4"为您想访问的引脚。通常您要将引脚配置为输入或输出,且需在创建时完成配置。使用以下指令创建一个输出引脚:

>>> pin = machine.Pin(4, machine.Pin.IN, machine.Pin.PULL_UP)

您可在使用PULL_UP或 None 来配置拉电阻。若未指定,则默认为None(并非拉电阻)。您可使用以下方法读取引脚上的值:

>>> pin.value()
0

此处您的板上的引脚可能回返回0或1,这取决于其连接的电路。使用以下指令创建一个输出引脚:

>>> pin = machine.Pin(4, machine.Pin.OUT)

用以下方法设置引脚值:

>>> pin.value(0)
>>> pin.value(1)

或:

>>> pin.off()
>>> pin.on()

# 外部中断

所有引脚都配置为在输入更改的情况下触发硬件中断。您可将代码(回调函数)设置为在触发时执行。

首先定义一个回调函数,此函数须用一个单独参数,即触发此函数的引脚。我们将使此函数设计为仅打印引脚:

>>> def callback(p):
...     print('pin change', p)

下一步我们将创建两个引脚并将其配置为输入:

>>> from machine import Pin
>>> p4 = Pin(4, Pin.IN)
>>> p5 = Pin(5, Pin.IN)

最后,我们需要确定引脚何时触发,以及检测事件时调用的函数:

>>> p4.irq(trigger=Pin.IRQ_FALLING, handler=callback)
>>> p5.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=callback)

我们将引脚4设置为仅在输入的下降沿触发(当其由高到低),将引脚5设置为在上升沿和下降沿都触发。 输入此代码后,您可将高电压和低电压用于引脚4和5,以观察中断的执行。

事件一旦发生,即将触发一个硬中断,并将中断所有运行代码,包括Python代码。 因此,您的回调函数在其所能做的事情中(例如,此函数无法分配内存)存在局限性,且函数应尽可能短小简易。

# PWM编程

脉宽调节(PWM)是获取数字引脚上人工模拟输出的一种方式。其通过引脚从低到高的迅速切换实现其功能。 有两个与此相关的参数:切换的频率和占空比。占空比定义为高电平的时长占单一周期的比率。 始终处于高电平时,占空比取最大值,始终处于低电平时占空比最小。

PWM可在所有输出引脚上启用。但其存在局限:须全部为同一频率,且仅有8个通道。频率须位于1Hz和78125Hz之间。

在引脚上使用PWM,您须首先创建一个引脚对象,例如:

>>> import machine
>>> p12 = machine.Pin(12)

使用以下指令创建PWM对象:

>>> pwm12 = machine.PWM(p12)

您可使用以下方法设置频率与占空比:

>>> pwm12.freq(500)
>>> pwm12.duty(512)

注意:占空比介于0至1023间,其中512为50%。若您打印PWM对象,则该对象将告知您其当前配置:

>>> pwm12
PWM(12, freq=500, duty=512)

您也可调用没有参数的freq()和duty()方法以获取其当前值。

引脚将继续保持在PWM模式,直至您使用以下指令取消此模式:

>>> pwm12.deinit()

# LED亮度渐变

我们可使用PWM特性来实现LED亮度渐变。假定您的板有一个连接到引脚2的LED,我们即可使用以下指令来创建一个LED-PWM对象:

>>> led = machine.PWM(machine.Pin(2), freq=1000)

注意:可在PWM构造函数中设置频率。

接下来,我们将使用定时和数学函数,因此请输入这些模块:

>>> import time, math

然后创建一个函数以驱动LED:

>>> def pulse(l, t):
...     for i in range(20):
...         l.duty(int(math.sin(i / 10 * math.pi) * 500 + 500))
...         time.sleep_ms(t)

您可使用以下指令尝试一下此函数:

>>> pulse(led, 50)

为获得更好效果,您可连续多次驱动:

>>> for i in range(10):
...     pulse(led, 20)

请记住您可使用ctrl-C来中断代码。

# 控制hobby伺服

使用PWM可控制hobby伺服电机。 此种电机需要50Hz的频率以及介于40至115之间的占空比,其中77为中心值。 若您将伺服器连接到电源与接地引脚上,信号线连接到引脚12 (其他引脚同样适用),您即可使用以下方法控制电机

>>> servo = machine.PWM(machine.Pin(12), freq=50)
>>> servo.duty(40)
>>> servo.duty(115)
>>> servo.duty(77)

# ACD编程

ESP32有ADC,可用于读取模拟电压并将之转换为数值。 您可使用以下指令创建这样的ADC引脚对象

>>> import machine, Pin
>>> adc = machine.ADC(Pin(35))

然后使用以下方法读取值:

>>> adc.read()
58

read() 函数返回的值介于0(0.0伏特)至4095(1.0伏特)之间。请注意:此输入仅能承受1.0伏的电压,因此您必须使用分压通路来测量大于1.0伏的电压

ADC 在引脚 32~39 上可用。

GPIO37-38 一般连接到一个电容,用于 ADC_PRE_AMP。

# 功耗控制

ESP32使您可在代码运行时改变CPU频率,并进入深度睡眠模式。此二者都可用于管理电力消耗。

# 改变CPU频率

机器模块有一个获取并设置CPU频率的函数。使用以下指令获取当前频率:

>>> import machine
>>> machine.freq()
80000000

默认情况下,CPU以80MHz的频率运行。 若您需要更多的处理能力,将其频率设置为160MHz, 但是是以当前消耗为代价的:

>>> machine.freq(160000000)
>>> machine.freq()
160000000

代码进行繁重的处理时,你可更改为更高频率,在处理结束时回复原有频率。

# 单总线编程

1-wire总线是仅将单一线用于通信的串行总线(地线和电源线除外)。 DS18B20温度传感器是一个广为人知的1-wire设备, 此处我们会显示如何使用1-wire协议读取数据。

运行以下代码,您至少需有DS18S20或DS18B20温度传感器,且传感器的数据线与GPIO12相连接。 您必须给传感器供电,并在数据引脚与电源引脚间连接一个4.7k欧姆的电阻:

import time
import machine
import onewire, ds18x20

# the device is on GPIO12
dat = machine.Pin(12)

# create the onewire object
ds = ds18x20.DS18X20(onewire.OneWire(dat))

# scan for devices on the bus
roms = ds.scan()
print('found devices:', roms)

# loop 10 times and print all temperatures
for i in range(10):
    print('temperatures:', end=' ')
    ds.convert_temp()
    time.sleep_ms(750)
    for rom in roms:
        print(ds.read_temp(rom), end=' ')
    print()

注意:您必须执行convert_temp()函数以启动温度读数,并在读取数值前等待至少750ms。

# 控制NeoPixels

NeoPixels,又名WS2812 LEDs,是串行连接的、单独可寻址的全色LED, 且可将其红、绿、蓝组件设置在0至255之间。 这些LED需要精准计时来实现控制,于是NeoPixels模块应运而生。

按照下列步骤创建一个NeoPixel对象:

>>> import machine, neopixel
>>> np = neopixel.NeoPixel(machine.Pin(4), 8)

这在具有GPIO4上配置了8像素的 NeoPixel 灯带。您可调整”4”(引脚编号)和”8”(像素数)来配合您的设置。

使用以下指令设置像素的颜色:

>>> np[0] = (255, 0, 0) # set to red, full brightness 设置为红色,全亮
>>> np[1] = (0, 128, 0) # set to green, half brightness 设置为绿色,半亮
>>> np[2] = (0, 0, 64)  # set to blue, quarter brightness 设置为蓝色,1/4亮度

对于颜色超过3种的LED而言,例如RGBW或RGBY,NeoPixel类传递一个bpp 参数。 设置RGBW Pixel的NeoPixel 对象,请遵循以下步骤:

>>> import machine, neopixel
>>> np = neopixel.NeoPixel(machine.Pin(4), 8, bpp=4)

在4-bpp模式下,请记得使用4-元组来设置颜色,而非3-元组。例如,使用以下指令来设置前三个像素:

>>> np[0] = (255, 0, 0, 128) # Orange in an RGBY Setup
>>> np[1] = (0, 255, 0, 128) # Yellow-green in an RGBY Setup
>>> np[2] = (0, 0, 255, 128) # Green-blue in an RGBY Setup

然后使用 write() 方法将颜色输出到LED:

>>> np.write()

下面的demo函数在LED上进行了展示:

import time

def demo(np):
    n = np.n

    # cycle
    for i in range(4 * n):
        for j in range(n):
            np[j] = (0, 0, 0)
        np[i % n] = (255, 255, 255)
        np.write()
        time.sleep_ms(25)

    # bounce
    for i in range(4 * n):
        for j in range(n):
            np[j] = (0, 0, 128)
        if (i // n) % 2 == 0:
            np[i % n] = (0, 0, 0)
        else:
            np[n - 1 - (i % n)] = (0, 0, 0)
        np.write()
        time.sleep_ms(60)

    # fade in/out
    for i in range(0, 4 * 256, 8):
        for j in range(n):
            if (i // 256) % 2 == 0:
                val = i & 0xff
            else:
                val = 255 - (i & 0xff)
            np[j] = (val, 0, 0)
        np.write()

    # clear
    for i in range(n):
        np[i] = (0, 0, 0)
    np.write()

使用以下指令执行:

>>> demo(np)

# 触摸传感器

触摸传感器系统建立在一个基底上, 此基底在保护平面下带有电极和相关连接。 当用户触碰表面时,即触发电容变化, 并产生一个表示触摸是否有效的二进制信号

A touch-sensor system is built on a substrate which carries electrodes and relevant connections under a protective flat surface. When a user touches the surface, the capacitance variation is triggered and a binary signal is generated to indicate whether the touch is valid.

ESP32可提供多达10个触摸GPIO。 这10个触摸GPIO为 0, 2, 4, 12, 13, 14, 15, 27, 32, 33

>>> import machine, Pin
>>> tc = machine.TouchPad(Pin(35))

当引脚为悬空时:

>>> tc.read()
1129

当用户触碰表面时:

>>> tc.read()
89

# 温度和湿度

DHT(数字湿度&温度)感应器为使用电容式湿度传感器和温度传感器来测量周围的空气的低成本数字式传感器。 其特点为处理模拟数字转换的芯片且提供单线接口。新的传感器额外提供了一个I2C接口。

DHT11(蓝)和DHT22(白)传感器提供相同的单线接口,但是DHT22需要一个特殊的对象,因为其计算更为复杂。 DHT22在湿度和温度读数上都有一个小数的分辨率。DHT11的温度和湿度都是整数。

自定义1-wire协议与Dallas 单线不同,此协议用于从感应器中获取测量结果。有效数据包括一个湿度值、一个温度值和一个校验和。

使用单线接口,应根据数据引脚构建对象:

>>> import dht
>>> import machine
>>> d = dht.DHT11(machine.Pin(4))

>>> import dht
>>> import machine
>>> d = dht.DHT22(machine.Pin(4))

使用以下指令测量和读取值:

>>> d.measure()
>>> d.temperature()
>>> d.humidity()

temperature() 返回的值单位为摄氏度,而自 humidity() 返回的值为相对湿度的百分比。

The DHT11只能以每秒1次的频率调用,DHT22为结果准确可达每秒2次。感应器准确度会随时间推移降低。每个感应器都支持不同的运算范围。详细信息请参考产品数据手册。

在单线模式中,4个引脚中只有3个启用;在I2C模式下,4个引脚全部启用。旧版感应器可能仍有4个引脚,尽管旧版并不支持I2C。第3个引脚不连接。

引脚配置:

Sensor without I2C in 1-wire mode (eg. DHT11, DHT22, AM2301, AM2302):

1=VDD, 2=Data, 3=NC, 4=GND Sensor with I2C in 1-wire mode (eg. DHT12, AM2320, AM2321, AM2322):

1=VDD, 2=Data, 3=GND, 4=GND Sensor with I2C in I2C mode (eg. DHT12, AM2320, AM2321, AM2322):

1=VDD, 2=SDA, 3=GND, 4=SCL 您应将上拉电阻用于数据、SDA和SCL引脚。

要在向后兼容的单线模式下运行新版的I2C感应器,您必须将引脚3和4与GND连接。这会禁用I2C接口。

DHT22 sensors are now sold under the name AM2302 and are otherwise identical.