编程技巧(基于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。

cpp 复制代码
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文件中编写获取键码值的子函数。

cpp 复制代码
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定时中断函数调用。

cpp 复制代码
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文件中的主函数调用。

cpp 复制代码
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定时中断函数调用。

cpp 复制代码
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文件中的主函数调用。

cpp 复制代码
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中断函数中。

cpp 复制代码
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		Key_Tick();
		LED_Tick();
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

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

cpp 复制代码
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)将程序编译、下载,按照程序功能与要求进行调试。

相关推荐
可待电子单片机设计定制(论文)1 小时前
【STM32设计】数控直流稳压电源的设计与实现(实物+资料+论文)
stm32·嵌入式硬件·mongodb
march_birds2 小时前
FreeRTOS 与 RT-Thread 事件组对比分析
c语言·单片机·算法·系统架构
小麦嵌入式3 小时前
Linux驱动开发实战(十一):GPIO子系统深度解析与RGB LED驱动实践
linux·c语言·驱动开发·stm32·嵌入式硬件·物联网·ubuntu
触角010100014 小时前
STM32F103低功耗模式深度解析:从理论到应用实践(上) | 零基础入门STM32第九十二步
驱动开发·stm32·单片机·嵌入式硬件·物联网
昊虹AI笔记4 小时前
使用STM32CubeMX和Keil在STM32上创建并运行一个简单的FreeRTOS多任务程序
stm32·单片机·嵌入式硬件
王光环5 小时前
单片机使用printf,不用微库
单片机·嵌入式硬件
LS_learner5 小时前
小智机器人关键函数解析,Application::OutputAudio()处理音频数据的输出的函数
人工智能·嵌入式硬件
西城微科方案开发6 小时前
体重秤PCBA电路方案组成结构
单片机·嵌入式硬件
深圳市青牛科技实业有限公司6 小时前
「青牛科技 」GC4931P/4938/4939 12-24V三相有感电机驱动芯片 对标Allegro A4931/瑞盟MS4931
科技·单片机·扫地机器人吸尘·筋膜枪电机·驱动轮电机·服务机器人驱动轮电机·工业机器人减速电机
集和诚JHCTECH6 小时前
集和诚携手Intel重磅发布BRAV-7820边缘计算新品,为车路云一体化场景提供强大算力支撑
人工智能·嵌入式硬件·边缘计算