Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Generating sine wave of 4kHz using PWM.

Status
Not open for further replies.

MrNobody

New Member
Hi..
Below is the code to generate 4kHz sine wave using PWM for PIC18F4620. I am going to test it soon but at the mean time, i don't have the equipment such as oscilloscope so am not able to test it jst yet.. However, by simulating it in Proteus, I am able to see the pulsewidth (or duty cycle changes accordingly). The PWM output will connect to sallen key's 2nd order low pass filter with a cut off frequency of 4kHz or slightly higher so that the higher frequency will be filtered off leaving behind a sine wave (hopefully).

I am in the middle of building a low pass filter similar to the low pass filter of this preamp.
Anybody done this before..? Please advice..
Thanks..


Code:
#include <p18f4620.h>
#include <delays.h>
#include <usart.h>
#include <stdio.h>
#include <timers.h>
#include <pwm.h>

#pragma config OSC=HSPLL 
#pragma config PWRT=ON
#pragma config BOREN=OFF
#pragma config WDT=OFF
#pragma config MCLRE=ON
#pragma config PBADEN=OFF
#pragma config LVP=OFF
#pragma config XINST=OFF
#define CLOCK_FREQ		(40000000)      // Hz 

/////////////////////////////////////
// FUNCTION PROTOTYPE
/////////////////////////////////////
void low_isr();
void InitInterrupt();
void InitTimer2();
void InitPWM();
void UpdateDutyCycle();



/////////////////////////////////////
// GLOBAL VARIABLE
/////////////////////////////////////
unsigned int cnt8000 = 0;
char cntDty = 0;
char boolUpdateDty = 0;
const unsigned char Dty [24] = {128, 160, 191, 218, 238, 251, 255, 251, 238, 218, 191, 160, 128, 95, 64, 37, 17, 4, 0, 4, 17, 37, 64, 95}; // Testing
unsigned long int tempDty1 = 0, tempDty2 = 0;

/////////////////////////////////////
// SETUP ISR VECTOR
/////////////////////////////////////
#pragma code low_vector=0x18    //setup the ISR vector
void low_interrupt (){
  _asm GOTO low_isr _endasm    //jump to interrupt handler
}
#pragma code


/////////////////////////////////////
// ISR
/////////////////////////////////////
#pragma interruptlow low_isr    //the ISR
void low_isr()
{
    if (PIR1bits.TMR2IF == 1)
	{
		PIR1bits.TMR2IF=0;
		boolUpdateDty = 1;
		
		if (cnt8000 < 8000)
			cnt8000++;
		else 
			cnt8000 = 0;
    }
}
#pragma code

void main()
{
	InitInterrupt();
	InitTimer2();
	InitPWM();
	T2CONbits.TMR2ON = 1;
	
	while (1)
	{
		if (boolUpdateDty==1)
		{
			boolUpdateDty = 0;
			UpdateDutyCycle();
		}
	}
}

void InitInterrupt()
{
	PIE1bits.TMR2IE = 1;	//enable Timer2 interrupt
        INTCONbits.PEIE = 1;    //enable peripheral interrupts
        INTCONbits.GIE = 1;     //enable glogal interrupts
	IPR1bits.TMR2IP = 0;	// low priority
}	

void InitTimer2()
{
	T2CON = 0b01001000;
	PR2 = 124;				// 125 counts including 0
}	

	
void InitPWM()
{

	SetDCPWM1(250);
	CCP1CON=0b00001100;    //ccpxm3:ccpxm0 11xx=pwm mode
	T2CONbits.TMR2ON = 0;  // STOP TIMER2 registers to POR state
	PR2 = 124;
}

void UpdateDutyCycle()
{
	
	if ((cnt8000%334)==0)
	{
		tempDty1 = 50000/255;
		tempDty2 = Dty[cntDty]*tempDty1/100;
		
		SetDCPWM1(tempDty2);
		if (cntDty < 22)
			cntDty++;
		else 
			cntDty = 0;
	}
}
 
Last edited:
I've played around with such op amp based filters years ago. Ham radio was the application, trying to get tighter bandwidth from cheaper radios for CW (Morse code) reception. They worked well and were much cheaper and easier to build then passive LC filters at these low frequencies.

A couple of things to look out for are input signal voltage swing range Vs the op amp design and output impedance needed to drive whatever your sending the signal to. I'm sure others have more and better experiences to share.

Good luck

Lefty
 
It looks to me that you want a 4khz sign wave and the PWM is running at 24x or 96khz. You want 5 volts p-p centered at 2.5 volts. Your filter should pass 4khz and block 96khz and above.

Lose U1A! It has a gain of 100. It blocks DC and you may not want that.
U1B has gain set by R3,R5. I think you want gain=1. There fore R3=0, R5=open.

Because the PWM goes from 0% to 100% the sign wave will go from 0 to 5 volts. U1 needs to be R-R input/output type. If you can run U1 from a negative supply and a 7 volts supply then it’s input and output will not need to go rail to rail.

I would change U1B filter to: C1=10nF, C1.8nF, R1,R2=10k,10k. That makes a filter with a gain of 1 at 4khz and below. At 96khz the gain is –56db. That should remove the high frequency.
 
Thanks for all the reply..
Yes, the output of the PWM will bypass the U1A and will directly be connected to R2..

ronsimpson:
Just need some clarification with the filter calculation. Is there any specific reason to use R1 and R2 at 10K, C1 = 10nF, C2 = 1.8nF..? Can I use R1 and R2 at 1K, C1 and C2 at 40nF..? I obtained the values from the formula w^2=1/(R1*R2*C1*C2) where w=2*pi*f, R1 = R2 and C1 = C2. I am just interested to know the reasoning and wish to learn and understand new things...

Another thing is, I am planning to generate the PWM frequency of 80kHz because i will be using that same frequency for other things so that it is synchronize.. Since you say that the PWM is running at 24 times with the frequency of 96kHz, can you please explain how I can obtain the frequency of 80kHz..?
I have been careful to set PR2 = 124 and using prescaler of 1 running on Fosc of 40MHz.. and using the formula of PWM freq = (Fosc/(PR2 + 1) * 4). In that formula, the prescale value is ignored because it has the value of 1. Anyway, I may have missed something and resulting in PWM frequency of 96kHz instead of 80kHz.. Please again advice..
Thanks..
 
Frequency?
I looked at how many steps in the sign lookup table and multiplied by 4khz in my head. I should have looked at the counter. My mistake.

Filter?
Your two state filter had a gain of hundreds. It did not seem right. I changed things to get a gain of 1 at 4khz and roll off greatly above that. I did not check your filter. I can when I get up in 8 hours.
 
Sorry, the code above is not right..?
I tried it out with an oscilloscope and had a bit of a laugh because what i saw was not even close to a sine wave..
Below is the corrected code and yes, the PWM frequency is actually 4kHz multiply by number of steps (which is 20).


Code:
#include <p18f4620.h>
#include <delays.h>
#include <usart.h>
#include <stdio.h>
#include <timers.h>
#include <pwm.h>

#pragma config OSC=HSPLL 
#pragma config PWRT=ON
#pragma config BOREN=OFF
#pragma config WDT=OFF
#pragma config MCLRE=ON
#pragma config PBADEN=OFF
#pragma config LVP=OFF
#pragma config XINST=OFF
#define CLOCK_FREQ		(40000000)      // Hz 

/////////////////////////////////////
// FUNCTION PROTOTYPE
/////////////////////////////////////
void low_isr();
void InitInterrupt();
void InitTimer2();
void InitADC();
void InitPWM();
void UpdateDutyCycle();
void InitUART();
void Test();


/////////////////////////////////////
// GLOBAL VARIABLE
/////////////////////////////////////
char results;
char UARTSendF = 0;
unsigned int cnt8000 = 0;
unsigned char cnt80002 = 0;
unsigned int cnt80003 = 0;
char cntDty = 0;
char boolUpdateDty = 0;
const unsigned char Dty [20] = {128, 167, 202, 231, 249, 255, 249, 231, 202, 167, 128, 88, 53, 24, 6, 0, 6, 24, 53, 88}; // Testing
unsigned long int tempDty1 = 0, tempDty2 = 0, tempDty3 = 0;

