TWI(IIC)使用範例AT24C02

 

    這不,看了OURAVR網站的範例,稍微修改了一下,帖在這裏當做個筆記.

//usart.h

#include 
<avr/io.h>

//管腳定義
#define PIN_RXD            0     //PD0   RXD
#define PIN_TXD            1     //PD1   TXD

//常量定義
#define BAUDRATE        9600        //波特率

void put_c(unsigned char c);
void put_s(unsigned char *ptr);
void init_USART(void);

 

//usart.c

#include 
"usart.h"

void put_c(unsigned char c) //發送採用查詢方式
{
    
while!(UCSRA & (1<<UDRE)) );
    UDR
=c;
}


void put_s(unsigned char *ptr)
{
    
while (*ptr)
    
{
        put_c(
*ptr++);
    }

    put_c(
0x0D);
    put_c(
0x0A);  //結尾發送回車換行
}


void init_USART(void)//USART 初始化
{

    
//USART 9600 8, n,1  PC上位機軟件(超級終端等)也要設成同樣的設置才能通訊
    UCSRC = (1<<URSEL) | 0x06;
    UBRRL
= (F_CPU/BAUDRATE/16-1)%256;
    UBRRH
= (F_CPU/BAUDRATE/16-1)/256;
    UCSRA 
= 0x00;
    UCSRB 
= (1<<TXEN);
    
//使能接收中斷,使能接收,使能發送
}

 

//TWI_main.c

/***********************************************
**** 名  稱:AVR TWI使用範例_AT24C02         
****                                          
**** 作  者:zhiyu                       
**** 編譯器:WINAVR20070525                   
****                                         
**** 參  考:
http://www.ouravr.com/guide_index.html(TWI範例(AT24C02))
**** 日  期:2007.07.26
****
**** 芯  片:M16L
**** 時鐘源:外部4M晶振
****
**** 結  果:測試成功
**** 問  題:中斷裏面的state部分不理解,如自加操作,其他基本沒什麼問題
**********************************************
*/

/*
本程序簡單的示範瞭如何使用ATMEGA16的TWI 讀寫AT24C02 IIC EEPROM
    TWI協議
         (即IIC協議,請認真參考IIC協議的內容,否則根本就不能掌握)
    一主多從的應用,M16作主機
         (M16做從機和多主多從的應用不多,請自行參考相關文檔)
    中斷模式
        (因爲AVR的速度很高,而IIC的速度相對較低,
         採用查詢模式會長時間獨佔CPU,令CPU的利用率明顯下降。
         特別是IIC速度受環境影響只能低速通訊時,對系統的實時性產生嚴重的影響。
         查詢模式可以參考其它文檔和軟件模擬IIC的文檔)
     AT24C02/04/08的操作特點
 
出於簡化程序考慮,各種數據沒有對外輸出,學習時建議使用JTAG ICE硬件仿真器
*/

#include 
<avr/io.h>
#include 
<avr/interrupt.h>
#include 
<util/delay.h>
#include 
"usart.h"
#include 
<util/twi.h>
//定義了各種模式下的狀態碼列表(TWSR已屏蔽預分頻位),本文後面附上中文描述

//管腳定義
#define  pinSCL                0                    //PC0 SCL
#define  pinSDA                1                    //PC1 SDA
//爲保險起見,最好在SCL/SDA接上1~10K的外部上拉電阻到VCC。

#define FREQ 4 //FREQ爲系統振盪週期(以MHZ爲單位,因爲現在用的是外部4M晶振,所以FREQ=4)
#define fSCL                100000                //TWI時鐘爲100KHz
//預分頻係數=1(TWPS=0)
#if F_CPU < fSCL*36
  
#define TWBR_SET             10;                    //TWBR必須大於等於10
#else
  
#define TWBR_SET          (F_CPU/fSCL-16)/2;    //計算TWBR值
#endif

#define TW_ACT                (1<<TWINT)|(1<<TWEN)|(1<<TWIE)
//TWCR只能IN/OUT,直接賦值比邏輯運算(|= &=)更節省空間

#define SLA_24CXX            0xA0                //24Cxx系列的廠商器件地址(高四位)
#define ADDR_24C02            0x00
// AT24C02的地址線A2/1/0全部接地,SLAW=0xA0+0x00<<1+0x00,SLAR=0xA0+0x00<<1+0x01

//TWI_操作狀態
#define TW_BUSY                0
#define TW_OK                1
#define TW_FAIL                2
//TWI_讀寫命令狀態
#define OP_BUSY                0
#define OP_RUN                1


//TWI讀寫操作公共步驟
#define ST_FAIL                0    //出錯狀態
#define ST_START            1    //START狀態檢查
#define ST_SLAW                2    //SLAW狀態檢查
#define ST_WADDR            3    //ADDR狀態檢查
//TWI讀操作步驟
#define ST_RESTART            4    //RESTART狀態檢查
#define ST_SLAR                5    //SLAR狀態檢查
#define ST_RDATA            6    //讀取數據狀態檢查,循環n字節
//TWI寫操作步驟
#define ST_WDATA            7    //寫數據狀態檢查,循環n字節

#define FAIL_MAX            20    //重試次數最大值


//定義全局變量
unsigned char ORGDATA[8]=
    
{65,66,67,68,69,70,71,72};    //原始數據:64:A; 66:B; 67:c……
unsigned char CMPDATA[8];                        //比較數據
unsigned char BUFFER[256];                        //緩衝區,可以裝載整個AC24C02的數據

struct str_TWI                                    //TWI數據結構
{
    
volatile unsigned char    STATUS;                //TWI_操作狀態
    unsigned char    SLA;                        //從設備的器件地址
    unsigned int    ADDR;                        //從設備的數據地址
    unsigned char    *pBUF;                        //數據緩衝區指針
    unsigned int    DATALEN;                    //數據長度
    unsigned char    STATE;                        //TWI讀寫操作步驟
    unsigned char    FAILCNT;                    //失敗重試次數
}
;

struct str_TWI strTWI;                            //TWI的數據結構變量

//仿真時在watch窗口,監控這些全局變量。


//AT24C02的讀寫函數(包括隨機讀,連續讀,字節寫,頁寫)
//根據sla的最低位決定(由中斷程序中判斷)
//bit0=1 TW_READ  讀
//bit0=0 TW_WRITE 寫
//  sla            器件地址(不能搞錯)
//    addr        EEPROM地址(0~1023)
//    *ptr        讀寫數據緩衝區
//    len            讀數據長度(1~1024),寫數據長度(1 or 8 or 16)
//  返回值        是否能執行當前操作


//此函數參考《AVR單片機GCC程序設計》P72和P120
void DelayMs(unsigned int t)
{
    unsigned 
int i;
    
for(i=0;i<t;i++)
        _delay_loop_2(FREQ
*4-1);
}


unsigned 
char TWI_RW(unsigned char sla,unsigned int addr,unsigned char *ptr,unsigned int len)
{
    unsigned 
char i;
    
if (strTWI.STATUS==TW_BUSY)
    
{//TWI忙,不能進行操作
        return OP_BUSY;
    }

    strTWI.STATUS
=TW_BUSY;
    i
=(addr>>8)<<1;
    i
&=0x06;                                    //考慮了24C04/08的EEPROM地址高位放在SLA裏面
    strTWI.SLA=sla+i;
    strTWI.ADDR
=addr;
    strTWI.pBUF
=ptr;
    strTWI.DATALEN
=len;
    strTWI.STATE
=ST_START;
    strTWI.FAILCNT
=0;
    TWCR
=(1<<TWSTA)|TW_ACT;                        //啓動start信號
    return OP_RUN;
}


/*
TWI中斷函數 
    這個函數流程只是考慮了器件地址後有一個字節數據(命令)地址的IIC器件
    (大部分IIC接口器件都是這種類型,常見的例如AT24C01/02/04/08/16,DS1307,DS1721等)
    對於有兩個字節數據地址的IIC器件(例如AT24C32/64/128/256等大容量EEPROM),請稍作改動
 
//根據strTWI.SLA的最低位決定 
//bit0=1 TW_READ  讀
//bit0=0 TW_WRITE 寫

    雖然中斷服務程序很長,但每次只執行一個 case,所以耗時並不長。
*/

ISR(TWI_vect)
{//IIC中斷
    unsigned char action,state,status;
    action
=strTWI.SLA&TW_READ;                    //取操作模式
    state=strTWI.STATE;
    status
=TWSR&0xF8;                            //屏蔽預分頻位
    if ((status>=0x60)||(status==0x00))
    
{//總線錯誤或從機模式引發的中斷,不予處理
        return;
    }

    
switch(state)
    
{
    
case ST_START:    //START狀態檢查
        if(status==TW_START)
        
{//發送start信號成功
            TWDR=strTWI.SLA&0xFE;                //發送器件地址寫SLAW
            TWCR=TW_ACT;                         //觸發下一步動作,同時清start發送標誌
        }

        
else
        
{//發送start信號出錯
            state=ST_FAIL;
        }

        
break;
    
case ST_SLAW:    //SLAW狀態檢查
        if(status==TW_MT_SLA_ACK)
        
{//發送器件地址成功
            TWDR=strTWI.ADDR;                    //發送eeprom地址
            TWCR=TW_ACT;                         //觸發下一步動作
        }

        
else
        
{//發送器件地址出錯
            state=ST_FAIL;
        }

        
break;
    
case ST_WADDR:    //ADDR狀態檢查
        if(status==TW_MT_DATA_ACK)
        
{//發送eeprom地址成功
            if (action==TW_READ)
            
{//讀操作模式
                TWCR=(1<<TWSTA)|TW_ACT;            //發送restart信號,下一步將跳到RESTART分支
            }

            
else
            
{//寫操作模式
                TWDR=*strTWI.pBUF++;             //寫第一個字節
                strTWI.DATALEN--;
                state
=ST_WDATA-1;                //下一步將跳到WDATA分支
                TWCR=TW_ACT;                     //觸發下一步動作
            }

        }

        
else
        
{//發送eeprom地址出錯
            state=ST_FAIL;
        }

        
break;
    
case ST_RESTART:    //RESTART狀態檢查,只有讀操作模式才能跳到這裏
        if(status==TW_REP_START)
        
{//發送restart信號成功
            TWDR=strTWI.SLA|TW_READ;            //發器件地址讀SLAR(原來的程序只是TWDR=strTWI.SLA,不得)
            TWCR=TW_ACT;                         //觸發下一步動作,同時清start發送標誌
        }

        
else
        
{//重發start信號出錯
            state=ST_FAIL;
        }

        
break;
    
case ST_SLAR:    //SLAR狀態檢查,只有讀操作模式才能跳到這裏
        if(status==TW_MR_SLA_ACK)
        
{//發送器件地址成功
            if (strTWI.DATALEN--)
            
{//多個數據
                TWCR=(1<<TWEA)|TW_ACT;            //設定ACK,觸發下一步動作
            }

            
else
            
{//只有一個數據
                TWCR=TW_ACT;                    //設定NAK,觸發下一步動作
            }

        }

        
else
        
{//發送器件地址出錯
            state=ST_FAIL;
        }

        
break;
    
case ST_RDATA:    //讀取數據狀態檢查,只有讀操作模式才能跳到這裏
        state--;                                //循環,直到讀完指定長度數據
        if(status==TW_MR_DATA_ACK)
        
{//讀取數據成功,但不是最後一個數據
            *strTWI.pBUF++=TWDR;
            
if (strTWI.DATALEN--)
            
{//還有多個數據
                TWCR=(1<<TWEA)|TW_ACT;            //設定ACK,觸發下一步動作
            }

            
else
            
{//準備讀最後一個數據
                TWCR=TW_ACT;                    //設定NAK,觸發下一步動作
            }

        }

        
else if(status==TW_MR_DATA_NACK)
        
{//已經讀完最後一個數據
            *strTWI.pBUF++=TWDR;
            TWCR
=(1<<TWSTO)|TW_ACT;                //發送停止信號,不會再產生中斷了
            strTWI.STATUS=TW_OK;
        }

        
else
        
{//讀取數據出錯
            state=ST_FAIL;
        }

        
break;
    
case ST_WDATA:    //寫數據狀態檢查,只有寫操作模式才能跳到這裏
        state--;                                //循環,直到寫完指定長度數據
        if(status==TW_MT_DATA_ACK)
        
{//寫數據成功
            if (strTWI.DATALEN)
            
{//還要寫
                TWDR=*strTWI.pBUF++;
                strTWI.DATALEN
--;
                TWCR
=TW_ACT;                     //觸發下一步動作
            }

            
else
            
{//寫夠了
                TWCR=(1<<TWSTO)|TW_ACT;            //發送停止信號,不會再產生中斷了
                strTWI.STATUS=TW_OK;
                
//啓動寫命令後需要10ms(最大)的編程時間才能真正的把數據記錄下來
                
//編程期間器件不響應任何命令
            }

        }

        
else
        
{//寫數據失敗
            state=ST_FAIL;
        }

        
break;
    
default:
        
//錯誤狀態
        state=ST_FAIL;
        
break;
    }


    
if (state==ST_FAIL)
    
{//錯誤處理
        strTWI.FAILCNT++;
        
if (strTWI.FAILCNT<FAIL_MAX)
        
{//重試次數未超出最大值,
            TWCR=(1<<TWSTA)|TW_ACT;                //發生錯誤,啓動start信號
        }

        
else
        
{//否則停止
            TWCR=(1<<TWSTO)|TW_ACT;                //發送停止信號,不會再產生中斷了
            strTWI.STATUS=TW_FAIL;
        }

    }

    state
++;
    strTWI.STATE
=state;                            //保存狀態
}




int main(void)
{
    unsigned 
char i;
    unsigned 
int m,n,k,t;
    
//上電默認DDRx=0x00,PORTx=0x00 輸入,無上拉電阻
    PORTA=0xFF;                                    //不用的管腳使能內部上拉電阻。
    PORTB=0xFF;
    PORTC
=0xFF;                                    //SCL,SDA使能了內部的10K上拉電阻
    
    
//串口初始化
    DDRD  =(1<<PIN_TXD);        //TXD爲輸出
    PORTD =0xFF;    
    init_USART();

    
//TWI初始化
    TWSR=0x00;                                    //預分頻=4^0=1
    TWBR=TWBR_SET;
    TWAR
=0x00;                                    //主機模式,該地址無效
    TWCR=0x00;                                    //關閉TWI模塊
    sei();                                        //使能全局中斷
    put_s("Hello!");
    put_s(
"這是一個簡單TWI程序");
    put_s(
"請你仔細對照數據");
    
    strTWI.STATUS
=TW_OK;

    TWI_RW(SLA_24CXX
+(ADDR_24C02<<1)+TW_WRITE,0x10,&ORGDATA[0],8);
    
//從0x10地址開始寫入8個字節數據
    while(strTWI.STATUS==TW_BUSY);                //等待操作完成
    if (strTWI.STATUS==TW_FAIL)
    
{
        put_s(
"TWI寫操作FAIL!");
    }

    DelayMs(
10);                                //延時等待編程完成
    put_s("TWI寫操作succeed!");
    
    
while(1)
    
{
        
//從0x10地址開始讀出8個字節數據放入CMPDATA數組中
        i=TWI_RW(SLA_24CXX+(ADDR_24C02<<1)+TW_READ,0x10,&CMPDATA[0],8);
        
while(strTWI.STATUS==TW_BUSY);            //等待操作完成
        
// 如果不加等待,則需要檢測返回值i才能知道當前操作是否執行了
        
// 0 OP_BUSY 之前的操作沒完成,沒執行當前操作
        
// 1 OP_RUN  當前操作執行中
        if (strTWI.STATUS==TW_FAIL)
        
{
            put_s(
"TWI讀操作FAIL!");
        }

        
        
//讀取成功,對比ORGDATA和CMPDATA的數據
        put_s("原來ORGDATA的數據:");
        
for(m=0;m<8;m++)
        
{
            put_c(ORGDATA[m]);
            put_c(
' ');
        }

        put_c(
0x0D);
        put_c(
0x0A);
        put_s(
"新的CMPDATA的數據:");
        
for(n=0;n<8;n++)
        
{
            put_c(CMPDATA[n]);
            put_c(
' ');
        }

        put_c(
0x0D);
        put_c(
0x0A);
        
        
//讀取整個AC24C02的數據
        i=TWI_RW(SLA_24CXX+(ADDR_24C02<<1)+TW_READ,0x00,&BUFFER[0],256);
        
//從0x00地址開始讀出256個字節數據(整個ATC24C02)
        while(strTWI.STATUS==TW_BUSY);            //等待操作完成
        
        put_s(
"整個AC24C02的數據:");
        
for(k=0,t=1;k<256;k++,t++)
        
{
            
if(BUFFER[k]>=65 && BUFFER[k]<=72)
                put_c(BUFFER[k]);
            
else
                put_c(
48);
            put_c(
' ');
            
if(t==16)
            
{
                put_c(
0x0D);
                put_c(
0x0A);
                t
=0;
            }

        }

        put_c(
0x0D);
        put_c(
0x0A);
        
        DelayMs(
50000);    //延時,好看數據
    }
;
}

 

//Makefile,主要的幾項,只是針對我這裏的程序,要靈活運用哦

MCU 
= atmega16

F_CPU 
= 4000000

TARGET 
= main

SRC 
= TWI_main.c  usart.c  //多文件編譯纔會用到這一項,可以參考這個帖子:

http:
//www.mcublog.com/blog/user1/4266/archives/2006/6145.html

當然了,看一下協議本身是很重要的,也是一定的.這個例子不錯,值得一看.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章