单片机论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 448|回复: 19
收起左侧

51单片机控制74LS164芯片输出64位的数组

[复制链接]
izhonguo 发表于 2019-2-12 02:37 | 显示全部楼层 |阅读模式
本帖最后由 izhonguo 于 2019-3-7 00:59 编辑

各位大神新年好,想请教一个简单的程序如何写。想利用51单片机的P00和P01两个串口输出一个16位的数组【0 0 0 1 1 1 0 0 ****】等数据,然后利用LS164这个芯片实现并口输出,写的程序如下,请各位大佬看一下是哪里有问题,为何不能按照想要的结果输出?

#include <reg52.h>
sbit LS164_DATA = P0^0;  //74LS164数据线
sbit LS164_CLK = P0^1;  //74LS164时钟线


/********************************************************************
* 名称 : delay()
* 功能 : 延时
* 输入 : i
* 输出 : 无
***********************************************************************/


void LS164_Write(unsigned long state, unsigned long state2)  //74LS164写32位扩展引脚函数
{
        unsigned char i;
        for(i=0;i<32;i++)  //循环写后32位扩展引脚状态
        {
                LS164_CLK = 0;
                if(state2 & 0x00000001)
                {
                        LS164_DATA = 1;
                }
                else
                {
                        LS164_DATA = 0;
                }
                LS164_CLK = 1;
                state2 = state2>>1;
        }
        for(i=0;i<32;i++)  //循环写前32位扩展引脚状态
        {
                LS164_CLK = 0;
                if(state & 0x00000001)
                {
                        LS164_DATA = 1;
                }
                else
                {
                        LS164_DATA = 0;
                }
                LS164_CLK = 1;
                state = state>>1;
        }
}

/********************************************************************
* 名称 : Main()
* 功能 : 主函数
* 输入 : 无
* 输出 : 无
***********************************************************************/
void main(void)
{
        unsigned char state_in[] = {0,0,0,0,0,0,0,1,
                                                                0,0,0,0,0,0,1,1,
                                                                0,0,0,0,0,1,1,1,
                                                                0,0,0,0,1,1,1,1,
                                                                1,0,0,0,0,0,0,0,
                                                                1,1,0,0,0,0,0,0,
                                                                1,1,1,0,0,0,0,0,
                                                                1,1,1,1,0,0,0,0};
        unsigned char j;
        unsigned long state=0x0;         //0x0103070F
        unsigned long state2=0x0;         //0x80C0E0F0
        LS164_Write(0x0,0x0);  //所有继电器无动作,开关常闭NC(24V接NO)
        for(j=0;j<32;j++)  //循环写数组内前32位的状态为一个32位的long类型的4字节(32bit)
        {
                if(state_in[j]==1)
                {
                        state = state^(0x80000000>>j); //按位异或运算:两个二进制位不同时,结果为 1,相同时结果为 0。例如0^1为1,0^0为0,1^1为0。
                }
        //        state=0x01;
        //        LS164_Write(state);
        }
        for(j=32;j<64;j++)  //循环写数组内前32位的状态为一个32位的long类型的4字节(32bit)
        {
                if(state_in[j]==1)
                {
                        state2 = state2^(0x80000000>>(j-32));
                }
        //        state=0x01;
        //        LS164_Write(state);
        }
        LS164_Write(state,state2);
//        state=0x10;         //1 0 0 0 0 0 1 1
        while(1);
//        {
//                   LS164_Write(state);
//                delayms(100);
//        }
}




异或运算

异或运算
回复

使用道具 举报

wulin 发表于 2019-2-12 16:54 | 显示全部楼层
标题与内容描述不符,内容描述与程序不符,程序中数据类型定义与实际需要不符,看不明白楼主最终目的是什么。
回复

使用道具 举报

 楼主| izhonguo 发表于 2019-2-12 18:08 | 显示全部楼层
用仿真跑的输入结果显示是正确的,但是把相同的hex文件烧入单片机去控制真实的LS164却出现不一样的结果,这个是为什么呢?
TIM截图20190212180715.png
回复

使用道具 举报

