[FastBond2阶段2]基于STM32F1+机械键盘pcb设计完成一个魔改60键键位的PC电子琴
设计了一个简易的魔改电子琴键盘,用于与PC端的音乐软件互通,而实现简易的电子琴功能。具体为一个STM32单片机控制一个机械键盘的设计,其中把常规的键盘设计改为类似电子琴的键位,同时使用usb的hid协议,成功设计一个电子琴键盘
标签
嵌入式系统
开发板
USB
FastBond第二季
得捷
ZHAO
更新2023-12-01
南京邮电大学
480

一、项目介绍

本项目是一个基于STM32F103为核心主控,加上一个组合的PCB设计的机械键盘,加上更改为60键位的电子琴键位,结合PC的音源软件,设计了一个简易的60键位电子琴。可以使用这个键盘去进行一定的演奏和练习曲子,音阶为2、3、4、5、6,五个刚好组成60个键位。

(成品图)Fgeg8SQUXuGVjWUPXDNY4QKwbin7

二、市场应用介绍

本次项目可作为常见电子琴的平替,总共成本算下来非常低,不超过50,加上3D外壳估计也不超过100,而且后续可以通过更改太空轴,使得更加贴合电子琴键位,pcb总计12,市面上的stm32f103价格也不高,热插拔座也只有20,唯一的成本只有轴体,而这个可以通过拆卸旧的键盘或者购入二手的低成本键盘即可。其次,为了方便些,后续可以改为蓝牙,也可以在pcb上加一下其他的设计,以实现智能化的交互。

三、项目设计思路

  1. 由于pc音源软件的存在,完全可以自己diy一个非常规配列的矩阵键盘。
  2. 主控使用最常见的stm32的开发版即可,同时使用较为便宜的f1也非常合适
  3. 键盘部分,考虑到一个80cm长的pcb成本会非常高,所以采取分段式的设计
  4. 由于3,所以使用五行12列的配置,且考虑到尺寸问题,使用小尺寸二极管更合适,所以阶段一的方案三淘汰掉,使用常规方案2
  5. stm32部分使用正常的usb hid协议去实现键盘功能
  6. 具体功能如下
  • 使用矩阵键盘加机械轴体的pcb设计,作为输入设备
  • 使用stm32作为主控,接受到键盘输入的数据并且进行处理
  • 使用串口和USB向电脑输入数据,使得键盘按下的时候,电脑可以通过串口调试助手接受到键盘按下的数据,同时通过usb hid协议,实现键盘功能。
  • 结合上述基本功能,在结合freepiano软件的使用,即可实现一个简易的电子琴配列

四、项目方案框图和原理图解

1、主要包括外部矩阵键盘、stm32主控、连接pc的音源软件,具体细节详见图片

(方案框图)FnvnCU9xa2PPWyAC1jpTlih8k_C9

2、实现流程图如下,也就是代码顺序(流程图)

FrBCPLKyRIntJzRmav5v79C6yhfJ

 

  • 初始化配置,初始化串口GPIO等苦库
  • 加载存放usbhid的数据数组,存放扫描键值的变量
  • 进入循环,此时加载扫描函数
  • 扫描函数开始初始化
  • 扫描函数行扫描
  • 对应行值基础上列扫描
  • 获得值并且返回函数
  • 此时根据扫描值进入对应的usb函数
  • 向串口打印键值,同时向pc设备,通过usb协议发送数据
  • pc获得键值后,进行一个低延时
  • 延时完毕对发送函数进行清零,再重新发送数据。
  • 回到循环初始的扫描函数

五、设计中用到规定厂商的元器件介绍

1、STM32F103RCT6

STM32F103RCT6是一款由STMicroelectronics生产的32位ARM Cortex-M3内核微控制器芯片。它具有丰富的外设集成,适用于广泛的应用领域,如工业控制、嵌入式系统和自动化。以下是该芯片的主要特点:

  • 核心架构: ARM Cortex-M3
  • 时钟频率: 可达72 MHz
  • 存储器: Flash存储器最大为256 KB,SRAM最大为48 KB
  • 通信接口: 支持多种通信接口,包括SPI、I2C、USART等
  • 外设: 包括定时器、ADC、DAC等多个外设,适用于多种应用场景
  • 低功耗: 支持多种低功耗模式,延长电池寿命

