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

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

[复制链接]

24

主题

39

回帖

288

积分

vip

积分
288
发表于 2016-9-21 20:34:19 | 显示全部楼层 |阅读模式
本帖最后由 miaozhuang 于 2016-9-21 20:35 编辑 + u+ B* j0 T- B" H6 |
/ z8 M3 Z: n" X) U/ W
http://bbs.eeworld.com.cn/forum. ... digest%26digest%3D1  f, A5 J7 ?4 `$ r2 l0 m9 \! l
这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。6 p' B  Z* x% n9 [

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

, E: E7 J- c$ H# g  v

' \* }2 F& N- ~# v 235140i3a36qivqzuvmt5q.jpg.thumb.jpg ) _% v1 Z* {$ e2 Y1 w7 i
上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。
+ J0 n  K& [# g+ I5 _$ L 001734klbyoluenuwz4h4b.png.thumb.jpg
, k1 D* T+ }2 c+ q  C2 s  m为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:
. M  `) b9 F1 p 003625r2agx2f5v922cf2f.png.thumb.jpg
' L" J' }9 h& {7 o. c0 }7 K* y3 p  r其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.
  W; P' a4 z$ M  x% Y0 u5 |Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:
- v# J& R. L* q# |/ d0 Z
005836yvs0wvovwsssgd3o.png.thumb.jpg
) `/ x# T: W/ d  A* k5 k7 C- w0 }Dvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。
1 G! n4 A6 K* v' k/ _0 {. o5 Z4 o3 r8 v5 e
我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。: \# J# e  {0 a' {6 `7 \8 ^+ x# I
6 i1 n1 v% ~" Z9 f+ o
到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。
$ V7 Z+ J' y2 j/ N) j4 @; P9 f
4 M/ e6 [3 d: {5 R" b- O, H% F机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:

9 S  D' `# H) h* W6 U 123206vx24rfxfp2dfrpxg.jpg.thumb.jpg $ Q  T4 k" }8 w( ~3 t! g- L7 M
----------------------------------------------------  分割线 ----------------------------------------------------------
, x8 ^* _4 O! t, a8 \2 l" y8 w9 D* L5 F+ t
先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

! y2 k3 y4 {5 p5 J/ _6 U) N3 ?" j4 s 020011osionbunl4ui44vi.jpg.thumb.jpg 0 M4 \5 d2 z, M0 ]9 D' |! `, m/ \
轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。
# c+ ]- V9 e* x  J1 ^ 020017j8ycmnv7788bqv52.jpg.thumb.jpg / Q/ Y" H, w) s8 A% G; X) Q
特写,80C49
1 M" I$ N! N+ P  l' [7 I7 t 021040oujzuvtut6iujtvz.jpg.thumb.jpg
! W- F  @: t3 U) O* Z# ZLED部分,使用了一片D触发器锁存指示灯状态.
5 P: u3 e- g% N4 k. ]) [. L 022209ixzjfjc9crmrrqjm.jpg.thumb.jpg 5 W( I7 m% T4 a0 [
暴力破坏,将80C49拆掉6 ]7 _' [* K9 }/ p3 k
021113e48qq98vyohvhzzh.jpg.thumb.jpg 6 @. E! l, I' H7 d7 {1 z
拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。* t1 P; K) D6 x) Y9 q: D4 z' Z
021125nc9az6dj33rlds2r.jpg.thumb.jpg
( o' X1 @# `+ J- T
焊好元件后的板子,准备替换80C49! |+ |: N" Z) E' m: k
021146zmmpmh4pav4o7g6a.jpg.thumb.jpg
% [$ I# R# N0 B' s, b用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。
) j' V* d) }( g. E; _4 \ 021104shifhnrqbr3o5nlo.jpg.thumb.jpg % z5 h4 B+ I) a. M1 |7 ~  S
这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.
1 h* U, H. B* J; G 022003ym1p9u4ug40280uu.jpg.thumb.jpg 2 _/ t( N* {1 P& }: Y* h
开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。& b. R) f2 l4 V% b. {
023313kt141q9qajtol7ma.jpg.thumb.jpg 2 k! {- D; x" I$ i
我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。" s0 @* }: o& R; P0 i1 Y
023322nt7l5xb3ltttkltt.jpg.thumb.jpg ! w2 o+ H, e$ [& K& F- d+ Z
主键区键帽就位- V* }) F( f5 ?* q: d$ I6 g( h
023331hin88e8wkrwzwikx.jpg.thumb.jpg - ~9 T% o4 X- d* L
编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。; Z% q. z: l( O$ X; w+ u) s
023336wjzlgopugg1jyy79.jpg.thumb.jpg
$ J* E: n+ Q  I4 U6 D: g最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。
  m" h' ]  E# e! o 023341sffu4j3g2323h6fl.jpg.thumb.jpg
2 g( ^6 E5 j/ J

: p, a! E9 L# y2 L; x7 H8 F----------------------------------------------------- 分割线 --------------------------------------------------3 S  \0 d' O: V- K  }6 `

: {; r2 J9 X& w) f

8 l& m+ t# n- q% ?. |8 T7 e/ D6 E% K1 R1 @* A

- I" [' T  b+ N8 [: A# i  D2 `+ w2 C8 n- @6 J. j7 Q

/ e) i! d. X2 Q+ b- l- f: T( k) e4 D1 a+ A" X
2 L/ Y3 X5 W& x7 e
) u, W8 m: \& `$ g
9 X7 n7 d2 G3 M1 d: J
- M2 i9 O2 o3 m
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:40:25 | 显示全部楼层
DIY过程直播完了,下面说硬件的设计。  p$ b: R7 e7 D
80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:
% E3 \7 @* T, P+ g5 @; K 104025nzibm2rmiomhyirm.png.thumb.jpg   A/ i" E& U9 w( b& e6 i! c6 ~
! g; G+ G3 D0 |$ o) c
其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号), N2 C$ ~4 b" z$ U+ w/ o( n
105004zkrez5houvkkznko.jpg.thumb.jpg # j; j/ ]* X, A' V5 C2 x! \' q4 {
) D8 U& z4 s7 ]
扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。, C! w+ L5 B* l# \' w
$ d  w, ]$ L5 N$ D3 l, F
这是我设计的电路图:
8 l. p. l, u( f4 e5 s6 G; h1 m; u 110344ej2z2oo2rflo7oe7.png.thumb.jpg
' G6 e: J' ]! I' _0 ~# T

3 e: p8 U0 S4 P! s  d5 LPCB Layout:
# ]; u* [1 j, g* D" B  l" a8 H 110847jjbjvt34vwt3v5bb.png.thumb.jpg
- K9 q4 E" k, ~  t9 y! e# K
) M5 L! g# E* R" c4 O# w& f
不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O. * k  T: F, p* }9 b

" }6 l. P: }2 j! J% d

/ B9 k% X, E7 Y( V- a
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:47:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 20:55 编辑
# c/ e" L$ y( _' z+ _1 N  N
7 y- v$ V4 S/ {7 D软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。2 T" g( \* S9 m

% g6 z' f0 l6 R总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:
* U, u  G# X4 I9 T  ^  ^7 Q$ S 113818pmrfsb6z0byt6t06.png.thumb.jpg ! x: _- M) e" M' v
5 D. q, i8 k3 `3 k  M( Q, S
其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)& l( B8 ~. Z5 x( T4 i) P  i: X$ @5 P

4 s: ?& w2 d6 b, B5 t! UUSB的中断ISR,bare metal哦4 H/ m  L# T7 ^5 K0 F' i% g8 {3 a
1 }1 `6 k, g% P0 o- x1 r5 H
void USB_IRQHandler(void), @& T! S0 p1 S% X) l
{
* Y& W9 Q  e- G+ e+ H6 L$ ]; `    if(USB->ISTR & USB_ISTR_CTR)
7 h: V& Y: {4 F    {
: @5 v! e; e- i! i' M/ o        if((USB->ISTR & 0x0f)==0)   // EP_ID==0
) ~; X* z9 C) ~' _% g! `        {" ?: H/ t: y; G8 b$ u& a- p
            switch(ep0_state)  r) O! P7 A7 t) c! c& t. r2 }
            {
. }3 _+ D2 O# i5 W* [- {                case 0: ep0_state |= 0x80;3 f2 a( L8 |3 g; H3 N  g6 S7 X
                        break;
