10:STM32------I2C通信

目次

1: I2C通信プロトコル

1:I2C再開

2:ハードウェア回路

3:I2Cタイミング基本ユニット

A:オープニング/エンディング条件

2: バイトを送信する

3: バイトを受信する

4:応答メカニズム 

4:I2Cタイミング 

1:書き込むアドレスを指定

2: 現在アドレスの読み出し

3: 指定アドレスから読み出し

2:MPU6050

1: 再開

2: パラメータ

3:ハードウェア回路

4:ブロック図

5:アドレスを登録する

3: ケース

A: ソフトウェア I2C 読み書き MPU6050

1: 接続図

2: コード

B: ハードウェア I2C 読み取りおよび書き込み MPU6050

1 はじめに

2:l2C ブロック図

3: l2Cの基本構造

4: ホストが送信する

5:ホスト受信

6: 接続図

7: 機能紹介

8: コード


1: I2C通信プロトコル

ソフトウェアl2Cの読み書き

1:I2C再開

I2C (Inter IC Bus) はフィリップスによって開発されたユニバーサル データ バスです。

2つの通信ライン: SCL (シリアルクロック)、SDA (シリアルデータ)

同期、半二重

データで返信する

バス上での複数のデバイスのマウントをサポート (1 つのマスターと複数のスレーブ、複数のマスターと複数のスレーブ)

        多くの周辺機器は I2C 通信プロトコルに準拠しています。例: 上の図: MPU6050、OLED、AT24C02、DS3231 モジュール

        SCL : クロック     SDA : データ  ホストは CS を完全に制御します

        半二重: 1 本のデータ ラインがデータの送受信を担当します。例: I2C 通信用の SDA ライン

        同期: 受信機はクロック信号のガイダンスの下でサンプリングできます。

        一般的に使用されるのは 1 つのマスターと複数のスレーブですが、以下で説明するものも 1 つのマスターと複数のスレーブです。

2:ハードウェア回路

すべての I2C デバイスの SCL は相互に接続され、SDA も相互に接続されます。

デバイスの SCL と SDA は両方ともオープン ドレイン出力モードに設定する必要があります。

SCL と SDA にそれぞれプルアップ抵抗を追加します。抵抗値は一般的に約 4.7KΩです。

以下は 1 つのマスターと複数のスレーブ モードでの I2C 通信です。

 ホスト:

        A: SCL ラインの完全な制御 ---- ホストはいつでも SCL ラインを完全に制御できます。

        B: アイドル状態では、ホストはアクティブに SDA の制御を開始でき、スレーブがデータを送信し、スレーブが応答した場合にのみ、ホストは SDA の制御をスレーブに転送します。

スレーブマシン:

         A: スレーブは SCL ラインを制御することはできません。SCL クロック ラインについては、いつでも受動的に読み取ることしかできません。

        B: SDA データ ラインの場合、スレーブは SDA の制御をアクティブに開始することはできません。-----マスターがスレーブを読み取るコマンドを送信した後、またはスレーブが応答した場合にのみ、スレーブは SDA を一時的に取得できます。コントロール

 弱懸垂---スプリングロッドモデル

        下向きの引っ張りのみが許可され、上向きの引っ張りは許可されません。

        プルダウンは低電力周波数を意味し、プルダウンしないことは高電力周波数を意味します。

線と現象:

        A: 1 つ以上のスレーブ デバイスがロー レベルを出力している限り、バスはロー レベルになります。

        B: すべてのデバイスがハイレベルを出力した場合のみ、バスはハイレベルになります。

利点

        ピンモードの頻繁な切り替えを回避します

        電源ショート現象を完全に排除し、回路の安全性を確保します。

3:I2Cタイミング基本ユニット

A:オープニング/エンディング条件

        開始条件: SCL ハイレベル中に SDA がハイレベルからローレベルに切り替わる

        終了条件:SCLハイレベル中にSDAがローレベルからハイレベルに切り替わる

        開始と終了は両方ともホストによって生成されます。スレーブは開始と終了を生成することはできません。バスがアイドル状態のとき、スレーブは常に手を放す必要があり、飛び出してバスに触れることはできません。

2: バイトを送信する

        ホストはバイトを送信します。SCL ロー レベル中に、ホストはデータ ビットを SDA ラインに順番に配置し (上位ビットが最初)、その後ホストは SCL を解放します。スレーブは SCL ハイ レベル中にデータ ビットを読み取るため、SCLハイレベル 通常時は、SDA はデータの変更を許可しませんので、上記の処理を 8 回繰り返して 1 バイト送信します。

