
简介ALIENTEK MiniSTM32开发板例程包面向STM32初学者与嵌入式开发者基于STM32F103C8T6微控制器帮助读者从GPIO、串口、PWM、ADC到I2C、SPI等外设逐步上手解决入门阶段缺少可运行参考代码的问题。压缩包共1415个文件以676个h头文件、633个c源文件为核心辅以31个s汇编启动文件、29个uv2工程文件及若干txt说明、asm、pdf文档整体约5.3MB覆盖Keil与uCOS等典型工程结构。目前已有285人学习下载。例程涵盖LED控制、串口通信、PWM调光调速、ADC采样、I2C与SPI外设驱动等常见实验代码注释详尽便于理解底层寄存器与HAL库调用逻辑适合作为课程设计、项目原型与自学练手的参考素材。1. 从一块吃灰的 MiniSTM32 说起这套例程到底能帮你省下多少事手头有一块 ALIENTEK MiniSTM32 开发板配套的例程包却一直躺在硬盘角落里吃灰——这大概是很多嵌入式新手的真实写照。这块板子主控是 STM32F103RCT648 个引脚引出板载了 USB 串口、SD 卡座、NRF24L01 接口、OLED 屏接口资源不算豪华但胜在够用。问题在于很多人拿到板子之后不知道从哪下手寄存器手册厚得像砖头标准外设库的例程又散落在各个文件夹里点灯之后下一步该干什么完全没有头绪。这套 ALIENTEK MiniSTM32 开发板例程包本质上就是把这块板子从「点亮第一颗 LED」到「跑通综合项目」的完整路径给铺好了。它适合刚接触 STM32 的在校学生、需要快速验证方案的硬件工程师以及想从 51 单片机过渡到 32 位平台的开发者。你不需要从头去啃参考手册的每一页但也不能指望复制粘贴就能理解一切——例程是脚手架不是替你做作业的人。2. 例程包的目录结构与工程组织先看清地图再走路2.1 为什么例程要按「实验编号」而不是「外设名称」来组织打开例程包你会看到一堆以数字开头的文件夹比如「实验1 跑马灯」「实验2 按键输入」「实验3 串口实验」这样排列下去。这种编号方式看起来有点土但它背后有一条清晰的学习曲线从纯 GPIO 输出到 GPIO 输入再到中断、定时器、串口、I2C、SPI、ADC、DMA最后到综合项目。每个实验只引入一两个新概念前面的代码在后面会被反复引用和扩展。如果你按外设名称去组织很容易陷入「学一个忘一个」的循环因为外设之间是有关联的——比如串口实验里会用到的 NVIC 配置在定时器中断里还会再用一次。我一般建议新手严格按照编号顺序往下走哪怕前面几个实验看起来简单到无聊也要把代码敲一遍、改几个参数看现象变化。跳着看的代价是后面遇到问题时你分不清是外设配置错了还是中断优先级设错了。2.2 工程目录里哪些文件该动、哪些碰都不要碰以「实验3 串口实验」为例展开后的目录结构大致是这样的实验3 串口实验/ ├── CORE/ ├── HARDWARE/ │ ├── LED/ │ ├── KEY/ │ ├── USART/ ├── STM32F10x_FWLib/ ├── SYSTEM/ │ ├── delay/ │ ├── sys/ │ ├── usart/ ├── USER/ │ ├── main.c │ ├── stm32f10x_it.c │ ├── stm32f10x_conf.h └── OBJ/这里有几个关键点。STM32F10x_FWLib是 ST 官方标准外设库的源码除非你要修改库本身的 bug否则不要动。CORE里是启动文件选错了型号会导致程序跑飞。SYSTEM里的delay和sys是 ALIENTEK 自己封装的延时和系统配置usart是串口底层驱动。HARDWARE里放的是板载外设的驱动比如 LED、按键、串口。USER里的main.c是你主要写业务逻辑的地方stm32f10x_it.c放中断服务函数。OBJ是编译输出目录可以整个删掉重新编译。常见做法是新建一个实验时把上一个实验的整个文件夹复制一份改个名字然后只动USER和HARDWARE里对应的文件。这样能保证工程配置不出错。2.3 用 Keil MDK 打开工程时最容易忽略的三个编译选项工程文件是.uvprojx格式用 Keil MDK 打开后先别急着点编译。在「Options for Target」里检查三处第一Device选项卡里选的芯片型号必须是STM32F103RC选成STM32F103RB会因为 Flash 容量不同导致下载失败。第二C/C选项卡里的Define框里要有STM32F10X_HD,USE_STDPERIPH_DRIVER前者表示大容量产品后者表示使用标准外设库。第三Debug选项卡里如果用的是 ST-Link要选ST-Link Debugger如果是 J-Link 就选J-LINK / J-TRACE Cortex选错了连不上芯片。这三处配置在例程包里通常是预设好的但如果你从别人那里拷来的工程打不开或者编译报错先回来检查这里。// main.c 里最简化的跑马灯主循环 #include stm32f10x.h #include delay.h #include led.h int main(void) { delay_init(); // 延时函数初始化必须最先调用 LED_Init(); // LED 端口初始化 while(1) { GPIO_ResetBits(GPIOB, GPIO_Pin_5); // PB5 输出低电平点亮 LED delay_ms(500); // 延时 500 毫秒 GPIO_SetBits(GPIOB, GPIO_Pin_5); // PB5 输出高电平熄灭 LED delay_ms(500); } }这段代码的逻辑很直白初始化延时和 LED 端口然后在死循环里翻转 PB5 的电平。delay_init()之所以必须最先调用是因为它配置了 SysTick 定时器后面的delay_ms依赖这个配置。LED_Init()里做了两件事使能 GPIOB 的时钟把 PB5 配置为推挽输出模式。如果你把GPIO_ResetBits和GPIO_SetBits写反了LED 会一直亮着不闪这时候先检查硬件上 LED 是高电平点亮还是低电平点亮——MiniSTM32 板载的 LED 是低电平点亮所以ResetBits才是亮。3. 从串口实验切入把「黑匣子」变成可观测系统3.1 串口初始化的四个参数到底怎么算串口是嵌入式开发里最重要的调试手段没有之一。例程包里「实验3 串口实验」的核心是USART1的初始化涉及四个参数波特率、字长、停止位、校验位。以波特率 115200 为例STM32F103 的 USART1 挂在 APB2 总线上时钟频率通常是 72MHz。波特率的计算公式是波特率 fPCLK2 / (16 * USARTDIV)反推USARTDIV 72000000 / (16 * 115200) 39.0625。这个值要拆成整数部分和小数部分分别写入USART_BRR寄存器整数部分是 39小数部分 0.0625 乘以 16 等于 1所以USART_BRR的值是0x271。标准外设库的USART_Init函数会自动帮你算这个值但你要知道它算得对不对——如果串口助手上收到乱码第一个要检查的就是波特率是否匹配第二个要检查的是系统时钟配置是否正确。例程包里sys.c里的Stm32_Clock_Init函数把系统时钟配到了 72MHz如果你改成了其他频率串口初始化里的pclk2参数也要跟着改。// usart.c 里的串口初始化片段 void USART1_Init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能 GPIOA 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 使能 USART1 时钟 // PA9 复用推挽输出作为 TX 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); // PA10 浮空输入作为 RX GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate bound; 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); // 使能 USART1 }这段代码里PA9 配置为复用推挽输出是因为它要输出串口信号PA10 配置为浮空输入是因为它接收外部信号。USART_Mode同时使能了收发。如果你只需要发送不需要接收可以把USART_Mode_Rx去掉但通常调试时收发都要用。中断配置部分在例程里是单独的函数用来接收不定长数据这里不展开。3.2 用 printf 重定向把调试信息打到串口助手标准库的printf默认输出到调试窗口要让它走串口需要重写fputc函数。例程包里已经帮你写好了放在usart.c的末尾// 重定向 fputc 函数使 printf 输出到 USART1 int fputc(int ch, FILE *f) { while((USART1-SR 0X40) 0); // 等待上一个字节发送完成 USART1-DR (u8)ch; // 把当前字节写入数据寄存器 return ch; }这里用到了寄存器直接操作USART1-SR的 bit6 是TCTransmission Complete标志位为 1 表示发送完成。USART1-DR是数据寄存器写入的字节会被硬件自动发送出去。注意使用printf之前要在main.c里包含stdio.h并且在 Keil 的「Options for Target」→「Target」里勾选「Use MicroLIB」否则printf会卡死。这是一个非常经典的坑代码编译通过但一调用printf程序就停在while循环里出不来原因就是没勾 MicroLIB。3.3 串口接收中断里为什么不能放延时例程包里有一个「串口接收中断」的示例用来接收上位机发来的不定长数据。中断服务函数里只做一件事把接收到的字节存入缓冲区然后置一个标志位。主循环里检测到标志位后再去处理数据。有些新手会在中断里直接调用delay_ms或者做复杂的字符串解析结果就是串口数据丢包、程序响应变慢甚至死机。原因是中断服务函数应该尽可能短delay_ms依赖 SysTick 中断而在串口中断里等待 SysTick 中断会产生优先级冲突。常见做法是中断里只收数据主循环里处理数据。如果数据量很大可以用 DMA 加空闲中断的方式例程包里也有对应的实验。4. 避坑与排查那些让新手抓狂的典型现象4.1 程序下载后没反应LED 也不亮现象Keil 里显示下载成功但板子上的 LED 没有任何变化按复位键也没用。原因通常有三个第一启动模式跳线帽没插对。MiniSTM32 板子上有 BOOT0 和 BOOT1 两个跳线帽正常运行时 BOOT0 要接 GND如果接成了 VCC芯片会进入系统存储器启动模式不执行你下载的程序。第二下载算法选错了。在 Keil 的「Utilities」→「Settings」里Flash 算法要选STM32F10x High-density Flash选成中容量或低容量的算法会导致下载地址不对。第三程序里在main函数之前就卡死了比如SystemInit里配置外部晶振失败。解决方法是先检查跳线帽再检查 Flash 算法最后用调试器单步运行看程序停在哪里。4.2 串口助手收到乱码现象串口助手能收到数据但全是乱码或者偶尔有几个正常字符。原因波特率不匹配是最常见的比如代码里设的是 9600串口助手开的是 115200。其次是系统时钟配置错误比如外部晶振是 8MHz但Stm32_Clock_Init里按 12MHz 去算 PLL 倍频导致实际波特率偏差超过 5%。还有一种情况是串口助手的「停止位」或「校验位」设错了代码里是 1 位停止位、无校验助手却设成了 2 位停止位。解决方法是先用示波器或者逻辑分析仪测一下 TX 引脚上的波形算出实际波特率再回头检查时钟配置。4.3 按键中断触发多次现象按一次按键中断服务函数执行了好几次。原因机械按键存在抖动按下和释放的瞬间会产生多个边沿信号。例程包里「实验2 按键输入」用的是轮询方式在KEY_Scan函数里加了 10ms 的延时消抖。但如果你改成了外部中断方式硬件上又没有加 RC 滤波电路就会遇到这个问题。解决方法有两个一是在中断服务函数里加软件延时消抖但这样会拉长中断时间二是在中断里关闭该中断启动一个定时器定时器到期后再重新使能中断。例程包里没有直接给中断方式的按键实验但你可以基于定时器中断实验自己改。4.4 编译时报错「cannot open source input file stm32f10x.h」现象从别人那里拷来的工程或者自己新建的工程编译时提示找不到头文件。原因Keil 工程里的头文件搜索路径没有包含STM32F10x_FWLib/inc和USER目录。解决方法是在「Options for Target」→「C/C」→「Include Paths」里把这两个目录加进去。另外如果工程是从其他路径拷贝过来的原来的绝对路径可能失效需要重新添加。这是一个非常低级的错误但新手经常卡在这里半天。4.5 程序烧进去之后第二次就烧不进去了现象第一次下载成功第二次 Keil 提示「Flash Download failed」或者「Cannot access target」。原因程序里不小心把 SWD 或 JTAG 引脚配置成了普通 GPIO导致调试器无法连接芯片。STM32F103 的 PA13、PA14、PA15、PB3、PB4 默认是 JTAG 功能如果你在代码里把这些引脚复用成了普通 IO又没有关闭 JTAG 功能就会锁死调试接口。解决方法是按住复位键点击下载在复位释放的瞬间完成连接或者用 BOOT0 接 VCC 的方式进入系统存储器启动模式擦除芯片。预防措施是在代码里调用GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE)来关闭 JTAG 但保留 SWD这样就不会锁死。5. 把例程变成自己的代码几个能立刻用上的改造技巧5.1 用条件编译裁剪不需要的外设驱动例程包里的HARDWARE文件夹包含了很多外设驱动但你的项目可能只用其中几个。每次新建工程都把这些文件加进去编译时间会变长代码体积也会变大。我一般会在stm32f10x_conf.h里用条件编译来控制// stm32f10x_conf.h 里按需使能外设头文件 #define _USART1_ENABLE 1 #define _LED_ENABLE 1 #define _KEY_ENABLE 1 // #define _NRF24L01_ENABLE 0 // 不用无线模块就注释掉 // #define _SD_ENABLE 0 // 不用 SD 卡就注释掉然后在main.c里用#if包住对应的初始化代码。这样做的好处是当你把工程从 MiniSTM32 移植到其他板子上时只需要改#define的值不用去删代码。注意stm32f10x_conf.h是标准外设库的配置文件里面的#include语句也要跟着条件编译一起改否则编译时会报重复定义。5.2 把延时函数从 SysTick 换成定时器例程里的delay_ms用的是 SysTick 定时器精度是 1ms。如果你需要更精确的延时比如微秒级的脉冲SysTick 就不够用了。常见做法是用一个通用定时器比如 TIM2来做延时配置成 1MHz 的计数频率这样每个计数就是 1 微秒。改造步骤是在HARDWARE里新建一个timer.c初始化 TIM2写一个delay_us函数然后在需要的地方替换掉原来的delay_ms。注意TIM2 挂在 APB1 总线上时钟频率是 36MHz预分频系数设为 35 就能得到 1MHz 的计数频率。5.3 用宏定义统一管理硬件引脚例程里的引脚定义散落在各个驱动文件里比如 LED 在led.h里定义GPIOB和GPIO_Pin_5按键在key.h里定义GPIOA和GPIO_Pin_0。当你把代码移植到自己的板子上时引脚变了就要去每个文件里改。我一般会新建一个board.h把所有引脚定义集中在一起// board.h 里集中定义硬件引脚 #define LED1_GPIO_PORT GPIOB #define LED1_GPIO_PIN GPIO_Pin_5 #define LED1_GPIO_CLK RCC_APB2Periph_GPIOB #define KEY1_GPIO_PORT GPIOA #define KEY1_GPIO_PIN GPIO_Pin_0 #define KEY1_GPIO_CLK RCC_APB2Periph_GPIOA然后在led.c和key.c里引用这些宏。这样移植的时候只需要改board.h一个文件。这个习惯我从做第一个项目开始就养成了后来每次换板子十分钟就能把底层驱动改完。5.4 用串口命令解析框架替代裸 printf例程里的串口实验只是简单地把收到的数据回显出来。实际项目中你通常需要解析上位机发来的命令比如「LED ON」「LED OFF」「READ ADC」这样的字符串。我一般会在串口接收中断的基础上加一个简单的命令解析器中断里把收到的字符存入环形缓冲区主循环里检测到换行符后用strcmp匹配命令然后执行对应的操作。这个框架不复杂但能让你的调试效率提升一个档次。例程包里没有现成的命令解析代码但你可以基于「实验3 串口实验」和「实验4 外部中断实验」自己拼出来。5.5 用 Keil 的「Event Recorder」替代串口打印如果你觉得串口打印占用太多时间可以试试 Keil MDK 自带的 Event Recorder。它通过 SWD 接口输出调试信息不占用串口外设速度比串口快得多。配置方法是在「Options for Target」→「Debug」→「Settings」里勾选「Enable Event Recorder」然后在代码里调用EventRecorderInitialize和EventRecorderPrintf。这个功能在 STM32F103 上也能用但需要 ST-Link 支持。我一般在调试时序敏感的任务时会用它比如看中断服务函数的执行时间。不过 Event Recorder 的缓冲区有限大量打印时会丢数据所以只适合看关键节点。5.6 从例程到项目的最后一步建立自己的代码仓库例程包里的代码是很好的起点但不要直接在原文件夹上改。我一般会新建一个 Git 仓库把例程包里的CORE、STM32F10x_FWLib、SYSTEM作为基础库提交一次然后每做一个实验就新建一个分支改完合并到主分支。这样当你改坏了某个实验可以随时回退到上一个能跑的版本。Keil 工程里的OBJ文件夹和.uvguix文件要加到.gitignore里前者是编译输出后者是界面布局配置都不需要版本管理。这个习惯看起来麻烦但当你同时维护三四个不同的板子时没有版本管理会非常痛苦。5.7 一个具体的验证方法用逻辑分析仪抓 I2C 时序例程包里有一个「实验8 I2C 实验」驱动的是板载的 24C02 EEPROM。如果你怀疑 I2C 通信有问题最直接的方法是用逻辑分析仪抓 SCL 和 SDA 的波形。把逻辑分析仪的通道 0 接 SCL通道 1 接 SDA采样率设到 1MHz 以上然后运行程序。正常的 I2C 起始条件是SCL 为高时SDA 从高变低。停止条件是SCL 为高时SDA 从低变高。如果波形上看不到这两个条件说明 I2C 初始化有问题。如果起始条件正常但数据不对检查从机地址是否正确——24C02 的写地址是0xA0读地址是0xA1。这个验证方法比反复改代码快得多我每次调试 I2C 设备都会先抓波形确认时序没问题再去看代码逻辑。从那以后我每次拿到新的开发板例程包都会先花半小时把目录结构和工程配置过一遍再挑一个最简单的实验跑通确认工具链没问题之后才开始改代码。这个习惯帮我省下了很多原本会浪费在「为什么编译不过」上的时间。希望帮到你。本文还有配套的精品资源点击获取