找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
楼主: miaozhuang

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

[复制链接]

24

主题

39

回帖

288

积分

vip

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

  j# _3 P* }1 I. ]/ T

+ ?1 Q+ w( {5 E) q8 n6 z3 |  J; i 235140i3a36qivqzuvmt5q.jpg.thumb.jpg 3 s! w1 [7 }+ H* \; `: n
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。
  f& K- {% ]" n7 |& O# d3 H/ S 001734klbyoluenuwz4h4b.png.thumb.jpg   ~* |. d$ o* i9 }2 @( K
为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:6 y5 y; C; z) s; p' n' {
003625r2agx2f5v922cf2f.png.thumb.jpg
; x( w# b  Q7 }( ~! X' ^1 w. R, R其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.; S0 E7 e9 A# y9 f* K+ v
Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:
% \1 y  e; z% _1 ~4 w
005836yvs0wvovwsssgd3o.png.thumb.jpg
1 {+ W" y% Z$ R7 fDvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。8 t. t2 u% z# A

, m9 H( v) A2 W$ J5 O; `) T. Z我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。2 D( {% u2 I7 d' ~! M9 B# b

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

& _  m* e2 |# w' y, s: U 123206vx24rfxfp2dfrpxg.jpg.thumb.jpg
( m7 @3 n2 g+ o5 x1 _( O; s. Z----------------------------------------------------  分割线 ----------------------------------------------------------
: T  X2 J( Y+ Q( m+ P9 Y$ y
) s7 U4 E7 I- j4 R2 i先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。
7 B7 C2 v" c. s
020011osionbunl4ui44vi.jpg.thumb.jpg
2 ?$ i+ q+ I5 B3 U! f轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。. Y2 G  t  e; U
020017j8ycmnv7788bqv52.jpg.thumb.jpg . y9 p! P- Y& o  s$ {% T
特写,80C49( x* r, G9 C( E6 P; O) S5 _! ]1 z
021040oujzuvtut6iujtvz.jpg.thumb.jpg
$ E8 u0 R, u3 q: WLED部分,使用了一片D触发器锁存指示灯状态.
* @: {' X% \/ i) {8 t 022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
; u7 ~9 A8 \/ `* U1 J# B% ^, @暴力破坏,将80C49拆掉* I% k5 x% l4 {6 n
021113e48qq98vyohvhzzh.jpg.thumb.jpg 0 a  Z+ K9 m. M$ I* Z( j
拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。
; }3 `, I3 a7 O0 b 021125nc9az6dj33rlds2r.jpg.thumb.jpg , N, K: U6 s7 n+ Q, h8 W9 }: u: x, L
焊好元件后的板子,准备替换80C49* o; Z& b- q! d" N
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg
; E% w# B% A+ w7 P0 |用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。! t8 i# e2 `1 a( Y
021104shifhnrqbr3o5nlo.jpg.thumb.jpg
- D- h6 ]$ o$ U. t这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.
8 i2 H8 j- D; M0 T: U8 A: q 022003ym1p9u4ug40280uu.jpg.thumb.jpg , ^8 n& p+ r' Z, {
开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。
) N$ ?: Y8 W! p0 b 023313kt141q9qajtol7ma.jpg.thumb.jpg
3 Y3 a+ y1 w8 x7 u我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。. V9 v/ t  Z+ A! s9 D+ v3 X
023322nt7l5xb3ltttkltt.jpg.thumb.jpg
. \) P- U7 A7 t" o, C+ q
主键区键帽就位
$ Z3 V) V* V) W) Q 023331hin88e8wkrwzwikx.jpg.thumb.jpg
7 B- l: B( L$ E5 G9 b/ c编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。* K6 q# v; i$ {# b, i0 ?" H" g% w
023336wjzlgopugg1jyy79.jpg.thumb.jpg ; q- d$ L- P8 z3 a8 Q7 Z- f
最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
) G0 K8 V1 o: D* u8 l 023341sffu4j3g2323h6fl.jpg.thumb.jpg 3 e3 R" B, K. p, D0 Q

