ESP8266 硬件
乐鑫 ESP8266 芯片官网:https://www.espressif.com/zh-hans/products/socs/esp8266
ESP8266 是一款面向物联网应用的 WiFi MCU,具有以下特点:
Tensilica 处理器
ESP8266 集成的是 Tensilica L106 32 bit 处理器,Tensilica 公司创立的目的是提供一种可以实现可重构的、核基于 ASIC 的、拥有对应软件开发工具的专用微处理器解决方法。Tensilica 和 ARM IP 的一大区别是可定制化,因此乐鑫在购买 IP 后可以继续优化,形成自己产品的差异化优势。而传统购买 ARM IP 的公司的产品性能都差不多。
ESP8266 芯片管脚
管脚布局如下图所示:
管脚定义如下表所示:
管脚 | 名称 | 类型 | 功能 |
---|---|---|---|
1 | VDDA | P | 模拟电源 2.5 V ~ 3.6 V |
2 | LNA | I/O | 射频天线接口,芯片输出阻抗为 39 + j6 Ω。建议保留 π 型匹配网络对天线进行匹配。 |
3 | VDD3P3 | P | 功放电源 2.5 V ~ 3.6 V |
4 | VDD3P3 | P | 功放电源 2.5 V ~ 3.6 V |
5 | VDD_RTC | P | NC (1.1 V) |
6 | TOUT | I | ADC 端⼝(注:芯⽚内部 ADC 端⼝),可⽤于检测 VDD3P3 (Pin3, Pin4) 电源电压和 TOUT (Pin6) 的输⼊电压(⼆者不可同时使⽤)。 |
7 | CHIP_EN | I | 芯片使能端。 高电平:有效,芯片正常工作;低电平:芯片关闭,电流很小 |
8 | XPD_DCDC | I/O | 深度睡眠唤醒;GPIO16 |
9 | MTMS | I/O | GPIO14;HSPI_CLK |
10 | MTDI | I/O | GPIO12;HSPI_MISO |
11 | VDDPST | P | 数字/IO 电源 (1.8 V ~ 3.6 V) |
12 | MTCK | I/O | GPIO13;HSPI_MOSI;UART0_CTS |
13 | MTDO | I/O | GPIO15;HSPI_CS;UART0_RTS |
14 | GPIO2 | I/O | 可⽤作烧写 Flash 时 UART1_TX;GPIO2 |
15 | GPIO0 | I/O | GPIO0;SPI_CS2 |
16 | GPIO4 | I/O | GPIO4 |
17 | VDDPST | P | 数字/IO 电源 (1.8 V ~ 3.6 V) |
18 | SDIO_DATA_2 | I/O | 连接到 SD_D2(串联 200 Ω);PIHD;HSPIHD;GPIO9 |
19 | SDIO_DATA_3 | I/O | 连接到 SD_D3(串联 200 Ω);SPIWP;HSPIWP;GPIO10 |
20 | SDIO_CMD | I/O | 连接到 SD_CMD(串联 200 Ω);SPI_CS0;GPIO11 |
21 | SDIO_CLK | I/O | 连接到 SD_CLK(串联 200 Ω);SPI_CLK;GPIO6 |
22 | SDIO_DATA_0 | I/O | 连接到 SD_D0(串联 200 Ω);SPI_MISO;GPIO7 |
23 | SDIO_DATA_1 | I/O | 连接到 SD_D1(串联 200 Ω);SPI_MOSI;GPIO8 |
24 | GPIO5 | I/O | GPIO5 |
25 | U0RXD | I/O | 可⽤作烧写 flash 时 UART RX;GPIO3 |
26 | U0TXD | I/O | 可⽤作烧写 flash 时 UART TX;GPIO1;SPI_CS1 |
27 | XTAL_OUT | I/O | 连接晶振输出端,也可⽤于提供 BT 的时钟输⼊ |
28 | XTAL_IN | I/O | 连接晶振输⼊端 |
29 | VDDD | P | 模拟电源 2.5 V ~ 3.6 V |
30 | VDDA | P | 模拟电源 2.5 V ~ 3.6 V |
31 | RES12K | I | 串联 12 kΩ 电阻到地 |
32 | EXT_RSTB | I | 外部重置信号(低电平有效) |
ESP-12-F 模组
Gokit3 拓展板上的 ESP8266 模组是 ESP-12F 型号。ESP-12F WiFi 模块是由安信可科技开发的,核心模块就是乐鑫的 ESP8266 芯片。
GPIO
ESP8266 的 16 个通用 IO 的管脚位置和名称如下表所示:
通用 GPIO 以及复用功能可以查询表格:ESP8266_Pin_List.xls
ESP8266 SDK
ESP8266 Software Development Kit (SDK) 是乐鑫为开发者提供的物联网 (IoT) 应用开发平 台,包括基础平台以及上层应用开发示例,如智能灯、智能开关等。 SDK 的基础平台按照是否基于操作系统可分为:Non-OS 和 RTOS 两种版本。
- Non-OS SDK:Non-OS SDK 是不基于操作系统的 SDK,提供 IOT_Demo 和 AT 的编译。
- 开源项目地址:https://github.com/espressif/ESP8266_NONOS_SDK
- Non-OS SDK 主要使用定时器和回调函数的方式实现各个功能事件的嵌套,达到特定条件下触发特定功能 函数的目的。Non-OS SDK 使用 espconn 接口实现网络操作,⽤户需要按照 espconn 接口的使⽤规则进行软件开发。
- Non-OS SDK 为用户提供了一套应用程序编程接口(API),能够实现 ESP8266 的核心功能 改,例如数据接收/发送、TCP/IP 功能、硬件接口功能,以及基本的系统管理理功能等。用户不不必关心底层网络,如 Wi-Fi、TCP/IP 等的具体实现,只需要专注于物联网上层应用的开发,利用相应接口实现各种功能即可。
- RTOS SDK:RTOS SDK 基于 FreeRTOS。
- 开源项目地址: https://github.com/espressif/ESP8266_RTOS_SDK
- RTOS 版本 SDK 使⽤ FreeRTOS 系统,引入 OS 多任务处理的机制,用户可以使⽤ FreeRTOS 的标准接口实现资源管理、循环操作、任务内延时、任务间信息传递和 同步等⾯向任务流程的设计方式。具体接口使用方法参考 FreeRTOS 官方网站的使用说明或者 USING THE FreeRTOS REAL TIME KERNEL— A Practical Guide 介绍。
- RTOS 版本 SDK 的网络操作接口是标准 lwIP API,同时提供了 BSD Socket API 接口的封装实现,用户户可以直接按照 Socket API 的使用方式来开发软件应⽤,也可以直接编译运行其他平台的标准 Socket 应⽤,有效降低平台切换的学习成本。
- RTOS 版本 SDK 引入了 cJSON 库,使⽤该库函数可以更加⽅便的实现对 JSON 数 据包的解析。
- RTOS 版本兼容 Non-OS SDK 中的 Wi-Fi 接口、SmartConfig 接口、Sniffer 相关接口、系统接口、定时器接口、FOTA 接口和外围驱动接口,不支持 AT 实现。
RTOS SDK 是 Non-OS 的新版本,新版 ESP8266_RTOS_SDK 可帮助客户避免对单一 SDK 的依赖,允许客户应用程序同时兼容多款乐鑫芯片,包括 ESP8266 系列、ESP32 系列以及未来发布的新产品。
本文中的代码是通过机智云平台代码自动生成工具生成的 SoC 版本代码,截止到本文开始写作日期 (2020.9.17) ,平台生成的代码默认集成了 Non-OS SDK,理论上开发者自己替换成 RTOS SDK 也是可以的。为了理解保持环境的一致性,本文代码以 Non-OS SDK 版本的代码为例说明。
Non-OS SDK
Non-OS SDK 适用于用户需要完全控制代码执行顺序和用于事件驱动的应用程序。由于没有操作系统,Non-OS SDK 不支持任务调度,也不支持基于优先级的抢占,Non-OS SDK 没有单个任务堆栈大小的限制或者执行时隙要求。因此开发者需要自行保证程序的正确执行,用户代码不能长期占用 CPU,否则会导致看门狗复位,ESP8266 重启。 如果某些特殊情况下,用户线程必须执行行较长时间(比如大于 500 ms),建议调用 system_soft_wdt_feed() API 来喂软件看门狗,而不建议禁用软件看门狗。
Non-OS SDK 使用四种类型的函数:
- 应用函数:类似于嵌入式 C 编程中的常用 C 函数。这些函数必须由另一个函数调用。 应用函数在定义时建议添加 ICACHE_FLASH_ATTR 宏,相应程序将存放在 flash 中,被调用时才加载到 cache 运行,而如果添加了 IRAM_ATTR 宏的函数,则会在上电启动时就加载到 iRAM 中。
- 回调函数:指不直接从用户程序调用的函数,而是当某系统事件发生时,相应的回调函数由 Non-OS SDK 内核调用执行。这使得开发者能够在不使用 RTOS 或者轮询事件的情况下响应实时事件。要编写回调函数,用户首先需要使用相应的 register_cb API 注册回调函数。回调函数的示例包括定时器回调函数和网络事件回调函数。
- 中断服务程序 (ISR) :一种特殊类型的回调函数,发生硬件中断时会调用这些函数。当使能中断时,必须注册相应的中断处理理函数。请注意,ISR 必须添加 IRAM_ATTR。
- 用户任务:可以分为三个优先级:0、1、2。任务优先级为 2 > 1 > 0,即 Non-OS SDK 最多只支持 3 个用户任务,优先级分别为 0、1、2。 用户任务一般用于函数不能直接被调用的情况下。要创建用户任务,需要使用 system_os_task() API。例如,espconn_disconnect() API 不能直接在 espconn 的回调函数中调用,因此建议开发者可以在 espconn 回调中创建用户任务来执行 espconn_disconnect。
代码目录结构
对照 ESP8266_NONOS_SDK 的工程结构,我们很容易发现 Gokit3 代码自动生成工具生成的源代码里面包含了 Non-OS SDK 的文件,Gokit3 自定义的代码在 app/Gizwits 和 app/user 中。├── app // 用户目录
| ├── driver // 外设驱动的库⽂件
| | ├── gpio16.c // GPIO
| | ├── hal_infrared.c // 红外驱动函数
| | ├── hal_key.c // 按键驱动函数,实现了 2 个 key 的长短按键检测功能,使用 demo 见 user_main.c 文件
| | ├── hal_motor.c // 电机驱动函数
| | ├── hal_rgb_led.c // RGB LED驱动函数
| | ├── hal_temp_hum.c //
| | ├── hal_temp_hum.c //
| | ├── hw_timer.c // 硬件中断定时器
| | ├── i2c_master.c //
| | ├── key.c //
| | ├── spi_overlap.c // SPI
| | ├── spi.c //
| | └── uart.c // UART
| ├── Gizwits // 机器云相关的代码
| | ├── gizwits_product.c // 该文件包括产品相关处理函数,如 gizwitsEventProcess();
// 用户相关的初始化 userInit(),以及数据采集 userHandle()函数
| | ├── gizwits_product.h // 该文件为 gizwits_product.c 的头文件,定义软硬件版本号,如 HARDWARE_VERSION、SOFTWARE_VERSION。
| | ├── gizwits_protocol.c // 该文件为 SDK API 接口函数定义文件,gizwits 协议处理模块,提供对用户接口封装
| | └── gizwits_protocol.h // 该文件为 gizwits_protocol.c 对应头文件,相关 API 的接口声明均在此文件中,包括协议相关结构体
| ├── include // 模组驱动相关库头文件
| | ├── json //
| | ├── airkiss.h //
| | ├── ...
| ├── user // 入口程序
| | ├── Makefile //
| | ├── user_json.c //
| | └── user_main.c // Esp8266 程序主文件,入口函数为 void user_init(void),实现各模块初始化,task/timer 创建等功能
| ├── Utils // 工具程序
| ├── gen_misc.bat
| ├── gen_misc.sh
| └── Makefile
├── bin // 固件文件
| ├── at // AT指令
| ├── at_sdio //
| ├── upgrade
| | ├── user1.4096.new.6.bin // 主程序,编译代码⽣成
| ├── blank.bin // 初始化系统参数,由 ESP8266 SDK 提供
| ├── boot_v1.6.bin // Bootloader,由 ESP8266 SDK 提供
| └── esp_init_data_default.bin // 初始化射频参数,由 ESP8266 SDK 提供
├── include // SDK ⾃带头⽂件,包含了⽤户可使⽤的相关 API 函数及其他宏定义,⽤户⽆需修改
| ├── gagent_soc.h // 该文件为 libgagent.a 对应头文件
| ├── ...
├── ld // 动态链接库,链接时所需的脚本⽂件,若⽆特殊需求,⽤户⽆需修改
├── lib // SDK 提供的库⽂件
| ├── libgagent.a // 该文件为机智云设备接入协议库文件
| ├── ...
└── tools // 编译 BIN ⽂件所需的⼯具,⽤户⽆需修改
源码分析
程序入口 user_main.c
ESP8266 物联网平台的所有网络功能均在库中实现,对用户不透明。用户应用的初始化功能可以在 user_main.c 中实现。void user_init(void) 是上层程序的入口函数,给用户提供一个初始化接口,用户可在该函数内增加硬件初始化、网络参数设置、定时器器初始化等功能。 ```c /**
@brief program entry function
In the function to complete the user-related initialization
- @param none
@return none */ void ICACHE_FLASH_ATTR user_init(void) { uint32_t system_free_size = 0;
// 设置 ESP8266 Station 上电自动连接已记录的 AP(路由) wifi_station_set_auto_connect(1); // 设置省电模式。设置为 NONE_SLEEP_T,则关闭省电模式 wifi_set_sleep_type(NONE_SLEEP_T); // 设置允许的 TCP ⼤大连接数。在内存⾜足够的情况下,建议不不超过 10。默认值为 5。 espconn_tcp_set_max_con(10); // 设置 UART0 和 UART1 的波特率 uart_init_3(9600, 115200); // 切换打印函数输出端口,默认情况下,系统打印函数 os_printf 从 uart0 口输出内容 UART_SetPrintPort(1);
// 打印 ESP8266 SDK 版本 GIZWITS_LOG(“———————-SDK version:%s———————\n”, system_get_sdk_version()); // 查询系统剩余可用 heap 区空间⼤⼩ GIZWITS_LOG(“system_get_free_heap_size=%d\n”, system_get_free_heap_size());
// 查询当前启动的信息 struct rst_info *rtc_info = system_get_rst_info(); GIZWITS_LOG(“reset reason: %x\n”, rtc_info->reason); if (rtc_info->reason == REASON_WDT_RST ||
rtc_info->reason == REASON_EXCEPTION_RST ||
rtc_info->reason == REASON_SOFT_WDT_RST)
{
if (rtc_info->reason == REASON_EXCEPTION_RST) { GIZWITS_LOG("Fatal exception (%d):\n", rtc_info->exccause); } GIZWITS_LOG("epc1=0x%08x, epc2=0x%08x, epc3=0x%08x, excvaddr=0x%08x, depc=0x%08x\n", rtc_info->epc1, rtc_info->epc2, rtc_info->epc3, rtc_info->excvaddr, rtc_info->depc);
}
if (system_upgrade_userbin_check() == UPGRADE_FW_BIN1) {
GIZWITS_LOG("---UPGRADE_FW_BIN1---\n");
} else if (system_upgrade_userbin_check() == UPGRADE_FW_BIN2) {
GIZWITS_LOG("---UPGRADE_FW_BIN2---\n");
}
// 初始化按键 keyInit();
// gizwits 协议初始化接口 // 用户调用该接口可以完成 Gizwits 协议相关初始化(包括协议相关定时器、串口的初始化) gizwitsInit();
GIZWITS_LOG(“—- system_free_size = %d —-\n”, system_get_free_heap_size()); } ``` 通过 Non-OS SDK API 文档可以查询 ESP8266 库函数的含义,通过串口调试工具查看日志,可以看出初始化的流程。
对于 ESP8266_NONOS_SDK_v2.2.1 及之前的版本,应用程序必须在 user_main.c 的 user_rf_cal_sector_set 中设置 RF 校准扇区。按键初始化逻辑
默认生成的逻辑只初始化了 Key1 和 Key2 的逻辑。 ```c /**
- Key to initialize
- @param none
- @return none
*/
LOCAL void ICACHE_FLASH_ATTR keyInit(void)
{
singleKey[0] = keyInitOne(KEY_0_IO_NUM, KEY_0_IO_MUX, KEY_0_IO_FUNC,
singleKey[1] = keyInitOne(KEY_1_IO_NUM, KEY_1_IO_MUX, KEY_1_IO_FUNC,key1LongPress, key1ShortPress);
keys.singleKey = singleKey; keyParaInit(&keys); }key2LongPress, key2ShortPress);
单个按键初始化的逻辑如下:
c /** @brief single button initialization
In this function to complete a single key initialization, here need to combine the ESP8266 GPIO register description document to set the parameters
- @param [in] gpio_id ESP8266 GPIO number
- @param [in] gpio_name ESP8266 GPIO name
- @param [in] gpio_func ESP8266 GPIO function
- @param [in] long_press Long press the callback function address
- @param [in] short_press Short press state callback function address
@return single-button structure pointer / key_typedef_t ICACHE_FLASH_ATTR keyInitOne(uint8 gpio_id, uint32 gpio_name, uint8 gpio_func, gokit_key_function long_press, gokit_key_function short_press) { static int8_t key_total = -1;
key_typedef_t singleKey = (key_typedef_t )os_zalloc(sizeof(key_typedef_t));
singleKey->gpio_number = ++key_total;
//Platform-defined GPIO singleKey->gpio_id = gpio_id; singleKey->gpio_name = gpio_name; singleKey->gpio_func = gpio_func;
//Button trigger callback type singleKey->long_press = long_press; singleKey->short_press = short_press;
keyTotolNum++;
return singleKey; }
以 key1 为例,GPIO id 为 KEY_0_IO_NUM(值为 0)、name 为 KEY_0_IO_MUX(值为PERIPHS_IO_MUX_GPIO0_U),function 为 KEY_0_IO_FUNC(值为 FUNC_GPIO0)。将 Key1 按键 GPIO 的参数及按键短按和长按的函数赋值给 key_typedef_t 结构体 singleKey。<br />并将结构体传到 keyParaInit 函数中进行处理,keyParaInit 具体逻辑如下:
c /**@brief button driver initialization
In the function to complete all the keys GPIO initialization, and open a timer to start the key state monitoring
- @param [in] keys Key Function Global structure pointer
@return none / void ICACHE_FLASH_ATTR keyParaInit(keys_typedef_t keys) { uint8 tem_i = 0;
if(NULL == keys) {
return ;
}
//init key timer keys->key_timer_ms = KEY_TIMER_MS; // 取消定时器定时 os_timer_disarm(&keys->key_timer); // 设置定时器回调函数。使⽤定时器,必须设置回调函数。 os_timer_setfn(&keys->key_timer, (os_timer_func_t *)gokitKeyHandle, keys);
keys->keyTotolNum = keyTotolNum;
//Limit on the number keys (Allowable number: 0~12) if(KEY_MAX_NUMBER < keys->keyTotolNum) {
keys->keyTotolNum = KEY_MAX_NUMBER;
}
//GPIO configured as a high level input mode for(tem_i = 0; tem_i < keys->keyTotolNum; tem_i++) {
// 管脚功能选择 PIN_FUNC_SELECT(keys->singleKey[tem_i]->gpio_name, keys->singleKey[tem_i]->gpio_func); // 设置 GPIO 属性 GPIO_OUTPUT_SET(GPIO_ID_PIN(keys->singleKey[tem_i]->gpio_id), 1); // 管脚上拉使能 PIN_PULLUP_EN(keys->singleKey[tem_i]->gpio_name); // 配置管脚为输入模式 GPIO_DIS_OUTPUT(GPIO_ID_PIN(keys->singleKey[tem_i]->gpio_id)); os_printf("gpio_name %d \r\n", keys->singleKey[tem_i]->gpio_id);
}
//key timer start // 使能毫秒级定时器 os_timer_arm(&keys->key_timer, keys->key_timer_ms, 1); } ```
参考
写文章不容易,也许写这些代码就几分钟的事,写一篇大家好接受的文章或许需要几天的酝酿,如果文章对您有帮助请我喝杯咖啡吧!