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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

积分
288
发表于 2016-9-21 20:34:19 | 显示全部楼层 |阅读模式
本帖最后由 miaozhuang 于 2016-9-21 20:35 编辑 7 z9 b3 x; e4 _

+ U5 |+ ~) F( i. f* I/ ^http://bbs.eeworld.com.cn/forum. ... digest%26digest%3D1' b9 D! b. V0 \& f5 W9 n
这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。
0 V' r, P6 F, v1 d& ?1 u' M0 d9 G- Z& t* w7 Y
在看这个帖子的诸位一定都在用计算机键盘吧。键盘上的数字键是1到9从左至右排列,或者是右小键盘区那样三个一排有序排列,反正规律很明显。但是字母键却不是A,B,C...到Z这么按字母序有规律地排下来的。我刚接触电脑(其实还是学习机)的时候,没在意这个问题,觉得是要盲打嘛,反正对两手的手指头来说,按字母序排列并没有什么好处。于是用多了这些排列也就记住了,从来不管它为什么要这样。其实,PC的键盘键位排布上是延用了打字机的键盘,这是设备演变过程中很自然的一个延续。打字机的历史就要早很多了,我没有亲见过打字机长什么样,而且,咱们汉字是铅字打上去的,和英文打字机方式完全不同。
& d8 B1 q/ N- M; H; W/ j

) o+ Q+ C  X4 T! v 235140i3a36qivqzuvmt5q.jpg.thumb.jpg . V6 F' z3 M4 K/ ], v7 w( Z; b
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。; ~4 k& i0 D, [2 ^7 o8 I
001734klbyoluenuwz4h4b.png.thumb.jpg 3 e3 O! j% a* ~) x
为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:$ P% `% [. ^) Z; u
003625r2agx2f5v922cf2f.png.thumb.jpg - R8 B! M' _2 {+ h( W: H* ?. n/ ~0 ]
其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.6 Y1 h" s( m3 z# l2 }  ?( s1 a
Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:
5 s, e& c) e, ]5 A! i* l) o) [
005836yvs0wvovwsssgd3o.png.thumb.jpg 8 b3 Q6 p! x9 l1 {
Dvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。
* k. e: i0 W- P* x. M
/ p5 J" }  ]3 U$ c4 {; D我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。
2 H; P& ~2 h) S+ d
$ M3 y# Z* ^6 T到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。, e, Q9 W6 E0 Q: i: t! }) U: u

, v4 N3 V# G! ]- y4 N$ {机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:

0 i9 N& ~7 D8 P1 l2 k 123206vx24rfxfp2dfrpxg.jpg.thumb.jpg 3 ^% {$ ~! R. q" V$ ]
----------------------------------------------------  分割线 ----------------------------------------------------------1 u. Y) p$ @, W' q' {+ J

5 B* e  i6 v* }" {0 s: w先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。
! @5 W8 W# C! U+ D( r* L; N4 s
020011osionbunl4ui44vi.jpg.thumb.jpg # h, P. y5 h/ V8 y
轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。" V4 J# `( P! b7 v, v2 U/ J/ w" M
020017j8ycmnv7788bqv52.jpg.thumb.jpg
( t/ d; G$ g1 u7 C特写,80C49
+ v/ }4 D. b( b( q 021040oujzuvtut6iujtvz.jpg.thumb.jpg
$ D# a% N$ H4 W7 G; y( ~2 XLED部分,使用了一片D触发器锁存指示灯状态.6 y' Z3 _+ i+ X" F" b
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
, a( {. N, b2 `暴力破坏,将80C49拆掉
* B$ Z* {  g: w$ S 021113e48qq98vyohvhzzh.jpg.thumb.jpg , M# A* e- m$ \6 s( z& Q' r
拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。/ C. M9 X0 Q' w& s0 n
021125nc9az6dj33rlds2r.jpg.thumb.jpg   v6 q6 K/ O( w
焊好元件后的板子,准备替换80C49! q9 }( G# D  m+ b& h" C* N
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg
1 a6 n2 m9 A# q2 o- ]0 q用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。) Y( Q0 J, N; l% `# |; A/ A
021104shifhnrqbr3o5nlo.jpg.thumb.jpg
. M* `4 m8 b+ i* @- e5 Q这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.( f3 {  I; r& i
022003ym1p9u4ug40280uu.jpg.thumb.jpg 0 v- A" C2 y+ l8 f/ ?2 s9 _0 R
开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。* `4 l. D, A! U/ A
023313kt141q9qajtol7ma.jpg.thumb.jpg 4 o( q2 I! P* x1 m# ^7 F- p0 v4 G
我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。4 Y! _, N" J4 X+ p
023322nt7l5xb3ltttkltt.jpg.thumb.jpg
7 y( _) Q) n8 |7 b0 U* ?
主键区键帽就位
9 y) T  a9 `' Y7 V" ?. Q 023331hin88e8wkrwzwikx.jpg.thumb.jpg
* S, Z* L+ P8 }, z# ^+ W编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。: D1 S' s* W$ c# l6 A3 ]
023336wjzlgopugg1jyy79.jpg.thumb.jpg
; b" D8 ^7 L" e" E- z1 M最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
4 Q, @% `/ T1 l7 L 023341sffu4j3g2323h6fl.jpg.thumb.jpg
0 T- r+ ]$ H5 ~, C. Y

- h$ T/ F7 K* ?! }$ C----------------------------------------------------- 分割线 --------------------------------------------------9 N) J5 s; Y- T9 M5 B* ?
$ Z+ D7 L' h5 I/ `. B

5 m$ N. O, a! _: G- J/ Z" r
4 J! n1 _8 f; M# K) o8 w( O3 k$ w2 S4 x2 H: L8 y9 S6 ^/ @

  O0 d' X' v  u# J9 B1 p% u
+ k5 T- b: I0 n7 l  B
* u% i& q* z2 l- q# j; i+ ]

1 M( l! Q8 a- `/ |1 X3 X4 O1 i' i- _4 O8 [

0 n' a5 L7 x* b) ]8 ], {# A! C* P8 v7 N
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。
0 D* r; \7 t# y* j3 G80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:6 F& m* a7 l7 r; C5 @
104025nzibm2rmiomhyirm.png.thumb.jpg
) j  L& j# k1 d: q$ }. f& F

% _; s: I( d- T) q' o其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号): n* Z  c7 n" e8 T$ }8 j& l
105004zkrez5houvkkznko.jpg.thumb.jpg 8 d4 z9 C! a  |# i
- S# m0 V* s$ @( `2 U
扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。
" Y& X0 s! v; C
$ O" Y' f# u  U$ j6 M. @这是我设计的电路图:
  c; w  S2 a# E) H 110344ej2z2oo2rflo7oe7.png.thumb.jpg ) V# f' y: `# E$ s+ |
9 B) m. A' p5 ?4 E4 G; G
PCB Layout:- ^' ~- a" t7 _; r5 E: K/ A
110847jjbjvt34vwt3v5bb.png.thumb.jpg ( D! I) \+ H9 J- r5 ~- q+ d* s
* {3 Y4 ?2 i2 [" E9 U+ H% a
不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O. 2 r2 Q9 `: x7 V, j! Q6 M

: N! M$ B# R1 S
* J9 i7 Z, J: \* d
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑 ) K  `) ?7 s, a$ n1 D; c
* [( P. `: Y+ V% m  z7 L
软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。/ m2 l1 X; {. h& w" S* h- U

* ^& P2 E! a. c" z0 D总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:5 ~, G6 R; F4 J- J" V
113818pmrfsb6z0byt6t06.png.thumb.jpg : K+ f* i+ r/ s7 ~: b% ?, f! R
1 _" X. ~* b6 a2 c
其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)( O7 E6 u! V: D6 s9 d3 x

( f# ?$ {9 e( GUSB的中断ISR,bare metal哦
+ l0 R+ K5 c' b' K5 |

$ E: b3 H+ A# w  n6 |5 Hvoid USB_IRQHandler(void): Q# t: @+ o( v; R, Y. u( r
{
- @$ w5 ~, Q8 {3 U! U    if(USB->ISTR & USB_ISTR_CTR)
% L( f) E# P& Q* c* Y* u& T    {
# C- X  }4 f4 R# l& t1 ^        if((USB->ISTR & 0x0f)==0)   // EP_ID==05 e" [' A4 q# Q3 C. N8 V- s
        {
) ~5 h3 [/ s% M  }) f+ X8 C            switch(ep0_state)% e7 q. {0 _& E, p6 f" Q% N
            {
) D5 p/ e7 u. \$ i' D* O                case 0: ep0_state |= 0x80;  U8 Q, ]- a3 R- o
                        break;0 N( f& h( e4 w3 q
                case 1: if(USB->EP0R & USB_EP_CTR_TX)
/ a$ ^6 Y* b- X* B% J9 V6 Y+ x                        {" \- I/ _3 ?6 ^& ?
                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
2 z) P( ]" J- C$ H" i9 e% A: m                            ep0_state=3;
& C8 c1 O# Z8 N                            return;- n' M6 T( j% e# E  E8 y
                        }9 ?9 {# O* ]! o  @+ j
                        else+ z' M' w0 `* B8 O6 ~  W$ [
                            ep0_state=0;
- c% T0 [% O8 W0 V. D- q                        break;
$ s) c! W/ K) F                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet% m  f& @" |) D* r
                            ep0_state |= 0x80;! {/ P9 T7 ~2 m6 |6 R0 c
                        else
: {, ^$ w4 H7 r) O$ }4 e                            ep0_state=0;; |5 r8 X' L0 \- z9 j; B
                        break;! E  ?: B& w+ r! ^
                case 3: ep0_state=0;$ P; J3 C2 i! t1 v; T5 _, o: e
                        break;
: b/ S! x8 O& _% _- D$ L                case 4: ep0_state=0;
1 J1 g0 }$ L/ }: p  h# N                        break;
( H6 T! J# `+ C( h' p                default:ep0_state=0;, n! Q* j( u+ S- y
                        break;* f  @" m# D* }9 I
            }1 ^+ M$ c5 Z* J7 c. M7 I
            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
" X$ I  C- b2 v/ W( D( C            return;
% y" I4 v- o: k        }
+ N: A9 \, E: ~+ V/ J3 l8 U        else    // EP_ID can be 11 \0 b( K% q1 {& k( s; ?  c
        {
9 _: a# R/ s# {& C            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag  O, o( P3 c* u% J3 E+ A. `$ [: O
            ep1_wait=0;
9 A8 c" i2 y/ O( z5 y            return;
. k+ @4 e4 S) D& \        }: x5 M) f6 Q) f5 D; A, T( x, D
    }+ [7 b% m( y) o" j
    if(USB->ISTR & USB_ISTR_PMAOVR)
+ r, `& M$ v5 v( y! B9 \    {. N, A/ D4 ?6 }3 O) `5 E  T
        USB->ISTR = ~USB_ISTR_PMAOVR;
- p" b  n  K& Y+ g4 X( c3 F9 c% r    }, h7 r+ [- y+ W- B
    if(USB->ISTR & USB_ISTR_ERR)
) P: ~9 O) N# B7 {- Y4 o    {- m$ w& Y: E3 ^, L
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear  t/ {/ k8 D, b* ?9 l- t
    }
) b3 x/ d0 m# g: o3 Y% o( k# O' Q    if(USB->ISTR & USB_ISTR_WKUP)1 ^, m. K6 u# S' O
    {
0 j, d, g- p" @7 o: ?9 R  S0 g        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend9 C, c( `8 d! L
        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear+ o; S# U+ ^( q" B7 D) c
    }$ D; C+ W1 O2 H
    if(USB->ISTR & USB_ISTR_SUSP)% g  @; e; \" q6 r) D1 k9 n
    {
2 L& B- A& Q/ H        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend* q5 h5 O) R$ C& J4 |0 o/ E( j- v/ X
        USB->CNTR |= USB_CNTR_LPMODE;   // low power
' S2 J" z" i& s, F6 {5 h- G3 ?        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear: f& }7 W( W% `- R; w' D, _
    }
, {1 h/ X5 v1 t6 }$ q    if(USB->ISTR & USB_ISTR_RESET)+ V) i- ]  m; m' v* c( G% k
    {( k; g  ^6 T, ?4 Q
        USB->BTABLE = 0;    // buffer table at bottom of PMA
* x; W9 K  v( m9 f2 }' T        USB_PMA[0]=128; //ADDR0_TX3 b0 o4 E8 Y9 g+ G
        USB_PMA[1]=0;   //COUNT0_TX. \* q$ i% T, _5 z
        USB_PMA[2]=256; //ADDR0_RX
# r% i4 |0 r6 o2 C9 Z) [/ ]        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes
; c1 k' ?" J/ r. u0 ]        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;
3 b( V: I. |0 k" O& A        ep0_state=0;
2 Q$ D2 ~5 X- J, a# T; S$ A& w& l        USB_PMA[4]=384; //ADDR1_TX  ]1 G3 L) p3 q8 s% [
        USB_PMA[5]=0;   //COUNT1_TX
9 `. o& k% h% t) ~/ h$ Q4 V        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type. E2 Q4 L4 }+ T2 s; {5 \
        ep1_wait=0;
/ E1 G; H) i5 N. W# }- }4 t7 x5 B: T        USB->DADDR = USB_DADDR_EF;      // enable function2 M3 }" w4 {0 n, E- h  V
        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear+ @1 z/ n8 n3 E6 p
    }# ^" W; r/ Y& e
    if(USB->ISTR & USB_ISTR_SOF)) E4 d$ q( G5 D  y$ Y% |
    {! O/ {* q- h7 o/ `9 F! R
        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear5 U8 v! }+ {' `5 m# h4 T& q8 O$ q' C
    }' M1 }. t2 [5 Y# I4 v/ h2 Y2 N6 ^
}
+ [8 P5 _7 S  O$ W- k9 b/ n
  e1 y- b$ r5 M$ z4 v* _
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。: ]- n# `8 U  A  [7 j( p. R# R/ }

$ n" C( O+ S, u5 O4 N3 j( R6 M4 f主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。  \% y  T3 r7 X+ h( `
+ d; q$ Y4 K7 }1 T* q
    while(1)& ?% G! S! Q% ~/ Q# q1 Z7 S
    {) M" H$ n6 M6 n. \$ O! J
        static char row=0;
: h) a1 e2 d5 B. z8 i1 Y/ k% \        __WFI();
# a7 A0 g/ R% s3 s0 `1 j  |! T* `        if(ep0_state & 0x80)    // request data processing$ e' ^7 Y/ k0 u- K
        {
" k" j1 }. p" K. ?+ M            if(ep0_state==0x80) // SETUP phase0 F$ h2 b& ^' ?' G* Z
            {- @: B0 e! Q, a) A
                if(!setup_packet_service())
1 l2 m6 I& q6 f0 x3 N8 B3 O! {                {& I, S, q% B7 k
                    ep0_state=0;1 `; F, I+ w) I% Q. k
                    // not supported9 Z; j2 v" D3 A3 H
                }# y# U# e# l% C! _( s
                // ep0_state should be set to 1 or 2, if processed
, A. `# x+ b& L$ `# s" F. J            }
1 B! \, l5 C7 D! V# Z$ c, i- t. L            else    // OUT phase  O+ D7 t% `1 |2 `, v, Q8 _
            {; i* Q+ _" W1 N5 M  H
                // process data
! d: `. |/ c! t" f; ]  l                show_LED(*(uint8_t *)(USB_PMA+128));. K4 W% C' F5 R+ r% R0 Z4 A9 _
                ep0_state=4;
9 Z% Z* Q: w# E# d& h" Q                USB_PMA[1]=0;       // Zero length DATA0
# E% K  ?1 I6 T( o# j+ s                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
9 u2 g4 ]2 _$ O- }$ c/ `! i            }& J/ ^1 D( u! S. _8 v& k, p
        }, y$ w% c. L3 Q; z8 z1 e+ C3 ?0 j
        else
) F& e2 F" O% o7 z        {2 U% m: @# l5 m6 S
            if(usb_address && ep0_state==0)
" }: w* f5 v' u/ z; t            {! ?3 R" o* |3 N( p3 d4 M7 j
                USB->DADDR = USB_DADDR_EF|usb_address;$ L' d. M4 g; Z
                usb_address=0;
  N7 w. V6 d7 A" t3 P, ~( i            }
+ H5 B4 V; S7 x( I! G        }
0 _) i) R) `3 T' ^7 R9 ]        if(row!=scan_row)   // new scan line+ V* J- r/ ^" S$ `+ m3 b
        {" N0 t( @' n6 X' j% Q) P! y
            if(key_state[row]!=prev_key_state[row])
6 X$ w4 V  t& l2 f& c& f            {: G& e3 \- L& |$ K0 Z9 v
                uint8_t test=0x80;
+ k' a# J* y2 _) I3 g' X$ q                uint8_t diff=key_state[row]^prev_key_state[row];2 V* R3 {0 A2 L) O$ c
                for(i=0;i<8;i++)' J& ?3 R' A, D' s, x( p
                {
8 y* X& U" z: r# Y                    if(diff & test)
6 Y3 v" P7 n; F- Q2 \                        update_key_matrix(row,i,key_state[row]&test);: ]) F; `* s# Q( Q% J$ e
                    test>>=1;
6 |, V$ N9 A3 B5 K1 K' Z: b                }
5 i5 _1 v" e9 E0 F            }. G' k1 r4 O0 P
            row=scan_row;2 m) u* g- E, ]& `5 t
        }
1 N3 t5 K( T) h$ c    }
$ k0 ?8 L2 D  C, G& t9 J4 g! H% I; b

+ q8 c5 w! A/ s' Q. ^EP0的控制传输,把用到的请求处理一下
9 x6 b: p4 k/ K  O, O  G6 ]* `char setup_packet_service(void)
2 q% a7 [6 W! l- w1 u* @$ I{
4 K; F! U1 O/ p3 g  H    if(ep0_std_req->bmRequestType & 0x20)   // class-specific/ U" ^3 g7 k7 a, d
    {( v! d  z/ I; L, C; e$ F1 s
        switch(ep0_std_req->bRequest)! \& f% O( a/ L4 {3 W
        {: s! @2 d1 [# }4 u) K' _
            case REQ_GET_REPORT: break;
. I; ?1 I/ ^, `8 o' l            case REQ_GET_IDLE:
9 b# s; f( E" i( F6 ^& a                USB_PMA[64]=0xfa;   // return 1 byte
+ p- ]9 f  X9 H: S                USB_PMA[1]=1;8 N- t3 {# Q* h+ p
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
* [) J' J- j$ i" V                ep0_state=1;2 w- C. w$ s9 J" c- u$ `" D: ~/ a& H
                return 1;
9 H0 s% G( S) X                break;0 \3 L* a9 M  Z9 }
            case REQ_SET_REPORT:
( ~  D% a+ P( G/ N- c                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;0 c' d4 N( L- B
                ep0_state=2;# f9 q5 y3 [* B, k! j: C
                return 1;/ Y( K# q& P9 n- b* l7 ~  D9 F
                break;1 w  _% g% Q( Y9 \, w2 w8 ~5 Z
            case REQ_SET_IDLE:
2 {; v" B* V( u' H  M3 ?* ^$ W                USB_PMA[1]=0;   // Zero DATA" N" Z' \$ j+ R- z
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
+ u! b2 F; {( N5 r, i7 [8 D$ C                ep0_state=4;
/ Y- A+ w4 `/ g  z7 o* X7 X                return 1;
4 r" A! \! N) S( [! g                break;
; E9 C$ U2 O; t$ H. k( q/ |8 u        }* a" j  w+ a+ ?  O
        return 0;
9 b# ?$ p! L( ^  [" \- @1 q+ f    }3 V" f! C& j  ]* J6 y* X* Q$ q
    else    // standard' @7 Q4 @+ G+ l% N1 i3 W
    {
( }$ O3 n; @. E        switch(ep0_std_req->bRequest)
1 s- o" v% Q# ~$ G        {3 L, ~" a: S, C. g* v9 o: N
            case REQ_GET_DESCRIPTOR:
9 J5 y4 @1 I. k3 ]                return descriptor_service();
( H  J; L' P- y                break;# N# j9 }8 j8 I! b
            case REQ_SET_ADDRESS:
9 V2 k! j/ T* E                if(ep0_std_req->bmRequestType!=0x00)0 d2 \' q/ q4 B/ \& u
                    return 0;$ q! z0 m. B7 @; @; f) c
                usb_address=ep0_std_req->wValue;
* D8 }6 j3 y. u0 }5 x/ \2 f7 C$ h                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
- N8 m9 V" [6 M1 R( o                USB_PMA[1]=0;       // Zero length DATA07 a" L% @, O$ e4 ~  P$ Y! i
                ep0_state=4;    // No Data phase! b/ J5 I' D% `/ [8 a
                return 1;
7 u0 a/ Z! @8 i; r" l* Z            case REQ_SET_CONFIGURATION:. P3 k6 \( W& |* M
                if(ep0_std_req->bmRequestType!=0x00)6 \+ E3 b6 Q2 f, ^: h) y' y
                    return 0;
1 r9 Y4 X+ |5 K  _; O9 [                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
$ U+ k/ U5 ~  y8 i                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
8 N3 s+ t9 G! t. j" S                USB_PMA[1]=0;   // Zero DATA
5 a! O- c8 p  z4 l5 \8 t( f! Y3 v                ep0_state=4;    // No DATA phase
* k$ @! i+ K9 a! z) p                return 1;( j2 k! [! h: l
            default: return 0;; o# ~5 R3 {9 k( [, [/ J. D
        }
, I' b3 Y5 ]7 i, s    }
3 B4 ~4 J4 H3 p. l! G: q}6 r" |- p5 p, a* }4 ]/ X2 z$ S& O7 B

$ H+ P# |, k, C8 `9 ?9 b8 K  A# k6 k1 {9 i3 G! F3 b

% i- x, T5 d7 D3 \4 `9 U, ?
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的9 }6 |5 H5 F+ ^1 z" f( C
char descriptor_service(void)
% X& }! O: Z, h1 @{
& }4 R: }1 ]9 Z! }9 X    switch((ep0_std_req->wValue)>>8)
& T8 F0 n, }: Z: A% l1 ~' Z+ G% B& U    {( `( `# N) {' w5 I3 Z& q8 `
        case DESC_TYPE_DEVICE:( G( I& ?; [+ z% ~9 B
            return ep0_preparedata(&DevDesc, sizeof(DevDesc));6 e4 b  C2 V3 [1 s% t
            break;& B3 m; b3 v6 v" J
        case DESC_TYPE_CONFIG:
+ |, q2 {# K: N# ~; ]3 i& j# \; H            if(sizeof(ConfigDescData)>ep0_std_req->wLength)
. v" L' [  }0 L% N4 ^  h                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);
8 W* L( M* e1 U/ G  b7 o! |8 v            else
7 W6 _; w: b; a                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));- L( F7 d$ O- ?" g6 Q( F" O, W( g
            break;( A# D/ ^( N" H+ ?  t4 o* _
        case DESC_TYPE_STRING:
, p, o+ A$ Z6 U  B" Y, ^            switch(ep0_std_req->wValue &0xff)
. d3 S6 B+ H0 g) w5 i2 ~; J* A. ]            {
' d$ X9 A: P# j0 d4 m                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));9 O/ i5 l* s$ ^
                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));
4 p. l9 [4 q+ u' K                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));4 T$ ^# A  k! k& n* d, J/ ]! i
                default: return 0;
% a+ q6 J; n( k7 B, H4 m: \            }3 A' g3 O( X. ]  a0 ^
            break;
6 y/ Q  \* x, `6 _. @# _! \        case REPORT_DESC_TYPE:7 l$ Y+ X3 i& t( q& i/ ~- r
            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));
- k5 Y/ q' j; [) V8 Y" n        default:. E* J: J) L3 z8 k( h
            return 0;
+ e6 d; N' r" _' T- V. J& H    }5 G3 t& A0 }2 x9 k( z1 D
}6 z$ l+ D8 x! E0 M) N
下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.- _4 R. L: p: u( @
void TIM6_DAC_IRQHandler(void)9 e, i5 _4 C. N9 Q0 m5 q% ?
{' t1 l2 O# ~  T8 J& N
    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);
! o1 w; ~' {- @2 |
; g& n: E2 {% ~9 {3 N
/ I, g. ]" v# `- L; H
    TIM6->SR &= ~TIM_SR_UIF;3 z. x- z& h3 w2 @$ Z! E
    prev_key_state[scan_row]=key_state[scan_row];
" W1 \7 ]) Z$ ~- m# F* l/ l7 u0 P    key_state[scan_row]= *PA_IDR;   // update key states0 p1 u  v+ L1 A/ L  A0 N
    switch(scan_row)7 l5 A7 Q. t1 a1 L
    {
( L$ w  o5 c$ s2 c3 I9 q: |8 Z        case 13: // next row PB14
, U" m) q8 M5 u+ d6 G- o                GPIOC->MODER = GPIOC_DEFAULT;
" [, n4 y7 ?* V: O* J! d. Q2 V                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;
  x  s& M' V* u9 T5 z$ t                break;* z9 a9 {2 A" ^  e
        case  0: // next row PB15
3 z7 V) g0 {# |9 W& `4 J3 z                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;* Q) Y- E- I. W% q* `! O) C6 }9 }
                break;* h% }* L: y; D6 W' y$ |: Y
        case  1: // next row PB30 k* `) a& q: I1 i7 i
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;7 ]4 C5 u: S% o$ q  C
                break;
" Z" \. S' F3 f/ p) r+ ?        case  2: // next row PB4$ y, U* C0 H; C# d( `' J  G8 r
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
+ l9 X6 s. o! n  l' q  x; s                break;
- E; f4 d* s2 B" c        case  3: // next row PB5
' B2 K) ~# y% y2 Q                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;
8 p& E  |3 E3 F1 [0 @                break;
! A# M7 @# }6 K6 B        case  4: // next row PB6. U! Y7 K& J- a2 I( T, A; F% S
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;  G, W6 T$ g) |: t2 s: n
                break;7 j; v2 q, m; T, w
        case  5: // next row PB7: a+ n1 `) |( h" }( `
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;0 c2 X- _$ g- i
                break;; K9 A/ v9 e5 J; B' \
        case  6: // next row PB8
5 x! [! |) i1 P+ ?                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;5 `. V9 y! G; i* R
                break;
! G3 }" T/ I/ X2 _# K+ p        case  7: // next row PB9. ~3 S* a  E! t- \; ~8 a
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;+ |3 P2 t3 _5 B: S5 C
                break;
- S# V: m: u" B  u. P        case  8: // next row PA8. {4 ~# i; n! O( |$ C2 S
                GPIOB->MODER = GPIOB_DEFAULT;
' o' ?, W. e" o: k7 I! `& P) g: X                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
( ~# c7 ]4 r" Y6 ^+ L8 j( {1 [1 l7 K                break;
+ V+ U/ R* `/ _8 j4 a' Y7 t. ^        case  9: // next row PA95 h6 P" c% x, @+ ~: \- I
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;$ ~  I2 V* S" A& u. B, U
                break;3 |5 M  L6 Y4 k, H# S
        case 10: // next row PA10: x+ W9 h4 Z8 c( z0 T2 G# L
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
& e6 j) F5 d% _* [                break;
; k: m" C/ x; X        case 11: // next row PA15+ u/ |# O. G- q' L( H3 k& |4 I1 E
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;1 `# ^9 w) K9 ?3 \9 i
                break;/ a8 F( ?3 ?: j& j+ o
        case 12: // next row PC13' _7 R* e7 `7 g
                GPIOA->MODER = GPIOA_DEFAULT;" c+ r( S! y4 z- e# e" u
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
8 P  G( J5 a6 [# ]! T+ F                break;& l4 K. |5 ^# e& Y% e$ \) d7 m5 h/ S
    }
) D6 E3 q  \. b' k+ B    if(scan_row<13)
; `/ m5 T" l3 K- {        scan_row++;
7 ]) I. O; f! d# c4 G    else
$ `% G8 ]! b/ k2 q        scan_row=0;- h9 E8 S( _  n
}
2 _: J5 {! P# v) t9 Z  k
4 z6 M0 M% L+ Y5 i
, f' `5 i& {8 J5 _0 K0 N
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑 3 J3 Y, ~6 ^% o) s* D
# j. e) l0 _; w# N$ Q
扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。; R- D6 w. e" A$ N5 A7 j3 Q2 A3 S
" e# F9 D% F* a/ J2 {
要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:

! A- S% _7 M; a) X# y5 \
3 @: y$ g, K" }/ x! d. S( `0 @9 u- Q/ @6 _, ^6 W) T% l
const char hid_keymap_qwerty[14][8]={
; S9 b* b/ h  w/ a4 I& [! t    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},
+ Y; [: J, q5 w3 H3 s/ @; A    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},/ D& M6 g. y' z$ I
    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},
3 f; q# o8 z7 x* o. a    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},+ `5 H6 s9 p, s4 S. q; x
    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},
% I0 ~( k* [" F9 N: `+ Z    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},
5 W7 ?* R8 _3 u2 s; D! B& W    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
  f2 z& e; `, u7 r* F7 T    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},' r- F. W/ O! w' A6 {, U
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},5 b9 V/ S$ N, d# f2 ]
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
; g  S: A( G: B$ `    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
- g7 J0 U* k+ c4 ?    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},( N& u& R* N2 A, ~
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7}," k7 t$ p- v- m: r: C8 ?
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
( H3 V/ {) M" D! o};; ^$ |5 E3 D" T; Z& s; b
9 m9 _% ^* \6 m5 H6 Q/ L% M% m

: `+ p. f5 P/ J) R% Yconst char hid_keymap_dvorak[14][8]={( L+ z+ ~1 {% A6 H& c0 i* Y* a
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
0 @) \7 d; k9 Q4 y$ d9 }    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},& O& e. i3 H+ u- w) P
    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},
* v) L+ c" W0 O( i1 B    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
) J  ?5 a. E6 m5 W' Q    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},
! l$ `. G8 w5 c! W0 a& ~    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
" [# ]/ [" L6 e" l+ x    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},# O* W4 {. I9 G5 ]2 |
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},
( L" J* v% v; u( c  T/ `8 K8 D7 w5 Y    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},: w4 P" G3 V% X/ p2 Z/ `3 P
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},  ]: N2 A' d/ `/ f
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
* T) {' `; E( w) Y2 c* t    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},5 ]  B/ ]; J3 B3 I# ]* S% l6 n
    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},
" ?( i$ j! t  c    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}- E: A( H# u" `
};
' B" l; k) V$ f* \
; |3 _: G: j' }3 G8 z& Q8 x( z7 ?7 d( u4 Y+ U
上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。& ?, w1 U* j7 d3 J

: z! O, c" z. y# p5 ?HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:

! X! Z+ n0 w' G( r, S9 g) A0 T9 B. @$ `
void update_key_matrix(char row, char col, char onoff)
& Z. I2 T' g  x" w# w{; S) @- j- t- V" ?, I
    static uint16_t hid_report[4]={0,0,0,0};
0 f5 v; S; a+ T9 y3 g- f    static char (*hid_keymap)[8]=hid_keymap_dvorak;/ i0 Y$ }9 C( h5 `7 Z. ~- y

/ I( b# o' c# I5 |& J

& y9 W& ^. N2 P# g8 d( F$ _) I    unsigned char key=hid_keymap[row][col];7 W4 h' b' W" Y: I
    unsigned char *report =(unsigned char *)hid_report;3 Z3 `  K+ f0 o. [3 b& y# x
    char i;. Q  P  v) O1 f" C

5 e- U6 I% J; r& h0 I* ?

7 ]. E: W+ N9 {! z- d: C6 Y    if(key==HK_MODE): R1 o, Y$ }0 J; o7 t7 m; Z) Y
    {
3 w/ `' e7 R( V        if(!onoff)  ]& |' e, G" e' ?
        {
: r- q& X+ A5 r: e% n            if(hid_keymap==hid_keymap_dvorak)
. y8 m" d( G9 g4 t            {4 u) N, {) U' Y0 B# ~6 a* t
                hid_keymap=hid_keymap_qwerty;
$ _2 I9 l& v9 h0 a# M1 R: ?                GPIOB->BSRR = (1<<2);0 F; K4 b- H9 Z) A; i
            }8 M1 x- J* G* c: `7 z4 s& ~8 S
            else; |, j0 I5 f% ~
            {
" H% @( N7 [- d' c                hid_keymap=hid_keymap_dvorak;
# p; a* p0 V! V! B2 e% u& F                GPIOB->BRR = (1<<2);! c4 D" `7 T" a9 E& |& Y, C
            }
! _: G# ~7 p9 \- w& {; {) v: |0 N        }
2 ]% }3 T% g& V9 U$ W( a        return;
& ?2 N! L4 F1 y2 [- H' S# f    }
8 {  N! F; o: C& m( `: C  [1 R. Z" W) j9 }+ Z! t

" C# F! p  n$ U# A- j; R# m    if(key>=0x80)   // Alt, Ctrl, Shift
2 C2 }: C0 }! O3 f" j5 h    {+ J; _7 i: K# h9 z' H' J, Y; }
        uint8_t bitset = 1<<(key&7);  a9 B: p, s* C- G3 C4 R  S
        if(onoff)   // non-zero is key up
* D) t' B% D6 r' u; c: i* w% y- G  C            report[0] &= (~bitset);) z% M6 g7 o2 W! M$ F$ {
        else8 H. q1 A, R( [7 v( v" ?
            report[0] |= bitset;
  F0 \/ q9 N% H1 w' X4 C7 n    }
5 H1 v6 D- `: H" L$ b( P9 s8 L    else
5 u, k( |1 f. {1 ^/ [    {% N8 F  Q$ N. s3 d
        if(onoff)   // non-zero is key up
) }) N4 l, E1 F3 z  c! X        {5 h8 t* l/ |1 L* h- B9 C7 R; _
            for(i=2;i<8;i++): y! \, c" m; v5 e3 Z  u/ d+ v, T% R/ B
            {
  p1 ~6 S7 B8 r. v# S& J% S                if(report==key)
# D9 M- V; k2 h1 t                {
+ V- x+ i% [! O4 Q2 Y                    report=0;
9 Q/ K3 z* H$ y" |                    break;& D/ W& Y: w, b
                }- |, I0 X1 j5 ]# c% d/ r
            }8 v$ |$ Q. a( Z* B( A, i
        }1 g( g2 g6 r) `$ M2 e5 G1 O0 Q8 M5 P. n! w
        else
  \) v) t1 J% i1 m        {
$ U+ D0 p+ j$ K7 a            for(i=2;i<8;i++): K1 K- m5 A- Z8 x4 T
            {
- \/ t1 S8 s) Y5 y0 h( u, G$ \                if(report==key)
/ o3 K# j8 ~3 H- e5 R: {1 W; {4 j* z                    break;6 ]6 v8 {) }" h  g0 X( D. c6 `
                if(report==0)
4 D( M6 g6 X- d8 R                {
3 t) o  i! m& [9 F/ q# F$ `6 n) F( m                    report=key;
3 Q0 r6 [: i0 x+ ]6 \8 k2 a/ Q                    break;8 v4 B# `  {- \7 l9 u
                }
9 u6 L, N3 u" ?1 `            }
. D& y( [+ G$ @        }5 y, P( \0 t3 }0 R  N. G! w( ]/ |
    }  F9 K1 {# f- F* h
    for(i=0;i<4;i++)
% ~4 f$ A8 t% l8 ]8 ]        USB_PMA[192+i]=hid_report;
+ F! D. g1 K. @7 \9 u    USB_PMA[5]=8;   //COUNT1_TX
" k5 M  ]( w& z# J6 B    if(ep1_wait==0)6 r8 @# C& t8 \6 W# \- E
    {
: c. Y3 F' i/ o9 [( ]        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;4 K+ y! L; j& g% @% L
        ep1_wait=1;
8 ~' {. q0 D' o) E0 Q2 L    }
. ~* ]& c; c" J}
5 l8 p/ x! ~8 c0 q
4 d$ t' Z2 d# @8 c) u. E- \$ M- c" `, F- @  m
完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。
( j; }- w  V6 p2 u; n2 j& r keyboard.zip (8.7 KB, 下载次数: 6188) 7 y1 c2 t8 Z5 |* w$ A" S
- M! I- j& t6 a2 h
1 I/ _5 I- z! s6 x, G1 `4 z# |
1 c4 J2 q9 ]/ h# l) V$ f

( }8 e0 X/ `5 w" J; F+ o
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘$ I( R# I# S. s3 t; a
不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:481 Z; A* Q; ~# B! f0 Q5 K
刚开始我以为要把打字机改造成电脑键盘
' R4 ^8 H; q0 Y不过楼主也很厉害!
( f: K( q) x: z/ ]' u' F3 P. w
哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害7 G6 g0 X! r' ~
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-7-1 10:41 , Processed in 0.186411 second(s), 28 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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