hujj 发表于 2020-11-20 17:46

【GD32F307E-START】+软件I2C驱动DS1307日历模块

<p>&nbsp; &nbsp; 经过反复调试,今天总算成功驱动了DS1307日历模块,可以对日历模块进行读写操作了。</p>

<p>&nbsp; &nbsp; DS1307模块是通过I2C进行通讯的,开始准备使用硬件I2C进行驱动的,但尝试了多次均未成功,不得已改用软件驱动,但软件驱动也不顺利,前后花了一周多的时间来调试,为此还发帖求助。</p>

<p>&nbsp; &nbsp; 软件I2C的代码是从其他项目里移植过来的,首先就是通过逻辑学分析仪抓取的时序来调整时钟的延时,这一步比较容易,时钟的翻转为人微秒左右:</p>

<p></p>

<p>&nbsp; &nbsp; 时钟频率调整好了,但器件没有响应,得不到ACK:</p>

<p></p>

<p>&nbsp; &nbsp; 初步分析可能是数据引脚SDA的方向设置问题,一连几天反复修改引脚方向设置的代码都不见效果,更换其他引脚也无济于事,于是便暂停了I2C通讯的测试,先测试按键的轮询方式和中断方式。今天再恢复测试,发现器件竟然有回应了,基本上能够读取到数据:</p>

<p></p>

<p>&nbsp; &nbsp; 但数据引脚好象存在干扰,出现异常跳变现象,下图中黄色圈内就是,原因尚不得知:</p>

<p></p>

<p>&nbsp; &nbsp; 这些不规则的跳变现象尚未找出规律,还有待于进一步调试解决:</p>

<p></p>

<p>&nbsp; &nbsp; 跳变有时会引起误读,象下面的时序图,跳变产生了一个STOP信号:</p>

<p></p>

<p>&nbsp; &nbsp; 但不管如果,总算是基本上读取到的数据,还需要继续调试。下面是调试过程的照片:</p>

<p></p>

<p>&nbsp; &nbsp; 这是显示屏的特定镜头:</p>

<p></p>

<p>&nbsp; &nbsp; 这是测试效果的动画:</p>

<p></p>

<p>&nbsp;</p>

hujj 发表于 2020-11-20 17:51

<p>&nbsp; &nbsp; 这是SI2C的头文件:</p>

<pre>
<code class="language-cs">
#ifndef I2C_H
#define I2C_H

#include "gd32f30x.h"

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

#define SCL_PIN             GPIO_PIN_12
#define SCL_GPIO_PORT       GPIOD
#define SCL_GPIO_CLK      RCU_GPIOD
#define SDA_PIN             GPIO_PIN_14
#define SDA_GPIO_PORT       GPIOD
#define SDA_GPIO_CLK      RCU_GPIOD


#define SCL_1() {gpio_bit_set(SCL_GPIO_PORT, SCL_PIN);}   //写I2C时钟端口
#define SCL_0() {gpio_bit_reset(SCL_GPIO_PORT, SCL_PIN);}

#define SDA_1() {gpio_bit_set(SDA_GPIO_PORT, SDA_PIN);}   //写I2C数据端口
#define SDA_0() {gpio_bit_reset(SDA_GPIO_PORT, SDA_PIN);}
#define SDA_X() gpio_input_bit_get(SDA_GPIO_PORT, SDA_PIN)//读I2C数据端口状态

//#define SDA_OUT(){gpio_init(SDA_GPIO_PORT, GPIO_MODE_OUT_PP,GPIO_OSPEED_50MHZ,SDA_PIN);}
//#define SDA_IN()   {gpio_init(SDA_GPIO_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, SDA_PIN);}
#define SDA_OUT() gpio_init(SDA_GPIO_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SDA_PIN) //SDA推挽输出模式
#define SDA_IN()gpio_init(SDA_GPIO_PORT, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, SDA_PIN); //SDA浮空输入模式

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

void delay_us(uint8_t us);
void SI2C_Init(void);

uint8_t SI2C_8bitByteWrite(uint8_t I2C_addr,uint8_t addr,uint8_t data);               //向指定的器件及地址写入1个字节
uint8_t SI2C_8bitByteRead(uint8_t I2C_addr,uint8_t addr);                           //从指定的器件及地址读取1个字节
uint8_t SI2C_8bitBuffWrite(uint8_t I2C_addr,uint8_t addr,uint8_t size,uint8_t *buf);//向指定的器件及地址开始写入多个字节
uint8_t SI2C_8bitBuffRead(uint8_t I2C_addr,uint8_t addr,uint8_t size,uint8_t *buf);   //从指定的器件及地址开始读出多个字节
uint8_t SI2C_16bitBuffWrite(uint8_t I2C_addr,uint16_t addr,uint8_t size,uint8_t *buf);//写多个数据到16位地址的I2C设备
uint8_t SI2C_16bitBuffRead(uint8_t I2C_addr,uint16_t addr,uint8_t size,uint8_t *buf); //读取16位地址的多个数据


#endif/* I2C_H */

</code></pre>

<p>&nbsp;</p>

<p>&nbsp; &nbsp; 这是C文件,其中双字节地址的读写尚未测试:</p>

<pre>
<code class="language-cs">

#include "si2c.h"

extern uint8_t I2C_Buff;


void SI2C_Start(void);          //开始SI2C通讯
void SI2C_Stop(void);         //停止SI2C通讯
void SI2C_Send(uint8_t dat);    //向SI2C总线发送一个字节
uint8_t SI2C_Receive(void);   //从SI2C总结接收一个字节
void SI2CDoAck(void);         //发出应答信号
void SI2CNoAck(void);         //发出无应答信号
uint8_t SI2CIsAck(void);      //检测从机应答信号


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

/******************************************************************************************************************************************
* 函数名称: I2C_Init()
* 功能说明:        I2C_Init
* 输    入: 无
* 输    出: 无
******************************************************************************************************************************************/
void SI2C_Init(void)
{
    rcu_periph_clock_enable(SCL_GPIO_CLK);

    gpio_init(SCL_GPIO_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SCL_PIN | SDA_PIN); // SCL推挽输出模式

}


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


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


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


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


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


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


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


/**********************************************************************************************
* 函数名称:        I2C_8bitByteWrite()
* 功能说明: 向I2C器件的地址addr开始写入一个字节的数据
* 输    入: uint8_t I2C_addr 器件地址
*         uint8_t addr   写入数据的地址
*                        uint8_t data   要写入的数据
* 输    出: uint8_t        0=成功向器件写入数据大于1=向器件写入数据过程中出现错误
**********************************************************************************************/
uint8_t SI2C_8bitByteWrite(uint8_t I2C_addr,uint8_t addr,uint8_t data)
{
    SI2C_Start();         //产生起始信号
    SI2C_Send(I2C_addr|0);//发送器件地址及读写位,0表示写
    if(SI2CIsAck())       //检测器件是否有响应
    {
      SI2C_Stop();      //产生停止信号
      return 1;
        }

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

    SI2C_Send(data);      //写入数据
    if(SI2CIsAck())       //检测器件是否有响应
    {
      SI2C_Stop();      //产生停止信号
      return 3;
    }

    SI2C_Stop();          //产生停止信号
    return 0;
}

/**********************************************************************************************
* 函数名称:        I2C_8bitByteRead()
* 功能说明: 从I2C器件的地址addr读取1字节数据
* 输    入: uint8_t I2C_addr 器件地址
*         uint8_t addr   读取数据的地址
* 输    出: 读取的数据
**********************************************************************************************/
uint8_t SI2C_8bitByteRead(uint8_t I2C_addr,uint8_t addr)
{
    uint8_t data;

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

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

    data = SI2C_Receive();//从addr处读取1个字节的数据
      
    SI2CNoAck();          //器件要求必须使用NOAck来结束数据读取
    SI2C_Stop();          //产生停止信号

        return data;
}

/**********************************************************************************************
* 函数名称:        I2C_8bitBuffWrite()
* 功能说明: 向I2C器件的地址addr开始写入size个字节的数据,将要写入的数据存储在全局变量I2C_Buff中
* 输    入: uint8_t I2C_addr 器件地址
*         uint8_t addr   写入数据开始的地址
*                        uint8_t size   要设置的数据个数(1~8)
* 输    出: uint8_t        0=成功向器件写入数据大于1=向器件写入数据过程中出现错误
**********************************************************************************************/
uint8_t SI2C_8bitBuffWrite(uint8_t I2C_addr,uint8_t addr,uint8_t size,uint8_t *buf)
{
    uint8_t i = 0;

    SI2C_Start();         //产生起始信号
    SI2C_Send(I2C_addr|0);//发送器件地址及读写位,0表示写
    if(SI2CIsAck())       //检测器件是否有响应
    {
      SI2C_Stop();      //产生停止信号
      return 1;
        }

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

    for(i=0; i&lt;size; i++)
    {
      SI2C_Send(buf);
      if(SI2CIsAck())   //检测器件是否有响应
      {
            SI2C_Stop();//产生停止信号
            return 3;
      }
    }

    SI2C_Stop();          //产生停止信号
       
    return 0;
}


/******************************************************************************************
* 函数名称: I2C_8bitBuffRead()
* 功能说明: 从指定的I2C器件地址addr开始获取size个字节的数据,获取的数据存储在全局变量I2C_Buff中
* 输    入: uint8_t I2C_addr I2C器件地址
*         uint8_t addr   获取数据从addr开始
*                        uint8_t size   要获取的数据个数(1~8)
*         uint8_t *buff    数据缓存
* 输    出: 从器件获取数据(数组指针)
******************************************************************************************/
uint8_t SI2C_8bitBuffRead(uint8_t I2C_addr,uint8_t addr,uint8_t size,uint8_t *buf)
{
    uint8_t i = 0;

    SI2C_Start();         //产生起始信号
    SI2C_Send(I2C_addr);//发送器件地址及读写位,0表示写
    if(SI2CIsAck())       //检测器件是否有响应
    {
      SI2C_Stop();      //产生停止信号
                return 2;
    }

    SI2C_Send(addr);      //发送读取数据的起始地址
    if(SI2CIsAck())       //检测器件是否有响应
    {
      SI2C_Stop();      //产生停止信号
      return 3;
    }

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

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

    SI2CNoAck();          //器件要求必须使用NOAck来结束数据读取
    SI2C_Stop();          //产生停止信号

        return *buf;

}


/**********************************************************************************************
* 函数名称:        I2C_16bitBuffWrite()
* 功能说明: 写多个数据到16位地址的I2C设备
* 输    入: uint8_t I2C_addr 器件地址
*         uint8_t addr   写入数据开始的地址
*                        uint8_t size   要写入的数据个数(1~8)
*         uint8_t *buf   要写入的数据
* 输    出: uint8_t        0=成功向器件写入数据大于1=向器件写入数据过程中出现错误
**********************************************************************************************/
uint8_t SI2C_16bitBuffWrite(uint8_t I2C_addr,uint16_t addr,uint8_t size,uint8_t *buf)
{
    uint8_t i = 0;

    SI2C_Start();         //产生起始信号
    SI2C_Send(I2C_addr|0);//发送器件地址及读写位,0表示写
    if(SI2CIsAck())       //检测器件是否有响应
    {
      SI2C_Stop();      //产生停止信号
      return 1;
        }
       
    SI2C_Send(addr&gt;&gt;8);   //发送读取数据的起始地址高8位
    SI2CIsAck();          //等待应答
    SI2C_Send(addr%256);//发送低8位地址
    SI2CIsAck();          //等待应答

    for(i=0;i&lt;size;i++)
    {
      SI2C_Send(buf);
      if(SI2CIsAck())   //检测器件是否有响应
      {
            SI2C_Stop();//产生停止信号
            return 3;
      }
    }

    SI2C_Stop();          //产生停止信号

    return 0;
}


/******************************************************************************************
* 函数名称: I2C_16bitBuffRead()
* 功能说明: 从指定的16位I2C器件地址addr开始获取size个字节的数据,返回数组变量指针
* 输    入: uint8_t I2C_addr I2C器件地址
*         uint8_t addr   获取数据从addr开始
*                        uint8_t size   要获取的数据个数(1~8)
*         uint8_t *buff    数据缓存
* 输    出: 从器件获取数据(数组指针)
******************************************************************************************/
uint8_t SI2C_16bitBuffRead(uint8_t I2C_addr,uint16_t addr,uint8_t size,uint8_t *buf)
{
    uint8_t i = 0;

    SI2C_Start();         //产生起始信号
    SI2C_Send(I2C_addr);//发送器件地址及读写位,0表示写
    if(SI2CIsAck())       //检测器件是否有响应
    {
      SI2C_Stop();      //产生停止信号
                return 2;
    }
       
    SI2C_Send(addr&gt;&gt;8);   //发送读取数据的起始地址高8位
    SI2CIsAck();          //等待应答
    SI2C_Send(addr%256);//发送低8位地址
    SI2CIsAck();          //等待应答

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

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

    SI2CNoAck();          //器件要求必须使用NOAck来结束数据读取
   
    SI2C_Stop();          //产生停止信号
       
        return *buf;

}




</code></pre>

<p>&nbsp;</p>

w494143467 发表于 2020-11-20 19:41

<p>楼主这IIC通信之路真是非常坎坷啊,不过能看出楼主的毅力!!!</p>

freebsder 发表于 2020-11-20 21:26

<p>i2c看来有点麻烦呢</p>

hujj 发表于 2020-11-21 08:49

w494143467 发表于 2020-11-20 19:41
楼主这IIC通信之路真是非常坎坷啊,不过能看出楼主的毅力!!!

<p>关键问题是还没有找到不成功的原因。</p>

w494143467 发表于 2020-11-21 08:57

hujj 发表于 2020-11-21 08:49
关键问题是还没有找到不成功的原因。

<p>可惜你没有两块板子,要不就能知道是不是板子的问题了。</p>

okhxyyo 发表于 2020-11-23 11:00

<p><strong><a href="https://bbs.eeworld.com.cn/elecplay/content/139" target="_blank">兆易GD32307E-START测评</a></strong></p>

<p>汇总贴:<a href="https://bbs.eeworld.com.cn/thread-1143008-1-1.html" target="_blank">https://bbs.eeworld.com.cn/thread-1143008-1-1.html&nbsp;</a></p>

zxopenljx 发表于 2021-4-22 17:32

<p>谢谢分享</p>
页: [1]
查看完整版本: 【GD32F307E-START】+软件I2C驱动DS1307日历模块