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

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

[复制链接]
STMCU小助手 发布时间:2022-3-23 16:00
前言:
3 L" }! X* }* [# Y% a本系列教程将HAL库与STM32CubeMX结合在一起讲解,使您可以更快速的学会各个模块的使用8 y. R, C! O6 f8 F
4 e, `6 a- Q' g2 u2 y$ \: D- r
在之前的标准库中,STM32的硬件IIC非常复杂,更重要的是它并不稳定,所以都不推荐使用。2 \. m) ?7 M% i9 t
但是在我们的HAL库中,对硬件IIC做了全新的优化,使得之前软件IIC几百行代码,在HAL库中,只需要寥寥几行就可以完成 那么这篇文章将带你去感受下它的优异之处1 I& i( o# E4 x/ }$ `7 r  o

. p. Z# Y8 ~$ E/ b; g- W! f这可能是目前关于STM32CubeMX的硬件iic 讲的最全面和详细的一篇文章之一了
% U/ o: e: B( w3 Y; y+ c
4 K: h2 B% W" O1 P所用工具:% G& q2 |) t4 C. H3 y

) `% z/ t% P3 v1、芯片: STM32F103ZET6. X. A' I& s0 v5 \3 H/ n
0 [' q7 G5 _6 t( w4 {; x4 ^
2、STM32CubeMx软件
; ?$ \( }7 n! y2 T8 Y4 l# h+ T4 d
& [5 {2 p# W6 k! I7 L4 \3、IDE: MDK-Keil软件
. h3 q) u# r% z# z$ o) A$ o; e) \# {
4、STM32F1xx/STM32F4xxHAL库
: p/ M. @" L) _& w, m' |. s' a* C- p: P: n2 M; O2 {3 _6 X- a, i
5、IIC: 使用硬件IIC14 V* v5 n# |8 S1 w: Q% Q

) t6 b+ o: t' H7 h1 \知识概括:
: g* S! }7 a) a, `- R' J: y1 a6 D
& T5 V- A5 c% q) ^4 _, o通过本篇博客您将学到:8 E$ h' k1 m- H, s2 @

' Z, M' \% r! _. G0 E) zIIC的基本原理9 t0 |/ ^) I- y. M3 J

6 t' T9 i- Q2 ASTM32CubeMX创建IIC例程( B) ^* _/ H& R( t+ R! u' v3 C# {
' ]" m) q' {. [, N9 i) z5 O4 a
HAL库IIC函数库
* s& p% A+ c6 _$ i6 }6 S% W; h
. w0 U; x1 C; y& `4 X; \$ fAT24C02 芯片原理
$ ^/ Q4 N% Z* u6 W5 X+ P0 Z2 Y& p! ^( D& q- J
IIC 简介
( s; u# ?7 F$ R, T# ]  FIIC(Inter-Integrated Circuit)总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。
4 l0 y9 v( j4 J: |& {3 g0 ?' J3 p& b6 B" p1 B
在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。
/ }5 U" G) S7 p, r9 o/ P- V9 q: y' M- }3 @; `
PS: 这里要注意IIC是为了与低速设备通信而发明的,所以IIC的传输速率比不上SPI
1 @  C4 L9 k# {8 L- L# r4 f
' C+ W) [2 ~( j- H, E. ^IIC的物理层
" H+ T1 f, v, cIIC一共有只有两个总线: 一条是双向的数据线SDA,一条是串行时钟线SCL) z: b5 [6 q, F% [* u/ n; `
2 W2 L7 x) \6 p0 n. [' x; v
所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。I2C总线上的每一个设备都对应一个唯一的地址。& K- j; \5 d2 ?" R2 X/ s0 [; I# Z
  f9 C" e' i; q# S( `0 E' A
D}AKU7YZ{}~Y6MC6W%Z3IAR.png   v. a8 J: ?# x+ }

+ ]+ X) m4 y. J+ v! K1 d( L" c关于IIC的讲解,已经单独整理了一篇文章:
7 x, a0 G- ~7 ~: Y6 V6 }% S( o* ~. H3 J0 J! [
《IIC原理超详细讲解—值得一看》。
4 ~* E3 x* O% V+ d7 b+ u/ ]# ?如果对IIC还不是太了解的朋友请移步到这篇文章中; n* C# ~. O! F6 c, D1 |
- k7 S- }; [  O0 d* ]5 J
IIC起始信号和终止信号:; r- ]. v% L7 B/ S4 E. S* o
) H/ P; `* G+ x$ F  k
起始信号:SCL保持高电平,SDA由高电平变为低电平后,延时(>4.7us),SCL变为低电平。. a- X3 {8 T  e1 C3 ]9 z
停止信号:SCL保持高电平。SDA由低电平变为高电平。
; A: K+ [4 J9 l. _" n
2 M/ M  a  p6 L4 N% Y S2R5[}7Z`)G{P}2%QC5GHSD.png ( x* @, r8 m3 s

+ s* l8 a4 ?( p, t9 g* T8 w5 D数据有效性1 E( Q8 \, p% c2 ~; p: Y" h9 @: |8 j
IIC信号在数据传输过程中,当SCL=1高电平时,数据线SDA必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。- v! S5 y( c( ?; M; q( e) |3 t

/ o+ S5 n$ z2 z% a+ ~SCL=1时 数据线SDA的任何电平变换会看做是总线的起始信号或者停止信号。7 U+ o. r# Y' T3 I2 u

. o% b, h) j) a! J( k也就是在IIC传输数据的过程中,SCL时钟线会频繁的转换电平,以保证数据的传输) M+ j( L0 k# |' L
" d6 I; l: G  f2 {6 _# Q5 g# X! p
M(Q@A~}}1`7L@B9LIT84{]L.png 9 c! }8 H. B0 }; H$ S6 Y" G
8 O0 K- p4 Z7 z" k0 C! s; ?( M+ T
应答信号
4 c8 i! z% X7 V0 k每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,
- ^* t* k8 g) N! f' @8 |% q2 \+ t  F% n# N5 q0 G
应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答
( Q% E: h. Y9 ~7 Z' C5 y6 C6 e8 q  k9 P9 d. b
应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;. S8 R6 p- N) q, O2 j! d
应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
2 [2 N) _: x6 Q. r- g8 ~1 m, Y
T4O0TA%5T7[2WNU]U$(FF7C.png 5 ^' o+ _$ l' w+ v8 R) S
; e7 v# o7 w% h8 ]
每发送一个字节(8个bit)在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须回一个ACK应答信号给发送器,这样才能进行数据传输。
7 a( z1 }- o  ~. O  Z
3 [1 a2 w. V% |+ O应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答,9 U" T( m% s; ]( V7 L( R
' U6 P4 d& B( @
Z{ZNXO@GLVSV~GQGAYF4ZJC.png
0 ?  z1 D- [. }1 w$ g! h/ M; E  r) i% z) B/ ?5 E, B6 e" ?
这里我们仅介绍基于AT24C02的IIC通信
/ V+ A* l1 P, p& w
1 M/ m2 i' `) }6 f以AT24C02为例子
# {" U( O5 H$ r% f# T( ~24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。在24C02里面有一个8字节的页写缓冲器。
6 G8 z* V/ P  d" S5 u7 i
; _% r- Q6 O* q4 q# g [~@R9HH6_)]C5TLUF7~XB56.png
( A; w1 L  Y( K( O: _' U5 b% q
: a% r5 o7 O) l  j, R7 `% n- SA0,A1,A2:硬件地址引脚
2 _4 f/ i6 ?# N' ?6 d9 W- ]+ UWP:写保护引脚,接高电平只读,接地允许读和写& T+ D6 e  M* K' V( C
SCL和SDA:IIC总线
, F) q7 r- F0 q% R- C; t( j; K: O# P/ F0 |
3 x' J1 @, A, B' v- F0 m( {. [. K
以看出对于不同大小的24Cxx,具有不同的从器件地址。由于24C02为2k容量,也就是说只需要参考图中第一行的内容:& v, R; x/ M3 [8 a9 E
! G/ f* |/ A! k1 V# Z
N4YO0DDI0)A}[TPV1WJS%LA.png
# p6 y$ _# r+ t# [5 V/ s3 L5 N9 Q/ |/ z1 m1 Y+ S
芯片的寻址:
, P) v+ p5 P( j/ y: S0 C' RAT24C设备地址为如下,前四位固定为1010,A2~A0为由管脚电平。AT24CXX EEPROM Board模块中默认为接地。所以A2~A0默认为000,最后一位表示读写操作。所以AT24Cxx的读地址为0xA1,写地址为0xA0。
! p, A6 Y. r2 J$ }
* o% C# u+ e: U  k# O也就是说如果是6 J/ d; k! A. y% ^3 A" J. X% z+ B
写24C02的时候,从器件地址为10100000(0xA0);
  S2 A" J: U6 F$ a. {读24C02的时候,从器件地址为10100001(0xA1)。7 ^# n. @% |; w, ]( n/ q

3 h7 j2 V& c0 c; D+ F- ~* E片内地址寻址:* n1 v8 N1 c$ u) g& G* M& J

( X# X2 K  K' M$ K芯片寻址可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单位。
! P7 F+ m% C) F) _, P4 p
6 V& j6 `  M$ ^& [; w5 `对应的修改 A2A1A0 三位数据即可7 t+ p/ |$ z0 N

3 W+ ~( ^9 @# d0 U' G  J- j/ Y %L{8Q9IZVYLK57)VFQ(GD5L.png & X2 L: c; {. T% v' K) W% ~# V
6 e  a' D' q/ w1 w3 E
向AT24C02中写数据
; X/ l+ i$ H, P7 @
. o+ y7 A( t% |5 O: ^5 y7 | GVF(F2T]4}{O4}~FQ05B3JB.png
4 _) A5 y5 A+ c4 w9 c4 ~0 L( \7 L& x8 @# A# s9 P9 s$ ]
操作时序:
: L8 I! U! b- D# k# ?1.MCU先发送一个开始信号(START)启动总线; G) s5 _  {3 d9 X
2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0): e. K6 S+ F- }( _: q5 R1 W2 D
3.等待应答信号(ACK)
* `  b" q2 }8 ]6 x) |( W' u4.发送数据的存储地址。24C02一共有256个字节的存储空间,地址从0x00~0xFF,想把数据存储>在哪个位置,此刻写的就是哪个地址。
& E& }2 t# W1 j" k6 e6 D/ s5.发送要存储的数据第一字节、第二字节、…注意在写数据的过程中,E2PROM每个字节都会>回应一个“应答位0”,老告诉我们写E2PROM数据成功,如果没有回应答位,说明写入不成功。
! ?. X  h. d' o8 I% K6.发送结束信号(STOP)停止总线$ @+ ]3 m: Y8 J; |% R- V" y% G" Q% {
注意:$ @: @6 N$ g# C+ S8 `9 T
在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址就会溢出又变成0x00。
: m7 P/ ~9 I3 p2 c6 d3 ^
0 R; E! b3 O+ A/ j- D- q  g$ ^写数据的时候需要注意,E2PROM是先写到缓冲区,然后再“搬运到”到掉电非易失区。所以这个过程需要一定的时间,AT24C02这个过程是不超过5ms!
) w! o/ P3 u/ ?: v$ w所以,当我们在写多个字节时,写入一个字节之后,再写入下一个字节之前,必须延时5ms才可以
8 m  w, y) f# U; L, c
/ P4 o' d/ y& @0 U( B. E8 ]1 c从AT24C02中读数据
6 E% i  Y& C$ N0 ]$ T% g  t# R
1 C5 L3 U3 O, j5 @3 q7 N读当前地址的数据
. e: T" a3 A" L4 ]  c
$ J0 E  y# q; D: m7 \/ p [%JNO9U_{}GE@N5QAAXNBIV.png   [& ]' y# |' j% M7 z4 }

7 c5 d# `0 l4 u- d9 ]1 L2、读随机地址的数据

% Z% T: g! v  Z6 v% \! X/ A; B6 o
% P. T$ C" E! ]" ? ; r$ I4 j; d  k2 x# b
1.MCU先发送一个开始信号(START)启动总线0 f: W8 j+ j3 b# h2 |
2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)% D) H1 m0 ]$ L( K. r) e3 h
注意:这里写操作是为了要把所要读的数据的存储地址先写进去,告诉E2PROM要读取哪个地址的数据。% t. _* q" k  Q0 P
3.发送要读取内存的地址(WORD ADDRESS),通知E2PROM读取要哪个地址的信息。* D( b( v% ~, V8 S3 t9 Q  x
4.重新发送开始信号(START)
3 k" N" Q# t, p: _5.发送设备读操作地址(DEVICE ADDRESS)对E2PROM进行读操作 (0xA1)+ p# p+ ^4 R' z- E
6.E2PROM会自动向主机发送数据,主机读取从器件发回的数据,在读一个字节后,MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据
& P9 L6 o, ?% D7.如果不想读了,告诉E2PROM不想要数据了,就发送一个“非应答位NAK(1)”。发送结束信号(STOP)停止总线
0 H+ h. g& b/ c1 \+ q& H  I% h/ Z# @/ ^* `4 T0 ^6 `+ x
3、连续读数据0 [4 H$ N1 Q2 Z4 s# r
! s+ W# N5 V8 l" X! J0 s
YA%U6OE)]I$FOPP{TSTY6]W.png . I/ S2 p) ?3 A6 n8 v- Y$ D
E2PROM支持连续写操作,操作和单个字节类似,先发送设备写操作地址(DEVICE ADDRESS),然后发送内存起始地址(WORD ADDRESS),MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据。E2PROM的地址指针会自动递增,数据会依次保存在内存中。不应答发送结束信号后终止传输。  K/ \" ]- R6 M2 H+ b/ z) l( V
9 q  J  ^$ |+ N; q+ F' N% z  ?* U6 P9 l
基于CubeMx的讲解

. h1 C; k5 P9 K3 P0 s9 n. ]8 ^1设置RCC时钟

! Z# A0 q% G" r% E* U* c( b* }* d5 b+ l5 }
设置高速外部时钟HSE 选择外部时钟源1 P- k( P7 ~6 ^% U& w& p/ u4 s0 ]
4 |$ ^. C- W- g
H~}{4GU_`0~NYL1QTZ68CVU.png
) z& X- j" d: A; U9 Z3 Q" M/ M( ?7 q7 u% ]
2 IIC设置( |5 ?5 n  L  |8 y: W; f- {) x
) [4 I+ G( M& X3 ]
N}]X7_8ABWVCGHQ7[%]6NT8.png ( F0 [- T1 F& x5 |; r- S9 f
点击I2C1 设置为I2C 因为我们的硬件IIC 芯片一般都是主设备,也就是一般情况设置主模式即可
0 _5 H, h) I* A" F/ c" Z3 [2 i# f6 J9 W1 L5 K5 b( U- P( A2 C
Master  features  主模式特性1 W& f/ Y! }  }4 B' ?0 |

9 a* l$ n+ K* D! m) _" PI2C Speed Mode: IIC模式设置 快速模式和标准模式。实际上也就是速率的选择。, I$ r1 n8 D  a
9 [- e  i4 ]+ G5 S* b, ]* a  j, v
I2C Clock Speed:I2C传输速率,默认为100KHz9 B* I$ u( C$ D) G) c6 s
8 {$ H3 I7 Q3 M; l) u7 ?
Slave  features  从模式特性
. O4 x& \5 w, U
* a- B5 U! m- ?6 YClock No Stretch Mode: 时钟没有扩展模式- K% h2 [1 {3 P# u+ k8 ~0 x$ v
. b: Z5 a7 a5 {4 s0 `$ Z3 I5 E
IIC时钟拉伸(Clock stretching)9 n: h, n: j  h) k/ ]8 F
clock stretching通过将SCL线拉低来暂停一个传输.直到释放SCL线为高电平,传输才继续进行.clock stretching是可选的,实际上大多数从设备不包括SCL驱动,所以它们不能stretch时钟.
3 {; U0 t. s# \$ W; b. k. g' E  y. s* hPrimary Address Length selection: 从设备地址长度 设置从设备的地址是7bit还是10bit 大部分为7bit' x& Z' S$ U5 u4 X0 o& }# A
-Dual Address Acknowledged: 双地址确认
5 n# d: w( J- l0 U* a$ }
: y3 e, Y$ t5 f  B' X. W' @Primary slave address:  从设备初始地址
* h& M7 T7 D3 m3 s, B5 A% u$ d# ~: z/ ~2 @4 ~$ B' v
这里我们保持默认即可
1 g5 L& p, T, J$ l7 D$ w& n# G/ Y" R1 h! _
3 串口设置
" ?: M- u/ p( A1 B& p+ \/ q8 t$ F" ~7 L% w9 S
%)KSYV55BDGN]ECSR}C7O8P.png
: d8 ~/ g2 F9 A6 B8 G0 p) H& g. h
3 C% H" @# r  B5 U因为我们需要将AT24C02中存储的数据发送到上位机上,所以需要设置下串口
% |0 M2 p' J9 u" h
4 X4 g- n/ S8 X/ s9 }这里设置为异步通信,其他的默认即可
- Z0 s8 `( P) j9 @3 N$ P, c6 u9 k9 g  ]+ g  H4 E9 B# h& G
5 时钟源设置
. z2 l2 |, ?3 s5 a9 z/ O1 a! B/ z- e  ?" p, y2 e' y$ m/ d- D+ W- K9 c
{UI$O17GRVD)@4$XOFUYj.png 4 Q. _! [. x0 [: H: X4 ?! D

! y# B$ p- y! ]( e我的是 外部晶振为8MHz
7 C8 U2 E, r- y( l1选择外部时钟HSE 8MHz
/ V6 i3 {; e" [! X6 z2PLL锁相环倍频9倍6 z/ ~  u( k4 ~- V8 O
3系统时钟来源选择为PLL6 W, a5 C, C' [: P8 V; R3 K
4设置APB1分频器为 /2
+ b1 h3 I4 I8 Z5 使能CSS监视时钟4 M. P" i. C  P9 K9 ~9 Q3 f
* z3 p; S9 D1 F# M3 _1 C" i
6 项目文件设置& T6 V4 |& n/ }2 n
# b' S, v: X" o9 d! S3 F
T77Z1G%_O$[~PTC6J)ZTJVC.png & v, Z7 ]$ n4 n% E8 X, E' d

0 K3 u6 @: s; r  }# y1 设置项目名称: E. U9 R; L; D) f9 {* `
2 设置存储路径
" o. i, [  n3 C3 选择所用IDE
  j, Z; m$ s, F9 ?; q
" V% x/ H  Y  }  u! m; ?1 |4 N $HL2@`Y[HUT60}IW074]CTO.png ; W. b: H- e# f
5 O# s& z- i+ g2 k- o- g6 z: F
7创建工程文件

! o" p5 q3 @. f. F( L) y; p" \1 l. l9 b
然后点击GENERATE CODE 创建工程3 t/ Q, _# t+ F8 L
$ y( s2 o9 g" u; y- P7 O
配置下载工具
$ L" t  x1 p3 }" e9 y) A新建的工程所有配置都是默认的 我们需要自行选择下载模式,勾选上下载后复位运行
- D- g7 ~5 t1 m  E# Y; d  I6 k* C3 s# J2 L5 [  L) s8 Q7 q
%6{23MK8K4O39(CF8REOY2D.png ! j7 n  f9 J# b# {) j
! |; {4 h: b# M" ~- ~+ i
IIC HAL库代码部分
: S9 G$ y, h9 V. ~5 E* P, I8 R3 B在i2c.c文件中可以看到IIC初始化函数。在stm32f1xx_hal_i2c.h头文件中可以看到I2C的操作函数。分别对应轮询,中断和DMA三种控制方式
5 e! ^5 b! a7 C9 c/ X1 p  }0 T4 I4 r$ r3 M, b2 Z1 k
X(0}523E8C`}{VUFC[ETK%V.png / k6 S+ _+ G$ J# J) X1 q, N

8 d" E5 Q& [1 r. a9 Q4 e! P上面的函数看起来多,但是只是发送和接收的方式改变了,函数的参数和本质功能并没有改变5 @. w5 f4 W4 e' D" {" B
比方说IIC发送函数 还是发送函数,只不过有普通发送,DMA传输,中断 的几种发送模式1 H+ @1 N  N# R: {" x

  O6 v3 T. P4 N( @, J+ X' \% K5 M这里我们仅介绍下普通发送,其他的只是改下函数名即可
' b# @3 [' ~" o- B0 p8 H: ]9 a! w6 N2 L/ d# _& ^$ g
IIC写函数
6 H! o: O; n: R7 d# F
1 w5 n8 K2 x, B/ m; w3 N' |
  1. HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
复制代码
4 e6 [1 f0 B$ U9 V( p' e, m
功能:IIC写数据
  x' J( t8 B* F. ~( [4 i参数:7 ^7 h5 @* a0 k5 C
*hi2c 设置使用的是那个IIC 例:&hi2c24 u% {* H, ?" y! K7 \* v/ z
DevAddress 写入的地址 设置写入数据的地址 例 0xA0
; g: ]7 ]# }% g4 ~*pData 需要写入的数据
0 g. B, ]- J8 ?+ I# J" a; }4 M) @Size 要发送的字节数
7 X2 Z; u4 S" I- i2 Q, K* _Timeout 最大传输时间,超过传输时间将自动退出传输函数  A* O! c5 F% s& ~* o
, K; o! j1 G7 W' p8 ^- l! o! V
IIC读函数
2 t# ?: m# f! l7 q  \1 _* Y, }( E5 x: g3 V) e& \
  1. HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
复制代码

4 a9 J5 X( q+ \  c3 i功能:IIC读一个字节. d. e. j$ x9 i
参数:( U+ S" t  O" g% p
. e* x$ D8 L8 T5 L" S) d+ Q. V
*hi2c: 设置使用的是那个IIC 例:&hi2c2' b9 `  c( o0 W' ]: \

' X% q3 L7 s0 ~2 r# @DevAddress: 写入的地址 设置写入数据的地址 例 0xA0
0 ~" f1 V5 D4 {" P! s
- U& Y7 S3 h# x6 y% a/ l*pDat:a 存储读取到的数据- k# R$ x# k. n2 u9 W

+ C/ e/ n6 X. NSize: 发送的字节数
% J# b! p1 Z5 @- \' L
4 V( _  B. b; X$ a4 @4 e8 STimeout: 最大读取时间,超过时间将自动退出读取函数
! }/ m0 ^0 s$ }. `. [
" ^5 a. `% Q* `* B4 ]/ G8 D举例:
: O+ |2 H  h# G( _% n
# p: [1 A7 o" w) M% L& n
  1. HAL_I2C_Master_Transmit(&hi2c1,0xA1,(uint8_t*)TxData,2,1000) ;;
复制代码

' n5 ^& ~; X1 Y0 l& s1 w发送两个字节数据7 U. P# y$ N* E+ e7 a7 n* d

( y' U) ^. A% L6 jIIC写数据函数
- d! `; i3 t% b$ a
3 l1 h! |( |9 t! m) G6 u( N
  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& ~7 C, w1 F/ U, a: A
  2. /* 第1个参数为I2C操作句柄% o) o5 K( Z  ]8 C9 w$ x3 M& `
  3. 第2个参数为从机设备地址. X  p* }& t' [! A! c& m9 g7 g1 a3 A
  4. 第3个参数为从机寄存器地址- u, [5 i" U9 _. H* G
  5. 第4个参数为从机寄存器地址长度
    # E' K& D$ I) f* i: ]+ q1 x
  6. 第5个参数为发送的数据的起始地址7 @- G  S7 G& g/ U) P7 B# E
  7. 第6个参数为传输数据的大小+ h9 M* v3 O6 k# ^, _$ S  p
  8. 第7个参数为操作超时时间   */
复制代码
+ r" `7 Y1 F! _) ]
功能: IIC写多个数据 该函数适用于IIC外设里面还有子地址寄存器的设备,比方说E2PROM,除了设备地址,每个存储字节都有其对应的地址
; L. A5 S! J$ j' ~1 C
" W3 U! W% v& j: U$ ]( I$ ^参数:$ t3 y$ [9 }/ {: P1 O9 C3 u% p

. }0 h3 c% M( l/ z) ^*hi2c: I2C设备号指针,设置使用的是那个IIC 例:&hi2c27 H9 M( R: f0 v# N

& I" I4 Q' I! _$ E( A/ L- qDevAddress: 从设备地址 从设备的IIC地址 例E2PROM的设备地址 0xA0
$ A" {+ Y; b- E, r* c+ w/ ?/ l! O% V6 R, P  E/ m
MemAddress: 从机寄存器地址 ,每写入一个字节数据,地址就会自动+14 |* Q- _0 x8 i9 @! o8 f

) i/ ~# v' _. t3 q" sMemAddSize: 从机寄存器地址字节长度 8位或16位
2 R: v+ ~# o& U% G* Z% v0 {+ V; u5 f4 L! h0 A
写入数据的字节类型 8位还是16位# C+ D5 `# ~% c! L; E
I2C_MEMADD_SIZE_8BIT! ]3 |( H0 F) L0 z3 Z. @
I2C_MEMADD_SIZE_16BIT
8 n: [3 E) w5 P5 Z4 J2 d在stm32f1xx_hal_i2c.h中有定义6 x  c, a, Z% p( m: ~- }+ W3 t0 ]

