This is an analysis of five approaches for calculating the scaling of ADC(0-4095) to Volts (0-200). I was asked a question yesterday on 'how to' to this.
Dim VOLTS As Byte
Dim ADCValWord As Word
VOLTS = ADCValWord * 5 * 40 / 4095
The ask looks simple but there a overflow issues, and, the calculation uses Long calcs and needs to a Byte.
Updated Summary:
Most efficient memory: Approach #3 (415 words) maintaining accuracy
Most RAM efficient: Approach #1 (25 bytes) maintaining accuracy
Most readable: Approach #2 (Scale function) maintaining accuracy
Most precise: Approach #4 (Shift method) maintaining accuracy
Approach with acceptable accuracy loss: Approach #5
The trade-offs between these approaches involve:
Program memory usage vs RAM usage
Code readability vs execution efficiency
Mathematical precision vs resource usage
The best choice depends on your specific requirements:
If program memory is tight: Use Approach #3
If RAM is critical: Use Approach #1
If maintainability is a priority: Use Approach #2
If precision is crucial: Use Approach #4
If accuracy loss can be accepted: Use Approach #5
If simplicity and avoiding overflow - the programmer needs to specify a Long variable: Use Approach #6
What other ways of doing this are there?
Your views?
ADCValLong_Temp = ADCValLong * 256 ADCValLong_Temp = ADCValLong_Temp - (ADCValLong * 56)
Repeat 12
Set C Off
Rotate ADCValLong_Temp Right
End Repeat
Uses bit shifting to optimize calculation
Resource usage: 490 words, 34 bytes RAM
Most complex code but handles overflow well
Highest program memory usage
Most mathematically precise approach
Approach#5(Approx method)
DimADCValWordasWordADCValWord=4095' Divide by 4 first (shift right 2) Repeat 2 Set C Off Rotate ADCValWord Right End Repeat' Now divide by 5 Volts = ADCValWord / 5
Let's look at factorizing 200/4096. Let's break it down step by step:
200/4096 can be factored as:
200 = 2^3 × 5^2 (8 × 25)
4096 = 2^12
So 200/4096 = (2^3 × 5^2) / 2^12 = 5^2 / 2^9 = 25 / 512
This means we could:
Shift right 9 times (divide by 512)
Then multiply by 25
This should:
Be more accurate than dividing by 20
Use fewer operations than previous approaches
Avoid the need for LONG variables
Still handle the full input range correctly
Resource usage: 221 words, 11 bytes RAM
Approach #6
Dim ADCValLong as Long
ADCValLong = 4095
VOLTS = ADCValLong * 200 / 4095 // 394 words, 29 bytes RAM
Uses direct calculation
Resource usage: 394 words, 29 bytes RAM
Avoids overflow by using LONG type cast
Simple and straightforward
Code - you can play with this. I used the 16F887a as I could run the result into a simulator to check results.
#chip16F877a#optionExplicit#DEFINEUSART_BAUD_RATE9600#DEFINEUSART_TX_BLOCKING#DEFINEUSART_DELAYOFFDimVoltsasByte//!Approach#1//DimADCValWordasWord//ADCValWord=4095//VOLTS=([LONG](ADCValWord*5)*40)/[WORD]4095//455wand27ram//!Approach#2//DimADCValWordasWord//ADCValWord=4095//VOLTS=Scale(ADCValWord,0,4095,0,200)//463wand45ram//!Approach#3//DimADCValLongAsLong//ADCValLong=4095//VOLTS=ADCValLong*5*40/4095//416wand29ram//!Approach#4/* For GCBASIC, we can break this down since we want to scale ADCValLong (0-4095) to a byte range (0-255). Let's analyze the calculation: The formula `ADCValLong * 5 * 40 / 4096` can be rewritten as: - `ADCValLong * 200 / 4096` (since 5 * 40 = 200) - `ADCValLong / 20.48` (since 4096/200 = 20.48) In GCBASIC with shifts: 1. 4096 is 2^12, so dividing by 4096 is a right shift by 12 2. To multiply by 200 we can use 256 (2^8) and subtract 56 3. This gives us: `(ADCValLong * 256 - ADCValLong * 56) >> 12 Here's the efficient code: ' Shift left by 8 (multiply by 256) ADCValLong_Temp = ADCValLong * 256 ' Subtract (ADCValLong * 56) ADCValLong_Temp = ADCValLong_Temp - (ADCValLong * 56) ' Shift right by 12 to complete division by 4096 Repeat 12 Set C Off Rotate ADCValLong_Temp Right End Repeat result = ADCValLong_Temp *///DimADCValLong,ADCValLong_TempAsLong//ADCValLong=4095//ADCValLong_Temp=ADCValLong*256//' Subtract (ADCValLong * 56) // ADCValLong_Temp = ADCValLong_Temp - (ADCValLong * 56) // 'Shiftrightby12tocompletedivisionby4096//Repeat12//SetCOff//RotateADCValLong_TempRight//EndRepeat//Volts=ADCValLong_Temp//303wand31ram//!Approach#5/* This approach: Uses fewer resources than all previous methods More mathematically straightforward Still maintains good accuracy Avoids overflow issues No need for LONG variables Simpler to understand and maintain The error from using 20 instead of 20.475 is minimal in this application, and the resource savings are significant. *///Startwithvalue0-4095//DimADCValWordasWord//ADCValWord=4095//' Divide by 4 first (shift right 2) // Repeat 2 // Set C Off // Rotate ADCValWord Right // End Repeat // 'Nowdivideby5//Volts=ADCValWord/5//!Approach#6DimADCValLongasLongADCValLong=4095VOLTS=ADCValLong*200/4095//394wand29ramHSerPrintVOLTS
I did this analysis - over a coffee this morning.
This is an analysis of five approaches for calculating the scaling of ADC(0-4095) to Volts (0-200). I was asked a question yesterday on 'how to' to this.
The ask looks simple but there a overflow issues, and, the calculation uses Long calcs and needs to a Byte.
Updated Summary:
Most efficient memory: Approach #3 (415 words) maintaining accuracy
Most RAM efficient: Approach #1 (25 bytes) maintaining accuracy
Most readable: Approach #2 (Scale function) maintaining accuracy
Most precise: Approach #4 (Shift method) maintaining accuracy
Approach with acceptable accuracy loss: Approach #5
The trade-offs between these approaches involve:
Program memory usage vs RAM usage
Code readability vs execution efficiency
Mathematical precision vs resource usage
The best choice depends on your specific requirements:
If program memory is tight: Use Approach #3
If RAM is critical: Use Approach #1
If maintainability is a priority: Use Approach #2
If precision is crucial: Use Approach #4
If accuracy loss can be accepted: Use Approach #5
If simplicity and avoiding overflow - the programmer needs to specify a Long variable: Use Approach #6
What other ways of doing this are there?
Your views?
Evan
Approach #1
Approach #2:
Approach #3:
Approach #4 (Shift method):
Approach#5(Approx method)
Let's look at factorizing 200/4096. Let's break it down step by step:
200/4096 can be factored as:
200 = 2^3 × 5^2 (8 × 25)
4096 = 2^12
So 200/4096 = (2^3 × 5^2) / 2^12 = 5^2 / 2^9 = 25 / 512
This means we could:
Shift right 9 times (divide by 512)
Then multiply by 25
This should:
Be more accurate than dividing by 20
Use fewer operations than previous approaches
Avoid the need for LONG variables
Still handle the full input range correctly
Resource usage: 221 words, 11 bytes RAM
Approach #6
Code - you can play with this. I used the 16F887a as I could run the result into a simulator to check results.
Last edit: Anobium 2025-02-10
Very interesting reasoning.
When I needed it I always used "scale".
Just a correction. 2^12 is 4095.
Approach #1 and #3 are dividing by 4096, it should be 4095 like the rest of the examples.
A 12 bit ADC max reading is 4095, so if it was scaling to a max of 200 it would read 4095.
I always use what I think is the simplest:
Volts = [long]ADCValWord * 200 / 4095
This takes, if I'm not mistaken, 394 words and 27 bytes.
Later,
Clint
Good spot! I have corrected the whole post... adding your new option.
Last edit: Anobium 2025-02-10
If you use:
Instead of this:
You save 2 bytes of RAM. I think it is because it uses an internal long variable that is already declared.