diff --git a/cores/esp32/esp32-hal-gpio.c b/cores/esp32/esp32-hal-gpio.c index f0f99db9abf..b5e17eb2d09 100644 --- a/cores/esp32/esp32-hal-gpio.c +++ b/cores/esp32/esp32-hal-gpio.c @@ -135,7 +135,9 @@ extern void ARDUINO_ISR_ATTR __pinMode(uint8_t pin, uint8_t mode) extern void ARDUINO_ISR_ATTR __digitalWrite(uint8_t pin, uint8_t val) { #ifdef RGB_BUILTIN + log_d("RGB_BUILTIN is defined"); if(pin == RGB_BUILTIN){ + log_d("and its the target"); //use RMT to set all channels on/off const uint8_t comm_val = val != 0 ? RGB_BRIGHTNESS : 0; neopixelWrite(RGB_BUILTIN, comm_val, comm_val, comm_val); diff --git a/cores/esp32/esp32-hal-rgb-led.c b/cores/esp32/esp32-hal-rgb-led.c index 61558b86ee6..c8b9b76bedb 100644 --- a/cores/esp32/esp32-hal-rgb-led.c +++ b/cores/esp32/esp32-hal-rgb-led.c @@ -2,6 +2,7 @@ void neopixelWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val){ + log_d("light it up to %d", red_val); rmt_data_t led_data[24]; static rmt_obj_t* rmt_send = NULL; static bool initialized = false; diff --git a/libraries/BLE/src/BLEAddress.h b/libraries/BLE/src/BLEAddress.h index e8fa326a226..a01fe295633 100644 --- a/libraries/BLE/src/BLEAddress.h +++ b/libraries/BLE/src/BLEAddress.h @@ -30,7 +30,7 @@ class BLEAddress { bool operator>(const BLEAddress& otherAddress) const; bool operator>=(const BLEAddress& otherAddress) const; esp_bd_addr_t* getNative(); - std::string toString(); + String toString(); private: esp_bd_addr_t m_address; diff --git a/libraries/BLE/src/BLEAdvertisedDevice.h b/libraries/BLE/src/BLEAdvertisedDevice.h index b785838cb76..e9a5a8c9a5e 100644 --- a/libraries/BLE/src/BLEAdvertisedDevice.h +++ b/libraries/BLE/src/BLEAdvertisedDevice.h @@ -28,82 +28,82 @@ class BLEScan; */ class BLEAdvertisedDevice { public: - BLEAdvertisedDevice(); - - BLEAddress getAddress(); - uint16_t getAppearance(); - std::string getManufacturerData(); - std::string getName(); - int getRSSI(); - BLEScan* getScan(); - std::string getServiceData(); - std::string getServiceData(int i); - BLEUUID getServiceDataUUID(); - BLEUUID getServiceDataUUID(int i); - BLEUUID getServiceUUID(); - BLEUUID getServiceUUID(int i); - int getServiceDataCount(); - int getServiceDataUUIDCount(); - int getServiceUUIDCount(); - int8_t getTXPower(); - uint8_t* getPayload(); - size_t getPayloadLength(); - esp_ble_addr_type_t getAddressType(); - void setAddressType(esp_ble_addr_type_t type); - - - bool isAdvertisingService(BLEUUID uuid); - bool haveAppearance(); - bool haveManufacturerData(); - bool haveName(); - bool haveRSSI(); - bool haveServiceData(); - bool haveServiceUUID(); - bool haveTXPower(); - - std::string toString(); + BLEAdvertisedDevice(); + + BLEAddress getAddress(); + uint16_t getAppearance(); + std::string getManufacturerData(); + std::string getName(); + int getRSSI(); + BLEScan* getScan(); + std::string getServiceData(); + std::string getServiceData(int i); + BLEUUID getServiceDataUUID(); + BLEUUID getServiceDataUUID(int i); + BLEUUID getServiceUUID(); + BLEUUID getServiceUUID(int i); + int getServiceDataCount(); + int getServiceDataUUIDCount(); + int getServiceUUIDCount(); + int8_t getTXPower(); + uint8_t* getPayload(); + size_t getPayloadLength(); + esp_ble_addr_type_t getAddressType(); + void setAddressType(esp_ble_addr_type_t type); + + + bool isAdvertisingService(BLEUUID uuid); + bool haveAppearance(); + bool haveManufacturerData(); + bool haveName(); + bool haveRSSI(); + bool haveServiceData(); + bool haveServiceUUID(); + bool haveTXPower(); + + std::string toString(); private: - friend class BLEScan; - - void parseAdvertisement(uint8_t* payload, size_t total_len=62); - void setPayload(uint8_t* payload, size_t total_len=62); - void setAddress(BLEAddress address); - void setAdFlag(uint8_t adFlag); - void setAdvertizementResult(uint8_t* payload); - void setAppearance(uint16_t appearance); - void setManufacturerData(std::string manufacturerData); - void setName(std::string name); - void setRSSI(int rssi); - void setScan(BLEScan* pScan); - void setServiceData(std::string data); - void setServiceDataUUID(BLEUUID uuid); - void setServiceUUID(const char* serviceUUID); - void setServiceUUID(BLEUUID serviceUUID); - void setTXPower(int8_t txPower); - - bool m_haveAppearance; - bool m_haveManufacturerData; - bool m_haveName; - bool m_haveRSSI; - bool m_haveTXPower; - - - BLEAddress m_address = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); - uint8_t m_adFlag; - uint16_t m_appearance; - int m_deviceType; - std::string m_manufacturerData; - std::string m_name; - BLEScan* m_pScan; - int m_rssi; - std::vector m_serviceUUIDs; - int8_t m_txPower; - std::vector m_serviceData; - std::vector m_serviceDataUUIDs; - uint8_t* m_payload; - size_t m_payloadLength = 0; - esp_ble_addr_type_t m_addressType; + friend class BLEScan; + + void parseAdvertisement(uint8_t* payload, size_t total_len=62); + void setPayload(uint8_t* payload, size_t total_len=62); + void setAddress(BLEAddress address); + void setAdFlag(uint8_t adFlag); + void setAdvertizementResult(uint8_t* payload); + void setAppearance(uint16_t appearance); + void setManufacturerData(std::string manufacturerData); + void setName(std::string name); + void setRSSI(int rssi); + void setScan(BLEScan* pScan); + void setServiceData(std::string data); + void setServiceDataUUID(BLEUUID uuid); + void setServiceUUID(const char* serviceUUID); + void setServiceUUID(BLEUUID serviceUUID); + void setTXPower(int8_t txPower); + + bool m_haveAppearance; + bool m_haveManufacturerData; + bool m_haveName; + bool m_haveRSSI; + bool m_haveTXPower; + + + BLEAddress m_address = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); + uint8_t m_adFlag; + uint16_t m_appearance; + int m_deviceType; + std::string m_manufacturerData; + std::string m_name; + BLEScan* m_pScan; + int m_rssi; + std::vector m_serviceUUIDs; + int8_t m_txPower; + std::vector m_serviceData; + std::vector m_serviceDataUUIDs; + uint8_t* m_payload; + size_t m_payloadLength = 0; + esp_ble_addr_type_t m_addressType; }; /** @@ -115,27 +115,27 @@ class BLEAdvertisedDevice { */ class BLEAdvertisedDeviceCallbacks { public: - virtual ~BLEAdvertisedDeviceCallbacks() {} - /** - * @brief Called when a new scan result is detected. - * - * As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the - * device that was found. During any individual scan, a device will only be detected one time. - */ - virtual void onResult(BLEAdvertisedDevice advertisedDevice) = 0; + virtual ~BLEAdvertisedDeviceCallbacks() {} + /** + * @brief Called when a new scan result is detected. + * + * As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the + * device that was found. During any individual scan, a device will only be detected one time. + */ + virtual void onResult(BLEAdvertisedDevice advertisedDevice) = 0; }; #ifdef CONFIG_BT_BLE_50_FEATURES_SUPPORTED class BLEExtAdvertisingCallbacks { public: - virtual ~BLEExtAdvertisingCallbacks() {} - /** - * @brief Called when a new scan result is detected. - * - * As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the - * device that was found. During any individual scan, a device will only be detected one time. - */ - virtual void onResult(esp_ble_gap_ext_adv_reprot_t report) = 0; + virtual ~BLEExtAdvertisingCallbacks() {} + /** + * @brief Called when a new scan result is detected. + * + * As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the + * device that was found. During any individual scan, a device will only be detected one time. + */ + virtual void onResult(esp_ble_gap_ext_adv_reprot_t report) = 0; }; #endif // CONFIG_BT_BLE_50_FEATURES_SUPPORTED diff --git a/libraries/BLE/src/BLEAdvertising.cpp b/libraries/BLE/src/BLEAdvertising.cpp index 3dcd99b77c3..a3cd7189b2f 100644 --- a/libraries/BLE/src/BLEAdvertising.cpp +++ b/libraries/BLE/src/BLEAdvertising.cpp @@ -297,6 +297,7 @@ void BLEAdvertisementData::addData(std::string data) { if ((m_payload.length() + data.length()) > ESP_BLE_ADV_DATA_LEN_MAX) { return; } + log_d("Appending 0x%X", data.c_str()); m_payload.append(data); } // addData diff --git a/libraries/BLE/src/BLEBeacon.cpp b/libraries/BLE/src/BLEBeacon.cpp index 177926d9ea8..2bf988d7168 100644 --- a/libraries/BLE/src/BLEBeacon.cpp +++ b/libraries/BLE/src/BLEBeacon.cpp @@ -51,6 +51,7 @@ int8_t BLEBeacon::getSignalPower() { * Set the raw data for the beacon record. */ void BLEBeacon::setData(std::string data) { + log_d("getting data of length %d", data.length()); if (data.length() != sizeof(m_beaconData)) { log_e("Unable to set the data ... length passed in was %d and expected %d", data.length(), sizeof(m_beaconData)); return; diff --git a/libraries/BLE/src/BLEEddystoneURL.cpp b/libraries/BLE/src/BLEEddystoneURL.cpp index 1c23e52bf44..85040bc7ab7 100644 --- a/libraries/BLE/src/BLEEddystoneURL.cpp +++ b/libraries/BLE/src/BLEEddystoneURL.cpp @@ -20,8 +20,26 @@ BLEEddystoneURL::BLEEddystoneURL() { memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url)); } // BLEEddystoneURL -std::string BLEEddystoneURL::getData() { - return std::string((char*) &m_eddystoneData, sizeof(m_eddystoneData)); + +BLEEddystoneURL(BLEAdvertisedDevice *advertisedDevice){ + uint8_t *payLoad = advertisedDevice->getPayload(); + beaconUUID = 0xFEAA; + m_eddystoneData.frameType = EDDYSTONE_URL_FRAME_TYPE; + if(payload[11] != 0x10){ // Not Eddystone URL! + log_e("Failed to interpret Advertised Device as Eddystone URL!"); + lengthURL = 0; + m_eddystoneData.advertisedTxPower = 0; + memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url)); + } + lengthURL = payload[7] - 6; // Subtracting 6 Bytes containing header and other data which are not actual URL data + //setData(String((char*)payLoad+11, lengthURL)); + m_eddystoneData.advertisedTxPower = payload[12]; + log_d("using data from [14]=0x%02X=%c up to [%d]=0x%02X=%c", payload[14], payload[14], lengthURL, payload[14+lengthURL], payload[14+lengthURL]); + memcpy(m_eddystoneData.url, payload[14], lengthURL); +} + +String BLEEddystoneURL::getData() { + return String((char*) &m_eddystoneData, sizeof(m_eddystoneData)); } // getData BLEUUID BLEEddystoneURL::getUUID() { @@ -32,13 +50,14 @@ int8_t BLEEddystoneURL::getPower() { return m_eddystoneData.advertisedTxPower; } // getPower -std::string BLEEddystoneURL::getURL() { - return std::string((char*) &m_eddystoneData.url, sizeof(m_eddystoneData.url)); +String BLEEddystoneURL::getURL() { + return String((char*) &m_eddystoneData.url, sizeof(m_eddystoneData.url)); } // getURL -std::string BLEEddystoneURL::getDecodedURL() { - std::string decodedURL = ""; - +String BLEEddystoneURL::getDecodedURL() { + String decodedURL = ""; + log_d("prefix = m_eddystoneData.url[0] 0x%02X",m_eddystoneData.url[0]); + log_e("prefix type m_eddystoneData.url[0]=%d", m_eddystoneData.url[0]); // this is actually debug switch (m_eddystoneData.url[0]) { case 0x00: decodedURL += "http://www."; @@ -60,6 +79,7 @@ std::string BLEEddystoneURL::getDecodedURL() { if (m_eddystoneData.url[i] > 33 && m_eddystoneData.url[i] < 127) { decodedURL += m_eddystoneData.url[i]; } else { + log_d("suffix = m_eddystoneData.url[%d] 0x%02X", i, m_eddystoneData.url[i]); switch (m_eddystoneData.url[i]) { case 0x00: decodedURL += ".com/"; @@ -116,7 +136,7 @@ std::string BLEEddystoneURL::getDecodedURL() { /** * Set the raw data for the beacon record. */ -void BLEEddystoneURL::setData(std::string data) { +void BLEEddystoneURL::setData(String data) { if (data.length() > sizeof(m_eddystoneData)) { log_e("Unable to set the data ... length passed in was %d and max expected %d", data.length(), sizeof(m_eddystoneData)); return; @@ -134,7 +154,7 @@ void BLEEddystoneURL::setPower(int8_t advertisedTxPower) { m_eddystoneData.advertisedTxPower = advertisedTxPower; } // setPower -void BLEEddystoneURL::setURL(std::string url) { +void BLEEddystoneURL::setURL(String url) { if (url.length() > sizeof(m_eddystoneData.url)) { log_e("Unable to set the url ... length passed in was %d and max expected %d", url.length(), sizeof(m_eddystoneData.url)); return; diff --git a/libraries/BLE/src/BLEUUID.cpp b/libraries/BLE/src/BLEUUID.cpp index 45f698afb3a..d6f4d38de66 100644 --- a/libraries/BLE/src/BLEUUID.cpp +++ b/libraries/BLE/src/BLEUUID.cpp @@ -64,6 +64,7 @@ static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size) { * @param [in] value The string to build a UUID from. */ BLEUUID::BLEUUID(std::string value) { + Serial.printf("BLEUUID string constructor- input=\"%s\"\n", value.c_str()); m_valueSet = true; if (value.length() == 4) { m_uuid.len = ESP_UUID_LEN_16; @@ -118,7 +119,6 @@ BLEUUID::BLEUUID(std::string value) { } } //BLEUUID(std::string) - /** * @brief Create a UUID from 16 bytes of memory. * diff --git a/libraries/BLE/src/BLEValue.h b/libraries/BLE/src/BLEValue.h index 31734e489cc..5d2d90702ee 100644 --- a/libraries/BLE/src/BLEValue.h +++ b/libraries/BLE/src/BLEValue.h @@ -7,6 +7,7 @@ #ifndef COMPONENTS_CPP_UTILS_BLEVALUE_H_ #define COMPONENTS_CPP_UTILS_BLEVALUE_H_ +#include #include "sdkconfig.h" #if defined(CONFIG_BLUEDROID_ENABLED) #include @@ -17,22 +18,22 @@ class BLEValue { public: BLEValue(); - void addPart(std::string part); + void addPart(String part); void addPart(uint8_t* pData, size_t length); void cancel(); void commit(); uint8_t* getData(); size_t getLength(); uint16_t getReadOffset(); - std::string getValue(); + String getValue(); void setReadOffset(uint16_t readOffset); - void setValue(std::string value); + void setValue(String value); void setValue(uint8_t* pData, size_t length); private: - std::string m_accumulation; + String m_accumulation; uint16_t m_readOffset; - std::string m_value; + String m_value; }; #endif // CONFIG_BLUEDROID_ENABLED diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino b/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino index e3b40d2c8d5..47cc732f3da 100644 --- a/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino +++ b/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino @@ -1,4 +1,7 @@ /* + NOTE: This is a legacy example using IDF I2S driver. There is an I2S library using Arduino API. + You can find it on relative address `../../../../I2S/` or absolute address `arduino-esp32/libraries/I2S/` + This example demonstrates I2S ADC capability to sample high frequency analog signals. The PWM signal generated with ledc is only for ease of use when first trying out. To sample the generated signal connect default pins 27(PWM) and 32(Sampling) together. diff --git a/libraries/I2S/README.md b/libraries/I2S/README.md new file mode 100644 index 00000000000..92cf5a4cca7 --- /dev/null +++ b/libraries/I2S/README.md @@ -0,0 +1,535 @@ +# I2S / IIS / Inter-IC Sound + +Inter-IC Sound bus is designed for digital transmission of audio data. + + +This library is based on Arduino I2S library (see doc [here](https://docs.arduino.cc/learn/built-in-libraries/i2s)) + +This library mimics the behavior and extends possibilities with ESP-specific functionalities. + +**The main differences are:** + +| Property | Arduino | ESP32 extends | +| ---------------- | ----------- | -------------------------- | +| Bits per sample | 8,16,32 | 24 | +| data channels | 1 (simplex) | 2 (duplex) | +| modes | Philips | PDM, ADC/DAC | +| I2S modules | 1 | ESP32 (only) has 2 modules | +| Pins | Fixed | configurable | + +**In this document you will find overview for this version of library and description of class functions.** + +**The bus uses following signals:** + + - **SCK** - Serial Clock signal. + - **WS** - Word Select - switching between Left and Right channel. + - **SD** - Serial Data - In Simplex mode this is multiplexed data - depending on function calls this is either input or output. + - **SD OUT** - In duplex mode this line is only for outgouing data (TX). + - **SD IN** - In duplex mode this line is only for incoming data (RX). + - **MCLK** - Master Clock signal - runs on higher frequency than SCK and for most cases is not needed. + +**The library uses following data structures:** + + - **Bits per sample (bps)** - determines how many bits is used for each sample (see line below). More bits means greater audio quality, however it is more demanding on memory and timing. The allowed values are `8`, `16`, `24` and `32` i.e. `1`, `2`, `3` or `4` Bytes per sample. + - **Sample** - single channel data. The size of sample depends on `bits per sample` the size of single sample in bytes is simply calculated by `bits per sample / 8`. + - **Frame** - sum of samples across all channels. This library is currently hard-coded to dual channel / stereo, i.e. the number of channels is constantly `2`. The size of frame in Bytes can be calculated as `sample size * number of channels = (bits per sample / 8) * number of channels = (bits per sample / 8) * 2`. For example when `bps=16` the frame has `(16 / 8) * 2 = 4 B`. + - **DMA buffer** - is used in underlying IDF driver and its size is in frames. The Byte size of single DMA buffer is `DMA buf len * frame size = DMA buf len * sample size * number of channels = DMA buf len * (bits per sample / 8) * number of channels = DMA buf len * (bits per sample / 8) * 2`. By default the `DMA buf len = 128` therefore the Byte size is `128 * (bits per sample / 8) * 2`. For example when set `bps=16` then the Byte size is `128 * (16 / 8) * 2 = 512 B`. The functions `setDMABufferFrameSize` and `setDMABufferSampleSize` can be used to change the size (see function section for more details). + - **DMA buffer array** - There is more than one DMA buffer. The number of DMA buffers is defined in constant `_I2S_DMA_BUFFER_COUNT` by default to value `2`. If you want to change it modify file `I2S.cpp`. The Byte size of all DMA buffer can be calculated as `_I2S_DMA_BUFFER_COUNT * DMA buf len * (bits per sample / 8) * 2`. Extending the previous example (`bps=16`, default `dma buf len=128`): `2* 128 *(16 / 8) * 2 = 1024 B` + - **Ring buffer** - is used as an abstraction layer above the DMA buffers. There is one ring buffer for transmitting size and second one for receiving side. The size of a ring buffer will be always automatically set to the sum of all DMA buffers. The size of a single ring buffer in Bytes can be calculated as `DMA buffer len * _I2S_DMA_BUFFER_COUNT * (bits per sample / 8) * number of channels`. + +**Usage of underlying IDF driver** + +Since all ESP32 code runs on FreeRTOS (operating system handling execution time on CPUs and allowing pseudo-parallel execution) this library uses 1 task which monitors messages from underlying IDF I2S driver which sends them whenever it sends contents of single DMA buffer to output (TX) data line, or when it fills a DMA buffer with samples received on input (RX) data line. +Since Arduino utilizes single sample writes and reads heavily, which would cause unbearable lags, there had to be implemented another layer of ring buffers (one for transmitted data and one for received data). The user of this library writes, or reads from this buffer via functions `write` and `read` (exact variants are described in functions section). +The task, upon receiving the message from IDF I2S driver performs either write data from transmit ring buffer into IDFs I2S driver (into TX DMA buffer) or reads received data from IDF I2S driver (from RX DMA buffer) to the receive ring buffer. +Usage of the ring buffers also enabled implementation of functions `available`, `availableForWrite`, `peak` and single sample write/read. + +The IDF I2S driver uses multiple sets of buffers of the same size. The size of ring buffer equals to sum of all those buffers. By default the number of IDF I2S buffers is 2. Usually there should be no need to change this, however if you choose to change it, change the constant `_I2S_DMA_BUFFER_COUNT` in the library, in `src/I2S.cpp`: + +The size of IDF I2S buffer is by default 128. The unit used for this buffer is a frame. A frame means the data of all channels in a WS cycle. For example dual channel 16 bits per sample and default buffer size = 2 * 2 * 128 = 512 Bytes. + +From this we can get also size of the ring buffers. As stated their size is equal to the sum of IDF I2S buffers. For the example of dual channel, 16 bps the size of each ring buffer is 1024 B. +The functions `setDMABufferFrameSize` and `setDMABufferSampleSize` allows you to change the size in frames and samples respectively (see detailed description below). + +On Arduino and most ESPs you can use object `I2S` to use the I2S module. on ESP32 and ESP32-S3 you have the option to use addititonal object `I2S_1`. +I2S module functionality on each SoC differs, please refer to the following table. More info can be found in [IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html?highlight=dac#overview-of-all-modes). + +## Modes (i2s_mode_t) +.. note:: (Arduino MKRZero) First transmitted sample is in Right channel (WS=1). If your audio has swapped left and right channel try feed single zero-value sample before the data stream. + + * **I2S_PHILIPS_MODE** Most common mode, FS signal spans across whole channel period. MSB is transmitted first and data starts SCK falling edge 1 SCK period after WS change. WS 0 = Left channel, 1 = Right channel. See more on [Wikipedia](https://en.wikipedia.org/wiki/I%C2%B2S) + * **I2S_RIGHT_JUSTIFIED_MODE** Does not work on MKRZero, opened [issue](https://github.com/arduino/ArduinoCore-samd/issues/682). Should be expected to the ooposite of `I2S_LEFT_JUSTIFIED_MODE`. + * **I2S_LEFT_JUSTIFIED_MODE** Input data belonging to righ channel are ignored and data belonging to left channel are transmitted in both channels. Example: `int16_t sample[] = {0x0001, 0x0002, 0x0003, 0x0004}` samples with value `0x0001` and `0x0003` belong to the right channel and will be ignored. Samples with value `0x0002` and `0x0004` will be transmitted as follows: Right channel (WS=1) `0x0002` followd by Left channel )WS=0) `0x0004`. this pattern repeats. + * **ADC_DAC_MODE** Outputting and inputting raw analog signal - see [example ADCPlotter](https://github.com/espressif/arduino-esp32/blob/master/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino). Can only be initialized with data pins set to 25 a 26. + * **PDM_STEREO_MODE** Pulse Density Modulation is basically an over-sampled 1-bit signal. Not to be confused with PCM. (ESP32-C3 supports only Transmit mode - read attempts will time-out) + * **PDM_MONO_MODE** Same as previous but transmits only 1 channel. TODO explain timing diagram and data interpretation. + +.. note:: First 2 samples in a stream are zero value, this will not affect audio quality. + +.. note:: Some ESP32 SoCs support PCM, TDM and LCD/Camera modes, however support for those modes is out of scope in this simplified library. If you need to use those mode you will need to use [IDF I2S driver](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html). + + +### Overview of I2S Modes: +| SoC | Philips | ADC/DAC | PDM TX | PDM RX | +| -------- | ----------- | ------- | ------ | ------ | +| ESP32 | I2S + I2S_1 | I2S | I2S | I2S | +| ESP32-S2 | I2S | N/A | N/A | N/A | +| ESP32-C3 | I2S | N/A | I2S | N/A | +| ESP32-S3 | I2S + I2S_1 | N/A | I2S | I2S | + +## Pins +ESP I2S has fully configurable pins. There is a group of setter and getter functions for each pin separately, two setters for all pins at once (detailed description below) and one un-setter for MCLK. Calling the setter (or un-setter) will take effect immediately and does not need driver restart. + +The MCLK pin is usually not needed and for most cases can be ignored. By default the MCLK pin is not attached to any GPIO. To use the MCLK pin it is has to be explicitly configured using `setMclkPin()`, or by providing last parameter to function `setAllPins()`. The MCLK pin can be attached only to few specific pins, the suggested pin is specified by `PIN_I2S_MCLK` constant (each SoC may have different GPIO number). + +### Default pins for SoCs and I2S modules: +**I2S object:** + +| SoC | SCK | WS | SD(OUT) | SDIN | MCLK | +|:---------------------------- |:---:|:--:|:-------:|:----:|:----:| +| ESP32 | 18 | 19 | 21 | 34 | 0 | +| ESP32-C3, ESP32-S2, ESP32-S3 | 18 | 19 | 4 | 5 | 0 | + +**I2S_1 object:** + +| SoC | SCK | WS | SD(OUT) | SDIN | MCLK | +|:-------- |:---:|:--:|:-------:|:----:|:----:| +| ESP32 | 22 | 23 | 27 | 35 | 1 | +| ESP32-S3 | 36 | 37 | 39 | 40 | 35 | + +**DAC pin limitation** + +Unlike other modes, the ADC output is hard-wired to GPIO 25 (R) and 26 (L). DAC output is slightly limited too - please refer to the following table. Attempt to initialize I2S in `ADC_DAC_MODE` with SD IN pin set to anything other than allowed GPIO will result in failure. + +**ADC pin limitation:** + +ADC input uses internally ADC modules - usage of I2S in `ADC_DAC_MODE` may interfere with other usage of these ADC modules. Please refer to the [documentation]() for more info. + +| SoC | GPIOs supporting ADC | +|:-------- |:----------------------------:| +| ESP32 | 0, 2, 4, 12-15, 25-27, 32-39 | +| ESP32-C3 | 0-5 | +| ESP32-S2 | 1-20 | +| ESP32-S3 | 1-20 | + +## Master / Slave[ ](https://en.wiktionary.org/wiki/slave#Etymology) modes +There are two versions of initializer function `begin` - one initializes in master mode and the other one in slave mode (see functions below for details). +The usual use-case is operation in master mode which means that the MCU generates the clock signals which are transmitted to slave devices - usually microphones, speakers or I2S decoders. + +In master mode it is required to connect the device with clock (SCK), word-select (WS) and data (SD) signals. In duplex mode (see below) it is necessary to connect two data lines: input data (SDIN) and output data (SDOUT). The shared data (SD) signal acts as output in duplex mode. + +In slave mode it is necessary to connect the devices with the same signals as in master mode and also master clock (MCLK) signal. If you should try to operat the MCU in slave mode without MCLK, the MCU would not be able properly deduce the timing only from the SCK and there would be no data output from the slave and also the slave would not be able to read any data. + +## Duplex / Simplex +Arduino has only one data pin and the driver is switching between receiving and transmitting upon calling `read` or `write`. On all ESP32s each I2S module has 2 independent data lines, one for transmitting and one for receiving. +For backward compatibility with Arduino we are using DataPin for multiplexed data and DataOutPin + DataInPin for independent duplex communication. +The default mode is simplex and the shared data pin switches function upon calling `read` or `write` same as Arduino. +If you wish to use duplex mode call `setDuplex();` this will change the pin setup to use both data lines. If you want to switch back ti simplex call `setSimplex();` and if you need to get current state call `isDuplex();` (detailed function description below). + + +## Buffers + +### Direct Memory Access buffers +The underlying IDF I2S driver uses a set of Direct Memory Access (DMA) buffers which can read from (in case of transmit line) or written to (in case of receive line) directly by the I2S module without using CPU. This helps to achieve flawless audio experience. However if TX buffers are not filled, or RX buffers emptied fast enough the I2S driver will transmit zeros (silence), or rewrite existing data (missing recorded samples) and this can be perceived as audio artifacts such as digital noise, lags, slowed playback, etc. + +The best way is to fill and read the buffers in chunks and let the CPU be used on other tasks. Once the DMA buffers are transmitted and freed for write, or read and stored elsewhere the CPU can fill the TX DMA buffer again and the I2S driver can fill the RX DMA buffers with new incoming samples. + +The number of DMA buffers can be from 2 - 128 for each data line (TX & RX) this is set by constant `_I2S_DMA_BUFFER_COUNT` in `I2S.cpp`. The default value is `2` and can be changed only in code - requires recompilation. + +The size of each DMA buffer is in sample frames and can be between 8 and 1024. One frame equals to number of channels (in this library always 2) multiplied by Bytes per samples (or `(bits_per_sample/8)`. The default value s `128` and can be changed changed by functions `setDMABufferFrameSize()` and `setDMABufferSampleSize()`, this automatically reinstalls the driver. The DMA buffer size can be read by functions `getDMABufferFrameSize()`, `getDMABufferSampleSize()`, and `getDMABufferByteSize()`. More info can be found in section *FUNCTIONS*. + +### Ring buffers buffers +Original Arduino I2S library uses extensively single-sample writes and reads - such usage would result in poor audio quality. Therefore another layer of buffers has been added - ring buffers. +There are two ring buffers - one for transmit line and one for receiving line. + +The size of ring buffer is equal to the sum of DMA buffers: `ring_buffer_bytes_size = (CHANNEL_NUMBER * (bits_per_sample/8)) * DMABufferFrameSize * _I2S_DMA_BUFFER_COUNT`. +Maximum size of those buffers can be read with functions `getRingBufferSampleSize()` and `getRingBufferByteSize()`. + +To get number of free Bytes in the transmit buffer, that can be actually written is performed with function `availableForWrite()`. To get number of Bytes ready to be read from receiving ring buffer use function `available()`. Changing the size of ring buffer is not possible directly - it is automatically calculated during driver installation based on DMA Buffer size. + +### Known issues +InputSerialPlotter often freezes for a short while - this is caused by the fact that the serial output and the incoming data are not synchronized - portion of the input buffer is written to serial followed by few milliseconds until input data are buffered and ready to be read by the loop. + +## Functions + +#### I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin) + +Constructor - initializes object with default values + +**Parameters:** + +* **uint8_t deviceIndex** In case the SoC has more I2S modules, specify which one is instantiated. Possible values are "0" (for all ESPs) and "1" (only for ESP32 and ESP32-S3) +* **uint8_t clockGenerator** Has no meaning for ESP and is kept only for compatibility +* **uint8_t sdPin** Shared data pin used for simplex mode +* **uint8_t sckPin** Clock pin +* **uint8_t fsPin** Frame Sync (Word Select) pin + +**Default settings:** + +* Input data pin (used for duplex mode) is initialized with `PIN_I2S_SD_IN` +* Out data pin (used for duplex mode) is initialized with `PIN_I2S_SD` +* Mode = `I2S_PHILIPS_MODE` +* Buffer size = `128` + +*** +#### int begin(int mode, int sampleRate, int bitsPerSample) + +*Init in MASTER mode.* + +The SCK and FS pins are driven as outputs using the sample rate. +Initializes IDF I2S driver, creates ring buffers and callback handler task. + +**Parameters:** + +* **int mode** Operation mode (Phillips, Left/Right Justified, ADC+DAC,PDM) see **Modes** for exact enumerations +* **int sampleRate** sampling frequency in Hz. Common values are 8000,11025,16000,22050,32000,44100,64000,88200,128000 +* **int bitsPerSample** Number of bits per one sample (one channel). Possible values are 8,16,24,32 + +**Returns:** 1 on success; 0 on error +*** +#### int begin(int mode, int bitsPerSample) + +*Init in SLAVE mode[.](https://en.wiktionary.org/wiki/slave#Etymology)* + +The SCK and FS pins are inputs and must be controlled(generated) be external source (MASTER device). +Initializes IDF I2S driver, creates ring buffers and callback handler task. + +**Parameters:** + +* **int mode** Operation mode (Phillips, Left/Right Justified, ADC+DAC,PDM) see i2s_mode_t for exact enumerations +* **int bitsPerSample** Number of bits per one sample (one channel). Possible values are 8,16,24,32 + +**Returns:** 1 on success; 0 on error +*** +#### void end() + +*De-initialize IDF I2S driver, frees ring buffers and terminates callback handler task.* +*** +#### int setXPin() + +*Change pin setup for each pin separately.* + +Can be called on both uninitialized and initialized object (before and after `begin`). +The change takes effect immediately and does not need driver restart. + +Note: You can use value `-1` for default value. (Default for `MCLK` is detached state). + +**Parameter:** + +* **int pin** number of GPIO which should be used for the requested pin setup. Any negative value will set default pin number defined in PIN_I2S_X or PIN_I2S1_X. + +**Returns:** 1 on success; 0 on error + +**Function list:** + +* **int setSckPin(int sckPin)** Set Clock pin +* **int setFsPin(int fsPin)** Set Frame Sync (Word Select) pin +* **int setDataPin(int sdPin)** Set shared Data pin for simplex mode +* **int setDataOutPin(int outSdPin)** Set Data Output pin for duplex mode +* **int setDataInPin(int inSdPin)** Set Data Input pin for duplex mode +* **int setMclkPin(int sckPin)** Set Master Clock pin (by default not used-no pin attached) + +*** +#### int setAllPins() + +*Change pin setup for all pins at one call using default values set constants in I2S.h* + +Can be called only on initialized object (after `begin()`). +The change takes effect immediately and does not need driver restart. +The `MCLK` pin will be un-set - i.e. detached from any GPIO. + +**Returns:** 1 on success; 0 on error +*** +#### int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin, int mclkPin=-1) + +*Change pin setup for all pins at one call.* + +Can be called only on initialized object (after `begin()`). +The change takes effect immediately and does not need driver restart. +Note: You can use value `-1` for default value. (Default for `MCLK` is detached state). + +**Parameters:** + +* **int sckPin** Clock pin +* **int fsPin** Frame Sync (Word Select) pin +* **int sdPin** Shared Data pin for simplex mode +* **int outSdPin** Data Output pin for duplex mode +* **int inSdPin** Data Input pin for duplex mode +* **int mclkPin** Master Clock pin - this parameter has default value `-1` i.e. detached state (this parameter can be omitted in function call) + +**Returns:** 1 on success; 0 on error + +*** +#### int unSetMclkPin() +*Unset MCLK pin making it available for other use* + +Can be called on both uninitialized and initialized object (before and after begin). +The change takes effect immediately and does not need driver restart. + +**Returns:** 1 on success; 0 on error +*** +#### int getXPin() + +*Get current pin GPIO number* + +**Returns:** the GPIO number of requested pin + +**Function list:** + +* **int getSckPin()** Get Clock pin +* **int getFsPin()** Get Frame Sync (Word Select) pin +* **int getDataPin()** Get shared Data pin for simplex mode +* **int getDataOutPin()** Get Data Output pin for duplex mode +* **int getDataInPin()** Get Data Input pin for duplex mode +* **int getMclkPin()** Get Master Clock pin + +*** +#### int setDuplex() + +*Change mode to duplex* (default is simplex) + +**Returns:** 1 on success; 0 on error +*** +#### int setSimplex() +Change mode to Simplex + +**Returns:** 1 on success; 0 on error +*** +#### int isDuplex() + +*Get current mode* + +**Returns:** 1 if current mode is Duplex; 0 If current mode is not Duplex +*** +#### int available() +**Returns:** number of Bytes available to read from ring buffer. + +.. note:: The ring buffer is filled automatically by handler task from IDF I2S driver. + +*** +#### int peek() + +Reads a single sample from ring buffer and keeps it available for future read (i.e. does not remove the sample from ring buffer) + +**Returns:** First sample from ring buffer + +.. note:: The ring buffer is filled automatically by handler task from IDF I2S driver. + +*** +#### int read() + +*Reads a single sample* from ring buffer and removes it from the ring buffer. + +**Returns:** First sample from ring buffer + +.. note:: The ring buffer is filled automatically by handler task from IDF I2S driver. + +*** +#### int read(void* buffer, size_t size) + +*Reads an array of samples* from ring buffer and removes them from the ring buffer. + +**Parameters:** + +* **[OUT] void* buffer** Buffer into which the samples will be copied. The buffer must allocated before calling this function! +* **[IN] size_t size** Requested number of bytes to be read + +**Returns:** Number of bytes that were actually read. + +.. note:: Always check the returned value! + +*** +#### virtual int availableForWrite() + +**Returns:** number of bytes that can be written into the ring buffer. +*** +#### virtual int availableSamplesForWrite() + +**Returns:** number of samples that can be written into the ring buffer. +*** +#### virtual size_t write(uint8_t data) + +*Write single sample of 8 bit size.* + +This function is **blocking** - if there is not enough space in ring buffer the function will wait until it can write the sample. + +**Parameter:** +* **uint8_t sample** The sample to be sent + +**Returns:** 1 on successful write; 0 on error = did not write the sample to ring buffer + +.. note:: This functions is used in many examples for it's simplicity, but it's use is discouraged for performance reasons. + +Please consider sending data in arrays using function `size_t write(const uint8_t *buffer, size_t size)` +*** +#### size_t write(int32_t) + +*Write single sample of up to 32 bit size.* + +This function is **blocking** - if there is not enough space in ring buffer the function will wait until it can write the sample. + +**Parameter:** +**int32_t sample** The sample to be sent + +**Returns:** Number of written bytes, if successful the value will be equal to `bitsPerSample/8` + +.. note:: This functions is used in many examples for it's simplicity, but it's use is discouraged for performance reasons. + +Please consider sending data in arrays using function `size_t write(const uint8_t *buffer, size_t size)` + +.. note:: If used with `bits_per_sample == 24` then the Most Significant Byte will be ignored. For example `int32_t sample = 0xFFEEDDCC` will be transferred as an array: `sample[0]=0xCC, sample[1]=0xDD, sample[2]=0xEE` + +*** +#### size_t write(const uint8_t* buffer, size_t size) + +*Write array of samples.* + +This function is **non-blocking** - the function might write only portion of samples into ring buffer, or potentially none at all. Do check the returned value at all times! + +**Parameters:** + +* **uint8_t* buffer** Array of samples +* **size_t size** Number of bytes in array + +**Returns:** Number of bytes successfully written to ring buffer. + +.. note:: This is the preferred function for writing samples. + +*** +#### size_t write(const void* buffer, size_t size) + +*Write array of samples.* + +This function is **non-blocking** - the function might write only portion of samples into ring buffer, or potentially none at all. Do check the returned value at all times! + +**Parameters:** +* **void* buffer** Array of samples +* **size_t size** Number of bytes in array + +**Returns:** Number of bytes successfully written to ring buffer. + +.. note:: This is the preferred function for writing samples. + +*** +#### void flush() + +*Force-write data* from ring buffer to IDF I2S driver. + +This function is useful when sending low amount of data, however such use will lead to low quality audio. + +.. note:: The ring buffer is emptied (sent) automatically by handler task from IDF I2S driver. + +*** +#### void onTransmit(void(\*)(void)) + +*Callback handle* which will be used each time when the IDF I2S driver **transmits** data from buffer. +*** +#### void onReceive(void(\*)(void)) + +*Callback handle* which will be used each time when the IDF I2S driver **receives** data into buffer. +*** +#### int setDMABufferFrameSize(int DMABufferFrameSize) + +*Change the size of DMA buffers.* The unit is number of sample frames `(CHANNEL_NUMBER * (bits_per_sample/8))` + +**Parameter:** + +* **int DMABufferFrameSize** The number of frames in one DMA buffer. The value must be between 8 and 1024 (including). + +**Returns:** 1 on successful setup; 0 on error = could not install driver with new settings or could not take mutex. + +The resulting Bytes size of ring buffers can be calculated: + +`ring_buffer_bytes_size = (CHANNEL_NUMBER * (bits_per_sample/8)) * DMABufferFrameSize * _I2S_DMA_BUFFER_COUNT` + +**Example:** + + * This library statically set to *dual channel*, therefore `CHANNEL_NUMBER` is always **2** + * For this example let's have `bits_per_sample` set to **16** + * Default value of `DMABufferFrameSize` is **128** + * Default value of `_I2S_DMA_BUFFER_COUNT` is **2** +``` +ring_buffer_bytes_size = (bits_per_sample / 8) * DMABufferFrameSize * CHANNEL_NUMBER * _I2S_DMA_BUFFER_COUNT + 1024 = ( 16 / 8) * 128 * 2 * 2 +``` +*** +#### int setDMABufferSampleSize(int DMABufferSampleSize) + +*Change the size of DMA buffers.* The unit is number of samples `bits_per_sample/8` + +This function simply calls `setDMABufferFrameSize(DMABufferSampleSize / CHANNEL_NUMBER);` + +**Parameter:** + +* **int DMABufferFrameSize** The number of samples in one DMA buffer. The value must be always even (multiple of 2) and between 16 and 2048 (including). + +**Returns:** 1 on successful setup; 0 on error = could not install driver with new settings or could not take mutex. + +**Example:** + + * This library statically set to *dual channel*, therefore `CHANNEL_NUMBER` is always **2** + * For this example let's have `bits_per_sample` set to **16** + * Same value as default can be achieved with parameter value `DMABufferSampleSize` set to **256** + * Default value of `_I2S_DMA_BUFFER_COUNT` is **2** +``` +ring_buffer_bytes_size = (bits_per_sample / 8) * DMABufferSampleSize * _I2S_DMA_BUFFER_COUNT + 1024 = ( 16 / 8) * 256 * 2 +``` +*** +#### int getDMABufferFrameSize() +*Get size of single DMA buffer.* + +The unit is number of sample frames: Bytes size of 1 frame = `(CHANNEL_NUMBER * (bits_per_sample / 8))` + +For more info see `setDMABufferFrameSize` +*** +#### int getDMABufferSampleSize() +*Get size of single DMA buffer.* + +The unit is number of samples: 1 sample = `(bits_per_sample / 8)` + +For more info see setDMABufferFrameSize +*** +#### int getDMABufferByteSize() +*Get size of single DMA buffer in Bytes.* + +For more info see setDMABufferFrameSize +*** +#### int getRingBufferSampleSize(); +*Get ring buffer size.* + +The unit is number of samples: 1 sample = `(bits_per_sample / 8)` + +For more info see setDMABufferFrameSize +*** +#### int getRingBufferByteSize(); +Get ring buffer size in Bytes. + +For more info see setDMABufferFrameSize +*** +#### int getI2SNum() + +Get the ID number of I2S module used for particular object. + +Object `I2S` **returns** value `0` + +Object `I2S_1` **returns** value `1` + +Only ESP32 and ESP32-S3 have two modules, other SoCs have only one I2S module controlled by object `I2S` and the return value will always be `0`, the second object `I2S_1` does not exist. +*** +#### bool isInitialized() + +**Returns** `true` if I2S module is correctly initialized and ready for use (function `begin()` was called and returned `1`) + +**Returns** `false` if I2S module has not yet been initialized (function `begin()` was not called, or returned `0`), or it has been de-initialized (function `end()` was called) +*** +#### int getSampleRate() +**Returns:** 0 on un-initialized object, or if the object is initialized as slave. + +On initialized master object **returns** sample rate in Hz (same value which was passed as argument with begin() function) +*** +#### int getBitsPerSample() + +**Returns:** 0 on un-initialized object. + +On initialized object **returns** bits per sample (same value which was passed as argument with begin() function) \ No newline at end of file diff --git a/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino b/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino index 5f3bd93ca9d..83839295da7 100644 --- a/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino +++ b/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino @@ -49,10 +49,10 @@ Second option to measure voltage on trimmer / potentiometer has following connec Tools -> Board -> ESP32 Arduino -> your board 2. Upload sketch Press upload button (arrow in top left corner) - When you see in console line like this: "Connecting........_____.....__" - On your board press and hold Boot button and press EN button shortly. Now you can release both buttons. + When you see in console line like this: "Connecting........_____.....__" press "Boot" button on your board. You should see lines like this: "Writing at 0x00010000... (12 %)" with rising percentage on each line. - If this fails, try the board buttons right after pressing upload button, or reconnect the USB cable. + If this fails, try the pressing the "Boot" button on board right after pressing upload button in IDE. + You can also try reconnect the USB cable. Reconnecting may change the serial port on your computer. 3. Open plotter Tools -> Serial Plotter Enjoy @@ -72,8 +72,7 @@ void setup() { ; // wait for serial port to connect. Needed for native USB port only } - // start I2S at 8 kHz with 32-bits per sample - if (!I2S.begin(ADC_DAC_MODE, 8000, 16)) { + if (!I2S.begin(ADC_DAC_MODE, 44100, 16)) { Serial.println("Failed to initialize I2S!"); while (1); // do nothing } diff --git a/libraries/I2S/examples/BufferedTone/BufferedTone.ino b/libraries/I2S/examples/BufferedTone/BufferedTone.ino new file mode 100644 index 00000000000..2202547f333 --- /dev/null +++ b/libraries/I2S/examples/BufferedTone/BufferedTone.ino @@ -0,0 +1,162 @@ +/* + This example generates a square wave based tone at a specified frequency + and sample rate. Then outputs the data using the I2S interface to a + MAX08357 I2S Amp Breakout board. + + I2S Circuit: + * Arduino/Genuino Zero, MKR family, Nano 33 IoT or any ESP32 + * MAX08357 or PCM510xA: + | Pin | Zero | MKR | Nano | ESP32 | ESP32-S2, ESP32-C3, ESP32-S3 | + | -----|-------|-------|-------|-------|------------------------------| + | GND | GND | GND | GND | GND | GND | + | 5V | 5V | 5V | 5V | 5V | 5V | + | SCK | 1 | 2 | A3 | 18 | 18 | + | FS | 0 | 3 | A2 | 19 | 19 | + | SD | 9 | A6 | 4 | 21 | 4 | + * note: those chips supports only 16/24/32 bits per sample, i.e. 8 bps will be refused = no audio output + + DAC Circuit: + * ESP32 + * Audio amplifier + - Note: + - ESP32 has DAC on GPIO pins 25 and 26. + - Connect speaker(s) or headphones. + + created 17 November 2016 + by Sandeep Mistry + For ESP extended + Tomas Pilny + 2nd September 2021 + */ + +#include +const int frequency = 1000; // frequency of square wave in Hz +const int sampleRate = 8000; // sample rate in Hz +//const int sampleRate = 44100; // sample rate in Hz +const int bps = 16; +const int amplitude = (1<<(bps-1))-1; // amplitude of square wave + +const int halfWavelength = (sampleRate / frequency); // half wavelength of square wave + +int32_t sample = amplitude; // current sample value +int count = 0; + +void *data; +const size_t data_elements = halfWavelength * 2 * CHANNEL_NUMBER; +const size_t data_len = (bps / 8) * data_elements; + +i2s_mode_t mode = I2S_PHILIPS_MODE; // I2S decoder is needed +//i2s_mode_t mode = ADC_DAC_MODE; // bps must be 16; Audio amplifier is needed + +// Mono channel input +// This is ESP specific implementation - +// samples will be automatically copied to both channels inside I2S driver +// If you want to have true mono output use I2S_PHILIPS_MODE and interlay +// second channel with 0-value samples. +// The order of channels is RIGH followed by LEFT +//i2s_mode_t mode = I2S_RIGHT_JUSTIFIED_MODE; // I2S decoder is needed + +void setup() { + Serial.begin(115200); + Serial.println("I2S simple tone"); + + // Generate data for buffer write + data = malloc(data_len); + if(data == NULL){ + Serial.println("Failed to create data buffer!"); + while (1); // do nothing + } + + int high; + int low; + switch(bps){ + case 8: + high = (mode == I2S_PHILIPS_MODE) ? 0x7F : 0xFF; + low = (mode == I2S_PHILIPS_MODE) ? 0x80 : 0x00; + break; + case 16: + high = (mode == I2S_PHILIPS_MODE) ? 0x7FFF : 0xFFFF; + low = (mode == I2S_PHILIPS_MODE) ? 0x8000 : 0x00; + break; + case 24: + // 24 bps is stored in 32 bit data type on lower 3 Bytes (MSB is ignored) + high = (mode == I2S_PHILIPS_MODE) ? 0x007FFFFF : 0x00FFFFFF; + low = (mode == I2S_PHILIPS_MODE) ? 0x00800000 : 0x00; + break; + case 32: + high = (mode == I2S_PHILIPS_MODE) ? 0x7FFFFFFF : 0xFFFFFFFF; + low = (mode == I2S_PHILIPS_MODE) ? 0x80000000 : 0x00; + break; + } + + Serial.printf("Create data buffer for bps %d with values %d=0x%d for high and %d=0x%x for low\n", bps, high, high, low, low); + for(int i = 0; i < data_elements; ++i){ + switch(bps){ + case 8: + //((int8_t*)data)[i] = i <= data_elements/2 ? 0x7F : 0x80; + //((int8_t*)data)[i] = i <= data_elements/2 ? 0xFF : 0x0; + ((int8_t*)data)[i] = (int8_t)(i <= data_elements/2 ? high : low); + break; + case 16: + //((int16_t*)data)[i] = i <= data_elements/2 ? 0x7FFF : 0x8000; + //((int16_t*)data)[i] = i <= data_elements/2 ? 0xFFFF : 0x0; + ((int16_t*)data)[i] = (int16_t)(i <= data_elements/2 ? high : low); + break; + case 24: + // 24 bps is stored in 32 bit data type on lower 3 Bytes (MSB is ignored) + //((int32_t*)data)[i] = i <= data_elements/2 ? 0x007FFFFF : 0x00800000; + //((int32_t*)data)[i] = i <= data_elements/2 ? 0x00FFFFFF : 0x0; + ((int32_t*)data)[i] = (int32_t)(i <= data_elements/2 ? high : low); + break; + case 32: + //((int32_t*)data)[i] = i <= data_elements/2 ? 0x7FFFFFFF : 0x80000000; + //((int32_t*)data)[i] = i <= data_elements/2 ? 0xFFFFFFFF : 0x0; + ((int32_t*)data)[i] = (int32_t)(i <= data_elements/2 ? high : low); + break; + } + } + + I2S.setDMABufferSampleSize(data_elements); + Serial.printf("Set buffer sample size to %d\n", data_elements); + + // start I2S at the sample rate with 16-bits per sample + if (!I2S.begin(mode, sampleRate, bps)) { + Serial.println("Failed to initialize I2S!"); + while (1); // do nothing + } + //I2S.write(data, data_len); + //I2S.write(data, data_len); +} + +void loop() { +//size_t bytes_written; +//i2s_write(I2S_NUM_0, data, data_len, &bytes_written, portMAX_DELAY); + + int avail = I2S.availableForWrite(); + if(avail >= data_len){ + Serial.printf("avail for write = %d; data_len=%d\n", avail, data_len); + I2S.write(data, data_len); + //int written = I2S.write(data, data_len); + //Serial.printf("written = %d\n", written); + }else{ + //Serial.printf("data_len=%d cannot fit inside buffer of size%d => flush\n",data_len, avail); + //I2S.flush(); + //TODO + delay(1); + } + + + /* + if (count % halfWavelength == 0 ) { + // invert the sample every half wavelength count multiple to generate square wave + sample = -1 * sample; + Serial.printf("sample %d\n", sample); + } + + I2S.write(sample); // Right channel + I2S.write(sample); // Left channel + + // increment the counter for the next sample + count++; +*/ +} diff --git a/libraries/I2S/examples/ADCPlotter/.skip.esp32c3 b/libraries/I2S/examples/DualModule/.skip.esp32c2 similarity index 100% rename from libraries/I2S/examples/ADCPlotter/.skip.esp32c3 rename to libraries/I2S/examples/DualModule/.skip.esp32c2 diff --git a/libraries/I2S/examples/FullDuplex/.skip.esp32c3 b/libraries/I2S/examples/DualModule/.skip.esp32c3 similarity index 100% rename from libraries/I2S/examples/FullDuplex/.skip.esp32c3 rename to libraries/I2S/examples/DualModule/.skip.esp32c3 diff --git a/libraries/I2S/examples/InputSerialPlotter/.skip.esp32c3 b/libraries/I2S/examples/DualModule/.skip.esp32s2 similarity index 100% rename from libraries/I2S/examples/InputSerialPlotter/.skip.esp32c3 rename to libraries/I2S/examples/DualModule/.skip.esp32s2 diff --git a/libraries/I2S/examples/DualModule/DualModule.ino b/libraries/I2S/examples/DualModule/DualModule.ino new file mode 100644 index 00000000000..2641763c84e --- /dev/null +++ b/libraries/I2S/examples/DualModule/DualModule.ino @@ -0,0 +1,287 @@ +/* + This example is only for ESP32 and ESP32-S3 which have two separate I2S modules + This example demonstrates simultaneous usage of both I2S module. + The application generates square wave for both modules and transfers to each other. + You can plot the waves with Arduino plotter + + To prepare the example you will need to connect the output pins of the modules as if they were standalone devices. + The pin-out differ for the SoCs - refer to the table of used SoC. + + ESP32 + | Pin | I2S | I2S_1 | + | -----|-------|- --------| + | SCK | 18 | 22 | + | FS | 19 | 23 | + | SD 1 | 21 | 35 | I2S DOUT -> I2S_1 DIN + | SD 2 | 34 | 27 | I2S DIN <- I2S_1 DOUT + + ESP32-S3 + | Pin | I2S | I2S_1 | + | -----|-------|- --------| + | SCK | 18 | 36 | + | FS | 19 | 37 | + | SD 1 | 4 | 40 | I2S DOUT -> I2S_1 DIN + | SD 2 | 5 | 39 | I2S DIN <- I2S_1 DOUT + + created 7 Nov 2022 + by Tomas Pilny + */ + +//#define INTERNAL_CONNECTIONS // uncomment to use without external connections + +//#if SOC_I2S_NUM > 1 + +#include +#include "hal/gpio_hal.h" +#include "esp_rom_gpio.h" + +#include "soc/i2s_periph.h" +#include "soc/gpio_sig_map.h" + +const long sampleRate = 8000; +const int bps = 8; +uint8_t *buffer; +uint8_t *buffer1; + +const int signal_frequency = 1000; // frequency of square wave in Hz +const int signal_frequency1 = 2000; // frequency of square wave in Hz +const int amplitude = (1<<(bps-1))-1; // amplitude of square wave +const int halfWavelength = (sampleRate / signal_frequency); // half wavelength of square wave +const int halfWavelength1 = (sampleRate / signal_frequency1); // half wavelength of square wave +int32_t write_sample = amplitude; // current sample value +//int32_t write_sample = 165; // current sample value +int32_t write_sample1 = amplitude; // current sample value +int count = 0; +int count1 = 0; + +void *write_buffer0; +size_t wr_buf_size0 = (bps/8)*halfWavelength*2; + +void *write_buffer1; +size_t wr_buf_size1 = (bps/8)*halfWavelength1*2; + +//#define TASK_PRIORITY 0 // only reads +//#define TASK_PRIORITY 1 // sometimes working ok; sometimes only writes +#define TASK_PRIORITY 2 // only writes + +/* +static void I2S_write_task(void *args){ + while(true){ + if(I2S.availableForWrite() >= wr_buf_size0){ + Serial.printf("ok write because: I2S.availableForWrite() = %d\n", I2S.availableForWrite()); + I2S.write(write_buffer0, wr_buf_size0); + }else{ + //Serial.printf("no write because: I2S.availableForWrite() = %d\n", I2S.availableForWrite()); + } + delay(1); + } +} + +static void I2S_1_write_task(void *args){ + while(true){ + if(I2S_1.availableForWrite() >= wr_buf_size1){ + Serial.printf("ok write because: I2S_1.availableForWrite() = %d\n", I2S_1.availableForWrite()); + I2S_1.write(write_buffer1, wr_buf_size1); + }else{ + Serial.printf("no write because: I2S_1.availableForWrite() = %d\n", I2S_1.availableForWrite()); + } + delay(1); + } +} +*/ + +void setup() { + Serial.begin(115200, SERIAL_8N1, 16, 17); + while(!Serial); + + Serial.printf("Try to init\nI2S: MCLK=%d, SCK=%d, FS=%d, SD=%d, OUT=%d, IN=%d\nI2S1: MCLK=%d, SCK=%d, FS=%d, SD=%d, OUT=%d, IN=%d\n", + I2S.getMclkPin(), I2S.getSckPin(), I2S.getFsPin(), I2S.getDataPin(), I2S.getDataOutPin(), I2S.getDataInPin(), + I2S_1.getMclkPin(), I2S_1.getSckPin(), I2S_1.getFsPin(), I2S_1.getDataPin(), I2S_1.getDataOutPin(), I2S_1.getDataInPin()); + + I2S.setDMABufferFrameSize(32); + //I2S.setDMABufferFrameSize(halfWavelength > 8 ? halfWavelength : 8); + I2S_1.setDMABufferFrameSize(32); + //I2S_1.setDMABufferFrameSize(halfWavelength1 > 8 ? halfWavelength1 : 8); + + if(!I2S.setDuplex()){ + Serial.println("ERROR - could not set duplex for I2S"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + + if (!I2S.begin(I2S_PHILIPS_MODE, sampleRate, bps)) { + Serial.println("Failed to initialize I2S!"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + buffer = (uint8_t*) malloc(I2S.getDMABufferByteSize()); + if(buffer == NULL){ + Serial.println("Failed to allocate buffer!"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + + if(!I2S_1.setDuplex()){ + Serial.println("ERROR - could not set duplex for I2S_1"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + if (!I2S_1.begin(I2S_PHILIPS_MODE, bps)) { // start in slave mode + Serial.println("Failed to initialize I2S_1!"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + + buffer1 = (uint8_t*) malloc(I2S_1.getDMABufferByteSize()); + if(buffer1 == NULL){ + Serial.println("Failed to allocate buffer1!"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + + Serial.printf("Initialized successfully\nI2S: MCLK=%d, SCK=%d, FS=%d, SD=%d, OUT=%d, IN=%d\nI2S1: MCLK=%d, SCK=%d, FS=%d, SD=%d, OUT=%d, IN=%d\n", + I2S.getMclkPin(), I2S.getSckPin(), I2S.getFsPin(), I2S.getDataPin(), I2S.getDataOutPin(), I2S.getDataInPin(), + I2S_1.getMclkPin(), I2S_1.getSckPin(), I2S_1.getFsPin(), I2S_1.getDataPin(), I2S_1.getDataOutPin(), I2S_1.getDataInPin()); + /* + Serial.printf("Setup done; create tasks with priority %d\n", (int) TASK_PRIORITY); + delay(1000); + + xTaskCreate(I2S_write_task, "I2S_write_task", 4096, NULL, (UBaseType_t) TASK_PRIORITY, NULL); + xTaskCreate(I2S_1_write_task, "I2S_1_write_task", 4096, NULL, (UBaseType_t) TASK_PRIORITY, NULL); + xTaskCreate(read_task, "read_task", 4096, NULL, (UBaseType_t) TASK_PRIORITY, NULL); + Serial.println("Tasks created"); + */ + Serial.printf("write value (amplitude) = %d\n", write_sample); + + // create buffers + write_buffer0 = malloc(wr_buf_size0); + if(write_buffer0 == NULL){ + Serial.printf("ERROR creating write_buffer0; halt."); + while(true); + } + write_buffer1 = malloc(wr_buf_size1); + if(write_buffer1 == NULL){ + Serial.printf("ERROR creating write_buffer0; halt."); + while(true); + } + + for(int i = 0; i < halfWavelength*2; ++i){ + if(i % halfWavelength == 0 ) { + write_sample = -1 * write_sample; + } + switch(bps){ + case 8: + ((int8_t*)write_buffer0)[i] = write_sample; + break; + case 16: + ((int16_t*)write_buffer0)[i] = write_sample; + break; + case 24: + case 32: + ((int32_t*)write_buffer0)[i] = write_sample; + break; + } + } + + for(int i = 0; i < halfWavelength1*2; ++i){ + if(i % halfWavelength1 == 0 ) { + write_sample1 = -1 * write_sample1; + } + switch(bps){ + case 8: + ((int8_t*)write_buffer1)[i] = write_sample1; + break; + case 16: + ((int16_t*)write_buffer1)[i] = write_sample1; + break; + case 24: + case 32: + ((int32_t*)write_buffer1)[i] = write_sample1; + break; + } + } + +/* + xTaskCreate(I2S_write_task, "I2S_write_task", 4096, NULL, (UBaseType_t) TASK_PRIORITY, NULL); + xTaskCreate(I2S_1_write_task, "I2S_1_write_task", 4096, NULL, (UBaseType_t) TASK_PRIORITY, NULL); + Serial.println("Tasks created"); +*/ + +#ifdef INTERNAL_CONNECTIONS + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_MCLK], PIN_FUNC_GPIO); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SCK], PIN_FUNC_GPIO); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_FS], PIN_FUNC_GPIO); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SD_OUT], PIN_FUNC_GPIO); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SD_IN], PIN_FUNC_GPIO); + + gpio_set_direction(PIN_I2S_MCLK, GPIO_MODE_INPUT_OUTPUT); + gpio_set_direction(PIN_I2S_SCK, GPIO_MODE_INPUT_OUTPUT); + gpio_set_direction(PIN_I2S_FS, GPIO_MODE_INPUT_OUTPUT); + gpio_set_direction(PIN_I2S_SD_OUT, GPIO_MODE_INPUT_OUTPUT); + gpio_set_direction(PIN_I2S_SD_IN, GPIO_MODE_INPUT_OUTPUT); + + esp_rom_gpio_connect_out_signal(PIN_I2S_SCK, I2S0I_BCK_OUT_IDX, 0, 0); + esp_rom_gpio_connect_in_signal(PIN_I2S1_SCK, I2S1O_BCK_IN_IDX, 0); + + esp_rom_gpio_connect_out_signal(PIN_I2S_FS, I2S0I_WS_OUT_IDX, 0, 0); + esp_rom_gpio_connect_in_signal(PIN_I2S1_FS, I2S1O_WS_IN_IDX, 0); + + esp_rom_gpio_connect_out_signal(DATA_MASTER_TO_SLAVE, I2S0_DATA_OUT_IDX, 0, 0); + esp_rom_gpio_connect_in_signal(DATA_MASTER_TO_SLAVE, I2S1_DATA_IN_IDX, 0); + + esp_rom_gpio_connect_out_signal(DATA_SLAVE_TO_MASTER, I2S1_DATA_OUT_IDX, 0, 0); + esp_rom_gpio_connect_in_signal(DATA_SLAVE_TO_MASTER, I2S0_DATA_IN_IDX, 0); +#endif +} + +int read_sample; +int read_sample1; + +void loop() { + if(I2S.availableForWrite() >= wr_buf_size0){ + //Serial.printf("ok write because: I2S.availableForWrite() = %d and the buffer is %d Bytes (wr_buf_size0)\n", I2S.availableForWrite(), wr_buf_size0); + I2S.write(write_buffer0, wr_buf_size0); + } + if(I2S_1.availableForWrite() >= wr_buf_size1){ + //Serial.printf("ok write because: I2S_1.availableForWrite() = %d and the buffer is %d Bytes (wr_buf_size1)\n", I2S_1.availableForWrite(), wr_buf_size1); + //Serial.printf("ok write because: I2S_1.availableForWrite() = %d\n", I2S_1.availableForWrite()); + I2S_1.write(write_buffer1, wr_buf_size1); + } + + if(I2S.available()){ + read_sample = I2S.read(); + Serial.printf("Slave to master: 0x%X=%hhd\n", read_sample, (char)read_sample); + //Serial.printf("1->0: 0x%X=%hhd\n", read_sample, (char)read_sample); + } + + if(I2S_1.available()){ + read_sample1 = I2S_1.read(); + //Serial.printf("0->1: 0x%X=%hhd\n", read_sample1, (char)read_sample1); + Serial.printf("Master to slave: 0x%X=%hhd\n", read_sample1, (char)read_sample1); + } + +// if(read_sample && read_sample1){ +/* + if(read_sample || read_sample1){ + Serial.printf("1->0: 0x%X=%hhd; 0->1: 0x%X=%hhd\n", read_sample, (char)read_sample, read_sample1, (char)read_sample1); + } +*/ +} +/* +extern "C" void app_main(){ + setup(); + while(true){loop();} +} +*/ +//#else +// #error "This example cannot run on current SoC - the example requires two I2S modules found in ESP32 and ESP32-S3" +//#endif + + + diff --git a/libraries/I2S/examples/FullDuplex/FullDuplex.ino b/libraries/I2S/examples/FullDuplex/FullDuplex.ino index 9b3625fbd60..e9fb22f679c 100644 --- a/libraries/I2S/examples/FullDuplex/FullDuplex.ino +++ b/libraries/I2S/examples/FullDuplex/FullDuplex.ino @@ -1,18 +1,17 @@ /* This example is only for ESP This example demonstrates simultaneous usage of microphone and speaker using single I2S module. - The application transfers data from input to output + The application transfers data from input to output. + You will need I2S microphone I2S decoder + headphones or speaker to be plugged into the I2S decoder. - Circuit: - * ESP32 - * GND connected GND - * VIN connected 5V - * SCK 5 - * FS 25 - * DIN 35 - * DOUT 26 - * I2S microphone - * I2S decoder + headphones / speaker + | Pin | ESP32 | ESP32-S2, ESP32-C3, ESP32-S3 | + | -------- |-------|------------------------------| + | GND | GND | GND | + | VIN | 5V | 5V | + | SCK | 18 | 18 | + | FS | 19 | 19 | + | SD(DOUT) | 21 | 4 | + | DIN | 34 | 5 | created 8 October 2021 by Tomas Pilny @@ -25,7 +24,7 @@ uint8_t *buffer; void setup() { Serial.begin(115200); - //I2S.setAllPins(5, 25, 35, 26); // you can change default pins; order of pins = (CLK, WS, IN, OUT) + //I2S.setAllPins(18, 19, 21, 21, 34); // you can change default pins; order of pins = (CLK, WS, SD, SDIN, SDOUT) if(!I2S.setDuplex()){ Serial.println("ERROR - could not set duplex"); while(true){ @@ -38,7 +37,7 @@ void setup() { vTaskDelay(10); // Cannot continue } } - buffer = (uint8_t*) malloc(I2S.getBufferSize() * (bitsPerSample / 8)); + buffer = (uint8_t*) malloc(I2S.getDMABufferByteSize()); if(buffer == NULL){ Serial.println("Failed to allocate buffer!"); while(true){ @@ -49,11 +48,9 @@ void setup() { } void loop() { - //I2S.write(I2S.read()); // primitive implementation sample-by-sample + //I2S.write(I2S.read()); // Primitive implementation sample-by-sample is discouraged - // Buffer based implementation - I2S.read(buffer, I2S.getBufferSize() * (bitsPerSample / 8)); - I2S.write(buffer, I2S.getBufferSize() * (bitsPerSample / 8)); - - //optimistic_yield(1000); // yield if last yield occurred before CPU clock cycles ago + // Buffer based implementation which is strongly advised + I2S.read(buffer, I2S.getDMABufferByteSize()); + I2S.write(buffer, I2S.getDMABufferByteSize()); } diff --git a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino index db9f7d0d4f8..99dc2041152 100644 --- a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino +++ b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino @@ -7,11 +7,14 @@ Circuit: * Arduino/Genuino Zero, MKR family and Nano 33 IoT * ICS43432: - * GND connected GND - * 3.3V connected to 3.3V (Zero, Nano, ESP32), VCC (MKR) - * WS connected to pin 0 (Zero) or 3 (MKR), A2 (Nano) or 25 (ESP32) - * CLK connected to pin 1 (Zero) or 2 (MKR), A3 (Nano) or 5 (ESP32) - * SD connected to pin 9 (Zero) or A6 (MKR), 4 (Nano) or 26 (ESP32) + | Pin | Zero | MKR | Nano | ESP32 | ESP32-S2, ESP32-C3, ESP32-S3 | + | -----|-------|-------|-------|-------|------------------------------| + | GND | GND | GND | GND | GND | GND | + | 3.3V | 3.3V | 3.3V | 3.3V | 3.3V | 3.3V | + | SCK | 1 | 2 | A3 | 18 | 18 | + | FS | 0 | 3 | A2 | 19 | 19 | + | SD | 9 | A6 | 4 | 21 | 4 | + created 17 November 2016 by Sandeep Mistry */ @@ -28,17 +31,21 @@ void setup() { } // start I2S at 8 kHz with 32-bits per sample - if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 32)) { + if (!I2S.begin(I2S_PHILIPS_MODE, 32000, 32)) { Serial.println("Failed to initialize I2S!"); while (1); // do nothing } } void loop() { - // read a sample - int sample = I2S.read(); + int sample; + int available = I2S.available(); + //Serial.printf("avail %d\n", available); + if(available){ + sample = I2S.read(); // read a sample - if (sample && sample != -1 && sample != 1) { - Serial.println(sample); + //if(abs(sample) > 2){ + Serial.println(sample); + //} } } diff --git a/libraries/I2S/examples/PinMap/PinMap.ino b/libraries/I2S/examples/PinMap/PinMap.ino new file mode 100644 index 00000000000..afbd4685fbb --- /dev/null +++ b/libraries/I2S/examples/PinMap/PinMap.ino @@ -0,0 +1,88 @@ +// This is actually some personal stuff, not actually example ... +// probably to search usable pins - hence the name + + +/* + This example is only for ESP32 and ESP32-S3 which have two separate I2S modules + This example demonstrates simultaneous usage of both I2S module. + The application generates square wave for both modules and transfers to each other. + You can plot the waves with Arduino plotter + + To prepare the example you will need to connect the output pins of the modules as if they were standalone devices. + The pin-out differ for the SoCs - refer to the table of used SoC. + + ESP32 + | Pin | I2S | I2S_1 | + | -----|-------|- --------| + | SCK | 18 | 22 | + | FS | 19 | 23 | + | SD 1 | 21 | 35 | I2S DOUT -> I2S_1 DIN + | SD 2 | 34 | 27 | I2S DIN <- I2S_1 DOUT + + ESP32-S3 + | Pin | I2S | I2S_1 | + | -----|-------|- --------| + | SCK | 18 | 36 | + | FS | 19 | 37 | + | SD 1 | 4 | 40 | I2S DOUT -> I2S_1 DIN + | SD 2 | 5 | 39 | I2S DIN <- I2S_1 DOUT + + created 7 Nov 2022 + by Tomas Pilny + */ + +#include +#include "hal/gpio_hal.h" +#include "esp_rom_gpio.h" + +#include "soc/i2s_periph.h" +#include "soc/gpio_sig_map.h" + +//uint8_t ban_pins[] = {1,3,6,7,8,9,10,11,24,28,29,30,31}; // ESP32 WROVER-B +uint8_t ban_pins[] = {1,3,6,7,8,9,10,11,20,24,28,29,30,31}; // ESP32 WROVER-B +uint8_t data[] = {0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55}; + +void setup() { + Serial.begin(115200); + while(!Serial); + + if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 8)) { + Serial.println("Failed to initialize I2S!"); + while(true){ + vTaskDelay(10); // Cannot continue + } + } + int ret; + bool skip; + I2S.setDataPin(21); + int ban_size = sizeof(ban_pins) / sizeof(ban_pins[0]); + Serial.println("Initial setup done"); + for(int pin = 22; pin < 34; ++pin){ + skip = false; + Serial.printf("* * * * * * * * Try to set SCK pin to %d * * * * * * * *\n", pin); + for(int i = 0; i < ban_size; ++i){ + if(pin == ban_pins[i]){ + Serial.printf("pin %d is on ban list - skip\n", pin); + skip = true; + break; // out of inner loop + } + } + if(skip){ continue; } + if(pin == I2S.getFsPin()) I2S.setFsPin(pin-1); + if(pin == I2S.getDataPin()) I2S.setDataPin(pin-1); + ret = I2S.setSckPin(pin); + Serial.printf("Set pin returned %s\n", ret ? "OK" : "FAILED"); + Serial.printf("I2S: SCK=%d, FS=%d, SD=%d\n", I2S.getSckPin(), I2S.getFsPin(), I2S.getDataPin()); + uint8_t byte; + while(!Serial.available()){ + I2S.write_blocking(data, sizeof(data) / sizeof(data[0])); + //delay(10); + } + byte = Serial.read(); + Serial.printf("Serial received byte %d = char %c\n", byte, byte); + } +} + +void loop() { + delay(1000); +} diff --git a/libraries/I2S/examples/SimpleTone/.skip.esp32c3 b/libraries/I2S/examples/SimpleTone/.skip.esp32c3 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/SimpleTone/SimpleTone.ino b/libraries/I2S/examples/SimpleTone/SimpleTone.ino index dd312cf8a82..c98d75d4468 100644 --- a/libraries/I2S/examples/SimpleTone/SimpleTone.ino +++ b/libraries/I2S/examples/SimpleTone/SimpleTone.ino @@ -4,20 +4,22 @@ MAX08357 I2S Amp Breakout board. I2S Circuit: - * Arduino/Genuino Zero, MKR family and Nano 33 IoT - * MAX08357: - * GND connected GND - * VIN connected 5V - * LRC connected to pin 0 (Zero) or 3 (MKR), A2 (Nano) or 25 (ESP32) - * BCLK connected to pin 1 (Zero) or 2 (MKR), A3 (Nano) or 5 (ESP32) - * DIN connected to pin 9 (Zero) or A6 (MKR), 4 (Nano) or 26 (ESP32) + * Arduino/Genuino Zero, MKR family, Nano 33 IoT or any ESP32 + * MAX08357 or PCM510xA: + | Pin | Zero | MKR | Nano | ESP32 | ESP32-S2, ESP32-C3, ESP32-S3 | + | -----|-------|-------|-------|-------|------------------------------| + | GND | GND | GND | GND | GND | GND | + | 5V | 5V | 5V | 5V | 5V | 5V | + | SCK | 1 | 2 | A3 | 18 | 18 | + | FS | 0 | 3 | A2 | 19 | 19 | + | SD | 9 | A6 | 4 | 21 | 4 | + * note: those chips supports only 16/24/32 bits per sample, i.e. 8 bps will be refused = no audio output DAC Circuit: - * ESP32 or ESP32-S2 + * ESP32 * Audio amplifier - Note: - ESP32 has DAC on GPIO pins 25 and 26. - - ESP32-S2 has DAC on GPIO pins 17 and 18. - Connect speaker(s) or headphones. created 17 November 2016 @@ -29,17 +31,17 @@ #include const int frequency = 440; // frequency of square wave in Hz -const int amplitude = 500; // amplitude of square wave const int sampleRate = 8000; // sample rate in Hz const int bps = 16; +const int amplitude = (1<<(bps-1))-1; // amplitude of square wave const int halfWavelength = (sampleRate / frequency); // half wavelength of square wave -short sample = amplitude; // current sample value +int32_t sample = amplitude; // current sample value int count = 0; i2s_mode_t mode = I2S_PHILIPS_MODE; // I2S decoder is needed -// i2s_mode_t mode = ADC_DAC_MODE; // Audio amplifier is needed +//i2s_mode_t mode = ADC_DAC_MODE; // Audio amplifier is needed // Mono channel input // This is ESP specific implementation - @@ -66,13 +68,8 @@ void loop() { sample = -1 * sample; } - if(mode == I2S_PHILIPS_MODE || mode == ADC_DAC_MODE){ // write the same sample twice, once for Right and once for Left channel - I2S.write(sample); // Right channel - I2S.write(sample); // Left channel - }else if(mode == I2S_RIGHT_JUSTIFIED_MODE || mode == I2S_LEFT_JUSTIFIED_MODE){ - // write the same only once - it will be automatically copied to the other channel - I2S.write(sample); - } + I2S.write(sample); // Right channel + I2S.write(sample); // Left channel // increment the counter for the next sample count++; diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 78dc5c202df..4465d8f1f17 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -23,11 +23,6 @@ #define _I2S_EVENT_QUEUE_LENGTH 16 #define _I2S_DMA_BUFFER_COUNT 2 // BUFFER COUNT must be between 2 and 128 -#define I2S_INTERFACES_COUNT SOC_I2S_NUM - -#ifndef I2S_DEVICE - #define I2S_DEVICE 0 -#endif #ifndef I2S_CLOCK_GENERATOR #define I2S_CLOCK_GENERATOR 0 // does nothing for ESP @@ -35,19 +30,24 @@ I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin) : _deviceIndex(deviceIndex), - _sdPin(sdPin), // shared data pin +#if SOC_I2S_NUM > 1 + _mclkPin(I2S_PIN_NO_CHANGE), // By default the MCLK pin is not assigned + _inSdPin(deviceIndex == 0 ? PIN_I2S_SD_IN : PIN_I2S1_SD_IN), // input data pin + _outSdPin(deviceIndex == 0 ? PIN_I2S_SD : PIN_I2S1_SD), // output data pin +#else + _mclkPin(I2S_PIN_NO_CHANGE), // By default the MCLK pin is not assigned _inSdPin(PIN_I2S_SD_IN), // input data pin _outSdPin(PIN_I2S_SD), // output data pin +#endif _sckPin(sckPin), // clock pin _fsPin(fsPin), // frame (word) select pin + _sdPin(sdPin), // shared data pin _state(I2S_STATE_IDLE), _bitsPerSample(0), _sampleRate(0), _mode(I2S_PHILIPS_MODE), - _buffer_byte_size(0), - _driverInstalled(false), _initialized(false), _callbackTaskHandle(NULL), @@ -55,7 +55,7 @@ I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, u _i2s_general_mutex(NULL), _input_ring_buffer(NULL), _output_ring_buffer(NULL), - _i2s_dma_buffer_size(128), // Number of frames in each DMA buffer. Frame size = number of channels * Bytes per sample; Must be between 8 and 1024 + _i2s_dma_buffer_frame_size(128), // Number of frames in each DMA buffer. Must be between 8 and 1024. Frame size = number of channels (always 2) * Bytes per sample _driveClock(true), _peek_buff(0), _peek_buff_valid(false), @@ -64,16 +64,16 @@ I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, u _onTransmit(NULL), _onReceive(NULL) { - _i2s_general_mutex = xSemaphoreCreateMutex(); + _i2s_general_mutex = xSemaphoreCreateRecursiveMutex(); if(_i2s_general_mutex == NULL){ - log_e("I2S could not create internal mutex!"); + log_e("(I2S#%d) I2S could not create internal mutex!", _deviceIndex); } } int I2SClass::_createCallbackTask(){ int stack_size = 20000; if(_callbackTaskHandle != NULL){ - log_e("Callback task already exists!"); + log_e("(I2S#%d) Callback task already exists!", _deviceIndex); return 0; // ERR } @@ -81,12 +81,12 @@ int I2SClass::_createCallbackTask(){ onDmaTransferComplete, // Function to implement the task "onDmaTransferComplete", // Name of the task stack_size, // Stack size in words - NULL, // Task input parameter - 2, // Priority of the task + (void *)&_deviceIndex, // Task input parameter + 1, // Priority of the task &_callbackTaskHandle // Task handle. ); if(_callbackTaskHandle == NULL){ - log_e("Could not create callback task"); + log_e("(I2S#%d) Could not create callback task", _deviceIndex); return 0; // ERR } return 1; // OK @@ -94,7 +94,13 @@ int I2SClass::_createCallbackTask(){ int I2SClass::_installDriver(){ if(_driverInstalled){ - log_e("I2S driver is already installed"); + log_e("(I2S#%d) I2S driver is already installed", _deviceIndex); + return 0; // ERR + } + log_d("(I2S#%d) I2S driver install...", _deviceIndex); + + if(_deviceIndex >= SOC_I2S_NUM){ + log_e("Max allowed I2S device number is %d but requested %d", SOC_I2S_NUM-1, _deviceIndex); return 0; // ERR } @@ -109,29 +115,33 @@ int I2SClass::_installDriver(){ if(_mode == ADC_DAC_MODE){ #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) if(_bitsPerSample != 16){ // ADC/DAC can only work in 16-bit sample mode - log_e("ERROR invalid bps for ADC/DAC. Allowed only 16, requested %d", _bitsPerSample); + log_e("(I2S#%d) ERROR invalid bps for ADC/DAC. Allowed only 16, requested %d", _deviceIndex, _bitsPerSample); + // TODO handle data transfer and allow users to use any bps return 0; // ERR } i2s_mode = (esp_i2s::i2s_mode_t)(i2s_mode | esp_i2s::I2S_MODE_DAC_BUILT_IN | esp_i2s::I2S_MODE_ADC_BUILT_IN); #else - log_e("This chip does not support ADC / DAC mode"); + log_e("(I2S#%d) This chip does not support ADC / DAC mode", _deviceIndex); return 0; // ERR #endif }else if(_mode == I2S_PHILIPS_MODE || _mode == I2S_RIGHT_JUSTIFIED_MODE || _mode == I2S_LEFT_JUSTIFIED_MODE){ // End of ADC/DAC mode; start of Normal Philips mode if(_bitsPerSample != 8 && _bitsPerSample != 16 && _bitsPerSample != 24 && _bitsPerSample != 32){ - log_e("Invalid bits per sample for normal mode (requested %d)\nAllowed bps = 8 | 16 | 24 | 32", _bitsPerSample); + log_e("(I2S#%d) Invalid bits per sample for normal mode (requested %d)\nAllowed bps = 8 | 16 | 24 | 32", _deviceIndex, _bitsPerSample); return 0; // ERR } if(_bitsPerSample == 24){ - log_w("Original Arduino library does not support 24 bits per sample.\nKeep that in mind if you should switch back to Arduino"); + log_w("(I2S#%d) Original Arduino library does not support 24 bits per sample.\nKeep that in mind if you should switch back to Arduino", _deviceIndex); } }else if(_mode == PDM_STEREO_MODE || _mode == PDM_MONO_MODE){ // end of Normal Philips mode; start of PDM mode - #if (SOC_I2S_SUPPORTS_PDM_TX && SOC_I2S_SUPPORTS_PDM_RX) + #if (SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX) i2s_mode = (esp_i2s::i2s_mode_t)(i2s_mode | esp_i2s::I2S_MODE_PDM); + #ifndef SOC_I2S_SUPPORTS_PDM_RX + i2s_mode = (esp_i2s::i2s_mode_t)(i2s_mode & ~esp_i2s::I2S_MODE_RX); // remove PRM RX which is not supported on ESP32-C3 + #endif #else - log_e("This chip does not support PDM"); + log_e("(I2S#%d) This chip does not support PDM", _deviceIndex); return 0; // ERR #endif } // Mode @@ -143,34 +153,58 @@ int I2SClass::_installDriver(){ .communication_format = (esp_i2s::i2s_comm_format_t)(esp_i2s::I2S_COMM_FORMAT_STAND_I2S), .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, .dma_buf_count = _I2S_DMA_BUFFER_COUNT, - .dma_buf_len = _i2s_dma_buffer_size, - .use_apll = false + .dma_buf_len = _i2s_dma_buffer_frame_size, + .use_apll = false, + .tx_desc_auto_clear = false, + .fixed_mclk = 0, + .mclk_multiple = esp_i2s::I2S_MCLK_MULTIPLE_DEFAULT, + .bits_per_chan = esp_i2s::I2S_BITS_PER_CHAN_DEFAULT, +#if SOC_I2S_SUPPORTS_TDM + .chan_mask = (esp_i2s::i2s_channel_t)(esp_i2s::I2S_TDM_ACTIVE_CH0 | esp_i2s::I2S_TDM_ACTIVE_CH1), + .total_chan = 2, + .left_align = false, + .big_edin = false, + .bit_order_msb = false, + .skip_msk = false +#endif }; if(_driveClock == false){ i2s_config.use_apll = true; - i2s_config.fixed_mclk = 512*_sampleRate; + i2s_config.fixed_mclk = 8*_sampleRate; + } + + if(_mode == ADC_DAC_MODE){ + i2s_config.communication_format = (esp_i2s::i2s_comm_format_t)(esp_i2s::I2S_COMM_FORMAT_STAND_MSB); + } + + if(_mode == I2S_RIGHT_JUSTIFIED_MODE){ + i2s_config.communication_format = (esp_i2s::i2s_comm_format_t)(esp_i2s::I2S_CHANNEL_FMT_ALL_RIGHT); // Load right channel data in both two channels + } + + if(_mode == I2S_LEFT_JUSTIFIED_MODE){ + i2s_config.communication_format = (esp_i2s::i2s_comm_format_t)(esp_i2s::I2S_CHANNEL_FMT_ALL_LEFT); // Load left channel data in both two channels } // Install and start i2s driver while(ESP_OK != esp_i2s::i2s_driver_install((esp_i2s::i2s_port_t) _deviceIndex, &i2s_config, _I2S_EVENT_QUEUE_LENGTH, &_i2sEventQueue)){ - // increase buffer size - if(2*_i2s_dma_buffer_size <= 1024){ - log_w("WARNING i2s driver install failed.\nTrying to increase I2S DMA buffer size from %d to %d\n", _i2s_dma_buffer_size, 2*_i2s_dma_buffer_size); - setBufferSize(2*_i2s_dma_buffer_size); - }else if(_i2s_dma_buffer_size < 1024){ - log_w("WARNING i2s driver install failed.\nTrying to decrease I2S DMA buffer size from %d to 1024\n", _i2s_dma_buffer_size); - setBufferSize(1024); + // Double the DMA buffer size + if(2*_i2s_dma_buffer_frame_size <= 1024){ + log_w("(I2S#%d) WARNING i2s driver install failed.\nTrying to double the I2S DMA buffer size from %d to %d", _deviceIndex, _i2s_dma_buffer_frame_size, 2*_i2s_dma_buffer_frame_size); + setDMABufferFrameSize(2*_i2s_dma_buffer_frame_size); // Double the buffer size + }else if(_i2s_dma_buffer_frame_size < 1024){ + log_w("(I2S#%d) WARNING i2s driver install failed.\nTrying to decrease I2S DMA buffer size from %d to maximum of 1024", _deviceIndex, _i2s_dma_buffer_frame_size); + setDMABufferFrameSize(1024); }else{ // install failed with max buffer size - log_e("ERROR i2s driver install failed"); + log_e("(I2S#%d) ERROR i2s driver install failed", _deviceIndex); return 0; // ERR } } //try installing with increasing size - if(_mode == I2S_RIGHT_JUSTIFIED_MODE || _mode == I2S_LEFT_JUSTIFIED_MODE || _mode == PDM_MONO_MODE){ // mono/single channel + if(_mode == PDM_MONO_MODE){ // mono/single channel // Set the clock for MONO. Stereo is not supported yet. if(ESP_OK != esp_i2s::i2s_set_clk((esp_i2s::i2s_port_t) _deviceIndex, _sampleRate, (esp_i2s::i2s_bits_per_sample_t)_bitsPerSample, esp_i2s::I2S_CHANNEL_MONO)){ - log_e("Setting the I2S Clock has failed!\n"); + log_e("(I2S#%d) Setting the I2S Clock has failed!", _deviceIndex); return 0; // ERR } } // mono channel mode @@ -180,20 +214,20 @@ int I2SClass::_installDriver(){ esp_i2s::i2s_set_dac_mode(esp_i2s::I2S_DAC_CHANNEL_BOTH_EN); esp_i2s::adc_unit_t adc_unit; if(!_gpioToAdcUnit((gpio_num_t)_inSdPin, &adc_unit)){ - log_e("pin to adc unit conversion failed"); + log_e("(I2S#%d) pin to adc unit conversion failed", _deviceIndex); return 0; // ERR } esp_i2s::adc_channel_t adc_channel; if(!_gpioToAdcChannel((gpio_num_t)_inSdPin, &adc_channel)){ - log_e("pin to adc channel conversion failed"); + log_e("(I2S#%d) pin to adc channel conversion failed", _deviceIndex); return 0; // ERR } if(ESP_OK != esp_i2s::i2s_set_adc_mode(adc_unit, (esp_i2s::adc1_channel_t)adc_channel)){ - log_e("i2s_set_adc_mode failed"); + log_e("(I2S#%d) i2s_set_adc_mode failed", _deviceIndex); return 0; // ERR } if(ESP_OK != esp_i2s::i2s_set_pin((esp_i2s::i2s_port_t) _deviceIndex, NULL)){ - log_e("i2s_set_pin failed"); + log_e("(I2S#%d) i2s_set_pin failed", _deviceIndex); return 0; // ERR } @@ -211,7 +245,7 @@ int I2SClass::_installDriver(){ if(_mode == I2S_PHILIPS_MODE || _mode == I2S_RIGHT_JUSTIFIED_MODE || _mode == I2S_LEFT_JUSTIFIED_MODE || _mode == PDM_STEREO_MODE || _mode == PDM_MONO_MODE){ // if I2S mode _driverInstalled = true; // IDF I2S driver must be installed before calling _applyPinSetting if(!_applyPinSetting()){ - log_e("could not apply pin setting during driver install"); + log_e("(I2S#%d) could not apply pin setting during driver install", _deviceIndex); _uninstallDriver(); return 0; // ERR } @@ -221,50 +255,50 @@ int I2SClass::_installDriver(){ // Init in MASTER mode: the SCK and FS pins are driven as outputs using the sample rate int I2SClass::begin(int mode, int sampleRate, int bitsPerSample){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } // master mode (driving clock and frame select pins - output) int ret = begin(mode, sampleRate, bitsPerSample, true); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } // Init in SLAVE mode: the SCK and FS pins are inputs, other side controls sample rate int I2SClass::begin(int mode, int bitsPerSample){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } // slave mode (not driving clock and frame select pin - input) int ret = begin(mode, 96000, bitsPerSample, false); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } - // Core function int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } if(_initialized){ - log_e("ERROR: Object already initialized! Call I2S.end() to disable"); - _give_if_top_call(); + log_e("(I2S#%d) ERROR: Object already initialized! Call I2S.end() to disable", _deviceIndex); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } _driveClock = driveClock; _mode = mode; _sampleRate = (uint32_t)sampleRate; + _bitsPerSample = bitsPerSample; // There is work in progress on this library. if(_bitsPerSample == 16 && _sampleRate > 16000 && driveClock){ - log_w("This sample rate is not officially supported - audio might be noisy.\nTry using sample rate below or equal to 16000"); + log_w("(I2S#%d) The sample rate value %d is not officially supported - audio might be noisy.\nTry using sample rate below or equal to 16000", _deviceIndex, _sampleRate); } if(_bitsPerSample != 16){ - log_w("This bit-per-sample is not officially supported - audio quality might suffer.\nTry using 16bps, with sample rate below or equal 16000"); + log_w("(I2S#%d) The %d bit-per-sample is not officially supported - audio quality might suffer.\nTry using 16bps, with sample rate below or equal 16000", _deviceIndex, _bitsPerSample); } if(_mode != I2S_PHILIPS_MODE){ - log_w("This mode is not officially supported - audio quality might suffer.\nAt the moment the only supported mode is I2S_PHILIPS_MODE"); + log_w("(I2S#%d) The mode %s is not officially supported - audio quality might suffer.\nAt the moment the only supported mode is I2S_PHILIPS_MODE", _deviceIndex, i2s_mode_text[_mode]); } if (_state != I2S_STATE_IDLE && _state != I2S_STATE_DUPLEX) { - log_e("Error: unexpected _state (%d)", _state); - _give_if_top_call(); + log_e("(I2S#%d) Error: unexpected _state (%d)", _deviceIndex, _state); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } @@ -272,51 +306,66 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock case I2S_PHILIPS_MODE: case I2S_RIGHT_JUSTIFIED_MODE: case I2S_LEFT_JUSTIFIED_MODE: + break; #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) case ADC_DAC_MODE: + break; + #else + log_e("(I2S#%d) ERROR: ADC/DAC is not supported on this SoC. Change mode or SoC", _deviceIndex); + if(!_give_mux()){ return 0; /* ERR */ } + return 0; // ERR #endif +#if defined(SOC_I2S_SUPPORTS_PDM_TX) || defined(SOC_I2S_SUPPORTS_PDM_RX) case PDM_STEREO_MODE: case PDM_MONO_MODE: break; +#else + log_e("(I2S#%d) ERROR: PDM is not supported on this SoC. Change mode or SoC", _deviceIndex); + if(!_give_mux()){ return 0; /* ERR */ } + return 0; // ERR +#endif default: // invalid mode - log_e("ERROR: unknown mode"); - _give_if_top_call(); + if(mode >= 0 && mode < MODE_MAX){ + log_e("(I2S#%d) ERROR: Mode '%s' is not supported on this SoC", _deviceIndex, i2s_mode_text[mode]); + }else{ + log_e("(I2S#%d) ERROR: Mode %d is not recognized", _deviceIndex, mode); + } + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } - if(!_installDriver()){ - log_e("ERROR: failed to install driver"); + log_e("(I2S#%d) ERROR: failed to install driver", _deviceIndex); end(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } - _buffer_byte_size = _i2s_dma_buffer_size * (_bitsPerSample / 8) * _I2S_DMA_BUFFER_COUNT * 2; - _input_ring_buffer = xRingbufferCreate(_buffer_byte_size, RINGBUF_TYPE_BYTEBUF); - _output_ring_buffer = xRingbufferCreate(_buffer_byte_size, RINGBUF_TYPE_BYTEBUF); + _input_ring_buffer = xRingbufferCreate(getRingBufferByteSize(), RINGBUF_TYPE_BYTEBUF); + _output_ring_buffer = xRingbufferCreate(getRingBufferByteSize(), RINGBUF_TYPE_BYTEBUF); if(_input_ring_buffer == NULL || _output_ring_buffer == NULL){ - log_e("ERROR: could not create one or both internal buffers. Requested size = %d\n", _buffer_byte_size); - _give_if_top_call(); + log_e("(I2S#%d) ERROR: could not create one or both internal buffers. Requested size = %d", _deviceIndex, getRingBufferByteSize()); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } if(!_createCallbackTask()){ - log_e("ERROR: failed to create callback task"); + log_e("(I2S#%d) ERROR: failed to create callback task", _deviceIndex); end(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } _initialized = true; - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 1; // OK } int I2SClass::_applyPinSetting(){ if(_driverInstalled){ esp_i2s::i2s_pin_config_t pin_config = { + .mck_io_num = _mclkPin, .bck_io_num = _sckPin, .ws_io_num = _fsPin, .data_out_num = I2S_PIN_NO_CHANGE, @@ -338,7 +387,7 @@ int I2SClass::_applyPinSetting(){ } } if(ESP_OK != esp_i2s::i2s_set_pin((esp_i2s::i2s_port_t) _deviceIndex, &pin_config)){ - log_e("i2s_set_pin failed; attempted settings: SCK=%d; FS=%d; DIN=%d; DOUT=%d", pin_config.bck_io_num, pin_config.ws_io_num, pin_config.data_in_num, pin_config.data_out_num); + log_e("(I2S#%d) i2s_set_pin failed; attempted settings: SCK=%d; FS=%d; DIN=%d; DOUT=%d", _deviceIndex, pin_config.bck_io_num, pin_config.ws_io_num, pin_config.data_in_num, pin_config.data_out_num); return 0; // ERR }else{ return 1; // OK @@ -347,163 +396,228 @@ int I2SClass::_applyPinSetting(){ return 1; // OK } +void I2SClass::_setMclkPin(int mclkPin){ + if(!_take_mux()){ return; /* ERR */ } + if(mclkPin >= 0){ + _mclkPin = mclkPin; + }else{ + _mclkPin = I2S_PIN_NO_CHANGE; + } + if(!_give_mux()){ return; /* ERR */ } +} + void I2SClass::_setSckPin(int sckPin){ - _take_if_not_holding(); + if(!_take_mux()){ return; /* ERR */ } if(sckPin >= 0){ _sckPin = sckPin; }else{ _sckPin = PIN_I2S_SCK; + #if SOC_I2S_NUM > 1 + if(_deviceIndex==1){_sckPin = PIN_I2S1_SCK;} + #endif } - _give_if_top_call(); -} - -int I2SClass::setSckPin(int sckPin){ - _take_if_not_holding(); - _setSckPin(sckPin); - int ret = _applyPinSetting(); - _applyPinSetting(); - _give_if_top_call(); - return ret; + if(!_give_mux()){ return; /* ERR */ } } void I2SClass::_setFsPin(int fsPin){ + if(!_take_mux()){ return; /* ERR */ } if(fsPin >= 0){ _fsPin = fsPin; }else{ _fsPin = PIN_I2S_FS; + #if SOC_I2S_NUM > 1 + if(_deviceIndex==1){_sckPin = PIN_I2S1_FS;} + #endif } -} - -int I2SClass::setFsPin(int fsPin){ - _take_if_not_holding(); - _setFsPin(fsPin); - int ret = _applyPinSetting(); - _give_if_top_call(); - return ret; + if(!_give_mux()){ return; /* ERR */ } } // shared data pin for simplex void I2SClass::_setDataPin(int sdPin){ + if(!_take_mux()){ return; /* ERR */ } if(sdPin >= 0){ _sdPin = sdPin; }else{ _sdPin = PIN_I2S_SD; + #if SOC_I2S_NUM > 1 + if(_deviceIndex==1){_sckPin = PIN_I2S1_SD;} + #endif } -} - -// shared data pin for simplex -int I2SClass::setDataPin(int sdPin){ - _take_if_not_holding(); - _setDataPin(sdPin); - int ret = _applyPinSetting(); - _give_if_top_call(); - return ret; + if(!_give_mux()){ return; /* ERR */ } } void I2SClass::_setDataInPin(int inSdPin){ + if(!_take_mux()){ return; /* ERR */ } if(inSdPin >= 0){ _inSdPin = inSdPin; }else{ _inSdPin = PIN_I2S_SD_IN; + #if SOC_I2S_NUM > 1 + if(_deviceIndex==1){_sckPin = PIN_I2S1_SD_IN;} + #endif } -} - -int I2SClass::setDataInPin(int inSdPin){ - _take_if_not_holding(); - _setDataInPin(inSdPin); - int ret = _applyPinSetting(); - _give_if_top_call(); - return ret; + if(!_give_mux()){ return; /* ERR */ } } void I2SClass::_setDataOutPin(int outSdPin){ + if(!_take_mux()){ return; /* ERR */ } if(outSdPin >= 0){ _outSdPin = outSdPin; }else{ - _outSdPin = PIN_I2S_SD; + _outSdPin = PIN_I2S_SD_OUT; + #if SOC_I2S_NUM > 1 + if(_deviceIndex==1){_sckPin = PIN_I2S1_SD_OUT;} + #endif } + if(!_give_mux()){ return; /* ERR */ } +} + +int I2SClass::setSckPin(int sckPin){ + if(!_take_mux()){ return 0; /* ERR */ } + _setSckPin(sckPin); + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::setFsPin(int fsPin){ + if(!_take_mux()){ return 0; /* ERR */ } + _setFsPin(fsPin); + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +// shared data pin for simplex +int I2SClass::setDataPin(int sdPin){ + if(!_take_mux()){ return 0; /* ERR */ } + _setDataPin(sdPin); + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::setDataInPin(int inSdPin){ + if(!_take_mux()){ return 0; /* ERR */ } + _setDataInPin(inSdPin); + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; } int I2SClass::setDataOutPin(int outSdPin){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } _setDataOutPin(outSdPin); int ret = _applyPinSetting(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::setMclkPin(int mclkPin){ + if(!_take_mux()){ return 0; /* ERR */ } + _setMclkPin(mclkPin); + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::setAllPins(){ - _take_if_not_holding(); - int ret = setAllPins(PIN_I2S_SCK, PIN_I2S_FS, PIN_I2S_SD, PIN_I2S_SD_OUT, PIN_I2S_SD_IN); - _give_if_top_call(); + if(!_take_mux()){ return 0; /* ERR */ } + int ret = 0; + if(_deviceIndex==0){ + ret = setAllPins(PIN_I2S_SCK, PIN_I2S_FS, PIN_I2S_SD, PIN_I2S_SD_OUT, PIN_I2S_SD_IN, I2S_PIN_NO_CHANGE); + } + #if SOC_I2S_NUM > 1 + if(_deviceIndex==1){ + ret = setAllPins(PIN_I2S1_SCK, PIN_I2S1_FS, PIN_I2S1_SD, PIN_I2S1_SD_OUT, PIN_I2S1_SD_IN, I2S_PIN_NO_CHANGE); + } + #endif + if(!_give_mux()){ return 0; /* ERR */ } return ret; } -int I2SClass::setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin){ - _take_if_not_holding(); - _setSckPin(sckPin); +int I2SClass::setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin, int mclkPin /*=-1*/ ){ + if(!_take_mux()){ return 0; /* ERR */ } _setFsPin(fsPin); _setDataPin(sdPin); _setDataOutPin(outSdPin); _setDataInPin(inSdPin); + _setMclkPin(mclkPin); int ret = _applyPinSetting(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } -int I2SClass::setDuplex(){ - _take_if_not_holding(); - _state = I2S_STATE_DUPLEX; - _give_if_top_call(); - return 1; -} - -int I2SClass::setSimplex(){ - _take_if_not_holding(); - _state = I2S_STATE_IDLE; - _give_if_top_call(); - return 1; -} - -int I2SClass::isDuplex(){ - _take_if_not_holding(); - int ret = (int)(_state == I2S_STATE_DUPLEX); - _give_if_top_call(); +int I2SClass::unSetMclkPin(){ + if(!_take_mux()){ return 0; /* ERR */ } + _setMclkPin(I2S_PIN_NO_CHANGE); + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::getSckPin(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = _sckPin; - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::getFsPin(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = _fsPin; - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::getDataPin(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = _sdPin; - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::getDataInPin(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = _inSdPin; - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } int I2SClass::getDataOutPin(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = _outSdPin; - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::getMclkPin(){ + if(!_take_mux()){ return 0; /* ERR */ } + int ret = _mclkPin; + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::setDuplex(){ + if(!_take_mux()){ return 0; /* ERR */ } + _state = I2S_STATE_DUPLEX; + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::setSimplex(){ + if(!_take_mux()){ return 0; /* ERR */ } + _state = I2S_STATE_IDLE; + int ret = _applyPinSetting(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::isDuplex(){ + if(!_take_mux()){ return 0; /* ERR */ } + int ret = (int)(_state == I2S_STATE_DUPLEX); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } @@ -524,7 +638,7 @@ void I2SClass::_uninstallDriver(){ } void I2SClass::end(){ - _take_if_not_holding(); + if(!_take_mux()){ return; /* ERR */ } if(xTaskGetCurrentTaskHandle() != _callbackTaskHandle){ if(_callbackTaskHandle){ vTaskDelete(_callbackTaskHandle); @@ -543,59 +657,62 @@ void I2SClass::end(){ } _initialized = false; }else{ - log_w("WARNING: ending I2SClass from callback task not permitted, but attempted!"); + log_w("(I2S#%d) WARNING: ending I2SClass from callback task not permitted, but attempted!", _deviceIndex); } - _give_if_top_call(); + if(!_give_mux()){ return; /* ERR */ } } // Bytes available to read int I2SClass::available(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = 0; if(_input_ring_buffer != NULL){ - ret = _buffer_byte_size - (int)xRingbufferGetCurFreeSize(_input_ring_buffer); + ret = getRingBufferByteSize() - (int)xRingbufferGetCurFreeSize(_input_ring_buffer); } - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } union i2s_sample_t { - uint8_t b8; + int8_t b8; int16_t b16; int32_t b32; }; int I2SClass::read(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } i2s_sample_t sample; sample.b32 = 0; if(_initialized){ read(&sample, _bitsPerSample / 8); if (_bitsPerSample == 32) { - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } + return sample.b32; + } else if (_bitsPerSample == 24) { + if(!_give_mux()){ return 0; /* ERR */ } return sample.b32; } else if (_bitsPerSample == 16) { - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return sample.b16; } else if (_bitsPerSample == 8) { - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return sample.b8; } else { - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // sample value } } // if(_initialized) - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // sample value } int I2SClass::read(void* buffer, size_t size){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } size_t requested_size = size; if(_initialized){ if(!_enableReceiver()){ - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // There was an error switching to receiver } // _enableReceiver succeeded ? @@ -607,7 +724,7 @@ int I2SClass::read(void* buffer, size_t size){ _peek_buff_valid = false; requested_size -= _bitsPerSample/8; } - tmp_buffer = xRingbufferReceiveUpTo(_input_ring_buffer, &item_size, pdMS_TO_TICKS(1000), requested_size); + tmp_buffer = xRingbufferReceiveUpTo(_input_ring_buffer, &item_size, pdMS_TO_TICKS(0), requested_size); if(tmp_buffer != NULL){ memcpy(buffer, tmp_buffer, item_size); #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) @@ -618,57 +735,57 @@ int I2SClass::read(void* buffer, size_t size){ } // ADC/DAC mode #endif vRingbufferReturnItem(_input_ring_buffer, tmp_buffer); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return item_size; }else{ - log_w("input buffer is empty - timed out"); - _give_if_top_call(); + log_w("(I2S#%d) input buffer is empty - timed out", _deviceIndex); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // 0 Bytes read / ERR } // tmp buffer not NULL ? } // ring buffer not NULL ? } // if(_initialized) - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // 0 Bytes read / ERR } size_t I2SClass::write(uint8_t data){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } size_t ret = 0; if(_initialized){ ret = write_blocking((int32_t*)&data, 1); } - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } size_t I2SClass::write(int32_t sample){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } size_t ret = 0; if(_initialized){ ret = write_blocking(&sample, _bitsPerSample/8); } - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } size_t I2SClass::write(const uint8_t *buffer, size_t size){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } size_t ret = 0; if(_initialized){ ret = write((const void*)buffer, size); } - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } size_t I2SClass::write(const void *buffer, size_t size){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } size_t ret = 0; if(_initialized){ //size_t ret = write_blocking(buffer, size); ret = write_nonblocking(buffer, size); } // if(_initialized) - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } @@ -676,40 +793,43 @@ size_t I2SClass::write(const void *buffer, size_t size){ // This version of write will wait indefinitely to write requested samples // into output buffer size_t I2SClass::write_blocking(const void *buffer, size_t size){ - _take_if_not_holding(); + //log_d("(I2S#%d) ...", _deviceIndex); + if(!_take_mux()){ return 0; /* ERR */ } if(_initialized){ if(!_enableTransmitter()){ - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // There was an error switching to transmitter } // _enableTransmitter succeeded ? if(_output_ring_buffer != NULL){ int ret = xRingbufferSend(_output_ring_buffer, buffer, size, portMAX_DELAY); if(pdTRUE == ret){ - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return size; }else{ - log_e("xRingbufferSend() with infinite wait returned with error"); - _give_if_top_call(); + log_e("(I2S#%d) xRingbufferSend() with infinite wait returned with error", _deviceIndex); + if(!_give_mux()){ return 0; /* ERR */ } return 0; } // ring buffer send ok ? } // ring buffer not NULL ? } // if(_initialized) return 0; - log_w("I2S not initialized"); - _give_if_top_call(); + log_w("(I2S#%d) I2S not initialized", _deviceIndex); + if(!_give_mux()){ return 0; /* ERR */ } return 0; } // non-blocking version of write // In case there is not enough space in buffer to write requested size // this function will try to flush the buffer and write requested data with 0 time-out +// The function will return number of successfully written bytes. It is users responsibility +// to take care of the remaining data. size_t I2SClass::write_nonblocking(const void *buffer, size_t size){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } if(_initialized){ if(_state != I2S_STATE_TRANSMITTER && _state != I2S_STATE_DUPLEX){ if(!_enableTransmitter()){ - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // There was an error switching to transmitter } } @@ -718,17 +838,17 @@ size_t I2SClass::write_nonblocking(const void *buffer, size_t size){ } if(_output_ring_buffer != NULL){ if(pdTRUE == xRingbufferSend(_output_ring_buffer, buffer, size, 0)){ - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return size; }else{ - log_w("I2S could not write all data into ring buffer!"); - _give_if_top_call(); + log_w("(I2S#%d) I2S could not write all data (%d B) into ring buffer!", _deviceIndex, size); + if(!_give_mux()){ return 0; /* ERR */ } return 0; } } } // if(_initialized) + if(!_give_mux()){ return 0; /* ERR */ } // this should not be needed return 0; - _give_if_top_call(); // this should not be needed } /* @@ -736,7 +856,7 @@ size_t I2SClass::write_nonblocking(const void *buffer, size_t size){ Repeated peeks will return the same sample until read is called. */ int I2SClass::peek(){ - _take_if_not_holding(); + if(!_take_mux()){ return 0; /* ERR */ } int ret = 0; if(_initialized && _input_ring_buffer != NULL && !_peek_buff_valid){ size_t item_size = 0; @@ -753,83 +873,144 @@ int I2SClass::peek(){ if(_peek_buff_valid){ ret = _peek_buff; } - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } - +// Requests data from ring buffer and writes data to I2S module +// note: it is NOT necessary to call the flush after writes. Buffer is flushed automatically after receiveing enough data. void I2SClass::flush(){ - _take_if_not_holding(); + if(!_take_mux()){ return; /* ERR */ } if(_initialized){ - const size_t single_dma_buf = _i2s_dma_buffer_size*(_bitsPerSample/8)*2; + const size_t single_dma_buf_byte_size = _i2s_dma_buffer_frame_size*(_bitsPerSample/8)*2; size_t item_size = 0; void *item = NULL; if(_output_ring_buffer != NULL){ - item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, 0, single_dma_buf); + // TODO while available data to fill entire DMA buff - keep flushing + item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, 0, single_dma_buf_byte_size); if (item != NULL){ _fix_and_write(item, item_size); vRingbufferReturnItem(_output_ring_buffer, item); } } } // if(_initialized) - _give_if_top_call(); + if(!_give_mux()){ return; /* ERR */ } } // Bytes available to write int I2SClass::availableForWrite(){ - _take_if_not_holding(); int ret = 0; + if(!_take_mux()){ return 0; /* ERR */ } if(_initialized){ if(_output_ring_buffer != NULL){ ret = (int)xRingbufferGetCurFreeSize(_output_ring_buffer); } } // if(_initialized) - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::availableSamplesForWrite(){ + int ret = 0; + if(!_take_mux()){ return 0; /* ERR */ } + ret = availableForWrite() / (_bitsPerSample/8); + if(!_give_mux()){ return 0; /* ERR */ } return ret; } void I2SClass::onTransmit(void(*function)(void)){ - _take_if_not_holding(); + if(!_take_mux()){ return; /* ERR */ } _onTransmit = function; - _give_if_top_call(); + if(!_give_mux()){ return; /* ERR */ } } void I2SClass::onReceive(void(*function)(void)){ - _take_if_not_holding(); + if(!_take_mux()){ return; /* ERR */ } _onReceive = function; - _give_if_top_call(); + if(!_give_mux()){ return; /* ERR */ } } -int I2SClass::setBufferSize(int bufferSize){ - _take_if_not_holding(); + +// Change buffer size. The unit is in frames. +// Byte value can be calculated as follows: +// ByteSize = (bits_per_sample / 8) * CHANNEL_NUMBER * DMABufferFrameSize +// Note: CHANNEL_NUMBER is hard-coded to value 2 +// Calling this function will automatically restart the driver, which could cause audio output gap. +int I2SClass::setDMABufferFrameSize(int DMABufferFrameSize){ + if(!_take_mux()){ return 0; /* ERR */ } int ret = 0; - if(bufferSize >= 8 && bufferSize <= 1024){ - _i2s_dma_buffer_size = bufferSize; + if(DMABufferFrameSize >= 8 && DMABufferFrameSize <= 1024){ + _i2s_dma_buffer_frame_size = DMABufferFrameSize; }else{ - log_e("setBufferSize: wrong input! Buffer size must be between 8 and 1024. Requested %d", bufferSize); - _give_if_top_call(); + log_e("(I2S#%d) setDMABufferFrameSize: wrong input! Buffer size must be between 8 and 1024. Requested %d", _deviceIndex, DMABufferFrameSize); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } // check requested buffer size if(_initialized){ _uninstallDriver(); ret = _installDriver(); - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return ret; }else{ // check requested buffer size - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 1; // It's ok to change buffer size for uninitialized driver - new size will be used on begin() } // if(_initialized) - _give_if_top_call(); + if(!_give_mux()){ return 0; /* ERR */ } return 0; // ERR } -int I2SClass::getBufferSize(){ - _take_if_not_holding(); - int ret = _i2s_dma_buffer_size; - _give_if_top_call(); +// Change buffer size. The unit is in samples. +// Byte value can be calculated as follows: +// ByteSize = CHANNEL_NUMBER * DMABufferSampleSize +// Note: CHANNEL_NUMBER is hard-coded to value 2 +// Calling this function will automatically restart the driver, which could cause audio output gap. +int I2SClass::setDMABufferSampleSize(int DMABufferSampleSize){ + return setDMABufferFrameSize(DMABufferSampleSize / CHANNEL_NUMBER); +} + +int I2SClass::getDMABufferFrameSize(){ + if(!_take_mux()){ return 0; /* ERR */ } + int ret = _i2s_dma_buffer_frame_size; + if(!_give_mux()){ return 0; /* ERR */ } return ret; } +int I2SClass::getDMABufferSampleSize(){ + if(!_take_mux()){ return 0; /* ERR */ } + int ret = _i2s_dma_buffer_frame_size * CHANNEL_NUMBER; + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::getDMABufferByteSize(){ + if(!_take_mux()){ return 0; /* ERR */ } + int ret = _i2s_dma_buffer_frame_size * CHANNEL_NUMBER * (_bitsPerSample/8); + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::getRingBufferSampleSize(){ + if(!_take_mux()){ return 0; /* ERR */ } + int ret = _i2s_dma_buffer_frame_size * CHANNEL_NUMBER * _I2S_DMA_BUFFER_COUNT; + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::getRingBufferByteSize(){ + if(!_take_mux()){ return 0; /* ERR */ } + int ret = _i2s_dma_buffer_frame_size * CHANNEL_NUMBER * (_bitsPerSample/8) * _I2S_DMA_BUFFER_COUNT; + if(!_give_mux()){ return 0; /* ERR */ } + return ret; +} + +int I2SClass::getI2SNum(){ + return _deviceIndex; +} + +bool I2SClass::isInitialized(){ + return _initialized; +} + int I2SClass::_enableTransmitter(){ if(_state != I2S_STATE_DUPLEX && _state != I2S_STATE_TRANSMITTER){ _state = I2S_STATE_TRANSMITTER; @@ -848,7 +1029,6 @@ int I2SClass::_enableReceiver(){ void I2SClass::_tx_done_routine(uint8_t* prev_item){ static bool prev_item_valid = false; - const size_t single_dma_buf = _i2s_dma_buffer_size*(_bitsPerSample/8)*2; // *2 for stereo - it has double number of samples for 2 channels static size_t item_size = 0; static size_t prev_item_size = 0; static void *item = NULL; @@ -864,22 +1044,20 @@ void I2SClass::_tx_done_routine(uint8_t* prev_item){ prev_item_size -= bytes_written; } // prev_item_valid - if(_output_ring_buffer != NULL && (_buffer_byte_size - xRingbufferGetCurFreeSize(_output_ring_buffer) >= single_dma_buf)){ // fill up the I2S DMA buffer + if(_output_ring_buffer != NULL && (getRingBufferByteSize() - xRingbufferGetCurFreeSize(_output_ring_buffer) >= getDMABufferByteSize())){ // fill up the I2S DMA buffer bytes_written = 0; item_size = 0; - if(_buffer_byte_size - xRingbufferGetCurFreeSize(_output_ring_buffer) >= _i2s_dma_buffer_size*(_bitsPerSample/8)){ // don't read from almost empty buffer - item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, pdMS_TO_TICKS(0), single_dma_buf); - if (item != NULL){ - _fix_and_write(item, item_size, &bytes_written); - if(item_size != bytes_written){ // save item that was not written correctly for later - memcpy(prev_item, (void*)&((uint8_t*)item)[bytes_written], item_size-bytes_written); - prev_item_size = item_size - bytes_written; - prev_item_offset = 0; - prev_item_valid = true; - } // save item that was not written correctly for later - vRingbufferReturnItem(_output_ring_buffer, item); - } // Check received item - } // don't read from almost empty buffer + item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, pdMS_TO_TICKS(0), getDMABufferByteSize()); + if(item != NULL){ + _fix_and_write(item, item_size, &bytes_written); + if(item_size != bytes_written){ // save item that was not written correctly for later + memcpy(prev_item, (void*)&((uint8_t*)item)[bytes_written], item_size-bytes_written); + prev_item_size = item_size - bytes_written; + prev_item_offset = 0; + prev_item_valid = true; + } // save item that was not written correctly for later + vRingbufferReturnItem(_output_ring_buffer, item); + } // Check received item } // fill up the I2S DMA buffer if(_onTransmit){ _onTransmit(); @@ -888,77 +1066,97 @@ void I2SClass::_tx_done_routine(uint8_t* prev_item){ void I2SClass::_rx_done_routine(){ size_t bytes_read = 0; - const size_t single_dma_buf = _i2s_dma_buffer_size*(_bitsPerSample/8); + size_t request_read = 0; if(_input_ring_buffer != NULL){ - uint8_t *_inputBuffer = (uint8_t*)malloc(_i2s_dma_buffer_size*4); + uint8_t *_inputBuffer = (uint8_t*)malloc(getDMABufferByteSize()); size_t avail = xRingbufferGetCurFreeSize(_input_ring_buffer); if(avail > 0){ - esp_err_t ret = esp_i2s::i2s_read((esp_i2s::i2s_port_t) _deviceIndex, _inputBuffer, avail <= single_dma_buf ? avail : single_dma_buf, (size_t*) &bytes_read, 0); + if(_bitsPerSample == 8){ // 8-bps is received with padding as 16bits - read twice than actually needed + request_read = avail*2 <= getDMABufferByteSize() ? avail*2 : getDMABufferByteSize(); + }else{ + request_read = avail <= getDMABufferByteSize() ? avail : getDMABufferByteSize(); + } + + esp_err_t ret = esp_i2s::i2s_read((esp_i2s::i2s_port_t) _deviceIndex, _inputBuffer, request_read, &bytes_read, 0); if(ret != ESP_OK){ - log_w("i2s_read returned with error %d", ret); + log_w("(I2S#%d) i2s_read returned with error %d", _deviceIndex, ret); + } + if(request_read != bytes_read){ + log_w("(I2S#%d) i2s_read read only %d bytes instead of %d requested", _deviceIndex, bytes_read, request_read); } _post_read_data_fix(_inputBuffer, &bytes_read); } if(bytes_read > 0){ // when read more than 0, then send to ring buffer if(pdTRUE != xRingbufferSend(_input_ring_buffer, _inputBuffer, bytes_read, 0)){ - log_w("I2S failed to send item from DMA to internal buffer\n"); + log_w("(I2S#%d) I2S failed to send item from DMA to internal buffer\n", _deviceIndex); } // xRingbufferSendComplete } // if(bytes_read > 0) free(_inputBuffer); - if (_onReceive && avail < _buffer_byte_size){ // when user callback is registered && and there is some data in ring buffer to read + if (_onReceive && avail < getRingBufferByteSize()){ // when user callback is registered && and there is some data in ring buffer to read _onReceive(); } // user callback } } void I2SClass::_onTransferComplete(){ - uint8_t prev_item[_i2s_dma_buffer_size*4]; + uint8_t prev_item[_i2s_dma_buffer_frame_size*4]; esp_i2s::i2s_event_t i2s_event; - while(true){ - xQueueReceive(_i2sEventQueue, &i2s_event, portMAX_DELAY); - if(i2s_event.type == esp_i2s::I2S_EVENT_TX_DONE){ - _tx_done_routine(prev_item); - }else if(i2s_event.type == esp_i2s::I2S_EVENT_RX_DONE){ - _rx_done_routine(); - } // RX Done - } // infinite loop -} - -void I2SClass::onDmaTransferComplete(void*){ - I2S._onTransferComplete(); + if(_i2sEventQueue == NULL){ + log_e("(I2S#%d) Event Queue is NULL!", _deviceIndex); + }else{ + while(true){ + xQueueReceive(_i2sEventQueue, &i2s_event, portMAX_DELAY); + if(i2s_event.type == esp_i2s::I2S_EVENT_TX_DONE){ + _tx_done_routine(prev_item); + }else if(i2s_event.type == esp_i2s::I2S_EVENT_RX_DONE){ + _rx_done_routine(); + } // RX Done + } // infinite loop + } // _i2sEventQueue NULL check } -void I2SClass::_take_if_not_holding(){ - TaskHandle_t mutex_holder = xSemaphoreGetMutexHolder(_i2s_general_mutex); - if(mutex_holder != NULL && mutex_holder == xTaskGetCurrentTaskHandle()){ - ++_nesting_counter; - return; // we are already holding this mutex - no need to take it +void I2SClass::onDmaTransferComplete(void *deviceIndex){ + uint8_t *index; + index = (uint8_t*) deviceIndex; + if(*index == 0){ + I2S._onTransferComplete(); } - - // we are not holding the mutex - wait for it and take it - if(xSemaphoreTake(_i2s_general_mutex, portMAX_DELAY) != pdTRUE ){ - log_e("I2S internal mutex take returned with error"); +#if SOC_I2S_NUM > 1 + if(*index == 1){ + I2S_1._onTransferComplete(); } - //_give_if_top_call(); // call after this function +#endif + log_w("(I2S#%d) Deleting callback task from inside!", *index); + vTaskDelete(NULL); } -void I2SClass::_give_if_top_call(){ - if(_nesting_counter){ - --_nesting_counter; - }else{ - if(xSemaphoreGive(_i2s_general_mutex) != pdTRUE){ - log_e("I2S internal mutex give error"); - } +inline bool I2SClass::_take_mux(){ + if(_i2s_general_mutex == NULL || xSemaphoreTakeRecursive(_i2s_general_mutex, portMAX_DELAY) != pdTRUE){ + log_e("(I2S#%d) Could not take semaphore %p", _deviceIndex, _i2s_general_mutex); + return false; } + return true; } +inline bool I2SClass::_give_mux(){ + if(_i2s_general_mutex == NULL || xSemaphoreGiveRecursive(_i2s_general_mutex) != pdTRUE){ + log_e("(I2S#%d) Could not give semaphore %p", _deviceIndex, _i2s_general_mutex); + return false; + } + return true; +} // Fixes data in-situ received from esp i2s driver. After fixing they reflect what was on the bus. // input - bytes as received from i2s_read - this serves as input and output buffer // size - number of bytes (this may be changed during operation) +// In 8 bit mode data are received as 16 bit number with LSB padded with 0s and actual data on MSB, +// this function removes the padding (shrinking the size to 1/2) +// In 16 bit mode data are received with wrong endianity, this function swaps the bytes +// In 24 bit mode - not tested if needs fixing. +// In 32 bit mode data are ok - no action is performed. void I2SClass::_post_read_data_fix(void *input, size_t *size){ ulong dst_ptr = 0; switch(_bitsPerSample){ @@ -977,7 +1175,11 @@ void I2SClass::_post_read_data_fix(void *input, size_t *size){ ((uint16_t*)input)[dst_ptr++] = tmp; } break; - default: ; // Do nothing + case 24: + // TODO check if need fix and if so, implement it + break; + default: ; + // Do nothing } // switch } @@ -991,12 +1193,13 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, ulong src_ptr = 0; uint8_t* buff = NULL; size_t buff_size = size; + uint32_t offset = 1; // 24bit specific switch(_bitsPerSample){ case 8: buff_size = size *2; buff = (uint8_t*)calloc(buff_size, sizeof(uint8_t)); if(buff == NULL){ - log_e("callock error"); + log_e("(I2S#%d) callock error", _deviceIndex); if(bytes_written != NULL){ *bytes_written = 0; } return; } @@ -1008,7 +1211,7 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, case 16: buff = (uint8_t*)malloc(buff_size); if(buff == NULL){ - log_e("malloc error"); + log_e("(I2S#%d) malloc error", _deviceIndex); if(bytes_written != NULL){ *bytes_written = 0; } return; } @@ -1018,21 +1221,45 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, } break; case 24: - buff = (uint8_t*)output; + buff_size = (size/3)*4; // Increase by 1/3 + buff = (uint8_t*)calloc(buff_size, sizeof(uint8_t)); + for(int i = 0; i < size/3; i+=3){ + // LSB in buffer's 4-Byte Word is 0 to compensate IDF driver behavior + buff[i+offset] = ((uint8_t*)output)[i]; + buff[i+offset+1] = ((uint8_t*)output)[i+1]; + buff[i+offset+2] = ((uint8_t*)output)[i+2]; + ++offset; + } break; case 32: buff = (uint8_t*)output; break; - default: ; // Do nothing + default: + break; // Do nothing } // switch +/* +// Should be taken care of by comm format: I2S_CHANNEL_FMT_ALL_RIGHT / I2S_CHANNEL_FMT_ALL_LEFT + if(_mode == I2S_RIGHT_JUSTIFIED_MODE){ + for(int i = 0; i < buff_size; i+=2){ + buff[i] = buff[i+1]; + } + } + if(_mode == I2S_LEFT_JUSTIFIED_MODE){ + for(int i = 0; i < buff_size; i+=2){ + buff[i+1] = buff[i]; + } + } +*/ + size_t _bytes_written; - esp_err_t ret = esp_i2s::i2s_write((esp_i2s::i2s_port_t) _deviceIndex, buff, buff_size, &_bytes_written, 0); // fixed + esp_err_t ret = esp_i2s::i2s_write((esp_i2s::i2s_port_t) _deviceIndex, buff, buff_size, &_bytes_written, 0); + log_d("written %d/%d", _bytes_written,buff_size); if(ret != ESP_OK){ - log_e("Error: writing data to i2s - function returned with err code %d", ret); + log_e("(I2S#%d) Error: writing data to i2s - function returned with err code %d", _deviceIndex, ret); } if(ret == ESP_OK && buff_size != _bytes_written){ - log_w("Warning: writing data to i2s - written %d B instead of requested %d B", _bytes_written, buff_size); + log_w("(I2S#%d) Warning: writing data to i2s - written %d B instead of requested %d B", _deviceIndex, _bytes_written, buff_size); } // free if the buffer was actually allocated if(_bitsPerSample == 8 || _bitsPerSample == 16){ @@ -1046,6 +1273,12 @@ void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, } } +// TODO change to usage of +/* +int8_t digitalPinToTouchChannel(uint8_t pin); +int8_t digitalPinToAnalogChannel(uint8_t pin); +int8_t analogChannelToDigitalPin(uint8_t channel); +*/ #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) int I2SClass::_gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit){ @@ -1065,7 +1298,10 @@ int I2SClass::_gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit) // ADC 2 case GPIO_NUM_0: - log_w("GPIO 0 for ADC should not be used for dev boards due to external auto program circuits."); + log_w("(I2S#%d) GPIO 0 for ADC should not be used for dev boards due to external auto program circuits.", _deviceIndex); + // Only to suppress Warnings "may fall through" which exactly what is intended + *adc_unit = esp_i2s::ADC_UNIT_2; + return 1; // OK case GPIO_NUM_4: case GPIO_NUM_2: case GPIO_NUM_15: @@ -1122,8 +1358,22 @@ int I2SClass::_gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit) return 1; // OK #endif default: - log_e("GPIO %d not usable for ADC!", gpio_num); - log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html"); + log_e("(I2S#%d) GPIO %d not usable for ADC!", _deviceIndex, gpio_num); + #if CONFIG_IDF_TARGET_ESP32 + log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html"); + #elif + CONFIG_IDF_TARGET_ESP32S2 + log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/gpio.html"); + #elif + CONFIG_IDF_TARGET_ESP32S3 + log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/gpio.html"); + #elif + CONFIG_IDF_TARGET_ESP32C2 + log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32c2/api-reference/peripherals/gpio.html"); + #elif + CONFIG_IDF_TARGET_ESP32C3 + log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/gpio.html"); + #endif return 0; // ERR } } @@ -1143,7 +1393,7 @@ int I2SClass::_gpioToAdcChannel(gpio_num_t gpio_num, esp_i2s::adc_channel_t* adc // ADC 2 case GPIO_NUM_0: - log_w("GPIO 0 for ADC should not be used for dev boards due to external auto program circuits."); + log_w("(I2S#%d) GPIO 0 for ADC should not be used for dev boards due to external auto program circuits.", _deviceIndex); *adc_channel = esp_i2s::ADC_CHANNEL_1; return 1; // OK case GPIO_NUM_4: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK case GPIO_NUM_2: *adc_channel = esp_i2s::ADC_CHANNEL_2; return 1; // OK @@ -1191,18 +1441,17 @@ int I2SClass::_gpioToAdcChannel(gpio_num_t gpio_num, esp_i2s::adc_channel_t* adc case GPIO_NUM_5: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK #endif default: - log_e("GPIO %d not usable for ADC!", gpio_num); + log_e("(I2S#%d) GPIO %d not usable for ADC!", _deviceIndex, gpio_num); log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html"); return 0; // ERR } } #endif // SOC_I2S_SUPPORTS_ADC_DAC -#if I2S_INTERFACES_COUNT > 0 - I2SClass I2S(I2S_DEVICE, I2S_CLOCK_GENERATOR, PIN_I2S_SD, PIN_I2S_SCK, PIN_I2S_FS); // default - half duplex +#if SOC_I2S_NUM > 0 + I2SClass I2S(0, I2S_CLOCK_GENERATOR, PIN_I2S_SD, PIN_I2S_SCK, PIN_I2S_FS); // default - half duplex #endif -#if I2S_INTERFACES_COUNT > 1 - // TODO set default pins for second module - //I2SClass I2S1(I2S_DEVICE+1, I2S_CLOCK_GENERATOR, PIN_I2S_SD, PIN_I2S_SCK, PIN_I2S_FS); // default - half duplex +#if SOC_I2S_NUM > 1 + I2SClass I2S_1(1, I2S_CLOCK_GENERATOR, PIN_I2S1_SD, PIN_I2S1_SCK, PIN_I2S1_FS); // default - half duplex #endif diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h index 623fa8917b4..61bca1d3daa 100644 --- a/libraries/I2S/src/I2S.h +++ b/libraries/I2S/src/I2S.h @@ -27,99 +27,438 @@ namespace esp_i2s { } // Default pins +//I2S0 is available for all ESP32 SoCs +//MCLK SCK WS SD(OUT) SDIN +// 0 18 19 21 34 ESP32 +// 0 18 19 4 5 ESP32-x (C3,S2,S3) + +#ifndef PIN_I2S_MCLK + #if CONFIG_IDF_TARGET_ESP32 + #define PIN_I2S_MCLK GPIO_NUM_0 + #else + #define PIN_I2S_MCLK GPIO_NUM_0 + #endif +#endif + #ifndef PIN_I2S_SCK - #define PIN_I2S_SCK 14 + #define PIN_I2S_SCK GPIO_NUM_18 #endif #ifndef PIN_I2S_FS - #if CONFIG_IDF_TARGET_ESP32S2 - #define PIN_I2S_FS 27 - #else - #define PIN_I2S_FS 25 - #endif + #define PIN_I2S_FS GPIO_NUM_19 #endif #ifndef PIN_I2S_SD - #define PIN_I2S_SD 26 + #if CONFIG_IDF_TARGET_ESP32 + #define PIN_I2S_SD GPIO_NUM_21 + #else + #define PIN_I2S_SD GPIO_NUM_4 + #endif #endif #ifndef PIN_I2S_SD_OUT - #define PIN_I2S_SD_OUT 26 + #if CONFIG_IDF_TARGET_ESP32 + #define PIN_I2S_SD_OUT GPIO_NUM_21 + #else + #define PIN_I2S_SD_OUT GPIO_NUM_4 + #endif #endif #ifndef PIN_I2S_SD_IN - #define PIN_I2S_SD_IN 35 // Pin 35 is only input! + #if CONFIG_IDF_TARGET_ESP32 + #define PIN_I2S_SD_IN GPIO_NUM_34 + #else + #define PIN_I2S_SD_IN GPIO_NUM_5 + #endif +#endif + +#if SOC_I2S_NUM > 1 + // I2S1 is available only for ESP32 and ESP32-S3 + #if CONFIG_IDF_TARGET_ESP32 + // ESP32 pins + // MCLK SCK WS SD(OUT) SDIN + // 1 22 23 27 35 + + #ifndef PIN_I2S1_MCLK + #define PIN_I2S1_MCLK GPIO_NUM_1 + #endif + + #ifndef PIN_I2S1_SCK + #define PIN_I2S1_SCK GPIO_NUM_22 + #endif + + #ifndef PIN_I2S1_FS + #define PIN_I2S1_FS GPIO_NUM_23 + #endif + + #ifndef PIN_I2S1_SD + #define PIN_I2S1_SD GPIO_NUM_27 + #endif + + #ifndef PIN_I2S1_SD_OUT + #define PIN_I2S1_SD_OUT GPIO_NUM_27 + #endif + + #ifndef PIN_I2S1_SD_IN + #define PIN_I2S1_SD_IN GPIO_NUM_35 + #endif + #endif + + #if CONFIG_IDF_TARGET_ESP32S3 + // ESP32-S3 pins + // MCLK SCK WS SD(OUT) SDIN + // 35 36 37 39 40 + #ifndef PIN_I2S1_MCLK + #define PIN_I2S1_MCLK GPIO_NUM_35 + #endif + + #ifndef PIN_I2S1_SCK + #define PIN_I2S1_SCK GPIO_NUM_36 + #endif + + #ifndef PIN_I2S1_FS + #define PIN_I2S1_FS GPIO_NUM_37 + #endif + + #ifndef PIN_I2S1_SD + #define PIN_I2S1_SD GPIO_NUM_39 + #endif + + #ifndef PIN_I2S1_SD_OUT + #define PIN_I2S1_SD_OUT GPIO_NUM_39 + #endif + + #ifndef PIN_I2S1_SD_IN + #define PIN_I2S1_SD_IN GPIO_NUM_40 + #endif + #endif #endif +#define CHANNEL_NUMBER (2) + typedef enum { - I2S_PHILIPS_MODE, - I2S_RIGHT_JUSTIFIED_MODE, - I2S_LEFT_JUSTIFIED_MODE, - ADC_DAC_MODE, - PDM_STEREO_MODE, - PDM_MONO_MODE + I2S_PHILIPS_MODE, // Most common I2S mode, FS signal spans across whole channel period + I2S_RIGHT_JUSTIFIED_MODE, // Right channel data are copied to both channels + I2S_LEFT_JUSTIFIED_MODE, // Left channel data are copied to both channels + ADC_DAC_MODE, // Receive and transmit raw analog signal + PDM_STEREO_MODE, // Pulse Density Modulation - stereo / 2 channels + PDM_MONO_MODE, // Pulse Density Modulation - mono / 1 channel + MODE_MAX // Helper value - not actual mode } i2s_mode_t; +const char i2s_mode_text[][32] = { + "I2S_PHILIPS_MODE", + "I2S_RIGHT_JUSTIFIED_MODE", + "I2S_LEFT_JUSTIFIED_MODE", + "ADC_DAC_MODE", + "PDM_STEREO_MODE", + "PDM_MONO_MODE" +}; + class I2SClass : public Stream { public: - // The device index and pins must map to the "COM" pads in Table 6-1 of the datasheet + /* + * Constructor - initializes object with default values + * + * Parameters: + * uint8_t deviceIndex In case the SoC has more I2S modules, specify which one is instantiated. Possible values are "0" (for all ESPs) and "1" (only for ESP32 and ESP32-S3) + * uint8_t clockGenerator Has no meaning for ESP and is kept only for compatibility + * uint8_t sdPin Shared data pin used for simplex mode + * uint8_t sckPin Clock pin + * uint8_t fsPin Frame Sync (Word Select) pin + * + * Default settings: + * Input data pin (used for duplex mode) is initialized with PIN_I2S_SD_IN + * Out data pin (used for duplex mode) is initialized with PIN_I2S_SD + * Mode = I2S_PHILIPS_MODE + * Buffer size = 128 + */ I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin); - // Init in MASTER mode: the SCK and FS pins are driven as outputs using the sample rate + /* + * Init in MASTER mode: the SCK and FS pins are driven as outputs using the sample rate. + * Initializes IDF I2S driver, creates ring buffers and callback handler task. + * Parameters: + * int mode Operation mode (Phillips, Left/Right Justified, ADC+DAC,PDM) see i2s_mode_t for exact enumerations + * int sampleRate sampling frequency in Hz. Common values are 8000,11025,16000,22050,32000,44100,64000,88200,128000 + * int bitsPerSample Number of bits per one sample (one channel). Possible values are 8,16,24,32 + * Returns: 1 on success; 0 on error + */ int begin(int mode, int sampleRate, int bitsPerSample); - // Init in SLAVE mode: the SCK and FS pins are inputs, other side controls sample rate + /* Init in SLAVE mode: the SCK and FS pins are inputs and must be controlled(generated) be external source (MASTER device). + * Initializes IDF I2S driver, creates ring buffers and callback handler task. + * Parameters: + * int mode Operation mode (Phillips, Left/Right Justified, ADC+DAC,PDM) see i2s_mode_t for exact enumerations + * int bitsPerSample Number of bits per one sample (one channel). Possible values are 8,16,24,32 + * Note: You can use value -1 for default value. (Default for MCLK is detached state) + * Returns: 1 on success; 0 on error + */ int begin(int mode, int bitsPerSample); - // change pin setup and mode (default is Half Duplex) - // Can be called only on initialized object (after begin) - int setSckPin(int sckPin); - int setFsPin(int fsPin); - int setDataPin(int sdPin); // shared data pin for simplex - int setDataOutPin(int outSdPin); - int setDataInPin(int inSdPin); + /* + De-initialize IDF I2S driver, frees ring buffers and terminates callback handler task. + */ + void end(); + /* + * Change pin setup for each pin separately. + * Can be called on both uninitialized and initialized object (before and after begin). + * The change takes effect immediately and does not need driver restart. + * Parameter: int pin number of GPIO which should be used for the requested pin setup + * any negative value will set default pin number defined in PIN_I2S_X or PIN_I2S1_X + * Returns: 1 on success; 0 on error + * Note: You can use value -1 for default value. (Default for MCLK is detached state) + */ + int setSckPin(int sckPin); // Set Clock pin + int setFsPin(int fsPin); // Set Frame Sync (Word Select) pin + int setDataPin(int sdPin); // Set shared Data pin for simplex mode + int setDataOutPin(int outSdPin); // Set Data Output pin for duplex mode + int setDataInPin(int inSdPin); // Set Data Input pin for duplex mode + int setMclkPin(int sckPin); // Set Master Clock pin + + /* + * Change pin setup for all pins (except MCLK) at one call using default values set constants in I2S.h + * Can be called on both uninitialized and initialized object (before and after begin). + * The change takes effect immediately and does not need driver restart. + * The MCLK pin will be un-set - i.e. detached from any GPIO. + * Returns: 1 on success; 0 on error + */ int setAllPins(); - int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin); - - int getSckPin(); - int getFsPin(); - int getDataPin(); - int getDataOutPin(); - int getDataInPin(); + /* + * Change pin setup for all pins at one call. + * Can be called on both uninitialized and initialized object (before and after begin). + * The change takes effect immediately and does not need driver restart. + * Parameters: + * int sckPin Clock pin + * int fsPin Frame Sync (Word Select) pin + * int sdPin Shared Data pin for simplex mode + * int outSdPin Data Output pin for duplex mode + * int inSdPin Data Input pin for duplex mode + * int mclkPin Master Clock pin + * Returns: 1 on success; 0 on error + * Note: You can use value -1 for default value. (Default for MCLK is detached state) + */ + int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin, int mclkPin=-1); + + /* + * Unset MCLK pin making it available for other use + * Can be called on both uninitialized and initialized object (before and after begin). + * The change takes effect immediately and does not need driver restart. + * Returns: 1 on success; 0 on error + */ + int unSetMclkPin(); + + /* + * Get current pin GPIO number + * Returns: the GPIO number of requested pin + */ + int getMclkPin(); // Get Master Clock pin + int getSckPin(); // Get Clock pin + int getFsPin(); // Get Frame Sync (Word Select) pin + int getDataPin(); // Get shared Data pin for simplex mode + int getDataOutPin(); // Get Data Output pin for duplex mode + int getDataInPin(); // Get Data Input pin for duplex mode + + /* + * Change mode (default is Simplex) + * Returns: 1 on success; 0 on error + */ int setDuplex(); int setSimplex(); - int isDuplex(); - void end(); + /* + * Get current mode + * Returns: 1 if current mode is Duplex; 0 If current mode is not Duplex + */ + int isDuplex(); - // from Stream + /* + * Returns: number of Bytes available to read from ring buffer. + * Note: The ring buffer is filled automatically by handler task from IDF I2S driver. + */ virtual int available(); - virtual int read(); + + /* + * Reads a single sample from ring buffer and keeps it available for future read (i.e. does not remove the sample from ring buffer) + * Returns: First sample from ring buffer + * Note: The ring buffer is filled automatically by handler task from IDF I2S driver. + */ virtual int peek(); - virtual void flush(); - // from Print - virtual size_t write(uint8_t); - virtual size_t write(const uint8_t *buffer, size_t size); + /* + * Reads a single sample from ring buffer and removes it from the ring buffer. + * Returns: First sample from ring buffer + * Note: The ring buffer is filled automatically by handler task from IDF I2S driver. + */ + virtual int read(); + /* + * Reads an array of samples from ring buffer and removes them from the ring buffer. + * Parameters: + * [OUT] void* buffer Buffer into which the samples will be copied. The buffer must allocated before calling this function! + * [IN] size_t size Requested number of bytes to be read + * Returns: Number of bytes that were actually read. + * Note: Always check the returned value! + */ + int read(void* buffer, size_t size); + + /* + * Returns: number of bytes that can be written into the ring buffer. + */ virtual int availableForWrite(); - int read(void* buffer, size_t size); + /* + * Returns: number of samples that can be written into the ring buffer. + */ + int availableSamplesForWrite(); + + /* + * Write single sample of 8 bit size. + * This function is blocking - if there is not enough space in ring buffer the function will wait until it can write the sample. + * Parameter: + * uint8_t sample The sample to be sent + * Returns: 1 on successful write; 0 on error = did not write the sample to ring buffer + * Note: This functions is used in many examples for it's simplicity, but it's use is discouraged for performance reasons. + * Please consider sending data in arrays using function `size_t write(const uint8_t *buffer, size_t size)` + */ + virtual size_t write(uint8_t sample); + + /* + * Write single sample of up to 32 bit size. + * This function is blocking - if there is not enough space in ring buffer the function will wait until it can write the sample. + * Parameter: + * int32_t sample The sample to be sent + * Returns: Number of written bytes, if successful the value will be equal to bitsPerSample/8 + * Note: This functions is used in many examples for it's simplicity, but it's use is discouraged for performance reasons. + * Please consider sending data in arrays using function `size_t write(const uint8_t *buffer, size_t size)` + */ + size_t write(int32_t sample); + + /* + * Write array of samples. + * This function is non-blocking - the function might write only portion of samples into ring buffer, or potentially none at all. Do check the returned value at all times! + * Parameters: + * uint8_t* buffer Array of samples + * size_t size Number of bytes in array + * Returns: Number of bytes successfully written to ring buffer. + * Note: This is the preferred function for writing samples. + */ + virtual size_t write(const uint8_t *buffer, size_t size); - //size_t write(int); - size_t write(int32_t); + /* + * Write array of samples. + * This function is non-blocking - the function might write only portion of samples into ring buffer, or potentially none at all. Do check the returned value at all times! + * Parameters: + * void* buffer Array of samples + * size_t size Number of bytes in array + * Returns: Number of bytes successfully written to ring buffer. + * Note: This is the preferred function for writing samples. + */ size_t write(const void *buffer, size_t size); + + // Internally used functions size_t write_blocking(const void *buffer, size_t size); size_t write_nonblocking(const void *buffer, size_t size); + /* + * Force-write data from ring buffer to IDF I2S driver + * This function is useful when sending low amount of data, however such use will lead to low quality audio. + * Note: The ring buffer is emptied (sent) automatically by handler task from IDF I2S driver. + */ + virtual void flush(); + + /* + * Callback handle which will be used each time when the IDF I2S driver transmits data from buffer. + */ void onTransmit(void(*)(void)); + + /* + * Callback handle which will be used each time when the IDF I2S driver receives data into buffer. + */ void onReceive(void(*)(void)); - int setBufferSize(int bufferSize); - int getBufferSize(); + /* + * Change the size of DMA buffers. The unit is number of sample frames (CHANNEL_NUMBER * (bits_per_sample/8)) + * The resulting Bytes size of ring buffers can be calculated: + * ring_buffer_bytes_size = (CHANNEL_NUMBER * (bits_per_sample/8)) * DMABufferFrameSize * _I2S_DMA_BUFFER_COUNT + * Example: + * - This library statically set to dual channel, therefore CHANNEL_NUMBER is always 2 + * - For this example let's have bits_per_sample set to 16 + * - Default value of bufferSize is 128 + * - Default value of _I2S_DMA_BUFFER_COUNT is 2 + * ring_buffer_bytes_size = (CHANNEL_NUMBER * (bits_per_sample / 8)) * DMABufferFrameSize * _I2S_DMA_BUFFER_COUNT + * 1024 = ( 2 * ( 16 / 8)) * 128 * 2 + */ + int setDMABufferFrameSize(int DMABufferFrameSize); + + /* + * Change the size of DMA buffers. The unit is number of samples (bits_per_sample/8)) + * The resulting Bytes size of ring buffers can be calculated: + * ring_buffer_bytes_size = (CHANNEL_NUMBER * DMABufferSampleSize * _I2S_DMA_BUFFER_COUNT + */ + int setDMABufferSampleSize(int DMABufferSampleSize); + + /* + * Get size of single DMA buffer. + * The unit is number of sample frames: Bytes size of 1 frame = (CHANNEL_NUMBER * (bits_per_sample / 8)) + * For more info see setDMABufferFrameSize + */ + int getDMABufferFrameSize(); + + /* + * Get size of single DMA buffer. The unit is number of samples: Byte size of 1 sample = (bits_per_sample / 8) + * For more info see setDMABufferFrameSize + */ + int getDMABufferSampleSize(); + + /* + * Get size of single DMA buffer in Bytes. + * For more info see setDMABufferFrameSize + */ + int getDMABufferByteSize(); + + /* + * Get ring buffer size. The unit is number of samples: 1 sample = (bits_per_sample / 8) + * For more info see setDMABufferFrameSize + */ + int getRingBufferSampleSize(); + + /* + * Get ring buffer size in Bytes. + * For more info see setDMABufferFrameSize + */ + int getRingBufferByteSize(); + + /* + Get the ID number of I2S module used for particular object. + Object I2S returns value 0 + Object I2S1 returns value 1 + Only ESP32 and ESP32-S3 have two modules, other SoCs have only one I2S module + controlled by object I2S and the return value will always be 0, the second object I2S1 does not exist. + */ + int getI2SNum(); + + /* + * Returns true if I2S module is correctly initialized and ready for use (function begin() was called and returned 1) + * Returns false if I2S module has not yet been initialized (function begin() was called returned 1), or it has been de-initialized (function end() was called) + */ + bool isInitialized(); + + /* + * Returns 0 on un-initialized object, or if the object is initialized as slave. + * On initialized master object returns sample rate in Hz (same value which was passed as argument with begin() function) + */ + int getSampleRate(); + + /* + * Returns 0 on un-initialized object. + * On initialized master object returns bits per sample (same value which was passed as argument with begin() function) + */ + int getBitsPerSample(); + private: #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) int _gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit); @@ -133,9 +472,10 @@ class I2SClass : public Stream int _createCallbackTask(); - static void onDmaTransferComplete(void*); + static void onDmaTransferComplete(void *device_index); int _installDriver(); void _uninstallDriver(); + void _setMclkPin(int sckPin); void _setSckPin(int sckPin); void _setFsPin(int fsPin); void _setDataPin(int sdPin); @@ -152,19 +492,18 @@ class I2SClass : public Stream } i2s_state_t; int _deviceIndex; - int _sdPin; + int _mclkPin; int _inSdPin; int _outSdPin; int _sckPin; int _fsPin; + int _sdPin; i2s_state_t _state; int _bitsPerSample; uint32_t _sampleRate; int _mode; - uint16_t _buffer_byte_size; - bool _driverInstalled; // Is IDF I2S driver installed? bool _initialized; // Is everything initialized (callback task, I2S driver, ring buffers)? TaskHandle_t _callbackTaskHandle; @@ -172,17 +511,16 @@ class I2SClass : public Stream SemaphoreHandle_t _i2s_general_mutex; RingbufHandle_t _input_ring_buffer; RingbufHandle_t _output_ring_buffer; - int _i2s_dma_buffer_size; + int _i2s_dma_buffer_frame_size; bool _driveClock; uint32_t _peek_buff; bool _peek_buff_valid; - void _tx_done_routine(uint8_t* prev_item); void _rx_done_routine(); uint16_t _nesting_counter; - void _take_if_not_holding(); - void _give_if_top_call(); + inline bool _take_mux(); + inline bool _give_mux(); void _post_read_data_fix(void *input, size_t *size); void _fix_and_write(void *output, size_t size, size_t *bytes_written = NULL, size_t *actual_bytes_written = NULL); @@ -192,4 +530,8 @@ class I2SClass : public Stream extern I2SClass I2S; +#if SOC_I2S_NUM > 1 + extern I2SClass I2S_1; +#endif + #endif diff --git a/libraries/Ticker/src/Ticker.cpp b/libraries/Ticker/src/Ticker.cpp index 629361b2dd0..28b209f1d72 100644 --- a/libraries/Ticker/src/Ticker.cpp +++ b/libraries/Ticker/src/Ticker.cpp @@ -31,9 +31,9 @@ Ticker::~Ticker() { detach(); } -void Ticker::_attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback, uint32_t arg) { +void Ticker::_attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback) { esp_timer_create_args_t _timerConfig; - _timerConfig.arg = reinterpret_cast(arg); + _timerConfig.arg = NULL; _timerConfig.callback = callback; _timerConfig.dispatch_method = ESP_TIMER_TASK; _timerConfig.name = "Ticker"; diff --git a/tests/i2s/i2s.ino b/tests/i2s/i2s.ino new file mode 100644 index 00000000000..661849a3a9f --- /dev/null +++ b/tests/i2s/i2s.ino @@ -0,0 +1,562 @@ +#include +#include +#include "Arduino.h" + +// Internal connections +#include "hal/gpio_hal.h" +#include "esp_rom_gpio.h" + +I2SClass *I2S_obj; +I2SClass *I2S_obj_arr[SOC_I2S_NUM]; +int i2s_index; + +#define TEST_SCK 1 +#define TEST_FS 2 +#define TEST_SD 3 +#define TEST_SD_OUT 4 +#define TEST_SD_IN 5 + +// Supported sample rates (do not change) +int sample_rate[] = {8000,11025,16000,22050,32000,44100,64000,88200,128000}; +int srp_max = sizeof(sample_rate)/sizeof(int); + +// Supported bits per sample (do not change) +int bps[] = {8,16,24,32}; +int bpsp_max = sizeof(bps)/sizeof(int); + +// Frame 1 | | Frame 2 | | Frame 3 +// R channel | L channel | | R channel | L channel | | R channel | L channel +uint8_t data_8bit[] = { 0xFF, 0xAA, 0x55, 0x00, 0xCC, 0x33}; +uint16_t data_16bit[] = { 0xFFFF, 0xAAAA, 0x5555, 0x0000, 0xCCCC, 0x3333}; +uint8_t data_24bit[] = {0xFF, 0xFF, 0xFF, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33}; +uint32_t data_32bit[] = { 0xFFFFFFFF, 0xAAAAAAAA, 0x55555555, 0x00000000, 0xCCCCCCCC, 0x33333333}; + +/* These functions are intended to be called before and after each test. */ +void setUp(void) { + // use default pins + I2S.setAllPins(); + #if SOC_I2S_NUM > 1 + I2S_1.setAllPins(); + #endif +} + +void tearDown(void){ + I2S.end(); + #if SOC_I2S_NUM > 1 + I2S_1.end(); + #endif +} + +// test which will always pass - this is used for chips wich do not have I2S module +void test_pass(void){ + TEST_ASSERT_EQUAL(1, 1); // always pass +} + +// Test begin in master mode - all expected possibilities, then few unexpected option +void test_01(void){ + Serial.printf("[%lu] test_01: I2S_obj=%p\n", millis(), I2S_obj); + int ret = -1; + + Serial.printf("*********** [%lu] Begin I2S with golden parameters **************\n", millis()); + for(int mode = 3; mode < MODE_MAX; ++mode){ + //for(int mode = 0; mode < MODE_MAX; ++mode){ + Serial.printf("[%lu] begin: mode %d \"%s\"\n", millis(), mode, i2s_mode_text[mode]); + for(int srp = 0; srp < srp_max; ++srp){ + for(int bpsp = 0; bpsp < bpsp_max; ++bpsp){ + Serial.printf("[%lu] begin: mode %d \"%s\", sample rate %d, bps %d\n", millis(), mode, i2s_mode_text[mode], sample_rate[srp], bps[bpsp]); + #if defined(SOC_I2S_SUPPORTS_ADC) && defined(SOC_I2S_SUPPORTS_DAC) + if(mode == ADC_DAC_MODE && bps[bpsp] == 16){ + #if CONFIG_IDF_TARGET_ESP32 + I2S_obj->setDataInPin(32); // Default data pin does not support DAC + #else + I2S_obj->setDataInPin(4); // Default data pin does not support DAC + #endif + } + #endif + ret = I2S_obj->begin(mode, sample_rate[srp], bps[bpsp]); + + if(mode == ADC_DAC_MODE){ + #if defined(SOC_I2S_SUPPORTS_ADC) && defined(SOC_I2S_SUPPORTS_DAC) + if(bps[bpsp] == 16){ + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success + I2S_obj->setDataInPin(-1); // Set to default data pin + }else{ + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + } + #else + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + #endif + }else if(mode == PDM_STEREO_MODE || mode == PDM_MONO_MODE){ + #if defined(SOC_I2S_SUPPORTS_PDM_TX) || defined(SOC_I2S_SUPPORTS_PDM_RX) + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success + #else + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + #endif + }else{ + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success + } + I2S_obj->end(); + } + } + } + + Serial.printf("*********** [%lu] Begin I2S with nonexistent mode **************\n", millis()); + // Nonexistent mode - should fail + ret = I2S_obj->begin(MODE_MAX, 8000, 8); + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + I2S_obj->end(); + + Serial.printf("*********** [%lu] Begin I2S with Unsupported Bits Per Sample **************\n", millis()); + // Unsupported Bits Per Sample - all should fail + int unsupported_bps[] = {-1,0,1,7,9,15,17,23,25,31,33,255,65536}; + int ubpsp_max = sizeof(unsupported_bps)/sizeof(int); + for(int ubpsp = 0; ubpsp < ubpsp_max; ++ubpsp){ + ret = I2S_obj->begin(I2S_PHILIPS_MODE, 8000, unsupported_bps[ubpsp]); + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + I2S_obj->end(); + } + + Serial.printf("*********** [%lu] Begin I2S with Unusual, yet possible sample rates **************\n", millis()); + // Unusual, yet possible sample rates - all should succeed + int unusual_sample_rate[] = {4000,64000,88200,96000,128000}; + int usrp_max = sizeof(unusual_sample_rate)/sizeof(int); + for(int usrp = 0; usrp < usrp_max; ++usrp){ + ret = I2S_obj->begin(I2S_PHILIPS_MODE, unusual_sample_rate[usrp], 32); + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success + I2S_obj->end(); + } + Serial.printf("*********** [%lu] test_01 complete **************\n", millis()); +} + +// Test begin in slave mode - all expected possibilities, then few unexpected option +void test_02(void){ + Serial.printf("[%lu] test_02: I2S_obj=%p\n", millis(), I2S_obj); + int ret = -1; + + Serial.printf("*********** [%lu] Begin I2S with golden parameters **************\n", millis()); + for(int mode = 0; mode < MODE_MAX; ++mode){ + for(int bpsp = 0; bpsp < bpsp_max; ++bpsp){ + Serial.printf("[%lu] begin: mode %d \"%s\", bps %d\n", millis(), mode, i2s_mode_text[mode], bps[bpsp]); + #if defined(SOC_I2S_SUPPORTS_ADC) && defined(SOC_I2S_SUPPORTS_DAC) + if(mode == ADC_DAC_MODE && bps[bpsp] == 16){ + #if CONFIG_IDF_TARGET_ESP32 + I2S_obj->setDataInPin(32); // Default data pin does not support DAC + #else + I2S_obj->setDataInPin(4); // Default data pin does not support DAC + #endif + } + #endif + ret = I2S_obj->begin(mode, bps[bpsp]); + + if(mode == ADC_DAC_MODE){ + #if defined(SOC_I2S_SUPPORTS_ADC) && defined(SOC_I2S_SUPPORTS_DAC) + if(bps[bpsp] == 16){ + I2S_obj->setDataInPin(4); // Default data pin does not support DAC + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success + I2S_obj->setDataInPin(-1); // Set to default data pin + }else{ + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + } + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + #endif + }else if(mode == PDM_STEREO_MODE || mode == PDM_MONO_MODE){ + #if defined(SOC_I2S_SUPPORTS_PDM_TX) || defined(SOC_I2S_SUPPORTS_PDM_RX) + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success + #else + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + #endif + }else{ + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + TEST_ASSERT_EQUAL(true, I2S_obj->isInitialized()); // Expecting true == Success + } + I2S_obj->end(); + } + } + + Serial.printf("*********** [%lu] Begin I2S with nonexistent mode **************\n", millis()); + // Nonexistent mode - should fail + ret = I2S_obj->begin(MODE_MAX, 8000, 8); + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + I2S_obj->end(); + + Serial.printf("*********** [%lu] Begin I2S with Unsupported Bits Per Sample **************\n", millis()); + // Unsupported Bits Per Sample - all should fail + int unsupported_bps[] = {-1,0,1,7,9,15,17,23,25,31,33,255,65536}; + int ubpsp_max = sizeof(unsupported_bps)/sizeof(int); + for(int ubpsp = 0; ubpsp < ubpsp_max; ++ubpsp){ + ret = I2S_obj->begin(I2S_PHILIPS_MODE, unsupported_bps[ubpsp]); + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == Failure + TEST_ASSERT_EQUAL(false, I2S_obj->isInitialized()); // Expecting false == Failure + I2S_obj->end(); + } + + Serial.printf("*********** [%lu] test_02 complete **************\n", millis()); +} + +// Pin setters and geters +// set all pins and check if they are set as expected +// +// set all pins to default +// get all pins and check is the are default +// +// set wrong pin numbers - expect fail +void test_03(void){ + Serial.printf("[%lu] test_03: I2S_obj=%p\n", millis(), I2S_obj); + int ret = -1; + bool end = false; + while(!end){ + end = I2S_obj->isInitialized(); // initialize after first set of test and set end flag for next cycle + Serial.printf("[%lu] test_03: intialized = %d\n", millis(), end); + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // Test on UNINITIALIZED I2S + //////////////////////////////////////////////////////////////////////////////////////////////////////// + // Set pins to test values and check + + ret = I2S_obj->setSckPin(TEST_SCK); // Set Clock pin + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getSckPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SCK, ret); + + ret = I2S_obj->setFsPin(TEST_FS); // Set Frame Sync (Word Select) pin + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getFsPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_FS, ret); + + ret = I2S_obj->setDataPin(TEST_SD); // Set shared Data pin for simplex mode + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getDataPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD, ret); + + ret = I2S_obj->setDataOutPin(TEST_SD_OUT); // Set Data Output pin for duplex mode + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getDataOutPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD_OUT, ret); + + ret = I2S_obj->setDataInPin(TEST_SD_IN); // Set Data Input pin for duplex mode + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getDataInPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD_IN, ret); + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // set All pins to default values and check + + ret = I2S_obj->setAllPins(); // Set pins to default + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + + ret = I2S_obj->getSckPin(); // Get Clock pin + TEST_ASSERT_EQUAL(i2s_index ? PIN_I2S1_SCK : PIN_I2S_SCK, ret); + ret = I2S_obj->getFsPin(); // Get Frame Sync (Word Select) pin + TEST_ASSERT_EQUAL(i2s_index ? PIN_I2S1_FS : PIN_I2S_FS, ret); + ret = I2S_obj->getDataPin(); // Get shared Data pin for simplex mode + TEST_ASSERT_EQUAL(i2s_index ? PIN_I2S1_SD : PIN_I2S_SD, ret); + ret = I2S_obj->getDataOutPin(); // Get Data Output pin for duplex mode + TEST_ASSERT_EQUAL(i2s_index ? PIN_I2S1_SD_OUT : PIN_I2S_SD_OUT, ret); + ret = I2S_obj->getDataInPin(); // Get Data Input pin for duplex mode + TEST_ASSERT_EQUAL(i2s_index ? PIN_I2S1_SD_IN : PIN_I2S_SD_IN, ret); + ret = I2S_obj->setAllPins(); // Set default pins to test values + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // Set All pins to test values and check + + ret = I2S_obj->setSckPin(TEST_SCK); // Set Clock pin + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getSckPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SCK, ret); + + ret = I2S_obj->setFsPin(TEST_FS); // Set Frame Sync (Word Select) pin + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getFsPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_FS, ret); + + ret = I2S_obj->setDataPin(TEST_SD); // Set shared Data pin for simplex mode + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getDataPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD, ret); + + ret = I2S_obj->setDataOutPin(TEST_SD_OUT); // Set Data Output pin for duplex mode + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getDataOutPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD_OUT, ret); + + ret = I2S_obj->setDataInPin(TEST_SD_IN); // Set Data Input pin for duplex mode + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + ret = I2S_obj->getDataInPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD_IN, ret); + + ret = I2S_obj->setAllPins(); // Set pins to default + ret = I2S_obj->setAllPins(TEST_SCK, TEST_FS, TEST_SD, TEST_SD_OUT, TEST_SD_IN); // Set pins to test values + ret = I2S_obj->getSckPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SCK, ret); + ret = I2S_obj->getFsPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_FS, ret); + ret = I2S_obj->getDataPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD, ret); + ret = I2S_obj->getDataOutPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD_OUT, ret); + ret = I2S_obj->getDataInPin(); // Get Clock pin + TEST_ASSERT_EQUAL(TEST_SD_IN, ret); + + // Test on INITIALIZED I2S + I2S_obj->begin(I2S_PHILIPS_MODE, 32000, 32); + } + + Serial.printf("*********** [%lu] test_03 complete **************\n", millis()); +} + + +// test simplex / duplex switch functions (not actual data transfer) +void test_04(void){ + Serial.printf("*********** [%lu] test_04 starting **************\n", millis()); + int ret = -1; + bool end = false; + while(!end){ + end = I2S_obj->isInitialized(); // initialize after first set of test and set end flag for next cycle + + ret = I2S_obj->isDuplex(); + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == simplex (default) + + ret = I2S_obj->setDuplex(); + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + + ret = I2S_obj->isDuplex(); + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == duplex + + ret = I2S_obj->setSimplex(); + TEST_ASSERT_EQUAL(1, ret); // Expecting 1 == Success + + ret = I2S_obj->isDuplex(); + TEST_ASSERT_EQUAL(0, ret); // Expecting 0 == simplex (default) + Serial.printf("Tests done, now begin\n"); + // Test on INITIALIZED I2S + I2S_obj->begin(I2S_PHILIPS_MODE, 32000, 32); + } + Serial.printf("*********** [%lu] test_04 complete **************\n", millis()); +} + +#if CONFIG_IDF_TARGET_ESP32 +#define I2S0_DATA_OUT_IDX I2S0O_DATA_OUT23_IDX +#define I2S0_DATA_IN_IDX I2S0I_DATA_IN15_IDX +#elif CONFIG_IDF_TARGET_ESP32S2 +#define I2S0_DATA_OUT_IDX I2S0O_DATA_OUT23_IDX +#define I2S0_DATA_IN_IDX I2S0I_DATA_IN15_IDX +#elif CONFIG_IDF_TARGET_ESP32C3 +#define I2S0_DATA_OUT_IDX I2SO_SD_OUT_IDX +#define I2S0_DATA_IN_IDX I2SI_SD_IN_IDX +#elif CONFIG_IDF_TARGET_ESP32S3 +#define I2S0_DATA_OUT_IDX I2S0O_SD_OUT_IDX +#define I2S0_DATA_IN_IDX I2S0I_SD_IN_IDX +#endif + +// Simple data transmit, returned written bytes check and buffer check. +void test_05(void){ + Serial.printf("*********** [%lu] test_05 starting **************\n", millis()); + I2S_obj->setAllPins(); + // set input and output on same pin and connect internally + I2S_obj->setDataOutPin(PIN_I2S_SD); + I2S_obj->setDataInPin(PIN_I2S_SD); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SD], PIN_FUNC_GPIO); + gpio_set_direction(PIN_I2S_SD, GPIO_MODE_INPUT_OUTPUT); + esp_rom_gpio_connect_out_signal(PIN_I2S_SD, I2S0_DATA_OUT_IDX, 0, 0); + esp_rom_gpio_connect_in_signal(PIN_I2S_SD, I2S0_DATA_IN_IDX, 0); + + int ret = -1; + + for(int mode = 0; mode < MODE_MAX; ++mode){ + Serial.printf("[%lu] data transfer begin: mode %d \"%s\"\n", millis(), mode, i2s_mode_text[mode]); + for(int srp = 0; srp < srp_max; ++srp){ + for(int bpsp = 0; bpsp < bpsp_max; ++bpsp){ + //Serial.printf("[%lu] begin: mode %d \"%s\", sample rate %d, bps %d\n", millis(), mode, i2s_mode_text[mode], sample_rate[srp], bps[bpsp]); + ret = I2S_obj->begin(mode, sample_rate[srp], bps[bpsp]); + + // Output buffer after init (before any write) should be empty and available for write should equal to the size of buffer + int DMA_buffer_frame_size = I2S_obj->getDMABufferFrameSize(); + int DMA_buffer_sample_size = I2S_obj->getDMABufferSampleSize(); + int buffer_byte_size = I2S_obj->getRingBufferByteSize(); + int available_for_write = I2S_obj->availableForWrite(); + int available_samples_for_write = I2S_obj->availableSamplesForWrite(); + TEST_ASSERT_EQUAL(DMA_buffer_frame_size * 2, DMA_buffer_sample_size); + TEST_ASSERT_EQUAL(DMA_buffer_frame_size * 2 * (bps[bpsp]/8), buffer_byte_size); + TEST_ASSERT_EQUAL(available_for_write, buffer_byte_size); + TEST_ASSERT_EQUAL(available_samples_for_write, DMA_buffer_sample_size); + + switch(bps[bpsp]){ + case 8: + ret = I2S_obj->write((int8_t)data_8bit[0]); + break; + case 16: + ret = I2S_obj->write((int16_t)data_16bit[0]); + break; + case 24: + // 24 bits per sample mode will ignore the MSB from the 32-bit sample and return 3 on success, + // therefore a 32-bit function inside the following case can be used. + case 32: + ret = I2S_obj->write((int32_t)data_32bit[0]); + break; + } // switch + TEST_ASSERT_EQUAL(ret, bps[bpsp]/8); + TEST_ASSERT_EQUAL(available_for_write-ret, I2S_obj->availableForWrite()); + available_for_write -= ret; // update for next tests + + // write rest of the + size_t expected_bytes_written = 0; + switch(bps[bpsp]){ + case 8: + expected_bytes_written = sizeof(data_8bit) - sizeof(data_8bit[0]); + ret = I2S_obj->write((uint8_t*)(&data_8bit[1]), expected_bytes_written); + break; + case 16: + expected_bytes_written = sizeof(data_16bit) - sizeof(data_16bit[0]); + ret = I2S_obj->write((uint8_t*)(&data_16bit[1]), expected_bytes_written); + break; + case 24: + expected_bytes_written = sizeof(data_24bit) - 3; + ret = I2S_obj->write((uint8_t*)(&data_24bit[3]), expected_bytes_written); + break; + case 32: + expected_bytes_written = sizeof(data_32bit) - sizeof(data_32bit[0]); + ret = I2S_obj->write((uint8_t*)(&data_32bit[1]), expected_bytes_written); + break; + } // switch + TEST_ASSERT_EQUAL(ret, expected_bytes_written); + TEST_ASSERT_EQUAL(available_for_write-ret, I2S_obj->availableForWrite()); + } // for all bits per sample + } // for all sample rates + } // for all modes + + Serial.printf("*********** [%lu] test_05 complete **************\n", millis()); +} + +// Loop-back data transfer +void test_06(void){ + Serial.printf("*********** [%lu] test_06 starting **************\n", millis()); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[PIN_I2S_SD], PIN_FUNC_GPIO); + gpio_set_direction(PIN_I2S_SD, GPIO_MODE_INPUT_OUTPUT); + esp_rom_gpio_connect_out_signal(PIN_I2S_SD, I2S0_DATA_OUT_IDX, 0, 0); + esp_rom_gpio_connect_in_signal(PIN_I2S_SD, I2S0_DATA_IN_IDX, 0); + + int ret = -1; + + for(int mode = 0; mode < MODE_MAX; ++mode){ + Serial.printf("[%lu] data transfer begin: mode %d \"%s\"\n", millis(), mode, i2s_mode_text[mode]); + for(int srp = 0; srp < srp_max; ++srp){ + for(int bpsp = 0; bpsp < bpsp_max; ++bpsp){ + //Serial.printf("[%lu] begin: mode %d \"%s\", sample rate %d, bps %d\n", millis(), mode, i2s_mode_text[mode], sample_rate[srp], bps[bpsp]); + ret = I2S_obj->begin(mode, sample_rate[srp], bps[bpsp]); + size_t expected_bytes_written = 0; + int available_for_write = I2S_obj->getRingBufferByteSize(); + int max_i = 0; + switch(bps[bpsp]){ + case 8: + expected_bytes_written = sizeof(data_8bit); + ret = I2S_obj->write(data_8bit, expected_bytes_written); + max_i = sizeof(data_8bit) / sizeof(data_8bit[0]); + break; + case 16: + expected_bytes_written = sizeof(data_16bit); + ret = I2S_obj->write(data_16bit, expected_bytes_written); + max_i = sizeof(data_16bit) / sizeof(data_16bit[0]); + break; + case 24: + expected_bytes_written = sizeof(data_24bit); + ret = I2S_obj->write(data_24bit, expected_bytes_written); + max_i = sizeof(data_24bit) / sizeof(data_24bit[0]); + break; + case 32: + expected_bytes_written = sizeof(data_32bit); + ret = I2S_obj->write(data_32bit, expected_bytes_written); + max_i = sizeof(data_32bit) / sizeof(data_32bit[0]); + break; + } // switch + TEST_ASSERT_EQUAL(ret, expected_bytes_written); + TEST_ASSERT_EQUAL(available_for_write-ret, I2S_obj->availableForWrite()); + while(!I2S_obj->available()){ delay(1); } + uint8_t buffer[sizeof(data_32bit)]; // Create buffer to accommodate the largest possible array + ret = I2S_obj->read(buffer, expected_bytes_written); + TEST_ASSERT_EQUAL(ret, expected_bytes_written); + TEST_ASSERT_EQUAL(0, I2S_obj->available()); // Input buffer should be empty + + // Check data + for(int i = 0; i < max_i; ++i){ + switch(bps[bpsp]){ + case 8: + TEST_ASSERT_EQUAL(((uint8_t*)buffer)[i], data_8bit[i]); + break; + case 16: + TEST_ASSERT_EQUAL(((uint16_t*)buffer)[i], data_16bit[i]); + case 24: + break; + TEST_ASSERT_EQUAL(((uint8_t*)buffer)[i], data_24bit[i]); + break; + case 32: + TEST_ASSERT_EQUAL(((uint32_t*)buffer)[i], data_32bit[i]); + break; + } // switch + } // Check data for loop + } // for all bps + } // for all sample rates + } // for all modes +} + +// Dual module data transfer test (only for ESP32 and ESP32-S3) +void test_0x(void){ + // TODO +} + +void setup() { + Serial.begin(115200); + while (!Serial) { + ; + } + delay(500); + //Serial.printf("Num of I2S module =%d\n", SOC_I2S_NUM); + //Serial.printf("I2S0=%p\n", &I2S); + + UNITY_BEGIN(); + #if SOC_I2S_NUM == 0 + RUN_TEST(test_pass); // This SoC does not have I2S - pass the test without running the tests + #endif + + #if SOC_I2S_NUM > 0 + I2S_obj_arr[0] = &I2S; + #if SOC_I2S_NUM > 1 + I2S_obj_arr[1] = &I2S_1; + //Serial.printf("I2S_1=%p\n", &I2S_1); + #endif + i2s_index = 0; + for(; i2s_index < SOC_I2S_NUM; ++ i2s_index){ + Serial.printf("*******************************************************\n"); + Serial.printf("********************* I2S # %d *************************\n", i2s_index); + Serial.printf("*******************************************************\n"); + I2S_obj = I2S_obj_arr[i2s_index]; + //RUN_TEST(test_01); // begin master + //RUN_TEST(test_02); // begin slave + //RUN_TEST(test_03); // pin setters and geters + RUN_TEST(test_04); // duplex / simplex + + RUN_TEST(test_05); // Simple data transmit, returned written bytes check and buffer check. + RUN_TEST(test_06); // Loop-back data transfer + } + #if SOC_I2S_NUM > 1 + RUN_TEST(test_0x); // Dual module data transfer test (only for ESP32 and ESP32-S3) + #endif + #endif // SOC_I2S_NUM > 0 + UNITY_END(); +} + +void loop() { +} diff --git a/tests/i2s/test_i2s.py b/tests/i2s/test_i2s.py new file mode 100644 index 00000000000..b49bc2b6ed8 --- /dev/null +++ b/tests/i2s/test_i2s.py @@ -0,0 +1,2 @@ +def test_i2s(dut): + dut.expect_unity_test_output(timeout=120) diff --git a/tools/sdk/esp32/include/driver/include/driver/i2s.h b/tools/sdk/esp32/include/driver/include/driver/i2s.h index d231ad396bb..3807a8854ee 100644 --- a/tools/sdk/esp32/include/driver/include/driver/i2s.h +++ b/tools/sdk/esp32/include/driver/include/driver/i2s.h @@ -140,11 +140,11 @@ typedef intr_handle_t i2s_isr_handle_t; // for backward compatible */ typedef enum { I2S_EVENT_DMA_ERROR, - I2S_EVENT_TX_DONE, /*!< I2S DMA finish sent 1 buffer*/ - I2S_EVENT_RX_DONE, /*!< I2S DMA finish received 1 buffer*/ - I2S_EVENT_TX_Q_OVF, /*!< I2S DMA sent queue overflow*/ - I2S_EVENT_RX_Q_OVF, /*!< I2S DMA receive queue overflow*/ - I2S_EVENT_MAX, /*!< I2S event max index*/ + I2S_EVENT_TX_DONE, /*!< 1 I2S DMA finish sent 1 buffer*/ + I2S_EVENT_RX_DONE, /*!< 2 I2S DMA finish received 1 buffer*/ + I2S_EVENT_TX_Q_OVF, /*!< 3 I2S DMA sent queue overflow*/ + I2S_EVENT_RX_Q_OVF, /*!< 4 I2S DMA receive queue overflow*/ + I2S_EVENT_MAX, /*!< 5 I2S event max index*/ } i2s_event_type_t; /**