前言

入门任何一款 MCU,GPIO 是必须掌握的基础知识,虽然每款 MCU GPIO 的数量和能力各不一样,但是使用用法是基本一样的。本文将通过两个小实验去理解 GPIO 中的核心知识,通过 ESP8266 了解一下 GPIO 中的基本概念,从而更好的掌握 ESP8266 其他的功能。通用输入/输出 (GPIO) 是集成电路上的一个引脚功能,它既可以是输入引脚,也可以是输出引脚,这些功能均可以在编写程序时进行控制。

ESP8266 与 NodeMCU 管脚

ESP8266 管脚定义

ESP8266 共有16个通用 IO,管脚的位置和管脚的分别为:
image.png**

管脚 名称 类型 功能
1 VDDA P 模拟电源 3.0V ~ 3.6V
2 LNA I/O 射频天线接口,芯片输出阻抗为 50Ω。
无需对芯片进行匹配,但建议保留π型匹配网络对天线进行匹配。
3 VDD3P3 P 功放电源 3.0V ~ 3.6V
4 VDD3P3 P 功放电源 3.0V ~ 3.6V
5 VDD_RTC P NC(1.1V)
6 TOUT I ADC端(芯片内部ADC端口),可用于检测VDD3P3(Pin3、Pin4)电源电压和TOUT(Pin6)的输入电压。(二者不可同时使用)
7 CHIP_PU I 芯片使能端。
高电平:有效,芯片正常工作;低电平:芯片关闭,电流很小
8 XPD_DCDC I/O 深度睡眠唤醒;GPIO16
9 MTMS I/O GPOIO14;HSPI_CLK
10 MTDI I/O GPIO12;HSPI_MISO
11 VDDPST P 数字/IO电源(1.8V ~ 3.3V)
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.8V ~ 3.3V)
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_CKL;GPIO6
22 SDIO_DATA_0 I/O 连接到SD_D0(串联200Ω);SPI_MSIO;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 模拟电源 3.0V ~ 3.6V
30 VDDA P 模拟电源 3.0V ~ 3.6V
31 RES12K I 串联12kΩ电阻到地
32 EXT_RSTB I 外部重置信号(低电平有效)

其中,在四线 (QUAD) 模式 flash 下,有六个 IO 于 flash 通讯;在两线 (DUAL) 模式 flash 下,有四个 IO 于与 flash 通讯。

NodeMCU 管脚映射图

image.png

| Label | GPIO | Input | Output | Notes | | D0 | GPIO16 | no interrupt | no PWM or I2C support | HIGH at boot used to wake up from deep sleep | | —- | —- | —- | —- | —- | | D1 | GPIO5 | OK | OK | often used as SCL (I2C) | | D2 | GPIO4 | OK | OK | often used as SDA (I2C) | | D3 | GPIO0 | pulled up | OK | connected to FLASH button, boot fails if pulled LOW | | D4 | GPIO2 | pulled up | OK | HIGH at boot connected to on-board LED, boot fails if pulled LOW | | D5 | GPIO14 | OK | OK | SPI (SCLK) | | D6 | GPIO12 | OK | OK | SPI (MISO) | | D7 | GPIO13 | OK | OK | SPI (MOSI) | | D8 | GPIO15 | pulled to GND | OK | SPI (CS) Boot fails if pulled HIGH | | RX | GPIO3 | OK | RX pin | HIGH at boot | | TX | GPIO1 | TX pin | OK | HIGH at boot debug output at boot, boot fails if pulled LOW | | A0 | ADC0 | Analog Input | X | |

NodeMCU 开发板引脚的编号与 NodeMCU 的内部 GPIO 编号不是一个编号,例如板上的 D4 对应的是 ESP8266 的 GPIO 引脚 2,可以通过 GPIO_Pin_2 变量引用。
根据 ESP8266 的系统 (SoC) 设计,其内部包含了处理器芯片等组件,处理器大约有 16 条 GPIO 线路,其中一些 GPIO 规定默认用于与其他内部组件进行通信,比如与内部闪存的通信,GPIO6 至 GPIO11 通常连接到 ESP8266 板上的 Flash 芯片,不建议使用这些引脚,这样我们大约还有 11 个GPIO引脚可按常规 GPIO 进行使用,在这 11 个针脚中,又有 2 个针脚预留给串口 RX 和 TX。因此最后只剩下 9 个通用 I/O 引脚,即 D0 到 D8。
image.png
在实际使用中,从上图我们可以看到一些 GPIO 引脚同时兼备了其他功能,如 RX, TX, SD2, SD3,这些引脚大多不作为 GPIO 使用,因为它们可用于其他进程。极端情况下,可使用 SD3 (D12) 引脚,D12 引脚主要用于响应 GPIO/PWM/中断等功能。需要注意的是,D0|GPIO16 引脚只能作为 GPIO 读/写使用,不支持任何特殊功能。

点亮 NodeMCU 开发板上的 LED

GPIO2 引脚电路图

点亮开发板上的 LED 灯是嵌入式编程开发中的 “Hello, World”。NodeMCU ESP8266 开发板有两个 LED 灯,一个 LED 位于 ESP-12 模组的 PCB 上;一个 LED 位于 NodeMCU 的 PCB 上。
image.png
ESP-12 模组板载 LED 引脚对应 GPIO2/TXD1,电路图如下:
image.png
NodeMCU 板载 LED 引脚对应 GPIO16,电路图如下:
屏幕快照 2020-12-13 下午4.19.41.png
两个 LED 都在反向模式下工作,关于引脚电平-当引脚高时,LED是关闭的;当引脚低时,LED是亮的。GPIO 0-15 引脚都配有内置上拉电阻,可以通过 GPIO 配置使能上拉。

配置 GPIO2 引脚

GPIO 的初始化通过一个结构体来配置,如下:

  1. typedef struct {
  2. uint16 GPIO_Pin; /**< GPIO pin:配置某一号管脚 */
  3. GPIOMode_TypeDef GPIO_Mode; /**< GPIO mode:设置输入输出模式 */
  4. GPIO_Pullup_IF GPIO_Pullup; /**< GPIO pullup:使能上拉 */
  5. GPIO_INT_TYPE GPIO_IntrType; /**< GPIO interrupt type:是否使能中断,并配置中断模式 */
  6. } GPIO_ConfigTypeDef;

中断模式配置结构体:

  1. typedef enum {
  2. GPIO_PIN_INTR_DISABLE = 0, /**< disable GPIO interrupt 失能中断 */
  3. GPIO_PIN_INTR_POSEDGE = 1, /**< GPIO interrupt type : rising edge 上升沿触发 */
  4. GPIO_PIN_INTR_NEGEDGE = 2, /**< GPIO interrupt type : falling edge 下降沿触发 */
  5. GPIO_PIN_INTR_ANYEDGE = 3, /**< GPIO interrupt type : bothe rising and falling edge 双边沿触发 */
  6. GPIO_PIN_INTR_LOLEVEL = 4, /**< GPIO interrupt type : low level 输入低电平触发 */
  7. GPIO_PIN_INTR_HILEVEL = 5 /**< GPIO interrupt type : high level 输入高电平触发 */
  8. } GPIO_INT_TYPE;

输入输出模式配置结构体:

  1. typedef enum {
  2. GPIO_Mode_Input = 0x0, /**< GPIO mode : Input */
  3. GPIO_Mode_Out_OD, /**< GPIO mode : Output_OD */
  4. GPIO_Mode_Output , /**< GPIO mode : Output */
  5. GPIO_Mode_Sigma_Delta , /**< GPIO mode : Sigma_Delta */
  6. } GPIOMode_TypeDef;

下面我们以控制 GPIO2 连接的 LED 灯为例加以说明。设置 GPIO2 为不开启中端,输出端口并使能上拉:

  1. #include "gpio.h"
  2. void led_init(void){
  3. GPIO_ConfigTypeDef gpio_in_cfg; // 定义 GPIO 初始化结构体
  4. gpio_in_cfg.GPIO_IntrType = GPIO_PIN_INTR_DISABLE; // 是否使能中断,并配置中断模式
  5. gpio_in_cfg.GPIO_Mode = GPIO_Mode_Output; // 设置 GPIO2 口为输出端口
  6. gpio_in_cfg.GPIO_Pullup = GPIO_PullUp_EN; // 设置 GPIO2 口上拉有效
  7. gpio_in_cfg.GPIO_Pin = GPIO_Pin_2; // 使能 GPIO2 口
  8. gpio_config(&gpio_in_cfg); // GPIO 初始化
  9. }
  10. void user_init(void){
  11. led_init();
  12. }

注:gpio.c 和 gpio.h 驱动文件可以 framework-esp8266-rtos-sdk 中拷贝。

GPIO2 LED 灯闪烁实例

1607851695610384.mp4
如果我们想实现 LED 闪烁的效果,我们可以定义一个任务来实现:

  1. /**
  2. * 创建一个任务,参数分别为:
  3. * - pvTaskCode:任务函数
  4. * - pcName: 任务名称
  5. * - usStackDepth: 任务堆栈
  6. * - pvParameters: 任务函数的参数
  7. * - uxPriority: 任务优先级
  8. * - pvCreatedTask: 任务句柄
  9. */
  10. xTaskCreate(led_toggle_task, "led_toggle_task", 256, NULL, 1, NULL);

任务函数实现:

  1. void led_init(void){
  2. GPIO_ConfigTypeDef gpio_in_cfg; // 定义 GPIO 初始化结构体
  3. gpio_in_cfg.GPIO_IntrType = GPIO_PIN_INTR_DISABLE; // 是否使能中断,并配置中断模式
  4. gpio_in_cfg.GPIO_Mode = GPIO_Mode_Output; // 设置 GPIO 口为输出端口 /
  5. gpio_in_cfg.GPIO_Pullup = GPIO_PullUp_EN; // 设置 GPIO 口上拉有效
  6. gpio_in_cfg.GPIO_Pin = GPIO_Pin_2; // 使能 GPIO IO 口
  7. gpio_config(&gpio_in_cfg); // GPIO 初始化
  8. }
  9. void led_toggle(void){
  10. // 获取 GPIO2 管脚的电平状态
  11. uint32_t bit = GPIO_INPUT_GET(2);
  12. // 设置 GPIO2 管脚输出电平
  13. GPIO_OUTPUT(GPIO_Pin_2, bit^1);
  14. printf("led toggle \n");
  15. }
  16. void led_toggle_task(void *pvParameters){
  17. led_init();
  18. for( ;; ){
  19. led_toggle();
  20. // 延时 500 ms
  21. vTaskDelay(500 / portTICK_RATE_MS);
  22. }
  23. vTaskDelete(NULL);
  24. }

制作 RGB 三色 LED 呼吸灯

电路连接图

我使用 RGB 三色 LED 灯分别连接 NodeMCU GPIO12、GPIO13、GPIO14、GND 四个引脚,连接图如下:
屏幕快照 2020-12-13 下午6.29.40.png
我们按照电路图接好电路会发现 RGB 三色灯均发亮了,这是因为 GPIO12、GPIO13、GPIO14 UBOOT电平默认是高电平。

PWM 实现呼吸灯

