# 乐鑫ESP32芯片介绍

# ESP32基本信息

ESP32是有乐鑫科技开发的主流的嵌入式WiFi蓝牙SoC(System-on-Chip)。

# ESP32开发板

现在许多来自不同生产厂商(包括乐鑫)的开发板搭载ESP32芯片。MicroPython试图提供一个通用端口,该端口可以在尽可能多的开发板或模块上运行,但可能存在一些限制。乐鑫开发板作为端口的参考 (例如,对它们进行测试)。对于您正在使用的任何开发板,请确保您有数据表、示意图和其他参考资料,以便您可以查找任何开发板的特定功能。

为了制作一个通用的ESP32端口并支持尽可能多的板,做出了以下设计和实施决定:

  • GPIO管脚编号基于ESP32芯片编号。请将您的开发板的手册或管脚图放在手边,以便找到您的开发板管脚 和实际的ESP32管脚之间的对应关系。
  • MicroPython支持所有管脚,但并非所有管脚都可以在任何板上使用。 例如,不应使用连接到外部SPI闪存的管脚,并且开发板可能只公开某些管脚选择。

# 技术参数和SoC数据表

ESP32芯片的数据表和其他参考资料可从供应商网站获得: 乐鑫官方ESP32说明 (opens new window) 这里有芯片技术规格、性能、工作模式、内部功能等的主要参考。

为了方便你了解ESP32,我们给你提供了部分参数:

  • 架构: Xtensa Dual-Core 32-bit LX6
  • CPU频率: up to 240MHz
  • RAM可用空间: 528KB (保留一部分给系统)
  • BootROM: 448KB
  • 内部FlashROM: none
  • 外部FlashROM: code and data, via SPI Flash; usual size 4MB
  • GPIO: 34 (gpio与其他功能多路复用,包括外部FlashROM、UART等。)
  • UART: 3个RX/TX UART (无硬件握手), 一个TX-only UART
  • SPI: 4 SPI interfaces (one used for FlashROM)
  • I2C: 2 I2C (任何管脚上都有bitbang实现)
  • I2S: 2
  • ADC: 12位 SAR ADC 最高18频道
  • DAC: 2个8位 DACs
  • RMT: 8通道允许精确的脉冲发射/接收

编程方式: 从UART使用BootROM bootloader-由于外部FlashROM和始终可用的BootROM bootloader,ESP32是不可分块的

获取更多有关ESP32的信息?请访问:https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf (opens new window)

MicroPython是在ESP-IDF(乐鑫科技的ESP32开发框架)之上实现的。这是一个基于FreeRTOS的系统。 想要深入了解ESP-IDF?请移步至: ESP-IDF编程指南 (opens new window)

# ESP32通用控制

MicroPython 的串口交互调试(REPL)在 UART0 (GPIO1=TX, GPIO3=RX),波特率为:115200。 Tab按键补全功能对于找到每个对象的使用方法非常有用。 粘贴模式 (ctrl-E) 对需要复制比较多的python代码到REPL是非常有用。

# MicroPython 特定库在ESP32上的使用

# ESP32的MicroPython端口实现了以下machine模块的子集:

import machine

machine.freq()          # 获取CPU当前工作频率
machine.freq(240000000) # 设置CPU的工作频率为 240 MHz
import esp

esp.osdebug(None)       # 关闭原厂 O/S 调试信息
esp.osdebug(0)          # 将原厂 O/S 调试信息重定向到 UART(0) 输出

# 与flash交互的低级方法
esp.flash_size()
esp.flash_user_start()
esp.flash_erase(sector_no)
esp.flash_write(byte_offset, buffer)
esp.flash_read(byte_offset, buffer)

ESP32专用的库:

import esp32

esp32.hall_sensor()     # 获取霍尔传感器的值
esp32.hall_sensor(500)  # 设置霍尔传感器的阈值为500

esp32.raw_temperature() # 读取内部温度传感器,在MCU上, 单位:华氏度F
esp32.ULP()             # 使用超低功耗协处理器(ULP)

请注意ESP32内部温度读取数值会比实际要高,因为芯片工作时候回发热。 从睡眠状态唤醒后立即读取温度传感器可以最大限度地减少这种影响。

# ESP32网络相关

使用ESP32的网络功能network模块连接到WiFi网络:

import network

wlan = network.WLAN(network.STA_IF) # 创建 station 接口
wlan.active(True)       # 激活接口
wlan.scan()             # 扫描允许访问的SSID
wlan.isconnected()      # 检查创建的station是否连已经接到AP
wlan.connect('essid', 'password') # 连接到指定ESSID网络
wlan.config('mac')      # 获取接口的MAC地址
wlan.ifconfig()         # 获取接口的 IP/netmask(子网掩码)/gw(网关)/DNS 地址

使用ESP32的网络功能network模块创建一个WiFi热点:

import network
ap = network.WLAN(network.AP_IF) # 创捷一个AP热点接口
ap.config(essid='ESP-AP') # 激活接口
ap.config(max_clients=10) # 设置热点允许连接数量
ap.active(True)           # 设置AP的ESSID名称

连接到本地WIFI网络的函数参考:

def do_connect():
    import network
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('开始连接网络...')
        wlan.connect('WiFi名称', 'WiFi密码')
        while not wlan.isconnected():
            pass
    print('网络配置:', wlan.ifconfig())

一旦网络建立成功,你就可以通过 socket 模块创建和使用 TCP/UDP sockets 通讯, 以及通过 urequests 模块非常方便地发送 HTTP 请求。

提示

在调用 wlan.connect() 后,默认情况下,设备将永远重试连接,即使身份验证失败或范围内没有AP。 wlan.status() 将返回网络。STAT_CONNECTING 处于此状态,直到连接成功或接口被禁用。 这可以通过调用 wlan.config(reconnections=n) 来更改,其中n是所需的重新连接尝试次数(0表示不会重试,-1将恢复尝试永远重新连接的默认行为)。 如果 wlan.config(reconnections=0) 被调用,wlan.connect() 将返回False,如果 wlan.config(reconnections=-1) 被调用,wlan.connect() 将返回True或False,具体取决于连接是否成功。

# 延时和时间

Use the time module:

import time

time.sleep(1)           # 睡眠1秒
time.sleep_ms(500)      # 睡眠500毫秒
time.sleep_us(10)       # 睡眠10微妙
start = time.ticks_ms() # 获取毫秒计时器开始值
delta = time.ticks_diff(time.ticks_ms(), start) # 计算从开始到当前时间的差值

# 定时器

ESP32拥有4个定时器。使用 machine.Timer 类通过设置timer ID号为 0-3

from machine import Timer

tim0 = Timer(0)
tim0.init(period=5000, mode=Timer.ONE_SHOT, callback=lambda t:print(0))

tim1 = Timer(1)
tim1.init(period=2000, mode=Timer.PERIODIC, callback=lambda t:print(1))

该周期的单位为毫秒(ms) 此端口当前不支持虚拟计时器。

# 引脚和GPIO口

使用 machine.Pin 模块:

from machine import Pin

p0 = Pin(0, Pin.OUT)    # 创建对象p0,对应GPIO0口输出
p0.on()                 # 设置引脚为 "on" (1)高电平
p0.off()                # 设置引脚为 "off" (0)低电平
p0.value(1)             # 设置引脚为 "on" (1)高电平

p2 = Pin(2, Pin.IN)     # 创建对象p2,对应GPIO2口输入
print(p2.value())       # 获取引脚输入值, 0(低电平) 或者 1(高电平)

p4 = Pin(4, Pin.IN, Pin.PULL_UP) # 打开内部上拉电阻
p5 = Pin(5, Pin.OUT, value=1) # 初始化时候设置引脚的值为 1(高电平)

可以使用引脚排列如下 (包括首尾): 0-19, 21-23, 25-27, 32-39.分别对应ESP32芯片的实际引脚编号。 请注意,用户使用自己其它的开发板有特定的引脚命名方式(例如:DO, D1, …)。 由于MicroPython致力于支持不同的开发板和模块,因此我们采用最原始简单具且有共同特征的引脚命名方式。如果你使用自己的开发板,请参考其原理图。

注意:

  • 引脚1和3分别是串口交互(REPL)的TX和RX。
  • 引脚6, 7, 8, 11, 16, 和 17 are 连接到模块的Flash,不建议做其它用途。
  • 引脚 34-39 只允许输入, 没有内部上拉电阻。
  • 部分引脚的pull值可以设置为 Pin.PULL_HOLD 以降低深度睡眠时候的功耗。

# UART (串口总线)

库 machine.UART

from machine import UART

uart1 = UART(1, baudrate=9600, tx=33, rx=32)
uart1.write('hello')  # write 5 bytes
uart1.read(5)         # read up to 5 bytes

ESP32有三个硬件UART:UART0、UART1和UART2。每个引脚都分配了默认的GPIO,但根据您的ESP32芯片和开发板,这些引脚可能与嵌入式闪存、板载PSRAM或外围设备冲突。 任何GPIO都可以用于使用GPIO矩阵的硬件UART,因此为了避免冲突,只需在构建时提供tx和rx引脚。下面列出了默认端号。

UART0 UART1 UART2
tx 1 10 17
rx 3 9 16

# PWM (脉宽调制)

PWM 能在所有可输出引脚上实现。基频的范围可以从 1Hz 到 40MHz 但需要权衡: 随着基频的 增加 占空分辨率 下降. 详情请参阅: LED Control.

Use the machine.PWM class:

from machine import Pin, PWM

pwm0 = PWM(Pin(0))         # create PWM object from a pin
freq = pwm0.freq()         # get current frequency (default 5kHz)
pwm0.freq(1000)            # set PWM frequency from 1Hz to 40MHz

duty = pwm0.duty()         # get current duty cycle, range 0-1023 (default 512, 50%)
pwm0.duty(256)             # set duty cycle from 0 to 1023 as a ratio duty/1023, (now 25%)

duty_u16 = pwm0.duty_u16() # get current duty cycle, range 0-65535
pwm0.duty_u16(2**16*3//4)  # set duty cycle from 0 to 65535 as a ratio duty_u16/65535, (now 75%)

duty_ns = pwm0.duty_ns()   # get current pulse width in ns
pwm0.duty_ns(250_000)      # set pulse width in nanoseconds from 0 to 1_000_000_000/freq, (now 25%)

pwm0.deinit()              # turn off PWM on the pin

pwm2 = PWM(Pin(2), freq=20000, duty=512)  # create and configure in one go
print(pwm2)                               # view PWM settings

ESP 芯片具有不同的硬件外设:

硬件规格 ESP32 ESP32-S2 ESP32-C3
组数(速度模式) 2 1 1
每组计时器数 4 4 4
每组通道数 8 8 6
不同的 PWM 频率(组 * 定时器) 8 4 4
PWM 通道总数(引脚、工作)(组 * 通道) 16 8 6

ESP32 上提供的最大数量的 PWM 通道 - 16 个通道,但只有 8 个不同的 PWM 频率可用,其余 8 个通道必须具有相同的频率。另一方面,在相同的频率下可以有16个独立的PWM占空比。

有关脉宽调制教程中的更多示例。

# ADC (模数转换)

ADC功能在ESP32引脚32-39上可用。 请注意,使用默认配置时,ADC引脚上的输入电压必须 介于0.0v和1.0v之间(任何高于1.0v的值都将读为4095)。 如果需要增加测量范围,需要配置 衰减器。

使用机器。模数转换器类:

from machine import ADC

adc = ADC(Pin(32))          # 在ADC引脚上创建ADC对象
adc.read()                  # 读取测量值, 0-4095 表示电压从 0.0v - 1.0v

adc.atten(ADC.ATTN_11DB)    # 设置 11dB 衰减输入 (测量电压大致从 0.0v - 3.6v)
adc.width(ADC.WIDTH_9BIT)   # 设置 9位 精度输出 (返回值 0-511)
adc.read()                  # 获取重新配置后的测量值

# ESP32 特定的 ADC 类使用方法说明:

# ADC.atten(attenuation)

该方法允许设置ADC输入的衰减量,以获取更大的电压测量范围,但是以精度为代价的。 (配置后相同的位数表示更宽的范围)。 衰减选项如下:

  • ADC.ATTN_0DB: 0dB 衰减, 最大输入电压为 1.00v - 这是默认配置
  • ADC.ATTN_2_5DB: 2.5dB 衰减, 最大输入电压约为 1.34v
  • ADC.ATTN_6DB: 6dB 衰减, 最大输入电压约为 2.00v
  • ADC.ATTN_11DB: 11dB 衰减, 最大输入电压约为3v

警告

尽管通过配置11dB衰减可以让测量电压到达3.6v,但由于ESP32芯片的最大允许输入电压是3.6V, 因此输入接近3.6V的电压可能会导致IC烧坏!

# ADC.宽度(宽度)

该方法允许设置ADC输入的位数精度。 选项如下:

  • ADC.WIDTH_9BIT: 9 bit data
  • ADC.WIDTH_10BIT: 10 bit data
  • ADC.WIDTH_11BIT: 11 bit data
  • ADC.WIDTH_12BIT: 12 bit data - 这是默认配置

# 软件SPI总线

EPS32内部有两个SPI驱动。其中1个是通过软件实现 (bit-banging),并允许配置到所有引脚, 通过 machine.SoftSPI 类模块配置:

from machine import Pin, SoftSPI

# 在给定的引脚上创建SoftSPI总线
# (极性)polarity是指 SCK 空闲时候的状态
# (相位)phase=0 表示SCK在第1个边沿开始取样,phase=1 表示在第2个边沿开始。
spi = SoftSPI(baudrate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4))

