STM32驱动4位5引脚数码管显示驱动
假设main调用把当前main里的寄存器值存到栈(Stack)里(怕被破坏)。PC 指针跳到的地址。跑那几行 GPIO 代码。把栈里的值取回来。回到main继续跑。如果函数体很小(比如只有 2 行代码),“跳转和压栈”的时间可能比“执行代码”的时间还长!这就好比你去楼下便利店买瓶水(执行),结果花在穿鞋、等电梯(开销)上的时间比买水还多。编译器看到static函数代码很少,它会想:“反正也没别人用
一、电路原理图

二、代码实现原理
原理:利用人眼暂缓效果,依次点亮相应的LED段即可显示对应的数字
1、数据驱动(Data-Driven)而非逻辑驱动
观察、抽象、落地(查表法,把硬件映射成一张静态的表,代码只需要遍历这张表)。
利用结构体数组来定义使用到的端口及引脚号。

利用结构体数组来定义对应的阴极和阳极。

2. “三态”思维(Tri-state Logic)
普通编程只有 0 (False) 和 1 (True)。但在嵌入式硬件中,还有第三种状态:高阻态(High-Z),也就是“断开”。
-
核心逻辑:Charlieplexing 的精髓在于,当我要点亮 Pin1->Pin2 的灯时,Pin3、4、5 必须“消失”(变成高阻态)。如果不消失,电流就会乱窜,导致不该亮的灯微亮(鬼影)。
-
原子操作:我设计
Light_Single_Segment函数时,严格遵循了 “全灭(复位) -> 开阳极 -> 开阴极 -> 延时 -> 全灭” 的原子流程。
3. 分层架构(Layered Architecture)
我把代码分成了三层,互不干扰:
-
硬件抽象层 (HAL/GPIO):
Set_Pin_Output,Reset_All_Pins。只管电气特性,不管显示什么。 -
驱动层 (Driver):
Light_Single_Segment,Charlieplex_Refresh。只管怎么把数字变成亮灯信号,不管硬件是 STM32 还是 Arduino。 -
应用层 (App/Main):
main函数。只管我要显示 "188",不管底层怎么实现。

