最全的STM8 IIC学习笔记

首先说一下,这也就是个人学习笔记,如果有不对的地方欢迎各位大神指出。软件部分的代码网上到处都是,我这份不过是自己手敲了一遍,确保可用而已。

最近公司要在ST8的板子上使用硬件IIC,网上这方面的资料太少了,就想着把自己的学习经历记录一下,也给后来的人省一些时间。这份笔记更多的不是代码,而是为什么要这么写代码。想直接抄作业的朋友们可以直接看每个大章节的最后一节。

IIC分为硬件实现和软件模拟,这两种在工作模式下都有四种工作模式
● 从发送器
● 从接收器
● 主发送器
● 主接收器
 

1.软件IIC主机实现

1.1 初始化

万物始于初始化嘛,首先肯定是要写个初始化

因为使用的是软件模拟IIC,任意使用两个IO口作为SDA和SCL,只要和你硬件接的是一样的就可以了。ST8 IIC模块对于IO口时钟也没有单独的管理,所以初始化非常简单。

void box_I2C_Init(void)
{

    /* 内部时钟 16 M 不分频 */
    CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8); 
    GPIO_Init(IIC_GPIO, IIC_SCLPIN, GPIO_MODE_OUT_PP_HIGH_FAST);
    GPIO_Init(IIC_GPIO, IIC_SDAPIN, GPIO_MODE_OUT_PP_HIGH_FAST);
}

1.2 延时

//软件延时

void delay_1us(uint32_t num)
{
    for(int i=0;i

1.3 开始

由图可知,开始时SCL置高,SDA线输出一个高到底的信号。图是stm32f4 的数据手册里的图,但是IIC的逻辑是一样的,st8的数据手册里没有这个图。

 

最全的STM8 IIC学习笔记_第1张图片

//发送开始信号
void box_I2C_Start(void)
{
    SDA_OUT();          //SDA设置为输出
    SDA_HIGH();            //拉高SDA
    SCL_HIGH();         //拉高SCL
    delay_us(2);        //等待2us
    SDA_LOW();          //拉低SDA,给一个由高到低的跳变
    delay_us(2);        //等待2us
    SCL_LOW();          //钳住I2C时钟总线,准备发送或接收数据 
}

1.3 终止信号

还是如上图所示,截止时SCL需要保持在高电平,SDA有一个由低到高的跳变

//发送停止信号
void box_I2C_Stop(void)
{
    SDA_OUT();          //SDA设置为输出
    SDA_LOW();            //拉低SDA
    SCL_HIGH();         //拉高SCL
    delay_us(2);        //等待2us
    SDA_HIGH();          //拉低SDA,给一个由低到高的跳变
    delay_us(2);        //等待2us
}

1.4 ACK应答与NACK应答

应答信号是第九个时钟周期内发生的情况。ACK(低电平)---规定为有效应答位,NACK(高电平),规定为非应答位。应答和非应答都是一种信号,应答表示通信还要继续,非应答表示通信要结束了。这里要注意一下,不发送应答信号和非应答信号不是一回事。

SCL一直由主机控制,开始和结束信号都由主机控制

SDA根据读写确定传输数据方向:主机->从机、从机->主机

SDA数据按位发送(或读取),每发送8位,第9位被判定为应答和非应答

应答和非应答可由主机发送给从机,可以由从机发送给主机。

最全的STM8 IIC学习笔记_第2张图片

//产生一个ACK应答信号
void box_I2C_Ack(void)
{
      SCL_LOW();          //拉低SCL
      SDA_OUT();          //SDA设置为输出
      SDA_LOW();          //拉低SDA
      delay_1us(4);        //保持4us
      SCL_HIGH();         //拉高SCL
      delay_1us(4);        //保持4us
      SCL_LOW();          //拉低SCL
}

//产生一个NACK非应答信号
void box_I2C_NAck(void)
{
      SCL_LOW();          //拉低SCL
      SDA_OUT();          //SDA设置为输出
      SDA_HIGH();          //拉低SDA
      delay_1us(4);        //保持4us
      SCL_HIGH();         //拉高SCL
      delay_1us(4);        //保持4us
      SCL_LOW();          //拉低SCL
}

 

1.5 等待一个应答

最全的STM8 IIC学习笔记_第3张图片

由图可知,在已经经过八个时钟周期后,我们此时应当等待一个应答。逻辑是先拉高SDA和SCL,然后去读SDA线的状态,如果在250次判断(可以适当拉长,不要超过256)内读到SDA变为0,则说明成功。如果没有读到,就说明没有接收到ACK信号,执行结束并返回读取失败。

//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
u8 box_I2C_Wait_Ack(void)
{
    	u8 uTime=0;
        SDA_IN(); //SDA设置为输入  
//	SDA_HIGH();   
	SCL_HIGH();
	delay_1us(4);	 	

	while(GPIO_ReadInputPin(IIC_GPIO,SDAPIN))
	{
		uTime++;
		if(uTime>250)
		{
			box_I2C_Stop();
			return 1;
		}
	}
	SCL_LOW();//时钟输出0 	   
	return 0;
}

到此为止,准备工作基本完成,接下来可以写点有逻辑的东西了。

1.6 写一个字节

这段代码的思想就是先发送一个字节中的最高位,然后左移,依次发送8次,走完也就发完了一个字节,代码如下。

//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答			  
void box_I2C_Send_Byte(u8 txd)
{                        
    u8 t;//用来计数的临时变量   
    SDA_OUT();  //配置数据为输出	    
    SCL_LOW();  //拉低时钟开始数据传输
    for(t=0;t<8;t++)//发送八位
    {              
        if((txd&0x80)>>7)//判断最高位数据  0x1000 0000
        {
            SDA_HIGH();//如果最高位为1,SDA线置高发送1
        }
        else
        {
            SDA_LOW();//如果最高位为0,SDA线置低发送0
        }
					
        txd<<=1; 	//将txd左移一位 ,等待下次发送
        //写EEprom需要加这个延时
//		delay_us(1);   //对TEA5767这三个延时都是必须的
//		SCL_HIGH();
//		delay_us(1); 
//		SCL_LOW();	
//		delay_us(1);
    }	
	box_I2C_Wait_Ack();	
} 

1.7 读一个字节

读一个字节算是主接收器,主机接收数据。

这段代码逻辑思想是:因为时候串行同行,只能按位接收,所以定义1个u8类型的变量recive,用来存储读入的数据,每个时钟周期内读到一位数据,如果是高电平,就令recive+1然后左移一位,如果是低电平,就不进行操作直接左移一位,循坏八次后,正好读出一个字节的数据。

//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
u8 box_I2C_Read_Byte()
{
	unsigned char i,receive=0;
	SDA_OUT();//SDA设置为输出
	SDA_HIGH();//SDA输出一个高电平
	SDA_IN();//SDA设置为输入
    for(i=0;i<8;i++ )
	{
        SCL_LOW(); 
        delay_us(2);				
        SCL_HIGH();        
        delay_us(2);
        //一个时钟周期,把receive左移一位,最低位
        receive<<=1;
        if(GPIO_ReadInputPin(IIC_GPIO,SDAPIN))receive++; 
        SCL_LOW();
        delay_us(2);		
				 
    }					 
//    if (!ack)
//        IIC_NAck();//发送nACK
//    else
//        IIC_Ack(); //发送ACK   
    return receive;
}

1.8 与eeprom交互

IIC 软件模拟底层模块到此就结束了,接下来的步骤是由另一个IIC决定的,也就是IIC从机,一般使用的比较多的从机是eeprom设备。简单的说一下写一下。

这个写数据和上边那个写数据有什么不同呢,两方通信就要满足两方的规则,可以简单的理解为上边那个发送写完,我们就能发出去一个数据了,但是这个数据是什么,对方怎么接受,还需要满足对方的规则。话不多说,老规矩,上图。

最全的STM8 IIC学习笔记_第4张图片

这张图就是向从机中写入数据的时序图,可以看到,我们需要发送一个开始信号,然后发送设备地址(注意是从设备地址),然后发送内部地址(也就是数据在从设备中存放的地址),然后发送数据,最后发送停止信号,只有按这样的格式发送出去的数据,从设备才能识别,才能认识。

//一般的写逻辑
void box_I2C_Write(u8 reg_addr,u8 data)
{
	box_I2C_Start();//开始信号
	box_I2C_Send_Byte(Slave_Address);//第一个八位数据发送的是从设备地址
	box_I2C_Send_Byte(reg_addr);//第二个八位数据发送的是从设备内部地址,一般也就是这个数据要存放在从设备的那个地址
	box_I2C_Send_Byte(data);//第三个八位数据发送的是从设备地址,开始写入数据
	box_I2C_Stop();//停止信号
}

最全的STM8 IIC学习笔记_第5张图片

读逻辑要相对复杂一些。宗旨还是看图写程序,他需要什么东西就给他发送什么东西。和上一段没有什么本质不同,这里就不细说了,直接上源码。

//一般的读逻辑
u8 box_I2c_Read(u8 addr)
{
	u8 data;
	box_I2C_Start();
	box_I2C_Send_Byte(Slave_Address);
	box_I2C_Send_Byte(addr);
//	IIC_Stop();
	box_I2C_Start();
	box_I2C_Send_Byte(Slave_Address+1);
	data=box_I2C_Read_Byte();
	box_I2C_NAck();
	box_I2C_Stop();
	return data;
}

到这里为止的代码网上到处都是,我这份不过是自己手敲了一遍,确保可用。软件模拟IIC的方法写在哪个板子里都差不多,写在STM32里初始化时候记得开时钟就可以了。别的板子的要用做煮粉代码的时候改一下端口改一下头文件就能用了,这就是软件模拟的好处,可移植性强。缺点也很明显,就是主机在进行IIC通信时,只能进行IIC通信,CPU利用率低,速度慢。

以下是软件IIC源码

/**
  ******************************************************************************
  * @file    box_i2c.h
  * @author  box
  * @version V1.0.0
  * @date    2021-6-10
  ******************************************************************************
  */

#include "stm8s.h"
#include "stm8s_gpio.h"
extern GPIO_Pin_TypeDef SDAPIN ;
extern GPIO_Pin_TypeDef SCLPIN ;
#define IIC_GPIO        GPIOE
#define IIC_SDAPIN      GPIO_PIN_2
#define IIC_SCLPIN      GPIO_PIN_1
#define u8 uint8_t
#define u16 uint16_t
	
#define	Host_Address		0x75
#define	Slave_Address	        0xD0 //IIC写入时的地址字节数据,+1为读取

//IO方向设置
#define SDA_IN()        {GPIO_Init(IIC_GPIO, IIC_SDAPIN, GPIO_MODE_IN_PU_NO_IT);}
#define SDA_OUT()       {GPIO_Init(IIC_GPIO, IIC_SDAPIN, GPIO_MODE_OUT_PP_HIGH_FAST);}
#define SDA_HIGH()      {GPIO_WriteHigh(IIC_GPIO,IIC_SDAPIN);}
#define SDA_LOW()       {GPIO_WriteLow(IIC_GPIO,IIC_SDAPIN);}
#define SCL_HIGH()      {GPIO_WriteHigh(IIC_GPIO,IIC_SCLPIN);}
#define SCL_LOW()       {GPIO_WriteLow(IIC_GPIO,IIC_SCLPIN);}
//IO操作函数	  
#define READ_SDA()   {(u8)GPIO_ReadInputPin(IIC_GPIO,IIC_SDAPIN) }//输入SDA 

//IIC所有操作函数
void box_I2C_Init(void);                        //初始化IIC的IO口				 
void box_I2C_Start(void);			//发送IIC开始信号
void box_I2C_Stop(void);	  		//发送IIC停止信号
void box_I2C_Send_Byte(u8 txd);			//IIC发送一个字节
u8 box_I2C_Read_Byte(void);
u8 box_I2C_Wait_Ack(void); 			//IIC等待ACK信号
void box_I2C_Ack(void);				//IIC发送ACK信号
void box_I2C_NAck(void);			//IIC不发送ACK信号
void delay_1us(uint32_t num);
void delay_1ms(uint32_t num);
void box_I2C_Write(u8 reg_addr,u8 data);
u8 box_I2c_Read(u8 addr);
/**
  ******************************************************************************
  * @file    box_i2c.c
  * @author  box
  * @version V1.0.0
  * @date    2021-6-10
  ******************************************************************************
  */


#include "box_i2c.h"

 
void delay_1us(uint32_t num)
{
    for(int i=0;i250)
		{
			box_I2C_Stop();
			return 1;
		}
	}
	SCL_LOW();//时钟输出0 	   
	return 0;
}

//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答			  
void box_I2C_Send_Byte(u8 txd)
{                        
    u8 t;//用来计数的临时变量   
    SDA_OUT();  //配置数据为输出	    
    SCL_LOW();  //拉低时钟开始数据传输
    for(t=0;t<8;t++)//发送八位
    {              
        if((txd&0x80)>>7)//判断最高位数据  0x1000 0000
        {
            SDA_HIGH();//如果最高位为1,SDA线置高发送1
        }
        else
        {
            SDA_LOW();//如果最高位为0,SDA线置低发送0
        }
					
        txd<<=1; 	//将txd左移一位 ,等待下次发送
//		delay_us(1);   //对TEA5767这三个延时都是必须的
//		SCL_HIGH();
//		delay_us(1); 
//		SCL_LOW();	
//		delay_us(1);
    }	
	box_I2C_Wait_Ack();	
} 

//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
u8 box_I2C_Read_Byte()
{
	unsigned char i,receive=0;
	SDA_OUT();//SDA设置为输出
	SDA_HIGH();//SDA输出一个高电平
	SDA_IN();//SDA设置为输入
    for(i=0;i<8;i++ )
	{
        SCL_LOW(); 
        delay_us(2);				
        SCL_HIGH();        
        delay_us(2);
        //一个时钟周期,把receive左移一位,最低位
        receive<<=1;
        if(GPIO_ReadInputPin(IIC_GPIO,IIC_SDAPIN))receive++; 
        SCL_LOW();
        delay_us(2);		
				 
    }					 
//    if (!ack)
//        IIC_NAck();//发送nACK
//    else
//        IIC_Ack(); //发送ACK   
    return receive;
}


//一般的写逻辑
void box_I2C_Write(u8 reg_addr,u8 data)
{
	box_I2C_Start();//开始信号
	box_I2C_Send_Byte(Slave_Address);//第一个八位数据发送的是从设备地址
	box_I2C_Send_Byte(reg_addr);//第二个八位数据发送的是从设备内部地址,一般也就是这个数据要存放在从设备的那个地址
	box_I2C_Send_Byte(data);//第三个八位数据发送的是从设备地址,开始写入数据
	box_I2C_Stop();//停止信号
}
//一般的读逻辑
u8 box_I2c_Read(u8 addr)
{
	u8 data;
	box_I2C_Start();
	box_I2C_Send_Byte(Slave_Address);
	box_I2C_Send_Byte(addr);
//	IIC_Stop();
	box_I2C_Start();
	box_I2C_Send_Byte(Slave_Address+1);
	data=box_I2C_Read_Byte();
	box_I2C_NAck();
	box_I2C_Stop();
	return data;
}


2. 硬件IIC主机实现

首先STM32标准库的硬件IIC部分肯定是有问题的,这一点毋庸置疑。那么这个问题是什么,又是怎么解决的,请看下文。

2.1 初始化

首先还是万物始于初始化,

ST8这个板子里没有对于IO口的单独时钟管理,所以只要记得开IIC时钟就可以了。

既然要用硬件IIC,直接使用标准库里的IIC_Init(),记得在项目中添加文件stm8s_i2c.c,记得包含头文件

接下来就是看参数应该怎么填了,来看下库里的说明

/**
  * @brief  Initializes the I2C according to the specified parameters in standard
  *         or fast mode.
  * @param  OutputClockFrequencyHz : Specifies the output clock frequency in Hz.
  * @param  OwnAddress : Specifies the own address.
  * @param  I2C_DutyCycle : Specifies the duty cycle to apply in fast mode.
  *         This parameter can be any of the  @ref I2C_DutyCycle_TypeDef enumeration.
  * @note   This parameter don't have impact when the OutputClockFrequency lower
  *         than 100KHz.
  * @param  Ack : Specifies the acknowledge mode to apply.
  *         This parameter can be any of the  @ref I2C_Ack_TypeDef enumeration.
  * @param  AddMode : Specifies the acknowledge address to apply.
  *         This parameter can be any of the  @ref I2C_AddMode_TypeDef enumeration.
  * @param  InputClockFrequencyMHz : Specifies the input clock frequency in MHz.
  * @retval None
  */

那么,翻译成人话是什么意思呢,

void I2C_Init(uint32_t OutputClockFrequencyHz, //IIC的输出时钟频率Hz 100k或400k
              uint16_t OwnAddress,  //本I2C设备地址
              I2C_DutyCycle_TypeDef I2C_DutyCycle, //占空比
              I2C_Ack_TypeDef Ack, //应答模式
              I2C_AddMode_TypeDef AddMode, //地址模式设置, 7 or 10
              uint8_t InputClockFrequencyMHz //输入时钟频率MHz,提供给I2C硬件时钟
              );


void IIC_Init(void)
{
   CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); /* 内部时钟 16 M 不分频 */
   CLK_PeripheralClockConfig (CLK_PERIPHERAL_I2C,ENABLE);//开启IIC时钟  
   I2C_DeInit();//复位
   GPIO_Init(GPIOE, SCLPIN, GPIO_MODE_OUT_OD_HIZ_FAST);//PE1 I2C_SCL 功能引脚,总线的时钟脚,设为高速开漏高阻输出。
   GPIO_Init(GPIOE, SDAPIN, GPIO_MODE_OUT_OD_HIZ_FAST);//PE2 I2C_SDA 功能引脚,总线的数据脚,设为高速开漏高阻输出
    
   
   I2C_Init(100000, //输出时钟频率Hz 100k
            Host_address, //本I2C设备地址
            I2C_DUTYCYCLE_2, //2 or 17,占空比
            I2C_ACK_CURR, //应答模式
            I2C_ADDMODE_7BIT, //地址设置, 7 or 10
            8//输入时钟频率MHz,提供给I2C硬件时钟
            );//初始化I2C
   
   I2C_Cmd(ENABLE);//使能I2C
   r = 1;//调试用的变量

}

2.2 写一个字节

使用库里的函数去进行一个写字节的操作,但是ST8的手册中没有传输序列图,这里就参考了STM32的。和软件模拟一样,想要双方进行通信,不光需要满足主发送器的逻辑,还要满足从接收器的逻辑。

最全的STM8 IIC学习笔记_第6张图片

最全的STM8 IIC学习笔记_第7张图片

最全的STM8 IIC学习笔记_第8张图片

最全的STM8 IIC学习笔记_第9张图片

这里为了方便对比,根据四个图的逻辑做了一个表。

最全的STM8 IIC学习笔记_第10张图片

根据以上我总结的那部分逻辑,不难写出一个 写一个字节的函数  (r是一个用来调试的函数,看r的值卡在多少就说明函数卡在哪一步)

void IIC_Write(u8 reg_addr,u8 data)
{
r=30;
  /*开始*/
r=31;
  I2C_GenerateSTART(ENABLE);//开启I2C
r=32;
  while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));/*等待EV5,主模式*/
r=33;
  I2C_Send7bitAddress(Slave_Address, I2C_DIRECTION_TX);器件地址 -- Slave_Address
r=34;
  while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));/*等待EV6*/
r=35;
  I2C_SendData(reg_addr);//寄存器地址
  while (!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTING));//等待传输完成,等待EV8事件
r=36;
  I2C_SendData(data);//发送数据
  while (!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));//等待传输完成,等待EV8_2事件
r=37; 
  I2C_AcknowledgeConfig(I2C_ACK_CURR);//使能或者失能 I2C的应答功能
r=38;
  I2C_GenerateSTOP(ENABLE);//关闭I2C总线
 r = 40;
}

 

2.3 读一个字节

还是看上边总结出来的逻辑图,一步步的实现,就可以得到如下代码

u8 IIc_Read(u8 addr)
{
 uint8_t i;
r=1;
  while(I2C_GetFlagStatus(I2C_FLAG_BUSBUSY));//等待空闲
r=2;
  I2C_GenerateSTART(ENABLE);//开启I2C
  while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));/*等待EV5,主模式*/
r=3;
  I2C_Send7bitAddress(Slave_Address,I2C_DIRECTION_TX);//器件地址 -- Slave_Address
  while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));/*等待EV6事件*/
r=4;
  I2C_SendData(addr);//寄存器地址
r=5;  
  while (!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));//等待传输完成
r=6;


  I2C_GenerateSTART(ENABLE);//开启I2C1 
  while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));/*EV5,主模式*/
r=7;
  I2C_Send7bitAddress(Slave_Address,I2C_DIRECTION_RX);//器件地址 -- Slave_Address
r=8;
  while (!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_RECEIVED));//等待EV7事件,接收完成
r=8;
  i=I2C_ReceiveData();//读取数据,在DR寄存器内
r=9;
  I2C_AcknowledgeConfig(I2C_ACK_CURR);//使能应答
r=10;
  I2C_GenerateSTOP(ENABLE);//关闭I2C1总线
  r = 99;
  return i;
}

这个部分就比较有意思了,正常的初始化读写等操作写万以后下载调试,发现总是卡死,从调试上看r=34,卡在了while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));/*等待EV6*/这一步

最全的STM8 IIC学习笔记_第11张图片

后来上了逻辑分析仪,看到的现象和调试时一样。

从代码上来看,每次都会卡死在Event判断,那么这是为什么呢,刚开始以为是这句话的问题查了好久,后来发现是上一句话的问题

最全的STM8 IIC学习笔记_第12张图片

再看一下库里的I2C_CheckEvent()函数的操作。这里只进行了读操作但是并没有进行写DR寄存器的操作,所以会导致程序卡死,

ErrorStatus I2C_CheckEvent(I2C_Event_TypeDef I2C_Event)
{
  __IO uint16_t lastevent = 0x00;
  uint8_t flag1 = 0x00 ;
  uint8_t flag2 = 0x00;
  ErrorStatus status = ERROR;

  /* Check the parameters */
  assert_param(IS_I2C_EVENT_OK(I2C_Event));

  if (I2C_Event == I2C_EVENT_SLAVE_ACK_FAILURE)
  {
    lastevent = I2C->SR2 & I2C_SR2_AF;
  }
  else
  {
    flag1 = I2C->SR1;
    flag2 = I2C->SR3;
    lastevent = ((uint16_t)((uint16_t)flag2 << (uint16_t)8) | (uint16_t)flag1);
  }
  /* Check whether the last event is equal to I2C_EVENT */
  if (((uint16_t)lastevent & (uint16_t)I2C_Event) == (uint16_t)I2C_Event)
  {
    /* SUCCESS: last event is equal to I2C_EVENT */
    status = SUCCESS;
  }
  else
  {
    /* ERROR: last event is different from I2C_EVENT */
    status = ERROR;
  }

  /* Return status */
  return status;
}

2.4 卡死的解决方法

初步定下的方法比较靠谱的方法有三种,我这里直实现了一种,有兴趣的大佬可以去写一写别的,

  • 1.不用标准库,直接写寄存器或者使用HAL库
  • 2.修改标准库
  • 3.增加额外的函数,等待一定次数后清除响应寄存器和标志位

这里用的是第三种方法,代码如下

/*写一个字节的数据
*data	要发送的数据
*reg_addr	保存数据的地址
*返回	0失败,1成功
*/
u8 IIC_Write(u8 reg_addr,u8 data)
{
   //1、产生起始
    I2C_GenerateSTART(ENABLE);
    I2CTimeout = I2CT_FLAG_TIMEOUT;
    /* 检测 EV5 事件(返回1)并清除标志,表示信号已发送, */
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT))
    {//返回0后等待一会,并结束
      if((I2CTimeout--) == 0) return r=0;//打印错误0
    }     
    
    //2、设备地址,发送或接受模式
    I2C_Send7bitAddress(Slave_Address,I2C_DIRECTION_TX);
    I2CTimeout = I2CT_FLAG_TIMEOUT;
    //检测事件6,并清除标志
     while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))//事件发生返回1,否则返回0
    {//返回0后等待一会,并结束
      if((I2CTimeout--) == 0) return r=1;//打印错误1
    }
    
    //3、发送E2PROM内部地址
    I2C_SendData(reg_addr);
    I2CTimeout = I2CT_FLAG_TIMEOUT;
    //检测事件8,并清除标志
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED))//事件发生返回1,否则返回0
    {//返回0后等待一会,并结束
      if((I2CTimeout--) == 0) return r=2;//打印错误2
    }
    
    //4、发送数据
    I2C_SendData(data);
    I2CTimeout = I2CT_FLAG_TIMEOUT;
    //检测事件8,并清除标志
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED))//事件发生返回1,否则返回0
    {//返回0后等待一会,并结束
      if((I2CTimeout--) == 0) return r=3;//打印错误3
    }
    
    //5发送停止
    I2C_GenerateSTOP(ENABLE);
    r=10;
    return (1); 
}

