硬汉嵌入式论坛

 找回密码
 立即注册
查看: 901|回复: 11
收起左侧

[I2C] STM32的硬件I2C时序配置算法

[复制链接]

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106678
QQ
发表于 2023-8-25 14:18:57 | 显示全部楼层 |阅读模式



I2C的硬件时序配置非常麻烦,涉及到好几个参数

所以现在基本都是CubeMX来实现,不需要用户自己倒腾了。

下载 (3).png


如果要用户自己倒腾怎么办,CMSIS-Driver里面提供了计算方法:

[C] 纯文本查看 复制代码
/* -----------------------------------------------------------------------------
 * Copyright (c) 2013-2022 Arm Limited (or its affiliates). All 
 * rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the License); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * [url]www.apache.org/licenses/LICENSE-2.0[/url]
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an AS IS BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *
 * $Date:        11. February 2022
 * $Revision:    V1.4
 *
 * Driver:       Driver_I2C1, Driver_I2C2, Driver_I2C3, Driver_I2C4,
 *               Driver_I2C5
 * Configured:   via STM32CubeMx configuration tool
 * Project:      I2C Driver for ST STM32H7xx
 * --------------------------------------------------------------------------
 * Use the following configuration settings in the middleware component
 * to connect to this driver.
 *
 *   Configuration Setting                 Value   I2C Interface
 *   ---------------------                 -----   -------------
 *   Connect to hardware via Driver_I2C# = 1       use I2C1
 *   Connect to hardware via Driver_I2C# = 2       use I2C2
 *   Connect to hardware via Driver_I2C# = 3       use I2C3
 *   Connect to hardware via Driver_I2C# = 4       use I2C4
 *   Connect to hardware via Driver_I2C# = 5       use I2C5
 * --------------------------------------------------------------------------
 * Note:
 *  DMA transfers are disabled. IRQ transfer will be performed even if
 *  DMA channels for I2C are configured in STM32CubeMX.
 * -------------------------------------------------------------------------- */

/* History:
 *  Version 1.4
 *    - Added I2C5 interface accessed by Driver_I2C5
 *  Version 1.3
 *    - Corrected pin configuration: MX_I2Cx_yyy_GPIO_PuPd replaced with MX_I2Cx_yyy_GPIO_Pu
 *    - Disabled DMA transfers
 *  Version 1.2
 *    - Synchronized to HAL V1.10.0 (HAL_I2C_Master_Seq_Receive_IT() and HAL_I2C_Slave_Seq_Receive_IT())
 *  Version 1.1
 *    Master clock frequency generation corrected
 *  Version 1.0
 *    Initial release
 */
 
 /*! \page stm32h7_i2c CMSIS-Driver I2C Setup 

The CMSIS-Driver I2C requires:
  - Setup of I2Cx input clock
  - Setup of I2Cx in I2C mode with optional DMA for Rx and Tx transfers <b> (DMA transfers are disabled in current driver)</b>
 
The example below uses correct settings for STM32H743I-EVAL:
  - I2C1 Mode: I2C

For different boards, refer to the hardware schematics to reflect correct setup values.

The STM32CubeMX configuration steps for Pinout, Clock, and System Configuration are 
listed below. Enter the values that are marked \b bold.
   
Pinout tab
----------
  1. Configure mode
    - Peripherals \b I2C1: Mode=<b>I2C</b>

  2. Configure pin PH7 and pin PH8 as I2C3 peripheral alternative pins
    - Click in chip diagram on pin \b PH7 and select \b I2C3_SCL
    - Click in chip diagram on pin \b PH8 and select \b I2C3_SDA

Clock Configuration tab
-----------------------
  1. Configure I2C1 Clock
    - Setup "To I2C1,2,3 (MHz)" to match application requirements

Configuration tab
-----------------
  1. Under Connectivity open \b I2C1 Configuration:
     - \e optional <b>DMA Settings</b>: setup DMA transfers for Rx and Tx <b> (DMA transfers are disabled in current driver) </b>\n
       \b Add - Select \b I2C1_RX: Stream=DMA1 Stream 0, Direction=Peripheral to Memory, Priority=Low
          DMA Request Settings         | Label             | Peripheral | Memory
          :----------------------------|:------------------|:-----------|:-------------
          Mode: Normal                 | Increment Address | OFF        |\b ON
          Use Fifo OFF Threshold: Full | Data Width        |\b Byte     | Byte
          .                            | Burst Size        | Single     | Single
       \b Add - Select \b I2C1_TX: Stream=DMA1 Stream 1, Direction=Memory to Peripheral, Priority=Low
          DMA Request Settings         | Label             | Peripheral | Memory
          :----------------------------|:------------------|:-----------|:-------------
          Mode: Normal                 | Increment Address | OFF        |\b ON
          Use Fifo OFF Threshold: Full | Data Width        |\b Byte     | Byte
          .                            | Burst Size        | Single     | Single
     \n\note
     I2C4 transfers with BDMA:\n
         BDMA can access only SRAM4 memory. Ensure that Tx and Rx buffers are be positioned in SRAM4 memory, when I2C4 is used in DMA mode (BDMA).

     \n
     - <b>GPIO Settings</b>: review settings, no changes required
          Pin Name | Signal on Pin | GPIO mode | GPIO Pull-up/Pull..| Maximum out | User Label
          :--------|:--------------|:----------|:-------------------|:------------|:----------
          PB6      | I2C1_SCL      | Alternate | Pull-up            | High        |.
          PB7      | I2C1_SDA      | Alternate | Pull-up            | High        |.

     - <b>NVIC Settings</b>: enable interrupts
          Interrupt Table                      | Enable | Preemption Priority | Sub Priority
          :------------------------------------|:-------|:--------------------|:--------------
          DMA1 stream0 global interrupt        |   ON   | 0                   | 0
          DMA1 stream1 global interrupt        |   ON   | 0                   | 0
          I2C1 event interrupt                 |\b ON   | 0                   | 0
          I2C1 error interrupt                 |\b ON   | 0                   | 0

     - Parameter Settings: not used
     - User Constants: not used
   
     Click \b OK to close the I2C1 Configuration dialog
*/

/*! \cond */

#include "I2C_STM32H7xx.h"

#define ARM_I2C_DRV_VERSION ARM_DRIVER_VERSION_MAJOR_MINOR(1,4)    /* driver version */

#define I2C_DMA_ENABLED              0

/* Analog noise filter state: 0 - disable, 1 - enable */
#ifndef I2C1_ANF_ENABLE
#define I2C1_ANF_ENABLE              0
#endif
#ifndef I2C2_ANF_ENABLE
#define I2C2_ANF_ENABLE              0
#endif
#ifndef I2C3_ANF_ENABLE
#define I2C3_ANF_ENABLE              0
#endif
#ifndef I2C4_ANF_ENABLE
#define I2C4_ANF_ENABLE              0
#endif
#ifndef I2C5_ANF_ENABLE
#define I2C5_ANF_ENABLE              0
#endif

/* Digital noise filter coefficient: 0 - disable, [1:15] - enabled */
#ifndef I2C1_DNF_COEFFICIENT
#define I2C1_DNF_COEFFICIENT         0
#endif
#ifndef I2C2_DNF_COEFFICIENT
#define I2C2_DNF_COEFFICIENT         0
#endif
#ifndef I2C3_DNF_COEFFICIENT
#define I2C3_DNF_COEFFICIENT         0
#endif
#ifndef I2C4_DNF_COEFFICIENT
#define I2C4_DNF_COEFFICIENT         0
#endif
#ifndef I2C5_DNF_COEFFICIENT
#define I2C5_DNF_COEFFICIENT         0
#endif

#ifndef I2C_DCACHE_MAINTENANCE
#define I2C_DCACHE_MAINTENANCE       (1U)
#endif
#ifndef I2C_DCACHE_DATA_RX_ALIGNMENT
#define I2C_DCACHE_DATA_RX_ALIGNMENT (1U)
#endif 
#ifndef I2C_DCACHE_DATA_RX_SIZE
#define I2C_DCACHE_DATA_RX_SIZE      (1U)
#endif
#ifndef I2C_DCACHE_DATA_TX_ALIGNMENT
#define I2C_DCACHE_DATA_TX_ALIGNMENT (1U)
#endif 
#ifndef I2C_DCACHE_DATA_TX_SIZE
#define I2C_DCACHE_DATA_TX_SIZE      (1U)
#endif

#define I2C_DCACHE_DATA_SIZE(rxtx)       rxtx ? I2C_DCACHE_DATA_TX_SIZE     : I2C_DCACHE_DATA_RX_SIZE
#define I2C_DCACHE_DATA_ALIGNMENT(rxtx)  rxtx ? I2C_DCACHE_DATA_TX_ALIGNMENT: I2C_DCACHE_DATA_RX_ALIGNMENT

/* Driver Version */
static const ARM_DRIVER_VERSION DriverVersion = {
  ARM_I2C_API_VERSION,
  ARM_I2C_DRV_VERSION
};

/* Driver Capabilities */
static const ARM_I2C_CAPABILITIES DriverCapabilities = { 
  0U                    /* Does not support 10-bit addressing */
#if (I2C_API_PADDING_EN != 0U)
, 0U                    /* Reserved bits */
#endif
};


