Skip to content

Commit

Permalink
Documentation and API adjustments for I2S (#309)
Browse files Browse the repository at this point in the history
* Fix mono mode slot mask.

This changes the slot mask configuration in standard mode for
non ESP32/ESP32-S3 chips to use left-channel only mode. This behavior
differs from the ESP-IDF SDK, which appears to be in error due when
a preprocessor macro was renamed.

* Improve I2S documentation.

* Run cargo fmt.

* Fix clippy issues on 4.4.

* Fix another instance of dma_desc for 4.4.

* Add missing doc() attr.
  • Loading branch information
dacut authored Sep 23, 2023
1 parent 9c85dee commit 29360a2
Show file tree
Hide file tree
Showing 13 changed files with 1,060 additions and 310 deletions.
186 changes: 95 additions & 91 deletions src/i2s.rs

Large diffs are not rendered by default.

334 changes: 258 additions & 76 deletions src/i2s/pdm.rs

Large diffs are not rendered by default.

151 changes: 96 additions & 55 deletions src/i2s/std.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
//! Standard mode driver for the ESP32 I2S peripheral.
//!
//! # Microcontroller support for Standard mode
//!
//! | Microcontroller | Standard Rx | Standard Tx |
//! |--------------------|-----------------|-----------------|
//! | ESP32 | I2S0, I2S1 | I2S0, I2S11 |
//! | ESP32-S2 | I2S0 | I2S0 |
//! | ESP32-S3 | I2S0, I2S1 | I2S0, I2S1 |
//! | ESP32-C2 (ESP8684) | _not supported_ | _not supported_ |
//! | ESP32-C3 | I2S0 | I2S0 |
//! | ESP32-C6 | I2S0 | I2S0 |
//! | ESP32-H2 | I2S0 | I2S0 |
use super::*;
use crate::{gpio::*, peripheral::*};

Expand All @@ -9,19 +22,23 @@ pub(super) mod config {
use crate::{gpio::*, i2s::config::*, peripheral::*};
use esp_idf_sys::*;

/// The standard mode configuration for the I2S peripheral.
/// Standard mode configuration for the I2S peripheral.
pub struct StdConfig {
/// The base channel configuration.
#[allow(dead_code)]
pub(super) channel_cfg: Config,

/// Standard mode channel clock configuration.
#[allow(dead_code)]
clk_cfg: StdClkConfig,

/// Standard mode channel slot configuration.
#[allow(dead_code)]
slot_cfg: StdSlotConfig,

/// Standard mode channel GPIO configuration.
#[cfg(not(esp_idf_version_major = "4"))]
#[allow(dead_code)]
gpio_cfg: StdGpioConfig,
}

Expand Down Expand Up @@ -124,8 +141,8 @@ pub(super) mod config {
channel_format: chan_fmt,
communication_format: self.slot_cfg.comm_fmt.as_sdk(),
intr_alloc_flags: 1 << 1, // ESP_INTR_FLAG_LEVEL1
dma_buf_count: self.channel_cfg.dma_desc as i32,
dma_buf_len: self.channel_cfg.frames as i32,
dma_buf_count: self.channel_cfg.dma_buffer_count as i32,
dma_buf_len: self.channel_cfg.frames_per_buffer as i32,
#[cfg(any(esp32, esp32s2))]
use_apll: matches!(self.clk_cfg.clk_src, ClockSource::Apll),
#[cfg(not(any(esp32, esp32s2)))]
Expand Down Expand Up @@ -154,7 +171,7 @@ pub(super) mod config {
}

/// Standard mode channel clock configuration.
#[derive(Clone)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct StdClkConfig {
/// I2S sample rate.
sample_rate_hz: u32,
Expand Down Expand Up @@ -222,10 +239,10 @@ pub(super) mod config {
}

/// The communication format used by the v4 driver.
#[cfg(esp_idf_version_major = "4")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum StdCommFormat {
/// Standard I2S/Philips format.
#[default]
Philips,

/// MSB-aligned format (data present at first bit clock).
Expand All @@ -238,8 +255,8 @@ pub(super) mod config {
PcmLong,
}

#[cfg(esp_idf_version_major = "4")]
impl StdCommFormat {
#[cfg(esp_idf_version_major = "4")]
#[inline(always)]
pub(in crate::i2s) fn as_sdk(&self) -> i2s_comm_format_t {
match self {
Expand All @@ -252,8 +269,7 @@ pub(super) mod config {
}

/// Standard mode GPIO (general purpose input/output) configuration.
#[cfg(not(esp_idf_version_major = "4"))]
#[derive(Default)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct StdGpioConfig {
/// Invert the BCLK signal.
bclk_invert: bool,
Expand All @@ -265,7 +281,6 @@ pub(super) mod config {
ws_invert: bool,
}

#[cfg(not(esp_idf_version_major = "4"))]
impl StdGpioConfig {
/// Create a new standard mode GPIO configuration with the specified inversion flags for BCLK, MCLK, and WS.
pub fn new(bclk_invert: bool, mclk_invert: bool, ws_invert: bool) -> Self {
Expand Down Expand Up @@ -337,8 +352,72 @@ pub(super) mod config {

/// Standard mode channel slot configuration.
///
/// To create a slot configuration, use [StdSlotConfig::philips_slot_default], [StdSlotConfig::pcm_slot_default], or
/// [StdSlotConfig::msb_slot_default], then customize it as needed.
/// To create a slot configuration, use [`StdSlotConfig::philips_slot_default`], [`StdSlotConfig::pcm_slot_default`], or
/// [`StdSlotConfig::msb_slot_default`], then customize it as needed.
///
/// # Note
/// The `slot_mode` and `slot_mask` cause the data to be interpreted in different ways, as noted below.
/// WS is the "word select" signal, sometimes called LRCLK (left/right clock).
///
/// ## Transmit
///
/// Assuming the buffered data contains the following samples (where a sample may be 1, 2, 3, or 4 bytes, depending
/// on `data_bit_width`):
///
/// | **`d[0]`** | **`d[1]`** | **`d[2]`** | **`d[3]`** | **`d[4]`** | **`d[5]`** | **`d[6]`** | **`d[7]`** |
/// |------------|------------|------------|------------|------------|------------|------------|------------|
/// | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
///
/// The actual data on the line will be:
///
/// <table>
/// <thead>
/// <tr><th><code>slot_mode</code></th><th><code>slot_mask</code></th><th colspan=8>Transmitted Data</th></tr>
/// <tr><th></th><th></th><th>WS Low</th><th>WS High</th><th>WS Low</th><th>WS High</th><th>WS Low</th><th>WS High</th><th>WS Low</th><th>WS High</th></tr>
/// </thead>
/// <tbody>
/// <tr><td rowspan=3><code>Mono</code></td><td><code>Left</code></td> <td>11</td><td><font color="red">0</font></td><td>12</td><td><font color="red">0</font></td><td>13</td><td><font color="red">0</font></td><td>14</td><td><font color="red">0</font></td></tr>
/// <tr> <td><code>Right</code></td><td><font color="red">0</font></td><td>11</td><td><font color="red">0</font></td><td>12</td><td><font color="red">0</font></td><td>13</td><td><font color="red">0</font></td><td>14</td></tr>
/// <tr> <td><code>Both</code></td> <td>11</td><td>11</td><td>12</td><td>12</td><td>13</td><td>13</td><td>14</td><td>14</td></tr>
/// <tr><td rowspan=3><code>Stereo</code></td><td><code>Left</code></td> <td>11</td><td><font color="red">0</font></td><td>13</td><td><font color="red">0</font></td><td>15</td><td><font color="red">0</font></td><td>17</td><td><font color="red">0</font></td></tr>
/// <tr> <td><code>Right</code></td><td><font color="red">0</font></td><td>12</td><td><font color="red">0</font></td><td>14</td><td><font color="red">0</font></td><td>16</td><td><font color="red">0</font></td><td>18</td></tr>
/// <tr> <td><code>Both</code></td> <td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td></tr>
/// </tbody>
/// </table>
///
///
/// ## Receive
///
/// Assuming the received data contains the following samples (where a sample may be 8, 16, 24, or 32 bits, depending on `data_bit_width`):
///
/// | **WS Low** | **WS High** | **WS Low** | **WS High** | **WS Low** | **WS High** | **WS Low** | **WS High** | |
/// |-------------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|-----|
/// | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ... |
///
/// The actual data in the buffer will be (1-4 bytes, depending on `data_bit_width`):
///
/// <table>
/// <thead>
/// <tr><th><code>slot_mode</code></th><th><code>slot_mask</code></th><th colspan=8>Buffer Contents</th></tr>
/// <tr><th></th><th></th><th><code>d[0]</code></th><th><code>d[1]</code></th><th><code>d[2]</code></th><th><code>d[3]</code></th><th><code>d[4]</code></th><th><code>d[5]</code></th><th><code>d[6]</code></th><th><code>d[7]</code></th></tr>
/// </thead>
/// <tbody>
/// <tr><td rowspan=3><code>Mono</code></td> <td><code>Left</code></td> <td>11</td><td>13</td><td>15</td><td>17</td><td>19</td><td>21</td><td>23</td><td>25</td></tr>
/// <tr> <td><code>Right</code></td><td>12</td><td>14</td><td>16</td><td>18</td><td>20</td><td>22</td><td>24</td><td>26</td></tr>
/// <tr> <td><code>Both</code></td> <td colspan=8><i>Unspecified behavior</i></td></tr>
/// <tr><td rowspan=2><code>Stereo</code></td><td><i>Any</i></td> <td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td></tr>
/// </tbody>
/// </table>
///
/// For details, refer to the
/// _ESP-IDF Programming Guide_ details for your specific microcontroller:
/// * ESP32: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#std-rx-mode).
/// * ESP32-S2: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/i2s.html#std-tx-mode)
/// * ESP32-S3: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#std-tx-mode)
/// * ESP32-C3: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/i2s.html#std-tx-mode).
/// * ESP32-C6: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/i2s.html#std-tx-mode).
/// * ESP32-H2: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/i2s.html#std-tx-mode).
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct StdSlotConfig {
/// I2S sample data bit width (valid data bits per sample).
data_bit_width: DataBitWidth,
Expand Down Expand Up @@ -474,7 +553,7 @@ pub(super) mod config {

/// Configure in Philips format in 2 slots.
pub fn philips_slot_default(bits_per_sample: DataBitWidth, slot_mode: SlotMode) -> Self {
let slot_mask = if slot_mode == SlotMode::Mono && cfg!(any(esp32, esp32s2)) {
let slot_mask = if slot_mode == SlotMode::Mono {
StdSlotMask::Left
} else {
StdSlotMask::Both
Expand Down Expand Up @@ -508,7 +587,7 @@ pub(super) mod config {

/// Configure in PCM (short) format in 2 slots.
pub fn pcm_slot_default(bits_per_sample: DataBitWidth, slot_mode: SlotMode) -> Self {
let slot_mask = if slot_mode == SlotMode::Mono && cfg!(any(esp32, esp32s2)) {
let slot_mask = if slot_mode == SlotMode::Mono {
StdSlotMask::Left
} else {
StdSlotMask::Both
Expand Down Expand Up @@ -542,7 +621,7 @@ pub(super) mod config {

/// Configure in MSB format in 2 slots.
pub fn msb_slot_default(bits_per_sample: DataBitWidth, slot_mode: SlotMode) -> Self {
let slot_mask = if slot_mode == SlotMode::Mono && cfg!(any(esp32, esp32s2)) {
let slot_mask = if slot_mode == SlotMode::Mono {
StdSlotMask::Left
} else {
StdSlotMask::Both
Expand Down Expand Up @@ -597,42 +676,10 @@ pub(super) mod config {
}
}

// The derived Clone appears to have problems with some of the cfg attributes in rust-analyzer.
impl Clone for StdSlotConfig {
fn clone(&self) -> Self {
Self {
data_bit_width: self.data_bit_width,
slot_bit_width: self.slot_bit_width,
slot_mode: self.slot_mode,
slot_mask: self.slot_mask,
#[cfg(not(esp_idf_version_major = "4"))]
ws_width: self.ws_width,
#[cfg(not(esp_idf_version_major = "4"))]
ws_polarity: self.ws_polarity,
#[cfg(not(esp_idf_version_major = "4"))]
bit_shift: self.bit_shift,
#[cfg(all(any(esp32, esp32s2), not(esp_idf_version_major = "4")))]
msb_right: self.msb_right,
#[cfg(esp_idf_version_major = "4")]
comm_fmt: self.comm_fmt,
#[cfg(not(any(esp32, esp32s2)))]
left_align: self.left_align,
#[cfg(not(any(esp32, esp32s2)))]
big_endian: self.big_endian,
#[cfg(not(any(esp32, esp32s2)))]
bit_order_lsb: self.bit_order_lsb,
}
}
}

/// I2S slot selection in standard mode.
///
/// The default is `StdSlotMask::Both`.
///
/// # Note
/// This has different meanings in transmit vs receive mode, and stereo vs mono mode. This may have different
/// behaviors on different targets. For details, refer to the I2S API reference.
#[derive(Clone, Copy, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum StdSlotMask {
/// I2S transmits or receives the left slot.
Left,
Expand All @@ -641,16 +688,10 @@ pub(super) mod config {
Right,

/// I2S transmits or receives both slots.
#[default]
Both,
}

impl Default for StdSlotMask {
#[inline(always)]
fn default() -> Self {
Self::Both
}
}

impl StdSlotMask {
/// Convert to the ESP-IDF SDK `i2s_std_slot_mask_t` representation.
#[cfg(not(esp_idf_version_major = "4"))]
Expand Down
Loading

0 comments on commit 29360a2

Please sign in to comment.