21.1 實驗內(nèi)容
通過本實驗主要學(xué)習(xí)以下內(nèi)容:
? AT24C16 EEPROM的工作原理;
? IIC模塊原理以及IIC驅(qū)動原理。
21.2 實驗原理
21.2.1 AT24C16 EEPROM的工作原理
下圖為AT24CXX系列EEPROM相關(guān)參數(shù),由該圖可知,AT24C16的存儲容量為16Kbit,共2048字節(jié),共128頁,每頁為16字節(jié)。
由下圖可知,AT24C16由8塊組成,每塊256字節(jié)。
I2C開始信號后,第一個字節(jié)為器件地址,由1010+3位塊地址+1位讀寫標(biāo)志組成, 3位塊地址剛好可以表示 8個塊, 所以一次寫完256字節(jié),換到下一下塊的時候,要重新更改器件地址。
AT24C16支持頁寫入模式,一次最多可支持寫入16字節(jié)。主機每發(fā)送一個字節(jié),24c16收到確認,內(nèi)部地址遞增(僅限低4bit,所以1次可寫16字節(jié))。
21.2.2 IIC接口原理
GD32F30X系列MCU的I2C 接口模塊實現(xiàn)了I2C 協(xié)議的標(biāo)速模式,快速模式以及快速+模式,具備CRC 計算和校驗功能、支持 SMBus(系統(tǒng)管理總線)和PMBus(電源管理總線),此外還支持多主機 I2C 總線架構(gòu),其主要特性如下:
? 并行總線至 I2C 總線協(xié)議的轉(zhuǎn)換及接口;
? 同一接口既可實現(xiàn)主機功能又可實現(xiàn)從機功能;
? 主從機之間的雙向數(shù)據(jù)傳輸;
? 支持 7 位和 10 位的地址模式和廣播尋址;
? 支持 I2C 多主機模式;
? 支持標(biāo)速(最高 100 KHz),快速(最高 400 KHz)和快速+ 模式(最高 1MHz);
? 從機模式下可配置的 SCL 主動拉低;
? 支持 DMA 模式;
? 兼容 SMBus 2.0 和 PMBus;
? 兩個中斷:字節(jié)成功發(fā)送中斷和錯誤事件中斷;
? 可選擇的 PEC(報文錯誤校驗)生成和校驗。
IIC模塊結(jié)構(gòu)框圖如下所示。
21.3 硬件設(shè)計
EEPROM硬件電路圖如下所示,IIC引腳使用PB10和PB11引腳,SDA和SCL總線通過4.7K電阻上拉,且對地接30pf電容以及100歐姆串阻濾波。
21.4 代碼解析
21.4.1 EEPROM初始化配置函數(shù)
EEPROM初始化配置函數(shù)如下,主要實現(xiàn)對IIC總線引腳配置以及IIC模塊配置。
C
void bsp_eeprom_init_AT24C16(void)
{
driver_i2c_init(&EEPROM_I2C);
}
void driver_i2c_init(typdef_i2c_struct *i2cx)
{
rcu_periph_clock_enable(i2cx->rcu_i2c_x);
i2c_deinit(i2cx->i2c_x);
driver_gpio_general_init(i2cx->i2c_scl_gpio);
driver_gpio_general_init(i2cx->i2c_sda_gpio);
/* I2C clock configure */
i2c_clock_config(i2cx->i2c_x, i2cx->frequency, I2C_DTCY_2);
/* I2C address configure */
i2c_mode_addr_config(i2cx->i2c_x, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, i2cx->slave_addr);
/* enable I2C0 */
i2c_enable(i2cx->i2c_x);
/* enable acknowledge */
i2c_ack_config(i2cx->i2c_x, I2C_ACK_ENABLE);
}
21.4.2 EEPROM buf寫入接口函數(shù)
EEPROM buf寫入接口函數(shù)實現(xiàn)如下,通過該函數(shù)可實現(xiàn)對AT24C16任意地址的多字節(jié)寫入。內(nèi)部已根據(jù)地址和寫入長度自動識別從機地址以及對應(yīng)的塊,然后寫入正確的地址空間。
C
EEPROM_STATE eeprom_buffer_write_AT24C16(uint8_t* p_buffer, uint16_t write_address, uint16_t number_of_byte)
{
uint8_t number_of_page = 0, number_of_single = 0, address = 0, count = 0;
uint8_t deviceId;
address = write_address % I2C_PAGE_SIZE;
count = I2C_PAGE_SIZE - address;
number_of_page = ?number_of_byte / I2C_PAGE_SIZE;
number_of_single = number_of_byte % I2C_PAGE_SIZE;
if(write_address+write_address>EEPROM_SIZE)
{
return EEPROM_ERROR;
}
/* if write_address is I2C_PAGE_SIZE aligned ?*/
if(0 == address){
while(number_of_page--){
deviceId=(write_address>>8)>0 ? (EEPROM_ADDR | (uint8_t)((write_address>>7)&0x0E)):EEPROM_ADDR ;
if(driver_i2c_mem_poll_write(&EEPROM_I2C,deviceId,write_address,MEM_ADDRESS_8BIT,p_buffer,I2C_PAGE_SIZE) == DRV_ERROR)
{
return EEPROM_ERROR;
}
if(eeprom_wait_standby_state(&EEPROM_I2C) == EEPROM_ERROR)
{
return EEPROM_ERROR;
}
write_address += ?I2C_PAGE_SIZE;
p_buffer += I2C_PAGE_SIZE;
}
if(0 != number_of_single){
deviceId=(write_address>>8)>0 ? (EEPROM_ADDR | (uint8_t)((write_address>>7)&0x0E)):EEPROM_ADDR ;
if(driver_i2c_mem_poll_write(&EEPROM_I2C,deviceId,write_address,MEM_ADDRESS_8BIT,p_buffer, number_of_single)==DRV_ERROR)
{
return EEPROM_ERROR;
}
if(eeprom_wait_standby_state(&EEPROM_I2C) == EEPROM_ERROR)
{
return EEPROM_ERROR;
}
}
return ????????EEPROM_SUCCESS;
}else{
/* if write_address is not I2C_PAGE_SIZE aligned */
if(number_of_byte < count){
deviceId=(write_address>>8)>0 ? (EEPROM_ADDR | (uint8_t)((write_address>>7)&0x0E)):EEPROM_ADDR ;
if(driver_i2c_mem_poll_write(&EEPROM_I2C,deviceId,write_address,MEM_ADDRESS_8BIT,p_buffer, number_of_byte)==DRV_ERROR)
{
return EEPROM_ERROR;
}
if(eeprom_wait_standby_state(&EEPROM_I2C)==EEPROM_ERROR)
{
return EEPROM_ERROR;
}
}else{
number_of_byte -= count;
number_of_page = ?number_of_byte / I2C_PAGE_SIZE;
number_of_single = number_of_byte % I2C_PAGE_SIZE;
if(0 != count){
deviceId=(write_address>>8)>0 ? (EEPROM_ADDR | (uint8_t)((write_address>>7)&0x0E)):EEPROM_ADDR ;
if(driver_i2c_mem_poll_write(&EEPROM_I2C,deviceId,write_address,MEM_ADDRESS_8BIT,p_buffer, count)==DRV_ERROR)
{
return EEPROM_ERROR;
}
if(eeprom_wait_standby_state(&EEPROM_I2C)==EEPROM_ERROR)
{
return EEPROM_ERROR;
}
write_address += count;
p_buffer += count;
}
/* write page */
while(number_of_page--){
deviceId=(write_address>>8)>0 ? (EEPROM_ADDR | (uint8_t)((write_address>>7)&0x0E)):EEPROM_ADDR ;
if(driver_i2c_mem_poll_write(&EEPROM_I2C,deviceId,write_address,MEM_ADDRESS_8BIT,p_buffer, I2C_PAGE_SIZE)==DRV_ERROR)
{
return EEPROM_ERROR;
}
if(eeprom_wait_standby_state(&EEPROM_I2C)==EEPROM_ERROR)
{
return EEPROM_ERROR;
}
write_address += ?I2C_PAGE_SIZE;
p_buffer += I2C_PAGE_SIZE;
}
/* write single */
if(0 != number_of_single){
deviceId=(write_address>>8)>0 ? (EEPROM_ADDR | (uint8_t)((write_address>>7)&0x0E)):EEPROM_ADDR ;
if(driver_i2c_mem_poll_write(&EEPROM_I2C,deviceId,write_address,MEM_ADDRESS_8BIT,p_buffer, number_of_single)==DRV_ERROR)
{
return EEPROM_ERROR;
}
if(eeprom_wait_standby_state(&EEPROM_I2C)==EEPROM_ERROR)
{
return EEPROM_ERROR;
}
}
}
return ????????EEPROM_SUCCESS;
}
}
21.4.3 EEPROM buf讀取接口函數(shù)
EEPROM buf讀取接口函數(shù)實現(xiàn)如下,通過該函數(shù)可實現(xiàn)對EEPROM任意地址的多字節(jié)數(shù)據(jù)讀取,內(nèi)部也對讀取的地址進行自動識別從機地址。
C
EEPROM_STATE eeprom_buffer_read_AT24C16(uint8_t* p_buffer, uint16_t read_address, uint16_t number_of_byte)
{
uint8_t rNum=0; //讀取的數(shù)據(jù)長度
uint16_t lenLeft=number_of_byte;//剩余的數(shù)據(jù)長度
uint8_t deviceId;//讀取的器件地址
if(read_address+number_of_byte>EEPROM_SIZE)//如果讀取的長度加上讀取地址超過了EEPROM的空間大小,則報錯誤
{
return EEPROM_ERROR;
}
/*calculate the current read position to know how many word can read continully*/
rNum=16-read_address & 0x0F;
if(rNum == 0) ?rNum=16;
rNum = lenLeft>=rNum ? rNum : lenLeft;//剩余未讀字節(jié)數(shù)如果大于rNum, 則讀rNum個,如果小于rNum,則一次讀完了
/*read the data from e2prom*/
while(lenLeft)
{
//這里計算頁地址,當(dāng)?shù)刂沸∮?56時,右移8位會小于0,所以器件地址為基地址A1
//如果讀取的地址大于256時,右移8位則不會小于0,所以器件地址為 基地址A1 | 3位頁地址
deviceId=(read_address>>8)>0 ? (EEPROM_ADDR | (uint8_t)((read_address>>7)&0x0E)):EEPROM_ADDR ;
if(driver_i2c_mem_poll_read(&EEPROM_I2C,deviceId,read_address,MEM_ADDRESS_8BIT,p_buffer,rNum)==DRV_ERROR)
{
// ???????????????????????printf("i2c read error\r\n");
return EEPROM_ERROR;
}
read_address+=rNum;//已經(jīng)讀了rNum個了,所以地址后移rNum個
lenLeft-=rNum;//剩余未讀數(shù)據(jù)減少rNum個
p_buffer+=rNum;
rNum=lenLeft>16? 16 : lenLeft;//如果剩余大于16個,則下次再讀16個,如果小于,則一次讀完
}
return EEPROM_SUCCESS;
}
21.4.4 EEPROM讀寫實驗主函數(shù)
EEPROM讀寫實驗主函數(shù)如下所示。通過該實驗實現(xiàn)對AT24C16任意地址256字節(jié)的寫入、讀取以及校驗測試。
C
int main(void)
{
uint16_t i;
uint8_t i2c_buffer_write[BUFFER_SIZE];
uint8_t i2c_buffer_read[BUFFER_SIZE];
bsp_eeprom_init_AT24C16();
/* initialize i2c_buffer_write */
for(i = 0;i < BUFFER_SIZE;i++){
i2c_buffer_write[i]=i;
// ???????printf("0x%02X ",i2c_buffer_write[i]);
// ???????if(15 == i%16){
// ???????????printf("\r\n");
// ???????}
}
if(eeprom_buffer_write_AT24C16(i2c_buffer_write,0x0153,BUFFER_SIZE)==EEPROM_SUCCESS)
{
__nop();
}
if(eeprom_buffer_read_AT24C16(i2c_buffer_read,0x0153,BUFFER_SIZE)==EEPROM_SUCCESS)
{
__nop();
}
/* compare the read buffer and write buffer */
for(i = 0;i < BUFFER_SIZE;i++){
if(i2c_buffer_read[i] != i2c_buffer_write[i]){
__nop();
// ???????????printf("0x%02X ", i2c_buffer_read[i]);
// ???????????printf("Err:data read and write aren't matching.\n\r");
// ???????????return I2C_FAIL;
}
//printf("0x%02X ", i2c_buffer_read[i]);
// ???????if(15 == i%16){
// ???????????printf("\r\n");
// ???????}
}
__nop();
// ???printf("I2C-AT24C02 test passed!\n\r");
while (1)
{
}
}
21.5 實驗結(jié)果
將本實驗歷程燒錄到紅楓派開發(fā)板中,運行后,可通過串口打印測試結(jié)果,可實現(xiàn)對于AT24C16任意地址寫入、讀取以及校驗。
紅楓派開發(fā)板使用手冊:??????????????????????????????????????????????????GD32F303紅楓派使用手冊 - 飛書云文檔 (feishu.cn)