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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

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

5 U5 H& p$ k% S# n4 ~. L 235140i3a36qivqzuvmt5q.jpg.thumb.jpg
1 y# n1 e+ |8 P, V7 u. v
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。0 G; b- {% x6 ^$ V- O' {
001734klbyoluenuwz4h4b.png.thumb.jpg
- M/ _) A; C8 j! k! ^为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:
4 S5 n: ^1 j' L2 {6 Q' B 003625r2agx2f5v922cf2f.png.thumb.jpg / C3 ?; T3 \1 ?) }" ]1 L; `% |+ A
其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.
) p# @. f8 I/ X% a0 u$ c& m8 }6 ]Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:

( S: {; v' C6 p# I2 p 005836yvs0wvovwsssgd3o.png.thumb.jpg
: s$ B1 V  L3 s- ?9 w: n; g, zDvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。
$ S$ Q, }/ F# n6 ~& d
" ^5 k7 ~3 }& K0 O我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。1 L. T7 M1 v* x# m& O8 f4 F

5 Y- E" G1 h0 G0 `, e) l* B1 L到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。
9 X! f! C- G) _0 ]6 \& Z# I; B
机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:
6 {" ~0 F& w3 W
123206vx24rfxfp2dfrpxg.jpg.thumb.jpg
. w' g- D$ q/ c( c% |8 P3 s2 @----------------------------------------------------  分割线 ----------------------------------------------------------
% P4 i& q  N0 h* L! t3 M- k2 J& h
先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

; }8 D/ o: W) g- Z( @3 i 020011osionbunl4ui44vi.jpg.thumb.jpg
& Y; W# F( \" r; L2 l( o* b轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。
9 Z0 j# n9 C* ` 020017j8ycmnv7788bqv52.jpg.thumb.jpg
, p$ U) c3 b( T- Y- A: N0 M5 f特写,80C499 h6 g* o+ ~# r0 J4 F& P
021040oujzuvtut6iujtvz.jpg.thumb.jpg 3 U3 P9 [5 ~( D$ D* d' i$ A( Z  `) p
LED部分,使用了一片D触发器锁存指示灯状态.- E& {4 _" ^9 P% D& x3 x. p
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
- ~0 \* s6 j$ o3 Z% `! p6 f' _暴力破坏,将80C49拆掉+ s0 W+ s3 P3 l1 G$ C* ?4 p% P
021113e48qq98vyohvhzzh.jpg.thumb.jpg 6 B' w7 T5 E$ U. }" v+ T6 ^- D
拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。' ]# m- z' {, E* o) o; Y$ K/ [
021125nc9az6dj33rlds2r.jpg.thumb.jpg 6 m) |  {* M3 g+ a
焊好元件后的板子,准备替换80C49$ d4 y3 q- S/ d  ~0 M, E3 v
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg 3 }" p1 D% e1 y' l, p7 J* [' ^
用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。. e5 A, A/ Y1 C7 }
021104shifhnrqbr3o5nlo.jpg.thumb.jpg ) H& G$ l. f8 i7 m2 x$ \$ p
这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.) v0 b3 {( Q* w1 ]
022003ym1p9u4ug40280uu.jpg.thumb.jpg
5 _( d9 s" k% D7 D* f开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。
* ], t" X) c' ?, n2 S+ ^- R7 f 023313kt141q9qajtol7ma.jpg.thumb.jpg * [; P3 c' \& [7 l  p; `
我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。
" \" M) Z- ]' \$ S4 I2 K' [4 T; X 023322nt7l5xb3ltttkltt.jpg.thumb.jpg % a1 f$ G7 z: D( V2 i2 R* g, R
主键区键帽就位
" Q6 a4 b/ z; b2 `: ^. _ 023331hin88e8wkrwzwikx.jpg.thumb.jpg $ N/ v+ E& _* x) W0 U# {
编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。
1 x9 \  G2 `% W# M  U 023336wjzlgopugg1jyy79.jpg.thumb.jpg ; k0 `- ]! {1 H' s
最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
9 o+ W- B+ |9 L# u9 o3 t0 G* B 023341sffu4j3g2323h6fl.jpg.thumb.jpg 7 F! |) |% a( e1 ^. s' }1 x+ X( J
: _, z. W% \# Z
----------------------------------------------------- 分割线 --------------------------------------------------
% o  P, m0 H' ?) [
- V' I$ a' V6 b6 I) O$ t/ u
9 ]6 H4 p& z, ~" X1 Y. F! c3 u* c6 G3 ^
! V1 o  L- F8 V: |( F  `
& L4 u, r' C6 n1 |' ^$ \: w5 i

  `* b# U" p9 Y

: b" V6 `  x1 N; Q( X8 L  |8 S" F6 O5 G5 H/ Z1 h

, U6 i! H4 Z9 x/ D, o" l6 t
, n# y" Z5 P3 G' V( Q
" _( W; M* B# M3 {( t, e! j) Y3 E% w
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。3 P! ?( J1 }' ]* ^0 v  d+ X
80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:
0 K, h+ u' e7 p& l5 V/ U 104025nzibm2rmiomhyirm.png.thumb.jpg " [+ {. \) C/ i3 M+ U; ^6 H7 h
- T$ q8 Q5 [: h' Y9 F% K& e0 Z
其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)
7 J* u5 D7 F, S/ m! X/ A 105004zkrez5houvkkznko.jpg.thumb.jpg
1 s* h/ a) m. E2 p
8 t. e7 A. }6 ?) ^& {4 T, \
扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。3 T* @! Q& S) B4 a

