首页 > 其他分享 >I2C软件模拟与Delay寄存器延迟函数

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

时间:2024-08-23 19:51:16浏览次数:12  
标签:SCL DELAY ACK void Delay SDA 寄存器 I2C

环境

芯片: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
          }
      }
      
      

标签:SCL,DELAY,ACK,void,Delay,SDA,寄存器,I2C
From: https://blog.csdn.net/m0_60824353/article/details/141473074

相关文章

  • STM32/ARM-M系列 如何用C语言指针操作寄存器 上篇
    1、操作外设实际上就是操作寄存器使用STM32进行编程,我们一般是用官方提供的库函数(HAL库)来操作各种外设。本质上,每个外设都有自己的一组外设寄存器,操作外设就是操作各种外设寄存器。HAL库的各个库函数就是对他们的寄存器操作的高度抽象后的封装。打开stm32f10x的数据手册的地址......
  • 【论文解读】Macroblock Level Rate Control for Low Delay H.264/AVC based Video Co
    级别:IEEE时间:2015作者:MinGao等机构:哈尔滨工业大学下载:MacroblockLevelRateControlforLowDelayH.264/AVCbasedVideoCommunication摘要算法目的:提出了一种针对低延迟H.264/AVC视频通信的宏块(MB)级别速率控制算法。算法基础:基于ρ域速率模型,该模型涉......
  • STM32F4/M4 波特率寄存器 计数公式
    前言STM32中,USART控制器中的波特率寄存器是可以写入分频数(USARTDIV)小数部分的因此能够更精准地得到我们想要的波特率。波特率:每秒钟传输的二进制代码的位数波特率寄存器位说明 波特率计算公式:其中OVER8通过串口控制寄存器1(USART_CR1第15位来配置它就是用来设......
  • 自动重装影子寄存器
    很痛心这个内容下的帖子都是一大串文字,怎能让人明白,所以先贴图为敬。当ARPE=0,也就是不进行预装载,ARR立刻更新,CNT溢出的值也随之更新,比如ARR从A0更新至B0,而此时CNT的值为90,CNT将不再至A0溢出,而是在B0溢出。当ARPE=1,也就是进行预装载时,ARR任然会立即更新,但是自动重装影子寄存......
  • Xilinx资源浅析之移位寄存器,BRAM,URAM
    移位寄存器SRLC32Eram_based_shifter Xilinx系列FPGA硬核IP,能够有效对移位寄存器进行处理,节省LUT资源1,移位寄存器两种基本数据流1、动态读操作(移位长度不固定)(1)输出Q由5位地址决定(2)每当一个新地址到达时,在经过访问LUT的时间延迟后,输出Q变化(3)读操作是异步的,独立于时钟......
  • AMD Xilinx MPSoC 在分别下载 PL bit文件、PS软件的情况下,PS软件如何访问 PL AXI寄存
    在调试模式下,可以通过JTAG下载MPSoCPL的bit文件,再下载MPSoCPS的软件。这时候,PL已经下载,PS软件应该能够访问PL实现的AXI寄存器。但是PS的软件会卡住。如果使用同样的软件和bit文件,做成boot.bin,在QSPI/SD启动模式下,又一切正常。或者boot.bin里只有PS的软件,启动过程中通过Vivado加......
  • 基于STM32的寄存器实现点亮LED--基于RUST实现
    main.rs#![no_std]#![no_main]usecore::ptr;usecortex_m_rt::entry;usepanic_haltas_;//当发生panic时停止执行//定义寄存器地址constRCC_BASE:u32=0x40021000;constGPIOB_BASE:u32=0x40010C00;constRCC_APB2ENR_OFFSET:u32=0x18;constGPIOB......
  • UART\SPI\I2C的区别与联系
    UART全双工(两根线tx,rx),无时钟线,只能两个设备SPI全双工(两根线tx、rx+时钟线+片选),一主多从,扩展了接入的设备,同步传输,速度更快I2C半双工(一根数据线+时钟线),多主一从或者多主多从UART(UniversalAsynchronousReceiver/Transmitter)全双工:意味着数据可以同时在两个方向上......
  • 基于STM32的寄存器实现点亮LED
    1.启动文件startup_stm32f103xe.s;********************(C)COPYRIGHT2017STMicroelectronics********************;*FileName:startup_stm32f103xe.s;*Author:MCDApplicationTeam;*Description:STM32F103xEDevicesvectort......
  • 汇编语言:call、call far ptr、call word ptr、call dword ptr、call 寄存器
    引言        call指令是转移指令,CPU执行call指令,进行两步操作:(1)将当前IP或当前CS和IP压入栈中(2)转移。call指令不能短转移,除此之外,call指令转移的方法跟jmp指令的原理相同。1.call标号    call标号是根据位移进行进转移的call指令,实现的是段内转移,指令......