spi.init(baudrate=200000) # 设置频率

spi.read(10)            # 在MISO引脚读取10字节数据
spi.read(10, 0xff)      # 在MISO引脚读取10字节数据同时在MOSI输出0xff

buf = bytearray(50)     # 建立缓冲区
spi.readinto(buf)       # 读取数据并存放在缓冲区 (这里读取50个字节)
spi.readinto(buf, 0xff) # 读取数据并存放在缓冲区,同时在MOSI输出0xff

spi.write(b'12345')     # 在MOSI引脚上写5字节数据

buf = bytearray(4)      # 建立缓冲区
spi.write_readinto(b'1234', buf) # 在MOSI引脚上写数据并将MISO读取数据存放到缓冲区
spi.write_readinto(buf, buf) # 在MOSI引脚上写缓冲区的数据并将MISO读取数据存放到缓冲区

警告

目前在创建软件SPI对象时,sck, mosi 和 miso 所有的引脚必须定义。

# 硬件SPI总线

有两个硬件SPI通道允许更高速率传输(到达80MHz)。 也可以配置成任意引脚,但相关引脚要 符合输入输出的方向性,这可以参阅(see 引脚和GPIO口)内容。通过自定义引脚而非 默认引脚,会降低传输速度,上限为40MHz。以下是硬件SPI总线默认引脚:

HSPI (id=1) VSPI (id=2)
sck 14 18
mosi 13 23
miso 12 19

硬件SPI通过机器访问。硬件SPI类并具有与上述软件SPI相同的方法:

from machine import Pin, SPI

hspi = SPI(1, 10000000)
hspi = SPI(1, 10000000, sck=Pin(14), mosi=Pin(13), miso=Pin(12))
vspi = SPI(2, baudrate=80000000, polarity=0, phase=0, bits=8, firstbit=0, sck=Pin(18), mosi=Pin(23), miso=Pin(19))

# 软件I2C总线

I2C总线分软件和硬件对象,硬件可以定义0和1,通过配置可以在任意引脚上实现改功能, 详情请看 machine.SoftI2C 类模块:

from machine import Pin, SoftI2C

# 构建1个I2C对象
i2c = SoftI2C(scl=Pin(5), sda=Pin(4), freq=100000)

# 构建一个硬件 I2C 总线
i2c = I2C(0)
i2c = I2C(1, scl=Pin(5), sda=Pin(4), freq=400000)

i2c.scan()              # 扫描从设备

i2c.readfrom(0x3a, 4)   # 从地址为0x3a的从机设备读取4字节数据
i2c.writeto(0x3a, '12') # 向地址为0x3a的从机设备写入数据"12"

buf = bytearray(10)     # 创建1个10字节缓冲区
i2c.writeto(0x3a, buf)  # 写入缓冲区数据到从机

# 硬件I2C总线

有两个硬件I2C外围设备,标识符分别为0和1。任何可用的具有输出功能的引脚都可以用于SCL和SDA,但下面给出了默认值:

I2C(0) I2C(1)
scl 18 25
sda 19 26

可通过机器访问驱动程序。硬件I2C类,并具有与上述软件I2C相同的方法:

from machine import Pin, I2C

i2c = I2C(0)
i2c = I2C(1, scl=Pin(5), sda=Pin(4), freq=400000)

# I2S 总线

使用 machine.I2S 类模块:

from machine import I2S, Pin

i2s = I2S(0, sck=Pin(13), ws=Pin(14), sd=Pin(34), mode=I2S.TX, bits=16, format=I2S.STEREO, rate=44100, ibuf=40000) # 创建I2S对象
i2s.write(buf)             # 将音频样本的缓冲区写入I2S设备