Static const的作用
在 C 语言的 .c 文件中,针对全局变量和函数使用 static 关键字,核心作用是控制 “可见性”(Scope/Visibility) 和 “链接属性”(Linkage)。
简单来说,就是区分 “私有(Private)” 和 “公有(Public)”。
1. 为什么要加 static?(私有化)
当你在函数或全局变量前加上 static,意味着:这个东西只能在当前 .c 文件内部被看见和使用,其他文件(比如 main.c)完全不知道它的存在。
代码中的例子:
-
static const CharliePin_t CP_PINS[...]-
作用: 这是定义引脚硬件连接的数组。
-
好处: 防止外部修改。你肯定不希望
main.c里某行代码不小心把CP_PINS[0]的引脚号改了,导致驱动崩溃。加了static,外部代码连访问它的权限都没有,绝对安全。
-
-
static void Set_Pin_Output(...)-
作用: 这是一个底层的“脏活累活”函数,只负责设置寄存器。
-
好处: 隐藏实现细节。对于使用这个驱动的人(比如写
main.c的人),他只需要调用Charlieplex_ShowNumber(),不需要知道底层还要把引脚设为推挽输出。把这个函数藏起来,可以让库的接口更干净。
-
-
防止命名冲突(最重要的原因):
-
如果你的项目里还有一个
lcd.c,里面也写了一个函数叫void Reset_All_Pins(void)。 -
如果都不加
static:编译器在链接时会报错:“重复定义(Multiple Definition)”,因为它不知道该调用哪一个。 -
如果都加了
static:这两个函数互不干扰,各自只在自己的文件里生效。
-
2. 为什么要不加 static?(公有化/API)
不加 static 的函数,默认是 “全局可见” 的。只要在 .h 文件里声明了,其他任何文件引用了头文件就能调用它。
代码中的例子:
-
void Charlieplex_Init(void) -
void Charlieplex_ShowNumber(uint16_t number)
原因: 这些是给外部调用的 接口(API)。 如果把 Charlieplex_Init 也加上 static,那 main.c 就没办法初始化这个屏幕了,这个驱动也就废了。
RAM 和 Flash
你需要区分两个概念:静态存储区(RAM) 和 代码/只读数据区(Flash)。
1. static 修饰函数(如 static void Set_Pin_Output)
-
占用哪里? 占用 Flash(ROM),也就是存代码的地方。
-
会多占吗? 不会。无论你加不加
static,这个函数的代码指令都要存在 Flash 里。 -
区别: 加了
static,编译器知道这个函数“私有”,如果不被调用,编译器甚至可以直接把它优化删掉(Dead Code Elimination),反而省了空间。如果不加static,编译器不敢删,因为它怕别的文件会调用它。
2. static 修饰常量数组(如 static const uint8_t NUM_FONT[])
-
关键点: 注意你代码里有一个
const。 -
占用哪里? Flash(ROM)。
-
解释: 在 STM32 这种 ARM 架构上,
const变量直接烧录在 Flash 里。程序运行时,CPU 直接从 Flash 读取数据,完全不占用宝贵的 RAM。 -
如果不加 static? 也没事,还是在 Flash。但加了
static可以防止命名污染。
3. static 修饰变量(如 static int counter; —— 你代码里没这个,但假设有)
-
占用哪里? RAM(静态存储区 .bss/.data)。
-
会浪费吗? 这才是你担心的点。是的,这种变量会永久占用 RAM,直到断电。
-
但是: 全局变量(不加 static)一样会永久占用 RAM。加了
static只是限制了它的访问范围,并没有增加它的物理体积。
总结: 你的代码中主要是 static 函数和 static const 数组,它们完全不消耗 RAM,非常安全且高效。
第二部分:深度解析“编译器自动内联 (Auto-Inlining)”
这是现代编译器(GCC, ARMCC, Clang)最迷人的特性之一。
1. 什么是普通函数调用?(有开销)
假设 main 调用 Set_Pin_Output,CPU 实际上在做这些事:
-
保护现场(压栈): 把当前
main里的寄存器值存到栈(Stack)里(怕被破坏)。 -
跳转(Jump): PC 指针跳到
Set_Pin_Output的地址。 -
执行: 跑那几行 GPIO 代码。
-
恢复现场(出栈): 把栈里的值取回来。
-
跳回(Return): 回到
main继续跑。
成本: 如果函数体很小(比如只有 2 行代码),“跳转和压栈”的时间可能比“执行代码”的时间还长!这就好比你去楼下便利店买瓶水(执行),结果花在穿鞋、等电梯(开销)上的时间比买水还多。
2. 什么是内联(Inlining)?(零开销)
编译器看到 static 函数代码很少,它会想:“反正也没别人用,我直接把这几行代码复制粘贴到调用的地方吧。”
效果:
-
没有压栈。
-
没有跳转。
-
代码直接顺序执行下来。
这就是为什么“比喻成搬代码”: 原本的代码逻辑是 A -> 跳到B -> 做B -> 回到A。 内联后变成了 A -> 做B -> A。
3. 为什么 static 极其关键?
这是编译器的心理博弈:
2. static 修饰常量数组(如 static const uint8_t NUM_FONT[])
3. static 修饰变量(如 static int counter; —— 你代码里没这个,但假设有)
总结: 你的代码中主要是 static 函数和 static const 数组,它们完全不消耗 RAM,非常安全且高效。
关键实现点:利用const修饰的常量放入rom,直接利用查表法实现各项功能逻辑实现。
-
情况 A:不加
static(全局函数) 编译器想:“这个函数Light_Single_Segment虽然很短,但也许隔壁main.c或者interrupt.c会通过函数名来调用它。所以我必须保留这个函数的实体,不能随便把它弄消失,也不敢随便把它揉碎了塞进别的函数里。” -> 优化受限。 -
情况 B:加了
static(私有函数) 编译器想:“哈!这个函数是static的,意味着只有当前这个文件能看见它。我扫描了一遍当前文件,发现只有Charlieplex_Refresh调用了它一次。那我根本不需要生成这个函数的实体,直接把它的肉(代码逻辑)挖出来,填到调用者肚子里去!” -> 极致优化。你需要区分两个概念:静态存储区(RAM) 和 代码/只读数据区(Flash)。
1.
static修饰函数(如static void Set_Pin_Output) -
占用哪里? 占用 Flash(ROM),也就是存代码的地方。
-
会多占吗? 不会。无论你加不加
static,这个函数的代码指令都要存在 Flash 里。 -
区别: 加了
static,编译器知道这个函数“私有”,如果不被调用,编译器甚至可以直接把它优化删掉(Dead Code Elimination),反而省了空间。如果不加static,编译器不敢删,因为它怕别的文件会调用它。 -
关键点: 注意你代码里有一个
const。 -
占用哪里? Flash(ROM)。
-
解释: 在 STM32 这种 ARM 架构上,
const变量直接烧录在 Flash 里。程序运行时,CPU 直接从 Flash 读取数据,完全不占用宝贵的 RAM。 -
如果不加 static? 也没事,还是在 Flash。但加了
static可以防止命名污染。 -
占用哪里? RAM(静态存储区 .bss/.data)。
-
会浪费吗? 这才是你担心的点。是的,这种变量会永久占用 RAM,直到断电。
-
但是: 全局变量(不加 static)一样会永久占用 RAM。加了
static只是限制了它的访问范围,并没有增加它的物理体积。
更多推荐


所有评论(0)