找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
楼主: miaozhuang

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

[复制链接]

24

主题

39

回帖

288

积分

vip

积分
288
发表于 2016-9-21 20:34:19 | 显示全部楼层 |阅读模式
本帖最后由 miaozhuang 于 2016-9-21 20:35 编辑 % B9 X6 ^2 T3 g' E8 \/ B
. F* m3 i# r0 t, ^- [+ E
http://bbs.eeworld.com.cn/forum. ... digest%26digest%3D1- m$ n, l7 I. E9 P6 d. F
这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。  `# v! x/ g  `" R. {6 u, M
9 _+ h4 M$ `7 _0 i: Z2 l
在看这个帖子的诸位一定都在用计算机键盘吧。键盘上的数字键是1到9从左至右排列,或者是右小键盘区那样三个一排有序排列,反正规律很明显。但是字母键却不是A,B,C...到Z这么按字母序有规律地排下来的。我刚接触电脑(其实还是学习机)的时候,没在意这个问题,觉得是要盲打嘛,反正对两手的手指头来说,按字母序排列并没有什么好处。于是用多了这些排列也就记住了,从来不管它为什么要这样。其实,PC的键盘键位排布上是延用了打字机的键盘,这是设备演变过程中很自然的一个延续。打字机的历史就要早很多了,我没有亲见过打字机长什么样,而且,咱们汉字是铅字打上去的,和英文打字机方式完全不同。

6 v& h( _' }/ O8 C; n9 _

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

( T/ t5 C! E0 s到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。- K6 ]% K8 R0 ^& t- T9 g

) t  d1 o3 v9 Y; M机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:
5 U5 T: O: R; Q3 i
123206vx24rfxfp2dfrpxg.jpg.thumb.jpg 4 B4 L8 ^+ l6 j* ^% n0 |
----------------------------------------------------  分割线 ----------------------------------------------------------' L' w/ Q$ u, j1 W7 ~
5 f3 h- c3 D% n% e
先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

; [4 {  i  Y+ I' s& d$ O, ^' ? 020011osionbunl4ui44vi.jpg.thumb.jpg - t. h) c1 e3 H/ q
轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。
  o1 {6 l4 z# J# r 020017j8ycmnv7788bqv52.jpg.thumb.jpg
. V% x  Z  k: S6 T6 Z特写,80C49& R1 Z4 s6 \! C4 t$ M$ I5 [# U
021040oujzuvtut6iujtvz.jpg.thumb.jpg ; K" F) [! k4 Q- M+ M: z
LED部分,使用了一片D触发器锁存指示灯状态.( k8 c; N, t% o# Z  c' V5 j5 ^4 x
022209ixzjfjc9crmrrqjm.jpg.thumb.jpg
( n; X( S% z3 [1 n# K) Z5 G暴力破坏,将80C49拆掉
; r9 [5 s# I9 ~+ v8 y7 N 021113e48qq98vyohvhzzh.jpg.thumb.jpg & y- x% \7 y3 q4 E
拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。; ?0 l# O7 q: g+ k' ]" w+ J, O
021125nc9az6dj33rlds2r.jpg.thumb.jpg 8 j( ?6 U: g( c' ~
焊好元件后的板子,准备替换80C49, r2 f4 r* D. C" F/ J" \
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg ) Y: G" e5 V& y* Q
用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。
# P7 g, k# Q( R7 H/ B. ?5 o& o7 w 021104shifhnrqbr3o5nlo.jpg.thumb.jpg 0 m; {+ N/ R; V
这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.' t! t; z* H0 H3 p( ]
022003ym1p9u4ug40280uu.jpg.thumb.jpg 5 P  g5 O/ o: v' ?' z' `6 Y! [
开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。
* {6 M: I- d: C 023313kt141q9qajtol7ma.jpg.thumb.jpg
  Z: s- a" Z/ m$ O6 [9 ^9 ]我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。6 z6 }! P  F, u2 Y
023322nt7l5xb3ltttkltt.jpg.thumb.jpg
1 [9 t( h" l) ~) n
主键区键帽就位. }% Y* k8 k+ W9 U# `% Q2 e
023331hin88e8wkrwzwikx.jpg.thumb.jpg 9 t# w6 `, Q+ v5 Q  Q
编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。
' F1 \/ B# [: E* C 023336wjzlgopugg1jyy79.jpg.thumb.jpg
5 c5 |9 G7 @! ^1 b# Y最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。+ y. U0 l6 M  i1 C; x! S' p
023341sffu4j3g2323h6fl.jpg.thumb.jpg
0 C; }! x+ p: u; c4 D/ J

1 ]; Z& s- S, l# ]& j& p& T+ C4 H----------------------------------------------------- 分割线 --------------------------------------------------# o* h4 \9 `4 N! w1 l
' u: E" h( @2 `6 H0 h" m' e( R

. m4 ?' {. R9 ?: T7 ^: K- @2 `: J# r  t# y( I; l' i2 K2 E! U

; x! j7 Y( O! l; Y/ _( i* x9 r) i
; M& v: u4 Z3 P) ?. U4 a

; [3 n; i$ S: n
) K3 G6 w! {) p5 S# A/ I- K0 k; I* H8 v* q: V* S5 O: m

( V" s$ e* ]& t$ N& ?. c/ b* Q
. G6 f3 o8 Z3 Q5 X! G  W
6 `6 q3 i7 F3 X/ `9 P/ V
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。
- L# s7 U. d4 X- [) t3 s* V80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:
& {- D! ^# F# D 104025nzibm2rmiomhyirm.png.thumb.jpg
. S0 }" J5 x9 t7 y4 g' n# D
, o( k) c; z3 ]' Y
其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)4 F2 X0 s; I4 ?8 t5 F2 ~& `
105004zkrez5houvkkznko.jpg.thumb.jpg . y" s3 `; y8 o0 B$ p4 v
9 v: y, T) ?" Z) _" H3 t3 s
扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。
' g6 A1 T4 K. m# j2 ?" M+ P' X6 t& Q" A, C- k  w+ V0 k
这是我设计的电路图:: o  H; _. u) N; M0 A: S* A. P
110344ej2z2oo2rflo7oe7.png.thumb.jpg 6 N- U, [9 M' ]  [% l" ]- A; u9 ?3 @
- d, f4 }* X, X1 {
PCB Layout:
; G& S# f0 d6 J+ O; n 110847jjbjvt34vwt3v5bb.png.thumb.jpg
8 c( f- D3 L% p
% t) y/ G1 ?' m( _; w. @
不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O.
/ [" h3 P; @! I
/ H6 s  D4 P8 T! `

$ U) h, e2 u% k  O
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑
6 x7 r8 {' n2 f" [: l3 z# f' G
3 a8 a: p* J( V  o- W: D1 }  z软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。
4 W5 V9 r$ j: ^& |; H" A  o  K6 E7 k. M5 J+ j% }
总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:
8 i% d( N) H8 ~ 113818pmrfsb6z0byt6t06.png.thumb.jpg
/ e- D) R2 k( B2 r$ ^: d* @

! I! y( ~- E- |) f其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)& n& ~3 x7 |9 Z
" N' G; X! R" }
USB的中断ISR,bare metal哦
4 u' ?9 n( c$ |) E* ]9 v$ j
  d; `3 x( e2 d: W
void USB_IRQHandler(void)
! e7 e3 F; J- F{
: ^4 C# v8 Q: z8 `2 q' e$ i+ ]    if(USB->ISTR & USB_ISTR_CTR)
" T: z: x+ V* h* M    {
6 G" D6 Z9 @9 R+ @        if((USB->ISTR & 0x0f)==0)   // EP_ID==0/ l, @6 `$ Z7 N: z
        {
4 }3 a; w/ w& n- D$ r            switch(ep0_state)
- R( |2 O% v* h" J6 o8 }9 A            {! r: b* v; @  G8 b/ J9 p
                case 0: ep0_state |= 0x80;. ~& A$ |) @! ^; L0 S5 |, ?# ?
                        break;; r0 z$ j) m0 c# C4 e7 H6 m1 k& T
                case 1: if(USB->EP0R & USB_EP_CTR_TX)" e8 k2 z8 q; ~3 N1 |! X
                        {
9 C# ~  v' b3 n' t. b; P                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;9 S) Z$ f+ K2 y* e. D# ?4 k
                            ep0_state=3;8 s( q. Q, B, D5 E0 r  q
                            return;# W& ~1 k+ v3 v5 \7 L) g+ q& {) f
                        }
) H7 R7 t& q3 Y/ g6 v                        else
2 t6 N" L2 I& {1 y/ X; i                            ep0_state=0;, d% m) T6 \, S5 t6 Z+ |8 y+ T: r, e
                        break;
: p/ ?8 }1 R5 }( C& d                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet; c9 K1 P9 Z: }% Y8 x
                            ep0_state |= 0x80;8 L$ g! g7 P' k/ p
                        else* D8 V  v  J8 X
                            ep0_state=0;
- b- w! r2 O. a9 m' W* E                        break;- _6 s9 Q$ A! e3 G8 ^% y5 h
                case 3: ep0_state=0;; c; M5 ~3 L1 i+ {- f( k
                        break;
; [3 E1 B6 i( J- x8 ]! q                case 4: ep0_state=0;3 Z, ~4 B% C" t: [( P$ r7 {9 t! X
                        break;
* @2 `5 c% I* ?                default:ep0_state=0;
$ Z( _7 J, Q# d4 q5 W: {, Y  M                        break;
. L" [0 W! h: i6 P& O. _0 d: C  F0 u            }
3 ]2 V- P# {: q- W! Z            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
) z& m. U1 D  o( l1 D- \            return;& ^' P  @% ~0 D! [' Q/ U
        }3 L9 H  n+ i, [, N% [. ]
        else    // EP_ID can be 1# x5 d8 W6 U/ o* N' t% V
        {7 P" Y/ O7 d7 Z0 ~7 p; {
            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag6 }1 y% g6 o% Z- m
            ep1_wait=0;
  N3 j% N# c; C9 x/ U            return;: l  k& C8 o' m% A. B
        }1 m9 k2 ^8 }/ {: K
    }
7 d& G5 r( U: X6 S' [, W' I; w6 S    if(USB->ISTR & USB_ISTR_PMAOVR)2 s1 [4 M6 C$ D% M% @8 P  y
    {
" s! }( h2 H) p3 _$ q0 X: z1 ~        USB->ISTR = ~USB_ISTR_PMAOVR;. m3 N" K$ C, Y8 |/ @7 H2 Z  D; |
    }
; M4 _+ v1 n' S+ U    if(USB->ISTR & USB_ISTR_ERR)8 _2 {7 m5 w6 N% M5 Z' R# {, _
    {6 r' P/ h1 u- _! t! B: G( Q. k" l/ K
        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear6 V- q9 i. O8 |6 a5 \, m& u
    }
" }5 l4 s8 L6 z) Q' _, ~    if(USB->ISTR & USB_ISTR_WKUP)+ s2 Y3 j) k) I
    {
# b1 Z2 x2 I* `9 V        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
. W9 u* x( E) ?" g9 y- G        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear
' m' J3 t4 G/ s7 d0 S' J    }
! }4 k4 Z2 K3 A8 E! ?* }( c    if(USB->ISTR & USB_ISTR_SUSP)- e: @9 ?! Z) w+ _3 E4 f' P" K! L! a
    {* T' l/ P( s# X( H# f" ^* p; q& W
        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend5 p. V2 l) l5 M* |
        USB->CNTR |= USB_CNTR_LPMODE;   // low power, s$ s, h% L8 `1 C' p
        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear
6 [" \9 `+ }9 A; s7 p- u    }
- O( `5 K( X8 t7 d) _! y* o    if(USB->ISTR & USB_ISTR_RESET). W0 s5 [' S* |9 l
    {" K! a( t/ Q3 [- z) u
        USB->BTABLE = 0;    // buffer table at bottom of PMA$ t3 m( T: o" x$ ^% a) w% y6 e
        USB_PMA[0]=128; //ADDR0_TX; N' u1 t8 U; h( W
        USB_PMA[1]=0;   //COUNT0_TX
- f' _; L8 W( E+ t# ?& `6 [        USB_PMA[2]=256; //ADDR0_RX
$ P6 {- H$ c* I) Q7 Q        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes
0 k+ v5 _0 }1 B; o6 K& S/ k2 C/ W        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;' E& m! I2 {( l4 v3 v5 a( x
        ep0_state=0;
( i5 J; @5 U2 ^        USB_PMA[4]=384; //ADDR1_TX
; z' ~. j* w$ [) s3 |- A$ I7 o: b        USB_PMA[5]=0;   //COUNT1_TX% h) O3 ~6 w: T9 u
        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type5 E0 C0 o! _) s8 ?7 W5 @
        ep1_wait=0;$ A3 G, Y" F0 ^8 U, F4 {$ @0 a
        USB->DADDR = USB_DADDR_EF;      // enable function; {% K6 j  I+ H. i; \' a, ?& Q) C
        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear: `2 s+ H: w- \
    }
2 c7 G% \# m# a# T    if(USB->ISTR & USB_ISTR_SOF), O* I' v5 z/ Z
    {
2 h7 N  h! ]3 ?, z" w        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear- g1 H# Q' f) T
    }9 o$ _* T; ~3 U: _9 l7 i
}" \$ k! q5 P3 H4 J
! c( [5 O) ?6 B) t5 R1 }
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。. }6 A4 b* M7 m+ H- d4 W  z# |) i

6 H1 g# a1 N- S% J主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。
4 @) L* U- O1 C
. f- }. F" _9 r1 Z$ i; G
    while(1)8 u& C& Q8 f0 g/ R1 Q) Q4 l
    {
4 d( s# ~, ~3 y, d' b, t- l$ j* s7 S        static char row=0;* ^' ?8 }6 ?6 K$ L4 \" d6 P
        __WFI();& M0 D0 \& g' M1 i" N/ B, N' h
        if(ep0_state & 0x80)    // request data processing& z; h/ \) I: I( H- h5 h7 Z. p
        {/ [- E! C4 q0 C0 g( E; Q! C
            if(ep0_state==0x80) // SETUP phase4 N( R* C6 O. e* r
            {
: W( G: d2 F4 w8 c5 ]0 ?                if(!setup_packet_service())
/ k. S! A6 I) |1 p9 h                {5 ]7 L5 l: u2 b8 i
                    ep0_state=0;
: C+ R0 }1 P2 |: J& c                    // not supported
& ^( @, ~, x/ g4 _- }                }) _, x' z: O& `: E9 \. s
                // ep0_state should be set to 1 or 2, if processed( }/ \. J( u  y$ i* k3 F
            }0 \0 H" G& i5 x$ z1 w" J$ B
            else    // OUT phase; D+ @7 x+ E% O
            {
4 e; y! W2 i) \- k/ _" g* `                // process data
, I' V  P6 S% ~                show_LED(*(uint8_t *)(USB_PMA+128));
8 j* B3 f, p6 p0 a0 c                ep0_state=4;; {& A9 k! y$ o! ?5 T; p. v
                USB_PMA[1]=0;       // Zero length DATA00 E8 s/ Y% R. L1 t+ p5 M# n! J4 A
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;% W+ a& i8 b% u9 h+ I! @" \9 r0 G* h
            }& c; K. x/ K/ V. d) O2 B
        }
# q. K  `7 }& n2 V6 w$ b$ @9 p( f        else, }7 o8 E4 y, S$ ]
        {/ X2 Z) a6 x" R5 ^2 s
            if(usb_address && ep0_state==0)
/ L1 _5 l3 m0 F$ H$ z            {
! n% ]* T0 R8 P8 Q                USB->DADDR = USB_DADDR_EF|usb_address;' Q) U( G8 [9 @5 ~; J% o7 f/ N
                usb_address=0;% l' \# d4 s7 t7 h+ Z# I5 m4 Y: i
            }* `2 D$ P: O- c( R4 E  m! T
        }% H6 s! k2 a  n; [2 r1 ]+ U3 p
        if(row!=scan_row)   // new scan line
" H: @/ |* \3 x. [/ f7 p$ |        {
( Q3 z/ d, k. E# J( h            if(key_state[row]!=prev_key_state[row])" A% u; P% c# ^8 K7 h9 A1 N
            {
$ a% ^6 J  f  c+ Q$ o                uint8_t test=0x80;6 B7 i) C% G" p  }9 b) ]; u- m
                uint8_t diff=key_state[row]^prev_key_state[row];4 }( c8 d" Q8 ]' S
                for(i=0;i<8;i++)
( H; K. [% f# ?  O, v+ d                {
" v$ Y+ ]+ A, v+ k                    if(diff & test)( n: V8 Z; B7 S+ J! I
                        update_key_matrix(row,i,key_state[row]&test);) U% t4 n; Z$ o! @" @! \% U
                    test>>=1;
- i  n! N# m7 A4 b5 `- I2 H# ~! C                }" K$ W* U4 Q7 j  l% I  @. |3 y
            }