; J0 T) a2 @7 T5 R----------------------------------------------------- 分割线 --------------------------------------------------% q2 {: ^+ M, b, s4 P
/ U4 t( J. s" g9 N& I  L3 M4 v
, m9 ^: \3 ^6 x8 h4 a) N3 a
3 b: O( |1 ^# s9 T# R5 G( s0 J
, s% K5 w& k  r9 ]* c8 I' p

3 ]" e/ [, g8 n9 |( y/ G
- L$ s, u' q) _
( ~. W" p& \6 A% J# o
4 [9 i- |' k7 U  `  j

! q; |! E6 e' @7 m; n9 |
7 U1 O9 }3 t) T  i; I; {: Y& \" ]5 G+ M& f( k% J3 r! G4 r8 u
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。0 `6 o; p; k% w* Z- m1 ^/ {: W% E
80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:' l' u+ Y9 k+ s. j
104025nzibm2rmiomhyirm.png.thumb.jpg 2 P3 ?7 q+ I& _+ K$ s- b

7 d8 a" V8 F8 e( J其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号): }! Q& i$ e7 y2 V( }- {
105004zkrez5houvkkznko.jpg.thumb.jpg
1 I. \" B/ G4 ~; i/ l, u. s

$ N# v9 P9 H4 k& T0 s/ ]扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。3 A0 |. `% v: Q. t

- ?; n) }9 h3 z: a这是我设计的电路图:$ i- S* x" @0 u- J3 @4 w
110344ej2z2oo2rflo7oe7.png.thumb.jpg
, _2 s- Y- S; m* I, }/ O
0 r7 ~: x* y4 z4 w$ n* m4 }
PCB Layout:
, L' W- D- H: y: b& A. w% u 110847jjbjvt34vwt3v5bb.png.thumb.jpg
* P! V& D6 }$ i* m

5 L& h/ g+ r7 ?: p. |' J" l不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O. 0 S  k0 g8 ]- M

