5个IO驱动188数码管
驱动单个数码管
一般常见的数码管都是由7段或者8段(8段可以显示小数点)LED组成。利用发光二极管的单向导电性就可以点亮LED。实际项目中一般不会用专用的驱动芯片驱动数码管,除非很多个数码管。那常见方法就是直接IO口驱动。单个数码管长这个样子:
要想点亮它,用8个IO口驱动,每个IO口控制一个段选。要想显示数字0~9,则需要给IO口不同的段码。一般会对应数码管的段码数组。
结论:点亮单个数码管,一般至少需要7个或8个IO口。
如何驱动多个数码管?
多个数码管就是把单个数码管封装在一起,如3位数码管:
它们共用段码,用不同的位选控制不同的数码管点亮。
常用方法就是动态扫描。没有什么特殊,本质就是分时复用,利用人的视觉暂留现象即视觉暂停现象(Persistence of vision,Visual staying phenomenon,duration of vision)又称“余晖效应”。让数码管看起来是同时点亮的。
COM1位选选中 | COM2位选选中 | COM3位选选中 | |
---|---|---|---|
第一个数码管亮 | 1 | 0 | 0 |
第二个数码管亮 | 0 | 1 | 0 |
第三个数码管亮 | 0 | 0 | 1 |
在某一时刻只有一个数码管点亮,其他数码管位选要关闭,这时候可以针对不同的选中数码管显示不同的内容。如分别显示”123“。
结论:驱动3个数码管,一般至少要8+3共11个IO口。
一般IO口够用的情况下,这个是最常见的应用,不用再搭一颗其他芯片驱动数码管。
5个IO驱动3位数码管
有些特殊应用中,只需要显示0~100的数字。这时候又一种封装的数码管出现了。只需要5个IO就能驱动。其原理一般叫做正反推驱动LED,更专业的说法是查理复用算法。该电路的优缺点参考该作者的文章。
我重新调整一下引脚分配,
对应画出数码管的段码对应表:
驱动数码管的思路是:刷新20次,保证每一次只有一个LED点亮(一个数码管段码点亮),其余IO口设置高阻态。
以下我用51单片机演示驱动手边的一个数码管,使用的是十速的TM52F1363 24pin的MCU。使用正反推的方式驱动数码管要保证芯片IO口能有高阻态模式。
/****************************************************************************************************
IC : TM52F1363
8K ROM, 512 RAM, 128 EEPROM.
功能: 烧录脚:ic-4:
*****************************************************************************************************/
#include <REGtenxTM52F1363.h>
#include "tm52f1363_bsp.h"
#include <intrins.h>
#define LongToBin(n) \
( \
((n >> 21) & 0x80) | \
((n >> 18) & 0x40) | \
((n >> 15) & 0x20) | \
((n >> 12) & 0x10) | \
((n >> 9) & 0x08) | \
((n >> 6) & 0x04) | \
((n >> 3) & 0x02) | \
((n) & 0x01))
// write binary charactor set,exsample : Bin(11111111) = 0xff
#define Bin(n) LongToBin(0x##n##l)
unsigned char xdata g_smg_write_buffer[4] = {0}; // 全局数组,用于数码管显示缓存数据
/**
* 点亮数码管数字的段码,abcdefg+h 段码存放在code中,不占用RAM空间
*/
unsigned char code Display_Seg_Buffer1[10] = {0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // 显示段码数组,只有段码是“1”才显示,其他数字都不显示
unsigned char code Display_Seg_Buffer2[20] = {0xFC, 0x60, 0xDA, 0xF2, 0x66, 0xB6, 0xBE, 0xE0, 0xFE, 0xF6, // 数字不带小数点的段码
0xFD, 0x61, 0xDB, 0xF3, 0x67, 0xB7, 0xBF, 0xE1, 0xFF, 0xF7}; // 数字带小数点的段码,需要显示的时候,在数字后加10,具体看程序
unsigned char code Display_Seg_Buffer3[8] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; // 显示段码数组,第一位是无显示
unsigned int t_500ms = 0; // 计时变量
bit flicker_flag = 0; // 控制闪烁标志
#define TIME_500MS 2500
/**********************************************************************************************************
**函数名称 :iap_eeprom_delay()
**函数描述 :写eeprom延时函数
**输 入 :None
**输 出 :None
**********************************************************************************************************/
void iap_eeprom_delay()
{
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
}
void SMG_init(void)
{
unsigned char modl = 0; // 修改寄存器临时变量,防止多次读写寄存器
unsigned char modh = 0; // 修改寄存器临时变量,防止多次读写寄存器
modl = Bin(01010101); // P1.3- P1.0 引脚控制
modh = Bin(10101001); // P1.7- P1.4 引脚控制
P1MODL = modl; // P1.3- P1.0 引脚控制
P1MODH = modh; // P1.7- P1.4 引脚控制
P1 = Bin(00011111); // 设置为高阻态
}
void SMG_IO_Direct_Drive(unsigned char xdata *pBuffer)
{
unsigned char modl = 0; // 修改寄存器临时变量,防止多次读写寄存器
unsigned char modh = 0; // 修改寄存器临时变量,防止多次读写寄存器
unsigned char pdat = 0; // 修改寄存器临时变量,防止多次读写寄存器
unsigned char num1 = 0; // 断码临时变量
unsigned char num2 = 0; // 断码临时变量
unsigned char num3 = 0; // 断码临时变量
unsigned char num4 = 0; // 断码临时变量
static unsigned char s_com_state = 0;
// 1 2 3 4 5
// 消影 1363 io 全部设置高阻态 P1.0 P1.1 1.2 1.3 1.4
// 00:模式 0 伪开漏输出
// 01:模式 1 伪开漏输出 Pin 脚写1 高阻态
// 10:模式 2 CMOS 推挽输出
// 11:模式 3 替代功能,如 ADC
// 将所有引脚设置高阻,相当于消影!
modl = Bin(01010101); // P1.3- P1.0 引脚控制
modh = Bin(10101001); // P1.7- P1.4 引脚控制
P1MODL = modl; // P1.3- P1.0 引脚控制
P1MODH = modh; // P1.7- P1.4 引脚控制
P1 = Bin(00011111); //
// 解析段码
num1 = Display_Seg_Buffer1[pBuffer[0]]; // '1'
num2 = Display_Seg_Buffer2[pBuffer[1]]; //
num3 = Display_Seg_Buffer2[pBuffer[2]]; //
num4 = Display_Seg_Buffer3[pBuffer[3]]; //
pdat = Bin(00000000); // 初始化
/**
* 2345
* ↓↓↓↓
* 1111
*
* 1345
* ↓↓↓↓
* 2222
*
* 1245
* ↓↓↓↓
* 3333
*
* 1235
* ↓↓↓↓
* 4444
*
* 1234
* ↓↓↓↓
* 5555
*/
// 5个IO口,正反推驱动LED,理论上可以点亮20个LED,故扫描20次.
switch (s_com_state)
{
case 0:
if (num3 & 0x40) //'B3'
{
modl = Bin(01011010); // 2->1
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00011110);
P1 = pdat;
}
break;
case 1:
if (num3 & 0x10) //'D3'
{
modl = Bin(01100110); // 3->1
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00011110);
P1 = pdat;
}
break;
case 2:
if (num3 & 0x04) //'F3'
{
modl = Bin(10010110); // 4->1
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00011110);
P1 = pdat;
}
break;
case 3:
if (num3 & 0x02) //'G3'
{
modl = Bin(01010110); //
modh = Bin(10101010); // 5->1
P1MODL = modl; // P1.3- P1.0 引脚控制
P1MODH = modh; // P1.7- P1.4 引脚控制
pdat |= Bin(00011110);
P1 = pdat;
}
break;
case 4:
if (num3 & 0x80) //'A3'
{
modl = Bin(01011010); // 1->2
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00011101);
P1 = pdat;
}
break;
case 5:
if (num2 & 0x40) //'B2'
{
modl = Bin(01101001); // 3->2
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00011101);
P1 = pdat;
}
break;
case 6:
if (num2 & 0x10) //'D2'
{
modl = Bin(10011001); // 4->2
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00011101);
P1 = pdat;
}
break;
case 7:
if (num2 & 0x08) //'E2'
{
modl = Bin(01011001); //
modh = Bin(10101010); // 5->2
P1MODL = modl; // P1.3- P1.0 引脚控制
P1MODH = modh; // P1.7- P1.4 引脚控制
pdat |= Bin(00011101);
P1 = pdat;
}
break;
case 8:
if (num3 & 0x20) //'C3'
{
modl = Bin(01100110); // 1->3
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00011011);
P1 = pdat;
}
break;
case 9:
if (num2 & 0x80) //'A2'
{
modl = Bin(01101001); // 2->3
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00011011);
P1 = pdat;
}
break;
case 10:
if (num2 & 0x20) //'C2'
{
modl = Bin(10010101); // 4->3
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00011011);
P1 = pdat;
}
break;
case 11:
if (num2 & 0x04) //'F2'
{
modl = Bin(01100101);
modh = Bin(10101010); // 5->3
P1MODH = modh; // P1.7- P1.4 引脚控制
P1MODL = modl;
pdat |= Bin(00011011);
P1 = pdat;
}
break;
case 12:
if (num3 & 0x08) //'E3'
{
modl = Bin(10010110); // 1->4
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00010111);
P1 = pdat;
}
break;
case 13:
if (num1 & 0x20) //'C1'
{
modl = Bin(10011001); // 2->4
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00010111);
P1 = pdat;
}
break;
case 14:
if (num1 & 0x40) //'B1'
{
modl = Bin(10100101); // 3->4
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00010111);
P1 = pdat;
}
break;
case 15:
if (num2 & 0x02) //'G2'
{
modl = Bin(10010101);
modh = Bin(10101010); // 5->4
P1MODH = modh; // P1.7- P1.4 引脚控制
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00010111);
P1 = pdat;
}
break;
case 16:
if (num3 & 0x01) //'DP'
{
modl = Bin(01100110); // 1->5
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00000001);
P1 = pdat;
}
break;
case 17:
if (num4 & 0x01) //'J'
{
modl = Bin(01011001); // 2->5
modh = Bin(10101010); //
P1MODH = modh; // P1.7- P1.4 引脚控制
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00001111);
P1 = pdat;
}
break;
case 18:
if (num4 & 0x02) //'I'
{
modl = Bin(01100101); // 3->5
modh = Bin(10101010); //
P1MODH = modh; // P1.7- P1.4 引脚控制
P1MODL = modl; // P1.3- P1.0 引脚控制
pdat |= Bin(00001111);
P1 = pdat;
}
break;
case 19:
if (num4 & 0x04) //'H'
{
modh = Bin(10101010); // 4->5
P1MODH = modh; // P1.7- P1.4 引脚控制
pdat |= Bin(00010000);
P1 = pdat;
}
break;
default:
break;
}
s_com_state++;
s_com_state %= 20; // s_com_state 从0到19,共20次
}
/******************************************************************************************
** 函数名称: time2_T_16bit_init
** 函数描述: time2初始化函数
** 输 入: 无
** 输 出: 无
** 说 明: time2 16位自动重装载定时器
** 计算公式:16位定时器模式,定时时间(单位:us):1/(system/2)*(TH2<<8+TL2)
*******************************************************************************************/
void time2_T_16bit_init()
{
CT2 = 0; // 定时器模式
CPRL2 = 0; // 重载模式
// TH2 = (65536 - 4147) / 256; // 1ms
// TL2 = (65536 - 4147) % 256;
// RCP2H = (65536 - 4147) / 256; // 自动重载值
// RCP2L = (65536 - 4147) % 256;
// TH2 = (65536 - 415) / 256; // 100us
// TL2 = (65536 - 415) % 256;
// RCP2H = (65536 - 415) / 256; // 自动重载值
// RCP2L = (65536 - 415) % 256;
TH2 = (65536 - 829) / 256; // 200us
TL2 = (65536 - 829) % 256;
RCP2H = (65536 - 829) / 256; // 自动重载值
RCP2L = (65536 - 829) % 256;
TR2 = 1;
ET2 = 1;
}
/**********************************************************************************************************
**函数名称 :main()
**函数描述 :主函数
**输 入 :None
**输 出 :None
**********************************************************************************************************/
void main(void)
{
unsigned char i, j; // 临时变量
unsigned char dis_num = 0; // 数码管显示变量
bsp_clock_init(); // 系统快钟16.588 div 2 = 8.294Mhz
/*上电延时,防止电压不稳*/
i = 10;
while (i--)
{
iap_eeprom_delay(); //
iap_eeprom_delay(); //
iap_eeprom_delay(); //
}
SMG_init(); // io初始化函数
time2_T_16bit_init(); // 16bit 自动重载,定时1ms
t_500ms = TIME_500MS; // 500ms初始化
EA = 1; // 开启总中断
while (1)
{
for (j = 0; j < 10; j++) // 调整j的值来延时不同时间
{
i = 250;
while (i--)
{
iap_eeprom_delay(); //
iap_eeprom_delay(); //
iap_eeprom_delay(); //
}
} // 为了制造延时时间,时间不用精确计算,肉眼来判断“快慢”
dis_num++;
if (dis_num > 100)
{
dis_num = 0; // 防止数据显示溢出
}
g_smg_write_buffer[0] = dis_num / 100 % 10;
g_smg_write_buffer[1] = dis_num / 10 % 10;
g_smg_write_buffer[2] = dis_num % 10;
if (flicker_flag)
{
g_smg_write_buffer[3] = 0x03; // %和充电标都亮
}
else
{
g_smg_write_buffer[3] = 0x01; // 充电标灭 只亮%
}
} // end of while(1)
} // end of mian()
/******************************************************************************************
** 函数名称: time2_irq
** 函数描述: time2中断处理函数
** 输 入: 无
** 输 出: 无
** 说 明: 自动重载计数器和定时器不需要重载
*******************************************************************************************/
void time2_irq() interrupt 5
{
if (TF2) // 溢出标志
{
TF2 = 0; // 除了用作串口的时基外,其他应用必须清零
// 添加用户程序
SMG_IO_Direct_Drive(g_smg_write_buffer); // 扫描数码管,只要肉眼看着不闪烁,可以把定时器的时间加长
t_500ms--;
if (!t_500ms)
{
t_500ms = TIME_500MS; // 500ms初值
flicker_flag ^= 1;
}
}
}
程序效果:
5个IO驱动188数码管,惊呆了老铁!不信?看过来!
参考文章: