1 项目介绍
本项目使用ESP32-S3-BOX-LITE,基于其WIFI功能、屏幕显示功能、TTS语音功能,实现网络请求B站用户信息,并将信息显示与屏幕上,然后通过强大的TTS语音播报出来。
2 设计思路
要获取B站用户信息,首先设备需要具备网络连接功能,然后通过相应的API发起请求,然后解析请求结果,并在屏幕上进行GUI显示。最后将解析的粉丝数、视频数、点赞数等用户信息,一一通过ESP32的TTS功能进行播报输出。
3 硬件介绍
3.1 简介
ESP-BOX 是乐鑫发布的新一代 AIoT 开发平台,ESP32-S3-BOX-Lite 开发套件配备了一块2.4寸LCD显示屏、双麦克风、一个扬声器、两个用于硬件拓展的 Pmod™ 兼容接口和3个独立按键,可构建多样的HMI人机交互应用。开发板可实现离线语音唤醒和命令词识别,支持乐鑫自研的高性能声学前端算法构建语音交互系统。
3.2 结构图
4 开发环境搭建
使用ESP32进行开发,最基础的一步是要搭建好开发环境。本人比较喜欢使用C语言和VSCODE进行开发,所以本文开发环境主要介绍基于VSCODE的IDF开发环境搭建。
4.1 安装IDF插件
4.2 打开设置向导
4.3 安装
此处需要重点注意,服务器选择Espressif,不要选择github,否则会很慢,而且容易下载到一半失败。同时选择对应的IDF版本,此处我选的是V4.4.5版本,因为ESP32-S3-BOX-LITE的相关示例目前对V4.4版本具有较好的兼容性。版本选择时,注意选择Release版本,不要选择带branch的版本,否则还是会链接到github进行下载。
5 基础代码仓库
(1)IDF仓库
https://github.com/espressif/esp-idf.git
这个仓库实际上安装IDF环境的时候,代码就会被安装了,它包含了很多ESP32 MCU的SDK库及示例,这些库和示例是针对MCU的,不依赖于板载环境。
(2)box仓库
https://github.com/espressif/esp-box.git
这个仓库是ESP32-S3-BOX(LITE)专有仓库,它实现了在ESP32-S3-BOX(LITE)硬件环境下的板级支持包,如本项目将会使用的LCD屏幕、I2S音频播放、按键检测等。
6 软件开发
6.1 新建工程
本项目主要用到https request、LVGL显示、TTS功能,所以比较便捷的方法是,基于IDF的https request工程模板,在BOX仓库的example下建立工程,结构如下:
6.2 任务创建
xTaskCreate(&https_request_task, "https_get_task", 10240, NULL, 5, NULL);
xTaskCreate(&app_gui_task, "app_gui_task", 10240, NULL, 5, NULL);
xTaskCreate(&app_tts_task, "app_tts_task", 10240, NULL, 4, NULL);
本项目在main task中创建了三个任务,https_get_task用于从B站获取粉丝数并解析;app_gui_task用于GUI显示;app_tts_task任务用于语音播报。
static void btn_next_click_cb(void *arg)
{
g_tabview_index++;
if(g_tabview_index >= MAX_TABVIEW_TABS)
{
g_tabview_index = 0;
}
xSemaphoreGive(xSemaph_UpdateTabView);
}
static void btn_prev_click_cb(void *arg)
{
g_tabview_index--;
if(g_tabview_index >= MAX_TABVIEW_TABS)
{
g_tabview_index = (MAX_TABVIEW_TABS-1);
}
xSemaphoreGive(xSemaph_UpdateTabView);
}
static void btn_enter_click_cb(void *arg)
{
if(g_tabview_index < 2)
{
g_https_req_index = g_tabview_index;
xSemaphoreGive(xSemaph_HttpsRequest);
xSemaphoreGive(xSemaph_MessageBoxShow);
}
}
按键回调函数,主要处理页面切换,并按键触发https请求。
6.4 https请求任务
void https_request_task(void *pvparameters)
{
int idx;
while(1)
{
xSemaphoreTake( xSemaph_HttpsRequest, portMAX_DELAY );
http_request_buf = (char *) heap_caps_malloc(HTTP_REQUEST_BUF_LEN, MALLOC_CAP_SPIRAM);
if(http_request_buf != NULL)
{
https_get_request_using_cacert_buf(g_https_req_index);
ESP_LOGI(TAG, "%s\n", http_request_buf);
for (idx = 0; idx < HTTP_REQUEST_BUF_LEN; idx++)
{
if(http_request_buf[idx] == '{')
break;
}
cJSON *json = cJSON_Parse(&http_request_buf[idx]);
if(json != NULL)
{
//ESP_LOGI(TAG, "%s", cJSON_Print(json));
cJSON *data = cJSON_GetObjectItem(json, "data");
cJSON *card = cJSON_GetObjectItem(data, "card");
if(card != NULL)
{
char *name = cJSON_GetStringValue(cJSON_GetObjectItem(card, "name"));
strcpy(g_stc_bili_info.name, name);
g_stc_bili_info.fans = cJSON_GetNumberValue(cJSON_GetObjectItem(card, "fans"));
g_stc_bili_info.friends = cJSON_GetNumberValue(cJSON_GetObjectItem(card, "friend"));
g_stc_bili_info.attention = cJSON_GetNumberValue(cJSON_GetObjectItem(card, "attention"));
g_stc_bili_info.vides = cJSON_GetNumberValue(cJSON_GetObjectItem(data, "archive_count"));
g_stc_bili_info.likes = cJSON_GetNumberValue(cJSON_GetObjectItem(data, "like_num"));
xSemaphoreGive(xSemaph_HttpsRequestDone);
xSemaphoreGive(xSemaph_TTSRequest);
}
else
{
ESP_LOGI(TAG, "Json data failed");
}
}
else
{
ESP_LOGI(TAG, "Json failed");
}
/* Don't forget to free allocated memory */
free(http_request_buf);
}
else
{
ESP_LOGI(TAG, "malloc http request buf failed");
}
}
ESP_LOGI(TAG, "End https_request example");
vTaskDelete(NULL);
}
该任务在收到按键释放的信号量之后,便进行https请求,通过API向B站请求用户信息,由于返回的是Json格式数据,因此还需要对Json数据进行解析,然后将解析的目标数据存放于g_stc_bili_info结构体中,并释放信号量给TTS任务和GUI任务进行播报和显示。
6.5 GUI显示任务
void app_gui_task(void *pvparameters)
{
lv_gui_creat();
do {
lv_task_handler();
//update handler
if( xSemaphoreTake( xSemaph_UpdateTabView, 0 ) == pdTRUE )
{
ESP_LOGI(TAG, "btn down");
lv_tabview_set_act(g_tabview, g_tabview_index, LV_ANIM_ON);
}
if( xSemaphoreTake( xSemaph_HttpsRequestDone, 0 ) == pdTRUE )
{
update_tabview_user();
update_tabview_videos();
lv_msgbox_close(g_msgbox);
}
if( xSemaphoreTake( xSemaph_MessageBoxShow, 0 ) == pdTRUE )
{
g_msgbox = lv_msgbox_create(lv_scr_act(), NULL, "Waiting for https request...", NULL, false);
static lv_style_t msgbox_style;
lv_style_init(&msgbox_style);
lv_style_set_bg_color(&msgbox_style, lv_color_hex(0x999999));
lv_obj_add_style(g_msgbox, &msgbox_style, LV_PART_MAIN);
lv_obj_center(g_msgbox);
}
} while (vTaskDelay(1), true);
}
6.6 TTS任务
void app_tts_task(void *pvparameters)
{
/*** 1. create esp tts handle ***/
// initial voice set from separate voice data partition
.........
while(1)
{
xSemaphoreTake( xSemaph_TTSRequest, portMAX_DELAY );
char *tts_i2s_buffer = (char *) heap_caps_malloc(1024*100, MALLOC_CAP_SPIRAM);
if (NULL != tts_i2s_buffer)
{
/*** 2. play prompt text ***/
char prompt[256];
sprintf(prompt, "粉丝数%d,关注数%d,视频数%d,点赞数%d", g_stc_bili_info.fans,g_stc_bili_info.attention,g_stc_bili_info.vides,g_stc_bili_info.likes);
//char *prompt1="我是谁从哪来到哪去,你在干什么";
printf("%s\n", prompt);
uint32_t index = 0;
if (esp_tts_parse_chinese(tts_handle, prompt)) {
int len[1]={0};
do {
short *pcm_data=esp_tts_stream_play(tts_handle, len, 0);
memcpy(&tts_i2s_buffer[index], pcm_data, len[0]*2);
index += len[0]*2;
//esp_audio_play(pcm_data, len[0]*2, portMAX_DELAY);
//printf("data:%d \n", len[0]);
} while(len[0]>0);
}
esp_tts_stream_reset(tts_handle);
esp_audio_play(tts_i2s_buffer, index, portMAX_DELAY);
/* Don't forget to free allocated memory */
free(tts_i2s_buffer);
}
else
{
printf("malloc buf for tts failed! \n");
}
}
}
该任务是接到TTS信号量后,便开始执行。先将文字与数据进行拼接,然后调用TTS解析转换成音频数据。此处,我分配一个大内存,先将转换的音频数据存储到数组中,全部转换完成之后,再调用I2S接口进行播放。这样做的好处是,播放声音会很流畅,但会占用比较大的缓存空间,还好ESP-BOX-LITE板载了8M SPIRAM,足够使用。
7 效果展示
8 活动小结
通过本次活动,熟悉了ESP32开发环境的搭建,以及基于ESP-IDF开发应用的流程,同时也被ESP32丰富的外设资源、强大的运算能力、超高的集成度所震撼,使用ESP32系列芯片及配套的SDK可以很快速的开发网络、GUI、语音识别等应用。