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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

积分
288
发表于 2016-9-21 20:34:19 | 显示全部楼层 |阅读模式
本帖最后由 miaozhuang 于 2016-9-21 20:35 编辑
2 `6 k) u8 R* r5 {4 J- Q7 v4 [  S6 P2 U) d
http://bbs.eeworld.com.cn/forum. ... digest%26digest%3D1
0 |6 H" ^4 }2 i! s3 t: y1 f/ x! N9 l这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。4 {0 h7 A5 g, U3 d! G4 }% T( v
& V, J' @1 i9 h
在看这个帖子的诸位一定都在用计算机键盘吧。键盘上的数字键是1到9从左至右排列,或者是右小键盘区那样三个一排有序排列,反正规律很明显。但是字母键却不是A,B,C...到Z这么按字母序有规律地排下来的。我刚接触电脑(其实还是学习机)的时候,没在意这个问题,觉得是要盲打嘛,反正对两手的手指头来说,按字母序排列并没有什么好处。于是用多了这些排列也就记住了,从来不管它为什么要这样。其实,PC的键盘键位排布上是延用了打字机的键盘,这是设备演变过程中很自然的一个延续。打字机的历史就要早很多了,我没有亲见过打字机长什么样,而且,咱们汉字是铅字打上去的,和英文打字机方式完全不同。
: j- K" r3 b: Q+ m3 P4 p( D

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

) o. m. A1 j* R' f 005836yvs0wvovwsssgd3o.png.thumb.jpg
5 Q& d6 r0 K' u3 Z% P. D4 T0 B. qDvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。
5 P* D! f* O+ J, o7 _
8 U0 D( o7 g( v& S% G我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。
( v; l9 e+ C' n! E: z5 l
8 x3 r5 w3 K& q1 W5 j. L到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。) c& F! p# x: O* m1 K  U
! }+ g; T' Q3 O3 c# w
机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:
) v: K0 ?: w; j' q# ~1 {
123206vx24rfxfp2dfrpxg.jpg.thumb.jpg $ t7 P8 u# V) L% G% B  [' [
----------------------------------------------------  分割线 ----------------------------------------------------------8 Y3 c; n8 o/ o
# \& N: R5 F6 g  @
先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

5 x7 G8 m4 h+ N* r% g5 s 020011osionbunl4ui44vi.jpg.thumb.jpg
$ C3 b/ B3 ?/ k2 F" O轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。
4 a4 b  y8 a, ~- G* n: \7 T7 i 020017j8ycmnv7788bqv52.jpg.thumb.jpg
9 o% Z% Z# Z8 ]! E+ Z9 x8 ~  t特写,80C493 g/ u5 h; n1 A. b
021040oujzuvtut6iujtvz.jpg.thumb.jpg
3 w  j; s* l" ULED部分,使用了一片D触发器锁存指示灯状态.* V( j) L* |+ P# C1 X& `
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg 1 U% V* O- ^  E+ @! d
暴力破坏,将80C49拆掉* p* n! y# h' P7 l  K+ I" @) `
021113e48qq98vyohvhzzh.jpg.thumb.jpg , |+ S! e8 s- H# v1 m9 _
拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。
6 Y& |' X: j& @3 z8 J! R+ e- U2 s 021125nc9az6dj33rlds2r.jpg.thumb.jpg 2 Q1 M+ _2 y0 U% s% F
焊好元件后的板子,准备替换80C498 G4 l6 x3 p" T  q$ q
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg 3 c* ?( A! M" P5 X
用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。
) ?7 i& X  c# ^1 {) x9 n2 h 021104shifhnrqbr3o5nlo.jpg.thumb.jpg
8 s1 ~' w! T3 j" g4 a% q这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.
. ^0 `$ u7 K6 e2 [ 022003ym1p9u4ug40280uu.jpg.thumb.jpg - o! W$ T+ O) B' n4 m6 h9 o1 U/ j
开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。
) E. q+ @4 B, z! ` 023313kt141q9qajtol7ma.jpg.thumb.jpg 4 Q  k+ b) C+ u! T
我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。) A6 [' ?! h3 e& o: d
023322nt7l5xb3ltttkltt.jpg.thumb.jpg
: t' ^* S* C* r% A& l8 Z3 K
主键区键帽就位' Y3 w& i3 R( s6 s3 I% j, ~
023331hin88e8wkrwzwikx.jpg.thumb.jpg
9 g1 y! W( f3 j  x% C' X编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。4 h  r( t  S: V/ L' |' w
023336wjzlgopugg1jyy79.jpg.thumb.jpg 5 D6 q4 s, p. u# q& D
最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
7 k! j: N% a' @1 i' ^7 d9 ^$ Q 023341sffu4j3g2323h6fl.jpg.thumb.jpg
( }4 L7 Q3 d: f3 p6 v7 {5 k8 A8 k

. h, I4 d5 F; k. |2 q----------------------------------------------------- 分割线 --------------------------------------------------7 k# J- L- M7 Q

2 l3 i5 |2 c4 l. x4 a
. K  T5 Z- I) h* r1 g
8 n5 u- E: _' P$ g! i% ~: [3 i

' o' W' i6 q  A9 x; q9 j) x! x
2 j, h. I' h( O. Q

. t# Q& }' w, B5 q' V
) D/ _6 N: D" r, f. z9 r. q6 Q1 n& r6 e
; r) l6 I' R. T7 J' o( A, S9 a$ K! A# g/ S
. \  o5 S2 x8 e% ~) s+ ^  `' c# Z" q+ H

+ j) A+ R7 d3 s7 F, J
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。+ n7 H: t+ v" e, E6 ~
80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:
$ }6 ?% D" y! q, o) x4 U 104025nzibm2rmiomhyirm.png.thumb.jpg 8 ]. o  i, H# w4 e* y
3 }- O8 R3 v& F2 g9 g6 B$ S
其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)
& w" }* @8 E/ z 105004zkrez5houvkkznko.jpg.thumb.jpg
4 O2 o- z' K* k4 z

9 ?! f5 O0 h' g- R/ G3 R4 r扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。1 T1 {" t6 i0 d* ?2 P6 k/ W
3 X% b* A  E+ h% p5 P8 x* M* G
这是我设计的电路图:
0 a& ]# n/ {  z. L1 S 110344ej2z2oo2rflo7oe7.png.thumb.jpg 6 c' T3 G$ v  E6 ^

/ j1 {. k: @2 @4 M4 Q# ePCB Layout:
/ O$ b4 G5 ~( @1 p. V& ~3 d 110847jjbjvt34vwt3v5bb.png.thumb.jpg # Z) H$ o4 [8 X
% a" }* ]8 X" {; F# G
不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O. 5 O( s* W6 t1 N# Z- x- f

( F) E2 C- x3 K/ Y; A! g1 q; I
% l. m3 m. G+ C1 x, F; d+ f
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑 ' j& L, [2 h! i6 m( C  Z
/ p- x0 ~- y% t8 k' k& G
软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。: ^" H4 R4 K/ ?& m; @8 w
* ?/ y. E: Y: _/ v
总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:( P3 X  T2 ]* p
113818pmrfsb6z0byt6t06.png.thumb.jpg
# ?" T: U3 n, W7 D
# _% s1 v7 U. \% Y0 x, x
其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)/ [! L) |- x# m3 `1 j( M

: A3 x- w. J* B) R4 qUSB的中断ISR,bare metal哦: K8 x) S/ ?6 z7 I  s

6 A( V; r1 h, G& _7 A  b. rvoid USB_IRQHandler(void)+ h; F& e  {+ v/ h4 Q" ]+ ?
{8 r, Y( R' E$ ^% Y2 D* ]& h+ k
    if(USB->ISTR & USB_ISTR_CTR)) G$ d# B) v6 p
    {
, t" B* ^- Z3 j# q3 ?2 E$ a' C        if((USB->ISTR & 0x0f)==0)   // EP_ID==04 y7 I2 n0 b5 Z( c
        {2 d1 B2 `& b, r; W4 i
            switch(ep0_state)# i' Q- L" c4 `# M, n* @( a+ p
            {+ {1 F8 n5 a. [# _- T! Q. }: v
                case 0: ep0_state |= 0x80;
* x( U# m7 L. t# a# y0 m                        break;& p& C* e. E6 x; j
                case 1: if(USB->EP0R & USB_EP_CTR_TX)
' K7 ], o4 W! y                        {+ Q+ t- t/ j. H' P9 b/ `- [
                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
/ g2 R  c5 J( v. E, U                            ep0_state=3;! O3 Y7 h4 K- J8 H* ~+ D9 W
                            return;
  v# \% z& k. w+ @8 g( @; a                        }
6 o. |+ l& T% o                        else
4 G/ L# b( G' t- @( M2 h                            ep0_state=0;9 Q: K: M" D7 P* A: E+ T! e5 z- w
                        break;; \  _( w8 X. X% W& b& A; j8 l
                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet
: N. C2 O2 f* w                            ep0_state |= 0x80;6 n/ X8 g' T* Y
                        else
' i0 B) `& G! C7 `4 b8 E+ B# [8 T                            ep0_state=0;7 c5 h7 l' h7 @+ e- @
                        break;6 @$ n' Z$ G: z# g1 h$ D  S$ c, ?+ [
                case 3: ep0_state=0;- p" E- ]5 \: G3 L. M) {
                        break;
; e" C( W7 J! L$ V  ^                case 4: ep0_state=0;
9 o; Y( `  a; _4 P) n0 ~$ f* N                        break;  y6 f4 j( a; j' k& V( o2 K
                default:ep0_state=0;
( F, c9 S. C: {5 ?& R                        break;
1 {' t8 e+ A/ _5 Q/ H! J/ r: m            }
- g2 y+ \9 J$ ?3 M; G            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
0 N: S8 U% {0 s& |% Z            return;4 V# o  _, t. _& u. E" t
        }
" e) k% V) `6 @. J5 Y& ?0 c        else    // EP_ID can be 1
6 }) g/ D3 r- Q: k        {, o5 W5 _4 \) t
            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag
( [; u1 S, I( J5 _9 D" }0 v            ep1_wait=0;
; N$ i& ?, b  C+ x            return;
+ L" X$ E0 v2 ^/ W! P! t        }
0 I1 a5 s* @" n1 X5 D    }, v1 y, i7 u7 U2 M
    if(USB->ISTR & USB_ISTR_PMAOVR)
8 y9 v! `( v3 b( t- Z) \$ v    {
: a3 Z" Y  G+ X8 i- G6 h( v* R        USB->ISTR = ~USB_ISTR_PMAOVR;  U- U. T0 I& I) K
    }/ f1 k3 t# H# x$ F
    if(USB->ISTR & USB_ISTR_ERR)& Y3 |/ B1 d! n, q
    {
' b& O$ b* b$ ~# w( |$ p        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear
3 v+ f2 {- c1 h. ~% L    }
2 l: u$ v, m5 z$ U' X. _* [    if(USB->ISTR & USB_ISTR_WKUP)4 K% k0 |/ v8 Y( J% N0 v6 b( ?
    {/ H9 U" @7 Y  A+ @- g4 O* i
        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
6 A+ u' l3 D3 Y6 w) T        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear' w4 X2 q0 j) s6 B: Y! ?
    }
; [3 N4 \; i% B1 K6 R    if(USB->ISTR & USB_ISTR_SUSP)
# ^! t0 p8 r1 Z6 ~1 P) I" q    {
; ~/ L$ j" Y9 r5 z8 q        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend
1 j+ ^  j7 {& I8 a$ i# Q        USB->CNTR |= USB_CNTR_LPMODE;   // low power
% A  f% d+ [8 J9 Q6 g0 |, x        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear4 L" x, m5 T0 E8 p# l8 I$ o
    }
* G2 y6 X; c/ V, X" y, S    if(USB->ISTR & USB_ISTR_RESET)6 M6 f) X( q: F7 Z, M0 _/ c
    {
# l, _: C+ c  l        USB->BTABLE = 0;    // buffer table at bottom of PMA: m% h! F0 }3 s0 b2 [6 I* a2 r
        USB_PMA[0]=128; //ADDR0_TX
+ f' r+ m$ u& v$ o; L3 a9 Y        USB_PMA[1]=0;   //COUNT0_TX  C' j6 D$ h% K) K* H; {7 N
        USB_PMA[2]=256; //ADDR0_RX
6 R- j4 |& p8 k9 M! |0 I        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes+ E( U! Z9 |" P6 x. Y
        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;* Q# ^  }* b2 ?4 _
        ep0_state=0;
$ ]4 K0 F% m6 Q& A7 J/ s! E3 ]. ^        USB_PMA[4]=384; //ADDR1_TX
: k- [  \: N6 H& ^6 D" m        USB_PMA[5]=0;   //COUNT1_TX2 j3 b+ p  w! O
        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type& |- o& ~# P' c5 Y) ?7 |# i% I
        ep1_wait=0;
# S$ `* b: x1 b; N5 n1 E( b- ~        USB->DADDR = USB_DADDR_EF;      // enable function4 z( j& h) {% ~' a! k: R
        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear% ~9 W* Y+ F$ K: g0 G
    }9 n- _& R( O9 K  ^
    if(USB->ISTR & USB_ISTR_SOF)
# B8 }$ \1 e4 v+ `0 A6 H6 A  C    {
" O6 w" ^  f8 N2 B' X0 }" E3 w        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear, s  U( U5 m2 s; L- V
    }0 d$ o7 Z! U  X% l9 Q/ W0 w
}4 `1 m- p' W0 H& @1 x
* [9 V, n+ V, B) L
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。
: l  ]7 }1 [/ y6 o2 i" s/ M* o' A3 R, }
主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。
6 j# `! P" t, @7 r( _+ b. w& u( f* ^
    while(1)
* f, I7 n- U; j! i7 f) b) V    {# M% J4 l( m/ A) {) V9 f" N
        static char row=0;
  g& L. \% M! N4 \        __WFI();
( _5 k. t, M5 R- r: d        if(ep0_state & 0x80)    // request data processing
" R1 @4 f: L* S1 Z: G5 U$ {        {* V+ F. [4 A9 L+ Z
            if(ep0_state==0x80) // SETUP phase; r- p9 P* \: j% w
            {# E+ u( p9 ^( K  D1 n! l) K# K5 x
                if(!setup_packet_service())+ o8 w' X; Q- y1 B" e7 K
                {' a+ p8 |7 f( _1 b
                    ep0_state=0;  ]* z5 L; M( q' ]& @
                    // not supported3 K4 ?" R) S9 q* i) c
                }* d7 j8 W' Z. h* s3 V, A
                // ep0_state should be set to 1 or 2, if processed
& K  z0 x6 @  z: j+ p% p            }  b* A( B3 U# j  j+ l$ h
            else    // OUT phase* ?, @, c  q# O2 f6 l, e; i. r
            {
* v  \5 h2 U! Q+ L) S7 W                // process data
& n( v+ i, M3 g: `- j# G9 }1 z                show_LED(*(uint8_t *)(USB_PMA+128));
! v2 g( ?5 D% R$ M$ X                ep0_state=4;: a6 F, V" i- T; _8 J
                USB_PMA[1]=0;       // Zero length DATA0- N1 ^/ ^. r1 h! N' c
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;* @8 s; ?2 N" W
            }
$ R, E2 ]* O4 v& S        }
" N, O" A  @. N7 V        else
/ W* i: K3 X+ O( z9 B, s        {: \8 o9 M( T6 f
            if(usb_address && ep0_state==0)3 C& R) `5 d: ?# n: X$ b! I0 c
            {
+ C' Q, f0 @6 U' g                USB->DADDR = USB_DADDR_EF|usb_address;  t! J4 L; A+ }; ?: x- v* M
                usb_address=0;% y' m" w7 |8 T* L! s
            }* l, q# B/ V2 x% j
        }4 n0 x3 @( y/ v* Z* S
        if(row!=scan_row)   // new scan line
1 O+ q0 D0 N& c7 a& b) k; w        {3 O" S8 |$ z9 ?/ x) \  E& b9 f1 q
            if(key_state[row]!=prev_key_state[row])8 G# N# d  P6 F" Q
            {0 {, H' b; [! X- H* a5 ?
                uint8_t test=0x80;
$ X2 Q  w& |7 |' ^( q/ l                uint8_t diff=key_state[row]^prev_key_state[row];9 I  F' P7 r6 p2 f$ D8 r
                for(i=0;i<8;i++)
' o+ B: `% Z: q% |* Y( }                {" G2 P' A5 d! D. K* v( N$ b
                    if(diff & test)) ]3 j/ m5 V4 R
                        update_key_matrix(row,i,key_state[row]&test);
' l  V' D7 ^# h# ?% x* [                    test>>=1;. {8 o$ ~8 J. y6 _  m. B0 B
                }3 \& g7 X' h7 D, P$ ?
            }' p+ C& `6 x% c/ d
            row=scan_row;
5 w8 ?1 }% {* S4 V/ ?        }5 T$ U9 e  c! H% A
    }
5 o# F3 y' T: p( z$ H! {  o, D
6 x2 @6 W. \3 ~/ Y0 j8 r+ y
' Q1 J/ c+ \" I& x7 d6 D) M( iEP0的控制传输,把用到的请求处理一下
3 i& ?8 p5 k6 c* _) c- U0 Kchar setup_packet_service(void)
: v' `$ S) R# G: M  x  i{! D5 }0 [" f) b7 X8 r
    if(ep0_std_req->bmRequestType & 0x20)   // class-specific/ n! U) J: M9 ~+ Y$ z$ z+ E7 o
    {
$ B2 _! `; n* E- Q        switch(ep0_std_req->bRequest)# [3 W1 p/ I6 n
        {4 m1 }) U2 B# X
            case REQ_GET_REPORT: break;
) q2 ]7 p" O+ M3 i' X+ R            case REQ_GET_IDLE:* m1 a0 O  d6 j
                USB_PMA[64]=0xfa;   // return 1 byte
9 K5 Q* k/ J5 j" z/ T. ^) @# U                USB_PMA[1]=1;
- f! X) U# e: p1 C! p( N. b                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;5 o: _, E7 k, \9 O
                ep0_state=1;( L+ d5 p1 D' m$ i$ V
                return 1;/ f1 x7 w- j' g1 V9 c! Y8 U* r/ Z
                break;
4 w$ y/ @* q/ N            case REQ_SET_REPORT:! o' {5 x1 ?5 J# Q
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
; `8 \) h  P0 [$ E                ep0_state=2;; Y4 u# F5 Y( L2 x3 W
                return 1;
4 t, D, f" m( g7 w1 |; F9 d9 ~% B' w                break;9 W8 k* N; G, |, v9 z! h
            case REQ_SET_IDLE:
+ U3 C0 i  H) o# o3 u                USB_PMA[1]=0;   // Zero DATA
% v( M& n8 m- x# E                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
: Q/ L6 x' C( C3 m; f- ~2 Z                ep0_state=4;
. C/ Q- e* e- R9 |* J1 O                return 1;
- Y2 ?6 J* z" N- k! X' P. J                break;) ?9 g$ R0 h) q2 D
        }- Q2 H; Y7 c3 I# [4 j1 Q; r/ }/ `+ Y" Y
        return 0;) A( I2 p/ y$ ~8 }
    }* Z1 k9 M5 k3 X1 H. {, f6 ~7 X
    else    // standard
& w6 r3 O7 f7 k$ o    {
; v7 [3 x* a) I        switch(ep0_std_req->bRequest)7 `; M; t: u# p3 \* r
        {) v  i: z7 E. t! l' P2 S
            case REQ_GET_DESCRIPTOR:) Y! B+ B' G; j9 w  i
                return descriptor_service();, j. ^  V* g7 [5 C
                break;" G, [/ _- ?; t( }  n, q& Y
            case REQ_SET_ADDRESS:, N$ B* j# {$ O) J  E  W0 R+ G+ _
                if(ep0_std_req->bmRequestType!=0x00)9 J+ B  c+ o, x0 ?0 c
                    return 0;/ e/ ]0 M* |# F+ J' u9 M
                usb_address=ep0_std_req->wValue;
; p/ ?; T% N- J" \4 T3 L: n% }4 a                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;5 `/ c: z8 H0 e
                USB_PMA[1]=0;       // Zero length DATA0
0 O; ^. y2 X. y+ S+ Z1 o                ep0_state=4;    // No Data phase
' ]; m; e; q& o1 _- e7 C                return 1;
$ A4 j2 }9 F2 E- G2 ?# ~8 }: C            case REQ_SET_CONFIGURATION:
0 X' v( ~  i/ [# F3 k- b# |                if(ep0_std_req->bmRequestType!=0x00)
" L4 ~+ S) D( L/ Z                    return 0;
# Y3 A/ A# e0 L3 l2 u6 `! C                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;+ y2 I' k' u) T$ }. m
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;' r" j8 R8 e( v3 G0 _
                USB_PMA[1]=0;   // Zero DATA
. K+ \! r! B. J: P* d                ep0_state=4;    // No DATA phase, x/ A& O, b6 [, u) K
                return 1;, Y- X7 K/ R! d) `; l1 Y
            default: return 0;
/ g+ \" L2 e8 k2 Q3 b        }
9 F' x6 u- O& U8 I/ r  }    }; X' s- f; S; u* T. W! l
}% T& i9 ~3 V' R* ?* y# P
% m; V2 |( ^' O) }
' @" d- y  R8 ^0 C
! c# X& r6 j, p/ ?& c% T; _
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的: x8 \5 k6 N% w+ t3 C' e
char descriptor_service(void)) ?' r8 \1 h4 ]) l6 q0 o  p! ]
{0 e9 ]8 V4 Q/ ~9 p0 b
    switch((ep0_std_req->wValue)>>8)
" c, J  Z1 A: f# B5 U/ u! X    {
! N! ^% A+ ?6 M: w8 E( B; h        case DESC_TYPE_DEVICE:
1 D& R4 f. e8 P! t3 U            return ep0_preparedata(&DevDesc, sizeof(DevDesc));
- M5 Z; i% t3 x# G/ E6 \+ u            break;& C( f  H/ o( }
        case DESC_TYPE_CONFIG:. t; H  m9 ?; C) K9 _
            if(sizeof(ConfigDescData)>ep0_std_req->wLength)
5 O' H  |5 M1 g/ T                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);. o6 K+ x, ~1 M+ }  @9 g
            else1 G" {1 V1 r0 w" B( i* F
                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));
' g5 j7 N. u. A; n7 t4 O            break;* K( k# W" c" ~6 N' n( S
        case DESC_TYPE_STRING:7 p+ v1 `, Z5 Q' `8 ?; w, ]
            switch(ep0_std_req->wValue &0xff)
% H! J! W- j) J            {
7 r" O7 M  c4 p+ |; m                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));" e" g! p0 ?; T+ B! v
                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));2 G# `/ z7 q0 p
                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));& Y' D5 H1 V5 x' ?& F( L8 o. x
                default: return 0;
1 [7 T, J7 ~7 D! D% v" I  Z4 k            }6 {! F, i% U( e, k! `
            break;" N& d, v( X, m" J% j* O
        case REPORT_DESC_TYPE:9 a; ~; Y( d0 i
            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));
' c; C4 h, t1 f, t        default:1 Q& v* ?' ~% N8 A
            return 0;  a0 c/ L6 t+ f" a) _9 s
    }
/ X) v2 P4 K+ x+ ]2 o0 n/ E7 N/ u}. W, u9 L" Z, U" f* Z" A
下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.3 k+ i. Y& n7 L" f/ N
void TIM6_DAC_IRQHandler(void)+ C* j7 t4 p9 T6 W' A
{6 _  O7 k5 B" }1 `' G
    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);
/ k$ ]# h6 a# M" `, A3 m1 t
' O  x) Q# {0 M1 j+ `8 \

9 M7 ?9 E  U; ~- }( S9 ^    TIM6->SR &= ~TIM_SR_UIF;
6 }8 r6 @0 K2 C9 ^) }7 d% z: F    prev_key_state[scan_row]=key_state[scan_row];' _3 e- ?( p2 f
    key_state[scan_row]= *PA_IDR;   // update key states
) T! ?0 ^+ `) W/ U/ L" @    switch(scan_row)* r3 f, [3 ?2 l8 h- U& F+ f
    {& o% N5 Z5 K. o( b# X
        case 13: // next row PB140 q% T" |8 H5 j0 n7 d
                GPIOC->MODER = GPIOC_DEFAULT;* ^$ K- p+ L8 B2 ^+ K! i9 H
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;
2 P. W2 t) S0 p" {                break;% f+ ?/ K3 ]: u& [% x8 c
        case  0: // next row PB15
8 }! K  b8 h' w, ]                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
. P) j$ j6 E2 T& Q4 u( G, `                break;
5 U; t2 D' S, m5 w        case  1: // next row PB3
+ M8 L- A' L5 J# S                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;
, H/ }8 H- y( Z4 ]                break;
% k0 q5 X9 M" R+ Y        case  2: // next row PB4" d% I# \" r4 K
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
3 z7 k, N3 S1 I                break;
- Y- {, B# ?5 C- U0 y3 \        case  3: // next row PB5- D7 d  M/ M4 l& H4 u  D3 c1 L
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;8 p& }+ p( u: {1 J7 Y
                break;
& H# B* T( _5 ?5 q4 R  t( u. {/ J$ J0 |        case  4: // next row PB60 P+ |. f  L9 |
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;
. |) T$ Q/ z. a4 Z& M, W( ?  d5 A: |& G                break;
! J9 m, S# b; K8 j5 W9 o; _        case  5: // next row PB7
7 l5 K0 a; `9 h0 r4 i                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;! T2 c7 c5 e- E
                break;, k$ \0 e& j4 P
        case  6: // next row PB87 u+ }* z8 z. q0 K' X+ ]% e7 M
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;
) U( e: s- ~- R/ R6 C5 ~                break;
! {# x* @2 R, b) T        case  7: // next row PB94 O3 h0 e' J% w7 g
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;/ A; f9 ?) T7 @" x! _
                break;
$ S: @/ b/ N& ~. F- }4 f  A% g        case  8: // next row PA8
- {( Z: ^4 Y2 E7 o' ]. Q                GPIOB->MODER = GPIOB_DEFAULT;
; E% C, i# O8 k9 D) \. M/ e8 G( |* K+ {                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
$ e% k2 w; O" [, S# f                break;8 ~5 o0 d9 g  G* H$ I, J
        case  9: // next row PA94 h* L7 J3 @! d* ?, Y
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;+ X0 Y& T9 M! L; x7 a4 b
                break;
4 i; c" t. t) |) w" M. y& _        case 10: // next row PA10
3 o+ w4 T8 ?& ~  k                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
: |: f1 q3 D: |: [. l5 W9 j                break;
; F3 ]9 S' |7 r1 D        case 11: // next row PA152 Y5 r* R% k( U6 d4 K& Q8 D
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;
! N, h2 k) v' r                break;
; B4 f0 Z8 w/ T: S. u7 w        case 12: // next row PC13
) r) O0 B' A( G+ Y8 ^0 E5 }                GPIOA->MODER = GPIOA_DEFAULT;, v/ \0 T" Q8 ]% A) A5 M- W7 |, [# i
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;; P6 C/ ?7 L5 Y* A% a
                break;
* `# W% k7 Q9 ~1 A4 r3 I' ?- x  `    }7 W& S' F% j7 Q1 k' {# s
    if(scan_row<13)
- k- _1 b' ?% I1 O5 M6 n        scan_row++;
& g  W/ Q9 h9 s* P    else: i( a& J1 {3 k2 O' ~' w! y' f8 R
        scan_row=0;+ ?. U% b% F' G3 [; f- T9 O6 ?
}
. Z: V# Q; \3 h3 T% X
1 _$ Q; p8 {$ j2 S! g- \7 D9 E' k" ~' W
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑 2 Y  \. Z' g1 r. J/ d0 F  m

: V) A4 }3 ^( C/ o( {扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。/ ^" |; q) f  {. v# T7 h, S
( H/ A' K3 C8 d2 K6 E) }
要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:

1 o/ ^$ O. l: q# ]
3 x$ N0 b6 G/ k: E% P0 k& Q8 Y
! u) g& U; ^( [' q. }. lconst char hid_keymap_qwerty[14][8]={
& K% U$ N! S0 s5 l) n8 }    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},
1 P' B0 n/ w/ W7 }/ F7 D7 J    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},5 @0 R# @" e, m  E
    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},# N# j( V8 F; f7 F' v  H7 q9 Y1 P
    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},
& }/ O1 U$ ?, k$ J+ B+ `. t    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},! C$ I; T/ d+ x( c/ S* k
    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},
  L# i/ a" {1 V1 W. O8 y4 G3 s0 @    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
" Q8 s4 s' T& x    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},: t, q& T* J, `/ ]) @8 d" l7 K
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
8 E1 d. P- n5 E/ l% E' W2 o( c( l! U    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},# F& a4 E3 d2 L6 z. E: U) {+ b
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},( b+ E) q* E/ z8 @
    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},
