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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

积分
288
发表于 2016-9-21 20:34:19 | 显示全部楼层 |阅读模式
本帖最后由 miaozhuang 于 2016-9-21 20:35 编辑
! k) e& s  _  ^% u" S
# G" U8 U# E+ Hhttp://bbs.eeworld.com.cn/forum. ... digest%26digest%3D1
0 |6 L" `, R7 R& i/ O" x1 u这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。3 S8 B; O6 D5 m+ Y/ @

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

6 A9 c: A- e$ O2 j4 u! z% \' { 235140i3a36qivqzuvmt5q.jpg.thumb.jpg
- E5 `7 {8 X+ o' X
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。
( X$ L1 D7 x' H. ~. W* J 001734klbyoluenuwz4h4b.png.thumb.jpg
4 P. N7 w. i+ O为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:) s6 F$ x3 }" ]+ m
003625r2agx2f5v922cf2f.png.thumb.jpg ! l! e2 n" z% w1 T& K1 t& I
其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.$ v) S( v+ d. a, {* ^. l2 {' @
Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:
* y- @( S3 g$ h9 ^7 _
005836yvs0wvovwsssgd3o.png.thumb.jpg
# ]+ y- }( G: N# \& |0 \Dvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。+ z" {* g' X7 ^8 X. E
! `# I7 ?+ z% |4 d: l
我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。: q7 P: L) B1 q! o* p! i

- F* g+ E7 H8 Z1 V2 v到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。: y4 q. d3 v8 B' g/ B- U+ m: G
3 L7 a- P6 q5 Y
机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:

6 ^3 w  @7 l8 M/ q 123206vx24rfxfp2dfrpxg.jpg.thumb.jpg
  V0 h. [; M& X% l/ t( U  @----------------------------------------------------  分割线 ----------------------------------------------------------
* L+ `7 R7 b& t; N; p( Z
, g7 P+ @, C; L$ v, U先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

6 A1 d8 a/ Q- a 020011osionbunl4ui44vi.jpg.thumb.jpg
5 r( k% g0 f' Y+ q6 b( R; d轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。
! }% J1 }6 m: e+ w  e# E# B 020017j8ycmnv7788bqv52.jpg.thumb.jpg ' B& U$ j0 n" _1 Z% K9 |( r
特写,80C49
, V: V( v' m' \6 x 021040oujzuvtut6iujtvz.jpg.thumb.jpg ; ]9 X  a7 J) U% \1 D" I3 F
LED部分,使用了一片D触发器锁存指示灯状态.
6 z- C% d& Z# x- T) I/ t 022209ixzjfjc9crmrrqjm.jpg.thumb.jpg + ^5 V) r6 n' E4 a
暴力破坏,将80C49拆掉
2 X! f/ g" v% c" Z3 n! y 021113e48qq98vyohvhzzh.jpg.thumb.jpg
: X0 A6 q# F2 G拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。
) s+ Y3 ~8 l; h; \7 n2 x 021125nc9az6dj33rlds2r.jpg.thumb.jpg * g& L! |0 _+ {, ~+ f+ l+ h  @6 p
焊好元件后的板子,准备替换80C49
* \* K4 v  Z% r  ?. _ 021146zmmpmh4pav4o7g6a.jpg.thumb.jpg
; v. K( D; Z, f  U0 E# u" C4 l用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。5 R, n0 R7 }( e( J5 {0 y
021104shifhnrqbr3o5nlo.jpg.thumb.jpg ; X  E7 z& S8 k# h3 l0 j! `
这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.+ V/ D* G7 v4 S5 y! |, U& x
022003ym1p9u4ug40280uu.jpg.thumb.jpg
6 c; a- |2 U1 _- a开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。0 _( e3 ~1 L$ l6 z  ~( o* S1 d
023313kt141q9qajtol7ma.jpg.thumb.jpg
( z5 N: X  j9 w  _7 Y: \5 ~我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。
1 G* _" e1 g! O 023322nt7l5xb3ltttkltt.jpg.thumb.jpg + a4 [8 a4 s. e, ?) ^, I, v
主键区键帽就位# A" P& V2 S2 `" Y( y% \5 K
023331hin88e8wkrwzwikx.jpg.thumb.jpg 6 t6 w2 F% B" m7 P8 k! ^, X
编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。! n2 h) m. T4 c
023336wjzlgopugg1jyy79.jpg.thumb.jpg - F3 ?9 h/ y* v$ v
最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。' I1 A4 ?4 y9 n" `6 N$ R
023341sffu4j3g2323h6fl.jpg.thumb.jpg ! B7 R& [) I3 a! s  T7 A
+ J1 F/ X, y0 v5 l5 y8 {* i3 u! k
----------------------------------------------------- 分割线 --------------------------------------------------
7 e, }9 a, p! F9 n

+ l+ l8 k% u$ S3 }( f; Y; [
4 d0 q) t; B; Q9 [: d* ~6 u! u" C/ z2 P
4 n1 y1 i2 n+ X. D: ~  h7 C6 v

8 Y- W6 W  c$ y6 S
  x" r8 O6 ?8 N# {1 Z: A9 f
" ~' c2 Y( h$ L9 N
) k1 N' B7 [7 k

* A1 [% [( n$ [* }
, M# [& s& h& `1 z0 s( e% p/ O
& A' z- d6 z4 ^6 G1 ^. I
; w6 I" p: [% g3 Q6 [3 [; i
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。6 X! C/ t/ ~1 Y- ?' ?8 `2 y
80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:
. z% V) w4 P) G: m! s9 D7 L- G- H 104025nzibm2rmiomhyirm.png.thumb.jpg # N- M, T+ e! y8 E% h0 i1 t
9 I& G/ c( R* Z8 y6 h6 {+ t
其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)4 u, h5 L" G0 N; ], ]. ]
105004zkrez5houvkkznko.jpg.thumb.jpg 5 B. O# w! K& @2 {( P

2 j( \0 M/ J( n# F; P扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。
$ I9 c; C1 d' ?  P8 F( p( i9 l6 X/ O- k  s
这是我设计的电路图:, k! c8 r" u3 T2 V; f" H
110344ej2z2oo2rflo7oe7.png.thumb.jpg , ^  Z  h0 u0 C

3 y; R/ Y$ k/ ]* ?5 ~) S  bPCB Layout:5 [+ K- r5 X* N+ P
110847jjbjvt34vwt3v5bb.png.thumb.jpg
0 x! ?+ J" F! y, n
; Z* V' F8 {# R+ g; ?' i+ u8 W: b
不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O. ( o+ g) {7 J9 c% f" n

8 k/ |' }7 E+ ^. ?* W

& c5 Y# v8 [0 s& O
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑 / y+ O, K  r& l  r; ^
6 Y9 O; m  @4 v/ K# V) z
软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。
1 B7 k/ Z! w) e& p  w4 @
) g/ F% q3 g% }& d7 i( Y总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:
0 x; c. _  _. n7 Y" Z% ^' ` 113818pmrfsb6z0byt6t06.png.thumb.jpg
' s4 r( k& Y6 R9 W0 d8 `
( F$ a8 ^7 o) i) ~# u/ T
其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)" n+ d$ z9 l, _) R4 y# U. i
' _$ j: t+ W& y' e% C# K7 ?
USB的中断ISR,bare metal哦2 T& v/ a4 U/ R' w% S
4 a1 F4 j8 Y* ?" @5 O, T
void USB_IRQHandler(void)
" X: B. _9 w6 J3 H4 k2 y8 n# T$ }{4 ^( A8 ?3 O; Z/ q$ L- Q4 @( ^
    if(USB->ISTR & USB_ISTR_CTR)
! H2 h& S3 m' Y, h' P    {
- C$ M0 [7 c- R) {/ {& Q" _        if((USB->ISTR & 0x0f)==0)   // EP_ID==0
6 A6 s% K' N+ F        {
# t& [( i- f: Z            switch(ep0_state)
8 x! f) E3 F/ v$ i            {0 m5 {" S$ ?. ^% X) X  \
                case 0: ep0_state |= 0x80;0 |. }( y3 Z6 z
                        break;( `, E5 S) l3 a2 W
                case 1: if(USB->EP0R & USB_EP_CTR_TX)
; {, v* T$ _! J( }4 T                        {
+ @3 [2 B/ H. w: W, Y1 Q1 Q                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;0 a9 f2 p- b( y4 J
                            ep0_state=3;
: V0 L; X& a) h0 U. {, Z                            return;
; H# k! m' o" I4 P; A                        }
6 @3 f4 j1 W% t  D. R' r                        else
- n. \: Q6 l3 i6 `0 p                            ep0_state=0;
  y9 [) C& j7 b/ C( U. [' s  A                        break;5 i. \; o- \" P$ v- A
                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet
  n8 w7 S" l+ e& P# u                            ep0_state |= 0x80;
$ X2 V! Y( R9 |2 C4 q1 d4 a                        else7 K; Z: P1 S( \
                            ep0_state=0;& N9 z  Z5 m( k( Q
                        break;5 {9 b* d9 d4 `' D2 f+ a# Y* ?
                case 3: ep0_state=0;8 P, O+ c( N- H
                        break;
6 _9 U- `+ G/ b0 i                case 4: ep0_state=0;7 ^. B8 r; u- M# ?0 F9 {: |
                        break;8 b" `2 r- T: `* {% L* n" m
                default:ep0_state=0;" a% [) x7 L, D7 r6 W' J& g/ z0 V
                        break;
, B% h' v3 G' R  [/ }            }
+ ]7 @0 ^& ?: s$ @0 i  z6 q  F! u2 P            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
' |1 ^7 u* Q! Y0 L9 K) Z" ~, ?            return;
# i4 [2 Q4 ^$ d- u        }
- e  n3 p6 h, {1 @9 m1 O% e        else    // EP_ID can be 1
% {3 I1 y/ }! i- K        {/ G# P. X$ j# k) ~) I2 }
            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag" h" e9 c6 w1 @, |5 A
            ep1_wait=0;) i6 Y$ Q2 F( @; }, `1 Y) n9 U
            return;
) j  h; F$ O, d2 D        }% A$ O* }* [" Q1 [: H% W( R" N
    }
5 s7 i% r* ~; I; D9 e7 U    if(USB->ISTR & USB_ISTR_PMAOVR)" q# j  q0 \  K+ m$ ^
    {
8 l& i; g' c6 n; p, |* X  U        USB->ISTR = ~USB_ISTR_PMAOVR;
9 `0 A/ _8 t7 X' c    }
& V/ |2 i* I8 Y$ ]) X- C) c9 p    if(USB->ISTR & USB_ISTR_ERR); P7 T0 ~, C. [" r5 ?
    {  r% i- l( c8 a" h) R
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear  {" J$ I! h9 \# H2 o
    }
1 e- ?6 x6 ^, f0 ~! V8 T% l4 w1 Y    if(USB->ISTR & USB_ISTR_WKUP)
6 I: g8 L! U$ M! f' W    {$ S9 c5 G/ `4 p: p
        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
2 @1 _& }' O# i) [* B! p        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear, I4 f! o7 c; ^1 h
    }
% l. n7 G$ @5 R* \3 x! w; q    if(USB->ISTR & USB_ISTR_SUSP)
. n0 v  o! Y/ p; Q: l- [    {
1 ~( F( B% `4 O2 ]' p$ O3 K! ?        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend
- J9 Q$ w$ ^8 x' i0 R+ K# t6 O$ ?        USB->CNTR |= USB_CNTR_LPMODE;   // low power
/ p) D4 [& ^! `. h, Z4 j8 T5 }, n        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear, G% @' a# {2 q& s# \. J$ t7 V0 W
    }& e! o' u" K, p' H2 \0 d, Q
    if(USB->ISTR & USB_ISTR_RESET)
# r, D: _1 q8 f/ a6 q% r; `, l    {
% I3 U% z  T( D  k/ Z6 B        USB->BTABLE = 0;    // buffer table at bottom of PMA4 ]; e( K+ K8 d; g( K4 x9 H
        USB_PMA[0]=128; //ADDR0_TX9 ?+ t& K+ S; t. ]; W
        USB_PMA[1]=0;   //COUNT0_TX. u8 Y- ~" a! J% ~4 Y7 P1 d$ K
        USB_PMA[2]=256; //ADDR0_RX
( |. u# S7 n2 h8 O( i5 E2 G) H0 N        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes
/ D1 \1 {" ~4 c2 e, V1 d  I        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;
2 |- S$ J* W3 H9 a# P$ U- E( O6 S9 S        ep0_state=0;+ o$ H' A0 }! q, f. J, u) c
        USB_PMA[4]=384; //ADDR1_TX
' y5 D* P8 T5 G5 q: Y+ N        USB_PMA[5]=0;   //COUNT1_TX" i; x" T! K/ j$ i5 B, S/ ]
        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type
) P( a  b4 T2 W2 V  a        ep1_wait=0;
5 @7 h" i7 j  z* e# E5 g        USB->DADDR = USB_DADDR_EF;      // enable function
2 F* I9 e! V! b. G. [" D$ k        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear. y& s9 ]' U& v" S/ J
    }
6 L& j+ u' q. A- N' S    if(USB->ISTR & USB_ISTR_SOF)
4 d8 ]" Z9 n7 o$ T; {8 w* e    {
3 _- B0 \/ \0 l; k) E! }        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear, N' s5 }/ l$ f9 t1 {; b
    }
9 Z# n2 a' T- k" b+ D5 C+ ~}
- R& h; ]4 R3 ?& z" }. Q
; `8 t0 L# n' `. O( D8 N: |* N/ b2 @
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。
$ {# b4 F( s. \# _( C  m& o) Q0 S5 D6 [6 ~
主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。  {1 X8 i6 w  [
2 p- d$ S+ z4 P4 y- c4 W
    while(1)
0 P6 U" A2 U% o7 c1 D, m    {5 T' B4 l- r1 y; d( Z$ A3 {
        static char row=0;7 m$ g$ }) V4 |4 W6 C- D' v
        __WFI();7 n- g) P4 t7 p  J2 @3 Q
        if(ep0_state & 0x80)    // request data processing
6 M$ I$ }8 K$ _. o        {
1 ^" D4 y& \! z# I# E0 A3 w  n            if(ep0_state==0x80) // SETUP phase
$ ~4 S+ Y  Y4 E6 Q' b* C' G            {4 d  d. |% p3 \4 C3 g) D  y
                if(!setup_packet_service()), c2 v- g( m. y& `( x+ q' b# N
                {
0 c* R/ m9 F2 ^$ a* Y8 N+ e" |                    ep0_state=0;# a, v2 a% h! q* R7 @0 L
                    // not supported
* |2 v$ G1 _0 P  ^1 z* O8 @% I5 d                }
- I1 p" p$ O( Y" t0 A/ D                // ep0_state should be set to 1 or 2, if processed: g+ ]$ ]8 S. l" w3 c
            }
* v6 l4 ]* J: b4 H2 h" t            else    // OUT phase
) |: H1 d0 E! r) e. ]            {
" q% u$ C2 d: W                // process data: y6 p+ {) D. c5 ?8 S; n) o/ q9 [& j
                show_LED(*(uint8_t *)(USB_PMA+128));/ j0 y  {5 |8 M3 m9 A( l
                ep0_state=4;1 D0 t# }5 i7 ^
                USB_PMA[1]=0;       // Zero length DATA0& }+ T' c! P7 o8 d
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
; A4 d- i* o% x. K            }
2 K0 z9 M: P" h        }' y, Y3 H$ J3 o
        else0 n" O3 `( x) g# Q3 d
        {
7 [5 T9 {5 k. X* I6 ?            if(usb_address && ep0_state==0)
6 W3 y; V. A6 t( o# ^. y7 J            {
  k) o* u3 g% j/ X1 F                USB->DADDR = USB_DADDR_EF|usb_address;- K# Y- ]8 e: a7 X2 w- K
                usb_address=0;
! D+ o- {" D& j" B5 s, X            }" e; a+ [% _2 V
        }
' a1 Z. \3 N; m/ t/ K        if(row!=scan_row)   // new scan line
+ }6 v8 N5 E, x% P0 {9 Q/ F, ]4 [/ {        {
; K. x% e- T7 H3 o% l4 d) b; ]4 B            if(key_state[row]!=prev_key_state[row])
' v. x6 H  p1 r) c, A            {
8 j# Y, b- |; M; I+ \                uint8_t test=0x80;
$ L/ f0 a0 D) ^7 J* }( m                uint8_t diff=key_state[row]^prev_key_state[row];; y) U$ }" D4 ]) Z7 Z
                for(i=0;i<8;i++): s- L: G1 z4 e8 d* g# Z
                {
% g9 i6 K7 j+ Z. j! W; g3 K                    if(diff & test)
4 A, c5 K6 m0 I$ d: e                        update_key_matrix(row,i,key_state[row]&test);' u( ?3 }) c6 a8 e, q1 V
                    test>>=1;0 y% v9 ]$ h( d7 \
                }: B2 s8 K% n3 h+ X# O0 _$ Q
            }
$ U( I. g: s+ b' Y0 J            row=scan_row;" s& F0 }" {$ o* a. s% l( S- R
        }
+ z: P4 @' q5 d. E4 K' C# M    }; i, L7 r( L7 D  ?. e- P5 I9 o- ]
0 P6 b# `! a5 \4 Z2 A" b
+ b7 Z  O2 A$ R/ R4 c
EP0的控制传输,把用到的请求处理一下2 a- b2 Q# N' Y" z
char setup_packet_service(void)# @1 p1 a. g2 U5 ?: [0 w
{
3 Q' h# t1 x- j$ w5 M# K    if(ep0_std_req->bmRequestType & 0x20)   // class-specific
1 j' Z9 V+ |: Z    {
; |& ^$ R9 L1 Q( T* ^        switch(ep0_std_req->bRequest)
9 C+ n& o( d/ `; S( c0 C        {
% g8 K4 q2 Y9 \* @            case REQ_GET_REPORT: break;
. c9 }) U/ _; M: Q- l1 M+ r            case REQ_GET_IDLE:
- c+ `7 L0 p. E" U3 {                USB_PMA[64]=0xfa;   // return 1 byte" H4 K- _# ~7 F) M6 F9 F- V5 L
                USB_PMA[1]=1;
# u- ]; w) y4 p. v* R2 g                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
0 ?# y( i3 f. f. o& g                ep0_state=1;  s) P, y& J: u: X6 i1 V$ @% Q
                return 1;
# g  ?& o  V9 S, W$ Q                break;, V/ D% j" U9 N$ m4 r7 w
            case REQ_SET_REPORT:
" h1 \$ Z" R! t; {# W/ \                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
* j7 x' m" i7 j2 u5 |# Y                ep0_state=2;% K5 T, D- A4 b" X; f' a3 g
                return 1;
* q/ ^2 o& ~2 s9 w- `6 R                break;6 S# D  e& A0 z7 F
            case REQ_SET_IDLE:
6 T% b) N4 h& }4 {& P/ O                USB_PMA[1]=0;   // Zero DATA
, L9 w+ K2 Q8 J$ O$ v; h                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;9 [) d# E4 }  J8 O0 F" n
                ep0_state=4;( r8 _) L6 d: w$ ]" z
                return 1;
% N0 N9 t$ D2 Z% b) T, t, W+ r                break;
$ M& ^5 k1 r# }: h        }5 `7 M( a1 B9 F) r6 N7 k9 O' P
        return 0;
/ q. Q% g9 Y2 W; z$ q4 z  [# j    }$ r- y3 O" p9 I; q3 C$ Z& a
    else    // standard% A  V) }; b8 `) i
    {  E" d! p, |+ B$ `) J
        switch(ep0_std_req->bRequest)
% y: x; g* F5 r1 s        {
6 E  V% D/ V; A/ T. m. O' |            case REQ_GET_DESCRIPTOR:. N/ L) v) D5 d) Z; s( q
                return descriptor_service();
- t4 U* T- P. Z. A* W& w4 i                break;
0 O# k5 a0 M1 R1 K) M- u$ _  I            case REQ_SET_ADDRESS:) o$ e& G- B8 X4 [5 i3 J% B  C! @
                if(ep0_std_req->bmRequestType!=0x00)& x  M4 s) k8 i$ |$ x
                    return 0;% }* m3 b! s: f+ b) i
                usb_address=ep0_std_req->wValue;+ @+ Q0 A# P$ {/ d" z* l
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;  }  z3 @1 \7 i
                USB_PMA[1]=0;       // Zero length DATA0
/ d2 {: ]9 Q4 U4 Y0 G$ ]: b                ep0_state=4;    // No Data phase
6 C/ @7 F* @  \# z                return 1;: p: }1 Q+ R+ k$ m. J/ R$ P
            case REQ_SET_CONFIGURATION:$ t, D% `: E6 Z: @- S
                if(ep0_std_req->bmRequestType!=0x00)3 A: B0 G. B: y5 m2 K. F$ p
                    return 0;
" s4 N" Z) t$ V& o                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
; [/ M. G7 g5 U4 v                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;4 M" w6 Y& ?2 e+ P% ]" x3 H
                USB_PMA[1]=0;   // Zero DATA
5 K  Q8 @" R8 z8 ?, V                ep0_state=4;    // No DATA phase/ P8 U; {' }& o  g& M
                return 1;" W" K- |+ x& }! V
            default: return 0;4 O9 A) f4 u. Z" @' v
        }; Y9 u, ?2 g( z- e: X4 ?$ b& r
    }0 X5 z+ X" n, u' M8 V: y5 E7 _
}7 P. U# _  V7 l& i, P7 y: X
) p# ~2 P9 e9 \( a" S( ?

  J6 D- h7 V( f- C: _! T7 G5 Z( S1 [4 j6 N8 h
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的8 s* x% j- A" y3 A$ b
char descriptor_service(void)
8 |2 Q& k, u$ t% K0 u% N{/ H- O4 @, j1 F$ Q% T6 x" r
    switch((ep0_std_req->wValue)>>8)0 C6 r- s, u; L  B9 }
    {
2 O1 J1 z' r; r5 L3 o6 J, I        case DESC_TYPE_DEVICE:
( }8 V5 }2 H8 E% C7 b5 X            return ep0_preparedata(&DevDesc, sizeof(DevDesc));
$ }/ O9 E1 D  ?0 ~$ N1 e            break;; K# t" g7 t. H! C1 f0 q# E9 j
        case DESC_TYPE_CONFIG:; v' c1 U6 J! i6 M; v
            if(sizeof(ConfigDescData)>ep0_std_req->wLength)! G  q- F3 l2 i
                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);
  n" V" O% m: ^0 P! a  ~1 _+ Z$ b            else7 t. j+ Q5 Q3 e1 l( [
                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));& ?5 I! y# j" b4 Q1 @3 ^" B% U
            break;4 Q0 q% U/ J- U% o
        case DESC_TYPE_STRING:4 D4 ?% T7 d, p- R
            switch(ep0_std_req->wValue &0xff)! F8 u+ ?8 `8 @) Q# F- {
            {
3 i& F3 V$ e. N' ^' z- J# Y0 ^                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
8 u: y' b- w) B                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));( O4 \' V* j3 @. @8 [
                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));
0 p4 Q" J% ~0 g- B- }                default: return 0;1 ~0 W8 \1 l7 o% t. ], U% C& l
            }5 n! k& ~: a, i$ J5 }. e) d; r
            break;
9 T7 r, D7 O. e: Y$ D        case REPORT_DESC_TYPE:
9 Y/ z- U3 I  E% |            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));- A5 P: g3 h/ U7 S) f8 Z
        default:4 y& b( C( i$ Q1 N
            return 0;4 k4 o2 B. q, v8 m, n
    }
% ^: L$ Y  ]( i+ m}( E: I# J# f' J1 @' {  l9 M
下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.
- V+ {3 M$ d" E* N. J1 i; Wvoid TIM6_DAC_IRQHandler(void)
: p1 P- E7 J/ B. k{
. j' t* n" }+ ?    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);) o' }) d8 q+ {, R! Y# x* |7 O1 V6 M- v
  d% \- Z, _& w3 e

! f+ U& ~* R% ?2 h6 o7 `; J    TIM6->SR &= ~TIM_SR_UIF;% `+ G  @! a6 f9 s3 G- [" A
    prev_key_state[scan_row]=key_state[scan_row];! u9 s/ `+ J' D) ?8 H8 I4 q# K
    key_state[scan_row]= *PA_IDR;   // update key states% I# i7 d7 G2 \+ ]
    switch(scan_row)
- c! S- ^9 Y" A2 U" x    {4 m# p! w! E: N' }6 d
        case 13: // next row PB14' ~5 z' k6 F" x) J
                GPIOC->MODER = GPIOC_DEFAULT;2 @9 ^: V" X; g; t) |( P2 D
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;
! o& u" x8 X4 q$ B8 q! P2 w/ W- r$ @                break;' o  v- r' G. |' t! o
        case  0: // next row PB15- U8 \9 q4 [+ C9 |3 B
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
+ T; [1 Q' o9 E$ M4 ]; D* n                break;
1 @' r" |0 j1 F" e        case  1: // next row PB3
/ @0 a% u/ o0 n, K                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;0 C( r8 g! U3 A# A% r* V
                break;
. D& u6 N/ u: i: x+ ^' }        case  2: // next row PB4
. h# M1 |/ [% i# `  L7 V/ c                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;4 ^9 d: I: N. I& R7 e
                break;
0 {9 \. Z! M0 d3 o# ]- I        case  3: // next row PB5
" w% A% N" W6 \  u' ~. i" g                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;6 C* s+ ?. J/ |3 d6 C$ r. U% f* p
                break;
/ o3 @) j" m5 ~: i        case  4: // next row PB6
9 X; T3 J! e( z7 |6 u7 j0 ~; ~                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;
1 x: k! C) [* y7 ]7 M& r                break;
- \& b: }* z9 A# z9 C% D; M        case  5: // next row PB7
5 n- ^+ {/ P# R1 `% h                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;+ b/ a) M: n% s* A- G( z6 N
                break;, H' ]/ G2 F. H0 C
        case  6: // next row PB87 }5 ~. @2 i/ ~' P' F
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;
& A3 |  ^1 S+ G) y3 v6 ?# ]                break;
/ D+ K# r% p! G0 e        case  7: // next row PB9% Y$ m* _3 A) a/ e; u' @  g
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;: g# G( S% G! P
                break;& x/ {5 {% _* T' g2 b+ E9 j/ Y( l
        case  8: // next row PA83 p8 c/ E( W% q6 _' l7 Z
                GPIOB->MODER = GPIOB_DEFAULT;8 M) j% A9 \% w7 Z# J& F' `. _" O
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
+ T# z" O# D. Z7 E% k" J( Y                break;
7 w6 D2 e1 }$ Z! L+ N8 C# }        case  9: // next row PA9; _2 K; x& q! {
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;  [& X. d9 K4 ?( `
                break;
% X# y. V' o. Y6 K  \5 \# P        case 10: // next row PA10; V# _( r% e( V' o, `% P; p* h
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
# k4 i! Q8 G% j- R3 O. J4 n% J                break;/ t% U& O6 a& P6 q% J% m* a
        case 11: // next row PA159 i/ A% X; }( g" p
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;
" T: o  e/ b' I                break;, s7 P9 s: w, J4 {
        case 12: // next row PC13
' o1 a  L1 s1 v9 F* f" F                GPIOA->MODER = GPIOA_DEFAULT;
5 m% u; ~0 U  [& [, O' x; A                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;: j( F6 `) x0 {+ L5 h9 p: d. Q) v
                break;9 x- L( H" L4 B) B; W
    }
4 k$ k$ c) v; d! f    if(scan_row<13)# ~. b, p4 a; s" @4 Z  A+ R
        scan_row++;
, @% Q0 O9 f2 t    else( U: h5 c, K" ?2 f( ^: q1 s
        scan_row=0;
( |! b( K! x0 C}% a5 ^2 o" C2 O: h

, e7 B* c0 j& }  @6 a0 C, R, Q# v
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑 ' b# v5 V9 X) h7 p2 [; _1 @
7 I' M2 X" x+ D$ p# M& K' t
扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。# }! ~- Q3 j1 k
% o' x: y6 q7 F  _: [4 w
要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:

! V, f( C: y4 G2 O) Z
4 o3 Y: {$ {/ G- Q; ?1 f  a+ i2 l6 _
const char hid_keymap_qwerty[14][8]={
0 m; u7 Y9 \! H  K1 V% E    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},# f! P/ L% N" C, c1 p! e
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
; g0 ~# ?/ Y& \8 g    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},7 X" ?+ @0 R! ^+ s& {) H0 U# G
    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},( e7 g( k0 }  `# ?& P0 A- |
    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},
* _1 A! F) f& {5 P( J5 C) i" H" A4 h    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},  b" G3 ^3 F! o, q5 j
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},5 I- b, I# r0 h, P7 n8 N
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},8 j4 g5 o9 o  A5 g/ {& \) q
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},* U6 G9 J( k3 _9 \5 w  O0 K3 R- J
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
: m! ^& X; u8 b. t1 }- g; \  x0 U    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
# b4 ]! P' i6 z% r9 }    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},+ V4 N4 E( E$ T2 e" v
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},2 U, s% H1 [% V! S  d7 k# Q# K& i4 F4 r3 _
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
4 y' W6 V# o* V1 X4 B) ?};% E- P0 @3 E+ N( r2 e+ v( d
9 Q/ o9 L% ]8 f5 X/ N" \, J, J

7 {: F* T; P) {1 M% Rconst char hid_keymap_dvorak[14][8]={6 f( B& A: `: N  ^
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},/ r+ o8 u0 j, H' \# ?  L; Y4 S. A
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
) `, l, I4 T5 y4 u  B) l    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},% l1 \4 a0 [" [5 u
    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
. B$ c( w: M2 p4 r" W: I( o    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},; F9 a: r, w7 \
    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},+ W: A, m( y7 D5 V
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},, ~: B( S1 a. G- O# z! g9 j
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},
7 E0 n9 p$ S/ i5 V1 I: H    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},8 ^" e) V( d8 ?' g4 Z0 y) J
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},* p8 K  E# t' g7 F3 y
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
. x1 ~4 o3 {4 X+ l- ]    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},+ U* v: i% S3 Q$ P
    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},
3 S9 \5 b! }1 O1 B2 `    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}7 x9 l  ]6 L& D" R: A7 n4 D
};0 j# P3 A) u+ R% G8 n! S
  s1 |  I* k. t; R8 ~6 R9 x$ \

) x  o- M) W! i: D# y上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
; o; G3 A- y, Z1 F3 R7 H9 H2 L" J( i7 T6 z
HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:

, W5 ^' m% Y8 J  s( P9 R/ T: D9 B0 B% W2 ^2 P) y0 _
void update_key_matrix(char row, char col, char onoff)$ `' W$ g3 v$ R9 S9 }" {0 l% n
{4 ~$ {( N0 E$ @* z7 d" J* F5 b
    static uint16_t hid_report[4]={0,0,0,0};
! O  }- O1 q7 r  A) g" |. b; o    static char (*hid_keymap)[8]=hid_keymap_dvorak;
  v, W) C7 q% d+ }+ \2 ?
/ D/ o  y9 J- R) V

# C9 w4 E5 ~3 z2 x# r    unsigned char key=hid_keymap[row][col];
$ t8 X. p1 a: ^5 d' r+ ]: e, X    unsigned char *report =(unsigned char *)hid_report;( D2 L" H1 W. o3 F8 O* q
    char i;, h' j6 V" [) \/ F

5 G& \; x% |( X1 N
8 t) F/ a3 l2 f$ v; K
    if(key==HK_MODE)
/ |- O3 M/ a# `- n7 C1 g* f    {
) q" a  [( s5 T9 I( ~        if(!onoff)
4 u6 M# _: y- O4 l: N3 Q! b        {
" U" a! ?8 A! h) H5 J$ n            if(hid_keymap==hid_keymap_dvorak)& B3 W" C5 [: e3 P- ^# q4 n
            {/ Y) z0 ^* f' C( |
                hid_keymap=hid_keymap_qwerty;( v5 T; l( H4 z; N2 x
                GPIOB->BSRR = (1<<2);
7 [5 k+ ^$ H) K) M0 G% ]            }
3 i& c, H& `0 t3 K' D            else! `4 G0 V* u0 H2 g8 t! K
            {
$ P, L  B/ q, X8 U+ h  i                hid_keymap=hid_keymap_dvorak;
+ R0 r/ ?  N! k                GPIOB->BRR = (1<<2);8 o9 T; D3 B, {' I+ ^
            }. W% i* I  V+ Q
        }
4 ^( V+ V% j& v1 `( f( E        return;
6 M4 i* }; O! ~1 i; M2 p1 m    }7 b1 N7 l0 j) U5 j: j0 D2 [
9 X3 r9 {. y( p! F' q
. J- J5 ]' i% g6 e
    if(key>=0x80)   // Alt, Ctrl, Shift
$ L" [4 Q1 u6 d, c! y% u    {3 b  X* u0 E* m/ {
        uint8_t bitset = 1<<(key&7);
% r  }0 T+ A0 W- A" |        if(onoff)   // non-zero is key up* R) N/ Q3 U6 z' b5 z
            report[0] &= (~bitset);
" x3 o5 |4 J, ]) S+ I        else: a5 ^+ Z' u+ o; ^1 F
            report[0] |= bitset;
) i. I! ~$ ~5 T( g    }
5 |2 n+ P6 |8 A( X& {% A; f    else
, Q: Q; F: N+ i    {! Q# H! J: ]: J$ S
        if(onoff)   // non-zero is key up
/ f& U& n) r) u8 z! n" @        {- v# t* v4 u5 f* B- p% Z
            for(i=2;i<8;i++); o6 \, q& p; ^8 X% l( Z& i
            {
# r" c9 m2 c# K0 U                if(report==key)
) ^7 R& M* b* g' \+ O1 I# B                {
7 Z) a, G1 G4 H4 `7 K" B                    report=0;
3 f; e- M! x$ ]+ f6 ?                    break;
5 U5 X  _. [8 Q& \; u                }
% ~  z  o" t( C  x# Z( c% e            }
+ a7 E( P, L. X        }
) T  p( R# k3 {5 q, P( `$ H  R& u        else
8 u$ S% c. E7 T! j0 ]  q) u. H3 T7 s' y        {5 M# C+ P1 Q6 q/ y8 D4 G# I8 y
            for(i=2;i<8;i++)) [: |% q; Z; ~, D9 R2 o- O$ K) T$ ^5 x
            {3 o0 Q- `+ q! T: |
                if(report==key)6 F, P7 q' q' |6 X
                    break;
4 K6 \( O7 a0 D: g0 }+ }' B8 e                if(report==0)
& n  R) Y% ]( p7 B' n                {
% o; v! f4 W0 \: c  h/ T$ C9 _5 W                    report=key;3 ]6 a0 {" h) S: n: s1 d  _
                    break;
0 Q7 g3 S  ~7 N& Q                }
- X3 Z! Q% c( W+ j) c            }# l4 e3 C- u$ H6 K# w4 o
        }
4 L7 ^: R2 `; ^' r3 l1 V    }
: S/ Z  `9 v0 l) D    for(i=0;i<4;i++)0 |/ ^6 d  M. v
        USB_PMA[192+i]=hid_report;, `$ I; A9 z: n. x" y& |
    USB_PMA[5]=8;   //COUNT1_TX$ Q0 Q  q) x  h
    if(ep1_wait==0)
1 t6 k- N  v1 A$ o6 S& \6 N/ T    {
8 T% g: U/ b7 `3 E- W        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;
& b9 X! [' W  {        ep1_wait=1;
. W" K2 i$ v5 O3 u% [0 P& f    }- F! Z$ ?& f: X( \' ~
}
+ [9 z! U) A. I  U4 ~  M0 E8 }, z6 p. Y: y  r3 P* a( y' \8 A( s

# z; M% N7 W7 K8 G完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。
% j- D; ^( o8 x8 j7 E3 G' A keyboard.zip (8.7 KB, 下载次数: 6209) " l6 B2 y) x; E; X; j) e

* b+ }2 n. Z- d# u! F( Q# W/ ]  `* m% i/ P7 p2 b
  E$ S7 ~6 s$ Q- g" p
6 r9 {3 d% t- y
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘
! ]6 H0 d; o2 ?不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48& N  X6 u+ c# ^2 }) {
刚开始我以为要把打字机改造成电脑键盘, J0 o+ _1 E# v) u8 A8 X9 l: u
不过楼主也很厉害!

+ F1 ?5 [. c1 r  f" Y+ @6 G  k哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害5 i6 X1 t5 T, n8 f! A: n& q; r9 a
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-7-12 10:06 , Processed in 0.173905 second(s), 27 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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