AVR PCM player + CLASS D Amp - Page 1
Forum section: electronics / general

author: DELTA67
author_rank: Regular Contributor
timestamp: August 31, 2023, 03:07:08 pm
content: Hi all,I've some mono 8bit, 8KHz PCM samples:Code:const char fw[] PROGMEM = {0xA5, 0x.....}; // fireworksconst char cn[] PROGMEM = {0xC1, 0x.....}; // cannonconst char th[] PROGMEM = {0xF0, 0x.....}; // thinderusing 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:ISR(TIMER2_OVF_vect){// read value from PCM table and send it to PWM DACOCR1A = 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:char* effect[] = {fw, cn, th};unsigned int length[] = {sizeof(fw), sizeof(cn), sizeof(th)};char* effptr;// effect pointervolatile sample;volatile len;void init_timers(){// timers initialization here}ISR(TIMER2_OVF_vect){// read value from PCM table and send it to PWM DACOCR1A = pgm_read_byte(&effptr[sample++]);if(sample == len) TIMSK2 = 0;// disable TIM2 overflow Interrupt}void main(){init_timers();for(;;){sample=0;len = length;effptr = effect;TIMSK2 = 1;// enable TIM2 overflow Interrupt_delay_ms(3000);// repeat for the other 2 samplessample=0;len = length;effptr = effect;.....}}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 qualityQ3: 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

author: DELTA67
author_rank: Regular Contributor
timestamp: August 31, 2023, 08:15:31 pm
content: 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!

author: T3sl4co1l
author_rank: Super Contributor
timestamp: September 01, 2023, 12:14:25 pm
content: For reference:https://godbolt.org/z/Gs5d5W6TrI 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!QuoteSet OCR to a final (mid scale) value before disabling interrupts.QuoteUse a DAC. Use higher sample rate.QuoteOne 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, comparison being done on the raw pointer instead of the index. This saves a half dozen instructions already.Tim

author: DELTA67
author_rank: Regular Contributor
timestamp: September 01, 2023, 07:05:53 pm
content: Thanks Tim for your in depth reply.Quote from: T3sl4co1l on September 01, 2023, 12:14:25 pmI 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:void main(){init_timers();sample=0;len = length;effptr = effect;TIMSK2 = 1; // enable TIM2 overflow Interruptfor(;;);}But if I do this:Code:void main(){init_timers();for(;;){sample=0;len = length;effptr = effect;TIMSK2 = 1; // enable TIM2 overflow Interrupt_delay_ms(3000);}}I get only some cliks!!

author: DELTA67
author_rank: Regular Contributor
timestamp: September 01, 2023, 07:55:00 pm
content: This code worksCode:const char fw[] PROGMEM = {0xA5, 0x.....}; // fireworksvolatile sample;volatile len;void init_timers(){// timers initialization here}ISR(TIMER2_OVF_vect){// read value from PCM table and send it to PWM DACOCR1A = 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);}}

author: T3sl4co1l
author_rank: Super Contributor
timestamp: September 01, 2023, 08:16:43 pm
content: 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: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 thevolatile, because C declarations are notoriously cryptic. This may prove useful in the future:https://cdecl.org/Tim

author: DELTA67
author_rank: Regular Contributor
timestamp: September 01, 2023, 08:48:26 pm
content: 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?