Skip to content

Instantly share code, notes, and snippets.

@mofosyne
Last active February 27, 2025 15:23
Show Gist options
  • Save mofosyne/afd4cbd06a7d3b9ee00088ad366a21dc to your computer and use it in GitHub Desktop.
Save mofosyne/afd4cbd06a7d3b9ee00088ad366a21dc to your computer and use it in GitHub Desktop.
These are C macro for converting between ADC to millivolt and back. Might be useful for embedded projects.
/* ADC Linear Conversion Macros
Brian Khuu 2025
ADC_MILLIVOLT_FROM_ADC_VAL(ADC_BIT_COUNT, MILLI_VOLT_REFL, MILLI_VOLT_REFH, ADC_VAL) = ( (MILLI_VOLT_REFL) + ((ADC_VAL) * (((MILLI_VOLT_REFH) - (MILLI_VOLT_REFL)) / (1 << (ADC_BIT_COUNT)))) )
ADC_VAL_FROM_MILLIVOLT(ADC_BIT_COUNT, MILLI_VOLT_REFL, MILLI_VOLT_REFH, MILLIVOLT) = ( (((MILLIVOLT) - (MILLI_VOLT_REFL)) * (1 << (ADC_BIT_COUNT))) / ((MILLI_VOLT_REFH) - (MILLI_VOLT_REFL)) )
*/
// This is the linear conversion macros
#define ADC_MILLIVOLT_FROM_ADC_VAL(ADC_BIT_COUNT, MILLI_VOLT_REFL, MILLI_VOLT_REFH, ADC_VAL) ( (MILLI_VOLT_REFL) + ((ADC_VAL) * (((MILLI_VOLT_REFH) - (MILLI_VOLT_REFL)) / (1 << (ADC_BIT_COUNT)))) )
#define ADC_VAL_FROM_MILLIVOLT(ADC_BIT_COUNT, MILLI_VOLT_REFL, MILLI_VOLT_REFH, MILLIVOLT) ( (((MILLIVOLT) - (MILLI_VOLT_REFL)) * (1 << (ADC_BIT_COUNT))) / ((MILLI_VOLT_REFH) - (MILLI_VOLT_REFL)) )
// This uses left shift (Akin to scaling down via multiplication) to temporarily convert the integer division into fixed integer arithmetic operation
// before using right shift (Akin to scaling upward via division) to convert from fixed notation back to the original scale but with better precision
#define ADC_MILLIVOLT_FROM_ADC_VAL_INTEGER(ADC_BIT_COUNT, SCALING_FACTOR, MILLI_VOLT_REFL, MILLI_VOLT_REFH, ADC_VAL) (ADC_MILLIVOLT_FROM_ADC_VAL(ADC_BIT_COUNT, MILLI_VOLT_REFL << SCALING_FACTOR, MILLI_VOLT_REFH << SCALING_FACTOR, ADC_VAL) >> SCALING_FACTOR)
#define ADC_VAL_FROM_MILLIVOLT_INTEGER(ADC_BIT_COUNT, SCALING_FACTOR, MILLI_VOLT_REFL, MILLI_VOLT_REFH, MILLIVOLT) (ADC_VAL_FROM_MILLIVOLT(ADC_BIT_COUNT, MILLI_VOLT_REFL, MILLI_VOLT_REFH, MILLIVOLT))
// Same as above, but scaling factor rearranged for minimal operations.
#define ADC_MILLIVOLT_FROM_ADC_VAL_INTEGER_FAST(ADC_BIT_COUNT, SCALING_FACTOR, MILLI_VOLT_REFL, MILLI_VOLT_REFH, ADC_VAL) (( (MILLI_VOLT_REFL << SCALING_FACTOR) + ((ADC_VAL) * ((((MILLI_VOLT_REFH) - (MILLI_VOLT_REFL)) << SCALING_FACTOR) / (1 << (ADC_BIT_COUNT)))) ) >> SCALING_FACTOR)
#define ADC_VAL_FROM_MILLIVOLT_INTEGER_FAST(ADC_BIT_COUNT, SCALING_FACTOR, MILLI_VOLT_REFL, MILLI_VOLT_REFH, MILLIVOLT) ( (((((MILLIVOLT) - (MILLI_VOLT_REFL)) * (1 << (ADC_BIT_COUNT))) << SCALING_FACTOR) / ((MILLI_VOLT_REFH) - (MILLI_VOLT_REFL)) ) >> SCALING_FACTOR)
#include <stdio.h>
#include <stdint.h>
#define MILLI_VOLT_REFL (0L) ///< Lower Ref Voltage
#define MILLI_VOLT_REFH (3300L) ///< Upper Ref Voltage
#define ADC_BIT_COUNT 10
#define SCALING_FACTOR 10
int main()
{
printf("\n\n# ADC CONVERSION MACRO\n\n");
printf("\n\n## Fixed Linear Conversion\n\n");
printf("\n\n### ADC Value --> Millivolt --> ADC Value \n\n");
printf("| ADC Value | Millivolts (Fixed Point) | ADC From mV (Fixed Point) | error |\n");
printf("|-----------|--------------------------|---------------------------|-------|\n");
for (uint16_t adc_val = 0; adc_val <= (1 << ADC_BIT_COUNT); adc_val += 64)
{
uint64_t millivolt_fixed = ADC_MILLIVOLT_FROM_ADC_VAL_INTEGER(ADC_BIT_COUNT, SCALING_FACTOR, MILLI_VOLT_REFL, MILLI_VOLT_REFH, adc_val);
uint16_t adc_from_mv_fixed = ADC_VAL_FROM_MILLIVOLT_INTEGER(ADC_BIT_COUNT, SCALING_FACTOR, MILLI_VOLT_REFL, MILLI_VOLT_REFH, millivolt_fixed);
printf("| %8d | %22ld | %25d | %5d |\n", adc_val, millivolt_fixed, adc_from_mv_fixed, adc_from_mv_fixed - adc_val);
}
printf("\n\n");
printf("\n\n### Millivolt --> ADC Value --> Millivolt \n\n");
printf("| MV Value | ADC From mV (Fixed Point) | Millivolts (Fixed Point) | error |\n");
printf("|--------------|---------------------------|--------------------------|-------|\n");
for (uint64_t mv_val = MILLI_VOLT_REFL; mv_val <= MILLI_VOLT_REFH; mv_val += 100)
{
uint16_t adc_from_mv_fixed = ADC_VAL_FROM_MILLIVOLT_INTEGER(ADC_BIT_COUNT, SCALING_FACTOR, MILLI_VOLT_REFL, MILLI_VOLT_REFH, mv_val);
uint64_t millivolt_fixed = ADC_MILLIVOLT_FROM_ADC_VAL_INTEGER(ADC_BIT_COUNT, SCALING_FACTOR, MILLI_VOLT_REFL, MILLI_VOLT_REFH, adc_from_mv_fixed);
printf("| %8lu mV | %25d | %22ld | %5d |\n", mv_val, adc_from_mv_fixed, millivolt_fixed, (int) millivolt_fixed - (int) mv_val);
}
printf("\n\n");
printf("\n\n## Integer Linear Conversion\n\n");
printf("Note: Interesting to see less accuracy here... but this is why we use floats or fixed point\n\n");
printf("\n\n### ADC Value --> Millivolt --> ADC Value \n\n");
printf("| ADC Value | Millivolts (Integer) | ADC From mV (Integer) | error |\n");
printf("|-----------|------------------------|------------------------|-------|\n");
for (uint16_t adc_val = 0; adc_val <= (1 << ADC_BIT_COUNT); adc_val += 64)
{
uint64_t millivolt_int = ADC_MILLIVOLT_FROM_ADC_VAL(ADC_BIT_COUNT, MILLI_VOLT_REFL, MILLI_VOLT_REFH, adc_val);
uint16_t adc_from_mv_int = ADC_VAL_FROM_MILLIVOLT(ADC_BIT_COUNT, MILLI_VOLT_REFL, MILLI_VOLT_REFH, millivolt_int);
printf("| %8d | %22ld | %22d | %5d |\n", adc_val, millivolt_int, adc_from_mv_int, adc_from_mv_int - adc_val);
}
printf("\n\n");
printf("\n\n### Millivolt --> ADC Value --> Millivolt \n\n");
printf("| MV Value | ADC From mV (Integer) | Millivolts (Integer) | error |\n");
printf("|--------------|------------------------|------------------------|-------|\n");
for (uint64_t mv_val = MILLI_VOLT_REFL; mv_val <= MILLI_VOLT_REFH; mv_val += 100)
{
uint16_t adc_from_mv_int = ADC_VAL_FROM_MILLIVOLT(ADC_BIT_COUNT, MILLI_VOLT_REFL, MILLI_VOLT_REFH, mv_val);
uint64_t millivolt_int = ADC_MILLIVOLT_FROM_ADC_VAL(ADC_BIT_COUNT, MILLI_VOLT_REFL, MILLI_VOLT_REFH, adc_from_mv_int);
printf("| %8lu mV | %22d | %22ld | %5d |\n", mv_val, adc_from_mv_int, millivolt_int, (int) millivolt_int - (int) mv_val);
}
printf("\n\n");
printf("\n\n## Floating Linear Conversion\n\n");
printf("\n\n### ADC Value --> Millivolt --> ADC Value \n\n");
printf("| ADC Value | Millivolts (dfloat) | ADC From mV (Integer) | error |\n");
printf("|-----------|------------------------|------------------------|-------|\n");
for (uint16_t adc_val = 0; adc_val <= (1 << ADC_BIT_COUNT); adc_val += 64)
{
double millivolt_int = ADC_MILLIVOLT_FROM_ADC_VAL(ADC_BIT_COUNT, (double)MILLI_VOLT_REFL, (double)MILLI_VOLT_REFH, (double)adc_val);
uint16_t adc_from_mv_int = ADC_VAL_FROM_MILLIVOLT(ADC_BIT_COUNT, MILLI_VOLT_REFL, MILLI_VOLT_REFH, millivolt_int);
printf("| %8d | %22lf | %22d | %5d |\n", adc_val, millivolt_int, adc_from_mv_int, adc_from_mv_int - adc_val);
}
printf("\n\n");
printf("\n\n### Millivolt --> ADC Value --> Millivolt \n\n");
printf("| MV Value | ADC From mV (Integer) | Millivolts (dfloat) | error |\n");
printf("|--------------|------------------------|------------------------|-------|\n");
for (uint64_t mv_val = MILLI_VOLT_REFL; mv_val <= MILLI_VOLT_REFH; mv_val += 100)
{
uint16_t adc_from_mv_int = ADC_VAL_FROM_MILLIVOLT(ADC_BIT_COUNT, MILLI_VOLT_REFL, MILLI_VOLT_REFH, mv_val);
double millivolt = ADC_MILLIVOLT_FROM_ADC_VAL(ADC_BIT_COUNT, (double)MILLI_VOLT_REFL, (double)MILLI_VOLT_REFH, (double)adc_from_mv_int);
printf("| %8lu mV | %22d | %22lf | %5.2f |\n", mv_val, adc_from_mv_int, millivolt, (double) millivolt - (double) mv_val);
}
printf("\n\n");
return 0;
}
@mofosyne
Copy link
Author

