STM32遥控时钟带闹钟
设计目的
本次设计是基于STM32F103ZET6,正点原子战舰版制作的,采用正点原子的TFT-LCD 3.5寸屏幕和红外遥控等硬件,实现了实时日期和实时时间,以及闹钟和响铃,并且能用遥控器进行日期、时间、闹钟的更改。
设计原理
本设计运用STM32芯片内部RTC进行计时以及实时时钟和闹钟,采用定时器4来进行红外遥控的接收,LCD使用纯人工画点的方法通过FSMC驱动,RGB565显示当前日期、时间、闹铃。遥控器仅使用三个按键,UP(加)、DOWN(减)、ALIENTEK(切换)进行遥控设置。
TFT-LCD
ILI9341 液晶控制器自带显存,其显存总大小为 172800( 24032018/8),即 18 位模式( 26万色)下的显存量。在 16 位模式下, ILI9341 采用 RGB565 格式存储颜色数据,此时 ILI9341的 18 位数据线与 MCU 的 16 位数据线以及 LCD GRAM 的对应关系如图所示: ![RGB565](https://img-blog.csdnimg.cn/a1c4f09b5e7c4ee6a8c267ea9387f9be.png)
FSMC
在STM32中,使用FSMC需要先进行一些初始化设置,包括时钟频率、读写模式、地址宽度、数据宽度等。然后就可以通过FSMC控制器来读写存储器,包括读写数据、擦除数据、写入保护等操作。
例如,如果要使用FSMC控制器读取一块外部的SRAM存储器,可以按照以下步骤进行:
配置FSMC时钟和引脚配置FSMC控制器的寄存器,包括读写模式、地址宽度、数据宽度等配置SRAM存储器的地址范围和参数通过FSMC控制器进行读写操作,包括读取数据、写入数据、擦除数据等
RTC
RTC是STM32的实时时钟模块,可以提供高精度的时间和日期计数,可以被用于各种应用,例如日历、定时器、报警等。 RTC正常工作配置如下
使能电源时钟和备份区域时钟。取消备份区写保护。复位备份区域,开启外部低速振荡器。选择 RTC 时钟,并使能。设置 RTC 的分频,以及配置 RTC 时钟。更新配置,设置 RTC 中断分组。要开启闹钟相应,则要先启动中断线17编写中断服务函数。
NEC
NEC 遥控指令的数据格式为:同步码头、地址码、地址反码、控制码、控制反码。同步码由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码均是8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性(可用于校验), NEC 码规定的连发码(由 9ms 低电平+2.5m 高电平+0.56ms 低电平+97.94ms 高电平组成)。输入捕获来测量高电平的脉宽,解码红外遥控信号,利用输入捕获的这个功能来实现遥控解码。
硬件电路
![lcd](https://img-blog.csdnimg.cn/77630bf30faa43189cdb5a531d680397.png)
软件设计
rtc.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "rtc.h"
#include "beep.h"
_calendar_obj calendar;//时钟结构体
static void RTC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级1位,从优先级3位
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //先占优先级0位,从优先级4位
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
NVIC_InitStructure.NVIC_IRQChannel = RTCAlarm_IRQn; //RTC闹钟中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级1位,从优先级3位
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //先占优先级0位,从优先级4位
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
//若ALRF=1,如果在EXTI控制器中设置了EXTI线 17的中断模式,则允许产生RTC闹钟中断;如果在EXTI控制器中设置了EXTI线 17的事件模式,则这条线上会产生一个脉冲(不会产生RTC闹钟中断)
EXTI_ClearITPendingBit(EXTI_Line17); //启动中断线17
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Line = EXTI_Line17;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
//实时时钟配置
//初始化RTC时钟,同时检测时钟是否工作正常
//BKP->DR1用于保存是否第一次配置的设置
//返回0:正常
//其他:错误代码
u8 RTC_Init(void)
{
//检查是不是第一次配置时钟
u8 temp=0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
{
BKP_DeInit(); //复位备份区域
RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp=250)return 1;//初始化时钟失败,晶振有问题
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
RCC_RTCCLKCmd(ENABLE); //使能RTC时钟
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_WaitForSynchro(); //等待RTC寄存器同步
RTC_ITConfig(RTC_IT_SEC|RTC_IT_ALR, ENABLE); //使能RTC秒中断和闹钟中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_EnterConfigMode();/// 允许配置
RTC_SetPrescaler(32767); //设置RTC预分频的值
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
//RTC_Set(2023,04,13,14,40,00); //设置时间
RTC_WaitForSynchro();
// RTC_Alarm_Set(); //设置闹钟
RTC_ExitConfigMode(); //退出配置模式
BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的后备寄存器中写入用户程序数据
}
else//系统继续计时
{
RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
}
RTC_NVIC_Config();//RCT中断分组设置
RTC_Get();//更新时间
return 0; //ok
}
//RTC时钟中断
//每秒触发一次
//extern u16 tcnt;
extern int count;
//秒钟中断
void RTC_IRQHandler(void)
{
if(RTC_GetITStatus(RTC_IT_SEC)!=RESET)
{
RTC_Get();
}
RTC_ClearITPendingBit(RTC_IT_OW|RTC_IT_SEC);
RTC_WaitForLastTask();
}
//闹钟中断
void RTCAlarm_IRQHandler(void)
{
if(RTC_GetITStatus(RTC_IT_ALR) != RESET) //响铃 延时1000ms自动关闭
{
BEEP = 1;
delay_ms(1000);
BEEP = 0;
}
EXTI_ClearITPendingBit(EXTI_Line17);
RTC_WaitForLastTask();
RTC_ClearITPendingBit(RTC_IT_ALR);
RTC_WaitForLastTask();
}
//判断是否是闰年函数
//月份 1 2 3 4 5 6 7 8 9 10 11 12
//闰年 31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//输入:年份
//输出:该年份是不是闰年.1,是.0,不是
u8 Is_Leap_Year(u16 year)
{
if(year%4==0) //必须能被4整除
{
if(year%100==0)
{
if(year%400==0)return 1;//如果以00结尾,还要能被400整除
else return 0;
}else return 1;
}else return 0;
}
//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//返回值:0,成功;其他:错误代码.
//月份数据表
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
//平年的月份日期表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=0;
if(syear2099)return 1;
for(t=1970;t=29)temp-=29;//闰年的秒钟数
else break;
}
else
{
if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
else break;
}
temp1++;
}
calendar.w_month=temp1+1; //得到月份
calendar.w_date=temp+1; //得到日期
}
temp=timecount%86400; //得到秒钟数
calendar.hour=temp/3600; //小时
calendar.min=(temp%3600)/60; //分钟
calendar.sec=(temp%3600)%60; //秒钟
calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期
return 0;
}
//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//输入参数:公历年月日
//返回值:星期号
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{
u16 temp2;
u8 yearH,yearL;
yearH=year/100; yearL=year%100;
// 如果为21世纪,年份数加100
if (yearH>19)yearL+=100;
// 所过闰年数只算1900年之后的
temp2=yearL+yearL/4;
temp2=temp2%7;
temp2=temp2+day+table_week[month-1];
if (yearL%4==0&&month |