|
说到IIC(也叫I2C,其实都是一样的)通讯,是一种最简单的通讯协议。在学习STM32时第一个接触的就是串口USART通讯协议,接下来就是IIC通讯协议了还有的就是SPI协议,SPI我们下一章再说,这一章就说说IIC吧。很多模块都用到过IIC通讯,最常见的就是4针的0.96寸OLED显示屏,当然啦在学习STM32是我们一般最先接触到就是通过IIC来与EEPROM进行通讯,但是本章我们只讲协议本身。 6 z) K& z8 j+ q* z IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与 IC之间进行双向传送,高速IIC总线一般可达 400kbps 以上。
IIC 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。$ F" O' Y3 n; Y8 ]9 b 开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。 结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。2 i& j0 l. E0 d& C4 B) t8 b! d 应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。这些信号中,起始信号是必需的,结束信号和应答信号都可以不要。5 x) [6 z/ u# I9 K' ~. H0 D
IIC使用 SDA信号线来传输数据,使用 SCL信号线进行数据同步。SDA数据线在 SCL的每个时钟周期传输一位数据。传输时,SCL为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL为低电平时,SDA的数据无效,一般在这个时候SDA进行电平切换,为下一次表示数据做好准备。每次数据传输都以字节为单位,每次传输的字节数不受限制。8 t! \( x9 V6 q) @ 如果我们直接控制STM32的两个GPIO 引脚,分别用作 SCL和SDA,按照上述信号的时序要求,直接像控制 LED 灯那样控制引脚的输出(若是接收数据时则读取 SDA电平),就可以实现 IIC通讯。同样假如我们按照 USART的要求去控制引脚,也能实现 USART通讯。所以只要遵守协议,就是标准的通讯,不管您如何实现它,不管是ST生产的控制器还是ATMEL生产的存储器, 都能按通讯标准交互。 由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。相对地,还有“硬件协议”方式,STM32 的 IIC片上外设专门负责实现IIC通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理IIC协议的方式减轻了 CPU 的工作,且使软件设计更加简单。 1.IIC初始化函数) _8 Z* g9 [6 t U+ ]# n' M- j6 b $ l! n6 Y+ j! [" g2 L% X4 }* l' K0 ?4 o & Y* e7 l+ W1 d: D H. P" f+ F 功能:配置IIC的时钟线和数据线 void IIC_Init(void); h9 |/ M7 G' D( Z7 I5 C
因为是软件模拟IIC那么我们选择IIC通讯的引脚就相对来说说比较随意,具体使用的引脚可查阅《STM32F1xx 规格书》,以它为准。这里我们就选择PC11、PC12作为IIC的数据和时钟引脚。设置为推挽输出即可。 2 G( U" T/ w0 s! o3 M 2.起始信号 , a. j/ B0 ~, Y3 P, G5 _! A 功能:CPU 发起 IIC总线启动信号void IIC_Start(void) ( j& Y4 F- b7 A9 _ { IIC_SDA=1; IIC_SCL=1; delay_us(4); IIC_SDA=0;//START:当 CLK 为高电平时,DATA 从高到低改变 delay_us(4); + z* f6 D, G7 e2 L- ~- @ IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 delay_us(4);, p) X+ m7 h i! g% n! ] } 起始信号产生后,所有从机设备就开始等待STM32紧接下来的从机地址信号。在IIC总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。根据IIC协议,从机地址可以是 7位或10位。在地址位之后,是传输方向的选择位,该位为 0时,表示后面的数据传输方向是由主机传输至从机,即主机向从机写数据。该位为 1时,则相反,即主机由从机读数据。$ J' H6 K) Y% M ; x) C; n$ e! \. f 3.等待应答信号' Z& Y ]+ D4 k+ T1 ] c+ `+ j+ k Y2 Z) q 9 K6 C1 o4 J8 I 功能:CPU 产生一个时钟,并读取器件的 ACK 应答信号//返回值:1,接收应答失败 0,接收应答成功 3 m6 t) v- X" l _5 w u8 IIC_Wait_Ack(void)+ C* O4 k" p6 u& A) u1 d9 ^( F' V {" V# D8 `' u# a+ a6 J u8 re; IIC_SDA=1;delay_us(1);//CPU释放SDA总线 IIC_SCL=1;delay_us(1);//CPU驱动SCL=1,此时器件会返回ACK应答3 Z) G0 y! F# { if(READ_SDA){//CPU读取SDA口线状态- C# `) `- `' m( X( z re=1;! [$ C1 O2 L% V8 C- T }else{9 B; ~* I: l) A5 ~1 g re=0; } IIC_SCL=0;//时钟输出0 # Y( M& o6 V6 B return re; : N. p! O# `- k6 ]; E7 I } ) A+ ^- j0 ^2 Z8 f 该函数用于 STM32 作为发送方时,等待及处理接收方传来的响应或非响应信号, 即一般调用前面的 IIC_SendByte 函数后,再调用本函数检测响应。 STM32控制 SDA 信号线输出高阻态,释放它对 SDA的控制权,由接收方控制;控制 SCL 信号线切换高低电平,产生一个时钟信号,根据IIC协议,此时接收方若把 SDA 设置为低电平,就表示返回一个“应答”信号,若 SDA 保持为高电平,则表示返回一个“非应答 ”信号;在 SCL 切换高低电平之间,有个延时确保给予了足够的时间让接收方返回应答信号,延时后使用宏READ_SDA读取 SDA 线的电平,根据电平值赋予 re 变量的值;函数的最后返回 re的值,接收到响应时返回 0,未接收到响应时返回 1。当 STM32 作为数据接收端,调用 IIC_ReadByte函数后,需要给发送端返回应答或非应答信号,此时可使用 IIC_Ack及 IIC_Nack 函数处理,该处理与 IIC_Wait_Ack函数相反,此时 SDA线也由 STM32控制。 / i0 }+ \0 y, ?2 X+ a) j - ?5 C: m, s( c 4.应答信号9 |9 q3 u5 P/ _8 [" f2 x4 Q0 _ 功能: CPU 产生一个 ACK 信号//CPU产生一个ACK信号) n' v9 l' T! v; S7 }3 C , H4 ^, k( g0 J! j; t% a void IIC_Ack(void)0 M% p7 m6 ~* o" S/ C0 A" { { IIC_SDA=0;//CPU驱动SDA=02 l5 Y. y. I" K U# \6 U% ]# b delay_us(2); IIC_SCL=1;//CPU产生一个时钟$ W9 ]$ n/ u3 d" K& P- g; L# E delay_us(2); IIC_SCL=0;5 ]9 V* O) b5 X R V; h$ c4 Z delay_us(2); IIC_SDA=1;//CPU释放SDA总线) g% w! d% ?+ j/ } }& S8 |+ w$ T) O/ M' i //CPU产生1个NACK信号 void IIC_Nack (void) { IIC_SDA=1();//CPU驱动SDA=1& m& |# F I2 e; c0 ~+ F0 m delay_us(2);* C$ W5 A. }3 Y# p IIC_SDA=1;//CPU产生1个时钟/ m L- D% w. s4 Y0 i2 Y1 W+ V delay_us(2); IIC_SCL=0; T9 B! t! y, K9 F6 p0 q3 G8 q delay_us(2); } IIC的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备接收到 IIC 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。/ M8 D1 V2 T& s+ k9 C6 ] 代码的具体流程就是:根据要返回“应答”还是“非应答”信号,先准备好 SDA 线的电平,IIC_Ack函数中把 SDA 线设置为低电平,表示“应答”信号,IIC_Nack 函数中把 SDA 线设置为高电平,表示“非应答”信号;控制 SCL 线进行高低电平切换,产生一个时钟信号,在 SCL 线的高低电平之间加入一个延时,确保有足够的时间让通讯的另一方接收到 SDA信号线的电平;在 IIC_Ack 函数的末尾,响应信号发送结束后,重新把 SDA 线设置为高电平以释放总线的控制权,方便后续的通讯。 * m" \; y* x2 B9 g+ b$ b 3 e. t- N. w8 j" o 5.停止信号: z. B# r4 @, M6 G' D- R. O) w 6 q( S9 L* c7 w0 V/ U; z( w: Y+ V ) D# W/ b$ R! F7 V 功能:CPU 发起IIC总线停止信号{ - M. s+ l) \9 q# a7 o IIC_SDA=0;//STOP:当 CLK 为高电平时候, SDA出现一个上调表示IIC总线停止信号 IIC_SCL=1;9 A8 T5 }6 X1 }1 G9 _ delay_us(4);2 x" w. B. ^% N* B3 C IIC_SDA=1;//发送IIC总线结束信号 }$ [1 z% `( z5 \# D 停止信号直接看是时序图就可以搞定了,在SCL和SDA都为低电平的情况下,首先把时钟线SCL拉高,再把数据线SDA拉高,IIC就会结束传输了。 以上就是软件模拟IIC协议了,在平时的应用中我们实际上不需要掌握这些具体的代码,只要知道IIC协议的过程原理就行了,因为一般来说我们用的都是别人写好的代码,我们只需要会用就可以了,如果你的代码和我这些有出入也没有关系,只要能正常通讯即可,当然如果你的设计在过程中出现了一些问题,或者显示不正常,我们首先考虑的也不是底层协议的问题,而是你代码的其他问题。 H4 n& Z6 i: y( h- f- _& Y6 y " b1 y+ g7 Q$ {( n' U1 \7 k 6.IIC发送字节9 G- F) M1 ]* Z# H \0 x/ i 2 f% \3 g0 r+ [& s' e 功能: CPU向IIC总线设备发送8bit数据void IIC_SendByte(u8 Byte) & f" |( J+ x, `. c% y8 q { u8 i; /* 先发送字节的高位bit7 */ for (i = 0; i < 8; i++) { 3 c7 w1 T/ f2 X% H. D if (Byte & 0x80); M3 D: `1 N1 N; M f {+ D/ a! z! p5 f7 f1 U {7 N: V6 z$ `+ W8 w6 H IIC_SDA=1; }/ {# S9 e# u M+ B$ J2 F7 e else1 J$ p/ N F: \$ N0 A" z {$ Q8 w( T" W' d IIC_SDA=0; }+ a) ?# P$ F" J) S2 W3 i delay_us(2);. G: a7 w5 B8 N! _8 | IIC_SCL=1;% s' j/ B4 h2 @ delay_us(2); IIC_SCL=0; if (i == 7)+ K0 [" j( w8 q y o7 C5 M% h$ P { IIC_SDA=1;// 释放总线0 |$ ^9 ]& U! z+ K } Byte <<= 1; //左移一个bit delay_us(2); } }# Y) B, p/ b; C5 y/ Z4 e. h% H 该函数以其输入参数作为要使用IIC协议输出的数据,该数据大小为一字节。函数的主体是一个8次的 for 循环,循环体执行一次将会对外发送一个数据位,循环结束时刚好发送完该字节数据。步骤分解如下:9 f5 z7 n/ T2 d( ?6 v9 v 首先程序对输入参数Byte和 0x80“与”运算,判断其最高位的逻辑值,为 1 时控制 SDA输出高电平,为 0则控制 SDA输出低电平;接下来延时,以此保证 SDA 线输出的电平已稳定,再进行后续操作;之后控制 SCL线产生高低电平跳变,也就是产生 IIC协议中 SCL线的通讯时钟;在 SCL线高低电平之间有个延时,该延时期间 SCL线维持高电平,根据 IIC协议,此时数据有效,通讯的另一方会在此时读取 SDA 线的电平逻辑,高电平时接收到该位为数据 1,否则为 0;就这样一次循环体执行结束,Byte 左移一位以便下次循环发送下一位的数据;如次循环 8 次,把Byte 中的 8 位个数据位发送完毕,在最后一位发送完成后(此时循环计数器 i=7),控制 SDA 线输出 1(即高阻态),也就是说发送方释放 SDA总线,等待接收方的应答信号。1 N, C$ Z* r3 x7 r `, ^9 i" K9 } 7.IIC读取字节 5 y$ |% T' n, x6 Q4 k2 M 功能: CPU从IIC总线设备读取8bit数据u8_t IIC_ReadByte(void) { u8 i;8 d3 o# x( V3 ^9 N u8 value; //读到第1个bit为数据的bit75 N; w* Z: \; v" a value = 0;' B2 k2 R+ p) u/ g for (i = 0; i < 8; i++) r. q6 M+ U6 C2 ?+ K {2 l5 G& s3 V J+ a a H" ?, h value <<= 1;7 Y& d3 `6 v& M( W( D, r IIC_SCL=1;8 I0 P9 k; c, B+ t4 X delay_us(2);# w5 X$ _! n! I0 v1 k; t7 W if (SDA_READ) {+ g) }$ d; C# S% @! e value++;+ A* ~' O* Q o3 ^ }: g: q1 Q) e# f8 n! F# v IIC_SCL=0; delay_us(2);. W2 K' f* k& {3 G' a } return value;, H2 M) w- a8 V$ n* y# |" z* F }8 ~, l1 g8 B9 G* t! q9 s7 T: ~( v IIC_ReadByte 函数也是以 for 循环为主体,循环体会被执行 8次,执行完毕后将会接收到一个字节的数据,循环体接收数据的流程如下:. I2 [' N( F% R 首先使用一个变量 value 暂存要接收的数据,每次循环开始前先对 value 的值左移 1 位,以给 value 变量的 bit0 腾出空间,bit0 将用于缓存最新接收到的数据位,一位一位地接收并移位,最后拼出完整的 8位数据;然后控制 SCL线进行高低电平切换,输出 IIC 协议通讯用的时钟;在SCL线高低电平切换之间,有个延时,该延时确保给予了足够的时间让数据发送方进行处理,即发送方在 SCL 时钟驱动下通过 SDA 信号线发出电平逻辑信号,而这个延时之后,作为数据接收端的 STM32 使用宏SDA_READ读取 SDA信号线的电平,若信号线为 1,则 value++,即把它的 bit0置 1,否则不操作(这样该位将保持为 0),这样就读取到了一位的数据;接下来SCL线切换成低电平后,加入延时,以便接收端根据需要切换 SDA 线输出数据;直到循环结束后,value 变量中包含有 1 个字节数据,使用 return 把它作为函数返回值返回。 $ E6 d6 H% e# r# L/ I3 ]1 m* ~0 E 三、 硬件协议 # t2 C% e! {- q7 Q. [ 相对来说,硬件IIC直接使用外设来控制引脚,可以减轻 CPU 的负担。不过使用硬件IIC 时必须使用某些固定的引脚作为 SCL 和 SDA,软件模拟IIC则可以使用任意 GPIO 引脚,相对比较灵活。 N1 I' J3 [ Z$ Q : c) [4 j' B! Z. g5 M! R) Y2 H6 { STM32的IIC外设可用作通讯的主机或从机,支持 100Kbit/s 和 400Kbit/s 的速率,支持 7位、10位设备地址,支持 DMA数据传输,并具有数据校验功能。它的IIC外设还支持 SMBus2.0协,SMBus 协议与IIC类似,主要应用于笔记本电脑的电池管理中。9 M, Z* z7 ^. p. j/ r K STM32 芯片有多个IIC外设,它们的IIC通讯信号引出到不同的 GPIO 引脚上,使用时必须配置到这些指定的引脚,GPIO引脚的复用功能,可查阅《STM32F1xx 规格书》,以它为准。
IIC初始化函数 void IIC_init(void) 7 v2 m4 z# |. C$ @. ~$ p {2 K( v" o* }; e GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);$ L0 |; q0 I* y m* [1 I GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; o' \$ |% R. \ GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;// 开漏输出 ( J. V! G( a, J( C$ n( H9 ^ GPIO_Init(GPIOA, &GPIO_InitStructure); `1 O6 C, a8 s( G" L) L4 f8 K% y+ l IIC_SCL=1; IIC_SDA=1;//给一个停止信号, 复位I2C总线上的所有设备到待机模式 } 因为是硬件IIC直接使用外设来控制引脚,那么我们选择IIC通讯的引脚就比较固定,具体使用的引脚可查阅《STM32F1xx 规格书》,以它为准。可以看到PB6和PB7两个引脚可以作为IIC的通讯引脚,而且PB6为SCL时钟线,而PB7则为SDA数据线,并设置为开漏输出。, |% j, K7 P; t9 D X 这里为啥设置为开漏输出的方式呢?/ q Z5 V+ i! g 这是由于使用的是软件模拟IIC方式,而IIC协议的 GPIO 必须的开漏输出模式,开漏输出模式在输出高电平时实际输出高阻态,当IIC该总线上所有设备都输出高阻态时,由外部的上拉电阻上拉为高电平。另外当 STM32 的 GPIO 配置成开漏输出模式时,它仍然可以通过读取GPIO 的输入数据寄存器获取外部对引脚的输入电平,也就是说它同时具有浮空输入模式的功能,因此在后面控制 SDA线对外输出电平或读取 SDA线的电平信号时不需要切换 GPIO的模式。; k( M. A( I2 a+ h, \( H6 g( g 另外在硬件IIC协议之下,它的起始信号、等待应答信号、应答信号、停止信号都与软件模拟IIC协议之下的函数相同,在这里我就不重复说明了。 总结:IIC通讯协议很简单,在实际项目中我们不需要掌握具体的IIC协议代码,只要会用即可,作为最常见且常用的协议,我们最好能够背下来或者有所了解。现在IIC通讯不陌生了吧!' J2 a, J7 f4 v+ v2 D# s4 V |
| 谢谢分享 |
STM32L562DK探索板——串口通讯测试成功
OpenBLT移植到STM32F103战舰开发板上适用于所有STM32F103系列的Bootloader
NUCLEO-H723ZG开发板试用 ——串口点灯测试
MultiButton移植到STM32F103战舰开发板
汇编浮点库qfplib移植STM32F769I-DISCO开发板与硬件浮点运算性能测试对比
【STM32MP157】从ST官方例程中分析RPMsg-TTY/SDB核间通信的使用方法
S2LP 的 sniff 模式如何同时满足通讯和低功耗要求
双证齐发!移远通信通过ISO 26262功能安全流程认证及产品认证
意法半导体 STHV200超声波 IC单片简化设计
了解NFC技术优势与应用场景
微信公众号
手机版