* S  O# i  ^) q* |6 E/ I* H# Z {R2Q(IR$`LHMIG`7XKEUS[1.png
: v( V9 N; ~( _! h+ B+ F
4 {& |7 P3 |5 y/ Q3 s3 I8 d*pData: 需要写入的的数据的起始地址! i% W4 s- L' H& U- v' h
. r: K' `; P, R
Size: 传输数据的大小 多少个字节
& V+ L9 D' u$ ~9 w; M
% S# L/ M+ P$ {9 k* f# O9 U5 xTimeout: 最大读取时间,超过时间将自动退出函数; W0 Z3 a* y3 t* [5 V: D6 [1 H
1 Q, \" Q" X0 W+ F
使用HAL_I2C_Mem_Write等于先使用HAL_I2C_Master_Transmit传输第一个寄存器地址,再用HAL_I2C_Master_Transmit传输写入第一个寄存器的数据。可以传输多个数据, i/ Q& S& E+ Z; o+ P9 m3 M
. P- C; K3 v. W: b
  1. void Single_WriteI2C(uint8_t REG_Address,uint8_t REG_data)
    , S4 B7 F' s5 {! ]( f8 y1 J
  2. {0 h4 Y3 x$ a* K8 g3 Y9 p2 L
  3. uint8_t TxData[2] = {REG_Address,REG_data};
    ( @# n7 ^2 i& [+ Y
  4. while(HAL_I2C_Master_Transmit(&hi2c1,I2C1_WRITE_ADDRESS,(uint8_t*)TxData,2,1000) != HAL_OK), X8 h' n2 K, k3 l; r, Z
  5. {
    5 }* ]9 x4 v$ @7 R. o
  6. if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)! n9 z! |& w* z; d
  7. {9 L$ y) \  v* e- |$ i/ ^
  8. Error_Handler();
      q! ~# e9 D1 r% p2 ?
  9. }
    4 @2 L  e. Q: \! R" l+ |
  10. }
    1 ~1 B# P2 Z& l6 i' o4 u( l( d
  11. }
复制代码

* \% l6 E4 A5 ~  r- l- g# _8 o+ @在传输过程,寄存器地址和源数据地址是会自加的。
, j7 F0 C+ B4 s1 V1 _4 j. R* I5 B
2 s! S8 o8 w+ \至于读函数也是如此,因此用HAL_I2C_Mem_Write和HAL_I2C_Mem_Read,来写读指定设备的指定寄存器数据是十分方便的,让设计过程省了好多步骤。
. n" N8 U6 W7 H. h' Q% t6 K/ u
: t% F6 X, a  @4 B+ D举例:4 t8 j; C5 S: U4 p! y
: m# i' K+ @  u
8位:
  1. HAL_I2C_Mem_Write(&hi2c2, ADDR, i, I2C_MEMADD_SIZE_8BIT,&(I2C_Buffer_Write<i>),8, 1000);</i>
    ! Y4 S! @  K5 b  Y% j

  2. + I/ t: c, N: \( T: s: i
  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>
复制代码
* O, L; K; D: |& b$ \& K$ h; b; \
16位:2 W) Q5 ?! J7 r: B8 q; X/ ^
  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);
    . a. a. [3 s) y, m
  2. 1 v" _8 e  t$ p4 l7 e
  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>
复制代码

