当前位置:   article > 正文

STM32——IIC(I2C)附代码_stm32 i2c demo

stm32 i2c demo

1.什么是IIC

我们可以想象一下,两个芯片该怎么通信呢?这就要用的通信协议(如IIC,USART,SPI等)了,通信协议就是规范数据交换的规则。  I2C(Inter-Integrated Circuit)就是是一种串行通信总线协议,其目的是用于连接集成电路(IC)之间的通信(交换数据)。I2C由飞利浦公司(现在的NXP半导体)于上世纪80年代开发,是一种双向通信协议,需要两根线路:时钟线(SCL)数据线(SDA)。其中:SCL是由主设备提供时钟信号来同步数据传输(SCL的作用)。SDA:用来传输数据的(双向传输)。

I2C协议的设计使得多个设备可以通过同一条总线进行通信,其中每个设备都有一个唯一的地址。这使得微控制器可以轻松地与多个设备进行通信,并能确定与哪个设备进行通信(也称寻址)。

在 I2C总线通信中,有主机(Master)从机(Slave)两种角色。I2C总线中的主机负责控制整个通信过程,发起数据传输请求和驱动时钟信号,而从机则被动地接受并响应主机的请求,进行数据的接收和发送。

下面是拥有IIC外设的芯片,其通信特点是同步,半双工,带数据应答,支持总线挂载多设备(一主多从、多主多从)

通信协议通常包括硬件规定软件规定两个方面:

2.IIC硬件规定

硬件规定包括:硬件如何连接、端口的输入输出模式等。

主机对SCL线:任何时候都完全控制;从机任何时候都不能口直SCL线。

主机对SDA线:在空闲状态下,主机可以主动发起对SDA的控制,只有在从机发送数据与从机应答的时候,主机才会转交SDA的控制权给从机

IIC原理图

所有I2C设备的SCL连在一起,SDA连在一起

SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

设备的SCL和SDA均要配置成开漏输出模式(强下拉),在通过上拉电阻实现弱上拉。

开漏加若上拉模式,同时兼具了输入与输出的功能,如果想输入数据,直接观察电平变化。

在开漏模式下,输出高电平就相当于断开引脚。这么做避免了输入输出的来回切换。

3.IIC软件规定

软件规定包括:时序定义,字节传输、高位or低位先行、一个完整的时序有哪些部分构成等。

IIC时序的基本单元

起始条件

SCL高电平期间,SDA从高电平切换到低电平,即SCL不动,把SDA拽下来,产生一个下降沿,当从机捕获到SCL高电平,SDA下降沿时,就会进行自身复位,等待主句召唤。

终止条件

SCL高电平期间,SDA从低电平切换到高电平

一个完整的数据帧总是以起始条件开始,终止条件结束

起始与终止都是由主机产生,所以在空闲状态时,从机必须双手放开。

发送一个字节

发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。总结:低电平切换(放)数据,高电平读取(读)数据。

接收一个字节

接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后主机释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)。

发送应答

发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据

规则:数据0表示应答,数据1表示非应答。

        发送应答是为了告诉从机:主机是否还要数据(命令从机发不发数据)。

目的:通知从机是否继续发送数据。

接收应答

接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)。

规则:数据0表示应答,数据1表示非应答

从机告诉主机接收到了主机传递的数据了,主机刚发送一个字节,就立刻说有没有人收到数据(SDA高),如果有接收数据就会回应(SDA拉低)。目的:判断从机是否接收到了主机发送的数据。

4.IIC时序

通过拼接基本的时序单元,可以组成读写时序,进而实现芯片之间的通信。

IIC指定地址写时序

对于指定设备地址(Slave Address),在指定寄存器地址(Reg Address)下,写入指定数据(Data)。该图意思为:在0xD0地址的设备中,在0x19寄存器下写入0xAA数据。其中第一个字节(7位地址位+1位读写位,读:1,写:0

在从机中,所有的寄存器被分配到一个线性区域中,且有个单独的指针变量,指示着其中的一个寄存器。上电默认指向0地址,并且每写入一个字节后读出一个字节后,这个指针就会自动自增一次。移动到下一个位置。

注:从机设备可以自己定义第二个字节和后续字节的用途。如用途:寄存器地址;指令控制字;存储器地址等

主机首先发送一个字节(地址),所有从机都会收到这一个字节,和自己的地址进行比较,如果不是自己的地址,则认为主机没有叫它,之后的时序就不用管了

不同型号的芯片的地址都还是不同的,相同型号的芯片地址都是一样的,如果有相同的芯片挂载到同一条总线呢?

        地址中的可变部分,一般器件的最后几位可以在电路中改变的,通过接对应的电路,可以有效的改变地址。

顺序:开始条件→7位从机地址+1为写(0)位→接收从机的应答位(Receive ACK,RA)→发送寄存器地址→从机应答→发送一个字节的数据→从机应答→结束条件。

IIC指定地址读时序

指定地址读后,会接收一个从机应答,之后,总线控制权交给从机。

对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)。该图意思为:在0xD0地址的设备中,读取0x19寄存器中的数据(0xAA)。

顺序:开始条件→7位从机地址+1位写(0)位→从机应答(Receive ACK,RA)→发送寄存器地址→从机应答位→重新开始→7位地址+读(1)位→从机应答(0)→读取一个字节→主机发送非应答(1)→结束。

注:指定地址读写只能跟着起始条件的第一个字节,所以如果想切换读写方向,只能再来个起始条件,因为IC协议规定了起始之后必须是地址。

5.指定地址写多个字节

顺序:开始条件→7位从机地址+1为写(0)位→接收从机的应答位(Receive ACK,RA)→发送寄存器地址→从机应答→发送一个字节的数据→从机应答→发送字节的数据→从机应答→结束条件。可以多次重复绿色的步骤。

只需要将发送字节的数据→从机应答重复即可。

6.指定地址读多个字节

顺序:开始条件→7位从机地址+1为写(0)位→接收从机的应答位(Receive ACK,RA)→发送寄存器地址→从机应答→重新开始→7位地址+读(1)位→发送应答→读取一个字节→主机发送应答读取一个字节→主机发送非应答→结束。可以多次重复绿色的步骤。

补充:寄存器也是存储器,普通的存储器只能写与读,没有实际的意义。寄存器的每一位数据都对应了硬件的电路状态,寄存器与外界的硬件电路是可以互动的

7.IIC外设

数据收发的核心部分是数据寄存器数据移位寄存器。

发送数据(橙色线):

        当我们发送数据时,可以把一个字节的数据写到数据寄存器DR中,当数据移位寄存器没有数据移位时,这个数据寄存器的值会进一步转到移位寄存器里,在移位的过程中,我们就可以直接把下一个数据放到数据寄存器中里等着,一旦前一个数据移位完成,下一个数据就可以无缝衔接继续发送,当数据由数据寄存器转移到数据移位寄存器时,置状态寄存器TXE位为1,表示发送寄存器为。--发送流程。

接收数据(绿色线):

        输入的数据一位一位的从引脚移入到移位寄存器里,当一个字节的数据收齐之后,数据就会整体从移位寄存器转到数据寄存器,同时置标志位RXNE为1,表示接收寄存器非空,这时就可以把数据从数据寄存器读出来。在IIC中数据收发是用一组数据寄存器与数据移位寄存器。通过控制寄存器的对应位控制什么时候收什么时候发。

注:在stm32的IIC中,读与写不是同一个寄存器。

I2C外设,利用硬件电路实现I2C的通信。一般外设寄存器有:控制寄存器CR、数据寄存器DR,状态寄存器SR

CR:控制寄存器:具有控制功能,比如开关灯等(就像,踩油门、打方向盘,控制汽车)

DR:数据寄存器:存储数据,比如汽车运行时产出的速度、温度等

SR:状态寄存器:描述当前状态,比如汽车是否启动等(通过观看仪表盘,了解汽车运行状态)

8.IIC外设简约框图

9.IIC代码(软件模拟IIC)

  1. #include "stm32f10x.h" // Device header
  2. #include "Delay.h"
  3. //参数给1或0,就可以释放或拉低SCL了。
  4. void MyI2C_W_SCL(uint8_t BitValue)
  5. {
  6. GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
  7. Delay_us(10);
  8. }
  9. //写数据
  10. void MyI2C_W_SDA(uint8_t BitValue)
  11. {
  12. GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
  13. Delay_us(10);
  14. }
  15. //读与写不是一个寄存器
  16. //读数据
  17. uint8_t MyI2C_R_SDA(void)
  18. {
  19. uint8_t BitValue;
  20. BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
  21. Delay_us(10);
  22. return BitValue;
  23. }
  24. //IIC的SDA与SCL引脚初始化
  25. void MyI2C_Init(void)
  26. {
  27. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  28. GPIO_InitTypeDef GPIO_InitStructure;
  29. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
  30. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
  31. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  32. GPIO_Init(GPIOB,&GPIO_InitStructure);
  33. //将两根线置为高电平,初始化为开漏输出且释放总线
  34. GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);
  35. }
  36. //开始
  37. void MyI2C_Start(void)
  38. {
  39. MyI2C_W_SDA(1);
  40. MyI2C_W_SCL(1);
  41. MyI2C_W_SDA(0);
  42. MyI2C_W_SCL(0);
  43. }
  44. //结束
  45. void MyI2C_Stop(void)
  46. {
  47. MyI2C_W_SDA(0);
  48. MyI2C_W_SCL(1);
  49. MyI2C_W_SDA(1);
  50. }
  51. //发送单元
  52. void MyI2C_SendByte(uint8_t Byte)
  53. {
  54. uint8_t i;
  55. for(i = 0; i < 8; i++)
  56. {
  57. MyI2C_W_SDA(Byte & (0x80 >> i));
  58. MyI2C_W_SCL(1);
  59. MyI2C_W_SCL(0);
  60. }
  61. }
  62. //接收单元
  63. uint8_t MyI2C_ReceiveByte(void)
  64. {
  65. uint8_t i,Byte = 0x00;
  66. //MyI2C_W_SCL(1);
  67. MyI2C_W_SDA(1);
  68. for (i = 0; i< 8;i++)
  69. {
  70. MyI2C_W_SCL(1);
  71. if (MyI2C_R_SDA() == 1)
  72. {
  73. Byte |= (0x80>>i);
  74. }
  75. MyI2C_W_SCL(0);
  76. }
  77. return Byte;
  78. }
  79. //发送应答
  80. void MyI2C_SendAck(uint8_t AckBit)
  81. {
  82. MyI2C_W_SDA(AckBit);
  83. MyI2C_W_SCL(1);
  84. MyI2C_W_SCL(0);
  85. }
  86. //接收应答
  87. uint8_t MyI2C_ReceiveAck(void)
  88. {
  89. uint8_t AckBit;
  90. //主机释放SDA
  91. MyI2C_W_SDA(1);
  92. MyI2C_W_SCL(1);
  93. AckBit = MyI2C_R_SDA();
  94. MyI2C_W_SCL(0);
  95. return AckBit;
  96. }

10.IIC代码(硬件外设IIC)

  1. #include "stm32f10x.h" // Device header
  2. #include "MPU6050_Reg.h"
  3. #define MPU6050_ADDRESS 0XD0
  4. //带有超时退出机制的WaitEvent函数
  5. void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
  6. {
  7. uint32_t Timeout;
  8. Timeout = 10000;
  9. while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
  10. {
  11. Timeout --;
  12. if (Timeout == 0)
  13. {
  14. break;
  15. }
  16. }
  17. }
  18. //主机给从机写数据(单片机给MPU6050写数据)
  19. void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
  20. {
  21. I2C_GenerateSTART(I2C2,ENABLE);
  22. MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
  23. I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
  24. //在库函数中,发送数据自带接收应答,接收数据自带发送应答。
  25. MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
  26. I2C_SendData(I2C2,RegAddress);
  27. MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);
  28. I2C_SendData(I2C2,Data);
  29. MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);
  30. I2C_GenerateSTOP(I2C2,ENABLE);
  31. }
  32. //主机读从机的数据(单片机读MPU6050的数据)
  33. uint8_t MPU6050_ReadReg(uint8_t RegAddress)
  34. {
  35. uint8_t Data;
  36. I2C_GenerateSTART(I2C2,ENABLE);
  37. MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
  38. I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
  39. //在库函数中,发送数据自带接收应答,接收数据自带发送应答。
  40. MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
  41. I2C_SendData(I2C2,RegAddress);
  42. //问题:如果使用硬件操作I2C,在重复开始条件时,EVENT_MASTER_BYTE_TRANSMITTING这个状态正在发送数据(数据并没有发生完成),但是实验结果显示数据也能正常的接收,这是为什么?
  43. MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);
  44. I2C_GenerateSTART(I2C2,ENABLE);
  45. MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
  46. I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);
  47. MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
  48. I2C_AcknowledgeConfig(I2C2,DISABLE);
  49. I2C_GenerateSTOP(I2C2,ENABLE);
  50. MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);
  51. Data = I2C_ReceiveData(I2C2);
  52. I2C_AcknowledgeConfig(I2C2,ENABLE);
  53. return Data;
  54. }
  55. //获取数据寄存器的函数
  56. void MPU6050_GetData(int16_t *AccX,int16_t *AccY,int16_t *AccZ,
  57. int16_t *GyroX,int16_t *GyroY,int16_t *GyroZ)
  58. {
  59. uint8_t DataH,DataL;
  60. DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
  61. DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
  62. *AccX = (DataH << 8) | DataL;
  63. DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
  64. DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
  65. *AccY = (DataH << 8) | DataL;
  66. DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
  67. DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
  68. *AccZ = (DataH << 8) | DataL;
  69. DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
  70. DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
  71. *GyroX = (DataH << 8) | DataL;
  72. DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
  73. DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
  74. *GyroY = (DataH << 8) | DataL;
  75. DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
  76. DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
  77. *GyroZ = (DataH << 8) | DataL;
  78. }
  79. //初始化MPU6050
  80. void MPU6050_Init(void)
  81. {
  82. //第一步:开启时钟
  83. RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
  84. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  85. //第二步:配置pin引脚为复用开漏输出模式
  86. GPIO_InitTypeDef GPIO_InitStructure;
  87. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
  88. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
  89. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  90. GPIO_Init(GPIOB,&GPIO_InitStructure);
  91. //第三步:初始化I2C外设
  92. I2C_InitTypeDef I2C_InitStructure;
  93. //接收一个字节后是否给从机应答
  94. I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
  95. //stm32从机可以响应几位的地址(stm32作为从机才会用到)
  96. I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
  97. I2C_InitStructure.I2C_ClockSpeed = 10000;
  98. //占空比:这里指的是SCL低电平与高电平的比,因为SCL是弱上拉,所以在快速状态下,SDA波形翻转需要一些时间(低电平需要更多的时间)
  99. I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
  100. I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
  101. //自身地址1(stm32作为从机才会用到),指定stm32的自身地址,方便别的主机呼叫。
  102. I2C_InitStructure.I2C_OwnAddress1 = 0x00;
  103. I2C_Init(I2C2,&I2C_InitStructure);
  104. //第三步:I2C使能
  105. I2C_Cmd(I2C2,ENABLE);
  106. MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
  107. MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
  108. MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
  109. MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
  110. MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
  111. MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
  112. }
  113. //获取MPU6050的ID
  114. uint8_t MPU6050_GetID(void)
  115. {
  116. return MPU6050_ReadReg(MPU6050_WHO_AM_I);
  117. }

以上是利用IIC实现stm32与MPU6050姿态传感器的通信代码。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/894448
推荐阅读
相关标签
  

闽ICP备14008679号