STM32f103 I2C应用代码

前言

这是本人在学习 stm32 过程中总结的代码,希望能对新手有所帮助,若有谬误之处,恳请各位指正。

基本说明

此代码为基于 stm32f1 固件库的对 I2C 的封装,可以方便地配置和使用 硬件 I2C1硬件 I2C1 重映射硬件 I2C2,同时可以配置 软件模拟 I2C,可以选择模拟 I2C 的引脚。

使用说明

本 I2C 代码除了使用了固件库,还使用了GPIO、系统定时器、串口(可选)的内容,因此有以下三个头文件:
#include “GPIOpinInit.h” // 为了用寄存器操作IO,模拟I2C操控引脚需要
#include “SysTick.h” // 模拟I2C延时需要
#include “USARTcontrol.h” // 总线出错报告需要

相关代码见我的前三篇文章:
STM32f103 GPIO应用代码
STM32f103 SysTick应用代码
STM32f103 串口应用代码

为节省程序空间,采用条件编译,启用 I2C 只需在h文件中启用对应的宏,如:
示例1、仅开启软件模拟I2C

//#define I2C1D//硬件I2C1
//#define I2C1R//硬件I2C1重映射
//#define I2C2D//硬件I2C2
#define I2CS//软件模拟I2C

示例2、仅开启硬件I2C1重映射

//#define I2C1D//硬件I2C1
#define I2C1R//硬件I2C1重映射
//#define I2C2D//硬件I2C2
//#define I2CS//软件模拟I2C

示例3、同时开启硬件I2C2和软件模拟I2C

//#define I2C1D//硬件I2C1
//#define I2C1R//硬件I2C1重映射
#define I2C2D//硬件I2C2
#define I2CS//软件模拟I2C

允许同时定义硬件和软件模拟 I2C ,但两者不能有引脚冲突,且3个硬件 I2C 只能有一个开启
硬件 I2C 的引脚见 stm32 数据手册,软件 I2C 的引脚可以修改,见h文件的如下 3 行:

//IO操作函数
	#define IIC_SCL    PBout(8) //SCL
	#define IIC_SDA    PBout(9) //SDA
	#define READ_SDA   PBin(9)  //输入SDA

此处选择了 B8 为 SCL,B9 为 SDA,需要更换引脚时修改这 3 行即可。
在修改好宏定义之后,只需调用:

void	I2C_Config(void);//I2C初始化

就能完成所启用的 I2C 的初始化。
初始化后要使用 I2C 通信,则可调用以下的函数:

//操作函数	
u8	I2C_Write_1Byte(u8 DeviceAddr,u8 WriteAddr,u8 Data);//硬件I2C写1字节
u8	I2C_Read_1Byte(u8 DeviceAddr,u8 ReadAddr);//硬件I2C读1字节
u8	I2C_Write_nByte(u8 DeviceAddr,u8 WriteAddr,u8 *DataArray,u8 n);//硬件I2C写n字节
u8	I2C_Read_nByte(u8 DeviceAddr,u8 ReadAddr,u8 *DataArray,u8 n);//硬件I2C读n字节

u8	I2CS_Write_1Byte(u8 DeviceAddr,u8 WriteAddr,u8 Data);//模拟I2C写1字节
u8	I2CS_Read_1Byte(u8 DeviceAddr,u8 ReadAddr);//模拟I2C读1字节
u8	I2CS_Write_nByte(u8 DeviceAddr,u8 WriteAddr,u8 *DataArray,u8 n);//模拟I2C写n字节
u8	I2CS_Read_nByte(u8 DeviceAddr,u8 ReadAddr,u8 *DataArray,u8 n);//模拟I2C读n字节

其中 DeviceAddr 为设备地址,WriteAddr 为读地址,ReadAddr 为写地址,Data 为要写入的字节,DataArray 为要写入或存放读取信息的数组,n 为数组长度。
I2C_ 开头的是硬件 I2C 操作函数,I2CS_ 开头的是模拟 I2C 操作函数。

注意

①当 I2C 出现通信错误时,将调用如下的错误处理函数:

void	I2C_ERROR(u8 errNum);//I2C错误处理函数

