# 外设

[English]

## 外设时钟门控

与更新之前相同，外设的时钟仍由驱动处理，用户无需对外设模块的时钟门控进行设置。

但是，如果用户想基于组件 `hal` 和 `soc` 开发自己的驱动，请注意时钟门控的头文件引用路径已由 `driver/periph_ctrl.h` 更新为 `esp_private/periph_ctrl.h`。

## RTC 子系统控制

RTC 控制 API 已经从 `driver/rtc_cntl.h` 移动到了 `esp_private/rtc_ctrl.h`。

## ADC

### ADC 单次模式及连续模式驱动

ADC 单次模式的驱动已更新。

- 新的驱动位于组件 `esp_adc` 中，头文件引用路径为 `esp_adc/adc_oneshot.h`。

- 旧版驱动仍然可用，其头文件引用路径为 `driver/adc.h`。

对于 ADC 连续模式驱动，其位置已由组件 `driver` 更新为 `esp_adc`。

- 头文件引用路径由 `driver/adc.h` 更新为 `esp_adc/adc_continuous.h`。

但是，引用两种模式的旧版路径 `driver/adc.h` 会默认触发如下编译警告，可通过配置 Kconfig 选项 CONFIG_ADC_SUPPRESS_DEPRECATE_WARN 关闭该警告。

    legacy adc driver is deprecated, please migrate to use esp_adc/adc_oneshot.h and esp_adc/adc_continuous.h for oneshot mode and continuous mode drivers respectively

### ADC 校准驱动

ADC 校准驱动已更新。

- 新的驱动位于组件 `esp_adc` 中，头文件引用路径为 `esp_adc/adc_cali.h` 和 `esp_adc/adc_cali_scheme.h`。

旧版驱动仍然可用，其头文件引用路径为 `esp_adc_cal.h`。如果用户要使用旧版路径，需要将组件 `esp_adc` 添加到文件 CMakeLists.txt 的组件需求表中。

默认情况下，引用路径 `esp_adc_cal.h` 会默认触发如下编译警告，可通过配置 Kconfig 选项 CONFIG_ADC_CALI_SUPPRESS_DEPRECATE_WARN 关闭该警告。

    legacy adc calibration driver is deprecated, please migrate to use esp_adc/adc_cali.h and esp_adc/adc_cali_scheme.h

### API 更新

- ADC 电源管理 API `adc_power_acquire` 和 `adc_power_release` 已被移至 `esp_private/adc_share_hw_ctrl.h`，用于内部功能。

  > - 更新前，由于硬件勘误表的工作原理，这两个 API 可以被用户调用。

  > - 更新后，ADC 电源管理完全由驱动在内部实现。

  > - 如果用户仍需调用这个 API，可以通过引用路径 `esp_private/adc_share_hw_ctrl.h` 来调用它。

- 更新后， `driver/adc2_wifi_private.h` 已被移至 `esp_private/adc_share_hw_ctrl.h`。

- `adc_unit_t` 中的枚举 `ADC_UNIT_BOTH`， `ADC_UNIT_ALTER` 及 `ADC_UNIT_MAX` 已被删除。

- 由于只有部分芯片支持下列枚举的某些取值，因此将下列枚举删除。如果用户使用了不支持的取值，会造成驱动运行错误。

  > - 枚举 `ADC_CHANNEL_MAX`

  > - 枚举 `ADC_ATTEN_MAX`

  > - 枚举 `ADC_CONV_UNIT_MAX`

- ESP32 中的 API `hall_sensor_read` 已被删除，因此 ESP32 不再支持霍尔传感器。

- API `adc_set_i2s_data_source` 和 `adc_i2s_mode_init` 已被弃用，相关的枚举 `adc_i2s_source_t` 也已被弃用，请使用 `esp_adc/adc_continuous.h` 进行迁移。

- API `adc_digi_filter_reset` ， `adc_digi_filter_set_config` ， `adc_digi_filter_get_config` 和 `adc_digi_filter_enable` 已被移除. 这些接口的行为不被保证。 枚举 `adc_digi_filter_idx_t` ， `adc_digi_filter_mode_t` 和结构体 `adc_digi_iir_filter_t` 已被移除。

