完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
背景
键盘设备是我们使用最多的人机交互设备之一,USB联盟制定的HID协议为人机交互设备的兼容性和即插即用性提供了保障。机械轴体键盘由于优越的触感和长久的寿命日益被人们所喜爱,但比普通键盘更贵的价格使得机械键盘只在游戏等领域发展迅速,经我们组分析发现大部分人员对于机械键盘的需求固定在特殊的部分按键,如大部分游戏玩家使用的按键在20个以下,财会人员更多地使用数字按键,特殊需求人员也不会经常使用键盘上的100多个按键。基于此我们将键盘上数字按键部分进行改动,做成了现在的项目,将数字和字母按键整合到双层键盘中,且可以自定义按键功能,在机械键盘价格和实用性中折中出一个较为优化的方案。 整体设计 此次设计的系统以 Arduino micro核心开发板为控制器,EC11编码器和矩阵按键以及2N2222A驱动电路为外围器件,USB供电以及与电脑通信。整体设计构想的系统框架图如下所示: 在系统中,单片机与电脑通过USB接口依据HID协议发送和接收信息,同时整个该系统通过USB获取电源。2N2222A集电极与并联组成的LED组串接,单片机通过PWM对2N2222A的基极进行控制,达到调节亮度的目的。单片机通过扫描程序对键盘状态进行读取,并且依据状态机改变并记录按键的状态(按下、长按、释放、空闲四个状态),以此为依据向电脑发送信息。EC11编码器使用中断方式与单片机进行检测,通过检测AB两个出点的电平状态得出旋转方向和距离,并上报电脑。 主要功能 支持双层键盘切换,以及每层键盘的切换按键可以不同。 支持按键全部自定义,可任意设置HID协议定义的按键。 支持和电脑键盘联动控制键盘切换。 支持数字键盘开启指示灯显示,支持5级背光调节。 支持最多同时6键触发。 支持旋钮调用Windows轮盘功能,可进行音量,屏幕亮度等调节。 硬件设计与仿真 电路图部分主要由按键矩阵,led矩阵以及驱动电路,控制器,EC11旋转编码器等组成,在矩阵键盘中加入二极管1N4148硬件上消除矩阵键盘的误触和错误检测问题,且在LED部分每个LED均串接一个100欧姆电阻保证led上压降为3.0V左右,且在2N2222A基极串接1K欧姆限流电阻。PCB部分采用双层板设计,双面铺铜,单面布置元器件的方式,且尽量选取插孔原件,减小焊接难度。为EC11编码器设计了硬件级电容消抖,保证了中断的触发稳定。除此以外还添加了数字大小写的指示灯,方便键盘切换后的指示。按键采用的紫色机械轴体,还为2U大小的两个按键添加了卫星轴体,减小按键的摇晃。 采用Multisim仿真2N2222A驱动电路,并且使用电阻代替3mm灯珠进行实物仿真验证,为实际电路电阻参数设置提供保障,因为未曾仿真时错误的电阻值选择曾导致一个2N2222A被烧毁,所以在使用大电流以及设置供电因素时要特别注意。 软件设计 使用VS code设计Arduino的程序文件具有点击跳转,函数高亮,悬浮显示等原来IDE不具有的优点,极大的增加了开发的便利性。 程序从底层往上依次是:矩阵键盘扫描程序,编码器中断服务程序,HID库提供的描述符定义和数据发送程序,PWM背光调节程序,以及顶层的逻辑控制主程序。 程序开发中遇到的问题和解决办法如下: EC11旋转编码器对应arduino的引脚不支持直接中断(PCB设计失误):使用PinChangeInterrupt库,将中断映射到内部的其他中断。 矩阵键盘存在多按键同时按下识别错误的问题:使用1N4148二极管和按键串联,防止电流反向流动,硬件上阻断识别错误。 双层键盘切换之间由于两层键盘中各切换按键的位置不同,会导致触发切换按键的第二功能:使用标志记录转换过程,失能误触按键。 共调用了如下几个库,分别实现HID协议内容,矩阵键盘扫描程序,实现C++ STL支持,以及引脚中断映射,主函数代码见附录。 #include "HID-Project.h" #include #include #include 附录 #include "HID-Project.h" #include //#include #include #include // input pins for encoder channel A and channel B int channelA = 16; int channelB = 14; // input pin for pushbutton int dial_Button = 15; volatile bool previousButtonValue = false; volatile int previous = 0; volatile int counter = 0; //map对象,存放字符串和对应的要发送的值 arx::map { { 'I', KEY_ENTER}, { 'R', KEY_RESERVED}, { 'S', KEY_LEFT_SHIFT}, { 'L', KEY_NUM_LOCK}, { '9', KEY_9}, { '8', KEY_8}, { '7', KEY_7}, { '6', KEY_6}, { '5', KEY_5}, { '4', KEY_4}, { '3', KEY_3}, { '2', KEY_2}, { '1', KEY_1}, { '0', KEY_0}, { '.', KEYPAD_DOT}, { '+', KEYPAD_ADD}, { '/', KEYPAD_DIVIDE}, { '*', KEYPAD_MULTIPLY}, { '-', KEYPAD_SUBTRACT}, { 'q',KEY_Q}, { 'w',KEY_W}, { 'e',KEY_E}, { 'r',KEY_R}, { 'a',KEY_A}, { 's',KEY_S}, { 'd',KEY_D}, { 'f',KEY_F}, { 'b',KEY_B}, { ')',KEY_ESC}, { '!',KEY_F1}, { '@',KEY_F2}, { '#',KEY_F3}, { '$',KEY_F4}, { '%',KEY_F5}, { '^',KEY_F6}, { '&',KEY_F7}, { '?',KEY_F8}, { '(',KEY_F9}, { 'H',KEY_HOME}, { 'E',KEY_END}, { 'S',KEY_LEFT_SHIFT} }; const byte ROWS = 6; //six rows const byte COLS = 4; //four columns char numberKeys[ROWS][COLS] = { { 'H','E','S','S' }, { 'L','/','*','-' }, { '7','8','9','+' }, { '4','5','6','R' }, { '1','2','3','I' }, { 'R','0','.','R' } }; char alphaKeys[ROWS][COLS] = { { 'q','w','e','r' }, { 'a','s','d','f' }, { '&','?','(','b' }, { '$','%','^','R' }, { '!','@','#','I' }, { 'R',')','L','R' } }; boolean first_keyboard = true; boolean change_pad_flag = false; byte rowPins[ROWS] = {6, 7, 8, 9, A1, A0}; //connect to the row pinouts of the keypad byte colPins[COLS] = {2, 3, 4, 5}; // Create two new keypads, one is a number pad and the other is a letter pad. Keypad numpad( makeKeymap(numberKeys), rowPins, colPins, sizeof(rowPins), sizeof(colPins) ); Keypad ltrpad( makeKeymap(alphaKeys), rowPins, colPins, sizeof(rowPins), sizeof(colPins) ); unsigned long startTime; int ledpwm = 10; int pwm = 0; int ledPin = A2; void setup() { //开关 Serial.begin(9600); //pinMode(kaiguan,INPUT); //SurfaceDial pinMode(channelA, INPUT_PULLUP); pinMode(channelB, INPUT_PULLUP); pinMode(dial_Button, INPUT_PULLUP); attachPCINT(digitalPinToPCINT(channelA), changed, CHANGE); attachPCINT(digitalPinToPCINT(channelB), changed, CHANGE); SurfaceDial.begin(); //BootKeyboard BootKeyboard.begin(); //Keypad ltrpad.begin( makeKeymap(alphaKeys) ); numpad.begin( makeKeymap(numberKeys) ); ltrpad.addEventListener(NULL); // 取消自动按键监听,更改按键状态时不会自动调用函数 ltrpad.setHoldTime(500); // Default is 1000mS numpad.addEventListener(NULL); // numpad.setHoldTime(500); //Led of KEY_NUM_LOCK pinMode(ledPin, OUTPUT); digitalWrite(ledPin, LOW); // Turns the LED on. //ledpwm pinMode(ledpwm, OUTPUT); } void changed() { int A = digitalRead(channelA); int B = digitalRead(channelB); int current = (A << 1) | B; int combined = (previous << 2) | current; if(combined == 0b0010 || combined == 0b1011 || combined == 0b1101 || combined == 0b0100) { counter++; } if(combined == 0b0001 || combined == 0b0111 || combined == 0b1110 || combined == 0b1000) { counter--; } previous = current; } void nextpwm(){ //设置下一个pwm的值 pwm = pwm + 51; if(pwm>255) pwm = 0; } void loop(){ //如果打开开关高电平则工作,低电平则不工作 //if(digitalRead(kaiguan)){ //初始化背光 analogWrite(ledpwm,pwm); //SurfaceDial bool buttonValue = digitalRead(dial_Button); if(buttonValue != previousButtonValue){ if(buttonValue) { SurfaceDial.press(); } else { SurfaceDial.release(); } previousButtonValue = buttonValue; } if(counter >= 2) { SurfaceDial.rotate(10); counter -= 2; } else if(counter <= -2) { SurfaceDial.rotate(-10); counter += 2; } //led状态改变 uint8_t k = BootKeyboard.getLeds(); //Serial.print(k); if(k & 0x01){ //如果获取数据的numlock是一则点亮led digitalWrite(ledPin, HIGH); first_keyboard = true;//数字按键被禁止 } else{ digitalWrite(ledPin, LOW); first_keyboard = false; } //Keypad键盘数据获取 + BootKeyboard发送报告(如果按键状态改变) if(first_keyboard){ digitalWrite(ledPin, HIGH); //是第一个键盘,两层键盘中的第一层,数字键盘 if(numpad.getKeys()){ //获取按键更新,有更新返回true //循环检测激活按键列表 if(change_pad_flag){ //此部分可以防止两个键盘切换时兼职numlock的按键误触发 //numpad.key[0].stateChanged = false; Serial.print("in1"); if(numpad.key[1].kchar=='.') { numpad.key[1].stateChanged = false; } else{ if(numpad.key[0].kchar=='.') numpad.key[0].stateChanged = false; } change_pad_flag = false; } for(int i=0;i<6;i++){ if(numpad.key.stateChanged){ char x = numpad.key.kchar; Serial.print(i); Serial.print(x); //Serial.print(mymap[x]); Serial.print(numpad.key.kstate); Serial.print("<>"); switch(numpad.key.kstate) { case PRESSED: if(x=='L') { first_keyboard = false;//反转 change_pad_flag = true; } if(x=='R') nextpwm(); BootKeyboard.set(mymap[x],true); break; case RELEASED: BootKeyboard.set(mymap[x],false); break; default: break; } } } Serial.print("<-1->n"); BootKeyboard.send();//发送报告 if(first_keyboard==false) { //离开前释放大小写按键 BootKeyboard.set(mymap['L'],false); BootKeyboard.send(); } } } else{ digitalWrite(ledPin, LOW); if(ltrpad.getKeys()){ //获取按键更新,有更新返回true //循环检测激活按键列表 if(change_pad_flag){ //此部分可以防止两个键盘切换时兼职numlock的按键误触发 //numpad.key[0].stateChanged = false; Serial.print("in1"); if(ltrpad.key[1].kchar=='a') { ltrpad.key[1].stateChanged = false; } else{ if(ltrpad.key[0].kchar=='a') ltrpad.key[0].stateChanged = false; } change_pad_flag = false; } for(int i=0;i<6;i++){ if(ltrpad.key.stateChanged){ char y = ltrpad.key.kchar; Serial.print(i); Serial.print(y); //Serial.print(mymap[y]); Serial.print(ltrpad.key.kstate); Serial.print("<>"); switch (ltrpad.key.kstate) { case PRESSED: if(y=='L') { first_keyboard = true;//反转 change_pad_flag = true; } if(y=='R') nextpwm(); BootKeyboard.set(mymap[y],true); break; case RELEASED: BootKeyboard.set(mymap[y],false); break; default: break; } } } Serial.print("<-2->n"); BootKeyboard.send();//发送报告 if(first_keyboard==true) { //离开前释放大小写按键 BootKeyboard.set(mymap['L'],false); BootKeyboard.send(); } } } } |
|
|
|
只有小组成员才能发言,加入小组>>
3310 浏览 9 评论
2991 浏览 16 评论
3492 浏览 1 评论
9057 浏览 16 评论
4086 浏览 18 评论
1176浏览 3评论
604浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
597浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2334浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1895浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-22 16:54 , Processed in 0.909969 second(s), Total 47, Slave 38 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号