写在前边

弄这个文档的初衷是为了更快地入门ESP32,一寻思自己弄太慢了,就大家一起来完善吧。文档内容肯定会有错误,在群里说一下改正即可,如果进度比较快or有地方需要完善,按照已有格式进行完善即可,众人拾柴火焰高,对吧。

文档说明

本文档中只说明ESP32中的情况
下边的函数或者某些参数如果没有看懂,需要结合PDF和AI了解。

GPIO

GPIO有四种常见的工作模式:

  • 输出:可以设置GPIO的高低电平
  • 输入:可以获取外部输入的高低电平信息,一般要设置加上拉电阻或下拉电阻
  • 浮空输出:可以设置GPIO的高低电平,但要在电路外部中增加上拉电阻
  • 开漏输入:可以获取外部输入的高低电平信息,但要在电路外部中增加上拉电阻

GPIO的使用流程:初始化->设置GPIO工作模式

GPIO初始化

定义结构体->调用API初始化GPIO

①定义结构体

结构体由以下内容组成

gpio_config_t led_GPIO_config = { //结构体名字可以随便改
    .pin_bit_mask = (1<<On_Board_LED_GPIO), // GPIO掩码,用来同时操作多个GPIO
    .pull_up_en = GPIO_PULLUP_DISABLE, //GPIO上拉使能
    .pull_down_en = GPIO_PULLDOWN_ENABLE, //GPIO下拉使能
    .mode = GPIO_MODE_OUTPUT, //GPIO模式
    .intr_type = GPIO_INTR_DISABLE, //GPIO中断类型
};
gpio_config_t led_GPIO_config = { //结构体名字可以随便改
    .pin_bit_mask = (1<<On_Board_LED_GPIO), // GPIO掩码,用来同时操作多个GPIO
    .pull_up_en = GPIO_PULLUP_DISABLE, //GPIO上拉使能
    .pull_down_en = GPIO_PULLDOWN_ENABLE, //GPIO下拉使能
    .mode = GPIO_MODE_OUTPUT, //GPIO模式
    .intr_type = GPIO_INTR_DISABLE, //GPIO中断类型
};

②使用API初始化GPIO

gpio_config(&led_GPIO_config); // 通过 乐鑫提供的API初始化GPIO ,使用上述的结构体
gpio_config(&led_GPIO_config); //通过乐鑫提供的API初始化GPIO,使用上述的结构体

GPIO输出

输出的两个参数分别是:

  • gpio_num:GPIO引脚编号(如GPIO_NUM_2)。
  • level:电平值(0/1)这里level非零即为1
GPIO_set_level ( gpio_num ,level );
GPIO_set_level(gpio_num,level);

GPIO交换矩阵

GPIO交换矩阵的核心作用:

  • 动态信号路由:允许将90%以上的外设信号(如UART、PWM、SPI等)自由映射到 任意可用的GPIO引脚(部分特殊功能引脚除外)。
  • 突破物理限制:解决PCB设计时的引脚冲突问题,例如可将同一外设(如 UART0)的TX和RX分配到不同位置的引脚。

简而言之,可以让任意引脚使用任意的功能。

定时器PWM

定时器是通道的基础.通道必须绑定到一个已通过ledc_timer_config配置的定时器。定时器定义了PWM的全局参数(如频率),而通道负责将这一时间基准转化为具体GPIO的输出。共享性:一个定时器可被多个通道共享。例如,定时器配置为1kHz频率后,多个通道可基于同一频率输出不同占空比的信号。参数一致性:通道的speed_mode必须与绑定的定时器一致,否则PWM信号无法正确生成。

配置流程:

配置PWM,绑定通道和GPIO

①配置PWM:

创建PWM配置结构体->通过API使用结构体对PWM的定时器进行配置

②绑定:

完成通道绑定设置->通过API使用结构体对PWM的定时器进行绑定

创建PWM配置结构体

对PWM进行初始化设置,设置定时器频率、通道,时钟源,翻转频率,分辨率

ledc_timer_config_t On_Board_LED_GPIO_Timer_change = {
    .speed_mode = LEDC_LOW_SPEED_MODE, //定时器频率(8MHz/80MHz)
    .timer_num = LEDC_TIMER 0, //使用的定时器通道(0~3)
    .clk_cfg = LEDC_AUTO_CLK, //时钟源(一般为AUTO)
    .freq_hz = 5000, //翻转的频率(Hz)
    .duty_resolution = LEDC_TIMER_13_BIT, //定时器的分辨率为2^13-1(表示占空比范围0~2^13-1)
};

↓↓ ESP32的时钟源类型 ↓↓

ledc_timer_config_t On_Board_LED_GPIO_Timer_change = {
    .speed_mode = LEDC_LOW_SPEED_MODE, //定时器频率(8MHz/80MHz)
    .timer_num = LEDC_TIMER0, //使用的定时器通道(0~3)
    .clk_cfg = LEDC_AUTO_CLK, //时钟源(一般为AUTO)
    .freq_hz = 5000, //翻转的频率(Hz)
    .duty_resolution = LEDC_TIMER_13_BIT, //定时器的分辨率为2^13-1(表示占空比范围0~2^13-1)
};
时钟源类型频率范围适用模式特点
APB_CLK80MHz(默认)高速模式高精度、低抖动
RTC8M_CLK~8MHz(可校准)低速模式低功耗、可深度睡眠工作
REF_TICK1MHz特殊应用同步系统时钟

此处一般为自动

使用API配置PWM

ledc_timer_config用于初始化用到的定时器 ()

ledc_timer_config(&On_Board_LED_GPIO_Timer_change);//调用API配置PWM
ledc_timer_config(&On_Board_LED_GPIO_Timer_change);//调用API配置PWM

通道初始化

指定配置的通道,及其绑定的定时器与GPIO,并指明定时器频率,中断类型和信号占空比

ledc_channel_config_t On_Board_LED_GPIO_Timer_channel_change = {
    .channel = 0, //GPIO绑定的通道
    .gpio_num = On_Board_LED_GPIO, //绑定的GPIO引脚
    .timer_sel = 0, //所绑定的定时器
    .speed_mode = LEDC_LOW_SPEED_MODE, //定时器频率(与所绑定的定时器频率相同)
    .duty = 0, //占空比
    .intr_type = LEDC_INTR_DISABLE, //中断类型
};
ledc_channel_config_t On_Board_LED_GPIO_Timer_channel_change = {
    .channel = 0, //GPIO绑定的通道
    .gpio_num = On_Board_LED_GPIO, //绑定的GPIO引脚
    .timer_sel = 0, //所绑定的定时器
    .speed_mode = LEDC_LOW_SPEED_MODE, //定时器频率(与所绑定的定时器频率相同)
    .duty = 0, //占空比
    .intr_type = LEDC_INTR_DISABLE, //中断类型
};

