前言
本系列学习笔记是本人跟随米醋电子工作室学习嵌入式的学习笔记,自用为主,不是教学或经验分享,若有误,大佬轻喷,同时欢迎交流学习,侵权即删。
一、基本概念
什么是嵌入式开发?
嵌入式开发是为特定目的而设计的计算系统编写软件的过程。这些系统通常具有受限的资源(处理能力、内存、能源等),并且需要可靠地执行特定任务。在嵌入式开发中,有两种主要的编程范式:裸机编程和基于调度器的开发。
1.裸机执行
控制流:线性执行
任务切换:无
执行模式:阻塞式
特点:简单、可预测
比喻理解
裸机开发:单车道公路
想象一条单车道公路,车辆必须按照严格的顺序依次通过。这就像裸机编程:
•顺序执行:就像车辆必须一辆接一辆地通过,裸机代码也是按照固定顺序一步步执行
•阻塞问题:如果一辆车停下来(例如任务卡住),后面所有车辆都必须等待
•简单可靠:单车道设计简单,没有车辆需要变换车道,不会发生碰撞
2.调度器执行
- 优先级驱动调度:会对任务划分高 / 中 / 低优先级(如 "无线通信" 为高优先级),优先处理高优先级任务。
- 自动化任务切换:无需手动操作,系统自动完成不同任务之间的切换。
- 支持并发执行:采用 "并发" 模式处理任务,提升 CPU 资源的利用效率。
- 流程化管理:遵循 "任务创建→优先级分配→调度决策→上下文切换→任务执行" 的流程,有序推进任务处理。
- 集中式优先级管理:调度器承担 "优先级管理" 职责,统一协调任务队列的执行顺序。
比喻理解
调度器开发:繁忙的餐厅
想象一家繁忙的餐厅,有一位餐厅经理(调度器)协调服务员和厨师的工作:
•优先级处理:餐厅经理根据订单的紧急程度分配任务,如VIP客人的订单优先处理
•资源分配:厨师(CPU)被分配给不同的任务,而餐厅经理确保资源得到最有效利用
•任务切换:服务员可以在多个任务之间切换,如一个服务员可以同时照顾多桌客人
3.裸机开发与调度器开发的对比
| 对比维度 | 裸机开发 (Bare-Metal) | 调度器开发 (Scheduler-Based) |
|---|---|---|
| 定义 | 直接在硬件上编程,无操作系统 / 调度器支持;代码直接控制硬件资源,按严格顺序执行 | 使用实时操作系统 (RTOS) 或任务调度器管理多任务执行;由调度器决定任务的执行时机 |
| 优势 | - 直接控制硬件:精确控制硬件资源,获得最佳性能- 低资源消耗:无调度器 / 操作系统的额外开销- 确定性执行:执行时间可精确预测,每次运行结果一致 | - 多任务支持:可并发处理多个任务,类似多车道公路同时容纳多辆车- 优先级管理:重要任务可优先执行,类似紧急服务车辆的优先通行权- 资源管理:协调共享资源以避免冲突,类似餐厅经理协调厨房资源- 扩展性更好:新功能可作为独立任务添加,无需重构整个系统 |
| 劣势 | - 复杂性增加:功能增多后管理困难,如同单人管理整条流水线- 难处理并发:顺序执行,一个任务阻塞会影响整个系统- 响应延迟:紧急任务需等待当前任务完成,如同急救车卡在单车道 | - 额外开销:调度器本身消耗系统资源,如同交通管理系统需要额外设施- 机制复杂:需理解调度机制、任务优先级及同步等问题 |
二、工程模板搭建
1.时钟配置
1.1 时钟基本概念
在电子学和微控制器领域,时钟信号是一种周期性变化的电子信号,用于同步系统中的操作和数据传输。时钟信号通常是方波,并具有特定的频率,频率决定了系统的运行速度。
比方:时钟是乐队的节拍器
在微控制器中,时钟就像是乐队中的节拍器。节拍器以固定的频率发出滴答声,指导乐队成员的演奏节奏。同样,时钟信号以固定的频率引导微控制器内部各种操作的执行。
时钟的作用
同步操作
时钟信号用于同步微控制器内部的各种操作,例如CPU指令的执行、数据的读取和写入等。
确定运行速度
时钟频率直接影响系统的运行速度。较高的时钟频率意味着更快的处理速度,但也意味着更高的功耗。
驱动外设
许多外设(如定时器、USART、SPI等)需要时钟信号来工作。正确的时钟配置确保外设按照预期的速度运行。
1.2 时钟配置流程
配置时钟的主要步骤
1选择时钟源
确定使用内部时钟(HSI/LSI)还是外部时钟(HSE/LSE)。内部时钟提供便捷性,外部时钟提供更高的精度。(一般都是用外部)
2配置PLL
通过相位锁定环(PLL)调整时钟频率,生成所需的系统时钟。PLL能够倍频或分频输入时钟,产生更高或更低的频率。
补充:PLL的作用
本质上 PLL 是解决「基础时钟源频率无法满足系统与外设多样化需求」的核心桥梁,具体意义可以从这几个实用角度理解
核心意义可概括为 3 点:
- 倍频生成高频系统时钟,提升 STM32 芯片的运算效率;
- 分频产生多路精准时钟,适配不同外设的差异化需求;
- 兼顾时钟精度、硬件成本与功耗平衡,适配多样化应用场景。
3设置时钟树
配置系统时钟、AHB时钟、APB1时钟和APB2时钟,确保各外设时钟频率正确。这是分配时钟资源的关键步骤。
4使能外设时钟
为需要使用的外设使能时钟信号。只有使能了相应的时钟,外设才能正常工作。
2.SYS配置
SYS配置主要涉及调试接口的选择和设置,正确的配置可以确保芯片与调试器的稳定通信,是开发过程中的重要一环
常用调试接口
Serial Wire (SW-DP) (一般无脑选他)
-
**引脚需求:**仅需2个引脚 (SWCLK, SWDIO)
-
**适用于:**ST-Link调试器
-
**优势:**引脚占用少,速度快
-
**常用性:**使用频率高,推荐首选
SW调试接口(SW-DP)仅需要两个引脚:
- **SWCLK:**主机到从机的时钟信号
- **SWDIO:**双向数据信号
这种模式下,ST-Link工作最稳定,占用引脚少,是日常开发的首选。
JTAG (JTAG-DP)
-
**引脚需求:**4个引脚(4pin)或5个引脚(5pin,含复位)
-
**适用于:**J-Link等高级调试器
-
**优势:**功能更完整,支持高级调试
-
**常用性:**在复杂应用中使用
JTAG调试接口(JTAG-DP)需要4或5个引脚:
- **TCK:**测试时钟
- **TMS:**测试模式选择
- **TDI:**测试数据输入
- **TDO:**测试数据输出
- **TRST:**测试复位(可选,5pin模式才有)
JTAG适合复杂的调试场景,尤其是在使用J-Link调试器时。
3.工程配置
工程配置是创建一个结构良好、可维护的嵌入式项目的关键步骤。它定义了项目的整体结构、代码生成方式和中间件配置等重要参数。


4.NVIC配置
中断控制与优先级管理
4.1为什么选择SysTick作为时间基准源
在STM32微控制器项目中,许多与计时相关的功能通常依赖于软件定时器,即默认的SysTick定时器。如果SysTick定时器的优先级设置过低,它将频繁地被其他任务抢占,导致计时精度下降和系统响应延迟。
原因分析
软件定时器依赖
许多计时功能(如`HAL_Delay()`函数和各种超时机制)依赖于SysTick定时器提供的时间基准。
优先级的重要性
SysTick定时器的中断优先级决定了它在系统中断和任务调度中的地位。如果优先级过低,其他高优先级的中断和任务将频繁抢占SysTick定时器,影响其计时准确性。
系统稳定性
为了确保系统的计时功能稳定运行,必须合理设置SysTick定时器的优先级,以避免频繁的中断抢占。
推荐配置
在一般应用中,建议将SysTick的优先级设置为较高级别(0-2),以确保系统时基的准确性。如果项目中有更关键的实时响应需求,可根据具体情况调整,但应保证SysTick不会频繁被抢占。
4.2 NVIC配置要点
1中断分组设置
根据应用需求,选择合适的中断分组方式,以平衡抢占优先级和子优先级的分配。常用配置为"4 bits for pre-emption priority, 0 bits for subpriority"。
补充:中断分组理解
- 中断分组的核心是拆分 4 位优先级寄存器,分配「抢占优先级」和「子优先级」的位数占比;
- 抢占优先级决定中断能否嵌套,子优先级决定同抢占等级中断的执行顺序;
- "4 位抢占、0 位子优先级" 是分组 4,支持最多 16 级抢占嵌套,适用于复杂中断场景。
2优先级分配原则
为不同的中断源分配合适的优先级,通常遵循"响应时间越严格,优先级越高"的原则。例如,安全相关的中断应具有高优先级,而后台处理任务可以分配较低的优先级。
3中断嵌套考虑
在设计中断处理系统时,需要考虑中断嵌套的可能性。过度使用中断嵌套可能导致栈溢出和系统不稳定,应当谨慎设计。
4中断处理函数优化
中断处理函数应尽量简短,只执行必要的操作。可以使用标志位方式,在主循环中处理耗时操作,减少中断处理的时间。
5.Keil配置
Keil MDK是STM32开发中常用的集成开发环境,了解其工程文件结构对项目开发和维护至关重要。本节将介绍STM32CubeMX生成的Keil工程主要文件和目录的作用。
.mxproject
包含项目的元数据和配置。它通常是由STM32CubeMX等工具生成的,用于描述工程的整体设置和生成代码的选项。
Core
包含核心代码,如主程序、系统初始化和中断服务例程等。包括主程序入口、系统初始化和中断处理代码。
Drivers
包含设备驱动程序,通常包括外设的初始化和控制代码,以及HAL/LL库的硬件抽象层。
MDK-ARM
用于存放ARM MDK (Keil) 集成开发环境相关的文件,包含工程文件如.uvprojx和.uvoptx,以及编译设置。
MyAPP
用户自定义的应用程序代码目录,用于存放用户编写的应用逻辑代码和功能模块实现。
Yz_V1.ioc
I/O配置文件,用于描述微控制器的引脚和外设配置,定义外设的初始化参数和工作模式。

补充知识点:头文件保护


"#ifndef SCHEDULER_H #define SCHEDULER_H"这是什么东西,为什么要这么干?
这是 C 语言中非常基础且核心的头文件保护机制(也叫 "防止头文件重复包含宏")
这组指令是 C 语言的预处理指令 (编译前先执行的指令,不参与最终代码运行),三者是成对出现的(#ifndef和#endif必须配套,#define在中间):
| 指令 | 含义(通俗解释) |
|---|---|
#ifndef SCHEDULER_H |
if not defined 的缩写,意思是 "检查SCHEDULER_H这个宏是否未被定义 "→ 若未定义:执行后续代码(直到#endif)→ 若已定义:直接跳过后续所有代码(直到#endif) |
#define SCHEDULER_H |
定义SCHEDULER_H这个宏(无需给它赋值,仅作为 "标记" 使用),标记该头文件已被包含过 |
#endif |
结束#ifndef的条件判断块,相当于 "条件判断的结束符" |
简单类比:这就像图书馆的 "借书登记本"------
-
#ifndef SCHEDULER_H:检查登记本上有没有 "scheduler.h 已借出" 的记录; -
若没有记录(未定义):先做登记(
#define SCHEDULER_H),再把书借给你(执行头文件内容); -
若已有记录(已定义):直接拒绝重复借书(跳过头文件内容);
-
#endif:登记流程结束。
为什么必须要加这组指令?(核心用途:解决 "头文件重复包含" 问题)
在 C 语言项目中,头文件(.h)被重复包含是很常见的情况(比如你的代码中就存在:scheduler.h包含mydefine.h,mydefine.h又包含scheduler.h,这是 "循环包含";还有main.c同时包含mydefine.h和scheduler.h,也会导致scheduler.h被重复包含),而重复包含会引发严重的编译错误,具体危害如下:
- 直接导致编译报错的场景
-
类型 / 结构体重复定义 :如果头文件中定义了
task_t结构体,重复包含会让编译器看到两次struct task_t的定义,报 "redefinition of struct task_t"(结构体重定义)错误; -
宏定义重复 :如果头文件中有
#define MAX_TASK 10,重复包含会让编译器看到两次该宏定义,报 "redefinition of macro MAX_TASK"(宏重定义)错误; -
变量重复声明 :如果头文件中声明了全局变量(如
uint8_t task_num;,注:全局变量建议在.c文件定义,.h文件用extern声明),重复包含会导致 "multiple definition of task_num"(变量多重定义)错误。
- 间接影响:增加编译时间
即使某些重复包含不会报错(比如仅包含函数声明,函数声明允许重复),也会让编译器重复解析相同的头文件内容,浪费编译时间,项目越大,影响越明显。
而头文件保护宏的核心作用,就是确保任意一个头文件,在整个项目的编译过程中,只会被编译器解析一次,从根源上避免上述问题。
结合代码举例(帮你规避隐藏问题)
你的代码中存在 "循环包含":
-
scheduler.h→ 包含mydefine.h -
mydefine.h→ 包含scheduler.h
这种循环包含即使有头文件保护宏,也可能导致 "某些声明未定义" 的报错(比如编译器解析时,还没读到scheduler_init的声明就先解析mydefine.h,进而找不到对应函数)。