CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

PWM as DAC

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
andyd



Joined: 08 Mar 2007
Posts: 30

View user's profile Send private message

PWM as DAC
PostPosted: Tue Mar 20, 2007 4:17 pm     Reply with quote

I'm having problems using the PWM of my PIC16F88 as a DAC and was wondering where I'm going wrong. I'm basing what I'm trying to do on Microchip's AN643 application note (http://ww1.microchip.com/downloads/en/AppNotes/00643b.pdf) which involves using a routine to decode ADPCM audio and then use the PIC's PWM as a DAC by using the decoded sample to set the duty cycle of the PWM (if I'm understanding it right) and feeding the resulting output into a 4kHz LPF.

The problem is that I basically can't get anything resembling audio out of the system. Before moving onto the PWM I made the PIC sit there and dump the values of the 16 bit output of the decoding routine into Hyperterminal and it seemed to be doing something. However, since the duty cycle can't be any greater than the PR2 value in setup_timer_2, the resolution is very limited. I was setting the PWM frequency to 8kHz as that's the audio sample rate (right? wrong?) and that gives me a PR2 value of 250. Going by the Microchip application note, I'm limited to a resolution of 7 bits so as not to exceed this value, so I'm shifting the 16 bit value my decoding routine produces right by 9 bits (upper 9 bits will then be 0 and it won't exceed the PR2 value). However, using this method, I seem to have hardly any movement in the PWM output waveform and consequently surely can't really be representing the audio properly? The ADPCM decoding function and all relevant data structures are Microchip's own, so I trust them to be doing their job right.

Another problem I'm having is with the timing - obviously the audio has to be played out at the right speed, so I'm using the timer interrupt to make sure this happens. However, I'm dubious of my ablity to have coded this right...

Lastly, surely by using a 4kHz LPF as advised in the Microchip application note, my 8kHz PWM will get completely filtered out anyway?

Relevant bits of code are posted below, any pointers on the above issues would be incredibly welcome!

Device setup/global variables:
Code:
#include <16f88.h>
#include <stdio.h>
#include <stdlib.h>


#fuses CCPB3, HS, NOWDT, NOLVP, NOBROWNOUT, NOPROTECT, PUT
#use delay(clock = 8000000)                              // Delay setup
#use rs232(baud = 9600, xmit = PIN_B5, rcv = PIN_B2, stream = PC) // RS232 setup
#use I2C(Master, sda = PIN_B1, scl = PIN_B4, FAST)               // I2C setup


struct  ADPCMState{
    signed long prevsample;        /* Predicted sample */
    int         previndex;         /* Index into step size table */
} state;


/* Table of index changes */
const int IndexTable[16] = {
    0xff, 0xff, 0xff, 0xff, 2, 4, 6, 8,
    0xff, 0xff, 0xff, 0xff, 2, 4, 6, 8
};

/* Quantizer step size lookup table */
const long StepSizeTable[89] = {
    7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
    19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
    50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
    130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
    337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
    876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
    2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
    5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
    15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
};


unsigned char wait;
long step;                      /* Quantizer step size */
signed long predsample;         /* Output of ADPCM predictor */
signed long diffq;              /* Dequantized predicted difference */
int index;                      /* Index into step size table */


At top of main() after variables:
Code:

      setup_adc_ports(NO_ANALOGS);         // Turn off analogue inputs
      setup_oscillator(OSC_8MHZ|OSC_INTRC);   // Use internal 8MHz oscillator
      setup_uart(9600, PC);
      setup_timer_2(T2_DIV_BY_1, 250, 1);
      setup_ccp1(CCP_PWM);

      enable_interrupts(GLOBAL);
      enable_interrupts(INT_TIMER2);


Decode routine in main() and interrupt function:
Code:

state.prevsample = 0;   // Clear ADPCM structure
         state.previndex = 0;   

         address_upper = 0b00000000;   
         address_lower = 0b00000001;

         
            
            fputs("Press button to start playback.", PC);

            while(input(PIN_A1)) // Wait for button press
            {
      
            }
   while(1){
         
            wait = 1;
            code = eeprom_read(address_upper, address_lower); // Read ADPCM code byte from EPROMs
         
            address_lower++;                     // Add 1 to lower address byte
                 if(address_lower == 0x00) address_upper++;   // If lower address = 0, add 1 to upper address byte

         
            sample = ADPCMDecoder((code>>4)&0x0f);   // Decode upper 4 bits of code
            

            while(wait){
                     
                     }    // Wait for 8kHz to output
            wait = 1;
            pwmout = sample;       
         
            if(sample<0) pwmout -= 0x8000;   
            else pwmout += 0x8000;
         
            set_pwm1_duty(pwmout>>9);

            

            sample = ADPCMDecoder(code&0x0f);   // Decode lower 4 bits of code
            while(wait){
                     
                     }    // Wait for 8kHz to output
            wait = 1;
            pwmout = sample;         

            if(sample<0) pwmout -= 0x8000;   
            else pwmout += 0x8000;
         

            set_pwm1_duty(pwmout>>9);
         
         }
            
      
            fputs("Decode routine finished", PC);
      }

}

#int_timer2
void timer_isr()
{
    wait = 0;
   clear_interrupt(INT_TIMER2);
}


ADPCM decoding routine:
Code:

