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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

积分
288
发表于 2016-9-21 20:34:19 | 显示全部楼层 |阅读模式
本帖最后由 miaozhuang 于 2016-9-21 20:35 编辑 9 |6 F/ A/ _( @1 ?6 M! H# r
/ c# [5 u  C) p. O
http://bbs.eeworld.com.cn/forum. ... digest%26digest%3D1
) o, _# I4 y3 N* {3 W- U% ^这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。0 q% j: y2 B2 I6 U) k- D

% }& u! V. S3 a5 w' n在看这个帖子的诸位一定都在用计算机键盘吧。键盘上的数字键是1到9从左至右排列,或者是右小键盘区那样三个一排有序排列,反正规律很明显。但是字母键却不是A,B,C...到Z这么按字母序有规律地排下来的。我刚接触电脑(其实还是学习机)的时候,没在意这个问题,觉得是要盲打嘛,反正对两手的手指头来说,按字母序排列并没有什么好处。于是用多了这些排列也就记住了,从来不管它为什么要这样。其实,PC的键盘键位排布上是延用了打字机的键盘,这是设备演变过程中很自然的一个延续。打字机的历史就要早很多了,我没有亲见过打字机长什么样,而且,咱们汉字是铅字打上去的,和英文打字机方式完全不同。
) d" \  ~: H4 }+ X7 X3 P

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

  H# x5 Z7 w/ q  `' k7 ?% V  e 005836yvs0wvovwsssgd3o.png.thumb.jpg
8 T, H. j; N, g9 `Dvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。+ V. Q9 b7 ?8 Q) x7 a

& ~( ?& k; S. O" j. _6 I9 O我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。
2 M) V- H1 c. q/ i$ B! ?! C+ d+ j/ v; O# y$ Y3 M1 A; I2 ?  }7 O
到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。1 X7 h% i" Q8 L; |

% `: s$ J9 d+ p) N5 ?) I机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:
. s, T. q; s' ?* f
123206vx24rfxfp2dfrpxg.jpg.thumb.jpg $ Q4 b! v$ I6 G" a- D' ?  h3 {! h% V
----------------------------------------------------  分割线 ----------------------------------------------------------, |' |! A0 j0 f: s/ G' J

1 X$ t# I. e$ a先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

1 ~9 {5 d1 K7 p 020011osionbunl4ui44vi.jpg.thumb.jpg
# P& u& p9 q0 R1 ?5 L轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。% F7 ^3 |0 X8 I; P6 ]; p; ^) U! D5 M
020017j8ycmnv7788bqv52.jpg.thumb.jpg 2 ?0 {( X8 a5 o9 ^$ K( E
特写,80C49' |  ^1 N, R! `+ v
021040oujzuvtut6iujtvz.jpg.thumb.jpg   f4 q5 `, [7 u
LED部分,使用了一片D触发器锁存指示灯状态.
, e4 K* O2 M3 t  R' N 022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
, e( T  _9 y$ g& b, u7 S. F9 ~" F暴力破坏,将80C49拆掉
. ^# T8 Y: ]7 v- ]8 l3 ]2 e0 T4 ? 021113e48qq98vyohvhzzh.jpg.thumb.jpg 8 p4 {. U: {" U0 d. t# k& v
拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。
+ \+ [9 f/ Z" b2 {5 @9 M+ Z9 ~# c 021125nc9az6dj33rlds2r.jpg.thumb.jpg 1 H' a$ V1 F: L; F
焊好元件后的板子,准备替换80C49
" s2 U* {% ]" `* F+ }0 x3 ^7 K 021146zmmpmh4pav4o7g6a.jpg.thumb.jpg / V" e  G: @% G8 S" t& y
用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。6 l( v7 f2 k) G$ q/ V7 m8 N3 ]
021104shifhnrqbr3o5nlo.jpg.thumb.jpg + ?/ V5 \1 D% |+ A; L( E7 P' r
这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.3 \' O$ e9 ~. K. z  p8 r" X7 @# Q
022003ym1p9u4ug40280uu.jpg.thumb.jpg ! Y' S' {) v4 i4 L" I5 n
开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。
9 n; b& ]- z  y( S+ B6 U" f. ^ 023313kt141q9qajtol7ma.jpg.thumb.jpg ! R" m9 s; |4 c  M& o$ n9 k
我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。
4 P$ w/ H9 v8 y3 S8 j5 ~ 023322nt7l5xb3ltttkltt.jpg.thumb.jpg ) _% K' r. Z' _7 ]
主键区键帽就位) V# o& Q9 }- d- f4 `- H0 D
023331hin88e8wkrwzwikx.jpg.thumb.jpg
' v8 O# P) p3 c8 |" d7 X编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。2 B( a9 L0 H) X) |( q1 f  `
023336wjzlgopugg1jyy79.jpg.thumb.jpg
2 o8 J: y8 l$ Q最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
- k/ X6 y- u/ M) u/ j 023341sffu4j3g2323h6fl.jpg.thumb.jpg
: z$ ^0 H6 O  R1 D, x% a

- G8 ]  d3 R5 B9 q/ ^! A----------------------------------------------------- 分割线 --------------------------------------------------
( G' @! t  D2 r7 i
6 \* `0 C) k) t" K- o
  [/ I: V" H2 n: p! g8 K
/ O8 Q! J  I) A3 h# i- w

! s! P1 I+ U; B9 s- D
& ?- O2 C9 G; e: h" ?( p" K

# d( C! M: B, \( w9 H1 G- a. ]. m: L
' S0 x* W5 `! V; v! }
7 h) ^! y3 B- Z/ I' w( O& r8 t) B
: C. v' |( L, d) P' M: Z: j# G: _2 }, ]# K' H$ I

  \9 c. e8 M$ d1 A9 B
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。
9 A6 }7 K* c: Y- P: J: l! {80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:) D) Z' |- \% C0 @* \' g; N$ L
104025nzibm2rmiomhyirm.png.thumb.jpg 0 z4 `. m( @9 C
3 a* q, h4 I' y# X  R
其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)
8 `4 F" m0 l/ v6 A. a 105004zkrez5houvkkznko.jpg.thumb.jpg 7 g- C* F- {% b' x7 M
3 Z% X" N- @4 R- ]
扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。
/ e: J) Q" M8 ^, {% k
, r9 p  [& [  N4 g, s5 U这是我设计的电路图:4 \) }; P! R* Q* m5 a$ K* |
110344ej2z2oo2rflo7oe7.png.thumb.jpg 2 H6 G) q( K' Q) j) n

5 d: \7 L4 P2 ]/ p7 q6 y5 U  kPCB Layout:; ?* @5 ?, B8 Z# k1 t) m5 H
110847jjbjvt34vwt3v5bb.png.thumb.jpg
8 |0 o7 R! p2 a) ^% W3 A' B# d( W

. W4 B2 o% X3 f5 M不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O.
! l9 M0 e! ?2 }6 W2 p
7 U# `. k: q  }* _

! ?8 |; }. Z  W
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑
- K+ N* b' @6 i4 |( x9 }; O# }! h/ t. w2 j2 B
软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。4 |5 g7 s+ F% @- v" `+ Z

& c1 M! x/ u6 i& l/ r总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:
( {$ L* [0 \: U5 |  X 113818pmrfsb6z0byt6t06.png.thumb.jpg 4 ?2 c, f8 r* |
0 B5 }9 o7 d, a' `; {; s$ j
其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)
5 H* G( s0 G; ^
% [+ n) C- \: w+ X. p& EUSB的中断ISR,bare metal哦4 {* P  ]3 \$ _( Z6 q4 R
# i# w4 u  t! j9 _$ l
void USB_IRQHandler(void)4 ?6 x( M# C8 m+ M# d% J/ g
{
  E4 J6 S- d+ T1 G    if(USB->ISTR & USB_ISTR_CTR)
9 q- C! W( H, p    {! e& o+ X5 f* Y0 j, a8 ]( r4 U
        if((USB->ISTR & 0x0f)==0)   // EP_ID==0
' K3 R( Z8 o% z. L5 i; I        {
! `5 ]7 j" N5 z4 r            switch(ep0_state)
" I( k. j2 j0 Q; P2 K/ _) N4 m            {# {4 @- j$ B2 Q/ M+ H! u+ \
                case 0: ep0_state |= 0x80;
) F8 F# s( {$ O; z                        break;$ ^1 A1 x" ]! R
                case 1: if(USB->EP0R & USB_EP_CTR_TX)! V+ k9 a8 |* {8 {, Q4 k- h
                        {  I+ e! \9 I' t
                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
4 Z0 u7 v/ P" ^' |$ _( h. E, e- E                            ep0_state=3;
/ m& G! `) A' H# r6 R                            return;
# {6 [8 W0 ?0 j                        }& b$ x- U; {+ w7 V" Y
                        else) |, ~+ X+ d0 k6 A6 L; p* ?
                            ep0_state=0;! R+ ~- T/ d, x. ~" y# e' k
                        break;
, d( {2 N$ ?$ {7 ^                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet8 E& ~. [! }5 S6 e6 S% E0 ~
                            ep0_state |= 0x80;
1 A& v8 K' u+ ]% B7 @                        else: G; F6 m2 F# V9 O
                            ep0_state=0;; L. d' F+ Z2 u
                        break;7 W3 ?% W# Q( c2 D9 \
                case 3: ep0_state=0;4 ]  H2 s3 P/ h2 h
                        break;
5 j% q2 M5 }" \8 ?& ?- N                case 4: ep0_state=0;# v; O% u# [: N
                        break;! q& Q* i) t1 w6 b8 H
                default:ep0_state=0;( r( F) r; b3 ^7 N
                        break;
5 z8 m5 ]' e7 t1 n) f4 R! P9 Y            }! D  B" i) K8 @5 j8 ^# A
            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag) A- ~: X# [+ A8 W& x% B+ ?
            return;' t) E6 P4 n7 N$ i- i
        }1 J1 C8 [* t6 B
        else    // EP_ID can be 1: x9 T" \% L$ w  z
        {6 N/ \9 z6 w: M; x: Q( ^
            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag& p& Q: @$ e3 s3 ^+ X% |
            ep1_wait=0;
- _) d: I: w, t8 z& f& h# [            return;
3 b- a, l7 q$ T- R* p0 H        }- U3 I( G! F- Z2 [3 [
    }
$ A3 F0 q$ c) z% n! V3 a    if(USB->ISTR & USB_ISTR_PMAOVR)  d6 _( {4 x0 H* P
    {
8 K1 C% X. \9 f6 n- S) Z- _" R        USB->ISTR = ~USB_ISTR_PMAOVR;
% n8 D1 a% P. q0 `# C; K0 F( s* K    }5 d) }: d5 t8 _* ]( Y
    if(USB->ISTR & USB_ISTR_ERR)
9 V$ _8 U5 V/ o1 W    {# A8 [. y+ ^' ], `& L
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear
* r! p! M0 y& P2 |    }
+ f" o' H0 e# g( f) i, v    if(USB->ISTR & USB_ISTR_WKUP)9 z1 P' e$ Z: p3 h6 [
    {% m4 E; B0 b9 o, ?
        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
% j/ s+ J- y) D" T        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear" l: W/ `: B; a3 T0 n9 [2 B& Z5 l
    }) P8 s8 h* z# O3 j% o& r, L
    if(USB->ISTR & USB_ISTR_SUSP)! F( m+ v/ N' A; H& Y) m! D- C; B
    {
2 s( w9 }' H" y4 F8 {+ L5 L        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend2 K1 O( z8 d$ H
        USB->CNTR |= USB_CNTR_LPMODE;   // low power# N& ^4 q7 C8 x% C9 Q
        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear# v4 d) u! n$ [, h) F7 f
    }/ r/ x4 l/ e5 _, r# q6 P* C
    if(USB->ISTR & USB_ISTR_RESET)7 B: Z  I$ F% K- r
    {
; @5 b; [( r1 n4 A- G. Y; K+ j, J        USB->BTABLE = 0;    // buffer table at bottom of PMA0 K( N" `2 }8 j8 T( A' G5 @
        USB_PMA[0]=128; //ADDR0_TX
" e, T! X$ W6 |/ b  c8 }        USB_PMA[1]=0;   //COUNT0_TX9 E# X, S2 X) W+ i' B4 q0 e
        USB_PMA[2]=256; //ADDR0_RX
% |+ r4 {" L- P* c) u1 k' A        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes' d8 ~8 |3 ^- P7 w& f8 }- N
        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;0 j3 x, g  E1 n2 O, i5 J; h8 N
        ep0_state=0;+ n% T$ u+ Q% a$ ]
        USB_PMA[4]=384; //ADDR1_TX( S' \* b' ^- f* y
        USB_PMA[5]=0;   //COUNT1_TX
, {9 x' m* T! j( v# U% b- e        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type5 S4 D. b, D& ^+ t5 l5 C2 K
        ep1_wait=0;
; a  U' S6 {8 c+ t% S$ v( Y        USB->DADDR = USB_DADDR_EF;      // enable function
9 n  t& ~! @0 B4 n% ?        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear
, M$ x( c, q" c5 B, n$ @, Z1 O    }  y8 O: l+ w1 ?( U7 p
    if(USB->ISTR & USB_ISTR_SOF)1 R& ~: L7 V3 q0 f
    {* P3 }- V# o" A: E' r* s4 u
        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear
4 T$ s( ~8 B; _+ b3 G    }
* P  }' k/ ]( F( L6 @}* k8 m9 N3 v( l; Q7 y

2 f: \9 u  y0 U, J0 ~
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。
9 F# O+ ~& B0 o9 j: h/ H8 o$ L
! K* C7 o( J' r' {  B主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。
# ]1 g# S% ]$ B* s7 i: P9 v
3 b: u* g5 G6 [2 d1 g. u
    while(1)- q5 a9 ]- ?7 V
    {
2 _8 b. Y4 T2 }        static char row=0;
% k0 _# B* @8 A! l5 d8 {        __WFI();% z6 X0 P3 s9 |2 `# l* R2 e. U
        if(ep0_state & 0x80)    // request data processing
$ ^. u( j3 @0 ^; n2 G        {7 g* a& q; {. R4 ^. }6 H. G
            if(ep0_state==0x80) // SETUP phase
, p( `- A& y0 S' K5 U            {
  n. h+ D/ t  e$ Q                if(!setup_packet_service()): Q8 _+ \0 e/ B; C/ _$ B
                {2 \, q+ ^( [$ o9 h0 H
                    ep0_state=0;
! u# _; |* `5 H& T4 i                    // not supported0 j, C% o: T2 U2 I/ @' L
                }# t& B8 [+ v7 e7 K$ Y' x$ K; U
                // ep0_state should be set to 1 or 2, if processed
: J9 `+ l- @$ t/ [* J: ]2 Z" f            }& B1 |  Z) X/ a/ V5 j7 T. {, w1 m
            else    // OUT phase
) T% S  X9 I1 q( S& }            {
5 y6 L9 }* w- i6 [4 i                // process data3 r, f6 n$ D1 f7 ~1 w4 y
                show_LED(*(uint8_t *)(USB_PMA+128));
4 R. S# k6 w1 ]1 b5 [5 r* T1 d                ep0_state=4;
# M8 v9 s: j3 q# a                USB_PMA[1]=0;       // Zero length DATA04 Y% ]5 z4 q/ z# X7 {' y
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;/ T: C; G$ ]' x3 J
            }
) I$ m. Y* z8 }" r1 p( |2 y        }0 C# z( E! P3 @# z
        else
2 D( x& T# U+ j- D* C5 d        {
' u7 W* w! q/ c- o, U# w            if(usb_address && ep0_state==0)
8 A& S5 m; u7 }% J            {8 x1 ^( Y8 o; f' f; E1 L" ]
                USB->DADDR = USB_DADDR_EF|usb_address;
/ T1 H# q5 Y+ k$ o: J$ ]                usb_address=0;
6 i/ S5 A) c% _8 p0 ~5 `6 }            }( G6 `& T  p( F
        }, l$ G( [2 X% F; v
        if(row!=scan_row)   // new scan line" Q5 _; q2 y6 h8 I* m/ Z
        {: k9 \& C& J. n' d
            if(key_state[row]!=prev_key_state[row])" Q# q8 R) w  V9 h: g7 i
            {* O) W0 B9 p5 h; V/ w: ~
                uint8_t test=0x80;% N! p; n$ ?* M7 y" @
                uint8_t diff=key_state[row]^prev_key_state[row];* t) [5 G- i1 F0 v# G7 o: g2 h1 s2 ]4 ^
                for(i=0;i<8;i++)4 ?: g7 K4 N  A9 A0 P
                {
6 ^, g  r+ q5 _; x                    if(diff & test)
9 x1 O/ ]& a1 r4 e                        update_key_matrix(row,i,key_state[row]&test);
3 W: a* A8 e* o5 z+ t: |                    test>>=1;
* A, e8 X/ D- ~                }. u* P4 M/ P5 s
            }/ E9 U! @( `; m1 S1 w0 U/ w. P
            row=scan_row;
* ?+ a4 e3 @3 B( n$ H' A1 k6 @; c        }
7 A" s6 D3 c; _! B. O    }
2 X7 a2 n$ _$ z6 e% Z: N
' T6 x+ N, R/ \' B( a
0 S4 u0 t9 _" K3 i, KEP0的控制传输,把用到的请求处理一下
9 ?* ]( J, ]+ Tchar setup_packet_service(void)
3 d& K5 ?* J' m* j! j$ Z) l{+ F7 n& `7 q& x7 |, x
    if(ep0_std_req->bmRequestType & 0x20)   // class-specific
4 S# {1 r" G. T; U    {
- j9 [; i8 \7 n( u* j  n4 N        switch(ep0_std_req->bRequest)
6 R4 U/ V; f# L. n3 j: Q5 |/ Q        {
0 D0 c4 E3 k+ V9 f; o( W( Q$ G            case REQ_GET_REPORT: break;. Z4 x6 V$ h" U+ q& G% t# i
            case REQ_GET_IDLE:
4 l5 v3 T: E0 ~2 V                USB_PMA[64]=0xfa;   // return 1 byte# v9 D4 M. c2 Q1 M0 I, M6 }7 n
                USB_PMA[1]=1;
5 \  p2 H7 i, b5 P4 p                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
7 \2 ~& G) V. a4 h                ep0_state=1;2 t! T( i. b) S+ q9 n) E
                return 1;" I( j% l4 d& P! ~  f) ^
                break;
: O" K$ [9 a* ?( [2 }! q            case REQ_SET_REPORT:
- G9 V! r( u5 k- P) [! V                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
  d7 `; f* I6 J9 [1 y                ep0_state=2;  n7 j$ e$ K( ?; D
                return 1;
+ @2 V& V) J( n! _                break;9 y- D- m, b; l3 v/ ~" @9 J7 x& G' Q: q
            case REQ_SET_IDLE:- G3 F) T& T  c8 b# V
                USB_PMA[1]=0;   // Zero DATA+ g* }5 R* t$ B3 }
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
/ z& D: O/ N& I; S8 J& h                ep0_state=4;( S4 S: k0 J8 t/ d8 Y
                return 1;
0 @7 P" g' k: R( t5 h2 R                break;
' J7 D0 X% t! x% W6 B% @+ @        }/ ?! h  O4 W, S) H6 _, d7 N+ L# ]
        return 0;- s* h6 m, w9 l! F. ]
    }
2 h9 N# I3 {. y/ `    else    // standard
' l; K' `- w7 \: b3 w" l' @- y    {: c+ K( W4 R  n# I: ?
        switch(ep0_std_req->bRequest)$ `3 J/ C- F! `3 M/ R
        {
2 b- V2 N. V* p" v# ]            case REQ_GET_DESCRIPTOR:4 I$ N: g# P6 ~% ~5 h
                return descriptor_service();# g; c* O7 ]  X( m8 g
                break;+ X' s7 k& t+ u$ b: a
            case REQ_SET_ADDRESS:
( M9 {* L8 ]% U4 d5 l                if(ep0_std_req->bmRequestType!=0x00)! E  p2 d3 z9 E0 V) U1 Q' ]7 A
                    return 0;
0 h( y0 i: X* d2 j2 K                usb_address=ep0_std_req->wValue;; F) |" w6 b9 i- D) F; F
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
' W2 J" T1 J# \                USB_PMA[1]=0;       // Zero length DATA0$ ?1 a2 p$ ?2 ^; @
                ep0_state=4;    // No Data phase
0 z$ g. t$ W2 g, t; |                return 1;, K4 J! y$ \. z. |, A3 @+ m. _
            case REQ_SET_CONFIGURATION:
* P% L7 J1 N1 V- {. J2 l8 n                if(ep0_std_req->bmRequestType!=0x00)9 Y! \9 Z8 k7 p" N
                    return 0;) ~: A) V2 p) f3 i* H+ a9 n
                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;1 X, \% z: M' Z+ p$ j2 Q
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;1 I/ D! p4 n7 E6 _. e' J
                USB_PMA[1]=0;   // Zero DATA+ {, t9 y* `4 m# V, @* L
                ep0_state=4;    // No DATA phase6 i8 V' ]) j8 |- Q1 O& L* y9 h
                return 1;& b( @6 H, E) }: y  t/ }2 n8 `  F1 V
            default: return 0;5 G  r" ?# `$ w
        }& ?) l" J# q' X2 }- e
    }
# f: C# K- G7 |. y! u8 d}& s. n/ _# w2 I$ b4 j
+ W' U( k: W- Q5 N6 Y$ \

5 S! n; P2 R& {
, J; r, ?6 P* ^: J
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的
' t: r' p) V- y$ x1 |* Y% M+ R- Lchar descriptor_service(void)) w/ L6 V, t. h1 w
{
: j* e4 R  g1 C; s    switch((ep0_std_req->wValue)>>8)- q  C$ c% W8 i# o# n7 g. o
    {6 u) V; {% Z) G& |6 w2 `8 Q
        case DESC_TYPE_DEVICE:
; @0 e7 r1 g3 G. Z7 i            return ep0_preparedata(&DevDesc, sizeof(DevDesc));
9 R/ c8 v2 H; l: r            break;
, _% d' |4 u! W: f) s        case DESC_TYPE_CONFIG:
1 m- i8 i7 F8 u# l& M" c  g& {            if(sizeof(ConfigDescData)>ep0_std_req->wLength)
" {8 @8 Z: c! D                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);
. h3 T0 G: `; ?7 ]: N7 A; O            else( k1 R, b+ D$ ]) z# S  W8 _
                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));
. I6 ^0 i5 B( E, q$ L3 f            break;) W6 ~. N% U" q
        case DESC_TYPE_STRING:
- l; t& l. e. q            switch(ep0_std_req->wValue &0xff)
. r4 b7 N4 M  {( |& w: J3 L3 B            {* a! o, N" ~9 Y+ \0 N* H
                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));4 Y3 p+ D. h1 u1 X" [: b
                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));
; {( L) P% o5 h: K                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));
: G3 i+ [! K1 P! |- u' N                default: return 0;
+ U" y% z/ A4 a            }
/ x1 S) o. m  A/ T! e3 u9 H            break;0 b5 h+ |. X9 \  h+ K& e( B
        case REPORT_DESC_TYPE:6 r9 N: c# r0 q/ }. H: @9 Z, C% A
            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));. [, `: y4 w2 a5 y' l
        default:/ ~" t; e0 N6 u9 j. G4 m) O& A) d
            return 0;
9 d$ Z' ]( f; ?9 N8 N# d' d1 C    }
  t" k# W4 j  ?/ A}
3 o" Q! F( l  F" ^- I2 O: D! e下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.
8 N+ W$ K7 ?6 E+ k1 ^9 J( Zvoid TIM6_DAC_IRQHandler(void)
7 ]4 ~. {& D( T" g{
3 ^3 A7 O' J  n, P! f) I    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);
' Q1 u( A0 M& S: z4 Z3 |0 g2 e0 v
$ O" z& B/ }+ b$ y& l
) Q& E* j6 g! V8 c7 c6 N( R3 {
    TIM6->SR &= ~TIM_SR_UIF;* k) P+ F% ^- U6 B; C) C
    prev_key_state[scan_row]=key_state[scan_row];9 j, k7 f6 V3 Y* n. w( Q6 Z* d7 ?; A8 x/ B
    key_state[scan_row]= *PA_IDR;   // update key states' _/ r! B  u5 V/ X8 q3 A
    switch(scan_row)1 }  V3 l% V  i9 ?5 {- n& ]
    {: s! \! F( U" J; y
        case 13: // next row PB14, l7 |& o: z% t- F; n+ p; |$ V% d
                GPIOC->MODER = GPIOC_DEFAULT;
  @# F5 E. U' K+ w( U                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;* n1 y4 `; f5 P4 V
                break;
0 E2 F7 V5 _  `) P        case  0: // next row PB15
, b! z6 Z. |$ b; F. ]                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
. i1 t" H( e+ ?                break;
- O" m* b5 f* e: \# y* v4 j( d2 m        case  1: // next row PB3
: s# l& r3 Q# |: S2 Y# g" e                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;* e+ l2 y5 X4 f# O. R
                break;
) p" d/ f. [: o. u4 I# ?2 G3 Q        case  2: // next row PB4- z7 I% H. ]/ r3 R5 f) T( c. b( ~
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
$ W7 x" Y/ U3 ^9 S5 I9 q: r  ?                break;
  O- u+ V* p5 s; H/ o        case  3: // next row PB5
/ E8 Y# Z2 q2 z0 g1 A- P- R% Y% {                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;+ K# E1 y9 q# k' G# C! o; Z' z! p& F
                break;7 C. k$ e1 E& X$ g. R# M1 V
        case  4: // next row PB6
0 g/ H9 d2 Y$ L( ], \% B8 j                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;
  r$ H% g) j/ |                break;7 Q% ?# U) }/ X" D! L+ g/ X: B- ^
        case  5: // next row PB7
, B! R0 l+ K3 f! p2 o: h3 Z                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;. B8 N, J6 L$ K2 ^
                break;
& a* G3 J7 ]) t( }+ y        case  6: // next row PB8
& o$ u% N$ o/ P                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;, }) V* |! G* w- l0 _% A, `
                break;
) x, P, g+ b/ |8 l        case  7: // next row PB92 n8 ?0 J3 }6 m
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;. j- _; ]# _* X) l
                break;