3 w9 F" C! i! a, |( f; z  N            row=scan_row;
9 t' i5 E+ k( E* i4 [( K1 l/ D6 E        }) j# Q) R$ y( {* n9 D
    }
( H2 ~# F4 t6 ~$ k; \9 ^* z
" l) j7 W  c* u, n5 V
$ z& R0 s$ ?) g+ wEP0的控制传输,把用到的请求处理一下+ I$ Q6 J0 S2 I9 J0 w
char setup_packet_service(void)7 G% Q7 r  Z- ?% a& h+ W1 V8 b
{) C9 a& |- e0 o8 T7 V5 F
    if(ep0_std_req->bmRequestType & 0x20)   // class-specific3 g% ?) s" S  {7 y* |
    {
0 y2 B9 \* S/ E0 g7 Z& T        switch(ep0_std_req->bRequest)  h6 \, p  m4 J& A
        {; ^. O* l) S: J9 |! n9 ]
            case REQ_GET_REPORT: break;2 N  s# D4 _) s2 z! Z! B  E
            case REQ_GET_IDLE:
  x. S% p# g8 E. n, X- T, q" L                USB_PMA[64]=0xfa;   // return 1 byte- [) t8 O  N6 d; [3 w5 t
                USB_PMA[1]=1;
/ T+ V, v5 O/ z                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
$ @1 C( R4 b: r0 Y* V                ep0_state=1;
3 ]3 X  V0 h/ b/ X+ ~( f                return 1;
% F+ A4 u% b- G+ I; o$ r6 m& Q# ~; w                break;
: h  y& g% V- w& l            case REQ_SET_REPORT:, P+ w+ r" S3 y* y4 [* Y$ b
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
+ A  C1 O+ s# C, ~6 e. F7 c$ E% Z                ep0_state=2;
4 F3 P. Z: r. v                return 1;
6 b- z* l' @; X                break;
* X. P7 e4 l8 \            case REQ_SET_IDLE:
% H& \5 k8 s# u, B5 @                USB_PMA[1]=0;   // Zero DATA; J% I$ E( N. c8 b- s5 o/ x  x
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;! Z% l' ?2 A2 D+ d$ |
                ep0_state=4;
; Y+ f3 C2 l5 T- W2 G' s+ Z- L: r; f                return 1;
7 H7 y* I$ E$ N                break;7 R7 G+ i: j" N# d
        }
8 b- H+ A7 I7 S        return 0;( I5 y" i+ L. Q" b
    }! U: O- O3 i! ~4 E- r! \
    else    // standard9 T6 d# W; d% C' N
    {
8 K0 y- z* R  _0 N! J" T        switch(ep0_std_req->bRequest)2 b* r9 S/ N+ e# a5 X
        {
' o" `  T* [8 Y' t( `. S6 ?            case REQ_GET_DESCRIPTOR:
# T0 Q" L9 t( Q# k! R& T8 w                return descriptor_service();
" P3 o' l. P6 P0 M                break;2 G& S; k: I3 x, K1 ?9 p
            case REQ_SET_ADDRESS:; c3 |' t: ]  y8 t9 }. }6 @
                if(ep0_std_req->bmRequestType!=0x00)
1 }, f0 ^) }; _; s                    return 0;
/ ~% D" P: u( i7 K: M! f; O4 J                usb_address=ep0_std_req->wValue;
. U5 b! n' ]% G* {: n1 r+ C                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
) F0 q$ F6 j4 |, X/ X( A9 u                USB_PMA[1]=0;       // Zero length DATA0
, V% w7 I" j" ^: b                ep0_state=4;    // No Data phase
8 M: v8 |$ E' `8 K( s                return 1;$ Y8 }$ H0 F0 P7 E2 ^6 X
            case REQ_SET_CONFIGURATION:0 U, F- b; ~. E! q$ ~
                if(ep0_std_req->bmRequestType!=0x00)& G7 H% W! p% h/ M5 C1 z' g$ N
                    return 0;- d1 w- H; w% a9 `+ o
                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;( j9 s4 z0 x$ y- h
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
" p% P$ |9 a+ h2 L1 @5 y  w                USB_PMA[1]=0;   // Zero DATA
; \  D* d) j9 z2 f                ep0_state=4;    // No DATA phase5 t, r/ ?5 j. L4 R" f- ~. m  k
                return 1;9 p# K2 J# e, S! O" o
            default: return 0;
6 j# j' ?  {7 M/ P: b        }
( U& F1 q" {' ?' x% a    }
. t) _& a2 t, T5 e}+ G* C2 B& a3 @' d1 ^- ~
' n$ k4 S8 G0 }6 W, f2 \( `
: p( v4 A5 h' e2 F* ]* X0 Q9 [- V0 v

) T2 I  M: _3 O
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的" f! H  x; F2 B! |3 p* h
char descriptor_service(void)# S4 n0 P+ M' O& [) z4 F$ d8 W% a
{9 Y' C+ I, P4 A
    switch((ep0_std_req->wValue)>>8)
) J. n4 P5 y: P- ^6 N( L6 a: q/ i    {
, e' l* d: p9 W: k( r        case DESC_TYPE_DEVICE:; x7 z$ L# x2 T& n4 Q6 _
            return ep0_preparedata(&DevDesc, sizeof(DevDesc));
1 g; j( v9 o* F# w/ |3 A6 E            break;) L$ k6 M9 U" J) G  ?/ K
        case DESC_TYPE_CONFIG:
; n+ E: L: \2 M8 A; {            if(sizeof(ConfigDescData)>ep0_std_req->wLength)
- O5 {: \& Q" |/ m                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);5 A" ^! q4 ^! ~* M$ P
            else
, t/ H. w( ~4 e9 d" t8 M, b                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));
; s9 n4 O8 N5 k            break;8 e& j' B3 {/ o) j4 V6 g: d7 v$ x# H
        case DESC_TYPE_STRING:
5 T2 h2 b5 j5 U7 F. l2 L1 R            switch(ep0_std_req->wValue &0xff)
  T7 m8 c! j' z. o: k1 Y            {& U& u% O7 |8 W# g
                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
. v! ^8 q' ^' h% X                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));
4 b6 d- i" z8 q# \5 H                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));3 N# ?$ j$ d  r' Q* ~+ J. l
                default: return 0;
5 ^" Q1 n6 L1 A* T& }            }) J1 e+ ]$ Z& W; i& F% q
            break;
# l  A2 i7 s1 i  a# B! J        case REPORT_DESC_TYPE:
1 c7 ^  N; a7 M+ H- x/ \  q+ ~            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));
+ K; c3 [2 K0 D8 L  \& T        default:
1 `0 H4 H* Y  F& H1 Y9 W            return 0;
+ a# P% G% y  e( H    }
4 j$ ]; @: f8 W0 U" z3 v: E3 O}5 Z0 G( l" q$ A4 D
下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.8 J, `3 F1 i$ Y$ j' A! B
void TIM6_DAC_IRQHandler(void)' B; a+ [+ r- r; j" P2 w9 F2 m! ^+ I" E
{
) k" w/ ]6 \6 w3 m    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);5 N5 Z  I. o7 w$ V  @
8 {2 Z0 Q9 X) g3 J% y
9 {8 c" o7 V- A4 N- ?1 ^
    TIM6->SR &= ~TIM_SR_UIF;' }. r5 z/ A, y1 l2 R8 I; J# \
    prev_key_state[scan_row]=key_state[scan_row];- U/ ~4 f0 `, a/ W- }
    key_state[scan_row]= *PA_IDR;   // update key states
) G, a( o5 d# k2 e    switch(scan_row)
: E: T8 I! n: O1 {3 R    {
, h5 p& ~" {! v1 t' O        case 13: // next row PB14
4 @. z. {+ J* y- q                GPIOC->MODER = GPIOC_DEFAULT;, J% f& M% Z0 M  T; X0 R
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;
$ s: h* ^( U0 A. ~0 ~4 z                break;* c  d# m4 ?: W* D& ^* T
        case  0: // next row PB15
  r) L& T) ]  B/ I/ o4 C; [" K" G; F                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;7 w6 k' |8 j1 [  A: b5 q4 ]* L9 t
                break;
- ^+ ?  J! s  ~7 G3 N* T# C        case  1: // next row PB38 j- U3 N6 p6 C" a" i
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;
/ b: _1 c( c* W: e/ r2 A                break;! K) [4 I! c  h3 h& [! F8 b  I: X9 B
        case  2: // next row PB4& I" @( m+ D: f. N' f$ L( k& s
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
8 _7 d$ a+ B; N                break;
) U5 Q1 G5 U7 t7 O0 R, C        case  3: // next row PB5( a9 X" ?0 c# v& }( G
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;2 @) N+ T1 p  k( u" z* Y& L% b0 m4 `
                break;
" h/ s; c$ a$ k# w+ h( x        case  4: // next row PB6
  i+ [6 S/ C! N7 Y$ T( ]* l" _, u3 o8 o                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;
+ Z8 F/ y- |2 A                break;
& P6 Y+ ~- y3 Q- H        case  5: // next row PB7: J  \  a+ H3 O0 j
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;8 s" F) ~8 \2 x6 Y
                break;; k+ Y2 V9 Q' j5 e7 J3 `- I$ w
        case  6: // next row PB82 |. z% b  ^1 R0 U$ e+ D: X
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;  I. H- n+ c- N" B' B) R8 }) a2 ^
                break;7 W/ B* y8 s* _3 B3 N# d% v
        case  7: // next row PB9
) _  T. _5 i; T/ |$ T, k                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;
+ z& o. v: l2 j* M# [# `" o5 M4 s; H                break;
! V. g3 J: b0 g        case  8: // next row PA8
! Y  d4 }: ?+ X4 Y* [4 L2 Z' }/ a                GPIOB->MODER = GPIOB_DEFAULT;# m* N7 g$ @' o5 ^! l9 ^7 m6 {: F
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
$ Q2 y! I/ i* T' D% Z0 W  |                break;
" C- x4 v( p- ~2 @        case  9: // next row PA9
+ Y; y" W) B# X0 u5 T- i                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;
" ], L4 ~7 X/ T# k                break;
! z8 j  b7 z% v8 ~+ ]& r        case 10: // next row PA10  j8 N# M+ b! k
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;. J5 F% T$ F9 W, B
                break;% J5 ~- i/ M( k/ y+ v# C
        case 11: // next row PA15
$ H/ w, C/ m& {0 {4 e2 t                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;
1 I$ l9 m0 D: k) ]; r3 r                break;
+ |8 S( V) R/ V! M# l0 k9 W* b        case 12: // next row PC13% e, |4 \' d4 t* F. f! t$ Y  @7 x) |
                GPIOA->MODER = GPIOA_DEFAULT;
+ ^+ g; q+ U: X# ^$ Z: j6 i) }                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
- R( f) ^6 ~3 {                break;
- w+ Q2 s" Y7 m; _9 }' n    }& M' u! L0 x# `/ t+ |1 W
    if(scan_row<13)
( |- m) W. W4 z+ g  q: I8 j  b$ G7 T        scan_row++;
, P- w/ W6 k7 K& M3 L6 ]    else
1 L! [" S+ s6 w5 i& h        scan_row=0;. @* T, a# y0 a/ ]3 l9 g; F
}) Y: Z5 K5 H! V  t0 n! {5 Q* {) D

9 v- ]) M9 {9 l$ ?
* O7 Q2 x) l/ }: k$ o2 {4 s
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑
; w1 t+ B* T- `- ^7 t" @/ f
$ N3 r5 \/ G5 x9 z# B扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。- Y+ r% q9 ~$ U, T
) T# i  v  r. a% c- o, ]
要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:
/ I( u( {2 j8 S; [3 a+ {. I+ Y

& L- a/ n' H0 b4 b1 z; {
1 G* m4 @8 |8 q% S1 z, g4 Iconst char hid_keymap_qwerty[14][8]={
/ u0 m1 O$ D5 m( k    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},! `! U# D) L) r/ O) [# x+ p
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
3 N% z1 k! L+ N0 A+ ?. w    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},$ W) t: i9 c4 ~2 b0 j: ^, ~+ J
    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},
" H' ~: p% @4 `    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},  a, z! d; Q& F% e( _0 I1 L
    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},
6 N2 _$ i* u2 ]8 x8 m. H    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},  g/ y$ j% v$ R( H
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},! U2 K* W) y2 z, G3 ~$ A
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},0 \' O( Q* T$ \( R, r
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
" K4 o! f: x  b1 S1 h$ b3 ^    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
1 a: z. X% f. ]    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},
7 @& O( \1 o: W    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
/ B+ }/ n% n9 D+ h. }( G+ l    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}6 z# p+ ]# A, c. B/ h; l9 x! A
};, b  W5 @6 C/ a