i2s = I2S(1, sck=Pin(33), ws=Pin(25), sd=Pin(32), mode=I2S.RX, bits=16, format=I2S.MONO, rate=22050, ibuf=40000) # 创建I2S对象
i2s.readinto(buf)          # 用I2S设备的音频样本填充缓冲区

I2S类目前作为技术预览版提供。在预览期间,鼓励用户提供反馈。基于此反馈,I2S类API和实现可能会发生变化。

ESP32有两条id为0和id为1的I2S总线

# 实时时钟(RTC)

使用 machine.RTC 类模块:

from machine import RTC

rtc = RTC()
rtc.datetime((2017, 8, 23, 1, 12, 48, 0, 0)) # 设置时间(年,月,日,星期,时,分,秒,微秒)
                                             # 其中星期使用0-6表示星期一至星期日。
rtc.datetime() # 获取当前日期和时间

# WDT(看门狗计时器)

使用 machine.WDT 类模块:

from machine import WDT

# 启用WDT,超时时间为5s(最短为1s)
wdt = WDT(timeout=5000)
wdt.feed()

# 深度睡眠模式

下面代码可以用来睡眠、唤醒和检测复位唤醒:

import machine

# 检测设备是否从深度睡眠中唤醒
if machine.reset_cause() == machine.DEEPSLEEP_RESET:
    print('woke from a deep sleep')

# 使设备进入深度睡眠,时间10秒。
machine.deepsleep(10000)

注意事项:

调用深度睡眠函数 deepsleep() 如果不提供参数(时间)的话可能会让设备无限期休眠。 软件复位不能触发复位事件(reset cause)。 可能会出现一些泄漏电流流经内部上下拉电阻,为了进一步降低功耗,可以关闭GPIO的上下拉电阻:

p1 = Pin(4, Pin.IN, Pin.PULL_HOLD)

退出深度睡眠后,有必要恢复GPIO原来的状态 (例如:原来是输出引脚)

p1 = Pin(4, Pin.OUT, None)

# SD 卡管理

使用 machine.SDCard 类模块:

import machine, os

# 插槽2使用引脚 sck=18,cs=5,miso=19,mosi=23
sd = machine.SDCard(slot=2)
os.mount(sd,"/sd")  # 装载

os.listdir('/sd')   # 列出目录内容

os.umunt('/sd')     # 弹出

# RMT

RMT是ESP32专用的,可以生成分辨率为12.5ns的精确数字脉冲。

详见esp32.RMT。用法是:

import esp32
from machine import Pin

r = esp32.RMT(0, pin=Pin(18), clock_div=8)
# RMT(channel=0, pin=18, source_freq=80000000, clock_div=8)
# The channel resolution is 100ns (1/(source_freq/clock_div)).
r.write_pulses((1, 20, 2, 40), 0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns

# 单总线驱动(Onewire)

单总线驱动允许通过软件在各个引脚上实现:

from machine import Pin
import onewire

ow = onewire.OneWire(Pin(12)) # 在引脚 GPIO12 创建单总线对象ow
ow.scan()               # 扫描设备,返回设备编号列表
ow.reset()              # 复位总线
ow.readbyte()           # 读取1字节
ow.writebyte(0x12)      # 写入1个字节(0x12)
ow.write('123')         # 写入多个字节('123')
ow.select_rom(b'12345678') # 根据ROM编号选择总线上的指定设备

# DS18B20(数字温度传感器)设备驱动

下面是一个DS18B20设备的驱动函数:

import time, ds18x20
ds = ds18x20.DS18X20(ow)
roms = ds.scan()
ds.convert_temp()
time.sleep_ms(750)
for rom in roms:
    print(ds.read_temp(rom))

注意

确保数据引脚连接了 4.7k 的上拉电阻。另外请注意每次采集温度都需要用到 convert_temp() 模块。

# DHT(温湿度传感器)驱动

DHT 温湿度驱动允许通过软件在各个引脚上实现:

import dht
import machine

d = dht.DHT11(machine.Pin(4))
d.measure()
d.temperature() # eg. 23 (°C)
d.humidity()    # eg. 41 (% RH)

d = dht.DHT22(machine.Pin(4))
d.measure()
d.temperature() # eg. 23.6 (°C)
d.humidity()    # eg. 41.3 (% RH)