一、IIC基礎知識
1. 串口通信與IIC通信
-
串口通信通常需要至少三條線(TX、RX和GND),而 I2C 總線僅需要兩條信號線(SDA和SCL);
-
串口通信僅支持一對一通信,而 I2C 總線支持多機通信,允許單個主機與多個從機設備進行通信;
-
串口通信通常無應答機制,而 I2C 必須有應答機制;
-
串口通訊一般是異步通信,而 I2C 使用同步傳輸方式,數據在時鐘信號(SCL)的控制下傳輸。
2. IIC總線介紹
I2C總線,全稱Inter-Integrated Circuit(互連集成電路),是一種由Philips(現NXP半導體)公司在1980年
代初開發的同步 串行 半雙工通信總線。
IIC串行總線一般有兩根信號線,一根是雙向的數據線SDA,另一根是時鐘線SCL,其時鐘信號是由主控器件產生。所有接到IIC總線設備上的串行數據SDA都接到總線的SDA上,各設備的時鐘線SCL接到總線的SCL上。對于并聯在一條總線上的每個IC都有唯一的地址。
IIC接上拉電阻,因此引腳配置成開漏輸出(只可以輸出低電平)。
2.1 工作原理
- 主從關系:主器件用于啟動總線傳送數據,并產生時鐘以開放傳送的器件,此時任何被尋址的器件均被認為是從器件。在總線上主和從、發和收的關系不是恒定的,而取決于此時數據傳送方向。
- 數據傳送:如果主機要發送數據給從器件,則主機首先尋址從器件,然后主動發送數據至從器件,最后由主機終止數據傳送。
如果主機要接收從器件的數據,首先由主器件尋址從器件,然后主機接收從器件發送的數據,最后由主機終止接收過程。 - 時鐘同步:SCL用于數據的時鐘同步,確保主從設備之間的數據傳輸同步進行。
2.2 主要特點
- 硬件簡單:I2C總線只需要一根數據線和一根時鐘線兩根線,總線接口已經集成在芯片內部,不需要特殊的接口電路。
- 多主機總線:I2C總線是一個真正的多主機總線,如果兩個或多個主機同時初始化數據傳輸,可以通過沖突檢測和仲裁防止數據破壞。
- 在線檢測:I2C總線可以通過外部連線進行在線檢測,便于系統故障診斷和調試。
- 數據傳輸與地址設定:數據傳輸和地址設定由軟件設定,非常靈活。總線上的器件增加和刪除不影響其他器件正常工作。
- 負載能力:由于線路中電容會影響總線傳輸速度,I2C總線的負載能力為400pF,因此可以估算出總線允許長度和所接器件數量。
2.3 應用領域
I2C總線廣泛應用于各種設備和應用領域,例如傳感器、存儲器(如EEPROM)、顯示屏、溫度傳感器、實時時鐘(RTC)、擴展IO芯片等。
3. IIC總線時序
3.1 起始和停止信號
當 SCL 為高電平時,SDA 產生下降沿,就是電平由高變低表示起始信號;
當 SCL 為高電平時,SDA 產生上升沿,就是電平由低變高表示停止信號;
3.2 數據有效性
在SCL的高電平期間,SDA是不允許變化的;而只有在時鐘線SCL的低電平期間,SDA才能夠出現變化;
3.3 響應和非響應
每當主機向從機發送完一個字節的數據,主機總是需要等待從機給出一個應答信號,以確認從機是否成功接收到了數據。
應答信號:主機SCL拉高,讀取從機SDA的電平,為低電平表示產生應答。
二、軟件模擬IIC
EEPROM芯片最常用的通訊方式就是I2C協議。我們使用的芯片是AT24C02。
需求描述:
向EEPROM寫入一段數據,再讀取出來,最后發送到串口。
1. AT24C02簡介
AT24C02是一款串行電可擦除編程只讀存儲器(EEPROM)。存儲器可存儲256個字節數據,分為32頁,每頁8字節,隨機字尋址需要8位數據字地址。
A0,A1,A2:硬件地址引腳
WP:寫保護引腳,接高電平只讀,接地允許讀和寫
SCL和SDA:IIC總線
VCC和GND: 電源線和地線
從上圖可以看出,AT24C02設備地址如下,前四位固定為1010,低三位由A0~A2信號線的電平決定設備地址,這里都接的低電平。最后一位表示讀寫操作。
所以AT24C02的讀地址為0xA1,寫地址為0xA0。
2. 操作時序
2.1 寫入一個字節時序
- 發送一個START信號
- 接著發送器件地址,器件地址的最后一位為數據的傳輸方向位,R/W,低電平0表示主機往從機寫數據(W),1表示主機從從機讀數據(R)。ACK應答,應答是從機發送給主機的應答,這里不用管。
- 傳送數據的存儲地址,即數據要寫入的位置。同樣ACK應答不用管。
- 傳送要寫入的數據。ACK應答不用管。
- 產生STOP信號。
寫入數據時,EEPROM先寫到緩沖區,再寫入非易失性存儲器,這個過程需要不超過5ms的時間。每一次頁寫或字節寫結束后延時5ms確保完成寫周期,否則時序會出錯。
一次性寫入多個字節,也叫頁寫入。每頁只有8個字節,所以一次性最多只能寫入8個字節。當一次性寫入超過8個字節,繼續寫數據會從頁的首地址寫入覆蓋原來的數據。
2.2 隨機讀取
- 產生START信號
- 傳送器件地址(寫操作),ACK應答
- 傳送字地址,ACK應答
- 再次產生START信號
- 再傳送一次器件地址(讀操作),ACK應答
- 讀取一個字節數據 ,讀數據最后結束前無應答信號
- 產生STOP信號
這里先寫入器件地址和字地址,而不寫入實際數據,稱為“假寫”,其目的是加載數據字地址。事實上,EEPROM有一個內部數據字地址計數器。內部數據字地址計數器保持上次讀或寫操作訪問的最后一個地址,并加一。只要芯片電源保持,該地址在操作之間保持有效。如果不進行假寫,直接傳送器件地址進行讀操作,SDA數據線直接交給EEPROM控制,此時無法傳送我們所需讀取的地址。
IIC協議在讀寫數據時,總是要發送器件地址,這里需要注意的是,不是主機給從機發送地址,而是主機給地址總線上發送地址,掛IIC總線上的所有從機都能收到地址,如果發過來的地址和自己的地址匹配上了,從機就會給主機一個應答,這樣就建立起來了一個通訊。
IIC協議就是通過地址不同來判斷給哪個器件傳送數據的,如果兩個器件的地址完全一樣,器件會產生應答,那么兩個器件就通過競爭判斷給誰通信了,有隨機性。即IIC協議一次只能和一個設備/器件進行通訊。
除了隨機讀取,EEPROM的讀操作還有當前地址讀和順序讀。
2.3 當前地址讀
很顯然當前地址讀沒有假寫操作,由于內部數據字地址計數器已經保持了上次讀或寫操作訪問的最后地址+1,所以直接從該地址讀取,就是所謂的當前地址讀。
2.4 順序讀取
順序讀取就是單次讀出多個字節。
順序讀取由當前地址讀取或隨機地址讀取啟動。在微控制器接收到數據字后,它會以確認響應。只要 EEPROM 接收到確認,它就會繼續遞增數據字地址并串行輸出順序數據字。當達到內存地址限制時,數據字地址將“回滾”,順序讀取將繼續。
讀操作中的地址“回滾”是從最后一個內存頁的最后一個字節到第一個頁的第一個字節。寫操作中的地址“回滾”是從當前頁的最后一個字節到同一頁的第一個字節。
3.示例
3.1 i2c.h
#ifndef __I2C_H
#define __I2C_H#include "stm32f10x.h"
#include "delay.h"#define ACK 0
#define NACK 1// PB6 SCL
#define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR6)
#define SCL_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR6)
// PB7 SDA
#define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR7)
#define SDA_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR7)#define READ_SDA (GPIOB->IDR & GPIO_ODR_ODR7)void I2C_init(void);
void I2C_Start(void);
void I2C_Stop(void);
// 產生應答信號
void I2C_Ack(void);
// 產生非應答信號
void I2C_NAck(void);
// 等待應答
uint8_t I2C_WaitAck(void);
// 發送一個字節的數據
void I2C_SendByte(uint8_t byte);
// 讀一個字節數據
uint8_t I2C_ReadByte(void);#endif
3.2 i2c.c
#include "i2c.h"#define I2C_delay Delay_us(10)void I2C_init(void)
{// PB6 SCL// PB7 SDA 設置開漏輸出 MODE=11 CNF=01RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;GPIOB->CRL |= GPIO_CRL_MODE6;GPIOB->CRL |= GPIO_CRL_CNF6_0;GPIOB->CRL &= ~GPIO_CRL_CNF6_1;GPIOB->CRL |= GPIO_CRL_MODE7;GPIOB->CRL |= GPIO_CRL_CNF7_0;GPIOB->CRL &= ~GPIO_CRL_CNF7_1;
}void I2C_Start(void)
{// 拉高sda sclSDA_HIGH;SCL_HIGH;// 延時I2C_delay;// 拉低sdaSDA_LOW;// 延時I2C_delay;
}void I2C_Stop(void)
{// 拉低sda 拉高sclSDA_LOW;SCL_HIGH;// 延時I2C_delay;// 拉高sdaSDA_HIGH;// 延時I2C_delay;
}// 產生應答信號
void I2C_Ack(void)
{// 拉高sda 拉低sclSDA_HIGH;SCL_LOW;// 延時I2C_delay;// 拉低sdaSDA_LOW;// 延時I2C_delay;// 拉高sclSCL_HIGH;// 延時I2C_delay;// 拉低sclSCL_LOW;// 延時I2C_delay;// 拉高sdaSDA_HIGH;// 延時I2C_delay;
}// 產生非應答信號
void I2C_NAck(void)
{SDA_HIGH;SCL_LOW;I2C_delay;SCL_HIGH;I2C_delay;SCL_LOW;I2C_delay;
}// 等待應答
uint8_t I2C_WaitAck(void)
{uint8_t ack = ACK;// sda拉高 sda主動權交給對方SDA_HIGH;// scl拉低SCL_LOW;// 延時I2C_delay;// scl拉高SCL_HIGH;// 延時I2C_delay;// 讀取sda電平if (READ_SDA){ack = NACK;}// scl拉低SCL_LOW;// 延時I2C_delay;return ack;
}// 發送一個字節的數據
void I2C_SendByte(uint8_t byte)
{for (uint8_t i = 0; i < 8; i++){// 拉低sda sclSDA_LOW;SCL_LOW;I2C_delay;// 向sda寫數據 高字節優先if (byte & 0x80){SDA_HIGH;}else{SDA_LOW;}I2C_delay;// 拉高sclSCL_HIGH;I2C_delay;// 拉低sclSCL_LOW;I2C_delay;// 左移1位 為下一次發送做準備byte <<= 1;}
}// 讀一個字節數據
uint8_t I2C_ReadByte(void)
{uint8_t data = 0;for (uint8_t i = 0; i < 8; i++){// 拉低sclSCL_LOW;I2C_delay;// 拉高sclSCL_HIGH;I2C_delay;// 讀取sdadata <<= 1;if (READ_SDA){data |= 0x01;}// 拉低sclSCL_LOW;I2C_delay;}return data;
}
3.3 at24c02.h
#ifndef __AT24C02_H
#define __AT24C02_H
#include "stm32f10x.h"#define ADDR_READ 0xA1
#define ADDR_WRITE 0xA0void AT24C02_Init(void);
void AT24C02_WriteByte(uint8_t inneraddr, uint8_t byte);
void AT24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len);
uint8_t AT24C02_ReadByte(uint8_t inneraddr);
void AT24C02_ReadBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len);#endif
3.4 at24c02.c
#include "at24c02.h"
#include "i2c.h"void AT24C02_Init(void)
{I2C_init();
}void AT24C02_WriteByte(uint8_t inneraddr, uint8_t byte)
{// 開始信號I2C_Start();// 發送寫地址I2C_SendByte(ADDR_WRITE);// 等待響應I2C_WaitAck();// 發送內部地址I2C_SendByte(inneraddr);// 等待響應I2C_WaitAck();// 發送數據I2C_SendByte(byte);// 等待響應I2C_WaitAck();// 停止信號I2C_Stop();// 寫周期Delay_ms(5);
}// 頁寫入 一次性寫入多個字節
void AT24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len)
{// 開始信號I2C_Start();// 發送寫地址I2C_SendByte(ADDR_WRITE);// 等待響應I2C_WaitAck();// 發送內部地址I2C_SendByte(inneraddr);// 等待響應I2C_WaitAck();for (uint8_t i = 0; i < len; i++){// 發送數據I2C_SendByte(bytes[i]);// 等待響應I2C_WaitAck();}// 停止信號I2C_Stop();// 寫周期Delay_ms(5);
}uint8_t AT24C02_ReadByte(uint8_t inneraddr)
{// 起始信號I2C_Start();// 發送寫地址 假寫I2C_SendByte(ADDR_WRITE);// 等待響應I2C_WaitAck();// 發送內部地址I2C_SendByte(inneraddr);// 等待響應I2C_WaitAck();// 起始信號I2C_Start();// 發送讀地址 真讀I2C_SendByte(ADDR_READ);// 等待響應I2C_WaitAck();// 讀取一個字節uint8_t byte = I2C_ReadByte();// 給對方非應答I2C_NAck();// 停止信號I2C_Stop();return byte;
}void AT24C02_ReadBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len)
{// 起始信號I2C_Start();// 發送寫地址 假寫I2C_SendByte(ADDR_WRITE);// 等待響應I2C_WaitAck();// 發送內部地址I2C_SendByte(inneraddr);// 等待響應I2C_WaitAck();// 起始信號I2C_Start();// 發送讀地址 真讀I2C_SendByte(ADDR_READ);// 等待響應I2C_WaitAck();for (uint8_t i = 0; i < len; i++){// 讀取一個字節bytes[i] = I2C_ReadByte();if (i < len - 1){// 主動發送應答信號I2C_Ack();}else{// 最后一個字節發送非應答信號I2C_NAck();}}// 停止信號I2C_Stop();
}
3.5 main.c
#include "led.h"
#include "delay.h"
#include "usart.h"
#include "at24c02.h"
#include "string.h"int main(void)
{USART1_init();AT24C02_Init();printf("hello world!\r\n");AT24C02_WriteByte(0x00, 'a');AT24C02_WriteByte(0x01, 'b');AT24C02_WriteByte(0x02, 'c');uint8_t byte1 = AT24C02_ReadByte(0x00);uint8_t byte2 = AT24C02_ReadByte(0x01);uint8_t byte3 = AT24C02_ReadByte(0x02);printf("%c\r\n", byte1);printf("%c\r\n", byte2);printf("%c\r\n", byte3);AT24C02_WriteBytes(0x00, "123456", 6);uint8_t buff[16] = {0};AT24C02_ReadBytes(0x00, buff, 6);printf("%s\r\n", buff);// 頁寫 最多一次性寫入8個字節 超過覆蓋掉前面數據AT24C02_WriteBytes(0x00, "123456abc", 9);memset(buff, 0, sizeof(buff));AT24C02_ReadBytes(0x00, buff, 9);printf("%s\r\n", buff);while (1){}
}
三、硬件實現IIC
STM32的 I2C 外設可用作通訊的主機及從機,支持100Kbit/s和400Kbit/s的速率,支持7位、10位設備地址,支持DMA數據傳輸,并具有數據校驗功能。
它的I2C外設還支持 SMBus2.0協議,SMBus協議與I2C類似。
1. 寄存器實現
1.1 i2c.h
#ifndef __I2C_H
#define __I2C_H#include "stm32f10x.h"
#include "delay.h"#define OK 1
#define FAIL 0void I2C_init(void);
uint8_t I2C_Start(void);
void I2C_Stop(void);
// 產生應答信號
void I2C_Ack(void);
// 產生非應答信號
void I2C_NAck(void);
// 發送設備地址
uint8_t I2C_SendAddr(uint8_t addr);
// 發送一個字節的數據
uint8_t I2C_SendByte(uint8_t byte);
// 讀一個字節數據
uint8_t I2C_ReadByte(void);#endif
1.2 i2c.c
#include "i2c.h"// #define I2C_delay Delay_us(10)void I2C_init(void)
{// 1.開啟時鐘// 1.1 GPIO時鐘RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;// 1.2 i2c硬件時鐘RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;// 2. 設置工作模式// PB6 SCL// PB7 SDA 設置復用開漏輸出 MODE=11 CNF=11GPIOB->CRL |= GPIO_CRL_MODE6;GPIOB->CRL |= GPIO_CRL_CNF6;GPIOB->CRL |= GPIO_CRL_MODE7;GPIOB->CRL |= GPIO_CRL_CNF7;// 3. 設置i2c// 3.1 配置硬件工作模式I2C1->CR1 &= ~I2C_CR1_SMBUS;// 3.2 配置給i2c設備提供的時鐘頻率 36MHZI2C1->CR2 |= 36;// 3.3 設置標準模式/快速模式I2C1->CCR &= ~I2C_CCR_FS;// 3.4 配置時鐘控制分頻系數// Thigh=CCR * Tcplk1// ccr = Thigh/=Tcplk1 = 5us / (1/36)us = 180I2C1->CCR |= 180;// 3.5 時鐘信號的上升沿// 時鐘頻率是36MHz則 寫入:1 /(1/36) + 1 = 37// 計算的是最大上升沿時間/時鐘周期 + 1I2C1->TRISE |= 37;// 4. 使能i2cI2C1->CR1 |= I2C_CR1_PE;
}uint8_t I2C_Start(void)
{// 重復產生起始條件// SDA數據線開始可能會被占用 所以這里需要重復產生I2C1->CR1 |= I2C_CR1_START;uint16_t timeout = 0xFFFF;while ((I2C1->SR1 & I2C_SR1_SB) == 0 && timeout){timeout--;}return timeout ? OK : FAIL;
}void I2C_Stop(void)
{// 產生終止條件I2C1->CR1 |= I2C_CR1_STOP;
}// 產生應答信號
void I2C_Ack(void)
{I2C1->CR1 |= I2C_CR1_ACK;
}// 產生非應答信號
void I2C_NAck(void)
{I2C1->CR1 &= ~I2C_CR1_ACK;
}// 發送設備地址
uint8_t I2C_SendAddr(uint8_t addr)
{I2C1->DR = addr;uint16_t timeout = 0xffff;while ((I2C1->SR1 & I2C_SR1_ADDR) == 0 && timeout){timeout--;}// 讀取SR1寄存器后,對SR2寄存器的讀操作將清除該位I2C1->SR2;return timeout ? OK : FAIL;
}// 發送一個字節的數據
uint8_t I2C_SendByte(uint8_t byte)
{uint16_t timeout = 0xffff;// 判斷數據寄存器是否為空while ((I2C1->SR1 & I2C_SR1_TXE) == 0 && timeout){timeout--;}// 要發送的數據寫到寄存器I2C1->DR = byte;// 判斷字節是否發送結束timeout = 0xffff;while ((I2C1->SR1 & I2C_SR1_BTF) == 0 && timeout){timeout--;}return timeout ? OK : FAIL;
}// 讀一個字節數據
uint8_t I2C_ReadByte(void)
{uint16_t timeout = 0xffff;// 判斷數據寄存器是否為非空while ((I2C1->SR1 & I2C_SR1_RXNE) == 0 && timeout){timeout--;}// 數據寄存器的值返回uint8_t data = timeout ? I2C1->DR : 0;return data;
}
下面代碼截取了i2c.c的一部分代碼,用于配置CCR和TRISE寄存器的相應位。
// 3.4 配置時鐘控制分頻系數// Thigh=CCR * Tcplk1// ccr = Thigh/=Tcplk1 = 5us / (1/36)us = 180I2C1->CCR |= 180;// 3.5 時鐘信號的上升沿// 時鐘頻率是36MHz則 寫入:1 /(1/36) + 1 = 37// 計算的是最大上升沿時間/時鐘周期 + 1I2C1->TRISE |= 37;
如何根據需求配置?
1.2.1 CCR相應位配置
T(high) 為SCL上升時間加SCL高電平時間
仿照上面來自手冊的截圖的示例來計算:
在標準模式下,產生100KHZ的SCL的頻率,對應周期10us,T(high) =T(low) =5us。
FREQR選擇36MHZ,則T(PCLK1) = 1/36M = (1/36)us
那么相應CCR位寫入5us/ (1/36)us = 180
1.2.2 TRISE相應位配置
100KHz(標準i2c)的時候要求最大上升沿不超過1us
仿照示例來計算:
時鐘(FREQ[5:0])選擇36MHZ 則寫入1us / (1/36 us) +1 = 37
1.3 at24c02.h
#ifndef __AT24C02_H
#define __AT24C02_H
#include "stm32f10x.h"#define ADDR_READ 0xA1
#define ADDR_WRITE 0xA0void AT24C02_Init(void);
void AT24C02_WriteByte(uint8_t inneraddr, uint8_t byte);
void AT24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len);
uint8_t AT24C02_ReadByte(uint8_t inneraddr);
void AT24C02_ReadBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len);#endif
1.4 at24c02.c
#include "at24c02.h"
#include "i2c.h"void AT24C02_Init(void)
{I2C_init();
}void AT24C02_WriteByte(uint8_t inneraddr, uint8_t byte)
{// 開始信號I2C_Start();// 發送寫地址I2C_SendAddr(ADDR_WRITE);// 發送內部地址I2C_SendByte(inneraddr);// 發送數據I2C_SendByte(byte);// 停止信號I2C_Stop();// 寫周期Delay_ms(5);
}// 頁寫入 一次性寫入多個字節
void AT24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len)
{// 開始信號I2C_Start();// 發送寫地址I2C_SendAddr(ADDR_WRITE);// 發送內部地址I2C_SendByte(inneraddr);for (uint8_t i = 0; i < len; i++){// 發送數據I2C_SendByte(bytes[i]);}// 停止信號I2C_Stop();// 寫周期Delay_ms(5);
}uint8_t AT24C02_ReadByte(uint8_t inneraddr)
{// 起始信號I2C_Start();// 發送寫地址 假寫I2C_SendAddr(ADDR_WRITE);// 發送內部地址I2C_SendByte(inneraddr);// 起始信號I2C_Start();// 發送讀地址 真讀I2C_SendAddr(ADDR_READ);// 設置非應答I2C_NAck();// 設置在接收下一個字節后發出停止信號I2C_Stop();// 讀取一個字節uint8_t byte = I2C_ReadByte();return byte;
}void AT24C02_ReadBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len)
{// 起始信號I2C_Start();// 發送寫地址 假寫I2C_SendAddr(ADDR_WRITE);// 發送內部地址I2C_SendByte(inneraddr);// 起始信號I2C_Start();// 發送讀地址 真讀I2C_SendAddr(ADDR_READ);for (uint8_t i = 0; i < len; i++){if (i < len - 1){// 設置發送應答信號I2C_Ack();}else{// 設置最后一個字節發送非應答信號I2C_NAck();// 設置停止信號I2C_Stop();}// 讀取一個字節bytes[i] = I2C_ReadByte();}
}
在上面代碼中設置非應答和停止信號要在讀取字節之前
// 設置非應答I2C_NAck();// 設置在接收下一個字節后發出停止信號I2C_Stop();// 讀取一個字節uint8_t byte = I2C_ReadByte();
因為在手冊中對主接收器有下面說明:
1.5 main.c
#include "led.h"
#include "delay.h"
#include "usart.h"
#include "at24c02.h"
#include "string.h"int main(void)
{USART1_init();AT24C02_Init();printf("hello world!\r\n");AT24C02_WriteByte(0x00, 'a');AT24C02_WriteByte(0x01, 'b');AT24C02_WriteByte(0x02, 'c');uint8_t byte1 = AT24C02_ReadByte(0x00);uint8_t byte2 = AT24C02_ReadByte(0x01);uint8_t byte3 = AT24C02_ReadByte(0x02);printf("%c\r\n", byte1);printf("%c\r\n", byte2);printf("%c\r\n", byte3);AT24C02_WriteBytes(0x00, "123456", 6);uint8_t buff[16] = {0};AT24C02_ReadBytes(0x00, buff, 6);printf("%s\r\n", buff);// 頁寫 最多一次性寫入8個字節 超過覆蓋掉前面數據AT24C02_WriteBytes(0x00, "123456abc", 9);memset(buff, 0, sizeof(buff));AT24C02_ReadBytes(0x00, buff, 9);printf("%s\r\n", buff);while (1){}
}
2. HAL庫實現
使用STM32CubeMX配置,開啟I2C1,默認配置。同時需要開啟UART1,因為我們需要使用串口打印。
在usart.c中重寫fputc,方便后面使用printf
#include "stdio.h"
int fputc(int ch, FILE *file)
{HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);return ch;
}
向工程中添加at24c02.h、at24c02.c文件
2.1 at24c02.h
#ifndef __AT24C02_H
#define __AT24C02_H#include "stm32f1xx_hal.h"#define ADDR_READ 0xA1
#define ADDR_WRITE 0xA0void AT24C02_Init(void);
void AT24C02_WriteByte(uint8_t inneraddr, uint8_t byte);
void AT24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len);
uint8_t AT24C02_ReadByte(uint8_t inneraddr);
void AT24C02_ReadBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len);#endif
2.2 at24c02.c
#include "at24c02.h"
#include "i2c.h"void AT24C02_Init(void)
{MX_I2C1_Init();
}void AT24C02_WriteByte(uint8_t inneraddr, uint8_t byte)
{// 向設備指定內存地址寫入數據// MemAddSize:內存地址大小HAL_I2C_Mem_Write(&hi2c1, ADDR_WRITE, inneraddr, I2C_MEMADD_SIZE_8BIT, &byte, 1, 1000);// 寫周期HAL_Delay(5);
}// 頁寫入 一次性寫入多個字節
void AT24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len)
{HAL_I2C_Mem_Write(&hi2c1, ADDR_WRITE, inneraddr, I2C_MEMADD_SIZE_8BIT, bytes, len, 1000);// 寫周期HAL_Delay(5);
}uint8_t AT24C02_ReadByte(uint8_t inneraddr)
{uint8_t byte;HAL_I2C_Mem_Read(&hi2c1, ADDR_READ, inneraddr, I2C_MEMADD_SIZE_8BIT, &byte, 1, 1000);return byte;
}void AT24C02_ReadBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len)
{HAL_I2C_Mem_Read(&hi2c1, ADDR_READ, inneraddr, I2C_MEMADD_SIZE_8BIT, bytes, len, 1000);
}