1、基本介绍
一般数码管的位和段分别采用不同的IO进行控制,即每个位均采用不同的IO,段则共用IO,通过扫描切换位来显示。那么3位的数码管,最起码需要的IO数量=3(dig)+7(seg)。
在特殊情况下,如做小型化或IO资源比较紧张的产品时,IO的数量则显示得非常珍贵。在数码管显示时如何保证IO能够合理利用也非常重要,故市场上出现一种3位6引脚的数码管,仅需要6个IO即可实现其显示,如下图所示。

2、工作原理
每个厂家对3位6引脚的数码管针脚的定义可能会有所区别,但其使用的原理是一样的。在这里只选择其中的一种定义进行分析其工作原理。

如上图所示,单片机若要点亮DIG1的A段仅需要将2引脚设置为高电平,3引脚设置为低电平即可;若要点高DIG2的B段仅需要将3引脚设置为高电平,5引脚设置为低电平。 当然在画电路时记得在每个引脚添加限流电阻如220R/330R或其他,通过不同的限流电阻值可控制数码管的亮度。
按上述点亮,那此时出现3引脚又是低电平又是高电平情况,如何处理?其实原理非常简单,采用扫描方式点亮,由于人的眼睛视觉暂留,只要扫描的速度足够快,即可认为其一直在显示。
那么3位6引脚的数码管采用每段扫描,按1ms刷新1段的速率,那么3位8段(带DP)则需要扫描24次才能够刷新完毕。在刷新的过程中,其余未点亮时则将其设定为输入浮空状态,以保证其没电流输出。
3、程序编写
在这里以小华的HC32F072的MCU为例,对代码部分进行编写。
3.1IO初始化
在应用前初始化相应的IO接口是最基本的,其初化如下:
void LED_ShowInit(void)
{
stc_gpio_cfg_t stcGpioCfg;
///< 打开GPIO外设时钟门控
Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
///< 端口方向配置->输出(其它参数与以上(输入)配置参数一致)
stcGpioCfg.enDir = GpioDirOut;
///< 端口上下拉配置->下拉
stcGpioCfg.enPu = GpioPuEnable;
stcGpioCfg.enPd = GpioPdDisable;
// stcGpioCfg.enDrv = GpioDrvH;
///< GPIO IO LED端口初始化
Gpio_Init(LED1_DISP_Port, LED1_DISP_Pin, &stcGpioCfg);
Gpio_Init(LED2_DISP_Port, LED2_DISP_Pin, &stcGpioCfg);
Gpio_Init(LED3_DISP_Port, LED3_DISP_Pin, &stcGpioCfg);
Gpio_Init(LED4_DISP_Port, LED4_DISP_Pin, &stcGpioCfg);
Gpio_Init(LED5_DISP_Port, LED5_DISP_Pin, &stcGpioCfg);
Gpio_Init(LED6_DISP_Port, LED6_DISP_Pin, &stcGpioCfg);
}
3.2获取数码管的引脚
为方便获取各数码管的引脚编号和端口号,故做为通用函数方便后续调用。
en_gpio_pin_t LED_GetPin(uint8_t PinIndex)
{
en_gpio_pin_t PinValue;
switch(PinIndex)
{
case 1:
PinValue = LED1_DISP_Pin;
break;
case 2:
PinValue = LED2_DISP_Pin;
break;
case 3:
PinValue = LED3_DISP_Pin;
break;
case 4:
PinValue = LED4_DISP_Pin;
break;
case 5:
PinValue = LED5_DISP_Pin;
break;
case 6:
PinValue = LED6_DISP_Pin;
break;
default:
PinValue = LED1_DISP_Pin;
break;
}
return PinValue;
}
en_gpio_port_t LED_GetPort(uint8_t PinIndex)
{
en_gpio_port_t PortValue;
switch(PinIndex)
{
case 1:
PortValue = LED1_DISP_Port;
break;
case 2:
PortValue = LED2_DISP_Port;
break;
case 3:
PortValue = LED3_DISP_Port;
break;
case 4:
PortValue = LED4_DISP_Port;
break;
case 5:
PortValue = LED5_DISP_Port;
break;
case 6:
PortValue = LED6_DISP_Port;
break;
default:
PortValue = LED1_DISP_Port;
break;
}
return PortValue;
}
3.3数码管IO重置复位
在扫描过程中,为了保证未扫描到的段不被点亮,故需要在扫描新的段前将其复位,其代码如下:
void LED_ShowReset(void)
{
uint8_t num;
stc_gpio_cfg_t stcGpioCfg;
stcGpioCfg.enDir = GpioDirIn;
stcGpioCfg.enPu = GpioPuDisable;
stcGpioCfg.enPd = GpioPdDisable;
stcGpioCfg.enDrv = GpioDrvH;
stcGpioCfg.enOD = GpioOdDisable;
//stcGpioCfg.enCtrlMode = GpioAHB;
for(num=1;num<=6;num++)
{
Gpio_ClrIO(LED_GetPort(num), LED_GetPin(num)); // 全部拉低电平 必须拉低,不然会有拖影
}
for(num=1;num<=6;num++)
{
Gpio_Init(LED_GetPort(num), LED_GetPin(num), &stcGpioCfg);
}
}
3.4数码管段点亮
此代码为显示数码管的核心部分,其中highPinNum表示需要设定为高电平输出的引脚,lowPinNum表示需要设定为低电平输出的引脚。并且在每次点亮新的段时必须调用LED_ShowReset()函数将其余的IO恢复为默认状态。
void LED_ShowUnitSegCore(uint8_t highPinNum, uint8_t lowPinNum)
{
stc_gpio_cfg_t stcGpioCfg;
stcGpioCfg.enDir = GpioDirOut;
stcGpioCfg.enPu = GpioPuDisable;
stcGpioCfg.enPd = GpioPdDisable;
stcGpioCfg.enDrv = GpioDrvH;
stcGpioCfg.enOD = GpioOdDisable;
// 重置
LED_ShowReset();
// 初始化为推挽输出
Gpio_Init(LED_GetPort(lowPinNum), LED_GetPin(lowPinNum), &stcGpioCfg);
Gpio_Init(LED_GetPort(highPinNum), LED_GetPin(highPinNum), &stcGpioCfg);
// 设置高低电平
Gpio_ClrIO(LED_GetPort(lowPinNum), LED_GetPin(lowPinNum));
Gpio_SetIO(LED_GetPort(highPinNum), LED_GetPin(highPinNum));
}
3.5显示相应的IO引脚
根据上面图片的定义,点亮DIG1的A段则需要将2引脚设定为高电平,3引脚设定为低电平即可,以此类推故其函数编写如下:
void LED_ShowUnitSeg(uint8_t unit,uint8_t seg)
{
if(unit==LED_DIG1)
{
if( seg == 'A' ) LED_ShowUnitSegCore(2,3);
else if( seg == 'B' ) LED_ShowUnitSegCore(2,4);
else if( seg == 'C' ) LED_ShowUnitSegCore(5,2);
else if( seg == 'D' ) LED_ShowUnitSegCore(2,6);
else if( seg == 'E' ) LED_ShowUnitSegCore(2,5);
else if( seg == 'F' ) LED_ShowUnitSegCore(3,2);
else if( seg == 'G' ) LED_ShowUnitSegCore(4,2);
else if( seg == 'H' ) LED_ShowUnitSegCore(2,1); //DP1
}
else if(unit==LED_DIG2)
{
if( seg == 'A' ) LED_ShowUnitSegCore(5,4);
else if( seg == 'B' ) LED_ShowUnitSegCore(3,5);
else if( seg == 'C' ) LED_ShowUnitSegCore(4,5);
else if( seg == 'D' ) LED_ShowUnitSegCore(3,4);
else if( seg == 'E' ) LED_ShowUnitSegCore(6,3);
else if( seg == 'F' ) LED_ShowUnitSegCore(4,3);
else if( seg == 'G' ) LED_ShowUnitSegCore(5,3);
else if( seg == 'H' ) LED_ShowUnitSegCore(3,1); //DP2
}
else if(unit==LED_DIG3)
{
if( seg == 'A' ) LED_ShowUnitSegCore(1,6);
else if( seg == 'B' ) LED_ShowUnitSegCore(3,6);
else if( seg == 'C' ) LED_ShowUnitSegCore(5,6);
else if( seg == 'D' ) LED_ShowUnitSegCore(6,4);
else if( seg == 'E' ) LED_ShowUnitSegCore(4,6);
else if( seg == 'F' ) LED_ShowUnitSegCore(6,5);
else if( seg == 'G' ) LED_ShowUnitSegCore(1,5);
}
}
3.6定时刷新函数
此函数为LED显示的第二个核心部分,采用定时1ms刷新一次,动态刷新各输出接口,其定义如下:
void LED_ShowUpdate(void)
{
uint8_t Value;
if(Led_Disp_Struct.Timer!=NowTime_1ms) //NowTime_1ms为1ms增加1
{
Led_Disp_Struct.Timer = NowTime_1ms;
Led_Disp_Struct.Seg_Cnt++;
if(Led_Disp_Struct.Seg_Cnt>7)
{
Led_Disp_Struct.Seg_Cnt = 0;
Led_Disp_Struct.Dig_Cnt++;
}
if(Led_Disp_Struct.Dig_Cnt >= LED_DIG_MAX)
{
Led_Disp_Struct.Dig_Cnt = 0;
}
//DIGITRON_ShowUnitSeg(Led_Disp_Struct.Dig_Cnt+1,Led_Disp_Struct.Seg_Cnt+'A');
Value = Led_Disp_Struct.Seg_Value[Led_Disp_Struct.Dig_Cnt][Led_Disp_Struct.Seg_Cnt];
if(Value!='0')
{
LED_ShowUnitSeg(Led_Disp_Struct.Dig_Cnt,Value);
}
else
{
LED_ShowReset(); //必须要重置,否则有残影
}
}
}
3.7其余的赋值函数
剩余的函数相对比较容易,就是赋值并保存相应的数值以便定时刷新函数进行定时刷新。
//保存状态值
void LED_SaveStatusValue(uint8_t unit,uint8_t Index,uint8_t seg)
{
Led_Disp_Struct.Seg_Value[unit][Index] = seg;
}
/**
* 显示数码管值(单管的值)
*
* @param uint8_t unit 数码管位号(即第几个数码管)
* @param uint8_t num 0-9 数字
* @param uint8_t dot 小数点
*/
void LED_ShowUnitNum(uint8_t unit,uint8_t number,uint8_t dot)
{
// 初始化
uint8_t numSegs = SEG_DIGIT_VALUE[number];
// 显示数字
if( (numSegs&0x01) == 0x01 )
{
LED_SaveStatusValue(unit,0,'A'); // & 1000 0000
}
else
{
LED_SaveStatusValue(unit,0,'0');
}
if( (numSegs&0x02) == 0x02 )
{
LED_SaveStatusValue(unit,1,'B'); // & 0100 0000
}
else
{
LED_SaveStatusValue(unit,1,'0');
}
if( (numSegs&0x04) == 0x04 )
{
LED_SaveStatusValue(unit,2,'C'); // & 0010 0000
}
else
{
LED_SaveStatusValue(unit,2,'0');
}
if( (numSegs&0x08) == 0x08 )
{
LED_SaveStatusValue(unit,3,'D'); // & 0001 0000
}
else
{
LED_SaveStatusValue(unit,3,'0');
}
if( (numSegs&0x10) == 0x10 )
{
LED_SaveStatusValue(unit,4,'E'); // & 0000 1000
}
else
{
LED_SaveStatusValue(unit,4,'0');
}
if( (numSegs&0x20) == 0x20 )
{
LED_SaveStatusValue(unit,5,'F'); // & 0000 0100
}
else
{
LED_SaveStatusValue(unit,5,'0');
}
if( (numSegs&0x40) == 0x40 )
{
LED_SaveStatusValue(unit,6,'G'); // & 0000 0010
}
else
{
LED_SaveStatusValue(unit,6,'0');
}
if( dot > 0 ) // 显示小数点
{
LED_SaveStatusValue(unit,7,'H');
}
else
{
LED_SaveStatusValue(unit,7,'0');
}
}
/**
* 显示数码管值
*
* @param float numval 要显示的数值
*/
void LED_ShowDigit(uint16_t numval)
{
// 初始化
uint8_t num;
// 数字范围:0~999
if(numval < 1000)
{
num = numval % 1000 / 100;
LED_ShowUnitNum(LED_DIG1,num, 0);
num = numval % 100 / 10;
LED_ShowUnitNum(LED_DIG2,num, 0);
num = numval % 10;
LED_ShowUnitNum(LED_DIG3,num, 0);
}
// 数字范围:其他
// else
// {
// LED_ShowUnitNum(LED_DIG1, 9, 0);
// LED_ShowUnitNum(LED_DIG2, 9, 0);
// LED_ShowUnitNum(LED_DIG3, 9, 0);
// }
}
3.8主函数运行
int32_t main(void)
{
App_ClockCfg();
LED_ShowInit();
while (1)
{
LED_ShowUpdate();
if(ElapsedTime(LedTimer)>1000)
{
LedTimer = NowTime_1ms;
LED_ShowDigit(LedValue);
LedValue++;
if(LedValue>999) LedValue=0;
}
}
}
3.9数码管数值定义
在上述函数中会调用一个常量,那就是const uint8_t SEG_DIGIT_VALUE[10],那其如何定义?我们可以采用《LED段码数据生成器.exe》软件,这个网上找下就可以了或者通过下面的链接进行下载可以的。
其软件定义如下:

根据上述的自动生成的数据,那么常量值如下:
//按a=0,b=1,c=2,d=3,e=4,f=5,g=6,h=7的共阴极,其显示0-9的数值如下
const uint8_t SEG_DIGIT_VALUE[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
若要增加其他内容显示,采用同样的配置即可。
4、工程文件
不藏私,文件直接上传供大家参考:
1、数码管软件下载地址:https://download.csdn.net/download/shujian123/88098214
2、工程文件下载地址:https://download.csdn.net/download/shujian123/88098216
3、听说有些朋友需要STM32的版本,在此专门更新了STM32版本的2个LED文件,其使用方法请参考第2点的工程文件一样:https://download.csdn.net/download/shujian123/90477907
7856

被折叠的 条评论
为什么被折叠?



