基于ESP32-S2制作本地气象台/温度计
文章目录
- 项目介绍
- 设计思路
- 功能实现
- 使用方法
源码地址
项目介绍
使用 ESP32-S2
制作一个本地气象台/温度计,在 oled
屏幕上显示本地的实时时间和天气信息。
设计思路
功能实现
(1)连接 wifi 功能
- wifi 初始化
- 连接 wifi
- 事件处理
ESP32-S2
连接 wifi 需要设置成 AP
模式。
注册 wifi 开始连接事件、wifi 断联事件和获取 IP 地址事件,在事件回调函数中对这三种情况分别处理:
- 连接 wifi
- 重新连接
- 获得 IP 地址
/*
** @brief 处理wifi连接和ip分配时候事件的回调函数
*/
static void event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{// 如果是wifi station开始连接事件,就尝试将station连接到APif (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START){esp_wifi_connect();}// 如果是wifi station从AP断连事件else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED){// 如果没有达到最高尝试次数,继续尝试if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY){esp_wifi_connect();s_retry_num ++;ESP_LOGI(TAG, "retry to connect to the AP");}else // 如果达到了最高尝试次数,就标记连接失败{xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);}ESP_LOGI(TAG,"connect to the AP fail");}// 如果是ip获取事件,获取到了ip就打印出来else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP){ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));s_retry_num = 0;xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); // 成功获取到了ip,就标记这次wifi连接成功}
}/*
** @brief 用于连接wifi的函数
** @param[in] 无
** @retval 无
** @note 这里wifi连接选项设置了使用nvs,会把每次配置的参数存储在nvs中。因此请查看分区表中是否对nvs分区进行了设置
*/
void wifi_init_sta(void)
{// 00 创建wifi事件组s_wifi_event_group = xEventGroupCreate();/******************** 01 Wi-Fi/LwIP 初始化阶段 ********************/// 01-1 创建LWIP核心任务ESP_ERROR_CHECK(esp_netif_init());// 01-2 创建系统事件任务,并初始化应用程序事件的回调函数ESP_ERROR_CHECK(esp_event_loop_create_default());// 01-3 创建有 TCP/IP 堆栈的默认网络接口实例绑定 stationesp_netif_create_default_wifi_sta();// 01-4 创建wifi驱动程序任务,并初始化wifi驱动程序wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&cfg));// 01-5 注册,用于处理wifi连接的过程中的事件esp_event_handler_instance_t instance_any_id; // 用于处理wifi连接时候的事件的句柄esp_event_handler_instance_t instance_got_ip; // 用于处理ip分配时候产生的事件的句柄// 该句柄对wifi连接所有事件都产生响应,连接到event_handler回调函数ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&event_handler,NULL,&instance_any_id));// 该句柄仅仅处理IP_EVENT事件组中的从AP中获取ip地址事件,连接到event_handler回调函数ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,IP_EVENT_STA_GOT_IP,&event_handler,NULL,&instance_got_ip));/******************** 02 WIFI配置阶段 ********************/// 02-1 定义wifi配置参数wifi_config_t wifi_config = {.sta = {.ssid = EXAMPLE_ESP_WIFI_SSID,.password = EXAMPLE_ESP_WIFI_PASS,/* Setting a password implies station will connect to all security modes including WEP/WPA.* However these modes are deprecated and not advisable to be used. Incase your Access point* doesn't support WPA2, these mode can be enabled by commenting below line */.threshold.authmode = WIFI_AUTH_WPA2_PSK,.pmf_cfg = {.capable = true,.required = false},},};// 02-2 配置station工作模式ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));// 02-3 配置ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));/******************** 03 wifi启动阶段 ********************/// 03-1 启动wifi驱动程序ESP_ERROR_CHECK(esp_wifi_start()); // 会触发回调函数ESP_LOGI(TAG, "wifi_init_sta finished.");/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) *//******************** 输出wifi连接结果 ********************/EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,pdFALSE,pdFALSE,portMAX_DELAY);/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually* happened. */if (bits & WIFI_CONNECTED_BIT){ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);}else if (bits & WIFI_FAIL_BIT){ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);}else{ESP_LOGE(TAG, "UNEXPECTED EVENT");}/* The event will not be processed after unregister */// 05 事件注销ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));vEventGroupDelete(s_wifi_event_group);
}
(2)通过 http 协议获取天气数据
使用 esp32
的 http client
库发送 http
协议。
使用通过流的方法:
- 通过
esp_http_client_config_t
结构体定义http
的参数 - 通过
esp_http_client_init()
进行初始化 - 通过
esp_http_client_set_method()
设置发送get
请求 - 通过
esp_http_client_open()
与目标主机建立连接,发送请求 - 通过
esp_http_client_fetch_headers()
获取目标主机的response
报文的头信息,判断是否成功获取数据 - 通过
esp_http_client_read_response()
获取报文的返回数据内容
获取返回的数据后,使用 cJSON
进行解包。
void display_weather(char output_buffer[])
{cJSON* root = NULL; // 头指针root = cJSON_Parse(output_buffer); // 解析整段JSON数据// 逐层解析键值对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_text = cJSON_GetObjectItem(cjson_now, "text");cJSON* cjson_time = cJSON_GetObjectItem(cjson_results, "last_update");printf("weather:%s\n", cjson_text->valuestring);printf("temperature:%s\n", cjson_temperature->valuestring);char str[80];sprintf(str, "temperature:%s", cjson_temperature->valuestring);oled_string(0, 35, str, 12);sprintf(str, "weather:%s", cjson_text->valuestring);oled_string(0, 20, str, 12);vTaskDelay(1000 / portTICK_RATE_MS);
}static void http_test_task(void *pvParameters)
{// 02-1 定义需要的变量char output_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0}; // 用于接收通过http协议返回的数据int content_length = 0; // http协议头的长度// 02-2 配置http结构体// 定义http配置结构体,并且进行清零(避免初始化随机值)esp_http_client_config_t config;memset(&config, 0, sizeof(config));// 向配置结构体内部写入url(心知天气API接口地址)static const char *URL = WEATHER_API;config.url = URL;// 初始化结构体esp_http_client_handle_t client = esp_http_client_init(&config); // 初始化http客户端// 设置发送get请求esp_http_client_set_method(client, HTTP_METHOD_GET);bool last = false, cur = false;// 02-3 循环通讯while(1){// 与目标主机创建连接,并且声明写入内容长度为0esp_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));cur = false;if (last) oled_clear(); // 连接成功变为连接失败last = false;} // 连接成功else{cur = true;if (!last) oled_clear(); // 连接失败变为连接成功last = true;// 读取目标主机的返回内容的协议头长度content_length = esp_http_client_fetch_headers(client);// 如果协议头长度小于0,说明没有成功读取到if (content_length < 0){ESP_LOGE(TAG, "HTTP client fetch headers failed");}// 如果成功读取到了协议头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), // 获取HTTP响应状态esp_http_client_get_content_length(client)); // 获取响应信息长度printf("data:%s\n", output_buffer);// 对接收到的数据作相应的处理display_weather(output_buffer);}// 如果不成功else{ESP_LOGE(TAG, "Failed to read response");}}}// 关闭连接esp_http_client_close(client);// 延时,因为心知天气免费版本每分钟只能获取20次数据vTaskDelay(3000 / portTICK_PERIOD_MS);}
}
(3)获得实时时间
SNTP
同步。SNTP
协议是用来同步本地的时间到 unix
时间戳. 通常嵌入式设备上电, 连接 AP(access point)
, 获取 IP 地址后, 就需要使用 SNTP
协议获取全球时间. 以便于下一步的应用交互和使用。
使用 C 库函数 time()
、localtime_r()
获得时间,strftime()
将时间转换成指定格式。
static void esp_initialize_sntp(void)
{ESP_LOGI(TAG, "Initializing SNTP");sntp_setoperatingmode(SNTP_OPMODE_POLL);sntp_setservername(0, "ntp1.aliyun.com");sntp_init();
}void esp_wait_sntp_sync(void)
{char strftime_buf[64];esp_initialize_sntp();// wait for time to be settime_t now = 0;struct tm timeinfo = { 0 };int retry = 0;while (timeinfo.tm_year < (2019 - 1900)) {ESP_LOGD(TAG, "Waiting for system time to be set... (%d)", ++retry);vTaskDelay(100 / portTICK_PERIOD_MS);time(&now);localtime_r(&now, &timeinfo);}// set timezone to China Standard Timesetenv("TZ", "CST-8", 1);tzset();strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);ESP_LOGI(TAG, "The current date/time in Shanghai is: %s", strftime_buf);
}// 获得实时时间
void display_time(void)
{// 死循环防止出现返回值,从而导致freertos报错while (1){time_t now;char strftime_buf[64];struct tm timeinfo;time(&now);// Set timezone to China Standard Timesetenv("TZ", "CST-8", 1);tzset();localtime_r(&now, &timeinfo);strftime(strftime_buf, sizeof(strftime_buf), "%Y-%m-%d %H:%M:%S", &timeinfo);ESP_LOGI(TAG, "The current date/time in Shanghai is: %s", strftime_buf);oled_string(0, 50, strftime_buf, 12);oled_refresh();vTaskDelay(300 / portTICK_RATE_MS);}
}
(4)oled
显示
配置 SPI
驱动 oled
屏幕显示信息,分别设置主设备输出、从设备输出引脚号(35),时钟引脚号(36),D/C引脚号(33)和复位引脚号(34)。使用 12 * 12
的1206 ASCII
字符集点阵。
DRAM_ATTR static uint8_t screen[8][128] = {0}; // 显示数据,大小(y,x)=64x128
static spi_device_handle_t spi; // SPI句柄void send_cmd(const uint8_t cmd, spi_device_handle_t spi)
{esp_err_t ret;spi_transaction_t t;memset(&t, 0, sizeof(t)); // Zero out the transactiont.length = 8; // Command is 8 bitst.tx_buffer = &cmd; // The data is the cmd itselft.user = (void*)0; // D/C needs to be set to 0ret = spi_device_polling_transmit(spi, &t); // Transmit!assert(ret == ESP_OK); // Should have had no issues.
}void send_data(spi_device_handle_t spi, const uint8_t *data, int len)
{esp_err_t ret;spi_transaction_t t;if (len == 0) return; // no need to send anythingmemset(&t, 0, sizeof(t)); // Zero out the transactiont.length = len * 8; // Len is in bytes, transaction length is in bits.t.tx_buffer = data; // Datat.user = (void*)1; // D/C needs to be set to 1ret = spi_device_polling_transmit(spi, &t); // Transmit!assert(ret == ESP_OK); // Should have had no issues.
}void oled_spi_pre_transfer_callback(spi_transaction_t *t)
{int dc = (int)t->user;gpio_set_level(PIN_NUM_DC, dc);
}void oled_refresh()
{uint8_t m;for (m = 0; m < 8; m ++){send_cmd(0xb0 + m, spi);send_cmd(0x00, spi);send_cmd(0x10, spi);send_data(spi, screen[m], 128);}
}void oled_drawpoint(uint8_t x, uint8_t y)
{uint8_t i = 0, j = 0;x = 127 - x;i = y / 8;j = y % 8;j = 1 << j;screen[i][x] |= j;
}void oled_clearpoint(uint8_t x, uint8_t y)
{uint8_t i = 0, j = 0;x = 127 - x;i = y / 8;j = y % 8;j = 1 << j;screen[i][x] = ~screen[i][x];screen[i][x] |= j;screen[i][x] = ~screen[i][x];
}void oled_char(uint8_t x, uint8_t y, char chr,uint8_t size1)
{uint8_t i, m, temp, size2, chr1;uint8_t y0 = y;size2 = (size1 / 8 + ((size1 % 8) ? 1 : 0)) * (size1 / 2); // 得到字体一个字符对应点阵集所占的字节数chr1 = chr - ' '; // 计算偏移后的值for (i = 0; i < size2; i ++){if (size1 == 12) // 调用1206字体{temp = asc2_1206[chr1][i];}else if (size1 == 16) // 调用1608字体{temp = asc2_1608[chr1][i];}else return;for (m = 0; m < 8; m ++){if (temp & 0x80) oled_drawpoint(x, y);else oled_clearpoint(x, y);temp <<= 1;y --;if ((y0 - y) == size1){y = y0;x ++;break;}}}
}void oled_string(uint8_t x, uint8_t y, char *chr, uint8_t size1)
{while ((*chr >= ' ') && (*chr <= '~') && (*chr != '.')) // 判断是不是非法字符!{oled_char(x, y, *chr, size1);x += size1 / 2;if (x > 128 - size1) // 换行{x = 0;y -= size1;}chr ++;}
}void oled_init()
{// Initialize non-SPI GPIOsgpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT);gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);// Reset the displaygpio_set_level(PIN_NUM_RST, 0);vTaskDelay(100 / portTICK_RATE_MS);gpio_set_level(PIN_NUM_RST, 1);vTaskDelay(100 / portTICK_RATE_MS);
}// 将screen数组置零
void oled_clear()
{uint8_t i, j;for (i = 0; i < 8; i ++)for (j = 0; j < 128; j ++)screen[i][j] = 0;oled_refresh();
}// 启动LCD
void oled_start()
{esp_err_t ret;spi_bus_config_t buscfg = {.miso_io_num=PIN_NUM_MISO,.mosi_io_num=PIN_NUM_MOSI,.sclk_io_num=PIN_NUM_CLK,.quadwp_io_num=-1,.quadhd_io_num=-1,.max_transfer_sz=128*8,};spi_device_interface_config_t devcfg = {.clock_speed_hz=20*1000*1000, //Clock out at 20 MHz.mode=0, //SPI mode 0.spics_io_num=PIN_NUM_CS, .queue_size=1, .pre_cb=oled_spi_pre_transfer_callback, };// Initialize the SPI busret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);ESP_ERROR_CHECK(ret);// Attach the oled to the SPI busret = spi_bus_add_device(SPI2_HOST, &devcfg, &spi);ESP_ERROR_CHECK(ret);oled_init();send_cmd(0x8D, spi);send_cmd(0x14, spi);send_cmd(0xAF, spi);// 初始化配置完成char *str = "Connecting to wifi";oled_string(10, 30, str, 12);oled_refresh();vTaskDelay(1000 / portTICK_RATE_MS);
}
使用方法
下载源码后,根据自身情况修改 wifi 账号、密码和 API 的秘钥(your_key):
#define EXAMPLE_ESP_WIFI_SSID "account" // 账号
#define EXAMPLE_ESP_WIFI_PASS "password" // 密码
#define WEATHER_API "https://api.seniverse.com/v3/weather/now.json?key=your_key&location=ip&language=en&unit=c"
编译烧录成功之后,即可在 oled
屏幕上显示时间和天气信息。
基于ESP32-S2制作本地气象台/温度计相关推荐
- 基于ESP32与phyphox的DIS实验制作(5)-基于无线光电门的速度传感器制作
上两篇我们讲到了基于ESP32自带的电压传感器,以及外接创客常用的超声波测距模块.实验本身没有什么创意,仅仅是用自制的传感器来实现普通的DIS设备都能实现的功能.但是在这一篇,我们将会在自制DIS实验 ...
- 基于ESP32的蓝牙刷屏器自动点击器的制作
ESP32模块的选型: 这里是利用蓝牙连接手机来做点击器或刷屏器,ESP8266只有WIFI而ESP32有WIFI和蓝牙,所以选择ESP32模块. ESP32模块可以选择ESP32-NodeMCU: ...
- 基于ESP32做低功耗墨水屏时钟
基于ESP32做低功耗墨水屏时钟 电子墨水屏 概述 ESP32 实验 低功耗电子时钟功能描述 接线 开发 实验结果 电子墨水屏 概述 电子墨水是一种革新信息显示的新方法和技术.和传统纸差异是电子墨水在 ...
- 基于ESP32智能车竞赛裁判系统第二版硬件调试-6-26
简 介: 对于新版的比赛裁判系统进行硬件测试,验证了新版的硬件满足比赛的要求.对于感光板的不同区域灵敏度不同的问题,最后验证是由于LED的分布电容所引起的时间常数不同造成了.对于单条串联的LED修改成 ...
- 基于ESP32智能车竞赛比赛系统硬件初步调试-5-6
简 介: 给出了对于基于ESP32设计的智能车竞赛的の比赛系统的硬件调试过程.基本上验证了硬件设计的合理与正确性.在第一部分的"修改建议"中也给出了硬件电路的修改意见. 关键词: ...
- AI视觉组基于ESP32的裁判系统第一版本设计要求
简 介: 面对第十六届全国大学生智能车竞赛中新增加的一些组别的要求,比如室内AI组,对于车模任务增加的检测任务,设计了基于ESP32为核心的比赛系统.本文给出了对于比赛系统功能的要求. 关键词: 比赛 ...
- SAP UI5 应用开发教程之六十三 - 基于 OData V4 的本地 Mock Server 实现的深入介绍试读版
一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...
- 基于ESP32环境监测控制和预警(微信小程序)
1.项目背景 随着科技的进步,人工智能逐渐发展,人们也越来越依赖科技的力量. 目前,科学技术发展十分迅速,其渗透到各行各业以及生活的方方面面,通过远程控制.预约控制.个性化设计.一键控制等功能进一步提 ...
- Ubuntu 20.04制作本地源
文章目录 下载 脚本 索引 源 使用 本地搭建 局域网 错误示范 正确姿势 就为了一个镜像我整了一整天,我的妈 基于Ubuntu 20.04系统制作本地软件包源,在没有网络的时候使用本地源可以下载软件 ...
- 【物联网初探】- 09 - 基于 ESP32 和微信小程序的土壤湿度监测【完结篇】
文章目录 1. 硬件.接线.环境配置 2. 项目简介 2.1 初衷 2.2 技术路线 3. 实现方法 3.1 接线及电源选型 3.2 ESP32 端程序 3.2.1 源码 3.2.2 特别说明 3.3 ...
最新文章
- dtree和jquery构建树型结构
- Shell 数组中 @ 跟 * 的区别
- 密码技术--证书及go语言生成自签证书
- Python学习笔记(三) Python基础
- 为什么Spring仍然会是云原生时代最佳平台之一?
- U盘安装Ubuntu14.04 server版 提示无法挂载cd-rom数据的解决办法
- Windows10和Ubuntu双系统下用windows引导Ubuntu
- Vue中子组件如何向父组件传递数据?
- 【CometOJ】CometOJ#8 解题报告
- 接口测试工具SoapUI(一)安装和破解
- php中求10递归算法,PHP递归算法的应用(含示例)
- 如何画一个算法流图?
- 大数据面试题——用shell打印200以内的质数
- 【NOIP2015】推销员
- 列表中使用bootstrap-switch开关
- 大家都在努力,你凭什么不努力
- android 检测 Home 键
- 托福100分什么水平
- java实现手机验证码功能
- java程序封装最小单位,Java面试真题精选