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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

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

3 I) H0 C, p: t1 F) n  `" S& v$ [
- w; {$ Z* D" Y, _+ v
235140i3a36qivqzuvmt5q.jpg.thumb.jpg ) O: `7 q" |0 H9 b) y4 U
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。6 J7 R* f) d& [4 i
001734klbyoluenuwz4h4b.png.thumb.jpg
: J9 S9 n0 y4 x$ @" C) N8 }9 D为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:
% \' H9 N" C/ v5 |6 K! g9 C 003625r2agx2f5v922cf2f.png.thumb.jpg
$ Q* x6 w  U% [' K. x/ W0 R其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.5 k# D$ M! D' E& n( O
Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:
, b+ F2 W6 j' B: d; i
005836yvs0wvovwsssgd3o.png.thumb.jpg
7 ?6 s- Z3 I3 k, N* yDvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。
5 R2 D" s: N4 _, u! e& _/ U" y% {  t3 y/ _' a
我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。' l0 W! ^/ _5 }. \

" @2 \) B0 ?7 d) h% c% B( E到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。
* `: V: n7 }$ \2 W! y6 A5 K8 w; l  P, G' H  l' Z+ u
机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:
6 \4 a5 i2 d8 y$ R' b4 U
123206vx24rfxfp2dfrpxg.jpg.thumb.jpg
5 O9 H7 B) N5 k3 M----------------------------------------------------  分割线 ----------------------------------------------------------
1 b: r* L7 O, ^3 }9 s! |/ x- ]. {' d
先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。
  ]( H! ]/ ]/ G( X: _3 \
020011osionbunl4ui44vi.jpg.thumb.jpg
5 T0 C0 h, {3 r' E" O5 u轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。8 K* m* [5 Y* [6 {" B" [7 ^
020017j8ycmnv7788bqv52.jpg.thumb.jpg
5 q* B, r( w: ]$ P8 b* ]8 g) }特写,80C49
& w, V, I9 N# E 021040oujzuvtut6iujtvz.jpg.thumb.jpg
0 D0 u1 v" ^. g8 m$ cLED部分,使用了一片D触发器锁存指示灯状态.0 k$ L1 s- w+ H6 U: m) s
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg ( e+ O; N% X5 L5 Y0 a3 E5 N/ z; L
暴力破坏,将80C49拆掉1 Z; |' _' z' ~( e" a* Z5 A8 ?/ ?
021113e48qq98vyohvhzzh.jpg.thumb.jpg
' N% `; b8 \6 T' s8 K; |拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。$ v3 _6 P5 V+ Z" P. ~5 N. ^+ K
021125nc9az6dj33rlds2r.jpg.thumb.jpg
8 F5 d. p" H7 U) y0 ^# T
焊好元件后的板子,准备替换80C49
  A6 q3 _7 _" }; k 021146zmmpmh4pav4o7g6a.jpg.thumb.jpg   o1 K( q3 z% H: h2 w
用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。8 i: f$ h0 @! u+ F/ _
021104shifhnrqbr3o5nlo.jpg.thumb.jpg + I& f2 Q' {% p: D8 A$ H) s
这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.
8 b4 W7 c8 j4 p9 Q4 a8 x 022003ym1p9u4ug40280uu.jpg.thumb.jpg
8 A& v" l! }( \+ P- L开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。! b; P1 Z! l( B2 ^; O. V
023313kt141q9qajtol7ma.jpg.thumb.jpg
; M8 r) L% E" ?8 B我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。& z/ C+ A$ s* o4 z/ Z1 M1 K4 W
023322nt7l5xb3ltttkltt.jpg.thumb.jpg 8 r: N* z1 ?8 W$ @9 m4 x+ i
主键区键帽就位+ H4 R- L; |- q3 V7 j5 J0 g/ A
023331hin88e8wkrwzwikx.jpg.thumb.jpg + n% s- p/ F) U
编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。3 v) b: F9 n  C7 [0 V# l
023336wjzlgopugg1jyy79.jpg.thumb.jpg 5 Q* i% r- M7 y# y) W
最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
. T, Y- f8 |- X5 e& s+ T 023341sffu4j3g2323h6fl.jpg.thumb.jpg 5 E2 t2 k! q9 s/ H) i& I' T' ]! F
/ j  S1 D1 l8 y9 ~
----------------------------------------------------- 分割线 --------------------------------------------------7 n, r# w, Q% `* {8 Z  l8 k
8 k( h  K: v) M2 E

6 r) h; G! Y  O, N& q9 M
# k: s3 ?  `3 V; U( i& x7 I- p
+ K* @* N$ |9 V. h$ e$ x
+ o0 t2 @( {9 @- t

8 _* v/ F% S( X. k& y
' b. d& P# v: m8 D7 W+ c6 j9 ?4 c3 |/ ]4 q  N, D( R: R6 H/ H
1 C) C  z- J. P. [

* ^) \( R' E0 F( z
& t$ d7 d1 D' s+ L3 O  u
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。
/ c* ~/ v7 t. ]' g! `* l7 o; g80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:, ^, g: {6 Y5 }! v
104025nzibm2rmiomhyirm.png.thumb.jpg , B3 d) D# C. U1 C; e8 T/ U
6 L1 A0 l! X8 v$ {3 i
其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)/ s1 ]5 Z0 l, q
105004zkrez5houvkkznko.jpg.thumb.jpg ; }& }; z( ]& p, U
& e. U8 n2 d! s: x9 U; U9 b
扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。
8 O/ ], V% E" e. w/ i: Z  a' C" Q$ x- t$ Q* N) u9 E1 c% N- `$ {
这是我设计的电路图:
! F5 Q& H7 K9 P& ?$ l4 E, U5 Q 110344ej2z2oo2rflo7oe7.png.thumb.jpg 3 S1 A+ r7 s9 B9 C$ K( X

+ i% V0 y( o: ]! SPCB Layout:, B4 j) t5 Y- ~) C6 O% C; x% {
110847jjbjvt34vwt3v5bb.png.thumb.jpg
- h- g: ?/ P0 H! Q
5 C( H1 q; r1 M6 B. T7 r+ v3 R
不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O. " Z4 h: G8 [' a+ U; x2 i
: l  W) G# N( N* P. x  J  f/ E4 F
& t- t% g, d) ^- G6 F3 y: V
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑 ; t, z, w2 M( C" C7 G

& Z9 d1 t& W* [软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。0 i. I1 o" t4 c

  [( y9 d9 o% |& d; G总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:
" u' o5 o3 a% K% R7 R$ G; f! P 113818pmrfsb6z0byt6t06.png.thumb.jpg   u) Q: t! s8 j1 ^# _0 a2 U* d
7 V/ [9 H- B6 e( o0 M+ b. D" g/ n
其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)+ m: r5 N7 i  H; L2 W- K
* _7 @8 n2 E$ `4 G( w
USB的中断ISR,bare metal哦
1 a6 F/ o$ X+ v9 L0 B1 Q

4 N) {1 J; |+ c) {3 ]! b4 Wvoid USB_IRQHandler(void)) y3 U2 {* b7 h% v
{3 |) [8 G; x3 ~
    if(USB->ISTR & USB_ISTR_CTR)
% k$ r* H' o( i* j5 d4 c  O6 i    {
5 |, M3 A2 h* c  o+ j3 d+ }( Y        if((USB->ISTR & 0x0f)==0)   // EP_ID==02 k% b  |% f# m& }  b
        {
, s" h% Q% A' n* n& c: C4 a            switch(ep0_state)
3 F8 B% M5 h& N, ^& y" p+ F            {% l: }9 J  s& `: B/ b1 n' Z' }3 D, V
                case 0: ep0_state |= 0x80;
% A2 D3 N6 r8 J8 n. W  ]                        break;0 s6 T9 d- I4 L) p4 h9 T
                case 1: if(USB->EP0R & USB_EP_CTR_TX)1 E! {; F( W- P+ s, ~/ U
                        {- ]6 _( p* i% z/ y. l' n& c( o
                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
2 j' @! V8 F! ~$ \- t                            ep0_state=3;/ n9 k% h6 P, ~% h* B& w
                            return;
6 q5 [( R6 |$ R: M. Z5 o                        }
/ A( ^5 ~  k% }" \, u                        else
( z7 p* r9 ], g5 _+ I: j                            ep0_state=0;
& v& |  l( ]5 }2 [                        break;7 H8 J1 ]" p4 t* P: p0 A; S
                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet0 |5 A: O9 p* e( d; ~
                            ep0_state |= 0x80;
' A! w9 U" I: B( a# p. v                        else
0 c9 g+ Q+ p, e. A                            ep0_state=0;
# |& N* s( p# y) z- r) h1 q: `                        break;- s" y- S7 }. r4 v. ?+ i
                case 3: ep0_state=0;
5 P% Y$ P$ |6 N7 o9 s                        break;4 X% [6 D. @& b: M9 g2 l* ^
                case 4: ep0_state=0;' v4 W: T# }7 L- }' \5 J) x
                        break;
- Y" z' u& B) u4 i3 B                default:ep0_state=0;
7 F! D7 N8 ^/ u8 ^* D* j. x                        break;
! S# H' O$ |5 t: S- u: F  n9 k! x            }% z! A  P1 q* J" J3 [0 y
            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
1 t$ d3 D/ i$ }+ \            return;
4 f5 e9 O* _2 }# x) v        }
8 K- c$ M1 I1 R( Z        else    // EP_ID can be 15 a+ L! [7 k2 e* U
        {. w, D& }  U) M& {! d8 h$ {
            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag$ e" D+ Z) R' S
            ep1_wait=0;2 H; S3 U" f+ o3 r# y3 I( `# b
            return;0 P. A* d  N4 M2 P- l, p' N
        }
' Z4 a- W2 X& y: K; \; U    }5 o6 t: ^% g& `2 M
    if(USB->ISTR & USB_ISTR_PMAOVR)
% q+ D9 R- l/ E0 B- N    {
7 V  k( r7 c5 ^0 u        USB->ISTR = ~USB_ISTR_PMAOVR;
8 j. ]$ g  M9 n1 X1 ^- r    }
, L$ K( q) K! ]' @# E* f/ S- T& m; F/ {    if(USB->ISTR & USB_ISTR_ERR)
9 w% A' h' L7 s, `- w/ K    {( ^: v+ A# Z- {& F% M  `* M: k
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear1 C! R7 D  i. ?, h
    }
7 k( J& C7 C% g) j7 m2 A, t    if(USB->ISTR & USB_ISTR_WKUP)
! s0 ?( G7 C  G* ?* L    {
0 f; ]$ ~- n# v3 H' K* X1 \) J        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend- \+ ^8 b6 S$ E! Z0 U  O
        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear/ Y8 l) i. ^- }% a
    }
) y$ K" H. n  f# D    if(USB->ISTR & USB_ISTR_SUSP): v  g4 p1 T8 d
    {8 X0 b6 W$ ?# I0 m: P5 S8 X
        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend
0 w5 H8 s# |* n2 ^( |6 P) L        USB->CNTR |= USB_CNTR_LPMODE;   // low power6 S( u! C. E$ |' X, q! O1 k
        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear
; E4 k" A$ s) j, n2 |5 k    }7 R% [* M- l  s" s; C9 Z. i
    if(USB->ISTR & USB_ISTR_RESET)
1 \. q$ ?# u/ C0 q1 o, m    {
- `* K6 A! p  g        USB->BTABLE = 0;    // buffer table at bottom of PMA8 l) o: K+ V  ?' K1 {
        USB_PMA[0]=128; //ADDR0_TX' Y& L5 H$ ~1 Y+ ^
        USB_PMA[1]=0;   //COUNT0_TX! L* l5 b( H! D( Z
        USB_PMA[2]=256; //ADDR0_RX6 c0 s- F0 f; T: Q, c6 M9 G
        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes
) Y$ S4 P/ q6 u9 |6 ]8 A5 d        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;/ _" v! \! T; f/ }( ~. r
        ep0_state=0;
2 v) X$ u9 [. l4 b8 e+ G        USB_PMA[4]=384; //ADDR1_TX
1 W: s- b! S7 w6 h        USB_PMA[5]=0;   //COUNT1_TX( L0 v' s. B! t$ I; C4 l. j
        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type
! k  v3 v" ]+ y) `+ T* o' N' h        ep1_wait=0;( J. t1 H( A, A" O9 n' K6 ?
        USB->DADDR = USB_DADDR_EF;      // enable function9 x/ m5 D# D( b9 ]( d2 @. m7 ?
        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear0 S1 U2 L$ d) Z% `4 b* @5 ~
    }- h' R0 A5 I. k/ ^% Q/ y) L+ C0 i
    if(USB->ISTR & USB_ISTR_SOF)
' v- t" f$ ?9 N+ U4 D- [4 V    {* f- ^0 S" T; r$ T# _
        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear, A' ?/ u. U$ Z1 F
    }
' |& y+ ?( Q1 `+ U" T9 r' O, P/ q}3 x" A0 O" A  ]* g# ?9 d0 U
1 O7 E5 k: p( {. k3 C
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。
% D- ]. @5 N& {0 S* C4 q1 ?" s. S; o
% C" N6 D$ i" N2 r主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。$ ^. V! L' C+ S# Q8 e
2 n: [# J1 ~1 w1 Y& x7 z" r
    while(1)- I2 m* V+ P+ @& R9 }
    {
# @2 m8 ^$ M8 w6 s' j4 m$ H$ t        static char row=0;2 e5 ~  B+ q6 A
        __WFI();
- a6 b1 H$ e- B1 Z, R1 {) X; b        if(ep0_state & 0x80)    // request data processing
( l+ H& s' u) q& M+ l6 f1 A& L        {
5 `) M" h& E+ E  W3 h            if(ep0_state==0x80) // SETUP phase
% |* c0 H2 r* V/ n$ N& F            {
( A$ w* J# \8 P( g                if(!setup_packet_service()), o8 h! W. c; X% Y5 S
                {
) o" F+ l* b  ?* ?                    ep0_state=0;
+ m* V- Q. u4 ~+ E$ M4 n7 G- M9 t9 ?                    // not supported
) ^4 E5 M+ s; `2 R7 r                }8 ^3 e' X) v$ W
                // ep0_state should be set to 1 or 2, if processed
4 ?  V' g  I5 v            }
' w1 b: P+ x$ [            else    // OUT phase
# Y. v. E# e, s  E            {: n% \% v- }) _. O5 f8 x- D/ x8 u
                // process data1 \7 s9 J/ m$ X4 V& w+ w, r
                show_LED(*(uint8_t *)(USB_PMA+128));
; [/ o" T% ?5 E3 N( i                ep0_state=4;: T2 K6 N5 p, a
                USB_PMA[1]=0;       // Zero length DATA0
$ q3 l9 E8 s3 l  O: c                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;4 r( h+ d1 W$ H4 o
            }3 r  M: W9 }+ Q# ^  f7 [+ F' W
        }, j) f% h/ M; h! X
        else
* f5 O0 y( R* w& W/ E' H        {1 i+ Q7 K5 |- ?2 D8 ]
            if(usb_address && ep0_state==0)
0 @6 S$ R: O- r            {  o8 N6 t% |) J* d8 {" E
                USB->DADDR = USB_DADDR_EF|usb_address;
4 ^$ X$ a% `8 d( k9 v: K0 k# U                usb_address=0;: s7 Z( y, Y" p  h2 W9 q% L
            }9 H/ p7 W" F5 I0 }5 T
        }" _. [4 R: }5 \0 Y  Q5 a, G  ~4 }
        if(row!=scan_row)   // new scan line
" p( _" y6 y, s  @8 P# Q4 ?        {9 q1 z8 f! F; j+ K& J
            if(key_state[row]!=prev_key_state[row])
* D" e5 [. @1 c5 n7 S8 d0 u. D            {% u, ^, R/ j8 g! M7 o0 v
                uint8_t test=0x80;1 x: S, \% X! L# f( D# k
                uint8_t diff=key_state[row]^prev_key_state[row];
+ I$ W& K, z  b7 @                for(i=0;i<8;i++)* I1 h9 p: `2 t
                {! @1 R6 ]; t) d8 E& a+ [
                    if(diff & test)( ?5 g: T/ X% x% Z
                        update_key_matrix(row,i,key_state[row]&test);
, e# E" F5 W2 [; i% K  R                    test>>=1;
6 A9 i. V. _7 D# H7 n                }
9 m$ a, V) U+ Y8 B) m  M- x4 D            }/ W' L6 S' Q) z7 C0 c- L% I
            row=scan_row;
+ O, X9 o1 o  F        }
: G, i. r/ O2 Z    }; ~$ y8 Q- `5 P- W

  [% v# H8 t2 S# B, S4 {
! Q, F4 ^* s0 AEP0的控制传输,把用到的请求处理一下4 z0 Y0 P, E* {- p9 m( o/ \% W1 c+ `" g
char setup_packet_service(void)* z/ t2 {- {: m, u3 N7 J
{
- a- J& `0 `! c) M( q    if(ep0_std_req->bmRequestType & 0x20)   // class-specific
! z/ g' f3 X3 D$ H" K6 Y0 I    {
6 T6 J" `: A+ X4 H        switch(ep0_std_req->bRequest)8 q5 m2 N6 y1 u. }5 A5 q: o; l
        {3 x) k" P6 R* f1 V
            case REQ_GET_REPORT: break;+ {) {: Z) U9 d1 O6 K
            case REQ_GET_IDLE:8 [3 c; B* }2 Q9 I# J( ?) n
                USB_PMA[64]=0xfa;   // return 1 byte
4 m- b: e+ m- H# O5 {- G! h                USB_PMA[1]=1;
4 Z/ j6 p8 y6 H' y                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
0 {' q# [' n6 k( c5 l% M( B                ep0_state=1;
. M- @( H- [- t                return 1;& R( \: U( M6 P4 p+ i+ J* S4 Z+ |5 n2 H: V
                break;5 i8 B6 P+ P, O9 j3 i9 d" E
            case REQ_SET_REPORT:5 z1 O" S3 H; `% ~( F  Z
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;  \( H$ [. }" m) ^6 P3 i- H
                ep0_state=2;
4 b3 W1 c0 @4 W! N                return 1;- R, P/ H* O" A8 P' n
                break;
: E5 q# W" c9 f            case REQ_SET_IDLE:: G7 @6 {- C) m
                USB_PMA[1]=0;   // Zero DATA
% u1 }0 n+ c9 c" q. }2 ^/ s                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;' \, }6 c+ L) N" Q
                ep0_state=4;( b+ z  B5 C6 J5 y6 Q
                return 1;
6 U# D2 ]; W! i: x5 W                break;+ i" ]& W  M$ d
        }& o7 s; O+ Q( f$ s
        return 0;/ {0 F7 [, X/ I+ J6 \7 @: M1 R% V8 \+ A
    }! K; |( r" P% S
    else    // standard
3 |, Y$ \, S6 H8 ~/ x, N    {& N0 [2 M! p* i) n( x! C* E) N" ~
        switch(ep0_std_req->bRequest)
% j, V# I" N0 X        {, s5 x/ n( y2 z- M
            case REQ_GET_DESCRIPTOR:2 w' s7 N0 ]+ J3 d
                return descriptor_service();
1 m% f) k  \8 |0 m                break;
- S! z) }' M+ J            case REQ_SET_ADDRESS:
7 c. r+ \8 j5 V! y3 _) f9 h                if(ep0_std_req->bmRequestType!=0x00): b: n% g& `" J, d- a7 m7 C
                    return 0;2 J$ S+ C6 f/ k' p- v
                usb_address=ep0_std_req->wValue;. U+ ^: ?9 P% L0 K
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;1 R. a* d0 g" c* D$ G
                USB_PMA[1]=0;       // Zero length DATA01 ]2 ~0 l5 |7 z" k
                ep0_state=4;    // No Data phase6 K" O6 c& o. Q
                return 1;( x1 E$ i7 D; U- J5 c1 `
            case REQ_SET_CONFIGURATION:* |6 [/ d# g2 u; @
                if(ep0_std_req->bmRequestType!=0x00)2 H* T6 V  X- N' z0 F. ]
                    return 0;" o7 ^) f0 B0 O# V' G- @3 ^6 q  d* y
                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
- j6 ^6 U& S! l4 e* A4 \/ C3 C6 i                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
: d2 A) y# o" }! ]* l1 m4 U7 y0 c                USB_PMA[1]=0;   // Zero DATA
- c  b* h: ]! n8 \& f                ep0_state=4;    // No DATA phase/ z) V1 M# I  u2 ]1 P- s; |
                return 1;
! n0 ?6 U6 f( a            default: return 0;
$ N7 n& P- f# t, `6 u        }. y" I. J7 @4 C; x
    }
  \8 e" s* F( q  c# p. r* L}1 i& k$ j0 |9 v9 z  {. k

( U% C7 j! q. @: K4 J) i) Q" G) K  d% Q" C9 u" n
- Q0 _3 Q1 `8 Z( _" Q0 `5 V
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的/ ~( a2 S5 ]. r: Z# ^* \# N: Y" ]
char descriptor_service(void)1 d. t" K5 z$ D! ^, C' D7 w
{" o1 m  m# s6 U4 A% Q# E2 N
    switch((ep0_std_req->wValue)>>8)5 Y; R3 q; q8 c4 P- G4 h% D
    {5 d3 |1 S1 A- l
        case DESC_TYPE_DEVICE:
2 {# d. r' F1 m3 s: ~            return ep0_preparedata(&DevDesc, sizeof(DevDesc));
+ a. q3 p5 o( _            break;' Q7 j7 k/ C1 p; r$ I! m8 ~
        case DESC_TYPE_CONFIG:
3 q. u- E; P2 x0 I6 a- W+ Y# q            if(sizeof(ConfigDescData)>ep0_std_req->wLength)
! X* Z% H/ N) l! X) l, ]7 D3 b                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);
. ^$ E2 o. [+ v# w8 M4 R: j            else
% F6 a) w  p) s/ C2 p2 L8 N                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));& R0 y7 W5 y, B- @
            break;0 ~$ Z+ X% J8 L2 x
        case DESC_TYPE_STRING:) Z, m* H1 _3 x6 D+ ?5 C' L
            switch(ep0_std_req->wValue &0xff); ]' b; Q( Z8 m6 M
            {
! }' Q" Y; m& i# L9 Z# S                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));1 a6 Q& f; Q# p& ~
                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));/ I' M- I2 M+ F9 S' `! l0 S0 _
                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));
7 g8 d& s; i: Q" V. G% ]                default: return 0;/ }' E/ P: _0 M% }9 {+ G
            }
9 |! X. L9 [: y3 m7 [2 E$ m; @+ a            break;0 {8 _" H9 j$ ]
        case REPORT_DESC_TYPE:
  l% n2 p' p5 l            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));! Z# p& d0 m3 X7 Y. h2 v
        default:
9 A. E& [& J# f; O            return 0;3 V7 c% m; W" ^+ ^+ E
    }
3 p+ Z  R2 S* l8 E0 o}  I8 Y8 L: }% A8 V+ X
下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.
7 c0 I) V3 B  uvoid TIM6_DAC_IRQHandler(void); e: w$ D) i9 t- G2 i
{. @) A# u- Q+ S* A* \
    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);
: f* E8 c: B: [8 q# e$ S& y0 B  x8 ^7 l& @$ P
3 d8 v. U3 a$ h) Q
    TIM6->SR &= ~TIM_SR_UIF;
' x3 e9 a; g+ g" S    prev_key_state[scan_row]=key_state[scan_row];
7 S" {8 b2 r$ x' G+ S* d6 \    key_state[scan_row]= *PA_IDR;   // update key states0 X/ g! J, A7 `! d$ R
    switch(scan_row)4 ~6 u  q  u, y- C7 B6 I
    {0 b7 z9 S5 |+ c& j7 T1 n/ j' S6 f
        case 13: // next row PB14
1 g! V8 m. ]6 ?0 W                GPIOC->MODER = GPIOC_DEFAULT;
, q: g0 h: t! C7 g1 x: J                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;
! s: C5 |+ _% ?7 \/ u4 z                break;8 g3 s% e, b7 W0 j% A3 v' l9 ^
        case  0: // next row PB15! U+ b& Q! f9 h$ ]1 B+ S
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;# m+ [$ A5 l6 a0 `
                break;$ c* |0 N1 Z: \) d
        case  1: // next row PB3
! z7 P8 |9 P9 `                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;
6 I! ^" f( E- _: p3 [* L0 ^! G' V                break;  }7 K; M; t7 k3 _3 V+ ~2 x- c
        case  2: // next row PB45 y$ k- R, P3 [$ k
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;8 O$ u+ R9 ?2 O( B. ~# E( p
                break;
: Z3 ^3 M' {* I* B( s% \% k" B        case  3: // next row PB5
! {! A4 s8 W' p& F2 y' n, b                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;3 t1 S) f% V* ~/ k0 v8 a
                break;
/ h6 y8 N+ v5 y7 P/ L2 B" X        case  4: // next row PB6
! b, A; ]) D/ o, E9 s                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;$ l7 Q9 v" J/ R& j
                break;
7 @" b7 F; F- e        case  5: // next row PB7! \* ]6 {& v' ~0 M6 }2 n
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;4 ^6 a% ]" z2 D. f! `& n; q
                break;' b3 d7 _0 N5 C) `+ R( ]
        case  6: // next row PB8
. f, C2 L. D' G) W4 _- N) S                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;, D% P+ }8 c6 f3 s7 T! @6 _
                break;
) K& G+ S. A6 ?9 w        case  7: // next row PB96 H" N& E# Z4 B) o- \) {( W
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;
/ K: |1 K$ d8 k- r                break;
: \) V' k: n0 B* D' {( C9 {: {) _3 A        case  8: // next row PA8
9 c8 e# }) W; p- W& {                GPIOB->MODER = GPIOB_DEFAULT;
! Z; l9 q: v) G) h7 @! A8 a                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;1 t5 A; p# }0 n5 M8 Z8 a* _
                break;
, n: M9 o( k0 E. E        case  9: // next row PA9" C' P7 L6 r$ c) P& `0 c
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;
2 d* n& C+ g& A: T5 ~                break;- ?, ?. H" k: @' S
        case 10: // next row PA10) X" e8 [# J; F- O: ]+ ?1 H* e7 i5 V
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;9 Z1 s& P- N% `4 Q; k4 J
                break;
) w) K% `0 L8 I6 h        case 11: // next row PA15
9 N; i  W; \. Q$ e; D                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;7 u, s' G/ |8 z5 A
                break;- E3 ^. m; K2 n4 U" ?) Z
        case 12: // next row PC13. D9 F& g7 D. s# Y
                GPIOA->MODER = GPIOA_DEFAULT;
8 H) l: |9 @7 ?                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
( o$ |+ l9 D6 L+ a                break;
$ D; u( ]0 c, O$ F    }( J' D( ?3 e4 k- L# ]# V/ v! I5 J, e
    if(scan_row<13): N/ ^6 g' C1 _; d/ c2 s
        scan_row++;( w6 L1 X) a9 Y& l
    else" O% t7 S5 K! W0 m* O8 z" d7 A
        scan_row=0;
; N" ]6 H. n+ t. [4 S1 H}. |# Q8 h! y# M, @

6 t- O( U- F6 \, G0 L9 l) ~( U2 R  w; W4 J; t
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

3 p. F/ n' E3 P
, M( v( o8 p) }# p- z+ D; z, b9 j% v
: p. O! V; d* N  f) W1 K! Yconst char hid_keymap_qwerty[14][8]={7 O5 @. }. l; f6 |3 ?
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},
6 t6 p7 ?. O8 X# [6 }9 [    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},- g. r$ n( E7 Z/ ~* E
    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},) p1 }( ~/ G- Q  T8 K: T
    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},
+ F6 C3 _# ^0 [7 H2 ~- |    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},
3 r0 }# k+ X2 |# t$ w3 ?    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},% p: h$ v* h5 U, I& ?6 c! ]
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
' y% X: `4 q1 i    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},1 P0 I* I" u* r. t/ v
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},0 A# `2 E; f. J
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},& M: z/ Y5 k% i4 H8 q
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
0 K+ I( ?6 l' z    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},3 P% _+ j' u6 P( Z# ^
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},! c/ ]. H1 Z+ ^
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
+ n) Q7 _9 _& d+ f  K. L3 _' _0 I};) \6 M8 ]  X( J# q9 e% X
2 ~- N6 B1 w" e" J7 v  w1 D

/ Z8 H! g5 R- x8 @% y$ |0 f7 y+ H: nconst char hid_keymap_dvorak[14][8]={
/ ~% S4 W" O, O; t9 r    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
) h, u4 _: E; P7 g2 F2 C/ {0 a    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},9 ^7 m) O; T8 x/ Y* {
    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},/ U2 G$ ]4 T% L7 _) k. }+ d5 S( u
    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
/ F+ c5 f$ W" x3 T' T% w- w    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},8 z. V* D' @, H0 U3 K
    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
; {8 t8 H7 I- V, `2 A    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},7 r1 T$ Y2 T& h8 O, B
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},
' U1 J( j' X9 Q  V5 f5 F! N    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
" N: }2 z7 ?* U6 U% j9 E    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE}," a7 _1 V( P  u7 R( K7 w9 Y
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
7 |/ ]( \2 g/ w5 b    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},7 a  u4 N5 D8 x: h3 N, }1 b
    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},4 n: z7 e  g! k3 a: z, \% |% \3 ^
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}: }& n6 ?  e8 n& F* T
};
; T9 U2 [2 N- D" T/ |
! ~0 H' v+ \) p1 D9 x, ?+ s0 O0 o) \0 ^7 L8 H8 l8 I  ^/ ?
上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
% l0 j+ v, g" K) g8 o, b# l( [: t, t# E; X" `/ p$ f
HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:
5 A5 f( s( K  h# G' G( r% Y. a

0 t' V8 u! y! x- w& g! p3 Uvoid update_key_matrix(char row, char col, char onoff)
& @) h# k% |( V7 G4 r{/ A7 H) s" c8 x% ^5 q8 c
    static uint16_t hid_report[4]={0,0,0,0};3 g9 I8 X7 p! ]; U% G
    static char (*hid_keymap)[8]=hid_keymap_dvorak;3 ]: P9 T& n; P# L+ r% @! L) }

5 G# i( M" R) l% G4 }
$ ?' L1 U) {" y- H
    unsigned char key=hid_keymap[row][col];
* s5 p4 h- }' ~( D2 Y$ w% F    unsigned char *report =(unsigned char *)hid_report;
- I& B1 g% m; r  v7 u    char i;
: f- I' }: o8 k& G% n: O8 X& P) D4 v' _0 U

5 L9 \. h6 O8 Y. F    if(key==HK_MODE)
2 R9 j: R. I* C    {
3 S' Z. g4 w# R0 ^        if(!onoff)
  B* c' B5 t# w# W7 t. b+ M        {
9 u5 W. J7 Z5 q( }* h            if(hid_keymap==hid_keymap_dvorak)
, Y. Q% a; ]5 Z. W4 o/ v            {/ I! G" m$ q6 Z# y% P) U2 |2 @
                hid_keymap=hid_keymap_qwerty;' |0 f, d* C  Y3 u
                GPIOB->BSRR = (1<<2);4 B% b5 T0 x# e; s' x
            }
. n1 I: F" f( q9 u9 ]: p1 W3 i            else
! f/ \$ u+ U' g" U! Y& a            {% T5 i+ s$ Z$ C1 Q9 u8 L
                hid_keymap=hid_keymap_dvorak;
" O! k( S( v" ~( ?5 B. z) Q                GPIOB->BRR = (1<<2);# O( e7 W; [6 Z' t
            }8 F: S6 P' x. g: k9 D+ q! Y6 T
        }6 {! d* p% j, a
        return;1 Y/ q, r6 ~+ k+ p9 U9 s, A  k
    }9 d7 V6 h$ s+ }* I' E
5 b5 f! |0 T# u$ ]$ J. a

4 |& R+ u$ u: ^    if(key>=0x80)   // Alt, Ctrl, Shift
% Z( Z. ~5 j4 f. d, x7 l' j9 c    {6 b! V: t! |; V4 J2 \% H+ t1 U
        uint8_t bitset = 1<<(key&7);1 u; O1 }6 p$ ^% A5 B
        if(onoff)   // non-zero is key up0 H9 b+ j$ L3 z: P( ]$ W
            report[0] &= (~bitset);
9 j" o8 g' U  V5 K3 P* G3 r        else9 {; B+ s2 G# d( V
            report[0] |= bitset;
3 w7 {) t( Q$ u% w. E! M    }
7 s( W& y' W8 Y/ u5 i- Q1 v, U    else- `" \1 L( i3 t$ ~
    {
4 G5 l$ ~- `! P# i        if(onoff)   // non-zero is key up' `4 ~& V: [3 N0 O2 D
        {9 P/ b9 ?9 |' f+ E
            for(i=2;i<8;i++)
$ _) q" ~- K. z0 k, m' Y, \3 C            {  G9 g  Z& i  X8 B
                if(report==key)
3 b, W: \2 F- `                {9 ~, l' L9 r3 p# q" h6 u
                    report=0;
0 h! J9 v! J4 t6 T5 A6 z1 w                    break;
* D8 \% f% ]7 W$ ~0 K                }
! T. C$ l; q# v( ], e1 m/ V            }! c/ E$ ]( o' R- K6 H
        }
/ y; R( i1 V- ^! u- z2 {        else% p) S5 z6 l* g. R% _; S5 i
        {
* e. K# J, d! h            for(i=2;i<8;i++)  B* ]4 U& I3 B" y
            {
3 a& k- w! H1 S* Q6 o( ^                if(report==key)
5 q# _( [7 ]  i2 F$ p( e/ T. e                    break;
' p4 P1 B" c: n; i4 @" t1 O                if(report==0)
8 m+ h; K; S  l  g) `                {
, ~; a7 _* f" f5 j                    report=key;
$ E; r5 }3 i8 c) K                    break;
# m6 V7 J5 {$ w. x9 h                }. |9 Z0 l/ ?! i9 T0 q
            }/ H3 U  ^* g# K4 o
        }( q  j& F: Z' F
    }
+ Q" T* E" [' R( H4 A    for(i=0;i<4;i++)& i  N/ n$ ^$ V, D: N
        USB_PMA[192+i]=hid_report;6 N8 ~0 \& Z, y# G! Z# c8 N! k% k) p
    USB_PMA[5]=8;   //COUNT1_TX
- q  C! N' C3 @1 J! ^    if(ep1_wait==0)
: R* ?+ }. O" x; ]) O. Z, P/ X+ G    {
4 J9 C; y. R5 j! ^. I$ e        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;
7 n. a! K" y% u/ E- ]6 u& z0 ~        ep1_wait=1;- _1 X4 i5 ?3 v- S8 `" e
    }
* t' Z! |0 I/ [5 O# q$ K}
+ z0 a; I3 g6 V8 p% Y+ @6 U
; r1 B, w8 U5 m7 |  E% Y' o' m/ R/ I# o* S. ?
完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。- ^: h; c: ], K( X0 C" I$ j3 J
keyboard.zip (8.7 KB, 下载次数: 6382)
1 d0 c& O: ~6 Q$ c' {

. {3 u2 L- K# g. E+ W5 f
2 N6 n3 T; G+ L& d: N8 }  }$ c& U
4 d- t$ I) k8 C0 G/ a0 z. f2 P& E6 M1 p, [
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘
% ^9 @$ f8 Q2 r8 p" {+ R1 Q不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48/ X$ @" r& \9 S
刚开始我以为要把打字机改造成电脑键盘
& X5 a4 g0 J. b/ a5 o不过楼主也很厉害!

3 T. P% b2 M' V, S; a$ X哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
( C& ^, P; y5 r9 L: K0 f6 n% ]
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-1-8 22:12 , Processed in 0.174492 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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