核心心法:如何阅读手册中的寄存器表格?

STM32F1系列下的不同产品子系列
- 基础/增强型:
STM32F10X_LD/_MD/_HD。这是最常用的分类,根据Flash容量大小划分-。STM32F103C8T6就属于这个类别,对应STM32F10X_MD。
- 互联型:
STM32F10X_CL(Connectivity Line)。指集成了更丰富外设(如以太网、USB OTG)的STM32F105/107系列。
- 超值型:
STM32F10X_LD_VL/_MD_VL/_HD_VL(Value Line)。专指性价比高、成本敏感的STM32F100系列。
频率和秒的知识点



格式化输出符号
C语言常用转义字符
- 转义字符以
\开头,用来表示无法直接输入 / 显示的特殊字符,或解决\、"这类符号的冲突问题。
转义字符 | ASCII 码(十进制 / 十六进制) | 功能说明 | 串口 / 终端常见场景 |
\0 | 0 / 0x00 | 字符串结束标志 | C 语言中 char[]的终止符,while(*str != '\0')就是靠它判断字符串结束 |
\n | 10 / 0x0A | 换行(Line Feed) | 光标移到下一行,但不回到行首 |
\r | 13 / 0x0D | 回车(Carriage Return) | 光标回到当前行的行首,但不换行 |
\r\n | 13+10 / 0x0D 0x0A | Windows 标准换行 | 同时实现「回到行首 + 换行」,串口助手里按回车就会发送这两个字节 |
\t | 9 / 0x09 | 水平制表符(Tab) | 光标跳到下一个 Tab 位置,用来对齐文本 |
\b | 8 / 0x08 | 退格(Backspace) | 光标回退一格,删除前一个字符 |
\f | 12 / 0x0C | 换页(Form Feed) | 在 Windows 超级终端 / 串口助手发送 \f(即0x0C)可实现清屏 |
\\ | 92 / 0x5C | 反斜杠本身 | 因为 \是转义起始符,要输出单个\必须写两个\\ |
\' | 39 / 0x27 | 单引号 | 解决 C 语言中 '作为字符边界的冲突 |
\" | 34 / 0x22 | 双引号 | 解决 C 语言中 "作为字符串边界的冲突 |
\ddd | 8 进制表示的字符 | 用 1-3 位 8 进制数表示 ASCII 字符 | 比如 \012等价于\n(8 进制 12 = 十进制 10) |
\xhh | 16 进制表示的字符 | 用 1-2 位 16 进制数表示 ASCII 字符 | 比如 \x0A等价于\n,\x0D等价于\r |
ANSI 终端控制码(彩色文本 + 格式控制)
- NSI 控制码以
\033[(即ESC[,\033是 ESC 的 8 进制表示)开头,用来控制终端的文本颜色、格式,支持大部分串口助手(如 DYS 串口助手、Xshell、PuTTY)
(1)基础格式控制码
开启码 | 对应关闭码 | 功能说明 | 杜洋串口助手 V1 支持情况 | 备注 |
0 | 无(全局重置) | 恢复所有默认设置(黑底白字,关闭所有格式) | ✅ 完美支持 | 最常用!所有格式输出后必须加这个,否则后续文字都会带格式 |
1 | 22 | 高亮 / 加粗显示 | ✅ 完美支持 | 最常用!让文字更醒目,调试信息必备 |
2 | 22 | 暗淡 / 低亮度显示 | ❌ 不支持 | 绝大多数串口助手都不支持 |
3 | 23 | 斜体显示 | ❌ 不支持 | 只有 Xshell、SecureCRT 等专业终端支持 |
4 | 24 | 下划线 | ✅ 完美支持 | 可以用来标记重要标题 |
5 | 25 | 慢速闪烁(<150 次 / 分钟) | ⚠️ 部分支持 | 杜洋串口助手会显示为高亮,不会真的闪烁 |
6 | 25 | 快速闪烁(>150 次 / 分钟) | ❌ 不支持 | 几乎没有终端支持 |
7 | 27 | 反白显示(前景色和背景色互换) | ✅ 完美支持 | 用来突出显示警告信息 |
8 | 28 | 隐藏 / 不可见 | ❌ 不支持 | 杜洋串口助手会显示为黑色文字 |
9 | 29 | 删除线 / 划除线 | ❌ 不支持 | 只有现代终端支持 |
(2)前景 / 背景颜色对照表
(2.1)基础8色(所有终端通用)
前景色代码 | 背景色代码 | 颜色名称 | 效果 |
30 | 40 | 黑色 | 深黑色 |
31 | 41 | 红色 | 深红色 |
32 | 42 | 绿色 | 深绿色 |
33 | 43 | 黄色 | 深黄色(偏棕) |
34 | 44 | 蓝色 | 深蓝色 |
35 | 45 | 紫红色 | 深紫色 |
36 | 46 | 青蓝色 | 深青色 |
37 | 47 | 白色 | 浅灰色(标准白色) |
(2.2)16 色(绝大多数串口助手支持,推荐使用)
- 在基础 8 色的基础上,增加了高亮版本,通过在颜色代码前加
1;(高亮属性)实现,颜色更鲜艳、更清晰
前景色代码 | 背景色代码 | 颜色名称 | 效果 |
30 | 40 | 黑色 | 深黑色 |
90 | 100 | 亮黑色 | 深灰色 |
31 | 41 | 红色 | 深红色 |
91 | 101 | 亮红色 | 鲜红色 |
32 | 42 | 绿色 | 深绿色 |
92 | 102 | 亮绿色 | 鲜绿色 |
33 | 43 | 黄色 | 深黄色 |
93 | 103 | 亮黄色 | 纯黄色 |
34 | 44 | 蓝色 | 深蓝色 |
94 | 104 | 亮蓝色 | 天蓝色 |
35 | 45 | 紫红色 | 深紫色 |
95 | 105 | 亮紫红色 | 洋红色 |
36 | 46 | 青蓝色 | 深青色 |
96 | 106 | 亮青蓝色 | 天青色 |
37 | 47 | 白色 | 浅灰色 |
97 | 107 | 亮白色 | 纯白色 |
- 示例(STM32):
(2.3)256 色(部分高级串口助手支持)
- 支持 Xshell、SecureCRT、MobaXterm 等专业终端
- 格式:
- 前景色:
38;5;颜色编号 - 背景色:
48;5;颜色编号
- 颜色编号范围:
0~255,分为 3 个区域: 0~15:对应上面的 16 色16~231:216 种 RGB 颜色(6×6×6 色板)232~255:24 级灰度(从黑到白)
- 示例:
(2.4)24 位真彩色(仅专业终端支持)
- 支持 Xshell 7+、SecureCRT 9+、Windows Terminal 等现代终端,绝大多数串口助手不支持
- 格式:
- 前景色:
38;2;R;G;B - 背景色:
48;2;R;G;B
- 其中 R、G、B 的范围是
0~255
- 示例:
(3)控制码格式与示例
- 格式:
\033[<格式>;<背景>;<前景>m,用\033[0m结束控制序列
- 示例 1:黑底绿色高亮文字
\033[1;40;32m:开启高亮、黑背景、绿前景
\033[0m:恢复默认格式,避免后续文本也带颜色
- 示例 2:红色闪烁文字
- 示例 3:带背景的蓝色文字
存储器映像


STM32F103xx增强型模块框图(后续设置SYSCLK、AHB、APB1、APB2等都需要查看这幅图)

时钟树(后续设置SYSCLK、AHB、APB1、APB2等都需要查看这幅图)

单片机内部电路常见的四种模式
51单片机识别模式方式

双向模式



开漏模式

推挽模式



- 上方MOS管:PMOS管,下方MOS管:NMOS管

左边NMOS管:高电平导通
右边PMOS管:低电平导通
浮空模式

一、四种方法实现的LED闪灯程序文件分析
main.c文件代码:
1.1 RCC_Configuration(void)
sys.c文件部分代码:
① 宏观目的

② 编写者视角🚀


③ RCC时钟是什么?为什么要从HSI设置为HSE+PLL?为什么需要外设时钟?




【代码解释】:
ErrorStatus HSEStartUpStatus
- 出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
- 含义:定义状态变量
- 我写这行的想法:定义一个叫
HSEStartUpStatus的变量,它的类型是ErrorStatus。我查到ErrorStatus只有两个值:SUCCESS和ERROR。我用它来存“等待外部晶振”的结果:成功了还是失败了。
- stm32f10x.h文件中ErrorStatus类型的定义代码:
RCC_DeInit( )
- 编写这行代码之前,芯片的时钟系统被人动过什么手脚(可能别的代码改过、或者系统从某个异常状态恢复)。我不能冒险在一个不确定的状态上直接配置,万一某个寄存器里还残留着上次的“半吊子”设置,我的配置加上去可能就乱了。所以我的第一件事是:全部清零,重新来过。
- 含义:恢复出厂时钟设置
- 设计意图:这是一个“回到原点”的操作。它不假定任何硬件状态,强制把时钟系统拨回最原始的上电默认值。这样我后续的配置就是从一张白纸开始,不会出现“旧配置残留导致异常”的棘手Bug。
- 我写这行的想法:先把时钟系统“恢复出厂设置”。为什么第一件事要做这个?因为我不知道之前系统被谁动过、处于什么状态。直接配置可能出乱子。所以我调用
RCC_DeInit,它帮我把所有时钟寄存器拨回到上电默认值(系统切回内部8MHz时钟,关掉外部晶振和PLL)。

- 出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
- stm32f10x_rcc.c文件中RCC_DeInit函数代码:
★(1)RCC->CR |= 0x01
- RCC_CR时钟控制寄存器在参考手册第60~61页有定义

- 其中与0x01或运算,代表将HSION置为1,其余不变(在或运算中,0代表位的值不变,1代表将改位置为1)


- 至于CR寄存器的其他位为什么不清零?
位 | 名称 | 类型 | 复位值 | 描述 |
0 | HSION | 控制位 | 1 | 内部高速时钟使能。复位后默认开。 |
1 | HSIRDY | 状态位 | ? | 硬件自动管理。HSI稳定后由硬件置1。 |
7:3 | HSITRIM | 控制位 | 16 | HSI频率微调,默认值16对应8MHz。 |
15:8 | HSICAL | 校准值 | ? | 这是芯片出厂时校准好的,保证HSI频率精准的。你要是改了它,8MHz就不准了,千万别改。 |
16 | HSEON | 控制位 | 0 | 外部高速时钟使能。复位后默认关。【当系统已经安全切回HSI,才能关闭;意思是先设置CFGR寄存器】 |
17 | HSERDY | 状态位 | 0 | 硬件自动管理。HSE稳定后由硬件置1。 |
18 | HSEBYP | 控制位 | 0 | 外部时钟旁路选择。【当系统已经安全切回HSI,才能关闭;意思是先设置CFGR寄存器】 |
19 | CSSON | 控制位 | 0 | 时钟安全系统使能。【当系统已经安全切回HSI,才能关闭;意思是先设置CFGR寄存器】 |
24 | PLLON | 控制位 | 0 | PLL使能。复位后默认关。【当系统已经安全切回HSI,才能关闭;意思是先设置CFGR寄存器】 |
25 | PLLRDY | 状态位 | 0 | 硬件自动管理。PLL锁定后由硬件置1。 |
★(2)RCC->CFGR &= (uint32_t)0xF8FF000
- 因为使用的是STM32F103C8T6芯片,所以是非互联型,因此条件判断为不是STM32F10X_CL。
- 根据这行代码,表示位0~15位清零,24~26位清零,其余位不变(在与运算中,0代表位的值清零,1代表不变)
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义







★(3)RCC->CR &= (uint32_t)0xFEF6FFFF
- RCC_CR时钟控制寄存器在参考手册第60~61页有定义

- 根据这行代码,表示第16、19、24位清零,其余不变




★(4)RCC->CR &= (uint32_t)0xFFFBFFFF
- RCC_CR时钟控制寄存器在参考手册第60~61页有定义

- 这行代码表示第18位清零,其余不变(旁路模式是用来接外部方波时钟的,我们用的是晶振,所以这个模式必须关掉。)


★(5)RCC->CFGR &= (uint32_t)0xFF80FFFF
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义
- 这行代码表示第16~22位清零,其余不变




★(6)最后的条件编译
- 有关参考手册内容,在本小节后面部分有图片显示











- 参考手册第85~86页
互联型:

- 参考手册第63~65页
非互联型:

- 参考手册第88~89页
互联型:


- 参考手册第101~102页
互联型:


RCC_HSEConfig(RCC_HSE_ON)
- 设计意图:函数先把“开关”和“旁路”都关干净,然后再按需要打开。这是一种“先复位再设置”的模式,保证任何情况调这个函数都是安全可预期的。
- 我写这行的想法:现在要打开8MHz外部晶振了。
RCC_HSE_ON是一个宏,值是0x00010000。我调用RCC_HSEConfig函数,它里面会把这个值写到硬件寄存器里。

- 参数RCC_HSE_ON的值为((uint32_t)0x00010000)【0000 0000 0000 0001 0000 0000 0000 0000】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
RCC_CR(时钟控制寄存器)- - - 与参数有关
- RCC_CR时钟控制寄存器在参考手册第60~61页有定义



- RCC_HSEConfig(RCC_HSE_ON)这个函数出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
★(1)RCC_CR &= CR_HSEON_Reset和RCC_CR &= CR_HSEBYP_Reset
- CR_HSEON_Reset的值为((uint32_t)0xFFFEFFFF),CR_HSEBYP_Reset的值为((uint32_t)0xFFFBFFFF),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_CR(时钟控制寄存器)- - - 与代码语句有关
- RCC_CR时钟控制寄存器在参考手册第60~61页有定义




- 含义:前者表示将HSEON这个16位进行清零操作,后者表示将HSEBYP这个18位进行清零操作,其余位不变(与运算,1表示不变,0表示清零)
HSEStartUpStatus = RCC_WaitForHSEStartUp( )
- HSEStartUpStatus这个变量的类型在第一行有说明(ErrorStatus,有两值:ERROR(0)、SUCCESS(1))
- RCC_WaitForHSEStartUp( )的作用(大白话):打开外部高速时钟(HSE)的开关之后,等着它“起床”,直到它彻底稳定,或者等了太久没反应就报错。
- 设计意图:等待一个外部硬件事件,必须带超时机制。如果晶振坏了或者虚焊了,程序总不能在这里死循环一万年。等1280次还没好,就返回
ERROR,让上层代码决策(比如降级用内部时钟跑,或者报错)。
- 我写这行的想法:通电只是开始,晶振需要时间“抖稳”。在手册第60页查到
HSERDY位(第17位),它会在晶振稳定后被硬件自动置1。我调用RCC_WaitForHSEStartUp函数,它会反复去查这个位,并把结果(成功或超时失败)存到HSEStartUpStatus里。

- 出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
★(1)HSEStatus = RCC_GetFlagStatus(RCC_FLAG_HSERDY)
- HSEStatus是FlagStatus类型的变量,有两值:RESET(0)、SET(1)
- HSEStatus的值是随硬件而变化的
- 参数RCC_FLAG_HSERDY的值为((uint8_t)0x31),出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
RCC_CR(时钟控制寄存器)- - - 与参数有关
- RCC_CR时钟控制寄存器在参考手册第60~61页有定义




RCC_BDCR(备份域控制寄存器)- - - 与参数有关
- RCC_BDCR备份域控制寄存器在参考手册第74页有定义


RCC_CSR(控制/状态寄存器)- - - 与参数有关
- RCC_CSR控制/状态寄存器在参考手册第75~76页有定义








★ RCC_GetFlagStatus(RCC_FLAG_HSERDY)是一个被调用函数
- 整体作用:看时钟是否达到稳定的状态
- 出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
★(2)while((StartUpCounter != HSE_STARTUP_TIMEOUT) && (HSEStatus == RESET))
- HSE_STARTUP_TIMEOUT出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义) - - - ✿ 固体库文件
- 首先判断计数StartUpCounter是否不等于1280,其次判断目标位的状态HSEStatus是否等于RESET(0);
- 因为这个是要判定时钟是否稳定,所以要进行的退出循环状态
- 那么如何退出循坏,要满足三个条件情况:一种是要计数值等于1280但HSEStatus等于RESET(0),一种是计数值等于1280且HSEStatus不等于RESET(0),一种是计数值不等于1280但HSEStatus不等于RESET(0)
- 其中第一个条件情况是代表时钟还没有稳定,二三条件代表时钟稳定
if(HSEStartUpStatus == SUCCESS){ /*省略*/ }
- 我写这行的想法:如果外部晶振启动成功了,才进行后面的倍频和切换操作。因为外部晶振是PLL的输入源,它没稳定,后面一切免谈。
- 含义:如果HSEStartUpStatus是否为SUCCESS(1),则执行下面条件判断内部语句
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMUl_9)
- 设计意图:采用“读-改-写”模式,只修改需要改变的位,不影响其他无关位。同时必须先关闭 PLL 才能修改配置,前面的
RCC_DeInit已经确保 PLL 是关闭的。
- 我写这行的想法:晶振好了,现在设置倍频器PLL。我要告诉PLL两件事:
(1)输入源:用外部晶振(
RCC_PLLSource_HSE_Div1 = 0x00010000)。(2)倍频系数:乘9倍(
RCC_PLLMul_9 = 0x001C0000)。
- 第一个参数RCC_PLLSource_HSE_Div1的值为((uint32_t)0x00010000)【000 0000 0000 0001 0000 0000 0000 0000】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
RCC_CFGR(时钟配置寄存器)- - - 与参数有关
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义



- 第二个参数RCC_PLLMul_9的值为((uint32_t)0x001C0000)【0000 0000 0001 1100 0000 0000 0000 0000】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
RCC_CFGR(时钟配置寄存器)- - - 与参数有关
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义


- RCC_PLLConfig函数出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
★(1)tmpreg &= CFGR_PLL_Mask
- CFGR_PLL_Mask的值为((uint32_t)0xFFC0FFFF),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_CFGR(时钟配置寄存器)- - - 与代码语句有关
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义




- 这行代码表示需要对RCC_CFGR(时钟配置寄存器)进行PLL设置,则需要对目标位(即第16~21位进行清零操作)
RCC_HCLKConfig(RCC_SYSCLK_Div1)
- 设计意图:AHB 是系统主总线,连接 CPU、内存、DMA 等核心部件,设为不分频以获得最高吞吐性能。
- 我写这行的想法:设完PLL参数,现在设AHB总线(主血管)的速度。
RCC_SYSCLK_Div1=0x00000000,意思是“不分频”。等会儿系统时钟是多少,AHB总线就跑多少。

- 参数RCC_SYSCLK_Div1的值为((uint32_t)0x00000000)【0000 0000 0000 0000 0000 0000 0000 0000】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
RCC_CFGR(时钟配置寄存器)- - - 与参数有关
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义


- RCC_HCLKConfig函数出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
★(1)tmpreg &= CFGR_HPRE_Reset_Mask
- CFGR_HPRE_Reset_Mask的值为((uint32_t)0xFFFFFF0F),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_CFGR(时钟配置寄存器)- - - 与代码语句有关
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义


- 这条语句表示对CRR_CFGR(时钟配置寄存器)进行第4~7位的清零操作
RCC_PCLK1Config(RCC_HCLK_Div2)和RCC_PCLK2Config(RCC_HCLK_Div1)
- 设计意图:前者:APB1 总线连接定时器、串口、I2C 等外设,手册规定其最高频率为 36MHz。当 HCLK 跑 72MHz 时,必须至少 2 分频才能保证这些外设不会超频损坏。后者:APB2 连接 GPIO、USART1、SPI1 等高速外设,支持 72MHz 全速运行,所以不分频。
- 我写这两行的想法:前者:设低速外设总线APB1的速度。
RCC_HCLK_Div2=0x00000400,意思是“2分频”。等会儿AHB跑72MHz,APB1就跑36MHz。手册上查到APB1最高只能跑36MHz,所以必须分频。后者:设高速外设总线APB2的速度。它也能跑72MHz,所以不用分频。RCC_HCLK_Div1=0x00000000。


- 参数RCC_HCLK_Div2的值为((uint32_t)0x00000400)【0000 0000 0000 0000 0000 0100 0000 0000】,参数RCC_HCLK_Div1的值为((uint32_t)0x00000000)【0000 0000 0000 0000 0000 0000 0000 0000】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
RCC_CFGR(时钟配置寄存器)- - - 与参数有关
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义



- RCC_PCLK1Config函数出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
★(1)tmpreg &= CFGR_PPRE1_Reset_Mask
- CFGR_PPRE1_Reset_Mask的值为((uint32_t)0xFFFFF8FF),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_CFGR(时钟配置寄存器)- - - 与代码语句有关
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义


- 这条语句表示对CRR_CFGR(时钟配置寄存器)进行第8~10位的清零操作
- RCC_PCLK2Config函数出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
★(1)tmpreg &= CFGR_PPRE2_Reset_Mask
- CFGR_PPRE2_Reset_Mask的值为((uint32_t)0xFFFFC7FF),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_CFGR(时钟配置寄存器)- - - 与代码语句有关
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义


- 这条语句表示对CRR_CFGR(时钟配置寄存器)进行第11~13位的清零操作
FLASH_SetLatency(FLASH_Latency_2)
- 设计意图:当系统时钟超过 48MHz 时,Flash 存储器的读取速度跟不上 CPU 的取指速度。手册规定 48MHz~72MHz 必须使用 2 个等待周期。这一步如果不做,CPU 可能读到错误的指令,系统立即崩溃。
- 我写这行的想法:CPU跑72MHz太快了,FLASH存储器跟不上。我在手册上查到,频率超过48MHz时必须设置2个等待周期。
FLASH_Latency_2=0x00000002

为什么要设置FLASH?
- 把单片机理解成一个工厂:
- CPU:是工厂里手脚麻利的工人。
- 系统时钟频率 (72MHz):是工人的干活速度。频率越高,他取指令和执行指令的节奏就越快。
- Flash 存储器:是存放所有程序指令的仓库。
- Flash 等待周期:是工人到达仓库后,喊“管理员,把下一条指令给我!”,然后站在原地等待的时间。
- 核心矛盾来了:工人(CPU)的干活速度是可以调快的(超频到72MHz)。但是,仓库管理员(Flash控制电路)从货架上拿东西的物理速度是有上限的,他没法无限快。
- 在默认8MHz低速时,工人慢悠悠去仓库,管理员能轻松在他下次来之前把货备好。
- 当我们突然把工人的节奏加到72MHz,他跑去仓库催货的频率瞬间提高了9倍。管理员动作再快也跟不上了。如果工人不等管理员拿货就走,那他拿走的就是一个错误的空箱子,工厂(程序)就要出大乱子(跑飞或死机)。
- 设置Flash等待周期,就是告诉工人:“兄弟,你以后跑快了,到仓库提货时,必须原地多等我2个周期(这就是
FLASH_Latency_2的含义),确保我把正确的货交给你才能走。”
- 参数FLASH_Latency_2的值为((uint32_t)0x00000002)【0000 0000 0000 0000 0000 0000 0000 0010】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_flash.h(外设固体库头文件)- - - ✿ 固体库文件
FLASH_ACR(闪存访问控制寄存器)- - - 与参数有关
- FLASH_ACR闪存访问控制寄存器在STM32F10xxx闪存编程手册第15页有定义


- FLASH_SetLatency函数出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
★(1)tmpreg &= ACR_LATENCY_Mask
- ACR_LATENCY_Mask的值为((uint32_t)0x00000038),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
FLASH_ACR(闪存访问控制寄存器)- - - 与代码语句有关
- FLASH_ACR闪存访问控制寄存器在STM32F10xxx闪存编程手册第15页有定义


- 这条语句表示对FLASH_ACR(闪存访问控制寄存器)进行第0~2位的清零操作
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable)
- 设计意图:预取缓冲是硬件加速功能。开启后,Flash 控制器会在 CPU 读取指令的同时预读后面的指令放进缓冲区。当 CPU 需要下一条指令时可以直接从缓冲区取,减少等待时间。这能显著补偿等待周期带来的性能损失,让程序在 72MHz 下也能接近“零等待”执行。
- 我写这行的想法:打开FLASH的“预取缓冲”功能。这能让FLASH在读当前指令时,偷偷把后面几条指令也读进来,CPU就不用等了,能大大提速。

- 参数FLASH_PrefetchBuffer_Enable的值为((uint32_t)0x00000010)【0000 0000 0000 0000 0000 0000 0001 0000】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_flash.h(外设固体库头文件)- - - ✿ 固体库文件
FLASH_ACR(闪存访问控制寄存器)- - - 与参数有关
- FLASH_ACR闪存访问控制寄存器在STM32F10xxx闪存编程手册第15页有定义


- FLASH_PrefetchBufferCmd函数出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
★(1)FLASH->ACR &= ACR_PRFTBE_Mask
- ACR_PRFTBE_Mask的值为((uint32_t)0xFFFFFFEF),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
FLASH_ACR(闪存访问控制寄存器)- - - 与参数有关
- FLASH_ACR闪存访问控制寄存器在STM32F10xxx闪存编程手册第15页有定义


- 这条语句表示对FLASH_ACR(闪存访问控制寄存器)进行第4位的清零操作
RCC_PLLCmd(ENABLE)
- 设计意图:PLL 参数已设好,总线已配好,Flash 已调优,现在可以按下“启动键”。位带操作比“读-改-写”更快、更安全,避免了中断干扰的风险。
- 我写这行的想法:现在PLL的参数设好了,系统总线的分频也设好了,FLASH的等待周期也调好了。万事俱备,可以“点火”启动PLL了!
ENABLE的值是1。

- 参数ENABLE为枚举类型FunctionalState,值为1,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
- RCC_PLLCmd函数出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
★(1)*(__IO uint32_t *) CR_PLLON_BB = (uint32_t)NewState
- CR_PLLON_BB涉及到“位段”的知识点,“位段”出自参考手册第29~30页有定义


- CR_PLLON_BB的值为0x42420060,它的值的计算分别出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件 ,
出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
计算步骤:(外设RCC的起始地址为0x4002 1000 ~ 0x4002 13FF)





*(__IO uint32_t *) CR_PLLON_BB两个*号的含义





while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
- 设计意图:PLL 启动后,内部电路需要时间将频率“锁定”到目标值。这个时间是不确定的(取决于环境和电路),必须用死循环等待硬件给出“就绪”信号。不等待就直接切换时钟,系统必然崩溃。
- 我写这行的想法:PLL启动后需要时间稳定,不能立刻切换过去。在手册里找到
PLLRDY位(第25位),它会在PLL稳定后被硬件置1。RCC_FLAG_PLLRDY=0x39就是指向这一位的暗号。我就在这死等,直到RCC_GetFlagStatus告诉我它好了。

- 参数RCC_FLAG_PLLRDY的值为((uint8_t)0x39)【0011 1001,(1,25)】,代表RCC_CR(时钟控制寄存器)的第26位PLLRDY(PLL时钟就绪标志);这个第25位是有硬件设置的,所以主要是看上一步代码是否成功设置第24位PLLON(PLL使能)为1;


- RCC_GetFlagStatus(RCC_FLAG_PLLRDY)这个函数在之前的“HSEStartUpStatus = RCC_WaitForHSEStartUp( )”的“★(1)
HSEStatus = RCC_GetFlagStatus(RCC_FLAG_HSERDY)”有详细解释,可往上面翻一番;
- 这行代码其实就是让你判断第25位PLLRDY(PLL时钟就绪标志)是否设置为1了,设置成功,就借宿循坏,进入下一步代码;
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK)
- 设计意图:SW 位是系统时钟的“选择开关”。将开关拨到 PLL 档,硬件就会开始切换过程,但并不是瞬间完成。
- 我写这行的想法:PLL稳定了,现在把系统时钟从HSI(内部8MHz)切换到PLL(72MHz)。
RCC_SYSCLKSource_PLLCLK=0x00000002。

- 参数RCC_SYSCLKSource_PLLCLK的值为((uint32_t)0x00000002)【0000 0000 0000 0000 0000 0000 0000 0010】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
- 函数RCC_SYSCLKConfig出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
★(1)tmpreg &= CFGR_SW_Mask
- CFGR_SW_Mask的值为((uint32_t)0xFFFFFFFC),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_CFGR(时钟配置寄存器)- - - 与代码语句有关
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义


- 这条语句表示对CRR_CFGR(时钟配置寄存器)进行第0~1位的清零操作
while(RCC_GetSYSCLKSource() != 0x08)
- 设计意图:SW 是我们要的目标,SWS 是硬件切换完成的回执。手册明确指出,必须检查 SWS 位来确认切换真正完成。时钟源切换不是原子操作,需要数个时钟周期。如果不等待,CPU 可能在时钟切换的“震荡期”执行指令,后果不可预测。
- 我写这行的想法:虽然我写了“请切换到PLL”的命令,但硬件切换需要几个时钟周期。
RCC_GetSYSCLKSource函数会去读CFGR寄存器里的SWS位(状态位)。当SWS位变成10(二进制)时,整个寄存器的对应值是0x08。我就死等,直到确认硬件真的切换完成了。

- RCC_GetSYSCLKSource( )函数出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
- CFGR_SWS_Mask的值为((uint32_t)0x0000000C),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_CFGR(时钟配置寄存器)- - - 与代码语句有关
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义


RCC->CFGR & CFGR_SWS_Mask这条语句表示对CRR_CFGR(时钟配置寄存器)进行第2~3位的清零操作
RCC_APB2PeriphClockCmd(两个参数)
- 设计意图:芯片为了省电,外设时钟默认关上。你不开,GPIO 和串口就没法工作。这行代码就是“我明确告诉你,这些水管我要用,请给它们通水”。
- 我写这行的想法:时钟系统搞定了,现在“打开水龙头”,给外设供血。我把要用的外设(串口1、GPIO A/B/C/D/E)的位用
|运算合在一起,打包发给RCC_APB2PeriphClockCmd函数。这些外设都挂在APB2总线上,所以我要去APB2ENR寄存器里把对应的位置1。

- 第一个参数为外设的相关变量,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
RCC_APB2ENR(APB2外设时钟使能寄存器)
- APB2外设时钟使能寄存器RCC_APB2ENR在参考手册第70~71页有定义,英文版第112~114页有定义(其中TIM15~17,参考手册和英文版都没有找到对应定义处)





- 第二个参数ENABLE为枚举类型FunctionalState,值为1;DISABLE,值为0;出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
- 函数RCC_APB2PeriphClockCmd出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_APB1PeriphClockCmd(两个参数)
- 设计意图:芯片为了省电,外设时钟默认关上。你不开,GPIO 和串口就没法工作。这行代码就是“我明确告诉你,这些水管我要用,请给它们通水”。
- 第一个参数为外设的相关变量,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
RCC_APB1ENR(APB1外设时钟使能寄存器)
- APB1外设时钟使能寄存器RCC_APB1ENR在参考手册第71~73页有定义,英文版第115~117页有定义(其中CAN2(第26位)和CEC(第30位),参考手册和英文版都没有找到对应定义处)












- 第二个参数ENABLE为枚举类型FunctionalState,值为1;DISABLE,值为0;出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
- 函数RCC_APB1PeriphClockCmd,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
1.2 LED_Init(void)
led.c文件代码
GPIO_InitTypeDef GPIO_InitStructure
- 含义:创建GPIO_InitTypeDef的变量
- GPIO_InitTypeDef这个类型,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_gpio.h(外设固体库头文件)- - - ✿ 固体库文件
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE)
- 含义:在外设时钟使能寄存器RCC_APB2ENR,打开所需要的GPIOA、GPIOB、GPIOC三个外设IO端口的位的开关
- 在1.1 RCC_Configuration(void)的RCC_APB2PeriphClockCmd(两个参数)有说明其详细代码和作用
GPIO_InitStructure.GPIO_Pin = LED1 | LED2
- 含义:引脚设置为LED1和LED2,即第0个和第1个引脚,GPIO_InitStructure.GPIO_Pin = 0x0003
- LED1和LED2出自led.h文件
#define LED1 PBout(0)和#define LED2 PBout(1)
- 含义:定义LED1为PBout(0)【值为0x42218180,代表对ODR寄存器的第0位0DR0进行修改读取的虚拟地址(位段)】,定义LED2为PBout(1)【值为0x42218184,代表对ODR寄存器的第1位0DR1进行修改读取的虚拟地址(位段)】
- “位段”出自参考手册第29~30页有定义


- PBout(0或1)出自sys.h文件
GPIOA/B/C/D/E/F/G_BASE,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
- 例子中的PBout(1):
POut(0):
#define LEDPORT GPIOB
- 含义:定义LEDPORT为GPIOB,其中GPIOB的十六进制值代表在寄存器的起地址0x40010C00
- GPIO端口B在寄存器的起始地址:0x4001 0C00 ~ 0x4001 0FFF
- GPIOB,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
- GPIO_TypeDef,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件

- GPIOA/B/C/D/E/F/G_BASE,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
计算下来:(GPIOA~G计算出来的十六进制为该外设在存储器映像中寄存器的开始地址,即基地址)
#define LED1 GPIO_Pin_0和#define LED2 GPIO_Pin_1
- 含义:定义LED1为GPIO_Pin_0【0x0001,表示第0个引脚被选中】,定义LED2为GPIO_Pin_1【0x0002,表示第1个引脚被选中】
- GPIO_Pin_0或1,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_gpio.h(外设固体库头文件)- - - ✿ 固体库文件
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP
- 含义:将GPIO的工作模式设置为推挽输出(0x10)
- GPIO_Mode_Out_PP为推挽输出(0x10),出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_gpio.h(外设固体库头文件)- - - ✿ 固体库文件
端口配置高寄存器(GPIOx_CRH)
- GPIOx_CRH在参考手册第106页和参考手册第114页有定义



GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz
- 含义:将GPIO的速度设置为50MHz
- GPIO_Speed_50MHz为2,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_gpio.h(外设固体库头文件)- - - ✿ 固体库文件
GPIO_Init(LEDPORT, &GPIO_InitStructure)
- 含义:调用GPIO_Init函数,其目的在于将前几行设置的值写入对应外设的寄存器中
- 第一个参数在之前的“
GPIO_InitStructure.GPIO_Pin = LED1 | LED2”的“#define LEDPORT GPIOB”有说明,即为GPIOB的基地址(0x0x40010C00),GPIO端口B在寄存器的起始地址:0x4001 0C00 ~ 0x4001 0FFF。LEDPORT(即GPIOB)本质上是一个指向地址0x40010C00的结构体指针。
- 第二个参数则为之前说明的GPIO_InitStructure变量(内部根据前几行代码设置的值:0x0003(引脚0和1)、0x10(推挽输出)、2(50MHz)),GPIO_InitStructure表示结构体本身,而&GPIO_InitStructure表示指向这个结构体的地址,因为函数内部要拿到结构体的地址,才能对其本身进行修改
- GPIO_Init函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_gpio.c(外设固体库文件)- - - ✿ 固体库文件
端口配置低寄存器(GPIOx_CRL)
- GPIOx_CRL端口配置低寄存器在参考手册第113页有定义

端口配置高寄存器(GPIOx_CRH)
- GPIOx_CRH端口配置高寄存器在参考手册第114页有定义

端口输出数据寄存器(GPIOx_ODR)
- GPIOx_ODR端口输出数据寄存器在参考手册第115页有定义

端口位设置/清除寄存器(GPIOx_BSRR)
- GPIOx_BSRR端口位设置/清除寄存器在参考手册第115页有定义

端口位清除寄存器(GPIOx_BRR)
- GPIOx_BRR端口位清除寄存器在参考手册第115~116页有定义


1.3 GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1或0或1-GPIO_ReadOutputDataBit(LEDPORT,LED1)))
- 含义:BitAction(1)为高电平,BitAction(0)为低电平
- 第一个参数在之前的“
1.2 LED_Init(void)”的“GPIO_InitStructure.GPIO_Pin = LED1 | LED2”的“#define LEDPORT GPIOB”有说明,即为GPIOB的基地址(0x0x40010C00),GPIO端口B在寄存器的起始地址:0x4001 0C00 ~ 0x4001 0FFF。LEDPORT(即GPIOB)本质上是一个指向地址0x40010C00的结构体指针。
- 第二个参数LED1在之前的“
1.2 LED_Init(void)”的“GPIO_InitStructure.GPIO_Pin = LED1 | LED2”的“#define LED1 GPIO_Pin_0和#define LED2 GPIO_Pin_1”有说明,即(uint16_t)0x0001
- 第三个参数(BitAction)(1)代表强制将1转换为BitAction,则为Bit_Set;(BitAction)(0)代表强制将0转换为BitAction,则为Bit_RESET。BitAction出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_gpio.h(外设固体库头文件)- - - ✿ 固体库文件
- 第三个参数还有一个情况:(BitAction)(1-GPIO_ReadOutputDataBit(LEDPORT,LED1))
- 这个1-GPIO_ReadOutputDataBit(LEDPORT,LED1)是取反的意思。
- 假设GPIO_ReadOutputDataBit(LEDPORT,LED1)为0时,1-GPIO_ReadOutputDataBit(LEDPORT,LED1)为1。(BitAction)(1)。
- 假设GPIO_ReadOutputDataBit(LEDPORT,LED1)为1时,1-GPIO_ReadOutputDataBit(LEDPORT,LED1)为0。(BitAction)(0)。
- GPIO_ReadOutputDataBit(LEDPORT,LED1),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_gpio.c(外设固体库文件)- - - ✿ 固体库文件
- GPIO_WriteBit函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_gpio.c(外设固体库文件)- - - ✿ 固体库文件
端口输出数据寄存器(GPIOx_ODR)
- GPIOx_ODR端口输出数据寄存器在参考手册第115页有定义

端口位设置/清除寄存器(GPIOx_BSRR)
- GPIOx_BSRR端口位设置/清除寄存器在参考手册第115页有定义

端口位清除寄存器(GPIOx_BRR)
- GPIOx_BRR端口位清除寄存器在参考手册第115~116页有定义


1.4 GPIO_SetBits(LEDPORT,LED1)和GPIO_ResetBits(LEDPORT,LED1)
- 含义:前者让引脚0为高电平,后者让引脚0为低电平
- 第一个参数在之前的“
1.2 LED_Init(void)”的“GPIO_InitStructure.GPIO_Pin = LED1 | LED2”的“#define LEDPORT GPIOB”有说明,即为GPIOB的基地址(0x0x40010C00),GPIO端口B在寄存器的起始地址:0x4001 0C00 ~ 0x4001 0FFF。LEDPORT(即GPIOB)本质上是一个指向地址0x40010C00的结构体指针。
- 第二个参数LED1在之前的“
1.2 LED_Init(void)”的“GPIO_InitStructure.GPIO_Pin = LED1 | LED2”的“#define LED1 GPIO_Pin_0和#define LED2 GPIO_Pin_1”有说明,即(uint16_t)0x0001
- GPIO_SetBits和GPIO_ResetBits两个函数,都出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_gpio.c(外设固体库文件)- - - ✿ 固体库文件
端口输出数据寄存器(GPIOx_ODR)
- GPIOx_ODR端口输出数据寄存器在参考手册第115页有定义

端口位设置/清除寄存器(GPIOx_BSRR)
- GPIOx_BSRR端口位设置/清除寄存器在参考手册第115页有定义

端口位清除寄存器(GPIOx_BRR)
- GPIOx_BRR端口位清除寄存器在参考手册第115~116页有定义


1.5 GPIO_Write(LEDPORT,0x0001或者0x0000)
- 含义:直接数值操作将变量值写入LED
- 第一个参数在之前的“
1.2 LED_Init(void)”的“GPIO_InitStructure.GPIO_Pin = LED1 | LED2”的“#define LEDPORT GPIOB”有说明,即为GPIOB的基地址(0x0x40010C00),GPIO端口B在寄存器的起始地址:0x4001 0C00 ~ 0x4001 0FFF。LEDPORT(即GPIOB)本质上是一个指向地址0x40010C00的结构体指针。
- GPIO_Write函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_gpio.c(外设固体库文件)- - - ✿ 固体库文件
1.6 delay_us、delay_ms、delay_s
- 三个函数,出自delay.c代码文件
- SysTick的来源,出自CMSIS文件夹下的内核相关的core_cm3.h(内核寄存器定义)- - - ✿ 固体库文件




二、按键控制LED程序
main.c文件代码文件:


2.1 KEY_Init(void)
key.c代码文件
GPIO_InitStructure.GPIO_Pin = KEY1 | KEY2
- 含义:引脚设置为KEY1和KEY2,即第0个和第1个引脚,GPIO_InitStructure.GPIO_Pin = 0x0003
- KEY1和KEY2出自key.h文件
#define KEY1 PAin(0)和#define KEY2 PAin(1)
- 含义:定义KEY1为PAin(0)【值为0x42210100,代表对IDR寄存器的第0位IDR0进行修改读取的虚拟地址(位段)】,定义KEY2为PAin(1)【值为0x42210104,代表对IDR寄存器的第1位IDR1进行修改读取的虚拟地址(位段)】
- “位段”出自参考手册第29~30页有定义


- PAin(0或1)出自sys.h文件
GPIOA/B/C/D/E/F/G_BASE,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
- 例子中的PAin(1):
PAin(0):
2.2 GPIO_ReadInputDataBit(KEYPORT,KEY1)
- GPIO_ReadInputDataBit(KEYPORT,KEY1),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_gpio.c(外设固体库文件)- - - ✿ 固体库文件
端口输入数据寄存器(GPIOx_IDR)
- GPIOx_IDR端口输出数据寄存器在参考手册第114页有定义

Bit_SET和Bit_RESET
- BitAction出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_gpio.h(外设固体库头文件)- - - ✿ 固体库文件
三、FLASH读写程序
main.c文件代码文件:
3.1 FLASH_R和FLASH_W
flash.c代码文件:

RCC_HSICmd(ENABLE)
- 含义:打开HSI时钟
- 参数ENABLE为枚举类型FunctionalState,值为1,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
- RCC_HSICmd函数出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
*(__IO uint32_t *) CR_HSION_BB = (uint32_t)NewState
- 含义:由于NewState是ENABLE(1),所以将1写入RCC_CR(时钟控制寄存器)的位0的虚拟地址0x42420000,即将位0(HSION)设置为1;
- CR_HSION_BB涉及到“位段”的知识点,“位段”出自参考手册第29~30页有定义


- CR_HSION_BB的值为0x42420000,它的值的计算分别出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
计算步骤:(外设RCC的起始地址为0x4002 1000 ~ 0x4002 13FF)
FLASH_Unlock()
- 含义:解锁FLASH编程擦除控制器
- FLASH_Unlock函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR)
- 含义:清除标志位
- FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR = 0x00000035【0000 0000 0000 0000 0000 0000 0011 0101】
- 参数FLASH_FLAG_BSY、FLASH_FLAG_EOP、FLASH_FLAG_PGERR、FLASH_FLAG_WRPRTERR,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_flash.h(外设固体库头文件)- - - ✿ 固体库文件
FLASH_SR(闪存状态寄存器)
- FLASH_SR闪存状态寄存器在STM32F10xxx闪存编程手册第16~17页有定义



FLASH_OBR(选项字节寄存器)
- FLASH_OBR选项字节寄存器在STM32F10xxx闪存编程手册第18页有定义


- FLASH_ClearFlag函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
FLASH_ErasePage(add)
- 含义:擦除指定地址页
- FLASH_ErasePage函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
FLASH_CR(闪存控制寄存器)
- FLASH_CR闪存控制寄存器在STM32F10xxx闪存编程手册第17页有定义



FLASH_AR(闪存地址寄存器)
- FLASH_AR闪存地址寄存器在STM32F10xxx闪存编程手册第18页有定义

FLASH_Status status = FLASH_COMPLETE
- 含义:FLASH的状态为操作成功完成(值为4),即对应FLASH_SR(闪存状态寄存器)的第5位
- 类型FLASH_Status,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_flash.h(外设固体库头文件)- - - ✿ 固体库文件
枚举值 | 数值 | 含义(大白话) | 手册对应位置 | 对应硬件标志位 |
FLASH_BUSY | 1 | FLASH 正在忙,无法进行新的操作 | 第 16 页,3.4 节,位 0 | FLASH_SR->BSY |
FLASH_ERROR_PG | 2 | 编程错误,试图向未擦除的地址写入数据 | 第 16 页,3.4 节,位 2 | FLASH_SR->PGERR |
FLASH_ERROR_WRP | 3 | 写保护错误,试图写入被写保护的 FLASH 页 | 第 16 页,3.4 节,位 4 | FLASH_SR->WRPRTERR |
FLASH_COMPLETE | 4 | 操作成功完成 | 第 16 页,3.4 节,位 5 | FLASH_SR->EOP |
FLASH_TIMEOUT | 5 | 操作超时,这是软件添加的状态,手册中没有 | 无 | 无 |
FLASH_SR(闪存状态寄存器)
- FLASH_SR闪存状态寄存器在STM32F10xxx闪存编程手册第16~17页有定义



assert_param(IS_FLASH_ADDRESS(Page_Address))
- 含义:检查参数是否合法
- IS_FLASH_ADDRESS函数,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_flash.h(外设固体库头文件)- - - ✿ 固体库文件
芯片类型 | FLASH 容量 | 实际结束地址 | 是否小于 0x080FFFFF |
小容量 | 16K~32K | 0x08007FFF | ✅ |
中容量 | 64K~128K | 0x0801FFFF | ✅ |
大容量 | 256K~512K | 0x0807FFFF | ✅ |
互联型 (F105/F107) | 128K~256K | 0x0803FFFF | ✅ |
XL 密度 (F10x_XL) | 1MB | 0x080FFFFF | ✅(刚好等于) |
芯片类型 | FLASH 总容量 | BANK 数量 | BANK1 地址范围 | BANK2 地址范围 | 你的 C8T6 属于哪类 |
小容量 | 16K~32K | 1 个 | 0x08000000~0x08007FFF | 无 | ❌ |
中容量 | 64K~128K | 1 个 | 0x08000000~0x0801FFFF | 无 | ✅(C8T6 是 64K) |
大容量 | 256K~512K | 1 个 | 0x08000000~0x0807FFFF | 无 | ❌ |
XL 密度 | 1MB | 2 个 | 0x08000000~0x0807FFFF(512K) | 0x08080000~0x080FFFFF(512K) | ❌ |
status = FLASH_WaitForLastBank1Operation(EraseTimeout)
- 含义:返回当前FLASH的状态
- FLASH_waitForLastBank1Operation函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
status = FLASH_GetBank1Status()
- 含义:获取FLASH的状态
- STM32 的 FLASH 擦除和写入操作,不是 CPU 指令直接完成的,而是由一个叫 FPEC(闪存编程 / 擦除控制器) 的独立硬件模块完成的。整个流程的大白话类比:你(CPU)让一个工人(FPEC)去搬砖(擦除 / 写入 FLASH):
- 你先告诉工人要搬哪块砖(写地址寄存器)
- 你喊一声 "开始干活"(写 STRT 位触发操作)
- 工人开始干活,同时举起一个 "正在忙" 的牌子(硬件自动将 BSY 位置 1)
- 你不能催他,也不能让他同时干另一个活,只能站在旁边等
- 工人干完活,放下 "正在忙" 的牌子(硬件自动将 BSY 位清 0)
- 如果干活过程中出错了,他会举起 "编程错误" 或 "写保护错误" 的牌子(硬件自动置 PGERR/WRPRTERR 位)
- 如果顺利干完,他会举起 "干完了" 的牌子(硬件自动置 EOP 位)
- FLASH_GetBank1Status函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
FLASH_SR(闪存状态寄存器)
- FLASH_SR闪存状态寄存器在STM32F10xxx闪存编程手册第16~17页有定义



status = FLASH_WaitForLastBank2Operation(EraseTimeout)
- 含义:返回当前FLASH的状态
- FLASH_waitForLastBank2Operation函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
status = FLASH_GetBank2Status()
- 含义:获取FLASH的状态
- STM32 的 FLASH 擦除和写入操作,不是 CPU 指令直接完成的,而是由一个叫 FPEC(闪存编程 / 擦除控制器) 的独立硬件模块完成的。整个流程的大白话类比:你(CPU)让一个工人(FPEC)去搬砖(擦除 / 写入 FLASH):
- 你先告诉工人要搬哪块砖(写地址寄存器)
- 你喊一声 "开始干活"(写 STRT 位触发操作)
- 工人开始干活,同时举起一个 "正在忙" 的牌子(硬件自动将 BSY 位置 1)
- 你不能催他,也不能让他同时干另一个活,只能站在旁边等
- 工人干完活,放下 "正在忙" 的牌子(硬件自动将 BSY 位清 0)
- 如果干活过程中出错了,他会举起 "编程错误" 或 "写保护错误" 的牌子(硬件自动置 PGERR/WRPRTERR 位)
- 如果顺利干完,他会举起 "干完了" 的牌子(硬件自动置 EOP 位)
- FLASH_GetBank2Status函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
FLASH_SR(闪存状态寄存器)- - - SR2与之一样,只是名字变了
- FLASH_SR闪存状态寄存器在STM32F10xxx闪存编程手册第16~17页有定义



status = FLASH_WaitForLastOperation(EraseTimeout)
- 含义:返回当前FLASH的状态
- FLASH_waitForLastOperation函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
status = FLASH_GetBank1Status()
- 含义:获取FLASH的状态
- STM32 的 FLASH 擦除和写入操作,不是 CPU 指令直接完成的,而是由一个叫 FPEC(闪存编程 / 擦除控制器) 的独立硬件模块完成的。整个流程的大白话类比:你(CPU)让一个工人(FPEC)去搬砖(擦除 / 写入 FLASH):
- 你先告诉工人要搬哪块砖(写地址寄存器)
- 你喊一声 "开始干活"(写 STRT 位触发操作)
- 工人开始干活,同时举起一个 "正在忙" 的牌子(硬件自动将 BSY 位置 1)
- 你不能催他,也不能让他同时干另一个活,只能站在旁边等
- 工人干完活,放下 "正在忙" 的牌子(硬件自动将 BSY 位清 0)
- 如果干活过程中出错了,他会举起 "编程错误" 或 "写保护错误" 的牌子(硬件自动置 PGERR/WRPRTERR 位)
- 如果顺利干完,他会举起 "干完了" 的牌子(硬件自动置 EOP 位)
- FLASH_GetBank1Status函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
FLASH_SR(闪存状态寄存器)
- FLASH_SR闪存状态寄存器在STM32F10xxx闪存编程手册第16~17页有定义



FLASH_ProgramHalfWord(add,dat)
- 含义:从指定页的add地址开始写
- FLASH_ProgramHalfWord函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
FLASH_CR(闪存控制寄存器)
- FLASH_CR闪存控制寄存器在STM32F10xxx闪存编程手册第17页有定义



status = FLASH_WaitForLastBank1Operation(EraseTimeout)
- 含义:返回当前FLASH的状态
- FLASH_waitForLastBank1Operation函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
status = FLASH_GetBank1Status()
- 含义:获取FLASH的状态
- STM32 的 FLASH 擦除和写入操作,不是 CPU 指令直接完成的,而是由一个叫 FPEC(闪存编程 / 擦除控制器) 的独立硬件模块完成的。整个流程的大白话类比:你(CPU)让一个工人(FPEC)去搬砖(擦除 / 写入 FLASH):
- 你先告诉工人要搬哪块砖(写地址寄存器)
- 你喊一声 "开始干活"(写 STRT 位触发操作)
- 工人开始干活,同时举起一个 "正在忙" 的牌子(硬件自动将 BSY 位置 1)
- 你不能催他,也不能让他同时干另一个活,只能站在旁边等
- 工人干完活,放下 "正在忙" 的牌子(硬件自动将 BSY 位清 0)
- 如果干活过程中出错了,他会举起 "编程错误" 或 "写保护错误" 的牌子(硬件自动置 PGERR/WRPRTERR 位)
- 如果顺利干完,他会举起 "干完了" 的牌子(硬件自动置 EOP 位)
- FLASH_GetBank1Status函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
FLASH_SR(闪存状态寄存器)
- FLASH_SR闪存状态寄存器在STM32F10xxx闪存编程手册第16~17页有定义



status = FLASH_WaitForLastBank2Operation(EraseTimeout)
- 含义:返回当前FLASH的状态
- FLASH_waitForLastBank2Operation函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
status = FLASH_GetBank2Status()
- 含义:获取FLASH的状态
- STM32 的 FLASH 擦除和写入操作,不是 CPU 指令直接完成的,而是由一个叫 FPEC(闪存编程 / 擦除控制器) 的独立硬件模块完成的。整个流程的大白话类比:你(CPU)让一个工人(FPEC)去搬砖(擦除 / 写入 FLASH):
- 你先告诉工人要搬哪块砖(写地址寄存器)
- 你喊一声 "开始干活"(写 STRT 位触发操作)
- 工人开始干活,同时举起一个 "正在忙" 的牌子(硬件自动将 BSY 位置 1)
- 你不能催他,也不能让他同时干另一个活,只能站在旁边等
- 工人干完活,放下 "正在忙" 的牌子(硬件自动将 BSY 位清 0)
- 如果干活过程中出错了,他会举起 "编程错误" 或 "写保护错误" 的牌子(硬件自动置 PGERR/WRPRTERR 位)
- 如果顺利干完,他会举起 "干完了" 的牌子(硬件自动置 EOP 位)
- FLASH_GetBank2Status函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
FLASH_SR(闪存状态寄存器)- - - SR2与之一样,只是名字变了
- FLASH_SR闪存状态寄存器在STM32F10xxx闪存编程手册第16~17页有定义



status = FLASH_WaitForLastOperation(EraseTimeout)
- 含义:返回当前FLASH的状态
- FLASH_waitForLastOperation函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
status = FLASH_GetBank1Status()
- 含义:获取FLASH的状态
- STM32 的 FLASH 擦除和写入操作,不是 CPU 指令直接完成的,而是由一个叫 FPEC(闪存编程 / 擦除控制器) 的独立硬件模块完成的。整个流程的大白话类比:你(CPU)让一个工人(FPEC)去搬砖(擦除 / 写入 FLASH):
- 你先告诉工人要搬哪块砖(写地址寄存器)
- 你喊一声 "开始干活"(写 STRT 位触发操作)
- 工人开始干活,同时举起一个 "正在忙" 的牌子(硬件自动将 BSY 位置 1)
- 你不能催他,也不能让他同时干另一个活,只能站在旁边等
- 工人干完活,放下 "正在忙" 的牌子(硬件自动将 BSY 位清 0)
- 如果干活过程中出错了,他会举起 "编程错误" 或 "写保护错误" 的牌子(硬件自动置 PGERR/WRPRTERR 位)
- 如果顺利干完,他会举起 "干完了" 的牌子(硬件自动置 EOP 位)
- FLASH_GetBank1Status函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
FLASH_SR(闪存状态寄存器)
- FLASH_SR闪存状态寄存器在STM32F10xxx闪存编程手册第16~17页有定义



FLASH_Lock()
- 含义:锁定FLASH编程擦除控制器
- FLASH_Lock函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_flash.c(外设固体库文件)- - - ✿ 固体库文件
FLASH_CR(闪存控制寄存器)
- FLASH_CR闪存控制寄存器在STM32F10xxx闪存编程手册第17页有定义



四、蜂鸣器驱动程序和MIDI音乐播放程序
buzzer.c代码文件:




buzzer.h代码文件:
main.c部分代码文件:
五、USART串口发送程序
main.c代码文件:
5.1 USART1_Init(115200)和USART1_printf("STM32 %d %d ",a,b)
- usart.h代码文件:
- USART1_Init函数和USART1_printf函数,出自usart.c文件
半主机模式

#if 1 #endif
- 这是一个代码开关:
- 写
#if 1:开启 printf 重定向,能用 printf 打印到串口 - 写
#if 0:关闭 printf 重定向,回到默认的半主机模式
#progma import(__use_no_semihosting)
- 这是给编译器下的命令:"我明确告诉你,不要用半主机模式!所有输入输出我自己处理,你别瞎操心。"如果不写这一行,编译器会默认使用半主机模式,拔了调试器程序就会卡死。
struct __FILE {int handle;};FILE __stdout;
- 为什么要写?
printf是 C 标准库的函数,标准库规定:所有输出都要写到一个叫FILE的 "文件" 里- 电脑上的
FILE对应屏幕、硬盘文件等 - 单片机上没有这些东西,但标准库还是要求必须有一个
FILE对象 - 所以我们随便定义一个空的
FILE结构体,再定义一个__stdout对象(标准输出) - 就像你去银行办业务,银行要求你填一张表,虽然有些空你根本不需要填,但你必须填上 "无",不然不给你办
- FILE是
struct __FILE的别名,struct __FILE是结构体的真名,而定义别名这一步,在stdio.h已经写好了,__stdout不能改
_sys_exit(int x){x = x;}
- 为什么要写?
- 标准库的
printf函数在某些情况下会调用_sys_exit()(程序退出函数) - 电脑上的程序可以退出(比如关闭窗口),但单片机的程序是死循环,永远不会退出
- 所以我们写一个空的
_sys_exit()函数,告诉标准库:"我不需要退出,你调用我也什么都不做" - 不写的话,编译器会报 "未定义引用_sys_exit" 的错误
- “系统异常、程序崩溃、程序要退出” 时准备的函数
int fputc(int ch, FILE *f){while((USART_n->SR&0X40)==0); USART_n->DR = (u8) ch; return ch;}
- fputc函数的原理:
printf函数本身并不直接输出字符,它会把要输出的字符串拆成一个一个的字符,然后每一个字符都调用一次fputc函数来输出。原来的fputc是标准库写的,会把字符输出到电脑屏幕。现在我们重写了这个函数,让它把字符发送到 STM32 的串口上。
while((USART_n->SR&0X40)==0);
- 大白话:等串口把上一个字符发完。
- 类比:你用快递柜寄快递,必须等上一个人把柜门关上,你才能打开下一个柜门。
USART_n->SR是串口的状态寄存器,0x40(0100 0000)是第 6 位(TC 位,发送完成位)。当 TC 位为 1 时,表示上一个字符已经发送完毕,可以发下一个了。如果不等就直接写,会导致字符丢失。
- TC位=1(发送完成)- - - 退出循环,TC位=0(正在发送,还没完)- - - 等着
- USART_n值为0x40013800,出自usart.h
且USART1值为0x40013800,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件

SART_n->DR = (u8) ch;
- 大白话:把要发送的字符扔给串口,让它发出去。
USART_n->DR是串口的数据寄存器,你往里面写一个字符,串口硬件就会自动把它转换成电信号发送出去。
#if EN_USART1或EN_USART2或EN_USART3
- 表示判断串口1或2或3是使能状态还是禁止状态
- EN_USART1、EN_USART2、EN_USART3,出自usart.h文件
u8 USART1_RX_BUF[USART1_REC_LEN]或USART2_RX_BUF[USART2_REC_LEN]或USART3_RX_BUF[USART3_REC_LEN]
- 表示定义串口的接受缓冲区(长度在200字节)
- USART1_REC_LEN、USART2_REC_LEN、USART3_REC_LEN,出自usart.h文件
u16 USART1_RX_STA=0或USART2_RX_STA=0或USART3_RX_STA=0
- 表示定义无符号的16位的变量名USART1_RX_STA或USART2_RX_STA或USART3_RX_STA
- USART1_RX_STA、USART2_RX_STA、USART3_RX_STA出自usart.h文件
va-list arg_ptr
- va-list来自于
#include <stdarg.h>头文件中
- 含义:专门找可变参数的指针,指向函数里那些“不确定数量的参数”,就比如
USART1_printf(”数字:%d,字符串:%s”,123,”abc”),其中123和”abc”就是数量不固定、类型也不固定的参数,所以123和”abc”在函数参数中一律按USART1_printf(char *fmt, …)写
va_start(arg_ptr, fmt)
- va_start来自于
#include <stdarg.h>头文件中
- 含义:指向fmt后面的第一个可变参数(也就是上例中的
123),即从fmt这个参后面开始找
while((i < USART1_REC_LEN) && (I < strlen(buffer)))
- 含义:循环发送草稿纸上的每个字符,直到发完为止
- i < USART1_REC_LEN:防止超过草稿纸的长度
- i < strlen(buffer):防止超过buffer的本身长度,即防止发送多余的空字符
vsnprintf(buffer, USART1_REC_LEN+1, fmt, arg_ptr)
- vsnprintf来自于
#include "stdio.h"头文件中
- 含义:如上例,把%d、%s这些占位符,替换成真正的参数,拼成完整的字符串
- 参数
- buffer:草稿纸,把拼好的字符串存在这里
- USART1_REC_LEN+1:草稿纸的最大长度,防止写超了(这里的加1代表是为了存字符串结尾的
'\0') - fmt:格式化字符串(如”数字:%d,字符串:%s”)
- arg_ptr:指向可变参数的探测器
USART_SendData(USART1, (u8)buffer[i++])
- 含义:将草稿纸上的第i个字符发给串口1,然后计算器i加1
- USART_SendData函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_usart.c(外设固体库文件)- - - ✿ 固体库文件
IS_USART_ALL_PERIPH(USARTx)
- IS_USART_ALL_PERIPH函数,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
IS_USART_DATA(Data)
- IS_USART_DATA函数,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
- 为什么是小于等于0x1FF(511)呢?
- 源于这个数据是要存入数据寄存器(USART_DR)中,且该寄存器用于存数据的位有9位,即DR[8:0]
- USART_DR数据寄存器在参考手册第541~542页有定义


while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
- 含义:等串口把这个字符发完,再发下一个不然会丢字符
- USART_GetFlagStatus函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_usart.c(外设固体库文件)- - - ✿ 固体库文件
- USART_TypeDef,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
- USART_FLAG_TC(发送完成)、USART_FLAG_CTS(CTS标志)、IS_USART_FLAG函数、IS_USART_123_PERIPH函数,都出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_usart.h(外设固体库头文件)- - - ✿ 固体库文件
状态寄存器(USART_SR)
- USART_SR状态寄存器在参考手册第540~541页有定义




va_end(arg_ptr)
- va_end来自于
#include <stdarg.h>头文件中
- 含义:用完探测器了,把它收起来,即结束可变参数使用
GPIO_InitTypeDef GPIO_InitStructure
- 含义:创建GPIO_InitTypeDef的变量
- GPIO_InitTypeDef这个类型,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_gpio.h(外设固体库头文件)- - - ✿ 固体库文件
USART_InitTypeDef USART_InitStructure
- 含义:创建USART_InitTypeDef的变量
- USART_InitTypeDef这个类型,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_usart.h(外设固体库头文件)- - - ✿ 固体库文件
NVIC_InitTypeDef NVIC_InitStructure
- 含义:创建NVIC_InitTypeDef的变量
- NVIC_InitTypeDef这个类型,出自Lib文件夹下的inc文件夹下的内核相关的misc.h(跟SVIC和SysTICK相关的函数)- - - ✿ 固体库文件
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0)
- 含义:全局设置中断优先级分组为 Group_0,在此情况下,优先级无论设置什么,都最终变成0,而子优先级的范围为0~15
- 参数NVIC_PriorityGroup_0,出自Lib文件夹下的inc文件夹下的内核相关的misc.h(跟SVIC和SysTICK相关的函数)- - - ✿ 固体库文件
分组号 | 抢占优先级占几位 | 子优先级占几位 | 抢占优先级范围 | 子优先级范围 |
0 | 0 位 | 4 位 | 只有 0 级 | 0~15 |
1 | 1 位 | 3 位 | 0~1 | 0~7 |
2 | 2 位 | 2 位 | 0~3 | 0~3 |
3 | 3 位 | 1 位 | 0~7 | 0~1 |
4 | 4 位 | 0 位 | 0~15 | 只有 0 级 |
- NVIC_PriorityGroupConfig函数,出自Lib文件夹下的src文件夹下的内核相关的misc.c(跟SVIC和SysTICK相关的函数)- - - ✿ 固体库文件
SCB->AIRCR
- SCB,出自CMSIS文件夹下的内核相关的core_cm3.h(内核寄存器定义)- - - ✿ 固体库文件


NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn
- 含义:选择串口1中断通道
- USART1_IRQn,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
以上来源于参考手册第132~134页有定义




NVIC_Init(&NVIC_InitStructure)
- NVIC_Init函数,出自Lib文件夹下的src文件夹下的内核相关的misc.c(跟SVIC和SysTICK相关的函数)- - - ✿ 固体库文件
- 示例:

- IS_FUNCTIONAL_STATE函数,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
- IS_NVIC_PREEMPTION_PRIORITY函数、IS_NVIC_SUB_PRIORITY函数,都出自Lib文件夹下的inc文件夹下的内核相关的misc.h(跟SVIC和SysTICK相关的函数)- - - ✿ 固体库文件
SCB->AIRCR
- SCB,出自CMSIS文件夹下的内核相关的core_cm3.h(内核寄存器定义)- - - ✿ 固体库文件


NVIC->IP、NVIC->ISER、NVIC->ICER
- NVIC,出自CMSIS文件夹下的内核相关的core_cm3.h(内核寄存器定义)- - - ✿ 固体库文件
RESERVED的作用和计算
- 作用
RESERVED(保留区)是ARM官方规定的内存填充机制,目的是让NVIC_Type结构体的每个成员偏移地址,与Cortex-M3内核手册中定义的NVIC寄存器物理地址完全一一对应。Cortex-M3内核的NVIC寄存器是稀疏分布的(寄存器之间有大量未使用的地址空间),如果没有这些保留区,结构体成员会自动连续排列,导致后续寄存器的偏移地址全部错位,无法正确访问硬件。
- 每个RESERVED大小的精确计算
uint32_t类型占4字节uint8_t类型占1字节ISER[8]总大小:8 × 4 = 32字节,占用地址0x000 ~ 0x01F- 下一个寄存器
ICER的起始偏移是0x080 - 中间空白地址范围:
0x020 ~ 0x07F,总字节数:0x080 - 0x020 = 0x60 = 96字节 - 保留区元素个数:
96 ÷ 4 = 24→ 即RESERVED0[24] ICER[8]总大小:32字节,占用地址0x080 ~ 0x09F- 下一个寄存器
ISPR起始偏移:0x100 - 空白地址:
0x0A0 ~ 0x0FF,共96字节 - 保留区元素个数:
96 ÷ 4 = 24→RESERVED1[24] - 计算逻辑同上,空白地址
0x120 ~ 0x17F共96字节 - 保留区元素个数:24 →
RESERVED2[24] - 空白地址
0x1A0 ~ 0x1FF共96字节 - 保留区元素个数:24 →
RESERVED3[24] IABR[8]总大小:32字节,占用地址0x200 ~ 0x21F- 下一个寄存器
IP起始偏移:0x300 - 空白地址:
0x220 ~ 0x2FF,总字节数:0x300 - 0x220 = 0xE0 = 224字节 - 保留区元素个数:
224 ÷ 4 = 56→RESERVED4[56] IP[240]总大小:240 × 1 = 240字节,占用地址0x300 ~ 0x3EF- 下一个寄存器
STIR起始偏移:0xE00 - 空白地址:
0x3F0 ~ 0xDFF,总字节数:0xE00 - 0x3F0 = 0xA10 = 2576字节 - 保留区元素个数:
2576 ÷ 4 = 644→RESERVED5[644]
所有计算基于结构体基地址
0xE000E100,偏移地址均相对于该基地址。(1)
RESERVED0[24](2)
RESERVED1[24](3)
RESERVED2[24](4)
RESERVED3[24](5)
RESERVED4[56](6)
RESERVED5[644]内核版本 | 发布时间 | 实现的优先级位数 | 代表芯片 |
Cortex-M3 r0p0 | 2005 年 | 3 位 | 非常早期的原型芯片,市面上几乎没有 |
Cortex-M3 r1p0 | 2006 年 | 3 位 | 极少数早期的 STM32F101 芯片 |
Cortex-M3 r2p0 及以上 | 2007 年以后 | 4 位 | 所有你能买到的 STM32F103/F105/F107 芯片 |
NVIC的IP寄存器解释 1 ,可参考《pm0056-stm32f10xxx-and-stm32f20xxx-cortexm3-programming-manual-stmicroelectronics》的第125页

翻译为:



NVIC的IP寄存器解释 2 ,可参考《ARM Cortex-M3权威指南 第2版_13598339》的第122~123页


NVIC(基地址:0xE000E100)的所有相关寄存器解释【ISER:0xE000E100、ICER:0xE000E180、ISPR:0xE000E200、ICPR:0xE000E280、IABR:0xE000E300、IP:0xE000E400、STIP:0xE000EF00】,可参考《Cortex_M3权威指南(中文)》的第303、304、313页





USART_InitStructure.USART_WordLength = USART_WordLength_8b
- 含义:数据位长度(8位 或 9位),设为8位数据位长度
- 参数USART_WordLength_8b,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_usart.h(外设固体库头文件)- - - ✿ 固体库文件
USART_InitStructure.USART_StopBits = USART_StopBits_1
- 含义:停止位(1位 或 2位),用来表示一帧数据结束了,设为1位停止位
- 参数USART_StopBits_1,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_usart.h(外设固体库头文件)- - - ✿ 固体库文件
USART_InitStructure.USART_Parity = USART_Parity_No
- 含义:校验位(奇校验、偶校验、无校验),用来检查数据传输出错没,设为无校验
- 参数USART_Parity_No,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_usart.h(外设固体库头文件)- - - ✿ 固体库文件
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None
- 含义:硬件流控制CTS/RTS(用来防止发送太快导致数据丢失),设为无硬件流控
- 参数USART_HardwareFlowControl_None,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_usart.h(外设固体库头文件)- - - ✿ 固体库文件
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx
- 含义:模式选择(使能发送(Tx) / 使能接收(Rx) / 都使能),设为收发都开
- 参数USART_Mode_Rx、USART_Mode_Tx,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_usart.h(外设固体库头文件)- - - ✿ 固体库文件
USART_Init(USART1, &USART_InitStructure)
- 含义:初始化串口
- USART_Init函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_usart.c(外设固体库文件)- - - ✿ 固体库文件
RCC_ClocksTypeDef RCC_ClocksStatus
- 创建了RCC_ClocksTypeDef类型的变量,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
IS_USART_BAUDRATE(USART_InitStruct->USART_BaudRate)
- 函数IS_USART_BAUDRATE,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_usart.h(外设固体库头文件)- - - ✿ 固体库文件
控制寄存器2(USART_CR2)
- USART_CR2控制寄存器2在参考手册第544~545页有定义


控制寄存器1(USART_CR1)
- USART_CR1控制寄存器1在参考手册第542~544页有定义




控制寄存器3(USART_CR3)
- USART_CR3控制寄存器3在参考手册第545~546页有定义


RCC_GetClocksFreq(&RCC_ClocksStatus)
- RCC_GetClocksFreq函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
tmp = RCC->CFGR & CFGR_SWS_Mask
- CFGR_SWS_Mask的值为((uint32_t)0x0000000C),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_CFGR(时钟配置寄存器)- - - 与代码语句有关
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义


RCC_Clocks->SYSCLK_Frequency = HSI_VALUE
- 含义:将SYSCLK 系统主时钟频率设为HSI(内部高速)
- RCC_Clocks->SYSCLK_Frequency:SYSCLK 系统主时钟频率
- HSI_VALUE,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
RCC_Clocks->SYSCLK_Frequency = HSE_VALUE
- 含义:将SYSCLK 系统主时钟频率设为HSE(外部高速)
- RCC_Clocks->SYSCLK_Frequency:SYSCLK 系统主时钟频率
- HSE_VALUE,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
pllmull = RCC->CFGR & CFGR_PLLMull_Mask和pllsource = RCC->CFGR & CFGR_PLLSRC_Mask
- 含义:前者:读取PLL倍频系数(CFGR寄存器的PLLMULL[21:18]位);后者:读取PLL时钟源选择(CFGR寄存器的PLLSRC[16]位)
RCC_CFGR(时钟配置寄存器,非互联网型)
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义



RCC_CFGR(时钟配置寄存器,互联网型)
- RCC_CFGR时钟配置寄存器在参考手册第86~88页有定义




prediv1factor = (RCC->CFGR2 & CFGR2_PREDIV1) + 1和RCC_Clocks->SYSCLK_Frequency = (HSE_VALUE / prediv1factor) * pllmull
- 属于超值型范畴
- 含义:前者:读取CFGR2寄存器的0~3位,然后在此基础上加上1,最后赋值给prediv1factor。后者:将HSE_VALUE(8000000)除以prediv1factor,得出的值乘以之前“
pllmull = (pllmull >> 18) + 2;”处理的倍频系数值,最后写回到RCC_Clocks对象的SYSCLK系统主时钟频率中。
- CFGR2_PREDIV1,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_CFGR2(时钟配置寄存器2)
- RCC_CFGR2时钟配置寄存器2在参考手册第101~103页有定义



RCC->CFGR & CFGR_PLLXTPRE_Mask
- 含义:读取CFGR寄存器的第17位PLLXTPRE(HSE分频器作为PLL输入)
RCC_CFGR(时钟配置寄存器)
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义


prediv1source = RCC->CFGR2 & CFGR2_PREDIV1SRC
- 属于互联网型范畴
- 含义:读取CFGR2寄存器的第16位,赋值给prediv1source
RCC_CFGR2(时钟配置寄存器2)
- RCC_CFGR2时钟配置寄存器2在参考手册第101~103页有定义



prediv2factor = ((RCC->CFGR2 & CFGR2_PREDIV2) >> 4) + 1
- 属于互联网型范畴
- 含义:读取CFGR2寄存器的第4~7位,然后右移4位,变成原本第4~7位移到了第0~3位,之后加上1,赋值给prediv2factor
RCC_CFGR2(时钟配置寄存器2)
- RCC_CFGR2时钟配置寄存器2在参考手册第101~103页有定义



pll2mull = ((RCC->CFGR2 & CFGR2_PLL2MUL) >> 8 ) + 2
- 属于互联网型范畴
- 含义:读取CFGR2寄存器的第8~11位,然后右移8位,变成原本第8~11位移到了第0~3位,之后加上2,赋值给pll2mull
RCC_CFGR2(时钟配置寄存器2)
- RCC_CFGR2时钟配置寄存器2在参考手册第101~103页有定义



usartxbase == USART1_BASE
- USART1_BASE值为0x40013800,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
配置BRR寄存器(波特率寄存器)的第三步到第六步的解释
- 上面的代码如下:
- 解释OVER8这一位
- 因为芯片为STM32F103C8T6,所以是普通型,因此在参考手册中USART_CR1(控制寄存器1)的第14、15位是保留位,硬件强制为0。
- 而互联网型,则存在第15位为0VER8,值0默认为16倍过采样,1为8倍过采样
- 而上面的代码,本身是STM32F10系列的固件库文件,所以在一定程度上是兼容所有的STM32F10系列的芯片(包含普通型、互联网型等),因此代码并没有直接显示没有OVER8这一位
- 普通型是默认16倍过采样,而互联网型则看第15位的值情况
普通型参考手册上的依据




过采样的解释
过采样模式 | BRR 寄存器位分配 | 小数部分位数 | 每一位的权重 | 小数部分最大值 |
16 倍(OVER8=0) | 高 12 位:整数部分低 4 位(bit3~bit0):小数部分 | 4 位 | 1/16 | 15/16=0.9375 |
8 倍(OVER8=1) | 高 12 位:整数部分低 3 位(bit2~bit0):小数部分bit3:保留位(必须写 0) | 3 位 | 1/8 | 7/8=0.875 |
- 过采样率本质是串口接收端对每个数据比特位的采样次数,直接决定波特率上限、BRR 寄存器计算方式和通信抗干扰能力。
核心公式与BRR位分配对比
- STM32F103串口波特率由USARTDIV(串口分频系数)决定,公式和BRR寄存器位分配随OVER8位变化:
过采样率 | OVER8位值 | 波特率计算公式 | BRR寄存器位分配 | 最大波特率(72MHz时钟) |
16倍(默认) | 0 | 波特率 = 72000000 / (16 × USARTDIV) | 整数部分:bit4~bit15<br>小数部分:bit0~bit3(4位,0~15) | 4.5Mbps |
8倍 | 1 | 波特率 = 72000000 / (8 × USARTDIV) | 整数部分:bit4~bit15<br>小数部分:bit0~bit2(3位,0~7)<br>bit3必须强制置0 | 9Mbps |
115200波特率的实际计算(72MHz时钟)
- 16倍过采样(默认,最常用)
- 整数部分:39 → 二进制
100111 - 小数部分:0.0625 × 16 = 1 → 二进制
0001 - BRR寄存器值:
0x0271(39<<4 + 1 = 624 + 1 = 625) - 实际波特率:72000000/(16×39.0625) = 115200 → 误差0%
- 8倍过采样
- 整数部分:78 → 二进制
1001110 - 小数部分:0.125 × 8 = 1 → 二进制
001 - BRR寄存器值:
0x04E1(78<<4 + 1 = 1248 + 1 = 1249) - 注意:bit3必须置0,这里小数部分只有3位,所以直接加1即可
- 实际波特率:72000000/(8×78.125) = 115200 → 误差0%
关键差异与适用场景
特性 | 16倍过采样(默认) | 8倍过采样 |
抗干扰能力 | 强(采样点多,能过滤毛刺) | 稍弱(采样点少,对线路质量要求高) |
波特率上限 | 低(72MHz下4.5Mbps) | 高(72MHz下9Mbps) |
波特率误差 | 通常更小(小数精度4位) | 稍大(小数精度3位) |
适用场景 | 绝大多数常规串口通信(115200及以下) | 高速串口通信(1Mbps以上) |
大白话解释






假设情况:芯片STM32F103C8T6,USART1挂载在APB2总线(72MHz),波特率为115200,那么OVER8默认为16倍过采样(该位值为0)











波特比率寄存器(USART_BRR)
- USART_BRR波特比率寄存器在参考手册第542页有定义

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE)
- 含义:操作串口控制寄存器,开启对应中断位
- 这行代码是 STM32 串口「自动接收数据」的总开关:
开启它,串口每收到 1 个字节,就会自动通知 CPU,并自动执行接收处理函数;不开启它,串口只能被动等待程序手动查询,无法实现中断接收。
- 第一个参数:USART1值为0x40013800,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
- 第二个参数:USART_IT_RXNE值为0x0525【0000 0101 0010 0101】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_usart.h(外设固体库头文件)- - - ✿ 固体库文件
控制寄存器1(USART_CR1)
- USART_CR1控制寄存器1在参考手册第542~544页有定义


控制寄存器2(USART_CR2)
- USART_CR2控制寄存器2在参考手册第544~545页有定义


控制寄存器3(USART_CR3)
- USART_CR3控制寄存器3在参考手册第545~546页有定义



- 第三个参数ENABLE值为1
- 函数USART_ITConfig,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_usart.c(外设固体库文件)- - - ✿ 固体库文件
USART_Cmd(USART1, ENABLE)
- USART_Cmd函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_usart.c(外设固体库文件)- - - ✿ 固体库文件
控制寄存器1(USART_CR1)
- USART_CR1控制寄存器1在参考手册第542~544页有定义


USART_GetITStatus(USART1, USART_IT_RXNE) != RESET
- 含义:判断CR1寄存器第五位RXNEIE(接受缓冲区非空中断使能)是否成功设置,若成功设置,则该行代码为true
- USART_GetITStatus函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_usart.c(外设固体库文件)- - - ✿ 固体库文件
Res =USART_ReceiveData(USART1)
- 含义:读取接收到的数据
- USART_ReceiveData函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_usart.c(外设固体库文件)- - - ✿ 固体库文件
USART_DR(数据寄存器)
- USART_DR数据寄存器1在参考手册第541~542页有定义


USART1_IRQHandler函数的模拟过程
- 初始状态
USART1_RX_STA = 0(空白记录本,没收完、没收回车、收 0 个字节)USART1_RX_BUF空数组- 仓库最大容量
USART1_REC_LEN=200
- 场景:你在串口助手发
ab+ 按回车键(Enter) - 电脑发送顺序:
a→b→0x0D(回车) →0x0A(换行) - 第 1 步:收到字节
a - Res = 'a'
- printf 回显
a,串口助手显示a - 没收完、没收回车 → 把
a存进USART1_RX_BUF[0] - 字节数 + 1 →
USART1_RX_STA = 1 - 第 2 步:收到字节
b - Res = 'b'
- 回显
b,串口助手显示ab - 存进
USART1_RX_BUF[1] - 字节数 + 1 →
USART1_RX_STA = 2 - 第 3 步:收到字节
0x0D(回车) - Res = 0x0D
- 回显回车,串口助手换行
- 标记收到回车 →
USART1_RX_STA |= 0x4000→ 现在STA=0x4002 - 字节数不增加
- 第 4 步:收到字节
0x0A(换行) - Res = 0x0A
- 回显换行
- 已经收到过回车,现在收到换行 → 标记接收完成
USART1_RX_STA |= 0x8000→ 最终STA=0xC002- bit15=1(收完了)
- bit14=1(收到回车)
- 低 14 位 = 2(收了 2 个字节:ab)
USART的各个参数含义
- 波特率(USART_BaudRate)
- 是什么?
- 串口通信的「语速」
- 单位:bit/s(比特每秒),常见值:9600 / 115200
- 作用
- 决定数据传输的快慢
- 之前算的
BRR寄存器分频值,就是根据这个数计算出来的 - 核心要求:STM32 和 电脑串口助手的波特率必须一模一样!
- 对串口的影响
- 波特率不一致 → 直接乱码 / 收不到任何数据
- 115200 比 9600 速度快很多(调试首选 115200)
- 字长(USART_WordLength)
- 代码固定:
USART_WordLength_8b(8 位字长) - 是什么?
- 每个数据的「长度」,也就是:发一个字符用多少位二进制
- 作用
- 我们用的字母 / 数字 / 符号,都是 8 位二进制(ASCII 码)
- 8 位 = 标准通用长度,对应
a~z、0~9、符号 - 对串口的影响
- 99% 的串口通信都用 8 位
- 9 位字长仅用于带硬件校验的特殊场景,我们完全用不到
- 停止位(USART_StopBits)
- 代码固定:
USART_StopBits_1(1 位停止位) - 生活比喻
- 一句话结尾的「句号」
- 是什么?
- 发送完一个字符后,额外发的结束标记位,告诉接收方:这个字符传完了!准备接收下一个!
- 作用
- 让 STM32 和 电脑同步信号,避免数据粘连
- 通用标准:1 位停止位
- 对串口的影响
- 停止位不一致 → 数据接收错误、乱码
- 校验位(USART_Parity)
- 代码固定:
USART_Parity_No(无校验) - 生活比喻
- 数据的「防伪校验码」
- 是什么?
- 额外加一位,检查数据在传输过程中有没有被干扰出错
- 奇校验:数据中
1的个数凑成奇数 - 偶校验:数据中
1的个数凑成偶数 - 作用
- 简单的错误检测,普通串口通信完全不需要(现在传输线很稳定,几乎不会出错)
- 对串口的影响
- 我们默认无校验,最简单、最通用
- 开校验会多占用 1 位,降低传输效率
- 硬件流控(USART_HardwareFlowControl)
- 代码固定:
USART_HardwareFlowControl_None(无硬件流控) - 生活比喻
- 收发数据的「红绿灯」
- 是什么?
- 用额外的硬件引脚(CTS/RTS)控制:我忙不过来了,你先别发!
- 作用
- 防止数据太多、接收不过来导致溢出
- 对串口的影响
- 普通串口通信(连电脑)完全不用,只有蓝牙、WiFi 等高速模块才会用到
- 关闭(NONE)是标准配置
- 收发模式(USART_Mode)
- 代码固定:
USART_Mode_Rx | USART_Mode_Tx - 是什么?
Tx= Transmit 发送Rx= Receive 接收- 作用
- 开启串口的发送功能(STM32 发数据给电脑)
- 开启串口的接收功能(电脑发数据给 STM32)
- 对串口的影响
- 只开
Tx:只能发,不能收 - 只开
Rx:只能收,不能发 - 我们两个都开:全双工通信(同时收发)
5.2 USART_SendData(USART1,0x55)
- USART_SendData函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_usart.c(外设固体库文件)- - - ✿ 固体库文件
USART_DR(数据寄存器)
- USART_DR数据寄存器1在参考手册第541~542页有定义


六、LED灯显示RTC走时程序
main.c代码文件:

rtc.h代码文件:
rtc.c代码文件:
6.1 RTC_First_Config(void)
函数完整流程解析(含顺序逻辑、设计原因、依赖关系)







什么叫「仅备份域掉电才执行一次,主芯片复位不重复执行」


这个初始化流程是怎么想出来的?设计思路是什么?




RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE)
- 含义:对APB1外设时钟使能寄存器(RCC_APB1ENR)上的第28位PWREN(电源接口时钟使能)和第27位BKPEN(备份接口时钟使能)进行开启操作
- 第一个参数为外设的相关变量RCC_APB1Periph_PWR【值:0x10000000,二进制:0001 0000 0000 0000 0000 0000 0000 0000 】、RCC_APB1Periph_BKP值:0x08000000,二进制:0000 1000 0000 0000 0000 0000 0000 0000 】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
RCC_APB1ENR(APB1外设时钟使能寄存器)
- APB1外设时钟使能寄存器RCC_APB1ENR在参考手册第71~73页有定义,英文版第115~117页有定义(其中CAN2(第26位)和CEC(第30位),参考手册和英文版都没有找到对应定义处)












PWR_BackupAccessCmd(ENABLE)
- 含义:主要是对电源控制寄存器(PWR_CR)的第8位DBP(取消后备区域的写保护)设置为1(允许写入RTC和后备寄存器)
- PWR_BackupAccessCmd函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_pwr.c(外设固体库文件)- - - ✿ 固体库文件
*(__IO uint32_t *) CR_DBP_BB = (uint32_t)NewState
- 外设PWR(电源控制)的起始地址:0x40007000~0x400073FF
- 含义:对电源控制寄存器(PWR_CR)的第8位DBP(取消后备区域的写保护)设置为1(允许写入RTC和后备寄存器)
- CR_DBP_BB(值为0x420E0020【0100 0010 0000 1110 0000 0000 0010 0000】)的计算过程涉及的值,分别出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_pwr.c(外设固体库文件)- - - ✿ 固体库文件
出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
计算过程:
CR_DBP_BB涉及到“位段”的知识点,“位段”出自参考手册第29~30页有定义


PWR_CR(电源控制寄存器)
- PWR_CR电源控制寄存器在参考手册第44页有定义


BKP_DeInit( )
- 含义:备份域寄存器复位
- BKP_DeInit函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_bkp.c(外设固体库文件)- - - ✿ 固体库文件
为什么要先设置ENABLE,然后在设置DISABLE?目的?




- RCC_BackupResetCmd(ENABLE或DISABLE),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
BDCR_BDRST_BB
- RCC(复位和时钟控制)的起始地址:0x40021000~0x400213FF
- BDCR_BDRST_BB(值为0x42451180【0100 0010 0100 0101 0001 0001 1000 0000】)的计算过程涉及的值,分别出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
计算过程:
BDCR_BDRST_BB涉及到“位段”的知识点,“位段”出自参考手册第29~30页有定义


RCC_BDCR(备份域控制寄存器)
- RCC_BDCR备份域控制寄存器在参考手册第74页有定义


RCC_LSEConfig(RCC_LSE_ON)
- 含义:将RCC_BDCR备份域控制寄存器的第0位LSEON的值1(外部32kHz振荡器开启)
- 参数RCC_LSE_ON,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
- RCC_LSEConfig函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
BDCR_ADDRESS
- RCC(复位和时钟控制)的起始地址:0x40021000~0x400213FF
- BDCR_ADDRESS(值为0x40021020【0100 0000 0000 0010 0001 0000 0010 0000】,表示RCC基地址的基础上偏移0x20,即RCC_BDCR(备份域控制寄存器))的计算过程涉及的值,分别出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
计算过程:
RCC_BDCR(备份域控制寄存器)
- RCC_BDCR备份域控制寄存器在参考手册第74页有定义



while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
- 含义:判断RCC_BDCR(备份域控制寄存器)的第1位LSERDY(外部低速LSE就绪)是否为0,若等于0,则循环继续;否则结束
- 参数RCC_FLAG_LSERDY的值为((uint8_t)0x41),出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
RCC_CR(时钟控制寄存器)- - - 与参数有关
- RCC_CR时钟控制寄存器在参考手册第60~61页有定义




RCC_BDCR(备份域控制寄存器)- - - 与参数有关
- RCC_BDCR备份域控制寄存器在参考手册第74页有定义


RCC_CSR(控制/状态寄存器)- - - 与参数有关
- RCC_CSR控制/状态寄存器在参考手册第75~76页有定义








RCC_GetFlagStatus(RCC_FLAG_LSERDY)
- 含义:看RCC_BDCR(备份域控制寄存器)的第1位LSERDY(外部低速LSE就绪)是否为1,即看外部32kHz振荡器是否就绪
- 出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE)
- 含义:将RCC_BDCR(备份域控制寄存器)的第8~9位RTCSEL[1:0](RTC时钟源选择)设置值01(LSE振荡器作为RTC时钟)
- 参数RCC_RTCCLKSource_LSE的值为0x00000100,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
- RCC_RTCCLKConfig函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_BDCR(备份域控制寄存器)
- RCC_BDCR备份域控制寄存器在参考手册第74页有定义


RCC_RTCCLKCmd(ENABLE)
- 含义:将RCC_BDCR(备份域控制寄存器)的第15位RTCEN(RTC时钟使能)设为1(RTC时钟开启)
- RCC_RTCCLKCmd函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
BDCR_RTCEN_BB
- 含义:代表RCC_BDCR(备份域控制寄存器)的第15位RTCEN(RTC时钟使能)的位带地址
- RCC(复位和时钟控制)的起始地址:0x40021000~0x400213FF
- BDCR_RTCEN_BB(值为0x4245117C【0100 0010 0100 0101 0001 0001 0111 1100】)的计算过程涉及的值,分别出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
计算过程:
BDCR_RTCEN_BB涉及到“位段”的知识点,“位段”出自参考手册第29~30页有定义


RCC_BDCR(备份域控制寄存器)
- RCC_BDCR备份域控制寄存器在参考手册第74页有定义


RTC_WaitForSynchro()
- 含义:先对RTC_CRL(RTC控制寄存器低位)的第3位RSF(寄存器同步标志)进行清零,然后等待RTC核心寄存器与APB1总线时钟完成同步,即硬件会将第3位RSF(寄存器同步标志)置为1
- RTC_WaitForSynchro函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rtc.c(外设固体库文件)- - - ✿ 固体库文件
RTC_FLAG_RSF
- 含义:代表RTC_CRL(RTC控制寄存器低位)的第3位RSF(寄存器同步标志)
- RTC_FLAG_RSF的值为:0x0008【0000 0000 0000 1000】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rtc.h(外设固体库头文件)- - - ✿ 固体库文件
RTC_CRL(RTC控制寄存器低位)
- RTC_CRLRTC控制寄存器低位在参考手册第311~312页有定义




RTC_WaitForLastTask()
- 含义:等待上一次对RTC寄存器的写操作已经完成,即硬件会将RTC_CRL(RTC控制寄存器低位)的第5位RTOFF(RTC操作关闭)置为1
- RTC_WaitForLastTask函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rtc.c(外设固体库文件)- - - ✿ 固体库文件
RTC_FLAG_RTOFF
- 含义:代表RTC_CRL(RTC控制寄存器低位)的第5位RTOFF(RTC操作关闭)
- RTC_FLAG_RTOFF的值为:0x0020【0000 0000 0010 0000】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rtc.h(外设固体库头文件)- - - ✿ 固体库文件
RTC_CRL(RTC控制寄存器低位)
- RTC控制寄存器低位RTC_CRL在参考手册第311~312页有定义




RTC_SetPrescaler(32767)
- 含义:把32767的值写入PRL寄存器中,目的是把高频的 32.768kHz 时钟,分频降频成 1Hz 的秒脉冲,给 RTC 计数器使用
- RTC_SetPrescaler函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rtc.c(外设固体库文件)- - - ✿ 固体库文件
IS_RTC_PRESCALER(PrescalerValue)
- IS_RTC_PRESCALER函数,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rtc.h(外设固体库头文件)- - - ✿ 固体库文件
RTC_EnterConfigMode()
- 含义:将RTC_CRL(RTC控制寄存器低位)的第4位CNF(配置标志)设为1(进入配置模式)
- RTC_EnterConfigMode函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rtc.c(外设固体库文件)- - - ✿ 固体库文件
RTC_CRL_CNF
- 出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
RTC_CRL(RTC控制寄存器低位)
- RTC控制寄存器低位RTC_CRL在参考手册第311~312页有定义




RTC->PRLH = (PrescalerValue & PRLH_MSB_MASK) >> 16
- 含义:将RTC_PRLH(RTC预分频装载寄存器高位)的第0~15位设置为0x0000
- PRLH_MSB_MASK的值为0x000F0000【0000 0000 0000 1111 0000 0000 0000 0000】
(PrescalerValue & PRLH_MSB_MASK):在示例中,PrescalerValue为32767(0x7FFF【0111 1111 1111 1111】),那么(PrescalerValue & PRLH_MSB_MASK)的值为0x00000000
(PrescalerValue & PRLH_MSB_MASK) >> 16的值为0x00000000
RTC_PRLH(RTC预分频装载寄存器高位)
- RTC预分频装载寄存器高位RTC_PRLH在参考手册第312~313页有定义


RTC->PRLL = (PrescalerValue & RTC_LSB_MASK)
- 含义:将RTC_PRLL(RTC预分频装载寄存器低位)的第0~15位设置为0x7FFF
- RTC_LSB_MASK的值为0x0000FFFF【0000 0000 0000 0000 1111 1111 1111 1111】
(PrescalerValue & RTC_LSB_MASK):在示例中,PrescalerValue为32767(0x7FFF【0111 1111 1111 1111】),那么(PrescalerValue & RTC_LSB_MASK)的值为0x00007FFF
RTC_PRLL(RTC预分频装载寄存器低位)
- RTC预分频装载寄存器高位RTC_PRLH在参考手册第313页有定义

RTC_ExitConfigMode()
- 含义:将RTC_CRL(RTC控制寄存器低位)的第4位CNF(配置标志)设为0(退出配置模式(开始更新RTC寄存器))
- RTC_EnterConfigMode函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rtc.c(外设固体库文件)- - - ✿ 固体库文件
RTC_CRL_CNF
- 出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
RTC_CRL(RTC控制寄存器低位)
- RTC控制寄存器低位RTC_CRL在参考手册第311~312页有定义




★ ※ 什么是 RTC 预分频?预分频装载是什么?

★ ※ 参考手册中寄存器RTC预分频装载寄存器(RTC_PRLH/RTC_PRLL)的解释



★ ※ 为什么这么设计?(设计原因深度解析)


★ ※ 为什么要设置成 32767?(公式推导)

RTC_ITConfig(RTC_IT_SEC, ENABLE)
- 含义:将RTC_CRH(RTC控制寄存器高位)的第0位SECIE(允许秒中断)设置为1(允许秒中断)
- 第一个参数RTC_IT_SEC的值为0x0001,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
宏定义 | 十六进制值 | 对应 CRH 寄存器位 | 位名称 | 功能说明 |
RTC_IT_OW | 0x0004 | 位 2 | OWIE | 溢出中断使能:1 = 开启溢出中断,0 = 屏蔽 |
RTC_IT_ALR | 0x0002 | 位 1 | ALRIE | 闹钟中断使能:1 = 开启闹钟中断,0 = 屏蔽 |
RTC_IT_SEC | 0x0001 | 位 0 | SECIE | 秒中断使能:1 = 开启秒中断,0 = 屏蔽 |
- RTC_ITConfig函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rtc.c(外设固体库文件)- - - ✿ 固体库文件
RTC_CRH(RTC控制寄存器高位)
- RTC控制寄存器高位RTC_CRH在参考手册第311页有定义

6.2 RTC_Config(void)
NRST 是什么

为什么要写RTC_Config 函数

函数整体流程框架
逐段拆解:每一步为什么这么设计?寄存器有什么关联?






从零开始:怎么一步步想出这个设计流程?


所有寄存器操作的依赖关系

重新使能RTC(之前错误解释,现在重新解释)



时钟使能 | 所在寄存器 | 所属电源域 | 电源复位(VDD 掉电 / 上电)的影响 |
RTC 内核时钟使能(RTCEN 位) | RCC_BDCR 第 15 位 | 备份域(VBAT 供电) | 完全无影响,不会清零。只要纽扣电池有电,RTCEN 位一直保持 1,RTC 内核全程计时不中断 |
RTC/BKP 寄存器访问接口时钟(BKPEN 位) | RCC_APB1ENR 第 18 位 | 主电源域(VDD 供电) | 一定会清零。复位后 CPU 要读写 RTC、BKP 的寄存器,必须重新开启这个接口时钟 |


BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5
- 含义:从0x40006C04地址读出的备份数据寄存器x(BKP_DRx)的值不等于0xA5A5,则为true;否则为false
- 参数BKP_DR1的值为0x0004【0000 0000 0000 0100】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_bkp.h(外设固体库头文件)- - - ✿ 固体库文件
- BKP_ReadBackupRegister函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_bkp.c(外设固体库文件)- - - ✿ 固体库文件
BKP_BASE
- 后备寄存器(BKP)的起始地址:0x4000 6C00 ~ 0x4000 6FFF
- BKP_BASE的值为0x40006C00,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
备份数据寄存器x(BKP_DRx)
- BKP_DRx备份数据寄存器x在参考手册第48页有定义

BKP_WriteBackupRegister(BKP_DR1, 0xA5A5)
- 含义:将0xA5A5的值写入到地址为0x40006C04的备份数据寄存器x(BKP_DRx)
- 第一个参数BKP_DR1的值为0x0004【0000 0000 0000 0100】(上一个有说明)
- BKP_WriteBackupRegister函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_bkp.c(外设固体库文件)- - - ✿ 固体库文件
备份数据寄存器x(BKP_DRx)
- BKP_DRx备份数据寄存器x在参考手册第48页有定义

RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET 和 RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET
- 含义:
- 前者:看RCC_CSR(控制/状态寄存器)的第27位PORRSFT(上电/掉电复位标志)是否发生上电/掉电复位,若发生,则该位的值为1,则代码为true;否则值为0,代码为false。
- 后者:看RCC_CSR(控制/状态寄存器)的第26位PINRSTF(NRST引脚复位标志)是否发生NRST引脚复位,若发生,则该位的值为1,则代码为true;否则值为0,代码为false。
- 参数RCC_FLAG_PORRST的值为((uint8_t)0x7B)、RCC_FLAG_PINRST的值为((uint8_t)0x7A),出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
RCC_CR(时钟控制寄存器)- - - 与参数有关
- RCC_CR时钟控制寄存器在参考手册第60~61页有定义




RCC_BDCR(备份域控制寄存器)- - - 与参数有关
- RCC_BDCR备份域控制寄存器在参考手册第74页有定义


RCC_CSR(控制/状态寄存器)- - - 与参数有关
- RCC_CSR控制/状态寄存器在参考手册第75~76页有定义








★ RCC_GetFlagStatus(RCC_FLAG_PORRST) 和 RCC_GetFlagStatus(RCC_FLAG_PINRST)
- 整体作用:
- 前者:看是否有上电/掉电复位
- 后者:看是否有NRST引脚复位
- 出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_ClearFlag( )
- 含义:对RCC_CSR(控制/状态寄存器)的第24位RMVF(清除复位标志)置为1(清除复位标志),导致硬件自动清零全部复位标志
- RCC_ClearFlag函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_CSR(控制/状态寄存器)
- RCC_CSR控制/状态寄存器在参考手册第75~76页有定义


BKP_TamperPinCmd(DISABLE)
- 含义:对备份控制寄存器(BKP_CR)的第0位TPE(启动侵入检测TAMPER引脚)设置为0(侵入检测TAMPER引脚作为通用IO口使用)
Tamper 是什么意思?

Tamper 侵入检测功能是什么?侵入代表什么?(形象举例)

- BKP_TamperPinCmd函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_bkp.c(外设固体库文件)- - - ✿ 固体库文件
*(__IO uint32_t *) CR_TPE_BB = (uint32_t)NewState
- 外设BKP(后备寄存器)的起始地址:0x4000 6C00 ~ 0x4000 6FFF
- 含义:对备份控制寄存器(BKP_CR)的第0位TPE(启动侵入检测TAMPER引脚)设置为0(侵入检测TAMPER引脚作为通用IO口使用)
- CR_TPE_BB(值为0x420D8600【0100 0010 0000 1101 1000 0110 0000 0000】)的计算过程涉及的值,分别出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_bkp.c(外设固体库文件)- - - ✿ 固体库文件
出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
计算过程:
CR_TPE_BB涉及到“位段”的知识点,“位段”出自参考手册第29~30页有定义


BKP_CR(备份控制寄存器)
- BKP_CR备份控制寄存器在参考手册第49页有定义

BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock)
- 含义:将BKP_RTCCR(RTC时钟校准寄存器)的第7位CCO(校准时钟输出)设置为1(此位置1可以在侵入检测引脚输出经64分频后的RTC时钟。当CCO位置1时,必须关闭侵入检测功能以避免检测到无用的侵入信号)
- 参数BKP_RTCOutputSource_CalibClock的值为0x0080【0000 0000 1000 0000】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_bkp.h(外设固体库头文件)- - - ✿ 固体库文件
寄存器位 | 位序号 | 对应功能模式 | 硬件功能说明 |
ASOS | 位 9 | 闹钟 / 秒输出选择 | 仅 ASOE 置 1 时生效:0 = Tamper 引脚输出 RTC 闹钟脉冲1 = Tamper 引脚输出 RTC 秒脉冲 |
ASOE | 位 8 | 闹钟 / 秒输出使能 | 总开关:置 1 后允许 RTC 闹钟 / 秒脉冲输出到 Tamper 引脚 |
CCO | 位 7 | 校准时钟输出使能 | 置 1 后,Tamper 引脚输出经 64 分频后的 RTC 时钟,用于时钟校准 |
- BKP_RTCOutputConfig函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_bkp.c(外设固体库文件)- - - ✿ 固体库文件
BKP_RTCCR(RTC时钟校准寄存器)
- RTC时钟校准寄存器BKP_RTCCR在参考手册第48~49页有定义


BKP_RTCCR 寄存器每一位的含义(大白话 + 举例)


BKP_RTCCR 每个位之间的关联和上一个图片末尾的CAL的举例计算说明






6.3 RTC_IRQHandler(void)
函数整体定位与核心目的

逐行流程拆解 + 设计逻辑(为什么是这个顺序)
- 整个函数完全遵循 STM32 外设中断的通用标准流程:「进入中断 → 校验中断源 → 执行业务逻辑 → 清除中断标志 → 等待操作生效 → 退出中断」,每一步的顺序都是硬件机制决定的,不是凭空设计的。
步骤 | 代码 | 作用与设计逻辑 | 硬件寄存器关联 |
1 | if (RTC_GetITStatus(RTC_IT_SEC) != RESET) | 中断源校验:确认本次中断是不是「秒中断」触发的。👉 设计原因:RTC 的秒、闹钟、溢出 3 个中断共用同一个中断向量,进入中断后必须先区分是哪个事件触发的,避免误处理;同时校验「中断使能 + 事件标志」双条件,排除未使能的干扰。 | 同时读取两个寄存器:- RTC_CRH 位 0 SECIE(秒中断使能开关)- RTC_CRL 位 0 SECF(秒事件标志)只有两者都为 1,才是合法的秒中断触发。 |
2 | 秒中断处理逻辑 | 业务执行:放在判断分支内,只有确认是秒中断才执行。👉 设计原则:中断服务函数要「快进快出」,这里只放短平快的核心逻辑(如时间计数 + 1),绝对不能放延时、串口打印等耗时操作,否则会阻塞其他中断。 | 无寄存器操作,纯软件业务逻辑。 |
3 | RTC_ClearITPendingBit(RTC_IT_SEC); | 清除中断挂起标志:把秒事件标志位清零,应答本次中断。👉 为什么必须做?硬件机制决定:中断标志位被硬件置 1 后会一直保持,如果软件不清零,退出中断后标志位还是 1,会立刻再次触发中断,导致程序死在中断里永远出不来。 | 写 RTC_CRL 位 0 SECF,写 0 清零(硬件规定写 1 无效)。 |
4 | RTC_WaitForLastTask(); | 等待写操作完成:等待 RTC 硬件真正完成寄存器写入。👉 为什么要等?RTC 是低速外设(时钟 32.768kHz),APB1 总线是高速时钟(最高 36MHz),软件写寄存器只是把数据发到总线接口,硬件需要几个 RTC 时钟周期才能真正写入寄存器。如果不等就退出,后续操作 RTC 可能出现写入失败、值不对的问题。 | 循环读 RTC_CRL 位 5 RTOFF,直到硬件将其置 1(代表上一次写操作完成)。 |
寄存器的关联与顺序必然性

从零推导:没有参考怎么写出这个流程?

核心疑问解答:为什么不一块清除中断使能?
概念 | 对应位 | 作用 | 类比 |
中断使能 SECIE | RTC_CRH 位 0 | 「中断总开关」:决定事件发生时要不要通知 CPU。置 1 就通知,置 0 就不通知,但事件本身还是会发生(标志位照常置 1)。 | 门铃的电源开关:开着的时候有人按就响,关了之后按了也不响。 |
中断标志 SECF | RTC_CRL 位 0 | 「事件标记」:只要秒事件发生,硬件就自动置 1,和使能开关无关。 | 门铃的铃声:有人按就一直响,直到你手动关掉。 |

RTC_GetITStatus(RTC_IT_SEC) != RESET
- 含义:检查RTC指定中断是否已经触发(同时校验「中断使能状态」与「中断事件标志」),即检查RTC_CRH(RTC控制寄存器高位)的第0位SECIE(允许秒中断)是否为1(允许秒中断)和RTC_CRL(RTC控制寄存器低位)的第0位SECF(秒标志)是否为1(秒标志条件成立),若都为1,则返回1,那么代码为true;否则为false
- 参数RTC_IT_SEC的值为0x0001,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
宏定义 | 十六进制值 | 对应 CRH 寄存器位 | 位名称 | 功能说明 |
RTC_IT_OW | 0x0004 | 位 2 | OWIE | 溢出中断使能:1 = 开启溢出中断,0 = 屏蔽 |
RTC_IT_ALR | 0x0002 | 位 1 | ALRIE | 闹钟中断使能:1 = 开启闹钟中断,0 = 屏蔽 |
RTC_IT_SEC | 0x0001 | 位 0 | SECIE | 秒中断使能:1 = 开启秒中断,0 = 屏蔽 |
- RTC_GetITStatus函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rtc.c(外设固体库文件)- - - ✿ 固体库文件
RTC_CRH(RTC控制寄存器高位)
- RTC控制寄存器高位RTC_CRH在参考手册第311页有定义

RTC_CRL(RTC控制寄存器低位)
- RTC控制寄存器低位RTC_CRL在参考手册第311~312页有定义


RTC_ClearITPendingBit(RTC_IT_SEC)
- 含义:对RTC_CRL(RTC控制寄存器低位)的第0位SECF(秒标志)置为0(秒标志条件不成立),即清除RTC指定的中断挂起标志位(清除RTC_CRL寄存器中的事件标志)
- 参数RTC_IT_SEC在上一个有说明,且值为0x0001
- RTC_ClearITPendingBit函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rtc.c(外设固体库文件)- - - ✿ 固体库文件
RTC_CRL(RTC控制寄存器低位)
- RTC控制寄存器低位RTC_CRL在参考手册第311~312页有定义


6.4 u8 const table_week[12] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5}




6.5 RTC_Set(u16 syear, u8 smon, u8 sday, u8 hour, u8 min, u8 sec)
函数核心定位与设计目的

逐段流程拆解 + 设计逻辑



硬件场景 | 重新初始化的作用 |
第一次使用,备份域完全没电(纽扣电池也空了) | 所有配置都丢了,必须从头完整初始化,否则根本写不了 RTC 寄存器 |
主电源掉电 / 复位,备份域有电池供电 | 主电源域的 PWR 时钟、BKP 接口时钟会被复位清零,备份域写保护会重新开启;重新初始化能重新开时钟、解写保护,恢复 CPU 对 RTC 的写权限 |
正常运行中,用户主动修改时间 | 所有配置本来就是好的,重新初始化只是再写一遍相同的值,没有任何影响,相当于加了一层保险,防止万一寄存器被干扰篡改 |

寄存器操作的关联与顺序必然性

复位过程

RTC_SetCounter(seccount)
- 含义:主要是将seccount(秒的差距总数)写入RTC_CNTH / RTC_CNTL(RTC计数器寄存器)中,其中高16位写入RTC_CNTH,低16位写入RTC_CNTL
- RTC_SetCounter函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rtc.c(外设固体库文件)- - - ✿ 固体库文件
RTC_CRL(RTC控制寄存器低位)
- RTC控制寄存器低位RTC_CRL在参考手册第311~312页有定义


RTC_CNTH / RTC_CNTL(RTC计数器寄存器)
- RTC计数器寄存器RTC_CNTH / RTC_CNTL在参考手册第313~314页有定义


6.6 RTC_Get(void)
以2024年2月29号18点20分20秒为例子进行计算




循环次数 | 当前年份 temp1 | 年份类型 | 当年天数 | 剩余天数 temp | 操作 |
1 | 1970 | 平年 | 365 | 19782-365=19417 | temp1 自增为 1971 |
2 | 1971 | 平年 | 365 | 19417-365=19052 | temp1 自增为 1972 |
3 | 1972 | 闰年 | 366 | 19052-366=18686 | temp1 自增为 1973 |
... | ... | ... | ... | ... | ... |
54 | 2023 | 平年 | 365 | 59-365?不,循环到第 54 次后 temp 剩余 59 | temp1 自增为 2024 |

循环次数 | 月份索引 temp1 | 对应月份 | 月份类型 | 当月天数 | 剩余天数 temp | 操作 |
1 | 0 | 1 月 | 平年月 | 31 | 59-31=28 | temp1 自增为 1 |
2 | 1 | 2 月 | 闰年 2 月 | 29 | 28 < 29,不满足减法条件 | 执行 break,退出循环 |


timecount=RTC_GetCounter()
- 含义:获取RTC计数器寄存器的值,即之前的时间距离差总秒数
- RTC_GetCounter函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rtc.c(外设固体库文件)- - - ✿ 固体库文件
RTC_CRL(RTC控制寄存器低位)
- RTC控制寄存器低位RTC_CRL在参考手册第311~312页有定义


RTC_CNTH / RTC_CNTL(RTC计数器寄存器)
- RTC计数器寄存器RTC_CNTH / RTC_CNTL在参考手册第313~314页有定义


6.7 RTC_Get_Week(u16 year, u8 month, u8 day)
函数核心定位与设计目的

逐行代码流程拆解 + 实例验证




设计思路推导:没有参考也能一步步写出来


搞懂 yearL=124 到底是什么意思



年份编号 yearL | 对应目标年 1 月 1 日 | 经过的整年 | 经过的年是平 / 闰 | 当年带来的偏移 | 累计总偏移(实际值) | 公式计算值 yearL + yearL/4 | 差值(公式 - 实际) |
0 | 1900 年 | 无 | - | 0 | 0 | 0 + 0 = 0 | 0 |
1 | 1901 年 | 1900 年 | 平年 | +1 | 1 | 1 + 0 = 1 | 0 |
2 | 1902 年 | 1901 年 | 平年 | +1 | 2 | 2 + 0 = 2 | 0 |
3 | 1903 年 | 1902 年 | 平年 | +1 | 3 | 3 + 0 = 3 | 0 |
4 | 1904 年 | 1903 年 | 平年 | +1 | 4 | 4 + 1 = 5 | +1 (第一次出现差) |
5 | 1905 年 | 1904 年 | 闰年 | +2 | 4+2=6 | 5 + 1 = 6 | 0 |
6 | 1906 年 | 1905 年 | 平年 | +1 | 7 | 6 + 1 = 7 | 0 |
7 | 1907 年 | 1906 年 | 平年 | +1 | 8 | 7 + 1 = 8 | 0 |
8 | 1908 年 | 1907 年 | 平年 | +1 | 9 | 8 + 2 = 10 | +1 |
9 | 1909 年 | 1908 年 | 闰年 | +2 | 11 | 9 + 2 = 11 | 0 |
10 | 1910 年 | 1909 年 | 平年 | +1 | 12 | 10 + 2 = 12 | 0 |

七、触摸按键
7.1 根据以上的文档,对下面的图片进行解释

图1

图2

图3

图4

图5

图6
- 根据6张图片,得出已知信息
- 图1和图2表明:OUT_1 = TTP223-1 = PA0
- 图5和6表明:PA0端口内部为上拉电阻模式
- 图4表明:第4脚AHLB和第6脚TOG内部为下拉电阻模式
- 图3的电路图表明:
- 线路1:第4脚AHLB与3V电源相连
- 线路2:第4脚AHLB还连接C4电容到GND
- 线路3:第5脚VCC与3V电源相连
- 线路4:第6脚TOG与GND相连
- 线路5:第3脚IN与(J3与C3组成的并联线路)相连,最终流向GND
- 线路6:第2脚GND与GND相连
- 线路7:第1脚OUT与OUT_1相连,即第1脚OUT直接连接在PA0端口上
- 线路8:同时在第1脚上还并联了(LED0的指示灯并且串联了一个限流电阻R12)的线路,整个电路是连接在3V电源上
- 图3的外围器件:
- 图3的J3:是触摸电极接口,本质就是一块裸露的铜箔、金属弹簧片或者导电触摸片,是你手指实际触摸的地方。
- 角色:触摸传感器的前端,相当于芯片伸到外面的 “感应片”;
- 作用:手指靠近 / 触摸它时,会改变它的电容大小,芯片通过 3 脚检测到这个变化,就知道有人摸按键了。
- 图4补充:
- IO类型解释
- 第3脚IN:
- 内部有一个充放电的电路,可以输入和输出;
- 内部有一个检测电容变化的电路,原始电路(没触摸状态下)的电容为J3本身的小电容+C3的电容;触摸后,电容变大,变为J3本身小电容+人体中存在的大电容+C3的电容;
- 第1脚OUT:CMOS输出为可以输出高电平或低电平
- 第4脚AHLB和第6脚TOG:只能输入
- 第6脚TOG有两个模式:触发模式和直接模式
- 触发模式(TOG=1)
- 也叫翻转模式 / 自锁模式,和家里的墙壁电灯开关逻辑一样:
- 第一次摸按键:输出翻转一次(从关→开),松开手之后,输出保持开启状态;
- 第二次摸按键:输出再翻转一次(从开→关),松开手之后,输出保持关闭状态。
- 适用场景:触摸台灯、电源开关,不需要一直按着,摸一下切换一次状态。
- 直接模式(TOG=0,本电路就是这个模式)
- 也叫点动模式,和普通机械按键行为完全一致:
- 手指按住触摸键:输出一直保持触发状态(本电路里就是 LED 一直亮);
- 手指一松开:输出立刻恢复成未触发状态(LED 立刻灭)。
- 适用场景:门铃、键盘按键、需要按住才生效的功能,和 “按下去就通、松开就断” 的普通按钮一模一样。
- 第4脚AHLB的“输出高电平或者低电平有效选择”解释
- 通过 AHLB 引脚,你可以选择「摸按键的时候,OUT 脚输出高电平代表触发」,还是「摸按键的时候,OUT 脚输出低电平代表触发」。
- 高电平有效:没摸 = 低电平,摸了 = 高电平;
- 低电平有效:没摸 = 高电平,摸了 = 低电平。

器件 | 参数 | 接线位置 | 角色与作用 |
R12 | 1KΩ 电阻 | 串在 OUT 脚和 LED 之间 | 限流电阻:限制流过 LED 的电流,防止电流太大烧坏 LED 和芯片输出脚 |
D1(LED0) | 发光二极管 | 正极接 3V,负极接 R12 | 状态指示灯:触摸触发时点亮,直观显示有没有摸到按键 |
C3 | 15pF 电容 | 接在 IN 脚和 GND 之间 | 灵敏度调节电容:用来调整触摸的灵敏程度,数值越大灵敏度越低 |
C4 | 0.1μF 电容 | 接在 3V 和 GND 之间 | 电源滤波电容:过滤电源里的杂波、抖动,让芯片供电更平稳,避免电源波动导致误触发 |

类型缩写 | 全称 | 含义 | 对应引脚例子 |
O | Output | 纯输出引脚 | 1 脚 Q/OUT:只能往外发高低电平信号,不能当输入用 |
P | Power | 电源引脚 | 2 脚 VSS/GND、5 脚 VDD/VCC:只负责供电,不传输信号 |
I/O | Input/Output | 双向输入输出引脚 | 3 脚 I/IN:内部有振荡电路,既可以输出充电信号,也可以输入检测电容的信号,双向工作 |
I-PL | Input with Pull-down | 带内部下拉电阻的输入引脚 | 4 脚 AHLB、6 脚 TOG:只能读外面的电平,芯片内部自带一个接地的电阻,悬空时默认就是低电平,不用额外接零件 |
八、数码管
8.1 共阴/共阳数码管

共阴
- 公共部分都接到GND上
- 意味着左边只有是高电平,才能使数码管上对应的LED亮
- 示例:

如图所示,当要显示数字1时,就需要点亮b、c两个LED,那么按照LED排列(a、b、c、d、e、f、g、dp 依次为 bit0、bit1、bit2、bit3、bit4、bit5、bit6、bit7),又因为点亮LED需要设置为高电平,则二进制位 0000 0110,十六进制为 0x06 = 6H
共阳
- 公共部分都接到VCC上
- 意味着右边只有是低电平,才能使数码管上对应的LED亮
- 示例:

如图所示,当要显示数字1时,就需要点亮b、c两个LED,那么按照LED排列(a、b、c、d、e、f、g、dp 依次为 bit0、bit1、bit2、bit3、bit4、bit5、bit6、bit7),又因为点亮LED需要设置为低电平,则二进制位 1111 1001,十六进制为 0xF9 = F9H
8.2 两个电路原理图文档相关数码管内容解释
开发板总图文档相关内容和与之相关的数码管文档的部分内容


以上两张图:表明PA11连接在TM1640芯片的第8脚SCLK(时钟线)上,PA12连接在TM164芯片的第脚DIN(数据线)上
TM1640数码管文档内容

- 这张图:表明 J1 右边方框内的数字 1、2、3、4 表示从下到上的数码管顺序,左边方框内的字母 A~H 分别代表一位数码管中的 a、b、c、d、e、f、g、dp 八个LED灯,且 a、b、c、d、e、f、g、dp 八个LED灯分别在图中为 SEG1、SEG2、SEG3、SEG4、SEG5、SEG6、SEG7、SGE8
- 由于是共阴数码管,所以若要LED亮,则要使其LED对应位的值为高电平

- 这张图:表明 J2 右边方框内的数字 1、2、3、4 表示从下到上的数码管顺序,左边方框内的字母 A~H 分别代表一位数码管中的 a、b、c、d、e、f、g、dp 八个LED灯,且 a、b、c、d、e、f、g、dp 八个LED灯分别在图中为 SEG1、SEG2、SEG3、SEG4、SEG5、SEG6、SEG7、SGE8
- 由于是共阴数码管,所以若要LED亮,则要使其LED对应位的值为高电平

- 结合上两张图片,第一张图 J1 右边方框内的数字 1、2、3、4 根据右边对应关系 1、2、3、4 ,连接在这张图的 TM1640 芯片的第 18 ~ 21 脚 GR1 ~ GR4 ,左边方框内的字母则连接在第 9 ~ 16 脚 SEG1 ~ SEG8;
- 第二张图 J2 右边方框内的数字 1、2、3、4 根据右边对应关系 5、6、7、8 ,连接在这张图的 TM1640 芯片的第 22 ~ 25 脚 GR5 ~ GR8 ,左边方框内的字母则连接在第 9 ~ 16 脚 SEG1 ~ SEG8;

- 这张图:表示为了更形象展示数码管的各个 LED ,又因为之前使用了 8 位 8 段共阴数码管,即第一张和第二张图的数码管,所以只能使用用于表示第 9 位数码管的 GR9(TM1640 芯片第 26 脚),然后 8 个 LED 直接用如图所示的 LED1 ~ LED8 表示;
- 倘若向 GR9 所在的显存地址 C9H 写入 0x01,那么图中 LED1 亮,其余不亮;比如下面例子。


8.3 TM1640数据手册文档解释
SCLK与DIN对应关系

- 这张图换种说法说明:最初 SCLK 和 DIN 都是高电平,且分别为时钟线和数据线。开始:当 DIN 出现由高电平变成低电平的过程,且 SCLK 始终为高电平的情况下,则为数据传输开始标志。传输:之后数据从低到高位次依次传输高电平或低电平,则只有当 SCLK 为高电平时有效。结束:最后当在 SCLK 为高电平时,DIN 从低电平变成高电平的过程表示为数据传输结束标志。
- 代码示例:
- 可参考 SCL 和 SDA 的传输方式和向寄存器读写方式



自动地址或者固定地址写数据格式


- 这两张图片:表明两种模式下的操作方式,注意每个命令(Command1、Command2、Command3)的开始和结束部分,三种命令不是连在一起发送的,而是各自有开始标志和结束标志
- 其中数据指令分类、数据命令、显示控制命令、地址命令具体要求如下:






- 代码示例:
九、旋转编码器
9.1 旋转编码器介绍

- 编码器具有左旋,右旋,按下三个功能。4、5 脚是中间按下去的开关接线 1 2 3 脚一般中间 2 脚接地,1、3 脚上拉电阻后,当左转、右转旋纽时,在 1、3 脚就有脉冲信号输出了。

- 输出 1 为高电平时,输出 2 出现一个高电平,这时开关就是向顺时针旋转;当输出 1 为高电平,输出 2 出现一个低电平,这时就一定是逆时针方向旋转。编程时只需要判断当输出 1 为高电平时,输出 2 当时的状态就可以判断出是左旋转或是右旋转。
9.2 两个电路原理图文档关于旋转编码器内容的解释


- 第一张图片:表明 JS_X、JS_Y、JS_D 分别连接在 PA6、PA7、PB2端口上
- 第二张图片:由于这是关于编码器的解释,所以不看模拟量摇杆部分的电路,只看编码器部分。
- 第一、二张图片结合后的编码器 PD1 连线如下:
- 1、4 两个脚接在 GND 上
- 2 脚连在 P18 上的右边 4 脚上,且 4 脚对应左边的 3 脚,又因为左边 3 脚连在 JS_Y(PA7端口)上
- 3 脚连在 P18 上的右边 6 脚上,且 6 脚对应左边的 5 脚,又因为左边 5 脚连在 JS_D(PB2端口)上
- 5 脚连在 P18 上的右边 2 脚上,且 2 脚对应左边的 1 脚,又因为左边 1 脚连在 JS_X(PA6端口)上
9.3 旋转编码器波形示意图和机械抖动图


- 第一张图表示:1、2 脚用于按下键的操作,且结合之前的图片可知,1 脚连接在 GND 上,因此 K1 按键为用于 2 脚与 1 脚的开关相连。3、4、5 脚用于左右旋转的操作,且结合之前图片可知,4 脚连接在 GND 上,因此 K2 按键为用于 4 脚与 5 脚的开关相连, K3 按键为用于 4 脚与 3 脚的开关相连
- 【重点】:第二张图片所示的方向 1 和 2 并不固定,因硬件本身而变,因此方向 1 可能是左转也可能是右转,方向 2 可能是右转也可能是左转
- 第二张图片表示(PA6/PA7/PB2 三个端口内部是上拉电阻,因此 K2/K3 一开始输出的是高电平):
- 看 K2/K3 哪个先变成低电平
- 方向 1 的时候:K3 先变成低电平,随后 K2 才变成低电平
- 方向 2 的时候:K2 先变成低电平,随后 K3 才变成低电平
- K2 刚触发从高电平变成低电平的瞬间,K3 是什么情况
- 方向 1 的时候,K3 处于低电平状态
- 方向 2 的时候,K3 处于高电平状态

- 这张图表明:转动 1 格,由高电平变成低电平的瞬间的机械抖动小于等于 2ms,360 度内有 20 位格指的是我所持有的旋转编码器的格数,不同的旋转编码器格数各有不同
9.4 旋转编码器数码管显示程序的示例代码
- main.c
- encoder.h
- encoder.c
延时代码 | 作用对象 | 核心作用 | 消除 / 解决的问题 | 时间怎么来的 | 和图 3 的关联 |
delay_us(100) | 旋转相位 K2(ENCODER_L) | 初步毛刺过滤 | K2 刚接触瞬间的极短尖峰抖动 | 经验值,100 微秒很短,只过滤最开头的杂波 | 弱关联:针对图 3 里抖动最开始的尖峰,不是完整消抖 |
delay_ms(3) | 旋转相位 K2(ENCODER_L) | 完整机械消抖 | K2 闭合时的机械抖动,避免一次转动识别成多次 | 图 3 标注抖动≤2ms,取 3ms 略大于最大抖动时间,确保抖完 | 直接关联:就是针对图 3 的 2ms 机械抖动设计的消抖延时 |
delay_us(20) | 锁死计数器 cou | 控制循环速度,计算超时时间 | 不是消抖,是用来算 “停多久算锁死” | 经验值,循环一次 20us,60000 次就是 1.2 秒左右 | 无关联:纯软件逻辑的超时计时,和机械抖动没关系 |
delay_ms(20) | 按键 K1(ENCODER_D) | 按键机械消抖 | 按键按下时的机械抖动,避免按一次识别成多次 | 普通按键抖动通常 5~10ms,取 20ms 是保守值,确保稳定 | 原理关联:和图 3 的机械抖动是同一个原理,只是按键行程长,抖动更久,所以延时更长 |
十、温度传感器数码管显示程序

I2C手册内容解析


解释1





解释2


涉及到的寄存器位
OAR1 - - - OAR1寄存器

OAR2、ENDUAL - - - OAR2寄存器

ENGC - - - CR1寄存器


ADDR - - - SR1寄存器


ITEVTEN - - - CR2寄存器


DUALF - - - SR2寄存器



解释3


解释4 - - - 图 243







涉及到的寄存器位
ADDR、TxE、BTF、AF - - - SR1寄存器





ITEVTEN、ITBUFEN - - - CR2寄存器



DR - - - DR(数据)寄存器


解释5

涉及到的寄存器位
ADDR、RxNE、BTF - - - SR1寄存器




ITEVTEN、ITBUFEN - - - CR2寄存器



DR - - - DR(数据)寄存器


解释6

涉及到的寄存器位
ADDR、RxNE、STOPF - - - SR1寄存器




ITEVTEN - - - CR2寄存器


DR - - - DR(数据)寄存器


解释7


涉及到的寄存器位
START、PE(启动外设) - - - CR1寄存器



FREQ[5:0](设定该模块的输入时钟以产生正确时序)、ITEVTEN - - - CR2寄存器



CCR时钟控制寄存器 - - - CCR寄存器


TRISE上升时间寄存器 - - - TRISE寄存器

BUSY、MSL - - - SR2寄存器



ADD10、ADDR、SB - - - SR1寄存器




DR - - - DR(数据)寄存器


解释8

涉及到的寄存器位
ADDR - - - SR1寄存器


ITEVTEN - - - CR2寄存器


TRA - - - SR2寄存器



解释9



涉及到的寄存器位
ADDR、TxE、BTF、AF、SB、ADD10 - - - SR1寄存器







ITEVTEN、ITBUFEN - - - CR2寄存器



DR - - - DR(数据)寄存器

MSL - - - SR2寄存器


STOP - - - CR1寄存器



解释10

涉及到的寄存器位
ADDR、RxNE、BTF - - - SR1寄存器




ITEVTEN、ITBUFEN - - - CR2寄存器



DR - - - DR(数据)寄存器

ACK - - - CR1寄存器



解释11



解释12 - - - 图246



涉及到的寄存器位
ADDR、RxNE、SB、ADD10 - - - SR1寄存器





DR - - - DR(数据)寄存器

MSL - - - SR2寄存器


ACK、STOP、START - - - CR1寄存器




LM75温度传感器相关手册和电路图解析



- 在本案例上(第二张图所示),LM75A温度传感器A0、A1、A2,即第7、6、5脚连接在3V(高电平)的电源上;第三图所示,LM75A温度传感器的SDA、SCL,即第1、2脚分别连接在PB7、PB6端口上;


- LM75温度传感器的从机地址按从高位到低位的顺序规定为1001+A2+A1+A0+R(1)/W(0),根据A2/A1/A0的不同值,可以分出8个不同从地址
- 温度寄存器的地址为0x00,只能读取,且读取两个字节(共16位)
- 配置寄存器的地址为0x01,读/写皆可,为1个字节(共8位)
- 滞后(Thyst)寄存器的地址为0x02,读/写皆可,为0x4B0X(0100 1011 0XXX XXXX)
- 过温关断阈值(Tos)寄存器的地址为0x03,读/写皆可,为0x500X(0101 0000 0XXX XXXX)



- 除了第二张图所展示的温度计算方法之外,第一张图所展示的方法为温度相加,因此D7为0.5,D6为0.25,D5为0.125
- 第二张图所说的补码是在原码的基础上,进行反码操作,再加上1

- D0=1,为代码中所说的省电模式


main.c文件代码文件:
i2c.h文件代码文件:
i2c.c文件代码文件:
Im75a.h文件代码文件:
Im75a.c文件代码文件:
10.1 I2C_Configuration(void)
I2C_InitTypeDef I2C_InitStructure
- I2C_InitTypeDef类型的结构体,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_i2c.h(外设固体库头文件)- - - ✿ 固体库文件
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C
- 含义:将I2C_CR1(控制寄存器1)的第1位SMBUS(SMBus模式)设置为0(I2C模式)
- I2C_Mode_I2C,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_i2c.h(外设固体库头文件)- - - ✿ 固体库文件
I2C_CR1(控制寄存器1)- - -【因涉及代码中I2C模式和SMBUS模式,所以在此做出解释】
对下方手册内容的解释【豆包】- - - SMBUS和I2C区别和联系




- I2C_CR1控制寄存器1在参考手册第505~507页有定义


I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2
- 含义:将I2C_CCR(时钟控制寄存器)下的第14位DUTY(快速模式时的占空比)设置为0(快速模式下:/ = 2)
- I2C_DutyCycle_2,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_i2c.h(外设固体库头文件)- - - ✿ 固体库文件
I2C_CCR(时钟控制寄存器)- - -【因涉及代码中计算问题(I2C标准/快速模式的CCR),所以在此做出解释】
对下方手册内容的解释【豆包】





- I2C_CCR时钟控制寄存器在参考手册第513~514页有定义


I2C_InitStructure.I2C_OwnAddress1 = HostAddress
- 含义:I2C的总线主机的器件地址为0xc0【1100 0000】,即将I2C_OAR1(自身地址寄存器1)的第1~7位ADD[7:1](接口地址)设置为11000000(0xc0,总线主机的器件地址)
- HostAdress,出自i2c.h文件
I2C_OAR1(自身地址寄存器1)
- I2C_OAR1自身地址寄存器1在参考手册第508~509页有定义


I2C_InitStructure.I2C_Ack = I2C_Ack_Enable
- 含义:将I2C_CR1(控制寄存器1)的第10位ACK(应答使能)设置为1(在接收到一个字节后返回一个应答(匹配的地址或数据))
- I2C_Ack_Enable,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_i2c.h(外设固体库头文件)- - - ✿ 固体库文件
I2C_CR1(控制寄存器1)
- I2C_CR1控制寄存器1在参考手册第505~507页有定义


I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit
- 含义:将I2C_OAR1(自身地址寄存器1)的第15位ADDMODE(寻址模式(从模式))设置为0(7位从地址(不响应10位地址))
- I2C_AcknowledgedAddress_7bit,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_i2c.h(外设固体库头文件)- - - ✿ 固体库文件
I2C_OAR1(自身地址寄存器1)
- I2C_OAR1自身地址寄存器1在参考手册第508~509页有定义


I2C_InitStructure.I2C_ClockSpeed = BusSpeed
- 含义:I2C的总线速度为200000,I2C标准模式和快速模式下的速度分别为100kHz和400kHz
- BusSpeed,出自i2c.h文件
I2C_CCR(时钟控制寄存器)- - -【因涉及代码中计算问题(I2C标准/快速模式的CCR),所以在此做出解释】
对下方手册内容的解释【豆包】





- I2C_CCR时钟控制寄存器在参考手册第513~514页有定义


I2C_Init(I2C1,&I2C_InitStructure)
- 含义:根据之前配置的结构体I2C_InitStruct,来将对应寄存器的相应位进行配置I2C
- I2C1的起始地址:0x4000 5400 ~ 0x4000 57FF
- 第一个参数I2C1的地址值为0x4000 5400,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
- I2C_Init函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_i2c.c(外设固体库文件)- - - ✿ 固体库文件
IS_I2C_ALL_PERIPH(I2Cx)
- IS_I2C_ALL_PERIPH函数,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_i2c.h(外设固体库头文件)- - - ✿ 固体库文件
I2C_CR2(控制寄存器2)
- I2C_CR2控制寄存器2在参考手册第507~508页有定义


RCC_ClocksTypeDef rcc_clocks
- 创建了RCC_ClocksTypeDef类型的变量,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_rcc.h(外设固体库头文件)- - - ✿ 固体库文件
RCC_GetClocksFreq(&RCC_ClocksStatus)
- RCC_GetClocksFreq函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
tmp = RCC->CFGR & CFGR_SWS_Mask
- CFGR_SWS_Mask的值为((uint32_t)0x0000000C),出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_CFGR(时钟配置寄存器)- - - 与代码语句有关
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义


RCC_Clocks->SYSCLK_Frequency = HSI_VALUE
- 含义:将SYSCLK 系统主时钟频率设为HSI(内部高速)
- RCC_Clocks->SYSCLK_Frequency:SYSCLK 系统主时钟频率
- HSI_VALUE,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
RCC_Clocks->SYSCLK_Frequency = HSE_VALUE
- 含义:将SYSCLK 系统主时钟频率设为HSE(外部高速)
- RCC_Clocks->SYSCLK_Frequency:SYSCLK 系统主时钟频率
- HSE_VALUE,出自CMSIS文件夹下的外设相关的stm32f10x.h(外设寄存器的定义)- - - ✿ 固体库文件
pllmull = RCC->CFGR & CFGR_PLLMull_Mask和pllsource = RCC->CFGR & CFGR_PLLSRC_Mask
- 含义:前者:读取PLL倍频系数(CFGR寄存器的PLLMULL[21:18]位);后者:读取PLL时钟源选择(CFGR寄存器的PLLSRC[16]位)
RCC_CFGR(时钟配置寄存器,非互联网型)
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义



RCC_CFGR(时钟配置寄存器,互联网型)
- RCC_CFGR时钟配置寄存器在参考手册第86~88页有定义




prediv1factor = (RCC->CFGR2 & CFGR2_PREDIV1) + 1和RCC_Clocks->SYSCLK_Frequency = (HSE_VALUE / prediv1factor) * pllmull
- 属于超值型范畴
- 含义:前者:读取CFGR2寄存器的0~3位,然后在此基础上加上1,最后赋值给prediv1factor。后者:将HSE_VALUE(8000000)除以prediv1factor,得出的值乘以之前“
pllmull = (pllmull >> 18) + 2;”处理的倍频系数值,最后写回到RCC_Clocks对象的SYSCLK系统主时钟频率中。
- CFGR2_PREDIV1,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_rcc.c(外设固体库文件)- - - ✿ 固体库文件
RCC_CFGR2(时钟配置寄存器2)
- RCC_CFGR2时钟配置寄存器2在参考手册第101~103页有定义



RCC->CFGR & CFGR_PLLXTPRE_Mask
- 含义:读取CFGR寄存器的第17位PLLXTPRE(HSE分频器作为PLL输入)
RCC_CFGR(时钟配置寄存器)
- RCC_CFGR时钟配置寄存器在参考手册第61~63页有定义


prediv1source = RCC->CFGR2 & CFGR2_PREDIV1SRC
- 属于互联网型范畴
- 含义:读取CFGR2寄存器的第16位,赋值给prediv1source
RCC_CFGR2(时钟配置寄存器2)
- RCC_CFGR2时钟配置寄存器2在参考手册第101~103页有定义



prediv2factor = ((RCC->CFGR2 & CFGR2_PREDIV2) >> 4) + 1
- 属于互联网型范畴
- 含义:读取CFGR2寄存器的第4~7位,然后右移4位,变成原本第4~7位移到了第0~3位,之后加上1,赋值给prediv2factor
RCC_CFGR2(时钟配置寄存器2)
- RCC_CFGR2时钟配置寄存器2在参考手册第101~103页有定义



pll2mull = ((RCC->CFGR2 & CFGR2_PLL2MUL) >> 8 ) + 2
- 属于互联网型范畴
- 含义:读取CFGR2寄存器的第8~11位,然后右移8位,变成原本第8~11位移到了第0~3位,之后加上2,赋值给pll2mull
RCC_CFGR2(时钟配置寄存器2)
- RCC_CFGR2时钟配置寄存器2在参考手册第101~103页有定义



I2C_CR1(控制寄存器1)- - -【因涉及代码中I2C模式和SMBUS模式,所以在此做出解释】
对下方手册内容的解释【豆包】- - - SMBUS和I2C区别和联系




- I2C_CR1控制寄存器1在参考手册第505~507页有定义



I2C_CCR(时钟控制寄存器)- - -【因涉及代码中计算问题(I2C标准/快速模式的CCR),所以在此做出解释】
对下方手册内容的解释【豆包】





- I2C_CCR时钟控制寄存器在参考手册第513~514页有定义


I2C_TRISE(TRISE寄存器)- - -【因涉及代码中计算问题(I2C标准/快速模式的TRISE(单次最大上升时间)),所以在此做出解释】
对下方手册内容的解释【豆包】
PCLK和SCL区别



手册内容解释1





手册内容解释2





那TRISE最大上升时间指的是总共要花费的时间的吗?还是指单次由低电平到高电平的上升沿的时间?

- TRISE寄存器I2C_TRISE在参考手册第514页有定义

I2C_OAR1(自身地址寄存器1)
- I2C_OAR1自身地址寄存器1在参考手册第508~509页有定义


I2C_Cmd(I2C1,ENABLE)
- 含义:将I2C_CR1(控制寄存器1)的第0位PE(I2C模块使能)设置为1(启用I2C模块:根据SMBus位的设置,相应的I/O口需配置为复用功能)
- I2C_Cmd函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_i2c.c(外设固体库文件)- - - ✿ 固体库文件
I2C_CR1(控制寄存器1)
- I2C_CR1控制寄存器1在参考手册第505~507页有定义


10.2 I2C_SAND_BUFFER(u8 SlaveAddr,u8 WriteAddr,u8* pBuffer,u16 NumByteToWrite)
- 含义:I2C发送数据串(器件地址,寄存器内部地址,数据串,数量)
★※【注意事项】该函数中EV事件的触发顺序(此触发顺序较参考手册略有不同,故在此做出解释)





I2C_GenerateSTART(I2C1,ENABLE)
- 含义:将I2C_CR1(控制寄存器1)的第8位START(起始条件产生)设置为1(在主模式下:重复产生起始条件;在从模式下:当总线空闲时,产生起始条件)
- I2C_GenerateSTART函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_i2c.c(外设固体库文件)- - - ✿ 固体库文件
I2C_CR1(控制寄存器1)- - -【因涉及代码中主模式和从模式,所以在此做出解释】
对下方手册内容的解释【豆包】- - - 主模式和从模式



- I2C_CR1控制寄存器1在参考手册第505~507页有定义


while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT))
- 含义:判断已指定的事件I2C_EVENT_MASTER_MODE_SELECT:EV5(SR1寄存器的第0位SB【起始位(主模式);值:0(未发送起始条件),1(起始条件已发送)】和SR2寄存器的第0位MSL【主从模式;值:0(从模式),1(主模式)】和SR2寄存器的第1位BUSY【总线忙;值:0(在总线上无数据通讯),1(在总线上正在进行数据通讯)】)是否发生(若发生,则三个位都为1),若发生,则退出循环
- 第二个参数I2C_EVENT_MASTER_MODE_SELECT的值为0x00030001【0000 0000 0000 0011 0000 0000 0000 0001】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_i2c.h(外设固体库头文件)- - - ✿ 固体库文件
- I2C_CheckEvent函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_i2c.c(外设固体库文件)- - - ✿ 固体库文件
I2C_SR1(状态寄存器1)
- I2C_SR1状态寄存器1在参考手册第510~512页有定义





I2C_SR2(状态寄存器2)
- I2C_SR2状态寄存器2在参考手册第512~513页有定义


SR1和SR2区别和各自位详解




代码为什么要把两个寄存器拼起来


为什么要设置三个位(SB/BUSY/MSL),才能表示清除EV5


I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Transmitter)
- 含义:将数据寄存器I2C_DR的第0~7位DR[7:0]写入(器件地址(SlaveAddr,高7位)+I2C_Direction_Transmitter(写操作,低1位,值为0))
- 第二个参数SlaveAddr为器件地址,来源于上一级函数I2C_SAND_BUFFER的第一个参数
- 第三个参数I2C_Direction_Transmitter,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_i2c.h(外设固体库头文件)- - - ✿ 固体库文件
- I2C_Send7bitAddress函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_i2c.c(外设固体库文件)- - - ✿ 固体库文件
I2C_DR(数据寄存器)
- I2C_DR数据寄存器在参考手册第509页有定义

I2C_SendData(I2C1,WriteAddr)
- 含义:将寄存器内部地址写入I2C_DR(数据寄存器)中
- 第二个参数WriteAddr,来源于上一层函数的第二个参数WriteAddr,表明寄存器内部地址
- I2C_SendData函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_i2c.c(外设固体库文件)- - - ✿ 固体库文件
I2C_DR(数据寄存器)
- I2C_DR数据寄存器在参考手册第509页有定义

I2C_GenerateSTOP(I2C1,ENABLE)
- 含义:将I2C_CR1(控制寄存器1)的第9位STOP(停止条件产生)设置为1(在主模式下:在当前字节传输或在当前起始条件发出后产生停止条件;在从模式下:在当前字节传输或释放SCL和SDA线)
- I2C_GenerateSTOP函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_i2c.c(外设固体库文件)- - - ✿ 固体库文件
I2C_CR1(控制寄存器1)- - -【因涉及代码中主模式和从模式,所以在此做出解释】
对下方手册内容的解释【豆包】- - - 主模式和从模式



- I2C_CR1控制寄存器1在参考手册第505~507页有定义


10.3 I2C_READ_BUFFER(u8 SlaveAddr,u8 readAddr,u8* pBuffer,u16 NumByteToRead)
- 含义:I2c读取数据串(器件地址,寄存器内部地址,数据串,数量)
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY))
- 含义:根据I2C_FLAG_BUSY的值,首先将该值与SR2寄存器进行与运算,判断SR2寄存器的位1:总线忙标志。0=总线空闲无通信,1=总线正在被占用通信中)是否设置为1,如果设置为1,那么为true,则继续循环;否则为false,退出循环
- 第二个参数I2C_FLAG_BUSY值为0x00020000【0000 0000 0000 0010 0000 0000 0000 0000】,出自Lib文件夹下的inc文件夹下的外设相关的stm32f10x_i2c.h(外设固体库头文件)- - - ✿ 固体库文件
- I2C_GetFlagStatus函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_i2c.c(外设固体库文件)- - - ✿ 固体库文件
I2C_AcknowledgeConfig(I2C1,DISABLE)
- 含义:将I2C_CR1(控制寄存器1)的第10位ACK(应答使能)设置为0(无应答返回)
- I2C_AcknowledgeConfig函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_i2c.c(外设固体库文件)- - - ✿ 固体库文件
I2C_CR1(控制寄存器1)- - -【因涉及代码中主模式和从模式,所以在此做出解释】
对下方手册内容的解释【豆包】- - - 主模式和从模式



- I2C_CR1控制寄存器1在参考手册第505~507页有定义


*pBuffer = I2C_ReceiveData(I2C1)
- 含义:读取I2C1外设数据寄存器DR中的接收字节,将该字节写入指针pBuffer指向的内存位置
- I2C_ReceiveData函数,出自Lib文件夹下的src文件夹下的外设相关的stm32f10x_i2c.c(外设固体库文件)- - - ✿ 固体库文件
I2C_DR(数据寄存器)
- I2C_DR数据寄存器在参考手册第509页有定义

十一、温度值OLED屏显示程序
main.c文件代码文件:
oled0561.c文件代码文件:
oled0561.h文件代码文件:
ASCII_8x16.h文件代码文件:
11.1 OLED0561_ADD、COM、DAT的值解释(oled0561.h中定义的)
在SH1106的手册上是这么定义的




解释1






11.2 OLED0561_Init (void)
- 含义:OLED屏开显示初始化
- OLED0561_Init (void)函数,出自oled0561.c文件
OLED_DISPLAY_OFF()
- 含义:OLED屏关显示
- 该函数,出自oled0561.c文件
buf数组的解释(L = 0,H = 1)
0xae(关显示)
- 含义: 这是关闭 OLED 面板显示的命令。当 D=0 时,显示关闭。
- 设置原因: 在进行一系列初始化配置时,通常先关闭显示,以避免在配置过程中屏幕出现闪烁或显示异常内容。配置完成后再统一开启。

0x8d,0x10(关显示时:关闭电荷泵(断电))



0xaf(开显示)
- 含义: 这是开启 OLED 面板显示的命令。当 D=1 时,显示开启。
- 设置原因: 在所有初始化配置完成后,发送此命令正式点亮屏幕,开始显示内容。

I2C_SAND_BUFFER(OLED0561_ADD,COM,buf,3)
- 含义:将数组的3个数据发送给0x78的器件地址
- 该函数本身含义:I2C发送数据串(器件地址,寄存器内部地址,数据串,数量),具体可参考上文中“10.2 I2C_SAND_BUFFER(u8 SlaveAddr,u8 WriteAddr,u8* pBuffer,u16 NumByteToWrite)”这一部分
OLED_DISPLAY_CLEAR()
- 含义:清屏操作
- 该函数,出自oled0561.c文件
0xB0(设置页地址)
- 含义: 将页地址设置为 0 (命令范围是
0xB0到0xB7,对应页 0-7)。
- 设置原因: SH1106 将 64 行分为 8 个页(Page),每页 8 行。此命令将操作的起始页设置为第 0 页(最顶部的 8 行)。

0x00,0x10(设置列地址的低/高四位)
- 含义: 这两个命令共同设置显示 RAM 的起始列地址。
0x00设置低 4 位为 0,0x10设置高 4 位为 0。组合起来,起始列地址为0x00。
- 设置原因: 将显示的起始位置设置为最左边的第 0 列,这是最常用的显示模式。

- A7/6/5/4是代表高四位,A3/2/1/0是代表低四位



OLED_DISPLAY_ON()
- 含义:OLED屏初始值设置并开显示
- 该函数,出自oled0561.c文件
buf数组的解释(L = 0,H = 1)- - - 【命令也可以改,也可以不改,在实际开发中,他们都是通用的!一般的方法就是直接问豆包他,让他给我生成一个初始化程序】
0xae(关显示)
- 含义: 这是关闭 OLED 面板显示的命令。当 D=0 时,显示关闭。
- 设置原因: 在进行一系列初始化配置时,通常先关闭显示,以避免在配置过程中屏幕出现闪烁或显示异常内容。配置完成后再统一开启。

0x00,0x10(设置列地址的低/高四位)
- 含义: 这两个命令共同设置显示 RAM 的起始列地址。
0x00设置低 4 位为 0,0x10设置高 4 位为 0。组合起来,起始列地址为0x00。
- 设置原因: 将显示的起始位置设置为最左边的第 0 列,这是最常用的显示模式。

- A7/6/5/4是代表高四位,A3/2/1/0是代表低四位



0xd5,0x80(设置显示时钟分频/振荡器频率)
- 含义: 这是一个双字节命令。
0xd5是命令本身,0x80是参数。 - 参数
0x80(二进制1000 0000) 的高 4 位1000设置振荡器频率为 POR (Power-On Reset) 默认值。 - 低 4 位
0000设置分频比为 1 (分频比 = A[3:0] + 1)。
- 设置原因: 配置屏幕的刷新时钟。使用默认频率和 1 分频是一个标准且稳定的设置,能确保屏幕正常刷新。





0xa8,0x3f(设置复用率)
- 含义:
0xa8是命令,0x3f(十进制 63) 是参数。这表示设置复用率为 1/64。
- 设置原因: 你的 OLED 屏幕是 128x64 分辨率,共有 64 个 COM 行。此命令告诉驱动芯片需要驱动 64 行,以匹配屏幕的物理特性。




0xd3,0x00(设置显示偏移)
- 含义:
0xd3是命令,0x00是参数。这表示显示起始行映射到 COM0,即 无垂直偏移。
- 设置原因: 确保屏幕显示的内容从最顶部的物理行开始,没有向上或向下移动。



- 这里有存疑:偏移值不知道是指谁移到哪里去?
0xB0(设置页地址)
- 含义: 将页地址设置为 0 (命令范围是
0xB0到0xB7,对应页 0-7)。
- 设置原因: SH1106 将 64 行分为 8 个页(Page),每页 8 行。此命令将操作的起始页设置为第 0 页(最顶部的 8 行)。

0x40(设置显示起始行)
- 含义: 设置显示 RAM 的哪一行作为屏幕的起始显示行。
0x40(二进制0100 0000) 的低 6 位为 0,表示从 RAM 的第 0 行开始显示。
- 设置原因: 与
0xd3命令类似,确保显示内容从 RAM 的顶部开始,通常与物理屏幕顶部对齐。

0x8d,0x14(设置电荷泵)
- 含义: 这是一个非常常见的命令,通常用于兼容 SSD1306 等其他驱动芯片。
0x8d是设置电荷泵的命令,0x14是开启内部电荷泵的参数。电荷泵的作用是为 OLED 像素提供所需的高驱动电压。
- 设置原因: 尽管 SH1106 手册使用
0xAD, 0x8B来开启内部 DC-DC,但许多模块为了兼容性,也支持0x8D, 0x14这种写法。其目的都是为了开启内部电压倍增电路,为屏幕发光提供能量。

解释1



解释2



0xa1(设置段重映射)
- 含义: 设置段(列)驱动的输出方向。
0xa1表示左右反向映射(列地址 127 映射到 SEG0)。
- 设置原因: 这通常与屏幕的物理安装方向有关。如果屏幕是倒置安装的,使用此命令可以将显示内容水平翻转过来,使其看起来是正的。

0xc8(设置 COM 输出扫描方向 )
- 含义: 设置 COM(行)的扫描方向。
0xc8表示从 COM[N-1] 扫描到 COM0(即从下到上扫描)。
- 设置原因: 与
0xa1类似,这用于垂直翻转显示内容。通常与0xa1配合使用,将整个画面旋转 180 度,以匹配屏幕的物理安装方向。

0xda,0x12(设置 COM 引脚硬件配置)
- 含义:
0xda是命令,0x12(二进制0001 0010) 是参数。 - 高 4 位
0001表示启用交替 COM 引脚配置 (Alternative)。 - 低 4 位
0010。
- 设置原因: 这是为了匹配 OLED 面板的物理引脚布局。
0x12是 128x64 分辨率屏幕的一个标准配置。







0x81,0xff(设置对比度控制)
- 含义:
0x81是对比度设置命令,0xff(十进制 255) 是对比度值。
- 设置原因: 设置屏幕的亮度/对比度。
0xff是最大值,表示将屏幕设置为最高亮度。


0xd9,0xf1(设置预充电周期)
- 含义:
0xd9是命令,0xf1(二进制1111 0001) 是参数。 - 高 4 位
1111设置预充电周期为 15 个 DCLK。 - 低 4 位
0001设置去放电周期为 1 个 DCLK。
- 设置原因: 调整像素点亮的时序,可以影响屏幕的亮度和显示效果,是一个高级优化参数。

0xdb,0x30(设置 VCOM 取消选择电平)
- 含义:
0xdb是命令,0x30(二进制0011 0000) 是参数。高 4 位0011用于设置 VCOMH 电压。
- 设置原因: VCOMH 是公共电极的电压,调整它可以影响屏幕的整体亮度和功耗。
0x30是一个常见的设置值。





0x20,0x00(设置内存寻址模式)
- 含义: 这同样是来自 SSD1306 等兼容芯片的命令。
0x20是设置寻址模式的命令,0x00表示设置为水平寻址模式 (Horizontal Addressing Mode)。
- 设置原因: 在水平寻址模式下,写完一个字节的数据后,列地址指针会自动加 1,写满一页后会自动跳到下一页的起始列。这极大地简化了连续写入一屏数据的编程工作。
0xa4(设置全局显示开/关)
- 含义:
0xa4表示恢复正常显示,即显示内容由显示 RAM 中的数据决定。
- 设置原因: 确保屏幕显示的是我们写入 RAM 的数据,而不是强制全亮或全灭。



0xa6(设置正常/反色显示)
- 含义:
0xa6表示正常显示。RAM 中为 1 的像素点亮,为 0 的像素点灭。
- 设置原因: 设置屏幕为正常的显示逻辑。

0xaf(开显示)
- 含义: 这是开启 OLED 面板显示的命令。当 D=1 时,显示开启。
- 设置原因: 在所有初始化配置完成后,发送此命令正式点亮屏幕,开始显示内容。

I2C_SAND_BUFFER(OLED0561_ADD,COM,buf,28)
- 含义:将数组的28个数据发送给0x78的器件地址
- 该函数本身含义:I2C发送数据串(器件地址,寄存器内部地址,数据串,数量),具体可参考上文中“10.2 I2C_SAND_BUFFER(u8 SlaveAddr,u8 WriteAddr,u8* pBuffer,u16 NumByteToWrite)”这一部分
那么为什么COM设为0x00,明明buf数组中有命令,也有数据呀?


11.3 OLED_DISPLAY_LIT (u8 x)
- 含义:OLED屏亮度设置(0~255)
- 该函数,出自oled0561.c文件
0x81,0xff(设置对比度控制)
- 含义:
0x81是对比度设置命令,0xff(十进制 255) 是对比度值。
- 设置原因: 设置屏幕的亮度/对比度。
0xff是最大值,表示将屏幕设置为最高亮度。


11.4 OLED_DISPLAY_8x16(u8 x, u8 y, u16 w)
- 含义:显示英文与数字8*16的ASCII码,取模大小为16*16,取模方式为“从左到右从上到下”“纵向8点下高位”
- 该函数,出自oled0561.c文件
y = y + 2(探究为什么要加上2)

I2C_SAND_BYTE(OLED0561_ADD, COM, (y / 16) + 0x10)以及I2C_SAND_BYTE(OLED0561_ADD, COM, y % 16) (探究为什么要除以或者取余16,16从哪来的)


步骤 | 核心操作 | 目的 |
坐标偏移 | y = y + 2 | 修正硬件物理显示起点偏移。 |
双页循环 | for (t = 0; t < 2; t++) | 16像素高度的字符需要占用 2 个页(Page)来显示。 |
定位行列 | 发送 0xb0+x、y/16+0x10、y%16 | 告诉屏幕:“接下来要往第 x 页、第 y 列开始写数据”。 |
写入数据 | 发送 DAT (0x40) 标志位及 8 字节数据 | 写入具体的像素点亮数据(字模)。 |
字库寻址 | (w * 16) + c - 512 | 精确计算字模数据在数组中的存储位置。 |
(w * 16) + c - 512 寻址公式详解






11.5 OLED_DISPLAY_8x16_BUFFER(u8 row,u8 *str)
- 含义:在 OLED 指定行连续显示一串 ASCII 字符 (8x16 字体)
- 该函数,出自oled0561.c文件
11.6 OLED_DISPLAY_16_16(u8 x, u8 y, u16 w)
- 含义:在 OLED 上显示一个 16x16 大小的汉字,取模大小为16*16,取模方式为“从左到右从上到下”“纵向8点下高位”
- 该函数,出自oled0561.c文件
下面这个文件是显示“成功了哦”的字库数组文件:
11.7 OLED_DISPLAY_16_16_UC(u16 w)
- 含义:用户自定义汉字库调用(显示 16x16 汉字)
- 该函数,出自oled0561.c文件
11.8 ASCII数据表来源
- 作者:清弦子
- 链接:https://tangly1024.com/%E5%8D%95%E7%89%87%E6%9C%BA%E6%8A%80%E6%9C%AF/stm32_01
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
.jpg?table=collection&id=aeb9995f-c21f-83d9-b9b7-07efa597272a&t=aeb9995f-c21f-83d9-b9b7-07efa597272a)