该函数默认将 I2C 的错误信息打印至串口(打印到哪个串口将取决于串口那边 printf 的重定向,见之前串口的文章),可以帮助查找是通信的哪一步出现了问题。
除此之外,它还会对来自硬件 I2C 的错误进行计数,当达到一定次数后,会试图重启 I2C 总线。经测试,这样做后即使在通信中拔插 I2C 总线,也基本都能恢复通信(模拟 I2C 是直接操作引脚电平,所以不需要这样重启,但硬件 I2C 不这样做的话在断线重连后经常不能自行恢复)。
当然,以上功能都是可选的,不需要则可以注释掉,也可添加其他功能,如出错则点亮板载 LED 等。

②当使用硬件 I2C1 重映射时,涉及到一些重映射时钟和 GPIO 重映射功能的使能,如果项目需要失能这些时钟的操作,则务必放在 I2C_Config(void); 函数之前,否则会将设置好的重映射取消掉。

③对于模拟 I2C,采用的是最一般的方式对所用的引脚进行的配置,直接将 SCL 和 SDA 均配置为一般的开漏,因此外接上拉电阻是必须的。另外 stm32 的某些引脚有特殊功能,不能用普通的方法配置,因此一般不要将它们作为模拟 I2C 的引脚,如一定要用,则要查清楚它们的配置方法。
可以参考以下内容:
STM32引脚使用选择注意

代码

h文件:

#ifndef _I2CCONTROL_H
#define _I2CCONTROL_H

#include "stm32f10x.h"

#include "GPIOpinInit.h"//实际为了用寄存器操作IO,模拟I2C操控引脚需要
#include "SysTick.h"//模拟I2C延时需要
#include "USARTcontrol.h"//总线出错报告需要

/*************宏定义操作区*************/
//#define I2C1D//硬件I2C1
//#define I2C1R//硬件I2C1重映射
//#define I2C2D//硬件I2C2
//#define I2CS//软件模拟I2C
/*************************************/

#ifdef I2C1D//若定义硬件I2C1
	#define I2Cx             I2C1
	#define I2Cx_Clock       RCC_APB1Periph_I2C1//I2C时钟
	#define I2Cx_SCL_PIN     GPIO_Pin_6//I2C SCL GPIO引脚
	#define I2Cx_SDA_PIN     GPIO_Pin_7//I2C SDA GPIO引脚
#endif

#ifdef I2C1R//若定义硬件I2C1重映射
	#define I2Cx             I2C1
	#define I2Cx_Clock       RCC_APB1Periph_I2C1//I2C时钟
	#define I2Cx_SCL_PIN     GPIO_Pin_8//I2C SCL GPIO引脚
	#define I2Cx_SDA_PIN     GPIO_Pin_9//I2C SDA GPIO引脚
#endif

#ifdef I2C2D//若定义硬件I2C2
	#define I2Cx             I2C2
	#define I2Cx_Clock       RCC_APB1Periph_I2C2//I2C时钟
	#define I2Cx_SCL_PIN     GPIO_Pin_10//I2C SCL GPIO引脚
	#define I2Cx_SDA_PIN     GPIO_Pin_11//I2C SDA GPIO引脚
#endif

#if (defined I2C1D)||(defined I2C1R)||(defined I2C2D)//若定义硬件I2C
	//配置宏定义
	#define I2C_Speed        400000//快速模式400k
	#define I2Cx_MCU_Addr_7  0X55//单片机本身的7位I2C地址
	#define I2Cx_GPIO_Clock  RCC_APB2Periph_GPIOB//I2C GPIO时钟
	#define I2Cx_SCL_PORT    GPIOB//I2C SCL GPIO端口
	#define I2Cx_SDA_PORT    GPIOB//I2C SDA GPIO端口
	//操作函数	
	u8	I2C_Write_1Byte(u8 DeviceAddr,u8 WriteAddr,u8 Data);//硬件I2C写1字节
	u8	I2C_Read_1Byte(u8 DeviceAddr,u8 ReadAddr);//硬件I2C读1字节
	u8	I2C_Write_nByte(u8 DeviceAddr,u8 WriteAddr,u8 *DataArray,u8 n);//硬件I2C写n字节
	u8	I2C_Read_nByte(u8 DeviceAddr,u8 ReadAddr,u8 *DataArray,u8 n);//硬件I2C读n字节
#endif

#ifdef I2CS//若定义软件模拟I2C
	/****************************修改SCL和SDA引脚需正确修改以下宏****************************/
	//配置宏定义
	#define SCL_GPIO_Clock   RCC_APB2Periph_GPIOB//SCL GPIO时钟
	#define SCL_GPIO_Port    GPIOB               //SCL GPIO端口
	#define SCL_GPIO_Pin     GPIO_Pin_8          //SCL GPIO引脚
	#define SDA_GPIO_Clock   RCC_APB2Periph_GPIOB//SDA GPIO时钟
	#define SDA_GPIO_Port    GPIOB               //SDA GPIO端口
	#define SDA_GPIO_Pin     GPIO_Pin_9          //SDA GPIO引脚
	//SDA IO方向设置
	//若SDA引脚号n为0-7,则用都用CRL,&=右边的16位数从右向左从0开始数第n位为0,<<右边=4*n;
	//若SDA引脚号n为8-15,则用都用CRH,&=右边的16位数从右向左从0开始数第n-8位为0,<<右边=4*n-32;
	//SDA输入设置,<<左边=4为浮空输入,=8为上下拉输入
	#define SDA_IN()  {SDA_GPIO_Port->CRH&=0XFFFFFF0F;SDA_GPIO_Port->CRH|=4<<4;}
	//SDA输出设置,<<左边=3为50MHz推挽输出,=7为50MHz开漏输出
	#define SDA_OUT() {SDA_GPIO_Port->CRH&=0XFFFFFF0F;SDA_GPIO_Port->CRH|=7<<4;}
	//具体内容参考:STM32F10xxx参考手册-8.2节-GPIO寄存器描述
	//IO操作函数	
	#define IIC_SCL    PBout(8) //SCL
	#define IIC_SDA    PBout(9) //SDA
	#define READ_SDA   PBin(9)  //输入SDA
	/***************************************************************************************/
	//操作函数	
	void	I2CS_Start(void);	//发送I2C开始信号
	void	I2CS_Stop(void);//发送I2C停止信号
	void	I2CS_Send_Byte(u8 txd);//I2C发送一个字节
	u8 		I2CS_Read_Byte(u8 ack);//I2C读取一个字节
	u8		I2CS_Wait_Ack(void);//I2C等待ACK信号
	void	I2CS_Ack(void);	//I2C发送ACK信号
	void	I2CS_NAck(void);//I2C不发送ACK信号
	u8		I2CS_Write_1Byte(u8 DeviceAddr,u8 WriteAddr,u8 Data);//模拟I2C写1字节
	u8		I2CS_Read_1Byte(u8 DeviceAddr,u8 ReadAddr);//模拟I2C读1字节
	u8		I2CS_Write_nByte(u8 DeviceAddr,u8 WriteAddr,u8 *DataArray,u8 n);//模拟I2C写n字节
	u8		I2CS_Read_nByte(u8 DeviceAddr,u8 ReadAddr,u8 *DataArray,u8 n);//模拟I2C读n字节
#endif

void	I2C_Config(void);//I2C初始化
void	I2C_ERROR(u8 errNum);//I2C错误处理函数

#endif

c文件:

#include "I2Ccontrol.h"

#define  I2C_Wait_Short 0x1000//硬件I2C短等待时间
#define  I2C_Wait_Long  0xA000//硬件I2C长等待时间

uint32_t I2C_Wait = 0;//硬件I2C等待变量
u8 I2C_ERROR_Count = 0;//I2C错误计数变量

void I2C_Config(void)//I2C初始化
{
	GPIO_InitTypeDef GPIO_InitStructure;//创建GPIO初始化结构体
	
	#if (defined I2C1D)||(defined I2C1R)||(defined I2C2D)//若定义硬件I2C
		I2C_InitTypeDef  I2C_InitStructure;//创建I2C初始化结构体
		RCC_APB1PeriphClockCmd( I2Cx_Clock,ENABLE );//使能与I2C有关的时钟
		RCC_APB2PeriphClockCmd( I2Cx_GPIO_Clock,ENABLE );//使能与I2C GPIO有关的时钟
		
		#ifdef I2C1R//若定义硬件I2C1重映射
			RCC_APB2PeriphClockCmd( RCC_APB2Periph_AFIO,ENABLE );//使能重映射时钟,注意之后不得取消使能
			GPIO_PinRemapConfig( GPIO_Remap_I2C1,ENABLE );//使能重映射
		#endif
		
		GPIO_InitStructure.GPIO_Pin = I2Cx_SCL_PIN;//SCL引脚
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHz
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;//开漏输出
		GPIO_Init( I2Cx_SCL_PORT,&GPIO_InitStructure );//GPIO初始化
		
		GPIO_InitStructure.GPIO_Pin = I2Cx_SDA_PIN;//SDA引脚
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHz
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;//开漏输出
		GPIO_Init( I2Cx_SDA_PORT,&GPIO_InitStructure );//GPIO初始化
		
		I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//配置为普通I2C模式
		I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//高电平数据稳定,低电平数据变化SCL时钟线的占空比
		I2C_InitStructure.I2C_OwnAddress1 =I2Cx_MCU_Addr_7;//单片机本身的7位或10位I2C地址
		I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//使能应答
		I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//7位或10位寻址模式
		I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;//通信速率
		I2C_Init(I2Cx, &I2C_InitStructure);//I2C初始化
		I2C_Cmd(I2Cx, ENABLE);//使能I2C
	#endif
	
	#ifdef I2CS//若定义软件模拟I2C
		RCC_APB2PeriphClockCmd(	SCL_GPIO_Clock,ENABLE );//使能SCL GPIO时钟
		RCC_APB2PeriphClockCmd(	SDA_GPIO_Clock,ENABLE );//使能SDA GPIO时钟
		GPIO_InitStructure.GPIO_Pin = SCL_GPIO_Pin;//SCL引脚
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHz
		GPIO_Init( SCL_GPIO_Port,&GPIO_InitStructure );//SCL GPIO初始化
		
		GPIO_InitStructure.GPIO_Pin = SDA_GPIO_Pin;//SDA引脚
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHz
		GPIO_Init( SDA_GPIO_Port,&GPIO_InitStructure );//SDA GPIO初始化
		
		GPIO_SetBits( SCL_GPIO_Port,SCL_GPIO_Pin );//SCL输出高
		GPIO_SetBits( SDA_GPIO_Port,SDA_GPIO_Pin );//SCL输出高
	#endif
}

#if (defined I2C1D)||(defined I2C1R)||(defined I2C2D)//若定义硬件I2C
u8 I2C_Write_1Byte(u8 DeviceAddr,u8 WriteAddr,u8 Data)//硬件I2C写1字节
{
	I2C_GenerateSTART( I2Cx,ENABLE );//发送起始符
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))//检查EV5并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(1);return 0;	}	}
	
	I2C_Send7bitAddress( I2Cx,DeviceAddr,I2C_Direction_Transmitter );//发送设备地址(8位以及写)
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))//检查EV6(写)并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(2);return 0;	}	}
	
	I2C_SendData( I2Cx,WriteAddr );//写8位寄存器地址
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))//检查EV8并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(3);return 0;	}	}
	
	I2C_SendData( I2Cx,Data );//写8位数据
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))//检查EV8并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(4);return 0;	}	}
	
	I2C_GenerateSTOP( I2Cx,ENABLE );//发送结束符
	return 1;
}