/*从地址为ReadAddr读取
***/
u32 IIc_Read(u8 ReadAddr)
{
	u8 tmp;
	I2C__WaitStandbyState();
	//1、等待总线空闲
	I2CTimeout = I2CT_LONG_TIMEOUT;
	while(I2C_GetFlagStatus(I2C_FLAG_BUSBUSY))   
        {if((I2CTimeout--) == 0) return r=51;}
	I2C__WaitStandbyState();//没有这句会超时
        
	//2、产生起始
	I2C_GenerateSTART(ENABLE);
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT))
	{if((I2CTimeout--) == 0) return r=52;	}
        
	//3、发送器件地址,方向 写
	I2C_Send7bitAddress(Slave_Address,I2C_DIRECTION_TX);
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* Test on EV6 and clear it清除SR1 */
	while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) 
	{if((I2CTimeout--) == 0) return r=53;	}
	/*通过重新设置 PE 位清除 EV6 事件 */
	I2C_Cmd(ENABLE);
	
	//4、发送内部地址(存放数据的地方)
	I2C_SendData(ReadAddr); 
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* Test on EV8 and clear it */
	while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED))
        {if((I2CTimeout--) == 0) return r=54;	}
        
	//5、产生开始
	I2C_GenerateSTART(ENABLE);
        I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* Test on EV5 and clear it 清除DR寄存器*/
	while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT))//
	{if((I2CTimeout--) == 0) return  r=55;	}
        
	//6、写从器件地址 方向 1读
	I2C_Send7bitAddress(Slave_Address, I2C_DIRECTION_RX);
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* Test on EV6 and clear it */
	while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
	{if((I2CTimeout--) == 0) return r=56;	}
        
	//7、产生非应答和停止位,在收到数据前设置,收到数据后生效
	I2C_AcknowledgeConfig(I2C_ACK_NONE);
	I2C_GenerateSTOP(ENABLE);
	//等待事件7
	I2CTimeout = I2CT_LONG_TIMEOUT;
	while(I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_RECEIVED)==0)  
	{if((I2CTimeout--) == 0) return r=57;	}
        
	//8、接收数据
	tmp=I2C_ReceiveData();//读数据	
    return tmp;
}

static u8 I2C__WaitStandbyState(void)
{
#define	MAX_NUM	300
	
	u16	tmpSR1	=0;
	u32	EETrials = 0;
	//总线忙是等待
	I2CTimeout = I2CT_LONG_TIMEOUT;
	while(I2C_GetFlagStatus(I2C_FLAG_BUSBUSY))//总线返回 繁忙状态 等待
	{if((I2CTimeout--) == 0) return r=20;	}
	
	//等待从机应答,最多等待 300 次
	while(1)
	{
		I2C_GenerateSTART(ENABLE);
		I2CTimeout = I2CT_FLAG_TIMEOUT;
		/* 检测 EV5 事件(返回1)并清除标志,表示信号已发送, */
		while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT))
		{if((I2CTimeout--) == 0) return r=21;		}
		
		//发送设备地址,发送或接受模式
		I2C_Send7bitAddress(Slave_Address,I2C_DIRECTION_TX);
		I2CTimeout = I2CT_LONG_TIMEOUT;
		do
		{									//ADDR 1 发送结束,AF 1 应答失败
			tmpSR1=I2C->SR1;//获取 SR1 寄存器状态
			if((I2CTimeout--) == 0) return r=22;//地址发送未结束,或者,未发生应答失败
		}while((tmpSR1 & (I2C_SR2_AF | I2C_SR1_ADDR)) ==0 );//一直等待直到 addr 或 af 标志为 1
		/*这个AF待定,原为I2C_SR1_AF*/
		if(tmpSR1 & I2C_SR1_ADDR)//如果是发送结束
		{
			(void)I2C->SR2;//通过读取I2C_SR2清除ADDR
			I2C_GenerateSTOP(ENABLE);
			return 1;
		}
		else //如果是应答失败
		{
                    I2C_ClearFlag(I2C_FLAG_ACKNOWLEDGEFAILURE);//清除AF标志,原来为I2C_FLAG_AF		
                }
		if(EETrials++ == MAX_NUM)//最大等待300次
		{
                    return r=23;		
                }
	}
}

https://www.cnblogs.com/1024E/p/13322890.html
https://blog.csdn.net/zhejfl/article/details/82866684

如果这篇文章帮助到了您,希望可以给我点一个赞,求三连求硬币求转发哈哈哈,这对我真的很重要!

你可能感兴趣的:(关于ST8的IIC,单片机,stm32)