最近沉迷抖音,无意看到点灯大师的点灯操作,发现了一种有趣的LED控制技术——查理复用(Charlieplexing)。通过这种技术,可以用n个GPIO引脚控制n×(n-1)个LED。本文将详细记录我的学习过程、实现方案以及遇到的问题和解决方法。
查理复用原理
在本文中我们将利用STM32的GPIO引脚三态特性(输出高、输出低、高阻输入)来控制多个LED的技术。其核心思想是:
- 每个LED连接在两个不同的GPIO引脚之间
- 通过控制引脚的电平状态决定LED是否导通
- 任何时候只允许一个电流路径存在
硬件设计
电路原理图
以下电路连接方式以及真值表:
连接方式
Num | GPIO |
---|---|
LED1 | PA0(+) → PA1(-) |
LED2 | PA1(+) → PA0(-) |
LED3 | PA0(+) → PA2(-) |
LED4 | PA2(+) → PA0(-) |
LED5 | PA1(+) → PA2(-) |
LED6 | PA2(+) → PA1(-) |
真值表
驱动代码部分
已模块封装,便于扩展更多LED。代码需在最后一行留空,否则Keil编译会有警告。
CharlieLED.h
#ifndef __CHARLIELED_H
#define __CHARLIELED_H
#include "stm32f10x.h" // Device header
typedef enum {
LED_1, // PA0 + PA1
LED_2, // PA1 + PA0
LED_3, // PA0 + PA2
LED_4, // PA2 + PA0
LED_5, // PA1 + PA2
LED_6 // PA2 + PA1
} CharlieLED;
void Set_CharlieLED(CharlieLED led, FunctionalState state);
#endif
CharlieLED.c
#include "CharlieLED.h"
// FunctionalState 是一个枚举类型,用于表示功能状态的开启或关闭
void Set_CharlieLED(CharlieLED led, FunctionalState state)
{
static const struct
{
uint16_t anode_pin;
uint16_t cathode_pin;
} led_map[6] = {
{GPIO_Pin_0, GPIO_Pin_1}, // LED1
{GPIO_Pin_1, GPIO_Pin_0}, // LED2
{GPIO_Pin_0, GPIO_Pin_2}, // LED3
{GPIO_Pin_2, GPIO_Pin_0}, // LED4
{GPIO_Pin_1, GPIO_Pin_2}, // LED5
{GPIO_Pin_2, GPIO_Pin_1} // LED6
};
// 初始化 1. 先全部设为高阻态(安全隔离)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 默认保持彻底高阻态
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 判断是否点亮
if (state == ENABLE)
{
// 2. 配置阳极(推挽输出高)
GPIO_SetBits(GPIOA, led_map[led].anode_pin); // 高电平
GPIO_InitStructure.GPIO_Pin = led_map[led].anode_pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置阴极(推挽输出低)
GPIO_ResetBits(GPIOA, led_map[led].cathode_pin); // 低电平
GPIO_InitStructure.GPIO_Pin = led_map[led].cathode_pin;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
}
学习笔记
1️⃣. 为何不使用开漏输出而是推挽输出?
推挽输出首选是因为它内置了完整的驱动能力,高电平时通过PMOS强上拉,低电平时通过NMOS强下拉,无需外接元件。而开漏输出在高电平时依赖外部上拉电阻,会导致驱动能力不足、电路复杂化,且在多LED系统中可能引发电流路径冲突。
在驱动实现中,推挽输出相比开漏输出能直接提供高/低电平驱动能力,无需外接上拉电阻,可双向电流快速切换LED状态,降低功耗并提高响应速度,同时简化电路设计(省去上拉网络)。直白来讲,推挽输出对IO有绝对的控制权。
2️⃣. 为什么必须用高阻态?
在查理复用中,高阻态是确保LED精确控制的关键。高阻态可以彻底断开引脚,防止非目标LED因漏电微亮,通过GPIO_Mode_IN_FLOATING
实现真正电气隔离,确保每次只有一条确定的电流路径。
3️⃣. 切换机制是怎样的?
首先将所有引脚默认设为高阻态(电气隔离),然后配置目标阳极为推挽输出高,最后设置阴极为推挽输出低。
4️⃣. 如何进行扩展,软件应该怎样实现?
可以知道该原理为n个GPIO可控制n×(n-1)个LED,沿用二维数组存储引脚组合。然后需先建立引脚映射表,然后加以扩展,如动态扫描(定时器中断控制)、PWM 调光(呼吸灯、亮度渐变)、矩阵键盘(输入检测)以及多路 ADC 模拟信号采集等。查理复用的核心思想是 “分时复用 + 三态控制”,只要灵活运用,可以用极少的 GPIO 实现复杂的交互功能!
5️⃣. 多路 ADC 模拟信号采集具体方式?
利用GPIO的三态特性(高电平、低电平、高阻态)分时切换多个模拟开关,将不同传感器轮流连接到同一路ADC上。
例如,3个GPIO通过组合控制可管理6个传感器——当GPIO1输出高、GPIO2输出低、GPIO3设为高阻时,选通传感器1;切换其他组合即可选通其他传感器。每次仅一个传感器被连接,ADC分时读取各信号。其关键点在于:
🟢通过GPIO状态组合动态控制模拟开关;
🔵严格分时避免信号冲突;
🟣切换后需短暂延时确保信号稳定。虽以最少引脚实现多路采集,但需注意抗干扰和时序控制。
一句话总结:通过动态切换GPIO输入/输出状态或控制模拟开关,实现多路模拟信号的分时采集。
原来还能这样玩 GPIO!用 STM32 的三态特性实现查理复用,3 个引脚就能点亮 6 个 LED,代码还做了模块封装,学到了推挽输出和高阻态的关键应用~这种分时复用的思路太妙了,感觉还能拓展到传感器采集之类的场景,收藏起来慢慢研究!
哇 很有用 期待下一次的内容