当前位置: 首页 > news >正文

编程技巧(基于STM32)第一章 定时器实现非阻塞式程序——按键控制LED灯闪烁模式

参考教程:[编程技巧] 第1期 定时器实现非阻塞式程序 按键控制LED闪烁模式_哔哩哔哩_bilibili

一、实验前信息储备

1、程序功能与要求

(1)程序功能:两个按键分别控制两个LED灯的闪烁模式,每按下1个按键,对应的LED灯切换点亮模式。

(2)程序要求:

①按键灵敏,每次按键按下都能准确切换模式,不可出现按一次按键没有反应或者一口气做了若干次模式切换的情况。

②模块要高度封装,主程序调用要简洁。

③在任何时候模块代码都不能阻塞主程序。

2、阻塞和非阻塞的概念

(1)阻塞:执行某段程序时,CPU因为需要等待延时或者等待某个信号而被迫处于暂停状态一段时间,程序执行时间较长或者时间不定。

(2)非阻塞:执行某段程序时,CPU不会等待,程序很快执行结束。

3、定时器扫描按键的方法

(1)定时器扫描按键-单按键:

①使用定时中断,每隔20ms读取一次本次引脚电平和上次引脚电平。

②判断,如果本次次引脚电平是1,上次次引脚电平是0,则表示按键先前被按下且当前处于刚松手的状态。

(2)定时器扫描按键-多按键:

①先写一个获取键码值的子函数(非阻塞式,即获取当前哪个接了按键的引脚为低电平0,返回其对应的键码值,如全部接按键的引脚均为高电平0,则返回键码值0)。

②使用定时中断,每隔20ms读取一次本次键码值和上次键码值。

③判断,如果本次是0,上次非0,则表示按键按下且当前处于刚松手的状态,记录最近一次捕获到的非0键码,并向主程序报告此事件。

4、定时器实现LED闪烁

(1)在定时中断函数中定义计次变量(静态),每隔1ms计次变量自增,计到周期值时归零。

(2)判断,如果计次变量小于一个比较值则开灯,否则关灯。

二、实验步骤

1、准备工作

(1)拷贝一份STM32教程中“使用OLED屏进行显示”的工程文件夹,并更名为“定时器实现非阻塞式程序”。

(2)在STM32教程中“定时器定时中断”的工程文件夹中找到Timer.c和Timer.h文件,将其添加进本工程中,并将TIM2的定时时间配置为1ms。

void Timer_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	TIM_InternalClockConfig(TIM2);
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;        //重装载值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;    //预分频系数(TIM2的频率是72MHz)
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
	
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_Cmd(TIM2, ENABLE);
}

2、按键模块编写

(1)在Key.c文件中编写获取键码值的子函数。

uint8_t Key_GetState(void)
{
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
	{
		return 1;   //按键1按下,返回键码1
	}
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)
	{
		return 2;   //按键2按下,返回键码2
	}
	return 0;       //无按键按下,返回键码0
}

(2)在Key.c文件中编写多按键扫描(需要记录键码,而不仅仅是读取)的函数,并在头文件中声明,供main.c文件中的TIM2定时中断函数调用。

uint8_t Key_Num;     //记录最近一次捕获到的非0键码(也可认为是事件标志位)

void Key_Tick(void)   //供TIM2定时中断函数调用
{
	static uint8_t Count;     //用于分频
	static uint8_t CurrState;  //存储当前按键状态
	static uint8_t PrevState;  //存储上次按键状态
	
	Count++;             //TIM2定时中断函数每1ms执行一次
	if(Count >= 20)        //Key_Tick函数每20ms执行一次
	{
		Count = 0;
		
		PrevState = CurrState;        //获取上次按键状态
		CurrState = Key_GetState();    //获取当前按键状态
		
		if(CurrState == 0 && PrevState != 0)  //检测到有按键按下且当前已松手
		{
			Key_Num = PrevState;  //记录最近一次捕获到的非0键码
		}
	}
}

(3)在Key.c文件中编写向主函数报告按键事件的函数,并在头文件中声明,供main.c文件中的主函数调用。

uint8_t Key_GetNum(void)
{
	uint8_t Temp;
	if(Key_Num)   //定时中断没有记录到非0键码时勿入
	{
		Temp = Key_Num;
		//防止按键松开时定时中断发生在此处,中断结束后Key_Num清零
		//而Temp也没记录到键码,这会导致按键“失灵”
		Key_Num = 0;   //事件标志位清零
		return Temp;    //将非0键码返回
	}
	return 0;
}

3、LED模块编写

(1)在LED.c文件中编写控制LED灯闪烁的函数,并在头文件中声明,供main.c文件中的TIM2定时中断函数调用。

uint16_t LED1_Count;    //LED1的计次变量
uint16_t LED2_Count;    //LED2的计次变量
uint8_t LED1_Mode;     //维护LED1的模式
uint8_t LED2_Mode;     //维护LED2的模式

void LED_Tick(void)
{
	if(LED1_Mode == 0)      //常暗
		LED1_OFF();
	else if(LED1_Mode == 1)  //常亮
		LED1_ON();
	else if(LED1_Mode == 2)  //慢闪
	{
		LED1_Count++;LED1_Count %= 1000;  //周期为1000ms
		if(LED1_Count < 500) LED1_ON();     //周期内500ms,LED为亮
		else              LED1_OFF();
	}
	else if(LED1_Mode == 3)  //快闪
	{
		LED1_Count++;LED1_Count %= 100;  //周期为100ms
		if(LED1_Count < 50)  LED1_ON();    //周期内50ms,LED为亮
		else               LED1_OFF();
	}
	else if(LED1_Mode == 4)  //点闪
	{
		LED1_Count++;LED1_Count %= 1000;
		if(LED1_Count < 100) LED1_ON();
		else              LED1_OFF();
	}
	
	if(LED2_Mode == 0)      //常暗
		LED2_OFF();
	else if(LED2_Mode == 1)  //常亮
		LED2_ON();
	else if(LED2_Mode == 2)  //慢闪
	{
		LED2_Count++;LED2_Count %= 1000;
		if(LED2_Count < 500) LED2_ON();
		else              LED2_OFF();
	}
	else if(LED2_Mode == 3)  //快闪
	{
		LED2_Count++;LED2_Count %= 100;
		if(LED2_Count < 50)  LED2_ON();
		else               LED2_OFF();
	}
	else if(LED2_Mode == 4)  //点闪
	{
		LED2_Count++;LED2_Count %= 1000;
		if(LED2_Count < 100) LED2_ON();
		else              LED2_OFF();
	}
}

(2)在LED.c文件中编写更改LED闪烁模式的函数,并在头文件中声明,函数参数为期望闪烁模式,供main.c文件中的主函数调用。

void LED1_SetMode(uint8_t Mode)
{
	if(Mode != LED1_Mode)    //如果期望模式和当前模式一致,则跳过
	{
		LED1_Mode = Mode;  //模式更改
		LED1_Count = 0;      //周期可能变更,计次变量要从零开始自增
	}
}

void LED2_SetMode(uint8_t Mode)
{
	if(Mode != LED2_Mode)    //如果期望模式和当前模式一致,则跳过
	{
		LED2_Mode = Mode;  //模式更改
		LED2_Count = 0;      //周期可能变更,计次变量要从零开始自增
	}
}

4、main.c文件编写与调试

(1)将按键模块和LED模块供定时中断函数调用的函数添加进TIM2中断函数中。

void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		Key_Tick();
		LED_Tick();
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

(2)更改主函数和新增4个全局变量,并添加相应的头文件。

uint16_t i;
uint8_t KeyNum;
uint8_t LED1Mode, LED2Mode;

int main(void)
{
	OLED_Init();
	Key_Init();
	LED_Init();
	Timer_Init();
	
	OLED_ShowString(1, 1, "i:");
	OLED_ShowString(2, 1, "LED1Mode:");
	OLED_ShowString(3, 1, "LED2Mode:");
	
	while (1)
	{
		KeyNum = Key_GetNum();     //获取按键事件
		if(KeyNum == 1)
		{
			LED1Mode++;
			LED1Mode %= 5;        //闪烁模式轮换
			LED1_SetMode(LED1Mode);  //模式设置
		}
		if(KeyNum == 2)
		{
			LED2Mode++;
			LED2Mode %= 5;        //闪烁模式轮换
			LED2_SetMode(LED2Mode);  //模式设置
		}
		
		OLED_ShowNum(1, 3, i++, 5);  //观察主循环是否在一直运行,是否保持快速刷新
		OLED_ShowNum(2, 10, LED1Mode, 1);
		OLED_ShowNum(3, 10, LED2Mode, 1);
	}
}

(3)将程序编译、下载,按照程序功能与要求进行调试。

相关文章:

  • Spring Boot 定时任务:轻松实现任务自动化
  • PyQt6/PySide6 的信号与槽原理
  • YOLOv5-Seg 深度解析:与 YOLOv5 检测模型的区别
  • 四元数如何用于 3D 旋转(代替欧拉角和旋转矩阵)【ESP32指向鼠标】
  • 基于Python的Optimal Interpolation (OI) 方法实现
  • ZZNUOJ(C/C++)基础练习1091——1100(详解版)⭐
  • 差分解方程
  • [矩形绘制]
  • 图的遍历: 广度优先遍历和深度优先遍历
  • FPGA的星辰大海
  • Windows环境下使用Ollama搭建本地AI大模型教程
  • MAC 系统关闭屏幕/睡眠 后被唤醒 Wake Requests
  • spring针对抽象类注入属性
  • 6.2.1 数据模型的基本概念、数据模型三要素
  • Linux alias使用
  • Ant-Design-Vue:Button按钮SVG图标垂直未居中问题
  • 深度学习R4周:LSTM-火灾温度预测
  • 【Deepseek 零门槛指南】DeepSeek 教程和常见问题解答 | 大白技术控
  • 一文理解蓝牙Core 6.0 channel sounding精确测距
  • Python学习心得数据的验证
  • 陈吉宁龚正黄莉新胡文容等在警示教育基地参观学习,出席深入贯彻中央八项规定精神学习教育交流会
  • 京东回应外卖系统崩溃:订单暴涨所致,已恢复
  • 大外交|中美联合声明拉升全球股市,专家:中美相向而行为世界提供确定性
  • 通辽警方侦破一起积压21年的命案:嫌疑人企图强奸遭反抗后杀人
  • 体坛联播|巴萨4比3打服皇马,利物浦2比2战平阿森纳
  • “海豚音”依旧,玛丽亚·凯莉本周来沪开唱