寫在前面
與之前的筆記一樣,爲了讓自己更好的總結,也爲了和大家一起交流學習,寫下了這些學習總結。如果能幫助到學習中的各位,我不勝歡喜。如果筆記中有錯誤,歡迎大家指出。歡迎大傢俬聊一起學習進步噢 😃
學習過程中,個人認爲《手把手教你51單片機》真的是一本很好的書,有很詳細的解析和代碼,推薦給大家。
原理圖
I2C
如果不想看一大堆的知識點,可以直接翻到後面的模塊化代碼。
知識點
在每次我們學習新知識之前,必須要先了解這個東西是什麼,之前對於I2C和Uart都是一個模模糊糊的認識,沒有一個比較明確的概念,查詢資料後總結如下:
微型計算機、單片機系統大都採用總線結構,這種結構就是採用一組公共的信號線來作爲微型計算機各部件之間的通信線,這個公共信號線就被稱作爲總線,單片機常用總線就是並行總線和串行總線,其一個區別就是串行總線就是一位一位發送數據,而並行總線是一次性發送多位數據。
問題就緊接着來了:我們怎麼能夠保證我的信號能準備送到我需要的那一個部件上呢?
這樣就有了不同的串行總線,其實就是不同種類的使用規則,來實現我們的目的。
一般我們使用的串線總線有四種:UART,1-wire,I2C,SPI總線
Uart:異步通信(一條數據輸出線,一條數據輸出線)
1-wire:單總線
I2C:同步串行2線方式進行通信(一條時鐘線,一條數據線)
SPI:同步串行3線方式進行通信(一條時鐘線,一條數據輸入線,一條數據輸出線)
我們這裏學習的是I2C總線,首先一定要記住兩條鍾線的名稱!!
SDA(serial data I/O) 數據線
SCL(serial clock) 時鐘線
這兩個名字記住了,我們之後學才能看得懂
在清翔單片機裏面學到,I2C總線上可以掛多個器件,每一個器件又有唯一的地址,於是我們就可以用這個地址來找到我們需要的部件。而數據之間的方式採用的是,主機主動聯繫從機,從機被動的返回數據。
(注:多機系統中,可能會有幾個主機要同時要求從機返回信號,而總線數據線只有一條,這時候就需要用到總線仲裁來確定哪一個主機控制從機)
I2C總線通過上拉電阻連接正電源。當總線空閒的時候,兩根線均爲高電平。連到總線的任一器件輸出的都是低電平,都將使總線的信號變低,各器件的SDA和SCL都是線“與”的關係,即SDA = 1 才能夠釋放總線,一旦SDA = 0,那麼總線被佔,其他器件發1發0都會失效。
採用了漏極接線模式
傳輸協議
知道了知識點之後,我們就需要來了解如何傳輸信號了。
SCL爲高電平期間,SDA數據保持穩定,即穩定傳輸一個數據
SCL爲低電平期間,SDA纔可以變,即發送數據
起始和終止信號
SCL爲高電平期間,SDA產生 高–>低 的信號表示起始信號
SCL爲高電平期間,SDA產生 低–>高 的信號表示終止信號
(注:SDA、SCL接在I/O口上,需要我們自行調高調低,所以起始和終止信號都需要我們自己控制產生,當起始信號產生之後,總線就被佔用,當終止信號產生後,總線就處於空閒狀態)
傳送和應答
傳輸按照一個字節一個字節傳輸,需要保證8位長度,並且每一個字節後面必須要跟一個應答位(所以一幀信號有9位)
模塊化代碼
但是80C51芯片的單片機上是沒有I2C接口的,所以我們要採用軟件來模仿I2C通信協議。
I2C的最底層操作有五個,起始信號,終止信號,字節寫,字節讀+應答,字節讀+非應答。字節讀+應答就是告訴部件,我還想繼續讀,你繼續發數據。字節讀+非應答就是告訴部件,我不想讀了,你不用發了數據了。
//pbdata.h
#ifndef __PBDATA_H__
#define __PBDATA_H__
#define uchar unsigned char
#define uint unsigned int
#include <reg52.h>
#include "I2C.h"
/*I2C*/
sbit I2C_SCL = P2^1;
sbit I2C_SDA = P2^0;
#endif
//I2C.h
#ifndef __I2C_H__
#define __I2C_H__
void I2CStart();
void I2CStop();
bit I2CWrite(unsigned char dat);
unsigned char I2CReadNAK();//讀+非應答
unsigned char I2CReadACK();//讀+應答
#endif
//I2C.c
#include <intrins.h>
#include "pbdata.h"
#define I2Delay(){_nop_;_nop_;_nop_;_nop_;}
/* 產生總線起始信號 */
void I2CStart()
{
I2C_SDA = 1; //首先確保SDA、SCL 都是高電平
I2C_SCL = 1;
I2CDelay();
I2C_SDA = 0; //先拉低SDA
I2CDelay();
I2C_SCL = 0; //再拉低SCL
}
/* 產生總線停止信號 */
void I2CStop()
{
I2C_SCL = 0; //首先確保SDA、SCL 都是低電平
I2C_SDA = 0;
I2CDelay();
I2C_SCL = 1; //先拉高SCL
I2CDelay();
I2C_SDA = 1; //再拉高SDA
I2CDelay();
}
/* I2C 總線寫操作,dat-待寫入字節,返回值-從機應答位的值 */
bit I2CWrite(unsigned char dat)
{
bit ack; //用於暫存應答位的值
unsigned char mask; //用於探測字節內某一位值的掩碼變量
for (mask=0x80; mask!=0; mask>>=1) //從高位到低位依次進行
{
if ((mask&dat) == 0) //該位的值輸出到SDA 上
I2C_SDA = 0;
else
I2C_SDA = 1;
I2CDelay();
I2C_SCL = 1; //拉高SCL
I2CDelay();
I2C_SCL = 0; //再拉低SCL,完成一個位週期
}
I2C_SDA = 1; //8 位數據發送完後,主機釋放SDA,以檢測從機應答
I2CDelay();
I2C_SCL = 1; //拉高SCL
ack = I2C_SDA; //讀取此時的SDA 值,即爲從機的應答值
I2CDelay();
I2C_SCL = 0; //再拉低SCL 完成應答位,並保持住總線
return (~ack); //應答值取反以符合通常的邏輯:
//0=不存在或忙或寫入失敗,1=存在且空閒或寫入成功
}
/* I2C 總線讀操作,併發送非應答信號,返回值-讀到的字節 */
unsigned char I2CReadNAK()
{
unsigned char mask;
unsigned char dat;
I2C_SDA = 1; //首先確保主機釋放SDA
for (mask=0x80; mask!=0; mask>>=1) //從高位到低位依次進行
{
I2CDelay();
I2C_SCL = 1; //拉高SCL
if(I2C_SDA == 0) //讀取SDA 的值
dat &= ~mask; //爲0 時,dat 中對應位清零
else
dat |= mask; //爲1 時,dat 中對應位置1
I2CDelay();
I2C_SCL = 0; //再拉低SCL,以使從機發送出下一位
}
I2C_SDA = 1; //8位數據發送完後,拉高SDA,發送非應答信號
I2CDelay();
I2C_SCL = 1; //拉高SCL
I2CDelay();
I2C_SCL = 0; //再拉低SCL 完成非應答位,並保持住總線
return dat;
}
/* I2C 總線讀操作,併發送應答信號,返回值-讀到的字節 */
unsigned char I2CReadACK()
{
unsigned char mask;
unsigned char dat;
I2C_SDA = 1; //首先確保主機釋放SDA
for (mask=0x80; mask!=0; mask>>=1) //從高位到低位依次進行
{
I2CDelay();
I2C_SCL = 1; //拉高SCL
if(I2C_SDA == 0) //讀取SDA 的值
dat &= ~mask; //爲0 時,dat 中對應位清零
else
dat |= mask; //爲1 時,dat 中對應位置1
I2CDelay();
I2C_SCL = 0; //再拉低SCL,以使從機發送出下一位
}
I2C_SDA = 0; //8 位數據發送完後,拉低SDA,發送應答信號
I2CDelay();
I2C_SCL = 1; //拉高SCL
I2CDelay();
I2C_SCL = 0; //再拉低SCL 完成應答位,並保持住總線
return dat;
}
尋址模式
E2Prom
如果不想看一大堆的知識點,可以直接翻到後面的模塊化代碼。
知識點
我們如果用電腦來推及到單片機,其實E2Prom就相當於電腦的硬盤,它可以在單片機掉電之後仍然保存數據,達到存儲數據的目的。特性就是掉電不丟失。
E2Prom和I2C兩個其實是需要合體一起使用的,但是E2Prom != I2C,I2C是通信協議,而E2Prom是使用I2C通信協議來使用的一個硬件。
使用的芯片
要重點注意的是,AT24C系列的E2Prom芯片地址的固定部分是1010,然後A2.A1.A0引腳接高低電平後得到確定的3位編碼,形成的7位編碼就是地址碼
寫數據流程
讀數據的過程
注:在讀數據和寫數據的過程中,E2Prom的地址會自動+1。
代碼
//E2Prom.h
#ifndef __E2Prom_H__
#define __E2Prom_H__
void E2Read(unsigned char *buf, unsigned char addr, unsigned char len);
void E2Write(unsigned char *buf, unsigned char addr, unsigned char len);
#endif
#include "pbdata.h"
//調用I2C.c中的函數
extern void I2CStart();
extern void I2CStop();
extern unsigned char I2CReadACK();
extern unsigned char I2CReadNAK();
extern bit I2CWrite(unsigned char dat);
/* E2 讀取函數,buf-數據接收指針,addr-E2 中的起始地址,len-讀取長度 */
void E2Read(unsigned char *buf, unsigned char addr, unsigned char len)
{
do { //用尋址操作查詢當前是否可進行讀寫操作
I2CStart(); //給一個起始信號
if (I2CWrite(0x50<<1))//0x50=0101 //應答則跳出循環,非應答則進行下一次查詢
{ //0x50<<1即1010,也就是24C02的前面固定位
break;
}
I2CStop(); //終止信號
} while(1);
I2CWrite(addr); //寫入起始地址,因爲是從循環出來,I2CStart已經調用過一次
I2CStart(); //發送重複啓動信號
I2CWrite((0x50<<1)|0x01); //尋址器件,後續爲讀操作
while (len > 1) //連續讀取len-1 個字節
{
*buf++ = I2CReadACK(); //最後字節之前爲讀取操作+應答
len--;
}
*buf = I2CReadNAK(); //最後一個字節爲讀取操作+非應答
I2CStop();
}
/* E2 寫入函數,buf-源數據指針,addr-E2 中的起始地址,len-寫入長度 */
void E2Write(unsigned char *buf, unsigned char addr, unsigned char len)
{
while (len > 0)
{
//等待上次寫入操作完成
do { //用尋址操作查詢當前是否可進行讀寫操作
I2CStart();
if (I2CWrite(0x50<<1)) //應答則跳出循環,非應答則進行下一次查詢
{
break;
}
I2CStop();
} while(1);
//按頁寫模式連續寫入字節
I2CWrite(addr); //寫入起始地址
while (len > 0)
{
I2CWrite(*buf++); //寫入一個字節數據
len--; //待寫入長度計數遞減
addr++; //E2 地址遞增
if ((addr&0x07) == 0) //檢查地址是否到達頁邊界,24C02 每頁8 字節,
{ //所以檢測低3 位是否爲零即可
break; //到達頁邊界時,跳出循環,結束本次寫操作
}
}
I2CStop();
}
}