找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
楼主: miaozhuang

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

[复制链接]

24

主题

39

回帖

288

积分

vip

积分
288
发表于 2016-9-21 20:34:19 | 显示全部楼层 |阅读模式
本帖最后由 miaozhuang 于 2016-9-21 20:35 编辑
  p+ k; w5 X! Y/ e
  J) l4 h' k- ~0 hhttp://bbs.eeworld.com.cn/forum. ... digest%26digest%3D1
8 U' I; v1 H! l- a这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。1 ?. X5 L. [: ?4 _9 y: `4 d

! E/ C" z% j8 h3 d- i" a在看这个帖子的诸位一定都在用计算机键盘吧。键盘上的数字键是1到9从左至右排列,或者是右小键盘区那样三个一排有序排列,反正规律很明显。但是字母键却不是A,B,C...到Z这么按字母序有规律地排下来的。我刚接触电脑(其实还是学习机)的时候,没在意这个问题,觉得是要盲打嘛,反正对两手的手指头来说,按字母序排列并没有什么好处。于是用多了这些排列也就记住了,从来不管它为什么要这样。其实,PC的键盘键位排布上是延用了打字机的键盘,这是设备演变过程中很自然的一个延续。打字机的历史就要早很多了,我没有亲见过打字机长什么样,而且,咱们汉字是铅字打上去的,和英文打字机方式完全不同。
+ S7 d/ I2 `# D" o4 r
5 `& k; N7 B: ?" u& c
235140i3a36qivqzuvmt5q.jpg.thumb.jpg 9 [8 Q/ D; B( K/ a2 `% O$ p+ {) ^
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。; o5 A1 A& Z3 V: d  G9 p+ o
001734klbyoluenuwz4h4b.png.thumb.jpg : D) c: Y% g$ l$ \3 t
为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:
9 A- b2 L% z! j4 Z# m* m6 M$ x 003625r2agx2f5v922cf2f.png.thumb.jpg 8 d7 w9 H2 j2 P8 r
其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.
7 k, c) t1 ?3 j  o% n- R* P; YDvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:
5 O- z) J  W0 h9 X2 T
005836yvs0wvovwsssgd3o.png.thumb.jpg 1 {, H; V( P, H+ o
Dvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。
' u1 W# Y5 s( C( E( H6 f: F0 v* `3 L0 S6 |, Y
我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。1 n' X# |9 E  o

5 B( I7 X; h3 C, B: ^2 j2 d到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。6 S3 I6 E- `. y1 P% h1 e8 k: D% U
; k" d1 y2 S& g; l8 L! d
机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:

/ S1 a" {8 N1 `1 K 123206vx24rfxfp2dfrpxg.jpg.thumb.jpg ! i0 ^# @) D4 b- `, F
----------------------------------------------------  分割线 ----------------------------------------------------------
/ W) f( i3 `" E8 h. |: u8 |0 Z& v  Z) Y* s( ^( l. r7 w
先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

6 X6 [& I" ^" w 020011osionbunl4ui44vi.jpg.thumb.jpg
3 D& x9 b/ W. _轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。9 X, x+ r4 c2 g- f4 S* R' N) w
020017j8ycmnv7788bqv52.jpg.thumb.jpg ) F- {/ S. J6 q  K6 ~! }
特写,80C49% N9 i6 p" K* Q8 \" r9 @
021040oujzuvtut6iujtvz.jpg.thumb.jpg
: W( ^  Z9 ]! S2 |! TLED部分,使用了一片D触发器锁存指示灯状态., |; _. j! N' I
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
5 z: }, V8 g+ o( P! l: h暴力破坏,将80C49拆掉
5 c2 p3 W' u% B5 m# H, D6 ] 021113e48qq98vyohvhzzh.jpg.thumb.jpg ! d* V5 t( i/ j* ?- C, i  n
拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。
1 z8 Q/ _4 }8 ]2 ]$ {# x. ? 021125nc9az6dj33rlds2r.jpg.thumb.jpg : n) H7 @9 n9 d1 n$ r
焊好元件后的板子,准备替换80C49$ d/ D+ m; B- h' P$ c
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg
0 \: |9 [5 D3 a* `& x用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。. i2 ?- ^% }+ F# O. n7 X
021104shifhnrqbr3o5nlo.jpg.thumb.jpg
6 s  G( f) p" c# T这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.
( E, L) u  V3 N; n0 Y 022003ym1p9u4ug40280uu.jpg.thumb.jpg
5 r: y. ^" d$ U# @/ n; V3 H开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。* E% W! j" ^7 m3 z; @! S
023313kt141q9qajtol7ma.jpg.thumb.jpg / H* I. C# p9 _
我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。
3 M/ K) v: b' g; |6 N  j- z 023322nt7l5xb3ltttkltt.jpg.thumb.jpg
4 L* o. f% I7 B) U' Y( x2 a4 P
主键区键帽就位
3 k4 U" _8 l  D5 V9 e& Q; { 023331hin88e8wkrwzwikx.jpg.thumb.jpg
. T- R0 H9 N2 V# ?2 Y, G" O4 w编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。) Q8 z: A6 @+ e* L
023336wjzlgopugg1jyy79.jpg.thumb.jpg ) B# p: H+ Y7 V4 k, c7 e- U6 d7 ?
最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
" G% L/ }/ Z: Z- [- w 023341sffu4j3g2323h6fl.jpg.thumb.jpg
  f8 @+ F3 [% {' u9 a$ `
' l/ H! O# H5 B$ f
----------------------------------------------------- 分割线 --------------------------------------------------
5 I8 p9 }: t% j. a

- |: k& l$ j$ _+ w3 o8 I8 ^) C

6 @; I9 I5 m  B* T0 J% c& P
9 h1 y2 P. j! ]) K. F; W: k
  m, N4 h) }7 a" N& P! f/ T7 r) e4 O2 h6 D9 X8 i" f" V

, Q$ Z, W! d+ q3 j
% i8 s0 f6 k4 I. e
/ x% ^2 c( M; G" i! C* d5 N+ o6 E; {6 C7 C

3 W# H. r& q* {5 @
2 _. d  \  U, f3 G# t+ D0 o
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。
- I( @! O9 x6 M4 c# F) ^2 T80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:0 G$ u* T* U7 r( ~: X$ W
104025nzibm2rmiomhyirm.png.thumb.jpg , q! v( ]0 r, M. a

0 |8 i4 t. \' \2 |7 \6 Q其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)! w, J1 v! [/ [8 d
105004zkrez5houvkkznko.jpg.thumb.jpg 3 z7 G4 I! D; \8 B0 u& c
3 b) F6 s+ l; q! \, D4 s
扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。1 H* ]- A, F7 I$ F0 ^( U1 M" n7 ^
+ v9 X; t8 d7 C
这是我设计的电路图:
! p9 Z" r. {. Q* W# d, u 110344ej2z2oo2rflo7oe7.png.thumb.jpg ; G! l. f' G1 e* o4 ?3 q
5 N) X' u4 p: ?- C+ t6 }& c  H
PCB Layout:
# H& M: f/ ]! C/ k 110847jjbjvt34vwt3v5bb.png.thumb.jpg
. S6 _' k4 l! [! ?- K
) C9 N- w# F3 S
不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O.
% m3 r6 u9 l7 G# E5 r& ~! w2 q5 n: d) _  T/ j1 M/ z+ S8 S" y1 j
8 [; D5 g& Q, x$ d; _. I! j8 k2 N4 g
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑
' m0 Y; X& i) C1 E7 g7 O
% D5 ?( K7 T. {软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。& y6 f1 }$ Z; u
* E6 }) c0 d2 V
总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:
; i1 m8 ?$ `# f4 K5 w 113818pmrfsb6z0byt6t06.png.thumb.jpg
6 Y' h* J$ N: T+ m2 c
- u; }% ]( h: S
其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)
# I* t/ x8 E1 \9 X& r- g
" Y2 b% x+ q- g' ^5 |) yUSB的中断ISR,bare metal哦$ U3 h8 |3 t/ c. _  U" n

9 R! w. B0 R- j- `4 Ivoid USB_IRQHandler(void). Q- r1 q4 m5 O4 b" e4 Q5 |0 B
{
( t) i, u! z( i% i& b6 r    if(USB->ISTR & USB_ISTR_CTR)
4 }1 n' N6 a9 v    {& j. A  [6 e0 C4 W. `
        if((USB->ISTR & 0x0f)==0)   // EP_ID==0/ m8 {: T/ Q5 ^- A$ I
        {+ G" z/ {( Q( V5 |( h
            switch(ep0_state)
1 h1 a8 ~5 j8 v            {
3 j4 X1 u; Q# U9 p6 B                case 0: ep0_state |= 0x80;
$ F; a6 [( i: k                        break;
# q) Y9 f# w/ C; J6 ]. E$ f$ [/ n                case 1: if(USB->EP0R & USB_EP_CTR_TX)0 M( m4 R2 ~: @6 K! ^  z7 A
                        {
3 h! G: t. I: ]& J# i5 @5 j. U$ |/ A                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;$ a; n" a9 A; b
                            ep0_state=3;! u, W; K( j' O' K
                            return;7 A5 D+ E4 {, {# G+ F
                        }6 o$ K- |4 p: y/ `! L5 J4 x
                        else7 _3 j3 f6 `/ S* e& W/ Q
                            ep0_state=0;) c4 r6 a7 ]# n, \, ~
                        break;, G! ~& e" |( G4 t5 A2 K
                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet
# Z2 e: G, B/ h  \                            ep0_state |= 0x80;
3 ~0 j+ n- A7 g3 o8 Z                        else' S. n& F: P+ G* E% l
                            ep0_state=0;1 }0 B: ~% m7 e( d
                        break;
4 n4 t2 C+ ^4 v                case 3: ep0_state=0;
! M9 I; S& S! F" n8 _6 P; r                        break;+ }. ?& W7 {$ b" P
                case 4: ep0_state=0;
: E1 E& q% C3 t$ L# }- k6 J                        break;
  L3 @* X! X7 ?6 D' j- S% P                default:ep0_state=0;
/ b) ?* d4 Y9 C: H& |                        break;1 V# f8 l" h& M3 W4 P* S! f; I& Q
            }
: ?4 k; |% m1 @; ]9 e            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag4 P: s. {  P/ K! Y; M% M
            return;' _; P# k% H5 d- s( X
        }
6 \( s! ^* [- A8 _( a& D        else    // EP_ID can be 1
3 |/ N& L. V0 v# P# {8 c" q2 }        {
5 `; Q: U" I# i( h, w& {. g            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag- Z! @" e' r6 u" c, [4 H/ N
            ep1_wait=0;- V6 N$ B' V5 ^9 q
            return;
6 J2 J' v7 X$ [& Z0 }        }
- D: p( a+ c! {, Y! ^    }
9 D) m  [& e, B* n    if(USB->ISTR & USB_ISTR_PMAOVR)
: G7 h9 ]" K. G0 c    {% u3 E8 n, {; }1 x3 H8 H2 r. c' G
        USB->ISTR = ~USB_ISTR_PMAOVR;, G$ U) _; p2 c0 ^6 w; @3 _
    }
* o9 I7 X; A' X- C2 w$ N. z9 P% ~    if(USB->ISTR & USB_ISTR_ERR)* b; C, l" W1 J% [, C4 V
    {
8 a1 f' ^4 Q; i# [/ Y8 J( _        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear
$ S' B# {3 l, V" L    }
, b; @$ q- \7 k- t4 t6 o8 B    if(USB->ISTR & USB_ISTR_WKUP)
5 \0 v1 B6 b8 K0 {8 x    {
( ~! ~' A: q( Y2 J4 h0 q$ a. Z: v        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend1 u. J' M% V4 h5 A1 K: f' v: X
        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear7 c. m/ t& I; k: e  A! m
    }) J7 H+ p1 K3 b. B
    if(USB->ISTR & USB_ISTR_SUSP)4 b1 t; L; N* D/ d
    {+ f$ W9 H, _0 b$ p/ v
        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend
" C; V$ ^# [9 M7 h* S) E- b- a/ @        USB->CNTR |= USB_CNTR_LPMODE;   // low power5 D2 g4 u+ F' h( L: d
        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear
2 \- A& {* s0 }    }. b3 }* C5 v1 y
    if(USB->ISTR & USB_ISTR_RESET)
- g& _) S+ C7 e8 L7 Z# U6 T4 B    {
3 m* K! w& l& p' Z/ g# G6 f, U        USB->BTABLE = 0;    // buffer table at bottom of PMA
8 j4 H: m$ A$ f! x; [( p/ c1 N        USB_PMA[0]=128; //ADDR0_TX
( _1 u/ k/ l$ H        USB_PMA[1]=0;   //COUNT0_TX* x) {, K( O, Y2 v$ v$ K7 |: e% v
        USB_PMA[2]=256; //ADDR0_RX
1 s" _" Y9 E, B; ~6 N% e8 e% e! K        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes% J; P7 F! a0 j
        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;
: r3 Q3 i& j+ T  N: z' S        ep0_state=0;
! n7 b( D% Q4 b- E' ]4 I6 {! V        USB_PMA[4]=384; //ADDR1_TX% ?, g4 w) w9 ]* f) m* K
        USB_PMA[5]=0;   //COUNT1_TX* h9 w+ g; ~' Z3 O: I# o1 ^7 a# T
        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type
9 t' J7 C! x1 K' A: D+ P        ep1_wait=0;3 P1 H) `; l$ I% E  K
        USB->DADDR = USB_DADDR_EF;      // enable function
. d# S: S0 l! U        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear- U& e$ K! L' R1 c7 Q9 t  \
    }
, V" W+ m) X9 m; T2 V    if(USB->ISTR & USB_ISTR_SOF)
* I7 m% q5 M5 s/ T3 I9 K) r    {0 k6 @# f! o6 w0 H
        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear4 F, ]9 w) `4 _- p1 D$ V6 X
    }8 R+ Q. P+ E# Y1 a  h' m
}5 u* T! G9 N0 I- R( q
  d* i! b5 j3 Z6 f3 z
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。+ w7 _& G: m. X5 r

" C) t5 [# E8 [& A2 c主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。7 g6 H# W( F* \7 I, \  ?
$ a; j+ Z; s6 [3 K' p
    while(1)
8 F  _  ?+ {+ m+ |$ R+ `    {
& s3 K: G; W' t0 {        static char row=0;
' R# k3 f; c( t9 Y" Z4 M7 D* U        __WFI();; m" ?. _4 N* J' h7 \
        if(ep0_state & 0x80)    // request data processing
6 {( t1 T$ C: k; {        {
8 }' B" y( a' t7 a            if(ep0_state==0x80) // SETUP phase
- E! A/ C# S# Y/ s% l  m+ h            {$ e* \* s2 Q: G! j$ k
                if(!setup_packet_service())- }) y/ g% r3 _5 E, M& \+ x0 j
                {
" L$ P& }% d7 A- q% X/ T                    ep0_state=0;
; C1 k) b# ?% }1 M: l1 X/ J                    // not supported  L% L7 I* s9 C8 d9 ^/ ^" C
                }
. ~- j" Q) ?) w7 B/ C8 g+ v                // ep0_state should be set to 1 or 2, if processed
' R+ X& u- x( V6 a1 e8 ]" e: L3 t$ N            }
! D* {0 r5 K: M! m4 x8 T            else    // OUT phase, z1 a: E# T! V& v; c& X: W
            {
  G0 G# l: R; ?! q: N  h' `8 T                // process data4 z7 @: Q+ g' v5 X0 \! m& j) c8 q
                show_LED(*(uint8_t *)(USB_PMA+128));3 ?$ o! q) o9 D/ w- i
                ep0_state=4;
# w; X) ^9 s; c2 g  C3 I6 v4 A% j                USB_PMA[1]=0;       // Zero length DATA0
9 T8 X1 {* h9 e8 ^/ M7 j: ^0 m7 b7 S                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
& }. ?6 A& `  l1 Z- A7 ^            }
8 _; X& ^; C1 h4 l        }
* l; {5 l# c% _' k5 T4 C        else
  {% z. w1 @" e- \# A        {6 H# u$ _( E+ i, Y5 Q4 b, h8 ]
            if(usb_address && ep0_state==0)2 O+ Z, C9 {6 X
            {' y  q7 v6 U9 h, p" }& p9 I5 C
                USB->DADDR = USB_DADDR_EF|usb_address;# N6 t- t. k2 q: j1 D
                usb_address=0;7 m1 D* k9 S, P. i
            }& x1 U' n2 v- d, X1 U% N2 p  Z7 {
        }
" m  y9 m' L& {+ n9 R        if(row!=scan_row)   // new scan line
' G5 ~) S2 ^# t+ i% L- p        {
1 l0 s" I0 M3 k  \, T            if(key_state[row]!=prev_key_state[row])1 y; F& }' j/ \+ e3 N0 \9 v
            {
  M0 m$ L) ^9 w/ b+ K                uint8_t test=0x80;, w; W6 [% a) x* J" L3 e. H$ A. s
                uint8_t diff=key_state[row]^prev_key_state[row];
1 g& a1 Y, S8 F. n# g" q                for(i=0;i<8;i++)6 l' _8 A7 \+ q/ c1 a' u
                {$ X; |5 T7 P$ c0 z: Y
                    if(diff & test)
: L  i- @) @) l5 H( v7 n                        update_key_matrix(row,i,key_state[row]&test);
8 h7 t# d5 K& o* R/ ?' Q' h& T6 n                    test>>=1;
3 A$ a8 x2 R; H( }- k. H, Z" D- v                }9 a" `/ i/ Y  d, N
            }
8 r& B0 g' R8 [( m0 C1 Y+ C            row=scan_row;
2 N! B9 P4 i: j2 a: \        }
7 y7 m% G& A+ W    }
9 V# d* P# n/ \' h6 V/ x# }$ g1 H( n% {0 k1 Y- a

2 p( q+ R. r- X( v$ wEP0的控制传输,把用到的请求处理一下
; b9 ^, j/ X$ n- ?- [& Ichar setup_packet_service(void)
6 j; D" r9 y4 L{
5 \0 s, n( E8 ~; k# v    if(ep0_std_req->bmRequestType & 0x20)   // class-specific4 v5 J8 L# n8 M
    {, N$ s: I5 o3 \; p: J; J  m( d& ^  {
        switch(ep0_std_req->bRequest)$ g5 R) K% U* L4 s7 |
        {
( s6 \! ?# e4 ?1 s% j9 Y            case REQ_GET_REPORT: break;5 |, W0 x3 T2 d9 j! Y: B1 ?, U
            case REQ_GET_IDLE:$ a+ |3 }9 R* y- d0 f. g
                USB_PMA[64]=0xfa;   // return 1 byte  M) F. |8 [* K  q& S1 s- J
                USB_PMA[1]=1;
# f$ ?) a  U8 C; N/ Q9 ^! w1 C                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;) m3 F* Y+ C. F9 Y
                ep0_state=1;
9 S( V4 ?; b+ J' V* h* s+ P                return 1;5 O$ V; Q9 i9 O% r. I2 A
                break;
' D' H! r4 v  Z$ |. M            case REQ_SET_REPORT:
3 N( Z0 z! y# H3 |, q3 ~                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;# B: m2 x1 y/ P8 j. }! X. z7 N$ R
                ep0_state=2;
% a6 w1 e4 X5 \- b1 n0 |                return 1;1 r, Z* P7 o& u: n; F
                break;
" `* ~+ o' Y# n% ~' `/ c% P# Z            case REQ_SET_IDLE:- M! [* {! o6 b# g
                USB_PMA[1]=0;   // Zero DATA
! s$ v6 U# p- e% @* G' ^                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
3 k6 u$ b+ `, d                ep0_state=4;
& Q8 Q; A- ?: A                return 1;3 g# z& n8 Z; ~4 D1 T9 c
                break;+ C+ k' \& F2 f
        }% e0 v$ {8 o% T' u
        return 0;9 }( s% C1 @- V& }7 d/ p- L
    }9 V8 a- W/ m6 @( v" r
    else    // standard
: R, I& U9 @; s    {
$ T( [% {/ Y' j        switch(ep0_std_req->bRequest)! a7 a4 F4 b: I  t9 D7 |6 u
        {" q8 w! Z: ~& e! l2 h: b
            case REQ_GET_DESCRIPTOR:
" v' u* `% {  C$ `; [! K5 t                return descriptor_service();
3 [) C7 F- ^5 V& a* E( M) Q; U                break;
2 [$ K( _& X* R# t            case REQ_SET_ADDRESS:+ f6 ^) T  M# K; l# |& o& V) R( {
                if(ep0_std_req->bmRequestType!=0x00)
4 [- l: e: p( m5 N) N  U" p                    return 0;/ _' x. |& ]; w1 W
                usb_address=ep0_std_req->wValue;
2 W# d( B' R# R, m, q" Y! K8 u                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;8 p9 B0 P- K0 b, m  n
                USB_PMA[1]=0;       // Zero length DATA0
$ m* b- R6 E* O6 f- [                ep0_state=4;    // No Data phase* i* u) `$ C4 v% R
                return 1;
4 q9 A- W: T4 A3 t2 y6 Q            case REQ_SET_CONFIGURATION:9 X7 e- v5 P% r) N) k- p& y5 S5 ?
                if(ep0_std_req->bmRequestType!=0x00)
- d+ l# e% }8 R; R. J! |                    return 0;9 K' D1 @+ s) D
                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
* i" m' b, d0 q                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
0 M  T/ ], D8 Y: k# o, Z' b! a                USB_PMA[1]=0;   // Zero DATA; x1 F& A6 i: U0 D3 V4 Z( d1 i
                ep0_state=4;    // No DATA phase
3 g5 a' W6 [5 h- A                return 1;* T0 [8 `' T/ b
            default: return 0;
* s6 T" h$ N& j2 c        }
" h' t% Y% E6 G  ^) f! M9 i  C7 w    }
% @0 L! @5 [% _; d0 ?  k3 O$ U, c}
) e) d& r7 X8 Y% ^* t. G  K% T# f  c' V( f% b3 Q  ?
4 {- p3 O, \+ U# z" ^; K/ z  i) T: E
8 E& H; D5 |5 E5 c/ X
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的
1 Q8 P4 s2 R* X/ H7 d4 Z! P4 Ichar descriptor_service(void)
$ y/ M4 N& ^$ b{6 {# m- o& c0 ~( e) F0 B
    switch((ep0_std_req->wValue)>>8)0 Y. n8 N6 u: L1 m: w
    {
! g) P7 b4 H, S& [        case DESC_TYPE_DEVICE:
- g& r) M. h  f9 l            return ep0_preparedata(&DevDesc, sizeof(DevDesc));) |3 e" i+ T7 t0 F+ Q7 X7 X% _
            break;! }1 |: b7 ]& J8 S* g* |
        case DESC_TYPE_CONFIG:
6 ^/ E$ `3 i. i  E: Z            if(sizeof(ConfigDescData)>ep0_std_req->wLength). A- m  \8 z) T) T+ @# {
                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);4 D. \# B9 n% t! U- b( j: ^
            else
: p6 _  G# @' R/ B: f- g                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));6 u" _: ^2 G6 w# G  x/ h: `9 w
            break;
% s' x# ?* M% z5 d3 n        case DESC_TYPE_STRING:
3 G. y/ {( O  N            switch(ep0_std_req->wValue &0xff)
4 |3 c; J, F% s6 t            {* A& m9 F; [. R3 Y% i! j
                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
, u7 w: w5 v. \* M# T  \                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));
0 r  H' ]* T( e3 U9 V                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));0 K) q: Z! S( \2 |; _4 N
                default: return 0;
  w+ k+ c! V- h            }
6 X: E% j3 h9 e; `# u            break;
+ v( U! y% [$ u" v; u3 ?: e        case REPORT_DESC_TYPE:
. v3 r* m7 Q6 `+ N- I) D* z! N$ u            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));+ p" P! ^, v) B2 y
        default:
' T$ Y6 R+ y& I3 @            return 0;5 ^% x+ K; p: \( Z! h8 s
    }2 Q$ Y8 I: h* d) O7 @9 H; [
}
, d  A: e. u) v下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.. q( h# R3 [9 V. r6 M* f, T2 Q
void TIM6_DAC_IRQHandler(void)
4 c, I7 A! j! Q- d{2 r: Y+ `/ |2 R2 G3 \/ M
    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);( P9 q% j( W9 A3 F
$ r' F( }( d$ N9 a

  A: s6 N4 c( w) v) L! R3 o' M    TIM6->SR &= ~TIM_SR_UIF;4 M2 t+ a5 _4 b! }# k' I
    prev_key_state[scan_row]=key_state[scan_row];+ J0 @3 ?/ k* j- Z# M! R$ r1 K
    key_state[scan_row]= *PA_IDR;   // update key states/ `1 o! `3 `* }2 ^+ f% e8 u" C- h
    switch(scan_row)9 g# h1 {9 k, B& e' l% e# ^
    {
; A3 w2 Z2 ~5 ^( A- A        case 13: // next row PB14
. r% C+ R6 u7 q& e+ Y' n! P  H                GPIOC->MODER = GPIOC_DEFAULT;
5 m# e& [. |5 c# X: D4 Q) L                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;
2 S/ R2 g$ A; B. K+ m; E- q                break;
+ X+ a. [' t8 Q5 e        case  0: // next row PB15* P( D5 u) r% I  |, @8 w
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;+ t) W- B  l% f* l5 ?+ t
                break;: s4 X! s- _- s6 p  g9 Z% ~# X
        case  1: // next row PB3
# M& g3 [! |( ^) Z2 P& d                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;
) _4 f5 i9 l) |. J! L                break;
4 n. q+ h3 M; G5 |        case  2: // next row PB4
# J: m6 L- L. p                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;$ U! y  j+ {8 E  |
                break;/ ^6 O! C1 @, L9 I8 I5 `- m
        case  3: // next row PB5
1 f0 D0 C3 i1 A% {3 s" Y" K1 C                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;
) ]" D" {8 P: {. G8 r1 p                break;
# @! `/ c3 E; k& ^3 A& k7 b: E3 W        case  4: // next row PB6
2 D# B1 m# |: N                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;& k. t: U/ h; y# q! v/ F
                break;/ r9 M$ v  s1 I: |) V# h# V
        case  5: // next row PB7+ X9 f* m) V0 t5 r( `5 o% n, P
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;/ Q. Y. P+ s; @6 v& Q+ t  ^! H
                break;
5 b7 i% K2 K% I/ N& o        case  6: // next row PB8% y: s" x! [+ x  F
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;
9 z0 O  ]7 t, d" Y0 s4 ~                break;
! m% v6 j8 f6 u6 L( L        case  7: // next row PB9
, o( N$ G) x! _/ A$ w" u% U3 S                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;
) W  \9 X- f  ?9 A. D  g; ~                break;" N* v! a# o' r( k4 i
        case  8: // next row PA8: P) D, x) Q+ b2 Y! u1 K
                GPIOB->MODER = GPIOB_DEFAULT;
& H& q+ \! Q0 ~7 C; I2 F6 G                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;9 e) O0 ~2 Y' @0 y  i( ]- z
                break;  K# N$ D+ n3 q; l1 [
        case  9: // next row PA9* X$ _, A6 g- C- M$ B+ t+ o
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;
0 t: [* {$ q) u( w                break;6 }9 M5 j+ l* n( m/ F3 K
        case 10: // next row PA10
9 u1 N4 M( b6 R! D$ s                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
  a6 j2 P& d5 T                break;4 y' m5 k! q  g
        case 11: // next row PA15
' t! @0 n: r; u( J. T                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;5 `& U* N: l! ]+ y/ i
                break;
; A, ^2 _+ A2 ^; l9 R        case 12: // next row PC13/ [. ~5 F0 K2 \& {" d/ x
                GPIOA->MODER = GPIOA_DEFAULT;  t6 ?3 d$ c. t( e$ y0 F) m2 U2 G) T, k
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
9 K, W# B& [3 v  q  m1 m4 `1 Y# g                break;% X" m; f1 P+ O5 i
    }& q  ^3 d4 e) q. W* l# [
    if(scan_row<13)' a, s+ F* d* ?! C& D* N8 @
        scan_row++;
0 m5 d% u! s1 l( f6 @  T    else4 H% U& F0 ^/ h9 _' d& _# u
        scan_row=0;
# a, y8 e/ u$ `) g+ m1 E0 }}
9 I4 W6 u2 ^2 u/ |8 V: {' S8 k6 i

3 V: Q* {" u$ x0 p. E' |3 `; X& H& l
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑
# J- [/ g7 ?5 j7 g$ r4 U/ d9 N( T; A: g( I5 ?4 s3 Z6 q
扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。# q5 Z4 ]& w/ \+ i

! E- W5 f/ m2 u+ g, V要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:
" n; p0 |0 d/ I, c/ q1 k* K) H
- H% G7 u( T' H

0 k7 o* n' p( gconst char hid_keymap_qwerty[14][8]={( d9 V4 S5 x( f
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},0 L7 g; D+ Q( v
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},- X- \. W. p" j4 ?1 l1 `3 Z. F$ n
    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},( v8 G1 B. s$ }' q# x! A- R# R
    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},# Y5 w  X5 ?8 x" A
    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},0 o+ u) l. A# _
    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},4 M, b* p# v$ d3 }. ~
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
; X4 t' K( R% v* I6 r    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1}," A7 H( N# B. P5 y3 u; S; ?) C! X
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
" ~& e7 B. u- o7 T4 z  e    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
3 E, K" C8 J. @& }% g% z    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},* C2 f; B! ?. R/ i# X1 ~+ C
    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},  d) m' K2 d  H4 F3 ]' O& Y
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},2 U# ?1 ~, \& Q9 c
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
4 w6 A, r5 l( K; k};3 g- t2 U# c( `" T5 r- y

. M8 n+ S+ \, y8 c) S* _2 H$ N

; u0 [) a* ^7 T7 ]9 o! O+ N! Dconst char hid_keymap_dvorak[14][8]={
( `* X& n0 c& K) |2 M' V( b7 l5 Z    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
3 Y: l) k3 M) E! T* l1 O    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},4 k$ A/ J9 x( r
    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},
  j9 w* c7 ?  K    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},# w( f4 b3 B/ O: ]0 ]0 h
    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},- ~- [  i3 j6 m7 o+ Y# Z9 z
    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},, ^; \+ u3 f" ^3 ?
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},5 Z0 Q7 M" S1 W
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},2 G+ M2 T* N0 p
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},: T$ z3 I  C8 L1 u
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},4 T; C  f9 O3 p( [1 {
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
/ A' h) R2 O; Z3 K& Q8 _. I    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},
  p; a% ]! t5 B1 U; M    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},
: h% R0 M7 _6 e. U, i    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}, h) |# Y* M7 V5 d2 f: m
};
% J: s# c; j0 h2 F/ f# F
7 \5 g6 H! w/ a1 m; m9 S, I3 f$ ]. O+ A2 r) c9 b& P
上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
- P: N- |! m( F+ P8 n6 U6 p* a6 E% K
: U& w# w( a2 a; fHID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:

8 [' k# T; i* s5 e5 I& f# _  g9 x- z! w7 K+ {
void update_key_matrix(char row, char col, char onoff)0 ~5 `2 j0 [* K
{* A3 W# w" i0 t8 G2 b
    static uint16_t hid_report[4]={0,0,0,0};
5 S6 m* o( E) w$ u3 d; R3 ~% H    static char (*hid_keymap)[8]=hid_keymap_dvorak;3 S( n/ x. a* \  ~% \
7 @8 S. A# L, T3 E8 r. c& [

7 f* A4 J7 c3 [  d9 u5 x: O    unsigned char key=hid_keymap[row][col];0 C) S+ @( s$ v( j0 I. _
    unsigned char *report =(unsigned char *)hid_report;2 q8 q0 ~+ P. S# Z/ A1 G1 Q
    char i;1 t0 ^$ R6 N' d1 B5 \

, F+ j* r' A' h) X- N5 v1 f" K

6 L; Q( T  _7 R7 K( a  g    if(key==HK_MODE)
5 d; {$ D. K7 y9 R    {9 l! Q1 Q" \' q+ d9 w
        if(!onoff)
% `5 M; a! r8 D. j0 @        {
% ~& ]4 j0 M* s            if(hid_keymap==hid_keymap_dvorak)
, a9 I$ t6 A( j) G7 r: C( x4 o" {            {$ ?  s3 s' J3 z/ c
                hid_keymap=hid_keymap_qwerty;
8 x6 S4 [5 t8 X! N$ t3 v5 I* H                GPIOB->BSRR = (1<<2);
+ G9 H1 P  _/ ^& L( |" u5 e            }
  H8 G9 ], }9 G! q            else$ i2 I  V* q( Z" X
            {
; \4 H) G5 D1 Q' a$ z0 J9 k                hid_keymap=hid_keymap_dvorak;
# {' I: Z& V5 o; ~; i1 ]  Q/ V                GPIOB->BRR = (1<<2);3 O& J$ M" W6 z! [# H# e( L
            }7 W1 _) r7 M$ ]" k0 ?
        }
; R% e9 O2 T; l$ Z2 H4 _4 S- i. W        return;
3 m' x2 @) @, n5 r' [    }1 i# t; V# R$ D- ^' v5 W6 ?

5 Z- x9 j* _" K4 E! ]' f
* u6 R- Z: T& b8 c6 D+ d
    if(key>=0x80)   // Alt, Ctrl, Shift4 w( [" ~5 }+ K8 J" X( _# l! c
    {
7 x5 K! d, W  W5 N4 E) ^2 c        uint8_t bitset = 1<<(key&7);
1 d% L7 `1 P( A. Y7 a. O2 J  i4 A' I        if(onoff)   // non-zero is key up
( _4 Z' \: O8 [, {& b            report[0] &= (~bitset);
; g; K8 i# F4 R9 Y# ^( J3 h6 d: C        else& U- `/ W& y. y( |2 S; ?
            report[0] |= bitset;
! d; v. ^; Q& A5 G' R/ G    }" w5 B! ^) T/ v
    else
1 j# O; P( {* l9 G, s9 ^3 u' r% Y' d    {
3 S! G( ]3 ~! b0 n7 S$ e        if(onoff)   // non-zero is key up
* F% j6 o: _! n1 O        {: ]  f- U0 |& ?0 I- g7 j3 z
            for(i=2;i<8;i++)3 n1 e! k; u5 R# n7 u  |
            {4 G  b4 c* `+ h& n5 f- p( M
                if(report==key)
  C/ q) U; k8 [$ i% }: K) r                {8 |9 C# N% H+ p/ W9 G
                    report=0;
3 c# G9 L; k' ~; v5 ]                    break;- y" ~0 S5 i6 B( g: K- f
                }
! [: J! {  o6 j9 _: C  q            }
' _/ Y( z$ Z4 {# V        }
' `+ r; K! _# ^        else3 A6 o' }& n& M! _5 p$ M9 Z# _
        {, v. z4 F" e2 Z8 c
            for(i=2;i<8;i++)
  ?9 n& x! b1 P) y, O6 C            {
8 Y9 m/ }2 e) ^# J; g. _1 k                if(report==key)
( z; k% ^% `  d+ D" `                    break;' O9 ]! g  B# o% Y1 u& w# e5 c
                if(report==0)
4 Z/ C! q3 T% Z# M: h                {* X7 R' _0 ]7 \  p1 o  m* h0 W# C/ S
                    report=key;
) j; n4 M3 x3 u+ l0 I                    break;
, Z- Y# d+ z: _$ W1 @2 H7 w3 N2 F# D                }. _- }0 L3 T1 r
            }
& B2 D, p, D# d        }2 }; B8 H* U) @' N- x
    }
( s$ f; }0 F/ B" N    for(i=0;i<4;i++)
, L( g1 x: K7 |6 q        USB_PMA[192+i]=hid_report;
" {/ x6 ~+ K/ A1 N3 G( E  B, x    USB_PMA[5]=8;   //COUNT1_TX# ~. \+ L) a2 o
    if(ep1_wait==0)
8 t5 ^+ ^2 J  U' c: @9 u" h; T    {; H4 Y; m  g* X) i  H) F
        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;6 X7 _. V* N4 U7 m: p  c, l
        ep1_wait=1;
- V- e4 J+ @: C5 r: ~9 X# p    }
. o- P+ U% ~3 C1 T" y7 m}  Z4 B1 l- F7 d0 _& G
: r/ m3 q; P2 x/ z$ E
' `/ K# N1 N: o/ c9 c( e# X
完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。
. H/ p  Q5 B. s, r+ J keyboard.zip (8.7 KB, 下载次数: 6694) 2 R% J* `  M, Z- a2 B
! Y0 e$ n2 y; a( w' H% y& B* l6 L

. W5 g$ \! f$ L$ p) `
6 Q( m3 ], L3 b, Q! U; u' D
, Z1 G( w2 V4 U6 x4 G( K' {2 W
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘, Z$ J0 C5 H/ k% y( G
不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48! }/ C8 K: V$ M2 Z- V  F1 Y
刚开始我以为要把打字机改造成电脑键盘3 g. c& `9 A1 q; ]
不过楼主也很厉害!
, B) {# X2 Z0 M
哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
* U5 G4 [6 V, B7 A6 s
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-10-6 07:56 , Processed in 0.206058 second(s), 23 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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