目次
ソフトウェアシミュレーションIICドライバAT24C02共有
IIC (Inter-Integrated Circuit) は、I²C または TWI (Two-Wire Interface) とも呼ばれ、低速集積回路の接続に使用される広く使用されているシリアル バス インターフェイスです。この通信プロトコルは、単一のマスター デバイスと複数のスレーブ デバイス間の短距離通信に最適です。
I²C の物理層
IIC 通信に必要なワイヤは 2 本だけです。1 つはシリアル データ ライン (SDA)、もう 1 つはシリアル クロック ライン (SCL) です。両方のラインをプルアップ抵抗を介して正電源に接続し、信号が駆動していないときにラインが High を維持できるようにする必要があります。
I²C プロトコル層
IIC プロトコルは、開始信号、停止信号、データ有効性および応答信号を含む一連の信号を定義します。開始信号と停止信号は通信の開始と終了を識別するために使用され、データ有効性によりクロック信号が安定しているときにデータが読み取られることが保証されます。応答信号は、スレーブデバイスが受信データを確認する信号です。
I²C の機能
- 2 線式インターフェイス: I2C 通信には 2 本の線のみが必要です。1 つはシリアル データ ライン (SDA)、もう 1 つはシリアル クロック ライン (SCL) であるため、I2C は半二重通信です。
- マルチマスターデバイス: I2C により、複数のマスターデバイス (マスター) と複数のスレーブデバイス (スレーブ) が同じバス上で通信できるようになります。
- アドレス識別: 各スレーブ デバイスは、マスター デバイスが特定のスレーブ デバイスと通信するための固有のアドレスを持っています。
- 同期通信: I2C は同期通信プロトコルであり、データ送信はクロック信号 (SCL) によって同期されます。
- 複数のレートをサポート: I2C は、標準モード (100kbps)、高速モード (400kbps)、高速モード プラス (1Mbps)、高速モード (3.4Mbps) など、さまざまなデータ転送速度をサポートします。
- ソフトウェアで設定可能: I2C デバイスのアドレスと一部の機能はソフトウェアで設定できます。
- シンプルで使いやすい: I2C インターフェイスのハードウェア実装は比較的シンプルで、さまざまなマイクロコントローラーやその他の集積回路に簡単に統合できます。
- 幅広い用途: I2C は、携帯電話、テレビ、医療機器、組み込みシステムなどのさまざまな電子製品で広く使用されています。
- ホットスワップのサポート: I2C デバイスは、システムの実行中の追加または削除、つまりホットスワップをサポートします。
- バス調停: 複数のマスター デバイスがある場合、I2C プロトコルはどのマスター デバイスがバスを制御できるかを決定する調停メカニズムを提供します。
- クロック ストレッチング: スレーブ デバイスは、受信データの処理またはデータ送信の完了に十分な時間を確保するために、クロック ラインを Low に引き下げることで通信を一時停止できます (クロック ストレッチングと呼ばれます)。
- 応答メカニズム: I2C 通信には、データが正常に受信されたかどうかを示す応答 (ACK) 信号と非応答 (NACK) 信号が含まれます。
I2C はそのシンプルさと柔軟性により、センサー、EEPROM、ディスプレイなどの低速周辺機器の接続に最適です。
I²C バスのタイミング図
バスのタイミング図は、IIC 通信を理解するための鍵となります。スタート信号、データビットの送信、アクノリッジビット、ストップ信号のシーケンスを示します。IIC 通信では、SCL ラインが High のときにデータ ビットが安定しているとみなされるため、データは SCL の High 期間中に読み取られる必要があります。
開始条件: SCL ハイレベル中に SDA がハイレベルからローレベルに切り替わる
終了条件: SCLハイレベル中にSDAがローレベルからハイレベルに切り替わる
開始条件と停止条件はホストによって生成されます
バイトの送信: SCL ロー レベル中に、ホストは SDA ラインにデータ ビットを順番に配置し (ハイ ビットが最初)、その後 SCL を解放します。スレーブは SCL ハイ レベル中にデータ ビットを読み取るため、この間 SCL ハイ レベルになります期間中、SDA はデータを変更できません。上記のプロセスを 8 回繰り返して 1 バイトを送信します。
バイトの受信: SCL が Low レベルの間、スレーブは SDA ラインにデータ ビットを順番に配置し (High ビットが最初)、その後 SCL を解放します。ホストは SCL High レベルの間にデータ ビットを読み取るため、SCL が High になると通常、SDA はデータ変換を許可しません。1 バイトを受信するには上記のプロセスを 8 回繰り返します (ホストはデータを受信する前に SDA を解放する必要があります)。
応答送信: ホストは 1 バイト受信後、次のクロックで 1 ビットのデータを送信します(データ 0 が応答、データ 1 が非応答を示します)。
応答の受信: ホストがバイトを送信した後、次のクロックでデータのビットを受信して、スレーブが応答するかどうかを判断します。データ 0 は応答を示し、データ 1 は非応答を示します (ホストは受信する前に SDA を解放する必要があります)
ソフトウェア シミュレーション I²C タイミング共有
/**
* @brief 定义SCL写函数
* @param None
* @retval None
*/
void myi2c_w_scl(uint8_t bitval){
GPIO_WriteBit(GPIOA, GPIO_Pin_1, (BitAction)bitval); //将bitval的值写入GPIOA的Pin_1,也就是SCL线
Delay_us(10); //延迟10微秒
}
/**
* @brief 定义SDA写函数
* @param None
* @retval None
*/
void myi2c_w_sda(uint8_t bitval){
GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)bitval); //将bitval的值写入GPIOA的Pin_0,也就是SDA线
Delay_us(10); //延迟10微秒
}
/**
* @brief 读取SDA数据
* @param None
* @retval None
*/
uint8_t myi2c_r_sda(void){
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0); //读取GPIOA的Pin_0,也就是SDA线的值
}
/**
* @brief 软件模拟I2C初始化
* SDA PA0 推挽输出
* SCL PA1 推挽输出
* @param None
* @retval None
*/
void myi2c_init(void){
//初始化GPIO口
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO初始化结构体
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //设置GPIO模式为开漏输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; //设置GPIO的Pin_0和Pin_1
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置GPIO速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA
//释放总线
GPIO_SetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1); //将GPIOA的Pin_0和Pin_1设置为高电平,释放总线
}
/**
* @brief I2C起始条件
* @param None
* @retval None
*/
void i2c_start(void){
//输出起始条件
myi2c_w_sda(1); //将SDA线设置为高电平
myi2c_w_scl(1); //将SCL线设置为高电平
myi2c_w_sda(0); //将SDA线设置为低电平,生成起始条件
myi2c_w_scl(0); //将SCL线设置为低电平
}
/**
* @brief I2C结束条件
* @param None
* @retval None
*/
void i2c_stop(void){
//输出起始条件
myi2c_w_sda(0); //将SDA线设置为低电平
myi2c_w_scl(1); //将SCL线设置为高电平
myi2c_w_sda(1); //将SDA线设置为高电平,生成结束条件
}
/**
* @brief I2C发送一个字节
* @param None
* @retval None
*/
void myi2c_sendbyte(uint8_t byte){
for(uint8_t i = 0; i < 8; i++){ //循环8次,发送一个字节
myi2c_w_sda(byte & 0x80 >> i); //每发送一次向右偏移一个字节
myi2c_w_scl(1); //将SCL线设置为高电平
myi2c_w_scl(0); //将SCL线设置为低电平
}
}
/**
* @brief I2C接收一个字节
* @param None
* @retval None
*/
uint8_t myi2c_recv_byte(void){
uint8_t byte = 0; //定义一个字节变量
for(uint8_t i = 0; i < 8; i++){ //循环8次,接收一个字节
myi2c_w_scl(1); //将SCL线设置为高电平
if(myi2c_r_sda() == 1){byte |= (0x80 >> i);} //如果SDA线为高电平,将byte的相应位设置为1
myi2c_w_scl(0); //将SCL线设置为低电平
}
return byte; //返回接收到的字节
}
/**
* @brief I2C接收应答
* @param None
* @retval None
*/
uint8_t myi2c_recv_ack(void){
uint8_t ackbit = 0; //定义一个应答位变量
myi2c_w_sda(1); //将SDA线设置为高电平
myi2c_w_scl(1); //将SCL线设置为高电平
ackbit = myi2c_r_sda(); //读取SDA线的值,也就是应答位
myi2c_w_scl(0); //将SCL线设置为低电平
return ackbit; //返回应答位
}
/**
* @brief I2C发送应答
* @param None
* @retval None
*/
void myi2c_send_ack(uint8_t ackbit){
myi2c_w_sda(ackbit); //将应答位的值写入SDA线
myi2c_w_scl(1); //将SCL线设置为高电平
myi2c_w_scl(0); //将SCL线设置为低电平
}
ソフトウェアシミュレーションIICドライバAT24C02共有
日常の紹介
I2C プロトコルを介して AT24C04 EEPROM チップと通信する機能。EEPROM は、Electrically Erasable Programmable Read-Only Memory の略で、コンピュータやその他の電子機器で停電後に保持する必要がある少量のデータを保存するために使用される不揮発性メモリの一種です。
各機能の概要は次のとおりです。
- :
AT24_init
この関数は、AT24C04 チップと通信する I2C インターフェイスを初期化します。 - :
AT24_write_byte
この関数は、AT24C04 チップの指定されたアドレスに 1 バイトのデータを書き込みます。 - :
AT24_read_byte
この関数は、AT24C04 チップの指定されたアドレスから 1 バイトのデータを読み取ります。 - :
AT24_write_page
この関数は、AT24C04 チップの指定されたアドレスに複数バイトのデータを書き込みます。AT24C04 のメモリは複数のページに分割されており、各ページは複数バイトのデータを保持できます。 - :
AT24_WriteBuffer
この関数はデータ バッファを AT24C04 チップに書き込みます。チップのメモリのページ構造を考慮し、必要に応じて複数のページにデータを書き込みます。 - :
AT24_readBuffer
この関数は、AT24C04 チップからデータ バッファを読み取ります。AT24_WriteBuffer
と同様に、チップのメモリのページ構造も考慮されます。
日常的な共有
/*源代码*/
#include "AT24.h"
uint8_t AT24_ADDR_W1 = 0XA0;
uint8_t AT24_ADDR_W2 = 0XA2;
uint8_t AT24_ADDR_R1 = 0xA1;
uint8_t AT24_ADDR_R2 = 0xA3;
/**
* @brief AT24C04初始化
* @param None
* @retval None
*/
void AT24_init(void){
myi2c_init();
}
/**
* @brief 指定地址写入一个字节数据(0 ---- 255)
* @param uint16_t addr 写入数据地址
* @param uint8_t data 写入字节
* @retval 写入成功返回4
*/
uint8_t AT24_write_byte(uint16_t addr, uint8_t data){
i2c_start(); //发送起始信号
myi2c_sendbyte(AT24_ADDR_W1); //发送从机地址
if(myi2c_recv_ack() == 1){
i2c_stop(); //发送停止位
printf("AT24寻址未应答\r\n");
return 1;
}
myi2c_sendbyte(addr); //发送要写入的地址
if(myi2c_recv_ack() == 1){
i2c_stop(); //发送停止位
printf("AT24内部寻址未应答\r\n");
return 2;
}
myi2c_sendbyte(data); //发送要写入的数据
if(myi2c_recv_ack() == 1){
i2c_stop(); //发送停止位
printf("AT24写入数据未应答\r\n");
return 3;
}
i2c_stop(); //发送停止位
printf("AT24写入数据成功\r\n");
return 4;
}
/**
* @brief 指定地址读出一个字节数据(0 ---- 255)
* @param uint16_t addr 读数据地址
* @retval 成功返回读出数据
*/
uint8_t AT24_read_byte(uint16_t addr){
uint8_t read_data = 0;
i2c_start(); //发送起始信号
myi2c_sendbyte(AT24_ADDR_W1); //发送从机地址
if(myi2c_recv_ack() == 1){
i2c_stop(); //发送停止位
printf("AT24寻址未应答\r\n");
return 1;
}
myi2c_sendbyte(addr); //发送要写入的地址
if(myi2c_recv_ack() == 1){
i2c_stop(); //发送停止位
printf("AT24内部寻址未应答\r\n");
return 2;
}
i2c_stop(); //发送停止位
i2c_start(); //发送起始信号
myi2c_sendbyte(AT24_ADDR_R1); //发送从机地址
if(myi2c_recv_ack() == 1){
i2c_stop(); //发送停止位
printf("AT24寻址未应答\r\n");
return 1;
}
read_data = myi2c_recv_byte();
myi2c_send_ack(1);
i2c_stop(); //发送停止位
return read_data;
}
/**
* @brief 指定地址页写入数据(0 ---- 255)
* @param uint16_t addr 写入数据地址
* @param uint8_t data 写入字节首地址
* @param uint8_t num 写入字节个数
* @retval 写入成功返回4
*/
uint8_t AT24_write_page(uint16_t addr, uint8_t num, uint8_t *data){
i2c_start(); //发送起始信号
myi2c_sendbyte(AT24_ADDR_W1); //发送从机地址
if(myi2c_recv_ack() == 1){
i2c_stop(); //发送停止位
printf("AT24寻址未应答\r\n");
return 1;
}
myi2c_sendbyte(addr); //发送要写入的地址
if(myi2c_recv_ack() == 1){
i2c_stop(); //发送停止位
printf("AT24内部寻址未应答\r\n");
return 2;
}
while(num--){
myi2c_sendbyte(*data); //发送要写入的数据
if(myi2c_recv_ack() == 1){
i2c_stop(); //发送停止位
printf("AT24写入数据未应答\r\n");
return 3;
}
data++;
}
i2c_stop(); //发送停止位
printf("AT24写入数据成功\r\n");
return 4;
}
/**
* @brief 随机写
* @param uint8_t *pBuffer 写入数据的首地址
* @param uint32_t WriteAddr 写入地址
* @param uint16_t NumByteToWrite 数据长度
* @retval None
*/
void AT24_WriteBuffer(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite){
uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
Addr = WriteAddr % 16; //判断地址是否为整页
count = 16 - Addr; //当前页剩余字节数
NumOfPage = NumByteToWrite / 16; //需要的整页数
NumOfSingle = NumByteToWrite % 16; //除整页剩余的字节数
if (Addr == 0) /*整页开始 */
{
if (NumOfPage == 0) /*所写数据不够一整页,直接调用页编程函数 */
{
AT24_write_page(WriteAddr, NumByteToWrite, pBuffer);
}
else /*所写数据超过一页*/
{
while (NumOfPage--) //整页写
{
AT24_write_page(WriteAddr, 16, pBuffer);
WriteAddr += 16;
pBuffer += 16;
}
AT24_write_page(WriteAddr, NumOfSingle, pBuffer); //除整页之外剩余的
}
}
else /*不是整页开始写 */
{
if (NumOfPage == 0) /*所写不到一页 */
{
if (NumOfSingle > count) /*所需空间大于当前页所剩空间*/
{
temp = NumOfSingle - count; //当前页写完之后剩余量
AT24_write_page(WriteAddr, count, pBuffer); //在当前页写,写满
WriteAddr += count;
pBuffer += count;
AT24_write_page(WriteAddr, temp, pBuffer); //剩余写入下一页
}
else
{
AT24_write_page(WriteAddr, NumByteToWrite, pBuffer); //直接写当前页
}
}
else /*写入数据量大于一页 */
{
NumByteToWrite -= count; //写满当前页所剩数据
NumOfPage = NumByteToWrite / 16; //要写入的整页
NumOfSingle = NumByteToWrite % 16; //写完整页剩余的字节
AT24_write_page(WriteAddr, count, pBuffer);//把当前页写满
WriteAddr += count;
pBuffer += count;
while (NumOfPage--) //写整页
{
AT24_write_page(WriteAddr, 16, pBuffer);
WriteAddr += 16;
pBuffer += 16;
}
if (NumOfSingle != 0) //写剩余不满一页的字节
{
AT24_write_page(WriteAddr, NumOfSingle, pBuffer);
}
}
}
}
/**
* @brief 随便读
* @param None
* @retval None
*/
uint8_t AT24_readBuffer(uint16_t addr, uint16_t num, uint8_t *recvdata){
i2c_start(); //发送起始信号
myi2c_sendbyte(AT24_ADDR_W1); //发送从机地址
Delay_us(10);
if(myi2c_recv_ack() == 1){
i2c_stop(); //发送停止位
printf("AT24器件寻址未应答\r\n");
return 1;
}
myi2c_sendbyte(addr); //发送要写入的地址
if(myi2c_recv_ack() == 1){
i2c_stop(); //发送停止位
printf("AT24内部寻址未应答\r\n");
return 2;
}
i2c_stop(); //发送停止位
i2c_start(); //发送起始信号
myi2c_sendbyte(AT24_ADDR_R1); //发送从机地址
if(myi2c_recv_ack() == 1){
i2c_stop(); //发送停止位
printf("AT24器件2寻址未应答\r\n");
return 1;
}
while(num--){
*recvdata = myi2c_recv_byte();
myi2c_send_ack(0);
recvdata++;
Delay_us(5);
}
myi2c_send_ack(1);
i2c_stop(); //发送停止位
return num;
}
/*头文件*/
#ifndef __AT24_H_
#define __AT24_H_
#include "stm32f4xx.h" // Device header
#include "myi2c.h"
#include "usart.h"
#include "delay.h"
void AT24_init(void);
uint8_t AT24_write_byte(uint16_t addr, uint8_t data);
uint8_t AT24_read_byte(uint16_t addr);
uint8_t AT24_write_page(uint16_t addr, uint8_t num, uint8_t *data);
void AT24_WriteBuffer(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
uint8_t AT24_readBuffer(uint16_t addr, uint16_t num, uint8_t *recvdata);
#endif
STM32 用 I²C ペリフェラル
STM32 にはハードウェア I²C トランシーバ回路が統合されており、クロック生成、開始条件と終了条件の生成、応答ビット トランシーバ、データ送信などの機能をハードウェアによって自動的に実行できるため、CPU の負担が軽減されます。STM32 の I²C ペリフェラルは、マルチマスター モード、7 ビットまたは 10 ビットのアドレス モード、さまざまな通信速度 (標準速度は最大 100KHZ、高速は 400KHZ)、DMA、SMBus プロトコルとの互換性をサポートしています。
- ハードウェア自動実行:STM32内のハードウェアI2Cモジュールは、クロック生成、開始条件と終了条件の生成、応答ビットの送受信、データ送信などの機能を自動的に実行できるため、CPUの負担が軽減され、通信がより効率的になります。
- マルチホスト モード: STM32 ハードウェア I2C モジュールは、複数のホスト デバイスが同じバス上で通信できるようにするマルチホスト モードをサポートしています。
- 7 ビットまたは 10 ビット アドレス モード: STM32 ハードウェア I2C モジュールは、さまざまなデバイスのアドレス指定ニーズに適応できる 7 ビットまたは 10 ビット アドレス モードをサポートしています。
- さまざまな通信速度: STM32 のハードウェア I2C モジュールはさまざまな通信速度をサポートしており、標準速度は 100KHz に達し、高速モードは 400KHz に達することができ、特定のニーズに応じて適切な通信速度を選択できます。
- DMA のサポート: STM32 ハードウェア I2C モジュールは DMA (ダイレクト メモリ アクセス) 機能をサポートしており、DMA を介してデータを送信し、データ送信効率を向上させ、CPU 負荷を軽減できます。
- SMBusプロトコルとの互換性:STM32のハードウェアI2Cモジュールは、電子デバイスの管理と制御に使用されるI2Cベースの通信プロトコルであるSMBus(システム管理バス)プロトコルと互換性があります。
これらの特性により、STM32 ハードウェア I2C モジュールは組み込みシステムで I2C 通信を実装するための理想的な選択肢となり、便利で効率的かつ信頼性の高い通信機能を提供します。