• 试玩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这块语音芯片。

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

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

    1. if (esp_tts_parse_chinese(tts_handle, buff))
    2. {
    3. int len[1] = {0};
    4. do
    5. {
    6. short *pcm_data = esp_tts_stream_play(tts_handle, len, 0);
    7. esp_audio_play(pcm_data, len[0] * 2, portMAX_DELAY);
    8. // printf("data:%d \n", len[0]);
    9. } while (len[0] > 0);
    10. vTaskDelay(10000 / portTICK_PERIOD_MS);
    11. }
    12. esp_tts_stream_reset(tts_handle);
    13. }

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

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

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

    按键监听

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

    1. //ADC按键
    2. int key=4; // 0 右键 2 中间键 3 左键 4 无按键
    3. static esp_adc_cal_characteristics_t adc1_chars;
    4. #define ADC1_EXAMPLE_CHAN0 ADC1_CHANNEL_0
    5. static bool adc_calibration_init(void)
    6. {
    7. esp_err_t ret;
    8. bool cali_enable =false;
    9. ret = esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP_FIT);
    10. if(ret == ESP_ERR_NOT_SUPPORTED)
    11. ESP_LOGW(TAG,"Calibration scheme not supported,skip software calibration");
    12. else if(ret == ESP_ERR_INVALID_VERSION)
    13. {
    14. ESP_LOGW(TAG,"eFuse");
    15. }
    16. else if(ret == ESP_OK)
    17. {
    18. cali_enable =true;
    19. esp_adc_cal_characterize(ADC_UNIT_1,ADC_ATTEN_DB_11,ADC_WIDTH_BIT_DEFAULT,0,&adc1_chars);
    20. }
    21. else{
    22. ESP_LOGW(TAG,"Invalid arg");
    23. }
    24. return cali_enable;
    25. }

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

    1. int listen_key_task()
    2. {
    3. adc_calibration_init();
    4. adc1_config_width(ADC_WIDTH_BIT_DEFAULT);
    5. adc1_config_channel_atten(ADC1_EXAMPLE_CHAN0,ADC_ATTEN_DB_11);
    6. u16_t adcval=adc1_get_raw(ADC1_EXAMPLE_CHAN0) / 1000;
    7. if(adcval !=4 &&adcval !=key )
    8. {
    9. ESP_LOGI(TAG,"Adc value key:%d",adcval);
    10. key = adcval;
    11. }
    12. vTaskDelay(400 / portTICK_PERIOD_MS);
    13. return key;
    14. }

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

    http获取心知天气的url

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

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

    1. //天气解析结构体
    2. typedef struct
    3. {
    4. char cit[20];
    5. char weather_text[20];
    6. char weather_code[2];
    7. char temperatur[3];
    8. char feels_like[3]; //体感温度
    9. char pressure[4]; //气压,单位为mb百帕或in英寸
    10. char humidity[3]; //相对湿度,0~100,单位为百分比
    11. char visibility[5]; //能见度,单位为km公里或mi英里
    12. char wind_direction[20]; //风向文字
    13. char wind_direction_degree[4]; //风向角度,范围0~3600为正北,90为正东,180为正南,270为正西
    14. char wind_speed[6]; //风速,单位为km/h公里每小时或mph英里每小时
    15. char wind_scale[3]; //风力等级,请参考:http://baike.baidu.com/view/465076.htm
    16. }weather_info;
    17. weather_info weathe;
    18. static const char *REQUEST = "GET "WEB_URL""APIKEY"&location="city"&language="language" HTTP/1.1\r\n"
    19. "Host: "WEB_SERVER"\r\n"
    20. "Connection: close\r\n"
    21. "\r\n";

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

    1. /*解析json数据 只处理 解析 城市 天气 天气代码 温度 其他的自行扩展
    2. * @param[in] text :json字符串
    3. * @retval void :无
    4. * @note 修改日志
    5. * Ver0.0.1:
    6. hx-zsj, 2018/08/10, 初始化版本\n
    7. */
    8. void cjson_to_struct_info(char *text)
    9. {
    10. cJSON *root,*psub;
    11. cJSON *arrayItem;
    12. //截取有效json
    13. char *index=strchr(text,'{');
    14. strcpy(text,index);
    15. root = cJSON_Parse(text);
    16. if(root!=NULL)
    17. {
    18. psub = cJSON_GetObjectItem(root, "results");
    19. arrayItem = cJSON_GetArrayItem(psub,0);
    20. cJSON *locat = cJSON_GetObjectItem(arrayItem, "location");
    21. cJSON *now = cJSON_GetObjectItem(arrayItem, "now");
    22. if((locat!=NULL)&&(now!=NULL))
    23. {
    24. psub=cJSON_GetObjectItem(locat,"name");
    25. sprintf(weathe.cit,"%s",psub->valuestring);
    26. ESP_LOGI(TAG,"[city:%s]",weathe.cit);
    27. psub=cJSON_GetObjectItem(now,"text");
    28. sprintf(weathe.weather_text,"%s",psub->valuestring);
    29. ESP_LOGI(TAG,"[weather:%s]",weathe.weather_text);
    30. psub=cJSON_GetObjectItem(now,"code");
    31. sprintf(weathe.weather_code,"%s",psub->valuestring);
    32. // ESP_LOGI(TAG,"%s",weathe.weather_code);
    33. psub=cJSON_GetObjectItem(now,"temperature");
    34. sprintf(weathe.temperatur,"%s",psub->valuestring);
    35. ESP_LOGI(TAG,"[temperatur:%s]",weathe.temperatur);
    36. psub=cJSON_GetObjectItem(now,"feels_like");
    37. sprintf(weathe.feels_like,"%s",psub->valuestring);
    38. ESP_LOGI(TAG,"[feels_like:%s]",weathe.feels_like);
    39. psub=cJSON_GetObjectItem(now,"pressure");
    40. sprintf(weathe.pressure,"%s",psub->valuestring);
    41. ESP_LOGI(TAG,"[pressure:%s]",weathe.pressure);
    42. psub=cJSON_GetObjectItem(now,"humidity");
    43. sprintf(weathe.humidity,"%s",psub->valuestring);
    44. ESP_LOGI(TAG,"[humidity:%s]",weathe.humidity);
    45. psub=cJSON_GetObjectItem(now,"visibility");
    46. sprintf(weathe.visibility,"%s",psub->valuestring);
    47. ESP_LOGI(TAG,"[visibility:%s]",weathe.visibility);
    48. psub=cJSON_GetObjectItem(now,"wind_direction");
    49. sprintf(weathe.wind_direction,"%s",psub->valuestring);
    50. ESP_LOGI(TAG,"[wind_direction:%s]",weathe.wind_direction);
    51. psub=cJSON_GetObjectItem(now,"wind_direction_degree");
    52. sprintf(weathe.wind_direction_degree,"%s",psub->valuestring);
    53. ESP_LOGI(TAG,"[wind_direction_degree:%s]",weathe.wind_direction_degree);
    54. psub=cJSON_GetObjectItem(now,"wind_speed");
    55. sprintf(weathe.wind_speed,"%s",psub->valuestring);
    56. ESP_LOGI(TAG,"[wind_speed:%s]",weathe.wind_speed);
    57. psub=cJSON_GetObjectItem(now,"wind_scale");
    58. sprintf(weathe.wind_scale,"%s",psub->valuestring);
    59. ESP_LOGI(TAG,"[wind_scale:%s]",weathe.wind_scale);
    60. sprintf(buff,"%s天气%s温度%s体感温度%s气压%s相对湿度%s能见度%s风向%s风向角度%s风速%s风力等级%s",weathe.cit,weathe.weather_text,weathe.temperatur,weathe.feels_like,
    61. weathe.pressure,weathe.humidity,weathe.visibility,weathe.wind_direction,weathe.wind_direction_degree,weathe.wind_speed,weathe.wind_scale);
    62. // lv_label_set_text(ui_Celsius,weathe.weather_text);
    63. }
    64. }
    65. cJSON_Delete(root);
    66. }

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

    1. static void http_get_task(void *pvParameters)
    2. {
    3. const struct addrinfo hints = {
    4. .ai_family = AF_INET,
    5. .ai_socktype = SOCK_STREAM,
    6. };
    7. struct addrinfo *res;
    8. struct in_addr *addr;
    9. int s, r;
    10. char recv_buf[1024];
    11. char mid_buf[1024];
    12. while(1) {
    13. //DNS域名解析
    14. int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);
    15. if(err != 0 || res == NULL) {
    16. ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p", err, res);
    17. vTaskDelay(1000 / portTICK_PERIOD_MS);
    18. continue;
    19. }
    20. //打印获取的IP
    21. addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
    22. ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*addr));
    23. //新建socket
    24. s = socket(res->ai_family, res->ai_socktype, 0);
    25. if(s < 0) {
    26. ESP_LOGE(TAG, "... Failed to allocate socket.");
    27. freeaddrinfo(res);
    28. vTaskDelay(1000 / portTICK_PERIOD_MS);
    29. continue;
    30. }
    31. ESP_LOGI(TAG, "... allocated socket");
    32. //连接ip
    33. if(connect(s, res->ai_addr, res->ai_addrlen) != 0) {
    34. ESP_LOGE(TAG, "... socket connect failed errno=%d", errno);
    35. close(s);
    36. freeaddrinfo(res);
    37. vTaskDelay(4000 / portTICK_PERIOD_MS);
    38. continue;
    39. }
    40. ESP_LOGI(TAG, "... connected");
    41. freeaddrinfo(res);
    42. //发送http包
    43. if (write(s, REQUEST, strlen(REQUEST)) < 0) {
    44. ESP_LOGE(TAG, "... socket send failed");
    45. close(s);
    46. vTaskDelay(4000 / portTICK_PERIOD_MS);
    47. continue;
    48. }
    49. ESP_LOGI(TAG, "http write:%s",REQUEST);
    50. ESP_LOGI(TAG, "... socket send success");
    51. //设置http超时
    52. struct timeval receiving_timeout;
    53. receiving_timeout.tv_sec = 5;
    54. receiving_timeout.tv_usec = 0;
    55. if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout,
    56. sizeof(receiving_timeout)) < 0) {
    57. ESP_LOGE(TAG, "... failed to set socket receiving timeout");
    58. close(s);
    59. vTaskDelay(4000 / portTICK_PERIOD_MS);
    60. continue;
    61. }
    62. ESP_LOGI(TAG, "... set socket receiving timeout success");
    63. //清缓存
    64. memset(mid_buf,0,sizeof(mid_buf));
    65. //获取http应答包
    66. do {
    67. //清缓存
    68. bzero(recv_buf, sizeof(recv_buf));
    69. //读取http应答包
    70. r = read(s, recv_buf, sizeof(recv_buf)-1);
    71. strcat(mid_buf,recv_buf);
    72. // for(int i = 0; i < r; i++) {
    73. // putchar(recv_buf[i]);
    74. // }
    75. } while(r > 0);
    76. ESP_LOGI(TAG, "http read:%s", mid_buf);
    77. //json解析
    78. cjson_to_struct_info(mid_buf);
    79. vTaskDelay(10000 / portTICK_PERIOD_MS);
    80. //关闭socket,http是短连接
    81. ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d.", r, errno);
    82. close(s);
    83. vTaskDelay(10000 / portTICK_PERIOD_MS);
    84. }
    85. }

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

    UI页面

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

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

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

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

    1. void app_lvgl_display(void)
    2. {
    3. bsp_display_lock(0);
    4. ui_init();
    5. bsp_display_unlock();
    6. }

    注意:

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

     主函数

     

    1. int app_main()
    2. {
    3. /* Initialize display and LVGL */
    4. bsp_display_start();
    5. /* Set default display brightness */
    6. bsp_display_brightness_set(APP_DISP_DEFAULT_BRIGHTNESS);
    7. /* Add and show objects on display */
    8. app_lvgl_display();
    9. ESP_LOGI(TAG, "Example initialization done.");
    10. ESP_ERROR_CHECK( nvs_flash_init() );
    11. ESP_ERROR_CHECK(esp_netif_init());
    12. ESP_ERROR_CHECK(esp_event_loop_create_default());
    13. /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
    14. * Read "Establishing Wi-Fi or Ethernet Connection" section in
    15. * examples/protocols/README.md for more information about this function.
    16. */
    17. ESP_ERROR_CHECK(example_connect());
    18. xTaskCreate(&http_get_task, "http_get_task", 8196, NULL, 5, NULL);
    19. /*** 2. play prompt text ***/
    20. ESP_ERROR_CHECK(esp_board_init(AUDIO_HAL_16K_SAMPLES, 1, 16));
    21. esp_tts_voice_t *voice = (esp_tts_voice_t *)&esp_tts_voice_xiaole; // 配置tts的声音配置文件
    22. esp_tts_handle_t *tts_handle = esp_tts_create(voice); // 创建tts对象
    23. esp_tts_handle_t *tts = esp_tts_create(voice); // 创建tts对象
    24. es8156_codec_set_voice_volume(100);
    25. bool tts_flag;
    26. bool flag=0;
    27. while(1)
    28. {
    29. listen_key_task(key);
    30. switch(key)
    31. {
    32. case 2:
    33. {
    34. key =4;
    35. tts_flag=true;
    36. break;
    37. }
    38. case 0:
    39. {
    40. key=4;
    41. flag=1;
    42. break;
    43. }
    44. default:
    45. break;
    46. }
    47. if(tts_flag)
    48. {
    49. tts_flag=false;
    50. if (esp_tts_parse_chinese(tts_handle, buff))
    51. {
    52. int len[1] = {0};
    53. do
    54. {
    55. short *pcm_data = esp_tts_stream_play(tts_handle, len, 0);
    56. esp_audio_play(pcm_data, len[0] * 2, portMAX_DELAY);
    57. // printf("data:%d \n", len[0]);
    58. } while (len[0] > 0);
    59. vTaskDelay(10000 / portTICK_PERIOD_MS);
    60. }
    61. esp_tts_stream_reset(tts_handle);
    62. }
    63. if(flag)
    64. {
    65. flag=0;
    66. char *prompt1 = "我是ljt2333,大家关注我吧!";
    67. if (esp_tts_parse_chinese(tts, prompt1))
    68. {
    69. int len[1] = {0};
    70. do
    71. {
    72. short *pcm_data = esp_tts_stream_play(tts, len, 2);
    73. esp_audio_play(pcm_data, len[0] * 2, portMAX_DELAY);
    74. // printf("data:%d \n", len[0]);
    75. } while (len[0] > 0);
    76. vTaskDelay(1000 / portTICK_PERIOD_MS);
    77. }
    78. esp_tts_stream_reset(tts);
    79. }
    80. }
    81. return 0;
    82. }

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

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

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

  • 相关阅读:
    优化group By查询很慢的问题建议使用DISTINCT字段做分组的查询优化
    注意:Spring Boot 2.7开始spring.factories不推荐使用了,接下来这么玩...
    基于el-tooltip组件封装超出显示省略号,鼠标hover显示tooltip的组件
    (二)k8-集群创建
    【无标题】
    经典递归回溯问题之——解数独(LeetCode 37)
    [carla入门教程]-3 在carla中遥控汽车并采集传感器数据(一个简单Demo,附代码)
    知识储备--基础算法篇-回溯法
    【JVM】垃圾回收
    TAMRA-NHS 荧光素-活性酯
  • 原文地址:https://blog.csdn.net/ljt2333/article/details/133150452