2 z% c* ?. t: y0 Q3 }* r这是我设计的电路图:
' o: }3 ?, D! g9 R6 {( D 110344ej2z2oo2rflo7oe7.png.thumb.jpg 7 ?4 c& ]; Z" q4 G

7 U0 e, X4 L; h, S3 VPCB Layout:+ C7 Y- \& t2 k0 n7 ^- Q
110847jjbjvt34vwt3v5bb.png.thumb.jpg
1 K9 ~6 O7 ^# y! C: K

# P0 g" n- R8 j" h# w, v3 g不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O. / B# J% T) M) w8 a+ j) ]2 }

* \* _/ N3 Y5 a$ X# U& L# b8 d
6 n" V2 W/ q# l5 ?+ k4 @7 [
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑 3 w% S, ?9 _$ Y% A2 O+ r

4 {/ b3 P* t  b9 g' f$ f* b; @; |软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。
; t. O& T1 n& Z; ~: Z& O' s( t) z& j, Q( b5 B, i
总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:
7 G! M5 x; K" x, e- n) d 113818pmrfsb6z0byt6t06.png.thumb.jpg & g& Y8 m; z6 w$ ~% p

& L! V0 ~9 B* t! }' J其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)8 ?7 e" ?6 }4 x9 ]% V. @  q* m' F
; f) ]& A6 n7 n9 E! d0 j+ e; v5 M
USB的中断ISR,bare metal哦
2 a! B4 A7 u* D; _5 _8 b

, d# i# b9 r: K8 Gvoid USB_IRQHandler(void)
0 a/ I' J: z2 C; X{) {: V' z* `( g/ T" A1 u% G# x/ I
    if(USB->ISTR & USB_ISTR_CTR)
, [( b4 h' r9 j; D0 \3 S    {
4 r4 b, j6 R6 N        if((USB->ISTR & 0x0f)==0)   // EP_ID==0
$ N7 [8 _- T9 |1 F" C/ j# p) j        {0 m9 }4 f) s( ?* H. N
            switch(ep0_state)
1 z7 t' ~0 g8 P            {
5 U, P2 H# d5 l% G) k5 q                case 0: ep0_state |= 0x80;* S: d" S% f) `* S; G% P
                        break;
