Using the on-board ADC on STM32 for pots - noise and accuracy

I’m making a piece of hardware which can process the voltage from a pot. An STM32F013K6 takes the value from the pot and then i use a look up table to process this value and send it to a 12 bit DAC, after it used as a VCA CV.

The data from the ADC is 12bits and the LUT is a 256 (8 bit) list of uint16_t. i create an 8 bit number from the original 12 bit - I right shift to get rid of the lowest 2 bits (to improve the noise) then mask the two highest bits, leaving an 8 bits number which I use as an index to the LUT, so the LUT cycles around four times.

Unfortunately this has proved very noisy. two adjacent numbers in the lookup table read (for example) 416,448 which as im using a 3.3V reference on the DAC is 3.3*32/2^12 = ~26mV, added to this I still have some noise on the first two or three bits (the first four/five of the original ADC reading are noisy) so I end up with sometimes 200mV of noise on the DAC output.

Does anyone have any idea how I can implement this better? I thought of averaging out some of the noise on the ADC readings and maybe using linear interpolation to keep the noise on the lookup table down, but I don’t know if I’m missing something simpler, as is my first real project using STM32.

any help much appreciated

Yes, you need to filter the values provided by the ADC since they are noisy. You can use a moving average filter (“boxcar filter”), or a 1-pole IIR filter.

Also, don’t discard any bits from the averaging or filtering process (for example if you add 8 consecutive value you’ll end up with an 15-bit sum), and use linear interpolation to access your lookup table.

I don’t get any Google result for STM32F013K6 ; but some recent STM32 MCUs have a built-in hardware averager in their ADC peripheral. Check the datasheet!

thanks! my mistake its the STM32F031K6, I’m pretty sure it doesn’t have any built in averager

if I add four numbers for an average and get a 15 bit number, without discarding data, how would I then use that to access a 256 bits long LUT?

Hmmm if you add 4 12-bit numbers you’ll get a 14-bit number.

Assuming you have a 15-bit number N, you linearly interpolate between (N >> 7) and (N >> 7) + 1 using the lowest 7-bits of N.

integral = N >> 7
fractional = N & 127
(lut[integral] * (128 - fractional) + lut[integral + 1] * fractional) >> 7

A different way of evaluating this depending on the LUT’s data type:

integral = N >> 7
fractional = N & 127
a = lut[integral]
b = lut[integral + 1]
a + ((b - a) * fractional >> 7)

i played around a little - I found if I average the DAC I get very low noise, most of the time around 1LSB.

however, if I use this filtered ADC to index my look up table without interpolation I get better noise than if I use my interpolation so i’ve gone wrong somewhere. As far as I can see i get the right numbers in the simulator. My code is as follows

xfadeint_1 =  VCA_raw[0] >>6; // VCA_raw is 4 samples added to gether - 14 bits - integral leaves 8 bits for 256 long LUT

xfadefrac_1 =  VCA_raw[0] & 63; // fractional is 6 bits 111111

point1 = fade_table[xfadeint_1];        //two adjacent points from LUT
point1plus = fade_table[xfadeint_1+1];

if(point1 < point1plus)       // check which is larger 
	offset1 = ((point1*(64-xfadefrac_1))+((point1plus)*xfadefrac_1))>>6;
if(point1 > point1plus)
	offset1 = ((point1plus*(64-xfadefrac_1))+((point1)*xfadefrac_1))>>6; 

Does this look correct, or have i gone wrong somewhere?

Why a different linear interpolation formula depending on the two values read?

Its in case the two values are bigger than / less than. I dont need it?

You don’t need that. It’s called linear interpolation after all, it should only involve linear operations and < is not very linear!

Also, what are your data types? Some of the operations involved in computing the linear interpolation might overflow if you are not careful about the range of your data types…