Square wave generator

Hi,

I need to build a square wave generator with a frequency ranging from 0 to 1KHz in steps of 0.25Hz.

Ive looked at the tone library but due to the 8bit timer used it wont go below 31Hz. It does go above 1KHz tough.
I am wondering if it would be possible to reduce the maximun frequency to archieve a lower minimun frequency. Would this be possible? If not what other alternatives should I look at?

Regards

Can't you just do it using micros() for timing and digitalWrite() ?

...R

Not digitalWrite, write to the Input register instead.

PIND = 0b00000100; // toggle D2 on an Uno.

Mix with blink without delay coding using micros()

void loop(){
currentMicros = micros();
elapsedMicros = currentMicros - previousMicros; // all time related elements are unsigned long
  if (elapsedMicros >= duration){
  previousMicros = previpousMicros + duration; // set up for next toggle time
  PIND = 0b00000100; // toggle D2 on an Uno.
  }
// add code to adjust duration
}

Then decide how you will change duration

CrossRoads:
Not digitalWrite, write to the Input register instead.

I figured I should start with something simple.

...R

Robin2:

CrossRoads:
Not digitalWrite, write to the Input register instead.

I figured I should start with something simple.

...R

Thanks Robin.
I find it much easier using direct port manipulation. Its on the datasheet, unlike multiple arduino functions I have to remember :wink:

This is my code:

//Variable to hold time
unsigned long previousmicros = 0;

//Dumy variable to set max frequency
const long interval = 167;         

void setup()
{
  //Set PORTD2 as output
  DDRD= B11111101;
}

void loop()
{
    
  //Run the loop, to togle the pin. Set interval variable. 
  unsigned long currentmicros = micros();
 
  if(currentmicros - previousmicros >= interval) 
      {
        // save the last time you blinked the LED 
        previousmicros = currentmicros;   
        //toggle the output pin
        PIND = 0b00000100; 
      }
}

This runs at 2.97KHz out of 3KHz expected. Not a great deal

However when I add the routine to read the ADC's

//Variable to hold time
unsigned long previousmicros = 0;

//Dumy variable to set max frequency
const long interval = 167;         

void setup()
{
  //Set PORTD2 as output
  DDRD= B11111101;
}

void loop()
{
  
  //Read both POT's 8 MSB's
  int coarse = analogRead(A0) >> 2;
  int fine = analogRead(A1) >> 2;
  
  //Convert into a 16bit word
  unsigned int pot = ((coarse * 256) + fine);
  
  //Run the loop, to togle the pin. Set interval variable. 
  unsigned long currentmicros = micros();
 
  if(currentmicros - previousmicros >= interval) 
      {
        // save the last time you blinked the LED 
        previousmicros = currentmicros;   
        //toggle the output pin
        PIND = 0b00000100; 
      }
}

The code slows right down to 2.15KHz.
I can re-write the ADC to use DPM, since I could set the register to return the 8MSB instead of reading 16 bits for the 10bit register and shift them, but again I am afraid the ADC read time would slow things down.

Any sugestion?

How's performance if you stay within your requirements?

I need to build a square wave generator with a frequency ranging from 0 to 1KHz

analogRead takes a while in the grand scheme of things, especially since you're using micros timing. analogRead() takes over 100 miccroseconds to complete. That can easily cause you to miss a rollover, and introduce a lot of jitter into your signal. Instead of reading it every loop, why not read the pots every 100 ms?That'll be fast enough for a human-set value.

You could use Timer 1 in 16-bit CTC mode for your square wave generation. With max prescaler and max Output Compare Register, you could get down to about 0.1Hz as the slowest output. You could implement additional frequency division in software if you needed to get lower than that. This would allow you to just set and forget the square wave generation, and not have to micromanage it.

I wonder if this line is the problem

previousmicros = currentmicros;

Try changing it to

previousmicros += interval;

For an explanation you can enjoy reading this long Thread.

...R

CrossRoads:
How's performance if you stay within your requirements?

I need to build a square wave generator with a frequency ranging from 0 to 1KHz

Using an interval of 500uS I get 994.00xxHz.
Enabling the ADC's (Just enabling I'm not doing anything with the result yet) I get 717.37Hz.

Robin2:
Try changing it to

previousmicros += interval;

...R

Brilliant sugestion. It most certainly helps at lower frequencies

999.1xHz versus 999.0xHz -> edit: tested true up to 2KHz

Increasing to 3Khz the problem returns
2.99KHz, versus 2.152Khz

Im curious on how to solve this, just for the sake of good programming practice

I think analogRead() is slow and you probably don't need to do it in every iteration of loop().

At the very least I would try changing the code so the different analog pins are read in different iterations of loop - i.e. only one each time.

But I suspect it would be even better just to read each one at slightly staggered timed intervals - perhaps only once every 100 millisecs.

If you really do need to read both of them close together I would still try to reduce the number of times you do it.

And maybe you could synchronize the analogRead()s so they occur immediately after a pulse is started so they have minimal effect on timing.

Sorry, this is a bit disjointed due to one thought giving rise to the next.

...R

Robin,

I am setting the ADC to run in free running mode, as per atmel datasheet.
Basically the ADC triggers an interrupt when the reading is complete.
I can also left justify the result, so I only get the relevant 8 bits instead of reading 16 bits and shifting. In this case the 8MSB are stored on the ADCH register. The remaining 2 bits on the ADCL are simply ignored

I think the analogread should have an option to set the resolution. This is true for the due, where I can set 10 or 12bits!

Currently i am facing some timing issues with the reading as when i set to read ADC0 I get the older result, in this case ADC1.
But even if I set the channel to start a conversion manually, I can take advantage of the interrupt, instead of standing and waiting for the result. instead the old value is used and when the interrupt is triggered I update the variable for that channel and start start reading the next and so on. Basically the loop never gets stuck waiting for the ADC result.

casemod:
I am setting the ADC to run in free running mode, as per atmel datasheet.
Basically the ADC triggers an interrupt when the reading is complete.

I don't see any interrupt code for the ADC to trigger.

As far as I know you wouldn't use analogRead() if the ADC is in free-running mode. In your ISR you would just read the result from the appropriate register.

I am confused.

Maybe you haven't posted your complete sketch?

What is the ADC measuring?

...R

Robin2:
I don't see any interrupt code for the ADC to trigger.

As far as I know you wouldn't use analogRead() if the ADC is in free-running mode. In your ISR you would just read the result from the appropriate register.

I am confused.

Maybe you haven't posted your complete sketch?

What is the ADC measuring?

...R

casemod:
I am setting the ADC to run in free running mode, as per atmel datasheet.

Yes, I am, but I havent posted the code yet :wink:

This is the code, but its not working. Basically it samples first thing when the arduino comes from a reset and then the S/H capacitor just discharges. Seems that the multiplexer goes open (Im working on it, sampling a single channel works fine).

int chan;
//Set how many analog inputs to read, starting from A0
int ADC_CHANNELS=5;
//Create a N position array to store the result of the ADC conversions
int myvar[5];

void setup()
{
  Serial.begin(115200);      //begin Serial comm
  
  ADMUX |= (1 << REFS0);     // Set ADC reference to AVCC
  ADMUX |= (1 << ADLAR);     // Left Adjust the result 
  ADCSRA |= (1 << ADEN);     // Enable ADC
  ADCSRA |= (1 << ADIE);     // Enable ADC Interrupt
  sei();                     // Enable Global Interrupts
  ADCSRA |= (1 << ADSC);     // Start A2D Conversions  
}

