-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
stm32/machine_i2s: Add support for I2S on H7 MCUs (WIP) #8270
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
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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.
ports/stm32/machine_i2s.c
Outdated
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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
ports/stm32/machine_i2s.c
Outdated
@@ -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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
- transform for 32-bit sample TX:
micropython/ports/stm32/machine_i2s.c
Line 246 in 2ea21ab
STATIC void reformat_32_bit_samples(int32_t *sample, uint32_t num_samples) { - "cherry-picking" transform for RX:
micropython/ports/stm32/machine_i2s.c
Line 308 in 2ea21ab
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
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. |
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>
Better alphablend features
This is an automated heads-up that we've just merged a Pull Request See #13763 A search suggests this PR might apply the STATIC macro to some C code. If it Although this is an automated message, feel free to @-reply to me directly if |
Master TX is tested, in blocking and non-blocking IO mode.
To do:
(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.)