对本文的一些说明
本文来源于阅读《MicroPython开发与实战》时所做的笔记,这本书不是很厚,所以内容也不是很全面,但作为一个入门工具书还是够的,再由于本人不是这方面的大佬,也不是这个专业的,所做的笔记也必然不是很全面,这仅是我本人阅读笔记,放出来希望能帮助到一些刚入门的ESP32学者。
本人使用的开发板是合宙esp32c3,书籍使用的是NodeMCU-32S,大部分只是引脚不同和资源个数不同,资源的使用方法是相通的,我对部分代码参数进行调整以适配合宙esp32c3,如果有使用其他型号开发板的学者,也可以很容易复现代码。
书中部分代码我在开发板上无法运行成功,对此类代码已做标注
书籍情况介绍,本书共262页
1~12MPy介绍及Python安装,占比4.6%,累计4.6%
13~125讲解Python语法,占比43.1%,累计47.7%
126~172讲通过MPy使用WIFI,PWM,ADC,定时器,UART,SPI(ADC和SPI仅作必要介绍),占比17.94%,累计65.65%
后面讲的MQTT连接阿里云,MPy for STM32F4,暂未看到,先不写。
- 初始环境配置
- 下载对应的micropython固件
- pip install esptool
- 清空flash: esptool.py --chip esp32c3 --port COM? erase_flash
- 烧写固件:esptool.py --chip esp32c3 --port COM? --baud 460800 write_flash -z 0x0 esp32c3-xxx.bin
- 写代码,注意utf-8编码,main.py
- cp ./main.py /pyboard/
- pip install rshell,安装完成后rshell --buffer-size 512 --editor D:\programfiles\notepad++\notepad++.exe -p COM?
- repl并重启开发板,可以看到输出信息
由于每次都要重新输入rshell
的一长串命令,十分不便,所以想写成一个bat脚本,但是由于从未接触过bat命令,从网上找了不少,又结合AI,勉强拼凑出一份能用的脚本
conn.bat文件内容,该文件会调用下面的getCOM.bat文件
@echo off
chcp 65001
call getCOM.bat
setlocal EnableDelayedExpansion
:: 获取用户输入的COM端口号
set /p "port=请输入COM端口号: "
:: 移除用户输入中可能的前导"COM"字符串
set "COM_PORT=!port:COM=!"
:: 如果用户没有输入"COM",则添加前缀
if not "!COM_PORT:~0,3!"=="COM" set "COM_PORT=COM!COM_PORT!"
:: 调用rshell命令,使用用户输入的COM端口
rshell --buffer-size 512 --editor "D:\programfiles\notepad++\notepad++.exe" -p !COM_PORT!
goto :EOF
getCOM.bat内容
@echo off
setlocal
:: wmic /format:list strips trailing spaces (at least for path win32_pnpentity)
for /f "tokens=1* delims==" %%I in ('wmic path win32_pnpentity get caption /format:list ^| find "COM"') do (
call :setCOM "%%~J"
)
:: display all _COM* variables
set _COM
:: end main batch
goto :EOF
:setCOM <WMIC_output_line>
:: sets _COM#=line
setlocal
set "str=%~1"
set "num=%str:*(COM=%"
set "num=%num:)=%"
set str=%str:(COM=&rem.%
endlocal & set "_COM%num%=%str%"
goto :EOF
现象截图
如果我有任何错误及改进意见,欢迎指出!
esp32网络基础使用
import network
sta_if = network.WLAN(network.STA_IF) #STA模式
ap_if = network.WLAN(network.AP_IF) #AP模式
使用以下命令检查接口是否有效
sta_if.active()
ap_if.active()
连接WIFI
#首先激活station接口
sta_if.active(True)
#然后连接到网络
sta_if.connect("WIFI名","WIFI密码")
使用以下命令检查连接是否建立sta_if.isconnected()
查看IPsta_if.ifconfig()
开机自动连接WIFI
下面的函数可以自动运行并连接到WIFI网络,放入boot.py可以自启动
def do_connect():
import network
sta_if = network.WLAN(network.STA_IF)
if not sta_if.isconnected():
print('connecting to network...')
sta_if.active(True)
sta_if.connect("WIFI名","WIFI密码")
while not sta_if.isconnected():
pass
print('network config:', sta_ifconfig())
获取系统时间
import time
time.localtime()
此方法获取的并不是当前真正的时间,若需获取准确时间,可以用MPY的ntptime时间同步模块,从服务器校准时间
ntptime.settime(timezone = 8,server = 'ntp.ntsc.ac.cn')
为默认参数,可以自己修改,目前感觉似乎没有参数
import time
import ntptime
print(f'同步前本地时间{str(time.localtime())}')
ntptime.settime()
print(f'同步后本地时间{str(time.localtime())}')
网上找的
import ntptime
def sync_ntp():
ntptime.NTP_DELTA = 3155644800 # 可选 UTC+8偏移时间(秒),不设置就是UTC0
ntptime.host = 'ntp1.aliyun.com' # 可选,ntp服务器,默认是"pool.ntp.org"
ntptime.settime() # 修改设备时间,到这就已经设置好了
sync_ntp()
get请求
import urequests
a = urequests.get("http://www.baidu.com")
a.text
socket模块
socket模块的宏
宏 | 名称 | 含义 |
---|---|---|
socket.AF_INET | 地址簇 | TCP/IP-IPV4 |
socket.AF_INET6 | 地址簇 | TCP/IP-IPV6 |
socket.SOCK_STREAM | 套接字类型 | TCP流 |
socket.SOCK_DGRAM | 套接字类型 | UDP数据报 |
socket.SOCK_RAW | 套接字类型 | 原始套接字 |
socket.SO_REUSEADDR | 套接字选项 | 允许重用地址 |
socket.IPPROTO_TCP | IP协议号 | TCP协议 |
socket.IPPROTO_UDP | IP协议号 | UDP协议 |
socket.SOL_SOCKET | 套接字选项等级 | 套接字选项 |
socket模块的API
1.socket.getaddrinfo(host, port)
将主机域名和端口转换为用于创建套接字的5元组序列
(family, type, proto, canonname, sockaddr)
>>> info = socket.getaddrinfo("172.0.0.1",80)
>>> info
[(2, 1, 0, '172.0.0.1', ('172.0.0.1', 80))]
2.socket.socket([af, type, proto])
创建套接字
af:地址,type:类型,proto:协议号
一般不指定proto
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> print(s)
>>> print(s)
<socket>
3.socket.bind(address)
以列表或元组方式绑定地址和端口号
address:一个包含地址和端口号的元组或列表
addr = ("127.0.0.1", 10000)
s.bind(addr)
4.socket.listen([backlog])
监听套接字,使服务器能够接收连续
backlog:接收套接字的最大个数,有默认值
s.listen(100)
5.socket.accept()
阻塞接收连接请求,只能在绑定地址端口号和监听后调用,返回conn和address
返回值:conn:新的套接字对象,可以用来收发消息
address:连接到服务器的客户端地址
conn, address = s.accept()
6.socket.connect(address)
连接服务器
address:服务器地址和端口号的元组或列表
host = "192.168.3.147"
port = 100
s.connect((host, port))
7.socket.send(bytes)
发送数据,并返回发送的字节数。
bytes:bytes类型数据
s.send("hello, I am TCP Client")
8.socket.recv(bufsize)
接收数据,并返回接收到的数据对象
bufsize: 接收数据的最大字节数
data = conn.recv(1024)
9.socket.close()
关闭套接字
s.close()
利用socket下载网页数据
使用socket定义一个可以下载和打印URL的函数
# 目前似乎无法成功
import socket
def httpGet(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()
# 函数使用
httpGet('http://www.baidu.com/')
利用socket实现ESP32的网络通信
esp32与PC须在同一局域网内
电脑端代码
import socket
import _thread
def tcplink(conn, addr):
print('addr:',addr)
print('conn',conn)
while 1:
data = conn.recv(1024)
#防止对面断线连接没关掉
if not data:
break
print('msg:', str(data,"utf-8"))
conn.close()
sock_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_tcp.bind(('0.0.0.0', 6000))
sock_tcp.listen(100)
print('listening')
while 1:
conn, addr = sock_tcp.accept()
_thread.start_new_thread(tcplink, (conn, addr))
ESP32端代码
import socket
import _thread
sock_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_tcp.connect(("电脑IP地址172.26.96.101", 6000))
sock_tcp.sendall(bytes("i am zzp!", "utf-8"))
硬件控制
Pin例程
from machine import Pin
D4 = Pin(12, Pin.OUT)
led.value([x])# 可读可写
led.on()
led.off()
延时
utime
模块
utime.sleep(s)
utime.sleep_ms(ms)
utime.sleep_us(us)
import utime
utime.sleep_ms(500) #延时500ms
控制LED闪烁
import utime
from machine import Pin
D4 = Pin(12, Pin.OUT)
# 循环10次亮灭
for i in range(10):
D4.value(1)
utime.sleep_ms(500)
D4.value(0)
utime.sleep_ms(500)
PWM脉宽调制技术-呼吸灯
ESP32的占空比(duty)不是百分比而是一个分辨率,范围0~1023
from machine import Pin, PWM
D4 = Pin(2, Pin.OUT)
# 把Pin对象传入PWM构造器
D4 = PWM(D4) #也可以直接初始化,D4 = PWM(D4, freq=1000, duty=500)
方法
PWM.init(freq, duty)
PWM.freq([freq_val])
可读可写PWM.duty([duty_val])
可读可写PWM.deinit()
释放PWM对象
PWM控制LED亮度
把下列代码保存为pwm_led.py
from machine import Pin, PWM
class PWM_LED:
def __init__(self,pinNum,freq=1000):
pin = Pin(pinNum,Pin.OUT)
self.pwm = PWM(pin,freq=freq)
def change_duty(self,duty):
self.pwm.duty(duty)
def deinit(self):
self.pwm.deinit()
呼吸灯
import machine, utime, math
from pwm_led import *
from machine import Pin
pwm_led = PWM_LED(12)
def pulse(switch, period, gears):
# 呼吸灯核心代码
# 借用sin正弦函数,将PWM范围控制在23~1023
# switch开关对象
# period呼吸一次的周期,单位ms
#gears呼吸过程经历的亮度档位数
for i in range(2*gears):
switch.change_duty(int(math.sin(i/gears*math.pi)*500)+523)
# 延时
utime.sleep_ms(int(period/(2*gears)))
# 呼吸10次
for i in range(10):
pulse(pwm_led, 2000, 100)
# 释放资源
pwm_led.deinit()
ADC例程
构造对象
classmachine.ADC(pin)
创建与设定引脚关联的ADC对象。用户可以读取该引脚上的模拟值
Pin:ADC在专用因脚伤可以用
from machine import ADC, Pin
adc = ADC(Pin(0))
方法
ADC.atten(db)
设置ADC衰减量,即设置输入范围,未设置默认为1V
宏定义 | 衰减量/db | 数值 | 满量程电压 |
---|---|---|---|
ADC.ATTN_0DB | 0 | 0 | 1 |
ADC.ATTN_2_5DB | 2.5 | 1 | 1.5 |
ADC.ATTN_6DB | 6 | 2 | 2 |
ADC.ATTN_11DB | 11 | 3 | 3.3 |
ADC.width(bit)
设置分辨率
宏定义 | 数值 | 满量程 |
---|---|---|
ADC.WIDTH_9BIT | 0 | 511 |
ADC.WIDTH_10BIT | 1 | 1023 |
ADC.WIDTH_11BIT | 2 | 2047 |
ADC.WIDTH_12BIT | 3 | 4095 |
ADC.read()
读取ADC值并返回读取结果
RTC例程
RTC是独立的时钟,可以跟踪日期和时间
构造对象
classmachine.RTC()
创建RTC对象
初始化RTC时间
RTC.init([datetimetuple])
元组(年,月,日,周(0~6),时,分,秒,毫秒)
rtc.init((2024, 4, 17, 2, 15, 50, 40, 0))
查看RTC时间
RTC.init([datetimetuple])
未给参数则读取时间
缺陷:每过7h45min就会有秒级别的误差,所以建议每隔7小时校准一次时间
Timer例程
构造对象
class machine.Timer(id,...)
构造给定id的新计时器对象,id为任意正数
初始化定时器
Timer.init(*, mode=Timer.PERIODIC, period=-1,callback=None)
mode
是定时器模式之一Timer.ONE_SHOT
:计时器运行一次Timer.PERIODIC
:计时器运行多次
period
:定时时间,0~3 435 973 836ms
callback:回调函数
示例代码:tim.init(mode=Timer.ONE_SHOT, period=1000, callback=lambda t:print('haha))
释放定时器资源
Timer.deinit()
取消定时器初始化,停止计时器,并禁用计时器外围设备
定时器控制LED闪烁
示例代码
from machine import Timer, Pin
import utime
def toggle_led(led_pin):
# LED翻转
led_pin.value(not led_pin.value())
def led_blink_timed(timer, led_pin, freq):
"""
led按照特定的频率闪烁
LED闪烁周期 = 1000ms/2
状态变换时间间隔(period) = LED闪烁周期/2
"""
# 计算状态变换时间间隔
period = int(1000/freq/2)
#初始化定时器
# 这里回调是使用了lambda表达式,因为回调函数需要传入led_pin
timer.init(period=period, mode=Timer.PERIODIC, callback=lambda t:toggle_led(led_pin))
# 声明D4作为LED引脚
led_pin = Pin(12, Pin.OUT)
timer = Timer(0) # 创建定时器对象,实测1不好使
led_blink_timed(timer, led_pin, freq=20)
串口UART例程
合宙esp32c3
UART0_RX:08 UART0_TX:09
UART1_RX:01 UAER1_TX:00
构造对象
class machine.UART(id, baudrate, bits, parity, rx, tx, stop, timeout)
id:串口编号
bandrate:波特率
bits:数据位,默认8,可选7,9
parity:校验方式,默认None不校验,0偶校验,1奇校验
rx:接收口的GPIO编号
tx:发送口的GPIO编号
stop:停止位,默认1,可选2
timeout:超时时间,0~2 147 483 647
>>> from machine import UART
>>> u = UART(1)
>>> print(u)
UART(1, baudrate=115211, bits=8, parity=None, stop=1, tx=10, rx=9, rts=-1, cts=-1, txbuf=256, rxbuf=256, timeout=0, timeout_char=0)
方法
UART.read([nbytes])
:读字符,参数可选,为最多读取字节数,否则尽可能读取多的数据
返回值:包含读入的字节的字节对象,超时返回NoneUART.readinto(buf[, nbytes])
:将字节读入buf.
返回读取并写入到buf的字节数,超时返回NoneUART.readline()
:读一行,读到换行符结束,超时返回NoneUART.write(buf)
:串口发送数据,返回发送的字节数,超时返回NoneUART.any()
:检查是否有可读的数据,返回可读数据长度
ESP32串口通信——字符串自收发实验
将UART1的TX和RX连接起来,在合宙esp32c3中就是IO00和IO01连接
from machine import UART, Timer
import select, time
# 创建一个UART对象,将引脚0和引脚1相连
uart = UART(1, baudrate=9600, tx=01, rx=00)
# 创建一个Timer,使用中断来轮询串口是否有数据可读
timer = Timer(0)
timer.init(period=50,mode=Timer.PERIODIC, callback=lambda t:read_uart(uart))
def read_uart(uart):
if uart.any():
print('received:'+uart.read().decode()+'\n')
if __name__ == '__main__':
try:
for i in range(10):
uart.write(input('send:'))
time.sleep_ms(50)
except:
timer.deinit()
SPI例程
构造对象
硬件SPI
HSPI后遭,代码如下
>>> from machine import SPI
>>> hspi = SPI(1)
>>> print(hspi)
SPI(id=1, baudrate=500000, polarity=0, phase=0, bits=8, firstbit=0, sck=6, mosi=7, miso=2)
软件SPI
方法1:类构造
SPI(baudrate, polarity, phase, bits, firstbit, sck, mosi, miso)
baudrate:SCK时钟频率,范围0~0x0FFFFFFF(2 147 483 647)
polarity:极性,分为以下两种情况:- 0:空闲电平:底
- 1:空闲电平:高
phase:相位,分为以下两种情况: - 0:在第一时钟沿采集数据
- 1:在第二时钟沿采集数据
bits:数据位
firstbit:从地位向高位发还是从高位往低位发
sck:时钟引脚
mosi:主设备出,从设备入引脚
miso:主设备入,从设备出引脚
from machine import SPI, Pin
spi = SPI(baudrate=115200, polarity=1, phase=0, sck=Pin(17), mosi=Pin(27), miso=Pin(18))
方法2:使用init构造
SPI.init(baudrate, polarity, phase, sck, mosi, miso)
初始化SPI总线
from machine import SPI, Pin
spi = SPI.init(baudrate=115200, polarity=1, phase=0, sck=Pin(17), mosi=Pin(27), miso=Pin(18))
方法
SPI.deinit()
:关闭SPI总线SPI.read(nbytes, write=0x00)
:读取由nbytes指定的字节数,同时连续写入由write给定的单字节
(Read a number of bytes specified by nbytes while continuously writing the single byte given by write. Returns a bytes object with the data that was read.)SPI.readinto(buf, write=0x00)
:读入由buf指定的缓冲区,同时不断写入由write给定的单字节。返回读取的字节数SPI.write(buf)
:写入buf中的字节,返回写入的字节数- SPI.write_readinto(write_buf, read_buf)
从write_buf中写入字节,同时读入read_buf中。缓冲区可以是相同的,也可以不同,但是两个缓冲区都必须有相同长度,
返回写入的字节数