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

hujj   2020-11-20 17:46 楼主

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

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

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

I2C_S03.jpg

    时钟频率调整好了,但器件没有响应,得不到ACK:

I2C_S02.jpg

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

I2C_S04.jpg

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

I2C_S05.jpg

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

I2C_S06.jpg

    跳变有时会引起误读,象下面的时序图,跳变产生了一个STOP信号:

I2C_S07.jpg

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

I2C_S08.jpg

    这是显示屏的特定镜头:

I2C_S09.jpg

    这是测试效果的动画:

DS1307_Test.gif

 

回复评论 (7)

    这是SI2C的头文件:


#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 */

 

    这是C文件,其中双字节地址的读写尚未测试:



#include "si2c.h"

extern uint8_t I2C_Buff[16];


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>0;x--)    
    for(y=22;y>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&0x80){
            SDA_1();
        }else{
            SDA_0();
		}
        dat<<=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<<=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>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<size; i++)
    {
        SI2C_Send(buf[i]);
        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<size;i++)	  //从addr处开始读取size个字节的数据
    {
        buf[i] = 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>>8);   //发送读取数据的起始地址高8位
    SI2CIsAck();          //等待应答
    SI2C_Send(addr%256);  //发送低8位地址
    SI2CIsAck();          //等待应答

    for(i=0;i<size;i++)
    {
        SI2C_Send(buf[i]);
        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>>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<size;i++)   //从addr处开始读取size个字节的数据
    {
        buf[i] = SI2C_Receive();
        SI2CDoAck();
    }

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

}




 

点赞  2020-11-20 17:51

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

点赞  2020-11-20 19:41

i2c看来有点麻烦呢

默认摸鱼,再摸鱼。2022、9、28
点赞  2020-11-20 21:26
引用: w494143467 发表于 2020-11-20 19:41 楼主这IIC通信之路真是非常坎坷啊,不过能看出楼主的毅力!!!

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

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

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

点赞  2020-11-21 08:57
玩板看这里: https://bbs.eeworld.com.cn/elecplay.html EEWorld测评频道众多好板等你来玩,还可以来频道许愿树许愿说说你想要玩的板子,我们都在努力为大家实现!
点赞  2020-11-23 11:00

谢谢分享

点赞  2021-4-22 17:32
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复