Handling 10-bit PWM versus 8-bit

Go To Last Post
12 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm working on a project using the Tiny84 AVR.
I'm using 2 ADCs and PWM. I read battery voltage to calculate what PWM duty is required to give an RMS voltage equal to the user's "requested voltage".
ADC2 reads bettery voltage via a resistor divider.
ADC3 reads the user's "request voltage" via a pot.
I have this working fine by setting up 8-bit PWM and reading the upper 8-bits of the ADC (ADCH) by setting the ADLAR bit.
The code below shows the main() section of my code.
This works very well, but it doesn't quite give me the resolution I'd like.

uint16_t pwmval = 0;
uint16_t adc2ct = 0;
uint16_t adc3ct = 0;
uint16_t adcsq = 0;
uint16_t vbulb = 0;

int main()
{
 CLKPR = 128;
 CLKPR = 0;
 
 initPORTS();
 initPWM(); //8-bit, Phase correct
 initADC(); //initializes the ADC
   for (;;)
   {
    setreadADC(0x82); //0x82 changes ADMUX for Battery voltage read.
    adc2ct = ADCH;

    setreadADC(0x03); //0c03 changes ADMUX for Request voltage read
    adc3ct = ADCH;

    adcsq = (adc2ct * adc2ct) >> 8;// square and scale Vbatt^2
    vbulb = adc3ct;

    if (adcsq * pwmval >= vbulb * vbulb) --pwmval;
    else ++pwmval;
    
    if (pwmval > 255) pwmval=255;
    if (pwmval < 0) pwmval=0;
    
    OCR1A = pwmval;
   }
	
}

But I can't seem to get it to work using the full 10-bit ADC (no ADLAR set), and 10-bit PWM. I know it has something to do with squaring a 10-bit number, but I'm not sure how to fix it.
I've been trying to research the different typedefs and floating point math, but I'm still kind of lost.
Could someone help me out here?
Thanks a lot.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hint: when ADLAR is zero, you must read both ADCL and ADCR, in that order, to get your 10-bit result.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

wg0z wrote:
Hint: when ADLAR is zero, you must read both ADCL and ADCR, in that order, to get your 10-bit result.

Sorry, I didn't indicate that I was no longer just reading ADCH. In trying to test 10-bit I was using ADC not ADCH.
In testing I was able to use "ADC" to get the ADC results.
For testing purposes, I set up 10-bit PWM, did not set the ADLAR bit, and sent the adc reading right to the OCR1A register so I could get feedback with my scope. It worked fine as below...

  adc2ct = ADC;

  OCR1A = adc2ct;

You said I would have to read both ADCH and ADCL. Is using "ADC" the same thing?

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

yep... so you're OK wrt that.
but as you suspected: when you square two 10 bit numbers,
in the general case 16 bits is insufficient for the result.
-> change adcsq to type U32

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

wg0z wrote:
yep... so you're OK wrt that.
but as you suspected: when you square two 10 bit numbers,
in the general case 16 bits is insufficient for the result.
-> change adcsq to type U32

Would I do that by...

uint32_t adcsq = 0;

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That is a start, but with this:

    adcsq = (adc2ct * adc2ct) >> 8;

then adc2ct is still 16 bit, so all the math on the right hand side will be done with 16 bit math. It is only converted to 32 bit when the "=" is evaluated. You need to coerce to 32 bit before the multiply:

    adcsq = ((uint32_t)adc2ct * adc2ct) >> 8;

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK. I'll give it a try.
But since I got the 8-bit code from a friend. I'm not sure what the ">> 8" means at the end.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK. Below is the code I'm using...

uint16_t pwmval = 0;
uint32_t pwmduty = 0;
uint16_t adc2ct = 0;
uint16_t adc3ct = 0;
uint32_t vbatsq = 0;
uint32_t vbulbsq = 0;

vbatsq = ((uint32_t)adc2ct * adc2ct) >> 8;
vbulbsq = ((uint32_t)adc3ct * adc3ct) >> 8;

pwmduty = ((uint32_t)(pwmval/1023.0)) >> 8;

if (vbatsq * pwmduty >= vbulbsq) --pwmval;
  else ++pwmval;
		
  if (pwmval > 1023) pwmval=1023;
  if (pwmval < 0) pwmval=0;
	 	
OCR1A = pwmval;

As soon as I program the chip.
Duty shoots to 100% (pwmval=1023) and slowly decreases until 0 then it starts again at 1023 and begins down counting.
Could you help me understand what's going on?
From the code it looks like (vbatsq * pwmduty) is always less than (vbulbsq). But why would the pwmduty then instantly become 100% again?
Thanks, this has got me perplexed.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I was able to figure out what the >> 8 is. A quick divide by 256.
So, I got rid of the >> 8 business. But it still never reaches equilibrium.
Given the following:
adc2ct = 1023
adc3ct = 512
The following code should reach ~equilibrium
with a pwmval = 256.

vbatsq = ((uint32_t)adc2ct * adc2ct);
vbulbsq = ((uint32_t)adc3ct * adc3ct);

pwmduty = ((uint32_t)(pwmval/1024.0));

if (vbatsq * pwmduty >= vbulbsq) --pwmval;
 else ++pwmval

But it doesn't
Can someone help shed some light on this?
Thanks, all.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

if you want to use pwmduty as a fraction in the range 0 to 1.0 you'll need to make it a float...
better to use it as a percentage 0..100, thus:

// begin snippet
vbatsq = ((uint32_t)adc2ct * adc2ct);
vbulbsq = ((uint32_t)adc3ct * adc3ct);

// must multiply by 100 BEFORE the div-by-1024
pwmduty_pct = ((uint32_t)(pwmval)* 100L /1024L));

if (vbatsq * pwmduty_pct / 100 > vbulbsq)
--pwmval;
else if (vbatsq * pwmduty_pct / 100 < vbulbsq)
++pwmval;

// regardless of all else, limit pwmval to 1023
if (pwmval > 1023)
pwmval = 1023;

// end snippet

explicit type promotions left in for clarity and certainty

for your test values pwmduty_percent should stabilize at 25, pwmval = 25

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

oops add a '6' at the end of previous

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks a lot for the code. I ended up trying something a little different. To avoid using Floats,
I did the following...

vbatsq = ((uint32_t)adc2ct * adc2ct) >> 10;
  // square and divide above by 2^10 = 1024
vbulbsq = ((uint32_t)adc3ct * adc3ct);
  // Square Request Voltage, no division required.
        
if (vbatsq * pwmval > vbulbsq)
{
	--pwmval;
}
else ++pwmval;

I used a "trick" a friend used in his 8-bit code. >> 8 shifts bits 8 places to divide by 256. So I used >>10 to divide by 1024. My code stays quite small.
I'm using the same trick while averaging my ADC reads. As long as I read 2, 4, 8, or 16 times, the math stays quick.
Do you see any issues using this "division trick"?
But, I appreciate your response. I'm still learning, so even though I fixed the problem one way, It's good to see other examples of how to do it.
-cheers

Jim M., Rank amateur AVR guy.