Programming a PIC timer

Have you got the greatest 48 bit multiplier ever conceived? Prove it - post your code here.

Moderator: phalanx

Post Reply
tylerwolf
Posts: 22
Joined: Sat Oct 02, 2010 1:03 pm
Location: Los Angeles, CA

Programming a PIC timer

Post by tylerwolf » Tue Nov 16, 2010 2:48 pm

Hi, I've been working like mad on this timer code, trying to get it right. I get a result, but it's never what I'm trying for, it's off from anywhere between a factor of 2 or 3. The purpose of the code is to create a square wave output with the desired frequency (test_freq). Consider AMP_PIN_1 to be positive 5V and AMP_PIN_2 to be negative 5V.
I'm using timer1 on a PIC16F877 and I'm looking to output frequencies between 15kHz and 40kHz.
The first function calculates how many clock cycles are required on timer1 for half of a wavelength. It then turns AMP_PIN_1 on for that many clock cycles, then off, then turns AMP_PIN_2 on for that many clock cycles, then off, and repeats indefinitely (in this test code.)
NOTE: test_freq is the desired frequency divided by 100 and clock freq is 20000000 (external crystal)

Code: Select all

void frequency_test(int test_freq) {
	float period, count_on;
	period = 200000/(4*test_freq);
	count_on = (period/2);
	//find period of 1 wavelength and 1/2 wavelength
       while (1) {
		AMP_PIN_1 = !AMP_PIN_2;	//AMP_PIN_1 = 1
		hold_it(count_on);
		AMP_PIN_2 = AMP_PIN_1; //Both AMP_PIN's = 1
		AMP_PIN_1 = !AMP_PIN_2; //AMP_PIN_1 = 0
		hold_it(count_on);
		AMP_PIN_2 = AMP_PIN_1; //Both AMP_PIN's = 0
	}
	
}

The second function actually works with the timer. It resets timer1, then counts the number of clocks until the required value is reached and that's it.
I'm trying to get the output frequencies as consistent as possible for this, so any comments or suggestions on my overall technique would also be appreciated.

Code: Select all

void hold_it(float count) {
	long buffer = 0;
	T1CON = T1CON && 0xFE;
	TMR1IF = 0;
	TMR1H = 0;
	TMR1L = 0;
	T1CON = T1CON || 0x01;
	while (buffer < count) {		
		buffer = TMR1H;
		buffer << 8;
		buffer = buffer || TMR1L;
	}
	T1CON && 0xFE;
}
Thanks

waltr
Support Volunteer
Posts: 2823
Joined: Tue Sep 08, 2009 12:07 pm
Location: Philadelphia, USA

Re: Programming a PIC timer

Post by waltr » Tue Nov 16, 2010 4:09 pm

I see a number of things in your code that I wouldn't do.

Code: Select all

void frequency_test(int test_freq) {
   float period, count_on;
   period = 200000/(4*test_freq);
   count_on = (period/2);
1- No need to use a float for a counter value since a counter (hardware timer) can only use whole numbers. Use an unsigned int for 16 bit values or an unsigned char for 8 bit values.

2- Also, test_freq is an int and you are multiplying by a constant then dividing into a constant to get a float. If you really need a float result some explicit casts may be required. Are you getting the correct values in period & count_on when you step through this code in the MPLAB SIMulator?

3- I have a feeling that the assembler code produced by the C code has too many instruction cycles to allow the call and return to complete before the timer over runs to target value.
a- Change the float to unsigned char since the largest count value is only 166.
b- You may also want to get rid of the second function and put that code in the first function even if needs to be done twice.

4- Why are you turning off the timer? This only increases the period to the outputs being on or off by the amount of time needed to call and return from that function. Remember a half of a 15kHz period is only 166 instruction cycles and half of 40kHz is 62 cycles.
a- leave the timer running

Do run this code in the MPLAB SIMulator (remembering to set the correct PIC frequency). Set a break point where the output is changed then show the Stopwatch to time the program execution.

tylerwolf
Posts: 22
Joined: Sat Oct 02, 2010 1:03 pm
Location: Los Angeles, CA

Re: Programming a PIC timer

Post by tylerwolf » Tue Nov 16, 2010 6:19 pm

Thanks for the advice. Mad respect. I made those changes and a few more and here's what I got:

Code: Select all

void main (void) {
	unsigned int test_freq = 200;
	unsigned int period, buffer;
	unsigned char count_on;
	TRISC = 0x00;
	T1CON = 0x01;
	period = 200000/(4*test_freq);
	count_on = (period/2);
	//find period of 1 wavelength and 1/2 wavelength
	while (1) {
		AMP_PIN_1 = !AMP_PIN_2;	//AMP_PIN_1 = 1
		TMR1IF = 0;
		TMR1H = 0;
		TMR1L = 0;
		while (count_on > TMR1L) {
			NOP();
		}
		AMP_PIN_2 = AMP_PIN_1; //Both AMP_PIN's = 1
		AMP_PIN_1 = !AMP_PIN_2; //AMP_PIN_1 = 0
		TMR1IF = 0;
		TMR1H = 0;
		TMR1L = 0;
		while (count_on > TMR1L) {
			NOP();
		}
		AMP_PIN_2 = AMP_PIN_1; //Both AMP_PIN's = 0
	}
}
Unfortunately, this still throws it off by a couple kHz depending on the frequency. It's looking like if I want exact frequencies, which I do, I'll need to do this particular routine in assembly, but I suppose that's life...
If you caught anything I missed or you know something I don't that could make assembly unnecessary, input would be appreciated!

Thanks again for the help!

waltr
Support Volunteer
Posts: 2823
Joined: Tue Sep 08, 2009 12:07 pm
Location: Philadelphia, USA

Re: Programming a PIC timer

Post by waltr » Tue Nov 16, 2010 7:34 pm

this still throws it off by a couple kHz depending on the frequency.
Did you really do the math to get those frequencies?

With a 20MHz XTAL the instruction cycle and the hardware timers run at 5MHz or 200nSec per count.

If you set the timer for 62 counts per half cycle which is 124 counts per full cycle the cycle is 24.8usec or 40.322kHz. If you now use 63 counts (126 per full cycle) the cycle is 25.2usec or 39.682kHz.
It is impossible to get exactly 40kHz.
On the other end of the frequency range you want a half cycle the count is 166 (333 per full cycle) which give a cycle time of 66.6usec or 15.015kHz. One count less gives a cycle of 66.2usec or 15.105kHz.
Again it is impossible to the exact frequency.

Did you run your code in the MPLAB Simulator????????

If you did you will find that this code:

Code: Select all

            TMR1IF = 0;
      TMR1H = 0;
      TMR1L = 0;
Will eat up instruction cycles making the timing inaccurate. This can be compensated for by subtracting a few counts from the value placed in the timer. Use MPLAB simulator and the Stopwatch feature to determine how many counts to subtract.

One other item. You may find the Read-Modify-Write issue with real hardware as per the second paragraph in section 3.1 of the data sheet. The microchip web site and user's forum has much more infomation about this issue and solutions. A start is in the Mid-Range MCU Family
Reference Manual, chapter 9.

Edit to add:
I run your latest code and the timer to working.
The timing results are:
Desired freq count_on real freq
20000 125 15,723
I counted 18 cycles to set the port and the timer so I subtracted 18 from count_on. New values are:
20000 107 17,985
but the while loop waiting for the timer takes some cycles so now subtracted 27 from count_on.
20000 98 19,083
Lets subtract a few more since we're getting close.
20000 96 19,083
What? the frequency didn't change!!
The reason why is that the while loop and compare takes 8 instruction cycles which means that the timer counted 8 ticks before the compare can check the timer value again. This means that the resolution of the set frequency is much worse than what just the math above shows. Lets see if there is a better way.

I set up the timer with a max vlaue (0xffff) minus count_on then tested for the timer interrupt flag (TMR1IF) like this:

Code: Select all

   period = 200000/(4*test_freq);
   count_on = 0xff -((period/2) -29);
   //find period of 1 wavelength and 1/2 wavelength
   while (1) {
      AMP_PIN_1 = !AMP_PIN_2;   //AMP_PIN_1 = 1
      TMR1IF = 0;
      TMR1H = 0xff;
      TMR1L = count_on;
      while (!TMR1IF) {
      }
      AMP_PIN_2 = AMP_PIN_1; //Both AMP_PIN's = 1
      AMP_PIN_1 = !AMP_PIN_2; //AMP_PIN_1 = 0
      TMR1IF = 0;
      TMR1H = 0xff;
      TMR1L = count_on;
      while (!TMR1IF) {
      }
      AMP_PIN_2 = AMP_PIN_1; //Both AMP_PIN's = 0
   }
Now the timing results are:
desired freq timer1 real freq
20000 0xff9f 20,491
30000 0xffc9 30,487
40000 0xffde 40,322
15000 0xff76 14,970

These are pretty good. You could get just a little better with some tweeks or writing in assembler but remember to be the math like I showed above to see what can ideally be obtained.

If you really need exact frequency accuracy and a much finer frequency resolution I recommend using a DDS chip. The AD9850 and AD9832 would work well. You can control them from the PIC through a serial (SPI) interface.

Here is a good tutorial on programming PIC. It may help.
http://www.gooligum.com.au/tutorials.html

Any questions?

tylerwolf
Posts: 22
Joined: Sat Oct 02, 2010 1:03 pm
Location: Los Angeles, CA

Re: Programming a PIC timer

Post by tylerwolf » Thu Nov 18, 2010 2:57 pm

Wow, that was really clever and again, much appreciated. I did use the stopwatch and noticed the ridiculous hangup on the while statement. I tried out the code you suggested and it does improve the resolution of the output, but I don't think it's quite enough for my application. I'm actually a BS in EE, so I'm well versed with the read-modify-write issue and I have a good understanding of how all these things work, but the thing that will help me most at this point is programming experience. Anyway, enough with the tangent. Thanks again for the help.

waltr
Support Volunteer
Posts: 2823
Joined: Tue Sep 08, 2009 12:07 pm
Location: Philadelphia, USA

Re: Programming a PIC timer

Post by waltr » Thu Nov 18, 2010 8:04 pm

To get more insight on the while test statement open the dissasembly listing (under the View menu). You can then step through the assembler code that the compiler creates. Your method takes about 10 instruction cycles per compare whereas mine takes 3 which is the minimum if the compare was written in assembler.
Have you considered using a DDS chip?

tylerwolf
Posts: 22
Joined: Sat Oct 02, 2010 1:03 pm
Location: Los Angeles, CA

Re: Programming a PIC timer

Post by tylerwolf » Sat Nov 20, 2010 2:39 pm

I checked out the disassembler and I can see the while statement is not the most efficient. After doing some testing with the code you suggested, I figured I would lose a little resolution as frequencies got higher, but it's a little worse than I expected. I'm looking for a resolution close to 200 Hz and not much worse than that at higher frequencies. Just from doing a basic calculation (resolution * 3/10) I get somewhere around 800 Hz with the code written in assembly at higher frequencies.
I'm now seriously considering a DDS chip. The two pins alternating high scheme I have is preferred, so I'm thinking I can produce a square wave at the desired frequencies on the DDS chip and split the output into an inverted output and a regular output and just use those in place of the amp_pin_1 and 2. I haven't looked into it in detail yet, but I don't expect a NOT gate would have enough delay to affect the output frequency of the system.

User avatar
leon_heller
Support Volunteer
Posts: 5734
Joined: Sun May 01, 2005 11:20 am
Location: St. Leonards-on-Sea, E. Sussex, UK.

Re: Programming a PIC timer

Post by leon_heller » Sat Nov 20, 2010 2:48 pm

At those frequencies you can implement a DDS in software on the PIC, with a DAC, that will give you fractional Hz resolution.
Leon Heller
G1HSM

tylerwolf
Posts: 22
Joined: Sat Oct 02, 2010 1:03 pm
Location: Los Angeles, CA

Re: Programming a PIC timer

Post by tylerwolf » Tue Dec 14, 2010 1:05 am

I'm not entirely sure how one would implement a DDS at those frequencies (with 200 Hz resolution) because I can only get a resolution of about 500 Hz at the high end (40 kHz) using nop's. If you can point me to something that shows how to program with resolution that high, I'd appreciate it.
I've been looking at DDS's and the ad9834 is looking good. If anyone knows something I don't about it, advice would be greatly appreciated.

User avatar
leon_heller
Support Volunteer
Posts: 5734
Joined: Sun May 01, 2005 11:20 am
Location: St. Leonards-on-Sea, E. Sussex, UK.

Re: Programming a PIC timer

Post by leon_heller » Tue Dec 14, 2010 3:57 am

Leon Heller
G1HSM

tylerwolf
Posts: 22
Joined: Sat Oct 02, 2010 1:03 pm
Location: Los Angeles, CA

Re: Programming a PIC timer

Post by tylerwolf » Tue Dec 14, 2010 6:03 pm

Has that been done on a PIC? I did some inline assembly on the code from above to see if I could tighten things up. Ideally, that should give me resolution down to 1 instruction cycle, which it does, but an instruction cycle is actually 4 clocks on the oscillator, which effectively makes my resolution 0.2 µs, which is slightly better than I had before.
I also tried pwm and I got about the same resolution and the signal became unstable around 20kHz.

I noticed that the design you showed me uses an atmel MCU. The datasheet for that MCU said it can do instructions in one clock. If that's the case, then I think that's why the software DDS has such high resolution.

eejake52
Posts: 80
Joined: Wed Sep 08, 2004 2:09 pm
Location: Invermere, BC, Canada

Re: Programming a PIC timer

Post by eejake52 » Wed Jul 27, 2011 6:51 am

The PIC16F877 has a CCP module, so you could use the PWM to do the frequency generation in hardware. From section 11.5 of the datasheet, the period is :
t = [(PR2) + 1] • 4 • TOSC
so you should be able to calculate the nearest/best value for PR2, set the duty cycle to 50%, and voila!

HTH,
Jake

User avatar
leon_heller
Support Volunteer
Posts: 5734
Joined: Sun May 01, 2005 11:20 am
Location: St. Leonards-on-Sea, E. Sussex, UK.

Re: Programming a PIC timer

Post by leon_heller » Wed Jul 27, 2011 6:56 am

tylerwolf wrote:Has that been done on a PIC? I did some inline assembly on the code from above to see if I could tighten things up. Ideally, that should give me resolution down to 1 instruction cycle, which it does, but an instruction cycle is actually 4 clocks on the oscillator, which effectively makes my resolution 0.2 µs, which is slightly better than I had before.
I also tried pwm and I got about the same resolution and the signal became unstable around 20kHz.

I noticed that the design you showed me uses an atmel MCU. The datasheet for that MCU said it can do instructions in one clock. If that's the case, then I think that's why the software DDS has such high resolution.
It's nothing to do with that. You shouldn't have any problems getting 50 kHz with a PIC using the same technique. The first software DDS using an MCU was actually implemented on a PIC.
Leon Heller
G1HSM

Post Reply