سؤال

I've recently come across a problem where, using a cheap 16 bit uC (MSP430 series), I've had to generate a logarithmically spaced output value based on the 10 bit ADC read. The reason for this is that I require fine grain control at the low end of the integer space, while, at the same time, requiring the use of the larger values, though at less precision, (to me, the difference between 2^15 and 2^16 in my feedback loop is of little consequence). I've never done this before and I had no luck finding examples online, so I came up with a little scheme to do this on my operation-limited uC.

With my method here, the ADC result is linearly interpolated between the two closest integer powers-of-two via only integer multiplication/addition/summation and bitwise shifting, (outlined below).

My question is, is there a better, (faster/less operations), way than this to generate a smooth, (or smooth-ish), set of data logarithmically spaced over the integer resolution? I haven't found anything online, hence my attempt at coming up with something from scratch in the first place.

N is the logarithmic resolution of the micro controller, (here assumed to be 16 bit). M is the integer resolution of the ADC, (here assumed to be 10 bit). ADC_READ is the value read by the ADC at a given time. On a uC that supports floating point operations, doing this is trivial:

x = N / M  #16/1024
y = (float) ADC_READ / M   #ADC_READ/1024
result = 2 ^ ( x * y )  

In all of the plots below, this is the "Ideal" set of values. The "Resultant" values are generated by variations of the following:

unsigned int returnValue( adcRead ){

    unsigned int e;
    unsigned int a;
    unsigned int rise;
    unsigned int base;
    unsigned int xoffset;
    unsigned int yoffset;

    e = adcRead >> 6;
    a = 1 << e;

    rise = ( 1 << (e + 1) )  - ( 1 << e );
    base = e << 6;

    xoffset = adcRead - base;
    yoffset = ( rise >>  rise_shift ) * (xoffset >> offset_shift);  //this is an operation to prevent rolling over.   rise_shift + offset_shift = M/N, here = 6

    result = a + yoffset;
    return result;
}

The extra declarations and what not are for readability only. Assume the final product is condensed. Basically, it does as intended, with varying degrees of discretization at the low end and smoothness at the high end based on the values of rise_shift and offset_shift. Here, they are both equal to 3: rise  loading=> 3, offset >> 3"> Here rise_shift = 2, offset_shift = 4 rise  loading=> 2, offset >> 4"> Here rise_shift = 4, offset_shift = 2 rise  loading=> 4, offset >> 2"> I'm interested to see if anyone has come up with or knows of anything better. Currently, I only have to run this code ~20-30 times a second, so I obviously have not encountered any delays. But, with a 16MHz clock, and using information from here, I estimate this entire operation taking at most ~110 clock cycles, or ~7us. This is on the scale the ADC read time, which is ~4us.

Thanks

EDIT: By "better" I do not necessarily just mean faster, (it's already quite fast, apparently). Immediately, one sees that the low end has fairly drastic discretization to the integer powers of two, which results from the shifting operations to prevent roll-ever. Other than a look-up table, (suggested below), the answer to how this could be improved is not immediate.

هل كانت مفيدة؟

المحلول

based on the 10 bit ADC read.

This ADC can output only 1024 different values (0-1023), so you can use a table of 1024 16-Bit values, which would consume 2KB Flash memory:

const uint16_t LogarithmicTable[1024] = { 0, 1, ... , 64380};

Calculating the logarithmic output is now a simple array access:

result =  LogarithmicTable[ADC_READ];

You can use a tool like Excel to generate the constants in this Table for you.

نصائح أخرى

It sounds like you want to compute the function 2n/64, which would map 1024 to 65536 just above the high end but maps anything up to 64 to zero (or one, depending on rounding). Other exponential functions could avoid the low-end discretization, but it's not clear whether that would help the functionality.

We can factor 2n/64 into 2floor( n/64 ) × 2(n mod 64)/64. Usually multiplying by an integer power of 2 involves a left shift, but because the other side is a fraction between one and two, we're better off doing a right shift.

uint16_t exp_table[ 64 ] = {
        32768u,
        pow( 2, 1./64 ) * 32768u,
        pow( 2, 2./64 ) * 32768u,
        ...
};

uint16_t adc_exp( uint16_t linear ) {
    return exp_table[ linear % 64 ] >> ( 15 - linear / 64 );
}

This loses no precision against a full, 2-kilobyte table. To save more space, use linear interpolation.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top