1、利用SysTick定时器编写倒计时程序,如初始设置为2分30秒,每秒在屏幕上输出一次时间,倒计时为0后,红灯亮,停止屏幕输出,并关闭SysTick定时器的中断。
//includes.h
/**
* 用户定义的全局变量
*/
G_VAR_PREFIX vuint8_t cTime[3]; // 存储当前时间的数组,格式为时、分、秒
G_VAR_PREFIX vuint8_t ctime; // 总倒计时时间,单位为秒
G_VAR_PREFIX vuint8_t ltime; // 剩余时间,单位为秒
G_VAR_PREFIX vuint8_t gtime; // 系统时间,单位为秒
//isr.c
/**
* 系统滴答中断处理函数
*/
void SysTick_Handler()
{
// 模拟打印语句,实际使用时可注释掉
// printf("***\n");
static uint8_t SysTickCount = 0; // 定义静态变量SysTickCount,用于计数滴答数
SysTickCount++; // 滴答计数器增加1
wdog_feed(); // 重置看门狗定时器,防止看门狗复位
if (SysTickCount >= 100) // 当计数达到100时,更新时间
{
SysTickCount = 0; // 重置滴答计数器
// 计算当前系统时间(秒)
gtime = gTime[0] * 3600 + gTime[1] * 60 + gTime[2];
// 计算剩余时间(秒)
ltime = ctime - gtime;
// 更新当前时间数组
cTime[0] = ltime / 3600;
cTime[1] = (ltime % 3600) / 60;
cTime[2] = ltime % 60;
SecAdd1(gTime); // 系统时间加1秒
}
}
//main.c
int main(void)
{
ctime = 61; // 设置总倒计时时间为61秒
ltime = 0; // 初始化剩余时间为0
cTime[0] = 0; // 初始化小时为0
cTime[1] = 0; // 初始化分钟为0
cTime[2] = 0; // 初始化秒为0
gpio_init(LIGHT_RED, GPIO_OUTPUT, LIGHT_OFF); // 初始化红灯为关闭状态
for(;;) // 无限循环,程序的主循环
{
if (gTime[2] != mSec) // 如果系统时间的秒数发生变化
{
mSec = gTime[2]; // 更新mSec为当前秒数
if (ltime != 0) // 如果倒计时未结束
{
printf("剩余时间:%d:%d:%d\n", cTime[0], cTime[1], cTime[2]); // 打印剩余时间
}
else // 倒计时结束
{
printf("倒计时结束,红灯亮\n"); // 打印提示信息
gpio_set(LIGHT_RED, LIGHT_ON); // 点亮红灯
for(;;) { } // 无限循环,保持红灯亮状态
}
}
}
return 0; // 主函数返回值,实际中可能不需要
}
2、利用RTC显示日期(年月日、时分秒),每秒更新。并设置某个时间的闹钟。闹钟时间到时,屏幕上显示有你的姓名的文字,并点亮绿灯。
main.c:
#define GLOBLE_VAR // 定义全局变量宏
#include "includes.h" // 包含总头文件
int main(void)
{
uint32_t mMainLoopCount; // 主循环计数器
// 关闭总中断,以确保初始化过程的原子性
DISABLE_INTERRUPTS;
// 初始化主函数使用的局部变量
mMainLoopCount = 0; // 初始化主循环计数器
// 初始化全局变量
g_RTC_Flag = 0;
// 初始化用户外设模块
gpio_init(LIGHT_GREEN, GPIO_OUTPUT, LIGHT_OFF); // 初始化绿灯为关闭状态
uart_init(UART_User, 115200); // 初始化串口,设置波特率为115200
RTC_Init(); // 初始化实时时钟(RTC)
// 设置RTC时间为16:51:49
RTC_Set_Time(16, 51, 49);
// 设置RTC日期为2024年6月6日星期四
RTC_Set_Date(24, 6, 6, 4);
// 使能模块中断
RTC_PeriodWKUP_Enable_Int(); // 使能周期性唤醒中断
uart_enable_re_int(UART_User); // 使能串口接收中断
// 打开总中断,允许中断处理
ENABLE_INTERRUPTS;
// 使能RTC闹钟中断
RTC_Alarm_Enable_Int(0);
// 设置闹钟时间为星期四的16:52:00
RTC_Set_Alarm(0, 4, 16, 52, 0);
// 配置周期性唤醒中断,每秒触发一次
RTC_Set_PeriodWakeUp(1);
// 设置中断优先级
NVIC_SetPriority(RTC_WKUP_IRQn, 1); // 设置周期性唤醒中断优先级为1
NVIC_SetPriority(RTC_Alarm_IRQn, 0); // 设置闹钟中断优先级为0(更高优先级)
// 主循环
for(;;)
{
// 增加主循环计数器
mMainLoopCount++;
// 如果主循环计数器未达到设定值,继续循环
if (mMainLoopCount <= 12888999) continue;
// 如果达到设定值,重置计数器,并根据条件处理灯的状态
mMainLoopCount = 0;
if (g_RTC_Flag == 1)
{
// 处理接收到的时间设置
g_RTC_Flag = 0;
// 从串口接收的缓冲区解析日期和时间,并设置到RTC
gcRTC_Date_Time.Year = (uint8_t)((gcRTCBuf[1] - '0') * 10 + (gcRTCBuf[2] - '0'));
gcRTC_Date_Time.Month = (uint8_t)((gcRTCBuf[4] - '0') * 10 + (gcRTCBuf[5] - '0'));
gcRTC_Date_Time.Date = (uint8_t)((gcRTCBuf[7] - '0') * 10 + (gcRTCBuf[8] - '0'));
gcRTC_Date_Time.Hours = (uint8_t)((gcRTCBuf[10] - '0') * 10 + (gcRTCBuf[11] - '0'));
gcRTC_Date_Time.Minutes = (uint8_t)((gcRTCBuf[13] - '0') * 10 + (gcRTCBuf[14] - '0'));
gcRTC_Date_Time.Seconds = (uint8_t)((gcRTCBuf[16] - '0') * 10 + (gcRTCBuf[17] - '0'));
gcRTC_Date_Time.Weekday = (uint8_t)((gcRTCBuf[23] - '0'));
// 设置解析出的日期和时间到RTC
RTC_Set_Time(gcRTC_Date_Time.Hours, gcRTC_Date_Time.Minutes, gcRTC_Date_Time.Seconds);
RTC_Set_Date(gcRTC_Date_Time.Year, gcRTC_Date_Time.Month, gcRTC_Date_Time.Date, gcRTC_Date_Time.Weekday);
}
}
}
修改icr.c中的函数,完成 RTC闹钟中断后的一系列处理,闹钟中断的优先级必须高于周期性唤醒中断,以确保闹钟中断程序能够被正确处理。
/**
* RTC周期性唤醒中断处理函数
*/
void RTC_WKUP_IRQHandler(void)
{
uint8_t hour, min, sec; // 定义变量以存储获取的时间
uint8_t year, month, date, week; // 定义变量以存储获取的日期
char *p; // 定义字符串指针,用于格式化日期和时间
// 检查是否发生了周期性唤醒中断
if (RTC_PeriodWKUP_Get_Int())
{
// 清除周期性唤醒中断标志
RTC_PeriodWKUP_Clear();
// 从RTC获取当前日期
RTC_Get_Date(&year, &month, &date, &week);
// 从RTC获取当前时间
RTC_Get_Time(&hour, &min, &sec);
// 使用NumToStr函数将日期和时间格式化为字符串
// 此处NumToStr函数的实现未给出,假设它能够将数字转换为格式化的字符串
p = NumToStr("%02d/%02d/%02d %02d:%02d:%02d 星期%d\n", year, month, date, hour, min, sec, week);
// 通过串口发送格式化的日期和时间字符串
uart_send_string(UART_User, p);
// 打印当前日期和时间到控制台或调试接口
printf("%02d/%02d/%02d %02d:%02d:%02d 星期%d\n", year, month, date, hour, min, sec, week);
}
}
//======================================================================
// 程序名称:RTC_Alarm_IRQHandler
// 中断类型:RTC闹钟中断处理函数
//======================================================================
void RTC_Alarm_IRQHandler(void)
{
// 检查是否发生了闹钟A的中断
if (RTC_Alarm_Get_Int(A)) // A是闹钟的标识,具体值可能依赖于硬件
{
// 清除闹钟A的中断标志
RTC_Alarm_Clear(A);
// 打印闹钟响了的提示信息
printf("闹钟触发了!\n");
// 打印设置的姓名
printf("蔡潮嘉\n");
// 控制GPIO,点亮绿灯,指示闹钟时间到
gpio_set(LIGHT_GREEN, LIGHT_ON);
}
}
3、利用PWM脉宽调制,交替显示红灯的5个短闪和5个长闪。
uint8_t dFlag = 1; // 定义并初始化标志变量dFlag,用于控制长闪和短闪的显示
// 主循环,用于控制PWM的占空比变化和闪烁模式
for (uint8_t i = 1; i <= 5; i++)
{
// 更新PWM的占空比
pwm_update(PWM_USER, m_duty);
// 使占空比递增,每次增加5%
m_duty = m_duty + 5.0;
// 当占空比达到或超过90%时,将其重置为1%,以便循环使用
if (m_duty >= 90.0) m_duty = 1.0;
// 用于控制一个周期内相同占空比的PWM波形打印三次
for (m_i = 0; m_i < 3; m_i++)
{
m_K = 0; // 初始化m_K,用于控制do-while循环
do
{
// 读取PWM_USER引脚的当前状态
mFlag = gpio_get(PWM_USER);
// 如果当前为高电平,并且Flag为1,表示应该显示长闪
if ((mFlag == 1) && (Flag == 1))
{
Flag = 0; // 切换Flag状态,准备显示短闪
m_K++; // 增加m_K,以便退出do-while循环
// 反转LIGHT_RED引脚的状态,实现LED闪烁
gpio_reverse(LIGHT_RED);
// 如果dFlag为1,表示是第一次长闪,打印信息并延时
if (dFlag == 1)
{
Delay_ms(1000); // 延时1秒
printf("第%d次长闪\n", i); // 打印长闪次数
dFlag = 0; // 重置dFlag,准备下一次短闪
continue; // 跳过本次循环的剩余部分
}
// 重置dFlag,准备下一次长闪
dFlag = 1;
printf("第%d次短闪\n", i); // 打印短闪次数
}
// 如果当前为低电平,并且Flag为0,表示应该显示短闪
else if ((mFlag == 0) && (Flag == 0))
{
Flag = 1; // 切换Flag状态,准备显示长闪
m_K++; // 增加m_K,以便退出do-while循环
// 反转LIGHT_RED引脚的状态,实现LED闪烁
gpio_reverse(LIGHT_RED);
}
}
// 循环直到m_K大于等于1,即至少打印一次完整的PWM波形
while (m_K < 1);
}
} // 结束for循环
4、GEC39定义为输出引脚,GEC10定义为输入引脚,用杜邦线将两个引脚相连,验证捕捉实验程序Incapture-Outcmp-20211110,观察输出的时间间隔。
for(;;) //for(;;)(开头)
{
flag = gpio_get(INCAP_USER);
//灯状态标志mFlag为'L',改变灯状态及标志
if (mFlag=='L' && flag == 1) //判断灯的状态标志
{
mFlag='A'; //灯的状态标志
gpio_set(LIGHT_BLUE,LIGHT_ON); //灯“亮”
}
//如灯状态标志mFlag为'A',改变灯状态及标志
else if(mFlag=='A' && flag == 0) //判断灯的状态标志
{
mFlag='L'; //灯的状态标志
gpio_set(LIGHT_BLUE,LIGHT_OFF); //灯“暗”
}
}
可以看到时间间隔逐渐减少