$ F3 B, F" i1 a. h        case  8: // next row PA87 |: O, n! X8 X  h4 f; ~! ?
                GPIOB->MODER = GPIOB_DEFAULT;/ s3 \( c& }( W7 D6 }5 N" k! C$ Y* g
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;: t. t7 b; _* l4 c& Q8 X# K
                break;. R( G# j, w1 f
        case  9: // next row PA9
7 q. E* I6 S' v; ^! ^5 z8 @                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;% z- i! _: N: z9 I
                break;0 R3 A* m, \1 ^2 ?) a( }; I
        case 10: // next row PA109 g% u) p; C) @2 m
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
  s' D$ {) L8 z# Z( N: ?! W                break;
9 @$ o5 s. C  K9 }! `+ D: g+ \        case 11: // next row PA15
6 H( d, `7 B9 s8 j  Q/ _                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;; z4 }! n: D( v: E
                break;6 a  D3 t4 b( R" _
        case 12: // next row PC13
$ [; s7 p' m& D+ v8 Y                GPIOA->MODER = GPIOA_DEFAULT;
1 W) K7 }. I+ c5 h$ h. n                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
! z$ f* g* g. D. d1 M                break;' d  v% c% [6 t+ C) e+ H
    }
$ E' o$ f3 a' ~/ N# i7 X    if(scan_row<13)
) a# p# Q. C' L4 L4 \2 f2 x0 ]        scan_row++;; O4 |2 d: J4 B/ O5 C2 A, F; ~
    else% n4 @! O  o. X, g3 T1 F5 H
        scan_row=0;" o6 Y" w! W  y
}! K9 {1 w% K4 q) A9 F
* D0 s5 Y  l! V! f

