I2C: Make I2C clock frequency accurate

This commit is contained in:
Cao Sen Miao 2022-09-13 18:31:35 +08:00
parent 1d5efaddac
commit 32dca66fb6
8 changed files with 201 additions and 33 deletions

View File

@ -603,14 +603,14 @@ static esp_err_t i2c_hw_fsm_reset(i2c_port_t i2c_num)
// A workaround for avoiding cause timeout issue when using
// hardware reset.
#if !SOC_I2C_SUPPORT_HW_FSM_RST
int scl_low_period, scl_high_period;
int scl_low_period, scl_high_period, scl_wait_high_period;
int scl_start_hold, scl_rstart_setup;
int scl_stop_hold, scl_stop_setup;
int sda_hold, sda_sample;
int timeout;
uint8_t filter_cfg;
i2c_hal_get_scl_timing(&(i2c_context[i2c_num].hal), &scl_high_period, &scl_low_period);
i2c_hal_get_scl_clk_timing(&(i2c_context[i2c_num].hal), &scl_high_period, &scl_low_period, &scl_wait_high_period);
i2c_hal_get_start_timing(&(i2c_context[i2c_num].hal), &scl_rstart_setup, &scl_start_hold);
i2c_hal_get_stop_timing(&(i2c_context[i2c_num].hal), &scl_stop_setup, &scl_stop_hold);
i2c_hal_get_sda_timing(&(i2c_context[i2c_num].hal), &sda_sample, &sda_hold);
@ -625,7 +625,7 @@ static esp_err_t i2c_hw_fsm_reset(i2c_port_t i2c_num)
i2c_hal_master_init(&(i2c_context[i2c_num].hal), i2c_num);
i2c_hal_disable_intr_mask(&(i2c_context[i2c_num].hal), I2C_LL_INTR_MASK);
i2c_hal_clr_intsts_mask(&(i2c_context[i2c_num].hal), I2C_LL_INTR_MASK);
i2c_hal_set_scl_timing(&(i2c_context[i2c_num].hal), scl_high_period, scl_low_period);
i2c_hal_set_scl_clk_timing(&(i2c_context[i2c_num].hal), scl_high_period, scl_low_period, scl_wait_high_period);
i2c_hal_set_start_timing(&(i2c_context[i2c_num].hal), scl_rstart_setup, scl_start_hold);
i2c_hal_set_stop_timing(&(i2c_context[i2c_num].hal), scl_stop_setup, scl_stop_hold);
i2c_hal_set_sda_timing(&(i2c_context[i2c_num].hal), sda_sample, sda_hold);

View File

@ -887,6 +887,39 @@ static inline void i2c_ll_update(i2c_dev_t *hw)
;// ESP32 do not support
}
/**
* @brief Configure I2C SCL timing
*
* @param hw Beginning address of the peripheral registers
* @param high_period The I2C SCL hight period (in core clock cycle, hight_period > 2)
* @param low_period The I2C SCL low period (in core clock cycle, low_period > 1)
* @param wait_high_period The I2C SCL wait rising edge period.
*
* @return None.
*/
static inline void i2c_ll_set_scl_clk_timing(i2c_dev_t *hw, int high_period, int low_period, int wait_high_period)
{
(void)wait_high_period;
hw->scl_low_period.period = low_period;
hw->scl_high_period.period = high_period;
}
/**
* @brief Get I2C SCL timing configuration
*
* @param hw Beginning address of the peripheral registers
* @param high_period Pointer to accept the SCL high period
* @param low_period Pointer to accept the SCL low period
*
* @return None
*/
static inline void i2c_ll_get_scl_clk_timing(i2c_dev_t *hw, int *high_period, int *low_period, int *wait_high_period)
{
*wait_high_period = 0; // Useless
*high_period = hw->scl_high_period.period;
*low_period = hw->scl_low_period.period;
}
#ifdef __cplusplus
}
#endif

View File

@ -111,12 +111,12 @@ static inline void i2c_ll_cal_bus_clk(uint32_t source_clk, uint32_t bus_freq, i2
clk_cal->clkm_div = clkm_div;
clk_cal->scl_low = half_cycle;
// default, scl_wait_high < scl_high
int scl_wait_high = (bus_freq <= 50000) ? 0 : (half_cycle / 8); // compensate the time when freq > 50K
clk_cal->scl_wait_high = scl_wait_high;
clk_cal->scl_high = half_cycle - scl_wait_high;
// Make 80KHz as a boundary here, because when working at lower frequency, too much scl_wait_high will faster the frequency
// according to some hardware behaviors.
clk_cal->scl_wait_high = (bus_freq >= 80*1000) ? (half_cycle / 2 - 2) : (half_cycle / 4);
clk_cal->scl_high = half_cycle - clk_cal->scl_wait_high;
clk_cal->sda_hold = half_cycle / 4;
// scl_wait_high < sda_sample <= scl_high
clk_cal->sda_sample = half_cycle / 2;
clk_cal->sda_sample = half_cycle / 2 + clk_cal->scl_wait_high;
clk_cal->setup = half_cycle;
clk_cal->hold = half_cycle;
//default we set the timeout value to about 10 bus cycles
@ -147,9 +147,14 @@ static inline void i2c_ll_update(i2c_dev_t *hw)
static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_clk_cal_t *bus_cfg)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clk_conf, sclk_div_num, bus_cfg->clkm_div - 1);
//scl period
hw->scl_low_period.period = bus_cfg->scl_low - 2;
hw->scl_high_period.period = bus_cfg->scl_high - 3;
/* According to the Technical Reference Manual, the following timings must be subtracted by 1.
* However, according to the practical measurement and some hardware behaviour, if wait_high_period and scl_high minus one.
* The SCL frequency would be a little higher than expected. Therefore, the solution
* here is not to minus scl_high as well as scl_wait high, and the frequency will be absolutely accurate to all frequency
* to some extent. */
hw->scl_low_period.period = bus_cfg->scl_low - 1;
hw->scl_high_period.period = bus_cfg->scl_high;
hw->scl_high_period.scl_wait_high_period = bus_cfg->scl_wait_high;
//sda sample
hw->sda_hold.time = bus_cfg->sda_hold - 1;
hw->sda_sample.time = bus_cfg->sda_sample - 1;
@ -904,6 +909,39 @@ static inline void i2c_ll_slave_init(i2c_dev_t *hw)
hw->fifo_conf.fifo_addr_cfg_en = 0;
}
/**
* @brief Configure I2C SCL timing
*
* @param hw Beginning address of the peripheral registers
* @param high_period The I2C SCL hight period (in core clock cycle, hight_period > 2)
* @param low_period The I2C SCL low period (in core clock cycle, low_period > 1)
* @param wait_high_period The I2C SCL wait rising edge period.
*
* @return None.
*/
static inline void i2c_ll_set_scl_clk_timing(i2c_dev_t *hw, int high_period, int low_period, int wait_high_period)
{
hw->scl_low_period.period = low_period;
hw->scl_high_period.period = high_period;
hw->scl_high_period.scl_wait_high_period = wait_high_period;
}
/**
* @brief Get I2C SCL timing configuration
*
* @param hw Beginning address of the peripheral registers
* @param high_period Pointer to accept the SCL high period
* @param low_period Pointer to accept the SCL low period
*
* @return None
*/
static inline void i2c_ll_get_scl_clk_timing(i2c_dev_t *hw, int *high_period, int *low_period, int *wait_high_period)
{
*high_period = hw->scl_high_period.period;
*wait_high_period = hw->scl_high_period.scl_wait_high_period;
*low_period = hw->scl_low_period.period;
}
#ifdef __cplusplus
}
#endif

View File

@ -1,16 +1,8 @@
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
//
// 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
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// 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.
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// The LL layer for I2C register operations
@ -905,6 +897,39 @@ static inline void i2c_ll_update(i2c_dev_t *hw)
;// ESP32S2 do not support
}
/**
* @brief Configure I2C SCL timing
*
* @param hw Beginning address of the peripheral registers
* @param high_period The I2C SCL hight period (in core clock cycle, hight_period > 2)
* @param low_period The I2C SCL low period (in core clock cycle, low_period > 1)
* @param wait_high_period The I2C SCL wait rising edge period.
*
* @return None.
*/
static inline void i2c_ll_set_scl_clk_timing(i2c_dev_t *hw, int high_period, int low_period, int wait_high_period)
{
hw->scl_low_period.period = low_period;
hw->scl_high_period.period = high_period;
hw->scl_high_period.scl_wait_high_period = wait_high_period;
}
/**
* @brief Get I2C SCL timing configuration
*
* @param hw Beginning address of the peripheral registers
* @param high_period Pointer to accept the SCL high period
* @param low_period Pointer to accept the SCL low period
*
* @return None
*/
static inline void i2c_ll_get_scl_clk_timing(i2c_dev_t *hw, int *high_period, int *low_period, int *wait_high_period)
{
*high_period = hw->scl_high_period.period;
*wait_high_period = hw->scl_high_period.scl_wait_high_period;
*low_period = hw->scl_low_period.period;
}
#ifdef __cplusplus
}
#endif

View File

