ESP32-S3-WROOM-1-N16R8

基于立创实战派S3

参考链接:实战派开发板S3介绍 | 立创开发板技术文档中心

硬件和代码,(大部分图片)基于立创文档,在此基础上进行个人的学习记录和理解补充

硬件参数

类别 型号 参数
模组 ESP32-S3-WROOM-1-N16R8 搭载 Xtensa 32 位 LX7 双核处理器,主频高达 240 MHz,内置SRAM 512kB,外置PSRAM 8MB,外置FLASH 16MB,2.4 GHz Wi-Fi (802.11 b/g/n) 40MHz带宽,Bluetooth 5 (LE) 和 Bluetooth Mesh,集成AI向量指令,加速神经网络计算和信号处理
显示屏(SPI) ST7789 2.0寸、IPS全视角、分辨率320*240、SPI接口
(触摸屏(I2C)) FT6336 电容触摸、I2C接口
姿态传感器(I2C) QMI8658 三轴加速度+三轴陀螺仪、I2C接口
音频DAC(I2C) ES8311 单通道、I2C接口
音频ADC(I2C) ES7210 四通道(开发板用三个通道)、I2C接口
音频功放 NS4150B 单声道D类音频放大器
麦克风 ZTS6216 配套双路麦克风、模拟输出
喇叭 DB1811AB50 1811音腔喇叭、1W
USB HUB CH334F USB2.0 HUB
USB转串口 CH340K 波特率最大2Mbps
电源芯片 SY8088AAC 提供双路、每路1A
GH1.25接口 两路外拓传感器接口,可以给外部传感器供电5V和3.3V,可以作为GPIO、CAN、I2C、UART、PWM等接口
TF卡接口(1-SD) 采用1-SD模式与ESP32连接
Type-C接口 用于供电、程序下载、程序调试,以及USB数据通信
按键 一个复位按键、一个用户自定义按键

代码框架:

使用乐鑫官方移植的实时操作系统FreeRTOS

依托乐鑫官方封装的底层函数

用户通过调用乐鑫提供的API函数即可实现绝大部分功能

基础例程

点灯:

#include <stdio.h>          // 引入标准输入输出库,用于基本的输入输出操作
#include "freertos/FreeRTOS.h" // 引入FreeRTOS库,用于实时操作系统相关的功能
#include "driver/gpio.h" // 引入GPIO驱动库,用于控制GPIO引脚
#include "freertos/task.h" // 引入任务管理库,用于创建和管理任务
void app_main(void)
{
gpio_reset_pin(10);
gpio_set_direction(10, GPIO_MODE_OUTPUT);
while (1)
{
gpio_set_level(10, 0);
vTaskDelay(1000 / portTICK_PERIOD_MS);
gpio_set_level(10, 1);
vTaskDelay(1000 / portTICK_PERIOD_MS);
/* code */
}
}

要点:引入FreeRTOS库,freertos任务管理库,GPIO驱动库

freertos任务创建,GPIO驱动

中断按键

#include <stdio.h>          // 引入标准输入输出库,用于输入输出操作
#include <string.h> // 引入字符串处理库,用于字符串操作
#include <stdlib.h> // 引入标准库,用于内存分配、控制进程等
#include <inttypes.h> // 引入固定宽度整数类型库,用于确保整数类型的宽度
// 引入FreeRTOS相关库
#include "freertos/FreeRTOS.h" // 引入FreeRTOS内核相关定义
#include "freertos/task.h" // 引入任务管理相关定义
#include "freertos/queue.h" // 引入队列管理相关定义
// 引入ESP-IDF的GPIO驱动库
#include "driver/gpio.h" // 引入GPIO驱动相关定义,用于GPIO操作 void key_Init(void)//key-->GPIO 0,中断模式,下降沿触发
{
//zero-initialize the config structure.
gpio_config_t io_conf =
{
//disable interrupt
.intr_type = GPIO_INTR_NEGEDGE,// 下降沿中断
.mode = GPIO_MODE_INPUT,// 输入模式
.pin_bit_mask = 1ULL<<0,
.pull_down_en = 0,
.pull_up_en = 1//上拉电阻打开
};//定义结构体
//configure GPIO with the given settings
gpio_config(&io_conf);//初始化按键
}
void LED_Init(void)
{
gpio_reset_pin(10);//复位GPIO10
gpio_set_direction(10, GPIO_MODE_OUTPUT);//设置GPIO10为输出模式
} static QueueHandle_t gpio_evt_queue = NULL;//定义队列句柄
static void IRAM_ATTR gpio_isr_handler(void* arg)//中断服务函数
{
uint32_t gpio_num = (uint32_t) arg;
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);//将gpio_num放入队列(0)
} uint32_t keynum;
static void gpio_task_example(void* arg)//任务函数————接收任务消息
{
uint32_t io_num;
for(;;) {
if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
printf("GPIO[%"PRIu32"] intr, val: %d\n", io_num, gpio_get_level(io_num));//打印0,打印引脚电平
keynum++;
if(keynum%2==1)gpio_set_level(10, 0);
if(keynum%2==0)gpio_set_level(10, 1);
}
}
} void app_main(void)
{
key_Init();
LED_Init();
//create a queue to handle gpio event from isr
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
//start gpio task
xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);//创建任务 //install gpio isr service
gpio_install_isr_service(0);
//hook isr handler for specific gpio pin
gpio_isr_handler_add(0, gpio_isr_handler, (void*) 0);//获取GPIO0的中断
}

要点:freertos队列创建和使用,GPIO外部中断触发设置,中断服务函数

定时器:

#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "driver/gptimer.h"//定时器头文件
#include "esp_log.h" static const char *TAG = "example"; /**
* @函数说明 定时器回调函数
* @传入参数
* @函数返回
*/
static bool IRAM_ATTR TimerCallback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
BaseType_t high_task_awoken = pdFALSE;
//将传进来的队列保存
QueueHandle_t queue = (QueueHandle_t)user_data;
static int time = 0;
time++;
//从中断服务程序(ISR)中发送数据到队列
xQueueSendFromISR(queue, &time, &high_task_awoken);
return (high_task_awoken == pdTRUE);
} /**
* @函数说明 定时器初始化配置
* @传入参数 resolution_hz=定时器的分辨率 alarm_count=触发警报事件的目标计数值
* @函数返回 创建的定时器回调队列
*/
QueueHandle_t timerInitConfig(uint32_t resolution_hz, uint64_t alarm_count)
{
//定义一个通用定时器
gptimer_handle_t gptimer = NULL;
//创建一个队列
QueueHandle_t queue = xQueueCreate(10, sizeof(10));
//如果创建不成功
if (!queue) {
ESP_LOGE("queue", "Creating queue failed");
return NULL;
}
//配置定时器参数
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT, //定时器时钟来源 选择APB作为默认选项
.direction = GPTIMER_COUNT_UP, //向上计数
//计数器分辨率(工作频率)以Hz为单位,因此,每个计数滴答的步长等于(1 / resolution_hz)秒
//假设 resolution_hz = 1000 000
//1 / resolution_hz = 1 / 1000000 = 0.000001(秒) = 1(微秒) ( 1 tick= 1us )
.resolution_hz = resolution_hz,
};
//将配置设置到定时器
gptimer_new_timer(&timer_config, &gptimer);
//绑定一个回调函数
gptimer_event_callbacks_t cbs = {
.on_alarm = TimerCallback,
};
//设置定时器gptimer的 回调函数为cbs 传入的参数为NULL
gptimer_register_event_callbacks(gptimer, &cbs, queue);
//使能定时器
gptimer_enable(gptimer);
//通用定时器的报警值设置
gptimer_alarm_config_t alarm_config = {
.reload_count = 0, //重载计数值为0
.alarm_count = alarm_count, // 报警目标计数值 1000000 = 1s
.flags.auto_reload_on_alarm = true, //开启重加载
};
//设置触发报警动作
gptimer_set_alarm_action(gptimer, &alarm_config);
//开始定时器开始工作
gptimer_start(gptimer); return queue;
}
void LED_Init(void)
{
gpio_reset_pin(10);
gpio_set_direction(10, GPIO_MODE_OUTPUT); }
void app_main(void)
{
LED_Init();
int number = 0;
QueueHandle_t queue = 0;
// 初始化定时器 1秒进入回调函数一次
queue = timerInitConfig(1000000,1000000);
while(1)
{
//从队列中接收一个数据,不能在中断服务函数使用
if (xQueueReceive(queue, &number, pdMS_TO_TICKS(2000)))
{
ESP_LOGI(TAG, "Timer stopped, count=%d", number);
gpio_set_level(10, 1);
} else {
ESP_LOGW(TAG, "Missed one count event");
gpio_set_level(10, 0);
}
} }

要点:引入FreeRTOS定时器库,创建定时器

led,pwm调光

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#define LEDC_TIMER LEDC_TIMER_0 //定时器0
#define LEDC_MODE LEDC_LOW_SPEED_MODE //低速模式
#define LEDC_OUTPUT_IO (10) // 定义输出GPIO为GPIO10
#define LEDC_CHANNEL LEDC_CHANNEL_0 // 使用LEDC的通道0
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT // LEDC分辨率设置为13位
#define LEDC_DUTY (4095) // 设置占空比为50%。 ((2的13次方) - 1) * 50% = 4095
#define LEDC_FREQUENCY (100) // 频率单位是Hz。设置频率为5000 Hz /**
* @函数说明 LEDC功能初始化
* @传入参数 无
* @函数返回 无
* @备 注 PWM频率越高,可用的占空比分辨率越低
*/
void LedcInitConfig(void)
{
// 准备并应用led PWM定时器配置
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_MODE, //LED模式 低速模式
.timer_num = LEDC_TIMER, //通道的定时器源 定时器0
.duty_resolution = LEDC_DUTY_RES, //将占空比分辨率设置为13位
.freq_hz = LEDC_FREQUENCY, // 设置输出频率为5 kHz
.clk_cfg = LEDC_AUTO_CLK //设置LEDPWM的时钟来源 为自动
//LEDC_AUTO_CLK = 启动定时器时,将根据给定的分辨率和占空率参数自动选择led源时钟
};
ledc_timer_config(&ledc_timer); // 准备并应用LEDC PWM通道配置
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_MODE, //LED模式 低速模式
.channel = LEDC_CHANNEL, //通道0
.timer_sel = LEDC_TIMER, //定时器源 定时器0
.intr_type = LEDC_INTR_DISABLE, //关闭中断
.gpio_num = LEDC_OUTPUT_IO, //输出引脚 GPIO5
.duty = 0, // 设置占空比为0
.hpoint = 0
};
ledc_channel_config(&ledc_channel);
}
void app_main(void)
{
gpio_reset_pin(10);
gpio_set_direction(10, GPIO_MODE_OUTPUT);
int duty_value = 0;
// 设置led外设配置
LedcInitConfig();
// 设置占空比为50
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, LEDC_DUTY);
// 更新通道占空比
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL); while (1)
{
// 实现渐亮效果
for(duty_value=0;duty_value<8191;duty_value+=100)
{
// 设置亮度模拟值,占空比不断加大
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty_value);
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
// 延时 10ms
vTaskDelay(10 / portTICK_PERIOD_MS);
}
// 实现渐灭效果
for(duty_value=8191;duty_value>=0;duty_value-=100)
{
// 设置亮度模拟值,占空比不断减少
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty_value);
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
// 延时 10ms
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
}

ADC电压测量

#include <stdio.h>                  // 引入标准输入输出库,用于基本的输入输出操作
#include "freertos/FreeRTOS.h" // 引入FreeRTOS库,用于实时操作系统相关的功能
#include "driver/gpio.h" // 引入GPIO驱动库,用于控制GPIO引脚
#include "freertos/task.h" // 引入FreeRTOS任务管理库,用于创建和管理任务
#include "esp_adc_cal.h" // 引入ESP32 ADC校准库,用于ADC的校准和转换
#include "driver/adc.h" // 引入ADC驱动库,用于配置和使用ADC(模数转换器) #define ADC_ADCX_CHY ADC1_CHANNEL_9 //io10 void adc_init(void)
{
adc_digi_pattern_config_t adc1_digi_pattern_config; /* ADC1 配置句柄 */
/* ADC1 初始化句柄 */
adc_digi_configuration_t adc1_init_config; /* 配置 ADC1 */
adc1_digi_pattern_config.atten = ADC_ATTEN_DB_12; /* 配置 ADC 衰减程度 */
adc1_digi_pattern_config.channel = ADC_ADCX_CHY;
adc1_digi_pattern_config.unit = ADC_UNIT_1; /* 配置 ADC 单元 */
adc1_digi_pattern_config.bit_width = ADC_BITWIDTH_12; /* 配置 ADC 位宽 */
/* 配置将要使用的每个 ADC 参数 */
adc1_init_config.adc_pattern = &adc1_digi_pattern_config;
adc_digi_controller_configure(&adc1_init_config); /* 配置 ADC1 */
}
/**
* @brief 获取 ADC 转换且进行均值滤波后的结果
* @param ch : 通道号, 0~9
* @param times : 获取次数
* @retval 通道 ch 的 times 次转换结果平均值
*/
uint32_t adc_get_result_average(uint32_t ch, uint32_t times)
{
uint32_t temp_val = 0;
uint8_t t;
for (t = 0; t < times; t++) /* 获取 times 次数据 */
{
temp_val += adc1_get_raw(ch);
vTaskDelay(5);
}
return temp_val / times; /* 返回平均值 */
} void app_main(void)
{ gpio_reset_pin(10);
gpio_set_direction(10, GPIO_MODE_OUTPUT); adc_init();
uint16_t adcdata;
float voltage;
int num = 0;
while (1)
{
adcdata = adc_get_result_average(ADC_ADCX_CHY, 10);
/* 显示 ADC 采样后的原始值 */
voltage = (float)adcdata * (3.3 / 4096);/* 获取计算后的带小数的实际电压值 */
adcdata = voltage; /* 赋值整数部分给 adcx 变量 */
printf("ADC = %d\n", adcdata); voltage -= adcdata;/* 把已经显示的整数部分去掉,留下小数部分 */
voltage *= 1000; /* 小数部分乘以 1000 */
printf("ADC2 = %f\n", voltage); num++;
printf("num = %d\n", num);
vTaskDelay(1000 / portTICK_PERIOD_MS);
/* code */
} }

要点:引入ESP32 ADC校准库,引入ADC驱动库

姿态传感器QMI8658(IIC)

  • 内部集成 3 轴加速度传感器和
  • 3 轴陀螺仪传感器
  • 支持 SPI 和 I2C 通信,
  • QMI8658 的 7 位 I2C 地址是 0x6A。

在我们的开发板上使用的是 I2C 通信,ESP32-S3 只有 1 个 I2C 外设,我们开发板上的所有 I2C 设备,都使用同一个 I2C 通信接口,通过 I2C 设备的地址,来决定和谁通信

IIC驱动部分(用于主机和姿态传感器通信)

BSP_IIC.h

// 包含ESP-IDF错误代码定义的头文件
#include "esp_err.h"
// 包含ESP-IDF日志记录功能的头文件
#include "esp_log.h"
// 包含I2C驱动程序的头文件,用于I2C通信
#include "driver/i2c.h" // 再次包含ESP-IDF日志记录功能的头文件,可能是为了确保在其他地方也能使用
#include "esp_log.h"
// 包含FreeRTOS实时操作系统的头文件,用于任务管理和调度
#include "freertos/FreeRTOS.h"
// 包含FreeRTOS任务相关的头文件,用于创建和管理任务
#include "freertos/task.h"
// 包含数学函数的头文件,用于数学计算
#include "math.h" #define BSP_I2C_SDA 1 //IO1
#define BSP_I2C_SCL 2 #define BSP_I2C_NUM 0 // I2C外设,选择IIC0
#define BSP_IIC_FREQ_HZ 100000 // 100kHz #define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0 esp_err_t i2c_master_init(void);//初始化i2c
// 初始化I2C接口

BSP_IIC.c

#include "BSP_IIC.h"

esp_err_t i2c_master_init(void)//初始化i2c
{
i2c_config_t conf = {
// 设置I2C模式为主机模式
.mode = I2C_MODE_MASTER,
.sda_io_num = BSP_I2C_SDA,
.scl_io_num = BSP_I2C_SCL,
// 启用I2C数据线(SDA)的内部上拉电阻
.sda_pullup_en = GPIO_PULLUP_ENABLE,
// 启用I2C时钟线(SCL)的内部上拉电阻
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = BSP_IIC_FREQ_HZ,
}; i2c_param_config(BSP_I2C_NUM, &conf);//选择IIC0初始化 //通常用来检查驱动程序是否成功安装。确保在调用 i2c_driver_install 之前,已经正确配置conf.mode 等参数。
return i2c_driver_install(BSP_I2C_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
}

姿态传感器驱动函数编写

QMI8658.h

#include "BSP_IIC.h" // 包含I2C底层驱动头文件

#define qmi8658_SENSOR_ADDR 0x6A// QMI8658 I2C地址

// QMI8658寄存器地址
enum qmi8658_reg
{
QMI8658_WHO_AM_I, // 设备标识寄存器
QMI8658_REVISION_ID, // 版本号寄存器
QMI8658_CTRL1, // 控制寄存器1
QMI8658_CTRL2, // 控制寄存器2
QMI8658_CTRL3, // 控制寄存器3
QMI8658_CTRL4, // 控制寄存器4
QMI8658_CTRL5, // 控制寄存器5
QMI8658_CTRL6, // 控制寄存器6
QMI8658_CTRL7, // 控制寄存器7
QMI8658_CTRL8, // 控制寄存器8
QMI8658_CTRL9, // 控制寄存器9
QMI8658_CATL1_L, // 加速度计阈值寄存器低字节
QMI8658_CATL1_H, // 加速度计阈值寄存器高字节
QMI8658_CATL2_L, // 加速度计阈值寄存器低字节
QMI8658_CATL2_H, // 加速度计阈值寄存器高字节
QMI8658_CATL3_L, // 加速度计阈值寄存器低字节
QMI8658_CATL3_H, // 加速度计阈值寄存器高字节
QMI8658_CATL4_L, // 加速度计阈值寄存器低字节
QMI8658_CATL4_H, // 加速度计阈值寄存器高字节
QMI8658_FIFO_WTM_TH, // FIFO水印阈值寄存器
QMI8658_FIFO_CTRL, // FIFO控制寄存器
QMI8658_FIFO_SMPL_CNT, // FIFO样本计数寄存器
QMI8658_FIFO_STATUS, // FIFO状态寄存器
QMI8658_FIFO_DATA, // FIFO数据寄存器
QMI8658_STATUSINT = 45, // 中断状态寄存器
QMI8658_STATUS0, // 状态寄存器0
QMI8658_STATUS1, // 状态寄存器1
QMI8658_TIMESTAMP_LOW, // 时间戳低字节寄存器
QMI8658_TIMESTAMP_MID, // 时间戳中字节寄存器
QMI8658_TIMESTAMP_HIGH, // 时间戳高字节寄存器
QMI8658_TEMP_L, // 温度低字节寄存器
QMI8658_TEMP_H, // 温度高字节寄存器
QMI8658_AX_L, // 加速度计X轴低字节寄存器
QMI8658_AX_H, // 加速度计X轴高字节寄存器
QMI8658_AY_L, // 加速度计Y轴低字节寄存器
QMI8658_AY_H, // 加速度计Y轴高字节寄存器
QMI8658_AZ_L, // 加速度计Z轴低字节寄存器
QMI8658_AZ_H, // 加速度计Z轴高字节寄存器
QMI8658_GX_L, // 陀螺仪X轴低字节寄存器
QMI8658_GX_H, // 陀螺仪X轴高字节寄存器
QMI8658_GY_L, // 陀螺仪Y轴低字节寄存器
QMI8658_GY_H, // 陀螺仪Y轴高字节寄存器
QMI8658_GZ_L, // 陀螺仪Z轴低字节寄存器
QMI8658_GZ_H, // 陀螺仪Z轴高字节寄存器
QMI8658_COD_STATUS = 70, // 状态码寄存器
QMI8658_dQW_L = 73, // 四元数W低字节寄存器
QMI8658_dQW_H, // 四元数W高字节寄存器
QMI8658_dQX_L, // 四元数X低字节寄存器
QMI8658_dQX_H, // 四元数X高字节寄存器
QMI8658_dQY_L, // 四元数Y低字节寄存器
QMI8658_dQY_H, // 四元数Y高字节寄存器
QMI8658_dQZ_L, // 四元数Z低字节寄存器
QMI8658_dQZ_H, // 四元数Z高字节寄存器
QMI8658_dVX_L, // 速度X低字节寄存器
QMI8658_dVX_H, // 速度X高字节寄存器
QMI8658_dVY_L, // 速度Y低字节寄存器
QMI8658_dVY_H, // 速度Y高字节寄存器
QMI8658_dVZ_L, // 速度Z低字节寄存器
QMI8658_dVZ_H, // 速度Z高字节寄存器
QMI8658_TAP_STATUS = 89, // 点击状态寄存器
QMI8658_STEP_CNT_LOW, // 步数计数低字节寄存器
QMI8658_STEP_CNT_MIDL, // 步数计数中字节寄存器
QMI8658_STEP_CNT_HIGH, // 步数计数高字节寄存器
QMI8658_RESET = 96 // 复位寄存器
}; // 倾角结构体
typedef struct{
int16_t acc_x; // 加速度计X轴数据
int16_t acc_y; // 加速度计Y轴数据
int16_t acc_z; // 加速度计Z轴数据
int16_t gyr_x; // 陀螺仪X轴数据
int16_t gyr_y; // 陀螺仪Y轴数据
int16_t gyr_z; // 陀螺仪Z轴数据
float AngleX; // X轴倾角
float AngleY; // Y轴倾角
float AngleZ; // Z轴倾角
}t_sQMI8658;
// 初始化QMI8658传感器
void qmi8658_init(void);
// 从加速度计数据中获取倾角
void qmi8658_fetch_angleFromAcc(t_sQMI8658 *p); // 获取倾角

QMI8658.c

#include "QMI8658.h"
static const char *TAG = "BSP_QMI8658"; //日志标签
// 读取QMI8658寄存器的值
static esp_err_t qmi8658_register_read(uint8_t reg_addr, uint8_t *data, size_t len)
{
return i2c_master_write_read_device(BSP_I2C_NUM, qmi8658_SENSOR_ADDR, &reg_addr, 1, data, len, 1000 / portTICK_PERIOD_MS);
} // i2c_master_write_read_device 是一个函数,用于执行I2C主控制器上的写-读操作。
// BSP_I2C_NUM 是I2C端口号,指定了将要进行通信的I2C总线。
// qmi8658_SENSOR_ADDR 是从设备的地址,这里是QMI8658传感器的I2C地址。
// &reg_addr 是一个指向要写入从设备的字节(通常是寄存器地址)的指针。
// 1 是要写入的字节数,这里是1个字节。
// data 是一个指向用于存储从从设备读取数据的缓冲区的指针。
// len 是要读取的数据字节数。
// 1000 / portTICK_PERIOD_MS 是操作的超时时间,以FreeRTOS的tick周期为单位。这里指定为1000个tick周期,大约等于1秒。 // 给QMI8658的寄存器写值
static esp_err_t qmi8658_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, qmi8658_SENSOR_ADDR, write_buf, sizeof(write_buf), 1000 / portTICK_PERIOD_MS);
}
// 初始化qmi8658
void qmi8658_init(void)
{
uint8_t id;
qmi8658_register_read(QMI8658_WHO_AM_I, &id, 1); // 读芯片的ID号 if (id != 0x05)//判断是否为qmi8658
{
vTaskDelay(1000/portTICK_PERIOD_MS);// 延时1秒
qmi8658_register_read(QMI8658_WHO_AM_I, &id, 1);// 读取ID号
}
ESP_LOGI(TAG, "QMI8658 OK!");// 打印信息 qmi8658_register_write_byte(QMI8658_RESET, 0xb0); // 复位
vTaskDelay(10 / portTICK_PERIOD_MS); // 延时10ms
qmi8658_register_write_byte(QMI8658_CTRL1, 0x40); // CTRL1 设置地址自动增加
qmi8658_register_write_byte(QMI8658_CTRL7, 0x03); // CTRL7 允许加速度和陀螺仪
qmi8658_register_write_byte(QMI8658_CTRL2, 0x95); // CTRL2 设置ACC 4g 250Hz
qmi8658_register_write_byte(QMI8658_CTRL3, 0xd5); // CTRL3 设置GRY 512dps 250Hz
} // 读取加速度和陀螺仪寄存器值
void qmi8658_Read_AccAndGry(t_sQMI8658 *p)
{
uint8_t status, data_ready=0;
int16_t buf[6]; qmi8658_register_read(QMI8658_STATUS0, &status, 1); // 读状态寄存器
if (status & 0x03) // 判断加速度和陀螺仪数据是否可读
data_ready = 1;
if (data_ready == 1){ // 如果数据可读
data_ready = 0;
qmi8658_register_read(QMI8658_AX_L, (uint8_t *)buf, 12); // 读加速度和陀螺仪值
p->acc_x = buf[0];
p->acc_y = buf[1];
p->acc_z = buf[2];
p->gyr_x = buf[3];
p->gyr_y = buf[4];
p->gyr_z = buf[5];
}
} // 获取XYZ轴的倾角值
void qmi8658_fetch_angleFromAcc(t_sQMI8658 *p)
{
float temp; qmi8658_Read_AccAndGry(p); // 读取加速度和陀螺仪的寄存器值
// 根据寄存器值 计算倾角值 并把弧度转换成角度
temp = (float)p->acc_x / sqrt( ((float)p->acc_y * (float)p->acc_y + (float)p->acc_z * (float)p->acc_z) );
p->AngleX = atan(temp)*57.29578f; // 180/π=57.29578
temp = (float)p->acc_y / sqrt( ((float)p->acc_x * (float)p->acc_x + (float)p->acc_z * (float)p->acc_z) );
p->AngleY = atan(temp)*57.29578f; // 180/π=57.29578
temp = sqrt( ((float)p->acc_x * (float)p->acc_x + (float)p->acc_y * (float)p->acc_y) ) / (float)p->acc_z;
p->AngleZ = atan(temp)*57.29578f; // 180/π=57.29578
}

主函数main.c

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#include "freertos/task.h"
#include "QMI8658.h"//姿态传感器
#include "BSP_IIC.h"
static const char *TAG = "main";
t_sQMI8658 QMI8658; // 定义QMI8658结构体变量
void app_main(void)
{
ESP_ERROR_CHECK(i2c_master_init());//初始化IIC
ESP_LOGI(TAG, "I2C initialized successfully");//调试时在终端打印(成功)
qmi8658_init();//初始化QMI8658
while (1)
{
vTaskDelay(1000 / portTICK_PERIOD_MS); // 延时1000ms
qmi8658_fetch_angleFromAcc(&QMI8658); // 获取XYZ轴的倾角
// 输出XYZ轴的倾角
ESP_LOGI(TAG, "angle_x = %.1f angle_y = %.1f angle_z = %.1f",QMI8658.AngleX, QMI8658.AngleY, QMI8658.AngleZ);
}
}

读写SD卡(SDIO,1-SD)

ESP32-S3 有 SDIO 接口,支持当前的 SDIO3.0 协议

可使用 SDIO 总线读写 SD 卡。

可使用 SPI 接口读写 SD 卡。

在实战派 ESP32-S3 开发板上,使用 SDIO 接口读写 SD 卡,由于 ESP32-S3 引脚太少了,

所以开发板上采用了 1-SD 模式,即 1 条数据线。

目前市面上的 TF 卡,32GB 以下一般是 FAT32 系统,32GB 以上的卡,一般是 exFAT 系统。

ESP32 IDF 目前只支持 FAT32 系统,不支持 exFAT 系统。如果你的 TF 卡是 exFAT 系统,且在程序中开启了“如果挂载不成功就格式化 SD 卡”,开发板上电后,会自动把SD 卡格式化为 FAT

SD.h

#ifndef SD_H
#define SD_H #include <stdio.h> // 标准输入输出库
#include <string.h> // 字符串操作库
#include "esp_vfs_fat.h" // ESP VFAT文件系统库
#include "sdmmc_cmd.h" // SDMMC命令库
#include "driver/sdmmc_host.h" // SDMMC主机驱动库
#include "esp_log.h" // ESP日志库 // 定义SD卡时钟引脚
#define BSP_SD_CLK (47)
// 定义SD卡命令引脚
#define BSP_SD_CMD (48)
// 定义SD卡数据0引脚
#define BSP_SD_D0 (21) #define MOUNT_POINT "/sdcard"//挂载点
#define EXAMPLE_MAX_CHAR_SIZE 64//示例最大字符大小 // 定义一个函数,用于写入数据到指定路径的文件中
// 参数path:文件的路径
// 参数data:要写入文件的数据
// 返回值:esp_err_t类型,表示操作的结果,成功为ESP_OK,失败为其他错误码
esp_err_t s_example_write_file(const char *path, const char *data); // 定义一个函数,用于从指定路径的文件中读取数据
// 参数path:文件的路径
// 返回值:esp_err_t类型,表示操作的结果,成功为ESP_OK,失败为其他错误码
esp_err_t s_example_read_file(const char *path); #endif // SD_H

注意声明函数去掉static,换成esp_err_t

SD.c

#include <SD.h>

static const char *TAG = "SD";

// 写文件内容 path是路径 data是内容
static esp_err_t s_example_write_file(const char *path, char *data)
{
ESP_LOGI(TAG, "Opening file %s", path);
FILE *f = fopen(path, "w");// 以只写方式打开路径中文件,f是文件指针
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for writing");// 如果打开失败,输出错误信息
return ESP_FAIL;//退出函数,返回错误
}
fprintf(f, data); // 写入内容
fclose(f); // 关闭文件
ESP_LOGI(TAG, "File written");// 输出文件写入成功日志 return ESP_OK;
} // 读文件内容 path 是路径
static esp_err_t s_example_read_file(const char *path)
{
ESP_LOGI(TAG, "Reading file %s", path);// 打印读取文件日志
FILE *f = fopen(path, "r"); // 以只读方式打开文件
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for reading");
return ESP_FAIL;
}
char line[EXAMPLE_MAX_CHAR_SIZE]; // 定义一个字符串数组
fgets(line, sizeof(line), f); // 获取文件中的内容到字符串数组
fclose(f); // 关闭文件 // strip newline
char *pos = strchr(line, '\n');// 查找字符串中的“\n”并返回其位置
if (pos) {
*pos = '\0'; // 把\n替换成\0
}
ESP_LOGI(TAG, "Read from file: '%s'", line); // 把数组内容输出到终端 return ESP_OK;
}

main.c

#include <stdio.h> // 标准输入输出库
#include "freertos/FreeRTOS.h" // FreeRTOS实时操作系统库
#include "driver/gpio.h" // GPIO驱动库
#include "freertos/task.h" // FreeRTOS任务管理库 #include "SD.h" // SD卡操作相关库
static const char *TAG = "main"; // 日志标签,用于标识日志来源
void app_main(void)
{
esp_err_t ret; // 用于存储ESP-IDF返回的错误码
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true, // 如果挂载不成功是否需要格式化SD卡
.max_files = 5, // 允许打开的最大文件数
.allocation_unit_size = 16 * 1024 // 分配单元大小
};
sdmmc_card_t *card; // 指向SD卡结构体的指针
const char mount_point[] = MOUNT_POINT; // 挂载点路径
ESP_LOGI(TAG, "Initializing SD card"); // 初始化SD卡日志
ESP_LOGI(TAG, "Using SDMMC peripheral"); // 使用SDMMC外设日志 sdmmc_host_t host = SDMMC_HOST_DEFAULT(); // 使用默认的SDMMC主机配置
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); // 使用默认的SDMMC插槽配置
slot_config.width = 1; // 设置为1线SD模式
slot_config.clk = BSP_SD_CLK; // SD卡时钟引脚
slot_config.cmd = BSP_SD_CMD; // SD卡命令引脚
slot_config.d0 = BSP_SD_D0; // SD卡数据引脚 slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;// 打开内部上拉电阻 ESP_LOGI(TAG, "Mounting filesystem"); // 挂载文件系统日志
ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card); // 挂载SD卡文件系统 if (ret != ESP_OK) { // 如果没有挂载成功
if (ret == ESP_FAIL) {// 如果挂载失败
ESP_LOGE(TAG, "Failed to mount filesystem"); // 挂载失败日志
} else {// 如果是其它错误 打印错误名称
ESP_LOGE(TAG, "Failed to initialize the card (%s)", esp_err_to_name(ret)); // 初始化卡失败日志
}
return;
}
ESP_LOGI(TAG, "Filesystem mounted"); // 提示挂载成功日志
sdmmc_card_print_info(stdout, card); // 终端打印SD卡的一些信息 // 新建一个txt文件 并且给文件中写入几个字符
const char *file_hello = MOUNT_POINT"/hello.txt";//写入文件路径
char data[EXAMPLE_MAX_CHAR_SIZE];//数据大小
snprintf(data, EXAMPLE_MAX_CHAR_SIZE, "%s %s!\n", "Hello", card->cid.name); // 格式化数据
ret = s_example_write_file(file_hello, data); // 写入文件
if (ret != ESP_OK) {
return;
} //读取文件 // 打开txt文件,并读出文件中的内容
ret = s_example_read_file(file_hello); // 读取文件
if (ret != ESP_OK) {
return;
} //移除sd卡
esp_vfs_fat_sdcard_unmount(mount_point, card); // 卸载SD卡
ESP_LOGI(TAG, "Card unmounted"); // 提示SD卡已卸载日志
}

程序执行:

绿色为打印的日志,白色为串口打印信息

音频输入-ES7210

实战派ESP32-S3 开发板的音频输入和输出则使用了 2 个芯片,

es7210 连接 MIC 负责音频输入

es8311 只负责音频输出

es7210 可以连接 4 个 MIC,开发板上连接了 3 个 MIC,

MIC1 和 MIC2 接收人说话的声音,

MIC3 连接了 es8311 的输出,用于回声消除

S3 芯片可以做 AI 语音识别对话,需要用到回声消除,就是音响在播放声音的时候,我们也可以说话打断它。这个原理就是 es8311 输出的信号,不仅给了喇叭,还给了 es7210 的 MIC3 输入,

ESP32 在接收到 MIC1 MIC2 和 MIC3 的声音后,可以分离出 MIC3,从而进行识别。

以上回声消除的算法,乐鑫已经写好代码,我们直接调用就可以。

I2S作为音频传输通道

例程主要实现的功能是录音,声音文件格式保存为 wav,写到 SD 卡里面。

依赖组件es7210组件

乐鑫组件管理器官网:https://components.espressif.com/

下载组件会自动创建一个名称为managed_components的文件夹,然后把组件代码放到里面。

点进进入,选择命令行添加,复制

idf.py add-dependency "espressif/es7210^1.0.1~1"

在工程文件夹内的命令行中粘贴,回车执行

注意打开的是esp-idf终端,不是cmd终端。

成功执行后会在main目录下自动添加

idf_component.yml文件

该文件指定了组件名称和版本

在选择芯片后会按照该文件联网下载组件,添加到工程路径下的managed_components文件夹下(没有会先自动创建)

组件的使用:

下载至工程文件夹后,直接声明头文件即可

需要添加format_wav.h/c到工程路径下,用于录音生成MAV格式的转化

该文件路径在esp-idf的例程,外设,i2s的common下,复制到工程中

D:\ESP_VScode\5.14\v5.1.4\esp-idf\examples\peripherals\i2s\common

要将录音文件保存到SD卡下,需添加头文件:

#include "sdmmc_cmd.h"           // 包含SDMMC命令相关的函数和定义,用于SD卡操作
#include "driver/sdmmc_host.h" // 包含SDMMC主机控制器的驱动接口,用于SD卡通信

主函数:

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#include "freertos/task.h" #include "Sound_input.h"//麦克风输入
#include "BSP_SD.h"//SD挂载配置 #include "format_wav.h" // 包含WAV音频格式处理相关的函数和定义 static const char *TAG = "main"; void app_main(void)
{
/* 初始化I2S接口 */
i2s_chan_handle_t i2s_rx_chan = es7210_i2s_init();
/* 初始化es7210芯片 */
es7210_codec_init();
/* 挂载SD卡 */
sdmmc_card_t *sdmmc_card = mount_sdcard();
/* 录音 */
esp_err_t err = record_wav(i2s_rx_chan);
/* 弹出SD卡 */
// 卸载SD卡文件系统
esp_vfs_fat_sdcard_unmount(EXAMPLE_SD_MOUNT_POINT, sdmmc_card);
// 检查错误码是否为ESP_OK,即操作成功
if(err == ESP_OK) {
// 如果操作成功,记录日志信息,提示音频已成功录制到指定文件路径
ESP_LOGI(TAG, "Audio was successfully recorded into "EXAMPLE_RECORD_FILE_PATH
". You can now remove the SD card safely");
} else {
// 如果操作失败,记录错误日志信息,提示SD卡上的音频文件可能无法播放
ESP_LOGE(TAG, "Record failed, "EXAMPLE_RECORD_FILE_PATH" on SD card may not be playable.");
} }

主函数逻辑:

  1. 初始化I2S接口
  2. 初始化es7210芯片
  3. 挂载SD卡
  4. 录音
  5. 弹出SD卡
  6. 卸载SD卡文件系统*

编写的驱动文件

Sound_input.c:

#include "Sound_input.h"
#include "BSP_IIS.h"//IIS音频通道初始化
#include "esp_log.h"
#include "BSP_IIC.h"//IIC初始化S7210编解码器
#include "BSP_SD.h" // 定义日志标签
static const char *TAG = "Sound_input"; //IIS音频通道初始化
i2s_chan_handle_t es7210_i2s_init(void)
{
i2s_chan_handle_t i2s_rx_chan = NULL; // 定义接收通道句柄
ESP_LOGI(TAG, "Create I2S receive channel");
i2s_chan_config_t i2s_rx_conf = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); // 配置接收通道
ESP_ERROR_CHECK(i2s_new_channel(&i2s_rx_conf, NULL, &i2s_rx_chan)); // 创建i2s通道 ESP_LOGI(TAG, "Configure I2S receive channel to TDM mode");
// 定义接收通道为I2S TDM模式 并配置
i2s_tdm_config_t i2s_tdm_rx_conf = {
.slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(EXAMPLE_I2S_SAMPLE_BITS, I2S_SLOT_MODE_STEREO, EXAMPLE_I2S_TDM_SLOT_MASK),
.clk_cfg = {
.clk_src = I2S_CLK_SRC_DEFAULT,
.sample_rate_hz = EXAMPLE_I2S_SAMPLE_RATE,
.mclk_multiple = EXAMPLE_I2S_MCLK_MULTIPLE
},
.gpio_cfg = {
.mclk = EXAMPLE_I2S_MCK_IO,
.bclk = EXAMPLE_I2S_BCK_IO,
.ws = EXAMPLE_I2S_WS_IO,
.dout = -1, // ES7210 only has ADC capability
.din = EXAMPLE_I2S_DI_IO
},
}; ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(i2s_rx_chan, &i2s_tdm_rx_conf)); // 初始化I2S通道为TDM模式 return i2s_rx_chan;
} // 初始化ES7210编解码器————IIC
//I2C接口用于配置ES7210编解码器的参数,
//如采样率、位宽、增益等,以便正确地进行音频数据的采集和处理。
void es7210_codec_init(void)
{
// 初始化I2C接口
ESP_LOGI(TAG, "Init I2C used to configure ES7210");
i2c_config_t i2c_conf = {
.sda_io_num = BSP_I2C_SDA,
.scl_io_num = BSP_I2C_SCL,
.mode = I2C_MODE_MASTER,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = EXAMPLE_ES7210_I2C_CLK,
};
ESP_ERROR_CHECK(i2c_param_config(BSP_I2C_NUM, &i2c_conf));
ESP_ERROR_CHECK(i2c_driver_install(BSP_I2C_NUM, i2c_conf.mode, 0, 0, 0)); // 创建es7210器件句柄
es7210_dev_handle_t es7210_handle = NULL;
es7210_i2c_config_t es7210_i2c_conf = {
.i2c_port = BSP_I2C_NUM,
.i2c_addr = EXAMPLE_ES7210_I2C_ADDR//es7210,IIC地址
};
ESP_ERROR_CHECK(es7210_new_codec(&es7210_i2c_conf, &es7210_handle)); // 初始化es7210芯片
ESP_LOGI(TAG, "Configure ES7210 codec parameters");
es7210_codec_config_t codec_conf = {
.i2s_format = EXAMPLE_I2S_TDM_FORMAT,
.mclk_ratio = EXAMPLE_I2S_MCLK_MULTIPLE,
.sample_rate_hz = EXAMPLE_I2S_SAMPLE_RATE,
.bit_width = (es7210_i2s_bits_t)EXAMPLE_I2S_SAMPLE_BITS,
.mic_bias = EXAMPLE_ES7210_MIC_BIAS,
.mic_gain = EXAMPLE_ES7210_MIC_GAIN,
.flags.tdm_enable = true
};
ESP_ERROR_CHECK(es7210_config_codec(es7210_handle, &codec_conf));
ESP_ERROR_CHECK(es7210_config_volume(es7210_handle, EXAMPLE_ES7210_ADC_VOLUME));
} // 定义另一个日志标签
static const char *TAG2 = "Sound_input"; // 录制WAV音频文件
esp_err_t record_wav(i2s_chan_handle_t i2s_rx_chan)
{
// 检查i2s通道句柄是否有效,如果无效则返回ESP_FAIL
ESP_RETURN_ON_FALSE(i2s_rx_chan, ESP_FAIL, TAG2, "invalid i2s channel handle pointer");
esp_err_t ret = ESP_OK; // 初始化返回值为ESP_OK // 计算每秒的字节数(采样率 * 通道数 * 每样本位数 / 8)
uint32_t byte_rate = EXAMPLE_I2S_SAMPLE_RATE * EXAMPLE_I2S_CHAN_NUM * EXAMPLE_I2S_SAMPLE_BITS / 8;
// 计算录制总字节数(每秒字节数 * 录制时间秒数)
uint32_t wav_size = byte_rate * EXAMPLE_RECORD_TIME_SEC; // 创建WAV文件头,使用默认PCM格式
const wav_header_t wav_header =
WAV_HEADER_PCM_DEFAULT(wav_size, EXAMPLE_I2S_SAMPLE_BITS, EXAMPLE_I2S_SAMPLE_RATE, EXAMPLE_I2S_CHAN_NUM); // 打印日志,显示正在打开文件
ESP_LOGI(TAG2, "Opening file %s", EXAMPLE_RECORD_FILE_PATH);
// 打开文件,路径为SD卡挂载点加上文件路径,以写入模式打开
FILE *f = fopen(EXAMPLE_SD_MOUNT_POINT EXAMPLE_RECORD_FILE_PATH, "w");
// 检查文件是否成功打开,如果失败则返回ESP_FAIL
ESP_RETURN_ON_FALSE(f, ESP_FAIL, TAG2, "error while opening wav file"); /* Write wav header */
ESP_GOTO_ON_FALSE(fwrite(&wav_header, sizeof(wav_header_t), 1, f), ESP_FAIL, err,
TAG2, "error while writing wav header"); /* Start recording */
size_t wav_written = 0;
static int16_t i2s_readraw_buff[4096];
ESP_GOTO_ON_ERROR(i2s_channel_enable(i2s_rx_chan), err, TAG2, "error while starting i2s rx channel");
while (wav_written < wav_size) {
if(wav_written % byte_rate < sizeof(i2s_readraw_buff)) {
ESP_LOGI(TAG2, "Recording: %"PRIu32"/%ds", wav_written/byte_rate + 1, EXAMPLE_RECORD_TIME_SEC);
}
size_t bytes_read = 0;
/* Read RAW samples from ES7210 */
ESP_GOTO_ON_ERROR(i2s_channel_read(i2s_rx_chan, i2s_readraw_buff, sizeof(i2s_readraw_buff), &bytes_read,
pdMS_TO_TICKS(1000)), err, TAG2, "error while reading samples from i2s");
/* Write the samples to the WAV file */
// 使用宏 ESP_GOTO_ON_FALSE 检查 fwrite 函数的返回值是否为假(即写入失败)
// 如果 fwrite 返回值为假,则跳转到标签 err,并设置错误码为 ESP_FAIL
// 同时记录错误信息,其中 TAG2 是一个宏或变量,表示日志标签
ESP_GOTO_ON_FALSE(fwrite(i2s_readraw_buff, bytes_read, 1, f), ESP_FAIL, err,
TAG2, "error while writing samples to wav file");
// 更新已写入 WAV 文件的字节数
wav_written += bytes_read;
} err:
i2s_channel_disable(i2s_rx_chan);
ESP_LOGI(TAG2, "Recording done! Flushing file buffer");
fclose(f); return ret;
}

在该文件中封装了函数:

  1. IIS音频通道初始化
  2. IIC初始化S7210编解码器
  3. 录制WAV音频文件

头文件:

Sound_input.h:

#ifndef _SOUND_INPUT_H_
#define _SOUND_INPUT_H_ #include <string.h> // 包含标准字符串操作函数,如strcpy、strlen等
#include "sdkconfig.h" // 包含SDK配置相关的头文件,用于访问配置参数
#include "esp_check.h" // 包含ESP-IDF的检查宏,用于错误处理和断言
#include "esp_vfs_fat.h" // 包含ESP-IDF的FAT文件系统虚拟文件系统(VFS)接口
#include "driver/i2s_tdm.h" // 包含I2S TDM(TDM模式)驱动接口,用于多通道音频数据传输
#include "driver/i2s_std.h" // 包含I2S标准模式驱动接口,用于常见的I2S音频数据传输
#include "driver/i2c.h" // 包含I2C驱动接口,用于I2C总线通信 #include "es7210.h" // 命令行添加ES7210芯片的组件,包含ES7210音频编解码器的驱动接口 #include "format_wav.h" // 包含WAV音频格式处理相关的函数和定义 #include "sdmmc_cmd.h" // 包含SDMMC命令相关的函数和定义,用于SD卡操作
#include "driver/sdmmc_host.h" // 包含SDMMC主机控制器的驱动接口,用于SD卡通信 /* ES7210 configurations */
#define EXAMPLE_ES7210_I2C_ADDR (0x41)
#define EXAMPLE_ES7210_I2C_CLK (100000)
#define EXAMPLE_ES7210_MIC_GAIN (ES7210_MIC_GAIN_30DB)
#define EXAMPLE_ES7210_MIC_BIAS (ES7210_MIC_BIAS_2V87)
#define EXAMPLE_ES7210_ADC_VOLUME (0) i2s_chan_handle_t es7210_i2s_init(void);
void es7210_codec_init(void);
esp_err_t record_wav(i2s_chan_handle_t i2s_rx_chan);// 录制WAV音频文件 #endif

BSP_SD.c

#include "BSP_SD.h"          // 包含BSP_SD头文件,可能包含SD卡相关的定义和函数
#include "esp_vfs_fat.h" // 包含ESP VFS FAT文件系统头文件,用于挂载FAT文件系统 static const char *TAG = "BSP_SD"; // 定义日志标签,用于标识日志输出的来源 // 挂载SD卡
sdmmc_card_t * mount_sdcard(void)
{
sdmmc_card_t *sdmmc_card = NULL; // 初始化SD卡指针为NULL ESP_LOGI(TAG, "Mounting SD card"); // 输出日志信息,表示开始挂载SD卡
esp_vfs_fat_sdmmc_mount_config_t mount_config = { // 配置挂载参数
.format_if_mount_failed = true, // 如果挂载失败则格式化SD卡
.max_files = 2, // 最大打开文件数
.allocation_unit_size = 8 * 1024 // 分配单元大小为8KB
}; ESP_LOGI(TAG, "Initializing SD card"); // 输出日志信息,表示开始初始化SD卡
ESP_LOGI(TAG, "Using SDMMC peripheral"); // 输出日志信息,表示使用SDMMC外设 sdmmc_host_t sdmmc_host = SDMMC_HOST_DEFAULT(); // SDMMC主机接口配置
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); // SDMMC插槽配置
slot_config.width = 1; // 设置为1线SD模式
slot_config.clk = EXAMPLE_SD_CLK_IO;
slot_config.cmd = EXAMPLE_SD_CMD_IO;
slot_config.d0 = EXAMPLE_SD_DAT0_IO;
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; // 打开内部上拉电阻 ESP_LOGI(TAG, "Mounting filesystem");//日志:正在挂载文件系统 esp_err_t ret;
while (1) {
ret = esp_vfs_fat_sdmmc_mount(EXAMPLE_SD_MOUNT_POINT, &sdmmc_host, &slot_config, &mount_config, &sdmmc_card);
if (ret == ESP_OK) {
break;
} else if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount filesystem.");
} else {
ESP_LOGE(TAG, "Failed to initialize the card (%s). ", esp_err_to_name(ret));
}
vTaskDelay(pdMS_TO_TICKS(1000));
} ESP_LOGI(TAG, "Card size: %lluMB, speed: %dMHz",
(((uint64_t)sdmmc_card->csd.capacity) * sdmmc_card->csd.sector_size) >> 20,
sdmmc_card->max_freq_khz / 1000); return sdmmc_card;
}

BSP_SD.h

#ifndef __BSP_SD_H
#define __BSP_SD_H #include "sdmmc_cmd.h" // 包含SDMMC命令相关的函数和定义,用于SD卡操作
#include "driver/sdmmc_host.h" // 包含SDMMC主机控制器的驱动接口,用于SD卡通信 #include "esp_check.h" // 包含ESP-IDF的检查宏,用于错误处理和断言 /* SD card GPIOs */
// 定义SD卡的命令线(CMD)
#define EXAMPLE_SD_CMD_IO (48)
// 定义SD卡的时钟线(CLK)
#define EXAMPLE_SD_CLK_IO (47)
// 定义SD卡的数据线(DAT0)
#define EXAMPLE_SD_DAT0_IO (21) /* SD card & recording configurations */
#define EXAMPLE_RECORD_TIME_SEC (10) // 录音时间,单位为秒
#define EXAMPLE_SD_MOUNT_POINT "/sdcard"// 挂载点
#define EXAMPLE_RECORD_FILE_PATH "/Sound_Intput.WAV"// 录音文件名称和路径 sdmmc_card_t * mount_sdcard(void);//挂载SD卡 #endif

BSP_IIC.h

定义接口

// 包含ESP-IDF错误代码定义的头文件
#include "esp_err.h"
// 包含ESP-IDF日志记录功能的头文件
#include "esp_log.h"
// 包含I2C驱动程序的头文件,用于I2C通信
#include "driver/i2c.h" #define BSP_I2C_SDA 1 //IO1
#define BSP_I2C_SCL 2 #define BSP_I2C_NUM 0 // I2C外设,选择IIC0
#define BSP_IIC_FREQ_HZ 100000 // 100kHz #define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0

BSP_IIS.h

定义IIS接口引脚

#ifndef __BSP_IIS_H
#define __BSP_IIS_H #include "driver/i2s_tdm.h" // 包含I2S TDM(TDM模式)驱动接口,用于多通道音频数据传输
#include "driver/i2s_std.h" // 包含I2S标准模式驱动接口,用于常见的I2S音频数据传输 /* I2S port and GPIOs */
// 定义I2S编号为0,用I2S0
#define EXAMPLE_I2S_NUM (0)
// 定义I2S模块的主时钟(MCK)引脚编号为38
#define EXAMPLE_I2S_MCK_IO (38)
// 定义I2S模块的位时钟(BCK)引脚编号为14
#define EXAMPLE_I2S_BCK_IO (14)
// 定义I2S模块的左右声道选择(WS)引脚编号为13
#define EXAMPLE_I2S_WS_IO (13)
// 定义I2S模块的数据输入(DI)引脚编号为12
#define EXAMPLE_I2S_DI_IO /* I2S configurations */
// 定义I2S TDM格式为I2S格式
#define EXAMPLE_I2S_TDM_FORMAT (ES7210_I2S_FMT_I2S)
// 定义I2S通道数为2
#define EXAMPLE_I2S_CHAN_NUM (2)
// 定义I2S采样率为48000Hz
#define EXAMPLE_I2S_SAMPLE_RATE (48000)
// 定义I2S主时钟倍数为256
#define EXAMPLE_I2S_MCLK_MULTIPLE (I2S_MCLK_MULTIPLE_256)
// 定义I2S采样位宽为16位
#define EXAMPLE_I2S_SAMPLE_BITS (I2S_DATA_BIT_WIDTH_16BIT)
// 定义I2S TDM插槽掩码,使用插槽0和插槽1
#define EXAMPLE_I2S_TDM_SLOT_MASK (I2S_TDM_SLOT0 | I2S_TDM_SLOT1) #endif // __BSP_IIS_H

注意main文件夹下的CMakeList.txt应包含所用源文件路径

idf_component_register(SRCS "BSP_IIC.c" "main.c" "Sound_input.c" "BSP_SD.c"
INCLUDE_DIRS ".")

音频输出-ES8311

添加ES8311组件和依赖

乐鑫组件管理器官网:https://components.espressif.com/

搜索ES8311,找到命令下载并复制

esp-idf终端通过命令添加组件

idf.py add-dependency "espressif/es8311^1.0.0~1"

注意:添加组件后需重新选择芯片,才能使组件成功添加,然后再检查flash配置是否还是16M

PCA9557,IO拓展芯片

该芯片可以拓展为8位IO输出或输入

IIC高位地址0011 低位地址由A2,A1,A0和控制位控制

PCA9557通过IIC总线写入数据控制8位IO口输出,A0,A1,A2可通过接上拉电阻或接地,改变PCA9557的IIC的器件地址,避免冲突

该io拓展芯片在实战派s3上通过I2C总线控制,拓展出三个io输出口,分别控制LCD,喇叭和DVP摄像头

A2:0 A1:0 A0:1

IIC地址:0011 001x=0x19+(0输出,1输入)。在此应用为IO输出拓展,控制位为0,即IIC地址为0x19

将mp3格式音乐转化为pcm

详见目录——工具:

将mp3格式音乐转化为pcm

主函数main.c:

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#include "freertos/task.h"
#include "BSP_IIC.h" #include "Sound_output.h"
#include "IOadd_PCA9557.h"//IO拓展
static const char *TAG = "main"; void app_main(void)
{
printf("i2s es8311 codec example start\n-----------------------------\n");
/* 初始化I2S外设 */
if (i2s_driver_init() != ESP_OK) {
ESP_LOGE(TAG, "i2s driver init failed");
abort();
} else {
ESP_LOGI(TAG, "i2s driver init success");
} /* 初始化I2C 以及初始化es8311芯片 */
if (es8311_codec_init() != ESP_OK) {
ESP_LOGE(TAG, "es8311 codec init failed");
abort();
} else {
ESP_LOGI(TAG, "es8311 codec init success");
} pca9557_init(); //初始化IO扩展芯片
pa_en(1); // 打开音频 /* 创建播放音乐任务 */
xTaskCreate(i2s_music, "i2s_music", 4096, NULL, 5, NULL);
}
  1. 初始化I2S音频通道
  2. 初始化I2C,用于控制IO拓展芯片和初始化es8311芯片
  3. 初始化IO拓展芯片,开启喇叭控制引脚
  4. 创建任务,播放音乐

Sound_output.c

#include "Sound_output.h"
// 包含Sound_output头文件,用于声音输出相关的定义和函数声明
// 初始化I2C接口 并初始化es8311芯片
#include "BSP_IIS.h"
// 包含BSP_IIS头文件,用于I2S外设的初始化和配置
// 定义一个全局变量tx_handle,用于存储I2S发送通道的句柄
i2s_chan_handle_t tx_handle = NULL; static const char *TAG = "Sound_output";
// 定义一个静态常量TAG,用于日志输出时的标签
static const char err_reason[][30] = {"input param is invalid",
"operation timeout"
};
// 定义一个静态常量数组err_reason,用于存储错误原因的字符串
esp_err_t es8311_codec_init(void)
{
/* 初始化I2C接口 */
ESP_ERROR_CHECK(bsp_i2c_init()); // 调用bsp_i2c_init函数初始化I2C接口,并检查返回值是否为ESP_OK
/* 初始化es8311芯片 */
es8311_handle_t es_handle = es8311_create(BSP_I2C_NUM, ES8311_ADDRRES_0);
// 创建es8311芯片的句柄,使用BSP_I2C_NUM和ES8311_ADDRRES_0作为参数
ESP_RETURN_ON_FALSE(es_handle, ESP_FAIL, TAG, "es8311 create failed");
// 检查es_handle是否为NULL,如果是则返回ESP_FAIL并输出错误信息
const es8311_clock_config_t es_clk = {
.mclk_inverted = false,
.sclk_inverted = false,
.mclk_from_mclk_pin = true,
.mclk_frequency = EXAMPLE_MCLK_FREQ_HZ,
.sample_frequency = EXAMPLE_SAMPLE_RATE
}; // 定义es8311的时钟配置结构体,设置相关参数
// 初始化es8311芯片,传入句柄、时钟配置、分辨率等参数,并检查返回值是否为ESP_OK ESP_ERROR_CHECK(es8311_init(es_handle, &es_clk, ES8311_RESOLUTION_16, ES8311_RESOLUTION_16));
// 设置es8311的采样频率,并检查返回值是否为ESP_OK
ESP_RETURN_ON_ERROR(es8311_sample_frequency_config(es_handle, EXAMPLE_SAMPLE_RATE * EXAMPLE_MCLK_MULTIPLE, EXAMPLE_SAMPLE_RATE), TAG, "set es8311 sample frequency failed");
// 设置es8311的音量,并检查返回值是否为ESP_OK
ESP_RETURN_ON_ERROR(es8311_voice_volume_set(es_handle, EXAMPLE_VOICE_VOLUME, NULL), TAG, "set es8311 volume failed");
ESP_RETURN_ON_ERROR(es8311_microphone_config(es_handle, false), TAG, "set es8311 microphone failed"); // 配置es8311的麦克风,并检查返回值是否为ESP_OK
return ESP_OK;
// 返回ESP_OK表示初始化成功
} /* Import music file as buffer */
extern const uint8_t music_pcm_start[] asm("_binary_Music_pcm_start");
extern const uint8_t music_pcm_end[] asm("_binary_Music_pcm_end"); // 声明外部变量music_pcm_start和music_pcm_end,分别指向音乐文件的开始和结束位置
// 初始化I2S外设
esp_err_t i2s_driver_init(void)
{
/* 配置i2s发送通道 */
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM, I2S_ROLE_MASTER);
// 定义i2s发送通道的配置结构体,使用默认配置
chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
// 设置自动清除DMA缓冲区中的旧数据
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
// 创建新的I2S通道,传入配置结构体和句柄指针,并检查返回值是否为ESP_OK
/* 初始化i2s为std模式 并打开i2s发送通道 */
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EXAMPLE_SAMPLE_RATE),
// 设置时钟配置,使用默认配置和示例采样率
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
// 设置插槽配置,使用Philips模式,16位数据宽度和立体声模式
.gpio_cfg = {
.mclk = I2S_MCK_IO,
.bclk = I2S_BCK_IO,
.ws = I2S_WS_IO,
.dout = I2S_DO_IO,
.din = I2S_DI_IO,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
};
// 定义I2S的标准模式配置结构体,设置相关GPIO和时钟反转标志
std_cfg.clk_cfg.mclk_multiple = EXAMPLE_MCLK_MULTIPLE; // 设置主时钟倍数
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
// 初始化I2S通道为标准模式,传入通道句柄和配置结构体,并检查返回值是否为ESP_OK
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle)); // 启用I2S通道,并检查返回值是否为ESP_OK
return ESP_OK;
// 返回ESP_OK表示初始化成功
} void i2s_music(void *args)
{
esp_err_t ret = ESP_OK;
size_t bytes_write = 0;
uint8_t *data_ptr = (uint8_t *)music_pcm_start; // 定义局部变量ret、bytes_write和数据指针data_ptr,初始指向音乐文件开始位置
/* (Optional) Disable TX channel and preload the data before enabling the TX channel,
* so that the valid data can be transmitted immediately */
ESP_ERROR_CHECK(i2s_channel_disable(tx_handle));
// 禁用I2S发送通道,并检查返回值是否为ESP_OK
ESP_ERROR_CHECK(i2s_channel_preload_data(tx_handle, data_ptr, music_pcm_end - data_ptr, &bytes_write));
// 预加载数据到DMA缓冲区,并检查返回值是否为ESP_OK
data_ptr += bytes_write; // Move forward the data pointer
// 移动数据指针,跳过已预加载的数据
/* Enable the TX channel */
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
// 启用I2S发送通道,并检查返回值是否为ESP_OK
while (1) {
/* Write music to earphone */
ret = i2s_channel_write(tx_handle, data_ptr, music_pcm_end - data_ptr, &bytes_write, portMAX_DELAY);
// 向I2S通道写入音乐数据,传入通道句柄、数据指针、数据长度、写入字节数指针和超时时间,并获取返回值
if (ret != ESP_OK) {
/* Since we set timeout to 'portMAX_DELAY' in 'i2s_channel_write'
so you won't reach here unless you set other timeout value,
if timeout detected, it means write operation failed. */
ESP_LOGE(TAG, "[music] i2s write failed, %s", err_reason[ret == ESP_ERR_TIMEOUT]);
// 如果返回值不为ESP_OK,输出错误信息
abort();
}
// 检查写入的字节数是否大于0
if (bytes_write > 0) {
ESP_LOGI(TAG, "[music] i2s music played, %d bytes are written.", bytes_write);
} else {
ESP_LOGE(TAG, "[music] i2s music play failed.");
// 终止程序执行
abort();
}
// 将数据指针指向音乐PCM数据的起始位置
data_ptr = (uint8_t *)music_pcm_start;
// 延时1000毫秒(1秒),使用RTOS的tick周期进行计算
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}

修改播放音乐文件:

例如music.pcm

/* Import music file as buffer */
extern const uint8_t music_pcm_start[] asm("_binary_canon_pcm_start");
extern const uint8_t music_pcm_end[] asm("_binary_canon_pcm_end");

将canon替换成music

/* Import music file as buffer */
extern const uint8_t music_pcm_start[] asm("_binary_music_pcm_start");
extern const uint8_t music_pcm_end[] asm("_binary_music_pcm_end");

修改CMakeLists.txt

idf_component_register(SRCS "IOadd_PCA9557.c" "Sound_output.c" "BSP_IIC.c" "main.c" "BSP_IIS.c" "IOadd_PCA9557.c"
INCLUDE_DIRS "."
EMBED_FILES "canon.pcm")

修改为

idf_component_register(SRCS "IOadd_PCA9557.c" "Sound_output.c" "BSP_IIC.c" "main.c" "BSP_IIS.c" "IOadd_PCA9557.c"
INCLUDE_DIRS "."
EMBED_FILES "music.pcm")

LCD显示(240*320,ISP,2寸,SPI,ST7789)

开发板上的液晶屏是 2.0 寸的 IPS 高清液晶屏,分辨率 240*320。

液晶屏驱动芯片 ST7789,采用 SPI 通信方式与 ESP32-S3 连接。

main.c

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#include "freertos/task.h" #include "IOadd_PCA9557.h"// IO扩展芯片
#include "BSP_IIC.h"
#include "LCD.h"
#include "yingwu.h"//图片头文件
#include "logo_en_240x240_lcd.h"//图片头文件 static const char *TAG = "main";
void app_main(void)
{
i2c_master_init(); // I2C初始化
pca9557_init(); // IO扩展芯片初始化,开启LCD_CS
bsp_lcd_init(); // 液晶屏初始化
// lcd_draw_pictrue(0, 0, 240, 240, gImage_maozi); // 显示帽子图片 lcd_set_color(0x669E); // 设置整屏颜色
lcd_draw_pictrue(0, 0, 240, 240, logo_en_240x240_lcd); // 显示乐鑫logo图片
}

IIC初始化:

#include "BSP_IIC.h"

#define BSP_I2C_SDA 1  //IO1
#define BSP_I2C_SCL 2
#define BSP_I2C_NUM 0 // I2C外设,选择IIC0
#define BSP_IIC_FREQ_HZ 100000 // 100kHz
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0 esp_err_t i2c_master_init(void)//初始化i2c
{
i2c_config_t conf = {
// 设置I2C模式为主机模式
.mode = I2C_MODE_MASTER,
.sda_io_num = BSP_I2C_SDA,
.scl_io_num = BSP_I2C_SCL,
// 启用I2C数据线(SDA)的内部上拉电阻
.sda_pullup_en = GPIO_PULLUP_ENABLE,
// 启用I2C时钟线(SCL)的内部上拉电阻
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = BSP_IIC_FREQ_HZ,
}; i2c_param_config(BSP_I2C_NUM, &conf);//选择IIC0初始化
//通常用来检查驱动程序是否成功安装。确保在调用 i2c_driver_install 之前,已经正确配置了 conf.mode 等参数。
return i2c_driver_install(BSP_I2C_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
}

LCD初始化

LCD驱动初始化:


// 液晶屏初始化
esp_err_t bsp_display_new(void)
{
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_err_t ret = ESP_OK;
// 背光初始化
ESP_RETURN_ON_ERROR(bsp_display_brightness_init(), TAG, "Brightness init failed");
// 初始化SPI总线
ESP_LOGD(TAG, "Initialize SPI bus");
const spi_bus_config_t buscfg = {
.sclk_io_num = BSP_LCD_SPI_CLK,
.mosi_io_num = BSP_LCD_SPI_MOSI,
.miso_io_num = GPIO_NUM_NC,
.quadwp_io_num = GPIO_NUM_NC,
.quadhd_io_num = GPIO_NUM_NC,
.max_transfer_sz = BSP_LCD_H_RES * BSP_LCD_V_RES * sizeof(uint16_t),
};
ESP_RETURN_ON_ERROR(spi_bus_initialize(BSP_LCD_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI init failed");
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
const esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = BSP_LCD_DC,
.cs_gpio_num = BSP_LCD_SPI_CS,
.pclk_hz = BSP_LCD_PIXEL_CLOCK_HZ,
.lcd_cmd_bits = LCD_CMD_BITS,
.lcd_param_bits = LCD_PARAM_BITS,
.spi_mode = 2,
.trans_queue_depth = 10,
};
ESP_GOTO_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)BSP_LCD_SPI_NUM, &io_config, &io_handle), err, TAG, "New panel IO failed");
// 初始化液晶屏驱动芯片ST7789
ESP_LOGD(TAG, "Install LCD driver");
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = BSP_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = BSP_LCD_BITS_PER_PIXEL,
};
ESP_GOTO_ON_ERROR(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle), err, TAG, "New panel failed"); esp_lcd_panel_reset(panel_handle); // 液晶屏复位
lcd_cs(0); // 拉低CS引脚
esp_lcd_panel_init(panel_handle); // 初始化配置寄存器
esp_lcd_panel_invert_color(panel_handle, true); // 颜色反转
esp_lcd_panel_swap_xy(panel_handle, true); // 显示翻转
esp_lcd_panel_mirror(panel_handle, true, false); // 镜像 return ret; err:
if (panel_handle) {
esp_lcd_panel_del(panel_handle);
}
if (io_handle) {
esp_lcd_panel_io_del(io_handle);
}
spi_bus_free(BSP_LCD_SPI_NUM);
return ret;
}

LCD显示初始化设置:

// LCD显示初始化
esp_err_t bsp_lcd_init(void)
{
esp_err_t ret = ESP_OK; ret = bsp_display_new(); // 液晶屏驱动初始化
lcd_set_color(0x0000); // 设置整屏背景黑色
ret = esp_lcd_panel_disp_on_off(panel_handle, true); // 打开液晶屏显示
ret = bsp_display_backlight_on(); // 打开背光显示 return ret;
}

LCD基础操作函数:

bsp_display_brightness_init()背光PWM初始化

// 背光PWM初始化
esp_err_t bsp_display_brightness_init(void)
{
// Setup LEDC peripheral for PWM backlight control
const ledc_channel_config_t LCD_backlight_channel = {
.gpio_num = BSP_LCD_BACKLIGHT,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LCD_LEDC_CH,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = 1,
.duty = 0,
.hpoint = 0,
.flags.output_invert = true
};
const ledc_timer_config_t LCD_backlight_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_10_BIT,
.timer_num = 1,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK
}; ESP_ERROR_CHECK(ledc_timer_config(&LCD_backlight_timer));
ESP_ERROR_CHECK(ledc_channel_config(&LCD_backlight_channel)); return ESP_OK;
}

背光亮度设置bsp_display_brightness_set(brightness_percent)

// 背光亮度设置
esp_err_t bsp_display_brightness_set(int brightness_percent)
{
if (brightness_percent > 100) {
brightness_percent = 100;
} else if (brightness_percent < 0) {
brightness_percent = 0;
} ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness_percent);
// LEDC resolution set to 10bits, thus: 100% = 1023
uint32_t duty_cycle = (1023 * brightness_percent) / 100;
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty_cycle));
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH)); return ESP_OK;
}
开关背光(设置背光亮度):bsp_display_backlight_on(),bsp_display_backlight_off()
// 关闭背光
esp_err_t bsp_display_backlight_off(void)
{
return bsp_display_brightness_set(0);
} // 打开背光 最亮
esp_err_t bsp_display_backlight_on(void)
{
return bsp_display_brightness_set(100);
}

LCD显示函数:

显示图片:

void lcd_draw_pictrue(int x_start, int y_start, int x_end, int y_end, const unsigned char *gImage)
{
// 分配内存 分配了需要的字节大小 且指定在外部SPIRAM中分配
size_t pixels_byte_size = (x_end - x_start)*(y_end - y_start) * 2;
uint16_t *pixels = (uint16_t *)heap_caps_malloc(pixels_byte_size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
if (NULL == pixels)
{
// 使用ESP_LOGE宏记录一个错误级别的日志信息
// ESP_LOGE是ESP-IDF(Espressif IoT Development Framework)提供的日志记录功能
// TAG是一个宏定义,用于标识日志的来源,通常是一个字符串,表示模块或文件的名称
// "Memory for bitmap is not enough" 是要记录的日志信息,表示位图所需的内存不足
ESP_LOGE(TAG, "Memory for bitmap is not enough");
return;
}
memcpy(pixels, gImage, pixels_byte_size); // 把图片数据拷贝到内存
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_end, y_end, (uint16_t *)pixels); // 显示整张图片数据
heap_caps_free(pixels); // 释放内存
}

设置液晶屏颜色

void lcd_set_color(uint16_t color)
{
// 分配内存 这里分配了液晶屏一行数据需要的大小
uint16_t *buffer = (uint16_t *)heap_caps_malloc(BSP_LCD_H_RES * sizeof(uint16_t), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); if (NULL == buffer)
{
ESP_LOGE(TAG, "Memory for bitmap is not enough");
}
else
{
for (size_t i = 0; i < BSP_LCD_H_RES; i++) // 给缓存中放入颜色数据
{
buffer[i] = color;
}
for (int y = 0; y < 240; y++) // 显示整屏颜色
{
esp_lcd_panel_draw_bitmap(panel_handle, 0, y, 320, y+1, buffer);
}
free(buffer); // 释放内存
}
}

背景颜色设置:

lcd_set_color() 函数设置背景色,背景色是一个 16 位数,格式为 RGB565。

在计算机的各种画图软件中,颜色一般用 RGB888 表示,用 3 个字节表示颜色,顺序为红绿蓝,每个字节的大小用十进制表示,就是 0~255。例如,白色的表示就是(255,255,255),黑色的表示就是(0,0,0),纯红色(255,0,0),纯绿色(0,255,0),纯蓝色(0,0,255),其它颜色,都是改变这些数字的合成,例如黄绿色(154 205 50)。

我们想在液晶屏上显示自己的颜色,可以把 RGB888 转成 RGB565,转换原理是,R 取字节的前 5 位,G 取字节的前 6 位,B 取字节的前 5 位。例如,黄绿色(154 205 50),转换过程如下:

红:154,写成二进制为 1001 1010,取前 5 位为 10011

绿:205,写成二进制为 1100 1101,取前 6 位为 110011

蓝:50,写成二进制为 0011 0010,取前 5 位为 00110

把 RGB565 合起来,即 10011 110011 00110,4 位对齐显示 1001 1110 0110 0110,正好是 2 个字节,写成十六进制就是 0x9E66。

使用 lcd_set_color 函数显示颜色,需要将高低字节对调,即写成:

lcd_set_color(0x669E);

显示自定义图片:

准备一张你要显示的图片,把它搞成 320*240 大小,或者小于这个大小,常见的图片格式都可以。

借助一款软件:Image2LCD

设置:

输出数据类型为“C 语言数组”,

扫描方式为“水平扫描”,

输出灰度选择“16 位真彩色”,

最大宽度和高度设置为 320 和 240

底下勾选“高位在前”。

我转换好的图片头文件:

esps3_LCD_240_320: 保存esps3实战派转化的图片头文件

可添加后直接使用

显示效果(肉眼效果好一些,手机摄像头失真有颗粒感)

long.h 320*240

liang.h 320*240

baimao.h 256*240

maozi.h 320*240

摄像头(DVP 接口)

乐鑫组件管理器官网:https://components.espressif.com/

搜索esp32-camera

添加组件

idf.py add-dependency "espressif/esp32-camera^2.0.15"

重新选择芯片,等待组件下载添加

配置SDK

Serial flasher

config__Flash size:16M

SPIRAM

Support for external, SPI-connected RAM

Octal Mode PSRAM

80MHz clock speed

数据缓存指令CACHE

CPU

240MHz

详见目录板级配置

主函数app_main()

void app_main(void)
{
i2c_master_init(); // I2C初始化,配置IO拓展芯片
pca9557_init(); // IO扩展芯片初始化,开启LCD和摄像头控制引脚
bsp_lcd_init(); // 液晶屏初始化 lcd_draw_pictrue(0, 0, 320, 240, gImage_maozi); //显示一张图
vTaskDelay(2000 / portTICK_PERIOD_MS); bsp_camera_init(); // 摄像头初始化
app_camera_lcd(); // 摄像头显示到LCD,内含创建任务执行
}

主函数逻辑:

初始化总线,使用总线配置IO芯片初始化,

初始化使用到的硬件:LCD,摄像头

初始化IIC,IO拓展,LCD略(和前文一致)

初始化摄像头硬件bsp_camera_init():

// 摄像头硬件初始化
void bsp_camera_init(void)
{
dvp_pwdn(0); // 打开摄像头 camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_1; // LEDC通道选择 用于生成XCLK时钟 但是S3不用
config.ledc_timer = LEDC_TIMER_1; // LEDC timer选择 用于生成XCLK时钟 但是S3不用
config.pin_d0 = CAMERA_PIN_D0; // 设置摄像头数据引脚D0
config.pin_d1 = CAMERA_PIN_D1; // 设置摄像头数据引脚D1
config.pin_d2 = CAMERA_PIN_D2; // 设置摄像头数据引脚D2
config.pin_d3 = CAMERA_PIN_D3; // 设置摄像头数据引脚D3
config.pin_d4 = CAMERA_PIN_D4; // 设置摄像头数据引脚D4
config.pin_d5 = CAMERA_PIN_D5; // 设置摄像头数据引脚D5
config.pin_d6 = CAMERA_PIN_D6; // 设置摄像头数据引脚D6
config.pin_d7 = CAMERA_PIN_D7; // 设置摄像头数据引脚D7
config.pin_xclk = CAMERA_PIN_XCLK; // 设置摄像头XCLK引脚
config.pin_pclk = CAMERA_PIN_PCLK; // 设置摄像头PCLK引脚
config.pin_vsync = CAMERA_PIN_VSYNC; // 设置摄像头VSYNC引脚
config.pin_href = CAMERA_PIN_HREF; // 设置摄像头HREF引脚 config.pin_sccb_sda = -1; // 这里写-1 表示使用已经初始化的I2C接口
config.pin_sccb_scl = CAMERA_PIN_SIOC; // 设置摄像头SCCB SCL引脚
config.sccb_i2c_port = 0; // 设置摄像头SCCB I2C端口
config.pin_pwdn = CAMERA_PIN_PWDN; // 设置摄像头PWDN引脚
config.pin_reset = CAMERA_PIN_RESET; // 设置摄像头RESET引脚
config.xclk_freq_hz = XCLK_FREQ_HZ; // 设置摄像头XCLK频率
config.pixel_format = PIXFORMAT_RGB565; // 设置摄像头像素格式
config.frame_size = FRAMESIZE_QVGA; // 设置摄像头帧大小
config.jpeg_quality = 12; // 设置摄像头JPEG质量
config.fb_count = 2; // 设置摄像头帧缓冲区数量
config.fb_location = CAMERA_FB_IN_PSRAM; // 设置摄像头帧缓冲区位置
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; // 设置摄像头抓取模式 // camera init
esp_err_t err = esp_camera_init(&config); // 配置上面定义的参数
if (err != ESP_OK)
{
// ESP_LOGE(TAG2, "Camera init failed with error 0x%x", err);
return;
} sensor_t *s = esp_camera_sensor_get(); // 获取摄像头型号 if (s->id.PID == GC0308_PID) {
s->set_hmirror(s, 1); // 这里控制摄像头镜像 写1镜像 写0不镜像
}
}

引用组件#include "esp_camera.h"

初始化摄像头任务(软件):

// 让摄像头显示到LCD
void app_camera_lcd(void)
{
// 创建一个队列,用于存储camera_fb_t类型的指针,队列长度为2
xQueueLCDFrame = xQueueCreate(2, sizeof(camera_fb_t *));
// 创建一个任务,用于处理摄像头数据,任务名为"task_process_camera",堆栈大小为3KB,优先级为5,任务句柄为NULL,核心号为1
xTaskCreatePinnedToCore(task_process_camera, "task_process_camera", 3 * 1024, NULL, 5, NULL, 1);
// 创建一个任务,用于处理LCD数据,任务名为"task_process_lcd",堆栈大小为4KB,优先级为5,任务句柄为NULL,核心号为0
xTaskCreatePinnedToCore(task_process_lcd, "task_process_lcd", 4 * 1024, NULL, 5, NULL, 0);
}

任务(LCD,Camera):

// lcd处理任务
static void task_process_lcd(void *arg)
{
camera_fb_t *frame = NULL;
while (true)
{
// 从xQueueLCDFrame队列中接收一个frame,等待时间为portMAX_DELAY
if (xQueueReceive(xQueueLCDFrame, &frame, portMAX_DELAY))
{
// 在panel_handle面板上绘制frame中的位图,从(0,0)开始,宽度为frame->width,高度为frame->height,位图数据为frame->buf
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, frame->width, frame->height, (uint16_t *)frame->buf);
// 返回frame
esp_camera_fb_return(frame);
}
}
} // 摄像头处理任务
static void task_process_camera(void *arg)
{
while (true)
{
// 获取摄像头帧
camera_fb_t *frame = esp_camera_fb_get();
// 如果获取成功
if (frame)
// 将帧发送到队列
xQueueSend(xQueueLCDFrame, &frame, portMAX_DELAY);
}
}

执行逻辑

摄像头通过panel_handle指针传递给LCD显示

效果(这两摄像头是卧龙凤雏):

LVGL8.3.0

添加 esp_lvgl_port(接口)、lvgl(例程)、和 esp_lcd_touch_ft5x06 (触摸屏驱动)组件

乐鑫组件管理器官网:https://components.espressif.com/

使用特定版本:

修改idf_component.yml:

## IDF Component Manager Manifest File
dependencies:
# espressif/esp32-camera: "^2.0.10" # 摄像头驱动
lvgl/lvgl: '~8.3.0'
espressif/esp_lvgl_port: '~1.4.0' # LVGL接口
espressif/esp_lcd_touch_ft5x06: '~1.0.6' # 触摸屏驱动

重新选择芯片,自动下载组件

主函数main

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#include "freertos/task.h" #include "IOadd_PCA9557.h"// IO扩展芯片
#include "BSP_IIC.h"
#include "LCD.h"
#include "esp_log.h" #include "lvgl_lcd_port.h" // 用户编写的lvgl液晶屏接口
#include "demos/lv_demos.h" // 演示例程 void app_main(void)
{
i2c_master_init(); // I2C初始化
pca9557_init(); // IO扩展芯片初始化 bsp_lvgl_start(); // 初始化液晶屏lvgl接口(初始化lcd+touch,添加lvgl接口)
//lv_demo_benchmark();
lv_demo_music(); //音乐例程
}

主函数逻辑——相关初始化:

初始化LCD外设显示和触摸(FT6336驱动,使用组件espressif__esp_lcd_touch_ft5x06),并添加到lvgl接口

运行lvgl例程(music)

用户编写lvgl使用的

lvgl_lcd_port.c

#include "lvgl_lcd_port.h"
#include "LCD.h"
#include "esp_lcd_touch_ft5x06.h"//触摸组件
#include "esp_check.h"
#include "BSP_IIC.h" static lv_disp_t *disp; // 指向液晶屏 // LCD_lvgl_int
lv_disp_t *bsp_display_lcd_init(void)
{
/* 初始化液晶屏 */
bsp_display_new(); // 液晶屏驱动初始化
lcd_set_color(0xffff); // 设置整屏背景白色
esp_lcd_panel_disp_on_off(panel_handle, true); // 打开液晶屏显示 /* 液晶屏添加LVGL接口 */
ESP_LOGD(TAG, "Add LCD screen");
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = io_handle,
.panel_handle = panel_handle,
.buffer_size = BSP_LCD_H_RES * BSP_LCD_DRAW_BUF_HEIGHT, // LVGL缓存大小
.double_buffer = false, // 是否开启双缓存
.hres = BSP_LCD_H_RES, // 液晶屏的宽
.vres = BSP_LCD_V_RES, // 液晶屏的高
.monochrome = false, // 是否单色显示器
/* Rotation的值必须和液晶屏初始化里面设置的 翻转 和 镜像 一样 */
.rotation = {
.swap_xy = true, // 是否翻转
.mirror_x = true, // x方向是否镜像
.mirror_y = false, // y方向是否镜像
},
.flags = {
.buff_dma = false, // 是否使用DMA 注意:dma与spiram不能同时为true
.buff_spiram = true, // 是否使用PSRAM 注意:dma与spiram不能同时为true
}
}; return lvgl_port_add_disp(&disp_cfg);//将一个显示设备添加到 LittlevGL(LVGL)图形库中。
} static esp_lcd_touch_handle_t tp; // 触摸屏句柄
static lv_disp_t *disp; // 指向液晶屏 // touch_int
esp_err_t bsp_touch_new(esp_lcd_touch_handle_t *ret_touch)
{
/* Initialize touch */
// 初始化触摸
esp_lcd_touch_config_t tp_cfg = {
.x_max = BSP_LCD_V_RES, // X轴最大值
.y_max = BSP_LCD_H_RES, // Y轴最大值
.rst_gpio_num = GPIO_NUM_NC, // Shared with LCD reset
.int_gpio_num = GPIO_NUM_NC,
.levels = {
// 设置触摸屏的复位级别
.reset = 0,
// 设置触摸屏的中断级别
.interrupt = 0,
},
// 设置触摸屏的标志位
.flags = {
// 设置触摸屏的XY轴交换
.swap_xy = 1,
// 设置触摸屏的X轴镜像
.mirror_x = 1,
// 设置触摸屏的Y轴镜像
.mirror_y = 0,
},
};
// 声明触摸屏的IO句柄
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
// 声明触摸屏的IO I2C配置
esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG();
// 创建触摸屏的IO I2C句柄
ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)BSP_I2C_NUM, &tp_io_config, &tp_io_handle), TAG, "");
// 创建触摸屏的FT5x06句柄
ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, ret_touch));
return ESP_OK;
} // touch_lvgl_int
lv_indev_t *bsp_display_indev_init(lv_disp_t *disp)
{
/* 初始化触摸屏 */
// 使用ESP-IDF的API创建一个新的触摸屏设备,并将句柄存储在tp变量中
ESP_ERROR_CHECK(bsp_touch_new(&tp));
// 断言tp不为空,确保触摸屏设备成功初始化
assert(tp);
/* 添加LVGL接口 */
// 定义一个LVGL触摸配置结构体,用于配置触摸屏接口
const lvgl_port_touch_cfg_t touch_cfg = {
.disp = disp, // 指向LVGL显示设备的指针
.handle = tp, // 触摸屏设备的句柄
};
// 调用lvgl_port_add_touch函数,将触摸屏设备添加到LVGL的输入设备中,并返回输入设备的指针
return lvgl_port_add_touch(&touch_cfg);
} // LVGL总初始化
void bsp_lvgl_start(void)
{
/* 初始化LVGL */
// 定义一个lvgl_port_cfg_t类型的变量lvgl_cfg,并初始化为ESP_LVGL_PORT_INIT_CONFIG()的返回值
lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG();
// 调用lvgl_port_init函数,传入lvgl_cfg作为参数,初始化lvgl
lvgl_port_init(&lvgl_cfg); /* 初始化液晶屏 并添加LVGL接口 */
disp = bsp_display_lcd_init();
/* 初始化触摸屏 并添加LVGL接口 */
disp_indev = bsp_display_indev_init(disp);
/* 打开液晶屏背光 */
bsp_display_backlight_on(); }

文件作用:

封装触摸初始化函数,lvgl调用LCD显示,触摸的接口函数。

头文件

#ifndef __lvgl_lcd_port_h__
#define __lvgl_lcd_port_h__ #include "esp_lvgl_port.h"//esp #include "LCD.h"
#include "esp_lcd_touch_ft5x06.h"//lcd触摸组件
#include "esp_check.h"
#include "BSP_IIC.h" static const char *TAG = "lvgl_lcd_port";
static lv_disp_t *disp; // 指向液晶屏
static lv_indev_t *disp_indev = NULL; // 指向触摸屏 #define BSP_LCD_DRAW_BUF_HEIGHT (20) //LCD绘图缓冲区的高度 // touch_int
esp_err_t bsp_touch_new(esp_lcd_touch_handle_t *ret_touch);
// LCD_lvgl_int
lv_disp_t *bsp_display_lcd_init(void);
// touch_lvgl_int
lv_indev_t *bsp_display_indev_init(lv_disp_t *disp); // 开发板显示lvgl接口初始化
void bsp_lvgl_start(void); #endif

声明封装函数,提供给main.c的main()函数调用

注意

CMakeLists.txt中添加"lvgl_lcd_port.c"

LVGL_menuconfig配置

使用官方例程,需添加头文件#include "demos/lv_demos.h" // 演示例程

同时在menuconfig中勾选使用的例程,如我所用的music

同时选择该例程使用到的字体大小(music例程使用到12,14,16)

注意,主函数一次只能运行一个例程,除使用到的例程注释。

测试最新版组件是否兼容

LVGL9.2.2

esp_lvgl_port 2.4.3

idf.py add-dependency "lvgl/lvgl^9.2.2"
idf.py add-dependency "espressif/esp_lvgl_port^2.4.3"
idf.py add-dependency "espressif/esp_lcd_touch_ft5x06^1.0.6~1"

替换使用后,字体,颜色,帧率均有影响,不知是适配问题还是配置问题,将就着也能用,就是效果不好

之后有机会再改配置重新测试

目前建议还是先使用测试过稳定的8.3.0

WIFI

WiFi的AP模式(Access Point模式)和STA模式(Station模式)是无线网络中的两种基本工作模式,它们定义了设备在网络中的角色和行为。

模式 描述
AP模式 在AP模式下,设备充当无线网络的中心节点,也就是无线路由器热点。设备负责管理无线网络,包括广播SSID,管理连接的客户端设备,以及提供数据传输的中继点。客户端设备(如智能手机、笔记本电脑等)可以连接到AP模式的设备上,通过它访问互联网或其他网络资源。AP模式的设备通常具备更强的处理能力和更稳定的网络连接,以支持多个客户端设备同时连接和数据传输。
STA模式 STA模式是指设备作为无线网络中的一个客户端节点,也就是普通的无线终端设备。在STA模式下,设备连接到一个已经存在的无线网络(通常是通过AP模式的设备建立的),以便访问网络资源或互联网。STA模式的设备不需要广播SSID,它们只需要搜索并连接到可用的无线网络,并根据网络配置进行数据传输。STA模式的设备通常更注重移动性和便携性,它们可能不具备AP模式设备那样的高级网络管理功能。

先粗浅简单的理解为,

AP是设备作为热点开启WIFI

STA是设备连接WIFI

AP模式

STA模式

WIFI扫描

WIFI_can.c
	/* Scan Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/*
This example shows how to scan for available set of APs.//(station模式)扫描ap
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "esp_wifi_types.h" #define DEFAULT_SCAN_LIST_SIZE CONFIG_EXAMPLE_SCAN_LIST_SIZE static const char *TAG = "scan"; // 用于打印WiFi认证模式
static void print_auth_mode(int authmode)
{
// 使用switch语句根据传入的authmode参数值选择相应的分支
switch (authmode) {
// 如果authmode为WIFI_AUTH_OPEN,打印对应的认证模式
case WIFI_AUTH_OPEN:
ESP_LOGI(TAG, "Authmode \tWIFI_AUTH_OPEN");
break;
case WIFI_AUTH_OWE:
ESP_LOGI(TAG, "Authmode \tWIFI_AUTH_OWE");
break;
//略......
}
} // 定义一个静态函数,用于打印WiFi的成对密码类型和组密码类型
static void print_cipher_type(int pairwise_cipher, int group_cipher)
{
// 根据成对密码类型进行分支处理
switch (pairwise_cipher) {
case WIFI_CIPHER_TYPE_NONE:
// 如果成对密码类型为无加密,打印相关信息
ESP_LOGI(TAG, "Pairwise Cipher \tWIFI_CIPHER_TYPE_NONE");
break;
case WIFI_CIPHER_TYPE_WEP40:
// 如果成对密码类型为WEP40,打印相关信息
ESP_LOGI(TAG, "Pairwise Cipher \tWIFI_CIPHER_TYPE_WEP40");
break;
//略...... // 根据组密码类型进行分支处理
switch (group_cipher) {
case WIFI_CIPHER_TYPE_NONE:
ESP_LOGI(TAG, "Group Cipher \tWIFI_CIPHER_TYPE_NONE");
break;
case WIFI_CIPHER_TYPE_WEP40:
ESP_LOGI(TAG, "Group Cipher \tWIFI_CIPHER_TYPE_WEP40");
break;
//略...... }
} /* Initialize Wi-Fi as sta and set scan method */
//分为wifi的初始化阶段、配置阶段、启动阶段
static void wifi_scan(void)//(STA)
{
// wifi初始化阶段。
ESP_ERROR_CHECK(esp_netif_init());//创建一个 LwIP 核心任务,并初始化 LwIP 相关工作
ESP_ERROR_CHECK(esp_event_loop_create_default());//创建系统事件任务和回调函数,wifi的事件会在回调函数中处理。
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta(); //创建有 TCP/IP 堆栈的默认网络接口
assert(sta_netif); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));//创建Wi-Fi驱动程序任务,并初始化Wi-Fi驱动程序。 uint16_t number = DEFAULT_SCAN_LIST_SIZE;
wifi_ap_record_t ap_info[DEFAULT_SCAN_LIST_SIZE];
uint16_t ap_count = 0;
memset(ap_info, 0, sizeof(ap_info)); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));//将Wi-Fi模式配置为station模式。
ESP_ERROR_CHECK(esp_wifi_start());//负责启动WI-FI驱动程序。
esp_wifi_scan_start(NULL, true);//wifi扫描部分
ESP_LOGI(TAG, "Max AP number ap_info can hold = %u", number);
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count));
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&number, ap_info));
ESP_LOGI(TAG, "Total APs scanned = %u, actual AP number ap_info holds = %u", ap_count, number);
for (int i = 0; i < number; i++) {
ESP_LOGI(TAG, "SSID \t\t%s", ap_info[i].ssid);
ESP_LOGI(TAG, "RSSI \t\t%d", ap_info[i].rssi);
print_auth_mode(ap_info[i].authmode);//打印WIFI认证模式
if (ap_info[i].authmode != WIFI_AUTH_WEP) {
print_cipher_type(ap_info[i].pairwise_cipher, ap_info[i].group_cipher);
}
ESP_LOGI(TAG, "Channel \t\t%d\n", ap_info[i].primary);
}
} void app_main(void)
{
// Initialize NVS初始化nvs
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret ); wifi_scan();//STA mode
}

  • wifi_init 表示 Wi-Fi 初始化成功,并启用了 Wi-Fi IRAM 操作。
  • phy_init 显示了物理层初始化的版本信息。
  • wifi:mode : sta 表示 Wi-Fi 已经设置为站点模式。
  • scan 部分显示了扫描到的 AP 信息,包括 SSID、RSSI、认证模式、加密类型和信道。

WIFI连接

WiFi_station.c
/* WiFi station Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h" #include "lwip/err.h"
#include "lwip/sys.h" /* The examples use WiFi configuration that you can set via project configuration menu If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
#define EXAMPLE_ESP_MAXIMUM_RETRY CONFIG_ESP_MAXIMUM_RETRY #if CONFIG_ESP_WPA3_SAE_PWE_HUNT_AND_PECK
#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_HUNT_AND_PECK
#define EXAMPLE_H2E_IDENTIFIER ""
#elif CONFIG_ESP_WPA3_SAE_PWE_HASH_TO_ELEMENT
#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_HASH_TO_ELEMENT
#define EXAMPLE_H2E_IDENTIFIER CONFIG_ESP_WIFI_PW_ID
#elif CONFIG_ESP_WPA3_SAE_PWE_BOTH
#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_BOTH
#define EXAMPLE_H2E_IDENTIFIER CONFIG_ESP_WIFI_PW_ID
#endif
#if CONFIG_ESP_WIFI_AUTH_OPEN
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN
#elif CONFIG_ESP_WIFI_AUTH_WEP
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP
#elif CONFIG_ESP_WIFI_AUTH_WPA_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WAPI_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK
#endif /* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group; /* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1 static const char *TAG = "wifi station"; static int s_retry_num = 0; // 事件处理函数,处理WiFi和IP事件
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
// WiFi启动事件,尝试连接到AP
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
} void wifi_init_sta(void)
{
s_wifi_event_group = xEventGroupCreate(); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg)); esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip)); wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS,
/* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (pasword len => 8).
* If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value
* to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to
* WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards.
*/
.threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,
.sae_pwe_h2e = ESP_WIFI_SAE_MODE,
.sae_h2e_identifier = EXAMPLE_H2E_IDENTIFIER,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() ); ESP_LOGI(TAG, "wifi_init_sta finished."); /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
} void app_main(void)
{
//Initialize NVS
// 初始化NVS闪存,并获取返回值
esp_err_t ret = nvs_flash_init();
// 检查返回值是否为NVS闪存无空闲页面错误或NVS闪存版本错误
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
// 如果是上述错误,则擦除NVS闪存
ESP_ERROR_CHECK(nvs_flash_erase());
// 重新初始化NVS闪存
ret = nvs_flash_init();
}
// 检查NVS闪存初始化的返回值,确保没有错误
ESP_ERROR_CHECK(ret); // 打印日志信息,表示当前WiFi模式为STA模式
ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
// 初始化WiFi为STA模式
wifi_init_sta();
}

代码虽长,逻辑却简单:

  1. 定义了许多wifi参数宏定义,并设置为可在menuconfig中进行配置。
  2. 初始化WIFI必备的NVS
  3. 初始化WIFI为STA模式

WIFI连接的函数,乐鑫例程有已经封装可参考,普通用户直接使用即可

运行结果:

    • Wi-Fi驱动程序正在调整监听间隔。
    • 设置接收信标的参数。
    • 再次调整监听间隔。
    • 显示接入点的信标间隔和DTIM周期。
    • 设备获得了IP地址、子网掩码和网关。
    • Wi-Fi站点模式下设备获得了IP地址。
    • 设备成功连接到SSID为“jianzhiji”的接入点,密码为“8765432111”。
    • app_main()函数执行完毕并返回。

成功连接我手机热点jianzhiji

WIFI扫描连接

使用 wifi,必须初始化 NVS

wifi_scan_connect.c

#include "app_ui.h"
#include "esp32_s3_szp.h"
#include "esp_wifi.h"
#include "freertos/event_groups.h"
#include "esp_event.h" static const char *TAG = "app_ui"; LV_FONT_DECLARE(font_alipuhui20); lv_obj_t *wifi_scan_page; // wifi扫描页面 obj
lv_obj_t *wifi_connect_page; // wifi连接页面 obj
lv_obj_t *wifi_password_page; // wifi密码页面 obj
lv_obj_t *wifi_list; // wifi列表 list
lv_obj_t *label_wifi_connect; // wifi连接页面label
lv_obj_t *ta_pass_text; // 密码输入文本框 textarea
lv_obj_t *roller_num; // 数字roller
lv_obj_t *roller_letter_low; // 小写字母roller
lv_obj_t *roller_letter_up; // 大写字母roller
lv_obj_t *label_wifi_name; // wifi名称label #define DEFAULT_SCAN_LIST_SIZE 10 // 最大扫描wifi个数 // wifi事件组
static EventGroupHandle_t s_wifi_event_group;
// wifi事件
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
#define WIFI_START_BIT BIT2
// wifi最大重连次数
#define EXAMPLE_ESP_MAXIMUM_RETRY 3 // wifi账号队列
static QueueHandle_t xQueueWifiAccount = NULL;
// 队列要传输的内容
typedef struct {
char wifi_ssid[32]; // 获取wifi名称
char wifi_password[64]; // 获取wifi密码
} wifi_account_t; // 密码roller的遮罩显示效果
static void mask_event_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * obj = lv_event_get_target(e); static int16_t mask_top_id = -1;
static int16_t mask_bottom_id = -1; if(code == LV_EVENT_COVER_CHECK) {
lv_event_set_cover_res(e, LV_COVER_RES_MASKED);
}
else if(code == LV_EVENT_DRAW_MAIN_BEGIN) {
/* add mask */
const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
lv_coord_t font_h = lv_font_get_line_height(font); lv_area_t roller_coords;
lv_obj_get_coords(obj, &roller_coords); lv_area_t rect_area;
rect_area.x1 = roller_coords.x1;
rect_area.x2 = roller_coords.x2;
rect_area.y1 = roller_coords.y1;
rect_area.y2 = roller_coords.y1 + (lv_obj_get_height(obj) - font_h - line_space) / 2; lv_draw_mask_fade_param_t * fade_mask_top = lv_mem_buf_get(sizeof(lv_draw_mask_fade_param_t));
lv_draw_mask_fade_init(fade_mask_top, &rect_area, LV_OPA_TRANSP, rect_area.y1, LV_OPA_COVER, rect_area.y2);
mask_top_id = lv_draw_mask_add(fade_mask_top, NULL); rect_area.y1 = rect_area.y2 + font_h + line_space - 1;
rect_area.y2 = roller_coords.y2; lv_draw_mask_fade_param_t * fade_mask_bottom = lv_mem_buf_get(sizeof(lv_draw_mask_fade_param_t));
lv_draw_mask_fade_init(fade_mask_bottom, &rect_area, LV_OPA_COVER, rect_area.y1, LV_OPA_TRANSP, rect_area.y2);
mask_bottom_id = lv_draw_mask_add(fade_mask_bottom, NULL); }
else if(code == LV_EVENT_DRAW_POST_END) {
lv_draw_mask_fade_param_t * fade_mask_top = lv_draw_mask_remove_id(mask_top_id);
lv_draw_mask_fade_param_t * fade_mask_bottom = lv_draw_mask_remove_id(mask_bottom_id);
lv_draw_mask_free_param(fade_mask_top);
lv_draw_mask_free_param(fade_mask_bottom);
lv_mem_buf_release(fade_mask_top);
lv_mem_buf_release(fade_mask_bottom);
mask_top_id = -1;
mask_bottom_id = -1;
}
} // 数字键 处理函数
static void btn_num_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
ESP_LOGI(TAG, "Btn-Num Clicked");
char buf[2]; // 接收roller的值
lv_roller_get_selected_str(roller_num, buf, sizeof(buf));
lv_textarea_add_text(ta_pass_text, buf);
}
} // 小写字母确认键 处理函数
static void btn_letter_low_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
ESP_LOGI(TAG, "Btn-Letter-Low Clicked");
char buf[2]; // 接收roller的值
lv_roller_get_selected_str(roller_letter_low, buf, sizeof(buf));
lv_textarea_add_text(ta_pass_text, buf);
}
} // 大写字母确认键 处理函数
static void btn_letter_up_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
ESP_LOGI(TAG, "Btn-Letter-Up Clicked");
char buf[2]; // 接收roller的值
lv_roller_get_selected_str(roller_letter_up, buf, sizeof(buf));
lv_textarea_add_text(ta_pass_text, buf);
}
} static void lv_wifi_connect(void)
{
lv_obj_del(wifi_password_page); // 删除密码输入界面
// 创建一个面板对象
static lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_opa( &style, LV_OPA_COVER );
lv_style_set_border_width(&style, 0);
lv_style_set_pad_all(&style, 0);
lv_style_set_radius(&style, 0);
lv_style_set_width(&style, 320);
lv_style_set_height(&style, 240); wifi_connect_page = lv_obj_create(lv_scr_act());
lv_obj_add_style(wifi_connect_page, &style, 0); // 绘制label提示
label_wifi_connect = lv_label_create(wifi_connect_page);
lv_label_set_text(label_wifi_connect, "WLAN连接中...");
lv_obj_set_style_text_font(label_wifi_connect, &font_alipuhui20, 0);
lv_obj_align(label_wifi_connect, LV_ALIGN_CENTER, 0, -50);
} // WiFi连接按钮 处理函数
static void btn_connect_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
ESP_LOGI(TAG, "OK Clicked");
const char *wifi_ssid = lv_label_get_text(label_wifi_name);
const char *wifi_password = lv_textarea_get_text(ta_pass_text);
if(*wifi_password != '\0') // 判断是否为空字符串
{
wifi_account_t wifi_account;
strcpy(wifi_account.wifi_ssid, wifi_ssid);
strcpy(wifi_account.wifi_password, wifi_password);
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
wifi_account.wifi_ssid, wifi_account.wifi_password);
lv_wifi_connect(); // 显示wifi连接界面
// 发送WiFi账号密码信息到队列
xQueueSend(xQueueWifiAccount, &wifi_account, portMAX_DELAY);
}
}
} // 删除密码按钮
static void btn_del_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
ESP_LOGI(TAG, "Clicked");
lv_textarea_del_char(ta_pass_text);
}
} // 返回按钮
static void btn_back_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
ESP_LOGI(TAG, "btn_back Clicked");
lv_obj_del(wifi_password_page); // 删除密码输入界面
}
} // 进入输入密码界面
static void list_btn_cb(lv_event_t * e)
{
// 获取点击到的WiFi名称
const char *wifi_name=NULL;
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * obj = lv_event_get_target(e);
if(code == LV_EVENT_CLICKED) {
wifi_name = lv_list_get_btn_text(wifi_list, obj);
ESP_LOGI(TAG, "WLAN Name: %s", wifi_name);
} // 创建密码输入页面
wifi_password_page = lv_obj_create(lv_scr_act());
lv_obj_set_size(wifi_password_page, 320, 240);
lv_obj_set_style_border_width(wifi_password_page, 0, 0); // 设置边框宽度
lv_obj_set_style_pad_all(wifi_password_page, 0, 0); // 设置间隙
lv_obj_set_style_radius(wifi_password_page, 0, 0); // 设置圆角 // 创建返回按钮
lv_obj_t *btn_back = lv_btn_create(wifi_password_page);
lv_obj_align(btn_back, LV_ALIGN_TOP_LEFT, 0, 0);
lv_obj_set_size(btn_back, 60, 40);
lv_obj_set_style_border_width(btn_back, 0, 0); // 设置边框宽度
lv_obj_set_style_pad_all(btn_back, 0, 0); // 设置间隙
lv_obj_set_style_bg_opa(btn_back, LV_OPA_TRANSP, LV_PART_MAIN); // 背景透明
lv_obj_set_style_shadow_opa(btn_back, LV_OPA_TRANSP, LV_PART_MAIN); // 阴影透明
lv_obj_add_event_cb(btn_back, btn_back_cb, LV_EVENT_ALL, NULL); // 添加按键处理函数 lv_obj_t *label_back = lv_label_create(btn_back);
lv_label_set_text(label_back, LV_SYMBOL_LEFT); // 按键上显示左箭头符号
lv_obj_set_style_text_font(label_back, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(label_back, lv_color_hex(0x000000), 0);
lv_obj_align(label_back, LV_ALIGN_TOP_LEFT, 10, 10); // 显示选中的wifi名称
label_wifi_name = lv_label_create(wifi_password_page);
lv_obj_set_style_text_font(label_wifi_name, &font_alipuhui20, 0);
lv_label_set_text(label_wifi_name, wifi_name);
lv_obj_align(label_wifi_name, LV_ALIGN_TOP_MID, 0, 10); // 创建密码输入框
ta_pass_text = lv_textarea_create(wifi_password_page);
lv_obj_set_style_text_font(ta_pass_text, &lv_font_montserrat_20, 0);
lv_textarea_set_one_line(ta_pass_text, true); // 一行显示
lv_textarea_set_password_mode(ta_pass_text, false); // 是否使用密码输入显示模式
lv_textarea_set_placeholder_text(ta_pass_text, "password"); // 设置提醒词
lv_obj_set_width(ta_pass_text, 150); // 宽度
lv_obj_align(ta_pass_text, LV_ALIGN_TOP_LEFT, 10, 40); // 位置
lv_obj_add_state(ta_pass_text, LV_STATE_FOCUSED); // 显示光标 // 创建“连接按钮”
lv_obj_t *btn_connect = lv_btn_create(wifi_password_page);
lv_obj_align(btn_connect, LV_ALIGN_TOP_LEFT, 170, 40);
lv_obj_set_width(btn_connect, 65); // 宽度
lv_obj_add_event_cb(btn_connect, btn_connect_cb, LV_EVENT_ALL, NULL); // 事件处理函数 lv_obj_t *label_ok = lv_label_create(btn_connect);
lv_label_set_text(label_ok, "OK");
lv_obj_set_style_text_font(label_ok, &lv_font_montserrat_20, 0);
lv_obj_center(label_ok); // 创建“删除按钮”
lv_obj_t *btn_del = lv_btn_create(wifi_password_page);
lv_obj_align(btn_del, LV_ALIGN_TOP_LEFT, 245, 40);
lv_obj_set_width(btn_del, 65); // 宽度
lv_obj_add_event_cb(btn_del, btn_del_cb, LV_EVENT_ALL, NULL); // 事件处理函数 lv_obj_t *label_del = lv_label_create(btn_del);
lv_label_set_text(label_del, LV_SYMBOL_BACKSPACE);
lv_obj_set_style_text_font(label_del, &lv_font_montserrat_20, 0);
lv_obj_center(label_del); // 创建roller样式
static lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_color(&style, lv_color_black());
lv_style_set_text_color(&style, lv_color_white());
lv_style_set_border_width(&style, 0);
lv_style_set_pad_all(&style, 0);
lv_style_set_radius(&style, 0); // 创建"数字"roller
const char * opts_num = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9"; roller_num = lv_roller_create(wifi_password_page);
lv_obj_add_style(roller_num, &style, 0);
lv_obj_set_style_bg_opa(roller_num, LV_OPA_50, LV_PART_SELECTED); lv_roller_set_options(roller_num, opts_num, LV_ROLLER_MODE_INFINITE);
lv_roller_set_visible_row_count(roller_num, 3); // 显示3行
lv_roller_set_selected(roller_num, 5, LV_ANIM_OFF); // 默认选择
lv_obj_set_width(roller_num, 90);
lv_obj_set_style_text_font(roller_num, &lv_font_montserrat_20, 0);
lv_obj_align(roller_num, LV_ALIGN_BOTTOM_LEFT, 15, -53);
lv_obj_add_event_cb(roller_num, mask_event_cb, LV_EVENT_ALL, NULL); // 事件处理函数 // 创建"数字"roller 的确认键
lv_obj_t *btn_num_ok = lv_btn_create(wifi_password_page);
lv_obj_align(btn_num_ok, LV_ALIGN_BOTTOM_LEFT, 15, -10); // 位置
lv_obj_set_width(btn_num_ok, 90); // 宽度
lv_obj_add_event_cb(btn_num_ok, btn_num_cb, LV_EVENT_ALL, NULL); // 事件处理函数 lv_obj_t *label_num_ok = lv_label_create(btn_num_ok);
lv_label_set_text(label_num_ok, LV_SYMBOL_OK);
lv_obj_set_style_text_font(label_num_ok, &lv_font_montserrat_20, 0);
lv_obj_center(label_num_ok); // 创建"小写字母"roller
const char * opts_letter_low = "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz"; roller_letter_low = lv_roller_create(wifi_password_page);
lv_obj_add_style(roller_letter_low, &style, 0);
lv_obj_set_style_bg_opa(roller_letter_low, LV_OPA_50, LV_PART_SELECTED); // 设置选中项的透明度
lv_roller_set_options(roller_letter_low, opts_letter_low, LV_ROLLER_MODE_INFINITE); // 循环滚动模式
lv_roller_set_visible_row_count(roller_letter_low, 3);
lv_roller_set_selected(roller_letter_low, 15, LV_ANIM_OFF); //
lv_obj_set_width(roller_letter_low, 90);
lv_obj_set_style_text_font(roller_letter_low, &lv_font_montserrat_20, 0);
lv_obj_align(roller_letter_low, LV_ALIGN_BOTTOM_LEFT, 115, -53);
lv_obj_add_event_cb(roller_letter_low, mask_event_cb, LV_EVENT_ALL, NULL); // 事件处理函数 // 创建"小写字母"roller的确认键
lv_obj_t *btn_letter_low_ok = lv_btn_create(wifi_password_page);
lv_obj_align(btn_letter_low_ok, LV_ALIGN_BOTTOM_LEFT, 115, -10);
lv_obj_set_width(btn_letter_low_ok, 90); // 宽度
lv_obj_add_event_cb(btn_letter_low_ok, btn_letter_low_cb, LV_EVENT_ALL, NULL); // 事件处理函数 lv_obj_t *label_letter_low_ok = lv_label_create(btn_letter_low_ok);
lv_label_set_text(label_letter_low_ok, LV_SYMBOL_OK);
lv_obj_set_style_text_font(label_letter_low_ok, &lv_font_montserrat_20, 0);
lv_obj_center(label_letter_low_ok); // 创建"大写字母"roller
const char * opts_letter_up = "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ"; roller_letter_up = lv_roller_create(wifi_password_page);
lv_obj_add_style(roller_letter_up, &style, 0);
lv_obj_set_style_bg_opa(roller_letter_up, LV_OPA_50, LV_PART_SELECTED); // 设置选中项的透明度
lv_roller_set_options(roller_letter_up, opts_letter_up, LV_ROLLER_MODE_INFINITE); // 循环滚动模式
lv_roller_set_visible_row_count(roller_letter_up, 3);
lv_roller_set_selected(roller_letter_up, 15, LV_ANIM_OFF);
lv_obj_set_width(roller_letter_up, 90);
lv_obj_set_style_text_font(roller_letter_up, &lv_font_montserrat_20, 0);
lv_obj_align(roller_letter_up, LV_ALIGN_BOTTOM_LEFT, 215, -53);
lv_obj_add_event_cb(roller_letter_up, mask_event_cb, LV_EVENT_ALL, NULL); // 事件处理函数 // 创建"大写字母"roller的确认键
lv_obj_t *btn_letter_up_ok = lv_btn_create(wifi_password_page);
lv_obj_align(btn_letter_up_ok, LV_ALIGN_BOTTOM_LEFT, 215, -10);
lv_obj_set_width(btn_letter_up_ok, 90);
lv_obj_add_event_cb(btn_letter_up_ok, btn_letter_up_cb, LV_EVENT_ALL, NULL); // 事件处理函数 lv_obj_t *label_letter_up_ok = lv_label_create(btn_letter_up_ok);
lv_label_set_text(label_letter_up_ok, LV_SYMBOL_OK);
lv_obj_set_style_text_font(label_letter_up_ok, &lv_font_montserrat_20, 0);
lv_obj_center(label_letter_up_ok); } static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
static int s_retry_num = 0; if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
xEventGroupSetBits(s_wifi_event_group, WIFI_START_BIT);
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
} // 扫描附近wifi
static void wifi_scan(wifi_ap_record_t ap_info[], uint16_t *ap_number)
{
s_wifi_event_group = xEventGroupCreate(); ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
assert(sta_netif); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg)); esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip)); uint16_t ap_count = 0; memset(ap_info, 0, *ap_number * sizeof(wifi_ap_record_t)); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
esp_wifi_scan_start(NULL, true); ESP_LOGI(TAG, "Max AP number ap_info can hold = %u", *ap_number);
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count)); // 获取扫描到的wifi数量
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(ap_number, ap_info)); // 获取真实的获取到wifi数量和信息
ESP_LOGI(TAG, "Total APs scanned = %u, actual AP number ap_info holds = %u", ap_count, *ap_number);
} // lcd处理任务
static void wifi_connect(void *arg)
{
wifi_account_t wifi_account; while (true)
{
// 如果收到wifi账号队列消息
if(xQueueReceive(xQueueWifiAccount, &wifi_account, portMAX_DELAY))
{
wifi_config_t wifi_config = {
.sta = {
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
.sae_h2e_identifier = "",
},
};
strcpy((char *)wifi_config.sta.ssid, wifi_account.wifi_ssid);
strcpy((char *)wifi_config.sta.password, wifi_account.wifi_password);
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
wifi_config.sta.ssid, wifi_config.sta.password);
esp_wifi_connect();
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY); /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
wifi_config.sta.ssid, wifi_config.sta.password);
lvgl_port_lock(0);
lv_label_set_text(label_wifi_connect, "WLAN连接成功");
lvgl_port_unlock();
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
wifi_config.sta.ssid, wifi_config.sta.password);
lvgl_port_lock(0);
lv_label_set_text(label_wifi_connect, "WLAN连接失败");
lvgl_port_unlock();
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
lvgl_port_lock(0);
lv_label_set_text(label_wifi_connect, "WLAN连接异常");
lvgl_port_unlock();
}
}
}
} // wifi连接
void app_wifi_connect(void)
{
lvgl_port_lock(0);
// 创建WLAN扫描页面
static lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_opa( &style, LV_OPA_COVER ); // 背景透明度
lv_style_set_border_width(&style, 0); // 边框宽度
lv_style_set_pad_all(&style, 0); // 内间距
lv_style_set_radius(&style, 0); // 圆角半径
lv_style_set_width(&style, 320); // 宽
lv_style_set_height(&style, 240); // 高
wifi_scan_page = lv_obj_create(lv_scr_act());
lv_obj_add_style(wifi_scan_page, &style, 0);
// 在WLAN扫描页面显示提示
lv_obj_t *label_wifi_scan = lv_label_create(wifi_scan_page);
lv_label_set_text(label_wifi_scan, "WLAN扫描中...");
lv_obj_set_style_text_font(label_wifi_scan, &font_alipuhui20, 0);
lv_obj_align(label_wifi_scan, LV_ALIGN_CENTER, 0, -50);
lvgl_port_unlock(); // 扫描WLAN信息
wifi_ap_record_t ap_info[DEFAULT_SCAN_LIST_SIZE]; // 记录扫描到的wifi信息
uint16_t ap_number = DEFAULT_SCAN_LIST_SIZE;
wifi_scan(ap_info, &ap_number); // 扫描附近wifi lvgl_port_lock(0);
// 扫描附近wifi信息成功后 删除提示文字
lv_obj_del(label_wifi_scan);
// 创建wifi信息列表
wifi_list = lv_list_create(wifi_scan_page);
lv_obj_set_size(wifi_list, lv_pct(100), lv_pct(100));
lv_obj_set_style_border_width(wifi_list, 0, 0);
lv_obj_set_style_text_font(wifi_list, &font_alipuhui20, 0);
lv_obj_set_scrollbar_mode(wifi_list, LV_SCROLLBAR_MODE_OFF); // 隐藏wifi_list滚动条
// 显示wifi信息
lv_obj_t * btn;
for (int i = 0; i < ap_number; i++) {
ESP_LOGI(TAG, "SSID \t\t%s", ap_info[i].ssid); // 终端输出wifi名称
ESP_LOGI(TAG, "RSSI \t\t%d", ap_info[i].rssi); // 终端输出wifi信号质量
// 添加wifi列表
btn = lv_list_add_btn(wifi_list, LV_SYMBOL_WIFI, (const char *)ap_info[i].ssid);
lv_obj_add_event_cb(btn, list_btn_cb, LV_EVENT_CLICKED, NULL); // 添加点击回调函数
}
lvgl_port_unlock(); // 创建wifi连接任务
xQueueWifiAccount = xQueueCreate(2, sizeof(wifi_account_t));
xTaskCreatePinnedToCore(wifi_connect, "wifi_connect", 4 * 1024, NULL, 5, NULL, 1); // 创建wifi连接任务
}

此代码使用到LVGL,freertos任务,事件组,队列,WIFI(STA模式)扫描,连接

LVGL作为桥梁,将扫描到的wifi通过列表显示在LCD上,同时通过LVGL触控接口传入参数,保存后传入让WIFI连接用户WIFI。

WIFI网络授时

使用 LwIP SNTP 模块从 Internet 服务器获取时间

sntp_example_main.c

/* LwIP SNTP example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "esp_sleep.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "esp_netif_sntp.h"
#include "lwip/ip_addr.h"
#include "esp_sntp.h" static const char *TAG = "example"; #ifndef INET6_ADDRSTRLEN
#define INET6_ADDRSTRLEN 48
#endif /* Variable holding number of times ESP32 restarted since first boot.
* It is placed into RTC memory using RTC_DATA_ATTR and
* maintains its value when ESP32 wakes from deep sleep.
*/
RTC_DATA_ATTR static int boot_count = 0; static void obtain_time(void); #ifdef CONFIG_SNTP_TIME_SYNC_METHOD_CUSTOM
void sntp_sync_time(struct timeval *tv)
{
settimeofday(tv, NULL);
ESP_LOGI(TAG, "Time is synchronized from custom code");
sntp_set_sync_status(SNTP_SYNC_STATUS_COMPLETED);
}
#endif void time_sync_notification_cb(struct timeval *tv)
{
ESP_LOGI(TAG, "Notification of a time synchronization event");
} void app_main(void)
{
++boot_count;
ESP_LOGI(TAG, "Boot count: %d", boot_count); time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
// Is time set? If not, tm_year will be (1970 - 1900).
if (timeinfo.tm_year < (2016 - 1900)) {
ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
obtain_time();
// update 'now' variable with current time
time(&now);
}
#ifdef CONFIG_SNTP_TIME_SYNC_METHOD_SMOOTH
else {
// add 500 ms error to the current system time.
// Only to demonstrate a work of adjusting method!
{
ESP_LOGI(TAG, "Add a error for test adjtime");
struct timeval tv_now;
gettimeofday(&tv_now, NULL);
int64_t cpu_time = (int64_t)tv_now.tv_sec * 1000000L + (int64_t)tv_now.tv_usec;
int64_t error_time = cpu_time + 500 * 1000L;
struct timeval tv_error = { .tv_sec = error_time / 1000000L, .tv_usec = error_time % 1000000L };
settimeofday(&tv_error, NULL);
} ESP_LOGI(TAG, "Time was set, now just adjusting it. Use SMOOTH SYNC method.");
obtain_time();
// update 'now' variable with current time
time(&now);
}
#endif char strftime_buf[64]; // Set timezone to Eastern Standard Time and print local time
setenv("TZ", "EST5EDT,M3.2.0/2,M11.1.0", 1);
tzset();
localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI(TAG, "The current date/time in New York is: %s", strftime_buf); // Set timezone to China Standard Time
setenv("TZ", "CST-8", 1);
tzset();
localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI(TAG, "The current date/time in Shanghai is: %s", strftime_buf); if (sntp_get_sync_mode() == SNTP_SYNC_MODE_SMOOTH) {
struct timeval outdelta;
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_IN_PROGRESS) {
adjtime(NULL, &outdelta);
ESP_LOGI(TAG, "Waiting for adjusting time ... outdelta = %jd sec: %li ms: %li us",
(intmax_t)outdelta.tv_sec,
outdelta.tv_usec/1000,
outdelta.tv_usec%1000);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
} const int deep_sleep_sec = 10;
ESP_LOGI(TAG, "Entering deep sleep for %d seconds", deep_sleep_sec);
esp_deep_sleep(1000000LL * deep_sleep_sec);
} static void print_servers(void)
{
ESP_LOGI(TAG, "List of configured NTP servers:"); for (uint8_t i = 0; i < SNTP_MAX_SERVERS; ++i){
if (esp_sntp_getservername(i)){
ESP_LOGI(TAG, "server %d: %s", i, esp_sntp_getservername(i));
} else {
// we have either IPv4 or IPv6 address, let's print it
char buff[INET6_ADDRSTRLEN];
ip_addr_t const *ip = esp_sntp_getserver(i);
if (ipaddr_ntoa_r(ip, buff, INET6_ADDRSTRLEN) != NULL)
ESP_LOGI(TAG, "server %d: %s", i, buff);
}
}
} static void obtain_time(void)
{
ESP_ERROR_CHECK( nvs_flash_init() );
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK( esp_event_loop_create_default() ); #if LWIP_DHCP_GET_NTP_SRV
/**
* NTP server address could be acquired via DHCP,
* see following menuconfig options:
* 'LWIP_DHCP_GET_NTP_SRV' - enable STNP over DHCP
* 'LWIP_SNTP_DEBUG' - enable debugging messages
*
* NOTE: This call should be made BEFORE esp acquires IP address from DHCP,
* otherwise NTP option would be rejected by default.
*/
ESP_LOGI(TAG, "Initializing SNTP");
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG(CONFIG_SNTP_TIME_SERVER);
config.start = false; // start SNTP service explicitly (after connecting)
config.server_from_dhcp = true; // accept NTP offers from DHCP server, if any (need to enable *before* connecting)
config.renew_servers_after_new_IP = true; // let esp-netif update configured SNTP server(s) after receiving DHCP lease
config.index_of_first_server = 1; // updates from server num 1, leaving server 0 (from DHCP) intact
// configure the event on which we renew servers
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
config.ip_event_to_renew = IP_EVENT_STA_GOT_IP;
#else
config.ip_event_to_renew = IP_EVENT_ETH_GOT_IP;
#endif
config.sync_cb = time_sync_notification_cb; // only if we need the notification function
esp_netif_sntp_init(&config); #endif /* LWIP_DHCP_GET_NTP_SRV */ /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect()); #if LWIP_DHCP_GET_NTP_SRV
ESP_LOGI(TAG, "Starting SNTP");
esp_netif_sntp_start();
#if LWIP_IPV6 && SNTP_MAX_SERVERS > 2
/* This demonstrates using IPv6 address as an additional SNTP server
* (statically assigned IPv6 address is also possible)
*/
ip_addr_t ip6;
if (ipaddr_aton("2a01:3f7::1", &ip6)) { // ipv6 ntp source "ntp.netnod.se"
esp_sntp_setserver(2, &ip6);
}
#endif /* LWIP_IPV6 */ #else
ESP_LOGI(TAG, "Initializing and starting SNTP");
#if CONFIG_LWIP_SNTP_MAX_SERVERS > 1
/* This demonstrates configuring more than one server
*/
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG_MULTIPLE(2,
ESP_SNTP_SERVER_LIST(CONFIG_SNTP_TIME_SERVER, "pool.ntp.org" ) );
#else
/*
* This is the basic default config with one server and starting the service
*/
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG(CONFIG_SNTP_TIME_SERVER);
#endif
config.sync_cb = time_sync_notification_cb; // Note: This is only needed if we want
#ifdef CONFIG_SNTP_TIME_SYNC_METHOD_SMOOTH
config.smooth_sync = true;
#endif esp_netif_sntp_init(&config);
#endif print_servers(); // wait for time to be set
time_t now = 0;
struct tm timeinfo = { 0 };
int retry = 0;
const int retry_count = 15;
while (esp_netif_sntp_sync_wait(2000 / portTICK_PERIOD_MS) == ESP_ERR_TIMEOUT && ++retry < retry_count) {
ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
}
time(&now);
localtime_r(&now, &timeinfo); ESP_ERROR_CHECK( example_disconnect() );
esp_netif_sntp_deinit();
}

先连接用户设置的WIFI

随后更新系统时间后打印准确的实时时间

分区表

partitions.csv 文件,此文件是分区表文件,负责给 flash 分区。

分区表负责给 flash 划分为多个区域,存放不同类型的文件,包括 bootloader、应用程序等。

Partition

使用分区表需要先在menuconfig中选择使用用户自定义分区表,Custom partition table CSV

配置使用分区表partitions.csv

# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, , 0x1000,
factory, app, factory, , 7M,

ESP_LOGI() 日志作用

您提供的代码片段是使用ESP-IDF(Espressif IoT Development Framework)进行日志记录的一个例子。ESP_LOGI 是一个宏,用于在信息级别(INFO)记录日志。下面是代码的详细解释:

ESP_LOGI(TAG, "Reading file %s", path);
  • ESP_LOGI:这是一个宏,用于记录信息级别的日志。ESP-IDF 提供了多个日志级别,包括 ERROR、WARN、INFO、DEBUG 和 VERBOSE。
  • TAG:这是一个字符串,用于标识日志的来源。通常,它被定义为当前文件的名称或模块名称,以便在日志输出中区分不同的日志来源。
  • "Reading file %s":这是一个格式化字符串,其中 %s 是一个占位符,用于插入一个字符串。
  • path:这是一个变量,其值将被插入到格式化字符串中的 %s 位置。

当这段代码执行时,它会输出一条信息级别的日志,内容类似于:

I (xxxx) TAG: Reading file /some/file/path

其中 I 表示信息级别,(xxxx) 是时间戳,TAG 是日志的来源标识,/some/file/pathpath 变量的值。

这是一个非常有用的功能,可以在调试和运行时记录程序的行为和状态,帮助开发者了解程序的运行情况。

日志标识:main

打印日志

遇到的问题与解决方法

烧录失败:

原因:双开两个工程,一个工程正在监控设备,占用串口,导致烧录失败

解决办法:断开监控设备(ctrl +]),使下载串口空闲

内存爆炸

解决办法:

更改Flash配置大小

头文件声明函数

1、如果不是静态函数

直接加上esp_err_t前缀声明

2、如果是静态函数

删除static关键字,源文件.c和.h都删除static

添加头文件后识别不到函数

在main的CMakeList.txt中添加文件路径

链接错误

编译错误日志:

d:/esp_vscode/5.14/tool_all/tools/xtensa-esp32s3-elf/esp-12.2.0_20230208/xtensa-esp32s3-elf/bin/../lib/gcc/xtensa-esp32s3-elf/12.2.0/../../../../xtensa-esp32s3-elf/bin/ld.exe: esp-idf/main/libmain.a(BSP_IIS.c.obj):D:/Esp_ALL_Project/Sound_output/main/BSP_IIS.h:60: multiple definition of tx_handlex_BSP_IIS'; esp-idf/main/libmain.a(main.c.obj):D:/Esp_ALL_Project/Sound_output/main/BSP_IIS.h:60: first defined here d:/esp_vscode/5.14/tool_all/tools/xtensa-esp32s3-elf/esp-12.2.0_20230208/xtensa-esp32s3-elf/bin/../lib/gcc/xtensa-esp32s3-elf/12.2.0/../../../../xtensa-esp32s3-elf/bin/ld.exe: esp-idf/main/libmain.a(Sound_output.c.obj):D:/Esp_ALL_Project/Sound_output/main/BSP_IIS.h:60: multiple definition of tx_handlex_BSP_IIS'; esp-idf/main/libmain.a(main.c.obj):D:/Esp_ALL_Project/Sound_output/main/BSP_IIS.h:60: first defined here

溯源错误:

i2s_chan_handle_t* tx_handlex_BSP_IIS = *NULL*;

实验得知声明这个句柄使用全局变量,并且在头文件.h中,并被其他文件调用。

解决办法:

将声明放置该文件.c文件中,此时该全局变量只会在该文件中使用,解决了报错。但是不够完美。

猜测应该是将该变量声明成static静态变量,还未尝试。

LCD不显示图片

错误信息“Memory for bitmap is not enough”表明内存不足以存储所需的位图数据。

解决办法

编译之前,先设置目标芯片为 esp32s3,然后设置 menuconfig,把 flash 大小设置为 16MB,然后打开 PSRAM,因为程序中需要使用外部内存。

配置SPIRAM

我们看上图中第 ③ 步这里,有两个选项,默认是 Quad,我们改成 Octal。Quad 是 4 线,Octal 是 8 线,因为我们的模组所使用的 ESP32-S3 芯片内部是 8 线 SPI,所以这里要选 Octal。第 ④ 步设置速度,默认 40M,我们改成 80M。

编译提示编译器在处理string.h头文件时遇到了问题,具体来说,是关于一些函数的声明。这些函数包括strcoll_lstrerror_lstrxfrm_l,它们都涉及到locale_t类型,这是一个与区域设置(locale)相关的类型

原因:在main.c和其他.c文件中同时调用了FreeRTOS的头文件

解决办法:将任务创建,处理函数放在main.c中,驱动文件只写初始化配置

添加组件后需要重新选择芯片

使用其他文件句柄变量报错

解决办法:

例如要使用panel_handle

首先去掉声明该panel_handle源文件内的static

在定义变量的头文件声明为全局变量

然后需要使用该句柄的文件引用该头文件

无法识别工程文件路径

例如添加main.c文件上级文件夹BSP里文件路径,工程使用到pr.c文件里的函数

解决办法(main.c上级目录)

1、在CMakeLists添加文件夹相对路径

 "../BSP/pr.c"  	        //“..”表示上级

为函数文件相对main路径(文件)

 "../BSP"

添加头文件路径(文件夹)

2、在main文件中声明头文件相对路径

#include "../BSP/pr.h"  //表示在main.c文件的上层BSP文件夹中的pr.h文件

板级设置

设置数据缓存指令

设置 CPU 频率为 240MHz。

工具:

乐鑫组件管理器官网:https://components.espressif.com/

音频转化:

  1. 安装 'ffmpeg' 工具

    推荐教程:

    ffmpeg安装教程(windows版)_windows ffmpeg安装-CSDN博客

将mp3格式音乐转化为pcm

esp官方例程有教程文档Readme,地址:

D:\ESP_VScode\5.14\v5.1.4\esp-idf\examples\peripherals\i2s\i2s_codec\i2s_es8311

检查您的音乐格式

ffprobe mp3.mp3

剪切您的音乐,避免超内存

ffmpeg -i mp3.mp3 -ss 00:00:10 -t 00:00:15 mp3_cut.mp3

将音乐格式转换为 .pcm

注意教程文档有误,其中的s16ls应改为s16le

ffmpeg -i mp3_cut.mp3 -f s16le -ar 16000 -ac -1 -acodec pcm_s16le Music.pcm

ESP32-S3-WROOM-1-N16R8的更多相关文章

  1. ESP32与MicroPython入门-01 搭建开发环境

    ESP32简介 ESP32 是上海乐鑫公司开发的一款比较新的32位微控制器,它集成了WiFi及蓝牙等功能,有着性能稳定.功耗低.价格低廉等特点,非常适用于物联网开发,但也可以作为普通的MCU使用. E ...

  2. [MicroPython ESP32] 内存分析

    [MicroPython ESP32] 内存分析 [(1)芯片:ESP32-WROOM-DA] 手册: https://www.espressif.com.cn/zh-hans/support/doc ...

  3. 借助亚马逊S3和RapidMiner将机器学习应用到文本挖掘

    本挖掘典型地运用了机器学习技术,例如聚类,分类,关联规则,和预测建模.这些技术揭示潜在内容中的意义和关系.文本发掘应用于诸如竞争情报,生命科学,客户呼声,媒体和出版,法律和税收,法律实施,情感分析和趋 ...

  4. Ceph RGW服务 使用s3 java sdk 分片文件上传API 报‘SignatureDoesNotMatch’ 异常的定位及规避方案

    import java.io.File;   import com.amazonaws.AmazonClientException; import com.amazonaws.auth.profile ...

  5. 亚马逊S3下载上传文件

    引用网址: http://www.jxtobo.com/27697.html 下载 CloudBerry Explorer http://www.cloudberrylab.com/download- ...

  6. AWS CLI使用s3

    aws CLI是什么东西,暂且先不去了解,目前的需求是s3. 我在Jenkins上创建一个bucket,然后申请access_key,然后就可以使用s3来存储数据了.也就是说,s3就是一个网盘. 1. ...

  7. AWS S3 CLI的权限bug

    使用AWS CLI在S3上创建了一个bucket,上传文件的时候报以下错误: A client error (AccessDenied) occurred when calling the Creat ...

  8. ceph rgw s3 java sdk 上传大文件分批的方法

    Using the AWS Java SDK for Multipart Upload (High-Level API) Topics Upload a File Abort Multipart Up ...

  9. cosbench read异常解决办法。 Unable to verify integrity of data download. Client calculated content hash didn't match hash calculated by Amazon S3. The data may be corrupt.

    问题:cosbench read测试failed 报错如下 Cosbench v0.4.2.c4 against Ceph (Hammer) / radosgw / HAproxy's HTTP en ...

  10. Android开发-mac上使用三星S3做真机调试

    之前一直未使用真机进行Android开发,为准备明天的培训,拿出淘汰下来的s3准备环境,竟然发现无法连接mac,度娘一番找到答案,如下:mac 系统开发android,真机调试解决方案(无数的坑之后吐 ...

随机推荐

  1. CVE-2023-0461 漏洞分析与利用

    PS: 文章首发于补天社区 漏洞分析 tcp_set_ulp里面会分配和设置 icsk->icsk_ulp_data,其类型为 tls_context tcp_setsockopt do_tcp ...

  2. 鸿蒙UI开发快速入门 —— part10: PersistentStorage与Environment

    1.前言 我们在鸿蒙UI开发快速入门 -- part09: 应用级状态管理LocalStorage & AppStorage中已经学习了LocalStorage与AppStorage,但他们都 ...

  3. 浅聊web前端性能测试

    最近正好在做web前端的性能测试,这次就来聊聊关于这个的测试思路~ 首先从用户的思维去思考,关于web前端性能,用户最看重的是什么...... 其实就是下面三个点: 1. 加载性能(即页面加载时间+资 ...

  4. 【C#】【平时作业】习题-2-数据类型运算符表达式

    目录 1.请设计程序,验证算数运算符和赋值运算符. Int a,b,c C=a+b += privatevoid button1_Click(object sender, EventArgse) { ...

  5. 设置VirtualBox共享文件夹的方法

    1.创建共享文件夹进入linux终端,通过如下指令创建共享文件夹.在此处的/mnt/share是Linux下的共享文件夹. sudo mkdir /mnt/share2.在VirtualBox的设置中 ...

  6. The "https://packagist.phpcomposer.com/packages.json" file could not be down

    composer自身版本太低了,更新下 composer self-update 使用阿里云镜像 composer config -g repo.packagist composer https:// ...

  7. Qt音视频开发7-ffmpeg音频播放

    一.前言 之前用ffmpeg解码出来了音频,只是做了存储部分,比如存储成aac文件,播放的话早期用的是sdl来播放音频,自从Qt5以后提供了QAudioOutput来播放输入的音频数据,就更加方便了, ...

  8. Qt开源作品33-图片开关控件

    一.前言 进入智能手机时代以来,各种各样的APP大行其道,手机上面的APP有很多流行的元素,开关按钮个人非常喜欢,手机QQ.360卫士.金山毒霸等,都有很多开关控制一些操作,在WINFORM项目上,如 ...

  9. 生产环境Sentinel改造实践(二):规则管理推送改造

    前文介绍了Sentinel相关的核心概念,本文开始动手对规则管理推送进行改造. 这里挑选流控规则模式改造为示例 Sentinel Dashboard 改造 在com.alibaba.csp.senti ...

  10. vue3 路由-导航守卫

    假设用户登录,在地址栏输入了Login,人性化的设计应该自动回到home页面.或者用户输入不存在路由,也应该回到home页面. 这个时候需要用到vue-router的导航守卫功能. 在我们封装的rou ...