您當前的位置:首頁 > 農業

Stm32之I2C通訊的使用(基於PCF8591)

作者:由 月與樹 發表于 農業時間:2020-04-30

I2C協議簡介

I2C 通訊協議(Inter-Integrated Circuit)是由Philips公司開發的,它具有引腳少,硬體實現簡單,可擴充套件性強,不需要 USART、CAN 等通訊協議的外部收發裝置的特點,現在被廣泛地使用在系統內多個積體電路(IC)間的通訊。

物理層

Stm32之I2C通訊的使用(基於PCF8591)

IIC物理層

其具有以下特性:

一個 I2C 匯流排只使用兩條匯流排線路,一條雙向

序列資料線(SDA)

,一條

序列時鐘線(SCL)

。資料線即用來表示資料,時鐘線用於資料收發同步。

每個連線到匯流排的裝置都有一個

獨立的地址

,主機可以利用這個地址進行不同裝置之間的訪問。

它是一個支援裝置的匯流排。“匯流排”指多個裝置共用的訊號線。在一個 I2C 通訊匯流排中,可連線多個 I2C 通訊裝置,支援

多個通訊主機及多個通訊從機

匯流排透過上拉電阻接到電源,當 I2C 裝置空閒時,會輸出高阻態,而當所有裝置都空閒,都輸出高阻態時,由上拉電阻把匯流排拉成高電平,可以參見GPIO的開漏輸出模式。

協議層

Stm32之I2C通訊的使用(基於PCF8591)

IIC協議層

如圖為I2C通訊的流程,可以總結如下

(概要,具體細節還得看手冊)

主機(MCU)的I2C介面發出一個

起始訊號(S)

,告訴從機(SLAVE,奴隸,很形象哈哈)我要開始傳輸資料啦

此時所有從機都會收到這個訊號,但是,到底是哪個從機要進行資料的讀寫呢?,這個時候主機就會廣播

從機地址

(SLAVE ADDRESS,注意每個從機都有唯一的地址),告訴指定的從機要和你進行通訊,以及是

讀資料還是寫資料

(1為讀,0為寫)

被選定的從機此時要給出一個應答訊號,告訴主機到底要不要進行通訊

如果從機答應的話就可以進行通訊了,如果要停止,主機會發送一個停止訊號(P),告訴從機到此結束

STM32的I2C架構

Stm32之I2C通訊的使用(基於PCF8591)

架構就沒什麼好說的了,圖裡一看就明白。

基於PCF8591的I2C通訊程式分析

Stm32之I2C通訊的使用(基於PCF8591)

PCF8591是一款由Philips公司開發的8-bit A/D and D/A converter,採用I2C協議通訊,雖然STM32內建有ADC,但是為了練習I2C的通訊方式,還是採用這款晶片。

我們查閱其datesheet,怎麼去查閱呢,抓關鍵字,

address byte

,確定其地址,手冊上是這樣寫的:

The address always has to be sent as the first byte after the start condition in the I2C-bus protocol。 The last bit of the address byte is the read/write-bit which sets the direction of the following data transfer

Stm32之I2C通訊的使用(基於PCF8591)

從晶片原理圖得知A0,A1,A2接地,1001000X,那麼其地址就是0x90或者是0x91,看是讀還是寫來決定,接下來看

Control byte

,手冊上是這樣寫的:

The second byte sent to a PCF8591 device will be stored in its control register and is required to control the device function。

Stm32之I2C通訊的使用(基於PCF8591)

假設我需要從通道0單端輸出A/D轉換資料,那麼

control byte

就是

01000000 = 0x40

,萬事大吉 ,開寫通訊協議:

首先看

i2c。h

#include

“stm32f10x。h”

#include

“delay。h”

//應答訊號

extern

u8

ack

//初始化函式

extern

void

I2c_Init

void

);

//起動匯流排函式

extern

void

Start_I2c

void

);

//結束匯流排函式

extern

void

Stop_I2c

void

);

//應答子函式

extern

void

Ack_I2c

u8

a

);

//位元組資料傳送函式

extern

void

SendByte

unsigned

char

c

);

//有子地址傳送多位元組資料函式

extern

u8

ISendStr

unsigned

char

sla

unsigned

char

suba

unsigned

char

*

s

unsigned

char

no

//無子地址傳送多位元組資料函式

extern

u8

ISendStrExt

unsigned

char

sla

unsigned

char

*

s

unsigned

char

no

);

//無子地址讀位元組資料函式

extern

unsigned

char

RcvByte

void

);

//有子地址讀取多位元組資料函式

extern

u8

IRcvStr

unsigned

char

sla

unsigned

char

suba

unsigned

char

*

s

unsigned

char

no

);

//無子地址讀取多位元組資料函式

extern

u8

IRcvStrExt

unsigned

char

sla

unsigned

char

*

s

unsigned

char

no

);

然後看

i2c。c

,實際上的i2c協議很複雜,下面使用操作GPIO的方式來模擬I2C通訊(不使用庫函式操作),不是想要了解其中的原理可直接複製網路上現成的資源,每個晶片的i2C通訊方式也略有差別,具體如何實現還是要看datesheet怎麼寫的。

/*************************此部分為I2C匯流排的驅動程式*************************************/

#include

“i2c。h” //變數定義和函式宣告全在此檔案中

/************************************硬體介面******************************/

#define i2c_port GPIOB

#define SData GPIO_Pin_7

//PB7; //I2C 時鐘

#define SCLK GPIO_Pin_6

//PB6; //I2C 資料

/********************************宏定義*******************************************/

#define SCL(x) x ? GPIO_SetBits(GPIOB , SCLK) : GPIO_ResetBits(GPIOB , SCLK)

#define SDA(x) x ? GPIO_SetBits(GPIOB , SData) : GPIO_ResetBits(GPIOB , SData)

/********************************變數*******************************************/

u8

ack

/*******************************************************************

初始化函式

函式原型: void I2c_Init();

功能: 初始化引腳

********************************************************************/

void

I2c_Init

void

{

GPIO_InitTypeDef

GPIO_InitStructure

RCC_APB2PeriphClockCmd

RCC_APB2Periph_GPIOB

|

RCC_APB2Periph_AFIO

ENABLE

);

GPIO_InitStructure

GPIO_Pin

=

SData

|

SCLK

GPIO_InitStructure

GPIO_Mode

=

GPIO_Mode_Out_OD

//設定為開漏輸出,實現線與功能

GPIO_InitStructure

GPIO_Speed

=

GPIO_Speed_50MHz

GPIO_Init

i2c_port

&

GPIO_InitStructure

);

}

/*******************************************************************

起動匯流排函式

函式原型: void Start_I2c();

功能: 啟動I2C匯流排,即傳送I2C起始條件。

********************************************************************/

void

Start_I2c

()

{

SDA

1

);

//SDA=1; 傳送起始條件(S)的資料訊號

delay_us

1

);

SCL

1

);

//SCL=1;

delay_us

5

);

//起始條件建立時間大於4。7us,延時,關於建立時間與保持時間,這些是數位電路中的重要概 念,不再解釋。

SDA

0

);

//SDA=0; /*傳送起始訊號*/

delay_us

5

);

// 起始條件鎖定時間大於4μs

SCL

0

);

//SCL=0; /*鉗住I2C匯流排,準備傳送或接收資料 */

delay_us

2

);

}

/*******************************************************************

結束匯流排函式

函式原型: void Stop_I2c();

功能: 結束I2C匯流排,即傳送I2C結束條件。

********************************************************************/

void

Stop_I2c

()

{

SDA

0

);

//SDA=0;傳送結束條件的資料訊號

delay_us

1

);

//傳送結束條件的時鐘訊號

SCL

1

//SCL=1;結束條件建立時間大於4μs

delay_us

5

);

SDA

0

);

//SDA=1;傳送I2C匯流排結束訊號

delay_us

4

);

}

/*******************************************************************

位元組資料傳送函式

函式原型: void SendByte(UCHAR c);

功能: 將資料c傳送出去,可以是地址,也可以是資料,發完後等待應答,並對

此狀態位進行操作。(不應答或非應答都使ack=0)

傳送資料正常,ack=1; ack=0表示被控器無應答或損壞。

********************************************************************/

void

SendByte

unsigned

char

c

{

unsigned

char

BitCnt

for

BitCnt

=

0

BitCnt

<

8

BitCnt

++

//要傳送的資料長度為8位

{

if

((

c

<<

BitCnt

&

0x80

SDA

1

);

//SDA=1; 判斷髮送位

else

SDA

0

);

// SDA=0

delay_us

1

);

SCL

1

);

//SCL=1 置時鐘線為高,通知被控器開始接收資料位

delay_us

5

);

//保證時鐘高電平週期大於4μs

SCL

0

);

//SCL = 0

}

delay_us

2

);

SDA

1

);

//SDA=1 位傳送完後釋放資料線,準備接收應答位

delay_us

2

);

SCL

1

);

//SCL=1

delay_us

3

);

if

GPIO_ReadInputDataBit

GPIOB

SData

==

1

ack

=

0

else

ack

=

1

