I2S interfacing with the Prop2

Mark_TMark_T Posts: 1,956
edited 2019-01-13 - 02:25:26 in Propeller 2
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.
' 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

  • Hi Mark,

    Nice to see interest in this topic! :smile:
    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.
    SIDcog - The sound of the Commodore 64 in a single cog: Thread, OBEX
  • Mark_TMark_T Posts: 1,956
    edited 2019-01-13 - 14:28:37
    And here's a version using cordic to generate cosine/sine quadrature at 1kHz (sample rate 39.0625kSPS),
    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
Sign In or Register to comment.