& g% w+ s% A( [+ ]7 i' B3 Y

7 G; {; L5 L" I: a
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑
- |6 S! u' j, z2 S
  w8 b& R2 E3 A5 J" F& I7 E5 r软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。
0 U8 S' P  q+ e/ d
/ j1 X# B7 I. w! Y4 y' d总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:8 _# i9 X* A+ V
113818pmrfsb6z0byt6t06.png.thumb.jpg
2 Q7 |7 U: b( ?2 ^5 Q

; n( q  M# H( S8 q* H/ Y9 A其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)8 x( u8 W: k" l& Q/ Z: I- r

' U  W5 s, [- J3 c! Q& h- lUSB的中断ISR,bare metal哦
' `, B' f0 w( _' z1 c

% a5 x1 N5 U2 G$ a: ?8 ]/ s" ~7 zvoid USB_IRQHandler(void)5 B& ~- N4 j1 b
{8 r+ P5 [, u; P% E8 O5 r5 s
    if(USB->ISTR & USB_ISTR_CTR)' N% @/ P/ W5 X8 W7 y7 h( ]: m3 `/ c& S
    {
' v& N+ Y; C6 p5 k' H$ h6 D        if((USB->ISTR & 0x0f)==0)   // EP_ID==01 E+ K: c, q' C8 B) c, f
        {
7 i, U2 l( @6 T            switch(ep0_state)  M" t8 s5 U# q
            {* C& v! Z% _- U0 e$ ]  E* V
                case 0: ep0_state |= 0x80;  f8 Q+ G0 w. P* X" X5 S1 t: L
                        break;) S7 d0 r/ _" R& U4 C3 H
                case 1: if(USB->EP0R & USB_EP_CTR_TX)
0 j8 r& u- [) y: Q% D: f0 f% A2 Z6 A                        {6 X5 M0 |7 m3 i2 q# ]7 V" |* F
                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;/ K4 V7 r/ C1 D2 I0 x$ m8 ^5 U
                            ep0_state=3;
& R- W$ A- V& n                            return;
2 s/ C" ]! f# E3 H7 u$ y0 c, w                        }9 b( L4 k( O1 a1 C
                        else3 N- x( D" @5 E, J8 y& b
                            ep0_state=0;
$ d  y5 e7 W8 W' T3 n                        break;
' @* S6 R( |/ a+ }) k" f0 A                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet
# x- B/ |5 E$ p6 o& S2 x                            ep0_state |= 0x80;
" l, c" v$ b0 W2 {% p- c) d7 ^                        else
( [+ ]1 \7 h/ u! c                            ep0_state=0;" i1 I0 n& I! z% p8 `% Q* n8 n
                        break;3 r+ d4 _8 v- \
                case 3: ep0_state=0;1 a/ A' ]# P/ K: h$ h9 q- T
                        break;
9 v( H' f4 J  d. v) A* J- j; B                case 4: ep0_state=0;
' {  g9 I8 J" n$ c- x1 L                        break;
% u; |: Q6 q3 n8 Q4 n                default:ep0_state=0;
. a: ^: i" W* m  |& n, P) I                        break;
. b5 D9 d- Q, L1 y# W) z            }
& r, l+ O# x% x+ j' }; @4 E& r            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
5 x; y. o0 ]1 ?% r; m0 w            return;% K2 c/ b' `6 T3 A
        }: Z. _0 E: U  c$ l  h0 y
        else    // EP_ID can be 18 P8 H/ d6 K9 w; Z
        {/ F- l  G. p6 _' F
            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag# w0 O5 n' M' B6 k
            ep1_wait=0;1 _% t5 @7 h' J
            return;- `9 V6 q( L+ n! w. m
        }! k. N' E3 s6 h9 x  o
    }
8 H: f7 Z2 p( p& C' t' L$ w    if(USB->ISTR & USB_ISTR_PMAOVR)
1 O9 z. N4 I- ?/ \' ^    {& x1 w( `) r/ m- t/ V
        USB->ISTR = ~USB_ISTR_PMAOVR;
+ C* {8 }2 _2 R' [7 o    }
+ `2 r1 h6 I) C    if(USB->ISTR & USB_ISTR_ERR)& a& y0 o! m3 H, C9 I" A; t9 l* v' G
    {; h5 H' i/ j% v& o4 V6 H+ p6 J8 R
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear4 }% k% T0 v8 v! Z. [
    }
8 N( [) z, S5 D2 e    if(USB->ISTR & USB_ISTR_WKUP)
( N/ M0 p- }4 i  V- P6 ]! W" z    {2 i( Y5 p( D& Q
        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend  n1 ]7 {- O8 L# O' D
        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear
  }& a! L/ n; f4 ?) G1 K5 X    }
* h6 i1 K/ A1 r. v' I1 Z0 ]5 W8 x    if(USB->ISTR & USB_ISTR_SUSP)7 w( ^  Q  ~2 N! {3 K0 d" j
    {! z0 E, o+ S3 M  S/ Z! j) Q' t
        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend
  F. `5 s9 N" }8 a( V$ G        USB->CNTR |= USB_CNTR_LPMODE;   // low power: \3 R1 [4 P% S+ P1 x) K/ W3 s
        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear
- j2 ]3 |( h7 @9 p& }- r    }
+ F& _0 w5 e% `    if(USB->ISTR & USB_ISTR_RESET)
# ?$ H7 I2 D# i# Y0 H4 e5 K    {3 D, U1 K, K# l+ z; L( L
        USB->BTABLE = 0;    // buffer table at bottom of PMA: i2 ?% _+ J) f/ Y( w! @
        USB_PMA[0]=128; //ADDR0_TX5 Q! F4 P7 T* y( X* X
        USB_PMA[1]=0;   //COUNT0_TX
0 N0 c, }7 {3 j* U        USB_PMA[2]=256; //ADDR0_RX
2 T4 H/ D! Q8 @. E3 R        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes& g. J$ A( ?& s/ F1 j" V
        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;
& T6 G! u' d* [( P6 u' H- s9 P. Z* H        ep0_state=0;
1 j- |, K* M% d2 v( I        USB_PMA[4]=384; //ADDR1_TX9 L# I. ~, F% ?: _# M# f- }2 d
        USB_PMA[5]=0;   //COUNT1_TX! _% i3 u6 T8 W6 @9 b
        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type' U( x3 B& w' i, ^: M' S# n
        ep1_wait=0;
5 Z7 ^3 H& z$ x& f3 y! F        USB->DADDR = USB_DADDR_EF;      // enable function# n- K8 b4 e  c- ?! J; m
        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear
+ m5 O7 t" r% v    }# y% ?' ^- J/ h6 ]- y1 N
    if(USB->ISTR & USB_ISTR_SOF)
) J5 |6 ?: Y% G+ M    {- M1 v" }# u3 m4 V' Y6 t
        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear
' M" w0 ]; X, O3 Q' C    }0 G; W1 B: I6 {: B
}
  ]$ _* v$ U$ T7 s& h8 s; y& E7 w2 r
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。/ ~2 b2 U& I, E0 ~0 Y) ?2 _

3 x1 r# t0 ]/ X3 _0 T主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。
9 @4 L3 F$ _* p) L, H1 f
5 i* a% I  n. ^& j
    while(1)+ M9 P: Q  F1 Z. Y  K6 Y
    {
+ Z/ ~& h+ Y1 B. X& s; ]        static char row=0;
# C$ z8 _4 k* N0 e3 S% E0 w5 L% ~        __WFI();
4 s& I$ C4 \; R0 o/ C  y        if(ep0_state & 0x80)    // request data processing
' O: Q! X. y: G4 M3 Q, [6 g+ ]        {- r8 A( C+ V# |8 m
            if(ep0_state==0x80) // SETUP phase
, o" C& T1 ?$ Y( W  G            {
* I& d2 X+ V  G4 f# I                if(!setup_packet_service())6 u2 _0 J/ F) [  V: m- @9 \: ]# h
                {
) }: ^! a/ P9 c0 r7 p& A                    ep0_state=0;) z2 x) e* Z2 w& F- q5 h8 ]/ r
                    // not supported8 s; @! M  J( y
                }
- a$ k, U1 Z- s) N/ h! `; E                // ep0_state should be set to 1 or 2, if processed/ J1 l2 }& D8 M$ i
            }
6 h) v) E# s9 n+ [            else    // OUT phase
4 H; q! u0 I8 l6 l7 [+ \/ o            {5 |  b- C/ v+ w' I/ Z7 j9 U& `) b
                // process data
* T' O' M9 k/ i4 V                show_LED(*(uint8_t *)(USB_PMA+128));
- s, ^: a- V/ L- |                ep0_state=4;
5 K) ?7 _# k7 J  u$ o7 g                USB_PMA[1]=0;       // Zero length DATA0
7 A/ E% x$ \( g* d, d0 z                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
- W+ w0 o$ p, c3 T5 Q            }1 U! V( K; S, f$ Z5 W7 `
        }
. y  `0 P1 A3 F) P& y( m) s( h1 K        else
. K2 _* x4 \* Q6 b; ~- E        {
0 d3 x8 a7 @1 z$ W- U            if(usb_address && ep0_state==0)" k# \% L* |; z' O3 E5 D9 ?# i
            {
, e% s0 U! _. Y+ L0 N2 E                USB->DADDR = USB_DADDR_EF|usb_address;/ C+ H2 c6 o0 p2 D. w; l
                usb_address=0;
! w) a: r" Z7 }$ Z  n            }
! n8 ?# `; a& A4 c" w        }4 X% v7 U7 _8 G4 i
        if(row!=scan_row)   // new scan line% t4 g6 O' g4 E  r! i
        {
/ h, `& x9 o* l$ U- F% u4 Q/ H            if(key_state[row]!=prev_key_state[row]). b9 S- V( O# [9 \. o6 O1 c7 b
            {7 ~: ?; t! r" f! V) ^1 r# d5 v
                uint8_t test=0x80;4 @5 Y; O* o" v1 t% U
                uint8_t diff=key_state[row]^prev_key_state[row];4 m2 \: @. o6 U* r4 O( u7 m$ d: G8 F
                for(i=0;i<8;i++)# E/ h% ^- ^3 t4 z7 w) R
                {
$ S% S: p2 q- V5 o% J7 s                    if(diff & test)3 o" u' T. C4 b. z8 S9 R* L
                        update_key_matrix(row,i,key_state[row]&test);
. L, O! p" C' n3 {5 K9 I                    test>>=1;$ L: ~- ]5 l! Z( m0 J1 v/ W7 G8 D
                }
, n* H  o: j& ]+ J8 p4 `1 R            }9 t7 Z/ w7 O, Q
            row=scan_row;
) ~/ x( V  T/ k1 H5 i        }5 R5 z* F4 `: \' A" I
    }
. b; R! K0 r$ F4 N% [6 O
+ c! s6 f' ^1 `1 \! p& U7 ?9 k" k( k. U' R1 Z
EP0的控制传输,把用到的请求处理一下
7 a. t  U$ R' M# J, kchar setup_packet_service(void)% V/ W. Y8 Q* P" y0 r' B
{! o3 @; V7 _' \  @, a: `
    if(ep0_std_req->bmRequestType & 0x20)   // class-specific& u5 X, Q6 n5 X4 f0 i! R" T! h
    {
/ ?" u8 p9 r% a- H$ h0 Z0 t        switch(ep0_std_req->bRequest)8 y( d7 h& S5 t( Q/ T
        {: M% W. B9 d  ~9 e1 H
            case REQ_GET_REPORT: break;: z7 G4 G' ~' m! T; A/ P- e
            case REQ_GET_IDLE:( H  g% E" s/ N9 ^
                USB_PMA[64]=0xfa;   // return 1 byte
5 f+ X7 L  v9 s% d- e' l% W9 ]                USB_PMA[1]=1;
( M# ~3 n4 A! V8 m" x/ x. X+ ?3 o  W                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;: v+ z" P* w, h9 ?
                ep0_state=1;
) Q# }' p3 `0 |8 F0 ~                return 1;
1 a: }. [- m8 l9 R) ~8 v                break;
- M" U0 I  L7 s            case REQ_SET_REPORT:: w  j6 }* a2 a% b
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;# [% k6 m3 t/ V+ w
                ep0_state=2;5 V9 d" Z1 U9 c% d1 j9 V- H; c5 k
                return 1;
$ [8 b6 W" Y  l  o- p9 Z3 z                break;
3 a- M0 A! x  x" `            case REQ_SET_IDLE:
% U) `/ ~% |# O& h' d& r# V* L                USB_PMA[1]=0;   // Zero DATA9 B- m2 }& @/ k8 T
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;0 D. ]( ^2 [, H5 Y. B4 [
                ep0_state=4;9 v+ I. P) ~* s1 P1 R) y4 l0 t- L3 _
                return 1;
1 K( D& E( x  C                break;
; ]8 }* n. O( ^& w0 ]        }
, O" `* Y& |7 @. x+ I: a' k        return 0;: W6 D% ~. J) N3 E1 o; \$ W1 ?% N
    }4 F; {; S1 l- d5 V
    else    // standard
' B" f% p" t6 r7 |+ \7 x7 p    {
4 \( b* B7 E0 }: g1 G# X        switch(ep0_std_req->bRequest)
; t  O6 v2 S) ?, [        {$ q$ q% P! _' z. O7 T
            case REQ_GET_DESCRIPTOR:' V, h0 J' R5 E1 w' E2 e3 J' e
                return descriptor_service();) p2 L& C9 f5 r6 {. A- F5 A) ?0 l
                break;. G8 K4 O+ c: l3 v
            case REQ_SET_ADDRESS:1 O3 J; b& g' |- t3 Y$ u
                if(ep0_std_req->bmRequestType!=0x00)+ P% r) p; L+ S! W% o9 Y
                    return 0;+ s% k) a: a! ]2 c
                usb_address=ep0_std_req->wValue;
; e; J4 X; L3 T5 T                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
) O0 |& V1 A. D) o7 t& X                USB_PMA[1]=0;       // Zero length DATA0
" \2 W% x# Y  a* U8 M: l( D                ep0_state=4;    // No Data phase
' {% ~9 |* j) W  \  k' Z6 a& y6 e1 r                return 1;; p0 y% W+ B1 V4 q" u/ \: p
            case REQ_SET_CONFIGURATION:$ O/ H" H( @# q/ }
                if(ep0_std_req->bmRequestType!=0x00)" N7 d# g9 H5 e3 ^
                    return 0;
- W0 ^5 }4 _5 ?2 g( x                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;- w# t2 ?+ `" G, ~" \; R$ o& n/ n
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
0 A9 h# v; o7 u/ y7 {                USB_PMA[1]=0;   // Zero DATA
- s) V6 H/ q/ ]! p/ n2 M                ep0_state=4;    // No DATA phase
8 W# s% ^4 P; q                return 1;
3 d9 }9 M: e8 |9 c' q5 W& X) D            default: return 0;
4 ^- {, A: }* ~/ W) [        }7 a, D; m  m- `4 o* @: |
    }
) P5 P* R  f5 X6 U& J! j" n/ Q4 m4 e( K}
3 V3 J5 l. K2 h, ?2 V. ?$ ~7 ?
+ h7 X( J9 m& b: f+ r8 N" O3 B5 w  ?; _. z6 ~5 D4 w( h

' [5 I6 T3 T/ f6 h
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的" k: q% U& t8 t  l7 i8 f2 K: n
char descriptor_service(void)% T( q$ E4 d1 w5 o5 l
{
6 Y, o  x" h( u    switch((ep0_std_req->wValue)>>8)5 E! F. C/ v/ i. q
    {
8 B; ?% l1 @; `- _% e; O! R& ?        case DESC_TYPE_DEVICE:
  m" A1 v$ w' f            return ep0_preparedata(&DevDesc, sizeof(DevDesc));/ a; ~/ C: W7 C4 X0 ^( ~
            break;
# w9 |4 O' J0 X# U        case DESC_TYPE_CONFIG:: y7 N, s; a6 L
            if(sizeof(ConfigDescData)>ep0_std_req->wLength)
+ i3 s) O! L. M' y- K8 n                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);
3 n" h, I) F- b/ `+ [% S! Q            else9 M% r/ I6 ?  q# {7 t% |
                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));
