NodeMCU ESP8266 GPIO 和 PWM 学习
前言
入门任何一款 MCU,GPIO 是必须掌握的基础知识,虽然每款 MCU GPIO 的数量和能力各不一样,但是使用用法是基本一样的。本文将通过两个小实验去理解 GPIO 中的核心知识,通过 ESP8266 了解一下 GPIO 中的基本概念,从而更好的掌握 ESP8266 其他的功能。通用输入/输出 (GPIO) 是集成电路上的一个引脚功能,它既可以是输入引脚,也可以是输出引脚,这些功能均可以在编写程序时进行控制。
ESP8266 与 NodeMCU 管脚
ESP8266 管脚定义
ESP8266 共有16个通用 IO,管脚的位置和管脚的分别为:
管脚 | 名称 | 类型 | 功能 |
---|---|---|---|
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 管脚映射图
| 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。
在实际使用中,从上图我们可以看到一些 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 上。
ESP-12 模组板载 LED 引脚对应 GPIO2/TXD1,电路图如下:
NodeMCU 板载 LED 引脚对应 GPIO16,电路图如下:
两个 LED 都在反向模式下工作,关于引脚电平-当引脚高时,LED是关闭的;当引脚低时,LED是亮的。GPIO 0-15 引脚都配有内置上拉电阻,可以通过 GPIO 配置使能上拉。
配置 GPIO2 引脚
GPIO 的初始化通过一个结构体来配置,如下:
typedef struct {
uint16 GPIO_Pin; /**< GPIO pin:配置某一号管脚 */
GPIOMode_TypeDef GPIO_Mode; /**< GPIO mode:设置输入输出模式 */
GPIO_Pullup_IF GPIO_Pullup; /**< GPIO pullup:使能上拉 */
GPIO_INT_TYPE GPIO_IntrType; /**< GPIO interrupt type:是否使能中断,并配置中断模式 */
} GPIO_ConfigTypeDef;
中断模式配置结构体:
typedef enum {
GPIO_PIN_INTR_DISABLE = 0, /**< disable GPIO interrupt 失能中断 */
GPIO_PIN_INTR_POSEDGE = 1, /**< GPIO interrupt type : rising edge 上升沿触发 */
GPIO_PIN_INTR_NEGEDGE = 2, /**< GPIO interrupt type : falling edge 下降沿触发 */
GPIO_PIN_INTR_ANYEDGE = 3, /**< GPIO interrupt type : bothe rising and falling edge 双边沿触发 */
GPIO_PIN_INTR_LOLEVEL = 4, /**< GPIO interrupt type : low level 输入低电平触发 */
GPIO_PIN_INTR_HILEVEL = 5 /**< GPIO interrupt type : high level 输入高电平触发 */
} GPIO_INT_TYPE;
输入输出模式配置结构体:
typedef enum {
GPIO_Mode_Input = 0x0, /**< GPIO mode : Input */
GPIO_Mode_Out_OD, /**< GPIO mode : Output_OD */
GPIO_Mode_Output , /**< GPIO mode : Output */
GPIO_Mode_Sigma_Delta , /**< GPIO mode : Sigma_Delta */
} GPIOMode_TypeDef;
下面我们以控制 GPIO2 连接的 LED 灯为例加以说明。设置 GPIO2 为不开启中端,输出端口并使能上拉:
#include "gpio.h"
void led_init(void){
GPIO_ConfigTypeDef gpio_in_cfg; // 定义 GPIO 初始化结构体
gpio_in_cfg.GPIO_IntrType = GPIO_PIN_INTR_DISABLE; // 是否使能中断,并配置中断模式
gpio_in_cfg.GPIO_Mode = GPIO_Mode_Output; // 设置 GPIO2 口为输出端口
gpio_in_cfg.GPIO_Pullup = GPIO_PullUp_EN; // 设置 GPIO2 口上拉有效
gpio_in_cfg.GPIO_Pin = GPIO_Pin_2; // 使能 GPIO2 口
gpio_config(&gpio_in_cfg); // GPIO 初始化
}
void user_init(void){
led_init();
}
GPIO2 LED 灯闪烁实例
如果我们想实现 LED 闪烁的效果,我们可以定义一个任务来实现:
/**
* 创建一个任务,参数分别为:
* - pvTaskCode:任务函数
* - pcName: 任务名称
* - usStackDepth: 任务堆栈
* - pvParameters: 任务函数的参数
* - uxPriority: 任务优先级
* - pvCreatedTask: 任务句柄
*/
xTaskCreate(led_toggle_task, "led_toggle_task", 256, NULL, 1, NULL);
任务函数实现:
void led_init(void){
GPIO_ConfigTypeDef gpio_in_cfg; // 定义 GPIO 初始化结构体
gpio_in_cfg.GPIO_IntrType = GPIO_PIN_INTR_DISABLE; // 是否使能中断,并配置中断模式
gpio_in_cfg.GPIO_Mode = GPIO_Mode_Output; // 设置 GPIO 口为输出端口 /
gpio_in_cfg.GPIO_Pullup = GPIO_PullUp_EN; // 设置 GPIO 口上拉有效
gpio_in_cfg.GPIO_Pin = GPIO_Pin_2; // 使能 GPIO IO 口
gpio_config(&gpio_in_cfg); // GPIO 初始化
}
void led_toggle(void){
// 获取 GPIO2 管脚的电平状态
uint32_t bit = GPIO_INPUT_GET(2);
// 设置 GPIO2 管脚输出电平
GPIO_OUTPUT(GPIO_Pin_2, bit^1);
printf("led toggle \n");
}
void led_toggle_task(void *pvParameters){
led_init();
for( ;; ){
led_toggle();
// 延时 500 ms
vTaskDelay(500 / portTICK_RATE_MS);
}
vTaskDelete(NULL);
}
制作 RGB 三色 LED 呼吸灯
PWM 实现呼吸灯
什么是呼吸灯?
呼吸灯最早是由苹果公司发明并应用于笔记本睡眠提示上,一经展出,立刻吸引众多科技厂商争相效仿。呼吸灯是指灯在微控器控制之下完成由暗到亮的逐渐变化,再由亮到暗的逐渐变化,亮暗的节奏感觉像是人在呼吸。 用微控器做呼吸灯是利用频率来控制呼吸灯的呼吸时间,用占空比来控制灯的亮度。也就是采用 PWM 的方式,在固定的频率下,采用占空比的方式来实现 LED 亮度的变化。占空比为 0,LED 灯不亮,占空比为 100%,则 LED 灯最亮。所以将占空比从 0 到 100%,再从 100% 到 0 不断变化,就可以实现 LED 灯实现特效呼吸。
什么是 PWM 控制信号?
PWM(pulse-width modulation)脉冲宽度调制,MCU(微控制器)通过对开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需的波形。当我们需要连续控制电压变化,实现呼吸灯或者电机转速的时候,就要用到PWM,如下图所示。
电路连接图
NodeMCU 套件上的 PWM 引脚如下图所示:
下面我们用 NodeMCU D5 (GPIO14)、D6 (GPIO12)、D7 (GPIO13) 这三个支持 PWM 功能的引脚去控制 RGB 三色 LED 灯。电路连接图如下:
我们按照电路图接好电路会发现 RGB 三色灯均发亮了,这是因为 GPIO12、GPIO13、GPIO14 UBOOT电平默认是高电平。
呼吸灯代码实现
PWM 驱动接⼝函数不能跟 hw_timer.c 的接⼝同时使⽤,因为它们共⽤了同⼀个硬件定时器。
#define PWM_0_OUT_IO_MUX PERIPHS_IO_MUX_MTDI_U
#define PWM_0_OUT_IO_NUM 12
#define PWM_0_OUT_IO_FUNC FUNC_GPIO12
#define PWM_1_OUT_IO_MUX PERIPHS_IO_MUX_MTCK_U
#define PWM_1_OUT_IO_NUM 13
#define PWM_1_OUT_IO_FUNC FUNC_GPIO13
#define PWM_2_OUT_IO_MUX PERIPHS_IO_MUX_MTMS_U
#define PWM_2_OUT_IO_NUM 14
#define PWM_2_OUT_IO_FUNC FUNC_GPIO14
LOCAL os_timer_t rgb_light_timer;
/** PWM占空比变量 */
LOCAL u8 set_duty = 0;
/** PWM占空比加减标志 */
LOCAL bool flag = true;
void ESP8266_PWM_RUN(void)
{
if (flag)
{
if (++set_duty >= 100)
{
flag = false;
}
}
else
{
if (--set_duty <= 0)
{
flag = true;
}
}
/** 更新 PWM 通道的占空比 */
pwm_set_duty(set_duty, 0);
pwm_set_duty(set_duty, 1);
pwm_set_duty(set_duty, 2);
pwm_start();
}
void rgb_light_pwm_init(void)
{
uint32 period = 1000;
uint32 duty[3] = {0};
uint32 io_info[3][3] = {
{PWM_0_OUT_IO_MUX, PWM_0_OUT_IO_FUNC, PWM_0_OUT_IO_NUM}, // GPIO12
{PWM_1_OUT_IO_MUX, PWM_1_OUT_IO_FUNC, PWM_1_OUT_IO_NUM}, // GPIO13
{PWM_2_OUT_IO_MUX, PWM_2_OUT_IO_FUNC, PWM_2_OUT_IO_NUM} // GPIO14
};
// 配置 PWM 的周期为 1000us,占空比为 0,PWM 通道数量为 3
pwm_init(period, duty, 3, io_info);
os_timer_disarm(&rgb_light_timer);
os_timer_setfn(&rgb_light_timer, (os_timer_func_t *)(ESP8266_PWM_RUN), NULL);
os_timer_arm(&rgb_light_timer, 20, true);
}
实现效果如下:
参考
- ESP8266学习历程——GPIO配置
- Onboard LEDs? NodeMCU's got two!
- ESP8266开发之旅 基础篇③ ESP8266与Arduino的开发说明
- 如何在ESP8266上实现变色呼吸灯效果?
写这些代码也许就一两个小时的事,写一篇大家好接受的文章需要几天的酝酿,如果文章对您有帮助请我喝杯咖啡吧!