STM32矩阵键盘串口输入实战指南
在嵌入式系统开发中,用户输入与数据通信是实现人机交互的核心环节。本系统基于STM32F103微控制器,构建一个以矩阵键盘为输入源、通过串口通信发送数据的嵌入式应用平台。矩阵键盘因其引脚占用少、操作直观,广泛应用于工业控制、智能仪表等人机接口场景;而串口通信则以其稳定、简单、易集成的特性,成为嵌入式设备与上位机之间数据交换的常用方式。系统设计目标是实现按键信息的准确识别与高效传输,具备良好的实时性与
简介:本项目围绕STM32F103微控制器,讲解如何实现矩阵键盘的扫描与串口通信功能。通过配置GPIO端口、编写键盘扫描算法以及利用USART模块实现按键信息的串口发送,帮助开发者掌握嵌入式系统中人机交互的基本实现方法。实验包含完整的固件代码、电路原理图与实验指导,适用于智能家居、工业控制等人机界面开发场景,是嵌入式初学者提升实战能力的重要项目。 
1. STM32矩阵键盘串口输入系统概述
在嵌入式系统开发中,用户输入与数据通信是实现人机交互的核心环节。本系统基于STM32F103微控制器,构建一个以矩阵键盘为输入源、通过串口通信发送数据的嵌入式应用平台。矩阵键盘因其引脚占用少、操作直观,广泛应用于工业控制、智能仪表等人机接口场景;而串口通信则以其稳定、简单、易集成的特性,成为嵌入式设备与上位机之间数据交换的常用方式。
系统设计目标是实现按键信息的准确识别与高效传输,具备良好的实时性与稳定性。通过将矩阵键盘扫描与串口通信模块结合,为后续开发如远程控制、数据采集终端等应用提供基础支撑。本章为全文奠定技术背景与开发思路,引导读者进入具体实现环节。
2. STM32F103微控制器与矩阵键盘基础
STM32F103系列微控制器基于ARM Cortex-M3内核架构,凭借其丰富的外设资源、高效的处理能力和较低的功耗,广泛应用于嵌入式控制系统中。本章将深入解析STM32F103的硬件架构,特别是其核心内核与系统时钟配置机制,并结合矩阵键盘的基本原理,探讨其在嵌入式输入系统中的应用方式。通过分析矩阵键盘的行列扫描机制与硬件连接设计,为后续GPIO配置与键盘扫描算法的实现奠定理论基础。
2.1 STM32F103微控制器架构简介
2.1.1 Cortex-M3内核特性
Cortex-M3内核是ARM公司专为嵌入式系统设计的高性能、低功耗32位处理器内核。其采用Harvard架构,具有独立的指令总线和数据总线,能够实现指令预取与并行数据访问,显著提升指令执行效率。
Cortex-M3支持Thumb-2指令集,兼顾16位与32位指令,使得代码密度与执行效率达到良好平衡。此外,其内置的嵌套向量中断控制器(NVIC)支持最多240个可配置优先级中断源,为实时系统提供了强大的中断响应能力。
在STM32F103中,Cortex-M3内核通过AHB总线连接Flash、SRAM和各种外设模块,形成一个统一的系统架构。该架构支持DMA传输、定时器控制、ADC采样、串口通信等多种功能,非常适合构建复杂的嵌入式控制系统。
2.1.2 系统时钟与外设资源
STM32F103微控制器的系统时钟管理是其性能优化的核心部分。系统时钟由多个时钟源组成,包括内部高速时钟(HSI)、外部高速时钟(HSE)、内部低速时钟(LSI)和外部低速时钟(LSE)。这些时钟源通过锁相环(PLL)倍频后,可提供高达72MHz的系统时钟频率。
时钟树结构如下图所示,展示了系统时钟的生成路径:
graph TD
A[HSI 8MHz] --> B[PLL]
C[HSE 4-16MHz] --> B
B --> D[SYSCLK 72MHz]
D --> E[APB1 36MHz]
D --> F[APB2 72MHz]
D --> G[AHB 72MHz]
其中,AHB总线直接连接Cortex-M3内核,而APB1和APB2则分别连接低速与高速外设。例如,USART1连接在APB2上,可获得更高的通信速率,而USART2/3则运行在APB1的频率下。
STM32F103的外设资源包括:
| 外设类型 | 数量 | 特点说明 |
|---|---|---|
| GPIO端口 | 5组(A-E) | 每个端口最多16个引脚,支持多种模式配置 |
| USART | 3个 | 支持异步、同步、LIN、IrDA等通信方式 |
| SPI | 2个 | 支持主/从模式,DMA传输 |
| I2C | 2个 | 支持标准/快速模式 |
| 定时器 | 多个 | 包括基本定时器、通用定时器、高级定时器 |
| ADC/DAC | 12位ADC | 多通道采集,支持DMA |
| USB | 1个 | 支持全速USB设备模式 |
在本系统中,GPIO用于矩阵键盘的行扫描与列检测,USART用于串口数据发送,定时器可用于按键去抖或延时控制,ADC可用于检测模拟按键输入(如电位器控制),而USB接口则可用于PC端通信。
2.2 矩阵键盘的工作原理
2.2.1 行列扫描机制
矩阵键盘通过行列交叉的方式减少所需的I/O引脚数量。典型的4x4矩阵键盘仅需8个引脚即可识别16个按键。其工作原理如下:
- 行扫描法 :将所有行引脚设置为输出,列引脚设置为输入。依次将每一行拉低(或拉高),读取列引脚状态,若某列为低电平(或高电平),则对应行与列交叉处的按键被按下。
例如,假设第1行被拉低,第2列读取为低电平,则表示S12按键被按下。
以下是4x4矩阵键盘的扫描代码示例:
char matrix_keys[4][4] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
char scan_keypad() {
for(int row = 0; row < 4; row++) {
// 设置当前行为低电平,其余行为高电平
set_row_low(row);
// 延时去抖
delay_ms(10);
// 读取列值
int cols = read_cols();
if(cols != 0xF0) { // 假设列初始为高电平
// 检测哪一列被拉低
for(int col = 0; col < 4; col++) {
if(!(cols & (0x10 << col))) {
return matrix_keys[row][col];
}
}
}
}
return 0; // 无按键按下
}
逻辑分析:
set_row_low(row):将指定行设置为低电平,其余行为高电平。read_cols():读取列引脚的状态,返回4位数据(如0xF0表示全部列高电平)。cols & (0x10 << col):检测某一列是否为低电平。- 该函数返回被按下的键值,若无按键则返回0。
参数说明:
row:当前扫描的行号(0~3)。col:列号(0~3)。cols:读取到的列值,用于判断哪一列有低电平。
2.2.2 键盘布局与硬件连接方式
矩阵键盘的布局通常采用4x4或4x3结构,每个按键位于行与列交叉点。硬件连接时,行引脚连接到微控制器的GPIO输出端口,列引脚连接到GPIO输入端口。
例如,在STM32F103中,可使用GPIOA的PA0~PA3作为行输出,PA4~PA7作为列输入。连接方式如下:
| 行引脚 | 列引脚 |
|---|---|
| PA0 | PA4 |
| PA1 | PA5 |
| PA2 | PA6 |
| PA3 | PA7 |
为防止短路,行引脚应设置为推挽输出模式,列引脚设置为上拉输入模式。这样,在无按键按下时,列引脚为高电平;当某行被拉低,对应列检测到低电平时即可判断按键按下。
2.3 硬件电路设计要点
2.3.1 按键的电气特性
机械按键在按下和释放时会产生抖动(bounce),通常持续时间为5~20ms。抖动会导致误判,因此需要在硬件或软件层面进行去抖处理。
按键的电气模型可以简化为一个开关。当按键未按下时,列引脚处于高电平;按下时,列引脚被拉低。由于机械触点的弹性作用,电平会在短时间内反复跳变。
2.3.2 上拉/下拉电阻配置
在STM32F103中,GPIO引脚支持内部上拉/下拉电阻配置。在本系统中,列引脚应配置为上拉输入模式,以确保在无按键按下时列线保持高电平。
配置代码如下:
GPIO_InitTypeDef GPIO_InitStruct;
// 配置列为输入,上拉
GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
逻辑分析:
GPIO_MODE_INPUT:设置为输入模式。GPIO_PULLUP:启用内部上拉电阻,使列线默认为高电平。HAL_GPIO_Init:初始化GPIO引脚配置。
参数说明:
Pin:指定列引脚(PA4~PA7)。Mode:引脚工作模式。Pull:上下拉配置。
2.3.3 与STM32F103的引脚连接设计
STM32F103的GPIO引脚具有灵活的配置能力,支持多种模式,包括:
- 输入模式(浮空、上拉、下拉)
- 输出模式(推挽、开漏)
- 复用功能(AF)
- 模拟输入
在本系统中,行引脚应配置为推挽输出模式,以确保输出电平稳定:
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
逻辑分析:
GPIO_MODE_OUTPUT_PP:推挽输出模式,输出高电平时为VDD,低电平时为GND。GPIO_SPEED_FREQ_HIGH:设置输出速度为高频,以适应快速扫描需求。HAL_GPIO_Init:初始化行引脚配置。
参数说明:
Pin:指定行引脚(PA0~PA3)。Mode:引脚模式。Speed:输出速度等级。
通过合理配置GPIO引脚,结合行列扫描机制,STM32F103可以高效地实现矩阵键盘的输入检测,并为后续的串口通信提供准确的按键数据。
3. STM32 GPIO端口配置与键盘扫描实现
在嵌入式系统中,GPIO(通用输入/输出)端口是实现外设控制与交互的基础模块。STM32F103微控制器提供了丰富的GPIO资源,支持多种配置模式,可以灵活适应不同应用场景。本章将围绕STM32的GPIO端口配置展开,重点讲解其在矩阵键盘扫描中的应用。矩阵键盘是一种常见的输入设备,通过行扫描与列读取的方式识别按键状态,具有结构紧凑、节省IO资源等优点。
本章将从GPIO的输入/输出模式配置开始,逐步深入讲解键盘扫描算法的设计与实现,包括轮询方式与中断优化策略,并针对多键冲突、按键抖动等常见问题提出解决方案。最终目标是构建一个稳定、高效的键盘扫描机制,为后续的串口数据发送提供可靠的数据输入基础。
3.1 GPIO端口模式配置
STM32F103的GPIO端口支持多种工作模式,包括输入、输出、复用功能、模拟输入等。在矩阵键盘应用中,通常将行线配置为输出,列线配置为输入。这种配置方式可以实现行列扫描,从而识别按键的按下状态。
3.1.1 输入/输出模式设置
STM32的GPIO支持以下四种基本输入/输出模式:
| 模式名称 | 功能描述 |
|---|---|
| 输入浮空 | 不接上拉或下拉电阻,适用于外部驱动 |
| 输入上拉/下拉 | 内部有上拉或下拉电阻,常用于按键检测 |
| 推挽输出 | 驱动能力强,输出高/低电平稳定 |
| 开漏输出 | 输出高电平时为高阻态,需外部上拉电阻 |
在矩阵键盘中,行线一般设置为 推挽输出 ,用于主动驱动高电平;列线则设置为 输入上拉 模式,用于检测电平变化。
示例代码:GPIO初始化配置
void GPIO_Init(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
// 行线配置为推挽输出(PA0-PA3)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 列线配置为输入上拉(PA4-PA7)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 输入浮空,外部需上拉
GPIO_Init(GPIOA, &GPIO_InitStruct);
}
代码分析:
- RCC_APB2PeriphClockCmd :使能GPIOA的时钟,STM32外设使用前必须开启对应时钟。
- GPIO_Mode_Out_PP :推挽输出模式,适用于输出高电平,具有较强驱动能力。
- GPIO_Mode_IN_FLOATING :输入浮空模式,适合外部已有上拉电路的按键检测。
- GPIO_Speed_50MHz :设置输出速度为50MHz,适用于中高速应用。
3.1.2 推挽/开漏输出特性分析
在GPIO输出模式中,推挽与开漏是两种常见类型,其电气特性差异对按键扫描具有重要影响。
| 特性 | 推挽输出 | 开漏输出 |
|---|---|---|
| 输出高电平 | 直接连接VCC | 高电平为高阻态,需外部上拉 |
| 输出低电平 | 直接接地 | 直接接地 |
| 负载能力 | 强 | 弱,需外部上拉电阻 |
| 抗干扰能力 | 较强 | 较弱 |
| 典型应用场景 | 驱动LED、数码管、行线控制 | 总线通信(如I2C) |
在矩阵键盘中,行线需要主动输出高电平来驱动列线读取,因此选择 推挽输出 更为合适。开漏输出虽然节省功耗,但无法直接输出高电平,不适合用于行扫描。
3.2 键盘扫描算法设计
矩阵键盘的扫描是通过逐行输出高电平,再读取列线电平状态来判断按键是否按下。常见的扫描方式包括 轮询方式 和 中断方式 。
3.2.1 轮询方式实现原理
轮询方式是最基础的键盘扫描方法,通过主循环不断扫描各行,并读取列值,判断是否有按键按下。其优点是实现简单,缺点是效率较低,占用CPU资源。
示例代码:轮询方式扫描键盘
char Key_Scan(void) {
char key = 0;
uint8_t row, col;
uint8_t row_pins[4] = {GPIO_Pin_0, GPIO_Pin_1, GPIO_Pin_2, GPIO_Pin_3};
uint8_t col_pins[4] = {GPIO_Pin_4, GPIO_Pin_5, GPIO_Pin_6, GPIO_Pin_7};
for (row = 0; row < 4; row++) {
// 拉低所有行
GPIO_ResetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3);
// 拉高当前行
GPIO_SetBits(GPIOA, row_pins[row]);
// 延时去抖
Delay_ms(10);
for (col = 0; col < 4; col++) {
if (GPIO_ReadInputDataBit(GPIOA, col_pins[col]) == 0) {
// 按键按下
key = row * 4 + col + 1;
while(GPIO_ReadInputDataBit(GPIOA, col_pins[col]) == 0); // 等待释放
return key;
}
}
}
return 0; // 无按键按下
}
代码分析:
- GPIO_ResetBits :将所有行线拉低。
- GPIO_SetBits :依次拉高每一行。
- Delay_ms(10) :软件延时去抖,防止按键抖动误判。
- GPIO_ReadInputDataBit :读取列线状态,若为低电平则表示按键按下。
- while(GPIO_ReadInputDataBit(…) == 0) :等待按键释放,防止重复触发。
3.2.2 中断方式优化响应效率
为了提高响应效率,可以将列线配置为 外部中断输入 ,当有按键按下时触发中断,避免主循环不断轮询,从而降低CPU负载。
示例代码:列线中断配置
void EXTI_Init(void) {
EXTI_InitTypeDef EXTI_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
// 配置PA4-PA7为中断源
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource4);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource5);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource6);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource7);
EXTI_InitStruct.EXTI_Line = EXTI_Line4 | EXTI_Line5 | EXTI_Line6 | EXTI_Line7;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
// 配置NVIC中断优先级
NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x00;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
中断服务函数示例:
void EXTI4_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line4 | EXTI_Line5 | EXTI_Line6 | EXTI_Line7) != RESET) {
// 处理按键中断
char key = Key_Scan(); // 再次扫描确定按键
// 发送按键信息
USART_SendData(USART1, key + '0');
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
EXTI_ClearITPendingBit(EXTI_Line4 | EXTI_Line5 | EXTI_Line6 | EXTI_Line7);
}
}
逻辑分析:
- EXTI_Trigger_Falling :下降沿触发,适用于按键按下(列线被拉低)。
- NVIC_IRQChannel = EXTI4_IRQn :PA4-PA7共用EXTI4中断线。
- EXTI_GetITStatus :判断是否为指定中断触发。
- EXTI_ClearITPendingBit :清除中断标志位,防止重复触发。
3.2.3 多键按下与冲突处理策略
在实际使用中,用户可能同时按下多个按键,这会导致行扫描误判。为解决该问题,可采用以下策略:
- 单键优先 :仅识别最先被扫描到的按键。
- 多键屏蔽 :发现多个按键按下时,返回错误码或忽略后续按键。
- 按键矩阵映射 :建立按键矩阵映射表,根据行列位置判断是否冲突。
示例代码:多键检测逻辑
char Key_Scan_Multi(void) {
char keys[16] = {0};
int count = 0;
uint8_t row, col;
for (row = 0; row < 4; row++) {
GPIO_ResetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3);
GPIO_SetBits(GPIOA, row_pins[row]);
Delay_ms(10);
for (col = 0; col < 4; col++) {
if (GPIO_ReadInputDataBit(GPIOA, col_pins[col]) == 0) {
keys[count++] = row * 4 + col + 1;
if (count > 1) {
return -1; // 多键按下
}
}
}
}
return count ? keys[0] : 0;
}
逻辑分析:
- keys[count++] :记录扫描到的按键。
- if (count > 1) :检测到多个按键时返回错误码-1。
- return count ? keys[0] : 0 :返回第一个按键或0(无按键)。
3.3 按键去抖动技术
机械按键在按下和释放时会产生抖动,表现为电压信号的不稳定,可能被误判为多次按下。为提高系统稳定性,需进行去抖处理。
3.3.1 硬件去抖与软件去抖对比
| 方法 | 原理描述 | 优点 | 缺点 |
|---|---|---|---|
| 硬件去抖 | 使用RC电路或施密特触发器滤波 | 响应快,稳定性高 | 成本高,占用PCB空间 |
| 软件去抖 | 延时或状态机检测 | 无需额外元件,灵活 | 占用CPU时间,响应稍慢 |
在嵌入式系统中,通常优先采用软件去抖方式,以节省成本与空间。
3.3.2 延时去抖算法实现
最简单的软件去抖方法是在检测到按键按下后,延时10ms再次检测,若仍为按下状态则确认按键有效。
示例代码:延时去抖
int Debounce_Key(uint16_t pin) {
if (GPIO_ReadInputDataBit(GPIOA, pin) == 0) {
Delay_ms(10); // 延时10ms
if (GPIO_ReadInputDataBit(GPIOA, pin) == 0) {
return 1; // 确认为按下
}
}
return 0; // 未按下或抖动
}
逻辑分析:
- GPIO_ReadInputDataBit :首次检测低电平。
- Delay_ms(10) :等待抖动结束。
- 再次检测 :若仍为低电平,则确认按键按下。
3.3.3 状态机去抖优化方案
状态机去抖是一种更高效的方法,通过多个状态(未按下、按下检测、确认按下、已按下)实现更精准的去抖。
状态机流程图(mermaid)
stateDiagram
[*] --> Idle
Idle --> Detect : 检测到低电平
Detect --> Confirm : 延时10ms仍为低
Confirm --> Pressed : 确认按键按下
Pressed --> Release : 检测到高电平
Release --> Idle : 延时10ms仍为高
示例代码:状态机去抖
typedef enum {
KEY_IDLE,
KEY_DETECT,
KEY_CONFIRM,
KEY_PRESSED,
KEY_RELEASE
} KeyState;
KeyState key_state = KEY_IDLE;
char Key_StateMachine(uint16_t pin) {
switch (key_state) {
case KEY_IDLE:
if (GPIO_ReadInputDataBit(GPIOA, pin) == 0) {
key_state = KEY_DETECT;
}
break;
case KEY_DETECT:
Delay_ms(10);
if (GPIO_ReadInputDataBit(GPIOA, pin) == 0) {
key_state = KEY_CONFIRM;
} else {
key_state = KEY_IDLE;
}
break;
case KEY_CONFIRM:
return 1; // 按键按下
case KEY_PRESSED:
if (GPIO_ReadInputDataBit(GPIOA, pin) == 1) {
key_state = KEY_RELEASE;
}
break;
case KEY_RELEASE:
Delay_ms(10);
if (GPIO_ReadInputDataBit(GPIOA, pin) == 1) {
key_state = KEY_IDLE;
} else {
key_state = KEY_PRESSED;
}
break;
}
return 0;
}
逻辑分析:
- KEY_IDLE :初始状态,等待按键按下。
- KEY_DETECT :检测到低电平后进入检测状态。
- KEY_CONFIRM :延时确认后进入按下状态。
- KEY_PRESSED :持续检测按键是否释放。
- KEY_RELEASE :检测到释放并确认后返回空闲状态。
本章通过详细讲解STM32的GPIO配置、键盘扫描算法设计与去抖策略,构建了一个稳定可靠的矩阵键盘输入系统。下一章将围绕串口通信展开,介绍如何将按键信息通过串口发送到PC端或其它设备。
4. 串口通信模块配置与数据传输实现
在嵌入式系统中,串口通信是实现设备间数据交换的重要手段之一。STM32F103系列微控制器集成了多个USART模块,支持异步串行通信协议,广泛应用于人机交互、数据采集与远程控制等场景。本章将围绕STM32F103的串口通信模块配置与数据传输机制展开详细讲解,重点分析USART的基本结构、寄存器配置流程、通信参数设置方法、按键信息的编码与发送实现,以及如何通过串口助手工具验证通信功能。
4.1 USART模块基础与配置流程
4.1.1 串口通信的基本结构
STM32F103中的USART模块是一种全双工异步串行通信接口,支持标准的RS232电平逻辑。其基本通信结构包括发送端(TX)、接收端(RX)、波特率发生器、数据寄存器、状态寄存器等。USART通信通过帧格式传输数据,每帧包括起始位、数据位、校验位和停止位。
通信流程如下:
graph TD
A[数据写入发送寄存器] --> B[波特率发生器生成时钟]
B --> C[逐位发送到TX引脚]
D[RX引脚接收数据] --> E[数据存储至接收寄存器]
4.1.2 寄存器配置步骤
配置STM32的USART模块通常涉及以下寄存器:
| 寄存器名称 | 功能说明 |
|---|---|
| USART_CR1 | 控制寄存器1,使能发送/接收、设置数据位数等 |
| USART_CR2 | 控制寄存器2,设置停止位 |
| USART_CR3 | 控制寄存器3,设置硬件流控制 |
| USART_BRR | 波特率寄存器 |
| USART_DR | 数据寄存器,用于读写数据 |
配置流程如下:
-
使能GPIO与USART时钟 :
c RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); -
配置GPIO为复用推挽输出(TX)与浮空输入(RX) :
```c
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; // TX
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; // RX
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
```
-
初始化USART配置 :
c USART_InitTypeDef USART_InitStruct; USART_InitStruct.USART_BaudRate = 9600; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_Parity = USART_Parity_No; USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init(USART1, &USART_InitStruct); -
使能USART模块 :
c USART_Cmd(USART1, ENABLE);
4.2 串口通信参数设置
4.2.1 波特率计算与误差分析
STM32的波特率由系统时钟和分频器决定,其计算公式为:
波特率 = fck / (16 * USARTDIV)
其中, fck 为系统时钟频率, USARTDIV 为BRR寄存器的值。例如,系统时钟为72MHz,设置波特率为9600,则:
USARTDIV = 72000000 / (16 * 9600) ≈ 468.75
BRR寄存器高4位表示整数部分,低4位表示小数部分乘以16。因此,写入BRR为 0x1D4C 。
误差分析 :实际波特率可能因四舍五入产生微小误差,建议使用波特率误差小于3%的设置。
4.2.2 数据位、停止位与校验位设置
| 参数类型 | 可选值 | 说明 |
|---|---|---|
| 数据位 | 8/9位 | 默认使用8位 |
| 停止位 | 1/0.5/1.5/2位 | 常用为1位 |
| 校验位 | 无校验、奇校验、偶校验 | 常用于数据完整性校验 |
例如,配置为无校验、1位停止位、8位数据位:
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
4.3 按键信息编码与发送实现
4.3.1 ASCII码与自定义编码方式
在矩阵键盘输入系统中,每个按键对应一个字符或控制码。通常采用ASCII码进行编码,例如数字键“1”对应ASCII码 0x31 ,字母“A”对应 0x41 。对于特殊功能键,可自定义编码规则,如 0x01 表示“Enter”, 0x08 表示“Backspace”。
4.3.2 单字节与多字节发送策略
STM32的USART模块支持单字节发送与多字节DMA发送。在键盘输入系统中,由于数据量较小,通常采用单字节发送方式,使用 USART_SendData() 函数发送数据。
示例代码如下:
void USART_SendChar(char ch) {
USART_SendData(USART1, ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待发送完成
}
4.3.3 发送缓冲与中断发送机制
为提高效率,避免主循环阻塞,可使用发送缓冲区与中断机制。例如,定义一个发送队列,当缓冲区非空时开启发送中断,逐字节发送。
#define TX_BUFFER_SIZE 128
char txBuffer[TX_BUFFER_SIZE];
uint8_t txHead = 0, txTail = 0;
void USART_EnableTXEInterrupt() {
USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
}
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_TXE) != RESET) {
if (txHead != txTail) {
USART_SendData(USART1, txBuffer[txTail++]);
txTail %= TX_BUFFER_SIZE;
} else {
USART_ITConfig(USART1, USART_IT_TXE, DISABLE); // 缓冲区为空,关闭中断
}
}
}
4.4 串口调试与数据接收验证
4.4.1 使用串口助手工具验证通信
为了验证串口通信是否正常,可以使用如XCOM、SSCOM、PuTTY等串口调试工具。连接开发板的USART1(TX、RX)到USB转TTL模块,设置相同的波特率(如9600),打开串口助手,按下矩阵键盘按键后应能接收到对应的字符。
示例发送流程如下:
- 按下“1”键,发送ASCII码
0x31 - 串口助手显示字符“1”
- 若发送失败,检查波特率、引脚连接、中断配置等
4.4.2 数据完整性与同步机制分析
为确保数据完整性和同步性,可采用以下机制:
- 校验位 :启用奇偶校验,检测传输错误。
- 帧头帧尾标识 :如使用
0x02作为帧头,0x03作为帧尾,确保数据包完整。 - 接收缓冲与状态机 :接收端使用缓冲区和状态机判断帧起始、接收数据、校验并处理。
示例接收函数:
#define RX_BUFFER_SIZE 128
char rxBuffer[RX_BUFFER_SIZE];
uint8_t rxIndex = 0;
uint8_t rxState = 0; // 0:等待帧头, 1:接收数据
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
char ch = USART_ReceiveData(USART1);
if (rxState == 0) {
if (ch == 0x02) {
rxState = 1;
rxIndex = 0;
}
} else {
if (ch == 0x03) {
rxState = 0;
ProcessReceivedData(rxBuffer, rxIndex); // 处理数据
} else {
rxBuffer[rxIndex++] = ch;
if (rxIndex >= RX_BUFFER_SIZE) rxIndex = 0;
}
}
}
}
本章从USART模块的基本结构讲起,详细介绍了STM32F103中串口通信的配置流程、通信参数设置方法、按键信息的编码与发送实现策略,以及如何通过串口助手工具进行调试与数据验证。通过本章的学习,读者应能掌握STM32串口通信模块的核心配置方法,并能将其应用于实际的嵌入式人机交互系统中。
5. 基于STM32的矩阵键盘串口输入系统综合实现
5.1 系统整体架构设计
5.1.1 硬件模块整合
本系统由三个主要硬件模块构成:
- STM32F103微控制器 :作为主控单元,负责处理键盘扫描、串口通信及数据发送。
- 4x4矩阵键盘 :作为输入设备,通过行列扫描方式识别按键输入。
- 串口通信接口(USART1) :用于将按键信息发送至PC端或其他设备。
硬件连接方式如下:
| 模块 | 引脚连接 | 功能说明 |
|---|---|---|
| STM32F103 | PA0~PA3 | 行输出(Row) |
| STM32F103 | PA4~PA7 | 列输入(Col) |
| STM32F103 | USART1 TX (PA9) | 串口发送端 |
| PC端 | USB转TTL模块RXD | 接收串口数据 |
该设计通过STM32的GPIO口模拟行列扫描方式实现键盘检测,并通过内置的USART模块实现串口数据发送,结构清晰,便于维护与扩展。
5.1.2 软件流程图与模块划分
系统软件流程如下图所示:
graph TD
A[系统初始化] --> B[GPIO初始化]
B --> C[USART初始化]
C --> D[进入主循环]
D --> E[扫描键盘]
E --> F{是否有按键按下?}
F -->|是| G[获取按键值]
G --> H[将按键值转换为ASCII码]
H --> I[通过串口发送数据]
F -->|否| D
整个系统软件分为以下几个模块:
- 初始化模块 :负责配置GPIO、USART等外设。
- 键盘扫描模块 :实现行列扫描,识别按键。
- 数据处理模块 :将按键值转换为ASCII码或自定义编码。
- 串口通信模块 :负责数据的发送与缓冲管理。
5.2 固件代码分析与实现
5.2.1 主程序结构与初始化流程
主程序结构如下:
int main(void)
{
// 初始化系统时钟
SystemInit();
// 初始化GPIO
GPIO_Init();
// 初始化串口
USART_Init();
while (1)
{
uint8_t key = Key_Scan(); // 扫描键盘
if (key != 0xFF)
{
USART_SendChar(key); // 发送按键信息
}
}
}
代码说明:
SystemInit():STM32官方库函数,用于初始化系统时钟。GPIO_Init():配置行输出、列输入的GPIO模式。USART_Init():配置串口波特率、数据位、停止位等参数。Key_Scan():键盘扫描函数,返回按键值(ASCII码)。USART_SendChar():串口发送函数,发送单个字符。
5.2.2 扫描键盘与串口发送主循环
键盘扫描函数采用轮询方式实现,每次扫描一行,读取列状态:
uint8_t Key_Scan(void)
{
uint8_t row, col;
static const uint8_t keymap[4][4] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
for(row = 0; row < 4; row++)
{
// 设置当前行为低电平
ROW_PORT->ODR &= ~(0x0F); // 清除所有行
ROW_PORT->ODR |= (0x01 << row); // 当前行置高(假设为高有效)
Delay_ms(10); // 去抖延时
col = (COL_PORT->IDR) & 0xF0; // 读取列状态
if(col != 0xF0)
{
// 找到被按下的列
for(uint8_t i = 0; i < 4; i++)
{
if((col & (0x10 << i)) == 0)
{
return keymap[row][i];
}
}
}
}
return 0xFF; // 无按键按下
}
参数说明:
ROW_PORT:行控制端口,如GPIOA。COL_PORT:列读取端口,如GPIOA。keymap:4x4键盘对应字符映射表。Delay_ms(10):用于软件去抖。
5.2.3 关键函数与代码注释说明
USART_SendChar():串口发送单个字符
void USART_SendChar(uint8_t ch)
{
USART1->DR = ch; // 写入发送寄存器
while(!(USART1->SR & USART_SR_TC)); // 等待发送完成
}
- 代码逻辑:
- 将字符写入USART1的数据寄存器。
- 等待发送完成标志位
TC置位,确保数据已发送完毕。
5.3 系统调试与优化技巧
5.3.1 常见问题排查方法
常见问题及解决方法如下:
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 无串口输出 | 波特率配置错误 | 检查USART初始化参数 |
| 键盘识别错误 | 行列顺序接反 | 检查硬件连接 |
| 多键识别冲突 | 未处理多键同时按下 | 增加多键检测逻辑 |
| 串口数据乱码 | 编码方式不一致 | 确认发送与接收端编码一致 |
5.3.2 性能调优建议
-
键盘扫描优化 :
- 使用中断方式替代轮询方式,提高响应效率。
- 使用状态机实现按键去抖动,提高稳定性。 -
串口通信优化 :
- 启用DMA进行数据发送,减少CPU负担。
- 使用环形缓冲区管理发送数据,提升吞吐量。 -
功耗优化 :
- 在无按键状态下,进入低功耗模式。
- 使用外部中断唤醒系统。
5.3.3 实际应用中的扩展方向
- 支持更多按键 :通过增加行列数扩展为8x8矩阵键盘。
- 支持多串口通信 :利用STM32多个USART模块实现多设备通信。
- 加入LCD显示 :配合液晶屏实现本地显示与输入反馈。
- 集成蓝牙/WiFi模块 :实现无线串口通信,提升应用场景灵活性。
(未完待续)
简介:本项目围绕STM32F103微控制器,讲解如何实现矩阵键盘的扫描与串口通信功能。通过配置GPIO端口、编写键盘扫描算法以及利用USART模块实现按键信息的串口发送,帮助开发者掌握嵌入式系统中人机交互的基本实现方法。实验包含完整的固件代码、电路原理图与实验指导,适用于智能家居、工业控制等人机界面开发场景,是嵌入式初学者提升实战能力的重要项目。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)