unable to toggle a led on port pin using interrupt, went through code & can't find the problem

JohnInTX

Joined Jun 26, 2012
4,379
(2) my next step is to use interrupts to control the brightness of 1 led then move on to 4 then 8 leds & can you point me in that direction .
thank you again .
What you are looking for is a a bunch of software PWMs. There are several threads on AAC that have various ones. Here's a thread we did awhile back that shows one firmware PWM to control the brightness of an LED desk lamp. The link points at a .zip with the C code for the project. Scan the interrupt service routine and see how we did the PWM (and other things). Multiple PWMs can be implemented with the same technique. There's lots of other stuff in the thread but the key is to compute the output value of all of the PWMs into simple variables then jam those to the IO port(s) on the interrupt. After that you have the rest of the interrupt period to compute the PWM values for the next interrupt.

Here's a link to code tag information:

Have fun!

chaosenvoy

Joined Aug 6, 2017
49
What you are looking for is a a bunch of software PWMs. There are several threads on AAC that have various ones. Here's a thread we did awhile back that shows one firmware PWM to control the brightness of an LED desk lamp. The link points at a .zip with the C code for the project. Scan the interrupt service routine and see how we did the PWM (and other things). Multiple PWMs can be implemented with the same technique. There's lots of other stuff in the thread but the key is to compute the output value of all of the PWMs into simple variables then jam those to the IO port(s) on the interrupt. After that you have the rest of the interrupt period to compute the PWM values for the next interrupt.

Here's a link to code tag information:

Have fun!
@JohnInTX
thank you & i will look into it ASAP & will keep you updated

chaosenvoy

Joined Aug 6, 2017
49
@JohnInTX
hello John sorry i haven't been able to do much for a while, financial issues to get the scope for now,still doing some reading up on the page you sent me i haven't fully understand it but i will keep trying. i read it from the beginning so i wouldn't be lost to what's going on.
thank you again & hopefully by the end of the week i will have something to post.

chaosenvoy

Joined Aug 6, 2017
49
@JohnInTX
Hello John i have been looking at what you sent me & i wasn't too sure about how you want me to do it, but i have been searching the net & came across a post that i studied, understand & got it working using the interrupt code i had been using before to toggle a led.
i have seen many times what you said but i haven't been ask anyone to explain what is really happening to me i try my best to understand but sorry i'm lost. i will post the code that i'm using & an explanation of what i thing is the values & what is happening .

C:
//21st July 2018
//pic 18f2550
//Pickit2 programmer
//MikroC Pro compiler
// Interrupt PWM

sbit Led at LATA1_bit;
sbit Led_Direction at TRISA1_bit;
unsigned char counter = 50, brightness ;

void InitTimer0 () {
T0CON = 0x80; //TMR0 on,at 16 bit,internal clock,low to high transition,1 :                                  256 prescaler
TMR0H = 0xFF;
TMR0L = 0xFE;           //TMR0 load value
GIE_bit = 1;               // enable global interrupt
TMR0IE_bit = 1;        // enable interrupt
}
void interrupt() {
if ((TMR0IF_bit)&&(TMR0IE_bit)){  //if both TMR0IF & TMR0IE is true
INTCON.TMR0IF = 0;     // reset interrupt flag
TMR0H = 0xFF;              //reload timer value
TMR0L = 0xFE;
if(--counter = 0){          // decrement counter
Led = 0 ;                     // turn off led
counter =50;               // reset counter
}
if (counter == brightness)  // if counter & brightness are equal turn on Led
Led = 1;
}
}
void main() {
OSCCON = 0b1111110;          //start internal oscillator  at 8Mz
TRISA = 0b00000000;            // make all PORTA pins output
PORTA = 0;                          //turn off PORTA pins
ADCON1 = 0b00001111;        //make all analog pins as digital
CMCON = 0b00000111;         //turn off comparator
brightness = 1;                    // PWM value 2% which is on 1 tic  & off 50 tics
InitTimer0 ();
while (1){
}
}
By my understanding i'm running the oscillator at max speed 1:2 prescaler, 8 Mz internal oscillator, 16 bit timer/counter, based on my timer load value it interrupting at 500hz, so my PWM is 500hz & i have 50 steps of resolution due to counter = 50.
where i have brightness =1 //PWM value 2% should be Duty cycle value 2%.
i appreciate all the help to correct my mistakes in the code & i think it may be 51 steps in resolution due to 1 tic on + 50 tics off.
what i problems in understanding what you said in this link you sent me .

// You can do both with one timer/interrupt scheme.
Forget about the switch for now.
Start by free running TMR0 and enable its interrupt. At 4MHz internal clock it will give you an overflow interrupt every 256uS. Use that as the PWM base (256uS period, 1-of-256 resolution). You can change it later.
Strip down the code I posted to one channel and incorporate everything inside the do-while loop into the interrupt routine. You can inline the call to outPWM too. Set the remaining PWMduty to 50 (%).
Fire it up, you should get the LED running at 50% duty cycle. Play around with the fixed duty if you want.

You will probably find that the PWM base (PWMbaseK) with the 256us tik is too long 65ms and the LED will flicker. Reduce it by reducing the constant PWMbaseK. Reducing to 100 gives a base of 25.6ms. Note that the TMR0 interrupt period is still 256us, you don't have as much resolution (you changed from 256 to 100 counts) but its OK for a visible LED. The alternative would be to reload TMR0 at the interrupt (as opposed to free running it) to make the tiks happen faster but at only 4MHz you don't want to interrupt too often.

To complete the PWM:
Make the DUTY register a variable. Its value can run from 0 - PWMdutyK for 0-100%. Now the main code can just jam a value there and the PWM will run at that value with no further intervention. //

i played around with the values in my code just to get a better understanding, i hope i'm not asking too much but i really try by best to understand how to make Duty register a variable & to let the value to run from 0 - PWMdutyk . my question is can i use my code but correct whatever issues i have & incorporate PWMduty & PWMbasek to get it to work then move on to dimming & brightening a led then control of 3 leds .
i just need an explanation of what's happening in terms of doing the variable to control PWMduty & how the interrupt control the 3 different led value
Thank you in advance i hope i'm not asking too much, but whatever help is given is greatly appreciated
thank you again.

Attachments

• 1.4 KB Views: 2
Last edited by a moderator:

John P

Joined Oct 14, 2008
1,876
"8 Mz internal oscillator, 16 bit timer/counter, based on my timer load value it interrupting at 500hz, so my PWM is 500hz..."

Uh, what I'm seeing is that you're reloading the counter with 0xFF in the high byte, and 0xFE in the low byte. That gives you a magnificent count of 4 clock ticks (because you have a 1:2 prescaler and your counter will go 0xFFFE, 0xFFFF, 0x0000, then reload 0xFFFE again). I can't believe this is what you intended. You're saying that you get a 500Hz interrupt rate--how's that going to happen? With a 2MHz clock and a 2:1 prescaler, I'd have thought you need to reload the counter so as to get 2000 counts, not 2 counts.

Your comment says "256 prescaler" but setting T0CON = 0x80 gives you a 2 prescaler. What's the correct setting?

Also, if your interrupt rate were the 500Hz that you say, and you're counting down from 50, your output frequency will be 10Hz. Is that what you want?

chaosenvoy

Joined Aug 6, 2017
49
"8 Mz internal oscillator, 16 bit timer/counter, based on my timer load value it interrupting at 500hz, so my PWM is 500hz..."

Uh, what I'm seeing is that you're reloading the counter with 0xFF in the high byte, and 0xFE in the low byte. That gives you a magnificent count of 4 clock ticks (because you have a 1:2 prescaler and your counter will go 0xFFFE, 0xFFFF, 0x0000, then reload 0xFFFE again). I can't believe this is what you intended. You're saying that you get a 500Hz interrupt rate--how's that going to happen? With a 2MHz clock and a 2:1 prescaler, I'd have thought you need to reload the counter so as to get 2000 counts, not 2 counts.

Your comment says "256 prescaler" but setting T0CON = 0x80 gives you a 2 prescaler. What's the correct setting?

Also, if your interrupt rate were the 500Hz that you say, and you're counting down from 50, your output frequency will be 10Hz. Is that what you want?
@John P
Thank you for your quick response & i'm just a beginner learning on my own as much as i can on the net, yes i'm loading the high bite at 0xFF & the low bite at 0xFE which is 2 tics , i see my mistake i just got caught up in trying to run the clock as fast as possible & that's already happening where prescaler is 1:2 & 0xFF high & 0xFE low just have the interrupt firing after 2 counts,
secondly i was just modifying a code i was using to do an interrupt & forgot to change the comment when i change T0CON = 0x83 TO 0x80.
yes i did the calculation in terms of Time needed x internal osc / 4 x prescaler = Time needed x 8Mz / 4 x 2 =Time neened X 8 / 8
Time needed / 1 , because of my load value high 0xFF & low value 0xFE it's only 2 counts as you pointed out & i think you are telling me it should be longer 2000 counts..
As for the 500hz & count down from 50 my output frequency will be 10hz, i'm not looking for any particular frequency just trying to learn how to produce a PWM signal on 1 led & able to control it's brightness & move on to 3 leds.
The code i have posted is working & i don't see any flicker , when modifying brightness = 1 or brightness = 25 & i see the difference in terms of brightness.
Can i move brightness from main to some other part of the code to have better control & how ?
I will look over the the part where i said 500hz interrupt rate & see where i went wrong & post it ASAP
I don't have a scope or anything to test with i just use a visual test for now.
Thank you for all the help & i'm always willing to learn, not the sharpest tack but i will do my best.

JohnInTX

Joined Jun 26, 2012
4,379
As John_P indicates, your timer setttings need attention.

