tianzhiying 发表于 2012-3-28 17:47:22

调试DS1302时遇到的问题

这几天用DS1302,程序出来之后很不理想
先贴贴程序:
void WriteByte(uchar dat)
{
        uchar i;
        ACC=dat;
        for(i=8;i>0;i--)
        {
                Dat_1302=ACC0;
                CLK_1302=1;       
                _nop_();_nop_();_nop_();
                CLK_1302=0;        //产生上升沿输入数据
                ACC=ACC>>1;
        }       
}

void WriteSet1302(uchar Cmd,uchar dat)
{
        RST_1302=0;
        CLK_1302=0; //确保写数居前SCLK被拉低
        RST_1302=1; //启动数据传输
        WriteByte(Cmd);
        WriteByte(dat);
        RST_1302=0;
        CLK_1302=1;       
}

uchar ReadByte(void)
{
        uchar i;
        for(i=8;i>0;i--)
        {
                ACC7=Dat_1302;//ds1302读数据的时候,第一个数据读取在发一个Byte命令后,在第八位的下降沿
                ACC=ACC>>1;
                CLK_1302=1;
                CLK_1302=0;//产生下降沿输出一位数据
                _nop_();_nop_();_nop_();
        }
        return(ACC);
}

uchar ReadSet1302(uchar Cmd)
{
        uchar dat;
        RST_1302=0;
        CLK_1302=0; //确保写数居前SCLK被拉低
        RST_1302=1; //启动数据传输
        WriteByte(Cmd);
        dat=ReadByte();
        RST_1302=0;
        CLK_1302=1;
        return dat; //将读出的数据返回
}

void Init1302(void)
{
        WriteSet1302(0x8e,0x00);
        WriteSet1302(0x80,0x05);//初始化时分秒
        WriteSet1302(0x82,0x10);
        WriteSet1302(0x84,0x15);
        WriteSet1302(0x90,0xab);
        WriteSet1302(0x8e,0x80);
}
程序遇到的问题是读取时间时老是出错,有时候是秒的数据错了,有时候是分的数据,有时候是时间的数据
而且写进去时间后读出来的数据也有错
不知道是哪里的问题
在网上看了很多这方面的程序,感觉都没有错

ypradio 发表于 2012-3-28 17:55:43

不一定是软件问题,叫你的硬件工程师测试下信号,说不定硬件的问题。
出了问题,软硬件一定要一起处理,千万不要踢皮球!!

tianzhiying 发表于 2012-3-28 17:59:54

ypradio 发表于 2012-3-28 17:55 static/image/common/back.gif
不一定是软件问题,叫你的硬件工程师测试下信号,说不定硬件的问题。
出了问题,软硬件一定要一起处理,千 ...

我想应该不是硬件的问题
因为这块板曾经有人写过程序
是可以的
而且运行很稳定
只不过那个人没有给我源代码就走了
所以我从头开始
今天弄了一整个下午
改了好多次都是数据读错
换了一块板也是一样

xue2003265029 发表于 2012-3-28 18:43:14

void WriteByte(uchar SendByte)
{
      unsigned char i;
      for(i=0; i<8; i++)
   {
            SCLK=0;
            Dat_1302=SendByte&0x01; //LSB first
            SendByte>>=1;
            SCLK=1;
             _nop_();
    }
    Dat_1302=1;

}

uchar ReadByte(void)
{
      unsigned char i,RecvByte=0;
         Dat_1302=1;   //将IO口置为输入状态
   for(i=0; i<8; i++)
         {
        SCLK=0;
        RecvByte>>=1;
        if( Dat_1302)
        RecvByte|=0x80;
        SCLK=1;
        _nop_();
         }
          return RecvByte;
}

Orpheus 发表于 2012-3-28 18:48:14

没记错的话,DS1302的数据读出来的是BCD码

anvy178 发表于 2012-3-28 18:52:44

uchar ReadByte(void)
{
      uchar i;
      for(i=8;i>0;i--)
      {
                ACC7=Dat_1302;//ds1302读数据的时候,第一个数据读取在发一个Byte命令后,在第八位的下降沿
                ACC=ACC>>1;
                CLK_1302=1;
                CLK_1302=0;//产生下降沿输出一位数据
                _nop_();_nop_();_nop_();
      }
      return(ACC);
}

这里有问题吧 ACC7ACC分别是什么啊 ?

YZDREAM8 发表于 2012-3-28 19:03:24

这东东网上参考程序一大把,你参考一下再自己走一遍就OK了,或者codevison里有现成的函数。这东东那些高手不屑一顾,只能自己吃点辛苦了。我前不久第一次用这个IC,仔细看下datasheet基本就OK了。

elecfun 发表于 2012-3-28 19:13:51

本帖最后由 elecfun 于 2012-3-28 19:16 编辑

试试我这个 DS1302.H


//DS1302功能函数
// 51 @ 12MHz
//elecfun @ 2010-11-6
#ifndef _DS1302_
#define _DS1302_

sbit DS1302_RST = P2^3;   //引脚定义,移植请修改此处
sbit DS1302_CLK = P2^4;   //
sbit DS1302_DAT = P2^5;   //

#define DS1302_SECOND   0x80
#define DS1302_MINUTE   0x82
#define DS1302_HOUR         0x84
#define DS1302_WEEK         0x8A
#define DS1302_DAY         0x86
#define DS1302_MONTH    0x88
#define DS1302_YEAR         0x8C

#define BCD2DEC(X)   (((X&0x70)>>4)*10 + (X&0x0F))   //用于将BCD码转成十进制的宏
#define DEC2BCD(X)   ((X/10)<<4 | (X%10))               //用于将十进制转成BCD码的宏

typedef struct __SYSTEMTIME__
{
    unsigned char Second;
    unsigned char Minute;
    unsigned char Hour;
    unsigned char Week;
    unsigned char Day;
    unsigned char Month;
    unsigned char Year;
}SYSTEMTIME;   //定义的时间类型



/*********************************************************************************************
函数名:DS1302写字节函数
调用:DS1302_WriteByte(uchar);
参数:需要写入的数据
返回值:
备注:内部函数
/**********************************************************************************************/
void DS1302_WriteByte(unsigned char wByte)
{
    unsigned char i=0;
    for (i=0; i<8; i++) {
      if (wByte & 0x01)
            DS1302_DAT = 1;
      else
            DS1302_DAT = 0;

      DS1302_CLK = 1;
      DS1302_CLK = 0;

      wByte >>= 1;
    }
}

/*********************************************************************************************
函数名:DS1302读字节函数
调用:uchar DS1302_ReadByte();
参数:
返回值:读出的一个字节
备注:内部函数
/**********************************************************************************************/
unsigned char DS1302_ReadByte(void)
{
    unsigned char i=0,result=0;
    DS1302_DAT = 1;
    for (i=0; i<8; i++) {
      if (DS1302_DAT == 1)
            result |= (0x01<<i);

      DS1302_CLK = 1;
      DS1302_CLK = 0;
    }
    DS1302_DAT = 0;
    return (result);
}

/*********************************************************************************************
函数名:DS1302读字节函数
调用:uchar DS1302_ReadData(uchar);
参数:指定的地址
返回值:读出指定地址的一个字节数据
备注:
/**********************************************************************************************/   
unsigned char DS1302_ReadData(unsigned char rAdd)
{
    unsigned char rDat=0;

    DS1302_RST = 0;
    DS1302_CLK = 0;
    DS1302_RST = 1;
    DS1302_WriteByte(rAdd);       //写地址
    rDat = DS1302_ReadByte();   //读数据
    DS1302_RST = 0;
    DS1302_CLK = 1;
    return (rDat);
}

/*********************************************************************************************
函数名:DS1302写字节函数
调用:DS1302_WriteData(uchar,uchar);
参数:指定的地址,指定的数据
返回值:
备注:
/**********************************************************************************************/   
void DS1302_WriteData(unsigned char wAdd, unsigned char wDat)
{
    DS1302_CLK = 0;
    DS1302_RST = 0;
    DS1302_RST = 1;
    DS1302_WriteByte(wAdd);       //写地址
    DS1302_WriteByte(wDat);       //写数据
    DS1302_RST = 0;
    DS1302_CLK = 1;
}

/*********************************************************************************************
函数名:DS1302读数据函数
调用:DS1302_GetTime_ALL(SYSTEMTIME);
参数:存储数据的SYSTEMTIME结构体
返回值:
备注:
/**********************************************************************************************/
void DS1302_GetTime_ALL(SYSTEMTIME *Time)
{
    unsigned char ReadValue;
    ReadValue = DS1302_ReadData(DS1302_SECOND+1);
    Time->Second = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
    ReadValue = DS1302_ReadData(DS1302_MINUTE+1);
    Time->Minute = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
    ReadValue = DS1302_ReadData(DS1302_HOUR+1);
    Time->Hour = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
    ReadValue = DS1302_ReadData(DS1302_DAY+1);
    Time->Day = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);   
    ReadValue = DS1302_ReadData(DS1302_WEEK+1);
    Time->Week = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
    ReadValue = DS1302_ReadData(DS1302_MONTH+1);
    Time->Month = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
    ReadValue = DS1302_ReadData(DS1302_YEAR+1);
    Time->Year = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);   
}

/*********************************************************************************************
函数名:DS1302设置时间数据函数
调用:DS1302_SetTime(uchar, uchar);
参数:存储数据的SYSTEMTIME结构体
返回值:
备注:
/**********************************************************************************************/
void DS1302_SetTime(unsigned char sADD, unsigned char sDAT)
{
    DS1302_WriteData(0x8e,0x00);          //关闭写保护

    DS1302_WriteData(sADD,DEC2BCD(sDAT));
    /*DS1302_WriteData(DS1302_MONTH,DEC2BCD(Time->Month));
    DS1302_WriteData(DS1302_WEEK,DEC2BCD(Time->Week));
    DS1302_WriteData(DS1302_DAY,DEC2BCD(Time->Day));
    DS1302_WriteData(DS1302_HOUR,DEC2BCD(Time->Hour)|Time_24_Hour);
    DS1302_WriteData(DS1302_MINUTE,DEC2BCD(Time->Minute));
    DS1302_WriteData(DS1302_SECOND,DEC2BCD(Time->Second)|Time_Start);*/
    DS1302_WriteData(0x8e,0x80);          //打开写保护   
}

/*********************************************************************************************
函数名:DS1302辅助设置日期函数
调用:unsigned char DS1302_GetTheDay();
参数:
返回值:当月总天数
备注:自动读取DS1302内部年、月,并计算当月总天数
/**********************************************************************************************/
unsigned char DS1302_GetTheDay(void)
{
    unsigned char tYear,tMonth;

    tYear = BCD2DEC(DS1302_ReadData(DS1302_YEAR+1));//当前年
    tMonth = BCD2DEC(DS1302_ReadData(DS1302_MONTH+1));//当前月
    switch (tMonth)
    {
    case 1:
    case 3:
    case 5:
    case 7:
    case 8:
    case 10:
    case 12:return 31;//1、3、5、7、8、10、12 月均为31天
    case 4:
    case 6:
    case 9:
    case 11:return 30;//4、6、9、11 月均为30天
    case 2:            
      if (tYear%4 == 0) //2月闰年为29天
            return 29;
      else
            return 28;//平年为28天
    default:    return 0;
    }
}

/*********************************************************************************************
函数名:DS1302初始化函数
调用:Init_DS1302();
参数:存储数据的SYSTEMTIME结构体
返回值:
备注:
/**********************************************************************************************/
void Init_DS1302(void)                        //-设置1302的初始时间(2007年1月1日00时00分00秒星期一)
{
    unsigned char i;
    if(DS1302_ReadData(0xc1) != 0x10){
      DS1302_WriteData(0x8e,0x00);      //允许写操作
      DS1302_WriteData(0x8c,0x10);      //年
      DS1302_WriteData(0x8a,0x04);      //星期
      DS1302_WriteData(0x88,0x07);      //月
      DS1302_WriteData(0x86,0x01);      //日
      DS1302_WriteData(0x84,0x12);      //小时
      DS1302_WriteData(0x82,0x00);      //分钟
      DS1302_WriteData(0x80,0x00);      //秒
      DS1302_WriteData(0x90,0xACC5);      //充电   
      DS1302_WriteData(0xc0,0x10);      //写入初始化标志RAM(第00个RAM位置)
      for(i=0; i<60; i+=2){            //清除闹钟RAM位为0
            DS1302_WriteData(0xC2+i,0x00);
      }
      DS1302_WriteData(0x8E,0x80);      //禁止写操作
    }
}
#endif

millwood0 发表于 2012-3-28 19:16:22

       ACC=dat;

anyone writing that kind of code has no business writing code at all.

caiden_chen 发表于 2012-3-28 19:47:37

检查读取过程有没有被中断 检查时序

BXAK 发表于 2012-3-28 20:07:02

caiden_chen 发表于 2012-3-28 19:47 static/image/common/back.gif
检查读取过程有没有被中断 检查时序

不同于DS18B20单总线,
DS1302的时序是不受中断影响的

caiden_chen 发表于 2012-3-28 21:55:50

BXAK 发表于 2012-3-28 20:07 static/image/common/back.gif
不同于DS18B20单总线,
DS1302的时序是不受中断影响的

其实我说的是两个问题. 一个是有没有中断, 另一个是时序对不对.
我的意思是:先检查中断 如果确认没有中断影响的话 则进一步检查时序(因为程序时序问题可能性小一些 而中断容易被忽视. 注意同样晶振频率 在不同51内核的单片机不一样,所以参考已有的程序时要注意,不要照搬)

另外, 你说"DS1302的时序是不受中断影响" 可能吗?实际上,读写DS1302的接口是软件实现的,当然会有影响. 比如说中断处理时间太长,或者外部中断没处理好,造成本应只中断一次的却反复中断多次,能没影响吗

BXAK 发表于 2012-3-28 23:43:11

caiden_chen 发表于 2012-3-28 21:55 static/image/common/back.gif
其实我说的是两个问题. 一个是有没有中断, 另一个是时序对不对.
我的意思是:先检查中断 如果确认没有中 ...

我做的一个带时钟遥控开关,
解码用定时器中断查询方式,定时器每256us频繁中断,
不关中断读取DS1302没发现什么问题

定时器每100us频繁中断解码时,读取DS1302也没发现什么问题

caiden_chen 发表于 2012-3-29 00:08:00

BXAK 发表于 2012-3-28 23:43 static/image/common/back.gif
我做的一个带时钟遥控开关,
解码用定时器中断查询方式,定时器每256us频繁中断,
不关中断读取DS1302没 ...

关这一点就下结论"DS1302的时序是不受中断影响"是不科学的
因为DS1302时序操作大概在几百纳秒到几微妙
256us对DS1302来说比较长了 而你中断处理时间短啊 影响不明显
所以中断处理不好就有影响 处理好当然就没影响 当调试不通过时是要考虑的

WangF 发表于 2012-3-29 01:19:10

值得看看

xue2003265029 发表于 2012-3-29 13:40:46

本帖最后由 xue2003265029 于 2012-3-29 13:48 编辑

DS1302是三线SPI接口方式,CE、SCL、SDA
由于SPI是三线SPI口,SDA为双向脚,单片机或者DS1302都会向SDA上发送数据。
因此,必须在程序上做好相应的时序处理,使两者不会同时向SDA发送数据。
(如单片机向SDA发送0,而DS1302向SDA发送1,这样DS1302的i/O的输出电流会变大,可能会烧坏DS1302)

最底层与DS1302时序相关的程序有两个:
void WriteByte(unsignedchar SendByte),主要完成向DS1302发送一个字节,上升沿发送将数据发送到SDA,
unsignedchar ReadByte(void),主要完成从DS1302接受一个字节,下降沿读取SDA,

下面的时序图为DS1302 Single Read 时序,它包含了WriteByte()和ReadByte()。



WriteByte()发送了第8个上升沿后,已经完成了向DS1302的字节发送,就不要发送下降沿了(此下降沿为第8个下降沿,DS1302会将数据放在SDA线了,但此时单片机在第八个上升沿向SDA发送的数据还有效,两者在SDA上就会“打架”了)。
等到了ReadByte()再发送第8个下降沿,这样第8个上升沿到第8个下降沿之间时间空隙可以作为DS1302响应单片机写命令的缓冲时间。
void WriteByte(unsigned char SendByte)
{
        unsigned char i;
        for(i=0; i<8; i++)
                   {
                SCLK=0;
                SDA=SendByte&0x01; //LSB first ,
                SendByte>>=1; //利用移位操作作为延迟
                SCLK=1;//上升沿发送
                _nop_();
        }
               //循环完成后,刚好发送第8*N(N=1,2,3……)个上升沿,此时SCLK处于高电平。
         SDA=1; //数据发送完毕,单片机IO变为接收状态,这样不会与DS1302在SDA上产生冲突了。
}

unsigned char ReadByte(void)
{
        unsigned char i,RecvByte=0;
        SDA=1;   //将IO口置为输入状态,51单片机特有
         //   进入循环前SCL已经发送了第8*N(N=1,2,3……)个上升沿,进入循环后第一次SCLK=0,将产生第8*N个下降沿
        for(i=0; i<8; i++)
               {
                SCLK=0;//下降沿接收数据
                RecvByte>>=1;//利用移位操作作为延迟
                if(SDA) //下降沿延迟一段时间,再读取SDA上的数据
                RecvByte|=0x80;
                SCLK=1;
                _nop_();
        }
        return RecvByte;
}
当然,8楼elefun的程序也不会出现问题。
只不过在DS1302_WriteByte()函数中,for循环在最后的SLK =0,即单片机已经发送了SCLK的第8个下降沿,
如果发送的是Single Read 命令的话,那么DS1302在该下降沿时已经开始往SDA数据上发送数据了。为什么不会出现问题呢。
这是因为DS1302的命令字节的最高位恰好为1(DS1302先发送低位),这样在SCL第8个时钟上升沿时,
51单片机恰好往SDA数据线上发送1(“无线插柳”将单片机IO口变为输入状态)
不过这也只是针对51这种准双向口而言的。如果换成STM32、AVR等只能通过程序将IO口配置输入或输出的MCU而言,可能就没那么乐观了。
针对DS1302_WriteByte()向elefun提两个建议。   
(1) 因为51的IO口,从0变1会比从1变0慢的多,IO线越长的话,上升沿会变的更加缓慢
       如果DS1302与51单片机的连线SCLK及SDA很长的话,SCLK的上升沿就会变得更加缓慢
    所以在
            DS1302_CLK = 1;
            DS1302_CLK = 0;
         这两个语句之间应该加延迟了,增加SCL高电平时间(可以将移位指令放在wByte >>= 1中间做延迟)。
(2) 对于51单片机SPI发送数据,上升沿前应该将数据保持稳定,因此在
if (wByte & 0x01)
            DS1302_DAT = 1;
      else
            DS1302_DAT = 0;

      DS1302_CLK = 1;
之间加_nop_()延迟,使SCLK上升沿时变为SDA的数据稳定。

void DS1302_WriteByte(unsigned char wByte)
{
    unsigned char i=0;
    for (i=0; i<8; i++) {
      if (wByte & 0x01)
            DS1302_DAT = 1;
      else
            DS1302_DAT = 0;

      DS1302_CLK = 1;
      DS1302_CLK = 0;

      wByte >>= 1;
    }
}

caiden_chen和BXAK 争论很精彩哈,其实大家理解可能都一样,只是侧重点不同。
DS1302时钟芯片,1s之内读1次实时时钟数据应该没问题的,(我的是定时0.5s读一次)
这样的话,相比其他外设,单片机访问DS1302算是最低了。(就连按键还要20ms读一次状态呢)
如果单片机访问DS1302都要害怕中断的话,那么访问其他外设不是更怕中断了么。所以我个人感觉没必要考虑中断对DS1302的影响。
如果caiden_chen坚持的话,可以考虑在WriteByte()和ReadByte()的程序前加#pragma disable ,或者在读实时时钟数据时,关闭中断,读完数据后再打开中断就是了。

