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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

积分
288
发表于 2016-9-21 20:34:19 | 显示全部楼层 |阅读模式
本帖最后由 miaozhuang 于 2016-9-21 20:35 编辑
/ k: S: H8 k6 `6 c9 N# x) W$ e( o$ E3 O) Z
http://bbs.eeworld.com.cn/forum. ... digest%26digest%3D1
. S) ^1 I4 [. L+ A( s5 r这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。6 T$ r, V7 i+ E1 g6 D  w
" ~8 ~! W4 j7 P6 i& X
在看这个帖子的诸位一定都在用计算机键盘吧。键盘上的数字键是1到9从左至右排列,或者是右小键盘区那样三个一排有序排列,反正规律很明显。但是字母键却不是A,B,C...到Z这么按字母序有规律地排下来的。我刚接触电脑(其实还是学习机)的时候,没在意这个问题,觉得是要盲打嘛,反正对两手的手指头来说,按字母序排列并没有什么好处。于是用多了这些排列也就记住了,从来不管它为什么要这样。其实,PC的键盘键位排布上是延用了打字机的键盘,这是设备演变过程中很自然的一个延续。打字机的历史就要早很多了,我没有亲见过打字机长什么样,而且,咱们汉字是铅字打上去的,和英文打字机方式完全不同。

( r# Z- H& g# w# w2 L8 g
& E# b$ w1 t9 H
235140i3a36qivqzuvmt5q.jpg.thumb.jpg 7 K# l) x5 B$ S: @+ E
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。. \) Z; Y7 Q' w
001734klbyoluenuwz4h4b.png.thumb.jpg ; s+ L5 h7 I7 ^9 d6 T5 F7 n3 @
为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:+ h3 J9 @) e' f' q' _4 R) W
003625r2agx2f5v922cf2f.png.thumb.jpg * d" p9 r8 \6 `% V' |( P
其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.# c" P. A, e3 E$ |/ M
Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:

5 J- |  N0 o; g) | 005836yvs0wvovwsssgd3o.png.thumb.jpg 7 q9 t4 B. S2 H5 w* t% F+ Y; q
Dvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。
$ I; ]1 f6 B- S0 D1 G/ p
# ^8 A) d' f# F1 _7 n7 X4 G我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。% ^7 N# v+ l0 E$ Z
8 b0 k+ e' `. z4 ]
到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。
' Y% @4 v# S: u8 H% N
' N9 M# U$ G& ?) }机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:
6 v- J. n; }2 n- @! T% m
123206vx24rfxfp2dfrpxg.jpg.thumb.jpg
" B0 U3 i  u. n8 m1 T: O----------------------------------------------------  分割线 ----------------------------------------------------------
! @) u6 L2 G9 z) _. F, o9 p8 w
' h) D9 x% |5 D& j8 }7 M( o先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。
8 i" {# }9 J$ Z3 P
020011osionbunl4ui44vi.jpg.thumb.jpg
7 T/ W, w+ d( c$ S& Z轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。
$ l, a' ?; \& O% x' R$ l: M; p 020017j8ycmnv7788bqv52.jpg.thumb.jpg ! `: r, n! ?; m+ c% n5 t2 _) m" ^: a- x
特写,80C49  ]. c# L3 p: t
021040oujzuvtut6iujtvz.jpg.thumb.jpg
% X$ G( R6 }) \$ T! m) TLED部分,使用了一片D触发器锁存指示灯状态.0 w. o! }9 ?) J+ b! j
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
( Q5 I* P; p# G暴力破坏,将80C49拆掉& z4 N' Q2 Z4 t' S4 ^# M# w
021113e48qq98vyohvhzzh.jpg.thumb.jpg 1 p! J. ]) f; P9 k+ y* z. m
拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。9 {# p. G+ [3 X
021125nc9az6dj33rlds2r.jpg.thumb.jpg   U7 \4 W% y+ M, ~( U8 R2 t
焊好元件后的板子,准备替换80C49, J  Q2 S4 A, z7 I
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg
* \  R( \! Z# ], L. ^# x用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。
) G+ C. @: M9 M* q. ]. j 021104shifhnrqbr3o5nlo.jpg.thumb.jpg 4 J3 ?/ R- a% a) {: w. N$ o
这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.
* R" Y$ t: f) J! y6 N: v' E 022003ym1p9u4ug40280uu.jpg.thumb.jpg
- }6 s: r* O6 ~4 @开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。7 V0 ]: C+ S9 J+ @; H
023313kt141q9qajtol7ma.jpg.thumb.jpg * q/ z3 }& v, y0 s, D) I
我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。, F! N, m6 |( G, ]
023322nt7l5xb3ltttkltt.jpg.thumb.jpg & r/ k1 g! d8 h. w- }  Z. }
主键区键帽就位1 \* J' Z! n( p4 J% ]
023331hin88e8wkrwzwikx.jpg.thumb.jpg * c) S0 E7 w# L$ |% j( _! t
编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。7 |2 }4 X4 Q1 F4 A3 ?/ m2 ^
023336wjzlgopugg1jyy79.jpg.thumb.jpg
6 S4 d+ D# {+ h# U最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。0 N/ `9 D4 t, B6 C' [
023341sffu4j3g2323h6fl.jpg.thumb.jpg
! N# W& L& h2 |4 y) j) u
& t' M  u! l: v) @  ]
----------------------------------------------------- 分割线 --------------------------------------------------" p1 {" {0 q! ^
3 `- b& a+ B, Z% b

4 ~! O$ H$ D( W0 t- L% n
' L* t, F- q3 B5 w
  a4 F+ h0 ?5 |; F, {0 @
' p- V  D1 Z& U) r/ e. W2 Z. g

( w4 a' K9 ^6 ~2 B$ X" f- Q! j% X7 n& i7 f* \1 `) d* x/ u
: X6 ~3 Q$ O; t5 _# q. Y

5 _7 |3 L* p8 H' @0 {
6 M& [- n' [. j6 ?5 }- u$ X5 r6 h
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。: E  `+ Z1 c. k; x' {. \9 l
80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:0 N/ }4 r- l1 x" M! J  h/ L
104025nzibm2rmiomhyirm.png.thumb.jpg # j( T8 h% k, U

% k# d6 |1 T& u) b/ I( P. o其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)6 p( ]% E* b! V' Z- a( R6 `
105004zkrez5houvkkznko.jpg.thumb.jpg 0 B) [1 u$ L( R3 ?

0 u% W( l* t3 T- |- j3 d* c- u扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。
7 Q# Z2 u6 c' i0 X
' c( W+ u& ?1 J/ t2 M' M& {# m) q这是我设计的电路图:* c- B. i) C% f, H
110344ej2z2oo2rflo7oe7.png.thumb.jpg
2 p* R7 P( H2 O8 ?

: s: J. D; @2 u& H$ I8 ~/ BPCB Layout:6 y6 `1 F  d  `3 t; B( z; p
110847jjbjvt34vwt3v5bb.png.thumb.jpg
4 ?$ T9 k/ H- I7 F1 G3 ~2 e8 N

/ X% F% {% u/ `不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O.
8 o  r( F+ @) }
0 z' {1 |0 b/ W) x( Y
! q9 U* X" b) J: c8 ?8 B' y
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑
! i9 X% z& y. U  k' T# @/ ^. ]. e3 g$ X3 A9 k) h( t% r
软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。
5 E8 @! u% w9 @" V2 _; U6 w% B6 X
# T2 c  l7 P( j. B. Y# q总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:+ _- W, j9 b. g5 F( d% I
113818pmrfsb6z0byt6t06.png.thumb.jpg . o  D: o3 J" Q+ @" _* [

  K2 Z  K4 a; D1 v! I% D1 {4 p% H其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)1 B: P/ B3 I5 `/ f5 r! n8 S5 m
& y! T/ y+ T9 J5 O( O, I
USB的中断ISR,bare metal哦# J3 v/ V8 B  {- F: _+ _
0 }! I7 ^: H9 m$ ^
void USB_IRQHandler(void)
* {4 S9 u6 \4 t2 c* `{3 P  y' P- z& Y/ V
    if(USB->ISTR & USB_ISTR_CTR)
, p; S- s; W$ v    {
+ x$ t1 F" u) A, N% P9 V        if((USB->ISTR & 0x0f)==0)   // EP_ID==0
) j! M( ^7 W7 U- E& o9 {' l9 e        {
* a% U$ C( K0 G) C2 U& F/ b            switch(ep0_state)
& u4 Y6 m- l# D% s            {0 c) f9 F' s$ ~# y8 v
                case 0: ep0_state |= 0x80;+ z4 @3 g# E7 n, `8 z3 @# b! E6 d
                        break;
8 J9 X( k, P$ e                case 1: if(USB->EP0R & USB_EP_CTR_TX)% b: ?+ g! |6 V9 G8 i
                        {1 G5 d0 _3 w, k0 ]/ a
                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;; Y! W4 a4 H$ S4 q' v1 v: G. Z3 o
                            ep0_state=3;8 ]4 m$ D+ z7 X" R
                            return;
+ G" I& U: X# i3 M6 Z                        }4 v" `0 Y- n9 w3 a& x7 d8 |% v9 V
                        else
: i+ V2 |& b% `8 _: q& i  [' I% [, z                            ep0_state=0;
% L$ D& m# z! B7 w                        break;, S$ r" ~6 _+ B3 o6 F; k' _  X
                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet
4 L. W# J% ^+ g- n5 X! h                            ep0_state |= 0x80;3 F& h, v" s: r3 w9 ?6 h
                        else
3 n/ M  \5 a9 i7 Y, l- b                            ep0_state=0;; O  ^9 N* M$ ?  k: J
                        break;
4 ~9 n9 Q) V' t( y  z) G                case 3: ep0_state=0;
2 L" z( u+ d9 t                        break;% v) I, w3 c; I9 C# `0 r
                case 4: ep0_state=0;
! m+ b, u3 E' S% y# I9 k1 u                        break;; K% x# Q6 @0 I4 [
                default:ep0_state=0;
# l+ \9 X0 ~/ ], U) ?8 P                        break;
2 D' b* ]( n! w' a% A            }
" T# M. l, J0 Z3 Q/ L            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag" @$ @2 i; r+ k6 \$ y  U% i
            return;7 M3 @  `* Q7 h
        }
/ _1 [; z1 U% O8 ?        else    // EP_ID can be 1
1 K/ C7 u) g$ B+ S; R        {
7 Q& f% S8 b. L$ K* D8 ~+ F+ |$ ~            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag, o2 Q, H) {$ \
            ep1_wait=0;0 E* c0 R5 i7 [6 `% R
            return;
3 j5 u, u: v7 n" _1 U! h- Q* I        }; b5 v: M5 r' Y  b6 ~; q1 ?
    }. ~* J8 [/ t% `/ i1 ~$ u9 c
    if(USB->ISTR & USB_ISTR_PMAOVR)$ P* W& [1 y  v+ f% \* o7 l9 ^
    {
+ ?8 J5 R# F% \        USB->ISTR = ~USB_ISTR_PMAOVR;
) M* y! c' I- R# ~9 y  J/ K8 Q    }7 ~% J' U1 M) D, y
    if(USB->ISTR & USB_ISTR_ERR)
5 z: w. n0 a, E! ^7 h  }$ \    {
7 G+ W' p9 V& i; S; G7 L        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear
! i$ Z1 T9 t0 W: t. P  [% ], Y8 z    }3 g/ v; q8 u! g1 j
    if(USB->ISTR & USB_ISTR_WKUP)' a; \, a  T. @& i& u$ ]
    {
5 L* h0 z' {; ]7 s        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend" M" {. d9 }! x- b/ @. s
        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear
" ]6 Q4 \) o7 i2 f$ R' L2 |& q    }% i" k! Q- A, r- E
    if(USB->ISTR & USB_ISTR_SUSP)
. W- o" c+ |6 t/ l5 F5 D    {5 i: H# |& E$ p& q5 k: N
        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend
$ k: n8 E% E8 f* W+ Q( ?. E        USB->CNTR |= USB_CNTR_LPMODE;   // low power0 G; [% r! X" ~
        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear
2 d' ]6 p+ L1 m    }2 N6 y- f; p& C1 s
    if(USB->ISTR & USB_ISTR_RESET)# |- S9 M8 O3 y8 G5 z+ O  n/ V$ `
    {
) u% D+ C! y2 J) _* v  W3 X        USB->BTABLE = 0;    // buffer table at bottom of PMA
+ ~2 a0 ?, U- U) g        USB_PMA[0]=128; //ADDR0_TX% u  N4 w/ m: {/ f  B/ q) C
        USB_PMA[1]=0;   //COUNT0_TX
0 q3 Q$ g6 k5 g0 R  J8 e        USB_PMA[2]=256; //ADDR0_RX+ Z2 f4 _8 h7 M3 F
        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes% y% r9 _. t* q( y1 @3 A/ x
        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;' ]  w, G" R; f/ T. h# D- ^0 N5 Q
        ep0_state=0;7 c1 D2 w. p! J! w5 b6 H+ l
        USB_PMA[4]=384; //ADDR1_TX' _0 f: Q2 j) W7 n/ w# R  X0 z
        USB_PMA[5]=0;   //COUNT1_TX
( D+ [: F& d, G- F) B" m3 G* N        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type
4 }3 |* L4 H! X6 D" d& Q1 o. t        ep1_wait=0;
: m3 Y3 Q& W, B) Y& E3 p2 }        USB->DADDR = USB_DADDR_EF;      // enable function
5 X5 Q! q8 t  ~, A        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear
" V7 g% }* o. v- Z# R' W: r7 M    }* K+ y# Q/ ~0 H* B
    if(USB->ISTR & USB_ISTR_SOF)2 v' e) U+ D$ O6 m$ O3 J7 Y
    {
7 X; W& M% v$ ~, v  }        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear4 J+ E; s+ \+ G! _6 T
    }. c' T7 I9 [. c8 w: F6 s% {  A
}
# a3 Z1 a; {* C" |* w0 R
% F( i: ]( V7 H5 m5 M+ i. B
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。
* m" }1 x7 q9 M& G& `2 Z# |; x9 n) f. _7 m8 C) r
主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。
' c# m! N5 r8 b4 r$ h" U+ ^6 a: m8 s0 r( K' K6 B
    while(1)
- D+ Z! S- d/ S+ i% F/ s    {
; e+ S3 A3 I5 [        static char row=0;
3 q- O1 _; F( V3 Q! B        __WFI();
1 _2 V) I9 `; F        if(ep0_state & 0x80)    // request data processing: b! W5 ~3 F0 N3 V, z1 P
        {
5 q* ^3 S9 Q/ G            if(ep0_state==0x80) // SETUP phase
& [! R; q4 f' q0 l6 k; D            {
9 [+ p% ?" z1 S6 T: t6 S# O                if(!setup_packet_service())
+ x4 ]  \1 T' K) V! U                {' n3 X6 {# e$ y: Q5 q
                    ep0_state=0;
+ C! ?* U# B" }6 y) X5 ^) }                    // not supported: o/ C( Z; W1 W, F4 x3 E% I3 a
                }
1 W& b; _9 t& x, x6 C9 _                // ep0_state should be set to 1 or 2, if processed1 K4 s/ z! P" v9 y
            }
: |$ }2 [1 U8 w1 ], y$ I* o+ P+ f* Q            else    // OUT phase
# c( |# X( z3 P- C/ `) t            {* ^/ S  {- E) B
                // process data
0 W, A$ Z" |, ~                show_LED(*(uint8_t *)(USB_PMA+128));( k2 e1 {" I  N0 k. U7 d
                ep0_state=4;2 V* s' }' \4 m- g" b! Z* J
                USB_PMA[1]=0;       // Zero length DATA0. |' U, w' c' t% V6 Z7 g% x. G
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;0 z8 I! e, g7 D3 N
            }
" ?$ R$ K' ?+ w7 T        }- `# c/ F& I1 A% R8 j
        else8 z, n8 w% A3 @8 [. B9 O$ \
        {
/ C" `0 t; m8 u6 @) w: X            if(usb_address && ep0_state==0)
2 g- z8 v1 F% w& g: ?% g: k            {
3 ]0 x  S0 A3 l9 X( C8 L. Y                USB->DADDR = USB_DADDR_EF|usb_address;
# G# w7 E, [( @' d/ d, \                usb_address=0;& H  l0 ~# R* g3 ~, j( L
            }
0 U0 U% K# P# B. G        }0 |5 @. W3 f: F8 d" a
        if(row!=scan_row)   // new scan line0 Z& K1 e& a9 Q' O8 b
        {
6 z7 E  b& v0 d) y" J            if(key_state[row]!=prev_key_state[row])
- D9 q+ _5 {* m9 `$ U% j            {/ K1 n* c+ H+ v! ]  j. r) C4 b! s
                uint8_t test=0x80;
: T) y# j6 J2 ?1 l# ~+ V& B                uint8_t diff=key_state[row]^prev_key_state[row];
/ _( ]0 z) q; X) k% h1 A( _$ z2 _& E% l                for(i=0;i<8;i++)$ S0 ]6 s3 D( h8 t6 [. E) T
                {5 _. H! b  w( e8 `
                    if(diff & test)
) p" j. [- _, ?, e0 g9 a$ w                        update_key_matrix(row,i,key_state[row]&test);4 L4 ^/ k9 C, n
                    test>>=1;, ^/ p# F* B: @1 I
                }
- V' j  p$ R4 z+ R. ~/ U' h            }
% J# Z/ P' N- q# o. y            row=scan_row;9 b, y5 F: |; I# O
        }
5 |5 @' l9 i0 O' E; y* Q" @    }( r: @$ m) k( [4 }
$ U4 ^$ s& \5 _% P

7 X6 Y- r9 c9 _8 j* k2 b9 CEP0的控制传输,把用到的请求处理一下" i/ N: Q8 V- T& }0 ]0 ?' Z$ D
char setup_packet_service(void)
3 p2 D# h- y+ a3 }& |+ i7 `{
0 _0 Q" z# E! E/ m; g    if(ep0_std_req->bmRequestType & 0x20)   // class-specific# U6 ^* L1 M( Z( F* e7 ]
    {
$ ?# b# Y; \- O+ B: Y        switch(ep0_std_req->bRequest)/ ^, t  V% j1 b. D- @5 ^
        {
3 L, j6 a3 t4 P$ Z0 G  m4 @$ Q8 e            case REQ_GET_REPORT: break;
% G! Z' V' V# N, U# e0 x# p9 n            case REQ_GET_IDLE:& g0 O0 q) `  u$ Z! g! d# ]. _6 b3 K" _
                USB_PMA[64]=0xfa;   // return 1 byte
' c& W0 f: F0 q. h' H                USB_PMA[1]=1;' W0 R* M8 `: V) I4 t$ q
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;$ [# Q7 }4 N7 z2 i& c6 x% M
                ep0_state=1;
( _$ ^( O8 `( }0 y! @" Q) ?2 \8 y                return 1;9 k. r# }9 q. M0 H' L
                break;
& Y6 }- `# _0 m& p6 @2 @            case REQ_SET_REPORT:- `$ z8 y6 p, J' U. N+ Z( X: x: i
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
4 h2 ~) X  s+ s  Q0 n$ g                ep0_state=2;
4 r# o5 u1 T* l3 G' P; B                return 1;8 W- Y2 u' G2 \2 e% [
                break;
, ]  v% g6 N. K+ z4 k% E+ j% ~7 D            case REQ_SET_IDLE:
3 i* `/ R8 f$ y7 t# }; G/ [4 F                USB_PMA[1]=0;   // Zero DATA! v9 @1 j3 v; a2 @( A0 B- U/ J
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;1 P" z# K) D7 ]6 m; c
                ep0_state=4;
; E- Z# T8 W- w  G" u" X" V# O                return 1;/ P* c# |: Z8 d3 i( C
                break;
" _$ ]* i! j! H, `        }( m# m: C" W2 A6 {
        return 0;; ^/ A# a6 y6 e( ~0 ~1 r2 d5 _
    }
" G; _; M9 J2 U# u) A    else    // standard
. D0 d  n! G% U8 x1 {, ]2 p    {
! ?4 _4 T# ?! y; g! S8 z4 {        switch(ep0_std_req->bRequest): A& I$ k7 c" b
        {
; T) P0 W8 P( v/ `) u2 s. j            case REQ_GET_DESCRIPTOR:0 Q8 w+ M. G% I) D' B0 g
                return descriptor_service();3 I" ?6 A6 Z5 Q- H
                break;/ S. [4 ]  N" t/ s. Q
            case REQ_SET_ADDRESS:8 G1 N$ n- I  m& z3 R0 ?/ k/ v
                if(ep0_std_req->bmRequestType!=0x00)
* o# w$ c9 C7 n& o; f: Z7 f, j# b                    return 0;
  n! e% k. J7 B: Z. n& v                usb_address=ep0_std_req->wValue;: {# ?( b5 O4 m: f* j( x
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
" d* [1 n0 `) F7 U& n                USB_PMA[1]=0;       // Zero length DATA06 t* T5 _/ C- R$ S% x; E! ]! E
                ep0_state=4;    // No Data phase
4 \& ~7 \# w' X  p9 v                return 1;- r- l2 J% E; v
            case REQ_SET_CONFIGURATION:
' @- u" o  M9 @0 J5 `- M3 t; H, Q5 `                if(ep0_std_req->bmRequestType!=0x00)
0 N9 `) }/ G" `: p3 b) g                    return 0;) `9 A) N# m6 H  B1 R9 ~  r
                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;% d0 {' N8 W# r
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;9 B7 z1 r8 i1 [5 j2 Y$ r+ ~6 J7 t0 z
                USB_PMA[1]=0;   // Zero DATA
/ l! C) u# l" P! k                ep0_state=4;    // No DATA phase
# c% T8 c8 u1 S7 H/ V% G                return 1;0 P: O3 f- ]" h
            default: return 0;
- ]4 ]+ _3 N4 F        }
3 H6 m! Z+ |, |4 ^; x4 u  L6 @0 H$ n    }
  P: n0 X; z+ t2 W}, g2 B- `4 v9 g" ^- P, b6 k, f$ s
/ q5 R4 G4 O' O2 C( w: }4 W6 R* ~: u
: P# p) T$ |6 d& J' a

, ^' d" e2 X" }# P& X% W; _) }
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的
% u0 b- A1 }/ \& `& |; l5 n/ ?char descriptor_service(void)8 V& J# v6 B2 J3 L! {
{+ j) K  ^0 P9 K+ t; g) X0 u
    switch((ep0_std_req->wValue)>>8)
% z  b* n" O& N7 Y    {
9 j' Y/ c. H% y7 @        case DESC_TYPE_DEVICE:9 X# g( N" F! Y8 W4 u
            return ep0_preparedata(&DevDesc, sizeof(DevDesc));0 m! p3 D! J# I. z# s
            break;3 l/ ?  e$ }- c, J, M1 H
        case DESC_TYPE_CONFIG:
+ F# ~( K0 y+ ^9 w            if(sizeof(ConfigDescData)>ep0_std_req->wLength)
. g8 r( c/ ]* ~  A3 A7 b                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);
9 B5 z8 ^6 p2 {: ^* ?8 P            else
& w$ C- v7 O% r6 _1 R0 Q3 e                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));( R! r& u# m/ L' l9 J. ~1 Y
            break;
' v8 g* j$ n0 |' k* f& ~        case DESC_TYPE_STRING:; H/ T3 ]- p, H2 h9 V
            switch(ep0_std_req->wValue &0xff)9 M2 L& `0 M0 y& m/ B3 u/ J3 i5 b
            {5 f- J& a) h/ n( r- f
                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));  r5 \0 |* ?" A3 U" t& D
                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));! B5 }8 a7 J/ ^" t
                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));
+ |2 X9 u' }3 F+ o# ]- d% t                default: return 0;
" w/ p7 [$ H0 G, n4 I- b            }2 s  l# }: U9 e$ Q) b$ L3 ?+ [" _
            break;
! @" L+ v! b0 O        case REPORT_DESC_TYPE:% {9 C$ W' A3 ^% E6 D3 q/ p6 A. T
            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));
, a( \5 i  y, B7 x2 h6 h6 R        default:
, u; M" N; N6 e5 j* P' I            return 0;
: _" X9 |; t9 W    }" t/ A& n, ?, z# {" r
}3 T5 |# N1 d5 J
下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.4 F& q! u1 S4 x: d% H( z0 }
void TIM6_DAC_IRQHandler(void), }, A! }6 z3 K1 X$ f+ y1 H
{
4 W$ \& _" E! p- H4 F) E4 _    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);* w! L1 z2 q' u' L# V$ q
- @  Q- [1 r  r4 f" ~4 F

% e3 q: a( b: J0 B( p! b    TIM6->SR &= ~TIM_SR_UIF;( x+ n2 r+ B' Y- |
    prev_key_state[scan_row]=key_state[scan_row];3 A9 n/ z# [6 S: Y7 {
    key_state[scan_row]= *PA_IDR;   // update key states+ ^) i, R( `. h9 @8 f7 |- y. H5 w) s
    switch(scan_row)5 \1 J) E) d4 k' b. }
    {
; h) N7 y0 |7 {% O! d' \* z        case 13: // next row PB14- K, M2 O: d1 v& t! \) {
                GPIOC->MODER = GPIOC_DEFAULT;! @& J" t+ {, e6 h
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;. S, V. Y8 X2 d/ t* X1 k8 M# i: y
                break;7 c" K0 _$ `( L. G% `8 Z$ [
        case  0: // next row PB158 o. H( l! b# q: F* E( [$ N+ _# ~6 H6 p
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;4 y- K( U/ e+ C& b1 ^
                break;
4 B/ v, ~) O9 C; f% u& v        case  1: // next row PB30 F4 I( z% q- _
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;' e+ e" R+ |% D
                break;& w1 j5 U9 \. U) d, \, `6 s
        case  2: // next row PB4$ s: n9 `$ o/ N: \0 a4 W' }
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
  r1 C8 T' U0 j) x5 R                break;+ t" Y! s- f1 k! v; V; y* W  F4 @
        case  3: // next row PB5
7 J; c+ A. e1 E, Y                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;1 [; ~* k( [/ ]
                break;
* o; y1 L& b2 X7 Y3 I$ P" j" y6 N) [        case  4: // next row PB66 }; k  Y* O* N, Q7 [6 U, e/ q
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;  t, w; U$ |& u1 I! C) |
                break;9 j' S' p- {& m2 S
        case  5: // next row PB7' g. M* Q" ~' H8 l
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;& N7 A0 J- C* e, |& T/ x
                break;& R3 M3 L- x' ^7 M$ X! R( x& n3 \. w
        case  6: // next row PB8$ m7 w6 z" J) C0 I7 A
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;
! c0 C" X# E# d' D- D8 D                break;
8 l* A4 p8 L$ h& P. E4 q/ Q        case  7: // next row PB9! A0 e3 `5 n: H" G; Z1 B
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;
1 G) P7 D2 w9 j8 O% G! z                break;; Z( Y3 |# U0 j
        case  8: // next row PA8
+ K% W) c7 S7 T6 u                GPIOB->MODER = GPIOB_DEFAULT;
6 P9 y: Y: y1 q# \3 O+ m7 R                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
$ \" z. ]1 T+ V8 @" Y3 ?1 t                break;, ]+ Q. w* ?0 @5 q3 H
        case  9: // next row PA9( _) h8 l- h2 `
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;- l. M: z) `3 K
                break;: h6 R" {2 m" _! l" H
        case 10: // next row PA10" }  Z4 h5 L1 B6 i( x! p2 Z. J
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
3 ?; ~% \9 K; F( ~) B* F( X2 R                break;. s. s8 {. J% e# Y+ r. p4 U2 [6 z" ^
        case 11: // next row PA155 C0 B! I8 ]! [0 {7 U: F
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;+ V5 E& m& p5 v& |, q
                break;: z, Q9 M/ H9 i; p
        case 12: // next row PC131 E7 O9 o2 Q4 k% }9 {
                GPIOA->MODER = GPIOA_DEFAULT;8 i0 F" e" e! ]) K5 }. ^6 r2 X
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
: H8 z6 A: k, a* F7 B                break;
9 Y: L4 C- T$ e: Y    }
* B# e. ?7 ^& ?7 K9 G    if(scan_row<13)  A- F' R' H. o, D
        scan_row++;
8 a( b$ O9 \7 M$ N    else
/ [9 v( ^* E) c: ?: U        scan_row=0;
2 r; ^8 E3 p. h2 E' I}
/ R. _9 o# \/ x( B% J( [& n
/ P# Z! U% J8 ^/ W! q2 ^* W: V' C! w9 }& I7 \7 Q# N
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑
& D3 P4 o$ J# q% A! c, K+ L1 F0 e) A/ @/ r' t$ V& V
扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。8 \) k- j* f3 u2 e/ ]7 F  }! Y
# U! C* N  m3 G, K$ Q  S/ ^
要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:
5 h- L: D. P  }4 Q2 c

( Y- m, r2 X3 E( d. ]0 B( H# k9 c
! f+ n- G; J# ^& Lconst char hid_keymap_qwerty[14][8]={
  p: z/ {5 B% W    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},, C' T4 A9 q5 ^& }6 O$ @
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},: N* z0 l0 w. ~7 ]4 Z; o( f( W" p
    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},
+ U0 @( w3 n! y* s! a  V    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},
# M# j9 V. w; u" B1 |1 G    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},
  q  ?" g8 G$ s    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},
6 v- I% Z6 X! C, Q  h; a' X! ?    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
3 N" w" F6 Y/ K& J& I. \& d    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},) \: C& s, t& R
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
5 z' i- Z. j& W# I) @4 C    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},* H, Y! N9 ~6 S+ r. s! x" @* f
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
0 L6 w) z8 o8 d2 w9 U+ a5 a" e; g5 H    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},& T# ?- {- j6 E
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
1 o# A* x! L, l    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}; L" Q1 M9 a* ]1 P3 C$ A3 X
};
- r0 A3 R4 c, C: A1 n" r3 l
. X6 g2 F% w6 S# D

1 D# O5 q# r0 c2 h" e! [* L* \const char hid_keymap_dvorak[14][8]={8 Y; g) ^8 D3 A
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},2 R7 @! R5 G$ M/ i6 y( i
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},8 o* B  u8 p, g  h
    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},( r4 s' @2 C4 j, \
    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},+ G- Q9 X5 b# ^% G3 _, m
    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},- L% n. R( N6 Y
    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},; U& ^% l5 o( h! G  h7 u
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},
- u$ }; G8 C! L0 n% D    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},; a# n/ B5 d  Z" j
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
9 E" k; J# G4 R    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
, \8 W# h& z+ r# t    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
. O# m& c. M/ z8 Z( m) G( ^    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},  T  v( N0 X+ Z( ?, s0 m
    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7}," c, Z# u7 u  o3 [# v8 \- ]
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}! D# R+ M6 h. ]3 u4 {$ ?4 d
};4 l2 {8 X6 g8 g, o
4 z& P' e. Q. D6 J8 V, r
4 h, m: C" i$ V# S" G
上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
* g) H3 y2 O; {! |" M# A  D2 ^8 Y( O/ x! W9 R/ E5 T$ }6 e
HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:
! K3 [5 r( x  G1 u
4 w$ i- G9 O+ q! U- f) m
void update_key_matrix(char row, char col, char onoff)& e' t' h. F- s; A" ?) S! p" T8 s
{
' M* [* X* B" `# y( P    static uint16_t hid_report[4]={0,0,0,0};
* c& z& _2 ?/ w2 y  L! t  ^) c    static char (*hid_keymap)[8]=hid_keymap_dvorak;4 ?) {% s7 f2 |& z7 m0 V9 u$ y
% T: I) x5 S( \& r9 O" `
( Q: v% m2 o0 k& s) D* s4 W
    unsigned char key=hid_keymap[row][col];) o5 m$ \6 p7 Q& p- Y
    unsigned char *report =(unsigned char *)hid_report;
8 v9 H' x# j% A' f  V( G! @! G    char i;$ X* y! ~' E  i% P& E' u) r# A* y& _
7 e; j; d3 ]' z4 N8 `

& P6 B( q7 @, |3 k    if(key==HK_MODE)' G  h8 B) Y( c* a7 F" k4 L
    {- N* i  j$ W8 f: c4 Z7 e+ x2 v
        if(!onoff)
& K" r% b7 L: X% Y* r: ^! C; z        {
2 @+ z9 c1 S2 d+ J# w            if(hid_keymap==hid_keymap_dvorak)
9 _# @9 I; Z9 P0 j# x' m( p            {
: n, r4 N& v% v' T( O, s# g                hid_keymap=hid_keymap_qwerty;
- k: M5 U8 c; Z5 n# f7 t9 A2 K                GPIOB->BSRR = (1<<2);- f9 e  u$ ^9 W& j* O
            }
, a9 O/ T' t2 b6 [; N7 v            else5 t( I' ?8 h7 s- W1 _- H
            {
% @7 G" R3 V" g7 c& M                hid_keymap=hid_keymap_dvorak;
, m7 a; {6 x: ]/ Z7 ^" \, L                GPIOB->BRR = (1<<2);
6 j  V0 O8 T4 @! w            }* n8 k5 a/ D. |" Q7 l
        }
  N3 I$ ~/ N& p8 C+ a" l        return;( [' I0 u! `- X4 A  O
    }
5 |" Q' H, e$ i4 X" T) N; B6 K
' M  O% c, ?- T3 M2 j8 z

! S$ U) O  D+ E    if(key>=0x80)   // Alt, Ctrl, Shift7 a5 I3 y1 k" s* y, K
    {& [* V" ]& i) T6 \# i9 w3 V/ \
        uint8_t bitset = 1<<(key&7);. Q3 w% Z7 ~2 H4 _
        if(onoff)   // non-zero is key up7 z, i2 h6 `8 h0 Y& I% j
            report[0] &= (~bitset);# G. g  `4 e" d0 f) w& q
        else
5 s. C- ?4 _/ z; ^1 N: e. y& |4 a            report[0] |= bitset;
: z1 J. j1 j" T/ n: e' X    }; I# `6 T4 M7 E: E' o
    else2 }. d* t' T# c. p$ [; a5 T2 j' V
    {  e. I/ ^- Y) |* P0 G
        if(onoff)   // non-zero is key up
9 ?9 H: u7 E' X* W: |4 ^7 S5 Z        {& \) X* |" m% k5 M3 m# r
            for(i=2;i<8;i++)$ K  u, R0 K6 Z8 u9 e
            {" d# |+ M+ k* U$ `  {. E8 E$ k& b
                if(report==key)) Y- Y; n1 U5 J# h9 @6 O$ j5 |! ?
                {
4 m" V! m2 v% E1 t+ z2 {! a  Z                    report=0;
7 Y9 d& \0 z; B- {, V6 _                    break;
: r( }  N! `8 Z8 _* ?                }
( E9 o0 ?" G5 m, f% O$ m            }
7 `" Q3 b& O; m' U. S/ l        }) L& O# L+ Y# k/ v" g: A5 c
        else* K) d8 o- n# M1 Z  K* \2 g! [5 \/ t
        {
8 r$ C2 R* Y- R: }            for(i=2;i<8;i++)4 R3 ~& K2 R+ X
            {' y: e2 }6 g7 M/ B* q
                if(report==key)
; n! [, R6 V0 _& F0 I                    break;
& X, F6 U  F2 P$ p$ |. t                if(report==0)! A( Y# ~  J3 h+ A/ p
                {% r' z- U* `2 i  j
                    report=key;; _& ]! L5 ^; V2 H% N$ q
                    break;
0 F( F/ t+ Z, ]2 C                }
  J" |0 [2 F1 v            }; A: ?$ f  z! W& p
        }
2 u  t9 ?4 Z$ g3 c- o4 |9 U2 k5 y( c    }
, u8 m& u4 s! W( F. [0 {    for(i=0;i<4;i++)/ G+ T; L) c8 b! ?
        USB_PMA[192+i]=hid_report;" @( E3 G- ~: a( p( z
    USB_PMA[5]=8;   //COUNT1_TX$ U- s# X; t. y
    if(ep1_wait==0), E! ~" M1 Z* `1 h5 T8 A$ z
    {: n6 n& t* N+ l
        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;* Z' @' a5 \' q) y3 z8 t/ G4 p
        ep1_wait=1;( F+ G# w' r! I: m$ Q! K
    }: Q. _0 F" }: [* w6 e& w
}
) `0 w; X0 l! z( C3 B1 H$ |/ ~; G7 |& N7 @
, @% \( Q% R6 U. u1 b
完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。
( B, q) }4 {7 z- c. k# V! d8 Q keyboard.zip (8.7 KB, 下载次数: 6685)
$ o) l$ f' X; }  X/ R. X
/ a7 R9 t5 p* U9 i, ~* {$ n% ]

( M+ B* B. u  ]' d% T8 g; g1 j2 ?5 f2 t
3 ?) P6 e8 J7 A  a6 d% d
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘
5 k! _7 }  l. c6 J% R: V- ?不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48
( s/ |9 q' S/ o0 [. q, m刚开始我以为要把打字机改造成电脑键盘
; C8 {. D3 m) ~* h) [不过楼主也很厉害!

# O' G; b) X; K* ?* L7 i5 l6 p哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害
( d3 Q2 y: v, M
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-9-27 17:14 , Processed in 0.189800 second(s), 27 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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