hujj 发表于 2020-9-21 14:53

【CH579M-R1】+求助:模拟I2C读取数据不成功(已解决)

本帖最后由 hujj 于 2020-9-22 15:39 编辑

<p>&nbsp; &nbsp; 继LCD5110显示屏驱动成功后,上周开始试图驱动DS1307日历模块,下图是从淘宝购买的模块:</p>

<p></p>

<p>&nbsp;</p>

<p>&nbsp; &nbsp; 这个模块是通过I2C总线进行通讯的,模块上还附带一个24C32芯片,同时还留有温湿度传感器安装位置。下面是我测试过程的图片:</p>

<p></p>

<p>&nbsp; &nbsp; 查找CH579的资料,没有找到硬件I2C的叙述或范例,于是只好试图设置软件I2C,因为LCD显示屏与I2C总线上的设备不会同时使用,所以我用利用了驱动LCD5110的CLK和DIN引脚,重复用于I2C的SCK和SDA,i2c.h文件如下:</p>

<pre>
<code>
#ifndef I2C_H
#define I2C_H

#include "CH57x_common.h"
#include "CH57x_gpio.h"

/*************************** 宏定义 *****************************/

#define I2C_SCL         GPIO_Pin_5      //时钟信号脚⑤
#define I2C_SDA         GPIO_Pin_3      //数据输入④

#define SCL_1() GPIOB_SetBits(I2C_SCL)    //写I2C时钟端口
#define SCL_0() GPIOB_ResetBits(I2C_SCL)

#define SDA_1() GPIOB_SetBits(I2C_SDA)    //写I2C数据端口
#define SDA_0() GPIOB_ResetBits(I2C_SDA)
#define SDA_X() GPIOB_ReadPortPin(GPIO_Pin_3)//读I2C数据端口状态

#define SDA_OUT() GPIOB_ModeCfg(I2C_SDA, GPIO_ModeOut_PP_5mA) //SDA推挽输出模式
#define SDA_IN()GPIOB_ModeCfg(I2C_SDA, GPIO_ModeIN_PU);   //SDA上拉输入模式


/***************************函数声明*****************************/

void delay_us(uint8_t us);
void SI2C_DAT_Dir(uint8_t dir); //设置SDA的读(1)写(0)方向
void SI2C_Start(void);          //开始I2C通讯
void SI2C_Stop(void);         //停止I2C通讯
void SI2C_Send(uint8_t dat);    //向I2C总线发送一个字节
uint8_t SI2C_Receive(void);   //从I2C总结接收一个字节
void SI2CDoAck(void);         //发出应答信号
void SI2CNoAck(void);         //发出无应答信号
uint8_t SI2CIsAck(void);      //检测从机应答信号

#endif/* I2C_H */
</code></pre>

<p>&nbsp;</p>

<p>&nbsp; &nbsp; i2c.c代码如下:</p>

<pre>
<code>

#include "CH57x_common.h"
#include "i2c.h"
//#include &lt;stdio.h&gt;

/***************************************************
函数功能:微秒延时
入口参数:延时的微秒数
***************************************************/
void delay_us(uint8_t us)
{
    uint8_t x,y;
    for(x=us;x&gt;0;x--)   
    for(y=4;y&gt;0;y--);
}


/******************************************************************************************************************************************
* 函数名称: SI2C_DAT_Dir()
* 功能说明:        设置I2C_SDA引脚的读或写
* 输    入: 0=读入,1=写
* 输    出: 无
******************************************************************************************************************************************/
void SI2C_DAT_Dir(uint8_t dir)
{
if(dir)
        GPIOB_ModeCfg(I2C_SDA, GPIO_ModeOut_PP_5mA); //SDA推挽输出模式
else
        GPIOB_ModeCfg(I2C_SDA, GPIO_ModeIN_PU);      //SDA上拉输入模式
}