Fv4bqN2Ey2NFKpGBgmi3JbhUOUVw

2、1N4148

1N4148是一种常见的高速开关二极管,广泛用于电子电路中的整流、开关和保护应用。以下是1N4148二极管的主要特性:

正向电压降: 典型值为0.7 V

反向峰值电压: 100 V

正向持续电流: 300 mA

封装: DO-35封装,适合常见的电子元器件安装

1N4148通常用于信号整流、快速开关和保护电路,其快速响应时间和小封装使其成为电子设计中常见的元器件之一。

六、PCB绘制打板介绍及遇到的问题和解决方法

(1)介绍

1、PCB绘制使用KiCad,原理图比较简单,这边没有用比较形象的库,直接用开关,比较简单

2、PCB的布线按照正常的就行,注意的地方只有模型要改一下,详细的导入就不说了

3、最后外框的尺寸可以小一点,特别是跳线部分改一下尺寸就行

4、正面是轴体,背面是热插拔座和二极管。

5、PCB的正面和背面图如下

Fp3XWZ5RnHWQEdCoB43OTQAT-i3aFoIrcNUNEsnDAJhxutdwFBpbHA3l

总体

FrDXxWQ2LY-KgaugQji4SCKhgPW2

(2)问题+解决方法

1、由于矩阵键盘的原理(见前面),这里为了避免鬼键的问题,所以必须使用二极管,但是由于5*12的配列,以及考虑到代码的效率,所以行列数是严格规定的,所以PCB的设计中,二极管的方向要对,第一版本中就是把二极管方向搞混了,虽然焊接时只要交换位置就行,但是因为行列的规定,所以接线会反过来,如果是正常的一块PCB是没问题的,但是因为拼接设计,所以这里没办法解决。

2、热插拔的布局,由于轴体除了两个金属连接口之外,还有一个固定底座,我最初开始画PCB的时候,就把热插拔放反了,所以会挡住底座的口,这个也要注意

3、焊接的时候,二极管的方向也要对,如果反了的话,测试的时候,串口应该是不会接受到数据的,这个比较容易修改

4、测试的时候只需要万用表就可以了,这个比较简单就不详细说了,只需要两个表笔对准焊盘或者焊上去的器件的两端,按下轴体,看看蜂鸣器响不响就行了,如果没有声音,大部分是焊接虚焊了或者锡不够,这个热插拔底座的锡要的还是挺多的。

七、关键代码及说明

(1)关键代码

1、这部分是usbhid的配置

  /* USER CODE BEGIN 2 */
	printf("begin test");
	/*
buffer[0] - 
bit0:left ctrl
bit1:left shift
bit2:left ali
bit3:left gui
bit4:right ctrl
bit5:right shift
bit6:right ali
bit7:right gui
buffer[1]-padding = always  0x00
buffer[2]-key1
...
bufer[7]-key6
*/

	uint8_t buffer[8] = {0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00};
  /* USER CODE END 2 */

2、gpio的配置

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4
                          |GPIO_PIN_5, GPIO_PIN_RESET);

  /*Configure GPIO pins : PA1 PA2 PA3 PA4
                           PA5 */
  GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4
                          |GPIO_PIN_5;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pins : PB10 PB11 PB12 PB13
                           PB14 PB15 PB4 PB5
                           PB6 PB7 PB8 PB9 */
  GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13
                          |GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_4|GPIO_PIN_5
                          |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

3、gpio扫描的函数

/* USER CODE BEGIN 4 */
uint8_t Key_Scan(void)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	//PA5行,PB12列
	//cubemx配置错了,现在应该是5行12列,input是列,output是行
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13
                          |GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_4|GPIO_PIN_5
                          |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_RESET);
	//PA是L列,也就是竖着的,5列
	GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13
                          |GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_4|GPIO_PIN_5
                          |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
	//PB的4-15,12行H
	GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5;
	GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	HAL_Delay(10);

	//第一行PA1
	if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET)//读取第1行
	{
		/*后4个端口输出低电平*/
		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5, GPIO_PIN_RESET);
		//后4个端口推挽输出
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
		//前4个端口上拉输入
		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		GPIO_InitStruct.Pull = GPIO_PULLUP;
		GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13
                          |GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_4|GPIO_PIN_5
                          |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
		HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_4)==GPIO_PIN_RESET)return 1;//此时列引脚,对应一行的1、2、3、4列
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_5)==GPIO_PIN_RESET)return 2;//所以返回四个值
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6)==GPIO_PIN_RESET)return 3;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7)==GPIO_PIN_RESET)return 4;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8)==GPIO_PIN_RESET)return 5;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_9)==GPIO_PIN_RESET)return 6;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10)==GPIO_PIN_RESET)return 7;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_11)==GPIO_PIN_RESET)return 8;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12)==GPIO_PIN_RESET)return 9;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13)==GPIO_PIN_RESET)return 10;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_14)==GPIO_PIN_RESET)return 11;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_15)==GPIO_PIN_RESET)return 12;
	}
	
	
	//PA2第二行
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_2)==GPIO_PIN_RESET)//读取第1行
	{
		/*后4个端口输出低电平*/
		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5, GPIO_PIN_RESET);
		//后4个端口推挽输出
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
		//前4个端口上拉输入
		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		GPIO_InitStruct.Pull = GPIO_PULLUP;
		GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13
                          |GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_4|GPIO_PIN_5
                          |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
		HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_4)==GPIO_PIN_RESET)return 13;//此时列引脚,对应一行的1、2、3、4列
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_5)==GPIO_PIN_RESET)return 14;//所以返回四个值
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6)==GPIO_PIN_RESET)return 15;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7)==GPIO_PIN_RESET)return 16;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8)==GPIO_PIN_RESET)return 17;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_9)==GPIO_PIN_RESET)return 18;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10)==GPIO_PIN_RESET)return 19;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_11)==GPIO_PIN_RESET)return 20;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12)==GPIO_PIN_RESET)return 21;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13)==GPIO_PIN_RESET)return 22;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_14)==GPIO_PIN_RESET)return 23;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_15)==GPIO_PIN_RESET)return 24;
	}
	
	//第三行PA3
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_3)==GPIO_PIN_RESET)//读取第1行
	{
		/*后4个端口输出低电平*/
		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5, GPIO_PIN_RESET);
		//后4个端口推挽输出
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
		//前4个端口上拉输入
		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		GPIO_InitStruct.Pull = GPIO_PULLUP;
		GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13
                          |GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_4|GPIO_PIN_5
                          |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
		HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_4)==GPIO_PIN_RESET)return 25;//此时列引脚,对应一行的1、2、3、4列
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_5)==GPIO_PIN_RESET)return 26;//所以返回四个值
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6)==GPIO_PIN_RESET)return 27;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7)==GPIO_PIN_RESET)return 28;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8)==GPIO_PIN_RESET)return 29;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_9)==GPIO_PIN_RESET)return 30;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10)==GPIO_PIN_RESET)return 31;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_11)==GPIO_PIN_RESET)return 32;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12)==GPIO_PIN_RESET)return 33;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13)==GPIO_PIN_RESET)return 34;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_14)==GPIO_PIN_RESET)return 35;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_15)==GPIO_PIN_RESET)return 36;
	}
	
	//第四行PA4
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4)==GPIO_PIN_RESET)//读取第1行
	{
		/*后4个端口输出低电平*/
		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5, GPIO_PIN_RESET);
		//后4个端口推挽输出
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
		//前4个端口上拉输入
		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		GPIO_InitStruct.Pull = GPIO_PULLUP;
		GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13
                          |GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_4|GPIO_PIN_5
                          |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
		HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_4)==GPIO_PIN_RESET)return 37;//此时列引脚,对应一行的1、2、3、4列
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_5)==GPIO_PIN_RESET)return 38;//所以返回四个值
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6)==GPIO_PIN_RESET)return 39;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7)==GPIO_PIN_RESET)return 40;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8)==GPIO_PIN_RESET)return 41;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_9)==GPIO_PIN_RESET)return 42;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10)==GPIO_PIN_RESET)return 43;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_11)==GPIO_PIN_RESET)return 44;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12)==GPIO_PIN_RESET)return 45;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13)==GPIO_PIN_RESET)return 46;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_14)==GPIO_PIN_RESET)return 47;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_15)==GPIO_PIN_RESET)return 48;
	}
	//第五行//PA5
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==GPIO_PIN_RESET)//读取第1行
	{
		/*后4个端口输出低电平*/
		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5, GPIO_PIN_RESET);
		//后4个端口推挽输出
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
		//前4个端口上拉输入
		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		GPIO_InitStruct.Pull = GPIO_PULLUP;
		GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13
                          |GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_4|GPIO_PIN_5
                          |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
		HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_4)==GPIO_PIN_RESET)return 49;//此时列引脚,对应一行的1、2、3、4列
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_5)==GPIO_PIN_RESET)return 50;//所以返回四个值
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6)==GPIO_PIN_RESET)return 51;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7)==GPIO_PIN_RESET)return 52;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8)==GPIO_PIN_RESET)return 53;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_9)==GPIO_PIN_RESET)return 54;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10)==GPIO_PIN_RESET)return 55;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_11)==GPIO_PIN_RESET)return 56;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12)==GPIO_PIN_RESET)return 57;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13)==GPIO_PIN_RESET)return 58;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_14)==GPIO_PIN_RESET)return 59;
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_15)==GPIO_PIN_RESET)return 60;
	}

	return 0;
}
/* USER CODE END 4 */

4、发送串口和键盘数据的函数

		key_val = Key_Scan();
//这个只是一部分,总共60个,详情见附件代码,这个只是一个具体展示,其他的一样的
		if(key_val == 3)
		{
			printf("03");
			buffer[2] = 0x1D;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}		

5、键盘描述符的改动

__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE]  __ALIGN_END =
{
  0x05,0x01,   //USAGE_PAGE (Generic Desktop)                    
  0x09,0x06,   //USAGE (Keyboard)  
  0xA1,0x01,   //COLLECTION (Application)    
        //以下为输入报告描述符,用于中断输入端点
        //输入报告描述符2字节,第一个字节各位代表的是控制键,第二个字为0,后面跟6个数组,用于普通按键键码
  0x05,0x07,  //USAGE_PAGE (Keyboard)             
  0x19,0xE0,   //USAGE_MINIMUM (Keyboard LeftControl)          
  0x29,0xE7,   //USAGE_MAXIMUM (Keyboard Right GUI)         
  0x15,0x00,   //LOGICAL_MINIMUM (0)                   
  0x25,0x01,   //LOGICAL_MAXIMUM (1)       
  0x75,0x01,   //  REPORT_SIZE (1)            
  0x95,0x08,   //   REPORT_COUNT (8)         
  0x81,0x02,   // INPUT (Data,Var,Abs) 
  0x95,0x01,   //   REPORT_COUNT (1)     
  0x75,0x08,   //   REPORT_SIZE (8)          
  0x81,0x03,   //   INPUT (Cnst,Var,Abs)      
  0x95,0x06,   //   REPORT_COUNT (6)           
  0x75,0x08,   //   REPORT_SIZE (8)                 
  0x15,0x00,   //   LOGICAL_MINIMUM (0)     
  0x25,0x65,   //   LOGICAL_MAXIMUM (101)       
  0x05,0x07,   //  USAGE_PAGE (Keyboard)     
  0x19,0x00,   //  USAGE_MINIMUM (Reserved (no event indicated)) 
  0x29,0x65,   // USAGE_MAXIMUM (Keyboard Application)        
  0x81,0x00,   // INPUT (Data,Ary,Abs)         
//输出报告描述符,用于控制键盘灯
//以下为中断输出报告描述符,用于中断输出端点
//第一节字的低5位表示相应的指示灯,高3位为0
  0x95,0x05,   //  REPORT_COUNT (5)         
  0x75,0x01,   //  REPORT_SIZE (1)         
  0x05,0x08,   //  USAGE_PAGE (LEDs)       
  0x19,0x01,   //  USAGE_MINIMUM (Num Lock)   
  0x29,0x05,   //  USAGE_MAXIMUM (Kana)    
  0x91,0x02,   //  OUTPUT (Data,Var,Abs)   
  0x95,0x01,   //  REPORT_COUNT (1)        
  0x75,0x03,   //  REPORT_SIZE (3)         
  0x91,0x03,   //  OUTPUT (Cnst,Var,Abs)                    
  0xC0      //END_COLLECTION                                 
};

(2)说明

1、usbhid主要是看这个8位的数组--buffer,其中0、1是已经规定了数值的,要发送这些数据,必须得要从这里面发送,而2、3、4、5、6、7是对应六个键,也就是说,是完全可以使用它去做六键无冲的。

buffer[0] - 
bit0:left ctrl
bit1:left shift
bit2:left ali
bit3:left gui
bit4:right ctrl
bit5:right shift
bit6:right ali
bit7:right gui
buffer[1]-padding = always  0x00
buffer[2]-key1
...
bufer[7]-key6

2、gpio的配置无非就是五个行和12个列配置,这个比较简单,看cubemx和keil的函数就知道了。

(cubemx放一张图)

FkVVFCAxi8AHcAxirW3KqQLG-YRA

3、扫描函数主要还是因为矩阵键盘的原理,这里简单说明一下,矩阵键盘大概就是这么一个图,随便画的,以2*2为例,为什么叫矩阵,看看图就知道了,如果使用如图的方式去连接,那么以行为H0、H1;列为L0、L1;(H0,L0)为A,为了方便我使用abcd表示四个位置,如图,也就是按下第一个按键时,行列检测到,其实简单来说就是一个按键有两个信息,而主控根据这两个信息判断是哪个按键按下。于是引申4

(矩阵键盘图)FjoRng6suuFGYVVdhbzza2VWTuCl

4、一个开关,一般是一边是gnd,一边是单片机的IO,但是矩阵键盘这边连接两个IO,单片机要如何操作呢,其实就是一个轮询过程,比如以H为高电平,L为低电平,若A按下,此时H0监测低电平,其实B按下的话H0也是低电平,则行信息确认,反之,L为高电平,H低电平,A按下,则L0检测低电平,L0为列信息确认,所以判断成功

5、综上,具体扫描过程为,先初始配置IO,然后开始扫描,从行开始,扫描到行之后,再开始扫描列,从而得到对应的值。

6、鬼键问题,一般来说一个一个按键正常按下时是没问题的,但是当你出现按下多个时,可能会导致导线联通到别的IO口,此时单片机会检测到新的值,也就是鬼键,为了避免这个问题,加一个单向的二极管,限定方向,即可解决该方法。

7、串口发送不说了略过,usb描述的值详见这个链接(去找官方的最好),每个按键都有对应的一个值:USB协议中HID设备描述符以及键盘按键值对应编码表_usb键盘编码表-CSDN博客

8、改动部分,其实cubex初始配置是鼠标的,为了改成键盘的,只需要按下图改这几个就行了,具体为:

//hid.h部分
#define HID_EPIN_SIZE                 0x08U

#define HID_MOUSE_REPORT_DESC_SIZE    63U

//hid.c部分
  0x01,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
  • 04改成08
  • 74改成63
  • 函数有解释,照着改
  • 描述符改动(见前面)

八、功能展示及说明

  1. 全部按键演示见视频,这里单独演示第五音阶,也就是第四个部分,这部分分别按下SW1、2、6、7、8,对应的值为37、38、42、43、44,此时为黑键,可以看到串口接受到的信息FiuCF9DJN8vUDgoeDMONiSqLoRNV
  2. 同1,此时若打开freepiano软件,可以看到对应的五个黑键被按下FveTCNCB73uh24gF_cHBRVjdVxwz
  3. 若按下3、4、5、9、10、11、12,对应的是39,40,41,45,46,47,48;串口接收到对应的数值FvO2RP1SUQUPQz04XVZQWs_MXGrI
  4. 同3,此时打开软件,可以看到对应的7个白键被按下Fv-EJGxoQSwK8ej2ItHgcoC8jsQX
  5. 打开键盘测试链接,以第四阶为例,七个白键对应键盘的qwertyu,此时按下第四阶的白键,可以看到网页检测到对应的按键,左为轨迹,右为顺序FgmuxpBdRs9GFysUaQ05I9NRk4vH
  6. 连接到PC端口后可以在设备处看到stm32human的键盘指示,在设备管理器也可以看到。Fu8N4ccrd6RqHTRZXRPydah4QZ6N

九、整个设计过程遇到的难点和解决方法

1、PCB设计部分没有考虑到设计需求,因为设计需求对于二极管的方向是固定的,而二极管方向在初始设计的时候没有调整好,后续重新更改了一遍

2、实际焊接的时候,热插拔上锡上的太少,测试是否短路的时候明显没有短路,上锡给多点即可解决

3、二极管焊接方向反了,在结合代码测试的时候发现的,具体现象为按下键盘时,串口没有收到数据,说明是二极管的问题,用一根线按住焊盘两侧测试一下就知道了。

4、测试USB HID的时候,发现按下安静后,测试网页的按键会一直响应,说明是usb的发送函数在持续不断向pc发送数据,经过验证,需要加一个延时,在发送完数据之后,要给他清零,这样子即可避免。

5、cubex初始配置时忘记给io上拉了,这个检查矩阵键盘代码的时候可以查出来,因为若是别的按键没什么问题,检查代码也没什么问题,一般会看usart和gpio的文件,此时检查gpio.h即可发现问题。

 

十、对本大赛的心得体会(包括意见或建议)。

1、本次大赛我对我的成品不太满意,近期比较忙导致没办法去完善成品,我在10月底就已经完成所有的代码和基础功能的测试了,但是问题在于这个成品物理结构不稳定,而且辅助功能也没加,最难绷的还是尺寸没量好,甚至现在那个跳线连接还是很畸形的。

2、至于难点和解决方法,前面基本上都说了,就不在这里讲了,大部分还是硬件结合代码的调试,而且多看看别人的源码,有助于自己的代码开发。

3、本次设计过程主要点还是USB部分,这个我确实没怎么接触过,光是学习协议的一些基本原理和一些配置,就已经画了好几天时间了,不过最后确实发现,因为这个项目要求不是很多,所以基本上配置hid的时候也不是很麻烦。(USB的一个问题见代码部分)

4、本次感谢电子森林提供这么一个机会,让广大用户可以从其中锻炼自己独立完成产品的过程,不仅仅是硬件的设计和代码的学习,主要还是综合调试能力的提升,对个人的实践能力和动手能力非常有效的提升。

 

十一、附件
1、项目框图和原理图(放在电子森林项目-软硬件-电路图处)
ps:见附件2
2、原理图文件、PCB文件
ps:见kicad文件的附件1,同时已经上传两个kicad设计图
3、可编译下载的代码文件(放在电子森林项目-软硬件-附件处)
ps:第三个附件,记得解压
物料清单
附件下载
61keyboard.zip
kicad
图片.zip
框图
f013keyboard_5_12.zip
代码,记得解压
团队介绍
逸!误!
团队成员
ZHAO
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号