试玩ESP32S3 BOX Lite

作者是参加比赛申请的一块ESP32S3 BOX Lite的开发板。

图片如下:

别的不说,用过乐鑫的idf的同学都知道,这开发环境真的一言难尽,我是用vscode+espidf4.4.4+python3.8.7。插一嘴这里最好不要下py3.11容易安装报错,然后去换源。

开发思路

因为我当时做的是跟农业相关的,所以这块开发板当时是做的交互模块。(其实也没做啥)

我用这块开发板,通过http协议访问心知天气的url,然后我们不是看见这块开发板有三个按钮吗,于是我设置了一个按键检测,当我按下某个键的时候,播放当地的实时天气情况。然后做了一个UI页面,用的SquareLine,制作了一个简易的静态UI画面。

大概就做成了这个样子,然后按中间的按键就能实时播放当地的天气情况。

 配置声音

先去github上,找到esp-skainet框架,然后找到chinese-tts,也就就是找到它的语音框架,我们在这个基础上进行开发。

我们这块BOX lite采用的是es8311这块语音芯片。

 esp_tts_voice_t *voice = (esp_tts_voice_t *)&esp_tts_voice_xiaole; // 配置tts的声音配置文件
        esp_tts_handle_t *tts_handle = esp_tts_create(voice);              // 创建tts对象
        esp_tts_handle_t *tts = esp_tts_create(voice);              // 创建tts对象
        es8156_codec_set_voice_volume(100);

我们可以这主函数里面看见,我们首先就是要配置tts的声音配置文件,然后创建一个tts对象,然后函数调节我们想要的音量。

if (esp_tts_parse_chinese(tts_handle, buff))
        {
         int len[1] = {0};
         do
        {
            short *pcm_data = esp_tts_stream_play(tts_handle, len, 0);
            esp_audio_play(pcm_data, len[0] * 2, portMAX_DELAY);
            // printf("data:%d \n", len[0]);
         } while (len[0] > 0);
         vTaskDelay(10000 / portTICK_PERIOD_MS);
        }
           esp_tts_stream_reset(tts_handle);
        }

然后这块开发板是每次一个字节的输出,所以可能听起来有点机械感。

当然更多的配置文件,全是编译的时候,我们的espidf给我们下载一大堆。

可见这里面的配置文件都是一些底层,驱动寄存器。

按键监听

我是采用ADC采样获取按键的数据的。

//ADC按键
int key=4;  // 0 右键    2 中间键   3 左键  4 无按键
static esp_adc_cal_characteristics_t adc1_chars;
#define ADC1_EXAMPLE_CHAN0 ADC1_CHANNEL_0
static bool adc_calibration_init(void)
{
    esp_err_t ret;
    bool cali_enable =false;
    ret = esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP_FIT);
    if(ret == ESP_ERR_NOT_SUPPORTED)
        ESP_LOGW(TAG,"Calibration scheme not supported,skip software calibration");
        else if(ret == ESP_ERR_INVALID_VERSION)
        {
            ESP_LOGW(TAG,"eFuse");
        }
        else if(ret == ESP_OK)
        {
            cali_enable =true;
            esp_adc_cal_characterize(ADC_UNIT_1,ADC_ATTEN_DB_11,ADC_WIDTH_BIT_DEFAULT,0,&adc1_chars);
        }
        else{
           ESP_LOGW(TAG,"Invalid arg");
        }
    return cali_enable;
}

因为ESP32S3自带FreeRTOS操作系统(如果不知道什么是FreeRTOS的读者,可以查看我前几篇博客),所以我这里直接创建一个监听任务。

int listen_key_task()
{
    adc_calibration_init();
    adc1_config_width(ADC_WIDTH_BIT_DEFAULT);
    adc1_config_channel_atten(ADC1_EXAMPLE_CHAN0,ADC_ATTEN_DB_11);
        u16_t adcval=adc1_get_raw(ADC1_EXAMPLE_CHAN0) / 1000;

        if(adcval !=4 &&adcval !=key )
        {
            ESP_LOGI(TAG,"Adc value key:%d",adcval);
            key = adcval;
        }
      vTaskDelay(400 / portTICK_PERIOD_MS);  
    
    return key;
}

 我设定了一个KEY的初始值为4,当按下按钮时,数据就会变化。0 右键    2 中间键   3 左键  4 无按键。

