你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32Cube HAL IIC

[复制链接]
STMCU小助手 发布时间:2022-4-7 15:15
一、原理图/ S. I  C7 G: ]4 V! R7 m4 N

" a% J: M: b2 V6 e3 G MFP5}ZMDG56)O0KGS3_6K95.png
; 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
M@689[00TAE~R73KG~PJQTU.png 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
QTLDH1P5O$FX0EVMBGKP_XC.png
" c5 J( B4 j$ ^# e) a' s( I1 _% H6 I" E* N3 n- D  G0 a& Z7 X
QSQI`M$I}P]YBGY]KI6I)_4.png
( 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
5)KJ}19@Y{PF`%IU20PTMDJ.png
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
(05F88{5QTDXV%@)[L(F`KK.png
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
  1. //重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数1 j- j9 [/ |; U& z3 Q! {- ^; Z
  2. int fputc(int ch, FILE *f)4 {! H, F4 |0 }* @& @
  3. {
    - L% Y! w# ~  ?- s. ^
  4.         /* 发送一个字节数据到串口DEBUG_USART */  E8 M8 g$ s2 M! G
  5.         HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);        
    5 k* U% Y$ W' {, o5 Y- B
  6.         $ u( b' v& \0 [: n4 ~3 Z
  7.         return (ch);
    , E$ O" F! a4 w" ]' B( U
  8. }  ~; U4 r; U# n6 l

  9. 8 a  S) V5 o5 e' h: a
  10. //重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数
    ( g$ ^. {* @# @9 a8 O/ i8 f: m
  11. int fgetc(FILE *f)/ V& R8 I5 e$ U3 ?: B
  12. {               
    # v$ ]; s3 J& ]! \7 u! K
  13.         int ch;
    / C8 a+ v1 e: }* J$ V, S5 X
  14.         HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 1000);        / ^# B* [# @8 `( x
  15.         return (ch);8 M6 B) w2 l, H1 i
  16. }
复制代码

& 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
  1. #define I2C_SCL_1() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
    # J/ K# y4 O3 z1 H4 L7 E( s; [. C
  2. #define I2C_SCL_0() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
    ) L; Q0 B$ @9 I. b* q) _- G
  3. ! t) b6 q9 d' H# m5 k. {
  4. #define I2C_SDA_1() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
      i1 |6 ]/ H- D$ n( d  N+ F- X4 R
  5. #define I2C_SDA_0() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
    # b' v0 V2 `* `" Y4 i' {) J

  6. % U5 L; K# A+ l( f
  7. #define I2C_SDA_READ() HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)$ L# w  h' J; V' J
  8. #define I2C_WR 0 //写控制bit
    - b6 b& u3 q- p+ h* w) T, ^# Z9 G
  9. #define I2C_RD 1 //读控制bit
    2 k0 n3 l- o( J, c) n

  10. / z9 f) F9 Q! R* g- v* `+ T
  11. void MX_GPIO_Init(void);
    0 D$ T( d# i8 J" Z* [$ j/ F
  12. , w* ]" w; C( S1 S4 [' r( Q
  13. void i2c_Start(void);
    ' M1 V; L/ I5 r3 z% x6 {7 y
  14. void i2c_Stop(void);
    % X6 ?/ U* G0 a$ f9 h& D- @- H
  15. void i2c_SendByte(uint8_t HS_Byte);- ~2 ], o6 V1 y& h  _1 N: H
  16. uint8_t i2c_ReadByte(void);
    + l, O6 w' p. Q2 X; R) [/ d( [- D2 n
  17. uint8_t i2c_WaitAck(void);
    / {( o9 B; P2 `  X% y
  18. void i2c_Ack(void);" D! Z$ f* e4 r0 ?, M
  19. void i2c_NAck(void);0 C1 ]2 _6 o0 Y8 A+ U6 Q
  20. 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
  1. /*延时函数*/
    4 Y1 `( m6 e: G4 d, k
  2. static void i2c_Delay(void)
    ( p0 b: Y0 y) ^, f( S" F
  3. {
    6 r2 x$ w- }* l4 I% [3 x
  4.         uint8_t i;
    1 D, ~: e: W% `( g- A+ p0 S! {
  5. / C6 c3 f3 P/ A/ _+ B' h
  6.         /* 
    . W; g+ a( G4 L1 L; H8 A2 L( W
  7.                  下面的时间是通过逻辑分析仪测试得到的。
    / `' t& P- m$ g( W* G6 K( c" M
  8.     工作条件:CPU主频72MHz ,MDK编译环境,1级优化
    * `( ?; I% _$ U* q1 f5 {$ {  I
  9. ; o) ^* P& C5 |+ m1 Q# m. {
  10.                 循环次数为10时,SCL频率 = 205KHz 8 t/ B& |% A% z4 G; n8 ^
  11.                 循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us
    / E+ G' f) q- z! z
  12.                  循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us 1 j7 \; p7 i( U) ]7 {0 T% K
  13.         */
    ) p; Z0 s* N4 b6 o7 w! o
  14.         for (i = 0; i < 10; i++);1 r9 r) k+ M" a8 E9 w% r
  15. }, Q1 O& |1 |4 p2 f* N5 G( i- o4 m' R
  16. /* CPU发起I2C总线启动信号:当 SCL 线是高电平时 SDA 线从高电平向低电平切换*/
    . l$ W$ V/ h5 L( L1 ]  ~
  17. void i2c_Start(void)5 l$ d' t% F8 U  @/ L1 F/ F0 R
  18. {
    4 |6 n4 `! D, Z% M- d$ Z
  19.         I2C_SCL_1();0 L5 E; S  o8 l
  20.         I2C_SDA_1();
    * U. Q7 N4 [6 |0 F
  21.         i2c_Delay();
    - x( x# k2 |5 Z3 H1 Y
  22.         I2C_SDA_0();
    9 T  W8 g% N7 s% v) z
  23.         i2c_Delay();
    ; K# S# V% g9 e. p1 Z3 F
  24.         I2C_SCL_0();2 [: J/ f" H7 d% O
  25. }
    + k8 K% v; D# ]! |! G% l
  26. /* CPU发起I2C总线停止信号:当 SCL 是高电平时 SDA 线由低电平向高电平切换*/* Y2 Y6 E; [0 Y7 x+ z
  27. void i2c_Stop(void)$ f' ^5 l. f" n2 @0 y
  28. {/ a8 r1 f$ b) t7 F5 S" n& Q2 @  j8 @
  29.         I2C_SCL_1();) l3 a* u* E2 G" u" A
  30.         I2C_SDA_0();
    * z) K1 V. b' J) B6 ]
  31.         i2c_Delay();- P4 M# v( h, P. }
  32.         I2C_SDA_1();/ n. e. Z& m; H9 X
  33.         i2c_Delay();7 g7 T& N) }3 u! s" L! t
  34.         I2C_SCL_0();: `5 T; r. I" d& ]) Q' H. Q' T9 g  v8 o
  35. }
    2 \$ m/ {% [! }( r! d6 f
  36. /*CPU向I2C总线设备发送8bit数据*/
    5 h% q4 L: A  W1 [6 l. e& L. Z  r
  37. void i2c_SendByte(uint8_t HS_Byte)4 ]! [* G0 O* _: b4 K( W( z
  38. {
    ' I0 V# {0 M" l' r3 O, @( r/ ]
  39.         uint8_t i;
    5 L" @  R5 S$ A& S8 w' C$ A
  40.         for(i=0;i<8;i++)4 n1 k8 W* }/ f) u. L- V
  41.         {6 X: O' C! b8 ^1 F8 k
  42.                 & I0 o: s( ^. o; J3 w
  43.                 if(HS_Byte&0X80)//先发送高位. \* T  D% i& Q0 ^+ [
  44.                 {
    8 V* C" K/ x1 H# M
  45.                         I2C_SDA_1();6 G0 `2 N# g7 R, f  A0 S, i5 C
  46.                 }5 Q! T; `9 O# a8 R  H. O
  47.                 else6 A( s  d! J1 E+ H9 j6 H
  48.                 {$ q5 L% |( Q! w" J
  49.                         I2C_SDA_0();
    7 L4 C* x; f  m1 q6 [
  50.                 }
    " n8 ]; R7 P5 K4 A/ H
  51.                 i2c_Delay();; b* z- h% [, }1 k$ p# N! ], C
  52.                 I2C_SCL_1();
    6 Y0 x9 m& ^6 N3 m* l
  53.                 i2c_Delay();
    6 g& }& `1 _4 M8 u! k
  54.                 I2C_SCL_0();- x1 s  I& P! U
  55.                 if(i==7)//发送完成一个字节,释放数据总线,接收应答/非应答信号
    ' r2 u& i+ [: A- t/ K4 {
  56.                 {1 S; ~1 H7 `- W- v" \0 m1 v
  57.                         I2C_SDA_1();9 s1 _' E, j3 K1 Y
  58.                 }
    - @$ p) R: }5 n
  59.                 HS_Byte<<=1;//左移一位
      ?+ r( v4 z; }; b
  60.                 i2c_Delay();
    6 @$ o: W; W2 M1 m# v
  61.         }
    7 c+ W) K( A/ U
  62. }
    ) \' U& f# f0 ]' B$ [8 `
  63. /*CPU从I2C总线设备读取8bit数据*/6 i6 y# u5 L9 d6 i  |3 F# t
  64. uint8_t i2c_ReadByte(void)
    * ?1 d* w0 T% {6 W6 O
  65. {% q! |- j! f: P; l, a, R, p
  66.         uint8_t SH_Byte, i;" J2 ~& C9 B* P2 {* Q/ A+ h4 l
  67.         
    0 \) T! B' O! W* D0 w2 }
  68.         SH_Byte=0;
    ; ?# a3 M( o/ U9 _  ]* D
  69.         for(i=0;i<8;i++)//先读取高位
    ! A9 v8 U2 S& w; T
  70.         {! o, r2 @  S) ]+ |
  71.                 SH_Byte <<= 1;
    5 I0 o" l* [! e8 W
  72.                 I2C_SCL_1();+ ^2 O1 n, H- |; N  b+ |' n
  73.                 i2c_Delay();, _! f- [7 l& [0 C6 ~
  74.                 if(I2C_SDA_READ())' U/ F+ v$ Y, Q
  75.                 {3 X) R- |" ~0 D2 D: X9 C, E/ F
  76.                         SH_Byte++;
    ( K9 L, s4 m" Z: A# u' S- l
  77.                 }
      t& N* f$ }; `4 t
  78.                 I2C_SCL_0();3 n" e5 }3 ]  M8 h% l: Z0 B
  79.           i2c_Delay();
    9 U3 f9 p: Z8 t7 i
  80.         }
    ' O) ^+ F5 |. E# i8 I
  81.         return SH_Byte;% M0 E9 I' s" b2 V* ^/ ]
  82. }
    * W' j" G, g' B; c6 a# }
  83. /*CPU产生一个时钟,并读取器件的ACK应答信号*/
      ~7 M, I- _9 p6 a
  84. uint8_t i2c_WaitAck(void)
    , {# k! K# ]7 O/ Q& R
  85. {1 o$ |' {1 y" a- w  E% n; ]
  86.         uint8_t re;9 {+ \4 W3 S1 s, p8 M8 T

  87. % ^' _# O* r! G2 w: G! |5 Q
  88.         I2C_SDA_1();        // CPU释放SDA总线 ! r; }9 w# `" `4 C- e) r% f5 y( }( y
  89.         i2c_Delay();
    # v+ U6 s5 `5 R) n* F
  90.         I2C_SCL_1();        //CPU驱动SCL = 1, 此时器件会返回ACK应答 . N1 [/ |; i/ z+ r; C! X6 K
  91.         i2c_Delay();4 W7 D9 f" K, [2 ]. M
  92.         if (I2C_SDA_READ())        // CPU读取SDA口线状态
    $ e- v4 w& f7 y/ {: j
  93.         {! b4 a1 K( P$ U( w: L+ Z
  94.                 re = 1;
    8 R1 y2 l! ~! I. _- v, A, X, Q. ^
  95.         }
    " P. d5 I' m! F' `( \' m
  96.         else
    / X) D0 s) k$ L# f# i
  97.         {4 J; C2 l( N2 ~( v# Y' ]& c
  98.                 re = 0;, L1 W: B1 t+ U
  99.         }
    8 A2 F/ P  k, n8 V! U8 w6 r. T( c( K, U
  100.         I2C_SCL_0();
    0 u  _: `4 \) a
  101.         i2c_Delay();
      y/ P0 J1 s: [. `; T
  102.         return re;# J5 D9 W  Q& E. d9 A4 T1 i' X
  103. }
    ! A1 Z* n9 R3 v2 C* J" }; d
  104. /*CPU产生1个ACK信号*/& l7 L" I8 E; C/ }0 y
  105. void i2c_Ack(void)7 n4 _# n' P, {( R+ J8 t2 @, Y
  106. {7 g, f; R0 \2 H. x' y
  107.         I2C_SDA_0();        // CPU驱动SDA = 0 ,应答信号9 ^5 Y. X+ i! K6 e  e7 u
  108.         i2c_Delay();) p/ }, D( ~! ]: L$ X/ J
  109.         I2C_SCL_1();        // CPU产生1个时钟
    ( J9 ~& \! `2 _, ~5 I: p6 |
  110.         i2c_Delay();
    & c9 |" N6 ^. }; }2 i
  111.         I2C_SCL_0();
    % j3 S& \  G8 o# c/ h
  112.         i2c_Delay();5 a& T' l1 c/ m6 S: y6 G
  113.         I2C_SDA_1();        // CPU释放SDA总线
    $ D5 B6 c) h! M: ^& b* E
  114. }6 t/ N* r: p" c# X
  115. /*CPU产生1个NACK信号*/3 I. A; h, @+ s( f% P4 E1 r; n
  116. void i2c_NAck(void)8 l- Q, k! g2 m$ Z- X. ~- U
  117. {9 `. x8 U2 K& g
  118.         I2C_SDA_1();        // CPU驱动SDA = 1 ,非应答信号
      @* C: |# f3 r. X5 y
  119.         i2c_Delay();
    " O" ?+ x+ q, U  d( j
  120.         I2C_SCL_1();        // CPU产生1个时钟 ; U0 e, B5 B9 g
  121.         i2c_Delay();7 t: t3 }0 E4 ?! q
  122.         I2C_SCL_0();
    9 C# F0 W; a4 ]" ?- _
  123.         i2c_Delay();* Q) C3 y  o3 _8 D
  124. }- T- D0 ?8 w$ i$ n
  125. /*检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在*/1 f( u/ n8 T- s% r3 c, E% h  Z4 L' @
  126. uint8_t i2c_CheckDevice(uint8_t Addr)
    + Y1 [; e) }! Q5 Y' Z
  127. {  P! H. A( o3 I1 X: W9 a
  128.         uint8_t ucAck;
    5 |# q( G! ?  M  C2 {/ D0 q/ S/ g

  129. 5 O4 _5 w( Z0 T! x& A- d+ }
  130.         i2c_Start();                //发送启动信号* l% H+ D2 X. e& ]; \
  131. ! t0 x: b. i2 c* S. ~4 t0 u8 O
  132.         /* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */0 V' \) w2 s1 Z
  133.         i2c_SendByte(Addr|I2C_WR);/ Q0 }3 O- c+ R7 U9 u; O
  134.         ucAck = i2c_WaitAck();        // 检测设备的ACK应答
    ; S$ E% P6 i( P3 [6 c/ p

  135. 2 m/ U, M& H/ k: J" F
  136.         i2c_Stop();                        // 发送停止信号 # {- \* O4 }) ^2 k

  137. % x) d2 G$ K# ?
  138.         return ucAck;" `. H" Z( `/ T( W
  139. }
复制代码
+ 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
  1. * AT24C02 2kb = 2048bit = 2048/8 B = 256 B
    $ b7 a2 T0 i# u4 a
  2. * 32 pages of 8 bytes each6 x+ `% `8 b% M' H
  3. *
    2 m' C2 x8 y" J- Y5 k3 h+ C" }
  4. * Device Address
    ) f; O# Z+ W, k. q: ~- a
  5. * 1 0 1 0 A2 A1 A0 R/W4 E; G- t+ m( N+ _- f% m4 J/ H
  6. * 1 0 1 0 0  0  0  0 = 0XA0  g3 p. Z( P( y6 R3 [% h
  7. * 1 0 1 0 0  0  0  1 = 0XA1
    ( [/ t$ l/ ~% {, a6 K- i$ N
  8. */* K; l! D- ]# C& j/ u. C
  9. 6 P+ P/ K" n" N) g  Z( Y
  10. /* AT24C01/02每页有8个字节 8 y7 X" \5 t' }: G  K
  11. * AT24C04/08A/16A每页有16个字节   R4 L* }$ f* T/ x
  12. */
    4 g' p0 Z5 r. g* ~. a5 F3 F5 L
  13.         - ?* k5 Q. A+ L  T, W

  14. / z2 }" M" v& c# s0 r
  15. #define EEPROM_DEV_ADDR                        0xA0                // 24xx02的设备地址 $ C6 F) e4 F: `+ ]5 k3 G' K- d
  16. #define EEPROM_PAGE_SIZE                  8                          // 24xx02的页面大小 ' b1 S& f5 f  n  z4 A( H
  17. #define EEPROM_SIZE                                  256                          // 24xx02总容量 5 d! Z1 O# n6 z& L, d
  18. 6 K8 t, K8 C6 d, z  T% H: O( y6 L+ }
  19. . |" C8 \3 f1 r8 y' r
  20. uint8_t ee_CheckOk(void);, A( \) q6 H# j' L& h5 q8 P
  21. uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize);* r* ~* X3 t% d+ e- i- S2 r
  22. uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize);7 z5 Q  k1 v7 p% d* L
  23. void ee_Erase(void);
    . \9 V$ L, M9 N3 ?$ m8 ^; O) ?6 E
  24. 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
  1. /* 判断串行EERPOM是否正常 */
    9 Q1 |, L: ~9 {7 ]5 s5 F! U
  2. uint8_t ee_CheckOk(void)# i8 |4 c0 G1 {( W0 B* m
  3. {3 ?% s: R1 m+ C+ b" a- }1 k/ x, G
  4.         if(i2c_CheckDevice(EEPROM_DEV_ADDR))! @7 }" X* K, S4 c$ h/ @# b9 {
  5.         {0 P5 s) ?) e7 s9 [& }' x7 u
  6.                 i2c_Stop();//失败后,切记发送I2C总线停止信号+ u& k* t* Y' m
  7.                 return 0;//设备异常' q" E) ]- h6 s( H9 I8 Z; k
  8.         }
    8 P$ {/ x# [3 ~
  9.         else2 t3 P& C( u% o) w8 u
  10.         {, b/ A% {- ~5 p* [1 M; _& Y6 l
  11.                 return 1;//设备正常
    % n: C" [( o( ^% T
  12.         }9 c0 K: C$ F8 B' Y5 c' i
  13. }
    0 @! h( i+ p0 b7 G5 D

  14. " w/ Q' ^3 g9 ]5 ^/ p5 c+ ?
  15. /*从串行EEPROM指定地址处开始读取若干数据*/
    * l% Q1 _* n$ Y0 |, y% c
  16. uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
    % n+ e& |/ E: d, h4 v+ H0 p5 K9 f
  17. {4 J% }9 u& Q/ _8 L5 n6 M, }
  18.         uint16_t i;
    ' r6 x4 Q2 n! {- U- `- j
  19.         i2c_Start();//第1步:启动I2C总线& M7 Y! S) m6 ^- O" b3 D/ l
  20.         
      B' B) @( e# W# T4 ~  N
  21.         i2c_SendByte(EEPROM_DEV_ADDR|I2C_WR);//第2步:发送地址+写指令
    / I$ _3 o. l7 ^! R
  22.         if(i2c_WaitAck()!=0)//第3步:判断设备是否响应
    $ k* l4 {) }3 A- a* ]' @
  23.         {/ W! U7 A6 t% x; T
  24.                 goto res_error;
    $ W- p- N: \+ d+ o/ A% K+ [
  25.         }
    ! u" f- ~7 @# r) k& j. t5 C2 }
  26. ( a' ^, |2 [5 A$ c2 F; M% G/ Z
  27.         i2c_SendByte(_usAddress);//第4步:发送字节地址
    " N" {' v- ~, p0 {: u+ j
  28.         if(i2c_WaitAck()!=0)//第5步:判断设备是否响应
      P* c' f# w& g7 Q  R
  29.         {" A* j- Q6 a5 y2 g0 |0 g( ~: k" H
  30.                 goto res_error;
    % E' ^: B# p4 O' y& {
  31.         }
    . \- }8 v0 W" p) Y0 D
  32.         
    3 y4 ~. P, o0 p7 c1 Z* X9 N
  33.         i2c_Start();//第6步:重启I2C总线,开始读数据8 p3 b& I& j; s% r2 g- p2 q
  34.         . T+ x9 g& j4 k8 L- [
  35.         i2c_SendByte(EEPROM_DEV_ADDR|I2C_RD);//第7步:发送地址+读指令
    ! L6 d8 o. \: ~
  36.         if(i2c_WaitAck()!=0)//第8步:判断设备是否响应9 i0 Z& L( \. X3 I2 r1 P5 o
  37.         {4 W/ k+ [4 m' ]; S( ^% [1 S# N
  38.                 goto res_error;
    ( y3 j8 v/ P" g. d" E
  39.         }. y' c* m$ V8 G5 ~$ x. f- G0 k
  40.         
    ; i) U0 i' t/ O6 Z- U0 V4 P2 W9 N
  41.         for(i=0;i<_usSize;i++)
    8 D1 A7 f0 j: c
  42.         {) |3 _* w5 Q! i  m. }
  43.                 _pReadBuf=i2c_ReadByte();//第9步:开始读取数据' r% W3 e: K$ N' [2 D8 @6 N1 {. r
  44.                 if(i!=_usSize-1)//第10步:在接收_usSize-1个字节前都发送应答信号,最后一个字节发送非应答信号
    ! J4 S/ h% T0 L8 [! c( n$ T' m
  45.                 {
    9 a- P, q$ K2 W% O9 e! x
  46.                         i2c_Ack();
    / t; l6 F$ V- A  H( `" U
  47.                 }
    . M! |- n4 a* P0 R- m7 i9 f4 w! {
  48.                 else
    ( Z. ?. r8 @' ^) M6 u1 F/ y5 K
  49.                 {
    " b4 B( z' w3 H6 K' `; r: t
  50.                         i2c_NAck();
    * C; X$ {5 g/ K6 s* E
  51.                 }6 ]9 U! |% g& Q# B
  52.         }5 P) H& X4 z5 h9 v7 o2 ~) y
  53.         
    ; S  G8 x2 R  |9 [% u6 h
  54.         i2c_Stop();//第11步:停止I2C总线
    " W/ {8 h; `' H5 s8 u
  55.         return 1;//读取成功0 Q, }: V: d  |2 r8 y6 d+ _
  56. 5 z9 Y! u7 x5 \! Y4 B
  57.         res_error:
    8 _' Z5 h: \( t
  58.                         printf("\r\n响应超时\r\n");' Q7 Q7 }( d: l& r4 u, ]
  59.                         return 0;//读取失败4 O% P+ b$ k$ ~* N
  60. }
    ( y2 y( N3 t, S& E( o1 l# I
  61. ; M% J# n- W1 ^5 t' P% Y  ^7 t
  62. /*向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率*/1 o" o  w8 z  z4 `. t1 f
  63. uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)! w7 {& x* b4 {1 o( k: v
  64. {& ^8 n; Y( s3 A
  65.         uint16_t i,m,Addr;
    * N1 K/ N2 z* _5 M- F# E  \
  66.         Addr=_usAddress;* }9 g8 U/ U! a

  67. ( ~% @- e6 H( v. r# p1 S
  68.         for(i=0;i<_usSize;i++)
    , R1 a, T7 Y" _2 T8 r6 E2 A
  69.         {
    1 n1 w% b$ k7 T0 z% S) v' {, U
  70.                
    : H' t8 O. o- s6 A, d
  71.                 if((i==0)||(Addr&(EEPROM_PAGE_SIZE - 1))==0)//当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址
    6 c: V0 M7 Z5 a  h: e/ M
  72.                 {) f  I# S+ N* d  M/ s1 T, j
  73.                         i2c_Stop();//发停止信号,启动内部写操作) {# U$ G$ v7 A: z! \" ]6 V8 t4 [
  74.                         for(m=0;m<1000;m++)//通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms,CLK频率为200KHz时,查询次数为30次左右) {" o$ u3 d: u' @
  75.                         {0 t4 g9 t% S3 @6 \" J, q& B) t
  76.                                 i2c_Start();//第1步:发起I2C总线启动信号
    / a9 f9 |% a. ]5 |: N) ?
  77.                                 4 o! G( r& M; I
  78.                                 i2c_SendByte(EEPROM_DEV_ADDR|I2C_WR);//第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
    ! M; x$ B- Q2 K, x% h0 `+ x. L
  79.                                 * I8 x% P" Q& Q
  80.                                 if(i2c_WaitAck()==0)//第3步:判断设备是否响应* s% }5 R) b' E5 i; H
  81.                                 {4 E9 `$ g5 ^/ q$ S8 J# }( ?
  82.                                         break;/ Y8 E0 @9 H  \0 v" ^
  83.                                 }
    , J* f) H9 R( P: n# j6 M
  84.                         }
    $ Q# M+ w# `6 q7 h' `* O
  85.                         if(m==1000)//响应超时
    + m% }8 m0 e: }' _- }: H
  86.                         {
    # G) K5 O) \5 L
  87.                                 goto res_error;6 r" X& A8 j3 }/ q: s
  88.                         }7 d4 \! k/ w; P2 Y: r+ U
  89.                         
    & y5 m( s1 J& {/ z6 k4 G7 _. [7 d
  90.                         i2c_SendByte(Addr);// 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 * h+ A" Q( R' O6 L1 y
  91.                         if(i2c_WaitAck()!=0)//第5步:判断设备是否响应
    3 `2 u" _5 J& Q' N0 C0 d) [
  92.                         {
    7 @7 I: w! `" U' h
  93.                                 goto res_error;2 o9 D" c; V) P: }: I& E
  94.                         }: Q  |8 Q7 o9 v
  95.                 }
    ' ]8 a' X8 @1 i4 p5 [+ k: Q
  96.                 " C0 d. k# R2 k, l; Z5 D$ \# e7 n: X
  97. <i>                i2c_SendByte(_pWriteBuf);//第6步:开始写入数据) [! B5 H' e% H; ~, l
  98.                 if(i2c_WaitAck()!=0)//第7步:判断设备是否响应
    4 q# L2 I& z8 M0 l
  99.           {
    ' C9 Y% ?' F- P/ w) N. t$ `7 T
  100.                   goto res_error;) E( k7 Z7 J6 W( i+ W6 e
  101.           }
    " x! C/ l  T. ]' `6 W7 q
  102.                 Addr++;
    1 S) g7 P8 M7 E" q! U. C0 a
  103.         }
    ' W6 l" p, E8 S( y7 C0 Z
  104.         
    # r- a( H. M( U/ U0 h" v) T
  105.         i2c_Stop();//第8步:停止I2C总线% ~& q3 B& G; L% T) ?) C, k
  106.         return 1;//写入成功* x, p2 T  o: P
  107.         
    ( q& e2 N8 V* a; y+ l
  108.         res_error:, E! T$ ^: ~. h# i4 o  P
  109.                         printf("\r\n响应超时\r\n");
    / E5 ]9 {3 O" L4 @9 a
  110.                         return 0;//写入失败
    0 n) e. E8 M% u  ]$ m4 W- P
  111. }0 _* H  a, Y2 O8 p! |

  112. : f  e7 {" u2 S  F7 A8 T
  113. /* 擦除EEPROM */' Z9 x' L- A2 |) L
  114. void ee_Erase(void)
    1 p, C  m! |5 }6 {; D9 I6 K( \& W  l
  115. {
    4 m" c! k' a6 L
  116.         uint16_t i;
    4 m3 Z2 f% v0 q* y" v/ m9 `* H% ?
  117.         uint8_t buf[EEPROM_SIZE];* _4 u- r% B3 r. Q6 Q/ M
  118.         
    . z, L$ t% C& W4 j# [$ y' _2 O$ J: F
  119.         for (i = 0; i < EEPROM_SIZE; i++)// 填充缓冲区
    7 z. A# d4 V& o6 L6 f& Q* g. ]
  120.         {! F/ j( }6 U8 f9 z$ W" r
  121.                 buf = 0xFF;9 @7 a9 Q, l$ Q3 B6 s" j* I
  122.         }
    * ~( X% H9 @0 U  ~* z9 k5 P# e
  123.         
    0 A4 P4 Z  Q, C/ g/ m
  124.         if (ee_WriteBytes(buf, 0, EEPROM_SIZE) == 0)//写EEPROM, 起始地址 = 0,数据长度为 256' r: {: c. p5 A2 p- z! p/ m
  125.         {; L/ @% R/ x8 v5 n' _3 z' m3 o, a
  126.                 printf("擦除eeprom出错!\r\n");
    & m4 z+ `% W6 ^4 U
  127.         }
    ( Y8 K6 h' `& c# I$ ?' e
  128.         else
    - i3 }0 t4 G) H6 J( X
  129.         {  w2 A2 [: k4 j1 T* u' J" {
  130.                 printf("擦除eeprom成功!\r\n");
    $ _2 _$ B& B4 H! f
  131.         }) p- r2 v2 g# O7 l3 ^2 ~7 y- i
  132. }
    4 m- ]" q2 A' o7 \% {" a9 ?& N

  133. & N6 b, h" ^; }
  134. /* 测试程序 */; o. G; d! p4 ?7 z/ v
  135. uint8_t ee_Test(void)
    1 H6 L& b; Z; {' B' H% M! h
  136. {
    ; Q9 N) j! d/ B6 p
  137.         uint16_t i;8 |# a' U! R! [1 ^
  138.         uint8_t write_buf[EEPROM_SIZE];
    ) I% }8 v8 \4 d+ T: D' l' x
  139.   uint8_t read_buf[EEPROM_SIZE];
    . B! t% r/ D# c$ I3 n7 u0 B7 {8 q! u- i4 K( y

  140. . S" M, j6 F6 `* \
  141.   if (ee_CheckOk() == 0)// 没有检测到EEPROM - {& Z2 R( E) h! Q* D" i
  142.         {
    9 C4 D5 Y2 {6 U% @2 H4 e# z0 }3 L/ ]
  143.                 printf("没有检测到串行EEPROM!\r\n");                  y$ j  K' p8 Q9 N* r6 I
  144.                 return 0;
    7 z! C- l% y/ ~* y% K. @3 i7 D
  145.         }
    2 j& U5 F5 z# g
  146. ( k. X# p( i+ ]+ A: Q5 F
  147.         for (i = 0; i < EEPROM_SIZE; i++)//填充测试缓冲区
    0 Y! a4 m4 s' S& f+ f9 x
  148.         {                ) d" w9 B$ V% Y% B2 ^
  149.                 write_buf = i;
    ! O7 e3 p" i# t8 f
  150.         }
    9 B+ s: k$ ^, D3 d8 q
  151.         9 ?: U1 {4 q0 x5 W& m
  152.   if (ee_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0)//向EEPROM写入数据
    ' n0 e" I& [$ I9 V7 B
  153.         {3 A6 }# a$ z0 T2 J8 ?
  154.                 printf("写eeprom出错!\r\n");" G8 D- i3 r# R( _' b
  155.                 return 0;) k4 N5 m# j1 Y  R$ A* E
  156.         }) _1 Y* e. U. \+ e( i& G* L
  157.         else) h' d1 L3 @6 O- y3 o+ _5 ~
  158.         {                : ?# i( _  u9 @
  159.                 printf("写eeprom成功!\r\n");
    . d2 v8 ]+ y4 E5 y
  160.         }; p2 x0 Y" i0 a2 u$ b/ o0 R

  161. 7 I- r; S5 \# T
  162.   HAL_Delay(1);//写完之后需要适当的延时再去读,不然会出错
    1 f5 x5 e( J. {" a3 x
  163. , t% C  L! h3 B. V) q5 F. }, R0 A7 N
  164.   if (ee_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)//从EEPROM读取数据( m. i5 B7 G# q2 z
  165.         {
    : B3 W. V9 C! a0 q8 c. M7 H/ q
  166.                 printf("读eeprom出错!\r\n");7 Q$ ?' m+ o; d1 |- }& I
  167.                 return 0;- b! t1 ~( u/ F
  168.         }( I0 M# j& s) y+ ?) B' t
  169.         else: Q: Y$ r! K$ x2 b
  170.         {                ( |1 I1 M, v# X0 M" f/ U
  171.                 printf("读eeprom成功,数据如下:\r\n");* [. P' K3 ?% u" T) v
  172.         }: Y' T1 J& K3 `  ~( z$ u( \4 D  M

  173. % ^0 p! k' z) |- C/ O3 ?  G4 U3 t* i
  174.   for (i = 0; i < EEPROM_SIZE; i++)//比较读取和写入的数据是否一致
      _, N7 Y) P, N# \( K
  175.         {
    ; e9 C) P5 V; w1 _
  176.                 if(read_buf != write_buf)' O4 Y- Z$ E) Y3 ?$ H7 M
  177.                 {
    * n; ?. E, M. m
  178.                         printf("0x%02X ", read_buf);
    8 a7 h) B# |3 C" a& F; A5 o% b2 }
  179.                         printf("错误:EEPROM读出与写入的数据不一致");
      _% E0 y9 S9 M
  180.                         return 0;
    % G* h1 ^" t( b4 @0 I
  181.                 }
    % [$ F. f, P( L% [1 o# K& o- v
  182.     printf(" %02X", read_buf);* D( ^  E! m! Z4 x# a" Q
  183.                
    + a0 ~3 a' D  o& ~
  184.                 if ((i & 15) == 15)  B0 P5 P( h0 ^
  185.                 {
    ; b; B( [2 E* P6 j7 g
  186.                         printf("\r\n");        
    / S9 z! G- T7 K+ C
  187.                 }                7 }& A0 A  V/ U0 D0 @
  188.         }6 b2 s7 ]/ W3 K  x$ h$ q. [0 |
  189.   printf("eeprom读写测试成功\r\n");
    8 U" L/ a1 A' O
  190.   return 1;
    ( u# ~) l  Y$ [2 n' j. P+ Z- `% `
  191. }</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
  1. ee_Test();
复制代码

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
1Z_]S~I87CLM4TKQG3HOB.png
+ 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
  1. #define EEPROM_ADDRESS                        0xA0                // 24xx02的设备地址
      Y( I6 r2 c! M0 x! ?4 b7 _% l
  2. #define EEPROM_PAGESIZE                  8                          // 24xx02的页面大小
    6 y; \7 `# [2 N- R, k9 R
  3. #define  DATA_Size                        7//256   " t/ s/ _6 Z- F* j4 Z! w# `
  4. #define  EEP_Firstpage  0x02   // 0x00                % k9 T9 `) m7 N# _
  5. & Z, d6 z7 F1 e& [: H+ y0 K3 e
  6. void MX_I2C1_Init(void);
    * _& w+ |+ D8 u' `
  7. uint8_t I2C_Test(void);
    3 Z9 f0 p7 c" ]/ r$ J: S4 J
  8. void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite);
    # C; V" Z$ C7 z- m5 M
  9. uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr);
    8 m8 J  B+ e0 G5 X; A& y
  10. uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite);: C. ^3 O: L5 ]
  11. 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
  1. /*将缓冲区中的数据写到I2C EEPROM中*/' F2 o4 N% ^, ^! ?
  2. void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite)
    ) J. V5 A3 p8 z" V. F
  3. {0 n. {% H/ d" x. z9 U
  4.   uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
    2 s" g$ _% z! J- `
  5. 9 _+ ^9 Z4 {; I5 f
  6.   Addr = WriteAddr % EEPROM_PAGESIZE;//判断写入的首地址是否与EEPROM页的首地址对齐,0为对齐
    & V! v1 G% ^  W5 g4 H3 D, y
  7.   count = EEPROM_PAGESIZE - Addr;//计算从写入的首地址需要写多少数据才能填满当前页+ ?" `+ O: {' h, |1 m
  8.   NumOfPage =  NumByteToWrite / EEPROM_PAGESIZE;//计算写入数据需要写几个完整页(地址对齐的情况)0 M  U5 g' i( p4 T% g/ |
  9.   NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;//计算写完完整页剩下的数据个数(地址对齐的情况)
      }2 A0 |. N5 {, ~- f* d

  10. % `6 G/ a$ h5 B
  11.   if(Addr == 0) //判断写入的首地址是否与页地址对齐
    7 q5 ]8 F) T7 ?# a. ~/ C
  12.   {4 @' K7 |/ q# i- b: @
  13.     if(NumOfPage == 0) //如果页对齐,判断数据是否不满一页0 g8 c/ W& X( H- Q3 A) j
  14.     {; J1 J2 ~) m" X
  15.       I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不满一页,直接写入数据% U- \+ K5 l2 U0 o2 B0 I
  16.     }# y7 ^) s+ M) {
  17.     else  //在数据满一页的情况下,通过地址自增方式,循环写入数据(页写入的形式)
    1 E# B+ M3 E; @3 N1 S; r
  18.     {
    . t; N  y" |  \! v3 r$ U, D
  19.       while(NumOfPage--)//循环写入数据:先写入完整页
    # x$ B$ O/ O; |% J: d; d% z
  20.       {' I7 j$ p" ]+ @  i; J! Z" A5 H
  21.         I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);   }, w  i& `8 A' L
  22.         WriteAddr +=  EEPROM_PAGESIZE;, X0 e5 E- B/ i0 O$ S
  23.         pBuffer += EEPROM_PAGESIZE;5 F& t! k8 Q) s9 P1 R% B
  24.       }
    & \4 M4 Q, \; O  P/ b% ?  ]4 L+ R, D

  25. 8 F- l( `1 K0 J$ y4 V" q
  26.       if(NumOfSingle!=0)//循环写入数据:再写入不满一页的数据* o- F* e9 U- g
  27.       {
    " Z, \! K. g* X
  28.         I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);2 D( M; H7 s/ S4 i7 j: U
  29.       }
    ; \% c2 [% i% P3 z
  30.     }( q* @7 F; i* H2 k
  31.   }: C9 O# |6 n6 [1 D) Z" P8 Y
  32.   else
    * T( Y' x+ o# s9 Q  z0 T% N( G6 j$ U
  33.   {
    3 i. E$ Y8 n2 K: K2 q
  34.     if(NumOfPage== 0) //如果页不对齐,判断数据是否不满一页
    9 c7 X8 z, _7 `3 x
  35.     {
    0 R: Z) I. x, P
  36.                         if(NumOfSingle<=count)//如果不满一页,判断数据是否跨页% z+ h& x- a8 P0 l% S  s
  37.                         {, J. u% _& o8 @9 M* v
  38.                                 I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不跨页,直接写入数据
    2 q( T: O/ L6 g; j" v
  39.                         }& d# `; x# g) t1 ]$ J
  40.                         else
    ! b* G2 _# h6 J8 k7 q
  41.                         {
    8 c" Z' @3 e: s2 I
  42.                                 I2C_EE_PageWrite(pBuffer, WriteAddr, count);//如果跨页,先写首页数据,再写次页数据
    + F) W6 K, |9 M# ?' d2 S
  43.                                 I2C_EE_PageWrite(pBuffer+count, WriteAddr+count, NumOfSingle-count);$ @5 Q5 m2 D! B' g+ q! Q8 x
  44.                         }
    & P* X0 A2 i; v( @9 N, Q
  45.     }2 a- A9 m1 s0 P7 H
  46. ! x6 u" m% B! {+ B( x# H6 V6 U4 u
  47.     else
    $ i) b9 \+ I$ T6 y+ v& C- E0 Q2 q
  48.     {
    " X8 m$ k/ ]. o& s( h0 Y, R1 m# l
  49.                         /*如果数据满一页,对数据进行分离*// l& l" E  R# w- e% N
  50.       NumByteToWrite -= count;//扣除第一页数据个数3 g" p; g# O0 b/ P
  51.       NumOfPage =  NumByteToWrite / EEPROM_PAGESIZE;//计算写入数据需要写几个完整页$ S8 A7 Z2 O: r5 `: W
  52.       NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;        //计算写完完整页剩下的数据个数- m" Z4 C( N" t, `# z+ T- `  u3 D5 n9 e
  53. 4 R' A; y: \+ w7 @. O; I( O. ^% T& f
  54.       if(count != 0)
    ! L( W/ X8 x8 j0 p5 t
  55.       {  
    ! x3 o* T" f0 a2 g$ U9 m8 q
  56.         I2C_EE_PageWrite(pBuffer, WriteAddr, count);//写入首页数据) X! u5 R- U9 P; d' G2 d/ y' V' m
  57.         WriteAddr += count;//写地址自增
    " t% A3 n4 K2 i1 X
  58.         pBuffer += count;//缓冲区指针自增! m8 e2 O; a0 y1 c( k
  59.       } ; Q$ T4 o4 E# E3 o# _! M
  60. + G2 A9 O7 ~" x: Y5 I$ P9 g" g9 ^
  61.       while(NumOfPage--)//依次写入完整页的数据
    0 f( m5 S. J) ?* L
  62.       {3 y+ M: i( A% m8 }
  63.         I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);* [( c/ U$ L+ [* m: F
  64.         WriteAddr +=  EEPROM_PAGESIZE;//写地址自增+ S$ ?" o: c( Q, m1 Y  @, E/ f% G! g
  65.         pBuffer += EEPROM_PAGESIZE; //缓冲区指针自增
    2 s* }" ^3 \0 L1 {& k3 h' ?! B2 T
  66.       }* m$ A4 v+ Q' F+ ?
  67.       if(NumOfSingle != 0)//判断最后一页的数据是否是填满完整一页的$ {+ n3 Z2 F% y1 t1 }1 o
  68.       {
    / h3 A  A) }5 f, _% v0 d
  69.         I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); //写入最后一页的数据& Y' j9 f% `$ M8 D
  70.       }
    + {& L( ?1 v* w  N0 [; A) r
  71.     }: R, `8 ~+ I* ?8 s' {
  72.   }  
    3 u1 m% W' t! Y
  73. }: v  E, V6 [) Q4 P; R0 b8 o
  74. 1 Q. i1 q$ G3 }. V
  75. /*写一个字节到I2C EEPROM中*/
    8 S+ l$ {/ t% O, n* `# Y6 N
  76. uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr)
    ( R' l9 o1 j' |; Q6 s
  77. {5 g. V- h" U$ `# o
  78.         HAL_StatusTypeDef status = HAL_OK;2 z7 y2 J3 c1 `+ ]; ?# E1 v( z
  79. + f; S/ O  X+ Y1 t
  80.         status = HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDRESS, (uint16_t)WriteAddr, I2C_MEMADD_SIZE_8BIT, pBuffer, 1, 100);
    - G& q7 M! @" z' Y& t
  81. 5 b4 z4 D+ r4 Q: @* r5 r$ }* z
  82.         /* 检测IIC外设是否传输完成,并准备进行下一次通信 */
    2 U* H' n+ d* g2 e
  83.         if(status != HAL_OK)1 m) K' \8 K8 [+ ?7 g9 Y/ q
  84.         {. b( E. y( N+ ~4 v  k
  85.         /* Execute user timeout callback */
    . m! f" D. [+ y8 E
  86.         }3 n! \$ L1 L4 P. B( b; w
  87.         while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
    ; Q7 Z, _' E. d2 _9 X/ G( s
  88.         {
    % X+ o+ V* j( }1 k* T9 g. E
  89.                 : v8 u1 n! e3 {: S
  90.         }
    / p/ n3 Y1 r% _4 b% z, O' S; F
  91.         /* 检测目标设备是否准备好进行通信(即目标设备是否在进行读写操作)*/' _2 f& p4 Z5 z) \( ^
  92.         while (HAL_I2C_IsDeviceReady(&hi2c1, EEPROM_ADDRESS, 300, 300) == HAL_TIMEOUT);2 @' [+ N5 S# v+ u* |+ h9 ?( @
  93. 9 o2 r, p6 W/ [
  94. //        while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
    5 Q" N# V* {$ B* G+ v: X
  95. //        {
    . {: M8 [# }$ e0 x5 W6 r9 ^$ Z
  96. //               
    ) Y4 ]: {: m! X1 u  Y
  97. //        }# [$ ?3 u. }6 h" i- }
  98.         return status;$ J) D+ H, t, s7 b- \+ h; ^: A
  99. }
    % G" G3 b# K4 J- d3 M: v) p/ W( y) N

  100. 5 O: C9 \7 N7 O+ `9 ^9 e' D& x
  101. /*在EEPROM的一个写循环中可以写多个字节,但一次写入
    : c5 m. Y8 f: Y
  102. 的字节数不能超过EEPROM页的大小,AT24C02每页有8个字节*/
    # m" N& E( a; w0 l1 o
  103. uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite)
    $ ?- |9 B& d. H
  104. {
    : |  o6 o8 j) q5 i6 S
  105.         HAL_StatusTypeDef status = HAL_OK;9 l& J) j; n) {8 \
  106.         status=HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDRESS,WriteAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t*)(pBuffer),NumByteToWrite, 100);# \- c9 [2 I  _
  107.         /* 检测IIC外设是否传输完成,并准备进行下一次通信 */7 t1 A. \, W6 \
  108.         while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
    1 J4 U) l* `7 {' {& I% K6 U  D: c
  109.         {
    1 z, K0 h0 `$ D8 D
  110.                
    * l  M  r  C  g. E4 G# c* O( F
  111.         }* D4 O5 \' d/ d( ?
  112.         /* 检测目标设备是否准备好进行通信(即目标设备是否在进行读写操作)*/1 C- X$ U' Y( X& m0 K9 ?
  113.         while (HAL_I2C_IsDeviceReady(&hi2c1, EEPROM_ADDRESS, 300, 300) == HAL_TIMEOUT);
    / |% |7 i2 [+ |& S

  114. & ~9 S; S4 n1 x8 ~8 ?; ?( j
  115. //        while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
    - o( u8 Y" q1 b- t, N0 l. A
  116. //        {
    4 d0 k( ~. _) l' u* I
  117. //                " E6 S! {' @0 D, V
  118. //        }' o% X2 K6 y& Z
  119.         return status;$ S; N1 @3 T. |/ J+ h
  120. }
    5 M3 G! Z; L, j' z

  121. . P  J3 G' u4 O: u
  122. /*从EEPROM里面读取一块数据 */
    : w2 K1 R& j( x) v' z
  123. uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead)( e9 U( @* P- H
  124. {  J" P8 Z2 [' O
  125.         HAL_StatusTypeDef status = HAL_OK;
    ; @8 q6 |  |6 M& k2 N: m+ C
  126.         % l& R0 B  C- e) G% K% s; C
  127.         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

  128. ( C2 x7 w4 B4 P; B7 U: d% x, P" d
  129.         return status;. R( o) g1 M5 {1 r; e) U
  130. }
    . t& c9 P0 z# T* i( A# l$ b' R! k

  131. 1 l$ o$ r- O& W+ Z6 s* r0 ]
  132. uint8_t I2C_Test(void)
    2 o) p, D. V5 k1 a# B9 H( D
  133. {& c4 k% Y2 n. O! d* d* F
  134.         uint16_t i;( A4 y2 t( {: Y7 w9 k1 y8 B/ {

  135. ( {+ ~& r; V3 _, [3 `; l9 B
  136.         printf("写入的数据");
    - A- {3 o* |8 J

  137. ! ^& U/ }& S, U( i6 B( P! J6 h- X
  138.         for ( i=0; i<DATA_Size; i++ ) //填充缓冲
    % d- v& E5 g3 c
  139.         {   8 G2 f2 E7 f# ~
  140.                 I2c_Buf_Write =i;
    + _+ s7 j5 x  _4 X0 M' g1 |
  141.                 printf("0x%02X ", I2c_Buf_Write);
    0 G1 I# A% a# f( D1 v
  142.                 if(i%16 == 15)   
    % ]4 d5 s6 f+ F  L/ u! q" c
  143.                 printf("\n\r");   
    ) ]  {, [( v( y* @! O; I5 W4 J
  144.         }
    - ^& D) Z8 I2 C0 V8 K

  145. 9 x: P4 A$ P9 ~7 {) s
  146.         //将I2c_Buf_Write中顺序递增的数据写入EERPOM中
    # C) x% A6 c+ F
  147.         I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, DATA_Size);' \  X8 R9 V8 H3 y

  148. " f' Y" n) X- ]! d
  149.         printf("读出的数据");
      C  ?" x& {+ \1 k: @4 z2 X
  150.         //将EEPROM读出数据顺序保持到I2c_Buf_Read中
    5 k+ Y6 F! F. W% ~+ F: Y
  151.         I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, DATA_Size);
    ! D& \0 j+ N5 N4 Q
  152.         //将I2c_Buf_Read中的数据通过串口打印
    0 U" c# L  w1 X/ G  r
  153.         for (i=0; i<DATA_Size; i++)
    $ @. I( Y3 `* I' m# j
  154.         {        ( j+ b+ d3 S9 f: {6 G
  155.                 if(I2c_Buf_Read != I2c_Buf_Write)% o4 C1 A4 \+ v( z6 B" u
  156.                 {& l$ a+ `+ f5 s7 V1 a
  157.                         printf("0x%02X ", I2c_Buf_Read);
    ! M' j' R- l) W4 J1 ^" I# l
  158.                         printf("错误:I2C EEPROM写入与读出的数据不一致");
    7 q2 y( C% g. C# I1 h/ {
  159.                         return 0;% a% G! t+ y, g
  160.                 }
      k4 n0 J2 h6 X" L+ @( p
  161.                 printf("0x%02X ", I2c_Buf_Read);5 @8 I# n0 U1 z; y! I4 g) Y4 P
  162.                 if(i%16 == 15)   
    7 D3 k& O. F7 x  [
  163.                 printf("\n\r");; F& I( x4 }. e. ^8 T

  164. . y+ g3 A) c" O( l8 t
  165.         }- K( i2 x+ v: u/ H" K+ ?
  166.         printf("I2C(AT24C02)读写测试成功");/ {' b! K% |2 ?" i- c
  167.         return 1;
    5 ^' X2 e+ M! K8 A7 ^8 M$ |
  168. }
复制代码
" 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
  1. I2C_Test();
复制代码

5 U+ U$ L" b0 Z- d4 z" F以上程序主要参考野火的硬件IIC程序,但是在页写入程序中添加了一个判断,用于判断在地址不对齐,且数据不满一页的情况下,数据是否跨页。数据不跨页直接写入数据,跨页分两次写入数据。代码如下:
# z& y$ A: t; R3 j8 m1 i; {; N* V' T. L1 n
  1. if(NumOfPage== 0) //如果页不对齐,判断数据是否不满一页9 r/ ?- g6 d% X) i5 B
  2.     {  p( Y+ U: s, o. e
  3.                         if(NumOfSingle<=count)//如果不满一页,判断数据是否跨页8 x- y7 q9 v" M; C
  4.                         {* V4 p" D  ?5 R! l
  5.                                 I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不跨页,直接写入数据2 L6 l8 B, x: F  G" Y
  6.                         }/ c' }- p/ J+ h5 K$ ?9 \
  7.                         else/ T( f- I$ K1 [8 e8 G: H
  8.                         {2 I/ U1 |0 m: f# [; Q% E# i& ?9 i
  9.                                 I2C_EE_PageWrite(pBuffer, WriteAddr, count);//如果跨页,先写首页数据,再写次页数据
    " a* ]( R3 K# {+ M) T- U
  10.                                 I2C_EE_PageWrite(pBuffer+count, WriteAddr+count, NumOfSingle-count);
    3 I, i! K  \, z. J# U
  11.                         }
    5 z& ^5 |6 l4 s) K
  12.     }
复制代码

" 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 [133MADOI6VDUZL618T(WUC.png ' ^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 `
赞 收藏 评论0 发布时间:2022-4-7 15:15

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版