I2C驱动框架时序以及运用(可直接移植)

最近运用SN7326做了一个I2C驱动,由于没有多的I2C,所以用GPIO口模拟了I2C来通信,做一个记录,便于以后复习运用。

I2C最麻烦的就是时序问题,调试起来有点困难,最好是按照标准时序来调试,不然 会出现一些莫名其妙的错误,比如收到的数据为0xFF,比如没有ack回复,收不到数据等等。

  下面这是I2C时序图:

大概的原理是这样的,I2C一般是两根线,一根是SCL,一根是SDA,scl为时针线,sda为数据线,一般如果I2C不工作的时候SDA、SCL为一直保持高电平。

  我做的因为是GPIO模拟I2C,所以每次读取的时候要把端口配置为输出,写的时候要配置成输入。这个要注意一下。

  当INT中断来了之后,会有一个低电平的跳变, I2C总线的开始时序,将SDA数据线拉高,将SCL时钟线拉高,延时一段时间,在SCL为高电平时,拉低SDA,延时一段时间,就是在SCL为高电平时,在SDA上给一个下降沿,表示开始信号。

                                                    

   I2C总线的停止时序,将SDA数据线拉低,在SCL为高电平时,将SDA数据线拉高,延时一段时间,所以在SCL为高电平时,在SDA上给一下上升沿,表示停止信号。

  I2C总线应答时序,首先是需要将模拟数据线的SDA口配置为输入口,将时钟线SCL拉高,延时一段时间,等待从设备应答,从设备应答时,SDA上会产生一个高电平,主设备的GPIO口检测到高电平,则表示应答成功,如果检测到应答信号,则拉SCL时钟线拉低。延时一段时间,将模拟SDA口的GPIO口配置为输出。

  I2C写数据时序,由于I2C属于串行总线,所以数据都是一位一位的传输,将SCL时钟线拉高,准备传输数据,延时一段时间,将I2CBuf的最高位传输到SDA数据线上,拉低SCL时钟线,延时一段时间,所以是当SCL有一个上升沿时,传输数据。
        以上三步表示传输完一位数据。然后将I2CBuf左移一位,再重复以上三步。
        最后,将SCL位低,延时一段时间,再将SDA位高,延时一段时间。此操作是为了释放时钟线SCL和数据线SDA。

   I2C读数据函数的时序,首先需要将模拟SDA数据线的GPIO口配置为输入,将时钟线SCL拉高,延时一段时间,读取数据线SDA上的电平,然后写入I2CBuf的最低位。拉低SCL时钟线,延时一段时间。所以当SCL有一个下降沿时,读取数据。
        以上三步表示读取一位数据,读一个字节,需要重复以上操作步骤8次。

  接下来是写寄存器时序:

    I2C写寄存器函数的时序,主设备先发出一个开始信号,然后写入从设备地址。每一个从设备的地址是唯一的。,主设备接收应答信号,主设备向从设备写入寄存器的地址,主设备接收应答信号,主设备向从设备写入数据,主设备接收应答信号,主设备发出停止信号。

  然后是读寄存器时序:

    I2C读寄存器函数的时序:
         主设备发出开始信号,写入从设备地址,主设备接收从设备发来的应答信号,写入寄存器地址,主设备接收应答信号,主设备再次发出开始信号,主设备向从设备写入(从设备的地址+1),此时加1表示要从寄存器中读数据,从设备给出应答信号,从从设备的寄存器中读出数据传给变量,主设备发出停止信号。最后崽返回读出的数据。

  以上就是时序的大概的原理。

  接下来是代码运用,我写的还有一点问题,就是用逻辑分析仪采样的时候是正常的,但是代码接收的时候是0xFF,以后有修改再更新,先贴上:

  这是读取的细节:

 接下来是代码实现,一般代码分为这几个步骤:初始化为I2C模式,配置I2C的工作方式,比如地址,时钟源,分频,然后启动I2C控制器。

#include "SN7326.h"
#include <stdio.h>
#include <string>             // for STL string class
#include "define.h"



extern bool(__stdcall *InitializeWinIo)(void);
extern void(__stdcall *ShutdownWinIo)(void);
extern PBYTE(__stdcall *MapPhysToLin)(PBYTE pbPhysAddr, DWORD dwPhysSize, HANDLE *pPhysicalMemoryHandle);
extern bool(__stdcall *UnmapPhysicalMemory)(HANDLE PhysicalMemoryHandle, PBYTE pbLinAddr);
extern bool(__stdcall *GetPhysLong)(PBYTE pbPhysAddr, PDWORD pdwPhysVal);
extern bool(__stdcall *SetPhysLong)(PBYTE pbPhysAddr, DWORD dwPhysVal);
extern bool(__stdcall *GetPortVal)(WORD wPortAddr, PDWORD pdwPortVal, BYTE bSize);
extern bool(__stdcall *SetPortVal)(WORD wPortAddr, DWORD dwPortVal, BYTE bSize);
extern bool(__stdcall *InstallWinIoDriver)(PSTR pszWinIoDriverPath, bool IsDemandLoaded);
extern bool(__stdcall *RemoveWinIoDriver)(void);

/*
*FunctionName: I2CDelay

* Purpose : I2C时序模拟SCL时间间隔(周期),需要根据Slave性能及单片机工作频率调整

* Parameters : 无

*/
static void I2C_Delay(void) {
	__asm {
		nop
		nop
		nop
		nop
	}
}

/*
* FunctionName: I2CStart

* Purpose: 模拟I2C开始信号,	SCL电平拉高,SDA由高变低,I2C开始传输数据

* Parameters: 无

*/
static void SN7325_i2c_START(void) {

	SN7325_I2C_SCL_H;
	SN7325_I2C_SDA_H;
	I2C_Delay();
	SN7325_I2C_SDA_L;
	SN7325_I2C_SCL_L;
}

static void SN7325_i2c_RESTART(void) {
	SN7325_I2C_SCL_L;

	SN7325_I2C_SCL_H;
	SN7325_I2C_SDA_H;
	I2C_Delay();
	SN7325_I2C_SDA_L;
	SN7325_I2C_SCL_L;
}

/*

* FunctionName: I2CStop

* Purpose: 模拟I2C结束信号,SCL电平拉高,SDA由低边高,I2C停止传输数据

* Parameters: 无

*/
static void SN7325_i2c_STOP(void) {
	int i;
	SN7325_I2C_SCL_H;
	SN7325_I2C_SDA_L;
	I2C_Delay();
	SN7325_I2C_SDA_H;
}

/*

* FunctionName: I2CSendNoACK

* Purpose: 模拟I2C无ACK响应

* Parameters: 无

*/
static void SN7325_i2c_NOACK(void) {

	SN7325_I2C_SDA_H;
	SN7325_I2C_SCL_H;
	SN7325_I2C_SCL_L;
}

/*

* FunctionName: I2CCheckACK

* Purpose:把串口设置为输入模式,测试串口是否有响应

* Parameters: 无

*/
static bool SN7325_i2c_WAITACK(void) {
	unsigned long tempACK = 0;
	unsigned long res = 0;
	unsigned long time_out = 0;

	SN7325_I2C_SDA_L;

	SetPhysLong((PBYTE)KEY_IO5_ADDR, 0x00908200);//输入模式
	SN7325_I2C_SCL_H;
	do {
		GetPhysLong((PBYTE)KEY_IO5_ADDR, (PDWORD)&tempACK);//获取ack
		if (tempACK)
			res =1;
		else
			res = 0;

		time_out++;

		if (time_out > 3)//超时处理
		{
			res = 0;
		}

	} while (( !res )||(time_out> 3));

	SN7325_I2C_SCL_L;

	return res;
}

/*

* FunctionName: I2CTWRITE8BIT

* Purpose:模拟I2C发送一个字节数据,从低位开始写

* Parameters: sendData-发送的一个字节数据

*/
static void SN7325_i2c_WRITE8B(unsigned char  input) {
	unsigned char  serialNum;

	for (serialNum = 8; serialNum >= 1; serialNum--) 
	{
		if (input & 0x80) {//判断最高位是否为1
			SN7325_I2C_SDA_H;
		}
		else {
			SN7325_I2C_SDA_L;
		}
		SN7325_I2C_SCL_H;
		I2C_Delay();
		SN7325_I2C_SCL_L;

	    input <<= 1;//数据左移一位
	}
}

/*

* FunctionName: I2CTREAD8BIT

* Purpose:模拟I2C接收一个字节数据

* Parameters: 无

*/
static unsigned char  SN7325_i2c_READ8B(void)
{
	unsigned char  serialNum;
	unsigned char  I2CBUF = 0;
	unsigned long SDAStatus = 0;

	SetPhysLong((PBYTE)KEY_IO5_ADDR, 0x00908200);		//设置SDA/串口为输入模式
	for (serialNum = 8; serialNum >=1; serialNum--) {
		I2CBUF  <<= 1;
		SN7325_I2C_SCL_H;
		GetPhysLong((PBYTE)KEY_IO5_ADDR, (PDWORD)&SDAStatus); 
		if (SDAStatus) {
			I2CBUF |= 0x01;
			//I2CBUF++;//表示读取数据
		}
		SN7325_I2C_SCL_L;
	}
	printf("I2CBUF = %x", I2CBUF);
	return I2CBUF;
}

/*
写寄存器的标准流程为
1.    Master发起START
2.    Master发送I2C addr(7bit)和w操作0(1bit),等待ACK
3.    Slave发送ACK
4.    Master发送reg addr(8bit),等待ACK
5.    Slave发送ACK
6.   Master发送data(8bit),即要写入寄存器中的数据,等待ACK
7.    Slave发送ACK
8.    第6步和第7步可以重复多次,即顺序写多个寄存器
9.    Master发起STOP
*/
void SN7325_Write_i2c(unsigned char WriteDeviceAddress, unsigned char Command, unsigned char Wdata)
{
	SN7325_i2c_START();

	SN7325_i2c_WRITE8B(WriteDeviceAddress);
	SN7325_i2c_WAITACK();

	SN7325_i2c_WRITE8B(Command);
	SN7325_i2c_WAITACK();

	SN7325_i2c_WRITE8B(Wdata);
	SN7325_i2c_NOACK();
	SN7325_i2c_STOP();
}

/*
I2C读寄存器函数的时序:
1、主设备发出开始信号
2、写入从设备地址
3、主设备接收从设备发来的应答信号
4、写入寄存器地址
5、主设备接收应答信号
6、主设备再次发出开始信号
7、主设备向从设备写入(从设备的地址+1),此时加1表示要从寄存器中读数据
8、从设备给出应答信号
9、从从设备的寄存器中读出数据传给变量
10、主设备发出停止信号
11、返回读出的数据
*/
unsigned short SN7325_Read_i2c(unsigned char ReadDeviceAddress, unsigned char Command)
{

	unsigned char r_byte;
	SN7325_i2c_START();
	SN7325_i2c_WRITE8B(ReadDeviceAddress - 1);		//先写入从设备的地址。表示要向从设备中写地址和数据
	SN7325_i2c_WAITACK();
	SN7325_i2c_WRITE8B(Command);					//写入寄存器的地址,数据将从些地址中读出
	SN7325_i2c_WAITACK();

	//SN7325_i2c_RESTART();
	SN7325_i2c_START();
	SN7325_i2c_WRITE8B(ReadDeviceAddress);			//发送器件地址,地址LSB最后一位为0代表写入,1代表读取  
	SN7325_i2c_WAITACK();
	r_byte = SN7325_i2c_READ8B();
	//SN7325_i2c_WAITACK();//lsx add
	SN7325_i2c_NOACK();
	SN7325_i2c_STOP();

	//r_byte = SN7325_i2c_READ8B();					//从地址regaddr中读出数据
	return r_byte;
}

应用的话一般是先写,注意的是SN7325_Write_i2c(unsigned char WriteDeviceAddress, unsigned char Command, unsigned char Wdata)第一个是从机的地址,第二个是你要写入的寄存器地址,第三个是你要写入的值。根据个人所用的器件不同,具体查询器件的数据手册。

然后再判断接收到INT之后读取解析。

暂时就这么多,以后再深入运用的话再深入的探讨分享,有问题欢迎提出一起探讨。

发布了37 篇原创文章 · 获赞 16 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/double_lee3/article/details/90205474