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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

积分
288
发表于 2016-9-21 20:34:19 | 显示全部楼层 |阅读模式
本帖最后由 miaozhuang 于 2016-9-21 20:35 编辑
% n+ c. I& V# s1 i2 s' S" _
. V8 o3 J- n; H) N! \http://bbs.eeworld.com.cn/forum. ... digest%26digest%3D1
9 o6 l& f+ L# D6 R这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。1 H+ u' f" @. l# b+ z+ a

8 y: Q: ?: J8 `! X# e( V9 E0 \在看这个帖子的诸位一定都在用计算机键盘吧。键盘上的数字键是1到9从左至右排列,或者是右小键盘区那样三个一排有序排列,反正规律很明显。但是字母键却不是A,B,C...到Z这么按字母序有规律地排下来的。我刚接触电脑(其实还是学习机)的时候,没在意这个问题,觉得是要盲打嘛,反正对两手的手指头来说,按字母序排列并没有什么好处。于是用多了这些排列也就记住了,从来不管它为什么要这样。其实,PC的键盘键位排布上是延用了打字机的键盘,这是设备演变过程中很自然的一个延续。打字机的历史就要早很多了,我没有亲见过打字机长什么样,而且,咱们汉字是铅字打上去的,和英文打字机方式完全不同。

2 b. k& {) `5 @% K
( j( B3 R9 b* n
235140i3a36qivqzuvmt5q.jpg.thumb.jpg
2 x. Q6 v8 a# K+ k7 }3 u5 C5 K& K
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。
( K0 S. p0 q5 x# z2 }6 m 001734klbyoluenuwz4h4b.png.thumb.jpg / B# U# E! S- h5 }; u! a
为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:
. _0 t+ {: u3 c 003625r2agx2f5v922cf2f.png.thumb.jpg
7 n% K; c" u" T. V4 g. H其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.1 z+ ?  `1 l" H2 \
Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:

# v% x3 ~& _: `2 [+ D& x4 W 005836yvs0wvovwsssgd3o.png.thumb.jpg
9 w0 c7 ?0 }4 eDvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。/ s, a! S' |) b1 ?: _* y* m9 K

2 A4 c9 Q/ D# M( Z2 B' g我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。8 a- K- d; {) c  a8 O  t5 @/ e$ [( r1 V1 e
! v( x3 P- ^2 c, C1 Q% K
到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。
4 {# \8 H) G0 J6 a2 i( K. m
4 J) Z# Z" q+ ~+ U9 p/ V: Y机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:

# @, }  v" u5 ?. w7 \' X7 k9 m( l 123206vx24rfxfp2dfrpxg.jpg.thumb.jpg - K) v8 ?! i" a: J0 M2 v
----------------------------------------------------  分割线 ----------------------------------------------------------
9 a9 ]% s3 L7 c4 t+ o3 Y% `3 _9 M5 u5 U( k
先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。
  x1 q/ U5 C* p$ f9 ~8 `( i. F2 N0 |* I2 _
020011osionbunl4ui44vi.jpg.thumb.jpg
# v. c3 j3 ~5 L, s4 I2 o( X轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。
# D4 {  }2 W" B$ N( g, c6 ?. E 020017j8ycmnv7788bqv52.jpg.thumb.jpg 7 V# [# y' [6 I" D4 v0 {2 ]
特写,80C49& Y7 D8 o; m1 r) S0 c
021040oujzuvtut6iujtvz.jpg.thumb.jpg
: u) g/ a: {. |- hLED部分,使用了一片D触发器锁存指示灯状态.+ Q, p0 ^& i, c6 Q, C2 r* r  |
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
2 m1 @5 k/ u* q暴力破坏,将80C49拆掉
) V4 A3 L" D$ ^6 ]6 C 021113e48qq98vyohvhzzh.jpg.thumb.jpg / ~% l/ j3 k6 J  q
拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。
( j* J& ]% w) {7 }# p9 [$ j 021125nc9az6dj33rlds2r.jpg.thumb.jpg 3 m1 [: }7 s7 @( d, n  N3 b) v
焊好元件后的板子,准备替换80C49) j9 r# o( ?% }
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg
" e+ c/ \7 Z8 P0 Q2 _  K用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。
" p( w* K% T3 w+ o1 p+ V% L 021104shifhnrqbr3o5nlo.jpg.thumb.jpg
( L7 l( |0 C% T; Z: i, S1 [这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.; n4 I* g" B! P: @1 [/ }
022003ym1p9u4ug40280uu.jpg.thumb.jpg
, Y- o' ^7 n9 `: b' Y' l( A9 M开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。, m9 s( E6 @4 B8 K8 ]) H' v0 z* `
023313kt141q9qajtol7ma.jpg.thumb.jpg
" M/ U* P6 M/ d我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。
7 J9 U, T8 k! Q% k5 ]+ Z. W+ f 023322nt7l5xb3ltttkltt.jpg.thumb.jpg 9 \& Z; H: J% c  \/ u4 |4 {: h3 c4 h
主键区键帽就位: Y7 p8 H0 S5 h6 b8 `
023331hin88e8wkrwzwikx.jpg.thumb.jpg $ i, j6 E; Z$ B/ ^% ~+ h: O* X. G  q
编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。8 i- s1 _! B  Y: m+ B, W
023336wjzlgopugg1jyy79.jpg.thumb.jpg
  ?/ k2 f- t. \; ~最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
7 I, X) y7 L0 |: A; X: P0 m 023341sffu4j3g2323h6fl.jpg.thumb.jpg
9 k9 E  T0 D: h6 q
9 {6 u, M/ b3 |' `, D
----------------------------------------------------- 分割线 --------------------------------------------------
9 n* H( s7 b# S0 }- h  l
- g1 @' `3 H: I$ [

9 ]) Z& ^# z% u( O# |7 g$ B9 t  }4 _% c0 {& }' P$ U
. y+ f4 Z; a3 E& n& W- R/ @$ D7 N

& ]& V0 u9 c, Z0 e
! L0 d! @7 W, b6 Y3 E

  B& s2 [8 J) a$ s  `0 ~! e' [
# r2 {3 x9 Q( i5 Z' [3 S+ F& c. i# P; h% |& [; h( e( D9 V( H5 J2 n
, x0 l- I# Y- ]2 b, i* A

0 H$ j$ t9 ?9 A0 l; o
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。
4 O+ v3 w5 }' S0 d$ g+ ^" A. h80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:
7 ]8 Y' t7 n) t! v 104025nzibm2rmiomhyirm.png.thumb.jpg & s& u6 O: H2 ]7 a) k7 ~4 E

! T, l6 [3 a& t4 i1 M7 P* L, L4 U6 E其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)2 b! k0 g6 @" \- A" \
105004zkrez5houvkkznko.jpg.thumb.jpg 0 p: h( {& h! G5 p. a! J  k7 d, k0 u
" }* z7 q( e, |% v  l& D
扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。
7 N6 V& ~& F. E7 w( c$ |- e  B+ I3 C' o1 B
这是我设计的电路图:
- a$ |  s) g; U$ v& Q3 q 110344ej2z2oo2rflo7oe7.png.thumb.jpg : |& G8 E" P2 D# X* R8 N

  Z# p+ x4 z: Q! i. }+ |* @! OPCB Layout:8 J6 ?$ R" k, J0 |3 b9 f% u
110847jjbjvt34vwt3v5bb.png.thumb.jpg
7 o# u4 B+ ^+ g- T

& S% P0 L9 q" `. c* x/ M! f不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O.
) \' L$ ^9 ?0 c* `+ }
; V* o& ?- w2 S. Q0 S
! W! A0 H' ]$ F" D6 o
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑
7 z; h: c. \. o% [9 J" H) k3 t
5 \  Q5 D6 D. o软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。1 _/ {& U# |5 ]/ v

% ^5 n5 g! k0 }- e5 H总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:' M" ~2 e  k. f+ V+ @# V' K
113818pmrfsb6z0byt6t06.png.thumb.jpg
' e; Q! Q4 ]9 x- z* k) ~% D

- m6 V1 V4 O8 C8 X其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)5 |" c- _1 k5 l$ X' n4 b

2 F8 t% r# m5 J* [1 j7 DUSB的中断ISR,bare metal哦
( E8 s  Y9 Z! f1 }5 ^

$ o! Q: C9 C5 w% [void USB_IRQHandler(void)
* c' E+ R$ I$ t3 P6 T{5 a, L3 H# p- \( k4 e
    if(USB->ISTR & USB_ISTR_CTR)
2 f& s  D6 O8 P2 S9 p    {3 S8 u, ^' h( `# r
        if((USB->ISTR & 0x0f)==0)   // EP_ID==0# r1 ?# `$ \) l( K) S+ V
        {+ J% l; E  [) [9 }9 w
            switch(ep0_state)
; Y/ A/ e, i8 {( K. F4 w  @4 V            {
  u5 o! O# ^5 Y. |                case 0: ep0_state |= 0x80;
; i1 I* v& p! |: e5 f$ G                        break;1 h2 h6 |/ c. Z" H" G! l
                case 1: if(USB->EP0R & USB_EP_CTR_TX). _1 H- Y; X+ O) R* c5 C+ {
                        {4 \, z* Q) G4 v  G$ `  p' w: D- d) H
                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
+ ~! \( c4 H" o  d                            ep0_state=3;) A0 E$ j; g0 ^  a% `9 @% C6 A5 V
                            return;
  S) |0 U0 j5 S3 B6 h                        }8 S$ _# e% [) y0 d; y$ O7 K
                        else
: n( o$ {: Z2 W6 b9 ]- [( O& f                            ep0_state=0;
/ {$ N  c; r3 g$ D% v& C. ?8 q, {; q                        break;3 `# P* S: s' @- U& X  o
                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet6 M0 j! O+ D- |- F& ?' S0 ~
                            ep0_state |= 0x80;/ h8 x  N& s7 w( R, B
                        else
/ y. t; Z0 v' V4 s                            ep0_state=0;
0 _0 \: k% t9 f1 I8 t                        break;
6 j" f, f4 X# O* ]; {' A                case 3: ep0_state=0;
- u8 [- I5 Q, k, s$ i; Q* u                        break;
4 l5 D0 `$ i  g6 n1 U                case 4: ep0_state=0;
! u2 d& D5 h" k                        break;
& ]* ^+ Z( s0 I- J! E% `8 }' Z  I                default:ep0_state=0;
9 A/ r$ n1 S# T; b. h                        break;* z# a' L( d5 k$ W9 d
            }
7 |" J( p+ a9 h+ a5 s1 y, O4 `            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag1 \) O# v. _; \4 v3 U4 y2 ~! N
            return;2 G' x& i' F5 c* s
        }+ ^. Z$ g0 ~4 s8 k# w9 K: g
        else    // EP_ID can be 1% o  w% J: t" t: d* d* W2 a
        {
/ Z1 K5 h2 l" |$ S) f. q0 Q            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag
0 H' @$ t% I! y5 H: _! n4 v+ |            ep1_wait=0;. D3 c) q. x! w
            return;/ _; }5 m3 X& X7 H! @+ |7 ^1 I
        }; v( ?5 {$ c" ~& J! K
    }8 \) B# Y5 e: ~% Q
    if(USB->ISTR & USB_ISTR_PMAOVR)
, r  A, h  d. M7 S/ j  I& X    {* w! h3 \4 [2 H, N0 A- e
        USB->ISTR = ~USB_ISTR_PMAOVR;
  }! `+ f0 e# D    }
4 a; W7 ^* M3 M) @    if(USB->ISTR & USB_ISTR_ERR)
6 _6 E7 u" L4 G* w$ J1 [' j: s    {3 I- F4 S$ D6 i& i9 s! C1 \8 C
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear
( K0 X9 l8 `, F# T- K1 `2 }    }8 T- O! Z8 w+ _; k2 C
    if(USB->ISTR & USB_ISTR_WKUP)
1 W/ x1 x. |6 C% ]# \    {
& V) ^2 ^* G9 D) Q% V        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
4 l/ l2 b- K2 K) d$ i8 E) R  T* k        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear) t+ T0 {! T; p7 ^: L
    }
; Y. \. B: ~7 M/ R    if(USB->ISTR & USB_ISTR_SUSP)
& ]+ m0 H2 Q5 |  u. {, X3 i7 q! p    {1 s/ E" X* w0 R, V
        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend& l: ~( r2 R( A# R9 m
        USB->CNTR |= USB_CNTR_LPMODE;   // low power: z8 m7 e. F+ K+ \
        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear; b3 k: r' V" U* a$ y
    }
# u  E6 w; }) o    if(USB->ISTR & USB_ISTR_RESET)
) M9 `5 w+ c, `7 w+ t* l' n    {
2 O  e& L3 o/ ~% d& r6 l        USB->BTABLE = 0;    // buffer table at bottom of PMA$ O3 I4 P; N9 n0 ^' ?
        USB_PMA[0]=128; //ADDR0_TX
& F4 ]% e: w: d3 a* G; M2 f5 A& x        USB_PMA[1]=0;   //COUNT0_TX4 T/ Z4 ]( p* }. C; Y. m7 E
        USB_PMA[2]=256; //ADDR0_RX! @9 d8 g, ~: x# y# @& C
        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes+ l( B" d+ j: `' M2 b# z
        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;
5 E% B0 V3 z5 n# g3 J- l        ep0_state=0;( y9 P: H2 f: m# B
        USB_PMA[4]=384; //ADDR1_TX
5 C9 F9 A" A7 L- L        USB_PMA[5]=0;   //COUNT1_TX
; |  F" |- L7 m+ j8 w        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type* V! I7 w' U/ Y: Z  p% X
        ep1_wait=0;
5 T( g$ `' V+ A, {( a$ N# G$ N        USB->DADDR = USB_DADDR_EF;      // enable function, p& P  ]9 p1 j3 O  A
        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear
6 ^  f) O% X( X% L4 `% D+ Q    }: [7 @) ?1 d1 F
    if(USB->ISTR & USB_ISTR_SOF)5 D$ A( K) [  g. ~$ ~4 a
    {* P+ Q" j$ D1 N9 k1 U
        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear
0 H  o: K/ Y6 k5 r    }, ]) ]0 A* {. o( m5 z* A# O' \
}
3 d. l$ \3 _3 H( ~7 d, B3 y% }0 u; Z/ y8 }- ]
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。0 Q  [" a8 j! H& B3 P+ B6 _
2 ]2 H3 l: C& T9 Q8 c" z9 |, l
主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。
1 Q( }! K2 i! z% o8 O5 M, B( |8 J: K1 C3 R/ t+ k7 \. N
    while(1)
8 k7 e( X$ ^5 y, ^" d3 w    {
( I- ^* E: [5 [" ?8 C- {* q        static char row=0;
) M( I. d) ]1 Z8 t) f        __WFI();
4 Q# Q' T1 `4 E        if(ep0_state & 0x80)    // request data processing
* ^: q+ w. l, _# n! `3 i        {
6 Q- ?/ N/ W* B. S) ~            if(ep0_state==0x80) // SETUP phase
; D4 V5 i. `  J+ ]" E            {% M, L/ e. N/ ~' \0 G! E* G  @
                if(!setup_packet_service()): g( D. O! s, ^; p
                {% r$ ?9 A3 U% M' W/ {% B
                    ep0_state=0;7 o" u& I  \+ H) {3 v# l
                    // not supported
! p& f! G3 M( I" g                }
0 z. a- f. U. [9 @1 Q0 F                // ep0_state should be set to 1 or 2, if processed
9 H% [1 u* u$ x            }4 V. G4 a- _3 ?! I. w1 o, d! R, ], W
            else    // OUT phase
4 J2 q. J& a" r1 \- r( L; b            {) i0 F' a& f- b6 L# z. |, l* w
                // process data' h1 \! q+ w& V; V6 L
                show_LED(*(uint8_t *)(USB_PMA+128));( u  V7 K( x' l- b
                ep0_state=4;4 q0 a% F! T( z5 l7 J. _2 X) k4 x) ^
                USB_PMA[1]=0;       // Zero length DATA0
" ~1 y( {& k4 O* J. `, H                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
: J/ j' Y$ x5 w8 h            }, @, x. ?" G7 Q: _. @+ T
        }
9 E; h' Q& M$ r' L/ F4 c        else; b! q, I# W5 a/ d7 t
        {
6 F5 v  m) w' V1 s$ H, s            if(usb_address && ep0_state==0)9 g! O5 P! D8 j8 s, N
            {
8 p9 v: u) G, P6 ?) S                USB->DADDR = USB_DADDR_EF|usb_address;
: G! y) ?  k# \* x$ Y5 E& ^7 Y                usb_address=0;' T3 G" P# x: \0 J0 f: D2 ?: U: x
            }/ A8 ~3 p; l7 N6 b! H; T
        }
2 r2 M$ ?& L4 f- d        if(row!=scan_row)   // new scan line
5 Y7 V0 R) [2 M; R0 Q5 N/ k. @        {& e) K1 k: a# G
            if(key_state[row]!=prev_key_state[row])
- T$ c  ?7 s2 P+ e$ G            {- g# ~  x% x4 K7 J* y7 l
                uint8_t test=0x80;# _9 g  F- ]0 |" U# k
                uint8_t diff=key_state[row]^prev_key_state[row];
/ A$ P  ]" e4 u$ b: H                for(i=0;i<8;i++)4 t: z9 k2 o4 d# Z  D
                {  X, u+ ^' w1 F4 a% w
                    if(diff & test)* |" f. G: M9 @
                        update_key_matrix(row,i,key_state[row]&test);
6 E, C7 l% ~% W& M3 O                    test>>=1;
/ F0 d$ j9 `6 Z                }# t9 {$ L! ^7 `% o: h" h: u3 v6 _
            }+ g) `' Z* j0 |% H( n
            row=scan_row;
1 K" u* ]3 z" e/ ]( j        }
' \5 ]* E! R6 W    }$ h% {0 G4 {# N! ?
2 {7 V; w  q" L8 G7 j+ X; ^

) ~7 y7 v% w4 P9 m/ qEP0的控制传输,把用到的请求处理一下
5 W% Y& x4 P7 P" |- ~& d# o# Cchar setup_packet_service(void)( [) |. ^8 j0 W1 z; D
{0 c! Y% ]; y+ `5 H
    if(ep0_std_req->bmRequestType & 0x20)   // class-specific
