Author Topic: AVR PCM player + CLASS D Amp  (Read 858 times)

0 Members and 1 Guest are viewing this topic.

Offline DELTA67Topic starter

  • Regular Contributor
  • *
  • Posts: 57
  • Country: fr
AVR PCM player + CLASS D Amp
« on: August 31, 2023, 03:07:08 pm »
Hi all,

I've some mono 8bit, 8KHz PCM samples:

Code: [Select]
const char fw[] PROGMEM = {0xA5, 0x.....}; // fireworks
const char cn[] PROGMEM = {0xC1, 0x.....}; // cannon
const char th[] PROGMEM = {0xF0, 0x.....}; // thinder

using an AVR (M328) @ 16MHz, I used TIMER1 to generate the PWM carrier @ 62.5KHz and TIMER2 to play the samples at a rate of 8KHz.
If i play each sample alone it works (quality is not a concern).

Code: [Select]
ISR(TIMER2_OVF_vect){
// read value from PCM table and send it to PWM DAC
OCR1A = pgm_read_byte(&fw[sample++]); 
if(sample == len) TIMSK2 = 0; // disable TIM2 overflow Interrupt
}

Now  I want to play them sequentially to produce some sound effects.

Q1: Why this code doesn't work ?!!
Code: [Select]
char* effect[] = {fw, cn, th};
unsigned int length[] = {sizeof(fw), sizeof(cn), sizeof(th)};
char* effptr; // effect pointer
volatile sample;
volatile len;

void init_timers(){
// timers initialization here
}


ISR(TIMER2_OVF_vect){
// read value from PCM table and send it to PWM DAC
OCR1A = pgm_read_byte(&effptr[sample++]); 
if(sample == len) TIMSK2 = 0; // disable TIM2 overflow Interrupt
}

void main(){

init_timers();

for(;;){
sample=0;
len = length[0];
effptr = effect[0];
TIMSK2 = 1;// enable TIM2 overflow Interrupt
_delay_ms(3000);
// repeat for the other 2 samples
sample=0;
len = length[1];
effptr = effect[1];
.....
}
}

Q2: Is there any processing to do on the samples them selves or/and during their reproduction to:
   - reduce the click at the end of playback.
   - get the Max volume
   - get the max quality

Q3: Given that the output is already a High Freq PWM modualted with the sound:
   - What is the simplest circuit to build a CLASS D amplifier?
     can I use a simple MOSFET to drive the loud speaker?
Thanks
« Last Edit: August 31, 2023, 03:54:17 pm by DELTA67 »
 

Offline DELTA67Topic starter

  • Regular Contributor
  • *
  • Posts: 57
  • Country: fr
Re: AVR PCM player + CLASS D Amp
« Reply #1 on: August 31, 2023, 08:15:31 pm »
For Q1, I think the problem comes from passing a pointer to an array (effptr) to the interrupt service routine.
Even  with the  "volatile char* effptr" no success!
« Last Edit: August 31, 2023, 08:25:11 pm by DELTA67 »
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21688
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: AVR PCM player + CLASS D Amp
« Reply #2 on: September 01, 2023, 12:14:25 pm »
For reference: https://godbolt.org/z/Gs5d5W6Tr

I made some changes to make GCC up to 13 happy.  Everything in the output seems to be where it should, despite not using volatiles (not unexpected, the ISR read-modify-writes and it's a short function).  Writes to sample and len should be ATOMIC but as long as _delay_ms() is longer than the sample (highly likely) it's not like it's going to be interrupted.

It would help if you explained in what way it "doesn't work"... we're not psychic here!


Quote
Q2: Is there any processing to do on the samples them selves or/and during their reproduction to:
   - reduce the click at the end of playback.

Set OCR to a final (mid scale) value before disabling interrupts.


Quote
   - get the Max volume
   - get the max quality

Use a DAC. Use higher sample rate.


Quote
Q3: Given that the output is already a High Freq PWM modualted with the sound:
   - What is the simplest circuit to build a CLASS D amplifier?
     can I use a simple MOSFET to drive the loud speaker?

One MOSFET, not really, but a half-bridge yes.  You need both so that the output is fully driven (low impedance) at all times.  A gate driver chip like TC4420 would probably be fine up to a few hundred mA.  Use a series resistor and coupling capacitor to drive the speaker.

Note that you can't disable the carrier entirely without generating a pop, you need to keep it "simmering" between samples.  Alternately, make it ramp down slowly over many samples (longer than the RC time constant of the coupling cap (HPF)), so that the transition between 0 and 50% duty is slow enough to not pass the filter.  And then back up when a sample is transmitted.

Even just a little ramping may be desirable to turn the pop into a softer click or bump sound.

8 to 64 kSps is probably not a big deal, or, pushing (but not exceeding) the limits of what GCC can do, but you may find if you need to do anything more than fetch samples in the ISR, spare CPU cycles quickly dry up, choppy output or unexpected rate results, and hand tuned ASM is required.  Which is easy enough for AVR, it's a simple platform.  Before going to ASM, pay close attention to what variables are written and saved in the ISR, order of access, and etc., and minimize manipulation of them.  To wit: len could be assigned effptr + length[n], comparison being done on the raw pointer instead of the index.  This saves a half dozen instructions already.

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline DELTA67Topic starter

  • Regular Contributor
  • *
  • Posts: 57
  • Country: fr
Re: AVR PCM player + CLASS D Amp
« Reply #3 on: September 01, 2023, 07:05:53 pm »
Thanks Tim for your in depth reply.

It would help if you explained in what way it "doesn't work"... we're not psychic here!
I don't get the expected sound, only some noise.
As I said, I suspect the use of a pointer to let the ISR play the wanted sample.  

I tested this code using a pointer and it works:

Code: [Select]
void main(){

       init_timers();

       sample=0;
       len = length[0];
       effptr = effect[0];
      TIMSK2 = 1;   // enable TIM2 overflow Interrupt
      for(;;);
}

But if I do this:

Code: [Select]
void main(){

       init_timers();
       
      for(;;){
          sample=0;
          len = length[0];
          effptr = effect[0];
          TIMSK2 = 1;   // enable TIM2 overflow Interrupt
          _delay_ms(3000);
      }
}

I get only some cliks!!
« Last Edit: September 01, 2023, 07:44:23 pm by DELTA67 »
 

Offline DELTA67Topic starter

  • Regular Contributor
  • *
  • Posts: 57
  • Country: fr
Re: AVR PCM player + CLASS D Amp
« Reply #4 on: September 01, 2023, 07:55:00 pm »
This code works

Code: [Select]
const char fw[] PROGMEM = {0xA5, 0x.....}; // fireworks

volatile sample;
volatile len;

void init_timers(){
// timers initialization here
}

ISR(TIMER2_OVF_vect){
// read value from PCM table and send it to PWM DAC
OCR1A = pgm_read_byte(&fw[sample++]);
if(sample == len) TIMSK2 = 0; // disable TIM2 overflow Interrupt
}

void main(){

init_timers();

for(;;){
sample=0;
len = sizeof(fw);
TIMSK2 = 1;// enable TIM2 overflow Interrupt
_delay_ms(3000);
}
}
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21688
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: AVR PCM player + CLASS D Amp
« Reply #5 on: September 01, 2023, 08:16:43 pm »
Ah, it's not changing the value within main() (after the first pass if applicable), because nothing else is reading it, and main() never exits anyway, so it's not observable.  Remember that variables only need to be written back at a sequence point, and reads and writes between sequence points can be reordered or dropped as needed.

As far as the compiler is concerned, ISRs are disconnected from the execution graph; the linker is forced to include them only(?) by way of the IVT pointing to them, and the IVT structure is __attribute__((used)) so it's forced to be included as well (and thus everything pointed to).

So you've told the compiler you don't care about this variable, be lazy and optimize around it to any extent possible.

Using a,

Code: [Select]
const unsigned char * volatile effptr;
tells it to always read/write the value where referenced, as its value may change unexpectedly, or be read by separate processes (other threads, hardware registers).

The tricky part is where to put the volatile, because C declarations are notoriously cryptic.  This may prove useful in the future: https://cdecl.org/

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline DELTA67Topic starter

  • Regular Contributor
  • *
  • Posts: 57
  • Country: fr
Re: AVR PCM player + CLASS D Amp
« Reply #6 on: September 01, 2023, 08:48:26 pm »
Many thanks Tim,
It works!!!
It was really a very subtle bug (at least for me).

So Q1 and Q3 are solved.
Can you suggest, please, a simple algorithm to ramp the samples down at the end of play back in order to reduce the click?
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf