一、 时钟
时钟控制器(Reset Clock Control,RCC):
1.有低速高速两种(high speed,HS与Low Speed,LS)
- 低速用于实时时钟(Real Time Clock,RTC)
- 高速用于定时器,UART,ADC等多种外设
2.有内外部两种(Internal,I与External,E)
3.Bypass clock source(旁路时钟源)
- 由外部其他设备主动给出
- 该时钟信号不经过MCU内部时钟电路驱动可直接作为单片机的时钟

时钟树
- 基本时钟信号会经过单片机MCU内部电路的处理,通过倍频分频实现不同频率的子时钟
- 不同的子时钟作用到不同的设备上,适配不同设备的工作环境
- 按需配置,一般性能拉满
二、通用输入输出端口
四种输入
1.下拉输入:
- 数字信号输入,只有高低电平两种
- 等效在MCU内对引脚接一个电阻,这个电阻叫下拉电阻
- 阻值具体多少不重要,反正比较大
- 电阻另一端接MCU的低电平
- 引脚没有任何输入时,此时读取引脚则是低电平
2.上拉输入:
- 数字信号输入,只有高低电平两种
- 等效在MCU内对一个引脚接一个电阻,这个电阻叫上拉电阻
- 阻值具体多少不重要,反正比较大
- 电阻另一端接MCU的高电平
- 引脚没有任何输入时,此时读取引脚则是高电平
3.浮空输入:
- 数字信号输入,只有高低电平两种
- 不接入任何上拉下拉电阻
- 引脚没有任何输入时,此时读取引脚电平未定,即悬而不定的状态,浮空
- 万用表检测可能出现0到3.3V之间的任意值
4.模拟输入:
- 前三个输入均是离散时间的数字信号,也就是只有低电平和高电平,模拟输入当然可以对连续时间连续值的模拟信号进行读取
- 可以在单片机中转化成可以被处理的离散时间离散值的数字信号
输出
1.推挽输出
- 高低电平驱动能力都强
- 宁折不弯,推挽高低电平相遇会烧坏
2.开漏输出
- 低电平驱动能力强
- 高电平驱动能力弱
- 高电平通过自己主动外接上拉电阻实现输出
- 输出跳变速度取决于上拉电阻,电阻越小越快
- 开漏高低电平相遇会变低(类似于C语言的与运算&&)
3.复用***输出
用于外设等需要对引脚绑定某特定MCU内功能输出情况
推挽输出、开漏输出的补充讲解
1. 推挽输出(Push-Pull Output)
基本结构
- 推挽输出内部有两个晶体管(或MOS管):
- 上管(P型):连接到电源(VCC),负责将输出拉高(高电平)。
- 下管(N型):连接到地(GND),负责将输出拉低(低电平)。
- 两个管子像“推”和“拉”一样协同工作,但同一时间只能有一个导通,避免短路。
工作方式
- 输出高电平:上管导通,电流从电源通过上管流向输出引脚。
- 输出低电平:下管导通,电流从输出引脚通过下管流向地。
- 特点:
- 可以主动输出高电平或低电平,驱动能力强(无需外部电路)。
- 高低电平切换速度快,适合高速信号。
- 多个推挽输出不能直接并联(可能短路)。
应用场景
- 普通GPIO控制(如点亮LED)。
- PWM信号输出(控制电机、LED亮度)。
- 需要高速切换的场合(如SPI通信、UART的TX引脚)。
2. 开漏输出(Open-Drain Output)
基本结构
- 开漏输出只有一个下管(N型)连接到地,上管被省略。
- 输出引脚需要通过外部上拉电阻连接到电源(VCC)才能输出高电平。
工作方式
- 输出低电平:下管导通,电流通过外部上拉电阻流向地,输出被拉低。
- 输出高电平:下管关闭,输出通过外部上拉电阻被拉高到电源电压。
- 特点:
- 高电平依赖外部上拉电阻,驱动能力较弱。
- 多个开漏输出可以并联(实现“线与”逻辑)。
- 支持不同电压的系统(通过改变上拉电源电压)。
应用场景
- I2C总线:多个设备共享同一总线,避免电平冲突。
- 中断信号线:多个设备可以通过拉低同一中断线触发中断。
- 电平转换:通过外部上拉不同电压的电源,实现不同电压系统的兼容。
3. 两者的核心区别
特性 |
推挽输出 |
开漏输出 |
结构 |
有上下两个晶体管 |
只有下管,需外部上拉电阻 |
高电平驱动 |
主动输出高电平(内部驱动) |
依赖外部上拉电阻 |
低电平驱动 |
主动输出低电平(内部驱动) |
主动输出低电平(内部驱动) |
总线兼容性 |
不能直接并联(可能短路) |
可并联(实现“线与”逻辑) |
电平兼容性 |
只能输出VCC或GND |
可通过外部上拉适配不同电压 |
速度 |
快 |
较慢(受上拉电阻影响) |
4. 生活中的比喻
- 推挽输出:像一辆车有两个司机,一个专门踩油门(拉高),一个专门踩刹车(拉低),能快速切换方向。
- 开漏输出:像一辆车只有刹车(拉低),想加速(拉高)需要靠外部力量(比如用绳子拉)。
5. 总结
- 选推挽:需要快速切换、独立驱动高/低电平的场景(如普通IO、PWM)。
- 选开漏:需要总线共享、电平转换或避免冲突的场景(如I2C、中断线)。
工程配置
基础设备配置
1.打开CubeMX,选择MCU芯片,选择单片机MCU型号,双击
2.配置复位时钟控制器(Reset Clock Control,RCC)
Crystal/ceramic resonator
3.配置时钟树
- 根据开发板晶振频率设置HSE的频率
- 让主频通过HSE
- 主频配置成50MHz
4.根据不同的烧写器,选择不同的调试模式
- ST-Link调试器—调试模式: Serial Wire Debug (SWD)
- J-Link调试器—调试模式: JTAG 或 Serial Wire Debug (SWD)
- 如果用其他烧写器,请对应配置好
工程管理设置中
- 1.命名工程,不要有中文等非ASCII码的字符,最好以C语言变量要求
- 2.修改IDE为MDK-ARM,对应Keil这一套工具
- 3.代码生成中:配置每个外设生成一个单独的头文件,便于管理
生成代码
必要的开发前的Keil配置
1.编译优化不好,取消编译优化
2.打开C99模式,添加–cpp11
3.烧写即运行,选择Reset and run
Cortex-M3/M4处理器通俗结构
处理器系统
1.处理器
2.唤醒中断控制器
3.调试与跟踪子系统(调试用)
4.指令、数据码存储器专用传输接口(获取指令编码,数据内容)
5.外设传输接口(各种外部设备,如通信模块,存储器)
存储器
指令存储,数据存储(有地址,just like point)
对存储器的抽象认识:
- 按一定地址编排,像C语言指针访问内存一样可以被访问。
- 能存指令
- 能存数据
- 有些地址保存给寄存器,用于存储其他外部设备,如通信模块、调试组件、附加存储器等。
处理器可以通过访问存储器来获取指令编码和数据内容
搭配一些电路,对指令编码和数据内容进行运算与操作,输出出来
中断
1.处理器系统在执行代码的时候,会从存储器依次取出指令和数据,这种能力需要在处理器里保存一个存储器地址,就是所谓的程序计数器(Program Counter,PC),也叫程序指针
2.当外部中断(Extern Interrupt,EXTI)事件发生的时候,单片机有一定的策略保护好当前执行的状态,将PC跳转到中断服务函数(即回调函数),然后执行相应的用户自定义编写的处理策略
3.完成中断后,恢复原有执行
上述过程依赖嵌套中断向量控制器(Nested Vectored Interrupt Controller,NVIC)
中断事件有两类优先级
- 抢占优先级:高的可以直接中断掉低优先级的中断服务函数直接处理
- 子优先级:在多中断事件发生时 ,高的可以插队到低的前面先处理
嵌套中断向量控制器(Nested Vectored Interrupt Controller, NVIC)是 ARM Cortex-M 系列处理器的核心组件,负责管理中断和异常的优先级、嵌套和调度。其设计目标是实现低延迟、高灵活性的中断处理,满足实时系统的需求。以下从架构、寄存器、优先级机制、中断处理流程及优化技术等方面进行专业级分析。
NVIC补充讲解
1. NVIC 架构与核心特性
(1) 硬件架构
- 集成于 Cortex-M 内核:与处理器紧耦合,直接控制中断响应逻辑。
- 向量中断机制:每个中断/异常有唯一的向量地址(存储在向量表中),支持快速跳转。
- 优先级动态配置:支持软件实时修改中断优先级。
- 抢占与尾链优化:硬件自动处理中断嵌套时的现场保存与恢复,减少延迟。
(2) 关键特性
- 支持 1~240 个外部中断(具体数量由芯片厂商定义)。
- 4~256 级可编程优先级(优先级位数由芯片实现决定,如 Cortex-M3/M4 支持 8 级优先级,即 3 位)。
- 优先级分组:将优先级分为抢占优先级(Preemption)和子优先级(Subpriority)。
- 中断屏蔽:通过
PRIMASK
、FAULTMASK
和 BASEPRI
寄存器实现中断全局或条件屏蔽。
2. NVIC 寄存器详解
NVIC 通过内存映射寄存器配置,关键寄存器如下(以 Cortex-M3/M4 为例):
(1) 中断使能寄存器
- ISERx (Interrupt Set Enable Register):写 1 到某位使能对应中断。
- ICERx (Interrupt Clear Enable Register):写 1 到某位禁用对应中断。
(2) 中断挂起寄存器
- ISPRx (Interrupt Set Pending Register):手动触发中断挂起。
- ICPRx (Interrupt Clear Pending Register):清除挂起状态。
(3) 优先级寄存器
- IPRx (Interrupt Priority Register):配置中断优先级,每个中断占用 8 位(实际使用位数由芯片决定)。
示例:若使用 4 位优先级(高 4 位有效),优先级字段格式为:
Priority = (Preemption Priority << (8 - Priority_Width)) | Subpriority
(4) 系统异常寄存器
- SHPRx (System Handler Priority Register):配置系统异常(如 SysTick、PendSV)的优先级。
3. 优先级分组与抢占机制
(1) 优先级分组寄存器 (AIRCR)
-
AIRCR[10:8] (PRIGROUP):定义优先级分组方式,将优先级位划分为抢占和子优先级。
分组规则:
抢占优先级位数 = PRIGROUP[2:0]
子优先级位数 = Total_Priority_Bits - 抢占优先级位数
示例:若总优先级位数为 4(即 16 级优先级),设置 PRIGROUP=5
(二进制101),则:
抢占优先级位数 = 3
(高3位为抢占优先级),
子优先级位数 = 1
(低1位为子优先级)。
(2) 抢占规则
- 若新中断的抢占优先级高于当前执行中断的抢占优先级,则发生抢占。
- 子优先级仅在多个中断同时挂起且抢占优先级相同时决定执行顺序。
4. 中断处理流程
(1) 中断触发
- 中断源激活:外设触发中断信号(如定时器溢出)。
- 挂起状态设置:NVIC 将中断标记为挂起(Pending)。
- 优先级裁决:NVIC 比较当前执行任务的优先级与新中断的抢占优先级。
(2) 响应阶段
- 抢占判断:若允许抢占,CPU 暂停当前任务,保存现场(自动压栈:PC, PSR, R0-R3, R12, LR)。
- 向量表跳转:根据中断号从向量表获取 ISR 入口地址。
- ISR 执行:执行用户编写的中断服务程序。
(3) 中断返回
- 尾链优化 (Tail-Chaining):若挂起中断的优先级高于当前 ISR,直接跳转到新 ISR,跳过恢复现场。
- 现场恢复:通过
BX LR
或 POP {PC}
指令触发异常返回,硬件自动恢复现场。
5. 中断延迟与优化
(1) 关键延迟指标
- 中断响应时间:从中断触发到 ISR 第一条指令执行的时间(Cortex-M 典型值为 12 周期)。
- 上下文切换时间:现场保存/恢复的周期数(通常 12~20 周期)。
(2) 优化技术
- 尾链优化:避免不必要的现场恢复/保存,减少连续中断的延迟。
- 晚到中断处理 (Late Arrival):高优先级中断在保存现场期间到达时,优先处理高优先级中断。
- 优先级压缩:合理分配优先级,避免过多抢占导致堆栈溢出。
6. 高级功能
(1) 动态优先级修改
- 通过写
NVIC_IPRx
寄存器实时修改中断优先级。
- 应用场景:动态调整任务重要性(如实时任务优先级提升)。
(2) 中断屏蔽
- PRIMASK:全局关闭中断(
CPSID I
)。
- BASEPRI:屏蔽优先级低于某阈值的中断(如
BASEPRI = 0x40
屏蔽优先级 ≥ 4 的中断)。
(3) 软件触发中断
- 通过写
NVIC_STIR
寄存器(Software Trigger Interrupt Register)触发中断。
7. 调试与问题排查
(1) 常见问题
- 优先级配置错误:导致预期外的中断嵌套或阻塞。
- 中断服务程序过长:引发其他高优先级中断的响应延迟。
- 共享资源冲突:未使用临界区保护导致数据竞争。
(2) 调试工具
- CMSIS-Core 函数:如
__get_IPSR()
获取当前中断号。
- 调试器监控:通过 IDE 查看 NVIC 寄存器状态和中断挂起情况。
8. 实际代码示例(基于寄存器操作)
(1) 配置外部中断 EXTI0
NVIC_ISER0 |= (1 << 6);
uint8_t priority = (2 << (8 - 4)) | 1;
NVIC_IPR6 = priority << 4;
SCB->AIRCR = (0x5FA << 16) | (0x5 << 8);
(2) 临界区保护
#define ENTER_CRITICAL() __asm volatile ("cpsid i")
#define EXIT_CRITICAL() __asm volatile ("cpsie i")
9. 总结
NVIC 是 Cortex-M 实时性的核心保障,其设计在硬件层面实现了:
- 低延迟中断响应:通过向量化跳转和尾链优化。
- 灵活的优先级管理:支持动态调整和分组配置。
- 安全的嵌套机制:硬件自动处理现场保存与恢复。
深入掌握 NVIC 需结合芯片手册(如 STM32 参考手册)和 ARMv7-M 架构文档,重点关注寄存器操作、优先级策略及调试技巧。
虚函数
函数声明的前缀有_weak,表示该函数是虚函数
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
UNUSED(GPIO_Pin);
}
_weak
本质上是一个宏定义,实际上的文本对应的是编译指令_attribute((weak))
- 虚函数
- 有虚函数指令——weak的函数声明
- 虚函数可以有很多个
- 实函数
- 没有虚函数指令_weak的函数声明,也就是普通的函数,都是实函数
- 实函数至多只有一个,多了会编译报错(如果是C++可以兼容函数重载)
- 如果实函数不存在,则随机选取一个幸运的虚函数(对我们而言是随机的。但对编译器而言有一定的选取规则)
工程配置
1.根据原理图或者PCB图,找到开发板上按键开关对应的单片机引脚。
2.将单片机开关引脚设置为外部中断0号通道
3.检测模式选择下降沿
4.输入模式选择上拉输入,用于维持平时的高电平
5.使能嵌套中断向量控制器
6.中断的两种优先级默认配置即可
按键消抖方法:
1.通过延迟10ms,略过了不稳定的时间,等稳定下来之后再检测状态
缺点:不能同时调用多个,否则会事与愿违;
2.硬件上再按键两端并联一个电容器,滤除高频率的杂波
缺点:要重新设计电路板
3.不用外部中断,定时直接读取引脚信息,用定时器中断(充分利用了人类的反应时间比计算机慢得多的特点。只需要十几毫秒查询一次即可,同样节省了计算性能)
定时器
TIM定时器分类:
- 基本定时器:
- 预分频、自动装载值、自定义计数方向、自定义触发事件
- 单个脉冲输出
- 可以产生DMA请求定期搬运数据
- 通用定时器:
- 具有基本定时器的全部功能
- 四个独立的GPIO通道可以进行输入捕获、输出比较、PWM生成、编码模式等
- 高级定时器
- 通用定时器的全部功能
- 八个GPIO通道可以支持反向或互补PWM输出
- 一个GPIO通道可以支持外部输入信号引入刹车重启等功能