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