Go Down

Topic: best solution for 12 analog outputs (5 bits) (Read 3 times) previous topic - next topic

CrossRoads

How about this:
SPI.transfer()s to 8 74HC595 shift registers =>64 bits, pick off ouptut in groups of 5.
Output of each shift register drives an R2R Ladder.
http://www.bourns.com/pdfs/r2r.pdf (the heart of most DACs)
and then 3 quad op-amps to make voltage output into your controller.

500 Hz is 2mS period = 32,000 clock cycles.  Seems like more than enough to update 8  bytes & shift them out via PWM to create your output levels.
pseudocode:
Code: [Select]

loop(){
read current time in micros;
if (2mS elapsed){
store current time for next pass thru loop;
digitalWrite (latch_signal, LOW); // use direct port manipulation to speed it up
for (x=0 to 7){
SPI.transfer( outputArray[x]);
next x;
digitalWrite (latch_signal, HIGH);  // use direct port manipulation to speed it up
} // end transfer
} // end 2mS check
// now nearly 2 mS available to update the array before next 2mS window
} // end loop

this the essence of blink with delay. Every 2mS you do a little burst of transfers, then spend the rest of the time doing other stuff.

Trade off cost too - 8 74HC595s, 12 resistor networks, 3 quad op amps
vs 3 quad DACs
vs 2 octal DACs
and the time savings wiring it up and the real estate required.
Designing & building electrical circuits for over 25 years. Check out the ATMega1284P based Bobuino and other '328P & '1284P creations & offerings at  www.crossroadsfencing.com/BobuinoRev17.
Arduino for Teens available at Amazon.com.

BenF



Using a 16MHz mcu and bit-bang PWM at 20kHz, you would have 400 instructions available for each interrupt cycle (16e6 / 40e3). This may be achievable (not using the Arduino core), but it doesn't leave you with much for anything else. Direct port IO is 8 bit wide and requires two instructions only. Interrupt overhead is about 50 instructions.

You must help me with this calculation: 16e6/40e3 -> where is the 40e3 coming from.

For 20kHz PWM, you need a 40kHz interrupt (two half periods is one cycle). You will need to pre-compute the PWM patterns outside of the interrupt loop so all you need is a simple lookup of two bytes inside the timer interrupt handler. This will give you  max 16 PWM channels (8 per byte). This is tricky code to write, but as I said it may be achievable with good coding skills.

Oldchatterman


How about this:
SPI.transfer()s to 8 74HC595 shift registers =>64 bits, pick off ouptut in groups of 5.
Output of each shift register drives an R2R Ladder.
http://www.bourns.com/pdfs/r2r.pdf (the heart of most DACs)
and then 3 quad op-amps to make voltage output into your controller.

500 Hz is 2mS period = 32,000 clock cycles.  Seems like more than enough to update 8  bytes & shift them out via PWM to create your output levels.

this the essence of blink with delay. Every 2mS you do a little burst of transfers, then spend the rest of the time doing other stuff.

Trade off cost too - 8 74HC595s, 12 resistor networks, 3 quad op amps


This is definitely a no go, it can't compete with the DAC solutions. I found another alternative: DAC084s085. It has SPI and the cost are reasonable.

Oldchatterman




Using a 16MHz mcu and bit-bang PWM at 20kHz, you would have 400 instructions available for each interrupt cycle (16e6 / 40e3). This may be achievable (not using the Arduino core), but it doesn't leave you with much for anything else. Direct port IO is 8 bit wide and requires two instructions only. Interrupt overhead is about 50 instructions.

You must help me with this calculation: 16e6/40e3 -> where is the 40e3 coming from.

For 20kHz PWM, you need a 40kHz interrupt (two half periods is one cycle). You will need to pre-compute the PWM patterns outside of the interrupt loop so all you need is a simple lookup of two bytes inside the timer interrupt handler. This will give you  max 16 PWM channels (8 per byte). This is tricky code to write, but as I said it may be achievable with good coding skills.


Ok, thanks. I am just a beginner and this area is not my daily business, so I doubt that I have the required coding skills.

dhenry

Quote
How does that solve your problem?


It can be made to work.

Let's say that each time block is 1ms. You need 32ms to send 5 bits of data.

Run your timer isr in 1ms, 2ms, 4ms, 8ms, 16ms chunks. And set pins based on the data bits for each of those data chunks - essentially PPM.

Some pseudo code:

Code: [Select]

#define ANALOG_RESOLUTION 5 //analog resoultion, in bits
#define ANALOG_CHANNEL 12 //analog channel

const unsigned short duration[ANALOG_RESOLUTION]={1000, 2000, 4000, 8000, 16000}; //duration
unsigned char analog_output[ANALOG_CHANNEL]={2, 5, 1, 19, 2, ...};

timer_isr {
 static unsigned char i=0;
 set timer to interrupt after duration[i];
 increment i;
 if i = ANALOG_RESOLUTION then i = 0;
 loop:
   if analog_output[ch] & (1<<i) then set output pin for ch;
   else clear output pin for ch;
   increment ch;
 end loop
}



If you don't like ripples, you can shrink the minimum time block from 1ms to something smaller but I would go below 30 ticks (2us for an AVR at 16Mhz).

Oldchatterman


Quote
How does that solve your problem?


It can be made to work.

Let's say that each time block is 1ms. You need 32ms to send 5 bits of data.

Run your timer isr in 1ms, 2ms, 4ms, 8ms, 16ms chunks. And set pins based on the data bits for each of those data chunks - essentially PPM.

Some pseudo code:

Code: [Select]

#define ANALOG_RESOLUTION 5 //analog resoultion, in bits
#define ANALOG_CHANNEL 12 //analog channel

const unsigned short duration[ANALOG_RESOLUTION]={1000, 2000, 4000, 8000, 16000}; //duration
unsigned char analog_output[ANALOG_CHANNEL]={2, 5, 1, 19, 2, ...};

timer_isr {
 static unsigned char i=0;
 set timer to interrupt after duration[i];
 increment i;
 if i = ANALOG_RESOLUTION then i = 0;
 loop:
   if analog_output[ch] & (1<<i) then set output pin for ch;
   else clear output pin for ch;
   increment ch;
 end loop
}



If you don't like ripples, you can shrink the minimum time block from 1ms to something smaller but I would go below 30 ticks (2us for an AVR at 16Mhz).



I understand the principles of this solution, but not the details in de pseudocode. Is it true that timer_isr is called at the beginning of each chuck, so after 1 ms, after 2 ms, after 4 ms and so on? Is it possible to change the timing of the interrupt within the timer_isr? If this is true then I don't understand the 1st line in timer_isr: where i is defined as 0.



dhenry

This short sketch probably helps you understand how it works.

Code: [Select]
//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.

dhenry

Note: as this approach (PPM) is essentially PWM. It suffers all the drawbacks of any PWM approach - a compromise between ripple and response time.

You have to make a decision whether it works for you or not.

Oldchatterman


This short sketch probably helps you understand how it works.

Code: [Select]
//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.

dhenry

I changed the code slightly to make it easier and more consistent.

Code: [Select]
//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)

//set pwm duration for each bit
#define PWM_Bit     (PWM_100us)     //duration of 1 bit
//pin configuration

//defining timing constants
#define PWM_ms      (F_CPU / 64 / 1000)  //milli seconds, 64:1 prescaler
#define PWM_1ms     (PWM_ms)
#define PWM_500us   (PWM_1ms / 2)
#define PWM_250us   (PWM_500us / 2)
#define PWM_100us   (PWM_500us / 5)
#define PWM_2ms     (PWM_1ms * 2)
#define PWM_5ms     (PWM_1ms * 5)
#define PWM_10ms    (PWM_1ms * 10)

//duration examples for 6 channels
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
  if (index) OCR1A = OCR1A * 2;
  else OCR1A = PWM_Bit;
 
  //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_Bit);
 
  //enable interrupt
  sei();
}

void loop(void) {
}


Oldchatterman


I changed the code slightly to make it easier and more consistent.

Code: [Select]
//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)

//set pwm duration for each bit
#define PWM_Bit     (PWM_100us)     //duration of 1 bit
//pin configuration

//defining timing constants
#define PWM_ms      (F_CPU / 64 / 1000)  //milli seconds, 64:1 prescaler
#define PWM_1ms     (PWM_ms)
#define PWM_500us   (PWM_1ms / 2)
#define PWM_250us   (PWM_500us / 2)
#define PWM_100us   (PWM_500us / 5)
#define PWM_2ms     (PWM_1ms * 2)
#define PWM_5ms     (PWM_1ms * 5)
#define PWM_10ms    (PWM_1ms * 10)

//duration examples for 6 channels
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
  if (index) OCR1A = OCR1A * 2;
  else OCR1A = PWM_Bit;
 
  //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_Bit);
 
  //enable interrupt
  sei();
}

void loop(void) {
}




Just for confirmation: is it true that the PWM frequency is 500 Hz with a PWM_Bit duration of 1 ms?

dhenry

it would be 31*1ms.

1ms is for the shortest duration (a '1' at bit 0 if you will).

A short PWM_Bit will improve ripple, and response time, but it would tie up the mcu more.

I think 50us is probably the bare minimum you can go and I would actually go with a 1ms as that is more than sufficient for your stated response time of 1s.

Oldchatterman


it would be 31*1ms.

1ms is for the shortest duration (a '1' at bit 0 if you will).

A short PWM_Bit will improve ripple, and response time, but it would tie up the mcu more.

I think 50us is probably the bare minimum you can go and I would actually go with a 1ms as that is more than sufficient for your stated response time of 1s.



I tested 50 us and it worked for 12 channels. This means a PWM frequency of 1/31*50 us = 645 Hz.

Tested it with a R of 1 kOhm and a C of 47 uF -> this gives a ripple of 0.04 V p-p
Tested it with a R of 1 kOhm and a C of 4.7 uF -> this gives a ripple of 0.4 V p-p

dhenry

A 1k resistor is likely too small. I would go with 10k or 100k if possible.

dhenry

One short note: the 1st solution I provided can be easily modified to incorporate gamma correction.

The 2nd solution I provided allows easy adjustment of pwm frequency.

Otherwise, they are identical.

Go Up