Increasing the 3khz control rate frequency


#1

Hey people,
I want to use the axoloti to control the following 40Channel DAC board.
https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-ad5370.html#eb-overview


As i plan to use the axoloti mainly for controlling those channels I am wondering if there is a way to increase the control rate frequency from 3khz to for example 48 khz or 24khz.
I read the following topic and wanted to implement the spi communication similarily but I think that the script objects loop function is called at a rate of 3khz only, isnt it?
Or is there a better way to implement that spi code as a pseudo audio object or with a totally different approach which gives even better performance?
https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-ad5370.html#eb-overview

Thanks in advance,
Florian


#2

Axoloti editor has 2 fans for putting code for objects into, one for K-rate, which is 3khz and one for S-rate, which is 48khz.

You can probably just put the code in the 48khz fan and it will be updated at 48khz?


#3

I wonder what is the goal of the 40 channel DAC - to provide 40 channels of audio output?


#4

I want to control a lot of mini modules which are based around the cem/alfa synthesizer chips using a quite complex midi controller patch I created for the axoloti where it was the sound generator. It sounded not so good thats why I try using that 40 channel DAC Evaluation board to replace the audio objects with hardware. Somehow it is using the axoloti as a very complex and editable midi/CV module


#5

yummy. you got my attention.

are you using electro smith submodules? i got a couple of those and still plan to make an axoloti-controlled analog synth one day. currently only using the fv-1 one.


#6

What is a fv-1 ? this one? http://www.spinsemi.com/products.html
For what do you use it?
I bought the pcbs and parts for a raw implementation of the CEM3340 from Frank Buss https://hackaday.io/project/160538-cem3340-module Now i make a mother pcb for this little board to get the right signal levels and the sine etc.
I also bought many of the cem vca vcadsr and vcf chips. The next step is to build a prototype vca, vcadsr and vcf for these too. And if everything is designed well i want to create independant micro modules similar to the one of Frank Buss.
And then the last step is to design a routing pcb on which i can just plug those micro modules which could be used in other contexts too.


#7

I can now control the DAC via bitbanging but somehow the spi functions wont work. Perhaps someone can see the fault.
I put the gpio/spi/config object in the top left corner of the patch with the settings:
clock_polarity: 0
clock_phase: 0
baudrate: FPCLK/4
format: MSB first

txbuf[0] = 0b11001000;
txbuf[1] = 0x80; 
txbuf[2]= 0b00010011;

palWritePad(GPIOA, CLK, 0); 
palWritePad(GPIOA, CLK, 0); palWritePad(GPIOA,SYNC,1); 
palWritePad(GPIOA,SYNC,0);

spiSelect(&SPID1); 
spiSend(&SPID1,3,txbuf);        
spiUnselect(&SPID1);

palWritePad(GPIOA,SYNC,1); 
palWritePad(GPIOA,SYNC,0);

palWritePad(GPIOA,LDAC,0);
palWritePad(GPIOA,LDAC,1);

When i do it manually it works:

// Bit banging 
palWritePad(GPIOA, CLK, 0); 
palWritePad(GPIOA,SYNC,1); 
palWritePad(GPIOA,SYNC,0);

// Generate 24 falling clock edges	
for(int i=0; i<24; i++) { // MSB First
	// set Data - MSB First 
	if (i<8) { 			
		if (txbuf[0] & (1 << (7-i))) {
			palWritePad(GPIOA, MOSI, 1);
		} else {
			palWritePad(GPIOA, MOSI, 0);
		}
	} else if(i < 16) {
		if (txbuf[1] & (1 << (7-(i-8)))) {
			palWritePad(GPIOA, MOSI, 1);
		} else {
			palWritePad(GPIOA, MOSI, 0);
		}
	} else {
		if (txbuf[2] & (1 << (7-(i-16)))) {
			palWritePad(GPIOA, MOSI, 1);
		} else {
			palWritePad(GPIOA, MOSI, 0);
		}
	}
	
	palWritePad(GPIOA, CLK, 1);
	palWritePad(GPIOA, CLK, 0); 	 
}

palWritePad(GPIOA,SYNC,1); 
palWritePad(GPIOA,SYNC,0);

palWritePad(GPIOA,LDAC,0);
palWritePad(GPIOA,LDAC,1);

I need 24 falling clock edges while SYNC/NSS Pin (PA4) is LOW

I have the feeling that the clock output is wrong when i try to use the spi functions.
Can someone help me out?

eval_board-control_dev_05.axp (7.5 KB)

___ EDIT ___ SOLVED
I found the right information


And i had set the Outputmode of some Pins twice.
I still dont know what this PAL_MODE_ALTERNATE in the GPIO/SPI/CONFIG object code means ... but it works

palSetPadMode(GPIOA, 4, PAL_MODE_OUTPUT_PUSHPULL);// NSS