$ h; H5 F- W1 h                case 1: if(USB->EP0R & USB_EP_CTR_TX)4 d4 c( `* Z: Q
                        {
5 M" V7 _/ ^9 [7 `) R3 r/ Y                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
; f' T  n& A$ z6 b                            ep0_state=3;, h) X, K0 h/ K) @! O' }( D1 q: y  o
                            return;1 l0 n7 G0 U# p$ n6 V: b
                        }- y2 w. [5 h* K* r" x: I
                        else
% f* [4 r6 C8 V" p8 Z$ |' G# k& H                            ep0_state=0;
1 Q) |2 w, h9 P+ G- C0 ?1 c                        break;
" v% j: P; s) v% j0 Q1 N" [                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet$ y0 ?! w: r6 m. D8 b
                            ep0_state |= 0x80;3 Y! `+ v7 u* A1 y4 D
                        else
. x: M% }" t/ G2 p5 u                            ep0_state=0;
2 i8 B& K; U3 l% M  Z                        break;' I+ p! U* U' A8 R/ [" E) O
                case 3: ep0_state=0;
) P$ z, {& u5 d                        break;( `# g/ J" i: e% V' M/ b
                case 4: ep0_state=0;
$ A& K- s; t# {                        break;
1 a9 {, H; ]+ U4 Q( K& y& K' {+ D3 \                default:ep0_state=0;
/ T1 L5 z  j1 _8 E# \6 W' Z                        break;' t- p/ d! {5 A% n5 M3 y
            }$ S3 N) |' G: v. Q( u
            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
' I8 I7 w( p' e$ S, ?            return;
9 V$ C& W, h# L' d        }
7 m' A2 |$ T& m! e        else    // EP_ID can be 1. n( ~% Z5 |- j$ e" a
        {
6 a9 F- d9 |3 l/ @2 S; _            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag
3 N4 ^0 O( a/ \# |            ep1_wait=0;; }1 s2 O' V% q( ~  r& R: L
            return;" U: B* X5 j; D9 [! x+ {  L
        }
* I$ S( i$ i: d* `0 h  `3 g    }! j0 B6 d) X& m+ L4 `
    if(USB->ISTR & USB_ISTR_PMAOVR)7 Y& K2 A6 D" l
    {
* c6 c9 e1 E6 G: W: g* N6 i        USB->ISTR = ~USB_ISTR_PMAOVR;
8 S6 H& ?. `  k' @! n7 g    }4 ~  [1 m' K) A' z/ A& X
    if(USB->ISTR & USB_ISTR_ERR)0 M  o* [; H5 {+ a$ H/ s
    {9 q9 f* E) D7 I. d& i, d- _4 s
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear
0 N' d: S+ O2 \$ u    }3 `  B. p# Z# ?
    if(USB->ISTR & USB_ISTR_WKUP)0 O) ~% z! Z; n
    {
2 g5 ^+ |/ x8 L: a. ]9 R6 l        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
! L* I! _2 s3 N( B, X. H        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear
, S& y% W: D1 j  {& \3 F    }
! s: N3 Q* j# [: I# F/ ~    if(USB->ISTR & USB_ISTR_SUSP)* {) d+ ^. ?! S1 b% l. e1 I. R4 O
    {
# @8 k' ^8 ]7 W& P( }, \# Z1 o; r# O        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend- ~7 c0 _+ s' B# x# ~
        USB->CNTR |= USB_CNTR_LPMODE;   // low power
. h3 r' ~' X! Z( H: y5 C! ~1 E        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear
& a6 U5 w! n& I, N" i0 V, z    }- B6 _$ S! F/ ^! o2 Z; P* ?( B
    if(USB->ISTR & USB_ISTR_RESET)& [& Z" }) Y6 F6 H
    {
  S- V! e, C- X* Z- E) H5 ?        USB->BTABLE = 0;    // buffer table at bottom of PMA
9 d" ~2 o! B1 S% n        USB_PMA[0]=128; //ADDR0_TX
: `8 Z1 R5 K1 ]! I/ {9 D/ n        USB_PMA[1]=0;   //COUNT0_TX9 z  I2 u& ]" M
        USB_PMA[2]=256; //ADDR0_RX
+ z/ n6 m7 u. C* p. O        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes
6 T  Q! r$ h. {. \7 ^" I+ a( `3 L        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;
' c" O# G! u& f, F) F0 P        ep0_state=0;
: \- U2 _# y  f% x        USB_PMA[4]=384; //ADDR1_TX
2 e0 t) \! q% Y$ y( o* u5 G        USB_PMA[5]=0;   //COUNT1_TX* t/ }/ e8 j; Q# ]8 @8 R
        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type" B% N5 y/ l" n& \! |
        ep1_wait=0;& s% Y% F1 f: m. d* j$ z
        USB->DADDR = USB_DADDR_EF;      // enable function) k5 \; j% i) B9 B
        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear" r3 V/ b& o' t  E  F
    }
7 g& X3 y$ _0 }    if(USB->ISTR & USB_ISTR_SOF)6 V- |2 |' q, F2 z( ]) w- U
    {) H: s) d0 v5 G* m/ y
        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear
+ F6 ~6 T9 g6 F, q8 o5 x    }5 ^. l  E1 |. }  C/ q
}
' `+ e2 Z* @8 Q" M' r% S; Y' ^2 p+ u
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。1 E1 g8 o9 f/ ^
  w+ B  @; `7 O3 Q
主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。7 r) |# N; P, [8 `

