1032|1

44

帖子

2

TA的资源

一粒金砂(高级)

楼主
 

【STM32L476RG】第八节——矩阵键盘+usbhid的配置 [复制链接]

本帖最后由 Zhao_kar 于 2023-10-31 23:14 编辑

【STM32L476RG】第八节——矩阵键盘+usbhid的配置

闲聊:虽然申请的内容已经做完了,但是这个开发板还有很多功能没有测试,最近在做键盘,买了一个4*4的先测试代码了,硬件还在调试中,之前也确实没用到矩阵键盘,毕竟很多控制的东西我都是拿串口屏或者蓝牙+UI做的,我看好像也没人发矩阵键盘的帖子,这边就简单发一篇一个基础的应用。然后矩阵键盘这个东西其实理论相对比较简单,我就大概解释一下,顺带当成笔记用了。

一、基础理论

矩阵键盘的引入

1、在学单片机的最开始的时候,肯定会学习gpio+开关的控制学习,但是很明显,这种方法,一个开关就要用一个GPIO,相当浪费资源,单片机也不可能有很多的IO口,而光是平时的机械键盘的配列就有75,108什么的,所以有了矩阵键盘这个东西

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

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

其他可能性补充+经典鬼键问题:

1、有一个设计,一个IO,多个开关的操作,这是我在别的板子上面见到的,我觉得很有用,适合于不使用ADC或者低需求的场合,具体为,一个ADC连接多个开关,每个开关的电阻不一样,然后你按下按键的时候,因为不同的电阻,经过后,不同的开关电压是不一样的,然后通过这个去实现adc检测,从而知道哪个开关按下,肯定是要写个代码的,后续我看看在创意大赛里面自己画pcb的时候我自己试试看,或者后续测评我再尝试一下,这只是个思路,我还没有成功过,至于是什么板子我就不打广告了,毕竟一个IO就可以控制多个开关,还是不错的选择。

2、鬼键,这个就是,当按下多个按键时,可能会触发到其他的行列信息,也就是说,其他的按键没有按下却会被判定按下,所以这个时候一般要加二极管,防止信号反向的现象,其实正常的键盘一般都是这样设计的,但是那么多个二极管还是有一点点的麻烦,所以有了以下设计。

3、不做详解,见稚晖君的键盘,他用了74的寄存器电路去做的设计,思路完全不一样,而且避免了鬼键问题,但是这个设计我不建议初学者学习,先乖乖把基础的二极管+矩阵键盘的电路弄好再说,这里面涉及的74芯片的方法是完全不一样的,而且虽然100个二极管麻烦,但是便宜啊,多个74虽然也不算贵,但是性价比懂的都懂,市面厂商怎么选就不用说了。

二、usbhid协议

这个我就不说理论了,这个涉及的东西挺多的,我就讲这一篇报告会用的

1、首先是描述符,这个自己可以百度查,我就防在代码部分了

2、要知道发送的数据格式,8个比他,其中buffer0是几个关键控制,如ctrl和shift等等,然后1是固定0x00,然后2-7,也就是六个按键是可以做到六键无冲的。

3、也就是实际上只需要改描述符改成键盘的,然后使用函数+配置hid协议就行了

4、比如你让buffer2,发送0x04,也就是A,同理,只需要判断不同的值,控制发送端口,让buffer的值不一样,发送的值不同就行。

三、cubemx的实际配置

1、8个gpio,四个输入四个输出

2、输入设置为上拉

3、RCC、SYS略

4、connectivity打开USBdevice

5、middleware里面选择HID,然后其他参数不用改

四、keil的代码

1、usbd_hid.c

​
//这个是描述符的
_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、把初始的鼠标改成键盘(大概160多行位置,如下,初始配置是鼠标,注释里面说明了键盘是什么样的,更改一下)

  USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
  0x00,         /*bInterfaceNumber: Number of Interface*/
  0x00,         /*bAlternateSetting: Alternate setting*/
  0x01,         /*bNumEndpoints*/
  0x03,         /*bInterfaceClass: HID*/
  0x01,         /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
  0x01,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/

3、然后h文件里面要改一下,原先是四字节,改成8,如下,还有74改成63

#define HID_EPIN_SIZE                 0x08U
#define HID_MOUSE_REPORT_DESC_SIZE    63U

4、编写矩阵键盘的扫描函数

uint8_t Key_Scan(void)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	/*前4个端口输出低电平*/
	//输入是PA2,3,8,10
	//输出是PB3,4,5,10
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_8|GPIO_PIN_10, GPIO_PIN_RESET);
	//PA是L列,也就是竖着的
	//前4个端口推挽输出,列配置为推挽输出
	GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_8|GPIO_PIN_10;
	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);
	//PB的3,4,5,10是H行,也就是横着的
	//后4个端口上拉输入,行配置为上拉输入
	GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_10;
	GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
	HAL_Delay(10);

	//后四个端口作为行,也就是第一行,此时PB3,后续以此类推
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_3)==GPIO_PIN_RESET)//读取第1行
	{
		/*后4个端口输出低电平*/
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_10, GPIO_PIN_RESET);
		//后4个端口推挽输出
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
		//前4个端口上拉输入
		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		GPIO_InitStruct.Pull = GPIO_PULLUP;
		GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_8|GPIO_PIN_10;
		HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2)==GPIO_PIN_RESET)return 1;//此时列引脚,对应一行的1、2、3、4列
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3)==GPIO_PIN_RESET)return 2;//所以返回四个值
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_8)==GPIO_PIN_RESET)return 3;
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_10)==GPIO_PIN_RESET)return 4;
	}
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_4)==GPIO_PIN_RESET)//读取第2行
	{
		/*后4个端口输出低电平*/
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_10, GPIO_PIN_RESET);
		//后4个端口推挽输出
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
		//前4个端口上拉输入
		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		GPIO_InitStruct.Pull = GPIO_PULLUP;
		GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_8|GPIO_PIN_10;
		HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2)==GPIO_PIN_RESET)return 5;
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3)==GPIO_PIN_RESET)return 6;
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_8)==GPIO_PIN_RESET)return 7;
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_10)==GPIO_PIN_RESET)return 8;
	}	
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_5)==GPIO_PIN_RESET)//读取第3行
	{
		/*后4个端口输出低电平*/
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_10, GPIO_PIN_RESET);
		//后4个端口推挽输出
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
		//前4个端口上拉输入
		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		GPIO_InitStruct.Pull = GPIO_PULLUP;
		GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_8|GPIO_PIN_10;
		HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2)==GPIO_PIN_RESET)return 9;
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3)==GPIO_PIN_RESET)return 10;
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_8)==GPIO_PIN_RESET)return 11;
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_10)==GPIO_PIN_RESET)return 12;
	}
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10)==GPIO_PIN_RESET)//读取第4行
	{
		/*后4个端口输出低电平*/
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_10, GPIO_PIN_RESET);
		//后4个端口推挽输出
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
		//前4个端口上拉输入
		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		GPIO_InitStruct.Pull = GPIO_PULLUP;
		GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_8|GPIO_PIN_10;
		HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2)==GPIO_PIN_RESET)return 13;
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3)==GPIO_PIN_RESET)return 14;
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_8)==GPIO_PIN_RESET)return 15;
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_10)==GPIO_PIN_RESET)return 16;
	}
	return 0;
}

5、串口调试函数+键盘发送函数

备注:我本来做着做着,代码写完后,突然想到了一个很尴尬的东西,这个板子好像没办法连接USB,怎么说呢,就是usb是连接stlink的那个mcu的,但是我们控制的主控是l476的,然后我看了看说明手册,JP1是控制电压电流的,然后CN2两个是stlink的下载选择,CN3就是直接的串口,然后没有可以拉出来的引脚,可能我看漏了,如果有问题,希望大家可以纠错。

所以这里代码放在这,我在别的板子上面测试过了,是可行的,然后这个的展示没办法,只能通过串口接收矩阵键盘的值调试了,代码如下

key_val = Key_Scan();
		if(key_val == 1)
		{
			printf("01");
			buffer[2] = 0x04;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}
		if(key_val == 2)
		{
			printf("02");
			buffer[2] = 0x05;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}		
		if(key_val == 3)
		{
			printf("03");
			buffer[2] = 0x06;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}		
		if(key_val == 4)
		{
			printf("04");
			buffer[2] = 0x07;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}
		if(key_val == 5)
		{
			printf("05");
			buffer[2] = 0x08;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}
		if(key_val == 6)
		{
			printf("06");
			buffer[2] = 0x09;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}		
		if(key_val == 7)
		{
			printf("07");
			buffer[2] = 0x0A;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}		
		if(key_val == 8)
		{
			printf("08");
			buffer[2] = 0x0B;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}
		if(key_val == 9)
		{
			printf("09");
			buffer[2] = 0x0C;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}
		if(key_val == 10)
		{
			printf("10");
			buffer[2] = 0x0D;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}		
		if(key_val == 11)
		{
			printf("11");
			buffer[2] = 0x0E;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}		
		if(key_val == 12)
		{
			printf("12");
			buffer[2] = 0x0F;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}
		if(key_val == 13)
		{
			printf("13");
			buffer[2] = 0x10;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}
		if(key_val == 14)
		{
			printf("14");
			buffer[2] = 0x11;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}		
		if(key_val == 15)
		{
			printf("15");
			buffer[2] = 0x12;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}		
		if(key_val == 16)
		{
			printf("16");
			buffer[2] = 0x13;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay(15);
			buffer[2] = 0x00;
			USBD_HID_SendReport(&hUsbDeviceFS,buffer,8);
			HAL_Delay (500);
		}		

6、初始的声明和定义别忘了

/* USER CODE BEGIN PV */
extern USBD_HandleTypeDef hUsbDeviceFS;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
uint8_t Key_Scan(void);
/* USER CODE END PFP */


  /* USER CODE BEGIN Init */
	uint8_t key_val;
  /* USER CODE END Init */

  /* USER CODE BEGIN 2 */
	printf("begin test");
		uint8_t buffer[8] = {0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00};
  /* USER CODE END 2 */


//别忘记stdio,串口函数+勾选micro

五、实际测试

1、前面说了,usb搞不了,但是矩阵键盘的串口返回值还是可以的,我换一个板子,上面的usb拉出来了,可以测试,看第二个视频

  • 视频1

video1

 

  • 视频2

video2

 

2、补充

没什么总结的,adc、dac我看很多人做了,我就不写了,这个做一个矩阵键盘顺带加一个hid,难绷的是发现这个板子有点不太行,硬件有一点点的问题,然后换成别的板子了,其实理论是一样的,硬件上完全可以画一个转接板,然后上面有一个usb口,再把PA11、12这两个口接到上面,然后画一个usb口的电路,这个usb电路也有可以讲的东西,这里篇幅有限不展开,建议抄抄别人的usb电路就懂了,放一张图就知道了

 

此帖出自stm32/stm8论坛

最新回复

楼主很有想法呀,看来很用心的对这开发板进行了折腾。  详情 回复 发表于 2023-11-1 09:52
点赞 关注
 

回复
举报

6995

帖子

11

TA的资源

版主

沙发
 
楼主很有想法呀,看来很用心的对这开发板进行了折腾。
此帖出自stm32/stm8论坛
 
 

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

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

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

 
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
快速回复 返回顶部 返回列表