- API `esp_adc_cal_characterize` 已被弃用， 请迁移到 `adc_cali_create_scheme_curve_fitting` 或 `adc_cali_create_scheme_line_fitting`.

- API `esp_adc_cal_raw_to_voltage` 已被弃用， 请迁移到 `adc_cali_raw_to_voltage`.

- API `esp_adc_cal_get_voltage` 已被弃用， 请迁移到 `adc_oneshot_get_calibrated_result`.

## GPIO

- 之前的 Kconfig 选项 RTCIO_SUPPORT_RTC_GPIO_DESC 已被删除，因此数组 `rtc_gpio_desc` 已不可用，请使用替代数组 `rtc_io_desc`。

- 更新后，用户回调函数无法再通过读取 GPIO 中断的状态寄存器来获取用于触发中断的 GPIO 管脚的编号。但是，用户可以通过使用回调函数变量来确定该管脚编号。

  > - 更新前，GPIO 中断发生时，GPIO 中断状态寄存器调用用户回调函数之后，会被清空。因此，用户可以在回调函数中读取 GPIO 中断状态寄存器，以便确定触发中断的 GPIO 管脚。

  > - 但是，在调用回调函数后清空中断状态寄存器可能会导致边沿触发的中断丢失。例如，在调用用户回调函数时，如果某个边沿触发的中断 (re) 被触发，该中断会被清除，并且其注册的用户回调函数还未被处理。

  > - 更新后，GPIO 的中断状态寄存器在调用用户回调函数 之前 被清空。因此，用户无法读取 GPIO 中断状态寄存器来确定哪个管脚触发了中断。但是，用户可以通过回调函数变量来传递被触发的管脚编号。

## 定时器组驱动

为统一和简化通用定时器的使用，定时器组驱动已更新为 GPTimer。

尽管我们推荐使用新的驱动 API， 旧版驱动仍然可用，其头文件引用路径为 `driver/timer.h`。但是，引用 `driver/timer.h` 会默认触发如下编译警告，可通过配置 Kconfig 选项 CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN 关闭该警告。

    legacy timer group driver is deprecated, please migrate to driver/gptimer.h

概念和使用方法上的主要更新如下所示：

### 主要概念更新

- 用于识别定时器的 `timer_group_t` 和 `timer_idx_t` 已被删除。在新驱动中，定时器用参数 `gptimer_handle_t` 表示。

- 更新后，定时器的时钟源由 `gptimer_clock_source_t` 定义，之前的时钟源参数 `timer_src_clk_t` 不再使用。

- 更新后，定时器计数方向由 `gptimer_count_direction_t` 定义，之前的计数方向参数 `timer_count_dir_t` 不再使用。

- 更新后，仅支持电平触发的中断， `timer_intr_t` 和 `timer_intr_mode_t` 不再使用。

- 更新后，通过设置标志位 `gptimer_alarm_config_t::auto_reload_on_alarm`， 可以使能自动加载。 `timer_autoreload_t` 不再使用。

### 主要使用方法更新

- 更新后，通过从 `gptimer_new_timer()` 创建定时器示例可以初始化定时器。用户可以在 `gptimer_config_t` 进行一些基本设置，如时钟源，分辨率和计数方向。请注意，无需在驱动安装阶段进行报警事件的特殊设置。

- 更新后，报警事件在 `gptimer_set_alarm_action()` 中进行设置，参数在 `gptimer_alarm_config_t` 中进行设置。

- 更新后，通过 `gptimer_set_raw_count()` 设置计数数值，通过 `gptimer_get_raw_count()` 获取计数数值。驱动不会自动将原始数据同步到 UTC 时间戳。由于定时器的分辨率已知，用户可以自行转换数据。

- 更新后，如果 `gptimer_event_callbacks_t::on_alarm` 被设置为有效的回调函数，驱动程序也会安装中断服务。在回调函数中，用户无需配置底层寄存器，如用于“清除中断状态”，“重新使能事件”的寄存器等。因此， `timer_group_get_intr_status_in_isr` 与 `timer_group_get_auto_reload_in_isr` 这些函数不再使用。

- 更新后，当报警事件发生时，为更新报警配置，用户可以在中断回调中调用 `gptimer_set_alarm_action()`，这样报警事件会被重新使能。

- 更新后，如果用户将 `gptimer_alarm_config_t::auto_reload_on_alarm` 设置为 true，报警事件将会一直被驱动程序使能。

## UART

删除/弃用项目 | 替代 | 备注
--- | --- | ---
`uart_isr_register()` | 无 | 更新后，UART 中断由驱动处理。
`uart_isr_free()` | 无 | 更新后，UART 中断由驱动处理。
`uart_config_t` 中的 `use_ref_tick` | `uart_config_t::source_clk` | 选择时钟源。
`uart_enable_pattern_det_intr()` | `uart_enable_pattern_det_baud_intr()` | 使能模式检测中断。

## I2C

删除/弃用项目 | 替代 | 备注
--- | --- | ---
`i2c_isr_register()` | 无 | 更新后，I2C 中断由驱动处理。
`i2c_isr_register()` | 无 | 更新后，I2C 中断由驱动处理。
`i2c_opmode_t` | 无 | 更新后，该项不再在 esp-idf 中使用。

## SPI

删除/弃用项目 | 替代 | 备注
--- | --- | ---
`spi_cal_clock()` | `spi_get_actual_clock()` | 获取 SPI 真实的工作频率。

- 内部头文件 `spi_common_internal.h` 已被移至 `esp_private/spi_common_internal.h`。

## LEDC

删除/弃用项目 | 替代 | 备注
--- | --- | ---
`ledc_timer_config_t` 中的 `bit_num` | `ledc_timer_config_t::duty_resolution` | 设置占空比分辨率。

## LCD

- LCD 面板的初始化流程也有一些更新。更新后，`esp_lcd_panel_init()` 不再会自动打开显示器。用户需要调用 `esp_lcd_panel_disp_on_off()` 来手动打开显示器。请注意，打开显示器与打开背光是不同的。更新后，打开屏幕前，用户可以烧录一个预定义的图案，这可以避免开机复位后屏幕上的随机噪音。

- 更新后， `esp_lcd_panel_disp_off()` 已被弃用，请使用 `esp_lcd_panel_disp_on_off()` 作为替代。

- 更新后， `dc_as_cmd_phase` 已被删除，SPI LCD 驱动不再支持 9-bit 的 SPI LCD。请使用专用的 GPIO 管脚来控制 LCD D/C 线。

- 更新后，用于注册 RGB 面板的事件回调函数已从 `esp_lcd_rgb_panel_config_t` 更新为单独的 API `esp_lcd_rgb_panel_register_event_callbacks()`。但是，事件回调签名仍保持不变。

- 更新后， `esp_lcd_rgb_panel_config_t` 中的标志位 `relax_on_idle` 被重命名为 `esp_lcd_rgb_panel_config_t::refresh_on_demand`，后者虽表达了同样的含义，但是其命名更有意义。

- 更新后，如果创建 RGB LCD 时，标志位 `refresh_on_demand` 使能，驱动不会在 `esp_lcd_panel_draw_bitmap()` 中进行刷新，用户需要调用 `esp_lcd_rgb_panel_refresh()` 来刷新屏幕。

- 更新后，`esp_lcd_color_space_t` 已被弃用，请使用 `lcd_color_space_t` 来描述色彩空间，使用 `lcd_rgb_element_order_t` 来描述 RGB 颜色的排列顺序。

## 专用的 GPIO 驱动

- 更新后，所有与专用 GPIO 管脚相关的底层 (LL) 函数从 `cpu_ll.h` 中被移至 `dedic_gpio_cpu_ll.h`，并重新命名。

## I2S 驱动

旧版 I2S 驱动在支持 ESP32-C3 和 ESP32-S3 新功能时暴露了很多缺点，为解决这些缺点，I2S 驱动已更新（请参考:doc:I2S Driver <../../../api-reference/peripherals/i2s>）。用户可以通过引用不同 I2S 模式对应的头文件来使用新版驱动的 API，如 esp_driver_i2s/include/driver/i2s_std.h， esp_driver_i2s/include/driver/i2s_pdm.h 以及 esp_driver_i2s/include/driver/i2s_tdm.h。

为保证前向兼容，旧版驱动的 API 仍然在 driver/deprecated/driver/i2s.h 中可用。但使用旧版 API 会触发编译警告，该警告可通过配置 Kconfig 选项 CONFIG_I2S_SUPPRESS_DEPRECATE_WARN 来关闭。

以下是更新后的 I2S 文件概况。

### 主要概念更新

#### 独立的发送通道和接收通道

更新后，I2S 驱动的最小控制单元是发送/接收通道，而不是整个 I2S 控制器（控制器包括多个通道）。

- 用户可以分别控制同一个 I2S 控制器的发送通道和接收通道，即可以通过配置实现分别开启和关闭发送通道和接收通道。

- `i2s_chan_handle_t` 句柄类型用于唯一地识别 I2S 通道。所有的 API 都需要该通道句柄，用户需要对这些通道句柄进行维护。

- 对于 ESP32-C3 和 ESP32-S3，同一个控制器中的发送通道和接收通道可以配置为不同的时钟或不同的模式。

- 但是对于 ESP32 和 ESP32-S2， 同一个控制器中的发送通道和接收通道共享某些硬件资源。因此，配置可能会造成一个通道影响同一个控制器中的另一个通道。

- 通过将 `i2s_port_t::I2S_NUM_AUTO` 设置为 I2S 端口 ID，驱动会搜索可用的发送/接收通道，之后通道会被自动注册到可用的 I2S 控制器上。但是，驱动仍然支持将通道注册到一个特定的端口上。

- 为区分发送/接收通道和声音通道，在更新后的驱动中，“通道 (channel)”一词仅代表发送/接收通道，用“声道 (slot)”来表示声音通道。

#### I2S 模式分类

I2S 通信模式包括以下三种模式，请注意：

- 标准模式：标准模式通常包括两个声道，支持 Philips，MSB 和 PCM（短帧同步）格式，详见 esp_driver_i2s/include/driver/i2s_std.h。

- PDM模式：PDM 模式仅支持两个声道，16 bit 数据位宽，但是 PDM TX 和 PDM RX 的配置略有不同。对于 PDM TX，采样率可通过 `i2s_pdm_tx_clk_config_t::sample_rate` 进行设置，其时钟频率取决于上采样的配置。对于 PDM RX，采样率可通过 `i2s_pdm_rx_clk_config_t::sample_rate` 进行设置，其时钟频率取决于下采样的配置，详见 esp_driver_i2s/include/driver/i2s_pdm.h。

- TDM 模式：TDM 模式可支持高达 16 声道，该模式可工作在 Philips，MSB，PCM（短帧同步）和PCM（长帧同步）格式下，详见 esp_driver_i2s/include/driver/i2s_tdm.h。

在某个模式下分配新通道时，必须通过相应的函数初始化这个通道。我们强烈建议使用辅助宏来生成默认配置，以避免默认值被改动。

#### 独立的声道配置和时钟配置

可以单独进行声道配置和时钟配置。

- 通过调用 `i2s_channel_init_std_mode()`， `i2s_channel_init_pdm_rx_mode()`， `i2s_channel_init_pdm_tx_mode()` 或:cpp:func:i2s_channel_init_tdm_mode 初始化声道/时钟/GPIO 管脚配置。

- 通过调用 `i2s_channel_reconfig_std_slot()`， `i2s_channel_reconfig_pdm_rx_slot()`， `i2s_channel_reconfig_pdm_tx_slot()` 或 `i2s_channel_reconfig_tdm_slot()` 可以在初始化之后改变声道配置。

- 通过调用 `i2s_channel_reconfig_std_clock()`， `i2s_channel_reconfig_pdm_rx_clock()`， `i2s_channel_reconfig_pdm_tx_clock()` 或 `i2s_channel_reconfig_tdm_clock()` 可以在初始化之后改变时钟配置。

- 通过调用 `i2s_channel_reconfig_std_gpio()`， `i2s_channel_reconfig_pdm_rx_gpio()`， `i2s_channel_reconfig_pdm_tx_gpio()` 或 `i2s_channel_reconfig_tdm_gpio()` 可以在初始化之后改变 GPIO 管脚配置。

#### Misc

- 更新后，I2S 驱动利用状态和状态机避免在错误状态下调用 API。

- 更新后，ADC 和 DAC 模式已被删除，只有它们各自专用的驱动及 I2S 旧版驱动还支持这两种模式。

### 主要使用方法更新

请参考以下步骤使用更新后的 I2S 驱动：

1. 通过调用 `i2s_new_channel()` 来获取通道句柄。我们应该在此步骤中指定外设为主机还是从机以及 I2S 端口。此外，驱动负责生成发送通道或接收通道的句柄。不需要同时输入发送通道和接收通道句柄，但需要输入至少一个句柄。输入两个句柄时，驱动会工作在双工模式。在同一端口上，发送通道和接收通道同时可用，并且共享 MCLK，BCLK 和 WS 信号。如果只输入了发送通道句柄或接收通道句柄，该通道只能工作在单工模式。

2. 通过调用 `i2s_channel_init_std_mode()`， `i2s_channel_init_pdm_rx_mode()`， `i2s_channel_init_pdm_tx_mode()` 或 `i2s_channel_init_tdm_mode()` 将通道初始化为指定模式。进行相应的声道、时钟和 GPIO 管脚配置。

3. （可选）通过调用 `i2s_channel_register_event_callback()` 注册 ISR 事件回调函数。I2S 事件由回调函数同步接收，而不是从事件队列中异步接收。

4. 通过调用 `i2s_channel_enable()` 来开启 I2S 通道的硬件资源。在更新后的驱动中，I2S 在安装后不会再自动开启，用户需要确定通道是否已经开启。

5. 分别通过 `i2s_channel_read()` 和 `i2s_channel_write()` 来读取和写入数据。当然，在 `i2s_channel_read()` 中只能输入接收通道句柄，在 `i2s_channel_write()` 中只能输入发送通道句柄。

6. （可选）通过相应的 'reconfig' 函数可以更改声道、时钟和 GPIO 管脚配置，但是更改配置前必须调用 `i2s_channel_disable()`。

7. 通过调用 `i2s_channel_disable()` 可以停止使用 I2S 通道的硬件资源。

8. 不再使用某通道时，通过调用 `i2s_del_channel()` 可以删除和释放该通道资源，但是删除之前必须先停用该通道。

## 用于访问寄存器的宏

更新前，所有用于访问寄存器的宏都可以作为表达式来使用，所以以下命令是允许的:

    uint32_t val = REG_SET_BITS(reg, bits, mask);

在 ESP-IDF v5.0 中，用于写入或读取-修改-写入寄存器的宏不能再作为表达式使用，而只能作为语句使用，这适用于以下宏： `REG_WRITE`， `REG_SET_BIT`， `REG_CLR_BIT`， `REG_SET_BITS`， `REG_SET_FIELD`， `WRITE_PERI_REG`， `CLEAR_PERI_REG_MASK`， `SET_PERI_REG_MASK`， `SET_PERI_REG_BITS`。

为存储要写入寄存器的值，请按以下步骤完成操作:

    uint32_t new_val = REG_READ(reg) | mask;
    REG_WRITE(reg, new_val);

要获得修改后的寄存器的值（该值可能与写入的值不同），要增加一个显示的读取命令:

    REG_SET_BITS(reg, bits, mask);
    uint32_t new_val = REG_READ(reg);

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。