-->
Page 2 of 5

Re: I2S for generating waveforms for ledpixel devices

PostPosted: Sun Aug 30, 2020 4:21 am
by eriksl
davydnorris wrote:Also, put up your questions about the registers - let's try to get some answers!!

Ok, hold on, this is what I got! (EDIT: grmbl, it ate all the whitespace!)

enum
{
DR_REG_I2S_BASE = 0x60000e00, // pretty trivial
};

enum
{
I2STXFIFO = DR_REG_I2S_BASE + 0x0000, // pretty trivial too
I2SRXFIFO = DR_REG_I2S_BASE + 0x0004, // note: no queue usage counter?
};

enum
{
I2SCONF = DR_REG_I2S_BASE + 0x0008,

I2S_BCK_DIV_NUM = 0x0000003f, // clock prescalers and dividers, not too complex, I only don't
I2S_BCK_DIV_NUM_S = 22, // understand how to assign the total scaling to both, there doesn't
// seem to be a general rule, other than some comments
I2S_CLKM_DIV_NUM = 0x0000003f,
I2S_CLKM_DIV_NUM_S = 16,

I2S_BITS_MOD = 0x0000000f, // undocumented; seems to relate to the amount of bit clocks
I2S_BITS_MOD_S = 12, // between a word or channel clock toggle?

I2S_RECE_MSB_SHIFT = 1 << 11, // ?
I2S_TRANS_MSB_SHIFT = 1 << 10, // ?
I2S_I2S_RX_START = 1 << 9, // clear
I2S_I2S_TX_START = 1 << 8, // clear
I2S_MSB_RIGHT = 1 << 7, // ? (although I do have a hunch...)
I2S_RIGHT_FIRST = 1 << 6, // ? similar
I2S_RECE_SLAVE_MOD = 1 << 5, // ?
I2S_TRANS_SLAVE_MOD = 1 << 4, // ?
I2S_I2S_RX_FIFO_RESET = 1 << 3, // clear
I2S_I2S_TX_FIFO_RESET = 1 << 2, // clear
I2S_I2S_RX_RESET = 1 << 1, // clear
I2S_I2S_TX_RESET = 1 << 0, // clear
};

enum
{
I2SINT_RAW = DR_REG_I2S_BASE + 0x000c, // all interrupts three times
I2S_I2S_TX_REMPTY_INT_RAW = 1 << 5, // I am not sure what the "raw" section does
I2S_I2S_TX_WFULL_INT_RAW = 1 << 4, (compared to the "enable" and "status" sections)
I2S_I2S_RX_REMPTY_INT_RAW = 1 << 3,
I2S_I2S_RX_WFULL_INT_RAW = 1 << 2,
I2S_I2S_TX_PUT_DATA_INT_RAW = 1 << 1,
I2S_I2S_RX_TAKE_DATA_INT_RAW = 1 << 0
};

enum
{
I2SINT_ST = DR_REG_I2S_BASE + 0x0010,
I2S_I2S_TX_REMPTY_INT_ST = 1 << 5, // transmit fifo empty
I2S_I2S_TX_WFULL_INT_ST = 1 << 4, // transmit fifo full
I2S_I2S_RX_REMPTY_INT_ST = 1 << 3, // receive fifo empty
I2S_I2S_RX_WFULL_INT_ST = 1 << 2, // receive fifo full
I2S_I2S_TX_PUT_DATA_INT_ST = 1 << 1, // one sample (word) stored in the fifo?
I2S_I2S_RX_TAKE_DATA_INT_ST = 1 << 0 // one sample (word) fetched from the fifo?
}; // these last two are not really useful I think?

enum
{
I2SINT_ENA = DR_REG_I2S_BASE + 0x0014, // same
I2S_I2S_TX_REMPTY_INT_ENA = 1 << 5,
I2S_I2S_TX_WFULL_INT_ENA = 1 << 4,
I2S_I2S_RX_REMPTY_INT_ENA = 1 << 3,
I2S_I2S_RX_WFULL_INT_ENA = 1 << 2,
I2S_I2S_TX_PUT_DATA_INT_ENA = 1 << 1,
I2S_I2S_RX_TAKE_DATA_INT_ENA = 1 << 0
};

enum
{
I2SINT_CLR = DR_REG_I2S_BASE + 0x0018, // same
I2S_I2S_TX_REMPTY_INT_CLR = 1 << 5,
I2S_I2S_TX_WFULL_INT_CLR = 1 << 4,
I2S_I2S_RX_REMPTY_INT_CLR = 1 << 3,
I2S_I2S_RX_WFULL_INT_CLR = 1 << 2,
I2S_I2S_PUT_DATA_INT_CLR = 1 << 1,
I2S_I2S_TAKE_DATA_INT_CLR = 1 << 0
};

enum
{
I2STIMING = DR_REG_I2S_BASE + 0x001c,
I2S_TRANS_BCK_IN_INV = 1 << 22, // inverted bit clock?
I2S_RECE_DSYNC_SW = 1 << 21, // no clue
I2S_TRANS_DSYNC_SW = 1 << 20, // no clue
I2S_RECE_BCK_OUT_DELAY = 0x00000003, // receiver bit clock output delay
I2S_RECE_BCK_OUT_DELAY_S = 18,
I2S_RECE_WS_OUT_DELAY = 0x00000003, // receiver word clock output delay
I2S_RECE_WS_OUT_DELAY_S = 16,
I2S_TRANS_SD_OUT_DELAY = 0x00000003, // transmitter sample clock output delay
I2S_TRANS_SD_OUT_DELAY_S = 14,
I2S_TRANS_WS_OUT_DELAY = 0x00000003, // transmitter work clock output delay
I2S_TRANS_WS_OUT_DELAY_S = 12,
I2S_TRANS_BCK_OUT_DELAY = 0x00000003, // transmitter bit clock output delay
I2S_TRANS_BCK_OUT_DELAY_S = 10,
I2S_RECE_SD_IN_DELAY = 0x00000003, // receiver sample clock input delay
I2S_RECE_SD_IN_DELAY_S = 8,
I2S_RECE_WS_IN_DELAY = 0x00000003, // receiver word clock input delay
I2S_RECE_WS_IN_DELAY_S = 6,
I2S_RECE_BCK_IN_DELAY = 0x00000003, // receiver bit clock input delay
I2S_RECE_BCK_IN_DELAY_S = 4,
I2S_TRANS_WS_IN_DELAY = 0x00000003, // transmitter work clock input delay
I2S_TRANS_WS_IN_DELAY_S = 2,
I2S_TRANS_BCK_IN_DELAY = 0x00000003, // receiver bit clock input delay
I2S_TRANS_BCK_IN_DELAY_S = 0
};

enum
{
I2S_FIFO_CONF = DR_REG_I2S_BASE + 0x0020,
I2S_I2S_RX_FIFO_MOD = 0x00000007, // receiver sample layout
I2S_I2S_RX_FIFO_MOD_S = 16,
I2S_I2S_TX_FIFO_MOD = 0x00000007, // transmitter sample layout
I2S_I2S_TX_FIFO_MOD_S = 13,
I2S_I2S_DSCR_EN = 1 << 12, // select DMA (SLC) or FIFO (PIO) mode
I2S_I2S_TX_DATA_NUM = 0x0000003f, // no clue, amount of bits per sample like 0-16? this is not the fifo length counter
I2S_I2S_TX_DATA_NUM_S = 6,
I2S_I2S_RX_DATA_NUM = 0x0000003f, // no clue, amount of bits per sample like 0-16? this is not the fifo length counter
I2S_I2S_RX_DATA_NUM_S = 0
};

enum
{
I2SRXEOF_NUM = DR_REG_I2S_BASE + 0x0024, // this has something to do with SLC/DMA
I2S_I2S_RX_EOF_NUM = 0xffffffff, // possibly the amount of times the DMA pointer wrapped around
I2S_I2S_RX_EOF_NUM_S = 0 // due to lack of timely input (either side)
};

enum
{
I2SCONF_SIGLE_DATA = DR_REG_I2S_BASE + 0x0028, // magic!
I2S_I2S_SIGLE_DATA = 0xffffffff,
I2S_I2S_SIGLE_DATA_S = 0
};

enum
{
I2SCONF_CHAN = DR_REG_I2S_BASE + 0x002c,
I2S_RX_CHAN_MOD = 0x00000003, // more data layout options
I2S_RX_CHAN_MOD_S = 3,
I2S_TX_CHAN_MOD = 0x00000007, // more data layout options
I2S_TX_CHAN_MOD_S = 0
};

Re: I2S for generating waveforms for ledpixel devices

PostPosted: Sun Aug 30, 2020 4:23 am
by eriksl
I also found some source code to be used to turn on the module clock, instead of relying on the blob in ROM. Not that it makes the function very much clearer...

If I'd know what happens here (like register layouts...) I could create some code that would be so much more clear!

Be aware! Magic and Dragons be here!

Code: Select allstatic unsigned int get_peri_reg_bits(unsigned int reg, unsigned int hipos, unsigned int lowpos)
{
    return((read_peri_reg(reg) >> lowpos) & ((1 << (hipos - lowpos + 1)) - 1));
}

static unsigned int espressif_opaque_i2c_readReg(unsigned int block, unsigned int host_id, unsigned int reg_add)
{
    uint32_t mst_ctrl_addr = 0x60000d00 + (host_id * 4);

    write_peri_reg(mst_ctrl_addr, (0 << 24) | (0 << 16) | (reg_add << 8) | block);
    while(get_peri_reg_bits(mst_ctrl_addr, 25, 25) != 0);
    return((read_peri_reg(mst_ctrl_addr) >> 16) & 0xff);
}

static void espressif_opaque_i2c_writeReg(unsigned int block, unsigned int host_id, unsigned int reg_add, unsigned int pData)
{
    uint32_t mst_ctrl_addr = 0x60000d00 + (host_id * 4);

    write_peri_reg(mst_ctrl_addr, (1 << 24) | (pData << 16) | (reg_add << 8) | block);
    while(get_peri_reg_bits(mst_ctrl_addr, 25, 25) != 0);
}

static void espressif_opaque_i2c_writeReg_Mask(unsigned int block, unsigned int host_id, unsigned int reg_add, unsigned int msb, unsigned int lsb, unsigned int indata)
{
    unsigned int current = espressif_opaque_i2c_readReg(block, host_id, reg_add);

    espressif_opaque_i2c_writeReg(block, host_id, reg_add, (current & (~(((1 << (msb - lsb + 1)) - 1) << lsb))) | (indata << lsb));
}

static void espressif_opaque_start_i2s_clock(void)
{
    espressif_opaque_i2c_writeReg_Mask(0x67, 0x04, 0x04, 0x07, 0x07, 0x01);
}

Re: I2S for generating waveforms for ledpixel devices

PostPosted: Sun Aug 30, 2020 4:30 am
by eriksl
And this is the code for enabling I2S interrupts (without crashing)

Code: Select all    static const unsigned int all_spi_slave_ints =
            SPI_TRANS_DONE_EN |
            SPI_SLV_WR_STA_DONE_EN |
            SPI_SLV_RD_STA_DONE_EN |
            SPI_SLV_WR_BUF_DONE_EN |
            SPI_SLV_RD_BUF_DONE_EN |
            SPI_TRANS_DONE |
            SPI_SLV_WR_STA_DONE |
            SPI_SLV_RD_STA_DONE |
            SPI_SLV_WR_BUF_DONE |
            SPI_SLV_RD_BUF_DONE;

    clear_peri_reg_mask(SPI_SLAVE(0), all_spi_slave_ints);  // I2S interrupt is shared with SPI (flash) SLAVE mode interrupts, make sure we don't get them
    ets_isr_attach(ETS_SPI_INUM, i2s_callback, (void *)0);
    ets_isr_unmask(1 << ETS_SPI_INUM);

Re: I2S for generating waveforms for ledpixel devices

PostPosted: Sun Aug 30, 2020 9:40 am
by davydnorris
eriksl wrote:
davydnorris wrote:Awesome work!! I had seen the FIFO documentation but, as you say, it's completely useless.

I use I2S for audio input so I have to use DMA - I have a pair of I2S mics that I'm driving at 48kHz, 24 bit resolution and I sum the inputs to increase SNR, then run the results through some biquad filters to produce A, C or Z weighted output from the raw mic inputs. I then use that to calculate slow and fast time weighted dB readings as well as raw Leq values binned in 1 sec increments.

I do all of this in fixed point, including the sqrt and log10 at the end when I actually want to use the values.

Even more awesome! And the ESP8266 is simply fast enough to cope, very cool!

How do you get the data out of the chip, over wifi? Is it steady enough to not need significant buffering?


I don't stream the signal - I don't think it would work. I've been asked if we could take 30sec sound bites but I don't like the implications of listening in - it's easier to just say no it can't be done. If I was going to stream the signal, I think I would have to ditch all the signal processing except the initial summing, and I would use WAVPACK to compress the stream on the fly and then do the digital correction at the other end

WAVPACK is a tiny compression library suitable for audio and embedded systems but even then it's a big ask.

Right now, I use MQTT to send the dB(A) or dB(C) slow and fast SPL values whenever the sensor is programmed to, and I send Leq(A) values for windows of time. It seems to be the norm for SPL meters to use what they call short Leq, which is raw noise integrated over 0.125 sec.

I can easily do this, however almost all the noise measurements my clients want are 1min, 5min or 15min and so it makes no sense to calculate and store all those short Leq values when you're only going to combine them in much larger intervals. I calculate them in 1 sec buckets, and store them as an array of 60, 300 or 900 values, which can be moved into flash if needed. I can then do things like report Leq(A)-15min every minute by using a sliding window and a circular buffer.