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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

积分
288
发表于 2016-9-21 20:34:19 | 显示全部楼层 |阅读模式
本帖最后由 miaozhuang 于 2016-9-21 20:35 编辑 6 _7 N, ?  N# z0 l% }5 ?- T

8 `  c( T; t$ p! khttp://bbs.eeworld.com.cn/forum. ... digest%26digest%3D15 t# Y3 O) y6 m, E4 z
这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。
+ a7 B! z5 R$ {7 @' V7 k- z9 u
9 X/ P( J7 Y! R  f  p在看这个帖子的诸位一定都在用计算机键盘吧。键盘上的数字键是1到9从左至右排列,或者是右小键盘区那样三个一排有序排列,反正规律很明显。但是字母键却不是A,B,C...到Z这么按字母序有规律地排下来的。我刚接触电脑(其实还是学习机)的时候,没在意这个问题,觉得是要盲打嘛,反正对两手的手指头来说,按字母序排列并没有什么好处。于是用多了这些排列也就记住了,从来不管它为什么要这样。其实,PC的键盘键位排布上是延用了打字机的键盘,这是设备演变过程中很自然的一个延续。打字机的历史就要早很多了,我没有亲见过打字机长什么样,而且,咱们汉字是铅字打上去的,和英文打字机方式完全不同。

8 S! g9 ~! O0 r# ]% B

% t  b9 |( l1 p# Q 235140i3a36qivqzuvmt5q.jpg.thumb.jpg
+ l( x% `) m. J8 O0 v5 l3 D1 F. R" B
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。/ B: Z' ~/ \1 {8 L; E
001734klbyoluenuwz4h4b.png.thumb.jpg 2 ^1 b. p4 ~* ^8 |+ ]- G) N- a
为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:+ s1 [4 [* S" ~, R
003625r2agx2f5v922cf2f.png.thumb.jpg
6 p# J2 b" {/ ^# ^其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.+ B8 z7 D" v( q" t! Z. R0 @+ i
Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:
1 y% v7 ~' Z% H2 L
005836yvs0wvovwsssgd3o.png.thumb.jpg
; C, N, l; D. }( _) _  jDvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。# `# V) W1 ]' w( e

0 l* w. P' ~8 Q* U, ?9 P0 v我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。
' F" Z( I, L" Y
1 F! `& ]* c) v# I( g到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。9 ^0 F2 a$ U$ [. \

+ r# R3 _6 v! J, {. r机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:

( |9 {3 n2 P" t  Z' Q( Z 123206vx24rfxfp2dfrpxg.jpg.thumb.jpg
+ g- J! s$ ^8 u9 Z  P2 [- b/ H----------------------------------------------------  分割线 ----------------------------------------------------------
2 j7 ]# B6 N3 C% c
9 ?2 f  N3 j9 H7 u& ~2 o: _/ T* y先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

" ?0 Y- _, a# P  [5 ^( W 020011osionbunl4ui44vi.jpg.thumb.jpg
" U' v/ W7 u& `3 d; `* b5 q) r( {轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。
! y6 N# q! K1 |; }9 i 020017j8ycmnv7788bqv52.jpg.thumb.jpg ( I5 ]8 ^! @. F
特写,80C49  e, P  j" H/ y
021040oujzuvtut6iujtvz.jpg.thumb.jpg . r' t3 Z+ U! a. w4 Y: x$ f) ?
LED部分,使用了一片D触发器锁存指示灯状态.; `* s5 i) ^& h/ ~
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
- u" J" U+ d4 K" E暴力破坏,将80C49拆掉) L1 F4 c* ]4 J8 B! o
021113e48qq98vyohvhzzh.jpg.thumb.jpg
/ a: E3 H6 B4 L. A' i# p4 U拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。
9 [$ U) \" Z/ V8 D$ G 021125nc9az6dj33rlds2r.jpg.thumb.jpg
$ B9 B7 |8 W1 v+ T
焊好元件后的板子,准备替换80C49
& b* F3 O/ Y5 f. |4 O+ U, j 021146zmmpmh4pav4o7g6a.jpg.thumb.jpg
( m4 Z6 B2 f1 z4 X! p用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。/ Z) }- D( N; B
021104shifhnrqbr3o5nlo.jpg.thumb.jpg ) p' H! S" T; }8 E
这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.; l' i( |# w) n1 g# a) w8 I4 H8 f
022003ym1p9u4ug40280uu.jpg.thumb.jpg ; s* _7 s: S* W7 k: G+ L
开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。
7 X2 f+ t  }7 x0 k+ F0 W 023313kt141q9qajtol7ma.jpg.thumb.jpg ! J! V% H6 \0 q( x2 C/ d& E& c/ X
我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。
% D% C# x0 u; t6 U* p$ o# h# w 023322nt7l5xb3ltttkltt.jpg.thumb.jpg $ _) i& K9 d$ b
主键区键帽就位
: F) D9 I: j! G, q' L, i) ` 023331hin88e8wkrwzwikx.jpg.thumb.jpg
8 y- Z4 A% ?9 j- W1 d# q3 P编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。
6 B/ W! D; F! @5 N& y' J) v: ~ 023336wjzlgopugg1jyy79.jpg.thumb.jpg
. k2 V. w$ m1 S  g* C- ?* R5 @最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
6 S2 I$ \7 Y2 D6 `- |. p" e 023341sffu4j3g2323h6fl.jpg.thumb.jpg
0 K: f( Q0 ~! T( J2 H
6 d/ }, s" H0 b- i
----------------------------------------------------- 分割线 --------------------------------------------------9 u9 K' c4 F$ ~# U

) y& c4 ?2 F* a/ c7 `5 @
! K+ L7 a% h4 a' Y5 l
9 Z$ K4 V) F6 m" y! g

