Arduino C/C++编程:实现Shift组合大写字母按键检测
解决Arduino键盘Shift组合键检测与映射问题
需求说明
实现Arduino通过C/C++代码检测Shift键与字母按键的组合按下,当按住Shift时,从预设按键映射表中选取大写字母(或对应符号)输出到串口监视器。现有代码存在扫描值存储失效、Shift状态判断逻辑不完善等问题,需进行修复。
现有代码
#define CLOCK 6 //D- #define DATA 7 //D+ const char keymap[][2] = { {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, //0 {0,0}, {0,0}, {0,0}, {0,0}, {'`','~'}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, //10 {0,0}, {'q','Q'}, {'1','!'}, {0,0}, {0,0}, {0,0}, {'z','Z'}, {'s','S'}, {'a','A'}, {'w','W'}, //20 {'2','@'}, {0,0}, {0,0}, {'c','C'}, {'x','X'}, {'d','D'}, {'e','E'}, {'4','$'}, {'3','#'}, {0,0}, //30 {0,0}, {' ',' '}, {'v','V'}, {'f','F'}, {'t','T'}, {'r','R'}, {'5','%'}, {0,0}, {0,0}, {'n','N'}, //40 {'b','B'}, {'h','H'}, {'g','G'}, {'y','Y'}, {'6','^'}, {0,0}, {0,0}, {0,0}, {'m','M'}, {'j','J'}, //50 {'u','U'}, {'7','&'}, {'8','*'}, {0,0}, {0,0}, {',','<'}, {'k','K'}, {'i','I'}, {'o','O'}, {'0',')'}, //60 {'9','('}, {0,0}, {0,0}, {'.','>'}, {'/','?'}, {'l','L'}, {';',':'}, {'p','P'}, {'-','_'}, {0,0}, //70 {0,0}, {0,0}, {'\'','"'}, {0,0}, {'[','{'}, {'=','+'}, {0,0}, {0,0}, {0,0}, {0,0}, //80 {0,0}, {']','}'}, {0,0}, {'\\','|'}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, //90 {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {'1','!'}, {0,0}, {'4','$'}, {'7','&'}, {0,0}, //100 {0,0}, {0,0}, {'0',')'}, {'.','>'}, {'2','@'}, {'5','%'}, {'6','^'}, {'8','*'}, {0,0}, {0,0}, //110 {0,0}, {'+','+'}, {'3','#'}, {'-','_'},{'*','*'}, {'9','('}, {0,0}, {0,0}, {0,0}, {0,0}, //120 {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, //130 }; void setup() { Serial.begin(9600); pinMode(CLOCK, INPUT_PULLUP); pinMode(DATA, INPUT_PULLUP); } uint8_t lastscan; // doesn't work unsigned short detectKey(){ unsigned short scanval = 0; for(int i = 0; i<11; i++) { while(digitalRead(CLOCK)); scanval |= digitalRead(DATA) << i; while(!digitalRead(CLOCK)); } scanval >>= 1; scanval &= 0xFF; return scanval; } void loop() { bool shiftPressed= true //when the new code I need detects the shift held down; if(lastscan != 0xF0 && detectKey() != 0xF0) keymap[detectKey()][shiftPressed]; lastscan = detectKey(); //this doesn't work to save the scancode to a variable /* switch(detectKey()) { case 0x5A: //Enter Serial.println("Enter"); break; case 0x66: //Backspace break; case 0x12: //left shift while(detectKey() != 0xF0){ shiftPressed = true; } shiftPressed = false; } */ }
串口输出示例
16:47:56.832 -> 1C //hit "a" 16:47:56.924 -> F0 //let go 16:47:56.924 -> 1C 16:47:57.948 -> 1B //hit "s" 16:47:58.119 -> F0 //let go 16:47:58.119 -> 1B 16:48:02.119 -> 23 //held down "d" 16:48:02.722 -> 23 16:48:02.758 -> 23 16:48:02.758 -> F0 //let go 16:48:02.758 -> 23
从输出可知:
- 按键按下时输出对应扫描码,按住会重复输出
- 按键松开时先输出
0xF0,再输出对应扫描码 - Shift键扫描码为
0x12,按下/释放输出规律为12→F0→12
修复后的完整代码
#define CLOCK 6 //D- #define DATA 7 //D+ const char keymap[][2] = { {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, //0 {0,0}, {0,0}, {0,0}, {0,0}, {'`','~'}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, //10 {0,0}, {'q','Q'}, {'1','!'}, {0,0}, {0,0}, {0,0}, {'z','Z'}, {'s','S'}, {'a','A'}, {'w','W'}, //20 {'2','@'}, {0,0}, {0,0}, {'c','C'}, {'x','X'}, {'d','D'}, {'e','E'}, {'4','$'}, {'3','#'}, {0,0}, //30 {0,0}, {' ',' '}, {'v','V'}, {'f','F'}, {'t','T'}, {'r','R'}, {'5','%'}, {0,0}, {0,0}, {'n','N'}, //40 {'b','B'}, {'h','H'}, {'g','G'}, {'y','Y'}, {'6','^'}, {0,0}, {0,0}, {0,0}, {'m','M'}, {'j','J'}, //50 {'u','U'}, {'7','&'}, {'8','*'}, {0,0}, {0,0}, {',','<'}, {'k','K'}, {'i','I'}, {'o','O'}, {'0',')'}, //60 {'9','('}, {0,0}, {0,0}, {'.','>'}, {'/','?'}, {'l','L'}, {';',':'}, {'p','P'}, {'-','_'}, {0,0}, //70 {0,0}, {0,0}, {'\'','"'}, {0,0}, {'[','{'}, {'=','+'}, {0,0}, {0,0}, {0,0}, {0,0}, //80 {0,0}, {']','}'}, {0,0}, {'\\','|'}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, //90 {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {'1','!'}, {0,0}, {'4','$'}, {'7','&'}, {0,0}, //100 {0,0}, {0,0}, {'0',')'}, {'.','>'}, {'2','@'}, {'5','%'}, {'6','^'}, {'8','*'}, {0,0}, {0,0}, //110 {0,0}, {'+','+'}, {'3','#'}, {'-','_'},{'*','*'}, {'9','('}, {0,0}, {0,0}, {0,0}, {0,0}, //120 {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, //130 }; // 全局变量跟踪按键状态 uint8_t lastScanCode = 0; bool shiftPressed = false; bool isKeyReleased = false; void setup() { Serial.begin(9600); pinMode(CLOCK, INPUT_PULLUP); pinMode(DATA, INPUT_PULLUP); } unsigned short detectKey(){ unsigned short scanval = 0; for(int i = 0; i<11; i++) { while(digitalRead(CLOCK)); scanval |= digitalRead(DATA) << i; while(!digitalRead(CLOCK)); } scanval >>= 1; scanval &= 0xFF; return scanval; } void loop() { uint8_t currentScan = detectKey(); // 处理释放码0xF0 if(currentScan == 0xF0){ isKeyReleased = true; lastScanCode = currentScan; return; } // 处理释放后的收尾扫描码 if(isKeyReleased){ // 如果是Shift的释放码,更新Shift状态 if(currentScan == 0x12){ shiftPressed = false; } isKeyReleased = false; lastScanCode = currentScan; return; } // 处理Shift按键按下 if(currentScan == 0x12 && lastScanCode != 0x12){ shiftPressed = true; lastScanCode = currentScan; return; } // 处理普通按键,避免重复输出(按住时重复扫描码) if(currentScan != lastScanCode && currentScan != 0){ char outputChar = keymap[currentScan][shiftPressed ? 1 : 0]; if(outputChar != 0){ Serial.print(outputChar); } // 特殊按键处理(示例:Enter输出换行) if(currentScan == 0x5A){ Serial.println(); }else if(currentScan == 0x66){ Serial.print("\b"); // 退格 } } lastScanCode = currentScan; delay(5); // 防抖延迟 }
关键修复说明
- 扫描值存储修复:每次循环只调用一次
detectKey(),将结果存入currentScan,避免多次读取导致状态不一致,确保lastScanCode能正确跟踪上一次的扫描码。 - Shift状态管理:用全局变量
shiftPressed跟踪Shift的按下/释放状态,识别0xF0释放码后,处理后续的收尾扫描码来更新Shift状态。 - 单次触发逻辑:通过对比
currentScan和lastScanCode,避免按住按键时重复输出字符,只在按键首次按下时输出一次。 - 字符映射与输出:根据
shiftPressed状态从keymap中选取对应位置的字符(索引0为普通字符,1为Shift后的字符),并处理Enter、Backspace等特殊按键的输出。
内容的提问来源于stack exchange,提问作者Citadel
相关产品推荐
相关产品推荐

