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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

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

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

4 o& b. \- v8 x8 f3 d  f. \ 123206vx24rfxfp2dfrpxg.jpg.thumb.jpg 3 N. e9 a) z9 ~/ N& R, o- B# H5 [- O
----------------------------------------------------  分割线 ----------------------------------------------------------
: u+ M6 R- ]) B, V3 `" W7 y/ ?  r
9 I, s) T" Q, q, z6 W/ n先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

: I1 k; Y  v- E 020011osionbunl4ui44vi.jpg.thumb.jpg
6 x0 F/ ?/ U! P轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。
" u; y: L- L5 n9 ] 020017j8ycmnv7788bqv52.jpg.thumb.jpg ( ]/ I+ Z  s# w* f
特写,80C49$ c  Q+ {. K- i% H
021040oujzuvtut6iujtvz.jpg.thumb.jpg
1 p4 ^1 N5 B' q+ u, x) Z9 H) ZLED部分,使用了一片D触发器锁存指示灯状态.
( W, n& C$ ~/ H5 f4 L8 j8 w6 W 022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
5 [- U3 W, z2 [0 }4 G暴力破坏,将80C49拆掉
# t7 V8 p( t* h& F7 X1 B 021113e48qq98vyohvhzzh.jpg.thumb.jpg 2 W2 t' |+ }0 H, e5 e: ]; }5 _
拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。
6 ^- P3 Q+ e* _7 I) E9 u+ ~ 021125nc9az6dj33rlds2r.jpg.thumb.jpg
* I9 v5 i& r6 |( b& s
焊好元件后的板子,准备替换80C49; G2 |) i5 q7 p" Z; X
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg
4 v4 h" J$ f7 B7 r- P用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。
/ z- F3 N6 V; m" d' _  m' X 021104shifhnrqbr3o5nlo.jpg.thumb.jpg
' V& B- b! q  h7 \3 G1 V这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.
7 z% n3 M6 h; Q+ a" C! r 022003ym1p9u4ug40280uu.jpg.thumb.jpg
6 f+ a* q* _" w  x0 g2 f开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。
1 J) V- ^( d/ G; s) f/ i 023313kt141q9qajtol7ma.jpg.thumb.jpg / @% ^* c& S" E4 }1 x# t& I
我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。# Z3 Y1 \0 G# M' x2 g
023322nt7l5xb3ltttkltt.jpg.thumb.jpg 0 d1 V/ Z# B0 o
主键区键帽就位; R  t- O2 z+ m- Z* a! p
023331hin88e8wkrwzwikx.jpg.thumb.jpg ( }4 M2 m! J2 Z' O  e& l5 E
编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。$ z2 P! W7 y, f, {- ]
023336wjzlgopugg1jyy79.jpg.thumb.jpg 6 c' Q7 Y/ K9 s4 y
最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
4 P# F  g* ~; c$ g" J  J" u- A 023341sffu4j3g2323h6fl.jpg.thumb.jpg
6 `3 o5 h6 H" C" `

# h4 \  w' H) n. |( s& r, L* z----------------------------------------------------- 分割线 --------------------------------------------------1 K( S/ y% _9 S3 X, Y+ ?+ G  [
# u; {  K9 y, f1 C( `+ K' v
7 c2 x% b( [1 x+ e, t. m, y
6 q; U+ T( ]1 Q
4 p' [8 Z. s0 X& H& T* k
7 I: G4 H6 {/ ^  y
. x6 [% C/ n) W! ?

$ S6 J8 ?6 j& }$ ], g$ _, l2 ~5 i  l4 x2 ^7 O5 x0 p" e
* w+ |# Y9 f( w7 N% L1 n  i. D$ q

6 \9 X* _$ v" A; Y
: n7 [3 L8 e, W- X0 H
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。$ a& h" v2 M! H) Y6 U
80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:: w6 S" x. W+ r, C
104025nzibm2rmiomhyirm.png.thumb.jpg
1 E* Z, M2 E2 f* a

# n. {  }3 o+ a; C+ `' T- G其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)
6 V2 m" k) j6 ]  b" }! N- q 105004zkrez5houvkkznko.jpg.thumb.jpg
9 O0 |* `5 S+ H8 x, Y9 E

6 {. g/ @- e5 m) j4 h# [扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。
* P' P/ U& M/ }4 H4 j: t, w, c0 x1 G9 e" Z9 {) c" v
这是我设计的电路图:
4 l- p! h0 r$ B. y 110344ej2z2oo2rflo7oe7.png.thumb.jpg
& E$ t8 ?, p4 y4 U
& y) o# A  o" j) K
PCB Layout:# S2 o! Y. O1 t2 |; x$ z/ m, D
110847jjbjvt34vwt3v5bb.png.thumb.jpg
9 s  h. u9 k1 D
" a3 q8 V; y4 @5 p8 c9 m
不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O. * p4 J  t7 |9 o3 C5 K' t; f7 X! Q
6 I& {7 u) a& z7 D# L2 \
2 p/ C4 c+ f' |* @3 X# ~7 b
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑
9 x' b# f  x. }: s
+ |" J1 \9 V  D) r+ h/ I8 }软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。& \7 Y# O  ]2 T, c/ N; ~
+ m7 ^! P5 W; S4 i1 k
总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:7 A; H; B3 X& U  \0 J. @
113818pmrfsb6z0byt6t06.png.thumb.jpg ) Y* h' ]; e+ ~& [( S  J. s' ]
* P  z( E4 S, X. l' K
其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)5 H! T2 ]; l2 w; d7 b$ C
5 E" y& ]! ?# F8 l" n2 R
USB的中断ISR,bare metal哦. L8 y7 u( V1 h
2 T2 P/ e/ c  s
void USB_IRQHandler(void)
; I$ d  K9 }" y0 U* B* R+ o* ~{
. N: R# m" k* r& n: C2 e    if(USB->ISTR & USB_ISTR_CTR); a+ N! u4 d$ ?0 s
    {
* `( @: _  O0 }5 l        if((USB->ISTR & 0x0f)==0)   // EP_ID==0/ c8 b( s9 A3 H
        {( K, J- a! ]; Q3 [. Z
            switch(ep0_state)
8 k3 k  q' E' f& x            {4 L  p4 Z  i3 p, b, W
                case 0: ep0_state |= 0x80;& T7 m5 s% ]1 ~0 J
                        break;5 t  x9 n" t/ u* o
                case 1: if(USB->EP0R & USB_EP_CTR_TX)
) @6 C. Q/ j: S0 x                        {
* s: f  J* V( `8 r! {5 b9 b                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
7 s/ I8 E% y) n! @1 ^                            ep0_state=3;
; x- ]) i! b" F: n. D( m; E                            return;
* g1 r1 K) S1 r4 O" j; ]" n                        }
8 o3 Z( a0 @( V+ l  ?/ L- m# [                        else
6 b" s2 ^& n1 `& _& ~  N& {                            ep0_state=0;# ?* [) Q. g# ?% C: X
                        break;" |" K8 D. S0 A% O
                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet& I- \+ d8 V; y% \1 B
                            ep0_state |= 0x80;! @7 G6 I# y9 A
                        else) q7 p+ q$ ?) J! r& p# e# v
                            ep0_state=0;
. Q) u3 n$ C% M  O8 K                        break;& Y. \2 \- u+ G+ v$ d5 v4 q0 d
                case 3: ep0_state=0;' I, D, A. y9 v; M& O( G) H9 C! q
                        break;
6 q8 U0 o  f- G                case 4: ep0_state=0;, n( ~5 X: e  k" ]$ p
                        break;
/ @5 o# ?* Z7 i7 u# M7 N$ h                default:ep0_state=0;' }* p. U2 l9 o3 l2 ?1 r2 t
                        break;! u' Q/ [: c/ C% @6 Z; }8 X+ p6 c
            }  Q6 \* [. q$ E/ f
            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag! t% L8 x$ f( o6 Q# o- d8 x+ h
            return;5 o( t8 W, T% [, D; d7 ~- z
        }1 @( I7 s1 Y7 l6 m
        else    // EP_ID can be 1
) C$ y8 u' b) [% T: S& A        {* h5 B+ n' l. s: ~5 d
            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag
$ \( @" z: O5 Z. W2 ~9 U+ w0 ~            ep1_wait=0;- R) d( s1 E. C) p  J  C
            return;& V6 C( P4 H4 K% }1 ~, l6 U
        }$ N$ S* d# l( _
    }) m( r% R( H& P$ ~" |2 t
    if(USB->ISTR & USB_ISTR_PMAOVR)& V6 m) ^+ ~) u/ V* P( m" B
    {
; x/ G! m$ b, a        USB->ISTR = ~USB_ISTR_PMAOVR;! r; N& q- ^& S3 V2 U. E4 U
    }
/ H- X) N2 ?( E( E, l2 l    if(USB->ISTR & USB_ISTR_ERR)9 _+ K' L7 d7 `/ E" F7 o
    {! X7 }3 w  X! P5 q; k
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear
4 c+ `9 ?$ {2 B/ k  n' z    }, Z3 U7 P* ^4 R0 A5 A& g
    if(USB->ISTR & USB_ISTR_WKUP)1 C+ O1 V  j0 G0 y4 `
    {6 Q1 }/ v- {( O
        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
3 o4 Q9 X4 L/ ^9 ~& D- x        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear( j, ?) K( M# N3 W  m7 i
    }' z; Y2 x9 o: a, [1 ^3 A
    if(USB->ISTR & USB_ISTR_SUSP)1 ~# u9 E7 q6 ?( T% Y  g
    {- }/ {1 N& N5 D) X5 O4 X! _. M4 X
        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend0 ^5 R- Q4 W  z; t& W
        USB->CNTR |= USB_CNTR_LPMODE;   // low power" M) E- j5 C) E- v1 A$ p& P
        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear& d) Y  p/ t6 R7 p3 w
    }% o% p2 X1 _) ^/ N1 D! R8 p4 z% m
    if(USB->ISTR & USB_ISTR_RESET)
3 D4 x% u! ?' y  l    {3 Z" {! V$ W! f3 y) v
        USB->BTABLE = 0;    // buffer table at bottom of PMA8 m4 x' w" B+ o* ~3 N. b
        USB_PMA[0]=128; //ADDR0_TX+ a. v! Q. W% }( s
        USB_PMA[1]=0;   //COUNT0_TX3 S8 d5 I; s/ h1 J) V
        USB_PMA[2]=256; //ADDR0_RX
* }6 M7 Q9 B2 ]+ z) j, a7 X. A; C; ^        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes' b  l& A# G: g- d5 n( M
        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;
& T2 w; Q+ l6 Q+ }+ r7 l% Q        ep0_state=0;/ {* n6 |" F1 `( L
        USB_PMA[4]=384; //ADDR1_TX- L% {& e& @: h( G
        USB_PMA[5]=0;   //COUNT1_TX
& T! m) X" X! h# B  ^        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type
; D9 Z. R" v8 \. _+ f        ep1_wait=0;# U0 C9 x( R# e% |" J' k& x
        USB->DADDR = USB_DADDR_EF;      // enable function8 [7 _$ u2 t) p" q5 }% N
        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear
, Y* p6 Q. s5 U/ v) D    }
. q* e2 @5 t! U* v    if(USB->ISTR & USB_ISTR_SOF)" y7 K9 W  {3 Z8 D$ Y5 L2 ~- x: m
    {" h# u4 R% f, W; o' u' ]1 ^
        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear
6 X; R' w2 K" Q/ i$ j. A, E/ i* [    }
, ~" n  r6 C% C: R3 q" d* r& S8 j}* f1 \2 v3 z  k0 @6 I- ]5 X+ ^

1 S3 y  N6 {" D+ `2 p$ k2 N0 ~% B
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。4 g# m2 ]3 m) p- o" v1 o" e0 Z
" s& _8 |' K5 i4 Q4 u
主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。
) q8 H3 ~0 c4 h# I8 j
2 v( E. Y$ I( w+ T
    while(1)/ z" R$ \( A9 E1 f: c
    {
- P7 m) V, l4 \" r  R6 `/ D5 Y% k        static char row=0;. |/ `& e% }8 t; Y1 X
        __WFI();
8 A/ K1 G3 C; @- X5 b( H. V        if(ep0_state & 0x80)    // request data processing! q9 V: F0 x: {4 _0 a$ }3 Y" X/ N6 a
        {
* ^- J0 y+ r" b. L& s2 K# s0 C3 Q( p            if(ep0_state==0x80) // SETUP phase
& _4 B+ F' o5 L; [  \            {4 \8 e/ W3 v4 c* a
                if(!setup_packet_service())8 c/ S9 {# M, Y0 T, ?  z! m  U
                {+ t# O, z  M1 v  x) p- M
                    ep0_state=0;
/ z+ l; M- [  q# e                    // not supported$ {7 ?1 ~+ W1 [9 s0 z% H1 s
                }2 w( O! f% j3 T+ k
                // ep0_state should be set to 1 or 2, if processed
5 U% z+ k1 f3 v+ u5 a            }
  H" V% w* t( R- P7 m            else    // OUT phase
7 u5 |+ `/ Y- I% S/ {) d" o5 G            {
' t6 M) p# [$ g" r5 ?; K                // process data0 B& y* |7 X; t# \  K7 x
                show_LED(*(uint8_t *)(USB_PMA+128));
% ~' t4 v3 L8 y) Z# z7 g- o                ep0_state=4;* |: v# Q2 \, D2 Y
                USB_PMA[1]=0;       // Zero length DATA0- f3 ]) q5 T4 z
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
" S! s: z6 `2 P/ Z8 A            }0 p' D5 V7 ~% r: ~; F3 s* r- a
        }
" L8 j, I- f, A. M1 J) _        else+ V! y% G9 P# P8 T/ R
        {
" I6 z- i3 D! E3 C+ a            if(usb_address && ep0_state==0)
' S  ^! L" v. [8 f, V; t            {
, F( l6 l& B9 O) ^                USB->DADDR = USB_DADDR_EF|usb_address;
& c- E6 `8 B) i! A) [: e1 g                usb_address=0;( O) h' s6 \4 D# N! f) y3 \' Y5 q+ J
            }4 i% }- L' f# A: z5 G3 G
        }) D- C" v9 ~' X, g' B" ?/ }1 Y
        if(row!=scan_row)   // new scan line
1 b# n3 S' {: b" y        {
$ Y+ Z% e) D5 E1 Y            if(key_state[row]!=prev_key_state[row])
( B  g8 k" H5 B! ]* @            {
$ A" v. E( ^5 q% v# }, w) K& w6 G' M                uint8_t test=0x80;
# Y1 M& T/ @/ @0 z                uint8_t diff=key_state[row]^prev_key_state[row];4 r: |- M( e; O) A
                for(i=0;i<8;i++)7 a8 X0 c: |( m" C  K! g6 a/ b
                {
, i) v8 J/ B% ]                    if(diff & test)
" K) T1 d7 R! i                        update_key_matrix(row,i,key_state[row]&test);
$ d5 \  p1 ?! ]' l9 `                    test>>=1;. N* L* W, L7 q* L2 Y3 K. ^) O
                }/ @/ a$ S. G; ?3 ^; D5 d$ o( X3 P
            }3 }7 H* K1 c/ ]8 A: r* s5 e
            row=scan_row;
/ L+ m: ~) }4 U; ]; c        }2 m5 A8 F. J) V" o2 `& J
    }, L0 h# O3 Z1 R% u& M/ ?
  R8 {4 I0 n5 O5 V
. A6 q! U" }" f$ X* _: |
EP0的控制传输,把用到的请求处理一下
1 c* S; S, j3 L5 c7 D" ychar setup_packet_service(void)
5 T" J8 j* H6 j2 f% y{7 l' E4 \, r1 |6 G8 I' ~
    if(ep0_std_req->bmRequestType & 0x20)   // class-specific8 n+ `: H6 B' s0 ^5 w% n+ k$ l9 c3 ]
    {
; J" m  n! w7 `9 b5 G- I& [6 C        switch(ep0_std_req->bRequest)
$ j6 L% \$ @% K. ]. p! L) u5 i        {
# ?1 s) H+ ?% J! V            case REQ_GET_REPORT: break;+ N/ c1 K- l2 [9 i/ y+ L. {
            case REQ_GET_IDLE:
9 t$ {# {7 W9 W5 {                USB_PMA[64]=0xfa;   // return 1 byte+ t: Z2 A6 g2 n
                USB_PMA[1]=1;
. q4 W3 J" h/ U8 L: B. A                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;  P4 X9 O  a1 l% }- Q; i
                ep0_state=1;
6 I8 ^6 w8 X2 z* y# e! D/ p                return 1;
2 M1 T! R+ X( J, A/ o. r; H1 O                break;
0 C  Z' F# U3 Z: g% }1 C7 N2 N            case REQ_SET_REPORT:- P  w0 L3 E" L! m2 f0 X
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
: z( l/ G8 T' U! J  z# e$ g$ u                ep0_state=2;; }, I- [  y9 d7 q2 e' ~
                return 1;/ V3 x* O( K3 B( t3 s
                break;' P( K6 ~, L3 t% {& e
            case REQ_SET_IDLE:
  ~" {% R7 j+ k* {8 j& q                USB_PMA[1]=0;   // Zero DATA  E% _) G% D! w" r
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
$ i( J4 M& ~1 }8 H% t9 r+ ?; _6 |                ep0_state=4;9 e; F' J/ K/ V6 ^$ K
                return 1;
, G  k( [) \( ?+ A) ?                break;
+ u8 p1 C1 K  L" p) o6 \        }
! A  y8 y* ^4 q9 T        return 0;# d) o3 K7 m9 ]' H7 \- e+ l
    }
* u2 z& y/ A# L( R3 f1 Y    else    // standard& H* e/ I" y4 t0 N& b0 W
    {0 i- {! L( P1 y! U* M1 E3 `) [6 N
        switch(ep0_std_req->bRequest)
5 t# y' b5 R3 n        {
2 W$ f  A* p: f$ }            case REQ_GET_DESCRIPTOR:3 M- u  T6 z0 V) A/ e
                return descriptor_service();, k, h, i7 d8 ^+ }( e8 M
                break;
/ Z0 ?/ h" C! X3 L2 d) b            case REQ_SET_ADDRESS:" r) [; z: p: G; j8 T3 `
                if(ep0_std_req->bmRequestType!=0x00)
6 v: f/ n$ G) T& i                    return 0;+ E* T+ \& \3 y+ Q
                usb_address=ep0_std_req->wValue;
8 Z/ _1 a5 _! v/ k' Q, ]$ v  y8 T                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
& b) V8 U0 J  ]- }* H9 e, g0 V                USB_PMA[1]=0;       // Zero length DATA0* i7 h- f( r& R- s' b/ Z' Z3 A
                ep0_state=4;    // No Data phase
4 ^% ~9 f8 M' T' ^. V4 K                return 1;: k: {2 ]# q* L  B, {; v5 i
            case REQ_SET_CONFIGURATION:
- o$ n$ A# n, x6 d                if(ep0_std_req->bmRequestType!=0x00)
  h( N1 Q8 z# h0 p. L                    return 0;$ z+ ]+ s- o7 ~8 u- M$ i5 f7 d
                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
2 O* \! l9 H- I                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;0 M7 O8 l' J3 k2 ?' R7 T2 w
                USB_PMA[1]=0;   // Zero DATA8 F# o  P# c1 }! x) U* B
                ep0_state=4;    // No DATA phase
% M) p& D9 _8 a4 i- L                return 1;
' c+ z) ?& W! V( h7 i! G4 ~1 D            default: return 0;8 f! Y6 o( ^& w2 T, Q
        }5 M# l$ k, p9 o3 j) D
    }
' J; c. r* q6 g9 u}! G& b9 k& x- f" j8 ~7 B9 y! O
- V6 W7 H& N( v) C- E0 C

4 m* i3 i  o4 F( s
$ h5 I, |* C! ]# f" Y7 P! w
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的
) t! e" w9 G8 i, Ichar descriptor_service(void)
) [% D$ K) ]1 w& Y  n7 _5 s% l{# P- [1 w- ?% c# z2 r9 D
    switch((ep0_std_req->wValue)>>8): g& \( Q7 \9 j8 S
    {
& m4 |4 e5 D% f/ Q) y        case DESC_TYPE_DEVICE:$ e: h( S9 Y  e  Z1 M4 u9 r
            return ep0_preparedata(&DevDesc, sizeof(DevDesc));$ K3 o+ O4 X4 G% J6 j* T5 A
            break;# y6 i8 [  B$ Q6 \% J3 J# O  h; W( m
        case DESC_TYPE_CONFIG:3 I: s; E! i7 C% J" u5 z
            if(sizeof(ConfigDescData)>ep0_std_req->wLength)2 n1 u8 m& i9 R. c. A+ q' p' K' v
                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);
( W% ^4 h5 M9 N- A( x1 K. P1 L            else$ b! ?0 S1 R" y% [
                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));9 e- c/ v1 @3 y% E+ V6 t
            break;
5 X# |* `- Z/ L8 `: O% U        case DESC_TYPE_STRING:
1 @. T) Z7 |" B            switch(ep0_std_req->wValue &0xff)( n! k# r' d- }, P8 \/ Y
            {
: }: p$ e1 w1 n6 P  {3 ~                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));, z4 ~$ `* S  P" Y3 Z5 m( i% z4 v3 P
                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));* L' Z3 l/ G& H2 b: D9 O9 g# H5 ^
                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));. k7 R1 B+ J9 r5 z) f8 l7 N
                default: return 0;
  ~* ?; V: A( x! K            }" R! [; K6 ]/ Q; I; m
            break;& t) N2 f% P# h2 p4 w2 |) X
        case REPORT_DESC_TYPE:
3 Q, o& q, S% t6 ?  w; x3 H6 ^" w. |            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));2 I2 w+ G3 O: n( p; S3 I7 n
        default:/ e8 l+ A1 t7 M, _( y, S
            return 0;5 h: V9 g* w, t' G/ G
    }
  z8 p$ m! k  n7 T. z1 v* i}
/ f- l/ [2 m3 v下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.
6 L+ V9 N4 H2 K4 q7 y( H' Ivoid TIM6_DAC_IRQHandler(void)+ P* j" q4 A( v- J7 Q
{1 t+ U! z0 P! Q) N: }3 X
    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);: r9 Q/ D0 L5 D. S: e* O
  I$ y* A& b. p) a0 j

  L, Z* J& r6 |) k    TIM6->SR &= ~TIM_SR_UIF;. {5 U1 j$ r$ G; K- W1 J5 h  U8 f
    prev_key_state[scan_row]=key_state[scan_row];! `, N$ X( r; E  y1 ^. R
    key_state[scan_row]= *PA_IDR;   // update key states
3 R( \0 e) q+ m    switch(scan_row)
9 i9 d3 Z: i$ m) d. Q% h    {9 ^) G. U$ i5 U2 {8 @
        case 13: // next row PB142 N1 A; y9 {; W3 Z1 q* u
                GPIOC->MODER = GPIOC_DEFAULT;7 T" f! B. d3 i+ d# a
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;
& X# O# d! K5 d& f                break;6 \/ ]! ^6 E4 T3 Q
        case  0: // next row PB15
3 D. [) C( o9 R& g% U4 C( o                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
+ [8 B5 J1 l* b8 y$ x* r4 z0 F                break;
% _6 J' N0 H* W- p        case  1: // next row PB3
% B& u1 E5 j, Q0 j# ?8 x                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;
" `) n0 J. F* o! z                break;
! @8 m4 }& d" a  m; T        case  2: // next row PB4
* r" e" J3 }- ]: B% Z                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;* v6 C6 [8 ~) s8 Z. g' Q* O
                break;
$ }9 }4 L( }7 F        case  3: // next row PB5
. j+ V, j/ {7 }% z                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;
0 D; ?: y- j( i% I                break;) M* C0 F: g9 _% u
        case  4: // next row PB6+ {. M2 W1 _/ w" A% Q' p: _- u
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;
# R& b0 I0 O( D' w7 }0 {                break;* ?8 X3 d) b6 n3 a# U
        case  5: // next row PB76 K2 f1 H; N! [: R
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;
1 U! G: f5 q, M: Y- J8 c( W                break;$ ?5 a! M2 k# U* ?8 X
        case  6: // next row PB8+ ]' |7 I/ t1 O5 c
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;
# I( L9 v/ n  e                break;
0 B% Q8 x& [- ~6 \' h        case  7: // next row PB9' m4 o. V: ^: n" h! D6 c
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;
' u% v# a1 ?3 ^& W- Z8 Y                break;
/ S- w$ P( Z- y; O        case  8: // next row PA8, f% t; L: |' w
                GPIOB->MODER = GPIOB_DEFAULT;2 t. b1 z0 M/ s- r# v
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
6 H) ^: P) O  P, C2 Y* h                break;$ r3 ~' I* }2 ]
        case  9: // next row PA9
* N. ~  l/ o+ g7 i7 S8 ^5 N                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;3 ~5 W* [3 ?1 g& h0 X
                break;, D+ S+ G: m* B8 J! X1 Q
        case 10: // next row PA107 \. l& n8 c' U
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;; U- [# g! j+ s: c6 ~4 ~* G
                break;- I' u/ x1 {$ s9 ]' w/ v
        case 11: // next row PA151 }4 H: w) J( u! z1 N
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;6 e5 [1 Y% c; M+ o
                break;
: u8 _. n( [0 ]& u7 B3 ]* a        case 12: // next row PC13
4 n& |& ~8 F% _2 H7 `" p$ B# _" F                GPIOA->MODER = GPIOA_DEFAULT;* |) {( c& @) H8 _7 d. r" o
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
  b* `; b  m9 R; |+ P                break;# q( d% ^6 n- R# m" `
    }( A9 T% r$ `" e+ @- H
    if(scan_row<13)/ ]+ i  x# ~+ ^) V# L/ G  B
        scan_row++;
0 E; ?$ ~0 b: B6 W& F    else
; U1 r! m0 c, h& k2 V, @  j! ^8 o: o        scan_row=0;
% j) I1 C6 \' m" b: T1 s0 d5 a}1 [! v9 H8 @' P2 S! J9 Q# Y: I+ d

3 l) b/ i7 y3 |# E1 M+ k
# B. w& b0 m, w
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑 7 O/ {& j! G+ F/ Y  a
% |+ N% w5 Y9 g& y, l$ A
扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。. b3 K5 S, `: e. N+ ~
- ^, c3 }% o, F' T, n) I
要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:
$ g( ?, m) o; N9 m0 D7 _

& @8 ]: [. j2 |! [; u% b1 _, L$ p( t. _$ N- x2 z3 a: q5 {3 J
const char hid_keymap_qwerty[14][8]={
6 A# U& L& R  K0 n( s& j! B    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},4 }: H$ b5 t: \
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
9 \! }( L8 T+ ^1 j( p  }) k    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},
0 J; j/ J0 Q/ K; {! Q+ Y    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},
9 _0 `& m! S! }! n7 e    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},
0 M0 f9 r! r. F* j3 b9 P2 r    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},: c* _6 Z5 h, r. [0 N& A
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
. `# }) T" p) u' U: d    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},
8 Q& K: ]( M% ]( j: `5 F6 M+ ]    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
- M5 W% i& @" q2 f8 B    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},  g) ~& _3 a4 Q' N$ l4 ~" y4 p
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},: G  K3 b: h4 y! ?/ o' H
    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},
4 {2 v- h) W2 |/ T    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},# ^2 D% {4 p( l7 g
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
# \6 f/ Y( ?4 z  W};1 b7 G4 C% h/ W4 Y  m& u& }- d
1 a1 m: b$ n3 {, j6 C- d
: F' |. P/ i7 s0 A( r
const char hid_keymap_dvorak[14][8]={9 R0 I  H' }, w' p3 _  r! S& {, v
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},1 L. a9 D% L. p
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},3 C5 I2 A# T9 f+ ~7 S
    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},, Z7 u8 z. Z* K) Y3 `9 ^
    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
- v3 n" q" R! d    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},/ r1 J6 }! @$ J. _6 Q0 v3 T
    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
2 {( F9 p6 {5 |2 K6 @    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},
. h* w6 V0 ]. D& y% w, x4 l    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1}," N' Z; y* ~- J1 c' j3 D: V
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},4 w( ^# t# l6 y
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},9 Q0 m4 }1 Z" I2 a; i
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
' n9 c6 b+ s6 @4 \' l. e    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},
- h4 d6 {. \( D/ z" F3 |    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},' j: k3 q- e1 @
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}0 @/ Z* T. G- Q3 R3 D
};
5 X8 c8 B0 p! V6 |
  }( b* c7 g8 |6 O8 _6 ?
6 a9 H' c# f$ H( b$ K上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
, V5 S3 c4 G1 V# W3 s
$ @4 q, |. h9 ]6 @- VHID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:
$ v8 B2 z. U# G% L' k. m( N8 x

+ }( n; f  v2 a/ Dvoid update_key_matrix(char row, char col, char onoff)& i- T/ C7 A4 Y9 e
{7 r# r3 U* d/ {& Z' L+ t9 m% l
    static uint16_t hid_report[4]={0,0,0,0};
& S( W! P" D; ?4 f: z    static char (*hid_keymap)[8]=hid_keymap_dvorak;
5 E4 ~6 u' G, T7 O2 T3 B; H
# h, R$ D& j# R
8 x" x7 O( M( l. _8 L) b4 i9 h- y
    unsigned char key=hid_keymap[row][col];! f- ?% a, K, Q
    unsigned char *report =(unsigned char *)hid_report;( I( X- q" r* h% w% J( c
    char i;' X- w  M) a% ^* W# }" `

' u% p: H: z6 Y( X

: f* ]# u+ Z/ B/ k; B% ~. ]    if(key==HK_MODE); a* s" \3 [4 C
    {
) S4 O& V2 p  ]: E        if(!onoff)
. p% ]8 ~4 @  o2 O5 n. F        {
, n4 b7 B9 X# d( d$ C            if(hid_keymap==hid_keymap_dvorak)
7 ~2 ?$ N$ ^+ t* ~            {9 m. ~5 X) H2 y6 Z: _/ h" g2 f
                hid_keymap=hid_keymap_qwerty;. L* _5 Q3 F, O9 }
                GPIOB->BSRR = (1<<2);
2 y+ w! l% Z' f( v) \5 g5 A            }% O& E, Y: O4 t+ E. Q$ H: n& y# N
            else
% R& i- p8 ]0 S            {0 ?; M. q7 q' l
                hid_keymap=hid_keymap_dvorak;' T$ |/ z) T3 |
                GPIOB->BRR = (1<<2);
) a$ S- h" [3 h7 @            }
& H8 C" K2 b: l. M" a1 n( Y        }' N7 j2 W9 w2 R
        return;
2 b0 |6 t. k+ G) x4 H( L  ~    }
# y2 |, ~  H7 E+ E1 h; R. c( B( A- H$ ?
0 y" w; ]& ?9 }5 L9 Q7 P2 K
    if(key>=0x80)   // Alt, Ctrl, Shift
- n( {% C: t% |    {
% g* @* |8 C" m' N+ h        uint8_t bitset = 1<<(key&7);- ~1 }; q- b: S' ^1 N% q
        if(onoff)   // non-zero is key up
  p; z, S7 R2 y* P3 n! Q1 y: C; H8 e, A            report[0] &= (~bitset);
! Y# }, n" Z% N: B6 G) X$ g        else# v9 y, e4 [: ~) v2 e1 x
            report[0] |= bitset;
& W( m; d0 ?* c2 Q    }" L$ k8 d- S4 n. i$ i2 r
    else
1 n6 t8 b  j. q1 ^$ a4 H8 T    {! Y  A; H! N8 K1 x
        if(onoff)   // non-zero is key up7 g/ e4 b6 X/ O) i! W, n& h
        {
- Z' C* @5 w: e& J3 F6 @            for(i=2;i<8;i++)
' S9 }' X( A2 R  Q            {
7 Q9 w% B, }3 L& _, b, H  ]                if(report==key). L! y+ G2 X- F" J/ D; G: G6 j- R/ A
                {  T* E3 O: m2 z( W" L! H( x& F$ b
                    report=0;
2 ^4 i" N+ r3 a& A' i0 K( ]6 g9 a                    break;; O5 E1 R5 {. y5 F
                }
, k. U- y8 D* _' K: k/ l            }
$ L( e( k, z' d9 H+ O1 Y$ Y: S        }& ~' |! D2 h% Y& o5 Q" q" D& l4 v/ Z
        else
, Y* x8 |- m' p* o        {
, Y2 B5 `- s( h" M% F; S3 ?$ {            for(i=2;i<8;i++)1 P- i  I5 v  w
            {8 \( X$ M" }4 ^, l7 R
                if(report==key)
8 Z& h0 D7 f: e                    break;
# A. O/ u, J& E( S$ f& o                if(report==0)3 }) I8 V) _- c8 c- f
                {- w, m6 s% z. v. S' V; Y# J
                    report=key;; h% p1 O+ G; ~3 h
                    break;
) ~$ v/ `5 Z2 U; h( B                }
+ b2 ~- _0 C# _            }& T! [0 ?' `% ~. M8 W7 m
        }: [( V: o) R4 J/ W! y5 A
    }
0 r2 F% a/ w7 e& T+ A4 C+ Z& p    for(i=0;i<4;i++)
: H# E3 ?& J4 I2 A- k; ~( d9 q/ e        USB_PMA[192+i]=hid_report;
5 K" c. U+ U+ B% x3 ^! }    USB_PMA[5]=8;   //COUNT1_TX& N# d$ F1 _0 R7 i& s9 S1 s" g
    if(ep1_wait==0)  z3 ]( A# u0 U; U* n: R0 t8 z
    {& x0 O5 v, h4 }. t. L8 J& z' B
        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;
) J' z, V% B4 _        ep1_wait=1;
$ y8 F- y& ]6 M& B    }
! {, j' k3 {' r) ?) k4 @" i}7 R9 e$ h% Z5 p% H& @5 H4 d. l

1 U3 ^. t& C- \; E! m' o' w" u7 c7 `; w- @% D9 U; ^; P5 Y: J% I
完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。' d9 d- t, E* _0 G) c1 B0 j5 Q
keyboard.zip (8.7 KB, 下载次数: 6442)   q9 x6 L; w9 A
9 A- E& a* t4 B8 G
- F* R& B& G4 r" j; [% k/ h

5 E4 J6 B6 ]7 g' A$ [+ C; f
7 J9 `6 I% F4 ^8 n( r& Y! d" K
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘0 S5 \  ~6 I1 ~: O
不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48
/ t5 l8 t$ R- d8 I: v0 E, l刚开始我以为要把打字机改造成电脑键盘
3 y2 Z7 m& ?6 T: J# u+ s不过楼主也很厉害!
% k) ~# `+ T; B  M! z6 i+ ^4 J7 I
哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
4 l3 x, I+ o" c0 R
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-3-12 14:24 , Processed in 0.164110 second(s), 27 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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