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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

积分
288
发表于 2016-9-21 20:34:19 | 显示全部楼层 |阅读模式
本帖最后由 miaozhuang 于 2016-9-21 20:35 编辑
2 K4 f8 P% O* n5 o# V; s. v* G$ W* P; B8 G1 @0 \2 `
http://bbs.eeworld.com.cn/forum. ... digest%26digest%3D1; Z) A+ @. c) \8 E* V
这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。" P& z6 \) E8 ]# }- B" [

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

' ^. {" t3 M* q' p7 F
/ i' @, }! T: t2 @6 [, ?  E6 \4 M2 }8 a
235140i3a36qivqzuvmt5q.jpg.thumb.jpg
! r* ~$ u% q0 @$ l" B# _7 d! d
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。
2 D5 ^$ B5 l" T- i3 x 001734klbyoluenuwz4h4b.png.thumb.jpg
$ C! C" l4 D/ i' k( M为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:: [, M$ o+ P4 d/ N% K8 P
003625r2agx2f5v922cf2f.png.thumb.jpg 3 `5 b% ^: K) H9 N, @9 d9 c  a# y% V& {
其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.2 J8 s% K/ ^, u4 [
Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:
( E" ]- h" g$ [9 T
005836yvs0wvovwsssgd3o.png.thumb.jpg
! _: R7 e2 F2 c2 zDvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。# a+ k1 v' a, O0 L! ^- U

8 |. ?/ R) C% N( O) ?$ [我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。3 c) k/ \' Q! W6 x

% i& b' w$ U" b" r9 s到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。
9 r8 G) {- E0 v, w% y9 y" w* r# N3 I; U9 E1 O1 ?8 {) e( ]
机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:

9 s! E/ o: ~- [0 C 123206vx24rfxfp2dfrpxg.jpg.thumb.jpg
: q2 }" V1 F% c. |5 H" ]----------------------------------------------------  分割线 ----------------------------------------------------------
% q" U6 _9 o" ~! ?9 `( y/ ^" Q- ?0 I, B
先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

, J- f0 X( }% g  Q. H1 n( S6 P 020011osionbunl4ui44vi.jpg.thumb.jpg % G, N& E. a, w4 K5 b
轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。) u4 j4 b0 @& k, L# p& G
020017j8ycmnv7788bqv52.jpg.thumb.jpg
; c1 @7 Y7 c+ g3 l$ [) R/ g特写,80C49
' v- s& c# Z6 `2 z0 m9 x 021040oujzuvtut6iujtvz.jpg.thumb.jpg
* S1 Y* X: e) B: z6 N- V1 }LED部分,使用了一片D触发器锁存指示灯状态./ X& B" j1 g& D( y2 A2 I2 F& s6 s( U
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
1 d: v  h/ m( f. @" Z暴力破坏,将80C49拆掉
7 f& }. z* v$ p 021113e48qq98vyohvhzzh.jpg.thumb.jpg
: p; y4 A) F7 z% M* m拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。
; O  y- t- a: m. z 021125nc9az6dj33rlds2r.jpg.thumb.jpg
2 [0 I  _; Q+ c' [9 l  K
焊好元件后的板子,准备替换80C49. i' R. {# o- ]; W
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg 0 P" x0 E# o5 X, G9 M% A
用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。
( j- D/ _* S% X6 W% u. w 021104shifhnrqbr3o5nlo.jpg.thumb.jpg % h* M! F! h' r5 H9 W4 B5 T
这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.  E6 [  B1 f  d3 J8 v' @/ D
022003ym1p9u4ug40280uu.jpg.thumb.jpg
$ D% n& h8 `& z% F开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。
$ [' ]' D- ^( G/ V 023313kt141q9qajtol7ma.jpg.thumb.jpg
, P; D8 k, b9 v8 s( d6 Z, h& ]我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。' k1 O5 m* z, d
023322nt7l5xb3ltttkltt.jpg.thumb.jpg
- _) K0 ~! z' R
主键区键帽就位/ n/ \- [3 x' \" w$ _
023331hin88e8wkrwzwikx.jpg.thumb.jpg
( j0 d  f' B) \, @4 q编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。: l4 G  F* ~1 u9 W
023336wjzlgopugg1jyy79.jpg.thumb.jpg
0 h8 d1 J  ]7 X- d% t5 d最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
) Z+ u7 @; D4 t8 V/ N+ [, ?! }6 Y 023341sffu4j3g2323h6fl.jpg.thumb.jpg 1 O% w) X" G8 j9 }' N

# v9 j- J$ h; V& ]( J8 r----------------------------------------------------- 分割线 --------------------------------------------------
- g1 a5 a' x$ K
) f1 Z: _  g2 G" ]# Y1 ~2 C
  E% _2 z( ?1 D

6 E: Z2 b: {( v7 v4 t. J: ?! q" s2 j, \
9 N/ g9 B- J& I( u6 k; w2 f; w
1 z3 S% A7 x: ~6 P# o% P+ Q/ F
$ j% e1 M" K9 O" ^8 [2 h
+ i$ ~. R& {( r) O5 T

( V$ \7 C, ^) P5 ?
( s) y# i% M, N; p( ^  R
  c( l$ A: V5 A5 E
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。2 R8 P  D# s  s& r
80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:. w9 u$ B2 L: t9 k8 e- s! i* ]; R: o
104025nzibm2rmiomhyirm.png.thumb.jpg
  W  c+ M3 ~: x2 y

9 d( f* P3 r# M8 H其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)
0 b5 @  @1 ]& o2 ^6 ?; G 105004zkrez5houvkkznko.jpg.thumb.jpg , t9 F2 m; g! F9 s! K, k

  c4 S+ Y$ K) m. }7 S5 L' w7 A扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。$ ?4 U: H! l4 C( i% p( [7 p

- X" \) F/ D9 B6 J6 ?; K; q这是我设计的电路图:6 o, [6 b5 |; J* {- N2 r
110344ej2z2oo2rflo7oe7.png.thumb.jpg   n" T* C* W) X7 P. a) O

/ j- W: B7 H& C+ LPCB Layout:
0 z# A0 l! g2 r' s5 w" e 110847jjbjvt34vwt3v5bb.png.thumb.jpg ) y5 y8 \4 v. F- y  n
% Y  l8 E2 v; e' N8 {( g
不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O. 2 }) Z5 U0 y% v, O) ~- L! W4 R6 c: A

3 k/ R7 C% F' c/ W5 P$ g
8 x( d9 c, n8 Y. p
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑 % F5 q( B5 p. H% f% \; G2 N, r
. ~% U8 F* T! F( f
软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。6 [% f, X3 b9 X, u2 c

9 G9 }8 i$ I3 Q% G- q总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:
* }0 d5 A7 ^) ?9 r, d3 S 113818pmrfsb6z0byt6t06.png.thumb.jpg ! F& X! l/ N1 {8 S
( o. ?9 N8 t3 f' n* H$ P# j
其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)
. W. ^. Z( `. {6 g; x  F6 A# B+ b) o- m; G. a
USB的中断ISR,bare metal哦
) n6 `9 m) Q3 x7 `4 k8 _  X

9 j5 v1 V( z6 M# }+ O7 ^) yvoid USB_IRQHandler(void)0 T5 B& _! C/ m. r  K3 T
{. V  I2 b7 L6 J  N, i
    if(USB->ISTR & USB_ISTR_CTR)0 K9 a2 B% W4 _; e9 i3 \; k
    {
' O& n6 D1 {% i4 [; R4 z- @' y        if((USB->ISTR & 0x0f)==0)   // EP_ID==0
6 T" f! Y" }% z+ ]; y        {
' Q5 a3 F1 q& x1 z* i  @) Q3 x            switch(ep0_state)
4 E3 H" c, T4 R5 B1 D7 m            {
% n5 j% z! a) H8 h4 k1 K                case 0: ep0_state |= 0x80;$ Q) u4 `7 j4 B9 I
                        break;
) N3 s: n* L/ A- b. H, H                case 1: if(USB->EP0R & USB_EP_CTR_TX)
' X9 p* a/ Z- q( f" t4 z                        {& I! ~# }3 Y/ \: V. t; Y
                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
# h% w/ m9 l- l                            ep0_state=3;
1 Z2 U) ?4 R( G                            return;
7 a4 p- l- {- c5 x7 h+ o, ]                        }9 w: V/ u- k, }8 ^3 G7 @" X
                        else
3 P9 @/ D, \! @; [                            ep0_state=0;# H3 B8 l- N# u0 Q* j
                        break;6 \2 [& O5 d% _8 e! a3 Q/ o% q+ o! Y
                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet
6 W* j7 I- A. k& c                            ep0_state |= 0x80;8 i1 C( r7 i5 O8 a- m& {: Z- y$ h
                        else! B, A9 L3 w8 I# R# y0 A: i
                            ep0_state=0;
, y2 Y1 A* |& b1 \) d" D0 d                        break;
" {, p! C& C% @" }8 L                case 3: ep0_state=0;' T/ o* J2 }' T/ W
                        break;1 S# j; c! n% ~4 r5 |* x
                case 4: ep0_state=0;8 L6 Y6 [$ x3 r$ R9 _# Z( z
                        break;
" D- u, x6 O( I: l! d                default:ep0_state=0;
' _- f6 y& I! t                        break;
5 q) ?# X  B7 ]7 _8 E: |            }
0 v7 T) E8 D& L" d0 Y& g) n% }  |            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
4 b% @2 A* e7 J2 p0 k3 N+ J            return;- ^0 w% C. j+ L# \* N+ y/ w
        }
+ s" I. F; c  d. s8 g) H        else    // EP_ID can be 1
: G/ i1 p9 q" F' c9 D9 t        {
$ `7 E4 m4 s1 u            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag0 x) m1 c$ ^) s$ S5 w$ F
            ep1_wait=0;
- O: [. {: |  O4 {- }" r            return;
& m$ s% l, B  o3 D; W. S/ D        }
! |; I4 H1 |. T; ^1 Y, m8 m    }
, m- b: I+ M  P6 ~: ~; p    if(USB->ISTR & USB_ISTR_PMAOVR)
: z* i4 M6 {! u+ C/ b+ q    {
9 Y6 V4 [3 R3 d) b; Y; A! v        USB->ISTR = ~USB_ISTR_PMAOVR;- E. D, k& J7 S, f" P6 k
    }( T& W# H, V1 Q1 P0 G0 b/ z
    if(USB->ISTR & USB_ISTR_ERR)  O) B) g1 g0 y. Y2 V
    {5 m! v/ O; `* `# h
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear/ l) a" t% |* r/ r0 N+ I% b# d
    }
, D* ]3 W7 F6 ~4 Q- j8 \, z    if(USB->ISTR & USB_ISTR_WKUP)
' _% O# u7 }1 z3 l    {
" A2 t3 w7 @6 k# `* b        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
! W; c/ e0 {) q: W' e/ V        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear
& Z/ G# Z6 O/ P    }
- O6 p6 A* }: U2 F- k* v4 R    if(USB->ISTR & USB_ISTR_SUSP)! w+ W6 P' C  l* w3 X1 ~3 C8 ~
    {
) a1 p6 h& o2 Y7 z        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend
) ]4 y. P0 J4 v        USB->CNTR |= USB_CNTR_LPMODE;   // low power
! ^8 w  B. l, B& d7 b        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear' G8 s7 [6 P7 j+ q
    }
8 W9 Q/ k6 u4 x0 b% ~: B7 z    if(USB->ISTR & USB_ISTR_RESET)7 v. {* d" \8 ]8 p
    {
( I4 b2 p- s  D9 H+ x1 J2 z4 p        USB->BTABLE = 0;    // buffer table at bottom of PMA
* E# n1 d8 x+ S4 T0 e        USB_PMA[0]=128; //ADDR0_TX2 ?8 `: E% j) o/ I
        USB_PMA[1]=0;   //COUNT0_TX4 _- Q! W3 ]1 O( P# f* i+ h
        USB_PMA[2]=256; //ADDR0_RX
" }$ o5 C# @( T/ V5 m        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes
" h* H8 ?! ~( y, M6 z" t  _- A        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;8 i) M2 ?% C4 C/ m
        ep0_state=0;! [& G) a4 y" X! F
        USB_PMA[4]=384; //ADDR1_TX
* a, x* `6 v/ T2 b6 l        USB_PMA[5]=0;   //COUNT1_TX1 _# ^% N- k, m' O" r/ ?
        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type0 x9 x  V$ Z3 H; i
        ep1_wait=0;7 W, _; n- {/ j; V7 Q* A
        USB->DADDR = USB_DADDR_EF;      // enable function
& v3 L7 g3 S8 q3 @        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear
1 @0 L- m. H9 X. U' ^    }
2 H( a" u; l0 P; w, p    if(USB->ISTR & USB_ISTR_SOF)& E3 b* C4 B" d, t* A, O0 ~# f7 \( `
    {& j4 c: y# V; N9 l% O6 c
        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear, Z( k; [2 Q& [2 V
    }, G  C" r' W" @, G( B
}0 u9 q6 I1 m* Y0 r9 }) W, f
+ }7 d  o, h0 J2 U* A& X% m& x  d
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。  u% I) x5 C0 S( ^( L0 v

6 K& d* c2 @2 p主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。
2 x3 z$ Q" F2 m1 C8 l7 H, L" U7 v- w  ?' Q2 d6 F- t$ {
    while(1)
+ p* t; P" F3 ~/ k3 E% e1 b    {
5 n9 k$ U5 E& U7 {# H        static char row=0;( h+ {" z" Q# l
        __WFI();6 W& ^9 X# C, P" F/ T
        if(ep0_state & 0x80)    // request data processing% v! v) k, ]9 I7 p8 b
        {
, T" p2 Q; _4 Y7 n3 @8 I+ L            if(ep0_state==0x80) // SETUP phase% u# H- W) ?% ~+ d/ `6 }
            {
- R4 L2 G* P0 S: A  G# v- Y                if(!setup_packet_service())& U. d# q8 Y2 i: c% z
                {
- |  j  G1 R+ r* P7 N( p                    ep0_state=0;
2 i9 @9 l! K5 X7 z% k6 G                    // not supported
' Q; Q$ X9 m, @                }  K" N1 m# S+ x! f$ }! q$ e
                // ep0_state should be set to 1 or 2, if processed
* N' @/ O4 Y/ N9 h( Q+ O) U+ ]            }
0 x" I5 Y0 N2 }6 I            else    // OUT phase
7 j4 A, e7 N& R            {6 ~  O/ d( h, ?4 ]& Q
                // process data
4 X8 _: w; ^  ?6 K! c8 p: x                show_LED(*(uint8_t *)(USB_PMA+128));1 z' c' E3 U) `4 K* [0 r  u
                ep0_state=4;
6 |# `$ k% m6 a( }2 G                USB_PMA[1]=0;       // Zero length DATA0
& [1 X' _3 [4 [5 |                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;& Z: T- V4 U" x
            }. q, _' R& n$ G* N2 `- p& G
        }- \5 r7 a) N) h' E% v
        else
  [: W8 ~+ T5 M- m- N+ r* k4 f" S        {
6 q# I" b* b+ K- t; M            if(usb_address && ep0_state==0)* d1 ^! g' Y- k8 n6 [% W/ ]
            {. _' |5 G- g8 f! ]1 O
                USB->DADDR = USB_DADDR_EF|usb_address;
/ c# F- {3 {3 \; l; K$ D& `* T                usb_address=0;: o) `6 H2 }& c
            }. i- U% Z: G( l( _
        }' }# `( ], `4 {  X
        if(row!=scan_row)   // new scan line, [% A, A/ X! m% D, {' i$ a
        {
& B" k5 b9 I4 x9 r( H2 n3 N* x8 o            if(key_state[row]!=prev_key_state[row])
2 F' m; X$ o. ]8 s2 g' ~, r5 ]2 ]2 [  c            {
! \2 x" A/ C2 U9 P% V1 Y                uint8_t test=0x80;# Y7 H5 r* j' C9 o; ?
                uint8_t diff=key_state[row]^prev_key_state[row];
/ S) {! J+ @3 q) T                for(i=0;i<8;i++)
% V2 s6 m9 o# U$ y                {- K9 s* _! P3 T8 H0 g6 u
                    if(diff & test)3 {' f( J, b3 E
                        update_key_matrix(row,i,key_state[row]&test);
, W9 _) y& q* v, G. A; T% H                    test>>=1;
8 M" _" U# `( t+ O                }5 z+ n( E6 Z" y. w* H
            }
' e5 N, E7 n1 Z( D4 _' B            row=scan_row;
" n( i7 z6 J. }* {; x: `% S& p        }
8 d, P7 H7 K/ Q6 k5 O1 a    }
6 A3 t2 D: a) m$ H  k0 h% w: o: w+ O6 r( S) H3 a

0 T# K4 [7 u- w! L6 f* _( y7 UEP0的控制传输,把用到的请求处理一下
. O! e( D/ g; S; ychar setup_packet_service(void)
  v- R& r- a: x/ {/ E{
; m4 q* `) T2 q7 s/ N    if(ep0_std_req->bmRequestType & 0x20)   // class-specific
' H9 x! H3 T3 g& }    {7 |5 [7 v- U. J
        switch(ep0_std_req->bRequest)
6 `% V6 h' ~4 `5 H. A1 ?        {: Q# }0 c2 B( Z
            case REQ_GET_REPORT: break;
0 Y9 I8 B0 j+ c# }) c/ V# z  z            case REQ_GET_IDLE:
, d' s! |+ P9 b" x, W% N; Z9 O% F                USB_PMA[64]=0xfa;   // return 1 byte! O+ d, N% q$ _2 y
                USB_PMA[1]=1;7 V; g  E& F9 T1 p% L
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;) i: g3 e% D9 y$ }
                ep0_state=1;$ l- G6 c' t! P  e+ o1 X
                return 1;- p8 i7 u- n9 S  L; t; L8 U" i
                break;
: Q7 ?- j( H: A            case REQ_SET_REPORT:! G5 E+ G% i& U9 A
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;* f( e; l& {8 f' q: @8 T
                ep0_state=2;
0 F# f2 o1 y# ?7 G$ I5 e6 g                return 1;
1 K8 [1 t/ \7 z8 X' B                break;
, d  A3 }; ~, R/ e6 W1 @            case REQ_SET_IDLE:
( V! @+ l8 h' S0 T: c5 M/ {9 O                USB_PMA[1]=0;   // Zero DATA
7 X2 T% ]+ }% z' g$ J+ E5 s) l! P' \                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;# n7 e9 v: u' c, U( U
                ep0_state=4;
2 a: q$ j* I. V4 F' t5 `8 x                return 1;
/ {/ O1 _& M" @                break;& g" M4 U9 T$ k/ t& j
        }9 r% _* ^: `) H7 B5 `2 E. \/ @
        return 0;' i) n) x& m( v4 t
    }
4 d/ J- Z: N  Y# v/ @$ w    else    // standard
5 q9 b" J$ i2 a, g5 W- R, ~9 z    {* ^: g" l2 A0 i" r- |6 B5 [
        switch(ep0_std_req->bRequest)
& T" V- e: v2 P. p        {
5 C7 [6 M! ]7 T& m$ j( m  C: l            case REQ_GET_DESCRIPTOR:
' C1 i. @- Z2 E( i9 l) o                return descriptor_service();! |5 o3 Y3 R& x* ^
                break;% K" S0 Q  k( g9 R; h
            case REQ_SET_ADDRESS:0 `, r- H" j( Z% I
                if(ep0_std_req->bmRequestType!=0x00)  g) Q. `3 K8 T
                    return 0;# i3 ~: J  }3 L/ `
                usb_address=ep0_std_req->wValue;! S% O/ {7 {: p& ~( y* q
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
/ I. V/ p& n: Q/ g9 K* T7 P                USB_PMA[1]=0;       // Zero length DATA0/ Y! ^, k* ^: S& @, p
                ep0_state=4;    // No Data phase
$ F5 x/ f) u' a+ z# V                return 1;2 P  g( N' c: ?- T
            case REQ_SET_CONFIGURATION:1 N  C9 Z' _0 @* t" P
                if(ep0_std_req->bmRequestType!=0x00)
1 w7 Z7 M  Y1 I6 v7 V: J. G- a" t& d                    return 0;
" K% d8 v/ x3 v: s! h                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;: L% s1 f( f; k& ?+ Z
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
2 g& D9 d1 P4 w8 a                USB_PMA[1]=0;   // Zero DATA
1 e9 P$ A/ d3 k                ep0_state=4;    // No DATA phase
3 I9 I4 K  }/ M) }/ i1 Q                return 1;
( K  b& P+ t3 u% ?/ J$ h            default: return 0;
6 }$ j# o' N/ X3 u        }
" w) |+ h5 z5 M. h7 t; A( t( q! A    }
9 J1 U% ^/ T% ^- K- t3 O+ A4 [}
) R! V7 v- r2 s+ n6 K+ i, r& y3 ^

$ X( g0 e6 F6 A1 p# n0 h: T2 Y% K. d1 P8 O6 [# s
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的
! M& @% d. o' W* x9 Y4 Qchar descriptor_service(void)1 j5 n$ u9 q. ~5 X
{4 I7 q; [7 V0 G; P, w
    switch((ep0_std_req->wValue)>>8), C3 d, i+ X% |/ e, M# ~) g; |& k
    {9 \  p: g! `; @: n; y; k# W
        case DESC_TYPE_DEVICE:
! a' j; ?% w/ {7 A+ Z  J. M2 k            return ep0_preparedata(&DevDesc, sizeof(DevDesc));0 F+ F( p! ^3 T7 |
            break;
# Z: ^- A0 j0 A        case DESC_TYPE_CONFIG:
0 D8 M* N; m- H! Y5 e            if(sizeof(ConfigDescData)>ep0_std_req->wLength)
; v5 b8 f. c9 H4 n/ }+ D                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);4 m, K  q9 I" _4 r- b0 J1 _
            else- u; B. c" H) i, o
                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));
) G6 }! j" [+ A' D  u            break;: V& O7 J: |  P  v$ C/ a1 p9 _0 j
        case DESC_TYPE_STRING:
9 G% S/ o( R( e; |" O% N            switch(ep0_std_req->wValue &0xff), G0 C& l% z& C1 B
            {
" g2 U7 v$ q8 W$ Y) l5 Y2 @5 r9 p                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
& A& c7 S( V, t5 z& s, [. F+ m                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));' [0 @: E, v+ s9 l2 B
                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));  M5 ]3 y1 ?; [) h3 f
                default: return 0;
+ _# K, h. |! F" |, U            }. W5 |& |" Q7 x3 T
            break;2 p4 m- h' r4 E! U/ F. m
        case REPORT_DESC_TYPE:0 i$ z, x1 z- T, \  n. S) h8 \0 Y
            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));
4 U4 A* B7 D* \$ j! b        default:
; Z# c- I, s+ y: H. I            return 0;) g( H" e6 K5 z
    }  ]' r$ S" t2 A( o; x# {* L  x
}
( }, @6 C  o  D下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.
$ b! }( ]% A' O% nvoid TIM6_DAC_IRQHandler(void)
- Y; V  H2 G+ g- K2 g8 W8 y+ J{6 U& J; }4 {& X% r4 g6 L
    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);
) S" h& ^5 U" h% K2 d: H
9 }; R  a, _8 z- Y' }

) ?2 B; I# @0 u, Z- E, s    TIM6->SR &= ~TIM_SR_UIF;
- o  [, y# i9 I' ^) ]& {% Y    prev_key_state[scan_row]=key_state[scan_row];7 ]' Y1 d( I# k
    key_state[scan_row]= *PA_IDR;   // update key states
& ^% Q  K7 K; [- ~+ Q    switch(scan_row)
- F! l" u* \. n, q- p8 |    {" C5 K9 ^1 A" g1 F
        case 13: // next row PB14
+ e: j" p0 m/ K9 A: H$ c0 j- M                GPIOC->MODER = GPIOC_DEFAULT;9 h6 H7 H) W  H& a3 E7 y6 |
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;. j0 j, D7 u7 p$ A
                break;  l" _$ F# }+ Q
        case  0: // next row PB154 x( b; A6 @0 S( h: b
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
* H- v6 d  \3 C% b, d, f# {                break;
0 _! T3 D% l4 q% J) a: n8 P7 p        case  1: // next row PB3
4 o% r) [# x  _( [                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;8 m- [9 ^7 S. A% E2 C7 C
                break;
, k" b0 }" H$ ^; t        case  2: // next row PB42 F# b- |6 x0 E$ `# q' N4 d
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
& S  ~$ t! Q7 U& l; e                break;
5 |2 R7 C$ I! X, V8 Y        case  3: // next row PB5
4 D* \7 Q; i9 O- h# r/ }2 S                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;7 I& ^* f6 `, G9 d
                break;( n& l$ C1 c8 q: Q! Y
        case  4: // next row PB6
8 h# A0 O1 z/ l; o9 ?2 w+ h                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;
) e' n& a; f5 e, x" Y, F* C. Y- N- p                break;+ x0 [2 ^/ ?0 X0 h5 a
        case  5: // next row PB74 A2 u; j5 h- a8 x) }$ q' t  j' a9 ~
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;  ^5 c# G( c1 `
                break;! {; ?) c9 G% g. D! L
        case  6: // next row PB8- u# z8 L/ H# r) \  R6 k
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;! @* O2 _8 K8 I6 c2 m
                break;
5 |( J0 i( Y: P$ z% X3 x5 q        case  7: // next row PB9: S# O8 u- l1 U$ F$ p% ?/ `* y
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;
0 i+ r$ W) m) z# V4 _. v                break;1 J% t, E5 F% {% X( z" t, S# W
        case  8: // next row PA8
9 x, r5 O5 B/ O" {                GPIOB->MODER = GPIOB_DEFAULT;  a$ P4 b: ^& M& E
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
/ K( c; L& c9 X5 Y) v                break;& J9 ^( f6 V+ r( J' V; F+ |! k7 E
        case  9: // next row PA9
' H0 K* X6 J! `* e, R                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;. @& q3 I& h* J, ?/ u% i# d' [
                break;) [! ?; g8 l+ G! M! M& I
        case 10: // next row PA10
; p% `8 k5 {9 o( d                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
* b  o& F, }* v  c                break;
! o% P2 D5 ?7 h$ P0 k0 U        case 11: // next row PA152 y7 a! O, Z- g' {0 m! X/ y
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;0 O$ z" b6 g" g' R
                break;
- V/ I( T' H# ~* q7 L+ d4 I        case 12: // next row PC13
) w, _- Q/ [8 U; m9 Z8 r1 L                GPIOA->MODER = GPIOA_DEFAULT;* i" g5 W# B0 M* r, {3 e! U
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
$ t$ p1 b. j# I0 p                break;# ?+ ?' }) R6 y6 h8 G8 b2 B
    }  r4 ]) ?& Y7 t: t$ `1 r
    if(scan_row<13)- R" a+ k, h$ q, O% _6 d* |6 Q  ?
        scan_row++;+ E3 N* c  z7 W/ O  c+ N! R( i; _
    else
. h# K! [# j5 `        scan_row=0;
7 O; a  w/ z8 `4 Q+ }9 J}
6 R* ~  B+ v3 T* \" x# {0 ^; r' Z& p$ v3 N9 u; _- @" a- E
$ `3 T0 P& V3 }* \; C" I/ E
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑 ; t/ J) s* X+ r2 }) r

, ~/ i* H- {. h& R2 P- O. f( V, h扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。& q( D  }5 j0 I3 Q: p" X" A8 Q% R
& l' }. e7 H/ P4 K
要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:

+ p  V9 u" f8 X( T. Q
' y' v" X+ J! d( n% n# E/ }+ x( k% {' R8 R% \
const char hid_keymap_qwerty[14][8]={
' ]) W8 q1 h( r  x    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},- X! Q: e: q0 d" j
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
) f; ]7 ~" J( F    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},5 m) ~" z( Z0 O  l3 F9 j) Y- o+ b
    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},' _! `$ d0 ?+ t6 ~
    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},
% P0 k* a1 j5 W% K: M    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},. f, ]" O. [. T' f9 I: _+ o
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
4 d# b. i! z8 I! W- h* {4 q    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},/ n: _* f; {9 I$ i! K. T# M2 L) E
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
! `& K: [- w5 O$ j% J$ w    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
3 _1 R* }( g% X    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},2 M3 ^0 A  s+ p% E
    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},- ?) U9 E: S! s7 k  d
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
. ^: A0 [+ h  Z  F    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}& H7 g, k) E* c% z; S2 X
};
7 k1 ?+ Q2 ~8 u( l7 p3 g: f& R, X7 V5 \4 R$ s0 `7 W$ I: W5 P. O
8 Z* t2 F9 u# F7 M! |2 ]
const char hid_keymap_dvorak[14][8]={
# C+ I; F* G$ g8 P( p9 ~2 c$ W- [- o    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
& y1 M% r" r6 Q( t% b    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},/ A  T( @# a$ C3 y( P* a, g
    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},
/ R# F7 i9 Y# }8 y# W$ O4 M    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
# f% U+ Z; ], V& |+ `9 i    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},! O: o  Q! j/ M# M. I9 Y$ `
    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
" W) C' i3 x* S) [; g" B    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2}," }$ q1 P2 `3 F; I: Q
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},
9 u6 P9 `" a! s0 ^: a6 c    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},! Z: ~. z; K5 s2 y6 a5 R- J" }
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},7 e, G+ H- W0 O' v! ^2 D) e+ X/ U
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},1 y9 r3 [/ E' ~7 t5 W
    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},
  {! C/ |$ x( H' r# f& g5 c    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},3 \4 J. J6 j! ~/ n
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
6 z3 D' E$ ]/ _' c" c7 n};1 i" @& \( t1 O# S  u
0 E. x5 b0 S0 }6 N

& t+ W' U! ^! U5 O" U( @- Z' s/ u上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。+ `* V% ?. a& S% D
# ~# |8 G9 M0 t
HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:
* f6 y  p' ~+ y) ]5 L- c% {

6 h, O: ?$ w1 Fvoid update_key_matrix(char row, char col, char onoff)
/ e  D$ F! t# V{# l7 o  n; R% }' V
    static uint16_t hid_report[4]={0,0,0,0};! a) L4 w: {' |, x+ Z3 v
    static char (*hid_keymap)[8]=hid_keymap_dvorak;
! c, |5 D5 w. C* [3 M  v: k% v; `1 [7 h- O4 V2 C; @
  @7 }- `) t4 e: Y
    unsigned char key=hid_keymap[row][col];
+ H% d$ u; M$ d    unsigned char *report =(unsigned char *)hid_report;0 j& |9 \# p/ Q2 n9 c
    char i;3 P; v2 Z2 _7 Z9 Y* m! L5 U
5 b8 Z0 w) ^9 x- F( o1 T8 O3 {

  }! _3 r2 y; f- Z' f; o5 l8 O    if(key==HK_MODE)4 P/ V+ o4 f! d4 b6 u
    {$ z/ g) q3 W3 u2 A% E' {
        if(!onoff)
4 K) j* g; Q7 Y* A2 k' Y0 G8 q        {' N  L& h7 |% }2 w; ~4 Z
            if(hid_keymap==hid_keymap_dvorak), `* Q/ |- _1 o( d7 ]
            {; x/ G- M  s. z9 g( W
                hid_keymap=hid_keymap_qwerty;' S, Q4 Q- P  S6 K( ]0 `
                GPIOB->BSRR = (1<<2);$ m! y- X% k. h7 k, [$ E: O
            }
9 i6 B1 d; D( _. N: p4 N$ @9 N            else
! L) d8 ?1 O' y8 ^            {
, ?1 ?5 P1 G( u9 S* x                hid_keymap=hid_keymap_dvorak;2 V% R1 z0 Z8 o0 v7 Q
                GPIOB->BRR = (1<<2);
2 ~* F2 m4 d1 b+ O7 C            }
' S& d; M  _# x( I2 h  P0 ~        }
9 W- I& U7 G* P8 E1 R' D( O        return;9 n, q6 t& c6 N/ z) M
    }
2 D4 W0 ?- w8 a* P) |
& b& ^( ]- ^6 m9 p( K
* h  l: J" L" u1 e
    if(key>=0x80)   // Alt, Ctrl, Shift3 U/ W- }# O9 p/ J" F
    {) G1 @/ C: |3 H3 _$ o6 L
        uint8_t bitset = 1<<(key&7);
& J$ H3 o* s; Y$ G8 R0 `; s        if(onoff)   // non-zero is key up
9 a; g6 t9 C  ?3 e4 e# c            report[0] &= (~bitset);2 S. e: ^: A* B9 `8 q7 E. V
        else  N% _2 s1 ^  a. \! ?5 T2 L
            report[0] |= bitset;
; w0 U( l( l1 T* `/ h9 _% g    }
, e# w1 I4 y# [0 F8 M/ j4 |& S, W    else& R1 P7 M9 m" R' L" h- p% O
    {' X/ [# K) E: v& B% ^8 f; p
        if(onoff)   // non-zero is key up
1 r, K2 p5 m+ ?8 `$ M) i. N, ?        {+ K' f9 M# k7 d4 i$ K( h0 ?
            for(i=2;i<8;i++)
: T# C3 r" j5 z- v  e' O& o* [            {
3 v  _3 ^1 _, g9 B7 C: o) A                if(report==key)# @: r4 h0 T& d  N3 Z3 [& @: j
                {3 H5 w2 U  s  k# Y
                    report=0;
8 `; A' S2 x: h& Q* d+ B                    break;
- s: {' B/ \7 {                }% G3 o+ \9 z- ^/ n, l4 J
            }' J5 u8 i2 c' ]1 v$ }8 ]
        }! p; O' d$ A* a; k7 B3 u' B, _
        else
4 g; L$ s% O! Z: c, I1 Y        {
( @$ \  |2 Q: V$ G            for(i=2;i<8;i++)
. ^8 _1 d% D# y( B            {! W. K) M) R; S% V
                if(report==key)
% e6 U$ y+ V- _1 C                    break;" W7 @% O, p7 Q5 t( U
                if(report==0)
1 L/ h9 |, w8 D( E8 G3 z: r( \# z                {
5 `& L! D! z" z( ^% H% @: W! n                    report=key;
3 f) \: }% k7 q# ]4 a                    break;2 A# p. L  l: u$ t
                }2 A! b, {2 {( `
            }
9 p3 W( T3 v5 ~' ?        }7 ]. E3 \  C( N1 j0 T0 i+ j4 U$ ^% \
    }
& }/ b! y9 }5 V# R7 @" y- V. l    for(i=0;i<4;i++)
. G% w( ?) h' u0 P% O7 g. y3 l        USB_PMA[192+i]=hid_report;
% L) V/ _8 L( H" k+ q2 ~% B    USB_PMA[5]=8;   //COUNT1_TX% x- N5 b5 w2 A0 K7 d+ [
    if(ep1_wait==0)
2 w! y* }9 b( ~2 j7 H    {/ g! r) e/ ^" A& V
        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;" N8 W% Q* H& e/ `4 t
        ep1_wait=1;& q  ~8 t" E+ o$ o; U( ~, e) B! @" s( N
    }
1 \1 I1 @% J6 S3 r}7 q# Q% Q9 F! r- P& a0 L

' m( c/ D( m- {
9 N/ J  J2 H6 Z( \) Q6 K完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。# L- b9 N1 G% ^5 H0 x  N
keyboard.zip (8.7 KB, 下载次数: 6694) # T$ N0 |5 d7 ~
$ q0 |) R0 R: P# r/ J8 {2 \1 r

$ z/ g1 @# o2 E3 g* q- B  f
5 Q6 N& u0 E8 j' g! E) b/ s, O2 K% W% y
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘
: m+ f8 }' h* J  H不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48
0 H; F- G  f# ~# [: e1 r/ t/ y刚开始我以为要把打字机改造成电脑键盘
+ D8 R. ^% G& z5 a: y不过楼主也很厉害!

4 l5 P7 s$ p* r$ q/ ^哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
& ?- A# P' k! ^1 V: ?. s
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

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

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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