/ u1 `3 v/ x! X' P/ J) U6 d) f# k4 A                case 1: if(USB->EP0R & USB_EP_CTR_TX)' s3 M& |6 J$ c7 y4 @% X$ v
                        {
) l, `3 X- ]5 {1 p% I# ^  U                            USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
; o7 E  x5 U! P4 o+ ?6 s5 U                            ep0_state=3;+ |# B+ w7 S' y. f' F
                            return;0 [# `% b& T1 b* a) ?5 J
                        }  Q1 h- f, [# S6 X: T5 k; P: F
                        else1 k. x# b; g/ N" a. o- L4 H& V
                            ep0_state=0;- w: [* c3 W% T' b0 I% V- ^
                        break;
! h: ~# b/ Y# u( ~                case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet' \6 j. l( R% h  C: T8 |- G
                            ep0_state |= 0x80;3 ?/ w9 N5 ?6 V+ Q/ G; d; o
                        else8 a, C% b6 c$ M- _9 K, O! v3 c- A; ?
                            ep0_state=0;; P3 n* U( a: x. T5 H! H
                        break;
2 }1 p  H0 P* _) O1 P9 a                case 3: ep0_state=0;
" ]$ V: a( ^7 @1 _2 P                        break;
/ j( j4 t$ Z/ d5 A# l! `1 U- P                case 4: ep0_state=0;7 c5 H7 Q5 E4 a4 _  R
                        break;* a$ V( I0 R5 ], r: m
                default:ep0_state=0;
  v, x- V! F: L9 z6 O2 n                        break;
( M- m( _) V/ d& P            }
7 ]5 q* u1 j: p            USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag! b5 ~" @" ]; |1 z! d
            return;/ K+ o" _1 L% N' ~8 {& y, R5 j
        }
. A/ U. y' t$ y4 L        else    // EP_ID can be 1
9 D6 I) N1 x' J4 c        {
* T4 f# G+ a3 Y9 s            USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag; G! q( T/ U# K& \6 p6 G
            ep1_wait=0;
, O$ r, w2 {* G4 }            return;
. U/ B" \2 t6 O1 n$ _6 |/ ]6 ]        }0 E- m* e- ?; w" E! o/ j" q6 L
    }
/ s& C# d% ^6 |* |2 r0 ~    if(USB->ISTR & USB_ISTR_PMAOVR)
% h4 k1 O: g6 @    {, z: K& j9 o; R: h  w% K
        USB->ISTR = ~USB_ISTR_PMAOVR;
  S: d5 i/ {5 C3 G    }
2 t$ m* z( z; e# m    if(USB->ISTR & USB_ISTR_ERR)
5 L; ?4 J. g2 d8 t$ l    {
6 X* J# y8 y" j* U        USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear+ {: e: G, w& a
    }
) w6 L+ `  L( B7 ?4 L5 E$ Q    if(USB->ISTR & USB_ISTR_WKUP)
! ?* Y* g/ D5 ]) ^1 y" a* X' A, e    {
$ a' k+ B5 K: d# \' z1 h( e; v        USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
% i/ L9 ~/ a9 [( U% f        USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear6 p' m+ R$ L6 ^3 s- J- w0 V
    }
" G7 D2 }( Z, I3 w9 B2 d    if(USB->ISTR & USB_ISTR_SUSP)1 F, Z0 W: J/ K5 T
    {' z* I6 K- X8 Y$ }( R7 t7 N
        USB->CNTR |= USB_CNTR_FSUSP;    // force suspend
7 u6 q+ `8 J4 l9 f  ?        USB->CNTR |= USB_CNTR_LPMODE;   // low power
; g2 \4 W/ \, j& Z        USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear
6 C, ^8 @, i% p7 E9 Z( e6 k3 B' u    }
/ O" O; Q  e* y5 h    if(USB->ISTR & USB_ISTR_RESET)
1 p3 M# C7 ?) W5 v1 @& Z    {
3 k, c) Y  N" P% ^3 S# e        USB->BTABLE = 0;    // buffer table at bottom of PMA
: P4 P6 B" B# N9 \- e        USB_PMA[0]=128; //ADDR0_TX
: O  d5 }! K: G; P4 z, F# O        USB_PMA[1]=0;   //COUNT0_TX8 H8 `8 }9 S) \0 U; o: w3 U8 e+ ~
        USB_PMA[2]=256; //ADDR0_RX
# E1 |  |1 v; k+ y  }% |/ F        USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes2 ]6 ~) H2 J, y$ h. o
        USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;
4 F( M. J2 n6 I9 Q" F, @        ep0_state=0;$ ?, O) c. ?0 f+ X2 x
        USB_PMA[4]=384; //ADDR1_TX
4 p1 l6 R5 Q1 ?1 d& j        USB_PMA[5]=0;   //COUNT1_TX
" u( w, O5 L1 H3 Z' [        USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type+ B  r6 z  x$ a) q) Q5 Z
        ep1_wait=0;
$ I1 e+ u0 `- z        USB->DADDR = USB_DADDR_EF;      // enable function" n; W2 T  Z6 w" J' Q
        USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear4 l8 t* I) w  ~* h$ Q( x
    }6 {4 W. }1 Z9 N% K; \# ?5 ]2 n
    if(USB->ISTR & USB_ISTR_SOF)
* z5 N# F, [4 }' B- t    {
" t, d3 Z6 j6 Z# \( ~        USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear
6 }& `" l( t* m    }' e! F2 r3 c8 M2 I& T
}- M6 Q/ N* o6 j
( A  G( |$ r" z7 T" b8 N+ R
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。/ S1 B1 I1 K4 F
2 p4 U7 X+ x8 Q0 {9 C* H. N9 o
主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。
5 w! {: @+ L, G' q
9 T% \  P/ t4 v( s. K
    while(1): i, u" Q1 m0 i" S. F. E
    {
1 J8 s; v' s( J# @) k$ r; x" i3 J  B        static char row=0;
  I8 V! f. u" e. u3 X        __WFI();5 B8 P- @$ A8 m0 B, R! ^
        if(ep0_state & 0x80)    // request data processing
% ^3 k' _+ U* x! P        {
; h. M: u5 X) Q' o1 }            if(ep0_state==0x80) // SETUP phase
5 S: X# [# A/ b  l            {
. n& u1 U9 z- X8 f! ~7 z+ u                if(!setup_packet_service())& {3 W2 g( r; r3 M: e3 }3 m/ _! Y
                {! V) }6 A1 K1 ]5 _) O
                    ep0_state=0;* e9 z0 q: H5 S
                    // not supported- V% }( v' S# s  ~# M
                }. b. l0 q, \, T) h0 |0 s- U
                // ep0_state should be set to 1 or 2, if processed6 r7 |( ~; _: U( {
            }
9 V: h( x) E) v! F            else    // OUT phase
8 A0 M9 r$ i! ]5 x  m9 j            {8 z# _1 y. B6 [- i  z8 p. E
                // process data1 X- W: @  k2 y2 x/ R
                show_LED(*(uint8_t *)(USB_PMA+128));
: N  y4 E* ]1 Z+ ]* Y/ y% Z; X! W                ep0_state=4;
0 J8 K2 `( z/ O% N7 _' k6 F. ^                USB_PMA[1]=0;       // Zero length DATA02 ?) d1 `& A9 b+ `
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;5 A( t( K8 r9 M2 H" q, t
            }& S, V0 l; {! s* S9 P" S# q
        }
7 W& j# X: L, s0 r" Q5 b( ~9 e5 e        else
5 H! `  Y& U) J& T* F0 T6 s        {( `+ b0 o; k( |/ H2 x
            if(usb_address && ep0_state==0)
& U  f& r# F# p* O            {
+ ~: [- x  S0 \                USB->DADDR = USB_DADDR_EF|usb_address;5 A8 y2 \* }7 |$ D! @; R
                usb_address=0;2 }" I8 t' X: X
            }4 A& u1 ^3 Q- U. X
        }
& R* \, H$ q; f        if(row!=scan_row)   // new scan line* g4 M& C: ~/ z. P; l* h8 J0 a
        {
$ ]5 h% J9 `$ ~5 |- {3 h3 q            if(key_state[row]!=prev_key_state[row])
7 L) K! |6 L5 D, X* r6 C5 z& i            {
- _2 u6 s* V, y5 K; `                uint8_t test=0x80;
) w  I7 v: f" J% J, j5 x: g                uint8_t diff=key_state[row]^prev_key_state[row];  ~* j; t$ }' O# s
                for(i=0;i<8;i++)$ ?$ |  `0 A$ v8 c5 d
                {. ^+ B' ?& E  m
                    if(diff & test): J: X* i6 K* I  j# y0 x  H+ C; h
                        update_key_matrix(row,i,key_state[row]&test);9 F6 R& z+ h6 @  W& b
                    test>>=1;
" ]3 y% v$ n3 S                }6 p# c8 m" G9 U" Z$ ?! i
            }
, [+ f# I. w, h2 `8 a5 U% K; Y( }            row=scan_row;
" i8 g" E) S% k9 ^. s9 u        }3 u9 e8 H. g. v: x2 a
    }, [# F) E# Z* @5 }4 n1 ]
; Z' V& F" U" N* Q: \$ b& W: G3 k* J
! k( T* }, p1 O( J4 P
EP0的控制传输,把用到的请求处理一下/ t* `" L9 C' A2 l, v. Q$ }, [! A8 v% `
char setup_packet_service(void)+ |- O) f  f6 b% ~0 A- c
{
% D: v9 S) c; [- A' s4 T2 \9 ]8 t$ r    if(ep0_std_req->bmRequestType & 0x20)   // class-specific
* I0 Z, M1 ^* v4 M    {
3 o7 Z5 y0 b) Z9 [; Z' V* L        switch(ep0_std_req->bRequest)5 u" `8 Z9 [" o
        {
: L8 \  x% M2 ], _! \0 q            case REQ_GET_REPORT: break;% G# X# x# g7 R6 u
            case REQ_GET_IDLE:
1 R& q; E0 u* u* l) F                USB_PMA[64]=0xfa;   // return 1 byte! }, u( j. T  E
                USB_PMA[1]=1;
) S9 B3 O; d! c1 u, K; g4 l                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;  o6 O& e; t4 s) f4 m7 T1 i" J
                ep0_state=1;
" @+ N5 C0 v' ?/ G7 t                return 1;
$ ?& B" Q, O( X- ?$ i( I0 |                break;7 m2 M6 f. Y7 |4 t
            case REQ_SET_REPORT:% X! A- e. t) A; y9 R0 e. |
                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;; F. h3 ]+ X. f! k" E" g$ O
                ep0_state=2;
) M0 G4 Y6 }& g3 ^- S7 R                return 1;
, _; W. W8 e9 Q9 C! s                break;5 K2 u5 L' C0 D0 X3 ~
            case REQ_SET_IDLE:
. y4 e. k: @6 `; m1 Q3 j                USB_PMA[1]=0;   // Zero DATA
7 i( s8 e  j) `3 S                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;0 ]( B) @/ o: e& l+ P6 u
                ep0_state=4;
- S8 g) z4 K" L  {+ K                return 1;' o. A! g% m6 e; x1 n
                break;5 _* y6 T- E' i+ Y* O
        }! X9 q. q& I% \0 H2 x1 h4 T
        return 0;5 Z+ @- a8 V/ ?5 v5 }- n
    }* z7 p2 R" Z0 L9 Q
    else    // standard+ H: c& t) ~$ h( g0 u# e
    {! Y/ j- p, d2 q  @1 M4 T$ \
        switch(ep0_std_req->bRequest), O# U! h1 h" R  Q1 k
        {
' d5 [6 ^# ^  Q3 B6 D            case REQ_GET_DESCRIPTOR:
7 [6 e: D: k) X9 u                return descriptor_service();5 C) n+ S; N7 A' A/ b$ ?$ S
                break;' `' P. s; S2 r
            case REQ_SET_ADDRESS:' C* v# a" h4 t* w
                if(ep0_std_req->bmRequestType!=0x00)
9 Z7 H7 q/ m! A                    return 0;
+ E8 g' L  w5 V- ^# K- |& R, ^% U                usb_address=ep0_std_req->wValue;
0 g* c6 p3 A9 W5 f& |                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
) Y- w. G! i% Q5 w8 d0 O                USB_PMA[1]=0;       // Zero length DATA0
1 w7 B: M# i' {# d5 ]! X, U8 u                ep0_state=4;    // No Data phase
$ r( [* g; r# [' S0 D5 @. v                return 1;+ U6 \; q7 h3 t: o  m
            case REQ_SET_CONFIGURATION:' ]: Q# Y  k" A5 L7 `  v
                if(ep0_std_req->bmRequestType!=0x00)
$ _5 {, \- ?$ c8 U) c, x# X1 C* d0 p! w                    return 0;2 q- e# Q$ C" V1 p
                USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
; A+ F  r# X9 `* h! C& y* _                USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;6 f0 c7 E0 H! i; R) Y% G
                USB_PMA[1]=0;   // Zero DATA
( v( q2 d! s2 k3 B$ K                ep0_state=4;    // No DATA phase+ D3 F  Z% [+ y: n. n+ O
                return 1;3 k. V5 D! O2 I
            default: return 0;3 H$ ^4 i% y, e7 M- _6 ?
        }
8 }( G& M9 ~, O! k; e1 i& O    }  C4 @$ X1 W- x. U: `" p5 l
}
5 @0 v+ c3 f0 F$ s# D, v; {
! r1 H+ W5 }# r& u' t0 Z1 y, P, Q2 P# c" l1 v( t% M

* E3 e+ e3 a+ I7 H
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 20:59:07 | 显示全部楼层
各种描述符,是USB开发首先要处理的8 p% {/ V9 P5 `0 G
char descriptor_service(void): ~8 z; s. K- ^- O4 R$ ?7 l
{
3 \* p2 s6 r8 u2 C# g    switch((ep0_std_req->wValue)>>8)4 C) Q" R+ g  z7 b, y  W) Q
    {
% R0 [, x$ [. ^$ W. f8 p# V  s        case DESC_TYPE_DEVICE:! u$ G3 G; o+ X1 F4 k
            return ep0_preparedata(&DevDesc, sizeof(DevDesc));
" J/ J4 U+ H% d" r7 `; t            break;
; `3 X, @( X, ^' |, D, a( r        case DESC_TYPE_CONFIG:
* \2 T9 u- E5 \1 J! I. ~            if(sizeof(ConfigDescData)>ep0_std_req->wLength)6 }$ f( W6 ^! b3 x
                return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);: p, P* y6 V4 ?0 {: W) v
            else  L1 E8 L5 b7 \' J5 x
                return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));
/ e1 i9 _" z4 V7 i9 X  r5 g            break;, C& y% o2 V1 a% U
        case DESC_TYPE_STRING:
" }; e) Y2 U" P  h            switch(ep0_std_req->wValue &0xff)
8 F1 z- a% h! R* C            {( i1 V+ W/ N0 C7 M+ i% ~
                case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));- W. c' W2 H' G: m! Z
                case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));
+ ~) z- P" A: I0 i4 t7 [- ~                case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));
! ?+ f) R) [' J) P" i( g                default: return 0;$ o: h% ]4 J/ U0 G- v0 d8 d# L* E4 Q
            }
5 R  K  a5 F4 U            break;
7 W& a' u$ p6 ]% y        case REPORT_DESC_TYPE:
; s( w6 M0 p6 s8 z# @+ L            return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));+ Q6 o4 g7 a& M( ?" }$ z/ V- v
        default:
% j$ P2 U; @' k9 x5 X            return 0;
1 [* e6 [7 I: @# |    }
; h2 M( e. p9 b, d; {3 ]/ {}" F- ]+ D  s* {* Z9 `* ~( i% r
下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.
4 b1 A+ ^; m# n+ g# C$ qvoid TIM6_DAC_IRQHandler(void)
- r7 b$ V1 u" h4 h: ?9 M# @9 i3 F( Q{
4 {3 C! O! W+ h* U2 ]    __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);& }2 }+ B2 E8 K7 M8 a- y
5 H. F! H) f9 g6 o9 e% H
% A7 |0 j, C: d: f, y
    TIM6->SR &= ~TIM_SR_UIF;8 u4 ^" A3 ~  |. r
    prev_key_state[scan_row]=key_state[scan_row];
+ f7 h9 j( o4 R" L* W3 d6 k* d    key_state[scan_row]= *PA_IDR;   // update key states& E8 j& M# U( n9 Y, F5 l
    switch(scan_row)
& p) x, ^& t$ N    {
4 A* \8 K( ]8 u        case 13: // next row PB140 \) i3 O- ^. m& _8 A0 F$ D
                GPIOC->MODER = GPIOC_DEFAULT;; j& R& F; J; ^$ K' [5 d# F
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;) M2 B& M- I5 `( {- a
                break;4 u! S( b$ |4 K
        case  0: // next row PB15. ?$ @! S- u4 @+ t& z2 O
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
$ L$ c# [6 j. v1 B) Z( M                break;
6 I' s5 h# H# F3 F        case  1: // next row PB3* t9 ~& H2 E  |
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;, Y9 f' [9 I5 X7 e
                break;
* d' m/ I& c% `$ c0 l' L3 Y        case  2: // next row PB45 o3 V' d7 O# {+ ?
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
' Z2 N: n2 M1 I: p/ I1 t, a. D1 Z# |                break;
; D; z( _3 s6 E! t# U        case  3: // next row PB5
1 ]! K  \; q; ~+ c1 g1 |. v: u2 F7 `                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;
9 F% N2 X  Z3 }: ?" b3 x1 g9 u" x+ ?; M                break;
% c8 P3 z' a( L7 b6 V4 K        case  4: // next row PB6
% k) h! V. q7 K4 r# u                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;
% A0 M9 ~) f; V, H, t/ @7 s                break;
" [% c& B! C3 m/ e  ?8 i        case  5: // next row PB7
5 Q8 [4 Z. Q. {: y; r$ y  k& q6 g                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;
/ W0 x! C0 m& P  i2 u1 v" }3 R                break;6 R. ?3 E/ G: P* d
        case  6: // next row PB8$ x' @% g4 x' H
                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;
- I' }$ B$ ~' C: |# P7 e                break;
2 `* X2 M! @. ]        case  7: // next row PB9
3 R. ~. f8 ?# K1 i3 ~                GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;% s: G5 t2 v+ m3 ^
                break;  h; a5 Y% a! D: [( @- p
        case  8: // next row PA8
. h& ?" x& l# I) E. B8 P                GPIOB->MODER = GPIOB_DEFAULT;
; [' S8 y7 i" D  ]! `+ G                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
( a: d/ B: o2 ^7 s- q                break;. K& E7 g6 C, |! p+ [. G
        case  9: // next row PA99 Y0 o" l9 ], m2 l
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;
0 l- M/ z; w+ z2 L7 h0 @3 }                break;; @( h) W9 z9 p
        case 10: // next row PA10
% S) S/ s- Z. A8 `- |7 W                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;/ K2 k  S0 `1 v! n
                break;  P  M$ v# G2 N8 U+ Z* k  z( N
        case 11: // next row PA15& i8 V0 o% Y/ R9 J+ @7 n1 X
                GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;
  P! J  z) ?4 b# {                break;
+ G% w, S- `' B2 Z- ^9 Q/ h        case 12: // next row PC131 y8 L6 E& ?* o5 O/ v+ F; D2 {
                GPIOA->MODER = GPIOA_DEFAULT;+ V3 n4 B! K' k" \; b, }4 N
                GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
* y4 R; b2 B* n. G: v% r$ l3 X# D7 {                break;- g" d& B  J8 R2 c6 S" i, ]6 |. C  Q: \
    }
5 i# u% K; E% y" H: ]9 T5 Y. H7 c    if(scan_row<13)3 q. n; z8 q9 h
        scan_row++;8 \- Z! A, C& M5 e; S
    else
9 W& T+ F$ N, F  e        scan_row=0;( X  D$ y5 G8 M% v$ b
}
/ w/ [: S6 n0 |* ^# b) S7 T1 P
" z4 d  M+ @; g" l) ?
1 w( M) @, t4 l2 h" K6 Z$ ?1 k, r
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-21 21:01:28 | 显示全部楼层
本帖最后由 miaozhuang 于 2016-9-21 22:02 编辑
8 n! v! @# A# S! Z/ F5 L- ?
* Q! A( p* Y, U' R  \扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。
, j) v0 E( ~8 j% M% @
. k) y; m7 G  m+ T要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:

) m& c8 m/ ~3 A. z3 i( l0 I, ?3 N; @+ M+ L
# I! m) p+ C1 U0 C0 N5 z
const char hid_keymap_qwerty[14][8]={* \9 B3 u/ n8 W% K6 q& `$ _) q
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},
  Y- l" B8 h$ V' c1 {    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},$ h) R/ D( j+ t& m# X; P6 k* Y
    {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},
; |% n2 c3 H  T6 |    {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},
" v7 l/ R  d8 w    {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},
7 E% P% H  t3 V5 b! n8 u0 V    {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},0 ?& O+ Q& H1 a3 Q
    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},: k& o) a; `. y3 E8 o+ R# J$ {5 R
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},/ Z3 H1 ]- _/ w
    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
/ h. d0 A6 e& |% @, q! Z    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
0 C6 J/ U/ n, J1 A0 n    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
9 X% \- e, \" t( B    {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},2 Y/ A( S3 }, L8 a/ B6 t
    {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},2 @0 r- P; |% V' I( s+ T$ s1 z
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
3 J; }' ^, n2 ?- a; w3 J5 s};
/ a5 A* i/ I6 Y3 C  ]- P6 q
& b5 H3 u2 Q( w

4 D" L+ }$ e4 I! ~+ Dconst char hid_keymap_dvorak[14][8]={9 Z1 [( t/ k2 b  v4 L
    {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
: p( l; j  C3 H& s0 Y+ {! g4 t' O    {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},$ y) r$ ^1 |8 V' ^" p9 ~+ s
    {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},
& Y) u% ^# z! `, r- k& h% s. ?0 I    {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},* I4 T5 o# b1 c- N. c
    {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},2 j# f! t7 r( r% n1 ]
    {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
& A9 ]$ `5 k" r% j9 Y% x3 V    {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},& N/ f2 V$ H5 X' I2 s9 M
    {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},
$ X, V* ]' ~& ^! W    {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
8 R1 e$ S. H0 F& k! I4 ~) h2 e- L    {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
: C+ Y* n+ s# P2 N6 l    {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},% N; K2 d) ]8 j, x. T, h1 Y7 F
    {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},  ]2 F0 \/ B+ I" X2 y& p. O+ b
    {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},0 ^( K% F" L* S% P; @
    {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}. E: Y7 W) d: d2 p- A. n4 x
};5 u; N) p; ~. u" X# E% P, J9 s% s/ j0 \9 `
% [( j' `9 [& W, c& B# D
5 }4 C: S+ o# e1 H- V, ?- N9 b
上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。
8 {$ F- U0 Y% r* }# y1 w& x- a% j2 q0 l5 a. K
HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:
' e( C0 t7 ~2 n0 Q( h8 S( G3 n
. G) W4 j0 A0 N) |( n
void update_key_matrix(char row, char col, char onoff)" G3 w6 `" k  s5 p
{
8 B# b! n2 q9 v3 f1 |    static uint16_t hid_report[4]={0,0,0,0};
% Q  Z0 m; L% n& ~  |% G    static char (*hid_keymap)[8]=hid_keymap_dvorak;
4 B3 E& @/ x& u, @6 p
& M' T' Q5 f- s. v
' t1 L% Y3 o; I: z
    unsigned char key=hid_keymap[row][col];
$ f0 b9 O- O* Q' P2 O    unsigned char *report =(unsigned char *)hid_report;
+ W5 l; J& C- o. J6 N    char i;
8 F9 G7 ^2 i2 \2 r! A. J# \1 Y8 x4 L  L! a5 h

/ D1 d- ?& k$ \# H1 T7 r    if(key==HK_MODE)  k- P" M# C3 e/ Y4 o0 \8 a6 D
    {$ T; N) t* n( ^3 h
        if(!onoff)
: B6 [/ ?0 ]; z9 Z        {1 v/ U7 ^) I/ n6 F! a% ^
            if(hid_keymap==hid_keymap_dvorak)' N) A8 E2 n* p8 H
            {
" W' }. Z3 w( v# x. B                hid_keymap=hid_keymap_qwerty;
/ ?& H- t4 u# g0 G, u  D' Q" ^5 D                GPIOB->BSRR = (1<<2);
* Z! v/ T5 J* G            }
9 D8 r* }! ^( o! w            else6 k$ K* n* H2 @% i
            {
5 |, m4 p$ U" r; P- z' U0 C1 L4 q                hid_keymap=hid_keymap_dvorak;0 X. m0 W/ o$ r
                GPIOB->BRR = (1<<2);
$ E! T, F- s; Q            }
' p, U8 ~* m5 T6 m- U        }% Q$ u* d5 `" `6 ^. p0 k
        return;) p( h* y, w# Q- L
    }4 c6 E) ?- p7 L0 R7 G- Q6 e
7 G# e* Q. S  N% g4 }
. c9 F5 b+ d( x$ u. D3 n) |9 H: W
    if(key>=0x80)   // Alt, Ctrl, Shift0 C$ k. C. u7 Y9 X7 t! B. N% L# m$ w5 `
    {9 H: e0 u! u+ f2 g) s
        uint8_t bitset = 1<<(key&7);
% R3 @* l3 J# {        if(onoff)   // non-zero is key up5 h, y% e0 n$ ]. h" h5 g
            report[0] &= (~bitset);
9 S! G" Q/ v$ S        else
5 V% U% @; P/ a' d( d" k            report[0] |= bitset;
' c# |% M) f1 z) C4 z! b    }
! K2 W) y3 a* [6 i    else
# i3 ^2 a$ g! ~, B! w5 L7 c! F$ p    {5 V* U5 J9 h" h, M
        if(onoff)   // non-zero is key up0 d# L7 r' K3 K' U, J+ _+ O# ~
        {" a* t& a8 T9 u
            for(i=2;i<8;i++)% e7 C8 S- [. N1 L( ]5 c
            {6 P/ ?# v1 A  C6 d' v
                if(report==key)
* M  `8 h. E1 a( R5 N                {
  u2 ]: v" B' v% n                    report=0;+ d" p/ S: o. F) l# [: q
                    break;! R2 T; ]3 N5 s7 R8 N7 L
                }
4 z; |7 k! p" ?7 _7 i            }  j1 S7 T. b8 z. s/ |4 s, \- J$ Y
        }
& X) K2 X! p, c: v4 `        else
4 j1 g. M! U9 _, d/ v, G/ l, _        {! |7 L8 Q7 a" t$ i. f" h  u* f2 w
            for(i=2;i<8;i++)
# g! m! w5 p5 ^& W% N7 b4 a            {
' P: r9 r& {* I; {$ G; t" I                if(report==key)
$ c6 p5 w1 n: @                    break;3 H# R8 t, f& }) ^& E. p, {
                if(report==0)% x) e, j5 R( ]
                {
: Y* u' K0 Y" i6 y4 [+ o                    report=key;4 n9 R& z" E& a9 `$ H# ~
                    break;
9 f  K: e- c; q  M                }5 y- {: q  j7 g/ q
            }
& B" P2 A. m6 p! D        }
' }6 c% e/ n( X* I    }
5 X0 q8 |% x7 M    for(i=0;i<4;i++)
% u! p( x9 y) _7 T  c* p+ o/ d7 C        USB_PMA[192+i]=hid_report;6 c5 Q; k" j0 b$ p
    USB_PMA[5]=8;   //COUNT1_TX# i7 ]! E! f% _0 c0 G8 }. z
    if(ep1_wait==0)* b+ r) ~4 w4 U7 s% o
    {
  n4 r% I# k3 R: F) r5 p        USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;
- H1 w: }: t$ a1 q" \% O/ u        ep1_wait=1;* U  F: N! L  h5 E
    }% |5 ]) e) F- W' ~/ z! T( D
}
0 L0 @' v4 U: m3 W1 C  J) y9 |6 i3 I: |7 {
% d1 @1 ?+ x. `8 \) e
完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。! K* d5 X$ f5 M; v
keyboard.zip (8.7 KB, 下载次数: 6124) 3 R# r+ h0 K6 ^. T
" p6 w! {, m2 C  O
; U& e" u- r1 L: _& o

. X9 C, _2 X3 w& n
, A5 f; f' s% J
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

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

使用道具 举报

8

主题

143

回帖

217

积分

中级会员

积分
217

社区居民

发表于 2016-9-22 13:48:09 | 显示全部楼层
刚开始我以为要把打字机改造成电脑键盘
& o  |$ J* g* {不过楼主也很厉害!
回复

使用道具 举报

24

主题

39

回帖

288

积分

vip

积分
288
 楼主| 发表于 2016-9-22 19:17:17 | 显示全部楼层
网络孤客 发表于 2016-9-22 13:48
9 ^1 s/ q( F% g1 \6 f0 A) d# G刚开始我以为要把打字机改造成电脑键盘# a1 u$ {" g* M9 Z$ a) A
不过楼主也很厉害!

, H3 h# e' D: B  s7 w+ w哈哈  是有点像奥
回复

使用道具 举报

0

主题

3

回帖

4

积分

新手上路

积分
4
发表于 2017-7-10 21:06:48 | 显示全部楼层
很厉害6 Y; `- ~" B: q$ f* h8 E
回复

使用道具 举报

0

主题

2

回帖

2

积分

新手上路

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-5-10 02:35 , Processed in 0.177842 second(s), 24 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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