//判斷是否接收到應答訊號

SCL

0

);

//SCL=0;

delay_us

3

);

}

/*******************************************************************

位元組資料接收函式

函式原型: UCHAR RcvByte();

功能: 用來接收從器件傳來的資料,並判斷匯流排錯誤(不發應答訊號),

發完後請用應答函式應答從機。

********************************************************************/

unsigned

char

RcvByte

()

{

unsigned

char

retc

unsigned

char

BitCnt

retc

=

0

SDA

1

);

//SDA=1 置資料線為輸入方式

for

BitCnt

=

0

BitCnt

<

8

BitCnt

++

{

delay_us

1

);

SCL

0

);

//SCL=0 置時鐘線為低,準備接收資料位

delay_us

5

);

//時鐘低電平週期大於4。7μs

SCL

1

);

//SCL=1 置時鐘線為高使資料線上資料有效

delay_us

2

);

retc

=

retc

<<

1

if

GPIO_ReadInputDataBit

GPIOB

SData

==

1

retc

=

retc

+

1

// SDA == 1讀資料位,接收的資料位放入retc中

delay_us

2

);

}

SCL

0

);

// SCL=0;

delay_us

2

);

return

retc

);

}

/********************************************************************

應答子函式

函式原型: void Ack_I2c(bit a);

功能: 主控器進行應答訊號(可以是應答或非應答訊號,由位引數a決定)

********************************************************************/

void

Ack_I2c

u8

a

{

if

a

==

0

SDA

0

);

//SDA=0;在此發出應答或非應答訊號

else

SDA

1

);

//SDA=1;

delay_us

3

);

SCL

1

);

//SCL=1;

delay_us

4

);

//時鐘低電平週期大於4μs

SCL

0

//SCL=0; 清時鐘線,鉗住I2C匯流排以便繼續接收

delay_us

2

);

}

pcf8591。h

#ifndef __pcf8591_H__

#define __pcf8591_H__

#include

“sys。h”

#include

“i2c。h”

u8

DACconversion

unsigned

char

sla

unsigned

char

c

unsigned

char

Val

);

u8

ISendByte

unsigned

char

sla

unsigned

char

c

);

u8

IRcvByte

unsigned

char

sla

);

#endif

pcf8591。c

#include

“pcf8591。h”

/*******************************************************************

DAC 變換, 轉化函式

*******************************************************************/

u8

DACconversion

unsigned

char

sla

unsigned

char

c

unsigned

char

Val

{

Start_I2c

();

//啟動匯流排

SendByte

sla

);

//傳送器件地址

if

ack

==

0

return

0

);

SendByte

c

);

//傳送控制位元組

if

ack

==

0

return

0

);

SendByte

Val

);

//傳送DAC的數值

if

ack

==

0

return

0

);

Stop_I2c

();

//結束匯流排

return

1

);

}

/*******************************************************************

ADC傳送位元組[命令]資料函式

*******************************************************************/

u8

ISendByte

unsigned

char

sla

unsigned

char

c

{

Start_I2c

();

//啟動匯流排

SendByte

sla

);

//傳送器件地址

if

ack

==

0

return

0

);

//應答訊號

SendByte

c

);

//傳送資料

if

ack

==

0

return

0

);

Stop_I2c

();

//結束匯流排

return

1

);

}

/*******************************************************************

ADC讀位元組資料函式

*******************************************************************/

unsigned

char

IRcvByte

unsigned

char

sla

{

unsigned

char

c

Start_I2c

();

//啟動匯流排

SendByte

sla

+

1

);

//傳送器件地址,由於是讀取,所以加1

if

ack

==

0

return

0

);

c

=

RcvByte

();

//讀取資料0

Ack_I2c

1

);

//傳送非就答位

Stop_I2c

();

//結束匯流排

return

c

);

}

最後看

main。c

的操作

#include

“sys。h”

#include

“delay。h”

#include

“i2c。h”

#include

“usart。h”

#include

“pcf8591。h”

#define PCF8591 0x90

vu16

date

int

main

void

{

I2c_Init

();

uart_init

115200

);

delay_init

();

while

1

{

ISendByte

PCF8591

0x40

);

date

=

IRcvByte

PCF8591

);

//

printf

“Send:%d

\n\r

date

);

delay_ms

500

);

}

}

以後會更新更多基於IIC協議的器件通訊,也會給出庫函式版本的操作

總結

實現I2C通訊,以下幾個要點:

datasheet中找到address byte,以及相關暫存器的規則

配置I2C埠以及相關時鐘

根據I2C協議編寫通訊程式碼

標簽: I2C  SCL  delay  匯流排  SDA