, g# k) ~1 a1 l2 e" R1 p' @$ r3 m0 I) Z( _) F9 m* V

! @+ _! Y+ W  d! e. o! j; E. j4 N; n4 g, [. B
9 B6 N2 e3 a! D8 U9 A# w2 v& y2 I
; m7 w, S; R! D4 u- B# t4 e0 u

; [. a7 v4 X7 a7 c7 Z0 U9 p4 i# ?7 c  y. `/ d3 Q9 M
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。5 T" M8 v- c) Z- P' A1 Q
80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:
3 {9 x' D4 R) @ 104025nzibm2rmiomhyirm.png.thumb.jpg : h/ K8 L* X# I% r' C7 M4 N7 Q

1 g+ w! j5 _# A9 ^其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)
+ q; I) p# Q, m 105004zkrez5houvkkznko.jpg.thumb.jpg
0 }7 ~" ~3 [$ y5 \" h: G% ~

9 r0 Y/ h# t# U# C# {扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。) q/ Z9 X3 ^, X: n# Z
* P% n; n. W7 Z( }
这是我设计的电路图:* o* u7 z- B! u' S# v0 E
110344ej2z2oo2rflo7oe7.png.thumb.jpg % E& f$ Q" O8 k0 D5 z* A1 |2 a
: q  g/ L2 Z4 ^& {0 ~7 T/ W
PCB Layout:
# Z7 Z+ n1 X& R6 U) H2 i7 [* ]0 f 110847jjbjvt34vwt3v5bb.png.thumb.jpg 7 }  F' a* ~' l5 x1 u9 P, {

) K; ~  J3 j. G. c4 G* U5 H& n不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O. ; F# E% y0 }+ G. K/ [, Z' |* ]2 S' s

& w; y5 o# h% B1 I$ f
' X& j7 E( P8 k8 ]6 T6 ?
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑
9 k" m( P* u2 f) K& }3 ]/ D. [6 t! B+ }% C
软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。9 r; A" b+ X. Y6 M
7 d: I+ ]: }3 |& {1 _  w% n
总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:
+ T6 Q! U0 U# ~! L. ~  N8 n 113818pmrfsb6z0byt6t06.png.thumb.jpg
9 }+ O4 A8 l) `  g5 ^: p

, P$ i5 O$ j) p# O# x+ J其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)
0 S, ?4 ?- [- `8 ]- V. \; A* M/ A# ^8 |  F
USB的中断ISR,bare metal哦" y7 J. X* o( e2 m( M3 |  y2 _( t

