
简介这份资料围绕STM32F103C8T6微控制器展开面向嵌入式初学者、电子竞赛选手及需要快速选型验证的工程师帮助解决从芯片特性认知到最小系统搭建的入门与参考问题。压缩包为rar格式整体约50.46MB文件总数与类型明细上游暂未提供但结合描述可知内容涉及最小系统设计资料可能包含电路图、PCB布局与固件代码示例等便于对照硬件设计与程序烧录。资源重点覆盖Cortex-M3内核、64KB Flash与20KB SRAM存储配置、GPIO及USB/CAN/USART/SPI/I2C等外设接口、多种低功耗模式以及Keil、IAR、STM32CubeIDE等开发工具和SWD/JTAG调试方式并延伸至物联网、智能家居、工业控制等应用场景。目前已有2515人学习下载适合希望系统掌握该芯片基本特性、快速上手最小系统项目的读者参考。1. STM32F103C8T6资料到底包含什么从选型到点灯的完整路径STM32F103C8T6资料这几个字在搜索引擎里每天被大量初学者和转行工程师反复输入。它背后对应的是一颗基于 ARM Cortex-M3 内核的 32 位微控制器72MHz 主频、64KB Flash、20KB SRAMLQFP48 封装通常以“最小系统板”或“蓝色开发板”的形态出现在电子市场。很多人拿到手的第一份资料是一张原理图加一个例程压缩包但真正能让项目跑起来的资料应该覆盖数据手册、参考手册、标准外设库或 HAL 库、时钟树配置、启动文件、链接脚本以及一套可复现的编译烧录流程。这篇文章面向的是手里已经有一块板子、或者正准备选型做数据采集、电机控制、串口网关这类小项目的从业者。我会把资料拆成“必须看的、可以跳过的、容易看错的”三类然后带你从零建一个能编译能烧录能点灯的工程最后把踩过的坑摊开讲。整条路径不依赖任何特定厂商的 IDE 绑定命令行和 Makefile 也能走通。2. 资料分层与最小工程搭建哪些文件真正决定编译结果2.1 数据手册、参考手册、编程手册的分工STM32F103C8T6 的官方资料通常以三份文档为核心。数据手册Datasheet管的是引脚定义、电气特性、封装尺寸、工作温度范围、外设资源数量选型和画板子时看它。参考手册Reference Manual管的是每个外设的寄存器位定义、时钟使能位、工作模式、中断向量写底层驱动时看它。编程手册Programming Manual管的是 Cortex-M3 内核指令集、NVIC 中断控制器、SysTick、内存映射调 HardFault 和写启动代码时看它。很多新手把参考手册当教程从头读到尾结果卡在时钟配置那一章就放弃了。我的建议是先看数据手册的引脚表和电气参数确认供电 2.0V 到 3.6V、每个 IO 最大灌电流 25mA 这些硬边界参考手册只查当前要用的外设章节比如要配 USART1 就翻到 USART 那一章看 CR1、BRR、SR 这几个寄存器的位含义编程手册在遇到中断优先级分组、栈溢出、总线错误时再翻。注意网上流传的“中文参考手册”版本较多章节编号和英文原版可能对不上查寄存器时以英文版页码为准中文版只用来辅助理解。2.2 标准外设库、HAL 库、LL 库的选型理由STM32F103C8T6 属于较早期的产品线官方主推过标准外设库SPL后来 ST 把重心转向 HAL 库和 LL 库。标准外设库的代码直接操作寄存器结构清晰一个GPIO_Init函数里能看到 CRL、CRH 的配置过程适合想搞懂底层的人。HAL 库抽象层次高跨系列移植方便但代码体积大一个 GPIO 初始化可能绕好几层调试时不容易一眼看到寄存器。LL 库介于两者之间提供内联函数直接写寄存器又保留了一定的可读性。对于 F103C8T6 这种资源有限的芯片如果项目对 Flash 占用敏感标准外设库或 LL 库更合适如果团队要快速在多个 STM32 系列之间切换HAL 库的生态更省事。我一般给初学者的建议是先用标准外设库把 GPIO、USART、TIM 这三个外设的手动配置走一遍理解时钟树和寄存器操作之后再按项目需要决定是否换 HAL。2.3 用 Makefile 加 ARM GCC 搭建可复现的编译环境不依赖任何图形化 IDE直接用 ARM GCC 加 Makefile 编译能让你清楚每个源文件、每个链接脚本的作用。下面是一个最小工程的目录结构和 Makefile 核心片段。# 目录结构 # project/ # ├── Makefile # ├── startup_stm32f103c8t6.s # ├── stm32f103c8t6.ld # ├── main.c # └── system_stm32f1xx.c# Makefile 核心片段 CC arm-none-eabi-gcc OBJCOPY arm-none-eabi-objcopy CPU -mcpucortex-m3 -mthumb CFLAGS $(CPU) -O0 -g -Wall -Tstm32f103c8t6.ld CFLAGS -DSTM32F103xB -DUSE_STDPERIPH_DRIVER SRCS main.c system_stm32f1xx.c startup_stm32f103c8t6.s OBJS $(SRCS:.c.o) OBJS : $(OBJS:.s.o) all: firmware.elf firmware.bin firmware.elf: $(OBJS) $(CC) $(CPU) -Tstm32f103c8t6.ld -nostartfiles -Wl,-Mapfirmware.map -o $ $^ firmware.bin: firmware.elf $(OBJCOPY) -O binary $ $ %.o: %.c $(CC) $(CFLAGS) -c -o $ $ %.o: %.s $(CC) $(CPU) -c -o $ $ clean: rm -f $(OBJS) firmware.elf firmware.bin firmware.map这段 Makefile 里-mcpucortex-m3 -mthumb指定内核和指令集-Tstm32f103c8t6.ld指定链接脚本-DSTM32F103xB是给标准外设库用的宏定义告诉头文件当前芯片型号。-nostartfiles表示启动文件由我们自己的startup_stm32f103c8t6.s提供不链接系统默认启动。firmware.map文件在排查 Flash 和 RAM 占用时非常有用编译后打开它能看到每个函数和变量被放在哪个地址段。链接脚本里要定义 Flash 起始地址0x08000000、长度 64KRAM 起始地址0x20000000、长度 20K栈顶通常设在 RAM 末尾。2.4 时钟树配置从 HSI 到 HSE 再到 PLL 的倍频路径STM32F103C8T6 复位后默认使用内部 8MHz HSI 时钟经过 PLL 倍频后最高可到 64MHz但要想跑到 72MHz必须外接 8MHz 晶振走 HSE 加 PLL 的路径。配置顺序是使能 HSE等待 HSERDY 置位使能 Flash 预取和等待周期72MHz 下需要 2 个等待周期配置 PLL 源为 HSE倍频系数 9使能 PLL等待 PLLRDY切换系统时钟源到 PLL等待 SW 状态位确认。标准外设库的SystemInit函数已经封装了这套流程但如果你用寄存器直接写漏掉 Flash 等待周期会导致取指错误程序跑飞。下面是一个寄存器版本的时钟配置片段。// 时钟配置HSE 8MHz 倍频到 72MHz RCC-CR | RCC_CR_HSEON; // 使能 HSE while(!(RCC-CR RCC_CR_HSERDY)); // 等待 HSE 稳定 FLASH-ACR | FLASH_ACR_PRFTBE; // 使能预取 FLASH-ACR ~FLASH_ACR_LATENCY; FLASH-ACR | FLASH_ACR_LATENCY_2; // 2 个等待周期 RCC-CFGR | RCC_CFGR_PLLSRC; // PLL 源选 HSE RCC-CFGR | RCC_CFGR_PLLMULL9; // 9 倍频 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); // 确认切换完成这段代码里FLASH_ACR_LATENCY_2对应 48MHz 到 72MHz 区间的等待周期如果只跑到 48MHz 以下可以设 1 个等待周期。RCC_CFGR_PLLMULL9表示 8MHz 乘以 9 得到 72MHz。切换时钟源后一定要用RCC_CFGR_SWS确认否则后续外设时钟计算会出错。APB1 总线最高 36MHzAPB2 最高 72MHz配置定时器和串口波特率时要根据实际总线频率算分频值。3. 外设驱动落地GPIO、USART、TIM 的寄存器级操作3.1 GPIO 输出模式配置与点灯验证点灯是验证工具链和时钟配置是否正确的第一道关卡。STM32F103C8T6 的 GPIO 端口时钟挂在 APB2 总线上使用前必须使能对应端口的时钟。每个 GPIO 端口有 CRL 和 CRH 两个配置寄存器CRL 管 0 到 7 号引脚CRH 管 8 到 15 号引脚每个引脚占 4 个位低两位是 MODE高两位是 CNF。推挽输出模式下MODE 设为 11输出模式最大 50MHzCNF 设为 00。下面以 PC13 接 LED 为例。// PC13 推挽输出点亮 LED RCC-APB2ENR | RCC_APB2ENR_IOPCEN; // 使能 GPIOC 时钟 GPIOC-CRH ~(0xF 20); // 清除 PC13 配置位 GPIOC-CRH | (0x3 20); // MODE11, CNF00 推挽输出 50MHz GPIOC-BRR GPIO_BRR_BR13; // PC13 输出低电平点亮 LEDRCC_APB2ENR_IOPCEN是 APB2 使能寄存器里的 GPIOC 位不同端口的位号不同写之前查参考手册的 RCC 章节。GPIOC-CRH的 20 到 23 位对应 PC13先清零再置位避免影响其他引脚。BRR是位复位寄存器写 1 对应引脚输出低电平BSRR是位设置寄存器写 1 输出高电平。如果 LED 接的是高电平点亮就把BRR换成BSRR。点灯不亮时先量 PC13 对地电压如果是 3.3V 说明输出高检查 LED 极性如果是 0V 但灯不亮检查限流电阻和 LED 是否损坏如果电压在 1V 到 2V 之间浮动可能是引脚被复用功能占用检查是否有其他外设使能了 PC13 的复用时钟。3.2 USART1 中断收发波特率计算与环形缓冲区USART1 挂在 APB2 总线上时钟频率 72MHz。波特率寄存器 BRR 的计算公式是BRR fPCLK / (16 * 波特率)整数部分写高 12 位小数部分写低 4 位。以 115200 为例72000000 / (16 * 115200) 39.0625整数部分 39 即 0x27小数部分 0.0625 乘以 16 等于 1所以 BRR 值为 0x271。配置步骤是使能 USART1 和 GPIOA 时钟PA9 配为复用推挽输出PA10 配为浮空输入或上拉输入设置 BRR使能接收中断使能 USART。下面是一个带环形缓冲区的接收中断处理片段。// USART1 接收环形缓冲区 #define RX_BUF_SIZE 128 volatile uint8_t rx_buf[RX_BUF_SIZE]; volatile uint16_t rx_head 0, rx_tail 0; void USART1_IRQHandler(void) { if (USART1-SR USART_SR_RXNE) { // 接收数据寄存器非空 uint8_t data USART1-DR; // 读数据自动清标志 uint16_t next (rx_head 1) % RX_BUF_SIZE; if (next ! rx_tail) { // 缓冲区未满 rx_buf[rx_head] data; rx_head next; } } if (USART1-SR USART_SR_ORE) { // 溢出错误 (void)USART1-DR; // 读 DR 清溢出 } }USART_SR_RXNE是接收非空标志读DR寄存器会同时清除这个标志。环形缓冲区用head和tail两个索引head在中断里更新tail在主循环里更新判断满的条件是(head 1) % SIZE tail这样会浪费一个字节空间但省去了额外的计数变量。USART_SR_ORE是溢出错误标志如果中断处理太慢导致数据覆盖这个位会置位必须读一次DR才能清除否则中断会反复触发。主循环里取数据时先判断tail ! head然后读rx_buf[tail]并更新tail。3.3 TIM2 定时中断预分频与自动重装载值的计算TIM2 挂在 APB1 总线上APB1 时钟为 36MHz但定时器时钟在 APB1 分频系数不为 1 时会倍频所以 TIM2 的实际时钟是 72MHz。定时中断周期公式是T (PSC 1) * (ARR 1) / 72000000。要得到 1ms 中断可以设 PSC 为 71ARR 为 999这样(711)*(9991)/72000000 0.001秒。配置步骤是使能 TIM2 时钟设置 PSC 和 ARR使能更新中断使能计数器。下面是一个 1ms 定时中断的配置片段。// TIM2 1ms 定时中断 RCC-APB1ENR | RCC_APB1ENR_TIM2EN; // 使能 TIM2 时钟 TIM2-PSC 71; // 预分频 72 分频 TIM2-ARR 999; // 自动重装载 1000 计数 TIM2-DIER | TIM_DIER_UIE; // 使能更新中断 TIM2-CR1 | TIM_CR1_CEN; // 使能计数器 NVIC_EnableIRQ(TIM2_IRQn); // 使能 NVIC 中断 void TIM2_IRQHandler(void) { if (TIM2-SR TIM_SR_UIF) { // 更新中断标志 TIM2-SR ~TIM_SR_UIF; // 清除标志 // 用户代码每 1ms 执行一次 } }TIM2-PSC和TIM2-ARR都是 16 位寄存器写入后在下一次更新事件时生效。TIM_DIER_UIE是更新中断使能位TIM_SR_UIF是更新中断标志必须在中断服务函数里手动清除否则会反复进入中断。NVIC 的优先级分组默认是 0所有中断优先级都是 0如果同时使用多个中断建议在main开头调用NVIC_PriorityGroupConfig设置分组再给每个中断分配抢占优先级和子优先级。4. 避坑与排查烧录失败、时钟跑飞、串口乱码的现场记录4.1 烧录器连不上目标芯片现象ST-Link 或 J-Link 在 IDE 里提示“No target connected”或“Cannot access target”。原因通常有三种一是芯片进入了读保护或睡眠模式SWD 引脚被禁用二是供电不足目标板只靠烧录器的 3.3V 供电电流不够三是复位电路或 BOOT 引脚状态不对。解决方法是先检查目标板是否独立供电量 VDD 和 VSS 之间电压是否在 2.0V 到 3.6V然后把 BOOT0 拉低、BOOT1 拉低确保从 Flash 启动如果仍然连不上用烧录工具的“Connect under reset”模式在复位释放前建立连接再执行全片擦除解除读保护。4.2 程序下载后不运行或跑飞现象烧录成功但 LED 不闪或者运行几秒后死机。原因可能是链接脚本里 Flash 和 RAM 长度写错比如把 64K Flash 写成 128K导致代码被放到不存在的地址也可能是启动文件里的栈顶地址和链接脚本不一致栈溢出后踩到其他变量还可能是中断向量表没有正确对齐到0x08000000。解决方法是打开firmware.map文件看_estack和.isr_vector的地址是否符合预期检查启动文件里Stack_Size是否够用一般至少 0x400确认SCB-VTOR是否指向0x08000000如果程序里用了 Bootloader 跳转需要手动重定位向量表。4.3 串口输出乱码现象串口助手收到一堆乱码或者偶尔收到正确字符但大部分错误。原因通常是波特率不匹配比如代码里按 72MHz 算的 BRR但实际系统时钟还是默认的 8MHz HSI也可能是 GPIO 复用模式配错PA9 没有配成复用推挽导致输出波形畸变还可能是串口助手的地线和目标板没有共地。解决方法是先用示波器或逻辑分析仪量 PA9 的波形测一个位的时间宽度反推实际波特率检查SystemInit是否真的把时钟切到了 PLL可以在main里读RCC-CFGR的 SWS 位确认确认串口助手的数据位、停止位、校验位和代码里一致最常见的是代码用 8 数据位无校验助手设成了 7 数据位偶校验。4.4 中断优先级配置后仍然丢中断现象配置了 NVIC 优先级但高频中断仍然偶尔丢失或者两个中断嵌套时行为异常。原因是 STM32F103 的 NVIC 优先级分组默认是 04 个位全部是抢占优先级没有子优先级如果两个中断抢占优先级相同它们不能互相嵌套后到的中断会等待前一个处理完。解决方法是在main开头调用NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2)把 4 个位分成 2 位抢占和 2 位子优先级然后给实时性要求高的中断分配更高的抢占优先级同时检查中断服务函数里是否清除了所有可能的中断标志比如 USART 的 ORE 标志如果不读 DR 清除会一直触发中断把主循环卡死。4.5 Flash 写入后数据丢失现象用 Flash 模拟 EEPROM 保存参数断电后重新上电发现数据变成 0xFF 或旧值。原因是 Flash 写入前没有擦除STM32 的 Flash 只能把 1 写成 0不能把 0 写成 1写入前必须先擦除整个页也可能是写入地址没有按半字对齐F103 的 Flash 编程要求每次写入 16 位半字还可能是擦除和写入过程中被中断打断导致时序错误。解决方法是擦除时按页操作F103C8T6 的页大小是 1KB写入时用uint16_t指针地址按 2 字节对齐在擦写期间关闭全局中断或者把擦写代码放到 RAM 里执行避免从 Flash 取指时总线冲突。5. 进阶技巧用 SWD 调试口输出日志与 Flash 寿命估算当串口被业务数据占满或者板子上没有引出串口时SWD 调试口可以兼作日志输出通道。ST-Link 的 SWO 引脚通常是 PA10 旁边的第 13 脚支持单线异步输出配合 ITM 模块可以把printf重定向到调试器不占用任何 USART。配置步骤是在调试器设置里使能 SWO设置时钟频率为目标芯片主频的 1/4 左右比如 72MHz 对应 18MHz代码里使能ITM-TCR的 ITMENA 位和对应端口的使能位重定向fputc函数把字符写到ITM-PORT[0].u8。这样在 IDE 的 SWV 窗口里就能看到日志不占用串口引脚也不影响实时性。// ITM 重定向 fputc int fputc(int ch, FILE *f) { if (ITM-TCR ITM_TCR_ITMENA) { // ITM 已使能 while (ITM-PORT[0].u32 0); // 等待端口可用 ITM-PORT[0].u8 (uint8_t)ch; // 写入字符 } return ch; }Flash 寿命方面STM32F103C8T6 的 Flash 擦写次数官方标称 10000 次按页擦除。如果用来模拟 EEPROM每天擦写 10 次1KB 的页可以用 1000 天左右。要延长寿命可以做磨损均衡把参数区划分为多个页轮流写入记录当前写入页号擦除时只擦最旧的一页。另一个技巧是写前判断如果新数据和旧数据相同跳过擦写减少不必要的擦除。我一般会在参数区头部放一个 32 位的魔数和版本号上电时先校验魔数如果不对就恢复默认值避免因为 Flash 数据损坏导致程序行为异常。调试时我习惯先量电压再量波形最后看代码这个顺序帮我省下了大量反复烧录的时间。希望帮到你。本文还有配套的精品资源点击获取