基于 PlatformIO 平台玩转 NodeMCU 入门篇

前言

近期关注到《物联网开发实战》课程,买了一套极客商城的开发套件,打算把自己业余时间学习过程中的一些收获记录一下。之前学习 Gokit3 开发板的过程中是在 Windows 系统上从零开始搭建编译环境的,整体的开发体验上感觉还是比较麻烦琐碎,不过对 Flash 下载固件有了一些基本认知,这次打算在 MacOS 平台基于 PlatformIO IDE 进行尝试。

PlatformIO 简介

PlatformIO 简介

PlatformIO 是专业的嵌入式开发协作平台,支持 924 个的嵌入式设备,41 个的开发平台,23 个的框架。Arduino, ARM mbed, Espressif (ESP8266/ESP32), RISC-V, STM32, FPGA, FreeRTOS 等都在 PlatformIO 的支持范围内。PlatformIO 在 Atom, Subliem Text, Vim, VS Code, Eclipse 等开发工具上都有支持,而 VS Code 的 PlatformIO IDE 是其主力推荐的开发环境。智能的代码提示和 Linter,代码库管理,统一的代码调试体验,内置的 PlatformIO Terminal 等等强大的功能,使得不管你开发任何一个设备,都能得到相近的开发体验。

在使用 PlatformIO 之前我们先来了解一些基本概念。

Platform

平台,指的是芯片平台,具体来说就是芯片公司推出的系列芯片的开发平台。PlatformIO 支持嵌入式和桌面两种类型的开发平台,提供对各平台预先构建的工具链,调试器,上传器和框架,支持在 Mac、Linux (+ARM) 和 Windows 上开发。

支持的嵌入式平台包括:

Atmel AVR 一般指的就是 Arduino 系列开发板使用的芯片。之前大学的时候基于 TI 的 MSP430 做过智能小车和电子设计大赛,现在关于嵌入式的基础认知就是那个时候建立的。很多人入门 IoT 都会选择 ESP8266 平台,本文实战也会以 ESP8266 平台为例说明。

Frameworks

框架,指的是开发框架,通俗来讲就是 SDK,一般会由官方或第三方提供一个软件库。目前支持的框架包括:

因为 Arduino 极具影响力,所以很多芯片平台也都有了自己的 Arduino 框架,现在 Arduino 框架平台支持的平台有很多,比如 Atmel AVR、Atmel SAM、Espressif 32、Espressif 8266、Infineon XMC、Intel ARC32、Kendryte K210、Microchip PIC32、Nordic nRF51、Nordic nRF52、T STM32、ST STM8 、Teensy、TI MSP430 、TI TIVA。

反过来讲,一个平台有可能支持多个不同开发框架。例如 ESP8266 平台支持的框架有:ESP8266 Non-OS SDKESP8266 RTOS SDKArduinoSimba 等。ESP8266 官方的 SDK 按照是否基于操作系统可分为:Non-OS 和 RTOS 两种版本。

RTOS SDK 是 Non-OS 的新版本,新版 ESP8266_RTOS_SDK 可帮助客户避免对单一 SDK 的依赖,允许客户应用程序同时兼容多款乐鑫芯片,包括 ESP8266 系列、ESP32 系列以及未来发布的新产品。

Boards

开发版,PlatformIO 支持绝大部分流行的开发板,比如 Arduino 的全系开发板、NodeMCU 的全系列开发板,STM32 的 Nucleo 和 Discovery 系列评估板。PlatformIO 针对支持的开发板都提供完整的项目生成脚本,新建项目的时候选择对应的开发板和框架即可完成工程初始化。

这里我们类比一下 Android 生态来总结一下上述的三个概念:Platform 类比于 Android 平台,Frameworks 类比 Android 系统上层的 SDK 及框架,Boards 类似于基于 Android 的移动设备,例如华为、小米等厂商的各系列手机、Pad 之类的设备。

NodeMCU 开发板

我手上拿到是下面这款安信可 NodeMCU ESP8266 的开发板,该开发板延续了 NodeMCU 1.0 的设计,搭建了 ESP8266 ESP-12D 模组及 CP2102 驱动。NodeMCU 极大的简化 ESP8266 刷写固件的接线方式,直接即插即用。详细参数可以参考 NodeMCU-8266 规格书

NodeMCU

乐鑫 ESP8266 芯片官网:https://www.espressif.com/zh-hans/products/socs/esp8266

ESP8266 是一款面向物联网应用的 WiFi MCU,具有以下特点:

ESP8266

环境准备

1.下载 VS Code IDE,然后在 VS Code 拓展市场安装 PlatformIO IDE 拓展。

VS Code 插件

2.CP2102 USB 转 UART 驱动:https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers

下载完成后可以在 关于本机 / 系统报告 中查看:

新建工程

这里我以 NodeMCU 1.0 开发板选择 ESP8266 RTOS SDK 框架为例加以说明。

选择好参数、设置好项目名称之后就可以点击 Finish 完成了,然后等待第一次自动配置环境。自动生成的工程目录结构如下:

