在有限运算的微控制器上生成对数间隔值

时间:2013-08-11 18:41:11

标签: c embedded microcontroller logarithm

我最近遇到过一个问题,即使用便宜的16位uC(MSP430系列),我必须根据10位ADC读数生成对数间隔输出值。这样做的原因是我需要在整数空间的低端进行细粒度控制,同时需要使用较大的值,尽管精度较低,(对我来说,2 ^ 15之间的差异)并且在我的反馈循环中2 ^ 16没什么影响)。我以前从来没有这样做过,我没有运气在线找到例子,所以我想出了一个小计划,在我的操作限制uC上做到这一点。

使用我的方法,ADC结果在两个最接近的整数2次幂之间进行线性插值,仅通过整数乘法/加法/求和和按位移位(如下所述)。

我的问题是,是否有更好的(更快/更少的操作)方式来生成一个平滑的(或平滑的)数据集,这些数据在整数分辨率上以对数方式间隔?我没有在网上找到任何东西,因此我首先试图从头开始提出一些东西。

N是微控制器的对数分辨率(这里假设为16位)。 M是ADC的整数分辨率(此处假设为10位)。 ADC_READ是ADC在给定时间读取的值。在支持浮点运算的uC上,这样做很简单:

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

在下面的所有图中,这是“理想”值集。 “结果”值由以下变体生成:

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;
}

额外的声明和不仅仅是为了可读性。假设最终产品被浓缩。基本上,它是按照预期的,基于rise_shift和offset_shift的值,在低端具有不同程度的离散化,在高端具有不同的平滑度。在这里,它们都等于3: rise >> 3, offset >> 3 这里rise_shift = 2,offset_shift = 4 rise >> 2, offset >> 4 这里rise_shift = 4,offset_shift = 2 rise >> 4, offset >> 2 我很想知道是否有人提出或知道更好的事情。目前,我只需运行此代码〜每秒20-30次,所以我显然没有遇到任何延迟。但是,使用16MHz时钟并使用来自here的信息,我估计整个操作最多需要~110个时钟周期,或~7us。这是ADC读取时间的缩放比例,约为4us。

由于

编辑:通过“更好”,我不一定只是意味着更快,(它已经很快,显然)。立即,人们看到低端对两个整数幂的离散度相当大,这是由于移动操作以防止滚动。除了查询表(如下所示)之外,如何改进这一点的答案并不是立竿见影的。

2 个答案:

答案 0 :(得分:4)

  

基于10位ADC读取。

此ADC只能输出1024个不同的值(0-1023),因此您可以使用1024个16位值的表,这将消耗2KB闪存:

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

计算对数输出现在是一个简单的数组访问:

result =  LogarithmicTable[ADC_READ];

您可以使用Excel之类的工具为您生成此表中的常量。

答案 1 :(得分:1)

听起来你想要计算函数2 n / 64 ,它会在高端上方映射1024到65536但是将任何高达64的映射到零(或者一个,取决于舍入)。其他指数函数可以避免低端离散化,但目前尚不清楚这是否有助于功能。

我们可以将2 n / 64 分解为2 floor(n / 64)×2 (n mod 64)/ 64 。通常乘以2的整数次幂涉及左移,但由于另一侧是1到2之间的分数,我们最好做右移。

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 );
}

这对完整的2千字节表没有精确度。要节省更多空间,请使用线性插值。