STM32模拟时钟
本项目实现模拟时钟显示,整点报时功能,主要由几下9部分组成:
选择平台及基础设置,选择项目模板,液晶驱动,RTC时钟,按键,蜂鸣器,mpu6050与姿态,人机交互界面,总结报告.
1,选择平台及基础设置
此项目使用开发软件用到意法半导体生态链中重要的软件之一——stm32cubemx,还有另外一个编译软件—— keilV5。通过使用stm32cubemx 软件配置外设驱动,配置液晶驱动,按键驱动,iic驱动,rtc驱动,以及单片机内部时钟配置等,通过可视化配置外设,大大提高开发效率,keilv5用于代码编写,编译,通过板载仿真器,进行仿真,烧录
2,选择项目模板
采用stm32cubemx软件生成,配置包底层驱动后,通过查看液晶屏数据数据手册,及液晶屏初始化配置代码,完成液晶屏清屏,设置显示窗口,单点显示功能,通过mpu6050数据手册,设置mpu6050初始化配置,通过定时器控制蜂鸣器引脚翻转,通过读取按键,判断键值。此项目使用freertos操作系统,创建两个线程,分别是显示输出线程和按键读取线程。
3,液晶驱动
液晶屏为串行spi接口,实际硬件连接并未接到stm32硬件spi接口,需采用软件模拟spi,为提高刷新速率,对引脚操作采用直接寄存器操作,本项目实现模拟时钟显示及数字时钟功能,为达到美观,采用表盘图片作为背景,然后,加上表盘数字显示,指针显示,年月日汉字显示,因此液晶显示需要完成指定位置图片显示,画线,数字显示,汉字显示,翻转显示等功能。
/*液晶驱动*/
#include "lcd.h"
#include "stdlib.h"
#include "spi.h"
_lcd_dev lcddev;
//画笔颜色,背景颜色
uint16_t POINT_COLOR = 0x0000,BACK_COLOR = 0xFFFF;
uint16_t DeviceCode;
/*****************************************************************************
* @name :void LCD_WR_REG(uint8_t data)
* @date :2018-08-09
* @function :Write an 8-bit command to the LCD screen
* @parameters :data:Command value to be written
* @retvalue :None
******************************************************************************/
void LCD_WR_REG(uint8_t data)
{
LCD_CS_CLR;
LCD_RS_CLR;
SPIv_WriteData(data);
LCD_CS_SET;
}
/*****************************************************************************
* @name :void LCD_WR_DATA(uint8_t data)
* @date :2018-08-09
* @function :Write an 8-bit data to the LCD screen
* @parameters :data:data value to be written
* @retvalue :None
******************************************************************************/
void LCD_WR_DATA(uint8_t data)
{
LCD_CS_CLR;
LCD_RS_SET;
SPIv_WriteData(data);
LCD_CS_SET;
}
/*****************************************************************************
* @name :void LCD_WriteReg(uint8_t LCD_Reg, uint16_t LCD_RegValue)
* @date :2018-08-09
* @function :Write data into registers
* @parameters :LCD_Reg:Register address
LCD_RegValue:Data to be written
* @retvalue :None
******************************************************************************/
void LCD_WriteReg(uint8_t LCD_Reg, uint16_t LCD_RegValue)
{
LCD_WR_REG(LCD_Reg);
LCD_WR_DATA(LCD_RegValue);
}
/*****************************************************************************
* @name :void LCD_WriteRAM_Prepare(void)
* @date :2018-08-09
* @function :Write GRAM
* @parameters :None
* @retvalue :None
******************************************************************************/
void LCD_WriteRAM_Prepare(void)
{
LCD_WR_REG(lcddev.wramcmd);
}
/*****************************************************************************
* @name :void Lcd_WriteData_16Bit(uint16_t Data)
* @date :2018-08-09
* @function :Write an 16-bit command to the LCD screen
* @parameters :Data:Data to be written
* @retvalue :None
******************************************************************************/
void Lcd_WriteData_16Bit(uint16_t Data)
{
LCD_CS_CLR;
LCD_RS_SET;
SPIv_WriteData(Data>>8);
SPIv_WriteData(Data);
LCD_CS_SET;
}
/*****************************************************************************
* @name :void LCD_DrawPoint(uint16_t x,uint16_t y)
* @date :2018-08-09
* @function :Write a pixel data at a specified location
* @parameters :x:the x coordinate of the pixel
y:the y coordinate of the pixel
* @retvalue :None
******************************************************************************/
void LCD_DrawPoint(uint16_t x,uint16_t y)
{
LCD_SetCursor(x,y);//设置光标位置
Lcd_WriteData_16Bit(POINT_COLOR);
}
/*****************************************************************************
* @name :void LCD_Clear(uint16_t Color)
* @date :2018-08-09
* @function :Full screen filled LCD screen
* @parameters :color:Filled color
* @retvalue :None
******************************************************************************/
void LCD_Clear(uint16_t Color)
{
unsigned int i,m;
LCD_SetWindows(0,0,lcddev.width-1,lcddev.height-1);
LCD_CS_CLR;
LCD_RS_SET;
for(i=0; i<lcddev.height; i++)
{
for(m=0; m<lcddev.width; m++)
{
SPIv_WriteData(Color>>8);
SPIv_WriteData(Color);
}
}
LCD_CS_SET;
}
/*****************************************************************************
* @name :void LCD_RESET(void)
* @date :2018-08-09
* @function :Reset LCD screen
* @parameters :None
* @retvalue :None
******************************************************************************/
void LCD_RESET(void)
{
LCD_RST_CLR;
HAL_Delay (200);
LCD_RST_SET;
HAL_Delay(200);
}
/*****************************************************************************
* @name :void LCD_Init(void)
* @date :2018-08-09
* @function :Initialization LCD screen
* @parameters :None
* @retvalue :None
******************************************************************************/
void LCD_Init(void)
{
LCD_RESET(); //LCD 复位
//************* ST7789初始化**********//
LCD_WR_REG(0x36);
LCD_WR_DATA(0x00);
LCD_WR_REG(0x3A);
LCD_WR_DATA(0x05);
LCD_WR_REG(0xB2);
LCD_WR_DATA(0x0C);
LCD_WR_DATA(0x0C);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x33);
LCD_WR_DATA(0x33);
LCD_WR_REG(0xB7);
LCD_WR_DATA(0x35);
LCD_WR_REG(0xBB);
LCD_WR_DATA(0x19);
LCD_WR_REG(0xC0);
LCD_WR_DATA(0x2C);
LCD_WR_REG(0xC2);
LCD_WR_DATA(0x01);
LCD_WR_REG(0xC3);
LCD_WR_DATA(0x12);
LCD_WR_REG(0xC4);
LCD_WR_DATA(0x20);
LCD_WR_REG(0xC6);
LCD_WR_DATA(0x0F);
LCD_WR_REG(0xD0);
LCD_WR_DATA(0xA4);
LCD_WR_DATA(0xA1);
LCD_WR_REG(0xE0);
LCD_WR_DATA(0xD0);
LCD_WR_DATA(0x04);
LCD_WR_DATA(0x0D);
LCD_WR_DATA(0x11);
LCD_WR_DATA(0x13);
LCD_WR_DATA(0x2B);
LCD_WR_DATA(0x3F);
LCD_WR_DATA(0x54);
LCD_WR_DATA(0x4C);
LCD_WR_DATA(0x18);
LCD_WR_DATA(0x0D);
LCD_WR_DATA(0x0B);
LCD_WR_DATA(0x1F);
LCD_WR_DATA(0x23);
LCD_WR_REG(0xE1);
LCD_WR_DATA(0xD0);
LCD_WR_DATA(0x04);
LCD_WR_DATA(0x0C);
LCD_WR_DATA(0x11);
LCD_WR_DATA(0x13);
LCD_WR_DATA(0x2C);
LCD_WR_DATA(0x3F);
LCD_WR_DATA(0x44);
LCD_WR_DATA(0x51);
LCD_WR_DATA(0x2F);
LCD_WR_DATA(0x1F);
LCD_WR_DATA(0x1F);
LCD_WR_DATA(0x20);
LCD_WR_DATA(0x23);
LCD_WR_REG(0x21);
LCD_WR_REG(0x11);
//Delay (120);
LCD_WR_REG(0x29);
LCD_direction(USE_HORIZONTAL);//设置LCD显示方向
HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_SET);
LCD_Clear(WHITE);//清全屏白色
}
/*****************************************************************************
* @name :void LCD_SetWindows(uint16_t xStar, uint16_t yStar,uint16_t xEnd,uint16_t yEnd)
* @date :2018-08-09
* @function :Setting LCD display window
* @parameters :xStar:the bebinning x coordinate of the LCD display window
yStar:the bebinning y coordinate of the LCD display window
xEnd:the endning x coordinate of the LCD display window
yEnd:the endning y coordinate of the LCD display window
* @retvalue :None
******************************************************************************/
void LCD_SetWindows(uint16_t xStar, uint16_t yStar,uint16_t xEnd,uint16_t yEnd)
{
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA((xStar+lcddev.xoffset)>>8);
LCD_WR_DATA(xStar+lcddev.xoffset);
LCD_WR_DATA((xEnd+lcddev.xoffset)>>8);
LCD_WR_DATA(xEnd+lcddev.xoffset);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA((yStar+lcddev.yoffset)>>8);
LCD_WR_DATA(yStar+lcddev.yoffset);
LCD_WR_DATA((yEnd+lcddev.yoffset)>>8);
LCD_WR_DATA(yEnd+lcddev.yoffset);
LCD_WriteRAM_Prepare(); //开始写入GRAM
}
/*****************************************************************************
* @name :void LCD_SetCursor(uint16_t Xpos, uint16_t Ypos)
* @date :2018-08-09
* @function :Set coordinate value
* @parameters :Xpos:the x coordinate of the pixel
Ypos:the y coordinate of the pixel
* @retvalue :None
******************************************************************************/
void LCD_SetCursor(uint16_t Xpos, uint16_t Ypos)
{
LCD_SetWindows(Xpos,Ypos,Xpos,Ypos);
}
/*****************************************************************************
* @name :void LCD_direction(uint8_t direction)
* @date :2018-08-09
* @function :Setting the display direction of LCD screen
* @parameters :direction:0-0 degree
1-90 degree
2-180 degree
3-270 degree
* @retvalue :None
******************************************************************************/
void LCD_direction(uint8_t direction)
{
lcddev.setxcmd=0x2A;
lcddev.setycmd=0x2B;
lcddev.wramcmd=0x2C;
switch(direction) {
case 0:
lcddev.width=LCD_W;
lcddev.height=LCD_H;
lcddev.xoffset=0;
lcddev.yoffset=0;
LCD_WriteReg(0x36,0);//BGR==1,MY==0,MX==0,MV==0
break;
case 1:
lcddev.width=LCD_H;
lcddev.height=LCD_W;
lcddev.xoffset=0;
lcddev.yoffset=0;
LCD_WriteReg(0x36,(1<<6)|(1<<5));//BGR==1,MY==1,MX==0,MV==1
break;
case 2:
lcddev.width=LCD_W;
lcddev.height=LCD_H;
lcddev.xoffset=0;
lcddev.yoffset=80;
LCD_WriteReg(0x36,(1<<6)|(1<<7));//BGR==1,MY==0,MX==0,MV==0
break;
case 3:
lcddev.width=LCD_H;
lcddev.height=LCD_W;
lcddev.xoffset=80;
lcddev.yoffset=0;
LCD_WriteReg(0x36,(1<<7)|(1<<5));//BGR==1,MY==1,MX==0,MV==1
break;
default:
break;
}
}
4,RTC时钟
由于硬件没有外部低速时钟,内部低速时钟温漂大等原因,决定采用由仿真器提供8M方波信号给单片机作为时钟源,分频后为rtc提供时钟源,stm32cubemx配置rtc时,打开日历功能即可。
RTC初始化
/* RTC init function */
void MX_RTC_Init(void)
{
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
/** Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN Check_RTC_BKUP */
/* USER CODE END Check_RTC_BKUP */
/** Initialize RTC and set the Time and Date
*/
sTime.Hours = 0x12;
sTime.Minutes = 0x0;
sTime.Seconds = 0x0;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
DateToUpdate.WeekDay = RTC_WEEKDAY_MONDAY;
DateToUpdate.Month = RTC_MONTH_JANUARY;
DateToUpdate.Date = 0x4;
DateToUpdate.Year = 0x12;
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
}
5,按键驱动
配置按键引脚为输入上拉模式,创建一个20ms周期的线程,循环扫描,硬件有4个按键,即可实现确定返回,上下调节功能,无需复杂功能,单击按键功能即可满足功能。
6,蜂鸣器驱动
硬件采用无源蜂鸣器,通过不同频率使其导通,即可播放不同音调,由于蜂鸣器驱动没有接到stm32硬件定时器输出引脚,无法直接通过硬件pwm驱动,本项目采用定时器翻转蜂鸣器驱动引脚,模拟pwm输出,配置通用定时器,周期为1ms,在定时器中断回调函数翻转蜂鸣器引脚,当需要蜂鸣器播放声音时,打开定时器即可,关闭声音时,关闭定时器即可。
定时器回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM4) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
if (htim->Instance == TIM3) {
HAL_GPIO_TogglePin (BEEP_GPIO_Port,BEEP_Pin);
}
/* USER CODE END Callback 1 */
}
7,mpu6050驱动
mpu6050接到stm32硬件IIC外设上,初始化iic配置,通过mpu6050数据手册初始化mpu6050,读取三轴加速度值即可,判断重力加速度方向,实现姿态判断。
#include "mpu6050.h"
#include "stdio.h"
extern I2C_HandleTypeDef hi2c2;
//初始化MPU6050
//返回值:0,成功
// 其他,错误代码
uint8_t MPU_Init(void)
{
uint8_t res;
HAL_I2C_Init(&hi2c2);
MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X80); //复位MPU6050
MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X00); //唤醒MPU6050
MPU_Set_Gyro_Fsr(3); //陀螺仪传感器,±2000dps
MPU_Set_Accel_Fsr(0); //加速度传感器,±2g
MPU_Set_Rate(50); //设置采样率50Hz
MPU_Write_Byte(MPU_INT_EN_REG,0X00); //关闭所有中断
MPU_Write_Byte(MPU_USER_CTRL_REG,0X00); //I2C主模式关闭
MPU_Write_Byte(MPU_FIFO_EN_REG,0X00); //关闭FIFO
MPU_Write_Byte(MPU_INTBP_CFG_REG,0X80); //INT引脚低电平有效
res=MPU_Read_Byte(MPU_DEVICE_ID_REG);
// printf("\r\nMPU6050:0x%2x\r\n",res);
if(res==MPU_ADDR)//器件ID正确
{
MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X01); //设置CLKSEL,PLL X轴为参考
MPU_Write_Byte(MPU_PWR_MGMT2_REG,0X00); //加速度与陀螺仪都工作
MPU_Set_Rate(50); //设置采样率为50Hz
} else
return 1;
return 0;
}
//设置MPU6050陀螺仪传感器满量程范围
//fsr:0,±250dps;1,±500dps;2,±1000dps;3,±2000dps
//返回值:0,设置成功
// 其他,设置失败
uint8_t MPU_Set_Gyro_Fsr(uint8_t fsr)
{
return MPU_Write_Byte(MPU_GYRO_CFG_REG,fsr<<3);//设置陀螺仪满量程范围
}
//设置MPU6050加速度传感器满量程范围
//fsr:0,±2g;1,±4g;2,±8g;3,±16g
//返回值:0,设置成功
// 其他,设置失败
uint8_t MPU_Set_Accel_Fsr(uint8_t fsr)
{
return MPU_Write_Byte(MPU_ACCEL_CFG_REG,fsr<<3);//设置加速度传感器满量程范围
}
//设置MPU6050的数字低通滤波器
//lpf:数字低通滤波频率(Hz)
//返回值:0,设置成功
// 其他,设置失败
uint8_t MPU_Set_LPF(uint16_t lpf)
{
uint8_t data=0;
if(lpf>=188)data=1;
else if(lpf>=98)data=2;
else if(lpf>=42)data=3;
else if(lpf>=20)data=4;
else if(lpf>=10)data=5;
else data=6;
return MPU_Write_Byte(MPU_CFG_REG,data);//设置数字低通滤波器
}
//设置MPU6050的采样率(假定Fs=1KHz)
//rate:4~1000(Hz)
//返回值:0,设置成功
// 其他,设置失败
uint8_t MPU_Set_Rate(uint16_t rate)
{
uint8_t data;
if(rate>1000)rate=1000;
if(rate<4)rate=4;
data=1000/rate-1;
data=MPU_Write_Byte(MPU_SAMPLE_RATE_REG,data); //设置数字低通滤波器
return MPU_Set_LPF(rate/2); //自动设置LPF为采样率的一半
}
//得到温度值
//返回值:温度值(扩大了100倍)
float MPU_Get_Temperature(void)
{
unsigned char buf[2];
short raw;
float temp;
MPU_Read_Len(MPU_TEMP_OUTH_REG,2,buf);
raw=(buf[0]<<8)| buf[1];
temp=(36.53+((double)raw)/340)*100;
// temp = (long)((35 + (raw / 340)) * 65536L);
return temp/100.0f;
}
//得到陀螺仪值(原始值)
//gx,gy,gz:陀螺仪x,y,z轴的原始读数(带符号)
//返回值:0,成功
// 其他,错误代码
uint8_t MPU_Get_Gyroscope(short *gx,short *gy,short *gz)
{
uint8_t buf[6],res;
res=MPU_Read_Len(MPU_GYRO_XOUTH_REG,6,buf);
if(res==0)
{
*gx=((uint16_t)buf[0]<<8)|buf[1];
*gy=((uint16_t)buf[2]<<8)|buf[3];
*gz=((uint16_t)buf[4]<<8)|buf[5];
}
return res;
}
//得到加速度值(原始值)
//gx,gy,gz:陀螺仪x,y,z轴的原始读数(带符号)
//返回值:0,成功
// 其他,错误代码
uint8_t MPU_Get_Accelerometer(short *ax,short *ay,short *az)
{
uint8_t buf[6],res;
res=MPU_Read_Len(MPU_ACCEL_XOUTH_REG,6,buf);
if(res==0)
{
*ax=((uint16_t)buf[0]<<8)|buf[1];
*ay=((uint16_t)buf[2]<<8)|buf[3];
*az=((uint16_t)buf[4]<<8)|buf[5];
}
return res;;
}
//IIC连续写
uint8_t MPU_Write_Len(uint8_t reg,uint8_t len,uint8_t *buf)
{
HAL_I2C_Mem_Write(&hi2c2, MPU_WRITE, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 0xfff);
HAL_Delay(1);
return 0;
}
//IIC连续读
//addr:器件地址
//reg:要读取的寄存器地址
//len:要读取的长度
//buf:读取到的数据存储区
//返回值:0,正常
// 其他,错误代码
uint8_t MPU_Read_Len(uint8_t reg,uint8_t len,uint8_t *buf)
{
HAL_I2C_Mem_Read(&hi2c2, MPU_READ, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 0xfff);
HAL_Delay(1);
return 0;
}
//IIC写一个字节
//reg:寄存器地址
//data:数据
//返回值:0,正常
// 其他,错误代码
uint8_t MPU_Write_Byte(uint8_t reg,uint8_t data)
{
unsigned char W_Data=0;
W_Data = data;
HAL_I2C_Mem_Write(&hi2c2, MPU_WRITE, reg, I2C_MEMADD_SIZE_8BIT, &W_Data, 1, 0xfff);
HAL_Delay(1);
return 0;
}
//IIC读一个字节
//reg:寄存器地址
//返回值:读到的数据
uint8_t MPU_Read_Byte(uint8_t reg)
{
unsigned char R_Data=0;
HAL_I2C_Mem_Read(&hi2c2, MPU_READ, reg, I2C_MEMADD_SIZE_8BIT, &R_Data, 1, 0xfff);
HAL_Delay(1);
return R_Data;
}
float Gyro_y; //Y轴陀螺仪数据暂存
float Angle; //最终测量角度
/*************卡尔曼滤波*********************************/
void Kalman_Filter(float Accel,float Gyro)
{
static const float Q_angle=0.001;
static const float Q_gyro=0.003;
static const float R_angle=0.5;
static const float dt=0.01; //dt为kalman滤波器采样时间;
static const char C_0 = 1;
static float Q_bias, Angle_err;
static float PCt_0, PCt_1, E;
static float K_0, K_1, t_0, t_1;
static float Pdot[4] = {0,0,0,0};
static float PP[2][2] = { { 1, 0 },{ 0, 1 } };
Angle+=(Gyro - Q_bias) * dt; //先验估计
Pdot[0]=Q_angle - PP[0][1] - PP[1][0]; // Pk-先验估计误差协方差的微分
Pdot[1]= -PP[1][1];
Pdot[2]= -PP[1][1];
Pdot[3]=Q_gyro;
PP[0][0] += Pdot[0] * dt; // Pk-先验估计误差协方差微分的积分
PP[0][1] += Pdot[1] * dt; // =先验估计误差协方差
PP[1][0] += Pdot[2] * dt;
PP[1][1] += Pdot[3] * dt;
Angle_err = Accel - Angle; //zk-先验估计
PCt_0 = C_0 * PP[0][0];
PCt_1 = C_0 * PP[1][0];
E = R_angle + C_0 * PCt_0;
K_0 = PCt_0 / E;
K_1 = PCt_1 / E;
t_0 = PCt_0;
t_1 = C_0 * PP[0][1];
PP[0][0] -= K_0 * t_0; //后验估计误差协方差
PP[0][1] -= K_0 * t_1;
PP[1][0] -= K_1 * t_0;
PP[1][1] -= K_1 * t_1;
Angle += K_0 * Angle_err; //后验估计
Q_bias += K_1 * Angle_err; //后验估计
Gyro_y = Gyro - Q_bias; //输出值(后验估计)的微分=角速度
}
8,人机交互界面
界面分为两部分,模拟时钟显示和时钟设置界面。模拟时钟显示部分有表盘和指针组成,表盘采用240*240分辨率表盘图片通过转换成黑白单色二进制文件(受单片机flash限制,如果是16位彩色图片需要flash大小为240*240*2=112.5K,单片机flash大小为128K,剩余空间不多,无法在放其他图片如果采用单色图片则需要flash 240*240/8=7200字节)然后显示指针,为提高刷新速度,只在刚进入模拟时钟显示界面是才刷新表盘背景及旋转屏幕是刷新表盘背景,时钟表针可以刷新频繁一点。设置时钟界面,数字显示部分为0-9的图片,图片为彩色图片,白色背景,正常显示时,为图片正常显示,设置时间时,被选中的图片为黑色背景,图片为同一张图片,通过分析图片数据,白的点的值大与0xfd00,因此,显示黑色背景时需要将大与0xfd00设置为0x00即可。
9总结报告
本次项目学到很多东西,遇到很多问题,1.采用仿真器为单片机提供时钟源的时候,遇到等待外部时钟源稳定时间过短,上电后仿真器还没准备好时钟输出,单片机等待外部时钟源超时,导致外部时钟源无法初始化成功,采用内部低速时钟源,屏幕刷新过慢,处理结果是增大等待外部时钟源稳定时间。2.液晶屏没接到硬件spi外设,采用hal库函数导致屏幕刷新过慢,优化方法是采用寄存器操作引脚翻转。3.本想采用lvglgui界面,在移植官方测试例程时候,由于单片机flash太小,无法运行,只好自己写个简单的gui界面。4.屏幕刷新优化,在设置时钟界面时,旋转屏幕需刷新全屏,刷新全屏时有点慢,影响美观,后来通过局部覆盖的方式,在刷新新界面之前,将汉字颜色改为背景色,图片显示区域刷新背景色,从而提高刷新速度.宝剑锋从磨砺出,梅花香自苦寒来,不经历风雨怎见彩虹,不抛弃,不放弃,坚持克服困难,才能迈出铿锵步伐,感谢主办方给我一次锻炼的机会,让我学会成长。