Funpack第二季第五期任务一基于ESP32-S3-BOX-LITE的天气播报系统
Funpack第二季第五期任务一使用ESP32-S3-BOX-LITE开发板,使用wifi模块和tts模块实现了天气语音播报系统
标签
嵌入式系统
Funpack活动
TTS
天气播报系统
wjhgbpqm
更新2023-08-02
杭州电子科技大学
353

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™ 兼容接口支持多种外设扩展

FgdhsAfun50gUQTglUzChf3smcUu

 

图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所示。Fhhv9kTn-728czj4XHrl4vGt9_tt

图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获取到的按键状态进行相应的播报。

 

 

 

FjGTlruTkyBWJnTiZF5gnN-Lj1NC

图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函数(监控按键状态进行相应语音播报)。

Fgb9z4fi4cy8DtdFd60ZpGELf_Nh

图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所示,详细演示在视频里。

FlQiIsIXzEQBP3L3Q5ba7LXIBCw_

图5. 串口打印信息

5.2总结

很高兴参加硬禾学堂举办的Funpack第二季第五期活动,学习了esp32-s3-box-lite的在ESP-IDF平台下的软件开发流程,以及WIFI模块和TTS模块的使用,更加熟悉了ESP32的使用。

 

 

 

 

 

 

 

 

 

附件下载
station.zip
这是主程序编译需要components的库,由于库文件太大上传不上来可以去esp32的GitHub仓库下载esp32-box-lite v0.3.0版本
团队介绍
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号