STM32极速入门教程:十天掌握,超详细图文解析!
今天的标题,有点刺激,但我学STM32,到项目上能把资源跑起来,差不多就是花了这么多时间。
不过,对于完全没学过编程和单片机,劝你踏踏实实先学51,放弃幻想,十天估计连STM32是咋回事,都搞不明白。
如果已经玩过51开发板,那么开启疯狗模式,十天学完STM32,完全能做到。
我特别能理解学习STM32时的那种抓狂感。刚开始接触时,感觉完全无从下手,面对厚得像砖头一样的芯片手册,还有那些让人头晕的寄存器配置,感觉根本学不完。搭个开发环境都能折腾半天,更别提写代码实现一个小功能了,那种迷茫和无力感真的很打击信心。
我也试过零散学点教程,但总是东一榔头西一棒子,学了半天还是不会用。
所以我特别渴望一种靠谱、系统的方法,能让我在短时间内抓住STM32的重点,少走弯路,快速上手做项目。
作为前辈,为了帮更多初学者摆脱这种痛苦,也让我下定决心,要总结出一条清晰的学习路径,其实刚开始学STM32压根就不需要深入细节,把它当做一个工具,而不是一门技术,你就稳了。
这篇3000字+文章为你准备了一份超实用的STM32十天学习计划,从基础的GPIO操作到高级的通信协议,目标明确,步步为营,让你学得明白、用得顺手。
准备工作
在开始学习之前请确保准备好以下软硬件。
硬件:STM32F103C8T6开发板,性价比高且功能齐全,适合初学者;辅助元件,包括LED灯、按键、电阻、杜邦线和面包板等,用于实验,电路基础差,动手能力差的,也可以直接买现成的开发板。
软件:Keil MDK,用于程序编写和调试的集成开发环境;ST-Link驱动,用于将程序下载至开发板。
以下内容将基于 STM32F10x 标准库展开。
第一天:GPIO输出控制
目标:
学习GPIO的基本输出功能,通过控制LED灯的亮灭初步掌握STM32的硬件操作。
代码示例:
以下代码使用标准库点亮连接在PA5引脚的LED:
#include "stm32f10x.h"
void GPIO_Config(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; // 配置PA5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;// 输出速度50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
int main(void) {
GPIO_Config();
GPIO_SetBits(GPIOA, GPIO_Pin_5); // 设置PA5为高电平,点亮LED
while (1) {
}
}
在此示例中,RCC_APB2PeriphClockCmd 用于使能GPIOA的时钟,GPIO_Init配置PA5为推挽输出模式,GPIO_SetBits 将引脚置高以点亮LED。
第二天:GPIO输入与按键检测
目标:
学习GPIO的输入功能,通过读取按键状态控制LED。
代码示例:
以下代码检测PC13的按键状态控制PA5的LED:
#include "stm32f10x.h"
void GPIO_Config(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);
// 配置PA5为输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置PC13为输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
int main(void) {
GPIO_Config();
while (1) {
if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) == Bit_RESET) {
GPIO_SetBits(GPIOA, GPIO_Pin_5); // 按键按下,LED亮
} else {
GPIO_ResetBits(GPIOA, GPIO_Pin_5); // 按键松开,LED灭
}
}
}
说明:
PC13配置为上拉输入模式,当按键按下时引脚电平为低,通过 GPIO_ReadInputDataBit 检测状态并控制LED。
第三天:定时器实现LED闪烁
目标:
掌握定时器的基本配置,使用定时器中断实现LED每秒闪烁一次。
代码示例:
#include "stm32f10x.h"
void GPIO_Config(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void TIM_Config(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能TIM2时钟
TIM_TimeBaseStructure.TIM_Period = 9999; // 计数周期
TIM_TimeBaseStructure.TIM_Prescaler = 7199; // 预分频值,1秒中断
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 启用更新中断
TIM_Cmd(TIM2, ENABLE); // 启动定时器
}
void NVIC_Config(void) {
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5)));
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
int main(void) {
GPIO_Config();
TIM_Config();
NVIC_Config();
while (1) {
}
}
说明:
TIM2 的时钟频率通过预分频和周期设置实现1秒中断,在中断服务函数中翻转LED状态。
第四天:外部中断
目标:
学习外部中断的使用,通过按键触发中断翻转LED状态。
代码示例:
#include "stm32f10x.h"
void GPIO_Config(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
void EXTI_Config(void) {
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource13);
EXTI_InitStructure.EXTI_Line = EXTI_Line13;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
void NVIC_Config(void) {
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void EXTI15_10_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line13) != RESET) {
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5)));
EXTI_ClearITPendingBit(EXTI_Line13);
}
}
int main(void) {
GPIO_Config();
EXTI_Config();
NVIC_Config();
while (1) {
}
}
说明
外部中断通过下降沿触发,在中断服务函数中翻转LED状态,避免了轮询的低效性。
第五天:UART通信
目标
掌握UART串口通信,向PC发送数据。
代码示例
#include "stm32f10x.h"
void GPIO_Config(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // USART1_TX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void USART_Config(void) {
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}
void USART_SendString(char* str) {
while (*str) {
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, *str++);
}
}
int main(void) {
GPIO_Config();
USART_Config();
USART_SendString("Hello, STM32!\r\n");
while (1) {
}
}
说明
USART1 配置为115200波特率,通过 USART_SendString
函数发送字符串至PC。
第六天:ADC(模数转换器)应用
目标:
学习如何配置和使用STM32的ADC模块,将模拟信号转换为数字信号。
应用场景:
ADC广泛用于嵌入式系统中读取模拟量,例如电压、电流、光照强度等。在实际产品中,如智能家居的环境监测模块,ADC可用于读取光敏电阻或气体传感器的输出,用于检测环境光照或空气质量。
代码示例
以下代码使用ADC1的通道0(PA0)读取模拟电压,并通过串口输出结果:
#include "stm32f10x.h"
void ADC_Config(void) {
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 无外部触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; // 1个通道
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1); // 复位校准
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1); // 开始校准
while (ADC_GetCalibrationStatus(ADC1));
}
uint16_t ADC_Read(void) {
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 软件触发转换
while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 等待转换完成
return ADC_GetConversionValue(ADC1); // 返回ADC值
}
int main(void) {
GPIO_Config(); // 假设已配置PA0为模拟输入
USART_Config(); // 假设已配置串口
ADC_Config();
while (1) {
uint16_t adc_value = ADC_Read();
float voltage = (float)adc_value * 3.3 / 4095; // 转换为电压(3.3V参考)
char buf[20];
sprintf(buf, "Voltage: %.2fV\r\n", voltage);
USART_SendString(buf); // 通过串口发送
Delay(1000); // 假设有延时函数
}
}
说明
ADC配置为独立模式,读取PA0通道的模拟信号,结果通过串口以电压形式输出(12位分辨率,参考电压3.3V)。在实际应用中,可结合传感器特性,将电压转换为具体的物理量,如光照强度或压力。
第七天:PWM(脉冲宽度调制)控制
目标
掌握STM32定时器的PWM模式配置,用于模拟输出控制。
应用场景
PWM常用于调节电机转速、LED亮度或蜂鸣器音量。在实际产品中,如智能照明系统,PWM可用于控制LED灯的亮度,实现节能和舒适的调光效果。
代码示例
以下代码使用TIM1通道1(PA8)生成PWM信号,控制LED亮度:
#include "stm32f10x.h"
void TIM_Config(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 999; // PWM周期为1kHz
TIM_TimeBaseStructure.TIM_Prescaler = 71; // 定时器时钟1MHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500; // 初始占空比50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
TIM_Cmd(TIM1, ENABLE);
TIM_CtrlPWMOutputs(TIM1, ENABLE); // 启用PWM输出
}
int main(void) {
GPIO_Config(); // 假设已配置PA8为复用推挽输出
TIM_Config();
while (1) {
// 可动态修改TIM1->CCR1调整占空比
}
}
说明
TIM1配置为1kHz PWM信号,初始占空比50%。通过修改TIM1->CCR1
寄存器值,可动态调整占空比,从而控制LED亮度或电机转速。
第八天:I2C通信
目标
学习I2C总线通信,掌握与I2C外设的交互方法。
应用场景
I2C适用于低速设备通信,如EEPROM、传感器、实时时钟等。在实际产品中,用途非常非常广。
代码示例
IIC可以用软件模拟,也可以直接用芯片内部的IIC功能,以下代码使用单片机内部I2C1读取EEPROM(例如24C02)地址0x00处的数据:
#include "stm32f10x.h"
void I2C_Config(void) {
I2C_InitTypeDef I2C_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 100000; // 100kHz
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
}
uint8_t I2C_ReadByte(uint8_t addr) {
while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter); // EEPROM地址
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, addr);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Receiver);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
I2C_AcknowledgeConfig(I2C1, DISABLE);
I2C_GenerateSTOP(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
return I2C_ReceiveData(I2C1);
}
int main(void) {
GPIO_Config(); // 假设已配置PB6、PB7为I2C1引脚
I2C_Config();
uint8_t data = I2C_ReadByte(0x00);
// 可通过串口输出data
while (1) {
}
}
说明
I2C1配置为100kHz通信速率,读取EEPROM的数据。实际应用中,可扩展为读取多个传感器数据或存储配置信息。
第九天:SPI通信
目标
掌握SPI总线通信,学习与SPI外设的高速数据交互。
应用场景
SPI适用于高速数据传输场景,如SD卡、闪存、显示屏等。在实际产品中,如数据采集系统,SPI可用于连接高速ADC或闪存芯片,实现快速数据采样和存储。
代码示例
以下代码使用SPI1读取闪存(例如W25Q16)的数据:
#include "stm32f10x.h"
void SPI_Config(void) {
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
uint8_t SPI_ReadByte(void) {
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, 0xFF); // 发送空数据以触发读取
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
int main(void) {
GPIO_Config(); // 假设已配置PA4、PA5、PA6、PA7为SPI1引脚
SPI_Config();
// 假设已选中闪存芯片
uint8_t data = SPI_ReadByte();
// 可通过串口输出data
while (1) {
}
}
说明
SPI1配置为主机模式,8位数据帧,用于读取闪存数据。实际应用中,可结合闪存的指令集实现数据读写,或驱动显示屏显示内容。
第十天:综合项目
项目目标
设计一个智能照明系统,使用STM32控制LED灯的亮度,支持手动按键调节和自动光照调节,实现节能和舒适的照明效果。
项目组件
STM32F103C8T6开发板
LED灯
光敏电阻(检测环境光照)
按键(手动调节亮度)
串口(调试和状态显示)
实现步骤
1.PWM控制LED亮度:使用TIM1生成PWM信号,调整LED亮度。
2.ADC读取光敏电阻:通过ADC1读取光敏电阻电压,判断环境光照强度。
3.按键控制:使用GPIO外部中断检测按键,实现手动亮度调节。
4.自动调光:根据光照强度自动调整PWM占空比。
5.串口调试:输出当前光照强度和PWM值。
代码框架
#include "stm32f10x.h"
void System_Init(void) {
GPIO_Config(); // 配置PA8为PWM,PA0为ADC,PC13为按键
TIM_Config(); // 配置TIM1 PWM
ADC_Config(); // 配置ADC1
EXTI_Config(); // 配置外部中断
USART_Config(); // 配置串口
}
uint16_t Calculate_PWM(uint16_t light_value) {
// 简单线性映射:光照越强,亮度越低
return (4095 - light_value) / 4; // 调整范围到0-999
}
int main(void) {
System_Init();
while (1) {
uint16_t light_value = ADC_Read();
uint16_t pwm_value = Calculate_PWM(light_value);
TIM_SetCompare1(TIM1, pwm_value); // 更新PWM占空比
char buf[50];
sprintf(buf, "Light: %d, PWM: %d\r\n", light_value, pwm_value);
USART_SendString(buf);
Delay(1000);
}
}
void EXTI15_10_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line13) != RESET) { // PC13按键中断
static uint16_t duty = 500;
duty = (duty + 100) % 1000; // 循环调整占空比
TIM_SetCompare1(TIM1, duty);
EXTI_ClearITPendingBit(EXTI_Line13);
}
}
说明
该项目结合PWM、ADC和外部中断实现智能照明控制。光敏电阻用于自动调光,按键提供手动控制选项,串口用于调试。实际应用中,可扩展为多路灯光控制或添加无线通信功能。
其实学完以上的外设资源,基本能做80%的项目了,其它外设产品用到了再针对性。
那学完以上的,要怎么继续提升?
个人建议是把编程思维和功底打扎实,多做项目,我收集了一些开源项目,可以拿去练手。
最近很多粉丝问我单片机怎么学,我根据自己从业十年经验,累积耗时一个月,精心整理一份「单
片机最佳学习路径+单片机入门到高级教程+工具包」,全部无偿分享给铁粉!!!
除此以外,再含泪分享我压箱底的22个热门开源项目,包含源码+原理图+PCB+说明文档,让你迅速进阶成高手!
教程资料包和详细的学习路径可以看我下面这篇文章的开头。
《单片机入门到高级开挂学习路径(附教程+工具)》
《单片机入门到高级开挂学习路径(附教程+工具)》
《单片机入门到高级开挂学习路径(附教程+工具)》
作者:无际单片机编程