; x; r# t* G: n- x2 g$ P! c; o4 ?void USB_IRQHandler(void)
% k/ {+ u& z! [, k4 M8 T8 n4 \{
: R/ ~: Z+ C4 Q" W4 D$ p    if(USB->ISTR & USB_ISTR_CTR)
; }7 Y- k. f. P* b    {& I" E% g% @$ B, ?  \
        if((USB->ISTR & 0x0f)==0)   // EP_ID==0) V4 m) U* X  U2 o' E7 g2 N
        {; T: b; h% s/ k
            switch(ep0_state)- j% ]; Z: G, h
            {8 F% H5 Q! _* c% [5 U
                case 0: ep0_state |= 0x80;
) @8 f3 t; i' j% Y: j. M                        break;/ W* W2 _6 V. W( U; u( q$ N1 J
                case 1: if(USB->EP0R & USB_EP_CTR_TX)
" X2 [+ P* ~3 z: C# C' C                        {
* v% L0 {2 ^9 I' k5 C6 i2 d8 }                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;4 A/ P" s4 l  m( o! I
                            ep0_state=3;' a, }4 A+ C" U# w! `
                            return;
5 f! |4 ?1 T. u                        }  r3 h) m+ \- B
                        else! u/ A- Y9 G! I. W( P" P
                            ep0_state=0;' C, u( b- a/ Q# s7 a; Y1 W
                        break;
3 W' E. J! W: }: C                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet
+ s! O! O1 z6 c' n0 n                            ep0_state |= 0x80;
& \+ t. c' Z4 o/ [6 e4 F                        else
( L& W+ x  `2 w$ s                            ep0_state=0;. r% b3 L  F4 }  }' V
                        break;# w3 s  _2 T, c  ?# f+ t  x
                case 3: ep0_state=0;
) P  k3 x  @, e8 K7 Z                        break;9 U$ P$ N" x3 Z) @
                case 4: ep0_state=0;2 d; k% M6 f) ^2 O
                        break;; ~% ]4 \% i2 ~/ u5 x
                default:ep0_state=0;
, S& N+ @: @/ k8 t  \, [- x8 L! F                        break;& P3 t2 O" d8 H; q
            }  P# {+ Z0 _5 Z* [# ^) k; M& j
            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag( ^0 \; a0 v8 ]
            return;
$ }6 f$ p  \6 _! e* F  r; D        }
- m, S2 q0 z# ~        else    // EP_ID can be 1' J" |$ \- ~. c# Q
        {' I- x8 N5 @+ F) O
            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag
+ H7 {' |  M9 W7 d, O9 n: h& p            ep1_wait=0;
; j3 Q% Y4 [% K            return;, B1 a' N3 o, c" C, E$ H
        }" K' d8 @7 d: l$ c6 n
    }; @2 p+ U8 n7 Z  h4 s
    if(USB->ISTR & USB_ISTR_PMAOVR)3 z' p# ]+ H, A  x
    {( L8 G1 |/ C" Y
        USB->ISTR = ~USB_ISTR_PMAOVR;
  C! {3 k! k- r, \& I/ i0 \# N! g    }
2 `, K+ i* e& ~- V3 f! y. _    if(USB->ISTR & USB_ISTR_ERR). R# `+ h0 W) [: u7 Y  K0 T
    {( {( t( e* ^- a. X) U
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear
" }# m3 @7 T( g- ?# x' D8 |    }
& D' W/ v: z7 t& n! Z    if(USB->ISTR & USB_ISTR_WKUP)
$ @+ Z  N  b5 N$ x    {
3 ^  M9 F5 `' M, E        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
. y# l% Z6 f9 d& j9 ~        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear
4 N# X' S0 F6 m# q- L5 G) \$ J    }
" g5 }! m; Q7 Q( @! v$ l; W, \    if(USB->ISTR & USB_ISTR_SUSP)
3 _+ D+ Q% j4 m1 |6 m' a6 V" h5 E    {
' I5 |/ l( h' Y. ~  @        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend
0 s& q8 G& F2 R5 s1 e9 ~' a        USB->CNTR |= USB_CNTR_LPMODE;   // low power
& R2 I) H) D5 F' w# e        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear
  ~# o. P; J* ]% S3 U6 B2 y0 U    }
$ Y; J- I7 Z  y/ j4 Z& |    if(USB->ISTR & USB_ISTR_RESET)# ~. A! w3 C5 a7 N  D0 X3 [8 H2 b
    {
. c/ @0 X8 l+ U# U0 p5 _9 X2 t4 U; n3 r        USB->BTABLE = 0;    // buffer table at bottom of PMA1 f; s; F- r3 U3 M0 }
        USB_PMA[0]=128; //ADDR0_TX
5 }: t4 }% P( v        USB_PMA[1]=0;   //COUNT0_TX; N  Y. d0 D$ `2 m3 c# F) C' M! d
        USB_PMA[2]=256; //ADDR0_RX4 q: a- J6 h. G
        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes
( [  x* H% D2 ]2 m7 U        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;
3 b9 J3 N! K5 \! x) D        ep0_state=0;0 @3 q$ w: ]" H2 h! @2 q3 s
        USB_PMA[4]=384; //ADDR1_TX
9 F' E0 m/ D. E( ?; W) V        USB_PMA[5]=0;   //COUNT1_TX
# U) G$ b" h- c) A* {" i& j        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type
5 t; m7 u- W; Z, I- f; ]        ep1_wait=0;" }( x; X% P8 F$ y
        USB->DADDR = USB_DADDR_EF;      // enable function! p. o, k$ z1 ]1 x/ |
        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear  X  \6 f+ m2 K* k" t$ _& t
    }
4 L& f" y5 Q6 F% z    if(USB->ISTR & USB_ISTR_SOF); }, f& k3 K  n% K
    {* e( H+ E  B) @, i
        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear0 h1 D( e  G) }  P9 J# G2 Y' Y
    }  X% l& V! `8 `' \5 D7 _% Z* w" n% o
}
9 A5 c7 W: f: o! t, H! H6 x" @6 G  M' g. ]; r% }
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。, o. k$ y! k' Z3 [

8 B8 `( m) @4 @2 g主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。4 K7 z4 D% M* v4 B6 E5 C
  X; q3 E5 \+ p9 V: U6 G" F8 j/ E
    while(1)
' H/ g( |6 e6 }6 l5 C) k    {
" u* R& i, o5 w+ j- D        static char row=0;
, A  D3 x* I! W9 \4 o  I% [2 v        __WFI();
. M( ~- s! T3 T& [9 v/ _: A: B  `$ A  a        if(ep0_state & 0x80)    // request data processing1 T* U" d0 O$ A' t0 j3 @
        {
' L. @6 }8 t( F9 j) h            if(ep0_state==0x80) // SETUP phase0 E3 P+ H4 H6 l% ]$ K3 i
            {
, d, V7 a; S, Z" V, W) r/ S1 j; m1 x                if(!setup_packet_service()); O: v4 y& q9 R( ^: @# Z
                {4 t. S6 z5 a* J  |  |1 Y0 e1 k. G
                    ep0_state=0;
, P* G4 q& @8 v- ]4 D3 @                    // not supported
# C- h. e: v9 F) E( c7 Q0 l4 m                }9 _5 {* ^5 e$ U5 d! g8 V
                // ep0_state should be set to 1 or 2, if processed1 C: ?% Z# h7 @7 c
            }9 w2 q9 J5 H  _) L% j
            else    // OUT phase
) K( w, M3 y) o% m, _+ q+ S            {; y' i2 B. X, s" B) {9 v, H- `
                // process data
- f) L# f4 x3 @. `0 x, g8 M7 u                show_LED(*(uint8_t *)(USB_PMA+128));
; e2 U7 b, }3 n; Y  r2 b  p; f                ep0_state=4;7 G1 B  u- B7 u' m) t& Q
                USB_PMA[1]=0;       // Zero length DATA0
) L  f6 p$ ~# N                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
0 Q/ J6 i) o8 x( r2 p, ]& T            }
( ^- T$ @5 V3 ^        }! _+ }2 \( k( e2 \/ x
        else
" J7 Q& d/ N. B/ c9 a        {
  A1 k( y0 x* k) H% y7 H            if(usb_address && ep0_state==0)
  X0 }0 V, g7 S& z9 o2 l" E            {! N2 ~0 `' l2 W; O) [1 E
                USB->DADDR = USB_DADDR_EF|usb_address;
+ Y7 x* X0 |5 {; v2 @! X+ r                usb_address=0;
# w; @4 j- h* R& {: |/ b' M9 i            }
8 @: k% n: Q% Y+ l" g3 R; [        }
6 a2 e* b. C! Z* r! [+ ]2 P        if(row!=scan_row)   // new scan line
0 e# E1 ]; T/ u1 M        {
" m% t+ I; {# v9 t9 u3 u            if(key_state[row]!=prev_key_state[row])
9 {9 l$ ^# M1 X* n; [            {8 [6 t' h; p. L5 W! m3 u6 B* G0 k
                uint8_t test=0x80;
% f% A9 a* a4 L                uint8_t diff=key_state[row]^prev_key_state[row];
; @3 R5 K" Q* t4 [) u                for(i=0;i<8;i++)( M/ O7 @1 o2 i( A& v$ {1 u% l
                {3 Y! A5 R, T# L+ b
                    if(diff & test)
" _2 C6 x! {% s. H$ v7 R( l                        update_key_matrix(row,i,key_state[row]&test);
, N7 l5 o/ f) t                    test>>=1;
1 d$ @. D/ I7 W3 ^4 [                }
' G4 e& y+ J3 w4 q: F3 l            }
* X2 q( R" N* h# I$ [9 W            row=scan_row;
6 e7 B! S1 ?+ ?$ V        }4 I! j9 }$ P0 J' A) {
    }6 @2 Y/ `! U9 c1 i2 W: {0 n
1 n$ w6 ?) k( b: `7 A4 i

$ f: D- [3 e3 O5 m9 ?EP0的控制传输,把用到的请求处理一下
$ i9 m$ O6 d5 i  Jchar setup_packet_service(void)& ]8 J3 m8 ]5 \. c: B4 F
{( x' o1 _$ j1 K. O3 R
    if(ep0_std_req->bmRequestType & 0x20)   // class-specific# d  h/ q  f+ l; f
    {& m' }( n" k- w' t$ x) {' G
        switch(ep0_std_req->bRequest)
2 j% V0 y( I: F: m: `( x+ v3 m        {- j  a$ e+ k5 k% a
            case REQ_GET_REPORT: break;
3 ?5 O2 e0 }  z( X' p4 @8 P2 p* C            case REQ_GET_IDLE:- v" g) B7 D- C3 \4 ~
                USB_PMA[64]=0xfa;   // return 1 byte+ [' b' K" D9 b$ _  F
                USB_PMA[1]=1;! r! E* x5 b3 q* P% ~
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
5 F" b2 t# q- K7 L  J0 _                ep0_state=1;
$ x1 d% w2 q6 y1 [& g                return 1;) a; w/ |* Z! x
                break;
4 G  \0 {  I% [# S3 r            case REQ_SET_REPORT:
7 D# B+ W( u( \0 I% n5 A                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;1 b  c# k8 ~$ Q! ^* X
                ep0_state=2;
: {. ~5 R7 @  L5 [, R( P                return 1;) u" e5 {0 g. a: B
                break;; M8 Z( \/ O  L
            case REQ_SET_IDLE:% w+ g* V; S& n: n
                USB_PMA[1]=0;   // Zero DATA( f: H# d. R7 @" a- I! W
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
: `8 l+ l' u- q/ g2 ?) ]                ep0_state=4;
" E, {" G$ M' Q( J" w                return 1;; L. K: m4 n# {# r
                break;, f$ y) M% _( N
        }
( N: ?! X: ]/ P        return 0;! S& D0 ?( V+ k. K: q2 y9 W7 `. S
    }
$ M5 n; W+ w3 Q" z    else    // standard
" `. `' x# h1 E) X9 [! _( z    {  R4 I! ?" e9 L5 \
        switch(ep0_std_req->bRequest)" f. o: [. z# Q( _8 F6 S7 E
        {' X; d% C6 [- ]% B7 |4 F: n
            case REQ_GET_DESCRIPTOR:
3 N& d, [% o# T6 Q2 `( P5 v                return descriptor_service();
+ j) Z" B, u* ^+ w4 l                break;
6 q4 ?* |9 n8 c0 r            case REQ_SET_ADDRESS:
. @& e& F: Q3 }0 ]1 o+ c$ l% }& E' J                if(ep0_std_req->bmRequestType!=0x00)! p  l0 r8 G7 @3 N* A
                    return 0;
+ y8 |/ ?, |+ [6 p                usb_address=ep0_std_req->wValue;7 n5 G! Y  g& J" C" U0 ?# k. k
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
1 \9 b/ Y/ Y3 u  K                USB_PMA[1]=0;       // Zero length DATA0& {& l8 F/ e. X' ?/ l( m
                ep0_state=4;    // No Data phase0 Q$ J7 K1 ?/ l% Y% B) c! Q
                return 1;6 y: d5 I+ p$ @! z' c
            case REQ_SET_CONFIGURATION:$ O+ f! d+ d# A# S/ D$ M
                if(ep0_std_req->bmRequestType!=0x00)
+ p: L/ ^( f* W, u  q9 t9 ^                    return 0;7 y8 X) E: V) F3 W3 T& i, E
                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;; w. P' `5 |5 T! M9 b" I2 t: B
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
0 F/ R! |# v$ p: k/ `                USB_PMA[1]=0;   // Zero DATA
2 B9 b1 w; L, N4 X$ x                ep0_state=4;    // No DATA phase
3 ?1 b% G  F$ z' B5 J                return 1;5 j* d4 x: [6 \* D
            default: return 0;
! q7 t5 B  j; U9 H        }$ }- R8 n5 M9 t  _
    }$ @, [0 Z1 l' D' R8 o" S- X$ j
}8 H- N: U( [: N

3 \1 E3 X' i3 v0 S2 ~6 T$ S0 [* `+ v" o- R* M- R

* L) b0 P7 y4 |) V1 F, ^% d, [$ \; x  H
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的
2 H, B* d4 U0 Q5 h1 a0 Qchar descriptor_service(void)' |! M6 B. m5 M  z' F; K" P/ ^
{$ A* ^. I5 Z- o
    switch((ep0_std_req->wValue)>>8)
; q( I, d6 h0 k2 g: v3 E/ O7 k0 v    {
5 J) }. ^; ~  K        case DESC_TYPE_DEVICE:  j, d  @3 C5 G1 B
            return ep0_preparedata(&DevDesc, sizeof(DevDesc));
  |( D- L+ P7 ^& y            break;2 ?& T9 T3 O- t. C( e
        case DESC_TYPE_CONFIG:, u# [$ ^/ N+ P
            if(sizeof(ConfigDescData)>ep0_std_req->wLength)
' e' g+ Y, h& n/ d                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);, S% |' H9 Y" t, C6 V# O
            else
& M& M9 b+ ]/ {  t* p0 T3 ^                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));
; ~# m8 L5 a- y' p7 U1 @% I9 B( D9 q            break;" @" A5 R2 M, V
        case DESC_TYPE_STRING:5 H! J. m& [6 w( X
            switch(ep0_std_req->wValue &0xff)$ G6 u6 n) I. ]% c, w# f) S
            {
* f) ^& B+ e& A; y; j                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
* V) L( U( M; J* G" T5 l                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));0 b' b, g8 S& b. o
                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));' L+ B. e7 K$ {, @; G7 d7 y2 J( Q
                default: return 0;