: g2 w6 ^/ q6 V% [/ f' c            break;- j7 ]7 h: L8 m+ a) _+ g
        case DESC_TYPE_STRING:
# O" S7 X1 {, a            switch(ep0_std_req->wValue &0xff)
! a4 j$ R$ P2 t            {
* K0 ?: s, W+ a5 X3 t( Q- Y' w; F. Z( P                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
* t* F, M+ W& Y! t+ L                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));
5 c4 f6 a' O3 g5 w                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));
( S# X- i4 _# o# n' ]( A+ g! S                default: return 0;
: j) C, v4 D3 y. z* P3 R, d            }4 T; K2 L% n$ e5 F* B& r: R
            break;" m2 o6 t& R  W; T7 B
        case REPORT_DESC_TYPE:6 w; }( n8 O  F* t  p. I' E
            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));; o# T. x9 D) }5 d9 p
        default:
5 \" V: |3 n) v4 g            return 0;# V0 C! l7 {$ K  ~2 h4 q9 j" u0 J
    }
, g- V4 ]; E5 p( F}
+ B/ d( I6 V$ V6 e8 p3 e$ D下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.0 I+ A& F! o6 M, ^
void TIM6_DAC_IRQHandler(void)
: B- G! m. Y( k1 m9 F{
' I; L8 X& C6 P3 b6 t7 o    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);
! q' N3 q6 s4 }# L) I$ s" T7 y
2 ?* s2 T4 Z% P
0 _, y! H, P* N3 H5 \5 W
    TIM6->SR &= ~TIM_SR_UIF;
9 m1 d. N* K: L    prev_key_state[scan_row]=key_state[scan_row];
/ x+ K) }0 S, s    key_state[scan_row]= *PA_IDR;   // update key states0 e9 O. G! A2 S$ {& v
    switch(scan_row)# M# `# w. m! q; J
    {
' w2 d; b, X7 G- V0 J        case 13: // next row PB148 C. ]* ^3 D8 |: V
                GPIOC->MODER = GPIOC_DEFAULT;
8 o" n4 A, d- |9 g9 I  _                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;' `9 f  D. |9 I( H8 J5 `% K" A
                break;, I. m8 U2 a( |6 Z) p8 s( V% c
        case  0: // next row PB15, ~2 P, b9 t+ \0 x+ c
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
* f# s0 O" ^# ]# D5 H                break;
' i; O& p! A* w        case  1: // next row PB3
+ M: g3 B; H2 \6 [( T* t; K                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;
& p& B, c/ c( A. J                break;
0 G, S9 K3 m6 t4 g1 R4 k        case  2: // next row PB4
; U. m' s* e2 X7 m) u                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
$ o" u: r' m8 n' D- A                break;$ c! [, `9 Y1 [0 c6 }9 {
        case  3: // next row PB56 d( C- Q( B" i
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;
: k  D5 P4 o4 O5 R2 S2 b                break;+ g5 g9 R" ]7 D$ C2 ^! Z% H7 ~. b
        case  4: // next row PB6
3 g6 w4 p0 ]5 |# K# I3 D                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;
- P. h! W( b  {+ P, p                break;
0 A  [5 M, _* P* j1 K        case  5: // next row PB7
. B2 w* [5 q( T4 ]2 |0 s) i0 ?                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;0 k4 y/ D# ^! o+ M
                break;
* f2 ?3 i# ~+ Y2 p, q        case  6: // next row PB89 z0 e4 d( a0 r$ G: ?& ^
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;) F" y; U+ d' L7 T0 ^! B& n( x
                break;
, b- p& v5 q& x$ O        case  7: // next row PB9
  e$ a. a  R, F, u8 O6 B: ^$ }                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;
4 i6 |/ _4 d$ d2 H  B6 D5 D0 n; d9 N                break;. n1 L0 P8 L7 w  _
        case  8: // next row PA8
. b. c# B0 E4 m5 y* R, ?/ H: r8 Z% E                GPIOB->MODER = GPIOB_DEFAULT;5 W, W: F! i% j' Q* V
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;0 T1 I. k  |& U1 {2 Z- }
                break;
2 [; Y" a: `1 C( o4 X4 S7 w$ U        case  9: // next row PA9, y+ p; \; Q* O2 `# N6 k9 r
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;6 B* I. G! c, U( I& E9 C
                break;+ ~  G. E/ _, D$ a* _! |0 ?
        case 10: // next row PA10
, e# f& k: y1 P% O2 W! E2 x/ s                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
4 y9 x1 t: B4 K4 y2 D( N% m9 q; d                break;
& F5 a4 S' p1 R9 G" u$ g$ c        case 11: // next row PA15/ m# _4 |4 H' {3 E, ?& H
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;
" S3 \; {# n$ `: V0 v  K                break;
9 o  a9 C, J; S- ]: j6 t        case 12: // next row PC13- Y3 H# W! \3 t6 _
                GPIOA->MODER = GPIOA_DEFAULT;
2 S! C3 }8 m" |2 _( \: T                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;3 z: `6 B& ?% {5 w$ ?" K; Y7 I
                break;" u( N/ M) a2 J
    }
3 V; ~$ N: _# R9 L- h0 ^4 F5 ^    if(scan_row<13)
  d6 ^7 x6 e( w" B9 g  |, h        scan_row++;
5 B0 ~3 z) p1 C% `    else
5 f$ t! o- Z1 x' `" [        scan_row=0;( T- a8 e/ o, O5 o8 `& A  n( `
}
/ s; \6 z! e" F6 A  n3 ]! n" p: E* o( {; n! R" I9 w
. r7 ]. K* C& C/ }' f) h
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑 5 `" r- d6 e  i6 \( C( x4 Q. w

3 a" t' R+ B3 v- {. N扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。
* ?+ Q2 D+ |8 Y% ~- P* Z& Z9 I1 o, ~# o9 }: D  {
要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:

* O6 g$ e' {" q* n0 l& j) a
9 W: m0 V  p9 q3 O1 M0 H+ o' _8 a' t! p- J" H8 Y! n5 x7 L* V2 k
const char hid_keymap_qwerty[14][8]={# S# |$ C% K) ^1 B" q) ~
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},
- J6 ?7 s4 i  h% E    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},$ F: O/ n9 r: F7 v3 d, o
    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},8 l4 B5 U' x1 i: v4 D9 d
    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},
) y1 F* t! |8 T) N0 U    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},) n' u  e0 g) }2 _- n! W& s' ?' f8 r
    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},' {3 ?6 @( Z+ K' W" u4 {# w. x4 j2 g
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
$ E$ b1 R) K$ d+ E5 G' N    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},
0 v( e. m  G! ?: J3 [    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},4 T6 g. b- I( Y3 I* E8 w; R( P
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},* e( X% a$ G& a$ `* x' N# G
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},# K$ f* m+ X9 n: d
    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},+ Y6 I' K6 F* q- H( u2 @. q- e( A
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},: L: F* ~1 g0 P( \
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}! d- ?' O. [# p: u& d6 c' Y5 ^) D3 ]
};
5 g/ o# |, k) w5 H( s
5 |# r6 Z& [7 ^& V* l' M$ r! O' f
6 g- i1 |! m. {0 }: \  y
const char hid_keymap_dvorak[14][8]={2 z3 r2 U2 Q2 S; H
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},* p9 `' g6 ?4 f0 B4 T
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
9 e: M/ p# ]. X9 K4 i    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},
( x: g/ e; x% W2 D    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
! t% Q3 ]! B* j' y2 O0 v/ ^" ]    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},
( \- \2 K& l+ F) W. C6 |5 I    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},& ^$ @7 Y7 N3 t* h
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},; s" N% j: {: m
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},3 n' W5 Q( C0 D
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},5 h/ o) a* v6 I, c
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
  E0 d, R- N1 R0 H% y    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
( x2 X$ P; I$ c' d+ F/ W    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},
" s2 `# n7 U* S+ U2 O( P$ g3 Q    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},
6 M1 @4 R5 f2 H3 i1 U    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}+ d; C: {4 N) y1 w
};
- Q) S; ^: q+ |3 |- ~" o3 p+ K0 F# Z9 M7 L