yaoji123 发表于 2019-2-13 07:39 | 显示全部楼层
AT89C52的P0口在用时要加上拉电阻,否则电平不对
回复

使用道具 举报

 楼主| izhonguo 发表于 2019-2-13 15:36 | 显示全部楼层
wulin 发表于 2019-2-12 16:54
标题与内容描述不符,内容描述与程序不符,程序中数据类型定义与实际需要不符,看不明白楼主最终目的是什么 ...

总工程师您好,因为自己一直再改代码,所以最终放上去的一个和题目描述的不对,就是我现在需要处理一个64位的数组,这里面随机的分布着0或者1, 然后我需要8个LS164的位移芯片共计64个输出端口上输出这个数组里的高电平和低电平。仿真结果我看是正确的,但是打上实物,用万用表量LS164的输出端口电压不能和仿真上的结果一样。
64位数组.png
回复

使用道具 举报

 楼主| izhonguo 发表于 2019-2-13 15:40 | 显示全部楼层
yaoji123 发表于 2019-2-13 07:39
AT89C52的P0口在用时要加上拉电阻,否则电平不对

您好,请问P0的上拉电阻如何接线?是和51单片机的最小系统里的上拉电阻一样接法就可以吗?直接插一个10KΩ的电阻接地就可以吗?谢谢!
回复

使用道具 举报

 楼主| izhonguo 发表于 2019-2-13 17:29 | 显示全部楼层
这两天又改了一下代码,现在可以正确按照顺序输出我自己定义的64bit的数组里面的0或者1,但是我有个疑问想向论坛的大牛确认一下,这样子用位移寄存器的64个输出端一个一个推出我64bit数组里的高电平和低电平取控制比如说5V的继电器的闭合状态的话,会不会导致位移高电平过程中,意外导致其他继电器不断被打开。比如说不幸情况下64bit的数组最右的是个1,然后我需要推64步才能把这个1推到我第8块的74LS164芯片的第八位输出上,但是推这个1到指定位置的时候不是要经过前面63个位置吗?这样这63个位置的继电器不是都要被触发一遍吗?还望各位大佬耐心指点。多谢!
还有一个问题,我用这个生成的HEX文件烧入单片机然后在面包板上测量LS164输出的电压,发现和仿真结果完全不同,是乱七八槽的。是仿真有问题还是实物接线有其他特别需要注意的地方(74LS164在面包板上的接线检查了很多遍,应该不会有问题)。再次感谢大家的时间。



64位数组.png
回复

使用道具 举报

wulin 发表于 2019-2-13 20:39 | 显示全部楼层
izhonguo 发表于 2019-2-13 15:36
总工程师您好,因为自己一直再改代码,所以最终放上去的一个和题目描述的不对,就是我现在需要处理一个64 ...

建议改用74HC595连级输出。ST_CP(12脚):上升沿时移位寄存器的数据进入数据存储寄存器,下降沿时存储寄存器数据不变。通常将ST_CP置为低电平,这样在移位过程中各输出端状态保持不变,当移位结束后,在ST_CP端产生一个正脉冲(5V时,大于几十纳秒就行了。通常都选微秒级),更新输出数据。
回复

使用道具 举报

wulin 发表于 2019-2-14 08:48 | 显示全部楼层
izhonguo 发表于 2019-2-13 17:29
这两天又改了一下代码,现在可以正确按照顺序输出我自己定义的64bit的数组里面的0或者1,但是我有个疑问想 ...

你的上述疑问是真实存在的,如果改用74HC595连级输出可以解决你的上述疑问。给你一个示例程序和仿真。

无标题.jpg


#include <reg51.H>
#define uint unsigned int
#define uchar unsigned char
sbit        P_HC595_SER   = P0^0;
sbit        P_HC595_SRCLK = P0^1;
sbit        P_HC595_RCLK  = P0^2;

/*******************定义8个字节64 bit缓存数组*************************/
uchar data dis_buf[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};//

