单片机学习小组
直播中

倪love

11年用户 870经验值
私信 关注

如何实现基于Arduino的HID数字键盘设计?

如何实现基于Arduino的HID数字键盘设计?

回帖(1)

姬盼希

2022-2-15 10:43:29
背景

键盘设备是我们使用最多的人机交互设备之一,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 mymap
{
        { '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();
                        }
                }
        }
}
举报

更多回帖

×
20
完善资料,
赚取积分