/******************************************************************************************************************************************
* 函数名称: I2C_Start()
* 功能说明:        产生I2C传输的Start信号
* 输    入: 无
* 输    出: 无
******************************************************************************************************************************************/
void SI2C_Start(void)
{
    SI2C_DAT_Dir(1);   //SDA输出
    SDA_1();
    SCL_1();         //scl = 1;
        delay_us(5);
        SDA_0();         //sda = 0;        scl为高时sda的下降沿表示“起始”
        delay_us(3);
        SCL_0();         //scl = 0;钳住I2C总线,准备发送或接收数据 START:when CLK is high,DATA change form high to low
}

/******************************************************************************************************************************************
* 函数名称:        I2C_Stop()
* 功能说明:        产生I2C传输的Stop信号
* 输    入: 无
* 输    出: 无
******************************************************************************************************************************************/
void SI2C_Stop(void)
{
    SI2C_DAT_Dir(1);
    SCL_0();             // scl = 0;
    SDA_0();             // STOP:when CLK is high DATA change form low to high
    delay_us(5);
    SCL_1();             // scl = 1;
    delay_us(5);       
    SDA_1();             // sda = 1;        sclk为高时sdat的上升沿表示“停止”
}


/******************************************************************************************************************************************
* 函数名称: I2C_Send()
* 功能说明:        向IIC总线发送一个字节的数据
* 输    入: byte dat         要发送的数据
* 输    出: 无
******************************************************************************************************************************************/
void SI2C_Send(uint8_t dat)
{
        uint8_t i;
        SI2C_DAT_Dir(1);
        SCL_0();               //拉低时钟开始数据传输
        for(i=0;i&lt;8;i++)
        {
//                if(((dat&amp;0x80)&gt;&gt;7) == 1) SDA_1;//准备好SDA数据
      if((dat&gt;&gt;(7-i))&amp;0x01) SDA_1();
                else SDA_0();
//                dat&gt;&gt;=1;
                delay_us(4);
                SCL_1();                         //拉高时钟等待从设备读取数据
                delay_us(5);
                SCL_0();                         //拉低时钟准备下一位数据
//                delay_us(1);
        }
}


/******************************************************************************************************************************************
* 函数名称:        I2C_Receive()
* 功能说明:        从IIC总线接收一个字节的数据
* 输    入: 无
* 输    出: byte                从IIC总线上接收到得数据
* 注意事项: 无
******************************************************************************************************************************************/
uint8_t SI2C_Receive(void)
{
        uint8_t i,dat;
        SI2C_DAT_Dir(0);      //设置为输入       
        for(i=0;i&lt;8;i++)
        {
                SCL_0();
                delay_us(5);
                SCL_1();
                dat&lt;&lt;=1;
      if(1 == SDA_X())
            dat|=0x01;
                delay_us(4);
        }
        return dat;

/*
        ui08 i = 0;
        byte d = 0;
        byte dat = 0;

        for(i=0;i&lt;8;i++)
    {
                scl = 0;
                DELAY();
                sda = 1;                //本语句必须有:于IIC,是释放SDA线;于51单片机,则是由于51的IO不是真双向口,在读之前必须写0
              DELAY();

              scl = 1;
                DELAY();
                d = sda;
              DELAY();

                dat |= (d&lt;&lt;(7-i));
    }
    return dat;
*/
}

/******************************************************************************************************************************************
* 函数名称: I2CDoAck()
* 功能说明:        在应答位位置产生应答,从而继续连续传输
* 输    入: 无
* 输    出: 无
******************************************************************************************************************************************/
void SI2CDoAck(void)
{
    SCL_0();
        SI2C_DAT_Dir(1);
    SDA_0();            //sda = 0;        /拉低数据线,即给于应答
    delay_us(3);
    SCL_1();            //scl = 1;
           delay_us(5);
    SCL_0();            //scl = 0;
}

/******************************************************************************************************************************************
* 函数名称: I2CNoAck()
* 功能说明:        在应答位位置不产生应答,从而终止连续传输
* 输    入: 无
* 输    出: 无
******************************************************************************************************************************************/
void SI2CNoAck(void)
{
    SCL_0();
        SI2C_DAT_Dir(1);
    SDA_1();            // sda = 1;        不拉低数据线,即不给于应答
    delay_us(3);
    SCL_1();            // scl = 1;
    delay_us(5);
    SCL_0();            // scl = 0;
}

/******************************************************************************************************************************************
* 函数名称: I2CIsAck()
* 功能说明:        检测从机应答位
* 输    入: 无
* 输    出: uint8_t        0=ACK_OK 从机产生了应答;1=ACK_NO 从机没有产生应答
******************************************************************************************************************************************/
uint8_t SI2CIsAck(void)
{
    uint8_t i;
    SI2C_DAT_Dir(1);
    SDA_1();            // sda = 1; 释放数据线
    delay_us(3);
    SI2C_DAT_Dir(0);
    SCL_1();            // scl = 1;
    delay_us(3);
        while(SDA_X()){
                i++;
                if(i&gt;250){
                        SI2C_Stop();//数据线未被拉低,即未收到应答
                        return 1;
                }
        }
    SCL_0();
    return 0;
}


</code></pre>

<p>&nbsp;</p>

<p>&nbsp; &nbsp; DS1307模块的数据读写及日期转换的代码如下:</p>

<pre>
<code>
#include &quot;I2C.h&quot;
#include &quot;ds1307.h&quot;
#include &quot;lcd_5110.h&quot;

extern uint16_t year;            //年
extern uint8_t week,month,day,hour,minute,second,DS_Buff;//月日时分秒


/******************************************************************************************
* 函数名称: DS1307_Read()
* 功能说明: 从DS1307地址addr开始获取size个字节的数据,获取的数据存储在全局变量DS_Buff中
* 输    入: uint8_t addr        获取数据从addr开始
*                        uint8_t size        要获取的数据个数(1~8)
* 输    出: ui08    0=RET_OK 成功从DS1307获取数据1=RET_ERR 从DS1307获取数据过程中出现错误
******************************************************************************************/
uint8_t DS1307_Read(uint8_t addr,uint8_t size)
{
    uint8_t i = 0;
//    SCL_1();                        //拉起时钟引脚,准备发出开始信号

    SI2C_Start();                   //产生起始信号
    SI2C_Send(DS1307_ADDR);         //发送DS1307芯片地址及读写位,0表示写
    if(1 == SI2CIsAck())            //检测DS1307是否有响应
    {
      SI2C_Stop();                //产生停止信号
                return 2;
    }
    SI2C_Send(addr);                //发送读取数据的起始地址
    if(1 == SI2CIsAck())            //检测DS1307是否有响应
    {
      SI2C_Stop();                    //产生停止信号
      return 3;
    }

    SI2C_Start();                   //产生Repeated Start
    SI2C_Send(DS1307_ADDR|1);       //发送DS1307芯片地址及读写位,1表示读
    if(1 == SI2CIsAck())            //检测DS1307是否有响应
    {
      SI2C_Stop();                //产生停止信号
      return 4;
    }

    for(i=0;i&lt;size;i++)                    //从addr处读取size个字节的数据
    {
      DS_Buff<i> = SI2C_Receive();
      SI2CDoAck();
    }

    SI2C_Receive();               //DS1307要求必须使用NOAck来结束数据读取
    SI2CNoAck();                  //DS1307要求必须使用NOAck来结束数据读取

    SI2C_Stop();                  //产生停止信号
        SDA_OUT();                      //将SDA引脚恢复为输出模式
       
        return 0;
}


/**********************************************************************************************
* 函数名称:        DS1307_Write()
* 功能说明: 向DS1307地址addr开始写入size个字节的数据,将要写入的数据存储在全局变量DS_Buff中
* 输    入: uint8_t addr        数据被写入从addr开始的地址处
*                        uint8_t size        要设置的数据个数(1~8)
* 输    出: uint8_t                0=RET_OK 成功向DS1307设置数据1=RET_ERR 向DS1307设置数据过程中出现错误
**********************************************************************************************/
uint8_t DS1307_Write(uint8_t addr,uint8_t size)
{
    uint8_t i = 0;
//    SCL_1();                        //拉起时钟引脚,准备发出开始信号
       
    SI2C_Start();                   //产生起始信号
    SI2C_Send(DS1307_ADDR|0);       //发送DS1307芯片地址及读写位,0表示写
    if(1 == SI2CIsAck())            //检测DS1307是否有响应
    {
      SI2C_Stop();                //产生停止信号
      return 1;
        }

    SI2C_Send(addr);                //发送数据要写入的地址
    if(1 == SI2CIsAck())            //检测DS1307是否有响应
    {
      SI2C_Stop();                //产生停止信号
      return 2;
    }

    for(i=0;size&gt;0;i++,size--)
    {
      SI2C_Send(DS_Buff<i>);
      if(1 == SI2CIsAck())      //检测DS1307是否有响应
      {
            SI2C_Stop();            //产生停止信号
            return 3;
      }
    }

    SI2C_Stop();                  //产生停止信号
        SDA_OUT();                      //将SDA引脚恢复为输出模式
       
    return 0;
}


/******************************************************************************************
* 函数名称:        DS1307_Init()
* 功能说明: 用当前日期(yesr,month,day,hour,minute)初始化DS1307
* 输    入: 无
* 输    出: uint8_t        0=RET_OK 初始化成功        1=RET_ERR 初始化出错
******************************************************************************************/
uint8_t DS1307_Init(void)
{
    uint8_t temp;

        temp = DS1307_Read(0,1);
       
        LCD_write_value(0,3,3,0,0,temp);//监测读取值
       
        if(temp&gt;127){

      year = 2020;
                month = 9;
                day = 15;
                week = 3;
                hour = 18;
                minute = 10;
               
      DS_Buff = 0;                  //秒
      temp = ((minute/10)&lt;&lt;4|(minute%10));
      DS_Buff = temp;               //分
      temp = ((hour/10)&lt;&lt;4|(hour%10));
      DS_Buff = temp;               //时

      DS_Buff = week;               //星期

      temp = ((day/10)&lt;&lt;4|(day%10));
      DS_Buff = temp;               //日
      temp = ((month/10)&lt;&lt;4|(month%10));
          DS_Buff = temp;               //月
      temp = ((year%100)/10&lt;&lt;4|(year%10));
      DS_Buff = temp;               //年
       
      DS_Buff = 32;               //0010 0000 = 允许按1Hz输出方波

      return DS1307_Write(0,8);
    }
        else
                return 4;
}



/******************************************************************************************
* 函数名称:        DS1307_read_date()
* 功能说明: 读取DS1307日期时间数据
* 输    入: 无
* 输    出: 无
******************************************************************************************/
void DS1307_read_date(void)
{
    uint8_t info;
    info = DS1307_Read(0,7);                                 //读取前7个字节数据
//    LCD_write_value(30,3,3,0,0,info);
       
    second = ((DS_Buff&amp;0x70)&gt;&gt;4)*10 + (DS_Buff&amp;0x0F);//秒,屏蔽秒的第7位的标志
        minute = ((DS_Buff&amp;0x70)&gt;&gt;4)*10 + (DS_Buff&amp;0x0F);//分(取低7位)
        hour = ((DS_Buff&amp;0x10)&gt;&gt;4)*10 + (DS_Buff&amp;0x0F);//时(取低5位)
        week = (DS_Buff&amp;0x07);                              //周(取低3位)
    day = ((DS_Buff&amp;0x30)&gt;&gt;4)*10 + (DS_Buff&amp;0x0F);   //日(取低6位)
    month = ((DS_Buff&amp;0x10)&gt;&gt;4)*10 + (DS_Buff&amp;0x0F); //月(取低5位)
        year = 2000 + (DS_Buff&gt;&gt;4)*10 + (DS_Buff&amp;0x0F);//年
   
        LCD_write_value(0,3,3,0,0,DS_Buff);
        LCD_write_value(25,3,3,0,0,DS_Buff);
        LCD_write_value(50,3,3,0,0,DS_Buff);
        LCD_write_value(0,4,3,0,0,DS_Buff);
        LCD_write_value(25,4,3,0,0,DS_Buff);
        LCD_write_value(50,4,3,0,0,DS_Buff);
}


/******************************************************************************************
* 函数名称:        DS1307_write_date()
* 功能说明: 读取DS1307日期时间数据
* 输    入: 无
* 输    出: 无
******************************************************************************************/
void DS1307_write_date(void)
{
    uint8_t temp;
       
    DS_Buff = 0;                      //秒
    temp = ((minute/10)&lt;&lt;4|(minute%10));
    DS_Buff = temp;                   //分
    temp = ((hour/10)&lt;&lt;4|(hour%10));
    DS_Buff = temp;                   //时

    DS_Buff = week;                   //星期
    temp = ((day/10)&lt;&lt;4|(day%10));
    DS_Buff = temp;                   //日
    temp = ((month/10)&lt;&lt;4|(month%10));
    DS_Buff = temp;                   //月
    temp = ((year%100)/10&lt;&lt;4|(year%10));
    DS_Buff = temp;                   //年
       
    DS1307_Write(0,7);                   //写入前7个字节数据
//    DS1307_I2C_Write(0,7);               //写入前7个字节数据
}
</i></i></code></pre>

<p><i><i>&nbsp;</i></i></p>

<p><i><i>&nbsp; &nbsp; 上面的这些代码都是在其他程序中正常使用的,之前移植时通常只需要调整一下读写的时序就行,可是在这次移植却不那么顺利,调试了几天都毫无进展,就是读取不到数据。下面是逻辑分析仪的截图:</i></i></p>

<p><i><i></i></i></p>

<p><i><i>&nbsp;</i></i></p>

<p><i><i>&nbsp; &nbsp; 从逻辑分析仪抓取的时序图上看,I2C的读写命令都正确发出,I2C器件也作出了正常回应,发出了正确的数据,但读SDA引脚的代码却始终为0,也就说:I2C的写过程正常,I2C器件也正常回应,相应的数据也反映在SDA引脚上了,但读取SDA引脚电平却始终为低电平。问题应该是出在读取引脚电平的代码或者是设置引脚读写模式的代码上。我的设置SDA读写模式的宏定义如下:</i></i></p>

<p><i><i>#define SDA_1() GPIOB_SetBits(I2C_SDA) &nbsp; &nbsp;//写I2C数据端口<br />
#define SDA_0() GPIOB_ResetBits(I2C_SDA)<br />
#define SDA_X() GPIOB_ReadPortPin(I2C_SDA)//读I2C数据端口状态</i></i></p>

<p><i><i>&nbsp;</i></i></p>

<p><i><i>#define SDA_OUT()&nbsp; GPIOB_ModeCfg(I2C_SDA, GPIO_ModeOut_PP_5mA) //SDA推挽输出模式&nbsp;<br />
#define SDA_IN()&nbsp; &nbsp;GPIOB_ModeCfg(I2C_SDA, GPIO_ModeIN_PU); &nbsp; &nbsp; //SDA输入模式<br />
&nbsp;</i></i></p>

<p><i><i>&nbsp; &nbsp; 这段宏定义应该不会有问题,但实际测试时就行不通。在测试过程中,我更换了空闲的引脚来作SDA也没成功,将变换引脚模式的宏定义改为在函数中用代码设置也不行。反复折腾了一个多星期都没有成功,以致于我有点崩溃了。我所能想到的办法就是准备外部添加上拉电阻再测试,但模块上本身已经有上拉电阻,再加上拉电阻的意义不大,不过我也想不到其他办法了,看看诸位能帮助分析原因,提出建议。在此先谢谢大家!</i></i></p>

<p><i><i>&nbsp;</i></i></p>

hujj 发表于 2020-9-21 15:35

<p>&nbsp; &nbsp; 刚才我将SDA引脚的宏定义改成浮空输入进行测试,结果仍然照旧没有读到数据,用逻辑分析仪抓取明明有数据。</p>

<p>&nbsp; &nbsp; 宏定义代码如下:</p>

<p>#define SDA_OUT() GPIOB_ModeCfg(I2C_SDA, GPIO_ModeOut_PP_5mA) //SDA推挽输出模式&nbsp;<br />
//#define SDA_IN() &nbsp;GPIOB_ModeCfg(I2C_SDA, GPIO_ModeIN_PU) &nbsp; &nbsp; &nbsp;//SDA上拉输入模式<br />
#define SDA_IN() &nbsp;GPIOB_ModeCfg(I2C_SDA, GPIO_ModeIN_Floating)//SDA浮空输入模式<br />
&nbsp;</p>

freebsder 发表于 2020-9-21 20:29

<p>不至于吧,器件手册上不讲I2C的?</p>

hujj 发表于 2020-9-22 07:37

freebsder 发表于 2020-9-21 20:29
不至于吧,器件手册上不讲I2C的?

<p>也许是我没看到吧。</p>

hujj 发表于 2020-9-22 15:47

<p>&nbsp; &nbsp; 经过反复测试,终于找到了问题所在,还是判断I2C_SDA引脚电平的代码问题,我在其他项目中一直都是用 if(1 == SDA_X) 来判断,没有出现过问题,但在此项目却不行,将其修改成 if(SDA_X) 再测试就成功了。没想到这个小问题却花费了我一周多的时间才解决。</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

okhxyyo 发表于 2020-9-27 10:43

<p><a href="https://bbs.eeworld.com.cn/thread-1140005-1-1.html" target="_blank">沁恒CH579M-R1开发板测评</a></p>

<p>汇总贴:<a href="https://bbs.eeworld.com.cn/thread-1140005-1-1.html">https://bbs.eeworld.com.cn/thread-1140005-1-1.html</a></p>

damiaa 发表于 2020-9-27 10:57

hujj 发表于 2020-9-22 15:47
&nbsp; &nbsp; 经过反复测试,终于找到了问题所在,还是判断I2C_SDA引脚电平的代码问题,我在其他项目中一 ...

<p>有时候就是细节折腾人。<img height="48" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/congra.gif" width="48" /></p>

kingsleych 发表于 2020-9-27 11:31

<p>I2C 往往要配置成开漏,可以参考下这个例子,</p>

<p>https://www.cnblogs.com/iot-fan/p/13473877.html</p>

littleshrimp 发表于 2020-10-23 21:45

hujj 发表于 2020-9-22 15:47
&nbsp; &nbsp; 经过反复测试,终于找到了问题所在,还是判断I2C_SDA引脚电平的代码问题,我在其他项目中一 ...

<p>打印一下SDA_X看看,难道能输出大于1的值?</p>

hujj 发表于 2020-10-24 08:50

littleshrimp 发表于 2020-10-23 21:45
打印一下SDA_X看看,难道能输出大于1的值?

<p>当时确实将这个判断修改了就能正确运行,但后来我又尝试将判断改回if(1 == SDA_X),竟然也能正确运行,具体原因不清楚,也没有去深究。</p>

littleshrimp 发表于 2020-10-24 09:16

hujj 发表于 2020-10-24 08:50
当时确实将这个判断修改了就能正确运行,但后来我又尝试将判断改回if(1 == SDA_X),竟然也能正确运行,具 ...

<p>看来真正的问题不在这儿</p>

hujj 发表于 2020-10-24 10:07

littleshrimp 发表于 2020-10-24 09:16
看来真正的问题不在这儿

<p>&nbsp; &nbsp; 我也挺纳闷的,可当初确实穷尽办法都不行,最后中有尝试修改这个判断,再测试就通过了,但这个似乎不合逻辑。</p>
页: [1]
查看完整版本: 【CH579M-R1】+求助:模拟I2C读取数据不成功(已解决)