一、原理图/ S. I C7 G: ]4 V! R7 m4 N
" a% J: M: b2 V6 e3 G
; Q5 y9 l# P. r; {0 ~% C3 \" A z8 T6 K) S, E$ E: t8 S
—— —— —— —— —— —— —— —— 软件IIC—— —— —— —— —— —— —— ——
}: m$ ^ u+ Q& y3 L0 a: h$ c) I$ R2 S# v# R! h+ @/ i% b+ b
二、 CubeMX配置8 j- Y5 b) S1 a8 B2 V) i) J1 Q
2 q5 j) C/ J& D# p8 O6 a8 oStep1.打开 STM32CubeMX,点击“New Project”,选择芯片型号,STM32F103VETx。
. U' j; v8 Z& S' a) U4 V7 T8 Q' @$ g( G+ w9 j- f" y& b
8 |, Z/ p0 a# d) o! ]2 ^7 i, O
3 u- i7 H2 ^2 C) P' s! \; E
Step2.选择时钟源,并配置时钟树。选择Crystal/Ceramic Resonator,并配置系统时钟为72M。
( c/ d5 j7 e" F! D: F5 {$ Z$ K8 A& Q- X
" c5 J( B4 j$ ^# e) a' s( I1 _% H6 I" E* N3 n- D G0 a& Z7 X
( E. ?5 s# N4 B
: N: F8 _7 {. _6 k" t% TStep3.配置SYS,我们这里选择的是Serial Wire。(正常情况配置不配置不影响,debug可以使用。但是你不可以把这两个引脚用于其他复用功能,如果用于其他复用功能,debug就不起作用了。). t! I5 h6 B) z
# W3 H3 A" y9 H7 \. V
3 k' \) z9 o$ r) a' t/ _, y& x5 v+ J* G0 @/ L( R
Step4.串口配置(主要为了在串口调试助手显示读写数据),因为没有用到中断和DMA所以我们就不过多讲解。7 v% ]4 f! n5 K3 ?6 Q- v" ]
% L+ K6 s" T0 J0 L/ R8 \
0 s( l$ c8 l; z! `4 U
H8 T+ ^7 v! y" Z/ I" L( c: t
% t6 o: w' U; O6 x+ qStep5.配置软件IIC所需要的的GPIO口,需要注意的是这边要设置成开漏输出模式,上拉模式。这里选用PB6,PB7作为SCL,SDA,因为是软件IIC,所以这里的选择接口是不受限制的。% `7 C5 _! h* P9 E( }: o% ^
' A7 E8 p/ |4 {, S8 G5 D
8 c5 \/ h( B8 a; B+ a
& r0 P. R ]5 X. X 到这里关于软件IIC参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。% V5 q N F+ i. O# A# @' [
7 j4 H8 j' e# |. l: M! q: G( r3 u2 I 三、添加功能代码' ]% j2 u; l. d7 m' L" @( o) n& d- U
7 s E1 r" g8 _' `$ ^- |1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。重定向代码如下(usart.c)
/ U, t2 Z, K: }: ]0 L2 A0 }) U# D( \8 f
- //重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数1 j- j9 [/ |; U& z3 Q! {- ^; Z
- int fputc(int ch, FILE *f)4 {! H, F4 |0 }* @& @
- {
- L% Y! w# ~ ?- s. ^ - /* 发送一个字节数据到串口DEBUG_USART */ E8 M8 g$ s2 M! G
- HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
5 k* U% Y$ W' {, o5 Y- B - $ u( b' v& \0 [: n4 ~3 Z
- return (ch);
, E$ O" F! a4 w" ]' B( U - } ~; U4 r; U# n6 l
8 a S) V5 o5 e' h: a- //重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数
( g$ ^. {* @# @9 a8 O/ i8 f: m - int fgetc(FILE *f)/ V& R8 I5 e$ U3 ?: B
- {
# v$ ]; s3 J& ]! \7 u! K - int ch;
/ C8 a+ v1 e: }* J$ V, S5 X - HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 1000); / ^# B* [# @8 `( x
- return (ch);8 M6 B) w2 l, H1 i
- }
复制代码
& q" g+ J" m, Y$ h( Z1 Y8 V# ~& {% y) m' ~ B5 i4 E
2、为了把IIC和EEPROM的相关接口区分开,方便以后移植到其他IIC设备。在cubemx自动生成的gpio.c/gpio.h IIC的相关接口,同时新建了i2c_ee.c/i2c_ee.c中写读写EEPROM的接口。
$ }) U" U, g' O2 J- ~, z& T; J' o4 S3 V: ~. y; z: v7 v) X0 \: w
(1)IIC相关接口的书写。
9 |6 [ w3 f, x, y& Q7 U. m7 d& g% U4 c1 F& I* _4 a
首先是在gpio.h中,对SCL/SDA相关IO口的写0/1和读取进行宏定义,提高代码可读性。以及等会涉及到读写位进行宏定义。最后就是函数的声明。
& U3 _3 a/ _- N( n0 f9 ?5 A5 l( p: G$ j5 x- X, @# r5 h# E2 O6 y
7 ?; [% {4 E2 @; C, J3 ?7 j8 D+ r
- #define I2C_SCL_1() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
# J/ K# y4 O3 z1 H4 L7 E( s; [. C - #define I2C_SCL_0() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
) L; Q0 B$ @9 I. b* q) _- G - ! t) b6 q9 d' H# m5 k. {
- #define I2C_SDA_1() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
i1 |6 ]/ H- D$ n( d N+ F- X4 R - #define I2C_SDA_0() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
# b' v0 V2 `* `" Y4 i' {) J
% U5 L; K# A+ l( f- #define I2C_SDA_READ() HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)$ L# w h' J; V' J
- #define I2C_WR 0 //写控制bit
- b6 b& u3 q- p+ h* w) T, ^# Z9 G - #define I2C_RD 1 //读控制bit
2 k0 n3 l- o( J, c) n
/ z9 f) F9 Q! R* g- v* `+ T- void MX_GPIO_Init(void);
0 D$ T( d# i8 J" Z* [$ j/ F - , w* ]" w; C( S1 S4 [' r( Q
- void i2c_Start(void);
' M1 V; L/ I5 r3 z% x6 {7 y - void i2c_Stop(void);
% X6 ?/ U* G0 a$ f9 h& D- @- H - void i2c_SendByte(uint8_t HS_Byte);- ~2 ], o6 V1 y& h _1 N: H
- uint8_t i2c_ReadByte(void);
+ l, O6 w' p. Q2 X; R) [/ d( [- D2 n - uint8_t i2c_WaitAck(void);
/ {( o9 B; P2 ` X% y - void i2c_Ack(void);" D! Z$ f* e4 r0 ?, M
- void i2c_NAck(void);0 C1 ]2 _6 o0 Y8 A+ U6 Q
- uint8_t i2c_CheckDevice(uint8_t _Address);5 e( W+ W1 ?! c
复制代码 / G# C2 Z9 x/ |! r' V8 z- H
然后就是在gpio.c中,把我们刚才声明的函数进行完善。
0 B/ q- i- E# c$ @( |8 z7 P2 ?( s% u* p9 N. T
- /*延时函数*/
4 Y1 `( m6 e: G4 d, k - static void i2c_Delay(void)
( p0 b: Y0 y) ^, f( S" F - {
6 r2 x$ w- }* l4 I% [3 x - uint8_t i;
1 D, ~: e: W% `( g- A+ p0 S! { - / C6 c3 f3 P/ A/ _+ B' h
- /*
. W; g+ a( G4 L1 L; H8 A2 L( W - 下面的时间是通过逻辑分析仪测试得到的。
/ `' t& P- m$ g( W* G6 K( c" M - 工作条件:CPU主频72MHz ,MDK编译环境,1级优化
* `( ?; I% _$ U* q1 f5 {$ { I - ; o) ^* P& C5 |+ m1 Q# m. {
- 循环次数为10时,SCL频率 = 205KHz 8 t/ B& |% A% z4 G; n8 ^
- 循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us
/ E+ G' f) q- z! z - 循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us 1 j7 \; p7 i( U) ]7 {0 T% K
- */
) p; Z0 s* N4 b6 o7 w! o - for (i = 0; i < 10; i++);1 r9 r) k+ M" a8 E9 w% r
- }, Q1 O& |1 |4 p2 f* N5 G( i- o4 m' R
- /* CPU发起I2C总线启动信号:当 SCL 线是高电平时 SDA 线从高电平向低电平切换*/
. l$ W$ V/ h5 L( L1 ] ~ - void i2c_Start(void)5 l$ d' t% F8 U @/ L1 F/ F0 R
- {
4 |6 n4 `! D, Z% M- d$ Z - I2C_SCL_1();0 L5 E; S o8 l
- I2C_SDA_1();
* U. Q7 N4 [6 |0 F - i2c_Delay();
- x( x# k2 |5 Z3 H1 Y - I2C_SDA_0();
9 T W8 g% N7 s% v) z - i2c_Delay();
; K# S# V% g9 e. p1 Z3 F - I2C_SCL_0();2 [: J/ f" H7 d% O
- }
+ k8 K% v; D# ]! |! G% l - /* CPU发起I2C总线停止信号:当 SCL 是高电平时 SDA 线由低电平向高电平切换*/* Y2 Y6 E; [0 Y7 x+ z
- void i2c_Stop(void)$ f' ^5 l. f" n2 @0 y
- {/ a8 r1 f$ b) t7 F5 S" n& Q2 @ j8 @
- I2C_SCL_1();) l3 a* u* E2 G" u" A
- I2C_SDA_0();
* z) K1 V. b' J) B6 ] - i2c_Delay();- P4 M# v( h, P. }
- I2C_SDA_1();/ n. e. Z& m; H9 X
- i2c_Delay();7 g7 T& N) }3 u! s" L! t
- I2C_SCL_0();: `5 T; r. I" d& ]) Q' H. Q' T9 g v8 o
- }
2 \$ m/ {% [! }( r! d6 f - /*CPU向I2C总线设备发送8bit数据*/
5 h% q4 L: A W1 [6 l. e& L. Z r - void i2c_SendByte(uint8_t HS_Byte)4 ]! [* G0 O* _: b4 K( W( z
- {
' I0 V# {0 M" l' r3 O, @( r/ ] - uint8_t i;
5 L" @ R5 S$ A& S8 w' C$ A - for(i=0;i<8;i++)4 n1 k8 W* }/ f) u. L- V
- {6 X: O' C! b8 ^1 F8 k
- & I0 o: s( ^. o; J3 w
- if(HS_Byte&0X80)//先发送高位. \* T D% i& Q0 ^+ [
- {
8 V* C" K/ x1 H# M - I2C_SDA_1();6 G0 `2 N# g7 R, f A0 S, i5 C
- }5 Q! T; `9 O# a8 R H. O
- else6 A( s d! J1 E+ H9 j6 H
- {$ q5 L% |( Q! w" J
- I2C_SDA_0();
7 L4 C* x; f m1 q6 [ - }
" n8 ]; R7 P5 K4 A/ H - i2c_Delay();; b* z- h% [, }1 k$ p# N! ], C
- I2C_SCL_1();
6 Y0 x9 m& ^6 N3 m* l - i2c_Delay();
6 g& }& `1 _4 M8 u! k - I2C_SCL_0();- x1 s I& P! U
- if(i==7)//发送完成一个字节,释放数据总线,接收应答/非应答信号
' r2 u& i+ [: A- t/ K4 { - {1 S; ~1 H7 `- W- v" \0 m1 v
- I2C_SDA_1();9 s1 _' E, j3 K1 Y
- }
- @$ p) R: }5 n - HS_Byte<<=1;//左移一位
?+ r( v4 z; }; b - i2c_Delay();
6 @$ o: W; W2 M1 m# v - }
7 c+ W) K( A/ U - }
) \' U& f# f0 ]' B$ [8 ` - /*CPU从I2C总线设备读取8bit数据*/6 i6 y# u5 L9 d6 i |3 F# t
- uint8_t i2c_ReadByte(void)
* ?1 d* w0 T% {6 W6 O - {% q! |- j! f: P; l, a, R, p
- uint8_t SH_Byte, i;" J2 ~& C9 B* P2 {* Q/ A+ h4 l
-
0 \) T! B' O! W* D0 w2 } - SH_Byte=0;
; ?# a3 M( o/ U9 _ ]* D - for(i=0;i<8;i++)//先读取高位
! A9 v8 U2 S& w; T - {! o, r2 @ S) ]+ |
- SH_Byte <<= 1;
5 I0 o" l* [! e8 W - I2C_SCL_1();+ ^2 O1 n, H- |; N b+ |' n
- i2c_Delay();, _! f- [7 l& [0 C6 ~
- if(I2C_SDA_READ())' U/ F+ v$ Y, Q
- {3 X) R- |" ~0 D2 D: X9 C, E/ F
- SH_Byte++;
( K9 L, s4 m" Z: A# u' S- l - }
t& N* f$ }; `4 t - I2C_SCL_0();3 n" e5 }3 ] M8 h% l: Z0 B
- i2c_Delay();
9 U3 f9 p: Z8 t7 i - }
' O) ^+ F5 |. E# i8 I - return SH_Byte;% M0 E9 I' s" b2 V* ^/ ]
- }
* W' j" G, g' B; c6 a# } - /*CPU产生一个时钟,并读取器件的ACK应答信号*/
~7 M, I- _9 p6 a - uint8_t i2c_WaitAck(void)
, {# k! K# ]7 O/ Q& R - {1 o$ |' {1 y" a- w E% n; ]
- uint8_t re;9 {+ \4 W3 S1 s, p8 M8 T
% ^' _# O* r! G2 w: G! |5 Q- I2C_SDA_1(); // CPU释放SDA总线 ! r; }9 w# `" `4 C- e) r% f5 y( }( y
- i2c_Delay();
# v+ U6 s5 `5 R) n* F - I2C_SCL_1(); //CPU驱动SCL = 1, 此时器件会返回ACK应答 . N1 [/ |; i/ z+ r; C! X6 K
- i2c_Delay();4 W7 D9 f" K, [2 ]. M
- if (I2C_SDA_READ()) // CPU读取SDA口线状态
$ e- v4 w& f7 y/ {: j - {! b4 a1 K( P$ U( w: L+ Z
- re = 1;
8 R1 y2 l! ~! I. _- v, A, X, Q. ^ - }
" P. d5 I' m! F' `( \' m - else
/ X) D0 s) k$ L# f# i - {4 J; C2 l( N2 ~( v# Y' ]& c
- re = 0;, L1 W: B1 t+ U
- }
8 A2 F/ P k, n8 V! U8 w6 r. T( c( K, U - I2C_SCL_0();
0 u _: `4 \) a - i2c_Delay();
y/ P0 J1 s: [. `; T - return re;# J5 D9 W Q& E. d9 A4 T1 i' X
- }
! A1 Z* n9 R3 v2 C* J" }; d - /*CPU产生1个ACK信号*/& l7 L" I8 E; C/ }0 y
- void i2c_Ack(void)7 n4 _# n' P, {( R+ J8 t2 @, Y
- {7 g, f; R0 \2 H. x' y
- I2C_SDA_0(); // CPU驱动SDA = 0 ,应答信号9 ^5 Y. X+ i! K6 e e7 u
- i2c_Delay();) p/ }, D( ~! ]: L$ X/ J
- I2C_SCL_1(); // CPU产生1个时钟
( J9 ~& \! `2 _, ~5 I: p6 | - i2c_Delay();
& c9 |" N6 ^. }; }2 i - I2C_SCL_0();
% j3 S& \ G8 o# c/ h - i2c_Delay();5 a& T' l1 c/ m6 S: y6 G
- I2C_SDA_1(); // CPU释放SDA总线
$ D5 B6 c) h! M: ^& b* E - }6 t/ N* r: p" c# X
- /*CPU产生1个NACK信号*/3 I. A; h, @+ s( f% P4 E1 r; n
- void i2c_NAck(void)8 l- Q, k! g2 m$ Z- X. ~- U
- {9 `. x8 U2 K& g
- I2C_SDA_1(); // CPU驱动SDA = 1 ,非应答信号
@* C: |# f3 r. X5 y - i2c_Delay();
" O" ?+ x+ q, U d( j - I2C_SCL_1(); // CPU产生1个时钟 ; U0 e, B5 B9 g
- i2c_Delay();7 t: t3 }0 E4 ?! q
- I2C_SCL_0();
9 C# F0 W; a4 ]" ?- _ - i2c_Delay();* Q) C3 y o3 _8 D
- }- T- D0 ?8 w$ i$ n
- /*检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在*/1 f( u/ n8 T- s% r3 c, E% h Z4 L' @
- uint8_t i2c_CheckDevice(uint8_t Addr)
+ Y1 [; e) }! Q5 Y' Z - { P! H. A( o3 I1 X: W9 a
- uint8_t ucAck;
5 |# q( G! ? M C2 {/ D0 q/ S/ g
5 O4 _5 w( Z0 T! x& A- d+ }- i2c_Start(); //发送启动信号* l% H+ D2 X. e& ]; \
- ! t0 x: b. i2 c* S. ~4 t0 u8 O
- /* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */0 V' \) w2 s1 Z
- i2c_SendByte(Addr|I2C_WR);/ Q0 }3 O- c+ R7 U9 u; O
- ucAck = i2c_WaitAck(); // 检测设备的ACK应答
; S$ E% P6 i( P3 [6 c/ p
2 m/ U, M& H/ k: J" F- i2c_Stop(); // 发送停止信号 # {- \* O4 }) ^2 k
% x) d2 G$ K# ?- return ucAck;" `. H" Z( `/ T( W
- }
复制代码 + o- S' r% {4 u( X( u2 Q
4 K, I, _, V3 }% V/ C(2)使用刚才写好的IIC的接口编写对EEPROM的读写、擦除等程序。
5 S2 \8 J* @# b9 z2 [4 ^; |/ T% ]% _- g5 w
还是一样在i2c_ee.h中,进行相关代码宏定义,提高代码可读性。还有就是函数的声明。: N0 L' F! N, l, G4 i9 M
( E4 ~1 E7 y0 v+ I$ f& n- * AT24C02 2kb = 2048bit = 2048/8 B = 256 B
$ b7 a2 T0 i# u4 a - * 32 pages of 8 bytes each6 x+ `% `8 b% M' H
- *
2 m' C2 x8 y" J- Y5 k3 h+ C" } - * Device Address
) f; O# Z+ W, k. q: ~- a - * 1 0 1 0 A2 A1 A0 R/W4 E; G- t+ m( N+ _- f% m4 J/ H
- * 1 0 1 0 0 0 0 0 = 0XA0 g3 p. Z( P( y6 R3 [% h
- * 1 0 1 0 0 0 0 1 = 0XA1
( [/ t$ l/ ~% {, a6 K- i$ N - */* K; l! D- ]# C& j/ u. C
- 6 P+ P/ K" n" N) g Z( Y
- /* AT24C01/02每页有8个字节 8 y7 X" \5 t' }: G K
- * AT24C04/08A/16A每页有16个字节 R4 L* }$ f* T/ x
- */
4 g' p0 Z5 r. g* ~. a5 F3 F5 L - - ?* k5 Q. A+ L T, W
/ z2 }" M" v& c# s0 r- #define EEPROM_DEV_ADDR 0xA0 // 24xx02的设备地址 $ C6 F) e4 F: `+ ]5 k3 G' K- d
- #define EEPROM_PAGE_SIZE 8 // 24xx02的页面大小 ' b1 S& f5 f n z4 A( H
- #define EEPROM_SIZE 256 // 24xx02总容量 5 d! Z1 O# n6 z& L, d
- 6 K8 t, K8 C6 d, z T% H: O( y6 L+ }
- . |" C8 \3 f1 r8 y' r
- uint8_t ee_CheckOk(void);, A( \) q6 H# j' L& h5 q8 P
- uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize);* r* ~* X3 t% d+ e- i- S2 r
- uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize);7 z5 Q k1 v7 p% d* L
- void ee_Erase(void);
. \9 V$ L, M9 N3 ?$ m8 ^; O) ?6 E - uint8_t ee_Test(void);
复制代码
, i4 {7 c8 A+ o" T) d/ s& G5 D# v9 T& g6 w, A1 i' b- y2 B
在i2c_ee.c完善相关函数
/ `/ A& ]: i( F5 ?" R- U( I" i
8 |$ v2 J' n/ N& R. t! z/ m: i- /* 判断串行EERPOM是否正常 */
9 Q1 |, L: ~9 {7 ]5 s5 F! U - uint8_t ee_CheckOk(void)# i8 |4 c0 G1 {( W0 B* m
- {3 ?% s: R1 m+ C+ b" a- }1 k/ x, G
- if(i2c_CheckDevice(EEPROM_DEV_ADDR))! @7 }" X* K, S4 c$ h/ @# b9 {
- {0 P5 s) ?) e7 s9 [& }' x7 u
- i2c_Stop();//失败后,切记发送I2C总线停止信号+ u& k* t* Y' m
- return 0;//设备异常' q" E) ]- h6 s( H9 I8 Z; k
- }
8 P$ {/ x# [3 ~ - else2 t3 P& C( u% o) w8 u
- {, b/ A% {- ~5 p* [1 M; _& Y6 l
- return 1;//设备正常
% n: C" [( o( ^% T - }9 c0 K: C$ F8 B' Y5 c' i
- }
0 @! h( i+ p0 b7 G5 D
" w/ Q' ^3 g9 ]5 ^/ p5 c+ ?- /*从串行EEPROM指定地址处开始读取若干数据*/
* l% Q1 _* n$ Y0 |, y% c - uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
% n+ e& |/ E: d, h4 v+ H0 p5 K9 f - {4 J% }9 u& Q/ _8 L5 n6 M, }
- uint16_t i;
' r6 x4 Q2 n! {- U- `- j - i2c_Start();//第1步:启动I2C总线& M7 Y! S) m6 ^- O" b3 D/ l
-
B' B) @( e# W# T4 ~ N - i2c_SendByte(EEPROM_DEV_ADDR|I2C_WR);//第2步:发送地址+写指令
/ I$ _3 o. l7 ^! R - if(i2c_WaitAck()!=0)//第3步:判断设备是否响应
$ k* l4 {) }3 A- a* ]' @ - {/ W! U7 A6 t% x; T
- goto res_error;
$ W- p- N: \+ d+ o/ A% K+ [ - }
! u" f- ~7 @# r) k& j. t5 C2 } - ( a' ^, |2 [5 A$ c2 F; M% G/ Z
- i2c_SendByte(_usAddress);//第4步:发送字节地址
" N" {' v- ~, p0 {: u+ j - if(i2c_WaitAck()!=0)//第5步:判断设备是否响应
P* c' f# w& g7 Q R - {" A* j- Q6 a5 y2 g0 |0 g( ~: k" H
- goto res_error;
% E' ^: B# p4 O' y& { - }
. \- }8 v0 W" p) Y0 D -
3 y4 ~. P, o0 p7 c1 Z* X9 N - i2c_Start();//第6步:重启I2C总线,开始读数据8 p3 b& I& j; s% r2 g- p2 q
- . T+ x9 g& j4 k8 L- [
- i2c_SendByte(EEPROM_DEV_ADDR|I2C_RD);//第7步:发送地址+读指令
! L6 d8 o. \: ~ - if(i2c_WaitAck()!=0)//第8步:判断设备是否响应9 i0 Z& L( \. X3 I2 r1 P5 o
- {4 W/ k+ [4 m' ]; S( ^% [1 S# N
- goto res_error;
( y3 j8 v/ P" g. d" E - }. y' c* m$ V8 G5 ~$ x. f- G0 k
-
; i) U0 i' t/ O6 Z- U0 V4 P2 W9 N - for(i=0;i<_usSize;i++)
8 D1 A7 f0 j: c - {) |3 _* w5 Q! i m. }
- _pReadBuf=i2c_ReadByte();//第9步:开始读取数据' r% W3 e: K$ N' [2 D8 @6 N1 {. r
- if(i!=_usSize-1)//第10步:在接收_usSize-1个字节前都发送应答信号,最后一个字节发送非应答信号
! J4 S/ h% T0 L8 [! c( n$ T' m - {
9 a- P, q$ K2 W% O9 e! x - i2c_Ack();
/ t; l6 F$ V- A H( `" U - }
. M! |- n4 a* P0 R- m7 i9 f4 w! { - else
( Z. ?. r8 @' ^) M6 u1 F/ y5 K - {
" b4 B( z' w3 H6 K' `; r: t - i2c_NAck();
* C; X$ {5 g/ K6 s* E - }6 ]9 U! |% g& Q# B
- }5 P) H& X4 z5 h9 v7 o2 ~) y
-
; S G8 x2 R |9 [% u6 h - i2c_Stop();//第11步:停止I2C总线
" W/ {8 h; `' H5 s8 u - return 1;//读取成功0 Q, }: V: d |2 r8 y6 d+ _
- 5 z9 Y! u7 x5 \! Y4 B
- res_error:
8 _' Z5 h: \( t - printf("\r\n响应超时\r\n");' Q7 Q7 }( d: l& r4 u, ]
- return 0;//读取失败4 O% P+ b$ k$ ~* N
- }
( y2 y( N3 t, S& E( o1 l# I - ; M% J# n- W1 ^5 t' P% Y ^7 t
- /*向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率*/1 o" o w8 z z4 `. t1 f
- uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)! w7 {& x* b4 {1 o( k: v
- {& ^8 n; Y( s3 A
- uint16_t i,m,Addr;
* N1 K/ N2 z* _5 M- F# E \ - Addr=_usAddress;* }9 g8 U/ U! a
( ~% @- e6 H( v. r# p1 S- for(i=0;i<_usSize;i++)
, R1 a, T7 Y" _2 T8 r6 E2 A - {
1 n1 w% b$ k7 T0 z% S) v' {, U -
: H' t8 O. o- s6 A, d - if((i==0)||(Addr&(EEPROM_PAGE_SIZE - 1))==0)//当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址
6 c: V0 M7 Z5 a h: e/ M - {) f I# S+ N* d M/ s1 T, j
- i2c_Stop();//发停止信号,启动内部写操作) {# U$ G$ v7 A: z! \" ]6 V8 t4 [
- for(m=0;m<1000;m++)//通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms,CLK频率为200KHz时,查询次数为30次左右) {" o$ u3 d: u' @
- {0 t4 g9 t% S3 @6 \" J, q& B) t
- i2c_Start();//第1步:发起I2C总线启动信号
/ a9 f9 |% a. ]5 |: N) ? - 4 o! G( r& M; I
- i2c_SendByte(EEPROM_DEV_ADDR|I2C_WR);//第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
! M; x$ B- Q2 K, x% h0 `+ x. L - * I8 x% P" Q& Q
- if(i2c_WaitAck()==0)//第3步:判断设备是否响应* s% }5 R) b' E5 i; H
- {4 E9 `$ g5 ^/ q$ S8 J# }( ?
- break;/ Y8 E0 @9 H \0 v" ^
- }
, J* f) H9 R( P: n# j6 M - }
$ Q# M+ w# `6 q7 h' `* O - if(m==1000)//响应超时
+ m% }8 m0 e: }' _- }: H - {
# G) K5 O) \5 L - goto res_error;6 r" X& A8 j3 }/ q: s
- }7 d4 \! k/ w; P2 Y: r+ U
-
& y5 m( s1 J& {/ z6 k4 G7 _. [7 d - i2c_SendByte(Addr);// 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 * h+ A" Q( R' O6 L1 y
- if(i2c_WaitAck()!=0)//第5步:判断设备是否响应
3 `2 u" _5 J& Q' N0 C0 d) [ - {
7 @7 I: w! `" U' h - goto res_error;2 o9 D" c; V) P: }: I& E
- }: Q |8 Q7 o9 v
- }
' ]8 a' X8 @1 i4 p5 [+ k: Q - " C0 d. k# R2 k, l; Z5 D$ \# e7 n: X
- <i> i2c_SendByte(_pWriteBuf);//第6步:开始写入数据) [! B5 H' e% H; ~, l
- if(i2c_WaitAck()!=0)//第7步:判断设备是否响应
4 q# L2 I& z8 M0 l - {
' C9 Y% ?' F- P/ w) N. t$ `7 T - goto res_error;) E( k7 Z7 J6 W( i+ W6 e
- }
" x! C/ l T. ]' `6 W7 q - Addr++;
1 S) g7 P8 M7 E" q! U. C0 a - }
' W6 l" p, E8 S( y7 C0 Z -
# r- a( H. M( U/ U0 h" v) T - i2c_Stop();//第8步:停止I2C总线% ~& q3 B& G; L% T) ?) C, k
- return 1;//写入成功* x, p2 T o: P
-
( q& e2 N8 V* a; y+ l - res_error:, E! T$ ^: ~. h# i4 o P
- printf("\r\n响应超时\r\n");
/ E5 ]9 {3 O" L4 @9 a - return 0;//写入失败
0 n) e. E8 M% u ]$ m4 W- P - }0 _* H a, Y2 O8 p! |
: f e7 {" u2 S F7 A8 T- /* 擦除EEPROM */' Z9 x' L- A2 |) L
- void ee_Erase(void)
1 p, C m! |5 }6 {; D9 I6 K( \& W l - {
4 m" c! k' a6 L - uint16_t i;
4 m3 Z2 f% v0 q* y" v/ m9 `* H% ? - uint8_t buf[EEPROM_SIZE];* _4 u- r% B3 r. Q6 Q/ M
-
. z, L$ t% C& W4 j# [$ y' _2 O$ J: F - for (i = 0; i < EEPROM_SIZE; i++)// 填充缓冲区
7 z. A# d4 V& o6 L6 f& Q* g. ] - {! F/ j( }6 U8 f9 z$ W" r
- buf = 0xFF;9 @7 a9 Q, l$ Q3 B6 s" j* I
- }
* ~( X% H9 @0 U ~* z9 k5 P# e -
0 A4 P4 Z Q, C/ g/ m - if (ee_WriteBytes(buf, 0, EEPROM_SIZE) == 0)//写EEPROM, 起始地址 = 0,数据长度为 256' r: {: c. p5 A2 p- z! p/ m
- {; L/ @% R/ x8 v5 n' _3 z' m3 o, a
- printf("擦除eeprom出错!\r\n");
& m4 z+ `% W6 ^4 U - }
( Y8 K6 h' `& c# I$ ?' e - else
- i3 }0 t4 G) H6 J( X - { w2 A2 [: k4 j1 T* u' J" {
- printf("擦除eeprom成功!\r\n");
$ _2 _$ B& B4 H! f - }) p- r2 v2 g# O7 l3 ^2 ~7 y- i
- }
4 m- ]" q2 A' o7 \% {" a9 ?& N
& N6 b, h" ^; }- /* 测试程序 */; o. G; d! p4 ?7 z/ v
- uint8_t ee_Test(void)
1 H6 L& b; Z; {' B' H% M! h - {
; Q9 N) j! d/ B6 p - uint16_t i;8 |# a' U! R! [1 ^
- uint8_t write_buf[EEPROM_SIZE];
) I% }8 v8 \4 d+ T: D' l' x - uint8_t read_buf[EEPROM_SIZE];
. B! t% r/ D# c$ I3 n7 u0 B7 {8 q! u- i4 K( y
. S" M, j6 F6 `* \- if (ee_CheckOk() == 0)// 没有检测到EEPROM - {& Z2 R( E) h! Q* D" i
- {
9 C4 D5 Y2 {6 U% @2 H4 e# z0 }3 L/ ] - printf("没有检测到串行EEPROM!\r\n"); y$ j K' p8 Q9 N* r6 I
- return 0;
7 z! C- l% y/ ~* y% K. @3 i7 D - }
2 j& U5 F5 z# g - ( k. X# p( i+ ]+ A: Q5 F
- for (i = 0; i < EEPROM_SIZE; i++)//填充测试缓冲区
0 Y! a4 m4 s' S& f+ f9 x - { ) d" w9 B$ V% Y% B2 ^
- write_buf = i;
! O7 e3 p" i# t8 f - }
9 B+ s: k$ ^, D3 d8 q - 9 ?: U1 {4 q0 x5 W& m
- if (ee_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0)//向EEPROM写入数据
' n0 e" I& [$ I9 V7 B - {3 A6 }# a$ z0 T2 J8 ?
- printf("写eeprom出错!\r\n");" G8 D- i3 r# R( _' b
- return 0;) k4 N5 m# j1 Y R$ A* E
- }) _1 Y* e. U. \+ e( i& G* L
- else) h' d1 L3 @6 O- y3 o+ _5 ~
- { : ?# i( _ u9 @
- printf("写eeprom成功!\r\n");
. d2 v8 ]+ y4 E5 y - }; p2 x0 Y" i0 a2 u$ b/ o0 R
7 I- r; S5 \# T- HAL_Delay(1);//写完之后需要适当的延时再去读,不然会出错
1 f5 x5 e( J. {" a3 x - , t% C L! h3 B. V) q5 F. }, R0 A7 N
- if (ee_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)//从EEPROM读取数据( m. i5 B7 G# q2 z
- {
: B3 W. V9 C! a0 q8 c. M7 H/ q - printf("读eeprom出错!\r\n");7 Q$ ?' m+ o; d1 |- }& I
- return 0;- b! t1 ~( u/ F
- }( I0 M# j& s) y+ ?) B' t
- else: Q: Y$ r! K$ x2 b
- { ( |1 I1 M, v# X0 M" f/ U
- printf("读eeprom成功,数据如下:\r\n");* [. P' K3 ?% u" T) v
- }: Y' T1 J& K3 ` ~( z$ u( \4 D M
% ^0 p! k' z) |- C/ O3 ? G4 U3 t* i- for (i = 0; i < EEPROM_SIZE; i++)//比较读取和写入的数据是否一致
_, N7 Y) P, N# \( K - {
; e9 C) P5 V; w1 _ - if(read_buf != write_buf)' O4 Y- Z$ E) Y3 ?$ H7 M
- {
* n; ?. E, M. m - printf("0x%02X ", read_buf);
8 a7 h) B# |3 C" a& F; A5 o% b2 } - printf("错误:EEPROM读出与写入的数据不一致");
_% E0 y9 S9 M - return 0;
% G* h1 ^" t( b4 @0 I - }
% [$ F. f, P( L% [1 o# K& o- v - printf(" %02X", read_buf);* D( ^ E! m! Z4 x# a" Q
-
+ a0 ~3 a' D o& ~ - if ((i & 15) == 15) B0 P5 P( h0 ^
- {
; b; B( [2 E* P6 j7 g - printf("\r\n");
/ S9 z! G- T7 K+ C - } 7 }& A0 A V/ U0 D0 @
- }6 b2 s7 ]/ W3 K x$ h$ q. [0 |
- printf("eeprom读写测试成功\r\n");
8 U" L/ a1 A' O - return 1;
( u# ~) l Y$ [2 n' j. P+ Z- `% ` - }</i>
复制代码
4 X! i% S! R2 z$ t% Y3 v3 }& M( h
. v, u6 n% _7 E5 D3、在主函数中调用i2c_ee.c中的测试函数即可。
# F* k3 m( W' x, a+ S5 W/ r* n: n0 E1 w( a/ Q% Z
2 t: U+ A4 B1 N8 \" Q2 N关于软件IIC有几点需要注意的:. Y: j; U& b/ p) S- t" \7 C' N
, L! _, G1 x& K! ~4 a# B1 d
1、我们传输速率的设置:硬件IIC,标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。这里使用软件IIC的设置SCL频率200kbit/s左右,大家可以根据需要设置gpio.c中i2c_Delay(void)的延时时间来修改SCL频率。
4 e6 h R0 y! y0 _6 b$ m
+ s( V- S8 T1 E: y% Z5 b- }# K2、在i2c_ee.c 写ee_ReadBytes、ee_WriteBytes,可以发现步骤有一点区别,ee_WriteBytes在多了一个for循环来重复判断设备内的写操作是否完成,而不是直接采用判断一次。虽然后者的写法在实验结果上也没有出错,但是为了代码严谨性,避免不必要的错误,选用前者。- C0 v- e( Z. b# _* k
5 q# Q) F! z" B5 }$ Q. h# t
3、在写入数据到EEPROM的时候,根据数据的大小,如果需要读写的话,需要进行一定的延时,否则会错。% v3 ^6 C* D( N6 z2 z- A
, U# a1 m/ p/ d: S% u—— —— —— —— —— —— —— ——硬件IIC—— —— —— —— —— —— —— ——# k7 C. A& r. N5 @
! C* _! O7 J4 q, w: ]; h3 ^- V, ]
二、 CubeMX配置0 N; a1 S8 C; B" |! ]+ o- t
2 ~+ C1 _1 d$ X0 F) {7 Wstep1-step4与上面软件IIC模式一致
( B. v# f. V9 N9 W0 Z( I: r/ F
+ n8 O- U4 ?- {' a/ o5 R' P( K2 rstep5.IIC外设配置,这里选用的I2C1,因为使用的主机模式,所以从机参数默认即可。! |0 w4 I/ B* P j; E9 g" X
6 M( Z( _7 p1 \8 b# Y7 v# \ L
+ y2 C. t- l9 x8 n+ k! _7 [# J% ]. g- R- ?! q I1 N9 T3 ~+ T
到这里关于硬件IIC参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。
" T& q! n3 z* j, z& I' d7 c0 [1 s s3 \( A; {, g# D
三、添加功能代码/ V7 k3 n5 `5 Y
( Z0 M! q) e1 ~* x. P1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。(代码同上: 软件IIC)
/ D3 @2 H) e7 V8 \
0 i4 N- m/ x- h; L; i2、在i2c.h文件中添加相关的宏定义以及函数声明。
. P. P3 ], \, n
' R0 R. t, h) H% R, B- #define EEPROM_ADDRESS 0xA0 // 24xx02的设备地址
Y( I6 r2 c! M0 x! ?4 b7 _% l - #define EEPROM_PAGESIZE 8 // 24xx02的页面大小
6 y; \7 `# [2 N- R, k9 R - #define DATA_Size 7//256 " t/ s/ _6 Z- F* j4 Z! w# `
- #define EEP_Firstpage 0x02 // 0x00 % k9 T9 `) m7 N# _
- & Z, d6 z7 F1 e& [: H+ y0 K3 e
- void MX_I2C1_Init(void);
* _& w+ |+ D8 u' ` - uint8_t I2C_Test(void);
3 Z9 f0 p7 c" ]/ r$ J: S4 J - void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite);
# C; V" Z$ C7 z- m5 M - uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr);
8 m8 J B+ e0 G5 X; A& y - uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite);: C. ^3 O: L5 ]
- uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead);
复制代码 . Y6 d& G5 I1 e. r' w
3、在i2c.c实现EEPROM的读写函数,以及用于测试的测试函数。) Q( J$ }/ Y+ J& ?% F
; B) a( o! n. V6 s# j
- /*将缓冲区中的数据写到I2C EEPROM中*/' F2 o4 N% ^, ^! ?
- void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite)
) J. V5 A3 p8 z" V. F - {0 n. {% H/ d" x. z9 U
- uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
2 s" g$ _% z! J- ` - 9 _+ ^9 Z4 {; I5 f
- Addr = WriteAddr % EEPROM_PAGESIZE;//判断写入的首地址是否与EEPROM页的首地址对齐,0为对齐
& V! v1 G% ^ W5 g4 H3 D, y - count = EEPROM_PAGESIZE - Addr;//计算从写入的首地址需要写多少数据才能填满当前页+ ?" `+ O: {' h, |1 m
- NumOfPage = NumByteToWrite / EEPROM_PAGESIZE;//计算写入数据需要写几个完整页(地址对齐的情况)0 M U5 g' i( p4 T% g/ |
- NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;//计算写完完整页剩下的数据个数(地址对齐的情况)
}2 A0 |. N5 {, ~- f* d
% `6 G/ a$ h5 B- if(Addr == 0) //判断写入的首地址是否与页地址对齐
7 q5 ]8 F) T7 ?# a. ~/ C - {4 @' K7 |/ q# i- b: @
- if(NumOfPage == 0) //如果页对齐,判断数据是否不满一页0 g8 c/ W& X( H- Q3 A) j
- {; J1 J2 ~) m" X
- I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不满一页,直接写入数据% U- \+ K5 l2 U0 o2 B0 I
- }# y7 ^) s+ M) {
- else //在数据满一页的情况下,通过地址自增方式,循环写入数据(页写入的形式)
1 E# B+ M3 E; @3 N1 S; r - {
. t; N y" | \! v3 r$ U, D - while(NumOfPage--)//循环写入数据:先写入完整页
# x$ B$ O/ O; |% J: d; d% z - {' I7 j$ p" ]+ @ i; J! Z" A5 H
- I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE); }, w i& `8 A' L
- WriteAddr += EEPROM_PAGESIZE;, X0 e5 E- B/ i0 O$ S
- pBuffer += EEPROM_PAGESIZE;5 F& t! k8 Q) s9 P1 R% B
- }
& \4 M4 Q, \; O P/ b% ? ]4 L+ R, D
8 F- l( `1 K0 J$ y4 V" q- if(NumOfSingle!=0)//循环写入数据:再写入不满一页的数据* o- F* e9 U- g
- {
" Z, \! K. g* X - I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);2 D( M; H7 s/ S4 i7 j: U
- }
; \% c2 [% i% P3 z - }( q* @7 F; i* H2 k
- }: C9 O# |6 n6 [1 D) Z" P8 Y
- else
* T( Y' x+ o# s9 Q z0 T% N( G6 j$ U - {
3 i. E$ Y8 n2 K: K2 q - if(NumOfPage== 0) //如果页不对齐,判断数据是否不满一页
9 c7 X8 z, _7 `3 x - {
0 R: Z) I. x, P - if(NumOfSingle<=count)//如果不满一页,判断数据是否跨页% z+ h& x- a8 P0 l% S s
- {, J. u% _& o8 @9 M* v
- I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不跨页,直接写入数据
2 q( T: O/ L6 g; j" v - }& d# `; x# g) t1 ]$ J
- else
! b* G2 _# h6 J8 k7 q - {
8 c" Z' @3 e: s2 I - I2C_EE_PageWrite(pBuffer, WriteAddr, count);//如果跨页,先写首页数据,再写次页数据
+ F) W6 K, |9 M# ?' d2 S - I2C_EE_PageWrite(pBuffer+count, WriteAddr+count, NumOfSingle-count);$ @5 Q5 m2 D! B' g+ q! Q8 x
- }
& P* X0 A2 i; v( @9 N, Q - }2 a- A9 m1 s0 P7 H
- ! x6 u" m% B! {+ B( x# H6 V6 U4 u
- else
$ i) b9 \+ I$ T6 y+ v& C- E0 Q2 q - {
" X8 m$ k/ ]. o& s( h0 Y, R1 m# l - /*如果数据满一页,对数据进行分离*// l& l" E R# w- e% N
- NumByteToWrite -= count;//扣除第一页数据个数3 g" p; g# O0 b/ P
- NumOfPage = NumByteToWrite / EEPROM_PAGESIZE;//计算写入数据需要写几个完整页$ S8 A7 Z2 O: r5 `: W
- NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE; //计算写完完整页剩下的数据个数- m" Z4 C( N" t, `# z+ T- ` u3 D5 n9 e
- 4 R' A; y: \+ w7 @. O; I( O. ^% T& f
- if(count != 0)
! L( W/ X8 x8 j0 p5 t - {
! x3 o* T" f0 a2 g$ U9 m8 q - I2C_EE_PageWrite(pBuffer, WriteAddr, count);//写入首页数据) X! u5 R- U9 P; d' G2 d/ y' V' m
- WriteAddr += count;//写地址自增
" t% A3 n4 K2 i1 X - pBuffer += count;//缓冲区指针自增! m8 e2 O; a0 y1 c( k
- } ; Q$ T4 o4 E# E3 o# _! M
- + G2 A9 O7 ~" x: Y5 I$ P9 g" g9 ^
- while(NumOfPage--)//依次写入完整页的数据
0 f( m5 S. J) ?* L - {3 y+ M: i( A% m8 }
- I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);* [( c/ U$ L+ [* m: F
- WriteAddr += EEPROM_PAGESIZE;//写地址自增+ S$ ?" o: c( Q, m1 Y @, E/ f% G! g
- pBuffer += EEPROM_PAGESIZE; //缓冲区指针自增
2 s* }" ^3 \0 L1 {& k3 h' ?! B2 T - }* m$ A4 v+ Q' F+ ?
- if(NumOfSingle != 0)//判断最后一页的数据是否是填满完整一页的$ {+ n3 Z2 F% y1 t1 }1 o
- {
/ h3 A A) }5 f, _% v0 d - I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); //写入最后一页的数据& Y' j9 f% `$ M8 D
- }
+ {& L( ?1 v* w N0 [; A) r - }: R, `8 ~+ I* ?8 s' {
- }
3 u1 m% W' t! Y - }: v E, V6 [) Q4 P; R0 b8 o
- 1 Q. i1 q$ G3 }. V
- /*写一个字节到I2C EEPROM中*/
8 S+ l$ {/ t% O, n* `# Y6 N - uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr)
( R' l9 o1 j' |; Q6 s - {5 g. V- h" U$ `# o
- HAL_StatusTypeDef status = HAL_OK;2 z7 y2 J3 c1 `+ ]; ?# E1 v( z
- + f; S/ O X+ Y1 t
- status = HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDRESS, (uint16_t)WriteAddr, I2C_MEMADD_SIZE_8BIT, pBuffer, 1, 100);
- G& q7 M! @" z' Y& t - 5 b4 z4 D+ r4 Q: @* r5 r$ }* z
- /* 检测IIC外设是否传输完成,并准备进行下一次通信 */
2 U* H' n+ d* g2 e - if(status != HAL_OK)1 m) K' \8 K8 [+ ?7 g9 Y/ q
- {. b( E. y( N+ ~4 v k
- /* Execute user timeout callback */
. m! f" D. [+ y8 E - }3 n! \$ L1 L4 P. B( b; w
- while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
; Q7 Z, _' E. d2 _9 X/ G( s - {
% X+ o+ V* j( }1 k* T9 g. E - : v8 u1 n! e3 {: S
- }
/ p/ n3 Y1 r% _4 b% z, O' S; F - /* 检测目标设备是否准备好进行通信(即目标设备是否在进行读写操作)*/' _2 f& p4 Z5 z) \( ^
- while (HAL_I2C_IsDeviceReady(&hi2c1, EEPROM_ADDRESS, 300, 300) == HAL_TIMEOUT);2 @' [+ N5 S# v+ u* |+ h9 ?( @
- 9 o2 r, p6 W/ [
- // while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
5 Q" N# V* {$ B* G+ v: X - // {
. {: M8 [# }$ e0 x5 W6 r9 ^$ Z - //
) Y4 ]: {: m! X1 u Y - // }# [$ ?3 u. }6 h" i- }
- return status;$ J) D+ H, t, s7 b- \+ h; ^: A
- }
% G" G3 b# K4 J- d3 M: v) p/ W( y) N
5 O: C9 \7 N7 O+ `9 ^9 e' D& x- /*在EEPROM的一个写循环中可以写多个字节,但一次写入
: c5 m. Y8 f: Y - 的字节数不能超过EEPROM页的大小,AT24C02每页有8个字节*/
# m" N& E( a; w0 l1 o - uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite)
$ ?- |9 B& d. H - {
: | o6 o8 j) q5 i6 S - HAL_StatusTypeDef status = HAL_OK;9 l& J) j; n) {8 \
- status=HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDRESS,WriteAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t*)(pBuffer),NumByteToWrite, 100);# \- c9 [2 I _
- /* 检测IIC外设是否传输完成,并准备进行下一次通信 */7 t1 A. \, W6 \
- while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
1 J4 U) l* `7 {' {& I% K6 U D: c - {
1 z, K0 h0 `$ D8 D -
* l M r C g. E4 G# c* O( F - }* D4 O5 \' d/ d( ?
- /* 检测目标设备是否准备好进行通信(即目标设备是否在进行读写操作)*/1 C- X$ U' Y( X& m0 K9 ?
- while (HAL_I2C_IsDeviceReady(&hi2c1, EEPROM_ADDRESS, 300, 300) == HAL_TIMEOUT);
/ |% |7 i2 [+ |& S
& ~9 S; S4 n1 x8 ~8 ?; ?( j- // while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
- o( u8 Y" q1 b- t, N0 l. A - // {
4 d0 k( ~. _) l' u* I - // " E6 S! {' @0 D, V
- // }' o% X2 K6 y& Z
- return status;$ S; N1 @3 T. |/ J+ h
- }
5 M3 G! Z; L, j' z
. P J3 G' u4 O: u- /*从EEPROM里面读取一块数据 */
: w2 K1 R& j( x) v' z - uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead)( e9 U( @* P- H
- { J" P8 Z2 [' O
- HAL_StatusTypeDef status = HAL_OK;
; @8 q6 | |6 M& k2 N: m+ C - % l& R0 B C- e) G% K% s; C
- status=HAL_I2C_Mem_Read(&hi2c1,EEPROM_ADDRESS,ReadAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t *)pBuffer, NumByteToRead,1000);
! D- A% B. i/ {/ B0 C4 p8 x0 `, c
( C2 x7 w4 B4 P; B7 U: d% x, P" d- return status;. R( o) g1 M5 {1 r; e) U
- }
. t& c9 P0 z# T* i( A# l$ b' R! k
1 l$ o$ r- O& W+ Z6 s* r0 ]- uint8_t I2C_Test(void)
2 o) p, D. V5 k1 a# B9 H( D - {& c4 k% Y2 n. O! d* d* F
- uint16_t i;( A4 y2 t( {: Y7 w9 k1 y8 B/ {
( {+ ~& r; V3 _, [3 `; l9 B- printf("写入的数据");
- A- {3 o* |8 J
! ^& U/ }& S, U( i6 B( P! J6 h- X- for ( i=0; i<DATA_Size; i++ ) //填充缓冲
% d- v& E5 g3 c - { 8 G2 f2 E7 f# ~
- I2c_Buf_Write =i;
+ _+ s7 j5 x _4 X0 M' g1 | - printf("0x%02X ", I2c_Buf_Write);
0 G1 I# A% a# f( D1 v - if(i%16 == 15)
% ]4 d5 s6 f+ F L/ u! q" c - printf("\n\r");
) ] {, [( v( y* @! O; I5 W4 J - }
- ^& D) Z8 I2 C0 V8 K
9 x: P4 A$ P9 ~7 {) s- //将I2c_Buf_Write中顺序递增的数据写入EERPOM中
# C) x% A6 c+ F - I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, DATA_Size);' \ X8 R9 V8 H3 y
" f' Y" n) X- ]! d- printf("读出的数据");
C ?" x& {+ \1 k: @4 z2 X - //将EEPROM读出数据顺序保持到I2c_Buf_Read中
5 k+ Y6 F! F. W% ~+ F: Y - I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, DATA_Size);
! D& \0 j+ N5 N4 Q - //将I2c_Buf_Read中的数据通过串口打印
0 U" c# L w1 X/ G r - for (i=0; i<DATA_Size; i++)
$ @. I( Y3 `* I' m# j - { ( j+ b+ d3 S9 f: {6 G
- if(I2c_Buf_Read != I2c_Buf_Write)% o4 C1 A4 \+ v( z6 B" u
- {& l$ a+ `+ f5 s7 V1 a
- printf("0x%02X ", I2c_Buf_Read);
! M' j' R- l) W4 J1 ^" I# l - printf("错误:I2C EEPROM写入与读出的数据不一致");
7 q2 y( C% g. C# I1 h/ { - return 0;% a% G! t+ y, g
- }
k4 n0 J2 h6 X" L+ @( p - printf("0x%02X ", I2c_Buf_Read);5 @8 I# n0 U1 z; y! I4 g) Y4 P
- if(i%16 == 15)
7 D3 k& O. F7 x [ - printf("\n\r");; F& I( x4 }. e. ^8 T
. y+ g3 A) c" O( l8 t- }- K( i2 x+ v: u/ H" K+ ?
- printf("I2C(AT24C02)读写测试成功");/ {' b! K% |2 ?" i- c
- return 1;
5 ^' X2 e+ M! K8 A7 ^8 M$ | - }
复制代码 " X9 j' |1 S. q9 _" u1 j
! G+ C& ?9 w+ Y/ S4、在主函数中添加测试代码验证功能是否正常。, ^% m% z: }0 F3 B
. q* J+ x w; K! h* \7 z& c0 K
5 U+ U$ L" b0 Z- d4 z" F以上程序主要参考野火的硬件IIC程序,但是在页写入程序中添加了一个判断,用于判断在地址不对齐,且数据不满一页的情况下,数据是否跨页。数据不跨页直接写入数据,跨页分两次写入数据。代码如下:
# z& y$ A: t; R3 j8 m1 i; {; N* V' T. L1 n
- if(NumOfPage== 0) //如果页不对齐,判断数据是否不满一页9 r/ ?- g6 d% X) i5 B
- { p( Y+ U: s, o. e
- if(NumOfSingle<=count)//如果不满一页,判断数据是否跨页8 x- y7 q9 v" M; C
- {* V4 p" D ?5 R! l
- I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不跨页,直接写入数据2 L6 l8 B, x: F G" Y
- }/ c' }- p/ J+ h5 K$ ?9 \
- else/ T( f- I$ K1 [8 e8 G: H
- {2 I/ U1 |0 m: f# [; Q% E# i& ?9 i
- I2C_EE_PageWrite(pBuffer, WriteAddr, count);//如果跨页,先写首页数据,再写次页数据
" a* ]( R3 K# {+ M) T- U - I2C_EE_PageWrite(pBuffer+count, WriteAddr+count, NumOfSingle-count);
3 I, i! K \, z. J# U - }
5 z& ^5 |6 l4 s) K - }
复制代码
" A6 \3 b! E& t同时需要注意的EEPROM的几个关键特性:(AT24C02)! n3 s7 S/ r, [+ i
$ o) z1 a# r. Q% c4 o
在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址就会溢出又变成0x00。
, ^3 S" S1 ^% u$ e6 }' o' @& r0 A" S# { N) C) U( R% P) C
还有需要有一个页的概念,页写入的时候,写入的数据有跨页现象,需要进行数据分离处理,否则数据会重复覆盖当前页(发送的内存地址所在页),而不会到下一页,这也是我们代码中进行页写入的时候提前处理数据的原因。
0 M2 b) V8 M- K9 z6 z0 K
]4 g$ l6 X, X* v& b
' ^4 L9 N1 K6 X r8 r8 Q! r# l5 q5 a
5 J- [# [' @1 ^- L
写数据的时候需要注意,E2PROM是先写到缓冲区,然后再“搬运到”到掉电非易失区。所以这个过程需要一定的时间,AT24C02这个过程是不超过5ms。
! _& }6 L( h. r3 @
% M) U* g9 V+ ~% E5 r这里总结软件IIC和硬件IIC的优缺点:
Y; ^2 `! w% O8 M8 O& i7 j e% j& r1 P7 h8 ` e$ k* b: E
1、软件IIC可灵活选用引脚;硬件IIC引脚固定。 + L6 Q: f0 d3 `/ }+ e& ?! n( E
1 J% C, c- ^ |5 L9 f% }$ k2、软件IIC需要通过代码模拟时序以及读写;硬件IIC只需调用库函数。 [' E/ a7 p9 |1 x5 ^ C( L
: Z$ f! R3 U: ?
3、硬件IIC效率更高,可以使用DMA ,中断。
* Y+ \) g$ W/ a% R y! D' g5 m/ k" V0 f9 s( t% V( S, Z" H
4、软件IIC代码相对较多,但是流程更清晰;硬件IIC由于使用很多封装函数,处理的过程不够直观,不易理解。
, L' o. s0 D$ z. D2 i4 q6 S: K! M: ]0 Y- `$ {. T
$ H$ e e( c& K/ s( a5 w9 ` |