数码之家

 找回密码
 立即注册
搜索
查看: 22416|回复: 29

[C51] 像IO口一样方便的去控制74HC595的输出

[复制链接]
发表于 2019-5-5 21:50:48 | 显示全部楼层 |阅读模式


我想,74HC595的驱动网上有大把的例子,我也曾经参考过别人的。
但是,有时候控制起来确实不太方便。

能不能像IO口一样的来控制74HC595的输出了?
驱动LED,驱动数码管,驱动继电器。。。。

答案是有的。

这也是我在项目中总结出来的,不敢独享,
赠人玫瑰,手有余香。

先来看看原理图。
2组2片74HC595级联,可以扩展32个输出口,当然还可以一直级联下去。1组74HC595级联具体可以扩多少个输出口,
没有试过。
为了驱动和控制方便,2片一组足够了。



下面来看看软件部分。

#define ON   1
#define OFF  0


/*
*  定义引脚
*/
/* 时钟信号线引脚定义 */
sbit HC595CLK1  = P0^3;
/* 片选信号线引脚定义 */
sbit HC595RCK1  = P4^3;
/* 数据输入引脚定义 */
sbit HC595DATA1 = P7^7;

/* 时钟信号线引脚定义 */
sbit HC595CLK2  = P7^6;
/* 片选信号线引脚定义 */
sbit HC595RCK2  = P7^5;
/* 数据输入引脚定义 */
sbit HC595DATA2 = P7^4;


/******************************************************
* 函数名称:SendData
* 函数功能:74HC595数据的发送
* 入口参数:unsigned int uiDataOne, unsigned int uiDataTwo
* 出口参数:void
*******************************************************/
void SendData_0_15(unsigned int uiDataOne, unsigned int uiDataTwo)
{

    unsigned int i = 0;

    /* 将片选信号置为低电平 */
    HC595RCK1 = 0;

    /* 输入第一个数据:uiDataOne */
    for (i = 0; i < 8; i++)
    {
        /* 给出脉冲信号,首先将CLK置为0 */
        HC595CLK1 = 0;
        if (0 != (uiDataOne & 0x80))
        {
            HC595DATA1 = 1;
        }
        else
        {
            HC595DATA1 = 0;
        }
        /* 给出脉冲信号,首先将CLK置为1 */
        HC595CLK1 = 1;
        /* 准备第二个数据 */
        uiDataOne = uiDataOne << 1;
    }

    /* 输入第二个数据:uiDataTwo */
    for (i = 0; i < 8; i++)
    {
        /* 给出脉冲信号,首先将CLK置为0 */
        HC595CLK1 = 0;
        if (0 != (uiDataTwo & 0x80))
        {
            HC595DATA1 = 1;
        }
        else
        {
            HC595DATA1 = 0;
        }
        /* 给出脉冲信号,首先将CLK置为1 */
        HC595CLK1 = 1;
        /* 准备第二个数据 */
        uiDataTwo = uiDataTwo << 1;
    }

    /* 将片选信号置为高电平 */
    HC595RCK1 = 1;
}

/******************************************************
* 函数名称:SendData
* 函数功能:74HC595数据的发送
* 入口参数:unsigned int uiDataOne, unsigned int uiDataTwo
* 出口参数:void
*******************************************************/
void SendData_16_31(unsigned int uiDataOne, unsigned int uiDataTwo)
{

    unsigned int i = 0;

    /* 将片选信号置为低电平 */
    HC595RCK2 = 0;

    /* 输入第一个数据:uiDataOne */
    for (i = 0; i < 8; i++)
    {
        /* 给出脉冲信号,首先将CLK置为0 */
        HC595CLK2 = 0;
        if (0 != (uiDataOne & 0x80))
        {
            HC595DATA2 = 1;
        }
        else
        {
            HC595DATA2 = 0;
        }
        /* 给出脉冲信号,首先将CLK置为1 */
        HC595CLK2 = 1;
        /* 准备第二个数据 */
        uiDataOne = uiDataOne << 1;
    }

    /* 输入第二个数据:uiDataTwo */
    for (i = 0; i < 8; i++)
    {
        /* 给出脉冲信号,首先将CLK置为0 */
        HC595CLK2 = 0;
        if (0 != (uiDataTwo & 0x80))
        {
            HC595DATA2 = 1;
        }
        else
        {
            HC595DATA2 = 0;
        }
        /* 给出脉冲信号,首先将CLK置为1 */
        HC595CLK2 = 1;
        /* 准备第二个数据 */
        uiDataTwo = uiDataTwo << 1;
    }

    /* 将片选信号置为高电平 */
    HC595RCK2 = 1;
}


