NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.
Microcontroller Programming » pwm for dac
March 06, 2010 by norby31 |
Has anybody here used the pwm as a digital-analog converter? How do I get it to approximate values of a variable I have? |
---|---|
March 06, 2010 by BobaMosfet |
Yes, that is how the D/A Converter works on the ATMEGA168. What do you mean by 'variables'? (voltage inputs?) BM |
March 06, 2010 by treymd |
Can you elaborate a little Boba? Do you use pwm to intermittently charge a cap for D/A? |
March 06, 2010 by norby31 |
Variable: yeah, sure an input or a value I could write to an output pin. So how do you make the dac work using PWM? |
March 06, 2010 by norby31 |
To clarify: I have read a value from the ADC port and written it to a variable. I did a little math on the number stored in this variable. I now want to output this value in analog form. I tried a 4 bit DAC (after convert to binary) using a summing op-amp config but it sounded like crap. |
March 08, 2010 by BobaMosfet |
treymd- The capacitor is there only for jitter control. Smoothes out the bumps, as it were. norby31- The DAC on the ATMEGA168 uses a counter and a comparator (op amp) and keeps testing the counter value against the input digital signal to see if the two match. As long as the counter is less than the input, the counter keeps counting. BM |
March 08, 2010 by bretm |
The Atmega doesn't actually have a DAC, does it? It has an ADC. And I don't think it uses a counter. It uses a 10-bit register that will jump above and below the measured value as it tests the most-significant bit first, then the 2nd most, etc. If you make a 4-bit DAC with external components, you'll only get 16 distinct analog levels, so yeah, it's going to distort a lot. But if you filter a PWM output with a capacitor you can get 8 bits of resolution. It's not quite 8 bits of accuracy because of the filtering, but it's 8 bits of precision. And no, I've never done it. Atmel explains how to do it here. |
March 09, 2010 by norby31 |
Yes, it emulates a DAC as you described. I don't know how picky to get about calling it a counter but they refer to it as that in the datasheet. I don't care what it's called as long as I understand it. So I input a guitar signal through the ADC and do a little math to it. That's fine and dandy--I've got some ring modulation going and some slicing. What's a good way to get that signal back out of the chip?? The 4-bit actually sounded like a guitar but with tons of ringing when I looked at a scope. If the pwm/dac works I'd gladly try that as long as I can get nice unglitched binary to it. |
March 09, 2010 by bretm |
I started playing with this last night but it got late before I could finish. I converted a WAV file of someone saying "hello" into a byte array at 8000 samples per second and 8 bits per sample using "Free Audio Editor" and "wav2c". Then I set up PWM in phase-correct mode with no prescaling of the clock. Because it takes 510 cycles of the clock to do one PWM cycle, this comes out to about 29kHz. Then I set up Timer 0 to run at about 8kHz and I set the PWM duty cycle to match the sound samples. I used a simple RC filter with R=1k and C=0.1uF, and attached a piezo across the capacitor. It was very quiet, but understandable. The peak-to-peak voltage was fine, though, so I was going to work on a power amplifier tonight to see what kind of quality I can get out of it. This would be a nice "nerdkit parts only" type of demo project if it turns out to sound OK. |
March 09, 2010 by norby31 |
I wouldn't mind seeing your code if you are willing to share it. I haven't messed with a byte array for sound but I assume it's equivalent to having stored digital values of a waveform. |
March 09, 2010 by bretm |
I will definitely post it when I get home. The signal looks OK on the scope, except I probably need an even slower filter, like R=10k, because the 8kHz ripple is still kinda bad. I started with just a 445Hz sine wave which has a byte array like this: unsigned char wav_table[] = {128, 172, 210, 239, 254, 254, 239, 210, 172, 128, 84, 46, 17, 2, 2, 17, 46, 84}; (It's 445Hz because it's F_CPU divided by 8 (prescaler) divided by 230 (Timer 0 compare match) divided by 18 samples per cycle.) It came out definitely looking like a sine wave on the scope, but with a small 8kHz saw-tooth signal on top of the sloped parts. The crest and trough of the wave looked good. |
March 09, 2010 by BobaMosfet |
First, we need to understand what 'digital' voltage is, versus 'analog' voltage. They are the same thing at any given instance in time-- (which actually equates to a very specific number of electrons flowing at the time of measurement). Analog & Digital voltage are CONCEPTS-- NOT aspects of current flow in response to voltage. What makes the difference is the rate of change over a specific period of TIME.
In a way, it does-- It's using the PWM and a Comparator together). Successive approximation works by constantly comparing the source voltage (that you're testing) to the output of a Comparator (fed by the current value of the PWM) until the best approximation is achieved. A comparator is usually an operational amplifier. Resolution Given:
The caveat on resolution is that (due to rounding) they will resolve up or down by half-a-bit (which actually ends up being an error of 1 LSB as the smallest increment of resolution possible). They do it this way, because otherwise (as in high-speed hardware DACs) they would require an exponential number of comparators (op-amps) - which is not economical for this package. BM |
March 09, 2010 by BobaMosfet |
In case the above isn't entirely clear, think of it this way-- It starts with a reference voltage and compares a pulse from the PWM that is half the reference voltage. It then compares that with the input (source) voltage you're trying to evaluate. Depending on whether it is higher or lower, tells how it sets a bit, and which side (above the halfway point or below it) to cut in half again-- THIS is the successive approximation aspect. It does this until all 10 bits are set. BM |
March 09, 2010 by bretm |
I don't think the analog-to-digital converter in the Atmega uses PWM at all. The ADC chapter doesn't mention PWM at all, and the timer/PWM and analog comparator modules run independently of the ADC module. Yes, it does successive approximations against the output of an internal DAC and internal comparator that we can't access externally. But I don't see anything in the datasheet that suggests that this DAC is based on PWM. It's more likely to be a ladder arrangement. |
March 09, 2010 by BobaMosfet |
bretm- I thought of the R-2R, but they don't mention that in the datasheet either. Since they didn't, and they have the means to do it with the components already there, It seems logical that they are using a counter/comparator method. I'll see if I can find out, I have contacts in the engineering group at ATMEL. BM |
March 09, 2010 by BobaMosfet |
bretm-- I asked Srinivasan over at ATMEL about the figure in the datasheet (Image 23-1, pp 245) that looks like a separate DAC module (as you would expect with an R-2R). He confirmed that it is. It has a 10-bit resolution limit. Interestingly, he said that if I needed lower or higher precision (adjustable from 2 to 16 bits), I can use the PWM in "Fast PWM Mode" with single-slope operation. So we both learned something :P BM |
March 09, 2010 by bretm |
My reasoning for thinking it was a ladder was that PWM would be slow. The internal DAC does 10 comparisons per conversion and each comparison only takes one clock cycle. 10-bit PWM would take 1024 clock cycles per comparison, and then you'd be comparing against a moving target (filtered PWM output would be decaying exponentially if using a simple RC filter). A ladder would respond much more quickly. I'm not clear what was meant by "if you need lower or higher resolution we can use the PWM...". What if I don't need lower or higher resolution but I'm fine with 10 bits? I can use the internal DAC? I don't see how. And if we're building our own DAC out of filtered PWM, why would it need to be fast PWM single-slope, instead of phase-correct PWM? |
March 09, 2010 by norby31 |
Isn't that actually ADC, not DAC module on the drawing 23-1 ??? |
March 09, 2010 by BobaMosfet |
bretm- Ironically, after I posted my earlier response, the same question was floating in my mind-- can I use that 10-bit DAC? I'm waiting for a response from Srinivasan. To answer the Original Poster's question about whether or not anyone has used the PWM as a DAC, I'm not certain. But I'm going to give it a try. I don't see any reason why it can't be done, and I don't think it will take 1024 clock cycles for comparison, using the same concept of 'successive comparison.' I'll post what I find out and Srinivasan's response. BM |
March 09, 2010 by BobaMosfet |
Quote:
Yes, it's the ADC, but look inside that drawing. There is a 10-bit DAC within. That's what bretm and I are discussing. BM |
March 09, 2010 by norby31 |
"Yes, it's the ADC, but look inside that drawing." I was assuming that was a typo since AREF was going into it |
March 09, 2010 by norby31 |
"But I'm going to give it a try." Yes, and please post the code/methodology. Although I have programmed microprocessors, written tons of web code, have an electronics degree, CSCI minor and build guitar effects I could use a little help. |
March 09, 2010 by bretm |
It'll take 1024 cycles per comparison, because to make a comparison you need to generate an analog output voltage, which means one full PWM on/off cycle, which means the PWM timer/counter ramping up and resetting. For 10 bits of precision you need a 10 bit counter, which takes 1024 clock cycles to count to the maximum value. The "hello" wave file I'm trying to duplicate is hello.wav. The Atmega source code version of this file, plus the code to set up the PWM output is audio.c. It uses OC2A as the output, which is pin PB3. Run the output through a resistor and capacitor in series and then to ground, and then measure the voltage across the capacitor with a scope. It looks like an audio signal to me. Play around with the RC time constant to minimize ripple and maximize fidelity. |
March 09, 2010 by bretm |
Oh my god, it totally works. This is awesome. I connected the output to the gate of one of the 2N7000's, biased it with the blue Nerdkits potentiometer, and drove and 8-ohm speaker. It's crystal clear. I'll put together a schematic. |
March 09, 2010 by bretm |
Adjust R2 until the sound doesn't distort. Increasing R1 will reduce the ringing but will also reduce the volume. It just needs a better amplifier--I just whipped this together. |
March 09, 2010 by BobaMosfet |
bretm-
Hmmmm... I see what you're saying. I was thinking (instead) that when the counter reached TOP and the compare was made with OCR2xx, that it would reset the counter then and there, but perhaps not. I'm going to have to play with it, look at it when I'm less tired (my head is full right now). BM CONGRATULATIONS on getting your project to work-- that is AWESOME! |
March 09, 2010 by bretm |
Thanks! And norby31, for the guitar app all you really need from this are 3 lines from the main() routine from audio.c to get the PWM output going, and then just keep setting the OCR2A register to a value from 0 to 255 to represent the analog voltage level:
Then filter with R1 and C1 from the circuit above (but probably with a larger R1 value) and you should be able to connect to an amplifier. |
March 09, 2010 by norby31 |
Yeah, thanks man! I checked out your earlier post and figured it out. I hope I can run the adc and "dac" at the same time...seems like I read something in the datasheet??? I'll try it in the next couple days when I'm not studying. |
March 10, 2010 by bretm |
Should be no problem to run the ADC and PWM at the same time except that the ADC will be picking up a little bit of noise from the PWM circuitry. I don't think it will make a difference for your particular application. |
March 11, 2010 by norby31 |
Well I've got a signal in and a signal out. It sounds pretty crappy. I assume since your sample sounded much better that things are weird in the frontend. Things I am going to investigate: supply voltage, preamp(it's a guitar signal), scaling factors, slower tc on integrator. |
March 14, 2010 by norby31 |
The best quality signal I'm getting as output(at the moment--I had one that was semi-clean with kind of a puffy fuzz surrounding the notes)sounds like a guitar going through a distortion pedal. So I'm assuming there are square waves being output. .1 uF cap +10k for "integrator" ---helped I built a little voltage divider for the input to the ADC which didn't help. I figured I was just getting the tops of the signal and the bottom was being clipped. I made a preamp/buffer for the frontend and that didn't do much. I messed around with the ADC prescaler settings. Some really sound awful. Some are better. I messed around with internal scaling of the value that would be written to the OCR2A. Some of that helped. Seems like 101 was better than 111. I tried a cap on the adc input inline but that really made a lot of noise. I may get the sw environment loaded on my PC so I can use the debugger. I'd like to see what's going on in there. Didn't get out the scope again. |
March 30, 2010 by norby31 |
Ok folks. Haven't tried a voltage divider on the input to "normalize" the signal to 0-1.1V range. Somebody told me to try a preamp but it doesn't do much. Any help? |
March 30, 2010 by bretm |
Distortion usually means clipping near AREF or GND on the input or VCC or GND at the output. If you can look at it with an oscilloscope you might immediately be able to see what's going on. The guitar p-p signal is AC, so did you apply a DC bias voltage before measuring at the ADC? If not, you're clipping off all of the negative samples (and perhaps risking damage to the MCU). |
March 30, 2010 by norby31 |
"did you apply a DC bias voltage before measuring at the ADC? " Of course I tried it 5 minutes after the post and everything sounds better. |
March 30, 2010 by bretm |
Woohoo! |
May 25, 2010 by norby31 |
So I've been doing pretty well as far as making my guitar effects work except for what I call "processor noise." Seems like I can hear the code being executed. I have one cool effect that randomly kills the signal for a small amount of time but when the signal is present I hear way more hiss/noise/whatever than if I was running a clean signal with no "virtual killswitch" happening. Another thing is my ring modulator, which basically multiplies the guitar signal by a triangle wave--it multiplies everything including any noise by the triangle wave. Can I get around this crap? |
June 02, 2010 by norby31 |
So are you saying that I don't need all this code? Because I removed the TCCR0n stuff and I got no output.
|
June 03, 2010 by bretm |
The TCCR0n stuff just sets up an 8khz clock which in my case I was using to read pre-recorded samples from an 8khz wav file. In your case you're reading real-time samples from the ADC, so you don't need this 8khz clock but you do need a loop to keep the sampling going. |
June 03, 2010 by norby31 |
I hate to post the whole program, but I get no output if I remove the lines with * in front of them. double count; double amp_sca; uint8_t i; int amp_int; void adc_init() { // set analog to digital converter // for external reference (5v), single ended input ADC1 ADMUX = (1<<REFS0)|(1<<REFS1)|(1<<MUX0)|(1<<ADLAR); // set analog to digital converter // to be enabled, with a clock prescale of 1/128 // so that the ADC clock runs at ... ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)|(1<<ADATE)|(1<<ADIE); // fire a conversion just to get the ADC warmed up ADCSRA |= (1<<ADSC); } volatile float this_amp; volatile int bitey; int main() { DDRB |= _BV(PB3); // OC2A pin DDRC &= ~(1<<PC5); //get 1 or 0 from pc5 for state PORTC |= (1<<PC5); adc_init(); TCCR2A = _BV(COM2A1) | _BV(WGM22) | _BV(WGM20); // PWM, phase correct, output on OC2A TCCR2B = _BV(CS20); // no pre-scale, about 29khz for phase-correct PWM OCR2A =128;
sei(); // enable interrupts in general while(1) {
} return 0; } int idx = 0; uint16_t result; uint16_t amp; ISR(TIMER0_COMPA_vect) { OCR2A = bitey; } ISR(ADC_vect){ float last_amp; amp = ADCH; this_amp = amp; this_amp=this_amp*1100/2048; // scaling factor ADCSRA |= (1<<ADSC); //prime the pump } |
June 03, 2010 by norby31 |
**ok, sorry for the bad formatting. Anyway, if I take out the TCCR0A junk then I get no output, which is contrary to the above post by bretm that I was in agreement with. double count; double amp_avg; uint8_t i; int amp_int; int vallue; void adc_init() { // set analog to digital converter // for external reference (5v), single ended input ADC1 ADMUX = (1<<REFS0)|(1<<REFS1)|(1<<MUX0)|(1<<ADLAR); // set analog to digital converter // to be enabled, with a clock prescale of 1/128 // so that the ADC clock runs at ... ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)|(1<<ADATE)|(1<<ADIE); // fire a conversion just to get the ADC warmed up ADCSRA |= (1<<ADSC); } volatile float this_amp; volatile int bitey; int main() {
sei(); // enable interrupts in general while(1) {
}
} int idx = 0; uint16_t result; uint16_t amp; ISR(TIMER0_COMPA_vect) {
} ISR(ADC_vect) { float last_amp; amp = ADCH; this_amp = amp; this_amp=this_amp*1100/2048; ADCSRA |= (1<<ADSC); //prime the pump } |
June 04, 2010 by bretm |
Is there are reason you're not using ADCL? This would be the cause of much distortion because you're only using 2 bits of precision instead of 10. If you're setting the ADC to use 8-bit mode instead of 10-bit mode then it makes sense. But since you want to do post-processing on the values, I'd use all 10 bits. You get no output because you're still using the Timer0 interrupt to set OCR2A. Just get rid of the ISR(TIMER0_COMPA_vect) routine and set the value of OCR2A whenever you know what it should be, e.g. inside your ADC interrupt routine. It could look something like this, just to pass through the audio as-is:
If you want to do some post-processing, e.g. a simple low-pass filter by 2-sample moving average, you could do something like this:
|
June 04, 2010 by norby31 |
I'm not getting distortion, I'm getting noise. It's separate from the signal. I can hear the guitar but there's a cloud of junk obscuring it. I think I'm eventually gonna use a pic or some other better suited. I've seen videos of other chips doing what I need to do but actually sounding good. |
June 04, 2010 by mongo |
I had a similar situation when I built a speech processor. I had to add a stage between the audio amp and the processor to take out the hash generated bu the digital modulation. It was a form of low pass filter and I think it was a single resistor and a capacitor. I'll have to dig it up and take a closer look to be sure. |
June 05, 2010 by hevans (NerdKits Staff) |
Hi norby, There is no reason why a PIC would be better suited for this sort of processing (unless it is a more powerful PIC). If you take your time and really understand the code you are writing it should be possible to get some really neat audio processing done. Did you try the suggestion bretm gave above. It does look like you were just using the high two bits of the ADC input. That could definitely cause some crazy results. Also, is there a reason you are using a phase correct PWM output? A fast PWM output in my opinion is at least easier to think about, and can give you a faster PWM frequency. You then have more room to set your low pass filter to get a good sound. Humberto |
June 05, 2010 by norby31 |
"It does look like you were just using the high two bits of the ADC input." I set ADLAR up above in my code. If you set the ADLAR bit then you can just read the ADCH for 8 bit resolution. I had previously used 10 bits as presented by BretM. Results basically the same. The noise is neither harmonically nor rhythmically related to the signal nor a function of the volume at which the signal enters the processor. |
June 05, 2010 by norby31 |
also, to affect the tccr2b register, shouldn't it be:
instead of
|
June 08, 2010 by hevans (NerdKits Staff) |
Hi norby31, "I set ADLAR up above in my code" ah I hadn't noticed that, you are good there then. I took a closer look at your code and found a few things you might want to look at to see if they are causing your issue. I changed some things around in your code, and the whole thing is at the end of this post. Here is a list of what I did:
Note that this setup actually greatly approximates the suggestion bretm gave you on March 09, 2010 =) Below the modified version of your program. I didn't know why you had some of the scale factors the way you did, so I left them in there. You should take care to make sure the output ends up between 0-255 or else overflows will cause strange things to happen. Remember the is definitely still going to have to be a low pass filter at the output with the cut-off frequency in the right range. Give it a shot and let us know how it goes.
|
June 08, 2010 by norby31 |
"Note that this setup actually greatly approximates the suggestion bretm gave you on March 09, 2010 =)" You gotta say stuff like that? Why are you so condescending? It's clearly stated that people are supposed to be nice here, and you are an employee of the company and should stick to that credo. |
June 08, 2010 by hevans (NerdKits Staff) |
Hi Norby, I apologize if you thought I was being condecending. I really was merely trying to help you with your project. I put that note into my post to give credit to bretm (and thank him) for his earlier suggestion, and provide a reference to a different explanation. I was in no way trying to make a comment about you, and I'm very sorry if it came off that way. I assure you I had nothing but the best intentions. Humberto |
June 08, 2010 by norby31 |
Ok, thanks Humberto. Apology accepted. I'm gonna try the program when I get a chance and I appreciate the great advice. :) |
June 11, 2010 by norby31 |
So I have a question: I have input going to the PC1 pin and I'm getting an output signal on the OCR2A pin, yet--the MUX0:1:2:3 pins are set to zero?? Am I losing my marbles? |
June 11, 2010 by norby31 |
I mean MUX bits are set to zero. To clarify--I am expecting to be able to use the ADC0 as input but ADC1 is working instead. If I try ADC0 it sounds like I am trying to run my signal through an unpowered op-amp. And the noise is still a problem. |
June 13, 2010 by norby31 |
OK--- Check it out: noise problems 90% gone. What I did was I kept thinking of the 1.1V ref as being similar to a virtual ground on an op-amp, i.e some signal could go above 1.1V, some could go below 1.1V. As soon as I changed my voltage divider to make a .5V DC value everything sounded 10x better. For some reason I didn't think of the 1.1V as being the entire range. |
June 13, 2010 by BobaMosfet |
norby31- Can you post a schematic picture or something? I'd like to get a better idea of what components you're using, so I can look up the datasheets, and fiddle with it myself with an o-scope, to see if perhaps I can help you improve this. Also-- have you considered 60Hz noise (it's everywhere) from all the A/C power around us? You might want to put in a 60Hz notch filter. I'm mostly interested in your input signal chain and your output signal chain of components. If you've got a transistor in the mix, it may be that you haven't worked out the gain properties so that you know what the perfect range is (in which case distortion may be caused by clipping or some other issue). BM |
June 14, 2010 by norby31 |
Boba, it's sounding great. It could be improved but I'm pretty happy about going from constant loud vacuum cleaner sound to pretty mild high pitched oscillator noise. If I increase the sample rate perhaps that could rid the oscillator noise as well. |
June 26, 2010 by norby31 |
I was messing around with the cap values for the AREF pin for noise shunting and I changed from a .01 uF to a .1 uF. Wow!! amazingly quiet circuit now. Coolsville. |
March 31, 2011 by hariharan |
How do you output a specific frequency using pwm? i am really new to this topic,so i might need some 'extra' help. |
July 18, 2011 by kayrock |
Hey norby, I am trying to do the same thing. Could you post a schematic of what goes in between the guitar's output and the atmega's ADC? |
Please log in to post a reply.
Did you know that interrupts can cause problems if you're not careful about timing and memory access? Learn more...
|