Mapping input value to a exponential output value.

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

Hi all,

 

 

 

I'm trying to take a 10bit ADC value and output it as an exponential pwm value to OCR0A/OCR0B.

example:

ADC--->PWM

515--->256

514--->229

513--->192

512--->128

511--->64

510--->26

509--->0

 

CPU cycles are important so I would rather not do math or an if or switch statement.

Ideally some sort of lookup table would be best, but I spent the last day looking into options and I can't seem to find the right track. Arrays were a dead end as they are indexed from 0. Structures seemed to be viable, but I can't figure out how to select  the correct member from the adc result.

 

Any ideas? Thanks!

 

edit: The numbers I gave for the pwm output are logarithmic, but you get the idea.

Last Edited: Wed. Oct 25, 2017 - 05:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

binary_99 wrote:
Arrays were a dead end as they are indexed from 0.
So you subtract 509 from your ADC reading:

uint8_t PWMOutput[] = { 0, 26, 64, 128, 192, 229, 255 };

if ((ADCReading >= 509) && (ADCreading < 516)) {
  OCR = PWMOutput[ADCReading - 509];
}

 

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

Thanks for the input Clawson.

Like I said, I would like, if at all possible, to keep math and if statements out of it to keep the overhead to a bare minimum. If I can't find a solution, that is the route I'll take.

 

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

binary_99 wrote:
keep the overhead to a bare minimum

Written as a called function, this is a pretty minimal solution:

000000c4 <TableLookup>:
  uint8_t PWMOutput[] = { 0, 26, 64, 128, 192, 229, 255 };

uint8_t TableLookup(uint16_t adc_reading) {
    uint8_t value = 0;
  if ((adc_reading >= 509) && (adc_reading < 516)) {
  c4:	8d 5f       	subi	r24, 0xFD	; 253
  c6:	91 40       	sbci	r25, 0x01	; 1
  c8:	87 30       	cpi	r24, 0x07	; 7
  ca:	91 05       	cpc	r25, r1
  cc:	28 f4       	brcc	.+10     	; 0xd8 <TableLookup+0x14>
      value =(PWMOutput[adc_reading - 509]);
  ce:	fc 01       	movw	r30, r24
  d0:	e0 50       	subi	r30, 0x00	; 0
  d2:	ff 4f       	sbci	r31, 0xFF	; 255
  d4:	80 81       	ld	r24, Z
  d6:	08 95       	ret

  d8:	80 e0       	ldi	r24, 0x00	; 0
  if ((adc_reading >= 509) && (adc_reading < 516)) {
      value =(PWMOutput[adc_reading - 509]);
  }
  return value;
}
  da:	08 95       	ret

 

 

David (aka frog_jr)

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

binary_99 wrote:
to keep math and if statements out of it to keep the overhead to a bare minimum.

You have to quantify this for us to be more interested.  We have seen many requests for "perfect timekeeping" and the like.  In the end, the poster isn't really ready to pay the price for even "near perfect".

 

In this case, what overhead is tolerable?  Which AVR model are you using, and what clock speed?  On the surface your request/"requirements" don't make much sense:

--  Even at /1, if the timer is set up for 8-bit PWM then there are 256 AVR clock cycles between periods.

--  OCR0A/B cannot have a value of 256.

--  Fast PWM on an AVR8 cannot have meaningful OCR values of both full-on and full-off.

--  You really want to apply a new reading as fast as possible with a big jump on output OCR value, and a SINGLE LSB difference in the input value?!?

--  (yes, I usually get stable count AVR8 ADC readings from a stable signal...but that is after gathering and averaging a number of readings to get rid of mains hum and similar...that takes clock cycles and "math")

--  OK, so say you are using an AVR model with a PLL and the PWM clock is, say, 64MHz.  Then indeed there are only 4 microseconds for each period.  HOWEVER, an AVR8 ADC takes ~100us to get a new conversion value.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

frog_jr wrote:
Written as a called function, this is a pretty minimal solution:

I agree, but you are doing the dreaded math in there, with a 16-bit subtraction.  ;)  Darn those 500 nanoseconds.

 

OP could use ADLAR and a 256 entry lookup table.  Is the overkill in the table space "overhead"?  Anyway, then there would be no maths or conditional logic.

 

In any real app, there would be a test for >515 and a limit applied, as well as the lower bound.  Without them, OP will be doing head-scratching when the input signal is not totally well behaved.  [Hmmm--my solution eliminates that...]

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

And here I thought I used a straightforward process to start a fan motor from a battery using PWM and a reading of battery level to get a more-or-less constant fan speed regardless of battery level.

 

Another fudge-factor needed as the internal BG is used for the battery readings (and even that is indirect as Vcc (battery) is reference and BG is the channel) and one-time calibration is done.

				// Rev. g -- motor doesn't start/run at 60% of less than ~2.3V
				//	So make the PWM duty proportional to battery V
				//	ADJ = ADC * CAL / NOM but that needs 32 bit arithmetic and short of space.
				//	So tweak the ADC counts a bit using BG_NOM (341 at 3.3V) and ee_bg_cal.
				//	This will give a nearly-exact correction at 3.xV but about 10 counts off
				//	at lower supplyV values.
				worknum = adc_batt;		// e.g. 490 at 2.3V
				worknum += BG_NOM;		// 341 for exactly 1.1V @3.3Vcc
				worknum -= ee_bg_cal;	// 332 on test unit
									// so adc_batt moves about 10 counts in the right
									//	direction, closer to the middle column
				worknum -= 40;			// subtract a fudge factor, so that at 3.3V
				worknum /= 2;			//	the value ends up ~150 => ~60%
									// Alternative is to multiply by 0.44;  4/9
									// Now limit
				if (worknum < 150) worknum = 150;
				if (worknum > 255) worknum = 255;

				// Start PWM timer

				PRR &= 	~(1 << PRTIM0);

// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: 1000.000 kHz
// Mode: Fast PWM top=0xFF
// OC0A output: Non-Inverted PWM
// OC0B output: Disconnected
// Timer Period: 0.256 ms
// Output Pulse(s):
// OC0A Period: 0.256 ms Width: 0.256 ms

				OCR0A = (unsigned char)worknum;	// apply "proportional" value
...

[after the above, I was curious myself and looked at the generated code.  38 cycles, plus an RCALL for a 16-bit EEPROM read.  So about 50us at 1MHz.  WAY too much overhead for the OP, I'm afraid...]

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Wed. Oct 25, 2017 - 07:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Welcome to the Forum.

 

You've left a few of us intrigued as to why your system is so computationally constrained.

 

Knowing the system inputs and outputs, response times, etc., would generate more interest in "optimized" (for speed) approaches.

 

That said, an absolutely best possible approach is going to take X number of cycles.

So the downside to using a non-optimized approach is only the increase in cycles ABOVE the absolutely optimized approach.

That increase above baseline may be rather small, and just not worth the effort for the "clever" programming, etc.

 

Of course you can use another approach if every clock cycle counts... and use a faster micro!

 

If you are running your micro at 16 MHz the selecting one that runs at 20 MHz gives you a 25% increase in throughput.

 

If you switch to an Xmega, running in spec at 32 MHz, you have a 100% increase in throughput, or worded another way you have gained an extra 32, 16,000,000 clock cycles / second for processing your code.

 

With the Xmega, then, you can focus on working, easily understandable and debuggable code, with end point  and special case testing, etc., and still have room to spare, and more importantly move forward with your project rather than have to worry about throughput constraints.

 

JC

 

Edit:  Opps, I didn't have enough fingers to count on to verify my math...

Guess we can write this post off as Fake News...

Last Edited: Wed. Oct 25, 2017 - 08:08 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

DocJC wrote:
If you switch to an Xmega, running in spec at 32 MHz, you have a 100% increase in throughput, or worded another way you have gained an extra 32,000,000 clock cycles / second for processing your code.

 

Given the above along with e.g. current discussion on US tax code "overhaul", you should get a job writing government press releases.

 

DocJC wrote:
You've left a few of us intrigued as to why your system is so computationally constrained. Knowing the system inputs and outputs, response times, etc., would generate more interest in "optimized" (for speed) approaches.

Agreed.  As I tried to point out above, it doesn't make too much sense with what we know now.  But certainly there are apps with tight "pixel loops" or similar that may be constrained.  I've done many scores of production AVR8 apps and have yet to run out of gas at the relatively modest clock rates; only a couple run faster than 8MHz.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

Hello, 

    Math and If statements only take lots of time and memory when they use float type numbers.   Integers are processed (add, subtract, compare) in a few cycles.

 

    Your example shows a very narrow range of ADC: 6-8 out of a possible 1024.   This range is too small for real-world control, especially if you are using a potentiometer to set the voltage level that is going to go into the ADC.    I suggest using the entire range of the ADC.  If you are only referencing about 16 or so different settings, shift the ADC result (0-1203) five places to the right to divide by 64.  This splits the ADC range into 16 areas.   Then use a look-up table with the index being the modified ADC result:   table[2] = pwm value 40 (out of 256), ... etc...
 

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

theusch wrote:

In this case, what overhead is tolerable?  Which AVR model are you using, and what clock speed?  On the surface your request/"requirements" don't make much sense:

--  Even at /1, if the timer is set up for 8-bit PWM then there are 256 AVR clock cycles between periods.

--  OCR0A/B cannot have a value of 256.

--  Fast PWM on an AVR8 cannot have meaningful OCR values of both full-on and full-off.

--  You really want to apply a new reading as fast as possible with a big jump on output OCR value, and a SINGLE LSB difference in the input value?!?

--  (yes, I usually get stable count AVR8 ADC readings from a stable signal...but that is after gathering and averaging a number of readings to get rid of mains hum and similar...that takes clock cycles and "math")

--  OK, so say you are using an AVR model with a PLL and the PWM clock is, say, 64MHz.  Then indeed there are only 4 microseconds for each period.  HOWEVER, an AVR8 ADC takes ~100us to get a new conversion value.

 

Thanks theusch, you have brought up some interesting points. I can appreciate the mired  of nonsensical questions that you must all deal with, as mine may be one of them....lol.

I didn't want to go into too much detail about what I am doing because that just complicates everything and everyone always jumps in with their "idea" of how it should be done on forums and things quickly go off the rails.

 

-The 2.8V reference voltage the ADC reads only varies +/-0.0425V hence the need for the 10 bit ADC. The example range I gave was just that, an example. The true resolution will  be 32 values. 0.085V/(2.8V/1024)

 

-Please excuse my inexcusable use of 256. Of course the highest value could be 255 if it were theoretically possible to achieve......I was in need of a coffee.

 

-The clock will run at 1MHz with a /1 for the PWM giving my ~3.9K cycles/sec. I don't know if I was reading what you said right, but you quoted a 64MHz  clock would ~100us to get a new value? I was under the impression an AVR takes ~25 clock cycles after ADEN is enabled to get a reading or something similar? Never mind. I just read the datasheet and factored in the ADC clock division.

 

-The one thing you did say that renders my efforts mute is that I'm waiting 256 cycles to update the OCR registers anyway. I'll go with with something similar to what Clawson suggested with an array and subtract from the adc result to match the array.

 

Thanks guys!

Last Edited: Wed. Oct 25, 2017 - 08:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I'll go with with something similar to what Clawson suggested with an array and subtract from the adc result to match the array.

Don't forget to do bounds checking to ensure you don't get a negative array index, or fall off the other end of the array.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

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

joeymorin wrote:
do bounds checking
If only I'd thought of that in #2 eh? cheeky

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

Didn't see it ;-)

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]