5 years, 2 months ago.

STM32L4 12 to 16 bit ADC conversion

I was looking at the AnalogIn source for the STM32L4 series at this location:

https://github.com/ARMmbed/mbed-os/blob/master/targets/TARGET_STM/TARGET_STM32L4/analogin_api.c

I saw this for the 16-bit integer result and don't see what LSB part is supposed to do:

uint16_t analogin_read_u16(analogin_t *obj)
{
    uint16_t value = adc_read(obj);
    // 12-bit to 16-bit conversion
    value = ((value << 4) & (uint16_t)0xFFF0) | ((value >> 8) & (uint16_t)0x000F);
    return value;
}

If converting from 12-bit to 16-bit, why not do either of these much simpler options:

value *= 16;

Or this:

value = value << 4;

Or even make it much more explicit with:

value = value << (16 - 12);

So my real question is why take the MSBs and put them in the LSB positions using "| ((value >> 8) & (uint16_t)0x000F)" in the above example?

3 Answers

3 years, 7 months ago.

Can not understand why MBED to do so. Convert 12 bit data to 16 bit data like this break the real data and unreadable.

3 years, 7 months ago.

Hello,

In my opinion the conversion (probabbly scaling or mapping would be a more approapriate word in this case) applied by MBED isn't perfect, because that's impossible to achieve, but still better than a simple multiplication by 16 (or binary shift to the left by 4 positions).

The task in this case is to map (or scale) (0x000, ... , 0xFFF) to (0x0000, ... , 0xFFFF) as proportionally as possible. If we apply a simple multiplication by 16 then the result is (0x0000, ... , 0xFFF0). Which isn't exactly what we wanted. Specifically, rather than mapping 0xFFF to 0xFFFF, it was mapped to 0xFFF0. So the reason of taking the MSBs and putting them in the LSB positions, using | ((value >> 8) & (uint16_t)0x000F), is to improve the mapping and to proportionally use the entire range of 16-bit numbers as illustrated below:

12-bit value->16-bit value
0x000->0x0000
0x001->0x0010
......
0x0FF->0x0FF0
0x100->0x1001
0x101->0x1011
......
0x1FF->0x1FF1
0x200->0x2002
0x201->0x2012
......
0xE00->0xE00E
0xE01->0xE01E
......
0xEFF->0xEFFE
0xF00->0xF00F
......
0xFFE->0xFFEF
0xFFF->0xFFFF

3 years, 7 months ago.

Hi, Nice explanation Zoltan. MBED call this 'normalisation' to give the 16bit range. I often use the example of converting 0 to 99 to a range of 0 to 999. Without the 'normalisation' procedure then step changes of 10 would be developed.

The question then is why bother? Well as I understand it all the platforms, microcontrollers have an ARM core but different peripheral structures around them. So there is variation in ADC resolutions 8,10,12,14 etc., MBED takes this into account and have produced a generalised member function read_u16(). If you know the resolution of the platform you are using then you can bit shift as in the example below for a 12 bit ADC

#include "mbed.h"

AnalogIn  myADC(A0);

int main(){
 while(true){
   unsigned int result=myADC.read_u16()>>4
   printf|("12 bit value is %u \n",result);  
 }
}