Shop OBEX P1 Docs P2 Docs Learn Events
Propeller P1V CD quality audio over I2S (and now for regular Propellers/SIDCog/AYCog/SNECog patches) — Parallax Forums

Propeller P1V CD quality audio over I2S (and now for regular Propellers/SIDCog/AYCog/SNECog patches)

roglohrogloh Posts: 5,122
edited 2017-07-23 06:55 in Propeller 1
A while ago I purchased a nice 24 bit I2S digital audio DAC output module (the CS4344 based PMOD I2S module from Digilent) and in the last few days I finally got around to interfacing it with the P1V Verilog to get some sound output quality better than the typical counter DUTY cycle based PWM audio the Propeller often uses. Before I delved into the Verilog stuff I was also able to get this module operating with a real Propeller chip with some PASM and the video generator to prove it worked as is but I really wanted to support it directly within a P1V, both to save on three or four I/O pins mapped to the outa or outb ports, and in some other cases potentially save on the additional COG it may otherwise require too.

i2s.png

I2S module:
http://store.digilentinc.com/pmod-i2s-stereo-audio-output/

CS4344 datasheet:
http://www.mouser.com/ds/2/76/CS4344-45-48_F2-472818.pdf

My goal here was for playing CD quality stereo 16 bit x 44.1kHz WAV files from an SD card using the P1V and I now have this functioning and it sounds decent enough with headphones and I don't see problems on the I2S bus with my logic analyser.

logic.png

To get it to work I mostly just extended the hub.v file.

Firstly I created some new I2S signals and passed these to/from the hub module:
input 				mclk,    // 22.5792 MHz master input/output clock
output				lrclk,   // I2S LRCLK
output reg			sdata,   // I2S DATA
output 				sclk     // I2S CLOCK

These four signals were also connected through the FPGA internally in top.tdf and dig.v and link up to the external pins assigned to them (the extra plumbing Verilog for that part is not actually shown below, but is simple enough).

For clocking I just used an external oscillator as the source of the 22.5792 MHz master clock but in theory I believe it could also be generated internally using the FPGA PLLs. I determined there are some integer relationships possible to get reduced jitter from a 50MHz source cascaded through two Altera PLLs, but I haven't tried it yet to see if it works.