使用API进行绑定

ledc_channel_config用于初始化ledc输出通道 以及将timer关联起来

ledc_channel_config(&ledc_channel)
ledc_channel_config(&ledc_channel)

这样对应的通道就能输出PWM频率了(上例 为 On_Board_LED_GPIO这个GPIO接口 输出PWM )

计时计数

IIC

协议:注意!注意!新旧I2C协议代码不通用!不兼容!旧版 "driver/i2c.h" 新版 "driver/i2c_master.h"

配置流程:

写初始化I2C函数,写I2C对寄存器读写函数(用于寄存器初始化函数中),主函数调用I2C初始化函数与寄存器初始化函数

①配置I2C:

创建I2C初始化结构体->配置参数->安装I2C驱动

②寄存器读写函数->寄存器初始化函数

需要用到寄存器值读写

③ 在主函数中调用I2C初始化函数与寄存器初始化函数

文件中创建结构体初始化I2C

i2c_config_t conf = {
    .mode = I2C_MODE_MASTER, // 确定主从模式
    .sda_io_num = I2C_MASTER_SDA_IO, // 配置 SDA 的 GPIO
    .sda_pullup_en = GPIO_PULLUP_ENABLE, //打开上拉电阻,避免浮空
    .scl_io_num = I2C_MASTER_SCL_IO, // 配置 SCL 的 GPIO
    .scl_pullup_en = GPIO_PULLUP_ENABLE, //打开上拉电阻,避免浮空
    .master.clk_speed = I2C_MASTER_FREQ_HZ, // 为项目选择 频率
};
i2c_param_config(BSP_I2C_NUM, &i2c_conf); // 配置参数 // BSP_I2C_NUM是具体的I2C编号,宏定义一般为0或者1
return i2c_driver_install(BSP_I2C_NUM, i2c_conf.mode, 0, 0, 0); // 安装驱动
i2c_config_t conf = {
    .mode = I2C_MODE_MASTER, //确定主从模式
    .sda_io_num = I2C_MASTER_SDA_IO, // 配置 SDA 的 GPIO
    .sda_pullup_en = GPIO_PULLUP_ENABLE, //打开上拉电阻,避免浮空
    .scl_io_num = I2C_MASTER_SCL_IO, // 配置 SCL 的 GPIO
    .scl_pullup_en = GPIO_PULLUP_ENABLE, //打开上拉电阻,避免浮空
    .master.clk_speed = I2C_MASTER_FREQ_HZ, // 为项目选择频率
};
i2c_param_config(BSP_I2C_NUM, &i2c_conf);//配置参数 
//BSP_I2C_NUM是具体的I2C编号,宏定义一般为0或者1
return i2c_driver_install(BSP_I2C_NUM, i2c_conf.mode, 0, 0, 0);//安装驱动)

通过I2C对寄存器值进行读取写入(用于寄存器初始化函数中)

BSP_I2C_NUM依旧是使用的I2C外设编号,SENSOR_ADDR是寄存器的地址,I2C通过地址找到寄存器并进行读写操作。

// 读取寄存器的值
esp_err_t I2C_register_read(uint8_t reg_addr, uint8_t *data, size_t len){
    return i2c_master_write_read_device(BSP_I2C_NUM,SENSOR_ADDR, &reg_addr, 1, data, len, 1000 / portTICK_PERIOD_MS);
}

// 给寄存器写值
esp_err_t I2C_register_write_byte(uint8_t reg_addr, uint8_t data){
    uint8_t write_buf[2] = {reg_addr, data};
    return i2c_master_write_to_device(BSP_I2C_NUM, SENSOR_ADDR, write_buf, sizeof(write_buf), 1000 / portTICK_PERIOD_MS);
}
// 读取寄存器的值
esp_err_t I 2C _register_read(uint8_t reg_addr, uint8_t *data, size_t len) {
    return i2c_master_write_read_device(BSP_I2C_NUM,SENSOR_ADDR, &reg_addr, 1, data, len, 1000 / portTICK_PERIOD_MS);
}
// 给寄存器写值
esp_err_t I 2C _register_write_byte(uint8_t reg_addr, uint8_t data) {
    uint8_t write_buf[2] = {reg_addr, data};
    return i2c_master_write_to_device(BSP_I2C_NUM, SENSOR_ADDR, write_buf, sizeof(write_buf), 1000 / portTICK_PERIOD_MS);
}

在主函数中调用初始化函数

ESP_ERROR_CHECK 是一个宏函数,位于 esp-idf 文件中,用于检测执行函数的返回结果,如果没有错误,什么事情都不会发生,如果有错误,会引起单片机重启。使用这个宏函数,需要包含 esp_err.h 头文件。

ESP_ERROR_CHECK(bsp_i2c_init());
ESP_LOGI(TAG, "I2C initialized successfully"); //打印日志
sensor_init(); // 初始化 寄存器 芯片
ESP_ERROR_CHECK(bsp_i2c_init());ESP_LOGI(TAG, "I2C initialized successfully"); //打印日志sensor_init(); // 初始化寄存器芯片)

串口(UART)

使用方法与其他的配置大同小异
使用流程:创建配置结构体->调用结构体进行配置->安装驱动(设置RX/TX缓冲区)->读取/发送

创建配置结构体

baud_rate波特率设置数据传输速率,单位是每秒比特数(bps)
data_bits数据位定义每个数据包的位数
parity校验位错误检测机制,检测单比特错误
stop_bits停止位标识数据包结束的位数
flow_ctrl流控制防止数据溢出的硬件流控
source_clk时钟源选择 UART 的基准时钟源

这里对流控制进一步解释(因为我也不会)
流控制的作用:

  • 防止数据溢出:当接收方缓冲区满时,通过流控制信号让发送方暂停传输。
  • 保证传输可靠性:避免高速发送导致低速接收设备无法处理数据。
  • 适应不同硬件性能:协调不同处理速度的设备(如MCU与PC)之间的通信。

硬件流控制(通过物理引脚信号控制数据流):

  • RTS(RequestToSend):接收方准备好接收数据时,拉高此信号。
  • CTS(ClearToSend):发送方检测到CTS有效时,才允许发送数据。
uart_config_t uart_config = {
    .baud_rate = 115200, // 常用波特率
    .data_bits = UART_DATA_8_BITS, // 标准数据位
    .parity = UART_PARITY_DISABLE, // 无校验
    .stop_bits = UART_STOP_BITS_1, // 1停止位
    .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,// 禁用流控
    .source_clk = UART_SCLK_DEFAULT // 使用 APB 时钟
};
uart_config_t uart_config = {
    .baud_rate = 115200, // 常用波特率
    .data_bits = UART_DATA_8_BITS, // 标准数据位
    .parity = UART_PARITY_DISABLE, // 无校验
    .stop_bits = UART_STOP_BITS_1, // 1停止位
    .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,// 禁用流控
    .source_clk = UART_SCLK_DEFAULT // 使用 APB 时钟
};

调用API配置结构体

这里 UART_PORT_NUM 是 串口号 ( 选择 哪个 串口 )( ESP32芯片通常包含3个独立的UART硬件控制器,串口0默认串口下载,串口1一般为FLASH的引脚(可通过GPIO交换矩阵更改引脚),串口2常用 )

uart_param_config(UART_PORT_NUM, &uart_config)
uart_param_config(UART_PORT_NUM, &uart_config)

安装UART驱动

串口号-发送缓存大小-接收缓存大小-队列长度-队列句柄(传回来用的)-中断标志(一般使用0,使用默认配置)

uart_driver_install(UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags)
uart_driver_install(UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags)

读取串口数据

这函数有返回值:如果成功,则返回读取的字节数;失败则返回-1。串口号-缓冲区-缓冲区大小-等待数据的超时时间 portMAX_DELAY表示无限等待

uart_read_bytes(UART_PORT_NUM,BUF_SIZE, uint32_t buf_size, TickType_t ticks_to_wait);
uart_read_bytes(UART_PORT_NUM,BUF_SIZE, uint32_t buf_size, TickType_t ticks_to_wait);

发送串口数据

串口号-待发送数据的源地址-发送数据的字节数

uart_write_bytes( U ART_PORT_NUM,src,size);
uart_write_bytes(UART_PORT_NUM,src,size);

更改串口引脚(可选)

通过GPIO矩阵,将默认的串口引脚更改。若更改,则需在安装驱动前配置该项。

uart_set_pin(UART_PORT_NUM, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS) // 串口号 实际的发送GPIO 实际的接收GPIO 后壁那两个用于流控,-1为关闭
uart_set_pin(UART_PORT_NUM, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS)//串口号 实际的发送GPIO 实际的接收GPIO 后壁那两个用于流控,-1为关闭)

WiFi

STA模式

STA模式:连接已有的网络,ESP32作为终端设备
使用流程:总流程:创建并初始化函数->监听设备事件->分情况调用回调函数进行各项初始化和配置
初始化流程:初始化NVS(存储SSID和密码),初始化TCP/IP栈(使用esp_netif_init),初始化事件系统循环(存储系统事件,包括WIFI MQTT BLU等),初始化WIFI,设置事件回调函数并监听事件

初始化NVS,TCP/IP,系统事件循环

esp_netif_init() ; // 初始化TCP/IP栈
esp_event_loop_create_default() ; // 初始化事件系统循环
nvs_flash_init() ; // 初始化NVS
esp_netif_init(); //初始化TCP/IP栈esp_event_loop_create_default();//初始化事件系统循环nvs_flash_init(); //初始化NVS)

初始化WIFI

先定义个WIFI初始化结构体->再创建个设置STA的初始化结构体->调用API进行初始化(WIFI和WIFI设置)->启动WIFI

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); // 创建了一个WIFI初始化结构体
ESP_ERROR_CHECK(esp_wifi_init(&cfg)); // 调用API利用结构体初始化WIFI
wifi_config_t wifi_config ={
    .sta = {
        .ssid = DEFAULT_WIFI_SSID, //WIFI的SSID
        .password = DEFAULT_WIFI_PASSWORD, //WIFI密码
        .threshold.authmode = WIFI_AUTH_WPA2_PSK, //加密方式
        .pmf_cfg = // PMF(Protected Management Frames,受保护管理帧)是 Wi-Fi 安全标准(802.11w)中的一项功能,用于保护无线管理帧,防止被恶意攻击者伪造或篡改
        {
            .capable = true, // 设备支持PMF功能
            .required = false // 连接时不强制要求对方也支持 PMF
        },
    },
};
esp_wifi_set_mode(WIFI_MODE_STA) ; // 使用SAT模式
esp_wifi_set_config(WIFI_IF_STA, &wifi_config) // 调用API设置WIFI
esp_wifi_start() ; //启动WIFI
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();//创建了一个WIFI初始化结构体ESP_ERROR_CHECK(esp_wifi_init(&cfg)); //调用API利用结构体初始化WIFI
wifi_config_t wifi_config =
{ 
    .sta = 
        { 
        .ssid = DEFAULT_WIFI_SSID, //WIFI的SSID 
        .password = DEFAULT_WIFI_PASSWORD, //WIFI密码 
        .threshold.authmode = WIFI_AUTH_WPA2_PSK,//加密方式 
        .pmf_cfg = //PMF(Protected Management Frames,受保护管理帧)是 Wi-Fi 安全标准(802.11w)中的一项功能,用于保护无线管理帧,防止被恶意攻击者伪造或篡改 
        {
            .capable = true, //设备支持PMF功能 
            .required = false //连接时不强制要求对方也支持 PMF }
        }
};
esp_wifi_set_mode(WIFI_MODE_STA); //使用SAT模式
esp_wifi_set_config(WIFI_IF_STA, &wifi_config) //调用API设置WIFI
esp_wifi_start(); //启动WIFI)

设置事件回调函数

WIFI事件的回调函数
事件回调函数有以下几个要素:1. 有个能被调用的“注册回调函数的接口”(就是将回调函数与事件进行绑定)2. 有个回调函数(被调用之后产生的动作)
WIFI需要注册多个回调函数:WIFI和网络(IP_EVENT)
这里的事件回调函数,主要是为了判断WIFI的各个事件段该做什么,比如未连接WiFi怎么办,获取ip之后怎么办。

static void event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data) //上边 event_base 是监听到的大事件(WIFI BLU IP等)(就是下方 esp_event_handler_register 监听到的) // event_id表示同一事件类别下的具体事件标识符 , event_data 是事件附加的数据
{
    if( e vent_base == WIFI_EVENT) // 监测WIFI事件
    {
        swit c h( e vent_id )
        {
            case WIFI_EVENT_STA_START: // IF 启动了STA模式
                esp_wifi_connect( ); // 连接WIFI(调用上方设置的WiFi配置)
                break ;
            case WIFI_EVENT_STA_CONNECTED:
                ESP_LOGI( T AG, " ESP CONNECTED TO AP " )
                break;
            case WIFI_EVENT_STA_ DIS CONNECTED: // 没连上就反复连接
                esp_wifi_connect(); // 这里实际上应该在多次失败之后,等待一段时间再连接
                break;
            default:
                b reak;
        }
        if(event_base == IP _EVENT)
        {
            switch(event_id)
            {
                case IP_EVENT_STA_GOT_IP : // 获取到了IP
                    ESP_LOGI(TAG,"ESP GET IP ")
                    break;
                default:
                    break;
            }
        }
    }
}
// 注册 回调函数( 将事件与回调函数绑定 )(定义的名字是 event_handler )
esp_event_handler_register (WIFI_EVENT,ESP_EVENT_ANY_ID,&event_handler,NULL)); 
//监听的事件类型,监听该类型的哪些事件,事件的处理函数(该函数需要处理的程序)(这里名字是 event_handler ),自定义参数
esp_event_handler_register( IP _EVENT, IP _EVENT_ STA_GOT_IP ,&event_handler,NULL)); 
//上边这个函数是将回调函数与“STA获得IP“这个事件进行绑定
static void event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
//上边event_base是监听到的大事件(WIFI BLU IP等)(就是下方esp_event_handler_register监听到的)
//event_id表示同一事件类别下的具体事件标识符,event_data是事件附加的数据
{ 
    if(event_base == WIFI_EVENT) //监测WIFI事件 
    {
         switch(event_id) 
        { 
            case WIFI_EVENT_STA_START: //IF启动了STA模式 
                esp_wifi_connect(); //连接WIFI(调用上方设置的WiFi配置) 
                break; 
            case WIFI_EVENT_STA_CONNECTED: 
                ESP_LOGI(TAG,"ESP CONNECTED TO AP") 
                break; 
            case WIFI_EVENT_STA_DISCONNECTED: //没连上就反复连接 
                esp_wifi_connect(); //这里实际上应该在多次失败之后,等待一段时间再连接 
                break; 
            default:break; 
    } 
    if(event_base == IP_EVENT) 
    { 
        switch(event_id) 
        { 
            case IP_EVENT_STA_GOT_IP: //获取到了IP
                ESP_LOGI(TAG,"ESP GET IP") 
                break; 
            default:break; 
        } 
    }
}//注册回调函数(将事件与回调函数绑定)(定义的名字是event_handler)esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&event_handler,NULL));//监听的事件类型,监听该类型的哪些事件,事件的处理函数(该函数需要处理的程序)(这里名字是event_handler),自定义参数
esp_event_handler_register(IP_EVENT,IP_EVENT_STA_GOT_IP,&event_handler,NULL));//上边这个函数是将回调函数与“STA获得IP“这个事件进行绑定)

一键配网模式(SmartConfig)

MQTT

基于TCP/IP的低带宽需求报文传输服务
使用流程:①创建MQTT句柄(通过这个对MQTT进行操作)->创建并调用结构体(存储初始化MQTT的参数)->②->注册回调函数->启用MQTT ②配置事件回调函数,对MQTT各个状态进行反应(对订阅、发布、接收数据等进行操作)

①MQTT基础设置

创建MQTT句柄

这里实际上创建了一个MQTT客户端,名为s_mqtt_client后续所有对MQTT客户端的操作,都使用s_mqtt_client

esp_mqtt_client_handle_t s_mqtt_client = NULL;
esp_mqtt_client_handle_t s_mqtt_client = NULL;

创建并填充结构体

esp_mqtt_client_config_t mqtt_cfg = {0}; // 这些参数全是字符型
mqtt_cfg.broker.address.uri = MQTT_ADDRESS; //MQTT地址
mqtt_cfg.broker.address.port = MQTT_PORT ; //MQTT端口(一般为1883)
mqtt_cfg . credentials . client_id = MQTT_CLIENT; // 客户端MQTT的ID(自定义不能重复)
mqtt_cfg.credentials.username = MQTT_USERNAME; // MQTT用户名
mqtt_cfg.credentials.authentication.password = MQTT_PASSWORD; //密码
esp_mqtt_client_config_t mqtt_cfg = {0};//这些参数全是字符型
mqtt_cfg.broker.address.uri = MQTT_ADDRESS; //MQTT地址
mqtt_cfg.broker.address.port = MQTT_PORT; //MQTT端口(一般为1883)
mqtt_cfg.credentials.client_id = MQTT_CLIENT;//客户端MQTT的ID(自定义不能重复)
mqtt_cfg.credentials.username = MQTT_USERNAME; //MQTT用户名
mqtt_cfg.credentials.authentication.password = MQTT_PASSWORD;//密码)

调用配置结构体 并 初始化客户端

s_mqtt_client = esp_mqtt_client_init(&mqtt_cfg); // 初始化客户端并获取句柄
s_mqtt_client = esp_mqtt_client_init(&mqtt_cfg);// 初始化客户端并获取句柄)

注册回调函数

为 MQTT 客户端注册事件回调函数。具体来说:1. 调用 esp_mqtt_client_register_event() 来关联 MQTT 客户端 s_mqtt_client 和事件回调函数aliot_mqtt_event_handler。2. 指定 ESP_EVENT_ANY_ID 表示接收所有类型的 MQTT 事件(例如连接、断开、发布、订阅、接收数据等)。3. 回调函数中可以根据事件类型(event_id)做出相应处理,例如记录日志、修改连接状态或发布配置信息。4. 最后传入 s_mqtt_client 作为用户数据,这样在回调中可以访问到该 MQTT 客户端句柄或相关信息。

esp_mqtt_client_register_event(s_mqtt_client, ESP_EVENT_ANY_ID, aliot_mqtt_event_handler, s_mqtt_client); //为 MQTT 客户端注册事件回调函数(将MQTT客户端与MQTT回调函数绑定)
esp_mqtt_client_register_event(s_mqtt_client, ESP_EVENT_ANY_ID, aliot_mqtt_event_handler, s_mqtt_client);//为 MQTT 客户端注册事件回调函数(将MQTT客户端与MQTT回调函数绑定)

启用MQTT

esp_mqtt_client_start(s_mqtt_client); ( 这里其实调用了MQTT客户端 s_mqtt_client )
esp_mqtt_client_start(s_mqtt_client);(这里其实调用了MQTT客户端s_mqtt_client)

②配置MQTT事件回调函数

可以用这个回调函数直接读取MQTT订阅的数据
配置方式与WIFI大差不差

static void aliot_mqtt_event_handler(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
    esp_mqtt_event_handle_t event = event_data; // 获取MQTT接收到的数据 // 上方 这行代码的本质是将通用指针转换为 MQTT 事件数据结构指针,以便提取事件信息。
    switch ((esp_mqtt_event_id_t)event_id)
    {
        case MQTT_EVENT_CONNECTED: // 连接上MQTT后的操作
            ESP_LOGI(TAG, "mqtt connected");
            s_is_mqtt_connected = true;
            esp_mqtt_client_subscribe_single(s_mqtt_client, MQTT_SUBSCRIBE_TOPIC, 1); // 订阅主题。第一个参数是定义的MQTT句柄,第二个是订阅的主题,第三个是QoS , 表示消息至少送达一次。
            // 发布 Home Assistant 传感器配置消息
            publish_sensor_config();
            break;
        case MQTT_EVENT_DISCONNECTED: // 未 连接上MQTT后的操作
            ESP_LOGI(TAG, "mqtt disconnected");
            s_is_mqtt_connected = false;
            break;
        case MQTT_EVENT_SUBSCRIBED: // 订阅后的操作
            ESP_LOGI(TAG, "mqtt subscribed, msg_id=%d", event->msg_id);
            break;
        case MQTT_EVENT_PUBLISHED: // 发布后的操作
            ESP_LOGI(TAG, "mqtt publish ack, msg_id=%d", event->msg_id);
            break;
        case MQTT_EVENT_DATA: // 通过MQTT获取的数据
            ESP_LOGI(TAG, "mqtt data received");
            printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
            printf("DATA=%.*s\r\n", event->data_len, event->data); // event见下方注
            break;
        case MQTT_EVENT_ERROR: // MQTT事件IF错误的操作
            ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
            break;
        default:
            break;
    }
}
static void aliot_mqtt_event_handler(void* event_handler_arg,esp_event_base_t,event_base,int32_t event_id,void* event_data) 
{ 
    esp_mqtt_event_handle_t event = event_data; / / 获取MQTT接收到的数据 
    / / 上方这行代码的本质是将通用指针转换为 MQTT 事件数据结构指针,以便提取事件信息。 
    switch ((esp_mqtt_event_id_t)event_id) 
    { 
        case MQTT_EVENT_CONNECTED: //连接上MQTT后的操作 
            ESP_LOGI(TAG, "mqtt connected"); 
            s_is_mqtt_connected = true; 
            esp_mqtt_client_subscribe_single(s_mqtt_client, MQTT_SUBSCRIBE_TOPIC, 1);
//订阅主题。第一个参数是定义的MQTT句柄,第二个是订阅的主题,第三个是QoS,表示消息至少送达一次。 
// 发布 Home Assistant 传感器配置消息 
            publish_sensor_config(); 
            break; 
        case MQTT_EVENT_DISCONNECTED: //未连接上MQTT后的操作 
            ESP_LOGI(TAG, "mqtt disconnected"); 
            s_is_mqtt_connected = false; 
            break; 
        case MQTT_EVENT_SUBSCRIBED: //订阅后的操作 
            ESP_LOGI(TAG, "mqtt subscribed, msg_id=%d", event->msg_id); 
            break; 
        case MQTT_EVENT_PUBLISHED: //发布后的操作 
            ESP_LOGI(TAG, "mqtt publish ack, msg_id=%d", event->msg_id); 
            break; 
        case MQTT_EVENT_DATA: //通过MQTT获取的数据 
            ESP_LOGI(TAG, "mqtt data received"); 
            printf("TOPIC=%.*s\r\n", event->topic_len, event->topic); 
            printf("DATA=%.*s\r\n", event->data_len, event->data);
            //event见下方注 
            break; 
        case MQTT_EVENT_ERROR: //MQTT事件IF错误的操作 
            ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); 
            break; 
        default:break; 
})

注:

在 MQTT 事件回调函数中,传入的 event 对象其实是一个 esp_mqtt_event_t 结构体指针,它包含了多个字段,常用的有: 
- msg_id:消息的编号,用于标识发布、订阅等操作对应的消息。 
- topic:指向 MQTT 消息主题的指针(当接收数据时有效)。 
- topic_len:主题字符串的长度。 
- data:指向 MQTT 消息数据的指针,对于 MQTT_EVENT_DATA 事件,该字段保存了接收到的消息内容。
- data_len:消息数据的长度。 
- total_data_len:在分段接收数据的情况下,总的数据长度。 
- client:MQTT 客户端的句柄。 
- error_handle:在发生错误时可能包含错误相关的信息。 这些字段结合起来,可以通过 event 对象获取到当前 MQTT 事件的详细信息,从而在回调中根据不同的事件类型进行相应处理。

使用MQTT 发送 数据

发送流程:NVS 是 Non-Volatile Storage(非易失性存储)的缩写,是 ESP32 用来持久化保存数据的一种机制。它类似于“小型文件系统”,常用于保存 WiFi 配置、密钥、参数等,即使断电数据也不会丢失。此处,NVS 主要用于保证系统能正常保存和读取配置 信息。

esp_err_t ret = nvs_flash_init();
if ( ret == ESP_ERR_NVS_NO_FREE_PAGE||ret == ESP_ERR_NVS_NEW_VERSION_FOUND) //NVS出现错误,执行擦除
    ESP_ERROR_CHECK(nvs_flash_erase()); //重新尝试初始化
    ESP_ERROR_CHECK(nvs_flash_init()); }
esp_err_t ret = nvs_flash_init(); 
if (ret == ESP_ERR_NVS_NO_FREE_PAGE||ret == ESP_ERR_NVS_NEW_VERSION_FOUND) //NVS出现错误,执行擦除 
ESP_ERROR_CHECK(nvs_flash_erase()); //重新尝试初始化 
ESP_ERROR_CHECK(nvs_flash_init()); })

初始化WIFI
传入回调函数,用于通知连接成功事件

wifi_sta_init(wifi_event_handler);
wifi_sta_init(wifi_event_handler);

监听WIFI连接事件
直到WiFi连接成功后,才启动MQTT连接

ev=xEventGroupWaitBits(s_wifi_ev,WIFI_CONNECT_BIT,pdTRUE,pdFALSE,portMAX_DELAY);
if(ev & WIFI_CONNECT_BIT)
{
    mqtt_start();
}
ev=xEventGroupWaitBits(s_wifi_ev,WIFI_CONNECT_BIT,pdTRUE,pdFALSE,portMAX_DELAY); if(ev & WIFI_CONNECT_BIT) { mqtt_start(); })

发布数据

//延时2秒发布一条消息到/test/topic1主题
while(1)
{
    int count = 0;
    if(s_is_mqtt_connected)
    {
        snprintf(mqtt_pub_buff,64,"{\"count\":\"%d\"}",count); // 将数据发送到缓冲区
        esp_mqtt_client_publish(s_mqtt_client, MQTT_PUBLIC_TOPIC, mqtt_pub_buff, strlen(mqtt_pub_buff),1, 0); // 通过 MQTT 客户端,将刚刚生成的 JSON 字符串发布到主题
        count++;
    }
    vTaskDelay(pdMS_TO_TICKS(2000));
}
//延时2秒发布一条消息到/test/topic1主题 
while(1) 
{
    int count = 0; 
    if(s_is_mqtt_connected) 
    { 
        snprintf(mqtt_pub_buff,64,"{\"count\":\"%d\"}",count);//将数据发送到缓冲区         esp_mqtt_client_publish(s_mqtt_client, MQTT_PUBLIC_TOPIC, mqtt_pub_buff, strlen(mqtt_pub_buff),1, 0);//通过 MQTT 客户端,将刚刚生成的 JSON 字符串发布到主题 count++; 
} 
    vTaskDelay(pdMS_TO_TICKS(2000)); 
})

JSON

JSON是一种规范好的数据格式, 本质上就是字符串 。用于传输数据信息,基于“键-值对“,可以包含对象、数组、字符串、数字、布尔、NULL这六种最基本的数据。
ESP32中可以使用cJSON数据解析器来分析和打包JSON数据,需要调用"cJSON.h"。
JSON的基本格式如下

{
    "键名" : "值",
    "键名" : {
        "键名" : "值",
        "键名" : "值",
    },
    "键名" : "值",
    "键名" : 数字,
    "键名" : ["值","值","值",]
}
{ "键名" : "值", "键名" : { "键名" : "值", "键名" : "值", }, "键名" : "值", "键名" : 数字, "键名" : ["值","值","值",] })

解析JSON流程:

解析JSON->……>解析JSON->读取键的数据->读取数据的值

解析JSON并保存

本文档中只说明ESP32中的情况

cJson* js_get_from_mqtt = cJSON_Pqrse(json_str); //创建了个名为 js_get_from_mqtt的cJSON格式的指针 //将 json_str(这是个字符串)通过函数cJSON_Pqrse解析,然后复制到js_get_from_mqtt中
cJson* js_get_from_mqtt = cJSON_Pqrse(json_str);//创建了个名为js_get_from_mqtt的cJSON格式的指针//将json_str(这是个字符串)通过函数cJSON_Pqrse解析,然后复制到js_get_from_mqtt中)

读取JSON中某个键的数据

这里的数据,可以是具体的数值和字符串,也可以是对象。若JSON层层嵌套,则需层层解析。重复使用 cJSON_GetObjectItem,一层一层解析 , 以下操作也同理

cJson* Humidity_get_from_js =cJSON_ GetObjectItem( js_get_from_mqtt , " Humidity " ) ; // 读取 js_get_from_mqtt 中 Humidity 这个项目的数据,将其赋值到 Humidity_get_from_js
cJson* Humidity_get_from_js=cJSON_GetObjectItem(js_get_from_mqtt,"Humidity");//读取js_get_from_mqtt中Humidity这个项目的数据,将其赋值到Humidity_get_from_js)

读取获取数据的值

如果要获取数据的值为字符串,则用cJSON_GetStringValue()
如果是数值,则用cJSON_GetNumberValue()

Humidity _val = cJSON_Get NumberValue (Humidity_get_from_js); //把 Humidity_get_from_js 的数据转换成具体的数字,赋值到 Humidity_val 中
Humidity_val = cJSON_GetNumberValue(Humidity_get_from_js);//把Humidity_get_from_js的数据转换成具体的数字,赋值到Humidity_val中)

生成JSON流程:

创建cJSON格式的对象->向对象中添加键-值

创建cJSON格式的对象

这里创建了个名为 js _obj _tx_humidity的cJSON对象。

cJSON *js _obj _tx_humidity = cJSON_CreatObjecte();
cJSON *js _obj _tx_humidity = cJSON_CreatObjecte();)

向对象中添加键-值

这里向 js _obj _tx_humidity这个对象中添加的键-值为 "Humidity","%d"
然后向 js _obj _tx_humidity这个对象中又添加了一个对象js _obj _sys

cJSON_ Add NumberTo Object(js_obj_tx_humidity , " H umidity " , humidity ) ; // 自动识别数据类型
cJSON_Add Item ToObject(js_obj_tx_humidity," sys ",js_obj_sys);
cJSON_AddNumberToObject(js_obj_tx_humidity,"Humidity",humidity);//自动识别数据类型cJSON_AddItemToObject(js_obj_tx_humidity,"sys",js_obj_sys);)

WS2812

以下是调用已有配置对WS2812进行驱动单总线控制的彩色LED灯珠 ,因为对控制时序比较严格,故使用RMT红外控制。同GPIO等操作方式,需先使用结构体进行初始化,再使用API通过结构体配置。
Tips:使用"led_strip.h"
使用流程(基于官方Example):创建结构体(共两个)->调用API进行配置->使用函数配置亮度颜色等参数

结构体创建

先创建LED的配置结构体和被操控的LED对象

static led_strip_handle_t led_strip; // 创建句柄名为 led_strip
led_strip_config_t WS2812_ strip_config = {
    .strip_gpio_num = BLINK_GPIO, // 灯珠DATA所连接的GPIO引脚
    .max_leds = 1, // 最大灯珠数量
};
static led_strip_handle_t led_strip; / / 创建句柄名为led_stripled_strip_config_t WS2812_strip_config = { .strip_gpio_num = BLINK_GPIO, //灯珠DATA所连接的GPIO引脚 .max_leds = 1, //最大灯珠数量};)

再创建RMT的配置结构体

led_strip_rmt_config_t rmt_config = {
    .resolution_hz = 10 * 1000 * 1000, // 10MHz //RMT的频率
    .flags.with_dma = false, // 是否启用DMA
};
led_strip_rmt_config_t rmt_config = { .resolution_hz = 10 * 1000 * 1000, // 10MHz //RMT的频率 .flags.with_dma = false, //是否启用DMA};)

调用API进行配置

使用led_strip_new_rmt_device函数利用传递进来两个参数(LED和RMT的配置结构体),生成到 先前创建的 LED对象( led_strip )中

led_strip_new_rmt_device(&WS2812_strip_config, &rmt_config, &led_strip) ;
led_strip_new_rmt_device(&WS2812_strip_config, &rmt_config, &led_strip);)

配置LED亮度等参数

对生成的对象(led_strip),进行参数配置;配置后刷新LED状态;

/* Set the LED pixel using R LED GB from 0 (0%) to 255 (100%) for each color */
led_strip_set_pixel(led_strip, 0, 16, 16, 16);
/* Refresh the strip to send data */
led_strip_refresh(led_strip);
/* Set the LED pixel using RLEDGB from 0 (0%) to 255 (100%) for each color */led_strip_set_pixel(led_strip, 0, 16, 16, 16);/* Refresh the strip to send data */led_strip_refresh(led_strip);)

这样WS2812就会常亮了(不基于BLink例程)

以下是从RMT驱动开始,驱动WS2812
基于此帖子( 【ESP32 IDF】用RMT控制 WS2812 彩色灯带 - 东邪独孤 - 博客园 )

DHT11

单总线工作的数字温湿度传感器,工作范围“-20~60摄氏度”"5%~90%"
DHT11基于单总线,同样利用RMT红外进行控制(接收数据)。
工作流程:定义RMT接收用结构体->调用RMT接收通道API->使用队列接收数据->接收完使用回调至接收模式

初始化DHT11

rmt_rx_channel_config_t rx_chan_config = {
    .clk_src = RMT_CLK_SRC_APB, // 选择时钟源
    .resolution_hz = 1000 * 1000, // 1 MHz 滴答分辨率,即 1 滴答 = 1 µs
    .mem_block_symbols = 64, // 内存块大小,即 64 * 4 = 256 字节
    .gpio_num = dht11_pin, // GPIO 编号
    .flags.invert_in = false, // 不反转输入信号
    .flags.with_dma = false, // 不需要 DMA 后端(ESP32S3才有)
};
rmt_rx_channel_config_t rx_chan_config = 
{ 
    .clk_src = RMT_CLK_SRC_APB, // 选择时钟源 
    .resolution_hz = 1000 * 1000, // 1 MHz 滴答分辨率,即 1 滴答 = 1 µs
    .mem_block_symbols = 64, // 内存块大小,即 64 * 4 = 256 字节 
    .gpio_num = dht11_pin, // GPIO 编号
    .flags.invert_in = false, // 不反转输入信号 
    .flags.with_dma = false, // 不需要 DMA 后端(ESP32S3才有) 
};

调用rmt接受通道

//创建rmt接收通道
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan_handle));
//使能RMT接收通道
ESP_ERROR_CHECK(rmt_enable(rx_chan_handle))
//创建rmt接收通道 ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan_handle)); //使能RMT接收通道ESP_ERROR_CHECK(rmt_enable(rx_chan_handle)))

使用队列接收数据

//新建接收数据队列
rx_receive_queue = xQueueCreate(20, sizeof(rmt_rx_done_event_data_t));
//assert用于检测队列是否创建成功
assert(rx_receive_queue);

//接收完成回调函数
static bool IRAM_ATTR example_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
    BaseType_t high_task_wakeup = pdFALSE; // 标记发送队列后 无需 唤醒高优先级任务。
    QueueHandle_t rx_receive_queue = (QueueHandle_t)user_data; // 将 user_data 强制转换为 QueueHandle_t 类型,以获取预先传入的FreeRTOS队列句柄 , 将收到的 RMT 符号发送到解析器任务
    xQueueSendFromISR(rx_receive_queue, edata, &high_task_wakeup);//向队列头部发送一个消息的中断版本
    return high_task_wakeup == pdTRUE; // 返回需要触发任务切换
}
//新建接收数据队列 
rx_receive_queue = xQueueCreate(20, sizeof(rmt_rx_done_event_data_t)); / / assert用于检测队列是否创建成功 
assert(rx_receive_queue); //接收完成回调函数 
static bool IRAM_ATTR example_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data) 
{
    BaseType_t high_task_wakeup = pdFALSE; / / 标记发送队列后无需唤醒高优先级任务。
    QueueHandle_t rx_receive_queue = (QueueHandle_t)user_data;//将 user_data 强制转换为    
    QueueHandle_t 类型,以获取预先传入的FreeRTOS队列句柄,将收到的 RMT 符号发送到解析器任务
    xQueueSendFromISR(rx_receive_queue, edata, &high_task_wakeup);//向队列头部发送一个消息的中断版本
     return high_task_wakeup == pdTRUE;//返回需要触发任务切换 
}

将RMT读取到的脉冲数据处理为温度和湿度

static int parse_items(rmt_symbol_word_t *item, int item_num, int *humidity, int *temp_x10){};
static int parse_items(rmt_symbol_word_t *item, int item_num, int *humidity, int *temp_x10){};)

脉冲信号处理,复制即可

调整脉冲信号以接受DHT11数据

int DHT11_StartGet(int *temp_x10, int *humidity){}
int DHT11_StartGet(int *temp_x10, int *humidity){}

脉冲信号调整部分复制即可

调整后接收数据

static rmt_symbol_word_t raw_symbols[128];//接收缓存
static rmt_rx_done_event_data_t rx_data;//实际接收到的数据
ESP_ERROR_CHECK(rmt_receive(rx_chan_handle, raw_symbols, sizeof(raw_symbols), &receive_config));
// wait for RX done signal
if (xQueueReceive(rx_receive_queue, &rx_data, pdMS_TO_TICKS(1000)) == pdTRUE)
{
    //解析接收符号并打印结果
    return parse_items(rx_data.received_symbols, rx_data.num_symbols,humidity, temp_x10);
}
return 0;
static rmt_symbol_word_t raw_symbols[128];//接收缓存
static rmt_rx_done_event_data_t rx_data;//实际接收到的数据ESP_ERROR_CHECK(rmt_receive(rx_chan_handle, raw_symbols, sizeof(raw_symbols), &receive_config));
// wait for RX done 
signalif (xQueueReceive(rx_receive_queue, &rx_data, pdMS_TO_TICKS(1000)) == pdTRUE) 
{//解析接收符号并打印结果
    return parse_items(rx_data.received_symbols, rx_data.num_symbols,humidity, temp_x10);
} 
return 0;
)

LCD

液晶显示屏
这里采用 液晶屏驱动芯片ST7789,采用SPI通信方式与ESP32连接.由于esp32的引脚数量少,可以使用寄存器 PCA9557,对远程位置上的输入输出控制和状态监控,用于解决微控制器I/O资源不足的问题,并允许通过简单的I2C接口实现复杂的I/O操作。液晶屏的LCD_CS引脚由IO扩展芯片pca9557控制,所以需要先初始化pca9557,而pca9557是i2c通信芯片,所以又需要先初始化i2c。


BSP_I2C_NUM依旧是使用的I2C外设编号,SENSOR_ADDR是寄存器的地址,I2C通过地址找到寄存器并进行读写操作。…ESP_ERROR_CHECK 是一个宏函数,位于 esp-idf 文件中,用于检测执行函数的返回结果,如果没有错误,什么事情都不会发生,如果有错误,会引起单片机重启。使用这个宏函数,需要包含 esp_err.h 头文件。…假设您正在开发一个项目,其中微控制器的GPIO引脚不够用,而您需要连接更多的按钮和LED。这时,您可以使用PCA9557来扩展I/O端口。将按钮连接到PCA9557的一些GPIO引脚上,并将其配置为输入模式;将LED连接到其他GPIO引脚,并将其配置为输出模式。然后,通过I2C总线与PCA9557通信,以读取按钮状态并控制LED。

初始化PCA8559

首先需要设置PCA9557的工作模式(如输入或输出)。这是通过向特定寄存器写入配置数据完成的。

void pca9557_init(void)
{
    // 写入控制引脚默认值
    DVP_PWDN=1
    PA_EN = 0
    LCD_CS = 1
    pca9557_register_write_byte(PCA9557_OUTPUT_PORT, 0x05); // 把PCA9557芯片的IO1 IO1 IO2设置为输出 其它引脚保持默认的输入
    pca9557_register_write_byte(PCA9557_CONFIGURATION_PORT, 0xf8);
}
void pca9557_init(void) { // 写入控制引脚默认值 DVP_PWDN=1 PA_EN = 0 LCD_CS = 1 pca9557_register_write_byte(PCA9557_OUTPUT_PORT, 0x05); // 把PCA9557芯片的IO1 IO1 IO2设置为输出 其它引脚保持默认的输入 
pca9557_register_write_byte(PCA9557_CONFIGURATION_PORT, 0xf8); }

读写PCA8559

// 读取PCA9557寄存器的值
esp_err_t pca9557_register_read(uint8_t reg_addr, uint8_t *data, size_t len)
{
    return i2c_master_write_read_device(BSP_I2C_NUM, PCA9557_SENSOR_ADDR, &reg_addr, 1, data, len, 1000 / portTICK_PERIOD_MS);
}

// 给PCA9557的寄存器写值
esp_err_t pca9557_register_write_byte(uint8_t reg_addr, uint8_t data)
{
    uint8_t write_buf[2] = {reg_addr, data};
    return i2c_master_write_to_device(BSP_I2C_NUM, PCA9557_SENSOR_ADDR, write_buf, sizeof(write_buf), 1000 / portTICK_PERIOD_MS);
}
// 读取PCA9557寄存器的值 
esp_err_t pca9557_register_read(uint8_t reg_addr, uint8_t *data, size_t len) 
{ 
        return i2c_master_write_read_device(BSP_I2C_NUM, PCA9557_SENSOR_ADDR, &reg_addr, 1, data, len, 1000 / portTICK_PERIOD_MS); 
} // 给PCA9557的寄存器写值 
esp_err_t pca9557_register_write_byte(uint8_t reg_addr, uint8_t data) 
{ 
        uint8_t write_buf[2] = {reg_addr, data}; 
        return i2c_master_write_to_device(BSP_I2C_NUM, PCA9557_SENSOR_ADDR, write_buf, sizeof(write_buf), 1000 / portTICK_PERIOD_MS); 
}

控制PCA9557任意一个单独引脚变高变低

// 设置PCA9557芯片的某个IO引脚输出高低电平
esp_err_t pca9557_set_output_state(uint8_t gpio_bit, uint8_t level)
{
    uint8_t data;
    esp_err_t res = ESP_FAIL;
    pca9557_register_read(PCA9557_OUTPUT_PORT, &data, 1);
    res = pca9557_register_write_byte(PCA9557_OUTPUT_PORT, SET_BITS(data, gpio_bit, level));
    return res;
}
// 设置PCA9557芯片的某个IO引脚输出高低电平 
esp_err_t pca9557_set_output_state(uint8_t gpio_bit, uint8_t level) 
{ 
        uint8_t data; esp_err_t res = ESP_FAIL; 
        pca9557_register_read(PCA9557_OUTPUT_PORT, &data, 1); 
        res = pca9557_register_write_byte(PCA9557_OUTPUT_PORT, SET_BITS(data, gpio_bit, level)); 
        return res; 
}

控制PCA9557_LCD_CS输出高低电平

// 控制 PCA9557_LCD_CS 引脚输出高低电平 参数0输出低电平 参数1输出高电平
void lcd_cs(uint8_t level)
{
    pca9557_set_output_state(LCD_CS_GPIO, level);
}

// 控制 PCA9557_PA_EN 引脚输出高低电平 参数0输出低电平 参数1输出高电平
void pa_en(uint8_t level)
{
    pca9557_set_output_state(PA_EN_GPIO, level);
}

// 控制 PCA9557_DVP_PWDN 引脚输出高低电平 参数0输出低电平 参数1输出高电平
void dvp_pwdn(uint8_t level)
{
    pca9557_set_output_state(DVP_PWDN_GPIO, level);
}
// 控制 PCA9557_LCD_CS 引脚输出高低电平 参数0输出低电平 参数1输出高电平 
void lcd_cs(uint8_t level) { pca9557_set_output_state(LCD_CS_GPIO, level); }// 控制 PCA9557_PA_EN 引脚输出高低电平 参数0输出低电平 参数1输出高电平 
void pa_en(uint8_t level) { pca9557_set_output_state(PA_EN_GPIO, level); }// 控制 PCA9557_DVP_PWDN 引脚输出高低电平 参数0输出低电平 参数1输出高电平 
void dvp_pwdn(uint8_t level) { pca9557_set_output_state(DVP_PWDN_GPIO, level); })

初始化I2C

esp_err_t bsp_i2c_init(void)
{
    i2c_config_t i2c_conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = BSP_I2C_SDA,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_io_num = BSP_I2C_SCL,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = BSP_I2C_FREQ_HZ
    };
    i2c_param_config(BSP_I2C_NUM, &i2c_conf);
    return i2c_driver_install(BSP_I2C_NUM, i2c_conf.mode, 0, 0, 0);
}
esp_err_t bsp_i2c_init(void) 
{ 
    i2c_config_t i2c_conf = 
        { 
           .mode = I2C_MODE_MASTER, 
           .sda_io_num = BSP_I2C_SDA, 
           .sda_pullup_en = GPIO_PULLUP_ENABLE, 
           .scl_io_num = BSP_I2C_SCL, 
           .scl_pullup_en = GPIO_PULLUP_ENABLE, 
           .master.clk_speed = BSP_I2C_FREQ_HZ 
         }; 
        i2c_param_config(BSP_I2C_NUM, &i2c_conf); 
        return i2c_driver_install(BSP_I2C_NUM,i2c_conf.mode, 0, 0, 0); 
}

初始化LCD

CMake 是什么 ?

在 ESP-IDF(ESP32 开发框架)中,CMake 负责组织和管理各个组件的编译过程。详细内容可参考“小白入门笔记:CMake编译过程详解-腾讯云开发者社区-腾讯云” , 这里只说一下跟ESP32相关的或者是我遇到的问题 。

CMake Lists.txt 是什么 , 有什么用 ?

拿这段代码举例 :

idf_component_register(SRCS "uart_echo_example_main.c" INCLUDE_DIRS ".")
idf_component_register(SRCS "uart_echo_example_main.c" INCLUDE_DIRS "."))

简单来说,这就是告诉 ESP-IDF:“我要编译 uart_echo_example_main.c,并且头文件就在当前目录。”CMake 会自动把 uart_echo_example_main.c 加入编译,并把当前目录作为头文件搜索路径。

使用ESP32中遇到的奇奇怪怪的问题

明明添加了"esp_log.h"这种头文件,但是根本识别不到该怎么办?

  1. ESP-IDF的路径设置对了吗。
  2. 需要用“ ESP-IDF: 添加 VS Code 配置文件夹“给这个项目添加以下配置文件夹

编译卡在CMake过不去,但是检查了一圈程序发现没问题

  1. 删掉build文件夹,再重新编译一下

明明头文件设置好了,但是头文件中的函数无法调用怎么办?

  1. 在 CMakeLists.txt中,按照格式把需要编译的.C程序添加进去(根据已有的格式设置就行)