优化此C(AVR)代码

前端之家收集整理的这篇文章主要介绍了优化此C(AVR)代码前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有一个中断处理程序,只是没有足够快的运行我想做的事情.基本上,我正在使用它来产生正弦波,通过从AVR微控制器的一个查询表中输出一个值,但不幸的是,这并不足以让我得到我想要的波的频率.我被告知,我应该看看在汇编中实现它,因为编译器生成的程序集可能会稍微低效,可能可以被优化,但是在查看汇编代码后,我真的看不到我能做得更好.

这是C代码

const uint8_t amplitudes60[60] = {127,140,153,166,176,191,202,212,221,230,237,243,248,251,253,254,179,127,114,101,88,75,63,52,42,33,24,17,11,6,3,1,114};
const uint8_t amplitudes13[13] = {127,75};
const uint8_t amplitudes10[10] = {127,75};

volatile uint8_t numOfAmps = 60;
volatile uint8_t *amplitudes = amplitudes60;
volatile uint8_t amplitudePlace = 0; 

ISR(TIMER1_COMPA_vect) 
{
    PORTD = amplitudes[amplitudePlace];

    amplitudePlace++; 

    if(amplitudePlace == numOfAmps)
    {
        amplitudePlace = 0;
    }

}

振幅和numOfAmps都被另一个中断程序改变,运行速度比这个慢一点(它基本上是改变正在播放的频率).在一天结束时,我不会使用那些精确的数组,但它将是一个非常相似的设置.我最有可能有一个60个值的数组,另外一个只有30个.这是因为我正在构建一个扫频器,在较低的频率下,我可以承担更多的样本,因为我有更多的时钟周期来玩,在更高的频率下,我非常紧张的时间.

我确实意识到我可以让它以更低的采样率工作,但是我不想每个时期都不到30个样本.我不认为有一个指向数组的指针使得它从集合中获取一个数组的值更慢,而组件从一个指向数组的指针中获取一个值似乎是一样的(这是有道理的).

在我要生产的最高频率下,我被告知我应该能够在正弦波周期内使用大约30个样本.目前,30个样本的运行速度最快,大约是所需最大频率的一半,这意味着我的中断需要运行两倍.

所以当模拟的代码需要65个周期才能完成.再次,我被告知我应该可以把它降低到大约30个周期.

这是产生的ASM代码,我想到了每一行在旁边做什么:

ISR(TIMER1_COMPA_vect) 
{
push    r1
push    r0
in      r0,0x3f        ; save status reg
push    r0
eor     r1,r1      ; generates a 0 in r1,used much later
push    r24
push    r25
push    r30
push    r31         ; all regs saved


PORTD = amplitudes[amplitudePlace];
lds     r24,0x00C8     ; r24 <- amplitudePlace I’m pretty sure
lds     r30,0x00B4 ; these two lines load in the address of the 
lds     r31,0x00B5 ; array which would explain why it’d a 16 bit number
                    ; if the atmega8 uses 16 bit addresses


add     r30,r24            ; aha,this must be getting the ADDRESS OF THE element 
adc     r31,r1             ; at amplitudePlace in the array.  

ld      r24,Z              ; Z low is r30,makes sense. I think this is loading
                            ; the memory located at the address in r30/r31 and
                            ; putting it into r24

out     0x12,r24           ; fairly sure this is putting the amplitude into PORTD

amplitudePlace++; 
lds     r24,0x011C     ; r24 <- amplitudePlace
subi    r24,0xFF       ; subi is subtract imediate.. 0xFF = 255 so I’m
                        ; thinking with an 8 bit value x,x+1 = x - 255;
                        ; I might just trust that the compiler knows what it’s 
                        ; doing here rather than try to change it to an ADDI 

sts     0x011C,r24     ; puts the new value back to the address of the
                        ; variable

if(amplitudePlace == numOfAmps)
lds     r25,0x00C8 ; r24 <- amplitudePlace
lds     r24,0x00B3 ; r25 <- numOfAmps 

cp      r24,r24        ; compares them 
brne    .+4             ; 0xdc <__vector_6+0x54>
        {
                amplitudePlace = 0;
                    sts     0x011C,r1 ; oh,this is why r1 was set to 0 earlier
        }


}

pop     r31             ; restores the registers
pop     r30
pop     r25
pop     r24
pop     r19
pop     r18
pop     r0
out     0x3f,r0        ; 63
pop     r0
pop     r1
reti

除了可能在中断中使用较少的寄存器,以便我有更少的推/弹,我真的看不到这个汇编代码是低效率的.

我唯一的其他想法可能是if语句可以摆脱,如果我可以计算出如何在C中获得一个n位int数据类型,以便数字会绕到达到最后?我的意思是我会有2 ^ n – 1个样本,然后将amplitudePlace变量保持递增计数,以便当它达到2 ^ n时,它会溢出并将被重置为零.

我没有尝试模拟代码,而没有完全的位,而它确实提高了速度,它只需要大约10个周期,所以它是一个执行的时间约55个循环,仍然不够快,不幸的是,我需要要进一步优化代码,这是很难考虑,没有它只有2行!

我唯一的其他真正的想法是看看我是否可以将静态查找表存储在需要更少时钟周期的地方? LDS指令用于访问阵列,我认为这需要2个周期,所以我可能不会真的节省很多时间,但在这个阶段我愿意尝试任何事情.

我完全失去了从这里去哪里.我看不出我如何使我的C代码更有效率,但我只是相当新的这种事情,所以我可能会缺少一些东西.我会喜欢任何一种帮助..我意识到这是一个非常特别和涉及到的问题,通常我会尽量避免在这里提出这样的问题,但我一直在努力这个年龄,我完全失去了我真的会采取任何可以得到的帮助.

解决方法

我可以看几个地方开始工作,没有特定的顺序列出:

1.减少要推送的寄存器数量,因为每个推/弹对都需要四个周期.例如,avr-gcc允许您从其寄存器分配器中删除几个寄存器,因此您可以将它们用于该单个ISR中的寄存器变量,并确保它们仍然包含上次的值.你也可以摆脱r1和eor r1,r1的推动,如果你的程序从不将r1设置为0以外的任何东西.

2.使用本地临时变量来获取数组索引的新值,以将不必要的加载和存储指令存储到该volatile变量中.这样的事情

volatile uint8_t amplitudePlace;

ISR() {
    uint8_t place = amplitudePlace;
    [ do all your stuff with place to avoid memory access to amplitudePlace ]
    amplitudePlace = place;
}

3.从59到0而不是从0到59反转,以避免单独的比较指令(减法时与0进行比较).伪码:

sub  rXX,1
     goto Foo if non-zero
     movi rXX,59
Foo:

代替

add  rXX,1
     compare rXX with 60
     goto Foo if >=
     movi rXX,0
Foo:

4.可能使用指针和指针比较(带预先计算的值!)而不是数组索引.它需要检查,而不是向后计数,哪一个更有效率.也许将数组对齐到256字节的边界,并且仅使用8位寄存器来指针加载并保存地址的高8位. (如果用完了SRAM,则仍然可以将这60字节数组中的4个内容合并为一个256字节的数组,并且仍然可以获得由8个常数高位和8个可变低位组成的所有地址的优点.

uint8_t array[60];
uint8_t *idx = array; /* shortcut for &array[0] */
const uint8_t *max_idx = &array[59];

ISR() {
    PORTFOO = *idx;
    ++idx;
    if (idx > max_idx) {
        idx = array;
    }
}

问题是指针是16位,而你的简单数组索引以前是8位大小.如果设计阵列地址使得地址的高8位是常数(在汇编代码hi8(array))中,并且只处理在ISR中实际改变的低8位,那么可能是一个窍门.这的确意味着编写汇编代码.从上述生成的汇编代码可能是在汇编中编写ISR版本的良好起点.

5.如果从定时的角度考虑可行,则将样本缓冲区大小调整为2的幂,以简单的i =(i 1)& ((1 功能(并根据需要复制采样数据来填充256字节缓冲区)为256,甚至可以节省您and指令后add.="" 6.如果isr仅使用不影响状态寄存器的指令,请停止推送并弹出sreg.="" 一般="" 以下可能会派上用场,特别是手动检查所有其他汇编代码的假设:="" <="">

firmware-%.lss: firmware-%.elf
        $(OBJDUMP) -h -S $< > $@

这将生成一个已注释的整个固件映像的完整汇编语言列表.您可以使用它来验证寄存器(非)使用情况.请注意,启动代码只能运行一次,首先启用中断不会干扰您的ISR以后的排他性使用寄存器.

如果您决定不直接在汇编代码中写入ISR,我建议您在每次编译之后编写C代码并检查生成的汇编代码,以便立即观察您的更改最终生成内容.

您可能最终在C和汇编中编写ISR的十几种变体,将每个变体的周期相加,然后选择最佳的循环.

注意事项没有进行任何注册预约,我最终会为ISR(不包括进入和离开,增加另外8或10个周期)约31个周期的东西.完全摆脱寄存器推送将使ISR降至15个周期.更改为常量大小为256字节的样本缓冲区,并且允许ISR独占使用四个寄存器,可以降低到ISR中花费的6个周期(加上8或10个进入/离开).

猜你在找的C&C++相关文章