目录
本文简介
本文主要介绍一套按键驱动代码以及使用SYSTICK定时器实现延时和嵌入式单片机时间片轮循法实现。
实验平台
①单片机型号:STM32F103RCT6
②编译软件:KEIL5
③硬件平台:正点原子STM32-MINI开发板
④仿真器:J-Link
版权声明
①作者:KELIN
②声明:本文主要参考安富莱的BSP驱动,如若有误解的地方,请联系纠正。
③纠错/业务合作:1546730313@qq.com
正文
一、主函数实现
#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<KID_BUTT;i++){
bk_api_key_detectKey(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