基于STM32F103的“口袋”控制平台的模拟钟/数字钟
本项目是基于STM32F103RBT6的“口袋”嵌入式学习/控制平台,其有128KB Flash,20KB RAM,足够完成很多需求。本文章主要介绍其功能实现:可设置时间、整点报时的模拟时钟,转动板子,LCD屏上的时钟自动跟着旋转。
标签
嵌入式系统
STM32
MPU
显示
模拟时钟
偏爱鸽子味儿
更新2021-02-24
1375

器件一览

FjarBBE2N8ywqhjoRUY_rRuXCi2K

实现目标:完成模拟时钟/数字时钟显示,时间设置,整点报时,姿态感应旋转屏幕等功能。

平台选择:STM32CubeMX,Keil5 MDK

原理简介及实现过程:

  1. 借助STM32CubeMX配置各IO引脚,外设驱动,LCD驱动引脚,按键驱动,IIC驱动,RTC驱动,以及单片机内部时钟配置等,通过可视化配置外设,大大提高开发效率,Keil5 MDK用于CubeMX生成的工程进行代码编写,编译,通过板载仿真器Dap-link,进行仿真,烧录。
    FnZ26JTSdOjCerKCl8a0ZskvBcY-
    FvFtFvfKYLGB1yXB1kKxYtQRmcDW

  2. 显示功能需要由该开发板的LCD实现,故首先需要完成LCD驱动,该LCD采用的是驱动IC的SPI接口,由于相连的引脚没有可使用的硬件SPI可供使用,所以进行软件SPI的编写(正点、野火资源很多,成功移植即可)。驱动成功后能够调用一些GUI画图函数初步实现简单的构图或打印数字字母等;

  3. 既然是时钟,就需要一个稳定可靠的计时功能,有两个选择,首先想到的就是用定时器控制,定时1s产生中断来达到计时功能;另一个是RTC(Real Time Clock)实时时钟来计时,开始我也没想到这个,后来看芯片用户手册看到了,百度了解才知它是单片机内专门用来提供日历时钟功能,还具有闹钟中断和阶段性中断功能。得益于STM32CubeMX,RTC寄存器配置也十分简单,相比之下,用RTC它不香嘛,还能省下一个定时器;

    HAL_RTC_GetTime(&hrtc, &RTCTime, RTC_FORMAT_BIN);//获取时间函数
    HAL_RTC_SetTime(&hrtc, &RTCTime, RTC_FORMAT_BIN);//设置时间函数
    //俩函数搞定,直呼太香了

     

  4. 下一步是绘制表盘、刻度、指针,此部分也是调用LCD中各种函数完成,画圆画线画点。绘制指针原理如下,已知中心原点坐标,圆的半径r,指针线长,这里假设也为r,则走过时间t后该点坐标即可得出(如下图),进而用直线绘制函数可获得每秒的指针了,注意:不同象限的计算公式不同,做出一点修改就可。
    FgWXsjnL-YmMxc42E2Vqw0spcKeR

    void draw_sec_needle(u8 position,u8 len,u16 colour,u8 width)//绘制秒针
    {
        u16 xt,yt,xr,yr,x0,y0;
        xr=(u16)(sin(position*6*PI/180)*7);//中心圆点的尺寸
        yr=(u16)(cos(position*6*PI/180)*7);
        if(position<15)
        {
           xt=(u16)(sin(position*6*PI/180)*len);
           yt=(u16)(cos(position*6*PI/180)*len);
           x0=119+xr;
           y0=119-yr;
           GUI_LineWith(119+xt,119-yt,x0,y0,width,colour);
        }
      ......
    }

     

  5. 时间设置可以通过中断来设置,这时候需要按键来触发中断,对按键引脚配置寄存器即可,在中断函数对RTC时间设置即可实现更改时间的功能,由于板子上按键有限,所以我为按键增加定义有单击,双击,长按,以此来实现更多功能,当然,此功能需要定时器,所以仅定义了一个键有这三种功能,相当于有六个按键可以使用;

    //按键状态检测函数
    void key1_read(void)
    {	
    	if(KEY1==0)  //当按键按下
    	{
    		if(short_key1_flag==0)//如果短按标志值为0
    		{
    			short_key1_flag=1;//开始第一次按键键值扫描
    			key1_time=0;//按键按下计时变量清0,开始计时,1ms加1
    			HAL_TIM_Base_Start_IT(&htim2);
    		}
    		else if(short_key1_flag==1)
    		{
    			if(key1_time>=1000)
    			{
    				long_k1_press=1;
    				short_key1_flag=0;
    			}
    		}
    	}
    	if(KEY1==1)//当按键抬起
    	{
    		if(short_key1_flag==1)//当按键抬起后,此标志如果为1,说明是短按不是长按
    		{
    			short_key1_flag=0;
    			if(dou_key1_flag==0)
    			{
    				dou_key1_flag=1;//按键双击标志置1,等待确认按键是否为双击
    				key1_double_time=0;//开始计时,同样1ms加1
    			}
    			else
    			{
    				if(key1_double_time<500)//第一次短按发生后,在500ms内,发生第二次短按,则完成一次双击,刷新键值
    				{
    					dou_key1_flag=0;
    					double_k1_press=1;
    				}
    			}
    		}
    		else if(dou_key1_flag==1)
    		{
    			if(key1_double_time>500)
    			{
    				dou_key1_flag=0;
    				short_k1_press=1;
    			}
    		}
    	}
    }



  6. 整点报时需要使用蜂鸣器,开发板上的蜂鸣器是无源的,需要2k-4kHz的PWM波驱动它,很遗憾蜂鸣器的引脚PC10没有定时器复用功能,不能由该引脚直接产生PWM,需要初始化其他定时器定时中断反转PC10电平达到PWM驱动效果,至于整点报时只需判断时间是否到整点,再执行蜂鸣器即可;
    FmAFQR7TCaBNKxJkc6G0k_5msJHJ

    * TIM4 init function */
    void MX_TIM4_Init(void)
    {
      TIM_ClockConfigTypeDef sClockSourceConfig = {0};
      TIM_MasterConfigTypeDef sMasterConfig = {0};
    
      htim4.Instance = TIM4;
      htim4.Init.Prescaler = 71;
      htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
      htim4.Init.Period = 249;	//4kHz
      htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
      htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
      if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
      {
        Error_Handler();
      }
      sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
      if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
      {
        Error_Handler();
      }
      sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
      sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
      if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
      {
        Error_Handler();
      }
    }
    //定时器中断处理函数
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
    	if(htim == (&htim4))
    	{
    		HAL_GPIO_TogglePin(BEEP_GPIO_Port, BEEP_Pin);//蜂鸣器驱动
    	}
    }

     

  7. 姿态感应是通过板子上的mpu6050来获取板子实时的三轴加速度值以及重力加速度值,mpu6050通过I2C总线驱动,该mpu6050恰好接到板子硬件IIC外设上,这里移植的正点原子的mpu6050驱动程序,并用HAL库模拟IIC实现对mpu6050的通信。初始化好mpu6050就可以直接读取三轴加速度值以及重力加速度值了。

    MPU_Get_Accelerometer(&ax,&ay,&az);//读取三轴加速度值
    MPU_Get_Gyroscope(&gx,&gy,&gz);//读取重力加速度值

    虽说对获得的值还需要软件滤波以获取更精确的角度值来反映姿态,但本功能中姿态感应只用来翻转屏幕,故对这部分简单化处理了。通过串口实时向上位机发送x,y轴加速度值,同时翻转板子即可大概确定翻转导致各轴加速度变化的阈值。再将预估阈值设为条件判断屏幕翻转即可。
    FtsboPeAxnTDnGUWSXY7aVjr0A1i

  8. 最后的最后,实现屏幕的翻转,这部分我有两个想法,一种是通过实时更改LCD驱动中相应寄存器(控制色块刷新以及数据传输方向的寄存器,驱动代码中有),另一种则是更改画图函数,根据翻转角度实时更新,即做出每种翻转情况下的作图(想想就麻烦)。也因为当时我做的时候是把屏幕翻转放在最后一步,故选择了第一种方法。由于最开始用的LCD驱动不能改方向,这部分研究甚久,功夫不负有心人,更换移植了多次LCD驱动终于成功。

    /*****************************************************************************
     * @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;
    	}		
    }	 

    总结:这次项目让我更加地了解了单片机的工作模式以及STM32的开发应用,为以后的学习更是打下了基础,感谢硬禾平台给予我们这次学习的机会!!

工程文件:https://scuer.lanzous.com/b0166upmb   密码:8vts

附件下载
说明文档.zip
内涵说明文档,芯片用户手册,硬件原理图
工程文件.zip
程序
团队介绍
四川大学
团队成员
刘帅
四川大学 电气工程学院2019级
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号