void loop()
{
  
  Serial.print(myvar[chan]);
  Serial.print("  ");
  Serial.print("channel");
  Serial.println(chan);
 

ISR(ADC_vect)            //ADC interrupt
{
 //Count up to n channels
 if (++chan >= ADC_CHANNELS)
      {
      chan=0;
      } 
  myvar[chan] = ADCH;       //Read ADCL into the current channel
 
  ADMUX|=chan;              // Select the Analog Channel
  ADCSRA |= 0x40;           // Start the AD conversion
  ADCSRA |= (1 << ADSC);    // Start A2D Conversions
}
247  channel4
179  channel3
179  channel1
179  channel0
179  channel3
178  channel1
178  channel2
177  channel4
176  channel0
175  channel1
175  channel2
174  channel4
173  channel0
172  channel1
171  channel2
171  channel4
170  channel0
169  channel1

... All the way down to zero!

Can you describe in english what your code is trying to achieve because I can't make sense of it without spending far too much time on it.

You said earlier that you are setting the ADC to operate in free-running mode but from what I read in the 328 datasheet that requires clearing bits in the ADCSRB register - yet that is not mentioned in your code.

Then you have a variable called chan whose value seems to change in every interrupt - what is it for?

And, more generally, as I already asked, what is the ADC measuring?

...R

Robin2:
Can you describe in english what your code is trying to achieve because I can't make sense of it without spending far too much time on it.

I figured that out the hard way :wink:

Robin2:
You said earlier that you are setting the ADC to operate in free-running mode but from what I read in the 328 datasheet that requires clearing bits in the ADCSRB register - yet that is not mentioned in your code.

I was. Aparently the free running mode is only suitable for one channel. Long story:

In Free Running mode, always select the channel before starting the first conversion. The channel selection may be changed one ADC clock cycle after writing one to ADSC. However, the simplest method is to wait for the first conversion to complete, and then change the channel selection. Since the next conversion has already started automatically, the next result will reflect the previous channel selection. Subsequent conversions will reflect the new channel selection.

Source http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=100706&view=previous

I had experience using a DSPIC33F series, wich has 4 independednt ADC's and hence this is not an issue.
The readings are done in parallel, not sequentially.

Robin2:
Then you have a variable called chan whose value seems to change in every interrupt - what is it for?

Since I have to do the reading sequentially i initiate a variable to count the number of ADC's. A loop sets the ADC, selects the right channel on the multiplexer and the remaining code is executed. Once the ADC reading is complete an interrupt is generated, the counter is incremented to sample the next channel and the i variable is used to update an array with the channels to sample. once the variable exceeds the number of ADC's to sample a loop resets it back to zero and the whole cycle begins again.

Robin2:
And, more generally, as I already asked, what is the ADC measuring?

As with many of my projects the intent is to see how far I can optimize the arduino code for whatever it might take. Sampling 6 analog channels in a row consumes a LOT of time.
On this particular case it is to make a rudimentar square wave signal generator (this topic, right) but i certainly will have better uses for this in the future. Details of the ADC problems here: Potentiometer - Fine/course resolution - #15 by casemod - Project Guidance - Arduino Forum

The code (Working):

//---------------------------------------------------
/*CSilva 2014
ADC using using direct port access and 
Interrupt based sampling
Manual settings for ADC clock and Resolution
Results  stored in an array and easily accessible by the main loop
Array bits printed on serial port
AN0;AN1.AN2,AN3,AN4,AN5*/
//---------------------------------------------------

int i;
//Set how many analog inputs to read, starting from A0
const int ADC_CHANNELS = 6;
//Create a N position array to store the result of the ADC conversions
int myvar[ADC_CHANNELS];

void setup()
{
  Serial.begin(115200);      //begin Serial comm
   // Set ADC prescalar to 128 - 125KHz sample rate @ 16MHz
  ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
  ADMUX |= (1 << REFS0);     // Set ADC reference to AVCC
  ADMUX |= (1 << ADLAR);     // Left Adjust the result 
  ADCSRA |= (1 << ADEN);     // Enable ADC
  ADCSRA |= (1 << ADIE);       // Enable ADC Interrupt
  sei();                                           // Enable Global Interrupts
  ADCSRA |= (1 << ADSC);     // Start A2D Conversions  */
}

void loop()
{
  Serial.print(myvar[0]);
  Serial.print("  ");
  Serial.print(myvar[1]);
  Serial.print("  ");
  Serial.print(myvar[2]);
  Serial.print("  ");
  Serial.print(myvar[3]);
  Serial.print("  ");
  Serial.print(myvar[4]);
  Serial.print("  ");
  Serial.println(myvar[5]);
 
  delay(500);
}
//******************************************************
//-------------------ADC interrupt----------------------
//When the conversion is complete, increment the 
//counter to read next channel and update variables
//******************************************************

ISR(ADC_vect)           
  {
 //Count up to n channels
 (myvar[i]) = ADCH;
 //If i> number of sampled channels, set i back to zero and start again.
  if (++i >= ADC_CHANNELS)
      {i=0;} 

  ADMUX = (1<<ADLAR) | (1<<REFS0) | i; //Select ADC Channel
  ADCSRA |= (1 << ADSC);    // Start next ADC Conversion
  }

I still say you don't need to sample the pots that quickly if this is part of a human interface. Sampling each pot every 100 ms is good enough. Use the timers and interrupts for your square wave generation code. That way you won't have to bother with micros timing in your loop, and it won't be affected by the A/D conversions.

casemod:
I figured that out the hard way :wink:

I don't understand your comment ???

I was. Aparently the free running mode is only suitable for one channel. Long story:
SNIP
resets it back to zero and the whole cycle begins again.

And when you do explain what you are doing it is easy to make sense of your code

As with many of my projects the intent is to see how far I can optimize the arduino code for whatever it might take. Sampling 6 analog channels in a row consumes a LOT of time.
On this particular case it is to make a rudimentar square wave signal generator (this topic, right) but i certainly will have better uses for this in the future.

The problem I have is that it makes little sense to read 6 analog channels to drive a square wave generator. And people had been trying to assist with your square wave generator (the Thread title) when the goal posts move and the problem of the time to read ADCs interfering with square wave timing is introduced.

I suspect if you tell everyone what the ultimate "better uses ... in the future" will be you would get the best advice.

...R

Robin2:

casemod:
I figured that out the hard way :wink:

I don't understand your comment ???

It wasnt simple to find all the required information in one place. Even the AVR foruns base most of the examples in a single sampling. There are also commands that work on the AVR compiler but not on the arduino, so i spent a lot of time digesting all the information - In Short as you say its not easy to understand without spending some time looking at it, then there's the fact the code wasnt finished. Check below.

Robin2:

casemod:
I was. Aparently the free running mode is only suitable for one channel. Long story:
SNIP
resets it back to zero and the whole cycle begins again.

And when you do explain what you are doing it is easy to make sense of your code

Of course, but I could not explain 100% until I had the code finished. Got to keep my options open and try it different ways.

Robin2:

casemod:
As with many of my projects the intent is to see how far I can optimize the arduino code for whatever it might take. Sampling 6 analog channels in a row consumes a LOT of time.
On this particular case it is to make a rudimentar square wave signal generator (this topic, right) but i certainly will have better uses for this in the future.

The problem I have is that it makes little sense to read 6 analog channels to drive a square wave generator.
I suspect if you tell everyone what the ultimate "better uses ... in the future" will be you would get the best advice.

Jiggy-Ninja:
I still say you don't need to sample the pots that quickly if this is part of a human interface. Sampling each pot every 100 ms is good enough.

I have found an issue that bothered me with the arduino libraries. Some people are fine with that, I am not.
So it only made sense to find for a solution, which I could not find. So the next step is to make my own code, which i did. Why not acept the contribution to the forum and be happy with that?

I dont plan to read 6 channels. I posted a code that can read 6 channels and could easily be modified by changing the ADC_CHANNELS field, just dont mix this function with the arduino Analogueread. The code also allows oversampling and I found a few threads on the forum with people looking at how to implement that.

The better uses is up to the judgement of the designer. I can think of a whole lot of applications where I need fast sampling and cant afford to stop my code until the ADC results are retrieved. It also serves as a programming exercise. Arduino is cool, but i dont plan to use it forever. I am happy that I can made a quick and dirty "sketch", but the layer of abstraction is something i like to understand, and if necessary, be given the option to avoid as it locks many functions under the hood.

Robin2:
And people had been trying to assist with your square wave generator (the Thread title) when the goal posts move and the problem of the time to read ADCs interfering with square wave timing is introduced.

True I should have opened another thread.

Jiggy-Ninja:
Use the timers and interrupts for your square wave generation code. That way you won't have to bother with micros timing in your loop, and it won't be affected by the A/D conversions.

Thats today's exercise. One thing at a time :wink: