5个IO驱动188数码管

5个IO驱动188数码管

驱动单个数码管

一般常见的数码管都是由7段或者8段(8段可以显示小数点)LED组成。利用发光二极管的单向导电性就可以点亮LED。实际项目中一般不会用专用的驱动芯片驱动数码管,除非很多个数码管。那常见方法就是直接IO口驱动。单个数码管长这个样子:

image-20231222113620852

要想点亮它,用8个IO口驱动,每个IO口控制一个段选。要想显示数字0~9,则需要给IO口不同的段码。一般会对应数码管的段码数组。

结论:点亮单个数码管,一般至少需要7个或8个IO口。

如何驱动多个数码管?

多个数码管就是把单个数码管封装在一起,如3位数码管:

image-20231222114308882

它们共用段码,用不同的位选控制不同的数码管点亮。

常用方法就是动态扫描。没有什么特殊,本质就是分时复用,利用人的视觉暂留现象即视觉暂停现象(Persistence of vision,Visual staying phenomenon,duration of vision)又称“余晖效应”。让数码管看起来是同时点亮的。

COM1位选选中COM2位选选中COM3位选选中
第一个数码管亮100
第二个数码管亮010
第三个数码管亮001

在某一时刻只有一个数码管点亮,其他数码管位选要关闭,这时候可以针对不同的选中数码管显示不同的内容。如分别显示”123“。

结论:驱动3个数码管,一般至少要8+3共11个IO口。

一般IO口够用的情况下,这个是最常见的应用,不用再搭一颗其他芯片驱动数码管。

5个IO驱动3位数码管

有些特殊应用中,只需要显示0~100的数字。这时候又一种封装的数码管出现了。只需要5个IO就能驱动。其原理一般叫做正反推驱动LED,更专业的说法是查理复用算法。该电路的优缺点参考该作者的文章

188

引脚分配

我重新调整一下引脚分配,

数码管IO分配

对应画出数码管的段码对应表:

image-20231222124722867

image-20231222124819818

image-20231222124846942

驱动数码管的思路是:刷新20次,保证每一次只有一个LED点亮(一个数码管段码点亮),其余IO口设置高阻态。

以下我用51单片机演示驱动手边的一个数码管,使用的是十速的TM52F1363 24pin的MCU。使用正反推的方式驱动数码管要保证芯片IO口能有高阻态模式。

image-20231222130235851

/****************************************************************************************************
      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数码管,惊呆了老铁!不信?看过来!


参考文章:

6脚数码管驱动方式(7脚类似)

五脚18.8数码管显示

188数码管驱动程序(简洁)

STM32单片机通过按键控制LED灯实现正反切换的基本思路是利用中断处理程序配合GPIO驱动LED,并结合按键的状态变化。这里给出一个简化的示例代码,假设我们使用的是Keypad矩阵键盘和一个LED作为输入输出: ```c #include "stm32f10x.h" // 包含STM32的头文件 // LED配置 #define LED_PIN GPIO_Pin_0 #define LED_GPIO_PORT GPIOA #define LED_GPIO_CLK RCC_AHB1Periph_GPIOA // 按键配置 #define KEY_PIN_1 GPIO_Pin_1 #define KEY_PIN_2 GPIO_Pin_2 #define KEY_PORT GPIOB #define KEY_GPIO_CLK RCC_AHB1Periph_GPIOB void LedInit(void) { GPIO_InitTypeDef GPIO_InitStructure; // 初始化LED RCC_APB2PeriphClockCmd(LED_GPIO_CLK, ENABLE); GPIO_InitStructure.GPIO_Pin = LED_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 输出推挽模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(LED_GPIO_PORT, &GPIO_InitStructure); // 初始化按键 RCC_AHB1PeriphClockCmd(KEY_GPIO_CLK, ENABLE); GPIO_InitStructure.GPIO_Pin = KEY_PIN_1 | KEY_PIN_2; // 或者是整个键矩阵的Pin GPIO_InitStructure.GPIO_Mode = GPIO_Mode_In_FLOATING; // 浮空输入 GPIO_Init(KEY_PORT, &GPIO_InitStructure); } // 按键中断服务函数 void EXTI0_IRQHandler(void) { if (GPIO_ReadInputDataBit(KEY_PORT, KEY_PIN_1)) { // 检查第一个按键是否按下 if (!GPIO_ReadOutputDataBit(LED_GPIO_PORT, LED_PIN)) { GPIO_SetBits(LED_GPIO_PORT, LED_PIN); // LED点亮 } else { GPIO_ResetBits(LED_GPIO_PORT, LED_PIN); // LED熄灭 } } } int main(void) { LedInit(); NVIC_EnableIRQ(EXTI0_IRQn); // 开启按键中断 while (1) { // 循环等待按键触发中断 } }
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值