1.项目介绍
基于ESP32-S3-BOX-Lite天气语音播报系统,通过对接天气API获取原始天气数据和实时时间数据,随后解析出有用信息并且通过lvgl刷新屏幕显示,扬声器语音播报。
2.设计思路
本项目基于ESP-IDF框架进行开发,ESP-IDF 是乐鑫官方的物联网开发框架,适用于 ESP32、ESP32-S、ESP32-C 和 ESP32-H 系列 SoC。它基于 C/C++ 语言提供了一个自给自足的 SDK,方便用户在这些平台上开发通用应用程序,并集成了大量的软件组件,包括 RTOS、外设驱动程序、网络栈、多种协议实现技术以及常见应用程序的使用助手。
本项目使用VSCode编辑器进行代码编写,首先下载IDF的插件,下载V4.5版本的IDF,创建初始工程,添加官方给出的组件。配置CMake文件添加组件的路径,随后就可以进行代码的编写了。
3.硬件介绍
本项目使用ESP32-S3-BOX-Lite开发板,上图是该开发板的硬件资源介绍。ESP32-S3-BOX-Lite 开发套件配备了一块 2.4 寸 LCD 显示屏、双麦克风、一个扬声器、两个用于硬件拓展的 Pmod™ 兼容接口和3个独立按键,可构建多样的 HMI 人机交互应用。开发板可实现离线语音唤醒和命令词识别,支持乐鑫自研的高性能声学前端算法构建语音交互系统。开发者可利用开源的 SDK轻松构建在线离线语音助手、智能语音设备、HMI 人机交互设备、多协议网关等多样的应用。
4.软件流程图
整体的软件开发流程如上图所示,首先通过IDF框架新建工程、配置CMake文件,随后添加组件路径。然后就可进行代码的编写,首先初始化板子上需要用的的硬件,随后便开启四个FreeRTOS任务包括获取天气任务、获取时间任务、屏幕刷新任务、语音播报任务。
5.功能主要代码片段及说明
首先进行板子硬件初始化:
ESP_ERROR_CHECK(bsp_board_init());
ESP_ERROR_CHECK(bsp_board_power_ctrl(POWER_MODULE_AUDIO, true));
ESP_ERROR_CHECK(bsp_spiffs_init_default());
ESP_ERROR_CHECK(lv_port_init());
bsp_lcd_set_backlight(true);
配置wifi:
//! WIFI账号密码修改为可以联网的wifi(不支持5G频段)
#define WIFI_SSID "xxx"
#define WIFI_PASSWORD "xxxxxxxx"
// 初始化wifi要先初始化nvs flash
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_init_sta();
开启四个任务和LVGL屏幕刷新:
xTaskCreate(weather_task, "weather_task", 3072, NULL, WEATHER_TASK_PRIORITY, &weather_task_handle);
xTaskCreate(sntp_task, "sntp_task", 3072, NULL, NTP_TASK_PRIORITY, NULL);
xTaskCreate(data_task, "data_task", 3072, NULL, 7, NULL);
xTaskCreate(voice_task, "voice_task", 3072, NULL, 7, NULL);
do
{
lv_task_handler();
} while (vTaskDelay(1), true);
日期和天气刷新任务代码:
void data_task(void *pvParameters)
{
LV_FONT_DECLARE(lv_font_Chinese_HT);
lv_obj_t *day_label = lv_label_create(lv_scr_act());
lv_label_set_recolor(day_label, true);
lv_label_set_text(day_label, "日期:");
lv_obj_set_style_text_font(day_label, &lv_font_Chinese_HT, 0);
lv_obj_set_width(day_label, 24 * 3);
lv_obj_set_pos(day_label, 0, 0);
lv_obj_t *rt_day = lv_label_create(lv_scr_act());
lv_label_set_recolor(rt_day, true);
lv_obj_set_style_text_font(rt_day, &lv_font_Chinese_HT, 0);
lv_obj_set_width(rt_day, 320);
lv_obj_set_pos(rt_day, 24 * 2 + 8, 0);
lv_obj_t *time_label = lv_label_create(lv_scr_act());
lv_label_set_recolor(time_label, true);
lv_label_set_text(time_label, "时间:");
lv_obj_set_style_text_font(time_label, &lv_font_Chinese_HT, 0);
lv_obj_set_width(time_label, 24 * 3);
lv_obj_set_pos(time_label, 0, 24 * 1);
lv_obj_t *rt_time = lv_label_create(lv_scr_act());
lv_label_set_recolor(rt_time, true);
lv_obj_set_style_text_font(rt_time, &lv_font_Chinese_HT, 0);
lv_obj_set_width(rt_time, 320);
lv_obj_set_pos(rt_time, 24 * 2 + 8, 24 * 1);
lv_obj_t *city_label = lv_label_create(lv_scr_act());
lv_label_set_recolor(city_label, true);
lv_label_set_text(city_label, "城市:郑州");
lv_obj_set_style_text_font(city_label, &lv_font_Chinese_HT, 0);
lv_obj_set_width(city_label, 24 * 5);
lv_obj_set_pos(city_label, 0, 24 * 2);
lv_obj_t *weather_label = lv_label_create(lv_scr_act());
lv_label_set_recolor(weather_label, true);
lv_label_set_text(weather_label, "天气:");
lv_obj_set_style_text_font(weather_label, &lv_font_Chinese_HT, 0);
lv_obj_set_width(weather_label, 24 * 3);
lv_obj_set_pos(weather_label, 0, 24 * 3);
lv_obj_t *rt_weather = lv_label_create(lv_scr_act());
lv_label_set_recolor(rt_weather, true);
lv_obj_set_style_text_font(rt_weather, &lv_font_Chinese_HT, 0);
lv_obj_set_width(rt_weather, 320);
lv_obj_set_pos(rt_weather, 24 * 2 + 8, 24 * 3);
lv_obj_t *temph_label = lv_label_create(lv_scr_act());
lv_label_set_recolor(temph_label, true);
lv_label_set_text(temph_label, "最高气温(℃):");
lv_obj_set_style_text_font(temph_label, &lv_font_Chinese_HT, 0);
lv_obj_set_width(temph_label, 24 * 7);
lv_obj_set_pos(temph_label, 0, 24 * 4);
lv_obj_t *rt_temp_high = lv_label_create(lv_scr_act());
lv_label_set_recolor(rt_temp_high, true);
lv_obj_set_style_text_font(rt_temp_high, &lv_font_Chinese_HT, 0);
lv_obj_set_width(rt_temp_high, 320);
lv_obj_set_pos(rt_temp_high, 24 * 6, 24 * 4);
lv_obj_t *templ_label = lv_label_create(lv_scr_act());
lv_label_set_recolor(templ_label, true);
lv_label_set_text(templ_label, "最低气温(℃):");
lv_obj_set_style_text_font(templ_label, &lv_font_Chinese_HT, 0);
lv_obj_set_width(templ_label, 24 * 7);
lv_obj_set_pos(templ_label, 0, 24 * 5);
lv_obj_t *rt_temp_low = lv_label_create(lv_scr_act());
lv_label_set_recolor(rt_temp_low, true);
lv_obj_set_style_text_font(rt_temp_low, &lv_font_Chinese_HT, 0);
lv_obj_set_width(rt_temp_low, 320);
lv_obj_set_pos(rt_temp_low, 24 * 6, 24 * 5);
lv_obj_t *img = lv_img_create(lv_scr_act());
lv_obj_set_pos(img, 220, 120);
while (true)
{
rttime = *get_time();
lv_label_set_text_fmt(rt_day, "%4d-%02d-%02d---week:%d", rttime.tm_year + 1900, rttime.tm_mon + 1, rttime.tm_mday, rttime.tm_wday);
lv_label_set_text_fmt(rt_time, "%02d:%02d:%02d", rttime.tm_hour, rttime.tm_min, rttime.tm_sec);
lv_label_set_text_fmt(rt_weather, "%s", weather.text_day[0]);
lv_label_set_text_fmt(rt_temp_high, "%d", weather.degree_high[0]);
lv_label_set_text_fmt(rt_temp_low, "%d", weather.degree_low[0]);
sprintf(img_path, "S:/spiffs/%d@1x.png", weather.code_day[0]);
lv_img_set_src(img, img_path);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
语音播报代码:
void voice_task(void *pvParameters)
{
vTaskDelay(3000 / portTICK_PERIOD_MS);
while (1)
{
ESP_ERROR_CHECK(bsp_board_power_ctrl(POWER_MODULE_AUDIO, true));
size_t bytes_write;
esp_tts_voice_t *voice = (esp_tts_voice_t *)&esp_tts_voice_xiaole; // 配置tts的声音配置文件,来自libvoice_set_xiaole
esp_tts_handle_t *tts_handle = esp_tts_create(voice); // 创建tts对象
char prompt1[50]; // 需要转换的文字
sprintf(prompt1, "今天的天气为%s最高气温为%d最低气温为%d", weather.text_day[0], weather.degree_high[0], weather.degree_low[0]);
if (esp_tts_parse_chinese(tts_handle, prompt1)) // 文字解析成拼音
{
int len[1] = {0};
do
{
short *pcm_data = esp_tts_stream_play(tts_handle, len, 0); // 拼音转换成pcm音频
i2s_write(I2S_NUM_0, (const char *)pcm_data, len[0] * 2, &bytes_write, portMAX_DELAY); // 播放音频
} while (len[0] > 0);
}
esp_tts_stream_reset(tts_handle); // 重置 tts 流并清除 TTS 实例的所有缓存
ESP_ERROR_CHECK(bsp_board_power_ctrl(POWER_MODULE_AUDIO, false));
vTaskDelay(10000 / portTICK_PERIOD_MS);
}
}
6.功能展示
以上为最终屏幕效果显示图,因为需要播放音频才能看出效果,详细请看视频展示。
7.心得体会
很荣幸参加电子森林电子设计活动,我学到了很多关于电子设计的知识。我学会了如何使用各种工具和软件来设计电路板,以及如何选择正确的元件和材料。我还学会了如何解决各种问题和挑战,以及如何自己来完成一个项目。这次活动让我更加热爱电子设计,并且让我更加自信地面对未来的挑战。