新聞中心

        EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > 關(guān)于MSP硬件I2C講解

        關(guān)于MSP硬件I2C講解

        作者: 時間:2016-11-21 來源:網(wǎng)絡(luò) 收藏

        0.前言

        對于大多數(shù)單片機來說,I2C成了一個老大難問題。從51時代開始,軟件模擬I2C成了主流,甚至到ARMCortex M3大行其道的今天,軟件模擬I2C依然是使用最廣的方法。雖然軟件模擬可以解決所有的問題,但是總感覺沒有充分發(fā)揮MCU內(nèi)部的硬件資源。查閱了所有關(guān)于MSP430F5系列的圖書,沒有關(guān)于硬件I2C的應(yīng)用代碼,自己通過調(diào)試摸索,把經(jīng)驗總結(jié)之后和大家分享,希望大家喜歡。同時,I2C的使用可以分為等待法和中斷法,從理解的角度來說等待法思路清晰易于上手,從功耗的角度出發(fā),中斷法可以靈活的進入低功耗模式,但是不易理解。本文先從等待法入手。

        本文引用地址:http://www.104case.com/article/201611/319192.htm

        MSP430F5系列的硬件I2C使用大致會有以下問題:

        【I2C地址設(shè)定】一般情況下I2C的7位地址被寫成了8位長度,最低位無效。例如AT24C02的I2C地址為0xA0,其實真正的7位地址為0x50。而MSP430正是需要填入這7位地址0x50。

        【I2C停止位發(fā)送】在I2C讀操作過程中,讀取最后一個字節(jié)之后MCU應(yīng)向從機發(fā)送無應(yīng)答,MSP430F5系列的MCU發(fā)送無應(yīng)答的操作將自動完成,這就以為在讀取最后一個字節(jié)內(nèi)容時,應(yīng)先操作停止位相關(guān)寄存器。

        【I2C起始位發(fā)送】如果仔細分析MSP430F5參考手冊,將會發(fā)現(xiàn)讀操作和寫操作發(fā)送I2C起始位時略有不同。寫操作時需要先向TXBUF中寫入數(shù)據(jù),之后才可以等待TXSTT標志位變?yōu)?,而讀操作和寫操作稍有不同。

        【AT24C02操作時序圖】

        1.初始化設(shè)置

        1.1代碼實現(xiàn)

        1. voiducb0_config(void)
        2. {
        3. P3SEL&=~BIT2;//P3.2@UCB0SCL
        4. P3DIR|=BIT2;
        5. P3OUT|=BIT2;
        6. //輸出9個時鐘以恢復(fù)I2C總線狀態(tài)
        7. for(uint8_ti=0;i<9;i++)
        8. {
        9. P3OUT|=BIT2;
        10. __delay_cycles(8000);
        11. P3OUT&=~BIT2;
        12. __delay_cycles(8000);
        13. }
        14. P3SEL|=(BIT1+BIT2);//P3.1@UCB0SDAP3.2@UCB0SCL
        15. //P3.1@ISP.1P3.2@ISP.5
        16. UCB0CTL1|=UCSWRST;
        17. UCB0CTL0=UCMST+UCMODE_3+UCSYNC;//I2C主機模式
        18. UCB0CTL1|=UCSSEL_2;//選擇SMCLK
        19. UCB0BR0=40;
        20. UCB0BR1=0;
        21. UCB0CTL0&=~UCSLA10;//7位地址模式
        22. UCB0I2CSA=EEPROM_ADDRESS;//EEPROM地址
        23. UCB0CTL1&=~UCSWRST;
        24. }

        1.2代碼分析

        I2C從設(shè)備的地址一般有以下通俗說法——7位地址,寫地址(寫控制字)和讀地址(讀控制字)。1個I2C通信的控制字節(jié)(I2C啟動之后傳送的第一個字節(jié))由7位I2C地址和1位讀寫標志位組成,7位I2C地址即7位地址,若讀寫標志位為讀標志(讀寫標志位置位)加上7位I2C地址便組成了讀地址(讀控制字),若讀寫標志位為寫標志(讀寫標志位清零)加上7位地址便組成了寫地址(寫控制字)。例如AT24C02的I2C7位地址為0x50,讀地址(讀控制字)為0xA1,寫地址(寫控制字)為0xA1。

        在MSP430F5系列中,I2CSA地址寄存器應(yīng)寫入7位地址,參照上面的例子應(yīng)寫入0X50。至于I2C讀寫位的控制由CTL1寄存器完成,用戶無需干預(yù)。

        在I2C設(shè)置開始之前,可以先通過SCL端口發(fā)送9個時鐘信號,該時鐘信號可以是I2C從機芯片從一種錯誤的通信狀態(tài)恢復(fù),雖然這9個時鐘信號不起眼但是對于調(diào)試過程來說非常有用。例如在調(diào)試過程中,錯誤的發(fā)送了停止位,若再次啟動調(diào)試則I2C從設(shè)備仍處于一種錯誤的狀態(tài),這9個時鐘信號可以把I2C從設(shè)備從錯誤的狀態(tài)“拉”回來。

        2.寫單個字節(jié)

        向I2C從設(shè)備寫入單個字節(jié)應(yīng)該是最為簡單的一個操作,因為所有的控制權(quán)都在主機手中。寫單個字節(jié)實際包括了2個重要部分,一個便是寫寄存器地址,另一個便是寫寄存器內(nèi)容。對于AT24C02而言,存儲內(nèi)容的字節(jié)長度為一個字節(jié),而對于容量更大的EEPROM而言,存儲地址可為兩個字節(jié)。

        2.1 代碼實現(xiàn)

        1. uint8_teeprom_writebyte(uint8_tword_addr,uint8_tword_value)
        2. {
        3. while(UCB0CTL1&UCTXSTP);
        4. UCB0CTL1|=UCTR;//寫模式
        5. UCB0CTL1|=UCTXSTT;//發(fā)送啟動位
        6. UCB0TXBUF=word_addr;//發(fā)送字節(jié)地址
        7. //等待UCTXIFG=1與UCTXSTT=0同時變化等待一個標志位即可
        8. while(!(UCB0IFG&UCTXIFG))
        9. {
        10. if(UCB0IFG&UCNACKIFG)//若無應(yīng)答UCNACKIFG=1
        11. {
        12. return1;
        13. }
        14. }
        15. UCB0TXBUF=word_value;//發(fā)送字節(jié)內(nèi)容
        16. while(!(UCB0IFG&UCTXIFG));//等待UCTXIFG=1
        17. UCB0CTL1|=UCTXSTP;
        18. while(UCB0CTL1&UCTXSTP);//等待發(fā)送完成
        19. return0;
        20. }

        2.2 代碼分析

        關(guān)于代碼出口的說明,關(guān)于I2C的讀寫函數(shù),若返回值為0說明所有的操作正常,若返回值為非0說明操作有誤,例如1代表從機無應(yīng)答。這種組合方式可能與各位的編程習慣有出入,一般認為返回1表示操作成功,而返回0表示操作失敗。這種方式的問題便是無法有效的表達錯誤原因,因為“0”只有一個,而非“0”卻有很多。

        寫單個字節(jié)可以劃分為——從機寫地址發(fā)送、寄存器地址發(fā)送、寄存器內(nèi)容發(fā)送。寄存器地址的發(fā)送由MSP430自動完成,這和軟件模擬的操作有所區(qū)別。請勿發(fā)送I2C從機地址,若操作AT24C02發(fā)送需要寫入的存儲字節(jié)的首地址即可。

        在單字節(jié)和多字節(jié)寫操作過程中,尤其要注意UCTXSTT標志位的變化位置。UCTXSTT標志位會在從機接收完寫控制字節(jié)或讀控制字節(jié)之后變化,但是在寫控制字節(jié)發(fā)送之后,必須先填充TXBUF,再嘗試等待STT標志位復(fù)位,此時STT標志位和TXIFG標志位會同時變化。若從機沒有應(yīng)答,那么NACK標志位也會發(fā)生變化。再次強調(diào)需要先填充TXBUF,在等待STT標志位復(fù)位。以下代碼將導(dǎo)致程序一直停留在while(UCB0IFG & UCTXSTT)處,具體的原因可查看MSP430參考手冊。

        1. while(UCB0CTL1&UCTXSTP);
        2. UCB0CTL1|=UCTR;//寫模式
        3. UCB0CTL1|=UCTXSTT;//發(fā)送啟動位
        4. //等待UCTXSTT=0同時變化,但是很遺憾該變化不會發(fā)送
        5. while(UCB0IFG&UCTXSTT);
        6. UCB0TXBUF=word_addr;//發(fā)送字節(jié)地址

        3.寫多個字節(jié)

        3.1代碼實現(xiàn)

        1. uint8_teeprom_writepage(uint8_tword_addr,uint8_t*pword_buf,uint8_tlen)
        2. {
        3. while(UCB0CTL1&UCTXSTP);
        4. UCB0CTL1|=UCTR;//寫模式
        5. UCB0CTL1|=UCTXSTT;//發(fā)送啟動位
        6. UCB0TXBUF=word_addr;//發(fā)送字節(jié)地址
        7. //等待UCTXIFG=1與UCTXSTT=0同時變化等待一個標志位即可
        8. while(!(UCB0IFG&UCTXIFG))
        9. {
        10. if(UCB0IFG&UCNACKIFG)//若無應(yīng)答UCNACKIFG=1
        11. {
        12. return1;
        13. }
        14. }
        15. for(uint8_ti=0;i
        16. {
        17. UCB0TXBUF=*pword_buf++;//發(fā)送寄存器內(nèi)容
        18. while(!(UCB0IFG&UCTXIFG));//等待UCTXIFG=1
        19. }
        20. UCB0CTL1|=UCTXSTP;
        21. while(UCB0CTL1&UCTXSTP);//等待發(fā)送完成
        22. return0;
        23. }

        3.2 代碼分析

        多字節(jié)寫函數(shù)和單字節(jié)寫函數(shù)相似,不做過多的解釋。

        4.讀單個字節(jié)

        單字節(jié)讀函數(shù)是4中讀寫函數(shù)中最為復(fù)雜的,復(fù)雜的原因在于讀最后一個字節(jié)之前就需要操作UCTXSTP標志位。

        4.1 代碼實現(xiàn)

        1. uint8_teeprom_readbyte(uint8_tword_addr,uint8_t*pword_value)
        2. {
        3. UCB0CTL1|=UCTR;//寫模式
        4. UCB0CTL1|=UCTXSTT;//發(fā)送啟動位和寫控制字節(jié)
        5. UCB0TXBUF=word_addr;//發(fā)送字節(jié)地址,必須要先填充TXBUF
        6. //等待UCTXIFG=1與UCTXSTT=0同時變化等待一個標志位即可
        7. while(!(UCB0IFG&UCTXIFG))
        8. {
        9. if(UCB0IFG&UCNACKIFG)//若無應(yīng)答UCNACKIFG=1
        10. {
        11. return1;
        12. }
        13. }
        14. UCB0CTL1&=~UCTR;//讀模式
        15. UCB0CTL1|=UCTXSTT;//發(fā)送啟動位和讀控制字節(jié)
        16. while(UCB0CTL1&UCTXSTT);//等待UCTXSTT=0
        17. //若無應(yīng)答UCNACKIFG=1
        18. UCB0CTL1|=UCTXSTP;//先發(fā)送停止位
        19. while(!(UCB0IFG&UCRXIFG));//讀取字節(jié)內(nèi)容
        20. *pword_value=UCB0RXBUF;//讀取BUF寄存器在發(fā)送停止位之后
        21. while(UCB0CTL1&UCTXSTP);
        22. return0;
        23. }

        4.2代碼分析

        這段代碼給人一個錯覺,MSP430先發(fā)送了停止位,然后再讀取了一個字節(jié)內(nèi)容。其實實際情況并不是這樣的。I2C讀操作時,主機讀取最后一個字節(jié)內(nèi)容之后,應(yīng)向從機發(fā)送無應(yīng)答NACK(無應(yīng)答區(qū)別于應(yīng)答),之后主機發(fā)送停止位。MSP430為了完成這一組合動作,要求用戶提前操作UCTXSTP標志位,在讀取RXBUF之后做出發(fā)送NACK和I2C停止位的“組合動作”。

        1. while(!(UCB0IFG&UCRXIFG));
        2. *pword_value=UCB0RXBUF;//讀取BUF寄存器在發(fā)送停止位之后
        3. UCB0CTL1|=UCTXSTP;//發(fā)送停止位

        以上代碼可能導(dǎo)致后續(xù)的I2C操作無法進行。

        5.讀多個字節(jié)

        5.1代碼實現(xiàn)

        1. uint8_teeprom_readpage(uint8_tword_addr,uint8_t*pword_buf,uint8_tlen)
        2. {
        3. while(UCB0CTL1&UCTXSTP);
        4. UCB0CTL1|=UCTR;//寫模式
        5. UCB0CTL1|=UCTXSTT;//發(fā)送啟動位和寫控制字節(jié)
        6. UCB0TXBUF=word_addr;//發(fā)送字節(jié)地址
        7. //等待UCTXIFG=1與UCTXSTT=0同時變化等待一個標志位即可
        8. while(!(UCB0IFG&UCTXIFG))
        9. {
        10. if(UCB0IFG&UCNACKIFG)//若無應(yīng)答UCNACKIFG=1
        11. {
        12. return1;
        13. }
        14. }
        15. UCB0CTL1&=~UCTR;//讀模式
        16. UCB0CTL1|=UCTXSTT;//發(fā)送啟動位和讀控制字節(jié)
        17. while(UCB0CTL1&UCTXSTT);//等待UCTXSTT=0
        18. //若無應(yīng)答UCNACKIFG=1
        19. for(uint8_ti=0;i
        20. {
        21. while(!(UCB0IFG&UCRXIFG));//讀取字節(jié)內(nèi)容,不包括最后一個字節(jié)內(nèi)容
        22. *pword_buf++=UCB0RXBUF;
        23. }
        24. UCB0CTL1|=UCTXSTP;//在接收最后一個字節(jié)之前發(fā)送停止位
        25. while(!(UCB0IFG&UCRXIFG));//讀取最后一個字節(jié)內(nèi)容
        26. *pword_buf=UCB0RXBUF;
        27. while(UCB0CTL1&UCTXSTP);
        28. return0;
        29. }

        5.2代碼分析

        讀單個字節(jié)和寫單個字節(jié)相似。唯一需要注意的是,寫操作需要先填充TXBUF,而讀操作不存在這個問題。試想一下,I2C寫操作時必定會向I2C從機寫入一個字節(jié)內(nèi)容,所以先填充TXBUF也是合情合理的事情,填充TXBUF之后MSP430會進行一連串的動作——發(fā)送I2C起始位、I2C讀控制器和寫入從機的第一個字節(jié)。

        6 單元測試

        單元測試分為兩個部分。單字節(jié)寫函數(shù)和單字節(jié)讀函數(shù)分為一組,先使用單字節(jié)邪惡函數(shù)向某地址寫入某內(nèi)容,在使用單字節(jié)讀函數(shù)讀出某內(nèi)容,如果寫入的參數(shù)和讀出的內(nèi)容相同,則測試通過。多字節(jié)寫函數(shù)和多字節(jié)度函數(shù)分為一組,測試過程相似,不同的是寫入的內(nèi)容從一個變?yōu)榱诉B續(xù)8個。請注意AT24C02的頁大小為8,若從頁首地址開始,最大的寫字節(jié)個數(shù)為8。

        另外,EEPROM寫操作之后需要有10ms的延時,否則將無法進行寫操作和讀操作。具體請查看AT24C02數(shù)據(jù)手冊。

        6.1 測試代碼

        1. voideeprom_config()
        2. {
        3. #ifDEBUF_EEPROM_I2C
        4. uint8_ttest_byte1=0x0B;
        5. uint8_ttest_byte2=0x01;
        6. /*
        7. step1向地址0x00寫入某個值,例如0x0B
        8. 然后讀出地址0x00結(jié)果,判斷該值是否為0x0B
        9. */
        10. eeprom_writebyte(0x00,test_byte1);
        11. delay_ms(10);
        12. eeprom_readbyte(0x00,&test_byte2);
        13. assert_param(test_byte1==test_byte2);
        14. if(test_byte1==test_byte2)
        15. {
        16. printf("ByteReadandByteWriteTestPassrn");
        17. }
        18. /*
        19. step2以地址0x08作為起始地址,連續(xù)寫入8個字節(jié)數(shù)據(jù)
        20. 再連續(xù)從該起始地址讀取8字節(jié)內(nèi)容,比較寫入和讀出字節(jié)內(nèi)容
        21. 成功的條件為寫入和讀取字節(jié)內(nèi)容相同
        22. */
        23. uint8_ttest_buf1[8]={1,2,3,4,5,6,7,8};
        24. uint8_ttest_buf2[8]={0,0,0,0,0,0,0,0};
        25. eeprom_writepage(0x08,test_buf1,8);
        26. delay_ms(10);
        27. eeprom_readpage(0x08,test_buf2,8);
        28. assert_param(memcmp(test_buf1,test_buf2,8)==0);
        29. if(!memcmp(test_buf1,test_buf2,8))
        30. {
        31. printf("PageReadandPageWriteTestPass!rn");
        32. }
        33. #endif
        34. }

        6.2 測試結(jié)果



        關(guān)鍵詞: MSP硬件I2

        評論


        技術(shù)專區(qū)

        關(guān)閉
        主站蜘蛛池模板: 资兴市| 曲麻莱县| 宜都市| 克拉玛依市| 葵青区| 莎车县| 稻城县| 新建县| 北宁市| 元谋县| 嵊泗县| 富平县| 庐江县| 边坝县| 赣榆县| 玛多县| 龙江县| 临武县| 股票| 张家港市| 平度市| 青神县| 墨玉县| 图木舒克市| 五河县| 广水市| 湾仔区| 长治市| 密山市| 呼图壁县| 徐州市| 盐山县| 紫阳县| 临武县| 泉州市| 称多县| 古田县| 措美县| 芜湖县| 金平| 吉安县|