1. I2C介紹
I2C ( Inter—Integrated Circuit)總線是由PHILIPS公司開發(fā)的兩線式串行總線,用于連接微控制器及其外圍設(shè)備。是微電子通信控制領(lǐng)域廣泛采用的一種總線標準。它是同步通信的一種特殊形式,具有接口線少,控制方式簡單,器件封裝形式小,通信速率較高等優(yōu)點。I2C總線只有兩根雙向信號線。一根是數(shù)據(jù)線SDA,另一根是時鐘線SCL。 由于其管腳少,硬件實現(xiàn)簡單,可擴展性強等特點,因此被廣泛的使用在各大集成芯片內(nèi)。下面從I2C的物理層與協(xié)議層來了解 I2C。
1.1 I2C物理層
I2C通信設(shè)備常用的連接方式如下圖所示:
它的物理層有如下特點:
(1)它是一個支持多設(shè)備的總線。“總線”指多個設(shè)備共用的信號線。在一個I2C通訊總線中,可連接多個I2C通訊設(shè)備,支持多個通訊主機及多個通訊從機。
(2)一個I2C總線只使用兩條總線線路,一條雙向串行數(shù)據(jù)線(SDA),一條串行時鐘線(SCL)。數(shù)據(jù)線即用來表示數(shù)據(jù),時鐘線用于數(shù)據(jù)收發(fā)同步。
(3)每個連接到總線的設(shè)備都有一個獨立的地址,主機可以利用這個地址進行不同設(shè)備之間的訪問。
(4)總線通過上拉電阻接到電源。當I2C設(shè)備空閑時,會輸出高阻態(tài),而當所有設(shè)備都空閑,都輸出高阻態(tài)時,由上拉電阻把總線拉成高電平。
(5)多個主機同時使用總線時,為了防止數(shù)據(jù)沖突,會利用仲裁方式?jīng)Q定由哪個設(shè)備占用總線。
(6)具有三種傳輸模式:標準模式傳輸速率為100kbit/s,快速模式為400kbit/s,高速模式下可達3.4Mbit/s,但目前大多 I2C設(shè)備尚不支持高速模式。
(7)連接到相同總線的IC數(shù)量受到總線的最大電容400pF限制。
高阻態(tài)這是一個數(shù)字電路里常見的術(shù)語,指的是電路的一種輸出狀態(tài),既不是高電平也不是低電平, 如果高阻態(tài)再輸入下一級電路的話,對下級電路無任何影響,和沒接一樣,如果用萬用表測的話有可能是高電平也有可能是低電平,隨它后面接的東西定。
MCU(Micro Control Unit),中文為微控制單元,又稱單片微型計算機(Single Chip Microcomputer)或者單片機 ,是指將計算機的CPU、RAM、ROM、定時計數(shù)器和多種I/O接口集成在一片芯片上,形成芯片級的計算機。
I2C總線常涉及的術(shù)語
主機:啟動數(shù)據(jù)傳送并產(chǎn)生時鐘信號的設(shè)備;
從機:被主機尋址的器件;
多主機:同時有多于一個主機嘗試控制總線但不破壞傳輸;
主模式:用I2CNDAT支持自動字節(jié)計數(shù)的模式;位 I2CRM,I2CSTT,I2CSTP控制數(shù)據(jù)的接收和發(fā)送;
從模式:發(fā)送和接收操作都是由I2C模塊自動控制的;
仲裁:是一個在有多個主機同時嘗試控制總線但只允許其中一個控制總線并使傳輸不被破壞的過程;
同步:兩個或多個器件同步時鐘信號的過程;
發(fā)送器:發(fā)送數(shù)據(jù)到總線的器件;
接收器:從總線接收數(shù)據(jù)的器件。
1.2 I2C協(xié)議層
I2C的協(xié)議定義了通信的起始和停止信號、數(shù)據(jù)有效性、響應、仲裁、時鐘同步和地址廣播等環(huán)節(jié)。
(1)數(shù)據(jù)有效性規(guī)定
I2C總線進行數(shù)據(jù)傳送時,時鐘信號為高電平期間,數(shù)據(jù)線上的數(shù)據(jù)必須保持穩(wěn)定,只有在時鐘線上的信號為低電平期間,數(shù)據(jù)線上的高電平或低電平狀態(tài)才允許變化。 如下圖:
每次數(shù)據(jù)傳輸都以字節(jié)為單位,每次傳輸?shù)淖止?jié)數(shù)不受限制。
(2)起始和停止信號
SCL線為高電平期間,SDA 線由高電平向低電平的變化表示起始信號;SCL線為高電平期間,SDA線由低電平向高電平的變化表示終止信號。 如下圖:
起始和終止信號都是由主機發(fā)出的,在起始信號產(chǎn)生后,總線就處于被占用的狀態(tài);在終止信號產(chǎn)生后,總線就處于空閑狀態(tài)。
(3)應答響應
每當發(fā)送器件傳輸完一個字節(jié)的數(shù)據(jù)后,后面必須緊跟一個校驗位,這個校驗位是接收端通過控制SDA(數(shù)據(jù)線)來實現(xiàn)的,以提醒發(fā)送端數(shù)據(jù)我這邊已經(jīng)接收完成,數(shù)據(jù)傳送可以繼續(xù)進行。這個校驗位其實就是數(shù)據(jù)或地址傳輸過程中的響應。響應包括“應答(ACK)”和“非應答(NACK)”兩種信號。作為數(shù)據(jù)接收端時,當設(shè)備(無論主從機)接收到I2C傳輸?shù)囊粋€字節(jié)數(shù)據(jù)或地址后,若希望對方繼續(xù)發(fā)送數(shù)據(jù),則需要向?qū)Ψ桨l(fā)送“應答(ACK)”信號即特定的低電平脈沖,發(fā)送方會繼續(xù)發(fā)送下一個數(shù)據(jù);若接收端希望結(jié)束數(shù)據(jù)傳輸,則向?qū)Ψ桨l(fā)送“非應答(NACK)”信號即特定的高電平脈沖,發(fā)送方接收到該信號后會產(chǎn)生一個停止信號,結(jié)束信號傳輸。應答響應時序圖如下:
每一個字節(jié)必須保證是8位長度。數(shù)據(jù)傳送時,先傳送最高位(MSB),每一個被傳送的字節(jié)后面都必須跟隨一位應答位(即一幀共有9位)。
由于某種原因從機不對主機尋址信號應答時(如從機正在進行實時性的處理工作而無法接收總線上的數(shù)據(jù)),它必須將數(shù)據(jù)線置于高電平,而由主機產(chǎn)生一個終止信號以結(jié)束總線的數(shù)據(jù)傳送。
如果從機對主機進行了應答,但在數(shù)據(jù)傳送一段時間后無法繼續(xù)接收更多的數(shù)據(jù)時,從機可以通過對無法接收的第一個數(shù)據(jù)字節(jié)的“非應答”通知主機,主機則應發(fā)出終止信號以結(jié)束數(shù)據(jù)的繼續(xù)傳送。
當主機接收數(shù)據(jù)時,它收到最后一個數(shù)據(jù)字節(jié)后,必須向從機發(fā)出一個結(jié)束傳送的信號。這個信號是由對從機的“非應答”來實現(xiàn)的。然后,從機釋放SDA線,以允許主機產(chǎn)生終止信號。
這些信號中,起始信號是必需的,結(jié)束信號和應答信號都可以不要。
也就是說一個主機一個從機,主機向從機發(fā)送數(shù)據(jù),首先主機產(chǎn)生一個起始信號,主機開始發(fā)送數(shù)據(jù)給從機,如果從機接收數(shù)據(jù)之后,還想主機繼續(xù)發(fā)送數(shù)據(jù),就發(fā)送一個應答給主機,主機收到應答信號后,繼續(xù)向從機發(fā)送數(shù)據(jù)。如果從機接收數(shù)據(jù)之后,不想主機繼續(xù)發(fā)送數(shù)據(jù),終止數(shù)據(jù)傳輸,可以向主機發(fā)送一個非應答,主機就會產(chǎn)生一個停止信號,結(jié)束I2C的通信。
主機從從機讀取數(shù)據(jù)時,主機發(fā)送一個起始信號,主機接收數(shù)據(jù)之后,也要產(chǎn)生一個應答或非應答,如果主機產(chǎn)生一個應答,表示想繼續(xù)讀取從機數(shù)據(jù),從機接收到應答信號,從機繼續(xù)發(fā)送數(shù)據(jù)給主機。主機如果不想讀取從機數(shù)據(jù),就會產(chǎn)生一個非應答后,會產(chǎn)生一個停止信號,從機接收到非應答信號,停止數(shù)據(jù)發(fā)送。
(4)總線的尋址方式
I2C總線尋址按照從機地址位數(shù)可分為兩種,一種是7位,另一種是10位。采用7位的尋址字節(jié)(尋址字節(jié)是起始信號后的第一個字節(jié))的位定義如下:
D7~D1 位組成從機的地址。D0位是數(shù)據(jù)傳送方向位,為“0”時表示主機向從機寫數(shù)據(jù),為“1”時表示主機由從機讀數(shù)據(jù)。
10位尋址和7位尋址兼容,而且可以結(jié)合使用。10位尋址不會影響已有的7位尋址,有7位和10位地址的器件可以連接到相同的I2C總線。我們就以7位尋址為例進行介紹。
當主機發(fā)送了一個地址后,總線上的每個器件都將頭7位與它自己的地址比較,如果一樣,器件會判定它被主機尋址,其他地址不同的器件將被忽略后面的數(shù)據(jù)信號。 至于是從機接收器還是從機發(fā)送器,都由R/W位決定的。從機的地址由固定部分和可編程部分組成。在一個系統(tǒng)中可能希望接入多個相同的從機,從機地址中可編程部分決定了可接入總線該類器件的最大數(shù)目。如一個從機的7位尋址位有4位是固定位,3位是可編程位,2^3=8這時僅能尋址8個同樣的器件,即可以有8個同樣的器件接入到該I2C總線系統(tǒng)中。
(5)數(shù)據(jù)傳輸
I2C總線上傳送的數(shù)據(jù)信號是廣義的,既包括地址信號,又包括真正的數(shù)據(jù)信號。在起始信號后必須傳送一個從機的地址(7位),第8位是數(shù)據(jù)的傳送方向位(R/W),用“0”表示主機發(fā)送(寫)數(shù)據(jù)(W), “1”表示主機接收數(shù)據(jù)(R)。每次數(shù)據(jù)傳送總是由主機產(chǎn)生的終止信號結(jié)束。但是,若主機希望繼續(xù)占用總線進行新的數(shù)據(jù)傳送,則可以不產(chǎn)生終止信號,馬上再次發(fā)出起始信號對另一從機進行尋址。
在總線的一次數(shù)據(jù)傳送過程中,可以有以下幾種組合方式:
注意:有陰影部分表示數(shù)據(jù)由主機向從機傳送,無陰影部分則表示數(shù)據(jù)由從機向主機傳送。A表示應答,A非表示非應答(高電平)。S表示起始信號,Р表示終止信號。一個數(shù)據(jù)幀是9位。
由于51單片機沒有硬件IIC接口,即使有硬件接口我們通常還是采用軟件模擬I2C。 主要原因是硬件 IIC設(shè)計的比較復雜,而且穩(wěn)定性不怎么好,程序移植比較麻煩,而用軟件模擬IIC,最大的好處就是移植方便,同一個代碼兼容所有單片機,任何一個單片機只要有IO口(不需要特定IO),都可以很快的移植過去。
EEPROM (Electrically Erasable Programmable read only memory)是指帶電可擦可編程只讀存儲器。是一種掉電后數(shù)據(jù)不丟失的存儲芯片。 EEPROM 可以在電腦上或?qū)S迷O(shè)備上擦除已有信息,重新編程。一般用在即插即用。
2.AT24C02芯片介紹
AT24C02芯片也是EEPROM存儲芯片,是一種掉電后數(shù)據(jù)不丟失的存儲芯片。AT24C0、01/02/04/08/16...是一個1K/2K/4K/8K/16k位串行CMOS,內(nèi)部含有128/256/512/1024/2048個8位字節(jié),AT24C01有一個8字節(jié)頁寫緩沖器,AT24CO2/04/08/16有一個16 字節(jié)頁寫緩沖器。該器件通過I2C總線接口進行操作,它有一個專門的寫保護功能。開發(fā)板上使用的是 AT24C02 (EEPROM) 芯片,此芯片具有I2C通信接口,芯片內(nèi)保存的數(shù)據(jù)在掉電情況下都不丟失,所以通常用于存放一些比較重要的數(shù)據(jù)等。AT24CO2芯片管腳及外觀圖如下圖所示:
芯片管腳說明如下圖所示:
AT24C02器件地址為7位,高4位固定為1010,低3位由A0/A1/A2信號線的電平?jīng)Q定。因為傳輸?shù)刂坊驍?shù)據(jù)是以字節(jié)為單位傳送的,當傳送地址時,器件地址占7位,還有最后一位(最低位R/W)用來選擇讀寫方向,它與地址無關(guān)。其格式如下:
開發(fā)板已經(jīng)將芯片的 A0/A1/A2連接到GND,所以器件地址為1010000,即 0x50(未計算最低位)。如果要對芯片進行寫操作時,R/W即為0,寫器件地址即為0XA0;如果要對芯片進行讀操作時,R/W即為1,此時讀器件地址為0XA1。開發(fā)板上也將 WP引腳直接接在GND 上,此時芯片允許數(shù)據(jù)正常讀寫。
I2C總線時序如下圖所示:
開發(fā)板硬件連接
從圖中可以看出,芯片的SCL和SDA管腳是連接在單片機的P2.1和P2.0上,為了讓IIC總線默認為高電平,通常會在IIC總線上接上拉電阻,在圖中并沒有看到SCL和SDA管腳有上拉電阻,這是因為開發(fā)板單片機IO都外接了10K上拉電阻,當單片機IO口連接到芯片的SCL和SDA腳時即相當于它們外接上拉電阻,所以此處可以省去。
**3.多文件工程創(chuàng)建
**
實現(xiàn)的功能是:系統(tǒng)運行時,數(shù)碼管右 3 位顯示 0,按矩陣按鍵 S1 鍵將數(shù)據(jù)寫入到 EEPROM 內(nèi)保存,按 S2 鍵讀取 EEPROM 內(nèi)保存的數(shù)據(jù),按 S3 鍵顯示數(shù)據(jù)加 1,按 S4 鍵顯示數(shù)據(jù)清零,最大能寫入的數(shù)據(jù)是 255。
App文件夾:用于存放外設(shè)驅(qū)動文件,如LED、數(shù)碼管、定時器等。
Obj文件夾:用于存放編譯產(chǎn)生的 c/匯編/鏈接的列表清單、調(diào)試信息、hex文件、預覽信息、封裝庫等文件。
Public文件夾:用于存放51單片機公共的文件,如延時、51頭文件、變量類型重定義等。
User文件夾:用于存放用戶主函數(shù)文件,如 main.c。
1.新建一個工程
選擇對應的芯片型號
2.向工程添加文件
按照需要給工程分組并添加對應文件,在工程中分3組,User、App、Public,至于前面創(chuàng)建的Obj文件夾是在工程中無需體現(xiàn),因為只是編譯器生成的一些中間文件和.hex執(zhí)行文件。通常在工程組的命名與創(chuàng)建的文件夾名保持一致,方便查找到源文件位置。如下所示:
新建分組,雙擊可以修改名字,用英文分組,完成后,點擊OK
分組后,在工程中就會出現(xiàn)剛才的分組列表,如下所示:
對分組添加相應的文件,點擊小方框,選擇要添加文件的分組,添加文件。
同樣的方法,將App、Public工程組中文件也添加進去。一般除了main.c沒有對應的main.h的頭文件,其他文件通常會有對應的.h頭文件,例如public.h和public.c。
頭文件的書寫
#ifndef _public_H //public是與文件名相同 ifndef是c語言中的條件編譯,意思是如果沒有定義對應的頭文件,就會定義該頭文件
#define _public_H
#endif
主要目的是防止頭文件的重復包含和編譯。
配置魔術(shù)棒選項
這一步的配置工作非常重要,很多人編寫完程序編譯后發(fā)現(xiàn)找不到HEX文件,還有的人直接編譯前面添加好文件的工程出現(xiàn)報錯,這些問題都是在這個地方?jīng)]有配置好導致的。
(1)C51選項卡配置,此處目的是將我們前面添加到工程組中的文件路徑包括進來,否則程序中調(diào)用其他文件夾的頭文件則會報錯找不到頭文件路徑,具體步驟如下:
點擊魔術(shù)棒
頭文件的作用一般是(1)包含其他的頭文件;(2)聲明全局變量;(3)聲明自定義函數(shù)(4)自定義的變量類型(u8 u16),當其他程序中加載了該頭文件就可以使用該函數(shù)和全局變量。
public.h文件
#ifndef _public_H//public是與文件名相同
//ifndef是c語言中的條件編譯,意思是如果沒有定義對應的頭文件,就會定義該頭文件
//主要目的是防止頭文件的重復包含和編譯。
#define _public_H
#include "reg52.h" //加載reg52.h頭文件
//定義類型別名
typedef unsigned int u16;
typedef unsigned char u8;
//聲明兩個延時函數(shù)
void delay_10us(u16 time);
void delay_ms(u16 time);
#endif
public.c文件
#include "public.h"
void delay_10us(u16 time) //延時函數(shù) time=1大概延時10us
{
while(time--)
;
}
void delay_ms(u16 time) //延時函數(shù) time=1大概延時1ms
{
u16 i,j;
for(i=time;i >0;i--)
for(j=110;j >0;j--)
;
}
(2)Output選項卡中把輸出文件夾定位到我們實驗目錄下的 Obj文件夾,如果想在編譯的過程中生成hex文件,那么、Create HEX File 選項勾上。配置如下:
(3) Listing 選項卡中把輸出文件夾也定位到我們實驗目錄下的Obj文件夾。其它設(shè)置默認,配置如下:
同樣的方式把其他文件夾的文件添加到對應的分組,并將頭文件的路徑添加上。
4.實驗內(nèi)容和程序
實現(xiàn)的功能是:系統(tǒng)運行時,數(shù)碼管右 3 位顯示 0,按矩陣按鍵 S1 鍵將數(shù)據(jù)寫入到 EEPROM 內(nèi)保存,按 S2 鍵讀取 EEPROM 內(nèi)保存的數(shù)據(jù),按 S3 鍵顯示數(shù)據(jù)加 1,按 S4 鍵顯示數(shù)據(jù)清零,最大能寫入的數(shù)據(jù)是 255。
實驗結(jié)果
,時長00:36
[ ]
主函數(shù)main.c
#include "public.h"
#include "key.h"
#include "smg.h"
#include "at24c02.h"
//#include "iic.h" at24c02函數(shù)內(nèi)容調(diào)用了iic.h
/*
實現(xiàn)的功能是:系統(tǒng)運行時,數(shù)碼管右 3 位顯示 0,
按 S1 鍵將數(shù)據(jù) 寫入到 EEPROM 內(nèi)保存,
按 S2 鍵讀取 EEPROM 內(nèi)保存的數(shù)據(jù),
按 S3 鍵顯示數(shù)據(jù)加 1,
按 S4 鍵顯示數(shù)據(jù)清零,最大能寫入的數(shù)據(jù)是 255。
*/
#define EEPROM_ADDRESS 0 //宏定義數(shù)據(jù)存儲的地址
void main()
{
u8 key_value=0; //保存按鍵的鍵值
u8 save_value =0;//定義變量,存儲數(shù)據(jù)的變量
u8 save_buf[3];//三位數(shù)
while(1)
{
key_value=key_juzhen_fanzhuan_scan();
if(key_value==1) //按 K1 鍵將數(shù)據(jù) 寫入到 EEPROM 內(nèi)保存
{
at24c02_write_one_byte(EEPROM_ADDRESS,save_value);
}
else if(key_value==2) //按 K2 鍵讀取 EEPROM 內(nèi)保存的數(shù)據(jù)
{
save_value=at24c2_read_one_byte(EEPROM_ADDRESS);
}
else if(key_value==3)
{
save_value++;
//不能一直加 8位二進制 最大值255
if(save_value==255)//當數(shù)據(jù)大于255后會溢出 重新從0開始
save_value=255;
}
else if(key_value==4)
{
save_value=0;
}
//數(shù)碼管顯示
//對十進制數(shù)據(jù)進行取百位 十位 個位
/*
三位數(shù)提取 百位 假設(shè)三位數(shù)是100 100/100=1 取出百位
十位提取 (100%100)/10 數(shù)據(jù)對100取余 ,再除以10 取出十位
個位提取 (100%100)%10 數(shù)據(jù)對100取余,再對10取余 取出個位
*/
// save_buf[0]=save_value/100;//取出百位
// save_buf[1]= save_value%100/10;// 取出十位
// save_buf[2]= save_value%100%10;//取出個位
save_buf[0]=gsmg[save_value/100];//取出百位
save_buf[1]= gsmg[save_value%100/10];// 取出十位
save_buf[2]= gsmg[save_value%100%10];//取出個位
smg_display(save_buf,6);//數(shù)組名表示數(shù)組元素首地址
}
}
Key.c 矩陣按鍵程序
#include "key.h"
u8 key_juzhen_fanzhuan_scan(void) //定義線翻轉(zhuǎn)法函數(shù)
{
/*
線翻轉(zhuǎn)法,就是使所有行線為低電平時,檢測所有列線是否有低電平,
如果有,就記錄列線值;然后再翻轉(zhuǎn),使所有列線都為低電平,檢測所有行線的值,
由于有按鍵按下,行線的值也會有變化,記錄行線的值。從而就可以檢測到全部按鍵。
*/
static u8 key_value=0;
//定義靜態(tài)局部變量 key_value的值會賦完一次初值之后,key_value的值會保存上一次的結(jié)果值
/*開發(fā)板 4*4矩陣鍵盤 行控制端口 P1.7 P1.6 P1.5 P1.4 列控制端口: P1.3 P1.2 P1.1 P1.0
P1口輸出默認是高電平,使所有行線為低電平 此時P1控制端 二進制 0000 1111 十六進制 0x0f
找出按鍵的列值
{
按鍵在第1列時,此時P1控制端 二進制 0000 0111 十六進制 0x07 key_value=1
按鍵在第2列時,此時P1控制端 二進制 0000 1011 十六進制 0x0b key_value=2
按鍵在第3列時,此時P1控制端 二進制 0000 1101 十六進制 0x0d key_value=3
按鍵在第4列時,此時P1控制端 二進制 0000 1110 十六進制 0x0e key_value=4
}
再使所有的列線為低電平 此時P1控制端 二進制 1111 0000 十六進制 0xf0
{
按鍵在第1行時,此時P1控制端 二進制 0111 0000 十六進制 0x70 按鍵的位置 key_value=對應的行數(shù)=key_value
按鍵在第2行時,此時P1控制端 二進制 1011 0000 十六進制 0xb0 按鍵的位置 key_value=對應的行數(shù)+4*1 = key_value+4 第一行有4個按鍵
按鍵在第3行時,此時P1控制端 二進制 1101 0000 十六進制 0xd0 按鍵的位置 key_value=對應的行數(shù)+4*2 = key_value+8 前兩行有8個按鍵
按鍵在第4行時,此時P1控制端 二進制 1110 0000 十六進制 0xe0 按鍵的位置 key_value=對應的行數(shù)+4*3 = key_value+12 前兩行有12個按鍵
}
*/
KEY_Port=0x0f; //使所有行線為低電平
if(KEY_Port!=0x0f)
{
delay_10us(1000);//延時10ms實現(xiàn)按鍵消抖
//測試列
if(KEY_Port!=0x0f) //實現(xiàn)消抖后再次檢測
{
KEY_Port=0x0f; //設(shè)置再次賦值 行線為低電平,方便進行檢查
switch(KEY_Port)
{
case 0x07: key_value = 1;break;
case 0x0b: key_value = 2;break;
case 0x0d: key_value = 3;break;
case 0x0e: key_value = 4;break;
}
//測試列
KEY_Port=0xf0;//使所有的列線為低電平
switch(KEY_Port)
{
case 0x70: key_value = key_value;break;
case 0xb0: key_value = key_value+4;break;
case 0xd0: key_value = key_value+8;break;
case 0xe0: key_value = key_value+12;break;
}
while(KEY_Port!=0xf0);//等待按鍵松開 當按鍵松開時 KEY_Port = 0xf0 會跳出死循環(huán)
}
}
else
key_value=0;
return key_value;//返回函數(shù)值
}
Key.h 頭文件
#ifndef _key_H //條件編譯語句 如果沒有定義頭文件,就在下面重新定義頭文件
#define _key_H
#include "public.h" //加載公共部分Public中的頭文件的內(nèi)容
#define KEY_Port P1 //宏定義 無分號 將P1口定義為矩陣鍵盤的端口
//開發(fā)板的數(shù)碼管段選信號由P0口(P0.0-P0.7)控制 位選 P2口默認是輸出高電平 默認選擇LED8 第1個數(shù)碼管
//定義數(shù)碼管顯示內(nèi)容的數(shù)組
u8 key_juzhen_fanzhuan_scan(void);//定義線翻轉(zhuǎn)法函數(shù)
#endif
Smg.c 數(shù)碼管顯示函數(shù)
#include "smg.h"
u8 gsmg[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,
0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x00};
//共陰極數(shù)碼管顯示數(shù)據(jù)1-F,最后一個是數(shù)碼管全滅 表示c語言中的續(xù)行符
void smg_display(u8 dat[],u8 pos) //dat[]是數(shù)碼管段選數(shù)據(jù) 顯示0 1 2 3...
// pos 位選 1 2 3 4 5 6 7 8 第幾個數(shù)據(jù)管
//更改數(shù)碼管的顯示 提供數(shù)據(jù)和數(shù)碼管使用的位置
{
u8 i=0; //定義變量的時候賦初值,防止一些編譯器識別未用的變量產(chǎn)生報錯
u8 pos_temp=pos-1;//
for(i=pos_temp;i< 8;i++)
{
switch(i) //用switch-case語句進行位選
{
case 7: W_C =0;W_B = 0;W_A = 0;break; //對應選擇了Y0-LED1 最后一個數(shù)碼管
case 6: W_C =0;W_B = 0;W_A = 1;break; //對應選擇了Y0-LED2
case 5: W_C =0;W_B = 1;W_A = 0;break; //對應選擇了Y0-LED3
case 4: W_C =0;W_B = 1;W_A = 1;break; //對應選擇了Y0-LED4
case 3: W_C =1;W_B = 0;W_A = 0;break; //對應選擇了Y0-LED5
case 2: W_C =1;W_B = 0;W_A = 1;break; //對應選擇了Y0-LED6
case 1: W_C =1;W_B = 1;W_A = 0;break; //對應選擇了Y0-LED7
case 0: W_C =1;W_B = 1;W_A = 1;break; //對應選擇了Y0-LED8 第一個數(shù)碼管
}
// SMG_Port = gsmg[dat[i-pos_temp]];//段選信號 dat[]表示0 1 2 3 4 5 6 7 -F
SMG_Port = dat[i-pos_temp];//段選信號 dat[]表示0 1 2 3 4 5 6 7 -F
delay_10us(100);//大概1ms延時
SMG_Port=0x00;//消隱
}
}
Smg.h 頭文件
#ifndef _smg_H
#define _smg_H
#include "public.h"
#define SMG_Port P0 //宏定義 將P0口定義SMG_Port
//開發(fā)板的數(shù)碼管段選信號由P0口(P0.0-P0.7)控制
//定義數(shù)碼管顯示內(nèi)容的數(shù)組
//定義的是全局變量 gsmg 開頭
extern u8 gsmg[17]; //對數(shù)組聲明
sbit W_C = P2^4;//sbit定義位變量 位控制選擇數(shù)碼管
sbit W_B = P2^3; //sbit定義位變量
sbit W_A = P2^2;//sbit定義位變量
void smg_display(u8 dat[],u8 pos); //聲明數(shù)碼管動態(tài)掃描顯示函數(shù)
#endif
Iic.c
#include "iic.h"
//IIC起始和停止信號和應答信號函數(shù)
/*
SCL線為高電平期間,SDA 線由高電平向低電平的變化表示起始信號;
SCL線為高電平期間,SDA線由低電平向高電平的變化表示終止信號。
每當發(fā)送器件傳輸完一個字節(jié)的數(shù)據(jù)后,后面必須緊跟一個校驗位,
這個校驗位是接收端通過控制SDA(數(shù)據(jù)線)來實現(xiàn)的,
以提醒發(fā)送端數(shù)據(jù)我這邊已經(jīng)接收完成,數(shù)據(jù)傳送可以繼續(xù)進行。
這個校驗位其實就是數(shù)據(jù)或地址傳輸過程中的響應。
響應包括"應答(ACK)"和"非應答(NACK)"兩種信號。
作為數(shù)據(jù)接收端時,當設(shè)備(無論主從機)接收到I2C傳輸?shù)囊粋€字節(jié)數(shù)據(jù)或地址后,
若希望對方繼續(xù)發(fā)送數(shù)據(jù),則需要向?qū)Ψ桨l(fā)送"應答(ACK)"信號即特定的低電平脈沖,
發(fā)送方會繼續(xù)發(fā)送下一個數(shù)據(jù);若接收端希望結(jié)束數(shù)據(jù)傳輸,
則向?qū)Ψ桨l(fā)送"非應答(NACK)"信號即特定的高電平脈沖
,發(fā)送方接收到該信號后會產(chǎn)生一個停止信號,結(jié)束信號傳輸。
每一個字節(jié)必須保證是8位長度。數(shù)據(jù)傳送時,先傳送最高位(MSB),
每一個被傳送的字節(jié)后面都必須跟隨一位應答位(即一幀共有9位)。*/
void iic_start(void) //根據(jù)起始信號的時序編寫iic的起始信號函數(shù)
{
//根據(jù)數(shù)據(jù)手冊的時間參數(shù)級或者us SDA由高電平編程低電平
IIC_SDA=1;//如果把該條語句放在SCL后面,第二次讀寫會出現(xiàn)問題
delay_10us(1);
IIC_SCL=1; //SCL為高電平
delay_10us(1);
IIC_SDA=0;
delay_10us(1);
IIC_SCL=0; //SCL為低電平時,表示占用總線,總線處于工作狀態(tài)
delay_10us(1);
}
void iic_stop(void) //iic的停止信號
{
IIC_SDA=0; //如果把該條語句放在SCL后面,第二次讀寫會出現(xiàn)問題
//根據(jù)數(shù)據(jù)手冊的時間參數(shù)級或者us SDA由低電平編程高電平
delay_10us(1);
IIC_SCL=1; //SCL為高電平
delay_10us(1);
IIC_SDA=1;
delay_10us(1);
}
/*
響應包括"應答(ACK)"和"非應答(NACK)"兩種信號。
作為數(shù)據(jù)接收端時,當設(shè)備(無論主從機)接收到I2C傳輸?shù)囊粋€字節(jié)數(shù)據(jù)或地址后,
若希望對方繼續(xù)發(fā)送數(shù)據(jù),則需要向?qū)Ψ桨l(fā)送"應答(ACK)"信號即特定的低電平脈沖,
發(fā)送方會繼續(xù)發(fā)送下一個數(shù)據(jù);若接收端希望結(jié)束數(shù)據(jù)傳輸,
則向?qū)Ψ桨l(fā)送"非應答(NACK)"信號即特定的高電平脈沖,
發(fā)送方接收到該信號后會產(chǎn)生一個停止信號,結(jié)束信號傳輸。
*/
//主機發(fā)送數(shù)據(jù)給從機 的應答信號和非應答信號
void iic_ack(void) //iicACK應答信號函數(shù)
{
//ACK是特殊的低電平信號
//由時序圖編寫
IIC_SCL=0;
IIC_SDA=0;
delay_10us(1);
IIC_SCL=1;
delay_10us(1);
IIC_SCL=0;
}
void iic_noack(void) //iic非應答信號函數(shù)
{
//NACK是特殊的高電平信號
//由時序圖編寫
IIC_SCL=0;
IIC_SDA=1;
delay_10us(1);
IIC_SCL=1;
delay_10us(1);
IIC_SCL=0;
}
//判斷應答信號的函數(shù) 就是主機讀取IIC_SDA的電平
//函數(shù)設(shè)置一個返回值 1表示非應答 0表示應答
u8 iic_wait_ack(void)
{
/*第一步:因為SCL為高電平的時候數(shù)據(jù)才是穩(wěn)定的,將SCL設(shè)置為高電平.
第二步;延時一點時間進行讀取SDA的電平.
第三步:利用while循環(huán) while(IIC_SDA) 當IIC_SDA=0時,響應是ACK應答信號會跳出循環(huán),
將SCL設(shè)置為低電平,數(shù)據(jù)可以修改,等待下一次數(shù)據(jù)的變化,并且返回函數(shù)值0.
當IIC_SDA=1時表示響應是NACK非應答信號會執(zhí)行循環(huán)體里面的函數(shù),一直循環(huán),設(shè)置一個變量,用來計時
的時間,不能一直等待,當計時超過一定的時間,就認為SDA是高電平,此時是非應答,要產(chǎn)生停止信號
*/
u8 time_temp=0;
IIC_SCL=1;
delay_10us(1);
while(IIC_SDA)
{
time_temp++;//等價與time_step=time_step+1
if(time_temp >100)
{
iic_stop();
return 1;
}
}
IIC_SCL=0;
return 0;
}
//IIC的讀寫字節(jié)數(shù)據(jù)函數(shù)
//IIC的寫字節(jié)函數(shù)
void iic_write_byte(u8 dat)
{
//要寫數(shù)據(jù)或地址 從高位開始寫 SCL低電平數(shù)據(jù)可以變化 SCL高電平的時候數(shù)據(jù)穩(wěn)定,就可以發(fā)送出去
//一個字節(jié)有8位,因此要一位一位的寫用循環(huán)
u8 i=0;
IIC_SCL=0;//SCL是低電平 數(shù)據(jù)可以修改 先寫高位
for(i=0;i< 8;i++)
{
/*獲取dat最高位的 dat&0x80 d7 d6 d5 d4 d3 d2 d1 d0 & 1 0 0 0 0 0 0 0
根據(jù)與運算 dat的低7位都為0 當d7=1 時邏輯表達式的值為1 當d7=0時,邏輯表達式的值為0
*/
if((dat&0x80) >0) //dat最高位是1
IIC_SDA=1; //傳輸數(shù)據(jù)是1
else
IIC_SDA=0; //傳輸數(shù)據(jù)是0
/*對dat的次高位變成最高位 移位運算符dat< < 1 表示dat左移一位但是不會改變dat的值,
表達式的值是dat左移一位的結(jié)果 左移移位運算符 dat< <=1 表示dat左移一位將結(jié)果賦值給dat*/
dat< <=1;
delay_10us(1);//延時10us
IIC_SCL=1; //數(shù)據(jù)要進行傳輸 SCL為高電平 數(shù)據(jù)穩(wěn)定 可以進行數(shù)據(jù)傳輸
delay_10us(1);//延時10us
IIC_SCL=0; //重新SCL設(shè)置為0 為下一位的數(shù)據(jù)修改做準備
delay_10us(1);//延時10us
}
}
//IIC的讀字節(jié)函數(shù)
u8 iic_read_byte(u8 ack)
//IIC的讀字節(jié)函數(shù),要讀取數(shù)據(jù),因此由函數(shù)的返回值,一個字節(jié)的數(shù)據(jù)u8
//讀取數(shù)據(jù)需要看返回的響應信號 1 ACK 繼續(xù)讀取 0 NACK不讀取數(shù)據(jù)
{
u8 i=0;
u8 receive_dat=0;//接收數(shù)據(jù)
for(i=0;i< 8;i++)
{
IIC_SCL=0;//SCL為低電平 可以修改數(shù)據(jù)
delay_10us(1);
IIC_SCL=1; //SCL為高電平 數(shù)據(jù)穩(wěn)定 可以進行讀取
receive_dat< <=1;
if(IIC_SDA) //開始讀取數(shù)據(jù) 判斷SDA管腳電平 SDA==1 得到一個信號
//對最低位進行讀取
receive_dat++;
delay_10us(1);
/*例如 i=0 SDA=1 receive_dat=0+1=1 高位 先讀取高位
i=1 SDA=1 receive_dat=1+1=2 次高位
1 二進制 0000 0001
2 二進制 0000 0010
與實際不符合 實際應該是0000 0011 并且需要進行移位最后11移動最高位
即 receive_dat進行左移 假設(shè)i=0 SDA=1 0000 0001
i=1 SDA=0 0000 0010
添加 receive_dat< <=1;左移語句
假設(shè)從機數(shù)據(jù)是 1001 0001
i receive_dat< <=1; receive_dat 主機從從機讀取的數(shù)據(jù)
i=0 0000 0000 0000 0001 最高位數(shù)據(jù) SDA=1
i=1 0000 0010 0000 0010 次高位數(shù)據(jù) SDA=0
i=2 0000 0100 0000 0100 SDA=0
i=3 0000 1000 0000 1001 SDA=1
i=4 0001 0010 0001 0010 SDA=0
i=5 0010 0100 0010 0100 SDA=0
i=6 0100 1000 0100 1000 SDA=0
i=7 1001 0000 1001 0001 SDA=1
數(shù)據(jù)傳輸完成*/
}
//對響應信號進行處理 看主機是否需要繼續(xù)向從機讀取數(shù)據(jù) ACK=0 繼續(xù)讀取數(shù)據(jù) ACK=1停此讀取數(shù)據(jù)
if(!ack) // ack=0時發(fā)送一個ack
iic_noack();
else
iic_ack(); // ack=1 !ack=0 時發(fā)送一個nack
return receive_dat;//返回讀取的數(shù)據(jù)
}
Iic.h
#ifndef _iic_H
#define _iic_H
#include "public.h"
//利用I/O模擬IIC的時序 開發(fā)板的P2.1連接SCL時鐘線 P2.0連接SDA雙向數(shù)據(jù)傳輸線
sbit IIC_SCL=P2^1;
sbit IIC_SDA=P2^0;
//函數(shù)聲明
void iic_start(void);//根據(jù)起始信號的時序編寫iic的起始信號函數(shù)
void iic_stop(void); //iic的停止信號
void iic_ack(void); //iicACK應答信號函數(shù)
void iic_noack(void); //iic非應答信號函數(shù)
//判斷應答信號的函數(shù) 就是主機讀取IIC_SDA的電平
//函數(shù)設(shè)置一個返回值 1表示非應答 0表示應答
u8 iic_wait_ack(void);
//IIC的寫字節(jié)函數(shù)
void iic_write_byte(u8 dat);
//IIC的讀字節(jié)函數(shù)
u8 iic_read_byte(u8 ack);
#endif
At24c02.c 程序
#include "at24c02.h"
#include "iic.h"
//at24c02的讀寫程序,根據(jù)IIC協(xié)議層數(shù)據(jù)傳輸編寫
void at24c02_write_one_byte(u8 addr,u8 dat)
//24C02有256個字節(jié) 要寫到寫入地址 和寫入的數(shù)據(jù) 主機寫入從機 主機向從機發(fā)送數(shù)據(jù)
{
/* 第一步:發(fā)送起始信號
AT24C02器件地址為7位,高4位固定為1010,
低3位由A0/A1/A2信號線的電平?jīng)Q定。
因為傳輸?shù)刂坊驍?shù)據(jù)是以字節(jié)為單位傳送的,
當傳送地址時,器件地址占7位,還有最后一位(最低位R/W)用來選擇讀寫方向,它與地址無關(guān)。
寫AT24C02引腳二進制 1010 0000 十六進制:0xA0
讀AT24C02引腳二進制 1010 0001 十六進制:0xA1
*/
iic_start();//發(fā)送起始信號 后第一個字節(jié)是從機地址+讀寫位
iic_write_byte(0xA0);
//從機發(fā)的ACK 主機需要等待ACK
iic_wait_ack(); //等待一個ACK
iic_write_byte(addr); //發(fā)送寫地址的傳輸
//因為數(shù)據(jù)傳輸方向沒有改變 因此不需要從新產(chǎn)生起始信號和
iic_wait_ack(); //等待一個ACK
iic_write_byte(dat);//寫入一個字節(jié)數(shù)據(jù)
iic_wait_ack(); //等待一個ACK
iic_stop();//傳輸一個字節(jié)后 主機發(fā)出停止信號
delay_ms(10);//延時10ms 將數(shù)據(jù)存儲
}
//讀字節(jié)函數(shù) 就是主機向從機讀取數(shù)據(jù) 即從機向主機發(fā)送數(shù)據(jù)
//數(shù)據(jù)的傳輸方向改變
u8 at24c2_read_one_byte(u8 addr) //讀取數(shù)據(jù)的地址
{ //首先寫入讀取數(shù)據(jù)的存放的地址 然后改變數(shù)據(jù)傳輸方向 讀取數(shù)據(jù)傳輸方向改變
u8 temp=0;//讀取數(shù)據(jù)存儲變量
iic_start();//主機發(fā)送起始信號
iic_write_byte(0xA0);// 第一個字節(jié)是從機地址+讀寫位
iic_wait_ack(); //等待一個ACK
iic_write_byte(addr); //發(fā)送讀數(shù)據(jù)存放地址的傳輸
iic_wait_ack(); //等待一個ACK
//開始讀取數(shù)據(jù) 數(shù)據(jù)傳輸方向改變
iic_start();//主機發(fā)送起始信號
iic_write_byte(0xA1);// 第一個字節(jié)是從機地址+讀寫位
iic_wait_ack(); //等待一個ACK
temp=iic_read_byte(0); //1 ACK 繼續(xù)讀取 0 NACK不讀取數(shù)據(jù)
iic_stop();//產(chǎn)生停止信號
return temp;
}
At24c02.h 頭文件
#ifndef _at24c02_H
#define _at24c02_H
#include "public.h"
void at24c02_write_one_byte(u8 addr,u8 dat) ;
u8 at24c2_read_one_byte(u8 addr); //讀取數(shù)據(jù)的地址
#endif
-
微控制器
+關(guān)注
關(guān)注
48文章
7906瀏覽量
153692 -
51單片機
+關(guān)注
關(guān)注
277文章
5710瀏覽量
126548 -
總線
+關(guān)注
關(guān)注
10文章
2948瀏覽量
89344 -
EEPROM
+關(guān)注
關(guān)注
9文章
1082瀏覽量
83268 -
I2C
+關(guān)注
關(guān)注
28文章
1534瀏覽量
127007
發(fā)布評論請先 登錄
C51單片機模擬I2C總線的C語言實現(xiàn)
I2C總線的單片機C語言實現(xiàn)及其應用

使用C語言模擬51單片機的I2C總線的資料和程序免費下載

STM32F4 I2C-EEPROM實驗例程

評論