本实验设计基于正点电子 STM32 NANO 开发板,利用中断机制进行定时、检测按键动作。
设计实验版的软件,包括以下功能
开发板已经提供了一个8位数码管、5枚按键(KEY0KEY2、KEY_UP,本实验只用到KEY0和KEY1)、8颗LED(LED0LED7)
因为本次实验需要控制数码管按时间变化,同时需要随时读取按键状态,所以使用CPU的中断机制完成本实验。
main
函数的while(1)
循环中,每次循环delay一定时间,就很难确保每次更新数字的间隔是0.1s。计数器起到了分频的作用。 以下图为例,分频系数psc为9,计数器对输入的时钟信号进行计数,每个CLK脉冲计数器+1,直至达到设置的最大值9,溢出信号OV为1,每十个时钟周期输出一次OV=1,相当于将CLK的频率分成了原来的1/10。
在执行中断处理函数时,计数器仍然在不断工作,因为在触发中断后通过使能使得中断处理函数不会被自己打断,所以只要中断处理函数执行时间少于2ms,计时就是准确的。
移位寄存器:每个SCK的上升沿,数据向右移动一位。
用到的函数:
LED_Write_Data
:将8bit的串行段选信息按一定时序输入到移位寄存器中(段选)。LED_Wei
:使用3-8译码器选择其中一位。LED_Refresh
:用UPDATE信号将数据锁存到数据缓冲寄存器中(位于上方的寄存器)。中断初始化有两部分:定时器中断和外部中断,此处直接在正点电子的实验例程上做修改
// 定时器中断函数 @timer.c
void TIM3_Init(u16 arr,u16 psc)
{
TIM3_Handler.Instance=TIM3; // 通用定时器3
TIM3_Handler.Init.Prescaler=psc; // 分频系数
TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; // 向上计数器
TIM3_Handler.Init.Period=arr; // 自动装载值
TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;// 时钟分频因子
TIM3_Handler.Init.
ReloadPreload = TIM_
RELOAD_PRELOAD_ENABLE;// 使能自动重载
HAL_TIM_Base_Init(&TIM3_Handler);
HAL_TIM_Base_Start_IT(&TIM3_Handler); // 使能定时器3更新中断 TIM_IT_UPDATE
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM3)
{
__HAL_RCC_TIM3_CLK_ENABLE(); // 使能TIM3时钟
HAL_NVIC_SetPriority(TIM3_IRQn,1,3); // 设置中断优先级,响应优先级1,子优先级3
HAL_NVIC_EnableIRQ(TIM3_IRQn); // 开启TIM3中断
}
}
// 外部中断初始化 @exit.c
void EXTI_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOC_CLK_ENABLE(); // 开启GPIOC时钟
GPIO_Initure.Pin=GPIO_PIN_8|GPIO_PIN_9; // PC8、PC9
GPIO_Initure.Mode=GPIO_MODE_IT_FALLING; // 下降沿触发
GPIO_Initure.Pull=GPIO_PULLUP; // 上拉
HAL_GPIO_Init(GPIOC,&GPIO_Initure);
// 中断线8、9-PC8、9
// 因为外部中断回调函数执行时间超过2ms,保证计时准确,应该将外部中断设为低优先级
HAL_NVIC_SetPriority(EXTI9_5_IRQn,3,0); // 抢占优先级为3,子优先级为0
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
}
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "smg.h"
#include "timer.h"
#include "key.h"
#include "exti.h"
SMG_num_map
c
u8 SMG_num_map[] = {0xfc,0x60,0xda,0xf2,0x66,0xb6,0xbe,0xe0,0xfe,0xf6};
// 对应的数字 0 1 2 3 4 5 6 7 8 9
将数字作为索引即可取得对应的段码。在获得的段码的基础上+1可以显示小数点。
用一个3个元素的数组表示剩余的时间
u8 display_queue[] = {2,9,9};
timing
:
表示是否还在倒计时,初始为1,当输入正确的按键序列或计时结束后设为0boom
:炸弹是否已爆炸,初始为0,当计时结束后设为1,控制数码管的函数检测到后播放爆炸动画。t1
,t2
t1
用于每0.1刷新数码管显示的时间t2
用于控制炸弹爆炸动画每一帧的持续时间
u8 passcode[] = {0,0,1,1,0,1,0,1};
u8 pointer = 0;
passcode
为正确的案件序列,每按下一次按键,将该按键的值与pointer
指针所指向元素比较,相同则pointer+1
,不同则回退指针到0。
main()
函数分别初始化LED、数码管、定时器、外部中断。
int main(void)
{
HAL_Init(); // 初始化HAL库
Stm32_Clock_Init(RCC_PLL_MUL9); // 设置时钟,72M
delay_init(72); // 初始化延时函数
uart_init(115200); // 初始化串口
LED_Init(); // 初始化LED
LED_SMG_Init(); // 初始化数码管
TIM3_Init(19,7199); // 初始化定时器,自动装载值为20-1,分频系数为7200-1
EXTI_Init(); //³õʼ»¯ÍⲿÖжÏ
while(1){
}
}
reduce_time
:设置倒计时函数每调用一次reduce_time()
函数,
若倒计时未结束,
数码管应显示时间-0.1。
若倒计时结束,
将boom
设为1,timing
设为0,即炸弹爆炸,计时停止。
u8 display_queue[] = {2,9,9};
u8 t2=0,t1=0;
void reduce_time(){
// 将display_queue数组转换为整数
int num = 100*display_queue[0] + 10*display_queue[1] + display_queue[2];
num-=1; // 数字-1
if(num+1){
// 重设display_queue
display_queue[0] = num/100;
display_queue[1] = num%100/10;
display_queue[2] = num%10;
}else{
// 设置timing 为 0 ,即停止计时,boom=1,炸弹爆炸
timing = 0;
boom = 1;
t2 = 0;
}
}
HAL_TIM_PeriodElapsedCallback
:计时器中断回调函数当发生定时器中断时,此函数被调用。每2ms被调用一次
//定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
u8 i = 0;
if(htim==(&TIM3_Handler))
{
if(!boom)
for(i = 0;i<3;i++){
// 显示数码
LED_Write_Data(SMG_num_map[display_queue[i]] + (1==i),i);
LED_Refresh();
}
else{
// 绘制爆炸动画
Go_Boom();
}
LED_Write_Data(0x00,2);
LED_Refresh();
t1++;
if(t1==50) //每100ms刷新一次
{
// 测试计时是否结束
if(timing) reduce_time();
t1=0;
}
}
}
HAL_GPIO_EXTI_Callback
:外部中断回调函数// 正确的按键序列
u8 passcode[] = {0,0,1,1,0,1,0,1};
// 指针,指向下一按键的正确值
u8 pointer = 0;
// 当按下的按键正确时,对应位置的LED点亮
u8 LED = 0x0;
// 在终端服务程序中需要做的事情
// 在HAL库中所有外部中断都会调用此函数
// GPIO_Pin:中断引脚号
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
u8 t=0;
delay_ms(10); // 消抖
switch(GPIO_Pin)
{
// 检查按下的按键
case GPIO_PIN_8:
if(KEY0==0)
{
t = 1;
}
break;
case GPIO_PIN_9:
if(KEY1==0){
t = 2;
}
break;
default:
t = 0;
}
if(t){
if(passcode[pointer]==t-1){
LED++;
// 按键正确,点亮对应的LED灯
HAL_GPIO_WritePin(GPIOC, LED, GPIO_PIN_RESET);
LED<<=1;
if(pointer<7) pointer++; // 指针右移一位
else{
timing = 0;
delay_ms(500);
// 按键序列全部正确,所有LED熄灭
HAL_GPIO_WritePin(GPIOC, 0xff, GPIO_PIN_SET);
// printf("Mission completed!");
}
}else{
// LED闪烁效果,表示密码错误
HAL_GPIO_WritePin(GPIOC, 0xff, GPIO_PIN_SET);
delay_ms(500);
HAL_GPIO_WritePin(GPIOC, 0xff, GPIO_PIN_RESET);
delay_ms(500);
HAL_GPIO_WritePin(GPIOC, 0xff, GPIO_PIN_SET);
// 回退指针
pointer = 0;
LED = 0x00;
}
}
}
效果:
Go_Boom
:在数码管上绘制爆炸动画// 用于控制每帧动画的连续变化
u8 offset=0;
// 绘制爆炸图像
void Go_Boom(){
u8 i = 0;
for(i=0;i<offset;i++){
if(i==0){
LED_Write_Data(0x60, i+4);
LED_Refresh();
LED_Write_Data(0x0c, 3-i);
LED_Refresh();
}
else{
LED_Write_Data(0x9c, 3-i);
LED_Refresh();
LED_Write_Data(0xf0, i+4);
LED_Refresh();
}
}
if(t2==200){
if(offset<4) offset++;
else boom = 0;
t2 = 0;
}
t2++;
}
效果:
由于时间仓促,经验不足,所以本实验还有许多改进空间。
delay_ms(10)
) ,但是仍然容易出现抖动的情况。在实验过程中,有一些不解的地方。