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
硬體IIC和模擬IIC的區別?
硬體IIC指的是MCU上面專有的IIC介面(SDA資料線和SCL時鐘線)及在APB1上有IIC的指令暫存器,可以直接配置IIC指令暫存器及連線SDA線及SCL線至需要進行通訊的裝置(IC或者EEPROM)進行讀寫通訊。(在STM32的資料手冊上面寫的需要配置的就是硬體IIC)
如下圖所示:
模擬IIC與上面的硬體IIC有所不同,指通過通用的GPIO口進行配置與相應的IIC裝置(如EEPROM—AT24C02)進行通訊,需要配置兩個通用IO口,一個作為SDA線,一個作為SCL線與IC進行通訊,如同下面所示:
上面圖片是模擬IIC使用GPIO口進行配置,在進行IIC的配置前,首先要配置GPIO口。模擬IIC的配置&時序圖& 協議
首先要配置GPIO口,現在以電路圖的連線為主,PF6設定為SCL & PG7設定為SDA,則 首先需要使能GPIOF,PF6/SCL因為是時鐘線,所以只要配置成輸出就好了。
PF7因為是資料GPIO口,資料可以從MCU傳送到IC(AT24C02),也可以接收IC(AT24C02)傳送的資料。所以需要傳送和接收資料,把PF7/SDA設定為輸入輸出口。
1)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.
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的應答訊號—–>結束訊號程式
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恰到好處,所以當有示波器就可以看出寫的程式是否是遵循這個道理。如果沒有,完成可以根據程式手繪一個,同時根據繪製的時序圖可以反映出寫的程式是否正確。這個下面是我手繪的版本。這是比較笨的方法,但是很有效果。