STM32基础回顾:GPIO、定时器、PWM、串口、中断等关键外设的快速复习

在进入机器人状态机设计之前,我们必须先夯实STM32关键外设的使用基础。状态机的每个状态迁移、每个动作执行,最终都要落到寄存器操作和外设驱动上。本章不是面面俱到的参考手册,而是围绕机器人控制中最常用的五个外设——GPIO、定时器、PWM、串口和中断——进行高密度、面向实战的快速复习。我们将从核心原理出发,结合寄存器级和HAL库级两种视角,配合具体的机器人场景(如舵机控制、电机调速、传感器读取、调试信息输出),帮助你建立“外设服务于状态机”的工程思维。

STM32关键外设与机器人状态机的关系 机器人状态机 待机 / 巡航 / 识别 / 动作 GPIO 传感器输入/指示灯 电机方向控制 定时器 时基/输入捕获 编码器接口 PWM 舵机角度控制 电机转速调节 串口 调试日志输出 上位机通信 中断 外部触发/紧急停止 定时器更新事件 状态机通过外设感知环境(输入)并执行动作(输出),中断驱动状态迁移

上图展示了本章的核心逻辑:状态机是机器人的“大脑”,而GPIO、定时器、PWM、串口和中断则是“感官”与“肌肉”。GPIO负责数字信号的输入输出,定时器提供精确的时间基准和信号测量能力,PWM将数字信号转化为模拟控制量,串口承担调试与通信任务,中断则让系统能够实时响应外部事件。下面我们逐一深入。

2.1 GPIO:数字世界的“开关”与“眼睛”

核心原理

GPIO(General Purpose Input/Output)是STM32最基础的外设。每个引脚可配置为输入、输出、复用功能或模拟模式。在机器人中,GPIO输入常用于读取限位开关、光电传感器、编码器信号;GPIO输出则用于控制LED指示灯、电机方向引脚、蜂鸣器等。STM32的GPIO支持上下拉电阻、开漏/推挽输出、以及最高50MHz的翻转速度。

操作流程与配置要点

  1. 使能时钟:在HAL库中调用__HAL_RCC_GPIOx_CLK_ENABLE(),这是所有操作的前提。
  2. 配置模式:使用HAL_GPIO_Init()设置引脚方向、速度、上下拉。例如,读取按键需配置为输入模式并启用上拉电阻。
  3. 读写操作:输出用HAL_GPIO_WritePin()或HAL_GPIO_TogglePin();输入用HAL_GPIO_ReadPin()。

实战场景:在机器人底盘上,我们使用PB0和PB1控制电机驱动芯片的IN1/IN2引脚,实现正反转。同时用PA0读取光电传感器的数字输出,用于巡线检测。初始化代码如下:

// 使能GPIOA和GPIOB时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();

// 配置PB0、PB1为推挽输出
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

// 配置PA0为输入模式,使能上拉
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 使用示例:电机正转
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);

常见误区:初学者容易忘记使能GPIO时钟,导致引脚无响应。另外,开漏输出模式必须外接上拉电阻才能输出高电平,否则只能输出低电平或高阻态。在机器人电路中,若使用开漏模式驱动LED,务必确认外部上拉已连接。

2.2 定时器:精确的时间基准与信号测量

核心原理

STM32定时器分为高级定时器(TIM1/8)、通用定时器(TIM2/3/4/5)和基本定时器(TIM6/7)。在机器人中,定时器主要有三大用途:产生时基(如每1ms触发一次中断,用于状态机轮询)、输入捕获(测量PWM脉宽或编码器频率)、输出比较(生成PWM波形)。定时器的核心是16位或32位计数器,配合预分频器(PSC)和自动重载寄存器(ARR)实现灵活的分频。

配置流程与计算

  1. 确定时钟源:通常使用内部时钟(APB1或APB2),频率可查参考手册。
  2. 计算PSC和ARR:定时频率 = 时钟频率 / ((PSC+1) * (ARR+1))。例如,系统时钟72MHz,要产生1kHz的更新中断,可设PSC=71,ARR=999。
  3. 配置中断:使能定时器更新中断,并在中断服务函数中处理逻辑。

案例拆解:在巡航状态中,我们需要每10ms读取一次超声波传感器。使用TIM3产生10ms中断:

// 假设APB1时钟为72MHz
TIM_HandleTypeDef htim3;
htim3.Instance = TIM3;
htim3.Init.Prescaler = 719;      // 72MHz/(719+1) = 100kHz
htim3.Init.Period = 999;         // 100kHz/(999+1) = 100Hz = 10ms
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_Base_Init(&htim3);
HAL_TIM_Base_Start_IT(&htim3);

// 中断回调
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim == &htim3) {
        // 每10ms执行一次超声波测距触发
        HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_SET);
        // 实际应用中需延时10us后拉低
    }
}

经验提醒:在状态机设计中,定时器中断应尽量保持“短小精悍”,只做标志位置位或简单计数,避免在中断中执行耗时操作(如串口打印、复杂计算)。否则会阻塞主循环,导致状态机响应延迟。

2.3 PWM:模拟量的数字控制

核心原理

PWM(脉冲宽度调制)通过改变占空比来模拟不同的电压输出。STM32通过定时器的输出比较通道生成PWM波形。在机器人中,PWM最典型的应用是舵机控制(周期20ms,脉宽0.5ms~2.5ms对应0°~180°)和直流电机调速(通过改变占空比调节平均电压)。

配置步骤

  1. 初始化定时器:设置PSC和ARR确定PWM周期。例如,舵机需要50Hz(20ms),若时钟72MHz,PSC=71,ARR=19999。
  2. 配置输出比较通道:设置PWM模式(向上计数时,计数器小于比较值时输出高电平)。
  3. 使能PWM输出:调用HAL_TIM_PWM_Start()。
  4. 动态调整占空比:修改比较寄存器CCR的值。

实战场景:在动作执行状态中,我们需要控制机械臂舵机旋转到指定角度。假设舵机连接在TIM2的CH1(PA0)上:

// 初始化TIM2,PSC=71,ARR=19999,周期20ms
htim2.Init.Prescaler = 71;
htim2.Init.Period = 19999;
HAL_TIM_PWM_Init(&htim2);

TIM_OC_InitTypeDef sConfigOC;
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 1500;  // 初始脉宽1.5ms,舵机中位
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

// 设置舵机角度为90°(脉宽1.5ms)
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 1500);

常见误区:舵机PWM周期必须严格为20ms(50Hz),如果周期偏差过大,舵机会抖动或无法转动。另外,不同舵机的脉宽范围可能不同(如0°对应0.5ms,180°对应2.5ms),务必查阅舵机手册,避免超出范围损坏舵机。

2.4 串口:机器人的“口”与“耳”

核心原理

串口(UART/USART)是STM32与外部设备通信最常用的接口。在机器人开发中,串口用于调试信息输出(通过printf重定向)、与上位机通信(发送状态数据、接收控制指令)、以及连接蓝牙/WiFi模块。串口通信基于异步帧格式,包含起始位、数据位(8/9位)、可选的奇偶校验位和停止位。

配置与使用

  1. 使能时钟并配置引脚复用:将TX/RX引脚配置为复用功能(AF模式)。
  2. 初始化UART:设置波特率、字长、停止位、校验位。
  3. 数据收发:轮询方式(HAL_UART_Transmit())、中断方式(HAL_UART_Receive_IT())或DMA方式。

案例拆解:在待机状态中,我们通过串口接收上位机发送的启动指令“GO”。使用中断接收方式,每收到一个字节存入缓冲区:

// 初始化USART1,波特率115200
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
HAL_UART_Init(&huart1);

// 开启中断接收
uint8_t rx_buffer[10];
HAL_UART_Receive_IT(&huart1, rx_buffer, 1);

// 中断回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart == &huart1) {
        if (rx_buffer[0] == 'G' && rx_buffer[1] == 'O') {
            // 切换到巡航状态
            state = CRUISE;
        }
        HAL_UART_Receive_IT(&huart1, rx_buffer, 1);  // 重新开启接收
    }
}

经验提醒:使用printf重定向到串口时,需要重写fputc()函数,并确保使用微库(MicroLib)。在状态机调试中,建议在状态切换时打印状态名称,便于追踪逻辑错误。

2.5 中断:实时响应的关键机制

核心原理

中断机制允许CPU暂停当前任务,转而去处理紧急事件,处理完毕后再返回。STM32支持外部中断(EXTI)、定时器中断、串口中断、DMA中断等。在机器人中,中断常用于紧急停止(如碰撞传感器触发)、编码器计数(每转产生脉冲中断)、通信接收等场景。中断优先级由NVIC(嵌套向量中断控制器)管理,支持抢占优先级和子优先级。

配置与注意事项

  1. 配置中断源:对于外部中断,需设置GPIO为中断模式,并配置触发边沿(上升沿、下降沿或双边沿)。
  2. 设置NVIC优先级:根据系统实时性要求分配抢占优先级。
  3. 编写中断服务函数:在回调函数中处理事件,注意保持简短。

实战场景:在机器人前方安装碰撞传感器,连接到PA1,配置为下降沿触发外部中断。当碰撞发生时,立即停止电机并切换到待机状态:

// 配置PA1为外部中断,下降沿触发
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 设置中断优先级并使能
HAL_NVIC_SetPriority(EXTI1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI1_IRQn);

// 中断回调
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == GPIO_PIN_1) {
        // 紧急停止:关闭PWM输出
        HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);
        state = STANDBY;
    }
}

常见误区:中断服务函数中禁止调用HAL_Delay(),因为HAL_Delay依赖SysTick中断,而SysTick中断优先级可能低于当前中断,导致死锁。另外,多个中断共享同一回调函数时,必须通过参数区分中断源,避免逻辑混乱。

2.6 外设协同:状态机中的综合应用

核心原理

在真实机器人系统中,外设不是孤立工作的。状态机需要协调多个外设:GPIO读取传感器判断环境,定时器提供时间片轮询,PWM控制执行器,串口上报状态,中断处理紧急事件。一个典型的状态迁移流程如下:

  1. 待机状态:定时器中断每10ms扫描一次启动按键(GPIO输入),串口等待上位机指令。
  2. 巡航状态:PWM输出驱动电机,定时器输入捕获读取编码器计算速度,GPIO读取巡线传感器。
  3. 识别状态:串口接收摄像头模块的数据,GPIO触发拍照信号。
  4. 动作执行状态:PWM控制舵机/电机,GPIO输出同步信号。

案例拆解:假设我们要实现一个简单的“避障机器人”状态机。系统包含:一个超声波传感器(GPIO触发+定时器输入捕获测距)、两个直流电机(PWM调速)、一个碰撞开关(外部中断)。状态机逻辑如下:

状态触发条件外设操作迁移目标
待机启动按键按下(GPIO)LED亮(GPIO输出)巡航
巡航超声波距离 < 20cmPWM输出50%占空比前进避障
避障定时器延时1sPWM输出反转(后退+转向)巡航
紧急停止碰撞开关触发(中断)PWM输出0%,蜂鸣器响待机

在这个系统中,定时器中断负责周期性的超声波触发和距离计算,外部中断负责碰撞检测,PWM负责电机控制,GPIO负责按键和LED。状态机的每个状态迁移都由外设事件驱动,而外设的初始化、配置和错误处理则需要在状态机运行前完成。

总结复盘

本章快速回顾了STM32五大关键外设的核心用法。需要强调的是,外设配置的规范性直接影响状态机的稳定性。建议在实际项目中遵循以下原则:

  • 初始化集中管理:将所有外设初始化放在独立的函数中(如MX_GPIO_Init()、MX_TIM_Init()),便于维护和排查。
  • 中断服务函数精简:中断中只做标志位设置或数据缓存,具体处理放在主循环或状态机中。
  • 使用HAL库回调机制:充分利用HAL库的弱回调函数(如HAL_GPIO_EXTI_Callback()),避免直接修改中断向量表。
  • 调试信息分层:在状态切换、外设错误等关键节点打印日志,但注意控制打印频率,避免影响实时性。

经验提醒:在开发初期,建议先用简单的轮询方式验证外设功能,再逐步引入中断和DMA。例如,先使用HAL_UART_Transmit()轮询发送调试信息,确认串口硬件正常后,再切换到中断接收。这样能快速定位硬件问题,避免复杂的调试过程。