Then I added a P1V software extension to the existing hub operations with a new method to send samples from a COG to the I2S interface. Audio I2S samples are fed to the I2S interface by a COG simply executing the following instruction (you need PASM as SPIN doesn't support HUBOP directly to my knowledge, even if it could keep up with the rate):
HUBOP audio_sample, #9  ' where the 32 bit audio_sample COG register holds the combined left and right 16 bit signed audio samples

This hub operation instruction causes the COG to stall until its audio sample data is consumed shortly after the rising edge of lrclk, which is a safe time to do so to avoid any setup time metastability across the clock domain. The I2S bus is a shared hub resource and any other COGs also trying to do the same thing while an I2S transfer operation from another COG is still pending are ignored and just return immediately. I could also eventually add in some way to indicate to another COG if the I2S writer is busy and has ignored the request eg, set Carry flag if I2S transfer failed due to being busy, but for now its no big deal if only one COG will access it so I left the behavior as described.

Because hub operation #9 (0_00001001 binary) is also aliased to COGID (xxxxxx001 binary) it is quite safe to use this particular value without modifying the existing COGID operation and it has no side effect on the other hub resources. By default the HUBOP encoding will not write the result so the destination register holding the audio sample data is left as is. If the result bit was ever enabled for the result to be written it would just be the COGID value.

A desirable outcome of the stall is that the rate of data delivery is then accurately locked to the I2S LR clock rate (at 44.1kHz) and it can be used to synchronously pull data from a buffer containing the audio samples (eg. something being filled by reading from an SD card) and play out at the correct rate. This automatic HUBOP execution spacing can also be used to time the audio generation COG objects (such as the great SIDcog/AYcog etc from Ahle2) instead of their typical WAITCNT approaches, assuming they are patched appropriately for operating at the different sample rate. I have had some success there however I don't think SIDcog can run at 44.1kHz sample rate without adding support for a hardware multiply instruction first and then use it to reduce all the volume envelope calculation instructions needed per audio sample. My recollection is that SIDcog wouldn't be able run that fast without such a change. This hardware multiply instruction is of course possible on a P1V and I've successfully used it myself in the past.

An example of how to use it is shown below for AYcog. Original code:
mixer         mov      r1, val31bit                        '  DC offset
              add      r1, out1
              add      r1, out2
              add      r1, out3
              waitcnt  waitCounter, sampleRate             '  Wait until the right time to update
              mov      FRQA, r1                            '  the PWM values in FRQA/FRQB
              mov      FRQB, r1                            ' 

Code changes needed to use I2S output (creating and passing in signed 16 bit left and right samples, just mono in this case)
mixer         mov      r1, out1
              add      r1, out2
              add      r1, out3
              shr      r1, #16            ' create right sample
              mov      temp, r1           ' save it
              shl      r1, #16            ' create left sample
              or       r1, temp           ' combine them
              hubop    r1, #9             '  Send L&R sample pair to I2S

If anyone else wants to play about with I2S on P1V my Verilog additions to hub.v for supporting an I2S output bus are provided below:
// i2s cog domain stuff is clocked by clk_cog

// an i2s write request (HUBOP sample,#9)
wire i2s = ec && &sc && ac[8:0] == 9'b000001001; 

// this register tracks source of pending i2s request
reg [7:0] i2s_req; 

always @(posedge clk_cog or negedge nres)
if (!nres)
	i2s_req <= 8'b0;
else if (ena_bus && i2s && ~|i2s_req) // new requests while idle latch bus_sel to track the cog source of i2s request
	i2s_req <= bus_sel;
else if (ena_bus && i2s && |i2s_req && (bus_sel == i2s_req) && !i2s_pending) // clear request once data is written
	i2s_req <= 8'b0;

// flop is true while waiting for data to be consumed
reg i2s_pending; 

always @(posedge clk_cog or negedge nres)
if (!nres)
	i2s_pending <= 1'b0;
else if (ena_bus && i2s && ~|i2s_req) // new requests also set pending flag
	i2s_pending <= 1'b1;
else if (ena_bus && !lrclk_cog[2] && lrclk_cog[1]) // rising edge of (delayed) lrclk clears the flag
	i2s_pending <= 1'b0;

// holding buffer
reg [31:0] i2s_data; 

always @(posedge clk_cog or negedge nres)
if (!nres)
	i2s_data <= 32'b0;
else if (ena_bus && i2s && |i2s_req && (bus_sel == i2s_req) && !i2s_pending)
	i2s_data <= dc; // latch the data sample at at a safe time 

// lrclk is resampled to synchronize over the two clock domains and to avoid metastability
reg [2:0] lrclk_cog; 

always @(posedge clk_cog or negedge nres)
if (!nres)
	lrclk_cog <= 3'b0;
else if (ena_bus)
	lrclk_cog <= {lrclk_cog[1:0], lrclk}; // shift in the resampled lrclk signal 


// i2s audio domain stuff is clocked by mclk

reg [8:0] sclk_count; 

always @(posedge mclk or negedge nres)
if (!nres)
	sclk_count <= 9'b0;
else
	sclk_count <= sclk_count+1;

assign sclk = sclk_count[3];  // mclk divided by 16
assign lrclk = sclk_count[8]; // mclk divided by 512

reg [31:0] i2s_sample;

always @(posedge mclk or negedge nres)
if (!nres)
	i2s_sample <= 32'b0;
else
	if (&sclk_count[8:0])
		i2s_sample <= i2s_data;
	
always @(posedge mclk or negedge nres)
if (!nres)
	sdata <= 1'b0;
else if (&sclk_count[3:0])
	sdata <= i2s_sample[(sclk_count[8] ? 15 : 31 ) - sclk_count[7:4]];

Finally lower down I changed the existing bus_ack signal to effectively generate the wait states for the P1V while waiting for the I2S data to be consumed. This was the key to make it work at the right rate. Getting this line correct was a little tricky and for some time it was wrong and I had other COGs being impacted messing up my baud rates etc until I fully understood the pipeline timing and how the bus_sel rotation thing worked.
//assign bus_ack		= ed ? {bus_sel[1:0], bus_sel[7:2]} : 8'b0; // original code
assign bus_ack		= ed ? {bus_sel[1:0] & ~{i2s_req[0], i2s_req[7]}, bus_sel[7:2] & ~i2s_req[6:1]} : 8'b0; // new update for I2S

Now there are possibly simpler ways to implement all this (I'm no Verilog guru) but it works for me and I get CD quality sound output. Over time I might possibly add other features like more programmable clocking/sample rates, support for 24 bit samples and ramps back down to a zero value if I2S has become idle for some time but for now it seems to be working ok and I'm happy enough with it.

Enjoy,
Roger.

Comments

  • Nice project Roger. Thanks for sharing!
  • jmgjmg Posts: 15,140
    rogloh wrote: »
    My goal here was for playing CD quality stereo 16 bit x 44.1kHz WAV files from an SD card using the P1V and I now have this functioning and it sounds decent enough with headphones and I don't see problems on the I2S bus with my logic analyser.

    Very cool.
    Are you able to also test this on a P2 platform, using the Smart Pin cells ?
    In theory, i2s should be easy to craft via P2 pin cells, but the details can matter..... I'm not sure how the NCOs play here ?


  • Cluso99Cluso99 Posts: 18,066
    Congratulations Roger, well done!

    It would be possible to add that hubop into my faster spin if you really wanted it. I have a ROM version of the faster spin for the P1V.

    BTW I have been thinking of getting back into P1V to increase hub ram beyond 64KB using one of the larger cyclone boards supported for the P2.

    One of the things with P1V that would be good to do is speed up the hub access to 1:8 (ie single clock instead of the current 2 clock per cog window). This would permit either 2x hub speedup or 16 cogs with 1:16.

    The other P1V nicety would be to see if we could increase base clock frequency. However, I don't understand Quartus enough to use the timing to verify, and currently IIRC there are reported timing errors currently.

    Ray
  • roglohrogloh Posts: 5,122
    edited 2017-07-07 02:20
    Thanks guys.

    @jmg, haven't played with P2, staying out of that lot for now.

    @Cluso99, yeah adding more hub RAM to the P1V is pretty satisfying and opens up more possibilities. I had the earlier SDRAM and SRAM projects and they both allow more applications, especially with XMM & PropGCC and using it for larger programs, graphics etc. I've not played with increasing hub speeds or adding more COGs, though I'd admit there is probably scope there, I just haven't really had the need yet. Now that I have this decent quality I2S audio and once I put the external RAM support back in, I would also one day like to mess around with some wavetable stuff and mod files etc. But I have plenty of things to play about with right now...

    As to supporting hubop I2S transfers directly in spin, I was wondering if it would be helpful or not compared to PASM. Perhaps with a very tight spin loop you would be just able to use it, but it may not be able to do a whole lot more work or samples would begin to get skipped at 44.1kHz and the audio will be stretched. Maybe a player loop would just fit with included some data access operations and double buffer wraparound tests and song complete tests etc. But I'd be suprised it could reliably do much more than that without adding jitter to the output which is noticeable. Certainly no time to wait to read data off disk while in this loop. You'd need another COG anyway.

  • roglohrogloh Posts: 5,122
    edited 2017-07-07 05:09
    One other thing I wanted to mention.

    On a regular P1 it would be great to be able to incorporate an I2S bus in player loops like AYcog etc without using an additional PASM COG dedicated for outputting the I2S data like I had tested originally.

    For a 32 bit I2S sample (16 bit left and 16 bit right) it is easy enough to get the video generator output 32 bits with a frame rate very close to 44.1kHz and stream out all the 32 bits for you in one go, and I've already had that working. The MCLK signal can also be driven by the counter feeding the PLL. What's nice on this particular PMOD module the SCLK can be derived internally by the CS4344 chip so that output signal can be held fixed and there is no need to generate it, so that solves that problem too. However there still needs to be the LRCLK generated which is also out of phase by one bit from the overall WAITVID output data frame. I wonder if there is some way that two counters could be used, CTRA for doing the WAITVID and the CTRB to generate an LRCLK at a rate that is 1/32 of the WAITVID's data rate. The key there is to get the two counters in PLL mode and their phase offsets aligned by one I2S data bit and then remain locked. I'm not sure if the two COG counters PLL outputs can be reliably initialized like that, but if so it might be worth a try. The CS4344 might tolerate some amount of phase inaccuracy there too (if less than a data bit), but again not sure. Actually if the two PLLs can be locked to any fixed bit, data can always be rotated to compensate for the skew. Key is that this skew is known and remains fixed. Maybe it could be measured by the COG at boot time if it is random each time.

    So if this general concept above was achievable on a regular Propeller you could just patch AYcog etc to use a WAITVID instruction instead of my HUPOB #9 instruction in the modified example above. It would lock the output to the 44.1kHz nicely and save a COG.
    mixer         mov      r1, out1
                  add      r1, out2
                  add      r1, out3
                  shr      r1, #16            ' create right sample
                  mov      temp, r1           ' save it
                  shl      r1, #16            ' create left sample
                  or       r1, temp           ' combine them
                  waitvid  clkpinmask, r1     '  Send L&R sample pair to I2S
    

    If you can't initialise the two counters as I hoped above, you can still do I2S in a single COG with a single counter by using the WAITVID sending 16 bit frames (in two color mode) and send L&R samples separately using one WAITVID instruction for each. One WAITVID "color" is chosen to drive the LRCLK pin high, the other drives LRCLK low and in fact this is how I did my initial I2S module test. You do need to rotate the audio sample data by one bit too. The only downside with that approach is that you have to split your sample generation code into two hopefully equal pieces, so the two WAITVIDs can be issued in time every 11.34us. It is however obviously nicer to have it handled with a single WAITVID as that lets you use most of the 22.68us interval to do other useful work without stopping halfway to send out a signal audio channel's sample.
  • jmgjmg Posts: 15,140
    rogloh wrote: »
    ... I'm not sure if the two COG counters PLL outputs can be reliably initialized like that, but if so it might be worth a try...
    There are quite a few spare bits in the CTRA, CTRB config registers, so one easy addition would be a paired/or'd ClkEnable bit which would appear as 2 bits in both registers.
    A natural extension of this would be a CtrCLR pair too.
    With those, you could preload/configure, then enable last, and ensure they started in sync on the same and atomic clock edge.
  • Well done, Roger! That's the beauty of the P1V, trading cogs for HDL. I'm usually annoyed by the hub stall, but you found a good use for it.
    rogloh wrote: »
    However there still needs to be the LRCLK generated which is also out of phase by one bit from the overall WAITVID output data frame. I wonder if there is some way that two counters could be used, CTRA for doing the WAITVID and the CTRB to generate an LRCLK at a rate that is 1/32 of the WAITVID's data rate. The key there is to get the two counters in PLL mode and their phase offsets aligned by one I2S data bit and then remain locked. I'm not sure if the two COG counters PLL outputs can be reliably initialized like that, but if so it might be worth a try. The CS4344 might tolerate some amount of phase inaccuracy there too (if less than a data bit), but again not sure. Actually if the two PLLs can be locked to any fixed bit, data can always be rotated to compensate for the skew. Key is that this skew is known and remains fixed. Maybe it could be measured by the COG at boot time if it is random each time.

    I think it would be better to use CTRB in NCO mode. You can set an initial phase and start it deterministically, like right after a waitvid. It would be similar to the USB receiver. Maybe write PHSB after every waitvid to prevent an offset from accumulating.

  • roglohrogloh Posts: 5,122
    edited 2017-07-07 05:24
    Thanks James. Yeah I was also considering resetting a counter in this mode after the waitvid but was just worried about the 80MHz jitter appearing on the resulting LR clock. It might still be okay, depending on which clock that the CS4344 uses to drive its final audio output (probably MLCK latching its LR clock I'd expect). Worth a try anyway.
  • jmgjmg Posts: 15,140
    I think it would be better to use CTRB in NCO mode. You can set an initial phase and start it deterministically, like right after a waitvid. It would be similar to the USB receiver. Maybe write PHSB after every waitvid to prevent an offset from accumulating.
    A problem here is two counters have a finite aperture between the writes, and a generated (NCO) clock could sneak an extra edge into that gap.
    I'm guessing the LRCLK is a steering signal, and it can tolerate some jitter, so this aperture effect might be OK.

    Where LRCLK is used to synth a SCLK it may be less tolerant, and the write PHSB after every waitvid may help
    I get, for one example of MCLK
    80M*round(2^32*11.2896M/80M)/2^32 = 11289600 that one is exact
    80M*round(2^32*44.1k/80M)/2^32 = 44100.00518 that one is not quite exact
    but maybe this is better ?
    80M*256*round(2^32*44.1k/80M)/2^32 = 11289601.33 0.1ppm error, but now they are phase locked

    rogloh wrote: »
    ... What's nice on this particular PMOD module the SCLK can be derived internally by the CS4344 chip so that output signal can be held fixed and there is no need to generate it, so that solves that problem too...

    Did you check that with a NCO LRCLK to lock this internally SCLK ?
    Data I see says "12. In Internal SCLK Mode, the Duty Cycle must be 50% +/- 1/2 MCLK Period.", so 80MHz level jitters look to be OK.


  • jmg wrote: »
    Did you check that with a NCO LRCLK to lock this internally SCLK ?
    Data I see says "12. In Internal SCLK Mode, the Duty Cycle must be 50% +/- 1/2 MCLK Period.", so 80MHz level jitters look to be OK.
    No jmg I haven't tried it to date. For my P1V implementation, I generated the SCLK explicitly. On the real P1 when I tested this module with WAITVIDs and the internal SCLK, I generated LRCLK signal in parallel to the I2S serial data using a WAITVID driving two output pins and broke up the 32 bit transfer into 2x16 bit transfers one with LRCLK high and the other low. This way LRCLK remained synchronous to the data and to the MCLK, though MCLK probably has quite a bit jitter from the PLL of the P1 depending on the NCO values needed for the actual sample rates.

    If/when I get around to it I'd like to try a real P1 again with a single WAITVID implementation to see if it is doable and can be integrated into SIDcog/AYCog etc. Not sure when that will be as I'm mainly engrossed in P1V right now. Its probably possible to patch SIDcog with I2S and just keep its sample rate at 30789Hz or whatever it is and just clock the I2S device at that rate too. That way you don't need to modify the SIDcog code much at all to get the benefit of I2S audio.
  • jmg wrote: »
    A problem here is two counters have a finite aperture between the writes, and a generated (NCO) clock could sneak an extra edge into that gap.
    I'm guessing the LRCLK is a steering signal, and it can tolerate some jitter, so this aperture effect might be OK.

    Where LRCLK is used to synth a SCLK it may be less tolerant, and the write PHSB after every waitvid may help
    Yes, some consideration is needed to prevent the extra edge. Adjusting the phase after waitvid should be fine as LRCLK flips 1 bit before the new word is sent. I haven't checked the timing relationship between waitvid and the data output. The adjustment should be small enough that it won't be noticed for many cycles. Or just pick frequencies that will stay in phase.

    Even if LRCLK is fed to a PLL it shouldn't be any worse than using the Propeller PLL. The Propeller PLLs don't filter much jitter anyway. I don't know if there would be data transfer issues if the DAC PLL filters jitter and the Propeller doesn't. I don't think multiplying LRCLK up to SCLK or MCLK makes sense if it's only 44kHz. Do any parts do this?
  • If its relevant to the discussion I've used a few I2S DACs and ADCs before, typically using a 6.144MHz
    crystal for the Prop, running at 98.304MHz system clock which is exactly 2048 clocks per sample
    at 48kSPS (one of the standard I2S frequencies). That typically gives 8 instructions per BCLK which
    makes it easy to drive both an ADC and a DAC simultaneously in one cog IIRC, and the MCLK is jitter-free
    being 1/8th of the system clock typically.
  • roglohrogloh Posts: 5,122
    edited 2017-07-09 06:10
    @Mark_T. Yeah that would work well locking the MCLK to the Prop clock.

    So I couldn't help myself and dug out my ProtoBoard and was able to get the LRCLOCK generated in software and use WAITVID to output both 16 bit samples in a single go. Audio is working and it sounds okay for now.

    Hacked up PASM test code is shown below. Forgive my hardcoding.
    pin8 = SDATA (from WAITVID)
    pin9 = SCLK (fixed high, generates internal SCLK inside DAC)
    pin10 = LRCLK (counter B NCO)
    pin11 = MCLK (counter A PLL)
    DAT
       org 0
    entry
       movi ctra, countera
       movs ctra, #11  ' MCLK pin
       movi ctrb, counterb
       movs ctrb, #10  ' LRCLK pin
       mov  frqa, frequencya
       mov  outa, sclkmask ' hold I2S pin high to use internal SCLK in DAC
       mov  dira, pinmask
       movi vcfg, vmode
       movs vcfg, #1 ' pin 8 is I2SDATA
       movd vcfg, #1 ' group 1
       mov  vscl, vscale
       mov  frqb, #1
    
    loop
       waitvid color, data
       nop
       nop ' these two nops seem to help align the LR clock on a bit transition @80MHz
       mov phsb, resetvalue
    
       rdlong data, par
       rev data, #0
       rcl data, #1 wc
       jmp #loop
    
    data          long 0
    sclkmask      long 1<<9  ' I2S SCLK pin (not generated by Prop)
    pinmask       long ($F << 8)
    counterb      long %0_00100_000
    countera      long %0_00010_011
    color         long $100
    vmode         long %0_01_0_00_000
    vscale        long $4080
    frequencya    long 303052892
    resetvalue    long -908  ' half of 1814 cycle period @80MHz for 44.1kHz
    
       fit
    

    This code results in continously reading a stereo sample from the PAR register address and outputting using a 32 bit WAITVID with an LRCLK transition half way in the 22.68us loop. I think I may still need to invert the LRCLK pulse if the left sample comes first, need to confirm. The two NOPs after the waitvid seemed to help align the edge very close to the bit transition.

    In this screenshot from my scope you can see the effective jitter (yellow trace is the synthesized LR clock using counter B in NCO mode, blue is the MCLK output from PLL mode counter A that feeds the video generator, scope triggered on LRCLK edge). I had persistence on for a while and it seems to be showing only a small amount of jitter maybe within +/- 12.5ns and certainly less than 1/2 MCLK that the spec for the CS4344 mentioned. Sound quality doesn't seem to suffer from this on this DAC (at least to my non-golden ears).

    waveform.png

    One thing I found I had to do in the PASM code was save the last bit of the last sample and rotate it into the next sample to account for the I2S format. Also reversing the bits in the sample to deal with WAITVID sending the LSB first instead of MSB I2S expects. But they were simple things to put in.

    I'm still playing around with it and want to try to incorporate this into the SIDCog itself to see how nicely it plays (at a lower sample rate than 44.1kHz though). Should work ok.
    321 x 234 - 5K
  • Great to hear that it's working!
    rogloh wrote: »
    One thing I found I had to do in the PASM code was save the last bit of the last sample and rotate it into the next sample to account for the I2S format.

    I did not think that would be necessary. I was thinking that ctrb could free-run at the correct frequency, or nearly correct as calculated by @jmg. The LRCLK transition would happen before the last bit of the word is sent, probably before the waitvid. So instead of rotating the data we shift the clock. It could probably be done using ctrb in PLL mode*, but it's easy in NCO mode. If the counters are running at exact harmonics the phsb write should only be needed once after the first waitvid to adjust the phase of ctrb to match the video generator. Please forgive me if you tried this and it didn't work.

    *Nevermind. The recommended minimum pll frequency is 500kHz.
  • roglohrogloh Posts: 5,122
    edited 2017-07-10 07:31
    @SaucySoliton
    Haven't tried out your suggestions but it could be good to save a few more instructions if it works.

    Today I was able to modify the SIDCog audio generator object to output I2S audio itself to eliminate an additional COG otherwise required for doing this. If anyone wants to play with I2S, here is a patched version of v1.3 (I called it v1.3b) that supports the CS4344 I2S DAC (tested at 80MHz) instead of its original RC filtered DUTY cycle output. It relies on the CS4344 to generate the SCLK internally and the Propeller just holds this SCLK signal high to disable its DE-EMPHASIS mode. Counter PLL A generates MCLK, and clocks the WAITVIDs. Counter B is used in NCO mode to generate and align the LRCLK edges after WAITVID.

    Here's the updated SIDCog. Annoyingly I couldn't get the _CLKFREQ to work in my constant calculations (with BST) and so I created another PROP_FREQ constant that can be patched if ever different to 80MHz.

    Enjoy,
    Roger.
  • Hi rogloh

    Many thanks SIDi2s working great.

    Is possible make same AYcog modification and make obex i2s driver for general use ? (like sine wave gen and other)

    Many many thanks

    Kamil
  • roglohrogloh Posts: 5,122
    edited 2017-07-17 14:48
    JLS wrote: »
    Many thanks SIDi2s working great.

    Is possible make same AYcog modification and make obex i2s driver for general use ? (like sine wave gen and other)

    Glad to hear it works for you @JLS.

    Yes it should be possible to change the AYCog in a similar way, however the sample rate for that object is set higher at 125kHz so there would be some other changes required. I haven't tried but you could give it a go if you have your i2s already working. I think you'll probably want to run it at 88.2kHz and probably scale some of these audio parameters below down according to the new rate to keep it in tune. Maybe your i2s module can even be operated at 125kHz, not sure. But for that rate the mclk would probably then need to be either 8 or 16MHz. Perhaps the P1 can be coaxed to generate a nice 8MHz. The other timing information I use for the P1 PLLA would need to be scaled too for a LR clock rate different to 44.1kHz or the one I'd computed for SIDCogI2s. Have a play around and give it a go and see what you get out of it.
    ' WARNING !!
     ' Don't alter the constants below unless you know what you are doing
     '-------------------------------------------------------------------
      PSG_FREQ    = 2_000_000.0              ' Clock frequency input on the emulated AY chip 
      SAMPLE_RATE = 125_000                  ' Sample rate of AYcog (125 kHz is maximum for an 80 Mhz propeller)
       
      OSC_CORR    = trunc(1.05 * PSG_FREQ)   ' Relative oscillator frequency
    


    As to OBEX I2S drivers yeah I sort of hoped I might add that when I get a chance (or someone else knowledgable may do so in the meantime based on this thread information). I am deep into P1V Verilog right now for some other video stuff and so don't want to stop, but if there's a break I might.

    Cheers,
    Roger.

    ps. by the way, an I2S object can be constructed to read its stereo sample from a single long address that is written to by another COG for the sample. Like SIDcog already does at register offset 28. It would take another COG for the I2S output, but it is then generic and works with any sample rate being written into the sample address in memory by the generator object. It then outputs at its own sample rate and I guess it behaves like a rudimentary sample rate converter (no interpolation). I've used this approach before with I2S and SPDIF objects. YMMV with the sound quality by doing this but it sounded okay enough to me for general purpose use. You can also use this approach to scale/mix from various source objects that output into different hub addresses at different rates.
  • roglohrogloh Posts: 5,122
    edited 2017-07-19 11:59
    So I found a spare hour and played about with AYcog and my I2S interface to a CS4344 and I was able to make a mod that allows this object to play its audio out over an I2S bus using the video generator and it seems to sound ok.

    Here's the patched AYCog spin object code with the mod required for digital audio via a CS4344 I2S DAC (I called it v0.8b). You can change the SAMPLE_RATE parameter and it will adjust the required PLL rate accordingly and it should also remain in tune too. I tried a range of sample rates from 10kHz up to 100kHz which worked, but I have just set the default sample rate to run at 88200Hz (2xCD). The original value was 125kHz.

    It is currently coded for 80MHz Propellers but that should be adjustable too with the PROP_FREQ parameter, however I didn't try any other speeds than 80MHz so YMMV.

    Roger.
  • "Sounds" like your making good progress there Rog. :)
  • Many thanks

    New AYi2s is working.

    Next step is SNEcog ? :-D

    Thanks your work
  • Hmmm This might also come in handy in combination with my SPDIF decoder :-)

    https://hackaday.io/project/24911

    ===Jac
  • roglohrogloh Posts: 5,122
    edited 2017-07-23 06:53
    Here's my patched I2S variant of SNEcog (called v0.6b) for completeness. Works at 88.2kHz sample rate by default now but can be varied. I found that the original 176.4kHz sample rate caused a bit of audible warbling as I think it might be just over its main loop cycle limit now in some particular sample calculations with the newly added I2S overhead. But 88.2kHz and even some higher rates than that were okay.

    Roger.


  • Hi Roger

    Many thanks SIDcogi2s, AYcogi2s and SNEcogi2s all working fine.

    Is possible make Retronitus, PM_synth i2s mod ?

    Propeller is my dream platform :-)

    Many thanks

    Kamil

  • Well I took a look at the Retronitus and PM_synth objects mentioned above. They are fully packed so there is no space left for a regular P1 to add the extra I2S code without removing some stuff, though my P1V approach should still be possible with the hubop command I use instead of the usual waitcnt and mov frqa/frqb approach etc. I guess removal of a voice in PM_synth and a channel in Retronitus may allow space for the I2S stuff on regular Propellers, probably not something I intend to do.
  • Thank you for your info and your work
Sign In or Register to comment.