2

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

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

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

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: 上升 >> 3,偏移 >> 3 这里rise_shift = 2,offset_shift = 4 上升 >> 2,偏移 >> 4 这里rise_shift = 4,offset_shift = 2 上升 >> 4,偏移 >> 2 我有兴趣看看是否有人提出或知道更好的方法。目前,我只需要每秒运行大约 20-30 次这段代码,所以我显然没有遇到任何延迟。但是,使用 16MHz 时钟并使用此处的信息,我估计整个操作最多需要 ~110 个时钟周期,或 ~7us。这是 ADC 读取时间的比例,约为 4us。

谢谢

编辑:“更好”我并不一定意味着更快,(显然已经相当快了)。立即,人们看到低端具有相当大的离散化到 2 的整数次方,这是为了防止滚动的移位操作造成的。除了查找表(建议如下)之外,如何改进这一点的答案并不是立竿见影的。

4

2 回答 2

4

基于 10 位 ADC 读取。

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

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

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

result =  LogarithmicTable[ADC_READ];

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

于 2013-08-11T22:01:45.993 回答
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 KB 表没有任何精度损失。为了节省更多空间,请使用线性插值。

于 2013-08-12T04:54:26.783 回答