Go Down

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

EpicFoodCartDestroyer

Jul 02, 2018, 11:01 pm Last 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


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 pm Last 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


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 pm Last 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


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 pm Last 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