/////////////////////////////////////
// SETUP ISR VECTOR
/////////////////////////////////////
#pragma code low_vector=0x18    //setup the ISR vector
void low_interrupt (){
  _asm GOTO low_isr _endasm    //jump to interrupt handler
}
#pragma code


/////////////////////////////////////
// ISR
/////////////////////////////////////
#pragma interruptlow low_isr    //the ISR
void low_isr()
{
    if (PIR1bits.TMR2IF == 1)
	{
		
		boolUpdateDty = 1;
		
		if (cnt8000 < 8000)
			cnt8000++;
		else 
			cnt8000 = 0;
		PIR1bits.TMR2IF=0;
    }
}
#pragma code

void main()
{
	InitInterrupt();
	InitTimer2();
	InitPWM();
	T2CONbits.TMR2ON = 1;
	
	while (1)
	{
		if (boolUpdateDty==1)
		{
			UpdateDutyCycle();
			boolUpdateDty = 0;
		}
	}
}

void InitInterrupt()
{
	PIE1bits.TMR2IE = 1;	//enable Timer2 interrupt
        INTCONbits.PEIE = 1;    //enable peripheral interrupts
        INTCONbits.GIE = 1;     //enable glogal interrupts
	IPR1bits.TMR2IP = 0;	// low priority
}	

void InitTimer2()
{
	T2CON = 0b01001000;
	PR2 = 124;				// 125 counts including 0
}	

void InitADC()
{
	ADCON0 = 0b00000001;
	ADCON1 = 0b00001110;
	ADCON2 = 0b00010010;
	DDRAbits.RA0 = 1;
}
	
void InitPWM()
{

	SetDCPWM1(250);
	CCP1CON=0b00001100;    //ccpxm3:ccpxm0 11xx=pwm mode
	T2CONbits.TMR2ON = 0;  // STOP TIMER2 registers to POR state
	PR2 = 124;
}


void InitUART()
{
	DDRCbits.RC6 = 0;
	DDRCbits.RC7 = 1;
	SPBRG = 86;
	TXSTA = 0b00100110;
	RCSTA = 0b10010000;
	BAUDCON = 0b00001000;
	TRISC = 0b10000001;
}		

void UpdateDutyCycle()
{
		tempDty1 = 50000/255;
		tempDty2 = Dty[cntDty]*tempDty1/100;
		
		SetDCPWM1(tempDty2);
		if (cntDty < 18)
			cntDty++;
		else 
			cntDty = 0;
}
 
Your filter:
I removed the first stage!
Stage 2:
Gain=+4db @ 4khz
Gain=-3db @ 10.5khz
Gain=-40db @ 100khz
 
Hi,

I'm currently in the process of programming SPWM using PIC18F452.

I'm curious on the PWM signal that u have obtained. Is it possible to post a snapshot of how your pwm waveform and also your final sinewave?
 
Hi,

I'm currently in the process of programming SPWM using PIC18F452.

I'm curious on the PWM signal that u have obtained. Is it possible to post a snapshot of how your pwm waveform and also your final sinewave?

Sorry.. I am unable to post the oscilloscope picture as i did not capture it.. However, attached is a screenshot of it in Proteus. The yellow wave is the PWM signal before lowpass filter and the blue wave is the signal after filtering..

The blue signal can be much smoother if using a lower Fc.
 

Attachments

  • PWM.png
    PWM.png
    31.9 KB · Views: 712
  • PWM2.png
    PWM2.png
    34.4 KB · Views: 671
Sorry.. I am unable to post the oscilloscope picture as i did not capture it.. However, attached is a screenshot of it in Proteus. The yellow wave is the PWM signal before lowpass filter and the blue wave is the signal after filtering..

The blue signal can be much smoother if using a lower Fc.


Oh... That signal was the same as what i had implemented using hardware instead of microcontroller. I'm still trying out using pic18f452 to program the code. Maybe your code will be a good reference to guide me thru. :D
 
Last edited:
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top