Qurak

Make Code Talk

0%

STM32单片机HAL库学习MacOS(3)—— 键盘控制和串口传输

——Dec.12.2020

目录

本次实验采用正点原子STM32F407探索者开发版和STM32cubeIDE软件开发

  • 键盘外设原理以及控制
  • 利用STM32cubeIDE配置串口通信

一、外接4x4键盘的使用

(1)外接4x4键盘的基本结构

img

4x4矩阵键盘的原理图如上图所示,当按键按下时,对应的电路才是通路,我们可以根据这个特性进行设计。矩阵键盘一共有八个IO口,在接口端我们可以看到最右边的接口标记为1,这个接口为第一列,一次为第二列,第三列,第四列,然后是第一行……第四行。

(2)键盘扫描原理

我们在上面讲到了,我们要利用按键按下后才是通路的原理进行键盘的识别。然而细心的同学会发现,外接键盘内部没有相对电位差,所以如果我们将把个接口全部设置为输入Input,单片机是无法识别到任何有用信息的。我们可以采用一个变通的方法,将其中的四个接口设置为输出Output(选择行或者列),然后将另外四个接口设置为输入Input。我们就可以通过四个输出口不同的组合的输出以及四位输入的情况判断哪一个按键按下。

这里我设置前四个为输出后四个为输入,以下是我的引脚设置:(PS:找了好久感觉这一组比较好用,如果有另外的引脚推荐也可以和我说哦~ 😃 )

矩阵键盘引脚设置

引脚Pin 接口模式Mode IO模式 标签Label
G0 GPIO_Output NPND key_row3
F14 GPIO_Output NPND key_row2
F12 GPIO_Output NPND key_row1
B2 GPIO_Output NPND key_row0
B0 GPIO_Input Pull-up key_col3
C4 GPIO_Input Pull-up key_col2
A6 GPIO_Input Pull-up key_col1
A4 GPIO_Input Pull-up key_col0

[NPND: No pull-up and no pull-down]

现在我们进入代码部分~(here we go~⛵️)

键盘识别的思路大致分为两个环节:

  • 对于键盘进行列扫描
  • 列扫描的同时进行行扫描

首先,为了方便我们先设置一些宏定义,以及定一个数组变量存储四位输入引脚的状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//*********************PIN_MANAGER_引脚管理************************************
//SET_LOW
#define KEY_ROW3_OUT_LOW HAL_GPIO_WritePin(GPIOG,GPIO_PIN_0,GPIO_PIN_RESET)
#define KEY_ROW2_OUT_LOW HAL_GPIO_WritePin(GPIOF,GPIO_PIN_14,GPIO_PIN_RESET)
#define KEY_ROW1_OUT_LOW HAL_GPIO_WritePin(GPIOF,GPIO_PIN_12,GPIO_PIN_RESET)
#define KEY_ROW0_OUT_LOW HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2,GPIO_PIN_RESET)
//SET_HIGH
#define KEY_ROW3_OUT_HIGH HAL_GPIO_WritePin(GPIOG,GPIO_PIN_0,GPIO_PIN_SET)
#define KEY_ROW2_OUT_HIGH HAL_GPIO_WritePin(GPIOF,GPIO_PIN_14,GPIO_PIN_SET)
#define KEY_ROW1_OUT_HIGH HAL_GPIO_WritePin(GPIOF,GPIO_PIN_12,GPIO_PIN_SET)
#define KEY_ROW0_OUT_HIGH HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2,GPIO_PIN_SET)
//****************************************************************************

// 定义数组变量存储
uint8_t Key_col[1]={0xff}; //保存按键列扫描情况的状态数组

