• STM32实战总结:HAL之I2C


    I2C基础知识参考:

    嵌入式常见接口协议总结_路溪非溪的博客-CSDN博客

    电路图

    扩展的I2C接口,可以连接支持I2C的设备。常见于传感器等。

    参考手册

    目前大部分MCU都带有IIC总线接口,STM32F1也不例外。但是这里我们不使用STM32F1的硬件IIC,而是通过软件模拟。原因是ST为了规避飞利浦IIC专利问题,将STM32的硬件IIC设计的比较复杂,且稳定性不怎么好。
    用软件模拟IIC,最大的好处就是方便移植, 同一个代码兼容所有MCU,任何一个单片机只要有IO口,就可以很快的移植过去,而且不需要特定的IO口。而硬件IIC,则换一款 MCU,基本上就得重新搞一次,移植是比较麻烦的,这是我们推荐使用软件模拟IIC的另外一个原因。

    自行阅读参考手册,大概了解下STM32的IIC。

    此处通过SHT30的使用来学习IIC协议。

    电路图如下:

    注意,这里的引脚连接没有连到硬件IIC上。

    所以,就算想用硬件IIC也用不起来。

    STN30

    STN30是一款温湿度传感器芯片,关键性能如下:

    Wide supply voltage range, from 2.4 to 5.5 V

    I2C Interface with communication speeds up to 1MHz and two user selectable addresses Typical accuracy of 2%RH and 0.3°C

    The sensor shows best performance when operated within recommended normal temperature and humidity range of 5 – 60 °C and 20 – 80 %RH

    饮片引脚如下:

     

    设备地址:

    上面的原理图中,ADDR接了地,所以这里的地址是0x44.

    为了提高准确度,该芯片采用多次测量取均值的策略。所以,当重复度越高的时候,所用的时间会越长。

    更多自行阅读手册。

    GPIO配置

    因为是用软件I2C,所以只用配置对应的两个GPIO口,即PG11和PG12。

    要注意的一点是:

    我们在配置时,需要将端口配置成输出或者输入,但是,在I2C工作过程中,既需要输出,又需要输入,通过半双工来完成整个过程。

    那怎么办呢?怎么能让一个IO口既是输出也是输入呢?

    当引脚配置成输出,并且是开漏模式时,引脚对输入的数据也能进行读取。

    不过,读取时需要先输出一个高电平,关闭内部的NMOS管,才能作为输入使用。

    这就是STM32的准双向口。

    关键代码

    I2C.c

    1. /* Includes ------------------------------------------------------------------*/
    2. #include "MyApplication.h"
    3. /* Private define-------------------------------------------------------------*/
    4. //置位与清零SCL管脚
    5. #define SET_SCL HAL_GPIO_WritePin(SHT30_SCL_GPIO_Port,SHT30_SCL_Pin,GPIO_PIN_SET)
    6. #define CLR_SCL HAL_GPIO_WritePin(SHT30_SCL_GPIO_Port,SHT30_SCL_Pin,GPIO_PIN_RESET)
    7. //置位与清零SDA管脚
    8. #define SET_SDA HAL_GPIO_WritePin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin,GPIO_PIN_SET)
    9. #define CLR_SDA HAL_GPIO_WritePin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin,GPIO_PIN_RESET)
    10. //读SDA管脚状态
    11. #define READ_SDA HAL_GPIO_ReadPin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin)
    12. /* Private variables----------------------------------------------------------*/
    13. void Init(void); //I2C初始化
    14. void Start(void); //I2C起始信号
    15. void Stop(void); //I2C停止信号
    16. ACK_Value_t Write_Byte(uint8_t); //I2C写字节
    17. uint8_t Read_Byte (ACK_Value_t); //I2C读字节
    18. /* Public variables-----------------------------------------------------------*/
    19. I2C_Soft_t I2C_Soft =
    20. {
    21. Init,
    22. Start,
    23. Stop,
    24. Write_Byte,
    25. Read_Byte
    26. };
    27. /* Private function prototypes------------------------------------------------*/
    28. static void I2C_Delay_us(uint8_t);
    29. /*
    30. * @name Init
    31. * @brief I2C初始化
    32. * @param None
    33. * @retval None
    34. */
    35. static void Init(void)
    36. {
    37. SET_SCL;
    38. SET_SDA;
    39. }
    40. /*
    41. * @name Start
    42. * @brief I2C起始信号
    43. * @param None
    44. * @retval None
    45. */
    46. static void Start(void)
    47. {
    48. //SCL为高电平,SDA的下降沿为I2C起始信号
    49. SET_SDA;
    50. SET_SCL;
    51. I2C_Delay_us(1);
    52. CLR_SDA;
    53. I2C_Delay_us(10);
    54. CLR_SCL;
    55. I2C_Delay_us(1);
    56. }
    57. /*
    58. * @name Stop
    59. * @brief I2C停止信号
    60. * @param None
    61. * @retval None
    62. */
    63. static void Stop(void)
    64. {
    65. //SCL为高电平,SDA的上升沿为I2C停止信号
    66. CLR_SDA;
    67. SET_SCL;
    68. I2C_Delay_us(1);
    69. I2C_Delay_us(10);
    70. SET_SDA;
    71. }
    72. /*
    73. * @name Write_Byte
    74. * @brief I2C写字节
    75. * @param WR_Byte -> 待写入数据
    76. * @retval ACK_Value_t -> 从机应答值
    77. */
    78. static ACK_Value_t Write_Byte(uint8_t WR_Byte)
    79. {
    80. uint8_t i;
    81. ACK_Value_t ACK_Rspond;
    82. //SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
    83. //数据按8位传输,高位在前,利用for循环逐个接收
    84. for(i=0;i<8;i++)
    85. {
    86. //SCL清零,主机SDA准备数据
    87. CLR_SCL;
    88. I2C_Delay_us(1);
    89. if((WR_Byte&BIT7) == BIT7)
    90. {
    91. SET_SDA;
    92. }
    93. else
    94. {
    95. CLR_SDA;
    96. }
    97. I2C_Delay_us(1);
    98. //SCL置高,传输数据
    99. SET_SCL;
    100. I2C_Delay_us(10);
    101. //准备发送下一比特位
    102. WR_Byte <<= 1;
    103. }
    104. CLR_SCL;
    105. //释放SDA,等待从机应答
    106. SET_SDA;
    107. I2C_Delay_us(1);
    108. SET_SCL;
    109. I2C_Delay_us(10);
    110. ACK_Rspond = (ACK_Value_t)READ_SDA;
    111. CLR_SCL;
    112. I2C_Delay_us(1);
    113. //返回从机的应答信号
    114. return ACK_Rspond;
    115. }
    116. /*
    117. * @name Write_Byte
    118. * @brief I2C写字节
    119. * @param ACK_Value -> 主机回应值
    120. * @retval 从机返回值
    121. */
    122. static uint8_t Read_Byte(ACK_Value_t ACK_Value)
    123. {
    124. uint8_t RD_Byte = 0,i;
    125. 接收数据
    126. //SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
    127. //数据按8位传输,高位在前,利用for循环逐个接收
    128. for(i=0;i<8;i++)
    129. {
    130. //准备接收下一比特位
    131. RD_Byte <<= 1;
    132. //SCL清零,从机SDA准备数据
    133. CLR_SCL;
    134. I2C_Delay_us(10);
    135. //SCL置高,获取数据
    136. SET_SCL;
    137. I2C_Delay_us(10);
    138. RD_Byte |= READ_SDA;
    139. }
    140. //SCL清零,主机准备应答信号
    141. CLR_SCL;
    142. I2C_Delay_us(1);
    143. //主机发送应答信号
    144. if(ACK_Value == ACK)
    145. {
    146. CLR_SDA;
    147. }
    148. else
    149. {
    150. SET_SDA;
    151. }
    152. I2C_Delay_us(1);
    153. SET_SCL;
    154. I2C_Delay_us(10);
    155. //Note:
    156. //释放SDA数据线
    157. //SCL先清零,再释放SDA,防止连续传输数据时,从机错将SDA释放信号当成NACk信号
    158. CLR_SCL;
    159. SET_SDA;
    160. I2C_Delay_us(1);
    161. //返回数据
    162. return RD_Byte;
    163. }
    164. /*
    165. * @name I2C_Delay
    166. * @brief I2C延时
    167. * @param None
    168. * @retval None
    169. */
    170. static void I2C_Delay_us(uint8_t us)
    171. {
    172. uint8_t i = 0;
    173. //通过示波器测量进行校准
    174. while(us--)
    175. {
    176. for(i=0;i<7;i++);
    177. }
    178. }
    179. /********************************************************
    180. End Of File
    181. ********************************************************/

    SHT30.c

    1. /* Includes ------------------------------------------------------------------*/
    2. #include "MyApplication.h"
    3. /* Private define-------------------------------------------------------------*/
    4. /* Private variables----------------------------------------------------------*/
    5. void Measure_Period_Mode(void); //周期测量模式
    6. /* Public variables-----------------------------------------------------------*/
    7. SHT30_t SHT30 =
    8. {
    9. 0.0,
    10. 0,
    11. Measure_Period_Mode
    12. };
    13. /* Private function prototypes------------------------------------------------*/
    14. static uint8_t CRC_8(uint8_t *,uint8_t);
    15. /*
    16. * @name Measure_Period_Mode
    17. * @brief 周期测量模式
    18. * @param None
    19. * @retval None
    20. */
    21. static void Measure_Period_Mode(void)
    22. {
    23. uint8_t temp_array[6] = {0};
    24. uint16_t temp_uint = 0;
    25. float temp_float = 0;
    26. //启动周期性测量
    27. I2C_Soft.Start();
    28. I2C_Soft.Write_Byte(SHT30_ADDR & Write_CMD);
    29. I2C_Soft.Write_Byte(0x27); //High repeat , mps = 10
    30. I2C_Soft.Write_Byte(0x37);
    31. Timer6.SHT30_Measure_Timeout = 0;
    32. //发送接收数据命令
    33. do
    34. {
    35. if(Timer6.SHT30_Measure_Timeout >= TIMER6_2S) //2s内没获取到数据,退出等待
    36. break;
    37. I2C_Soft.Start();
    38. I2C_Soft.Write_Byte(SHT30_ADDR & Write_CMD);
    39. I2C_Soft.Write_Byte(0xE0);
    40. I2C_Soft.Write_Byte(0x00);
    41. I2C_Soft.Start();
    42. }
    43. while(I2C_Soft.Write_Byte(SHT30_ADDR | Read_CMD) ==NACK);
    44. //开始接收测量数据,并计算
    45. if(Timer6.SHT30_Measure_Timeout < TIMER6_2S)
    46. {
    47. temp_array[0] = I2C_Soft.Read_Byte(ACK);
    48. temp_array[1] = I2C_Soft.Read_Byte(ACK);
    49. temp_array[2] = I2C_Soft.Read_Byte(ACK);
    50. temp_array[3] = I2C_Soft.Read_Byte(ACK);
    51. temp_array[4] = I2C_Soft.Read_Byte(ACK);
    52. temp_array[5] = I2C_Soft.Read_Byte(NACK);
    53. I2C_Soft.Stop();
    54. //计算温度,精度0.1
    55. if(CRC_8(temp_array,2) == temp_array[2]) //CRC-8 校验
    56. {
    57. temp_uint = temp_array[0]*256+temp_array[1];
    58. temp_float = ((float)temp_uint)*0.267032-4500;
    59. SHT30.fTemperature = temp_float*0.01;
    60. }
    61. //计算湿度,精度1%RH
    62. if(CRC_8(&temp_array[3],2) == temp_array[5]) //CRC-8 校验
    63. {
    64. temp_uint = temp_array[3]*256+temp_array[4];
    65. temp_float = ((float)temp_uint)*0.152590;
    66. temp_float = temp_float*0.01;
    67. SHT30.ucHumidity = (unsigned char)temp_float;
    68. }
    69. }
    70. }
    71. /*
    72. * @name CRC_8
    73. * @brief CRC-8校验
    74. * @param Crc_ptr -> 校验数据首地址
    75. LEN -> 校验数据长度
    76. * @retval CRC_Value -> 校验值
    77. */
    78. static uint8_t CRC_8(uint8_t *Crc_ptr,uint8_t LEN)
    79. {
    80. uint8_t CRC_Value = 0xFF;
    81. uint8_t i = 0,j = 0;
    82. for(i=0;i<LEN;i++)
    83. {
    84. CRC_Value ^= *(Crc_ptr+i);
    85. for(j=0;j<8;j++)
    86. {
    87. if(CRC_Value & 0x80)
    88. CRC_Value = (CRC_Value << 1) ^ 0x31;
    89. else
    90. CRC_Value = (CRC_Value << 1);
    91. }
    92. }
    93. return CRC_Value;
    94. }
    95. /********************************************************
    96. End Of File
    97. ********************************************************/

  • 相关阅读:
    Tomcat 相关面试题,看这篇!
    golang大小端字节序
    Oracle database 21c 新特性:密码逐步切换策略
    ip地址、LINUX、与虚拟机
    2021年9月电子学会图形化三级编程题解析含答案:接红包游戏
    0401hive入门-hadoop-大数据学习.md
    【力扣】第2710题——移除字符串的尾随零
    (日积月累版)大数据基础知识点1-关系型数据库
    SpringData、SparkStreaming和Flink集成Elasticsearch
    1.1MQ的基本概念,优劣势介绍及 RabbitMQ简介
  • 原文地址:https://blog.csdn.net/qq_28576837/article/details/126881108