signed long ADPCMDecoder(char code ) // ADPCM decoding routine
{

   /* Restore previous values of predicted sample and quantizer step
      size index
   */
   
   predsample = state.prevsample;
   index = state.previndex;


   /* Find quantizer step size from lookup table using index
   */
   step = StepSizeTable[index];


   /* Inverse quantize the ADPCM code into a difference using the
      quantizer step size
   */
   diffq = step >> 3;
   if( code & 4 )
      diffq += step;
   if( code & 2 )
      diffq += step >> 1;
   if( code & 1 )
      diffq += step >> 2;


   /* Add the difference to the predicted sample
   */
   if( code & 8 )
      predsample -= diffq;
   else
      predsample += diffq;


   /* Find new quantizer step size by adding the old index and a
      table lookup using the ADPCM code
   */
   index += IndexTable[ code ]; // No spaces in the actual source code here but I've changed it as it was messing up the tags on the forum

   /* Check for overflow of the new quantizer step size index */
   if( index > 88 )
      index = 88;
   

   /* Save predicted sample and quantizer step size index for next
      iteration
   */
   state.prevsample = (short)predsample;
   state.previndex = index;

   /* Return the new speech sample */
   return( predsample );

}
andyd



Joined: 08 Mar 2007
Posts: 30

View user's profile Send private message

PostPosted: Fri Mar 23, 2007 11:12 am     Reply with quote

Has anyone got any ideas on this? I've made a little bit of progress by not bitshifting the decoded output before it goes into the PWM and letting CCS take care of sorting out the resolution and changing the playback loop so that it stops when it hits the end of the data in the EEPROM. By doing this I can see the duty cycle of the PWM changing very quickly but I just get a burst of static out of the speaker (of pretty much the right length) when I press play, but that's it. What could that indicate? Wrong values being output by the decoder? Too low a resolution? Wrong PWM frequency?

I tried bitshifting the decoded sample by 8 bits and then outputting it to PORTA followed by a simple R-2R ladder circuit (http://ww1.microchip.com/downloads/en/AppNotes/00655a.pdf) (I've ordered a parallel DAC, it just hasn't come yet). The result was the same - just a burst of static. Does that narrow it down at all?
Ttelmah
Guest







PostPosted: Fri Mar 23, 2007 4:13 pm     Reply with quote

On the audio filter, remember you do not want to see the PWM at all, but the _envelope_ generated by the changes in it's pulse width. Hence te filter.
Now, the code is too long to really look at, but a couple of things leap out. You are programming the PWM, to support values from 0-999. You show the value fed to the PWM, being rotated right 9 times. This is equivalent to division by 512. Hence to get the full scale input, would require a value ranging up to 511000ish. Yet all the numbers shown are only 16bit values (max 65536). Remember that on a lot of compilers, a 'long', is what CCS call an int32...
You show an interrupt enabled on timer2, but no handler for this (if you enable the interrupt, one is required). However do you really want an interrupt 8000 times a second?. The chip won't be able to do much else if this is occurring.

Best Wishes
andyd



Joined: 08 Mar 2007
Posts: 30

View user's profile Send private message

PostPosted: Sun Mar 25, 2007 6:03 am     Reply with quote

Thanks for the input Ttelmah. I've now realised the purpose of the filter so that's all good. Just curious as to where you get values 0-999 from?

Also, I've now changed the code to not bitshift the value at all and am feeding the PWM the full 16bit value, which as said, gives me a lot more movement on the output trace, but no intelligible audio. Was that the right thing to do?

Is
Code:

#int_timer2
void timer_isr()
{
    wait = 0;
   clear_interrupt(INT_TIMER2);
}

not the interrupt handler, or am I missing something here too?

I can see your point about interrupting 8k times/sec, but this is how the Microchip application note suggests doing it. Could you suggest a better way at all? The purpose of interrupting 8k times/sec is so that the samples get output at the right speed I think, so it's necessary to have something like that in there as far as I can see.

Andy
Ttelmah
Guest







PostPosted: Sun Mar 25, 2007 8:50 am     Reply with quote

OK. On the interrupt handler, I didn't see it because it is low down the code. It used to be in CCS, that the interrupt handlers _had_ to be at the top of he code. This is no longer the case, but I still look for them there, and nowwhere else. Smile
However, do it differently. The problem here, is that, because you are working with the compiler, there is a _huge_ amount of 'overhead' associated with the handler, that is not present in an assembler version. The difference is between a handler that can happily handle 8K calls/second, and one that will give problems. You are also 'adding' code, by clearing the interrupt (the compiler already does this auotmatically, if the naed interrupt handler is defined...).
Instead code an int_global. Look at the example 'ex_glint.c', for how to code this. Something like (using the definitions in ex_glint):
Code:

   #asm
   //store current state of processor
   MOVWF save_w
   SWAPF status,W
   BCF   status,5
   BCF   status,6
   MOVWF save_status
   // Nothing else changes in your interrupt
   #endasm
   wait = 0;
   clear_interrupt(INT_TIMER2); //Have to clear interrupt, because not
   //not using a 'named' handler.
   #asm
   // restore processor and return from interrupt
   SWAPF save_status,W
   MOVWF status
   SWAPF save_w,F
   SWAPF save_w,W
   #endasm

This will reduce the code size of the interrupt handler, by about 40+ instruction times...

I get '999', from your timer definiton. The maximum count, in the PWM mode, is 4* the timer value.

Best Wishes
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group