
前言: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 这可能是目前关于STM32CubeMX的硬件iic 讲的最全面和详细的一篇文章之一了 }2 @; K# a5 v. t, Y8 H7 q9 n 所用工具:/ D9 f/ i0 A* j& V2 Z m* p; D 1、芯片: STM32F103ZET6" r) p- N$ |1 y- O0 e8 y8 N " T {! y8 j/ E 2、STM32CubeMx软件 ! 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库 5、IIC: 使用硬件IIC1/ a9 ]4 Z& \- J2 G" X, _' q! J 知识概括:$ j- V' S9 o& e9 w B2 S 通过本篇博客您将学到: ' q$ @) p# K* G$ l7 O IIC的基本原理9 s% p1 t3 s) x8 x! m" g STM32CubeMX创建IIC例程% e( H- U( q4 u1 m2 Y! q1 t 4 k3 o5 ~, [- P$ ~ HAL库IIC函数库 ' }+ E7 C) P/ C AT24C02 芯片原理) U% w) ?2 \) H( f w% G IIC 简介 IIC(Inter-Integrated Circuit)总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。 ! 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总线上的每一个设备都对应一个唯一的地址。 % X2 r+ z& `# H& s% H) f* i ![]() % e3 u& s) B" r$ _ 关于IIC的讲解,已经单独整理了一篇文章: 3 s, L2 \% u2 y 《IIC原理超详细讲解—值得一看》。 如果对IIC还不是太了解的朋友请移步到这篇文章中 IIC起始信号和终止信号:, _% I# w7 r: x6 u 起始信号:SCL保持高电平,SDA由高电平变为低电平后,延时(>4.7us),SCL变为低电平。9 Q3 W5 @) Z3 {) p: z 停止信号:SCL保持高电平。SDA由低电平变为高电平。7 I$ Z0 N2 j" |8 n+ g& Q ![]() 9 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时钟线会频繁的转换电平,以保证数据的传输 - |9 {* d8 ]( F6 p ![]() + n w2 y9 c6 ]2 N1 b- V$ E 应答信号 每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,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,简称应答位),表示接收器已经成功地接收了该字节; 应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。* E) Y c- O: Q! e* L7 s* f) e: ` 1 |6 M6 @# P7 J, z ![]() , {( ~ {* ?; 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表示非应答, / g9 o! |8 w; C+ ~ ![]() 这里我们仅介绍基于AT24C02的IIC通信 以AT24C02为例子2 G# w N8 P* l 24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。在24C02里面有一个8字节的页写缓冲器。 ![]() ) g# n# }# j* t! u6 C" F! I$ T A0,A1,A2:硬件地址引脚! A. u# N, T, ^" J2 F! P WP:写保护引脚,接高电平只读,接地允许读和写 SCL和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容量,也就是说只需要参考图中第一行的内容: ![]() 芯片的寻址: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 也就是说如果是 写24C02的时候,从器件地址为10100000(0xA0); 读24C02的时候,从器件地址为10100001(0xA1)。5 A, a1 P7 g6 Y 4 H3 @# x. P4 R! D) q! D 片内地址寻址: 3 l: I; m0 w$ E6 P3 U3 t0 `( j7 k/ q 芯片寻址可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单位。 对应的修改 A2A1A0 三位数据即可% r3 h& ]$ b- P# P8 v ! d* V1 j6 z: t5 y9 X8 G ![]() 向AT24C02中写数据" e% u" R/ W! m5 P5 V* N ![]() 6 j; u$ S% F# H+ h' {- }$ X! P 操作时序: 1.MCU先发送一个开始信号(START)启动总线 2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)) T, V1 Y% H! A' f+ V+ w 3.等待应答信号(ACK) 4.发送数据的存储地址。24C02一共有256个字节的存储空间,地址从0x00~0xFF,想把数据存储>在哪个位置,此刻写的就是哪个地址。# b* F1 J) l4 ?" B/ y/ T! y6 V 5.发送要存储的数据第一字节、第二字节、…注意在写数据的过程中,E2PROM每个字节都会>回应一个“应答位0”,老告诉我们写E2PROM数据成功,如果没有回应答位,说明写入不成功。 6.发送结束信号(STOP)停止总线" I! i# p3 p- U+ G8 D/ l) \ 注意:, I1 L4 Y P2 \% W$ Y4 t. O 在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址就会溢出又变成0x00。 写数据的时候需要注意,E2PROM是先写到缓冲区,然后再“搬运到”到掉电非易失区。所以这个过程需要一定的时间,AT24C02这个过程是不超过5ms!2 k% m: z- U6 i5 u2 N% p 所以,当我们在写多个字节时,写入一个字节之后,再写入下一个字节之前,必须延时5ms才可以 从AT24C02中读数据/ }0 L; U$ H+ ~! N) k 读当前地址的数据 ![]() 9 V! z& j$ T% K 2、读随机地址的数据 4 p- l5 |* T2 c- n7 @1 m& y ![]() 1.MCU先发送一个开始信号(START)启动总线 2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0) 注意:这里写操作是为了要把所要读的数据的存储地址先写进去,告诉E2PROM要读取哪个地址的数据。 3.发送要读取内存的地址(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不断回应应答信号可以不断读取内存的数据 7.如果不想读了,告诉E2PROM不想要数据了,就发送一个“非应答位NAK(1)”。发送结束信号(STOP)停止总线 ' ~- t( J" n6 g- H8 \( D' [' q 3、连续读数据 ![]() E2PROM支持连续写操作,操作和单个字节类似,先发送设备写操作地址(DEVICE ADDRESS),然后发送内存起始地址(WORD ADDRESS),MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据。E2PROM的地址指针会自动递增,数据会依次保存在内存中。不应答发送结束信号后终止传输。 基于CubeMx的讲解 1设置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 ![]() 2 IIC设置 # N, @7 i+ @* Y6 w- |8 f ![]() 点击I2C1 设置为I2C 因为我们的硬件IIC 芯片一般都是主设备,也就是一般情况设置主模式即可! X" ~3 d1 p, Q* Z* {$ T+ F Master features 主模式特性 - 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 Clock No Stretch Mode: 时钟没有扩展模式! n2 K8 f+ A+ I, n ` IIC时钟拉伸(Clock stretching)/ j* ?$ I- U, K, Z clock stretching通过将SCL线拉低来暂停一个传输.直到释放SCL线为高电平,传输才继续进行.clock stretching是可选的,实际上大多数从设备不包括SCL驱动,所以它们不能stretch时钟. Primary Address Length selection: 从设备地址长度 设置从设备的地址是7bit还是10bit 大部分为7bit -Dual Address Acknowledged: 双地址确认9 ^$ s! ?8 X5 _$ p& m ' m& n' P& C3 y T1 z( v: R Primary slave address: 从设备初始地址 这里我们保持默认即可 3 串口设置* _. x* \* L6 ] T 4 W8 n5 c/ [' |2 U& s: ` ![]() ( 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 时钟源设置 x" P& ]' v8 p1 I ![]() # z4 x0 C. r2 `1 W& A& S# k/ C 我的是 外部晶振为8MHz7 m# Z; S" s$ v& r" A. f 1选择外部时钟HSE 8MHz 2PLL锁相环倍频9倍; f0 M- I3 p# v% T 3系统时钟来源选择为PLL 4设置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" [ ![]() ( }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 ![]() 7创建工程文件 " _ P2 N ?9 d2 s 然后点击GENERATE CODE 创建工程 配置下载工具& U. {9 i: c; U 新建的工程所有配置都是默认的 我们需要自行选择下载模式,勾选上下载后复位运行 ![]() IIC HAL库代码部分 在i2c.c文件中可以看到IIC初始化函数。在stm32f1xx_hal_i2c.h头文件中可以看到I2C的操作函数。分别对应轮询,中断和DMA三种控制方式: s- q/ q' B$ n' \# d) s% p: k% g" y ![]() , P1 ]1 c8 ?: `8 P s" `3 l. O& U* f 上面的函数看起来多,但是只是发送和接收的方式改变了,函数的参数和本质功能并没有改变0 F* l# N8 \& m' j" @( h6 G8 k" b 比方说IIC发送函数 还是发送函数,只不过有普通发送,DMA传输,中断 的几种发送模式 " p% B& I) T% p K0 l; Q 这里我们仅介绍下普通发送,其他的只是改下函数名即可 IIC写函数" B. Y' v: N8 Y) m7 q 3 S6 d+ y5 t6 q! {7 q; m, _8 |# T. \/ N
功能: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 *pData 需要写入的数据 Size 要发送的字节数 Timeout 最大传输时间,超过传输时间将自动退出传输函数. 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' |
功能:IIC读一个字节$ b7 o/ E3 ]* o7 Y 参数:1 M0 N, e5 O/ c3 _( e7 T. } 3 R) g& G8 ~! Y. n *hi2c: 设置使用的是那个IIC 例:&hi2c2 DevAddress: 写入的地址 设置写入数据的地址 例 0xA0" p" j7 }9 f$ N# C + e5 S# m" q3 j2 R *pDat:a 存储读取到的数据3 @8 p/ t. w& {# O Size: 发送的字节数, X5 u2 ^. Q/ V4 s Timeout: 最大读取时间,超过时间将自动退出读取函数 举例: # B2 m- O* o5 j4 l; `. z
发送两个字节数据& v4 ~' z# |3 l% @) V k IIC写数据函数- H. T9 X+ \; _+ z& Z$ o
功能: IIC写多个数据 该函数适用于IIC外设里面还有子地址寄存器的设备,比方说E2PROM,除了设备地址,每个存储字节都有其对应的地址+ Z/ v& G" A, O / Z3 g" G$ l u- `: ?. @3 r 参数: *hi2c: I2C设备号指针,设置使用的是那个IIC 例:&hi2c24 E6 Z3 e4 v; I( E$ z DevAddress: 从设备地址 从设备的IIC地址 例E2PROM的设备地址 0xA0& F" j' ~* _$ k/ A# O8 ?( q2 N ; T7 B- q4 w D* ]1 h. @5 ?& t MemAddress: 从机寄存器地址 ,每写入一个字节数据,地址就会自动+1 0 c- Q$ p9 L% D. d* [& \ MemAddSize: 从机寄存器地址字节长度 8位或16位 & Q. K" p0 ^3 E4 F1 M+ u6 Y 写入数据的字节类型 8位还是16位 I2C_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 ![]() ; o4 l# Z e. |" F" k! X) d0 A *pData: 需要写入的的数据的起始地址! L2 ~' d, J! s4 Z5 k 1 i9 F( B1 A( g; `; Z" _- { Size: 传输数据的大小 多少个字节 % o" T' Y0 v2 K) J Timeout: 最大读取时间,超过时间将自动退出函数 / I4 \' x) n$ {( ?7 z 使用HAL_I2C_Mem_Write等于先使用HAL_I2C_Master_Transmit传输第一个寄存器地址,再用HAL_I2C_Master_Transmit传输写入第一个寄存器的数据。可以传输多个数据 ! l, y( R" g3 `1 v8 b7 ?
在传输过程,寄存器地址和源数据地址是会自加的。3 n& A7 m; I N4 N( M 至于读函数也是如此,因此用HAL_I2C_Mem_Write和HAL_I2C_Mem_Read,来写读指定设备的指定寄存器数据是十分方便的,让设计过程省了好多步骤。. V0 a; N( f% j* h 举例: / D& P- X3 S- P* G5 D% R. k; _ 8位:
16位:6 y4 e1 E# O. V6 y5 N* Q
如果只往某个外设中写数据,则用Master_Transmit。 如果是外设里面还有子地址,例如我们的E2PROM,有设备地址,还有每个数据的寄存器存储地址。则用Mem_Write。" d, E3 v4 w6 q) k4 G Mem_Write是2个地址,Master_Transmit只有从机地址 硬件IIC读取AT24C023 S% d6 P V' [; N% i* a 在mian.c文件前面声明,AT24C02 写地址和读地址 ,定义写数据数组,和读数据数组" G% X& D6 q9 f 5 c. W5 E( m% ] a
重新定义printf函数 ! 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
在 stm32f4xx_hal.c 中重写fget和fput函数! j$ i5 Z. p/ m, t # Z! v1 p7 |0 C7 A; c
在main.c中添加# u8 R' B1 e8 y7 O) k: m4 @
注意事项: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的写入。 当然,你也可以每次写一个字节,分成256次写入,也是可以的 那就用注释了的代码即可
注意读取AT24C02数据的时候延时也要久一点,否则会造成读的数据不完整/ p+ _8 A C2 |' j3 H2 F# X& A% z: L E5 N 1 y% N2 E8 Y J" U" l- V ![]() 经测试,例程正常) |9 s3 F( B2 r! D; C9 I * k" ?8 s0 i6 E! k; ? ![]() |
【2025·STM32峰会】GUI解决方案实训分享2-编译运行TouchGFX咖啡机例程(含桌面仿真)
实战经验 | Keil工程使用NEAI库的异常问题
STM32 ISP IQTune:真正零门槛的免费ISP调整软件
【经验分享】STM32 新建基于STM32F40x 固件库的MDK5 工程
意法半导体MCU双供应链策略,打消中国客户后顾之忧
【经验分享】基于STM32使用HAL库实现USB组合设备CDC+MSC
2024意法半导体工业峰会:赋能智能电源和智能工业,构筑可持续未来
ST推出灵活、面向未来的智能电表通信解决方案,助力能源转型
意法半导体 x Qu-Bit Electronix:推动新一轮的数字声音合成革命
从STM32 MPU产品看嵌入式系统中微处理器的新变化