% H& `  ~2 g- B3 z  j
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑 9 @- n9 {1 ~) O* c: P

5 ]# s/ r- b% p; y3 z$ ?扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。; y4 i- D* }# T1 V% B, y. h% @% n

5 J/ {% h4 p! o' j要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:
0 i! S6 D$ N1 |* L' S3 n) p3 }
& c1 p* z0 M: n  F. b% b- d

! H) g9 V3 l$ \2 w! tconst char hid_keymap_qwerty[14][8]={
: ^6 T  S' p; f, O" r% k    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},
' b+ r2 \* @+ L+ H9 t; ^4 \    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
! Y+ e+ f& c3 |" K    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},
5 q- p3 U2 `+ u! y  D2 Y# @    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},# [. z' [8 n1 n/ \. ~0 Q( e
    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},: ~3 o, U. ]( S
    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},4 o2 |7 q( J: N) t' L  g# D9 w
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},& m2 ?, _. L5 Z  Q" Q
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},4 E, i% T! W% ~" x" N* r$ M
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
( H  Z2 T7 _5 l    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
+ A2 {4 S3 [9 Z6 J% O+ F( Z, ]    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
  r! p" A1 ]4 r    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},
9 o4 T$ t- U1 u: o    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
' {6 z2 q. q# S7 Q- e, \    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
1 [/ j' c; S: L! b7 P) a+ p0 c3 y' y};
6 ]& i: F) r/ T2 a0 F' a/ h! f
% g& X  d4 z5 l  c1 O

& ~1 k; T0 k2 L0 X$ w! ]const char hid_keymap_dvorak[14][8]={1 c* l+ y$ c7 |: o7 Z
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},  J* V2 E( f2 c. t0 t8 J
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
3 C- X, c2 X3 i    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},
0 }3 ~( Z( ^, H    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
% S4 U3 ?; w6 V% }& s3 k. w( F    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},
3 S( H9 L4 p& r    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
: I/ M& Y; R( O6 i; f- R2 @6 R    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},: C. O: D, B/ p! ^8 B
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},% D6 Z0 A2 Z" G) v) g
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
0 R: ]/ N7 f7 u: t0 W    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},  l$ e% @7 \  {+ H
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},$ i1 B" ]: I  g, g1 ]. H) Q' Y
    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},/ c( ?# s, d9 M. E4 ?) n
    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},
