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

【经验分享】HAL库 STM32CubeMX教程十二---IIC(读取AT24C02 )

[复制链接]
STMCU小助手 发布时间:2022-3-23 16:00
前言:4 F& ?% `/ z* l, P+ B
本系列教程将HAL库与STM32CubeMX结合在一起讲解,使您可以更快速的学会各个模块的使用' d7 G+ }. N& V+ z7 f( }) V
8 Q3 {/ x0 d3 {3 M* d3 L
在之前的标准库中,STM32的硬件IIC非常复杂,更重要的是它并不稳定,所以都不推荐使用。4 w% W/ d/ [  I" ]
但是在我们的HAL库中,对硬件IIC做了全新的优化,使得之前软件IIC几百行代码,在HAL库中,只需要寥寥几行就可以完成 那么这篇文章将带你去感受下它的优异之处( Q& V6 w; N7 W  F( B

  F9 N. u* R4 V/ A5 Y, Y' ~6 `这可能是目前关于STM32CubeMX的硬件iic 讲的最全面和详细的一篇文章之一了  }2 @; K# a5 v. t, Y8 H7 q9 n

1 g5 `' r3 Y/ s5 l) ~. }所用工具:/ D9 f/ i0 A* j& V2 Z  m* p; D

" ]6 R* l- j4 t2 K; l8 Z; r1、芯片: STM32F103ZET6" r) p- N$ |1 y- O0 e8 y8 N
" T  {! y8 j/ E
2、STM32CubeMx软件
/ A/ r8 `% l! {/ ?! C, P1 a/ |5 c" l" D! l& E% T
3、IDE: MDK-Keil软件; U& q! v/ k( v# V0 M/ r$ i3 u
0 u: z" g( w2 u% M* f
4、STM32F1xx/STM32F4xxHAL库
! C+ O, A9 |- V: n: c; N
6 h$ }: [4 ?! Q/ d( g5、IIC: 使用硬件IIC1/ a9 ]4 Z& \- J2 G" X, _' q! J

7 `% D; }/ _5 U9 ^( q知识概括:$ j- V' S9 o& e9 w  B2 S

$ w: M' \  S5 f, x通过本篇博客您将学到:
6 L( V; V5 A( s! N: a/ Y" ^. E' q$ @) p# K* G$ l7 O
IIC的基本原理9 s% p1 t3 s) x8 x! m" g

* N5 g( _* ?/ aSTM32CubeMX创建IIC例程% e( H- U( q4 u1 m2 Y! q1 t
4 k3 o5 ~, [- P$ ~
HAL库IIC函数库
. W  t  u- o, _  K8 \3 t7 F3 q; d' }+ E7 C) P/ C
AT24C02 芯片原理) U% w) ?2 \) H( f  w% G

# O4 G9 K5 b, D0 g* a7 |1 J$ wIIC 简介
( P1 B1 e; q6 k7 TIIC(Inter-Integrated Circuit)总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。
( c8 C$ O% M- _! a+ B! t& g' s% D" p$ L! Q( }; M
在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。, G5 u, |6 `; G( S, c' L
4 c$ F& ]- B6 Q; B* E
PS: 这里要注意IIC是为了与低速设备通信而发明的,所以IIC的传输速率比不上SPI% K- r7 d7 p) r1 B
; {9 f! _+ c' [2 i. y7 y& y
IIC的物理层  F- u5 _0 z! }8 R6 z# Q* R, \
IIC一共有只有两个总线: 一条是双向的数据线SDA,一条是串行时钟线SCL; T1 Q/ O  g( U5 M
  h) h5 h1 n( J1 E
所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。I2C总线上的每一个设备都对应一个唯一的地址。
/ |9 p$ U3 Q  O% X2 r+ z& `# H& s% H) f* i
D}AKU7YZ{}~Y6MC6W%Z3IAR.png & A! G- O3 l  v) L
% e3 u& s) B" r$ _
关于IIC的讲解,已经单独整理了一篇文章:
; B0 z# F3 J+ u( s7 h3 s, L2 \% u2 y
《IIC原理超详细讲解—值得一看》。
' u- F. P% {# }& b如果对IIC还不是太了解的朋友请移步到这篇文章中
6 r( `! m% Q$ k1 ]. I3 [
, @1 g5 c$ a. ]1 H4 m9 v) z3 NIIC起始信号和终止信号:, _% I# w7 r: x6 u

; m( A- y5 a; D: t起始信号:SCL保持高电平,SDA由高电平变为低电平后,延时(>4.7us),SCL变为低电平。9 Q3 W5 @) Z3 {) p: z
停止信号:SCL保持高电平。SDA由低电平变为高电平。7 I$ Z0 N2 j" |8 n+ g& Q

) a1 T0 Z( |* I- p$ T5 d) p+ Y! A S2R5[}7Z`)G{P}2%QC5GHSD.png
. W9 a/ _1 p% ?* P9 b- v% a# \' o4 k
数据有效性2 ~" g" k8 ?$ U; {
IIC信号在数据传输过程中,当SCL=1高电平时,数据线SDA必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。0 U  e5 r. t# K; H+ z: n" c& P6 i0 _
/ r% m; `2 R  M0 a
SCL=1时 数据线SDA的任何电平变换会看做是总线的起始信号或者停止信号。0 n" }" x  R4 L2 [; w" X6 n) \% v
% ~% }! f: c# y. C( p! J# N) X$ @
也就是在IIC传输数据的过程中,SCL时钟线会频繁的转换电平,以保证数据的传输
0 w8 x8 W) U) ], G2 O. D+ M- |9 {* d8 ]( F6 p
M(Q@A~}}1`7L@B9LIT84{]L.png
- E) `4 `6 e4 C1 o/ J+ n  w2 y9 c6 ]2 N1 b- V$ E
应答信号
2 H' m) p. Q0 }% |* Q每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,3 V& n+ v* R# q; F% @3 g- t
% n) F3 d& U* J8 q/ X  `8 w8 H
应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答& v, }: c' E: v0 Q
5 n5 I% N0 _: ?3 e7 h
应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
5 S1 Q% @: c) m: b应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。* E) Y  c- O: Q! e* L7 s* f) e: `
1 |6 M6 @# P7 J, z
T4O0TA%5T7[2WNU]U$(FF7C.png : P4 V) n# b3 O) u" C  ?
, {( ~  {* ?; W% c
每发送一个字节(8个bit)在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须回一个ACK应答信号给发送器,这样才能进行数据传输。8 L. o+ @! I8 v  M6 f7 z. L3 w
. r) Q2 M. E- q7 b  g. S' s
应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答,
: [' V4 D- D6 x" l4 ]. [/ g9 o! |8 w; C+ ~
Z{ZNXO@GLVSV~GQGAYF4ZJC.png
- w2 A. S5 z7 Y1 u  `
* T! x7 J/ H* {* X, H5 v这里我们仅介绍基于AT24C02的IIC通信
4 Y" N; b/ E& ]& }4 i0 s% K. q! z
; T! K1 L9 F5 A9 W' J/ t, Z% O$ c+ |以AT24C02为例子2 G# w  N8 P* l
24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。在24C02里面有一个8字节的页写缓冲器。
9 m( ~# n+ U7 I/ Z; G
' R5 ~5 k0 N2 W [~@R9HH6_)]C5TLUF7~XB56.png
! V" x/ o2 ?0 D7 r; h) g# n# }# j* t! u6 C" F! I$ T
A0,A1,A2:硬件地址引脚! A. u# N, T, ^" J2 F! P
WP:写保护引脚,接高电平只读,接地允许读和写
: a5 p2 p2 l; ]1 [# ESCL和SDA:IIC总线5 E/ Y, A; Y' {. Q7 y/ ~- ~; Y
; Z& ?1 ^$ D' F6 Q$ u
0 P3 B! F! `+ R( E* T/ |+ n+ y7 P4 j6 k
以看出对于不同大小的24Cxx,具有不同的从器件地址。由于24C02为2k容量,也就是说只需要参考图中第一行的内容:
: L9 I4 F' g) I% m$ G
0 L1 G. `" @8 B+ K N4YO0DDI0)A}[TPV1WJS%LA.png ; V9 f, q4 p- e8 \, I) }) E) B

- P, I% b6 v0 m3 U8 y芯片的寻址:8 f% D; f! M9 U
AT24C设备地址为如下,前四位固定为1010,A2~A0为由管脚电平。AT24CXX EEPROM Board模块中默认为接地。所以A2~A0默认为000,最后一位表示读写操作。所以AT24Cxx的读地址为0xA1,写地址为0xA0。. K% p0 E' A9 V! d- s

$ Y+ v- s1 d" \- Y" i. t! T3 Y3 d也就是说如果是
5 E6 A5 ^8 w- O( x写24C02的时候,从器件地址为10100000(0xA0);
/ j* X, C% H5 C* n读24C02的时候,从器件地址为10100001(0xA1)。5 A, a1 P7 g6 Y
4 H3 @# x. P4 R! D) q! D
片内地址寻址:
5 U3 n  g5 ]( R* x9 N; h! P% R" K3 l: I; m0 w$ E6 P3 U3 t0 `( j7 k/ q
芯片寻址可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单位。
+ {. m1 K" w: f& c: l' z
. P! q" I/ c& T& D' q; l5 W# k5 O对应的修改 A2A1A0 三位数据即可% r3 h& ]$ b- P# P8 v
! d* V1 j6 z: t5 y9 X8 G
%L{8Q9IZVYLK57)VFQ(GD5L.png ; ~8 q0 g5 Q7 X

9 b6 U: N( [+ _- C. ?/ R6 w7 `向AT24C02中写数据" e% u" R/ W! m5 P5 V* N

4 X5 d! l0 N5 l7 Z/ V! { GVF(F2T]4}{O4}~FQ05B3JB.png $ T1 J! Q) j  z
6 j; u$ S% F# H+ h' {- }$ X! P
操作时序:
$ P+ c, ]! s: c0 V# Y, R4 U1.MCU先发送一个开始信号(START)启动总线
1 C) ~% v% n. }2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)) T, V1 Y% H! A' f+ V+ w
3.等待应答信号(ACK)
; Q0 }$ Y7 Z3 M7 v. `& [1 i% X9 `4.发送数据的存储地址。24C02一共有256个字节的存储空间,地址从0x00~0xFF,想把数据存储>在哪个位置,此刻写的就是哪个地址。# b* F1 J) l4 ?" B/ y/ T! y6 V
5.发送要存储的数据第一字节、第二字节、…注意在写数据的过程中,E2PROM每个字节都会>回应一个“应答位0”,老告诉我们写E2PROM数据成功,如果没有回应答位,说明写入不成功。
+ Q8 B5 f# [4 r: v* G( ^) [- d6.发送结束信号(STOP)停止总线" I! i# p3 p- U+ G8 D/ l) \
注意:, I1 L4 Y  P2 \% W$ Y4 t. O
在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址就会溢出又变成0x00。
9 C; l; R8 H  ]" F5 @
# R1 f0 {2 O9 J. L" \写数据的时候需要注意,E2PROM是先写到缓冲区,然后再“搬运到”到掉电非易失区。所以这个过程需要一定的时间,AT24C02这个过程是不超过5ms!2 k% m: z- U6 i5 u2 N% p
所以,当我们在写多个字节时,写入一个字节之后,再写入下一个字节之前,必须延时5ms才可以
  A2 `, F/ o' H) k1 o) P; M
& F, D1 _7 G2 C( M- J' e7 o从AT24C02中读数据/ }0 L; U$ H+ ~! N) k

9 y# L- L. p7 l# Y( h3 B8 j读当前地址的数据
2 h* u, {/ E! a6 }& H2 ^
- T$ X/ t/ J! ^) o& ` [%JNO9U_{}GE@N5QAAXNBIV.png 6 K! W* N* I; e( t
9 V! z& j$ T% K
2、读随机地址的数据

% m1 ]& c$ ~% s% U4 p- l5 |* T2 c- n7 @1 m& y
9 x$ [8 o9 S0 ?9 G3 R5 j
1.MCU先发送一个开始信号(START)启动总线
# M. V1 u. U* I2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)
: W  v3 D7 R0 P* P注意:这里写操作是为了要把所要读的数据的存储地址先写进去,告诉E2PROM要读取哪个地址的数据。
  A- y  C: r2 ?" r! c3.发送要读取内存的地址(WORD ADDRESS),通知E2PROM读取要哪个地址的信息。7 [7 |4 X+ {/ R. X* t2 h
4.重新发送开始信号(START)! D* F3 c" e  @1 j- L
5.发送设备读操作地址(DEVICE ADDRESS)对E2PROM进行读操作 (0xA1)" S0 w$ o, |4 Z& N; c3 x
6.E2PROM会自动向主机发送数据,主机读取从器件发回的数据,在读一个字节后,MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据
) {* K) {1 n# A; ~' _' _7 \- i+ D7.如果不想读了,告诉E2PROM不想要数据了,就发送一个“非应答位NAK(1)”。发送结束信号(STOP)停止总线
7 e' ?  @- ~" |7 m# |! y% A' ~- t( J" n6 g- H8 \( D' [' q
3、连续读数据
; R$ C0 J3 w( v0 i2 c4 G0 b: N
( E/ f- I/ B; S$ Q9 r9 l: }8 p, j YA%U6OE)]I$FOPP{TSTY6]W.png
# D& @3 |5 ?4 V4 Z3 b5 P/ M( `E2PROM支持连续写操作,操作和单个字节类似,先发送设备写操作地址(DEVICE ADDRESS),然后发送内存起始地址(WORD ADDRESS),MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据。E2PROM的地址指针会自动递增,数据会依次保存在内存中。不应答发送结束信号后终止传输。
) l- s  J) |5 T* N) h/ \3 V# H
4 c2 X3 F3 g% q9 n5 [' l% w基于CubeMx的讲解

/ |# L* |+ D! l. p# L, W1设置RCC时钟
4 s, W  P. b! F. a1 W( l; g
8 l( E$ y- R: {. A
设置高速外部时钟HSE 选择外部时钟源' a/ s0 R& H& D7 d3 f# o9 j$ U
( b6 G, n: ~' _7 I/ Z( B! ]  r
H~}{4GU_`0~NYL1QTZ68CVU.png
5 k& h5 g( s( i. w! b1 F
; I+ ]+ B" ?3 v8 b3 a2 IIC设置
* K4 E; p% S* Y5 c6 W# N, @7 i+ @* Y6 w- |8 f
N}]X7_8ABWVCGHQ7[%]6NT8.png . x8 j% i* {2 d" `! R' H: {
点击I2C1 设置为I2C 因为我们的硬件IIC 芯片一般都是主设备,也就是一般情况设置主模式即可! X" ~3 d1 p, Q* Z* {$ T+ F

' h* t- j7 [" o/ ZMaster  features  主模式特性
& F* d8 C  q! T, a' j6 [- y  T6 `6 u" H, e7 n+ y" Q5 s
I2C Speed Mode: IIC模式设置 快速模式和标准模式。实际上也就是速率的选择。7 i- t2 ^3 g! i- }
8 k. _. t3 F, L
I2C Clock Speed:I2C传输速率,默认为100KHz# K' \! {" b& Z9 g' E- b
5 }0 F& ]( o& x" t0 {* Q" u, c
Slave  features  从模式特性$ K0 t; @' {/ O! g4 R; u$ g

* C9 `7 `2 A, _, G) YClock No Stretch Mode: 时钟没有扩展模式! n2 K8 f+ A+ I, n  `

- O) B3 Z5 c5 A. J5 ?& c8 k+ XIIC时钟拉伸(Clock stretching)/ j* ?$ I- U, K, Z
clock stretching通过将SCL线拉低来暂停一个传输.直到释放SCL线为高电平,传输才继续进行.clock stretching是可选的,实际上大多数从设备不包括SCL驱动,所以它们不能stretch时钟.
3 V& Y8 f1 O: @6 HPrimary Address Length selection: 从设备地址长度 设置从设备的地址是7bit还是10bit 大部分为7bit
5 G. N- O  _2 q9 u-Dual Address Acknowledged: 双地址确认9 ^$ s! ?8 X5 _$ p& m
' m& n' P& C3 y  T1 z( v: R
Primary slave address:  从设备初始地址
4 }1 n+ r. x  h
, l* |" t: A) j9 W9 l5 r' [这里我们保持默认即可
9 u0 a6 t! ?2 d/ X4 K' `
+ g9 r. N: Y. S( M: V3 串口设置* _. x* \* L6 ]  T
4 W8 n5 c/ [' |2 U& s: `
%)KSYV55BDGN]ECSR}C7O8P.png
" f, ~2 B' d" P1 G: q( Y/ I* H8 R2 p& c; _" P0 e
因为我们需要将AT24C02中存储的数据发送到上位机上,所以需要设置下串口) l- _8 @3 a3 k& x
1 O; M, T( J. l% A  ~- K0 g- y2 U
这里设置为异步通信,其他的默认即可8 F) l' K$ w/ d7 [% @9 C5 {" f5 U
0 d- R- g: [" z8 V
5 时钟源设置
! p1 |; f% }) W* r, I5 x  x" P& ]' v8 p1 I
{UI$O17GRVD)@4$XOFUYj.png ( Z- G- r4 ]' \$ s) X
# z4 x0 C. r2 `1 W& A& S# k/ C
我的是 外部晶振为8MHz7 m# Z; S" s$ v& r" A. f
1选择外部时钟HSE 8MHz
2 a3 v1 D9 b4 q& R) g2PLL锁相环倍频9倍; f0 M- I3 p# v% T
3系统时钟来源选择为PLL
8 A6 C9 |" b% f4设置APB1分频器为 /23 A' O9 t8 a( K+ r9 R* D
5 使能CSS监视时钟! v% ~3 b' ~: L) K- ]/ T: f4 N
3 o) a$ ?9 s# a  R+ {
6 项目文件设置/ H& W; A$ C" N" [

5 D, C6 p* w# L) g" u, t T77Z1G%_O$[~PTC6J)ZTJVC.png % m, q& _  Z# F; J
( }0 X' m" L7 o
1 设置项目名称$ Y- B- V. Z: M& t3 i4 t
2 设置存储路径# w# F1 v' _$ o) [2 e  `6 F# f
3 选择所用IDE; f0 [: ~& B  V, i  V5 S; ]& s
4 d' g2 y& O9 T  O0 r# o
$HL2@`Y[HUT60}IW074]CTO.png 1 G2 E4 @4 m8 Z. S- G, j

! H) O5 i, ~* p0 @! n" _7创建工程文件

2 y+ X' ?/ q! t7 R4 T5 h" _  P2 N  ?9 d2 s
然后点击GENERATE CODE 创建工程
4 b+ ?, V- {- k) B% Y6 u
/ C: |/ h: N( _, [配置下载工具& U. {9 i: c; U
新建的工程所有配置都是默认的 我们需要自行选择下载模式,勾选上下载后复位运行
& Y! K, S( z! q, b( |$ U
' [% J( A% C/ T0 E4 ? %6{23MK8K4O39(CF8REOY2D.png
8 S  F, W7 |' f( Y
: l$ Z+ H0 w" P: n3 JIIC HAL库代码部分
7 m+ r4 ~2 `" J4 O在i2c.c文件中可以看到IIC初始化函数。在stm32f1xx_hal_i2c.h头文件中可以看到I2C的操作函数。分别对应轮询,中断和DMA三种控制方式: s- q/ q' B$ n' \# d) s% p: k% g" y

) e. T9 C6 {+ N: W- a: B% E# z X(0}523E8C`}{VUFC[ETK%V.png ) p; L/ _% ?$ B
, P1 ]1 c8 ?: `8 P  s" `3 l. O& U* f
上面的函数看起来多,但是只是发送和接收的方式改变了,函数的参数和本质功能并没有改变0 F* l# N8 \& m' j" @( h6 G8 k" b
比方说IIC发送函数 还是发送函数,只不过有普通发送,DMA传输,中断 的几种发送模式
, R- [* e* k5 }" p% B& I) T% p  K0 l; Q
这里我们仅介绍下普通发送,其他的只是改下函数名即可
+ s, |  D  ?7 Q! O8 U
& J7 Q& t: r( o% HIIC写函数" B. Y' v: N8 Y) m7 q
3 S6 d+ y5 t6 q! {7 q; m, _8 |# T. \/ N
  1. HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
复制代码

$ a1 O$ _& X+ C( \% m功能:IIC写数据3 M4 m9 a( b8 r9 Q: @1 s) E
参数:, a% i+ z) ~2 k1 }
*hi2c 设置使用的是那个IIC 例:&hi2c2" r3 q9 V' z$ L/ R6 p
DevAddress 写入的地址 设置写入数据的地址 例 0xA0
: y. X# \# v6 r# b1 h*pData 需要写入的数据
/ T9 [+ T* @) g3 I8 u0 S" YSize 要发送的字节数
! {9 ~: W8 F) H$ gTimeout 最大传输时间,超过传输时间将自动退出传输函数. u0 C" B" X% W6 P" W6 o
6 V- x3 i% O% \3 [( M7 R, M
IIC读函数  J4 D! @* e  X# ^$ i
( c5 F- W/ R. w  o' |
  1. HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
复制代码
* F! Z8 ?& X2 \) a2 B4 `/ g" p
功能:IIC读一个字节$ b7 o/ E3 ]* o7 Y
参数:1 M0 N, e5 O/ c3 _( e7 T. }
3 R) g& G8 ~! Y. n
*hi2c: 设置使用的是那个IIC 例:&hi2c2
4 A8 N1 t! _) _- Z" P4 r' ^' h
' r& X6 f) A: Z6 c( b# XDevAddress: 写入的地址 设置写入数据的地址 例 0xA0" p" j7 }9 f$ N# C
+ e5 S# m" q3 j2 R
*pDat:a 存储读取到的数据3 @8 p/ t. w& {# O

! t" T$ g# X, V& n) FSize: 发送的字节数, X5 u2 ^. Q/ V4 s

4 Z& b! q8 S% a4 V, dTimeout: 最大读取时间,超过时间将自动退出读取函数
2 `: s6 K! ~0 p6 q
! o, S9 M" u7 ?0 y2 \: m举例:
& U3 W. y# I0 {2 d0 b# B2 m- O* o5 j4 l; `. z
  1. HAL_I2C_Master_Transmit(&hi2c1,0xA1,(uint8_t*)TxData,2,1000) ;;
复制代码
! L9 J7 ]$ Q! o
发送两个字节数据& v4 ~' z# |3 l% @) V  k

! m& z2 P8 f4 Z0 N3 _5 B: ^IIC写数据函数
- H. T9 X+ \; _+ z& Z$ o

; H% i6 W0 I+ a9 E; G) f( M, ^- a
  1. HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
    . @. V' B. {' I4 [$ h
  2. /* 第1个参数为I2C操作句柄6 c3 h1 Z7 N, X  Z7 n& e
  3. 第2个参数为从机设备地址) |5 a/ Y* e) r; h2 n- M% C
  4. 第3个参数为从机寄存器地址0 @7 n- I  `0 e/ T, a( _9 X
  5. 第4个参数为从机寄存器地址长度
    4 F2 Z/ u. l; t, i$ t8 U3 x
  6. 第5个参数为发送的数据的起始地址* |0 H# i: o* P) Y
  7. 第6个参数为传输数据的大小* t7 `. ^1 s! @6 `
  8. 第7个参数为操作超时时间   */
复制代码
. O. X6 y) u) X0 H
功能: IIC写多个数据 该函数适用于IIC外设里面还有子地址寄存器的设备,比方说E2PROM,除了设备地址,每个存储字节都有其对应的地址+ Z/ v& G" A, O
/ Z3 g" G$ l  u- `: ?. @3 r
参数:
6 x" X/ P! ~/ L/ V0 y+ ]1 k" H
  }2 ~1 R' _- V: J) d* c9 G1 s% q*hi2c: I2C设备号指针,设置使用的是那个IIC 例:&hi2c24 E6 Z3 e4 v; I( E$ z

1 q8 S  P9 ~! S6 V7 p% jDevAddress: 从设备地址 从设备的IIC地址 例E2PROM的设备地址 0xA0& F" j' ~* _$ k/ A# O8 ?( q2 N
; T7 B- q4 w  D* ]1 h. @5 ?& t
MemAddress: 从机寄存器地址 ,每写入一个字节数据,地址就会自动+1
% B5 f7 v, }3 m8 |3 u9 o5 G9 U1 M0 c- Q$ p9 L% D. d* [& \
MemAddSize: 从机寄存器地址字节长度 8位或16位
4 j/ S0 t0 O( U! |8 R" g% Y3 p& Q. K" p0 ^3 E4 F1 M+ u6 Y
写入数据的字节类型 8位还是16位
5 e% t% H  O3 d3 R  lI2C_MEMADD_SIZE_8BIT" j' e* m6 Z, Y
I2C_MEMADD_SIZE_16BIT& {; l, \- r9 Z! W! p2 ]) D/ d& ^
在stm32f1xx_hal_i2c.h中有定义  h. N7 V; W) ?5 g4 A  t6 t
4 T0 p9 k* X% \: c* e3 V5 I$ e
{R2Q(IR$`LHMIG`7XKEUS[1.png 3 [( c1 I/ N4 ]% H. G1 T, i' Q3 v6 V
; o4 l# Z  e. |" F" k! X) d0 A
*pData: 需要写入的的数据的起始地址! L2 ~' d, J! s4 Z5 k
1 i9 F( B1 A( g; `; Z" _- {
Size: 传输数据的大小 多少个字节
/ z1 F. u4 d& J4 ~3 S1 ]5 f0 x) E% o" T' Y0 v2 K) J
Timeout: 最大读取时间,超过时间将自动退出函数
" ?* u4 B4 d5 ~' p8 C/ I4 \' x) n$ {( ?7 z
使用HAL_I2C_Mem_Write等于先使用HAL_I2C_Master_Transmit传输第一个寄存器地址,再用HAL_I2C_Master_Transmit传输写入第一个寄存器的数据。可以传输多个数据
* C5 z- u" P' Y! O" G! l, y( R" g3 `1 v8 b7 ?
  1. void Single_WriteI2C(uint8_t REG_Address,uint8_t REG_data)
    2 J7 \0 T' g: k% a& Q4 M6 i
  2. {( P2 G0 ?# j0 a- D2 c8 Q
  3. uint8_t TxData[2] = {REG_Address,REG_data};4 b, e- f) S9 I: H) _7 f$ p
  4. while(HAL_I2C_Master_Transmit(&hi2c1,I2C1_WRITE_ADDRESS,(uint8_t*)TxData,2,1000) != HAL_OK)
    . o5 K6 t+ M* ^: ^  o4 ?
  5. {% I* g7 H1 Y3 w2 x
  6. if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)
    , d  _+ O% m  G/ g
  7. {
    , ^8 f2 l' G7 ~% `6 y, d
  8. Error_Handler();
    * P; o% ?" \5 \: Q5 A$ A. d- z
  9. }2 m/ b: F4 d7 B" W+ e6 D7 c
  10. }
    6 G/ D$ q' e* j
  11. }
复制代码
4 p- P6 a$ b+ l, l" |+ L
在传输过程,寄存器地址和源数据地址是会自加的。3 n& A7 m; I  N4 N( M

1 T! Z5 ~' v2 ~" r至于读函数也是如此,因此用HAL_I2C_Mem_Write和HAL_I2C_Mem_Read,来写读指定设备的指定寄存器数据是十分方便的,让设计过程省了好多步骤。. V0 a; N( f% j* h

- s2 ^0 |: X6 o- R$ {, W8 B举例:
& x; d# ~  t' b. b/ D& P- X3 S- P* G5 D% R. k; _
8位:
  1. HAL_I2C_Mem_Write(&hi2c2, ADDR, i, I2C_MEMADD_SIZE_8BIT,&(I2C_Buffer_Write<i>),8, 1000);</i>4 j+ M) w; V2 S
  2. 0 T( @0 ~# K  ?' \0 f" v0 |# m
  3. HAL_I2C_Mem_Read(&hi2c2, ADDR, i, I2C_MEMADD_SIZE_8BIT,&(I2C_Buffer_Write<span style="font-style: italic;"><span style="font-style: normal;">),8, 1000);</span></span>
复制代码
: H" f; y, M: E; J& n- k
16位:6 y4 e1 E# O. V6 y5 N* Q
  1. HAL_I2C_Mem_Write(&hi2c2, ADDR, i, I2C_MEMADD_SIZE_16BIT,&(I2C_Buffer_Write<span style="font-style: italic;"><span style="font-style: normal;">),8, 1000);6 c5 Q9 i( u# Y4 t5 ~
  2. 9 M6 |/ g' n7 n: y$ x/ y8 A
  3. HAL_I2C_Mem_Read(&hi2c2, ADDR, i, I2C_MEMADD_SIZE_16BIT,&(I2C_Buffer_Write</span><span style="font-style: normal;">),8, 1000);</span></span>
复制代码
' }5 T1 J* @- k& I. s
如果只往某个外设中写数据,则用Master_Transmit。 如果是外设里面还有子地址,例如我们的E2PROM,有设备地址,还有每个数据的寄存器存储地址。则用Mem_Write。" d, E3 v4 w6 q) k4 G
Mem_Write是2个地址,Master_Transmit只有从机地址
! _8 L( ^$ p% Q0 T0 B, Y# r6 T
0 H/ b! z& X' [$ K. c% b! w5 Z硬件IIC读取AT24C023 S% d6 P  V' [; N% i* a
在mian.c文件前面声明,AT24C02 写地址和读地址 ,定义写数据数组,和读数据数组" G% X& D6 q9 f
5 c. W5 E( m% ]  a
  1. /* USER CODE BEGIN PV */
    * {8 j6 q* g/ _, f
  2. #include <string.h>
    + I9 D! b; J3 s$ J+ O
  3. " }4 S- d- L" g; q1 x# q& @
  4. #define ADDR_24LCxx_Write 0xA0) y$ P2 x6 I: f) K0 m  o4 o- ^& c
  5. #define ADDR_24LCxx_Read 0xA1$ g0 {4 b# z9 O' H  ~6 Z: M
  6. #define BufferSize 256  T* Z6 B, B7 E; Z: u9 g
  7. uint8_t WriteBuffer[BufferSize],ReadBuffer[BufferSize];9 }9 G8 Y* J! R
  8. uint16_t i;
    / v% C, X' c, }
  9. /* USER CODE END PV */
    " [: K5 ~' K  z% X6 r' k. s( Z
复制代码
6 q- z- \4 }" g& U
重新定义printf函数
# z3 @& ]$ e. {1 G/ @! t1 h; D+ w7 Q' p- ?0 k
在 stm32f4xx_hal.c中包含#include <stdio.h>' i, E7 p4 `; B2 i. K
. ~4 K- W* z0 {7 N
  1. #include "stm32f4xx_hal.h"" ^" @% E  X0 b# Y, e0 e
  2. #include <stdio.h>
    2 {% J0 u# J9 a) f+ B3 _
  3. extern UART_HandleTypeDef huart1;   //声明串口
复制代码
0 E& n# F6 }) K0 r9 \
在 stm32f4xx_hal.c 中重写fget和fput函数! j$ i5 Z. p/ m, t
# Z! v1 p7 |0 C7 A; c
  1. /**1 `% B/ P+ x: z& v9 }" C

  2. / a" H+ v& u1 B5 {- l2 R4 [
  3. * 函数功能: 重定向c库函数printf到DEBUG_USARTx
    ' E  _! q0 }8 s. w4 Q9 `7 E
  4. * 输入参数: 无1 e- v9 C+ l! l0 h9 P$ e3 l
  5. * 返 回 值: 无
    3 z( B' g$ ^8 l" V# J
  6. * 说    明:无! q0 L+ n- X6 e" J/ Q9 e- g* Z4 l
  7.   */
    ; A0 M4 G. \0 v# B( k
  8.   int fputc(int ch, FILE *f). R0 S9 z* P  L
  9.   {" p% G6 V3 I  b* j( Z
  10.   HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
    " V- q; A: X, }2 j9 n8 h5 p5 J7 y# d
  11.   return ch;7 e0 N% K6 V7 h6 U( h! Y2 p! d( W
  12.   }& `2 J" k- U3 a) s9 ^8 {/ b
  13. 8 P- G$ t4 ?% F7 B0 \, k% I
  14. /**
    4 u5 B1 r5 X5 t; H5 D% I

  15. 5 P0 f: T8 v' g- x2 P, J/ e
  16. * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx! z% w' R$ b" w( X- T2 c
  17. * 输入参数: 无7 ?+ b, ?4 p2 f. t
  18. * 返 回 值: 无+ {, b7 n. g$ D
  19. * 说    明:无
      z4 ?* g. E) g( C
  20.   */4 k0 @/ u+ G" v2 @" A
  21.   int fgetc(FILE *f), h$ m/ [4 O5 E! `; @
  22.   {( ^, z8 ?6 _. ?$ g1 J
  23.   uint8_t ch = 0;
    " V9 J3 f  ^3 V
  24.   HAL_UART_Receive(&huart1, &ch, 1, 0xffff);# w* ?4 }8 Q  q, d! }4 |8 I
  25.   return ch;/ p& b2 r/ w# ~$ O/ Q" w
  26.   }
    5 J0 G  L; b6 P6 N2 Q  l1 Y4 \, x0 P
复制代码
4 [# ]1 u0 R" }- @
在main.c中添加# u8 R' B1 e8 y7 O) k: m4 @

7 @' k; t  c* u  M
  1. /* USER CODE BEGIN 2 */% p+ ^/ b" w/ t3 c$ H7 f
  2. for(i=0; i<256; i++)8 d( C+ T& E/ v) ^( G( m
  3. WriteBuffer<span style="font-style: italic;"><span style="font-style: normal;">=i;    /* WriteBuffer init */0 @. ^" `  p9 t* g! }# z4 s

  4. & q  ?) v) E8 V+ Q. H- F
  5.         printf("\r\n***************I2C Example Z小旋测试*******************************\r\n");
    $ L% n& |; B1 G3 ?
  6.                 for (int j=0; j<32; j++)
    7 c' o% B# }, Z$ F: J! o0 f+ [  V
  7.     {6 }- h* c. K. p: Y- H5 t
  8.             if(HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, 8*j, I2C_MEMADD_SIZE_8BIT,WriteBuffer+8*j,8, 1000) == HAL_OK)1 |! O4 ^5 {" d: y3 l: @5 ?, r
  9.             {
    4 S3 a* `9 K% p* s7 u
  10.                             printf("\r\n EEPROM 24C02 Write Test OK \r\n");( V. J- S" q( O  O; l
  11.                     HAL_Delay(20);" ]( G' u3 p; o$ u# h
  12.             }
    . L& A! N1 B( y1 K- N, m- e7 _
  13.             else
    1 U  n1 q: k* g* F
  14.             {+ ?! C3 \+ f* l9 N/ n. y
  15.                      HAL_Delay(20);9 ]. t" S3 P7 s& _- y
  16.                             printf("\r\n EEPROM 24C02 Write Test False \r\n");
    ) n) x% b! x# [, Y5 B
  17.             }: K& O* ?) P+ y5 `$ s% c
  18.         }5 m' |. d' _# P2 N3 ?, |+ u
  19.         /*- W1 \) W* w, v
  20.         // wrinte date to EEPROM   如果要一次写一个字节,写256次,用这里的代码
    ; t( Z/ k, O  J3 [$ P; l, w
  21.         for(i=0;i<BufferSize;i++)5 A3 W2 i3 f- N7 ^; m
  22.         {. u$ x9 `2 e0 G- B+ E
  23.             HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, i, I2C_MEMADD_SIZE_8BIT,&WriteBuffer</span><span style="font-style: normal;">,1,0xff);//使用I2C块读,出错。因此采用此种方式,逐个单字节写入
    3 D) N) {! T% a/ q( @
  24.           HAL_Delay(5);//此处延时必加,与AT24C02写时序有关$ d4 p/ H' N/ o$ \* T9 f
  25.         }4 R, D6 z; U+ _0 v+ ^
  26.         printf("\r\n EEPROM 24C02 Write Test OK \r\n");
    4 r! e& w. ]/ ?# g, \( O3 _0 L
  27.         */& f/ k3 c. S/ }5 t) i# _8 ?$ K

  28. 1 p: F- K+ e6 n3 Z
  29.         HAL_I2C_Mem_Read(&hi2c1, ADDR_24LCxx_Read, 0, I2C_MEMADD_SIZE_8BIT,ReadBuffer,BufferSize, 0xff);
    / w, |1 P4 y& M( w% W4 m2 O

  30. . G* U( m( B! _3 _) x0 X
  31.         for(i=0; i<256; i++)" e. ?  v. |# D- v& V. ~
  32.                 printf("0x%02X  ",ReadBuffer</span><span style="font-style: normal;">);
    - m+ H* v" d8 T( f
  33.         # D4 a7 E! _3 C. G: T; J
  34. /* USER CODE END 2 */
    ( T; ~/ D; C, B0 l8 u/ H
  35. </span></span>
复制代码

( j" \, A; L: D注意事项:9 r* i( J5 `) |3 T2 ?$ I
AT24C02的IIC每次写之后要延时一段时间才能继续写 每次写之后要delay 5ms左右 不管硬件IIC采用何种形式(DMA,IT),都要确保两次写入的间隔大于5ms;& }# Q% {$ M$ M7 ~: o  G
读写函数最后一个超时调整为1000以上 因为我们一次写8个字节,延时要久一点) J4 e' F& Q1 o
AT24C02页写入只支持8个byte,所以需要分32次写入。这不是HAL库的bug,而是AT24C02的限制,其他的EEPROM可以支持更多byte的写入。

, I) }( Q3 i  _8 Q' R5 _  O
- \) s9 s( V3 w& ]& V0 t: q1 A当然,你也可以每次写一个字节,分成256次写入,也是可以的 那就用注释了的代码即可
  1.         /*3 p+ F" r9 l$ z- o+ d/ c) t
  2.         // wrinte date to EEPROM   如果要一次写一个字节,写256次,用这里的代码
      N# S# a) e# A, ^
  3.         for(i=0;i<BufferSize;i++)) F% I: @1 P1 F$ d0 T  V
  4.         {& X- T* N) D. \6 D5 p
  5.             HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, i, I2C_MEMADD_SIZE_8BIT,&WriteBuffer<span style="font-style: italic;"><span style="font-style: normal;">,1,0xff);//使用I2C块读,出错。因此采用此种方式,逐个单字节写入
    + ~* U8 _& X0 ?2 F- {
  6.           HAL_Delay(5);//此处延时必加,与AT24C02写时序有关
    # ~7 `2 g1 ^9 D7 F3 D
  7.         }* ?) K/ e% s8 N# Z
  8.         printf("\r\n EEPROM 24C02 Write Test OK \r\n");; o6 j4 U! W' A. N( N
  9.         */</span></span>
复制代码

  N$ ?3 U% {4 |. C注意读取AT24C02数据的时候延时也要久一点,否则会造成读的数据不完整/ p+ _8 A  C2 |' j3 H2 F# X& A% z: L  E5 N
1 y% N2 E8 Y  J" U" l- V
2 m0 X( J: v* n/ ?) B/ E- O! ?

  `! G7 S& h* X% |: G& n' S经测试,例程正常) |9 s3 F( B2 r! D; C9 I

7 \" o- A5 r( j7 U1 @) a- e
* k" ?8 s0 i6 E! k; ?
}HXE1$R$NS[]3IB84QTD03L.png
  q$ t! N: M* W- l4 J6 M0 O- V
收藏 评论0 发布时间:2022-3-23 16:00

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版