Generating a Square Wave at a particular Frequency - Page 1
Forum section: electronics / beginners

author: blewisjr
author_rank: Frequent Contributor
timestamp: April 09, 2013, 03:43:33 pm
content: Hello,I am in the process of wanting to break out my new DS1102E Oscilloscope and learn about the various features and how to use it. So I want to start simple.I figured I can take my microcontroller and generate a square wave off of one the digital pins at a certain frequency similar to blinking a LED hello world program. I found some information on the net that this is a good way to learn the various controls. I want to use the micro so that I can change the frequency easily to see the differences on the scope and change the voltages via my lab power supply.My issue is I am not quite sure how to manipulate my delay to get the appropriate frequency. The micro is running at 8MHz / 8 so the actual cpu cycle frequency is 1MHz. Is there a formula I can use to calculate the delay I need to get a frequency of 500Hz on the wave being generated?Here is an Idea of the code I want to use to generate the square wave.Code:int main(void){DDRB != (1<<0); // set PORTB0 as an output.while (1){PORTB ^= (1<<0); // Toggle PORTB0_delay_ms(<some time>); // wait for appropriate ms to get proper frequency}}

author: smashedProton
author_rank: Frequent Contributor
timestamp: April 09, 2013, 04:00:59 pm
content: There is a square wave output ob the scope

author: blewisjr
author_rank: Frequent Contributor
timestamp: April 09, 2013, 04:02:24 pm
content: Quote from: smashedProton on April 09, 2013, 04:00:59 pmYes but it is fixed I want to be able to change the frequency so I can get use to reading and manipulating the scope. I don't have a frequency/function gen nor can I afford one at the moment so this is the next best thing.

author: w2aew
author_rank: Super Contributor
timestamp: April 09, 2013, 04:48:21 pm
content: Well, start off with *assuming* that the while-loop delay is minimal, and set the delay to equal half the period of 500Hz. The period of a 500Hz signal is 2ms, so half of that is 1ms. That will get you close, then tweak it from there.

author: AndrejaKo
author_rank: Frequent Contributor
timestamp: April 09, 2013, 04:50:06 pm
content: Well the formula is actually very simple (assuming 50% duty cycle).First step is to get required frequency, 500 Hz in your case. At 50% duty cycle, that would mean that your square wave would be low for 1 ms and high for 1 ms. Remember, frequency is 1/period. The period here is 2 ms, so that means that at 50% duty cycle, high is one half of that and low is another half of that time.Next we have a simple approximation: Just use what you already posted and set the _delay_ms() to have argument of 1. Rember that for AVR (your pseudocode reminds me of them), you need to set the constant called F_CPU to the actual frequency you'll be running core at. Number should be frequency in hertz and remember to put UL at the end, to make sure that whole number is read. So in your case you should have something like#define F_CPU=1000000ULin code.The void _delay_ms (double __ms) function uses F_CPU to calculate at compile time exactly how long to wait.If you need more precise timing, you could try to do some cycle counting in order to get more precise results or you could try using one of the timers on the chip.If you want to have wait time which can be changed at run time, you can make a loop which will do a short wait. For example you can have something like thisfor(int i=0;i<half_the_period_in_ms;i++){_delay_ms(1);}and after that part, toggle the pin and do same for the other part of the period. The half_the_period_in_ms variable could be sent for example over a serial port and changed on the fly. This way, we work around the need to know the delay at compile time, but we lose some accuracy.

author: ve7xen
author_rank: Super Contributor
timestamp: April 09, 2013, 06:14:02 pm
content: To make it accurate, you need to count cycles. This will only start to matter when you get down into dozens-of-cycles delay territory.You should use the PINx register to toggle output pins. PORTx ^= _BV(0) is equivalent to PINx = _BV(0) but saves the compiler (or processor, depending on your optimization setting) some work.You could also use one of the PWM peripherals for this.

author: bingo600
author_rank: Super Contributor
timestamp: April 09, 2013, 06:34:59 pm
content: When you want an AVR to make accurate Squares , have a look at timer1 in CTC mode.The the waveform is generated in hardware and output at (probably) OCR1B, and can reach F_OSC/2/Bingo

author: smashedProton
author_rank: Frequent Contributor
timestamp: April 09, 2013, 07:32:28 pm
content: Stab yourself with a scope probe. You should see 60 hz assuming that your house wiring is crap

author: casper.bang
author_rank: Frequent Contributor
timestamp: April 09, 2013, 07:57:15 pm
content: Quote from: smashedProton on April 09, 2013, 07:32:28 pmExcept in most parts of the world, the mains alternate at 50Hz.

author: MasterOfNone
author_rank: Regular Contributor
timestamp: April 09, 2013, 08:57:40 pm
content: BlewisjrI’ve only messed around with PIC’s but why are you toggling a single bit on the port if you want to play with multiple frequencies?If you use a counter for PORTB (instead of Toggling a single bit) you would have multiple frequencies to probe.So you could set PORT0 to PORT3 as outputs, and then do ‘PORTB++;’ (or PORTB = PORTB +1;) within the loop rather than the toggle. Next if you set the delay to give 500Hz as explained above, then:PORT0 - Would equal 500Hz,PORT1 - Would equal 250Hz,PORT2 - Would equal 125Hz &PORT3 - Would equal 62.5HzAlso if you have an ADC input on your microcontroller you could try feeding it from a variable resister and then using the value (read from the ADC) to set the delay in your loop (after you scale the value). This would get you a variable frequency generator to play with for now. Although you wouldn’t know which frequency you have set without using the scope.

author: blewisjr
author_rank: Frequent Contributor
timestamp: April 09, 2013, 09:28:35 pm
content: Quote from: MasterOfNone on April 09, 2013, 08:57:40 pmWow really great ideas. A few questions however. First why does the frequency halve across each port? The ADC idea is interesting but what about using a pot hooked into a ADC and using that directly on a PWM to modify the period of the pulses it puts out instead of using it to manipulate the delay? Not sure if it would work just a thought as it is technically a PWM's job to output these kinds of pulses.In all honesty PIC or AVR does not matter I just threw the example code together and made it as generic as possible. I am still on the fence of which chips I like better overall due to the prices of the PIC compiler otherwise I would go for PIC outright as I love their chips so far. Actually I am currently in the process of designing a ICSP breakout board for my 20 pin PICs to make it easier to use them on a breadboard and still be able to program them without losing crap tons of space. Might do the same thing for singular AVR chip I have.

author: ve7xen
author_rank: Super Contributor
timestamp: April 09, 2013, 10:01:58 pm
content: Quote from: blewisjr on April 09, 2013, 09:28:35 pmThis is a characteristic of counting in binary. Your LSB will have a frequency 1/2 the count rate. LSB+1 1/4 the count rate, etc. Look at a binary count and consider the length of time each bit stays on a value:000001010011100101110111If you do this directly in the PORTx register, that's exactly what appears on the output.QuoteIf you set up the timer in CDC mode as already suggested, it's basically the same thing as adjusting the delay, so yes that'd work.

author: MasterOfNone
author_rank: Regular Contributor
timestamp: April 09, 2013, 10:23:21 pm
content: Great answer Ve7xen, you got in before me. However I probably I did confuse him by saying PORT0 - PORT3 instead of PORTB0 to PORTB3. So it does look like I was talking about multiple ports.

author: Galaxyrise
author_rank: Frequent Contributor
timestamp: April 09, 2013, 10:29:37 pm
content: Your initial code was using delay_ms. If the minimum wait between setting the pin values is one millisecond, that limits you to 500Hz! (The period would be 2 ms.) You can do a little better with the microsleep function, but you really want to be operating in CPU clocks.Any square wave generated in code is going be very limited in the number of frequencies it can make, or is going to have jitter in the output. Using the clock pin should give you a nice result for the frequencies it can make, and it'll be a lot easier to control!But since you're doing this as an exercise, those other possibilities may be very interesting! Start with a dumb loop that changes the pin and then spins for, say, 10 iterations. Try again with 20 iterations Can you figure out how many CPU clocks are used by each iteration, and how many are used by the rest of the loop based on what you see on the scope?Spinning for an integer number of iterations can't generate lots of frequencies. To generate arbitrary frequencies, you'll have to keep track of an accumulated error, and occasionally iterate one extra from the baseline. You'll want to do that entirely with integer adds and subtracts (See the Breshenham line algorithm for inspiration.) That variance in the number of iterations will show up as jitter on the scope. Can you capture it? Keep in mind you need to keep the time of the outer loop consistent, or else the different code paths will interfere with output as well!

author: psycho0815
author_rank: Regular Contributor
timestamp: April 10, 2013, 10:01:19 am
content: For any kind of accuracy, you'll propably want to use a quarz oscillator. Also to get the timing right, this might be one of the very rare cases were it might pay to do at least the loop in assembler. That way you can count how many cycles on iteration takes instead of figuring it out by experimentation. Also that way a change in compiler-settings won't mess up your timing.

author: hlavac
author_rank: Frequent Contributor
timestamp: April 10, 2013, 07:59:50 pm
content: You will want to employ hardware timers counting a high frequency clock to get anywhere near a variable frequency generator in C. Have a look at the PWM hardware in your microcontroller.You should be able to come up with combination of hardware and software that will allow you togenerate arbitrarily long intervals of ones and zeros with resolution oflength of one pwm clock. That will allow you to play not only with frequency but also duty cycle.Note that you are not limited by the number of bits in your pwm counter, you can generate interrupts and add more bits in software...For example atmega/attiny with pll driving peripheral clock can do 64MHz PWM clock which means resolution of 15.625ns for your timing intervals.With dynamic reconfiguration of PWM hardware you can do even stuff that pwm hardware by itself cannot do. For example I did a logic probe tester that generated one 15.625ns pulse every second, both positive and negative polarity on two pins and a visible 10ms LED flashing pulse on third pin.You could count cpu cycles in a simple loop but that works better in assembly and the cpu clock isusually slower than pwm clock can be.Note that even the code between the delay function calls takes nonzero time. And if you add control buttons or display the code paths will not even have same length...

author: 4to20Milliamps
author_rank: Regular Contributor
timestamp: April 11, 2013, 12:20:46 am
content: here's some code I use to make square waves to pulse metal detector coils:#include <12F683.h>#fuses INTRC_IO, NOWDT, NOCPD, NOPROTECT, NOMCLR, NOBROWNOUT, PUT#use delay(clock=8000000)void main() {setup_adc_ports(NO_ANALOGS|VSS_VDD);setup_adc(ADC_OFF);setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);setup_timer_1(T1_DISABLED);setup_timer_2(T2_DISABLED,0,1);setup_ccp1(CCP_OFF);setup_comparator(NC_NC);setup_vref(FALSE);setup_oscillator(OSC_8Mhz);output_low(PIN_A2);while (TRUE) {delay_us(5500);output_high(PIN_A2);delay_us(500);output_low (PIN_A2);}}just tweak the delay times a bit to change the duty cycle and pulse width

author: westfw
author_rank: Super Contributor
timestamp: April 11, 2013, 12:59:40 am
content: Quote from: blewisjr on April 09, 2013, 03:43:33 pmStepping backward a bit...For a square wave to be "500 Hz", it has an alternating pattern of 1s and 0s that repeats 500 times each second.Each "cycle" therefore takes 1/500 s; the time for each cycle is called the "perdiod" of the waveform, and obeys the simple equation "f = 1/p" (and "p = 1/f" as well, by simple algebra.)So each period of the 500Hz square wave takes 0.002s, half of which is spent at 1, and half of which is spent at 0.So the time spent at each state is 0.001s; (1ms or 1000 us.)So the simple formula for the on and off times is t = (1/f)/2 or t = 1/(2*f)Now, conveniently, the functions provided by avr-libc in <util/delay.h> take floating point arguments, so you can actually USE this simple equation without worrying too much about rounding and so on:Code:// define the frequency in Hz#define freq 1234.0// Calculate the period in microseconds (have the compiler calculate it)#define period(f) (1000000*(1.0/(f)))// delay for half of the period_delay_us((period(freq))/2.0)Naturally, this will have accuracy limited by the frequency of your CPU, how much time the code around it uses, and "other factors." But to generate square waves with frequencies much less than the cpu frequency, it should work OK.I didn't see anything horribly wrong in other people's comments, but they're all about achieving more accurate square waves, instead of about calculating the time value you need.

author: blewisjr
author_rank: Frequent Contributor
timestamp: April 11, 2013, 11:20:22 am
content: Quote from: westfw on April 11, 2013, 12:59:40 amthanks west that is exactly what I wanted. I am not worried about the implementation I can easily work that out the equation is what I could not find. I guess I was searching for the wrong thing. Thanks again.