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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

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

% F$ N% _6 }# l, s; M7 y1 T! S$ f! [3 b
9 s' y3 F! I' J
235140i3a36qivqzuvmt5q.jpg.thumb.jpg & e/ m  u4 f; q, t
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。( {0 W% t8 V& x
001734klbyoluenuwz4h4b.png.thumb.jpg
/ f1 s6 y3 q* @2 @  s为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:( V( K4 |5 _' t9 @
003625r2agx2f5v922cf2f.png.thumb.jpg   b" @' a, j/ ?) G; J4 W
其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.! a1 o2 `0 q/ M
Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:
2 e9 S' C6 A& a6 w
005836yvs0wvovwsssgd3o.png.thumb.jpg
/ \6 o% C. m6 ]. M' [) GDvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。0 @3 C! L1 l. V; W7 K3 k. `" j

- {& K2 b% b- w/ d我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。% h9 N) w+ U1 a; N4 J- O/ R/ @! W
1 T" y/ S; I$ B8 I4 a
到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。' v" w+ ?' Z9 G" m$ Q6 a
/ |7 H; D; a: e# M0 ~# L9 Y
机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:
: `9 V# n; r$ j! o8 H8 ]5 ^
123206vx24rfxfp2dfrpxg.jpg.thumb.jpg
* k1 E$ O  {7 F6 G" x----------------------------------------------------  分割线 ----------------------------------------------------------9 K  p  t) \/ W. C

7 I! @/ k. Z* H. H; `8 [先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。
) G/ z8 r2 S- ]& I, c
020011osionbunl4ui44vi.jpg.thumb.jpg
2 z3 U/ I0 V# R$ z) J6 J$ C, }/ F! m轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。5 C( ~- `, r% D% \  T/ @
020017j8ycmnv7788bqv52.jpg.thumb.jpg
+ v& A1 j, g) Q& J7 M: g特写,80C49
+ M6 w; Q; r0 p$ S6 t! {* ]- E8 g8 h 021040oujzuvtut6iujtvz.jpg.thumb.jpg
5 {3 K( A% n; H$ S+ q/ m; f% MLED部分,使用了一片D触发器锁存指示灯状态.
: t. ~2 p+ T! W" c# E 022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
4 t( T/ }. z0 b$ p( R/ o0 U2 n暴力破坏,将80C49拆掉
3 a! W1 k. w, A* V" T/ _- L3 e 021113e48qq98vyohvhzzh.jpg.thumb.jpg
  o" T& R2 l" O% t/ }  D拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。
/ [1 V, h4 g4 ?4 g$ F; \: x1 } 021125nc9az6dj33rlds2r.jpg.thumb.jpg
0 n5 L7 P0 {1 R  `. z% S+ \% |6 v
焊好元件后的板子,准备替换80C49
1 s6 c# X6 [& V 021146zmmpmh4pav4o7g6a.jpg.thumb.jpg   N) _: [) b/ A- p& U. d+ g
用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。
  B$ O# v5 s* E. k9 E& X 021104shifhnrqbr3o5nlo.jpg.thumb.jpg
+ `# W! v" J, C! z1 |这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.
/ n6 W1 p, |0 n2 Q) t 022003ym1p9u4ug40280uu.jpg.thumb.jpg
: q4 @1 S- [3 }开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。
' X8 ~6 `1 w& o& J; o8 i9 l3 b 023313kt141q9qajtol7ma.jpg.thumb.jpg 0 w4 k/ b: |/ e& c3 M
我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。3 J- n0 D2 U$ V. }; u9 w, q/ [% V
023322nt7l5xb3ltttkltt.jpg.thumb.jpg
( m$ N5 A9 |% X+ r9 @
主键区键帽就位  ?$ y, z+ C9 D, s
023331hin88e8wkrwzwikx.jpg.thumb.jpg
5 E2 |$ b7 s1 Q编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。
7 x# m. O% J7 I8 m4 C 023336wjzlgopugg1jyy79.jpg.thumb.jpg . E* B% B" P1 {) U# Z- U& V6 |
最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。2 L! p9 Z0 \: l+ J" N2 D9 ?, L& ]
023341sffu4j3g2323h6fl.jpg.thumb.jpg + w" z9 y! l/ G7 E3 U* c3 W7 N

5 s0 J0 u* h% R8 ^- b----------------------------------------------------- 分割线 --------------------------------------------------
5 x: d* c+ T$ E4 t5 a" b$ ?8 v6 q
0 g0 ~" L% v4 N' d4 d
6 B! S2 c/ P0 d

! j8 A4 |0 |% s% K+ |+ g* X: n% {0 ~& S& n

$ Y( Y, \0 W5 ]5 k6 J- s

+ ]" w( C1 x& U" S, e) W
% b: f9 Z* Y% U6 T# B7 k& v1 d
4 W, U  h4 X/ R8 p
$ z3 l& S7 p6 d+ }! A
) n! {8 Z6 M" M0 t4 U- {& O4 [5 G
; e( M1 z) h2 H5 D
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。0 ~4 Q8 I3 O8 p  y+ }( f  K) ?
80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:" i/ k* v  l+ ]+ C
104025nzibm2rmiomhyirm.png.thumb.jpg
  J% n! r8 Z. g- c- Q; a! ^# v$ a5 W

9 h- ]( ]8 r" L* R其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)
! O$ k+ g& N! N, ~3 p3 j) d8 a 105004zkrez5houvkkznko.jpg.thumb.jpg . u( u" c  g  I: F( S/ {3 U! C

; @5 d: B6 S3 l" w  L& F扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。0 n7 A1 }: C& P  m. H& Z" F% I
" f5 x7 y- B1 Z. y! v5 v$ c
这是我设计的电路图:% |2 C  O/ U: Y0 }" p: m
110344ej2z2oo2rflo7oe7.png.thumb.jpg : s3 I0 f. R* x" X4 }- U; P

