dhenry:
This short sketch probably helps you understand how it works.
//generate multiple ppm output
//pin configuration
#define PWM_PORT PORTB
#define PWM_DDR DDRB
#define PWM0 (1<<0)
#define PWM1 (1<<1)
#define PWM2 (1<<2)
#define PWM3 (1<<3)
#define PWM4 (1<<4)
#define PWM5 (1<<5)
//pin configuration
//defining timing constants
#define PWM_ms (F_CPU / 64 / 1000) //milli seconds, 64:1 prescaler
#define PWM_1ms (PWM_ms)
#define PWM_2ms (PWM_1ms * 2)
#define PWM_4ms (PWM_2ms * 2)
#define PWM_8ms (PWM_4ms * 2)
#define PWM_16ms (PWM_8ms * 2)
#define PWM_32ms (PWM_16ms * 2)
//ppm parameters
const unsigned short pwm_duration[]={
PWM_1ms,
PWM_2ms,
PWM_4ms,
PWM_8ms,
PWM_16ms
};
//durations
unsigned char duration[]={
1, //expected output: 1 / 31 * 5v
4, //expected output: 4 / 31 * 5v
8, //expected output: 8 / 31 * 5v
16, //expected output: 16 / 31 * 5v
30, //expected output: 30 / 31 * 5v
7}; //expected output: 7 / 31 * 5v
//tmr1 ctc isr
ISR(TIMER1_COMPA_vect) {
static unsigned char index = 0; //index
//update the period
OCR1A = pwm_duration[index];
//change the pins
if (duration[0] & (1<<index)) PWM_PORT |= PWM0; //set pwm0
else PWM_PORT &=~PWM0; //clear pwm0
if (duration[1] & (1<<index)) PWM_PORT |= PWM1; //set pwm1
else PWM_PORT &=~PWM1; //clear pwm1
if (duration[2] & (1<<index)) PWM_PORT |= PWM2; //set pwm2
else PWM_PORT &=~PWM2; //clear pwm2
if (duration[3] & (1<<index)) PWM_PORT |= PWM3; //set pwm3
else PWM_PORT &=~PWM3; //clear pwm3
if (duration[4] & (1<<index)) PWM_PORT |= PWM4; //set pwm4
else PWM_PORT &=~PWM4; //clear pwm4
if (duration[5] & (1<<index)) PWM_PORT |= PWM5; //set pwm5
else PWM_PORT &=~PWM5; //clear pwm5
//update index
index += 1; // incrment index
if (index == 5) index = 0; //wrap around index
}
//set up the timer1, 64:1 prescaler
void tmr1_init(unsigned short period) {
TCCR1B &=~0x07; //stop tmr1
//set up tccr1a
TCCR1A = (0<<COM1A1) | (0<<COM1A0) | //com1a10 = 0b00, normal operations
(0<<COM1B1) | (0<<COM1B0) | //com1b10 = 0b00, normal operations
(0<<WGM11) | (0<<WGM10); //wgm2..0 = 0b0100, top at OCR1A
TCCR1B = (0<<ICNC1) | (0<<ICES1) | //disable input capture noise canceller and input capture edge select
(0<<WGM13) | (1<<WGM12) | //wgm3..0 = 0b0100, top at OCR1A
(0<<CS12) | (1<<CS11) | (1<<CS10); //cs2..0 = 0b0011, 64:1 prescaler
TCCR1C = 0x00;
//reset the timer/counter
TCNT1 = 0x0000; //reset the timer
//load the period
OCR1A = period;
//reset the flag
TIFR1 |= (1<<OCF1A);
//enable the interrupt
TIMSK1 |= (1<<OCIE1A);
}
void setup(void) {
//initialize the output pins
PWM_PORT |= PWM0 | PWM1 | PWM2 | PWM3 | PWM4 | PWM5; //set the pins
PWM_DDR |= PWM0 | PWM1 | PWM2 | PWM3 | PWM4 | PWM5; //set up the pins for output
//set up the timer
tmr1_init(PWM_1ms);
//enable interrupt
sei();
}
void loop(void) {
}
I implemented just 6 channels of output, on PORTB. duration[] specifies the corresponding output voltage, as expressed in 0..31.
Timing steps are defined in pwm_duration[], in 5 steps, from 1ms - 16ms.
The entire code is done in an isr and no impact on your user code.
Now, it is not exactly what you wanted (fewer channels and more steps) but you can easily modify it to your needs.
Thanks a lot for this sketch! I am going to test it.