前言
本文基于PlatformIO,使用freeRTOS写的简化版天气时钟代码如下,使用了3个task,分别为天气滚动栏目,时间栏目,动画栏目。使用互斥锁避免多个task同时对spi总线读写导致程序崩溃问题。代码如下
#include <TFT_eSPI.h>
#include <SPI.h>
#include <TJpg_Decoder.h>
#include <TimeLib.h> //配置文件
#include "weatherNum/weatherNum.h" //天气图库
#define timeY 82
#include "Animate/img/hutao.h"
#include<thread>
/* *****************************************************************
* 字库、图片库
* *****************************************************************/
#include "font/ZdyLwFont_20.h" //字体库
#include "font/timeClockFont.h" //字体库
#include "font/Chancery_L_20.h" //字体库
#include "img/temperature.h" //温度图标
#include "img/humidity.h" //湿度图标
#include "font/KT_20.h"
#include "soc/rtc_wdt.h"
/* *****************************************************************
* 函数声明
* *****************************************************************/
SemaphoreHandle_t xMutex; // 互斥锁句柄
void digitalClockDisplay(int *Hour_sign, int *Minute_sign, int* Second_sign);
//void refresh_AnimatedImage(int Animate_key,int Amimate_reflash_Time);
void scrollBanner(int currentIndex,String * str );
void weaterData(); // 天气信息写到屏幕上
//void imgAnim(int Animate_key,const uint8_t **Animate_value, uint32_t *Animate_size);
//----------------------------------------------------
// LCD屏幕相关设置
TFT_eSPI tft = TFT_eSPI(); // 引脚请自行配置tft_espi库中的 User_Setup.h文件
TFT_eSprite clk = TFT_eSprite(&tft);
#define LCD_BL_PIN 2 // LCD背光引脚
uint16_t bgColor = 0x0000;
uint16_t pinkColor = tft.color565(255, 174, 201);
uint16_t zongseColor = tft.color565(128, 64, 64);
uint16_t whiteColor = tft.color565(245, 246, 247);
uint16_t fhColor = tft.color565(253, 99, 139);
uint16_t blueColor = tft.color565(175, 221, 224);
// 其余状态标志位
int LCD_Rotation = 0; // LCD屏幕方向
int LCD_BL_PWM = 50; // 屏幕亮度0-100,默认50
/*** Component objects ***/
WeatherNum wrat;
volatile int tempnum = 0; // 温度百分比
volatile int huminum = 0; // 湿度百分比
volatile int tempcol = 0xffff; // 温度显示颜色
volatile int humicol = 0xffff; // 湿度显示颜色
// 显示页面
/* *****************************************************************
* 函数
* *****************************************************************/
// TFT屏幕输出函数
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap)
{
if (y >= tft.height())
return 0;
tft.pushImage(x, y, w, h, bitmap);
// Return 1 to decode next block
return 1;
}
// 进度条函数
volatile byte loadNum = 6;
void loading(byte delayTime) // 绘制进度条
{
clk.setColorDepth(8);
clk.createSprite(200, 100); // 创建窗口
clk.fillSprite(0x0000); // 填充率
clk.drawRoundRect(0, 0, 200, 16, 8, 0xFFFF); // 空心圆角矩形
clk.fillRoundRect(3, 3, loadNum, 10, 5, 0xFFFF); // 实心圆角矩形
clk.setTextDatum(CC_DATUM); // 设置文本数据
clk.setTextColor(TFT_GREEN, 0x0000);
clk.drawString("Connecting to WiFi......", 100, 40, 2);
clk.setTextColor(TFT_WHITE, 0x0000);
clk.drawRightString("v1", 180, 60, 2);
clk.pushSprite(20, 120); // 窗口位置
clk.deleteSprite();
loadNum += 1;
delay(delayTime);
}
// 湿度图标显示函数
void humidityWin()
{
clk.setColorDepth(8);
huminum = huminum / 2;
clk.createSprite(52, 6); // 创建窗口
clk.fillSprite(0x0000); // 填充率
clk.drawRoundRect(0, 0, 52, 6, 3, 0xFFFF); // 空心圆角矩形 起始位x,y,长度,宽度,圆弧半径,颜色
clk.fillRoundRect(1, 1, huminum, 4, 2, humicol); // 实心圆角矩形
clk.pushSprite(45, 222); // 窗口位置
clk.deleteSprite();
}
// 温度图标显示函数
void tempWin()
{
clk.setColorDepth(8);
clk.createSprite(52, 6); // 创建窗口
clk.fillSprite(0x0000); // 填充率
clk.drawRoundRect(0, 0, 52, 6, 3, 0xFFFF); // 空心圆角矩形 起始位x,y,长度,宽度,圆弧半径,颜色
clk.fillRoundRect(1, 1, tempnum, 4, 2, tempcol); // 实心圆角矩形
clk.pushSprite(45, 192); // 窗口位置
clk.deleteSprite();
}
// 天气信息写到屏幕上
void weaterData()
{
/***绘制相关文字***/
clk.setColorDepth(8);
clk.loadFont(ZdyLwFont_20);
// 温度
clk.createSprite(58, 24);
clk.fillSprite(bgColor);
clk.setTextDatum(CC_DATUM);
clk.setTextColor(TFT_WHITE, bgColor);
clk.drawString("21℃", 28, 13);
clk.pushSprite(100, 184);
clk.deleteSprite();
tempnum = 21.4;
tempnum = tempnum + 10;
if (tempnum < 10)
tempcol = 0x00FF;
else if (tempnum < 28)
tempcol = 0x0AFF;
else if (tempnum < 34)
tempcol = 0x0F0F;
else if (tempnum < 41)
tempcol = 0xFF0F;
else if (tempnum < 49)
tempcol = 0xF00F;
else
{
tempcol = 0xF00F;
tempnum = 50;
}
tempWin();
// 湿度
clk.createSprite(58, 24);
clk.fillSprite(bgColor);
clk.setTextDatum(CC_DATUM);
clk.setTextColor(TFT_WHITE, bgColor);
clk.drawString("73%", 28, 13);
// clk.drawString("100%",28,13);
clk.pushSprite(100, 214);
clk.deleteSprite();
// String A = sk["SD"].as<String>();
huminum = 82;
if (huminum > 90)
humicol = 0x00FF;
else if (huminum > 70)
humicol = 0x0AFF;
else if (huminum > 40)
humicol = 0x0F0F;
else if (huminum > 20)
humicol = 0xFF0F;
else
humicol = 0xF00F;
humidityWin();
// 城市名称
clk.createSprite(94, 30);
clk.fillSprite(bgColor);
clk.setTextDatum(CC_DATUM);
clk.setTextColor(TFT_WHITE, bgColor);
clk.drawString("苏州", 44, 16);
clk.pushSprite(15, 15);
clk.deleteSprite();
// PM2.5空气指数
uint16_t pm25BgColor = tft.color565(156, 202, 127); // 优
String aqiTxt = "优";
int pm25V = 53;
pm25BgColor = tft.color565(247, 219, 100); // 良
aqiTxt = "良";
clk.createSprite(56, 24);
clk.fillSprite(bgColor);
clk.fillRoundRect(0, 0, 50, 24, 4, pm25BgColor);
clk.setTextDatum(CC_DATUM);
clk.setTextColor(0x0000);
clk.drawString(aqiTxt, 25, 13);
clk.pushSprite(104, 18);
clk.deleteSprite();
// 天气图标
wrat.printfweather(170, 15, 2);
clk.unloadFont();
}
TFT_eSprite clkb = TFT_eSprite(&tft);
void scrollBanner(int currentIndex, String *scrollText)
{
if (xSemaphoreTake(xMutex, portMAX_DELAY))
{
clkb.setColorDepth(8);
clkb.loadFont(ZdyLwFont_20);
clkb.createSprite(150, 30);
clkb.fillSprite(bgColor);
clkb.setTextWrap(false);
clkb.setTextDatum(CC_DATUM);
clkb.setTextColor(TFT_WHITE, bgColor);
clkb.drawString(scrollText[currentIndex], 74, 16);
clkb.pushSprite(10, 45);
clkb.deleteSprite();
clkb.unloadFont();
xSemaphoreGive(xMutex);
}
}
// 用快速线方法绘制数字
void drawLineFont(uint32_t _x, uint32_t _y, uint32_t _num, uint32_t _size, uint32_t _color)
{
uint32_t fontSize;
const LineAtom *fontOne;
// 小号(9*14)
if (_size == 1)
{
fontOne = smallLineFont[_num];
fontSize = smallLineFont_size[_num];
// 绘制前清理字体绘制区域
tft.fillRect(_x, _y, 9, 14, TFT_BLACK);
}
// 中号(18*30)
else if (_size == 2)
{
fontOne = middleLineFont[_num];
fontSize = middleLineFont_size[_num];
// 绘制前清理字体绘制区域
tft.fillRect(_x, _y, 18, 30, TFT_BLACK);
}
// 大号(36*90)
else if (_size == 3)
{
fontOne = largeLineFont[_num];
fontSize = largeLineFont_size[_num];
// 绘制前清理字体绘制区域
tft.fillRect(_x, _y, 36, 90, TFT_BLACK);
}
else
return;
for (uint32_t i = 0; i < fontSize; i++)
{
tft.drawFastHLine(fontOne[i].xValue + _x, fontOne[i].yValue + _y, fontOne[i].lValue, _color);
}
}
// 日期刷新
void digitalClockDisplay(int *Hour_sign,int *Minute_sign,int *Second_sign)
{
// 时钟刷新,输入1强制刷新
int now_hour = hour(); // 获取小时
int now_minute = minute(); // 获取分钟
int now_second = second(); // 获取秒针
// 小时刷新
if (now_hour != *Hour_sign)
{
if (xSemaphoreTake(xMutex, portMAX_DELAY))
{
drawLineFont(20, timeY, now_hour / 10, 3, SD_FONT_WHITE);
drawLineFont(60, timeY, now_hour % 10, 3, SD_FONT_WHITE);
*Hour_sign = now_hour;
xSemaphoreGive(xMutex);
}
}
// 分钟刷新
if (now_minute != *Minute_sign)
{
if (xSemaphoreTake(xMutex, portMAX_DELAY))
{
drawLineFont(101, timeY, now_minute / 10, 3, SD_FONT_YELLOW);
drawLineFont(141, timeY, now_minute % 10, 3, SD_FONT_YELLOW);
*Minute_sign = now_minute;
xSemaphoreGive(xMutex);
}
}
// 秒针刷新
if (now_second != *Second_sign)
{
if (xSemaphoreTake(xMutex, portMAX_DELAY))
{
drawLineFont(182, timeY + 30, now_second / 10, 2, SD_FONT_WHITE);
drawLineFont(202, timeY + 30, now_second % 10, 2, SD_FONT_WHITE);
*Second_sign = now_second;
xSemaphoreGive(xMutex);
}
}
/***日期****/
if (xSemaphoreTake(xMutex, portMAX_DELAY))
{
clk.setColorDepth(8);
clk.loadFont(ZdyLwFont_20);
// 星期
clk.createSprite(58, 30);
clk.fillSprite(bgColor);
clk.setTextDatum(CC_DATUM);
clk.setTextColor(TFT_WHITE, bgColor);
clk.drawString("周一", 29, 16);
clk.pushSprite(102, 150);
clk.deleteSprite();
// 月日
clk.createSprite(95, 30);
clk.fillSprite(bgColor);
clk.setTextDatum(CC_DATUM);
clk.setTextColor(TFT_WHITE, bgColor);
clk.drawString("12月12日", 49, 16);
clk.pushSprite(5, 150);
clk.deleteSprite();
clk.unloadFont();
xSemaphoreGive(xMutex);
/***日期****/
}
}
// 切换天气 or 空气质量
void reflashBanner(void *param)
{
// UBaseType_t uxHighWaterMark;
// uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
int idx = 0;
Serial.println("reflashBanner");
Serial.println(xPortGetCoreID());
String scrollText[7] = {"实时天气 阴", "空气质量 良",
"风向 东风2级",
"今日 阴",
"最低温度 12℃",
"最高温度 20℃"};
while (1)
{
scrollBanner(idx++,scrollText);
if (idx >= 5)
idx = 0; // 回第一个
Serial.println("reflashBanner");
vTaskDelay(pdMS_TO_TICKS(5000));
// uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
// Serial.println("reflashBanner uxHighWaterMark:");
// Serial.println(uxHighWaterMark);
}
}
void refreshtime(void *param)
{
// UBaseType_t uxHighWaterMark;
// uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
int Hour_sign = 60;
int Minute_sign = 60;
int Second_sign = 60;
Serial.println("refresh time prev");
Serial.println(xPortGetCoreID());
while (1)
{
digitalClockDisplay(&Hour_sign,&Minute_sign,&Second_sign);
vTaskDelay(pdMS_TO_TICKS(100));
// uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
// Serial.println("refreshtime uxHighWaterMark:");
// Serial.println(uxHighWaterMark);
}
}
void refreshAnimatedImage(void *param)
{
int Amimate_reflash_Time = 1;
int Animate_key = -1; // 初始化图标显示帧数
const uint8_t *Animate_value; // 指向关键帧的指针
uint32_t Animate_size; // 指向关键帧大小的指针
// UBaseType_t uxHighWaterMark;
// uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
Serial.println("refreshAnimatedImage prev");
Serial.println(xPortGetCoreID());
while (1)
{
//Serial.println(millis());
//refresh_AnimatedImage(Animate_key++,Amimate_reflash_Time);
if (millis() - Amimate_reflash_Time > 100) // x ms切换一次
{
Animate_key++;
Amimate_reflash_Time = millis();
// Serial.println(Amimate_reflash_Time);
//imgAnim(Animate_key++, &Animate_value, &Animate_size);
Animate_value = hutao[Animate_key];
Animate_size = hutao_size[Animate_key];
if (xSemaphoreTake(xMutex, portMAX_DELAY))
{
TJpgDec.drawJpg(160, 160, Animate_value, Animate_size);
xSemaphoreGive(xMutex);
// Serial.println("Animate_size");
}
if (Animate_key >= 31)
{
Animate_key = -1;
}
vTaskDelay(pdMS_TO_TICKS(100));
// uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
// Serial.println("refreshAnimatedImage uxHighWaterMark:");
// Serial.println(uxHighWaterMark);
}
}
}
void func(void *param){
while(1){
if (xSemaphoreTake(xMutex, portMAX_DELAY))
{
TJpgDec.drawJpg(160, 160, hutao[1], hutao_size[1]);
vTaskDelay(100);
xSemaphoreGive(xMutex);
}
}
}
void setup()
{
xMutex = xSemaphoreCreateMutex();
Serial.begin(115200);
//disableCore0WDT();
//hw_timer_t *t = timerBegin(2, 80, true);
pinMode(LCD_BL_PIN, OUTPUT);
analogWrite(LCD_BL_PIN, 1023 - (LCD_BL_PWM * 10));
tft.begin(); /* TFT init */
tft.invertDisplay(1); // 反转所有显示颜色:1反转,0正常
tft.setRotation(LCD_Rotation);
tft.fillScreen(0x0000);
tft.setTextColor(TFT_BLACK, bgColor);
TJpgDec.setJpgScale(1);
TJpgDec.setSwapBytes(true);
TJpgDec.setCallback(tft_output);
delay(10);
while (loadNum < 194) // 让动画走完
{
loading(1);
}
TJpgDec.setJpgScale(1);
TJpgDec.setSwapBytes(true);
TJpgDec.setCallback(tft_output);
tft.fillScreen(TFT_BLACK); // 清屏
TJpgDec.drawJpg(15, 183, temperature, sizeof(temperature)); // 温度图标
TJpgDec.drawJpg(15, 213, humidity, sizeof(humidity)); // 湿度图标
weaterData();
Serial.println("weaterData");
xTaskCreatePinnedToCore(refreshAnimatedImage, "image", 2048, NULL, 1, NULL,1);
xTaskCreatePinnedToCore(reflashBanner, "scroller", 2048, NULL, 1, NULL,1);
xTaskCreatePinnedToCore(refreshtime, "time", 2048, NULL, 2, NULL,1);
//xTaskCreatePinnedToCore(func, "func", 2048, NULL, 1,NULL,1);
}
void loop()
{
}
实验结果如下
遇到的问题:在实验过程中主要遇到的问题为断言错误如下,通过添加互斥锁就能解决。
assert failed: xQueueGenericSend queue.c:832 (pxQueue->pcHead != ((void *)0) || pxQueue->u.xSemaphor
参考的文献如下: 乐鑫esp32官方文档;freertos官网;la_fe_ 的CSDN博客
百度网盘链接代码:https://pan.baidu.com/s/1WfqPu8D-bEKfIChEKYpBKw
提取码:gl9t