I2C软件模拟与Delay寄存器延迟函数

环境

芯片:STM32F103ZET6

库:来自HAL的STM32F1XX.H

原理图

有图可知SCL和SDA两条线接到了PB10和PB11

  • Driver_I2C.h

    • #ifndef __DRIVER_I2C
      #define __DRIVER_I2C
      
      #include "stm32f1xx.h"
      #include "Com_Delay.h"
      // 定义拉高SCL引脚的宏操作
      #define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR10)
      // 定义拉低SCL引脚的宏操作
      #define SCL_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR10)
      
      // 定义拉高SDA引脚的宏操作
      #define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR11)
      // 定义拉低SDA引脚的宏操作
      #define SDA_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR11)
      
      // 定义读取SDA引脚状态的宏操作
      #define READ_SDA (GPIOB->IDR & GPIO_IDR_IDR11)
      
      // 定义I2C通信中的短暂延时宏
      #define I2C_DELAY Delay_us(5)
      
      
      // 定义ACK和NACK信号的常量
      #define ACK 0
      #define NACK 1
      
      // 初始化I2C驱动的函数声明
      void Driver_I2C_Init(void);
      
      // I2C的启动信号函数声明,发送启动信号
      void Driver_I2C_Start(void);
      // I2C的停止信号函数声明,发送停止信号
      void Driver_I2C_Stop(void);
      
      // 发送ACK信号的函数声明
      void Driver_I2C_ACK(void);
      // 发送NACK信号的函数声明
      void Driver_I2C_NACK(void);
      
      // 等待并返回ACK信号的函数声明,返回值为接收到的ACK/NACK信号
      uint8_t Driver_I2C_WaitACK(void);
      
      // 发送一个字节数据的函数声明
      void Driver_I2C_SendChar(uint8_t ch);
      
      // 读取一个字节数据的函数声明,返回值为读取到的数据字节
      uint8_t Driver_I2C_ReceiveChar(void);
      #endif
      
  • Driver_I2C.c

    • #include "Driver_I2C.h"
      
      /**
       * I2C驱动初始化函数
       * 
       * 使用软件模拟的I2C,这意味着我们不需要利用STM32的硬件I2C外设
       * 而是通过GPIO的基本功能来实现I2C通信,具体为:
       * - 配置PB10作为SCL
       * - 配置PB11作为SDA
       * 
       * 主要工作步骤:
       * 1. 使能相关时钟
       * 2. 配置GPIO引脚模式
       */
      void Driver_I2C_Init(void)
      {
          // 使能GPIOB时钟,为PB10和PB11的配置做准备
          RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
      
          // 配置PB10和PB11为开漏输出模式,以支持I2C通信的需要
          // 这里通过掩码操作清除了有关配置位,然后设置为开漏输出模式
          GPIOB->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_CNF11_1);
          GPIOB->CRH |= (GPIO_CRH_CNF10_0 | GPIO_CRH_CNF11_0 | GPIO_CRH_MODE10 | GPIO_CRH_MODE11);
      }
      
      /**
       * @brief I2C通信开始函数
       * 
       * 本函数用于初始化I2C通信,并发送起始信号。在I2C通信的开始,
       * 需要确保SDA和SCL引脚都处于高电平状态,然后通过将SDA引脚拉低
       * 而SCL引脚保持高电平来产生起始条件。
       */
      void Driver_I2C_Start(void)
      {
          // 保持初始状态
          SDA_HIGH;
          SCL_HIGH;
          I2C_DELAY;
      
          // 发送起始信号
          SDA_LOW;
          I2C_DELAY;
      }
      
      /**
       * Driver_I2C_Stop函数用于在I2C通信中发送停止信号。
       * 
       * 该函数通过拉低SCL和SDA信号线到低电平,然后在SCL为高电平的时候将SDA线拉高,从而发送一个标准的I2C停止信号。
       * 这个操作确保了在I2C总线上其他设备能够识别到当前传输已经结束。
       */
      void Driver_I2C_Stop(void)
      {
          // 保持初始状态,确保SCL和SDA均为低电平
          SCL_LOW;
          SDA_LOW;
          I2C_DELAY;
      
          // 发送停止信号,当SCL为高电平时,将SDA拉高
          SCL_HIGH;
          I2C_DELAY;
          SDA_HIGH;
          I2C_DELAY;
      }
      
      /**
       * 函数名: Driver_I2C_ACK
       * 功能: 在I2C通信中发送一个ACK信号
       * 描述:
       *   该函数通过控制I2C总线上的SCL(时钟)和SDA(数据)信号来发送一个ACK(Acknowledge)信号。
       *   ACK信号用于响应接收到的字节,表示接收器已成功接收该字节。
       *   函数首先设置数据线SDA为高电平,时钟线SCL为低电平,进入空闲状态。
       *   然后将数据线SDA拉低,开始发送ACK(0)。
       *   随后时钟线SCL被拉高,完成数据的发送。
       *   最后恢复时钟线和数据线到空闲状态。
       * 注意: 该函数使用了特定的宏定义SCL_LOW, SDA_HIGH, SDA_LOW, SCL_HIGH和I2C_DELAY来控制I2C总线信号。
       */
      void Driver_I2C_ACK(void)
      {
          // 设置传输数据时的空闲状态
          SCL_LOW;
          SDA_HIGH;
          I2C_DELAY;
      
          // 发送ACK(0)
          SDA_LOW;
          I2C_DELAY;
      
          // 发送数据,伴随SCL上升沿
          SCL_HIGH;
          I2C_DELAY;
      
          // 恢复时钟线和数据线
          SCL_LOW;
          I2C_DELAY;
          SDA_HIGH;
          I2C_DELAY;
      }
      
      // 函数名称:Driver_I2C_NACK
      // 功能:处理I2C通信中的非应答(NACK)情况
      // 该函数通过设置I2C线路的状态来通知其他设备当前设备不响应
      void Driver_I2C_NACK(void)
      {
          // 保持初始状态
          SCL_LOW;
          SDA_HIGH;
          I2C_DELAY;
      
          // 准备数据
      
          // 发送数据
          SCL_HIGH;
          I2C_DELAY;
      
          // 恢复时钟线和数据线
      }
      
      /**
       * @brief 等待并检测I2C通信中的ACK信号
       * 
       * 此函数用于在I2C通信中发送数据后等待并检测从设备返回的ACK(应答信号)或NACK(非应答信号)。
       * 它通过操纵SCL(时钟线)和SDA(数据线)来同步和检测ACK信号。如果检测到ACK,则返回ACK;
       * 否则,返回NACK,表示从设备未正确接收到数据或未响应。
       * 
       * @return uint8_t 返回ACK表示成功接收到ACK信号,返回NACK表示未接收到ACK信号。
       */
      uint8_t Driver_I2C_WaitACK(void)
      {
          // 将SCL线设置为低电平,准备开始发送或接收数据
          SCL_LOW;
          // 将SDA线设置为高电平,为发送ACK或NACK做准备
          SDA_HIGH;
          // 延时以确保信号稳定
          I2C_DELAY;   
      
          // 将SCL线设置为高电平,使能数据传输
          SCL_HIGH;
          // 延时以确保数据线稳定
          I2C_DELAY;
          // 初始化返回值为ACK,表示准备确认接收到的数据
          uint8_t r = ACK;
          // 检查SDA线的状态,决定是否发送ACK或NACK
          if (READ_SDA)
          {
          // 如果SDA为高电平,则发送NACK,表示未收到预期的ACK
          r=NACK;
          }
      
          // 将SCL线再次设置为低电平,完成此次通信
          SCL_LOW;
          // 延时以确保信号稳定
          I2C_DELAY;
      
          // 返回确认状态,ACK表示成功,NACK表示失败
          return r;
      }
      
      /**
       * 通过I2C协议发送一个字符
       * @param ch 要发送的字符数据
       * 
       * 本函数实现了在I2C总线上发送一个字符的数据过程
       * 它通过操纵SCL和SDA线来发送数据位
       */
      void Driver_I2C_SendChar(uint8_t ch)
      {
          // 设置初始状态
          SCL_LOW;
      
          SDA_HIGH;
          
          I2C_DELAY;
      
          // 从高位到低位依次发送每一位数据
          for (uint8_t i = 0; i < 8; i++)
          {
              // 当前位为1时,拉高SDA线
              if (ch & 0x80)
              {
                  SDA_HIGH;
              }
              else
              {
                  // 当前位为0时,拉低SDA线
                  SDA_LOW;
              }
              I2C_DELAY;
              // 左移数据,准备发送下一位
              ch <<= 1;
      
              // 发送数据,伴随SCL上升沿
              SCL_HIGH;
              I2C_DELAY;
      
              // 恢复状态,拉低SCL线
              SCL_LOW;
      
              I2C_DELAY;
      
              // 准备发送下一个数据位,先拉高SDA线
              SDA_HIGH;
      
              I2C_DELAY;
          }
      }
      /**
       * @brief I2C总线接收一个字符
       * 
       * @return uint8_t 接收到的字符数据
       */
      uint8_t Driver_I2C_ReceiveChar(void)
      {
          uint8_t r = 0;
      
          // 设置初始状态
          SCL_LOW;
          SDA_HIGH;
          I2C_DELAY;
      
          // 从高位到低位依次接收每一位数据
          for (uint8_t i = 0; i < 8; i++)
          {
              // 拉高时钟线,准备接收数据
              SCL_HIGH;
              I2C_DELAY;
      
              // 接收数据
              if (READ_SDA)
              {
                  r |= 0x1;
              }
              I2C_DELAY;
      
              // 除最后一位外,接收的数据左移一位
              if (i < 7)
              {
                  r <<= 1;
              }
      
              // 恢复时钟线
              SCL_LOW;
              I2C_DELAY;
          }
          return r;
      }

延迟函数

  • Com_Delay.h

    • //
      // Created by seven on 2024/8/20.
      //
      
      #ifndef __DELAY_H
      #define __DELAY_H
      
      #include "stm32f1xx.h"
      
      void Delay_us(uint32_t nus);
      void Delay_ms(uint32_t nms);
      
      #endif
      
      
  • Com_Delay.c

    • #include "Com_Delay.h"
      
      /// @brief nus延时
      /// @param nus 延时的nus数
      void Delay_us(uint32_t nus)
      {
          uint32_t temp;
          SysTick->LOAD=nus*168-1; // 计数值加载
          SysTick->VAL=0x00; // 清空计数器
          SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; // 开始计数
          do
          {
              temp=SysTick->CTRL; // 读取控制寄存器状态
          }while((temp&0x01)&&!(temp&(1<<16))); // temp&0x01:定时器使能,!(temp&(1<<16)):定时器计数值不为0
          SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; // 关闭计数
          SysTick->VAL=0x00;// 清空计数器
      }
      
      /// @brief nms延时
      /// @param nus 延时的ms数
      void Delay_ms(uint32_t nms)
      {
          uint32_t repeat=nms/50;
          uint32_t remain=nms%50;
          while(repeat)
          {
              Delay_us(50*1000); // 延时 50 ms
              repeat--;
          }
          if(remain)
          {
              Delay_us(remain*1000); // 延时remain ms
          }
      }
      
      

  • 19
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值