|
I2C两线式串行总线通讯协议,它是由飞利浦开发的,主要用于连接微控制器及其外围设备之间,它是由数据线SDA和信号线SCL构成的,可发送和接收数据即在MUC和I2C设备之间,I2C和I2C之间进行全双工信号传输,高速I2C总线一般可达到400kbps。一般我们也称为TWI接口。
) A1 L$ q+ T# N7 R, e7 N" F
I2C支持多主机模式:
U% p; K" r7 H" E7 n' o% v) n) I6 @3 D1 `5 S! {* p6 e8 p. B
q) f1 _# t S- j1 U+ ?
6 ~$ K$ v9 U: j" G
即在这个主线上可以挂载n个I2C外设。
0 {! k; z N" D: Z+ u
对于I2C协议,其实也很简单,不要想的那么复杂,其实就是电平的变换。我们可以人为的分为6个部分 ) F) R& g7 H d3 D; N+ x# H
整体时序图:
/ F4 D- Y; P' B, I: f3 V, y2 B3 y$ B6 J8 w+ V& U. s
2 u6 y6 d* d- B2 t! A: ^
/ w x! r8 P3 v+ {) L. Q
各状态: ! } u ? O' W& I
+ P% j+ |6 }8 D
2 `2 q6 {1 o/ f3 @9 [# X0 {
, O' t2 E% V1 YI2C总线的SCK和SDA两个线同时处于高电平的时候,规定为总线的空闲状态,这个就是由总线上的上拉电阻把电平拉高的。 1 f! t5 e: u2 ?$ p/ q- U
起始信号 4 O# E* i4 Q# m% R6 l# K
9 g; W- q M1 U1 n0 O6 z+ t
! Y; u& B$ V) ~ m# ?8 x
; ~) ~4 F6 _* w. o8 E2 k% \( }当SCL为高电平期间,SDA由高电平变成低电平,即为起始信号。启动信号是一种电平跳变时序信号,不是一个电平信号。
. h0 G$ l; `3 N2 ^$ e; R
当SCL为高电平期间,SDA由低电平变为高电平,即为停止信号。停止信号也是一种电平跳变时序信号,不是一个电平信号。
' V3 w s& v& ~5 W6 H' T, l
应答信号
7 l |* f% \9 k+ k. \' N# p# W ' j3 g4 r3 O! h# ]! B( E9 B
$ B# K4 h4 f+ A9 ^$ m. j6 ^
0 B7 \& h4 N+ g4 ~) Z: p) U s% z, K+ p7 c5 j4 ~5 \5 g9 r
发送器每发送一个字节(8bit)数据,就在时钟脉冲(SCL)9期间释放数据线(SDA),再由接收器来反馈一个应答信号,应答信号为低电平的时候,规定为有效应答位(ACK:应答位),表明接收器已经成功的接收了该字节,应答信号为高电平时,规定为非应答位(NACK:非应答位),表示接收器没有成功的接收该字节。 " r* n- `; I6 ?/ m& Y
对于反馈有效应答位(ACK):接收器在第9个时钟脉冲之前的低电平期间将SDA拉低,并且保证在该时钟的高电平期间,SDA为稳定的低电平。大家主要看图,看看是不是这样的。 ~( N. `( }1 X0 c$ h+ `8 M
要是接收器是主控器,那么它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据的发送,并且释放SDA线,以便主控接收器发送一个停止信号。
2 t0 c; B7 G; R0 h( |/ {
数据的有效性
! x6 x2 y7 O7 p3 N + Q- s. a* ?2 M( ^; P) Z
/ q( c' H) q* L' I% ^/ b2 E! k
3 o1 i7 f& H0 C% `, |! i" i$ L+ X2 G# G* u& E* a
, h( u- v9 ` v* K. [
时钟信号(SCL)为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号(SCL)为低电平期间,数据线上的高电平或者低电平才能发生变化。 6 v* A1 J0 R3 R* }, [( \! G H
数据必须在时钟信号(SCL)的上升沿到来之前就准备好,并且在数据信号的下降沿来到之前必须稳定。 * `& K/ }: o8 h' I$ y
数据的传输 8 i; w) O0 O3 W7 d2 t. q: @& X
在SDA上的每一个位的数据的传输都需要一个时钟脉冲,即在SCL串行时钟的配合下,SDA上逐位的串行发送每一位数据。数据位的传输是边沿触发。
0 u9 c) U. i9 Z# `" Q2 _: W' z 示例代码讲解: E4 R a/ s: O( z0 L. F" G
/ h. S1 y6 Y. i- H! f. E- e
% R9 u {% j, K$ I, E( v- o0 @1 x. r, t$ H6 K
- H; w3 @# s& ?" R$ a其实就是对两个线的初始化,我这里使用的是PA6和PA7,开始都设置为输出,中途会改变PA7的输入输出属性,在空闲状态,我们知道SCL和SDA是被拉高的,所以这个地方我们给高电平。 $ |: u; u; l, @6 m
产生IIC起始信号 & _8 I! e5 P; s. C' p
* I& _) M8 { b$ D* L
* ^2 T' [$ N" @6 u9 L' q. o/ Z
! @. X; }" g" n1 Z' K- t' N+ u; r% Z# {% ?- w/ e" q# O0 u5 w
/ j$ K5 Z. \% k! H$ l( o
+ K; X2 O, ?8 q; t% @将SDA线设置为输出,然后SDA和SCL都设置为高电平,延迟4us,然后将SDA拉低,延迟4us,最后将SCL拉低。这其实就是协议规定的动作了。
2 M: C+ v0 j6 w
! O" K5 P7 q5 H1 N& P
8 d$ t7 y: I& w( s8 I" |
0 q% _- O: Z0 P2 e0 y( J
4 A- E) D5 o V: p) W3 R# F8 w5 y6 f& d2 C. |2 w, U
同样的道理,和协议的时序保持一致就好了。
! |; M% \( T1 k! }
等待应答信号到来 ) E' j; S" A. d6 ^( b
5 p0 I) i. O9 y# b. o- L
" b: F1 U0 ? `8 G8 I7 c2 v- d8 h
) t6 W. {) \/ H: \$ |* Z
, b6 k, _- H! _1 g
首先我们需要把SDA设置为输入,因为接收方要给发射方返回一个应答信号的。就是在SCL第9个高电平的时候,释放信号线,先拉高,然后持续等待,是不是有应答信号返回,其实就是返回一个低电平,所以我们一直在检测READ_SDA这个电平,持续一段时间,要是没有返回的话,我们认为超时了,就直接停止协议了, ( m g/ Q7 P4 U2 N7 p% P
产生应答信号
4 W$ X1 f( y6 k% i! i: N
7 i7 d- \- q& I5 N
; A" }# {/ k& P2 a$ f7 N
9 C, S* n0 p1 g- J
9 }/ v* G1 m1 J4 J; x
即在第9个时钟周期内,SDA都为低电平,为应答 6 [7 P& l8 G" O1 f0 K* w
不产生应答信号 ; @6 g( B8 F0 e. Z
& h2 W8 [ R/ w
0 o+ s+ Y: \; p4 D
' t: w0 s0 P1 M/ @
7 J3 R2 @, k1 |4 w: U! a; D
I5 Z" f4 S2 N4 y$ k+ y+ Q即在第9个时钟周期内,SDA都为高电平,为不应答 # Q0 Y2 x& r1 J+ z \
IIC发送一个字节
/ W' s. Y. h) z. D# N- } 3 ]$ p+ q; F6 a R3 W+ x9 r
# R& W, P( h7 Y
2 |8 r( B1 M5 J0 c
& X1 z0 ~ I& V5 o7 y
3 l5 l5 q) w/ Z# @- M! M2 S. D
1 |; v8 ~/ I& v! \+ n5 R, C发送数据,SDA设置为输出。SCL拉低,SDA准备。 做一个8次循环,拿出1byte的数据,将你发送的数据和0x80作与运算,拿出最高位,然后右移7位,将这个数据放到最低位,这个数据要是1的话,那么SDA输出一个高电平,要是与后的结果为低电平的话,那么SDA输出一个低电平。这都属于准备发送信号阶段。 然后SCL拉高,完成数据的发送,然后SCL拉低,此时SDA也就可以拉低了,接着开始次高位的传输,直到传输完成。
6 r, f: L, T- b
读取数据
$ \3 ^8 P/ r, _6 d8 P. G- q6 C3 N; Z* ~, ^4 J( b
2 l- K, Z9 [9 H& z
0 D4 e, U7 x0 M; y: v& G( ]% G; V( s6 K
读取数据,SDA要设置为输入了。SCL开始为低电平,然后SCL为高电平,我们开始读SDA上的数据,然后左移数据,将读取的数据放在低位。然后检测一下有没有应答。 其实基本思路就是这样了。我把源码附上: a/ q5 ~# E$ _4 b* `7 p* e
i2c.h - #ifndef __MYIIC_H
: z& u, @/ H( z0 P" p - #define __MYIIC_H
( N3 j e# z5 d9 a$ M9 Z - #include "sys.h"9 s) s3 v9 i! v- f# m
- #include "stm32f10x_gpio.h"
X0 S6 ]* j1 {/ c - //IO方向设置
. @ N( I# A8 h0 @ - #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
; u5 \3 N7 s& l& J1 W6 B: \# J - #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}2 `! j5 T. n# K" ~/ S* B y
- //IO操作函数* Y$ C6 C' n+ I9 o. U
- #define IIC_SCL PBout(6) //SCL5 _1 `! L- [! F
- #define IIC_SDA PBout(7) //SDA
8 K! v5 a& } P2 C' \) W9 z' ] - #define READ_SDA PBin(7) //输入SDA
& ]3 \. r7 |- X4 o z - //IIC所有操作函数' J+ Z; }5 q2 K3 j9 G. ~, R
- void IIC_Init(void); //初始化IIC的IO口
" a3 S# b& `% h - void IIC_Start(void); //发送IIC开始信号
G5 d$ j/ X! W) Q6 t0 A7 g - void IIC_Stop(void); //发送IIC停止信号
' |# u8 N; Y$ J" k3 C; b! S - void IIC_Send_Byte(u8 txd); //IIC发送一个字节
# a. f+ f J- {* X: s9 | - u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
. N, X$ T( R) {# \ - u8 IIC_Wait_Ack(void); //IIC等待ACK信号
# R, p! a$ c0 L" m. u; n% I - void IIC_Ack(void); //IIC发送ACK信号7 P) c" W! J2 \: s5 U' o/ `
- void IIC_NAck(void); //IIC不发送ACK信号
, @+ j0 a4 ?$ r' n3 m* F - void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);& k& s' \) e4 w1 C& [
- u8 IIC_Read_One_Byte(u8 daddr,u8 addr);& l+ }( ~. S; E. ~8 i# Y8 J: M
- #endif
复制代码
' a$ I* L3 z, V6 s6 D* ^, G3 I* }i2c.c - #include "myiic.h"
3 C! y- B O% X - #include "delay.h"
! V, ]" U( {9 `9 w6 p6 ~' J- B - //初始化IIC" H% S9 ]8 L# R& s
- void IIC_Init(void)( G: J1 g& E- a, J8 V. R
- {
4 Z7 \2 I3 h3 o; B" i2 p - GPIO_InitTypeDef GPIO_InitStructure;
3 d+ ^3 |+ e# d0 B, ` - RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //使能GPIOB时钟# y) h# a. m" b
-
' I" k9 I& U! o; j Q O- b- y" _9 n - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; A/ f8 s1 k! `5 H+ M
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出* R% w7 [ ~' J4 a6 `2 s2 d" l/ \
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;; F6 Y+ X V3 [+ f0 @, b/ a* {( t
- GPIO_Init(GPIOB, &GPIO_InitStructure);
7 e* H& D6 d' i9 _ - GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高
1 z- q# d. r8 ] - }1 W- f- ~" s7 I. n4 J
- //产生IIC起始信号: r/ \- c; z0 ?! R$ D# E
- void IIC_Start(void)
, u& B; f) Q) o M9 M7 G - {
5 r3 i2 [9 ?9 D- q3 w( n - SDA_OUT(); //sda线输出
3 o1 k1 L/ u. n - IIC_SDA=1;
5 e5 B* C" f6 B% K+ O" J# ~8 o - IIC_SCL=1;* U. Z7 n6 F/ O7 B; ^6 ^
- delay_us(4);4 ^3 |; j" J+ ]/ @# V1 ]0 _
- IIC_SDA=0;//START:when CLK is high,DATA change form high to low
7 N7 ]' J' O0 r3 N) y8 O - delay_us(4);& e0 w# K& A: H& x
- IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 5 ^/ c$ t+ x K, X
- } 2 X$ p& Q/ m- J* L, g0 ?3 D- f& k W
- //产生IIC停止信号
% f9 N( [, k& _9 ]$ @1 ` - void IIC_Stop(void)
7 K( q* ^" ?, F, f - {
; x' b$ |' u1 c6 B - SDA_OUT();//sda线输出1 Y4 N' r+ W" z4 d! l
- IIC_SCL=0;6 N3 F: H$ i" G
- IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
& y; @. o7 u+ p0 M$ Y% M. U3 c5 J- t% T - delay_us(4);
, ?. D7 v! e. F" e' K! P - IIC_SCL=1; 4 p! W' H: b' |7 c5 \% _
- IIC_SDA=1;//发送I2C总线结束信号
1 C8 ?1 _: }7 i8 `' Z% `, D - delay_us(4);
; B8 C4 x/ w' x1 | - }) v% r% w+ Q, z8 o1 G$ y
- //等待应答信号到来
# ?) N2 W$ |% l* H - //返回值:1,接收应答失败
! V2 @: ]( x4 `- n - // 0,接收应答成功& t* c/ ~) E' e- z S/ p# {( ~4 h$ J
- u8 IIC_Wait_Ack(void)3 c k7 ]" F9 {0 W9 K3 d7 [
- {( K9 w8 [/ A. w
- u8 ucErrTime=0;
+ @2 x* ]( s X* m, k% v - SDA_IN(); //SDA设置为输入
' w- N1 h4 R( P- x8 O- k$ b" T y - IIC_SDA=1;delay_us(1); . g. k& u7 i. E% R. ` {
- IIC_SCL=1;delay_us(1);
& l7 t- s$ V# C" L1 H2 |! i' @7 k5 T - while(READ_SDA). {! A) G7 g i
- {
. X# ~6 O# g, U - ucErrTime++;" D- D) S3 b- x4 P* z2 ~9 K; @" |! N
- if(ucErrTime>250)- a: w0 u3 {( l5 X0 a" C- s1 {; H
- { y: m( P! `/ Q. m6 r- u
- IIC_Stop();3 v7 |! ^' S% _: c/ s
- return 1;
" C) E; x2 `" o/ f0 S6 B8 _1 H+ Q/ v - }
) D3 C7 @, n! ^1 s4 r# ^% H3 h) h - }
1 c1 A& I" j2 D; w6 v: I% g - IIC_SCL=0;//时钟输出0 4 a: D& n' ]$ t$ V* k* f; c4 K* G5 g
- return 0; 1 N/ \6 [* }. F/ [, b0 P
- } * I3 V4 {( ]) m
- //产生ACK应答
5 q. ^7 ~/ W, _ ?0 e5 r" E - void IIC_Ack(void)
* H& Y* d1 _5 m. ~2 u. w0 r+ ? - {
- {" M& [. ]( h! p6 S# d/ k - IIC_SCL=0;/ ? O! H7 g( E5 {- V0 d& M
- SDA_OUT();
. M; h2 h/ l9 A# ^ - IIC_SDA=0;* G/ ?" h0 S T
- delay_us(2);
4 S% s/ W$ A8 [) \* a - IIC_SCL=1;/ Q: x& y3 `, o
- delay_us(2);
# p5 S, ?: r( t& z - IIC_SCL=0;9 B# J( t& g& v0 O8 V+ r# |
- }5 b& I1 |7 F" _$ b
- //不产生ACK应答 : f% U6 d. f- Z' T n( Y
- void IIC_NAck(void)
8 r; f7 h% c( ]5 i+ } - {
( p' g' [. h) Z" F3 Z& f - IIC_SCL=0;8 H( Y( u; @2 F# Y9 L5 z
- SDA_OUT();
: f/ N' p, `& f" u, Y& a9 y, u - IIC_SDA=1;* o+ M! S4 A. q" Q& b1 c
- delay_us(2);
" x0 N' b+ y# m: A - IIC_SCL=1;
; I( g+ _2 o3 q - delay_us(2);+ w" p3 A7 c+ Z$ A
- IIC_SCL=0;
5 J- B/ v( w5 ^7 b4 x6 Y/ }/ w - }
* B7 g, i, k1 f - //IIC发送一个字节
* V0 T1 T0 B* g! J7 | - //返回从机有无应答7 Z9 |1 k* _, e6 G
- //1,有应答7 ?. f# O6 v; @9 e. E5 c5 h
- //0,无应答 , @/ u3 [9 {1 `5 h
- void IIC_Send_Byte(u8 txd)
& \/ z. z& \+ P5 o9 k7 U: C+ E2 ^ e - {
5 e( Y( {8 }, M0 P5 U$ s - u8 t; $ i6 r! h3 V. i* D& E
- SDA_OUT();
8 ?/ @$ Z+ P( E6 W* F( { - IIC_SCL=0;//拉低时钟开始数据传输
. E& _9 ~6 t2 `; Y8 r l - for(t=0;t<8;t++)
% x! N8 |2 v/ H$ Y - {
, M. r4 U+ d. B0 ?. R0 g2 B - //IIC_SDA=(txd&0x80)>>7;
: x' d0 i! @4 I - if((txd&0x80)>>7)
6 r! b$ w. D" R& I, k# X, L - IIC_SDA=1;6 R3 | ?! a- `1 l
- else
. l- g7 d5 C8 L2 x - IIC_SDA=0;
( l) K! ]- i2 K) A - txd<<=1; / K# A/ ?: `) m5 r! M# z4 l4 I, ]
- delay_us(2); ! Q* e# \) {- x$ s7 `
- IIC_SCL=1;/ S9 x5 b% b/ N I" N) R) z
- delay_us(2);
% h& j" B# `8 T! c" I3 K - IIC_SCL=0;
9 ~" q9 {3 v/ l; v" e - delay_us(2);
$ R& z, S, O- c - }
4 b' P( `1 n/ S/ F- Q/ G - } ! O t7 R% d. h/ s" g j# g
- //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
$ j5 y) X. k4 K2 X6 k: N - u8 IIC_Read_Byte(unsigned char ack)
$ @$ a5 i5 e* t7 ^8 ^6 H- w - {
4 b$ l: O3 t7 X6 z/ ^! |4 f - unsigned char i,receive=0;
/ u( `2 O0 ~$ W9 V - SDA_IN();//SDA设置为输入
: t! x0 [! ^9 W - for(i=0;i<8;i++ )# N ^7 L$ q/ k3 N" g5 s6 U) g
- {
; p# Y7 l1 P! d$ ?$ g - IIC_SCL=0;
( C; Y H& ^4 t: G. ~) ? - delay_us(2);
8 O8 ]2 t3 A/ p" @/ A$ k$ ` - IIC_SCL=1;5 U! i& J: E+ \/ w
- receive<<=1;
, ?! Q$ p' y: @: R - if(READ_SDA)receive++;
p' e1 k7 s; W - delay_us(1); 8 S Q+ e6 t9 F+ d! _
- }
+ A E; M) ]) j. m - if (!ack)
5 l r' d% \7 J; `8 M5 l - IIC_NAck();//发送nACK
( K: d7 \. n3 y h2 j - else2 J( z4 W4 j+ v H7 r( [
- IIC_Ack(); //发送ACK & a! P4 f# c( |/ r
- return receive;* w! x% M* h4 Q( s$ C
- }
复制代码
4 H- c& U# B6 T/ v转载自:电源研发精英圈; M0 c G( s6 T& V
如有侵权请联系删除
% b: Q1 T# [5 Y, t3 s0 [! P3 m; s
) }1 x: B2 l& @0 A: D0 U
|