% `( g5 }3 o# F* Q* E    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
* Y  G2 @6 F- C! D0 [    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}- x. X- j  d3 ]( s% J
};8 e5 K- B2 T* U

2 R& k  g6 s$ Y- G+ H

/ p6 A: n* L* B; g) d( Q) g4 [const char hid_keymap_dvorak[14][8]={
+ `5 b$ Z$ ~  @6 `6 K  _( x    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
- [- \3 N- k3 b  G  y! b$ `    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
* Z/ D# `. r7 j& _; X& \1 D0 l    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},6 F$ ?# X- g$ }$ X" U
    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
: J6 L* f  }  {7 S. I7 |$ n( F    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},
; I6 ^6 W% j1 D) c: b    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
; H; ~3 g' @, K& K. M    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},
  q# R4 r9 q  s5 b. P. B; n    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},( \/ q/ w) F, R3 E( S) w
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},& J2 k8 l0 W9 b) u6 c% d1 u
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},2 t- w6 `. |2 ^% \5 h
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
% {8 X6 h4 H! H( }/ {    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},
- \% t* ^4 i, |& E( ?+ u6 m    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},
# e8 R. w5 g& p# E    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}& P; d$ j: Z( U# v
};' R' D7 j5 l0 K

2 H( |  i$ s* h+ I
: Y, _0 T8 n: n+ j# Q上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
( Q" N. Y! N8 i
+ s3 o2 e/ J" r4 o; ]! G( tHID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:

" `$ {1 F0 a+ Z. b$ x, h- |4 K7 ^3 j* D/ R  Q+ `2 S
void update_key_matrix(char row, char col, char onoff)
! \: _+ m+ Y' ~* g1 q* H{  S1 G) C$ M- j$ n
    static uint16_t hid_report[4]={0,0,0,0};( x# O, R0 o, k
    static char (*hid_keymap)[8]=hid_keymap_dvorak;& P* T/ a. ?5 [) G
4 @1 T: @4 C8 W2 w" t, ?

9 M1 f( c$ Y: Y  J% W    unsigned char key=hid_keymap[row][col];. O2 [; l: l0 G% p' k& U
    unsigned char *report =(unsigned char *)hid_report;
/ K: `3 w$ f0 d/ s' ^0 W    char i;
: m" p, \  @+ X
  |3 N* Q5 R) N. C

  _9 n; F0 d) E3 N3 b, I. q    if(key==HK_MODE)