8 q, W$ K0 G- a; S/ a5 ?( n0 A            }
* \9 e+ ]1 ^" J3 d3 [            break;& l9 G$ z* @5 r2 n: v% a5 O2 l
        case REPORT_DESC_TYPE:( V/ M* D' x* g. A$ T8 B9 Q
            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));' L8 F/ G$ c* j% V2 g0 p# k3 P
        default:
9 @! P; X4 a9 M0 o% H8 R            return 0;3 q) Z1 M& x, r( `
    }
& C+ b$ V9 @, X& E' Z7 ]7 h+ W}/ b0 ~8 S5 ^* |' i- O! C# s8 f* ^
下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.9 P4 B, A1 u* n
void TIM6_DAC_IRQHandler(void)
- Q; P2 h+ U7 A& ?( [{
: w( n. }; z! D( h4 O* J    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);
/ i4 I) o) `2 W! L4 g' b  b  R" b" N- G, U1 ^
. n+ M9 p2 }& s! h9 r5 N
    TIM6->SR &= ~TIM_SR_UIF;
# e& `: f8 q( w    prev_key_state[scan_row]=key_state[scan_row];/ \" M. q6 s8 F! v5 A
    key_state[scan_row]= *PA_IDR;   // update key states
7 e$ }& Q9 ^0 p    switch(scan_row)2 C) _6 p6 f5 s
    {
: h* Z2 ]8 [1 d6 a        case 13: // next row PB14
( K/ D2 E2 I' ~7 M; C( B, s; [                GPIOC->MODER = GPIOC_DEFAULT;4 x7 y1 G0 O) S. V9 S: V6 n
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;
" @, x( V  [& ]  R2 E) I& V: b+ }                break;! [0 ^+ H  ]9 n! Z/ p6 s
        case  0: // next row PB15
; O& n* U3 a0 m* W- |5 D# C                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
- G. c. E4 R/ m& O8 n+ \                break;6 [8 Q, x7 b) q% g0 u" c
        case  1: // next row PB39 ?$ K6 f* @  B
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;
" H9 u' Y, j4 p7 G1 G+ B0 D0 s                break;& @6 E+ g1 ^' Y) j- {$ b4 s
        case  2: // next row PB45 u4 d) V$ p' [( ~
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
5 R6 S7 {5 A5 t+ j2 z" b                break;, ?/ V) e! {. Z
        case  3: // next row PB5
- U  a; H6 }  B; n: W                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;
+ q7 W/ U1 I* Z7 e0 |) V% N                break;
6 R1 m' y1 N* M9 `. `1 `        case  4: // next row PB6
9 S: S# o! X7 v* h; |                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;6 e8 w9 F/ q/ D, B6 m3 j
                break;* t0 n" n: T) ?5 f, X7 R
        case  5: // next row PB7
' E/ H- t9 ?$ A+ G2 n                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;/ D7 V0 k2 n" O
                break;
3 ^$ ~2 ?1 B5 o5 M6 E4 x# `        case  6: // next row PB8
6 Y0 F1 b1 s: t& `( A4 F                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;
3 w$ R/ Q/ V4 n" h  [                break;
6 V* D5 W* `7 B4 V. |) K        case  7: // next row PB9
4 z4 @% h, V! X- C5 ]                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;
* Q. e7 U! U! J2 S( W: Y% s                break;
* {3 @8 P; y& Z. b" z0 u7 [4 K& b        case  8: // next row PA8# s. u1 ]7 \0 G4 d. m: c5 ^
                GPIOB->MODER = GPIOB_DEFAULT;; B! F) e1 L" F/ [% n% d5 X
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
1 H& p. m0 y, I                break;5 l' z5 l7 S9 n2 I; l
        case  9: // next row PA9
- V; O1 U! L5 e4 @                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;
- W: |" J1 x; n/ `! Q$ ]0 g0 n0 w  A                break;
  W  n2 @) N6 n0 _: S        case 10: // next row PA10