/**************** 向HC595发送一个字节函数 ******************/
void Send_595(uchar dat)
{               
        uchar i;
        for(i=0;i<8;i++)
        {
                dat <<= 1;
                P_HC595_SER  = CY; //溢出位赋值数据输出端
                P_HC595_SRCLK = 1; //移位时钟
                P_HC595_SRCLK = 0;
        }
}
/**************** 向HC595发送八个字节函数 ******************/
void DisplayScan()
{       
        uchar j;
        for(j=0;j<8;j++)
        {
                Send_595(dis_buf[j]);//向HC595发送一个字节
        }
        P_HC595_RCLK = 1;//锁存输出数据
        P_HC595_RCLK = 0;
}

void main()
{
        P_HC595_SRCLK = 0;//初始化移位时钟端
        P_HC595_RCLK  = 0;//初始化锁存输出端
        DisplayScan();                //发送八个字节
        while(1);
}

回复

使用道具 举报

 楼主| izhonguo 发表于 2019-2-19 20:47 来自手机 | 显示全部楼层
wulin 发表于 2019-2-14 08:48
你的上述疑问是真实存在的,如果改用74HC595连级输出可以解决你的上述疑问。给你一个示例程序和仿真。

...

太感谢大神了!看的小弟我热泪盈眶
回复

使用道具 举报

笨笨兔 发表于 2019-2-20 13:45 | 显示全部楼层
楼主:设计不是想的那么简单,实现相同的功能,选用什么样的器件,很重要。
慢慢体会!
回复

使用道具 举报

kmsj 发表于 2019-2-20 16:01 | 显示全部楼层
先不谈程序,51单片机的P0口是个非常扯淡的口,我的仿真器的仿真头排线在左边,插到目标板上P1和P3口就挡住了,P1和P3口用起来就不太方便,P0和P2口没有被挡住,P0口在左上角,接线最方便,所以我经常用P0口仿真,直到有一次仿真74HC165,简简单单的移位程序,怎么都无法成功,搞的我焦头烂额,后来用AVR单片机仿真,一次就成功。突发奇想,用51单片机的P2口仿真74HC165,也是一次成功。要知道,51单片机的P0口仿真CD4094、CD4014、CD4021都能成功,就是74HC165不能成功,我编了一些方波程序看P0口和其它口的差别,发现P0口下拉能力弱,速度也很慢,要想用P0口仿真74HC165成功,最简单的方法就是降低速度,但是,如果使用和研究51单片机,少用和慎用P0口是少走弯路的重要方法的之一。

评分

参与人数 1黑币 +70 收起 理由
admin + 70 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

struct00 发表于 2019-2-20 17:42 | 显示全部楼层

标题与内容描述不符,内容描述与程序不符,程序中数据类型定义与实际需要不符,最终全体不服!
回复

使用道具 举报

 楼主| izhonguo 发表于 2019-2-21 08:31 | 显示全部楼层
kmsj 发表于 2019-2-20 16:01
先不谈程序,51单片机的P0口是个非常扯淡的口,我的仿真器的仿真头排线在左边,插到目标板上P1和P3口就挡住 ...

多谢经验分享,刚接触单片机,还需要多向各位大牛勤快请教。
回复

使用道具 举报

kmsj 发表于 2019-2-21 13:34 | 显示全部楼层
izhonguo 发表于 2019-2-21 08:31
多谢经验分享,刚接触单片机,还需要多向各位大牛勤快请教。

不用谢!51单片机的P0口驱动LED、通过三极管驱动继电器,读开关状态之类的低速应用问题不大,高速读写的情况下就要注意了,比如I2C、SPI接口等等
回复

使用道具 举报

16#
无效楼层,该帖已经被删除
 楼主| izhonguo 发表于 2019-3-7 00:53 | 显示全部楼层
wulin 发表于 2019-2-14 08:48
你的上述疑问是真实存在的,如果改用74HC595连级输出可以解决你的上述疑问。给你一个示例程序和仿真。

...

