一、 儲存器介紹
儲存器分類圖
1. RAM
這類儲存器中的資料都是掉電即失的,例如計算機中的記憶體就是DRAM,但它們資料讀寫速度都是要比ROM要快得多的。
- SRAM:本質是電路,使用電路構成的觸發器來儲存資料(如JK觸發器),因此這種儲存器讀寫資料是最快的,而它們的成本也比較高,一般用作計算機的高速儲存器,暫存器等
- DRAM:使用電容來儲存資料,因為電容存在漏電現象,因此需要每隔一段時間進行掃描重新充電。它們一般用來構成計算機的記憶體,手機的快閃記憶體等。
2. ROM
這類儲存器中的資料有著掉電不丟失的特性,但它們讀寫資料的速度遠小於RAM
- Mask ROM:僅由電路構成,只讀的ROM,就是隻可讀不可寫
- PROM:可以寫入資料,但只能寫入一次資料
- EPROM:可讀可寫
- ...
二、AT24C02簡介
AT24C02
是一種可以實現掉電不丟失的儲存器,可用於儲存微控制器執行時想要永久儲存的資料資訊
- 儲存介質:E2PROM
- 通訊介面:I2C匯流排
- 容量:256位元組
電路連線
三、I2C匯流排和AT24C02資料幀
I2C匯流排(Inter IC BUS)是由Philips
公司開發的一種通用資料匯流排(通訊協議)
- 兩根通訊線:SCL(Serial Clock)、SDA(Serial Data)
- 同步、半雙工,帶資料應答
- 通用的I2C匯流排,可以使各種裝置的通訊標準統一,對於廠家來說,使用成熟的方案可以縮短晶片設計週期、提高穩定性,對於應用者來說,使用通用的通訊協議可以避免學習各種各樣的自定義協議,降低了學習和應用的難度
1. 電路規範
- 所有I2C裝置的SCL連在一起,SDA連在一起
- 裝置的SCL和SDA均要配置成開漏輸出模式
- SCL和SDA各新增一個上拉電阻,阻值一般為4.7KΩ左右
- 開漏輸出和上拉電阻的共同作用實現了“線與”的功能,此設計主要是為了解決多機通訊互相干擾的問題
2. I2C的時序結構
我們可以將通過I2C協議實現主機與從機通訊的過程分為以下六個部分,這六個部分可以像拼圖一樣拼湊出所有的通訊過程:
2.1 傳送起始資訊
起始條件:SCL
高電平期間,SDA
從高電平切換到低電平,如下圖所示:
同時為了使這塊拼圖可以和其他的部分連線上,我們在傳送起始資訊(
start
)之後,也將SCL拉低。
2.2 傳送終止資訊
終止條件:SCL
高電平期間,SDA
從低電平切換到高電平
同理,為了和其他拼圖拼接上,我們得到的SCL原來是低電平的(這是因為後面的4塊拼圖結束時SCL都是低電平,因此來到終止資訊時也是低電平),我們先拉高SCL,然後拉高SDA即可傳送終止訊號了。
2.3 傳送一個byte的資訊
傳送一個byte
(位元組)資訊可以分解為迴圈傳送8個bit 的資訊,因此我們只需要知道如何傳送一個bit的資訊即可。
傳送一個bit
資訊的操作:(1)SCL低電平期間,主機將資料位依次放到SDA線上(高位在前),(2)然後拉高SCL,從機將在SCL高電平期間讀取資料位,所以SCL高電平期間SDA不允許有資料變化
即如果我們希望傳送0,則:
- 在SCL在低電平時,將SDA的電平拉低(置零)
- 再將SCL的電平拉高,提醒從機讀取資訊
- 過一段時間(等待從機把資訊讀取完成)後再次將SCL拉低
具體的過程如圖所示:
例如在B7時間內,如果SDA為低電平,則傳送的資料為0,如果為高電平則傳送的資料為1
2.4 接收一個byte的資訊
和傳送資訊類似,我們只需要知道如何接收一個bit的資訊,然後只需要迴圈進行8次即可接收一個位元組的資訊了。
接收一個bit
資訊的操作:(1)SCL低電平期間,從機將資料位依次放到SDA線上(高位在前),(2)然後拉高SCL(相當於通知從機主機正在讀取這個bit的資料),主機將在SCL高電平期間讀取資料位,所以SCL高電平期間SDA不允許有資料變化。(主機在接收之前,需要釋放SDA)
具體的過程如圖所示:
這個過程我個人的理解是:
- 釋放SDA(拉高電平),將寫入資料的主動權交給從機
- 從機寫入下一位bit到SDA中
- 主機拉高SCL,提示從機我正在讀取這個bit的資料,你先不要變化
- 主機讀取完資料,將SCL拉低,實際上這個過程是在告訴從機我已經讀完這個bit了,你給我下一個bit的資料吧,然後如果還未滿8位則轉到第2步繼續接受資料,如果滿了一個位元組則接收結束
對比傳送資料和接收資料可以發現,這兩個過程非常的相似,只不過(1)傳送資訊時寫入資訊的一方是主機,而接收資訊時寫入資料的是從機,(2)傳送資訊時拉高和拉低SCL電平是通知從機讀取資訊,而在接收資訊時則是通知從機主機當前正在讀取資訊。
可以發現單從開始時的SCL
和SDA
的狀態是無法區分主機是想傳送還是接收資訊的,其實接收資料還是傳送資料是由後面的時序過程(資料幀)所決定的,不需要起始狀態進行區分。
2.5 傳送應答
在接收完一個位元組之後,主機在下一個時鐘傳送一位資料,資料0表示應答,資料1表示非應答
其實傳送應答的操作就是傳送一個bit的操作而已:
2.6 接收應答
在傳送完一個位元組之後,主機在下一個時鐘接收一位資料,判斷從機是否應答,資料0表示應答,資料1表示非應答(主機在接收之前,需要釋放SDA)
和傳送應答類似,接收應答的操作也就是接收一個bit資料的操作。
3. I2C資料幀
3.1 傳送一幀資料
其過程是(過程中的接收應答省略不寫了):
- 傳送一個開始訊號
- 傳送從機地址(加上寫入標記,最後一位為0)
- 迴圈傳送位元組資料
- 傳送完後傳送結束訊號
3.2 接收一幀資料
過程和傳送一幀資料的過程非常類似,只是中間的傳送位元組資料變成了接收位元組資料,此外還有地址部分的最後一位為1,代表讀取資料。
3.3 先傳送後接收資料
4. AT24C02資料幀
AT24C02是使用I2C通訊協議進行通訊的,I2C資料幀相當於是一輛卡車,而AT24C02資料幀則是在原來卡車的基礎上裝上了特定貨物的卡車。
4.1 位元組寫
在WORD ADDRESS
處寫入資料DATA
4.2 隨機讀
讀出在WORD ADDRESS
處的資料DATA
AT24C02的固定地址為1010,可配置地址本開發板上為000,所以
SLAVE ADDRESS+W
為0xA0
,SLAVE ADDRESS+R
為0xA1
四、程式碼實現
1. I2C模組
實現I2C模組,即實現上面介紹的6塊拼圖。
首先定義出SCL
和SDA
連線的引腳:
sbit I2C_SCL = P2 ^ 1;
sbit I2C_SDA = P2 ^ 0;
1.1 傳送起始資訊和傳送終止資訊
void I2C_Start() {
I2C_SDA = 1;
I2C_SCL = 1;
// 在SCL為高電平拉低SDA
I2C_SDA = 0;
I2C_SCL = 0;
}
void I2C_Stop() {
I2C_SDA = 0;
// 在SCL為高電平時拉高SDA
I2C_SCL = 1;
I2C_SDA = 1;
}
1.2 傳送位元組資訊和接收位元組資訊
void I2C_SendByte(unsigned char byte) {
unsigned char i = 0;
for (i = 0; i < 8; i++) {
// 先將一個bit的資料寫入到SDA中
I2C_SDA = byte & (0x80 >> i);
// SCL先拉高,後拉低,通知從機接收資料
I2C_SCL = 1;
I2C_SCL = 0;
}
}
unsigned char I2C_ReceiveByte() {
unsigned char byte = 0x00;
unsigned char i;
// 先釋放SDA(將主動權交給從機)
I2C_SDA = 1;
for (i = 0; i < 8; i++) {
// 通知從機主機正在讀取資料
I2C_SCL = 1;
// 如果是1則置一,否則預設為0
if (I2C_SDA) {
byte |= (0x80 >> i);
}
// 通知從機這個bit已經讀取完畢,可以傳送下一個bit
I2C_SCL = 0;
}
return byte;
}
1.3 傳送應答和接收應答
void I2C_SendAck(unsigned char AckBit) {
I2C_SDA = AckBit;
// SCL先拉高,後拉低,通知從機接收資料
I2C_SCL = 1;
I2C_SCL = 0;
}
unsigned char I2C_ReceiveAck() {
unsigned char AckBit;
// 先釋放SDA(將主動權交給從機)
I2C_SDA = 1;
// 通知從機主機正在讀取資料
I2C_SCL = 1;
AckBit = I2C_SDA;
// 通知從機這個bit已經讀取完畢
I2C_SCL = 0;
return AckBit;
}
2. AT24C02模組
這個模組依賴於I2C模組,即利用I2C傳送和接收資料。
首先定義從機地址:
// 最後一位為0代表寫,即傳送資料,為1代表讀,即接受資料
#define AT24C02_ADDRESS 0xA0
2.1 位元組寫
前面已經提到,AT24C02可以儲存256個位元組的資料,因此我們的資料可以任意0~255號地址的空間進行儲存:
void AT24C02_WriteByte(unsigned char WordAddress, unsigned char Byte) {
I2C_Start();
// 從機地址
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
// 字地址
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
// 傳送真正的資料
I2C_SendByte(Byte);
I2C_ReceiveAck();
I2C_Stop();
}
2.2 隨機讀
讀取WordAddress
地址中儲存的位元組資訊:
unsigned char AT24C02_ReadByte(unsigned char WordAddress) {
unsigned char Byte;
I2C_Start();
// 從機地址
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
// 字地址
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_Start();
// 從機地址,read模式
I2C_SendByte(AT24C02_ADDRESS|0x01);
I2C_ReceiveAck();
// 讀取資訊
Byte = I2C_ReceiveByte();
I2C_SendAck(1);
I2C_Stop();
return Byte;
}
3. 使用AT24C02進行資料儲存
我們使用LCD_1602
進行顯示,第二行顯示num數字,當我們單擊按鈕時:
- 點選k1,
num--
- 點選k2,
num++
- 點選k3,將
num
的資料儲存到AT24C02
中地址為1的空間中
void main() {
unsigned char key, num;
unsigned char storageData;
LCD_Init();
LCD_ShowString(1, 1, "Hello world");
LCD_ShowString(2, 1, "num:");
storageData = AT24C02_ReadByte(1);
num = storageData;
LCD_ShowNum(2, 5, storageData, 3);
Timer0_Init();
while (1) {
key = Key();
if (key) {
switch (key) {
case 1:
num--;
break;
case 2:
num++;
break;
case 3:
AT24C02_WriteByte(1, num);
break;
}
}
LCD_ShowNum(2, 5, num, 3);
}
}
void Timer0_Routine() interrupt 1 {
static unsigned int T0Count..;
TL0 = 0x18;//設定定時初值
TH0 = 0xFC;//設定定時初值
T0Count++;
if (T0Count >= 20) {
T0Count = 0;
Key_Loop();//每20ms呼叫一次按鍵驅動函式
}
}
這樣我們每次重啟時就可以看到上次儲存的數字了。