palSetPadMode(GPIOA, 5, PAL_MODE_OUTPUT_PUSHPULL);// SCK
palSetPadMode(GPIOA, 7, PAL_MODE_OUTPUT_PUSHPULL);// MOSI
//palSetPadMode(GPIOA, 4, PAL_MODE_ALTERNATE(5));// NSS
palSetPadMode(GPIOA, 5, PAL_MODE_ALTERNATE(5));// SCK
palSetPadMode(GPIOA, 6, PAL_MODE_ALTERNATE(5));// MISO
palSetPadMode(GPIOA, 7, PAL_MODE_ALTERNATE(5));// MOSI


#8

thats exciting please keep us updated. maybe oyu wanna do another thread just for the DAC board driver though so its easier to find, since this current thread title is kind of misleading.

i thought you might be using the electro smith modules that also mostly utilize CEM chips:
https://www.electro-smith.com/electro-boards
they have a VCO, VCF, VCA and the FV-1 multieffect, all in very basic but super convenient "developement boards". i got a couple of them around and was gonna do something similar to what you're doing i think, use the axoloti as a controller, for UI, LFOs, envelopes etc and just send CVs to the analog submodules. But it looks like the stuff you're using is very similar.


#9

No, what happens is that the Axoloti framwork puts a 16 times loop around the S-rate code, and runs the whole loop at the K-rate. So the code in the loop will be executed 48000 times per second, but not in a uniform manner, rather, 3000 times per second it will execute 16 times in a row. For some applications this is fine, but it doesn't sound like it would be a good idea if outputting data to one ore more DACs which need a regular sample interval corresponding to 48000 Hz.


#10

Thank you for the clarification, didn't know that :slight_smile:


#11

@weasel79
Ah, these are interesting modules. If i would have known that before buying the alfa chips in DIP-14 Housing I would have thinked about buying these instead. But they are also a little more expensive.

@ricard Thank you for that hint. I am not sure if I understood it correctly. What you described would happen if i put the spiSend function inside the S-Rate code tab?
Is it possible to rename the thread to "AD5370 16bit 40 Channel DAC"?

Is there a way to execute my code 48000 times per second?
Here is the first working version:

eval_board-control_dev_11.axp (8.7 KB)


#12

If you put code inside the K-rate tab, it will be executed 3000 times per second, at roughly equal intervals (1/3000 s = 0.333 ms). The exact time at with the code is executed (relative to the 3000 Hz K-rate 'tick') depends on where the patcher actually places your code relative to the code for all the other modules. The higher up and further to the left in the patcher GUI you place a module, the closer to the 3000 Hz K-rate 'tick' it will be executed. Most often, modules take about the same amount of time to execute each time, so your code will get called fairly regularly at 0.333 ms intervals wherever in the physical patcher GUI space it is locaed.

If you put code inside the S-rate tab, the patcher puts a 16 times loop around it, and executes the loop at the K-rate of 3000 Hz. Thus, your code will be executed 48000 times a second (16 x 3000 Hz), but the interval between each time your code is called will not be 1/48000 = 20.833 µs. Rather, every 0.333 ms, your code will be called 16 times in quick succession. This means that if you're driving, say, an external DAC directly, and want to supply data (samples) every 20.833 µs (corresponding to 48000 Hz), it won't work, as you'll get a significantly shorter sample interval during the 16 times loop, and then a significantly longer (essentially, 0.333 ms minus the time it took the loop to run) time before the next burst of 16 samples.

But let's assume that the DAC has an internal FIFO that is at least 16 samples deep, and that the DAC consumes one sample from the FIFO per sampling interval (i.e. 20.833 µs @ 48000 kHz). In that case you could fire off 16 samples from the (implied loop in the) S-rate code, and the DAC would take care of them at the proper sampling interval, operating on one sample per sampling interval from the FIFO.

The point here is that if you want to precisely fire off a hardware event at a determined and constant interval, it won't work with S-rate code. Even K-rate code is dodgy, as the exact interval depends on the execution time of all the modules preceding your module. In some cases, like driving an external shift register to increase the number of GPIOs, when the exact interval is not important, it can be perfectly fine. But if you want drive a DAC to generate a stream of samples you want a very precise interval between the sample output.

(This raises the question of how the Axoloti handles the ADC and DAC in the ADAU1961 codec on the Axoloti board. The answer is that the codec is set up to generate and receive one sample per 48000 kHz sampling interval. The samples are sent using an I2S serial communications line which communicates with an I2S transceiver in the Axoloti. The I2S transceiver is in turn configured to continually send and receive a circular buffer of 32 samples for the DAC and 32 samples for the ADC, and generate an interrupt to the Axoloti firmware every 16 samples. This is what generates the K-rate sampling interval. At every K-rate interrupt, the Axoloti framework switches which half of the 32 sample in- and output buffers which is connected to the input and output modules, respectively.

Essentially, 16 samples from the ADC are accumulated in the input buffer, at the S-rate, which at the next K-rate interrupt are made available for the input module, and processed using the active patch, which in turn writes samples to the output buffer. At the same time, 16 samples from the previous run of the patch are being sent to the DAC. So the patch is constantly working on the 16 samples from the ADC from the previous K-rate interval, and writing 16 samples which will be sent to the DAC at the next K-rate interval.

Technically, the K-rate could be the same as the S-rate. However, this would mean that all processing for a single sample would need to take place at a 20.833 µs interval rather than a 0.333 ms interval. There are two reasons why this is not practical. First of all, there is an overhead setting up inputs and outputs etc for all modules, which then occurs for each sample, and not every 16 samples. Secondly, in order to optimze the DSP code, certain parameters are assumed to be constant during a K-rate interval. The resonance parameter for a filter is a good example of this. Limiting the resonance to be constant during a K-rate interval makes it possible to optimize the DSP code for the filter, leading to shorter overall execution times. Also, many processors, although I'm not sure about the ARM Cortex-M4F in the Axoloti, have instructions which can execute several trivial operations in one cycle (SIMD instructions) which can be used to optimize DSP code. Many CPUs have caches which speed up code execution, but they become really useful first when there are (tight) loops in the code. As far as I know though, the Cortex-M4F used in the Axoloti has no cache so this is not relevant.

So all in all, running DSP code in a K-rate vs S-rate environment allows significant optimizations of the code, which one wouldn't get with code processing individual samples all the way at the S-rate.

In fact, the K-rate used in the Axoloti is rather fast. In many DSP systems it can be a factor of 64 slower than the S-rate, or even slower, to minimize overhead, at the cost of increased latency. Indeed, the Axoloti system, with its 3 kHz K-rate, as an end-to-end (analog in to analog out) latency of about 0.333 ms * 2 = 0.667 ms, i.e. significantly less than a millisecond. That's about the same time it takes to transmit two MIDI bytes, of for a sound wave in air to travel about 20 cm.

Sorry for the long winded explanation... )


#13

Alright, thank you for that! So I could somehow think about using a 16 x 16bit buffer/shift register which is clocked out with the samplerate and loaded on each k-rate.
Perhaps K-rate is also enough. I just recognized that it is a bigger problem that my midi controller "akai midimix" cannot send NRPN cc messages. I mean that i can only get 0-127 steps from the faders which is not perfect. But it might be also okay when i use a smooth object in between the midi values and the "DAC object" input.

Anyways, if 3000Hz is not enough, is it possible to setup an extra timer + interrupt routine which is running faster?


#14

Wow thanks @ricard for the thorough explanation i finally understood a lot of issues i was running into recently. You should copy this into a user guide thread...


#15

@Flub - what is the maximum frequency you want to send using these DACs?


#16

I am not sure yet. I want to make possible as much as possible so actually 48khz would be awesome to have. But I would just be fine with 3000 for a beginning.

I ve got a quick question about dynamically reading the inlet values.

disp_d1 = inlet_i1 >> 11; 
//setDAC(1, X, (inlet_i2 >> 11));
for (uint8_t i = 0; i < 40; i++) {
   setDAC(i, X, inlet_i??? >> 11));	
}
pulseLDAC();

I think you see what the problem is. Do you know how it is done to dynamically compose those constant names ?


#17

You could just put the inlets into an array, like this:

int a[3]={inlet_i1,inlet_i2,inlet_i3};

And then use a variable to select which slot of the array you want to select:

outlet_l = a[param_sel];

Another way is to take advantage of srate inlets. If you are using many inlets, this is going to be the easiest way I think. If you are routing tons of cables, using this method can make bi patches a little bit easier to comprehed and work with.

If you make srate inlets, the red ones, but put the code into krate fan, you can actually route 16 krate signals through a srate inlet. Krate is 3000 srate is 48000, so 48000/3000=16.....

Something like this image below. Here I have ONE single srate inlet and then I use inlet[0], inlet[1], inlet[2], etc. to display each value from each of the parts of the inlets.

You can simply use:

inlet_i[set number here]

to select one of the 16 inlet_i[x]


#18

Cool thank you! I just wanted to test it out, but i could not connect more than one blue line to the red input. How is that possible?
Anyways i think I will take that array approach so i can also check if the value changed before sending it to the DAC to save performance. When I send all of them it takes 60% of the cpu.


#19

See here how you get the values in. If you don't need many inlets, this is probably not the best solution. But if you do it's a lot easier.


#20

Ah now I get it. Thats a nice trick and good to know : )
I think I am gonna do it with a table and a kind of helper object where you can select the table index you want to control. so I can put the inlets for the DAC outputs speperated anywhere in the patch.

Another question:
I now implement the code in a patch/object. Is it somehow more perfomant to export it as a real object somehow? I dont know how to do it, but I am curious if it is worth trying out.