mborgerson
Well-known member
In response to a thread discussing issues with the interval timer in the "Suggestions and Bug Reports" forum:
(https://forum.pjrc.com/threads/62827-IntervalTimer-update-and-begin-documentation-improvement
I decided to see what minimum amount of generic C code it takes to implement a multi-channel PWM generator suitable for ~12-bit resolution and a PWM period of 50milliSeconds.
It turns out that you only need a single IntervalTimer, a few data arrays and about 40 lines of code to implement a 4-channel PWM generator. The timer IRQ handler only takes about 60nanoSeconds to execute on a T4.0 at 60MHz. Of course, you need some more code to set things up and make the values change so that you can evaluate things with an oscilloscope. Still, the demo would be less than 100 lines were it not for my verbose comments at the beginning ;-)
(https://forum.pjrc.com/threads/62827-IntervalTimer-update-and-begin-documentation-improvement
I decided to see what minimum amount of generic C code it takes to implement a multi-channel PWM generator suitable for ~12-bit resolution and a PWM period of 50milliSeconds.
It turns out that you only need a single IntervalTimer, a few data arrays and about 40 lines of code to implement a 4-channel PWM generator. The timer IRQ handler only takes about 60nanoSeconds to execute on a T4.0 at 60MHz. Of course, you need some more code to set things up and make the values change so that you can evaluate things with an oscilloscope. Still, the demo would be less than 100 lines were it not for my verbose comments at the beginning ;-)
Code:
/**************************************************
Simple multi-channel PWM output demo
This PWM generator can use any digital output pin
and needs only a single interval timer.
This is a lot like other software PWM functions in that
it is best suited to fairly long PWM periods if you
need high resolution.
This demo code has a few constraints:
* all the PWM channels have the same period.
* The channels are synchronized in that all channel outputs
go high at the same time--that could be problematic in
when controlling high-power hardware.
* unlike many of the fancier libraries, this is all standard C
except for the IntervalTimer and it is written for the T4.x, but
will probably work on the T3.X, with a higher fraction of CPU usage.
With some more complex data structures, this algorithm could be enhanced
to have different periods and resolutions on different channels (within the
constraints of the fundamental timer interrupt interval)
mborgerson 9/4/2020
*******************************************************************/
IntervalTimer pwmTimer;
#define PWMCHANNELS 4
#define PWMRESOLUTION 10 // Smallest increment of pulse width in microseconds
#define PWMPERIOD 5000 // Period of PWM in increments of PWMRESOLUTION---5000 x 10 gives 20Hz frequency
// Oscilloscope marker to show duration of timer chore
#define TMRLOW digitalWriteFast(tmrpin, LOW);
#define TMRHI digitalWriteFast(tmrpin, HIGH);
const uint16_t pwmin = 1;
const uint16_t pwmax = PWMPERIOD - 1; // 4999 for demo
// set pins near T4.0 board end as PWM outputs
const uint16_t chanpins[PWMCHANNELS] = {11, 12, 13, 14};
const int tmrpin = 0;
// Pwm High Counts set how long output is high at start of period
// these values get set in main program and are read by timer interrupt service routine
volatile uint16_t pwmHighCounts[PWMCHANNELS]; // initial values set in InitilizePWM()
const char compileTime [] = "\n\nInterval Timer PWM Test compiled on " __DATE__ " " __TIME__;
// set the pins as outputs and set an initial value for PWM
void initializePWM(uint16_t numchans, const uint16_t *pins, uint16_t initialvalue) {
uint16_t i;
for (i = 0; i < numchans; i++) {
pinMode(pins[i], OUTPUT);
digitalWriteFast(pins[i], 0);
pwmHighCounts[i] = initialvalue;
}
}
// pwm interval timer interrupt service routine
//NOTE: with 4 channels of PWM, the PWM chore executes in 30 to
// 60 nanoseconds with T4.x at 600MHz. (<1% of CPU bandwidth);
void pwmChore(void){
uint16_t i;
static uint16_t pcount; // for longer periods, this might need to be uint32_t
TMRHI
for(i=0; i< PWMCHANNELS; i++){
if(pcount >= pwmHighCounts[i]) digitalWriteFast(chanpins[i],LOW);
}
pcount++;
if(pcount >= PWMPERIOD){// set all pins high and restart count
for(i=0; i< PWMCHANNELS; i++) digitalWriteFast(chanpins[i], HIGH);
pcount = 0;
}
TMRLOW
}
void setup() {
Serial.begin(9600);
delay(1000); // wait for PC to connect
Serial.println(compileTime);
pinMode(tmrpin, OUTPUT); // pin to time length of pwmChore
initializePWM(PWMCHANNELS,chanpins, 10);
pwmTimer.begin(pwmChore, PWMRESOLUTION);
Serial.println("Starting PWM output");
}
void loop() {
// Generate sawtooth ramp changes to pwm channels
// Channel values are changed ~10 times per second
delay(100); // sets speed of channel updates
RampUp(0,pwmHighCounts, 5);
RampDown(1, pwmHighCounts,7);
RampUp(2,pwmHighCounts, 25); // This one is the LED pin
RampDown(3,pwmHighCounts, 30);
} // end of loop()
// Simple functions to vary the PWM outputs
void RampDown(uint16_t cnum, volatile uint16_t *hcounts, uint16_t increment){
uint16_t newcount;
newcount = hcounts[cnum]; // get existing value
if(newcount < increment){ //underflow takes us back to max
newcount = pwmax;
} else {
newcount-= increment;
}
noInterrupts();
hcounts[cnum] = newcount;
interrupts();
}
void RampUp(uint16_t cnum, volatile uint16_t *hcounts, uint16_t increment){
uint16_t newcount;
newcount = hcounts[cnum]; // get existing value
if((newcount+increment) > pwmax){ //overflow takes us back to pwmin
newcount = pwmin;
} else {
newcount+= increment;
}
noInterrupts();
hcounts[cnum] = newcount;
interrupts();
}