2 N' d' C8 J( u. M# C如果只往某个外设中写数据,则用Master_Transmit。 如果是外设里面还有子地址,例如我们的E2PROM,有设备地址,还有每个数据的寄存器存储地址。则用Mem_Write。
7 A. C& u  d5 y, ]) {% bMem_Write是2个地址,Master_Transmit只有从机地址" I7 r' i8 y( I# C4 H' a+ X/ E2 ]
" `* l: W$ f! x) ~" r. O3 }
硬件IIC读取AT24C02" R7 `! {. Z( a- w% g; R
在mian.c文件前面声明,AT24C02 写地址和读地址 ,定义写数据数组,和读数据数组
: m' Z9 N+ W  b2 v2 a
0 b  D4 N$ K: Z) r1 F
  1. /* USER CODE BEGIN PV */1 ^  A1 i2 {1 @3 X9 m
  2. #include <string.h>
    ( F/ b6 }2 ^4 F' L

  3. ; z6 j) D0 V* s5 Z
  4. #define ADDR_24LCxx_Write 0xA0
    * Z1 y' ?% y6 g+ V
  5. #define ADDR_24LCxx_Read 0xA1
    6 Y( a8 G: f  F4 C+ e3 e$ w1 p
  6. #define BufferSize 256
    0 q7 M  D9 N. L, B! c$ u+ G( I; b3 Z
  7. uint8_t WriteBuffer[BufferSize],ReadBuffer[BufferSize];; M/ T% k* [! Q) b& ^8 {5 h
  8. uint16_t i;
      p: y* B2 ]# n; K# u+ B- p
  9. /* USER CODE END PV */
    . ^/ u2 {  s2 M( Z0 r
复制代码

( d( B; Y2 a6 }% i0 e' I; T# b重新定义printf函数: X6 n& ^6 r* ^) y% k

6 |% b9 E6 F. m/ {$ N在 stm32f4xx_hal.c中包含#include <stdio.h>
, h% e# S1 C% ]0 u. s' E3 k0 }% Y$ u* ~; B( y& R* Y
  1. #include "stm32f4xx_hal.h"' j$ d$ _/ y+ S* \: V
  2. #include <stdio.h>
    ; t! V' \/ h5 I
  3. extern UART_HandleTypeDef huart1;   //声明串口
复制代码

' Y1 I" O0 O  T( J! Y& M在 stm32f4xx_hal.c 中重写fget和fput函数
" o' Z. _' N  K2 D. i, D0 G8 n$ J% k1 q
  1. /**
    1 n/ U% t: ?4 ^, T8 J: i, H
  2. 6 R! `, x; z' k" O1 m( |
  3. * 函数功能: 重定向c库函数printf到DEBUG_USARTx% g- p! [8 D) |* S% {9 ], y3 d
  4. * 输入参数: 无
    + ^; Q2 D1 a% b; L
  5. * 返 回 值: 无
    ( I, o* Z  E( z6 m( n' b
  6. * 说    明:无
    - C- I! R; m0 O; ~1 J* ^
  7.   */
    ( y, p9 t: J) w) n
  8.   int fputc(int ch, FILE *f)# ?! J( ?* v" F7 ]1 {
  9.   {/ ~& y9 H) P7 U0 P$ o! T) O
  10.   HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);( m8 _, b5 N' y7 j; H9 J! `7 {
  11.   return ch;& s% s; e9 Y5 z7 P5 i  X
  12.   }. R( d& D5 L- @

  13. : D3 I5 [* z( y1 h
  14. /**2 v* O5 P  j4 L! O) W! K
  15. * k& a% t# d9 m( D4 o% g
  16. * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx% O/ O$ L! T& F9 W8 d3 ~% J* v( S0 O
  17. * 输入参数: 无% J# J4 ^9 K; P) d6 ~
  18. * 返 回 值: 无. y) D/ C9 D) C- f
  19. * 说    明:无
    0 K! c' n* a/ y4 N9 Q" o
  20.   */0 X' m' ~% {0 h8 h
  21.   int fgetc(FILE *f)
    & c0 ?7 X/ X8 t- p8 _# \, ~
  22.   {
    ) E" S8 T8 Q8 S0 V5 U
  23.   uint8_t ch = 0;  L( g8 W& k8 d4 g$ T) c
  24.   HAL_UART_Receive(&huart1, &ch, 1, 0xffff);3 ^3 I, l/ u0 {( _; p$ z9 j
  25.   return ch;$ p  J( M( V# N) x. Q. M
  26.   }
    * J6 M! M9 ], W
复制代码

& M# i5 \* ?/ g在main.c中添加
$ f! B3 R/ K* ], v& u& j
$ Y' S% J( [( U# R/ W. n3 \- ~
  1. /* USER CODE BEGIN 2 */+ p$ l3 q5 x+ j2 L) W7 `
  2. for(i=0; i<256; i++)
    - ~4 z: p; y1 }3 G8 E* ?
  3. WriteBuffer<span style="font-style: italic;"><span style="font-style: normal;">=i;    /* WriteBuffer init */, C0 c. K. o) L! y4 T  U. ~
  4. ! I& y- Y2 O" ~& X6 E
  5.         printf("\r\n***************I2C Example Z小旋测试*******************************\r\n");
    7 Y+ M/ E" x, i2 D. L! s4 d, c/ `
  6.                 for (int j=0; j<32; j++): F( q9 w, \/ j5 Q, P
  7.     {, b- {: q& F3 k7 \; _) x5 v; i, J
  8.             if(HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, 8*j, I2C_MEMADD_SIZE_8BIT,WriteBuffer+8*j,8, 1000) == HAL_OK)
      L- z* t' T# @
  9.             {& \7 C) |  x$ i
  10.                             printf("\r\n EEPROM 24C02 Write Test OK \r\n");
    ! r( ?) b; L3 ~  T6 _/ S9 Q4 i
  11.                     HAL_Delay(20);
    ) Y" U( A6 O3 {% J. z2 Q. M0 q
  12.             }
    ! p, q0 \+ [4 `: [, Y) B
  13.             else8 B  E; D# `: l' f0 @+ u8 v# |
  14.             {
    8 Q5 X9 f* m9 y$ [
  15.                      HAL_Delay(20);& I) @5 C: A( O0 `
  16.                             printf("\r\n EEPROM 24C02 Write Test False \r\n");# P1 X1 Y+ F9 F; w
  17.             }
      Q+ |0 O) f, _# k/ {2 E3 [
  18.         }
    1 H, k6 B3 @( E' o5 G4 d- e7 A
  19.         /*
    2 z1 Q# L/ H/ w. I! b! a
  20.         // wrinte date to EEPROM   如果要一次写一个字节,写256次,用这里的代码/ F1 l" n4 C2 i6 K  @2 w
  21.         for(i=0;i<BufferSize;i++); u  ~) N" |) e0 x0 b
  22.         {9 y3 K: F  k/ H2 j
  23.             HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, i, I2C_MEMADD_SIZE_8BIT,&WriteBuffer</span><span style="font-style: normal;">,1,0xff);//使用I2C块读,出错。因此采用此种方式,逐个单字节写入' E7 M; {7 `% V2 g, H
  24.           HAL_Delay(5);//此处延时必加,与AT24C02写时序有关. A, j& C6 a! q0 H9 j
  25.         }/ {5 v3 R1 l& T( f9 C" f
  26.         printf("\r\n EEPROM 24C02 Write Test OK \r\n");' \' \# U: j1 {3 G6 e) ]: M4 X
  27.         */
    + ?  H* n! [& Y5 h

  28. , K2 I7 Y% S4 Q% v
  29.         HAL_I2C_Mem_Read(&hi2c1, ADDR_24LCxx_Read, 0, I2C_MEMADD_SIZE_8BIT,ReadBuffer,BufferSize, 0xff);
    5 C2 \; h  ^3 Q  K) s

  30. 7 c  T8 N& Y* X. y/ k) t
  31.         for(i=0; i<256; i++)# K- d/ \, j$ _. v* q% A
  32.                 printf("0x%02X  ",ReadBuffer</span><span style="font-style: normal;">);
    + I' R, S0 G4 L% A) x
  33.         ; h6 n# d- {# j  ?5 I: r: G
  34. /* USER CODE END 2 */- i& w3 l( {1 k1 L# M
  35. </span></span>
复制代码

$ H7 a- B2 M: w" l& p注意事项:1 H, d) C- ]2 O" |0 Z1 ~% X
AT24C02的IIC每次写之后要延时一段时间才能继续写 每次写之后要delay 5ms左右 不管硬件IIC采用何种形式(DMA,IT),都要确保两次写入的间隔大于5ms;, r, V5 Q! G1 k% J
读写函数最后一个超时调整为1000以上 因为我们一次写8个字节,延时要久一点. y& n. }: s4 }# b
AT24C02页写入只支持8个byte,所以需要分32次写入。这不是HAL库的bug,而是AT24C02的限制,其他的EEPROM可以支持更多byte的写入。
. Q- ~/ [. e+ u4 B

% _; F& A: w% `6 y4 t  U- q当然,你也可以每次写一个字节,分成256次写入,也是可以的 那就用注释了的代码即可
  1.         /*
    4 T+ M: J4 f. Z( o( ~- y
  2.         // wrinte date to EEPROM   如果要一次写一个字节,写256次,用这里的代码6 ?! D9 a6 R: k0 K. K- e# L
  3.         for(i=0;i<BufferSize;i++)
    8 {2 I9 k- g8 S( i$ w; t# T
  4.         {% C0 o* V+ ]6 k% H5 X* N. C. y
  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块读,出错。因此采用此种方式,逐个单字节写入8 o) ^. `! O' Y  [4 i& N2 ^+ ?
  6.           HAL_Delay(5);//此处延时必加,与AT24C02写时序有关
    + P" B) X; `' ~! ~0 _
  7.         }
    ) D# j; ~% B1 \5 \" J0 q; O1 S2 K" ~
  8.         printf("\r\n EEPROM 24C02 Write Test OK \r\n");
    , {3 x0 \" o! I) i' i. s% |
  9.         */</span></span>
复制代码

, H& Y1 _# ^# D注意读取AT24C02数据的时候延时也要久一点,否则会造成读的数据不完整0 ?- Q, u! c, _1 D4 z, C  g

9 Y* C1 d1 A+ Y* ]% J! \9 x! V

, N% A. x/ }0 d5 z- t' s* s8 B' G  b' x  ^: z: W+ e8 {( u
经测试,例程正常, I% P( m9 M: |; ?# e
& g  {+ D7 {, v

; f' A: l3 o6 B- c }HXE1$R$NS[]3IB84QTD03L.png
9 k& [1 [8 b/ E! L
收藏 评论0 发布时间:2022-3-23 16:00

举报

0个回答

所属标签

相似分享

官网相关资源

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