I2S interfacing with the Prop2
I've been looking at using Smartpins to generate I2S bus signals (initially for a DAC using synchronous serial tx).
My sample code is below, but I suspect there is more than one way to skin this particular cat (so to speak) with all the features
in the P2.
I've assumed that the Prop will be the bus master (so generating all the clocks), which I think is reasonable for most scenarios.
Things I wonder about include:
How best to handle an audio stream on the P2?
Can the streamers talk directly to synchronous serial smart pins?
Would FIFO reads and writes be best?
How to architect a system where an ADC will go through one or more cogs for DSP operations like digital filtering,
volume control, monitoring, and then out to a DAC again?
Is support for multiple ADCs or multiple DACs useful?
5.1 signal processor feasible?
How well would using the Prop2 ADC inputs work for quality audio (for feeding to an I2S DAC perhaps) - good enough
to implement a THD meter?
Can the Prop2 clock PLL scale to 196.608MHz? Would it be stable?
On the P1 things are sufficiently constrained that there are fewer choices/possibilities available.
I presume that synchronous serial RX will work well for ADCs, but so far I've only been looking at the 'scope screen
and getting outputs working. One nice thing is that its trivial to change the speed, so long as CLKS_PER_BIT is
a multiple of 8, since the bit timing is all handled in hardware. I think I managed upto 8x 39.0625kHz sample rate.
I've used 6.144MHz/12.288MHz xtals with Prop1 to run at 98.304MHz (power of two times 48kHz, the commonest I2S rate), but
I wonder if that's really necessary given the more flexible PPL.
[ incidentally I discovered the first bit shifted out in synchronous serial mode doesn't seem to last for the full duration, so the timing
is thrown by about one bit time. After its started it all runs in lockstep with WAITCT1 nicely though ]
My sample code is below, but I suspect there is more than one way to skin this particular cat (so to speak) with all the features
in the P2.
I've assumed that the Prop will be the bus master (so generating all the clocks), which I think is reasonable for most scenarios.
Things I wonder about include:
How best to handle an audio stream on the P2?
Can the streamers talk directly to synchronous serial smart pins?
Would FIFO reads and writes be best?
How to architect a system where an ADC will go through one or more cogs for DSP operations like digital filtering,
volume control, monitoring, and then out to a DAC again?
Is support for multiple ADCs or multiple DACs useful?
5.1 signal processor feasible?
How well would using the Prop2 ADC inputs work for quality audio (for feeding to an I2S DAC perhaps) - good enough
to implement a THD meter?
Can the Prop2 clock PLL scale to 196.608MHz? Would it be stable?
On the P1 things are sufficiently constrained that there are fewer choices/possibilities available.
' Smartpin I2S DAC driver proof of concept, 3 smartpins for MCLK, BCLK, DOUT, plus LRCLK bit-banged
' Mark Tillotson 2019-01-12
CON
OSCMODE = $010c3f04
FREQ = 160_000_000
NCO_FREQ =%0000_0000_000_00000_00000000_01_00110_0
SYNC_TX = %0000_1001_000_00000_00000000_01_11100_0 ' sync serial tx using pin+1 as clk
DOUT_PIN_NUM = 48
CLOCK_PIN_NUM = DOUT_PIN_NUM + 1
MCLK_PIN_NUM = 50
LRCLK_PIN_NUM = 51
' must be a multiple of 8 for MCLK = 4*BCLK
CLKS_PER_BIT = 64 ' for 39.0625kHz samplerate (1.5MHz bitclk) (would be 48kHz for sysclk = 12.288MHz x 16)
' 40 will give 62.5kSPS at 160MHz sysclk
PUB demo | i, a
clkset(OSCMODE, FREQ)
pausems (1000) ' time for 'scope capture setup
cognew (@tx_output)
repeat
pausems (1)
DAT
org 0
tx_output
wrpin ##NCO_FREQ, #CLOCK_PIN_NUM
wxpin ##CLKS_PER_BIT/2, #CLOCK_PIN_NUM ' every 64 cycles (32 instructions), bit clock
wypin ##$80000000, #CLOCK_PIN_NUM ' toggle every other
wrpin ##NCO_FREQ, #MCLK_PIN_NUM
wxpin ##CLKS_PER_BIT/8, #MCLK_PIN_NUM ' every 16 cycles (8 instructions), master clock
wypin ##$80000000, #MCLK_PIN_NUM ' toggle every other
dirh ##CLOCK_PIN_NUM
dirh ##MCLK_PIN_NUM ' start clocks together
mov left, ##$80FFF000 ' test pat with bot 8 bits zero
mov t, left
rev t
shl t, #1
dirh lrclk_pin
wrpin ##SYNC_TX, dout_pin
wxpin ##%011111, dout_pin
wypin t, dout_pin
GETCT time
dirh dout_pin
ADDCT1 time, idelay
outl lrclk_pin
jmp #.skipy ' compensate for truncated first bit shifted by smartpin sync serial mode
.loop ' left channel
outl lrclk_pin ' drive left/right clock
.skipy
WAITCT1
ADDCT1 time, delay
sub right, #$37 ' testing pattern, 31 bits used, decrementing
mov t, right ' preload right
rev t
shl t, #1 ' I2S justification, top bit is delayed 1 place
wypin t, dout_pin
WAITCT1
ADDCT1 time, delay
' right channel
outh lrclk_pin ' drive left/right clock
WAITCT1
ADDCT1 time, delay
add left, #$100 ' testing pattern, strictly 24 bits
mov t, left ' preload left
rev t
shl t, #1
wypin t, dout_pin
WAITCT1
ADDCT1 time, delay
jmp #.loop
left long 0
right long 0
lrclk_pin long LRCLK_PIN_NUM
dout_pin long DOUT_PIN_NUM
delay long 16 * CLKS_PER_BIT
idelay long 15 * CLKS_PER_BIT
time res 1
t res 1
I presume that synchronous serial RX will work well for ADCs, but so far I've only been looking at the 'scope screen
and getting outputs working. One nice thing is that its trivial to change the speed, so long as CLKS_PER_BIT is
a multiple of 8, since the bit timing is all handled in hardware. I think I managed upto 8x 39.0625kHz sample rate.
I've used 6.144MHz/12.288MHz xtals with Prop1 to run at 98.304MHz (power of two times 48kHz, the commonest I2S rate), but
I wonder if that's really necessary given the more flexible PPL.
[ incidentally I discovered the first bit shifted out in synchronous serial mode doesn't seem to last for the full duration, so the timing
is thrown by about one bit time. After its started it all runs in lockstep with WAITCT1 nicely though ]
Comments
Nice to see interest in this topic!
Some months ago I bought a 6 channel analog amplifier, 6 speakers, 3 I2S modules (two channels per module) and a spdif-to-6-analog-out module (6 ch in DTS/DD mode) for doing exactly these kind of things on the P2. The plan is to have a cogdriver that can take multiple audio input streams, mix them together (pan, gain, sample rate conversion, antialaising) and output to smart pins, I2S or spdif with up to 6 out channels (think 5.1 setup). In spdif mode it is only realistic to have 2 out channels though, since realtime DD/DTS encoding is CPU heavy and complex. Having a pure sound driver/mixer in one cog and doing audio input and DSP in others is the best way to go I think. Then there needs to be some kind of hub mechanism to synchronize data producing cogs with the sounddriver/mixer cog.
not yet tested on a live DAC.
' Smartpin I2S DAC driver proof of concept, 3 smartpins for MCLK, BCLK, DOUT, plus LRCLK bit-banged ' CORDIC quadrature output cosine/sine to left/right as a test waveform ' Mark Tillotson 2019-01-12 CON OSCMODE = $010c3f04 FREQ = 160_000_000 NCO_FREQ =%0000_0000_000_00000_00000000_01_00110_0 SYNC_TX = %0000_1001_000_00000_00000000_01_11100_0 ' sync serial tx using pin+1 as clk DOUT_PIN_NUM = 48 CLOCK_PIN_NUM = DOUT_PIN_NUM + 1 MCLK_PIN_NUM = 50 LRCLK_PIN_NUM = 51 ' must be a multiple of 8 for MCLK = 4*BCLK CLKS_PER_BIT = 64 ' for 39.0625kHz samplerate (1.5MHz bitclk) (would be 48kHz for sysclk = 12.288MHz x 16) ' 40 will give 62.5kSPS at 160MHz sysclk NOMINAL_SAMPLE_RATE_MILLIHERTZ = 39_062_500 ' 48_000_000 if suitable sysclk TEST_FREQUENCY_MILLIHERTZ = 1_000_000 ' 1kHz PUB demo | i, a clkset(OSCMODE, FREQ) pausems (1000) ' time for 'scope capture setup cognew (@tx_output) repeat pausems (1) DAT org 0 tx_output ' compute DDS frequency QFRAC ##TEST_FREQUENCY_MILLIHERTZ, ##NOMINAL_SAMPLE_RATE_MILLIHERTZ WAITX #55 GETQX freq_reg wrpin ##NCO_FREQ, #CLOCK_PIN_NUM wxpin ##CLKS_PER_BIT/2, #CLOCK_PIN_NUM ' every 64 cycles (32 instructions), bit clock wypin ##$80000000, #CLOCK_PIN_NUM ' toggle every other wrpin ##NCO_FREQ, #MCLK_PIN_NUM wxpin ##CLKS_PER_BIT/8, #MCLK_PIN_NUM ' every 16 cycles (8 instructions), master clock wypin ##$80000000, #MCLK_PIN_NUM ' toggle every other dirh ##CLOCK_PIN_NUM dirh ##MCLK_PIN_NUM ' start clocks together add phase, freq_reg ' DDS QROTATE amplitude, phase ' to (cosine, sine) WAITX #55 GETQX left and left, ##-256 mov t, left rev t shl t, #1 dirh lrclk_pin wrpin ##SYNC_TX, dout_pin wxpin ##%011111, dout_pin wypin t, dout_pin GETCT time dirh dout_pin ADDCT1 time, idelay .loop ' left channel outl lrclk_pin ' drive left/right clock WAITCT1 ADDCT1 time, delay GETQY right and right, ##-256 mov t, right ' preload right rev t shl t, #1 ' I2S justification, top bit is delayed 1 place wypin t, dout_pin add phase, freq_reg QROTATE amplitude, phase WAITCT1 ADDCT1 time, delay ' right channel outh lrclk_pin ' drive left/right clock WAITCT1 ADDCT1 time, delay GETQX left and left, ##-256 mov t, left ' preload left rev t shl t, #1 wypin t, dout_pin WAITCT1 ADDCT1 time, delay jmp #.loop left long 0 right long 0 amplitude long $3FFFFFFF ' -6dB level phase long 0 lrclk_pin long LRCLK_PIN_NUM dout_pin long DOUT_PIN_NUM delay long 16 * CLKS_PER_BIT idelay long 15 * CLKS_PER_BIT freq_reg res 1 time res 1 t res 1
Think it might be time to spin up a 64000 extension/accessory board with I2S ADC + DAC