我有一个中断处理程序,它运行得不够快,无法完成我想做的事情。基本上,我使用它通过将查找表中的值输出到 AVR 微控制器上的端口来生成正弦波,但不幸的是,这发生得不够快,无法获得我想要的波形频率。有人告诉我,我应该考虑在程序集中实现它,因为编译器生成的程序集可能效率低下并且可能能够进行优化,但是在查看了程序集代码之后,我真的看不出我可以做得更好。
这是C代码:
const uint8_t amplitudes60[60] = {127, 140, 153, 166, 176, 191, 202, 212, 221, 230, 237, 243, 248, 251, 253, 254, 253, 251, 248, 243, 237, 230, 221, 212, 202, 191, 179, 166, 153, 140, 127, 114, 101, 88, 75, 63, 52, 42, 33, 24, 17, 11, 6, 3, 1, 0, 1, 3, 6, 11, 17, 24, 33, 42, 52, 63, 75, 88, 101, 114};
const uint8_t amplitudes13[13] = {127, 176, 221, 248, 202, 153, 101, 52, 17, 1, 6, 33, 75};
const uint8_t amplitudes10[10] = {127, 176, 248, 202, 101, 52, 17, 1, 33, 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
除了可能在中断中使用更少的寄存器以便我有更少的推送/弹出之外,我真的看不出这个汇编代码效率低下的地方。
我唯一的另一个想法是,如果我能弄清楚如何在 C 中获得一个位 int 数据类型,以便数字在到达末尾时会环绕,那么 if 语句可能会被删除?我的意思是我会有 2^n - 1 个样本,然后让amplitudePlace 变量继续计数,这样当它达到 2^n 时它会溢出并重置为零。
我确实尝试在没有 if 位的情况下完全模拟代码,虽然它确实提高了速度,但它只需要大约 10 个周期,因此一次执行大约需要 55 个周期,不幸的是仍然不够快,所以我确实需要进一步优化代码,如果没有它只有 2 行,这很难考虑!
我唯一真正的想法是看看我是否可以将静态查找表存储在需要更少时钟周期来访问的地方?它用来访问数组的 LDS 指令我认为都需要 2 个周期,所以我可能不会真正节省太多时间,但在这个阶段我愿意尝试任何事情。
我完全不知道从这里去哪里。我看不出如何让我的 C 代码更有效率,但我对这类事情还很陌生,所以我可能会遗漏一些东西。我很想得到任何帮助。我意识到这是一个非常特殊且涉及到的问题,通常我会尽量避免在这里问这些问题,但我已经为此工作了很长时间,而且完全不知所措,所以我真的会接受任何我能得到的帮助。