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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

积分
288
发表于 2016-9-21 20:34:19 | 显示全部楼层 |阅读模式
本帖最后由 miaozhuang 于 2016-9-21 20:35 编辑
/ n1 ]0 t  x2 \8 b2 B3 M* q
9 }3 c; g) c3 ~+ b: Chttp://bbs.eeworld.com.cn/forum. ... digest%26digest%3D15 R4 c% p5 o) w
这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。
- V" D' X  R  i" c: G
# f" L( _, M" e2 s  l0 B7 z在看这个帖子的诸位一定都在用计算机键盘吧。键盘上的数字键是1到9从左至右排列,或者是右小键盘区那样三个一排有序排列,反正规律很明显。但是字母键却不是A,B,C...到Z这么按字母序有规律地排下来的。我刚接触电脑(其实还是学习机)的时候,没在意这个问题,觉得是要盲打嘛,反正对两手的手指头来说,按字母序排列并没有什么好处。于是用多了这些排列也就记住了,从来不管它为什么要这样。其实,PC的键盘键位排布上是延用了打字机的键盘,这是设备演变过程中很自然的一个延续。打字机的历史就要早很多了,我没有亲见过打字机长什么样,而且,咱们汉字是铅字打上去的,和英文打字机方式完全不同。
( H+ `# A1 n5 J3 i8 O$ M
/ }! ?/ _: c9 K
235140i3a36qivqzuvmt5q.jpg.thumb.jpg
/ o$ ^& K4 L0 f
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。5 A' t1 `/ v2 t+ P! H7 Y. \
001734klbyoluenuwz4h4b.png.thumb.jpg
6 t/ w! H% Z2 D6 I" G7 u5 y为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:
) [( i* J5 F& w" z$ Z 003625r2agx2f5v922cf2f.png.thumb.jpg
& B& d& ^, Z- p# E其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.
6 \' a# b5 ]9 l! N" BDvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:
) X  o1 Q  Q$ j/ f9 x/ Y8 V
005836yvs0wvovwsssgd3o.png.thumb.jpg
: W3 I2 V$ K# z, S) lDvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。
& U6 B4 U2 Y0 Q$ x+ D# P% X" c' T( [& R  }
我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。
' i9 f5 L+ r' q4 h/ p5 @  [4 o8 Q9 Z2 r5 w
到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。8 y" k7 L7 f9 W7 n" l9 X# M  S' n
2 d- w$ @0 D5 l5 c* y, c
机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:
  r, w3 L5 L) q) D4 q) l6 I  {/ Y7 z, @
123206vx24rfxfp2dfrpxg.jpg.thumb.jpg 7 a( T& t% ?$ F% C: k8 \
----------------------------------------------------  分割线 ----------------------------------------------------------
5 L  f0 D; v, x# M- c* F; Z4 A& c! b  j
先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

' s1 r: E$ I( u' S; N 020011osionbunl4ui44vi.jpg.thumb.jpg $ G- n. L# h! u$ `. D, t/ F0 o
轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。/ t. j$ l. h% z; t3 i1 K
020017j8ycmnv7788bqv52.jpg.thumb.jpg   t; [" U4 T: \8 h7 m% j/ a: O
特写,80C49- H: a/ _# Q+ A3 R  z
021040oujzuvtut6iujtvz.jpg.thumb.jpg - T& h; w2 y7 z9 m# L. V
LED部分,使用了一片D触发器锁存指示灯状态.& a8 C  D2 U3 }
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg   I" r! U* i) o
暴力破坏,将80C49拆掉
2 f# p) q* j* @; y: G+ s/ L! T8 ? 021113e48qq98vyohvhzzh.jpg.thumb.jpg
) c2 b! l. I) e2 o/ o, s拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。
2 H5 p5 {% l5 j) ~1 ` 021125nc9az6dj33rlds2r.jpg.thumb.jpg
6 ]: y$ J+ G1 m7 O2 l' {
焊好元件后的板子,准备替换80C49
! V: j3 Q( ^; s9 d6 ^ 021146zmmpmh4pav4o7g6a.jpg.thumb.jpg $ {. f3 [0 k3 y
用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。
1 ~4 c+ d" w$ ]( z3 R' |! [& C# ] 021104shifhnrqbr3o5nlo.jpg.thumb.jpg
' q& v' m$ H9 G( r# \这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.$ q$ Z1 u2 p$ Y, U; u  D
022003ym1p9u4ug40280uu.jpg.thumb.jpg
- c: N7 g& b" G/ N开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。; B$ h6 _+ f1 A' _
023313kt141q9qajtol7ma.jpg.thumb.jpg + e4 Y( `* x4 Y- T* e
我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。
% \5 N9 W2 @  C 023322nt7l5xb3ltttkltt.jpg.thumb.jpg
0 |# h% q" G! n2 d+ w% _
主键区键帽就位1 q) @0 h# t$ [6 _) r3 }5 y3 ?
023331hin88e8wkrwzwikx.jpg.thumb.jpg
% U' m' [! g0 Q( P$ B- b: E编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。
5 s& k% U( z8 m3 u1 L 023336wjzlgopugg1jyy79.jpg.thumb.jpg
& y6 N6 m9 G5 [$ d: C最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。; [4 u# }5 v( V& {  B4 V, J: q
023341sffu4j3g2323h6fl.jpg.thumb.jpg * ]% c8 e) A, Y

3 u9 h5 n: A& Y$ C----------------------------------------------------- 分割线 --------------------------------------------------
4 V, r5 F. F* @8 o9 N$ A; C0 z

! P* Q2 ]! b0 a6 q/ z& M

) Y  t! \3 P) Z' i$ X+ \1 Z
3 C) v/ {! Y5 a& k/ M! N- V9 Z7 j/ x8 V8 _: ]

