
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.
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 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.
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!
The traces below were made with the PICKit 2 logic analyzer.
I believe they're pretty self-explanatory.
Finally, the revised, working code.
{ ***************************************************************************** * 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