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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

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

. z4 f$ Q& g+ Q$ x

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

! p6 m/ }7 x. l5 W$ X: ^1 J4 H 005836yvs0wvovwsssgd3o.png.thumb.jpg
, g" z( Z% z8 S- LDvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。
( }' Y& }. ^' U1 o, M
6 L9 l1 V+ C- e+ ^" Q我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。" ?) S' q4 t& s2 K+ G3 `

* Q0 V* ~; L9 u& G到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。
- B( a+ R9 Y$ S& a
( U: s: z7 a7 |6 S7 v: f机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:
" N: T( E- s+ ^6 G
123206vx24rfxfp2dfrpxg.jpg.thumb.jpg 2 ?( D# y8 X% J9 L2 S) ^0 k
----------------------------------------------------  分割线 ----------------------------------------------------------
2 S9 v* h, j0 ]0 w1 D; K
8 L* V, m2 V" x3 F; C先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

2 O& S2 ~& [' ~. G 020011osionbunl4ui44vi.jpg.thumb.jpg 5 v# y# |5 Z$ {, I/ M1 n4 @) f
轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。5 N  L! f1 t: C0 b; ^7 i
020017j8ycmnv7788bqv52.jpg.thumb.jpg / U; {/ }$ R4 W! O) {  S- Y' y
特写,80C498 R( [8 o' T9 Q
021040oujzuvtut6iujtvz.jpg.thumb.jpg 9 b2 a4 r# g1 j/ V4 S  `: y' K
LED部分,使用了一片D触发器锁存指示灯状态.* S/ g5 f* I! p* Y% W& [0 w
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
0 g& ]; J& ~9 y9 y! J暴力破坏,将80C49拆掉# m% v' k% q: X- g
021113e48qq98vyohvhzzh.jpg.thumb.jpg . v! m* M" e1 Q( u
拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。% J6 h8 q1 M, x3 V7 B
021125nc9az6dj33rlds2r.jpg.thumb.jpg ; s; U3 F# f6 D" e: G9 p
焊好元件后的板子,准备替换80C49; G( x, A' e4 w" }+ F" D# ?8 {
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg
6 I& m  i5 u4 \" D+ w用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。
1 T: R, g; j+ I- l3 J 021104shifhnrqbr3o5nlo.jpg.thumb.jpg ( M- o+ |$ J( W
这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.
/ J8 i+ _. }0 K 022003ym1p9u4ug40280uu.jpg.thumb.jpg ' P  [' i- R4 f6 i. J
开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。
- T: i4 ~, V6 y0 ~! p- ~1 B# u 023313kt141q9qajtol7ma.jpg.thumb.jpg * G; m5 l. A# y5 a3 {
我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。
# _# ~% Q/ S: N# Q 023322nt7l5xb3ltttkltt.jpg.thumb.jpg
- Q2 F' h& Q7 ?6 u+ p& N0 \; F
主键区键帽就位4 U: H$ @$ e% I  q( k2 n
023331hin88e8wkrwzwikx.jpg.thumb.jpg
% y7 _# @/ e1 h0 m: v  F编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。
4 k9 J0 V* k* K0 K0 h" z 023336wjzlgopugg1jyy79.jpg.thumb.jpg
1 b3 V" z1 V( N) P# J# \& I最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
$ y6 G0 @5 E9 ?3 E 023341sffu4j3g2323h6fl.jpg.thumb.jpg
. ?2 t9 s9 i5 R

& ]. M$ B( j" L* }----------------------------------------------------- 分割线 --------------------------------------------------( J( H: ]# L! c3 q$ X3 ~4 h0 K  I
$ z  \5 Q3 O5 T+ W$ {" Z

4 c' h, R. ~5 L
2 n$ S. V" U* U3 C6 K6 D+ q! d* ~7 Y9 Q  D1 F' i  {! n

) [- K2 x# V9 x- {. D6 p# s

0 W4 M/ l5 H5 s) G* b. r9 F
* U4 }/ c  n# Q- ^# W& _1 a  W. r2 N9 g/ I/ ~

: b4 G# }1 X- a; ~# v3 V; K  w- P* N, }+ N) W

2 t3 N- E) k: }6 B' [) l7 S4 f% i
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。. U3 a, M* n; G% M
80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:
4 ~# z( a( T# q+ Z* Y$ G 104025nzibm2rmiomhyirm.png.thumb.jpg : a& r  ^, v2 O% D( a9 s5 a! T3 T

" y" k* |- ~1 C其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)
% G, X, ]6 t, n* [6 `+ N) A7 i 105004zkrez5houvkkznko.jpg.thumb.jpg
& d5 ?! V; |: }: Q& {+ V

" T. M& [% F6 }& a: F8 k! c扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。
& m. z1 _7 b) Q1 r
  G! N4 q. C; i: t这是我设计的电路图:8 c" E& L& y7 I! u3 A. l
110344ej2z2oo2rflo7oe7.png.thumb.jpg % P$ B& A' i4 M
4 V# Z6 T/ [1 v9 [8 v& u% {- [$ a, b
PCB Layout:; g9 X' ^/ q$ z) W( o
110847jjbjvt34vwt3v5bb.png.thumb.jpg
1 O& L8 ^; [! [: y* K2 a$ I; @5 h

+ B9 c4 w4 ]8 `" Y不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O.
! m6 A' |4 g/ U$ m
8 h' G4 Y9 u+ l* B' U

: e6 @+ H& i5 }  g/ q( D
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑
% Q& A3 J( \& c7 o" u9 ]) b5 ^: `9 L  F6 e: D0 `0 W
软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。
2 Y, ^& _4 X$ R6 [/ g* m- Y! |4 ~- \6 p) Y" _
总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:7 f3 y% _" @7 b6 B+ Q
113818pmrfsb6z0byt6t06.png.thumb.jpg " Q0 z" X2 I; J& {
  q  t! [9 v& x1 z
其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)% h) r4 ]' y0 b3 ~

4 [) {; v# t0 {USB的中断ISR,bare metal哦, ?8 C" ~. z% h0 x

, A) {& V* r2 G" Gvoid USB_IRQHandler(void)& {7 E( w. R* r; d& `
{
. W, R- z( L1 @2 u  f; X: W9 ]3 s    if(USB->ISTR & USB_ISTR_CTR)
' p: g2 c/ U, e( t" t) w' `    {2 C" F: O$ f/ U' B! B6 R
        if((USB->ISTR & 0x0f)==0)   // EP_ID==0: ~7 L' ~4 m+ P6 Z2 S  L+ E
        {
9 d: k( ~3 y2 B) v0 D# u            switch(ep0_state)
" u/ t( y6 O- g* U            {
: o1 w; f7 x/ M* z                case 0: ep0_state |= 0x80;6 x% u, F+ w$ h* v( e; P
                        break;
( Z$ S$ X2 z: O: I$ H                case 1: if(USB->EP0R & USB_EP_CTR_TX)1 L/ l* t! ~! G. {" Z4 Z1 n
                        {( N% }& i+ K2 t4 o
                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
$ {  B0 g" g; n0 @( ?9 ~, S                            ep0_state=3;
- \2 s9 F* w2 I! g                            return;- w+ }( C- G( Y  j
                        }
4 i" s/ n! l. X2 x+ s$ F' V2 K                        else$ |. u. P" |/ S" _3 O% f
                            ep0_state=0;
4 Q& g) w6 W) o. l! x; p- ~                        break;
! k# f  D; v6 E" \                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet
2 b( ]5 J# L' m- H4 V/ Y" v                            ep0_state |= 0x80;
- a( D# [& p4 d1 u+ G6 x                        else( K9 u2 F7 T5 B4 T
                            ep0_state=0;
1 ^# a4 e3 O, i/ _; R8 f                        break;5 D$ o7 B: @2 [8 W8 P9 X
                case 3: ep0_state=0;2 N0 j! j. u% }9 ~1 q- N3 I0 I8 ]: p
                        break;( F! I# M: ?5 @/ T+ X
                case 4: ep0_state=0;
% o; _: o: n8 V( Y# L9 U" u                        break;
& h; Z) [' |" }& r0 Q                default:ep0_state=0;1 i2 R/ V9 D0 k% H: c, q2 t, d
                        break;
" V- d# B$ Q3 e; O0 {+ z            }
; T% J( k" s2 h0 {8 V            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
, C7 d. I5 I; X            return;
/ A6 m% |/ [# M! L: L        }$ q* d: t: P2 {* \8 U# O  `( `
        else    // EP_ID can be 1, l9 f% {+ l; C$ n- \) K& Y
        {
9 m1 p! ^5 a- C6 K6 g# l            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag
8 z  p% g; o9 {" L$ g0 l/ o5 T+ Y            ep1_wait=0;, V3 E# t! H+ _8 F2 X7 z; |
            return;1 K6 Z' R" r) u  d. r
        }6 t0 p6 f3 ^$ R0 u/ n' m$ T
    }( o1 v' H% q% c# Z7 \
    if(USB->ISTR & USB_ISTR_PMAOVR): h' o2 [) S7 V" I
    {9 o- X6 C$ l6 l
        USB->ISTR = ~USB_ISTR_PMAOVR;3 M* W2 g! Q8 |
    }
  m; m# T9 w: N7 ~# S    if(USB->ISTR & USB_ISTR_ERR)
7 a, w, R. f/ s) V# ]/ V- `) p    {' M5 r$ X$ B' d/ d( X/ y
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear
: w/ ~( T, ~' n( Y" ~    }) G/ M" c' w/ P' d, N5 L4 v
    if(USB->ISTR & USB_ISTR_WKUP)
9 }$ t- I, j/ e) A- N) _, Q$ Z    {
. e6 z) D5 `6 X: p) k2 e  _        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
7 _- y1 _9 \1 `: j6 r' F$ E        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear
& A1 z- b0 A+ X9 H2 A6 G    }
8 N9 E0 y$ \3 B3 D7 j6 K    if(USB->ISTR & USB_ISTR_SUSP)- ?. s6 u# l! w% S" Y9 Y
    {
/ u, V) l6 a' v6 I2 J5 u: C        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend# y" H, P& N+ _
        USB->CNTR |= USB_CNTR_LPMODE;   // low power0 s" u. C$ m8 p
        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear
, B1 e2 a6 z) I. w- ^    }) A% h# k3 e* g
    if(USB->ISTR & USB_ISTR_RESET)
* O- A) Z' @  ^# D+ @. `' k1 K2 ~    {
. b% ?. f% D) Z% S' l2 s4 I$ z' W        USB->BTABLE = 0;    // buffer table at bottom of PMA$ r0 n+ m8 y& M7 q. F6 J
        USB_PMA[0]=128; //ADDR0_TX
* B6 [7 f3 J! P& o4 a        USB_PMA[1]=0;   //COUNT0_TX
" x* F5 _0 k, [        USB_PMA[2]=256; //ADDR0_RX
( V3 n' V; ]; f; T% ~        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes
: Z: c3 {+ e0 J+ n        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;1 }0 U" G1 U% a3 D! E1 m4 \( z
        ep0_state=0;: ^6 q! m9 j4 ~% C$ e" t
        USB_PMA[4]=384; //ADDR1_TX; B' B9 z; X! R) X
        USB_PMA[5]=0;   //COUNT1_TX
: G+ }1 \  A$ q        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type
9 z/ n/ Y; y, x) t( L9 o, d        ep1_wait=0;
) A! k/ O/ L8 B, J2 J* T: J        USB->DADDR = USB_DADDR_EF;      // enable function
8 [7 f: f+ K  D  j2 a        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear
. P7 B8 P* E; i* ?    }
* d( r1 t: n  l' {8 r% ?# x    if(USB->ISTR & USB_ISTR_SOF)
, m7 E  Q* A( j' f' K* ]    {
4 n- B' g! {2 z9 U, ^0 z        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear0 d# I6 Q( `7 i# z( z* u
    }% |2 V5 N. f1 D/ t' ^" `& G$ `2 r
}, C' y1 ~- g& m( H% ?0 z

6 G: H. D0 F% m8 t" A( S
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。* H/ b6 E9 m; T/ N, \

4 S: u  G! l) \, ~8 K主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。/ `# z  \& _. w6 v! N: J9 k
' u* B# D2 V1 L' [
    while(1)$ H* _9 F0 F% |" q: x1 A! j/ E8 G
    {6 C* a8 b  g  s
        static char row=0;
6 @2 O4 o- o* b, X% ]        __WFI();
! S! G' Z# y: q" A) Z9 p1 Y, w0 ^        if(ep0_state & 0x80)    // request data processing: @. {, `9 G/ Q: H6 X
        {- B* B$ w# h' ]; e* B% O0 Z
            if(ep0_state==0x80) // SETUP phase
& W9 o. [( f' D, e" e8 F; K            {
# Z5 [2 v# b% D" P                if(!setup_packet_service())
3 e3 v; O5 _  Z9 N; y) X1 z                {  F* T3 c1 V9 b+ ~2 g! O
                    ep0_state=0;
8 D; D( j0 j/ n9 @: u$ g                    // not supported1 l5 \% J5 N! o% ^3 p. T+ V- d) ]$ U
                }+ I4 V) q6 ^0 _+ G4 }
                // ep0_state should be set to 1 or 2, if processed
; y3 a4 i& \! C, b            }
8 X( J, H4 ^* [6 d7 @' e2 A8 H" R+ h            else    // OUT phase3 P2 I5 ^5 C5 d5 J' u
            {0 c$ C1 ~  Y- P1 u9 ~/ u& a5 g
                // process data/ m5 i( S7 C& G+ Q3 x* y
                show_LED(*(uint8_t *)(USB_PMA+128));
" s: [# f* d- Y- b% U                ep0_state=4;( {( z$ I1 t1 P6 H  P4 z
                USB_PMA[1]=0;       // Zero length DATA0: Q% \6 Y9 i/ |2 V; L; N
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
. @, s6 Z" M1 t) }* \; T            }
5 ]+ g$ u& k: z  b% E3 h3 r        }
" K3 \7 u) p" a. f4 _! B1 T        else- n6 r$ n% U* F4 y+ l; d3 c% H
        {5 _$ [. U, j) y. x2 z1 v' j, x
            if(usb_address && ep0_state==0)
3 K0 h+ _, \$ U            {
* a! n2 {7 z" _/ A% Z                USB->DADDR = USB_DADDR_EF|usb_address;; A4 F" H7 V- V! u. `/ F4 `# I
                usb_address=0;; s0 _( T" ~7 Y: R3 W- M6 s
            }! d% z. Z! K& B( F
        }
% g# T) M: z+ v, E$ C, Y7 r  e5 }        if(row!=scan_row)   // new scan line9 S# {: L" y+ L. f, e0 R# `
        {; K/ A  r! v! E6 |. @# i: z
            if(key_state[row]!=prev_key_state[row])$ b! n) r5 T  n5 f1 P, Q
            {
" y2 u( H9 i6 t8 A  u                uint8_t test=0x80;
7 P. @+ S; l8 n& t1 K) [3 G                uint8_t diff=key_state[row]^prev_key_state[row];6 z" ^: ^  \9 v3 S- g* F9 N
                for(i=0;i<8;i++)
& W# V. s, U& N1 \8 n                {
# _) z) q! l9 q3 K6 g( ^4 f# M                    if(diff & test)
& I' r' G; `$ l/ K2 A; B* Y1 V                        update_key_matrix(row,i,key_state[row]&test);
9 F& U' }; @5 f. ?1 g                    test>>=1;
) ?/ i, q8 n: C6 n                }
5 [3 K1 j6 i4 D% V            }& D* J6 g! ^% U7 J5 M7 R
            row=scan_row;/ E* v0 c2 r- W# `9 w  a6 T
        }
! V( [/ N& s' Z    }
3 @1 N7 t* c6 S9 T: l8 }
; ^- q& J  @' X# R3 B, w" X% o+ v3 n
EP0的控制传输,把用到的请求处理一下: H: c6 L/ _1 `3 J& G' X5 N- ~0 n
char setup_packet_service(void)
7 P% ~: x! A" F" h1 w/ M$ W{
# b. _; Z7 g, r/ R% e) |    if(ep0_std_req->bmRequestType & 0x20)   // class-specific
" }6 A3 i. @/ F# B    {
/ c1 S/ v' W9 Q  Y( A) |        switch(ep0_std_req->bRequest)
$ m7 i7 [3 K( l- q        {
8 U: R4 R" E5 r# Q+ \, X            case REQ_GET_REPORT: break;
+ b) t% d: A4 N) t            case REQ_GET_IDLE:; {" o* w% h3 g2 A
                USB_PMA[64]=0xfa;   // return 1 byte2 I& h! |. Y: f( S
                USB_PMA[1]=1;, u- S: f# R" i5 e- @) w8 W
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;* ?. w' G4 q$ S3 Y1 j& V4 `
                ep0_state=1;/ H% q) _7 h# H; K2 k# k8 Q/ f9 }
                return 1;
8 N8 p; R1 c' }' b                break;% I$ J8 ?4 f1 |( o1 ~
            case REQ_SET_REPORT:
  \- ?# }2 q- C7 V: R" i                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;* H% Q1 T5 r* }& S6 b+ v/ W
                ep0_state=2;
) g: N% _" F7 V8 Q9 T6 w                return 1;7 e) Q/ ]' f% e3 v) Y
                break;
' `" i' J! Z0 |" g$ q7 h) {            case REQ_SET_IDLE:$ n/ O+ Q( v. ~  R
                USB_PMA[1]=0;   // Zero DATA
2 N4 L9 m% c% `" p% L5 A                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
" u; h! p$ E: Y                ep0_state=4;
8 Y0 g/ b) q4 U( y- |                return 1;' m* B( b1 K# b) q# X( s
                break;
( W; w4 u& M% [  M0 _3 o( S' `        }
! |9 x! P1 N6 o& E: Z        return 0;
2 H, z8 j4 I3 @- B( x0 w% d. ?8 z    }
' h4 Z+ K# f8 p    else    // standard
2 ^* ?& Z( v) C5 o' }    {
, {1 N. z- s! E7 w) F        switch(ep0_std_req->bRequest)
* C; {! G. F: x: T        {9 @5 ?2 B1 b( m3 ^# ^: z
            case REQ_GET_DESCRIPTOR:* R0 U. u1 M0 y7 V- `, T
                return descriptor_service();5 _+ i4 i1 g( h) U4 i
                break;
$ W! G2 k# K9 x  Y4 M1 A) `' d            case REQ_SET_ADDRESS:; [/ k) A' T7 }( W2 w, v4 @
                if(ep0_std_req->bmRequestType!=0x00)$ Y2 k. ?$ p3 k" R0 j
                    return 0;" U3 R, U0 m; \; a5 V* i: _3 G
                usb_address=ep0_std_req->wValue;$ |- S4 U8 o2 k) M
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;5 j! x3 s# r; Y7 |) x
                USB_PMA[1]=0;       // Zero length DATA0  ]5 l' }$ ~+ L. j+ P- w; o
                ep0_state=4;    // No Data phase
3 [5 Q" P- q7 M+ h: l                return 1;
8 j4 d8 p& v3 ^/ w            case REQ_SET_CONFIGURATION:" T4 {7 E* n7 G+ Q, ~1 B3 }
                if(ep0_std_req->bmRequestType!=0x00)% l5 K  v3 U7 [0 V% R* X7 P
                    return 0;; U! k( c0 g; W# S! F
                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
" l4 B2 O) Q5 d, Z/ `                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
% S1 C. j1 J$ \; }9 P- M2 W( E                USB_PMA[1]=0;   // Zero DATA
# [' Z+ T& f  e/ k, z  A                ep0_state=4;    // No DATA phase! b' B" J) ?; }. l6 k) u
                return 1;
* Y- B9 T+ y$ B. m" i( {2 H# K            default: return 0;
0 Q( E* ]3 L4 e; z        }
0 J. t+ A6 B0 ?8 |% }4 p    }
9 j% ?7 [3 ~9 g! u- K! V! X}
2 ]; z8 ~9 z$ b+ D8 A, i
! v1 v$ z( b7 h) g2 K
. x# K8 P' c) Q
3 i" `% L: k% g
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的
1 g* e' ]1 I5 Y3 [/ R# Bchar descriptor_service(void)
( ?  ~8 v5 c4 h% {; R{. z, i$ Y" H" @$ V
    switch((ep0_std_req->wValue)>>8)  f( R' Q  b1 a! a: r
    {
* L% r- A4 C4 p4 j$ g        case DESC_TYPE_DEVICE:
( D  t/ K( K5 G' p$ b2 w+ ?            return ep0_preparedata(&DevDesc, sizeof(DevDesc));
2 d1 S5 i6 ?! E1 y  L& ?            break;& ^9 y+ D2 B" F9 k0 v
        case DESC_TYPE_CONFIG:7 y) f: L- M; z: d1 m/ ?: J9 Z$ o
            if(sizeof(ConfigDescData)>ep0_std_req->wLength)9 i. l2 ~8 b. F! E1 [: |/ u+ a6 r$ [
                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);
$ r4 I. p7 B5 `- w& i; n) D  Y            else8 n, F/ I4 U3 ~* `' `+ g
                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));% B# R: R( W& F
            break;
) l  q- j8 |# u: |+ |3 R        case DESC_TYPE_STRING:
# t9 y2 ^. |( h: _8 P6 c6 A            switch(ep0_std_req->wValue &0xff)1 h0 v$ |) F/ T
            {
! M, n, \* ?- ]) T, F1 |5 p                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
  n; q* ?  ~4 G2 M                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));: u9 T$ ?2 Z* i3 I/ m, D6 N/ _
                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));
+ n1 i  A  V8 X8 i( n                default: return 0;9 B6 |: L+ G% r7 c9 d/ p3 S0 X
            }; k$ O+ K5 M% W
            break;$ `( E" `* n# @1 `' r6 d
        case REPORT_DESC_TYPE:
8 X0 S2 J/ Q* |            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));
/ m( L" z4 m- t. @) A( c% O        default:
3 S# [" D% \& s9 I            return 0;. X* G2 _6 d: t2 u, x* C+ H: S) _
    }
! `* l5 Z, T, B/ B% Q}9 u9 p% [6 F8 k7 W
下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.
: Y9 i+ u7 B, j( T& _  F$ J9 `void TIM6_DAC_IRQHandler(void)
) V' ^. y! S. X. F0 V1 q{# y# M5 j% E0 r
    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);9 `! R3 [0 y) B! b$ U
0 n/ v5 i. ^4 Z- ~

; P7 D& {7 |, |& G/ ^. h    TIM6->SR &= ~TIM_SR_UIF;
9 U+ X0 W7 @0 x9 t$ r& H& h    prev_key_state[scan_row]=key_state[scan_row];+ a8 S4 I+ g# {3 Q4 X
    key_state[scan_row]= *PA_IDR;   // update key states
, F& g: t& O; i- Q    switch(scan_row)
( C1 q5 Z% \* B% x' v6 S0 `+ e    {
9 J9 i0 Y' D/ z) S; l8 E0 C7 V        case 13: // next row PB14
- d% l1 ^4 n% y) x6 I                GPIOC->MODER = GPIOC_DEFAULT;
$ p2 {0 n4 a" P                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;
6 r+ ]9 v/ M- x" ~                break;' T% o0 V- N6 ?7 v4 m
        case  0: // next row PB15: d/ ^4 P/ W& S- R
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;0 D( y: L* t) u; g! r+ _* `
                break;$ s! [3 }4 \" n7 j
        case  1: // next row PB3
4 S9 J  s/ o  y/ B3 i' `4 ^                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;/ p3 ^7 [8 _: k; v
                break;
( x; G* Q; [7 }( T& H        case  2: // next row PB4. J3 ]5 w- F- h" E
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
7 Z! A9 R5 \! D                break;5 B9 S/ b" o: }9 v! o1 j
        case  3: // next row PB5
2 \1 g) k( c- C- X4 i, T$ z% {                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;9 H/ H) S$ X$ W+ |( k
                break;
  x! W5 b+ X* F7 c        case  4: // next row PB6
8 W: y, x4 p& A5 Z1 j9 W1 j                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;
& N& c+ v- Y% D5 ~5 ?$ e                break;& R/ {* w  o  T8 N# V" q, k
        case  5: // next row PB7
7 S& P: j% L. Z) h; p' p. a- S                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;
7 I8 h' Q' o# [$ K" x% U+ f: }                break;
1 `7 X) E; G! Y        case  6: // next row PB8
9 R, s" p) U$ e7 i/ ~" f                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;
# \0 V& ?" d( }* @8 ?1 h% [                break;' K; l( u8 x/ `. b2 E/ G6 z7 y
        case  7: // next row PB9' Z. T0 |9 s" X+ k
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;/ V( b! o- A' E7 v2 k4 s
                break;4 C2 m8 v& o/ \( \$ {
        case  8: // next row PA8" X* D* W! S$ n0 y7 B6 O4 O4 M( o
                GPIOB->MODER = GPIOB_DEFAULT;
+ E2 X6 R. i  d6 }9 F/ y                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
' k% Z# c2 a/ [8 n$ e" v6 L                break;: Z" o* P7 J+ U1 R4 `1 N
        case  9: // next row PA91 m7 {% _( x8 C* i, Q/ h: j( C
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;* U: n, n2 I& X8 _. c( r
                break;
/ J: A0 m) Z, i/ F2 m" ~' f8 c        case 10: // next row PA100 P: R, O0 E( `# q7 p2 R7 V' C, J
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
( S, X' I* n. V% z5 p* J1 Q                break;% K$ a2 a& l/ i
        case 11: // next row PA15' S, ^4 j0 k6 c# Q5 f
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;
* M& ~& X/ z( d                break;7 s. _4 l$ A) |0 F7 o$ k# j. r
        case 12: // next row PC13- H3 z! t. A3 p. v/ X1 P' n
                GPIOA->MODER = GPIOA_DEFAULT;9 e6 q8 w$ X8 a% S
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;; F3 \5 }- ]" Q; b
                break;5 C7 M0 R# [7 A
    }
) s) ~* @! ?$ C    if(scan_row<13)
& Y/ J, x0 x' {) l2 Q. C        scan_row++;, Y. k' M. Z' O$ `
    else2 ^; Q2 `6 c5 `2 K; Q3 h0 i$ ]
        scan_row=0;8 A1 h7 s9 [+ T
}
1 @3 Q" @- R2 o- b+ N; z5 m" D6 n, i5 i1 g" M0 S' \5 a1 }( ]+ D

2 `3 {6 b8 J6 @& S8 T# d0 @9 [) S
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑
" c6 M7 l; k. U2 C, X, N- g# L( Q
/ t+ r- g4 A6 S1 J扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。9 _7 F$ E$ ]) g  N" \
3 {. j2 a5 i, \- H. ^
要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:
9 a- @% y! b6 O0 Q# E
( W. e' x3 K8 s7 p7 p0 M4 d

0 @! S1 I: O2 F2 _! s. gconst char hid_keymap_qwerty[14][8]={
* E: |) m# I$ V: S: E    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},, ?' P$ c; Z; W" z
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
/ t1 G3 d4 s- r, L7 j& H! ]2 ?    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},  w4 k) _/ R) b, N
    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},
! E9 R8 ?& i  z) O* X    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},! ^) Z5 t( W' m, z! `4 N* N: \6 W# f# ?
    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},
# @' O6 V' o( N    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
! v+ Y8 X6 H4 i  M* x    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},
: g; h4 ]7 x3 [6 Q    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
+ d: b7 X2 O) ^6 |    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},9 ]: h7 u3 l) |: y$ ^1 ~
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},) E+ _- s. w- ^) x1 F, S
    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},4 p# D% ^% b( s) p& N. I
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
& ^1 w: X& L5 n. z1 b6 a2 \! b    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
. s9 Y  k( r( v8 \% q};
$ d4 i! X' P3 h1 Y9 I
7 f: n  t  Z6 |  e. {4 i
) D8 Q, v9 k& n
const char hid_keymap_dvorak[14][8]={
" M; D8 l4 s- Y8 }0 R( v3 z, `    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
8 c" j$ d/ Q3 c    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},8 N- T8 T1 h0 d3 E' G6 R, \
    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},$ |+ X3 Y" H- B% u, o9 @" J! c
    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},  j6 S4 k9 ]' ?6 F, a
    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},
  m0 g8 ~& g: ~, o: @; p    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
! d3 p% B( o- N* n    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},4 T) H" y9 X' {: B& C
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},
) N! T% }; r' P" U6 m$ m3 h5 x    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
; S. x5 I: d5 B6 s" t5 Y) h    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},4 J, d! L6 k- [) R- \! O, i, O
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
) v6 w' x( g; F$ l( Q" r1 I    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},0 v- g/ ?% E7 `
    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},
2 z$ y  g9 l" P  m7 w% ~8 y    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
# B- J* z& C% N! R};  ?! M: \' d4 E& d) H

$ M/ r# Z5 V2 t1 Z( H# ]$ L" z$ N# k9 D
上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。1 m4 D8 c# h8 Z5 O( K* _
! m8 h; }# X8 W$ D9 t- J7 u
HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:

' d" Q+ ?: \2 o! X( C9 U5 u7 p0 }7 E2 K
void update_key_matrix(char row, char col, char onoff)
7 o( @4 m2 k8 t% A{
0 j, T( e7 E! O' \( n. m    static uint16_t hid_report[4]={0,0,0,0};4 A8 F/ t) K$ b1 _7 S9 x0 x' M+ k
    static char (*hid_keymap)[8]=hid_keymap_dvorak;
; y4 P7 C- M+ K+ J, Y; M  e5 m
3 W1 B( u% W! P5 J
4 n( v/ Y+ e- I$ g
    unsigned char key=hid_keymap[row][col];8 W, f2 s  \% E% S+ L  O* z# x
    unsigned char *report =(unsigned char *)hid_report;
. o& b$ G! x9 R2 X2 L    char i;. A( H5 s1 T2 `

8 {* j* |1 t+ D

5 `7 j- k' a% m3 _8 \7 D    if(key==HK_MODE)+ K  Q2 \2 Q7 W* E
    {
$ R% S9 ^0 Y) d4 p3 z$ k        if(!onoff)
# _$ L& _( N& c8 D& y$ u; m        {/ H+ y6 g! g) P: g
            if(hid_keymap==hid_keymap_dvorak)8 T4 V2 E0 O& ?6 p0 t
            {. p1 L5 k1 ]6 {' U8 n' I
                hid_keymap=hid_keymap_qwerty;, D, ^/ G$ e4 p* V
                GPIOB->BSRR = (1<<2);5 N* T% ?" |+ @$ z. G" v: {( Y5 D+ p
            }. `0 _  |' c2 }2 t% a4 n
            else
5 b8 @; X: h3 i9 k0 q# V            {. I# a) `2 R; @6 ^* O
                hid_keymap=hid_keymap_dvorak;$ ^2 c5 I8 W  U% a  N& g% |
                GPIOB->BRR = (1<<2);+ X4 f5 G( X% |6 ]
            }
8 v6 R; F7 W% D6 {        }  w1 y) @4 x( D" C0 m2 f' n7 |
        return;
# T. @% Y  K3 C' P  a: r    }
5 }. D1 E8 u2 q3 I0 f8 f. L# k4 Z. q* q& D. _1 c
& F$ |) B  m, ?7 t' p
    if(key>=0x80)   // Alt, Ctrl, Shift- `2 A+ v$ i1 O& |3 U! \) R
    {
: h' B% s5 X- Z" }$ D        uint8_t bitset = 1<<(key&7);
- X4 j/ t" M  S! v        if(onoff)   // non-zero is key up
! J+ X9 V' L3 i# B, n/ V            report[0] &= (~bitset);
: x/ R9 M/ H: k" g9 [: W0 u        else8 [$ l  |  h! ]0 y+ n. }! y, w& ^$ V) i
            report[0] |= bitset;( _/ T) N$ c9 ]( J
    }
3 E- ]4 C* W% b  D; u, V    else
7 K& d8 \& P9 E    {7 R8 D3 {1 T4 _
        if(onoff)   // non-zero is key up& ^3 C$ K- I( ~# K  d0 B
        {$ z% H$ ^( N- c9 Z7 `8 j
            for(i=2;i<8;i++)1 k+ C: G" M) L- U
            {
% W" ~8 a4 I! b( m* U                if(report==key)" h; L2 Z8 t! z; A. I
                {
6 l; J$ a4 p9 ]/ L9 h                    report=0;
( y, w% V* v% i0 \. c                    break;/ C- G$ ]( P) Q/ z4 h
                }, Z3 i) x& n9 I3 J: a( y; F
            }
0 v( z, V: p3 X4 b8 H7 h1 F6 U        }
) e& A; T, j3 K. \/ ~        else8 }1 s9 {% t( [  u+ R( {" m+ r& U
        {' X/ Q6 g. ~8 L0 s. W, w
            for(i=2;i<8;i++)
* i' b4 L  O/ _            {
1 k6 }. k' E6 _7 Z                if(report==key)
' ~1 a, l- b' b- ?: ^1 R9 h                    break;: p1 L2 W; h5 x0 B
                if(report==0)) Y% H, T+ p9 s, A6 Q
                {
; r: `4 c5 v+ v7 _- _; [                    report=key;
; N' D, p1 Z3 P* @5 b                    break;* o6 @8 q+ a5 L- f: k8 H3 a
                }  Z. ?) r3 m1 G4 z; G1 F8 Z
            }
8 G( q4 c2 C7 t2 r9 s        }$ J8 o6 f0 J2 Q' g; k, c
    }$ }' K) ?, m$ [4 c
    for(i=0;i<4;i++)
  ?$ [/ q1 n9 D; a& y  X* t- N        USB_PMA[192+i]=hid_report;- G: S. ?* e, {2 l* z! _( {
    USB_PMA[5]=8;   //COUNT1_TX; B* c' X9 [. K. @" i
    if(ep1_wait==0)0 [' d$ E3 b% ]: q6 M9 {
    {: Z( g0 o7 E0 L3 g
        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;
# d' r( Z0 E. V$ X! @4 j1 g        ep1_wait=1;
) Z: \$ u0 Q: s( F% _6 W- @# u* L+ E8 \    }
9 x$ I; _3 g" U- i8 D4 J  c7 u}
' @; c% A8 R" ]; v' s1 K  s$ N$ b6 y0 ~, _/ ~" \

. e" p/ e% z( h. d完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。
: F4 t; w4 P( E- W" h2 V  S keyboard.zip (8.7 KB, 下载次数: 6581) 4 `; M/ _8 h# t# B4 l' a* C/ z
6 ^8 G! a. \2 a/ e2 a2 L
" y. [! F$ W# V) L5 C+ Y5 _! a
1 O; q& `# Y) O" [
. K% s9 E2 V. H% I8 Z
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘
# s0 e- ?7 n+ U, o* Q& ~不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48
% C) U/ I6 o+ Q0 q刚开始我以为要把打字机改造成电脑键盘
7 p, \7 u, u5 t, r/ m% o5 Z, B2 J不过楼主也很厉害!
  ?+ j1 ~" N8 I3 u$ O+ V
哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
% l3 l# W! y- Y% d' Z6 X9 U
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-6-28 01:16 , Processed in 0.178994 second(s), 27 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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