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 按钮即可一步完成驱动程序的安装。

CH343SER_Driver.png

这里特别说明下,CH342 与 CH343 芯片共用 CH343SER 驱动,以免用户在芯片名称与驱动名称不对应的事情上产生不必要的疑惑。

CH342 芯片可将一路 USB 转 2 路串口。驱动安装完毕后,我们从 Windows 电脑的设备管理器中可以看到,端口列表中存在两个名称以 USB-Enhanced-SERIAL 开头的串口设备,如下图所示:

DeviceManager_Port

  • 串口 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_download.png

点此查看 QPYcom 工具的使用教程,重点查阅固件与脚本的下载部分。

下载 FCM360W 固件

必须烧录内置了 QuecPython 运行时环境的 FCM360W 固件包,才能执行 QuecPython 脚本。

访问 QuecPython 官网,进入 FCM360W 模块的详情页Download 标签页,找到名为 QPY_OCPU_FCM360W_FW 的下载项,点击右侧的下载按钮,即可下载固件包。

FCM360W_FW_Download.png

烧录 FCM360W 固件

开始烧录之前,必须明确两件事:

  1. 从 QuecPython 官网直接下载的固件包为 zip 格式,必须先解压改压缩包,QPY_OCPU_BETA0002_FCM360W_FW 文件夹下的 QPY_OCPU_BETA0002_FCM360W_FW.bin 即为待烧录的文件。
  2. 烧录端口为 USB-Enhanced-SERIAL-A CH342,波特率为 115200,烧录前必须先打开端口。

而后,请根据下图所示流程进行固件烧录。

FCM360W_FW_FLASH.png

软件接口介绍

在开发之前,开发者有必要对 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 连接过程中错误码、事件码、状态码

网络连接的基础流程

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 热点

整体流程如下:

  1. 准备一台 2.4GHz 的 Wi-Fi 路由器。
  2. 参考搭建开发环境章节,搭建好必要的软硬件开发环境。
  3. 连接 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 热点并接入设备连接

整体流程如下:

  1. 准备一台支持 2.4GHz Wi-Fi 的手机(几乎所有的手机都支持)。
  2. 参考搭建开发环境章节,搭建好必要的软硬件开发环境。
  3. 让 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.')

软件功能验证

功能验证步骤如下:

  1. 软件框架中的 tcp_echo_client 函数按照上述代码进行实现,形成完成的应用代码。
  2. 将代码保存为 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 的网络调试助手。

功能验证步骤如下:

  1. 软件框架中的 tcp_echo_server 函数按照上述代码进行实现,形成完成的应用代码。

  2. 将代码保存为 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 ...
    
  3. 将电脑连接至 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}
    
  4. 启动网络调试助手 NetAssist.exe,并连接到在模块中启动的服务器:10.10.10.1, 8080

    此时,模块输出的日志如下:

    - Accept a client: 10.10.10.2, 51434
    
  5. 在网络调试助手的数据发送区域输入字符串 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!'
    

    同时,每次发送数据后,网络调试助手的数据接收区域会回显发送的数据(蓝色字体),并显示其接收到的服务器回发的数据(绿色字体)。

    网络调试助手的运行效果如下图所示:

    NetAssist.png

如果你手上有两个 FCM360W Wi-Fi 模块,则可以按照下面的步骤进行测试,而不依赖电脑。

  1. 先在其中一个模块中以 AP 模式运行 TCP 服务器。
  2. 在前一个模块的 TCP 服务器启动完毕后,另一个模块作为 STATION,连接前一个模块产生的热点,并启动 TCP 客户端。

STATION 模式下的 TCP 客户端代码有两点需要注意:

  • 待连接的热点的 SSIDPASSWD 需要和前一个模块产生的热点保持一致,这一点很容易保证,提前约定好即可。
  • 待连接的服务器的 IP 地址和端口,需要和前一个模块启动的 TCP 服务器保持一致。
    • 默认情况下,AP 模式的 IP 地址为 10.10.10.1
    • 如果 AP 模式的模块修改了其 IP 地址和服务器端口,可以在日志中看到,客户端做对应修改即可。