十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32F103标准外设库V3.5.0实战:从点灯到串口

STM32F103标准外设库V3.5.0实战:从点灯到串口 简介STM32F10x_StdPeriph_Lib_V3.5.0是意法半导体官方发布的STM32F10x系列标准外设库面向Cortex-M3内核的嵌入式开发者将GPIO、定时器、串口、ADC、I2C、SPI等外设的底层寄存器操作封装为简洁API显著降低驱动编写门槛。压缩包内含1007个文件体积36.39MB以C源文件348个和头文件275个为主同时包含IAR、Keil等常见IDE的工程文件、链接脚本、示例代码以及PDF/CHM格式的用户手册便于直接查阅和移植。已有812人学习下载尤其适合刚接触STM32或需要快速搭建外设驱动框架的开发者。通过对照库内示例工程和文档说明可以掌握外设初始化与中断配置的标准写法并在此基础上根据实际项目需求进行裁剪和优化从而提升嵌入式开发效率。1. 项目背景与核心价值为什么今天还要聊标准外设库 V3.5.0拿到“STM32F10x_StdPeriph_Lib_V3.5.0”这个标题我第一反应是——又有人要在这条“古董技术”上踩坑了。别误会我说“古董”不是贬义。恰恰相反这个库在嵌入式圈子里活了十多年至今仍有大量存量项目跑在它上面。即便你现在用的是HAL库或者LL库大概率也绕不开它因为你可能正在维护一台老设备或者翻看一本老教材或者接手一份古老的祖传代码。先说清楚这个库是什么。ST官方早期为了让开发者快速上手STM32推了一套固件库全称是STM32F10x Standard Peripherals Library也就是标准外设库。V3.5.0是这个系列的最终版本发布于2012年左右之后ST全面转向HAL库和LL库标准库就冻结了。冻结的意思是功能不再更新但它稳定、文档齐全、资料多、案例多对于F103这颗芯片来说它到今天依然是完全可用的而且很多老工程师对它已经到了闭着眼都能写的地步。那它到底解决了什么问题很简单在寄存器操作成为噩梦之前它帮你把底层的寄存器读写封装成了一个个函数和结构体。你只需要告诉它“GPIOA的第5脚推挽输出50MHz”它就能帮你把那一串烦人的CRL、CRH、ODR寄存器配好。对于刚接触嵌入式的朋友来说这层封装简直是救命的。你不需要一开始就啃上千页的参考手册就能把LED点亮、把串口跑通然后在实际使用中逐步反过来理解寄存器到底在干什么。这个库适合谁来学习和参考如果你是刚入门STM32F103我强烈建议从标准库入手别看HAL。不是HAL不好而是标准库足够直白每个函数背后基本都对应一组寄存器操作代码逻辑一眼能看穿非常适合理解芯片的工作方式。如果你是有经验的开发者那这篇文章也能帮你快速回忆标准库的工程结构、常见坑点以及如何在新工具链比如新版Keil、GCC下重新把它跑起来。我这里说的所有内容都是围绕“STM32F10x_StdPeriph_Lib_V3.5.0”这个压缩包展开的。它能做什么、怎么把它用起来、哪里容易翻车、怎么排查都是我这几年实际摸过的经验。下面不废话直接进主题。2. 整体架构与关键设计库的目录、核心文件与底层逻辑拿到这个库之后第一件事不是打开工程而是把压缩包解开看看它长什么样。这个库的压缩包解压出来之后核心目录大概是下面这样的结构STM32F10x_StdPeriph_Lib_V3.5.0/ ├── Libraries/ │ ├── CMSIS/ │ │ ├── CM3/ │ │ └── DeviceSupport/ST/STM32F10x/ │ └── STM32F10x_StdPeriph_Driver/ │ ├── inc/ │ └── src/ ├── Project/ │ ├── STM32F10x_StdPeriph_Examples/ │ └── STM32F10x_StdPeriph_Template/ └── Utilities/这个结构看着简单但里面其实藏着两条设计主线一条是CMSIS层一条是标准外设驱动层。CMSIS是ARM官方搞的一个“芯片厂商统一接口标准”。你可以把它理解成一个中间协议层。以前各家ARM内核芯片的寄存器定义、系统初始化函数全都不一样厂家各写各的开发者和工具链厂商苦不堪言。CMSIS规定了不管你是ST还是NXP你的芯片头文件里必须有那几个固定的结构体定义必须提供SystemInit()和SysTick_Config()等标准接口。这样一来调试器、RTOS、编译器这些第三方工具只需要适配一套CMSIS就能通吃所有Cortex-M芯片。STM32F10x的CMSIS文件夹里核心文件是stm32f10x.h芯片寄存器定义和中断向量定义的头文件所有代码几乎都要包含它。system_stm32f10x.c负责SystemInit()函数完成时钟初始化也就是把芯片从默认的8MHz内部时钟切换到外部晶振、配置好PLL倍频到72MHz的过程。core_cm3.h/core_cm3.cARM官方提供的Cortex-M3内核访问层提供NVIC、SysTick、MPU等内核外设操作接口。标准外设驱动层就是我们平时说的“库函数”所在的位置。inc目录下是头文件src目录下是源文件。每个外设一个文件对比如GPIO就是stm32f10x_gpio.c/hUSART就是stm32f10x_usart.c/h。这套驱动库的设计思想是把寄存器操作封装成“初始化结构体 初始化函数 外围功能函数 中断处理辅助函数”四件套。举例来说初始化GPIO流程是固定的三连GPIO_InitTypeDef GPIO_InitStructure; // 定义初始化结构体 GPIO_InitStructure.GPIO_Pin GPIO_Pin_5; // 选择引脚 GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; // 翻转速率50MHz GPIO_Init(GPIOA, GPIO_InitStructure); // 调用初始化函数这四步背后GPIO_Init函数内部做的事是先读GPIOx的CRL/CRH寄存器当前值再根据结构体的参数对对应位做清位和置位最终写回寄存器。所以在标准库眼里所谓“初始化”本质就是一次安全的读-改-写操作。它替你避免了直接操作位域的麻烦。明白了这个库的分层设计你就能够理解为什么标准库比HAL库容易学。因为它没有那么多抽象层没有DMA、中断、时钟的多级联动配置一切都是直来直去。CMSIS管内核外设驱动管外设两者界限清晰逻辑绕的弯少。搞懂这一层之后后面你用任何芯片去看任何厂商的固件库思路都是通用的。3. 核心实操从零搭建一个标准库工程并跑通点灯说再多理论不如亲手把工程跑起来。我强烈建议你先从“空工程模板”开始而不是直接在官方Example上改。因为空工程模板要求你亲手把启动文件、系统初始化、链接脚本、头文件路径全部配好这个过程踩过一遍坑你对整个程序的启动流程和编译过程的理解会上一个台阶。3.1 准备基础文件清单以Keil MDK为例版本5.36以上旧版有些编译器对V3.5.0库有兼容问题后面我会单独讲你需要准备以下文件放进工程启动文件startup_stm32f10x_hd.s如果你的芯片是大容量产品也就是Flash在256KB以上才用HD如果是中容量如STM32F103C8T6就选startup_stm32f10x_md.s。系统初始化system_stm32f10x.c和system_stm32f10x.h。内核访问层core_cm3.c部分工程只用头文件但新版编译器建议把.c加上、core_cm3.h。外设驱动把需要的stm32f10x_xxx.c添加进去。最基础的一个工程至少要stm32f10x_gpio.c和stm32f10x_rcc.c。主头文件stm32f10x.h、stm32f10x_conf.h这个文件是外设头文件的集中管理器通过宏定义来决定要不要包含某个外设的头文件。其中stm32f10x_conf.h这个文件有点意思它本身不直接操作寄存器而是用了一堆条件编译宏来“统一include”。比如你用了GPIO就在conf文件里保证#include stm32f10x_gpio.h那句没有被注释掉。官方的默认配置是所有外设头文件全都包含这样省事但会增加编译时间精简工程时可以把没用的注释掉。3.2 Keil工程配置里最容易配错的两个地方第一是宏定义。全局宏里必须加上STM32F10X_HD或对应容量型号和USE_STDPERIPH_DRIVER两个宏。前一个宏决定stm32f10x.h里芯片型号对应的器件头文件选择哪个后一个宏告诉编译器“我在用标准外设库请把conf文件包含进来”。漏掉任何一个编译都会报出一大堆看不懂的错误比如stm32f10x.h里根本没有定义某个外设寄存器或者连GPIO_Init这个函数都没声明。第二是头文件路径。你需要把以下目录加到C/C的Include Paths里Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x Libraries\STM32F10x_StdPeriph_Driver\inc这三行缺一不可。很多新手只加了外设驱动目录结果stm32f10x.h找不到然后编译器把错误往屏幕上一甩几百行红色波浪线直接劝退。3.3 最小代码框架配好工程后代码就非常短了。下面这个是最基础的点灯程序我先把完整代码贴出来然后逐行解释它到底做了什么。#include stm32f10x.h void Delay(void) { uint32_t i; for (i 0; i 7200000; i); } int main(void) { GPIO_InitTypeDef GPIO_InitStructure; // 1. 打开GPIOA和GPIOC的时钟我这里用的是PC13点灯 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // 2. 配置PC13为推挽输出最大翻转速度2MHz GPIO_InitStructure.GPIO_Pin GPIO_Pin_13; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_2MHz; GPIO_Init(GPIOC, GPIO_InitStructure); while (1) { GPIO_SetBits(GPIOC, GPIO_Pin_13); // PC13输出高电平 Delay(); GPIO_ResetBits(GPIOC, GPIO_Pin_13); // PC13输出低电平 Delay(); } }代码里最重要的不是GPIO配置而是第一步的RCC_APB2PeriphClockCmd。STM32的外设时钟默认是关闭的你不打开GPIO的时钟后面写寄存器全是白搭。这就好比你租了一间办公室门锁着你光在门外比划里面怎么摆桌子没用得先拿钥匙开门。这个“钥匙”就是RCC时钟控制。你可能注意到我配置的是PC13不是最常见的PA1或者PB0。因为很多STM32F103最小系统板上的板载LED就接在PC13。不同板子的LED引脚不一样有的在PA0有的在PB12所以这一句一定要根据你自己的原理图来改。GPIO_Speed为什么选2MHz点LED根本不需要高速翻转2MHz完全够用还能减少电磁干扰。这个参数是给IO口内部的驱动电路设定翻转速率上限不是越高越好。延时函数我用了最简单的空循环。7200000这个数字怎么来的假设系统时钟72MHz空循环大概每3个周期执行一次那么7200000次循环大约是0.3秒。这个方法不精确但点灯验证足够了。真正需要定时的时候别用这个用SysTick或者TIM定时器。3.4 标准库中的时钟体系为什么系统时钟是72MHz跑通点灯之后建议你去做一件所有教程都不会强调的事点开system_stm32f10x.c找到SystemInit()函数一行行看懂它怎么把时钟从8MHz倍频到72MHz。这是整个标准库最值得读的代码之一比GPIO那一堆封装有价值得多。STM32F103上电之后默认使用内部8MHz RC振荡器HSI系统时钟是8MHz。如果这时候你配置一个波特率9600的串口误差可能高达百分之几通信很容易出错。所以正常使用前必须切换到外部高速晶振HSE一般评估板上是8MHz然后经过PLL锁相环9倍频得到72MHz。这个配置的代码藏在system_stm32f10x.c里的SetSysClockTo72()函数中。核心逻辑是打开HSE等待HSE就绪配置Flash等待周期为2因为72MHz下Flash读取需要插入等待周期否则CPU取指可能出错配置PLL倍频系数为9配置APB1分频器为2因为APB1最高只能跑36MHzAPB2不分频最后开启PLL并切换系统时钟源到PLL。我见过很多工程点灯能亮但一开串口就乱码排查半天发现是没调用SystemInit()或者宏定义选错了导致时钟配置走了默认的8MHz分支。所以你把时钟这块读透了后面排查问题会顺畅很多。4. 中断与定时器从点灯走向实用的两个核心外设点灯只能证明你的工程环境是通的。要让项目真正跑起来最常用到的两个外设就是中断NVIC和定时器TIM。标准库对这两个外设的处理方式非常有代表性学会了它们其他外设基本触类旁通。4.1 外部中断的标准库配置流程外部中断EXTI在标准库里的配置比HAL库直观很多。以PA0引脚的外部中断为例完整配置代码是这样的EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 1. 开启GPIOA和AFIO的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); // 2. 配置PA0为输入模式可以是上拉/下拉根据外部电路决定 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 上拉输入 GPIO_Init(GPIOA, GPIO_InitStructure); // 3. 把PA0映射到EXTI线0 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); // 4. 配置EXTI线0下降沿触发使能中断 EXTI_InitStructure.EXTI_Line EXTI_Line0; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); // 5. 配置NVICEXTI0中断通道优先级2 NVIC_InitStructure.NVIC_IRQChannel EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 2; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure);这套流程里有一步特别容易漏就是第3步GPIO_EXTILineConfig。标准库要求你在配置EXTI之前先把具体的GPIO引脚“连接”到对应的EXTI线上。如果没有这一步EXTI模块不知道你要监听的是PA0还是PB0还是PC0中断永远不会触发。这一步在HAL库中被合并到了HAL_GPIO_Init内部所以很多人切回标准库时总会卡在这里。然后是中断服务函数的写法。STM32F10x的中断服务函数名是固定的EXTI0线对应的函数名是EXTI0_IRQHandler。你需要自己实现这个函数并且记得在末尾清除中断标志位void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) ! RESET) { // 你的业务逻辑 EXTI_ClearITPendingBit(EXTI_Line0); } }注意标准库不会自动帮你清中断标志你不在服务函数里清中断就会反复进入表现为程序卡死在中断里出不来。这种问题特别隐蔽因为代码逻辑看起来完全没问题实际上程序在跑就是一直在跑中断。4.2 定时器中断让你彻底摆脱空循环延时点灯用的空循环延时在写点正经项目的时候千万别用。原因很简单CPU被占死了中断来了也响应不了实时性一塌糊涂。这时候就该轮到定时器上场了。STM32F103的TIM2是一个16位向上计数定时器挂在APB1上。标准库的定时器初始化分三步。第一步用RCC打开TIM2的时钟。第二步配置TIM_TimeBaseInitTypeDef结构体设置预分频系数PSC、自动重装载值ARR和计数模式。第三步使能更新中断配置NVIC最后启动定时器。这里最难理解的就是预分频系数和自动重装载值怎么选。公式其实就一条定时器溢出频率 定时器时钟频率 / ((PSC 1) * (ARR 1))举个例子定时器时钟是72MHz我想要1ms中断一次。先设PSC71那么计数器时钟就是72MHz / 72 1MHz也就是每1微秒计数一次。如果ARR999那么计数器从0数到999刚好1000微秒也就是1毫秒。代码如下TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseStructure.TIM_Period 999; // ARR TIM_TimeBaseStructure.TIM_Prescaler 71; // PSC TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); NVIC_InitStructure.NVIC_IRQChannel TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); TIM_Cmd(TIM2, ENABLE);然后实现中断服务函数void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { // 每毫秒进来一次 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }这里有个小细节TIM_Period为什么是999不是1000因为计数器是从0开始数的0到999正好1000个计数周期。你把ARR设成1000的话溢出周期就变成1001微秒了时间长了误差会累积如果你拿它当系统tick用一天下来能差好几秒。另外提醒一句TIM_Prescaler虽然是16位的但写大数值时一定要注意数据类型。标准库这个参数类型是uint16_t你写65536直接溢出变成0效果和没分频一样。这个问题我在实际项目中真遇到过排查了很久才发现是类型溢出。5. 串口通信最可靠的调试手段没有之一点灯和定时器都通了之后串口是你下一步必须搞定的事情。串口可能不是你做过最复杂的外设但它一定是调试效率提升最明显的一个。有了串口你就不用靠LED闪烁的节奏来猜程序跑到哪里了。5.1 USART初始化代码解析USART的初始化在标准库里也是固定的套路。以USART1为例USART1挂载在APB2上时钟频率72MHz配置成115200-8-N-1的代码USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置TX(PA9)为复用推挽输出RX(PA10)为浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); 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_Rx | USART_Mode_Tx; USART_Init(USART1, USART_InitStructure); USART_Cmd(USART1, ENABLE);其中最关键的USART_BaudRate标准库会用一个自动波特率计算逻辑根据你传入的时钟频率USART1在APB2上是72MHz所以不需要额外配置来计算对应的USARTDIV寄存器值。我见过有人在USART1上又调用一次RCC_APB1PeriphClockCmd完全没必要APB2外设的时钟源和APB1是不一样的。这类错误编译不会报错但行为就很诡异串口要么完全没输出要么波特率不对乱码。5.2 重定向printf让调试信息随手可打配置完串口你肯定希望直接用printf输出调试信息。标准库下重定向printf的方式很简单在工程里加一个串口发送字节的函数即可int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); return ch; }这里有个关键点这个函数返回前必须等待发送数据寄存器为空TXE标志置位。如果不等待连续调用printf时第二个字符可能覆盖掉第一个还没发出去的字符最终串口收到的数据就是乱的。另外一个坑是如果你的工程同时引用了MicroLIB和标准库的printf可能出现重定义错误。Keil里使用MicroLIB可以在Options for Target → Target页面勾选这样printf会走精简实现体积小很多而且重定向fputc的行为更稳定。我一般建议开启MicroLIB除非你的工程需要用到完整版标准库的某些特性。5.3 串口中断接收别再用轮询了接收串口数据新手总喜欢在主循环里轮询USART_GetFlagStatus但这有个严重问题主循环一旦被某个阻塞操作拖住串口数据就会丢失。正确做法是使用接收中断数据一到就进中断函数保存起来。void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { uint8_t data USART_ReceiveData(USART1); // 把data存入你的接收缓冲区 USART_ClearITPendingBit(USART1, USART_IT_RXNE); } }注意USART_ReceiveData这个函数不只是读数据读完之后RXNE标志位会自动清除所以不调用USART_ClearITPendingBit其实也能正常工作。但我的习惯还是加上避免依赖“读数据自动清标志”这种隐式行为代码可读性更好也防止以后移植到其他平台时踩坑。6. 常见问题与排查技巧标准库使用中的高频坑最后这部分我把这几年在使用标准库V3.5.0过程中遇到的典型问题整理一下希望能帮你少走一些弯路。6.1 编译报错core_cm3.h 与编译器版本不兼容V3.5.0的CMSIS版本比较老它针对的是ARM Compiler V5也就是AC5。现在新装的Keil MDK默认是AC6直接编译老工程会出现大量类似“unknown type name __ASM”的错误。解决办法有两个一是换成AC5编译器。在Keil的Options for Target → Target → ARM Compiler里选Version 5。这个选项只有安装了AC5才出现不是所有电脑都有没有的话需要去Keil官网下载安装。二是给老库打补丁。把core_cm3.h替换成新版CMSIS中的对应文件可以从其他新工程里拷贝。这是更彻底的办法但要注意新版core_cm3.h里可能有些函数实现和老库不完全兼容实测下来最常见的问题是NVIC_SetPriority等内联函数的行为变化一般编译能过就没大问题。6.2 程序运行不稳定Flash等待周期没配如果你用的是72MHz主频但没有配置Flash等待周期为2程序可能出现各种“幽灵bug”有时正常有时跑飞有时函数调用返回错误地址。这是因为Flash读取速度跟不上CPU取指出错。这问题在标准库里不太常见因为SystemInit()里已经配置好了但如果你自己写的时钟初始化覆盖了这部分就很容易翻车。6.3 外设时钟没打开配置毫无反应这是标准库使用中最高频的问题。80%的“GPIO没反应”、“定时器不工作”、“串口不输出”都是因为忘了调用对应外设的RCC_xxxPeriphClockCmd。排查的时候先查时钟再去查配置。STM32的外设模块和“门”的关系我前面用办公室锁门打过比方这里再强调一遍外设时钟不使能你写任何寄存器都不生效而且不会报错。这是标准库和HAL库差别最大的地方HAL会在初始化函数内部自动处理时钟标准库则完全由你手动控制。也正因如此标准库能让你对“哪些外设消耗了多少电力”有更清晰的概念。6.4 调试器连不上芯片这种情况多数不是库的问题而是芯片进入了低功耗模式或者SWD引脚被复用成GPIO了。有的开发者图省事把PB3、PA15这些默认的调试引脚配置成普通IO结果下次下载程序时调试器找不到芯片。解决办法是把BOOT0拉高上电进入ISP模式用串口擦除Flash然后再把BOOT0拉低重新上电就能恢复正常下载了。这个操作虽然是STM32的通用知识但我每次看到有人问“为什么程序能写一次就再也写不进去了”十有八九就是这个。6.5 标准库工程的启动文件选错启动文件选错是个很尴尬的错误。startup_stm32f10x_md.s对应中容量Flash 64KB-128KBstartup_stm32f10x_hd.s对应大容量Flash 256KB以上。如果选错程序可能上电后直接进HardFault或者中断完全没反应。最坑的是这种错不会稳定复现有时候程序能跑但串口中断不触发让人误以为代码写错了。判断启动文件选没选对最直接的方式是看芯片型号STM32F103C8T6是64KB Flash用mdSTM32F103ZET6是512KB Flash用hd。别凭感觉选去看数据手册。7. 写在最后标准库给我的一些体会关于STM32F10x_StdPeriph_Lib_V3.5.0能聊的实在太多。它也许已经不再是ST官方的主力推荐但只要你还在用STM32F103这颗芯片它就始终是你绕不开的一个选择。我个人在实际使用中最大的体会是标准库不是一个“已经过时、该被淘汰”的东西而是一个极好的学习工具。它把寄存器操作封装得恰到好处既保留了几乎全部底层细节又不用你手写那堆冗长的位操作。如果你能把这个库的GPIO、RCC、NVIC、TIM、USART这几个核心模块摸透那你对STM32F103这整个芯片的运转逻辑就有了一份非常扎实的底子。最后再分享一个小技巧如果你想快速验证一个功能模块能不能用别急着写完整业务逻辑先单独开一个最小工程只包含这个模块的初始化和一个简单的自检逻辑比如串口回环、定时器翻转LED。跑通了再往主工程里集成。这个习惯在很多老工程师那里已经成了肌肉记忆我也是踩过不少坑之后才明白它的价值。希望这些内容能帮你少走一些弯路。本文还有配套的精品资源点击获取
返回列表