坊间一直流传着一个传说~STM32的硬件I2C设计有BUG,最好不要用,用软件I2C比较靠谱。长久以来,为了不必要的麻烦,我也一直没有用过硬件I2C,主要是软件I2C也比较方便,基本上任意端口都可以用。, l) K2 _+ _4 ^' p- f
8 ?+ V" f* M1 O
最近画了块板子,正好用到了I2C,就顺便来测试一下硬件I2C是不是真的像有些人说的不好用。
1 p& Y9 f5 o6 f# u! e2 Y* N! N* Z4 N1 |
测试硬件:STM32F407VET6+AT24C64% d* @: J" K6 |# U( f: ], `( q$ @
测试软件:STM32CubeMX v6.1.1; _1 w; G' {. ?0 U- B4 x
HAL库:STM32CubeF4 Firmware Package V1.25.2 1 Y4 A! @+ O! l! \) V0 \; y
. \7 V& D3 Q& _/ x' @+ D5 {& `4 F' b+ r! Z
STM32CubeMX配置
# ]. r7 b6 L M* k$ S
, b [6 z6 Q" `$ @" A" _" f$ ^* p! |
使用STM32CubeMX配置很方便,时钟等基础配置不再详细介绍,直接看I2C配置如下:
q5 B5 g) y* j9 c" g G4 C `
- ~& P! x: W) ~ A这里的速度模式选择为标准模式,时钟为100K。要求高的可以选择Fast模式,400K时钟。! `% ^, Y& P3 T7 G( ]1 t
& c; t7 ?: W# b
3 ]) N5 ? j! d5 }配置完成后生成代码。
/ Q m: ^" Y. p. B7 J1 C; L! W' L5 ^, o) \+ v' \
2 Y6 k/ W7 T. q4 L8 V
编写代码" B( |7 V( y w2 O
* n% g w; a' V3 ]3 ]- m$ Y/ |& }5 [2 t8 t" j4 {$ ?
代码生成后,直接调用读写数据的函数即可:- i; V5 r' v% H/ d; t
* _: I. I2 q3 Z% h
! P2 n. }. R! y t3 h
HAL_I2C_Mem_Read4 q0 x! `6 p7 K- O0 P
HAL_I2C_Mem_Write2 ?0 c1 \9 M# N; ]! B5 W
8 p7 v; ^0 R' {
! c# |. ~' ?; n* i! @# ^函数参数可参考代码注释。/ C$ S8 W1 d l- m3 G; `- ]
+ n" n' k3 x) R( D
& {" r% S1 T) X1 G% r* G2 @24CXX系列的EEPROM进行写操作时需要注意,跨页写入时,要有一定的延时,否则会写入不成功。不同容量的页大小也不一样。
0 z5 u$ A/ ]0 r; |" C6 s, e: f A: N7 K4 A: K
1 ?1 e9 a. j. J4 V7 ^( U另外,24C16以下容量的地址为8位,24C32以上容量的地址为16位,在调用读写函数时需要注意,选择I2C_MEMADD_SIZE_8BIT或者I2C_MEMADD_SIZE_16BIT。测试使用的是24C64,所以选择I2C_MEMADD_SIZE_16BIT。
+ t' v2 v$ n% Y: F5 d6 d# a9 C
5 l' z! X6 T: h4 O( O( I" M F# q% S0 d9 i& {8 F0 W
为了方便操作,将读写函数再封装一层,将跨页写入的各种情况都考虑到,实现任意地址连续写入。程序如下:: F: ]: I2 d6 Z
$ v7 L, f- ?( n0 V/ R, U5 a) b. v- #include "at24c64.h"" _+ c- D6 c* N) C9 O0 m" Z
- #include "i2c.h"$ H3 j4 h0 z' z% K0 F
& M6 f* X1 r6 {5 ]) u+ g# _1 B- #define AT24CXX_ADDR_READ 0xA1
: y+ L" Z7 b7 X' j8 F3 y- c - #define AT24CXX_ADDR_WRITE 0xA0
# @% G8 K8 U E- P; y; y - #define PAGE_SIZE 32
0 S3 U4 D7 C# h- ^. q - /**
. L) W9 I' ^- N6 g' [ s6 T - * @brief AT24C64任意地址连续读多个字节数据0 O% v: K R$ Y& i
- * @param addr —— 读数据的地址(0-65535)
" C( _' I h* n6 _ - * @param dat —— 存放读出数据的地址6 m0 J5 a7 [( C+ m1 Y
- * @retval 成功 —— HAL_OK
3 W* {- F3 S+ K$ ~' e( o - */
4 P( }& ?$ p/ b - uint8_t At24cxx_Read_Amount_Byte(uint16_t addr, uint8_t* recv_buf, uint16_t size); W# ?, U& ~, c( g. M. x8 O
- {
- h: o8 N, ]. ^4 J" G" l l - return HAL_I2C_Mem_Read(&hi2c2, AT24CXX_ADDR_READ, addr, I2C_MEMADD_SIZE_16BIT, recv_buf, size, 0xFFFFFFFF);
# W& E" K1 F( m! {; {6 c' F9 o - }
9 Q' q. s2 w ?
3 I% X# L O" F3 h- /**! `! j- N& _1 B4 U. F
- * @brief AT24C64任意地址连续写多个字节数据
% Z! F/ n0 g/ F) k) r9 b, ~* H - * @param addr —— 写数据的地址(0-65535)
* X" c# e% \6 [6 C Y- [, c8 _1 D - * @param dat —— 存放写入数据的地址
# f, r. {5 J- u( x v9 Z& W - * @retval 成功 —— HAL_OK
C/ R4 x$ d' y2 r: c - */
% V# ?9 g1 t+ P - uint8_t At24cxx_Write_Amount_Byte(uint16_t addr, uint8_t* dat, uint16_t size)
* F3 g* }+ M0 q0 m7 y! N$ q - {- f+ |# L D* }* b' O
- uint8_t i = 0;( _/ r- v& m5 G6 [* o+ L
- uint16_t cnt = 0; //写入字节计数
t3 ]% F0 C% a -
# E, v+ G3 [5 m" I - /* 对于起始地址,有两种情况,分别判断 */. i8 u3 o7 \& S3 u9 e K/ g; i
- if(0 == addr % PAGE_SIZE )- @; X( l$ I" s
- {
* H) [# G* D4 t - /* 起始地址刚好是页开始地址 */3 n E7 ?( M* r6 U! P: C/ x
- " E7 R- u5 j3 Q5 R' d
- /* 对于写入的字节数,有两种情况,分别判断 */
& a1 J$ u1 I5 ^# v3 k - if(size <= PAGE_SIZE)
% v& _/ [! |7 r' W - {' y' {4 n" o. `( @
- //写入的字节数不大于一页,直接写入
9 f4 I, B+ @, L% ^! h) t7 e - return HAL_I2C_Mem_Write(&hi2c2, AT24CXX_ADDR_WRITE, addr, I2C_MEMADD_SIZE_16BIT, dat, size, 0xFFFFFFFF);8 V, U- y# ]7 y6 X( |/ ? q6 D
- }- a1 d- r. b1 w1 l1 T* p3 `
- else8 i1 l: q1 i3 V4 S
- {# s/ L: \4 A' g% i- m/ z
- //写入的字节数大于一页,先将整页循环写入
) q$ X) B5 g. D4 @ - for(i = 0;i < size/PAGE_SIZE; i++)9 Q0 q: N" ?1 X8 h# D+ P; s! H- y
- {
- r# ^ V, d* ^' J: F+ K - HAL_I2C_Mem_Write(&hi2c2, AT24CXX_ADDR_WRITE, addr, I2C_MEMADD_SIZE_16BIT, &dat[cnt], PAGE_SIZE, 0xFFFFFFFF);* w3 c7 h% f" Z" n, @
- HAL_Delay(3);
1 K3 y2 r, s( T, o/ {( S/ \ - addr += PAGE_SIZE;
* u- V. `3 ^* [ t3 o" E# n - cnt += PAGE_SIZE;. M* f/ n- r: O1 u9 G7 Q* n
- }
2 ?! A, R; `9 |) S& L+ I1 b - //将剩余的字节写入5 Z- J3 q4 P/ h) B6 A! h- E2 o0 a
- return HAL_I2C_Mem_Write(&hi2c2, AT24CXX_ADDR_WRITE, addr, I2C_MEMADD_SIZE_16BIT, &dat[cnt], size - cnt, 0xFFFFFFFF);- `0 L% n/ [2 E9 P9 E& z
- }- G) \0 J: \, K4 V/ n
- }
6 l# _, O' i# E1 M0 a @ - else" I/ }: l9 E' X" v3 H3 w
- {
# ~7 {% }" a, k4 X6 J - /* 起始地址偏离页开始地址 */7 m+ q& l* s7 Q- L9 q
- /* 对于写入的字节数,有两种情况,分别判断 */
" l9 `' N) k0 ~# ] - if(size <= (PAGE_SIZE - addr%PAGE_SIZE))0 ^; g' c0 @' r/ m' _
- {( z7 o5 u' u& w! E- a
- /* 在该页可以写完 */
3 z. E0 K- k/ E1 K5 I+ { - return HAL_I2C_Mem_Write(&hi2c2, AT24CXX_ADDR_WRITE, addr, I2C_MEMADD_SIZE_16BIT, dat, size, 0xFFFFFFFF);
6 q* S' f8 i* P& R7 v4 U5 q - }
' k D P6 i0 c; F8 y8 q. t$ } - else
1 J9 W: M- G# e - {
3 a* o# n9 {/ R) x! b- C4 { - /* 该页写不完 */
+ h1 n- Y" Q* S" j L: N - //先将该页写完
; C) g( L3 ~. v2 M) ]1 @5 Q0 t - cnt += PAGE_SIZE - addr%PAGE_SIZE;
. B# Q9 I( {# j% V - HAL_I2C_Mem_Write(&hi2c2, AT24CXX_ADDR_WRITE, addr, I2C_MEMADD_SIZE_16BIT, dat, cnt, 0xFFFFFFFF);% |1 I9 d8 S/ X d) U
- addr += cnt;; W) f2 c+ x4 G0 S- P
- HAL_Delay(3);0 M6 ~. M2 _- A8 A" `8 z
- //循环写整页数据$ f& @" F# c. L* V. Q5 g8 x
- for(i = 0;i < (size - cnt)/PAGE_SIZE; i++)
+ N) h \% T; R, M1 P; ? - {
' {0 `' w$ P$ R* O7 r - HAL_I2C_Mem_Write(&hi2c2, AT24CXX_ADDR_WRITE, addr, I2C_MEMADD_SIZE_16BIT, &dat[cnt], PAGE_SIZE, 0xFFFFFFFF);: m1 p) b ~( r' d/ U) I
- HAL_Delay(3);0 G( Y3 F u$ H- k9 j
- addr += PAGE_SIZE;) M7 d! m* h; x. O2 _" ?# Z
- cnt += PAGE_SIZE;
( A: p; b5 e2 E. F6 G* X; @) c - }
; H$ J9 q* G9 E$ c% L - 7 S/ Y X8 R/ Y( \2 \
- //将剩下的字节写入
' q) j J, _7 t( b - return HAL_I2C_Mem_Write(&hi2c2, AT24CXX_ADDR_WRITE, addr, I2C_MEMADD_SIZE_16BIT, &dat[cnt], size - cnt, 0xFFFFFFFF);" j8 ~) m3 X: N- U" |6 ~* ~) y; k
- } & D+ ?2 Y6 Q4 X. v: I
- }; Z# e* H; b$ R1 I+ }8 V
- }
复制代码 测试结果
) ?0 g L1 a/ b: V- C
" s, f1 \- G0 g$ |8 U" R+ l0 A
% U) M, f/ T* b7 ]$ ~经过测试硬件I2C读写EEPROM正常。没有发现所谓的BUG,当然这只是M4内核的针对EEPROM一种器件的测试,对于其它内核(M3等)和其它I2C器件,还有待验证。 E& H( g7 |+ T0 E& i: a% h
7 \* f$ y6 R- d( Z! D2 S
! [7 Y7 _" V6 X- F* G总结
$ k7 Q9 T6 y3 i- u* e
/ R# |4 Z4 q8 ^3 J, ^! e4 z5 `# p; H2 n2 Z( y9 _
硬件I2C使用起来比较简单,不需要自己去调节时序,但是只能使用固定的几个引脚。" x3 M9 U- C/ ~ Q
2 T, v" |$ U1 Z- r( p# J1 P7 E# K4 V
+ O6 g% `& i% `- r `6 L
软件模拟I2C可以使用任意引脚,针对不同的MCU,移植起来比较方便,但对于不同频率的MCU,时序调节比较麻烦。% y3 r4 T& y7 O" h3 N# {
' p( u8 H8 c2 r1 t$ p6 {, P8 H
! {; G6 {9 P& q; d8 {% Z, E两者各有其优缺点,需要根据实际需求去选择。/ g; E) v9 _& A i6 Z$ K
/ w0 N& J, P0 h* k: Q1 f
|