针对楼主的程序,本意是将利用51单片机寄存器A来发送数据。这样写程序可以是程序变得简短,速度也会变快。
但该C语言代码只能在51单片机中执行,不能移植到其他MCU上。void WriteByte(uchar dat)
{
      uchar i;
      ACC=dat;
      for(i=8;i>0;i--)
      {
                Dat_1302=ACC0;
                CLK_1302=1;      
                _nop_();_nop_();_nop_();
                CLK_1302=0;      //产生上升沿输入数据
         ACC=ACC>>1;
      }      
}
写成汇编后变为
WriteByte:   MOV A, R7                  ;R7传递dat
SPI_CYC:MOV R7, 8                   ;R7置循环次数
            RRC A                        ;带CY右循环移位,将ACC.0移位到C中
            MOV Dat_1302 , C      ;将C发送到Dat_1302上
            SETBCLK_1302
                      NOP
                      NOP
                      NOP
                      CLRCLK_1302
                      DJNZ R7, SPI_CYC
                      RET // 函数返回。
在用51C语言中直接利用寄存器A(或ACC),应注意不要在for循环该变ACC的值。
这里for循环必须用i--而不用i++,这是因为如果在循环内执行i++,会用ACC来执行i的加法操作,使ACC寄存器的值发生意想不到的改变。

最好不要在C语言中用直接利用寄存器的操作,无论是处于可移植性和可靠性方面考虑。
就像9楼millwood0大牛说的
anyone writing that kind of code has no business writing code at all.

就是这些了,之所以写这么多,就是希望新手能对DS1302底层驱动有进一度的认识。

dreamsky112 发表于 2012-3-29 15:06:54

有木有用cc2430做的

tianzhiying 发表于 2012-3-29 15:22:12

anvy178 发表于 2012-3-28 18:52 static/image/common/back.gif
uchar ReadByte(void)
{
      uchar i;


前面定义了
sbit ACC7=ACC^7;
sbit ACC0=ACC^0;

liuxiaoyun1210 发表于 2012-3-29 16:09:52

{:smile:}这个时序网上应该很多的呀!

tianzhiying 发表于 2012-3-29 16:15:07

别人都说可以的程序
放到我这里就不行了

ybs1902 发表于 2012-3-31 00:13:16

我也遇到过 同样的程序 运行在学习板上OK。运行在洞洞板上不行--理论上硬件问题。没有查出来。后来到了拿到了美信的样片,换上就OK了。 也有可能是国产的1302的问题。---从没有听说过的问题 。

millwood0 发表于 2012-3-31 03:34:34

本帖最后由 millwood0 于 2012-3-31 03:34 编辑

最好不要在C语言中用直接利用寄存器的操作,无论是处于可移植性和可靠性方面考虑。

that's just basic, embedded programming 101. because you never know, without looking at the assembly generated by the compiler, if the compiler is also utilizing ACC. if it is, writing to and reading from ACC can cause unpredictable behaviors that are impossible to debug.

关这一点就下结论"DS1302的时序是不受中断影响"是不科学的

I have not looked the datasheet for ds1302 but spi allows the master to hold the clock pulse indefinitely. and it would surprise me if DS1302 is an exception to that.

if so, the code doesn't have to factor in interrupt-caused delays.

zyyn123 发表于 2012-3-31 07:10:11

楼主还是看看时序上是不是出了点问题,记得原来自已做的时候也出现过这样的问题,后来从网上找个程序参考了下,问题就解决了.

jjj2012 发表于 2012-3-31 09:14:38

那个h文件啊不错

yihui184 发表于 2012-11-8 00:45:21

ypradio 发表于 2012-3-28 17:55 static/image/common/back.gif
不一定是软件问题,叫你的硬件工程师测试下信号,说不定硬件的问题。
出了问题,软硬件一定要一起处理,千 ...

说得太对!对!
页: [1]
查看完整版本: 调试DS1302时遇到的问题