# Potentiometer Interrupt

Hi,

I would like to be able to control the amplitude of a sine wave based on the position of a user controlled potentiometer. My system will be measuring the voltage of a couple other analog inputs, and hence I thought a good way to go about doing this was through an interrupt that would detect a change in position on the potentiometer.

I was wondering if this would be a good way to do this, or is there a better way? (This is my first time on the arduino due, and my first time really digging deep into a micro-controllers documentation, so I am not aware of all the nuances)

Note that the code below is not my full project, but simply a file used to test this concept out, additionally, the current code aims to pulse an LED when a threshold is reached (again simply to test the concept out).

My goal of the code below was to trigger a pulse when the pot was above or below a certain bound, and then reset the bound once the previous bound was reached (ex. first bound 0-1000 once 1000 is passed the new bound is between 1000-2000 etc. nothing should happen between the upper and lower bound values)

I have used this form post as a baseline for my code below as well: Analog interrupt - Arduino Due - Arduino Forum

I am using an Arduino Due board.

I hope that makes sense. If not I can try to extrapolate more.

Thanks,
-H

``````/*
Simple Waveform generator with Arduino Due

* connect two push buttons to the digital pins 2 and 3
with a 10 kilohm pulldown resistor to choose the waveform
to send to the DAC0 and DAC1 channels
* connect a 10 kilohm potentiometer to A0 to control the
signal frequency

*/

#include "Waveform.h"

#define oneHzSample 1000000/maxSamplesNum  // sample for the 1Hz signal expressed in microseconds

volatile int wave0 = 0;
int i = 0;
int sample;
int amplitude;
int val;

void setup() {
Serial.begin(9600);
pinMode(DAC0,OUTPUT);
pinMode(DAC1,OUTPUT);

pinMode(A0,INPUT);
pinMode(3,OUTPUT);

analogWriteResolution(12);  // set the analog output resolution to 12 bit (4096 levels)
analogReadResolution(12);   // set the analog input resolution to 12 bit

// attachInterrupt(val, poto, CHANGE);  // Interrupt attached to the button connected to pin 2

pmc_enable_periph_clk(ID_ADC);                                                             // To use peripheral, we must enable clock distributon to it
adc_configure_timing(ADC, 0, ADC_SETTLING_TIME_3, 1); // Set timings - standard values
adc_set_bias_current(ADC, 1);                                                             // Bias current - maximum performance over current consumption
adc_disable_tag(ADC);                                                                     // it has to do with sequencer, not using it
adc_enable_channel(ADC, ADC_CHANNEL_7);                                                   // one channel enabled (pin A0 on arduino due)

NVIC_EnableIRQ (ADC_IRQn) ;                                                     // enable ADC interrupt vector

}

void loop() {
// Read the the potentiometer and map the value  between the maximum and the minimum sample available
// 1 Hz is the minimum freq for the complete wave
// 170 Hz is the maximum freq for the complete wave. Measured considering the loop and the analogRead() time
//sample = map(analogRead(A0), 0, 4095, 0, oneHzSample);
//sample = constrain(t_sample, 0, oneHzSample);

//  amplitude = analogRead(A0);
//
//  if(amplitude < 820){val = 10;}
//  else if (amplitude >= 820 && amplitude < 1640){val = 20;}
//  else if (amplitude >= 1640 && amplitude < 2460){val = 40;}
//  else if (amplitude >= 2460 && amplitude < 3280){val = 60;}
//  else{val = 100;}
//
//
//  Serial.print(val);
//  Serial.print(",  ");
//  Serial.println(amplitude);
//  analogWrite(DAC1,  waveformsTable[wave0][i] / val);  // write the selected waveform on DAC0
//
//
//  i++;
//  if(i == maxSamplesNum)  // Reset the counter to repeat the wave
//    i = 0;
//
//  delayMicroseconds(oneHzSample/60);  // Hold the sample value for the sample time

}

