Skip to main content
Explorer
December 29, 2024
Solved

I2S weird bit shift problem

  • December 29, 2024
  • 6 replies
  • 4488 views

Hi,

I'm getting a problem in my project, where I tried to create a loopback between I2S3 and I2S4 on STM32F411 Disco.
It seems that only the least significant bit of 16-bit packets is somehow shifted to the next packet. The rest of the transmission is intact.
For example - I send {0x0001 0x0000 0x0000 0x0000} and I get {0x0000 0x0000 0x0001 0x0000}, but if I send {0xFFFE 0x0000 0x0000 0x0000}, I will receive {0x0000 0x0000 0x0000 0xFFFE}.
Packets order reversal is expected, but during analysis pay attention what happens with the least significant bit. Another example: transmitting {0x8001 0x0000 0x0000 0x0000} will end up with receiving {0x0000 0x0000 0x0001 0x8000} (I'd expect {0x0000 0x0000 0x000 0x8001}).
What is also interesting, readouts from logic analyzer seem to be correct.

I am not using HAL, I am configuring registers manually. I2S4 is configured as master receiver and uses DMA. I2S3 is configured as slave transmitter and I tried "feeding" it both with polling and interrupts. I always fill transmitter's buffer before initializing receiver with the first packet.

Since I've written my own abstraction layer, attaching code here may be difficult. Therefore I am uploading the link to repo: https://github.com/baator025/I2S_loopback . The most recent branch is rtos_integration.

I'd really appreciate help, because I've run out of ideas long ago.

Thanks,
Bartosz

    This topic has been closed for replies.
    Best answer by waclawek.jan

    Looking at your pin initialization code, you don't set OSPEEDR for the bit clock. Your problem thus may be the Corrupted last bit of data and/or CRC received in Master mode with delayed SCK feedback erratum. Try setting OSPEEDR to higher than the default lowest state.

    JW

    6 replies

    Super User
    December 30, 2024

    Observe the I2S signals using oscilloscope and/or LA. You may have inadvertently generated edges which act as clock during initialization. Mind the In I2S slave mode, WS level must be set by the external master
    when enabling the I2S erratum.

    JW

    baator025Author
    Explorer
    December 30, 2024

    Thanks for the reply.

    I already struggled with some edges during initialization - whole transmission was shifted by 3 bits. If I remember correctly I applied your suggestion about clock value from someone else's topic.

    There are still indeed some extra edges in the beginning, but I suppose they are somehow discarded by STM - WS line does not go high during their period and after that, there are exactly 16 clock cycles during first '1'. I think that it would also cause shift of all of data - not only the least significant bits by a packet size. 

    Either way, I attached some LA captures below. To make it readable, I cropped two last packets - there were zeroes only, so nothing unexpected happened.

    Data sent: 0x8001 0x0000 0x0000 0x000 0x8001 0x0000 0x0000 0x00000x8001 0x0000 0x0000 0x0000

    Data sent: 0x0001 0x0000 0x0000 0x000 0x0001 0x0000 0x0000 0x00000x0001 0x0000 0x0000 0x0000

    Data sent: 0xFFFE 0x0000 0x0000 0x000 

    0xFFFE 0x0000 0x0000 0x00000xFFFE 0x0000 0x0000 0x0000

     

    Regarding WS - What did you mean about setting WS by master? Could I forget something about I2S master if WS seems to behave properly on captures above?

     

    Thanks,
    Bartosz

    Super User
    December 31, 2024

    I am referring to the errata.

    Make sure you are enabling the slave according to the rule outlined there.

    JW

     

    baator025Author
    Explorer
    December 31, 2024

    I got to errata, it makes sense to work with this rule. If I understand correctly, since I am using MSB justified transmission, I should make sure the WS line should be low. 
    One thing that I'm not sure how to do - WS pins both in master and slave are switched to alternate mode in MODER register, so I'm not able to use it as output. How can I make it go to low state? 

    Visitor II
    December 31, 2024

    "hmmm" - I want to make you happy at the last day of the year - LOL

    Yes, discard the first word pairs. Let the I2S (both sides) run "forever".

    Please, can "we" do this:

    • you configure master and slave (with same protocol, in a regular way)
    • also all signals via ALT, on both
    • on the master receiver side: it should provide BCK and SCLK (WS) as output,
      on the transmitter slave side - it should have BCK and SCLK (WS) as inputs
    • can you let run the master receiver side - generating the I2S signals (actually a bit strange config: see below)
      and check with scope or logic analyzer that you see a correct endless transmission of I2S bus cycles?
      (BCLK is continuous, SCLK toggles every 16 clocks and is continuous)
    • now connect BCK out and SCLK out from master receiver with transmitter slave (where these signals should be inputs)
    • If you leave the DATA signal open on master receiver - it might read all as 0 or 1:
      if you ground now on master receiver the DATA pin to GND or pull-up to VDD - you should see a steady 0 or 1
    • At the end you connect DATA out (on slave transmitter) to DATA in (on master receiver)

    Personally, I would change the "logic" a bit:

    • configure a Master Transmitter - all signals out (also BCK, SCLK)
    • and configure a Slave Receiver - all signals in (also BCK, SCLK)

    Let the Master Transmitter run endlessly (after first enable and start - let it run forever), also the Slave Receiver.

    Now check the I2S signals, if they make sense (BCK, LRCK, every 16 BCK clocks the LRCK changes polarity).

    Try to send another set of words (2x 16bit, for each channel): do they come out as expected?

    Connect Tx master DATA with Rx slave DATA: now you should get what you have sent (plus BCK and SCLK from Master to Slave).

    But: make sure to load the I2S FIFO (data register) just when the previous transmission has completed (check the status of the transmitter, do not overwrite the words to send when it is still in progress).
    Maybe a "flow control" on transmitter is missing (to make sure just to load a new pair of words when the previous
    shift out has completed).

    It might be easier to have a Master Transmitter (with all out) and a Slave Receiver (with all in) - even it should work as well as a Master Receiver (which provides not BCK and SCLK for the Slave Transmitter, just a bit "strange").

    Super User
    December 31, 2024

    The short BCLK and also the unusually short WS pulses at the beginning are concerning, too.

    JW

    Visitor II
    December 31, 2024

    What I see:
    OK, it is not an I2S signal, potentially a Left or Right Adjusted signal (OK, as long as both sides use the same protocol), with 16bit per channel.

    Yes, the first short WS (LRCK) pulse can confuse the transmitter:
    based on your description: master receiver and slave transmitter. It means: the receiver generates the BCK and LRCK signal, the transmitter will receive it (in order to send data out).
    But if you load transmitter first but you start receiver afterwards - there can be a phase where the receiver master starts to generate the output signals (BCK and LRCK). These can be unreliable on the very first start - what we see in the waveform.

    BCK and SCLK are continuous clocks afterwards.

    I would ignore the very first cycle, instead check it at the 2nd or any following cycles (to make sure the signals are "stable").

    "Packet reversal": if words are sent as 16bit (and they are little endian), the bytes are "flipped" in the memory, but on the I2S bus it should be MSB...LSB (MSB first out and in).
    If Tx and Rx use the same protocol - you should receive what you send, didn't you?

    I would send: 0x0000 0x0000 0xFFFE 0x5551 and check for the 2nd word pairs (ignore the first two words, they can be wrong on the Rx side, e.g. due to this too short LRCK signal on first cycle).

    Never stop the Master Receiver: otherwise you will get all the time this "hick-up", when the signals start again.
    I2S is an always going on synchronous signal, the BCK and LRCK are always toggling. If you do not have anything to send, the data words sent should be 0.

    You "cannot" use I2S in "Start&Stop" mode (first cycle is potentially always "wrong").

    baator025Author
    Explorer
    December 31, 2024

    Thanks for involving in this topic. If I understood you correctly, you suggested discarding first packets. Unfortunately, it seems that the following packets are shifted in the same manner.
    I also enlarged the length of transmission to 16 words to see if the transmission stabilizes later on. It did not help as well - data also got shifted in following packets.

    baator025Author
    Explorer
    January 1, 2025

    Btw, here is the start of transmission, where I made sure WS is pulled down low when I start both Tx and Rx. 

    pull_down_wave.png

    Visitor II
    January 1, 2025

    WS with pull-down does not fix, does not do anything (actively driven signals).

    Your "problem" is: when all starts, the very first cycle is not correct (some BCK clocks but WS/LRCK changes - sure: now it goes to the 1st correct cycle).
    This is due to the fact (for my understanding):

    • the output signals BCK and LRCK/WS are generated by the Master Receiver
    • so, you start Slave Transmitter first (which will wait for these signals, but never mind how long you wait: as long as the Master Receiver does not start - no signals)
    • afterwards you initialize and start Master Receiver (now starting to generate the signals)
    • and starting the Master Receiver starts with "strange" cycle, but Slave Transmitter is hot and sees this "strange" cycle

    This creates a "kick-up" (incorrect first cycle). Your Slave Transmitter is confused and therefore the Master Receiver gets unpredictable Rx data.


    What happens on the following cycles? Do not do all the time Start and Stop (let both run forever after enabled).

    Have you tried to let both run forever and you pull-up or pull-down the DATA Rx input? What if you connect DATA Rx with LRCK/WS signal? (do you see 0xFFFF vs. 0x0000 in the two channels?)

    Visitor II
    January 1, 2025

    I think, your issue is due to the very first cycle is an "unreliable" (unpredictable) cycle.
    Even, you would change to a slightly different start sequence, e.g.:

    • enable Master Receiver first (which generates the "reference" signals, BCK, LRCK/WS) - but let it run forever
    • enable Slave Transmitter (which uses the generated signals) - and let it also run forever
    • and start sending something at least one I2S cycle later

    it is still a potential "hick-up": the Slave Transmitter might start anywhere, to see the generated signals at any time (the start of both instances are not in sync). So, the started Slave Transmitter might not see a complete valid I2S cycle, a bit too short, on the very first iteration.

    So, you have to wait a bit after Master Receiver was enabled (at least one complete I2S cycle), enable Slave Transmitter and wait also at least one complete I2C cycle). Now send something, but not immediately (Slave Transmitter might not be in sync yet with the I2S cycles).

    All it just stable after one fully completed I2S cycle (a second I2S cycle needed to be completed). Send something a while after both I2S instances are launched and running in a stable state (on I2S cycles). You have to discard the results of the very first I2S cycle (which is potentially not a correct valid complete cycle).

    Send something later and have the receiver hot (waiting for something received). This should match now. Potentially, you have to discard the result of the very first iteration.

    Super User
    January 2, 2025

    Looking at your pin initialization code, you don't set OSPEEDR for the bit clock. Your problem thus may be the Corrupted last bit of data and/or CRC received in Master mode with delayed SCK feedback erratum. Try setting OSPEEDR to higher than the default lowest state.

    JW

    baator025Author
    Explorer
    January 2, 2025

    Thanks, that did it! No more bit shift.

    First I noticed that my implementation used APB1 for first I2S channel and APB2 for second one. There would be nothing wrong with that aside from that I set two different prescalers. After I fixed it, I noticed improvement, but some of the bits were still shifted.
    After that I configured OSPEEDR and it seems that it fixed the problem.

    Thanks again for the help. I'll remember to look for erratas, when I start working with a new MCU.

    Visitor II
    January 3, 2025

    Cool, great - well done!

    Super User
    January 3, 2025

    > I assume: PDM works only with a SAI interface (configured for PDM).

    Why?

    PDM is just a clocked stream of bits. SPI is just a clocked stream of bits, either; and so is basically I2S (that's why it's one interface). You need to filter it, but SAI won't do that either; and while HW filter is more comfortable/efficient, SW is viable in many cases, too.

    SAI which supports PDM has one advantage, though, it supports stereo PDM in the both-edges mode. But that can be worked around with SPI/I2S, one timer, one extra pin, and a bit of thinking, too. And not all SAI support that mode, and not all STM32 have SAI at all.

    JW

    Visitor II
    January 3, 2025

    You are right, sorry: yes, with SPI as I2S it should be possible to connect a PDM MIC (but just in mono mode):

    chrome-extension://efaihttps://www.st.com/resource/en/application_note/an5027-interfacing-pdm-digital-microphones-using-stm32-mcus-and-mpus-stmicroelectronics.pdf 

    Just to bear in mind: the I2S clock for a PDM MIC is way faster as for a regular PCM I2S. And this frequency has to be configured properly in relation to the decimation factor used.

    Visitor II
    January 3, 2025