|

gtajhuabvgp64010115.gif
( {2 ^- \9 w$ D8 [5 O
点击上方蓝色字体,关注我们1 l1 W; e! I5 Z( z# q8 e+ t' f( K
/ d/ | S5 f4 b6 u! y# [
4 {( O& w( `9 s$ r9 P. M
假设有一个4×4的矩阵按键,它由4行(Row)和4列(Column)组成,共16个按键。
5 h$ m- A' c, c f! Z9 @; L8 @+ v$ M |
通常,行连接到单片机的GPIO输出端,列连接到GPIO输入端,且列端口通常需要上拉电阻来保持默认高电平。
, e: F3 L& q% H2 s6 V; w3 a
. z, p h+ r5 r! u/ p硬件连接示例:% c+ J! h$ z5 {$ Z
' d* D6 R- }3 _3 P
qaktvud54em64010215.png
! ?, F/ g* [/ z$ U% y1; k: h+ }& O* J! G7 q
矩阵按键的基本扫描方法% w; w0 H ]6 a$ ]
依次拉低每一行的电平,并读取列信号,判断是否有按键按下。
+ f7 X, @& {8 L( E( G& e" U& B2 @1 c+ H( [$ x4 J
实现步骤:" K% n* b- k4 c
设定所有行(Row)为高电平,所有列(Column)为输入模式,并上拉。依次将每一行拉低(低电平),然后读取所有列的状态。如果某列检测到低电平,说明该行与该列的交点处按键被按下。记录按键位置,并等待去抖动处理。继续扫描下一行,直到所有行扫描完毕。; t1 \8 s+ U" X3 s1 q
* i _- ^+ ? p1 k" ?- I+ I: c" G; r& X+ o5 m$ L1 C, b
示例代码(基于C语言):6 A% P' G+ v2 K* F7 Q( u
o+ Z( r, J) v1 [, E' v
9 v$ K# [. b4 V' s7 l# K#define ROWS 4#define COLS 4constuint8_t row_pins[ROWS] = {ROW1, ROW2, ROW3, ROW4};constuint8_t col_pins[COLS] = {COL1, COL2, COL3, COL4};void scan_matrix_keypad() { for (int i = 0; i // 设定当前行为低电平 gpio_write(row_pins, LOW); delay_us(5); // 确保稳定 // 读取列状态 for (int j = 0; j if (gpio_read(col_pins[j]) == LOW) { printf("按键[%d,%d]被按下, C, q/ [, T0 V: _ n$ t! T
", i, j); } } // 恢复当前行为高电平 gpio_write(row_pins, HIGH); }}* d" z5 n b- M- o2 ^+ r3 U! B
26 D/ {! Z8 n# N5 y! V" Q
低功耗优化
9 i; w) u9 U0 V% |' e2 q如果单片机支持外部中断,可以利用外部中断检测按键按下,降低CPU负载。+ U- _9 P8 p. a% P7 L
7 f. m) A! x3 G% S
方法如下:
: B* i4 R8 p4 S' z& @$ r初始状态:所有行设为高电平,所有列配置为带上拉输入,并开启中断。进入低功耗模式,等待外部中断。当按键按下时,列引脚的电平变化触发中断。进入中断后,采用行列扫描法识别具体按键。处理按键逻辑后,恢复低功耗状态。
6 u/ p; t t0 p! \4 X- i, j8 E$ o6 L) [2 c6 s
% q2 y. {& e9 u4 O
示例代码(基于C语言):
9 Z; F; _3 F; J6 m- @* q. G" G. R
2 G: }2 o2 l& D7 w) r8 n# ?5 F/ X8 L. I
void EXTI_Handler() { for (int j = 0; j if (gpio_read(col_pins[j]) == LOW) { scan_matrix_keypad(); // 仅在有按键按下时扫描 break; } }}void setup() { for (int i = 0; i gpio_mode(row_pins, OUTPUT); gpio_write(row_pins, HIGH); } for (int j = 0; j gpio_mode(col_pins[j], INPUT_PULLUP); attach_interrupt(col_pins[j], EXTI_Handler, FALLING); }}
" @1 M( y$ D! h K3; g6 d# m/ c& K1 V7 \: s1 f" z3 Q* n
按键去抖动策略
0 P5 \7 {0 f. c9 ^, D+ C按键在机械接触时会出现抖动,可能会误触发多次按键事件,因此需要去抖动处理。- D8 ^! m6 n+ ?/ p9 b
, A& s: }% D* K( L+ @7 h/ x3.1、软去抖动
7 ~- ^1 [) T Y7 a0 p' ^: X通过软件延迟来过滤抖动信号,例如检测到按键按下后,延迟20ms再次检测是否仍然按下。3 K& ^6 [" y I6 @+ I" A8 Z
# Z5 w1 o! Q3 D# Bbool is_key_pressed(uint8_t row, uint8_t col) { if (gpio_read(col_pins[col]) == LOW) { delay_ms(20); // 20ms去抖 if (gpio_read(col_pins[col]) == LOW) { return true; } } return false;}
6 W' A7 u, i; I$ C p. e: M3.2、硬件去抖动
0 l$ H1 H, X. a可在矩阵按键电路中增加一个小电容(如0.1uF)或者使用施密特触发器来稳定按键信号。/ h6 g! C. q7 K4 Q
, n0 B, ~- w, J0 a
b3gy0an3ywk64010315.png
: W" ]* |( r1 e. h4 B6 g3 p8 B
, @2 _2 y$ U9 N! D: Q& B" C- _在资源受限的嵌入式系统中,如果单片机 没有足够的外部中断资源,可以使用 定时器 进行周期性扫描矩阵按键,以减少CPU占用。
3 v% g2 u [+ X4 V; ]$ I) Q6 B# B1 d; e) V2 A7 N; O
同时,为了避免主循环(while(1))中阻塞等待按键事件,使用FIFO(First In, First Out)队列 存储按键事件,以提高系统响应速度。" z& z, Y4 ]3 e$ t& w
4) m: y) ]& _' q
进一步优化
6 Z* G5 j' v! D# {1 z! h8 e/ f6 D" F7 E基本原理:% s" ^+ C Y i* K! M9 I
定时器周期性触发扫描,间隔通常设为 10~20ms,以确保能及时捕获按键事件,同时避免过于频繁地占用CPU资源。在定时器中断函数内,执行一次完整的行列扫描,如果检测到按键按下,则将其加入FIFO队列。
6 Y& K) h4 G& l8 q以下是基于 STM32 的 定时器中断方式 进行按键扫描的示例代码:8 r% x \2 U5 z# O, {0 c5 t
5 Z! _" b5 U5 C#define ROWS 4#define COLS 4constuint8_t row_pins[ROWS] = {ROW1, ROW2, ROW3, ROW4};constuint8_t col_pins[COLS] = {COL1, COL2, COL3, COL4};// FIFO 队列结构体#define KEY_FIFO_SIZE 10typedefstruct { uint8_t keys[KEY_FIFO_SIZE]; // 按键事件队列 uint8_t head; // 队列头 uint8_t tail; // 队列尾} KeyFIFO;KeyFIFO key_fifo = {{0}, 0, 0};// 按键事件入队void key_fifo_enqueue(uint8_t key) { uint8_t next = (key_fifo.tail + 1) % KEY_FIFO_SIZE; if (next != key_fifo.head) { // 队列未满 key_fifo.keys[key_fifo.tail] = key; key_fifo.tail = next; }}// 读取FIFO队列中的按键uint8_t key_fifo_dequeue() { if (key_fifo.head == key_fifo.tail) { return0; // 队列为空 } uint8_t key = key_fifo.keys[key_fifo.head]; key_fifo.head = (key_fifo.head + 1) % KEY_FIFO_SIZE; return key;}// 定时器中断回调函数,每10ms扫描按键void TIM2_IRQHandler() { for (int i = 0; i gpio_write(row_pins, LOW); delay_us(5); // 确保稳定 for (int j = 0; j if (gpio_read(col_pins[j]) == LOW) { uint8_t key_id = (i * COLS) + j + 1; key_fifo_enqueue(key_id); } } gpio_write(row_pins, HIGH); } TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除定时器中断标志}// 定时器初始化void timer2_init() { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseStructure.TIM_Period = 10000 - 1; // 10ms定时 TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 1MHz时钟 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM_Cmd(TIM2, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);}这样,我们就能在 低资源占用 和 高响应速度 之间取得 良好平衡,构建更高效的 单片机矩阵按键控制系统。8 |) w+ d2 V1 y6 v( d
moyw1nbns3y64010415.jpg
T! }# H" s1 d+ ]! {
2wl2ms3x0cq64010515.gif
4 z: ^5 P" m& r( w; P% Q- A点击阅读原文,更精彩~ |
|