├── .pio            # 存放工程编译产生的文件
	└── build
		├── nodemcuv2
		└── project.checksum
├── .vscode         # 存放针对工程定制化的 vscode 配置文件
	├── c_cpp_properties.json
	├── extensions.json
	└── launch.json
├── include         # 存放统一管理的 h 头文件
├── lib             # 存放自己编写的库文件
├── src             # 存放工程项目的 C/C++ 源文件
├── test            # 存放工程项目的测试文件
└── platformio.ini  # 项目的核心配置文件,需要了解用到的配置项

c_cpp_properties.json 是 VS Code C/C++ 工程配置文件,c_cpp_properties.json 中定义了工程依赖的库及交叉编译工具 xtensa-lx106-elf-gcc 的路径。我们可以看到工程主要用到了两个包:

  • framework-esp8266-rtos-sdk:RTOS SDK 框架文件,位于 ~/.platformio/packages/framework-esp8266-rtos-sdk 目录
  • toolchain-xtensa:交叉编译工具链,位于 ~/.platformio/packages/toolchain-xtensa@1.40802.0

launch.json 是工程调试配置文件,具体文档可以参考:Debugging

我们看一下 ~/.platformio 的目录结构如下:

├── appstate.json
├── homestate.json
├── packages        # 平台依赖的包
	├── framework-esp8266-rtos-sdk
	├── tool-esptool
	├── tool-esptoolpy
	├── tool-mklittlefs
	├── toolchain-xtensa
	...
├── penv             # Python版本管理工具
├── platforms        # 本地缓存的平台文件
	├── espressif8266
    ...
└── python3          # Python3 环境

新建的项目是空白的项目,我们可以从 SDK 文件夹拷贝一份代码示例进行修改。我们可以 packages/framework-esp8266-rtos-sdk/examples 或 platforms/espressif8266/examples 中拷贝一份示例工程开始。

下面我将以 wifi_station_machine_demo 为例说明。

工程配置

我们从 wifi_station_machine_demo 中将 user/user_main.c 和 user/wifi_state_machine.c 拷贝到工程的 src 目录下,将 include/user_config.h 和 include/wifi_state_machine.h 拷贝到 include 目录下。修改 include/user_config.h 中的 SSID 和 PASSWORD,并修改 platformio.ini 配置文件。

platformio.ini

platformio.ini 配置文件:

[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = esp8266-rtos-sdk
monitor_speed = 74880
monitor_port = /dev/cu.SLAB_USBtoUART

参数说明:

  • monitor_speed:监视器波特率设置为 74880
  • monitor_port:监视器端口设置,设置为连接的串口端口

监视器波特率

ESP8266_RTOS_SDK 默认使用 UART0 打印调试信息,默认波特率为 74880。我们也可以使用下面的方法修改成 115200。

#include "uart.h"

...

void user_init(void)
{
    UART_ConfigTypeDef  uart_config;
    uart_config.baud_rate               =   BIT_RATE_115200;
    uart_config.data_bits               =   UART_WordLength_8b;
    uart_config.parity                  =   USART_Parity_None;
    uart_config.stop_bits               =   USART_StopBits_1;
    uart_config.flow_ctrl               =   USART_HardwareFlowControl_None;
    uart_config.UART_RxFlowThresh       =   120;
    uart_config.UART_InverseMask        =   UART_None_Inverse;
    UART_ParamConfig(UART0, &uart_config);
}

查看串口端口

Mac OS 系统查看串口端口的两种方法。

1.通用方法

ls /dev/cu.*

串口 port 为 /dev/cu.SLAB_USBtoUART。

2.使用 pio device list 命令

打开 PlatformIO CLI,输入 pio device list 命令可以获取连接的串口信息。

代码烧写

代码构建

我们修改完代码后,可以点击 Project Tasks 中的 Build 任务或点击状态栏的图标进行构建。构建的过程是编译代码,工程 .pio/build/nodemcuv2 目录下的文件就是编译生成的产物。

固件上传

上传的过程就是将编译产物(也称固件)上传到我们的开发板上。

串口监视器

我们可以直接在 IDE 端打开串口调试器查看日志信息,不需要额外的串口调试工具即可查看日志还是比较方便的。注意需要按一下 RST 按键重启模组。

代码分析

ESP8266 工作模式

ESP8266 利用 WiFi 联网时有三种工作模式。

  • Station 模式:工作在 Station 模式下的 ESP8266 就像是一个接收机一样,它可以接收来自无线路由器发出的信号。ESP8266 模块通过路由器连接互联网,手机或电脑通过互联网实现对设备的远程控制。

  • SoftAP 模式:无线接入点的简称,ESP8266 模块作为热点,实现手机或电脑直接与模块通信,实现局域网无线控制。

  • Station + SoftAP 模式:两种模式共存,既可以通过路由器连接到互联网,也可以作为 WiFi 热点,使其他设备连接到模块,实现广域网与局域网的无缝切换。

WiFi 接口核心函数

RTOS SDK API 基本兼容 Non-OS SDK API,可以参考:ESP8266 Non-OS SDK API 手册

示例采用固定 WiFi SSID 和密码的方式连接,主要用到的函数为:

软件定时器核心函数

wifi_station_machine_demo 分析

ESP8266 物联网平台的所有网络功能均在库中实现,对用户来说是不透明。用户应用的初始化功能可以在 user_main.c 中实现。void user_init(void) 是上层程序的入口函数,给用户提供一个初始化接口,用户可在该函数内增加硬件初始化、网络参数设置、定时器器初始化等功能。 示例中 ESP8266 RTOS SDK 使用的版本是 1.5.0-beta.5,接下来我将分析一下示例代码的关键逻辑。

示例代码的整体逻辑为:

  1. 初始化 station 建立连接和断开连接的回调函数
  2. 设置 ESP8266 的工作模式为 Station 模式
  3. 设置 WiFi 的 SSID 和 密码
  4. 开始连接 WiFi
  5. 监听 station 连接,检查 WiFi 连接状态
/******************************************************************************
 * FunctionName : user_init
 * Description  : entry of user application, init user function here
 * Parameters   : none
 * Returns      : none
*******************************************************************************/
void user_init(void)
{
    // 打印 ESP8266 RTOS SDK 版本
    printf("SDK version:%s\n", system_get_sdk_version());
	// 监听 station 连接
    set_on_station_connect(on_wifi_connect);
    // 监听 station 断开连接
    set_on_station_disconnect(on_wifi_disconnect);
    // 初始化 WiFi 模块 
    init_esp_wifi();
    // 停止 WiFi AP 模式
    stop_wifi_ap();
    // 启动 WiFi station 模式
    start_wifi_station(SSID, PASSWORD);
}

我们可以看到 user_init 中代码逻辑非常简单清晰,除了 system_get_sdk_version 是 SDK 的 API,其他都是 wifi_state_machine.c 驱动中定义的方法。上面的代码开启了 ESP8266 的 Station 模式,从而可以通过路由连接互联网。

代码中有几处代码值得说一下:

  1. 使用 &= ~ 位运算关闭 SoftAP 模式,使用 |= 位运算开启 Station 模式
// 关闭 SOFTAP_MODE
WIFI_MODE mode = wifi_get_opmode();
mode &= ~SOFTAP_MODE;
if(!wifi_set_mode(mode)){
	os_printf("Failed to disable AP mode!\n");
	return false;
}

// 开启 STATION_MODE
WIFI_MODE mode = wifi_get_opmode();
if((mode & STATION_MODE) == 0){
	mode |= STATION_MODE;
	if (!wifi_set_mode(mode)){
		os_printf("Failed to enable Station mode!\n");
		return false;
	}
}
  1. 使用软件定时器检查 WiFi 连接状态
LOCAL void ICACHE_FLASH_ATTR wait_for_connection_ready(uint8 flag)
{
    os_timer_disarm(&timer);
    if(wifi_station_connected()){
        os_printf("connected\n");
    } else {
        os_printf("reconnect after 2s\n");
        os_timer_setfn(&timer, (os_timer_func_t *)wait_for_connection_ready, NULL);
        os_timer_arm(&timer, 2000, 0);
    }
}

LOCAL void ICACHE_FLASH_ATTR on_wifi_connect(){
    // 取消定时器定时
    os_timer_disarm(&timer);
    // 设置定时器回调函数,使⽤定时器,必须设置回调函数。
    os_timer_setfn(&timer, (os_timer_func_t *)wait_for_connection_ready, NULL);
    // 使能毫秒级定时器
    os_timer_arm(&timer, 100, 0);
}

软件定时器由软件实现,定时器的函数在任务中被执⾏。因为任务可能被中断,或者被 其他⾼优先级的任务延迟,因此以下 os_timer 系列的接⼝并不能保证定时器精确执⾏。 如果需要精确的定时,例如,周期性操作某 GPIO,请使⽤硬件中断定时器,具体可参考 hw_timer.c,硬件定时器的执⾏函数在中断⾥被执⾏。

注意:

  • 对于同⼀个 timer,os_timer_arm 或 os_timer_arm_us 不能重复调⽤,必须先 os_timer_disarm;
  • os_timer_setfn 必须在 timer 未使能的情况下调⽤,在 os_timer_arm 或 os_timer_arm_us 之前或者 os_timer_disarm 之后。

后记

本文行文至此,我们基于 PlatformIO 平台在 NodeMCU 运行了一个示例代码,对 ESP8266 三种工作模式有一个基本认识,基本掌握 Station 模式的使用流程,也初步掌握软件定时器的用法。后续会继续围绕 ESP8266,并结合具体的传感器完成实战的开发。

参考

写这些代码也许就一两个小时的事,写一篇大家好接受的文章需要几天的酝酿,如果文章对您有帮助请我喝杯咖啡吧!