6 Y! X7 T6 `- {5 i! Z上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
$ g* L6 b& v  A* d9 |0 i. f0 J, S+ Q/ l8 l$ h; L' W
HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:

, ~& w0 C# s: \' L  I9 C$ {" Q8 B( Z. ^  P) i% t0 A7 e9 _: ^
void update_key_matrix(char row, char col, char onoff). N% y, k# j3 [# D& W2 }4 c3 F+ n
{4 h- r* @7 a+ E) T+ G1 U
    static uint16_t hid_report[4]={0,0,0,0};
: w* O. W' |5 A$ I    static char (*hid_keymap)[8]=hid_keymap_dvorak;
/ y# G# m" Q3 h( s5 w# i8 V* y8 R7 f5 [

" C2 e" \1 X* {1 _8 _    unsigned char key=hid_keymap[row][col];" r. n" [  _: |1 h$ {9 L4 W
    unsigned char *report =(unsigned char *)hid_report;
3 B) e) ?( t+ ]& n% b0 x* T9 b: D    char i;
6 M* {  }1 D' t3 P
# j& @8 q& h" P. W. \
3 o* b  O3 [0 Y7 j
    if(key==HK_MODE)5 Q( U" D, {/ ~( q2 t% D
    {# G0 w) u% {4 q  x
        if(!onoff)* d5 ?, S  V8 o  g" p7 g
        {  S3 L( i/ i9 t; \7 v5 Z- M  }1 Z
            if(hid_keymap==hid_keymap_dvorak)5 y: [, R/ d, b, z6 `
            {
4 k& h2 s2 f- n5 `                hid_keymap=hid_keymap_qwerty;
6 z3 u6 U7 @0 i! U                GPIOB->BSRR = (1<<2);3 Z' p1 T- u, G( e
            }
9 A. P7 N3 _! l4 ?6 o/ z            else% C1 Z# _, J0 G6 r9 ]
            {( _. B  ^* l# e, B4 w& M7 }: W
                hid_keymap=hid_keymap_dvorak;0 O  _: t' C4 q( E
                GPIOB->BRR = (1<<2);7 ~# c7 n- j. g- b( Q. _5 z3 V
            }6 Y* _  R# r1 T+ \) X" D4 F. B
        }) T# ~2 X9 u9 w, J2 B6 {
        return;; c% a: ~4 X+ E* L- Q9 [: A
    }! v) T! n) x$ k* X- K$ F  B7 Y6 F

2 i9 c; J! Z! M2 J! Y. j$ v+ r9 Q4 \

0 j" q+ O8 K# [0 N* x& i/ ~    if(key>=0x80)   // Alt, Ctrl, Shift
/ Z0 k, l  D7 s    {: t) ?* P/ M6 [9 q- b
        uint8_t bitset = 1<<(key&7);1 @+ b; z- P  [$ r- S1 M& F/ W: V
        if(onoff)   // non-zero is key up
2 d  X' Z6 I7 P3 d  j            report[0] &= (~bitset);. j' p/ l3 f. V
        else; v# Y! }& F) a2 y- p
            report[0] |= bitset;4 W" f; s2 }# W3 J
    }( b7 {# D0 U5 i/ l) y: d( P7 ?2 c
    else: q, N: M0 H  C# w8 N1 t! T% v
    {2 D3 a9 K# q. ~/ B
        if(onoff)   // non-zero is key up
0 C# u& Z3 K6 ^% m        {
7 K, v; J( _2 K! J& }8 W4 N            for(i=2;i<8;i++)
& q! F, c% g  ]! M% l) s8 N% S3 w            {' h4 v! w  L' P2 e
                if(report==key): K) a" Y! Z) C3 ]# i
                {0 g% Z, ?% E; g1 c) c5 H( E1 \
                    report=0;- j& Y% ]5 z  t* C  y' m" @
                    break;
" V" {( x' \% K, C2 B- b+ m$ v7 V                }
& F! w$ h: f4 {4 X! p  c7 S* E            }5 c! r# m3 K; e% _
        }( S4 ^9 J4 v! `7 x3 t
        else
. ?7 |& |: c, \$ F$ u" R6 g" l        {
# {. X2 J& c: G) p4 W2 D            for(i=2;i<8;i++)
" [: W$ I1 Z, B8 `+ i            {
) ~7 c  F0 i2 u1 Y( ?                if(report==key)- S) L4 W+ t8 M0 x9 v" F
                    break;
# J+ d; F$ }, x: D% X                if(report==0)- N' A( H+ n% f& o8 _) }3 V
                {2 w; b* e/ V5 A- [% Y  ^
                    report=key;
% G( D& v. H, M! b                    break;) g. e9 U, [( V% Q9 N6 }- t0 u
                }. U1 l" p4 J6 j! j
            }
' n4 @- o3 e# ~4 P" \! ?1 e6 R        }
2 z4 P" b: C! m    }
2 }$ U, P* q1 f' E" K+ M7 i    for(i=0;i<4;i++)6 ^$ X0 Y; x) ^& p& K
        USB_PMA[192+i]=hid_report;
  F9 b; ^$ ^. {: a8 R    USB_PMA[5]=8;   //COUNT1_TX
- {5 k$ }1 M% k  H" k, J6 G    if(ep1_wait==0)* f6 W& ~& O! @, [/ B. p$ u8 o* ?
    {
" h9 c+ i' B: N1 O: g        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;, s8 n6 w6 R, p
        ep1_wait=1;
5 O: u3 J' Z3 M8 J; N    }, c( b+ u, P. h  _0 b& Q; V6 A6 J
}: Q4 L0 i* }% O" {; z

+ Z( n$ e" n. |- J* Y) k9 L# G8 I' ?" x- k
完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。/ G, _4 n/ s: W8 ?; r
keyboard.zip (8.7 KB, 下载次数: 6653)
) h, c+ P  Y$ z
8 g1 l: \: P0 q$ ?9 t. ^* `

4 v& f. F- v, P7 J+ E" A4 X; l
: v2 }. C$ L  E4 I( K
* O0 |) \$ ?* g8 S. a
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘
5 q) }; m6 @: H7 v不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48+ b! M! O; |+ z3 ~" i+ E; `* L$ u
刚开始我以为要把打字机改造成电脑键盘7 p- |* q9 G  a, c  ^2 E0 n, ?
不过楼主也很厉害!
: A7 B2 Q/ S: ?* S3 @! L
哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害: P" _* H6 O7 _* W* k
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-8-20 03:03 , Processed in 0.176764 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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