目錄
一、I2C
1.I2C簡介
2.MPU6050參數?
3.I2C時序基本單元
二、I2C外設
1.I2C外設簡介
2.配置I2C基本結構
3.初始化函數模板
4.常用函數?
一、I2C
1.I2C簡介
本節課使用的是MPU6050硬件外設
2.MPU6050參數?
3.I2C時序基本單元
這里發送應答是指主機發送,即STM32作為主機,MPU6050或其他I2C通信外設作為從機,我們接收到從機的數據后要給從機發送應答位以供從機判斷我們是不是還需要數據。
接收應答則是判斷從機有沒有接收到數據,并且我們要將SDA的控制權轉交給從機,即釋放SDA(拉高)。
結合以下兩種時序理解上面6種基本時序單元
?在起始條件后緊跟著的一個字節就是七位從機地址和一位讀寫位,0代表寫,1代表讀。
不同的芯片廠商對第二位字節數據的作用要求不同,MPU6050規定第二個字節數據是指定寄存器地址
在對寄存器進行讀寫時,地址指針會自動加1,比如這里地址一開始指向0x19,寫入數據后指向0x1A,所以想要對特定地址進行讀操作,就要先寫入寄存器地址,再重復起始條件,發送從機地址和讀操作,這時候沒有指定寄存器的操作,從機會自動將數據發送給你。?
以上就是I2C通信的時序,可以通過GPIO口軟件模擬I2C通信,本節主要學習STM32的硬件I2C外設
二、I2C外設
1.I2C外設簡介
2.配置I2C基本結構
由于硬件I2C不同于軟件模擬,我們不知道一個字節的數據是否發送完畢等等,因此STM32設置了許多代表不同事件的標志位
3.初始化函數模板
void MPU6050_Init(void)
{ RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);//I2C1和2都是APB1的設備RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_OD;//復用開漏輸出GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10|GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStructure);//初始化I2CI2C_InitTypeDef I2C_InitStructure;I2C_InitStructure.I2C_Mode=I2C_Mode_I2C;//選擇I2C模式I2C_InitStructure.I2C_ClockSpeed=50000;//時鐘速度 可配置SCL時鐘頻率I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;//配置占空比I2C_InitStructure.I2C_Ack=I2C_Ack_Enable;//應答位配置I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;//作為從機時能響應多少位的地址I2C_InitStructure.I2C_OwnAddress1=0x00;//STM32作為從機時的地址,長度和上面參數對應I2C_Init(I2C2,&I2C_InitStructure);I2C_Cmd(I2C2,ENABLE);
}
這里GPIO口模式選擇復用開漏輸出,雖然是輸出,但仍然可以輸入,應為這里默認高電平是一種弱上拉,從機要發送數據只需要進行拉低電平或釋放這一種操作,“拉”或“不拉”,主機讀取電平就能接收從機發送的數據。PB10和PB11是I2C2的引腳。
初始化I2C后,封裝指定地址寫和指定地址讀兩個函數
//指定地址寫
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)//參數為寄存器地址和要寫入的數據
{
// MyI2C_Start();
// MyI2C_SendByte(0xD0);從機地址
// MyI2C_ReceiveAck();
// MyI2C_SendByte(RegAddress);
// MyI2C_ReceiveAck();
// MyI2C_SendByte(Data);
// MyI2C_ReceiveAck();
// MyI2C_Stop(); I2C_GenerateSTART(I2C2,ENABLE);//起始條件//判斷EV5事件while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS);I2C_Send7bitAddress(I2C2,0xD0,I2C_Direction_Transmitter);//發送從機地址,選擇“寫”//判斷EV6事件,這里的EV6選擇“發送事件已選擇”while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)!=SUCCESS);I2C_SendData(I2C2,RegAddress);//發送寄存器地址//判斷EV8事件,“字節正在發送”while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING)!=SUCCESS);I2C_SendData(I2C2,Data);//發送數據//判斷EV8_2事件,即移位寄存器空,且沒有新數據要發while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED)!=SUCCESS);I2C_GenerateSTOP(I2C2,ENABLE);//終止條件
}
這里判斷各個標志位用I2C_CheckEvent?()函數,完成各個標志位會返回SUCCESS,這里判斷等于SUCCESS跳出循環,以免數據沒有發送完全等。硬件I2C和軟件I2C不同的是:軟件I2C因為是手動模擬,發送字節數據都是完整的,是通過發送和接收應答位來判斷雙方交流是否正常;而硬件I2C則是判斷標志位,并不需要管應答位,只需要使能ACK應答位即可。
//指定地址讀
uint8_t MPU6050_ReadReg(uint8_t RegAddress)//參數為寄存器地址
{uint8_t Data;// MyI2C_Start();
// MyI2C_SendByte(0xD0);從機地址
// MyI2C_ReceiveAck();
// MyI2C_SendByte(RegAddress);
// MyI2C_ReceiveAck();
//
// MyI2C_Start();
// MyI2C_SendByte(0xD0|0x01);
// MyI2C_ReceiveAck();
// Data=MyI2C_ReceiveByte();
// MyI2C_SendAck(1);
// MyI2C_Stop();//這里和指定地址寫的前三步一樣,即指定寄存器地址I2C_GenerateSTART(I2C2,ENABLE);//起始條件while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS);I2C_Send7bitAddress(I2C2,0xD0,I2C_Direction_Transmitter);//發送從機地址,選擇“寫”while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)!=SUCCESS);I2C_SendData(I2C2,RegAddress);//發送寄存器地址while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING)!=SUCCESS);I2C_GenerateSTART(I2C2,ENABLE);//重復起始條件//判斷EV5while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS);I2C_Send7bitAddress(I2C2,0xD0,I2C_Direction_Receiver);//發送從機地址,選擇“讀”//等待EV6事件,選擇“接收事件已選擇”while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)!=SUCCESS);//在接收最后一個字節時,要提前將ACK置0,并且產生終止條件I2C_AcknowledgeConfig(I2C2,DISABLE);//ACK=0,不給應答I2C_GenerateSTOP(I2C2,ENABLE);//STOP=1,申請產生終止條件//判斷EV7事件,意味著數據移到DR寄存器,可以讀走了while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED)!=SUCCESS);Data=I2C_ReceiveData(I2C2);//讀取數據//最后將應答位置1I2C_AcknowledgeConfig(I2C2,ENABLE);return Data;
}
?指定地址讀的前三步和指定地址寫一樣,起始條件后發送從機地址,寫寄存器地址,然后重復起始條件,發送從機地址選擇“讀”,這里判斷EV6事件就不同于指定地址寫。
因為我們寫和讀都是只有一個字節的數據,所以指定地址讀后就是接收最后一個字節,如果接收多個字節則是判斷EV7事件,這里接收一個字節也是最后一個字節,要提前將ACK置0(失能),同時申請終止條件。最后終止條件產生后判斷EV7事件,讀走DR寄存器的數據。最后將ACK置1(使能)(我的理解是因為要在主循環里不斷調用讀的操作,在初始化函數里通過結構體參數I2C_Ack使能ACK,意味著正常讀寫ACK都是處于使能的狀態,而函數I2C_AcknowledgeConfig可以單獨配置ACK的狀態,為了后續正常運行,所以最后還要將ACK使能)
附:在主發送和主接收的序列圖中,發送從機地址后,我們會判斷EV6事件,可以看到后面還接了一個EV8_1事件,這個事件是提醒我們該寫入數據發送出去了,因此我們不需要判斷EV8_1事件
參數列表里也沒有EV8_1事件。
4.常用函數?
I2C_GenerateSTART,生成起始條件
I2C_GenerateSTOP,生成終止條件
l2C_SendData,發送數據
I2C_ReceiveData,接收數據
I2C_AcknowledgeConfig,使能或失能ACK,給ENABLE就是就是給從機應答