C 对数标度阶跃

C 对数标度阶跃,c,avr,logarithm,C,Avr,Logarithm,我正在用AVR微控制器制作一个键盘灯 有两个按钮,亮和暗,以及一个白色LED LED不是真正的线性,所以我需要使用对数刻度(在较高的值中增加亮度更快,在较低的值中使用微小的步长) 为此,我在PWM比较匹配控制寄存器中增加或减少1之间调整延迟 while (1) { if (btn_high() && OCR0A < 255) OCR0A += 1; if (btn_low() && OCR0A > 0) OCR0A -= 1;

我正在用AVR微控制器制作一个键盘灯

有两个按钮,亮和暗,以及一个白色LED

LED不是真正的线性,所以我需要使用对数刻度(在较高的值中增加亮度更快,在较低的值中使用微小的步长)

为此,我在PWM比较匹配控制寄存器中增加或减少1之间调整延迟

while (1) {
    if (btn_high() && OCR0A < 255) OCR0A += 1;
    if (btn_low() && OCR0A > 0) OCR0A -= 1;

    if (OCR0A < 25)
        _delay_ms(30);
    else if (OCR0A < 50)
        _delay_ms(25);
    else if (OCR0A < 128)
        _delay_ms(17);
    else
        _delay_ms(5);

}
while(1){
如果(btn_high()&&OCR0A<255)OCR0A+=1;
如果(btn_low()&&OCR0A>0)OCR0A-=1;
如果(OCR0A<25)
_延迟时间(30);
否则如果(OCR0A<50)
_延迟时间(25);
否则如果(OCR0A<128)
_延迟时间(17);
其他的
_延迟时间(5);
}
它工作得很好,但是当它从一个速度转到另一个速度时,有一个可见的步骤。如果延迟调整得顺利的话会好得多

我能用一些简单的公式吗? 它不能包含除法、模、sqrt、log或任何其他高等数学。我可以使用乘法、加法、子运算和位运算。此外,我不能在其中使用float

或者只是某种查找表?我真的不喜欢在if-else混乱中添加更多的分支。

仅仅使用
log()
进行缩放是不够的

我过去曾通过使用具有18个条目的LUT解决过这个问题,每次执行一整步(即,控制变量从0到17变化,然后通过LUT),但如果需要更精细的控制,则使用52个或更多肯定是可行的。但要确保它不会消耗任何SRAM


按强力工作编辑
这是我最后使用的阵列-通过线性插值从原始阵列获得

基础

#define BRIGHTNESS_LEN 60
const uint8_t BRIGHTNESS[] PROGMEM = {
    0, 1, 1, 2, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 9,
    10, 11, 13, 14, 16, 18, 21, 24, 27, 30, 32,
    35, 38, 40, 42, 45, 48, 50, 54, 58, 61, 65,
    69, 72, 76, 80, 85, 90, 95, 100, 106, 112,
    119, 125, 134, 142, 151, 160, 170, 180, 190,
    200, 214, 228, 241, 255
};
更平滑

#define BRIGHTNESS_LEN 121
const uint8_t BRIGHTNESS[] PROGMEM = {
    0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5,
    6, 6, 6, 7, 7, 8, 8, 8, 9, 10, 10, 10, 11, 12, 13, 14, 14,
    15, 16, 17, 18, 20, 21, 22, 24, 26, 27, 28, 30, 31, 32, 34,
    35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 48, 49, 50, 52, 54,
    56, 58, 59, 61, 63, 65, 67, 69, 71, 72, 74, 76, 78, 80, 82,
    85, 88, 90, 92, 95, 98, 100, 103, 106, 109, 112, 116, 119,
    122, 125, 129, 134, 138, 142, 147, 151, 156, 160, 165, 170,
    175, 180, 185, 190, 195, 200, 207, 214, 221, 228, 234, 241,
    248, 255
};

传递函数是非常线性的。建议进行线性延迟计算

delay = 32 - OCR0A/8;

接受编辑后

各种各样的公式都适用于紧密拟合的简单方程(构造时避免中间值>65535),例如


听起来你真的很想使用对数的线性函数,但是没有浮点数学库的开销。粗定点对数可以编码为

uint_8 log2fix(uint_8 in)
{
    if(in == 0)
        return 0;
    uint_8 out = 0;

    while(in > 0)
    {
        in = in >> 1;
        out++;
    }

    return out - 1;
}

这将给你一个粗略的近似值。如果您想要更高的精度,您应该能够修改一个问题。

您将问题复杂化了。通过定义可变更新率而不是可变PWM步长,您已经将对数问题转化为线性问题-因此您基本上解决了问题,但没有看到简单的算术关系

如果您选择OCR0A vs延迟点(25,30)、(50,25)、(128,17),可以看出,这是一个近似线性关系,由(近似)y=0.125x+32描述,可以重新排列为y=32-x/8

因此,您需要的是:

while (1) 
{
    if (btn_high() && OCR0A < 255) OCR0A += 1;
    if (btn_low() && OCR0A > 0) OCR0A -= 1;

    _delay_ms( 32 - OCR0A / 8 ) ;
}
while(1)
{
如果(btn_high()&&OCR0A<255)OCR0A+=1;
如果(btn_low()&&OCR0A>0)OCR0A-=1;
_延迟时间(32-OCR0A/8);
}

为什么不能使用日志?这是一个性能问题吗?如果是(OCR0A<50)?是的,这是一个很好的观点,但这仍然不是很顺利@Degustaf这是一个RISC 8位微控制器,如果我链接数学库,程序会很庞大。你有脉宽调制输出引脚吗?如果是这样,减少可能级别的数量并通过从查找表中检索日志(级别)来获得日志(级别)将提供一个平滑的过渡,并将消除您现在看到的30hz闪烁烦恼。@Sniggerfardimungus:基于正在使用OCR的事实,我认为PWM已经在使用。当然
delay=32-OCR0A/8-->
延迟=32-(OCR0A>>3)很好,但是大多数编译器会发现一个除以8的值,并且在任何情况下都会使用移位,但是如果有必要将斜率更改为非2的幂,那么这个除法可能更容易维护。因此,总的来说,我还是坚持使用显式除法吧?@Clifford
x/8
x>>3
给出了不同的结果,如果
x
是一个
int
并且值是负数,那么编译器不能总是按照您的建议进行替换。OTOH,如果
x
是小于
int
的无符号类型(这很可能),那么编译器可以替换,因为它知道该值永远不会为负。最后,如果
x
int
且编码者知道其值在正范围内(但编译器没有推断出),则编码者使用
x>>3
避免调用
x/8
将要调用的除法,OP明确指出这是要避免的。在@chux之后,值得一提的是,如果你需要除法,但不能,你可以用乘法和移位来近似除法。例1/7~36/256乘以36,然后8个移位(或者在汇编程序中删除ls字节)。这真的很好,可惜步骤太快了。当我通过调整增量的延迟来缩放它时,它总是平滑的。它与放大的LUT一起工作,它比我最初的尝试工作得更好@可能是什么“原始数组”?这是一些未发布的数据(为什么不发布?)还是源自
if(OCR00A<25)\u delay\u ms(30)_延迟_ms(5)
code?@chux从链接答案中获取数据。它是VHDL,所以我必须先转换成ofc。
while (1) 
{
    if (btn_high() && OCR0A < 255) OCR0A += 1;
    if (btn_low() && OCR0A > 0) OCR0A -= 1;

    _delay_ms( 32 - OCR0A / 8 ) ;
}