( k' Y1 y0 P- ]4 b5 M
    while(1)
9 B# A# J' {; w6 C' f    {
' M  H7 m1 P% P! f& \9 q        static char row=0;
$ l! P# L. ]0 N# \. u% Y        __WFI();
7 ?/ x+ H! L0 g4 n6 x' N        if(ep0_state & 0x80)    // request data processing/ E% T* X$ e! A" H2 A) }
        {0 l2 {5 p+ d# d& S+ r2 u9 Z( I: {+ u
            if(ep0_state==0x80) // SETUP phase
' U8 O# Y* T% ~; K( D            {
! ^8 j' X8 [; q8 o4 b; z: ^                if(!setup_packet_service())3 S) @3 [; \# b, e
                {
, J4 z+ N! E% O. F                    ep0_state=0;) j( U5 k4 e8 D# [
                    // not supported
* U3 a1 s7 m! `: u+ |, P: q- @                }
4 D* @1 R% j8 Q# o: Q& E                // ep0_state should be set to 1 or 2, if processed2 ?9 d! x2 x4 w! b8 @0 f# \2 O
            }3 f; b, t; U+ o8 `# r. [
            else    // OUT phase
4 ^. u- c4 q$ S: g2 \            {
/ X" O4 ^4 ^0 F                // process data/ I0 T; r1 _1 e& Y: F; C  w
                show_LED(*(uint8_t *)(USB_PMA+128));  O7 V' _% X# n
                ep0_state=4;
4 S1 J7 Y- G4 _+ B0 Y* v, l. W# ?                USB_PMA[1]=0;       // Zero length DATA0
$ ?& z+ V# ?$ ?* a6 O                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
# X3 P) v( C9 E% ^# }            }4 x1 b8 l6 H" s( J6 B
        }/ T$ R, {+ M+ n, d& z
        else# d" V* F* j3 N6 w9 X+ }
        {+ C2 O) j! U: t9 b$ T) P
            if(usb_address && ep0_state==0)7 ?* `$ P% e: N' x
            {
( u, }: l' i; z9 V* x' j5 j                USB->DADDR = USB_DADDR_EF|usb_address;
' }# c. y9 o  [, ]                usb_address=0;
+ U( e& G# i* ~9 q            }1 e9 T! R# F  [9 M
        }9 M5 t$ \9 R, V/ ^2 @" A
        if(row!=scan_row)   // new scan line
9 t" t3 w# K8 I" y/ g- F" ^        {& F8 ^, u* [; W0 x) g
            if(key_state[row]!=prev_key_state[row]); `' E) @- q. b1 a; y" V
            {+ ^, P3 b1 u0 p9 L/ ^% W7 {
                uint8_t test=0x80;4 }$ {* j+ e- }& V% M* X
                uint8_t diff=key_state[row]^prev_key_state[row];
* |9 n& ^- U% T% |5 W0 W& b                for(i=0;i<8;i++)
% R) T6 N- n# u# s% Q                {2 m+ X9 P) j  g! x
                    if(diff & test)$ h+ y+ B! M! s+ Z9 M$ n
                        update_key_matrix(row,i,key_state[row]&test);- p+ U( P; a' J6 s* P
                    test>>=1;
, S+ I. D: u, F                }
4 g2 X4 L# O, a            }
7 x# L, k1 n( H$ ^- I            row=scan_row;
; p* t9 A9 ~9 l. G4 Q& U( x7 |        }7 r) V: j( Z) D- O5 }3 Q& G0 y
    }2 d6 P4 P! A2 w' L% l

6 U8 J) Z: Z3 x& Z/ m( b7 o2 H% l3 g: ]  [
EP0的控制传输,把用到的请求处理一下7 F! I+ x( m0 y+ d# k, |
char setup_packet_service(void)2 [3 l) g4 a" o4 K
{
! J$ i! [" @+ D& d2 I0 r; e    if(ep0_std_req->bmRequestType & 0x20)   // class-specific
1 G) H" K& G6 N: \' K4 n    {0 n! [+ R( y+ @7 \9 D5 {
        switch(ep0_std_req->bRequest)! p* [* L2 ?$ `) G+ T& P* r3 N3 `
        {
7 s9 n" t2 l8 G7 g            case REQ_GET_REPORT: break;
# g9 r8 ^: k8 U' k( I            case REQ_GET_IDLE:
5 j' K" x" C' U' X* D7 k; v0 ]                USB_PMA[64]=0xfa;   // return 1 byte9 ~0 T9 s% e/ ], t$ A; C
                USB_PMA[1]=1;4 }- n( A4 K+ A. h* M0 d
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;# E0 z3 y7 _) {  n
                ep0_state=1;
( R4 F8 y& T) o6 [1 Q$ k& |                return 1;
7 K* j# Q0 P  [6 L. C. a" G                break;# y) ]4 Z9 l0 y
            case REQ_SET_REPORT:5 {3 {/ o) w3 t& p5 C+ J
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
1 O; U; y' \# ^- ?* ?" L                ep0_state=2;
7 v2 S6 q9 ?9 ]& ^& j* E0 V& D                return 1;# Z8 e6 O" x3 A7 y! @1 P
                break;$ D8 g) i% Y* B" c7 w- L
            case REQ_SET_IDLE:: A' L1 a( E0 C. H+ f# _' t" c# j2 b
                USB_PMA[1]=0;   // Zero DATA
( ]0 n  {( T" c                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;( X; o0 W$ c5 R7 n1 k! q
                ep0_state=4;
+ ?# Q$ ]  Z# o& Y5 q. A                return 1;! X6 y3 h* B; k3 M1 ?3 }+ e
                break;2 c: F; ?, p1 B  r6 L+ ~+ A
        }6 |; I% ^7 q  A$ m: F
        return 0;
( u( C* O9 e# G# X    }, O+ Z, }& i" @6 }3 z" N8 o4 j- O4 f
    else    // standard/ L9 ]. D$ s. J( y) Y
    {
( Y. h, v2 x7 j! q* \5 v1 D' D        switch(ep0_std_req->bRequest)) j$ d4 |" G4 f) J
        {
9 O6 F: ?2 p$ v) e            case REQ_GET_DESCRIPTOR:
& ^& Z3 N$ J' }7 Q+ a                return descriptor_service();& C+ \! X8 E9 G3 B: w) @( f
                break;9 N% O/ {* m, j8 Y% I) c
            case REQ_SET_ADDRESS:
0 ?* A+ e5 {& T                if(ep0_std_req->bmRequestType!=0x00)0 ~% A# q+ F8 v# _
                    return 0;8 |2 i- M3 t( w, B7 l( H' M/ Y3 ]
                usb_address=ep0_std_req->wValue;
- F3 W# a8 j* j% l! v                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
0 k8 D# e0 T) i' `7 @+ m, Q                USB_PMA[1]=0;       // Zero length DATA0- a$ f" C# U" x7 E4 [: x, `7 O
                ep0_state=4;    // No Data phase
+ ~6 R9 X( c$ p7 l7 V1 K; |8 N                return 1;1 |6 w  _- t, i) C9 Y
            case REQ_SET_CONFIGURATION:; L- h! P( `0 ~: @. i2 q3 a( o
                if(ep0_std_req->bmRequestType!=0x00)
% `& K/ c  m  U1 k: \) j* Y                    return 0;1 j5 D( e' p" z8 |/ N
                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
" o& B- d7 V( c% b1 @) {4 }                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
. O) J4 J6 b' R4 E/ u* }                USB_PMA[1]=0;   // Zero DATA: M' o% l  p4 n% _! Q$ A2 S
                ep0_state=4;    // No DATA phase9 b8 c! @  r9 W! e
                return 1;
2 C) E$ E9 ^% j( Z' }# X& n" M( W0 |            default: return 0;6 U  Z' p; U1 N  F. \
        }5 f; w7 f& N2 Y9 B8 @3 j
    }, l* S! @2 N4 R7 R& @1 T6 Y
}/ A4 V7 A  c$ ]1 Q/ b$ _7 p: _4 b
) c1 H5 J; V4 o2 z3 K

* [' ~, x2 y5 h2 t( Q3 |
# f+ |3 ?2 L# a  T# Q! E
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的1 b6 U; ^0 D& \8 R
char descriptor_service(void)5 c/ o& y0 x7 R
{
: C7 _- K5 l/ y) L    switch((ep0_std_req->wValue)>>8)
" x  t0 @9 W& K2 a    {; J6 H  v- T! {* v( i( y) E
        case DESC_TYPE_DEVICE:, y2 S/ o4 w6 F. }8 J7 G3 \
            return ep0_preparedata(&DevDesc, sizeof(DevDesc));- G; O0 S" P4 u  h
            break;3 q8 ~% U' \' D3 v$ a; x1 Y' s
        case DESC_TYPE_CONFIG:: x5 r. b- N$ ?9 i' R7 w
            if(sizeof(ConfigDescData)>ep0_std_req->wLength)
! E$ q7 V* v$ \" ^' u1 w2 b& G                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);
+ W5 K- D4 X& b2 g) i# j  u9 J! U! r9 e            else
' ^2 [+ C$ L$ G% ?                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));
( {& o: j' b! G            break;
5 j+ H1 V% F" x9 Z) m% D        case DESC_TYPE_STRING:' D' U! u6 `4 B9 P9 R' @
            switch(ep0_std_req->wValue &0xff)& x& {) H  p9 \
            {4 @1 V% {% L8 g; J2 o
                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
" L6 Y2 A3 t/ J5 |& ]4 M9 T% A" U                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));/ b7 p( s- E3 {% D! w
                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));  O5 ?; B. F2 \( d5 u7 ~; r
                default: return 0;
6 {& U# ~, i6 P2 a) T            }
/ W/ b- ]) w5 ]( C            break;6 L* s3 k& v8 l7 ^7 n3 J  F7 C
        case REPORT_DESC_TYPE:3 p/ T* X: h+ @  L4 R
            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));; M' _& Q6 Y0 m. S
        default:- m/ s9 y1 o, h7 k
            return 0;
3 F+ C# i0 T3 s5 R8 @    }! d5 I  l  b# u' V) t/ k- I' `9 _
}
% c9 D: C3 C9 c' P9 F% j4 q下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.
/ Q" }5 W5 \0 H9 evoid TIM6_DAC_IRQHandler(void)
/ ]. O! ?( |$ O) S{
6 k8 C- ]; w& {( d. l/ l    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);9 _, z; J; M2 E9 Z4 y
. N5 b, V/ }' C6 Q

2 J4 u6 d5 {! U% J    TIM6->SR &= ~TIM_SR_UIF;. M" Q8 l# \! J, B8 O* C
    prev_key_state[scan_row]=key_state[scan_row];- m3 m; A+ T5 C. l: X1 n0 M1 u
    key_state[scan_row]= *PA_IDR;   // update key states) `) f* e4 ]* q0 i4 G0 I
    switch(scan_row)
* _% j- L- k# Z2 i% ~/ ?    {
/ w2 g: M% p4 {6 J/ l: j        case 13: // next row PB14
; B  @, M' F+ ^9 U) M, h$ Q6 g                GPIOC->MODER = GPIOC_DEFAULT;0 E. k- b' W- C5 F
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;
9 S# B1 Q- \9 K! I                break;1 l7 n, d# \5 s4 p3 N; f
        case  0: // next row PB15$ h( L- Z( Y; H. d7 t
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
% k$ ]' @$ b% ?, ^                break;
/ p& B# S! P% z8 I; j        case  1: // next row PB3; ~; `. p/ h4 j* Q4 {7 ^/ m- T% c
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;- ^6 P# T9 J7 q0 i2 _
                break;: l- E! u) p3 J. Q$ H) F. j- V5 X
        case  2: // next row PB4
& E4 e1 w9 h; f: B                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
7 Q4 c; q% r# p% \9 S                break;& B: d/ ]2 p: `8 x& f/ w. o
        case  3: // next row PB5
) H  ?3 v4 C" `- Q5 }3 C                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;! f. p* D- n$ }4 I) L7 F
                break;# K% J" X8 ]: ]& O: n- c
        case  4: // next row PB6
3 Z  V1 ^. n0 d' q) R0 i                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;( z3 v) J! j1 p( ^
                break;
. Y* W/ H! ^$ t) W        case  5: // next row PB7
7 P9 u1 D8 c+ U- @                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;# i/ ?5 i2 l5 u( p: |  _3 i* \
                break;
! g9 o; W5 n! G+ m        case  6: // next row PB8* `1 Q7 d* L% d6 a% ]! [
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;4 o4 z# E3 k0 v% e! ?2 c
                break;
0 v1 z  x: v- u6 {. S& i        case  7: // next row PB9( H6 ]2 ^( P# m. m
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;
5 g  P( X! f0 i5 Z% G                break;
" R; e  ]1 u6 P; w- |$ ^        case  8: // next row PA8
  G/ J- M% f4 N$ e- s+ F                GPIOB->MODER = GPIOB_DEFAULT;
. B2 [% k+ A# h% m/ ]9 B/ I  l. l                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;& u' S) U2 r: [! q% ]
                break;
( s1 S: j8 k( p$ R        case  9: // next row PA9
) s8 ?% D  g8 U0 G                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;) w. i9 |7 L% L- t
                break;& F$ r0 |6 Y! I7 @0 X
        case 10: // next row PA10
0 x5 N! p, _" M' L; I, t: K                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
0 k& V7 T/ X$ c5 e3 L4 T                break;
" U4 \% {$ f! ~+ T7 R        case 11: // next row PA15
: w, |, z8 F9 s, [) b, O                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;
, W) w! H0 y2 Q+ D9 n  ?; R) S                break;' G) c& U, U) E; h0 Z/ ~
        case 12: // next row PC13
# L5 G3 X$ B/ T  I                GPIOA->MODER = GPIOA_DEFAULT;! ?' t6 q# T8 A: c, T
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;0 o/ p' p5 Z/ f/ ]) [6 r
                break;
- ?) c* a) m8 ~3 N. a  Y    }* N5 e- T; V7 j: e: A+ a
    if(scan_row<13)
& A8 G/ `% W* c5 M        scan_row++;
! f' _2 L7 i% g2 V    else- i$ q3 W; f3 |/ T3 v+ l$ ^
        scan_row=0;
, E% Y1 w" f' M/ X. {& V- |}6 F1 Z: k# f( W5 m. }

% V2 ^6 Y3 t* |' X, B- }# @  P. o) J
8 Q2 b/ M* R5 V0 a% s3 N" z, r& C9 m8 t) g
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑
3 Q/ R/ @! J! h, r* E+ H0 r4 p! j5 W
扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。5 H, _% {- @- T0 ^

& h9 ~  a5 M' ^7 m4 W8 o- R要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:

* |' q5 r5 _/ W! H% N$ X, P9 Z8 T  }
' D  p# Y6 s) `; b8 `$ W# u0 m
const char hid_keymap_qwerty[14][8]={
$ m; S" V7 R, n. V, H5 g. v/ b    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},
- S" P) v8 t, H    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},1 I' |4 _8 g( C. l9 k
    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},
5 F& ^, E! b* u9 k! t; T    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},7 T8 M, ^7 `* h+ E& w
    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},
9 t$ J* V4 U, M* |' X    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},7 s! p; t6 l& W) m
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},0 l* }3 i' u! C& c
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},
* E0 |( c9 t  n    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
, S8 ~/ X+ o& ^9 _- v! m4 V    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE}," \, E6 X% n( U/ O
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
/ r4 `5 r8 N/ Q    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},
7 v) a2 b" Y0 C* |" V    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
: @+ j7 N; |, L$ z* B1 @    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}5 Q& [% ^+ E, Z
};' l0 Y) c1 J& _0 H7 p

& i+ B. _. \3 Q2 t' q! m& ^7 z
5 O+ p- \. M# q5 O- Q6 k
const char hid_keymap_dvorak[14][8]={8 z* v  n( a% d: q9 M8 e- y* l
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
" x9 T- V5 g4 ~, K& T/ c4 B    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
5 H% t! U9 y! N/ W/ g2 l    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},' n0 j: W8 x9 ^  e+ }. q
    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
! E! \1 S4 w$ M, K    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},6 N5 ?5 c) A# P
    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
: }" K2 v" F- e( E5 I" h    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},
, a1 n& q% X$ |2 M5 D    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},, _" j+ U9 Y+ ~' t% q, J; D! ~/ H: }
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},% ?: i; j* V; m2 D: o% n
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
5 E& P( z8 a+ h8 ]2 u7 a. b. o8 x$ I    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
) H* K# n3 A3 i* D2 l5 R    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},5 b& y+ G5 `; F& h3 S
    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},
/ m' M. N$ \* F' }0 ]% r; H    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
# j; l' _0 y5 C( [, V};$ Y' o4 i+ u' `+ @+ {9 V5 r. W
9 c7 H7 m- K* i- A
2 j. K8 D5 X+ M: h+ P( a
上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。7 T$ }# Q2 T: N( l

" y1 C$ P% u; _5 YHID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:
3 a- D, r0 l# Y+ z
! ]5 v. ?) K. x, `+ X
void update_key_matrix(char row, char col, char onoff)
' }# _6 ]: V( x: \! C# ]/ r" |, `{
3 S! Z- C. S9 R  r& e" q    static uint16_t hid_report[4]={0,0,0,0};, b+ U! k4 ^! ^
    static char (*hid_keymap)[8]=hid_keymap_dvorak;
3 h3 e# E8 w& y: L9 b
  D7 D) H0 S, P5 ^& J& Q4 u- t
& ~* [/ d: i! [# W
    unsigned char key=hid_keymap[row][col];
" ^) h2 A& Z! n( H6 f1 t4 s3 @! N$ d* p    unsigned char *report =(unsigned char *)hid_report;
' @4 {7 ?. w  o& |( J7 |    char i;
3 ^" S9 ?( p9 l2 g& h
$ p( }- m" Z0 y& j. A
) B) z% o; {$ Z! e6 T  m
    if(key==HK_MODE)
: S# w$ `: m5 P3 I$ t    {
0 K- `5 i0 V4 x) Q8 w        if(!onoff)) k; H* ^! t7 D5 L
        {
7 x2 e* o" v( P5 g' w            if(hid_keymap==hid_keymap_dvorak)
; |% K( B" W# y4 p            {
- w1 Q4 t5 M' I( H. e1 O5 i, \8 d: l                hid_keymap=hid_keymap_qwerty;* M5 {/ O/ t0 z6 ^
                GPIOB->BSRR = (1<<2);/ q2 c! T4 E$ ^
            }
- ?7 [" {2 {1 p' {. P. q( I            else
; K( g4 @9 G2 E$ T+ K% e            {* J) {2 e3 i$ |7 @9 J. I
                hid_keymap=hid_keymap_dvorak;% Q  l( u. ^" ^9 M$ s- E+ `
                GPIOB->BRR = (1<<2);
! y- e7 j9 Q) Y            }# g+ x  S  C; A3 k" w1 L
        }4 o# D2 \; J  V5 {# U( |
        return;
