声明:文章仅为个人学习分享,不能保证所有注释无误,代码处已给出注释,文章不再赘述
一、开发环境以及设备
- VScode上的ESPIDF
- 合宙ESP32C3开发板
- 四针0.96OLED显示屏
- 一根type-c(用于烧录程序)
- 4个杜邦线(用于OLED显示屏和ESP32C3开发板连接)
- 可连网开热点设备(不限于移动手机或者笔记本电脑等)
- 心知天气免费API(自行申请)
二、 结果展示
三、 内容
1、 定时器
用于1秒计时
mTimer.c
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gptimer.h"
#include "esp_log.h"
#include "mTimer.h"
bool time_1s_falg = false; /*用于表示1S到达*/
/**
* @brief 将计数值发送到消息队列中
*
* @param timer 定时器句柄
* @param edata 报警事件数据
* @param user_data 用户注册回调函数时数据
* @return true 未使用,默认返回true
* @return false
*/
static bool IRAM_ATTR Timierng_Callback_Func(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
time_1s_falg = true;
return true;
}
void Timer_Init(uint64_t count_us)
{
gptimer_handle_t gptimer = NULL; /*通用定时器句柄*/
gptimer_config_t timer_config = { /*配置结构体*/
.clk_src = GPTIMER_CLK_SRC_DEFAULT, /*选择APB作为默认时钟源*/
.direction = GPTIMER_COUNT_UP, /*设置计数方向为向上计数*/
.resolution_hz = 1000000, /*设置计数器频率1M*/
};
/*创建一个新的定时器*/
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
/*设置事件回调函数*/
gptimer_event_callbacks_t cbs = {
.on_alarm = Timierng_Callback_Func,
};
/*注册事件回调函数*/
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
ESP_ERROR_CHECK(gptimer_enable(gptimer)); /*使能定时器*/
gptimer_alarm_config_t alarm_config = {
.reload_count = 0,
.alarm_count = count_us,
.flags.auto_reload_on_alarm = 1,
};
/*使警报配置生效*/
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
/*使定时器开始工作*/
ESP_ERROR_CHECK(gptimer_start(gptimer));
}
mTimer.h
#ifndef __MTIMER_H__
#define __MTIMER_H__
#include <stdio.h>
extern bool time_1s_falg;
void Timer_Init(uint64_t count_us);
#endif
2、 oled显示
I2C通讯,用于显示时间、天气、地点、温度
oled.c
#include "oled.h"
#include "mfont.h"
#include "esp_err.h"
#include "driver/i2c.h"
#define I2C_SCL 6 /*时钟线*/
#define I2C_SDA 5 /*数据线*/
#define I2C_FREQ_HZ 400000 /*频率:400K*/
#define I2C_ADDR 0X3C /*OLED地址*/
#define I2C_PORT 0 /*I2C通道*/
#define I2C_MASTER_TIMEOUT_MS 100 /*发出超时等待时间*/
#define OLED_CMD 0 /*写命令*/
#define OLED_DATA 1 /*写数据*/
/**
* @brief I2C初始化
*
* @return esp_err_t ESP_OK:表示初始化成功
*/
static esp_err_t I2C_Init(void) /*大多数特定的 ESP-IDF 函数使用 esp_err_t 类型来返回错误代码ESP_OK 表示为成功*/
{
int i2c_port = I2C_PORT;
/*I2C初始化配置*/
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.scl_io_num = I2C_SCL,
.sda_io_num = I2C_SDA,
.sda_pullup_en = GPIO_PULLUP_ENABLE, /*使能上拉*/
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_FREQ_HZ};
i2c_param_config(i2c_port, &conf);
/*参数:I2C端口号、模式、主机模式无需填写(写零)、主机模式无需填写(写零)、中断标志(0:忽略中断);返回ESP_OK表示成功安装驱动*/
return i2c_driver_install(i2c_port, conf.mode, 0, 0, 0);
}
/**
* @brief 写字节
*
* @param dat 数据
* @param mode 1:内容 0:命令
* @return esp_err_t
*/
static esp_err_t OLED_WR_Byte(uint8_t dat, uint8_t mode)
{
int ret;
uint8_t cmd;
if(1 == mode)
cmd = 0x40;
else
cmd = 0x00;
uint8_t write_buf[2] = {cmd, dat};
/*参数:I2C端口号、I2C设备7位地址、发送的字节、写入缓冲区大小、发出超时之前等待最大时长tick*/
ret = i2c_master_write_to_device(I2C_PORT, I2C_ADDR , write_buf, sizeof(write_buf), I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
return ret;
}
/**
* @brief 清屏
*
*/
void OLED_Clear(void)
{
uint8_t i, n;
for (i = 0; i < 8; i++)
{
OLED_WR_Byte(0xb0 + i, OLED_CMD);
OLED_WR_Byte(0x00, OLED_CMD);
OLED_WR_Byte(0x10, OLED_CMD);
for (n = 0; n < 128; n++)
OLED_WR_Byte(0, OLED_DATA);
}
}
/**
* @brief OLED初始化
*
*/
void OLED_Init(void)
{
ESP_ERROR_CHECK(I2C_Init());
OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
OLED_WR_Byte(0x40,OLED_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
OLED_WR_Byte(0xCF,OLED_CMD);// Set SEG Output Current Brightness
OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F)
OLED_WR_Byte(0x00,OLED_CMD);//-not offset
OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
OLED_WR_Byte(0x12,OLED_CMD);
OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
OLED_WR_Byte(0x02,OLED_CMD);//
OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7)
OLED_Clear();
OLED_WR_Byte(0xAF,OLED_CMD);
}
/**
* @brief 坐标设置
*
* @param x 列:0~127
* @param y 页:0~7
*/
void OLED_Set_Pos(uint8_t x, uint8_t y)
{
OLED_WR_Byte(0xb0 + y, OLED_CMD);
OLED_WR_Byte(((x & 0xf0) >> 4) | 0x10, OLED_CMD);
OLED_WR_Byte((x & 0x0f), OLED_CMD);
}
/**
* @brief 显示字符
*
* @param x 起始坐标:0~127
* @param y 起始坐标:0~7
* @param chr 显示的字符
* @param Char_Size 大小:8或16
*/
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t Char_Size)
{
uint8_t c = 0;
uint8_t i = 0;
c = chr - ' ';
if (x > 127)
{
x = 0;
y = y + 2;
}
if (Char_Size == 16)
{
OLED_Set_Pos(x, y);
for (i = 0; i < 8; i++)
OLED_WR_Byte(F8X16[c * 16 + i], OLED_DATA);
OLED_Set_Pos(x, y + 1);
for (i = 0; i < 8; i++)
OLED_WR_Byte(F8X16[c * 16 + i + 8], OLED_DATA);
}
else
{
OLED_Set_Pos(x, y);
for (i = 0; i < 6; i++)
OLED_WR_Byte(F6x8[c][i], OLED_DATA);
}
}
/**
* @brief 显示字符串
*
* @param x 起始坐标:0~127
* @param y 起始坐标:0~7
* @param chr 显示的字符串
* @param Char_Size 大小:8或16
*/
void OLED_ShowString(uint8_t x,uint8_t y,uint8_t *chr,uint8_t Char_Size)
{
uint8_t j = 0;
while (chr[j] != '\0')
{
OLED_ShowChar(x, y, chr[j], Char_Size);
x += 8;
if (x > 120)
{
x = 0;
y += 2;
}
j++;
}
}
/**
* @brief 显示汉字
*
* @param x 起始坐标:0~127
* @param y 起始坐标:0~7
* @param no 对应汉字序列号
*/
void OLED_ShowCHinese(uint8_t x,uint8_t y,uint8_t no)
{
uint8_t t, adder = 0;
OLED_Set_Pos(x, y);
for (t = 0; t < 16; t++)
{
OLED_WR_Byte(Hzk[2 * no][t], OLED_DATA);
adder += 1;
}
OLED_Set_Pos(x, y + 1);
for (t = 0; t < 16; t++)
{
OLED_WR_Byte(Hzk[2 * no + 1][t], OLED_DATA);
adder += 1;
}
}
/**
* @brief 显示图片,注意:起始坐标加上实际大小不要从超过显示最大范围
*
* @param x0 起始坐标0~127
* @param y0 0~7
* @param x1 长0~127
* @param y1 宽0~63
* @param BMP 所显示图片
*/
void OLED_DrawBMP(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t BMP[])
{
uint32_t j = 0;
uint8_t x, y;
y1 = y1 / 8 + ((y1 % 8)?1 : 0); /*计算后宽实际值还是0~7,为了方便调用时填入像素点长和宽值看起来和谐*/
for (y = y0; y < y1 + y0; y++)
{
OLED_Set_Pos(x0, y);
for (x = x0; x < x1 + x0; x++)
{
OLED_WR_Byte(BMP[j++], OLED_DATA);
}
}
}
/**
* @brief 求m的n次方
*
* @param m 底数
* @param n 指数
* @return uint32_t
*/
uint32_t oled_pow(uint8_t m,uint8_t n)
{
uint32_t result=1;
while(n--)result*=m;
return result;
}
/**
* @brief 显示数字,依据显示位数自动补零在前面
*
* @param x0 起始坐标0~127
* @param y0 0~7
* @param num 数字
* @param len 显示所占的长度(不应小于实际大小)
* @param size2 字体大小16、8
*/
void OLED_ShowNum(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size2)
{
uint8_t t, temp, m = 0;
if (8 == size2)
m = 2;
for (t = 0; t < len; t++)
{
temp = (num / oled_pow(10, len - t - 1)) % 10;
if (temp == 0)
{
OLED_ShowChar(x + (size2 / 2 + m) * t, y, '0', size2);
}
else
{
OLED_ShowChar(x + (size2 / 2 + m) * t, y, temp + '0', size2);
}
}
}
oled.h
#ifndef __OLED_H__
#define __OLED_H__
#include <stdio.h>
/*OLED初始化*/
void OLED_Init(void);
/*清屏函数*/
void OLED_Clear(void);
/*设置坐标*/
void OLED_Set_Pos(uint8_t x, uint8_t y);
/*显示字符*/
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t Char_Size);
/*显示字符串*/
void OLED_ShowString(uint8_t x,uint8_t y,uint8_t *chr,uint8_t Char_Size);
/*显示汉字*/
void OLED_ShowCHinese(uint8_t x,uint8_t y,uint8_t no);
/*显示图片*/
void OLED_DrawBMP(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t BMP[]);
/*显示数字*/
void OLED_ShowNum(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size2);
#endif
3、 main函数
程序功能简述
- 每次连上WiFi时候抓取一次时间,然后不再抓取,通过定时器来计时
- 每10秒钟抓取一次心知天气的天气信息
- 无论时间还是天气,再将数据处理后第一时间显示到OLED屏上
/**
* @file main.c
* @author Acy (2496188164@qq.com)
* @brief 上电获取一次时间,每隔一定时间获取指定地点一次天气和温度,并再OLED显示屏上显示
* 时间处理上一开始就打开了定时器,应当再获取到现在时间时先清空当前计数值,否则程序会有-1S~1S的误差
* 通过不断创建任务来替代while循环不断打开然后关闭http客户端(while循环按一定频率抓取天气,10S抓取一次时,大约十分钟左右就会报空间不足mbedtls_ssl_setup returned -0x7F00)
* 注意:注释的语句若没有特殊说明则为调试过程所用
* UTF-8编码:汉字3个字节
*
* @version 0.1
* @date 2024-01-11
*
* @copyright Copyright (c) 2024
*
*/
#include <stdio.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_http_client.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_netif_ip_addr.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "cJSON.h"
#include "mTimer.h"
#include "oled.h"
#include "mfont.h"
/*苏宁时间戳:http://quan.suning.com/getSysTime.do*/
/*我的心知天气:https://api.seniverse.com/v3/weather/now.json?key=SXadqEfe17oFmTEg_&location=qinzhou&language=zh-Hans&unit=c*/
#define WIFI_STA_NAME "Acy_PC" /*WIFI名字*/
#define WIFI_STA_PASSWORD "1234567890" /*WIFI密码*/
#define WEATHER_DATA_LEN_MAX 384 /*获取心知天气数据数组长度*/
#define TIME_DATA_LEN_MAX 248 /*获取苏宁时间戳的数组长度*/
#define WEATHER_GET_INTERVAL_TIME_S 10 /*获取心知天气的间隔时间*/
/*oled显示屏时间显示*/
typedef struct
{
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
}time_struct;
/*中文数组下标,用于可同拼音不同声调下*/
typedef enum{
CHINESE_TIAN = 0, /*天*/
CHINESE_QI, /*气*/
CHINESE_QING, /*晴*/
CHINESE_WU_4, /*雾*/
CHINESE_MAI, /*霾*/
CHINESE_YU, /*雨*/
CHINESE_XIAO, /*小*/
CHINESE_ZHONG, /*中*/
CHINESE_DA, /*大*/
CHINESE_BAO, /*暴*/
CHINESE_DUO, /*多*/
CHINESE_YUN, /*云*/
CHINESE_YIN, /*阴*/
CHINESE_C, /*℃*/
CHINESE_BEI, /*北*/
CHINESE_JING, /*京*/
CHINESE_NAN, /*南*/
CHINESE_QIN, /*钦*/
CHINESE_ZHOU, /*州*/
CHINESE_NING, /*宁*/
CHINESE_WU_2, /*梧*/
}CHINESE_DATA_CODE;
/*需要与compare_location地点顺序一致,判断代码中通过遍历compare_location字符串来判断对应的区域代号*/
typedef enum
{
BEIJING = 0,
QINZHOU,
NANJING,
NANNING,
WUZHOU
}WEATHER_LOCATION_CODE;
/*部分天气图标数组下标*/
typedef enum
{
WEATHER_DUOYUN = 0, /*多云*/
WEATHER_QINGTIAN, /*晴天*/
WEATHER_YINTIAN, /*阴天*/
WEATHER_WEIZHI, /*未知(这里未知代表未知天气或者未知天气图标)*/
WEATHER_MAI, /*霾*/
WEATHER_WU, /*雾*/
WEATHER_XIAOYU, /*小雨*/
WEATHER_ZHONGYU, /*中雨*/
WEATHER_DAYU, /*大雨*/
WEATHER_BAOYU, /*暴雨*/
}WEATHER_CODE;
/*用于处理城市数据结构体*/
typedef struct
{
WEATHER_LOCATION_CODE location_code; /*地点对应代号值*/
WEATHER_CODE weather_code; /*天气对应代号值*/
int8_t temperature; /*温度*/
}weather_struct;
/*用于调试日志ESP_LOGE*/
static const char *Wifi_Tag = "wifi_sta";
static const char *Http_Tag = "Http_Task";
static uint8_t wifi_retry_count = 0; /*wifi重连次数*/
static bool get_time_flag = false; /*是否需要重新获取时间(断网重连)*/
static bool get_weather_flag = false; /*是否可以创建获取天气任务*/
static bool wifi_connect_status = false; /*是否连接上WiFi(获取到WiFi的AP即可,无论该WiFi是否有网)*/
static uint8_t current_num = 0; /*当前数字,不同数字代表不同城市,这里用来遍历不同地点的url配置*/
/*
注意:添加抓取地点时,需要同时在location和compare_location中添加
地名只允许两个字地名,未进行不同长度地名显示处理
*/
const char *const location[5] = {
"beijing", "qinzhou", "nanjing", "nanning", "wuzhou" /*抓取地点*/
};
const char compare_location[] = "北京钦州南京南宁梧州"; /*用于比较抓取到的地点*/
time_struct oled_time = { /*显示时间结构体默认值*/
.year = 2019,
.month = 8,
.day = 30,
.hour = 10,
.minute = 11,
.second = 12,
};
weather_struct oled_weather = { /*当地天气代号结构体,默认值*/
.location_code = BEIJING,
.temperature = 25,
.weather_code = WEATHER_WEIZHI,
};
/*抓取天气信息任务*/
static void Http_Clinet_Weather_Task(void *pvParameters);
/*抓取时间信息任务*/
static void Http_Clinet_Time_Task(void *pvParameters);
/*主要运行的任务*/
static void Main_Task(void *pvParameters);
/*wifi事件处理函数*/
static void Event_Handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data);
/*主要程序入口函数*/
static void Program_Entry(void);
/*时间处理显示到oled函数*/
static void Timer_Deal_Func(void);
/*月份处理函数*/
static void February_Deal(void); /*二月处理*/
static bool Is_Leap_Year(void); /*是否为闰年*/
/*天气信息处理显示到oled函数*/
static void Weather_Data_Deal(const char *location_name, const uint8_t weather_code, const int8_t weather_temperature);
/*显示温度函数*/
static void OLED_Show_Temperature(void);
void app_main(void)
{
OLED_Init(); /*OLED初始化*/
/*默认显示数据*/
/*年月日时分秒*/
OLED_ShowNum(56, 3, oled_time.hour, 2, 16);
OLED_ShowChar(72, 3, ':', 16);
OLED_ShowNum(80, 3, oled_time.minute, 2, 16);
OLED_ShowChar(96, 3, ':', 16);
OLED_ShowNum(104, 3, oled_time.second, 2, 16);
OLED_ShowNum(54, 7, oled_time.year, 4, 8);
OLED_ShowChar(80, 7, '/', 8);
OLED_ShowChar(104, 7, '/', 8);
OLED_ShowNum(88, 7, oled_time.month, 2, 8);
OLED_ShowNum(112, 7, oled_time.day, 2, 8);
/*地点*/
OLED_ShowCHinese(48, 0, CHINESE_BEI);
OLED_ShowCHinese(64, 0, CHINESE_JING);
OLED_ShowChar(84, 0, ':', 16);
/*天气情况*/
OLED_ShowCHinese(96, 0, CHINESE_QING);
OLED_ShowCHinese(112, 0, CHINESE_TIAN);
/*图标*/
OLED_DrawBMP(0, 0, 48, 48, (uint8_t*)weather_logo[oled_weather.weather_code]);
/*温度*/
OLED_Show_Temperature();
/*1S定时器初始化*/
Timer_Init(1000000);
/*一直运行的任务*/
xTaskCreate(Main_Task, "oled_timer_client", 8096, NULL, 2, NULL);
/*主要程序入口*/
Program_Entry();
}
/**
* @brief 获取时间任务
*
* @param pvParameters 未使用传参
*/
static void Http_Clinet_Time_Task(void *pvParameters)
{
get_time_flag = true; /*允许再次创建抓取时间任务*/
cJSON *cjson_root = NULL;
cJSON *cjson_child = NULL;
char time_array[TIME_DATA_LEN_MAX] = {0};
char temp_year_arry[4];
char temp_time_arry[2];
int data_len = 0;
esp_http_client_config_t config_time =
{
.url = "http://quan.suning.com/getSysTime.do",
};
esp_http_client_handle_t client_time = esp_http_client_init(&config_time); /*创建http客户端句柄*/
esp_http_client_set_method(client_time, HTTP_METHOD_GET); /*设置http请求方法为GET*/
esp_err_t err = esp_http_client_open(client_time, 0);
if(ESP_OK != err)
{
ESP_LOGE(Http_Tag, "Failed to open HTTP connection: %s", esp_err_to_name(err));
}
else
{
data_len = esp_http_client_fetch_headers(client_time);
if(data_len < 0)
{
ESP_LOGE(Http_Tag, "HTTP client_weather fetch headers failed!");
}
else
{
int data_read = esp_http_client_read_response(client_time, time_array, 250);
if(data_read >= 0)
{
/*解析数据*/
cjson_root = cJSON_Parse(time_array); /*获取解析根*/
cjson_child = cJSON_GetObjectItem(cjson_root, "sysTime2"); /*解析的键results*/
if(NULL!= cjson_child)
{
get_time_flag = false; /*不允许再次抓取任务*/
ESP_LOGI(Http_Tag, "北京时间:%s", cjson_child->valuestring);
/*年-月-日*/
memcpy(temp_year_arry, cjson_child->valuestring, 4); /*前4个为年*/
oled_time.year = atoi(temp_year_arry);
memcpy(temp_time_arry, (cjson_child->valuestring) + 5, 2); /*第6、7个数据为月*/
oled_time.month = atoi(temp_time_arry);
memcpy(temp_time_arry, (cjson_child->valuestring) + 8, 2);
oled_time.day = atoi(temp_time_arry);
/*时:分:秒*/
memcpy(temp_time_arry, (cjson_child->valuestring) + 11, 2);
oled_time.hour = atoi(temp_time_arry);
memcpy(temp_time_arry, (cjson_child->valuestring) + 14, 2);
oled_time.minute = atoi(temp_time_arry);
memcpy(temp_time_arry, (cjson_child->valuestring) + 17, 2);
oled_time.second = atoi(temp_time_arry);
ESP_LOGI(Http_Tag, "%04d-%02d-%02d %02d:%02d:%02d", oled_time.year, oled_time.month, oled_time.day,
oled_time.hour, oled_time.minute, oled_time.second);
OLED_ShowNum(56, 3, oled_time.hour, 2, 16);
OLED_ShowNum(80, 3, oled_time.minute, 2, 16);
OLED_ShowNum(104, 3, oled_time.second, 2, 16);
OLED_ShowNum(54, 7, oled_time.year, 4, 8);
OLED_ShowChar(80, 7, '/', 8);
OLED_ShowChar(104, 7, '/', 8);
OLED_ShowNum(88, 7, oled_time.month, 2, 8);
OLED_ShowNum(112, 7, oled_time.day, 2, 8);
}
}
else
{
ESP_LOGE(Http_Tag, "Failed to read response");
}
}
}
esp_http_client_close(client_time); /*关闭连接*/
esp_http_client_cleanup(client_time); /*释放资源*/
cJSON_Delete(cjson_root); /*删除cjson_root结构体*/
ESP_LOGE(Http_Tag, "删除本次任务(时间获取)");
vTaskDelete(NULL); /*将自己删除*/
}
/**
* @brief 获取天气任务
*
* @param pvParameters 未使用传参
*/
static void Http_Clinet_Weather_Task(void *pvParameters)
{
get_weather_flag = 1;
cJSON *cjson_root = NULL;
cJSON *cjson_results = NULL;
cJSON *cjson_now = NULL;
cJSON *cjson_temperature = NULL;
cJSON *cjson_weather = NULL;
cJSON *cjson_code = NULL;
cJSON *cjson_location = NULL;
cJSON *cjson_name = NULL;
char weather_array[WEATHER_DATA_LEN_MAX] = {0};
char url[100] = "https://api.seniverse.com/v3/weather/now.json?key=SXadqEfe17oFmTEg_&location=";
strcat(url, location[current_num]);
strcat(url, (char *)"&language=zh-Hans&unit=c");
esp_http_client_config_t config_weather =
{
.url = url,
};
esp_http_client_handle_t client_weather = esp_http_client_init(&config_weather);
esp_http_client_set_method(client_weather, HTTP_METHOD_GET);
esp_err_t err = esp_http_client_open(client_weather, 0);
if(ESP_OK != err)
{
ESP_LOGE(Http_Tag, "Failed to open HTTP connection: %s", esp_err_to_name(err));
}
else
{
int data_len = esp_http_client_fetch_headers(client_weather);
if(data_len < 0)
{
ESP_LOGE(Http_Tag, "HTTP client_weather fetch headers failed!");
}
else
{
int data_read = esp_http_client_read_response(client_weather, weather_array, WEATHER_DATA_LEN_MAX);
if(data_read >= 0)
{
/*一些调试过程中未处理的数据*/
// ESP_LOGI(Http_Tag, "HTTP GET Status = %d, content_length = %lld",
// esp_http_client_get_status_code(client_weather),
// esp_http_client_get_content_length(client_weather));
// printf("data:%s", weather_array);
// ESP_LOGI(Http_Tag, "data:%s", weather_array);
/*解析数据*/
cjson_root = cJSON_Parse(weather_array); /*获取解析根*/
cjson_results = cJSON_GetObjectItem(cjson_root, "results"); /*解析的键results*/
cjson_now = cJSON_GetArrayItem(cjson_results, 0); /*键results数组第0个值*/
cjson_now = cJSON_GetObjectItem(cjson_now, "now"); /*键results数组第0个值中的键now*/
cjson_temperature = cJSON_GetObjectItem(cjson_now, "temperature");
cjson_weather = cJSON_GetObjectItem(cjson_now, "text");
cjson_code = cJSON_GetObjectItem(cjson_now, "code");
cjson_location = cJSON_GetArrayItem(cjson_results, 0); /*键results数组第0个值*/
cjson_location = cJSON_GetObjectItem(cjson_location, "location"); /*键results数组第0个值中的键location*/
cjson_name = cJSON_GetObjectItem(cjson_location, "name");
if(NULL!= cjson_temperature)
ESP_LOGI(Http_Tag, "%s温度:%s",cjson_name->valuestring, cjson_temperature->valuestring);
if(NULL != cjson_weather)
ESP_LOGI(Http_Tag, "%s天气:%s",cjson_name->valuestring, cjson_weather->valuestring);
Weather_Data_Deal(cjson_name->valuestring, atoi(cjson_code->valuestring), atoi(cjson_temperature->valuestring));
}
else
{
ESP_LOGE(Http_Tag, "Failed to read response");
}
}
}
esp_http_client_close(client_weather); /*关闭连接*/
esp_http_client_cleanup(client_weather); /*记得调用该函数清空内存(释放资源)*/
cJSON_Delete(cjson_root); /*释放内存,删除cJSON实体和所有子实体*/
ESP_LOGE(Http_Tag, "删除本次任务(天气获取)");
get_weather_flag = true; /*允许再次创建任务*/
vTaskDelete(NULL);
}
/**
* @brief
*
* @param arg 传递的参数
* @param event_base 基本事件
* @param event_id 事件ID
* @param event_data 事件数据
*/
static void Event_Handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if(WIFI_EVENT == event_base)
{
// wifi_event_sta_connected_t *wifi_sta_event = (wifi_event_sta_connected_t *)event_data;
switch (event_id)
{
case WIFI_EVENT_STA_START:
ESP_LOGI(Wifi_Tag, "connect to AP:%s ", WIFI_STA_NAME);
/*
成功连接到目标AP后产生WIFI_EVENT_STA_CONNECTED,失败则WIFI_EVENT_STA_DISCONNECTED;
中间省略,具体参考官网;若IP地址成功获取 产生IP_EVENT_STA_GOT_IP事件
*/
esp_wifi_connect();
break;
case WIFI_EVENT_STA_CONNECTED:
ESP_LOGI(Wifi_Tag, "connected");
wifi_retry_count = 0;
// wifi_sta_event = (wifi_event_sta_connected_t *)event_data;
// memcpy(sta_bssid, wifi_sta_event->bssid, 6);
// memcpy(sta_ssid, wifi_sta_event->ssid, wifi_sta_event->ssid_len);
// sta_ssid_len = wifi_sta_event->ssid_len;
break;
case WIFI_EVENT_STA_DISCONNECTED:
// ESP_LOGI(Wifi_Tag, "掉线了 ");
get_weather_flag = false; /*允许下次连上网后重新打开获取天气任务*/
wifi_connect_status = false; /*不允许创建抓取时间任务*/
wifi_retry_count++;
ESP_LOGI(Wifi_Tag, "The %dth:retry to connect to AP:%s ", wifi_retry_count, WIFI_STA_NAME);
esp_wifi_connect();
break;
default:
break;
}
}
else if(IP_EVENT == event_base)
{
ip_event_got_ip_t *ip_event = (ip_event_got_ip_t *)event_data;
/*连接上网后*/
if(IP_EVENT_STA_GOT_IP == event_id)
{
ESP_LOGI(Wifi_Tag, "got ip:" IPSTR, IP2STR(&ip_event->ip_info.ip));
wifi_retry_count = 0;
if(!get_weather_flag)
xTaskCreate(Http_Clinet_Weather_Task, "http_client_weather", 8192, NULL, 3, NULL); /*创建抓取天气任务*/
if(!get_time_flag) /*断网重连后会执行*/
xTaskCreate(Http_Clinet_Time_Task, "http_client_time", 8192, NULL, 3, NULL); /*创建抓取时间任务*/
wifi_connect_status = true;
}
}
return;
}
static void Program_Entry(void)
{
ESP_ERROR_CHECK(nvs_flash_init()); /*初始化默认的NVS(非易失性存储)分区*/
ESP_ERROR_CHECK(esp_netif_init()); /*创建LwIP核心任务,并初始化LwIP相关工作*/
ESP_ERROR_CHECK(esp_event_loop_create_default()); /*创建默认事件循环*/
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta(); /*创建有 TCP/IP 堆栈的默认网络接口实例绑定 station */
assert(sta_netif);
/*
esp_event_handler_register:参数1:要为其注册处理程序的事件的基本 ID 参数2:要为其注册处理程序的事件的 ID
参数3:调度事件时调用的处理程序函数 参数4:数据,除了事件数据之外,在调用处理程序时传递给处理程序
IP_EVENT_STA_GOT_IP:当 DHCP 客户端成功从 DHCP 服务器获取 IPV4 地址或 IPV4 地址发生改变时,将引发此事件
*/
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &Event_Handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &Event_Handler, NULL));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg)); /*创建 Wi-Fi 驱动程序任务,并初始化 Wi-Fi 驱动程序。*/
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); /*将 Wi-Fi 模式配置为 station 模式*/
wifi_config_t wifi_config=
{
.sta =
{
.ssid = WIFI_STA_NAME, /*WiFi名*/
.password = WIFI_STA_PASSWORD, /*WiFi密码*/
.bssid_set = 0, /*对于站位配置,bssid_set需要为0;并且只有当用户需要检查AP的MAC地址时,它才需要为1。*/
}
};
ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); /*设置STA的配置*/
ESP_ERROR_CHECK( esp_wifi_start() ); /*启动wifi驱动程序 会产生WIFI_EVENT_STA_START 事件*/
ESP_LOGI(Wifi_Tag, "wifi init sta finished!");
}
/**
* @brief 一直运行的任务
*
* @param pvParameters
*/
static void Main_Task(void *pvParameters)
{
uint8_t i = 0;
bool blink_flag = false;
while(1)
{
if(time_1s_falg)
{
blink_flag = !blink_flag;
/*如果联网状态则抓取地点的天气*/
if((true == get_weather_flag) && (0 == i % WEATHER_GET_INTERVAL_TIME_S) && (true == wifi_connect_status))
{
current_num++;
if(current_num > 4)
current_num = 0;
i = 0;
get_weather_flag = false;
xTaskCreate(Http_Clinet_Weather_Task, "http_client_weather", 8192, NULL, 3, NULL);
}
/*如果本次连上Wifi之后没抓取过时间*/
if((true == get_time_flag) && (true == wifi_connect_status))
{
xTaskCreate(Http_Clinet_Time_Task, "http_client_time", 8192, NULL, 3, NULL);
}
time_1s_falg = false;
oled_time.second++;
i++;
Timer_Deal_Func();
ESP_LOGI(Http_Tag, "%04d-%02d-%02d %02d:%02d:%02d", oled_time.year, oled_time.month, oled_time.day,
oled_time.hour, oled_time.minute, oled_time.second);
}
if(blink_flag)
{
OLED_ShowChar(72, 3, ':', 16);
OLED_ShowChar(96, 3, ':', 16);
}
else
{
OLED_ShowChar(72, 3, ' ', 16);
OLED_ShowChar(96, 3, ' ', 16);
}
vTaskDelay(10/portTICK_PERIOD_MS);
}
vTaskDelete(NULL); /*将自己删除*/
}
/**
* @brief 时间处理函数
*
*/
static void Timer_Deal_Func(void)
{
if(oled_time.second >= 60)
{
oled_time.second = 0;
oled_time.minute++;
if(oled_time.minute >= 60)
{
oled_time.minute = 0;
oled_time.hour++;
if(oled_time.hour >= 24)
{
/*这里可以重新抓取一下时间*/
oled_time.day++;
oled_time.hour = 0;
switch(oled_time.month)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
/*处理大月*/
if(oled_time.day > 31)
{
oled_time.month++;
oled_time.day = 1;
}
break;
case 2:
/*处理平月*/
February_Deal();
break;
case 4:
case 6:
case 9:
case 11:
/*处理小月*/
if(oled_time.day > 30)
{
oled_time.month++;
oled_time.day = 1;
}
break;
default:
break;
}
if(oled_time.month > 12)
{
oled_time.month = 1;
oled_time.year++;
OLED_ShowNum(54, 7, oled_time.year, 4, 8);
}
OLED_ShowNum(88, 7, oled_time.month, 2, 8);
OLED_ShowNum(112, 7, oled_time.day, 2, 8);
}
OLED_ShowNum(56, 3, oled_time.hour, 2, 16);
}
OLED_ShowNum(80, 3, oled_time.minute, 2, 16); /*分钟更新*/
}
OLED_ShowNum(104, 3, oled_time.second, 2, 16);
}
/**
* @brief 判断是否未润连
*
* @return true 闰年
* @return false 平年
*/
static bool Is_Leap_Year(void)
{
return (0 == oled_time.year % 4 && 0 != oled_time.year % 100) || (0 == oled_time.year % 400);
}
/**
* @brief 二月处理函数
*
*/
static void February_Deal(void)
{
if(Is_Leap_Year())
{
/*闰年处理*/
if(oled_time.day > 29)
{
oled_time.month++;
oled_time.day = 1;
}
}
else
{
/*平年处理*/
if(oled_time.day > 28)
{
oled_time.month++;
oled_time.day = 1;
}
}
}
/**
* @brief 处理地点和温度
*
* @param location_name 地点字符串
* @param weather_code 心知天气API返回的天气代号,不同于上述自己设定的天气代号
* @param weather_temperature 当地的温度
*/
static void Weather_Data_Deal(const char *location_name, const uint8_t weather_code, const int8_t weather_temperature)
{
/*处理天气图标以及显示天气状况*/
uint8_t i = 0;
uint8_t j = 0;
switch (weather_code)
{
case 0:
case 1:
case 2:
case 3:
/*晴天*/
oled_weather.weather_code = WEATHER_QINGTIAN;
OLED_ShowCHinese(96, 0, CHINESE_QING);
OLED_ShowCHinese(112, 0, CHINESE_TIAN);
break;
case 4:
/*多云*/
oled_weather.weather_code = WEATHER_DUOYUN;
OLED_ShowCHinese(96, 0, CHINESE_DUO);
OLED_ShowCHinese(112, 0, CHINESE_YUN);
break;
case 9:
/*阴*/
oled_weather.weather_code = WEATHER_YINTIAN;
OLED_ShowCHinese(96, 0, CHINESE_YIN);
OLED_ShowCHinese(112, 0, CHINESE_TIAN);
break;
case 13:
/*小雨*/
oled_weather.weather_code = WEATHER_XIAOYU;
OLED_ShowCHinese(96, 0, CHINESE_XIAO);
OLED_ShowCHinese(112, 0, CHINESE_YU);
break;
case 14:
/*中雨*/
oled_weather.weather_code = WEATHER_ZHONGYU;
OLED_ShowCHinese(96, 0, CHINESE_ZHONG);
OLED_ShowCHinese(112, 0, CHINESE_YU);
break;
case 15:
/*大雨*/
oled_weather.weather_code = WEATHER_DAYU;
OLED_ShowCHinese(96, 0, CHINESE_DA);
OLED_ShowCHinese(112, 0, CHINESE_YU);
break;
case 16:
/*暴雨*/
oled_weather.weather_code = WEATHER_BAOYU;
OLED_ShowCHinese(96, 0, CHINESE_BAO);
OLED_ShowCHinese(112, 0, CHINESE_YU);
break;
case 30:
/*雾*/
oled_weather.weather_code = WEATHER_WU;
OLED_ShowCHinese(96, 0, CHINESE_WU_4);
OLED_ShowCHinese(112, 0, CHINESE_TIAN);
break;
case 31:
/*霾*/
oled_weather.weather_code = WEATHER_MAI;
OLED_ShowCHinese(96, 0, CHINESE_MAI);
OLED_ShowCHinese(112, 0, CHINESE_TIAN);
break;
default:
break;
}
OLED_DrawBMP(0, 0, 48, 48, (uint8_t*)weather_logo[oled_weather.weather_code]);
/*温度处理*/
oled_weather.temperature = weather_temperature;
OLED_Show_Temperature();
/*地点处理*/
for(i = 0, j = 0;i < strlen(compare_location); i += 2 * 3, j++) /*注意utf-8编码中:汉字占三个字节*/
{
if(0 == strncmp(location_name, compare_location + i , 2 * 3)) /*这里同样也要注意,比较汉字长度为6*/
{
oled_weather.location_code =j;
}
}
switch (oled_weather.location_code)
{
case BEIJING:
OLED_ShowCHinese(48, 0, CHINESE_BEI);
OLED_ShowCHinese(64, 0, CHINESE_JING);
break;
case NANJING:
OLED_ShowCHinese(48, 0, CHINESE_NAN);
OLED_ShowCHinese(64, 0, CHINESE_JING);
break;
case NANNING:
OLED_ShowCHinese(48, 0, CHINESE_NAN);
OLED_ShowCHinese(64, 0, CHINESE_NING);
break;
case QINZHOU:
OLED_ShowCHinese(48, 0, CHINESE_QIN);
OLED_ShowCHinese(64, 0, CHINESE_ZHOU);
break;
case WUZHOU:
OLED_ShowCHinese(48, 0, CHINESE_WU_2);
OLED_ShowCHinese(64, 0, CHINESE_ZHOU);
break;
default:
/*后续可加入显示错误选项*/
break;
OLED_ShowChar(84, 0, ':', 16);
}
}
/**
* @brief 温度显示处理函数
*
*/
static void OLED_Show_Temperature(void)
{
/*温度 纵轴坐标暂定选6*/
/*大0*/
OLED_DrawBMP(0, 6, 48, 16, (uint8_t*)clear_local); /*清除温度区域内容*/
uint8_t len = (abs(oled_weather.temperature) / 10) == 0 ? 0 : 1;
uint8_t pos = 4;
uint8_t offset = 0;
if(oled_weather.temperature < 0)
{
offset = 0;
/*负号显示*/
if(len == 0)
OLED_ShowChar(0 , 6, '-', 16);
else
OLED_ShowChar(0 + pos, 6, '-', 16);
}
if(len == 1)
{
OLED_ShowNum(8 + offset, 6, abs(oled_weather.temperature), len + 1, 16);
OLED_ShowCHinese(24 + offset, 6, CHINESE_C);
}
else
{
OLED_ShowNum(8 + pos + offset, 6, abs(oled_weather.temperature), len + 1, 16);
OLED_ShowCHinese(16 + offset + pos, 6, CHINESE_C);
}
}
4、 完整程序链接
百度网盘链接:https://pan.baidu.com/s/1z4TatJjKScawEId91XgApQ
提取码:1234