! Y; ~$ R  ~: O0 k: F$ c/ z    {
0 i- F. V- N% X, d: l0 ]        switch(ep0_std_req->bRequest)
' w  Q* b- h* R, V% `( @4 t        {
6 P% s8 S) I6 u. O/ F            case REQ_GET_REPORT: break;
2 K  O. n) m4 @, r3 P8 v7 p            case REQ_GET_IDLE:
5 A+ J! t+ q+ y0 [7 O0 w                USB_PMA[64]=0xfa;   // return 1 byte
. [0 y  ?3 H9 H                USB_PMA[1]=1;( M$ b3 I" h1 E* J  o
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
! K7 W9 ?$ n& g+ {1 H                ep0_state=1;- f& \% l& {0 u2 J
                return 1;
  Z; S# q0 n5 y1 z* T7 n5 h% [) c                break;
4 |% I5 L4 Z# N, {  ^5 h3 e0 R            case REQ_SET_REPORT:7 l, l* e2 L4 j5 J
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
9 P6 }+ x3 J& m! Q9 Z                ep0_state=2;/ j8 |# e! Z2 E% j
                return 1;3 P9 D3 s8 {% J" E$ {$ h( R
                break;
: a1 G; d) W% C+ U0 g            case REQ_SET_IDLE:' k! ?, @) B7 L" H$ F# D
                USB_PMA[1]=0;   // Zero DATA
0 m4 f5 p- P" [' k/ q' R                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
- A: S# [& }% f4 B" R- P8 d/ k                ep0_state=4;
, M- j8 U# d9 b; _9 L- v2 k+ e                return 1;
! g$ Y, _' }; u                break;3 g, ?% a. x# n4 @0 b3 L( X
        }
) t* k  \; E1 D2 D        return 0;4 M6 h1 g- x( a& c' c- q  n+ |
    }
0 x, }6 C" O" L' N5 [    else    // standard3 n7 N) j; R# W0 q2 F
    {
9 n& T( f% T3 F" C, u8 ^, r6 h: _3 G        switch(ep0_std_req->bRequest)9 X# k4 Q8 Y% T/ N$ [' d; t
        {' F9 m% `3 V  j' R& I0 E6 a
            case REQ_GET_DESCRIPTOR:
$ U) e! u6 x1 Z# N1 L) L7 O                return descriptor_service();! r( i  n  I  x  U
                break;
2 a3 |5 u# ]1 o# ]! i0 N+ d; p            case REQ_SET_ADDRESS:- j' s, a  P% w% M6 ?! ^
                if(ep0_std_req->bmRequestType!=0x00)6 n! i5 Q1 Q2 n$ H) m* _, ^! Q
                    return 0;
. H9 J% ]8 Y. F, [+ U                usb_address=ep0_std_req->wValue;
& _# m4 M0 L7 S. E% n* O                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;: y, ~4 s" |, t# z( w" a7 n
                USB_PMA[1]=0;       // Zero length DATA0% r% o$ O; n9 b" t1 d
                ep0_state=4;    // No Data phase2 E/ M7 W" W  k6 o9 ^( K( t. S
                return 1;0 {3 b# N- `/ k' d
            case REQ_SET_CONFIGURATION:( ^$ @; C: K, C# r
                if(ep0_std_req->bmRequestType!=0x00)
* ^) b8 g" p) z$ ^+ A" ~                    return 0;
6 ?) @( w/ ?- j0 T8 y  o                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;9 a$ w  B+ n- ~, z, E$ R1 P$ a3 L4 `. _
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
% n6 A$ {3 _: m0 l* z2 ?; ?                USB_PMA[1]=0;   // Zero DATA
* @* d+ \& C( l# V$ e! c                ep0_state=4;    // No DATA phase
" u7 N. Q3 R/ k' B                return 1;. T' K+ S' z$ @
            default: return 0;/ E8 w. @* X% F5 r/ l1 G
        }
8 ]7 v( p3 V0 S8 _* ^8 x    }0 K- @- \' \; y' P; {
}9 d: h' S, w/ N+ U& ~
. j8 o) V6 K+ e  I% X

( {+ q/ T# k- w- B6 l3 N2 Z1 r0 j
# M; N1 [" O. R$ g1 O$ R8 [
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的$ M5 G4 w) I* ]) D0 P# g
char descriptor_service(void)) Z+ d) ~9 F/ i" T5 k4 K! x
{
& q& O9 e1 Y8 d* \' f$ U& N    switch((ep0_std_req->wValue)>>8)+ |5 e" G3 u/ U# _0 S
    {' ^  k7 m. {8 d! e
        case DESC_TYPE_DEVICE:
; f7 [1 K. ]8 J* f            return ep0_preparedata(&DevDesc, sizeof(DevDesc));5 B. @4 X$ b& G4 K% X0 A
            break;# y! f0 G, f: i( B" \- f3 G; Y
        case DESC_TYPE_CONFIG:- g- ~6 m) l' N
            if(sizeof(ConfigDescData)>ep0_std_req->wLength)
7 W+ c! b! Z2 b                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);+ `1 Q9 u" h9 _( J9 U
            else# C: }" D) `2 u0 G* B, O
                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));; D( s( @; c# N
            break;
' O8 G6 r( o0 x/ m1 }6 A        case DESC_TYPE_STRING:/ u5 H. C* J5 Q: X3 e! E( }% y
            switch(ep0_std_req->wValue &0xff)) v% _) V( Z, T9 A3 y
            {7 r! G/ m% q' E/ ?3 W1 }7 m/ m2 c8 [
                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));3 h3 J! o/ V% k
                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));8 W" O0 y) o% F! K8 c
                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));
0 V; w3 j# F2 T% f                default: return 0;
: G! |0 i, c7 J/ I+ I; p) Y            }
" `' n& z9 d- A) R            break;, G3 o) d+ j1 Z; W
        case REPORT_DESC_TYPE:
8 M" F5 s/ ?" c" r2 g# i4 m            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));
9 s) f/ b1 E+ }        default:5 s$ {$ K& {3 i) E' a
            return 0;) \5 v- Z+ W3 W: S
    }
; ^, C5 k1 b& q}
8 b0 c4 g, r- d4 j" P下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.
: O; l/ d4 n, }void TIM6_DAC_IRQHandler(void)
( w/ l+ f9 ^* E# P2 d& r! T* [{0 m0 @2 `, P8 L* F; D8 r
    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);
; X8 E8 e, [5 X1 F" d( A( s, ~/ d1 B, Z) G& T
3 E; B# f9 X3 `2 A! N7 f" X
    TIM6->SR &= ~TIM_SR_UIF;
4 S+ I5 z+ F; z3 D0 s    prev_key_state[scan_row]=key_state[scan_row];
. \# n$ t. H1 [    key_state[scan_row]= *PA_IDR;   // update key states
. V1 M$ e& k7 c0 O7 a2 W+ h9 e7 t5 E' x    switch(scan_row)% k5 }7 O8 b( `  y. [
    {
: g) ~7 o1 b7 e) j* c  F        case 13: // next row PB14
, w% [$ h$ ]: c* J7 E' t5 }                GPIOC->MODER = GPIOC_DEFAULT;
5 U2 h1 j4 Y! C/ {                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;2 ?9 m% f, Z, }4 Z. s: h
                break;3 O6 v7 ?' u& F, T# S
        case  0: // next row PB15
, F" c* V' f" O: z2 s                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
1 X) a$ i1 |; S" M                break;
( W$ T- X8 U$ n: z        case  1: // next row PB3
4 m# U+ J' I( W9 ?4 R# K                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;  Y+ v5 k5 M" C" O
                break;
# S1 _$ W; h' c* D        case  2: // next row PB4
2 r( R8 {) Q) m& U4 ?                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
$ D) \  t* V! V) z1 l( U                break;. I5 v! k: ~. b1 p  @7 R. G8 x
        case  3: // next row PB58 `% i. \; C0 A, Y$ @) @3 c: E
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;9 d  E* x  p. w2 H6 E$ T
                break;
3 Q3 C7 j' x+ k: s4 T) T( v5 ^+ n        case  4: // next row PB6
/ Q( b( t+ @) l, B) I                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;/ ?* d4 r1 i* K+ E) _1 t8 m2 B2 M
                break;
5 x0 I8 P# o+ o: p        case  5: // next row PB7: P2 w! M+ d) D8 y. M" i* I! R
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;% r' M# N: c& T( T& U, I2 w, d
                break;" W0 v) ^* C- R* J; D2 O% k
        case  6: // next row PB8
3 p% z( d* |+ g9 Y                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;3 D# ]$ p" r2 \8 X" z
                break;6 a7 e- P& b1 {) C+ J
        case  7: // next row PB9
- _1 Q. [0 U/ B, H8 U                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;) |' w! \7 r" D
                break;
7 [. d9 h6 M" o) a- z, \        case  8: // next row PA8
0 ?; M4 ?/ M2 y- |" |$ g                GPIOB->MODER = GPIOB_DEFAULT;8 t& a+ ]4 t0 _7 d9 D& v/ E
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
0 C3 D! s3 J) S* m6 m                break;: _: }# q% v& A: V
        case  9: // next row PA95 U/ ^3 g: M; F2 A' V
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;
' B- D8 h& W4 A  k& c* B                break;
' ?  L3 @* c( `9 y! v        case 10: // next row PA10; `* j, C9 z6 G2 K3 e4 y
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;: e5 V' k5 f7 y8 d3 \$ E0 i
                break;
( Q2 |1 k6 g" q6 j( {$ K        case 11: // next row PA15
9 s  R- A2 w: X/ t8 q                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;
0 S% e) s& p. C9 \                break;7 z# D9 V5 c' R' `8 }6 Z7 ]$ F2 Z
        case 12: // next row PC13
1 Y9 A2 P0 `5 z. l+ ^, @  Q) V                GPIOA->MODER = GPIOA_DEFAULT;: `7 s% W% i( z+ V
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
: M# l! _0 \/ V                break;) l: D% l' B' v# T
    }8 V1 A. F  ~) H% B  A( p, \
    if(scan_row<13)4 P' ~9 N9 [, Z' d  _
        scan_row++;
9 ]! V0 x: Q# A. p1 L    else
. K3 M* E) R' g( ~# P        scan_row=0;
$ g. Y2 V  C: z/ ^+ |- V3 j9 O$ @}
% n6 Q/ e  w0 T. `7 b2 ?( N/ k; r- p" b: D5 \8 e: V) G
4 _9 M" u6 T8 u0 Y, R, V6 b7 ~/ |) a
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑 / ^' q4 S! j5 B7 w" w, G) V

/ K" ~2 e- E* u$ P7 {扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。1 F# r9 s' D* \2 g7 k; c
7 o; w; X& T' z0 [+ B1 M
要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:
7 t/ d4 _1 V7 \

9 r7 L7 f  M) ?+ [6 v7 ^  U; g9 y% f' _. b  s/ e: k2 V9 l
const char hid_keymap_qwerty[14][8]={$ Q2 m& B4 {3 u0 F
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},$ \' y* m% ~1 I5 l/ U
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
5 [* x7 L0 X8 K$ g* G/ B+ j    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},% ]# D: U, D" {2 q
    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},
* m! R0 t9 P5 `1 n+ \5 l0 z  N( s    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},
, S. @3 q" R4 [) w7 D    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},
3 B) [/ j) \# {+ ?4 n    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},; C8 T, y7 L% u$ ?2 S# H
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},  ?" ]$ M6 F, Z4 e
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},  t- r6 |9 {7 Z
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
; W5 W- c4 w1 ^0 n) u    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
: n. |# V  [3 \) a" g1 \% l    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},% w# B. B5 K3 Z4 E! i% ^$ m# X$ v
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
2 B% q' s: L7 C. ?# w" O5 s% a    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}) @* b; Z! ~: [7 {
};
2 `* O& K6 y% w7 a
9 L4 @' a3 Y" G: b6 g* y" H0 Z

/ q0 q' x& S2 s0 E! Hconst char hid_keymap_dvorak[14][8]={
$ j1 `& a) ?3 q( e& p( c    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc}," s9 m* P( H9 b4 F% W
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},* s' j+ E2 q9 }7 l
    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},: p- ^. d1 J1 {' K* d/ p$ h
    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
# O) H0 r) f- t2 h% p    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},- o& H" Z( N. T4 P* G
    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},' z  F& X  j# X1 \; @! R6 j
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},
0 t% s% o# x* ^* l    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},% y3 H+ W+ e  n1 p! p4 j; j
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
9 R7 v6 Y7 {4 ?$ C) L6 K: r1 q. K    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
8 V1 C& I/ w0 K, v8 i5 O    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE}," m& x1 Z+ W$ O' S8 Y
    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},
% w& F' C4 p  ?" H" ~    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},: a, \( `% _3 q. L" W
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}% L) B. D0 j$ o& T2 o( z5 ^5 Y
};6 I2 K" n2 A7 ^' s
  w- w/ C/ s  [2 s0 J/ w

2 c7 X, d. f) z% w0 B$ t上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
0 T" l7 w4 E, ]. v. ]. f8 p8 t$ T# ?/ b& e& W5 ^0 D
HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:
& p( o' q& w; Q8 x/ T5 l) f1 f. R

* \7 Z: ]+ t; }/ V7 @void update_key_matrix(char row, char col, char onoff)
; l6 y+ ~5 \1 u( a- Z  l3 W{6 T% X( Q' i3 u8 y- p/ J
    static uint16_t hid_report[4]={0,0,0,0};- K5 \0 q) l, W$ N3 H
    static char (*hid_keymap)[8]=hid_keymap_dvorak;
2 t8 q( w9 P/ W. H9 p, q8 f. |5 D4 o5 l! [$ _. `8 p. M% r! e5 U) O
+ _; Q9 _0 w6 }3 m0 s
    unsigned char key=hid_keymap[row][col];+ h: `$ j' l" Q5 P( W0 y7 \' q
    unsigned char *report =(unsigned char *)hid_report;' f" D9 ~; r/ T  i3 v
    char i;+ v9 R: z; {+ D4 `3 w" w( l, g

6 b& g# x5 P! X0 b0 y+ d* ?4 X
* P: I' r0 L8 V  s/ F3 A! O4 `$ E+ c
    if(key==HK_MODE)- \+ P& m2 p% Y& j
    {; e* t  z8 _+ f
        if(!onoff)
* T- n6 Y3 _$ h" P! L        {# W; D# A# X. q. A8 c0 [* F
            if(hid_keymap==hid_keymap_dvorak)
& C' F3 h( G! x! v) G2 W+ K+ D            {
- T8 W9 s0 O( F! e) t                hid_keymap=hid_keymap_qwerty;
" Q5 I1 f5 z1 i! t: W8 d; k                GPIOB->BSRR = (1<<2);, F8 |% \0 G! m# P# E* N7 g* r3 e5 Y
            }1 T# R: p* A4 r
            else/ H3 X) l6 Z0 ]8 _+ c
            {
8 a% M& P2 @1 _" J9 L                hid_keymap=hid_keymap_dvorak;
4 i2 d1 I/ G" z1 w% z                GPIOB->BRR = (1<<2);
+ v+ o: @/ A/ `# A9 a            }
) W) O; c9 E# C) X7 q1 r, H        }
7 _. I* |8 ~/ I9 K4 e  P" q        return;6 b7 I* I, w+ k; U* w
    }, t. V& \9 i' U

: [& f$ |- N+ ?' R& P
4 q7 \3 A( v) B& H2 d5 C
    if(key>=0x80)   // Alt, Ctrl, Shift3 V* Q2 i- j/ @7 `0 i" w
    {
; n% M: U" U" H- M7 ]2 V+ v        uint8_t bitset = 1<<(key&7);: S, B3 p- S, Q5 h# V
        if(onoff)   // non-zero is key up
) M) r: s4 {0 u) @4 U            report[0] &= (~bitset);
# T  `4 P" P; \$ g( Y        else
; p9 a- B: t9 ?# ~) ~            report[0] |= bitset;9 Y9 C: Q9 u4 S7 y7 k
    }
, s7 [  F2 e8 q* f$ |) n' H& ]9 Y    else. X( F( }5 y4 N& }, f! m) Y, O
    {
% M0 E6 P( T# a        if(onoff)   // non-zero is key up* I  A0 _7 v3 k- [, A7 \. \
        {
* h- q+ H( q! @& P            for(i=2;i<8;i++)
3 y, h1 ?1 t" j/ U5 S            {( S! ]0 G. r# F, A3 D
                if(report==key)
! d5 W5 b, W+ y/ ^+ H+ H& [4 ?                {
$ }, E- T: j5 R1 `1 d. R* L6 Y# u                    report=0;
* h+ E: w: i' t  ~: U  }0 B                    break;
2 t$ {6 T$ V1 z3 f/ _                }
# E, L' v; |2 r. Y' w            }* F# l. X  _( z$ D# {9 c1 C, e
        }6 d6 j7 S, ~$ `) S  y
        else
0 K( Z/ {9 B: b3 v# D2 [        {& q5 n) J2 ?! V2 H- J- _* i
            for(i=2;i<8;i++)) X) w+ b4 k+ Z* Y
            {
' H/ e% j' ~0 E  R% f2 Q0 X4 K2 t                if(report==key)
! ^' c" z* N1 E                    break;
; q# }& ]$ e# `. g& @) w' f                if(report==0)$ @8 `# L0 Y: `" Q7 w, O9 w
                {
, e" U  L9 K4 }* m! ~+ a1 k7 T4 J                    report=key;
# l" n* D- P8 f- j$ M0 z                    break;
& o  I2 S8 L7 M                }# l0 E% w: K9 [; l! s4 K5 p
            }
( o4 `! p% w. }8 w! t        }: z5 b$ A9 e) U3 ?3 R. U$ h
    }4 y# H4 h' X4 ?' k8 Y; l$ S
    for(i=0;i<4;i++)
& Y! a# m: v+ ~3 U        USB_PMA[192+i]=hid_report;
' p8 ]% e; U7 ^, n6 M7 ?    USB_PMA[5]=8;   //COUNT1_TX9 }% D5 j  W5 L8 m
    if(ep1_wait==0)
- [) }  Q- w1 W1 o    {; H' m+ U' ^+ L! }7 m" j! ~
        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;6 e# X+ B8 ^( p) U& \
        ep1_wait=1;
2 b+ w, }" p' }# C0 K2 Z    }7 e; N1 g" p, S2 R) S# m* q
}
. L/ _6 F  I3 R! p4 G& ^% k( H4 c7 |
" X7 b$ B+ @' N% G4 \
0 X" d1 b8 L* ^( E$ w% S完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。2 h3 ~6 [3 }; ]% T
keyboard.zip (8.7 KB, 下载次数: 6347) # b0 F  {" t' K5 c8 G9 w4 ^" d0 Y

( E3 A0 ?7 z; M6 p- x
7 O2 M+ ^+ R- l  Y! d, V4 {# |4 _- S6 E9 v! [' v3 a, r

* ]3 B1 e  u  I
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:08:09 | 显示全部楼层
在电子论坛上看到的,这对于改造键盘的小伙伴来说,提供了很大的帮助,步骤很详细,拆解换主控,设计pcb板,编写固件,看的是很过瘾。" a8 s6 w" [$ t2 k% b# g
回复

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘( N# K! b5 D# E% G
不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48
, d  [* p" h6 X8 P4 F刚开始我以为要把打字机改造成电脑键盘0 U4 F) H) P. ^6 H' P8 L
不过楼主也很厉害!

- B" I* H* P$ @' ~/ n% t2 f哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
6 E6 W' |3 Z* R% Q. {4 q
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-11-8 00:33 , Processed in 2.699654 second(s), 24 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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