- O9 m  k% n7 c9 i" [/ p. v

6 L3 \8 |; k7 m: j" D" y1 yconst char hid_keymap_dvorak[14][8]={6 T1 f% w7 H6 W2 t4 K& q4 S
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},# p5 N8 z( O9 ^# T( I0 M8 R
    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
0 S+ g& ]  ], M. r1 v    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},! O/ _8 a4 s# l& e
    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
! h3 `& F* o( R0 `# c    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},( O' P7 V- \: F; n+ q" w
    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
& I: x1 A1 i* Z/ P+ V    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},
+ P7 J* k) o9 b5 S* ^    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},
1 v' A# \9 t: v: m    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},* n5 }" R) n4 W
    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},: c2 {+ A/ X, F# `
    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
4 _/ H5 C( z# W9 u0 `0 m    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8}," Y" s1 K  i. r6 ?3 c
    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},
) I9 m+ |. b! l' i% \  v' q    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}( B" L; s3 ]8 D+ F
};9 _3 O4 H8 ?( ~/ m) i% V: m9 x, V$ N
5 P* ~7 Q9 d0 I, \: }5 y9 s9 B

, i( q& `4 I( Q! H上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
! E' ~- x8 X( E: U5 b- h( `3 e/ w) i) s( _9 X5 L
HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:

3 ?$ t" ~2 c! {: Y, A# O
' _; h' l0 ~$ K1 K6 C9 B8 E8 ^void update_key_matrix(char row, char col, char onoff)
, P. D/ x8 k/ h6 g1 N: `{
' S" V& q" v6 O3 Q) z/ k# Q6 U    static uint16_t hid_report[4]={0,0,0,0};9 Q3 i9 T4 d' M# G% |3 @
    static char (*hid_keymap)[8]=hid_keymap_dvorak;
. s# p0 n# E  g. S4 ?" [# V3 c9 A7 u# r5 u5 }

- N1 w* X9 K* _7 N    unsigned char key=hid_keymap[row][col];
$ G* g0 p' I" m    unsigned char *report =(unsigned char *)hid_report;( r0 C! j( t3 J' Y2 I
    char i;
! ?) c8 }, x5 ]( x  K% U+ g: O2 ]0 `9 B
& ~9 `) v& B3 l; n' [6 R' K2 h
    if(key==HK_MODE)
; S6 x& Q# Y: T) @    {
! y8 L7 x; l: D8 X3 G9 ?8 \9 {$ }        if(!onoff)
' |: _( P! S# R9 u9 q1 c        {
' H2 Q- x3 _/ p; ?: g* Y2 D            if(hid_keymap==hid_keymap_dvorak)7 Y9 y" P5 l3 [: @/ F
            {5 _. v2 X+ O  C4 q2 P; f. i
                hid_keymap=hid_keymap_qwerty;
  J  h$ Y; a% K& U                GPIOB->BSRR = (1<<2);
3 j. h" L* B: u2 b, f$ ?6 F            }* {% A; M) V/ @3 ^! C4 S+ K
            else
: H7 {! ^" m/ p8 \            {
5 W" p( ~1 e3 n4 g. z1 U; \- P4 C                hid_keymap=hid_keymap_dvorak;
  A) h0 Y, U8 @                GPIOB->BRR = (1<<2);
. s* B  A9 ^+ `5 z' C. w; B            }
' Y3 U. l6 ]5 j9 s1 H( A        }
4 z4 J0 F. e, h: T: F8 l, S9 d        return;5 |* u4 g( j" j. O! p7 x5 N+ c3 A
    }# n2 w# V  G1 N3 B
  c- M* ?- r9 G) l3 w1 P. J* k, s, B

# o! S7 s, B2 h3 a9 q    if(key>=0x80)   // Alt, Ctrl, Shift0 ?+ z: T& f' _
    {
% U3 _6 F5 v9 i        uint8_t bitset = 1<<(key&7);
! V4 l1 j4 y+ c8 |+ j        if(onoff)   // non-zero is key up
) v2 ~; f% T, ~0 m: S7 t            report[0] &= (~bitset);
; r# B6 K6 b: @/ z+ ~6 r        else
) v* v. V: Q+ C+ v/ ~) `! [            report[0] |= bitset;* t2 ^( Y7 B& E' h! J7 l& ]0 y
    }! m0 I- |$ ]$ Q7 \7 U
    else0 n6 P( }2 h! a1 d
    {
$ ?2 k! b. J1 H* e$ |        if(onoff)   // non-zero is key up
6 `, L' Z, f% ?1 t$ \8 P        {: T% b3 V$ u: A' ^- U8 c( c& t
            for(i=2;i<8;i++)9 p! y; k5 V- v9 L5 r+ O  W
            {
0 R$ k, ?; o3 r% ~/ ^0 r" ?                if(report==key)
4 V4 U) W4 z$ W( B6 W6 Q                {8 `7 C3 b+ U; [, r+ B' f
                    report=0;& \8 P! }3 L% X5 ?0 N% D
                    break;' ?/ J9 U. m  E$ j
                }
6 x8 Y& c- [2 l: W; l2 j            }
, `" ^% X+ M0 x0 P6 g, m        }2 J# f; I9 M9 \) }( Z. c# \
        else, A0 [# c* {( S# b* P: H6 B
        {
( \* p4 B* t6 D4 m            for(i=2;i<8;i++)
& {8 ]- V8 D& ~2 F% x            {* V5 t3 c( o" e7 ^/ _( Z  _
                if(report==key)
8 P3 C# E3 m5 B3 W: R                    break;5 _! ~7 q- `( F% `
                if(report==0)- t: @) B; a9 B& I+ m
                {
0 {) z/ B, a2 \/ s                    report=key;  S# Z7 U1 S0 V+ ?" z3 P7 C
                    break;
- R/ R& R/ _; {9 w                }- G7 B& w( I. _, y
            }
! l: L( W" F% r& H) j        }6 U" C4 \% t2 I$ j5 y& z/ Y+ @
    }
+ I% e  t' k* l! q" e& C, z    for(i=0;i<4;i++)
8 f3 ?. x& L( {. T        USB_PMA[192+i]=hid_report;; ]8 q' J( i( `1 D: u+ D* v
    USB_PMA[5]=8;   //COUNT1_TX9 b+ p& q9 x6 e( x# |: B
    if(ep1_wait==0)
& R& v) X( E; n( U, ^6 _. E    {. R  I& S+ ~8 ]. ^
        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;
: W1 s& y: R: e& H, q        ep1_wait=1;
' J8 u0 s$ w& c7 O4 w0 w    }$ J% ~3 {4 ]  `* L$ L/ |
}
1 D2 W( A/ R- F& D  Y/ k2 ?6 p* J* Z( M
7 S3 R& p& d( O7 t
9 b. c1 X( d# z* L* B完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。
! ?/ v2 P' w- Q/ L6 F keyboard.zip (8.7 KB, 下载次数: 6209) , s5 j9 P9 d1 ~5 G: V; p7 H
$ a1 Y8 N# ~( T% G. u  z4 t& R
8 [4 V' m; m# {( Y
1 |) B' }* C3 C8 n, B! V) o

" c( w' [  C0 v  ^
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘
. ^+ y0 p/ `7 p4 d: d* i不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48
/ z$ L8 ]' o' s/ G5 i( K4 P刚开始我以为要把打字机改造成电脑键盘4 U( c6 W/ [" F
不过楼主也很厉害!

) [; v) Y& K6 z/ q1 l哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害/ p" ?1 d. o% W  ~' A
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-7-12 13:40 , Processed in 0.168476 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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