|
|
View previous topic :: View next topic |
Author |
Message |
andyd
Joined: 08 Mar 2007 Posts: 30
|
PWM as DAC |
Posted: Tue Mar 20, 2007 4:17 pm |
|
|
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
|
|
Posted: Fri Mar 23, 2007 11:12 am |
|
|
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
|
|
Posted: Fri Mar 23, 2007 4:13 pm |
|
|
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
|
|
Posted: Sun Mar 25, 2007 6:03 am |
|
|
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
|
|
Posted: Sun Mar 25, 2007 8:50 am |
|
|
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.
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 |
|
|
|
|
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
|