4、STM32微控制器基础:选型、GPIO、定时器中断与DMA
做嵌入式这么多年,我接触过不少MCU平台。但要说最顺手、生态最完善的,STM32绝对排在前列。这一章咱们就聊聊STM32的几个核心基础——选型、GPIO、定时器中断和DMA。这些是压传感器数据采集系统的地基,地基打不牢,后面再漂亮的算法也跑不稳。
4.1 STM32系列选型:别盲目追高,够用就好
很多新手问我:「选STM32是不是越贵越好?」其实不是。我见过有人用F407做LED闪烁,纯粹浪费。选型要看三点:性能、外设、功耗。
咱们压传感器项目,核心需求是:
- 至少1路ADC(12位以上)
- 1个定时器产生采样触发
- 支持DMA传输
- I2C或SPI接口(传感器通信)
- 成本敏感
我个人习惯,这种场景首选STM32F103C8T6(蓝色 pill板)。72MHz主频,20KB RAM,64KB Flash,够用。如果你需要更低功耗,可以考虑STM32L0系列。但记住——选型时留20%的余量,别把资源用满。
我的经验:有一次项目选了STM32F030,结果后期要加个FFT运算,RAM不够,只能换芯片。重新画板、重新调试,折腾了两周。所以,前期多花10分钟评估,后期省10天。
4.2 GPIO配置:别小看这最简单的外设
GPIO看似简单,但坑不少。我刚开始做项目时,就因为在GPIO配置上翻过车——输出模式选错了,导致LED死活不亮。
STM32的GPIO有8种模式,咱们常用的就几种:
| 模式 | 用途 | 说明 |
|---|---|---|
| 推挽输出 | 驱动LED、蜂鸣器 | 能输出高低电平,驱动能力强 |
| 开漏输出 | I2C总线 | 需要外部上拉电阻 |
| 浮空输入 | 按键检测 | 电平不确定,需外部上拉/下拉 |
| 上拉/下拉输入 | 按键检测(省外部电阻) | 内部有弱上拉/下拉 |
| 模拟输入 | ADC采样 | 必须配置为模拟模式 |
配置代码其实很简单,但要注意顺序:先使能时钟,再配置模式。我见过有人把顺序搞反,结果GPIO死活不工作。
// 以PA0为例,配置为推挽输出
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 先开时钟
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 速度选50MHz
GPIO_Init(GPIOA, &GPIO_InitStruct);
小技巧:GPIO速度别选太高。50MHz够用就别选100MHz,否则EMI干扰会变大。我在做传感器采集时,就因为这个吃了亏——ADC数据跳得厉害,最后发现是GPIO速度太高导致的串扰。
4.3 定时器中断:精准采样的心脏
压传感器数据采集,最关键的就是定时采样。你不能用delay(),那会卡死CPU。正确做法是用定时器中断——每隔固定时间触发一次中断,在中断里启动ADC采样。
STM32的定时器很强大。咱们用最基本的通用定时器TIM3举例:
// 配置TIM3,产生1ms中断
void TIM3_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_InitStruct.TIM_Prescaler = 72 - 1; // 72MHz / 72 = 1MHz
TIM_InitStruct.TIM_Period = 1000 - 1; // 1MHz / 1000 = 1kHz (1ms)
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_InitStruct);
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); // 使能更新中断
TIM_Cmd(TIM3, ENABLE); // 启动定时器
// 配置NVIC
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
// 中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
// 在这里启动ADC采样
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
}
注意:中断服务函数里别做复杂运算!我曾经在中断里做滤波计算,结果中断时间太长,导致下一次中断被错过。正确做法是:中断里只设置标志位,主循环里处理数据。
4.4 DMA传输:解放CPU的利器
你想想看,如果每次ADC转换完都要CPU去读数据,那CPU就一直在等。尤其是多通道采样时,CPU基本被占满。这时候就需要DMA出场了。
DMA(直接存储器访问)说白了就是——数据搬运工。它可以在外设和内存之间自动搬数据,搬完了再通知CPU。这样CPU就可以去干别的事。
咱们压传感器项目,典型配置是:ADC连续采样,DMA自动把结果搬到内存数组里。CPU只需要在数组满了之后一次性处理。
// 配置ADC1 + DMA1
#define ADC_BUFFER_SIZE 100
uint16_t adc_buffer[ADC_BUFFER_SIZE];
void ADC_DMA_Init(void)
{
// 1. 配置ADC(略,重点是DMA)
// 2. 配置DMA
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitTypeDef DMA_InitStruct;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; // 外设地址
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)adc_buffer; // 内存地址
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; // 外设→内存
DMA_InitStruct.DMA_BufferSize = ADC_BUFFER_SIZE; // 传输数量
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不变
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 16位
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; // 循环模式
DMA_InitStruct.DMA_Priority = DMA_Priority_High;
DMA_Init(DMA1_Channel1, &DMA_InitStruct);
// 3. 使能DMA传输完成中断(可选)
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
// 4. 使能DMA通道
DMA_Cmd(DMA1_Channel1, ENABLE);
// 5. 使能ADC的DMA请求
ADC_DMACmd(ADC1, ENABLE);
}
关键点:DMA的循环模式(Circular)特别适合数据采集。它会自动从头开始覆盖旧数据,你只需要在中断里判断「数据满了没」。我习惯用双缓冲——DMA填满一半时触发中断,CPU处理这一半,DMA继续填另一半。这样数据不会丢。
4.5 知识体系总览
说了这么多,咱们用一张图把整个逻辑串起来。你看了就明白——选型、GPIO、定时器、DMA,它们是怎么配合的。
这张图把整个流程说清楚了。定时器是「指挥官」,告诉ADC什么时候干活;DMA是「搬运工」,把数据从ADC搬到内存;CPU是「老板」,只负责最后的数据处理。各司其职,效率才高。
避坑指南:我曾经在配置DMA时,忘了使能ADC的DMA请求(ADC_DMACmd),结果DMA一直不工作。查了两天,最后发现是少了一行代码。所以,配置完一定要检查——外设的DMA请求使能了没有?
好了,这一章的内容就这些。选型、GPIO、定时器、DMA,这四个基础打牢了,后面的传感器驱动和数据处理就水到渠成。记住:基础不牢,地动山摇。别急着跑,先把走路练稳。
公众号:蓝海资料掘金营,微信deep3321