#if defined(MX_I2C1)
extern I2C_HandleTypeDef hi2c1;

/* I2C1 Information (Run-Time) */
static I2C_INFO I2C1_Info = {
  NULL,
  { 0U, 0U, 0U, 0U, 0U, 0U
#if (I2C_API_PADDING_EN != 0U)
  , 0U
#endif
  },
  0U,
  0U,
  0U,
  0U,
  NULL,
  0U
};

/* I2C1 Resources */
static I2C_RESOURCES I2C1_Resources = {
  &hi2c1,
  I2C1,
  {
    MX_I2C1_SCL_GPIOx,
    MX_I2C1_SDA_GPIOx,
    MX_I2C1_SCL_GPIO_Pin,
    MX_I2C1_SDA_GPIO_Pin,
    MX_I2C1_SCL_GPIO_Pu,
    MX_I2C1_SDA_GPIO_Pu,
    MX_I2C1_SCL_GPIO_AF,
    MX_I2C1_SDA_GPIO_AF
  },
  I2C1_EV_IRQn,
  I2C1_ER_IRQn,
  I2C1_ANF_ENABLE,
  I2C1_DNF_COEFFICIENT,
  &I2C1_Info
};

#endif /* MX_I2C1 */

#if defined(MX_I2C2)
extern I2C_HandleTypeDef hi2c2;

/* I2C2 Information (Run-Time) */
static I2C_INFO I2C2_Info = {
  NULL,
  { 0U, 0U, 0U, 0U, 0U, 0U
#if (I2C_API_PADDING_EN != 0U)
  , 0U
#endif
  },
  0U,
  0U,
  0U,
  0U,
  NULL,
  0U
};

/* I2C2 Resources */
static I2C_RESOURCES I2C2_Resources = {
  &hi2c2,
  I2C2,
  {
    MX_I2C2_SCL_GPIOx,
    MX_I2C2_SDA_GPIOx,
    MX_I2C2_SCL_GPIO_Pin,
    MX_I2C2_SDA_GPIO_Pin,
    MX_I2C2_SCL_GPIO_Pu,
    MX_I2C2_SDA_GPIO_Pu,
    MX_I2C2_SCL_GPIO_AF,
    MX_I2C2_SDA_GPIO_AF
  },
  I2C2_EV_IRQn,
  I2C2_ER_IRQn,
  I2C2_ANF_ENABLE,
  I2C2_DNF_COEFFICIENT,
  &I2C2_Info
};

#endif /* MX_I2C2 */

#if defined(MX_I2C3)
extern I2C_HandleTypeDef hi2c3;

/* I2C3 Information (Run-Time) */
static I2C_INFO I2C3_Info = {
  NULL,
  { 0U, 0U, 0U, 0U, 0U, 0U
#if (I2C_API_PADDING_EN != 0U)
  , 0U
#endif
  },
  0U,
  0U,
  0U,
  0U,
  NULL,
  0U
};

/* I2C3 Resources */
static I2C_RESOURCES I2C3_Resources = {
  &hi2c3,
  I2C3,
  {
    MX_I2C3_SCL_GPIOx,
    MX_I2C3_SDA_GPIOx,
    MX_I2C3_SCL_GPIO_Pin,
    MX_I2C3_SDA_GPIO_Pin,
    MX_I2C3_SCL_GPIO_Pu,
    MX_I2C3_SDA_GPIO_Pu,
    MX_I2C3_SCL_GPIO_AF,
    MX_I2C3_SDA_GPIO_AF
  },
  I2C3_EV_IRQn,
  I2C3_ER_IRQn,
  I2C3_ANF_ENABLE,
  I2C3_DNF_COEFFICIENT,
  &I2C3_Info
};

#endif /* MX_I2C3 */

#if defined(MX_I2C4)
extern I2C_HandleTypeDef hi2c4;

/* I2C4 Information (Run-Time) */
static I2C_INFO I2C4_Info = {
  NULL,
  { 0U, 0U, 0U, 0U, 0U, 0U
#if (I2C_API_PADDING_EN != 0U)
  , 0U
#endif
  },
  0U,
  0U,
  0U,
  0U,
  NULL,
  0U
};

/* I2C4 Resources */
static I2C_RESOURCES I2C4_Resources = {
  &hi2c4,
  I2C4,
  {
    MX_I2C4_SCL_GPIOx,
    MX_I2C4_SDA_GPIOx,
    MX_I2C4_SCL_GPIO_Pin,
    MX_I2C4_SDA_GPIO_Pin,
    MX_I2C4_SCL_GPIO_Pu,
    MX_I2C4_SDA_GPIO_Pu,
    MX_I2C4_SCL_GPIO_AF,
    MX_I2C4_SDA_GPIO_AF
  },
  I2C4_EV_IRQn,
  I2C4_ER_IRQn,
  I2C4_ANF_ENABLE,
  I2C4_DNF_COEFFICIENT,
  &I2C4_Info
};

#endif /* MX_I2C4 */

#if defined(MX_I2C5)
extern I2C_HandleTypeDef hi2c5;

/* I2C5 Information (Run-Time) */
static I2C_INFO I2C5_Info = {
  NULL,
  { 0U, 0U, 0U, 0U, 0U, 0U
#if (I2C_API_PADDING_EN != 0U)
  , 0U
#endif
  },
  0U,
  0U,
  0U,
  0U,
  NULL,
  0U
};

/* I2C5 Resources */
static I2C_RESOURCES I2C5_Resources = {
  &hi2c5,
  I2C5,
  {
    MX_I2C5_SCL_GPIOx,
    MX_I2C5_SDA_GPIOx,
    MX_I2C5_SCL_GPIO_Pin,
    MX_I2C5_SDA_GPIO_Pin,
    MX_I2C5_SCL_GPIO_Pu,
    MX_I2C5_SDA_GPIO_Pu,
    MX_I2C5_SCL_GPIO_AF,
    MX_I2C5_SDA_GPIO_AF
  },
  I2C5_EV_IRQn,
  I2C5_ER_IRQn,
  I2C5_ANF_ENABLE,
  I2C5_DNF_COEFFICIENT,
  &I2C5_Info
};

#endif /* MX_I2C5 */

/* I2C standard timing specification */
static I2C_STD_TIME i2c_spec_standard = {
  100000, /* clk_max    */
  300,    /* tf_max     */
  1000,   /* tr_max     */
  0,      /* hddat_min  */
  3450,   /* vddat_max  */
  250,    /* sudat_min  */
  4700,   /* scll_min   */
  4000,   /* sclh_min   */
};

static I2C_STD_TIME i2c_spec_fast = {
  400000, /* clk_max    */
  300,    /* tf_max     */
  300,    /* tr_max     */
  0,      /* hddat_min  */
  900,    /* vddat_max  */
  100,    /* sudat_min  */
  1300,   /* scll_min   */
  600,    /* sclh_min   */
};

static I2C_STD_TIME i2c_spec_fast_plus = {
  1000000, /* clk_max    */
  100,     /* tf_max     */
  120,     /* tr_max     */
  0,       /* hddat_min  */
  450,     /* vddat_max  */
  50,      /* sudat_min  */
  500,     /* scll_min   */
  260,     /* sclh_min   */
};

#if (I2C_DMA_ENABLED == 1U)
#if (I2C_DCACHE_MAINTENANCE == 1U)
/**
  \fn          int32_t I2C_GetDCacheMemBlockSize (uint32_t rxtx, uint32_t *data, uint32_t size)
  \brief       Get DCache memory block size
  \param[in]   rxtx  0 = rx, 1 = tx
  \param[in]   data  Data address
  \param[in]   size  Data size
  \return      DCache memory block size (n *32) or 0 = invalid size
*/
__STATIC_INLINE int32_t I2C_GetDCacheMemBlockSize (uint32_t rxtx, uint32_t *data, uint32_t size) {
  int32_t sz = 0;
  
#if (I2C_DCACHE_DATA_SIZE(rxtx) == 1U)
  if ((size & 0x1FU) == 0U) {
    sz = (int32_t)size;
  }
#else
  sz = (int32_t)((((uint32_t)data + size + 0x1FU) & ~0x1FU) - ((uint32_t)data & ~0x1FU));
#endif

  return sz;
}

/**
  \fn          uint32_t * I2C_GetDCacheMemBlockAddr (uint32_t rxtx, uint32_t *data)
  \brief       Get DCache memory block address
  \param[in]   rxtx  0 = rx, 1 = tx
  \param[in]   data  Data address
  \param[in]   size  Data size
  \return      DCache memory block address (aligned to 32-byte boundary) or 0 = invalid data address
*/
__STATIC_INLINE uint32_t * I2C_GetDCacheMemBlockAddr (uint32_t rxtx, uint32_t *data) {
  uint32_t *addr = NULL;
  
#if (I2C_DCACHE_DATA_ALIGNMENT(rxtx) == 1U)
  if (((uint32_t)data & 0x1FU) == 0U) {
    addr = data;
  }
#else
  addr = (uint32_t *)((uint32_t)data & ~0x1FU);
#endif

  return addr;
}
#endif
#endif

/* Private functions */
static I2C_RESOURCES *I2C_GetResources   (I2C_HandleTypeDef *hi2c);
static uint32_t       I2C_GetKernelClock (I2C_TypeDef *i2c);
static int32_t        I2C_GetSCLRatio    (I2C_CLK_SETUP *setup, I2C_STD_TIME *spec, I2C_TIMING *cfg);
static uint32_t       I2C_GetTimingValue (I2C_CLK_SETUP *setup, I2C_STD_TIME *spec);

/* Retrieve pointer to I2C instance resources */
static I2C_RESOURCES *I2C_GetResources (I2C_HandleTypeDef *hi2c) {
  I2C_RESOURCES *i2c = NULL;

  #if defined(MX_I2C1)
  if (hi2c->Instance == I2C1) { i2c = &I2C1_Resources; }
  #endif

  #if defined(MX_I2C2)
  if (hi2c->Instance == I2C2) { i2c = &I2C2_Resources; }
  #endif

  #if defined(MX_I2C3)
  if (hi2c->Instance == I2C3) { i2c = &I2C3_Resources; }
  #endif

  #if defined(MX_I2C4)
  if (hi2c->Instance == I2C4) { i2c = &I2C4_Resources; }
  #endif

  #if defined(MX_I2C5)
  if (hi2c->Instance == I2C5) { i2c = &I2C5_Resources; }
  #endif

  return (i2c);
}

/* Determine I2C kernel clock */
static uint32_t I2C_GetKernelClock (I2C_TypeDef *i2c) {
  PLL3_ClocksTypeDef pll3;
  uint32_t src, clk;
  uint32_t hclk, div;

  if (i2c == I2C4) {
    src = __HAL_RCC_GET_I2C4_SOURCE();
  }
  else /* I2C1, I2C2, I2C3 */ {
    src = __HAL_RCC_GET_I2C1_SOURCE();
  }

  if ((src == RCC_I2C1CLKSOURCE_D2PCLK1) || (src == RCC_I2C4CLKSOURCE_D3PCLK1)) {
    /* Get HCLK frequency (clock freq after HPRE prescaler) */
    hclk = HAL_RCC_GetHCLKFreq();
    div  = 1U;

    if (i2c == I2C4) {
      /* Determine D3PPRE value */
      if (RCC->D3CFGR & RCC_D3CFGR_D3PPRE_2) {
        div = 2U << ((RCC->D3CFGR >> 4U) & 0x03U);
      }
    }
    else {
      /* Determine D2PPRE1 value */
      if (RCC->D2CFGR & RCC_D2CFGR_D2PPRE1_2) {
        div = 2U << ((RCC->D2CFGR >> 4U) & 0x03U);
      }
    }
    clk = hclk / div;
  }
  else if ((src == RCC_I2C1CLKSOURCE_PLL3) || (src == RCC_I2C4CLKSOURCE_PLL3)) {
    /* Get PLL3 frequencies */
    HAL_RCCEx_GetPLL3ClockFreq(&pll3);
    
    /* I2C kernel clock runs from PLL3 R divider */
    clk = pll3.PLL3_R_Frequency;
  }
  else if ((src == RCC_I2C1CLKSOURCE_HSI) || (src == RCC_I2C4CLKSOURCE_HSI)) {
    /* Get HSI frequency (64MHz) */
    clk = MX_HSI_VALUE;
  }
  else /* if ((src == RCC_I2C1CLKSOURCE_CSI) || (src == RCC_I2C4CLKSOURCE_CSI)) */ {
    /* Get CSI frequency (4MHz) */
    clk = 4000000;
  }

  return (clk);
}

/* TIMING setup: Evaluate SCL low/high ratio */
static int32_t I2C_GetSCLRatio (I2C_CLK_SETUP *setup, I2C_STD_TIME *spec, I2C_TIMING *cfg) {
  uint32_t clk_max, clk_min;
  uint32_t tpresc, tsync;
  uint32_t tscl, tscll, tsclh;
  uint32_t scll, sclh;
  int32_t  err;
  
  /* Set minimum bus clock frequency to 80% of max */
  clk_min = (spec->clk_max * 80) / 100;

  /* Convert values to ns */
  clk_max = 1000000000 / clk_min;
  clk_min = 1000000000 / spec->clk_max;
  tsync   = setup->afd_min + setup->dfd + (2 * setup->i2cclk);
  tpresc = (cfg->presc + 1) * setup->i2cclk;

  err = 0;

  /* Evaluate all values of SCLL and SCLH */
  for (scll = 0; scll < I2C_TIMINGR_SCLL_MAX; scll++) {
    tscll = ((scll + 1) * tpresc) + tsync;

    if ((tscll >= spec->scll_min) && (setup->i2cclk < ((tscll - setup->afd_min - setup->dfd) / 4))) {
      /* SCL low meets specification */

      for (sclh = 0; sclh < I2C_TIMINGR_SCLH_MAX; sclh++) {
        tsclh = ((sclh + 1) * tpresc) + tsync;

        if ((tsclh >= spec->sclh_min) && (tsclh > setup->i2cclk)) {
          /* SCL high meets specification */
          tscl = tscll + tsclh;

          if ((tscl >= clk_min) && (tscl <= clk_max)) {
            /* Evaluate SCL period error */
            err = (int32_t)(tscl - setup->busclk);

            if (err >= 0) {
              cfg->sclh = (uint8_t)sclh;
              cfg->scll = (uint8_t)scll;
              return (err);
            }
          }
        }
      }
    }
  }
  /* No solution */
  return (-1);
}

/* TIMING setup: Determine TIMING register settings based on input structures */
static uint32_t I2C_GetTimingValue (I2C_CLK_SETUP *setup, I2C_STD_TIME *spec) {
  I2C_TIMING time;
  uint32_t dnf_en;
  uint32_t presc;
  uint32_t sdadel, sdadel_min, sdadel_max;
  uint32_t scldel, scldel_min;
  uint32_t p, l, a;
  uint32_t timing;
  int32_t  val, err;

  /* Set digital noise filter enabled flag */
  if (setup->dfd > 0U) {
    dnf_en = 1U;
  } else {
    dnf_en = 0U;
  }

  /* SDADEL (max) */
  val = (int32_t)(spec->vddat_max - spec->tr_max - setup->afd_max - ((dnf_en + 4) * setup->i2cclk));
  
  if (val > 0) {
    sdadel_max = (uint32_t)val;
  } else {
    sdadel_max = 0U;
  }

  /* SDADEL (min) */
  val = (int32_t)(spec->tf_max + spec->hddat_min - setup->afd_min - ((dnf_en + 3) * setup->i2cclk));

  if (val > 0) {
    sdadel_min = (uint32_t)val;
  } else {
    sdadel_min = 0U;
  }

  /* SCLDEL (min) */
  scldel_min = spec->tr_max + spec->sudat_min;

  /* Set timing register max value */
  timing = 0xF0FFFFFF;

  /* Evaluate all values of PRESC, SCLDEL and SDADEL */
  for (p = 0; p < I2C_TIMINGR_PRESC_MAX; p++) {
    presc = (p + 1) * setup->i2cclk;

    for (l = 0; l < I2C_TIMINGR_SCLDEL_MAX; l++) {
      /* tSCLDEL = (SCLDEL + 1) * ((PRESC + 1) * tI2CCLK) */
      scldel = (l + 1) * presc;

      if (scldel >= scldel_min) {
        /* SCLDEL is above low limit, evaluate SDADEL */
        for (a = 0; a < I2C_TIMINGR_SDADEL_MAX; a++) {
          /* tSDADEL = SDADEL * ((PRESC + 1) * tI2CCLK) */
          sdadel = a * presc;

          if ((sdadel >= sdadel_min) && (sdadel <= sdadel_max)) {
            /* Valid presc (p), scldel (l) and sdadel (a) */
            time.presc  = (uint8_t)p;
            time.scldel = (uint8_t)l;
            time.sdadel = (uint8_t)a;

            /* Determine SCLL and SCLH values */
            err = I2C_GetSCLRatio (setup, spec, &time);

            if (err >= 0) {
              if (err < setup->error) {
                /* Truncate error since it will never be bigger than 16-bit */
                setup->error = (uint16_t)err;

                /* Save timing settings */
                timing  = (time.scll   & 0xFFU);
                timing |= (time.sclh   & 0xFFU) <<  8;
                timing |= (time.sdadel & 0x0FU) << 16;
                timing |= (time.scldel & 0x0FU) << 20;
                timing |= (time.presc  & 0x0FU) << 28;
              }
            }
          }
        }
      }
    }
  }

  return (timing);
}

/**
  \fn          ARM_DRV_VERSION I2C_GetVersion (void)
  \brief       Get driver version.
  \return      \ref ARM_DRV_VERSION
*/
static ARM_DRIVER_VERSION I2CX_GetVersion (void) {
  return DriverVersion;
}


/**
  \fn          ARM_I2C_CAPABILITIES I2C_GetCapabilities (void)
  \brief       Get driver capabilities.
  \return      \ref ARM_I2C_CAPABILITIES
*/
static ARM_I2C_CAPABILITIES I2CX_GetCapabilities (void) {
  return DriverCapabilities;
}


/**
  \fn          int32_t I2C_Initialize (ARM_I2C_SignalEvent_t cb_event, I2C_RESOURCES *i2c)
  \brief       Initialize I2C Interface.
  \param[in]   cb_event  Pointer to \ref ARM_I2C_SignalEvent
  \param[in]   i2c   Pointer to I2C resources
  \return      \ref ARM_I2C_STATUS
*/
static int32_t I2C_Initialize (ARM_I2C_SignalEvent_t cb_event, I2C_RESOURCES *i2c) {

  if (i2c->info->flags & I2C_INIT) { return ARM_DRIVER_OK; }

  i2c->h->Instance = i2c->reg;
  
  i2c->h->Init.Timing           = 0U;
  i2c->h->Init.OwnAddress1      = 0U;
  i2c->h->Init.AddressingMode   = I2C_ADDRESSINGMODE_7BIT;
  i2c->h->Init.DualAddressMode  = I2C_DUALADDRESS_DISABLE;
  i2c->h->Init.OwnAddress2      = 0U;
  i2c->h->Init.OwnAddress2Masks = I2C_OA2_NOMASK;
  i2c->h->Init.GeneralCallMode  = I2C_GENERALCALL_DISABLE;
  i2c->h->Init.NoStretchMode    = I2C_NOSTRETCH_DISABLE;

  /* Reset Run-Time information structure */
  memset ((void *)i2c->info, 0x00, sizeof (I2C_INFO));

  i2c->info->cb_event = cb_event;
  i2c->info->flags    = I2C_INIT;

  return ARM_DRIVER_OK;
}


/**
  \fn          int32_t I2C_Uninitialize (I2C_RESOURCES *i2c)
  \brief       De-initialize I2C Interface.
  \param[in]   i2c  Pointer to I2C resources
  \return      \ref execution_status
*/
static int32_t I2C_Uninitialize (I2C_RESOURCES *i2c) {

  i2c->h->Instance = NULL;
  i2c->info->flags = 0U;

  return ARM_DRIVER_OK;
}


/**
  \fn          int32_t ARM_I2C_PowerControl (ARM_POWER_STATE state, I2C_RESOURCES *i2c)
  \brief       Control I2C Interface Power.
  \param[in]   state  Power state
  \param[in]   i2c  Pointer to I2C resources
  \return      \ref execution_status
*/
static int32_t I2C_PowerControl (ARM_POWER_STATE state, I2C_RESOURCES *i2c) {
  uint32_t val;

  switch (state) {
    case ARM_POWER_OFF:

      if (HAL_I2C_DeInit (i2c->h) != HAL_OK) {
        return ARM_DRIVER_ERROR;
      }

      i2c->info->status.busy             = 0U;
      i2c->info->status.mode             = 0U;
      i2c->info->status.direction        = 0U;
      i2c->info->status.general_call     = 0U;
      i2c->info->status.arbitration_lost = 0U;
      i2c->info->status.bus_error        = 0U;

      i2c->info->flags &= ~I2C_POWER;
      break;

    case ARM_POWER_LOW:
      return ARM_DRIVER_ERROR_UNSUPPORTED;

    case ARM_POWER_FULL:
      if ((i2c->info->flags & I2C_INIT)  == 0U) {
        return ARM_DRIVER_ERROR;
      }
      if ((i2c->info->flags & I2C_POWER) != 0U) {
        return ARM_DRIVER_OK;
      }
      
      if (HAL_I2C_Init (i2c->h) != HAL_OK) {
        return ARM_DRIVER_ERROR;
      }

      /* Configure analog noise filter */
      if (i2c->anf_enable != 0) {
        val = I2C_ANALOGFILTER_ENABLE;
      } else {
        val = I2C_ANALOGFILTER_DISABLE;
      }

      if (HAL_I2CEx_ConfigAnalogFilter (i2c->h, val) != HAL_OK) {
        return ARM_DRIVER_ERROR;
      }
      
      /* Configure digital noise filter */
      if (HAL_I2CEx_ConfigDigitalFilter (i2c->h, i2c->dnf_coef) != HAL_OK) {
        return ARM_DRIVER_ERROR;
      }

      if (i2c->h->hdmarx != NULL) {
        i2c->info->flags |= I2C_DMA_RX;
      }
      if (i2c->h->hdmatx != NULL) {
        i2c->info->flags |= I2C_DMA_TX;
      }

      /* Ready for operation */
      i2c->info->flags |= I2C_POWER;
      break;
  }

  return ARM_DRIVER_OK;
}


/**
  \fn          int32_t I2C_MasterTransmit (uint32_t       addr,
                                           const uint8_t *data,
                                           uint32_t       num,
                                           bool           xfer_pending,
                                           I2C_RESOURCES *i2c)
  \brief       Start transmitting data as I2C Master.
  \param[in]   addr          Slave address (7-bit or 10-bit)
  \param[in]   data          Pointer to buffer with data to send to I2C Slave
  \param[in]   num           Number of data bytes to send
  \param[in]   xfer_pending  Transfer operation is pending - Stop condition will not be generated
  \param[in]   i2c           Pointer to I2C resources
  \return      \ref execution_status
*/
static int32_t I2C_MasterTransmit (uint32_t       addr,
                                   const uint8_t *data,
                                   uint32_t       num,
                                   bool           xfer_pending,
                                   I2C_RESOURCES *i2c) {
  HAL_StatusTypeDef status;
  uint32_t buf = (uint32_t)data;
  uint16_t cnt, saddr;
  uint32_t opt;
#if (I2C_DMA_ENABLED == 1U)
  uint8_t  tx_dma = 0U;
#if (I2C_DCACHE_MAINTENANCE == 1U)
  int32_t   mem_sz;
  uint32_t *mem_addr;
#endif
#endif

  if ((data == NULL) || (num == 0U)) {
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  if ((addr & ~((uint32_t)ARM_I2C_ADDRESS_10BIT | (uint32_t)ARM_I2C_ADDRESS_GC)) > 0x3FFU) {
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  if (num > UINT16_MAX) {
    /* HAL does not handle 32-bit count in transfer */
    return ARM_DRIVER_ERROR;
  }

  cnt = (uint16_t)num;

  if (i2c->info->status.busy) {
    return (ARM_DRIVER_ERROR_BUSY);
  }

  saddr = (addr & 0x3FFU);
  if (i2c->h->Init.AddressingMode == I2C_ADDRESSINGMODE_7BIT) {
    saddr <<= 1;
  }

  i2c->info->status.busy             = 1U;
  i2c->info->status.mode             = 1U;
  i2c->info->status.direction        = 0U;
  i2c->info->status.bus_error        = 0U;
  i2c->info->status.arbitration_lost = 0U;

  i2c->info->xfer_sz = cnt;

  if (xfer_pending == true) {
    if ((i2c->info->flags & I2C_XFER_FIRST) == 0U) {
      i2c->info->flags |= I2C_XFER_FIRST;
      opt = I2C_FIRST_FRAME;
    } else {
      opt = I2C_NEXT_FRAME;
    }
    status = HAL_I2C_Master_Seq_Transmit_IT (i2c->h, saddr, (uint8_t *)buf, cnt, opt);
  }
  else {
#if (I2C_DMA_ENABLED == 1U)
    if ((i2c->info->flags & (I2C_DMA_TX | I2C_XFER_FIRST)) == I2C_DMA_TX) {
        
#if (I2C_DCACHE_MAINTENANCE == 1U)
      /* Is DCache enabled */
      if ((SCB->CCR & SCB_CCR_DC_Msk) != 0U) {
        mem_addr = I2C_GetDCacheMemBlockAddr(1U, (uint32_t *)data);
        mem_sz   = I2C_GetDCacheMemBlockSize(1U, (uint32_t *)data, num);
        if ((mem_addr != NULL) && (mem_sz != 0U)) {
          tx_dma = 1U;
          /* Clean data cache: ensure data coherency between DMA and CPU */
          SCB_CleanDCache_by_Addr (mem_addr, mem_sz);
        }
      }
      else {
        tx_dma = 1U;
      }
#else 
      tx_dma = 1U;
#endif
    }

    if (tx_dma != 0U) {
      status = HAL_I2C_Master_Transmit_DMA (i2c->h, saddr, (uint8_t *)buf, cnt);
    }
    else {
#endif
      i2c->info->flags &= ~I2C_XFER_FIRST;
      status = HAL_I2C_Master_Seq_Transmit_IT (i2c->h, saddr, (uint8_t *)buf, cnt, I2C_LAST_FRAME);
#if (I2C_DMA_ENABLED == 1U)
    }
#endif
  }

  if (status != HAL_OK) {
    return ARM_DRIVER_ERROR;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t I2C_MasterReceive (uint32_t       addr,
                                          uint8_t       *data,
                                          uint32_t       num,
                                          bool           xfer_pending,
                                          I2C_RESOURCES *i2c)
  \brief       Start receiving data as I2C Master.
  \param[in]   addr          Slave address (7-bit or 10-bit)
  \param[out]  data          Pointer to buffer for data to receive from I2C Slave
  \param[in]   num           Number of data bytes to receive
  \param[in]   xfer_pending  Transfer operation is pending - Stop condition will not be generated
  \param[in]   i2c           Pointer to I2C resources
  \return      \ref execution_status
*/
static int32_t I2C_MasterReceive (uint32_t       addr,
                                  uint8_t       *data,
                                  uint32_t       num,
                                  bool           xfer_pending,
                                  I2C_RESOURCES *i2c) {
  HAL_StatusTypeDef status;
  uint16_t cnt, saddr;
  uint32_t opt;
#if (I2C_DMA_ENABLED == 1U)
#if (I2C_DCACHE_MAINTENANCE == 1U)
  int32_t   mem_sz;
  uint32_t *mem_addr;
#endif
#endif

  if ((data == NULL) || (num == 0U)) {
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  if ((addr & ~((uint32_t)ARM_I2C_ADDRESS_10BIT | (uint32_t)ARM_I2C_ADDRESS_GC)) > 0x3FFU) {
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  if (num > UINT16_MAX) {
    /* HAL does not handle 32-bit count in transfer */
    return ARM_DRIVER_ERROR;
  }

  cnt = (uint16_t)num;

  if (i2c->info->status.busy) {
    return (ARM_DRIVER_ERROR_BUSY);
  }

  saddr = (addr & 0x3FFU);
  if (i2c->h->Init.AddressingMode == I2C_ADDRESSINGMODE_7BIT) {
    saddr <<= 1;
  }

  i2c->info->status.busy             = 1U;
  i2c->info->status.mode             = 1U;
  i2c->info->status.direction        = 1U;
  i2c->info->status.bus_error        = 0U;
  i2c->info->status.arbitration_lost = 0U;

  addr = (addr & 0x3FFU);

  if (i2c->h->Init.AddressingMode == I2C_ADDRESSINGMODE_7BIT) {
    addr <<= 1;
  }

  i2c->info->xfer_sz = cnt;
  i2c->info->rx_data = data;
  i2c->info->rx_dma  = 0U;
  
  if (xfer_pending == true) {
    if ((i2c->info->flags & I2C_XFER_FIRST) == 0U) {
      i2c->info->flags |= I2C_XFER_FIRST;
      opt = I2C_FIRST_FRAME;
    } else {
      opt = I2C_NEXT_FRAME;
    }
    status = HAL_I2C_Master_Seq_Receive_IT (i2c->h, saddr, data, cnt, opt);
  }
  else {
#if (I2C_DMA_ENABLED == 1U)
    if ((i2c->info->flags & (I2C_DMA_RX | I2C_XFER_FIRST)) == I2C_DMA_RX) {
#if (I2C_DCACHE_MAINTENANCE == 1U)
      /* Is DCache enabled */
      if ((SCB->CCR & SCB_CCR_DC_Msk) != 0U) {
        mem_addr = I2C_GetDCacheMemBlockAddr(0U, (uint32_t *)data);
        mem_sz   = I2C_GetDCacheMemBlockSize(0U, (uint32_t *)data, num);
        if ((mem_addr != NULL) && (mem_sz != 0U)) {
          i2c->info->rx_dma = 1U;
        }
      } else {
        i2c->info->rx_dma = 1U;
      }
#else 
      i2c->info->rx_dma = 1U;
#endif
    }

    if (i2c->info->rx_dma != 0U) {
      status = HAL_I2C_Master_Receive_DMA (i2c->h, saddr, data, cnt);
    }
    else {
#endif
      i2c->info->flags &= ~I2C_XFER_FIRST;
      status = HAL_I2C_Master_Seq_Receive_IT (i2c->h, saddr, data, cnt, I2C_LAST_FRAME);
#if (I2C_DMA_ENABLED == 1U)
    }
#endif
  }

  if (status != HAL_OK) {
    return ARM_DRIVER_ERROR;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t I2C_SlaveTransmit (const uint8_t *data, uint32_t num, I2C_RESOURCES *i2c)
  \brief       Start transmitting data as I2C Slave.
  \param[in]   data          Pointer to buffer with data to send to I2C Master
  \param[in]   num           Number of data bytes to send
  \param[in]   i2c           Pointer to I2C resources
  \return      \ref execution_status
*/
static int32_t I2C_SlaveTransmit (const uint8_t *data, uint32_t num, I2C_RESOURCES *i2c) {
  HAL_StatusTypeDef status;
  uint32_t buf = (uint32_t)data;
  uint16_t cnt;

  if ((data == NULL) || (num == 0U)) {
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  if (num > UINT16_MAX) {
    /* HAL does not handle 32-bit count in transfer */
    return ARM_DRIVER_ERROR;
  }

  cnt = (uint16_t)num;

  if (i2c->info->status.busy) {
    return (ARM_DRIVER_ERROR_BUSY);
  }

  if (HAL_I2C_GetState (i2c->h) != HAL_I2C_STATE_LISTEN) {
    return ARM_DRIVER_ERROR;
  }

  i2c->info->status.bus_error    = 0U;
  i2c->info->status.general_call = 0U;

  i2c->info->flags   = I2C_XFER_SET;
  i2c->info->xfer_sz = cnt;

  status = HAL_I2C_Slave_Seq_Transmit_IT (i2c->h, (uint8_t *)buf, cnt, I2C_NEXT_FRAME);

  if (status == HAL_ERROR) {
    return ARM_DRIVER_ERROR;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t I2C_SlaveReceive (uint8_t *data, uint32_t num, I2C_RESOURCES *i2c)
  \brief       Start receiving data as I2C Slave.
  \param[out]  data          Pointer to buffer for data to receive from I2C Master
  \param[in]   num           Number of data bytes to receive
  \param[in]   i2c           Pointer to I2C resources
  \return      \ref execution_status
*/
static int32_t I2C_SlaveReceive (uint8_t *data, uint32_t num, I2C_RESOURCES *i2c) {
  HAL_StatusTypeDef status;
  uint16_t cnt;

  if ((data == NULL) || (num == 0U)) {
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  if (num > UINT16_MAX) {
    /* HAL does not handle 32-bit count in transfer */
    return ARM_DRIVER_ERROR;
  }

  cnt = (uint16_t)num;

  if (i2c->info->status.busy) {
    return (ARM_DRIVER_ERROR_BUSY);
  }

  i2c->info->status.bus_error    = 0U;
  i2c->info->status.general_call = 0U;
  
  i2c->info->flags   = I2C_XFER_SET;
  i2c->info->xfer_sz = cnt;

  status = HAL_I2C_Slave_Seq_Receive_IT (i2c->h, (uint8_t *)data, cnt, I2C_NEXT_FRAME);

  if (status == HAL_ERROR) {
    return ARM_DRIVER_ERROR;
  }

  return ARM_DRIVER_OK;
}


/**
  \fn          int32_t I2C_GetDataCount (void)
  \brief       Get transferred data count.
  \return      number of data bytes transferred; -1 when Slave is not addressed by Master
*/
static int32_t I2C_GetDataCount (I2C_RESOURCES *i2c) {
  int32_t cnt;

  if (HAL_I2C_GetState (i2c->h) == HAL_I2C_STATE_LISTEN) {
    cnt = -1;
  } else {
    cnt = (int32_t)(i2c->info->xfer_sz - i2c->h->XferCount);
  }

  return (cnt);
}


/**
  \fn          int32_t I2C_Control (uint32_t control, uint32_t arg, I2C_RESOURCES *i2c)
  \brief       Control I2C Interface.
  \param[in]   control  operation
  \param[in]   arg      argument of operation (optional)
  \param[in]   i2c      pointer to I2C resources
  \return      \ref execution_status
*/
static int32_t I2C_Control (uint32_t control, uint32_t arg, I2C_RESOURCES *i2c) {
  GPIO_InitTypeDef GPIO_InitStruct;
  GPIO_PinState state;
  uint32_t i, val;
  uint32_t fpclk, fscl;

  I2C_CLK_SETUP  clk_setup;
  I2C_STD_TIME  *clk_spec;

  if ((i2c->info->flags & I2C_POWER) == 0U) {
    /* I2C not powered */
    return ARM_DRIVER_ERROR;
  }

  switch (control) {
    case ARM_I2C_OWN_ADDRESS:
      if (arg == 0) {
        /* Disable slave */
        HAL_I2C_DisableListen_IT (i2c->h);
      }
      else {
        if ((arg & ARM_I2C_ADDRESS_GC) != 0) {
          /* Enable general call */
          i2c->h->Init.GeneralCallMode = I2C_GENERALCALL_ENABLE;
        } else {
          /* Disable general call */
          i2c->h->Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
        }

        if ((arg & ARM_I2C_ADDRESS_10BIT) != 0) {
          /* Own address is a 10-bit address */
          i2c->h->Init.AddressingMode = I2C_ADDRESSINGMODE_10BIT;
        } else {
          /* Own address is a 7-bit address */
          i2c->h->Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
        }
        
        i2c->h->Init.OwnAddress1 = (arg << 1) & 0x03FFU;

        HAL_I2C_Init (i2c->h);
        HAL_I2C_EnableListen_IT (i2c->h);
      }
      break;

    case ARM_I2C_BUS_SPEED:
      fpclk = I2C_GetKernelClock(i2c->reg);
      switch (arg) {
        case ARM_I2C_BUS_SPEED_STANDARD: /* Clock = 100kHz */
          clk_spec = &i2c_spec_standard;
          fscl = 100000;
          break;

        case ARM_I2C_BUS_SPEED_FAST: /* Clock = 400kHz */
          clk_spec = &i2c_spec_fast;
          fscl = 400000;
          break;
        
        case ARM_I2C_BUS_SPEED_FAST_PLUS: /* Clock = 1MHz */
          clk_spec = &i2c_spec_fast_plus;
          fscl = 1000000;
          break;

        default:
          return ARM_DRIVER_ERROR_UNSUPPORTED;
      }

      /* Determine kernel and bus clock period (ns) */
      clk_setup.i2cclk = (uint16_t)((1000000000 + (fpclk / 2)) / fpclk);
      clk_setup.busclk = (uint16_t)((1000000000 + (fscl  / 2)) / fscl);

      /* Determine digital filter delay (ns) */
      clk_setup.dfd = clk_setup.i2cclk * i2c->dnf_coef;
      
      /* Set analog filter delay (ns) */
      clk_setup.afd_min = ((i2c->anf_enable != 0U) ? (I2C_ANALOG_FILTER_DELAY_MIN) : (0));
      clk_setup.afd_max = ((i2c->anf_enable != 0U) ? (I2C_ANALOG_FILTER_DELAY_MAX) : (0));

      /* Set max iteration error */
      clk_setup.error  = 0xFFFF;
      
      /* Get TIMING register values */
      val = I2C_GetTimingValue (&clk_setup, clk_spec);

      /* Set register values */
      i2c->reg->CR1    &= ~I2C_CR1_PE;
      i2c->reg->TIMINGR = val;
      i2c->reg->CR1    |=  I2C_CR1_PE;
      break;

    case ARM_I2C_BUS_CLEAR:
      /* Configure SCl and SDA pins as GPIO pin */
      GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_OD;
      GPIO_InitStruct.Pull  = GPIO_NOPULL;
      GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;

      GPIO_InitStruct.Pin = i2c->io.scl_pin;
      HAL_GPIO_Init(i2c->io.scl_port, &GPIO_InitStruct);
      GPIO_InitStruct.Pin = i2c->io.sda_pin;
      HAL_GPIO_Init(i2c->io.sda_port, &GPIO_InitStruct);

      /* Pull SCL and SDA high */
      HAL_GPIO_WritePin (i2c->io.scl_port, i2c->io.scl_pin, GPIO_PIN_SET);
      HAL_GPIO_WritePin (i2c->io.sda_port, i2c->io.sda_pin, GPIO_PIN_SET);

      HAL_Delay (I2C_BUS_CLEAR_CLOCK_PERIOD);

      for (i = 0U; i < 9U; i++) {
        if (HAL_GPIO_ReadPin (i2c->io.sda_port, i2c->io.sda_pin) == GPIO_PIN_SET) {
          /* Break if slave released SDA line */
          break;
        }
        /* Clock high */
        HAL_GPIO_WritePin (i2c->io.scl_port, i2c->io.scl_pin, GPIO_PIN_SET);
        HAL_Delay (I2C_BUS_CLEAR_CLOCK_PERIOD/2);

        /* Clock low */
        HAL_GPIO_WritePin (i2c->io.scl_port, i2c->io.scl_pin, GPIO_PIN_RESET);
        HAL_Delay (I2C_BUS_CLEAR_CLOCK_PERIOD/2);
      }

      /* Check SDA state */
      state = HAL_GPIO_ReadPin (i2c->io.sda_port, i2c->io.sda_pin);

      /* Configure SDA and SCL pins as I2C peripheral pins */
      GPIO_InitStruct.Mode  = GPIO_MODE_AF_OD;
      GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;

      GPIO_InitStruct.Pin       = i2c->io.scl_pin;
      GPIO_InitStruct.Pull      = i2c->io.scl_pull;
      GPIO_InitStruct.Alternate = i2c->io.scl_af;

      HAL_GPIO_Init (i2c->io.scl_port, &GPIO_InitStruct);

      GPIO_InitStruct.Pin       = i2c->io.sda_pin;
      GPIO_InitStruct.Pull      = i2c->io.sda_pull;
      GPIO_InitStruct.Alternate = i2c->io.sda_af;

      HAL_GPIO_Init (i2c->io.sda_port, &GPIO_InitStruct);
      
      if (i2c->info->cb_event != NULL) {
        i2c->info->cb_event (ARM_I2C_EVENT_BUS_CLEAR);
      }

      return (state == GPIO_PIN_SET) ? ARM_DRIVER_OK : ARM_DRIVER_ERROR;

    case ARM_I2C_ABORT_TRANSFER:
      if (HAL_I2C_GetMode (i2c->h) == HAL_I2C_MODE_SLAVE) {
        /* Generate NACK when in slave mode */
        __HAL_I2C_GENERATE_NACK (i2c->h);
      } else {
        /* Get slave address */
        i2c->info->abort = 0U;
        val = i2c->reg->CR2 & 0x3FFU;

        HAL_I2C_Master_Abort_IT (i2c->h, (uint16_t)val);

        /* Wait until abort operation complete */
        while (i2c->info->abort == 0);
      }

      i2c->info->status.busy             = 0U;
      i2c->info->status.mode             = 0U;
      i2c->info->status.direction        = 0U;
      i2c->info->status.general_call     = 0U;
      i2c->info->status.arbitration_lost = 0U;
      i2c->info->status.bus_error        = 0U;
      break;

    default: return ARM_DRIVER_ERROR;
  }
  return ARM_DRIVER_OK;
}


/**
  \fn          ARM_I2C_STATUS I2C_GetStatus (I2C_RESOURCES *i2c)
  \brief       Get I2C status.
  \param[in]   i2c      pointer to I2C resources
  \return      I2C status \ref ARM_I2C_STATUS
*/
static ARM_I2C_STATUS I2C_GetStatus (I2C_RESOURCES *i2c) {
  return (i2c->info->status);
}


/* Master tx transfer completed */
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) {
  I2C_RESOURCES *i2c;

  i2c = I2C_GetResources (hi2c);

  if (i2c != NULL) {
    i2c->info->status.busy = 0U;

    if (i2c->info->cb_event != NULL) {
      i2c->info->cb_event (ARM_I2C_EVENT_TRANSFER_DONE);
    }
  }
}

/* Master rx transfer completed */
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) {
  I2C_RESOURCES *i2c;
#if (I2C_DMA_ENABLED == 1U)
#if (I2C_DCACHE_MAINTENANCE == 1U)
  int32_t   mem_sz;
  uint32_t *mem_addr;
#endif
#endif

  i2c = I2C_GetResources (hi2c);

#if (I2C_DMA_ENABLED == 1U)
#if (I2C_DCACHE_MAINTENANCE == 1U)
  if (i2c->info->rx_dma != 0U) {
    if ((SCB->CCR & SCB_CCR_DC_Msk) != 0U) {
      mem_addr = I2C_GetDCacheMemBlockAddr(0U, (uint32_t *)i2c->info->rx_data);
      mem_sz   = I2C_GetDCacheMemBlockSize(0U, (uint32_t *)i2c->info->rx_data, i2c->info->xfer_sz);
      if ((mem_addr != NULL) && (mem_sz != 0U)) {
        /* Invalidate data cache: ensure data coherency between DMA and CPU */
        SCB_InvalidateDCache_by_Addr (mem_addr, mem_sz);
      }
    }
  }
#endif
#endif

  if (i2c != NULL) {
    i2c->info->status.busy = 0U;

    if (i2c->info->cb_event != NULL) {
      i2c->info->cb_event (ARM_I2C_EVENT_TRANSFER_DONE);
    }
  }
}

/* Slave addressed */
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode) {
  I2C_RESOURCES *i2c;
  uint32_t event;
  
  i2c = I2C_GetResources (hi2c);

  if (i2c != NULL) {
    i2c->info->status.mode = 0U;

    if ((i2c->info->flags & I2C_XFER_SET) == 0U) {

      if (TransferDirection == I2C_DIRECTION_TRANSMIT) {
        /* Master is transmitter, slave enters receiver mode */
        event = ARM_I2C_EVENT_SLAVE_RECEIVE;
        i2c->info->status.direction = 1U;
      }
      else {
        /* Master is receiver, slave enters transmitter mode */
        event = ARM_I2C_EVENT_SLAVE_TRANSMIT;
        i2c->info->status.direction = 0U;
      }

      if (AddrMatchCode == 0U) {
        /* General call address */
        event |= ARM_I2C_EVENT_GENERAL_CALL;
        i2c->info->status.general_call = 1U;
      }

      if (i2c->info->cb_event != NULL) {
        i2c->info->cb_event (event);
      }
    }

    if ((i2c->info->flags & I2C_XFER_SET) == 0) {
      __HAL_I2C_GENERATE_NACK (i2c->h);
      __HAL_I2C_ENABLE_IT (i2c->h, I2C_IT_ADDRI | I2C_IT_STOPI | I2C_IT_NACKI | I2C_IT_ERRI);
    }
    else {
      i2c->info->status.busy = 1U;
    }
    __HAL_I2C_CLEAR_FLAG(i2c->h, I2C_FLAG_ADDR);
  }
}

/* Slave tx transfer completed */
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c) {
  I2C_RESOURCES *i2c;

  i2c = I2C_GetResources (hi2c);

  if (i2c != NULL) {
    i2c->info->flags &= ~I2C_XFER_SET;

    i2c->info->status.busy = 0U;

    if (i2c->info->cb_event != NULL) {
      i2c->info->cb_event (ARM_I2C_EVENT_TRANSFER_DONE);
    }
    /* Re-enable listen mode */
    __HAL_I2C_ENABLE_IT (i2c->h, I2C_IT_ADDRI | I2C_IT_STOPI | I2C_IT_NACKI | I2C_IT_ERRI);
  }
}

/* Slave rx transfer completed */
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) {
  I2C_RESOURCES *i2c;

  i2c = I2C_GetResources (hi2c);

  if (i2c != NULL) {
    i2c->info->flags &= ~I2C_XFER_SET;

    i2c->info->status.busy = 0U;

    if (i2c->info->cb_event != NULL) {
      i2c->info->cb_event (ARM_I2C_EVENT_TRANSFER_DONE);
    }
    /* Re-enable listen mode */
    __HAL_I2C_ENABLE_IT (i2c->h, I2C_IT_ADDRI | I2C_IT_STOPI | I2C_IT_NACKI | I2C_IT_ERRI);
  }
}

/* Listen transfer complete */
void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c) {
  I2C_RESOURCES *i2c;

  i2c = I2C_GetResources (hi2c);

  if (i2c != NULL) {
    /* Re-enable listen mode */
    HAL_I2C_EnableListen_IT (i2c->h);
  }
}

/* Transfer error */
void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c) {
  I2C_RESOURCES *i2c;
  uint32_t err, event;

  err = HAL_I2C_GetError (hi2c);
  i2c = I2C_GetResources (hi2c);

  if (i2c != NULL) {
    event = ARM_I2C_EVENT_TRANSFER_DONE | ARM_I2C_EVENT_TRANSFER_INCOMPLETE;

    if ((err & HAL_I2C_ERROR_BERR) != 0U) {
      /* Bus error */
      event |= ARM_I2C_EVENT_BUS_ERROR;
      i2c->info->status.bus_error = 1U;
    }

    if ((err & HAL_I2C_ERROR_ARLO) != 0U) {
      /* Arbitration lost */
      event |= ARM_I2C_EVENT_ARBITRATION_LOST;
      i2c->info->status.arbitration_lost = 1U;
    }

    if((err & HAL_I2C_ERROR_AF) != 0U) {
      /* Acknowledge not received */
      if ((hi2c->XferCount == 0) && (hi2c->XferSize > 0)) {
        /* Slave address was not acknowledged */
        event |= ARM_I2C_EVENT_ADDRESS_NACK;
      }
    }

    i2c->info->status.busy = 0U;

    if (i2c->info->cb_event != NULL) {
      i2c->info->cb_event (event);
    }
  }
}

/* Abort completed */
void HAL_I2C_AbortCpltCallback(I2C_HandleTypeDef *hi2c) {
  I2C_RESOURCES *i2c;

  i2c = I2C_GetResources (hi2c);

  if (i2c != NULL) {
    i2c->info->abort = 1U;
  }
}


#if defined(MX_I2C1)
/* I2C1 Driver wrapper functions */
static int32_t I2C1_Initialize (ARM_I2C_SignalEvent_t cb_event) {
  return I2C_Initialize(cb_event, &I2C1_Resources);
}
static int32_t I2C1_Uninitialize (void) {
  return I2C_Uninitialize(&I2C1_Resources);
}
static int32_t I2C1_PowerControl (ARM_POWER_STATE state) {
  return I2C_PowerControl(state, &I2C1_Resources);
}
static int32_t I2C1_MasterTransmit (uint32_t addr, const uint8_t *data, uint32_t num, bool xfer_pending) {
  return I2C_MasterTransmit(addr, data, num, xfer_pending, &I2C1_Resources);
}
static int32_t I2C1_MasterReceive (uint32_t addr, uint8_t *data, uint32_t num, bool xfer_pending) {
  return I2C_MasterReceive(addr, data, num, xfer_pending, &I2C1_Resources);
}
static int32_t I2C1_SlaveTransmit (const uint8_t *data, uint32_t num) {
  return I2C_SlaveTransmit(data, num, &I2C1_Resources);
}
static int32_t I2C1_SlaveReceive (uint8_t *data, uint32_t num) {
  return I2C_SlaveReceive(data, num, &I2C1_Resources);
}
static int32_t I2C1_GetDataCount (void) {
  return I2C_GetDataCount(&I2C1_Resources);
}
static int32_t I2C1_Control (uint32_t control, uint32_t arg) {
  return I2C_Control(control, arg, &I2C1_Resources);
}
static ARM_I2C_STATUS I2C1_GetStatus (void) {
  return I2C_GetStatus(&I2C1_Resources);
}

/* I2C1 Driver Control Block */
ARM_DRIVER_I2C Driver_I2C1 = {
  I2CX_GetVersion,
  I2CX_GetCapabilities,
  I2C1_Initialize,
  I2C1_Uninitialize,
  I2C1_PowerControl,
  I2C1_MasterTransmit,
  I2C1_MasterReceive,
  I2C1_SlaveTransmit,
  I2C1_SlaveReceive,
  I2C1_GetDataCount,
  I2C1_Control,
  I2C1_GetStatus
};
#endif /* MX_I2C1 */


#if defined(MX_I2C2)
/* I2C2 Driver wrapper functions */
static int32_t I2C2_Initialize (ARM_I2C_SignalEvent_t cb_event) {
  return I2C_Initialize(cb_event, &I2C2_Resources);
}
static int32_t I2C2_Uninitialize (void) {
  return I2C_Uninitialize(&I2C2_Resources);
}
static int32_t I2C2_PowerControl (ARM_POWER_STATE state) {
  return I2C_PowerControl(state, &I2C2_Resources);
}
static int32_t I2C2_MasterTransmit (uint32_t addr, const uint8_t *data, uint32_t num, bool xfer_pending) {
  return I2C_MasterTransmit(addr, data, num, xfer_pending, &I2C2_Resources);
}
static int32_t I2C2_MasterReceive (uint32_t addr, uint8_t *data, uint32_t num, bool xfer_pending) {
  return I2C_MasterReceive(addr, data, num, xfer_pending, &I2C2_Resources);
}
static int32_t I2C2_SlaveTransmit (const uint8_t *data, uint32_t num) {
  return I2C_SlaveTransmit(data, num, &I2C2_Resources);
}
static int32_t I2C2_SlaveReceive (uint8_t *data, uint32_t num) {
  return I2C_SlaveReceive(data, num, &I2C2_Resources);
}
static int32_t I2C2_GetDataCount (void) {
  return I2C_GetDataCount(&I2C2_Resources);
}
static int32_t I2C2_Control (uint32_t control, uint32_t arg) {
  return I2C_Control(control, arg, &I2C2_Resources);
}
static ARM_I2C_STATUS I2C2_GetStatus (void) {
  return I2C_GetStatus(&I2C2_Resources);
}

/* I2C2 Driver Control Block */
ARM_DRIVER_I2C Driver_I2C2 = {
  I2CX_GetVersion,
  I2CX_GetCapabilities,
  I2C2_Initialize,
  I2C2_Uninitialize,
  I2C2_PowerControl,
  I2C2_MasterTransmit,
  I2C2_MasterReceive,
  I2C2_SlaveTransmit,
  I2C2_SlaveReceive,
  I2C2_GetDataCount,
  I2C2_Control,
  I2C2_GetStatus
};
#endif /* MX_I2C2 */


#if defined(MX_I2C3)
/* I2C3 Driver wrapper functions */
static int32_t I2C3_Initialize (ARM_I2C_SignalEvent_t cb_event) {
  return I2C_Initialize(cb_event, &I2C3_Resources);
}
static int32_t I2C3_Uninitialize (void) {
  return I2C_Uninitialize(&I2C3_Resources);
}
static int32_t I2C3_PowerControl (ARM_POWER_STATE state) {
  return I2C_PowerControl(state, &I2C3_Resources);
}
static int32_t I2C3_MasterTransmit (uint32_t addr, const uint8_t *data, uint32_t num, bool xfer_pending) {
  return I2C_MasterTransmit(addr, data, num, xfer_pending, &I2C3_Resources);
}
static int32_t I2C3_MasterReceive (uint32_t addr, uint8_t *data, uint32_t num, bool xfer_pending) {
  return I2C_MasterReceive(addr, data, num, xfer_pending, &I2C3_Resources);
}
static int32_t I2C3_SlaveTransmit (const uint8_t *data, uint32_t num) {
  return I2C_SlaveTransmit(data, num, &I2C3_Resources);
}
static int32_t I2C3_SlaveReceive (uint8_t *data, uint32_t num) {
  return I2C_SlaveReceive(data, num, &I2C3_Resources);
}
static int32_t I2C3_GetDataCount (void) {
  return I2C_GetDataCount(&I2C3_Resources);
}
static int32_t I2C3_Control (uint32_t control, uint32_t arg) {
  return I2C_Control(control, arg, &I2C3_Resources);
}
static ARM_I2C_STATUS I2C3_GetStatus (void) {
  return I2C_GetStatus(&I2C3_Resources);
}

/* I2C3 Driver Control Block */
ARM_DRIVER_I2C Driver_I2C3 = {
  I2CX_GetVersion,
  I2CX_GetCapabilities,
  I2C3_Initialize,
  I2C3_Uninitialize,
  I2C3_PowerControl,
  I2C3_MasterTransmit,
  I2C3_MasterReceive,
  I2C3_SlaveTransmit,
  I2C3_SlaveReceive,
  I2C3_GetDataCount,
  I2C3_Control,
  I2C3_GetStatus
};
#endif /* MX_I2C3 */


#if defined(MX_I2C4)
/* I2C4 Driver wrapper functions */
static int32_t I2C4_Initialize (ARM_I2C_SignalEvent_t cb_event) {
  return I2C_Initialize(cb_event, &I2C4_Resources);
}
static int32_t I2C4_Uninitialize (void) {
  return I2C_Uninitialize(&I2C4_Resources);
}
static int32_t I2C4_PowerControl (ARM_POWER_STATE state) {
  return I2C_PowerControl(state, &I2C4_Resources);
}
static int32_t I2C4_MasterTransmit (uint32_t addr, const uint8_t *data, uint32_t num, bool xfer_pending) {
  return I2C_MasterTransmit(addr, data, num, xfer_pending, &I2C4_Resources);
}
static int32_t I2C4_MasterReceive (uint32_t addr, uint8_t *data, uint32_t num, bool xfer_pending) {
  return I2C_MasterReceive(addr, data, num, xfer_pending, &I2C4_Resources);
}
static int32_t I2C4_SlaveTransmit (const uint8_t *data, uint32_t num) {
  return I2C_SlaveTransmit(data, num, &I2C4_Resources);
}
static int32_t I2C4_SlaveReceive (uint8_t *data, uint32_t num) {
  return I2C_SlaveReceive(data, num, &I2C4_Resources);
}
static int32_t I2C4_GetDataCount (void) {
  return I2C_GetDataCount(&I2C4_Resources);
}
static int32_t I2C4_Control (uint32_t control, uint32_t arg) {
  return I2C_Control(control, arg, &I2C4_Resources);
}
static ARM_I2C_STATUS I2C4_GetStatus (void) {
  return I2C_GetStatus(&I2C4_Resources);
}

/* I2C4 Driver Control Block */
ARM_DRIVER_I2C Driver_I2C4 = {
  I2CX_GetVersion,
  I2CX_GetCapabilities,
  I2C4_Initialize,
  I2C4_Uninitialize,
  I2C4_PowerControl,
  I2C4_MasterTransmit,
  I2C4_MasterReceive,
  I2C4_SlaveTransmit,
  I2C4_SlaveReceive,
  I2C4_GetDataCount,
  I2C4_Control,
  I2C4_GetStatus
};
#endif /* MX_I2C4 */


#if defined(MX_I2C5)
/* I2C5 Driver wrapper functions */
static int32_t I2C5_Initialize (ARM_I2C_SignalEvent_t cb_event) {
  return I2C_Initialize(cb_event, &I2C5_Resources);
}
static int32_t I2C5_Uninitialize (void) {
  return I2C_Uninitialize(&I2C5_Resources);
}
static int32_t I2C5_PowerControl (ARM_POWER_STATE state) {
  return I2C_PowerControl(state, &I2C5_Resources);
}
static int32_t I2C5_MasterTransmit (uint32_t addr, const uint8_t *data, uint32_t num, bool xfer_pending) {
  return I2C_MasterTransmit(addr, data, num, xfer_pending, &I2C5_Resources);
}
static int32_t I2C5_MasterReceive (uint32_t addr, uint8_t *data, uint32_t num, bool xfer_pending) {
  return I2C_MasterReceive(addr, data, num, xfer_pending, &I2C5_Resources);
}
static int32_t I2C5_SlaveTransmit (const uint8_t *data, uint32_t num) {
  return I2C_SlaveTransmit(data, num, &I2C5_Resources);
}
static int32_t I2C5_SlaveReceive (uint8_t *data, uint32_t num) {
  return I2C_SlaveReceive(data, num, &I2C5_Resources);
}
static int32_t I2C5_GetDataCount (void) {
  return I2C_GetDataCount(&I2C5_Resources);
}
static int32_t I2C5_Control (uint32_t control, uint32_t arg) {
  return I2C_Control(control, arg, &I2C5_Resources);
}
static ARM_I2C_STATUS I2C5_GetStatus (void) {
  return I2C_GetStatus(&I2C5_Resources);
}

/* I2C5 Driver Control Block */
ARM_DRIVER_I2C Driver_I2C5 = {
  I2CX_GetVersion,
  I2CX_GetCapabilities,
  I2C5_Initialize,
  I2C5_Uninitialize,
  I2C5_PowerControl,
  I2C5_MasterTransmit,
  I2C5_MasterReceive,
  I2C5_SlaveTransmit,
  I2C5_SlaveReceive,
  I2C5_GetDataCount,
  I2C5_Control,
  I2C5_GetStatus
};
#endif /* MX_I2C5 */

/*! \endcond */

回复

使用道具 举报

13

主题

89

回帖

128

积分

初级会员

积分
128
发表于 2023-8-25 22:36:21 | 显示全部楼层
Rise Time 和 Fall Time的选取有什么规则吗?
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106678
QQ
 楼主| 发表于 2023-8-26 01:15:01 | 显示全部楼层
qq1646544 发表于 2023-8-25 22:36
Rise Time 和 Fall Time的选取有什么规则吗?

手册里面有对应的范围,然后算法就是在各种参数的范围内取个值。
回复

使用道具 举报

13

主题

191

回帖

230

积分

高级会员

积分
230
发表于 2023-8-26 09:59:08 | 显示全部楼层
第一个图是什么软件,很好奇
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106678
QQ
 楼主| 发表于 2023-8-26 10:17:21 | 显示全部楼层
zhang0352505 发表于 2023-8-26 09:59
第一个图是什么软件,很好奇

ST早期的excel计算软件,现在都在CubeMX上面计算了。
回复

使用道具 举报

13

主题

191

回帖

230

积分

高级会员

积分
230
发表于 2023-8-26 11:16:09 | 显示全部楼层
eric2013 发表于 2023-8-26 10:17
ST早期的excel计算软件,现在都在CubeMX上面计算了。

CubeMX只有数据,没有这么直观
回复

使用道具 举报

75

主题

684

回帖

909

积分

金牌会员

积分
909
发表于 2023-8-26 11:37:25 | 显示全部楼层
qq1646544 发表于 2023-8-25 22:36
Rise Time 和 Fall Time的选取有什么规则吗?

和你的通信频率有关
回复

使用道具 举报

0

主题

6

回帖

6

积分

新手上路

积分
6
发表于 2023-8-26 14:25:25 来自手机 | 显示全部楼层
eric2013 发表于 2023-8-26 10:17
ST早期的excel计算软件,现在都在CubeMX上面计算了。

cubemx的图形配置是挺好的,但是哪里有文档说明这些配置项的意义?
毕竟不知道意义是有配错的可能,难不成还得看手册?但手册里的寄存器说明和cubemx的界面的配置项之间有不一致(配置项可能是比寄存器更高抽象的)
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106678
QQ
 楼主| 发表于 2023-8-27 09:27:59 | 显示全部楼层
liandao 发表于 2023-8-26 14:25
cubemx的图形配置是挺好的,但是哪里有文档说明这些配置项的意义?
毕竟不知道意义是有配错的可能,难不 ...

看参考手册就行,对应的硬件I2C章节有相应参数说明。
回复

使用道具 举报

55

主题

131

回帖

296

积分

高级会员

积分
296
发表于 2024-1-9 17:29:13 | 显示全部楼层
找到的一个文档,另外还有H7例子里有提供算法文件。

特别不理解那个Rise Time 和 Fall Time,该怎么填?怎么算?查数据手册都不知道对应的IO AC是什么样的?
SCLH and SCLL values depend on the rise and fall time.
The rise time is defined by:
tr= Rp x Cb x 0.8473 (Rp is the pull_up resistor and Cb is the bus capacitance)
The fall time depends on the software configuration of the I/O. Please refer to “I/O AC
characteristics” table in STM32 products datasheets to get the value of fall time.

I2C时序配置DM00074956_ENV2.2.pdf

558.87 KB, 下载次数: 12

i2c_timing_utility.c

11.42 KB, 下载次数: 5

i2c_timing_utility.h

1.14 KB, 下载次数: 4

回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106678
QQ
 楼主| 发表于 2024-1-10 00:48:24 | 显示全部楼层
lindahnu 发表于 2024-1-9 17:29
找到的一个文档,另外还有H7例子里有提供算法文件。

特别不理解那个Rise Time 和 Fall Time,该怎么填? ...

谢谢分享。
回复

使用道具 举报

55

主题

131

回帖

296

积分

高级会员

积分
296
发表于 2024-1-12 14:37:38 | 显示全部楼层

我帖子里的疑问硬汉能解答吗?这个上升和下降时间到底是要怎么配置的
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-4-29 08:42 , Processed in 0.217218 second(s), 28 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表