Continue to Site

# [PIC]PIC16F628A - generate 10kHz signal or similiar - 8 bit MCU - the calculation and wait

Status
Not open for further replies.

#### K33rg4t3

##### Full Member level 3
Hey
I have some questions about generating a 10kHz square signal on PIC, take a look at the pseudocode:

Code:
void main()
{
int freq = 10; // 10 kHz

while(1)
{
// QUESTION 1: I have only 8 bit integer, how can I calculate it?
int usToWait = 1 / freq;  ?? < - how to calculate it
// QUESTION 2: wil it be accurate?
while(usToWait)
{
__delay_us(1);
usToWait--;
}

// do something
}

}

as I said, how to calculate the us count to wait while having only 8 bit integers?
10kHz means 0.0001 seconds interval.... (100 us)

all I have figured out so far is using some kind of if condition for every frequency but this is silly because I want it to work for freq = 10, 11, 12, 13 ETC kHz so I need a calculation.
THIS would require a floating point division am I right?

Frequency = 1/time.

The '__delay_us();' is already in uS so using 100 as the parameter will give you 0.0001 seconds delay. Where your calculation goes astray is in wrapping the delay inside a loop because the decision making as to whether the loop is finished and the decrement instruction also take time to complete and therefore slow the loop down. In general, and especially for short delays, it's best to use one of the PIC timers so the delay is reliably created in hardware and not depending on the execution time.

The other issue you will have is making the output a square wave. You either have to toggle the pin state each time the outer while() loop executes or use two 'half length' delays, setting the pin high after the first delay then low after the second. Please don't fall into the trap of coding:
while(1)){'pin high', 'delay', 'pin low'} // pseudocode!
because that has no delay between setting the pin low and then high again.

A timing routine using integer values will be more accurate and faster than one using floating point values simply due to the relatively slow handling of floats. You will also find that it isn't possible to produce every frequency accurately using delays or timers, you can do it with DDS techniques but they are more hardware than software based. Some PICs have NCO modules which go some way to allowing any frequency to be produced, perhaps you can use one that has that facility.

Brian.

K33rg4t3

### K33rg4t3

Points: 2
Thank you for your reply, but somehow you didn't answer my main question.

When I do:
Code:
int freq = 10;
int time_us = 1 / freq;
this will result in 0, because integer can't store fraction parts, right?

So my question is how to get around it.

maybe:
Code:
float freq = 10;
float time_us = 1 / freq;
int time_us_int = time_us * 1000;
but still, if "int" is 8 bit (is it?) then it will be easy to get overflow....

The size of type int is implementation dependant, according to ANSI C it should be at least 16 bit. The standard is possibly ignored by some compilers, but there's no problem to have a 16 bit variable on a 8-bit processor.
Code:
int freq = 10;
int time_us = 1 / freq;
You are obviously confusing units. If your time unit is µs, the right frequency unit would be MHz.

Or, for kHz frequency
Code:
int freq = 10;
int time_us = 1000 / freq;

The real problem is however that delay statements are never accurate for frequency generation, as betwixt explained. Need to use direct output from a hardware timer.

K33rg4t3

### K33rg4t3

Points: 2
Yes indeed, I've confused units, but that's the smallest problem.

Or, for kHz frequency
Code:
int freq = 10;
int time_us = 1000 / freq;

So you say that this code is OK, but I just should use hardware timer to wait those "time_us"?

I wanted to test the code so I created a simple C desktop program:
Code:
#include <stdio.h>

int main(void) {

int time_us;
int freq;
int f2;
for(freq = 1; freq < 50; freq++)
{
time_us = 1000 / freq;
f2 = 1000 / time_us;
printf("Freq: %i, time us: %i, real freq %i\n",freq,time_us,f2);
}
return 0;
}
https://ideone.com/n1xzw5
and look, the output is:
Code:
Freq: 1, time us: 1000, real freq 1
Freq: 2, time us: 500, real freq 2
Freq: 3, time us: 333, real freq 3
Freq: 4, time us: 250, real freq 4
Freq: 5, time us: 200, real freq 5
Freq: 6, time us: 166, real freq 6
Freq: 7, time us: 142, real freq 7
Freq: 8, time us: 125, real freq 8
Freq: 9, time us: 111, real freq 9
Freq: 10, time us: 100, real freq 10
Freq: 11, time us: 90, real freq 11
Freq: 12, time us: 83, real freq 12
Freq: 13, time us: 76, real freq 13
Freq: 14, time us: 71, real freq 14
Freq: 15, time us: 66, real freq 15
Freq: 16, time us: 62, real freq 16
Freq: 17, time us: 58, real freq 17
Freq: 18, time us: 55, real freq 18
Freq: 19, time us: 52, real freq 19
Freq: 20, time us: 50, real freq 20
Freq: 21, time us: 47, real freq 21
Freq: 22, time us: 45, real freq 22
Freq: 23, time us: 43, real freq 23
Freq: 24, time us: 41, real freq 24
Freq: 25, time us: 40, real freq 25
Freq: 26, time us: 38, real freq 26
Freq: 27, time us: 37, real freq 27
Freq: 28, time us: 35, real freq 28
Freq: 29, time us: 34, real freq 29
Freq: 30, time us: 33, real freq 30
Freq: 31, time us: 32, real freq 31
Freq: 32, time us: 31, real freq 32
Freq: 33, time us: 30, real freq 33
Freq: 34, time us: 29, real freq 34
Freq: 35, time us: 28, real freq 35
Freq: 36, time us: 27, real freq 37
Freq: 37, time us: 27, real freq 37
Freq: 38, time us: 26, real freq 38
Freq: 39, time us: 25, real freq 40
Freq: 40, time us: 25, real freq 40
Freq: 41, time us: 24, real freq 41
Freq: 42, time us: 23, real freq 43
Freq: 43, time us: 23, real freq 43
Freq: 44, time us: 22, real freq 45
Freq: 45, time us: 22, real freq 45
Freq: 46, time us: 21, real freq 47
Freq: 47, time us: 21, real freq 47
Freq: 48, time us: 20, real freq 50
Freq: 49, time us: 20, real freq 50

Take a look for, example, 48 kHz case:
1000 / freq;
will be
1000 / 48 = 20
and for 49 kHz
1000 / 49 = 20
See? Some frequencies will be missed because of the integer rounding, am I right?

On a PIC16, you can use timer 2 in PWM mode, it gives you a period of 1 MHz/n with a 16 MHz crystal. Means, you get e.g. 10.99 kHz instead of exact 11 kHz. Don't know if it's acceptable.

Hi,

Maybe missed it, but you need two edges (fallling and rising) for one period, so for a 10kHz signal you need 50us high and 50us low.
So time out should be 50us.

***
But using the internal PWM or pulse generation hardware is the only useful way to go.
* most exact timing
* no jitter
* exact 50% duty cycle
* no processing power (only if you want to change the frequency)
* less code
* less power consumtion
* i can´t find a disadvantage...

Klaus

... but yes, you are right, it isn't possible to generate exact frequencies all the time. You will also find the delay method gives bigger frequency 'steps' as the frequency increases and the divisor becomes smaller. Also consider what happens in a software delay, there is a decision made each pass of the loop to determine whether the counter variable has reached zero, that decision takes a different number of clock cycles to complete if the answer is 'yes' or 'no'. With small count numbers the extra decision cycles become more significant. When used as fixed delays, it's easy to calculate the exact figures or even 'tweak' them slightly to compensate for instruction cycles but when you are trying to create almost any delay, the 'tweak' becomes almost impossible.

FvM's method with PWM hardware is good and reliable but it does tie you to using one particular PIC pin (PWM output) and of course, you can't use the PWM or Timer2 for anything else.

Brian.

PWM idea is very good.
It's a shame I didn't think about it earlier.

Big thanks for all, now I am trying to run it, based on code from web, but Idk how to adjust frequency:
Code:
// Define CPU Frequency
// This must be defined, if __delay_ms() or
// __delay_us() functions are used in the code
#define _XTAL_FREQ   20000000

#include <htc.h>

// CCP1 module is used here to generate the required PWM
// Timer2 module is used to generate the PWM
// This PWM has 10bit resolution

void SetPWMDutyCycle(unsigned int DutyCycle) // Give a value in between 0 and 1024 for DutyCycle
{
CCPR1L   = DutyCycle>>2;        	// Put MSB 8 bits in CCPR1L
CCP1CON &= 0xCF;                	// Make bit4 and 5 zero
CCP1CON |= (0x30&(DutyCycle<<4));   // Assign Last 2 LSBs to CCP1CON
}

void SetPWMFrequency(unsigned int freqKHz)
{
// TODO: figure out how to calculate appropriate freq?
PR2   = 0xFF;           // Configure the Timer2 period
T2CON = 0x01;           // Set Prescaler to be 4, hence PWM frequency is set to 4.88KHz.
}
void InitPWM(void)
{
TRISB3  = 0;            // Make CCP1 pin as output
CCP1CON = 0x0C;         // Configure CCP1 module in PWM mode

SetPWMFrequency(10);
SetPWMDutyCycle(0);     // Intialize the PWM to 0 duty cycle

T2CON |= 0x04;          // Enable the Timer2, hence enable the PWM.
}

// Main function
void main()
{
InitPWM();				// Initialize PWM at RB3 pin

SetPWMDutyCycle(512);   // 50% duty cycle

while(1)
{
}
}
it seems that I have to adjust both prescaler (the multipler) and timer2 period value...

Status
Not open for further replies.