前言:
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
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
( 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
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
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( @
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
( 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
# 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
& 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 |
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
[& ]' 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
. 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
) 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 ]
( 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
: 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
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
& 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
; 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
! 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
/ 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' |- 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& \
- 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- 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
- 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 - /* 第1个参数为I2C操作句柄% o) o5 K( Z ]8 C9 w$ x3 M& `
- 第2个参数为从机设备地址. X p* }& t' [! A! c& m9 g7 g1 a3 A
- 第3个参数为从机寄存器地址- u, [5 i" U9 _. H* G
- 第4个参数为从机寄存器地址长度
# E' K& D$ I) f* i: ]+ q1 x - 第5个参数为发送的数据的起始地址7 @- G S7 G& g/ U) P7 B# E
- 第6个参数为传输数据的大小+ h9 M* v3 O6 k# ^, _$ S p
- 第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
: 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
- void Single_WriteI2C(uint8_t REG_Address,uint8_t REG_data)
, S4 B7 F' s5 {! ]( f8 y1 J - {0 h4 Y3 x$ a* K8 g3 Y9 p2 L
- uint8_t TxData[2] = {REG_Address,REG_data};
( @# n7 ^2 i& [+ Y - 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 }* ]9 x4 v$ @7 R. o - if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)! n9 z! |& w* z; d
- {9 L$ y) \ v* e- |$ i/ ^
- Error_Handler();
q! ~# e9 D1 r% p2 ? - }
4 @2 L e. Q: \! R" l+ | - }
1 ~1 B# P2 Z& l6 i' o4 u( l( d - }
复制代码
* \% 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位:- HAL_I2C_Mem_Write(&hi2c2, ADDR, i, I2C_MEMADD_SIZE_8BIT,&(I2C_Buffer_Write<i>),8, 1000);</i>
! Y4 S! @ K5 b Y% j
+ I/ t: c, N: \( T: s: i- 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/ ^
- 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 - 1 v" _8 e t$ p4 l7 e
- 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- /* USER CODE BEGIN PV */1 ^ A1 i2 {1 @3 X9 m
- #include <string.h>
( F/ b6 }2 ^4 F' L
; z6 j) D0 V* s5 Z- #define ADDR_24LCxx_Write 0xA0
* Z1 y' ?% y6 g+ V - #define ADDR_24LCxx_Read 0xA1
6 Y( a8 G: f F4 C+ e3 e$ w1 p - #define BufferSize 256
0 q7 M D9 N. L, B! c$ u+ G( I; b3 Z - uint8_t WriteBuffer[BufferSize],ReadBuffer[BufferSize];; M/ T% k* [! Q) b& ^8 {5 h
- uint16_t i;
p: y* B2 ]# n; K# u+ B- p - /* 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
- #include "stm32f4xx_hal.h"' j$ d$ _/ y+ S* \: V
- #include <stdio.h>
; t! V' \/ h5 I - 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 n/ U% t: ?4 ^, T8 J: i, H - 6 R! `, x; z' k" O1 m( |
- * 函数功能: 重定向c库函数printf到DEBUG_USARTx% g- p! [8 D) |* S% {9 ], y3 d
- * 输入参数: 无
+ ^; Q2 D1 a% b; L - * 返 回 值: 无
( I, o* Z E( z6 m( n' b - * 说 明:无
- C- I! R; m0 O; ~1 J* ^ - */
( y, p9 t: J) w) n - int fputc(int ch, FILE *f)# ?! J( ?* v" F7 ]1 {
- {/ ~& y9 H) P7 U0 P$ o! T) O
- HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);( m8 _, b5 N' y7 j; H9 J! `7 {
- return ch;& s% s; e9 Y5 z7 P5 i X
- }. R( d& D5 L- @
: D3 I5 [* z( y1 h- /**2 v* O5 P j4 L! O) W! K
- * k& a% t# d9 m( D4 o% g
- * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx% O/ O$ L! T& F9 W8 d3 ~% J* v( S0 O
- * 输入参数: 无% J# J4 ^9 K; P) d6 ~
- * 返 回 值: 无. y) D/ C9 D) C- f
- * 说 明:无
0 K! c' n* a/ y4 N9 Q" o - */0 X' m' ~% {0 h8 h
- int fgetc(FILE *f)
& c0 ?7 X/ X8 t- p8 _# \, ~ - {
) E" S8 T8 Q8 S0 V5 U - uint8_t ch = 0; L( g8 W& k8 d4 g$ T) c
- HAL_UART_Receive(&huart1, &ch, 1, 0xffff);3 ^3 I, l/ u0 {( _; p$ z9 j
- return ch;$ p J( M( V# N) x. Q. M
- }
* J6 M! M9 ], W
复制代码
& M# i5 \* ?/ g在main.c中添加
$ f! B3 R/ K* ], v& u& j
$ Y' S% J( [( U# R/ W. n3 \- ~- /* USER CODE BEGIN 2 */+ p$ l3 q5 x+ j2 L) W7 `
- for(i=0; i<256; i++)
- ~4 z: p; y1 }3 G8 E* ? - WriteBuffer<span style="font-style: italic;"><span style="font-style: normal;">=i; /* WriteBuffer init */, C0 c. K. o) L! y4 T U. ~
- ! I& y- Y2 O" ~& X6 E
- printf("\r\n***************I2C Example Z小旋测试*******************************\r\n");
7 Y+ M/ E" x, i2 D. L! s4 d, c/ ` - for (int j=0; j<32; j++): F( q9 w, \/ j5 Q, P
- {, b- {: q& F3 k7 \; _) x5 v; i, J
- 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# @ - {& \7 C) | x$ i
- printf("\r\n EEPROM 24C02 Write Test OK \r\n");
! r( ?) b; L3 ~ T6 _/ S9 Q4 i - HAL_Delay(20);
) Y" U( A6 O3 {% J. z2 Q. M0 q - }
! p, q0 \+ [4 `: [, Y) B - else8 B E; D# `: l' f0 @+ u8 v# |
- {
8 Q5 X9 f* m9 y$ [ - HAL_Delay(20);& I) @5 C: A( O0 `
- printf("\r\n EEPROM 24C02 Write Test False \r\n");# P1 X1 Y+ F9 F; w
- }
Q+ |0 O) f, _# k/ {2 E3 [ - }
1 H, k6 B3 @( E' o5 G4 d- e7 A - /*
2 z1 Q# L/ H/ w. I! b! a - // wrinte date to EEPROM 如果要一次写一个字节,写256次,用这里的代码/ F1 l" n4 C2 i6 K @2 w
- for(i=0;i<BufferSize;i++); u ~) N" |) e0 x0 b
- {9 y3 K: F k/ H2 j
- 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
- HAL_Delay(5);//此处延时必加,与AT24C02写时序有关. A, j& C6 a! q0 H9 j
- }/ {5 v3 R1 l& T( f9 C" f
- printf("\r\n EEPROM 24C02 Write Test OK \r\n");' \' \# U: j1 {3 G6 e) ]: M4 X
- */
+ ? H* n! [& Y5 h
, K2 I7 Y% S4 Q% v- HAL_I2C_Mem_Read(&hi2c1, ADDR_24LCxx_Read, 0, I2C_MEMADD_SIZE_8BIT,ReadBuffer,BufferSize, 0xff);
5 C2 \; h ^3 Q K) s
7 c T8 N& Y* X. y/ k) t- for(i=0; i<256; i++)# K- d/ \, j$ _. v* q% A
- printf("0x%02X ",ReadBuffer</span><span style="font-style: normal;">);
+ I' R, S0 G4 L% A) x - ; h6 n# d- {# j ?5 I: r: G
- /* USER CODE END 2 */- i& w3 l( {1 k1 L# M
- </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次写入,也是可以的 那就用注释了的代码即可- /*
4 T+ M: J4 f. Z( o( ~- y - // wrinte date to EEPROM 如果要一次写一个字节,写256次,用这里的代码6 ?! D9 a6 R: k0 K. K- e# L
- for(i=0;i<BufferSize;i++)
8 {2 I9 k- g8 S( i$ w; t# T - {% C0 o* V+ ]6 k% H5 X* N. C. y
- 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 ^+ ?
- HAL_Delay(5);//此处延时必加,与AT24C02写时序有关
+ P" B) X; `' ~! ~0 _ - }
) D# j; ~% B1 \5 \" J0 q; O1 S2 K" ~ - printf("\r\n EEPROM 24C02 Write Test OK \r\n");
, {3 x0 \" o! I) i' i. s% | - */</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
9 k& [1 [8 b/ E! L |