I2C外設簡介
- STM32內部集成了硬件I2C收發電路(硬件收發器:自動生產波形,自動翻轉電平等),可以由硬件自動執行時鐘生成、起始終止條件生成、應答位收發、數據收發等功能,減輕CPU的負擔——軟件只需要寫入控制寄存器CR和DR,還有實時監控時序狀態的狀態寄存器SR
- 支持多主機模型
- 支持7位/10位地址模式
- 支持不同的通訊速度,標準速度(高達100 kHz),快速(高達400 kHz)
- 支持DMA
- 兼容SMBus協議
- STM32F103C8T6 硬件I2C資源:I2C1、I2C2
補:多主機模型:固定多主機(有固定的主機數和固定的從機數)和可變多主機(任何設備,都可以從空閑的狀態跳出來作為主機,然后指定通信,之后又跳回來),采用10位地址的時序:起始條件后的兩個字節都是尋址,其中前一個字節,是幀頭:內容是5位的標志位11110+2位地址+1位讀寫位,后一個字節:純粹的8位地址。
I2C框圖
引腳對應關系
I2C基本結構(一主多從)
硬件I2C的操作流程:
主機發送
主機接收
軟件/硬件波形對比
手冊——對應24章
接線圖
10-2 硬件I2C讀寫MPU6050
- 開啟I2C外設和對應GPIO口的時鐘
- 把I2C外設對應的GPIO口初始化為復用開漏模式
- 使用結構體,對整個I2C進行配置
- I2C_Cmd,使能I2C
相關函數:
void I2C_DeInit(I2C_TypeDef* I2Cx);
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);? //生產起始條件
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);? //生產結束條件
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);? //配置應答ACK
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);? //數據寫入DR寄存器
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);? //讀取DR的數值
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);? //發送7位地址的專用函數
//EV——多種監控
I2C_CheckEvent()? //基本
I2C_GetLastEvent()? //高級
I2C_GetFlagStatus()?? //基于狀態位的標準監控
void I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
ITStatus I2C_GetITStatus(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
void I2C_ClearITPendingBit(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
MPU6050.c//27硬件I2C讀寫MPU6050
//與軟件的區別就是MyI2C.c這個文件,硬件是不需要的
//意思是:底層的邏輯會有不同,其他是一樣的
#include "stm32f10x.h" // Device header#define MPU_ADD 0xD0
#include "MPU_Reg.h"
//封裝指定地址寫和指定地址讀的時序
//優化:在代碼中,存在很多死循環的地方——超時退出void CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT){uint32_t TimeOut;TimeOut=100; while(I2C_CheckEvent(I2Cx, I2C_EVENT)!=SUCCESS){TimeOut--;if(TimeOut==0){break;//錯誤處理}}
}void MPU_WriteReg(uint8_t RegAddress,uint8_t Data){//用此函數,則會一直傳輸數據,所以我們需要用標志位去確定它是否操作成功了,這里就要用EV5事件來確定I2C_GenerateSTART(I2C2, ENABLE);//while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS);//封裝
// while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS){
// TimeOut--;
// if(TimeOut==0){
// return;
// }
// }CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//發送函數會自帶應答位,所以我們不需要考慮應答,只需要考慮發送后的事件EV6即可I2C_Send7bitAddress(I2C2,MPU_ADD, I2C_Direction_Transmitter); CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV8_1的事件是提醒應該寫入DR數據,不需要等待I2C_SendData(I2C2, RegAddress); //同理,等待對應的事件EV8CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING); I2C_SendData(I2C2, Data);//當發送為最后一個數據時,就需要等待EV8_2事件CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);I2C_GenerateSTOP(I2C2, ENABLE); }uint8_t MPU_ReadingReg(uint8_t RegAddress){uint8_t Data;I2C_GenerateSTART(I2C2, ENABLE);CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //發送函數會自帶應答位,所以我們不需要考慮應答,只需要考慮發送后的事件EV6即可I2C_Send7bitAddress(I2C2,MPU_ADD, I2C_Direction_Transmitter); CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //EV8_1的事件是提醒應該寫入DR數據,不需要等待I2C_SendData(I2C2, RegAddress); //在最后一個數據,用EV8_2while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED)!=SUCCESS); I2C_GenerateSTART(I2C2, ENABLE);while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS); //主機接收I2C_Send7bitAddress(I2C2,MPU_ADD, I2C_Direction_Receiver); while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)!=SUCCESS); //接收從機的數據:規定:在接收數據之前,需要把ACK置0,同時設置停止位STOP//如果讀取多個字節,那直接等待EV7事件,讀取DR,就能收到數據,在接收最后一個字節之前EV7_1事件,需要把ACK置0,同時設置停止位STOP//如果讀取一個字節,那在EV6事件之后,需要把ACK置0,同時設置停止位STOP,在等待EV7事件,不然會多一個字節I2C_AcknowledgeConfig(I2C2, DISABLE);I2C_GenerateSTOP(I2C2,ENABLE);while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED)!=SUCCESS); //讀取DRData=I2C_ReceiveData(I2C2); I2C_AcknowledgeConfig(I2C2, ENABLE); //應答值設為1,給從機應答,這樣可以使指定地址收多個字節return Data;}void MPU6050_Init(void){RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;GPIO_Init(GPIOB, &GPIO_InitStructure);I2C_InitTypeDef I2C_InitStructure;I2C_InitStructure.I2C_Mode =I2C_Mode_I2C; //I2C的模式I2C_InitStructure.I2C_ClockSpeed=50000; //時鐘頻率,要低于400KHzI2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;//時鐘占空比,只有在時鐘頻率大于100KHz才有用,小于則固定的1:1;——能更快的傳輸I2C_InitStructure.I2C_Ack=I2C_Ack_Enable; //應答配置I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit; //STM做從機,可以被響應幾位地址I2C_InitStructure.I2C_OwnAddress1=0x00; //自身寄存器,當STM32做從機時,指定STM32的自身地址,方便主機呼叫I2C_Init(I2C2,&I2C_InitStructure);I2C_Cmd(I2C2,ENABLE);MPU_WriteReg(MPU6050_PWR_MGMT_1,0x01); //解除睡眠,選擇陀螺儀時鐘MPU_WriteReg(MPU6050_PWR_MGMT_2,0x00); //6個軸均不待機MPU_WriteReg(MPU6050_SMPLRT_DIV,0x09); //采樣分頻為10MPU_WriteReg(MPU6050_CONFIG,0x06); //濾波參數最大MPU_WriteReg(MPU6050_GYRO_CONFIG,0x18); //陀螺儀和加速度選擇最大MPU_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
// //此時的MPU就在進行大量的數據轉換,數據存放在其他的寄存器里
}void MPU_Getdata(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{//讀取加速度寄存器XYZ軸的高8位和低8位uint8_t DataH, DataL; //定義數據高8位和低8位的變量DataH = MPU_ReadingReg(MPU6050_ACCEL_XOUT_H); //讀取加速度計X軸的高8位數據DataL = MPU_ReadingReg(MPU6050_ACCEL_XOUT_L); //讀取加速度計X軸的低8位數據*AccX = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU_ReadingReg(MPU6050_ACCEL_YOUT_H); //讀取加速度計Y軸的高8位數據DataL = MPU_ReadingReg(MPU6050_ACCEL_YOUT_L); //讀取加速度計Y軸的低8位數據*AccY = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU_ReadingReg(MPU6050_ACCEL_ZOUT_H); //讀取加速度計Z軸的高8位數據DataL = MPU_ReadingReg(MPU6050_ACCEL_ZOUT_L); //讀取加速度計Z軸的低8位數據*AccZ = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU_ReadingReg(MPU6050_GYRO_XOUT_H); //讀取陀螺儀X軸的高8位數據DataL = MPU_ReadingReg(MPU6050_GYRO_XOUT_L); //讀取陀螺儀X軸的低8位數據*GyroX = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU_ReadingReg(MPU6050_GYRO_YOUT_H); //讀取陀螺儀Y軸的高8位數據DataL = MPU_ReadingReg(MPU6050_GYRO_YOUT_L); //讀取陀螺儀Y軸的低8位數據*GyroY = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU_ReadingReg(MPU6050_GYRO_ZOUT_H); //讀取陀螺儀Z軸的高8位數據DataL = MPU_ReadingReg(MPU6050_GYRO_ZOUT_L); //讀取陀螺儀Z軸的低8位數據*GyroZ = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回}