22537|24

1382

帖子

2

TA的资源

五彩晶圆(初级)

楼主
 

【任性DIY】老机械键盘改造USB,QWERTY/Dvorak一键切换 [复制链接]

 
本帖最后由 cruelfox 于 2016-8-2 12:16 编辑

这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。

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

上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。

为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:


其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.

Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:

Dvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。

我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。

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

机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:

从QWERTY换到Dvorak,除了决心以及过程中的痛苦外,还有额外的成本。一是操作系统的支持,虽然DOS, Windows, Linux都支持Dvorak,但需要加载keymap,或者设置键盘布局,且每台机器,每个用的系统都要改。在Windows上,Dvorak和默认的En-US是平级的,但中文输入法只能用En-US也就是压根儿没考虑Dvorak. 于是我将en_us.dll直接替换掉了,但也不是完美的解决,比如Sogou拼音会从更底层调用读键盘,还是没法用(于是我一直用智能ABC咯)。二是用别人电脑的时候,比如同事要请帮忙,又不能SSH过去,我就只好盯着键盘来“一指禅”了;以及电脑安装系统的时候,应急启动时候,类似的困难。三是我的电脑夫人也就没法用,同样的道理。四是虽然内部变成Dvorak,键盘上印的还是QWERTY那样的,必须盲打,必须双手干活,不能一只手拿着食物啦。这时候我多希望它还是QWERTY,可以用用一指禅。

综上,在操作系统软件层次上修改键盘布局来使用Dvorak,问题还是多多。那么我在键盘上面改,硬件直接搞定好了。附带的好处是可以随时切换键盘布局,键盘也可以共享给夫人用。国产老机械键盘里面主控是8049 MCU,虽然不能对它编程,我换掉它还是可以的。于是就有了这次的“任性"DIY。

----------------------------------------------------  分割线 ----------------------------------------------------------

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

轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。

特写,80C49

LED部分,使用了一片D触发器锁存指示灯状态.

暴力破坏,将80C49拆掉。


拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。

焊好元件后的板子,准备替换80C49


用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来下载程序的。

这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB.


开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。

我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。


主键区键帽就位

编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。


最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。


----------------------------------------------------- 分割线 --------------------------------------------------
DIY过程直播完了,下面说硬件的设计。
80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:

其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)

扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。

这是我设计的电路图:

PCB Layout:

不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O.


最新回复

太牛了。。。。。   详情 回复 发表于 2020-11-8 21:55

赞赏

4

查看全部赞赏

点赞(1) 关注(7)
 

回复
举报

1382

帖子

2

TA的资源

五彩晶圆(初级)

沙发
 
本帖最后由 cruelfox 于 2016-7-30 12:48 编辑

软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。

总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。下面是用USBTreeView这个工具查看到的的我的这个键盘的描述符:

其中USB报告描述符我没完全看懂,copy了现成的(这个也没必要自己重新写嘛)

