<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      關于軟件模擬IIC協議GPIO究竟使用開漏還是推挽輸出(附代碼)

      軟件模擬IIC的一些疑問

      一、為什么選擇軟件模擬?

      現在很多的開發板都支持硬件IIC,尤其筆者經常的接觸的STM32,直接調用相關函數就可以完成數據傳輸而且問題少,不明白為什么不直接使用硬件,反而要自己寫,不是會很麻煩。后來換了開發板,遇到一些bug就漸漸明白了原因了

      1.可移植性強

      并非所有開發板硬件IIC使用方法均一致,不同廠商之間的硬件IIC使用是不同的,比如TI開發板和STM開發板,在需要快速上手開發時軟件模擬無疑是最好的選擇,不論是什么型號的MCU均可以快速移植。

      2.易于拓展

      雖然一條IIC總線支持掛載多個主機與從機,但也有數量限制,掛載設備過多會導致信號噪聲,嚴重影響數據傳輸。若硬件IIC少,就必須依靠軟件模擬IIC擴容

      二、推挽還是開漏?(SDA盡可能使用開漏)

      網絡上有很多模擬IIC的開源代碼,但筆者十分疑惑為什么有人使用開漏輸出,有人使用推挽輸出。筆者早期學習的時候記得很清楚要使用開漏輸出,但在實際操作中發現推挽輸出也是可以進行軟件IIC的。

      1.開漏

      開漏輸出無法真正輸出高電平,即高電平時沒有驅動能力,必須進行外部上拉,需要借助外部上拉電阻完成對外驅動(開漏引腳不連接外部的上拉電阻時,只能輸出低電平)。
      開漏輸出時,P-MOS截止,N-MOS導通,當輸出數據寄存器輸出0時,經過輸出控制變為邏輯1,導通Vss,輸出高電平。反之N-MOS截至,I/O引腳呈現高阻態。很顯然可以看出芯片內部是無法輸出高電平的,只能進行外接上拉電阻依靠外部電平輸出高電平。
      值得注意的是,開漏是不需要對GPIO口進行輸入輸出模式切換的,本身就可直接讀取電平狀況
      開漏輸出有一個優點,通過改變上拉電源的電壓,便可以改變傳輸電平,比如STM32用3.3V供電,將GPIO設置為開漏輸出模式,同時引腳外部接上拉電阻到5V,則高電平時可以拉到5V,不需要接特殊的電平轉換電路或芯片

      2.推挽

      這里類比開漏簡單介紹,推挽不需要依靠外部即可直接輸出高低電平,但是推挽輸出不能在輸出狀態下直接讀取引腳狀態,必須進行模式切換,轉換為輸入模式
      PS:為什么呢,明明推挽輸出時施密特觸發器仍在工作?
      解答:推挽輸出是強輸出電流模式,在此模式下的輸出通道上的推挽結構MOS管,屬于強上拉和強下拉的,這會影響讀取輸入數據寄存器的值,強上拉意味著會將來自外部的低電平輸入強制置高,強下拉意味著會將來自外部的高電平輸入強制置低

      3.推挽可以模擬IIC嗎

      通過查閱資料以及實際操作,推挽是可以用作模擬IIC的,然而需要注意以下問題,并且實際使用體驗較差,可能導致通信緩慢。

      (1)無法實現線與,只能固定連接一個IIC設備

      將多個開漏輸出的IO口,連接到一條線上。通過一只上拉電阻,在不增加任何器件的情況下,形成“與邏輯”關系。這也是I2C,SMBus等總線判斷總線占用狀態的原理。

      (2)注意連接的IIC設備輸出電平大小,與IO口電平容忍情況

      由于使用推挽時判斷從機應答需要從機拉低/拉高SDA,也就是向IO輸入電平,如果從機輸出5V,而該IO口不能容忍5V很大概率會損壞IO口甚至MCU。

      (3)多設備時序沖突問題

      I2C被設計運用在多主機多從機的場景,所以不可避免地會遇到一個問題——總線沖突
      若因某種原因時序混亂導致一個IO口輸出高電平,一個輸出低電平,那么就會出現短路現象,有燒壞設備的風險。

      4.注意事項

      對于模擬IIC,可以SCL使用推挽輸出進行模擬,SDA使用開漏輸出,板載資源并非十分短缺則沒有必要SDA使用推挽輸出。尤其值得注意,只有單主-單從,即整條線上只有一個主機、一個從機,SCL不存在爭搶控制權的情況下,使用推挽輸出模擬SDA才沒有問題。

      5.簡單介紹IIC

      這里筆者還是簡單介紹一下IIC,主要是加深印象。

      d3bbca378362c28829cee243a8497ef7

      IIC協議的數據傳輸主要依靠SCL和SDA兩根線,其中SDA的數據(電平信號變化)僅在SCL高電平期間有效。需要注意,數據的傳遞與接收均為高位在前,低位在后。

      • 開始信號(START/S): SCL為高時,SDA從高到低的跳變產生開始信號
      • 結束信號(STOP/P): SCL為高時,SDA從低到高的跳變產生結束信號
        并且在完成數據接收與發送后主機或者從機都要進行應答(第9位),確認接收成功。
        ??應答信號分為兩種:
        ????1)當第9位(應答位)為 低電平 時,為 ACK???信號
        ????2)當第9位(應答位)為 高電平 時,為 NACK 信號
        值得注意的是,現在很多IIC設備均具有連續讀取寫入功能,即主機或從機發送 ACK信號后,被寫入或讀取的從機會進行地址自增,從而不在重新發送開始信號,很快的提高了效率。

      四、IIC通信中的問題

      對于想要查找IIC通信問題,必須借助邏輯分析儀查看,不然根本無法確認發送的信號是否正確。

      地址錯誤(常見硬件IIC)

      這種問題常見于OLED,例如OLED的IIC地址為0x78,但事實上使用HAL庫的硬件IIC發送地址應該是0x3C | (讀寫位),原因在于IIC設備地址由8位兩部分組成,前七位為固定地址,最后一位為讀寫位。而HAL庫自帶的IIC默認只需要固定地址,讀寫位HAL庫會自動左移添加。所以我們需要人工右移IIC地址來補償HAL庫代碼。

      五、附代碼

      筆者通過定義相應結構體,允許不同的IIC接口調用同一套代碼,減少重復工作。需要注意的是,GPIO_PinState為HAL庫自帶的枚舉結構,需要自己根據實際修改

      /**
       * @file   iic_gpio.h
       * @brief  模擬I2C的功能實現
       * @author 瀚海浮萍
       * @date   8/16/2025
       */
      #include "iic_gpio.h"
      // 定義引腳和端口(SDA SCL)
      Temp_IIC iic1 = {GPIOA,GPIO_PIN_9,GPIOA,GPIO_PIN_8};
      
      /*-------------------------GPIO引入()-------------------------------*/
      
      /* 若使用CubeMX配置完成則不需要初始化 */
      void I2C_GPIO_Init(Temp_IIC *iic)
      {
          GPIO_InitTypeDef GPIO_InitStruct = {0};
      
          // 初始化SCL引腳
          GPIO_InitStruct.Pin = iic->T_SCL_Pin;
          GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
          GPIO_InitStruct.Pull = GPIO_NOPULL;
          GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
          HAL_GPIO_Init(iic->GPIO_SCL, &GPIO_InitStruct);
      
          // 初始化SDA引腳
          GPIO_InitStruct.Pin = iic->T_SDA_Pin;
          GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
          GPIO_InitStruct.Pull = GPIO_NOPULL;
          GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
          HAL_GPIO_Init(iic->GPIO_SDA, &GPIO_InitStruct);
      
          // 將SCL和SDA拉高
          HAL_GPIO_WritePin(iic->GPIO_SCL, iic->T_SCL_Pin, GPIO_PIN_SET);
          HAL_GPIO_WritePin(iic->GPIO_SCL, iic->T_SDA_Pin, GPIO_PIN_SET);
      }
      
      static void Set_High_SCL(Temp_IIC *iic)
      {
            HAL_GPIO_WritePin(iic->GPIO_SCL, iic->T_SCL_Pin, GPIO_PIN_SET);
      }
      static void Set_Low_SCL(Temp_IIC *iic)
      {
            HAL_GPIO_WritePin(iic->GPIO_SCL, iic->T_SCL_Pin, GPIO_PIN_RESET);
      }
      static void Set_High_SDA(Temp_IIC *iic)
      {
            HAL_GPIO_WritePin(iic->GPIO_SDA, iic->T_SDA_Pin, GPIO_PIN_SET);
      }
      static void Set_Low_SDA(Temp_IIC *iic)
      {
            HAL_GPIO_WritePin(iic->GPIO_SDA, iic->T_SDA_Pin, GPIO_PIN_RESET);
      }
      static GPIO_PinState Read_SDA(Temp_IIC *iic)
      {
            return HAL_GPIO_ReadPin(iic->GPIO_SDA, iic->T_SDA_Pin);
      }
      static void Delay_us(volatile uint32_t delay)
      {
          int last, curr, val;
          int temp;
      
          while (delay != 0)
          {
              temp = delay > 900 ? 900 : delay;
              last = SysTick->VAL;
              curr = last - CPU_FREQUENCY_MHZ * temp;
              if (curr >= 0)
              {
                  do
                  {
                      val = SysTick->VAL;
                  }
                  while ((val < last) && (val >= curr));
              }
              else
              {
                  curr += CPU_FREQUENCY_MHZ * 1000;
                  do
                  {
                      val = SysTick->VAL;
                  }
                  while ((val <= last) || (val > curr));
              }
              delay -= temp;
          }
      }
      /*-------------------------協議基礎代碼實現(除引腳枚舉外,無需用戶修改)-------------------------------*/
      // 設置SCL線電平
      static void I2C_Set_SCL(Temp_IIC *iic,GPIO_PinState state)        
      {
          if(state)
              Set_High_SCL(iic);
          else
              Set_Low_SCL(iic);
      }
      // 設置SDA線電平
      static void I2C_Set_SDA(Temp_IIC *iic,GPIO_PinState state)
      {
          if(state)
              Set_High_SDA(iic);
          Set_Low_SDA(iic);
      }
      // 讀取SDA線電平
      static GPIO_PinState I2C_Read_SDA(Temp_IIC *iic)
      {
          return Read_SDA(iic);
      }
      void I2C_Delay(void)
      {
          Delay_us(8);
      }
      //開始信號
      void I2C_Start(Temp_IIC *iic)
      {
          I2C_Set_SDA(iic,GPIO_PIN_SET);
          I2C_Set_SCL(iic,GPIO_PIN_SET);
          I2C_Delay();
          I2C_Set_SDA(iic,GPIO_PIN_RESET);
          I2C_Delay();
          I2C_Set_SCL(iic,GPIO_PIN_RESET);
          I2C_Delay();
      }
      //結束信號
      void I2C_Stop(Temp_IIC *iic)
      {
          I2C_Set_SDA(iic,GPIO_PIN_RESET);
          I2C_Set_SCL(iic,GPIO_PIN_SET);
          I2C_Delay();
          I2C_Set_SDA(iic,GPIO_PIN_SET);
          I2C_Delay();
      }
      
      // 發送一個字節,返回ACK狀態
      IIC_StatusTypeDef I2C_Send_Byte(Temp_IIC *iic,uint8_t byte)
      {
          for (int8_t i = 7; i >= 0; i--)
          {
              GPIO_PinState bit = (byte & (1 << i)) ? GPIO_PIN_SET : GPIO_PIN_RESET;
              I2C_Set_SDA(iic,bit);
              I2C_Delay();
              I2C_Set_SCL(iic,GPIO_PIN_SET);
              I2C_Delay();
              I2C_Set_SCL(iic,GPIO_PIN_RESET);
              I2C_Delay();
          }
      
          // 接收ACK
          I2C_Delay();
          I2C_Set_SCL(iic,GPIO_PIN_SET);
          I2C_Delay();
          GPIO_PinState ack = I2C_Read_SDA(iic);  // 讀取ACK信號
          I2C_Set_SCL(iic,GPIO_PIN_RESET);
          I2C_Delay();
      
          return (ack == GPIO_PIN_RESET) ? IIC_EOK : IIC_ERR;
      }
      
      // 讀取一個字節,并發送ACK或NACK
      uint8_t I2C_Read_Byte(Temp_IIC *iic,GPIO_PinState ack)
      {
          uint8_t byte = 0;
          I2C_Delay();
      
          for (int8_t i = 7; i >= 0; i--)
          {
              I2C_Set_SCL(iic,GPIO_PIN_SET);
              I2C_Delay();
              if (I2C_Read_SDA(iic) == GPIO_PIN_SET)
              {
                  byte |= (1 << i);
              }
              I2C_Set_SCL(iic,GPIO_PIN_RESET);
              I2C_Delay();
          }
      
          // 發送ACK或NACK
          I2C_Set_SDA(iic,ack);  // 發送ACK或NACK
          I2C_Delay();
          I2C_Set_SCL(iic,GPIO_PIN_SET);
          I2C_Delay();
          I2C_Set_SCL(iic,GPIO_PIN_RESET);
          I2C_Delay();
      
          return byte;
      }
      /*-------------------------IIC代碼實現(無需用戶修改,直接調用即可)-------------------------------*/
      /**
       * @brief       I2C向指定地址寫入一字節
       * @param       iic:軟件IIC接口   Addr:設備地址  pData:寫入數據
       * @retval      無
      **/
      IIC_StatusTypeDef I2C_Write_Byte(Temp_IIC *iic, uint8_t Addr, uint8_t pData)
      {
          I2C_Start(iic);
      
          if (I2C_Send_Byte(iic,Addr << 1) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
          if (I2C_Send_Byte(iic,pData) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
      
          I2C_Stop(iic);
          return IIC_EOK;
      }
      /**
       * @brief       模擬I2C寫入函數,類似HAL_I2C_Mem_Write,僅支持8位地址
       * @param       iic:軟件IIC接口   devAddr:設備地址   memAddr:內存地址   pData:寫入數據   size:寫入字節數
       * @retval      無
      **/
      IIC_StatusTypeDef G_I2C_Mem_Write(Temp_IIC *iic, uint8_t devAddr, uint8_t memAddr, uint8_t *pData, uint16_t size)
      {
          I2C_Start(iic);
      
          if (I2C_Send_Byte(iic,devAddr << 1) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
      
          if (I2C_Send_Byte(iic,memAddr) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
      
          for (uint16_t i = 0; i < size; i++)
          {
              if (I2C_Send_Byte(iic,pData[i]) != IIC_EOK)
              {
                  I2C_Stop(iic);
                  return IIC_ERR;
              }
          }
      
          I2C_Stop(iic);
          return IIC_EOK;
      }
      /*
       * @brief       模擬I2C讀取函數,類似HAL_I2C_Mem_Read,僅支持8位地址
       * @param       iic:軟件IIC接口   devAddr:設備地址   memAddr:內存地址   pData:讀出數據存儲   size:讀出字節數
       * @retval      無
      **/
      IIC_StatusTypeDef G_I2C_Mem_Read(Temp_IIC *iic, uint8_t devAddr, uint8_t memAddr, uint8_t *pData, uint16_t size)
      {
          I2C_Start(iic);
      
          if (I2C_Send_Byte(iic, devAddr << 1) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
      
          if (I2C_Send_Byte(iic, memAddr) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
      
          I2C_Start(iic);  // 重新啟動信號
      
          if (I2C_Send_Byte(iic,(devAddr << 1) | 0x01) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
      
          for (uint16_t i = 0; i < size; i++)
          {
              pData[i] = I2C_Read_Byte(iic,(i == size - 1) ? I2C_NACK : I2C_ACK);
          }
      
          I2C_Stop(iic);
          return IIC_EOK;
      }
      
      /*
       * @brief       模擬I2C寫入函數,僅支持16位地址
       * @param       iic:軟件IIC接口   devAddr:設備地址   memAddr:內存地址   pData:寫入數據   size:寫入字節數
       * @retval      無
      **/
      IIC_StatusTypeDef I2C_Mem_Write(Temp_IIC *iic, uint8_t devAddr, uint16_t memAddr, uint8_t *pData, uint16_t size)
      {
          I2C_Start(iic);
      
       if (I2C_Send_Byte(iic,devAddr << 1) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
      
          // 發送高位地址字節
          if (I2C_Send_Byte(iic,(uint8_t)(memAddr >> 8)) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
      
          // 發送低位地址字節
          if (I2C_Send_Byte(iic,(uint8_t)(memAddr & 0xFF)) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
      
          // 發送數據
          for (uint16_t i = 0; i < size; i++)
          {
              if (I2C_Send_Byte(iic,pData[i]) != IIC_EOK)
              {
                  I2C_Stop(iic);
                  return IIC_ERR;
              }
          }
      
          I2C_Stop(iic);
          return IIC_EOK;
      }
      
      /*
       * @brief       模擬I2C讀取函數,僅支持16位地址
       * @param       iic:軟件IIC接口   devAddr:設備地址   memAddr:內存地址   pData:讀出數據   size:讀出字節數
       * @retval      無
      **/
      IIC_StatusTypeDef I2C_Mem_Read(Temp_IIC *iic,uint8_t devAddr, uint16_t memAddr, uint8_t *pData, uint16_t size)
      {
          I2C_Start(iic);
      
          if (I2C_Send_Byte(iic,devAddr << 1) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
      
          // 發送高位地址字節
          if (I2C_Send_Byte(iic,(uint8_t)(memAddr >> 8)) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
      
          // 發送低位地址字節
          if (I2C_Send_Byte(iic,(uint8_t)(memAddr & 0xFF)) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
      
          I2C_Start(iic);  // 重新啟動信號
      
          if (I2C_Send_Byte(iic,(devAddr << 1) | 0x01) != IIC_EOK)
          {
              I2C_Stop(iic);
              return IIC_ERR;
          }
      
          // 接收數據
          for (uint16_t i = 0; i < size; i++)
          {
              pData[i] = I2C_Read_Byte(iic,(i == size - 1) ? I2C_NACK : I2C_ACK);
          }
      
          I2C_Stop(iic);
          return IIC_EOK;
      }
      
      /**
       * @file iic_gpio.h
       * @brief 模擬I2C的功能實現
       * @author 瀚海浮萍
       * @date 8/16/2025
       */
      #ifndef _IIC_GPIO_H_
      #define _IIC_GPIO_H_
      /*-----------------------根據實況進行修改-------------------------------*/
      #include "stm32f1xx_hal.h"
      #define CPU_FREQUENCY_MHZ    72 // STM32時鐘主頻
      /*-----------------------以下無需進行修改-------------------------------*/
      // 定義 I2C ACK 和 NACK
      #define I2C_ACK   GPIO_PIN_RESET
      #define I2C_NACK  GPIO_PIN_SET
      /* IIC結構定義 */
      typedef struct{
          GPIO_TypeDef *GPIO_SDA;
          uint16_t T_SDA_Pin;
          GPIO_TypeDef *GPIO_SCL;
          uint16_t T_SCL_Pin;
      }Temp_IIC;
      typedef enum
      {
          IIC_EOK  = 0x00U,
          IIC_ERR  = 0x01U
      }IIC_StatusTypeDef;
      /*-----------------------用戶直接外部調用-------------------------------*/
      extern Temp_IIC iic1;         
      void I2C_GPIO_Init(Temp_IIC *iic);
      void I2C_Delay(void);
      void I2C_Start(Temp_IIC *iic);
      void I2C_Stop(Temp_IIC *iic);
      uint8_t I2C_Read_Byte(Temp_IIC *iic,GPIO_PinState ack);
      IIC_StatusTypeDef I2C_Send_Byte(Temp_IIC *iic,uint8_t byte);
      
      /*-------------------------用戶API調用-------------------------------*/
      // I2C API函數聲明(向指定地址寫入一字節 僅支持8位地址)
      IIC_StatusTypeDef I2C_Write_Byte(Temp_IIC *iic, uint8_t Addr, uint8_t pData);
      // I2C API函數聲明(僅支持16位地址)
      IIC_StatusTypeDef I2C_Mem_Write(Temp_IIC *iic ,uint8_t devAddr, uint16_t memAddr, uint8_t *pData, uint16_t size);
      IIC_StatusTypeDef I2C_Mem_Read(Temp_IIC *iic ,uint8_t devAddr, uint16_t memAddr, uint8_t *pData, uint16_t size);
      
      // I2C API函數聲明(僅支持8位地址)
      IIC_StatusTypeDef G_I2C_Mem_Write(Temp_IIC *iic, uint8_t devAddr, uint8_t memAddr, uint8_t *pData, uint16_t size);
      IIC_StatusTypeDef G_I2C_Mem_Read(Temp_IIC *iic, uint8_t devAddr, uint8_t memAddr, uint8_t *pData, uint16_t size);
      
      
      #endif /* __IIC_GPIO_H */
      
      
      posted @ 2025-08-07 18:56  瀚海浮萍  閱讀(473)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 亚洲精品一区| 国内自拍视频一区二区三区| 国产精品自产拍在线播放| 欧美大屁股喷潮水xxxx| 亚洲国产欧美一区二区好看电影| 性做久久久久久久久| 欧美肥老太牲交大战| 夜夜躁狠狠躁日日躁| 日韩精品福利一区二区三区| 女人张开腿让男人桶爽| 欧美 变态 另类 人妖| 一区二区不卡99精品日韩| 天天澡日日澡狠狠欧美老妇| 艳妇臀荡乳欲伦交换在线播放| 精品人妻伦一二三区久久aaa片| 午夜福利国产一区二区三区| 一级做a爰片久久毛片下载| 国产精品日韩中文字幕| 欧美激烈精交gif动态图| 精品亚洲国产成人av| 国产毛片基地| 久久国产精品第一区二区| 97人妻人人揉人人躁人人| 国产午夜伦伦午夜伦无码| 国产国拍亚洲精品永久软件 | 精品国产av一区二区三区| 久久精品一区二区东京热| 性欧美VIDEOFREE高清大喷水| 精人妻无码一区二区三区| 成人福利一区二区视频在线| 91精品久久久久久无码人妻| 另类 专区 欧美 制服| 亚洲一区二区三区人妻天堂| 中文字幕日韩国产精品| 米奇亚洲国产精品思久久| 国产精品亚洲二区在线播放| 2020无码专区人妻系列日韩| 成人亚洲国产精品一区不卡| 中文字幕有码高清日韩| 亚洲国产午夜精品福利| 国语做受对白XXXXX在线|