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

基于STM32 的IIC通讯原理及其实验经验分享

[复制链接]
攻城狮Melo 发布时间:2023-10-19 16:29
I2C两线式串行总线通讯协议,它是由飞利浦开发的,主要用于连接微控制器及其外围设备之间,它是由数据线SDA和信号线SCL构成的,可发送和接收数据即在MUC和I2C设备之间,I2C和I2C之间进行全双工信号传输,高速I2C总线一般可达到400kbps。一般我们也称为TWI接口。

I2C支持多主机模式:


微信图片_20231019162807.png

即在这个主线上可以挂载n个I2C外设。

对于I2C协议,其实也很简单,不要想的那么复杂,其实就是电平的变换。我们可以人为的分为6个部分

整体时序图:


微信图片_20231019162810.png

各状态:
  • 空闲状态



微信图片_20231019162813.png

I2C总线的SCK和SDA两个线同时处于高电平的时候,规定为总线的空闲状态,这个就是由总线上的上拉电阻把电平拉高的。

  • 起始信号


微信图片_20231019162816.png

当SCL为高电平期间,SDA由高电平变成低电平,即为起始信号。启动信号是一种电平跳变时序信号,不是一个电平信号。

  • 停止信号

当SCL为高电平期间,SDA由低电平变为高电平,即为停止信号。停止信号也是一种电平跳变时序信号,不是一个电平信号。

  • 应答信号



微信图片_20231019162819.png

发送器每发送一个字节(8bit)数据,就在时钟脉冲(SCL)9期间释放数据线(SDA),再由接收器来反馈一个应答信号,应答信号为低电平的时候,规定为有效应答位(ACK:应答位),表明接收器已经成功的接收了该字节,应答信号为高电平时,规定为非应答位(NACK:非应答位),表示接收器没有成功的接收该字节。

对于反馈有效应答位(ACK):接收器在第9个时钟脉冲之前的低电平期间将SDA拉低,并且保证在该时钟的高电平期间,SDA为稳定的低电平。大家主要看图,看看是不是这样的。

要是接收器是主控器,那么它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据的发送,并且释放SDA线,以便主控接收器发送一个停止信号。

  • 数据的有效性



微信图片_20231019162822.png


时钟信号(SCL)为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号(SCL)为低电平期间,数据线上的高电平或者低电平才能发生变化。

数据必须在时钟信号(SCL)的上升沿到来之前就准备好,并且在数据信号的下降沿来到之前必须稳定。

  • 数据的传输

在SDA上的每一个位的数据的传输都需要一个时钟脉冲,即在SCL串行时钟的配合下,SDA上逐位的串行发送每一位数据。数据位的传输是边沿触发。

示例代码讲解
  • 初始化IIC



微信图片_20231019162824.png


其实就是对两个线的初始化,我这里使用的是PA6和PA7,开始都设置为输出,中途会改变PA7的输入输出属性,在空闲状态,我们知道SCL和SDA是被拉高的,所以这个地方我们给高电平。

  • 产生IIC起始信号



微信图片_20231019162827.png

微信图片_20231019162830.png

将SDA线设置为输出,然后SDA和SCL都设置为高电平,延迟4us,然后将SDA拉低,延迟4us,最后将SCL拉低。这其实就是协议规定的动作了。

  • 产生IIC停止信号



微信图片_20231019162834.png


同样的道理,和协议的时序保持一致就好了。

  • 等待应答信号到来



微信图片_20231019162837.png

首先我们需要把SDA设置为输入,因为接收方要给发射方返回一个应答信号的。就是在SCL第9个高电平的时候,释放信号线,先拉高,然后持续等待,是不是有应答信号返回,其实就是返回一个低电平,所以我们一直在检测READ_SDA这个电平,持续一段时间,要是没有返回的话,我们认为超时了,就直接停止协议了,

  • 产生应答信号



微信图片_20231019162842.png

即在第9个时钟周期内,SDA都为低电平,为应答

  • 不产生应答信号



微信图片_20231019162851.png


即在第9个时钟周期内,SDA都为高电平,为不应答

  • IIC发送一个字节



微信图片_20231019162856.png

微信图片_20231019162858.png

发送数据,SDA设置为输出。SCL拉低,SDA准备。
做一个8次循环,拿出1byte的数据,将你发送的数据和0x80作与运算,拿出最高位,然后右移7位,将这个数据放到最低位,这个数据要是1的话,那么SDA输出一个高电平,要是与后的结果为低电平的话,那么SDA输出一个低电平。这都属于准备发送信号阶段。
然后SCL拉高,完成数据的发送,然后SCL拉低,此时SDA也就可以拉低了,接着开始次高位的传输,直到传输完成。

  • 读取数据


微信图片_20231019162903.png


读取数据,SDA要设置为输入了。SCL开始为低电平,然后SCL为高电平,我们开始读SDA上的数据,然后左移数据,将读取的数据放在低位。然后检测一下有没有应答。
其实基本思路就是这样了。我把源码附上:

i2c.h
  1. #ifndef __MYIIC_H
  2. #define __MYIIC_H
  3. #include "sys.h"
  4. #include "stm32f10x_gpio.h"
  5. //IO方向设置
  6. #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
  7. #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
  8. //IO操作函数
  9. #define IIC_SCL PBout(6) //SCL
  10. #define IIC_SDA PBout(7) //SDA
  11. #define READ_SDA PBin(7) //输入SDA
  12. //IIC所有操作函数
  13. void IIC_Init(void); //初始化IIC的IO口
  14. void IIC_Start(void); //发送IIC开始信号
  15. void IIC_Stop(void); //发送IIC停止信号
  16. void IIC_Send_Byte(u8 txd); //IIC发送一个字节
  17. u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
  18. u8 IIC_Wait_Ack(void); //IIC等待ACK信号
  19. void IIC_Ack(void); //IIC发送ACK信号
  20. void IIC_NAck(void); //IIC不发送ACK信号
  21. void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
  22. u8 IIC_Read_One_Byte(u8 daddr,u8 addr);
  23. #endif
复制代码

i2c.c
  1. #include "myiic.h"
  2. #include "delay.h"
  3. //初始化IIC
  4. void IIC_Init(void)
  5. {                                         
  6.         GPIO_InitTypeDef GPIO_InitStructure;
  7.         RCC_APB2PeriphClockCmd(        RCC_APB2Periph_GPIOB, ENABLE );        //使能GPIOB时钟
  8.          
  9.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
  10.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
  11.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  12.         GPIO_Init(GPIOB, &GPIO_InitStructure);
  13.         GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);         //PB6,PB7 输出高
  14. }
  15. //产生IIC起始信号
  16. void IIC_Start(void)
  17. {
  18.         SDA_OUT(); //sda线输出
  19.         IIC_SDA=1;                  
  20.         IIC_SCL=1;
  21.         delay_us(4);
  22.         IIC_SDA=0;//START:when CLK is high,DATA change form high to low
  23.         delay_us(4);
  24.         IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
  25. }         
  26. //产生IIC停止信号
  27. void IIC_Stop(void)
  28. {
  29.         SDA_OUT();//sda线输出
  30.         IIC_SCL=0;
  31.         IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
  32.         delay_us(4);
  33.         IIC_SCL=1;
  34.         IIC_SDA=1;//发送I2C总线结束信号
  35.         delay_us(4);                                                                
  36. }
  37. //等待应答信号到来
  38. //返回值:1,接收应答失败
  39. // 0,接收应答成功
  40. u8 IIC_Wait_Ack(void)
  41. {
  42.         u8 ucErrTime=0;
  43.         SDA_IN(); //SDA设置为输入
  44.         IIC_SDA=1;delay_us(1);         
  45.         IIC_SCL=1;delay_us(1);         
  46.         while(READ_SDA)
  47.         {
  48.                 ucErrTime++;
  49.                 if(ucErrTime>250)
  50.                 {
  51.                         IIC_Stop();
  52.                         return 1;
  53.                 }
  54.         }
  55.         IIC_SCL=0;//时钟输出0          
  56.         return 0;
  57. }
  58. //产生ACK应答
  59. void IIC_Ack(void)
  60. {
  61.         IIC_SCL=0;
  62.         SDA_OUT();
  63.         IIC_SDA=0;
  64.         delay_us(2);
  65.         IIC_SCL=1;
  66.         delay_us(2);
  67.         IIC_SCL=0;
  68. }
  69. //不产生ACK应答                 
  70. void IIC_NAck(void)
  71. {
  72.         IIC_SCL=0;
  73.         SDA_OUT();
  74.         IIC_SDA=1;
  75.         delay_us(2);
  76.         IIC_SCL=1;
  77.         delay_us(2);
  78.         IIC_SCL=0;
  79. }                                                                          
  80. //IIC发送一个字节
  81. //返回从机有无应答
  82. //1,有应答
  83. //0,无应答                         
  84. void IIC_Send_Byte(u8 txd)
  85. {
  86. u8 t;
  87.         SDA_OUT();          
  88. IIC_SCL=0;//拉低时钟开始数据传输
  89. for(t=0;t<8;t++)
  90. {
  91. //IIC_SDA=(txd&0x80)>>7;
  92.                 if((txd&0x80)>>7)
  93.                         IIC_SDA=1;
  94.                 else
  95.                         IIC_SDA=0;
  96.                 txd<<=1;          
  97.                 delay_us(2);
  98.                 IIC_SCL=1;
  99.                 delay_us(2);
  100.                 IIC_SCL=0;       
  101.                 delay_us(2);
  102. }         
  103. }          
  104. //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
  105. u8 IIC_Read_Byte(unsigned char ack)
  106. {
  107.         unsigned char i,receive=0;
  108.         SDA_IN();//SDA设置为输入
  109. for(i=0;i<8;i++ )
  110.         {
  111. IIC_SCL=0;
  112. delay_us(2);
  113.                 IIC_SCL=1;
  114. receive<<=1;
  115. if(READ_SDA)receive++;
  116.                 delay_us(1);
  117. }                                         
  118. if (!ack)
  119. IIC_NAck();//发送nACK
  120. else
  121. IIC_Ack(); //发送ACK
  122. return receive;
  123. }
复制代码

转载自:电源研发精英圈
如有侵权请联系删除


收藏 评论0 发布时间:2023-10-19 16:29

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版