Integrating I2S Functionality on openMV CAM RT1062 and H7 Pro

Hi everyone,

I’m currently evaluating the openMV CAM RT1062 and the openMV H7 Pro for a project and am impressed with both the product quality and the support system. Our decision between the two models hinges on the ease of enabling I2S communication.

For the RT1062, I’ve found existing I2S drivers that suggest DMA and non-DMA options:

  • src/hal/mimxrt/MIMXRT1062/drivers/fsl_flexio_i2s_edma.c (DMA, less CPU-intensive but more complex coding)
  • src/hal/mimxrt/MIMXRT1062/drivers/fsl_flexio_i2s.c (non-DMA, easier to code but more CPU-intensive)

However, integrating these drivers within the MicroPython environment and connecting them to the higher-level system components is not straightforward to me, especially when looking at:

  • src/micropython/ports/mimxrt/machine_i2s.c
  • src/micropython/extmod/machine_i2s.c

My goal is to enable both DMA and non-DMA I2S functionalities on the RT1062, akin to how I2S is implemented on a Teensy 4.1 (as documented here). Despite the RT1062 lacking dedicated I2S pins, we have a workaround for pin routing that we’re eager to test and provide feedback on.

With this context, I have several questions to better understand how to proceed:

  1. Firmware Compilation for I2S Drivers: How can I integrate and compile the existing I2S drivers (either the RT1062’s or any suitable for the H7) into the firmware? Is there a specific process or tools recommended for incorporating these drivers?
  2. Pin Multiplexing (MUX) Configuration: What steps are required to configure the chip’s MUX settings for I2S functionality? Are there particular files or sections within the project where these settings should be defined?
  3. Architecture Layers for I2S Integration: Could you clarify the necessary layers for integrating I2S? Specifically, is it sufficient to have one C/C++ driver alongside a Python wrapper, or are additional layers needed?
  4. Inclusion of machine_i2s.c: If a specific file like machine_i2s.c is essential for I2S functionality, how should it be included and adapted for our use case? Are there examples or guidelines on modifying such a file to work with our drivers?
  5. Python Wrapper Development: Lastly, where and how should the Python wrapper be created to make I2S functionalities accessible through MicroPython’s machine module? Are there particular conventions or best practices for developing this wrapper?

Thank you in advance for your guidance and support. I look forward to contributing to the community based on our findings and experience.

Best regards, Casper

Hi Casper,

The questions you are asking imply you have a professional development product that you need help with. For this, you should email us directly via the company email, and we can provide direct support for your project via consulting.

Otherwise, the instructions for building the firmware are here: openmv/src at master · openmv/openmv (github.com)

Navigating how to do things in the code is not part of our general purpose support as it’s not a feature exposed by the product. If you want to do this you’ll need to dive in and learn how to do things yourself.

Hi!

Will dive right in and try to make it work! We want to contribute the the community and make pull requests if it get it to work.

Will do some more research, and ask more specific questions, when the structure of the code becomes clearer for me.

Best, Casper

Hi!

After a deep dive, we got I2S to output data on the openMV CAM RT1062. We have not yet succeeded with the H7 pro, we have problems getting the driver to compile.

We found that by changing the muxes we could repurpose the JTAG connection as I2S output.

We need to use synchronous mode, and route some RX pins as TX pins.

As you implied all the drivers where already implemented! Thank you :slight_smile:

In file: src/micropython/ports/mimxrt/machine_i2s.c

    // if ((self->mode == TX) && (i2s_gpio_map[sck_index].mode == TX)) {
    //     saiConfig.syncMode = kSAI_ModeAsync;
    //     SAI_TxSetConfig(self->i2s_inst, &saiConfig);
    // } else if ((self->mode == RX) && (i2s_gpio_map[sck_index].mode == RX)) {
    //     saiConfig.syncMode = kSAI_ModeAsync;
    //     SAI_RxSetConfig(self->i2s_inst, &saiConfig);
    // } else if ((self->mode == TX) && (i2s_gpio_map[sck_index].mode == RX)) {
    // Workaround to get audio out from JTAG pins
    saiConfig.syncMode = kSAI_ModeAsync;
    SAI_RxSetConfig(self->i2s_inst, &saiConfig);
    saiConfig.bitClock.bclkSrcSwap = true;
    saiConfig.syncMode = kSAI_ModeSync;
    SAI_TxSetConfig(self->i2s_inst, &saiConfig);
    // } else if ((self->mode == RX) && (i2s_gpio_map[sck_index].mode == TX)) {
    //     saiConfig.syncMode = kSAI_ModeAsync;
    //     SAI_TxSetConfig(self->i2s_inst, &saiConfig);
    //     saiConfig.syncMode = kSAI_ModeSync;
    //     SAI_RxSetConfig(self->i2s_inst, &saiConfig);
    // } else {
    //     return false; // should never happen
    // }

In: src/micropython/ports/mimxrt/boards/OPENMV_RT1060/mpconfigboard.h


#define MICROPY_PY_MACHINE_I2S (1)
#define MICROPY_HW_I2S_NUM (2)
#define I2S_CLOCK_MUX { 0, 0, kCLOCK_Sai2Mux }
#define I2S_CLOCK_PRE_DIV { 0, 0, kCLOCK_Sai2PreDiv }
#define I2S_CLOCK_DIV { 0, 0, kCLOCK_Sai2Div }
#define I2S_IOMUXC_GPR_MODE { 0, 0, kIOMUXC_GPR_SAI2MClkOutputDir }
#define I2S_DMA_REQ_SRC_RX { 0, 0, kDmaRequestMuxSai2Rx }
#define I2S_DMA_REQ_SRC_TX { 0, 0, kDmaRequestMuxSai2Tx }
#define I2S_AUDIO_PLL_CLOCK (2U)

#define I2S_GPIO(_hwid, _fn, _mode, _pin, _iomux) \
    { \
        .hw_id = _hwid, \
        .fn = _fn, \
        .mode = _mode, \
        .name = MP_QSTR_##_pin, \
        .iomux = {_iomux}, \
    }

/*
See src/hal/MIMXRT1062/drivers/fsl_iomuxc.h for mapping:
#define IOMUXC_GPIO_AD_B0_09_SAI2_TX_DATA 0x401F80E0U, 0x3U, 0, 0, 0x401F82D0U
#define IOMUXC_GPIO_AD_B0_07_SAI2_RX_SYNC 0x401F80D8U, 0x3U, 0x401F85BCU, 0x1U, 0x401F82C8U
#define IOMUXC_GPIO_AD_B0_06_SAI2_RX_BCLK 0x401F80D4U, 0x3U, 0x401F85B4U, 0x1U, 0x401F82C4U
#define IOMUXC_GPIO_AD_B0_10_SAI2_MCLK 0x401F80E4U, 0x3U, 0x401F85B0U, 0x1U, 0x401F82D4U
*/
#define I2S_GPIO_MAP \
    { \
        I2S_GPIO(2, MCK, TX, GPIO_AD_B0_10, IOMUXC_GPIO_AD_B0_10_SAI2_MCLK), \
        I2S_GPIO(2, SCK, TX, GPIO_AD_B0_06, IOMUXC_GPIO_AD_B0_06_SAI2_RX_BCLK), \
        I2S_GPIO(2, WS, TX, GPIO_AD_B0_07, IOMUXC_GPIO_AD_B0_07_SAI2_RX_SYNC), \
        I2S_GPIO(2, SD, TX, GPIO_AD_B0_09, IOMUXC_GPIO_AD_B0_09_SAI2_TX_DATA), \
    }

We will try to connect it to a I2S amplifier next, but the signal looks good on the oscilloscope. As we understood, the JTAG is not activated:


We could also not find any code that set the muxes on the JTAG pins.

We wonder, if we are missing something that is obvious, or if you have any feedback?

Once again, your system is awesome, and the code is extremely well written!

Best, Casper

Hi Casper,

JTAG is controlled by the debugger. You don’t need to set Muxes for it as the debugger pushes a bit pattern into the chip which takes over those I/Os.

That said, if you turn I2S on and try to attach the JTAG afterwards that might not work. So, just know that the jtag pins are probably not usable anymore after you re-purpose them.

Glad to hear you were able to modify the code successfully.