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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

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

0 q) e- Z/ n% w6 O% [' W& l* t. a
& a1 o1 g  o. e8 X6 j5 ^5 a1 q
235140i3a36qivqzuvmt5q.jpg.thumb.jpg
" A) w; `. o9 \7 N' N1 Q
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。1 O# I9 Y2 [6 d7 g1 Y: h, V
001734klbyoluenuwz4h4b.png.thumb.jpg " D% z! s. H, p$ I; O
为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:8 X, b6 Z- @, X. v- ?+ p
003625r2agx2f5v922cf2f.png.thumb.jpg
. l2 `# E# k! t- p2 K* J其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.7 T+ m! B/ G6 T4 D) V' p7 X7 P
Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:
1 C: u, ]9 a* i
005836yvs0wvovwsssgd3o.png.thumb.jpg " ^( k& s$ }/ B2 ?, V' E+ ^
Dvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。* s7 C, u: Y( w; m6 U& i

( J9 @5 Q  [; D# U我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。# s/ O, l7 [" {* n% ]# J0 M2 h* P

/ v$ w+ J  `2 R( G1 D2 b1 }到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。; Q5 Q# C# G! K" f0 d
2 J( l. n/ @, J( y) M- n
机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:

* L& M+ v2 W* ]& k% e- R6 h  k9 }/ t; d 123206vx24rfxfp2dfrpxg.jpg.thumb.jpg
: ^6 |& Y  h2 c  f----------------------------------------------------  分割线 ----------------------------------------------------------% }  P7 Y9 o4 f1 a7 p2 ]

8 u# U" D8 @& o. c先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。
- f! [4 A( u4 k
020011osionbunl4ui44vi.jpg.thumb.jpg
/ O8 A7 z+ N9 P9 q& |8 C轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。
/ F2 j! l" L0 s; L 020017j8ycmnv7788bqv52.jpg.thumb.jpg / h% q# y, b& {, r) i$ e3 h
特写,80C49
6 p8 v2 h6 E3 z1 X( L% J0 c) L 021040oujzuvtut6iujtvz.jpg.thumb.jpg
, x0 e6 s' b7 O7 T4 jLED部分,使用了一片D触发器锁存指示灯状态.5 c% }3 F$ R$ V- `
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg . _7 Z, W5 `" R& {
暴力破坏,将80C49拆掉
9 e1 k4 \1 t# s- c 021113e48qq98vyohvhzzh.jpg.thumb.jpg ! n2 P  F/ Y+ C9 ]; U
拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。' m2 Q# _1 N3 m& E# B0 N
021125nc9az6dj33rlds2r.jpg.thumb.jpg 4 S1 G) m  T, x1 Y
焊好元件后的板子,准备替换80C49  ~- K$ B! C7 ~
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg 5 C: b- |6 J! a2 n( M$ `, ^
用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。6 C/ |+ W/ b7 Y# R2 _, W; b
021104shifhnrqbr3o5nlo.jpg.thumb.jpg
! w8 G0 O+ `4 u这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.
1 p) ^* d+ `! V) ^ 022003ym1p9u4ug40280uu.jpg.thumb.jpg ' k. c, j  }5 ~, U9 d
开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。
+ W4 z2 x$ m) l% O4 f: Q+ H/ M 023313kt141q9qajtol7ma.jpg.thumb.jpg
( T8 U* u6 a' P9 L2 e5 c! o# r我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。
: f/ G' b: y* B! T$ l* d4 a 023322nt7l5xb3ltttkltt.jpg.thumb.jpg
1 j/ M' a. q$ Y' @: U9 z+ L* a
主键区键帽就位
8 e* n; n- m9 \% H% h! ^4 \ 023331hin88e8wkrwzwikx.jpg.thumb.jpg
" B7 ?4 y" C5 E- e编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。
# ?5 r4 }5 y- K* J5 y 023336wjzlgopugg1jyy79.jpg.thumb.jpg
1 d7 C  o2 y/ e6 A最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
& X# ]2 z* f( [0 J9 L7 V 023341sffu4j3g2323h6fl.jpg.thumb.jpg + b: i& f0 r) U* Q# V
" o  I4 t" h2 p! e' }! K
----------------------------------------------------- 分割线 --------------------------------------------------
* W% \- b+ Y- J
/ f0 h) g4 F# U: h' S

- r: o6 w4 w& r$ W! S! E3 J
. {; {7 e1 F/ }# H, Z4 g; F4 a/ G4 X" b2 b: O2 C+ ]' s

$ b2 J2 m/ v3 n+ e/ Y

" W( W9 y8 ^, O9 h! |: ]! |4 O3 k$ S# U4 m% w

, F8 ^, k9 j0 i% @; T' Y* |% W# R( g2 ]) M

6 t) M5 n$ g$ O$ {
/ u1 M- ~) c4 I  z) }5 g
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。
: F- G/ s! }2 h80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:
- b* w) }0 v# p4 K& W! l( Z3 F 104025nzibm2rmiomhyirm.png.thumb.jpg " ~% h1 x6 N# U& ?6 e! u

; F! d1 K4 ?) P) q/ X; Z0 A/ R其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)
1 }) V  w+ U+ p. ^- B5 } 105004zkrez5houvkkznko.jpg.thumb.jpg
$ V) {- n* e# T% ?5 r% Y
! b8 u3 Z/ ^( ~9 _6 V: r
扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。1 [4 o1 m7 g3 [4 f
8 ~- ^" `, y- N" q/ \( u
这是我设计的电路图:
# D$ o3 T* W* a& }6 Q 110344ej2z2oo2rflo7oe7.png.thumb.jpg / g& {6 Z( m, @" {: V
; y+ l# @' }  N4 l1 g& g
PCB Layout:
5 _& \3 x+ a! A) K9 x# \ 110847jjbjvt34vwt3v5bb.png.thumb.jpg / \5 H4 J1 @2 _2 o9 J5 _' M, O

" {6 T: d1 v! j# S不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O. $ @) b- G0 s9 j4 Z

0 q  U4 G# m/ i4 D' h# z/ l
6 H; b: O3 z# H  k/ Y# g$ }
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑 $ ~  ?- d; d( T
8 U. x/ R( A$ a7 @- ^
软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。9 |; P9 R/ F" L3 L& _3 Q

+ [0 r/ T) F' ^/ U% C9 |& ^总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:/ V/ v3 O6 r% Q$ a7 P1 |6 ?
113818pmrfsb6z0byt6t06.png.thumb.jpg , f. A& _6 n) I
$ J8 m0 H! G; c( H
其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)1 h+ h* V# L+ W
! ]0 B3 H/ e, ]+ v) X4 _  _
USB的中断ISR,bare metal哦9 T! a6 ]+ b0 Y: G; e" c. @
; R. O- a! K5 X7 k
void USB_IRQHandler(void)
% G7 G3 B( ^! I' g( b6 l, F) E- C  k{6 d% D( ~% U- H0 K% L$ ~2 W+ A; Y! }+ `
    if(USB->ISTR & USB_ISTR_CTR)
8 k5 Q4 t3 H5 e% j; B2 z    {
  k- R' t% r% m# U8 p/ M        if((USB->ISTR & 0x0f)==0)   // EP_ID==0
  r6 U: @5 H) Z, l4 h+ p        {0 K( F8 e$ V( w! e" c8 O
            switch(ep0_state)0 d7 e+ {& g( P' I( @( N7 A4 K
            {
2 Y3 A* \5 ~7 s                case 0: ep0_state |= 0x80;
8 y/ U1 |5 ?! h2 d+ u% t3 X                        break;! R0 j( Z9 o+ n) S+ m. h
                case 1: if(USB->EP0R & USB_EP_CTR_TX)
# n8 ?- P3 S2 Q# a  ]& v" }; L% Y                        {  w$ W, g: n/ D8 o; R2 P& @7 T
                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;, A+ J* F3 c/ V% \; b0 C- U( l
                            ep0_state=3;( O5 L& [/ a9 Q% T8 ]
                            return;2 `% G' T) T/ M8 x6 w4 @  N
                        }
3 o" X0 K- ?' m5 d' ]' y0 o                        else6 N) f9 ^; K* t6 P: o
                            ep0_state=0;
6 r) \& S/ h) ]9 F. ~                        break;: P" @' N% ]0 o# }9 m+ t
                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet
0 y+ a4 S! r* S, @                            ep0_state |= 0x80;6 h! B: {. f: X- \2 [
                        else! l& V" e$ d) D; k9 i: @3 ]
                            ep0_state=0;
; F( P6 T( \  }& D" z: R' k  }                        break;
7 ]& ]4 K9 [, L- `! u' \4 }% y                case 3: ep0_state=0;
* ]% |! {9 S% D+ G3 l                        break;
7 v3 a- p/ m; W, H1 w% g& p: s9 E                case 4: ep0_state=0;
3 Z& F5 `  z5 i( k# h                        break;, _/ b5 l7 c) |4 y5 w
                default:ep0_state=0;
. K3 A- H* q( Q8 ~9 M3 n4 P                        break;
3 J  O. a$ Z1 ?; t            }
5 x' h  x5 ]1 n8 p            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
6 |- F9 K+ y4 Q& w: w9 z! }            return;
1 w0 n- V! e: u: t6 ~/ q( L        }3 y3 t- m" h$ F7 g7 t
        else    // EP_ID can be 1
: M! ]( h" D# c; d6 I1 e        {3 q+ m4 ^. p9 [" b' e- j
            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag
, B) x8 v+ m4 C) X            ep1_wait=0;0 O3 G  {2 D' E* x. Q. r! g: C6 K" L3 }
            return;+ |6 _0 R. h) P1 C0 j
        }
6 l0 u7 O! P. L& ~    }
" ?  s, `% g* E4 V- t9 Q. O    if(USB->ISTR & USB_ISTR_PMAOVR)
% q4 n, f9 s( L* d0 }, H  t- E    {
% d4 Q4 r2 ~+ G6 R: m- T        USB->ISTR = ~USB_ISTR_PMAOVR;
2 f3 F- L! R7 Z" V9 Y) j8 ]0 E    }! I9 \' O- }% O# P7 O
    if(USB->ISTR & USB_ISTR_ERR)
+ }. L4 S- s& X) z1 @    {
9 K0 K4 y1 x# N        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear- O! E/ |9 o0 h" l
    }1 x' V" G! b5 M. M. J% y
    if(USB->ISTR & USB_ISTR_WKUP)1 v$ P4 c4 b; q
    {
5 q" o7 L3 }. u+ O9 w; d2 k        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend5 I7 }. F# }$ e. Z. p8 \
        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear; i4 b8 G5 }; ?
    }
. M3 s- o6 l2 X; i0 B( f7 ]- f    if(USB->ISTR & USB_ISTR_SUSP)
8 O9 ?# D- g8 ^  F8 A* S* O) _    {
5 g: v* y: }5 c9 F- j6 C/ V        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend1 ]0 f& K, l0 n2 Q4 N" Z+ M6 H4 W
        USB->CNTR |= USB_CNTR_LPMODE;   // low power/ O# o; q- d$ t6 e3 r+ v: g' R
        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear* L: t+ g- J7 W% D4 S
    }; L- U6 v0 f$ |- P# I
    if(USB->ISTR & USB_ISTR_RESET), f/ R$ V/ ~! |
    {
9 V4 F! H: x3 ]4 q9 d        USB->BTABLE = 0;    // buffer table at bottom of PMA
: H# F! X. T: v& o- n0 [( |        USB_PMA[0]=128; //ADDR0_TX
: W$ @4 i! N; S. v; S" e        USB_PMA[1]=0;   //COUNT0_TX
5 C! ]( y$ w; P% |        USB_PMA[2]=256; //ADDR0_RX
" j1 L" j" w% j& V% m        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes
1 N, G4 D, c7 o, k        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;0 O% t" }0 L* c$ r$ [2 w! W% g7 x
        ep0_state=0;
; W) Y2 b6 N: @) q/ m        USB_PMA[4]=384; //ADDR1_TX6 y2 j# C; f5 D  y
        USB_PMA[5]=0;   //COUNT1_TX
3 E! l6 e9 X0 H! T8 |' z4 P        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type, R1 I" m# K$ z* U0 U% B* f, U) C
        ep1_wait=0;
- E9 P1 I% A6 }/ F1 _: M        USB->DADDR = USB_DADDR_EF;      // enable function
" P, o1 H5 N% a9 m        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear
. B* c  c4 X0 ^    }
  M& \' K2 s2 E) M3 X7 ]& {    if(USB->ISTR & USB_ISTR_SOF)
' p- V! [+ ]6 d% I, O    {
5 S2 ^+ n2 _, x7 l/ A+ }        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear
; X9 t: c: B& _( w$ c6 c    }) \: w) p+ y% q
}
7 w* G: K$ h3 d# [4 P( Z
$ J( V! t1 Z  ~; x) x1 z
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。2 C" B2 U1 o4 \7 i7 w6 }: R( ]6 V0 o; M
9 C9 V5 u8 a6 V0 v
主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。' n3 p9 A! k! T5 h5 R" @
7 D" Z2 W3 C+ B- J$ _4 R
    while(1), s" O. t# J1 ^3 h. R
    {" r+ R" X3 N& |  n( ?3 Q+ W
        static char row=0;
$ ^% s! X) p; Z0 R; B2 S# C  v( x        __WFI();$ K% O  D. b  V* I
        if(ep0_state & 0x80)    // request data processing$ C* a3 }! w/ P  U1 _. k
        {; z' e% I( z( d& c: c- n. \
            if(ep0_state==0x80) // SETUP phase0 O, x& ~' F/ L- z' l% k
            {
0 O! _1 U& B( S' e7 ?                if(!setup_packet_service())4 z8 `6 q3 L3 K% Y" Z& g5 D
                {
7 O& ^4 S. q) \9 z+ L6 N; H5 Z                    ep0_state=0;* S, t% K8 r3 u
                    // not supported
1 Z1 m; G* U$ |                }/ X0 k/ z. s3 R2 r% f! R
                // ep0_state should be set to 1 or 2, if processed
* p2 V/ L" M5 S% w            }* t0 i- b4 B" ?; I. n8 |4 S
            else    // OUT phase
, T* ~8 F% O- C$ f3 D            {
6 J2 [: [( w& E) N                // process data8 o6 j. w7 A  ^( L7 E
                show_LED(*(uint8_t *)(USB_PMA+128));
3 `! k, `8 s9 f, N+ Z                ep0_state=4;
9 B8 E3 [. u; m- `                USB_PMA[1]=0;       // Zero length DATA03 D8 o% N, I% a0 V) R0 _
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
% V6 Q; o; H# H, P( B' I            }
. E2 w3 X1 D* Q+ }, v9 j        }, _: Y: ]/ g: B5 Q; {& n& o
        else
  }! l9 l! B3 s: q+ B        {
: \4 {3 s1 q6 v3 [            if(usb_address && ep0_state==0)& X/ s9 P$ M5 D. j0 ~8 X9 |
            {
: E( n  W* S  H* l$ {! ]7 H                USB->DADDR = USB_DADDR_EF|usb_address;
/ T/ s  m2 h8 b8 w. p+ B" O: Q                usb_address=0;
2 |% d% l7 P: _2 C            }. F$ `4 p+ M5 K2 O$ Y
        }
' J0 t1 A2 D4 R0 v) l        if(row!=scan_row)   // new scan line
' y- Y5 ~: @3 |7 |2 d' \        {
5 g) f7 X3 ]. x, W            if(key_state[row]!=prev_key_state[row])
: ?' {+ w) W2 d8 L# R$ V4 C+ u            {
- \  Q- G% u4 u' G+ a9 B                uint8_t test=0x80;
! s* a8 V( a3 n5 o3 M9 U                uint8_t diff=key_state[row]^prev_key_state[row];. g8 }! z4 j# W8 \/ e; y
                for(i=0;i<8;i++)
. b; }  }- y) ^# ^. }                {
( I  T( D# C+ `                    if(diff & test)
0 I/ c9 ~6 r7 E" g3 @                        update_key_matrix(row,i,key_state[row]&test);
5 H  ?: N* T/ T1 b  G5 B                    test>>=1;
  K, h3 g: {& {5 G7 U/ f& ]                }
- f: Q: z' J  E) Y  s; ~            }" f- Q. Y( J& |& ]6 ]; z
            row=scan_row;
- j2 o3 r& B7 `9 `  s% {        }
" ]% Y( j" P; c9 j( d; ~8 e    }" x2 v8 V& J4 ]7 C* e! |

/ p3 v2 m0 R1 J  B% a# @6 q$ E4 M/ f( m! V! E
EP0的控制传输,把用到的请求处理一下$ I. L" o& L+ A( G( ^' O
char setup_packet_service(void)
' L( A$ M( L8 M! w6 W4 N1 s: ?{
$ z* F* G& n) c3 T7 w+ G8 r    if(ep0_std_req->bmRequestType & 0x20)   // class-specific2 _4 ~  J- W, F) r' Q) S
    {, ?, D# Z! y2 L% y& |" Z6 L
        switch(ep0_std_req->bRequest)
" d! _% @- l8 F$ B% \; m2 F        {
! b1 m+ w" d! R            case REQ_GET_REPORT: break;
3 a' M+ c) d  O- A/ W( h            case REQ_GET_IDLE:
* d- Y! D. g" S( l  L                USB_PMA[64]=0xfa;   // return 1 byte% f+ y  V9 S5 i) F, ~" r
                USB_PMA[1]=1;- c6 z! J4 ?3 L  Y4 C- [4 X1 {
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;: G) }! f. K9 s: N* M8 u
                ep0_state=1;5 d0 ]9 m6 f" W9 |" ?
                return 1;5 p( k" d. ~; j! T# \' ^& H$ p
                break;
& R$ c4 T( Y( b! W. p            case REQ_SET_REPORT:
; B% S7 ^0 y5 e9 d3 u# }3 h                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;% Y2 Z9 u$ v2 a) M1 l( J0 k
                ep0_state=2;
$ F' z& _' U* s5 g6 V                return 1;
8 @& U  I; x1 ]$ J( q3 G+ R$ P                break;
* ^. c& c9 p9 C( m5 i9 N            case REQ_SET_IDLE:
  z* h' e# y" A" f: Z                USB_PMA[1]=0;   // Zero DATA
# V  ~, h' |6 r( B6 \                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
2 K+ d# r" ]; z8 `! Z8 d                ep0_state=4;6 H/ I' [9 i4 i6 p6 _6 [! h, S
                return 1;' k2 V7 x& p0 U, [4 j
                break;; z: A: _  ~  @3 t4 z2 r( k5 a
        }
: \3 m0 ]( I8 c- L7 d        return 0;! m7 U9 o0 f6 j' B
    }2 y/ n7 L  `# ^( d3 q" X
    else    // standard5 _/ ^# @- ?0 i6 ~1 {% u
    {
; l" D+ s, w4 p# D9 r5 {9 |* L7 K( p        switch(ep0_std_req->bRequest)
; B; W+ J* l) _( \# @        {
) l: f+ \* q3 W* n* V6 o3 P            case REQ_GET_DESCRIPTOR:
, \9 S) H: b( d9 e! \                return descriptor_service();+ l) I, _& ?% q3 J
                break;
2 U+ f1 F; x. `: l            case REQ_SET_ADDRESS:
. A5 ?5 K( r& P                if(ep0_std_req->bmRequestType!=0x00)$ w1 T8 \/ z7 F5 J
                    return 0;2 l" J* v  @9 U. o3 c! w" F8 J0 `2 f
                usb_address=ep0_std_req->wValue;
2 _7 j: r4 T- s0 Q                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
, U9 X! J6 `2 W$ z1 e% _( n                USB_PMA[1]=0;       // Zero length DATA09 t9 J' \* ~4 l/ |5 q
                ep0_state=4;    // No Data phase
! W. I! m4 n! s1 o* i/ ^5 X! i. z                return 1;
) e2 B+ V8 \) v8 Q% W$ D  G            case REQ_SET_CONFIGURATION:
' t7 m  q! q9 p4 R2 a% B; v                if(ep0_std_req->bmRequestType!=0x00)$ O# b2 V8 p- u* R0 D
                    return 0;
& S) V! M  w) {9 T+ j5 M                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
0 x6 k, {( f1 ^3 ]& ~$ ^$ A5 N                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
/ ~9 X9 Q, v! \& }1 K6 ?                USB_PMA[1]=0;   // Zero DATA7 D5 D$ T- ], r3 I! e
                ep0_state=4;    // No DATA phase: Z* h) j% S8 f- ?" y3 t
                return 1;
) j7 g4 G! }& L  w( Y+ P            default: return 0;3 ]' Z' [4 ?* X% a. z  G' ]3 \, @
        }" f, ^5 W% b' A" J/ ]
    }( D- ]+ n2 ~' o+ ~6 [0 C0 _
}4 J. l% D# k* q9 o
' _) D/ Y# w2 _9 X