http获取心知天气的url

/* Constants that aren't configurable in menuconfig */
// #define WEB_SERVER "example.com"
// #define WEB_PORT "80"
// #define WEB_PATH "/"
//http组包宏,获取天气的http接口参数
#define WEB_SERVER          "api.thinkpage.cn"              
#define WEB_PORT            "80"
#define WEB_URL             "/v3/weather/now.json?key="
#define host 		        "api.thinkpage.cn"
#define APIKEY		        "S1owwyz1J-WgwE_k3"      
#define city		        "changsha"
#define language	        "zh-Hans"

我们首先定义http组的宏,我们提前去心知天气,获取APIKEY,我这个是14天的,现在已经过期了,然后我们修改city的地区,然后language可以修改语音。

//天气解析结构体
typedef struct 
{
    char cit[20];
    char weather_text[20];
    char weather_code[2];
    char temperatur[3];
    char feels_like[3];                        //体感温度
    char pressure[4];                          //气压,单位为mb百帕或in英寸
    char humidity[3];                          //相对湿度,0~100,单位为百分比
    char visibility[5];                        //能见度,单位为km公里或mi英里
    char wind_direction[20];                   //风向文字
    char wind_direction_degree[4];             //风向角度,范围0~360,0为正北,90为正东,180为正南,270为正西
    char wind_speed[6];                        //风速,单位为km/h公里每小时或mph英里每小时
    char wind_scale[3];                        //风力等级,请参考:http://baike.baidu.com/view/465076.htm
}weather_info;

weather_info weathe;

static const char *REQUEST = "GET "WEB_URL""APIKEY"&location="city"&language="language" HTTP/1.1\r\n"
    "Host: "WEB_SERVER"\r\n"
    "Connection: close\r\n"
    "\r\n";

然后我们定义一个结构体,因为我们从心知天气上获取的是cjson的数据包,所以我们要进行cjson的数据解析。


/*解析json数据 只处理 解析 城市 天气 天气代码  温度  其他的自行扩展
* @param[in]   text  		       :json字符串
* @retval      void                 :无
* @note        修改日志 
*               Ver0.0.1:
                    hx-zsj, 2018/08/10, 初始化版本\n 
*/
void cjson_to_struct_info(char *text)
{
    cJSON *root,*psub;
    cJSON *arrayItem;
    //截取有效json
    char *index=strchr(text,'{');
    strcpy(text,index);

    root = cJSON_Parse(text);
    
    if(root!=NULL)
    {
        psub = cJSON_GetObjectItem(root, "results");
        arrayItem = cJSON_GetArrayItem(psub,0);

        cJSON *locat = cJSON_GetObjectItem(arrayItem, "location");
        cJSON *now = cJSON_GetObjectItem(arrayItem, "now");
        if((locat!=NULL)&&(now!=NULL))
        {
            psub=cJSON_GetObjectItem(locat,"name");
            sprintf(weathe.cit,"%s",psub->valuestring);
            ESP_LOGI(TAG,"[city:%s]",weathe.cit);
             
            psub=cJSON_GetObjectItem(now,"text");
            sprintf(weathe.weather_text,"%s",psub->valuestring);
            ESP_LOGI(TAG,"[weather:%s]",weathe.weather_text);
            
            psub=cJSON_GetObjectItem(now,"code");
            sprintf(weathe.weather_code,"%s",psub->valuestring);
            // ESP_LOGI(TAG,"%s",weathe.weather_code);

            psub=cJSON_GetObjectItem(now,"temperature");
            sprintf(weathe.temperatur,"%s",psub->valuestring);
            ESP_LOGI(TAG,"[temperatur:%s]",weathe.temperatur);

             psub=cJSON_GetObjectItem(now,"feels_like");
            sprintf(weathe.feels_like,"%s",psub->valuestring);
            ESP_LOGI(TAG,"[feels_like:%s]",weathe.feels_like);
 
            psub=cJSON_GetObjectItem(now,"pressure");
            sprintf(weathe.pressure,"%s",psub->valuestring);
            ESP_LOGI(TAG,"[pressure:%s]",weathe.pressure);
            
            psub=cJSON_GetObjectItem(now,"humidity");
            sprintf(weathe.humidity,"%s",psub->valuestring);
            ESP_LOGI(TAG,"[humidity:%s]",weathe.humidity);
            
             psub=cJSON_GetObjectItem(now,"visibility");
            sprintf(weathe.visibility,"%s",psub->valuestring);
            ESP_LOGI(TAG,"[visibility:%s]",weathe.visibility);

             psub=cJSON_GetObjectItem(now,"wind_direction");
            sprintf(weathe.wind_direction,"%s",psub->valuestring);
            ESP_LOGI(TAG,"[wind_direction:%s]",weathe.wind_direction);

             psub=cJSON_GetObjectItem(now,"wind_direction_degree");
            sprintf(weathe.wind_direction_degree,"%s",psub->valuestring);
            ESP_LOGI(TAG,"[wind_direction_degree:%s]",weathe.wind_direction_degree);

            psub=cJSON_GetObjectItem(now,"wind_speed");
            sprintf(weathe.wind_speed,"%s",psub->valuestring);
            ESP_LOGI(TAG,"[wind_speed:%s]",weathe.wind_speed);

             psub=cJSON_GetObjectItem(now,"wind_scale");
            sprintf(weathe.wind_scale,"%s",psub->valuestring);
            ESP_LOGI(TAG,"[wind_scale:%s]",weathe.wind_scale);

            sprintf(buff,"%s天气%s温度%s体感温度%s气压%s相对湿度%s能见度%s风向%s风向角度%s风速%s风力等级%s",weathe.cit,weathe.weather_text,weathe.temperatur,weathe.feels_like,
            weathe.pressure,weathe.humidity,weathe.visibility,weathe.wind_direction,weathe.wind_direction_degree,weathe.wind_speed,weathe.wind_scale);
            
            // lv_label_set_text(ui_Celsius,weathe.weather_text);

        }
    }
    cJSON_Delete(root);
}