2 g9 x# e( Z/ g: V3 I                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
5 Q& _: s. |1 Z6 G- k2 ~                break;# [+ B# i9 x' T7 J) c, C3 c. B5 C
        case 11: // next row PA15% w# B6 ]9 Q2 a: y, F; y
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;+ w7 [3 Y- x; H3 |, O
                break;3 W5 l5 Z; P; r4 k
        case 12: // next row PC13; T* z) L( b) C
                GPIOA->MODER = GPIOA_DEFAULT;! q; s# }; Y8 ~) V
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
) ?" C& L% C" G% \0 {                break;3 Z' |& M" E- k, H
    }5 ]4 q2 ]2 `4 n8 z! Y: s
    if(scan_row<13)
  c9 J; ]% t2 a# Z6 c! ~, `/ h) C8 o        scan_row++;! f$ h2 h' N' E% K$ S
    else4 P6 S' F* Q  a  j6 M. E. L
        scan_row=0;
7 [( Z" I( o/ c* }  k+ \}: S$ s" O" V1 v) c: c+ G! S' W
) ^. D, e9 s3 p
1 G- Y9 G% d/ j& R8 G+ ~+ w
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑 % J) k, y8 n6 g
# I1 ]; |# [1 }
扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。; i" V7 e" @8 i% c8 l6 e" x
$ ?9 X4 w8 W& ?- ]' g, R9 q
要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:

$ X% Q# ~( d8 h4 M. G  }; O* a9 b+ }

! t/ P4 M; j* Rconst char hid_keymap_qwerty[14][8]={
# B# E$ R$ i; ]7 S3 R& E4 [    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},, _# Z5 D2 r7 A2 L. q
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
! o1 ?- V5 |" l; f3 l    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},
  b( S9 z# O# ^# f  \$ v6 u" B2 _    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},+ Y& d& E# z# ?! o
    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},$ }* A$ `1 _" B# B" U+ A
    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},; B* f9 g0 H# m! l6 r
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
2 N9 H0 O5 S6 s1 L1 w    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},
( D( J$ b; `, A/ y6 U* \    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
- H: u5 D5 D  @/ r, E    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},) l. J3 V- |$ S* L* B: q% B
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
' e* y+ S6 M1 f    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},; H6 a* O& h0 z0 g. {7 ~
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
/ Y& g( J  b: M0 Z; x; ^; _    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
( L9 c, x* ~  E& N- O};
$ b4 ^: f& u  T4 m: e, ^7 k
. }6 H  }" K: ~% t$ J( ^* Y6 x

" F3 u& B# O' D$ h8 W2 Aconst char hid_keymap_dvorak[14][8]={9 T" }& H9 Q! p
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
) m, H0 H* Z6 E4 }& L- l$ i    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
% r* a( H: S( S* x4 R    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},. R* u5 E9 A* c' J+ Z: L
    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
9 ^! y& v5 n5 {, x8 y. s    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},
; G% B, T7 u- _. ?4 M5 Q    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
# R9 }4 X- _! Z5 b3 _    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},
; k3 Q' A1 }2 G. N% y    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},
& b3 g& _8 O  ]. f- J, P5 b# Q    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},/ W* P5 p# A+ o
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
9 a7 i2 M& n0 {6 V+ I  E    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
+ P7 K5 S, |* K$ g, G    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},3 V1 m$ S+ K; r, W( N
    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},, a6 q! D* F4 n
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
5 ]- V4 o9 e$ u3 H, _/ ?3 n};6 s5 G  |, Z. `# a6 N! S

& V! u; X/ X+ \  G) O6 o$ }! I/ s: [; {2 J5 D7 o- e0 u0 `4 M8 Y
上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
% k& G3 r  G3 g2 n( U) v7 Y9 O& K6 G& c) L
HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:
1 r9 N& n: y' Z