然后我们完成整个扫描程序的一个子程序(列扫描):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 第一部分:列扫描
/***
*函数名:KEY_COL_SCAN
*功 能:按键列扫描
*返回值:1~4,对应1~4列按键位置
*/
char KEY_COL_SCAN(void)
{
//读出列扫描状态
Key_col[0] = HAL_GPIO_ReadPin(GPIOA,key_col0_Pin)<<3;
Key_col[0] = Key_col[0] | (HAL_GPIO_ReadPin(GPIOA,key_col1_Pin)<<2);
Key_col[0] = Key_col[0] | (HAL_GPIO_ReadPin(GPIOC,key_col2_Pin)<<1);
Key_col[0] = Key_col[0] | (HAL_GPIO_ReadPin(GPIOB,key_col3_Pin));

if(Key_col[0] != 0x0f) //列扫描有变化,判断该 列 有按键按下
{
HAL_Delay(10); //消抖
if(Key_col[0] != 0x0f)
{
//printf("Key_Row_DATA = 0x%x\r\n",Key_row[0]);
switch(Key_col[0])
{
case 0x07: //0111 判断为该列第1列的按键按下
return 1;
case 0x0b: //1011 判断为该列第2列的按键按下
return 2;
case 0x0d: //1101 判断为该列第3列的按键按下
return 3;
case 0x0e: //1110 判断为该列第4列的按键按下
return 4;
default :
return 0;
}
}
else return 0;
}
else return 0;
}

然后我们就可以编写完整的案件扫描函数了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/***
*函数名:KEY_SCAN
*功 能:4*4按键扫描
*返回值:0~16,对应16个按键
*/
char KEY_SCAN(void)
{
char Key_Num=0; //1-16对应的按键数
char key_col_num=0; //列扫描结果记录

KEY_ROW0_OUT_LOW; //第一行拉低,列扫描键盘
if( (key_col_num=KEY_COL_SCAN()) != 0 )
{
while(KEY_COL_SCAN() != 0); //消抖
Key_Num = 4*key_col_num-3;
}
KEY_ROW0_OUT_HIGH; //第一行拉高,恢复原样

KEY_ROW1_OUT_LOW; //第二行拉低,列扫描键盘
if( (key_col_num=KEY_COL_SCAN()) != 0 )
{
while(KEY_COL_SCAN() != 0);
Key_Num = 4*key_col_num-2;
}
KEY_ROW1_OUT_HIGH; //恢复

KEY_ROW2_OUT_LOW; //第三行拉低,列扫描键盘
if( (key_col_num=KEY_COL_SCAN()) != 0 )
{
while(KEY_COL_SCAN() != 0);
Key_Num = 4*key_col_num-1;
}
KEY_ROW2_OUT_HIGH; //恢复

KEY_ROW3_OUT_LOW; //第四行拉低,列扫描键盘
if( (key_col_num=KEY_COL_SCAN()) != 0 )
{
while(KEY_COL_SCAN() != 0);
Key_Num = 4*key_col_num;
}
KEY_ROW3_OUT_HIGH; //恢复

return Key_Num; //返回按键对应的编号(1-16)
}

然后我们调用该函数KEY_SCAN(),通过返回值判断按键。

二、串口通信配置

(1)打开串口通信以及配置

采用STM32cubeIDE打开串口的流程是比较简单的:

  • 首先我们打开Connectivity->USART1我们就可以看到相应的界面

  • 选择模式(Mode)Asynchronous(异步)

  • 点击下方Parameter Settings我们可以看到相应的参数设置:

    从上到下分别是波特率(Baud Rate),字节长度(Word Length),奇偶校验(Parity),停止位(Stop Bits)

    串口引脚配置1

  • 再点击NVIC Settings将串口的中断激活

    串口引脚配置2

    然后我们简单设置一下时钟树就可以进行串口通信了!⭐️(PS:若不知道如何设置的同学可以参考前篇教程,时钟配置基本一致)

(2)与电脑通信(虚拟串口)

根据(2)中的串口配置,我们已经开启了单片机的串口通信功能,但是我们还不可以让单片机i和点按哦进行通信,这是因为目前许多电脑端没有相应的串口接口,但是我们可以采用USB的虚拟串口进行通信。(PS:这个也需要单片机板子有相应的信号转换芯片),下面介绍具体的打开方法:

  • 点击Connectivity -> USB_OTG_FS模式Mode设置为Device_Only,并把下方中断使能打开
  • 接着我们点击Middleware -> USB_DEVICE -> Class For FS IP设置为虚拟串口通信(Communication Device Class (Virtual Port Com)

虚拟串口通信设置2

然后点击Generate Code就可以开始编写代码了!

(3)验证代码部分

验证思路

  • 正确配置好外接键盘以及串口通信
  • 通过外接按键输入,单片机读取按键信息并将信息通过串口传输到电脑上
  • 电脑打开串口助手接收到信息,对比按键验证是否配置正确。

[1] 函数介绍

在HAL函数库中,已经内置了有关串口传输和接受的相关函数。我们可以通过文件检索关键字UART来查询。之类例举几个本次实验会用到的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//Function_1   传输函数
/**
* @brief Sends an amount of data in blocking mode.
* @note When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),
* the sent data is handled as a set of u16. In this case, Size must indicate the number
* of u16 provided through pData.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @param pData Pointer to data buffer (u8 or u16 data elements).
* @param Size Amount of data elements (u8 or u16) to be sent
* @param Timeout Timeout duration
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
//对应的接口含义: 串口地址(这里需要用到取地址‘&’符号!), 传输数据信息, 传输的字节长度, 最长延迟时间
//示例
UART_HandleTypeDef huart1; //串口位置,系统会自动生成(可以去main.c去翻一翻)
HAL_UART_Transmit(&huart1,“data”,41000) //通过串口1发送“data”的字符串,长度为4,最长发送时间为1000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Function_2 接收函数
/**
* @brief Receives an amount of data in blocking mode.
* @note When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),
* the received data is handled as a set of u16. In this case, Size must indicate the number
* of u16 available through pData.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @param pData Pointer to data buffer (u8 or u16 data elements).
* @param Size Amount of data elements (u8 or u16) to be received.
* @param Timeout Timeout duration
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
//对应接口和发送一致,可以参考上面。 不同的是, 第二个接口为将输入接收后存储的地方,这里也应该用指针!
//示例
unit8_t data[100];
HAL_UART_Transmit(&huart1, data, 4, 1000);
//通过串口1接收数据,存储在data的数组中,字节长度为4,最长发送时间1000

另外还有HAL_UART_Transmit_DMA, HAL_UART_Transmit_IT等等,其中关于DMA与存储有关,而IT与中断有关。目前还没具体用到这些函数,在这里也不方便介绍。(挖坑

[2]验证代码

这里给了一个键盘验证的代码,由于函数十分简单,这里不做赘述。大家可以拷贝过去验证一下串口和外接键盘是否配置成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//验证代码
key=KEY_SCAN();

switch(key)
{
case 1:
{
HAL_UART_Transmit(&huart1,"1",1,0xff);
break;
}
case 2:
{
HAL_UART_Transmit(&huart1,"2",1,1000);
break;
}
case 3:
{
HAL_UART_Transmit(&huart1,"3",1,1000);
break;
}
case 4:
{
HAL_UART_Transmit(&huart1,"A",1,1000);
break;
}
case 5:
{
HAL_UART_Transmit(&huart1,"4",1,1000);
break;
}
case 6:
{
HAL_UART_Transmit(&huart1,"5",1,1000);
break;
}
case 7:
{
HAL_UART_Transmit(&huart1,"6",1,1000);
break;
}
case 8:
{
HAL_UART_Transmit(&huart1,"B",1,1000);
break;
}
case 9:
{
HAL_UART_Transmit(&huart1,"7",1,1000);
break;
}
case 10:
{
HAL_UART_Transmit(&huart1,"8",1,1000);
break;
}
case 11:
{
HAL_UART_Transmit(&huart1,"9",1,1000);
break;
}
case 12:
{
HAL_UART_Transmit(&huart1,"C",1,1000);
break;
}
case 13:
{
HAL_UART_Transmit(&huart1,"*",1,1000);
break;
}
case 14:
{
HAL_UART_Transmit(&huart1,"0",1,1000);
break;
}
case 15:
{
HAL_UART_Transmit(&huart1,"#",1,1000);
break;
}
case 16:
{
HAL_UART_Transmit(&huart1,"D",1,1000);
break;
}
}
}

点击编译后无错误就可以点击Run烧录到单片机上了,来看一下效果:

我么打开我们的串口助手(macOS的选手可以下一个“comtool”,windows的则很多选择)

效果图2

看到窗口反馈出信息,成功!


END