一开始我是解析json数据 只处理 解析 城市 天气 天气代码  温度,后面的可以自己添加,根据个人需求。

static void http_get_task(void *pvParameters)
{
    const struct addrinfo hints = {
        .ai_family = AF_INET,
        .ai_socktype = SOCK_STREAM,
    };
    struct addrinfo *res;
    struct in_addr *addr;
    int s, r;
    char recv_buf[1024];
    char mid_buf[1024];

    while(1) {
        //DNS域名解析
        int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);
        if(err != 0 || res == NULL) {
            ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p", err, res);
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            continue;
        }

        //打印获取的IP
        addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
        ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*addr));

        //新建socket
        s = socket(res->ai_family, res->ai_socktype, 0);
        if(s < 0) {
            ESP_LOGE(TAG, "... Failed to allocate socket.");
            freeaddrinfo(res);
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            continue;
        }
        ESP_LOGI(TAG, "... allocated socket");

        //连接ip
        if(connect(s, res->ai_addr, res->ai_addrlen) != 0) {
            ESP_LOGE(TAG, "... socket connect failed errno=%d", errno);
            close(s);
            freeaddrinfo(res);
            vTaskDelay(4000 / portTICK_PERIOD_MS);
            continue;
        }
        ESP_LOGI(TAG, "... connected");
        freeaddrinfo(res);

        //发送http包
        if (write(s, REQUEST, strlen(REQUEST)) < 0) {
            ESP_LOGE(TAG, "... socket send failed");
            close(s);
            vTaskDelay(4000 / portTICK_PERIOD_MS);
            continue;
        }
        ESP_LOGI(TAG, "http write:%s",REQUEST);
        ESP_LOGI(TAG, "... socket send success");
        //设置http超时
        struct timeval receiving_timeout;
        receiving_timeout.tv_sec = 5;
        receiving_timeout.tv_usec = 0;
        if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout,
                sizeof(receiving_timeout)) < 0) {
            ESP_LOGE(TAG, "... failed to set socket receiving timeout");
            close(s);
            vTaskDelay(4000 / portTICK_PERIOD_MS);
            continue;
        }
        ESP_LOGI(TAG, "... set socket receiving timeout success");

        //清缓存
        memset(mid_buf,0,sizeof(mid_buf));

        //获取http应答包
        do {
            //清缓存
            bzero(recv_buf, sizeof(recv_buf));
            //读取http应答包
            r = read(s, recv_buf, sizeof(recv_buf)-1);
            strcat(mid_buf,recv_buf);
            // for(int i = 0; i < r; i++) {
            //     putchar(recv_buf[i]);
            // }
        } while(r > 0);
        
        ESP_LOGI(TAG, "http read:%s", mid_buf);

        //json解析
        cjson_to_struct_info(mid_buf);
        vTaskDelay(10000 / portTICK_PERIOD_MS);
        //关闭socket,http是短连接
        ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d.", r, errno);
        close(s);
        vTaskDelay(10000 / portTICK_PERIOD_MS);
    }
}

