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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

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

9 u) V' a" W: f6 C, j
3 j1 i1 t. B, U& B9 W
235140i3a36qivqzuvmt5q.jpg.thumb.jpg
: G) J6 V7 ~$ P% P' q- P
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。
. v. [  i- Q: t7 [+ j6 d 001734klbyoluenuwz4h4b.png.thumb.jpg ! N0 B+ q9 v8 P, v5 ?
为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:( A; |) l0 Y  R& w" g' n4 ~
003625r2agx2f5v922cf2f.png.thumb.jpg
  j! U# j/ o+ N3 N( [! P9 U- [其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.1 Y* E8 R0 t: _
Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:

9 Z) Z4 ?0 m' M5 v# @ 005836yvs0wvovwsssgd3o.png.thumb.jpg 7 N9 H, Z( d' j6 ~- t* h
Dvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。, f7 R: w- c, R
$ \: v9 l  \0 g, n8 a% S/ V# U9 A
我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。
  `% J# f+ ?; E" C. v, [# @- O% E: R
到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。, J" a; o6 ^) z6 S0 i

4 f5 F% T1 Z2 t/ }" p4 P) A+ G" O" p机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:
8 [) W) p% R  p* x  k! E  @3 y; ]6 S. F
123206vx24rfxfp2dfrpxg.jpg.thumb.jpg
6 ], q% o9 d2 Q& p/ @6 n0 |" U8 _----------------------------------------------------  分割线 ----------------------------------------------------------1 F1 t% j$ _2 Q  E

# J9 l5 E; Y% X! L先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

, t. g; w. B. N# j( E6 o! ?' e1 a) j" y 020011osionbunl4ui44vi.jpg.thumb.jpg ( n7 m1 p* a$ @  p
轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。7 e% D5 S+ M4 N
020017j8ycmnv7788bqv52.jpg.thumb.jpg % F1 j7 t' i4 `5 P6 n: K
特写,80C494 J* l/ P& z: j) N5 h( b$ o
021040oujzuvtut6iujtvz.jpg.thumb.jpg
" O7 G( y3 s& ?# d( y7 ILED部分,使用了一片D触发器锁存指示灯状态.
& \# U5 y' N$ ?/ a. ^* ]8 n 022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
: h& S2 H7 ?1 T9 }0 A暴力破坏,将80C49拆掉
4 r- j# m8 k+ {0 Y 021113e48qq98vyohvhzzh.jpg.thumb.jpg
0 k# \" x' S% V# A$ b9 ]. R拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。
4 v; R7 `7 x9 k" N, L9 o 021125nc9az6dj33rlds2r.jpg.thumb.jpg - v* m5 y% [" _: E; C0 a
焊好元件后的板子,准备替换80C49
$ t1 R) |) c. o) T& b; S 021146zmmpmh4pav4o7g6a.jpg.thumb.jpg ) m* q7 c# r7 o, `, r- X9 U
用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。0 C" u6 G4 l$ A/ Y6 O7 m
021104shifhnrqbr3o5nlo.jpg.thumb.jpg
9 R* }; w: I8 z. Y这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.4 W4 i1 v% O, y3 T
022003ym1p9u4ug40280uu.jpg.thumb.jpg
3 m6 l. q0 I* t, g, |; O% u# C, V开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。# @% G& x4 A9 t. \& B! |
023313kt141q9qajtol7ma.jpg.thumb.jpg # Y  C+ I) G* r7 _; k" e2 Z6 R
我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。
5 w( ^  u9 C1 L% d" q6 R 023322nt7l5xb3ltttkltt.jpg.thumb.jpg
2 M6 ?! e+ A* U) A0 I3 R% _
主键区键帽就位
  x. B2 i+ ]5 g, I; V4 U' X9 z 023331hin88e8wkrwzwikx.jpg.thumb.jpg : b9 Z) A0 x1 j# d
编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。
. |' I' p- f" Z. v  U 023336wjzlgopugg1jyy79.jpg.thumb.jpg : t& g% x( E1 |0 W$ v' W
最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。- t6 }. z2 w/ d. A; A, b  ~7 c
023341sffu4j3g2323h6fl.jpg.thumb.jpg
* t4 u/ R$ `% K2 t# s# Q! ]9 S. A

/ a; G( [2 O" ~. i: N/ k----------------------------------------------------- 分割线 --------------------------------------------------4 H1 `, Q+ V2 q2 t! c. u$ o( `
) U/ l' u- X5 N6 V$ W: I

; I, J3 X; U; T  Q6 B
% I# ~6 w4 t, u% \% O4 Q$ ~8 d. V
! v6 E: }2 O% J- w  i5 y

+ h7 {4 F/ P. N7 W- G4 R
& A1 i3 _9 Y, [; ]; q# W+ j
+ F; \# ]+ V  v' [) N( O  z5 l

+ n* a5 ]" p% k6 H
4 A$ e0 Q- ]7 j6 X, Q
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。
$ }) K, m6 I2 m* X8 f4 N5 n80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:
0 v: |. u5 X# S/ L8 T# f 104025nzibm2rmiomhyirm.png.thumb.jpg
, A- j0 s$ j# }; l: O) l; J% c& S

" m( ~3 J" T  l% V# P, R# C, r其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)
0 F; G: k8 H0 n, Y( w) x8 y 105004zkrez5houvkkznko.jpg.thumb.jpg " ~$ Z/ h9 V9 C, b+ E: A

! R$ h$ n& p- @扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。' Y% r. E; n5 l! K

% D* o* E( [. E/ a这是我设计的电路图:
8 R5 [: u% o2 F- v; X 110344ej2z2oo2rflo7oe7.png.thumb.jpg 5 _* W. l8 D$ y% F8 s6 |; B0 Y
/ Z4 G* H* x! u6 g) c5 n
PCB Layout:
, M0 `$ L( P+ l1 M 110847jjbjvt34vwt3v5bb.png.thumb.jpg 2 b4 X6 _, J7 b; y* @: w
7 }6 b' s5 X# R4 g  H( v
不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O.
& x" I* ]: `1 _1 q
, ~4 Z% t( b# k# w0 M3 e! _7 ?" a5 k

+ @5 i, j% I5 l5 J
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑
9 x" p4 `3 A2 ^2 g$ O1 a( ]' W$ v3 G) W& z/ H8 I
软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。9 e% ~1 R# c1 U8 E6 j
2 z# B+ D# h+ |0 N+ }. Y$ V# Q" C
总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:! D+ g$ r0 c. r7 r
113818pmrfsb6z0byt6t06.png.thumb.jpg + J* h. J9 N, [7 C" Z3 L2 G6 w

: j* n0 [$ D' W# P其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)7 D- ~4 ~* d8 f& n1 T: B
' A# e) ~! ]9 H
USB的中断ISR,bare metal哦
$ r1 v" r0 `- {6 W4 s- }$ H

, e0 x1 _) y4 D( j& jvoid USB_IRQHandler(void)9 u2 h. j9 P! k5 E4 Z  a
{
- b2 V- j) v4 y$ F5 {    if(USB->ISTR & USB_ISTR_CTR). F+ v2 N: d" z" |* a( b: K
    {& P: u$ _( ?( m  I. e! S
        if((USB->ISTR & 0x0f)==0)   // EP_ID==0' N0 p5 A$ ]5 h
        {9 p. q  E7 f! T* O/ R
            switch(ep0_state)8 }* h: X3 O5 y1 z3 X- W
            {+ ]4 l1 Q8 N/ ]2 X* E5 q6 k
                case 0: ep0_state |= 0x80;9 n' Z# A; Z, P4 R  P6 u2 c3 C
                        break;  Z8 N) C) d! \. D5 z5 B
                case 1: if(USB->EP0R & USB_EP_CTR_TX)9 ~% I/ X; c; l6 w0 q* c6 b
                        {
5 c, E) R- u! M* h6 `8 X1 j2 f                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
5 p% H& O8 P7 z2 K- B1 t. J, `( y                            ep0_state=3;) q: W% }1 i  t% X9 @8 Z4 U4 g
                            return;
; ^' [$ ?3 m' k) p1 y6 p                        }5 ]0 G6 m* \2 A: B
                        else# @- r  P! h8 J% e. M: a
                            ep0_state=0;
# D% u7 q, `" b                        break;
& U' P1 j. j$ c- Z3 }                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet
: O# ]* w' ]$ K) t2 ?1 M/ t                            ep0_state |= 0x80;7 T* c) N6 n. h8 q* f+ b- J1 P
                        else: b$ V8 U5 W! R* D- ~+ t
                            ep0_state=0;
. X$ B% u- t4 @4 c" U                        break;
' @$ v; q1 F- n- \" h9 t% q" N& g+ O                case 3: ep0_state=0;
, T+ x# q' ^2 L. u# E                        break;- U1 b: f7 P% A  W  K! r
                case 4: ep0_state=0;  c' H. _5 e; `$ [$ q
                        break;
( [/ ^2 J- a! Z4 G9 m6 o8 i; n, W                default:ep0_state=0;* [& h5 z* F  k, l. i& F7 `. i5 J
                        break;# G0 ~. a' O9 o3 I0 [
            }
  g' c8 ^+ M" k( M6 Y6 v            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
8 j2 r5 t% i: j1 w7 Q# I            return;
7 C2 [4 e% l, C        }; ~. D+ l6 D9 T' ^7 k9 I2 L
        else    // EP_ID can be 1* X5 p3 R* `) Q3 y( Z
        {
, c& T6 ]$ i5 ^" d5 v7 K6 d            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag7 r! U3 ?' W1 ^+ ]; P* I
            ep1_wait=0;! ]0 G, U/ X  S: P! q. J
            return;. v) |3 Q9 y* n' T! Z  K
        }. ?$ v/ c9 W5 Q* X3 W& m9 `
    }4 o, W+ C: \5 I$ U4 s& Y
    if(USB->ISTR & USB_ISTR_PMAOVR)
( d* Z5 A; r: a: y) [    {
8 Y7 l( k# L7 L! M# E8 @- s- {        USB->ISTR = ~USB_ISTR_PMAOVR;: ?+ l3 m* C0 Z/ C
    }4 H* b& H4 y3 c4 H
    if(USB->ISTR & USB_ISTR_ERR)
# v# w6 `; x( p; N& f2 H! F7 ]/ {    {
1 u8 G2 L; t3 q- \$ x8 ^- ~        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear
' O3 ^2 h3 u7 V  C- U( R    }/ q( X: k% i6 |+ o# S9 c+ D7 b, l
    if(USB->ISTR & USB_ISTR_WKUP)
; ?& s6 a+ T: D* @& |) k    {% i9 ^3 w4 J, ?7 Y
        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend% D( G. z5 P2 ^9 n# v: n- ~* k3 m) l
        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear
  w# m" J# j5 N( E$ s' z    }3 W  h! l) a3 f, D& i& t
    if(USB->ISTR & USB_ISTR_SUSP)
8 G- Q2 Q0 A# N    {
& j* U1 m9 R5 C% F6 Z        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend
) ?6 p% i$ B8 N1 l4 T        USB->CNTR |= USB_CNTR_LPMODE;   // low power
* D; c7 S; ]9 Q0 M1 h2 @% I        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear& o8 h' r& ~) I# F3 d0 @2 M
    }# J# G4 P! n! t  B  _4 v9 ?
    if(USB->ISTR & USB_ISTR_RESET)# Y) U# ^& Q% \5 D0 z+ @  _( l+ Y
    {
/ z  {) U& D* \# x6 \" K        USB->BTABLE = 0;    // buffer table at bottom of PMA
0 c$ Y& V$ I1 Z' u9 m9 ?' T        USB_PMA[0]=128; //ADDR0_TX- o' k- H$ `0 n/ M. N
        USB_PMA[1]=0;   //COUNT0_TX
. X, x2 T6 N, g$ O7 L6 i        USB_PMA[2]=256; //ADDR0_RX
$ U' F& G5 `/ O$ @4 n+ X9 t        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes
4 H9 A$ y# S& @) w( Q# _5 ?        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;' E/ U2 k+ H  i0 s6 J
        ep0_state=0;# a9 Q4 R2 h4 m% M; o( [0 r4 |
        USB_PMA[4]=384; //ADDR1_TX
( o! I! ]9 t* c' L  v        USB_PMA[5]=0;   //COUNT1_TX
9 ?$ l( v+ F4 w, t! [8 C        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type
4 `) F, N" K0 a1 H        ep1_wait=0;+ _8 v5 M+ D4 \" ]
        USB->DADDR = USB_DADDR_EF;      // enable function
1 x4 i7 j, v. K' h        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear+ d, e. o* C" }" D6 e: C* a
    }
8 L  l0 Y) @. a0 E    if(USB->ISTR & USB_ISTR_SOF)! V$ p1 ~% p, H2 K
    {
# c; Q1 B1 P$ B1 C+ y# b5 H) l+ b        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear
6 s3 l9 F, F4 U* K    }
. E0 P+ N, P/ b, A' h+ }7 u, j}
' ~( U; V1 z( ~
% X# b0 X9 q! y. l1 j3 R8 n
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。
! v8 q( Y. M/ D7 P) C8 k8 h  o% ~* e. t7 M
主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。$ u. S; |- Q  V1 @! x! J" ]
, F8 M* d% q# \" T' V; A$ D
    while(1)  y  s8 U+ M% e8 O
    {9 |% {2 l' C0 M6 j! O
        static char row=0;
, O! Z6 |% o2 `3 a- w' |        __WFI();
8 g" k0 e' Y/ _' i4 K0 X6 u        if(ep0_state & 0x80)    // request data processing. C# N! X1 B6 l' g
        {
, h0 u3 V4 ?8 s; @  o! ^            if(ep0_state==0x80) // SETUP phase
5 j& O3 V( {) r% i            {
6 u, ~7 t& V! B/ i& [( ]7 Y; \8 G                if(!setup_packet_service())
0 k+ W- i6 q8 o4 Q: f! d                {
/ l5 \' ]# A- w3 B! \0 j                    ep0_state=0;# }+ ?" U  Y  G- W( n" v
                    // not supported. S: t$ T) w. C7 I0 b
                }! \- ~$ \" }& ^$ F0 r  F  u
                // ep0_state should be set to 1 or 2, if processed
) v  N) _) s0 \, q( d. W+ j            }& q: R* h- K7 p  g+ a
            else    // OUT phase
0 x0 `. `! u1 u8 q) v* F            {( |% _7 f4 [; Q0 ~. W8 M
                // process data
' S, \3 T9 P& O9 K/ w" L4 [                show_LED(*(uint8_t *)(USB_PMA+128));7 `8 `/ z" ?! ~& u$ b" ~# J
                ep0_state=4;4 \# Z4 f5 `  h( ?/ ~
                USB_PMA[1]=0;       // Zero length DATA08 d* t; q$ E  y, Z  a1 H
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;* x/ x, l, R  Z0 ~
            }( a# n% t, P1 N  W+ l: g$ I
        }
5 ^8 `: v& l# o6 f% T) k        else/ U/ H6 y9 G+ i- x, s
        {
! G% ?, N. {  |* {" K            if(usb_address && ep0_state==0)
1 E0 y6 V4 o" X6 y+ x            {
6 r2 ^0 I  Y$ u+ e                USB->DADDR = USB_DADDR_EF|usb_address;
7 A1 P6 m6 i7 p+ M                usb_address=0;# p  C( E; \; c
            }; G& n( U, Y: l. j
        }
8 T0 |3 [1 D5 `0 [' m        if(row!=scan_row)   // new scan line4 Y  A2 \" p$ W2 W
        {
. P& Z. Y0 P, k5 A' s            if(key_state[row]!=prev_key_state[row])1 H7 x( Q& Y! |* W) V! M1 s/ @6 d
            {' P  u$ U; s5 k" Q
                uint8_t test=0x80;
- E; ^+ a! s/ y  U                uint8_t diff=key_state[row]^prev_key_state[row];
& ^& G. n/ y  A7 Q2 U2 W6 ]: \                for(i=0;i<8;i++). M: r  N. O# D
                {
4 h* U+ g$ `' J8 f                    if(diff & test)
4 k1 N* s* V. ?( d0 M& k  ~* t                        update_key_matrix(row,i,key_state[row]&test);
& }" |- q3 B& r, g2 x: n                    test>>=1;1 t1 d0 k( a* I4 {
                }
5 `( `5 D7 l* M& e5 {. z* Z& Q9 x9 D' i            }
* J: a7 z- N9 R            row=scan_row;
: o2 t" g( E+ \        }2 C, v, _7 V, p! s; ]
    }" |) _6 N+ b$ n& ]0 b

1 K  r. S& J: \0 B$ @! @7 T! f4 u5 t' x: A
EP0的控制传输,把用到的请求处理一下
, \3 d: Y4 b  `6 \0 Schar setup_packet_service(void)
/ b; y& @7 u8 U2 K( ]{
/ I; |/ T3 X% f% r6 w' e    if(ep0_std_req->bmRequestType & 0x20)   // class-specific
. f! Q  X) `2 M4 \* M' I& C8 q- j    {
  b& G7 U% s' h* j0 \- I        switch(ep0_std_req->bRequest)8 J) `7 Z3 ^* A9 V4 E
        {* E! P- P9 }; p7 I6 |  Q) z- K
            case REQ_GET_REPORT: break;
3 Y4 i+ X! J3 E* |) ^6 X7 w            case REQ_GET_IDLE:
3 S3 I- G+ j/ n$ V1 ?                USB_PMA[64]=0xfa;   // return 1 byte
3 q& A( E3 ~0 y! x; n! K                USB_PMA[1]=1;6 U/ e- x) ^( w" G8 Z& x
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;9 D+ f* I$ @  w' u1 W
                ep0_state=1;
  R, f4 S. p* \3 D; J) r* B                return 1;
. W, ?9 r. [, z3 D0 J0 b1 @                break;9 l! m4 f, @$ B$ \
            case REQ_SET_REPORT:
# b: c6 `; U$ m: x/ t! r( R. A                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;3 z1 O& P6 _/ D
                ep0_state=2;. K2 H5 M& b3 m/ w; K
                return 1;
, d% T- T! V1 t  _5 {                break;
+ Z4 N- D( A0 A( ~* m3 f' c* i! B; d            case REQ_SET_IDLE:
3 C" e& F( Y' F/ v4 z; H                USB_PMA[1]=0;   // Zero DATA! X4 ^! B1 i/ X2 k: @. T
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;+ e! e$ b1 J& s! H5 z$ t
                ep0_state=4;6 b; v# S. F2 b& v& ^
                return 1;+ E4 j1 f- D1 C# ^) h
                break;
( Z! ~- }5 _8 i( C; a6 Q, m        }7 i0 O2 e. Y+ n6 w/ A
        return 0;
2 ?: G2 z5 P. ^    }, U2 i3 W" b, R7 i+ o6 I* w
    else    // standard
# `1 b, @( j" R0 K6 A( e    {
6 L2 t3 |' e" @$ N        switch(ep0_std_req->bRequest)$ n6 m$ j+ O3 R
        {: |, i( _/ h. V* S6 z1 G
            case REQ_GET_DESCRIPTOR:9 x; B/ G4 r$ g1 U% \; w5 [3 g, r
                return descriptor_service();
$ h3 [, @9 a3 a8 e  `; g                break;
5 Q" o! e0 T* S" @            case REQ_SET_ADDRESS:0 [) l. V6 Q, _6 v) h; W! P, x
                if(ep0_std_req->bmRequestType!=0x00)
4 T( O1 h' [. D                    return 0;$ {+ A, _& w* t2 Z! P
                usb_address=ep0_std_req->wValue;
# q! r8 s* A0 ], W8 [                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
! J6 m  T$ e: p8 T! V2 k4 F                USB_PMA[1]=0;       // Zero length DATA09 c; Y0 M5 p9 |# Q/ w
                ep0_state=4;    // No Data phase
+ v) z+ l( K3 H/ w. m! M. k+ N                return 1;- j1 y, l2 X; P8 `) Q; P9 J
            case REQ_SET_CONFIGURATION:
2 B$ k* n3 ?$ g0 R                if(ep0_std_req->bmRequestType!=0x00)! ]. b3 q; Q# \; g, I( W/ r
                    return 0;
* L9 R& I1 d8 p! y: E                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
3 T+ ^& b6 }0 |; I. Y                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;" d: D/ z1 \' ~- z6 Y
                USB_PMA[1]=0;   // Zero DATA
3 Y5 {8 a0 A% ~5 j9 Z8 A2 t2 n                ep0_state=4;    // No DATA phase
2 t0 P+ h9 U) p4 W% H5 p9 V! T                return 1;8 B. C( t7 j. x: [( ?4 n* _
            default: return 0;
9 e: p# H0 A, E8 V        }4 l0 Q& I7 W7 h( ^
    }- k( B; ~$ C( K
}
/ Q" O" u  D  D3 m' D3 X' W  w) W1 m! J, m) J  Q# J6 n  k" U. L
5 f: y: ?, x+ c  [* g% h+ a% R8 n

6 J& y2 G; t- z7 j! \$ W2 c
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的7 I# x' n. p6 D5 w  V: K# r
char descriptor_service(void)
  _- y& X) k5 F! \" l5 k2 {/ x{
6 X1 d2 Y( a5 T2 ]    switch((ep0_std_req->wValue)>>8)' A1 L1 Q7 E& j9 v) m8 S
    {7 p8 U1 P! l( {7 E" G: U2 ^
        case DESC_TYPE_DEVICE:
. E) ~3 R, a5 j; a1 I. C- ]- z            return ep0_preparedata(&DevDesc, sizeof(DevDesc));$ \9 E! C4 K7 y# U
            break;. m& v( B9 @9 M
        case DESC_TYPE_CONFIG:0 R5 b6 q* Z6 S) Q" W( s  G
            if(sizeof(ConfigDescData)>ep0_std_req->wLength)  b7 r- ]+ b2 e" s$ S( I5 i
                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);
7 A/ x: @) o" b! y0 t( K$ V+ x            else
9 Z# B9 g# ?7 V8 O2 m' u; u                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));
( {1 z2 t0 |4 P. ]            break;4 a) [3 y% J: p) ?+ k! C  }
        case DESC_TYPE_STRING:  D3 C& v! v. `: h- a5 x& m/ J  a
            switch(ep0_std_req->wValue &0xff)
8 J$ z7 @0 K( \4 p) O8 M            {
9 [: K4 C4 K! |) ~                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
/ o+ a$ I3 [6 O                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));5 v1 ?; r# B) d8 T6 j7 s. V1 t
                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));3 l  I6 k2 q  J) R9 f$ @
                default: return 0;
, h5 q* J6 x6 T) f8 b4 o            }0 @" {5 U: g7 t5 Q" C4 f
            break;; M  ]+ A4 o0 e2 L% \
        case REPORT_DESC_TYPE:
. K8 L9 F$ C# A8 e            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));
7 ^# S1 O; X5 y+ O7 T% ^) C" M6 a- S        default:
  L2 j- g! m+ z, ~            return 0;, c( N( G; `: z& W! B1 H
    }
8 S/ l( d; w5 |' c) P/ D$ D4 \}  q$ ?! I& a* T2 `
下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.
7 @/ x4 u1 G: Y3 r. e9 F5 L. dvoid TIM6_DAC_IRQHandler(void)0 r1 v  T7 @6 Q" }3 Y! @
{
  z0 @! k4 `7 q/ o2 D8 T, F0 X    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);( n- q. u& h3 b! f; P5 P
/ o) C9 G' u0 V+ Q1 ^% g; v! J
) q! j9 g  \( S- O
    TIM6->SR &= ~TIM_SR_UIF;
& g* B# x4 i6 W2 _( f( R# ~1 x. L7 y    prev_key_state[scan_row]=key_state[scan_row];# J5 H5 B- X  _4 G8 p* Z- u  k
    key_state[scan_row]= *PA_IDR;   // update key states
/ U; k: M4 [* t0 v. j! Y+ `5 B    switch(scan_row)
; T0 H; I& x. `1 z3 p    {
! |6 g5 u8 f/ m" z( @; [        case 13: // next row PB14
/ o, M% F) ?7 `- |                GPIOC->MODER = GPIOC_DEFAULT;
  g8 X% f3 _/ f0 a7 Y, H; f/ M                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;
8 s, w" P8 {. A" x& e# E                break;
6 @, G) K1 D+ J$ k3 l% I        case  0: // next row PB15
) J" s; {& n, y& b: v1 Q+ u                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;3 ^$ b1 [3 B6 X/ Z7 k
                break;
; S$ p( l- {- L" K) c        case  1: // next row PB3
: C, e9 ^9 j8 ~6 n! _- g- G                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;
: d  V. S4 N- m  e9 b/ N; |                break;
! C% R4 V0 i/ W        case  2: // next row PB47 O0 a% y2 M! A; Q/ D9 h
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;+ y" B1 g# f6 U  s; h
                break;. o; G/ p& z0 F$ ]" k
        case  3: // next row PB5
# o* A  \: |/ N; A# k3 l% U                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;7 _5 a3 ~# {% z, b% i& Q
                break;- M3 r9 r' C( J: C# N
        case  4: // next row PB6
3 S3 e, ~. F; ]  D8 e                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;6 P4 t4 o1 N  D! i, z
                break;
. m. A! c) v9 ?9 z, v7 W# C- F        case  5: // next row PB7
2 D: t& s6 a2 t* g                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;1 C# q1 R4 g& I# T1 m5 u8 S
                break;
& T0 C; F; ]9 ?2 I2 c        case  6: // next row PB8
* G' \" A, C( a7 X                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;( }4 `  C4 `( X6 q
                break;8 S7 V1 p, `% z$ w# l' }
        case  7: // next row PB9
& v# W. {8 u2 L4 ?9 Y% o$ {                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;" S$ u/ H- G6 q" B0 {; M, _
                break;% W  b5 U8 C8 y
        case  8: // next row PA8
* T5 f+ ~2 W% s: x. Y6 ?                GPIOB->MODER = GPIOB_DEFAULT;
2 ~9 {7 E! k/ k4 {                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;( x* g8 A) M. s  ?4 j6 y& J
                break;
9 |& S0 h( b' V2 J6 v        case  9: // next row PA9
( z% Y4 P5 }5 n7 c4 V; r8 b                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;% Q, R* A! o( o; u8 L( j
                break;4 ?0 h. n  Y1 P6 \. F
        case 10: // next row PA100 W) z6 {2 t3 D" d! A$ Y3 a& i
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
; M7 [5 G( m* E5 d9 u' }. N                break;/ b/ q; }: C( J& f$ k3 n  P9 A
        case 11: // next row PA154 Y. M# }; z9 ^
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;" A' P$ f5 X7 p6 ]7 F
                break;
) \1 h4 e4 Q( y1 z& e- K. r) w        case 12: // next row PC13( Q+ \+ U1 `7 _2 Q! {
                GPIOA->MODER = GPIOA_DEFAULT;6 r$ I  S5 U- U1 n# u7 n
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;9 ~0 D8 R7 Z6 D0 ^, E0 X4 F
                break;7 v, i, F9 \+ b* d+ d- U
    }
# C4 S. x7 _# P2 [. G4 X1 E# b. }    if(scan_row<13)! Q* S9 t1 o- l. ]
        scan_row++;
/ C$ n! l5 X! Q  y0 Z( t3 q7 M    else
# b+ v0 W' E5 ?1 @        scan_row=0;- ?! \) r! O  _, s2 H2 B
}
4 I/ e, Q2 e7 z/ |- j: f& r  Q" ^# b. l

/ ]5 i8 q8 }  e) A7 W2 q5 j
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑
. I: }' X% r% U; i# a# t6 T. Z" c7 {
扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。
* E5 O* W9 Y" y! A% {
+ P8 \+ w% S2 ~* Z0 ?要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:
9 ^* B& \+ U, U8 y/ t- c4 m

% j. J6 A, @, \' U9 H
& H9 U: g6 T3 y7 p4 B, D5 R/ M7 [const char hid_keymap_qwerty[14][8]={6 G9 Y8 L+ \+ s# L. r1 ~
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},' H0 U: j- c7 u2 O0 i, g6 f
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
9 E' I* }' _; \7 @# I  d    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},7 g9 I+ p# S& @
    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},
8 Y* d, [  C& b7 f% U7 a    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},
. n& {0 Z9 c6 o    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},; e# q$ M7 A! |) ?7 ~8 ^  k- W$ E
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},: _( f9 J6 @( r% N0 |8 U0 c
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},7 D- [$ t' x0 b
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
! l: Q: F; h8 ~* X5 M6 d( R    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},0 N2 d. v, k" ]6 I! @$ ^1 k& s% ^) Z
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
, Z- K5 _, B2 v! a    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},. T0 r2 [% o1 Y5 P5 P
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},4 A0 G. N/ T- x, T$ i
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}1 q# V  x$ Q4 U+ f1 ]
};( M: Q6 W. Q% s! n' ^

7 g3 Q3 v, e3 R7 ~$ ~- O7 r
  p# ?" {( C& Q8 N' x5 r
const char hid_keymap_dvorak[14][8]={, A7 E4 f: o! c& O
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
: i* O' [1 ~1 K7 S5 A9 b7 D7 R5 i- W    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
6 x* k. C1 C5 a- y5 w    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},
: K8 s* W& r2 g( K* r    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},) H5 P0 u, Q& I, F
    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},
! q9 h; y7 U+ P    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
1 J/ z8 `; U0 d! P0 V0 J    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},- j" F0 E1 X9 |$ l" ~: h
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},
( U1 q5 k; }- v; f2 x    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
$ j3 T- l# H9 Y# U    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},4 q  l$ b  V/ {" O) q; w. I9 a: \( C
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},3 k# M7 }0 O/ R/ l) m2 h' Q! x
    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},
3 \2 B0 S* {; W& u# t    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},- E1 k, [1 E: F. c5 ]' P! W
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}2 d5 `% K; v8 T  p5 e
};
3 n  d  B0 I  [
. H1 j) q* W6 N  o8 b4 W0 e
2 H8 l) c& ~& R0 t上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
, @, a9 U) R: h7 A- X# r/ N0 u/ Z! q+ v
6 l4 d+ P0 {$ U1 X' J; `. }4 D6 dHID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:

+ ?; G% N* n& r5 ]6 }: Q8 D( @4 a
3 m' l, \5 ]  q; v. A- ?4 Cvoid update_key_matrix(char row, char col, char onoff)
1 `$ W( L; ^/ P& J' L" x* s+ m2 a{
+ U) O" w0 Z. E' |5 i* I3 m6 F    static uint16_t hid_report[4]={0,0,0,0};! S7 x0 \0 |4 i  E) b
    static char (*hid_keymap)[8]=hid_keymap_dvorak;4 ]/ Z: B6 Z# y, F0 w7 [/ w- f; I

" B2 `+ c! @, T! ^
  W) e( Y5 I* J/ L* R0 L1 Q9 |. f
    unsigned char key=hid_keymap[row][col];2 Y' F8 D* T" E4 ^2 ?) x, _5 C
    unsigned char *report =(unsigned char *)hid_report;
9 M) h- I6 \' c8 U, \1 Q+ h! G    char i;
' Y, N* X% L0 ^/ J) M% h
8 B! B- l2 @3 t$ O. v/ {
5 z- E& @4 k4 u2 C& N
    if(key==HK_MODE)
' E: Z1 l% H6 r    {( F  }5 p/ ^% G- {0 U
        if(!onoff)
4 K7 B1 V2 h( m        {
' j; H# f5 }# G3 w            if(hid_keymap==hid_keymap_dvorak)# W# |/ A/ n$ n3 j& T' P# g* m
            {& L& J3 E* ?3 a' ^; I
                hid_keymap=hid_keymap_qwerty;
) c/ d! z$ ^$ L0 B& _! g+ A/ x                GPIOB->BSRR = (1<<2);: L5 w" h, e( j
            }
; S+ @; ?) V& h3 e- ?9 E% Z& z            else
6 V( k  P/ e- m- O            {
& `' o( I  B. l2 F                hid_keymap=hid_keymap_dvorak;0 U. n& |1 }9 t- Y, b" C
                GPIOB->BRR = (1<<2);  I) K1 A+ w' f8 u9 e0 {8 u
            }7 v( ^8 d; |# @( }7 U
        }* ]4 A1 y/ I( A& G8 B# |7 m
        return;( P# ~2 M- L2 d' J! g
    }
$ N# t* l1 d1 b" ?8 H( ~
; u: a3 n0 R( k4 k" F

1 _& D5 B& a# _* T' S" ^0 ?    if(key>=0x80)   // Alt, Ctrl, Shift
3 @3 P- \# \6 \    {
. ~5 L( j, z3 \5 I        uint8_t bitset = 1<<(key&7);: N  w, A9 A6 {+ U
        if(onoff)   // non-zero is key up4 G, P& ^# b7 `) Z; D6 F
            report[0] &= (~bitset);
7 k! F9 u1 a- K  i' x" ~        else
+ b# N' r; ~2 T" q* C- U% [            report[0] |= bitset;
# Z3 N  Q4 }& @8 B9 i& h    }
3 }7 }, G( f9 _* J. r    else
( c1 Q: l) ~2 j1 N    {
0 j# s# e3 ~1 q6 g' c/ F        if(onoff)   // non-zero is key up( M" Y) {5 O- q# a6 `- \
        {# |" Y3 I/ S1 @) {% S
            for(i=2;i<8;i++)- I( q( o2 ^# @$ T, [# M! }5 [
            {1 S% l. G$ }5 Z9 B( [% K
                if(report==key)
" G4 D9 X/ F" s6 @9 z& A3 T) O                {
. `% P+ i5 J& z+ T$ A                    report=0;% [" ^9 w- }  c9 ]0 ], ~6 f$ h
                    break;
0 X4 b0 Q3 y) c* A7 I4 U# f                }  ]# U9 ?" ~9 F; T- e7 h# L
            }- c  T* t8 `  z( x  T5 Y
        }( N+ B3 \& r  p' @8 J8 T
        else
4 X0 A* R: W6 O6 i        {: s4 B+ X" T! M1 _6 T
            for(i=2;i<8;i++)2 j, {; ]4 b* x. [& I# J
            {- v4 h  j# {) ~; T3 T
                if(report==key)0 U  K2 e9 |/ U1 i
                    break;
% l7 s; t" G: v2 C' j) j" S  g                if(report==0)
7 n" m4 F2 I, {8 |                {- G7 ?" `, @3 H% U" Z7 v) b+ l# l
                    report=key;
5 `, s3 Y$ P8 Q: F                    break;
* A; W. N8 T8 z                }4 B# |& w# E: R$ _2 m+ }9 P  e* u- P
            }6 y& l& [8 x+ {+ V
        }
0 I( v- n) u# V; |# n, Q* R( Q; U    }0 R4 u& o7 _" H# t% ?
    for(i=0;i<4;i++)
" F2 m1 H& b8 r1 ~) L        USB_PMA[192+i]=hid_report;, U/ l$ n* u) Z4 ?# c5 j/ J( a
    USB_PMA[5]=8;   //COUNT1_TX" x  U$ c' H: E( q) h
    if(ep1_wait==0), [: p/ J* P  [. K- C1 \
    {
* Z% W1 n. a$ e7 r: T; _        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;
5 L! Y3 I0 L, V2 R        ep1_wait=1;
/ M% l& s* i8 N    }
4 w) m) |9 w9 V/ b& d& |}! a) _- z% O, s4 ^! g2 g! m

% `0 F" p" g, o1 P# n3 `+ R/ s. H  e$ c( f& u; r% M) t
完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。
$ T, x7 \; _" e0 S8 r0 M8 u keyboard.zip (8.7 KB, 下载次数: 6380) , h3 U" X# m7 _8 Q0 m

6 N8 F: p2 C; |" F. b$ Y4 C2 `2 h2 m0 F  U0 ]

) t- W- W1 v; z7 s- T+ S! T+ `% ]$ a0 F5 x, R$ @
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘
# h3 ~, a+ Y" @5 d- Q不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48, {1 {0 x; l5 ?$ R. v8 U  G* `) W
刚开始我以为要把打字机改造成电脑键盘- F( l" ?/ q  C1 s7 d: x: p3 I
不过楼主也很厉害!
& d$ V0 Y# W% T  \9 U3 i3 @" W& m
哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害, ?; e0 T3 U- F, k
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-1-2 16:21 , Processed in 0.177700 second(s), 27 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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