6 `( N) V) R& z( Y) tvoid update_key_matrix(char row, char col, char onoff)
* M2 v) l4 y. o4 M3 u. R9 J{
- Y2 K' b/ Y/ L& v. i" U8 R, u    static uint16_t hid_report[4]={0,0,0,0};0 `% y4 z. S+ Z) C
    static char (*hid_keymap)[8]=hid_keymap_dvorak;) }( q. o4 e. T5 M% U

8 |+ N" u, a. }0 l; O4 O
0 u- {% E% I$ k+ ?% [; v
    unsigned char key=hid_keymap[row][col];
  Z# W: C/ I/ v) T* X8 M; S9 }& Y    unsigned char *report =(unsigned char *)hid_report;
2 F8 b7 ?% r8 z) u) f    char i;8 u- \4 S' B0 t% I9 W0 P
6 ^) L  x! ]! c  {0 n: j
" e& s0 b. F! K
    if(key==HK_MODE)
0 ^: ?% [, b) L! c5 S/ N9 ?    {2 @2 {: U' {8 V7 f
        if(!onoff)# J* V  w! {  Y! B6 y5 F( u! |1 }
        {
  p% `! r5 r- c            if(hid_keymap==hid_keymap_dvorak)" P' y: b4 g4 j+ _3 P; ^
            {
& o* G# n# N9 w& v) ^2 G                hid_keymap=hid_keymap_qwerty;) ?8 ]: P& C/ P4 R- _1 f
                GPIOB->BSRR = (1<<2);
$ r, L; |* \# ^            }) y  L1 U* ^7 _) L( f' M; e' l+ d
            else# ~4 }1 r% g. L8 U2 T
            {+ I$ V! _/ j; ]
                hid_keymap=hid_keymap_dvorak;
  ?7 t1 |; o8 ^7 e& T3 g7 y% R                GPIOB->BRR = (1<<2);5 V# O: O6 L5 A' ]. T
            }
$ Q. c/ X' o0 y4 o; _* r5 O        }
% ]1 g# d5 p4 i% R4 d0 q/ L4 M        return;
9 F' s6 ~  |) }    }
# {. K+ n5 ^6 l* ]: [5 b1 H) O& M- W7 o3 C2 H0 H
0 Y. n4 E3 V, Z  n2 w0 [3 M
    if(key>=0x80)   // Alt, Ctrl, Shift& b6 Z+ A* \6 R" L% }
    {- r( @; c/ J7 ?' m4 G  _" w* |
        uint8_t bitset = 1<<(key&7);) I# v! z- d- t% J; b8 s3 N
        if(onoff)   // non-zero is key up
, M0 V! {/ X4 K0 n0 K# i0 D# o            report[0] &= (~bitset);1 @  t' O6 X. a
        else) |7 h' r- ~1 v& ]
            report[0] |= bitset;0 ^6 }$ ^$ Q& t! L1 W
    }$ `3 {' i! X" p0 `4 y/ V7 m
    else' g+ P& O! V% K8 q5 y+ T
    {5 O4 v6 U# ]" U
        if(onoff)   // non-zero is key up6 Q+ c4 v) i" E, |2 f
        {* \/ ^0 A6 f( M5 {
            for(i=2;i<8;i++)
1 u5 y0 G& p- x9 O, D            {' S+ U) \( E0 |+ b2 [1 v5 \" l; A
                if(report==key): Z, x8 ]1 l* Z+ H' b
                {7 C' w7 H% Q: I0 ^$ _
                    report=0;/ ]9 s* q0 W0 H! f4 C
                    break;% _' A- G3 \) r# ]* W9 }: |' D
                }- _! H# p8 i9 h7 Y# t
            }
$ i) \$ ^. `3 `0 j        }
* G7 C% s2 W* e0 i        else: @+ Q) H/ c' }" d- ^" U, ~$ M
        {9 q- j* m, r- l5 F5 d: F
            for(i=2;i<8;i++)4 J3 C- f3 o: H# {. G. n
            {5 }+ s' T" F- G' W
                if(report==key)  Q( _" ?* X% F5 K9 |: v* V
                    break;+ q. n# g9 P; N* v
                if(report==0)
) d2 m* p  ?5 Q* V2 p. I                {
8 `# D" T& [2 D2 Y  T                    report=key;6 `- {& a. b) a& F
                    break;8 u' q- l: M& a3 m
                }
2 d( F9 n* i! l9 j7 m, ~            }
  X6 y6 p, z6 j7 _* R; R        }$ A6 b! m: }9 j+ Y' z# P+ j6 c, ~
    }
! S  ]$ w4 W6 h( x8 _    for(i=0;i<4;i++)
; Y2 F' q, Q8 |# A" L4 X6 `        USB_PMA[192+i]=hid_report;& y$ E& J5 {' G& \" b& n6 @& m6 Y, O9 z
    USB_PMA[5]=8;   //COUNT1_TX; S5 p$ Z; s/ R. f$ d8 O
    if(ep1_wait==0)- S  m8 Q$ O5 t# l' l( ~/ u
    {( I8 B. w8 P! Y6 l1 O* c7 \
        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;0 m4 @+ x5 E9 d: _
        ep1_wait=1;) e1 F4 K+ v6 E( f0 M
    }
9 U0 h/ |3 T, Q5 I4 O" Q3 U5 V}
8 A/ G8 o4 [/ F2 g5 Q( X
' e& c$ |3 s+ j4 j" O' S# `) U/ s( I: q, P
完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。, `; t; J* R% W4 i
keyboard.zip (8.7 KB, 下载次数: 6188) 8 V0 Q4 ?; s6 g( U( {: Y
, E% ^# Q7 Z7 K9 y& s) V. H
( o! b( F9 H* q! Z6 t: @' j; O
% L, I. m6 _6 `# \  _: B7 }, d( `

6 Z; D$ H% J* W0 f  O1 r  G
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:08:09 | 显示全部楼层
在电子论坛上看到的,这对于改造键盘的小伙伴来说,提供了很大的帮助,步骤很详细,拆解换主控,设计pcb板,编写固件,看的是很过瘾。
( M7 g1 V& v1 _' E: F, }, F: l; E; f
回复

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘' k' N( R; n% U( R
不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:487 ~, I" U6 w+ K' e  A
刚开始我以为要把打字机改造成电脑键盘4 \1 a0 D" V! {% e1 h/ S6 s
不过楼主也很厉害!
7 [- h$ j0 b$ r4 j9 l8 e- R3 z
哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
4 e0 p' Y) F) g0 u7 Y+ _; P
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-7-1 10:59 , Processed in 0.401275 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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