9 v1 a) w3 c$ G6 i4 \2 G" r) y; Y. K. C  G
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的3 S# O" F/ E6 e# x4 m
char descriptor_service(void): L* n9 C2 U: e2 w) O
{5 s  i9 R% Q4 `# W- Y0 y
    switch((ep0_std_req->wValue)>>8)
$ O* b% W3 a' C7 r+ ~1 o5 }% b& ?    {
. b6 S6 c4 k9 K# y        case DESC_TYPE_DEVICE:
6 F/ w' s* `: H1 [1 ?            return ep0_preparedata(&DevDesc, sizeof(DevDesc));% }+ A5 h! ~* s7 C# k, a: c2 ~
            break;) Q, n( Z0 g0 ]0 A3 N: n4 Q
        case DESC_TYPE_CONFIG:
- W9 C( W2 J- {0 O            if(sizeof(ConfigDescData)>ep0_std_req->wLength), A0 S) i, j4 q8 }
                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);4 D  k9 {4 T+ y( Y9 E" S6 o
            else
& o% f) M' p. E2 A( p6 W                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));
8 H3 L8 [0 a9 c/ f, [$ O( D            break;
. u7 a' e, t* O' c& H8 g6 H        case DESC_TYPE_STRING:
7 [3 x2 n! r5 [# w            switch(ep0_std_req->wValue &0xff)7 Z* L0 L1 `5 H% H3 q" s
            {
9 i; A' d/ ^2 \  j6 f                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
  H4 @) e; P5 S; `                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));
7 w( G. [3 U/ Z                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));: w9 t6 e7 D( `8 {. e+ C0 z
                default: return 0;4 E  N  [" h4 q$ R! ^$ K4 o
            }* F1 P" ~: ]5 w4 W- n
            break;6 y, F* m! [  u! s' [
        case REPORT_DESC_TYPE:, x; u+ ?/ n) s8 a9 L
            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));
4 J+ ~( T: y$ F+ b5 z        default:
/ a7 o; D+ ?. m6 P& u* X+ j" Q            return 0;
2 p% \* q) m$ R" ]/ j/ u9 u    }, }- m2 q: x& V7 Y6 W+ k
}
' \8 M1 N" L! H2 h; Y下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.) O6 p; i, p  d3 [
void TIM6_DAC_IRQHandler(void)
. V2 Z' `; t) q2 v5 Y& J{  ~/ A' Z9 G1 N# `0 _7 M1 h3 ^
    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);
5 W8 a, _2 N. j* X1 e
8 k9 l6 h/ p, ?, @4 H
* E; G! l7 Q" {* J
    TIM6->SR &= ~TIM_SR_UIF;
9 {/ b1 c9 ^5 x; o% e$ \    prev_key_state[scan_row]=key_state[scan_row];
3 Z# b9 ?3 y  _0 ?3 U* i! O    key_state[scan_row]= *PA_IDR;   // update key states
& B" Z$ R  h5 N* `7 f# x    switch(scan_row)
% B. @( T$ O3 }' W    {
8 [- c8 j. u6 S* ?        case 13: // next row PB145 r/ ^9 h6 O& {1 i' j
                GPIOC->MODER = GPIOC_DEFAULT;- ~& i/ ]5 Y& w$ a
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;; H2 P; D( e, F
                break;( k- b4 O2 h9 S. q- F
        case  0: // next row PB15
$ d# Y. c0 c  u; p                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
  e- X  a0 d; \2 U+ s& a                break;
/ L( _# `( g$ i2 ~- q, C        case  1: // next row PB32 n0 ]7 [- k8 Q9 }. y  t
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;+ d( D1 f( R+ C& J0 a5 b  J
                break;1 U$ y# V) D4 ^4 R* r. x: b
        case  2: // next row PB4
4 {" H( Y! _* G' F                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
. s+ f8 P/ Z% m9 Y( u                break;: p( W3 w' a' |0 R
        case  3: // next row PB5. ]5 ~7 ~* Q; _& \# S
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;
( t4 a& S; O# |                break;
1 o6 I, c, V2 t" u. a        case  4: // next row PB6
5 f: \" ]8 ]' q3 U' e1 L0 g                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;& Z9 C: C$ a3 r' _" n& c' i  a4 [
                break;' B1 M1 _5 e+ |7 w- l  r) L! a' H
        case  5: // next row PB7% |/ A' R, X; r8 [9 ?
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;" o7 _6 |; q1 f; C5 p
                break;& e* M; @9 l! ^% L- u! }9 Z& x" G9 r
        case  6: // next row PB8
5 n  ~9 {  I$ `6 H4 e                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;
' u9 [6 R. u" x( G( q7 |& v1 c                break;
* V0 m  P. d+ _, D: P# F+ c. n        case  7: // next row PB9
0 C, _0 N8 _2 f: w2 F) W                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;
6 |5 j* H( z1 T/ M' C* Y$ o# L                break;
2 Y6 c4 Y* u4 ^; |1 N        case  8: // next row PA8
) _; v1 K0 r$ g4 ]                GPIOB->MODER = GPIOB_DEFAULT;3 m1 B: o+ G+ r4 g" g9 _/ v$ n/ E
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;- c5 P8 e7 B3 G" W
                break;
/ H1 Z' [3 B. K0 e        case  9: // next row PA9  C3 Y0 @, T' o
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;: m: U' Q3 H: r5 u" _* _# ~0 ?
                break;9 t* G$ F" W2 }' l2 j
        case 10: // next row PA108 z+ N9 c) |. l
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
/ e6 [4 F; F3 L# T# z                break;, l7 J0 c1 B! Y' n8 y8 D4 [
        case 11: // next row PA15
4 B$ T( E4 Y* u1 z, I, v                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;- f- P9 |$ U# c& K- c7 K2 h
                break;
: v6 M& t( f7 y& B2 d        case 12: // next row PC131 r3 P( L2 q; p( b4 v* ]& [
                GPIOA->MODER = GPIOA_DEFAULT;' ~8 V% B& _5 h3 k
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;: [1 \# Z& l' Q8 C6 R/ M
                break;
$ t! q6 v* @0 `% Y# T    }
. ^0 r. F: Q5 U, v3 E    if(scan_row<13)
  n9 `" ^2 P) V4 d6 G        scan_row++;
3 b6 J3 R0 C1 B, s% K9 O. S! e    else
& Z0 [5 h3 A) t. G0 i) j2 A        scan_row=0;; ~2 W0 b) W: \  I
}9 n7 K% ?( h4 V3 Z: o/ Q

2 h: @& V7 D+ L2 g2 A) D4 V. n  _  y: Y7 p) s) z
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑 5 E* l3 G  R! \6 C3 O0 d! ?
3 `9 s' W( c, S
扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。
: O, ]( L1 k$ L
- f1 {9 a) r5 k要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:

$ @- O6 j4 A( ?) P0 T; b( T% B4 S+ F$ W7 i% y" j
) L' g) u+ B2 t5 s4 x
const char hid_keymap_qwerty[14][8]={! v. {' |6 g$ A! K! x
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},
5 l8 Q! b$ C$ N4 _    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},$ P) U0 r8 r) g3 ^6 {6 U
    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},
: x9 q' E5 c% L: [% a* l    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},/ Z7 T2 m$ g) N* @
    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},4 {' s" m1 Q6 V) G+ n: _
    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},
& O, q. d7 O. l    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
# Z5 a; K0 x' h! E    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},
2 Z9 N$ |3 g3 v8 R$ n  _1 {( ~    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
, G. ?$ ~  J  }1 R. b/ {+ w    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
2 i* `0 R1 C* \2 C8 V+ `* w    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},- [: e1 |+ `" |) [. V8 _% ]' F& y
    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8}," V( R  U% I5 G5 s2 G
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
8 B, ]& b3 h6 U- V    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
: Y' Q3 R  H# X0 e  i};. _! b+ G/ u8 w: n' R

  ?6 m3 O( r$ ^+ Z, C, X+ x
. O/ M, G  |! W4 Z
const char hid_keymap_dvorak[14][8]={3 `2 V# T; \3 P2 `4 C
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},% h2 k, ]2 `7 e
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},5 g) T# B3 q! }; x: S) E, d
    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},
, y0 P& \6 s1 e+ v  `+ z, e    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
% H. |; }+ H( l) r$ C* f    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},
$ l" S8 t4 Q( R7 r    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},0 N# `* s; o0 P
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},& U+ m5 s( X3 u! @9 N
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},
, r8 |. |7 M0 K/ i/ E2 V    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
1 W' }  G8 J( `1 g# ~4 ^    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
- x* i; u  u  K! n- X    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
( k3 ?2 B2 e( w; F$ h$ s( p    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},
1 l# [8 i+ |6 M* Q/ M$ A4 j& r    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},
) i. A9 L% k- W% ~    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}9 |0 y. J, L7 A' f) o! D
};" Y* q8 ]8 a2 Z  R5 l' X/ I3 u

' f& n# u9 E6 g* [+ i, N4 m# f& Z* D6 v- R2 d8 a" v3 K$ m
上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。8 Z$ T. H% t/ i/ U& ^

3 Z4 G0 y) Q- b1 X) t6 lHID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:
- y! m0 G+ y8 s$ o. B1 i
8 g* l- W' h% [
void update_key_matrix(char row, char col, char onoff)- H* ~; C* R; o- @3 @% O
{
8 J* _8 Z& E) w, P    static uint16_t hid_report[4]={0,0,0,0};4 {. H2 Y. ~4 p5 ?; l4 Z
    static char (*hid_keymap)[8]=hid_keymap_dvorak;7 Y# P; x" h8 G
. v2 l5 A$ r" ~1 G: W" `- U

" z! L) w3 A* ]* }4 f6 x2 [    unsigned char key=hid_keymap[row][col];
$ c# L% `1 N3 d: f9 G    unsigned char *report =(unsigned char *)hid_report;# W) G  A) }' d! F# J
    char i;: ?6 L0 C4 f1 S) U
# S* V9 `& F$ I  C9 {4 ?
/ y+ f) z8 ~# a5 b0 W, i+ d7 U
    if(key==HK_MODE)
" W' N6 P; w* Q) y    {
3 Z) g$ ?! {. D: ?- I        if(!onoff)4 A' K2 X" r4 J/ y# O
        {
0 F7 I. a5 S2 b  x, n/ q( l            if(hid_keymap==hid_keymap_dvorak)* F8 ^" M, n4 ?% A" f: v% I
            {
# A- T# b# x3 @- M/ Q- w8 R* h                hid_keymap=hid_keymap_qwerty;
' B% L" D4 s1 G                GPIOB->BSRR = (1<<2);% Z# W( E) v. W# l
            }6 i1 G$ w+ Y0 D' A( B  W% I
            else1 R/ I3 ^# e5 g( ]. h
            {
* b* p" S) U7 C) `! t; S- a                hid_keymap=hid_keymap_dvorak;9 y$ R  O; F& F0 E1 [
                GPIOB->BRR = (1<<2);
$ L6 T& @# `! h. H            }
/ T  y' r# k' K7 P4 M0 F) D( G% s        }7 o4 T( C) V+ R- ^  b9 B
        return;
0 q# O% s, I& |4 c; T, Q" J$ {    }; ~, f5 z0 Y( v
+ J# \; C, B. w5 B+ s& X: Z

; e/ M, u" |) s3 x    if(key>=0x80)   // Alt, Ctrl, Shift. G% \$ ~6 ?( N2 r
    {
; \" P9 ?/ a) }* P, r) _1 q( ?        uint8_t bitset = 1<<(key&7);
' f" k0 E6 D7 E+ s; F; ^        if(onoff)   // non-zero is key up
6 @) P6 `) S# ]$ m( n% U/ x& H- x1 K            report[0] &= (~bitset);/ ~8 g: p( U1 Q/ {5 }( Z% h/ S
        else
7 L6 j' |6 {- X! `0 a; N! E            report[0] |= bitset;
7 {* n- L) m/ S0 n  h6 J$ l    }# [: Q% w% X0 j9 I9 d
    else
6 C& z& v' _% g& g: j2 [    {- i& p' k  |! j2 U; A/ L0 E
        if(onoff)   // non-zero is key up
+ J0 o; k, L4 X4 y  G  p        {
! o) a+ \* F' _1 S# D. T            for(i=2;i<8;i++): `, {: v" B; S) Q# @: e7 s
            {
6 T' X0 k1 s" m$ `2 r                if(report==key); w, ^  w5 ~) ?/ M$ x- L2 i
                {
1 u+ j- x$ ?% l+ Z; P/ ?                    report=0;
  @4 H8 p* w: }* g: p, m3 Q9 {" D7 |  C                    break;
+ H+ i, c  ^6 T- R, c                }
1 M% G& `7 @6 l/ v( m            }
! h% Q; g" K* ^% G2 U0 }        }0 O9 `+ D# C9 e& i
        else, w; L8 k* c% u7 M0 W+ b
        {
; W% \$ `) K/ {) b) D& g            for(i=2;i<8;i++)- }- m) v; ^# ~9 J
            {! Z. u* w! D" \! T5 j$ |' R6 H
                if(report==key)
% i3 {2 s* a8 {/ q. f                    break;$ o% F. q) F: g- y) Y
                if(report==0)0 s& V7 S- u$ u9 V9 k! W
                {. C9 c# S0 ?( e$ O3 t
                    report=key;
3 Y$ w7 v2 ]+ K; W+ F$ _- _+ Q- `                    break;3 T/ @7 W8 V' w% X
                }
" H( D9 V3 i# J$ L/ D            }! ^" y1 O% T5 c$ H$ V5 K
        }9 e% C1 C, q$ [2 D% o
    }
  c+ s4 d% Z# n" `% K: y    for(i=0;i<4;i++)
- `  O' ?: o6 \        USB_PMA[192+i]=hid_report;# |  L( i# ^+ N2 |' t% x
    USB_PMA[5]=8;   //COUNT1_TX* ?7 x& q8 u# P5 U1 v9 D
    if(ep1_wait==0)) }  r$ C  I4 E# ~, w
    {
& E, {$ S4 W7 q0 T8 g        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;
/ Y1 a- I! \7 R+ ~        ep1_wait=1;
  i9 m/ T$ M5 U2 w  J    }
1 W# d2 r* E7 ]: d  e}
- I' f* \: b* e$ f" d* O- `  K3 e

7 |- P& b7 I( u" f* G" @完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。* W" F# q! f3 O' H: B
keyboard.zip (8.7 KB, 下载次数: 6302) * s8 C' J0 o. D) [

- z: Z  X, K3 n; M- N0 A5 s) A* A
/ U6 Q3 K' h7 B

; G3 ^  }  H  Q' q+ ?
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘
7 W: s& u- C/ K6 X3 x4 ^+ E不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48; I; k- g; u9 M" M; P8 Y
刚开始我以为要把打字机改造成电脑键盘
' ]' [: H& u( X4 w; h不过楼主也很厉害!

, C$ `- ^7 w. t& ]; L哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
$ Y) J* S5 Z5 V- P9 h" O5 U
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-9-17 01:07 , Processed in 0.207507 second(s), 27 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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