This article has been translated to Serbo-Croatian language at WebHostingGeeks.com. Thanks.
Pulse Width Modulation is a technique to provide an output logic one for a period of time and a logic zero for the balance of the time.
This part of Tutorial #5 shows how to control the brightness of an LED using this technique.
In Part 2 of this tutorial, this is extended to show how to control the speed of a DC motor using an L293 Dual 'H' Bridge Controller.
The PWM command may also be used to charge a capacitor so as to display quantities on an inexpensive 200 mV LCD panel meter or a voltmeter. This is presented in Part 3.
Finally, in Part 4 the tutorial presents a technique which uses the PWM command to continually charge a capacitor to progressively larger voltages, all the time comparing this with an unknown voltage. By using a single comparator, a counting type A/D converter and a sucessive approximation A/D converter are implemented.
Hardware Configuration for Part 1.
Please refer to Figure #1.
Note that the circuity consists of two pushbuttons, one on P11 and one on P10. Note that the pushbutton on P10 is a new addition. The 4-bit DIP switch on Inputs P15, P14, P13 and P12 should be retained for the next part of this tutorial.
In addition, there is one LED on P2. The other LEDs on P3, P1 and P0 may be retained for the next part of this tutorial.
PWM - Overview.
When driving motors one frequently desires to change the speed by varying the voltage applied to the winding. However, it is very expensive to use a linear amplifier which provides 0 to 12 Volts at say 2 Amps.
A far less exepensive technique is to turn on a transistor (or a power FET) for a period of time and then turn it off for a period of time. For example, if the voltage is +12 Volts and the transistor is on 25, 50 or 75 percent of the time, the effective voltage across the load is 3.0, 6.0 and 9.0 Volts.
The above applies to lighting a lamp as well.
A PWM Algorithm.
Assume an 8-bit variable DUTY has a value in the range of 0 through 255. If it is x, then an output is to be high x/256 of the time and low for the balance.
Consider an accumulator where on each pass ACC = ACC + DUTY and if no overflow occurs, the lead is brought low. But if an overflow does occur, the lead is brought to a logic one.
Assume a loop is repeated 256 times.
Thus, if DUTY is zero, an overflow never occurs and the output is always low.
If DUTY is one, an overflow will occur when ACC rolls over from FFH to 00H. This will occur once in 256 loops. Thus the output will be at a logic one, 1/256 of the time. Similarly, if DUTY is 2, ACC will roll over twice. Thus, the output will be a logic one, 2/256 of the time. If DUTY is 255, an overflow carry will always occur, except when ACC is zero. Thus, the output will be high 255/256 of the time.
Note that if the process is looped 256 times, the initial value of ACC may be any value. That is, zero added to any number never results in an overflow, one added to anything results in an overflow on only one pass through the loop, etc.
In the following program, DUTY begins at zero and ramps up to 255 and then down to zero. The program continually loops.
' Program PWM_1.BS2 ' ' Illustrates use of PWM. Causes LED on P2 to rise and fall in ' brightness. ' ' Note that this uses my implementation of the PWM command. ' ' P. H. Anderson, Dec 5, '97 DUTY VAR BYTE CYCLES VAR BYTE J VAR BYTE ' cycles counter ACCUM VAR BYTE ACCUM_NEW VAR BYTE N VAR BYTE ' index in PWM DIRS=$00FF CYCLES = 100 MAIN: FOR DUTY=0 TO 20 STEP 2 ' increase the brightness GOSUB PWM_BURST NEXT FOR DUTY=20 TO 0 STEP 2 ' decrease the brightness GOSUB PWM_BURST NEXT GOTO MAIN PWM_BURST: FOR J = 1 to CYCLES GOSUB _PWM RETURN _PWM: FOR N=0 TO 255 ACCUM_NEW = ACCUM + DUTY IF (ACCUM_NEW >= ACCUM) THEN NO_OVERFLOW ' else GOTO OVERFLOW _PWM_1: ACCUM = ACCUM_NEW NEXT RETURN OVERFLOW: OUT2=1 GOTO _PWM_1 ' continue NO_OVERFLOW: OUT2=0 GOTO _PWM_1 ' continueDiscussion.
Subroutine _PWM implements the algorithim described above. Note that the underscore (_) appearing before PWM was required as PWM is a keyword in PBASIC, much like GOTO or LOOKUP.
Always try to give your variables and labels meaningful names. In spite of my warnings, I will occassionally have a group of students who use their names and names of friends for variables and labels. After wasting many hours in getting themselves hopelessly confused, they become believers. In this case, I really wanted to use PWM as a label as it describes exactly what the subroutine does. However, there is an actual command termed PWM and an easy solution was to add the leading underscore.
In routine _PWM, DUTY is added to ACCUM. If the new value is larger than or equal to the current value of ACCUM, no overflow occurred. For example, if ACCUM is 254 and DUTY is 1, the new value is 255, and there was no overflow. Thus, a zero is output on P2.
However, if ACCUM is 254 and DUTY is 2, the new result rolled over to 1. That is, an overflow causes the new value to be less than the current value of ACCUM and a logic one is output on P2.
Routine PWM_BURST calls the _PWM routine CYCLES times. Thus DUTY and CYCLES are passed to PWM_BURST and _PWM is executed the number of times specified by CYCLES with the specified DUTY.
The main program simply ramps DUTY from 0 (LED off) to 20 (LED on 20/256 of the time. It is interesting that this corresponds to the LED being on for less than 10 percent of the time, but the eye apparently has a logarithmic response. I found there was a very noticable change in the brightness of the LED in going from 0 to 20, but in extending this to larger values did not appear all that much brighter. You might try this yourself.
In running this routine, you will note there is a noticeable flicker. The PIC on the Stamp is running with a 20 MHz clock which translates to 5 million instructions per second. However, in executing a Basic Stamp program, the PIC has a great deal of overhead; the token must be fetched from serial EEPROM, interpetted and then executed. Thus, the PIC itself is quite fast, but the Stamp appears very slow by comparison.
For example, the PIC code to bring a continually change a state is;
TOP: BCF PORTB, 0 ; set output to zero BSF PORTB, 0 ' to one GOTO TOPThis will cause the output to be low for 0.6 usecs and high for 0.2 usecs.
The Stamp code to do the same thing;
TOP: OUT0 = 0 OUT0 = 1 GOTO TOPHowever, in running this Stamp code, the PIC must fetch the commands from the serial EEPROM, interpret them and carry out the defined action and all of this increases the above time values by a factor of nominally 100.
You will also notice the delay when the program is in the MAIN and in PWM_BURST, that is, not in _PWM. Note that the only time that states are being output is in the _PWM routine itself.
This program uses the Stamp's PWM command. Note that this replaces the PWM_BURST and _PWM subroutines which were used in the previous program.
' Program PWM_2.BS2 ' ' Causes LED on P2 to rise and fall in brightness. Uses PWM commmand. ' ' P. H. Anderson, Dec 5, '97 DUTY VAR BYTE CYCLES VAR BYTE DIRS=$00FF CYCLES = 100 MAIN: debug "." FOR DUTY=0 TO 20 STEP 2 PWM 2, DUTY, CYCLES NEXT FOR DUTY=20 TO 0 STEP 2 PWM 2, DUTY, CYCLES NEXT GOTO MAINDiscussion.
Note that the format of the PWM command is;
PWM pin, duty, cycleswhere duty is a byte in the range of 0 to 255, and cycles is the number of times a routine like _PWM is called.
Each cycle requires about 1 msecs to execute. Thus, in this routine, PWM outputs at a specific duty for 100 cycles or about 100 msecs, and thus, the time required for the LED to "ramp up" or "ramp down" is nominally 1.1 seconds (11 steps of DUTY).
In running this routine, you will note there is considerably less flicker than with my implementation in the previous program and the reason is that the PWM command is now being directly executed by the PIC.
Howver, some flicker will continue to be noted when the Stamp is executing the FOR - NEXT, the GOTO and particularly when executing the DEBUG instruction. It is important to note that in lighting a lamp or in controlling a motor's speed, as presented in the next part of this tutorial, one must spend the large majority of time in executing the PWM instruction. When not executing the PWM instruction, the Stamp is not outputting any voltage on the defined pin and thus, if an excessive amount of time is spent outside the PWM instruction, the motor will slow or stop.
The Parallax implementation of the PWM command leaves the defined pin as a high impedance input on exiting the command. The importance of this will be seen in Parts 3 and 4 of this tutorial where the PWM command is being used to maintain a charge on a capacitor.
This program illustrates how the duty may be varied so as to vary the brightness of the LED. Depression of pushbutton PB11 or PB10 causes the LED to become brighter or dimmer.
' Program PWM_3.BS2 ' ' Causes LED on P2 to rise and fall in brightness in response to ' pushbutttons PB11 and PB10. Depression of PB11 causes LED to ' get brighter, depression of PB10 causes LED to get dimmer. ' ' Note there is a subtle problem with the low threshold. (See text). ' ' P. H. Anderson, Dec 7, '97 MAX_DUTY CON 30 MIN_DUTY CON 0 DUTY VAR BYTE CYCLES VAR BYTE DIRS=$00FF CYCLES = 100 SCAN: BRANCH (INC>>2), [SAME, FASTER, SLOWER, SAME] SAME: PWM 2, DUTY, CYCLES GOTO SCAN FASTER: DUTY=DUTY+2 IF DUTY<=MAX_DUTY THEN SKIP_HI_LIM DUTY = MAX_DUTY ' already at the maximum SKIP_HI_LIM: DEBUG ?DUTY PWM 2, DUTY, CYCLES GOTO SCAN SLOWER: DUTY=DUTY-2 IF DUTY>MIN_DUTY THEN SKIP_LO_LIM DUTY = MIN_DUTY ' already at the minimum SKIP_LO_LIM: DEBUG ?DUTY PWM 2, DUTY, CYCLES GOTO SCANDiscussion.
Recall that if a pusbutton is depressed, the Stamp reads the input as a logic 0. Consider the following truth table.
PB11 PB10 State Action 0 0 0 Ambiguous 0 1 1 Lower the duty 1 0 2 Increase the duty 1 1 3 Maintain the same dutyIn this case we have two pushbuttons, one associated with brighter (or motor faster) and another with dimmer (or motor slower). The actions associated with states 1, 2 and 3 are pretty clear. However, when the user depresses both "brighter" and "dimmer" at the same time, the intent of the user is a bit unclear and it is the job of the product designer (you) to deal with such ambiguous states. In this implementation, I decided to define the action for state 0 to be the same as for state 3; that is, maintain the same duty.
However, the state could well have been used for something else, say flash the LEDs or turn them off.
In the above program, the state of the two switches is read by reading input nibble C. Note that the two pushbuttons are on the higher two bits of this nibble. Thus, (INC >> 2) results in a number in the range of 0 through 3. This is mapped into an action using the powerful BRANCH instruction.
If the defined state is 0 or 3, the PWM instruction is executed with no change in the duty cycle.
However, if pushbutton PB11 is depressed resulting in binary %01 or state 1, program flow is redirected to FASTER where DUTY is increased by 2. However, as I desired that the maximum duty not exceed 30, a test is performed to verify DUTY does not exceed a this maximum value and if so, DUTY is reset to 30. The PWM instruction is executed with the new value of DUTY.
If pushbutton PB10 is depressed, program flow is directed to SLOWER where duty is decreased by 2 to a minimum value of 0.
In fact, there is a subtle problem with the SLOWER routines in PWM_3.BS2.
Consider the following snippet from PWM_3.BS2.
SLOWER: DUTY=DUTY-2 IF DUTY>MIN_DUTY THEN SKIP_LO_LIM DUTY = MIN_DUTY ' already at the minimum SKIP_LO_LIM: DEBUG ?DUTY PWM 2, DUTY, CYCLES GOTO SCANAssume DUTY is 2. Then, in subtracting 2, DUTY is zero and thus DUTY is not greater than MIN_DUTY (0). Thus, the GOTO is not executed, and DUTY is set to MIN_DUTY (0).
Now DUTY is 0. In subtracting 2, the result is $FE or 254. (Note the rollover). Thus, it is greater than 0 and thus the PWM command is executed with a duty of $FE, lighting the LED nearly as brightly as one can get when the LED is supposed to be off. The probelm continues. Two is again subtracted resulting in $FC or 252, which is certainly greater than 0 and PWM is executed with a DUTY of 252.
The problem is the fact that the value of DUTY rolled over from zero to $FF to $FE and in then doing the comparsion as to whether this is less than zero, the Stamp does not consider this to be -2, but rather 254 which is not less than zero.
There are many fixes and I am uncertain the following is the clearest. Note that a new byte variable NEW_DUTY is introduced. Two is subtracted from DUTY and this is then tested as to whether it is less than the previous value of DUTY. For example 30 - 2 would be less than 30. However, 0 - 2 = 254 is not less than the previous value of 0. That is, if NEW_DUTY is larger than DUTY, an underflow must have occurred.
Thus, in this revision, the underflow situation is detected and DUTY is set to the intended MIN_DUTY.
SLOWER: NEW_DUTY=DUTY-2 DEBUG ?DUTY IF (NEW_DUTY < DUTY) THEN SKIP_UNDERFLOW ' underflow NEW_DUTY = MIN_DUTY SKIP_UNDERFLOW: IF NEW_DUTY>MIN_DUTY THEN SKIP_LO_LIM NEW_DUTY = MIN_DUTY ' already at the minimum SKIP_LO_LIM: DUTY = NEW_DUTY PWM 2, DUTY, CYCLES GOTO SCANDo not get discouraged. These types of problems are not obvious. However, when you see strange behavior when running your program use the DEBUG command to investigate just what is causing the strange behavior. Don't get discouraged and keep fooling. It is the only way you will really learn, and this wisdom extends far beyond the Stamp.
Program PWM_1.BS2 illustrated an implementation of the PWM commmand and hopefully conveyed that although the PIC itself is relatively fast, execution of Stamp commands takes considerably longer.
Program PWM_2.BS2 illustrated the use of the use of the Stamp's PWM command and much of the flicker was eliminated as the code to implement the command is being directly executed by the PIC. However, there is a noticeable flicker during the time the program is not executing the PWM command.
The last program showed how a the duty cycle might be varied under the control of pushbutton inputs.
It also revealed that sometimes life can throw up some unanticipated and strange things in your path which can be mighty frustrating until you find and understand the problem.
On to controlling a DC Motor using the L293 Dual "H" Bridge in the next part of this tutorial.