image.png

ESP8266 硬件

乐鑫 ESP8266 芯片官网:https://www.espressif.com/zh-hans/products/socs/esp8266
ESP8266 是一款面向物联网应用的 WiFi MCU,具有以下特点:
image.png

Tensilica 处理器

ESP8266 集成的是 Tensilica L106 32 bit 处理器,Tensilica 公司创立的目的是提供一种可以实现可重构的、核基于 ASIC 的、拥有对应软件开发工具的专用微处理器解决方法。Tensilica 和 ARM IP 的一大区别是可定制化,因此乐鑫在购买 IP 后可以继续优化,形成自己产品的差异化优势。而传统购买 ARM IP 的公司的产品性能都差不多。

ESP8266 芯片管脚

管脚布局如下图所示:
image.png
管脚定义如下表所示:

管脚 名称 类型 功能
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 芯片。
image.png

GPIO

ESP8266 的 16 个通用 IO 的管脚位置和名称如下表所示:
image.png
通用 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 中。
    1. ├── app // 用户目录
    2. | ├── driver // 外设驱动的库⽂件
    3. | | ├── gpio16.c // GPIO
    4. | | ├── hal_infrared.c // 红外驱动函数
    5. | | ├── hal_key.c // 按键驱动函数,实现了 2 个 key 的长短按键检测功能,使用 demo 见 user_main.c 文件
    6. | | ├── hal_motor.c // 电机驱动函数
    7. | | ├── hal_rgb_led.c // RGB LED驱动函数
    8. | | ├── hal_temp_hum.c //
    9. | | ├── hal_temp_hum.c //
    10. | | ├── hw_timer.c // 硬件中断定时器
    11. | | ├── i2c_master.c //
    12. | | ├── key.c //
    13. | | ├── spi_overlap.c // SPI
    14. | | ├── spi.c //
    15. | | └── uart.c // UART
    16. | ├── Gizwits // 机器云相关的代码
    17. | | ├── gizwits_product.c // 该文件包括产品相关处理函数,如 gizwitsEventProcess();
    18. // 用户相关的初始化 userInit(),以及数据采集 userHandle()函数
    19. | | ├── gizwits_product.h // 该文件为 gizwits_product.c 的头文件,定义软硬件版本号,如 HARDWARE_VERSION、SOFTWARE_VERSION。
    20. | | ├── gizwits_protocol.c // 该文件为 SDK API 接口函数定义文件,gizwits 协议处理模块,提供对用户接口封装
    21. | | └── gizwits_protocol.h // 该文件为 gizwits_protocol.c 对应头文件,相关 API 的接口声明均在此文件中,包括协议相关结构体
    22. | ├── include // 模组驱动相关库头文件
    23. | | ├── json //
    24. | | ├── airkiss.h //
    25. | | ├── ...
    26. | ├── user // 入口程序
    27. | | ├── Makefile //
    28. | | ├── user_json.c //
    29. | | └── user_main.c // Esp8266 程序主文件,入口函数为 void user_init(void),实现各模块初始化,task/timer 创建等功能
    30. | ├── Utils // 工具程序
    31. | ├── gen_misc.bat
    32. | ├── gen_misc.sh
    33. | └── Makefile
    34. ├── bin // 固件文件
    35. | ├── at // AT指令
    36. | ├── at_sdio //
    37. | ├── upgrade
    38. | | ├── user1.4096.new.6.bin // 主程序,编译代码⽣成
    39. | ├── blank.bin // 初始化系统参数,由 ESP8266 SDK 提供
    40. | ├── boot_v1.6.bin // Bootloader,由 ESP8266 SDK 提供
    41. | └── esp_init_data_default.bin // 初始化射频参数,由 ESP8266 SDK 提供
    42. ├── include // SDK ⾃带头⽂件,包含了⽤户可使⽤的相关 API 函数及其他宏定义,⽤户⽆需修改
    43. | ├── gagent_soc.h // 该文件为 libgagent.a 对应头文件
    44. | ├── ...
    45. ├── ld // 动态链接库,链接时所需的脚本⽂件,若⽆特殊需求,⽤户⽆需修改
    46. ├── lib // SDK 提供的库⽂件
    47. | ├── libgagent.a // 该文件为机智云设备接入协议库文件
    48. | ├── ...
    49. └── 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 ||

    1. rtc_info->reason == REASON_EXCEPTION_RST ||
    2. 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,
                            key1LongPress, key1ShortPress);
    
    singleKey[1] = keyInitOne(KEY_1_IO_NUM, KEY_1_IO_MUX, KEY_1_IO_FUNC,
                            key2LongPress, key2ShortPress);
    
    keys.singleKey = singleKey; keyParaInit(&keys); } 单个按键初始化的逻辑如下: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); } ```

    参考

写文章不容易,也许写这些代码就几分钟的事,写一篇大家好接受的文章或许需要几天的酝酿,如果文章对您有帮助请我喝杯咖啡吧!
2020-12-06 at 11.48 AM.png