Stm32之I2C通訊的使用(基於PCF8591)
I2C協議簡介
I2C 通訊協議(Inter-Integrated Circuit)是由Philips公司開發的,它具有引腳少,硬體實現簡單,可擴充套件性強,不需要 USART、CAN 等通訊協議的外部收發裝置的特點,現在被廣泛地使用在系統內多個積體電路(IC)間的通訊。
物理層
IIC物理層
其具有以下特性:
一個 I2C 匯流排只使用兩條匯流排線路,一條雙向
序列資料線(SDA)
,一條
序列時鐘線(SCL)
。資料線即用來表示資料,時鐘線用於資料收發同步。
每個連線到匯流排的裝置都有一個
獨立的地址
,主機可以利用這個地址進行不同裝置之間的訪問。
它是一個支援裝置的匯流排。“匯流排”指多個裝置共用的訊號線。在一個 I2C 通訊匯流排中,可連線多個 I2C 通訊裝置,支援
多個通訊主機及多個通訊從機
。
匯流排透過上拉電阻接到電源,當 I2C 裝置空閒時,會輸出高阻態,而當所有裝置都空閒,都輸出高阻態時,由上拉電阻把匯流排拉成高電平,可以參見GPIO的開漏輸出模式。
協議層
IIC協議層
如圖為I2C通訊的流程,可以總結如下
(概要,具體細節還得看手冊)
:
主機(MCU)的I2C介面發出一個
起始訊號(S)
,告訴從機(SLAVE,奴隸,很形象哈哈)我要開始傳輸資料啦
此時所有從機都會收到這個訊號,但是,到底是哪個從機要進行資料的讀寫呢?,這個時候主機就會廣播
從機地址
(SLAVE ADDRESS,注意每個從機都有唯一的地址),告訴指定的從機要和你進行通訊,以及是
讀資料還是寫資料
(1為讀,0為寫)
被選定的從機此時要給出一個應答訊號,告訴主機到底要不要進行通訊
如果從機答應的話就可以進行通訊了,如果要停止,主機會發送一個停止訊號(P),告訴從機到此結束
STM32的I2C架構
架構就沒什麼好說的了,圖裡一看就明白。
基於PCF8591的I2C通訊程式分析
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
從晶片原理圖得知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。
假設我需要從通道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協議編寫通訊程式碼
上一篇:為什麼CSGO休閒連狙被罵?