STM32软件模拟IIC的实现

1、IO口配置

将IO口配置为开漏输出模式 GB6与GB7两个管脚

  GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

2、软件模拟

2.1宏定义

//IO操作函数	 
/* Exported types ------------------------------------------------------------*/
typedef struct
{
	unsigned short int b0  : 1;
	unsigned short int b1  : 1;
	unsigned short int b2  : 1;
	unsigned short int b3  : 1;
	unsigned short int b4  : 1;
	unsigned short int b5  : 1;
	unsigned short int b6  : 1;
	unsigned short int b7  : 1;
	unsigned short int b8  : 1;
	unsigned short int b9  : 1;
	unsigned short int b10 : 1;
	unsigned short int b11 : 1;
	unsigned short int b12 : 1;
	unsigned short int b13 : 1;
	unsigned short int b14 : 1;
	unsigned short int b15 : 1;
} REG16_TypeDef;

/******************************************************************************
                                  GPIO地址映射
                          基地址加上寄存器偏移地址组成
******************************************************************************/

#define GPIOA_ODR_Addr                           (GPIOA_BASE + 0x14)  //0x48000014
#define GPIOB_ODR_Addr                           (GPIOB_BASE + 0x14)  //0x48000414
#define GPIOC_ODR_Addr                           (GPIOC_BASE + 0x14)  //0x48000814
#define GPIOD_ODR_Addr                           (GPIOD_BASE + 0x14)  //0x48000C14
#define GPIOE_ODR_Addr                           (GPIOE_BASE + 0x14)  //0x48001014
#define GPIOF_ODR_Addr                           (GPIOF_BASE + 0x14)  //0x48001414

#define GPIOA_IDR_Addr                           (GPIOA_BASE + 0x10)  //0x48000010
#define GPIOB_IDR_Addr                           (GPIOB_BASE + 0x10)  //0x48000410
#define GPIOC_IDR_Addr                           (GPIOC_BASE + 0x10)  //0x48000810
#define GPIOD_IDR_Addr                           (GPIOD_BASE + 0x10)  //0x48000C10
#define GPIOE_IDR_Addr                           (GPIOE_BASE + 0x10)  //0x48001010
#define GPIOF_IDR_Addr                           (GPIOF_BASE + 0x10)  //0x48001410


/******************************************************************************
                       实现单一IO操作,类似于51的IO操作
                              n值要小于IO具体数目
******************************************************************************/
#define PAout                                    ((volatile REG16_TypeDef *)GPIOA_ODR_Addr)
#define PAin                                     ((volatile REG16_TypeDef *)GPIOA_IDR_Addr)

#define PBout                                    ((volatile REG16_TypeDef *)GPIOB_ODR_Addr)
#define PBin                                     ((volatile REG16_TypeDef *)GPIOB_IDR_Addr)

#define PCout                                    ((volatile REG16_TypeDef *)GPIOC_ODR_Addr)
#define PCin                                     ((volatile REG16_TypeDef *)GPIOC_IDR_Addr)

#define PDout                                    ((volatile REG16_TypeDef *)GPIOD_ODR_Addr)
#define PDin                                     ((volatile REG16_TypeDef *)GPIOD_IDR_Addr)

#define PEout                                    ((volatile REG16_TypeDef *)GPIOE_ODR_Addr)
#define PEin                                     ((volatile REG16_TypeDef *)GPIOE_IDR_Addr)

#define PFout                                    ((volatile REG16_TypeDef *)GPIOF_ODR_Addr)
#define PFin                                     ((volatile REG16_TypeDef *)GPIOF_IDR_Addr)
	

//IO操作函数	 
#define IIC_SCL    PBout->b6 //SCL
#define IIC_SDA    PBout->b7 //SDA	 
#define Read_SDA   PBin->b7  //输入SDA 

2.2、Start信号

当SCL为高时,SDA下跳为起始信号

// 发送IIC起始信号
uint8_t IIC_Start(void)
{
    IIC_SCL=1;	// 拉高时钟线IIC_SCL = 1;IIC_SDA=1;
    IIC_SDA=1; // 拉高信号线
    Delay_Us(2);
    if(!Read_SDA_Pin)		return 0;
    IIC_SDA=0;
    Delay_Us(2);

		IIC_SCL=0;
    return 1;
}

2.3、STOP信号

当SCL为高时,SDA上跳为停止信号。

//产生IIC停止信号
uint8_t IIC_Stop(void)
{	
	IIC_SCL=0;
	IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
 	Delay_Us(2);
	IIC_SCL=1;
	Delay_Us(2);	
	IIC_SDA=1;//发送I2C总线结束信号
	if(!Read_SDA_Pin)	return 0;
	Delay_Us(2);	
	return 1;	   	
}

2.4、ACK信号

//产生ACK应答		    
void IIC_Ack(void)
{
	IIC_SCL=0;
	IIC_SDA=1;
	Delay_Us(2);
	IIC_SCL=1;
	IIC_SDA=1;
	Delay_Us(5);
	IIC_SCL=0;
}	

2.5、NACK信号

//不产生ACK应答
void IIC_NAck(void)
{
	IIC_SCL=0;
	IIC_SDA=0;
	Delay_Us(2);
	IIC_SCL=1;
	Delay_Us(5);
	IIC_SCL=0;
}

2.6、等待ACK信号

//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功

uint8_t IIC_Wait_Ack(void)
{
	uint8_t ucErrTime=0;
	IIC_SDA=1;Delay_Us(1);	   
	IIC_SCL=1;Delay_Us(2);	 
	while(Read_SDA_Pin)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return 1;
		}
	}	
	IIC_SCL=0;//时钟输出0 	   
	return 0; 
}

2.7、发送一个字节

//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答			  
uint8_t IIC_Send_Byte(uint8_t txd)
{                        
    uint8_t t;
    uint8_t Status;
    for(t=0;t<8;t++)
    {   
		IIC_SCL=0;//数据准备
		Delay_Us(2);           
      	IIC_SDA = ( txd &0x80)>>7;
     	 txd <<= 1; 	  		
//开始发送数据			
		IIC_SCL=1;
		Delay_Us(2);
    }		
		IIC_SCL=0;	
	 	Status = IIC_Wait_Ack();
		return Status;	
} 	

2.8、接收一个字节

//// IIC读取一个字节
//////读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
uint8_t IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;
	for(i=0;i<8;i++ )
	{ 
		//准备数据
		IIC_SCL=0;
		Delay_Us(2);
		//传输数据
		IIC_SCL=1;	
		receive<<=1;
		if(Read_SDA_Pin)
			receive++;  			
		Delay_Us(2); 
	}			
	IIC_Ack();	
	return receive;
}	

写通讯过程:

  1. 主控在检测到总线空闲的
  2. 状况下,首先发送一个START信号掌管总线;
  3. 发送一个地址字节(包括7位地址码和一位R/W);
  4. 当被控器件检测到主控发送的地址与自己的地址相同时发送一个应答信号(ACK);
  5. 主控收到ACK后开始发送第一个数据字节;
  6. 被控器收到数据字节后发送一个ACK表示继续传送数据,发送NACK表示传送数据结束;
  7. 主控发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;
IIC_Start();			
IIC_Send_Byte(0x20);
IIC_Send_Byte(0x10);
IIC_Send_Byte(0x00);
IIC_Send_Byte(0x00);
IIC_Stop();//产生一个停止条件 

以上代码为发送三个数据0x10、0x00、0x00到IIC的地址0x10。
写的时序图如下图所示
在这里插入图片描述

读通讯过程:

主控在检测到总线空闲的状况下,首先发送一个START信号掌管总线;
发送一个地址字节(包括7位地址码和一位R/W,最后1bit为0,表示写),将地址写入地址计数器;
当被控器件检测到主控发送的地址与自己的地址相同时发送一个应答信号(ACK);
主控收到ACK后紧接着发送一个start状态,然后重复第一个地址字节(device code)最后1bit为’1’,代表读。
释放数据总线,开始接收第一个数据字节;
主控收到数据后发送ACK表示继续传送数据,发送NACK表示传送数据结束;

IIC_Start();				//START
IIC_Send_Byte(0X20);	   //发送写命令 0010 0000	
IIC_Send_Byte(ReadAddr);   //发送低地址	    	 
IIC_Start();	   		  //START
IIC_Send_Byte(0X21);  	 //进入接收模式		0010 0001	   
temp  = IIC_Read_Byte(1);
temp1 = IIC_Read_Byte(1);
temp = (temp1 << 8) |  temp;
IIC_Stop();//产生一个停止条件	

以上代码为读取0x10地址的两个字节的数据。

读取数据的时序为
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章