文章目录

  • 项目介绍
  • 设计思路
  • 功能实现
  • 使用方法

源码地址

项目介绍

使用 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 协议获取天气数据

使用 esp32http 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制作本地气象台/温度计相关推荐

  1. 基于ESP32与phyphox的DIS实验制作(5)-基于无线光电门的速度传感器制作

    上两篇我们讲到了基于ESP32自带的电压传感器,以及外接创客常用的超声波测距模块.实验本身没有什么创意,仅仅是用自制的传感器来实现普通的DIS设备都能实现的功能.但是在这一篇,我们将会在自制DIS实验 ...

  2. 基于ESP32的蓝牙刷屏器自动点击器的制作

    ESP32模块的选型: 这里是利用蓝牙连接手机来做点击器或刷屏器,ESP8266只有WIFI而ESP32有WIFI和蓝牙,所以选择ESP32模块. ESP32模块可以选择ESP32-NodeMCU: ...

  3. 基于ESP32做低功耗墨水屏时钟

    基于ESP32做低功耗墨水屏时钟 电子墨水屏 概述 ESP32 实验 低功耗电子时钟功能描述 接线 开发 实验结果 电子墨水屏 概述 电子墨水是一种革新信息显示的新方法和技术.和传统纸差异是电子墨水在 ...

  4. 基于ESP32智能车竞赛裁判系统第二版硬件调试-6-26

    简 介: 对于新版的比赛裁判系统进行硬件测试,验证了新版的硬件满足比赛的要求.对于感光板的不同区域灵敏度不同的问题,最后验证是由于LED的分布电容所引起的时间常数不同造成了.对于单条串联的LED修改成 ...

  5. 基于ESP32智能车竞赛比赛系统硬件初步调试-5-6

    简 介: 给出了对于基于ESP32设计的智能车竞赛的の比赛系统的硬件调试过程.基本上验证了硬件设计的合理与正确性.在第一部分的"修改建议"中也给出了硬件电路的修改意见. 关键词: ...

  6. AI视觉组基于ESP32的裁判系统第一版本设计要求

    简 介: 面对第十六届全国大学生智能车竞赛中新增加的一些组别的要求,比如室内AI组,对于车模任务增加的检测任务,设计了基于ESP32为核心的比赛系统.本文给出了对于比赛系统功能的要求. 关键词: 比赛 ...

  7. SAP UI5 应用开发教程之六十三 - 基于 OData V4 的本地 Mock Server 实现的深入介绍试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

  8. 基于ESP32环境监测控制和预警(微信小程序)

    1.项目背景 随着科技的进步,人工智能逐渐发展,人们也越来越依赖科技的力量. 目前,科学技术发展十分迅速,其渗透到各行各业以及生活的方方面面,通过远程控制.预约控制.个性化设计.一键控制等功能进一步提 ...

  9. Ubuntu 20.04制作本地源

    文章目录 下载 脚本 索引 源 使用 本地搭建 局域网 错误示范 正确姿势 就为了一个镜像我整了一整天,我的妈 基于Ubuntu 20.04系统制作本地软件包源,在没有网络的时候使用本地源可以下载软件 ...

  10. 【物联网初探】- 09 - 基于 ESP32 和微信小程序的土壤湿度监测【完结篇】

    文章目录 1. 硬件.接线.环境配置 2. 项目简介 2.1 初衷 2.2 技术路线 3. 实现方法 3.1 接线及电源选型 3.2 ESP32 端程序 3.2.1 源码 3.2.2 特别说明 3.3 ...

最新文章

  1. dtree和jquery构建树型结构
  2. Shell 数组中 @ 跟 * 的区别
  3. 密码技术--证书及go语言生成自签证书
  4. Python学习笔记(三) Python基础
  5. 为什么Spring仍然会是云原生时代最佳平台之一?
  6. U盘安装Ubuntu14.04 server版 提示无法挂载cd-rom数据的解决办法
  7. Windows10和Ubuntu双系统下用windows引导Ubuntu
  8. Vue中子组件如何向父组件传递数据?
  9. 【CometOJ】CometOJ#8 解题报告
  10. 接口测试工具SoapUI(一)安装和破解
  11. php中求10递归算法,PHP递归算法的应用(含示例)
  12. 如何画一个算法流图?
  13. 大数据面试题——用shell打印200以内的质数
  14. 【NOIP2015】推销员
  15. 列表中使用bootstrap-switch开关
  16. 大家都在努力,你凭什么不努力
  17. android 检测 Home 键
  18. 托福100分什么水平
  19. java实现手机验证码功能
  20. java程序封装最小单位,Java面试真题精选

热门文章

  1. 使用HTML实现百度首页界面
  2. GBase 8atmp 目录权限改变导致加载失败
  3. python实现论文查重系统_python 手把手教你基于搜索引擎实现文章查重
  4. 20金融学431考研应该注意些什么
  5. 从入门到放弃的华为手机忘记密码后的数据自救之旅
  6. 循环经济升级推动产业升级发展建议
  7. 象棋马走日正解判定表实现步数计算
  8. 第十九届泳联水中运动世锦赛
  9. PHP入门《PHP程序设计案例教程》-- PHP语法基础
  10. 为什么国内抖音没有网页版,原因竟然是这样!