Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Powersaving potential? #728

Open
PMKrol opened this issue Apr 19, 2024 · 9 comments
Open

Powersaving potential? #728

PMKrol opened this issue Apr 19, 2024 · 9 comments
Labels

Comments

@PMKrol
Copy link

PMKrol commented Apr 19, 2024

Hi!

I'm building portable audio player. I am wondering is it possible to reduce somehow power consumption.

I was wondering about lowering cpu frequency, but even with 160MHz sound gets choppy.
I had an idea to do something like this:

audio.loop();
setCpuFrequencyMhz(80);
vTaskDelay(1);
setCpuFrequencyMhz(240);

but it also does not work - sound gets choppy. I know i2s does some stuff behind, but I thought CPU frequency won't affect this. I was wrong, but I do not understand why.
I've checked what happens, and with lower CPU APB works on 80MHz:

CPU = 240 MHz, XTAL = 40 MHz, APB = 80000000 Hz
CPU = 160 MHz, XTAL = 40 MHz, APB = 80000000 Hz
CPU = 80 MHz, XTAL = 40 MHz, APB = 80000000 Hz

So the problem must be somewhere else. According to https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/i2s.html (I2S clock section) clock can be sourced from APLL:

Audio PLL clock, which is more precise than I2S_CLK_SRC_PLL_160M in high sample rate applications. Its frequency is configurable according to the sample rate. However, if APLL has been occupied by EMAC or other channels, the APLL frequency cannot be changed, and the driver will try to work under this APLL frequency. If this frequency cannot meet the requirements of I2S, the clock configuration will fail.

I do not know how ESP32-audioI2s is configured now, but maybe it is the way to reduce some power usage here - by safely reducing CPU clock? My CPU does have plenty free time as everything non-audio-crucial was moved to core 0.

Also, I've found this topic: https://www.esp32.com/viewtopic.php?t=14185, but it is way over my undestanding possibilities ;)

Best regards.

@schreibfaul1
Copy link
Owner

I'm not the I2S expert. As I see it, there are only two sources for the clock.
image
That is the crystal and the PLL.

I2S_CLK_SRC_APLL is in the documentation, I have no idea how to select it. image

Best regards.

@PMKrol
Copy link
Author

PMKrol commented Apr 19, 2024

This one i don't understand: https://www.esp32.com/viewtopic.php?t=3176

but finally this may be useful. Someone used APLL (without quality luck, but maybe it won't be our case:
https://esp32.com/viewtopic.php?f=12&t=32780

@PMKrol
Copy link
Author

PMKrol commented Apr 22, 2024

@schreibfaul1 , any hope for update?
or maybe instruction on how to modify i2s settings.

@schreibfaul1
Copy link
Owner

I don't think so. I2S_CLK_SRC_APLL no longer exists in IDF V5.1. (or I can't find it?)

@PMKrol
Copy link
Author

PMKrol commented Apr 22, 2024

I'm trying to find this for You:

here is some interesting structure:

typedef struct {
    void                *periph_dev;    /* DMA peripheral device address */
    intr_handle_t       intr_handle;    /* Interrupt handle */
    bool                use_apll;       /* Whether use APLL as clock source */
} dac_dma_periph_i2s_t;

from: https://github.com/espressif/esp-idf/blob/636ff35b52f10e1a804a3760a5bd94e68f4b1b71/components/esp_driver_dac/esp32/dac_dma.c#L41

Here, function i2s_set_get_apll_freq:

#if SOC_I2S_SUPPORTS_APLL
static uint32_t i2s_set_get_apll_freq(uint32_t mclk_freq_hz)
{
    /* Calculate the expected APLL  */
    int mclk_div = (int)((CLK_LL_APLL_MIN_HZ / mclk_freq_hz) + 1);
    /* apll_freq = mclk * div
        * when div = 1, hardware will still divide 2
        * when div = 0, the final mclk will be unpredictable
        * So the div here should be at least 2 */
    mclk_div = mclk_div < 2 ? 2 : mclk_div;
    uint32_t expt_freq = mclk_freq_hz * mclk_div;
    if (expt_freq > CLK_LL_APLL_MAX_HZ) {
        ESP_LOGE(TAG, "The required APLL frequency exceed its maximum value");
        return 0;
    }
    uint32_t real_freq = 0;
    esp_err_t ret = periph_rtc_apll_freq_set(expt_freq, &real_freq);
    if (ret == ESP_ERR_INVALID_ARG) {
        ESP_LOGE(TAG, "set APLL freq failed due to invalid argument");
        return 0;
    }
    if (ret == ESP_ERR_INVALID_STATE) {
        ESP_LOGW(TAG, "APLL is occupied already, it is working at %"PRIu32" Hz while the expected frequency is %"PRIu32" Hz", real_freq, expt_freq);
        ESP_LOGW(TAG, "Trying to work at %"PRIu32" Hz...", real_freq);
    }
    ESP_LOGD(TAG, "APLL expected frequency is %"PRIu32" Hz, real frequency is %"PRIu32" Hz", expt_freq, real_freq);
    return real_freq;
}
#endif

in https://github.com/espressif/esp-idf/blob/636ff35b52f10e1a804a3760a5bd94e68f4b1b71/components/esp_driver_i2s/i2s_common.c#L466

Here in i2s_driver_config_t use_apll:

typedef struct {

    i2s_mode_t              mode;                       /*!< I2S work mode */
    uint32_t                sample_rate;                /*!< I2S sample rate */
    i2s_bits_per_sample_t   bits_per_sample;            /*!< I2S sample bits in one channel */
    i2s_channel_fmt_t       channel_format;             /*!< I2S channel format.*/
    i2s_comm_format_t       communication_format;       /*!< I2S communication format */
    int                     intr_alloc_flags;           /*!< Flags used to allocate the interrupt. One or multiple (ORred) ESP_INTR_FLAG_* values. See esp_intr_alloc.h for more info */
    union {
        int dma_desc_num;                               /*!< The total number of descriptors used by I2S DMA to receive/transmit data */
        int dma_buf_count __attribute__((deprecated));  /*!< This is an alias to 'dma_desc_num' for backward compatibility */
    };
    union {
        int dma_frame_num;                              /*!< Frame number for one-time sampling. Frame here means the total data from all the channels in a WS cycle */
        int dma_buf_len __attribute__((deprecated));    /*!< This is an alias to 'dma_frame_num' for backward compatibility */
    };
    bool                    use_apll;                   /*!< I2S using APLL as main I2S clock, enable it to get accurate clock */
    bool                    tx_desc_auto_clear;         /*!< I2S auto clear tx descriptor if there is underflow condition (helps in avoiding noise in case of data unavailability) */
    int                     fixed_mclk;                 /*!< I2S using fixed MCLK output. If use_apll = true and fixed_mclk > 0, then the clock output for i2s is fixed and equal to the fixed_mclk value. If fixed_mclk set, mclk_multiple won't take effect */
    i2s_mclk_multiple_t     mclk_multiple;              /*!< The multiple of I2S master clock(MCLK) to sample rate */
    i2s_bits_per_chan_t     bits_per_chan;              /*!< I2S total bits in one channel, only take effect when larger than 'bits_per_sample', default '0' means equal to 'bits_per_sample' */

#if SOC_I2S_SUPPORTS_TDM
    i2s_channel_t           chan_mask;                  /*!< I2S active channel bit mask, set value in `i2s_channel_t` to enable specific channel, the bit map of active channel can not exceed (0x1<<total_chan). */
    uint32_t                total_chan;                 /*!< I2S Total number of channels. If it is smaller than the biggest active channel number, it will be set to this number automatically. */
    bool                    left_align;                 /*!< Set to enable left alignment */
    bool                    big_edin;                   /*!< Set to enable big endian */
    bool                    bit_order_msb;              /*!< Set to enable msb order */
    bool                    skip_msk;                   /*!< Set to enable skip mask. If it is enabled, only the data of the enabled channels will be sent, otherwise all data stored in DMA TX buffer will be sent */
#endif // SOC_I2S_SUPPORTS_TDM

in https://github.com/espressif/esp-idf/blob/636ff35b52f10e1a804a3760a5bd94e68f4b1b71/components/driver/deprecated/driver/i2s_types_legacy.h#L230

Clock config in
esp_err_t i2s_channel_reconfig_pdm_tx_clock(i2s_chan_handle_t handle, const i2s_pdm_tx_clk_config_t *clk_cfg):

esp_err_t i2s_channel_reconfig_pdm_tx_clock(i2s_chan_handle_t handle, const i2s_pdm_tx_clk_config_t *clk_cfg)
{
    I2S_NULL_POINTER_CHECK(TAG, handle);
    I2S_NULL_POINTER_CHECK(TAG, clk_cfg);
    ESP_RETURN_ON_FALSE(handle->dir == I2S_DIR_TX, ESP_ERR_INVALID_ARG, TAG, "This channel handle is not a TX handle");

    esp_err_t ret = ESP_OK;

    xSemaphoreTake(handle->mutex, portMAX_DELAY);
    ESP_GOTO_ON_FALSE(handle->mode == I2S_COMM_MODE_PDM, ESP_ERR_INVALID_ARG, err, TAG, "this handle is not working in standard mode");
    ESP_GOTO_ON_FALSE(handle->state == I2S_CHAN_STATE_READY, ESP_ERR_INVALID_STATE, err, TAG, "invalid state, I2S should be disabled before reconfiguring the clock");
    i2s_pdm_tx_config_t *pdm_tx_cfg = (i2s_pdm_tx_config_t *)handle->mode_info;
    ESP_GOTO_ON_FALSE(pdm_tx_cfg, ESP_ERR_INVALID_STATE, err, TAG, "initialization not complete");

#if SOC_I2S_SUPPORTS_APLL
    /* Enable APLL and acquire its lock when the clock source is changed to APLL */
    if (clk_cfg->clk_src == I2S_CLK_SRC_APLL && pdm_tx_cfg->clk_cfg.clk_src != I2S_CLK_SRC_APLL) {
        periph_rtc_apll_acquire();
        handle->apll_en = true;
    }
    /* Disable APLL and release its lock when clock source is changed to 160M_PLL */
    if (clk_cfg->clk_src != I2S_CLK_SRC_APLL && pdm_tx_cfg->clk_cfg.clk_src == I2S_CLK_SRC_APLL) {
        periph_rtc_apll_release();
        handle->apll_en = false;
    }
#endif

    ESP_GOTO_ON_ERROR(i2s_pdm_tx_set_clock(handle, clk_cfg), err, TAG, "update clock failed");
#ifdef CONFIG_PM_ENABLE
    // Create/Re-create power management lock
    if (pdm_tx_cfg->clk_cfg.clk_src != clk_cfg->clk_src) {
        ESP_GOTO_ON_ERROR(esp_pm_lock_delete(handle->pm_lock), err, TAG, "I2S delete old pm lock failed");
        esp_pm_lock_type_t pm_type = ESP_PM_APB_FREQ_MAX;
#if SOC_I2S_SUPPORTS_APLL
        if (clk_cfg->clk_src == I2S_CLK_SRC_APLL) {
            pm_type = ESP_PM_NO_LIGHT_SLEEP;
        }
#endif // SOC_I2S_SUPPORTS_APLL
        ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed");
    }
#endif //CONFIG_PM_ENABLE

    xSemaphoreGive(handle->mutex);

    return ESP_OK;
err:
    xSemaphoreGive(handle->mutex);
    return ret;
}

https://github.com/espressif/esp-idf/blob/636ff35b52f10e1a804a3760a5bd94e68f4b1b71/components/esp_driver_i2s/i2s_pdm.c#L221

This test_app might be very interesting: https://github.com/espressif/esp-idf/blob/636ff35b52f10e1a804a3760a5bd94e68f4b1b71/components/esp_driver_dac/test_apps/dac/main/test_dac.c#L297
cont_cfg.clk_src = DAC_DIGI_CLK_SRC_APLL;

@schreibfaul1 , I'm trying to help You at my best since it would be awesome for my solution (lowering CPU freq gives me around 30 mA (well, I should use REAL multimeter and I will, but currently I'm measuring on cheap USB power meter. So it might be 21 or 39 mA aswell xD).

@PMKrol
Copy link
Author

PMKrol commented Apr 29, 2024

@schreibfaul1 , shameless bump!

@SanZamoyski
Copy link

I do not know where actually I've found stuff that helped, but now I almost can use this in Audio loop:

       setCpuFrequencyMhz(80);   
        vTaskDelay(1 / portTICK_RATE_MS);

        if(!audio_pause){
            setCpuFrequencyMhz(240);
        }`

it hangs after short while but no choppy sound!

This is what I modified in Audio.cpp:197

#else
    m_i2s_config.sample_rate          = 44100; //16000;                                     //PMK
    m_i2s_config.bits_per_sample      = I2S_BITS_PER_SAMPLE_16BIT;
    m_i2s_config.channel_format       = I2S_CHANNEL_FMT_RIGHT_LEFT;
    m_i2s_config.intr_alloc_flags     = ESP_INTR_FLAG_LEVEL1; // interrupt priority
    m_i2s_config.dma_buf_count        = 16;
    m_i2s_config.dma_buf_len          = 512;
    m_i2s_config.use_apll             = 1; //0 // must be disabled in V2.0.1-RC1            //PMK
    m_i2s_config.tx_desc_auto_clear   = true;   // new in V1.0.1
    m_i2s_config.fixed_mclk           = false; //true;                                      //PMK
    m_i2s_config.mclk_multiple        = I2S_MCLK_MULTIPLE_128;
    m_i2s_config.bits_per_chan        = I2S_BITS_PER_CHAN_DEFAULT; // Use bits per sample    //PMK

    if (internalDAC)  {
        #ifdef CONFIG_IDF_TARGET_ESP32  // ESP32S3 has no DAC
        printf("internal DAC");
        m_i2s_config.mode             = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN );
        m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_MSB); // vers >= 2.0.5
        i2s_driver_install((i2s_port_t)m_i2s_num, &m_i2s_config, 0, NULL);
        i2s_set_dac_mode((i2s_dac_mode_t)m_f_channelEnabled);
        if(m_f_channelEnabled != I2S_DAC_CHANNEL_BOTH_EN) {
            m_f_forceMono = true;
        }
        #endif
    }
    else {
        // powerdown APLL/PLLA
        SET_PERI_REG_MASK(RTC_CNTL_ANA_CONF_REG, RTC_CNTL_PLLA_FORCE_PD_M);
        CLEAR_PERI_REG_MASK(RTC_CNTL_ANA_CONF_REG, RTC_CNTL_PLLA_FORCE_PU_M);

        m_i2s_config.mode             = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
        m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S); // Arduino vers. > 2.0.0
        i2s_driver_install((i2s_port_t)m_i2s_num, &m_i2s_config, 0, NULL);
        m_f_forceMono = false;

        // powerup APLL/PLLA
        SET_PERI_REG_MASK(RTC_CNTL_ANA_CONF_REG, RTC_CNTL_PLLA_FORCE_PU_M);
        CLEAR_PERI_REG_MASK(RTC_CNTL_ANA_CONF_REG, RTC_CNTL_PLLA_FORCE_PD_M);
    }
    i2s_zero_dma_buffer((i2s_port_t) m_i2s_num);

@SanZamoyski
Copy link

After alot of messing I got this (only important things to show idea:

As setup:

    audio.setBufsize(-1, UINT16_MAX * 8);  //didn't help on choppy sound
    loadAudioFile(dirName, fileName);

    int lowCpu = 0;
    unsigned long cpuLowTime = 0;
    unsigned long cpuHighTime = 0;
    unsigned long cpuTimeStart = millis();

    int bufFill = 0;
    int bufSize = /*audio.inBufferSize();*/ UINT16_MAX * 8;
    int queueCmd = 0;

"loop" part:

//if(audioloaded){
            audio.loop();
            bufFill = audio.inBufferFilled()*100/bufSize;
        //}
        //loopTimes[0][loopRow++] = millis() - loopStart;

        if(audioLowBuf > bufFill){
            audioLowBuf = bufFill;
        } 
        
        //Serial.println((String)bufFill + "%, " + (String)lowCpu);

        if(bufFill < 95 && lowCpu == 1){
            //Serial.println(">");
            cpuLowTime += millis() - cpuTimeStart;
            cpuTimeStart = millis();
            setCpuFrequencyMhz(240);
            lowCpu = 0;
        }

        if(bufFill > 95 && lowCpu == 0){
            //Serial.println("<");
            cpuHighTime += millis() - cpuTimeStart;
            cpuTimeStart = millis();
            setCpuFrequencyMhz(160);
            lowCpu = 1;
        }

In debug message I get:

17:43:42.770 > [DBG] [BLE] lastNot.: 15 s. ago. Clients�r
17:43:42.775 > �ٹ: 1    [Audio] Buf min.: 0%.   CPU Time Low: 7459, High: 2952, Low ratio: 71%[CPU] freq: 160
17:43:57.839 > [DBG] [BLE] lastNot.: 30 s. ago. Clients: 0. Adv.: 1     [Audio] Buf min.: 94%.  CPU Time Low: 11827, High: 3185, Low ratio: 78%[CPU] freq: 160

Output is corrupted due to often change of cpu freq.

But the conception seems to be corrent, is it?
So we get a little powersave. Few mA should be.
But esp32 hangs from time to time, ble does not work.

Is it possible to split PSRAM memory (buffer) filling and DMA stuff?
I'm thinking that everything works at low CPU while buffer is quite filled and only data is put to DMA memory.
Problem is when data from flac is decoded to PSRAM.

Is this correct?

Copy link

github-actions bot commented Jun 8, 2024

This issue is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale label Jun 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants