ashwatermelon

ashwatermelon

【学習ノート】stm32マイコンI2C

最近学んだ STM32 の I2C について、今日は学習ノートを書いて記録します。

(一)I2C とは?#

I2C(Inter IC Bus)は、Philips 社が開発した汎用データバスです。
通信線は 2 本あり:SCL(Serial Clock)、SDA(Serial Data)で、同期、半二重、データ応答を伴い、バスに複数のデバイスを接続することをサポートします(一主多従、多主多従)。

(二)I2C のハードウェア回路#

(画像がまた出せませんでしたので、口頭で説明します)
I2C には SCL と SDA の 2 本の通信線があり、SCL はクロック線、SDA はデータ線です。
マスターとスレーブはこの 2 本の線で接続されており、どちらもオープンドレイン出力の弱プルアップモードに設定されています。これは、デバイスが信号を送信していないときは、SCL と SDA が高電平に設定され、デバイスが信号を送信するときは、能動的に低電平に引き下げ、放すと自動的に高電平に戻ることを意味します。

(三)I2C のタイミング#

I2C のタイミングの基本単位には、開始、終了、1 バイトの送信、1 バイトの受信が含まれます。
開始:SDA を低に引き下げ、その後 SCL を低に引き下げます。
終了:SCL を高電平に解放し、その後 SDA を高電平に解放します。
1 バイトの送信:SDA を低に引き下げ / 解放し、このとき SCL を解放し、スレーブは SCL の高電平でデータを読み取ります。
1 バイトの受信:SCL を高電平に解放し、マスターはデータを読み取ります。

I2C では主に 2 つのタイミングを学びます。一つは指定位置への書き込み、もう一つは指定位置からの読み取りです。
指定位置への書き込み:開始 -> スレーブアドレス + 読み書きビット(書き込み)を送信 -> 応答を受信 -> レジスタアドレスを送信 -> 応答を受信 -> データを送信 -> 応答を受信 -> 終了
指定位置からの読み取り:開始 -> スレーブアドレス + 読み書きビット(書き込み)を送信 -> 応答を受信 -> レジスタアドレスを送信 -> 応答を受信 -> 開始(再開始)-> スレーブアドレス + 読み書きビット(読み取り)を送信 -> 応答を受信 -> レジスタアドレスを送信 -> 応答を受信 -> データを受信 -> 応答を送信 -> 終了

(四)ソフトウェア 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)//1バイトを送信
{
    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()//1バイトを受信
{
    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;
}

これらの 2 つのタイミングを利用することで、私たちの期待する目的を達成できます。

(五)ハードウェア 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 です。振り返るとこのレイアウトは本当に笑ってしまいますが、修正するのは面倒です。本来この数編を書く目的は復習することだったので、私の表現も不適切です。いつか学び終えたら、しっかりと復習してから戻って修正します。(悲)

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。