搬运同学课设,一起学习学习 成品展示视频如下: 成品展示视频
目录
背景整体设计主要功能硬件设计与仿真软件设计附录
背景
键盘设备是我们使用最多的人机交互设备之一,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 <Keypad.h>
#include <ArxContainer.h>
#include <PinChangeInterrupt.h>
附录
#include "HID-Project.h"
#include <Keypad.h>
#include <ArxContainer.h>
#include <PinChangeInterrupt.h>
int channelA
= 16;
int channelB
= 14;
int dial_Button
= 15;
volatile bool previousButtonValue
= false
;
volatile int previous
= 0;
volatile int counter
= 0;
arx
::map
<char,KeyboardKeycode
,41> 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;
const byte COLS
= 4;
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
};
byte colPins
[COLS
] = {2, 3, 4, 5};
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(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
.begin();
ltrpad
.begin( makeKeymap(alphaKeys
) );
numpad
.begin( makeKeymap(numberKeys
) );
ltrpad
.addEventListener(NULL);
ltrpad
.setHoldTime(500);
numpad
.addEventListener(NULL);
numpad
.setHoldTime(500);
pinMode(ledPin
, OUTPUT
);
digitalWrite(ledPin
, LOW
);
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
+ 51;
if(pwm
>255) pwm
= 0;
}
void loop(){
analogWrite(ledpwm
,pwm
);
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;
}
uint8_t k
= BootKeyboard
.getLeds();
if(k
& 0x01){
digitalWrite(ledPin
, HIGH
);
first_keyboard
= true
;
}
else{
digitalWrite(ledPin
, LOW
);
first_keyboard
= false
;
}
if(first_keyboard
){
digitalWrite(ledPin
, HIGH
);
if(numpad
.getKeys()){
if(change_pad_flag
){
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
[i
].stateChanged
){
char x
= numpad
.key
[i
].kchar
;
Serial
.print(i
);
Serial
.print(x
);
Serial
.print(numpad
.key
[i
].kstate
);
Serial
.print("<>");
switch(numpad
.key
[i
].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()){
if(change_pad_flag
){
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
[i
].stateChanged
){
char y
= ltrpad
.key
[i
].kchar
;
Serial
.print(i
);
Serial
.print(y
);
Serial
.print(ltrpad
.key
[i
].kstate
);
Serial
.print("<>");
switch (ltrpad
.key
[i
].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();
}
}
}
}