" S* w0 Q& ^8 f$ @2 u    }4 M, n, l& ^8 D$ Q+ e# w
9 |( X  t$ Q: x

- Z: v5 _1 y& G+ @3 h    if(key>=0x80)   // Alt, Ctrl, Shift" m1 M$ l7 U  A8 O' O) @$ m5 @& @
    {
, u+ c( L  ]+ y, |  L        uint8_t bitset = 1<<(key&7);
2 I- F+ R# l! K4 a" g        if(onoff)   // non-zero is key up( w2 E- t( P5 k' j% o2 M
            report[0] &= (~bitset);
) U2 O6 Y5 J' O# G        else
/ \: x2 r7 Z5 D0 L6 [$ M7 p            report[0] |= bitset;
& P  Z! P' b9 [, I    }
7 q' I; x7 T7 U8 Q  t2 b    else- K. k" ~- ?3 }- U
    {5 n; t+ @8 C1 ^7 L# s" z- b
        if(onoff)   // non-zero is key up( h7 e+ T1 w- O7 O( g1 b
        {
- f/ Q8 E; d) _% |0 T. N            for(i=2;i<8;i++)9 V0 P% P- S1 M0 U6 @0 T+ t- F) N
            {0 F5 o" q- O8 D$ h" E: |$ U' s
                if(report==key)
( f" w% i& w/ N7 j# k; s. a7 q% n                {4 l  Q- T( ]/ |* @- p+ h
                    report=0;
6 ]7 K* O/ j* S- I2 Z  \                    break;- D7 k' [9 ?( Q; m' V7 {; p4 L
                }
3 ]3 M: w& i5 t& k: |) Z1 h( f; v            }; _; u* x6 g9 `0 a  c
        }8 M& E' v8 R9 C2 u
        else+ X$ J; w  P: N- t6 w
        {
. h  w* K' [( b; R9 C* F            for(i=2;i<8;i++)) h2 X7 l) k1 K5 K
            {
$ S# m6 P- z7 l" G                if(report==key)
8 g$ L) ]) [/ e/ o' A                    break;* w& i& {& n# O: A# m
                if(report==0)
- {8 H2 t) w9 S" ~                {
* W( t8 {$ K- ~7 {                    report=key;1 T! H! O8 z3 Z6 Z: A9 C( c) h( a
                    break;
) K# o& Z- R; a5 q$ c& }                }" T% J2 {( F  H* y; h2 h5 c
            }8 S/ j/ f0 w5 r, r1 d9 J7 P, \
        }3 g0 O1 |7 J9 E* W7 {8 ?
    }
4 i: w# [1 J- R  T4 T# m    for(i=0;i<4;i++)2 m6 s( Y" X9 U) L- n
        USB_PMA[192+i]=hid_report;
# i9 S" a2 B6 S" t    USB_PMA[5]=8;   //COUNT1_TX
' T+ J8 s0 o8 E( r    if(ep1_wait==0)" c. o7 n  p+ O" g- k6 i5 [
    {
' _, i' _7 h( A; t* G! M' x/ [        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;. K/ ]. E- S  F' c) `- O0 M
        ep1_wait=1;* L+ l3 z( k. e+ o" h( M5 O
    }
7 Y/ [9 D! A/ \$ J}
1 Z, o& e4 ~" |+ T0 S" ~
1 F( U/ H2 a: {7 A6 y* O4 ^
0 Q$ Z7 F2 |& [0 x完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。
( W1 p7 P/ K( P. b  U1 H keyboard.zip (8.7 KB, 下载次数: 6188) , b' ?  G- W7 T1 j

" H  {1 M6 e4 x. p9 `2 C: P7 ^# Y9 Z9 D# W$ R' |0 q
4 ]9 U" B0 a' R5 T; b

6 `. t& X" J+ A8 v$ U
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘
( C, q8 o  P) L+ f6 G. D不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48* q9 P( f* r/ P! P
刚开始我以为要把打字机改造成电脑键盘
! @0 l, Z' S# g7 p$ s0 s- S不过楼主也很厉害!
' u& K8 j' J6 i9 V2 O+ }- g
哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
* J2 _2 L" N( t2 ^
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-7-1 14:02 , Processed in 0.169065 second(s), 27 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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