best solution for 12 analog outputs (5 bits)

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.