按键驱动
本节介绍
📝本节您将学习如何使用MSPM0G3507的keil环境驱动按键,并且使用开源按键库 flexible_button 完成长按短按的操作。
🏆本章⽬标
1️⃣明白如何配置GPIO外设为输入模式;
2️⃣能够使用常用的开源按键库并移植到自己的工程;
硬件连接
在项目扩展板上,配置了4个按键,分别是上(UP)、左(LEFT)、右(RIGHT)、下(DOWN)按键。
与开发板的硬件连接如下:
![]() | ![]() |
---|---|
按键驱动
按键功能的介绍和配置,老生常谈了。原理图中通过按键一端接IO,另一端接GND。当按键没有按下时,我们通过软件配置上拉电阻让其保持为高电平。而当按键按下时,IO口将被外部硬件拉低为低电平;
在这两种状态下,就能够分辨的出按键的状态并处理对应按键的事情了。
工程创建
复制粘贴上一个章节的工程,并重新命名为 03_key_driver
。
![]() |
---|
在新工程下的 harware 文件夹中,新建两个文件 hw_key.c 和 hw_key.h 。然后打开该工程,往工程下的 hardware 虚拟文件夹添加新建的 .c 文件。
![]() | ![]() |
---|---|
更新工程的头文件路径为该工程的路径。
![]() |
---|
引脚配置
打开工程的 .syscfg 文件,再打开图形化代码生成工具,配置按键对应的引脚为开启上拉电阻的输入模式。
![]() | ![]() |
---|---|
![]() | ![]() |
---|---|
![]() | ![]() |
编写获取按键状态代码
往 hw_key.c 文件中加入以下hw_key.c选项页的代码,往 hw_key.h 文件中加入以下hw_key.h选项页的代码:
#include "hw_key.h"
KEY_STATUS key_scan(void)
{
KEY_STATUS states;
// 读取每个按键的状态
states.up = DL_GPIO_readPins(GPIO_KEY_PORT, GPIO_KEY_PIN_UP_PIN) ? 1 : 0;
states.left = DL_GPIO_readPins(GPIO_KEY_PORT, GPIO_KEY_PIN_LEFT_PIN) ? 1 : 0;
states.right = DL_GPIO_readPins(GPIO_KEY_PORT, GPIO_KEY_PIN_RIGHT_PIN) ? 1 : 0;
states.down = DL_GPIO_readPins(GPIO_KEY_PORT, GPIO_KEY_PIN_DOWN_PIN) ? 1 : 0;
return states;
}
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef _HW_KEY_H_
#define _HW_KEY_H_
#include "ti_msp_dl_config.h"
typedef struct {
unsigned int up : 1; // 使用位字段来节省空间
unsigned int left : 1;
unsigned int right : 1;
unsigned int down : 1;
} KEY_STATUS;
KEY_STATUS keys;
KEY_STATUS key_scan(void);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
移植开源按键库
按键抖动现象
在使用按键进行控制时,按键的物理特性可能导致抖动现象
。抖动是指在按键按下或释放的瞬间,信号可能会出现快速的波动,导致系统误认为按键被多次按下或释放。这种现象在电子系统中尤为常见,尤其是在使用机械开关时。
![]() |
---|
为了消除按键抖动,通常可以采用以下几种方法:
- 硬件消抖:通过在按键电路中添加电容器来滤波,减少信号的波动。
- 软件消抖:通过软件算法来检测和过滤抖动。常见的方法包括延时法和计数法。
开源按键驱动
为了简化按键处理,同时实现消抖、长按、短按、双击等操作,我们可以使用现成的开源按键库,如 flexible_button 。这个库是一个基于C语言的灵活按键处理库,支持短按、长按、自动消抖等功能,并可以无限扩展按键数量。
使用这样的开源库可以避免重复造轮子,提高开发效率。类似的开源案例还有MultiButton、ButtonDrive等等。
如果你看到这里,请尽量给以上三个开源项目点一个star,致敬开源贡献者。
DEMO 程序说明
FlexibleButton 库中提供了一个测试例程 ./flexible_button_demo.c,该例程基于 RT-Thread OS 进行测试,硬件平台选择了 RT-Thread IoT Board v2.2 开发板。当然你可以选择使用其他的 OS,或者使用裸机测试,只需要移除 OS 相关的特性即可。
如果你使用自己的硬件平台,只需要将 FlexibleButton 库源码和例程加入你既有的工程下即可。
确定用户按键
typedef enum
{
USER_BUTTON_0 = 0, // 对应 IoT Board 开发板的 PIN_KEY0
USER_BUTTON_1, // 对应 IoT Board 开发板的 PIN_KEY1
USER_BUTTON_2, // 对应 IoT Board 开发板的 PIN_KEY2
USER_BUTTON_3, // 对应 IoT Board 开发板的 PIN_WK_UP
USER_BUTTON_MAX
} user_button_t;
static flex_button_t user_button[USER_BUTTON_MAX];
2
3
4
5
6
7
8
9
10
上述代码定义了 4 个按键,数据结构存储在 user_button 数组中。
程序入口
int flex_button_main(void)
{
rt_thread_t tid = RT_NULL;
user_button_init();
/* 创建按键扫描线程 flex_btn,线程栈 1024 byte,优先级 10 */
tid = rt_thread_create("flex_btn", button_scan, RT_NULL, 1024, 10, 10);
if(tid != RT_NULL)
{
rt_thread_startup(tid);
}
return 0;
}
/* 使用 RT-Thread 的自动初始化 */
INIT_APP_EXPORT(flex_button_main);
2
3
4
5
6
7
8
9
10
11
12
13
14
如上所示,首先使用 user_button_init(); 初始化用户按键硬件,该步骤将用户按键绑定到 FlexibleButton 库。然后,使用 RT-Thread 的 INIT_APP_EXPORT 接口导出为上电自动初始化,创建了一个 “flex_btn” 名字的按键扫描线程,线程里扫描检查按键事件。
按键初始化代码
static void user_button_init(void)
{
int i;
/* 初始化按键数据结构 */
rt_memset(&user_button[0], 0x0, sizeof(user_button));
user_button[USER_BUTTON_0].usr_button_read = button_key0_read;
user_button[USER_BUTTON_0].cb = (flex_button_response_callback)btn_0_cb;
user_button[USER_BUTTON_1].usr_button_read = button_key1_read;
user_button[USER_BUTTON_1].cb = (flex_button_response_callback)btn_1_cb;
user_button[USER_BUTTON_2].usr_button_read = button_key2_read;
user_button[USER_BUTTON_3].usr_button_read = button_keywkup_read;
/* 初始化 IoT Board 按键引脚,使用 rt-thread PIN 设备框架 */
rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT); /* 设置 GPIO 为输入模式 */
rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT); /* 设置 GPIO 为输入模式 */
rt_pin_mode(PIN_KEY2, PIN_MODE_INPUT); /* 设置 GPIO 为输入模式 */
rt_pin_mode(PIN_WK_UP, PIN_MODE_INPUT);/* 设置 GPIO 为输入模式 */
/* 核心的按键配置
* pressed_logic_level,设置按键按下的逻辑电平
* click_start_tick,设置触发按键按下事件的起始 tick(用于消抖)
* short_press_start_tick,设置短按事件触发的起始 tick
* long_press_start_tick,设置长按事件触发的起始 tick
* long_hold_start_tick,设置长按保持事件触发的起始 tick
*/
for (i = 0; i < USER_BUTTON_MAX; i ++)
{
user_button[i].pressed_logic_level = 0;
user_button[i].click_start_tick = 20;
user_button[i].short_press_start_tick = 100;
user_button[i].long_press_start_tick = 200;
user_button[i].long_hold_start_tick = 300;
if (i == USER_BUTTON_3)
{
user_button[USER_BUTTON_3].pressed_logic_level = 1;
}
flex_button_register(&user_button[i]);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
user_button_init(); 主要用于初始化按键硬件,设置按键长按和短按的 tick 数(RT-Thread RT_TICK_PER_SECOND 配置为 1000 时,默认一个 tick 为 1ms),然后注册按键到 FlexibleButton 库。
事件处理代码
static void btn_0_cb(flex_button_t *btn)
{
rt_kprintf("btn_0_cb\n");
switch (btn->event)
{
case FLEX_BTN_PRESS_DOWN:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_DOWN]\n");
break;
case FLEX_BTN_PRESS_CLICK:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_CLICK]\n");
break;
case FLEX_BTN_PRESS_DOUBLE_CLICK:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_DOUBLE_CLICK]\n");
break;
case FLEX_BTN_PRESS_SHORT_START:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_SHORT_START]\n");
break;
case FLEX_BTN_PRESS_SHORT_UP:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_SHORT_UP]\n");
break;
case FLEX_BTN_PRESS_LONG_START:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_START]\n");
break;
case FLEX_BTN_PRESS_LONG_UP:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_UP]\n");
break;
case FLEX_BTN_PRESS_LONG_HOLD:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_HOLD]\n");
break;
case FLEX_BTN_PRESS_LONG_HOLD_UP:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_HOLD_UP]\n");
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
按键事件定义
按键事件的定义并没有使用 Windows 驱动上的定义,主要是方便嵌入式设备中的应用场景(也可能是我理解的偏差),按键事件定义如下:
typedef enum
{
FLEX_BTN_PRESS_DOWN = 0, // 按下事件
FLEX_BTN_PRESS_CLICK, // 单击事件
FLEX_BTN_PRESS_DOUBLE_CLICK, // 双击事件
FLEX_BTN_PRESS_SHORT_START, // 短按开始事件
FLEX_BTN_PRESS_SHORT_UP, // 短按抬起事件
FLEX_BTN_PRESS_LONG_START, // 长按开始事件
FLEX_BTN_PRESS_LONG_UP, // 长按抬起事件
FLEX_BTN_PRESS_LONG_HOLD, // 长按保持事件
FLEX_BTN_PRESS_LONG_HOLD_UP, // 长按保持的抬起事件
FLEX_BTN_PRESS_MAX,
FLEX_BTN_PRESS_NONE,
} flex_button_event_t;
2
3
4
5
6
7
8
9
10
11
12
13
14
按键扫描接口
按键扫描的核心函数,需要放到应用程序中定时扫描间隔 5-20ms 即可。
void flex_button_scan(void);
移植到我们工程
在我们工程下的 middle 文件夹中新建两个文件 mid_button.c 和 mid_button.h,新建完成后将.c文件添加到工程的虚拟文件夹下。
这两个文件处于中间层,主要是按键库的处理。
![]() | ![]() |
---|---|
在我们工程下的 app 文件夹中新建两个文件 app_key_task.c 和 app_key_task.h,新建完成后将.c文件添加到工程的虚拟文件夹下。
这两个文件处于应用层,我们将按键库的按键回调函数放在这里,主要处理各个按键的逻辑任务。
![]() | ![]() |
---|---|
接下来将下面移植好的各文件代码移植到对应的文件中:
/**
* @File: flexible_button.c
* @Author: MurphyZhao
* @Date: 2018-09-29
*
* Copyright (c) 2018-2019 MurphyZhao <d2014zjt@163.com>
* https://github.com/murphyzhao
* All rights reserved.
* License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Change logs:
* Date Author Notes
* 2018-09-29 MurphyZhao First add
* 2019-08-02 MurphyZhao Migrate code to github.com/murphyzhao account
* 2019-12-26 MurphyZhao Refactor code and implement multiple clicks
*
*/
#include "mid_button.h"
#include "hw_key.h"
#include "app_key_task.h"
// 设置你要控制多少个按键
// 最后一位总是USER_BUTTON_MAX
typedef enum
{
BUTTON_UP = 0,
BUTTON_LEFT,
BUTTON_RIGHT,
BUTTON_DOWN,
USER_BUTTON_MAX
} user_button_t;
static flex_button_t user_button[USER_BUTTON_MAX];
static flex_button_t *btn_head = NULL;
static uint16_t trg = 0;
static uint16_t cont = 0;
static uint16_t keydata = 0xFFFF;
static uint16_t key_rst_data = 0xFFFF;
static uint8_t button_cnt = 0;
#define EVENT_CB_EXECUTOR(button) if(button->cb) button->cb((flex_button_t*)button)
#define MAX_BUTTON_CNT 16
// 配置你按键的返回值
static uint8_t button_up_read(void) { return key_scan().up; }
static uint8_t button_left_read(void) { return key_scan().left; }
static uint8_t button_right_read(void) { return key_scan().right; }
static uint8_t button_down_read(void) { return key_scan().down; }
// 配置按键初始化
// 用户自行根据案例配置
void user_button_init(void)
{
int i;
/* 初始化按键数据结构 */
memset(&user_button[0], 0x0, sizeof(user_button));
user_button[BUTTON_UP].usr_button_read = button_up_read;//按键读值回调函数
user_button[BUTTON_UP].cb = (flex_button_response_callback)btn_up_cb;//按键事件回调函数
user_button[BUTTON_LEFT].usr_button_read = button_left_read;//按键读值回调函数
user_button[BUTTON_LEFT].cb = (flex_button_response_callback)btn_left_cb;//按键事件回调函数
user_button[BUTTON_RIGHT].usr_button_read = button_right_read;//按键读值回调函数
user_button[BUTTON_RIGHT].cb = (flex_button_response_callback)btn_right_cb;//按键事件回调函数
user_button[BUTTON_DOWN].usr_button_read = button_down_read;//按键读值回调函数
user_button[BUTTON_DOWN].cb = (flex_button_response_callback)btn_down_cb;//按键事件回调函数
/* 初始化 按键引脚 */
for (i = 0; i < USER_BUTTON_MAX; i ++)
{
user_button[i].pressed_logic_level = 0; //按键按下时的逻辑电平
user_button[i].click_start_tick = 7;//10;
user_button[i].short_press_start_tick = 12;//20;//短按起始 tick
user_button[i].long_press_start_tick = 27;//40;//长按起始 tick
user_button[i].long_hold_start_tick = 32;//45;//长按保持起始tick
flex_button_register(&user_button[i]);
}
}
/**
* @brief Register a user button
*
* @param button: button structure instance
* @return Number of keys that have been registered
*/
int8_t flex_button_register(flex_button_t *button)
{
flex_button_t *curr = btn_head;
if (!button || (button_cnt > MAX_BUTTON_CNT))
{
return -1;
}
while (curr)
{
if(curr == button)
{
return -1; //already exist.
}
curr = curr->next;
}
button->next = btn_head;
button->status = 0;
button->event = FLEX_BTN_PRESS_NONE;
button->scan_cnt = 0;
button->click_cnt = 0;
btn_head = button;
key_rst_data = key_rst_data << 1;
button_cnt ++;
return button_cnt;
}
/**
* @brief Read all key values in one scan cycle
*
* @param void
* @return none
*/
static void flex_button_read(void)
{
flex_button_t* target;
uint16_t read_data = 0;
keydata = key_rst_data;
int8_t i = 0;
for(target = btn_head, i = 0;
(target != NULL) && (target->usr_button_read != NULL);
target = target->next, i ++)
{
keydata = keydata |
(target->pressed_logic_level == 1 ?
((!(target->usr_button_read)()) << i) :
((target->usr_button_read)() << i));
}
read_data = keydata^0xFFFF;
trg = read_data & (read_data ^ cont);
cont = read_data;
}
/**
* @brief Handle all key events in one scan cycle.
* Must be used after 'flex_button_read' API
*
* @param void
* @return none
*/
static void flex_button_process(void)
{
int8_t i = 0;
flex_button_t* target;
for (target = btn_head, i = 0; target != NULL; target = target->next, i ++)
{
if (target->status > 0)
{
target->scan_cnt ++;
}
switch (target->status)
{
case 0: /* is default */
if (trg & (1 << i)) /* is pressed */
{
target->scan_cnt = 0;
target->click_cnt = 0;
target->status = 1;
target->event = FLEX_BTN_PRESS_DOWN;
EVENT_CB_EXECUTOR(target);
}
else
{
target->event = FLEX_BTN_PRESS_NONE;
}
break;
case 1: /* is pressed */
if (!(cont & (1 << i))) /* is up */
{
target->status = 2;
}
else if ((target->scan_cnt >= target->short_press_start_tick) &&
(target->scan_cnt < target->long_press_start_tick))
{
target->status = 4;
target->event = FLEX_BTN_PRESS_SHORT_START;
EVENT_CB_EXECUTOR(target);
}
break;
case 2: /* is up */
if ((target->scan_cnt < target->click_start_tick))
{
target->click_cnt++; // 1
if (target->click_cnt == 1)
{
target->status = 3; /* double click check */
}
else
{
target->click_cnt = 0;
target->status = 0;
target->event = FLEX_BTN_PRESS_DOUBLE_CLICK;
EVENT_CB_EXECUTOR(target);
}
}
else if ((target->scan_cnt >= target->click_start_tick) &&
(target->scan_cnt < target->short_press_start_tick))
{
target->click_cnt = 0;
target->status = 0;
target->event = FLEX_BTN_PRESS_CLICK;
EVENT_CB_EXECUTOR(target);
}
else if ((target->scan_cnt >= target->short_press_start_tick) &&
(target->scan_cnt < target->long_press_start_tick))
{
target->click_cnt = 0;
target->status = 0;
target->event = FLEX_BTN_PRESS_SHORT_UP;
EVENT_CB_EXECUTOR(target);
}
else if ((target->scan_cnt >= target->long_press_start_tick) &&
(target->scan_cnt < target->long_hold_start_tick))
{
target->click_cnt = 0;
target->status = 0;
target->event = FLEX_BTN_PRESS_LONG_UP;
EVENT_CB_EXECUTOR(target);
}
else if (target->scan_cnt >= target->long_hold_start_tick)
{
/* long press hold up, not deal */
target->click_cnt = 0;
target->status = 0;
target->event = FLEX_BTN_PRESS_LONG_HOLD_UP;
EVENT_CB_EXECUTOR(target);
}
break;
case 3: /* double click check */
if (trg & (1 << i))
{
target->click_cnt++;
target->status = 2;
target->scan_cnt --;
}
else if (target->scan_cnt >= target->click_start_tick)
{
target->status = 2;
}
break;
case 4: /* is short pressed */
if (!(cont & (1 << i))) /* is up */
{
target->status = 2;
}
else if ((target->scan_cnt >= target->long_press_start_tick) &&
(target->scan_cnt < target->long_hold_start_tick))
{
target->status = 5;
target->event = FLEX_BTN_PRESS_LONG_START;
EVENT_CB_EXECUTOR(target);
}
break;
case 5: /* is long pressed */
if (!(cont & (1 << i))) /* is up */
{
target->status = 2;
}
else if (target->scan_cnt >= target->long_hold_start_tick)
{
target->status = 6;
target->event = FLEX_BTN_PRESS_LONG_HOLD;
EVENT_CB_EXECUTOR(target);
}
break;
case 6: /* is long pressed */
if (!(cont & (1 << i))) /* is up */
{
target->status = 2;
}
break;
}
}
}
/**
* flex_button_event_read
*
* @brief Get the button event of the specified button.
*
* @param button: button structure instance
* @return button event
*/
flex_button_event_t flex_button_event_read(flex_button_t* button)
{
return (flex_button_event_t)(button->event);
}
/**
* flex_button_scan
*
* @brief Start key scan.
* Need to be called cyclically within the specified period.
* Sample cycle: 5 - 20ms
*
* @param void
* @return none
*/
void flex_button_scan(void)
{
flex_button_read();
flex_button_process();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/**
* @File: flexible_button.h
* @Author: MurphyZhao
* @Date: 2018-09-29
*
* Copyright (c) 2018-2019 MurphyZhao <d2014zjt@163.com>
* https://github.com/murphyzhao
* All rights reserved.
* License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Change logs:
* Date Author Notes
* 2018-09-29 MurphyZhao First add
* 2019-08-02 MurphyZhao ??? murphyzhao ?
*
*/
#ifndef __FLEXIBLE_BUTTON_H__
#define __FLEXIBLE_BUTTON_H__
#include "stdint.h"
#include "string.h"
typedef void (*flex_button_response_callback)(void*);
typedef enum
{
FLEX_BTN_PRESS_DOWN = 0, // 按下事件
FLEX_BTN_PRESS_CLICK, // 单击事件
FLEX_BTN_PRESS_DOUBLE_CLICK, // 双击事件
FLEX_BTN_PRESS_REPEAT_CLICK, // 连击事件,使用 flex_button_t 中的 click_cnt 断定连击次数
FLEX_BTN_PRESS_SHORT_START, // 短按开始事件
FLEX_BTN_PRESS_SHORT_UP, // 短按抬起事件
FLEX_BTN_PRESS_LONG_START, // 长按开始事件
FLEX_BTN_PRESS_LONG_UP, // 长按抬起事件
FLEX_BTN_PRESS_LONG_HOLD, // 长按保持事件
FLEX_BTN_PRESS_LONG_HOLD_UP, // 长按保持的抬起事件
FLEX_BTN_PRESS_MAX,
FLEX_BTN_PRESS_NONE,
} flex_button_event_t;
/**
* flex_button_t
*
* @brief Button data structure
* Below are members that need to user init before scan.
*
* @member pressed_logic_level: Logic level when the button is pressed.
* Must be inited by 'flex_button_register' API
* before start button scan.
* @member debounce_tick: The time of button debounce.
* The value is number of button scan cycles.
* @member click_start_tick: The time of start click.
* The value is number of button scan cycles.
* @member short_press_start_tick: The time of short press start tick.
* The value is number of button scan cycles.
* @member long_press_start_tick: The time of long press start tick.
* The value is number of button scan cycles.
* @member long_hold_start_tick: The time of hold press start tick.
* The value is number of button scan cycles.
* @member usr_button_read: Read the logic level value of specified button.
* @member cb: Button event callback function.
* If use 'flex_button_event_read' api,
* you don't need to initialize the 'cb' member.
* @member next : Next button struct
*/
typedef struct flex_button
{
uint8_t pressed_logic_level : 1; /* need user to init */
/**
* @event
* The event of button in flex_button_evnt_t enum list.
* Automatically initialized to the default value FLEX_BTN_PRESS_NONE
* by 'flex_button_register' API.
*/
uint8_t event : 4;
/**
* @status
* Used to record the status of the button
* Automatically initialized to the default value 0.
*/
uint8_t status : 3;
uint16_t scan_cnt; /* default 0. Used to record the number of key scans */
uint16_t click_cnt; /* default 0. Used to record the number of key click */
uint16_t debounce_tick;
uint16_t click_start_tick;
uint16_t short_press_start_tick;
uint16_t long_press_start_tick;
uint16_t long_hold_start_tick;
uint8_t (*usr_button_read)(void);
flex_button_response_callback cb;
struct flex_button* next;
} flex_button_t;
#ifdef __cplusplus
extern "C" {
#endif
int8_t flex_button_register(flex_button_t *button);
flex_button_event_t flex_button_event_read(flex_button_t* button);
void flex_button_scan(void);
void user_button_init(void);
#ifdef __cplusplus
}
#endif
#endif /* __FLEXIBLE_BUTTON_H__ */
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include "app_key_task.h"
static char current_mode;
void set_app_key_current_mode(char mode)
{
current_mode = mode;
}
char get_app_key_current_mode(void)
{
return current_mode;
}
void btn_up_cb(flex_button_t *btn)
{
switch (btn->event)
{
case FLEX_BTN_PRESS_CLICK://单击事件
break;
case FLEX_BTN_PRESS_LONG_HOLD://长按保持事件
break;
case FLEX_BTN_PRESS_LONG_HOLD_UP://长按保持后抬起事件
break;
default:break;
}
}
void btn_left_cb(flex_button_t *btn)
{
switch (btn->event)
{
case FLEX_BTN_PRESS_CLICK://单击事件
set_app_key_current_mode( !get_app_key_current_mode() );
break;
case FLEX_BTN_PRESS_LONG_START://长击开始事件
break;
default:break;
}
}
void btn_right_cb(flex_button_t *btn)
{
switch (btn->event)
{
case FLEX_BTN_PRESS_CLICK://单击事件
set_app_key_current_mode( !get_app_key_current_mode() );
break;
case FLEX_BTN_PRESS_LONG_START://长击开始事件
break;
default:break;
}
}
void btn_down_cb(flex_button_t *btn)
{
switch (btn->event)
{
case FLEX_BTN_PRESS_CLICK://单击事件
break;
case FLEX_BTN_PRESS_LONG_HOLD://长按保持事件
break;
case FLEX_BTN_PRESS_LONG_HOLD_UP://长按保持后抬起事件
break;
default:break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#ifndef _APP_KEY_TASK_H_
#define _APP_KEY_TASK_H_
#include "ti_msp_dl_config.h"
#include "mid_button.h"
void set_app_key_current_mode(char mode);
char get_app_key_current_mode(void);
void btn_up_cb(flex_button_t *btn);
void btn_left_cb(flex_button_t *btn);
void btn_right_cb(flex_button_t *btn);
void btn_down_cb(flex_button_t *btn);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
按键驱动验证
往 empty.c 文件写入以下代码:
#include "ti_msp_dl_config.h"
#include "mid_debug_led.h"
#include "mid_debug_uart.h"
#include "string.h"
#include "hw_lcd.h"
#include "mid_button.h"
#include "app_key_task.h"
void ui_home_page(void);// 首页页面初始化
void ui_home_page_select(int mode);//绘制选择框
int main(void)
{
int sys_time = 0;
SYSCFG_DL_init();
//DEBUG串口初始化
debug_uart_init();
//按键任务初始化
user_button_init();
//LCD初始化
lcd_init();
//LCD显示UI
ui_home_page();
while (1)
{
sys_time++;
if( sys_time % 10 == 0 )//2*10=20ms进行一次按键扫描
{
//按键扫描
flex_button_scan();
}
if( sys_time % 50 == 0 )//2*50=100ms刷新一次屏幕
{
//屏幕刷新
ui_home_page_select( get_app_key_current_mode() );
}
delay_cycles(CPUCLK_FREQ / 1000 * 2);//时间基准2ms
}
}
/*
功能说明:显示居中字符串函数用于显示居中的文字,计算文字的居中坐标
参数说明: x=屏幕中心x坐标
w=屏幕宽度
y=屏幕中心y坐标
h=屏幕高度
str_len=字符串长度
sizey=字体大小
*str=需要显示的字符串
color背景颜色
备注:GRAYBLUE 浅蓝色
DARKBLUE 深蓝色
*/
void disp_x_center(int y, int str_len, uint16_t bc, unsigned char sizey, unsigned char* str)
{
int str_center_x = (sizey * str_len) / 2;//字符串x=字体大小*字符串长度/2
int str_center_y = sizey / 2;//字符串y=字体大小/2
//显示居中坐标的文字
LCD_ArcRect(screen_center_x - str_center_x - 10, y, screen_center_x + str_center_x + 10, sizey+y, bc);
LCD_ShowChinese(screen_center_x - str_center_x,y,str,WHITE,bc,sizey,1);
}
/*
功能说明:显示字符串矩形函数用于显示矩形的文字,计算文字的居中坐标
参数说明: x=矩形起始x坐标
w=矩形宽度
y=矩形起始y坐标
h=矩形高度
str_len=字符串长度
sizey=字体大小
*str=需要显示的字符串
color背景颜色
备注:GRAYBLUE 浅蓝色
DARKBLUE 深蓝色
*/
void disp_string_rect(int x, int w, int y, int h, int str_len, int sizey, unsigned char* str, int color)
{
int str_center_x = (sizey * str_len) / 2; //字符串x = 字体大小*字符串长度/2
int rect_center_x = x + (w / 2); //矩形中心x
int str_center_y = sizey / 2; //字符串y=字体大小/2
int rect_center_y = y + (h / 2); //矩形中心y
//显示背景矩形
LCD_ArcRect(x, y, x + w, y + h, color);
//显示字符串
LCD_ShowChinese(rect_center_x - str_center_x, rect_center_y - str_center_y,str,WHITE,color,sizey,1);
}
/*
功能说明:显示选择框函数用于显示选择框: x=按钮起始X坐标
w=显示的按钮选择框宽度
y=按钮起始Y坐标
h=显示的按钮选择框高度
line_length=选择框边线长度
interval=选择框边线间隔 按钮选择框边框之间的距离
color=选择框边线颜色
*/
void disp_select_box(int x, int w, int y, int h, int line_length, int interval, int color)
{
//计算按钮选择框边框之间的距离+边线
x = x - interval;
w = w + (interval + interval);
y = y - interval;
h = h + (interval + interval);
//左上角
LCD_DrawLine(x, y, x + line_length, y, color);
LCD_DrawLine(x, y, x, y + line_length, color);
//右上角
LCD_DrawLine(x + w, y, x + w - line_length, y, color);
LCD_DrawLine(x + w, y, x + w, y + line_length, color);
//左下角
LCD_DrawLine(x, y + h, x + line_length, y + h, color);
LCD_DrawLine(x, y + h, x, y + h - line_length, color);
//右下角
LCD_DrawLine(x + w, y + h, x + w - line_length, y + h, color);
LCD_DrawLine(x + w, y + h, x + w, y + h - line_length, color);
}
//家居页面初始化
void ui_home_page(void)
{
//关闭背光
LCD_BLK_Clr();
//显示全屏背景颜色
LCD_Fill(0,0,LCD_W,LCD_H,BLACK);
//显示标题
disp_x_center(3,5,BLUE,16,(unsigned char *)"立创开发板");
//显示副标题
disp_x_center(3+16+3,8,BLUE,16,(unsigned char *)"简易PID入门套件");
int x = 40;
int x_offset = 80;
int y = 65;
int y_offset = 80;
//显示第一个按钮:定速
disp_string_rect(x, x_offset, y, y_offset, 2, 24, "定速", BLUE);
int x2 = 200;
int x2_offset = 80;
int y2 = 65;
int y2_offset = 80;
//显示第二个按钮:定距
disp_string_rect(x2, x2_offset, y2, y2_offset, 2, 24, "定距", BLUE);
//显示选择框
disp_select_box(40,80,65,80,10,5,WHITE);
//打开背光
LCD_BLK_Set();
}
//根据按键选择绘制首页两个选项的选择框
void ui_home_page_select(int mode)
{
char select_box_seze = 5;
switch(mode)
{
case 0: //选择PID定速模式
disp_select_box(40,80,65,80,10,select_box_seze,WHITE);
disp_select_box(200,80,65,80,10,select_box_seze,BLACK);
break;
case 1: //选择PID定距模式
disp_select_box(40,80,65,80,10,select_box_seze,BLACK);
disp_select_box(200,80,65,80,10,select_box_seze,WHITE);
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
烧录代码后,按开发板的左键和右键应该会看到屏幕上的选择框会在定速和定距两个模式间跳转。