1. 程式人生 > >STM32 IIC的學習

STM32 IIC的學習

IIC
1. IIC的定義及作用?與SPI,USART有何不同?
STM32上有很多的通訊介面,主要用來連線MCU與IC(如下面將要介紹的EEPROM–AT24C02C),及IC與IC之間的通訊。因為對通訊的速度及功能要求不同,主要分為IIC,SPI,USART.
1).IIC (Inter-Integrated Circuit 意為IC之間匯流排):兩線式序列匯流排,是由資料SDA線和時鐘SCL線構成的序列匯流排,可傳送和接收資料。IIC是多主控匯流排,所以任何一個裝置都能像主控器一樣工作,並控制匯流排。 總線上每一個裝置都有一個獨一無二的地址,根據裝置它們自己的能力,它們可以作為發射器或接收器工作。非常適合在器件之間進行近距離、非經常性的資料通訊。IIC不可以實現全雙工(即非同步)。
標準模式 高達 100kHz
快速模式 高達 400kHz
超快速模式 高達 1 MHz
2).SPI(Serial Peripheral Interface:序列外設介面) :主從裝置間既可以實現同步通訊也可以非同步通訊,SPI匯流排由三條訊號線組成:序列時鐘(SCLK)、序列資料輸出(SDO)、序列資料輸入(SDI),當有多個從裝置時,還可以增加一條從裝置(SS)選擇線。SPI匯流排可以實現 多個SPI裝置互相連線。提供SPI序列時鐘的SPI裝置為SPI主機或主裝置(Master),其他裝置為SPI從機或從裝置(Slave)。
速度高達18M bit/s
如果用通用IO口模擬SPI匯流排,必須要有一個輸出口(SDO)或者一個輸入口(SDI),另一個口則視實現的裝置型別而定,如果要實現主從裝置,則需輸入輸出口,若只實現主裝置,則需輸出口即可,若只實現從裝置,則只需輸入口即可。資料(輸入或者輸出)在時鐘上沿或下沿時改變,在緊接著的下沿或上沿被讀取。這樣至少8次時鐘訊號的改變(上沿和下沿為一次),就可以完成8位資料的傳輸。SCL訊號線是由主裝置控制,從裝置不能控制SCL訊號線,所以至少有一個主控裝置。這樣傳輸的特點:與普通的序列通訊不同,普通的序列通訊一次連續傳送至少8位資料,而SPI允許資料一位一位的傳送,甚至允許暫停,因為SCK時鐘線由主控裝置控制,當沒有時鐘跳變時,從裝置不採集或傳送資料。也就是說,主裝置通過對SCK時鐘線的控制可以完成對通訊的控制。
SPI 通訊介面有:SS( Slave Select),SCK( Serial Clock),MOSI (Master Output, Slave Input),MISO (Master Input, Slave Output)。
3).USART(Universal Asynchronous Receiver Transmitter:通用非同步收發器):是用於控制計算機與序列裝置的晶片, USART匯流排是非同步串列埠,因此一般比前兩種同步串列埠的結構要複雜很多。硬體上有兩根線,一根用於傳送,一根用於接收。有一點要注意的是,它提供了RS-232C資料終端裝置介面,這樣計算機就可以和調變解調器或其它使用RS-232C介面的序列裝置通訊了。目前市場上有USB轉USART(如CP21xx ),也有USB轉RS-232C裝置(如PL230x)。其中USART 是TTL 電平3.3v,RS-232C是232電平5v.
顯然,如果用通用IO口模擬UART匯流排,則需一個輸入口,一個輸出口。
不同之處大概就是上面所示,SPI和UART可以實現全雙工,但I2C不行。IIC為兩條線(時鐘線SCL 和資料線SDA),SPI 為至少3條線,可同步接收和傳送,不過只有一條時鐘線,如果SPI裝置多的話,就還可加入CS片選線選擇從機裝置。USART

  1. 硬體IIC和模擬IIC的區別?

    硬體IIC指的是MCU上面專有的IIC介面(SDA資料線和SCL時鐘線)及在APB1上有IIC的指令暫存器,可以直接配置IIC指令暫存器及連線SDA線及SCL線至需要進行通訊的裝置(IC或者EEPROM)進行讀寫通訊。(在STM32的資料手冊上面寫的需要配置的就是硬體IIC)
    如下圖所示:
    IIIC硬體配置
    模擬IIC與上面的硬體IIC有所不同,指通過通用的GPIO口進行配置與相應的IIC裝置(如EEPROM—AT24C02)進行通訊,需要配置兩個通用IO口,一個作為SDA線,一個作為SCL線與IC進行通訊,如同下面所示:
    模擬IIC配置連線
    上面圖片是模擬IIC使用GPIO口進行配置,在進行IIC的配置前,首先要配置GPIO口。

  2. 模擬IIC的配置&時序圖& 協議

    首先要配置GPIO口,現在以電路圖的連線為主,PF6設定為SCL & PG7設定為SDA,則 首先需要使能GPIOF,PF6/SCL因為是時鐘線,所以只要配置成輸出就好了。
    PF7因為是資料GPIO口,資料可以從MCU傳送到IC(AT24C02),也可以接收IC(AT24C02)傳送的資料。所以需要傳送和接收資料,把PF7/SDA設定為輸入輸出口。
    1)IIC匯流排協議的時序圖:
    IIC匯流排協議時序圖
    1)).看上面的時序圖,每個位元組必須保證是8位長度。資料傳送時,先傳送最高位(MSB),每一個被傳送的位元組後面都必須跟隨一個應答位(即一幀共9位)。CPU向受控單元發出一個訊號後,等待受控單元發出一個應答訊號,CPU接收到應答訊號後,根據實際情況作出是否繼續傳遞訊號的判斷,若未收到應答訊號,則判斷為受控單元故障。
    2)).IIC匯流排進行資料傳送時,時鐘訊號為高電平期間,資料線上的資料必須保持穩定,只有在時鐘線上的訊號為低電平期間,資料線上的高電平或低電平狀態才允許變化。
    3)).主控器向被控器傳送的資訊種類有:空閒狀態,初始訊號,結束訊號,7位地址碼,讀/寫控制位,10位地址碼,資料位元組,應答訊號,脈衝時鐘。
    被控器向主控器傳送的資訊種類有:應答訊號,資料位元組,時鐘低電平。
    4)).空閒狀態: 時鐘線SCL與資料線SDA均為高電平訊號。
    初始訊號:在空閒狀態時,SCL與SDA均為高電平的狀態下,SDA資料線由高電平變為低電平。
    結束訊號:在SDA資料線和SCL均為低電平的狀態下,SDA資料線由低電平變為高電平。
    讀寫控制位:IIC匯流排進行資料傳送時,時鐘訊號為高電平期間,資料線上的資料必須保持穩定,只有在時鐘線上的訊號為低電平期間,資料線上的高電平或低電平狀態才允許變化。
    應答訊號:在傳送完8bit 位後,在第九位時鐘的時候,由被控器傳送一個應答位,主控器接收發送的應答位。當應答位為0時,表示應答。當應答位為1時,表示無應答。
    2).EEPROM—AT24C02的配置
    1)).目前我寫的IC/受控器為AT24C02C,該晶片的總容量為256個位元組(2kbit),IIC總線上啟動訊號後,需要傳送一個8位裝置地址,來使能IIC總線上與裝置地址匹配的裝置,匹配的裝置會有一個“0”(ACK)應答,這樣才能進行讀寫操作。一般所有的EEPROM裝置地址前四位是固定的1010,接下來的三位是裝置地址(不同容量規則不同),然後最後一位數是讀寫使能位。AT24C02的裝置地址不需要裝置地址,所以為000,所以這個晶片的讀是0xA1,寫是0xA0.
    AT24C02的裝置地址


    2)).EEPROM的讀協議如下:
    位元組寫
    頁寫
    MCU寫入AT24C02的流程為:起始訊號—->MCU傳送裝置地址給AT24C02(0xA0)/寫—–>MCU接收從AT24C02的應答訊號—->MCU傳送位元組地址給AT24C02(寫)(這裡有一點很重要,之前一直不明白,就是位元組地址都是怎麼樣寫的?一般的資料手冊都沒有寫這個,所以一度很困惑。後來經由大神的解釋弄清楚了,因為AT24C02的記憶體為256個位元組,因而0x00~0xFF就代表的是這個範圍,所以地址就是這個範圍內的。例AT20C128的為16384個位元組,為0x0000~0x3FFF. )—–>MCU接收從AT24C02的應答訊號—->MCU接收AT24C02傳送的資料——>MCU接收從AT24C02的應答訊號—–>結束訊號
    3)).EEPROM的寫協議如下:
    隨機讀
    連續讀
    MCU從AT23C02讀的流程為:起始訊號—->MCU傳送裝置地址給AT24C02(0xA0)/寫—–>MCU接收從AT24C02的應答訊號—->MCU傳送位元組地址給AT24C02(寫)—–>MCU接收從AT24C02的應答訊號—->起始訊號—–>MCU傳送裝置地址給AT24C02(0xA1)/讀—–>MCU接收AT24C02傳送的資料——>MCU接收從AT24C02的應答訊號—–>結束訊號

  3. 程式

IIC.C

#include <stm32f0xx.h>
#include "IIC.h"
#include "System.h"


void IIC_Init(void)/*initation the SDA=1 & SCL=1*/
{
    //I2C idle status
    SCLEnable; //SCL=1
    SYS_DelayUs(2);
    SDAEnable; //SDA=1
    SYS_DelayUs(2);
  SDA_OUT();
    SCL_OUT();
}

void IIC_Start(void)//Start the signal, when SCL&SDA in the Init status,pull down the SDA from 1 to 0 when SCL=1 to start the signal.
{
    SDA_OUT();
    SDAEnable; //SDA=1
    SCLEnable; //SCL=1
    SYS_DelayUs(2);
    SDAClean; //Pull down SDA from 1 to 0 when SCL=1 to start the signal transfer
    SYS_DelayUs(1);   //Baud Rate
    SCLClean;//SCL=0. The SDA only could valid when SCL=0;
}
void IIC_Stop(void)//Stop the signal,to pull up the SDA from 0 to 1 when SCL=1
{
    SDA_OUT();
    SCLClean; //SCL=0. The SDA only could change when SCL=0;
    SDAClean; //SDA=0
    SYS_DelayUs(2);
    SCLEnable; //SCL=1
    SYS_DelayUs(1);
    SDAEnable; //Pull up the SDA from 0 to 1 when SCL=1 to stop the signal.I2C idle status when SCL=1&SDA=1
    SYS_DelayUs(2);
}


/*TO write the string from MCU to the corresponding IC ,such as 24C02,
The first step to search the Device address(0xa0 write,0xa1 read),wait for the ACK/NoAck,
then search the Word address want to write in the address,wait for ACK/NoAck, then write the string*/
//I2C when transfer the data.The data in SDA must stable when SCL=1. The data in SDA could change only when SCL=0.
void IIC_Write(unsigned char String)
{   
    unsigned char i;
    SDA_OUT();
    SCLClean;//The data in SDA could change only when SCL=0
    SYS_DelayUs(1);
    for(i=0;i<8;i++)
    {
        if((String&0x80)==0) SDAClean;
        else SDAEnable;
        String=String<<1;
        SYS_DelayUs(1);
        SCLEnable;//The SDA must stble when SCL=1
        SYS_DelayUs(2);
        SCLClean;
        SYS_DelayUs(1);
    }
}

/*TO Read the string from IC(Such as 24C02) to MCU,
The first step to search the Device address(0xa0 write,0xa1 read),wait for the ACK/NoAck,
then search the Word address want to write in the address,wait for ACK/NoAck, then receive the string*/
//I2C when receive the data from IC. MCU can read it.The data in SDA must stable when SCL=1. The data in SDA could change only when SCL=0.
unsigned char IIC_Read(unsigned char ack)
{
    unsigned char i,receive=0;
    SDA_IN();
    for(i=0;i<8;i++)
    {
        SCLClean;
        SYS_DelayUs(2);
        SCLEnable;
        receive <<=1;
        if((GPIOF->IDR&GPIOF_IDR7)==0x80)  receive++;
        SYS_DelayUs(2);
    }
    if(!ack)
        IIC_Ack();
    else
        IIC_NoAck();
    return receive;
}


void IIC_Ack(void)//Response signal,Make sure that when SCL=1,SDA=0.
{
    SCLClean;//SCL=0
    SDA_OUT();
    SDAClean;//SDA=0,The data in SDA must stable when SCL=1. The data in SDA could change only when SCL=0.
    SYS_DelayUs(2);
    SCLEnable;//SCL=1
    SYS_DelayUs(2);
    SCLClean;//SCL=0
}

void IIC_NoAck(void)//No Response signal,Make sure that when SCL=1,SDA=1.
{
    SCLClean;//SCL=0
    SDA_OUT();
    SDAEnable;//SDA=1,The data in SDA must stable when SCL=1. The data in SDA could change only when SCL=0.
    SYS_DelayUs(2);
    SCLEnable;//SCL=1
    SYS_DelayUs(2);
    SCLClean;//SCL=0
}

unsigned char IIC_Wait_Ack(void)
{
    unsigned char i=0;
    SDA_IN();
    SYS_DelayUs(1);
    SCLEnable; SYS_DelayUs(1);
    while((GPIOF->IDR&GPIOF_IDR7)==0X80)
        {
            i++;
            if(i>250)  
            {
                IIC_Stop();
                return 1;
            }
        }
        SYS_DelayUs(1);
        SCLClean;
        return 0;
}

IIC.h

#if !defined(IIC_H)
#define IIC_H

#define SDAClean                          GPIOF->BSRR=1<<(16+7)  //SDA=0          
#define SDAEnable                         GPIOF->BSRR=1<<(0+7)   //SDA=1
#define SCLClean                          GPIOF->BSRR=1<<(16+6)  //SCL=0
#define SCLEnable                         GPIOF->BSRR=1<<(0+6)   //SCL=1
#define SDA_IN()                          GPIOF->MODER=(GPIOF->MODER&~(3<<(7*2)))|(0<<(7*2))
#define SDA_OUT()                         GPIOF->MODER=(GPIOF->MODER&~(3<<(7*2)))|(1<<(7*2))
#define SCL_OUT()                         GPIOF->MODER=(GPIOF->MODER&~(3<<(6*2)))|(1<<(6*2))
#define GPIOF_IDR7                        ((uint32_t)0x00000080U)


void SystemClock(void);
void IIC_Init(void);
void IIC_Start(void);
void IIC_Stop(void);
void IIC_Write(unsigned char String);
unsigned char IIC_Read(unsigned char ack);
void IIC_Ack(void);
void IIC_NoAck(void);
unsigned char IIC_Wait_Ack(void);
#endif

EEPROM.c

#include <stm32f0xx.h>
#include <String.h>
#include "IIC.h"
#include "EEPROM.h"
#include "System.h"

void EEPROM_Init(void)
{
    IIC_Init();
    return;
}
unsigned long AT24C02_ReadOneByte(unsigned long ReadAddr)
{
    unsigned long temp=0;
    IIC_Start();
    IIC_Write(0XA0);
  IIC_Wait_Ack();
    IIC_Write(ReadAddr);
    IIC_Wait_Ack();
    SYS_DelayUs(2);
    IIC_Start();
    IIC_Write(0XA1);
    IIC_Wait_Ack();
    temp=IIC_Read(0);
    IIC_Stop();
    return temp;
}

void AT24C02_WriteOneByte(unsigned long WriteAddr,unsigned long DataToWrite)
{
    IIC_Start();
    IIC_Write(0XA0);
    IIC_Wait_Ack();
    IIC_Write(WriteAddr);
    IIC_Wait_Ack();
    IIC_Write(DataToWrite);
    IIC_Wait_Ack();
}

void AT24C02_Read(unsigned long ReadAddr,unsigned long *pBuffer,unsigned long NumToRead)
{
    while(NumToRead)
        {
            *pBuffer++=AT24C02_ReadOneByte(ReadAddr++);
            NumToRead--;
        }
}

void AT24C02_Write(unsigned long WriteAddr,unsigned long *pBuffer,unsigned long NumToWrite)
{
    while(NumToWrite--)
        {
            AT24C02_WriteOneByte(WriteAddr,*pBuffer);
            WriteAddr++;
            pBuffer++;
        }
}

EEPROM.h

#if !defined(EEPROM_H)
#define EEPROM_H

#define EEPROM_ADDRESS 0xa0
void EEPROM_Init(void);
void EEPROM_Write(unsigned long Address,const void *pData,unsigned long Length);
void EEPROM_Read(unsigned long Address,unsigned char *ReadData,unsigned long Length);
unsigned long AT24C02_ReadOneByte(unsigned long ReadAddr);
void AT24C02_WriteOneByte(unsigned long WriteAddr,unsigned long DataToWrite);
void AT24C02_Read(unsigned long ReadAddr,unsigned long *pBuffer,unsigned long NumToRead);
void AT24C02_Write(unsigned long WriteAddr,unsigned long *pBuffer,unsigned long NumToWrite);

#endif

在這一定注意SCL時鐘線,就像有生命的心臟一樣,一定要保持它的跳動,不能有停止中斷的時候,因此Delay語句就很重要了,要是SCL時鐘的delay恰到好處,所以當有示波器就可以看出寫的程式是否是遵循這個道理。如果沒有,完成可以根據程式手繪一個,同時根據繪製的時序圖可以反映出寫的程式是否正確。這個下面是我手繪的版本。這是比較笨的方法,但是很有效果。
write a byte
Read a byte