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

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

[复制链接]
STMCU小助手 发布时间:2022-3-23 16:00
前言:
- s& \( {" ?$ v4 ?! U- m本系列教程将HAL库与STM32CubeMX结合在一起讲解,使您可以更快速的学会各个模块的使用8 S7 Q% [- @' l; I
( j- ?: V( i( g& ^% I% L. t0 V5 R
在之前的标准库中,STM32的硬件IIC非常复杂,更重要的是它并不稳定,所以都不推荐使用。
* h9 O& i6 I/ O: J但是在我们的HAL库中,对硬件IIC做了全新的优化,使得之前软件IIC几百行代码,在HAL库中,只需要寥寥几行就可以完成 那么这篇文章将带你去感受下它的优异之处
1 }$ r. d* u2 y% ~; ~7 l
- [( H3 t) S- n7 u* I$ K0 @% Y这可能是目前关于STM32CubeMX的硬件iic 讲的最全面和详细的一篇文章之一了% [& a, Q' Z' j; L
4 Z) a  j: l. {% H: m/ Z% z  o! E
所用工具:6 }; `+ N( M8 k: b' a. M

, `( x! }- V, O8 l1、芯片: STM32F103ZET62 n; d# Y* a+ b) F8 V0 _
- r- Q9 e) v" n2 o, [- P
2、STM32CubeMx软件  u. j' T0 T2 L
0 L; K3 B. y9 S# g
3、IDE: MDK-Keil软件/ b/ a1 R5 j4 d5 H/ s2 A
9 y! N8 }  `* c: Z6 U/ Q
4、STM32F1xx/STM32F4xxHAL库. K* A- x4 T4 f+ a7 C4 A  @

2 S3 \$ ?& D  k. k3 L5、IIC: 使用硬件IIC1
. o) l! O' Y- ?4 v6 k9 j9 j6 {) |) N8 X
知识概括:3 X& `  E; x) v7 p- ~) k1 q

/ Z, P) r+ u" x8 L- T  P+ }# b: r通过本篇博客您将学到:
. D9 x+ n; S7 ]# e+ a' r; ^
. M8 {6 j3 K, h. D/ `4 yIIC的基本原理1 K: M8 ~7 R5 g  i

! Z, S/ N( y  t6 rSTM32CubeMX创建IIC例程
7 b) {7 X# \' d
5 i$ B: h8 v) {1 WHAL库IIC函数库* {% y# O2 @' A7 Q

. ~  A4 r# T; N' C7 Y4 K0 wAT24C02 芯片原理* D- u& J& d) r) R. Y: z0 ~

- }+ f- S% a3 }% a* Z- q8 ?, W( F/ q; OIIC 简介
9 R1 q6 l" K: r1 {' x9 _IIC(Inter-Integrated Circuit)总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。
! b* q# j: d- \& e. Q* Y  u; Y+ `" P$ l
在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。3 k9 v' f: i7 o
. K/ `! ^9 }+ @; w5 K; V) _( I
PS: 这里要注意IIC是为了与低速设备通信而发明的,所以IIC的传输速率比不上SPI: F1 d" _$ _5 K8 @. ?& B! ~
! }: I* L( c  y; [2 m% u" q: z
IIC的物理层
( q- x" V2 Q3 E0 S( n9 nIIC一共有只有两个总线: 一条是双向的数据线SDA,一条是串行时钟线SCL/ G" P9 {; i# N! Z" C( b6 ^0 s

. E6 I9 i& `2 \/ N9 i, w所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。I2C总线上的每一个设备都对应一个唯一的地址。
; |$ I2 L0 T0 D2 W$ H. D) g. M8 H9 v5 B8 {7 t* W
D}AKU7YZ{}~Y6MC6W%Z3IAR.png : i' ?0 T0 ^0 w6 o3 ^! d

2 q" z" \7 Y! G; C2 e关于IIC的讲解,已经单独整理了一篇文章:7 }) }  ~* W2 a
' o% E4 @+ D4 I# G% N, \4 L
《IIC原理超详细讲解—值得一看》。
/ [& w* ~8 c; }如果对IIC还不是太了解的朋友请移步到这篇文章中  n' n" w% |! D8 M, S
) ]0 \8 E4 s  \  @2 [7 ~
IIC起始信号和终止信号:
8 i" m' z2 ^$ F
$ L; d3 s7 z# v' ]. A起始信号:SCL保持高电平,SDA由高电平变为低电平后,延时(>4.7us),SCL变为低电平。
: @7 G7 D9 g7 f) G1 v; C# Q停止信号:SCL保持高电平。SDA由低电平变为高电平。3 J0 q$ r  X0 y1 o. U! |, t

8 ?. h/ Q( I9 j) A/ n  W0 X- e S2R5[}7Z`)G{P}2%QC5GHSD.png 2 e: T1 `8 \8 V/ l' y+ H; a

7 ?4 f  ^3 U/ ?& u数据有效性
6 W: c" W0 ~6 s; m" W4 s) U5 eIIC信号在数据传输过程中,当SCL=1高电平时,数据线SDA必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
5 _4 ?8 P/ |' f2 D1 {; L2 V0 z+ y+ h1 M
SCL=1时 数据线SDA的任何电平变换会看做是总线的起始信号或者停止信号。5 w6 a9 W: D/ f4 ?5 f+ D

7 ^$ R. V: E/ C+ f+ z  X9 E也就是在IIC传输数据的过程中,SCL时钟线会频繁的转换电平,以保证数据的传输
% S: k- @$ b. o( Z8 ^: Z& \' a$ J% U8 B( T/ `. Z
M(Q@A~}}1`7L@B9LIT84{]L.png
: p. G' G5 r, E. a; \: r
$ o5 @! ?& G7 w& E应答信号
# f( B: \+ |- P. k+ ]每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,/ A- j/ [" H2 r

# ~: A; y+ C: R, k应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答
9 S! h% n: a( h: [  U2 _+ o5 Z- H: ~! S; F2 p3 F* V
应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
, T3 a$ A& R$ i: M  ^) {' c" \9 t应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
+ x- n# c. ~6 z. K7 [2 H3 `# M# m3 u- w  i3 V  j1 _- r
T4O0TA%5T7[2WNU]U$(FF7C.png
5 c* Z3 Z6 C+ `) J* ]2 Q+ U5 n. H2 g5 r
每发送一个字节(8个bit)在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须回一个ACK应答信号给发送器,这样才能进行数据传输。
7 k* B  V( L  i; J! ^  u6 M$ n8 c4 r. H' n$ @2 I5 U) u% X
应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答,% d+ A5 Q% h& Z5 Q4 e  m; i. ?

* w2 k9 H3 Y: |' H( c. P9 I Z{ZNXO@GLVSV~GQGAYF4ZJC.png
7 W; r. a+ ?2 b/ }! k" x. i! _  y& J  t( l8 H) n
这里我们仅介绍基于AT24C02的IIC通信
0 W+ `0 N+ z" {* W5 W
% F+ ^& I& A' j/ T7 _: N* @9 |9 T以AT24C02为例子
! S: `! \! Z! t" K24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。在24C02里面有一个8字节的页写缓冲器。
. F: v# I  B  l& E% i. T" i# I  ^
[~@R9HH6_)]C5TLUF7~XB56.png
" L8 `; i7 U# Z5 c& \# |. F$ B4 p7 {  s5 t; U9 Z9 g8 i
A0,A1,A2:硬件地址引脚
( M. ?5 e6 I# O: F/ V" MWP:写保护引脚,接高电平只读,接地允许读和写$ s; J/ `5 r# g
SCL和SDA:IIC总线. Z$ Z* P8 g! H: D9 {7 S3 d
) L3 C( j" ^/ z& l4 I0 l+ k6 z# [

1 t  Z+ d- l6 K7 }5 a& l以看出对于不同大小的24Cxx,具有不同的从器件地址。由于24C02为2k容量,也就是说只需要参考图中第一行的内容:
0 k1 `* Q" y$ U- K6 p, _, O) a4 ^( G$ ]$ w
N4YO0DDI0)A}[TPV1WJS%LA.png
" M% ^, U6 h( l
9 m' ~8 t  C8 _芯片的寻址:
5 j* z' O7 m- V, I2 x* `AT24C设备地址为如下,前四位固定为1010,A2~A0为由管脚电平。AT24CXX EEPROM Board模块中默认为接地。所以A2~A0默认为000,最后一位表示读写操作。所以AT24Cxx的读地址为0xA1,写地址为0xA0。' i& o, ^. A' J- c
1 x7 E1 w: c  `- x. G9 K
也就是说如果是  S/ z$ l/ O. D7 v
写24C02的时候,从器件地址为10100000(0xA0);. J& u" s0 Z6 `2 p' @# @  o
读24C02的时候,从器件地址为10100001(0xA1)。) e: {+ r* `/ g4 p& G  Q' x
7 o4 G2 h! G! l9 Y
片内地址寻址:
9 Z" [9 \6 i' Y& n! n
' o* ^+ }! u6 w; ^5 p" ~" k芯片寻址可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单位。
4 e( c% Z6 T/ V/ b' g6 B+ ]
! b& P* y$ X5 {% g对应的修改 A2A1A0 三位数据即可
( d! E; M7 ]0 |6 S* D1 l' B% r
7 b; a4 T" \; N* _; C- L) s %L{8Q9IZVYLK57)VFQ(GD5L.png
6 T1 s" a! C4 v2 }* c; J+ N
* }8 H# y* Z5 q8 T4 r/ Y9 Z7 p9 Z1 U向AT24C02中写数据
' v7 \, l# A' z/ |( S) A* m  Z1 u5 N, o! j6 c
GVF(F2T]4}{O4}~FQ05B3JB.png
/ }, d4 f$ `3 `/ ^" I7 t( j
5 b6 m' Z6 k) ?8 b, T' k6 h操作时序:
' X; b4 J1 _" O' M  ~$ ~5 t6 f3 [1.MCU先发送一个开始信号(START)启动总线
3 m$ w; v5 z6 s3 _3 \' d2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)" A* q) K+ l5 l( H. C' }5 u
3.等待应答信号(ACK)" N: s0 Y$ I- `! ]- y' Y" _
4.发送数据的存储地址。24C02一共有256个字节的存储空间,地址从0x00~0xFF,想把数据存储>在哪个位置,此刻写的就是哪个地址。5 ~% w0 W# M+ r2 P; {$ D4 `
5.发送要存储的数据第一字节、第二字节、…注意在写数据的过程中,E2PROM每个字节都会>回应一个“应答位0”,老告诉我们写E2PROM数据成功,如果没有回应答位,说明写入不成功。- l. V' X% m! {+ Y1 w; q4 ?0 m+ Y
6.发送结束信号(STOP)停止总线) l  S/ M$ v0 H9 A- p
注意:- }, \! M& x$ b
在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址就会溢出又变成0x00。4 b6 N9 b3 Y1 X4 F0 G5 z

3 ?6 R3 v# l6 y写数据的时候需要注意,E2PROM是先写到缓冲区,然后再“搬运到”到掉电非易失区。所以这个过程需要一定的时间,AT24C02这个过程是不超过5ms!
, w1 J/ R; W5 Q1 ?所以,当我们在写多个字节时,写入一个字节之后,再写入下一个字节之前,必须延时5ms才可以
1 A: K1 Z% I8 Q( B6 {' W
4 }$ E/ s( h, r2 w从AT24C02中读数据
2 S/ Y( }) X4 T1 j* H
2 ?' o$ @0 L. z* }1 T读当前地址的数据
+ d0 a  ?' ?- d3 O0 t. v* J! M$ b. Q: L, B! T
[%JNO9U_{}GE@N5QAAXNBIV.png 5 B" n9 ?. G9 F1 g$ `$ O

. C' ?2 z9 [6 L# A  w2、读随机地址的数据
) t: |/ u* i0 J8 ^' X& O& a
7 r7 w6 i/ E3 z9 ]! U1 ]

' y+ B1 f5 d% ^9 z1.MCU先发送一个开始信号(START)启动总线7 j! x$ ?& D1 h- P. u5 q3 X$ Z& Y; p
2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0); v+ D0 o8 o$ W9 h: w- M7 u
注意:这里写操作是为了要把所要读的数据的存储地址先写进去,告诉E2PROM要读取哪个地址的数据。
5 N1 V/ H& K" o/ M3.发送要读取内存的地址(WORD ADDRESS),通知E2PROM读取要哪个地址的信息。
' [9 a) s! u" s2 ~) d; X  C& o4.重新发送开始信号(START): ^) q1 Y/ k5 {9 o" Y
5.发送设备读操作地址(DEVICE ADDRESS)对E2PROM进行读操作 (0xA1), o+ _  ?7 |: b; y: x1 z
6.E2PROM会自动向主机发送数据,主机读取从器件发回的数据,在读一个字节后,MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据
% l1 S- s' @7 _$ K7.如果不想读了,告诉E2PROM不想要数据了,就发送一个“非应答位NAK(1)”。发送结束信号(STOP)停止总线# G4 G4 N; z8 o, {3 F
  s- t! m- F0 C3 T) X: ^+ g; z
3、连续读数据
2 T- K3 N/ ~7 \
( ~' G: g& s5 ~  @& _ YA%U6OE)]I$FOPP{TSTY6]W.png
8 \  X$ v9 o, v2 k1 QE2PROM支持连续写操作,操作和单个字节类似,先发送设备写操作地址(DEVICE ADDRESS),然后发送内存起始地址(WORD ADDRESS),MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据。E2PROM的地址指针会自动递增,数据会依次保存在内存中。不应答发送结束信号后终止传输。+ f# p, W' s; z0 _

5 |: z) @" h8 e# g3 N8 n* H! }, \# a基于CubeMx的讲解

1 X& @2 @& Y+ c' y1设置RCC时钟

/ a4 b. ?2 g6 V2 p$ L
% C0 Y7 x  A1 Z) z6 g8 y6 N) ]设置高速外部时钟HSE 选择外部时钟源
8 E# \7 e/ W( Q" L/ j- E" o2 v# u5 T
9 y5 H6 o, p+ _2 N8 i H~}{4GU_`0~NYL1QTZ68CVU.png
9 G* o' }# B$ i( F6 F' r
4 B, ]; s  W& T; U: i' U$ p2 IIC设置, o! b' `% B5 O5 ^

7 o7 I8 |2 T; b" Z N}]X7_8ABWVCGHQ7[%]6NT8.png
% }, @5 w6 B" ]: w* @点击I2C1 设置为I2C 因为我们的硬件IIC 芯片一般都是主设备,也就是一般情况设置主模式即可
6 s2 t& h/ R1 ~* y6 I2 M) Y* c- v& e7 R& m
Master  features  主模式特性) _& T) Y) F5 S* L

6 a9 [5 U# |$ L. S& F( [: hI2C Speed Mode: IIC模式设置 快速模式和标准模式。实际上也就是速率的选择。
0 Z% r3 z; l5 O, A9 T" d9 U0 ]! d- B8 Q
I2C Clock Speed:I2C传输速率,默认为100KHz
( T3 l- o, A7 @6 p  J
7 z) P1 |% ~2 r1 [. E( I8 jSlave  features  从模式特性% E1 Y9 z4 T" b) u* H

) g, n+ Y% n! w% g* U' JClock No Stretch Mode: 时钟没有扩展模式  d- w3 E$ P: g, K3 D6 B! E4 [

& n! ]7 B( l) \5 P4 ^0 MIIC时钟拉伸(Clock stretching)
( T0 p  x# P- I$ I0 X$ b7 z; }clock stretching通过将SCL线拉低来暂停一个传输.直到释放SCL线为高电平,传输才继续进行.clock stretching是可选的,实际上大多数从设备不包括SCL驱动,所以它们不能stretch时钟., C4 e8 ^" u0 ~( w0 o3 I! J
Primary Address Length selection: 从设备地址长度 设置从设备的地址是7bit还是10bit 大部分为7bit
6 z1 k. T; B: Z0 P-Dual Address Acknowledged: 双地址确认; K5 R/ x8 _6 k: e' ?6 P
  r) @& b2 V# n6 [" R
Primary slave address:  从设备初始地址" u/ E- k1 M; {5 M6 ?
0 c& O+ v! }: ]+ s2 V' v
这里我们保持默认即可
. a! e3 B# }6 s0 `& c( o5 W
& ^$ S) c# |  C1 U3 串口设置
5 R: t# x5 w/ ^) F3 K, k# @
8 |5 _* n& ]4 a' |) O! }  ]# _ %)KSYV55BDGN]ECSR}C7O8P.png
1 I" n3 r. j) F7 c( g' F
* f; \) n* }( m( z因为我们需要将AT24C02中存储的数据发送到上位机上,所以需要设置下串口
, v. P9 S# Z& g3 T3 E# \
3 k- a& [$ D: Q9 J这里设置为异步通信,其他的默认即可
" u% x7 Q5 H# N* [5 m* C% A% s, X1 `) ?% h6 |9 A5 X# e' N
5 时钟源设置
: w5 n7 S0 k& T4 N1 D2 T9 ^# m7 U+ K
& o5 k% o4 y8 S {UI$O17GRVD)@4$XOFUYj.png 8 d0 `( V) y. G/ n6 A, c

% J( S" Q8 `$ Y% T我的是 外部晶振为8MHz' O7 y2 ~8 i) `$ I
1选择外部时钟HSE 8MHz, `/ H8 H, Y( j  N. F( g- n
2PLL锁相环倍频9倍
8 ^% _- @) J, p: @/ Z& z( ?3系统时钟来源选择为PLL; H  K+ n5 ^( M# f' V' O
4设置APB1分频器为 /2
5 l5 I5 S) W- [9 @5 使能CSS监视时钟
" ]1 j, B0 f& J4 a* E* G/ c" m
+ Y" r! ^9 f5 a" t6 项目文件设置- \5 x$ G5 _6 i* L, [' s7 k( u
: I* R5 e1 {/ j
T77Z1G%_O$[~PTC6J)ZTJVC.png , L, v6 A6 A; H" o. L% G) P) ?% Q

" ~. y5 t8 x  ]4 q( z1 E$ {- D0 T2 G1 设置项目名称9 |) ]. g7 F4 j8 \3 v
2 设置存储路径/ J3 V0 C7 l2 Z: g6 o/ K( @. `
3 选择所用IDE& Q* S2 n% r3 o& l0 A% b
: d; N  Q2 @: k& \/ i" l
$HL2@`Y[HUT60}IW074]CTO.png - p) |6 e7 |- p
2 u/ j" P$ B0 W& I8 O9 I" ~: C
7创建工程文件

* q6 ~# _1 b- i7 C5 `
; Q; T, p  d/ y# k' s然后点击GENERATE CODE 创建工程
2 n$ C2 M  N# X9 J
' I  J1 s# D) T3 X配置下载工具& e$ d+ c' k1 G
新建的工程所有配置都是默认的 我们需要自行选择下载模式,勾选上下载后复位运行7 Q: G$ P8 _7 Y8 J; a  t

; y# E# V9 H) v6 \7 B %6{23MK8K4O39(CF8REOY2D.png 1 j) g5 x4 a% G8 [! f4 y

7 n2 q4 w3 O4 P4 A) R) g$ G- z  p* PIIC HAL库代码部分
2 ?" j7 Z) y; t3 Q6 v在i2c.c文件中可以看到IIC初始化函数。在stm32f1xx_hal_i2c.h头文件中可以看到I2C的操作函数。分别对应轮询,中断和DMA三种控制方式
% m8 H6 G  U/ t3 {2 t3 o
3 w% d2 O4 ^' U$ X8 [7 E2 X0 G/ P X(0}523E8C`}{VUFC[ETK%V.png 2 k# V( S8 Q* U, C$ E9 ^$ o

+ S1 l2 ^7 h6 {5 i+ w9 F0 B  [6 T上面的函数看起来多,但是只是发送和接收的方式改变了,函数的参数和本质功能并没有改变& i$ g0 c3 G$ O8 k& G& H& c& e4 p
比方说IIC发送函数 还是发送函数,只不过有普通发送,DMA传输,中断 的几种发送模式
  k8 F' x4 Q# X, [( B' I9 q1 @
; `+ ~7 j' T7 w+ }, a4 W这里我们仅介绍下普通发送,其他的只是改下函数名即可
$ b- i- W  _- ^/ Q2 D* x/ E6 J5 C& y) _  T
IIC写函数5 P$ D; s5 }- d$ r
' W( {, Z% {" f: O
  1. HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
复制代码

% S1 t# R5 Y9 [5 S7 d功能:IIC写数据3 B1 {" M. K8 P  t  j
参数:
- u9 Q. I: I2 _, ?' L! X9 X*hi2c 设置使用的是那个IIC 例:&hi2c2
) B! c4 \9 D  s. }DevAddress 写入的地址 设置写入数据的地址 例 0xA0( |) o9 m7 T, t% ?
*pData 需要写入的数据6 B% s5 @2 [) i$ [, C- i
Size 要发送的字节数
5 A" B: J2 N* e: }9 iTimeout 最大传输时间,超过传输时间将自动退出传输函数9 o9 j3 \4 s; J- y/ J% C: \
; M1 Q5 i. Q5 r  l
IIC读函数9 A: u& M  N; D0 @8 E

5 u/ l# c- |# J. F$ v# o/ i  ?; ?
  1. HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
复制代码
- |1 \, N) y4 W/ k
功能:IIC读一个字节0 M! y: w5 ]7 L8 `- L4 T# f" d8 e
参数:
3 A$ d" v( _: b1 s) r6 i7 L  p1 j* C* d
*hi2c: 设置使用的是那个IIC 例:&hi2c2/ ^! H7 y" l4 ^1 W4 i

! v' k. N- N2 C: ODevAddress: 写入的地址 设置写入数据的地址 例 0xA0) g  B  |  a1 y+ V$ i
; z" B! I: y" [! F
*pDat:a 存储读取到的数据0 W: O9 L. H# J1 S& i* C7 V
3 R# P# K' D! }  f
Size: 发送的字节数
! J1 {, M5 f! p2 X6 R" _  j$ }/ s% O
Timeout: 最大读取时间,超过时间将自动退出读取函数. N1 s  x4 S  A3 g3 ~. s, p3 v8 E5 g
. f& m. \! O3 ^8 V4 L
举例:
) R- p; g' E: @# }# [. T4 A% B1 B1 p  b0 B- V# ~5 l" ]6 i
  1. HAL_I2C_Master_Transmit(&hi2c1,0xA1,(uint8_t*)TxData,2,1000) ;;
复制代码
7 B4 T) E2 x, X
发送两个字节数据
6 k- _8 t5 ?  m! J" x. K  K" N) o% r8 k% u6 P0 p! p
IIC写数据函数

9 j( A" L$ G. F" F" r2 \8 H) C+ P0 h+ N4 k
  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);" W, Y. K$ O: T7 ?$ h# ~2 C! y* A% q
  2. /* 第1个参数为I2C操作句柄6 a: I; {( Z  T4 @0 c5 t' Q& M
  3. 第2个参数为从机设备地址
    ; |. g  ?5 @/ ~) P# |4 H5 N
  4. 第3个参数为从机寄存器地址
    ; x8 Z6 T. V, r7 M. i" S0 m% s! ]
  5. 第4个参数为从机寄存器地址长度
    . N+ Z0 |$ E5 J% q& @8 {; ?5 W, M
  6. 第5个参数为发送的数据的起始地址
    " `- q/ M0 `$ H, e4 _
  7. 第6个参数为传输数据的大小
    6 T$ |. \' I$ H( T( j) k
  8. 第7个参数为操作超时时间   */
复制代码

$ N3 z  N; a9 S+ E. X; f( N# H功能: IIC写多个数据 该函数适用于IIC外设里面还有子地址寄存器的设备,比方说E2PROM,除了设备地址,每个存储字节都有其对应的地址* I4 m: S' }' |) d8 z

* G0 \7 ]. q2 h8 }$ V参数:
3 P- z6 z! r- K( f
9 Q& Z; b% [# U6 t* k# i*hi2c: I2C设备号指针,设置使用的是那个IIC 例:&hi2c2
8 |8 t8 a7 c% s( H& R' r$ S, D: o, h, l* C+ N! |/ |' _
DevAddress: 从设备地址 从设备的IIC地址 例E2PROM的设备地址 0xA0
. }* d' H: d  J( \  h
" f- \! y* U" o" y* I5 Z9 FMemAddress: 从机寄存器地址 ,每写入一个字节数据,地址就会自动+1' l% Q- U6 N7 l1 i% x- }

2 d, H# a, Z) _* u' V8 {MemAddSize: 从机寄存器地址字节长度 8位或16位$ ~% [9 K3 C4 T, u1 m

- y. `5 f' f. J. Q) I: }写入数据的字节类型 8位还是16位  X. Z" \2 {/ y& z$ {) @
I2C_MEMADD_SIZE_8BIT
2 I: F/ d0 S. ^$ Y( O8 RI2C_MEMADD_SIZE_16BIT
1 M: j# b7 e) p0 \+ z3 a在stm32f1xx_hal_i2c.h中有定义0 A/ X; i. {6 q" D& l+ ?. I- s
4 k' t# J' A- B& L* a
{R2Q(IR$`LHMIG`7XKEUS[1.png 5 ^$ w- y( [' h% ]' X9 i

# ?9 g4 m* v4 A3 u( C- ]& T*pData: 需要写入的的数据的起始地址
9 `8 o6 O- Y1 r, j9 t* s+ r5 ~" e9 a. f# V) ]
Size: 传输数据的大小 多少个字节
, z8 Q4 |7 F3 m% O
) N( Y7 p" e% `  d; ?Timeout: 最大读取时间,超过时间将自动退出函数
! v: y' O# f1 P5 d. B  V
. X+ {: Q2 p5 `使用HAL_I2C_Mem_Write等于先使用HAL_I2C_Master_Transmit传输第一个寄存器地址,再用HAL_I2C_Master_Transmit传输写入第一个寄存器的数据。可以传输多个数据2 ~- l$ J1 v3 K" a) K- m: s. F# Z. G  X

7 e* k7 U' ?$ Q
  1. void Single_WriteI2C(uint8_t REG_Address,uint8_t REG_data)
    " W; y7 J7 C& R% }( L' Y
  2. {6 I1 V3 p3 P- p( L( v
  3. uint8_t TxData[2] = {REG_Address,REG_data};) G( D/ a7 G( ~
  4. while(HAL_I2C_Master_Transmit(&hi2c1,I2C1_WRITE_ADDRESS,(uint8_t*)TxData,2,1000) != HAL_OK)
    6 O. R) M% A, U4 K
  5. {9 W3 i' \+ M9 T: t: A% r: i2 E8 e
  6. if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)3 B# i/ j; o- d2 P6 y
  7. {
    ' |* l: t" H+ H  r3 J! N+ t+ c
  8. Error_Handler();. x0 G5 b2 T% f5 G6 H
  9. }( i8 @+ Z4 ]( [
  10. }
    ( V3 ?! J4 j' v9 x/ N
  11. }
复制代码
# b2 n3 M/ j; z9 E1 q: n! v* P
在传输过程,寄存器地址和源数据地址是会自加的。
% o0 p. V6 h' L! S0 H: X( f+ w! P/ ^7 L1 S' `5 i
至于读函数也是如此,因此用HAL_I2C_Mem_Write和HAL_I2C_Mem_Read,来写读指定设备的指定寄存器数据是十分方便的,让设计过程省了好多步骤。6 Q$ I' W: k$ K1 I
! M: Y; {  ]: o
举例:
. I7 H' N2 i! M: f2 F. [) g: b, t9 b8 x- Q2 E( [3 x5 @- H+ H' W
8位:
  1. HAL_I2C_Mem_Write(&hi2c2, ADDR, i, I2C_MEMADD_SIZE_8BIT,&(I2C_Buffer_Write<i>),8, 1000);</i>6 @/ U8 z7 s: P2 ~! @3 [& K: B
  2. 9 J8 M4 q; ~) I7 o
  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>
复制代码
& X1 x) Z* X5 \! S% r# h
16位:
" Z. P5 j; C* ~( o
  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);
    # H! P+ \, ?: `$ q  m0 T

  2. ( U7 n# N, j, Z7 |4 \( s
  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 ?$ X1 E" t. w/ F1 ]- e
如果只往某个外设中写数据,则用Master_Transmit。 如果是外设里面还有子地址,例如我们的E2PROM,有设备地址,还有每个数据的寄存器存储地址。则用Mem_Write。
$ {. }' w  ?+ Z( sMem_Write是2个地址,Master_Transmit只有从机地址9 x4 y! {6 a9 b- v8 a- Z

9 ^5 a0 L. v- \) c硬件IIC读取AT24C02
3 i6 ]7 a* `7 u' C在mian.c文件前面声明,AT24C02 写地址和读地址 ,定义写数据数组,和读数据数组
' v- c  s6 `5 r3 w' @
! @9 ~6 j6 P( t" c; q2 M* M
  1. /* USER CODE BEGIN PV */% K* n; h# _6 x9 J/ U4 y8 b6 w
  2. #include <string.h>6 W' o2 d# S1 p' @, f8 r
  3. 1 z3 X/ L+ Z0 C; ]; O- g
  4. #define ADDR_24LCxx_Write 0xA0; v" f7 q' z7 C1 n2 E
  5. #define ADDR_24LCxx_Read 0xA17 o+ [1 l, J* a! G" R. F0 @
  6. #define BufferSize 256
    , V6 s& {0 a# L+ ^+ @9 ]# L' K
  7. uint8_t WriteBuffer[BufferSize],ReadBuffer[BufferSize];. J: S8 v% W3 {& i$ Y) i
  8. uint16_t i;
    ; x+ P) o: G8 l) f' n' T& ^. q
  9. /* USER CODE END PV */
    ( T; T5 _9 q, |$ v0 f( q
复制代码
# a: w! w+ F; h, z0 _0 r
重新定义printf函数" f! [- m+ d; H0 i0 q
; `8 P# X6 f; N9 @& n+ z5 [
在 stm32f4xx_hal.c中包含#include <stdio.h>
- {) D: z* f! T% t+ O! ]( b( w3 i1 k8 |9 f/ P7 i1 L; l
  1. #include "stm32f4xx_hal.h"5 ^. S; L: C" K! R2 Y5 H
  2. #include <stdio.h>
      C& W) s0 M: \4 h5 K
  3. extern UART_HandleTypeDef huart1;   //声明串口
复制代码

& {! A' h' W: N: p, P# Y4 O在 stm32f4xx_hal.c 中重写fget和fput函数
' p  f. ~% E7 T( H( o% t. `
- ?  r0 Z9 k, X
  1. /**9 m8 c' L- P: m

  2. ! E6 |% O! V! K5 g# g
  3. * 函数功能: 重定向c库函数printf到DEBUG_USARTx
    & c' ?$ I: [4 G  d$ U7 A8 e% f/ C
  4. * 输入参数: 无1 t$ y; r# i; B4 o" h, Z
  5. * 返 回 值: 无/ _/ T! Z" m1 B& L' ?2 c
  6. * 说    明:无
    0 c" L) b# s: g/ ?8 N$ _
  7.   */
      ~# \' L0 f, @: i
  8.   int fputc(int ch, FILE *f)
    8 u) x: o+ }* n8 l7 E- X
  9.   {4 [/ s% P2 S- u: {* D8 s
  10.   HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);+ q: z  p. h. S" C
  11.   return ch;4 }1 {/ g' s3 w
  12.   }
    & a) L: x$ ?% W4 y' W

  13. , ]8 F, k, R1 Q- Z
  14. /**
    & f  V! A3 r3 g3 _/ y6 s& G
  15. * k7 `4 A8 A* D6 d; L
  16. * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
    * f: W9 P+ `9 \, j: V: M
  17. * 输入参数: 无' D8 W+ _  H! H; D, K5 q
  18. * 返 回 值: 无
    ' i, }+ w- V! N2 v3 m# x5 C2 v
  19. * 说    明:无0 {; T% Q8 K% A) Y4 e( m( L
  20.   */
    / Q- ?/ ^( a' \3 a# }- v( b
  21.   int fgetc(FILE *f)
    7 E* C' x! Z2 O* k' r" _
  22.   {
    & j# F6 k/ j) Y  ?
  23.   uint8_t ch = 0;
    9 \6 f! H% e) I- i) i0 j- s
  24.   HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
    $ F+ [, E5 f( L6 |, U" B9 c2 d
  25.   return ch;
    & h: Y0 J+ [8 [) l/ w5 S
  26.   }6 \; {  E$ k6 h
复制代码
1 C" @' a; j3 Q/ ?1 ?! [
在main.c中添加" t9 d7 w' ]+ }& B; T6 n
4 ]5 X2 K& D+ Z- G* l+ v
  1. /* USER CODE BEGIN 2 */9 Z+ T% j* c) V4 u  U
  2. for(i=0; i<256; i++)  _3 a5 v; t7 J' w# R- o
  3. WriteBuffer<span style="font-style: italic;"><span style="font-style: normal;">=i;    /* WriteBuffer init */
    " U" k% U# w, C6 m3 i3 g

  4. & L4 R+ t- f; \+ y0 @
  5.         printf("\r\n***************I2C Example Z小旋测试*******************************\r\n");
    $ G9 v" D; ^- j. z- @
  6.                 for (int j=0; j<32; j++)$ Q( B/ N8 o+ \  k
  7.     {
    8 `: S3 e; `% y# [
  8.             if(HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, 8*j, I2C_MEMADD_SIZE_8BIT,WriteBuffer+8*j,8, 1000) == HAL_OK)
    ' [8 J$ ?/ F1 I+ S, \7 R& A2 O6 K, }
  9.             {) ~# p8 [' p9 n2 U/ a
  10.                             printf("\r\n EEPROM 24C02 Write Test OK \r\n");- F& o: T# K1 t' R. @/ G
  11.                     HAL_Delay(20);
    7 {( b3 Y4 T% A$ I
  12.             }
    7 k! s" s, b) ~9 c
  13.             else. T. B0 O+ |/ N7 D! [% R; l! U
  14.             {  P/ ]' P7 B& K. N5 S. t
  15.                      HAL_Delay(20);' y9 k+ O+ D: M% j* q8 f" T
  16.                             printf("\r\n EEPROM 24C02 Write Test False \r\n");9 q+ n6 A: F& h5 f
  17.             }
    ( k- V! ]+ e, ^8 u! ~
  18.         }
    6 G, V3 d7 g$ h# t
  19.         /*, {! l9 j8 m6 o  V9 ?3 h* F$ N
  20.         // wrinte date to EEPROM   如果要一次写一个字节,写256次,用这里的代码
      C7 q2 o5 X; ^  |: K; B$ U9 {
  21.         for(i=0;i<BufferSize;i++)
    . r2 }8 D& Z4 F) n( j3 F
  22.         {( t6 E- J: `- q- [
  23.             HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, i, I2C_MEMADD_SIZE_8BIT,&WriteBuffer</span><span style="font-style: normal;">,1,0xff);//使用I2C块读,出错。因此采用此种方式,逐个单字节写入
    " I/ y: o* {  C1 V  q# [6 v
  24.           HAL_Delay(5);//此处延时必加,与AT24C02写时序有关" S+ o" g2 e. S  J5 r2 `9 V
  25.         }
    * t6 p. `/ B0 k& h: \
  26.         printf("\r\n EEPROM 24C02 Write Test OK \r\n");2 P. j, ~) F' i
  27.         */4 `/ S: n6 s% @2 B9 {! {
  28. / H) I) X& ]# i8 U
  29.         HAL_I2C_Mem_Read(&hi2c1, ADDR_24LCxx_Read, 0, I2C_MEMADD_SIZE_8BIT,ReadBuffer,BufferSize, 0xff);+ v! v0 S+ Z' H

  30. 7 A& K" Y$ q8 \( v
  31.         for(i=0; i<256; i++)
    * h0 ]0 @' m: @: ~5 b
  32.                 printf("0x%02X  ",ReadBuffer</span><span style="font-style: normal;">);; p/ }& t& f6 B- S7 d
  33.         
    : W! e: G$ K9 V3 e3 [" M- w
  34. /* USER CODE END 2 */
    3 M) C  G9 ~% ^+ f* u) Q
  35. </span></span>
复制代码

2 W8 h. z0 \1 O, [9 ]注意事项:
! C* q& q7 E( YAT24C02的IIC每次写之后要延时一段时间才能继续写 每次写之后要delay 5ms左右 不管硬件IIC采用何种形式(DMA,IT),都要确保两次写入的间隔大于5ms;5 _% `/ \2 l* _
读写函数最后一个超时调整为1000以上 因为我们一次写8个字节,延时要久一点9 U. s5 [8 `* o: f8 f$ L0 ]
AT24C02页写入只支持8个byte,所以需要分32次写入。这不是HAL库的bug,而是AT24C02的限制,其他的EEPROM可以支持更多byte的写入。

& |8 v. L( ~" E' j& V5 f4 M
8 R) ]7 S0 y$ P, _  U4 t8 q当然,你也可以每次写一个字节,分成256次写入,也是可以的 那就用注释了的代码即可
  1.         /*6 C8 B4 }, t8 r& N( s
  2.         // wrinte date to EEPROM   如果要一次写一个字节,写256次,用这里的代码
    5 i0 U8 n8 g9 u5 w$ Y: W' `
  3.         for(i=0;i<BufferSize;i++): S3 ]8 Q! j* P9 c* j4 ^) T! }
  4.         {
    , K9 K, l/ }; [- 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块读,出错。因此采用此种方式,逐个单字节写入
    : t: d# |5 k, \% D) {4 r
  6.           HAL_Delay(5);//此处延时必加,与AT24C02写时序有关
    2 L( y0 v3 Q4 K5 C4 l
  7.         }* b; ?' S; q. u$ k. V& B
  8.         printf("\r\n EEPROM 24C02 Write Test OK \r\n");
    $ r7 O6 _% L- t3 _* B) J- l
  9.         */</span></span>
复制代码
. \, U( N, _; a+ i
注意读取AT24C02数据的时候延时也要久一点,否则会造成读的数据不完整% S( W% b/ p. E: E) m; S

/ z- z: j% ^2 k1 h5 H9 h0 ?

7 J0 L( k5 h9 }+ {5 P! u# o7 u  A" L* m3 l  ]* H0 P
经测试,例程正常" s( w& ], m, g# I' A3 r8 W* N

8 g4 H6 K6 O: n+ R5 D
; E1 x2 y$ O$ s
}HXE1$R$NS[]3IB84QTD03L.png + S0 r' e) A  b% h# e7 e8 Z5 Q6 k+ w
收藏 评论0 发布时间:2022-3-23 16:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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