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地址的兩個字節的數據。

讀取數據的時序爲
在這裏插入圖片描述

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