从零到一手把手教你用STM32F103点亮第一个LED附完整代码与避坑指南1. 嵌入式开发入门为什么选择STM32F103对于刚接触嵌入式开发的初学者来说STM32F103系列微控制器是一个绝佳的起点。这款基于ARM Cortex-M3内核的MCU凭借其出色的性价比和丰富的生态资源已经成为嵌入式领域的国民芯片。STM32F103VBT6的核心优势72MHz主频性能足够应对大多数嵌入式场景丰富的外设资源GPIO、USART、SPI、I2C、ADC等完善的开发工具链和社区支持低功耗设计适合电池供电设备工业级温度范围-40℃~85℃我第一次接触STM32是在大学电子设计竞赛时当时被其简洁的开发方式和强大的性能所震撼。相比传统的51单片机STM32的库函数开发模式让硬件操作变得异常简单。2. 开发环境搭建Keil MDK-ARM实战指南2.1 工具链安装所需软件清单Keil MDK-ARM建议版本5.30STM32F1xx Device Family PackST-Link驱动串口调试工具如Putty安装步骤# 以管理员身份运行MDK安装包 # 安装完成后注册社区版有32KB代码限制 # 通过Pack Installer安装STM32F1xx_DFP2.2 工程创建关键步骤新建Project选择STM32F103VB作为目标器件配置工程属性时特别注意Target选项卡勾选Use MicroLIB简化printf重定向Output选项卡勾选Create HEX FileDebug选项卡选择ST-Link Debugger注意初次使用时常犯的错误是忘记安装对应的Device Family Pack导致无法选择目标器件。3. GPIO深度解析点亮LED的8种姿势3.1 STM32的GPIO架构STM32的每个GPIO端口有2个32位配置寄存器CRL, CRH2个32位数据寄存器IDR, ODR1个32位置位/复位寄存器BSRR1个16位复位寄存器BRR1个32位锁定寄存器LCKRGPIO工作模式对比表模式描述典型应用输入浮空浮空输入电平不确定按键检测输入上拉内部上拉电阻使能节省外部元件输入下拉内部下拉电阻使能节省外部元件模拟输入ADC采样输入传感器信号采集开漏输出只能输出低电平或高阻态I2C总线推挽输出可输出高/低电平LED驱动复用功能外设控制引脚USART, SPI等开漏复用开漏模式的复用功能I2C等3.2 LED电路设计要点典型LED驱动电路// LED阳极接3.3V阴极接GPIO低电平点亮 // 需串联限流电阻计算公式 R (Vcc - Vled) / Iled其中Vcc电源电压3.3VVledLED正向压降通常1.8-2.2VIled期望电流通常5-20mA4. 代码实战三种方式控制LED4.1 寄存器版本最底层#include stm32f10x.h void Delay(uint32_t nCount) { for(; nCount ! 0; nCount--); } int main(void) { // 1. 开启GPIOB时钟 RCC-APB2ENR | RCC_APB2ENR_IOPBEN; // 2. 配置PB0为推挽输出最大速度50MHz GPIOB-CRL ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0); GPIOB-CRL | GPIO_CRL_MODE0_0; while(1) { GPIOB-ODR ^ GPIO_ODR_ODR0; // 翻转PB0 Delay(500000); } }4.2 标准外设库版本推荐#include stm32f10x.h #include stm32f10x_gpio.h #include stm32f10x_rcc.h void Delay(__IO uint32_t nCount) { while(nCount--) {} } int main(void) { GPIO_InitTypeDef GPIO_InitStructure; // 1. 初始化时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 2. 配置GPIO GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); while(1) { GPIO_WriteBit(GPIOB, GPIO_Pin_0, (BitAction)(1-GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_0))); Delay(500000); } }4.3 HAL库版本CubeMX生成#include stm32f1xx_hal.h void SystemClock_Config(void); static void MX_GPIO_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); while(1) { HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0); HAL_Delay(500); } } void SystemClock_Config(void) { // 时钟配置代码由CubeMX生成 } void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); }5. 常见问题与解决方案5.1 LED不亮的排查步骤检查硬件连接确认LED极性正确用万用表测量电压检查限流电阻值验证软件配置确认已开启GPIO时钟检查GPIO模式设置验证引脚映射部分引脚有复用功能调试技巧使用调试器单步执行查看寄存器值特别是RCC和GPIO相关寄存器尝试更换其他GPIO引脚5.2 时钟配置要点STM32F103的时钟树较为复杂初学者常因时钟配置不当导致外设无法工作。关键点默认使用内部8MHz RC振荡器HSI外设时钟需要单独使能APB1/APB2GPIO位于APB2总线最高72MHz典型时钟初始化代码void RCC_Configuration(void) { RCC_DeInit(); RCC_HSEConfig(RCC_HSE_ON); while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) RESET); RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); RCC_PLLCmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while(RCC_GetSYSCLKSource() ! 0x08); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); }6. 进阶技巧从闪烁到呼吸灯6.1 软件延时优化基础延时函数的不足占用CPU资源延时精度受优化等级影响改进方案void Delay_us(uint32_t us) { uint32_t ticks us * (SystemCoreClock / 1000000); uint32_t start DWT-CYCCNT; while((DWT-CYCCNT - start) ticks); } void DWT_Init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; }6.2 PWM实现呼吸灯利用TIM4通道1输出PWMvoid PWM_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // GPIO配置 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); // 时基配置 TIM_TimeBaseStructure.TIM_Period 255; // 8位分辨率 TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM4, TIM_TimeBaseStructure); // PWM模式配置 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM4, TIM_OCInitStructure); TIM_Cmd(TIM4, ENABLE); TIM_CtrlPWMOutputs(TIM4, ENABLE); } void Breath_LED(void) { static uint8_t dir 0, val 0; TIM4-CCR1 val; if(dir) { if(val-- 0) dir 0; } else { if(val 255) dir 1; } Delay_ms(10); }7. 项目扩展构建你的第一个嵌入式系统完成LED控制后可以尝试以下扩展添加按键控制LED模式通过串口命令控制LED实现LED动画效果如流水灯加入光敏电阻实现自动亮度调节使用RTOS管理多个LED任务推荐学习路径掌握GPIO后学习外部中断按键了解定时器实现精确时序控制学习USART实现调试输出掌握ADC读取传感器数据最后尝试RTOS如FreeRTOS
从零到一:手把手教你用STM32F103点亮第一个LED(附完整代码与避坑指南)
发布时间:2026/6/13 3:51:03
从零到一手把手教你用STM32F103点亮第一个LED附完整代码与避坑指南1. 嵌入式开发入门为什么选择STM32F103对于刚接触嵌入式开发的初学者来说STM32F103系列微控制器是一个绝佳的起点。这款基于ARM Cortex-M3内核的MCU凭借其出色的性价比和丰富的生态资源已经成为嵌入式领域的国民芯片。STM32F103VBT6的核心优势72MHz主频性能足够应对大多数嵌入式场景丰富的外设资源GPIO、USART、SPI、I2C、ADC等完善的开发工具链和社区支持低功耗设计适合电池供电设备工业级温度范围-40℃~85℃我第一次接触STM32是在大学电子设计竞赛时当时被其简洁的开发方式和强大的性能所震撼。相比传统的51单片机STM32的库函数开发模式让硬件操作变得异常简单。2. 开发环境搭建Keil MDK-ARM实战指南2.1 工具链安装所需软件清单Keil MDK-ARM建议版本5.30STM32F1xx Device Family PackST-Link驱动串口调试工具如Putty安装步骤# 以管理员身份运行MDK安装包 # 安装完成后注册社区版有32KB代码限制 # 通过Pack Installer安装STM32F1xx_DFP2.2 工程创建关键步骤新建Project选择STM32F103VB作为目标器件配置工程属性时特别注意Target选项卡勾选Use MicroLIB简化printf重定向Output选项卡勾选Create HEX FileDebug选项卡选择ST-Link Debugger注意初次使用时常犯的错误是忘记安装对应的Device Family Pack导致无法选择目标器件。3. GPIO深度解析点亮LED的8种姿势3.1 STM32的GPIO架构STM32的每个GPIO端口有2个32位配置寄存器CRL, CRH2个32位数据寄存器IDR, ODR1个32位置位/复位寄存器BSRR1个16位复位寄存器BRR1个32位锁定寄存器LCKRGPIO工作模式对比表模式描述典型应用输入浮空浮空输入电平不确定按键检测输入上拉内部上拉电阻使能节省外部元件输入下拉内部下拉电阻使能节省外部元件模拟输入ADC采样输入传感器信号采集开漏输出只能输出低电平或高阻态I2C总线推挽输出可输出高/低电平LED驱动复用功能外设控制引脚USART, SPI等开漏复用开漏模式的复用功能I2C等3.2 LED电路设计要点典型LED驱动电路// LED阳极接3.3V阴极接GPIO低电平点亮 // 需串联限流电阻计算公式 R (Vcc - Vled) / Iled其中Vcc电源电压3.3VVledLED正向压降通常1.8-2.2VIled期望电流通常5-20mA4. 代码实战三种方式控制LED4.1 寄存器版本最底层#include stm32f10x.h void Delay(uint32_t nCount) { for(; nCount ! 0; nCount--); } int main(void) { // 1. 开启GPIOB时钟 RCC-APB2ENR | RCC_APB2ENR_IOPBEN; // 2. 配置PB0为推挽输出最大速度50MHz GPIOB-CRL ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0); GPIOB-CRL | GPIO_CRL_MODE0_0; while(1) { GPIOB-ODR ^ GPIO_ODR_ODR0; // 翻转PB0 Delay(500000); } }4.2 标准外设库版本推荐#include stm32f10x.h #include stm32f10x_gpio.h #include stm32f10x_rcc.h void Delay(__IO uint32_t nCount) { while(nCount--) {} } int main(void) { GPIO_InitTypeDef GPIO_InitStructure; // 1. 初始化时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 2. 配置GPIO GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); while(1) { GPIO_WriteBit(GPIOB, GPIO_Pin_0, (BitAction)(1-GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_0))); Delay(500000); } }4.3 HAL库版本CubeMX生成#include stm32f1xx_hal.h void SystemClock_Config(void); static void MX_GPIO_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); while(1) { HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0); HAL_Delay(500); } } void SystemClock_Config(void) { // 时钟配置代码由CubeMX生成 } void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); }5. 常见问题与解决方案5.1 LED不亮的排查步骤检查硬件连接确认LED极性正确用万用表测量电压检查限流电阻值验证软件配置确认已开启GPIO时钟检查GPIO模式设置验证引脚映射部分引脚有复用功能调试技巧使用调试器单步执行查看寄存器值特别是RCC和GPIO相关寄存器尝试更换其他GPIO引脚5.2 时钟配置要点STM32F103的时钟树较为复杂初学者常因时钟配置不当导致外设无法工作。关键点默认使用内部8MHz RC振荡器HSI外设时钟需要单独使能APB1/APB2GPIO位于APB2总线最高72MHz典型时钟初始化代码void RCC_Configuration(void) { RCC_DeInit(); RCC_HSEConfig(RCC_HSE_ON); while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) RESET); RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); RCC_PLLCmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while(RCC_GetSYSCLKSource() ! 0x08); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); }6. 进阶技巧从闪烁到呼吸灯6.1 软件延时优化基础延时函数的不足占用CPU资源延时精度受优化等级影响改进方案void Delay_us(uint32_t us) { uint32_t ticks us * (SystemCoreClock / 1000000); uint32_t start DWT-CYCCNT; while((DWT-CYCCNT - start) ticks); } void DWT_Init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; }6.2 PWM实现呼吸灯利用TIM4通道1输出PWMvoid PWM_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // GPIO配置 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); // 时基配置 TIM_TimeBaseStructure.TIM_Period 255; // 8位分辨率 TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM4, TIM_TimeBaseStructure); // PWM模式配置 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM4, TIM_OCInitStructure); TIM_Cmd(TIM4, ENABLE); TIM_CtrlPWMOutputs(TIM4, ENABLE); } void Breath_LED(void) { static uint8_t dir 0, val 0; TIM4-CCR1 val; if(dir) { if(val-- 0) dir 0; } else { if(val 255) dir 1; } Delay_ms(10); }7. 项目扩展构建你的第一个嵌入式系统完成LED控制后可以尝试以下扩展添加按键控制LED模式通过串口命令控制LED实现LED动画效果如流水灯加入光敏电阻实现自动亮度调节使用RTOS管理多个LED任务推荐学习路径掌握GPIO后学习外部中断按键了解定时器实现精确时序控制学习USART实现调试输出掌握ADC读取传感器数据最后尝试RTOS如FreeRTOS