平台介绍
本次使用的物联网处理平台,是基于乐鑫官方模块ESP32-S2-Mini-1制作的。该模块内置 ESP32-S2FH4芯片,Xtensa单核 32 位 LX7 微处理器,支持高达240 MHz 的时钟频率以及一些外围电路组成。官方提供了esp-idf和Arduino等开发框架,本次我在这使用VScode与esp-idf作为开发框架开发。
项目介绍
本次项目基于ESP32-S2模块的物联网/音频信号处理平台制作一个本地气象台/温度计,用到的主要外设包括OLED屏、WiFi模块等外围电路。
设计思路
通过学习ESP-IDF中的example中的GPIO,FreeRTOS系统、WIFI配网以及HTTP Client的例程,并充分利用,在主任务中建立三个任务分别为:OLED屏幕刷新任务、网络时间获取任务、HTTP请求任务,通过周期性的校时与天气情况的获取,在时间任务中进行硬件定时,然后每半个小时进行一次校准。
实现功能
1、固定wifi配网
下图为上电的第一个界面,由下图可以看出我设置的WIFI为固定的,需要创建一个SSID为:CMCC-ji4n的,密码为123456789.cc的WiFi热点。
2、获取WiFi和天气
在HTTP GET任务中获取网络时间与天气情况,半个小时进行一次天气获取以及网络校时,目前获取的地区是苏州的,天气情况是Overcast以及温度为5摄氏度。
主要代码片段
1、该代码通过参考例程esp-idf\examples\protocols\http_request,进行修改处理。主任务进行各个任务的初始化。
void app_main(void)
{
weather_event_group = xEventGroupCreate();
if(weather_event_group != NULL)
{
printf("group create success!\r\n");
}
ESP_ERROR_CHECK(nvs_flash_init() );
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
xTaskCreate(&ssd1306_oled_task, "ssd1306_oled_task", 2048, NULL, 1, NULL);
ESP_ERROR_CHECK(example_connect());
xTaskCreate(&http_get_task, "http_get_task", 2*4096, NULL, 5, NULL);
xTaskCreate(&online_time_task, "online_time_task", 1024, NULL, 6, NULL);
}
2、OLED刷新任务
该任务进行上电的界面显示以及配网成功之后的网络时间和天气气象情况的刷新,刷新率位20hz。
static void ssd1306_oled_task(void *pvParameters)
{
char oled_str[50]={0};
ssd1306_oled_init();
while (1)
{
if(realtime.init_flag == ONLINE_TIME_INIT_SUCCESS)
{
if(realtime.init_flag == ONLINE_TIME_INIT_SUCCESS)
{
sprintf(oled_str,"data:%4d-%02d-%02d ",realtime.year,realtime.month,realtime.day);
ssd1306_oled_P6x8Str(0,0,oled_str);
sprintf(oled_str,"time:%02d:%02d:%02d ",realtime.hour,realtime.minutes,realtime.second);
ssd1306_oled_P6x8Str(0,1,oled_str);
sprintf(oled_str,"city:%s ",city);
ssd1306_oled_P6x8Str(0,2,oled_str);
sprintf(oled_str,"weather:%s ",weather_name);
ssd1306_oled_P6x8Str(0,3,oled_str);
sprintf(oled_str,"temperature:%s ",temperature);
ssd1306_oled_P6x8Str(0,4,oled_str);
}
}else if(realtime.init_flag == ONLINE_TIME_INITING)
{
sprintf(oled_str,"connecting... ");
ssd1306_oled_P6x8Str(0,0,oled_str);
sprintf(oled_str,"ssid:CMCC-ji4n ");
ssd1306_oled_P6x8Str(0,1,oled_str);
sprintf(oled_str,"mima:123456789.cc ");
ssd1306_oled_P6x8Str(0,2,oled_str);
}
else
{
sprintf(oled_str,"online time error");
ssd1306_oled_P6x8Str(0,2,oled_str);
}
vTaskDelay(pdMS_TO_TICKS(50));
}
}
3、获取网络时间以及天气情况任务。
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[64];
while(1) {
//获取网络时间 start -------------------------------------------------------------------------
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;
}
/* Code to print the resolved IP.
Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code */
addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*addr));
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");
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);
if (write(s, REQUEST, strlen(REQUEST)) < 0) {
ESP_LOGE(TAG, "... socket send failed");
close(s);
vTaskDelay(4000 / portTICK_PERIOD_MS);
continue;
}
ESP_LOGI(TAG, "... socket send success");
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");
char str_json[512] = {0};
unsigned int rec_count = 0;
/* Read HTTP response */
do {
bzero(recv_buf, sizeof(recv_buf));
r = read(s, recv_buf, sizeof(recv_buf)-1);
for(int i = 0; i < r; i++) {
rec_count++;
if(rec_count>=168)
{
str_json[rec_count - 168] = recv_buf[i];
}
putchar(recv_buf[i]);
}
} while(r > 0);
printf("\r\n----------------------------------------------\r\n");
printf("\r\n%s\r\n",str_json);
printf("\r\n----------------------------------------------\r\n");
cJSON *jsonroot = cJSON_Parse(str_json);
if(jsonroot != NULL)
{
printf("jsonroot为json数据\r\n");
cJSON *json_result = cJSON_GetObjectItem(jsonroot,"result");
if(json_result != NULL)
{
printf("json_result为json数据\r\n");
cJSON *json_datetime_1 = cJSON_GetObjectItem(json_result,"datetime_1");
if(json_datetime_1 != NULL)
{
printf("datetime_1为json数据\r\n");
printf("datatime_1 数据为:%s",cJSON_Print(json_datetime_1));
printf("\r\n数据为:%s\r\n",json_datetime_1->valuestring);
str2time(json_datetime_1->valuestring,&realtime);
realtime.init_flag = ONLINE_TIME_INIT_SUCCESS;
}
else
{
printf("datetime_1非json数据\r\n");
realtime.init_flag = ONLINE_TIME_INIT_FAIL;
}
}
else
{
printf("json_result非json数据\r\n");
realtime.init_flag = ONLINE_TIME_INIT_FAIL;
}
}
else
{
printf("jsonroot非json数据\r\n");
realtime.init_flag = ONLINE_TIME_INIT_FAIL;
}
ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d.", r, errno);
cJSON_Delete(jsonroot);
//获取网络时间 end -------------------------------------------------------------------------
//获取天气 start -------------------------------------------------------------------------
res = NULL;
err = getaddrinfo(WEB_SERVER_WEATHER, 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;
}
/* Code to print the resolved IP.
Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code */
addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*addr));
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");
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);
if (write(s, REQUEST_WEATHER, strlen(REQUEST_WEATHER)) < 0) {
ESP_LOGE(TAG, "... socket send failed");
close(s);
vTaskDelay(4000 / portTICK_PERIOD_MS);
continue;
}
ESP_LOGI(TAG, "... socket send success");
// 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");
for(rec_count=0;rec_count<sizeof(str_json);rec_count++)
str_json[rec_count]=0;
rec_count=0;
/* Read HTTP response */
do {
bzero(recv_buf, sizeof(recv_buf));
r = read(s, recv_buf, sizeof(recv_buf)-1);
for(int i = 0; i < r; i++) {
rec_count++;
if(rec_count>=410)
{
str_json[rec_count-410] = recv_buf[i];
}
putchar(recv_buf[i]);
}
} while(r > 0);
printf("\r\n----------------------------------------------\r\n");
printf("\r\n%s\r\n",str_json);
printf("\r\n----------------------------------------------\r\n");
cJSON *json_weather = cJSON_Parse(str_json);
if(json_weather!=NULL)
{
printf("json_weather是JSON数据\r\n");
cJSON *json_weather_results = cJSON_GetObjectItem(json_weather,"results");
if(json_weather_results != NULL)
{
printf("json_weather_results 是 json\r\n");
cJSON *json_weather_array = cJSON_GetArrayItem(json_weather_results,0);
if(json_weather_array != NULL)
{
printf("json_weather_array 是 json array\r\n");
cJSON *json_weather_location = cJSON_GetObjectItem(json_weather_array,"location");
if(json_weather_location!=NULL)
{
cJSON *json_weather_city = cJSON_GetObjectItem(json_weather_location,"name");
if(json_weather_city!=NULL)
printf("city name :%s\r\n",json_weather_city->valuestring);
strcpy(city,json_weather_city->valuestring);
}
cJSON *json_weather_now = cJSON_GetObjectItem(json_weather_array,"now");
if(json_weather_now!=NULL)
{
cJSON *json_weather_text = cJSON_GetObjectItem(json_weather_now,"text");
if(json_weather_now!=NULL)
printf("weather text :%s\r\n",json_weather_text->valuestring);
strcpy(weather_name,json_weather_text->valuestring);
cJSON *json_weather_temperature = cJSON_GetObjectItem(json_weather_now,"temperature");
if(json_weather_temperature!=NULL)
printf("temperature :%s\r\n",json_weather_temperature->valuestring);
strcpy(temperature,json_weather_temperature->valuestring);
}
}
}
}
else
{
printf("json_weather不是JSON数据\r\n");
}
cJSON_Delete(json_weather);
ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d.", r, errno);
//获取天气 end -------------------------------------------------------------------------
close(s);
xEventGroupWaitBits(weather_event_group,GET_ONLINE_BITS,pdTRUE,pdFALSE,portMAX_DELAY);
}
}
主要遇到的困难
1、环境搭建
2、freeRTOS
3、WiFi配网
4、API接口
5、JSON数据解析
6、OLED显示
心得体会
此次训练营让我收获很多,让我第一次接触到物联网,接触到ESP32芯片的开发,之前只学过keil开发51和rt1052单片机,此次让我学到了用VScode配合ESP-IDF环境对ESP32的开发。让我熟悉了ESP32芯片的性能,考虑以后可以做一些小玩意用到这款芯片。