Funpack第二季第五期任务一基于ESP32-S3-BOX-LITE的天气播报系统
一 、 ESP32-S3-BOX-LITE硬件介绍
ESP32-S3-BOX-Lite 是目前对应的 AIoT 应用开发板,搭载支持 AI 加速的 ESP32-S3 Wi-Fi + Bluetooth 5 (LE) SoC。为用户提供了一个基于语音助手、传感器、红外控制器和智能 Wi-Fi 网关等功能开发和控制智能家居设备的平台。开发板出厂支持离线语音交互功能,用户通过乐鑫丰富的 SDK 和解决方案,能够轻松构建在线和离线语音助手、智能语音设备、HMI 人机交互设备、控制面板、多协议网关等多样的应用。开发板如图1所示。
支持特性:
- 双麦克风支持远场语音交互
- 高唤醒率的离线语音唤醒
- 高识别率的离线中英文命令词识别
- 可动态配置 200+ 中英文命令词
- 连续识别和唤醒打断
- 灵活可复用的 GUI 框架
- 端到端一站式接入云平台
- Pmod™ 兼容接口支持多种外设扩展
图1. ESP32-S3-BOX-Lite
核心微控制器:ESP32-S3
ESP32-S3 是一款集成 2.4 GHz Wi-Fi 和 Bluetooth 5 (LE) 的 MCU 芯片,支持远距离模式 (Long Range) 。ESP32-S3 搭载 Xtensa® 32 位 LX7 双核处理器,主频高达 240 MHz,内置 512 KB SRAM (TCM),具有 45 个可编程 GPIO 管脚和丰富的通信接口。ESP32-S3 支持更大容量的高速 Octal SPI flash 和片外 RAM,支持用户配置数据缓存与指令缓存。
- Xtensa® 32 位 LX7 双核处理器,主频高达 240 MHz
- 内置 512 KB SRAM、384 KB ROM 存储空间,并支持多个外部 SPI、Dual SPI、 Quad SPI、Octal SPI、QPI、OPI flash 和片外 RAM
- 额外增加用于加速神经网络计算和信号处理等工作的向量指令 (vector instructions)
- 45 个可编程 GPIO,支持常用外设接口如 SPI、I2S、I2C、PWM、RMT、ADC、UART、SD/MMC 主机控制器和 TWAITM 控制器等
- 基于 AES-XTS 算法的 Flash 加密和基于 RSA 算法的安全启动,数字签名和 HMAC 模块, “世界控制器 (World Controller)”模块
该开发板配备一块 2.4 寸 LCD 显示屏、双麦克风、一个扬声器、两个用于硬件拓展的 Pmod™ 兼容接口、结合三个独立按键,可构建多样的 HMI 人机交互应用。硬件总览如图2所示。
图2. 硬件总览图
二.开发环境搭建
这里使用的开发环境是ESP-IDF4.4的开发环境,环境的搭建参考了官方教程,教程链接为https://docs.espressif.com/projects/esp-idf/en/release-v4.4/esp32s3/get-started/index.html#installation-step-by-step
上手和开发指南参考了funpack活动主页提供的教程。
三.任务说明
任务一:
- 使用ESP32的WiFi和TTS功能,实现一个语音播报系统,如联网获取粉丝数并播报或者获取天气并播报。
这里完成的主要是任务一,项目框图如图3所示。硬件部分使用了esp32-s3-box-lite的按键模块,扬声器模块,以及按键模块;软件部分通过ESP32-IDF的开发框架进行开发,使用到了ADC, TTS, WIFI模块,程序的主要流程是通过wifi进行联网获取天气信息,然后使用TTS模块把天线信息转化成语音,通过ADC获取到的按键状态进行相应的播报。
图3. 项目框图
四.软件编写
程序部分使用了esp32-s3-box-lite的WiFi模块、tts模块、以及ADC模块,这些模块的初始化参考了官方例程,程序流程图如图4所示。主程序首先是通过nvs_flash_init函数进行flash的初始化(用于语音播放的tts的语音模型),然后使用bsp_board_init函数进行硬件初化(i2c和按键模块) ,之后使用wifi_init_sta函数初始化wifi模块进行联网,通过bsp_codec_set_voice_volume函数设置扬声器的音量为百分之百,最后通过xTaskCreate函数建立了两个任务,任务1是http_test_task函数(获取天气信息),任务2是btn_test_task函数(监控按键状态进行相应语音播报)。
图4. 程序流程图
获取天气情况的任务函数http_test_task程序如下
static void http_test_task(void *pvParameters)
{
// 02-1 定义需要的变量
int content_length = 0; // http协议头的长度
// 02-2 配置http结构体
// 定义http配置结构体,并且进行清零
esp_http_client_config_t config;
memset(&config, 0, sizeof(config));
// 向配置结构体内部写入url
static const char *URL = "http://api.seniverse.com/v3/weather/now.json?key=Sd1BdVJsfUOpncfuh&location=hangzhou&language=zh-Hans&unit=c";
// static const char *URL = " http://api.seniverse.com/v3/weather/daily.json?key=Sd1BdVJsfUOpncfuh&location=hangzhou&language=zh-Hans&unit=c&start=0&days=5";
config.url = URL;
// 初始化结构体
esp_http_client_handle_t client = esp_http_client_init(&config); // 初始化http连接
// 设置发送请求
esp_http_client_set_method(client, HTTP_METHOD_GET);
// 02-3 循环通讯
while (1)
{
// 与目标主机创建连接,并且声明写入内容长度为0
esp_err_t err = esp_http_client_open(client, 0);
// 如果连接失败
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
// return false;
}
// 如果连接成功
else
{
// 读取目标主机的返回内容的协议头
content_length = esp_http_client_fetch_headers(client);
// 如果协议头长度小于0,说明没有成功读取到
if (content_length < 0)
{
ESP_LOGE(TAG, "HTTP client fetch headers failed");
// return false;
}
// 如果成功读取到了协议头
else
{
// 读取目标主机通过http的响应内容
int data_read = esp_http_client_read_response(client, output_buffer, MAX_HTTP_OUTPUT_BUFFER);
if (data_read >= 0)
{
// 打印响应内容,包括响应状态,响应体长度及其内容
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
esp_http_client_get_status_code(client), // 获取响应状态信息
esp_http_client_get_content_length(client)); // 获取响应信息长度
printf("data:%s\n", output_buffer);
}
// 如果不成功
else
{
ESP_LOGE(TAG, "Failed to read response");
// return false;
}
}
}
// 关闭连接
esp_http_client_close(client);
// return true;
//;
// 延时,因为心知天气免费版本每分钟只能获取20次数据
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
}
监控按键状态进行语音播报的任务函数btn_test_task程序如下
static void btn_test_task(void *pvParameters)
{
const esp_partition_t *part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "voice_data");
if (part == NULL)
{
printf("Couldn't find voice data partition!\n");
return 0;
}
else
{
printf("voice_data paration size:%d\n", part->size);
}
void *voicedata;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
esp_partition_mmap_handle_t mmap;
esp_err_t err = esp_partition_mmap(part, 0, part->size, ESP_PARTITION_MMAP_DATA, &voicedata, &mmap);
#else
spi_flash_mmap_handle_t mmap;
esp_err_t err = esp_partition_mmap(part, 0, part->size, SPI_FLASH_MMAP_DATA, &voicedata, &mmap);
#endif
if (err != ESP_OK)
{
printf("Couldn't map voice data partition!\n");
return 0;
}
esp_tts_voice_t *voice = esp_tts_voice_set_init(&esp_tts_voice_template, (int16_t *)voicedata);
esp_tts_handle_t *tts_handle = esp_tts_create(voice);
while (1)
{
cJSON *root = NULL;
root = cJSON_Parse(output_buffer);
cJSON *cjson_item = cJSON_GetObjectItem(root, "results");
cJSON *cjson_results = cJSON_GetArrayItem(cjson_item, 0);
cJSON *cjson_now = cJSON_GetObjectItem(cjson_results, "now");
cJSON *cjson_temperature = cJSON_GetObjectItem(cjson_now, "temperature");
cJSON *cjson_weather = cJSON_GetObjectItem(cjson_now, "text");
if (bsp_btn_get_state(BOARD_BTN_ID_PREV))
{
/*** 2. play prompt text ***/
char *prompt1 = "今天杭州的气温是";
char *prompt2 = cjson_temperature->valuestring;
char *prompt3 = "度";
printf("%s\n", prompt1);
if (esp_tts_parse_chinese(tts_handle, prompt1))
{
int len[1] = {0};
do
{
// 速度设置为0
short *pcm_data = esp_tts_stream_play(tts_handle, len, 0);
#ifdef SDCARD_OUTPUT_ENABLE
wav_encoder_run(wav_encoder, pcm_data, len[0] * 2);
#else
iot_dac_audio_play(pcm_data, len[0] * 2, portMAX_DELAY);
#endif
} while (len[0] > 0);
i2s_zero_dma_buffer(0);
}
esp_tts_stream_reset(tts_handle);
#ifdef SDCARD_OUTPUT_ENABLE
wav_encoder_close(wav_encoder);
#endif
if (esp_tts_parse_chinese(tts_handle, prompt2))
{
int len[1] = {0};
do
{
// 速度设置为0
short *pcm_data = esp_tts_stream_play(tts_handle, len, 0);
#ifdef SDCARD_OUTPUT_ENABLE
wav_encoder_run(wav_encoder, pcm_data, len[0] * 2);
#else
iot_dac_audio_play(pcm_data, len[0] * 2, portMAX_DELAY);
#endif
// printf("data:%d \n", len[0]);
} while (len[0] > 0);
i2s_zero_dma_buffer(0);
}
esp_tts_stream_reset(tts_handle);
#ifdef SDCARD_OUTPUT_ENABLE
wav_encoder_close(wav_encoder);
#endif
if (esp_tts_parse_chinese(tts_handle, prompt3))
{
int len[1] = {0};
do
{
// 速度设置为0
short *pcm_data = esp_tts_stream_play(tts_handle, len, 0);
#ifdef SDCARD_OUTPUT_ENABLE
wav_encoder_run(wav_encoder, pcm_data, len[0] * 2);
#else
iot_dac_audio_play(pcm_data, len[0] * 2, portMAX_DELAY);
#endif
// printf("data:%d \n", len[0]);
} while (len[0] > 0);
i2s_zero_dma_buffer(0);
}
esp_tts_stream_reset(tts_handle);
#ifdef SDCARD_OUTPUT_ENABLE
wav_encoder_close(wav_encoder);
#endif
}
else if (bsp_btn_get_state(BOARD_BTN_ID_ENTER))
{
/*** 2. play prompt text ***/
char *prompt1 = "杭州今天的天气";
char *prompt2 = cjson_weather->valuestring;
printf("%s\n", prompt1);
if (esp_tts_parse_chinese(tts_handle, prompt1))
{
int len[1] = {0};
do
{
// 速度设置为0
short *pcm_data = esp_tts_stream_play(tts_handle, len, 0);
#ifdef SDCARD_OUTPUT_ENABLE
wav_encoder_run(wav_encoder, pcm_data, len[0] * 2);
#else
iot_dac_audio_play(pcm_data, len[0] * 2, portMAX_DELAY);
#endif
} while (len[0] > 0);
i2s_zero_dma_buffer(0);
}
esp_tts_stream_reset(tts_handle);
#ifdef SDCARD_OUTPUT_ENABLE
wav_encoder_close(wav_encoder);
#endif
/*** 2. play prompt text ***/
// strcat(prompt1,cjson_weather->valuestring);
printf("%s\n", prompt2);
if (esp_tts_parse_chinese(tts_handle, prompt2))
{
int len[1] = {0};
do
{
// 速度设置为0
short *pcm_data = esp_tts_stream_play(tts_handle, len, 0);
#ifdef SDCARD_OUTPUT_ENABLE
wav_encoder_run(wav_encoder, pcm_data, len[0] * 2);
#else
iot_dac_audio_play(pcm_data, len[0] * 2, portMAX_DELAY);
#endif
} while (len[0] > 0);
i2s_zero_dma_buffer(0);
}
esp_tts_stream_reset(tts_handle);
#ifdef SDCARD_OUTPUT_ENABLE
wav_encoder_close(wav_encoder);
#endif
}
else if (bsp_btn_get_state(BOARD_BTN_ID_NEXT))
{
/*** 2. play prompt text ***/
char *prompt1 = "杭州今天天气";
printf("%s\n", prompt1);
if (esp_tts_parse_chinese(tts_handle, prompt1))
{
int len[1] = {0};
do
{
// 速度设置为0
short *pcm_data = esp_tts_stream_play(tts_handle, len, 0);
#ifdef SDCARD_OUTPUT_ENABLE
wav_encoder_run(wav_encoder, pcm_data, len[0] * 2);
#else
iot_dac_audio_play(pcm_data, len[0] * 2, portMAX_DELAY);
#endif
// printf("data:%d \n", len[0]);
} while (len[0] > 0);
i2s_zero_dma_buffer(0);
}
esp_tts_stream_reset(tts_handle);
#ifdef SDCARD_OUTPUT_ENABLE
wav_encoder_close(wav_encoder);
#endif
}
vTaskDelay(1);
}
}
五.结果与总结
5.1结果
板子连接上电脑后,打开vscode软件,把程序下载到esp32-s3-box-lite中,按下不同的按键可以听到esp32-s3-box-lite播报的不同的天气信息,同时电脑串口助手上可以实时的看到天气信息和语音播报的内容,串口打印的信息如图5所示,详细演示在视频里。
图5. 串口打印信息
5.2总结
很高兴参加硬禾学堂举办的Funpack第二季第五期活动,学习了esp32-s3-box-lite的在ESP-IDF平台下的软件开发流程,以及WIFI模块和TTS模块的使用,更加熟悉了ESP32的使用。