STM32F103C8T6标准外设库例程详解:从点灯到串口收发

发布时间:2026/11/4 10:02:03
STM32F103C8T6标准外设库例程详解:从点灯到串口收发 简介这份资源是面向嵌入式初学者与单片机开发者的STM32F103C8T6参考例程代码集基于ARM Cortex-M3内核围绕芯片常用外设给出可直接编译运行的工程示例帮助读者跨越从寄存器手册到实际编程的门槛。内容覆盖ADC采样配置与通道读取、GPIO输入输出与中断、多路UART串口收发、按键检测与消抖、SPI主从通信、SysTick及通用定时器的延时计数与PWM输出并包含UCOS实时操作系统的任务创建、信号量与互斥锁等移植示例便于按模块逐个研读。压缩包共1616个文件以c源文件与h头文件为主体辅以Keil工程文件、hex烧录文件、map与lst编译清单及少量说明文档整体约16.54MB目录按功能分工程组织检索方便。目前已有9168人学习下载适合作为边调试边理解硬件资源的实践素材为后续项目开发积累底层驱动经验。1. 从一块最小系统板说起这套 STM32F103C8T6 例程到底能帮你省多少事手头有一块 STM32F103C8T6 最小系统板LED 接在 PC13串口想接 PA9/PA10但打开官方标准外设库一看光是建工程、配时钟、写启动文件就够折腾半天。这套开发参考例程代码解决的正是这个场景它把 C 语言嵌入式开发里最常打交道的片上外设——GPIO、USART、定时器、ADC、I2C、SPI、EXTI、看门狗——按模块拆成一个个能独立编译、独立烧录的最小工程每个工程只做一件事代码短、注释密、依赖少。适合刚接触单片机代码、想从“点灯”过渡到“能改能调”的开发者也适合熟手拿来当外设配置的速查模板。下面按“能干什么 → 怎么跑起来 → 怎么改”的顺序把这份例程拆开讲透。2. 工程结构与技术选型为什么是标准外设库而不是 HAL2.1 目录怎么分层每个文件夹负责什么这套例程的组织方式很朴素但分层清晰基本遵循“一个外设一个工程”的原则。拿到压缩包解压后常见的顶层结构是这样STM32F103C8T6_Examples/ ├── Libraries/ │ ├── CMSIS/ # 内核头文件、system_stm32f10x.c │ └── STM32F10x_StdPeriph_Driver/ │ ├── inc/ # 外设驱动头文件 │ └── src/ # 外设驱动源文件 ├── Start/ │ ├── startup_stm32f10x_md.s # 中容量启动文件 │ └── stm32f10x_conf.h # 外设使能裁剪配置 ├── User/ │ ├── main.c │ ├── stm32f10x_it.c # 中断服务函数集中放这里 │ └── stm32f10x_it.h ├── Hardware/ # 板级外设封装如 led.c、usart.c └── Project/ └── MDK-ARM/ # Keil 工程文件 .uvprojxLibraries放的是芯片原厂提供的标准外设库和 CMSIS 内核支持属于“不要动”的部分Start里最关键的是启动文件C8T6 属于中容量Medium Density器件必须选startup_stm32f10x_md.s选错成hd或ld会在链接阶段报一堆符号找不到User是业务入口Hardware是我个人最推荐保留的一层把 LED、按键、串口这些板级东西封装成xxx_init()函数主函数里只调用初始化换板子时只改这一层。Project下按 IDE 分目录Keil 工程里已经把上述源文件加进分组打开就能编译。提示不同例程可能把Hardware叫BSP或直接平铺在User下判断标准只有一个——看main.c里调用的初始化函数来自哪个文件那个文件所在目录就是板级层。2.2 标准外设库 vs HAL这套代码的取舍STM32F103C8T6 是 Cortex-M3 内核、64KB Flash、20KB RAM 的经典型号这套例程用的是 ST 标准外设库StdPeriph Library不是现在 CubeMX 默认生成的 HAL。原因很实际标准库的寄存器操作更贴近硬件一个GPIO_Init()背后就是 CRL/CRH 寄存器的位操作读代码时能直接对上参考手册代码体积也小一个点灯工程编译出来通常几 KB20KB RAM 的芯片跑起来毫无压力。HAL 抽象层厚、回调多对初学者反而容易“只知调用不知原理”。代价是标准库已经停止更新新芯片不支持。但对 F103 这个存量巨大的型号来说标准库生态成熟、例程多、资料全拿来练手和做小产品都够用。技术栈上就是纯 C 语言 标准库 Keil MDK或 IAR不依赖 RTOS全部裸机跑主循环加中断这也是嵌入式入门最该先吃透的模型。2.3 时钟树与启动流程上电后代码到底跑了什么很多人改例程时卡在“为什么我的延时不准”根子就在时钟配置。F103C8T6 外部一般挂 8MHz 晶振经 PLL 倍频到 72MHz 作为系统时钟。标准库工程里这段逻辑在system_stm32f10x.c的SystemInit()中启动文件复位后先调它再跳main()。典型配置代码长这样// system_stm32f10x.c 中的关键片段简化 #define SYSCLK_FREQ_72MHz 72000000 static void SetSysClockTo72(void) { RCC-CR | ((uint32_t)RCC_CR_HSEON); // 打开外部高速晶振 while (!(RCC-CR RCC_CR_HSERDY)); // 等待 HSE 稳定 RCC-CFGR | RCC_CFGR_PLLMULL9; // 8MHz * 9 72MHz RCC-CFGR | RCC_CFGR_PLLSRC; // PLL 源选 HSE RCC-CR | RCC_CR_PLLON; // 使能 PLL while (!(RCC-CR RCC_CR_PLLRDY)); // 等待 PLL 锁定 RCC-CFGR | RCC_CFGR_SW_PLL; // 切换系统时钟到 PLL while ((RCC-CFGR RCC_CFGR_SWS) ! RCC_CFGR_SWS_PLL); }这段代码的逻辑是先让外部晶振起振并等待稳定标志再配置 PLL 的倍频系数和时钟源等 PLL 锁定后把系统时钟切过去。参数上PLLMULL9对应 9 倍频如果板子焊的是 12MHz 晶振这里就得改成PLLMULL6否则超频到 108MHz 会跑飞。改完时钟SystemCoreClock变量会同步更新SysTick延时函数靠它算节拍所以延时不准先查这里别急着怀疑延时函数本身。3. 核心外设例程拆解从点灯到串口收发的完整链路3.1 GPIO 输出一个 LED 例程里的四个必改点点灯是所有例程的第一课但真正要改对得看清四件事时钟、引脚、模式、电平。以 PC13 接 LED低电平点亮为例#include stm32f10x.h void LED_Init(void) { GPIO_InitTypeDef gpio; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // 1. 开 GPIOC 时钟 gpio.GPIO_Pin GPIO_Pin_13; // 2. 选中 13 号引脚 gpio.GPIO_Mode GPIO_Mode_Out_PP; // 3. 推挽输出 gpio.GPIO_Speed GPIO_Speed_50MHz; // 输出速度 GPIO_Init(GPIOC, gpio); } int main(void) { LED_Init(); while (1) { GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 低电平点亮 for (volatile int i 0; i 500000; i); GPIO_SetBits(GPIOC, GPIO_Pin_13); // 高电平熄灭 for (volatile int i 0; i 500000; i); } }逻辑说明第一步开时钟是新手最容易漏的APB2 总线上 GPIOC 不使能后面写寄存器全是无效操作现象就是“代码没错但灯不亮”。第二步GPIO_Pin_13是位掩码多个引脚用按位或组合。第三步GPIO_Mode_Out_PP是推挽输出驱动 LED 够用如果要接需要上拉的器件改成GPIO_Mode_Out_OD开漏。参数GPIO_Speed_50MHz影响边沿陡峭程度点灯用 2MHz 也行但养成按实际需求选的习惯。主循环里的volatile不能省否则编译器优化后空循环可能被整个删掉延时直接消失——这是血泪经验。3.2 USART 串口轮询发送与中断接收怎么配串口是调试和通信的主力例程一般给两个版本轮询发送和中断接收。先看初始化以 USART1、PA9 发送、PA10 接收、115200 波特率为例void USART1_Init(uint32_t baud) { GPIO_InitTypeDef gpio; USART_InitTypeDef usart; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); gpio.GPIO_Pin GPIO_Pin_9; // TX gpio.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽 gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); gpio.GPIO_Pin GPIO_Pin_10; // RX gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; // 浮空输入 GPIO_Init(GPIOA, gpio); usart.USART_BaudRate baud; usart.USART_WordLength USART_WordLength_8b; usart.USART_StopBits USART_StopBits_1; usart.USART_Parity USART_Parity_No; usart.USART_HardwareFlowControl USART_HardwareFlowControl_None; usart.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, usart); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 开接收中断 NVIC_EnableIRQ(USART1_IRQn); // 使能 NVIC 通道 USART_Cmd(USART1, ENABLE); }参数说明TX 引脚必须配成GPIO_Mode_AF_PP复用推挽配成普通输出发不出波形RX 配浮空输入或上拉输入都行。USART_WordLength_8b加无校验是 8N1 的常见组合和上位机串口助手默认设置一致。中断接收的关键是USART_IT_RXNE接收寄存器非空和 NVIC 通道使能两者缺一不可。中断服务函数里必须读一次USART_ReceiveData()把 RXNE 标志清掉否则会反复进中断void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { uint8_t ch USART_ReceiveData(USART1); // 读数据即清标志 USART_SendData(USART1, ch); // 原样回显 while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); } }这段回显逻辑适合验证链路通不通。实际项目里接收数据一般塞进环形缓冲区主循环再取出来解析避免在中断里做耗时处理——中断里只做搬运这是裸机编程的基本纪律。3.3 定时器与 SysTick延时和定时的两种做法例程里通常有两种定时手段SysTick做毫秒级延时通用定时器做精确周期中断。SysTick配置简单void Delay_Init(void) { SysTick_Config(SystemCoreClock / 1000); // 1ms 中断一次 } void Delay_ms(uint32_t ms) { while (ms--) { while (!(SysTick-CTRL SysTick_CTRL_COUNTFLAG_Msk)); } }SystemCoreClock / 1000表示每 1ms 计满一次COUNTFLAG置位表示计数到零。这种忙等延时简单但占 CPU适合初始化阶段。需要周期性任务时用 TIM2 更合适配置预分频PSC和自动重装ARR中断频率 72MHz / ((PSC1)*(ARR1))。比如要 1kHz 中断PSC 取 71、ARR 取 999算下来正好 1ms 一次。参数改错最典型的症状是“中断进得太快导致主循环卡死”这时先拿计算器把分频和重装值重新算一遍。3.4 ADC 与 I2C/SPI模拟采集和总线通信的配置要点ADC 例程一般演示单通道规则转换配置顺序是开 ADC 时钟、配 GPIO 为模拟输入、设置规则组通道和采样时间、使能 ADC、校准、启动转换。采样时间这个参数容易被忽略采样时间太短高阻抗信号源采不准读数跳动大把ADC_SampleTime_55Cycles5往上调一档往往就稳了。I2C 和 SPI 例程则侧重主机模式读写外部器件I2C 要注意上拉电阻和时钟频率100kHz 标准模式、400kHz 快速模式SPI 要注意 CPOL/CPHA 四种模式必须和从机手册对上对不上就是“发出去的数据从机不认”的经典翻车现场。4. 编译、烧录与调试把第一个工程跑起来的完整步骤4.1 Keil 工程配置器件型号、宏定义和下载器打开Project/MDK-ARM下的工程文件后先确认三处配置。第一处是 Device 页必须选STM32F103C8选成C6或CB虽然 Flash 大小不同但通常也能跑不过保险起见按实际型号选。第二处是 C/C 页的宏定义标准库工程一般要预定义USE_STDPERIPH_DRIVER, STM32F10X_MD前者告诉stm32f10x.h去包含标准库头文件后者告诉库当前是中容量器件缺了会编译报错或行为异常。第三处是 Debug 页下载器选 ST-Link 或 J-Link勾选Reset and Run烧完自动复位运行。# 如果用命令行 OpenOCD 烧录无 Keil 环境时的替代方案 openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \ -c program build/demo.hex verify reset exit这条命令的含义是加载 ST-Link 接口配置和 F1 系列目标配置把 hex 文件烧进去并校验最后复位退出。verify不能省能提前发现烧录不完整的问题。4.2 上电自检灯不亮、串口无输出的排查顺序烧录成功后如果现象不对按固定顺序排查效率最高。先看电源3.3V 是否正常、BOOT0 是否接地接地才从 Flash 启动。再看复位NRST 引脚是否被拉低。然后看时钟晶振是否起振用示波器或万用表测 OSC_OUT 有无波形。最后看代码初始化函数是否被调用、时钟是否使能。串口无输出时先短接 TX/RX 做回环测试确认硬件通路再核对波特率和引脚复用配置。这个顺序能覆盖八成以上的“第一次跑不通”。4.3 用串口打印做黑匣子没有仿真器时的调试手段不是每个人都有仿真器串口打印就是最便宜的黑匣子。在关键分支打一行状态输出比盯着代码猜快得多。封装一个简单的打印函数void Log(const char *tag, int val) { while (*tag) { USART_SendData(USART1, *tag); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); } // 简单输出整数值够定位问题即可 char buf[12]; int i 0; if (val 0) buf[i] 0; while (val 0) { buf[i] 0 val % 10; val / 10; } while (i--) { USART_SendData(USART1, buf[i]); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); } }在main里Log(init ok , 1);这样打点能快速判断程序卡在哪个初始化之前。注意打印本身有耗时别放在高频中断里。5. 避坑与常见问题那些让例程跑不起来的细节5.1 启动文件选错导致链接报错现象编译通过链接时报Undefined symbol SystemInit或大量中断向量相关符号缺失。原因启动文件容量等级和芯片不匹配C8T6 必须用startup_stm32f10x_md.s误用了ld或hd版本。解决在工程分组里移除错误启动文件重新添加 md 版本并确认STM32F10X_MD宏已定义。5.2 时钟没使能外设“假装工作”现象GPIO 配置代码完全正确但引脚无输出或串口初始化后发不出数据。原因忘记调用RCC_APB2PeriphClockCmd()使能对应外设时钟寄存器写入被硬件忽略。解决每个外设初始化函数第一行就开时钟形成肌肉记忆排查时用调试器看 RCC_APB2ENR 对应位是否为 1。5.3 中断里没清标志导致反复进入现象程序看起来卡死主循环不执行或串口疯狂输出乱码。原因中断服务函数没有清除中断标志退出后立刻再次触发。解决USART 读数据清 RXNE定时器调TIM_ClearITPendingBit()EXTI 调EXTI_ClearITPendingBit()每个中断入口都确认清标志动作存在。5.4 延时函数被编译器优化掉现象点灯闪烁频率快得看不清或空循环延时完全失效。原因空循环变量没加volatile编译器认为无副作用直接删除。解决循环计数变量声明为volatile或改用SysTick做延时从根上避免依赖编译器行为。5.5 串口乱码先查波特率和时钟源现象串口助手收到乱码。原因波特率不匹配或系统时钟不是预期的 72MHz 导致实际波特率偏移。解决先确认上位机波特率和代码一致再核对晶振频率与 PLL 倍频配置最后检查USART_Init前 USART 时钟是否使能。6. 二次开发改造把例程变成自己项目的骨架拿到这套例程最忌讳的是在每个工程里东改西改最后版本散落一地。我一般做三件事把它变成可复用的骨架。第一抽一个公共模板工程保留Libraries、Start、User三层把Hardware层做成空壳新增外设时只往里加xxx.c/xxx.h主函数只负责调用初始化。第二统一板级引脚定义到一个头文件比如board_config.h里用宏写死LED_PIN、USART_TX_PIN换板子只改这一个文件避免在多个源文件里全局搜索替换。第三给每个外设封装“初始化 读 写”三个接口中断服务函数集中放stm32f10x_it.c业务逻辑一律不进中断。验证改造是否成功有个简单办法新建一个空工程只包含模板骨架编译应无警告通过然后依次加入 LED、串口、定时器模块每加一个烧录验证一次任何一步现象不对就回退。这种小步验证比一次性堆完再调试省时间得多。改造点改造前改造后收益引脚定义散落各文件集中到 board_config.h换板只改一处外设接口直接操作寄存器封装 init/read/write业务代码不碰硬件中断处理混在业务里集中 it.c只做搬运降低耦合好排查工程组织一例程一工程公共模板 模块追加版本可控还有一个具体技巧把SysTick延时和串口打印做成“调试开关”用宏DEBUG_LOG包起来发布时关掉宏打印代码整体不参与编译既不占 Flash 也不用删代码。我吃过一次亏——项目后期为了省空间手动删打印语句结果误删了一行状态判断排查了半天。从那以后我每次做裁剪都强制走一遍“改宏 → 全量编译 → 烧录验证现象”的流程不再手删代码。希望这套例程的拆解能帮到你先从点灯和串口两个工程跑通剩下的外设照着同样的套路往下推就行。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询