一、电路原理图

二、代码实现原理

原理:利用人眼暂缓效果,依次点亮相应的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 实际上在做这些事:

  1. 保护现场(压栈): 把当前 main 里的寄存器值存到栈(Stack)里(怕被破坏)。

  2. 跳转(Jump): PC 指针跳到 Set_Pin_Output 的地址。

  3. 执行: 跑那几行 GPIO 代码。

  4. 恢复现场(出栈): 把栈里的值取回来。

  5. 跳回(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 只是限制了它的访问范围,并没有增加它的物理体积。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