The preceding section covered the hardware side of this learning module. This section covers the software side. In particular, this section shows how to use the OC4 interrupt to generate a pulse width modulated signal.
Recall that the OC4han() ISR is called every time
the output compare register TOC4 equals the counter
register TCNT. Also recall that the generation of
the output compare event can also change the state of an
output pin if the appropriate bit in the control register
TCTL1 is set. We can use these two facts to revise
the OC4han() function so it automatically generates
a pulse width modulated wave.
The following listing shows the init() function and
the revised OC4han() function.
unsigned int _Time;
unsigned int _rate_high;
unsigned int _rate_low;
#pragma interrupt_handler OC4han()
void OC4han(void){
if(TCTL1 == 0x08){
TOC4 = TOC4 + _rate_high;
TCTL1 |= OM4; TCTL1 |= OL4;
_Time=_Time+1;
}else{
TOC4 = TOC4 + _rate_low;
TCTL1 |= OM4; TCTL1 &= ~OL4;
}
TFLG1 |= OC4F;
}
void init(void){
asm(" sei");
CONFIG = 0x04;
_Time=0;
_rate_low = 127;
_rate_high = 128;
TMSK2 = 0x03;
TMSK1 |= OC4I;
TFLG1 |= OC4F;
TCTL1 = OM4;
TOC4 = TCNT+_rate_low;
asm(" cli");
}
Let's first look at the global variables associated with
our revised OC4 handler. There is the usual _Time
global variable that we used earlier. But we have also
introduced two additional global variables _rate_low
and _rate_high. These two global variables are used
to set the duty cycle of the pwm signal.
Now let's look at the initialization function
init(). This function initializes _Time to
zero and initializes the variables _rate_low and
_rate_high to 127 and 128, respectively. The
instruction TMSK2 = 0x03 sets the clock update rate
at 500 nanoseconds. The instruction TMSK1 |= OC4I
enables the OC4 interrupt and the instruction
TFLG1 |= OC4F
acknowledges any previously caught OC4 interrupts.
The next instruction is TCTL1 = OM4. OM4 is
the logical name for one of the bits in the TCTL1
register. When this bit is set, then the output pin
OC4 is cleared (set to zero). The final instruction
in the initialization function
(TOC4 = TCNT+_rate_low)
sets the deadline for the next OC4 event.
What our initialization function has done is to set the
output pin OC4 low. The next OC4 event is
triggered _rate_low ticks into the future. When
this occurs then the OC4han() is executed. So now
let's look at the revised OC4han() function.
The first thing we note here is that our handler is much
more complex than the original OC4han() function we
used before. In this case, the first thing the ISR does is
check to see if TCTL1 == OM4. If this happens, then
we know that the output pin OC4 is low. So we reset
the output compare register TOC4 to trigger
_rate_high ticks into the future and then set the
output pin high using the instructions TCTL1 |= OM4
and TCTL1 |= OL4. We then have the global time
variable _Time.
If TCTL does not equal OM4, then we know that
this OC4 interrupt must have occurred when the
output pin OC4 was high. We then set the output pin
low with the instructions TCTL1 |= OM4 and
TCTL1 &= ~OL4. The output compare register
TOC4 is then set to trigger the output compare event
_rate_high ticks into the future.
There are a couple of things to note about this function.
First, we see that it forces the output pin OC4 to
be toggled between its high and low state. The amount of
time that the pin remains low is given by the global
variable _rate_low. The amount of time that the pin
remains high is given by the global variable
_rate_high. This change in pin state occurs every
time we execute the OC4han function. The end result
is that the voltage over pin OC4 is a pulse width
modulated wave whose duty cycle is given by
duty_cycle = _rate_high/(_rate_high + _rate_low);Initially
_rate_high and _rate_low are set to
128 and 127, respectively. So when our program starts the
output pin OC4 is already generating a 50 percent
duty cycle PWM signal.
Note that the variables _rate_high and
_rate_low are both global. This means that they can
be set from within your program to modify the duty cycle of
the PWM signal on pin OC4. A simple function that
modifies this duty cycle is given below.
void set_pwm(short idur){
short ilow = 20;
short ihigh = 240;
if( idur >= ihigh) idur=ihigh;
if( idur <= ilow) idur=ilow;
_rate_high = idur;
_rate_low = 255-idur;
}
All that this function does is set the global variables
_rate_high and _rate_low. The period of the
PWM wave is set to 256 ticks or 128 idur. Note
that we've provided some clipping on the variable
idur in order to keep it in the range between 20 and
240. The reason for this is that if we go outside of these
ranges, then the shorter half of the PWM wave is so short
that the OC4 interrupt will fire before the
interrupt handler has completed executing. This type of
timing problem can often be very difficult to debug.