& q* H8 W8 F0 _! D9 {/ L$ o- G
/ E0 x% I, Z' }0 Y$ o4 Q

/ o5 y2 w4 T1 L4 @* c* z" b$ y1 y' f! q% K; _

- C9 c  q% g4 o% q% [0 R, p& y' }1 d3 l& y3 Y
5 V# J) ^* g; [9 Y8 D  `, D
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。
. I/ K& n% R4 I. i$ f80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:
2 |0 k5 \% E! V, l8 ] 104025nzibm2rmiomhyirm.png.thumb.jpg # w) q; t8 V) A+ b7 `4 ?# a
4 l% U" Q9 {6 G8 ]$ h; U
其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)
$ T3 ~+ v7 ]1 I 105004zkrez5houvkkznko.jpg.thumb.jpg
" }8 A% w, e; y8 O( _! n
' H) Z( _7 a' d6 ~* s3 [% T
扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。
0 ~0 J2 p) @: a3 X( ?% y2 ?5 c$ F' X; C" J# k6 s! z% J
这是我设计的电路图:* z7 {+ P8 H: X
110344ej2z2oo2rflo7oe7.png.thumb.jpg # C  I' X! `! e6 @& O/ a( |* n1 s+ ]

8 {' v" n1 {5 UPCB Layout:
8 v/ t7 d" z( P& v' i, D9 n5 R( a) z 110847jjbjvt34vwt3v5bb.png.thumb.jpg 4 v: k7 o; R$ {, d: f" P9 ?! B! E

& y/ X, T, ^: N; K不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O.
+ c/ W4 V$ J" w5 L6 e; P
, N5 {9 }% J3 x: G

* J' o9 {6 `$ s( B6 Q
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑 " l$ d" u8 X, _* x
0 U# V. L5 x& x2 G
软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。
9 D) g: P+ }* C& S- q/ R4 q5 X" b* \6 v
总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:& }- U; t# S, w9 `: U1 u
113818pmrfsb6z0byt6t06.png.thumb.jpg
6 P, S  @4 @! L* q+ q  g9 v

" k! b  q: g* o! t其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)5 d' b1 J7 v+ E" z9 ~
2 j; {4 K* ^* k2 g3 g0 U
USB的中断ISR,bare metal哦2 J: W7 K, J& M9 D' V/ p# b$ Z
5 p" g. y* ?8 g  ?$ S& ~
void USB_IRQHandler(void)
8 N0 h( A# I) D6 z/ n2 b% {& t- l{% F; N0 d) E# K6 k2 f
    if(USB->ISTR & USB_ISTR_CTR)
5 b" Q" N& b( D. t0 E% V  _    {
9 M: |" {+ m& Y$ n4 G5 u8 X        if((USB->ISTR & 0x0f)==0)   // EP_ID==0
' _, ~& z0 I/ r* j; e7 c        {* T+ p& T7 _4 {( D1 l( X  w
            switch(ep0_state)3 g  @8 m5 ]0 x- C6 z" t
            {
3 H' A" t; T* j. t4 E  I$ j: E1 G5 T                case 0: ep0_state |= 0x80;" [* D" V/ ~: W- C% K! C
                        break;* p2 E( v3 m  I& H2 c
                case 1: if(USB->EP0R & USB_EP_CTR_TX)7 G0 h0 }6 L8 }! a+ K4 r
                        {, a! ~: @8 g$ P" A7 R3 O' ~" V
                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
1 M1 Q: V2 S9 e                            ep0_state=3;8 ~! y& ]0 }7 f1 I% k
                            return;' p6 l- O0 T. R4 S5 U: u, R$ X
                        }
8 T. o* h* ^5 D0 C9 F                        else
2 K3 Y: D0 o0 D! C5 p9 D! n                            ep0_state=0;0 _- U. V# e# F  g# B8 m* @# |
                        break;+ o. k, k- E/ s$ T
                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet
' h1 o) F+ m5 g$ {5 R) @                            ep0_state |= 0x80;. ^4 g5 P8 I3 ]2 a$ ~
                        else# I# E0 N1 R  X. k3 T+ q7 X
                            ep0_state=0;
8 U/ r! x2 P- n6 q                        break;# k5 @  X- B6 y& w) d2 w( r% z# L
                case 3: ep0_state=0;7 q1 ^$ `9 _9 Z3 S$ B6 H
                        break;
: [6 {! P5 t: z+ ~$ m                case 4: ep0_state=0;
" u$ J% v0 g8 w1 F7 _                        break;3 i* m( U9 M0 R, L1 c. x
                default:ep0_state=0;
% l. X! M$ d$ J* C                        break;
1 O; a3 u9 _; S9 H8 y0 r            }, U2 D0 n( }  W6 e
            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
  o0 u/ c4 s; r. G( q5 ^            return;
+ B4 b: |1 z: d' l6 Z  s8 V$ `        }7 \9 A, X1 \6 q: b6 c& `. P; j
        else    // EP_ID can be 1" W2 D& \" G: V/ l+ }
        {1 ~. ~( D7 K8 G. n# A3 O
            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag, ~& K' z7 U* z. L
            ep1_wait=0;: B; e- u0 b9 o- B7 q- k
            return;
5 S4 j" E( q5 n4 ]2 f8 X        }( j% G3 q) o( Q: z
    }
6 Y/ h2 \2 Q& U8 v. T7 Z$ V    if(USB->ISTR & USB_ISTR_PMAOVR)+ i# m: R- E: G5 e/ S
    {, T8 I- p  m9 F' N7 |& c" c+ e' M3 z
        USB->ISTR = ~USB_ISTR_PMAOVR;
1 J8 u& b0 o6 P1 t0 K8 V+ H    }: R6 {; `. n- {+ m$ u
    if(USB->ISTR & USB_ISTR_ERR)4 Z$ l5 ~/ U6 |' ~: R$ {, E3 a2 |& |
    {5 L1 d8 R+ ?7 g! R
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear" p6 f5 m8 L. Y% k& n3 j
    }3 q! G0 F1 g' y8 a
    if(USB->ISTR & USB_ISTR_WKUP)& Y  }4 X9 x. A, M. `0 F7 f
    {9 X/ I, g, d7 d4 n" Q8 G
        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend3 D  C$ F+ N* q* E8 ^/ t: T9 e
        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear
! J8 b, }$ E4 [, T    }
! a6 A0 O5 z& p    if(USB->ISTR & USB_ISTR_SUSP)
% x/ E: f( l0 B1 y, H    {
" e" A7 o! t' {5 T# x( q3 Q        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend9 E0 i, J/ E; O# H# l. ~+ @
        USB->CNTR |= USB_CNTR_LPMODE;   // low power6 k1 B* M: W" M! L( T' b- k
        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear
+ y+ l& u0 d- {    }% T$ f: m' ^6 W, y1 l
    if(USB->ISTR & USB_ISTR_RESET)
; E8 G. Q* _5 f2 s0 }    {
* r3 T) s+ t) |9 l9 @        USB->BTABLE = 0;    // buffer table at bottom of PMA
/ \2 x6 E  T  Z  A        USB_PMA[0]=128; //ADDR0_TX
. Y. A4 r# S+ s* C; U1 x* B        USB_PMA[1]=0;   //COUNT0_TX5 B2 E0 z* C6 ]0 [
        USB_PMA[2]=256; //ADDR0_RX
& U7 F1 v, W2 p! J9 A        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes2 s; e" I0 f& Y+ p6 `
        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;
) u; |6 ^9 z6 r4 B0 E        ep0_state=0;
- L% u* V$ _1 i+ N" K        USB_PMA[4]=384; //ADDR1_TX
( d4 B6 G2 v  [5 a, @  y3 d        USB_PMA[5]=0;   //COUNT1_TX
& y1 W0 p3 d4 Q9 S0 L' @        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type1 D4 K, g. w7 _: ]* q
        ep1_wait=0;2 Q! c8 _3 }+ N9 g
        USB->DADDR = USB_DADDR_EF;      // enable function. {8 h7 D( `. [& O
        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear
* Q' g4 ]: J4 i$ D, a# n* h+ X    }
: s' z- O! y/ B8 t2 }    if(USB->ISTR & USB_ISTR_SOF), w8 Y, ^* v5 i5 h! r) q$ z$ O
    {
6 ^4 H8 x  U' m5 x" c4 }        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear
% |: d2 h) t& m* L6 a7 j% ]' l0 M4 [    }: D, t/ H- @# [# p% z& V1 q3 z! s
}
/ m. o- ~3 o/ N3 I, r3 y9 Q# W6 H  S2 |0 d) o8 a% \
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。
) o2 F$ k5 F5 H4 h" E) [8 n: x
* m  ^2 Q+ \* y4 ]主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。2 b: h) o0 g0 v
( O6 f  K( Q2 M# G0 D
    while(1)4 {+ @9 G( t. U$ m* A
    {+ t% s" T5 n/ w& F  h$ Z
        static char row=0;! c: y/ C/ h' o7 B4 s+ C$ n
        __WFI();
9 q$ ]8 K4 g/ q0 m2 O( x" ]: ^) i& G        if(ep0_state & 0x80)    // request data processing
! }8 y' i" t/ r# n        {7 P9 K0 Q8 f5 {
            if(ep0_state==0x80) // SETUP phase
. B* C' G* L+ ?- q! G; L9 R. g5 @& g            {
8 W& q: C8 w9 r; G7 e# ~% T                if(!setup_packet_service())
. z5 Q4 L3 T7 x* {                {
9 C  L% _/ ~! ^' k8 e                    ep0_state=0;/ c4 b# O/ t5 T) p8 v+ e
                    // not supported( X* E6 o+ g$ s8 {- j) m" B
                }
9 x- o3 k) T0 H                // ep0_state should be set to 1 or 2, if processed3 j- l  Z4 K. Z; }  q9 ]+ K
            }) |/ h* u) J7 S. q) c. D# p
            else    // OUT phase
