In the previous tutorial, you have learned to generate a sine wave using DDS (Digital Direct Synthesis) method and transfer the sample data into the audio DAC through I2S bus.

Remember about the SPI transfer complete interrupts in the other earlier tutorials? Everytime an audio sample is done being sent to the DAC, the interrupt triggers and the next sample is being placed into the SPI buffer. With the small DDS algorithm in the interrupt area, you get the sine wave output from the DAC’s output.

However, there is one issue: the sampling rate specified in the microcontroller is 32kHz, and the SPI transfer complete interrupt happens below every 31.25μs. (Two audio channels: 31.25μs, and then for one is 15.625μs)

The PIC32MX/MZ microcontrollers are fast but still it could not catch up with the interrupts if more processing of the samples have to be done. Sure, the small DDS code in the interrupt vector in the earlier tutorial works well, but when you have to further process the samples (like filtering), another method must be employed.

Here it is, these 32-bit microcontrollers are equipped with a DMA (Direct Memory Access) module. What is a DMA? It allows the microcontroller to transfer data between memory to peripherals (or peripherals to other peripherals) without the intervention of the CPU inside. In short, it frees the CPU from sending the samples one-by-one, allowing you to do other tasks while the system is pushing the samples into the DAC.

As usual, you have to set up the DMA modules and its related interrupts. Here is the initialization code to enable and configure the DMA modules. Note that we have more than one DMA channel, so we are using two here, and attach the DMA channel sources to the buffers:

And also, we need to declare an array for the DMA module, because you need to place the sound samples into the data memory (the global area) first before these are transferred automatically to the DAC:

short int buffer_a[BUFFER_LENGTH];

That is simple. BUFFER_LENGTH is 64 in this tutorial. is However, if you are dealing with PIC32MZ, additional keywords must be used:

short int __attribute__((coherent)) buffer_a[BUFFER_LENGTH] ;

Why that “coherent” keyword? The PIC32MZ has an L1 (level 1) cache inside to speed up operations by allocating, queueing and managing the data into a special area (which is the L1 cache) before it is processed by the CPU. [In this microcontroller with that architecture, the variables are allocated/declared into the cacheable area by default, unless the “coherent” keyword is specified.]

In that case, this buffer is declared at the non-cacheable area in the PIC32MZ. It means the data in the buffer do not go into the cache during transfer. Therefore, all the contents in the buffer are safely (and directly) transferred to the peripheral without the interference of the contents in the L1 cache during the process.

You give the DMA module the address of your buffer to allow the module to transfer the data out into the SPI module.

DCH0SSA simply means “DMA channel 0 source address”.

Similarly, DCH1SSA simply means “DMA channel 1 source address”.

On PIC32MX/MZ, the “KVA_TO_PA” means “convert virtual address to physical address”. The DMA module only accepts physical address and you need to convert them first before it can do anything meaningful.

Coming back to the DMA, instead of you generating the audio samples per SPI transfer complete interrupt, you do all the activities in the main loop instead.

The activities mentioned can be generating the samples of the sine wave and put a part of them into the buffer. The DMA issues an interrupt if all the contents in the buffer are being transferred.

This interrupt is very less frequent than the continuous SPI transfer complete interrupts too. Look at the “IFS1bits.DMA0IF” and the “DCH0INTbits.CHDDIF” (CHDDIF means Channel Destination Done Interrupt Flag) in the following interrupt code. So when the DMA is done transferring the data, these interrupt flags go up, and you clear them inside the handler. Besides that, you can do some other things when the DMA has finished doing the job – it’ll be discussed afterwards.

In short, the system is configured to assert an interrupt if the DMA channel 0 or DMA channel 1 which have fully completed the transfer (when the DMA channel reads to the end of the array). It is possible to configure the system to assert an interrupt when the transfer is half-complete too but in the tutorial, the interrupt happens when the transfer is entirely complete.

Apart from that, when the system is busy transferring the contents of the buffer to the SPI module, modifying the stuff inside could cause the audio to crackle and pop. Well, with the ample data memory in PIC32s, you can declare another buffer which stores 512 samples (it must be identical to the other buffer). Let’s call the buffers buffer_a and buffer_b:

1.) The DMA channel 0 starts first by pushing the contents of the buffer_a to the SPI module.

2.) Some time passes. Once the interrupt flag for DMA channel 0 has been up, the transfer begins for buffer_b by switching on the DMA channel 1, and buffer_a can be accessed by the user. Buffer_b is now occupied by DMA channel 1 as the contents inside are being transferred to the SPI module.

3.) At the time DMA channel 1 interrupt is up, the transfer begins for buffer_a by switching on the DMA channel 0, and buffer_b can be accessed by the user. Buffer_a is now occupied by DMA channel 1 as the contents inside are being transferred to the SPI module.

This process (2 to 3) repeats over and over again, and it is called “double buffering method” or “ping-pong buffers“.

But first and foremost, the DMA and the SPI module must be initialized. The code is as follows:

After starting all the DMA and SPI module, the program does its job by switching the buffers every time the contents of the buffer are fully transferred.

The simplified program flow is as follows:

At least for now you can have more time for the samples to be processed! There is a lot of things you can do with the samples in that main loop. You will wonder what kind of thing you can actually do on that – check out the next tutorial where you can play a nice tune by combining the I2S and DMA!