本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目围绕STM32F103微控制器,讲解如何实现矩阵键盘的扫描与串口通信功能。通过配置GPIO端口、编写键盘扫描算法以及利用USART模块实现按键信息的串口发送,帮助开发者掌握嵌入式系统中人机交互的基本实现方法。实验包含完整的固件代码、电路原理图与实验指导,适用于智能家居、工业控制等人机界面开发场景,是嵌入式初学者提升实战能力的重要项目。
STM32矩阵键盘串口输入

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 多键按下与冲突处理策略

在实际使用中,用户可能同时按下多个按键,这会导致行扫描误判。为解决该问题,可采用以下策略:

  1. 单键优先 :仅识别最先被扫描到的按键。
  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 数据寄存器,用于读写数据

配置流程如下:

  1. 使能GPIO与USART时钟
    c RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);

  2. 配置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);
```

  1. 初始化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);

  2. 使能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. 按下“1”键,发送ASCII码 0x31
  2. 串口助手显示字符“1”
  3. 若发送失败,检查波特率、引脚连接、中断配置等

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 硬件模块整合

本系统由三个主要硬件模块构成:

  1. STM32F103微控制器 :作为主控单元,负责处理键盘扫描、串口通信及数据发送。
  2. 4x4矩阵键盘 :作为输入设备,通过行列扫描方式识别按键输入。
  3. 串口通信接口(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 性能调优建议

  1. 键盘扫描优化
    - 使用中断方式替代轮询方式,提高响应效率。
    - 使用状态机实现按键去抖动,提高稳定性。

  2. 串口通信优化
    - 启用DMA进行数据发送,减少CPU负担。
    - 使用环形缓冲区管理发送数据,提升吞吐量。

  3. 功耗优化
    - 在无按键状态下,进入低功耗模式。
    - 使用外部中断唤醒系统。

5.3.3 实际应用中的扩展方向

  • 支持更多按键 :通过增加行列数扩展为8x8矩阵键盘。
  • 支持多串口通信 :利用STM32多个USART模块实现多设备通信。
  • 加入LCD显示 :配合液晶屏实现本地显示与输入反馈。
  • 集成蓝牙/WiFi模块 :实现无线串口通信,提升应用场景灵活性。

(未完待续)

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目围绕STM32F103微控制器,讲解如何实现矩阵键盘的扫描与串口通信功能。通过配置GPIO端口、编写键盘扫描算法以及利用USART模块实现按键信息的串口发送,帮助开发者掌握嵌入式系统中人机交互的基本实现方法。实验包含完整的固件代码、电路原理图与实验指导,适用于智能家居、工业控制等人机界面开发场景,是嵌入式初学者提升实战能力的重要项目。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