普通IO口模拟SPI,中规中矩。和大多数驱动一样。
重点部分是控制部分。

.h文件中有4个对外函数,如下。
extern void hc595_init( void );
extern void SendData_0_15(unsigned int uiDataOne, unsigned int uiDataTwo);
extern void SendData_16_31(unsigned int uiDataOne, unsigned int uiDataTwo);
extern void HC595_0_31_OutCtr(unsigned char ucNumber,unsigned char ucState);


重点是
void HC595_0_31_OutCtr(unsigned char ucNumber,unsigned char ucState)

因为,我这是扩展32个输出口,所以是单个输出。想控制数码管的可以按照此方法修改。


void HC595_0_31_OutCtr(unsigned char ucNumber,unsigned char ucState)
{
//  datas.u16_Data = uiDat;

    if((ucNumber>=0) && (ucNumber<=15))
    {
        switch(ucNumber)
        {
        case 0:
            datas1.bits.u8_D0 = ucState;
            break;
        case 1:
            datas1.bits.u8_D1 = ucState;
            break;
        case 2:
            datas1.bits.u8_D2 = ucState;
            break;
        case 3:
            datas1.bits.u8_D3 = ucState;
            break;
        case 4:
            datas1.bits.u8_D4 = ucState;
            break;
        case 5:
            datas1.bits.u8_D5 = ucState;
            break;
        case 6:
            datas1.bits.u8_D6 = ucState;
            break;
        case 7:
            datas1.bits.u8_D7 = ucState;
            break;
        case 8:
            datas1.bits.u8_D8 = ucState;
            break;
        case 9:
            datas1.bits.u8_D9 = ucState;
            break;
        case 10:
            datas1.bits.u8_D10 = ucState;
            break;
        case 11:
            datas1.bits.u8_D11 = ucState;
            break;
        case 12:
            datas1.bits.u8_D12 = ucState;
            break;
        case 13:
            datas1.bits.u8_D13 = ucState;
            break;
        case 14:
            datas1.bits.u8_D14 = ucState;
            break;
        case 15:
            datas1.bits.u8_D15 = ucState;
            break;

        default:
            break;
        }
        SendData_0_15(datas1.Bytes.u8_data_L,datas1.Bytes.u8_data_H);
    }
    else if((ucNumber>=16) && (ucNumber <= 31))
    {
        switch(ucNumber)
        {
        case 16:
            datas2.bits.u8_D0 = ucState;
            break;
        case 17:
            datas2.bits.u8_D1 = ucState;
            break;
        case 18:
            datas2.bits.u8_D2 = ucState;
            break;
        case 19:
            datas2.bits.u8_D3 = ucState;
            break;
        case 20:
            datas2.bits.u8_D4 = ucState;
            break;
        case 21:
            datas2.bits.u8_D5 = ucState;
            break;
        case 22:
            datas2.bits.u8_D6 = ucState;
            break;
        case 23:
            datas2.bits.u8_D7 = ucState;
            break;
        case 24:
            datas2.bits.u8_D8 = ucState;
            break;
        case 25:
            datas2.bits.u8_D9 = ucState;
            break;
        case 26:
            datas2.bits.u8_D10 = ucState;
            break;
        case 27:
            datas2.bits.u8_D11 = ucState;
            break;
        case 28:
            datas2.bits.u8_D12 = ucState;
            break;
        case 29:
            datas2.bits.u8_D13 = ucState;
            break;
        case 30:
            datas2.bits.u8_D14 = ucState;
            break;
        case 31:
            datas2.bits.u8_D15 = ucState;
            break;

        default:
            break;
        }
        SendData_16_31(datas2.Bytes.u8_data_L,datas2.Bytes.u8_data_H);
    }
}


32个输出口,像IO口一样,想那个高电平,那个就输出高电平,想那个输出低电平,就低电平。
不会影响其他31个输出口的状态。这才是关键。代码量少,简单易懂,明明白白。



这里还对输出口做了翻转,像IO口一样。
没有用高深的写法,就是想看着简单易懂。


所有文件截图:




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

打赏

参与人数 3家元 +90 收起 理由
q562379863 + 20 我觉得直接使用位逻辑操作写起来更加方便吧.
家睦 + 50
2545889167 + 20 優秀文章

查看全部打赏

 楼主| 发表于 2019-5-5 22:12:01 | 显示全部楼层
一下子就有沙发坐了。

欢迎大家围观:smile:。

请多多指点。
回复 支持 反对

使用道具 举报

发表于 2019-5-5 23:12:25 | 显示全部楼层
没看懂,慢慢看,谢谢
回复 支持 反对

使用道具 举报

发表于 2019-5-5 23:17:51 | 显示全部楼层
西点钟 发表于 2019-5-5 22:12
一下子就有沙发坐了。

欢迎大家围观。

这么一来可以用8脚单片机驱动数码管做时钟了
回复 支持 反对

使用道具 举报

发表于 2019-5-6 00:26:09 | 显示全部楼层
zhkrid 发表于 2019-5-5 23:17
这么一来可以用8脚单片机驱动数码管做时钟了

驱动数码管还是用TM1637这类芯片来的直接:lol:
回复 支持 反对

使用道具 举报

 楼主| 发表于 2019-5-6 08:47:23 | 显示全部楼层
38263547 发表于 2019-5-6 00:26
驱动数码管还是用TM1637这类芯片来的直接

https://www.mydigit.cn/forum.php ... id=25150&extra=

用于数码管的1651驱动

我的1651的驱动。

595只是可以驱动很多数码管,省IO口。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2019-5-6 08:47:44 | 显示全部楼层
zhkrid 发表于 2019-5-5 23:17
这么一来可以用8脚单片机驱动数码管做时钟了

可以的。
回复 支持 反对

使用道具 举报

发表于 2019-5-6 10:39:00 | 显示全部楼层
开一个4字节的缓冲,直接刷新数据就OK了,不用这么复杂吧?
void SendData(u32 data)
{
。。。。。。
}:lol:
回复 支持 反对

使用道具 举报

 楼主| 发表于 2019-5-6 10:42:01 | 显示全部楼层
zhuls 发表于 2019-5-6 10:39
开一个4字节的缓冲,直接刷新数据就OK了,不用这么复杂吧?
void SendData(u32 data)
{

是不是需要判断不会打扰改变其他输出口的变化。
送数据前,是不是要做判断?
回复 支持 反对

使用道具 举报

发表于 2019-5-6 10:59:29 | 显示全部楼层
西点钟 发表于 2019-5-6 10:42
是不是需要判断不会打扰改变其他输出口的变化。
送数据前,是不是要做判断?
...

每次更改控制时,直接改缓冲寄存器的内容,改完就刷新到595,这是开环控制,如果是闭环控制,就比较复杂了,不是读IO就可以了,从安全角度来说,而是要通过对应的电路来读取受控端的状态,再做相应的控制修改。
回复 支持 反对

使用道具 举报

发表于 2019-5-6 12:03:48 | 显示全部楼层
先膜拜下,写的很相信,对我等新手很实用
回复 支持 反对

使用道具 举报

发表于 2019-5-6 12:42:36 | 显示全部楼层
如果能接受速度慢的话 还是挺好的封装方式的
回复 支持 反对

使用道具 举报

 楼主| 发表于 2019-5-6 13:10:02 | 显示全部楼层
这是利用联合体,位变量来实现的。

typedef union _UNION_DATA     // 联合体/共用体
{
  struct DATA_BIT    //  位变量
        {
         unsigned char u8_D8:1;     // 8
         unsigned char u8_D9:1;     // 9
         unsigned char u8_D10:1;    // 10
         unsigned char u8_D11:1;    // 11
         unsigned char u8_D12:1;    // 12
         unsigned char u8_D13:1;    // 13
         unsigned char u8_D14:1;    // 14
         unsigned char u8_D15:1;        // 15

         unsigned char u8_D0:1;    // 0
         unsigned char u8_D1:1;    // 1
         unsigned char u8_D2:1;    // 2
         unsigned char u8_D3:1;    // 3
         unsigned char u8_D4:1;    // 4
         unsigned char u8_D5:1;    // 5
         unsigned char u8_D6:1;    // 6
         unsigned char u8_D7:1;           // 7
  }bits;

  struct DATA_Byte    // 字节变量
  {
    unsigned char  u8_data_H;
        unsigned char  u8_data_L;
  }Bytes;
  
  unsigned int     u16_Data;
         
}Tdatas;

Tdatas datas1;
Tdatas datas2;



位变量,是一个很好的解决方式,只可惜,大学里面提及的太少了。
估计现在都没有了。

但是,在项目开发中还是比较实用的。

附一份 位变量 的使用。
输入,输出都有。

比如,你的IO口零散分布,比如LCD12864 的数据D0~~~D7,接在不同的IO口上,
怎么像操作 单片机端口一样操作数据了? 比如 51中 P1 = 0X5A。
位变量就可以帮你实现!!!



具体,请看附件。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

打赏

参与人数 1家元 +10 收起 理由
不长叶子的树 + 10

查看全部打赏

回复 支持 2 反对 0

使用道具 举报

发表于 2019-5-7 22:21:26 | 显示全部楼层
本帖最后由 座机呀 于 2019-5-7 22:37 编辑

OE需要控制一下吧,或者加个RC电路,让595上电慢一点,595上电的时候里面的RAM是乱的,可能会有误动作.楼主的封装方式其实可以参考楼上讲的,把一组595虚拟成2字节的缓冲,然后在底层以一定的频率刷新到硬件.
像你讲的2组可以模拟一个Port,如果函数封装成STM32标准库那样操作IO的方式,那样只需新添加一个Port定义,应用层输出数据到IO口的调用方式将不用变化.
以上,只是建议
回复 支持 反对

使用道具 举报

发表于 2019-5-8 16:10:24 | 显示全部楼层
有没有人试过用串口输出数据给595
回复 支持 反对

使用道具 举报

 楼主| 发表于 2019-5-8 16:36:05 | 显示全部楼层
lorn丁 发表于 2019-5-8 16:10
有没有人试过用串口输出数据给595

这个简单呀。
回复 支持 反对

使用道具 举报

发表于 2019-5-8 17:24:20 | 显示全部楼层
NB,还挺实用的。
这个方法比TI的TC系列端口扩展便宜多了,但综合起来不一定比天马微TM系列IO扩展芯片便宜,毕竟可以多段LCD还带键盘扫描带输入。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2019-5-8 17:57:14 | 显示全部楼层
infozx 发表于 2019-5-8 17:24
NB,还挺实用的。
这个方法比TI的TC系列端口扩展便宜多了,但综合起来不一定比天马微TM系列IO扩展芯片便宜 ...

主要是省IO口,这才是关键
回复 支持 反对

使用道具 举报

发表于 2019-5-8 19:16:26 | 显示全部楼层
本帖最后由 oscillator 于 2019-5-9 18:51 编辑
西点钟 发表于 2019-5-6 10:42
是不是需要判断不会打扰改变其他输出口的变化。
送数据前,是不是要做判断?
...

是的,用32bit的变量就行。也不用担心打扰其他输出口的变化,用位运算和移位单独改变某一个位就可以了。

例如,变量名叫OUT。想把第n位置1,只需要:OUT |= 0x01  << n  。然后把OUT发送出去就可以了。

你也没必要写32个函数,比如Q0_toggle到Q32_toggle,只需要用位异或运算,比如需要toggle第n位,只需要 OUT ^= 0x01 << n  。 然后把OUT发送出去。


打赏

参与人数 1家元 +15 收起 理由
q562379863 + 15 精彩回帖

查看全部打赏

回复 支持 2 反对 0

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2025-5-10 23:32 , Processed in 0.374400 second(s), 12 queries , Redis On.

Powered by Discuz!

© 2006-2025 MyDigit.Net

快速回复 返回顶部 返回列表