9 E  L/ u% d0 h( a! w/ y  G            {+ i6 z# Y9 @- G4 _. B! h! E
                // process data
7 J4 [! ?$ F; s7 _$ L* u" }                show_LED(*(uint8_t *)(USB_PMA+128));' ^) y2 [& N2 u" }' A7 p& k
                ep0_state=4;' w0 o( s  w- o, ?6 V" h
                USB_PMA[1]=0;       // Zero length DATA0
, V0 j* `" l5 [. p" {: u' B; z% m; Q                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
$ \) |! T& N+ n% _3 K            }3 J2 J% Q+ t' N* _  f
        }- r( \/ {1 g1 m* n% n5 s
        else# c6 b. }# S; g5 U. o
        {
- S5 V% R. l7 Q+ M. k            if(usb_address && ep0_state==0)9 A5 C: C) o2 j
            {
+ a+ v1 J; R7 k5 r9 B                USB->DADDR = USB_DADDR_EF|usb_address;! t* T2 T" B; I/ Y! _
                usb_address=0;
) {' ]4 T  V2 g; m- U5 ~            }
0 ?3 R: Y' R1 x: Y3 A        }
$ c' G7 r# @) R% H" K8 V" o        if(row!=scan_row)   // new scan line5 ]5 d6 O! V5 e& h+ h; W
        {# r( i+ A' x4 I* _( K& x
            if(key_state[row]!=prev_key_state[row])- i6 d% \' M' w
            {  l" K2 I) A& s5 d  `# @* d  J
                uint8_t test=0x80;
/ ]8 h( E/ [5 ?; m* i                uint8_t diff=key_state[row]^prev_key_state[row];
; A8 N; \) h# W6 }: h                for(i=0;i<8;i++)& Z4 F/ Q- Q, P: D* D/ x7 D
                {
& i/ f, T8 w0 F& T! W8 p5 o- m                    if(diff & test)- ~* j) Z6 x; [0 m, j5 N' }
                        update_key_matrix(row,i,key_state[row]&test);
9 z: m$ E6 q  C1 S4 I, \                    test>>=1;
5 L! ~: N7 _2 V# |                }3 Q' {8 k( Z0 N$ t9 a3 t
            }
5 R" p  J( X$ c2 D8 B: ]            row=scan_row;
# J% ~8 G8 m" L# b        }/ Z) T8 G) \) L1 M( J  }" Q
    }
5 ~0 p+ F9 {9 J. w3 z0 a" ]
' I9 P; U; a: E2 o' a# C& a& V. z! T0 [* t1 s+ e5 y
EP0的控制传输,把用到的请求处理一下
; d, q- L4 p& n$ \" d( z! w/ U! Ychar setup_packet_service(void)
. }/ `: O+ i& p7 w{) \8 z8 l5 Q) L+ F* @8 N# X) k
    if(ep0_std_req->bmRequestType & 0x20)   // class-specific: o. {4 }' z8 O( t1 s; U8 ~4 t
    {* }  F1 X) z4 K8 C' N% X
        switch(ep0_std_req->bRequest)% u- c$ g& l! A1 Q* Y
        {
/ q8 Q# \+ U  Z" n4 o! K& {            case REQ_GET_REPORT: break;
: U1 O$ y! ]' V  T            case REQ_GET_IDLE:
7 Z* A: E* [, x                USB_PMA[64]=0xfa;   // return 1 byte& A# D. O' x+ q4 o2 f
                USB_PMA[1]=1;
; P4 X8 p( _* ?* g' P                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;& C( ]1 L8 a0 f& o: V; b; [
                ep0_state=1;& S' g; N! l- q6 Q& b
                return 1;) x. ], T# ]& g3 l0 W/ f
                break;
9 M$ I; C- T  B0 R9 C6 n  R            case REQ_SET_REPORT:, y) ]$ C! A3 i; z  H; f
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;& H6 ?. N) K/ ]: i5 ^
                ep0_state=2;
% X( |$ V+ x, C9 T                return 1;  O+ E' x. n9 [# V* p' U7 P% W
                break;
9 V( `, M1 O4 ^. k/ _2 |* b            case REQ_SET_IDLE:& I- m; T1 R) K. ?) o
                USB_PMA[1]=0;   // Zero DATA