I don't know if 500Hz interrupts is what you want but at 8Mhz, you have to count 4000 Tcyc at 8MHz to get there.
Tcyc = 500ns at *MHz. 500KHz -> 2ms period. 2ms/500ns = 4000.
Since TMR1 counts up you need to set it to 65536-4000= 61536 (prescaler=1) or F060h.
But keep in mind that reloading timer 1 takes some time so your count will be inaccurate. There are ways to mitigate that (using the CCP + reset function for one) but a better way would be to use TIMER 2 as a period timer.
Prescaler = 16, Postscaler=1, PR2=250 gets you a 2ms interrupt. TIMER 2 is my choice for periodic timebases.

So, regardless of how you get your 2ms interrupt what do you do with it?
Software PWMs are implemented by 2 counters, a base counter and a duty cycle counter for each PWM. You count N interrupts to generate a steady period. In your case, you count 50 * 2ms ticks so your PWM period is 100ms.
The base counter counts the 50 interrupts.
The duty cycle counter is set to some fraction of the base counter setting and they count to 0 together. When the duty cycle counter counts to 0, it stays there until the base counter is 0. The PWM output is '1' when the duty counter is > 0 and '0' otherwise. The bigger the duty cycle counter's initial value, the longer it will take to count to 0 and the longer the output will be on.

I banged some code together to show how to do 3 software PWMs using one base counter, 3 PWM duty cycle counters and 3 PWM duty cycle setting registers. I didn't fix the basic interrupt timing. Decide on the timer setup and calculate accordingly.

Look at the interrupt code to see how each PWM duty cycle counter counts against the base counter. That plus the idea of pre-computing the NEXT PWM output values for quick output on the NEXT interrupt is key.

Code is not tested but have fun anyway.

C:
//21st July 2018
//pic 18f2550
//Pickit2 programmer
//MikroC Pro compiler
// Interrupt PWM

// 3 software PWMs from one base tik (Didn't check TIMER0 interrupt)
// JohnInTX  7/23/2018

// PWM outputs on PORTA - RA0->RA2
#define PWM1mask 0x01 // bit 00000001 RA0 is PWM1 on port out and in RAM image
#define PWM2mask 0x02 // bit 00000010 RA1 is PWM2 on port out and in RAM image
#define PWM3mask 0x04 // bit 00000100 RA2 is PWM3 on port out and in RAM image

#define PWMSmask 0b00000111 // 3 pwms take 3 bits

unsigned char PWM1duty, PWM2duty, PWM3duty;  // duty cycle setpoint 0->PWMbase
unsigned char PWM1dutyK, PWM2dutyK, PWM3dutyK; // duty cycle counter
unsigned char PWMbaseK;  // PWM base counter
#define  PWMbase 50  // PWM base setpoint
unsigned char PWMoutImage;  // current PWM on/off image
// 00000ppp where p is each PWM's output value this cycle

void InitTimer0 () {
T0CON = 0x80; //TMR0 on,at 16 bit,internal clock,low to high transition,1 :  256 prescaler
TMR0H = 0xFF;
TMR0L = 0xFE;  //TMR0 load value  <--- NEEDS REVISION!

PWMbaseK = PWMbase;  // init base counter and PWMs
PWM1dutyK = PWM2dutyK = PWM3dutyK = 0;
PWMoutImage = 0;

TMR0IF_bit = 0;  // clear any stray interrupts
TMR0IE_bit = 1;  // enable interrupt
GIE_bit = 1;  // enable global interrupt last
}

// PWM control.
// Timer 0 is base timer tick for 3 PWMs.  The code counts 'PWMbase' interrupts for ONE PWM cycle
// Each of the 3 PWMs is controlled by PWMxdutyK which counts down to 0 from PWMxduty where
// 0 <= PWMxduty <= PWMbase.
// When nonZero, the PWM output is turned ON. when 0, the PWM output is turned off.  The duty cycle counters
// are set from zero to PWMbase (0-100% duty cycle) each time the PWM base counter counts to 0 and is reset to PWMbase.
// The settings for each PWM are in PWMxduty.  That value is copied to the PWMxdutyK when the base counter is reset.
// PWM outputbits  are pre-computed to avoid jitter.  As soon as able after the interrupt, the output bits are
// combined with the current port values and output to LATA.  After that, new values can be computed at leisure.

