2. STM32基础
在机器人控制AI项目中,STM32作为主控芯片承担着传感器数据采集、运动控制指令下发、与上位机通信等关键任务。本章围绕STM32的核心外设展开,从芯片选型逻辑出发,深入剖析GPIO、定时器、PWM、中断、串口的工作原理与编程方法,并结合机器人控制场景给出可落地的工程实践建议。理解这些基础外设,是后续实现传感器识别与运动控制闭环的硬件前提。
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 | 典型机器人应用 |
|---|---|---|---|---|
| STM32F103 | Cortex-M3 | 72MHz | 64KB/20KB | 小型避障车、循迹车 |
| STM32F407 | Cortex-M4F | 168MHz | 1MB/192KB | 四轴飞行器、六足机器人 |
| STM32H743 | Cortex-M7F | 480MHz | 2MB/1MB | 视觉导航机器人、AI边缘计算 |
| STM32G474 | Cortex-M4F | 170MHz | 512KB/128KB | 高精度伺服控制、FOC电机驱动 |
判断方法:先列出项目外设需求清单,例如需要几路PWM、几个串口、是否需要USB OTG、是否要硬件加密。再评估算法复杂度——若需运行CNN模型,至少选择带FPU的M4内核,且RAM不低于128KB。最后考虑供货稳定性和开发工具链成熟度。
2.2 GPIO:机器人的“手脚”与“皮肤”
核心原理:GPIO(General Purpose Input/Output)是STM32最基础的外设,每个引脚可配置为输入、输出、复用功能或模拟模式。在机器人中,GPIO输入用于读取限位开关、光电编码器信号、按键状态;GPIO输出用于控制LED指示灯、蜂鸣器、电机方向引脚。GPIO内部有上拉/下拉电阻、施密特触发器(输入模式)和推挽/开漏输出结构。
操作流程:使用HAL库配置GPIO的典型步骤为:
- 使能GPIO所在端口的时钟(如
__HAL_RCC_GPIOA_CLK_ENABLE()); - 定义
GPIO_InitTypeDef结构体,设置Pin号、Mode(输入/输出/复用)、Pull(上拉/下拉/无)、Speed; - 调用
HAL_GPIO_Init()完成初始化; - 读取用
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为例:
- 初始化定时器时基(PSC、ARR);
- 初始化PWM通道(Mode、Pulse、极性);
- 调用
HAL_TIM_PWM_Start()启动输出; - 运行时修改占空比:
__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)应短小精悍,避免在中断中执行耗时操作。
操作流程:配置外部中断的步骤:
- 配置GPIO为中断模式(上升沿/下降沿/双边沿触发);
- 设置NVIC优先级分组和使能中断通道;
- 编写中断回调函数
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; // 仅置标志,不在中断中做复杂处理
}
}
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。
实现步骤:
- 初始化GPIO、定时器PWM、串口;
- 主循环中读取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视觉识别和更复杂的运动控制打下基础。