今天介紹下STC8A8K64S4A12系列單片機外部E2PROM存儲器原理和I2C總線的原理及工作模式,掌握STC8A8K64S4A12系列單片機I2C外設相關的寄存器配置及程序設計。
一、硬件設計1.E2PROM存儲器介紹EEPROM(全稱是Electrically Erasable Programmable Read-Only Memory)帶電可擦除可編程隻讀存儲器,該類存儲器是用戶可更改的隻讀存儲器(ROM),其可通過高于普通電壓的作用來擦除和重編程(重寫)。EEPROM(常寫成E2PROM)是一種特殊形式的閃存,其應用通常是個人電腦中的電壓來擦寫和重編程。
E2PROM存儲器可分為片内E2PROM和片外E2PROM,片外E2PROM和單片機之間通過各種通信接口連接,而最為常見的通信接口即是I2C接口。一般情況下,E2PROM可寫入或擦除的次數是30~100萬次,而讀取次數是沒有限制的。
24C02芯片是一種常用的基于I2C通信協議的E2PROM元件,例如ATMEL公司的AT24C02、CATALYST公司的CAT24C02和ST公司的ST24C02等芯片。24C02芯片的存儲空間是256字節(2048位),當然如果需要更大存儲空間的存儲器可以選擇其他型号的,比如24C04芯片(512字節)、24C08芯片(1024字節)和24C16芯片(2048字節)。
24Cxx系列引腳定義是一緻的,這方便用戶在項目設計時,如遇到選擇的芯片的存儲空間不夠時,可在該系列中選擇存儲空間更大的芯片直接替換,而無需改動硬件部分。
圖1:24C02芯片引腳
STC8A8K64S4A12開發闆上設計了可供用戶使用的AT24C02存儲器芯片,其中原理圖部分及硬件實物部分如下。
圖2:開發闆外擴E2PROM存儲器
2.開發闆DAC硬件電路介紹I2C總線(即IIC總線)是集成電路總線(Inter-Integrated Circuit)的縮寫,是一種簡單、雙向二線制同步串行總線。該I2C總線是1982年由荷蘭的Philips公司為了解決電視機内的CPU和外圍芯片之間連接而開發的,可以說電視機是最早的嵌入式系統之一。
I2C總線是一個真正的多主機總線,如果兩個或多個主機同時初始化數據傳輸,可以通過沖突檢測和仲裁防止數據破壞,每個連接到總線上的器件都有唯一的地址,任何器件既可以作為主機也可以作為從機,但同一時刻隻允許有一個主機。
2.1.主要特征典型的I2C應用原理如下圖所示,I2C總線通信僅需兩根信号線,可以連接多個設備,從設備都有唯一的地址,主設備通過從設備的地址和不同的從設備通信。
圖3:典型I2C總線結構
I2C總線中的設備必須要有唯一的地址,這意味着如果在總線中接入兩個相同的設備,該設備必須有配置地址的功能,這也是我們經常用的I2C接口的設備會有幾個引腳用來配置地址的原因。
對于I2C地址,我們經常看到有的I2C接口設備在規格書中描述的是7位地址,而有的I2C接口設備在規格書中描述的是8位地址,他們有什麼區别?(I2C也有10位地址,但用的較少,這裡不做介紹,本文檔中的内容不涉及到10位地址)。
7位地址和8位地址如下圖所示,他們結構上是一樣的,都是由7個地址位加一個用來表示讀寫的位組成,隻是描述上有所區别。
圖4:I2C地址
☆注:PCF8563時鐘芯片手冊上給出其I2C從機地址是8位,讀地址為0xA3,寫地址為0xA2。
圖5:PCF8563芯片手冊描述
由此可見,所謂的7位地址和8位地址實際上都是7位地址加上最低位的讀寫位,本質上是一樣的,隻是各個I2C接口設備的描述方式不一樣。
I2C保留了如下表所示的兩組I2C地址,這些地址用于特殊用途。
表1:保留地址
序号 |
從機地址 |
R/W位 |
描述 |
1 |
0000 000 |
0 |
廣播呼叫地址。 |
2 |
0000 000 |
1 |
起始字節。 |
3 |
0000 001 |
X |
CBUS 地址。 |
4 |
0000 010 |
X |
保留給不同的總線格式。 |
5 |
0000 011 |
X |
保留到将來使用。 |
6 |
0000 1XX |
X |
Hs 模式主機碼。 |
7 |
1111 1XX |
X |
保留到将來使用。 |
8 |
1111 0XX |
X |
10 位從機尋址。 |
■ 起始和停止條件(START and STOP conditions)
所有的I2C事務都是以START開始、STOP結束,起始和停止條件總是由主機産生,如下圖所示,當SCL為高電平時,SDA從高電平向低電平轉換表示起始條件,當SCL是高電平時,SDA由低電平向高電平轉換表示停止條件。如果總線中存在多個主機,先将SDA拉低的主機獲得總線控制權。
圖6:起始和停止條件
■ 字節格式(Byte format)
I2C總線發送到SDA上的數據必須為8位,即一次傳輸一個字節,每次傳輸可以發送的字節數量不受限制。每個字節後必須跟一個響應位,首先傳輸的是數據的最高位MSB,如果從機要完成一些其他功能後,例如一個内部中斷服務程序才能接收或發送下一個完整的數據字節,那麼從機可以将時鐘線SCL保持為低電平強制主機進入等待狀态,當從機準備好接收下一個字節數據并釋放時鐘線SCL後數據傳輸繼續。
圖7:I2C總線數據傳輸
2.4.ACK和NACK每個字節後會跟随一個ACK信号。接收者通過ACK位告知發送者已經成功接收一字節數據并準備好接收下一字節數據。所有的時鐘脈沖包括ACK信号的時鐘脈沖都是由主機産生的。
下面的5種情況會導緻産生NACK信号:
I2C數據傳輸如下圖所示,在起始條件(S)後,發送從機地址,從機地址是7位,從機地址後緊跟着的第8位是讀寫位(R/W),讀寫位為0表示寫,讀寫位為1表示讀。數據傳輸一般由主機産生的停止位P 終止,但是,如果主機仍希望在總線上通信,他可以産生重複起始條件 S和尋址另一個從機而不是首先産生一個停止條件,在這種傳輸中可能有不同的讀寫格式結合。
圖8:I2C總線傳輸時序
可能的數據傳輸格式有:
圖9:主機發送器7位地址尋址從機接收器(傳輸方向不改變)
圖10:主機發送第一個字節後立即讀取從機
圖11:複合格式
3.STC8A8K64S4A12系列單片機I2C介紹STC8A8K64S4A12系列單片機片内集成了1個I2C串行總線控制器,與标準I2C總線一樣,STC8A8K64S4A12系列單片機的I2C總線支持兩種操作模式:主模式和從模式。
圖12:I2C總線工作模式
但需要知道STC8A8K64S4A12系列單片機的I2C協議與标準I2C協議相比較,有以下2種機制被忽略。
STC8A8K64S4A12系列單片機的每一組I2C都有2個IO引腳供選擇使用,如下表所示。.
表2:單片機I2C外設引腳分配
☆注:獨立GPIO表示開發闆沒有其他的電路使用這個GPIO,非獨立GPIO說明開發闆有其他電路用到了該GPIO。須知同一時刻隻能使能一組IO口作為I2C使用。
STC8A8K64S4A12系列單片機I2C使用哪一組IO口由P_SW2外設端口切換寄存器2的B4、B5位決定,如下圖所示。
圖13:外設端口切換寄存器2
☆注:一般P_SW2寄存器B4、B5位默認是0,即如果沒有對P_SW2寄存器進行操作,則默認選擇的I2C是P1.4、P1.5這一組。
二、軟件設計1.I2C相關寄存器彙集STC8A8K64S4A12系列單片機使用I2C外設時會用到10個寄存器,如下表所示:
表3:STC8A8K64S4A12系列I2C使用寄存器彙總
2.寄存器解析2.1.I2C配置寄存器I2CCFG
I2C配置寄存器控制I2C外設使能選擇、工作模式、總線速度等,詳見下圖。
圖14:I2C配置寄存器I2CCFG
2.2.I2C主機控制寄存器I2CMSCRI2CMSCR主機控制寄存器的配置是針對I2C已配置為主機模式才有意義的,該寄存器的B7位用來控制主機模式的中斷是否打開,B0~B3位為主機模式下的命令,此部分是重點,詳見下圖。
圖15:I2C主機控制寄存器I2CMSCR
2.3.I2C主機輔助控制寄存器I2CMSAUX當配置I2C總線為主機模式時,雖然可以按照I2C标準協議,控制I2CMSCR寄存器發送主機命令,但很多場合還是希望可以簡化這個控制流程。I2CMSAUX寄存器的B0位可以幫助用戶實現I2C自動發送數據并接收ACK信号。
圖16:I2C主機輔助控制寄存器I2CMSAUX
2.4.I2C主機狀态寄存器I2CMSSTI2C主機狀态寄存器B7位是可讀位,用來讀取I2C控制器是否處于忙狀态(BUSY),I2CMSST寄存器B6位是I2C控制器執行I2C主機命令後的中斷标志位,需軟件清零。
圖17:I2C主機狀态寄存器I2CMSST
2.5.I2C從機控制寄存器I2CSLCRI2CSLCR從機控制寄存器可控制處于從機模式下的I2C設備是否使能接收START信号、接收STOP信号、接收1字節數據、發送1字節數據等的中斷功能,詳見下圖。
圖18:I2C從機控制寄存器I2CSLCR
2.6.I2C從機狀态寄存器I2CSLSTI2C從機控制寄存器使能了接收START信号、接收STOP信号、接收1字節數據、發送1字節數據等的中斷功能後,I2C從機狀态寄存器I2CSLST不僅可讀取I2C控制器是否處于忙狀态(BUSY),還可以對接收START信号、接收STOP信号、接收1字節數據、發送1字節數據等的中斷标志位進行讀取,但這些位需軟件清零。
圖19:I2C從機狀态寄存器I2CSLST
2.7.I2C從機地址寄存器I2CSLADRI2C從機地址寄存器B1~B7位是從機設備地址位,STC8A8K64S4A12系列單片機的I2C外設又配置有B0位(MA位),用來設置是否忽略對從機設備地址的匹配。
圖20:I2C從機地址寄存器I2CSLADR
☆注:前面有介紹I2C設備的地址是7位,此處I2C從機設備地址即是7位。
3.I2C配置步驟針對STC8A8K64S4A12系列單片機I2C,軟件的配置過程如下:
圖21:I2C中斷方式和非中斷方式軟件配置步驟
4.外部EEPROM存儲器讀寫單字節實驗(模拟I2C)☆注:本節的實驗源碼是在“實驗2-8-1:串口1收發實驗(P3.0和P3.1)”的基礎上修改。本節對應的實驗源碼是:“實驗2-14-1:外部EEPROM存儲器讀寫單字節實驗(模拟I2C)”。
4.1.工程需要用到的c文件本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。
表4:實驗需要用到的c文件
序号 |
文件名 |
後綴 |
功能描述 |
1 |
uart |
.c |
包含與用戶uart有關的用戶自定義函數。 |
2 |
i2c |
.c |
包含與用戶i2c通信有關的用戶自定義函數。 |
3 |
at24cxx |
.c |
操作AT24C系列存儲器相關的用戶自定義函數。 |
4 |
delay |
.c |
包含用戶自定義延時函數。 |
■ 需要引用的頭文件
#include "delay.h"
#include "at24cxx.h"
#include "uart.h"
■ 需要包含的頭文件路徑
本例需要包含的頭文件路徑如下表:
表5:頭文件包含路徑
序号 |
路徑 |
描述 |
1 |
…\ Source |
uart.h、i2c.h、at24cxx.h和delay.h頭文件在該路徑,所以要包含。 |
2 |
…\Use |
STC8.h頭文件在該路徑,所以要包含。 |
MDK中點擊魔術棒,打開工程配置窗口,按照下圖所示添加頭文件包含路徑。
圖22:添加頭文件包含路徑
4.3.編寫代碼首先,在i2c.c文件中編寫模拟i2c總線通信的基本函數,如下表所示。
表6:模拟i2c總線通信的相關函數彙集
關于每個模拟i2c總線通信的相關函數,下面給出詳細代碼。
程序清單:模拟I2C總線産生啟動信号
/***************************************************************************
* 描 述 : 模拟I2C總線産生啟動信号
* 入 參 : 無
* 返回值 : FALSE 報錯 TRUE 啟動成功
**************************************************************************/
bit I2C_Start(void)
{
SDA = 1; //I2C數據線置高電平
SCL = 1; //I2C時鐘線置高電平
Delay10us();
if(!SDA) return FALSE; //判斷SDA線為低電平則總線忙,退出
SDA = 0; //判斷SDA線為高電平則控制SDA為低,即SDA出現一個下降沿表示啟動I2C
Delay10us();
if(SDA) return FALSE; //判斷SDA線為高電平則總線出錯,退出
SDA = 0; //判斷SDA線為低電平則依然拉低SDA
Delay10us();
return TRUE;
}
程序清單:模拟I2C總線産生停止信号
/***************************************************************************
* 描 述 : 模拟I2C總線産生停止信号
* 入 參 : 無
* 返回值 : 無
**************************************************************************/
void I2C_Stop(void)
{
SDA = 0; //I2C數據線置低電平
Delay10us();
SCL = 1; //I2C時鐘線置高電平
Delay10us();
SDA = 1; //當SCL線為高電平時則SDA出現一個上升沿表示停止I2C
Delay10us();
}
程序清單:模拟I2C總線發送ACK命令
/***************************************************************************
* 描 述 : 模拟I2C總線發送ACK命令
* 入 參 : 無
* 返回值 : 無
**************************************************************************/
void I2C_Ack(void)
{
SCL = 0; //I2C時鐘線置低電平
Delay10us();
SDA = 0; //I2C數據線置低電平
Delay10us();
SCL = 1; //I2C時鐘線置高電平
Delay10us();
SCL = 0; //I2C時鐘線置低電平,完成了在SCL線上産生一個時鐘,此時SDA為低電平
Delay10us();
}
程序清單:模拟I2C總線發送NACK應答命令
/***************************************************************************
* 描 述 : 模拟I2C總線發送NACK應答命令
* 入 參 : 無
* 返回值 : 無
**************************************************************************/
void I2C_NoAck(void)
{
SCL = 0; //I2C時鐘線置低電平
Delay10us();
SDA = 1; //I2C數據線置高電平
Delay10us();
SCL = 1; //I2C時鐘線置高電平
Delay10us();
SCL = 0; //I2C時鐘線置低電平,完成了在SCL線上産生一個時鐘,此時SDA為高電平
Delay10us();
}
程序清單:模拟I2C總線接收ACK命令
/***************************************************************************
* 描 述 : 模拟I2C總線接收ACK命令
* 入 參 : 無
* 返回值 : FALSE 無ACK ; TRUE 有ACK
**************************************************************************/
bit I2C_Check_ACK(void)
{
SCL = 0; //I2C時鐘線置低電平
Delay10us();
SDA = 1; //I2C數據線置高電平
Delay10us();
SCL = 1; //I2C時鐘線置高電平
Delay10us();
if(SDA) //判斷SDA線為高電平
{
SCL = 0; //I2C時鐘線置低電平,完成了在SCL線上産生一個時鐘,此時SDA為高電平
return FALSE;
}
SCL = 0; //I2C時鐘線置低電平,完成了在SCL線上産生一個時鐘,此時SDA為低電平
return TRUE;
}
程序清單:模拟I2C總線發送數據
/***************************************************************************
* 描 述 : 模拟I2C總線發送數據(數據從高位到低位)
* 入 參 : 無
* 返回值 : FALSE 無ACK ; TRUE 有ACK
**************************************************************************/
void I2C_SendByte(uint8 SendByte)
{
uint8 i=8;
while(i--)
{
SCL = 0;
Delay10us();
if(SendByte&0x80)
SDA = 1;
else
SDA = 0;
SendByte<<=1;
Delay10us();
SCL = 1;
Delay10us();
}
SCL = 0;
}
程序清單:模拟I2C總線接收數據
/***************************************************************************
* 描 述 : 模拟I2C總線接收數據
* 入 參 : 無
* 返回值 : I2C總線返回的數據
**************************************************************************/
uint8 I2C_ReceiveByte(void)
{
uint8 i=8;
uint8 ReceiveByte=0;
SDA = 1;
while(i--)
{
ReceiveByte<<=1;
SCL = 0;
Delay10us();
SCL = 1;
Delay10us();
if(SDA)
{
ReceiveByte|=0x01;
}
}
SCL = 0;
return ReceiveByte;
}
然後,在at24cxx.c文件中編寫對E2PROM存儲器的基本操作函數,如下表所示。
序号 |
函數名 |
功能描述 |
1 |
AT24CXX_RcvOneByte |
從E2PROM指定地址讀取單字節數據。 |
2 |
AT24CXX_SendOneByte |
向E2PROM指定地址存入單字節數據。 |
3 |
AT24CXX_SendLenByte |
向E2PROM指定地址存入多字節數據。 |
4 |
AT24CXX_RcvLenByte |
從E2PROM指定地址讀取多字節數據。 |
5 |
AT24CXX_EraseOneByte |
擦除E2PROM指定地址單字節數據。 |
6 |
AT24CXX_EraseAll |
擦除整個E2PROM芯片。 |
關于每個操作外部E2PROM相關用戶函數,下面給出詳細代碼。
程序清單:從指定地址讀取單字節數據函數
/***********************************************************************************
* 描 述 : 從芯片AT24CXX指定地址讀取單字節數據
* 入 參 : 開始讀數據的地址
* 返回值 : 讀到的數據
***********************************************************************************/
uint8 AT24CXX_RcvOneByte(uint16 Addr)
{
uint8 temp=0;
I2C_Start(); //啟動總線
if(E2PROM_TYPE > AT24C16)
{
I2C_SendByte(SLAW); //發送寫命令
I2C_Check_ACK(); //等待應答
I2C_SendByte(Addr>>8); //發送高地址
I2C_Check_ACK(); //等待應答
}else I2C_SendByte(SLAW ((Addr/256)<<1)); //發送器件地址,寫數據
I2C_Check_ACK(); //等待應答
I2C_SendByte(Addr%6); //發送低地址
I2C_Check_ACK(); //等待應答
I2C_Start(); //啟動總線
I2C_SendByte(SLAR); //設置為讀模式
I2C_Check_ACK(); //等待應答
temp=I2C_ReceiveByte(); //讀字節
I2C_Stop(); //結束總線
return temp;
}
程序清單:向指定地址存入單字節數據函數
/***********************************************************************************
* 描 述 : 向芯片AT24CXX指定地址寫入單字節數據
* 入 參 : Addr:寫入的目的起始地址 Data:要寫入的數據
* 返回值 : 無
***********************************************************************************/
void AT24CXX_SendOneByte(uint16 Addr,uint8 Data)
{
I2C_Start(); //啟動總線
if(E2PROM_TYPE > AT24C16)
{
I2C_SendByte(SLAW); //發送寫命令
I2C_Check_ACK(); //等待應答
I2C_SendByte(Addr>>8); //發送高地址
}else I2C_SendByte(SLAW ((Addr/256)<<1)); //發送器件地址,寫數據
I2C_Check_ACK(); //等待應答
I2C_SendByte(Addr%6); //發送低地址
I2C_Check_ACK(); //等待應答
I2C_SendByte(Data); //發送字節數據
I2C_Check_ACK(); //等待應答
I2C_Stop(); //結束總線
delay_ms(10); //該延時保證連續發送字節的穩定性
}
程序清單:向指定地址存入多字節數據函數
/***********************************************************************************
* 描 述 : 向芯片AT24CXX裡面的指定地址開始寫入長度為Len的數據
* 入 參 : Addr:寫入的目的起始地址 Data:要寫入的數據 Len:要寫入數據的長度
* 返回值 : 無
***********************************************************************************/
void AT24CXX_SendLenByte(uint16 Addr,uint8 *Data,uint16 Len)
{
while(Len--)
{
AT24CXX_SendOneByte(Addr,*Data);
Addr ;
Data ;
}
}
程序清單:從指定地址讀取多字節數據函數
/***********************************************************************************
* 描 述 : 從芯片AT24CXX裡面的指定地址開始讀出長度為Len的數據
* 入 參 : Addr:讀出的目的起始地址 Data:讀出的數據 Len:要讀出數據的長度
* 返回值 : 無
***********************************************************************************/
void AT24CXX_RcvLenByte(uint16 Addr,uint8 *Data,uint16 Len)
{
while(Len)
{
*Data = AT24CXX_RcvOneByte(Addr );
Len--;
}
}
程序清單:從指定地址擦除單字節數據函數
/***********************************************************************************
* 描 述 : 擦除芯片AT24CXX指定地址數據
* 入 參 : Addr:要擦除的目的地址
* 返回值 : 無
***********************************************************************************/
void AT24CXX_EraseOneByte(uint16 Addr)
{
AT24CXX_SendOneByte(Addr,0xFF);
}
程序清單:擦除整片數據函數
/************************************************************************************
功能描述:擦除整個芯片AT24CXX(即芯片存儲單元數據都是0xFF)
入口參數:無
返回值:無
備注: 不同的存儲芯片,存儲空間不同,AT24CXX_SIZE數值不同。
*************************************************************************************/
void AT24CXX_EraseAll(void)
{
uint16 i;
// 填充緩沖區
for (i = 0; i < AT24CXX_SIZE; i )
{
AT24CXX_SendOneByte(i,0xFF);
}
}
最後,在主函數中對串口1進行初始化,并通過串口1發送不同的命令實現對單片機片外E2PROM的單字節讀、寫及擦除等操作。
代碼清單:主函數
int main(void)
uint8 Temp;
P3M1 &= 0xFE; P3M0 &= 0xFE; //設置P3.0為準雙向口
P3M1 &= 0xFD; P3M0 |= 0x02; //設置P3.1為推挽輸出
Uart1_Init(); //串口1初始化
EA = 1; //使能總中斷
delay_ms(10); //初始化後延時
while(1)
{
if(WriteFLAG) //寫模式
{
WriteFLAG=0; //寫标志變量清零,發送一次
AT24CXX_SendOneByte(0x0010,0x33); //在地址0x0010位置寫入1個字節數據0x33
SendDataByUart1(0x33); //串口1發送數據0x33表示寫操作完成
}
if(ReadFLAG) //讀模式
{
ReadFLAG=0; //讀标志變量清零,發送一次
Temp=AT24CXX_RcvOneByte(0x0010); //在地址0x0010位置處讀取1個字符
SendDataByUart1(Temp); //串口1發送讀取的字符
}
if(ClearFLAG) //清除模式
{
ClearFLAG=0; //清除标志變量清零,發送一次
AT24CXX_EraseOneByte(0x0010); //擦除地址0x0010位置處數據
SendDataByUart1(0x00); //串口1發送數據0x00表示擦除完成
}
}
圖23:開發闆連接圖
5.外部EEPROM存儲器讀寫多字節實驗(模拟I2C)5.1.編寫代碼首先,在at24cxx.c文件中編寫模拟I2C方式的讀寫字節函數和對E2PROM存儲器的基本操作函數。請參考“實驗2-14-1:外部EEPROM存儲器讀寫單字節實驗(模拟I2C)”部分。
然後,在主函數中對串口1進行初始化,并通過串口1發送不同的命令實現對單片機片外E2PROM的多字節讀、寫及擦除等操作。
代碼清單:主函數
int main(void)
{
P3M1 &= 0xFE; P3M0 &= 0xFE; //設置P3.0為準雙向口
P3M1 &= 0xFD; P3M0 |= 0x02; //設置P3.1為推挽輸出
Uart1_Init(); //串口1初始化
EA = 1; //使能總中斷
delay_ms(10); //初始化後延時
while(1)
{
if(WriteFLAG) //寫模式
{
WriteFLAG=0; //寫标志變量清零,發送一次
AT24CXX_SendLenByte(0x0010,scan,E2PROM_Length); //向E2PROM起始地址0x0010中連續寫入scan數組中的E2PROM_Length個字節數據
SendDataByUart1(0x33); //串口1發送數據0x33表示寫操作完成
}
if(ReadFLAG) //讀模式
{
ReadFLAG=0; //讀标志變量清零,發送一次
AT24CXX_RcvLenByte(0x0010,buffer,E2PROM_Length); //從E2PROM地址0x0010開始連續讀取E2PROM_Length字節數據并存入到buffer數組中
SendStringByUart1_n(buffer,E2PROM_Length); //串口1發送數組buffer中的值(即讀取的多字節數據)
}
if(ClearFLAG) //清除模式
{
ClearFLAG=0; //清除标志變量清零,發送一次
AT24CXX_EraseAll(); //擦除整片,需要一定的耗時
SendDataByUart1(0x00); //串口1發送數據0x00表示擦除完成
}
}
}
圖24:開發闆連接圖
6.外部EEPROM存儲器讀寫單字節實驗(硬件I2C)6.1.工程需要用到的c文件本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。
表8:實驗需要用到的c文件
序号 |
文件名 |
後綴 |
功能描述 |
1 |
uart |
.c |
包含與用戶uart有關的用戶自定義函數 |
2 |
i2c |
.c |
包含與用戶i2c通信有關的用戶自定義函數 |
3 |
at24cxx |
.c |
操作AT24C系列存儲器相關的用戶自定義函數 |
4 |
delay |
.c |
包含用戶自定義延時函數 |
■ 需要引用的頭文件
#include "delay.h"
#include "at24cxx.h"
#include "uart.h"
■ 需要包含的頭文件路徑
本例需要包含的頭文件路徑如下表:
表9:頭文件包含路徑
序号 |
路徑 |
描述 |
1 |
…\ Source |
uart.h、i2c.h、at24cxx.h和delay.h頭文件在該路徑,所以要包含 |
2 |
…\User |
STC8.h頭文件在該路徑,所以要包含 |
MDK中點擊魔術棒,打開工程配置窗口,按照下圖所示添加頭文件包含路徑。
圖25:添加頭文件包含路徑
6.3.編寫代碼首先,在i2c.c文件中對硬件I2C進行初始化,并編寫硬件I2C總線通信的基本函數,如下表所示。
表8:實驗需要用到的c文件
關于每個硬件i2c總線通信的相關函數,下面給出詳細代碼。
程序清單:硬件I2C初始化函數
/***************************************************************************
* 描 述 : I2C初始化函數
* 入 參 : 無
* 返回值 : 無
**************************************************************************/
void I2C_init(void)
{
P_SW2 |= 0x80; //将EAXFR位置1,以訪問在XDATA區域的擴展SFR
P_SW2 &= 0xCF; //将I2C_S[1:0]置10,以選擇I2C硬件功能腳為P7.6 P7.7
P_SW2 |= 0x20; //将I2C_S[1:0]置10,以選擇I2C硬件功能腳為P7.6 P7.7
I2CCFG=0xE0; //使能I2C主機模式,I2C總線速度為等待65個時鐘數
I2CMSST=0x00; //清零I2C主機狀态寄存器各标志位
// P_SW2 &= 0x7F; //将EAXFR位置0,恢複訪問XRAM
}
程序清單:硬件I2C總線等待中斷标志位函數
/***************************************************************************
* 描 述 : I2C總線等待中斷标志位
* 入 參 : 無
* 返回值 : 無
**************************************************************************/
void I2C_Wait(void)
{
while(!(I2CMSST&0x40)); //等待I2C主機狀态寄存器的中斷标志位置1
I2CMSST &= 0xBF; //主機模式中斷标志位軟件清零
}
程序清單:硬件I2C總線産生啟動信号函數
/***************************************************************************
* 描 述 : I2C總線産生啟動信号
* 入 參 : 無
* 返回值 : 無
**************************************************************************/
void I2C_Start(void)
{
I2CMSCR=0x01; //關閉主機模式中斷并發送主機命令:0001(起始命令)
I2C_Wait(); //等待中斷标志位置1
}
程序清單:硬件I2C總線産生停止信号函數
/***************************************************************************
* 描 述 : I2C總線産生停止信号
* 入 參 : 無
* 返回值 : 無
**************************************************************************/
void I2C_Stop(void)
{
I2CMSCR=0x06; //關閉主機模式中斷并發送主機命令:0110(發送STOP命令)
I2C_Wait(); //等待中斷标志位置1
}
程序清單:硬件I2C總線發送ACK命令函數
/***************************************************************************
* 描 述 : I2C總線發送ACK命令
* 入 參 : 無
* 返回值 : 無
**************************************************************************/
void I2C_SendACK(void)
{
I2CMSST=0x00; //設置ACK信号
I2CMSCR=0x05; //關閉主機模式中斷并發送主機命令:0101(發送ACK命令)
I2C_Wait(); //等待中斷标志位置1
}
程序清單:硬件I2C總線發送NACK應答命令函數
/***************************************************************************
* 描 述 : I2C總線發送NACK應答命令
* 入 參 : 無
* 返回值 : 無
**************************************************************************/
void I2C_SendNAK(void)
{
I2CMSST=0x01; //設置NACK信号
I2CMSCR=0x05; //關閉主機模式中斷并發送主機命令:0101(發送ACK命令)
I2C_Wait(); //等待中斷标志位置1
}
程序清單:硬件I2C總線接收ACK命令函數
/***************************************************************************
* 描 述 : I2C總線接收ACK命令
* 入 參 : 無
* 返回值 : 無
**************************************************************************/
void I2C_RecvACK(void)
{
I2CMSCR=0x03; //關閉主機模式中斷并發送主機命令:0011(接收ACK命令)
I2C_Wait(); //等待中斷标志位置1
}
程序清單:硬件I2C總線發送數據函數
/***************************************************************************
* 描 述 : I2C總線發送數據
* 入 參 : 無
* 返回值 : 無
**************************************************************************/
void I2C_SendData(uint8 dat)
{
I2CTXD=dat; //寫數據到數據緩沖區
I2CMSCR=0x02; //關閉主機模式中斷并發送主機命令:0010(發送數據命令)
I2C_Wait(); //等待中斷标志位置1
}
程序清單:硬件I2C總線接收數據函數
/***************************************************************************
* 描 述 : I2C總線接收數據
* 入 參 : 無
* 返回值 : I2C總線返回的數據
**************************************************************************/
uint8 I2C_RecvData(void)
{
I2CMSCR=0x04; //關閉主機模式中斷并發送主機命令:0100(接收數據命令)
I2C_Wait(); //等待中斷标志位置1
return I2CRXD;
}
然後,在at24cxx.c文件中編寫對E2PROM存儲器的基本操作函數,如下表所示。
表11:E2PROM相關用戶函數彙集
關于每個操作外部E2PROM相關用戶函數,下面給出詳細代碼。
程序清單:從指定地址讀取單字節數據函數
/***********************************************************************************
* 描 述 : 在芯片AT24CXX指定地址讀出一個數據
* 入 參 : 開始讀數據的地址
* 返回值 : 讀到的數據
***********************************************************************************/
uint8 AT24CXX_RcvOneByte(uint16 Addr)
{
uint8 temp=0;
I2C_Start(); //啟動總線
if(E2PROM_TYPE > AT24C16)
{
I2C_SendData(SLAW); //發送寫命令
I2C_RecvACK(); //等待應答
I2C_SendData(Addr>>8); //發送高地址
I2C_RecvACK(); //等待應答
}else I2C_SendData(SLAW ((Addr/256)<<1)); //發送器件地址,寫數據
I2C_RecvACK(); //等待應答
I2C_SendData(Addr%6); //發送低地址
I2C_RecvACK(); //等待應答
I2C_Start(); //啟動總線
I2C_SendData(SLAR); //設置為讀模式
I2C_RecvACK(); //等待應答
temp=I2C_RecvData(); //讀字節
I2C_Stop(); //結束總線
return temp;
}
程序清單:向指定地址存入單字節數據函數
/***********************************************************************************
* 描 述 : 在芯片AT24CXX指定地址寫入一個數據
* 入 參 : Addr:寫入的目的起始地址 Data:要寫入的數據
* 返回值 : 無
***********************************************************************************/
void AT24CXX_SendOneByte(uint16 Addr,uint8 Data)
{
I2C_Start(); //啟動總線
if(E2PROM_TYPE > AT24C16)
{
I2C_SendData(SLAW); //發送寫命令
I2C_RecvACK(); //等待應答
I2C_SendData(Addr>>8); //發送高地址
I2C_RecvACK(); //等待應答
}else I2C_SendData(SLAW ((Addr/256)<<1)); //發送器件地址,寫數據
I2C_RecvACK(); //等待應答
I2C_SendData(Addr%6); //發送低地址
I2C_RecvACK(); //等待應答
I2C_SendData(Data); //發送字節數據
I2C_RecvACK(); //等待應答
I2C_Stop(); //結束總線
delay_ms(10); //該延時保證連續發送字節的穩定性
}
程序清單:向指定地址存入多字節數據函數
/***********************************************************************************
* 描 述 : 向芯片AT24CXX裡面的指定地址開始寫入長度為Len的數據
* 入 參 : Addr:寫入的目的起始地址 Data:要寫入的數據 Len:要寫入數據的長度
* 返回值 : 無
***********************************************************************************/
void AT24CXX_SendLenByte(uint16 Addr,uint8 *Data,uint16 Len)
{
while(Len--)
{
AT24CXX_SendOneByte(Addr,*Data);
Addr ;
Data ;
}
}
程序清單:從指定地址讀取多字節數據函數
/***********************************************************************************
* 描 述 : 從芯片AT24CXX裡面的指定地址開始讀出長度為Len的數據
* 入 參 : Addr:讀出的目的起始地址 Data:讀出的數據 Len:要讀出數據的長度
* 返回值 : 無
***********************************************************************************/
void AT24CXX_RcvLenByte(uint16 Addr,uint8 *Data,uint16 Len)
{
while(Len)
{
*Data = AT24CXX_RcvOneByte(Addr );
Len--;
}
}
程序清單:從指定地址擦除單字節數據函數
/***********************************************************************************
* 描 述 : 擦除芯片AT24CXX指定地址數據
* 入 參 : Addr:要擦除的目的地址
* 返回值 : 無
***********************************************************************************/
void AT24CXX_EraseOneByte(uint16 Addr)
{
AT24CXX_SendOneByte(Addr,0xFF);
}
程序清單:擦除整片數據函數
/***********************************************************************************
功能描述:擦除整個芯片AT24CXX(即芯片存儲單元數據都是0xFF)
入口參數:無
返回值:無
備注: 不同的存儲芯片,存儲空間不同,AT24CXX_SIZE數值不同。
************************************************************************************/
void AT24CXX_EraseAll(void)
{
uint16 i;
// 填充緩沖區
for (i = 0; i < AT24CXX_SIZE; i )
{
AT24CXX_SendOneByte(i,0xFF);
}
}
最後,在主函數中對串口1和I2C進行初始化,并通過串口1發送不同的命令實現對單片機片外E2PROM的單字節讀、寫及擦除等操作。
代碼清單:主函數
int main(void)
{
uint8 Temp;
P3M1 &= 0xFE; P3M0 &= 0xFE; //設置P3.0為準雙向口
P3M1 &= 0xFD; P3M0 |= 0x02; //設置P3.1為推挽輸出
I2C_init(); //IIC初始化
Uart1_Init(); //串口1初始化
EA = 1; //使能總中斷
delay_ms(10); //初始化後延時
while(1)
{
if(WriteFLAG) //寫模式
{
WriteFLAG=0; //寫标志變量清零,發送一次
AT24CXX_SendOneByte(0x0010,0x33); //在地址0x0010位置寫入1個字節數據0x33
SendDataByUart1(0x33); //串口1發送數據0x33表示寫操作完成
}
if(ReadFLAG) //讀模式
{
ReadFLAG=0; //讀标志變量清零,發送一次
Temp=AT24CXX_RcvOneByte(0x0010); //在地址0x0010位置處讀取1個字符
SendDataByUart1(Temp); //串口1發送讀取的字符
}
if(ClearFLAG) //清除模式
{
ClearFLAG=0; //清除标志變量清零,發送一次
AT24CXX_EraseOneByte(0x0010); //擦除地址0x0010位置處數據
SendDataByUart1(0x00); //串口1發送數據0x00表示擦除完成
}
}
}
圖26:開發闆連接圖
7.外部EEPROM存儲器讀寫多字節實驗(硬件I2C)7.1.編寫代碼首先,在at24cxx.c文件中編寫硬件I2C方式的讀寫字節函數和對E2PROM存儲器的基本操作函數。請參考“實驗2-14-3:外部EEPROM存儲器讀寫單字節實驗(硬件I2C)”部分。
然後,在主函數中對串口1和I2C進行初始化,并通過串口1發送不同的命令實現對單片機片外EEPROM的多字節讀、寫及擦除等操作。
代碼清單:主函數
int main(void)
P3M1 &= 0xFE; P3M0 &= 0xFE; //設置P3.0為準雙向口
P3M1 &= 0xFD; P3M0 |= 0x02; //設置P3.1為推挽輸出
I2C_init(); //IIC初始化
Uart1_Init(); //串口1初始化
EA = 1; //使能總中斷
delay_ms(10); //初始化後延時
while(1)
{
if(WriteFLAG) //寫模式
{
WriteFLAG=0; //寫标志變量清零,發送一次
AT24CXX_SendLenByte(0x0010,scan,E2PROM_Length); //向E2PROM起始地址0x0010中連續寫入scan數組中的E2PROM_Length個字節數據
SendDataByUart1(0x33); //串口1發送數據0x33表示寫操作完成
}
if(ReadFLAG) //讀模式
{
ReadFLAG=0; //讀标志變量清零,發送一次
AT24CXX_RcvLenByte(0x0010,buffer,E2PROM_Length); //從E2PROM地址0x0010開始連續讀取E2PROM_Length字節數據并存入到buffer數組中
SendStringByUart1_n(buffer,E2PROM_Length); //串口1發送數組buffer中的值(即讀取的多字節數據)
}
if(ClearFLAG) //清除模式
{
ClearFLAG=0; //清除标志變量清零,發送一次
AT24CXX_EraseAll(); //擦除整片,需要一定的耗時
SendDataByUart1(0x00); //串口1發送數據0x00表示擦除完成
}
}
圖27:開發闆連接圖
總結以上是今天要講的内容,希望對大家有幫助,如果有啥不明白的,歡迎讨論哦!
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!