u8 I2C_Read_1Byte(u8 DeviceAddr,u8 ReadAddr)//硬件I2C读1字节
{
	I2C_Wait = I2C_Wait_Long;//等待时间复位
	while(I2C_GetFlagStatus( I2Cx,I2C_FLAG_BUSY ))
	{	if((I2C_Wait--) == 0){	I2C_ERROR(21);return 0;	}	}
	
	I2C_GenerateSTART( I2Cx,ENABLE );//发送起始符
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))//检查EV5并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(22);return 0;	}	}
	
	I2C_Send7bitAddress( I2Cx,DeviceAddr,I2C_Direction_Transmitter );//发送设备地址(8位以及写)
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))//检查EV6(写)并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(23);return 0;	}	}
	
	I2C_Cmd( I2Cx,ENABLE );//Clear EV6 by setting again the PE bit
	I2C_SendData( I2Cx,ReadAddr );//写8位寄存器地址
	I2C_Wait=I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))//检查EV8并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(24);return 0;	}	}
	
	I2C_GenerateSTART( I2Cx,ENABLE );//二次发送起始符
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))//检查EV5并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(25);return 0;	}	}
	
	I2C_Send7bitAddress( I2Cx,DeviceAddr,I2C_Direction_Receiver );//发送设备地址(8位以及读)
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))//检查EV6(读)并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(26);return 0;	}	}
	
	I2C_Wait = I2C_Wait_Long;//等待时间复位
	I2C_AcknowledgeConfig( I2Cx,DISABLE );//不使能应答
	I2C_GenerateSTOP( I2Cx,ENABLE );//发送结束符
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED))//检查EV7并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(27);return 0;	}	}
	
	I2C_AcknowledgeConfig( I2Cx,ENABLE );//为之后的通讯使能应答
	return I2C_ReceiveData(I2Cx);//取出数据
}

u8 I2C_Write_nByte(u8 DeviceAddr,u8 WriteAddr,u8 *DataArray,u8 n)//硬件I2C写n字节
{
	I2C_Wait = I2C_Wait_Long;//等待时间复位
	while(I2C_GetFlagStatus( I2Cx,I2C_FLAG_BUSY ))
	{	if((I2C_Wait--) == 0){	I2C_ERROR(41);return 0;	}	}
	
	I2C_GenerateSTART( I2Cx,ENABLE );//发送起始符
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))//检查EV5并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(42);return 0;	}	}
	
	I2C_Send7bitAddress( I2Cx,DeviceAddr,I2C_Direction_Transmitter );//发送设备地址(8位以及写)
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))//检查EV6(写)并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(43);return 0;	}	}
	
	I2C_Cmd( I2Cx,ENABLE );//Clear EV6 by setting again the PE bit
	I2C_SendData( I2Cx,WriteAddr );//写8位寄存器地址
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))//检查EV8并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(44);return 0;	}	}
	
	for(; n>0; n--)
	{
		I2C_SendData( I2Cx,*DataArray );//写8位数据
		I2C_Wait = I2C_Wait_Short;//等待时间复位
		while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))//检查EV8并清除
		{	if((I2C_Wait--) == 0){	I2C_ERROR(45);return 0;	}	}
		
		DataArray++;
	}
	I2C_GenerateSTOP( I2Cx,ENABLE );//发送结束符
	return 1;
}

u8 I2C_Read_nByte(u8 DeviceAddr,u8 ReadAddr,u8 *DataArray,u8 n)//硬件I2C读n字节
{
	I2C_Wait = I2C_Wait_Long;//等待时间复位
	while(I2C_GetFlagStatus( I2Cx,I2C_FLAG_BUSY ))
	{	if((I2C_Wait--) == 0){	I2C_ERROR(61);return 0;	}	}
	
	I2C_GenerateSTART( I2Cx,ENABLE );//发送起始符
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))//检查EV5并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(62);return 0;	}	}
	
	I2C_Send7bitAddress( I2Cx,DeviceAddr,I2C_Direction_Transmitter );//发送设备地址(8位以及写)
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))//检查EV6(写)并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(63);return 0;	}	}
	
	I2C_Cmd( I2Cx,ENABLE );//Clear EV6 by setting again the PE bit
	I2C_SendData( I2Cx,ReadAddr );//写8位寄存器地址
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))//检查EV8并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(64);return 0;	}	}
	
	I2C_GenerateSTART( I2Cx,ENABLE );//二次发送起始符
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))//检查EV5并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(65);return 0;	}	}
	
	I2C_Send7bitAddress( I2Cx,DeviceAddr,I2C_Direction_Receiver );//发送设备地址(8位以及读)
	I2C_Wait = I2C_Wait_Short;//等待时间复位
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))//检查EV6(读)并清除
	{	if((I2C_Wait--) == 0){	I2C_ERROR(66);return 0;	}	}
	
	for(; n>0; n--)
	{
		if(n == 1)
		{
			I2C_AcknowledgeConfig( I2Cx,DISABLE );//不使能应答
			I2C_GenerateSTOP( I2Cx,ENABLE );//发送结束符
		}
		I2C_Wait = I2C_Wait_Long;//等待时间复位
		while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED))//检查EV7并清除
		{	if((I2C_Wait--) == 0){	I2C_ERROR(67);return 0;	}	}
		
		*DataArray = I2C_ReceiveData(I2Cx);
		DataArray++;
	}
	I2C_AcknowledgeConfig( I2Cx,ENABLE );//为之后的通讯使能应答
	return 1;
}
#endif

#ifdef I2CS//若定义软件模拟I2C
void I2CS_Start(void)//产生I2C起始信号
{
	SDA_OUT();     //sda线输出
	IIC_SDA = 1;	  	  
	IIC_SCL = 1;
	Delay_us(4);
 	IIC_SDA = 0;//START:when CLK is high,DATA change form high to low 
	Delay_us(4);
	IIC_SCL = 0;//钳住I2C总线,准备发送或接收数据 
}	  

void I2CS_Stop(void)//产生IIC停止信号
{
	SDA_OUT();//sda线输出
	IIC_SCL = 0;
	IIC_SDA = 0;//STOP:when CLK is high DATA change form low to high
 	Delay_us(4);
	IIC_SCL = 1; 
	IIC_SDA = 1;//发送I2C总线结束信号
	Delay_us(4);							   	
}

u8 I2CS_Wait_Ack(void)//等待应答信号到来,返回值:1接收应答失败,0接收应答成功
{
	u8 ucErrTime = 0;
	SDA_IN();      //SDA设置为输入
	IIC_SDA = 1;
	Delay_us(1);
	IIC_SCL = 1;
	Delay_us(1);
	while(READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime > 250)
		{
			I2CS_Stop();
			return 1;
		}
	}
	IIC_SCL = 0;//时钟输出0 	   
	return 0;
} 

void I2CS_Ack(void)//产生ACK应答
{
	IIC_SCL = 0;
	SDA_OUT();
	IIC_SDA = 0;
	Delay_us(2);
	IIC_SCL = 1;
	Delay_us(2);
	IIC_SCL = 0;
}

void I2CS_NAck(void)//不产生ACK应答
{
	IIC_SCL = 0;
	SDA_OUT();
	IIC_SDA = 1;
	Delay_us(2);
	IIC_SCL = 1;
	Delay_us(2);
	IIC_SCL = 0;
}					 				     


void I2CS_Send_Byte(u8 txd)//I2C发送一个字节,返回从机有无应答:1有应答,0无应答
{                        
	u8 t;   
	SDA_OUT(); 	    
	IIC_SCL = 0;//拉低时钟开始数据传输
	for(t = 0; t < 8; t++)
	{              
        //IIC_SDA=(txd&0x80)>>7;
		if((txd & 0x80) >> 7){	IIC_SDA = 1;	}
		else{	IIC_SDA = 0;	}
		txd <<= 1;
		Delay_us(2);//对TEA5767这三个延时都是必须的
		IIC_SCL = 1;
		Delay_us(2);
		IIC_SCL = 0;
		Delay_us(2);
	}	 
} 	    

u8 I2CS_Read_Byte(u8 ack)//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
{
	u8 i,receive = 0;
	SDA_IN();//SDA设置为输入
	for(i = 0; i < 8; i++ )
	{
		IIC_SCL = 0;
		Delay_us(2);
		IIC_SCL = 1;
		receive <<= 1;
		if(READ_SDA){	receive++;	}
		Delay_us(1);
	}
	if(!ack){	I2CS_NAck();	}//发送nACK
	else{	I2CS_Ack();	}//发送ACK   
	return receive;
}

u8 I2CS_Write_1Byte(u8 DeviceAddr,u8 WriteAddr,u8 Data)//模拟I2C写1字节
{
	I2CS_Start();//启动总线
	I2CS_Send_Byte(DeviceAddr & 0xFE);//发送设备地址(写)
	if(I2CS_Wait_Ack()){	I2C_ERROR(11);return 0;	}
	
	I2CS_Send_Byte(WriteAddr);//发送寄存器地址
	if(I2CS_Wait_Ack()){	I2C_ERROR(12);return 0;	}
	
	I2CS_Send_Byte(Data);//发送数据
	if(I2CS_Wait_Ack()){	I2C_ERROR(13);return 0;	}
	
	I2CS_Stop();//产生一个停止条件
	return 1;
}

u8 I2CS_Read_1Byte(u8 DeviceAddr,u8 WriteAddr)//模拟I2C读1字节
{
	u8 Data = 0;
	I2CS_Start();//启动总线
	I2CS_Send_Byte(DeviceAddr & 0xFE);//发送设备地址(写)
	if(I2CS_Wait_Ack()){	I2C_ERROR(31);return 0;	}
	
	I2CS_Send_Byte(WriteAddr);//发送寄存器地址
	if(I2CS_Wait_Ack()){	I2C_ERROR(32);return 0;	}
	
	I2CS_Start();//二次启动总线
	I2CS_Send_Byte(DeviceAddr | 0x01);//发送设备地址(读)
	if(I2CS_Wait_Ack()){	I2C_ERROR(33);return 0;	}
	
	Data = I2CS_Read_Byte(0);//读取数据且不应答
	I2CS_Stop();//产生一个停止条件
	return Data;
}

u8 I2CS_Write_nByte(u8 DeviceAddr,u8 WriteAddr,u8 *DataArray,u8 n)//模拟I2C写n字节
{
	I2CS_Start();//启动总线
	I2CS_Send_Byte(DeviceAddr & 0xFE);//发送设备地址(写)
	if(I2CS_Wait_Ack()){	I2C_ERROR(51);return 0;	}
	
	I2CS_Send_Byte(WriteAddr);//发送寄存器地址
	if(I2CS_Wait_Ack()){	I2C_ERROR(52);return 0;	}
	
	for(; n>0; n--)
	{
		I2CS_Send_Byte(*DataArray);//发送数据
		if(I2CS_Wait_Ack()){	I2C_ERROR(53);return 0;	}
		
		DataArray++;
	}
	I2CS_Stop();//产生一个停止条件
	return 1;
}

u8 I2CS_Read_nByte(u8 DeviceAddr,u8 ReadAddr,u8 *DataArray,u8 n)//模拟I2C读n字节
{
	I2CS_Start();//启动总线
	I2CS_Send_Byte(DeviceAddr & 0xFE);//发送设备地址(写)
	if(I2CS_Wait_Ack()){	I2C_ERROR(71);return 0;	}
	
	I2CS_Send_Byte(ReadAddr);//发送寄存器地址
	if(I2CS_Wait_Ack()){	I2C_ERROR(72);return 0;	}
	
	I2CS_Start();//二次启动总线
	I2CS_Send_Byte(DeviceAddr|0x01);//发送设备地址(读)
	if(I2CS_Wait_Ack()){	I2C_ERROR(73);return 0;	}
	
	for(; n>1; n--)
	{
		*DataArray = I2CS_Read_Byte(1);//读取数据且应答
		DataArray++;
	}
	*DataArray = I2CS_Read_Byte(0);//读取数据且不应答
	I2CS_Stop();//产生一个停止条件
	return 1;
}
#endif

void I2C_ERROR(u8 errNum)//I2C错误处理函数
{
	printf("I2C_err%d\n", errNum);//串口打印错误信息
	//PCout(13) = 0;//点亮板载LED
	#if (defined I2C1D)||(defined I2C1R)||(defined I2C2D)//若定义硬件I2C
	I2C_ERROR_Count++;
	
	if(((u8)(errNum / 10) % 2) == 0){
		I2C_GenerateSTOP( I2Cx,ENABLE );//若来自硬件I2C错误则发送硬件I2C结束符
	}
	
	if(I2C_ERROR_Count >= 8){//多次错误则重启I2C总线
		I2C_ERROR_Count = 0;
		I2C_DeInit(I2Cx);
		I2C_Config();
		printf("I2C_RESTART\n");//串口打印I2C重启信息
	}
	#endif	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值