{
int newThres = ADC->ADC_CDR[7];                         //store the adc's read value to use for the next threshold value
REG_PIOC_SODR |= (0x01 << 28);                          //debug led

| ADC_EMR_CMPSEL(7) ;                    //on A0

if(newThres > 3095){newThres = 3095;}                   //define upper bound
else if(newThres < 1000){newThres = 1000;}              //define lower bound

ADC->ADC_CWR = ADC_CWR_HIGHTHRES(newThres + 1000) |  ADC_CWR_LOWTHRES(newThres);  //indicate new threshold value that must be surpassed in order to trigger the interrupt again

REG_PIOC_CODR |= (0x01 << 28);
}
``````

The code you posted is more or less a part of what you need.

I will try to sum up the code flow:

Set ADC_CWR to set a low and a high threshold, set ADC_EMR accordingly, set ADC_IER to trigger an interrupt each time a comparison matches, enable interrupts with NVIC_EnableIRQ(ADC_IRQn).

In the ADC interrupt Handler, update the maximum amplitude of the sine wave, update high and/or low thresholds in ADC_CWR and set a flag to trigger a request to update the sine wave look up table.

To output a sine wave at a constant frequency and variable amplitude, you can use a DAC or a PWM plus an RC filter.

If you choose a DAC, firstly, fill 2 identical Look Up Tables with a sine wave between 0 and 4095 (12-bit DAC). Then program a PDC DMA to output repeatedly the LUTs. While you output a LUT, update the amplitude of the second LUT in background if the flag is set, and so on… To set the sine wave frequency, trigger the DAC conversions with a Timer Counter or a PWM event line. If you have n samples to output a full period sine wave and you need a sine wave frequency of F Hz, program the DAC conversions with a frequency of n*F Hz.

If you choose a PWM, select the synchro mode with an automatic update of duty cycle by a PDC DMA or an AHB DMA plus a list linked item. The rest is identical with the above process.

I see, thank you.

ard_newbie:
If you choose a DAC, firstly, fill 2 identical Look Up Tables with a sine wave between 0 and 4095 (12-bit DAC). Then program a PDC DMA to output repeatedly the LUTs. While you output a LUT, update the amplitude of the second LUT in background if the flag is set, and so on…

Could you clarify the necessity for 2 LUTs? (couldn't you just modify one LUT based on the value from the potentiometer?)

I have been referencing the arduino due simple wave form generator (https://www.arduino.cc/en/Tutorial/DueSimpleWaveformGenerator) and was thinking of doing something along the lines of the following:

``````int i = 0;

volatile int newThres = 0;

double sineWaveMultipler = 4095;

int lowThres = 0;
int highThres = 200;

static int waveformsTable[120] =
// Sin wave
{
//4096
0x7ff, 0x86a, 0x8d5, 0x93f, 0x9a9, 0xa11, 0xa78, 0xadd, 0xb40, 0xba1,
0xbff, 0xc5a, 0xcb2, 0xd08, 0xd59, 0xda7, 0xdf1, 0xe36, 0xe77, 0xeb4,
0xeec, 0xf1f, 0xf4d, 0xf77, 0xf9a, 0xfb9, 0xfd2, 0xfe5, 0xff3, 0xffc,
0xfff, 0xffc, 0xff3, 0xfe5, 0xfd2, 0xfb9, 0xf9a, 0xf77, 0xf4d, 0xf1f,
0xeec, 0xeb4, 0xe77, 0xe36, 0xdf1, 0xda7, 0xd59, 0xd08, 0xcb2, 0xc5a,
0xbff, 0xba1, 0xb40, 0xadd, 0xa78, 0xa11, 0x9a9, 0x93f, 0x8d5, 0x86a,
0x7ff, 0x794, 0x729, 0x6bf, 0x655, 0x5ed, 0x586, 0x521, 0x4be, 0x45d,
0x3ff, 0x3a4, 0x34c, 0x2f6, 0x2a5, 0x257, 0x20d, 0x1c8, 0x187, 0x14a,
0x112, 0xdf, 0xb1, 0x87, 0x64, 0x45, 0x2c, 0x19, 0xb, 0x2,
0x0, 0x2, 0xb, 0x19, 0x2c, 0x45, 0x64, 0x87, 0xb1, 0xdf,
0x112, 0x14a, 0x187, 0x1c8, 0x20d, 0x257, 0x2a5, 0x2f6, 0x34c, 0x3a4,
0x3ff, 0x45d, 0x4be, 0x521, 0x586, 0x5ed, 0x655, 0x6bf, 0x729, 0x794
};

void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(A0,INPUT);
pinMode(3,OUTPUT);

analogWriteResolution(12);  // set the analog output resolution to 12 bit (4096 levels)
analogReadResolution(12);   // set the analog input resolution to 12 bit

//15ms Timer Setup (for reading the actuator line)
pmc_set_writeprotect(false);
pmc_enable_periph_clk(ID_TC3);

TC_Configure(TC1, 0, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK3);
TC_SetRC(TC1, 0, 365);                                                                    //call interrupt every .135ms for a 60hz sine wave
//TC_Start(TC1, 0);                                                                          //Timer counter started later

TC1->TC_CHANNEL[0].TC_IER=TC_IER_CPCS;                                                      // IER = interrupt enable register
TC1->TC_CHANNEL[0].TC_IDR=~TC_IER_CPCS;                                                     // IDR = interrupt disable register

NVIC_EnableIRQ(TC3_IRQn);                                                                   //enable interrupt

//Dac setup
pmc_enable_periph_clk(DACC_INTERFACE_ID);            // start clocking DAC
DACC->DACC_CR = DACC_CR_SWRST;                       // reset DAC

DACC->DACC_MR =  DACC_MR_TRGEN_DIS;
DACC->DACC_MR = (1 << DACC_MR_USER_SEL_Pos);                       // select channel 1

DACC->DACC_IDR = 0xFFFFFFFF;                         // no interrupts
//DACC->DACC_CHER = DACC_CHER_CH0 << 0;                // enable chan0
DACC->DACC_CHER = DACC_CHER_CH1 << 0;                // enable chan1

pmc_enable_periph_clk(ID_ADC);                                                             // To use peripheral, we must enable clock distributon to it
adc_configure_timing(ADC, 0, ADC_SETTLING_TIME_3, 1); // Set timings - standard values
adc_set_bias_current(ADC, 1);                                                             // Bias current - maximum performance over current consumption
adc_disable_tag(ADC);                                                                     // it has to do with sequencer, not using it
adc_enable_channel(ADC, ADC_CHANNEL_7);                                                   // one channel enabled (pin A0 on arduino due)

ADC->ADC_EMR = ADC_EMR_CMPMODE_OUT                                                      //set to compare with a window on channel 7 (A0)

NVIC_EnableIRQ (ADC_IRQn) ;                                                     // enable ADC interrupt vector

TC_Start(TC1, 0);
}

void loop() {
// put your main code here, to run repeatedly:

//Serial.println(newThres);

// while(DACC_IER_EOC == 0){}

}

{
newThres = ADC->ADC_CDR[7];                         //store the adc's read value to use for the next threshold value

lowThres = newThres - 200;                          //set low thres val
highThres = newThres + 200;                         //set high thres val

//multipler values based on potentiometer values
if(newThres <= 1000){sineWaveMultipler = .25;}
else if(newThres >= 1000 && newThres < 2000){sineWaveMultipler = .5;}
else if(newThres >= 2000 && newThres < 3000){sineWaveMultipler = .75;}
else {sineWaveMultipler = 1;}
//sineWaveMultipler = newThres;

//boundary cases
if(newThres < 200){lowThres = 0;}
if(newThres > 3895){highThres = 4095;}

ADC->ADC_CWR = ADC_CWR_HIGHTHRES(highThres) |  ADC_CWR_LOWTHRES(lowThres);  //indicate new threshold value that must be surpassed in order to trigger the interrupt again

}

void TC3_Handler()
{
TC1->TC_CHANNEL[0].TC_SR;                                                //check status of interupt

REG_PIOC_SODR |= (0x01 << 28);                          //debug led

if(i > 119){i = 0;}
DACC->DACC_CDR = waveformsTable[i] * sineWaveMultipler;     //set the dac
i++;

REG_PIOC_CODR |= (0x01 << 28);                    //debug led
}
``````

I understand that I would have to add the timer you mentioned early to get a desired frequency.

Basically the potentiometer interrupt sets a multiplier that modifies a waveform table filled with values.

Is there any reason why this would be a bad idea, or why what you mentioned would be better?

EDIT: Added Timer code and took wave form measurements

The amplitude change was done via movement on the potentiometer, and the first channel is an LED that fires every time the interrupt for the 60hz timer is triggered

At least 2 LUT buffers are necessary when you output DAC conversions through a PDC DMA. While one LUT is leveraged by the DMA, another one in background is updated in loop(). Thre is an example sketch of DAC conversions with a PDMA here, reply #10:

https://forum.arduino.cc/index.php?topic=224672.0

Thanks, I'll give that a read.

Not sure if this would warrant a new forum post but:

would using the DMA (with a circular buffer?) in conjunction with the DAC allow for a parallel operations? (in the sense that the DAC is continuously operating while the processor is doing other things)

Reply #4 hints at this here, but I can't find anything concrete in other forum posts/the data sheet

"The DAC can use the DMA direct memory access to play out a segment of memory at a defined rate, with no impact on the running speed of the main sketch."
https://forum.arduino.cc/index.php?topic=527853.0

EDIT:
I did also find this post: Using ADC with DMA - Arduino Zero - Arduino Forum, however, it is with relation to the Arduino Zero.

would using the DMA (with a circular buffer?) in conjunction with the DAC allow for a parallel operations?

Yes it does.

Page 503, Sam3x datasheet:

Using the PDC removes processor overhead by reducing its intervention during the transfer. This significantly reduces the number of clock cycles required for a data transfer, which improves microcontroller performance.

Took a look at the code you provided in the previous reply, and tried to implement something.

what I didn't understand is why the DAC stopped operating when an interrupt was fired. I thought that since the DMA was supplying data to the DAC it would function in parallel with other processes. (or have I just missed something coding wise?)

The attach image is a scope shot of what I am referring to (the interrupt runs for the duration of the high pulse).

Is it not possible to simultaneously operate the DAC (with the DMA) and do a separate operation (ex. read an adc channel) within an Interrupt?

``````volatile uint8_t bufn, Oldbufn, bufn_dac;
const uint16_t bufsize = 128;
const uint8_t bufnumber = 2;
const uint8_t _bufnumber = bufnumber - 1;
volatile uint16_t buf[bufnumber][bufsize] =
{
{//DATA (included in attachement)},
{//DATA (included in attachment)}

};

void setup()
{

pinMode(13,OUTPUT);                                                                       //DEBUG
pinMode(12,OUTPUT);                                                                       //DEBUG
pinMode(10,OUTPUT);                                                                       //DEBUG
pinMode(9,OUTPUT);                                                                        //DEBUG
pinMode(8,OUTPUT);                                                                        //DEBUG
pinMode(7,OUTPUT);                                                                        //DEBUG
pinMode(4,OUTPUT);                                                                        //DEBUG

pmc_set_writeprotect(false);
pmc_enable_periph_clk(ID_TC4);

TC_Configure(TC1, 1, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK2);
TC_SetRC(TC1, 1, 47250);
TC_Start(TC1, 1);                                                                          //Timer counter started later

TC1->TC_CHANNEL[1].TC_IER=TC_IER_CPCS;                                                      // IER = interrupt enable register
TC1->TC_CHANNEL[1].TC_IDR=~TC_IER_CPCS;                                                     // IDR = interrupt disable register

NVIC_EnableIRQ(TC4_IRQn);

dac_setup();

}

void loop()
{

REG_PIOB_SODR |= (0x01 << 27);
REG_PIOB_CODR |= (0x01 << 27);
}

//************************************Handlers***************************************************\\
//Timer Interupt handler
void TC4_Handler()
{
TC1->TC_CHANNEL[1].TC_SR;                                                //check status of interupt

REG_PIOC_SODR |= (0x01 << 22); // Turn on the LED
for(int i = 0; i < 512; i++)
{
while ((ADC->ADC_ISR & 0x40)== 0){}                                //Wait for end of conversion on channel 6 (or input A1 on arduino due)
ADC->ADC_CDR[6]; //get data from channel 6 and add it to the total

}
REG_PIOC_CODR |= (0x01 << 22); // Turn off the LED using the CODR register

}

void DACC_Handler() {   // DACC_ISR_HANDLER()         // move Sinus/PDC/DMA pointers to next buffer

//if ( DACC->DACC_ISR & DACC_ISR_ENDTX) {           // Useless because the only one

bufn_dac = (bufn_dac + 1) & _bufnumber;
DACC->DACC_TNPR = (uint32_t)buf[bufn_dac];
DACC->DACC_TNCR = bufsize;

}

//************************************SETUP FUNCTIONS***************************************************\\
{
pmc_enable_periph_clk(ID_ADC);                                                             // To use peripheral, we must enable clock distributon to it
adc_configure_timing(ADC, 0, ADC_SETTLING_TIME_3, 1); // Set timings - standard values
adc_set_bias_current(ADC, 1);                                                             // Bias current - maximum performance over current consumption
adc_disable_tag(ADC);                                                                     // it has to do with sequencer, not using it
adc_enable_channel(ADC, ADC_CHANNEL_6);                                                   // one channel enabled (pin A1 on arduino due)

}

void dac_setup()
{
pmc_enable_periph_clk(DACC_INTERFACE_ID);            // start clocking DAC
DACC->DACC_CR = DACC_CR_SWRST;                       // reset DAC

DACC->DACC_MR =  DACC_MR_TRGEN_DIS;
DACC->DACC_MR = (1 << DACC_MR_USER_SEL_Pos);         // select channel 1

DACC->DACC_IDR = 0xFFFFFFFF;                         // no interrupts
//DACC->DACC_CHER = DACC_CHER_CH0 << 0;               // enable chan0
DACC->DACC_CHER = DACC_CHER_CH1 << 0;                // enable chan1

DACC->DACC_IDR = ~DACC_IDR_ENDTX;
DACC->DACC_IER = DACC_IER_ENDTX;                    // TXBUFE works too !!!
//NVIC_SetPriority(DACC_IRQn, 0xFF);
NVIC_EnableIRQ(DACC_IRQn);
DACC->DACC_CHER = DACC_CHER_CH0 | DACC_CHER_CH1;    // enable channels 1 = DAC1 and 0 = DAC0

/*************   configure PDC/DMA  for DAC *******************/
DACC->DACC_TPR  = (uint32_t)buf[0];                 // DMA buffer
DACC->DACC_TCR  = bufsize;
DACC->DACC_TNPR = (uint32_t)buf[1];                 // next DMA buffer
DACC->DACC_TNCR =  bufsize;
bufn_dac = 1;
DACC->DACC_PTCR = DACC_PTCR_TXTEN;                  // Enable PDC Transmit channel request
}
//END SETUP FUNCTIONS
``````

Apologizes for having to attach the full code separately...I exceed 9000 characters.

EDIT: After further looking at the code I think I understand what is going on...Correct me if I am wrong, but the DAC keeps operating until it has reached the end of the buffer (regardless of if an interrupt has fired or not), however, if an interrupt has fired and the end of the buffer is reached during the interrupt, the system must wait till the interrupt finishes before sending the address of the next buffer. (and hence the DAC outputs nothing during that time).

Is it possible to code this in such a way where the DMA is always supplied with the next buffer address? (a way that could perhaps be coded once in the setup?)