[消费电子] 查理复用

[复制链接]
4|0
等我变优秀 发表于 2026-4-28 22:11 | 显示全部楼层 |阅读模式
   最近手头上有个项目,需要驱动两个数码管再加几个状态图标,但 MCU 的 GPIO 实在紧张,抠抠搜搜最后决定用查理复用(Charlieplexing) 来搞,只用 5 个 GPIO 就搞定了。今天在 e2s IDE 下把代码封装好了,实测稳定,来和大家分享一下。
    5225069f0bf57d147a.jpg

一、函数封装介绍
     先说说原理,查理复用这玩意儿确实省 GPIO,n个引脚能驱动n*(n-1)个 LED。我这次用了 5 个 GPIO(就叫它们 A、B、C、D、E 吧,对应 RA4E2 的 P000-P004,大家可根据板子改),算下来能驱动 20 个 LED。分配一下:两个数码管各 8 段(a-g + 小数点)占 16 个,剩下 4 个当图标(蓝牙、电池、闹钟等),刚好够。


封装了两个核心函数:

  • Charlieplex_Init():负责将 5 个 GPIO 配置为推挽输出模式,同时开启对应端口时钟。
  • Charlieplex_Display(uint8_t digit1, uint8_t digit0, uint8_t icon_mask):入参为两个数码管的数字(0-9)和图标掩码(4 位对应 4 个图标)。内部通过查表法控制引脚输出高低电平或高阻态,避免串扰。