USB的中断ISR,bare metal哦
  1. void USB_IRQHandler(void)
  2. {
  3.     if(USB->ISTR & USB_ISTR_CTR)
  4.     {
  5.         if((USB->ISTR & 0x0f)==0)   // EP_ID==0
  6.         {
  7.             switch(ep0_state)
  8.             {
  9.                 case 0: ep0_state |= 0x80;
  10.                         break;
  11.                 case 1: if(USB->EP0R & USB_EP_CTR_TX)
  12.                         {
  13.                             USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
  14.                             ep0_state=3;
  15.                             return;
  16.                         }
  17.                         else
  18.                             ep0_state=0;
  19.                         break;
  20.                 case 2: if((USB->EP0R & USB_EP_SETUP)==0)   // OUT packet
  21.                             ep0_state |= 0x80;
  22.                         else
  23.                             ep0_state=0;
  24.                         break;
  25.                 case 3: ep0_state=0;
  26.                         break;
  27.                 case 4: ep0_state=0;
  28.                         break;
  29.                 default:ep0_state=0;
  30.                         break;
  31.             }
  32.             USB->EP0R = USB_EP_TYPE_CONTROL; // just clear flag
  33.             return;
  34.         }
  35.         else    // EP_ID can be 1
  36.         {
  37.             USB->EP1R = USB_EP_TYPE_INTERRUPT|1; // just clear flag
  38.             ep1_wait=0;
  39.             return;
  40.         }
  41.     }
  42.     if(USB->ISTR & USB_ISTR_PMAOVR)
  43.     {
  44.         USB->ISTR = ~USB_ISTR_PMAOVR;
  45.     }
  46.     if(USB->ISTR & USB_ISTR_ERR)
  47.     {
  48.         USB->ISTR = ~USB_ISTR_ERR;  // write 0 to clear
  49.     }
  50.     if(USB->ISTR & USB_ISTR_WKUP)
  51.     {
  52.         USB->CNTR &= ~USB_CNTR_FSUSP;   // no suspend
  53.         USB->ISTR = ~USB_ISTR_WKUP; // write 0 to clear
  54.     }
  55.     if(USB->ISTR & USB_ISTR_SUSP)
  56.     {
  57.         USB->CNTR |= USB_CNTR_FSUSP;    // force suspend
  58.         USB->CNTR |= USB_CNTR_LPMODE;   // low power
  59.         USB->ISTR = ~USB_ISTR_SUSP; // write 0 to clear
  60.     }
  61.     if(USB->ISTR & USB_ISTR_RESET)
  62.     {
  63.         USB->BTABLE = 0;    // buffer table at bottom of PMA
  64.         USB_PMA[0]=128; //ADDR0_TX
  65.         USB_PMA[1]=0;   //COUNT0_TX
  66.         USB_PMA[2]=256; //ADDR0_RX
  67.         USB_PMA[3]=0x8000|(3<<10);  //RX buffer 128 bytes
  68.         USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX1;
  69.         ep0_state=0;
  70.         USB_PMA[4]=384; //ADDR1_TX
  71.         USB_PMA[5]=0;   //COUNT1_TX
  72.         USB->EP1R = USB_EP_TYPE_INTERRUPT|1;    // EP1 type
  73.         ep1_wait=0;
  74.         USB->DADDR = USB_DADDR_EF;      // enable function
  75.         USB->ISTR = ~USB_ISTR_RESET;    // write 0 to clear
  76.     }
  77.     if(USB->ISTR & USB_ISTR_SOF)
  78.     {
  79.         USB->ISTR = ~USB_ISTR_SOF;  // write 0 to clear
  80.     }
  81. }
复制代码
因为只有两个EP需要管,数据量也很小,STM32F0的PMA(Packet Memory Area)固定分配好就不动了,读写数据都直接在PMA上读写。在USB Reset中断的时候,把PMA和EP都重新初始化。

主程序中用一个无限循环,每次中断过后处理一下USB请求,以及来自键盘扫描的检测。
  1.     while(1)
  2.     {
  3.         static char row=0;
  4.         __WFI();
  5.         if(ep0_state & 0x80)    // request data processing
  6.         {
  7.             if(ep0_state==0x80) // SETUP phase
  8.             {
  9.                 if(!setup_packet_service())
  10.                 {
  11.                     ep0_state=0;
  12.                     // not supported
  13.                 }
  14.                 // ep0_state should be set to 1 or 2, if processed
  15.             }
  16.             else    // OUT phase
  17.             {
  18.                 // process data
  19.                 show_LED(*(uint8_t *)(USB_PMA+128));
  20.                 ep0_state=4;
  21.                 USB_PMA[1]=0;       // Zero length DATA0
  22.                 USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
  23.             }
  24.         }
  25.         else
  26.         {
  27.             if(usb_address && ep0_state==0)
  28.             {
  29.                 USB->DADDR = USB_DADDR_EF|usb_address;
  30.                 usb_address=0;
  31.             }
  32.         }
  33.         if(row!=scan_row)   // new scan line
  34.         {
  35.             if(key_state[row]!=prev_key_state[row])
  36.             {
  37.                 uint8_t test=0x80;
  38.                 uint8_t diff=key_state[row]^prev_key_state[row];
  39.                 for(i=0;i<8;i++)
  40.                 {
  41.                     if(diff & test)
  42.                         update_key_matrix(row,i,key_state[row]&test);
  43.                     test>>=1;
  44.                 }
  45.             }
  46.             row=scan_row;
  47.         }
  48.     }
复制代码

EP0的控制传输,把用到的请求处理一下
  1. char setup_packet_service(void)
  2. {
  3.     if(ep0_std_req->bmRequestType & 0x20)   // class-specific
  4.     {
  5.         switch(ep0_std_req->bRequest)
  6.         {
  7.             case REQ_GET_REPORT: break;
  8.             case REQ_GET_IDLE:
  9.                 USB_PMA[64]=0xfa;   // return 1 byte
  10.                 USB_PMA[1]=1;
  11.                 USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
  12.                 ep0_state=1;
  13.                 return 1;
  14.                 break;
  15.             case REQ_SET_REPORT:
  16.                 USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_RX0;
  17.                 ep0_state=2;
  18.                 return 1;
  19.                 break;
  20.             case REQ_SET_IDLE:
  21.                 USB_PMA[1]=0;   // Zero DATA
  22.                 USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
  23.                 ep0_state=4;
  24.                 return 1;
  25.                 break;
  26.         }
  27.         return 0;
  28.     }
  29.     else    // standard
  30.     {
  31.         switch(ep0_std_req->bRequest)
  32.         {
  33.             case REQ_GET_DESCRIPTOR:
  34.                 return descriptor_service();
  35.                 break;
  36.             case REQ_SET_ADDRESS:
  37.                 if(ep0_std_req->bmRequestType!=0x00)
  38.                     return 0;
  39.                 usb_address=ep0_std_req->wValue;
  40.                 USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
  41.                 USB_PMA[1]=0;       // Zero length DATA0
  42.                 ep0_state=4;    // No Data phase
  43.                 return 1;
  44.             case REQ_SET_CONFIGURATION:
  45.                 if(ep0_std_req->bmRequestType!=0x00)
  46.                     return 0;
  47.                 USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX1|1;
  48.                 USB->EP0R = USB_EP_TYPE_CONTROL|USB_EP_STAT_TX0;
  49.                 USB_PMA[1]=0;   // Zero DATA
  50.                 ep0_state=4;    // No DATA phase
  51.                 return 1;
  52.             default: return 0;
  53.         }
  54.     }
  55. }
复制代码

各种描述符,是USB开发首先要处理的
  1. char descriptor_service(void)
  2. {
  3.     switch((ep0_std_req->wValue)>>8)
  4.     {
  5.         case DESC_TYPE_DEVICE:
  6.             return ep0_preparedata(&DevDesc, sizeof(DevDesc));
  7.             break;
  8.         case DESC_TYPE_CONFIG:
  9.             if(sizeof(ConfigDescData)>ep0_std_req->wLength)
  10.                 return ep0_preparedata(&ConfigDescData, ep0_std_req->wLength);
  11.             else
  12.                 return ep0_preparedata(&ConfigDescData, sizeof(ConfigDescData));
  13.             break;
  14.         case DESC_TYPE_STRING:
  15.             switch(ep0_std_req->wValue &0xff)
  16.             {
  17.                 case 0 : return ep0_preparedata(&LangIDDesc, sizeof(LangIDDesc));
  18.                 case 1 : return ep0_preparedata(&UnicodeVendorDesc, sizeof(UnicodeVendorDesc));
  19.                 case 2 : return ep0_preparedata(&UnicodeProdDesc, sizeof(UnicodeProdDesc));
  20.                 default: return 0;
  21.             }
  22.             break;
  23.         case REPORT_DESC_TYPE:
  24.             return ep0_preparedata(&HidReportDesc, sizeof(HidReportDesc));
  25.         default:
  26.             return 0;
  27.     }
  28. }
复制代码

下面说下我对键盘的处理。因为有14+8条线,其中8条一组我称为列,用PA0~7读取;另外14条我称为行,在一个时刻只有1条为低电平(输出),其它13条为高阻。在列扫描线上加上上拉,因此没有键按下的时候,PA0~7都是高电平的。若某键被按下,当在所在的行被扫描时,对应的列就会变成低。我用了Timer6中断,每0.5ms切换一次扫描线,这样扫描一遍矩阵键盘用7ms.
  1. void TIM6_DAC_IRQHandler(void)
  2. {
  3.     __IO uint8_t *PA_IDR = (uint8_t *)&(GPIOA->IDR);

  4.     TIM6->SR &= ~TIM_SR_UIF;
  5.     prev_key_state[scan_row]=key_state[scan_row];
  6.     key_state[scan_row]= *PA_IDR;   // update key states
  7.     switch(scan_row)
  8.     {
  9.         case 13: // next row PB14
  10.                 GPIOC->MODER = GPIOC_DEFAULT;
  11.                 GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER14_0;
  12.                 break;
  13.         case  0: // next row PB15
  14.                 GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER15_0;
  15.                 break;
  16.         case  1: // next row PB3
  17.                 GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER3_0;
  18.                 break;
  19.         case  2: // next row PB4
  20.                 GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER4_0;
  21.                 break;
  22.         case  3: // next row PB5
  23.                 GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER5_0;
  24.                 break;
  25.         case  4: // next row PB6
  26.                 GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER6_0;
  27.                 break;
  28.         case  5: // next row PB7
  29.                 GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER7_0;
  30.                 break;
  31.         case  6: // next row PB8
  32.                 GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER8_0;
  33.                 break;
  34.         case  7: // next row PB9
  35.                 GPIOB->MODER = GPIOB_DEFAULT|GPIO_MODER_MODER9_0;
  36.                 break;
  37.         case  8: // next row PA8
  38.                 GPIOB->MODER = GPIOB_DEFAULT;
  39.                 GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER8_0;
  40.                 break;
  41.         case  9: // next row PA9
  42.                 GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER9_0;
  43.                 break;
  44.         case 10: // next row PA10
  45.                 GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER10_0;
  46.                 break;
  47.         case 11: // next row PA15
  48.                 GPIOA->MODER = GPIOA_DEFAULT|GPIO_MODER_MODER15_0;
  49.                 break;
  50.         case 12: // next row PC13
  51.                 GPIOA->MODER = GPIOA_DEFAULT;
  52.                 GPIOC->MODER = GPIOC_DEFAULT|GPIO_MODER_MODER13_0;
  53.                 break;
  54.     }
  55.     if(scan_row<13)
  56.         scan_row++;
  57.     else
  58.         scan_row=0;
  59. }
复制代码

扫描的状态被Timer 6 ISR记录在 key_state[] 数组当中,上一次的状态保存为 prev_key_state[]. 这样在主程序中只要比较这两个数组,就知道是否键盘的状态有变化了。有变化(按下或者抬起)的时候,再调用 update_key_matrix() 函数去生成USB HID报告。

要从键盘的扫描行、列坐标得到按键的编码(此处不是ASCII,也不是PS/2的键盘扫描码,而是USB HID定义的键盘Usage ID),需要用到查找表。我需要切换Dvorak和QWERTY两个布局,因此需要准备两个查找表:
  1. const char hid_keymap_qwerty[14][8]={
  2.     {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_R, HK_7, HK_F9, HK_Esc},
  3.     {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
  4.     {HK_Period, HK_V, HK_BSlash, HK_J, HK_P, HK_Q, HK_4, HK_F6},
  5.     {HK_Comma, HK_C, HK_Quote, HK_H, HK_O, HK_Equal, HK_3, HK_F5},
  6.     {HK_M, HK_X, HK_Colon, HK_G, HK_I, HK_Minus, HK_2, HK_F4},
  7.     {HK_LShift, HK_NONE, HK_Caps, HK_F, HK_U, HK_0, HK_1, HK_F3},
  8.     {HK_Scroll, HK_LAlt, HK_RCtrl, HK_D, HK_Y, HK_9, HK_BQuote, HK_F2},
  9.     {HK_NONE, HK_RAlt, HK_LCtrl, HK_S, HK_T, HK_8, HK_Enter, HK_F1},
  10.     {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
  11.     {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
  12.     {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
  13.     {HK_Space, HK_N, HK_Z, HK_L, HK_RBr, HK_E, HK_6, HK_F8},
  14.     {HK_Slash, HK_B, HK_Delete, HK_K, HK_LBr, HK_W, HK_5, HK_F7},
  15.     {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
  16. };

  17. const char hid_keymap_dvorak[14][8]={
  18.     {HK_RShift, HK_NONE, HK_NONE, HK_A, HK_P, HK_7, HK_F9, HK_Esc},
  19.     {HK_F12, HK_BS, HK_Up, HKR_Slash, HKR_Aster, HK_NONE, HKR_Enter, HK_NONE},
  20.     {HK_V, HK_K, HK_BSlash, HK_H, HK_L, HK_Quote, HK_4, HK_F6},
  21.     {HK_W, HK_J, HK_Minus, HK_D, HK_R, HK_RBr, HK_3, HK_F5},
  22.     {HK_M, HK_Q, HK_S, HK_I, HK_C, HK_LBr, HK_2, HK_F4},
  23.     {HK_LShift, HK_NONE, HK_Caps, HK_U, HK_G, HK_0, HK_1, HK_F3},
  24.     {HK_Scroll, HK_LAlt, HK_RCtrl, HK_E, HK_F, HK_9, HK_BQuote, HK_F2},
  25.     {HK_NONE, HK_RAlt, HK_LCtrl, HK_O, HK_Y, HK_8, HK_Enter, HK_F1},
  26.     {HK_PgUp, HK_Home, HK_Insert, HKR_5, HKR_6, HKR_4, HK_F10, HKR_Dot},
  27.     {HK_Right, HK_Down, HK_Left, HKR_Num, HKR_Plus, HK_NONE, HK_NONE, HK_NONE},
  28.     {HK_PgDn, HK_End, HK_F11, HKR_9, HKR_Minus, HKR_8, HKR_7, HK_NONE},
  29.     {HK_Space, HK_B, HK_Colon, HK_N, HK_Equal, HK_Period, HK_6, HK_F8},
  30.     {HK_Z, HK_X, HK_Delete, HK_T, HK_Slash, HK_Comma, HK_5, HK_F7},
  31.     {HK_Pause, HK_Tab, HK_PrtScr, HKR_2, HKR_3, HKR_1, HKR_0, HK_MODE}
  32. };
复制代码
上面的宏定义另写在 translate.h 头文件中。HK_NONE是没有实际按键的位置,HK_MODE是我另外加的一个键,用来切换两个键盘布局。这个“一键切换”的键加装,可以用原来键盘上的AT/XT开关,也可以用我预留的User I/O,做到后来发现用键盘矩阵的空闲位置更方便。

HID报告的8个字节,第一个是8个特殊键的状态(Shift, Alt, Ctrl, GUI),第二个保留,后6个每个非0值是一个按下的键的Usage ID. 产生HID报告的子程序:
  1. void update_key_matrix(char row, char col, char onoff)
  2. {
  3.     static uint16_t hid_report[4]={0,0,0,0};
  4.     static char (*hid_keymap)[8]=hid_keymap_dvorak;

  5.     unsigned char key=hid_keymap[row][col];
  6.     unsigned char *report =(unsigned char *)hid_report;
  7.     char i;

  8.     if(key==HK_MODE)
  9.     {
  10.         if(!onoff)
  11.         {
  12.             if(hid_keymap==hid_keymap_dvorak)
  13.             {
  14.                 hid_keymap=hid_keymap_qwerty;
  15.                 GPIOB->BSRR = (1<<2);
  16.             }
  17.             else
  18.             {
  19.                 hid_keymap=hid_keymap_dvorak;
  20.                 GPIOB->BRR = (1<<2);
  21.             }
  22.         }
  23.         return;
  24.     }

  25.     if(key>=0x80)   // Alt, Ctrl, Shift
  26.     {
  27.         uint8_t bitset = 1<<(key&7);
  28.         if(onoff)   // non-zero is key up
  29.             report[0] &= (~bitset);
  30.         else
  31.             report[0] |= bitset;
  32.     }
  33.     else
  34.     {
  35.         if(onoff)   // non-zero is key up
  36.         {
  37.             for(i=2;i<8;i++)
  38.             {
  39.                 if(report[i]==key)
  40.                 {
  41.                     report[i]=0;
  42.                     break;
  43.                 }
  44.             }
  45.         }
  46.         else
  47.         {
  48.             for(i=2;i<8;i++)
  49.             {
  50.                 if(report[i]==key)
  51.                     break;
  52.                 if(report[i]==0)
  53.                 {
  54.                     report[i]=key;
  55.                     break;
  56.                 }
  57.             }
  58.         }
  59.     }
  60.     for(i=0;i<4;i++)
  61.         USB_PMA[192+i]=hid_report[i];
  62.     USB_PMA[5]=8;   //COUNT1_TX
  63.     if(ep1_wait==0)
  64.     {
  65.         USB->EP1R = USB_EP_TYPE_INTERRUPT|USB_EP_STAT_TX0|1;
  66.         ep1_wait=1;
  67.     }
  68. }
复制代码

完整的程序在附件中。我这个程序没有使用任何USB的库函数,完全是从底层操作,这样对USB的工作细节可以了解得比较清楚,当然,也费了很多工夫啦。

keyboard.zip

8.7 KB, 阅读权限: 5, 下载次数: 53

 
 

回复

305

帖子

0

TA的资源

一粒金砂(中级)

板凳
 
楼主牛的不行啊
 
 
 

回复

6423

帖子

16

TA的资源

版主

4
 
既然是固件自己写的,为什么还要系统支持呢?只是键位布局变了,发的编码应该是没有变化的吧,所以之前的驱动什么的不就什么都不用动吗?

点评

说系统支持是针对普通的键盘,在普通的键盘上使用Dvorak. 现在我从键盘里面改,就把键盘发出的编码也改了,这样系统就不用动了。  详情 回复 发表于 2016-7-30 13:35
个人签名training
 
 
 

回复

1382

帖子

2

TA的资源

五彩晶圆(初级)

5
 
白丁 发表于 2016-7-30 13:30
既然是固件自己写的,为什么还要系统支持呢?只是键位布局变了,发的编码应该是没有变化的吧,所以之前的驱 ...

说系统支持是针对普通的键盘,在普通的键盘上使用Dvorak.
现在我从键盘里面改,就把键盘发出的编码也改了,这样系统就不用动了。
 
 
 

回复

821

帖子

0

TA的资源

一粒金砂(高级)

6
 
玩这个真有点牛
 
 
 

回复

196

帖子

0

TA的资源

一粒金砂(中级)

7
 
这个牛!编程使用Dvorak是会比较方便吗?

点评

编程语言书写会用到很多英语单词,用Dvorak布局也是受益的。就算是用汉语拼音,用Dvorak也能受益。用五笔的就不行了啊 有一种给程序员用的"Dvorak programmer's layout"更为激进, 把数字键的功能换成符号  详情 回复 发表于 2016-7-30 21:57
 
 
 

回复

1382

帖子

2

TA的资源

五彩晶圆(初级)

8
 
leifengfirst 发表于 2016-7-30 15:32
这个牛!编程使用Dvorak是会比较方便吗?

编程语言书写会用到很多英语单词,用Dvorak布局也是受益的。就算是用汉语拼音,用Dvorak也能受益。用五笔的就不行了啊
有一种给程序员用的"Dvorak programmer's layout"更为激进, 把数字键的功能换成符号了,输入数字需要用Shift.
如果已经习惯了QWERTY,最大的困难是要将QWERTY遗忘才能熟练用Dvorak.
 
 
 

回复

1万

帖子

25

TA的资源

版主

9
 
很牛。
 
 
 

回复

1950

帖子

4

TA的资源

版主

10
 
你还是那么的牛。说来就来,都是干货。
而且东西整的干净,代码整的也干净。
个人签名MicroPython中文社区https://micropython.org.cn/forum/  
 
 
 

回复

943

帖子

3

TA的资源

纯净的硅(中级)

11
 
顶礼膜拜
 
 
 

回复

1403

帖子

1

TA的资源

纯净的硅(中级)

12
 
666666,看的好过瘾~
个人签名HELLO_WATER
 
 
 

回复

105

帖子

0

TA的资源

一粒金砂(中级)

13
 
牛逼
 
 
 

回复

735

帖子

4

TA的资源

纯净的硅(初级)

14
 
这样的玩法有意思!
个人签名徐建
20091127
 
 
 

回复

16

帖子

0

TA的资源

一粒金砂(中级)

15
 
怀才就是象怀孕时间久了才能看出来
 
 
 

回复

9

帖子

0

TA的资源

一粒金砂(中级)

16
 
玩这个真有点牛
 
 
 

回复

24

帖子

0

TA的资源

一粒金砂(中级)

17
 
楼主厉害,收藏了你的代码,学习一下!
 
 
 

回复

178

帖子

0

TA的资源

一粒金砂(高级)

18
 
高人
 
 
 

回复

66

帖子

4

TA的资源

一粒金砂(中级)

19
 
点个赞先!!
 
 
 

回复

18

帖子

0

TA的资源

一粒金砂(初级)

20
 
楼主非常牛逼
 
 
 

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

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/6 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表