stm32 硬體IIC使用方法說明與範例---LIS3DH的IIC通訊

2020-10-16 22:00:24

出於某些原因,我們可能需要MCU進行速率較高的IIC匯流排通訊,一般stm32的IIC預設傳輸速率是100kpbs,最大為400kpbs。現在大部分專案會使用程式IO模擬的IIC,使用方便,具體網上例子很多。

這裡我需要使用LIS3DH三軸加速度感測器,獲取三個方向的加速度用於碰撞檢測

關於這個感測器的詳細設定,可以參考這篇:

https://blog.csdn.net/zhangfls/article/details/109021073

這個是LIS3DH加速度資料重新整理速率的設定暫存器:

出於某些原因,我需要達到500hz以上的資料重新整理速率,所以ODR要設定成0b1000,三軸晶片輸出1.6khz速率的加速度資訊。因為一條資訊包含x、y、z個位元組的資料,實際每讀取一個方向的暫存器需要總計4次讀寫操作,所以總共至少12位元組的通訊才能完整獲取到1次資料重新整理。則通訊的最小速率為1.6K*12*8=153600bps

開始用IO模擬IIC讀取,發現資料讀不全,總有一部分資料被漏掉。網上查了一下,好像軟體模擬IIC達不到這速率,所以只能考慮一下用stm32的硬體IIC,據說能到400Kbps。

這裡用的是STM32F105RCT6,與LIS3DH三軸加速度感測器進行IIC通訊,獲取三個方向的加速度用於碰撞檢測。

範例:stm32F1的IIC設定都差不多

一、IIC初始化:

void IIC1_Init()
{
	GPIO_InitTypeDef GPIO_InitStruct;
	I2C_InitTypeDef I2C_InitStruct;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

	//GPIO設定
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_OD;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	//IIC設定
	I2C_InitStruct.I2C_Ack=I2C_Ack_Enable;
	I2C_InitStruct.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;
	I2C_InitStruct.I2C_ClockSpeed=400000;
	I2C_InitStruct.I2C_DutyCycle=I2C_DutyCycle_2;
	I2C_InitStruct.I2C_Mode=I2C_Mode_I2C;
	I2C_InitStruct.I2C_OwnAddress1=0x55;
	I2C_Init(I2C1,&I2C_InitStruct);
	
	I2C_Cmd(I2C1,ENABLE);
}

二、IIC寫一個位元組暫存器(LIS3DH)

void IIC_WriteByte(u8 addr,u8 data)
{
    u16 timeout=1000;

	I2C_AcknowledgeConfig(I2C1,ENABLE);
	
	I2C_GenerateSTART(I2C1,ENABLE);    //start
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT))
	{
		  if((timeout--)==0)
			{
				RS232_Printf(1,"EV5 Fail");
				break;
			}
	}	timeout=1000;
	
	I2C_Send7bitAddress(I2C1,LIS_ADDR|0x00,I2C_Direction_Transmitter);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
	{
		  if((timeout--)==0)
			{
				RS232_Printf(1,"EV6 Fail");
				break;
			}
	}	timeout=1000;
	
	I2C_SendData(I2C1,addr);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED))
	{
		  if((timeout--)==0)
			{
				RS232_Printf(1,"EV8 Fail");
				break;
			}
	}	timeout=1000;
	
	I2C_SendData(I2C1,data);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED))
	{
		  if((timeout--)==0)
			{
				RS232_Printf(1,"EV8 Fail");
				break;
			}
	}	timeout=1000;
	
	I2C_GenerateSTOP(I2C1,ENABLE);
}

三、IIC讀一個位元組暫存器(LIS3DH)

u8 IIC_ReadByte(u8 addr)
{
	u8 readtemp;
    u16 timeout=1000;
	
    I2C_AcknowledgeConfig(I2C1,ENABLE);
	
	I2C_GenerateSTART(I2C1,ENABLE);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT))
	{
		  if((timeout--)==0)
			{
				RS232_Printf(1,"start Fail");
				break;
			}
	}	timeout=1000;
	
	I2C_Send7bitAddress(I2C1,LIS_ADDR|0x00,I2C_Direction_Transmitter);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
	{
		  if((timeout--)==0)
			{
				RS232_Printf(1,"EV5 Fail");
				break;
			}
	}	timeout=1000;
	
	I2C_SendData(I2C1,addr);
  while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS )
  {
		  if((timeout--)==0)
			{
				RS232_Printf(1,"EV8 Fail");
				break;
			}
	}	timeout=1000;
	
	I2C_GenerateSTART(I2C1,ENABLE);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT))
	{
		  if((timeout--)==0)
			{
				RS232_Printf(1,"start Fail");
				break;
			}
	}	timeout=1000;
	
	I2C_Send7bitAddress(I2C1,LIS_ADDR|0x01,I2C_Direction_Receiver);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
	{
		  if((timeout--)==0)
			{
				RS232_Printf(1,"EV5 Fail");
				break;
			}
	}	timeout=1000;
	
	I2C_AcknowledgeConfig(I2C1,DISABLE);
  while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS )
  {
		  if((timeout--)==0)
			{
				RS232_Printf(1,"ack Fail");
				break;
			}
	}	timeout=1000;
	
  readtemp = I2C_ReceiveData(I2C1);
	
  I2C_GenerateSTOP(I2C1,ENABLE);
  
  return readtemp;
}

四、LIS3DH初始化與資料讀取

uint8_t LIS3DH_Init(void)
{
	u8 res = 0;
	
	IIC1_Init();
	
	res = IIC_ReadByte(WHO_AM_I);
	if(res != 0x33)
	{
		return 0;
	}

	//測試FIFO
   IIC_WriteByte(LIS3DH_CTRL_REG1,0x80|0x0F);   //0010 0111	低功耗模式
	IIC_WriteByte(LIS3DH_CTRL_REG2,0x00);				//高通濾波關閉

	IIC_WriteByte(LIS3DH_CTRL_REG3,0x06);				//使能FIFO中斷    0000 0110
	IIC_WriteByte(LIS3DH_CTRL_REG4,0x00);				//解析度+-16g 		0011 0000

	IIC_WriteByte(LIS3DH_CTRL_REG5,0x48);					//FIFO使能	0100 1000
	IIC_WriteByte(LIS3DH_FIFO_CTRL,0x80|0x1D);		//0100 1111	設定FIFO模式和水印
	
	IIC_ReadByte(LIS3DH_INT1_SRC);	//清除中斷位
	
	collect_LIS_Data();
	
	return 1;
}


void collect_LIS_Data(void)
{
		uint16_t  LIS_temp_data[3] = {0,0,0};
		uint8_t data_len,i;

		FIFO_data_len = IIC_ReadByte(LIS3DH_FIFO_SRC);
		
//		if(FIFO_data_len<0x40)
//					return;

		FIFO_data_len &= 0x1F;
		for(i=0;i<FIFO_data_len;i++)
		{
			LIS3DH_ReadData(LIS_temp_data);
        }
}

上面的範例,將輸出設定為FIFOstream模式,同時將速率設定成了1.6khz,watermarker設定成了29。

實測在主程式中,每10ms呼叫一次資料採集,每次讀到的確實大約在15-17個之間,watermarker沒有發生溢位。IIC通訊也沒有把MCU的10ms全搶佔完,其他任務執行也是正常的。基本可以確定stm32硬體IIC可以達到這樣的速率。

另外,網上有一些說法,stm32的硬體IIC有BUG,長時間執行會崩潰之類的。我特意將裝置掛了1天,到結束時,資料依舊可以正常接收,mcu也沒有發生復位之類的問題

可能是ST已經修復了這個問題,也可能是我試的不夠多,所以要使用的話最好還是要謹慎一些