按键FIFO实现以及单片机时间片轮循法实现

目录

本文简介

实验平台

正文

一、主函数实现

二、按键驱动

C文件

H文件

三、SYSTICK定时器实现时间片轮循法方法

C文件

H文件


本文简介


本文主要介绍一套按键驱动代码以及使用SYSTICK定时器实现延时和嵌入式单片机时间片轮循法实现。

实验平台

①单片机型号:STM32F103RCT6

②编译软件:KEIL5

③硬件平台:正点原子STM32-MINI开发板

④仿真器:J-Link

版权声明
①作者:KELIN

②声明:本文主要参考安富莱的BSP驱动,如若有误解的地方,请联系纠正。

③纠错/业务合作:[email protected]

正文

一、主函数实现

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "key.h"
#include "timer.h"

void led_blink(void)
{
	static uint8_t i=0;
	
	
	if(!i){
		LED1=0;
		i=1;
	}else{
		LED1=1;
		i=0;
	}

}

 int main(void)
 {	
	u8 t=0;	  
	delay_init();
	LED_Init();		  	 	//初始化与LED连接的硬件接口
	bk_api_key_init();
	SysTick_Init();
	//LED0=0;					//点亮LED
	while(1)
	{
		if(s_Tmr.Flag_1ms){
			s_Tmr.Flag_1ms=0;
            task_x();
		}
		if(s_Tmr.Flag_10ms){
			s_Tmr.Flag_10ms=0;
			bk_api_key_scan();		//得到键值
			t = bk_api_key_fifo_out();
			if (t != KEY_NONE){
				switch(t){
					case KEY_1_DOWN:
						LED0=0;
					break;
					case KEY_1_UP:
						LED0=1;
					break;			
					case KEY_1_LONG:
						LED0=1;				
					break;			
				}
			}
		}
		if(s_Tmr.Flag_100ms){
			s_Tmr.Flag_100ms=0;
			
            task_xx();
		}
		if(s_Tmr.Flag_500ms){
			s_Tmr.Flag_500ms=0;
		     task_xxx();
		}
		if(s_Tmr.Flag_1s){
			s_Tmr.Flag_1s=0;
			led_blink();
		}
	}
}

二、按键驱动

C文件

#include "key.h"

//全局变量
static KEY_T s_tBtn[KID_BUTT];
static KEY_FIFO_T s_tKey;		/* 按键FIFO变量,结构体 */


//函数声明
static void bk_api_key_hal_init(void);
static void bk_api_key_var_init(void);




static uint8_t IsKeyDown1(void) 
{
    if (!GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)){
        return 1;
	}else{ 
        return 0;
	}
}

static uint8_t IsKeyDown2(void) 
{
    if (!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15)){
        return 1;
	}else{ 
        return 0;
	}
}

static uint8_t IsKeyDown3(void) 
{
    if (GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)){
        return 1;
	}else{ 
        return 0;
	}
}

/*
*********************************************************************************************************
*	函 数 名: bk_api_key_init
*	功能说明: 初始化按键
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void bk_api_key_init(void)
{
	bk_api_key_hal_init();
	bk_api_key_var_init();
}


/*
*********************************************************************************************************
*	函 数 名: bk_api_key_hal_init
*	功能说明: 初始化按键硬件
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
static void bk_api_key_hal_init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);//使能PORTA,PORTC时钟

	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//关闭jtag,使能SWD,可以用SWD模式调试
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_15;//PA15
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
 	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA15
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_5;//PC5
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
 	GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIOC5
 
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;//PA0
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0设置成输入,默认下拉	  
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0	
}

/*
*********************************************************************************************************
*	函 数 名: bk_api_key_var_init
*	功能说明: 初始化按键变量
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
static void bk_api_key_var_init(void)
{
    uint8_t i;

	/* 给每个按键结构体成员变量赋一组缺省值 */
	for (i = 0; i < KID_BUTT; i++)
	{
		s_tBtn[i].LongTime = KEY_LONG_TIME;			/* 长按时间 0 表示不检测长按键事件 */
		s_tBtn[i].Count = KEY_FILTER_TIME / 2;		/* 计数器设置为滤波时间的一半 */
		s_tBtn[i].State = 0;						/* 按键缺省状态,0为未按下 */
		s_tBtn[i].RepeatSpeed = 0;					/* 按键连发的速度,0表示不支持连发 */
		s_tBtn[i].RepeatCount = 0;					/* 连发计数器 */
	}
	/* 如果需要单独更改某个按键的参数,可以在此单独重新赋值 */
	/* 比如,我们希望按键1按下超过1秒后,自动重发相同键值 */
	s_tBtn[KID_K1].LongTime = 200;
	s_tBtn[KID_K1].RepeatSpeed = 5;	/* 每隔50ms自动发送键值 */
	
	s_tBtn[KID_K2].LongTime = 200;
	s_tBtn[KID_K2].RepeatSpeed = 5;	/* 每隔50ms自动发送键值 */
	
	s_tBtn[KID_JOY_U].LongTime = 200;
	s_tBtn[KID_JOY_U].RepeatSpeed = 5;	/* 每隔50ms自动发送键值 */

	/* 判断按键按下的函数 */
	s_tBtn[0].IsKeyDownFunc = IsKeyDown1;
	s_tBtn[1].IsKeyDownFunc = IsKeyDown2;

	/* 组合键 */
    s_tBtn[2].IsKeyDownFunc = IsKeyDown3;
}

/*
*********************************************************************************************************
*	函 数 名: bk_api_key_set_keyParam
*	功能说明: 设置按键参数
*	形    参:_ucKeyID : 按键ID,从0开始
*			_LongTime : 长按事件时间
*			 _RepeatSpeed : 连发速度
*	返 回 值: 无
*********************************************************************************************************
*/
void bk_api_key_set_keyParam(uint8_t _ucKeyID, uint16_t _LongTime, uint8_t  _RepeatSpeed)
{
	s_tBtn[_ucKeyID].LongTime = _LongTime;			/* 长按时间 0 表示不检测长按键事件 */
	s_tBtn[_ucKeyID].RepeatSpeed = _RepeatSpeed;	/* 按键连发的速度,0表示不支持连发 */
	s_tBtn[_ucKeyID].RepeatCount = 0;				/* 连发计数器 */
}


//清空缓冲区
void bk_api_key_fifo_clearKey(void)
{
	s_tKey.Read = s_tKey.Write;
}

/*
*********************************************************************************************************
*	函 数 名: bk_api_key_fifo_in
*	功能说明: 将1个键值压入按键FIFO缓冲区。可用于模拟一个按键。
*	形    参:  _KeyCode : 按键代码
*	返 回 值: 无
*********************************************************************************************************
*/
void bk_api_key_fifo_in(uint8_t _keyCode)
{
    s_tKey.Buf[s_tKey.Write] = _keyCode;

    if(++s_tKey.Write >= KEY_FIFO_SIZE){
        s_tKey.Write = 0;
    }
}

/*
*********************************************************************************************************
*	函 数 名: bk_api_key_fifo_out
*	功能说明: 从按键FIFO缓冲区读取一个键值。
*	形    参:  无
*	返 回 值: 按键代码
*********************************************************************************************************
*/
uint8_t bk_api_key_fifo_out(void)
{
    uint8_t ret=KEY_NONE;

    if(s_tKey.Read == s_tKey.Write){
        //缓冲区为空
        return KEY_NONE;
    }else{
        ret = s_tKey.Buf[s_tKey.Read];
        if(++s_tKey.Read >= KEY_FIFO_SIZE){
            s_tKey.Read=0;
        }
    }
    return ret;
}

/*
*********************************************************************************************************
*	函 数 名: bk_api_key_getState
*	功能说明: 读取按键的状态
*	形    参:  _ucKeyID : 按键ID,从0开始
*	返 回 值: 1 表示按下, 0 表示未按下
*********************************************************************************************************
*/
uint8_t bk_api_key_getState(KEY_ID_E _ucKeyID)
{
	return s_tBtn[_ucKeyID].State;
}

/*
*********************************************************************************************************
*	函 数 名: bk_api_key_detectKey
*	功能说明: 检测一个按键。非阻塞状态,必须被周期性的调用。
*	形    参:  按键结构变量指针
*	返 回 值: 无
*********************************************************************************************************
*/
void bk_api_key_detectKey(uint8_t i)
{
    KEY_T *pBtn=0;

    //如果没有初始化按键函数,则报错
    // if (s_tBtn[i].IsKeyDownFunc == 0)
    // {
    //     printf("Fault : DetectButton(), s_tBtn[i].IsKeyDownFunc undefine");
    // }

    pBtn = &s_tBtn[i];

    if(pBtn->IsKeyDownFunc()){
        //有按键按下 1:滤波
        if(pBtn->Count < KEY_FILTER_TIME){
            pBtn->Count=KEY_FILTER_TIME;
        }else if(pBtn->Count < 2 * KEY_FILTER_TIME){
            pBtn->Count++;
        }else{
            //2:短按
            if(pBtn->State == 0){
                pBtn->State = 1;
                //短按按下
                bk_api_key_fifo_in((uint8_t)3*i+1);
            }
            //3:长按
            if(pBtn->LongTime > 0){
                if(pBtn->LongCount < pBtn->LongTime){
                    //长按时间未到
                    if(++pBtn->LongCount >= pBtn->LongTime){
                        //长按按下
                        bk_api_key_fifo_in((uint8_t)3*i+3);
                    }
                }else{
                    //4:检测长按持续
                    if(pBtn->RepeatSpeed > 0){
						if(++pBtn->RepeatCount >= pBtn->RepeatSpeed){
							//检测到长按持续
							pBtn->RepeatCount = 0;
							//长按按键后,每隔RepeatSpeed ms发送一次短按
							bk_api_key_fifo_in((uint8_t)3*i+1);
						}
                    }
                }
            }
        }
    }else{
        //没有按键按下
        if(pBtn->Count > KEY_FILTER_TIME){
            pBtn->Count = KEY_FILTER_TIME;
        }else if(pBtn->Count != 0){
            //松开防抖
            pBtn->Count--;
        }else{
            if(pBtn->State == 1){
                //5:有按键按下->弹起
                pBtn->State=0;
                bk_api_key_fifo_in((uint8_t)3*i+2);
            }
        }
        pBtn->LongCount = 0;
        pBtn->RepeatCount=0;
    }
}

/*
*********************************************************************************************************
*	函 数 名: bk_api_key_scan
*	功能说明: 扫描所有按键。非阻塞,被systick中断周期性的调用
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void bk_api_key_scan(void)
{
    uint8_t i;

    for(i=0;i

H文件

#ifndef __KEY_H
#define __KEY_H	 
#include "sys.h"
 

#define KEY0_PRES	1		//KEY0  
#define KEY1_PRES	2		//KEY1 
#define WKUP_PRES	3		//WK_UP  


/* 按键ID, 主要用于bsp_KeyState()函数的入口参数 */
typedef enum
{
	KID_K1 = 0,
	KID_K2,

	KID_JOY_U,


    KID_BUTT
}KEY_ID_E;


/*
	每个按键对应1个全局的结构体变量。
*/
typedef struct
{
	/* 下面是一个函数指针,指向判断按键手否按下的函数 */
	uint8_t (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1表示按下 */

	uint8_t  Count;			/* 滤波器计数器 */
	uint16_t LongCount;		/* 长按计数器 */
	uint16_t LongTime;		/* 按键按下持续时间, 0表示不检测长按 */
	uint8_t  State;			/* 按键当前状态(按下还是弹起) 0:弹起  1:按下*/
	uint8_t  RepeatSpeed;	/* 连续按键周期 */
	uint8_t  RepeatCount;	/* 连续按键计数器 */
}KEY_T;

/*
	定义键值代码, 必须按如下次序定时每个键的按下、弹起和长按事件

	推荐使用enum, 不用#define,原因:
	(1) 便于新增键值,方便调整顺序,使代码看起来舒服点
	(2) 编译器可帮我们避免键值重复。
*/
typedef enum
{
	KEY_NONE = 0,			/* 0 表示按键事件 */

	KEY_1_DOWN,				/* 1键按下 1*/ 
	KEY_1_UP,				/* 1键弹起 2*/
	KEY_1_LONG,				/* 1键长按 3*/

	KEY_2_DOWN,				/* 2键按下 4*/
	KEY_2_UP,				/* 2键弹起 5*/
	KEY_2_LONG,				/* 2键长按 6*/
	/* 组合键 */
	KEY_9_DOWN,				/* 9键按下 7*/
	KEY_9_UP,				/* 9键弹起 8*/
	KEY_9_LONG,				/* 9键长按 9*/
}KEY_ENUM;


#define KEY_FILTER_TIME   10
#define KEY_LONG_TIME     100			


/* 按键FIFO用到变量 */
#define KEY_FIFO_SIZE	10
typedef struct
{
	uint8_t Buf[KEY_FIFO_SIZE];		/* 键值缓冲区 */
	uint8_t Read;					/* 缓冲区读指针1 */
	uint8_t Write;					/* 缓冲区写指针 */
	uint8_t Read2;					/* 缓冲区读指针2 */
}KEY_FIFO_T;

void bk_api_key_init(void);
void bk_api_key_scan(void);
void bk_api_key_fifo_in(uint8_t _keyCode);
uint8_t bk_api_key_fifo_out(void);
#endif

三、SYSTICK定时器实现时间片轮循法方法

C文件

#include "timer.h"
//#include "core_cm3.h"
//#include "system_stm32f10x.h"

SYS_SOFT_TMR	s_Tmr={0};

/* 这2个全局变量转用于 bsp_DelayMS() 函数 */
static volatile uint32_t s_uiDelayCount = 0;
static volatile uint8_t s_ucTimeOutFlag = 0;

/*
* 函数名:SysTick_Init
* 描述         :启动系统滴答定时器 SysTick
* 输入  : 无
* 输出  :无
* 调用  : 外部调用
*/
void SysTick_Init(void)
{
	SysTick_Config(SystemCoreClock/1000);         //1ms定时器
    //SysTick->CTRL &= SysTick_CTRL_ENABLE_Msk;                         //若无法启动则关闭
}

/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */
void SysTick_Handler(void)
{

	/* 每隔1ms进来1次 (仅用于 bsp_DelayMS) */
	if (s_uiDelayCount > 0)
	{
		if (--s_uiDelayCount == 0)
		{
			s_ucTimeOutFlag = 1;
		}
	}	
	s_Tmr.Count_1ms++;
	s_Tmr.Flag_1ms=1;
	if(s_Tmr.Count_1ms>=10){
		s_Tmr.Count_1ms=0;
		s_Tmr.Flag_10ms=1;
		s_Tmr.Count_10ms++;
		if(s_Tmr.Count_10ms>=10){
			s_Tmr.Count_10ms=0;
			s_Tmr.Flag_100ms=1;
			s_Tmr.Count_100ms++;
			if(s_Tmr.Count_100ms>=5){
				s_Tmr.Count_100ms=0;
				s_Tmr.Flag_500ms=1;
				s_Tmr.Count_500ms++;
				if(s_Tmr.Count_500ms>=2){
					s_Tmr.Count_500ms=0;
					s_Tmr.Flag_1s=1;
				}
			}
		}
	}
}

/*
*********************************************************************************************************
*	函 数 名: bk_sys_timer_idle
*	功能说明: 空闲时执行的函数。一般主程序在for和while循环程序体中需要插入 CPU_IDLE() 宏来调用本函数。
*			 本函数缺省为空操作。用户可以添加喂狗、设置CPU进入休眠模式的功能。
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void bk_sys_timer_idle()
{
	/* --- 喂狗 */

	/* --- 让CPU进入休眠,由Systick定时中断唤醒或者其他中断唤醒 */

	/* 对于 emWin 图形库,可以插入图形库需要的轮询函数 */
	//GUI_Exec();

	/* 对于 uIP 协议实现,可以插入uip轮询函数 */	
}

/*
*********************************************************************************************************
*	函 数 名: bk_sys_timer_delay_ms
*	功能说明: ms级延迟,延迟精度为正负1ms
*	形    参:  n : 延迟长度,单位1 ms。 n 应大于2
*	返 回 值: 无
*********************************************************************************************************
*/
void bk_sys_timer_delay_ms(uint32_t n)
{
	if (n == 0)
	{
		return;
	}
	else if (n == 1)
	{
		n = 2;
	}

	DISABLE_INT();  			/* 关中断 */

	s_uiDelayCount = n;
	s_ucTimeOutFlag = 0;

	ENABLE_INT();  				/* 开中断 */

	while (1)
	{
		bk_sys_timer_idle();				/* CPU空闲执行的操作, 见 bsp.c 和 bsp.h 文件 */

		/*
			等待延迟时间到
			注意:编译器认为 s_ucTimeOutFlag = 0,所以可能优化错误,因此 s_ucTimeOutFlag 变量必须申明为 volatile
		*/
		if (s_ucTimeOutFlag == 1)
		{
			break;
		}
	}
}


/*
*********************************************************************************************************
*    函 数 名: bk_sys_delay_us
*    功能说明: us级延迟。 必须在systick定时器启动后才能调用此函数。
*    形    参:  n : 延迟长度,单位1 us
*    返 回 值: 无
*********************************************************************************************************
*/
void bk_sys_delay_us(uint32_t n)
{
    uint32_t ticks;
    uint32_t told;
    uint32_t tnow;
    uint32_t tcnt = 0;
    uint32_t reload;
       
	reload = SysTick->LOAD;                
    ticks = n * (SystemCoreClock / 1000000);	 /* 需要的节拍数 */  
    
    tcnt = 0;
    told = SysTick->VAL;             /* 刚进入时的计数器值 */

    while (1)
    {
        tnow = SysTick->VAL;    
        if (tnow != told)
        {    
            /* SYSTICK是一个递减的计数器 */    
            if (tnow < told)
            {
                tcnt += told - tnow;    
            }
            /* 重新装载递减 */
            else
            {
                tcnt += reload - tnow + told;    
            }        
            told = tnow;

            /* 时间超过/等于要延迟的时间,则退出 */
            if (tcnt >= ticks)
            {
            	break;
            }
        }  
    }
} 

H文件

#ifndef __TIMER_H__
#define __TIMER_H__

#include "sys.h"  

typedef struct{
	volatile uint8_t  Count_1ms;	/* 计数器 */
	volatile uint8_t  Flag_1ms;		/* 定时到达标志  */
	volatile uint8_t  Count_10ms;	/* 计数器 */
	volatile uint8_t  Flag_10ms;	/* 定时到达标志  */
	volatile uint8_t  Count_100ms;	/* 计数器 */
	volatile uint8_t  Flag_100ms;	/* 定时到达标志  */
	volatile uint8_t  Count_500ms;	/* 计数器 */
	volatile uint8_t  Flag_500ms;	/* 定时到达标志  */
	volatile uint8_t  Count_1s;		/* 计数器 */
	volatile uint8_t  Flag_1s;		/* 定时到达标志  */
}SYS_SOFT_TMR;

extern SYS_SOFT_TMR	s_Tmr;//全局声明

/* 开关全局中断的宏 */
#define ENABLE_INT()	__set_PRIMASK(0)	/* 使能全局中断 */
#define DISABLE_INT()	__set_PRIMASK(1)	/* 禁止全局中断 */


void SysTick_Init(void);
void bk_sys_timer_delay_ms(uint32_t n);
void bk_sys_delay_us(uint32_t n);
#endif

你可能感兴趣的:(stm32,单片机)