mofosyne commented Feb 24, 2025

Considering including the above into https://github.com/clibs/clib/wiki/Packages once I'm happy with the consistency of the macro in practical usage. Ergo, should I have a _float macro version?

Recommended extra reading https://dobrian.github.io/cmp/topics/linear-mapping-and-interpolation/1.IntroductionToLinearInterpolation&LinearMapping.html

@mofosyne
Copy link
Author

mofosyne commented Feb 27, 2025

ADC CONVERSION MACRO

Fixed Linear Conversion

ADC Value --> Millivolt --> ADC Value

ADC Value Millivolts (Fixed Point) ADC From mV (Fixed Point) error
0 0 0 0
64 206 63 -1
128 412 127 -1
192 618 191 -1
256 825 256 0
320 1031 319 -1
384 1237 383 -1
448 1443 447 -1
512 1650 512 0
576 1856 575 -1
640 2062 639 -1
704 2268 703 -1
768 2475 768 0
832 2681 831 -1
896 2887 895 -1
960 3093 959 -1
1024 3300 1024 0

Millivolt --> ADC Value --> Millivolt

MV Value ADC From mV (Fixed Point) Millivolts (Fixed Point) error
0 mV 0 0 0
100 mV 31 99 -1
200 mV 62 199 -1
300 mV 93 299 -1
400 mV 124 399 -1
500 mV 155 499 -1
600 mV 186 599 -1
700 mV 217 699 -1
800 mV 248 799 -1
900 mV 279 899 -1
1000 mV 310 999 -1
1100 mV 341 1098 -2
1200 mV 372 1198 -2
1300 mV 403 1298 -2
1400 mV 434 1398 -2
1500 mV 465 1498 -2
1600 mV 496 1598 -2
1700 mV 527 1698 -2
1800 mV 558 1798 -2
1900 mV 589 1898 -2
2000 mV 620 1998 -2
2100 mV 651 2097 -3
2200 mV 682 2197 -3
2300 mV 713 2297 -3
2400 mV 744 2397 -3
2500 mV 775 2497 -3
2600 mV 806 2597 -3
2700 mV 837 2697 -3
2800 mV 868 2797 -3
2900 mV 899 2897 -3
3000 mV 930 2997 -3
3100 mV 961 3096 -4
3200 mV 992 3196 -4
3300 mV 1024 3300 0

Integer Linear Conversion

Note: Interesting to see less accuracy here... but this is why we use floats or fixed point

ADC Value --> Millivolt --> ADC Value

ADC Value Millivolts (Integer) ADC From mV (Integer) error
0 0 0 0
64 192 59 -5
128 384 119 -9
192 576 178 -14
256 768 238 -18
320 960 297 -23
384 1152 357 -27
448 1344 417 -31
512 1536 476 -36
576 1728 536 -40
640 1920 595 -45
704 2112 655 -49
768 2304 714 -54
832 2496 774 -58
896 2688 834 -62
960 2880 893 -67
1024 3072 953 -71

Millivolt --> ADC Value --> Millivolt

MV Value ADC From mV (Integer) Millivolts (Integer) error
0 mV 0 0 0
100 mV 31 93 -7
200 mV 62 186 -14
300 mV 93 279 -21
400 mV 124 372 -28
500 mV 155 465 -35
600 mV 186 558 -42
700 mV 217 651 -49
800 mV 248 744 -56
900 mV 279 837 -63
1000 mV 310 930 -70
1100 mV 341 1023 -77
1200 mV 372 1116 -84
1300 mV 403 1209 -91
1400 mV 434 1302 -98
1500 mV 465 1395 -105
1600 mV 496 1488 -112
1700 mV 527 1581 -119
1800 mV 558 1674 -126
1900 mV 589 1767 -133
2000 mV 620 1860 -140
2100 mV 651 1953 -147
2200 mV 682 2046 -154
2300 mV 713 2139 -161
2400 mV 744 2232 -168
2500 mV 775 2325 -175
2600 mV 806 2418 -182
2700 mV 837 2511 -189
2800 mV 868 2604 -196
2900 mV 899 2697 -203
3000 mV 930 2790 -210
3100 mV 961 2883 -217
3200 mV 992 2976 -224
3300 mV 1024 3072 -228

Floating Linear Conversion

ADC Value --> Millivolt --> ADC Value

ADC Value Millivolts (dfloat) ADC From mV (Integer) error
0 0.000000 0 0
64 206.250000 64 0
128 412.500000 128 0
192 618.750000 192 0
256 825.000000 256 0
320 1031.250000 320 0
384 1237.500000 384 0
448 1443.750000 448 0
512 1650.000000 512 0
576 1856.250000 576 0
640 2062.500000 640 0
704 2268.750000 704 0
768 2475.000000 768 0
832 2681.250000 832 0
896 2887.500000 896 0
960 3093.750000 960 0
1024 3300.000000 1024 0

Millivolt --> ADC Value --> Millivolt

MV Value ADC From mV (Integer) Millivolts (dfloat) error
0 mV 0 0.000000 0.00
100 mV 31 99.902344 -0.10
200 mV 62 199.804688 -0.20
300 mV 93 299.707031 -0.29
400 mV 124 399.609375 -0.39
500 mV 155 499.511719 -0.49
600 mV 186 599.414062 -0.59
700 mV 217 699.316406 -0.68
800 mV 248 799.218750 -0.78
900 mV 279 899.121094 -0.88
1000 mV 310 999.023438 -0.98
1100 mV 341 1098.925781 -1.07
1200 mV 372 1198.828125 -1.17
1300 mV 403 1298.730469 -1.27
1400 mV 434 1398.632812 -1.37
1500 mV 465 1498.535156 -1.46
1600 mV 496 1598.437500 -1.56
1700 mV 527 1698.339844 -1.66
1800 mV 558 1798.242188 -1.76
1900 mV 589 1898.144531 -1.86
2000 mV 620 1998.046875 -1.95
2100 mV 651 2097.949219 -2.05
2200 mV 682 2197.851562 -2.15
2300 mV 713 2297.753906 -2.25
2400 mV 744 2397.656250 -2.34
2500 mV 775 2497.558594 -2.44
2600 mV 806 2597.460938 -2.54
2700 mV 837 2697.363281 -2.64
2800 mV 868 2797.265625 -2.73
2900 mV 899 2897.167969 -2.83
3000 mV 930 2997.070312 -2.93
3100 mV 961 3096.972656 -3.03
3200 mV 992 3196.875000 -3.12
3300 mV 1024 3300.000000 0.00

@mofosyne
Copy link
Author

I suspect I may have gotten something wrong here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment