编程技巧(基于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)将程序编译、下载,按照程序功能与要求进行调试。

相关推荐
FreakStudio1 小时前
开源一款串口舵机驱动扩展板-FreakStudio多米诺系列
单片机·嵌入式·大学生·电子diy
艾格北峰2 小时前
STM32 物联网智能家居 (六) OLED显示设备
arm开发·stm32·单片机·嵌入式硬件·物联网·智能家居
weixin_535854225 小时前
oppo,汤臣倍健,康冠科技,高途教育25届春招内推
c语言·前端·嵌入式硬件·硬件工程·求职招聘
热爱嵌入式的小许5 小时前
STM32 HAL库&标准库+ESP8266+机智云
stm32·单片机·嵌入式硬件·stm32移植机智云·stm32连接机智云·hal库移植机智云·标准库移植机智云
无际单片机编程6 小时前
面对STM32的庞大体系,如何避免迷失在细节中?
java·stm32·单片机·嵌入式硬件·嵌入式开发
【云轩】7 小时前
【零基础实战】用STM32玩转DRV8313电机驱动:从原理到无人机/机器人控制
stm32·机器人·无人机
2301_764602238 小时前
stm32hal库寻迹+蓝牙智能车(STM32F103C8T6)
stm32·单片机·嵌入式硬件
楼台的春风9 小时前
PWM(脉宽调制)技术详解:从基础到应用实践示例
c语言·stm32·单片机·嵌入式硬件·mcu·物联网·嵌入式
Jack1530276827910 小时前
芯谷D668:便携式录音机与耳机式盒式录音机的理想音频解决方案
嵌入式硬件·音视频·家庭影院·麦克风阵列处理器·便携式录音机·耳机式盒式录音机
深圳市青牛科技实业有限公司 小芋圆10 小时前
芯谷D2761:为扬声器保护而生的音频限幅器
人工智能·科技·单片机·嵌入式硬件·机器人·音视频