3 N1 C! T" }3 k! R' l                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;" i* d: `- \: X0 t% A; r5 M. F5 h; u
                ep0_state=4;
6 a* f5 C$ O% Z                return 1;
. l0 U, l- X2 f5 x. I                break;9 l: x* U- H% b
        }% B# z5 ]; H3 ?8 b
        return 0;
$ r- X2 q) K2 U! S8 Q$ v    }
+ B! I# G# Z3 a: D    else    // standard& g* ]" t- o* b6 F! }0 j
    {* Y  S; t6 ], V( G
        switch(ep0_std_req->bRequest)
% f- {) k1 Y- P$ k        {+ P0 z& t8 s+ X
            case REQ_GET_DESCRIPTOR:
0 ^1 {$ E8 E& ^! J& a! R                return descriptor_service();
0 Y/ I$ y5 T4 U  n4 X0 S                break;0 Q3 n8 U! ^' W2 F
            case REQ_SET_ADDRESS:  G- _8 j# t0 k6 _! P
                if(ep0_std_req->bmRequestType!=0x00)
3 [' p5 }5 C5 _+ ]5 x' }7 J                    return 0;( Y. O& P( @/ x% l- N4 k$ ~; H
                usb_address=ep0_std_req->wValue;7 y: q& u5 C7 x2 U# p) Z: I: l
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;" f- B. F' y/ |* F
                USB_PMA[1]=0;       // Zero length DATA0
" J4 L9 U8 q% k# o7 e$ a                ep0_state=4;    // No Data phase
2 ?9 d) w# }7 K& q+ @                return 1;9 e( G' g# Q/ x8 k+ ?% i& l) q
            case REQ_SET_CONFIGURATION:
4 S1 J! i  }* g$ g( z# O5 q; V+ G2 s: |                if(ep0_std_req->bmRequestType!=0x00)
: x! O8 T. Q  ~4 f, U* e% |5 k- b                    return 0;
0 V! p! c+ K) d5 Z0 N! a; g7 [0 l                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
% U5 P9 c6 W2 {0 ~" D- t' Z                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
; G1 K# p3 A) x7 l2 L# l& ^                USB_PMA[1]=0;   // Zero DATA; F, i5 R. C: y, S
                ep0_state=4;    // No DATA phase* A6 j: N' y! X/ ~+ D, y
                return 1;5 K$ L# W3 x5 J- E: V8 R7 D$ g- Q
            default: return 0;* J( |7 R  m  Y
        }
1 ?+ x( j3 K. \: R    }
! P$ a" ^- H1 Z}
6 \/ ~4 N  L, E5 v$ m( }2 ?1 c9 _- Q* i6 x/ a
- d) C. p, h3 e6 ^  J# ]- V
: c+ P$ t2 y% s0 v8 F
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的$ {& ?& k& k1 @/ u" F1 n
char descriptor_service(void)! f- z( b/ N! g
{9 m9 [7 W+ Y1 f& x
    switch((ep0_std_req->wValue)>>8)
# G) T9 N5 N' Q: n$ |2 ?    {
7 w; n0 r8 w) F+ F6 V( V% {/ z% j        case DESC_TYPE_DEVICE:7 p0 P# p  g4 A  X8 N
            return ep0_preparedata(&DevDesc, sizeof(DevDesc));) ^$ l6 |. n6 @! \0 \
            break;4 ~8 t$ ?4 Z$ x; s
        case DESC_TYPE_CONFIG:
5 b3 W* w+ L5 O! V: C            if(sizeof(ConfigDescData)>ep0_std_req->wLength)6 G" v7 f( k, R/ ?9 W
                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);( a5 S: c# z* q1 B6 X* z
            else
, V) R+ c, j+ e* b" ?3 ]                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));
- V. {, ~& ^" v: t            break;
$ v# q5 `9 f, E$ N' K        case DESC_TYPE_STRING:- l) b2 L0 ^' K6 H/ S
            switch(ep0_std_req->wValue &0xff)" X( {1 d- H8 _
            {7 H/ y! D: U4 A) O5 f  Y/ ]* B
                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
. E; g- n; o1 X9 {                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));, A3 g$ e- w8 x3 Z
                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));# {" \" d& i! F4 N+ z+ w1 k
                default: return 0;* e" O. R$ {4 _  a/ h: P$ u
            }
7 g* u% y* _2 D/ A) F  \: a# a            break;% T2 }7 m# G- C( R* Y) Y6 p  P
        case REPORT_DESC_TYPE:9 e+ _; ^* T0 F. Q" D
            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));
, y8 i' v* u& z6 e6 E        default:# V$ o% `9 p' x% L7 ^- s
            return 0;6 w  G  e, {3 F* W9 v& O
    }
! b( M1 ]) R9 q}
9 O( C5 |; q5 U' T8 S下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.
! @( B% M: X7 M  @6 S. {" kvoid TIM6_DAC_IRQHandler(void)& l" L2 s4 E+ x9 v: p
{* s: G3 g, f5 H
    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);8 K  }5 o9 L, \' q9 a

  k, N/ E" A) y6 n% g" {

% P" P1 b8 m% l6 f- o6 W' K8 I9 d    TIM6->SR &= ~TIM_SR_UIF;
4 F4 l" ~3 i* ]9 y# p" a( M5 u! S    prev_key_state[scan_row]=key_state[scan_row];! }* x5 o7 F; C0 U+ D, H
    key_state[scan_row]= *PA_IDR;   // update key states" [2 x! X' L: y6 E: |
    switch(scan_row)
) N+ l& d; U/ m5 e1 V    {8 n; ~4 t1 H. R# u% L
        case 13: // next row PB140 s1 e' t- [1 t* E4 D- K9 `
                GPIOC->MODER = GPIOC_DEFAULT;! [7 v% y% D3 ]8 x
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;8 S; a! F8 ~7 S( o6 e& r
                break;
6 E& F& T7 c0 K0 O: H        case  0: // next row PB156 `8 Q  }* B  R
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
$ Z$ O; `7 @6 r                break;
3 B+ C# X7 \: i, P. o5 G& [1 u        case  1: // next row PB3
, b8 z" f" g3 h; h$ A! i/ w$ B# _                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;
6 B' d" v1 \" f  L. Y$ W                break;
5 q. W( P7 }# j; j3 S        case  2: // next row PB4& l  p# _; h, B! ^0 H
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
( w5 [8 e9 J1 P/ O9 c/ }8 H' V( ]  t5 P                break;+ D! E+ D3 U$ b4 ~) O
        case  3: // next row PB5
4 s4 ^- Y# _3 J, P2 M                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;
8 n0 ^8 O+ r) p2 I- s( E$ `                break;8 |( [3 b) X) m1 a0 H5 ]( J% m* N
        case  4: // next row PB60 q% X# o5 X# M
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;! \- L' U' z  t3 r2 _
                break;
& E+ P5 l! q* A        case  5: // next row PB7' o- d3 }8 z% I9 V5 ^3 u8 A
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;
9 E" h) M' [! S! ~" e2 S7 U" W                break;
: M- c" F* ]1 T( q* K        case  6: // next row PB8
& U- t5 x( l2 `0 @! b  U3 `3 f" m- z& d                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;
0 A$ [' K7 _9 y3 f9 n' }4 F" j$ l9 M1 [                break;; z3 p1 H' u8 _/ h% l
        case  7: // next row PB9- }8 T' k+ j2 U" M; Z
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;
: @0 T3 O0 x( z+ {' P8 P9 V( g                break;
7 d  h% w; |0 A4 R        case  8: // next row PA8. T8 U* n* C: M9 M
                GPIOB->MODER = GPIOB_DEFAULT;
. K" {0 ~- P1 N. F* {9 k4 Z  y: \                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;1 q+ o6 i! Y! A( `3 p
                break;
& g3 j' l' |" s( Z- a        case  9: // next row PA9
' s2 r" I; X( w& A# u; Q                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;! P% }+ d- J4 C  a3 \& Y5 ~
                break;
! s( T: v/ y* O9 H$ D( g        case 10: // next row PA107 e3 \( W7 O9 J+ e1 k6 N8 E! g% u
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;( F+ Q0 o+ O' _9 D$ c, B
                break;
. h9 T4 E$ w) T; Y# t3 Q        case 11: // next row PA15" o3 R9 t: M3 u& `3 s3 H
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;- H) P  a" i% p; V9 x5 h
                break;  T. s* l* Z8 Q8 x$ e
        case 12: // next row PC13
6 L" K2 Z( R; m5 X: [! ~" ~& z) ^                GPIOA->MODER = GPIOA_DEFAULT;
, [, Z6 }+ F. b  m- m                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
* i4 ?2 p+ F. h) E                break;
! D! G" M% E5 }& |- ^$ \! x    }
' I0 t2 {+ {9 F    if(scan_row<13)- U. v$ t: N) a; g: b
        scan_row++;& O( P8 D/ W5 z& L- N; {) C- K
    else
) w  ^4 e( [1 V        scan_row=0;/ t. r/ N; P* q
}) a3 e4 A- i5 F1 O4 V4 N
; c7 b5 B5 K  g; g1 y$ Q
# E0 l  i- X! _7 u$ w5 @1 H6 p
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑
0 P" [7 X/ v" ^0 x6 T
  {& {5 m/ p! X9 \& O扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。
0 m) T3 ~" Q- l$ H. V
8 b1 O; R8 V0 m5 A要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:

: [: z( ^' S3 g* Y
; V8 W* N; ]0 X; N1 K( L' N- \% E" O' h; T
const char hid_keymap_qwerty[14][8]={
( s& X+ Z4 u# F+ ?. ~9 ^: H    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},
2 y3 i( g1 k+ r) f- {4 [1 T( Z. z    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
6 c: G4 z$ o- ]; \( l! F! H    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},2 Y/ K. W1 [+ s0 o( J4 T
    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},. q: K* u, q1 B# b5 I' A
    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},
& Y4 Y2 N# _/ t" t% h5 f    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},/ S% v+ v- i% ?$ S
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
! V7 R0 q: P/ ]3 m7 g- l8 K' Y: U7 n    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},: ]) o3 m4 E) }# `6 I! ?9 h& N
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},, |/ M! z$ y$ }) z/ W9 K: k- A/ Q
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
( T0 Z" ]' l! S/ f% U' L    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
9 r) y# }- [: j* `2 y2 e& s    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},
; }+ _6 s3 o; V- k" _& n    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
! w& f5 J/ K- Q/ W8 X0 J    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}4 n: s6 a) @7 _" y
};
* m! W7 x# f$ E  F$ h' L; P/ w. s. k+ i" x; w0 {% O4 O6 H

0 _: x7 c4 v% L  B' W  {const char hid_keymap_dvorak[14][8]={
4 l$ x$ Z! ^0 \  P2 W0 ^& t$ K    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
5 e9 q1 l1 M3 L" i+ a' c    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
4 F# z4 j+ F$ c' \/ }    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},
9 K" P  p& {5 u( E    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},- c! s" Z0 g6 \6 u* |# e# R4 H2 \9 C
    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},
1 H# W4 w/ o1 X7 h" i( E; @    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},8 w# W6 t/ A4 y4 y# X
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},
" L0 n: K  t7 U  d5 z5 g" G" R    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},9 H( e5 Y9 P- o! L
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
# y2 ^$ h- T' e2 T0 O. V; L    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},7 T8 r: M( @* V0 e& U; c6 y$ I3 ]; r
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
1 h( N' a& [7 G% C7 ]0 G6 j- o    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},
5 \, s9 O' t- L9 @    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},
5 t( @: w! }! x5 X" q$ w/ i4 ~8 h+ h    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}, S: H4 U- t  e. e# l4 j
};' o! |0 o% ?/ d; X! Y, C  p
- {5 ~7 }7 _6 A$ j# J' g

9 b* Y/ ^* s' P8 m2 D. a& `上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。" X1 h* O) `$ K) |+ Z5 {' A

) A1 s$ l6 {/ ?' J1 _7 e) A7 [HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:

8 k; a- E( m2 h% ]: U. P9 k- O- F" D1 J  W/ ?" q/ g' p8 S/ Y
void update_key_matrix(char row, char col, char onoff)  p$ x; ^1 u0 |. F* K
{
3 m. N. w  w# [! X/ D    static uint16_t hid_report[4]={0,0,0,0};
3 s/ ]* S* h! M, _    static char (*hid_keymap)[8]=hid_keymap_dvorak;8 x' |' N$ f8 t/ }9 h/ }" w

$ }# D5 H) q" E! P3 O# |
" C" D* N( K- @- ^: N, Y0 d0 k
    unsigned char key=hid_keymap[row][col];
/ `8 v, `$ Z7 Q    unsigned char *report =(unsigned char *)hid_report;
* f* Z( `" g" h    char i;9 \  }  H2 D! G2 I+ o) e

9 l: B+ k  j; P6 ~
8 ^) T3 z" y- ]3 B5 G
    if(key==HK_MODE)
/ a# x& O& N) e/ z5 j9 e2 x7 _    {% I1 z! y7 N& n
        if(!onoff)
0 z, @  \0 {0 }+ ^( C% f        {
8 @. x: X, m$ ~3 |# u            if(hid_keymap==hid_keymap_dvorak)/ K) g, T( W! N. Y+ @$ e
            {0 M. N) F; Q- P$ J; m6 C1 \
                hid_keymap=hid_keymap_qwerty;
$ Z/ q8 L/ [- }4 i0 w# O                GPIOB->BSRR = (1<<2);
* N9 G' t! {9 Z) u! F2 j4 c* n            }2 a+ Z# d% s* D& n1 Z4 P
            else
. \) m% D, K4 F; ?6 m3 m+ ]. r            {
2 D! j0 S1 S  E( [4 V4 [% q                hid_keymap=hid_keymap_dvorak;4 v7 Q' O6 {) i6 A0 u
                GPIOB->BRR = (1<<2);8 {4 S/ v8 w! q
            }1 U* A8 P" m4 @8 F9 f2 C
        }2 M4 ^2 [4 M& T3 O
        return;
: L: l* @# [% w    }' Q: |- e2 W; _: }0 M/ _
% @2 `: C- O# j$ @

9 I0 X2 p2 h- a: R4 {3 `    if(key>=0x80)   // Alt, Ctrl, Shift
2 Q3 ~$ l, G5 K8 Y- {3 ^2 f- k% n    {: R' ?1 j3 F5 }, x& D! n0 N# d
        uint8_t bitset = 1<<(key&7);
( A9 b- e- h* ?5 j) @" s        if(onoff)   // non-zero is key up
8 o. a/ T/ p' }2 C9 R% R# d, {; o3 Q            report[0] &= (~bitset);
8 U+ q0 e0 M3 {- ]2 g        else* }  N, {# F9 s# o* `. K! Y
            report[0] |= bitset;
: A- W0 F9 H4 t. y1 U0 S    }
' ], b7 W# @' g    else; K/ v+ M. `- l; P+ b  \4 b# b7 B
    {+ B; j: c/ D$ A
        if(onoff)   // non-zero is key up9 W/ U* x7 j$ V, j) w+ P8 y0 T
        {( `1 N3 J# J" x' ]/ r
            for(i=2;i<8;i++)5 W! y1 v3 N% |7 V' k0 j) a
            {9 ^( K! j2 V0 N+ a
                if(report==key)
1 e: u) F1 L& ^8 H6 g0 e+ J                {
! |' q3 o. [( ?# v                    report=0;" W, p1 P$ x$ |! o2 E
                    break;
' Y5 n* G0 v# ^4 E3 ?! d$ J4 f) B                }" c; v% p6 O% ?; h5 _
            }( e" R1 M; w5 M6 W7 J
        }  ~( i) i8 a& S: Z' j7 f
        else
3 S: J9 G! ^$ Z        {- y% k: W+ x2 S' W+ m9 _0 A
            for(i=2;i<8;i++)2 ?+ P) m3 z2 y6 n8 W* D0 [
            {
) X* x; x/ e. }9 g                if(report==key)) @" Z8 A( d; ]
                    break;
/ i* P/ Z4 v  T& N                if(report==0)6 g' m* n3 z! [4 t+ G
                {, Z. ?$ _9 K  S
                    report=key;& E, ~4 a3 b5 E/ j/ f
                    break;
9 I& [8 w4 G# Z1 J, p6 G. B. P                }
3 u# X9 n# Q$ D; Y9 H- k2 e3 m# N            }% D0 G) K* i3 X' c
        }
2 D/ r  R( i) C5 X    }
; n) r/ W$ j9 `2 N0 u$ G    for(i=0;i<4;i++)
  n' U4 H( i/ Q0 {        USB_PMA[192+i]=hid_report;2 a7 r8 L0 m( {
    USB_PMA[5]=8;   //COUNT1_TX/ P4 R3 Y; Y9 Z; ~9 J: c1 T
    if(ep1_wait==0)
6 d" K+ I& S0 t    {" T; f" `' r+ U
        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;1 Y  V% U8 |; b
        ep1_wait=1;
3 Y0 A4 O1 `3 |: _    }; e) T, s3 f8 ^# N# H
}6 D) V( I6 Q7 D
" [- _* h, s. q4 [1 k- v( h

, D  S4 e/ q. k完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。
/ h* ]" u/ v0 B- @2 O keyboard.zip (8.7 KB, 下载次数: 6454) " R" I: E2 N; ^7 _
/ o0 L8 c% W: P" _. h
# G) `4 |* S; N  m
4 B0 ]! I1 Q6 _% r4 q
3 U# [. |, F) {. u
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘
$ Y" K* p) E. i1 w; P$ P. Z不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48& R; R# f, R) G3 P9 J
刚开始我以为要把打字机改造成电脑键盘
' s; c! z: P3 Q7 V, x: C不过楼主也很厉害!
# X5 z( [; v/ \* H8 o
哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
% o+ }7 U( }2 t9 u" y0 A6 }
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-4-9 07:31 , Processed in 3.846541 second(s), 27 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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