Go Down

### Topic: Potentiometer Interrupt  (Read 915 times)previous topic - next topic

#### EpicFoodCartDestroyer

##### Jul 02, 2018, 11:01 pmLast Edit: Jul 02, 2018, 11:38 pm by EpicFoodCartDestroyer
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: https://forum.arduino.cc/index.php?topic=482205.0

I am using an Arduino Due board.

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

Thanks,
-H

Code: [Select]
`/*  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_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST);                            // initialize, set maximum posibble speed      adc_disable_interrupt(ADC, 0xFFFFFFFF);      adc_set_resolution(ADC, ADC_12_BITS);      adc_configure_power_save(ADC, 0, 0);                                                      // Disable sleep      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_stop_sequencer(ADC);                                                                  // not using it      adc_disable_tag(ADC);                                                                     // it has to do with sequencer, not using it      adc_disable_ts(ADC);                                                                      // disable temperature sensor      adc_disable_channel_differential_input(ADC, ADC_CHANNEL_7);      adc_configure_trigger(ADC, ADC_TRIG_SW, 1);                                               // triggering from software, freerunning mode      adc_disable_all_channel(ADC);      adc_enable_channel(ADC, ADC_CHANNEL_7);                                                   // one channel enabled (pin A0 on arduino due)                  ADC->ADC_EMR = ADC_EMR_CMPMODE_OUT                   | ADC_EMR_CMPSEL(7) ;                                          ADC->ADC_CWR = ADC_CWR_HIGHTHRES(1000) |  ADC_CWR_LOWTHRES(0);                         // Compare with a window             ADC->ADC_IER=ADC_IER_COMPE;                                                      // IER = interrupt enable register      ADC->ADC_IDR=~ADC_IER_COMPE;                                                    // IDR = interrupt disable register            NVIC_EnableIRQ (ADC_IRQn) ;                                                     // enable ADC interrupt vector          adc_start(ADC);}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   }void ADC_Handler (void){  ADC->ADC_ISR;                                           // Read and clear status register  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->ADC_EMR = ADC_EMR_CMPMODE_OUT                      //set mode to window threshold                                                           | 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);                                     }`

#### ard_newbie

#1
##### Jul 03, 2018, 05:53 am

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.

#### EpicFoodCartDestroyer

#2
##### Jul 09, 2018, 09:19 pmLast Edit: Jul 09, 2018, 10:26 pm by EpicFoodCartDestroyer Reason: added images
I see, thank you.

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:

Code: [Select]
`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  //adc setup      pmc_enable_periph_clk(ID_ADC);                                                             // To use peripheral, we must enable clock distributon to it      adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST);                            // initialize, set maximum posibble speed      adc_disable_interrupt(ADC, 0xFFFFFFFF);      adc_set_resolution(ADC, ADC_12_BITS);      adc_configure_power_save(ADC, 0, 0);                                                      // Disable sleep      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_stop_sequencer(ADC);                                                                  // not using it      adc_disable_tag(ADC);                                                                     // it has to do with sequencer, not using it      adc_disable_ts(ADC);                                                                      // disable temperature sensor      adc_disable_channel_differential_input(ADC, ADC_CHANNEL_7);      adc_configure_trigger(ADC, ADC_TRIG_SW, 1);                                               // triggering from software, freerunning mode      adc_disable_all_channel(ADC);      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)                   | ADC_EMR_CMPSEL(7);                   //| ADC_EMR_CMPFILTER(2);                                          ADC->ADC_CWR = ADC_CWR_HIGHTHRES(highThres) |  ADC_CWR_LOWTHRES(lowThres);                       // Compare with a window             ADC->ADC_IER=ADC_IER_COMPE;                                                      // IER = interrupt enable register      ADC->ADC_IDR=~ADC_IER_COMPE;                                                    // IDR = interrupt disable register            NVIC_EnableIRQ (ADC_IRQn) ;                                                     // enable ADC interrupt vector          adc_start(ADC);      TC_Start(TC1, 0);}void loop() {  // put your main code here, to run repeatedly:  //Serial.println(newThres);       // while(DACC_IER_EOC == 0){}  }void ADC_Handler (void){  ADC->ADC_ISR;                                           // Read and clear status register  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

#### ard_newbie

#3
##### Jul 10, 2018, 05:19 am

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

#### EpicFoodCartDestroyer

#4
##### Jul 10, 2018, 11:56 pmLast Edit: Jul 11, 2018, 12:08 am by EpicFoodCartDestroyer
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: https://forum.arduino.cc/index.php?topic=518461.0, however, it is with relation to the Arduino Zero.

#### ard_newbie

#5
##### Jul 11, 2018, 06:06 am

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.

#### EpicFoodCartDestroyer

#6
##### Jul 11, 2018, 09:47 pmLast Edit: Jul 11, 2018, 11:51 pm by EpicFoodCartDestroyer
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?

Code: [Select]
`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();      adc_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***************************************************\\void adc_setup(){   //ADC   pmc_enable_periph_clk(ID_ADC);                                                             // To use peripheral, we must enable clock distributon to it   adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST);                            // initialize, set maximum posibble speed   adc_disable_interrupt(ADC, 0xFFFFFFFF);   adc_set_resolution(ADC, ADC_12_BITS);   adc_configure_power_save(ADC, 0, 0);                                                      // Disable sleep   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_stop_sequencer(ADC);                                                                  // not using it   adc_disable_tag(ADC);                                                                     // it has to do with sequencer, not using it   adc_disable_ts(ADC);                                                                      // disable temperature sensor   adc_disable_channel_differential_input(ADC, ADC_CHANNEL_6);     adc_configure_trigger(ADC, ADC_TRIG_SW, 1);                                               // triggering from software, freerunning mode   adc_disable_all_channel(ADC);     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?)

Go Up