这是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
firmware-%.lss: firmware-%.elf $(OBJDUMP) -h -S $< > $@
这将生成一个已注释的整个固件映像的完整汇编语言列表.您可以使用它来验证寄存器(非)使用情况.请注意,启动代码只能运行一次,首先启用中断不会干扰您的ISR以后的排他性使用寄存器.
如果您决定不直接在汇编代码中写入ISR,我建议您在每次编译之后编写C代码并检查生成的汇编代码,以便立即观察您的更改最终生成的内容.
您可能最终在C和汇编中编写ISR的十几种变体,将每个变体的周期相加,然后选择最佳的循环.
注意事项没有进行任何注册预约,我最终会为ISR(不包括进入和离开,增加另外8或10个周期)约31个周期的东西.完全摆脱寄存器推送将使ISR降至15个周期.更改为常量大小为256字节的样本缓冲区,并且允许ISR独占使用四个寄存器,可以降低到ISR中花费的6个周期(加上8或10个进入/离开).