简介:这份STM32教程PPT资源面向嵌入式初学者与进阶开发者,围绕STM32微控制器的架构原理与实战应用展开,帮助读者从寄存器级操作逐步过渡到外设驱动开发。内容覆盖初识STM32、RCC时钟配置(HSE/HSI)、新建寄存器工程、SRAM调试、内部FLASH读写、I2C读写EEPROM、ADC电压采集、SPI读写串行FLASH以及寄存器点亮LED等核心主题,兼顾基础设置与高级功能。资源包共3450个文件,以c源码、h头文件、s汇编启动文件为主,辅以pdf文档、pptx课件、hex固件及uvprojx工程文件,压缩包约276.94MB,目录结构完整,便于按模块检索与对照实验。目前已有890人学习下载,适合需要系统梳理STM32知识体系、动手实践外设驱动与调试流程的读者参考使用。
1. 从一份 stm32教程ppt 说起:为什么寄存器比库函数更值得先啃
很多人第一次拿到 stm32教程ppt,第一反应是翻到 GPIO 点灯那页,照着 HAL 库把代码抄进去,编译下载,灯亮了,然后呢?然后就没有然后了。换个需求,比如要读一个编码器、要配一个定时器触发 ADC,立刻卡住,因为不知道 HAL 那行HAL_GPIO_Init背后到底动了哪些寄存器。这份 stm32教程ppt 的价值恰恰在于它没有停在“调库”层面,而是把时钟树、GPIO 模式寄存器、NVIC 优先级分组这些底层配置摊开讲。它适合已经学过 C 语言、摸过单片机但被库函数惯坏的人,也适合带课的讲师直接拿去改造成自己的课件。整套内容围绕 STM32F103 系列展开,覆盖从新建工程到中断、定时器、串口、ADC 的完整链路,每一页都对应一个可以独立编译的工程骨架。你不需要先买开发板才能看,但手边有一块最小系统板会理解得快很多。
2. 把 PPT 里的时钟树讲清楚:从 HSE 到 SYSCLK 的配置链路
2.1 为什么时钟配置是第一个翻车点
几乎所有 stm32 新手遇到的第一个“玄学”问题都是:程序下载进去没反应,或者串口打印乱码。十有八九是时钟没配对。PPT 里有一页专门画了 STM32F103 的时钟树,从 HSE 外部晶振 8MHz 开始,经过 PLL 倍频到 72MHz,再分频给 AHB、APB1、APB2。很多人直接抄SystemInit就完事,但一旦换成内部 HSI 或者换一颗晶振频率不同的芯片,系统就跑飞了。
常见做法是:先确认板子上的晶振是 8MHz 还是 12MHz,然后手动算 PLL 的倍频因子。STM32F103 的 PLL 输出公式是PLLCLK = HSE * PLLMUL,其中 PLLMUL 可以是 2 到 16 倍。要得到 72MHz,8MHz 晶振就设 9 倍频。PPT 里给了一张表,把 HSE、PLLMUL、AHB 分频、APB1 分频、APB2 分频的常用组合列了出来,直接查表比翻参考手册快。
下面这段代码是 PPT 里 RCC 配置的简化版,用寄存器操作,不依赖 HAL:
// 假设 HSE = 8MHz,目标 SYSCLK = 72MHz // 1. 使能 HSE 并等待就绪 RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)); // 2. 配置 Flash 预取和等待周期(72MHz 需要 2 个等待周期) FLASH->ACR |= FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY_2; // 3. 配置 PLL:HSE 不分频,9 倍频 RCC->CFGR &= ~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL); RCC->CFGR |= RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL9; // 4. 使能 PLL 并等待锁定 RCC->CR |= RCC_CR_PLLON; while (!(RCC->CR & RCC_CR_PLLRDY)); // 5. 配置 AHB = SYSCLK,APB1 = HCLK/2,APB2 = HCLK RCC->CFGR &= ~(RCC_CFGR_HPRE | RCC_CFGR_PPRE1 | RCC_CFGR_PPRE2); RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // 6. 切换系统时钟到 PLL RCC->CFGR &= ~RCC_CFGR_SW; RCC->CFGR |= RCC_CFGR_SW_PLL; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);逻辑说明:第 1 步先让外部晶振起振,如果板子没焊晶振或者晶振坏了,程序会卡在while里,这是排查硬件问题的第一道关卡。第 2 步的 Flash 等待周期必须设对,72MHz 下如果只给 0 或 1 个等待周期,取指会出错,表现为程序跑飞或 HardFault。第 3 步的PLLXTPRE是 HSE 预分频,如果晶振是 8MHz 就不分频,如果是 16MHz 想得到 72MHz 就得先二分频再 9 倍频。第 5 步的 APB1 最高只能到 36MHz,所以必须二分频,APB2 可以到 72MHz。第 6 步切换时钟源后要等SWS状态位确认,不能直接往下跑。
参数怎么改:如果你用的是 12MHz 晶振,把PLLMULL9改成PLLMULL6,同时 Flash 等待周期可以降到 1。如果只想跑 48MHz,PLLMULL6配 8MHz 晶振,APB1 分频可以不改。PPT 里还标注了每种配置下的功耗差异,对电池供电的场景有参考价值。
2.2 用示波器验证 MCO 输出
PPT 里有一个实验:把系统时钟通过 MCO 引脚输出到 PA8,用示波器量频率。这个步骤看起来多余,但它是验证时钟配置是否正确的唯一硬手段。配置 MCO 只需要改RCC->CFGR的MCO位:
// 将 PLL 输出二分频后从 PA8 输出(72MHz/2 = 36MHz) RCC->CFGR &= ~RCC_CFGR_MCO; RCC->CFGR |= RCC_CFGR_MCO_PLL; // 配置 PA8 为复用推挽输出 GPIOA->CRH &= ~(GPIO_CRH_MODE8 | GPIO_CRH_CNF8); GPIOA->CRH |= GPIO_CRH_MODE8_1 | GPIO_CRH_MODE8_0 | GPIO_CRH_CNF8_1;逻辑说明:MCO位选择输出源,可选 HSI、HSE、PLL/2、SYSCLK。这里选 PLL/2 是因为 MCO 引脚的最高输出频率有限,直接输出 72MHz 可能波形畸变。PA8 的配置里MODE8设成 11 表示输出模式 50MHz,CNF8设成 10 表示复用推挽。如果量出来频率不对,先检查晶振是否起振,再检查 PLL 倍频因子。
注意:有些最小系统板的 PA8 引脚已经接了 LED 或者被其他外设占用,输出前先确认原理图。PPT 里建议用排线把 PA8 引到示波器探头,地线尽量短,否则高频下波形会振铃。
3. 中断与定时器:把 PPT 里的框图变成可调参数
3.1 NVIC 优先级分组到底怎么选
PPT 里有一页专门讲 NVIC 的优先级分组,但很多人看完还是不知道NVIC_PriorityGroup_2和NVIC_PriorityGroup_4有什么区别。简单说,Cortex-M3 的优先级寄存器有 4 个位,分组决定了这 4 个位里几个给抢占优先级、几个给响应优先级。分组 2 是 2 位抢占、2 位响应,分组 4 是 4 位全给抢占。
选哪个取决于你的系统里有没有“必须打断正在执行的中断”的需求。比如串口接收中断里要处理 Modbus 帧,而定时器中断要严格按周期触发 ADC 采样,那就需要抢占优先级。如果所有中断都是短任务,响应优先级就够了。PPT 里给了一个经验法则:中断嵌套层数不超过 2 层时用分组 2,超过 2 层用分组 4。
配置代码很简单,但位置很关键:
// 必须在任何中断使能之前调用,通常放在 main 开头 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置串口1中断:抢占优先级 1,响应优先级 1 NVIC_InitTypeDef nvic; nvic.NVIC_IRQChannel = USART1_IRQn; nvic.NVIC_IRQChannelPreemptionPriority = 1; nvic.NVIC_IRQChannelSubPriority = 1; nvic.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic);逻辑说明:NVIC_PriorityGroupConfig只能调用一次,重复调用会导致优先级混乱。PreemptionPriority越小优先级越高,SubPriority只在抢占优先级相同时起作用。如果两个中断的抢占优先级相同,它们不会互相打断,但响应优先级高的先执行。
参数怎么改:如果系统里有一个 1ms 的定时器中断和一个串口中断,串口中断里要解析长帧,那就把定时器的抢占优先级设得比串口高,保证采样周期不丢。PPT 里用表格列出了常见外设的建议优先级,比如 SysTick 设最高,PWM 捕获次之,串口再次之。
3.2 定时器输入捕获测频率的完整流程
PPT 里有一个用 TIM2 输入捕获测方波频率的实验,步骤写得很细,但有几个参数容易设错。完整流程是:配置 GPIO 为浮空输入,配置 TIM2 的通道 1 为输入捕获模式,设置预分频器和自动重装载值,开启捕获中断,在中断里读 CCR1 并计算频率。
// 1. GPIO 配置:PA0 浮空输入 GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0); GPIOA->CRL |= GPIO_CRL_CNF0_0; // 浮空输入 // 2. TIM2 时基:预分频 72-1,即 1MHz 计数频率 TIM2->PSC = 72 - 1; TIM2->ARR = 0xFFFF; // 最大重装载,避免溢出 // 3. 通道1 输入捕获:上升沿,不分频 TIM2->CCMR1 &= ~TIM_CCMR1_CC1S; TIM2->CCMR1 |= TIM_CCMR1_CC1S_0; // CC1 映射到 TI1 TIM2->CCER &= ~TIM_CCER_CC1P; // 上升沿捕获 TIM2->CCER |= TIM_CCER_CC1E; // 使能捕获 // 4. 使能捕获中断 TIM2->DIER |= TIM_DIER_CC1IE; NVIC_EnableIRQ(TIM2_IRQn); // 5. 启动定时器 TIM2->CR1 |= TIM_CR1_CEN;逻辑说明:预分频器设成 72-1 是因为系统时钟 72MHz,分频后计数频率 1MHz,每个计数代表 1 微秒。ARR设成最大是为了防止在两次捕获之间计数器溢出,但如果信号频率很低(比如 1Hz),16 位计数器还是会溢出,这时候需要开启溢出中断做扩展计数。CC1S设成 01 表示通道 1 配置为输入,映射到 TI1。CC1P清零表示上升沿捕获。
中断服务函数里这样算频率:
volatile uint32_t capture1 = 0, capture2 = 0; volatile uint8_t flag = 0; volatile float freq = 0; void TIM2_IRQHandler(void) { if (TIM2->SR & TIM_SR_CC1IF) { TIM2->SR &= ~TIM_SR_CC1IF; // 清标志 if (flag == 0) { capture1 = TIM2->CCR1; flag = 1; } else { capture2 = TIM2->CCR1; if (capture2 > capture1) { freq = 1000000.0f / (capture2 - capture1); } else { // 处理溢出:加上 ARR 的补偿 freq = 1000000.0f / (0xFFFF - capture1 + capture2); } flag = 0; } } }参数怎么改:如果要测占空比,需要同时捕获上升沿和下降沿,把CC1P在中断里动态切换,或者用两个通道分别捕获。PPT 里提到,测高频信号时预分频可以设成 1,让计数频率等于系统时钟,但要注意ARR不能太小,否则频繁溢出。测低频信号时预分频加大,比如 7200-1,计数频率 10kHz,这样 16 位计数器可以测到 0.15Hz 左右。
注意:输入捕获的 GPIO 必须配置成浮空或上拉输入,不能配成推挽输出,否则会烧引脚。如果信号源内阻较大,建议加上拉电阻,否则边沿会变缓,捕获位置抖动。
4. 串口与 ADC:PPT 里没写全的排错细节
4.1 串口乱码的三种排查路径
串口是 stm32 最常用的调试手段,但也是问题最多的外设。PPT 里讲了波特率计算和中断收发,但没展开讲乱码怎么查。我一般按三条路径排查:时钟、引脚、配置。
时钟问题:USART1 挂在 APB2 上,如果 APB2 分频不是 1,波特率寄存器BRR的值就要重算。比如 APB2 跑 36MHz,BRR就不能按 72MHz 算。PPT 里给了一个公式:USARTDIV = fCK / (16 * 波特率),然后整数部分写进BRR[15:4],小数部分乘以 16 写进BRR[3:0]。
引脚问题:USART1 默认是 PA9 和 PA10,但有些板子引到了 PB6 和 PB7,需要重映射。重映射要开 AFIO 时钟,然后写AFIO->MAPR。PPT 里有一页专门讲重映射,但代码里漏了开 AFIO 时钟这一步,很多人卡在这里。
配置问题:停止位、校验位、数据位必须和上位机一致。如果上位机是 8 数据位、无校验、1 停止位,那CR1和CR2就按默认值来。如果开了校验,CR1的PCE置 1,同时M位要改成 9 数据位。
// 串口1初始化:72MHz,115200,8N1 RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN; // PA9 复用推挽输出,PA10 浮空输入 GPIOA->CRH &= ~(GPIO_CRH_MODE9 | GPIO_CRH_CNF9 | GPIO_CRH_MODE10 | GPIO_CRH_CNF10); GPIOA->CRH |= GPIO_CRH_MODE9_1 | GPIO_CRH_MODE9_0 | GPIO_CRH_CNF9_1; GPIOA->CRH |= GPIO_CRH_CNF10_0; // 波特率:72MHz / (16 * 115200) = 39.0625 // 整数部分 39 = 0x27,小数部分 0.0625 * 16 = 1 USART1->BRR = (39 << 4) | 1; // 使能收发和串口 USART1->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;逻辑说明:BRR的计算必须用实际挂载的总线时钟,不是系统时钟。AFIOEN在重映射时才需要,但开了不影响。PA9 的CNF9设成 10 表示复用推挽,PA10 的CNF10设成 01 表示浮空输入。
参数怎么改:如果要用中断接收,在CR1里加上RXNEIE,然后在NVIC里使能USART1_IRQn。中断里读DR寄存器会自动清标志。如果要发长数据,建议用 DMA,PPT 里有一页讲 DMA 配置,但没给完整代码,需要自己补。
4.2 ADC 采样时间与参考电压的坑
PPT 里 ADC 部分讲了规则通道和注入通道的区别,但采样时间这个参数很多人忽略。STM32F103 的 ADC 采样时间从 1.5 个周期到 239.5 个周期可调,采样时间越长,转换结果越稳,但转换速率越慢。如果信号源内阻大,比如用电阻分压测电池电压,采样时间必须加长,否则读数跳变。
// ADC1 初始化:PA0 模拟输入,规则通道,采样时间 55.5 周期 RCC->APB2ENR |= RCC_APB2ENR_ADC1EN | RCC_APB2ENR_IOPAEN; // PA0 模拟输入 GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0); // ADC 时钟:PCLK2/6 = 12MHz,不能超过 14MHz RCC->CFGR &= ~RCC_CFGR_ADCPRE; RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6; // 采样时间:通道0,55.5 周期 ADC1->SMPR2 &= ~ADC_SMPR2_SMP0; ADC1->SMPR2 |= ADC_SMPR2_SMP0_2 | ADC_SMPR2_SMP0_1 | ADC_SMPR2_SMP0_0; // 规则序列:只有通道0 ADC1->SQR1 &= ~ADC_SQR1_L; ADC1->SQR3 &= ~ADC_SQR3_SQ1; ADC1->SQR3 |= 0; // 通道0 // 使能 ADC 并校准 ADC1->CR2 |= ADC_CR2_ADON; for (volatile int i = 0; i < 10000; i++); // 等待稳定 ADC1->CR2 |= ADC_CR2_CAL; while (ADC1->CR2 & ADC_CR2_CAL);逻辑说明:ADC 时钟不能超过 14MHz,所以 72MHz 系统时钟下至少 6 分频。采样时间 55.5 周期对应SMP0的 100 组合,具体查参考手册。校准必须在ADON置位后执行,否则首次转换误差大。
参数怎么改:如果测的是内部温度传感器,通道要改成 16,采样时间建议 239.5 周期。如果测的是 VDDA 的一半,通道改成 17。PPT 里给了一个表格,列出了不同信号源内阻对应的最小采样时间,比如内阻 10kΩ 以下用 55.5 周期,10kΩ 到 50kΩ 用 239.5 周期。
注意:ADC 的参考电压就是 VDDA,如果 VDDA 不稳,所有读数都会漂。PPT 里建议在 VDDA 和 VSSA 之间并一个 10uF 加 100nF 的电容,位置尽量靠近芯片引脚。
5. 避坑与常见问题:从 PPT 到实物之间的五道坎
5.1 下载后不运行,但调试器能连上
现象:Keil 或 IAR 里点下载,提示成功,但板子没反应,LED 不亮,串口无输出。调试器能读到芯片 ID,说明 SWD 链路正常。
原因:最常见的是 BOOT0 和 BOOT1 跳线不对。STM32F103 的启动模式由 BOOT0 和 BOOT1 决定,主 Flash 启动要求 BOOT0 接地。如果 BOOT0 接高,芯片会从系统存储器启动,跑的是出厂 bootloader,你的程序根本没执行。
解决:把 BOOT0 跳线帽拔掉或接到 GND,复位后重新下载。如果板子上没有跳线帽,检查 BOOT0 引脚有没有下拉电阻。PPT 里有一页专门画了启动模式表,建议打印出来贴在工位上。
5.2 中断进不去,但标志位已经置位
现象:串口接收中断配置好了,上位机发了数据,RXNE标志也置位了,但中断服务函数就是不执行。
原因:NVIC 的优先级分组没配置,或者中断使能位写错了。NVIC_EnableIRQ只是使能了 NVIC 通道,但外设自己的中断使能位(比如USART1->CR1的RXNEIE)也必须置位。另外,如果优先级分组设成了 4,所有中断都是抢占优先级,但NVIC_SetPriority的参数范围会变,写错值会导致优先级无效。
解决:先调NVIC_PriorityGroupConfig,再配NVIC_Init,最后开外设中断使能。顺序不能乱。PPT 里用红色标注了“先分组,后使能”的流程。
5.3 定时器 PWM 输出频率不对
现象:想要 1kHz 的 PWM,示波器量出来是 500Hz 或者 2kHz。
原因:PSC和ARR的计算公式记反了。PWM 频率 = 系统时钟 / ((PSC+1) * (ARR+1))。如果系统时钟 72MHz,想要 1kHz,PSC+1和ARR+1的乘积应该是 72000。常见错误是把PSC设成 72,ARR设成 1000,乘积是 72000,但实际写进寄存器的值要减 1,所以PSC=71,ARR=999。
解决:用 PPT 里的计算表,先确定PSC,再算ARR。如果频率很低,ARR超过 65535 就要换 32 位定时器或者加大PSC。注意PSC是 16 位,最大 65535。
5.4 ADC 读数跳动超过 10 个 LSB
现象:输入接的是稳压电源,但 ADC 读出来一直在跳,最大最小值差几十。
原因:采样时间太短,或者参考电压不稳,或者信号源内阻太大。另外,如果 ADC 的输入引脚旁边有数字信号线,串扰也会导致跳动。
解决:先加长采样时间到 239.5 周期,如果还跳,在输入引脚对地并一个 100nF 电容。PPT 里建议用平均值滤波,连续采 8 次去掉最大最小再平均。如果要求更高,用外部基准芯片给 VDDA 供电。
5.5 串口接收中断里处理长帧导致丢数据
现象:上位机发了一帧 20 字节的数据,串口中断里逐字节接收,但收到第 10 个字节后就不进中断了。
原因:中断服务函数里做了耗时操作,比如printf或者浮点运算,导致下一个字节到来时RXNE还没读走,溢出错误ORE置位,后续数据全丢。
解决:中断里只做数据搬运,把接收到的字节存进环形缓冲区,主循环里再解析。PPT 里给了一个环形缓冲区的实现,头尾指针加计数,中断里只写头指针,主循环读尾指针。如果帧长固定,也可以用 DMA 接收,DMA 传输完成中断里再处理。
6. 把 PPT 变成自己的工程模板:一个可复用的初始化框架
PPT 翻完一遍,最容易忘的是各个外设的初始化顺序。我一般会把时钟、GPIO、NVIC 的配置抽成一个board_init函数,每个外设一个独立的xxx_init,然后在main里按依赖关系调用。这样换一块板子,只需要改board_init里的时钟和引脚定义,其他代码不用动。
下面是一个最小框架的目录结构,直接对应 PPT 里每个实验的代码:
project/ ├── start/ │ ├── startup_stm32f10x.s // 启动文件,PPT 里没改过 │ └── system_stm32f10x.c // 系统时钟,按第 2 章改 ├── driver/ │ ├── led.c / led.h // GPIO 输出 │ ├── uart.c / uart.h // 串口,含环形缓冲区 │ ├── timer.c / timer.h // 定时器,含输入捕获和 PWM │ └── adc.c / adc.h // ADC,含滤波 ├── app/ │ └── main.c // 业务逻辑 └── Makefile // 或者 Keil 工程文件每个驱动文件里,初始化函数只做三件事:开时钟、配引脚、配外设寄存器。中断服务函数放在驱动文件里,通过回调函数指针暴露给上层。这样main.c里看不到任何寄存器操作,全是led_on()、uart_send()这种接口。
验证方法:拿一块新的 STM32F103 最小系统板,只接电源和 SWD,把框架下载进去。如果串口能打印出启动信息,LED 能按 1Hz 闪烁,说明时钟和 GPIO 没问题。然后逐个使能定时器、ADC,每加一个外设就用示波器或串口验证一次。PPT 里每个实验都有对应的验证步骤,照着走一遍,比只看不练快得多。
从那以后我每次拿到新的 stm32 板子,都强制先跑一遍时钟验证和串口打印,确认底层没问题再往上堆功能。这个习惯帮我省掉了至少一半的“玄学”调试时间。希望帮到你。
本文还有配套的精品资源,点击获取