Wi-Fi 模块开发快速入门
概述
本文以 FCM360W 模块为例,介绍了基于 QuecPython 的 Wi-Fi 模块开发的环境搭建和网络连接的方法,以便开发者能够迅速掌握 Wi-Fi 模块的开发模式。
FCM360W 模块的特性如下:
- 主频高达240MHz的单核处理器
- 内存可用100 KB SRAM和1310 KB Flash
- 单频2.4GHz Wi-Fi6,支持AP/STA模式,符合WPA-PSK、WPA2-PSK及WPA3-PSK 安全协议标准,支持AES128位硬件加密算法。
- 外设支持16*GPIO、16*ExtInt、2*UART、1*I2C、1*SPI、1*RTC、4*Timer、1*WDT、6*PWM、3*ADC
- 网络组件支持TCP/UDP socket、MQTT、HTTP
- 支持Modem Sleep低功耗休眠
- 支持OTA差分升级
- 支持RTOS以及LittleFS2
搭建开发环境
本节主要介绍 FCM360W 模块的硬件和软件的环境搭建流程。
搭建硬件环境
必要的器件列表如下:
- FCM360W 开发板 * 1
- USB Type-C 数据线 * 1
- Windows 电脑 * 1
- 具有公网访问能力的路由器 * 1
- 如果 Wi-Fi 模块与局域网内设备通信,则对路由器公网访问的能力无要求。
- 若无路由器,使用手机开热点亦可。对于 ios 系统的手机,请尽量打开热点的兼容模式,以免 Wi-Fi 模块无法接入。
使用 USB Type-C 数据线连接 FCM360W 开发板和电脑,开发板上电后即可自动开机运行。
点此查看 FCM360W 开发板资源和使用说明文档。
搭建软件环境
必要的软件列表如下:
- USB 转串口驱动 - CH343SER
- QPYcom 集成开发工具
- FCM360W 固件包
USB 转串口驱动 - CH343SER
FCM360W 模块没有 USB 接口,但是具有两路串口。只有借助 USB 转串口的电路,FCM360W 模块才能和电脑通信,包括固件烧录、Python 命令交互、系统日志输出等。
FCM360W 开发板已经集成了 USB 转串口芯片,芯片型号为 CH342,请访问芯片官网 下载 USB 转串口芯片的驱动程序。点击 INSTALL
按钮即可一步完成驱动程序的安装。
这里特别说明下,CH342 与 CH343 芯片共用 CH343SER 驱动,以免用户在芯片名称与驱动名称不对应的事情上产生不必要的疑惑。
CH342 芯片可将一路 USB 转 2 路串口。驱动安装完毕后,我们从 Windows 电脑的设备管理器中可以看到,端口列表中存在两个名称以 USB-Enhanced-SERIAL
开头的串口设备,如下图所示:
- 串口
USB-Enhanced-SERIAL-A CH342
用于固件烧录和系统日志输出。 - 串口
USB-Enhanced-SERIAL-B CH342
用于 Python 命令交互。
值得注意的是,为了实现固件的自动烧录,USB-Enhanced-SERIAL-A CH342
端口的 DTR
引脚连接到了FCM360W 模块的复位引脚,在固件烧录前一刻,烧录工具会自动拉低和释放 DTR
引脚,使得模块复位,实现自动烧录。
因此在使用串口调试工具查看模块系统日志时,在打开串口 USB-Enhanced-SERIAL-A CH342
前,请确保 DTR
功能被禁用,而后再打开串口,以免模块在运行途中被重启,除非你就是想这么做。
QPYcom 集成开发工具
QPYcom 是专为 QuecPython 打造的集成开发环境,包含 Python 命令交互、文件传输、固件烧录、脚本执行等功能。
访问 QuecPython 官网的下载页面,在 Tools
标签页查找 QPYcom 即可下载,解压后双击 QPYcom.exe
即可直接打开使用。
点此查看 QPYcom 工具的使用教程,重点查阅固件与脚本的下载部分。
下载 FCM360W 固件
必须烧录内置了 QuecPython 运行时环境的 FCM360W 固件包,才能执行 QuecPython 脚本。
访问 QuecPython 官网,进入 FCM360W 模块的详情页的 Download
标签页,找到名为 QPY_OCPU_FCM360W_FW
的下载项,点击右侧的下载按钮,即可下载固件包。
烧录 FCM360W 固件
开始烧录之前,必须明确两件事:
- 从 QuecPython 官网直接下载的固件包为 zip 格式,必须先解压改压缩包,
QPY_OCPU_BETA0002_FCM360W_FW
文件夹下的QPY_OCPU_BETA0002_FCM360W_FW.bin
即为待烧录的文件。 - 烧录端口为
USB-Enhanced-SERIAL-A CH342
,波特率为115200
,烧录前必须先打开端口。
而后,请根据下图所示流程进行固件烧录。
软件接口介绍
在开发之前,开发者有必要对 Wi-Fi 模块网络相关的接口做整理了解。
网络连接相关的 API 介绍
网络连接相关的 API 及其阻塞特性如下表所示:
功能描述 | 方法(点击查看详情) | 阻塞特性 |
---|---|---|
创建网卡 | class network.WLAN(mode) | - |
模式切换与查询 | WLAN.mode([mode]) | - |
参数配置与查询 | WLAN.config('param' | param=value) | - |
网卡激活与查询 | WLAN.active([enable]) | 默认阻塞模式,可通过 WLAN.config 接口配置为非阻塞模式 |
网络连接 | WLAN.connect([ssid, password, bssid, timeout=15]) | 默认阻塞模式,可通过 WLAN.config 接口配置为非阻塞模式 |
断开网络连接 | WLAN.disconnect([interface, mac, ip]) | - |
网卡状态查询 | WLAN.status('param') | - |
热点扫描 | WLAN.scan([ssid, bssid, channel, passive, max_item, scan_time]) | 默认阻塞模式,可通过 WLAN.config 接口配置为非阻塞模式 |
网络地址配置与查询 | WLAN.ifconfig([interface, config]) | - |
网络配置 | WLAN.netcfg([enable=True, type=nic.NETCFG_SMARTCONFIG, timeout=120]) | 默认阻塞模式,可通过 WLAN.config 接口配置为非阻塞模式 |
上述方法的参数中:
[]
表示参数可选,如WLAN.mode([mode])
。|
表示有多种参数形式,如WLAN.config('param' | param=value)
。'param'
表示参数是字符串类型。param=value
表示关键字参数。
不管是否工作在阻塞模式,所有 Wi-Fi 相关的事件均通过调用回调函数的方式通知给用户。
在创建网卡之后,强烈建议用户通过 WLAN.config
方法配置事件回调函数,否则会丢失 Wi-Fi 相关的事件。
Wi-Fi 连接过程中错误码、事件码、状态码
- 错误码:参考 WLAN - 无线网络控制 - 错误码。
- 事件码:参考 WLAN - 无线网络控制 - 事件码。
- 状态码:参考 WLAN - 无线网络控制 - 状态码。
网络连接的基础流程
Wi-Fi 在嵌入式行业的应用场景基本分为三类:STATION 模式、AP 模式及 STATION 与 AP 共存模式。
- STATION 模式用于接入 Wi-Fi 热点;
- AP 模式用于产生 Wi-Fi 热点,接入其他的 Wi-Fi 设备;
- STATION 与 AP 共存的模式既能接入 Wi-Fi 网络,又能产生 Wi-Fi 热点并接入其他 Wi-Fi 设备的连接。
鉴于目前 STATION 与 AP 共存的模式尚未良好地支持,本节将分别讲述 Wi-Fi 模块分别工作在 STATION 模式和 AP 模式时的网络连接流程。
连接 Wi-Fi 热点
整体流程如下:
- 准备一台 2.4GHz 的 Wi-Fi 路由器。
- 参考搭建开发环境章节,搭建好必要的软硬件开发环境。
- 连接 Wi-Fi 路由器的热点:
a. 创建网卡。
b. 连接路由器。
代码片段如下:
import network
# 创建 Wi-Fi 网卡,并设置为 STATION 模式
nic = network.WLAN(network.STA_MODE)
print('- Created a Wi-Fi NIC.')
# 定义 Wi-Fi 事件回调函数
def wifi_event_cb(event):
# 打印事件信息
print("- Event:\r\n ", event)
# 当获取到 IP 地址时,打印 IP 地址相关信息
if event['id'] == 3305:
print("- Got IP:\r\n ", nic.ifconfig())
# 设置事件回调函数
nic.config(event_callback = wifi_event_cb)
# 连接热点
SSID = "Quectel-Customer-2.4G"
PASSWD = "Customer-Quectel"
print('- Connecting to', SSID)
nic.connect(ssid = SSID, password = PASSWD)
代码执行结果如下:
- Created a Wi-Fi NIC.
- Connecting to Quectel-Customer-2.4G
- Event:
{'msg': None, 'type': 3300, 'id': 3301}
- Event:
{'msg': {'password': 'Customer-Quectel', 'ssid': 'Quectel-Customer-2.4G', 'rssi': -62, 'channel': 1, 'bssid': 'a4:00:e2:ef:f7:80', 'auth': 4, 'cipher': 4}, 'type': 3300, 'id': 3302}
- Event:
{'msg': ('10.66.117.73', '255.255.252.0', '10.66.116.1', '0.0.0.0', '0.0.0.0'), 'type': 3300, 'id': 3305}
- Got IP:
('10.66.117.73', '255.255.252.0', '10.66.116.1', '211.138.180.2', '114.114.114.114')
产生 Wi-Fi 热点并接入设备连接
整体流程如下:
- 准备一台支持 2.4GHz Wi-Fi 的手机(几乎所有的手机都支持)。
- 参考搭建开发环境章节,搭建好必要的软硬件开发环境。
- 让 Wi-Fi 模块产生热点
a. 创建网卡。
b. 产生热点。
代码片段如下:
import network
# 创建 Wi-Fi 网卡,并设置为 AP 模式
nic = network.WLAN(network.AP_MODE)
print('- Created a Wi-Fi NIC.')
# 定义 Wi-Fi 事件回调函数
def wifi_event_cb(event):
if event['id'] == 3201: # 热点创建成功
print("- AP is created successfully.")
elif event['id'] == 3202: # 热点创建失败
print("- AP is created failed.")
elif event['id'] == 3203: # 接入了设备连接
print("- AP accepted a device joining.")
elif event['id'] == 3204: # 与接入的设备断开连接
print("- AP disconnected from a joined device.")
elif event['id'] == 3205: # 为接入的设备分配了 IP 地址
print("- AP assigned IP address to the joined device.")
else:
print("- Other event occured.")
# 打印事件信息
print(" Event:", event)
# 设置事件回调函数
nic.config(event_callback = wifi_event_cb)
# 设置热点名称和密码
# 如果不设置热点名称和密码,则默认的热点名称和密码均为: quecpython
SSID = "QuecPython_SoftAP"
PASSWD = "12345678"
print("- Will create a AP:")
print(" SSID:", SSID)
print(" PASSWD:", PASSWD)
nic.config(ap_ssid = SSID, ap_password = PASSWD)
# 激活网卡,触发热点的产生
nic.active(True)
用手机搜索名称为 QuecPython_SoftAP 的热点,连接成功后,再断开连接,则上述代码的执行结果如下:
- Created a Wi-Fi NIC.
- Will create a AP:
SSID: QuecPython_SoftAP
PASSWD: 12345678
- AP is created successfully.
Event: {'msg': None, 'type': 3200, 'id': 3201}
- AP accepted a device joining.
Event: {'msg': {'mac': 'd6:d7:f0:b0:a3:bf', 'aid': 1}, 'type': 3200, 'id': 3203}
- AP assigned IP address to the joined device.
Event: {'msg': {'ip': '10.10.10.2', 'aid': 1, 'mac': 'd6:d7:f0:b0:a3:bf'}, 'type': 3200, 'id': 3205}
- AP disconnected from a joined device.
Event: {'msg': {'ip': '10.10.10.2', 'aid': 1, 'mac': 'd6:d7:f0:b0:a3:bf'}, 'type': 3200, 'id': 3204}
网络应用开发流程
在开始之前,需要先做个声明:在 Wi-Fi 模块中做基于 TCP 协议的应用开发时,模块做客户端还是服务器,与模块处于 STATION 模式还是 AP 模式是没关系的。
为了方便案例演示,本节分别演示两个功能的实现:
- 在 STATION 模式下进行 TCP 客户端的开发,实现功能为:
连接 TCP echo 服务器,周期性向服务器发送数据并接收其回显的数据;重复 10 次后,断开与服务器的连接,结束程序运行。
- 在 AP 模式下进行 TCP 服务器的开发,实现功能为:
接受 TCP 客户端的连接,并回发其接收到的 TCP 客户端的数据。
STATION 模式下 TCP 客户端的功能开发
软件框架
有了前面连接 Wi-Fi 热点的基础,基于本案例,我们需要在上述代码的基础上做些改动:
- 将 API 设置为非阻塞模式。
- 将 Wi-Fi 事件发送到消息队列,从队列中取出消息进行处理。
- 编写一个名为
tcp_echo_client
的函数,用以实现 TCP 客户端的功能。
完整的软件框架如下:
import network
from queue import Queue
# 创建消息队列
msg_q = Queue(8)
# 创建 Wi-Fi 网卡,并设置为 STATION 模式
nic = network.WLAN(network.STA_MODE)
print('- Created a Wi-Fi NIC.')
# 设置为非阻塞模式
nic.config(block = False)
# 定义 Wi-Fi 事件回调函数
def wifi_event_cb(event):
# 打印事件信息
print("- Event:", event)
# 发送消息到队列
msg_q.put(event)
# 设置事件回调函数
nic.config(event_callback = wifi_event_cb)
# 连接热点
SSID = "Quectel-Customer-2.4G"
PASSWD = "Customer-Quectel"
print('- Connecting to', SSID)
nic.connect(ssid = SSID, password = PASSWD)
# TCP 客户端函数
def tcp_echo_client(server_addr, server_port):
pass
while True:
# 等待队列中的消息
event = msg_q.get()
if event['id'] == 3302: # 成功连接到热点
print("- Connected to AP.")
elif event['id'] == 3305: # 当获取到 IP 地址时,打印 IP 地址相关信息,并启动 TCP 客户端
print("- Got IP:", nic.ifconfig())
print("- Start TCP client.")
# TCP 服务器的地址和端口分别为 `112.31.84.164` 和 `8305`
tcp_echo_client('112.31.84.164', 8305)
TCP 客户端代码编写
示例代码如下:
def tcp_echo_client(server_addr, server_port):
import usocket
import utime
# DNS 解析
print('- DNS resolving for', server_addr, ":", server_port)
addr = usocket.getaddrinfo(server_addr, server_port)[0][-1]
# 创建 socket 对象
sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP)
print('- Socket object created.')
# 连接服务器
print('- Connecting to', addr)
sock.connect(addr)
print('- TCP link established.')
# 数据收发
for i in range(10):
data = "Hello world!"
sock.send(data)
print("- - C --> S:", data)
data = sock.recv(1024)
print("- - S --> C:", data)
utime.sleep(1)
# 断开连接
sock.close()
print('- TCP link disconnected.')
软件功能验证
功能验证步骤如下:
- 软件框架中的
tcp_echo_client
函数按照上述代码进行实现,形成完成的应用代码。 - 将代码保存为 Python 脚本文件,并通过 QPYcom 将其导入至 Wi-Fi 模块,并执行脚本。
该功能运行的日志如下:
- Created a Wi-Fi NIC.
- Connecting to Quectel-Customer-2.4G
- Event: {'msg': None, 'type': 3300, 'id': 3301}
- Event: {'msg': {'password': 'Customer-Quectel', 'ssid': 'Quectel-Customer-2.4G', 'rssi': -61, 'channel': 1, 'bssid': 'a4:00:e2:ef:f7:80', 'auth': 4, 'cipher': 4}, 'type': 3300, 'id': 3302}
- Connected to AP.
- Event: {'msg': ('10.66.117.73', '255.255.252.0', '10.66.116.1', '0.0.0.0', '0.0.0.0'), 'type': 3300, 'id': 3305}
- Got IP: ('10.66.117.73', '255.255.252.0', '10.66.116.1', '211.138.180.2', '114.114.114.114')
- Start TCP client.
- DNS resolving for 112.31.84.164 : 8305
- Socket object created.
- Connecting to ('112.31.84.164', 8305)
- TCP link established.
- C --> S: Hello world!
- S --> C: b'Hello world!'
- C --> S: Hello world!
- S --> C: b'Hello world!'
- C --> S: Hello world!
- S --> C: b'Hello world!'
- C --> S: Hello world!
- S --> C: b'Hello world!'
- C --> S: Hello world!
- S --> C: b'Hello world!'
- C --> S: Hello world!
- S --> C: b'Hello world!'
- C --> S: Hello world!
- S --> C: b'Hello world!'
- C --> S: Hello world!
- S --> C: b'Hello world!'
- C --> S: Hello world!
- S --> C: b'Hello world!'
- C --> S: Hello world!
- S --> C: b'Hello world!'
- TCP link disconnected.
AP 模式下 TCP 服务器的功能开发
软件框架
有了前面产生 Wi-Fi 热点并接入设备连接的基础,基于本案例,我们需要在上述代码的基础上做些改动:
- 将 API 设置为非阻塞模式。
- 将 Wi-Fi 事件发送到消息队列,从队列中取出消息进行处理。
- 编写一个名为
tcp_echo_server
的函数,用以实现 TCP 服务器的功能。
完整的软件框架如下:
import network
from queue import Queue
# 创建消息队列
msg_q = Queue(8)
# 创建 Wi-Fi 网卡,并设置为 AP 模式
nic = network.WLAN(network.AP_MODE)
print('- Created a Wi-Fi NIC.')
# 设置为非阻塞模式
nic.config(block = False)
# 定义 Wi-Fi 事件回调函数
def wifi_event_cb(event):
# 打印事件信息
print("- Event:", event)
# 发送消息到队列
msg_q.put(event)
# 设置事件回调函数
nic.config(event_callback = wifi_event_cb)
# 设置热点名称和密码
# 如果不设置热点名称和密码,则默认的热点名称和密码均为: quecpython
SSID = "QuecPython_SoftAP"
PASSWD = "12345678"
print("- Will create a AP:")
print(" SSID:", SSID)
print(" PASSWD:", PASSWD)
nic.config(ap_ssid = SSID, ap_password = PASSWD)
# 激活网卡,触发热点的产生
nic.active(True)
# TCP 服务器函数
def tcp_echo_server(server_addr, server_port):
pass
while True:
# 等待队列中的消息
event = msg_q.get()
if event['id'] == 3201: # 热点创建成功
print("- AP is created successfully.")
print("- Start TCP server.")
# 读取模块本地的 IP 地址
local_addr = nic.ifconfig()[0]
# 启动 TCP 服务器
tcp_echo_server(local_addr, 8080)
elif event['id'] == 3202: # 热点创建失败
print("- AP is created failed.")
elif event['id'] == 3203: # 接入了设备连接
print("- AP accepted a device joining.")
elif event['id'] == 3204: # 与接入的设备断开连接
print("- AP disconnected from a joined device.")
elif event['id'] == 3205: # 为接入的设备分配了 IP 地址
print("- AP assigned IP address to the joined device.")
else:
print("- Other event occured.")
TCP 服务器代码编写
示例代码如下:
def tcp_echo_server(server_addr, server_port):
import usocket
import _thread
# 服务器与每一个客户端连接的处理线程
def _client_conn_proc(conn, ip_addr, port):
while True:
try:
# 接收客户端发送的数据
data = conn.recv(1024)
print('- Recv data: [client addr: %s, %s]:' % (ip_addr, port), data)
# 将数据回发给客户端
conn.send(data)
except:
# 出现异常,关闭连接
print('- Disconnected from client: %s, %s' % (ip_addr, port))
conn.close()
break
# 创建 socket 对象
sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP_SER)
print('- Socket object created.')
# 绑定服务器端口与地址
sock.bind((server_addr, server_port))
print('- Bind address: %s, %s' % (server_addr, server_port))
# 监听客户端连接请求
sock.listen(10)
print('- Start listening ...')
while True:
# 等待接受客户端连接
cli_conn, cli_ip_addr, cli_port = sock.accept()
print('- Accept a client: %s, %s' % (cli_ip_addr, cli_port))
# 为每一个客户端连接创建一个新线程
_thread.start_new_thread(_client_conn_proc, (cli_conn, cli_ip_addr, cli_port))
软件功能验证
验证该功能,需要准备一台能够连接 Wi-Fi 热点的电脑,并且能够在电脑上运行 TCP 客户端。
本案例在电脑上运行了一个名为 NetAssist.exe 的网络调试助手。
功能验证步骤如下:
软件框架中的
tcp_echo_server
函数按照上述代码进行实现,形成完成的应用代码。将代码保存为 Python 脚本文件,并通过 QPYcom 将其导入至 Wi-Fi 模块,并执行脚本。
此时,Wi-Fi 模块会创建热点,并启动 TCP 服务器,日志如下:
- Created a Wi-Fi NIC. - Will create a AP: SSID: QuecPython_SoftAP PASSWD: 12345678 - Event: {'msg': None, 'type': 3200, 'id': 3201} - AP is created successfully. - Start TCP server. - Socket object created. - Bind address: 10.10.10.1, 8080 - Start listening ...
将电脑连接至 Wi-Fi 模块产生的热点:
QuecPython_SoftAP
。此时,模块输出的日志如下:
- Event: {'msg': {'mac': 'c4:75:ab:bc:a5:ac', 'aid': 1}, 'type': 3200, 'id': 3203} - Event: {'msg': {'ip': '10.10.10.2', 'aid': 1, 'mac': 'c4:75:ab:bc:a5:ac'}, 'type': 3200, 'id': 3205}
启动网络调试助手 NetAssist.exe,并连接到在模块中启动的服务器:
10.10.10.1, 8080
。此时,模块输出的日志如下:
- Accept a client: 10.10.10.2, 51434
在网络调试助手的数据发送区域输入字符串
Hello world!
,并连续点击发送
按钮,向服务器不断发送数据。此时,模块输出的日志如下:
- Recv data: [client addr: 10.10.10.2, 51434]: b'Hello world!' - Recv data: [client addr: 10.10.10.2, 51434]: b'Hello world!' - Recv data: [client addr: 10.10.10.2, 51434]: b'Hello world!' - Recv data: [client addr: 10.10.10.2, 51434]: b'Hello world!' - Recv data: [client addr: 10.10.10.2, 51434]: b'Hello world!' - Recv data: [client addr: 10.10.10.2, 51434]: b'Hello world!' - Recv data: [client addr: 10.10.10.2, 51434]: b'Hello world!' - Recv data: [client addr: 10.10.10.2, 51434]: b'Hello world!' - Recv data: [client addr: 10.10.10.2, 51434]: b'Hello world!' - Recv data: [client addr: 10.10.10.2, 51434]: b'Hello world!'
同时,每次发送数据后,网络调试助手的数据接收区域会回显发送的数据(蓝色字体),并显示其接收到的服务器回发的数据(绿色字体)。
网络调试助手的运行效果如下图所示:
如果你手上有两个 FCM360W Wi-Fi 模块,则可以按照下面的步骤进行测试,而不依赖电脑。
- 先在其中一个模块中以 AP 模式运行 TCP 服务器。
- 在前一个模块的 TCP 服务器启动完毕后,另一个模块作为 STATION,连接前一个模块产生的热点,并启动 TCP 客户端。
STATION 模式下的 TCP 客户端代码有两点需要注意:
- 待连接的热点的
SSID
和PASSWD
需要和前一个模块产生的热点保持一致,这一点很容易保证,提前约定好即可。- 待连接的服务器的 IP 地址和端口,需要和前一个模块启动的 TCP 服务器保持一致。
- 默认情况下,AP 模式的 IP 地址为
10.10.10.1
。- 如果 AP 模式的模块修改了其 IP 地址和服务器端口,可以在日志中看到,客户端做对应修改即可。