[CH579M-R1] + Help: Simulating I2C to read data failed (solved)
[Copy link]
This post was last edited by hujj on 2020-9-22 15:39
After successfully driving the LCD5110 display, I started trying to drive the DS1307 calendar module last week. The following picture shows the module purchased from Taobao:
This module communicates via the I2C bus. It also comes with a 24C32 chip and a temperature and humidity sensor installation location. Below are pictures of my test process:
I searched for information about CH579, but could not find any description or examples of hardware I2C. So I had to try to set up software I2C. Because the LCD display and the devices on the I2C bus will not be used at the same time, I used the CLK and DIN pins that drive the LCD5110 and reused them for the SCK and SDA of I2C. The i2c.h file is as follows:
#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 */
The i2c.c code is as follows:
#include "CH57x_common.h"
#include "i2c.h"
//#include <stdio.h>
/***************************************************
函数功能:微秒延时
入口参数:延时的微秒数
***************************************************/
void delay_us(uint8_t us)
{
uint8_t x,y;
for(x=us;x>0;x--)
for(y=4;y>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<8;i++)
{
// if(((dat&0x80)>>7) == 1) SDA_1;//准备好SDA数据
if((dat>>(7-i))&0x01) SDA_1();
else SDA_0();
// dat>>=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<8;i++)
{
SCL_0();
delay_us(5);
SCL_1();
dat<<=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<8;i++)
{
scl = 0;
DELAY();
sda = 1; //本语句必须有:于IIC,是释放SDA线;于51单片机,则是由于51的IO不是真双向口,在读之前必须写0
DELAY();
scl = 1;
DELAY();
d = sda;
DELAY();
dat |= (d<<(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>250){
SI2C_Stop();//数据线未被拉低,即未收到应答
return 1;
}
}
SCL_0();
return 0;
}
The code for data reading and writing and date conversion of the DS1307 module is as follows:
#include "I2C.h"
#include "ds1307.h"
#include "lcd_5110.h"
extern uint16_t year; //年
extern uint8_t week,month,day,hour,minute,second,DS_Buff[8];//月日时分秒
/******************************************************************************************
* 函数名称: 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<size;i++) //从addr处读取size个字节的数据
{
DS_Buff = 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>0;i++,size--)
{
SI2C_Send(DS_Buff);
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>127){
year = 2020;
month = 9;
day = 15;
week = 3;
hour = 18;
minute = 10;
DS_Buff[0] = 0; //秒
temp = ((minute/10)<<4|(minute%10));
DS_Buff[1] = temp; //分
temp = ((hour/10)<<4|(hour%10));
DS_Buff[2] = temp; //时
DS_Buff[3] = week; //星期
temp = ((day/10)<<4|(day%10));
DS_Buff[4] = temp; //日
temp = ((month/10)<<4|(month%10));
DS_Buff[5] = temp; //月
temp = ((year%100)/10<<4|(year%10));
DS_Buff[6] = temp; //年
DS_Buff[7] = 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[0]&0x70)>>4)*10 + (DS_Buff[0]&0x0F);//秒,屏蔽秒的第7位的标志
minute = ((DS_Buff[1]&0x70)>>4)*10 + (DS_Buff[1]&0x0F);//分(取低7位)
hour = ((DS_Buff[2]&0x10)>>4)*10 + (DS_Buff[2]&0x0F); //时(取低5位)
week = (DS_Buff[3]&0x07); //周(取低3位)
day = ((DS_Buff[4]&0x30)>>4)*10 + (DS_Buff[4]&0x0F); //日(取低6位)
month = ((DS_Buff[5]&0x10)>>4)*10 + (DS_Buff[5]&0x0F); //月(取低5位)
year = 2000 + (DS_Buff[6]>>4)*10 + (DS_Buff[6]&0x0F); //年
LCD_write_value(0,3,3,0,0,DS_Buff[0]);
LCD_write_value(25,3,3,0,0,DS_Buff[1]);
LCD_write_value(50,3,3,0,0,DS_Buff[2]);
LCD_write_value(0,4,3,0,0,DS_Buff[3]);
LCD_write_value(25,4,3,0,0,DS_Buff[4]);
LCD_write_value(50,4,3,0,0,DS_Buff[5]);
}
/******************************************************************************************
* 函数名称: DS1307_write_date()
* 功能说明: 读取DS1307日期时间数据
* 输 入: 无
* 输 出: 无
******************************************************************************************/
void DS1307_write_date(void)
{
uint8_t temp;
DS_Buff[0] = 0; //秒
temp = ((minute/10)<<4|(minute%10));
DS_Buff[1] = temp; //分
temp = ((hour/10)<<4|(hour%10));
DS_Buff[2] = temp; //时
DS_Buff[3] = week; //星期
temp = ((day/10)<<4|(day%10));
DS_Buff[4] = temp; //日
temp = ((month/10)<<4|(month%10));
DS_Buff[5] = temp; //月
temp = ((year%100)/10<<4|(year%10));
DS_Buff[6] = temp; //年
DS1307_Write(0,7); //写入前7个字节数据
// DS1307_I2C_Write(0,7); //写入前7个字节数据
}
The above codes are all used normally in other programs. When porting before, it usually only needs to adjust the timing of reading and writing. However, this porting is not so smooth. After debugging for several days, there is no progress and no data can be read. The following is a screenshot of the logic analyzer:
From the timing diagram captured by the logic analyzer, the I2C read and write commands are issued correctly, and the I2C device also responds normally and sends correct data, but the code for reading the SDA pin is always 0, which means: the I2C write process is normal, the I2C device also responds normally, and the corresponding data is also reflected on the SDA pin, but the SDA pin level is always low. The problem should be in the code for reading the pin level or the code for setting the pin read and write mode. My macro definition for setting the SDA read and write mode is as follows:
#define SDA_1() GPIOB_SetBits(I2C_SDA) //Write I2C data port
#define SDA_0() GPIOB_ResetBits(I2C_SDA)
#define SDA_X() GPIOB_ReadPortPin(I2C_SDA) //Read I2C data port status
#define SDA_OUT() GPIOB_ModeCfg(I2C_SDA, GPIO_ModeOut_PP_5mA) //SDA push-pull output mode
#define SDA_IN() GPIOB_ModeCfg(I2C_SDA, GPIO_ModeIN_PU); //SDA input mode
There shouldn't be any problem with this macro definition, but it doesn't work when I actually test it. During the test, I changed the idle pin to SDA but it didn't work. I also changed the macro definition of changing the pin mode to set it in the function with code but it didn't work. I tried it over and over again for more than a week but it didn't work, so I was a little frustrated. The only way I can think of is to prepare to add an external pull-up resistor and test it again, but the module already has a pull-up resistor, so adding a pull-up resistor doesn't make much sense. But I can't think of any other way. Let's see if you can help analyze the cause and make suggestions. Thank you all in advance!
|