3 o2 W5 s7 [, Z, T. c$ X. [    {5 c  B# s. G# k% P( z$ U9 R) g
        if(!onoff)
3 W2 `+ A; S' f& s7 u        {- J( ?7 B( q' m* M
            if(hid_keymap==hid_keymap_dvorak)) ~# t/ {- i; @; `
            {
% k: m& a% s5 ~2 D2 h                hid_keymap=hid_keymap_qwerty;
, W( ^1 L  h& ]                GPIOB->BSRR = (1<<2);  U" v. [. V" ~' S
            }0 m# P8 x, S& |2 A
            else
0 |9 g1 t  h$ }1 s            {
  P0 p! n( X: E" X" F- w                hid_keymap=hid_keymap_dvorak;
- [$ a! Z2 |: {- V8 N% m+ L                GPIOB->BRR = (1<<2);
! c$ v* N! B% p3 W8 x            }
2 l$ b& S1 f3 `, O        }
# C& X" C" `  n9 d; e        return;7 X: z( ~0 z0 h: i% A  @) i; w
    }7 }1 v, V; W+ @1 G! n; @

1 L% r, ?, {* F$ g" G, G6 l
% a+ y3 @) C9 ^+ ?
    if(key>=0x80)   // Alt, Ctrl, Shift
3 L$ ?: z. X' f6 r' F1 |4 y( v( u4 @" w    {) h4 @( F/ b0 _% V( k1 v2 U
        uint8_t bitset = 1<<(key&7);# H- H2 B! T4 W; K/ v& k* H/ F- @0 A
        if(onoff)   // non-zero is key up; {4 V( e  \- \. k4 m4 \1 |
            report[0] &= (~bitset);
9 l! ~+ y6 I2 n0 b0 }/ R9 w        else4 U, x, I! d% n: |) f& i3 o( K. t
            report[0] |= bitset;! o$ f3 E1 H4 {0 ~- N
    }
! h) C+ Z5 U) K. P6 n5 V    else6 t. ]* ~# R2 M) z2 ]9 T) m" E" q
    {$ g) x& C1 S5 P$ N. h$ E
        if(onoff)   // non-zero is key up9 ?; R: J- J. U# Z& z; }" b4 \7 Q9 `
        {$ t  y% H! }6 ]) X& J* B
            for(i=2;i<8;i++)
