找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 24374|回复: 9

【灌水无罪】老机械键盘改造USB,QWERTY/Dvorak一键切换

[复制链接]

24

主题

39

回帖

288

积分

vip

积分
288
发表于 2016-9-21 20:34:19 | 显示全部楼层 |阅读模式
本帖最后由 miaozhuang 于 2016-9-21 20:35 编辑
3 u" `5 E$ K- b  m  O! e* R; @7 D/ w9 O+ @6 m
http://bbs.eeworld.com.cn/forum. ... digest%26digest%3D1* [- P: p% f* j$ a% ~! x
这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。; [* G' c4 h  F% ]) V
  E# t0 z; m) z$ a3 H0 ^6 u! d
在看这个帖子的诸位一定都在用计算机键盘吧。键盘上的数字键是1到9从左至右排列,或者是右小键盘区那样三个一排有序排列,反正规律很明显。但是字母键却不是A,B,C...到Z这么按字母序有规律地排下来的。我刚接触电脑(其实还是学习机)的时候,没在意这个问题,觉得是要盲打嘛,反正对两手的手指头来说,按字母序排列并没有什么好处。于是用多了这些排列也就记住了,从来不管它为什么要这样。其实,PC的键盘键位排布上是延用了打字机的键盘,这是设备演变过程中很自然的一个延续。打字机的历史就要早很多了,我没有亲见过打字机长什么样,而且,咱们汉字是铅字打上去的,和英文打字机方式完全不同。
: c/ I" q! d0 u# u, v) ^

3 T9 W1 t5 {2 J% v0 O6 D) Z$ { 235140i3a36qivqzuvmt5q.jpg.thumb.jpg
6 v9 e; z* _3 N# G5 S9 s' N
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。9 c& V* C! u# E* d1 t# i! u
001734klbyoluenuwz4h4b.png.thumb.jpg
& B( [0 {/ S' i& g. M. I为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:2 u+ x/ A/ a% e. \8 z, t
003625r2agx2f5v922cf2f.png.thumb.jpg
. b1 w; [/ q. _0 E其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.
8 Q; C: l  A- g% X0 N5 XDvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:
+ {3 _  z* c/ j7 c! |3 N) G0 g3 z
005836yvs0wvovwsssgd3o.png.thumb.jpg
4 ~  V/ y  \! k3 hDvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。8 P, m$ I. D6 \
7 T& V7 @3 o, s' l, p  S# _+ y/ C
我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。
- T: x) M0 [6 z; e* X- B
. k% [7 j3 R, K% ~) q' D% l+ {到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。& z2 b) n7 s' r
3 {& T1 c$ Z5 V5 F6 G1 V
机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:

0 p* v8 q; `2 r 123206vx24rfxfp2dfrpxg.jpg.thumb.jpg 6 a" [- l5 \/ D4 w! Z; ?" s
----------------------------------------------------  分割线 ----------------------------------------------------------0 n- i# ^1 b3 J& q2 F
0 i/ v. D8 [' E2 W! z
先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

. g5 V8 S  o5 E9 I" h) c( A6 @ 020011osionbunl4ui44vi.jpg.thumb.jpg 9 o; b/ R0 b0 [8 |
轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。" x- _4 U3 D+ X  M+ f, C, H" l
020017j8ycmnv7788bqv52.jpg.thumb.jpg 8 K8 x9 r+ o$ w
特写,80C49
! C9 A1 K! G0 l; y 021040oujzuvtut6iujtvz.jpg.thumb.jpg
8 A4 R; v' e! O' MLED部分,使用了一片D触发器锁存指示灯状态.+ [+ k; e( C, r8 G, ?8 d
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg ) ^# W! w4 e& `1 g  c
暴力破坏,将80C49拆掉
( H9 C* \$ N8 q) P) `# T) Q 021113e48qq98vyohvhzzh.jpg.thumb.jpg
, D2 V  Q3 S8 L' B拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。
: `$ }0 x( A; M 021125nc9az6dj33rlds2r.jpg.thumb.jpg $ j; ~. {& X2 n
焊好元件后的板子,准备替换80C49& @6 w) u8 Q7 H5 @9 b, r1 H( s, H
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg
- o9 T& v/ X" J( `用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。; t' l- F+ ~5 Q% M7 P4 S: _
021104shifhnrqbr3o5nlo.jpg.thumb.jpg
( L8 L0 q0 `' u, B这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB., O7 A9 f  v; S# F- h6 x- C/ e5 v
022003ym1p9u4ug40280uu.jpg.thumb.jpg
) |! X- y4 p7 ~# D开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。
6 m% D( X0 Z& B  W* ]' i6 ?. F 023313kt141q9qajtol7ma.jpg.thumb.jpg
/ e* e+ l" r; D  g/ T' F我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。) u$ m. l$ [: T$ \/ M
023322nt7l5xb3ltttkltt.jpg.thumb.jpg $ u! A7 b9 x0 f) D* P7 B
主键区键帽就位
, V9 j: S: e/ k5 L 023331hin88e8wkrwzwikx.jpg.thumb.jpg
, D' `* K' y& M编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。
' k( q% P9 \7 ^+ P; M7 Y2 I) U 023336wjzlgopugg1jyy79.jpg.thumb.jpg
3 q/ \* F( U* o* E( h* l' Z最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
# R. I7 q% P$ G3 o( ]0 T6 A* n( t; L6 `; r 023341sffu4j3g2323h6fl.jpg.thumb.jpg   }, r) l8 {' y( K% A2 ]

: l3 t' W4 `2 s2 {2 f& _4 Z----------------------------------------------------- 分割线 --------------------------------------------------. M9 V# s3 e/ T5 q9 X' n  S# }

8 F6 ~7 H; G$ j8 C# V: B0 f5 I8 n

5 I+ V4 E6 b* j( O+ k
+ l2 I; R" c$ Q( m4 s, y1 i$ g6 l( }; F( M6 V2 r
" ?2 J  Z( ]- S6 K7 [0 }
4 V2 i2 E7 y; e1 q3 t2 Z
; C' c: m; L! f3 Z2 E9 Q9 C( E

( Z. M! f4 E, i8 ~8 H- i# u6 J5 X/ _4 Y
, p* i" Q0 J  M6 _, x, l  p; b

# w6 C7 y4 j/ Q, n5 v) m
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。3 Y) a" ]" h# c4 H0 j4 r6 P; G. ^
80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:5 w) Y4 @4 I# E
104025nzibm2rmiomhyirm.png.thumb.jpg
# H: O7 d- f# K( ^) B

: P! y3 R, w# H  v& @3 V* R' T其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)% [. N9 @9 g  V* [) w$ s
105004zkrez5houvkkznko.jpg.thumb.jpg
2 r7 v( W8 R  P3 g5 n

; z8 N) e! S' ?扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。
- P- `5 C3 E0 [+ u: X/ V9 s' w1 ~
0 D" Q) c9 x9 h. ]* [这是我设计的电路图:
4 v5 S' a  q' L7 g1 V5 f 110344ej2z2oo2rflo7oe7.png.thumb.jpg $ X8 i% j2 U/ }6 l' ]+ z) `

2 o/ r7 {9 i7 ~" APCB Layout:
7 l7 u! W" s7 A; v. e 110847jjbjvt34vwt3v5bb.png.thumb.jpg 1 N0 B. ?$ N5 D, @7 F

, ?+ F6 T* f4 P; f$ o& j6 }不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O. , Q$ ?; X8 q6 |
+ C# _! e2 ?7 [
/ n! W/ z4 T# G% A
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑
( u4 C& y$ Q$ a
' ~9 e% M; H' q/ u8 K0 t软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。
9 _5 W! D2 d0 y$ S
: G1 @6 x% [4 z# J4 O! B总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:
# v  c4 c9 K- Q2 }% i7 F9 a 113818pmrfsb6z0byt6t06.png.thumb.jpg ' ~% g* X# W8 F9 b$ f
4 n6 x6 j% |6 p' V. @9 Y" k1 A9 E
其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)
& E, {6 y9 L5 A" {) a) E* X4 t4 y: K0 \3 {
USB的中断ISR,bare metal哦! y+ }: I; E6 v8 b. B0 G; T8 i+ @

% u3 i8 f9 Q* {+ N! f% kvoid USB_IRQHandler(void)
. j5 N5 ]3 {) H" ]9 n7 K- o9 T{) L8 @4 w6 `8 P( `' A7 b
    if(USB->ISTR & USB_ISTR_CTR)
6 [8 H0 A* [3 U1 s    {7 ]7 b, a( M3 K. N+ o& h4 R! F
        if((USB->ISTR & 0x0f)==0)   // EP_ID==0
% V  A" k% y" `* d% Z        {
0 @) R2 c, a4 R* ]8 |9 B4 s            switch(ep0_state)) _1 x. y8 I5 I' n8 c
            {
0 V! ^8 }, e# e7 O( e4 v+ o                case 0: ep0_state |= 0x80;
. {5 A; v2 N, j                        break;
* f0 s$ s% ~9 _4 F; {                case 1: if(USB->EP0R & USB_EP_CTR_TX)
* f* ~" o- l/ P                        {3 t5 ^1 o8 D9 c$ a" ~
                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
! z$ Q4 M9 @% N6 @' ]% j                            ep0_state=3;+ v/ v( p: A! M- z- J' x* i
                            return;; Q5 g. g3 Q4 j+ W  M; R" H
                        }
0 D) K' ^) G, N! M! q1 f                        else1 ^$ T+ p: o6 i) p# O; X0 J
                            ep0_state=0;
( M/ L7 w- s( s" a0 u: G( |                        break;
/ q7 n7 L) F& Z8 ?                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet9 _, Q8 n) Z8 C7 ?9 J  O/ m
                            ep0_state |= 0x80;7 l6 g4 [* j5 I% V0 H5 a  i
                        else3 C/ n7 t4 c( q) D, u4 f6 _2 ?
                            ep0_state=0;4 B3 @# @$ W' H  q# `8 e% f
                        break;
9 s3 Y# {. `8 U                case 3: ep0_state=0;
; g  l& f, e' I6 G/ h! W                        break;; z; k: E/ `  t
                case 4: ep0_state=0;% z5 C6 l8 q' d. B
                        break;
* X" L) B! e, P1 I# ?- e/ j                default:ep0_state=0;+ D8 m) W5 Y1 G1 g$ ]8 `, r- K! \
                        break;
' n6 M" ]3 l: l! y5 A. \            }
& R, q& c4 g. d6 G            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag- i& L+ S1 N4 C+ `& k5 ]5 f  A' n7 T
            return;$ \9 Q# Y/ P) v, ?
        }
% F0 R' U8 L% T" j        else    // EP_ID can be 1; [" a7 }0 O: H$ q# ^
        {
6 V9 [+ H7 f# R7 p            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag
" r7 `( _$ n  ^            ep1_wait=0;9 b8 e  |) z0 |: D5 S# a
            return;- h$ x7 g1 R" ?5 D) l* x
        }
: `3 p- m7 S4 P  \1 o    }; g) M, \+ L% L- H1 h$ D8 _
    if(USB->ISTR & USB_ISTR_PMAOVR)' d3 P% C$ j& L" m$ O+ }* ], Z
    {
1 k3 s; C* G7 ^% k  d" W3 M) R4 S        USB->ISTR = ~USB_ISTR_PMAOVR;6 Y+ O1 Q5 u; {- ~
    }
# g  J/ q: R& |) J. A    if(USB->ISTR & USB_ISTR_ERR). j! g1 K5 E! k2 `- F9 E
    {% Z" B" W: {$ d, K! @8 \3 e1 Z
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear
; v/ V. c! q. G( L& u7 p    }2 l# N" r5 B8 z/ [  D
    if(USB->ISTR & USB_ISTR_WKUP)" J$ _( ?5 g, P6 Y- _
    {
/ [; Y" P1 Q" \5 \9 U        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
+ J" L9 g7 G  y, G        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear
* F0 d& ^: N1 w2 U, F    }3 m- Y( U- o$ H' f  w* ~; X
    if(USB->ISTR & USB_ISTR_SUSP)* a! q' b) a4 I8 o, s/ `6 }
    {
. C% G# t1 Y) f# Q4 Z        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend+ B3 E; x; e5 k  I% K: b/ M
        USB->CNTR |= USB_CNTR_LPMODE;   // low power* y. L/ t1 S6 w! W. D0 F2 x, ~/ x( X
        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear
8 r: p( @  m; M& @, [    }8 y+ d5 ^" d0 i6 p7 Y
    if(USB->ISTR & USB_ISTR_RESET)
7 j: w  Y2 `4 _; }! F/ L) R+ R    {
- c3 r  z2 l* L        USB->BTABLE = 0;    // buffer table at bottom of PMA  a5 q2 F1 c* Z* t, G  k* S
        USB_PMA[0]=128; //ADDR0_TX; ~; H" U! U  H; L6 p
        USB_PMA[1]=0;   //COUNT0_TX
) }( L) r( y7 w( }2 Z( Y        USB_PMA[2]=256; //ADDR0_RX3 L& u* y0 P( I  g
        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes
& E" F+ L/ S7 D0 O7 i+ f        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;; ~3 h& \, K% t
        ep0_state=0;! \* Q* N5 {' {0 e9 u& `
        USB_PMA[4]=384; //ADDR1_TX, V5 z+ e# }/ e
        USB_PMA[5]=0;   //COUNT1_TX
+ u# ]( t6 t# ^        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type" C4 M0 y2 t1 n# X
        ep1_wait=0;
, b: V; |- p4 ], N8 r) V! H; s        USB->DADDR = USB_DADDR_EF;      // enable function2 K. {# T9 {4 C* p" @- h4 S, E6 _
        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear; C1 o+ G6 b' W
    }, G  g, E7 z9 a, Y) ^4 r7 y, D/ Q
    if(USB->ISTR & USB_ISTR_SOF)& x, S4 |) D( m/ I5 W
    {
2 ?. }2 c9 P. A, J  q        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear) ]/ `3 z6 M" K- @
    }3 G: n* q" c8 q9 Y4 P
}. b6 o3 n2 V" Q2 e# J% K6 w% l

3 {8 j4 p2 A3 u4 k' @+ w2 R# `+ [
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。
6 c. z; `4 F6 \% S1 |; F6 ^0 Z: \4 ]# ]5 H% q1 c" r
主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。
/ z/ W! P& I3 z) Y: T8 \1 T( O. Y' c3 ~  u2 T' x- e  _
    while(1)
/ u+ D+ B2 C7 y7 v( N- ?    {% i7 V0 W- q, n2 @* t) S
        static char row=0;
+ s& @% j! B$ f" a& ~. s        __WFI();
' i: A8 M+ Y$ J; {. z8 @        if(ep0_state & 0x80)    // request data processing; t9 \6 [) n3 ?% L
        {
$ r$ P" _, U0 q% I            if(ep0_state==0x80) // SETUP phase9 R8 z- i1 Z  Y' ~8 D4 I
            {' x) c' |- z: z5 @5 d4 @
                if(!setup_packet_service()). L0 I9 J' e9 z8 S. Z
                {
, n$ |' p; s# N0 w. N0 e                    ep0_state=0;# _) o9 e1 Q5 u. q$ a
                    // not supported
2 u4 A/ S2 o$ R& z0 B) d5 T                }
; [, a6 w1 m7 o( c7 u7 O                // ep0_state should be set to 1 or 2, if processed
; F$ P. Z# {, `! w0 T* U            }9 q# Y; p* G& ]* {# @, b. k2 k
            else    // OUT phase6 v1 F% G4 X7 D3 ]; t
            {
4 I# W# R2 t8 ?! _                // process data6 w* j: ]$ |$ d6 i$ E) p/ {
                show_LED(*(uint8_t *)(USB_PMA+128));
$ Z2 D" i6 o9 `. ?5 l# K                ep0_state=4;5 T; |% b4 t/ q$ _& J( C
                USB_PMA[1]=0;       // Zero length DATA0
: w0 @8 q1 |$ x& a5 G                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;6 `& C; U3 L4 _6 ~8 F/ _' G" U' }& I- U
            }
: A1 D9 Z! N$ T1 b- ?% H- d) z- P+ q6 b        }
7 g  q4 n6 `9 r5 p/ h; Q, q        else/ u# J- C# A7 ~$ e
        {1 R9 [" ~. d4 j' ~% J4 [
            if(usb_address && ep0_state==0)7 h9 ]7 l0 W7 p& w0 T8 Y5 ?
            {3 c. {6 t$ {1 }: h' G5 e
                USB->DADDR = USB_DADDR_EF|usb_address;
4 ?( O6 \5 ]' ]% ?* l* a                usb_address=0;+ C& R& t0 v8 u6 C$ D1 m+ O( {
            }' |' y7 S3 n9 K3 d1 }5 {2 {6 I7 I0 u
        }2 L. x$ c, U8 \! G6 T
        if(row!=scan_row)   // new scan line+ b" [6 s! c7 [
        {
" e" V# ]; _. U0 ^3 Y* Z( @            if(key_state[row]!=prev_key_state[row])
% K& j3 ?1 Q1 T- [+ N            {9 o% Y% P: g9 c9 a: S: d* a6 q
                uint8_t test=0x80;1 u0 X& Q. M/ `( P
                uint8_t diff=key_state[row]^prev_key_state[row];) E9 E1 r, @0 ^( N8 w7 }5 H
                for(i=0;i<8;i++)7 h9 a8 t9 e9 D, i7 O2 z3 Y
                {
; S+ W- D- A1 Q/ e; d" l  S# P                    if(diff & test)
3 p% I: b. x) x/ ~' |* t3 n2 F                        update_key_matrix(row,i,key_state[row]&test);
. O) Y$ G" o. O& V                    test>>=1;, d$ S3 c9 g. M
                }: v9 y. @" c$ O& f
            }/ N& F/ [8 l+ u6 i% ], E7 e
            row=scan_row;
/ C9 e! F, l4 g: [  B        }
) ^7 Y5 R# [- S& H    }* u" I1 Z) N1 w
' r4 I8 _# e+ ?2 L( }. ?. b

4 G# ~4 W* D; Y0 HEP0的控制传输,把用到的请求处理一下
+ ]( m/ t1 \: ~& q4 u% h2 S1 xchar setup_packet_service(void); v, n/ K( F2 b- |, |
{
& K, P2 Q# y; X! a    if(ep0_std_req->bmRequestType & 0x20)   // class-specific) q& o* s0 {  ?
    {/ O! L/ `0 ~( y2 G- A
        switch(ep0_std_req->bRequest)
8 F! ~( [! k8 b        {. `7 {& v3 y( i  A) E
            case REQ_GET_REPORT: break;
4 z/ ^+ c' E: l, q            case REQ_GET_IDLE:
0 `% Y' w, x7 p; U                USB_PMA[64]=0xfa;   // return 1 byte) ?9 e, ?8 |3 l* ]
                USB_PMA[1]=1;
9 }( k" d3 m2 p                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
$ K' x' V' i+ o7 n7 A                ep0_state=1;- `& A2 x8 |+ ?. ]2 M9 V
                return 1;. O+ J9 X6 N( K; A! k! e
                break;
# G# L  Q% ]0 f: X3 Z$ o  |. P            case REQ_SET_REPORT:5 x* u/ @2 Z9 S% N
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;5 x: [( F) @$ @, o
                ep0_state=2;
" l3 R( x! X9 E9 e" ?# I: D3 N; i                return 1;0 ?* \8 b! D# _+ t+ l$ `2 O# ^
                break;1 x/ ~+ o+ u6 `1 R
            case REQ_SET_IDLE:
& B7 t/ S! p0 H                USB_PMA[1]=0;   // Zero DATA; M4 I) L% v/ l) H1 C
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
! K5 `+ @5 K, S, U1 X                ep0_state=4;9 |3 E; `2 \9 e% r
                return 1;
% b  Q3 u& n$ J. y3 _+ s                break;
( t  O) O) G, k* I6 }        }
$ B% _4 v0 P& m* c9 L6 J8 E2 ?, L        return 0;
( K) p  Z& T  X* m" h3 U' n+ L    }1 l4 t+ t2 a4 x
    else    // standard; p$ e) y; z1 c; g
    {
, H: a" h! b% q. V        switch(ep0_std_req->bRequest)9 R9 ]: w+ \1 P9 A
        {! K, s- Q. N2 {0 U* n" `2 L
            case REQ_GET_DESCRIPTOR:
% u! e/ c$ [+ m% {) _) [6 G; {                return descriptor_service();; W7 y. _" H3 F& j. X1 A
                break;- Q8 Y& y5 J8 `  C5 R6 I* j
            case REQ_SET_ADDRESS:
. w6 S; f/ d# m1 t, M" ]                if(ep0_std_req->bmRequestType!=0x00)
3 l' f) d8 U9 K1 I8 A                    return 0;
; Q* ^1 y$ u. ^$ D2 O# C% W" V. u                usb_address=ep0_std_req->wValue;* F9 D2 j7 d1 E: Y% M
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
9 y. v2 t: w% @, ?) J! a                USB_PMA[1]=0;       // Zero length DATA0
1 N5 g6 ]2 S, r+ y                ep0_state=4;    // No Data phase' M  b! g, d& a0 k1 I, U
                return 1;# t+ b+ i4 f: C$ W7 S! v
            case REQ_SET_CONFIGURATION:, ?. o; F5 ~4 [# u* N
                if(ep0_std_req->bmRequestType!=0x00)
  C: z; q# c- |  S                    return 0;! b8 e9 q) \+ a0 H6 s% Q
                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
, P6 w1 ]- @( b                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;! ^. a# R5 f' [% p1 F
                USB_PMA[1]=0;   // Zero DATA
  w$ c' y9 l- f3 J" A& E                ep0_state=4;    // No DATA phase
; U( A; \8 I6 \8 [! S                return 1;- S) r2 H1 A/ q% V
            default: return 0;
% Y# H& ]$ m5 Y+ w3 C9 q        }
7 G4 V2 o% U4 }2 ?% y4 ?    }
6 B/ B9 [; C, w, I}
) v( c0 x# ]/ N6 x2 L
' P: r+ D  k: |6 M% P  N8 s! I; O% a. j; [5 t+ }" E  R/ k

7 K3 X) h6 o* A* I. S
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的
+ s* Y  T% Z+ u5 O/ t" E. H2 ?' vchar descriptor_service(void)
, R, j; F& a$ \  V2 p$ N{
' h7 U+ j7 z- u$ A$ V3 \    switch((ep0_std_req->wValue)>>8)
% E. U# q8 B7 l) `" s1 ^    {- \: ~. \" z& v- c  W
        case DESC_TYPE_DEVICE:1 m3 u/ z2 K/ w
            return ep0_preparedata(&DevDesc, sizeof(DevDesc));- [  `( ^# M8 r8 ]- j0 G9 W3 b" E; W
            break;8 q/ J  ?8 y7 w" x, c, `3 {
        case DESC_TYPE_CONFIG:
4 I& u  a' H& w2 G- E            if(sizeof(ConfigDescData)>ep0_std_req->wLength)3 M- h, |6 l" \( o7 w- t( ^0 N+ o8 h
                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);
2 j% [9 Y% \8 X+ i            else
! `; X- o  i$ @  t# l; C0 N                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));
5 c$ v" U, s' P* |            break;
4 O$ e& [" n. E. h        case DESC_TYPE_STRING:
# h; J- X2 v2 w4 J% i4 f            switch(ep0_std_req->wValue &0xff)
, c# P  C- _6 R            {
. }0 w' u3 P4 [8 f- T5 H! @                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
, t  Z& {6 d" R1 n: `" \" ?& L* N                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));
6 K: g% a1 z- U5 U  j                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));; r9 V  o/ O( q0 C5 N2 Q
                default: return 0;7 p6 j$ q1 N1 L( ^+ C+ P( J
            }
! l9 M1 u% O2 b            break;  y4 |7 _% q  S) [
        case REPORT_DESC_TYPE:  E7 ]- A/ u$ i" G* x% i
            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));
6 g) b) t" `( f+ ^# R2 O- K) V        default:
5 @% U6 L+ w5 Q6 d% F& k            return 0;
& l+ w, ]1 R) ~! W; |# j    }
. V3 k$ a* O+ j0 @7 b! |}
, G1 Q) X; p% h6 t6 O/ l下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.
0 ?# z0 W& y5 q4 i! H- mvoid TIM6_DAC_IRQHandler(void)$ o1 e7 ]" h# B0 G: D/ c% Q
{
; a6 i8 O' B2 E" `    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);
9 ~5 m  y3 o/ [( k  h: Z' H9 T* w) w% n) ~, K

" W/ G1 _  x& `9 d; n    TIM6->SR &= ~TIM_SR_UIF;5 @! i8 G5 @; e3 k0 _: B
    prev_key_state[scan_row]=key_state[scan_row];4 Q, E4 n; G5 r0 {& Z
    key_state[scan_row]= *PA_IDR;   // update key states
% Q3 n' m" B7 S- H6 m( q3 c: c    switch(scan_row)8 c# e9 R) _0 e* h/ A" x
    {2 r3 h  @1 @$ T8 M. V: _
        case 13: // next row PB14
" s' v, O5 V$ A1 E. r1 z. M                GPIOC->MODER = GPIOC_DEFAULT;
% a* Z* o/ n' q8 m' O                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;; Z. D( S! B0 |7 l! q
                break;0 z  Z! ^9 E1 @
        case  0: // next row PB151 c/ |, p9 u# R! N+ Q+ u
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
- d5 w5 D. [& k" X& F) s: y                break;4 E: ?4 Z0 N+ _; H) l5 p
        case  1: // next row PB38 ]# V* q/ I3 ^
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;9 M$ S$ [, ]  C( b0 q, m6 C
                break;
# I5 w: J& c( O" G" X6 N, m4 \9 w        case  2: // next row PB4
( `& {8 h4 {0 i+ k/ W3 V                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;% l3 C, i# j' B+ g9 U: c) }
                break;. I1 T8 M* @0 ^1 T" q; [  E
        case  3: // next row PB5& [- W2 U  i6 j
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;' }% V+ x9 \0 T
                break;9 a' Q1 p# |. |
        case  4: // next row PB6
( u+ q- }0 u2 |3 u                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;
) u) B' [2 c4 U0 W1 q3 s$ H                break;
( v3 N+ k. |) t' z( Z        case  5: // next row PB7
; H2 }" q- c3 {                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;6 U; _" I' ?- e# Z* k+ }
                break;) O2 L' _" H1 c& J
        case  6: // next row PB8, G" p; g1 S/ q7 B; m
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;
6 l8 s4 |7 C6 u9 K% M                break;
& s' J% m- M* Z$ ~7 S5 M        case  7: // next row PB9' e8 t" y$ |5 b! F
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;
( |# A- u$ |: ~3 q* T6 x                break;; a# d5 I* r9 H* W2 D, Y1 j# Q
        case  8: // next row PA8
6 }1 e" r! L; \) r* |9 M                GPIOB->MODER = GPIOB_DEFAULT;7 y+ Q' f* l: u6 n
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
* t  C/ v7 w" P- R3 ]. m                break;( V6 ]. b3 H; W4 g7 G& F
        case  9: // next row PA9$ M  h7 Z9 Q% B0 W, m; |
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;
/ h$ z* K; @8 f- ]$ Y" ^8 ?4 [                break;
: a) J. D: D& v2 t        case 10: // next row PA10# M, W3 C' g+ I/ n
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;/ ?* e) T5 I- ]
                break;' C& K1 T$ B' Z4 C6 Y
        case 11: // next row PA15
4 W* f$ e& `/ a, f, D                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;+ [: p& J5 P5 E& T
                break;
4 D' t6 Z% o. u8 ?" o) M        case 12: // next row PC13
$ x3 t5 R$ h- h4 e/ _1 N                GPIOA->MODER = GPIOA_DEFAULT;8 I5 C6 M/ ~  z0 y6 b" a6 Z% F/ M) {
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
- x+ l: {! Q5 D3 L3 L# l/ [                break;
& P8 {- z, t8 `5 y    }8 ?! s: o1 O8 N9 G3 j# Y% R+ A' a
    if(scan_row<13)
8 J# w& B' K- c2 h7 k8 u        scan_row++;
0 z' _3 l' X0 M) C* g. z    else9 @! J1 {# o, V' S; Q0 `. ]
        scan_row=0;; S8 i1 u. ~+ ~" q( m+ p- d
}/ k- v4 x: \* {6 f# N5 [- G2 R; P

4 \) _+ c% I( B  ^8 x8 x4 U, V8 e
" d7 g3 M# O* r4 y" Z
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑 $ d  T# @; Z2 G5 }( l( d4 T0 ?
+ h( |4 `8 J% ~) z" U# @7 |
扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。  ]7 @9 n5 p! R8 I! e

; A) O$ ~* N4 f( y' Z" d4 |要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:

+ o$ ?# c  b& @3 g
3 [5 \, Z* o7 ]3 R6 n$ e* j6 A1 [) L/ i1 A" ^
const char hid_keymap_qwerty[14][8]={! ?/ M! v) F8 E: F6 n
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},* ~' S1 ]; E7 c1 t. t- |
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},5 l0 T* a) S- E) i1 z2 h7 b7 Q+ j
    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},( ]* y: Y" Y- ]: P
    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},
" j  E7 c" ~% T3 i0 U$ W2 n  B' P    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},3 }8 V6 T: b7 z* m
    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},
8 w0 R7 o4 Q1 E. `    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},5 e- N& _+ c3 a7 s& _3 A" }
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},
( `4 {/ v  d  e- U8 l    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},( G, y7 G/ ]0 F/ A
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
: D7 E0 u- w* A7 P) \    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},( ^3 u* y  T8 {3 e- h- h
    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},# o; A% S0 X/ L- e( Z9 ?
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
' _( L1 W( F& l( P    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
9 X8 x9 X. A7 E! d  w8 K! D* }' h: I};: n$ {3 @( Y6 d$ v; c. U  J
" u, }* y" g# M" p; R
/ D9 r- E: Y! M4 h; k6 q
const char hid_keymap_dvorak[14][8]={8 H" H/ x1 L' A. K2 m4 a" y
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
1 ^  B, P/ r2 W1 b3 A+ T    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},' |% Z( J7 r+ z  m
    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},
3 f: M- Q8 l% j/ V  X    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
  i. C% |; b# P    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},
* `: ?9 x+ q8 a+ \, P    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},/ t, ^. R' K3 d* P: w- V
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},' c+ ~* n4 d/ H' }5 G3 F+ f
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},
6 @; f: _' j: S5 D1 d    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},0 B" f' T# U7 Y
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},7 W- u1 y. _% \3 _: p$ S
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},5 I- n+ Q- [+ u6 }& |
    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},
6 J, d: x1 O8 J  B' Z) v8 f    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},
3 W& |( P* g0 p7 ^! z    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}& }4 y$ I) K& C. Y
};. @. F0 E  O, e
* z9 t8 A$ M% q

2 U2 h  C$ x; ]5 [1 i" _- U  |上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
# M' b5 e- ^+ _$ W( y
# v. T3 g% p0 Y2 D1 Y0 rHID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:
# ]. w& z! M2 q# E, l0 ~
9 g. Q0 J  p6 m  R* M4 J
void update_key_matrix(char row, char col, char onoff)" i, F" N! y/ X: [5 s- E
{
' a7 z0 E! t% q7 J& b5 H6 X    static uint16_t hid_report[4]={0,0,0,0};
6 [; ]8 r5 u% q4 |    static char (*hid_keymap)[8]=hid_keymap_dvorak;
; B* @$ r" p0 }0 S. `6 \) ~" @
0 L6 m+ ^6 f9 t3 C! y

  h& T* h4 e7 Y! Q    unsigned char key=hid_keymap[row][col];' y8 K+ z" i( O; G
    unsigned char *report =(unsigned char *)hid_report;
! M/ m/ C) |! D  T$ w    char i;2 O* k% D0 B, d/ R: W- ^+ u- I0 [
9 U! l) l3 _% I( a/ h2 w

/ H0 _, W& F) W" n    if(key==HK_MODE)
2 a5 l+ A  {. z& C4 E& V    {; A" M" t2 Y' l; v( ]6 ^% @! g# f9 \
        if(!onoff)
; H; `; \8 }" M8 f! j        {( k' R% c7 ?6 j( Z; e- R( O# G
            if(hid_keymap==hid_keymap_dvorak). @+ V: {/ I) D* b2 e" W
            {
# _% b3 B3 s6 v                hid_keymap=hid_keymap_qwerty;4 v7 _8 b. H/ Q# T- \1 M
                GPIOB->BSRR = (1<<2);
: P+ K3 ]3 M. P            }
' d& y, e# b6 r& K            else
4 K) y: G- w/ K" h/ y- N            {9 p! J  n4 G0 v) N# m- ~  M
                hid_keymap=hid_keymap_dvorak;: \$ D0 _$ g- n% O
                GPIOB->BRR = (1<<2);; d6 Y4 X& K6 e
            }) e# _# @' d+ s
        }- Q" x4 P6 T2 S" @. D
        return;3 ]; [5 K6 _: D2 S% L
    }) ?6 d9 s$ i! E/ e' |% s  j6 I* S

2 e4 b6 u) D' Z7 y* s
8 L; ]+ X$ j8 H1 V" Z6 D' C# v: U9 ~# i
    if(key>=0x80)   // Alt, Ctrl, Shift
! W2 e- x1 E$ a( S! j" q$ b    {6 `8 X+ _  P9 I" u
        uint8_t bitset = 1<<(key&7);& D8 `- l+ W6 A  l9 H4 t( ]9 Y
        if(onoff)   // non-zero is key up4 `* c7 p9 R% H+ c5 h* q
            report[0] &= (~bitset);- {9 K% a  E6 g2 y! l6 h: u* N, `% F
        else
" R% k+ }  Q% k: j/ f: T+ k$ t            report[0] |= bitset;
+ Z+ _2 T% v  c    }
9 _$ x/ l  P  J; O: e1 D    else/ `3 m# X& ~# Y$ g
    {
& d8 |8 a6 K% T! J3 T        if(onoff)   // non-zero is key up
+ R! ?- @2 I0 I* f" b+ j9 U4 ~$ {" l        {( s1 a. ]6 Q) A; m" g5 L; N
            for(i=2;i<8;i++)( G7 R+ [0 g# R  Q' [' P
            {: x: Y7 _% x& q8 s% z
                if(report==key)
( T7 A, o' ~) d! L5 K                {
+ o# |1 R- U9 S1 q8 d# \% x0 j8 P                    report=0;+ m+ V. z! Y( ]
                    break;7 q+ T3 A/ v: O3 P: a- ]
                }
% o! j, I$ r5 \% t            }
& }) _: `! [' l5 G6 c# M7 T+ Y        }, c3 n9 U) H5 F  E0 u
        else6 w( O  M( Y2 N: H; V; ^- h& Q
        {9 V! k, G- m7 i( m) [  I
            for(i=2;i<8;i++)( x! D0 _4 n* R% q" G$ ^8 z# c
            {
0 ^0 o! v$ N! l* D6 N+ [5 }                if(report==key): U+ ^( n- _9 j
                    break;
9 }( j# ?0 S% E0 `- w                if(report==0)
9 U6 [* Z. B* G                {
; [+ o2 b0 E: l8 [+ U                    report=key;' ?& q& A2 c9 n! f
                    break;
- u0 q3 s6 v9 o2 F' F                }
& S1 I" G1 O; P+ ^. K" m1 _            }- P( I9 B5 M: B7 p8 j4 I
        }
* m8 C( e$ x) l3 v# x6 K    }
( E/ h- h8 k3 G  {    for(i=0;i<4;i++)
: S" W9 M* B5 S( o( F& }+ o, ^        USB_PMA[192+i]=hid_report;
  h3 g6 n. ~/ {0 N7 @! U, q, @* K    USB_PMA[5]=8;   //COUNT1_TX  h9 \, ]- j8 H* N, }
    if(ep1_wait==0)
. u, w( r5 n, J$ M" B    {
4 S' i+ H; H+ B4 D        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;
' ~" w( C* g7 X5 A2 x        ep1_wait=1;
9 m/ u8 y/ {) r5 n2 M- b5 P    }
2 Y0 V1 r: [# f+ g}! a9 @% z9 M# t4 F
. `' o7 D4 K- U1 j; R  y) P# c& T
0 I1 c2 p5 r) G6 A) y; v
完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。8 s2 @  [$ T8 J; s6 v
keyboard.zip (8.7 KB, 下载次数: 6617) . y- n6 O* w1 E; N  a5 Z4 L6 r. u

% L, o4 c! f8 W# R  s3 [" N
2 D+ `$ I6 H6 b4 ]' E! q
% J6 ^' D' i/ Q0 G" \  K/ R4 N9 p6 w: ?, f
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:08:09 | 显示全部楼层
在电子论坛上看到的,这对于改造键盘的小伙伴来说,提供了很大的帮助,步骤很详细,拆解换主控,设计pcb板,编写固件,看的是很过瘾。) J' z! R4 E% v$ \$ o8 v
回复

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘
5 C* p0 I  ^; Q- C  f/ ^不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48
- c- X6 B0 o* j8 X刚开始我以为要把打字机改造成电脑键盘
+ ?9 k! E% W" u不过楼主也很厉害!
3 P* C! l* Z2 @0 ?
哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
' M+ Y. ]3 n5 T. d
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

积分
2
发表于 2019-7-30 15:50:27 | 显示全部楼层
这个看着真硬核
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|MINIWARE产品技术交流 迷你工具-智能烙铁-加热平台-示波器-体感电动螺丝刀-数字电源-智能镊子 ( 粤ICP备07030012号-1 )

GMT+8, 2026-7-5 17:22 , Processed in 0.185245 second(s), 28 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表