文档
STM32 HAL 开发实战
本章目标
掌握 STM32CubeMX + HAL 的开发流程,理解时钟树配置与外设初始化,完成定时器 PWM 和 ADC 采集。
1. STM32 家族概览
| 系列 | 内核 | 频率 | 定位 |
|---|---|---|---|
| F0 | Cortex-M0 | 48MHz | 低成本替代 8-bit |
| F1 | Cortex-M3 | 72MHz | 经典入门 |
| F4 | Cortex-M4 | 168-180MHz | DSP + FPU |
| F7 | Cortex-M7 | 216MHz | 高性能 + Cache |
| H7 | Cortex-M7+M4 | 480MHz | 双核旗舰 |
| G0/G4 | Cortex-M0+/M4 | 64/170MHz | 新一代主流 |
| L0/L4 | Cortex-M0+/M4 | 32/80MHz | 超低功耗 |
2. CubeMX 配置流程
步骤
- 新建工程 → 选择 MCU 型号(如 STM32F407VGT6)
- Pinout → 配置引脚功能:
- RCC:HSE = Crystal/Ceramic Resonator
- SYS:Debug = Serial Wire
- Clock Configuration → 配置时钟树:
- HSE 8MHz → PLL /8 ×336 /2 = 168MHz (SYSCLK)
- APB1 = 42MHz, APB2 = 84MHz
- 外设配置(见下方各节)
- Project Manager → Toolchain = Makefile(或 CubeIDE)
- Generate Code
3. GPIO 最佳实践
// 读取按键(带软件消抖)
uint8_t read_button(void) {
if (HAL_GPIO_ReadPin(BTN_GPIO_Port, BTN_Pin) == GPIO_PIN_RESET) {
HAL_Delay(20); // 消抖
if (HAL_GPIO_ReadPin(BTN_GPIO_Port, BTN_Pin) == GPIO_PIN_RESET) {
while (HAL_GPIO_ReadPin(BTN_GPIO_Port, BTN_Pin) == GPIO_PIN_RESET);
return 1; // 按键释放后才返回
}
}
return 0;
}
// 翻转 LED
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
// 原子写入(位带操作,F1 系列不支持)
LED_GPIO_Port->BSRR = LED_Pin; // 置位
LED_GPIO_Port->BSRR = LED_Pin << 16; // 复位
4. 定时器 PWM
CubeMX 配置
- TIM3 → Clock Source = Internal Clock
- Channel 1 → PWM Generation CH1
- Prescaler = 84-1 (84MHz / 84 = 1MHz)
- Counter Period = 1000-1 (1MHz / 1000 = 1kHz PWM)
// 初始化
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
// 设置占空比(0-999)
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 500); // 50%
// 呼吸灯效果
for (int duty = 0; duty <= 999; duty++) {
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, duty);
HAL_Delay(1);
}
5. ADC 连续采样
CubeMX 配置
- ADC1 → IN0 (PA0), Scan Conversion = Enabled
- DMA2 Stream0 → Circular, Half Word
#define ADC_BUF_SIZE 100
static uint16_t adc_buf[ADC_BUF_SIZE];
void adc_init(void) {
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf, ADC_BUF_SIZE);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
// DMA 完成一帧,处理数据
float avg = 0;
for (int i = 0; i < ADC_BUF_SIZE; i++) {
avg += adc_buf[i];
}
avg /= ADC_BUF_SIZE;
float voltage = avg * 3.3f / 4095.0f;
printf("ADC Average: %.3f V\n", voltage);
}
6. HAL 超时机制
// 所有阻塞型 HAL API 都带超时参数
if (HAL_I2C_Master_Transmit(&hi2c1, addr, data, len, 100) != HAL_OK) {
// 100ms 超时后返回 HAL_TIMEOUT
handle_i2c_error();
}
非阻塞 API(
_IT或_DMA后缀)不使用超时,依赖回调。
思考题
- STM32 的 HSI vs HSE vs PLL 分别用于什么场景?
- 为什么 ADC 推荐用 DMA 而非中断方式?
- HAL 库和 LL 库各适合什么样的开发场景?