@ -104,11 +104,12 @@ static inline void i2c_ll_cal_bus_clk(uint32_t source_clk, uint32_t bus_freq, i2
clk_cal->clkm_div = clkm_div;
clk_cal->scl_low = half_cycle;
// default, scl_wait_high < scl_high
clk_cal->scl_high = (bus_freq <= 50000) ? half_cycle : (half_cycle / 5 * 4 + 4);
clk_cal->scl_wait_high = half_cycle - clk_cal->scl_high;
clk_cal->sda_hold = half_cycle / 2;
// scl_wait_high < sda_sample <= scl_high
clk_cal->sda_sample = half_cycle / 2;
// Make 80KHz as a boundary here, because when working at lower frequency, too much scl_wait_high will faster the frequency
// according to some hardware behaviors.
clk_cal->scl_wait_high = (bus_freq >= 80*1000) ? (half_cycle / 2 - 2) : (half_cycle / 4);
clk_cal->scl_high = half_cycle - clk_cal->scl_wait_high;
clk_cal->sda_hold = half_cycle / 4;
clk_cal->sda_sample = half_cycle / 2 + clk_cal->scl_wait_high;
clk_cal->setup = half_cycle;
clk_cal->hold = half_cycle;
//default we set the timeout value to about 10 bus cycles
@ -140,10 +141,13 @@ static inline void i2c_ll_set_bus_timing(i2c_dev_t *hw, i2c_clk_cal_t *bus_cfg)
{
hw->clk_conf.sclk_div_num = bus_cfg->clkm_div - 1;
/* According to the Technical Reference Manual, the following timings must be subtracted by 1.
* Moreover, the frequency calculation also shows that we must subtract 3 to the total SCL */
//scl period
hw->scl_low_period.period = bus_cfg->scl_low - 1 - 2;
hw->scl_high_period.period = bus_cfg->scl_high - 1 - 1;
* However, according to the practical measurement and some hardware behaviour, if wait_high_period and scl_high minus one.
* The SCL frequency would be a little higher than expected. Therefore, the solution
* here is not to minus scl_high as well as scl_wait high, and the frequency will be absolutely accurate to all frequency
* to some extent. */
hw->scl_low_period.period = bus_cfg->scl_low - 1;
hw->scl_high_period.period = bus_cfg->scl_high;
hw->scl_high_period.scl_wait_high_period = bus_cfg->scl_wait_high;
//sda sample
hw->sda_hold.time = bus_cfg->sda_hold - 1;
hw->sda_sample.time = bus_cfg->sda_sample - 1;
@ -200,6 +204,23 @@ static inline void i2c_ll_set_scl_timing(i2c_dev_t *hw, int high_period, int low
hw->scl_high_period.scl_wait_high_period = high_period - high_period_output;
}
/**
* @brief Configure I2C SCL timing
*
* @param hw Beginning address of the peripheral registers
* @param high_period The I2C SCL hight period (in core clock cycle, hight_period > 2)
* @param low_period The I2C SCL low period (in core clock cycle, low_period > 1)
* @param wait_high_period The I2C SCL wait rising edge period.
*
* @return None.
*/
static inline void i2c_ll_set_scl_clk_timing(i2c_dev_t *hw, int high_period, int low_period, int wait_high_period)
{
hw->scl_low_period.period = low_period;
hw->scl_high_period.period = high_period;
hw->scl_high_period.scl_wait_high_period = wait_high_period;
}
/**
* @brief Clear I2C interrupt status
*
@ -551,6 +572,22 @@ static inline void i2c_ll_get_scl_timing(i2c_dev_t *hw, int *high_period, int *l
*low_period = hw->scl_low_period.period + 1;
}
/**
* @brief Get I2C SCL timing configuration
*
* @param hw Beginning address of the peripheral registers
* @param high_period Pointer to accept the SCL high period
* @param low_period Pointer to accept the SCL low period
*
* @return None
*/
static inline void i2c_ll_get_scl_clk_timing(i2c_dev_t *hw, int *high_period, int *low_period, int *wait_high_period)
{
*high_period = hw->scl_high_period.period;
*wait_high_period = hw->scl_high_period.scl_wait_high_period;
*low_period = hw->scl_low_period.period;
}
/**
* @brief Write the I2C hardware txFIFO
*

View File

@ -110,6 +110,29 @@ typedef struct {
*/
#define i2c_hal_slave_clr_rx_it(hal) i2c_ll_slave_clr_rx_it((hal)->dev)
/**
* @brief Configure I2C SCL timing
*
* @param hw Beginning address of the peripheral registers
* @param high_period The I2C SCL hight period (in core clock cycle, hight_period > 2)
* @param low_period The I2C SCL low period (in core clock cycle, low_period > 1)
* @param wait_high_period The I2C SCL wait rising edge period.
*
* @return None.
*/
#define i2c_hal_set_scl_clk_timing(hal, high_period, low_period, wait_high_period) i2c_ll_set_scl_clk_timing((hal)->dev, high_period, low_period, wait_high_period)
/**
* @brief Get I2C SCL timing configuration
*
* @param hw Beginning address of the peripheral registers
* @param high_period Pointer to accept the SCL high period
* @param low_period Pointer to accept the SCL low period
*
* @return None
*/
#define i2c_hal_get_scl_clk_timing(hal, high_period, low_period, wait_high_period) i2c_ll_get_scl_clk_timing((hal)->dev, high_period, low_period, wait_high_period)
/**
* @brief Init the I2C master.
*

View File

@ -183,6 +183,12 @@ Explanations for :cpp:member:`i2c_config_t::clk_flags` are as follows:
The clock frequency of SCL in master mode should not be lager than max frequency for SCL mentioned in the table above.
.. note::
The clock frequency of SCL will be influenced by the pull-up resistors and wire capacitance (or might slave capacitance) together. Therefore, users need to choose correct pull-up resistors by themselves to make the frequency accurate. It is recommended by I2C protocol that the pull-up resistors commonly range from 1KOhms to 10KOhms, but different frequencies need different resistors.
Generally speaking, the higher frequency is selected, the smaller resistor should be used (but not less than 1KOhms). This is because high resistor will decline the current, which will lengthen the rising time and reduce the frequency. Usually, range 2KOhms to 5KOhms is what we recommend, but users also might need to make some adjustment depends on their reality.
.. _i2c-api-install-driver:
Install Driver

View File

@ -71,6 +71,12 @@ I2C 驱动程序管理在 I2C 总线上设备的通信,该驱动程序具备
在此阶段,:cpp:func:`i2c_param_config` 还将其他 I2C 配置参数设置为 I2C 总线协议规范中定义的默认值。有关默认值及修改默认值的详细信息,请参考 :ref:`i2c-api-customized-configuration`
.. note::
SCL 的时钟频率会被上拉电阻和线上电容(或是从机电容)一起影响。因此,用户需要自己选择合适的上拉电阻去保证 SCL 时钟频率是准确的。尽管 I2C 协议推荐上拉电阻值为 1K 欧姆到 10K 欧姆,但是需要根据不同的频率需要选择不同的上拉电阻。
通常来说,所选择的频率越高,需要的上拉电阻越小 (但是不要小于 1K 欧姆)。这是因为高电阻会减小电流,这会延长上升时间从而是频率变慢。通常我们推荐的上拉阻值范围为 2K 欧姆到 5K 欧姆,但是用户可能也需要根据他们的实际情况做出一些调整。
.. _i2c-api-install-driver:
安装驱动程序