A Simple Signal Generator

pwm_signal2

A square wave generator can be handy for simulating sensors and providing an input to a micro-controller.  This signal generator can be adjusted from 10 Hz - 10 kHz, with an adjustable duty cycle from 1 to 99 percent.

 

Theory of Operation

There are a variety of methods that can be used to generate a string of pulses.  Among the easiest to understand is based on delay statements.  Turn an output on for a period of time, and then turn it off for a period of time.  If these two periods are equal, the result will be a square wave with a 50% duty cycle.  The down side of this method is that the controller is always occupied waiting to change state, and can't do anything else while an output is being generated.

This is the method that has been used here.  The main program loop turns a port pin on, and at a prescribed time later, turns it off in an endless loop.  To this simple loop, 2 "if-then" statements have been added to check if a switch is pressed, in which case, a subroutine is called to set frequency and duty cycle parameters.  If you examine the code, you'll notice one of these if-then statements occurs in the "on" part of the loop, and one occurs in the "off" part of the loop.  This has been done to keep the "delay" caused by checking the port pin symmetric in both parts of the loop.  These if-then statements don't require much time to execute.  Allowing 2 µsec for each of these subroutines provides fairly accurate frequency results.

The period of a waveform or the time for one complete cycle, is equal to 1/frequency of the signal.  For a 10 kHz signal, the period is 100 µsec.  For a 50 Hz signal, it's 20,00 µsec.

The duty cycle is the percentage of time a signal is high during each cycle.  A duty cycle of 0 means it's a 0 volt DC signal.  50% is a symmetric square wave.  a duty cycle of 99% means the signal is at its positive potential 99% of the time, and 0 for 1% of the time.

Multiplying the duty cycle x the period yields the time for the output to be on.  The remainder of the period the output is off.

When a push button is pressed, the subroutine to set the frequency and duty cycle is called.  The subroutine sends a message out via the UART, asking first for frequency followed by duty cycle.  Error-checking is done to see that the input value is a valid integer number in the operating range - 10 Hz - 10 kHz for frequency and 1 - 99 for duty cycle.  Additionally, just pressing [Enter] keeps the previous value.  Note that this subroutine preempts generating the signal.

 

The Hardware

The hardware for this signal generator is non-critical.  I built the signal generator around a TAP-28 board with an 18F242 controller and a 20 MHz crystal.  The hardware is pretty simple and any 18F-series part should work fine.

PortC.2 is used for the output.  S2 on PortB.4 is used to exit the main program loop for setting the parameters.  The PIC's hardware USART is used to communicate via the Tx and Rx pins to the PC using the PICkit 2 UART tool.  The TAP-28 is powered by the PICkit 2.

 

The Results

This signal generator is a handy tool of reasonable accuracy.  It's low cost and if a USB-UART cable was used to replace the PICkit 2 interface, it would be a nice low-cost addition to the electronics bench.  On the other hand, the software will run on most PIC hardware with only a couple minor changes, so it could be loaded onto a handy dev board when it's needed.

The picture below is the interface shown on the PICkit 2 UART tool screen.  I've input a frequency of 1000 Hz.  My trusty Fluke 45 benchtop meter reads 1001.2 Hz.  Close enough for me!

sig_gen_UART_output

 

The traces below were made with the PICKit 2 logic analyzer.

sig_gen_output

I believe they're pretty self-explanatory. 

 

The Code

Finally, the revised, working code.

Signal Generator
{
*****************************************************************************
* Name : Sig Gen.bas *
* Author : Jon Chandler *
* Notice : Copyright (c) 2010 Jon Chandler *
* : All Rights Reserved *
* Date : 6/6/2010 *
* Version : 1.0 *
* Notes : Defaults to 5 KHz, 50% duty cycle. Output is PortC.2. *
* : Low on PORTB.4 allows entering new values via UART. 9600/N/1 *
* : *
* :Designed for the TAP-28 and worth every cent you paid for it *
*****************************************************************************
}
 
Device = 18f242
Clock = 20
 
Include "USART.bas"
Include "Convert.bas"
Include "utils.bas"
 
Dim DelayTotal As LongWord
Dim DelayOn As LongWord
Dim DelayOff As LongWord
Dim Duty As Word
Dim Freq As String
Dim DutyCycle As String
Dim FreqOld As String
Dim DutyCycleOld As String
 
Sub ClearUSART()
 Dim tmpByte As Byte 
 While USART.DataAvailable = True
 tmpByte = RCRegister
 Wend 
 USART.ClearOverrun
End Sub
 
Sub InputParam()
 
 USART.ClearOverrun
 
 freqError:
 USART.Write("Desired Frequency? (Hz)", 13, 10) 
 ClearUSART
 USART.Read(Freq)
 
 If Freq = "" Then
 Freq = FreqOld
 End If
 
 If IsDecValid(Freq) = false Then
 USART.Write("Frequency must be an integer value between 10 - 10000", 13, 10) 
 GoTo freqerror
 EndIf
 If StrToDec(Freq) < 10 Or StrToDec(Freq) > 10000 Then
 USART.Write("Frequency must be an integer value between 10 - 10000", 13, 10) 
 GoTo FreqError
 EndIf
 
 FreqOld = Freq
 
 DutyError:
 USART.Write("Desired Duty Cycle? (%)", 13, 10) 
 ClearUSART
 USART.Read(DutyCycle)
 
 If DutyCycle = "" Then
 DutyCycle = DutyCycleOld
 End If
 
 If IsDecValid(DutyCycle) = false Then
 USART.Write("Duty Cycle must be an integer value between 1 and 99", 13, 10) 
 GoTo dutyerror
 EndIf
 If StrToDec(DutyCycle) < 1 Or StrToDec(DutyCycle) > 99 Then
 USART.Write("Duty Cycle must be an integer value between 1 and 99", 13, 10) 
 GoTo dutyerror
 EndIf
 
 DutyCycleOld = DutyCycle
 
 Duty = StrToDec(DutyCycle)
 
 USART.Write("Frequency = ", Freq, 13, 10, "Duty Cycle = ", DecToStr(Duty), 13, 10)
 
 DelayTotal = 1000000/StrToDec(Freq)
 
 DelayOn = DelayTotal * Duty / 100
 DelayOff = DelayTotal - DelayOn
 
 DelayOn = DelayOn - 2 'adjust timing to compensate for instruction cycles
  DelayOff = DelayOff - 2 'adjust timing to compensate for instruction cycles
  
 ' usart.write ("delay-on= ",dectostr(delayon), " delay-off = ", dectostr(delayoff), 13,10)
  
End Sub
 
 
// main program start
SetAllDigital
 
SetBaudrate(br9600)
USART.ReadTerminator = 13
 
'startup default
  Freq = DecToStr(5000)
FreqOld=Freq
DutyCycle = DecToStr(50)
DutyCycleOld=DutyCycle
 
DelayOn = 98
DelayOff= 98
 
Output(PORTC.2)
 
USART.Write("Signal Generator - 10 Hz - 10 KHz at 1% - 99% duty cycle", 13, 10) 
USART.Write("Press S2 to enter new value", 13, 10, 13, 10) 
 
While 1 = 1
 
High(PORTC.2)
 If PORTB.4 = 0 Then '2 if statements to keep delays constant in on and off sections
  InputParam
 EndIf
DelayUS(DelayOn)
 
Low(PORTC.2)
 If PORTB.4 = 0 Then
 InputParam
 EndIf
DelayUS(DelayOff)
 
Wend

 

Share this article

Tags: PWM, Signal Generator, Swordfish, Timer