STM32 I2C調試過程心得記錄

又花了好幾天的時間調I2C,前前後後出現了很多問題,有一些是不仔細的問題,有一些是對於I2C協議不夠了解,手冊看的不夠認真,總之繼續學習,這裏將遇到的問題記錄下,以便日後查閱

  錯誤一:HardFault硬件錯誤,邏輯分析儀上顯示數據發送到一半就中斷了,但是總線上並沒有檢測到停止信號

  原因:這個錯誤我犯了兩次了,亂用指針,直接定義uint8_t * Rxbuffer來接收I2C數據,也沒有指定它的大小,造成野指針以及數組越界的問題

 

 錯誤二:硬件IO口上沒有數據輸出,檢測到總線一直處於busy狀態

 原因:I2C的IO口的複用功能沒有配置正確,PB6和PB7應該對應的是GPIO_AF_1而不是GPIO_AF_2(看手冊不夠仔細)

 

 錯誤三:主機循環發送數據,但從機中斷接收完一次數據後就不繼續接收

 解決辦法:從機中斷中檢測到StopF信號後重新使能一次I2C,I2C_Cmd(I2C1,ENABLE)

 

 錯誤四:主機循環查詢接收數據,但從機中斷髮送發送完一次數據後不繼續發送

 原因:

在103的中文參考手冊p511有關於STOPF的描述如下:

    STOPF:停止條件檢測位(從模式) (Stop detection (slave mode))
       0:沒有檢測到停止條件;
       1:檢測到停止條件。
      – 在一個應答之後(如果ACK=1),當從設備在總線上檢測到停止條件時,硬件將該位置’1’。
      – 軟件讀取SR1寄存器後,對CR1寄存器的寫操作將清除該位,或當PE=0時,硬件清除該位。
     注:在收到NACK後,STOPF位不被置位。

     最關鍵的是這個注,我們知道主機接收數據時,每接收到一個字節都會發回一個ACK信號(前提是主機配置要使能Ack_Enable),而當發送完最後一個字節時,主機這時發送的NACK。也就是說從機如果使能了AF中斷,當監測到總線上有NACK信號時就會進入中斷,而之後的Stop信號就算從機檢測到了也不會產生置位Stopf不會進入相應的中斷。

    而且,AF中斷是屬於I2C1_ER_IRQHandler這個中斷通道,在EV_IRQHandler中是檢測不到的,並且千萬記得要開啓I2C_IT_ERR中斷以及配置它的NVIC,不然中斷也不會響應

解決:開啓I2C_IT_ERR中斷並配置它的NVIC通道,在I2C1_ER_IRQHandler中處理AF中斷,清除AF標誌

 

代碼整理

STM32F070RB I2C硬件配置

    I2C_DeInit(I2C1);
	RCC_I2CCLKConfig(RCC_I2C1CLK_SYSCLK);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);


    /********I2C1 GPIO_Pins configuration***********/
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_1);
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_1);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //PB6 - SCL PB7 - SDA
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOB,&GPIO_InitStructure);

	/********I2C1 InitStruct configuration***********/
	I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
	I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C_InitStruct.I2C_AnalogFilter = I2C_AnalogFilter_Enable;
	I2C_InitStruct.I2C_DigitalFilter = 0x00;
	I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
	/*ÉèÖÃI2Cʱ¼ä¼Ä´æÆ÷Öµ,Ò²ÅäÖÃÁËI2C_Speed_Frequency ²Î¿¼ÅäÖù¤¾ßI2C_Timing_Config_Tool*/
	I2C_InitStruct.I2C_Timing = I2C_TIMING;  
	I2C_InitStruct.I2C_OwnAddress1 = 0x00;
	I2C_Init(I2C1,&I2C_InitStruct);
	
	//I2C_ClearFlag(I2C1,I2C_FLAG_BUSY);
	I2C_Cmd(I2C1,ENABLE);

 

I2C主機查詢發送數據

/******************************************************************
* Function Name : I2C_Write
* Description   : Write more than one byte to the Device with a 
*                 single WRITE cycle
* Input         : - DevAddr : Device Address
                  - RegAddr : Register Address
                  - pBuffer : pointer to the buffer containing the
                              data to be written to the Device
                  - NumByteToWrite : number of bytes to write
* Return        : I2C_Status
******************************************************************/
I2C_Status I2C_Write(uint8_t DevAddr, uint8_t* pBuffer, uint8_t NumByteToWrite)
{
	uint16_t timeout = I2C_TIMEOUT;
	
	/* while the bus is busy */
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY) != RESET)
	{
		if((timeout--) == 0)
		{
			return I2C_FAIL;
		}
	}
	
  I2C_TransferHandling(I2C1,DevAddr,NumByteToWrite ,I2C_AutoEnd_Mode,I2C_Generate_Start_Write);

		
	for(write_Num = 0;write_Num < NumByteToWrite;write_Num++)
	{
		I2C_SendData(I2C1,pBuffer[write_Num]);
		timeout = I2C_TIMEOUT;
	  while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXE) == RESET)
	  {
		  if((timeout--) == 0)
			{
				return1++;
				return I2C_FAIL;
			}
	  }	
  }
	
	timeout = I2C_TIMEOUT;
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_STOPF) == RESET)
	{
		if((timeout--) == 0)
		{
			return2++;
			return I2C_FAIL;
		}
	}
   	return I2C_OK;
}

 

STM32F103的從機中斷接收

void IIC_Init(void)
{
	GPIO_InitTypeDef      GPIO_InitStructure;
	I2C_InitTypeDef       I2C_InitStructure;
	NVIC_InitTypeDef      NVIC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
	
	/******** I2C1 GPIO_Pins Configuration***********/
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,	&GPIO_InitStructure);
	
	/******** I2C1 NVIC Configuration ***************/
	NVIC_InitStructure.NVIC_IRQChannel = I2C1_EV_IRQn;  //I2C Event IRQ
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	/********** I2C1 initial Configuration***********/
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
	I2C_InitStructure.I2C_OwnAddress1 = SLAVE_ADDRESS;
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C_InitStructure.I2C_ClockSpeed = 100000;
	I2C_Init(I2C1,&I2C_InitStructure);
	
	I2C_ITConfig(I2C1,I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR,ENABLE);
	I2C_Cmd(I2C1,ENABLE);  
}

void I2C1_EV_IRQHandler(void)
{
	switch(I2C_GetLastEvent(I2C1))
	{
		case I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED:
			break;
		/******* Slave receive mode *************/
		case I2C_EVENT_SLAVE_BYTE_RECEIVED:
			 read_buffer[Rx++] = I2C_ReceiveData(I2C1);
		   break;
		case I2C_EVENT_SLAVE_STOP_DETECTED:
             Rx = 0;
			 I2C_Cmd(I2C1,ENABLE);   //持續接收改成 I2C_Cmd(I2C1,ENABLE)
		   break;
 }
}

 

主機中斷髮送

I2C_Status I2C_INT_Write(uint8_t DevAddr, uint8_t* pBuffer, uint8_t NumByteToWrite)
{
	
	uint16_t timeout = I2C_TIMEOUT;
	uint8_t write_Num  = 0;
	Txptr = pBuffer;
	
	/* while the bus is busy */
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY) != RESET)
	{
		if((timeout--) == 0)
		{
			return I2C_FAIL;
		}
	}
	
	I2C_TransferHandling(I2C1,DevAddr,NumByteToWrite,I2C_AutoEnd_Mode,I2C_Generate_Start_Write);
	I2C_ITConfig(I2C1,I2C_IT_TXI,ENABLE);
	return I2C_OK;
}

 

主機查詢接收

I2C_Status I2C_Read(uint8_t DevAddr,uint8_t* pBuffer, uint8_t NumByteToRead)
{
	uint16_t timeout = I2C_TIMEOUT;
	
	/* while the bus is busy */
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY) != RESET)
	{
		if((timeout--) == 0)
			return I2C_FAIL;
	}
	
	/* Send Device address for read */
	I2C_TransferHandling(I2C1,DevAddr,NumByteToRead,I2C_AutoEnd_Mode,I2C_Generate_Start_Read);
		
	for(uint8_t read_Num = 0;read_Num < NumByteToRead;read_Num++)
	{
		timeout = I2C_TIMEOUT;
		while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE) == RESET)
	  {
		  if((timeout--) == 0)
			  return I2C_FAIL;
	  }
		pBuffer[read_Num] = I2C_ReceiveData(I2C1);
	}
	
	timeout = I2C_TIMEOUT;
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_STOPF) == RESET)
	{
		if((timeout--) == 0)
		{
			return I2C_FAIL;
		}
	}
	
	return I2C_OK;
}

從機中斷髮送 

void I2C1_EV_IRQHandler(void)
{
	switch(I2C_GetLastEvent(I2C1))
	{
		case I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED:
		case I2C_EVENT_SLAVE_TRANSMITTER_ADDRESS_MATCHED:
			I2C_ClearITPendingBit(I2C1,I2C_IT_ADDR);
		  break;
		/******* Slave transmit mode ************/
		case I2C_EVENT_SLAVE_BYTE_TRANSMITTED:
		case I2C_EVENT_SLAVE_BYTE_TRANSMITTING:
			I2C_SendData(I2C1,write_buffer[Tx++]);
			break;
		/******* Slave receive mode *************/
		case I2C_EVENT_SLAVE_BYTE_RECEIVED:
			 read_buffer[Rx++] = I2C_ReceiveData(I2C1);
		   break;
		case I2C_EVENT_SLAVE_STOP_DETECTED:
			 Rx = 0;
			 I2C_Cmd(I2C1,DISABLE);
		   break;
  }
}

void I2C1_ER_IRQHandler(void)
{
	switch(I2C_GetLastEvent(I2C1))
	{
			case I2C_EVENT_SLAVE_ACK_FAILURE:
			Tx = 0; 
		  I2C_ClearITPendingBit(I2C1,I2C_IT_AF);
	}
}

 

補充:

1、關於工作模式的切換

硬件I2C默認工作在Slave模式,當其產生一個Start信號時自動切換爲Master模式

而當仲裁丟失或者產生一個Stop信號時則自動由Slave模式切換爲Master模式

2、數據傳輸

在Master模式下,數據的傳輸在產生Start信號後開始,產生Stop信號後結束,而無論是Start信號或是Stop信號,都由軟件產生。

Start信號後緊跟的第一個字節(8 bit)包含着Slave的地址和讀寫位(0 write / 1 read)

3、7 bit地址和10 bit地址

7 bit地址模式下,Start信號後一個字節表示從機地址和讀寫位 Slave address + W/R );10 bit地址模式下,Start信號後的兩個字節表示從機地址和讀寫位(W/R + 1111 0 + Slave address

4、關於I2C的幾個模式解析:

   I2C_AutoEnd_Mode  ——  這種模式下,I2C傳輸完NBytes個字節後會自動產生停止信號以終止信號的傳輸,所以這個模式下的設備肯定是主機模式

   I2C_Reload_Mode  —— 自動裝載模式,在這種模式下傳輸完NBytes個字節後,I2C的TXR又會重新裝載需要傳輸的數據,每傳輸完NBytes個字節後TCR標誌位會置位

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