优化此C(AVR)代码
|
我有一个中断处理程序,它运行得不够快,无法完成我想做的事情。基本上,我正在使用它通过将查找表中的值输出到AVR微控制器上的PORT来生成正弦波,但不幸的是,这发生得不够快,无法获得我所需要的波形频率想。有人告诉我,我应该考虑在程序集中实现它,因为编译器生成的程序集可能效率不高并且可以进行优化,但是在查看了程序集代码之后,我真的看不到我能做得更好。
这是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中获取n位int数据类型,以便该数字在到达末尾时可以环绕起来,则可以消除if语句?我的意思是说我将有2 ^ n-1个样本,然后使振幅位置变量保持递增计数,以便当它达到2 ^ n时将溢出并重置为零。
我确实尝试了完全没有if的情况下模拟代码,虽然它确实提高了速度,但只花了大约10个周期,因此一次执行大约需要55个周期,但不幸的是,它的速度还不够快,所以我做到了需要进一步优化代码,如果没有这只有两行的话,这是很难考虑的!!
我唯一真正想到的是,是否可以将静态查找表存储在需要较少时钟周期访问的地方?我认为它用来访问阵列的LDS指令全都需要2个周期,所以我可能真的不会在那里节省很多时间,但是在这个阶段我愿意尝试任何事情。
我完全不知该从何处去。我看不到如何提高C代码的效率,但是我对这种事情还很陌生,所以我可能会丢失一些东西。我很乐意提供任何帮助。.我意识到这是一个非常特殊且涉及到的问题,通常我会尽量避免在这里提出此类问题,但是我已经为此工作了很长时间了,总的来说损失,所以我将竭尽所能。
没有找到相关结果
已邀请:
6 个回复
懊毁暗
设置为除anything6ѭ以外的任何值,您可能还摆脱了
和
的推送。 2.使用本地临时变量作为数组索引的新值,以节省不必要的负载并将指令存储到该volatile变量。像这样:
3.从59倒数到0,而不是从0倒数到59,以避免单独的比较指令(无论如何,减法都会与0进行比较)。伪代码:
代替
4.也许使用指针和指针比较(具有预先计算的值!)而不是数组索引。需要检查它,而不是倒数哪个更有效。也许将数组对齐到256字节边界,并且仅使用8位寄存器作为指针,以节省加载和保存地址的高8位。 (如果您用完了SRAM,您仍然可以将这60字节阵列中的4个内容装入一个256字节阵列中,并且仍然可以利用由8个恒定高位和8个可变低位组成的所有地址的优势。)
问题是指针是16位的,而以前的简单数组索引的大小是8位。如果设计阵列地址以使地址的高8位为常量(在汇编代码中为
),并且仅处理ISR中实际变化的低8位,则可能会帮助解决这一问题。但这确实意味着编写汇编代码。从上面生成的汇编代码可能是在汇编中编写该版本的ISR的良好起点。 5.如果从时序角度来看可行,则将样本缓冲区的大小调整为2的幂,以简单的ѭ12代替if-reset-zero到零部分。如果您想使用4.中建议的8位/ 8位地址拆分,甚至以256的幂乘以2(并根据需要复制示例数据以填充256字节缓冲区)甚至可以节省您添加后的AND指令。 6.如果ISR仅使用不影响状态寄存器的指令,则停止推入并弹出
。 一般 以下内容可能会派上用场,特别是对于手动检查所有其他汇编代码的假设而言:
这将生成整个固件映像的带注释的完整汇编语言列表。您可以使用它来验证注册(非)使用情况。请注意,启动代码仅在您第一次启用中断之前运行了很长时间,不会干扰您的ISR以后对寄存器的排他性使用。 如果您决定不直接在汇编代码中编写该ISR,我建议您在每次编译后编写C代码并检查生成的汇编代码,以便立即观察所做更改最终生成的结果。 您可能最终会在C和汇编语言中编写一打左右的ISR变体,为每个变体加总循环,然后选择最佳变体。 注意如果不进行任何寄存器保留,则ISR大约需要31个周期(不包括进入和离开,这会再增加8或10个周期)。完全摆脱寄存器压入将使ISR下降到15个周期。更改为恒定大小为256字节的样本缓冲区,并为ISR排他使用四个寄存器,可以减少ISR中花费的6个周期(输入/离开的周期为8或10)。
阀傻
;对其进行重构,以使其不会在三个不同的时间从内存中加载
,等等。
艾食魄轻县
抢垢洛韧
这将要求您的表长度为64个条目。如果可以选择表的地址,则可以用一个指针离开,增加它的指针,然后用0xffBf递增它。 如果使用变量而不是固定常量会使速度变慢,则可以将指针变量替换为特定的数组:
然后,您更改中断指针以对每个波形使用不同的功能。这不太可能节省很多,但我们的总周期数已降至10个周期。 寄存器使用事。一旦获得了这样一个非常简单的ISR,就可以检查ISR的序言和结语,以推入并弹出处理器状态。如果ISR仅使用1个寄存器,则可以在汇编器中进行操作,并且仅保存和恢复该1个寄存器。这将减少中断开销,而不会影响程序的其余部分。一些编译器可能会为您执行此操作,但我对此表示怀疑。 如果有时间和空间,您还可以创建一个长表,并用+ = freq替换++,其中freq将通过跳过而使波形为基本频率的整数倍(2x,3x,4x等)。这么多的样本。
翰冒绢县
稍惮
如果您的CPU以合理的速度执行模运算,那么它应该快得多。如果仍然不够用,请尝试在汇编器中编写此版本。