我有一个中断处理程序,但运行速度不够快,无法完成我想做的事情。基本上,我使用它通过将查找表中的值输出到 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;
}
}
amplitudes 和 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 中获取 n 位 int 数据类型,以便数字在到达末尾时会环绕,那么 if 语句可能会被删除?我的意思是,我将有 2^n - 1 个样本,然后让amplitudePlace 变量继续计数,这样当它达到 2^n 时,它就会溢出并重置为零。
我确实尝试完全不使用 if 位来模拟代码,虽然它确实提高了速度,但只需要大约 10 个周期,因此一次执行大约需要 55 个周期,不幸的是,这仍然不够快,所以我确实需要进一步优化代码,如果没有它只有 2 行,这是很难考虑的!
我唯一真正的想法是看看我是否可以将静态查找表存储在需要更少时钟周期访问的地方?我认为它用来访问数组的 LDS 指令都需要 2 个周期,所以我可能不会真正节省太多时间,但在这个阶段我愿意尝试任何事情。
我完全不知道从这里该去哪里。我不知道如何使我的 C 代码更加高效,但我对这类事情还很陌生,所以我可能会错过一些东西。我希望得到任何形式的帮助..我意识到这是一个非常特殊且复杂的问题,通常我会尽量避免在这里问这类问题,但我已经研究这个问题很多年了,我完全不知所措,所以我真的会接受任何我能得到的帮助。