什么是呼吸灯?
呼吸灯最早是由苹果公司发明并应用于笔记本睡眠提示上,一经展出,立刻吸引众多科技厂商争相效仿。呼吸灯是指灯在微控器控制之下完成由暗到亮的逐渐变化,再由亮到暗的逐渐变化,亮暗的节奏感觉像是人在呼吸。
用微控器做呼吸灯是利用频率来控制呼吸灯的呼吸时间,用占空比来控制灯的亮度。也就是采用 PWM 的方式,在固定的频率下,采用占空比的方式来实现 LED 亮度的变化。占空比为 0,LED 灯不亮,占空比为 100%,则 LED 灯最亮。所以将占空比从 0 到 100%,再从 100% 到 0 不断变化,就可以实现 LED 灯实现特效呼吸。
什么是 PWM 控制信号?
PWM(pulse-width modulation)脉冲宽度调制,MCU(微控制器)通过对开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需的波形。当我们需要连续控制电压变化,实现呼吸灯或者电机转速的时候,就要用到PWM,如下图所示。
PWM.gif

呼吸灯代码实现

PWM 驱动接⼝函数不能跟 hw_timer.c 的接⼝同时使⽤,因为它们共⽤了同⼀个硬件定时器。屏幕快照 2020-12-14 上午12.24.32.png
屏幕快照 2020-12-14 上午12.26.00.png
屏幕快照 2020-12-14 上午12.27.01.png

  1. #define PWM_0_OUT_IO_MUX PERIPHS_IO_MUX_MTDI_U
  2. #define PWM_0_OUT_IO_NUM 12
  3. #define PWM_0_OUT_IO_FUNC FUNC_GPIO12
  4. #define PWM_1_OUT_IO_MUX PERIPHS_IO_MUX_MTCK_U
  5. #define PWM_1_OUT_IO_NUM 13
  6. #define PWM_1_OUT_IO_FUNC FUNC_GPIO13
  7. #define PWM_2_OUT_IO_MUX PERIPHS_IO_MUX_MTMS_U
  8. #define PWM_2_OUT_IO_NUM 14
  9. #define PWM_2_OUT_IO_FUNC FUNC_GPIO14
  10. LOCAL os_timer_t rgb_light_timer;
  11. /** PWM占空比变量 */
  12. LOCAL u8 set_duty = 0;
  13. /** PWM占空比加减标志 */
  14. LOCAL bool flag = true;
  15. void ESP8266_PWM_RUN(void)
  16. {
  17. if (flag)
  18. {
  19. if (++set_duty >= 100)
  20. {
  21. flag = false;
  22. }
  23. }
  24. else
  25. {
  26. if (--set_duty <= 0)
  27. {
  28. flag = true;
  29. }
  30. }
  31. /** 更新 PWM 通道的占空比 */
  32. pwm_set_duty(set_duty, 0);
  33. pwm_set_duty(set_duty, 1);
  34. pwm_set_duty(set_duty, 2);
  35. pwm_start();
  36. }
  37. void rgb_light_pwm_init(void)
  38. {
  39. uint32 period = 1000;
  40. uint32 duty[3] = {0};
  41. uint32 io_info[3][3] = {
  42. {PWM_0_OUT_IO_MUX, PWM_0_OUT_IO_FUNC, PWM_0_OUT_IO_NUM}, // GPIO12
  43. {PWM_1_OUT_IO_MUX, PWM_1_OUT_IO_FUNC, PWM_1_OUT_IO_NUM}, // GPIO13
  44. {PWM_2_OUT_IO_MUX, PWM_2_OUT_IO_FUNC, PWM_2_OUT_IO_NUM} // GPIO14
  45. };
  46. // 配置 PWM 的周期为 1000us,占空比为 0,PWM 通道数量为 3
  47. pwm_init(period, duty, 3, io_info);
  48. os_timer_disarm(&rgb_light_timer);
  49. os_timer_setfn(&rgb_light_timer, (os_timer_func_t *)(ESP8266_PWM_RUN), NULL);
  50. os_timer_arm(&rgb_light_timer, 20, true);
  51. }

实现效果如下:
1607877484146039.mp4

参考

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