12
返回列表 发新帖我要提问本帖赏金: 10.00元(功能说明)

[资料干货] 188 数码管扫描频率超 60Hz,为什么无关灯管还会随机乱闪?

[复制链接]
1482|22
datouyuan 发表于 2026-8-27 17:05 | 显示全部楼层
我之前只在 51 以及更低端的 MCU 上实际使用过查理复用方案,对于楼主所用的芯片并不熟悉,这份示范代码就选用大家最常用的 STM32 来写。
刚好楼主所有数码管相关引脚都在同一个 GPIO 端口上,完全可以采用我16楼描述的优化方案,规避分步切换 GPIO 三态带来的鬼影乱闪问题。
5 线查理复用理论最大可实现 20 个显示状态,本例示范使用 19 态(0~18)
  1. #define SEG1_PIN GPIO_PIN_4
  2. #define SEG2_PIN GPIO_PIN_5
  3. #define SEG3_PIN GPIO_PIN_6
  4. #define SEG4_PIN GPIO_PIN_7
  5. #define SEG5_PIN GPIO_PIN_8
  6. #define DISP_PORT GPIOA
  7. #define DISP_MASK (SEG1_PIN | SEG2_PIN | SEG3_PIN | SEG4_PIN | SEG5_PIN)
  8. /**
  9. * [url=/u/brief]@brief[/url] 原子刷新:仅将标记为输出的引脚从输入切换为推挽
  10. * [url=/u/NOTE]@NOTE[/url] 前置条件:全部引脚已经处于浮空输入状态,ODR已提前预加载完成
  11. * @param out_dir : bit=1 该引脚设推挽输出;bit=0 保持高阻输入
  12. * [url=/u/Attention]@Attention[/url] 存在极低概率被休眠/初始化流程抢占,内部自带临界保护
  13. */
  14. void disp_refresh(uint32_t out_dir)
  15. {
  16.         uint32_t dir = out_dir & DISP_MASK;
  17.         if (dir == 0)
  18.                 return;
  19.         GPIO_InitTypeDef gpio_pp = {
  20.                 .Pin = dir,
  21.                 .Mode = GPIO_MODE_OUTPUT_PP,
  22.                 .Pull = GPIO_NOPULL,
  23.                 .Speed = GPIO_SPEED_FREQ_LOW};
  24.         uint32_t primask = __get_PRIMASK();
  25.         __disable_irq();
  26.         HAL_GPIO_Init(DISP_PORT, &gpio_pp);
  27.         __set_PRIMASK(primask);
  28. }
  29. /**
  30. * @brief 预设置引脚电平(兼容浮空输入状态下直接写入ODR锁存,底层逻辑同HAL_GPIO_WritePin)
  31. * @param mask : 需要操作的引脚掩码
  32. * @param state: GPIO_PIN_SET / GPIO_PIN_RESET
  33. * @note 建议在disp_blank之后(IO浮空输入)调用,仅预存ODR,不会直接驱动引脚输出
  34. */
  35. void disp_set_pin(uint32_t mask, GPIO_PinState state)
  36. {
  37.         mask &= DISP_MASK;
  38.         if (mask == 0)
  39.                 return;
  40.         if (state == GPIO_PIN_SET)
  41.                 DISP_PORT->BSRR = mask;
  42.         else
  43.                 DISP_PORT->BSRR = (uint32_t)mask << 16U;
  44. }
  45. static inline void disp_set_high(uint32_t mask)
  46. {
  47.         disp_set_pin(mask, GPIO_PIN_SET);
  48. }
  49. static inline void disp_set_low(uint32_t mask)
  50. {
  51.         disp_set_pin(mask, GPIO_PIN_RESET);
  52. }
  53. /**
  54. * @brief 消隐Blank
  55. * @note 全部切高阻输入,同时将DISP_MASK内ODR统一清零作为基准
  56. *       基准选全0,适合本扫描周期内大部分引脚为0、仅少量引脚需要置1的场景,后续仅需置1,无需清零;
  57. *       若大部分引脚为1、仅少量引脚需要置0,则改用基准全1,后续仅需置0
  58. * @attention 5线查理复用理论最多支持20态,本例示范使用19态(0~18)
  59. */
  60. void disp_blank(void)
  61. {
  62.         GPIO_InitTypeDef gpio_conf = {0};
  63.         gpio_conf.Pin = DISP_MASK;
  64.         gpio_conf.Mode = GPIO_MODE_INPUT;
  65.         gpio_conf.Pull = GPIO_NOPULL;
  66.         uint32_t primask = __get_PRIMASK();
  67.         __disable_irq();
  68.         HAL_GPIO_Init(DISP_PORT, &gpio_conf);
  69.         // ODR基准:全部清零
  70.         DISP_PORT->BSRR = (uint32_t)DISP_MASK << 16U;
  71.         __set_PRIMASK(primask);
  72. }
  73. /**
  74. * @brief 休眠配置:全部推挽输出固定电平
  75. * @note 仅深度休眠场景有意义:高阻悬空引脚会引入额外漏电流,设置确定电平可降低深度休眠功耗;
  76. *       正常运行、浅休眠时该漏电流影响很小,可不处理。
  77. */
  78. void disp_sleep(void)
  79. {
  80.         GPIO_InitTypeDef gpio_conf = {0};
  81.         gpio_conf.Pin = DISP_MASK;
  82.         gpio_conf.Mode = GPIO_MODE_OUTPUT_PP;
  83.         gpio_conf.Pull = GPIO_NOPULL;
  84.         gpio_conf.Speed = GPIO_SPEED_FREQ_LOW;
  85.         uint32_t primask = __get_PRIMASK();
  86.         __disable_irq();
  87.         DISP_PORT->BSRR = DISP_MASK;
  88.         HAL_GPIO_Init(DISP_PORT, &gpio_conf);
  89.         __set_PRIMASK(primask);
  90. }
  91. void disp_init(void)
  92. {
  93.         __HAL_RCC_GPIOA_CLK_ENABLE();
  94.         disp_blank();
  95.         disp_sleep();
  96. }
  97. void PS_ScanDisp(void)
  98. {
  99.         static uint8_t seg = 0;
  100.         disp_blank();
  101.         if (++seg > 18) // 0~18,合计19态;5线查理复用理论上限20态
  102.                 seg = 0;
  103.         uint32_t dir_val = 0;
  104.         uint32_t port_val = 0;
  105.         switch (seg)
  106.         {
  107.         case 0:
  108.                 // dig1为业务自定义位域变量,使用者根据实际显示定义
  109.                 if (dig1.bits.B)
  110.                 {
  111.                         port_val = SEG3_PIN;
  112.                         dir_val = SEG3_PIN | SEG4_PIN;
  113.                 }
  114.                 break;
  115.         // 其余18个case自行扩充
  116.         default:
  117.                 break;
  118.         }
  119.         disp_set_high(port_val);
  120.         disp_refresh(dir_val);
  121. }
 楼主| liao6 发表于 2026-8-30 18:33 | 显示全部楼层
datouyuan 发表于 2026-8-27 17:05
我之前只在 51 以及更低端的 MCU 上实际使用过查理复用方案,对于楼主所用的芯片并不熟悉,这份示范代码就 ...

好的,非常感谢。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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