У меня есть обработчик прерываний, который просто не работает достаточно быстро для того, что я хочу сделать. По сути, я использую его для генерации синусоидальных волн путем вывода значения из справочной таблицы в PORT на микроконтроллере 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
Помимо использования меньшего количества регистров в прерывании, чтобы у меня было меньше push / pops, я действительно не вижу, где этот код сборки неэффективен.
Моя единственная другая мысль - возможно, от оператора if можно было бы избавиться, если бы я смог разобраться, как получить n-битный тип данных int в C, чтобы число обернулось вокруг, когда оно достигнет конца? Под этим я подразумеваю, что у меня будет 2 ^ n - 1 выборок, а затем переменная ampitudePlace будет продолжать подсчитывать, так что, когда она достигнет 2 ^ n, она переполнится и обнулится.
Я попытался смоделировать код без бита if полностью, и хотя он действительно улучшил скорость, потребовалось всего около 10 циклов, так что это было около 55 циклов для одного выполнения, что, к сожалению, все еще не достаточно быстро, поэтому Мне нужно еще больше оптимизировать код, что сложно, без учета всего лишь двух строк !!
Моя единственная другая реальная мысль - посмотреть, смогу ли я где-нибудь хранить статические справочные таблицы, для доступа к которым требуется меньше тактов? Инструкции LDS, которые он использует для доступа к массиву, я думаю, что все занимают 2 цикла, так что я, вероятно, не буду экономить там много времени, но на этом этапе я готов попробовать что угодно.
Я совершенно не знаю, куда идти отсюда. Я не могу понять, как я мог бы сделать свой код на C более эффективным, но я только новичок в этом, поэтому я мог что-то упустить. Я хотел бы получить любую помощь ... Я понимаю, что это довольно специфическая и сложная проблема, и обычно я стараюсь не задавать подобные вопросы здесь, но я работаю над этим целую вечность, и я в полной растерянности, поэтому Я действительно приму любую помощь, которую смогу получить.