% }2 Y1 s8 k  ]    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
$ P' M+ s* [5 P};1 J* ?! p2 n/ m, W: l

8 N( o7 I) Z3 d" L4 m- |% }: |0 S4 q! D) e% z
上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
; j6 d0 t  O3 m7 \8 ~! [' Y
7 Y3 O/ A7 L6 |! O2 HHID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:
* L! q5 b( s- G) |6 ^8 f4 ^! L2 |

5 @0 S' I" B' T+ z1 @: z' bvoid update_key_matrix(char row, char col, char onoff)
9 _, L& B7 l# M2 V3 V6 s) M{
( B- F7 |/ A( z) j    static uint16_t hid_report[4]={0,0,0,0};
- J( l! a( G$ s1 Q* j) j    static char (*hid_keymap)[8]=hid_keymap_dvorak;. u9 M- y$ v( U
) s% X  z1 x9 S# v; l5 k

  H3 ]- d" D( U8 y  y/ J" X5 ~    unsigned char key=hid_keymap[row][col];
" Y* j7 t) _  u$ l  X7 ]1 I. E    unsigned char *report =(unsigned char *)hid_report;
+ O  u6 |' I' S3 d9 |# `+ \/ R    char i;
# Y! j: D) ~) b  j0 J( z
  T+ O: I/ `3 s3 {1 {0 d- q

$ O8 w% B) o/ U" x8 ]( c    if(key==HK_MODE); S+ `% e: b' H3 D
    {
3 X+ c9 ~5 E% |        if(!onoff)
; x+ p$ u# m7 P* e( Y, s& H        {
  `; T" ^4 q* f* u* A            if(hid_keymap==hid_keymap_dvorak). g! d% t7 M$ t' h( c. L- g$ G
            {5 {5 l2 L7 G, G! G% z
                hid_keymap=hid_keymap_qwerty;$ ^' v1 p, G4 \3 ]
                GPIOB->BSRR = (1<<2);
7 v; S8 o* m' z5 p$ Q2 s            }0 t6 V/ L3 m" U7 h+ j" T
            else9 {! k" P2 d9 J$ {3 d. V, t7 c
            {
; t# C7 V  S+ t, ~% @                hid_keymap=hid_keymap_dvorak;
4 s3 e! `  k% G, W7 H8 k' d# C  t* a                GPIOB->BRR = (1<<2);  n9 a/ }" }/ _, g8 y
            }
+ E8 R4 J$ o! G5 U        }; e5 B5 y# J& }- C7 G! W
        return;% w0 S+ Q" A) H( T  f
    }
