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

stm32/machine_i2s: Add support for I2S on H7 MCUs (WIP) #8270

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

dpgeorge
Copy link
Member

@dpgeorge dpgeorge commented Feb 7, 2022

Master TX is tested, in blocking and non-blocking IO mode.

To do:

  • test master RX mode
  • test asyncio mode

(Note: this supports I2S using the SPI peripheral. More sophisticated I2S support can be achieved using the SAI peripheral, but that's much more involved.)

Copy link
Sponsor Contributor

@miketeachman miketeachman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great that I2S is getting ported to another STM32 processor! I have a few comments and questions about the proposed changes.

const pin_af_obj_t *pin_af = pin_find_af(self->sd, AF_FN_I2S, self->i2s_id);
if (pin_af != NULL) {
if ((pin_af->type == AF_PIN_TYPE_I2S_SDI
&& (self->mode == I2S_MODE_MASTER_RX || self->mode == I2S_MODE_SLAVE_RX))
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of the slave mode macros, I2S_MODE_SLAVE_RX, I2S_MODE_SLAVE_TX?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have another patch for preliminary I2S slave support on stm32, and this is needed for that to work. I should put the slave PR up before this one...

if (!(pin_af != NULL && (
pin_af->type == AF_PIN_TYPE_I2S_SD
#if defined(STM32H7)
|| pin_af->type == AF_PIN_TYPE_I2S_SDO || pin_af->type == AF_PIN_TYPE_I2S_SDI
Copy link
Sponsor Contributor

@miketeachman miketeachman Feb 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can SDO and SDI pins work in either direction? if not, it would be good to validate the SDO and SDI versus RX and TX mode.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, SDO and SDI can be swapped to work for either in or out.

@@ -566,6 +565,21 @@ STATIC bool i2s_init(machine_i2s_obj_t *self) {
const pin_af_obj_t *af = pin_find_af(self->sd, AF_FN_I2S, self->i2s_id);
GPIO_InitStructure.Alternate = (uint8_t)af->idx;
HAL_GPIO_Init(self->sd->gpio, &GPIO_InitStructure);

#if defined(STM32H7)
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any other way to code this besides putting a processor macro into the file. e.g use a feature support macro in mpconfigboard.h files? Sometimes I find that processor macros seem to grow in micropython files, which tend to undermine code comprehension. Not a big deal, and more of a question on my part to learn the best practice to follow in micropython.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree these things grow, but it's hard to make it clean. The HAL is supposed to help there, and it does to some extent.

What's going on here is that the H7 (and maybe G4?) support SDI and SDO pins, and they can be swapped. So instead of #if defined(STM32H7) it could be #if MCU_SUPPORTS_SDI_SDO (probably use a better name than that). Does that sound reasonable?

Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for explaining the issue. Maybe just leave it as-is, and then consider refactoring if other processor variant(s) drive the use of additional macro logic?

@@ -790,7 +811,9 @@ STATIC void machine_i2s_init_helper(machine_i2s_obj_t *self, size_t n_pos_args,
init->MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
init->AudioFreq = args[ARG_rate].u_int;
init->CPOL = I2S_CPOL_LOW;
#if !defined(STM32H7)
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar comment as above about processor macro vs feature macro

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The H7 HAL just doesn't have this entry in the Init struct. So somehow need to deal with a HAL that is not 100% cross-MCU compatible.

@@ -584,13 +584,19 @@ STATIC bool i2s_init(machine_i2s_obj_t *self) {

if (HAL_I2S_Init(&self->hi2s) == HAL_OK) {
// Reset and initialize Tx and Rx DMA channels
uint32_t pm_size;
if (self->hi2s.Init.DataFormat == I2S_DATAFORMAT_16B) {
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a bug fix or DMA optimization for 32-bit audio?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bug fix! I'm surprised it works on other MCUs without this...

Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you describe how the bug manifests? I assume this bug was discovered during the H7 port. Thanks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using 32-bit RX (which is always the case, it's always 32-bit for incoming data from a mic) the values read by the DMA were always 0. And the DMA filled its buffer twice as fast. This makes sense because the DMA was doing only 16-bit reads from the I2S data register, and two such reads are needed per 32-bit sample.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I'm pretty sure 32-bit TX was partially broken. It did make sound on the speaker I used for testing, but it made "better" sound when I fixed this bug.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right, I think the original code is correct for F4 and F7.

It's on H7 that the I2S data register (which now has separate TXDR and RXDR registers) behaves differently.

h7i2s

Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense to me now.

Here is an implementation idea: An alternative to overriding the dma data width sizes in the dma init struct (eg dma_init_struct_i2s in dma.c) with the new function dma_init_with_size() is to modify the init structs in dma.c.

For example:
const dma_descr_t dma_I2S_2_TX = { DMA1_Stream3, DMA_CHANNEL_0, dma_id_3, &dma_init_struct_i2s };

would change to:
const dma_descr_t dma_I2S_2_TX_16 = { DMA1_Stream3, DMA_CHANNEL_0, dma_id_3, &dma_init_struct_i2s_16 };
and
const dma_descr_t dma_I2S_2_TX_32 = { DMA1_Stream3, DMA_CHANNEL_0, dma_id_3, &dma_init_struct_i2s_32 };

There would be separate init structs for 16-bit vs 32-bit sample sizes. These init structs would have macros for F4/F7/H7/etc to conditionally compile for uniqueness in the processor families, as it does now.

in machine_i2s.c, the code that looks like this:

        // configure DMA streams
        if (self->mode == I2S_MODE_MASTER_RX) {
            self->dma_descr_rx = &dma_I2S_1_RX;
        } else {
            self->dma_descr_tx = &dma_I2S_1_TX;
        }

would change to:

        // configure DMA streams
        if (self->mode == I2S_MODE_MASTER_TX && get_dma_bits(self->mode, self->bits) ==16  ) {
            self->dma_descr_rx = &dma_I2S_1_TX_16;
        } else if  (self->mode == I2S_MODE_MASTER_TX && get_dma_bits(self->mode, self->bits) ==32  ){
            self->dma_descr_tx = &dma_I2S_1_TX_32;
        } else if RX {
            self->dma_descr_rx = &dma_I2S_1_RX_32;    --- always 32 bits
        }

If this works, it has the advantage of keeping all the dma data width information in one place, dma.c -- which might aid in self-documenting the code.

Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another consideration for the H7 are the transformations that happen in machine_i2s.c code:

  1. transform for 32-bit sample TX:
    STATIC void reformat_32_bit_samples(int32_t *sample, uint32_t num_samples) {
  2. "cherry-picking" transform for RX:
    int8_t r_to_a_mapping = i2s_frame_map[f_index][i];

You mentioned that master TX has been tested, so that should eliminate item 1 as a concern. Hopefully the transform in item 2 will work as-is for the H7.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I like your idea about having multiple DMA descriptor structs, for 16 and 32 bit transfers. I'll try that out.

And will test again all combinations of TX, RX and bit size on H7.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the DMA config so it uses separate 16 and 32 bit descriptor structs.

And I tested the byte transformations, they were wrong on the H7, and are now fixed (at least for 32-bit stereo).

@dpgeorge
Copy link
Member Author

See #8451 for passive I2S support. This PR would need to be rebased/reworked on top of that, and the multitest can then be used to test things.

@dpgeorge
Copy link
Member Author

Rebased on #8451. Active and passive TX and RX now work on H7, but only 32-bit stereo has been tested.

Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
In particular, it is called by the constructor if the instance already
exists.  So if the previous instance was deinit'd then it will be deinit'd
a second time.

Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
Master TX is tested, in blocking and non-blocking IO mode.

Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
tannewt pushed a commit to tannewt/circuitpython that referenced this pull request Aug 17, 2023
@projectgus
Copy link
Contributor

This is an automated heads-up that we've just merged a Pull Request
that removes the STATIC macro from MicroPython's C API.

See #13763

A search suggests this PR might apply the STATIC macro to some C code. If it
does, then next time you rebase the PR (or merge from master) then you should
please replace all the STATIC keywords with static.

Although this is an automated message, feel free to @-reply to me directly if
you have any questions about this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants