前言:
" |$ Y4 d7 b! u0 I本系列教程将HAL库与STM32CubeMX结合在一起讲解,使您可以更快速的学会各个模块的使用' l; g) d: s; o: ?% f
/ X; U" y7 i. T0 i
在之前的标准库中,STM32的硬件IIC非常复杂,更重要的是它并不稳定,所以都不推荐使用。2 h( c7 {9 _5 N
但是在我们的HAL库中,对硬件IIC做了全新的优化,使得之前软件IIC几百行代码,在HAL库中,只需要寥寥几行就可以完成 那么这篇文章将带你去感受下它的优异之处
1 _3 A& p/ y2 Z8 b; B& R9 e; E o6 i3 F$ F* s; Q
这可能是目前关于STM32CubeMX的硬件iic 讲的最全面和详细的一篇文章之一了
3 d2 P1 H7 _" L1 d* q+ C
; j4 }8 v& a' p T1 A8 o所用工具:
" v8 c; \# ?; Y7 n' ^
5 V- o) T+ C( O1、芯片: STM32F103ZET6
* ]$ R6 F6 X6 X- s' i* z6 `; \
1 z( H4 j0 B3 T4 ~2、STM32CubeMx软件
' x( ?; @5 @. U- v
; Q: _! \" o j' u, U) @3、IDE: MDK-Keil软件% T2 x$ n& _: l6 q' j& D" u
c7 Q& K! y- `0 ]- ~
4、STM32F1xx/STM32F4xxHAL库
! X& x& w$ W: T) g
% W. C2 S0 s5 }, _ V5、IIC: 使用硬件IIC17 @) r% ?& ^8 F C
3 P# j0 L' ^$ Y: k
知识概括:
" r1 P3 l, x' e% W9 W
! D, O; _/ K$ H通过本篇博客您将学到:
: ]& y% N8 W# q) E7 E4 }
9 m* K+ W! ~2 ~! T8 g n7 X0 X. Q5 vIIC的基本原理
2 Z7 v' d% Q5 m" O Q, A$ E! j! \, G ]# z
STM32CubeMX创建IIC例程( @+ f; r& h) Y8 J7 H2 O2 f
0 u- J1 T5 L/ l, M4 @* oHAL库IIC函数库
5 R8 ]% I+ T0 u% H9 L5 G- H6 \5 G$ O2 V5 R$ B( l2 ~
AT24C02 芯片原理
$ c f8 Y- s I# [, ?
2 L. Z7 d% H4 V) mIIC 简介
! K$ e" S7 B/ eIIC(Inter-Integrated Circuit)总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。) Y# q' F' I$ ?, X+ H0 p0 [
" A6 y# ~: N9 q; P, h* h# R
在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。
0 X+ S9 M; Y, e; v6 Z, J
; w% o+ d- Z- n* jPS: 这里要注意IIC是为了与低速设备通信而发明的,所以IIC的传输速率比不上SPI( `0 ]+ ^ t# P/ o
+ a! }. }7 t" u7 H
IIC的物理层
% q8 G: c4 p& ?4 {, i3 v7 eIIC一共有只有两个总线: 一条是双向的数据线SDA,一条是串行时钟线SCL
$ k$ A R x+ @' T/ k- O: x3 ^3 b+ ]
所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。I2C总线上的每一个设备都对应一个唯一的地址。
9 K S+ G' o, x w
+ q; p2 W) k5 v. }
6 t1 A9 x8 w" _: P( j' P1 X# n: W$ Z" y4 C7 n
关于IIC的讲解,已经单独整理了一篇文章:
5 E l9 @( j7 P8 q( _( i
& x- B# i% U2 F' f; ]! n《IIC原理超详细讲解—值得一看》。
2 \* P% z" S, C5 H ?+ `如果对IIC还不是太了解的朋友请移步到这篇文章中
8 d2 n" M* H6 _. w) i8 k
2 o: b4 a, F1 `1 f& }IIC起始信号和终止信号:
$ p2 S- T) k7 v! O. Y, r
1 v& y4 M6 k; g, L起始信号:SCL保持高电平,SDA由高电平变为低电平后,延时(>4.7us),SCL变为低电平。
- B& @ D% ]5 Y) c4 V停止信号:SCL保持高电平。SDA由低电平变为高电平。
1 J3 a( |/ ^: l/ g6 o1 p1 y. Z- c# T
7 |# a! L6 h7 k
+ G7 F6 L/ J$ @0 ^
数据有效性* E9 w# K( [2 l! G( l
IIC信号在数据传输过程中,当SCL=1高电平时,数据线SDA必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。, ~7 t( A1 I: t( ]) u
: B* _* A. x8 ^8 s0 z6 O& `* w& T6 V
SCL=1时 数据线SDA的任何电平变换会看做是总线的起始信号或者停止信号。4 Q! w* K- {% I) j
* |+ }8 W" I5 \; R
也就是在IIC传输数据的过程中,SCL时钟线会频繁的转换电平,以保证数据的传输; Z$ i, W0 C% O0 A- h
+ c2 `/ A. l, Z' J' u
( j3 f6 z4 Q% ]" V1 W
4 ~8 v2 g9 ]3 k) w
应答信号
- s5 M! q- ? w( {5 y4 i每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,7 h7 v% G; G- {0 S
, O* K) K/ v D. W6 f, ^
应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答+ ~( s3 u4 x* V5 p2 o @9 X( y) n
$ Z1 a7 d7 w2 X3 g# q |应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;2 b1 i) k8 a% M8 o T. }" ~
应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
" O& r, \ p: t) u6 |- [" {( L
' p2 _5 Q; b/ {: I0 Z
" ?& d* L$ ^ x
每发送一个字节(8个bit)在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须回一个ACK应答信号给发送器,这样才能进行数据传输。# X; ~& V: e5 H( y! G6 L# r8 |
& e9 Q: W6 t4 q! \/ v/ L
应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答,
. X& s! R0 s) N, v4 a: @/ M/ o1 Y( F3 D! ^
+ T: D% x$ [5 _% K
! K6 E7 F( ?/ d3 x$ u: q这里我们仅介绍基于AT24C02的IIC通信( T* V" T' J8 g; R: {* x
! Y1 P1 s" [& Q5 ~以AT24C02为例子' ], z# H: ]1 s0 V3 A: S! v2 {+ y! i
24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。在24C02里面有一个8字节的页写缓冲器。$ R: k, Y' L2 e6 W
' k7 s3 J$ @5 C' C
; F4 p2 B$ I0 [7 U( v8 v, a" N+ U; q" M& E2 x" I
A0,A1,A2:硬件地址引脚
: a# i% S) p1 V9 L {, X+ kWP:写保护引脚,接高电平只读,接地允许读和写- \( ~1 v1 m! O" R
SCL和SDA:IIC总线
! O3 k6 Q7 a7 R1 O$ M可
8 c" _" y. Q4 W0 Z3 z, V; [2 d! C7 X. J ] B9 `, A; J
以看出对于不同大小的24Cxx,具有不同的从器件地址。由于24C02为2k容量,也就是说只需要参考图中第一行的内容:
: }6 c! H9 Y( ]/ j$ m. ~
- f3 I. X' ?9 D0 ?
4 S# J0 Z/ B1 H1 P( C
( C7 B3 `1 c6 Q+ x. X0 `! |芯片的寻址:
* J" i$ a( e" @! KAT24C设备地址为如下,前四位固定为1010,A2~A0为由管脚电平。AT24CXX EEPROM Board模块中默认为接地。所以A2~A0默认为000,最后一位表示读写操作。所以AT24Cxx的读地址为0xA1,写地址为0xA0。
. @8 c0 {" S) t9 g9 F
+ D' M& e) @. i9 d0 C' G p$ b也就是说如果是2 i q- h# e) A, R0 p
写24C02的时候,从器件地址为10100000(0xA0);. O( F+ j- Y/ ]
读24C02的时候,从器件地址为10100001(0xA1)。
3 H( i6 K+ K s
1 p) K& p; y) I# @& T) L$ g r片内地址寻址:1 ^; z5 ]' w5 J3 r
7 K: U* O( k5 a; q芯片寻址可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单位。
: y+ N8 n3 U6 @( c
( L) c! ]) A. A! z: s0 C1 E. C% U$ {对应的修改 A2A1A0 三位数据即可1 X6 g5 I- z! }
, Q4 i% j- b+ X! q
) v7 t0 Q" |1 Y& V! g/ u
4 S& V! n4 G1 E* A1 ?5 R& ~向AT24C02中写数据
' i! f0 w8 p' R# T$ l. h) l7 R
: [3 R& M# l6 s: U0 I# D2 B' k
2 Z% O1 x0 x& G V+ H( H3 T: q
1 r0 C3 L1 J: b# l; G0 \) n) s操作时序:- f* E# l! O3 X: Z, O( F" @
1.MCU先发送一个开始信号(START)启动总线
0 V7 o3 x9 `5 H& ` S4 Z. ^2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)6 V$ p/ e% U: ?7 `6 z0 `
3.等待应答信号(ACK)
. Q* a/ G8 r2 k v+ P# B2 }2 {+ r4.发送数据的存储地址。24C02一共有256个字节的存储空间,地址从0x00~0xFF,想把数据存储>在哪个位置,此刻写的就是哪个地址。
% y$ q1 L# w, b5.发送要存储的数据第一字节、第二字节、…注意在写数据的过程中,E2PROM每个字节都会>回应一个“应答位0”,老告诉我们写E2PROM数据成功,如果没有回应答位,说明写入不成功。
- v O6 G2 \4 x5 R+ g/ `& P6.发送结束信号(STOP)停止总线
' ^2 L: ?2 h: F; J注意:
' p" h U6 @+ o! b在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址就会溢出又变成0x00。
) z) d6 `, n3 v$ ?3 E( ^" a1 W. J1 G0 y1 A
写数据的时候需要注意,E2PROM是先写到缓冲区,然后再“搬运到”到掉电非易失区。所以这个过程需要一定的时间,AT24C02这个过程是不超过5ms!
8 }4 c4 p$ a a* \6 C3 e所以,当我们在写多个字节时,写入一个字节之后,再写入下一个字节之前,必须延时5ms才可以' J$ x; K2 E2 t" K5 q' j
( o) D7 A* U0 p' G
从AT24C02中读数据
7 |6 K ^! M) R |
/ \4 U0 k5 L' R. S3 u+ N2 [读当前地址的数据
1 l& }( z& S+ K9 I% t e5 R( H
* s; I0 a$ X0 U4 ]5 P9 a
! M! {" u: P5 k, X7 s0 C0 T6 m, q& H! Y3 B% t
2、读随机地址的数据
6 q; C8 Z W1 M: S
- G. ^7 N" p! I/ v+ f7 W+ u
& W1 @4 N1 Q+ t# @. L: a
1.MCU先发送一个开始信号(START)启动总线
0 m ` p; B9 {9 s2 _" x+ v7 x& h+ {8 k2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)
* u. J4 }2 S, L注意:这里写操作是为了要把所要读的数据的存储地址先写进去,告诉E2PROM要读取哪个地址的数据。
" o/ m5 _% Z9 q/ \' a' i' R) Z3.发送要读取内存的地址(WORD ADDRESS),通知E2PROM读取要哪个地址的信息。3 u, V9 N% P7 r0 ^
4.重新发送开始信号(START)0 ?6 M/ p% V% _/ A4 K, ]( n9 m! `
5.发送设备读操作地址(DEVICE ADDRESS)对E2PROM进行读操作 (0xA1)4 H! r5 K( b) g. i8 U L
6.E2PROM会自动向主机发送数据,主机读取从器件发回的数据,在读一个字节后,MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据
7 f" \: [- f8 n! J1 G7.如果不想读了,告诉E2PROM不想要数据了,就发送一个“非应答位NAK(1)”。发送结束信号(STOP)停止总线
. m# k2 l/ z% F
# E% [+ _, J/ C6 _4 F9 I; n5 N3、连续读数据2 _: V# P+ H! c" U& @( m
/ \9 g/ U4 ]' N2 J: _, z7 {, |
3 Q! L1 a D8 m! d
E2PROM支持连续写操作,操作和单个字节类似,先发送设备写操作地址(DEVICE ADDRESS),然后发送内存起始地址(WORD ADDRESS),MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据。E2PROM的地址指针会自动递增,数据会依次保存在内存中。不应答发送结束信号后终止传输。5 f) ~; ]7 T2 Y/ S" V: C5 l& f* E$ X( m
- z: Z, j9 i+ L; A, G8 Y9 u! z
基于CubeMx的讲解. M) _' w" b; F% }# m
1设置RCC时钟! s( ^5 c/ H& N9 A2 M
) L: D% l5 B2 w9 \% O' g# H设置高速外部时钟HSE 选择外部时钟源
7 K+ t0 G5 r: Y. Y) t2 k% Y+ s c* t4 D8 v/ X3 L
4 G$ \/ u1 Z- e$ W6 W2 ^# L
7 o8 k9 Q( u. _: a* T" f* k$ ^2 IIC设置
+ g) n' Z3 b! y- J* s: K) I% d1 |. x3 X; [. k; W! a9 ^- B0 E
) t, E# t& c, U5 F: ]
点击I2C1 设置为I2C 因为我们的硬件IIC 芯片一般都是主设备,也就是一般情况设置主模式即可, ?) e# X; { G* L' P ?! e+ U
3 I1 r- m9 t- _
Master features 主模式特性
: E& }( u9 q! R" m1 J# O+ D
; ] ~( r/ x0 @9 c* ]I2C Speed Mode: IIC模式设置 快速模式和标准模式。实际上也就是速率的选择。+ k/ h, ]& F# D6 H: `, F
2 V- E, t' D- D0 Y* x2 A; P
I2C Clock Speed:I2C传输速率,默认为100KHz
# |8 X( q, \' K9 w# i" s, s" k# v; q8 \5 S4 n
Slave features 从模式特性- a V3 Z4 Y4 H
1 ]6 c& c8 q7 F3 s- V, HClock No Stretch Mode: 时钟没有扩展模式
( |( c7 w0 w6 z t# H* ]6 h1 Y' }6 x. [ U, h
IIC时钟拉伸(Clock stretching)
/ W4 }' O# U+ K/ B9 A5 |4 sclock stretching通过将SCL线拉低来暂停一个传输.直到释放SCL线为高电平,传输才继续进行.clock stretching是可选的,实际上大多数从设备不包括SCL驱动,所以它们不能stretch时钟.
+ Q6 }% {- `" y( o, W2 e) wPrimary Address Length selection: 从设备地址长度 设置从设备的地址是7bit还是10bit 大部分为7bit4 i/ e1 D: P2 o* ?$ c
-Dual Address Acknowledged: 双地址确认 \$ {8 p3 l! k/ W
& F1 G: h. B; i" J+ o5 dPrimary slave address: 从设备初始地址. M( m/ V2 y( O
8 m% m) J$ G* }: F; I这里我们保持默认即可
: @) Z! v* U$ w8 @1 _5 l0 S) L) O* o, J
3 串口设置
* j9 F8 E2 W. _! F6 X6 z- H, Z/ C
5 d( { X) n8 Z6 G) M! I
: P2 Y" F+ j9 V$ B+ e9 i
) K) a. P& G: j+ d! L4 L+ L h, v因为我们需要将AT24C02中存储的数据发送到上位机上,所以需要设置下串口
5 |5 u H1 p' U* x2 ~5 X' G8 v' q- _$ d" [3 e; R! K( Q
这里设置为异步通信,其他的默认即可, V: J3 D0 c& T. M/ R+ q
7 Y- ?: i8 l" s; @; H0 Z q4 Z1 w
5 时钟源设置 G; q3 M3 g0 ~7 [+ Q5 [
Q' D: U! L: _5 I1 x* ~' {
6 E( F, Q+ D$ \: ^
i: F8 F( J9 F: z, K我的是 外部晶振为8MHz1 Q" ^) ~3 h7 m+ G1 ?) ~0 l
1选择外部时钟HSE 8MHz
* W6 J) e. v7 L/ V2PLL锁相环倍频9倍
$ R4 r( _( G! C7 W, h3系统时钟来源选择为PLL
; k( J/ G# s3 Q6 M3 h4设置APB1分频器为 /2
/ N3 r. k8 t4 N9 j! x5 使能CSS监视时钟2 ^0 L8 a+ @9 }- C. Y$ b# ^: Z7 V
2 I# W$ ]& S1 J+ O* f6 项目文件设置, N/ i' I; @' ]# v z& H% s, J
: I+ a1 L5 C! _* ~
; {( _* j9 {1 |; }" b4 q, b7 C; o- ^: \) ]$ V9 j4 b
1 设置项目名称
8 }: m Y, v, G6 R! t; ^9 x r. h2 设置存储路径$ c5 Z/ V0 }$ ^. v8 L( N9 J
3 选择所用IDE
: |2 @' K8 {& I) M2 M1 R
/ a' j3 ]9 k% ^3 P8 t9 A0 w; z& B" X
6 i1 j7 }# G$ q- x1 z- w
; l& u5 [3 j, j, j7创建工程文件
0 D, n; _% U5 G; x$ i0 C
l& s7 G, ~6 a4 u# \然后点击GENERATE CODE 创建工程" K+ N- [* e9 e% K v% Q" l
% [! v0 ^2 v6 F3 e% f
配置下载工具
2 c5 R x4 F2 _* Z4 J新建的工程所有配置都是默认的 我们需要自行选择下载模式,勾选上下载后复位运行8 T- d. K `+ F- o
" [9 R3 t% t& e. n3 I" T! q
; M% K M- M- t% b9 F1 ?
/ H; N9 ?3 \" R3 }! W) c" b+ S2 N# m
IIC HAL库代码部分6 v6 F2 C* J. R" |: P4 c& B' n
在i2c.c文件中可以看到IIC初始化函数。在stm32f1xx_hal_i2c.h头文件中可以看到I2C的操作函数。分别对应轮询,中断和DMA三种控制方式- I: ?+ I u, y7 l! n: b
" y: T8 G" y! B& N
% l% k, ~# O# a0 s! P! X
1 w- u# g4 ^& l/ m6 }上面的函数看起来多,但是只是发送和接收的方式改变了,函数的参数和本质功能并没有改变& k- [. ^) E/ b3 J0 s0 a) {# N
比方说IIC发送函数 还是发送函数,只不过有普通发送,DMA传输,中断 的几种发送模式
' ~- A% M3 W3 a: |
2 D! K# a7 y: r9 |1 U这里我们仅介绍下普通发送,其他的只是改下函数名即可! u9 Y4 {, y. u) E5 |
4 x$ v$ d! c0 R/ L* ]# t% JIIC写函数
# }: `7 C- B" `; B$ n k9 p4 K g) d# ]1 a( ^$ A
- HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
复制代码
5 W/ _+ h* a* A& u9 p功能:IIC写数据
* q. i' E. o% r- Z |$ ?! B8 X, g! s参数:
5 M$ ?* t7 o/ r* f8 \1 F0 X) v*hi2c 设置使用的是那个IIC 例:&hi2c2
9 J7 s& y! g- `$ U8 }3 |% o) wDevAddress 写入的地址 设置写入数据的地址 例 0xA0
, @# { F9 E: m) R1 D*pData 需要写入的数据
: s2 ?; k; ?3 a6 ?+ ZSize 要发送的字节数
# |% M( D9 s. n4 OTimeout 最大传输时间,超过传输时间将自动退出传输函数
! m! o+ W0 g- D K) c7 L; ^% Z# [4 B. ]$ o+ Z2 @* I# Z" { l8 k
IIC读函数; K( _; p. B& w' f$ C$ w- ?: \' a
+ _8 V( W' P8 s# _& p+ \* Y- HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
复制代码 2 Q# w# P/ m5 r i- j+ Q
功能:IIC读一个字节9 T4 Y( s5 O" m' G5 E: e
参数:
9 r0 g$ J3 q2 ]6 O
5 O* f& D. n4 _" O# A2 S*hi2c: 设置使用的是那个IIC 例:&hi2c25 l) y+ N2 C) h% {. U/ A5 t
# f1 ?% R- h! L1 y! N2 u
DevAddress: 写入的地址 设置写入数据的地址 例 0xA0
3 p3 Q# q" o# p$ F1 d% z+ }5 r2 E$ A# p! N6 _7 }" ], p, V6 z, K3 ]
*pDat:a 存储读取到的数据
5 F# u) E/ ^9 a7 m* A
- {7 j( L8 ^) o% ?. ]' u) D) Q& xSize: 发送的字节数
' M: G1 I, X( u# R; n, R( ]3 ^2 Y6 D0 s5 z; J
Timeout: 最大读取时间,超过时间将自动退出读取函数
" Q; P3 z. v, G9 R, n( ]- h5 m! }# h8 R1 `& v) L) F/ v+ `3 q
举例:# V4 V, h5 R& z* ]; @1 [: G7 j9 B9 x
/ U% L0 u F! W6 K" {: S- HAL_I2C_Master_Transmit(&hi2c1,0xA1,(uint8_t*)TxData,2,1000) ;;
复制代码 $ A* Z y3 k0 x+ z- B1 r% { e3 z, q
发送两个字节数据" Y B; A6 D# x
# k, _ ?0 V8 |7 D; y; n }* O" B
IIC写数据函数9 E& N x' g3 e4 S) p) P
5 ^1 i& J2 e2 V. j1 m- 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);* ?" W6 H7 }4 y. x
- /* 第1个参数为I2C操作句柄' S! O# t; f# N
- 第2个参数为从机设备地址$ \( G8 w- v( N" R( Z
- 第3个参数为从机寄存器地址& ]. c! S. {5 T- o1 C) ?" ]
- 第4个参数为从机寄存器地址长度
1 c; A$ W$ T8 V0 [. X - 第5个参数为发送的数据的起始地址
. m7 i# X( x2 F - 第6个参数为传输数据的大小
$ W* L9 H4 d, @8 d* b - 第7个参数为操作超时时间 */
复制代码
) A' S, H0 N" c% E; C* I$ L功能: IIC写多个数据 该函数适用于IIC外设里面还有子地址寄存器的设备,比方说E2PROM,除了设备地址,每个存储字节都有其对应的地址
7 L+ t7 v* U* B% a7 _0 g3 J. ^9 O/ {* M5 Y
参数:
- n# `7 @1 e( z# Y8 f
# Y. ]: H4 k2 ^1 T7 K+ D. e% F" }*hi2c: I2C设备号指针,设置使用的是那个IIC 例:&hi2c2
4 T- M0 c7 y2 E- h; G8 [0 [4 T9 J D4 T- d4 w, Y
DevAddress: 从设备地址 从设备的IIC地址 例E2PROM的设备地址 0xA0
/ y+ e# Z' h8 u) ?! H |. W/ p/ S* k
6 Y0 G+ U4 O" [; zMemAddress: 从机寄存器地址 ,每写入一个字节数据,地址就会自动+1) W" k9 |1 q8 K2 o) z9 b( _
2 G" {# r S6 v/ w: L( r3 E
MemAddSize: 从机寄存器地址字节长度 8位或16位: @) y) b3 g% C( x4 J* e& u* v- z: N
9 P7 v8 c8 c W y* f
写入数据的字节类型 8位还是16位# y# K5 H3 y- m0 Q/ r
I2C_MEMADD_SIZE_8BIT/ R. o4 h8 w" ` ?* M t! l+ J) t
I2C_MEMADD_SIZE_16BIT
7 U& g }- x! t! }在stm32f1xx_hal_i2c.h中有定义
9 p$ A' \7 e# r2 f
M. L, R6 Z' p2 `$ }* e3 I( L
* R( K+ K, p& X. U1 i+ t
3 m+ z; X; Z2 D# `& ]- z*pData: 需要写入的的数据的起始地址
1 r8 o2 j+ q5 g$ r3 Y. x2 [, N6 z4 E$ H
Size: 传输数据的大小 多少个字节
9 D, Y R p V7 U2 _( N' ^. c
0 U6 X0 J6 J4 ~6 {& G/ e& y2 J; l( J1 tTimeout: 最大读取时间,超过时间将自动退出函数& b4 F+ B2 ~5 W: _( Q% ?
5 H8 q: e* Y# t6 Q9 S p5 O使用HAL_I2C_Mem_Write等于先使用HAL_I2C_Master_Transmit传输第一个寄存器地址,再用HAL_I2C_Master_Transmit传输写入第一个寄存器的数据。可以传输多个数据
, h- M7 w- f/ H9 B5 q$ q& a; {$ [
8 O; ]" X% s# U9 o- void Single_WriteI2C(uint8_t REG_Address,uint8_t REG_data)
0 @- {1 E. |9 W; k* C" u) R% a - { @( e U' p) i# v; \
- uint8_t TxData[2] = {REG_Address,REG_data};
! r* Q: z' Y1 P' ^1 m - while(HAL_I2C_Master_Transmit(&hi2c1,I2C1_WRITE_ADDRESS,(uint8_t*)TxData,2,1000) != HAL_OK)0 N7 b5 U. Z% V
- {0 q. B5 e$ r3 W( ^* M+ C
- if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)
X' v. H8 a1 C& K6 ] n - {
( B* C n2 K1 @! f) J$ T - Error_Handler();
" s4 a4 k9 Q& O3 g0 @1 o7 I - }
D6 E: W" f5 T1 k% G% [ - }
9 g j' v. c# F - }
复制代码
% Y7 C3 d( m( a1 ?: u在传输过程,寄存器地址和源数据地址是会自加的。* q' o, ~0 }7 I
( i# Q: t L5 x至于读函数也是如此,因此用HAL_I2C_Mem_Write和HAL_I2C_Mem_Read,来写读指定设备的指定寄存器数据是十分方便的,让设计过程省了好多步骤。8 Q) i% d8 M- t6 D( m, T4 [
0 x9 y) u V8 @' m0 B! P' f
举例:1 g+ `! B" k* d$ G6 r( z+ ?. J5 y: h
9 w% ~% B3 t! F0 i
8位:- HAL_I2C_Mem_Write(&hi2c2, ADDR, i, I2C_MEMADD_SIZE_8BIT,&(I2C_Buffer_Write<i>),8, 1000);</i>
1 E5 V e5 g6 s7 t+ k1 C+ y
h Z+ h! L6 [- 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>
复制代码
# |3 x9 Y( o- z* I- c16位:
; C. C0 W B0 F2 h- 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);
E/ N5 A E* V$ M - 7 C* u8 s% Y5 L
- HAL_I2C_Mem_Read(&hi2c2, ADDR, i, I2C_MEMADD_SIZE_16BIT,&(I2C_Buffer_Write</span><span style="font-style: normal;">),8, 1000);</span></span>
复制代码 + c' ]0 z/ _2 r$ ]8 Z+ f1 r
如果只往某个外设中写数据,则用Master_Transmit。 如果是外设里面还有子地址,例如我们的E2PROM,有设备地址,还有每个数据的寄存器存储地址。则用Mem_Write。3 S# w8 y q9 z0 W1 s% w8 ]
Mem_Write是2个地址,Master_Transmit只有从机地址0 i& B$ t9 ~+ L/ I0 h9 }
( H: d2 d( \' o' S2 W! y ]
硬件IIC读取AT24C02
: Q" Y0 H5 K& G* V1 w在mian.c文件前面声明,AT24C02 写地址和读地址 ,定义写数据数组,和读数据数组# r( s+ Q4 G7 C A) Z8 o' h, O
, B4 |2 B$ @0 W9 d5 o R
- /* USER CODE BEGIN PV */- Q8 ]' ]3 d+ |5 j$ k J+ {6 a
- #include <string.h>% b! c2 y6 z9 S/ n3 p; p
, q2 e0 r( V2 q- #define ADDR_24LCxx_Write 0xA0
: ~1 X9 c3 j8 H+ E+ L6 u, D- V4 j - #define ADDR_24LCxx_Read 0xA1 H/ v& M0 m+ M' ^) r/ R/ v0 b
- #define BufferSize 256) w6 b+ l+ _( V) ]- ]: \
- uint8_t WriteBuffer[BufferSize],ReadBuffer[BufferSize];
# p8 H, A& D# B1 e2 W: A - uint16_t i; [1 g. b7 W$ f% ?" j* Z
- /* USER CODE END PV */6 q% t+ m4 Z6 p) x& |* q
复制代码 * J5 ^* H: X3 a# A
重新定义printf函数
0 {# m) r, ], `6 q+ z
2 N3 ~: K I; o在 stm32f4xx_hal.c中包含#include <stdio.h>6 d* G/ t; f4 t% P( j& S
! }8 P& ~+ ?. I- _: K
- #include "stm32f4xx_hal.h"
! S7 v4 y, i$ B; |( M - #include <stdio.h>2 A& G) v; B$ V& D. q
- extern UART_HandleTypeDef huart1; //声明串口
复制代码
9 x8 T6 i; |5 \4 f5 N* k1 h在 stm32f4xx_hal.c 中重写fget和fput函数; q6 `6 s4 z$ P: v @4 u* l
5 Y. K0 r/ u' r( _0 f1 H: G7 A4 j5 m- /**
" M5 j2 c6 [5 ]' w# h( R+ { - ' Q- d& b G) N$ _" U7 X% K
- * 函数功能: 重定向c库函数printf到DEBUG_USARTx
1 }: g' j& f }; [ - * 输入参数: 无* U; ~+ `, S U. I
- * 返 回 值: 无
4 `7 R0 Z8 M# \1 o. H/ X" h - * 说 明:无
" Z9 m4 O9 ]! G. M+ c5 @" U! ^4 b o - */! ~( Q2 `% x( ~& u6 J: Q2 Z
- int fputc(int ch, FILE *f)4 b; X$ Y1 Y6 T% ]$ O3 B( M9 R. B; {
- {
1 u7 A9 P8 F' Q - HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
8 E2 L: z# v! u' T* h2 a& F - return ch;
1 t' L- s) a0 T/ K3 z - }) `' |. m* Y$ z! L8 D. x; _2 N9 |
% ~( _& C; `5 h4 N) f+ z3 m0 q* T, C- /**
# \( f/ H4 ^1 O9 N0 K8 q - : C7 `" \$ h) e2 R" C
- * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
5 r/ P* C2 ^. R7 u- _ - * 输入参数: 无
, E7 Q* Y" ]- ?8 n - * 返 回 值: 无8 R3 S6 B) p/ V0 G/ P! i
- * 说 明:无
V6 b" u. D% z$ K4 `3 F- ]( d - */* Y2 V9 k1 j$ w1 H0 l' f6 d
- int fgetc(FILE *f)) A+ P4 i) x h
- {
; ^$ \/ d0 X: p; k - uint8_t ch = 0;
& q$ |* a6 W9 ]0 r! \4 U - HAL_UART_Receive(&huart1, &ch, 1, 0xffff);: |3 {6 J9 _, C7 ^
- return ch;
; B, g. w; c* r8 q8 C/ ~ - }
9 r0 z% B& A# k" r2 `2 b& o
复制代码 ; A9 w; B* ^3 J
在main.c中添加
0 Q8 M$ |, M% F4 ^
" E! M& e: w3 g E- `- r- /* USER CODE BEGIN 2 */
5 u6 z F6 ?* d' i1 G! d. j - for(i=0; i<256; i++)5 |+ x+ J0 z# M0 S& C; J: s
- WriteBuffer<span style="font-style: italic;"><span style="font-style: normal;">=i; /* WriteBuffer init */( D' x$ L& ~# t" w% @# T
% W& {* {) f' N( Z8 O5 x- printf("\r\n***************I2C Example Z小旋测试*******************************\r\n");
, g' m, C% F5 h$ O, I2 A - for (int j=0; j<32; j++)
+ B7 N: L) f$ M, F( H9 b - {0 i% B! B7 L' |
- if(HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, 8*j, I2C_MEMADD_SIZE_8BIT,WriteBuffer+8*j,8, 1000) == HAL_OK)
P& d+ z/ o5 K& `7 w5 V1 \& A - {
5 p3 @- ?7 }( ^" q8 l% O& d - printf("\r\n EEPROM 24C02 Write Test OK \r\n");
" L, ~- g/ W- N9 o2 t* e - HAL_Delay(20);; O9 \3 J9 N! [( G" r6 Z f
- }* [$ F( v9 P% P& _
- else
9 {& b5 L* H; o( m - {
0 q! Z. a( o4 u& t4 K - HAL_Delay(20);4 p/ P) @& {$ }, j- A% s- ]. |
- printf("\r\n EEPROM 24C02 Write Test False \r\n"); S3 L$ i2 N- e6 M
- }+ v6 T# u1 i- W* Q' F* _
- } E5 ?' L/ q2 y" s
- /*/ E3 o; M, X+ I8 D% j V
- // wrinte date to EEPROM 如果要一次写一个字节,写256次,用这里的代码3 h% O i* `& L) X* b, _0 _
- for(i=0;i<BufferSize;i++)7 X4 E4 m7 G: A. z
- {
G/ A2 J1 @6 t+ p - HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, i, I2C_MEMADD_SIZE_8BIT,&WriteBuffer</span><span style="font-style: normal;">,1,0xff);//使用I2C块读,出错。因此采用此种方式,逐个单字节写入- b# k- @# V+ A* c- E4 t
- HAL_Delay(5);//此处延时必加,与AT24C02写时序有关! U( W) l! r7 c. i
- }
% ?" r/ C0 |. v4 Y! ^/ Q- w - printf("\r\n EEPROM 24C02 Write Test OK \r\n");. ?6 P5 R# V9 O
- */. l) ^' m7 F4 W" K+ y% \1 h$ h# x
5 {- o7 ^& [% j* J8 p5 F/ h0 a0 q0 w- HAL_I2C_Mem_Read(&hi2c1, ADDR_24LCxx_Read, 0, I2C_MEMADD_SIZE_8BIT,ReadBuffer,BufferSize, 0xff);
! [3 \) G. G9 c5 F( u5 V - 7 J: _: L k K! v6 Y- S( e
- for(i=0; i<256; i++)
* x, n# w4 G, y2 y - printf("0x%02X ",ReadBuffer</span><span style="font-style: normal;">);: ^- B c- A! e- s
-
; X1 G7 M+ `! r - /* USER CODE END 2 */
c/ Q/ P7 U- u1 z7 U; ?# B+ a - </span></span>
复制代码
& c; b7 P) X! l N注意事项:
7 g7 h% Y6 p4 u' dAT24C02的IIC每次写之后要延时一段时间才能继续写 每次写之后要delay 5ms左右 不管硬件IIC采用何种形式(DMA,IT),都要确保两次写入的间隔大于5ms;* G% ~9 S% C; S& w3 L! y v
读写函数最后一个超时调整为1000以上 因为我们一次写8个字节,延时要久一点
& j7 c' P V( cAT24C02页写入只支持8个byte,所以需要分32次写入。这不是HAL库的bug,而是AT24C02的限制,其他的EEPROM可以支持更多byte的写入。
. Z! M% E( \; k' V8 w' w _( j; Y2 m" p& L/ E* A
当然,你也可以每次写一个字节,分成256次写入,也是可以的 那就用注释了的代码即可- /*
9 S7 M0 [- {$ Q2 n1 V3 v' Y - // wrinte date to EEPROM 如果要一次写一个字节,写256次,用这里的代码
O+ ]5 Y0 `! h2 O. C# W - for(i=0;i<BufferSize;i++)0 r+ ~5 D6 m7 s( r* M0 p
- {
& J4 b; z! W' S - 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块读,出错。因此采用此种方式,逐个单字节写入! I0 n: v( l6 |$ T1 H( u& h
- HAL_Delay(5);//此处延时必加,与AT24C02写时序有关, ~( ~3 }0 _' a: R) i4 J
- }
$ y; b, J* y) Q" `' \ - printf("\r\n EEPROM 24C02 Write Test OK \r\n");
" j0 M; N5 p+ }3 P/ W - */</span></span>
复制代码
8 @8 W" k, {9 n5 Q1 n0 \注意读取AT24C02数据的时候延时也要久一点,否则会造成读的数据不完整, s7 @- m' A* U0 T" x
# h' H" ~# c( M) }
/ m. S5 j# O3 v6 ~) F( X' X+ \
# g& t3 @. {2 g4 O, s: K; u经测试,例程正常
3 C( z2 V" V4 `" |6 {4 ^3 Y9 i Q" _* O; k1 }3 t' D
% X8 [1 @5 h1 t
) F0 f% H p+ N9 L% f- H
|