( L8 k2 K# G! `2 `& ~( l  U, v2 _9 C2 W  x* M( X8 x
( g% y, K. N% m
    if(key>=0x80)   // Alt, Ctrl, Shift4 U, B* ~/ a$ F5 ]2 `) H! z/ c
    {
9 O2 r' {# e, P& U, \        uint8_t bitset = 1<<(key&7);/ ]* O6 \1 C% E) ~: W- N
        if(onoff)   // non-zero is key up
0 D4 A" ^. r9 a% w            report[0] &= (~bitset);
4 x' ~7 T% X) @. F5 F; E/ D* Y: C        else
$ e+ D& V$ R* q# o0 T" d  }0 m            report[0] |= bitset;
2 U$ M9 v9 R0 r( Y1 @3 O    }5 _3 Q% W, F- [! O  z! c$ b7 b
    else
% v" ]5 C) ^- M; z! V+ W$ d5 g& ^" q    {8 f' V  `6 m; s: }; g" e
        if(onoff)   // non-zero is key up
2 ~9 V" z, ^- z; @0 k+ |% L4 }        {( n% {( k! R" K% w
            for(i=2;i<8;i++)% o: p+ j+ E4 P# h4 e
            {. y" E  f. C. J# w) i4 T8 j
                if(report==key)
. [# Y! _. ?2 V0 X                {
, d+ P% w6 k. i. W- s; G, }                    report=0;" |& p, ~4 A7 ?5 v
                    break;  H) M3 t% W. q7 _# Y% q1 @5 T
                }1 h1 O& ^0 a1 Y# n- K: C5 L
            }
# a) s1 M. W1 U4 ]' I! _' g# a7 U$ s) C9 P        }
2 o. ~7 m3 Z( z/ E3 x* v# {        else# F0 Y* \7 ]2 g- R6 `# [( [
        {" K% y. v* L6 v8 r1 C
            for(i=2;i<8;i++)" A1 o5 _+ ~0 N* F
            {% a) Q/ k/ M' L' f7 ^
                if(report==key)
5 Q1 H. X( r2 k, K8 `3 g1 N                    break;
4 D# }) U, A* t4 \                if(report==0)' D, F# v" j# Y/ S% n+ R& z
                {
- z! V4 s4 g& S0 e                    report=key;
: M2 a: D1 ^/ a, }2 K& h                    break;- m7 ^5 {; {) n/ E- S7 E
                }
4 V/ C) @5 F- f! [% ~            }
# O. {2 T' R; y  B1 s" o        }, v( f" x6 y: n5 k0 h
    }0 R' W/ n! t' j" K
    for(i=0;i<4;i++)
! D! p- _  O5 D) u5 z" V/ ?8 H4 l        USB_PMA[192+i]=hid_report;) \5 u7 h. n+ Z
    USB_PMA[5]=8;   //COUNT1_TX
* l! l1 c, d% z3 r! m; B    if(ep1_wait==0)4 H: x9 v9 S7 Q& m; A
    {
+ t7 `% I9 X3 _% r  F* _        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;7 n4 H" k9 d( {# V$ r
        ep1_wait=1;
3 t7 M$ N; [1 B    }( j  g: |0 @) M9 i- l
}
) J0 @. E' _- L! D. v+ \5 u/ ]! J- Y( A% V3 T, P

% H/ ^5 L6 w- N+ q; O完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。' {1 w% L- c+ a3 L  {9 t# m
keyboard.zip (8.7 KB, 下载次数: 6653) % Z, X0 H' F9 ?: X+ H. Z2 Z7 b* Q

1 p8 W- t0 j: v" V% l$ I
% Q( C5 b. r5 a8 a8 E) [. ?  C
3 G9 Q5 }7 z, W' \4 l4 `5 d
" d) F+ O: D4 |# p4 }  l, h
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘* t6 z, Q5 ^% g( A# P5 Q
不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48
4 v3 R* c- K" _9 t' I刚开始我以为要把打字机改造成电脑键盘
0 {# v$ u# x- e' t+ [0 o" g不过楼主也很厉害!

" [7 y; M; c/ w% o8 o哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
- Z( P* o  m$ @) i  h! P* J) M2 L
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-8-20 01:47 , Processed in 0.185952 second(s), 27 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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