1. 什么是STM32?为什么选择它作为入门MCU?
STM32是ST公司基于ARM Cortex-M内核开发的32位微控制器,你可能听说过Arduino,但STM32才是真正进入嵌入式开发世界的敲门砖。我刚开始接触嵌入式时也纠结过选什么芯片,后来发现STM32F103C8T6(俗称"蓝莓板")简直是新手福音——价格便宜(十来块钱一片)、资源丰富,而且社区资料多得看不完。
这块芯片内核是ARM Cortex-M3,主频72MHz,64KB闪存和20KB内存,对于大多数入门项目完全够用。更重要的是,STM32的生态非常完善,有标准库、HAL库和LL库三种开发方式,新手建议从标准库开始学起,虽然ST现在主推HAL库,但标准库更容易理解底层原理。
实际项目中,STM32可以做的事情超乎想象:智能小车的电机控制、无人机飞控、物联网设备的数据采集,甚至简单的工业控制都能胜任。我第一个STM32项目是做智能台灯,用PWM调光加上语音控制,虽然简单但让我彻底理解了嵌入式开发的整个流程。
2. 搭建最小系统:让STM32跑起来的关键步骤
2.1 电源电路设计
电源是最小系统中最容易出错的部分!STM32需要3.3V供电,但USB输出是5V,直接接上去肯定会烧芯片。我的第一个教训就是这么来的——当时连着USB线一阵青烟,50块钱的开发板就报废了。
正确的做法是用AMS1117-3.3稳压芯片,把5V降到3.3V。电路连接很简单:
- AMS1117的VIN接USB的5V
- VOUT接STM32的VDD引脚
- GND接地
还要在输入输出端各加一个10μF和0.1μF的电容滤波,不然电压波动会导致芯片频繁复位。我曾经因为省掉了一个0.1μF电容,调试了整整两天才找到问题。
2.2 时钟电路配置
STM32有时钟源和外置晶振两种工作方式。新手建议先用内部RC振荡器,等熟悉了再外接晶振。内部RC振荡器虽然精度稍差(±1%),但对于LED闪烁、按键检测这类基础应用完全足够。
如果要接外部晶振:
- 主晶振接8MHz(OSC_IN和OSC_OUT引脚)
- RTC晶振接32.768kHz(OSC32_IN和OSC32_OUT引脚)
记得在每个晶振两端接20pF的负载电容,这是我踩过的另一个坑——没接电容时晶振根本不起振。
2.3 复位电路与启动模式
复位电路简单但重要:一个10kΩ电阻和0.1μF电容串联在NRST引脚和地之间。当按下复位按钮时,NRST被拉低,芯片复位。
启动模式由BOOT0和BOOT1引脚决定:
- BOOT0=0, BOOT1=x:从主闪存启动(正常模式)
- BOOT0=1, BOOT1=0:从系统存储器启动(串口下载模式)
- BOOT0=1, BOOT1=1:从SRAM启动(调试模式)
新手最容易犯的错误是BOOT引脚没接好,导致程序下载后不运行。建议用10k电阻把BOOT0接到地,确保始终从闪存启动。
3. GPIO基础:从点亮第一个LED开始
3.1 GPIO工作模式详解
STM32的GPIO有8种工作模式,刚开始可能觉得复杂,其实就分三大类:
输入类:
- 输入浮空:引脚悬空,用于按键检测
- 输入上拉:内部上拉电阻,默认高电平
- 输入下拉:内部下拉电阻,默认低电平
- 模拟输入:用于ADC采样
输出类:
- 开漏输出:只能拉低不能拉高,需要外接上拉电阻
- 推挽输出:最常用,能输出高电平和低电平
复用功能:
- 开漏复用
- 推挽复用
推挽输出最常用,比如驱动LED:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
3.2 实际接线与代码实现
LED接线很简单:LED正极接330Ω限流电阻,再接到PA0引脚,负极接地。电阻不能省,我当初直接接LED,虽然亮度很足但几天后GPIO口就烧坏了。
代码示例:
#include "stm32f10x.h"
void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
int main(void)
{
LED_Init();
while(1)
{
GPIO_SetBits(GPIOA, GPIO_Pin_0); // 亮
for(int i=0; i<1000000; i++); // 简单延时
GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 灭
for(int i=0; i<1000000; i++);
}
}
这个程序虽然简单,但包含了STM32开发的所有核心要素:时钟使能、GPIO初始化、输出控制。延时用循环是为了简单,实际项目要用定时器。
4. 按键输入:检测与消抖实战
4.1 硬件连接方案
按键接法有两种:上拉和下拉。我推荐用上拉电阻接法,因为STM32内部就有上拉电阻,不用外接。
以上拉电阻为例:
- 按键一端接GND
- 另一端接GPIO引脚(如PA1)
- 在代码中启用内部上拉电阻
这样当按键未按下时,引脚读到的始终是高电平;按下时变成低电平。
4.2 软件消抖的三种方法
机械按键都有抖动问题,通常持续10-20ms。这是我当初最头疼的问题,按键时灵时不灵,后来才知道是没做消抖。
延时消抖最简单:
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 0) // 检测到按键按下
{
delay_ms(20); // 延时20ms跳过抖动期
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 0) // 再次检测
{
// 真正的按键操作
GPIO_WriteBit(GPIOA, GPIO_Pin_0,
(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_0)));
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 0); // 等待释放
}
}
定时器扫描更高效:用定时器每10ms扫描一次按键状态,通过状态机消除抖动。
中断方式最实时:配置EXTI中断,在中断中启动定时器,定时器超时后确认按键状态。
5. 调试技巧:常见问题与解决方法
5.1 程序下载失败排查
新手最常遇到的三个下载问题:
- BOOT引脚配置错误:确保BOOT0接地,BOOT1随意
- 复位电路问题:检查复位引脚是否有正常波形
- SWD接口被禁用:如果程序中将SWD引脚配置为GPIO,就只能通过串口下载了
我建议在main函数开头加个2秒延时,这样即使程序有问题,也有时间连接调试器。
5.2 硬件故障诊断
用万用表检查三个关键点:
- 3.3V电源是否稳定
- 复位引脚是否为高电平
- 晶振是否起振(用示波器看波形)
曾经有一次,芯片发热严重,排查半天发现是GPIO短路了。所以接线前一定要用万用表通断档检查。
6. 项目实战:制作呼吸灯
6.1 PWM原理与配置
呼吸灯用PWM实现,通过调节占空比来控制亮度。STM32的定时器可以直接产生PWM信号,以TIM2的CH1为例:
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置PA0为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_InternalClockConfig(TIM2); // 使用内部时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; // 自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; // 预分频器
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
}
6.2 呼吸效果实现
通过改变比较值来实现呼吸效果:
uint8_t dir = 0;
uint16_t pulse = 0;
while(1)
{
if(dir == 0) {
pulse++;
if(pulse >= 100) dir = 1;
} else {
pulse--;
if(pulse == 0) dir = 0;
}
TIM_SetCompare1(TIM2, pulse);
delay_ms(10);
}
这个例子综合了GPIO、定时器、PWM三个知识点,是检验学习成果的好项目。
7. 进阶学习:接下来该学什么?
掌握GPIO后,建议按这个顺序继续学习:
- 外部中断:用EXTI实现更高效的按键检测
- 定时器:STM32最强大的外设,用于定时、PWM、输入捕获等
- 串口通信:与电脑或其他设备通信
- ADC采样:读取模拟传感器数据
- I2C/SPI:连接各种传感器模块
每个知识点最好都做个小项目巩固,比如用ADC读取光敏电阻值,通过PWM自动调节LED亮度。
633



被折叠的 条评论
为什么被折叠?