* j0 ]$ n9 b7 @; A4 i            {
- @1 S- A  ^. Y% `8 K- A6 M+ t                if(report==key)( \( d9 `1 f7 d/ Y. F
                {
/ Y6 G) Y/ @: a                    report=0;6 I" e2 q3 m% L, ?. e* |
                    break;
+ C7 c- o. A0 ~+ X                }
# N( k# Z. N6 G9 R( u/ P2 U            }
7 T8 K! E3 x( S2 ~* @1 d        }/ r3 ~' \5 p* S- W$ E0 j  R
        else
; d/ K7 C( u2 U1 U5 s' Q# y        {; ?& F0 T2 |' u: V6 c5 W
            for(i=2;i<8;i++)) e: h9 r5 t7 g0 [
            {
$ Z* S! c+ i$ r2 Q                if(report==key), T; g) \8 U: q/ t( g
                    break;+ i8 I) R7 N' p5 w9 @6 h
                if(report==0)
3 z- e% s) e0 t( p  K; Y                {
5 U2 |3 o+ w9 l' s' j) [) J                    report=key;
1 h; H# J3 I9 U                    break;1 D8 u( H# _* o( o! p
                }
7 _( K6 I1 K4 A  h% a            }0 A4 B+ i" }9 w: u( P
        }2 V0 g- q3 i' U( M( o2 x2 E' v7 @
    }
. ^7 `* {3 H. m. C/ c  s    for(i=0;i<4;i++)
! o7 D, h! _1 C0 ?; w3 R4 b0 }9 M        USB_PMA[192+i]=hid_report;
9 s% N. {& O0 ]4 y  ]. O    USB_PMA[5]=8;   //COUNT1_TX/ S; I" C# A0 {" O4 S+ S. k/ N
    if(ep1_wait==0), J. f1 Y! M, J
    {
1 g, z9 l# L8 p; I2 |) C2 n        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;
0 m% j) `7 M0 X4 G: V9 w        ep1_wait=1;
3 b8 u7 L9 f# \6 U( z6 {    }
2 n' |! |) p  i" F* C  ?. `}4 `9 \' i( P1 x- S
3 B/ y  L2 X8 m1 {8 p$ f# e& u
  x* i! |) x0 j
完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。
3 w% V2 v5 h. q1 \ keyboard.zip (8.7 KB, 下载次数: 6209) * o/ u# L) X% t) @+ n

% Z: u' g3 b- F8 t$ z
+ }! Z9 o/ R7 P6 l* J* c/ e, W
" ^( z5 A+ Q- `6 Z4 R; K* @+ t, h& E5 [. e9 c' ~! O: a
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘; E+ J7 e* e- D1 r
不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48
* m1 l# X+ y/ h2 F5 Q刚开始我以为要把打字机改造成电脑键盘! R/ H& `$ N1 J! z% \+ z  A
不过楼主也很厉害!

, _' W  @5 r9 T: K+ A: d* v哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
7 O- r& i8 ^3 O% B  I
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-7-12 11:59 , Processed in 0.171412 second(s), 28 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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