" V9 V# W1 t3 g- HPCB Layout:' n- F- u& s* A! ]9 e! e
110847jjbjvt34vwt3v5bb.png.thumb.jpg - N0 f, {& }  g( d4 g

$ ~8 v& @" S& Q. A1 H/ G2 L不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O.
/ M" {# [) w  F: F& a. n
' ?& U, r+ `- x. l

" Y5 G) p$ x( D; M
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑
7 G5 l1 ~/ @% M5 u9 Y
# R+ [/ a. J' G( X软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。0 y7 y) y! Z! R6 H4 N% e* e

% y8 [$ w0 o9 b4 }总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:
& G' p. |2 t! t  p% @ 113818pmrfsb6z0byt6t06.png.thumb.jpg % F5 O- B6 O$ V; l* V
' T% [/ E) w) Z, Z1 `& [! K8 A
其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)8 B* ~( y! b' v) p0 ]% f

6 }5 J( O: t0 F* k8 Q: mUSB的中断ISR,bare metal哦& n* B4 [4 _, z

/ K1 X9 q! ^; W6 d! o" e' T7 P7 R1 evoid USB_IRQHandler(void)
) p! Y$ [7 X$ {# F+ N: H- k{; v/ n9 _" l4 B- Z, }$ c, u6 c# b
    if(USB->ISTR & USB_ISTR_CTR)
2 ~/ R  E1 H2 x% k2 z    {  Z% C7 N- F; `" @2 U) ?. Q) `
        if((USB->ISTR & 0x0f)==0)   // EP_ID==0# a" X1 e( v8 b$ C. b& P7 b
        {
- F  U) W: _6 t7 A; f; V: S- ?* o; E" Z            switch(ep0_state)
5 o. X0 j7 ~% Q* [& S  L$ q            {
$ F6 q- F$ L# _5 }                case 0: ep0_state |= 0x80;+ E( p! `) a1 U* o1 e" Q5 T2 Y
                        break;1 Z% x  B/ `0 W
                case 1: if(USB->EP0R & USB_EP_CTR_TX)
8 d5 \( z& Y4 V( T/ u, z                        {
1 a9 C/ t; W! o0 Y                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
! \* a1 E9 x4 N9 ]7 s3 T- o) v# e9 ?                            ep0_state=3;
0 n# D7 K" Y- ]0 w+ X: z                            return;9 S0 ?6 C% y2 L5 s: B4 ^, \
                        }
5 C5 p4 a+ q/ |* f0 |  H( P                        else
. O+ M; j, h- J4 [7 P                            ep0_state=0;
: L& c" D. _" c5 M3 D4 D                        break;! ]1 x, i2 }2 u5 s) e+ B9 F4 l
                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet$ ?' T, q& _) }6 G* R2 O
                            ep0_state |= 0x80;
' C/ n4 k! m& C* f" N2 y/ R                        else; w/ T% D; U$ u5 u
                            ep0_state=0;! T8 F% x( a* G( Q
                        break;
, p0 A6 ]5 S- D: S9 F" ]/ v                case 3: ep0_state=0;
% k" o' z% ~# b5 x1 L! A) b1 X                        break;  ?2 v; B  S! V+ @: q# q
                case 4: ep0_state=0;  ~% v7 r7 l5 @5 i0 n1 s
                        break;
' y7 [, `% P& v1 e6 G$ I                default:ep0_state=0;
' x' O$ P0 |6 R! q                        break;
$ a6 I0 S, f( x7 x            }
1 W9 j$ K# v4 A5 c( c* K) t            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
/ K/ c* S; t: Z1 v& Q4 E            return;- N& _0 b9 V/ e- W$ W( ^
        }: q! r5 @! @( L& ^
        else    // EP_ID can be 1
3 w, L6 v7 N3 P/ H) ^9 B$ O        {
! G% m7 ?5 `7 D) ]" ^$ o            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag! v+ s$ [+ \7 f, o# k' {: I, B7 D
            ep1_wait=0;
6 ?# R" J7 |+ k  ~; S            return;/ T- c+ h( Y3 o1 ~9 S
        }
$ [! l& w* K" f; R7 _/ L    }
5 V6 u# h" R7 ^, U    if(USB->ISTR & USB_ISTR_PMAOVR)6 V' Z4 k. c$ b( V" J0 y" T
    {
. d. V5 Q) c: X6 A/ L        USB->ISTR = ~USB_ISTR_PMAOVR;
8 _" a7 h5 S: w    }
3 M8 J" Q! {+ s) n8 a% J    if(USB->ISTR & USB_ISTR_ERR); _8 D, K! o! d) D) x; S$ V$ e
    {7 {# N7 ]8 X0 p$ ~: S
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear3 F& N  b, C) K7 u8 F* U
    }4 i- x* R9 z# M3 G) p
    if(USB->ISTR & USB_ISTR_WKUP)
0 T2 ~7 i  T0 g: g4 I/ X5 @) ^    {$ f$ e% m6 h& T: O8 v
        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
1 R9 F( G( |: f- n4 N        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear. _2 Z" k; X3 Q2 `2 `) b; P+ V( j
    }
/ X  i) u' I& l  n! M    if(USB->ISTR & USB_ISTR_SUSP)
! c( b3 F& H: r! W    {' h0 k3 C9 \6 d1 Z* f) N
        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend
% H8 k4 Z7 P( x0 Q( N# P        USB->CNTR |= USB_CNTR_LPMODE;   // low power- ]7 e0 p& M0 ], g. G
        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear
5 @) B& T6 K# Z, o. H+ Y/ q    }3 N4 R5 T" P( K9 g8 d
    if(USB->ISTR & USB_ISTR_RESET)
7 D( c' G( y+ a    {1 m& {/ {% b0 W9 s' h
        USB->BTABLE = 0;    // buffer table at bottom of PMA
. y% x6 r- M& }: q2 i$ o/ V: o        USB_PMA[0]=128; //ADDR0_TX
1 b4 d: s; M! o! P        USB_PMA[1]=0;   //COUNT0_TX' u1 o) ]% M1 D' T  k% m
        USB_PMA[2]=256; //ADDR0_RX8 T5 O( o7 }: O3 V: x4 S8 k
        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes
$ a' p1 j+ r" ^$ f* u' a) \        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;
1 l" i- w2 y4 [( Z# N! ?% u4 b8 i        ep0_state=0;
( j2 A$ f* l: Z6 o        USB_PMA[4]=384; //ADDR1_TX6 S8 Y5 k) C( w7 ]1 J# d. B
        USB_PMA[5]=0;   //COUNT1_TX& B2 n! J0 m! B
        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type$ C2 @7 |  ]5 ]* t
        ep1_wait=0;& ?. {2 e# L! i' R* x
        USB->DADDR = USB_DADDR_EF;      // enable function
; _# l' V$ `8 C# ^) e. d+ M        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear
6 _# H% `1 J/ O9 j* d    }
: o5 A' i$ ^3 n    if(USB->ISTR & USB_ISTR_SOF)
4 p1 h: x! A7 n, r( m    {
% \6 L8 Y# T1 |5 _& Y7 \        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear; U2 A/ r' A3 b. w8 g2 u1 O
    }
( `4 j! V8 C, O- P9 G}) o4 F; ]! h+ r  o! y( p5 c9 y5 e
3 W" s7 o" o& J1 }
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。' }" s/ S" ?; F2 W

: b9 x  k* V8 i  M5 }: z5 s6 H3 u主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。' Y; S5 h; a: M% K: n! u
( ^  I' ^6 ?* S( O2 G8 [7 c, x5 r
    while(1)
) l! L; R; X7 W1 v, [    {
! g4 Y9 P5 x# h* E; \! W2 S) D        static char row=0;! x" w8 X; `% ]3 S8 q
        __WFI();
) V4 g. o4 C6 L0 N        if(ep0_state & 0x80)    // request data processing
/ K8 U* P. ^2 O+ v4 E6 G7 y        {. r2 x4 p$ E% Q$ Q+ q0 r7 G- i4 w6 y" B
            if(ep0_state==0x80) // SETUP phase1 C; \) x# E  H8 u# U0 u! |
            {
7 K! f( A  L, }# {6 _                if(!setup_packet_service()); F4 ^" _2 M/ T; k
                {
0 m# p: h. m3 R1 v                    ep0_state=0;
, n$ q4 P% E$ H3 n/ `4 F                    // not supported' T* A( |, H6 S
                }
3 h6 C1 Y8 i9 L                // ep0_state should be set to 1 or 2, if processed* S; E3 G) ^  z7 Z% ]) B2 I- w
            }7 w* w. [) e% P, t5 @4 }
            else    // OUT phase9 t! H) d/ p5 i) K% o1 C
            {% X, x7 p0 o8 K' [, K8 r! p
                // process data5 z  H5 R8 z5 _
                show_LED(*(uint8_t *)(USB_PMA+128));
# I6 r: r& T7 y                ep0_state=4;
! J7 K7 i8 ^% a+ E: h                USB_PMA[1]=0;       // Zero length DATA0
: h& H/ A' K+ V* ~                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
; h' ^4 f$ z% A7 Z! t7 X            }6 z5 h9 `0 ?5 M5 H$ d! O
        }& n9 q( [+ X+ N& j" m* h
        else, ^( s. U( x. |3 g: t- @
        {: V5 j. H7 f0 m7 |* z9 O
            if(usb_address && ep0_state==0)
& _0 ~) g/ q9 ?% a9 p            {/ q& G3 b. s! e( J; W
                USB->DADDR = USB_DADDR_EF|usb_address;
/ @* y+ B& J4 v. k; f9 b* P                usb_address=0;
# T5 i# U* |6 O% e# J. ~            }
# N4 K+ d' _+ ^  H# n        }9 }  Y! ?* ^- I# @* P
        if(row!=scan_row)   // new scan line, A* l' V9 H$ q$ k" ?0 I: c& W
        {
* h+ }7 T: x2 Z. N9 f6 s            if(key_state[row]!=prev_key_state[row])' t# n# G! S! w4 W) Y0 p+ ]
            {& a* r2 P8 Y$ ~0 {" u8 `$ b9 w
                uint8_t test=0x80;
: [; z" M- y/ L+ C7 e                uint8_t diff=key_state[row]^prev_key_state[row];- N; O* q( d2 Q7 U; C
                for(i=0;i<8;i++)
9 Q$ V( L  Z! ?" ]/ G                {
+ s' ~: Q+ w5 a: B5 L% V                    if(diff & test)
: o! J2 t' M( t' @! Z                        update_key_matrix(row,i,key_state[row]&test);
* N5 h8 n% a* ?1 Y  A3 T                    test>>=1;
3 d8 S# f2 v* m: I  s) ^+ S                }( ]: p6 n7 o: e1 U# B1 e( f: T
            }
' M: t) S) B8 e9 |, G            row=scan_row;
$ v. Z+ A1 ^/ C' \# _2 t2 F        }. o) Q  d& ?/ u2 j2 d
    }( m, l& o! F6 x# V
' z: S9 n1 l% B
0 q( v  L9 S9 `* }) R
EP0的控制传输,把用到的请求处理一下
; {$ y7 X# |6 F; e% F/ s( L2 \! d/ p, Ychar setup_packet_service(void)
, T- g0 |" C/ v( k  J( i# d{
4 Y# c6 T/ d+ w    if(ep0_std_req->bmRequestType & 0x20)   // class-specific- ~. |3 ]9 S& Z
    {
0 Y8 h2 g+ @9 z, w, \        switch(ep0_std_req->bRequest)' t' r- S, L* ^+ o8 `
        {
4 b9 N7 Q. y: l, N            case REQ_GET_REPORT: break;
8 B: R5 ?' l6 [0 J            case REQ_GET_IDLE:: C  N1 P5 n. l
                USB_PMA[64]=0xfa;   // return 1 byte- s. X% b$ D% n  R9 x  ?7 A
                USB_PMA[1]=1;( I  {$ X) P4 a3 r5 U4 [
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
" H, m9 V2 k6 H4 A: M# G* l                ep0_state=1;
6 V% F' `- ]  c1 N) K: H! m                return 1;4 ?) [( x) C- X: {" I" }2 \& m
                break;
/ w6 c; ^2 x. @            case REQ_SET_REPORT:
+ x$ l7 p# `& e0 u1 v                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;; u3 a* Y+ {* p8 \3 K6 A
                ep0_state=2;
3 ~1 G0 G7 e7 p# {/ g                return 1;
+ u! N, n3 P. ?5 l$ l                break;' k+ ]; g& g  f  Z1 y) c
            case REQ_SET_IDLE:
+ P1 i% ]+ U$ {2 q# l, b, v                USB_PMA[1]=0;   // Zero DATA
1 w/ y* X: }& e8 M7 m2 J) s2 P, t                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
8 P$ z, Y( r" j1 B                ep0_state=4;
  t  W6 R% o" v                return 1;
% Y$ g: P% C( g" J1 t* ~/ [8 j                break;
0 c+ ], m, e! c- N& e        }. N; [) o6 t, u/ n) b0 @2 }
        return 0;# @; w. p+ u' a; L5 q. l
    }
: W% S6 p3 }  m- y    else    // standard
! g* T( ]/ m: P; F2 r% c, Z4 @9 @    {
9 ~% e- |4 B+ t; `        switch(ep0_std_req->bRequest)' e- I% Y+ `$ v% a0 b* ?) X
        {3 W' ~; l  l  R+ `
            case REQ_GET_DESCRIPTOR:' X2 K& [' ^6 O0 r- @+ R+ U
                return descriptor_service();; X+ U" V% j8 N# C  t' c5 A
                break;6 V* z( ?4 ^- O% G' W# Q
            case REQ_SET_ADDRESS:- M8 x& d3 ^2 M0 o; W
                if(ep0_std_req->bmRequestType!=0x00), _% h  O  o- u( Z5 q; x1 f
                    return 0;
; D  R7 P  R) T; X3 i' F$ b0 c                usb_address=ep0_std_req->wValue;# ?! v& o  C) p& k% s) Z1 h
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;! a1 L$ @" Q. Q, p+ D; }( c
                USB_PMA[1]=0;       // Zero length DATA08 K" S5 a7 u; J: N$ _7 H' N
                ep0_state=4;    // No Data phase
+ r  z0 L1 v4 j0 k! J" k                return 1;0 d6 P/ z& X' D* w4 t
            case REQ_SET_CONFIGURATION:
- T* k0 v8 q5 v  P                if(ep0_std_req->bmRequestType!=0x00)
6 e7 V+ S( ~% q, Z                    return 0;$ J# L  V) s5 z
                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
) R8 V( {/ V+ |4 X3 O- W  y, k                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
$ [/ ^! O3 T; B                USB_PMA[1]=0;   // Zero DATA7 o/ x: I+ ^" ]5 ^' q% b
                ep0_state=4;    // No DATA phase% {$ w7 j6 m6 G+ g
                return 1;
$ A' d& q% N9 c' }5 U' z            default: return 0;4 g" o" w% @# k7 m- P
        }
3 h. v: _( M3 z" k2 u7 H: `: |    }8 W' M3 [# G( ?4 l8 K; I1 k
}, B1 h* P9 Q; i2 @

# _- S  t; y# S
5 J6 K& e1 Y! ]4 l  Y" R: g$ A+ `& F; A. s% u% p! d1 x
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的9 o  Q: J4 L/ O2 s/ V/ c
char descriptor_service(void)9 A, B! [0 \( ~; B' z: T
{
1 }6 x, g5 H6 }5 f0 }( o, [" E    switch((ep0_std_req->wValue)>>8)
0 Y5 o5 }% K# [3 X, @: I    {+ A' \" Q5 f) ]* n  }$ b! e& F1 `
        case DESC_TYPE_DEVICE:
, I: a" e- Z* v7 S1 Y            return ep0_preparedata(&DevDesc, sizeof(DevDesc));" t* k0 E8 p" K0 x3 O" d4 V8 _5 d
            break;8 i6 m. H3 h% |' y# c, N1 b
        case DESC_TYPE_CONFIG:9 H  ]6 Z0 I9 ]/ E  c  E6 p
            if(sizeof(ConfigDescData)>ep0_std_req->wLength): ^8 J- j: u/ f0 M6 j
                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);
, a+ u+ i( p# y) I/ o+ L5 c            else
0 o0 J7 I- r2 o5 h4 O1 f* p- @1 z                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));- b3 @7 _) x# |8 V1 R# T
            break;
/ |! r9 A4 m% H& C8 F0 ]! Z        case DESC_TYPE_STRING:
" d. C: O/ E" L! A" T            switch(ep0_std_req->wValue &0xff)
+ ^3 b! E. s8 ~& E, |' ^            {
/ k' M3 K& ?: L( Q                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
8 r* f. c2 C& M1 x, T  x                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));
0 f$ t( j0 m& f2 Z                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));
4 Q- _: c- @) G/ B. @                default: return 0;
: V+ W- `: C2 j& R$ N) q            }
6 l: I  O$ A7 F. ~" `  B) [/ V            break;8 n% D" V! C; s$ q0 i2 J& w8 l" W
        case REPORT_DESC_TYPE:# K+ B# H1 w% W9 A& F
            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));
" x2 n( F/ h' g( x* M# A* l        default:
9 O1 |9 N; P7 ~1 @8 n2 M, ~            return 0;
" k. f3 _6 a( @  q' @  \    }' K2 y- Q$ i3 _+ I; A- l
}
0 G( ]% [1 d' ?3 O5 ]2 W下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.- A. e' H8 o* [
void TIM6_DAC_IRQHandler(void). \, ^1 x- J2 y7 k) x
{
% _  m3 P) g% U1 O  l) z! e    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);# N# {, _2 }/ W% M0 p$ c
$ ~/ E% d0 ~  D

3 w  f9 |: Q% I6 [/ H9 ~' C+ G    TIM6->SR &= ~TIM_SR_UIF;& K. r& `& a* v% w- d6 W1 B9 ^
    prev_key_state[scan_row]=key_state[scan_row];
2 Y$ N3 d0 V: p7 v2 f    key_state[scan_row]= *PA_IDR;   // update key states. V  A: x4 y2 _4 |
    switch(scan_row)7 o0 e  n/ b. e' r% s, ^) @" k
    {' `1 |9 @0 P1 [+ h
        case 13: // next row PB14
8 r; |, t/ c$ T& Z5 w2 c                GPIOC->MODER = GPIOC_DEFAULT;+ ^; Y$ b, B, e/ }( U9 j- h6 [: c
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;
7 {1 W- L& V  u: V" @+ d                break;
% E; k  p5 H) q  q- f        case  0: // next row PB15
$ v( E1 ?* I3 O& O1 C                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;/ r- J4 x6 N' @) ?1 r; |9 u' o
                break;3 s2 C) n) |  d" V" V' y0 e+ v
        case  1: // next row PB3
+ z1 ~' b8 V$ T; M1 }. I9 e+ }                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;
9 O2 n3 _( Z' x. q' T4 T                break;
2 J7 n0 C) U1 X. O        case  2: // next row PB4
% b7 Z6 {9 l% Z6 V                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
+ x9 O- r/ J5 `                break;+ ?) R6 W# _3 M" d; o9 e' i
        case  3: // next row PB5
& Y. o% x6 E) q. `( B  l                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;
' b9 P4 }! @/ d7 I, C: {% m4 j                break;
1 \% v+ \. r! L0 ]6 y0 w        case  4: // next row PB6
7 E0 i% r; P' \% ^% |/ W; I; I                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;- L/ l3 v* F7 s" q! }6 u4 y: U
                break;: D2 o% K$ J3 O- B: f
        case  5: // next row PB7
# V  m3 T8 u% o4 C5 |# x  o                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;
/ z# @( L! O  l- k6 r                break;+ y' l  B2 M- d% Q' D( m0 Z
        case  6: // next row PB8! F& H. N3 _# t/ G; \0 i
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;  l7 x/ v( _3 ~  j& n' ^! k* v" y
                break;
- {& b; z3 W7 L' A; ?& I8 A        case  7: // next row PB99 W( e+ v% [, j' |! C$ K6 t
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;0 h3 k6 O1 g: O1 k8 Z% f
                break;
& V4 a7 m9 U# \2 @        case  8: // next row PA8
. z) z: k0 T! Y; p                GPIOB->MODER = GPIOB_DEFAULT;
+ E. b! N9 b* m0 _+ g! L/ ]                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
: w0 ]6 _) ~0 d7 B" b% u+ w                break;0 \2 Z' V; m, }" [! {+ l
        case  9: // next row PA9" p2 @/ J5 x2 {7 g" ~) f3 `! @
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;
( h$ |3 r% ]- K! x                break;
9 h2 e/ t5 H, ~) t: K% b( T4 j        case 10: // next row PA10
5 d" g3 }% ^6 f, i9 i6 h# y                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;; b  C  ]' q1 `* K' n# U
                break;- D* C, F9 P2 G+ A9 a2 `- p# Y- ~
        case 11: // next row PA15
: Q! ~* p5 P& E) V0 b- K! x8 e0 r                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;" q& D" k- I  q9 p. K! W
                break;
8 y/ X! X7 \3 y( t$ x* ]* v1 Q        case 12: // next row PC13
. t3 z! `; |9 O- ?$ ^                GPIOA->MODER = GPIOA_DEFAULT;
# e, h* ?, {4 Y/ t! h+ `' ^: H; G. T                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;6 S3 _9 m4 {. o+ k
                break;3 O1 D% h) H& [$ t) _
    }
+ S! U% p3 v' r6 c7 w* q    if(scan_row<13)( p5 E; u/ |' w; |  E9 `" O) b$ t
        scan_row++;4 _" K( @7 ^$ c$ }
    else
# n  B9 K8 D4 |, {% |# k        scan_row=0;; v. N7 ]& F% V* Y- ?4 u6 H/ i2 M
}
- y: T6 O' ^8 U! A2 c9 h" v- b! r3 I) D6 v
: t6 h2 ?6 b8 v$ K" p; i
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑 ) A4 E, _4 H; Z- b. ~9 h: E0 S
3 N& {. z; l) O2 h' q/ o& _
扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。) J5 n( ?3 g4 _( k. o0 a) z. B
5 M* y5 Z9 [/ g+ B+ _- Q. e
要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:
& E, ?0 q9 m- R' G" h  c, n
: ~5 P; d* I$ w0 _" N' M( j( q' w5 ]

) D  D2 \, h! {# ^const char hid_keymap_qwerty[14][8]={
8 Z9 _/ n4 W5 L* i5 e7 o; ^! ]/ H5 d    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},
* l5 `, h# q7 }    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
7 Z) M' ?' V0 b/ i0 ~! Y. o    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},) v+ I- G" L) |- c) k& p5 c
    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},
1 ^+ _: g5 U; |9 G    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},
8 r7 X/ G+ c8 h9 V    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},
. Z) r- F! I0 h/ i    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
# }# J1 ^! A5 l- M    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},
/ j# C4 K$ I) L    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
+ \6 J* i0 N+ U" _' {: ^6 Q  K    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},1 m  ^9 ?3 ?7 j" q! U$ |
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},5 J6 D% |3 v" ]( |% x/ x
    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},( z  W5 L4 x  t+ b/ e: @7 J
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
2 h; r- _) j/ S9 U! _9 O0 B    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
4 Q# Z- p7 w2 S6 I, `1 w4 _};5 e$ U: v, k& X# D( S

# Q& h5 l! \5 r" Q
5 U- t6 Y6 X* M- Y
const char hid_keymap_dvorak[14][8]={6 X2 _4 t( o, v" u; a$ _* T
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
+ O4 a- V* F/ e# [- o    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
1 x! f$ Y! X; }9 F0 e' H    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},3 V0 u7 m* |  [5 c' E1 e. @
    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},& Q& \( S& D, _  t  P! [
    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},
* p& n0 N2 f: Y5 I. C/ s5 t    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
3 Y  a5 R6 B0 k    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},# N* x  }0 u4 D+ r- d7 V
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},
9 i* x: f6 f2 L8 U2 w5 r3 x8 I    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},% c9 c& Z+ y# n0 ^/ ]" U6 D
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
3 o) J& ^1 l7 b! x7 x    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
  O3 r2 \* A# K5 F- H6 ^+ `$ e- R    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},
6 |  R  t# X- b; @+ D    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},9 ]5 e9 l* p7 t- ~. ^5 `& P
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}$ a" {1 N6 M0 ~
};! H4 D+ W4 r7 T# v: d

4 Q! N1 \5 p) r6 l8 Y9 @1 d! c) L! J. H
上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。$ r$ h2 \3 G' s0 N4 c1 b
0 u( A; w: R6 G4 W# Z
HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:

+ e' R& S4 T4 ], z* e$ [" {1 T$ b- c+ T  m
void update_key_matrix(char row, char col, char onoff)
% u4 g+ h) Q# U' f! m/ s" P{
6 `, l. q, Q* r    static uint16_t hid_report[4]={0,0,0,0};' `/ o3 V2 r  F. u
    static char (*hid_keymap)[8]=hid_keymap_dvorak;/ N* B/ B+ k/ X! C6 J8 e! t
8 |+ c' d, L9 J1 r
" j% y" `. Y; K
    unsigned char key=hid_keymap[row][col];
0 H" h+ k6 F4 A& x3 j: _- T! z1 T    unsigned char *report =(unsigned char *)hid_report;
, Q  R0 _7 y+ Z' Z    char i;; n; V+ H8 o$ S5 |, U; k
) f7 E$ j3 l4 W' k9 \

1 w6 N+ ?( E/ I  ^- S    if(key==HK_MODE)
3 {( p% i' q5 c& T6 R1 f    {
5 Z  J6 T! ?$ @/ v        if(!onoff)
- S& T4 T- u) ]) a" \        {
- N4 [0 f$ @' P7 ^7 T            if(hid_keymap==hid_keymap_dvorak)
) u- W/ f$ E( _4 L' C. b            {' }4 Y) c( b2 f2 s- Y4 W
                hid_keymap=hid_keymap_qwerty;% |5 S/ R& }& `
                GPIOB->BSRR = (1<<2);; g' j3 J0 a( C3 K
            }  I$ H8 p. s3 G" D
            else4 T4 e4 b* a9 T* s$ ~: Z/ I9 n
            {
2 p) I( n, y! \* e/ i' s7 v- A  x                hid_keymap=hid_keymap_dvorak;& E. C% r+ N7 M3 F' l! G: v8 r
                GPIOB->BRR = (1<<2);
5 p8 A7 A0 ^+ F" F' h9 y5 Z  R            }/ M& ?, O$ B; A* S- ]: U1 C$ R
        }9 z2 I- L' S2 Y9 F  `: E4 \5 u
        return;
7 B" D: y# ^- \$ D0 Q/ f5 b    }
+ L. m% x* Z8 f( S& ^( X1 l/ p
6 L( b3 }4 e* f. Q
    if(key>=0x80)   // Alt, Ctrl, Shift! d; u3 B5 D: T
    {) k  O7 ^: s  E
        uint8_t bitset = 1<<(key&7);
7 v* B; F7 M  b4 N& G        if(onoff)   // non-zero is key up5 H3 M* s/ d/ L( a
            report[0] &= (~bitset);( `! T* x/ X8 b. `
        else
7 T: i7 T5 T2 ~3 j- _" [' m& N2 {# G' D            report[0] |= bitset;$ L! Y+ U' X! E9 q
    }
- X6 v1 k. l7 R! I. R1 V    else/ }4 i6 D) x9 M. K
    {1 Z  c& c" |/ K! L, O
        if(onoff)   // non-zero is key up
- S# S7 e% ]" a: w) f  b; k- W* G        {, R/ ?# e0 }$ _2 e  r5 N
            for(i=2;i<8;i++)8 `. S, T5 q9 e! q' I5 Q' y& B" h
            {' f# L6 O' P% s" _/ V) `9 S8 T
                if(report==key)
* g9 {0 q& E3 r# B) k; o& y                {* m2 _6 b4 ?  z0 @' r
                    report=0;
6 F. l3 B' o2 T3 Y                    break;; Q: {' k  r' I# [- o7 \. A% u
                }
+ J8 E3 d% o7 x* n" C, B% I            }
, F' e* f  _$ c6 O; ^- a: e1 C, A        }3 L1 t! M9 I* e6 A$ E4 `
        else
9 b9 d" I( ?2 i4 M0 Z: K        {
$ C1 d9 Z* {' ^. x8 r            for(i=2;i<8;i++)' n  f4 V6 \( q1 y  ^# A3 o- H
            {+ k: S: ^6 `  W
                if(report==key)) ]; a7 ~' ?/ Z* B% u" _$ v! h; x
                    break;4 T# r0 y# m. v8 C1 }! e
                if(report==0)7 t  B2 w6 o9 d7 f- b% i' e
                {- ^1 W* Z5 C8 \( k# S0 m) i" T
                    report=key;% m+ \9 |# w  `. }
                    break;0 I/ Y9 A7 \+ H: K% T, V  K! k
                }
( K% ~" C) F3 ^. ]5 N            }* d6 U9 A: Z4 ?% l& `) j
        }! l- J& [- Y  r6 B0 D( y- E
    }
6 w/ @8 ?& {9 p' p    for(i=0;i<4;i++), M: x! I8 R, F3 D% E! m
        USB_PMA[192+i]=hid_report;
% k& e& X0 K2 Z3 M/ q: m9 w; s. V    USB_PMA[5]=8;   //COUNT1_TX
2 r5 B% f7 @. @  C0 l, d    if(ep1_wait==0)' S+ P2 d, M* [& N! {$ m. i  b
    {7 b- p, T- C% h( B
        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;
4 l$ W# ~7 W2 V( T0 o3 j6 {        ep1_wait=1;
9 z5 X5 n+ ?. p8 n6 r    }4 }, D- C( @" }& g! [* l7 }3 k% k
}  M7 B7 U) {0 ~  m

" k2 e% S# n  q' G/ Q1 E' t) z9 q1 Q% C$ I- W6 @
完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。
2 e+ f! \- l# d% A' x3 ~- d keyboard.zip (8.7 KB, 下载次数: 6398) 9 ?2 s8 n. y! n+ A0 [
  _, k& Z/ x9 |4 {; S

  x% C: D' x! ]$ D* W8 N& l2 z8 P( e( Z4 Q

3 v7 c' ^& r7 m( V5 G. S  ^
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:08:09 | 显示全部楼层
在电子论坛上看到的,这对于改造键盘的小伙伴来说,提供了很大的帮助,步骤很详细,拆解换主控,设计pcb板,编写固件,看的是很过瘾。  F# u/ R5 L) d+ L, x
回复

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘2 @% ^: Z- |( u" |+ o7 E
不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48
+ A5 ~; }" \# {+ j2 `, q刚开始我以为要把打字机改造成电脑键盘9 L# K: n' T$ i- B: M  `3 u& S; [; b
不过楼主也很厉害!

2 O6 l- ]3 a* g8 X3 L/ {; X, V. G哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
( q1 M- H6 W. B
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-1-17 00:14 , Processed in 0.173142 second(s), 27 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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