Open DatanoiseTV opened 1 year ago
There is already input and output I2S support. You can instantiate both (or up to 4 total I2S interfaces, in fact...limited by DMA channels) with the same or different params. I don't think there'd be any savings in terms of compute, RAM, etc. in somehow merging the 2 together (and you'd have 1/2 the PIO FIFO space which may be a problem).
I think the benefit of processing the OSR and ISR (Input and output shift registers) of the PIO at once would be saving at least the PIO SMs. Also, in that case the CB could be used to process both the incoming audio and generating the audio output stream.
I have at least a dozen of use cases (including Vult DSP), which would greatly benefit from this.
From my experience, it's the PIO instruction memory that gets exhausted first, not the SMs (c.f. the cool PIO-USB stuff which uses all 32 insns on a single SM). :) Because there are no nop
s in the instruction stream (https://github.com/earlephilhower/arduino-pico/blob/master/libraries/I2S/src/pio_i2s.pio) you'd need to double the clock and insert nops every other cycle except for the one IN PINS, 1 so it's the same instruction count as an INPUT and OUTPUT I2S.
You would save a couple pins, though, which might be handy.
Also, two DMAs would be required and so you'd have 2 separate IRQs to handle, anyway. There's no bidir DMA option. You might be able to just assume the other one will fire, too, but it's awkward and open-loop at that point which feels unstable.
Another way to achieve a similar outcome would be to implement slave input - using the same BCKL/LRCLK GPIO pins as the output PIO program is setting. The PIO code would have a interrupts on each of these signals.
implement slave input - using the same BCKL/LRCLK GPIO pins
Unfortunately it's not possible to drive a PIO off of an input pin, only the system clocks. So any kind of slave mode is troublesome. I think you've got to run several multiples the frequency of the signal (to ensure you sample at proper setup/hold time). The PIO serial ports have this architecture, but they run at a much lower frequency...at I2S bit frequencies I don't think it's possible.
To get something like this you'd probably need a new I2S input PIO program and start it at exactly the same time as the I2S output clock. They would run in lockstep and you'd be operating in open loop mode, shifting in at the computed times. There are methods of triggering both starts off of an IRQ, too, but in the end your I2S input is really just an open loop shift-in program.
Thanks Earle,
You are absolutely correct for trying to synchronise to an external master clock for outputs. It would be very difficult to meet the I2S timing requirements if the PIO clock wasn't locked to some multiple of BCLK. Even then, just one PIO cycle delay may be too long to meet the BCLK edge to DOUT ready timing requirement (which is around 20nS for several CODECs I use regularly).
However, I think slave inputs are still worth considering - if only to save two GPIO pins on a CODEC setup. I was assuming that I could trigger actions via interrupts off GPIO pins for LRCLK and BCLK. With a master-mode Pico output, the issue of clock drift is avoided.
2.19.3. Interrupts An interrupt can be generated for every GPIO pin in four scenarios: ...
3.2.5. Pin Mapping... Each range can cover any of the GPIOs accessible to a given PIO block (on RP2040 this is the 30 user GPIOs), and the ranges can overlap.
Yes, the PIO clock does need to be greater than BCLK, but a factor of anything more than 4 (and possibly 2) should provide enough accuracy to meet the data setup time requirements, despite clock drift or complete lack of synchronisation. I think a possible issue would likely be that the number of registers available might be exhausted, if an extra counter was needed to position the read near the middle of the bit cycle. Or, maybe just waiting one PIO clock cycle after the BCLK interrupt would be good enough to get a clean bit read.
Is there any update for the RP2350 on this or is the situation the same? I am planing to use a audio codec for DSP (2i / 2o) instead of just a I2S DAC on my current hardware project.
Nope, no change. If you'd like to submit a PR w/a combined version, happy to look at it! But it's a really niche application and not on my radar right now.
Good to know someone else cares.
I haven't taken the approach I described above any further at this stage, I've been distracted by other projects.
I'll get back to it when I next have a call for a full I2S CODEC on a Pico project. Sorry for the indefinite delay at this stage. Someone else may well pick it up in the meantime.
I was wondering if shifting out and shifting in at the same time would not work?
.program pio_i2s_full_duplex
.side_set 2 ; 0 = bclk, 1 = wclk
; The C code should place (number of bits/sample - 2) in Y and
; update the SHIFTCTRL to be 24 or 32 as appropriate
mov x, y side 0b00
left_loop:
in pins, 1 side 0b01
out pins, 1 side 0b00
jmp x--, left_loop side 0b01
in pins, 1 side 0b00
out pins, 1 side 0b11 ; Last bit of left has WCLK change per I2S spec
mov x, y side 0b10
right_loop:
in pins, 1 side 0b11
out pins, 1 side 0b10
jmp x--, right_loop side 0b11
in pins, 1 side 0b10
out pins, 1 side 0b01 ; Last bit of right also has WCLK change
; Loop back to beginning...
I don't have a spec in front of me, but many of these interfaces shift on the opposite edge of sampling to help with setup and hold. Do it wrong and it will occasionally work, but not reliably across different instances.
Earl is correct, almost all interfaces have the data set-up (in and out) on one edge of BCLK and read on the other. Which edge differs between the standards and chip implementations (particularly on TDM). The Philips I2S standard is: set-up on the falling edge, sample on the rising edge. 1 bit delay after LRCLK (WCLK/WS) on the first channel. I2SBUS.pdf
This works to our advantage in this case, as the write and read are on opposite BCLK polarities.
I haven't examined your code in detail, but you seem to be on the right track, however you may need to reverse the order of your in and out instructions to set up the outputs first, leaving the side sets in the order you have them.
Add full-duplex (in+out) mode for I2S using PIO.