void interrupt() {
unsigned char PWMtemp;  // temp reg
if ((TMR0IF_bit)&&(TMR0IE_bit)){  //if both TMR0IF & TMR0IE is true
INTCON.TMR0IF = 0;  // reset interrupt flag
TMR0H = 0xFF;  //reload timer value
TMR0L = 0xFE;
// update PWM outputs with PRE-CALCULATED PWM bits..
PWMtemp = LATA;  // get current port bits
PWMtemp &= ~PWMSmask;  // clear out old PWM out bits
PWMtemp |= PWMoutImage;  // OR in the current PWM out bits
LATA = PWMtemp;  // write value to LATA with other bits unchanged

// then calculate PWM output bits for NEXT interrupt

if(--PWMbaseK){  // decrement base, iff non-zero, decrement any NZ PWM duty counters
if(PWM1dutyK)--PWM1dutyK;  // if duty counter 1 > 0 decrement it
if(PWM2dutyK)--PWM2dutyK;  // if duty counter 2 > 0 decrement it
if(PWM3dutyK)--PWM3dutyK;  // if duty counter 3 > 0 decrement it
}
//PWMbase was decremented and nonZero so each NZ PWM out was also decremented

else{  // PWM base was decremented and is now 0
PWMbaseK = PWMbase;  // reset base counter
PWM1dutyK = PWM1duty; // reset each duty cycle counter to current duty cycle
PWM2dutyK = PWM2duty;
PWM3dutyK = PWM3duty;
}
// finally, set the PWM output image to reflect the duty cycle counters (0 or nonZero)
PWMoutImage = 0;  // start with PWM out bits in a known state (0)
// for each NZ duty cycle counter, set out mask bit to 1
// PWM output mask now reflects the PWM bit outputs that will be output on NEXT base interrupt
} // TIMER 0
}//IRQ

void main() {
OSCCON = 0b1111110;  //start internal oscillator  at 8Mz
TRISA = 0b00000000;  // make all PORTA pins output
LATA = 0;  //turn off PORTA pins
ADCON1 = 0b00001111;  //make all analog pins as digital
CMCON = 0b00000111;  //turn off comparator

InitTimer0 ();
PWM1duty = 10;  // 10/50  (20%)
PWM2duty = 25;  // 25/50 (50%)
PWM3duty = 0;  // 0/50 = OFF

while (1);

} // main

Last edited:

chaosenvoy

Joined Aug 6, 2017
49
As John_P indicates, your timer setttings need attention.

I don't know if 500Hz interrupts is what you want but at 8Mhz, you have to count 4000 Tcyc at 8MHz to get there.
Tcyc = 500ns at *MHz. 500KHz -> 2ms period. 2ms/500ns = 4000.
Since TMR1 counts up you need to set it to 65536-4000= 61536 (prescaler=1) or F060h.
But keep in mind that reloading timer 1 takes some time so your count will be inaccurate. There are ways to mitigate that (using the CCP + reset function for one) but a better way would be to use TIMER 2 as a period timer.
Prescaler = 16, Postscaler=1, PR2=250 gets you a 2ms interrupt. TIMER 2 is my choice for periodic timebases.

So, regardless of how you get your 2ms interrupt what do you do with it?
Software PWMs are implemented by 2 counters, a base counter and a duty cycle counter for each PWM. You count N interrupts to generate a steady period. In your case, you count 50 * 2ms ticks so your PWM period is 100ms.
The base counter counts the 50 interrupts.
The duty cycle counter is set to some fraction of the base counter setting and they count to 0 together. When the duty cycle counter counts to 0, it stays there until the base counter is 0. The PWM output is '1' when the duty counter is > 0 and '0' otherwise. The bigger the duty cycle counter's initial value, the longer it will take to count to 0 and the longer the output will be on.

I banged some code together to show how to do 3 software PWMs using one base counter, 3 PWM duty cycle counters and 3 PWM duty cycle setting registers. I didn't fix the basic interrupt timing. Decide on the timer setup and calculate accordingly.

Look at the interrupt code to see how each PWM duty cycle counter counts against the base counter. That plus the idea of pre-computing the NEXT PWM output values for quick output on the NEXT interrupt is key.

Code is not tested but have fun anyway.

C:
//21st July 2018
//pic 18f2550
//Pickit2 programmer
//MikroC Pro compiler
// Interrupt PWM

// 3 software PWMs from one base tik (Didn't check TIMER0 interrupt)
// JohnInTX  7/23/2018

// PWM outputs on PORTA - RA0->RA2
#define PWM1mask 0x01 // bit 00000001 RA0 is PWM1 on port out and in RAM image
#define PWM2mask 0x02 // bit 00000010 RA1 is PWM2 on port out and in RAM image
#define PWM3mask 0x04 // bit 00000100 RA2 is PWM3 on port out and in RAM image

#define PWMSmask 0b00000111 // 3 pwms take 3 bits

unsigned char PWM1duty, PWM2duty, PWM3duty;  // duty cycle setpoint 0->PWMbase
unsigned char PWM1dutyK, PWM2dutyK, PWM3dutyK; // duty cycle counter
unsigned char PWMbaseK;  // PWM base counter
#define  PWMbase 50  // PWM base setpoint
unsigned char PWMoutImage;  // current PWM on/off image
// 00000ppp where p is each PWM's output value this cycle

void InitTimer0 () {
T0CON = 0x80; //TMR0 on,at 16 bit,internal clock,low to high transition,1 :  256 prescaler
TMR0H = 0xFF;
TMR0L = 0xFE;  //TMR0 load value  <--- NEEDS REVISION!

PWMbaseK = PWMbase;  // init base counter and PWMs
PWM1dutyK = PWM2dutyK = PWM3dutyK = 0;
PWMoutImage = 0;

TMR0IF_bit = 0;  // clear any stray interrupts
TMR0IE_bit = 1;  // enable interrupt
GIE_bit = 1;  // enable global interrupt last
}

// PWM control.
// Timer 0 is base timer tick for 3 PWMs.  The code counts 'PWMbase' interrupts for ONE PWM cycle
// Each of the 3 PWMs is controlled by PWMxdutyK which counts down to 0 from PWMxduty where
// 0 <= PWMxduty <= PWMbase.
// When nonZero, the PWM output is turned ON. when 0, the PWM output is turned off.  The duty cycle counters
// are set from zero to PWMbase (0-100% duty cycle) each time the PWM base counter counts to 0 and is reset to PWMbase.
// The settings for each PWM are in PWMxduty.  That value is copied to the PWMxdutyK when the base counter is reset.
// PWM outputbits  are pre-computed to avoid jitter.  As soon as able after the interrupt, the output bits are
// combined with the current port values and output to LATA.  After that, new values can be computed at leisure.

void interrupt() {
unsigned char PWMtemp;  // temp reg
if ((TMR0IF_bit)&&(TMR0IE_bit)){  //if both TMR0IF & TMR0IE is true
INTCON.TMR0IF = 0;  // reset interrupt flag
TMR0H = 0xFF;  //reload timer value
TMR0L = 0xFE;
// update PWM outputs with PRE-CALCULATED PWM bits..
PWMtemp = LATA;  // get current port bits
PWMtemp &= ~PWMSmask;  // clear out old PWM out bits
PWMtemp |= PWMoutImage;  // OR in the current PWM out bits
LATA = PWMtemp;  // write value to LATA with other bits unchanged

// then calculate PWM output bits for NEXT interrupt

if(--PWMbaseK){  // decrement base, iff non-zero, decrement any NZ PWM duty counters
if(PWM1dutyK)--PWM1dutyK;  // if duty counter 1 > 0 decrement it
if(PWM2dutyK)--PWM2dutyK;  // if duty counter 2 > 0 decrement it
if(PWM3dutyK)--PWM3dutyK;  // if duty counter 3 > 0 decrement it
}
//PWMbase was decremented and nonZero so each NZ PWM out was also decremented

else{  // PWM base was decremented and is now 0
PWMbaseK = PWMbase;  // reset base counter
PWM1dutyK = PWM1duty; // reset each duty cycle counter to current duty cycle
PWM2dutyK = PWM2duty;
PWM3dutyK = PWM3duty;
}
// finally, set the PWM output image to reflect the duty cycle counters (0 or nonZero)
PWMoutImage = 0;  // start with PWM out bits in a known state (0)
// for each NZ duty cycle counter, set out mask bit to 1
// PWM output mask now reflects the PWM bit outputs that will be output on NEXT base interrupt
} // TIMER 0
}//IRQ

void main() {
OSCCON = 0b1111110;  //start internal oscillator  at 8Mz
TRISA = 0b00000000;  // make all PORTA pins output
LATA = 0;  //turn off PORTA pins
ADCON1 = 0b00001111;  //make all analog pins as digital
CMCON = 0b00000111;  //turn off comparator

InitTimer0 ();
PWM1duty = 10;  // 10/50  (20%)
PWM2duty = 25;  // 25/50 (50%)
PWM3duty = 0;  // 0/50 = OFF

while (1);

} // main
@JohnInTX
Thank you so much i will look at it & do my best to understand how it all works & play around with it , i really appreciate the time you took to explain it all i know it will help a lot when i go through it all.
Will post the results when i think i have my head wrap around it.
Thank you @JohnInTX & @John P

John P

Joined Oct 14, 2008
1,876
The way I have done this is to do a comparison for each PWM duty cycle versus a single counter. Also, you can hold the final output (a byte containing the bits that get sent to a port) at the end of the interrupt, and send it out at the start of the next interrupt, so any variation in the processing time doesn't affect it.

Code:
  latc = output_byte;                      // This will be left from previous cycle, but it should be OK
output_byte = 0x7;                      // Use latc because it's more foolproof than portc, assuming this chip has it

count++;
if (count >= MAXIMUM)
count = 0;

if (count >= PWM1duty)
output_byte.F0 = 0;
if (count >= PWM2duty)
output_byte.F1 = 0;
if (count >= PWM3duty)
output_byte.F2 = 0;               // Note mismatch between PWM1 etc and bits F0 etc

chaosenvoy

Joined Aug 6, 2017
49
As John_P indicates, your timer setttings need attention.

I don't know if 500Hz interrupts is what you want but at 8Mhz, you have to count 4000 Tcyc at 8MHz to get there.
Tcyc = 500ns at *MHz. 500KHz -> 2ms period. 2ms/500ns = 4000.
Since TMR1 counts up you need to set it to 65536-4000= 61536 (prescaler=1) or F060h.
But keep in mind that reloading timer 1 takes some time so your count will be inaccurate. There are ways to mitigate that (using the CCP + reset function for one) but a better way would be to use TIMER 2 as a period timer.
Prescaler = 16, Postscaler=1, PR2=250 gets you a 2ms interrupt. TIMER 2 is my choice for periodic timebases.

So, regardless of how you get your 2ms interrupt what do you do with it?
Software PWMs are implemented by 2 counters, a base counter and a duty cycle counter for each PWM. You count N interrupts to generate a steady period. In your case, you count 50 * 2ms ticks so your PWM period is 100ms.
The base counter counts the 50 interrupts.
The duty cycle counter is set to some fraction of the base counter setting and they count to 0 together. When the duty cycle counter counts to 0, it stays there until the base counter is 0. The PWM output is '1' when the duty counter is > 0 and '0' otherwise. The bigger the duty cycle counter's initial value, the longer it will take to count to 0 and the longer the output will be on.

I banged some code together to show how to do 3 software PWMs using one base counter, 3 PWM duty cycle counters and 3 PWM duty cycle setting registers. I didn't fix the basic interrupt timing. Decide on the timer setup and calculate accordingly.

Look at the interrupt code to see how each PWM duty cycle counter counts against the base counter. That plus the idea of pre-computing the NEXT PWM output values for quick output on the NEXT interrupt is key.

Code is not tested but have fun anyway.

C:
//21st July 2018
//pic 18f2550
//Pickit2 programmer
//MikroC Pro compiler
// Interrupt PWM

// 3 software PWMs from one base tik (Didn't check TIMER0 interrupt)
// JohnInTX  7/23/2018

// PWM outputs on PORTA - RA0->RA2
#define PWM1mask 0x01 // bit 00000001 RA0 is PWM1 on port out and in RAM image
#define PWM2mask 0x02 // bit 00000010 RA1 is PWM2 on port out and in RAM image
#define PWM3mask 0x04 // bit 00000100 RA2 is PWM3 on port out and in RAM image

#define PWMSmask 0b00000111 // 3 pwms take 3 bits

unsigned char PWM1duty, PWM2duty, PWM3duty;  // duty cycle setpoint 0->PWMbase
unsigned char PWM1dutyK, PWM2dutyK, PWM3dutyK; // duty cycle counter
unsigned char PWMbaseK;  // PWM base counter
#define  PWMbase 50  // PWM base setpoint
unsigned char PWMoutImage;  // current PWM on/off image
// 00000ppp where p is each PWM's output value this cycle

void InitTimer0 () {
T0CON = 0x80; //TMR0 on,at 16 bit,internal clock,low to high transition,1 :  256 prescaler
TMR0H = 0xFF;
TMR0L = 0xFE;  //TMR0 load value  <--- NEEDS REVISION!

PWMbaseK = PWMbase;  // init base counter and PWMs
PWM1dutyK = PWM2dutyK = PWM3dutyK = 0;
PWMoutImage = 0;

TMR0IF_bit = 0;  // clear any stray interrupts
TMR0IE_bit = 1;  // enable interrupt
GIE_bit = 1;  // enable global interrupt last
}

// PWM control.
// Timer 0 is base timer tick for 3 PWMs.  The code counts 'PWMbase' interrupts for ONE PWM cycle
// Each of the 3 PWMs is controlled by PWMxdutyK which counts down to 0 from PWMxduty where
// 0 <= PWMxduty <= PWMbase.
// When nonZero, the PWM output is turned ON. when 0, the PWM output is turned off.  The duty cycle counters
// are set from zero to PWMbase (0-100% duty cycle) each time the PWM base counter counts to 0 and is reset to PWMbase.
// The settings for each PWM are in PWMxduty.  That value is copied to the PWMxdutyK when the base counter is reset.
// PWM outputbits  are pre-computed to avoid jitter.  As soon as able after the interrupt, the output bits are
// combined with the current port values and output to LATA.  After that, new values can be computed at leisure.

void interrupt() {
unsigned char PWMtemp;  // temp reg
if ((TMR0IF_bit)&&(TMR0IE_bit)){  //if both TMR0IF & TMR0IE is true
INTCON.TMR0IF = 0;  // reset interrupt flag
TMR0H = 0xFF;  //reload timer value
TMR0L = 0xFE;
// update PWM outputs with PRE-CALCULATED PWM bits..
PWMtemp = LATA;  // get current port bits
PWMtemp &= ~PWMSmask;  // clear out old PWM out bits
PWMtemp |= PWMoutImage;  // OR in the current PWM out bits
LATA = PWMtemp;  // write value to LATA with other bits unchanged

// then calculate PWM output bits for NEXT interrupt

if(--PWMbaseK){  // decrement base, iff non-zero, decrement any NZ PWM duty counters
if(PWM1dutyK)--PWM1dutyK;  // if duty counter 1 > 0 decrement it
if(PWM2dutyK)--PWM2dutyK;  // if duty counter 2 > 0 decrement it
if(PWM3dutyK)--PWM3dutyK;  // if duty counter 3 > 0 decrement it
}
//PWMbase was decremented and nonZero so each NZ PWM out was also decremented

else{  // PWM base was decremented and is now 0
PWMbaseK = PWMbase;  // reset base counter
PWM1dutyK = PWM1duty; // reset each duty cycle counter to current duty cycle
PWM2dutyK = PWM2duty;
PWM3dutyK = PWM3duty;
}
// finally, set the PWM output image to reflect the duty cycle counters (0 or nonZero)
PWMoutImage = 0;  // start with PWM out bits in a known state (0)
// for each NZ duty cycle counter, set out mask bit to 1
// PWM output mask now reflects the PWM bit outputs that will be output on NEXT base interrupt
} // TIMER 0
}//IRQ

void main() {
OSCCON = 0b1111110;  //start internal oscillator  at 8Mz
TRISA = 0b00000000;  // make all PORTA pins output
LATA = 0;  //turn off PORTA pins
ADCON1 = 0b00001111;  //make all analog pins as digital
CMCON = 0b00000111;  //turn off comparator

InitTimer0 ();
PWM1duty = 10;  // 10/50  (20%)
PWM2duty = 25;  // 25/50 (50%)
PWM3duty = 0;  // 0/50 = OFF

while (1);

} // main
@JohnInTX
Thank you i was confused before about the interrupts with regards to the number of leds & the amount of interrupts will stay the same with 4 or 8 leds, i saw a few posts on software PWM & the amount of interrupts but i didn't catch on to it..
This is my understanding of what you told me, lets say i have 4 guys, base counter guy counts out numbers from 10 down to 0, the 3 other guys each have an on & off switch controlling a led, guy red gets instructions whenever he hears 7 he turn on the led & 0 he turns the led off , guy green instruction is to turn on the led whenever he hears 5 & turn it off at 0, guy blue instructions is to turn on at 3 & off at 0, so i have 1 base counter controlling all 3 of them but at different duty cycle. to change the duty cycle just change the instructions to them & the base counter interrupt stays the same. Oh & changing the base counter is changing the resolution so if you have 10 as the base counter & you change it to 50 you have more steps in the resolution but you may need to speed up the clock if you are not at full speed. = prescaler.
The code you wrote is working with TMR0, but i will do it with TMR2 & see if i can get going.
I'm getting to the other parts of the code & understanding what they are all for & how they work, i don't know anything about ORing bits i saw a few tutorials on it before but i didn't get it, can you send me a link where i can read up on it, thanks in advance. If i have any problem with the rest of the code i will leave a message for any help where needed ,
Thank you again & i will keep you posted .

JohnInTX

Joined Jun 26, 2012
4,379
@chaosenvoy
ORing bits in a register with another register value is a way of selectively setting bits in the register. The OR function behaves according to the truth table below (shown with the logic gate equivalent). Since the PIC uses 8bit registers, each OR operation operates on 8 bits at one time. Since we only want to operate on one bit at a time here, we use a 'mask' value to select the bit in the register.

The '|' operator in C does a bitwise OR on each bit in the register:
00000001 the 'mask' value to select the LSbit
bbbbbbbb ORed with a register (b is the value of the bit 0 or 1) gives:
-------------
bbbbbbb1

Note that the new 1 bit in the result was set by ORing with the 1 bit in the mask. It doesn't matter what it was before, anything ORed with 1 is 1. Likewise, since any value ORed with 0 equals the same value, those bits are unchanged. That is how you can set certain bits in a register while leaving others alone. If you look at the statement:
PWM1mask is the constant value 00000001 and PWMoutImage is the register to operate on. After the operation, the LSbit of PWM1outImage will be 1 regardless of what it was originally and the other 7 bits will be unchanged. Each of the 3 statements operates on a different bit in PWMoutImage, setting them or leaving them at their current value (0 because that is what the register starts at) while leaving the other bits unchanged. Nice.

The image is from the AAC textbook here:

Your '4 guys' PWM analogy is essentially correct, too.

Making progress!

EDIT: how would you use the 'AND' function to selectively clear bits in a register?
If you used 'XOR' (exclusive OR) in the example above, what would happen to the LSbit in the register?

Last edited:

chaosenvoy

Joined Aug 6, 2017
49
@chaosenvoy
ORing bits in a register with another register value is a way of selectively setting bits in the register. The OR function behaves according to the truth table below (shown with the logic gate equivalent). Since the PIC uses 8bit registers, each OR operation operates on 8 bits at one time. Since we only want to operate on one bit at a time here, we use a 'mask' value to select the bit in the register.

The '|' operator in C does a bitwise OR on each bit in the register:
00000001 the 'mask' value to select the LSbit
bbbbbbbb ORed with a register (b is the value of the bit 0 or 1) gives:
-------------
bbbbbbb1

Note that the new 1 bit in the result was set by ORing with the 1 bit in the mask. It doesn't matter what it was before, anything ORed with 1 is 1. Likewise, since any value ORed with 0 equals the same value, those bits are unchanged. That is how you can set certain bits in a register while leaving others alone. If you look at the statement:
PWM1mask is the constant value 00000001 and PWMoutImage is the register to operate on. After the operation, the LSbit of PWM1outImage will be 1 regardless of what it was originally and the other 7 bits will be unchanged. Each of the 3 statements operates on a different bit in PWMoutImage, setting them or leaving them at their current value (0 because that is what the register starts at) while leaving the other bits unchanged. Nice.

The image is from the AAC textbook here:

Your '4 guys' PWM analogy is essentially correct, too.

Making progress!

EDIT: how would you use the 'AND' function to selectively clear bits in a register?
If you used 'XOR' (exclusive OR) in the example above, what would happen to the LSbit in the register?

chaosenvoy

Joined Aug 6, 2017
49
@JohnInTX
thanks a lot will have a look & reply soon

chaosenvoy

Joined Aug 6, 2017
49
Hello @JohnInTX
Sorry i haven't been able to put in all the time i need with the code but i'm working on it, i'm getting there.
Hopefully by Wednesday 26th September i will post my understanding of the code.
As for OR & XOR i understand it well in terms of what is the result if you have a 1 or a 0 at their inputs & the results if you are ORing or XORing bits. just have some problem of memorizing their logic table & i have to look at my notes.
Thank you again.

chaosenvoy

Joined Aug 6, 2017
49
Hello @JohnInTX sorry for the long delay in responding, i have been doing lots of reading up on some other stuff & going over my tutorials on writing C- sources code.i understand most of what's going on in the code & i have been trying to figure out how to fade in & out the leds but no success. i'm asking if you can help me with the fading of each the led.
if i need to selectively clear LSB in a register using the ''AND '' function
eg, PWMoutimage & = PWM1mask, the LSB will be cleared because only when both bits are 1 it will be set, outimage starts out at 0.
if i XOR the example above
00000001
bbbbbbbb
....................
00000001

the LSB will be 1 providing bbbbbbbb are all 0s
The ''XOR'' (exclusive OR) truth table states if both bits are 1s or 0s the bit will be cleared & if only 1 bit is 1 or 0 the bit will be set
.
I wouldn't be able to get the DDS140 till next year, they don't have any in stock to ship to my country through Amazon,
that's 1 of the reason why i'm doing lots of reading & revision till i get it
i've been looking up on 'Arrays' trying my best to understand how to create & use them,
i also saw something on Bit Angle Modulation & was wondering if it's easier to understand & work with in terms of controlling leds.
when i have a good understanding of interrupts & softpwm i will take a deeper look into it.
Thanks very much for all the help @JohnInTX .

JohnInTX

Joined Jun 26, 2012
4,379
if i XOR the example above
0000000 1
0000000 0
....................
0000000 1

the LSB will be 1 providing bbbbbbbb are all 0s
The ''XOR'' (exclusive OR) truth table states if both bits are 1s or 0s the bit will be cleared & if only 1 bit is 1 or 0 the bit will be set
Correct. I've called out the bit values explicitly. You can't XOR an unknown value 'b' with anything and know what the result is. A good way to remember XOR is that the result is '1' if the inputs are different, '0' if they are the same.

if i need to selectively clear LSB in a register using the ''AND '' function
eg, PWMoutimage & = PWM1mask, the LSB will be cleared because only when both bits are 1 it will be set, outimage starts out at 0.
?? If 'outimage' starts at 0, any AND function on 'outimage' will result in 0. As described, the AND can only clear bits. To clear selected bits in a register, you use the compliment of the mask i.e. all '1' except the bit(s) you want to clear:
PWMoutimage & = ~PWM1mask; // the '~' operator takes the compliment of the mask.
Example:
C:
#define PWMout1 0b00000001  // Where the PWM outputs are on a port
#define PWMout2 0b00000010
#define PWMout3 0b00000100
// more as required
#define PWMout6 0b01000000

unsigned char PWMoutimage;   // builds PWM output image in one byte to write to PORT when you are finished updating the PWMs

//------- Set bit(s) in PWMoutimage  ----------------

{
PWMoutimage |= mask;  // for each '1' bit in 'mask' the corresponding bit in PWMoutimage is set to '1' because 1 OR x = 1
}

// --------Clear bit(s) in PWMoutimage ------------
{
mask = ~mask;  // the local copy of 'mask' is first complemented - all bits except the mask bit is are to '1', the mask bit is set to '0'
PWMoutimage &= mask;  // for each original '1' bit in 'mask' the corresponding bit in PWMoutimage is cleared to '0' because
// the complemented mask is ANDed with PWMoutimage.
}

setPWMbits(PWMout1);  // sets bit 0x1
clearPWMbits(PWMout3; // clears bit 0x04
So clearing the PWMout3 bit does this:
complement it:

Then AND it with PWMoutimage:
11111011
xxxxxdxx PWMoutimage where x bits are unchanged (ANDed with '1'), d is the current value of PWMout3
--------------
xxxxx0xx

Note that I used functions but you don't have to do so. Functions or macros are preferred for easier code maintenance, moving bits around etc.

Hope that helps!