因为我在学习stm32f407的时候发现网上很少有基于标准库的一些项目,让作为新手的我十分难受,所以在这里分享用stm32f407实现圆形钟表的项目。(适合纯小白)
注意!!!!!本人对于F4系列纯新手,所有本文有错误的地方还请大佬指出批评,我会实时修改。
**主要说一下这个钟表的功能和实现思路**
---------------------------------------------------------------------------------------------------------------------------------
功能:
1.可以像钟表一样正常运转
2.可以通过串口来修改时间
3.触摸屏修改时间
对于功能的讲解,我只会选取需要的函数,如果想全面了解,请移步正点原子官方网站。
---------------------------------------------------------------------------------------------------------------------------------
实现思路:
一.在代码层面操作时间,可以定义一个结构体:
typedef struct
{
int hour;
int minute;
int second;
int Is_reseive;
int To_change;
} data;
用来装我们需要的时分秒,Is_reseive 是串口的标志,To_change是是否移动表针的标志,因为时间可以通过串口修改,所以就定义在时间结构体里,方便我们对变量归类。
二.得到基本变量之后,现在要做的就是让时间走起来,使用定时器1
每当完成一次更新中断,都会将data time结构体的To_change都会至1,然后会在绘制表针的函数里至0。保证秒针一秒移动,可以防止你的表针疯狂闪烁。
void TIM1_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE); ///使能TIM1时钟
TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStructure);//初始化TIM1
TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE); //允许定时器1更新中断
TIM_Cmd(TIM1,ENABLE); //使能定时器1
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_TIM10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void TIM1_UP_TIM10_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1,TIM_IT_Update)==SET)
{
time.second++;
time.To_change=1;
if(time.second>=60)
{
time.second=0;
(time.minute)++;
if(time.minute>=60)
{
time.minute=0;
(time.hour)++;
if(time.hour>=12)
{
time.hour=0;
}
}
}
}
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
}
#ifndef _TIMER1_H
#define _TIMER1_H
#include "sys.h"
void TIM1_Int_Init(u16 arr,u16 psc);
#endif
三.串口修改时间
串口发送的统一格式是 00-00-00 ,注意不要输错,这里没有做越界处理。
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 temp;
static u8 i=0;
if(USART_GetITStatus(USART1, USART_IT_RXNE))
{
temp=USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
USART_RX_BUF[i++]=temp ;
if(i==8){time.Is_reseive=1; i=0;}
}
}
void judge_is_right(void)
{
if(time.Is_reseive==1)
{
time.Is_reseive=0;
if(USART_RX_BUF[2]=='-'&&USART_RX_BUF[5]=='-')
{
time.hour=((USART_RX_BUF[0]-'0')*10+USART_RX_BUF[1]-'0');
time.minute=((USART_RX_BUF[3]-'0')*10+USART_RX_BUF[4]-'0');
time.second=((USART_RX_BUF[6]-'0')*10+USART_RX_BUF[7]-'0');
}
else
{
printf("-and- error\r\n");
}
}
}
四.绘制表针
1.先绘制表盘
放在while循环的外面。运用角度制来求坐标。
void Draw_colck(void)
{
uint16_t angle;
int16_t x1, y1, x2, y2,x3,y3;
uint8_t n;
char num[3];
LCD_Draw_Circle(CENTER_X, CENTER_Y, 2);
LCD_Draw_Circle(CENTER_X, CENTER_Y, DIAL_RADIUS);
LCD_Draw_Circle(CENTER_X, CENTER_Y, DIAL_RADIUS-2);
for (angle = 0; angle < 360; angle += 6)
{
x2 = CENTER_X + (DIAL_RADIUS - 3) * sin(angle * PI / 180);
y2 = CENTER_Y - (DIAL_RADIUS - 3) * cos(angle * PI / 180);
if (angle % 30 == 0)
{ //long line
x1 = CENTER_X + (DIAL_RADIUS - 25) * sin(angle * PI / 180);
y1 = CENTER_Y - (DIAL_RADIUS - 25) * cos(angle * PI / 180);
LCD_DrawLine(x1, y1, x2, y2);
if (angle == 0) { n=12;}
else { n=angle/30;}
sprintf(num, "%d", n);
x3 = CENTER_X + (DIAL_RADIUS - 40) * sin(angle * PI / 180) - 8;
y3 = CENTER_Y - (DIAL_RADIUS - 40) * cos(angle * PI / 180) - 10;
LCD_ShowString(x3, y3, 20,20,16,(u8*)num);
}
else
{
//short line
x1 = CENTER_X + (DIAL_RADIUS - 10) * sin(angle * PI / 180);
y1 = CENTER_Y - (DIAL_RADIUS - 10) * cos(angle * PI / 180);
LCD_DrawLine(x1, y1, x2, y2);
}
}
}
2.绘制动态表针
这里要先定义全局变量 old_ x或y _ 什么针 采取先擦后画的方式实现,入口是定时器的标志位,精准控制一秒一动
void Draw_change(void)
{
if(time.To_change)
{
float angle_sec = time.second * 6;
float angle_min = time.minute * 6 + time.second * 0.1;
float angle_hour = time.hour * 30 + time.minute * 0.5 + time.second * 0.1/60;
//clear old
LCD_Fill(CENTER_X, CENTER_Y, old_x_sec, old_y_sec,WHITE);
LCD_Fill(CENTER_X, CENTER_Y, old_x_min, old_y_min,WHITE);
LCD_Fill(CENTER_X, CENTER_Y, old_x_hour, old_y_hour,WHITE);
old_x_sec = CENTER_X +DIAL_RADIUS * 0.8 * cos((-angle_sec+90) * PI / 180);
old_y_sec = CENTER_Y - DIAL_RADIUS * 0.8 * sin((-angle_sec+90) * PI / 180);
LCD_DrawLine(CENTER_X, CENTER_Y, old_x_sec, old_y_sec);
old_x_min = CENTER_X + DIAL_RADIUS * 0.7 * cos((-angle_min+90) * PI / 180);
old_y_min = CENTER_Y - DIAL_RADIUS * 0.7 * sin((-angle_min+90) * PI / 180);
LCD_DrawLine(CENTER_X, CENTER_Y, old_x_min, old_y_min);
old_x_hour = CENTER_X + DIAL_RADIUS * 0.5 * cos((-angle_hour+90) * PI / 180);
old_y_hour = CENTER_Y - DIAL_RADIUS * 0.5 * sin((-angle_hour+90) * PI / 180);
LCD_DrawLine(CENTER_X, CENTER_Y, old_x_hour, old_y_hour);
time.To_change=0;
}
}
五.触屏改变时间
主要是通过读取坐标和中心点的距离来判断改变时针还是分针还是秒针.
这里的tp dev scan是一个结构体的函数指针,对应的实际的话,是你的触摸芯片的scan,比如GTxx_scan.
tp dev sta 是这个结构体里面的一个u8变量,可供操作位八位,倘若他的最后一位取1的时候,他与上0x01就会是1。当你触碰屏幕的时候,触碰芯片会读取到坐标,会改变sta的最后一位,使他至1。
这里也同样是读取他的坐标,然后通过反正切来计算他的角度,然后让让坐标旋转90度,因为默认的话,3点钟方向是0度,旋转90度后12点钟变为0度。
void TouchAdjustTime(void)
{
static u16 lastpos[2] = {0XFFFF, 0XFFFF};
int Rx, Ry;
float dist, angle;
u8 new_hour, new_min, new_sec;
tp_dev.scan(0);
if((tp_dev.sta) & 0x01)
{
if(tp_dev.x[0] < lcddev.width && tp_dev.y[0] < lcddev.height)
{
if(lastpos[0] == 0XFFFF)
{
lastpos[0] = tp_dev.x[0];
lastpos[1] = tp_dev.y[0];
}
else
{
Rx = tp_dev.x[0] - CENTER_X;
Ry = CENTER_Y - tp_dev.y[0];
dist = sqrt((float)(Rx*Rx + Ry*Ry));
if(dist >= 20)
{
angle = atan2((float)Ry, (float)Rx) * 180 / PI;
if(angle < 0) angle += 360;
angle = 90 - angle;
if(angle < 0) angle += 360;
if(dist > DIAL_RADIUS * 0.7) { // 秒针区域
new_sec = (u8)(angle / 6);
time.second = new_sec;
}
else if(dist > DIAL_RADIUS * 0.4) { // 分针区域
new_min = (u8)(angle / 6);
time.minute = new_min;
}
else { // 时针区域
new_hour = (u8)(angle / 30) % 12;
time.hour = new_hour;
}
}
lastpos[0] = tp_dev.x[0];
lastpos[1] = tp_dev.y[0];
}
}
}
}