这里同样的,创建一个进程,上面的注释写的很清楚,我们首先进行DNS域名解析,然后打印获取IP地址,新建socket,连接ip,发送http包,设置异常超时,获取我们得到的http应答包,然后进行json解析,关闭socket连接。

UI页面

我是在SquareLIne上生成的UI页面,然后移植lvgl包进我的文件夹里,这个软件只有三十天的试用期,但是网上有很多破解方法。

大概就是做的这样一个UI页面,时间不够就没有过多的追求精美的UI页面。

UI的话,生成的文件基本都是组件的位置大小啥的,具体的画面得去SquareLine里面自己调整布局。

然后我们在main.c函数里面引用对应的函数即可。

void app_lvgl_display(void)
{
    bsp_display_lock(0);

    ui_init();

    bsp_display_unlock();
}

注意:

因为SquareLine生成的不是配套的ESP32S3 BOX lite代码,里面是ESP32S3BOX的配套UI,所以我们要在UI里面去掉触摸功能。

 主函数

 


int app_main()
{
    /* Initialize display and LVGL */
    bsp_display_start();

    /* Set default display brightness */
    bsp_display_brightness_set(APP_DISP_DEFAULT_BRIGHTNESS);

    /* Add and show objects on display */
    app_lvgl_display();

    ESP_LOGI(TAG, "Example initialization done.");
    ESP_ERROR_CHECK( nvs_flash_init() );
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* 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());
    xTaskCreate(&http_get_task, "http_get_task", 8196, NULL, 5, NULL);
    /*** 2. play prompt text ***/
        ESP_ERROR_CHECK(esp_board_init(AUDIO_HAL_16K_SAMPLES, 1, 16));
        esp_tts_voice_t *voice = (esp_tts_voice_t *)&esp_tts_voice_xiaole; // 配置tts的声音配置文件
        esp_tts_handle_t *tts_handle = esp_tts_create(voice);              // 创建tts对象
        esp_tts_handle_t *tts = esp_tts_create(voice);              // 创建tts对象
        es8156_codec_set_voice_volume(100);
        bool tts_flag; 
        bool flag=0;
     while(1)
     {  
        listen_key_task(key);
        switch(key)
        {
        case 2:
        {
        key =4;
        tts_flag=true;
          break;
        
        }
         case 0:
        {
            key=4;
            flag=1;
            break;
        }
        
         default:
            break;
        }

        if(tts_flag) 
        {   
            tts_flag=false;
        if (esp_tts_parse_chinese(tts_handle, buff))
        {
         int len[1] = {0};
         do
        {
            short *pcm_data = esp_tts_stream_play(tts_handle, len, 0);
            esp_audio_play(pcm_data, len[0] * 2, portMAX_DELAY);
            // printf("data:%d \n", len[0]);
         } while (len[0] > 0);
         vTaskDelay(10000 / portTICK_PERIOD_MS);
        }
           esp_tts_stream_reset(tts_handle);
        }

        if(flag)
        {
            flag=0;
            char *prompt1 = "我是ljt2333,大家关注我吧!";
            if (esp_tts_parse_chinese(tts, prompt1))
         {
        int len[1] = {0};
        do
        {
            short *pcm_data = esp_tts_stream_play(tts, len, 2);

            esp_audio_play(pcm_data, len[0] * 2, portMAX_DELAY);
            // printf("data:%d \n", len[0]);
        } while (len[0] > 0);
          vTaskDelay(1000 / portTICK_PERIOD_MS);
          }
              esp_tts_stream_reset(tts);
        }
       

     }
    return 0;
}

媒体播放器 2023-09-22 17-37-06

视频放在最后是成品,可以看一下。

 总体来说,这是一块通过http协议获取心知天气的实时天气的数据包,进行cjson解析,然后通过乐鑫的esp-skainet语音框架播报语音,创建两个任务,一个adc的按键采样,当我们按下不同的键时,会播放不同的文字。

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值