注: これは上位ビットが先であるため、最初のビットはバイトの最上位ビット B7 で、次に最上位ビット B6  B5...B0 になります。

        シリアル ポートとは異なり、シリアル ポートのタイミングはローエンドが最初ですが、I2C はハイエンドが最初です。

        タイミング シーケンス全体はホストがバイトを送信するためのものであるため、-----このユニットでは、SCL と SDA はホストによって制御されます----------スレーブがデータを送信するときのみ、スレーブが応答します 時間が来ると、ホストは SDA の制御をスレーブに転送します ----- スレーブは SCL ラインを制御することを許可されていません ----- SCL クロック ラインについては、読み取りのみが可能ですいつでも受動的に

        SCL はホストによって完全に制御されます

3: バイトを受信する

        ホストはバイトを受信します。SCL ロー レベル中に、スレーブは SDA ラインにデータ ビットを順番に配置し (ハイ ビットが最初)、その後ホストは SCL を解放し、ホストは SCL ハイ レベル中にデータ ビットを読み取るため、SCLハイレベル 通常時、SDA はデータの変更を許可しません。上記のプロセスを 8 回繰り返して 1 バイトを受信します (ホストは受信する前に SDA を解放する必要があります

         ホストが受信する前に、ホストは SDA を解放する必要があります。----- この時点で、スレーブはデータを送信し、SCL の低電力周波数期間中にデータを SDA に置き、SDA コントローラーをスレーブに引き渡す必要があります。データを解放するホストの本質 SDA を高周波数に設定するだけです。


void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
	Delay_us(10);
} 
MyI2C_W_SDA(1); //从机发送数据,主机需要把SDA的控制权给从机

        SCL はホストによって完全に制御されます

まとめると、ホストがデータを送信する場合もデータを受信する場合も、SCL の Low レベルでデータが解放され、High レベルでデータが読み出されますが、ホストがデータを読み出す場合は SDA の制御を引き継ぐ必要があります。スレーブ --- SDA を 1 にします。すべてのプログラムでマスターを操作するだけでよく、スレーブは操作する必要がありません (マスターのコードを書くだけです)。


void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
	Delay_us(10);
} 
MyI2C_W_SDA(1); //从机发送数据,主机需要把SDA的控制权给从机

4:応答メカニズム 

誰がデータを送信し、誰が応答を受信するか

        ホストからの応答 ホストは 1 バイト受信後、次のクロックで 1 ビットのデータ(応答データ)を送信します(データ 0 が応答、データ 1 が非応答を示します)。

          スレーブはマスターにデータを送信します。マスターはスレーブに応答して、スレーブがマスターにデータを送信し続ける必要があるかどうかを確認する必要があります。

        データ 0 は応答を示し、データ 1 は非応答を示します。-----0 はホスト SCL クロック ラインを Low にプルし、スレーブがデータを SDA データに配置し続けることができるようにします。1: ホスト SCL クロック ラインを High にプルします。 from ホストは SDA データ ラインにデータを置くことはできませんが、ホストはデータを読み取ることができ、CSL 高周波数期間中に SDA に立ち上がりエッジを与えることもできます。

        ホストが応答を受信: バイトを送信した後、ホストは次のクロックでデータのビット (応答データ) を受信し、スレーブが応答するかどうかを判断します。データ 0 は応答を示し、データ 1 は非応答を示します (ホストはその前に SDA を解放する必要があります)受け取り

        マスターはスレーブにデータを送信し、スレーブはマスターに応答して、マスターがスレーブにデータを送信し続ける必要があるかどうかを確認します。

        データ 0 は応答を示し、データ 1 は非応答を示します。-----0 はホスト SCL クロック ラインを Low にプルし、ホストがデータを SDA データに配置し続けることができるようにします。1: SCL クロック ラインを High にプルし、スレーブは SDA データ ライン上のデータを読み取ることができません。スレーブがデータを読み取るとき、ホストは CSL 高周波数期間中に SDA に立ち上がりエッジを与えることもできます。

4:I2Cタイミング 

1:書き込むアドレスを指定

       指定されたアドレスの書き込み---ホストはスレーブの指定されたアドレスにデータを書き込みます

        指定したデバイス(Slave Address)に対して、指定したアドレス(Reg Address)の下に指定したデータ(Data)を書き込みます。

        まず、各スレーブ デバイスの一意のデバイス アドレスを決定します。スレーブ デバイス アドレスは、各デバイスの名前に相当します。開始条件の後、マスターは最初にスレーブ デバイス名を呼び出すためのバイトを送信する必要があります。すべてのスレーブ デバイスは、最初のバイトを自分の名前と比較してください

        スレーブデバイスのアドレスは、12C プロトコル標準では 7 ビット アドレスと 10 ビット アドレスに分割されます。7 ビット アドレス モードについて説明します。

          開始条件: SCL ハイレベル中に SDA がハイレベルからローレベルに切り替わる

        最初のバイト: 最初の 7 ビットは、データの書き込み先を決定するために書き込まれるスレーブのアドレスに書き込まれます。 8 番目のビット: 読み取りおよび書き込みビット ---0 は、マスターが書き込み操作を実行することを意味します。後続のタイミング、1 は、ホストが後続のタイミング シーケンスで読み出し動作を実行することを意味します。

       応答ビット: 各バイトが完了した後に応答ビットが必要です。ここでは、マスターがスレーブにデータを送信し、スレーブがマスターに応答します。マスターがスレーブにデータを送信し続けるかどうかを確認します------ ----この時点で、ホストは SDA を解放しようとしています。SDA を解放した後、ピン レベルは立ち上がりエッジのハイ レベルに戻ります。スレーブはこのビット、立ち下がりエッジで SDA をローにプルしたいと考えています。行 AND: 低電力周波数 このプロセスは、スレーブが応答を生成したことを意味します ------- 最後の高レベル中に、ホストは SDA を読み取り、それが 0 であることを検出します。これは、私がアドレス指定を実行し、誰かが応答したことを意味します。私。送信には問題ありません。ホストが SDA を読み取って、それが 1 であることがわかったら、アドレス指定を意味します。応答ビット中に手を放しました。誰もそれをつかみませんでした。誰も私に応答しませんでした。私はただ生成しただけです停止条件が表示され、いくつかの情報が表示されました。

        2 番目のバイト: 指定されたデバイスの内部に送信できます。スレーブ デバイスは 2 番目のバイトと後続のバイトの使用を定義できます。一般に、2 番目のバイトはレジスタ アドレスまたは命令制御ワードなどです。例:マスターはデータ 0x19 をスレーブに送信しますが、MPU6050 では、アドレス 0x19 のレジスタを操作したいことを意味します。

        3 番目のバイト: このバイトは、ホストがアドレス 0x19 のレジスタに書き込みたい内容です。

このデータ フレームの目的は、スレーブ アドレス 1101000 を指定するデバイスのアドレス 0x19 の内部レジスタにデータ 0xAA を書き込むことです。

2: 現在アドレスの読み出し

        現在のアドレスの読み取り----------ホストは現在のアドレスにあるスレーブのデータを読み取ります

        指定したデバイス(Slave Address)に対して、カレントアドレスポインタが示すアドレスのスレーブデータ(Data)を読み出します。

現在のアドレスを手動で読み出す場合、読み出すアドレスを指定できないため、このタイミングはあまり使用されません。

3: 指定アドレスから読み出し

指定アドレス読み取り----ホストは指定アドレスにあるスレーブのデータを読み取ります。

指定したデバイス(Slave Address)に対して、指定したアドレス(Reg Address)配下のスレーブデータ(Data)を読み出します。 

複合モードとも呼ばれます:   つまり、指定されたアドレスに書き込まれた最初の 2 バイトを終了せずにコピーしますが、再度開始する必要があります。最初のバイトの最後のビットがデータを決定するため、最初のバイトを書き換えます。操作はデータの読み取りまたは書き込みです。データ;その後、スレーブから通常どおりデータを読み取ります。

2:MPU6050

1: 再開

        MPU6050 は、チップ自身の X、Y、Z 軸の加速度および角速度パラメータを測定できる 6 軸姿勢センサーです。データ融合により、さらに姿勢角を取得できます。バランス調整などのシナリオでよく使用されます。自身の姿勢を検出する必要がある車両や航空機。

        3 軸加速度計 (加速度計): X、Y、Z 軸の加速度を測定します。

        3軸ジャイロセンサー(ジャイロスコープ):X、Y、Z軸の角速度を測定します

2: パラメータ

16 ビット ADC がセンサーのアナログ信号を収集、量子化範囲: -32768 ~ 32767

加速度計フルスケール選択: ±2、±4、±8、±16 (g) ----------ACCEL_CONFIG

ジャイロフルスケール選択: ±250、±500、±1000、±2000 (°/秒) ------GYRO_CONFIG

設定可能なデジタルローパスフィルター

設定可能なクロックソース

構成可能なサンプリング分周器 ---SMPLRT_DIV

I2C スレーブアドレス: 1101000 (AD0=0) 1101001 (AD0=1)

3:ハードウェア回路

        AD0: 7 ビットのスレーブ アドレスの最後のビットを変更してスレーブのアドレスを変更します。l

        AD0=0 7ビットスレーブアドレス=1101000

        AD0=1 7ビットスレーブアドレス=1101001

4:ブロック図

5:アドレスを登録する

16 進数はレジスタのアドレスを表します レジスタアドレスの10進表現 レジスタ名 読み取りおよび書き込み権限 No.7 No.6

...

... .... .... ... ...

SMPLRT_DIV----サンプリング分周器

CONFIG-----コンフィグレーションレジスタ

GYRO_CONFIG--------ジャイロスコープ設定レジスタ

ACCEL_CONFIG----アクセラレーション設定レジスタ

ACCEL---加速度; _H: 上位 8 ビット、_L: 下位 8 ビット

TEMP---温度センサー

GYRO-----ジャイロセンサー

PWR_MGMT_1----電源管理レジスタ 1; PWR_MGMT_2: 電源管理レジスタ 2

WHO_AM_I----デバイスのID番号

3: ケース

A: ソフトウェア I2C 読み書き MPU6050

1: 接続図

        私たちのコードはソフトウェア I2C を使用しており、通常の GPIO ポートを使用して電気周波数を反転する操作を実現しています。32 個の内部周辺リソースのサポートを必要としないため、ここでのポートは任意に指定できます。

2: コード

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MYI2C.h"
#include "MPU6050.h"

/**
* @brief  每个函数都是一SCL低电频结束的,除了MYI2C_Stop函数
	使用在除了MYI2C_Stop函数以外的函数,在函数开始的时候SCL都为低电频
  */

void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
	Delay_us(10);
}

void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
	Delay_us(10);
} 

uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
	Delay_us(10);
	return BitValue;
}

void MyI2C_Init(void)
{
	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(void)
{	
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
}
void MYI2C_Stop(void)
{
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}


/**
* @brief  主机发送一个字节给从机---主机发送字节
	在SCL低电频的时候,主机把数据放在SDA上面;然后拉高SCL从机读取数据;
	然后再拉低SCL主机继续放数据,循环8次
	
  * @param  Byte 要发送的字节
  * @retval 无
  */
void MyI2C_SendByte(uint8_t Byte)
{	
	MyI2C_W_SCL(0);
	for (uint8_t i=0;i<8;i++)
	{	
		//0x80 1000 0000 &依次取出发送字节的每一位(从高到低)I2C是高位先行
		MyI2C_W_SDA(Byte&(0x80>>i));
		MyI2C_W_SCL(1);
		MyI2C_W_SCL(0);
	}
}

/**
* @brief  从机发送一个字节给主机----主机接收字节
	从机发送数据,使用主机需要把SDA的控制权交从机(SDA置1),
循环8次:从机把数据放在SDA上,主机可以读取数据;拉低SCL从机放数据
  * @retval 无
  */
uint8_t MyI2C_ReceiveByte(void)
{	uint8_t Byte=0x00; //0000 0000
	MyI2C_W_SCL(0);
	MyI2C_W_SDA(1); //从机发送数据,主机需要把SDA的控制权给从机
	for (uint8_t i=0;i<8;i++)
	{ // |---置1
		MyI2C_W_SCL(1);
		if(MyI2C_R_SDA()==1){Byte |=(0x80>>i);} //0x80 1000 0000
		MyI2C_W_SCL(0);
	}
	return Byte;
}

/**
* @brief  主机在接收完一个字节之后,在下一个时钟发送一位数据
  * @param   AckBit 要发送的应答
  * @retval 无
  */
void MyI2C_SendAck(uint8_t AckBit)
{	
	MyI2C_W_SCL(0);
	MyI2C_W_SDA(AckBit);
	MyI2C_W_SCL(1);
	MyI2C_W_SCL(0);
}
/**
* @brief  主机在发送完一个字节之后,在下一个时钟接收一位数据,
  * @retval 无
  */
uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;
	MyI2C_W_SDA(1);//主机接收应答,把SDA的控制权给从机
	
	MyI2C_W_SCL(1);
	AckBit = MyI2C_R_SDA();
	MyI2C_W_SCL(0);
	return AckBit;

}



#include "stm32f10x.h"                  // Device header
#include "MYI2C.h"
#include "MUP6050_Rge.h"
#define MPU6050_addrees 0xD0

/**
* @brief  指定地址写
  * @param  RegAddress 第二个字节,在要操作MPU6050下RegAddress地址的寄存器
	 * @param  Data 第三个字节,实际在RegAddress地址的寄存器下写入的数据
  * @retval 无
  */
	
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	MYI2C_Start();
	MyI2C_SendByte(MPU6050_addrees);//叫MPU的名字--写入要操作的外设地址
	MyI2C_ReceiveAck();
	
	MyI2C_SendByte(RegAddress);
	MyI2C_ReceiveAck();
	
	MyI2C_SendByte(Data);
	MyI2C_ReceiveAck();
	MYI2C_Stop();

}

/**
* @brief  指定地址读
  * @param  RegAddress 第二个字节,在要操作MPU6050下RegAddress地址的寄存器
  * @retval 无
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{	
		uint8_t data;
		MYI2C_Start();
		MyI2C_SendByte(MPU6050_addrees);//叫MPU的名字--写入要操作的外设地址
		MyI2C_ReceiveAck();
		MyI2C_SendByte(RegAddress);
		MyI2C_ReceiveAck();
		
		
		MYI2C_Start();
		MyI2C_SendByte(MPU6050_addrees|0x01);//0000 0001  因为要进行写入的操作,所以最后一位要置1
		MyI2C_ReceiveAck();
		data=MyI2C_ReceiveByte();
		MyI2C_SendAck(1);
		MYI2C_Stop();
		return data;
}

/**
* @brief  读取MPU6050的id号码
MPU6050_WHO_AM_I 0x75 MPU6050的id号码在0x75这个寄存器里面
  * @retval 无
  */
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

void MPU6050_init(void)
{
	MyI2C_Init();
	//写寄存器--应该先解除芯片的睡眠模式
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//电源管理寄存器1配置
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//电源管理寄存器2配置
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);//采样分频器寄存器的配置
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);//配置寄存器的配置
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);//陀螺仪传感器寄存器的配置
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);//加速度配置寄存器的配置
}

void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	uint8_t DataH, DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}


#ifndef __MPU6050_RGE_H
#define __MPU6050_RGE_H

#define	MPU6050_SMPLRT_DIV		0x19
#define	MPU6050_CONFIG			0x1A
#define	MPU6050_GYRO_CONFIG		0x1B
#define	MPU6050_ACCEL_CONFIG	0x1C

#define	MPU6050_ACCEL_XOUT_H	0x3B
#define	MPU6050_ACCEL_XOUT_L	0x3C
#define	MPU6050_ACCEL_YOUT_H	0x3D
#define	MPU6050_ACCEL_YOUT_L	0x3E
#define	MPU6050_ACCEL_ZOUT_H	0x3F
#define	MPU6050_ACCEL_ZOUT_L	0x40
#define	MPU6050_TEMP_OUT_H		0x41
#define	MPU6050_TEMP_OUT_L		0x42
#define	MPU6050_GYRO_XOUT_H		0x43
#define	MPU6050_GYRO_XOUT_L		0x44
#define	MPU6050_GYRO_YOUT_H		0x45
#define	MPU6050_GYRO_YOUT_L		0x46
#define	MPU6050_GYRO_ZOUT_H		0x47
#define	MPU6050_GYRO_ZOUT_L		0x48

#define	MPU6050_PWR_MGMT_1		0x6B
#define	MPU6050_PWR_MGMT_2		0x6C
#define	MPU6050_WHO_AM_I		0x75

#endif



int main(void)
{	
	//I2C测试
//	uint8_t ACK;
//	OLED_Init();
//	MyI2C_Init();
//	
//	MYI2C_Start();
//	MyI2C_SendByte(0XD0);
//	ACK = MyI2C_ReceiveAck();
//	MYI2C_Stop();
//	OLED_ShowNum(1,1,ACK,3);
	
	//MPU6050的MPU6050_ReadReg测试
	
//	uint8_t ID;
//	OLED_Init();
//	MPU6050_init();
//	
//	OLED_ShowString(1, 1, "ID:");
//	ID = MPU6050_ReadReg(0x75);
//	OLED_ShowHexNum(1, 4, ID, 2);
//	OLED_ShowNum(2, 4, ID, 2);
//	
//	MPU6050的MPU6050_WriteReg写寄存器--应该先解除芯片的睡眠模式
//	MPU6050_WriteReg(0x6B,0x00);//解除芯片的睡眠模式
//	MPU6050_WriteReg(0x19,0x66);
//	uint8_t num=MPU6050_ReadReg(0x19);
//	OLED_ShowHexNum(3, 4, num, 2);

//------------------------------------------------------------------------------------
	uint8_t ID;
	int16_t AX, AY, AZ, GX, GY, GZ;
		OLED_Init();
		MPU6050_init();
		OLED_ShowString(1, 1, "ID:");
		ID = MPU6050_GetID();
		OLED_ShowHexNum(1, 4, ID, 2);
	while (1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 8, GX, 5);
		OLED_ShowSignedNum(3, 8, GY, 5);
		OLED_ShowSignedNum(4, 8, GZ, 5);
	}
}



ホストは最初に SDA を 1 に設定しますが、SDA を読み取ることに意味はありますか?

uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;
	MyI2C_W_SDA(1);//主机接收应答,把SDA的控制权给从机
	
	MyI2C_W_SCL(1);
	AckBit = MyI2C_R_SDA();
	MyI2C_W_SCL(0);
	return AckBit;

}

        1 つ目: I2C ピンはすべてオープン ドレイン出力 + 弱プルアップ構成であり、ホスト出力 SDA は 1 で、SDA を高周波に強制するのではなく、SDA を解放します。

        2 番目: I2C が通信中、マスターが SDA を解放します。スレーブが存在する場合、スレーブは SDA を Low にプルするため、マスターが以前に SDA を 1 に設定していたとしても、SDA の値を読み取って、それが 0 になる可能性があります。読み取り結果は 0 で、スレーブが応答したことを意味します。

書き込みを行わずに SDA を継続的に読み取り続けると、読み取り結果は常に同じになりますか?

uint8_t MyI2C_ReceiveByte(void)
{	uint8_t Byte=0x00; //0000 0000
	MyI2C_W_SCL(0);
	MyI2C_W_SDA(1); //从机发送数据,主机需要把SDA的控制权给从机
	for (uint8_t i=0;i<8;i++)
	{ // |---置1
		MyI2C_W_SCL(1);
		if(MyI2C_R_SDA()==1){Byte |=(0x80>>i);} //0x80 1000 0000
		MyI2C_W_SCL(0);
	}
	return Byte;
}

        I2C は通信を行っており、スレーブの存在があります。ホストが SDA クロックを継続的に駆動すると、スレーブは SDA の周波数を変更する義務があるため、ホストが各サイクルで SDA を読み取るとき、読み取られたデータは制御の対象になります。スレーブにとって、このデータはスレーブがホストに送信したいデータでもあります。

B: ハードウェア I2C 読み取りおよび書き込み MPU6050

1 はじめに

        STM32 はハードウェア I2C トランシーバ回路を統合しており、クロック生成、開始条件と終了条件の生成、応答ビットの送受信、データ送受信などの機能をハードウェアで自動的に実行できるため、CPU の負担が軽減されます。

        マルチホストモデルをサポート

        7ビット/10ビットアドレスモードをサポート

        標準速度(最大100 kHz)、高速(最大400 kHz)の異なる通信速度をサポート

        DMAをサポート

        SMBusプロトコルに対応

        STM32F103C8T6 ハードウェア I2C リソース: I2C1、I2C2

        マルチホストモデルをサポート-----STM32は複数のホストに変換できるモードを採用しており、ホストになりたい人が飛び出すことができます。

        SMBus プロトコルと互換性あり-------システム管理バス。主に電源管理システムで使用され、l2C から改良されたものです。

2:l2C ブロック図

        データ送信: ここではデータレジスタ(DATA_REGISTER)とデータシフトレジスタを指します;データを送信する必要がある場合、データレジスタDRに1バイトのデータを書き込むことができます。シフトレジスタにシフトするデータがない場合、その値はデータ レジスタはシフト レジスタに移動します。シフト プロセス中に、新しいデータをデータ レジスタに配置できます。前のデータ シフトが完了すると、データはエッジなしで接続され、送信を続けることができます。データ レジスタ内 シフト レジスタに移動すると --------ステータス レジスタの TXE ビットが 1 に設定されます (送信レジスタ ビットは空です)。

        データを受信します:

        

        オウンアドレスレジスタとデュアルアドレスレジスタ: STM32は複数のマスターになれるモードを採用しており、32をスレーブとして使用する場合に使用し、32が通信していない場合はスレーブとなります。

3: l2Cの基本構造

ハードウェア l2C を使用する------GPIO 多重化オープン ドレイン出力。多重化とは、GPIO のステータスがオンチップ周辺機器、オープン ドレイン出力によって制御されることを意味します。これは、12C プロトコルで必要なポート構成です。

4: ホストが送信する

作業手順

5:ホスト受信

6: 接続図

 ハードウェアの通信ピンは安易に接続できないため、ピン定義表を確認する必要があります。

7: 機能紹介

stm32f10x i2c.h ファイル内-----I2C を初期化します。

void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);

stm32f10x i2c.h ファイル内-----開始条件の生成

void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState)

stm32f10x i2c.h ファイル内-----終了条件の生成

void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState)

stm32f10x i2c.h ファイル-----ホストとして 32 を使用する場合、スレーブに応答するかどうか

void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState)

機能: ACK を 1 に設定します。STM がマスターの場合: ACK=1 はスレーブに応答します。ACK=0 はスレーブへの無応答を示します。 

stm32f10x i2c.h ファイル内-----データを送信

void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t データ)

実際、データ データは DR レジスタに直接書き込まれます。 

stm32f10x i2c.h ファイル内-----7 ビットアドレス、最初のバイトを送信する特別な関数

void I2C_Send7bitAddress(I2C_TypeDef* I2Cx、uint8_t アドレス、uint8_t I2C_Direction)

stm32f10x i2c.h ファイル内-----データの読み取り

uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);

stm32f10x i2c.h ファイル内-----ステータス監視機能

ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)

この方法は、1 つ以上のフラグ ビットを同時に判断して、EV または EV 状態が発生したかどうかを判断します。

8: コード

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MYI2C.h"
#include "MPU6050.h"

#define MPU6050_addrees 0xD0

void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout;
	Timeout = 10000;
	while (I2C_CheckEvent(I2Cx, I2C_EVENT) ==ERROR)
	{
		Timeout --;
		if (Timeout == 0)
		{
			break;
		}
	}
}

/**
* @brief  指定地址写
  * @param  RegAddress 第二个字节,在要操作MPU6050下RegAddress地址的寄存器
	 * @param  Data 第三个字节,实际在RegAddress地址的寄存器下写入的数据
  * @retval 无
  */
	
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
//	MYI2C_Start();
//	MyI2C_SendByte(MPU6050_addrees);//叫MPU的名字--写入要操作的外设地址
//	MyI2C_ReceiveAck();
//	
//	MyI2C_SendByte(RegAddress);
//	MyI2C_ReceiveAck();
//	
//	MyI2C_SendByte(Data);
//	MyI2C_ReceiveAck();
//	MYI2C_Stop();
//-----------------------------------------------------------------------------
	
	I2C_GenerateSTART(I2C2,ENABLE);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5
	
	I2C_Send7bitAddress(I2C2,MPU6050_addrees,I2C_Direction_Transmitter);//用于写从机的地址
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//发送时的EV6

	I2C_SendData(I2C2,RegAddress);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8

	I2C_SendData(I2C2,Data);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2 结束时产生EV8_2的标志位

	I2C_GenerateSTOP(I2C2,ENABLE);



}

/**
* @brief  指定地址读
  * @param  RegAddress 第二个字节,在要操作MPU6050下RegAddress地址的寄存器
  * @retval 无
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{	
//		uint8_t data;
//		MYI2C_Start();
//		MyI2C_SendByte(MPU6050_addrees);//叫MPU的名字--写入要操作的外设地址
//		MyI2C_ReceiveAck();
//		MyI2C_SendByte(RegAddress);
//		MyI2C_ReceiveAck();
//		
//		
//		MYI2C_Start();
//		MyI2C_SendByte(MPU6050_addrees|0x01);//0000 0001  因为要进行写入的操作,所以最后一位要置1
//		MyI2C_ReceiveAck();
//		data=MyI2C_ReceiveByte();
//		MyI2C_SendAck(1);
//		MYI2C_Stop();
//		return data;
//-----------------------------------------------------------------------------	

	uint8_t Data;
	I2C_GenerateSTART(I2C2,ENABLE);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5
	
	I2C_Send7bitAddress(I2C2,MPU6050_addrees,I2C_Direction_Transmitter);//用于写从机的地址
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//发送时的EV6
	
	I2C_SendData(I2C2,RegAddress);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2
	
	
	I2C_GenerateSTART(I2C2,ENABLE);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5
	
	I2C_Send7bitAddress(I2C2,MPU6050_addrees,I2C_Direction_Receiver);//用于写从机的地址
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//接收时的EV6

	//进入接收模式
	I2C_AcknowledgeConfig(I2C2, DISABLE);
	//这个终止条件,也不会截断当前字节, 当前字节接收完成后,再产生终止条件的波形
	I2C_GenerateSTOP(I2C2, ENABLE);
	
	
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);  //EV7
	Data = I2C_ReceiveData(I2C2);
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);//默认状态下ACK就是1,给从机应答
	
	return Data;
	
}

/**
* @brief  读取MPU6050的id号码
MPU6050_WHO_AM_I 0x75 MPU6050的id号码在0x75这个寄存器里面
  * @retval 无
  */

uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

void MPU6050_init(void)
{	
	//MyI2C_Init();
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,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_structinit;
	I2C_structinit.I2C_Ack=I2C_Ack_Enable; //应答位---操作ACK用于确定在接收一个字节后是否给从机应答
	I2C_structinit.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;//32做为从机: 响应机为的地址
	I2C_structinit.I2C_ClockSpeed=50000;
	I2C_structinit.I2C_DutyCycle=I2C_DutyCycle_2;//时钟占空比参数----高电频:低电频=2:1  ; I2C_DutyCycle_16_9---高电频:低电频=16:9
	I2C_structinit.I2C_Mode=I2C_Mode_I2C;
	I2C_structinit.I2C_OwnAddress1=0x00;//自身地址1  当32为从机是, 主机呼唤它的地址;于I2C_AcknowledgedAddress关联;
	/*
	I2C_AcknowledgedAddress 给7位时;  自身地址1 I2C_OwnAddress1---写7位
	I2C_AcknowledgedAddress 给10位时;  自身地址1 I2C_OwnAddress1---写10位

	*/
	I2C_Init(I2C2,&I2C_structinit);
	I2C_Cmd(I2C2,ENABLE);
	
	//写寄存器--应该先解除芯片的睡眠模式
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//电源管理寄存器1配置
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//电源管理寄存器2配置
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);//采样分频器寄存器的配置
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);//配置寄存器的配置
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);//陀螺仪传感器寄存器的配置
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);//加速度配置寄存器的配置
}



void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	uint8_t DataH, DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}






int main(void)
{	
	//I2C测试
//	uint8_t ACK;
//	OLED_Init();
//	MyI2C_Init();
//	
//	MYI2C_Start();
//	MyI2C_SendByte(0XD0);
//	ACK = MyI2C_ReceiveAck();
//	MYI2C_Stop();
//	OLED_ShowNum(1,1,ACK,3);
	
	//MPU6050的MPU6050_ReadReg测试
	
//	uint8_t ID;
//	OLED_Init();
//	MPU6050_init();
//	
//	OLED_ShowString(1, 1, "ID:");
//	ID = MPU6050_ReadReg(0x75);
//	OLED_ShowHexNum(1, 4, ID, 2);
//	OLED_ShowNum(2, 4, ID, 2);
//	
//	MPU6050的MPU6050_WriteReg写寄存器--应该先解除芯片的睡眠模式
//	MPU6050_WriteReg(0x6B,0x00);//解除芯片的睡眠模式
//	MPU6050_WriteReg(0x19,0x66);
//	uint8_t num=MPU6050_ReadReg(0x19);
//	OLED_ShowHexNum(3, 4, num, 2);

//------------------------------------------------------------------------------------
	uint8_t ID;
	int16_t AX, AY, AZ, GX, GY, GZ;
		OLED_Init();
		MPU6050_init();
		OLED_ShowString(1, 1, "ID:");
		ID = MPU6050_GetID();
		OLED_ShowHexNum(1, 4, ID, 2);
	while (1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 8, GX, 5);
		OLED_ShowSignedNum(3, 8, GY, 5);
		OLED_ShowSignedNum(4, 8, GZ, 5);
	}
}

という部分に注目してください

    //进入接收模式
	I2C_AcknowledgeConfig(I2C2, DISABLE);
	//这个终止条件,也不会截断当前字节, 当前字节接收完成后,再产生终止条件的波形
	I2C_GenerateSTOP(I2C2, ENABLE);
	
	
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);  //EV7
	Data = I2C_ReceiveData(I2C2);
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);//默认状态下ACK就是1,给从机应答
	
	return Data;
	;

ホスト受信モードに入ると、スレーブが送信したデータ波形の受信を開始します バイトを受信すると、EV6 1イベントが発生します このイベントにはフラグビットがなく、受信に適した状況になるまで待つ必要はありません1バイト。

        1 バイト読み取り: 上記のコードは、EV6 の直後、応答とストップ コンディションの生成をクリアするには、レスポンス ビット ACK を 0 に設定し、同時にストップ コンディション生成ビット STOP を 1 に設定する必要があります。 : 受信後 最終バイトの前に、あらかじめ ACK を 0 にセットし、ストップビット STOP をセットする必要があります。

        複数バイトを読み込む:EV7 イベントを直接待ち、DR を読み出し、データを受信します。最後のバイト (ここでは EV7-1 イベント) を受信する前に、事前に ACK を 0 に設定し、STOP を設定する必要があります。

ハードウェアの受信と応答: 動作するための機能は必要ありません データの送信には応答の受信処理が伴います データの受信にも同様に応答の送信処理が伴いますので、データの送受信には処理が必要ありません応答ビット。

おすすめ

転載: blog.csdn.net/m0_74739916/article/details/132766707