This post was last edited by Zhao_kar on 2023-10-31 23:14
[STM32L476RG] Section 8 - Matrix keyboard + usbhid configuration
Small talk: Although the application content has been completed, there are still many functions of this development board that have not been tested. I am making a keyboard recently. I bought a 4*4 to test the code first. The hardware is still being debugged. I have not used a matrix keyboard before. After all, many control things I have made are made with serial screens or Bluetooth + UI. I don’t think anyone has posted a post about matrix keyboards, so I will simply post a basic application here. Then the theory of matrix keyboard is relatively simple, so I will explain it roughly and use it as a note.
1. Basic Theory
Introduction of matrix keyboard
1. At the beginning of learning MCU, you will definitely learn the control of gpio+switch. But it is obvious that this method requires one GPIO for one switch, which is a waste of resources. MCU cannot have many IO ports, and the usual mechanical keyboard has 75, 108 or so, so there is a matrix keyboard.
2. A matrix keyboard is probably like this. I just drew it randomly. Take 2*2 as an example. Why it is called a matrix? You can see it by looking at the picture. If you connect it in the way shown in the picture, the rows are H0 and H1; the columns are L0 and L1; (H0, L0) is A. For convenience, I use abcd to represent the four positions, as shown in the picture. That is, when the first key is pressed, the row and column are detected. In fact, in simple terms, a key has two information, and the main control determines which key is pressed based on these two information. So the third point is derived (please forgive the ugly picture)
3. A switch usually has gnd on one side and the IO of the microcontroller on the other side. However, the matrix keyboard is connected to two IOs. How should the microcontroller operate it? In fact, it is a polling process. For example, H is a high level and L is a low level. If A is pressed, H0 monitors a low level. In fact, if B is pressed, H0 is also a low level, then the row information is confirmed. Conversely, L is a high level and H is a low level. If A is pressed, L0 detects a low level, and L0 confirms the column information, so the judgment is successful. This is one method, but in fact there are many methods, each with its own advantages and disadvantages, so I won’t go into details here.
Other possibilities + classic ghost key problems:
1. There is a design, one IO, multiple switch operations, which I have seen on other boards. I think it is very useful and suitable for occasions where ADC is not used or the demand is low. Specifically, one ADC is connected to multiple switches, and the resistance of each switch is different. Then when you press the button, because of the different resistances, the voltage of different switches is different. Then, through this, adc detection is implemented to know which switch is pressed. It is certain that a code must be written. Later, I will try it myself when I draw the PCB in the creative competition, or I will try it again in the subsequent evaluation. This is just an idea. I have not succeeded yet. As for what board it is, I will not advertise it. After all, one IO can control multiple switches, which is still a good choice.
2. Ghost keys: when multiple keys are pressed, other row and column information may be triggered. In other words, other keys may be judged as pressed even though they are not pressed. Therefore, diodes are generally added at this time to prevent signal reverse phenomenon. In fact, normal keyboards are generally designed in this way, but so many diodes are still a little troublesome, so the following design is made.
3. Without going into details, see Zhihuijun’s keyboard. He used 74 register circuits to design it. The idea is completely different and it avoids the problem of ghost keys. However, I do not recommend this design for beginners. You should first make the basic diode + matrix keyboard circuit. The method of the 74 chips involved is completely different. Although 100 diodes are troublesome, they are cheap. Although more 74 are not expensive, those who understand cost performance understand it. There is no need to explain how to choose from manufacturers on the market.
2. USBHID protocol
I won’t talk about the theory here, as it involves a lot of things. I will just talk about what will be used in this report.
1. First is the descriptor. You can check it on Baidu. I will focus on the code part.
2. You need to know the format of the data being sent. There are 8 bits in it, among which buffer0 is several key controls, such as ctrl and shift, etc., then 1 is fixed at 0x00, and then 2-7, that is, six keys can be used without conflict.
3. In fact, you only need to change the descriptor to the keyboard, and then use the function + configure the HID protocol.
4. For example, if you let buffer2 send 0x04, which is A, similarly, you only need to judge different values, control the sending port, make the buffer value different, and send different values.
3. Actual configuration of cubemx
1. 8 gpio, four input and four output
2. Input is set to pull-up
3. RCC, SYS
4. Connectivity Open USB device
5. Select HID in middleware, and do not change other parameters.
4. Keil code
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. Change the initial mouse to keyboard (about 160 lines, as shown below, the initial configuration is mouse, the comment explains what the keyboard looks like, change it)
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. Then change the h file. The original four bytes are changed to 8, as shown below, and 74 is changed to 63
#define HID_EPIN_SIZE 0x08U
#define HID_MOUSE_REPORT_DESC_SIZE 63U
4. Write the scanning function of the matrix keyboard
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. Serial port debugging function + keyboard sending function
Note: I was working on it, and after I finished writing the code, I suddenly thought of something very embarrassing. This board seems unable to connect to USB. How should I put it? The USB is connected to the MCU of stlink, but the main control we control is l476. Then I looked at the instruction manual. JP1 is used to control voltage and current, and CN2 is the download selection of stlink. CN3 is the direct serial port, and there is no pin that can be pulled out. Maybe I missed it. If there is a problem, I hope you can correct it.
So here is the code. I have tested it on other boards and it is feasible. However, there is no way to display it. I can only debug it by receiving the value of the matrix keyboard through the serial port. The code is as follows
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. Don’t forget the initial declaration and definition
/* 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
5. Actual Test
1. As mentioned before, the USB cannot be used, but the serial port return value of the matrix keyboard is still OK. I changed a board and pulled out the USB on it. I can test it. See the second video
video1
video2
2. Supplement
There is not much to summarize. I have seen many people make adc and dac, so I will not write about them. This makes a matrix keyboard and adds a hid. The difficult thing is that I found that this board is not very good. There are some problems with the hardware. Then I changed it to another board. In fact, the theory is the same. You can draw an adapter board on the hardware, and then there is a USB port on it, and then connect the PA11 and 12 ports to it, and then draw a circuit for the USB port. There are also things to talk about this USB circuit, but I will not expand on it due to limited space. I suggest you copy other people's USB circuits and you will understand. You will understand after you put a picture.