本帖最后由 aimejia 于 2018-5-29 17:18 编辑
/ X( I+ O$ B* u, I) }6 Q/ o3 X d4 i. L
iic协议是比较简单的双线协议,时钟线CLK和数据线SDA。
m$ }& o. h# b( D f# G
# K7 [+ F% S+ H+ z4 f一般我们常见的还有spi总线,这种总线可以可以根据需要扩展,还有单总线等等# }2 B- P) b: r( w9 g
; P! y8 o. l8 N8 I. x: v
这次还以at240c2为例进行操作!
$ T1 ?. t3 S( e- ^3 [% \4 b! n5 O" H6 [7 X9 Z: _. b
. X1 \" n% f4 j" v7 S! U+ |9 ~
: C. q) ?2 C: e( r$ N' _+ LPS:这就是传说中的iic时序图/ t$ P! q7 T; x) b5 {
7 I! i' g' H/ c7 |4 G$ h; J/ \
硬件构造我们不过多的分析,今天用到库了!我们先从库函数硬件iic初始化说起!
. t8 f2 j# j. H1 C# D, \
/ S& z+ M) Z7 c$ x4 X6 \! oPB6 -- CLK$ }, l+ P, G$ R8 d* E3 c
) g6 @/ W! |8 g$ A2 ?% Q
PB7 -- SDA
: F5 F0 @8 @; {4 Y& C- [cpp] view plain copy
- r, T C. |" o. Q* \ - void i2c_init(u8 addr,u32 clock)
# z) ]7 o! d8 V& L0 N& J7 \ H# i5 Q* N - {
: ]! `) z3 N' o, S - I2C_InitTypeDef i2c; 6 m$ ]( `+ y8 @/ i) _
- RCC->APB2ENR |= 1<<3; 4 F0 R B* x8 n
- GPIOB->CRL |= (u32)0xff<<(6*4); - c4 U. f5 j A
- 2 I2 G3 K5 G# v% H$ B2 T4 q; a
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
6 R3 ? r5 i8 z6 p0 d. \* ~ - i2c.I2C_Ack = I2C_Ack_Enable; 4 H, J3 ]1 I6 Y& L
- i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
& Z0 ~9 J9 b( u5 v0 N - i2c.I2C_ClockSpeed = clock*1000; % s- Q0 [! t8 K. q" s& [, r
- i2c.I2C_DutyCycle = I2C_DutyCycle_2; - M- P' ^6 b8 S2 W( {; ]
- i2c.I2C_Mode = I2C_Mode_I2C;
% g6 j+ [: a7 K+ B+ t - i2c.I2C_OwnAddress1 = addr;
" E* \# p* R% I; P -
0 N1 r9 @$ u: I3 T8 c8 \ - I2C_Cmd(I2C1,ENABLE);
5 s4 v* o) ~6 s+ k# r - I2C_Init(I2C1,&i2c);
& g" c( ~- l: h$ H: f - }
复制代码 在配置管脚方面,我还是喜欢用寄存器配置,因为我的两行代码可以解决库函数的N多行代码的问题!
! L2 t* |; j" @4 t$ w
6 a) {1 B2 f% K; }' i% v还有在结构体变量命名方面也是属于我自己的独创吧,这样反正我觉得是既容易识别,也少打几个字!
5 Q! g& H* Y9 O* s% P" y
. u' R: k. A7 T+ utypedef struct
" p6 A* C$ I+ W I0 F) w{0 A4 ~0 F0 n4 I4 x1 l- v' v6 U
uint32_t I2C_ClockSpeed; //I2C时钟频率设置
3 N1 D# s- D( b2 d t3 r uint16_t I2C_Mode; //I2C模式设置+ {1 s* [* z! M! j# |
uint16_t I2C_DutyCycle; //高低电平时间之比
/ y7 m+ ~3 F% C! ]" j uint16_t I2C_OwnAddress1; //主设备地址设置,也就是自己的地址
( P+ c6 X1 w, C$ E. [ uint16_t I2C_Ack; //Check
' H0 F8 E% @+ k @; I uint16_t I2C_AcknowledgedAddress; //地址长度,可以为7bit的也可以为10bit的
9 Z8 a& P1 G" {. O n1 {* S}I2C_InitTypeDef;: a8 | Q$ Z' `5 F4 K2 X& x# O& U
1 r# P r N) l- k- a' U HIIC初始化完之后,我们开始来研究eeprom
! d. F2 b$ c$ a1 @" z. u: U% |! E7 j! G) J
; _ ]: V$ S; i$ K: P! V" w! ^1 f6 ]- p; R- u, d6 @
' X* O, w1 \0 x! Y完这个写一个字节的协议之后,我们应该对这个写已经没有什么问题了,很简单的。' T& r9 }' _5 L$ |! j
: [! \/ P6 ^0 Z9 t5 L$ Z6 m0 J
6 a: N2 y7 X' l# o- x- N
9 @% Z. Z. q% ^+ t3 i
这个是写一个page9 b: O+ T4 g7 n9 k$ `0 J! o O
( B" K: w g. R7 e% |
注:在eeprom里面写数据时,一次最多只能写一个page,一个page为8byte,同时这个也有字节对齐的要求!9 C7 M& Q/ o9 M% I" R/ b% b' c
) v. p* W: k9 r, x& v7 } M5 Q6 g& q/ T" h2 D; K5 R2 U% G
, G% t* z& R. o6 E/ Z. G% r比如我们从Address = 4开始写,那么我们最多一次性可写4个byte,如果我们从8开始写的话,我们就可以8个byte,最后偏移到15。
1 U3 b, ~& t( t8 W; @- [cpp] view plain copy
9 r( j9 w& n6 l1 j3 U5 a7 N - void eeprom_write_byte(u8 wt_addr,u8 data)
& y- w# E$ K: u' |1 S* ^, c* ]; W - { 1 J" ? H2 n/ f/ m7 J; | M+ \; O
- 2 e- v- Y" `6 Y0 A5 ~
- I2C_GenerateSTART(I2C1,ENABLE);delay(5); : @+ O: }: w3 A r
- I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Transmitter); 8 `0 }# f& r7 r" Z/ |
- while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
1 I/ u" p8 q6 O8 R1 h9 o9 r - I2C_SendData(I2C1,wt_addr);delay(5);
2 j8 R9 Q# R6 V' W; U$ l - I2C_SendData(I2C1,data);delay(5); 0 e4 g# w) f( L* M
- I2C_GenerateSTOP(I2C1, ENABLE);
, @, ^# Z8 G0 n - delay(20);
0 X2 Z9 s6 ~& \ - }
复制代码 由于stm32的i2c确实做的不怎么样,标着寄存器太多,也不容易识别,我们就不要检测这些标志寄存器,用延时了把他们隔离了。不过在把地址发送出去之后,要检测设备是否被选中,这个在我们的模拟的i2c里面也是必须检测的!可以认为是必不可少的!
( z: F8 d7 \- \" T! {- [cpp] view plain copy8 B9 I9 A( z! g# \
- void eeprom_write_page(u8 wt_addr,u8 *buff,u32 length) 1 Q4 r, U' b: d |/ ?6 N6 ]& @/ R, n
- {
$ y% L. [ Z9 y/ x - int i = 0; - M# N) _) g) L5 g( }# D6 F" F
- I2C_GenerateSTART(I2C1,ENABLE);delay(5); - H1 f% c i( ^
- I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Transmitter); 9 h* k& F- |+ e4 J
- while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
: u7 z; b& n' u+ L! T" w+ e2 W - I2C_SendData(I2C1,wt_addr);delay(5);
* n1 P' T) e8 E! g* j - for(i=0; i<length; i++)
+ i7 [% X2 W* P& ^ - {
/ M; e( o l, i* ?2 [5 o. | - I2C_SendData(I2C1,buff[i]);delay(5); 0 q+ S3 g5 ^9 W/ ~2 r- m: T. f
- } 7 Y& i3 P! W* H$ F5 e) n' u
- I2C_GenerateSTOP(I2C1, ENABLE);
% u. O. X, j0 ~) m - }
复制代码 这个是写一个page的函数,如果大家想看比较灵活的写函数,去我看我前面发表的博客里面找找,那个无论你写几个都无所谓,只要不超过eeprom的大小!9 X. L4 V T' Z# b
$ O6 y9 Q. J* O) d* A7 Z. D* |' {/ n+ k! S' T
+ W' b5 h$ u* L( D9 h l4 ]0 h读数据是稍稍复杂一点点的,我们首先要选中设备,然后选择我们要操作的地址,这时候不要stop,如果stop信号一发出,总线就被释放掉了,设备也就跟处理器断开,所以这里需要一个RSTART,跟START不一样,多了个R,这个可以理解为重新开始,这个信号不会选中其他设备,也不会丢失当前设备。! d( s$ N# R c; `8 a
" L7 t9 ?" x7 g$ }' c1 f6 n( B
然后还有个注意点是,在读完第N个字节后,不要返回回应,直接stop,不然设备会以为你没有结束,会一直占据总线,等待下一个数据的发送,这样等你下一次来访问他的时候,他就不让你访问了,因为他还停留在给你传数据的状态,所以这里一定不要返回acK直接stop信号发出哦!" _2 P9 Q+ ]4 z& D2 n/ v3 K
- [cpp] view plain copy: l% Z& ` N$ Q8 ]
- unsigned char eeprom_read_byte(u8 rd_addr)
3 l" h- Y5 h- Y. r) F& G - { 6 G7 [! d7 F- b0 @. I% Y+ j+ o( V, |! l
- u8 temp = 0;
: j7 Y/ J# j+ A) M -
6 R. e4 O' }+ t- u: b - I2C_GenerateSTART(I2C1,ENABLE);delay(5);
/ B. X! J9 m$ C# F; }& M - . j% {4 P1 u" ?7 O
- I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Transmitter);
) L* Q% h$ j' z9 A0 { - while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); & ]+ G' Y) a; J9 D7 k% x5 q
-
, s) D. L2 `( F3 |6 X: B8 y7 S, S - I2C_Cmd(I2C1, ENABLE); - P1 J$ {+ \* h. t/ i& j
- I2C_SendData(I2C1,rd_addr);delay(10);
D1 D" g. n) C& N* |+ N! h - I2C_GenerateSTART(I2C1,ENABLE);delay(5);
# o8 k2 m# m: } -
6 d( O) x0 d) X4 i$ l; R - I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Receiver);
$ |0 p! \* O, ]6 D* x - while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); x8 D( B7 U5 f0 l& g
-
/ _$ f; L- K/ f - I2C_AcknowledgeConfig(I2C1, DISABLE); 2 Q. A. J7 n) [2 V9 P' K
- while(!(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))); " L% D8 W* m* M
- temp = I2C_ReceiveData(I2C1);delay(5);
9 N0 z% @6 V9 `" t$ a7 m -
) l( f0 U9 {6 W- ^5 L7 ^/ K - I2C_GenerateSTOP(I2C1, ENABLE); ; s' n5 s1 r( C* M, F
- delay(20);
9 d+ e/ T3 C) R8 |7 k6 H5 \ - I2C_AcknowledgeConfig(I2C1, ENABLE); # {7 I+ a" J7 S1 n( e
-
! R! E3 A4 @. z& R* U) n/ s - return temp;
/ H: B& y$ n( c - }
复制代码 还是有几个信号是必须确认的,设备地址发送出去,看设备是否有回应!
' ^% I7 N5 b \# R0 \2 M& {/ _. Y
这里最后一个NOACK必须在发送最后一个字节前使能,在stop信号发出后,记得吧ACK信号重新使能,因为我们刚刚开始是需要ack的,只是最后有时候不需要!
c. u, s& |3 b( e. S8 M; s# c5 W: x6 X
6 E2 J+ @ Y7 M# N# T( s+ L' y3 I$ ^
% o& C l" l4 n( Q& q2 Y1 M% A g对于数据的读,在所读数据长度上,是没有要求的,也没有page限制,想读多少,读多少!7 Y1 W! R3 d& g# `7 Y6 f, a2 c
- [cpp] view plain copy/ t y9 p# b* L+ o; G" j
- void eeprom_read(u8 rd_addr,u8 *buff,u32 length)
1 X: s! |3 }+ k H - { ~# v @: R( a3 B: d( M7 E
- int i = 0; h1 h) W0 a6 P2 j( L' w
- I2C_GenerateSTART(I2C1,ENABLE);delay(5); 4 k' V1 O2 X1 ~3 i# F2 n$ s# v6 t# v
- I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Transmitter); / G; t e. E3 F9 Z, V
- while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
( x L5 o% F5 L3 a5 e - I2C_Cmd(I2C1, ENABLE);
; s/ C/ D; H5 C( O - I2C_SendData(I2C1,rd_addr);delay(20);
$ ~ P# k1 o1 k: \: ~ - I2C_GenerateSTART(I2C1,ENABLE);delay(5);
/ t. R- y2 g9 ]3 C0 K - I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Receiver);
( n6 { n" ?& z2 R - while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); 5 {. T' I& F+ o2 ]( Y: {) X
- for(i=0; i<length; i++)
5 ]' c9 }) s# a$ C- `6 D" Z/ f - {
" i c4 Q) Y7 z - if(i == length-1) ) h4 }7 w) f6 p7 c
- I2C_AcknowledgeConfig(I2C1, DISABLE);
h) l6 v0 ?4 h* p+ F1 v+ P, w- x8 [* P/ P - while(!(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))); 2 y' ?5 e# H6 c! v) b* T; W
- buff[i] = I2C_ReceiveData(I2C1);delay(5);
' K; U; @8 k& K- E - } ; {' V. g3 U2 F1 ~
- I2C_GenerateSTOP(I2C1, ENABLE); , P+ A3 F: J; a. @
- I2C_AcknowledgeConfig(I2C1, ENABLE);
5 c; d* ~- E6 D/ g; _ - }
复制代码 PS:在提一下,在接收最后一个数据之前,必须关闭ACK,不然,。。。后果很严重!
' {" G& B a- F T) m; U9 u
0 L# i% f1 C; m* a4 E7 u4 i: T
3 o& S: J, ^$ p* Z% Q
1 i3 n% s9 G) a) v" o
3 T, i/ q4 H/ p9 b* e转载自ieczw
& r5 K- ?5 h h+ {5 d3 o8 ]4 e% d- @$ q# C$ `
6 l; r) ^( I* G$ @& J8 i. j
|