Hi everybody,
I'm working on a small synth and I was very disappointed when I discover how noisy DACs are.
So I've tried PWM method and I get better results.
For inspiration source I use two main codes:
-
a fast 1MHz PWM code (post#8):
PWM on DUE using registers - Arduino Due - Arduino Forum -
ISR for real time synthesis:
RCArduino: Quick And Dirty Synth For Arduino Due -
and an article about how to expand resolution with several PWM outputs:
Resistor/PWM DAC
My test code for a sine wave generator looks like this:
// It 's a fact Arduino Due's DACs are very noisy, this code is another approach to obtain good results by using audio PWM method.
// You can find here a simple sine wave generator. Audio is 14 bit encoded. It could be a good start to make real time audio synthesis.
// 2 pins are used for a mono audio out (pins 34 and 36, while pins 35 and 37 are copy). Each pin provides a 656Khz / 7 bits PWM encoded audio.
// pin 34 provides 7 Most Significant Bits and pin 36 provides 7 Least Significant Bits for a 14bit range.
// PWMs have to be filtered and sum to create analog audio mono out.
static int sineWaveform[256] = {
// 256 samples / 14 bits Sine wave. It is a non-optimized example.
8192 , 8393 , 8593 , 8794 , 8994 , 9194 , 9394 , 9592 , 9790 , 9986 , 10182 , 10376 , 10570 , 10761 , 10951 , 11140 , 11326 , 11511 , 11694 , 11875 , 12053 , 12229 , 12403 , 12574 , 12743 , 12909 , 13071 , 13231 , 13388 , 13542 , 13693 , 13840 , 13984 , 14125 , 14261 , 14395 , 14524 , 14650 , 14771 , 14889 , 15003 , 15113 , 15218 , 15319 , 15416 , 15509 , 15597 , 15681 , 15760 , 15835 , 15905 , 15970 , 16031 , 16087 , 16138 , 16184 , 16226 , 16263 , 16295 , 16322 , 16344 , 16361 , 16374 , 16381 , 16383 , 16381 , 16374 , 16361 , 16344 , 16322 , 16295 , 16263 , 16226 , 16184 , 16138 , 16087 , 16031 , 15970 , 15905 , 15835 , 15760 , 15681 , 15597 , 15509 , 15416 , 15319 , 15218 , 15113 , 15003 , 14889 , 14771 , 14650 , 14524 , 14395 , 14261 , 14125 , 13984 , 13840 , 13693 , 13542 , 13388 , 13231 , 13071 , 12909 , 12743 , 12574 , 12403 , 12229 , 12053 , 11875 , 11694 , 11511 , 11326 , 11140 , 10951 , 10761 , 10570 , 10376 , 10182 , 9986 , 9790 , 9592 , 9394 , 9194 , 8994 , 8794 , 8593 , 8393 , 8192 , 7990 , 7790 , 7589 , 7389 , 7189 , 6989 , 6791 , 6593 , 6397 , 6201 , 6007 , 5813 , 5622 , 5432 , 5243 , 5057 , 4872 , 4689 , 4508 , 4330 , 4154 , 3980 , 3809 , 3640 , 3474 , 3312 , 3152 , 2995 , 2841 , 2690 , 2543 , 2399 , 2258 , 2122 , 1988 , 1859 , 1733 , 1612 , 1494 , 1380 , 1270 , 1165 , 1064 , 967 , 874 , 786 , 702 , 623 , 548 , 478 , 413 , 352 , 296 , 245 , 199 , 157 , 120 , 88 , 61 , 39 , 22 , 9 , 2 , 0 , 2 , 9 , 22 , 39 , 61 , 88 , 120 , 157 , 199 , 245 , 296 , 352 , 413 , 478 , 548 , 623 , 702 , 786 , 874 , 967 , 1064 , 1165 , 1270 , 1380 , 1494 , 1612 , 1733 , 1859 , 1988 , 2122 , 2258 , 2399 , 2543 , 2690 , 2841 , 2995 , 3152 , 3312 , 3474 , 3640 , 3809 , 3980 , 4154 , 4330 , 4508 , 4689 , 4872 , 5057 , 5243 , 5432 , 5622 , 5813 , 6007 , 6201 , 6397 , 6593 , 6791 , 6989 , 7189 , 7389 , 7589 , 7790 , 7990
};
void setup() {
pwmc_setup();
isr_setup();
}
void isr_setup() // set ISR for real time audio synthesis
{
/* turn on the timer clock in the power management controller */
pmc_set_writeprotect(false);
pmc_enable_periph_clk(ID_TC4);
/* we want wavesel 01 with RC */
TC_Configure(/* clock */TC1,/* channel */1, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK2);
TC_SetRC(TC1, 1, 238); // sets <> 44.1 Khz interrupt rate
TC_Start(TC1, 1);
// enable timer interrupts on the timer
TC1->TC_CHANNEL[1].TC_IER = TC_IER_CPCS;
TC1->TC_CHANNEL[1].TC_IDR = ~TC_IER_CPCS;
/* Enable the interrupt in the nested vector interrupt controller */
/* TC4_IRQn where 4 is the timer number * timer channels (3) + the channel number (=(1*3)+1) for timer1 channel1 */
NVIC_EnableIRQ(TC4_IRQn);
}
void pwmc_setup() // Set PWM channels ( 656KHz )
{
//Configure PWM channels 0,1,2,3 (note I've deleted ch2 and ch3 commands from this section)(PWML0,PWMH0,PWML1,PWMH1,PWML2,PWMH2,PWML3,PWMH3), (port C.2,C.3,C.4,C.5,C.6,C.7,C.8,C.9), (pins P34,P35,P36,P37,P38,P39,P40,P41)
REG_PIOC_PDR = 0x3FC; //B1111111100, PIO Disable Register
REG_PIOC_ABSR = REG_PIOC_ABSR | 0x3FCu; //B1111111100, Peripheral AB Select Register
REG_PMC_PCER1 = REG_PMC_PCER1 | 16; //Peripheral Clock Enable Register 1 (activate clock for PWM, id36, bit5 of PMC_PCSR1)
REG_PWM_ENA = REG_PWM_SR | B0011; //PWM Enable Register | PWM Status Register (activate channels 0,1,not2,not3)
// Use 0x100000 to enable dead time
REG_PWM_CMR0 = 0x00000; //Channe0 Mode Register: Dead Time Enable DTE=1
REG_PWM_CMR1 = 0x00000; //Channe1 Mode Register: Dead Time Enable DTE=1
// available range is same as duty and depends on PWM frequency (see datasheet)
REG_PWM_DT0 = 0x000000; //Channe0 Dead Time Register for pins 34,35 (PWML0,PWMH0)
REG_PWM_DT1 = 0x000000; //Channe1 Dead Time Register for pins 36,37 (PWML1,PWMH1)
REG_PWM_CPRD0 = 128; //Channe0 Period Register (84mhz/128 = 656250 Hz ) note 7 bits: 2^7 = 128
REG_PWM_CPRD1 = 128; //Channe1 Period Register (84mhz/128 = 656250 Hz) note 7 bits: 2^7 = 128
}
void loop() {
// place your code here
}
void TC4_Handler() //ISR is useful to process real time synthesis
{
// We need to get the status to clear it and allow the interrupt to fire again
TC_GetStatus(TC1, 1);
static byte myWavePointer;
myWavePointer++;
int audioBridge = (sineWaveform[(myWavePointer)]) ; // read 14bit sine waveform
// here you can process audio synthesis by making operations on audioBridge
// prepare audio to set PWM duty cycle:
int myDutyCycleMSB = audioBridge >> 7; // channel 0 : 7 most significant bits
int myDutyCycleLSB = audioBridge - (myDutyCycleMSB << 7); // channel 1 : 7 less significant bits
pwmc_duty(myDutyCycleMSB, myDutyCycleLSB);
}
//Set the PWM duty-cycle
inline void pwmc_duty(unsigned short myDutyCycleMSB, unsigned short myDutyCycleLSB) {
REG_PWM_CDTY0 = myDutyCycleMSB;
REG_PWM_CDTY1 = myDutyCycleLSB;
}
// It 's a fact Arduino Due's DACs are very noisy This code is another approach to obtain good results by using audio PWM method.
// You can find here a simple sine wave generator. Audio is 14 bit encoded. It could be a good start to make real time audio synthesis.
// 2 pins are used for a mono audio out (pins 34 and 36, while pins 35 and 37 are copy).
// Each pin provides a 656Khz / 7 bits PWM encoded audio.
// pin 34 provides 7 Most Significant Bits and pin 36 provides 7 Least Significant Bits for a 14bits range.
// PWMs have to be filtered and sum to create analog audio mono out.
It works great but to be honest I'm beginner and do not understand PWM part, especially how PWM is assigned to pins. I've search words like "PIO, PIOC, ABSR, PMC, PCER, LASER SABER, PWML0, PDR..." in datasheet but this document seems to have been written by Klingon or Egyptian back from the past.
For my synth project I just need a mono audio out , which requires 2 pins only (one pin for MSB and one pin for LSB) while the PWM code I use is written for 8 PWM outputs. I wonder how this can be optimized.
In another discussion dlloys member told about "synchronous channel mode"as a possible answer.
Another bad thing: I have already make my synth PCB with 16 buttons, 32 leds, 12 potentiometers , a MIDI port...and pins 34/36 and 35/37 are busy, it will not be easy to desolder it.
So I wonder If i can address PWM to other pins (pin12 and 13 would be perfect).
Thanks.