ashwatermelon

ashwatermelon

【學習筆記】stm32單片機I2C

最近學了 stm32 的 I2C,今天來寫學習筆記記錄一下。

(一)什麼是 I2C?#

I2C(Inter IC Bus)是由 Philips 公司開發的一種通用數據總線
有兩根通信線:SCL(Serial Clock)、SDA(Serial Data),同步,半雙工,帶數據應答,支持總線掛載多設備(一主多從、多主多從)。

(二)I2C 的硬件電路#

(圖片又又又又發不出來了,還是口頭來講吧)
I2C 有 SCL 和 SDA 兩條通信線,SCL 是時鐘線,SDA 是數據線。
主機和從機這兩條線都連在一起,而且都設置為開漏輸出弱上拉模式,其意思大概為:當沒有設備發送信號時,SCL 和 SDA 都設置為高電平,當設備發送信號時,通過主動拉低發出低電平,鬆開則自動跳回高電平。

(三)I2C 的時序#

I2C 的時序的基本單元主要包括:開始,結束,發送一個字節,接收一個字節。
開始:SDA 拉低,然後 SCL 拉低。
結束:SCL 釋放為高電平,然後 SDA 釋放為高電平。
發送一個字節:SDA 拉低 / 釋放,這時 SCL 釋放,從機於 SCL 高電平讀取數據。
接收一個字節:SCL 釋放至高電平,主機讀取數據。

I2C 我們主要是學習兩個時序,一個是指定位置寫,一個是指定位置讀。
指定位置寫:開始 -> 發送從機地址 + 讀寫位(寫)-> 接收應答 -> 發送寄存器地址 -> 接收應答 -> 發送數據 -> 接收應答 -> 結束
指定位置讀:開始 -> 發送從機地址 + 讀寫位(寫)-> 接收應答 -> 發送寄存器地址 -> 接收應答 -> 開始(重新開始)-> 發送從機地址 + 讀寫位(讀)-> 接收應答 -> 發送寄存器地址 -> 接收應答 -> 接收數據 -> 發送應答 -> 結束

(四)軟件 I2C#

根據時序,我們可以寫出軟件配置 I2C 的代碼:
void MyI2C_W_SCL (uint8_t Bitvalue)//SCL 置高 / 低電平
{
GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)Bitvalue);
Delay_us(10);
}

void MyI2C_W_SDA (uint8_t Bitvalue)//SDA 置高 / 低電平
{
GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)Bitvalue);
Delay_us(10);
}

uint8_t MyI2C_R_SDA ()// 讀取 SDA 的數據
{
uint8_t Bitvalue;
Bitvalue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
Delay_us(10);
return Bitvalue;
}

void MyI2C_Init ()// 初始化 I2C
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10|GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);

GPIO_SetBits(GPIOB,GPIO_Pin_10|GPIO_Pin_11);
}

void MyI2C_Start ()// 開始時序
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}

void MyI2C_Stop ()// 結束時序
{
MyI2C_W_SCL(0);
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}

void MyI2C_SendByte (uint8_t Byte)// 發送一個字節
{
for(int i=0;i<8;i++)
{
MyI2C_W_SCL(0);
MyI2C_W_SDA(!!(Byte & (0x80 >> i)));
MyI2C_W_SCL(1);
}
MyI2C_W_SCL(0);
}

uint8_t MyI2C_ReceiveByte ()// 接收一個字節
{
uint8_t Byte = 0;
MyI2C_W_SCL(0);
MyI2C_W_SDA(1);
for(int i=0;i<8;i++)
{
MyI2C_W_SCL(1);
if(MyI2C_R_SDA())Byte|=(0x80>>i);
MyI2C_W_SCL(0);
}
return Byte;
}

void MyI2C_SendAck (uint8_t Ackbit)// 發送應答
{
MyI2C_W_SCL(0);
MyI2C_W_SDA(Ackbit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}

uint8_t MyI2C_ReceiveAck ()// 接收應答
{
uint8_t Ackbit = 0;
MyI2C_W_SCL(0);
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
Ackbit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return Ackbit;
}
由這些基本時序單元的代碼,我們就可以拼接出一個完整的時序:
void MPU6050_WriteReg (uint8_t regAddress,uint8_t Data)// 指定位置寫
{
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(regAddress);
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);
MyI2C_ReceiveAck();
MyI2C_Stop();
}
uint8_t MPU6050_ReadReg (uint8_t regAddress)// 指定位置讀
{
uint8_t Data;
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(regAddress);
MyI2C_ReceiveAck();

MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS|1);
MyI2C_ReceiveAck();
Data=MyI2C_ReceiveByte();
MyI2C_SendAck(1);
MyI2C_Stop();

return Data;
}
然後利用這兩時序,我們就可以達到我們期望的目的。

(五)硬件 I2C#

stm32 內部有自帶的 I2C 外設,不過只用於固定的引腳,下面是配置代碼:
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_Pin=GPIO_Pin_10|GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);

I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Ack=I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed=50000;
I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;
I2C_InitStructure.I2C_Mode=I2C_Mode_I2C;
I2C_InitStructure.I2C_OwnAddress1=0x00;
I2C_Init(I2C2,&I2C_InitStructure);

I2C_Cmd(I2C2,ENABLE);
// 初始化

void MPU6050_Wait (I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)// 檢查標誌位,並設置超時中斷
{
for(int time = 10000 ; time > 0 ; time--)
{
if(I2C_CheckEvent(I2Cx,I2C_EVENT) == SUCCESS)
return;
}
}

void MPU6050_WriteReg(uint8_t regAddress,uint8_t Data)
{
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_MODE_SELECT);

I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);

I2C_SendData(I2C2,regAddress);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);
I2C_SendData(I2C2,Data);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTOP(I2C2,ENABLE);
}

uint8_t MPU6050_ReadReg(uint8_t regAddress)
{
uint8_t Data;

I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_MODE_SELECT);

I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);

I2C_SendData(I2C2,regAddress);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);

I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_MODE_SELECT);

I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);

I2C_AcknowledgeConfig(I2C2,DISABLE);
I2C_GenerateSTOP(I2C2,ENABLE);

MPU6050_Wait(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);
Data = I2C_ReceiveData(I2C2);

I2C_AcknowledgeConfig(I2C2,ENABLE);

return Data;
}
那麼這些就是 I2C 使用的全部內容了,這裡再提一嘴 MPU6050 模塊,這個就是個陀螺儀,我們用 I2C 線與它連接,通過指定位置寫將它的功能配置好,再用指定位置讀讀取我們想要的數據(前面的代碼的有些命名還是 MPU6050,因為江協教的就是用 I2C 使用 MPU6050)

總之 I2C 部分就結束了,接下來就是 ISP 了,回頭一看這排版真讓人忍俊不禁,不過懶得改了,本來寫這幾篇的目的都只是複習一下,我表達也不貼切,等哪天我學完了鞏固好了再回來改吧。(悲)

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。