总工,您好:
我自己学习了一下51单片机的串口接收程序,我想把您这个程序改进一下,
做到可以接收比如说从XCOM这类软件发来的八个字符串(比如说1A2B3C4D),
然后写入到dis_buf[8]定义的这个定义的缓存数组中。下面这样子写正确吗?还望不吝赐教!谢谢!


  1. #include <reg51.H>
  2. #define uint unsigned int
  3. #define uchar unsigned char
  4. sbit        P_HC595_SER   = P0^0;
  5. sbit        P_HC595_SRCLK = P0^1;
  6. sbit        P_HC595_RCLK  = P0^2;

  7. /*******************定义8个字节64 bit接收缓存数组*************************/
  8. uchar data dis_buf[8]={0};//初始化字符串

  9. /*******************串口初始化函数*************************/
  10. void   URATinit( )
  11. {  
  12.         TMOD=0x20;  
  13.         SCON=0x50;  
  14.         EA=1;  
  15.         ES=1;  
  16.         TR1=1;  
  17.         TH1=0xfd;  
  18.         TL1=0xfd;
  19. }

  20. /*******************中断函数*************************/
  21. void receive() interrupt 4
  22. {
  23.         uchar i;        
  24.         if(RI) { //PC机向单片机发送命令是否被识别      
  25.                 dis_buf[i]=SBUF;   
  26.                 RI=0; //清0准备下一次接收  
  27.         }
  28.         while(!TI);   
  29.         TI=0;  
  30.         i++;  
  31.         if(i>=8){ //防止大于8溢出
  32.                 i=0;  
  33.         }
  34. }  
  35. /**************** 向HC595发送一个字节函数 ******************/
  36. void Send_595(uchar dat)
  37. {               
  38.         uchar i;
  39.         for(i=0;i<8;i++)
  40.         {
  41.                 dat <<= 1;
  42.                 P_HC595_SER  = CY; //溢出位赋值数据输出端
  43.                 P_HC595_SRCLK = 1; //移位时钟
  44.                 P_HC595_SRCLK = 0;
  45.         }
  46. }
  47. /**************** 向HC595发送八个字节函数 ******************/
  48. void DisplayScan()
  49. {        
  50.         uchar j;
  51.         for(j=0;j<8;j++)
  52.         {
  53.                 Send_595(dis_buf[j]);//向HC595发送一个字节
  54.         }
  55.         P_HC595_RCLK = 1;//锁存输出数据
  56.         P_HC595_RCLK = 0;
  57. }

  58. void main()
  59. {
  60.         P_HC595_SRCLK = 0;//初始化移位时钟端
  61.         P_HC595_RCLK  = 0;//初始化锁存输出端
  62.         URATinit( );
  63.         DisplayScan();                //发送八个字节
  64.         while(1);
  65. }
复制代码



回复

使用道具 举报

wulin 发表于 2019-3-7 15:54 | 显示全部楼层
izhonguo 发表于 2019-3-7 00:53
总工,您好:
我自己学习了一下51单片机的串口接收程序,我想把您这个程序改进一下,
做到可以接收比 ...

给你改了一下,已经实物验证,供参考。 无标题.jpg

  1. //模拟数据:0数据头  1~8有效数据               9数据尾  
  2. //HEX模式:    AA     01 0A 02 0B 03 0C 04 0D       38
  3. #include <reg51.H>
  4. #define uint unsigned int
  5. #define uchar unsigned char
  6. sbit P_HC595_SER   = P0^0;//使用P0口需外接5~10K上拉电阻
  7. sbit P_HC595_SRCLK = P0^1;
  8. sbit P_HC595_RCLK  = P0^2;

  9. uchar table0[] ="OK\n";        //用于串口助手返回验证
  10. uchar table1[]="ERROR\n";//用于串口助手返回验证
  11. uchar dis_buf[10];                //串口接收缓存
  12. bit flag=0;                                        //接收完成标志
  13. /*******************串口初始化函数*************************/
  14. void URATinit()
  15. {  
  16.         PCON &= 0x7F;                //波特率不倍速9600
  17.         SCON = 0x50;                //8位数据,可变波特率
  18.         TMOD= 0x20;                        //设定定时器1为8位自动重装方式
  19.         TL1 = 0xFD;                        //设定定时初值
  20.         TH1 = 0xFD;                        //设定定时器重装值
  21.         ET1 = 0;                                //禁止定时器1中断
  22.         TR1 = 1;                                //启动定时器1
  23.         EA = 1;                                //开总中断
  24.         ES = 1;                                //开串口中断
  25. }

  26. /**************** 向HC595发送一个字节函数 ******************/
  27. void Send_595(uchar dat)
  28. {               
  29.         uchar i;
  30.         for(i=0;i<8;i++)
  31.         {
  32.                 dat <<= 1;
  33.                 P_HC595_SER  = CY; //溢出位赋值数据输出端
  34.                 P_HC595_SRCLK = 1; //移位时钟
  35.                 P_HC595_SRCLK = 0;
  36.         }
  37. }
  38. /**************** 向HC595发送八个字节函数 ******************/
  39. void DisplayScan()
  40. {        
  41.         uchar j;
  42.         for(j=0;j<8;j++)
  43.         {
  44.                 Send_595(dis_buf[j+1]);//向HC595发送一个字节
  45.         }
  46.         P_HC595_RCLK = 1;//锁存输出数据
  47.         P_HC595_RCLK = 0;
  48. }
  49. /**********串口发送函数*************/
  50. void SendOneByte(uchar i)
  51. {
  52.     SBUF = i;                //发送数据
  53.     while(!TI);        //等待发送完成
  54.     TI = 0;                        //发送中断请求标志位清0
  55. }
  56. /************数据解析程序*************/
  57. void analysis()
  58. {
  59.         uchar i,j=0;                //临时变量
  60.         if(flag==1)                //10个字节数据串接收完成
  61.         {
  62.                 ES=0;                        //关串口中断
  63.                 flag=0;                //接收完成标志清0
  64.                 for(i=0;i<8;i++)
  65.                 {
  66.                         j+=dis_buf[i+1];//读取缓存里8个字节有效数据和,溢出部分抛弃
  67.                 }
  68.                 if(dis_buf[9]==j)//验证数据和
  69.                 {
  70.                         for(i=0;i<3;i++)
  71.                         {
  72.                                 SendOneByte(table0[i]);//返回PC "OK"(用串口助手文本模式接收)
  73.                         }
  74.                         DisplayScan();    //向HC595发送缓存收到的8个有效字节
  75. //                        for(i=0;i<10;i++)//返回PC,缓存收到的10个字节(用串口助手HEX模式接收)
  76. //                        {
  77. //                                SendOneByte(dis_buf[i]);
  78. //                        }
  79.                 }
  80.                 else
  81.                 {
  82.                         for(i=0;i<6;i++)
  83.                         {
  84.                                 SendOneByte(table1[i]);//返回PC "ERROR"
  85.                         }
  86.                         for(i=0;i<10;i++)
  87.                         {
  88.                                 dis_buf[i]=0;                        //清空缓存
  89.                         }
  90.                 }
  91.                 ES=1;                                                //开串口中断
  92.         }
  93. }
  94. /*******主函数********/
  95. void main()
  96. {
  97.         P_HC595_SRCLK = 0;                        //初始化移位时钟端
  98.         P_HC595_RCLK  = 0;                        //初始化锁存输出端
  99.         URATinit();                                        //初始化串口
  100.         while(1)
  101.         {
  102.                 analysis();                                //数据解析程序
  103.         }
  104. }
  105. /*******************中断函数*************************/
  106. void receive() interrupt 4
  107. {
  108.         static uchar i=0;                //静态计数变量

  109.         RI=0;                                        //接收中断请求标志位清0
  110.         dis_buf[i]=SBUF;                //接收到的数据串保存在缓存数组中
  111.         if(dis_buf[0]==0xAA)        //验证数据头(起始位),否则重收覆盖
  112.         {
  113.                 i++;
  114.                 if(i>=10)
  115.                 {
  116.                         flag=1;                        //接收完成标志置1(完成1帧10bit)
  117.                         i=0;                        //计数变量清0
  118.                 }
  119.         }
  120. }  
复制代码



回复

使用道具 举报

 楼主| izhonguo 发表于 2019-3-7 21:33 | 显示全部楼层
wulin 发表于 2019-3-7 15:54
给你改了一下,已经实物验证,供参考。

多谢总工!我再仔细学习一遍。。。
总工牛逼!
回复

使用道具 举报

 楼主| izhonguo 发表于 2019-3-13 21:40 | 显示全部楼层
wulin 发表于 2019-3-7 15:54
给你改了一下,已经实物验证,供参考。

总工,您好,这周测试了一下实物,意识到AA开头的可能会出现一个问题,比如说发出来的数据开头不是AA,中间却恰巧夹杂了一个AA,这样的话,就会出现意外的错误,我的理解是中断程序会从第一组中间的那个AA还是计算为接收10位数组的开头,然后等待第二次发送的后几位补足共计的10个Bit, 然后不出意外最后一位基本不会是前九位的和,所以肯定会触发报错程序。如图所示。第一张图,AA开头发送,返回OK, 然后第二次发送前改起始位AA为AB,然后点击发送,发送数增加为20,由于第九位是AA,所以此时中断程序认为第二次发送的10个数据里,前8为为无效,目前只接收到2位,即AA,和它后面的和F6,然后我再次点击发送数据,此时,AB位补位刚才不够10个Bit的第三位,然后到AA前的数据27就完成i>=10的判断条件,然后进入数据解析,进而由于末尾和不等于前8位有效数据导致报错。
1.png 2.png 3.jpg


所以在HEX模式下,是否可以使用出了0~F之外的特殊字符比如说#或者##作为接收数据的起始位判别,在中断函数里用命令判别:
        if(dis_buf[0]=='#')        //验证数据头(起始位),否则重收覆盖
        {
                i++;
                if(i>=10)
                {
                        flag=1;    //接收完成标志置1(完成1帧10bit)
                        i=0;      //计数变量清0
                }
        }
        else

        {
                P_HC595_OE = 1; //拉高OE引脚,复位所有继电器
                for(j=0;j<6;j++)
                {
                    SBUF = table2[j];   //发送数据 NOTAA\n
                         while(!TI);        //等待发送完成
                         TI = 0;
                 }

         }

中断函数的else部分如果触发,返回错误信息经过测试发现会自动循环三次(可能是中断函数自身的循环设定),结果如图:

4.png


所以如果我不用AA作为起始位验证的话,能否改用后续有效数据中不会出现的特殊字符如#字符串来验证,文本模式和HEX模式是否可以混用?

如果不可混用必须用HEX模式下的有效0~F作为起始位的话,我自己想的一个解决方案是中断函数不判别,仅无脑接收上层发过来的10个BIT, 然后我可以在数据解析里进行判别如,如计算完有效数据的和j之后,同时判别dis_buf[9]==j && dis_buf[0]==0xAA是否成立。成立则进行HC595发送函数,否则返回PC错误信息(这样也可以解决判别不是AA开头的话,错误信息重复三次的问题)。但是不确定这种方法是否会在中断函数中引起什么不良影响?

如果更严谨点的话,作为被动接收的中断函数,程序如何判别说到的数据不够10位?难道需要再增加一个判别数据结尾的特殊字符如!这样的方式吗?

谢谢!


回复

使用道具 举报

wulin 发表于 2019-3-14 08:46 | 显示全部楼层
本帖最后由 wulin 于 2019-3-14 13:17 编辑
izhonguo 发表于 2019-3-13 21:40
总工,您好,这周测试了一下实物,意识到AA开头的可能会出现一个问题,比如说发出来的数据开头不是AA,中 ...

izhonguo你好,很赞赏你勤于思考。给你的示例只是我随手写的,简单展示了自定义通讯协议的框架。实际运用的自定义通讯协议要比这复杂些。由于使用环境因素,数据头往往由好几个字节组成,甚至在数据头前加0x00无效数据引导以防数据头错码。验证有效数据的正确性是非常重要的措施。方法也很多,有用数据和、数据和取反+1、计算字节长度等等。目的都是为了保证有效数据的正确性。由于传输的数据帧包含的字节可能不确定,还要增加结束码等等。串口中断函数中还要有自动纪录字节长度,超时不等侯措施。
回复

使用道具 举报

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

本版积分规则

QQ|手机版|小黑屋|单片机论坛 |51hei电子论坛2群 联系QQ:125739409;技术交流QQ群219535678

Powered by 单片机教程网

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