https://github.com/jiejieTop/ButtonDrive
调试打印个人信息——__DEBUG__
调试信息PRINT_DEBUG_ENABLE
错误信息PRINT_ERR_ENABLE
个人信息PRINT_INFO_ENABLE
Printf在单片机上可以用串口打印出来。
#define PRINT_DEBUG_ENABLE 0 /* 打印调试信息 */
#define PRINT_ERR_ENABLE 0 /* 打印错误信息 */
#define PRINT_INFO_ENABLE 0 /* 打印个人信息 */
#if PRINT_DEBUG_ENABLE
#define PRINT_DEBUG(fmt, args...) do{(printf("\n[DEBUG] >> "), printf(fmt, ##args));}while(0)
#else
#define PRINT_DEBUG(fmt, args...)
#endif
#if PRINT_ERR_ENABLE
#define PRINT_ERR(fmt, args...) do{(printf("\n[ERR] >> "), printf(fmt, ##args));}while(0)
#else
#define PRINT_ERR(fmt, args...)
#endif
#if PRINT_INFO_ENABLE
#define PRINT_INFO(fmt, args...) do{(printf("\n[INFO] >> "), printf(fmt, ##args));}while(0)
#else
#define PRINT_INFO(fmt, args...)
#endif
断言
这是编译器内置宏,这些宏定义不仅可以帮助我们完成跨平台的源码编写,灵活使用也可以巧妙地帮我们输出非常有用的调试信息。ANSI C标准中几个标准预定义宏:
__LINE__:在源代码中插入当前源代码行号;
__FILE__:在源文件中插入当前源文件名;
__DATE__:在源文件中插入当前的编译日期
__TIME__:在源文件中插入当前编译时间;
__STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
__cplusplus:当编写C++程序时该标识符被定义。
/* 断言 Assert */
#define AssertCalled(char,int) printf("\nError:%s,%d\r\n",char,int)
#define ASSERT(x) if((x)==0) AssertCalled(__FILE__,__LINE__)
typedef enum
{
ASSERT_ERR = 0, /* 错误 */
ASSERT_SUCCESS = !ASSERT_ERR /* 正确 */
} Assert_ErrorStatus;
按键创建
第一步,判断输入按键结构体地址是否存在
第二步,清空按键结构体内存,参考附录。
第三步,初始化按键结构体
第四步,加入到按键列表中
使用链表存放按键,重要的是 NEXT*
/************************************************************
* @brief 按键创建
* @param name : 按键名称
* @param btn : 按键结构体
* @param read_btn_level : 按键电平读取函数,需要用户自己实现返回uint8_t类型的电平
* @param btn_trigger_level : 按键触发电平
***********************************************************/
void Button_Create(const char *name,
Button_t *btn,
uint8_t(*read_btn_level)(void),
uint8_t btn_trigger_level)
{
if( btn == NULL) //输入储存结构体的变量的地址为空
{ //一个python的断言用这两句输出
PRINT_ERR("struct button is null!"); //输出错误信息,主要错误信息
ASSERT(ASSERT_ERR); //输出断言信息,定位错误位置
}
memset(btn, 0, sizeof(struct button)); //清除结构体信息,建议用户在之前清除
StrnCopy(btn->Name, name, BTN_NAME_MAX); /* 创建按键名称 */
btn->Button_State = NONE_TRIGGER; //按键状态
btn->Button_Last_State = NONE_TRIGGER; //按键上一次状态
btn->Button_Trigger_Event = NONE_TRIGGER; //按键触发事件
btn->Read_Button_Level = read_btn_level; //按键读电平函数
btn->Button_Trigger_Level = btn_trigger_level; //按键触发电平
btn->Button_Last_Level = btn->Read_Button_Level(); //按键当前电平
btn->Debounce_Time = 0;
PRINT_DEBUG("button create success!");
Add_Button(btn); //创建的时候添加到单链表中
Print_Btn_Info(btn); //打印信息
}
按键删除
/************************************************************
* @brief 删除一个已经创建的按键
* @param btn : 按键结构体
* @return NULL
***********************************************************/
void Button_Delete(Button_t *btn)
{ //一个*其实和变量没区别,两个*是地址
struct button** curr; //这里**是申请二级指针
for(curr = &Head_Button; *curr;) //*curr是结构体地址,遍历完就等于NULL
{
struct button* entry = *curr;
if (entry == btn)
{
*curr = entry->Next; //这里就是删除,直接链接到下一个
}
else
{
curr = &entry->Next; //这里就是遍历
}
}
}
//书上
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */
Status ListDelete(LinkList *L,int i,ElemType *e)
{
int j;
LinkList p,q;
p = *L;
j = 1;
while (p->next && j < i) /* 遍历寻找第i个元素 */
{
p = p->next;
++j;
}
if (!(p->next) || j > i)
return ERROR; /* 第i个元素不存在 */
q = p->next;
p->next = q->next; /* 将q的后继赋值给p的后继 */
*e = q->data; /* 将q结点中的数据给e */
free(q); /* 让系统回收此结点,释放内存 */
return OK;
}
按键触发事件与回调函数映射链接起来
/************************************************************
* @brief 按键触发事件与回调函数映射链接起来
* @param btn : 按键结构体
* @param btn_event : 按键触发事件
* @param btn_callback : 按键触发之后的回调处理函数。需要用户实现
* @return NULL
***********************************************************/
void Button_Attach(Button_t *btn,Button_Event btn_event,Button_CallBack btn_callback)
{
if( btn == NULL) //没有按键实体,报警
{
PRINT_ERR("struct button is null!");
//ASSERT(ASSERT_ERR); //断言
}
if(BUTTON_ALL_RIGGER == btn_event) //所有都触发一遍
{
for(uint8_t i = 0 ; i < number_of_event-1 ; i++)
btn->CallBack_Function[i] = btn_callback; //按键事件触发的回调函数,用于处理按键事件
}
Else //只触发对应回调函数
{
btn->CallBack_Function[btn_event] = btn_callback; //按键事件触发的回调函数,用于处理按键事件
}
}
btn_event : 按键触发事件
typedef enum {
BUTTON_DOWM = 0, //按下
BUTTON_UP, //弹起
BUTTON_DOUBLE,
BUTTON_LONG,
BUTTON_LONG_FREE,
BUTTON_CONTINUOS, //连按
BUTTON_CONTINUOS_FREE,
BUTTON_ALL_RIGGER,
number_of_event, /* 触发回调的事件 */
NONE_TRIGGER
}Button_Event;
按键触发之后的回调处理函数
//定义一个函数指针输入(void*),返回void。 void*是任意类型
typedef void (*Button_CallBack)(void*); /* 按键触发回调函数,需要用户实现 */
按键周期处理函数
/************************************************************
* @brief 按键周期处理函数
* @param btn:处理的按键
* @return NULL
* @note 必须以一定周期调用此函数,建议周期为20~50ms
***********************************************************/
void Button_Cycle_Process(Button_t *btn)
{
uint8_t current_level = (uint8_t)btn->Read_Button_Level(); //获取当前按键电平
if((current_level != btn->Button_Last_Level)&&(++(btn->Debounce_Time) >= BUTTON_DEBOUNCE_TIME)) //按键电平发生变化,消抖
{
btn->Button_Last_Level = current_level; //更新当前按键电平
btn->Debounce_Time = 0; //确定了是按下
//如果按键是没被按下的,改变按键状态为按下(首次按下/双击按下)
if((btn->Button_State == NONE_TRIGGER)||(btn->Button_State == BUTTON_DOUBLE))
{
btn->Button_State = BUTTON_DOWM;
}
//释放按键
else if(btn->Button_State == BUTTON_DOWM)
{
btn->Button_State = BUTTON_UP;
PRINT_DEBUG("释放了按键");
}
}
switch(btn->Button_State)
{
case BUTTON_DOWM : // 按下状态
{
if(btn->Button_Last_Level == btn->Button_Trigger_Level) //按键按下
{
#if CONTINUOS_TRIGGER //支持连续触发
if(++(btn->Button_Cycle) >= BUTTON_CONTINUOS_CYCLE)
{
btn->Button_Cycle = 0;
btn->Button_Trigger_Event = BUTTON_CONTINUOS;
TRIGGER_CB(BUTTON_CONTINUOS); //连按
PRINT_DEBUG("连按");
}
#else
if(btn->Button_Trigger_Event != BUTTON_LONG)
btn->Button_Trigger_Event = BUTTON_DOWM;
if(++(btn->Long_Time) >= BUTTON_LONG_TIME) //释放按键前更新触发事件为长按
{
#if LONG_FREE_TRIGGER
btn->Button_Trigger_Event = BUTTON_LONG;
#else
if(++(btn->Button_Cycle) >= BUTTON_LONG_CYCLE) //连续触发长按的周期
{
btn->Button_Cycle = 0;
btn->Button_Trigger_Event = BUTTON_LONG;
TRIGGER_CB(BUTTON_LONG); //长按
}
#endif
if(btn->Long_Time == 0xFF) //更新时间溢出
{
btn->Long_Time = BUTTON_LONG_TIME;
}
PRINT_DEBUG("长按");
}
#endif
}
break;
}
case BUTTON_UP : // 弹起状态
{
if(btn->Button_Trigger_Event == BUTTON_DOWM) //触发单击
{
if((btn->Timer_Count <= BUTTON_DOUBLE_TIME)&&(btn->Button_Last_State == BUTTON_DOUBLE)) // 双击
{
btn->Button_Trigger_Event = BUTTON_DOUBLE;
TRIGGER_CB(BUTTON_DOUBLE);
PRINT_DEBUG("双击");
btn->Button_State = NONE_TRIGGER;
btn->Button_Last_State = NONE_TRIGGER;
}
else
{
btn->Timer_Count=0;
btn->Long_Time = 0; //检测长按失败,清0
#if (SINGLE_AND_DOUBLE_TRIGGER == 0)
TRIGGER_CB(BUTTON_DOWM); //单击
#endif
btn->Button_State = BUTTON_DOUBLE;
btn->Button_Last_State = BUTTON_DOUBLE;
}
}
else if(btn->Button_Trigger_Event == BUTTON_LONG)
{
#if LONG_FREE_TRIGGER
TRIGGER_CB(BUTTON_LONG); //长按
#else
TRIGGER_CB(BUTTON_LONG_FREE); //长按释放
#endif
btn->Long_Time = 0;
btn->Button_State = NONE_TRIGGER;
btn->Button_Last_State = BUTTON_LONG;
btn->Button_Trigger_Event = BUTTON_DOWM;
}
#if CONTINUOS_TRIGGER
else if(btn->Button_Trigger_Event == BUTTON_CONTINUOS) //连按
{
btn->Long_Time = 0;
TRIGGER_CB(BUTTON_CONTINUOS_FREE); //连发释放
btn->Button_State = NONE_TRIGGER;
btn->Button_Last_State = BUTTON_CONTINUOS;
}
#endif
break;
}
case BUTTON_DOUBLE :
{
btn->Timer_Count++; //时间记录
if(btn->Timer_Count>=BUTTON_DOUBLE_TIME)
{
btn->Button_State = NONE_TRIGGER;
btn->Button_Last_State = NONE_TRIGGER;
}
#if SINGLE_AND_DOUBLE_TRIGGER
if((btn->Timer_Count>=BUTTON_DOUBLE_TIME)&&(btn->Button_Last_State != BUTTON_DOWM))
{
btn->Timer_Count=0;
TRIGGER_CB(BUTTON_DOWM); //单击
btn->Button_State = NONE_TRIGGER;
btn->Button_Last_State = BUTTON_DOWM;
}
#endif
break;
}
default :
break;
}
}
整个逻辑是这样的按键“按下”状态触发状态为连按,长按,单击3种状态的分支。连按是及时触发。长按分两种,一种是及时触发,一种的弹起触发。
接下来如果“弹起”,触发双击,单击,长按释放。触发状态调整为初始或者等待双击。
最后就是“双击”状态,双击状态就是计时,记住双击的时间,判断触发状态是双击还是单击。
单链表插入元素
//数据结构中使用的头插法
/* 随机产生n个元素的值,建立带表头结点的单链线性表L(头插法) */
void CreateListHead(LinkList *L, int n)
{
LinkList p;
int i;
srand(time(0)); /* 初始化随机数种子 */
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; /* 先建立一个带头结点的单链表 */
for (i=0; i<n; i++)
{
p = (LinkList)malloc(sizeof(Node)); /* 生成新结点 */
p->data = rand()%100+1; /* 随机生成100以内的数字 */
p->next = (*L)->next;
(*L)->next = p; /* 插入到表头 */
}
}
//下面这个是自己打造的头插法
/************************************************************
* @brief 使用单链表将按键连接起来 --- 头插法
***********************************************************/
static struct button* Head_Button = NULL;
static void Add_Button(Button_t* btn)
{
struct button *pass_btn = Head_Button;
while(pass_btn)
{
pass_btn = pass_btn->Next;
}
btn->Next = Head_Button;
Head_Button = btn;
}
遍历单链表
//书上
/* 初始条件:顺序线性表L已存在 */
/* 操作结果:依次对L的每个数据元素输出 */
Status ListTraverse(LinkList L)
{
LinkList p=L->next;
while(p)
{
visit(p->data);
p=p->next;
}
printf("\n");
return OK;
}
//下面是自己构建
void Search_Button(void)
{
struct button* pass_btn;
for(pass_btn = Head_Button; pass_btn != NULL; pass_btn = pass_btn->Next)
{
PRINT_INFO("button node have %s",pass_btn->Name);
}
}
清空结构体内存
//下面这个是自己构造——0
memset(btn, 0, sizeof(struct button)); //在string.h中定义
//下面这个是自己构造——1,这个是错误的不知道为什么会把指针也清零?
#define DDL_ZERO_STRUCT(x) ddl_memclr((uint8_t*)&(x), (uint32_t)(sizeof(x)))
void ddl_memclr(void *pu8Address, uint32_t u32Count)
{
uint8_t *pu8Addr = (uint8_t *)pu8Address;
if(NULL == pu8Addr)
{
return;
}
while (u32Count--)
{
*pu8Addr++ = 0;
}
}
错误
防止初始化两遍按键程序,这样他就首位相接循环出不来了。