2. STM32基础

在机器人控制AI项目中,STM32作为主控芯片承担着传感器数据采集、运动控制指令下发、与上位机通信等关键任务。本章围绕STM32的核心外设展开,从芯片选型逻辑出发,深入剖析GPIO、定时器、PWM、中断、串口的工作原理与编程方法,并结合机器人控制场景给出可落地的工程实践建议。理解这些基础外设,是后续实现传感器识别与运动控制闭环的硬件前提。

STM32机器人控制核心外设架构图 STM32 MCU Cortex-M4/M7 内核 GPIO 输入 传感器数字信号读取 定时器 + 编码器 电机转速/位置测量 外部中断 EXTI 急停/限位/边沿触发 GPIO 输出 LED指示/继电器/方向 定时器 + PWM 电机速度/舵机角度控制 USART 串口 上位机/传感器通信 编程方法:HAL库 / 标准外设库 / 寄存器直接操作 CubeMX初始化 → 外设句柄配置 → 中断回调 → 主循环轮询 传感器数据 控制信号

2.1 STM32系列选型:从机器人项目需求出发

核心原理:STM32家族庞大,从F0到H7覆盖不同性能层级。机器人控制AI项目通常需要同时处理传感器数据(I2C/SPI接口的IMU、激光雷达)、执行电机控制(多路PWM)、运行轻量级AI推理(如TinyML)。选型需综合考量主频、Flash/RAM容量、外设资源、功耗和成本。F103系列(Cortex-M3,72MHz)适合入门级两轮平衡车;F407系列(Cortex-M4,168MHz,带FPU)适合需要浮点运算的四轴或机械臂;H743系列(Cortex-M7,480MHz)则面向复杂视觉SLAM或端侧AI推理。

系列内核主频Flash/RAM典型机器人应用
STM32F103Cortex-M372MHz64KB/20KB小型避障车、循迹车
STM32F407Cortex-M4F168MHz1MB/192KB四轴飞行器、六足机器人
STM32H743Cortex-M7F480MHz2MB/1MB视觉导航机器人、AI边缘计算
STM32G474Cortex-M4F170MHz512KB/128KB高精度伺服控制、FOC电机驱动

判断方法:先列出项目外设需求清单,例如需要几路PWM、几个串口、是否需要USB OTG、是否要硬件加密。再评估算法复杂度——若需运行CNN模型,至少选择带FPU的M4内核,且RAM不低于128KB。最后考虑供货稳定性和开发工具链成熟度。

经验提醒:对于机器人控制AI项目,不要盲目追求高主频。F407在大多数场景下已足够,其FPU能显著加速PID控制和姿态解算中的浮点运算。若预算有限,F103配合外部传感器预处理芯片也能完成基础闭环。

2.2 GPIO:机器人的“手脚”与“皮肤”

核心原理:GPIO(General Purpose Input/Output)是STM32最基础的外设,每个引脚可配置为输入、输出、复用功能或模拟模式。在机器人中,GPIO输入用于读取限位开关、光电编码器信号、按键状态;GPIO输出用于控制LED指示灯、蜂鸣器、电机方向引脚。GPIO内部有上拉/下拉电阻、施密特触发器(输入模式)和推挽/开漏输出结构。

操作流程:使用HAL库配置GPIO的典型步骤为:

  1. 使能GPIO所在端口的时钟(如 __HAL_RCC_GPIOA_CLK_ENABLE());
  2. 定义 GPIO_InitTypeDef 结构体,设置Pin号、Mode(输入/输出/复用)、Pull(上拉/下拉/无)、Speed;
  3. 调用 HAL_GPIO_Init() 完成初始化;
  4. 读取用 HAL_GPIO_ReadPin(),输出用 HAL_GPIO_WritePin() 或 HAL_GPIO_TogglePin()。
// 配置PA5为推挽输出,用于驱动LED
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_5;
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);

// 配置PB0为输入,带上拉,用于读取限位开关
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

// 主循环中读取开关状态并控制LED
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET) {
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);  // 开关按下,点亮LED
}

常见误区:初学者常忽略GPIO的复用功能配置。当使用定时器的PWM输出或串口TX/RX时,必须将引脚模式设置为 GPIO_MODE_AF_PP(复用推挽),并调用 HAL_GPIO_Ex_Init() 或通过CubeMX正确映射AF编号。另外,开漏输出模式必须外接上拉电阻才能输出高电平,否则只能拉低。

2.3 定时器:从延时到编码器接口

核心原理:STM32定时器分为高级定时器(TIM1/8)、通用定时器(TIM2~5)和基本定时器(TIM6/7)。通用定时器具备时基单元、捕获/比较通道、编码器接口和PWM生成功能。在机器人中,定时器有三大用途:精确延时(时基模式)、输入捕获(测量脉冲宽度或编码器计数)、输出比较(生成PWM波形)。定时器的核心是16位/32位自动重载计数器,时钟源经过预分频器(PSC)后驱动计数器递增/递减。

判断方法:需要产生1kHz~100kHz的PWM时,选择通用定时器即可;若需控制舵机(50Hz周期,1~2ms脉宽),则要求定时器时钟精度足够高。编码器接口模式(如TIM4的Encoder Mode)可直接读取正交编码器信号,硬件自动完成计数,无需占用CPU中断。

案例拆解:两轮差速机器人的速度测量
背景:两轮机器人使用霍尔编码器电机,每圈输出13个脉冲,减速比30:1,即电机轴每转一圈输出390个脉冲。目标:测量左右轮转速,用于PID闭环控制。
操作:将左轮编码器A/B相接入TIM3的CH1/CH2,配置为Encoder Mode TI1+TI2;右轮接入TIM4。初始化时设置计数器为0,每10ms读取一次CNT值并清零。
计算:转速(rpm) = (CNT值 / 390) × (1000/10ms) × 60。实测左轮CNT=195时,转速为3000rpm。通过串口打印验证数据合理性。
复盘:编码器模式省去了外部中断计数,但需注意TIM3和TIM4的时钟树配置,确保计数频率不超过定时器上限。

2.4 PWM:电机速度与舵机角度的控制核心

核心原理:PWM(脉冲宽度调制)通过改变占空比来控制输出功率。STM32通过定时器的比较寄存器(CCR)与计数器CNT比较,当CNT < CCR时输出高电平,否则输出低电平。占空比 = CCR / ARR(自动重载值)。对于直流电机,PWM频率通常选10kHz~20kHz以避开 audible noise;对于舵机,频率固定为50Hz,占空比5%~10%对应0°~180°。

操作流程:以HAL库生成PWM为例:

  1. 初始化定时器时基(PSC、ARR);
  2. 初始化PWM通道(Mode、Pulse、极性);
  3. 调用 HAL_TIM_PWM_Start() 启动输出;
  4. 运行时修改占空比:__HAL_TIM_SET_COMPARE(&htim, TIM_CHANNEL_1, new_ccr)。
// 以TIM2_CH1输出20kHz PWM,ARR=8399,PSC=0(假设时钟84MHz)
htim2.Init.Prescaler = 0;
htim2.Init.Period = 8399;  // 84MHz / 8400 = 10kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&htim2);

TIM_OC_InitTypeDef sConfigOC = {0};
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 4200;  // 初始占空比50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

// 动态调整占空比为25%
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 2100);

常见误区:PWM频率与占空比分辨率存在矛盾。ARR值越大,占空比调节越精细,但频率越低。例如ARR=999时,占空比步进0.1%;若需20kHz频率,ARR=4199(84MHz/20kHz),占空比步进约0.024%。对于电机控制,0.1%的精度已足够;但舵机要求脉宽精度达1μs,需计算ARR=19999(50Hz)时,CCR每变化1对应5μs,精度不足。此时应使用定时器从模式或更高分辨率模式。

2.5 中断系统:实时响应传感器事件

核心原理:STM32支持嵌套向量中断控制器(NVIC),可配置抢占优先级和子优先级。外部中断(EXTI)用于检测GPIO边沿事件,定时器更新/捕获事件也可触发中断。在机器人中,中断用于急停信号、编码器Z相零位检测、超声波测距回波捕获等实时性要求高的场景。中断服务函数(ISR)应短小精悍,避免在中断中执行耗时操作。

操作流程:配置外部中断的步骤:

  1. 配置GPIO为中断模式(上升沿/下降沿/双边沿触发);
  2. 设置NVIC优先级分组和使能中断通道;
  3. 编写中断回调函数 HAL_GPIO_EXTI_Callback()。
// 配置PA0为下降沿中断(连接急停按钮)
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);

// 中断回调中置标志位,主循环处理
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == GPIO_PIN_0) {
        emergency_stop_flag = 1;  // 仅置标志,不在中断中做复杂处理
    }
}
风险提示:中断优先级设置不当会导致实时任务丢失。例如,若PWM中断优先级高于编码器捕获中断,当PWM频率较高时可能频繁打断编码器读取,造成计数丢失。建议将编码器捕获中断设为最高优先级,PWM更新中断设为较低优先级。同时,ISR中禁止使用HAL_Delay()等阻塞函数。

2.6 串口(USART):机器人与外界的“神经”

核心原理:USART(通用同步异步收发器)支持全双工异步通信,常用于与上位机、蓝牙模块、GPS模块、激光雷达通信。其核心是波特率发生器、发送/接收移位寄存器和数据寄存器。HAL库提供阻塞式、中断式和DMA三种传输模式。在机器人AI项目中,串口通常用于:① 调试信息输出(printf重定向);② 接收上位机运动指令;③ 与传感器模块进行数据交互。

判断方法:若数据量小且实时性要求不高(如每100ms发送一次状态),用阻塞式即可;若需高频接收传感器数据(如激光雷达每秒输出数万字节),必须使用DMA+空闲中断,否则CPU将被大量占用。

// 串口1初始化:115200-8-N-1
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;
huart1.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&huart1);

// 重定向printf到串口
int fputc(int ch, FILE *f) {
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100);
    return ch;
}

// 中断方式接收一字节
uint8_t rx_byte;
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
// 在回调中处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart == &huart1) {
        process_command(rx_byte);
        HAL_UART_Receive_IT(&huart1, &rx_byte, 1);  // 重新开启接收
    }
}

常见误区:串口通信不稳定时,首先检查波特率误差。当系统时钟不是标准频率(如使用HSE=8MHz倍频到72MHz)时,USART的BRR寄存器计算可能产生误差。使用逻辑分析仪测量实际波形,误差超过2%就会导致乱码。另外,中断接收必须确保在回调中重新调用 HAL_UART_Receive_IT(),否则只能收到一帧数据。

2.7 综合实战:传感器识别与运动控制的闭环雏形

案例背景:设计一个简易的循迹机器人,使用3路红外传感器(GPIO输入)检测黑线,通过PWM控制两个直流电机差速转向。要求:传感器检测到黑线时,对应电机减速;未检测到时全速前进。系统需通过串口打印状态信息。

关键参数:PWM频率10kHz,初始占空比60%(左轮TIM2_CH1,右轮TIM2_CH2)。传感器接PB0~PB2,上拉输入,检测到黑线时输出低电平。串口波特率115200。

实现步骤:

  1. 初始化GPIO、定时器PWM、串口;
  2. 主循环中读取3路传感器状态;
  3. 根据状态组合调整左右轮占空比:
uint8_t s1 = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
uint8_t s2 = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
uint8_t s3 = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);

if (s2 == 0) {  // 中间传感器在黑线上,直行
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 600);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 600);
} else if (s1 == 0) {  // 左偏,右轮加速
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 400);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 800);
} else if (s3 == 0) {  // 右偏,左轮加速
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 800);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 400);
} else {  // 全丢线,停止
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 0);
}
printf("S1:%d S2:%d S3:%d\r\n", s1, s2, s3);
HAL_Delay(10);

结果与复盘:该闭环实现了基本的循迹功能,但存在两个问题:一是传感器检测到黑线后立即调整PWM,导致电机响应过于激进,车身抖动;二是未加入速度反馈,无法应对电池电压下降导致的转速变化。改进方案:在PWM调整中加入斜坡函数(每次变化不超过50),并增加编码器测速形成速度闭环。此案例展示了GPIO、PWM、定时器、串口的协同工作方式,为后续引入AI视觉识别和更复杂的运动控制打下基础。

总结复盘:STM32外设编程的核心是“配置-启动-处理”三步法。配置阶段使用CubeMX生成初始化代码,减少手动寄存器操作错误;启动阶段注意外设间的依赖关系(如PWM依赖定时器时钟);处理阶段根据实时性要求选择轮询、中断或DMA。在机器人项目中,建议将传感器读取、控制算法、通信任务分配到不同的执行层级,避免在中断中做复杂运算。掌握本章的GPIO、定时器、PWM、中断、串口后,即可搭建一个具备基本感知与运动能力的机器人平台,为后续AI算法部署提供稳定的硬件基础。