二、使用方法
三步搞定,非常简单:

  • 包含头文件:引入Charlieplex.h(内含函数声明和引脚宏定义)。
    1. #ifndef CHARLIEPLEX_H
    2. #define CHARLIEPLEX_H

    3. #include "hal_data.h"

    4. /* ================= 引脚配置(请根据实际板子修改) ================= */
    5. #define CHARLIEPLEX_PIN_A  BSP_IO_PORT_00_PIN_00  // 引脚A
    6. #define CHARLIEPLEX_PIN_B  BSP_IO_PORT_00_PIN_01  // 引脚B
    7. #define CHARLIEPLEX_PIN_C  BSP_IO_PORT_00_PIN_02  // 引脚C
    8. #define CHARLIEPLEX_PIN_D  BSP_IO_PORT_00_PIN_03  // 引脚D
    9. #define CHARLIEPLEX_PIN_E  BSP_IO_PORT_00_PIN_04  // 引脚E

    10. /* ================= 函数声明 ================= */
    11. void Charlieplex_Init(void);
    12. void Charlieplex_Display(uint8_t digit1, uint8_t digit0, uint8_t icon_mask);

    13. #endif

  • 初始化:在main函数开头调用Charlieplex_Init()配置 GPIO。
  • 调用显示:在主循环或定时器中断中调用Charlieplex_Display(),建议 1ms 刷新一次避免闪烁。

  1. #include "Charlieplex.h"

  2. /* ================= 内部变量与表 ================= */
  3. // 共阴数码管段码表(0-9,a-g+dp)
  4. static const uint8_t seg_table[] = {
  5.     0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F
  6. };

  7. // 引脚数组,方便循环操作
  8. static const bsp_io_port_pin_t pins[] = {
  9.     CHARLIEPLEX_PIN_A, CHARLIEPLEX_PIN_B,
  10.     CHARLIEPLEX_PIN_C, CHARLIEPLEX_PIN_D, CHARLIEPLEX_PIN_E
  11. };

  12. /* ================= 内部辅助函数 ================= */
  13. // 将所有引脚设为高阻态(输入模式)
  14. static void _Charlieplex_AllHighZ(void)
  15. {
  16.     for (uint8_t i = 0; i < 5; i++) {
  17.         R_PORT_PinCfgSet(pins[i], BSP_IO_CFG_INPUT);
  18.     }
  19. }

  20. // 驱动单个LED:anode_idx=阳极引脚索引(0-4),cathode_idx=阴极引脚索引(0-4)
  21. static void _Charlieplex_DriveLED(uint8_t anode_idx, uint8_t cathode_idx)
  22. {
  23.     _Charlieplex_AllHighZ();
  24.     // 阳极设为推挽输出高
  25.     R_PORT_PinCfgSet(pins[anode_idx], BSP_IO_CFG_OUTPUT_PUSH_PULL);
  26.     R_PORT_PinWrite(pins[anode_idx], BSP_IO_LEVEL_HIGH);
  27.     // 阴极设为推挽输出低
  28.     R_PORT_PinCfgSet(pins[cathode_idx], BSP_IO_CFG_OUTPUT_PUSH_PULL);
  29.     R_PORT_PinWrite(pins[cathode_idx], BSP_IO_LEVEL_LOW);
  30. }

  31. /* ================= 核心函数实现 ================= */

  32. /**
  33. * [url=/u/brief]@brief[/url] 查理复用初始化
  34. * 开启端口时钟,所有引脚初始化为高阻态
  35. */
  36. void Charlieplex_Init(void)
  37. {
  38.     // 开启P0端口时钟(若用其他端口请修改)
  39.     R_SYS_ModuleClockEnable(SYS_MODULE_PORT0);
  40.     // 所有引脚初始化为高阻
  41.     _Charlieplex_AllHighZ();
  42. }

  43. /**
  44. * @brief 查理复用显示函数
  45. * @param digit1: 十位数字(0-9),超过9则不显示
  46. * @param digit0: 个位数字(0-9),超过9则不显示
  47. * @param icon_mask: 图标掩码(bit0-bit3对应4个图标)
  48. */
  49. void Charlieplex_Display(uint8_t digit1, uint8_t digit0, uint8_t icon_mask)
  50. {
  51.     uint8_t seg;
  52.    
  53.     /* ---------------- 1. 显示十位数码管 ---------------- */
  54.     if (digit1 <= 9) {
  55.         seg = seg_table[digit1];
  56.         // 段映射:a=A阳B阴, b=A阳C阴, c=A阳D阴, d=A阳E阴, e=B阳A阴, f=B阳C阴, g=B阳D阴, dp=B阳E阴
  57.         if (seg & 0x01) { _Charlieplex_DriveLED(0, 1); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // a
  58.         if (seg & 0x02) { _Charlieplex_DriveLED(0, 2); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // b
  59.         if (seg & 0x04) { _Charlieplex_DriveLED(0, 3); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // c
  60.         if (seg & 0x08) { _Charlieplex_DriveLED(0, 4); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // d
  61.         if (seg & 0x10) { _Charlieplex_DriveLED(1, 0); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // e
  62.         if (seg & 0x20) { _Charlieplex_DriveLED(1, 2); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // f
  63.         if (seg & 0x40) { _Charlieplex_DriveLED(1, 3); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // g
  64.         if (seg & 0x80) { _Charlieplex_DriveLED(1, 4); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // dp
  65.     }
  66.    
  67.     /* ---------------- 2. 显示个位数码管 ---------------- */
  68.     if (digit0 <= 9) {
  69.         seg = seg_table[digit0];
  70.         // 段映射:a=C阳A阴, b=C阳B阴, c=C阳D阴, d=C阳E阴, e=D阳A阴, f=D阳B阴, g=D阳C阴, dp=D阳E阴
  71.         if (seg & 0x01) { _Charlieplex_DriveLED(2, 0); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // a
  72.         if (seg & 0x02) { _Charlieplex_DriveLED(2, 1); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // b
  73.         if (seg & 0x04) { _Charlieplex_DriveLED(2, 3); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // c
  74.         if (seg & 0x08) { _Charlieplex_DriveLED(2, 4); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // d
  75.         if (seg & 0x10) { _Charlieplex_DriveLED(3, 0); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // e
  76.         if (seg & 0x20) { _Charlieplex_DriveLED(3, 1); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // f
  77.         if (seg & 0x40) { _Charlieplex_DriveLED(3, 2); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // g
  78.         if (seg & 0x80) { _Charlieplex_DriveLED(3, 4); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // dp
  79.     }
  80.    
  81.     /* ---------------- 3. 显示图标 ---------------- */
  82.     // 图标映射:图标1=E阳A阴, 图标2=E阳B阴, 图标3=E阳C阴, 图标4=E阳D阴
  83.     if (icon_mask & 0x01) { _Charlieplex_DriveLED(4, 0); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // 图标1
  84.     if (icon_mask & 0x02) { _Charlieplex_DriveLED(4, 1); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // 图标2
  85.     if (icon_mask & 0x04) { _Charlieplex_DriveLED(4, 2); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // 图标3
  86.     if (icon_mask & 0x08) { _Charlieplex_DriveLED(4, 3); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); } // 图标4
  87.    
  88.     // 最后恢复高阻,避免串扰
  89.     _Charlieplex_AllHighZ();
  90. }


三、调用代码验证
直接上测试代码,我在主循环里让数码管从 00 跑到 99,图标依次点亮,大家可直接复制到 e2s 里试:
8669469f0bfde9a75b.png


  1. #include "hal_data.h"
  2. #include "Charlieplex.h"

  3. void main(void)
  4. {
  5.     /* 初始化系统时钟(根据实际项目配置) */
  6.     R_SYS_ClockSourceEnable(SYS_CLOCK_SOURCE_MAIN_OSC);
  7.     R_SYS_ClockConfig(SYS_CLOCK_TYPE_ICLK, 8, SYS_CLOCK_SOURCE_MAIN_OSC);
  8.    
  9.     /* 查理复用初始化 */
  10.     Charlieplex_Init();
  11.    
  12.     uint8_t num = 0;
  13.     uint8_t icon = 0x01;
  14.    
  15.     while (1)
  16.     {
  17.         /* 显示数字十位、个位,图标轮流点亮 */
  18.         Charlieplex_Display(num / 10, num % 10, icon);
  19.         
  20.         /* 延时切换数字和图标 */
  21.         R_BSP_SoftwareDelay(200, BSP_DELAY_UNITS_MILLISECONDS);
  22.         num = (num > 99) ? 0 : (num + 1);
  23.         icon = (icon > 0x08) ? 0x01 : (icon << 1);
  24.     }
  25. }


注:延时用了瑞萨 FSP 的R_BSP_SoftwareDelay,大家可根据自己的库替换,或用定时器中断刷新更稳定。


四、预期输出结果
烧录后你会看到:

  • 两个数码管从00开始,每秒加 1,循环到99;
  • 4 个图标(需硬件对应)从第一个开始,每隔 200ms 切换下一个,轮流点亮;
  • 刷新频率足够时,数码管无闪烁,图标显示稳定。

    因为手机拍摄出来的效果会有闪烁比较严重的现象,这里就没有放图。这是因为查理复用利用的是人眼的视觉暂留,但是对于手机这种设备来说是可以看到快速闪烁的。
五、移植方法
想移到自己的板子?主要改这几点:

  • 引脚定义:打开Charlieplex.h,修改CHARLIEPLEX_PIN_A等宏为你实际使用的端口和引脚(如 P105)。
  • 时钟使能:在Charlieplex_Init()中添加对应端口的时钟使能代码(如用 P1 口需调用R_SYS_ModuleClockEnable(SYS_MODULE_PORT1))。
  • 共阴 / 共阳切换:若数码管是共阳,在显示函数中将高低电平逻辑反转即可。


六、注意事项
踩过的坑分享给大家,避免重蹈覆辙:

  • 限流电阻:每个 LED 必须串 220Ω-1kΩ 的限流电阻,别直接接 GPIO!
  • 刷新频率:推荐用定时器中断(1ms-5ms 一次),主循环延时易被打断导致闪烁。
  • 高阻态设置:未使用的引脚务必设为输入模式(高阻),否则会有串扰(LED 微亮)。
  • 引脚顺序:硬件接线必须与软件定义的 A→B→C→D→E 顺序一致,否则显示乱码。
  • 驱动能力:若 LED 较多或较亮,建议用三极管扩流,瑞萨 GPIO 单引脚驱动能力一般为几 mA。

您需要登录后才可以回帖 登录 | 注册

本版积分规则

1

主题

14

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部
0