Когда на ATmega328 работает прескейлер часов 64, один из моих таймеров ускоряется по неизвестным причинам в определенный момент выполнения.
Я использую два таймера на ATmega328 для генерации тактирования, необходимого для TLC5940 (см. Ниже, почему; это несущественно для вопроса). TIMER0
генерирует тактовый сигнал, используя Fast PWM, OC0B
и настраивается следующим образом:
TCCR0A = 0
|(0<<COM0A1) // Bits 7:6 – COM0A1:0: Compare Match Output A Mode
|(0<<COM0A0) //
|(1<<COM0B1) // Bits 5:4 – COM0B1:0: Compare Match Output B Mode
|(0<<COM0B0)
|(1<<WGM01) // Bits 1:0 – WGM01:0: Waveform Generation Mode
|(1<<WGM00)
;
TCCR0B = 0
|(0<<FOC0A) // Force Output Compare A
|(0<<FOC0B) // Force Output Compare B
|(1<<WGM02) // Bit 3 – WGM02: Waveform Generation Mode
|(0<<CS02) // Bits 2:0 – CS02:0: Clock Select
|(1<<CS01)
|(0<<CS00) // 010 = clock/8
;
OCR0A = 8;
OCR0B = 4;
TIMSK0 = 0;
TIMER2
поворачивает строку данных для генерации импульса гашения каждые 256 TIMER0
циклов и настраивается следующим образом:
ASSR = 0;
TCCR2A = 0
|(0<<COM2A1) // Bits 7:6 – COM0A1:0: Compare Match Output A Mode
|(0<<COM2A0) //
|(0<<COM2B1) // Bits 5:4 – COM0B1:0: Compare Match Output B Mode
|(0<<COM2B0)
|(0<<WGM21) // Bits 1:0 – WGM01:0: Waveform Generation Mode
|(0<<WGM20)
;
TCCR2B = 0
|(0<<FOC2A) // Force Output Compare A
|(0<<FOC2B) // Force Output Compare B
|(0<<WGM22) // Bit 3 – WGM02: Waveform Generation Mode
|(1<<CS22) // Bits 2:0 – CS02:0: Clock Select
|(0<<CS21)
|(0<<CS20) // 100 = 64
;
OCR2A = 255;
OCR2B = 255;
TIMSK2 = 0
|(1<<TOIE2); // Timer/Counter0 Overflow Interrupt Enable
TIMER2
вызывает ISR при переполнении (каждые 256 циклов). ISR вручную генерирует импульс гашения и, если необходимо, фиксирующий импульс:
volatile uint8_t fLatch;
ISR(TIMER2_OVF_vect) {
if (fLatch) {
fLatch = 0;
TLC5940_XLAT_PORT |= (1<<TLC5940_XLAT_BIT); // XLAT -> high
for (int i=0;i<10;i++)
nop();
TLC5940_XLAT_PORT &= ~(1<<TLC5940_XLAT_BIT); // XLAT -> high
}
// Blank
TLC5940_BLANK_PORT |= (1<<TLC5940_BLANK_BIT);
for (int i=0;i<10;i++)
nop();
TLC5940_BLANK_PORT &= ~(1<<TLC5940_BLANK_BIT);
}
nop()
Задержка в приведенном выше коде просто сделать импульс более очевидным на логический анализатор следа. Вот как main()
выглядит цикл в функции: отправьте некоторые последовательные данные, подождите, пока ISR позаботится о блокировке, а затем повторите это снова:
for (;;) {
if (!fLatch) {
sendSerial();
fLatch = 1;
_delay_ms(1);
}
nop();
}
sendSerial()
делает некоторые SPI-посылки ( код на pastebin для краткости ). Моя проблема в том, что после sendSerial()
завершения во время ожидания fLatch
установки низкого уровня (обработано) таймер тактирования ускоряется. Вот трассировка логического анализатора (я выделил области, где один и тот же сигнал продолжает уменьшать изображение):
На левой стороне каналы 0 и 1 показывают хвостовую часть отправляемых данных SPI. Также слева, на канале 4, вы можете видеть гасящий импульс. На канале 2 тактовый импульс подается, как и ожидалось. Прямо там, где есть зазор на изображении, fLatch
устанавливается 1
внутри main()
рутины. И вскоре после этого TIMER0
ускоряется примерно в 4 раза. В конце концов, выполняются импульс гашения и импульс фиксации (каналы 3 и 4, правая треть изображения), и теперь тактовый импульс возобновляет свою регулярную частоту, и последовательные данные отправил еще раз. Я пытался вытащить delay_ms(1);
линию main()
, но результаты те же. В чем дело? Я должен отметить, что ATmega синхронизируется с кристаллом 20 МГц, а затем замедляется в 64 раза, используя следующий код:
CLKPR = 1<<CLKPCE;
CLKPR = (0<<CLKPS3)|(1<<CLKPS2)|(1<<CLKPS1)|(0<<CLKPS0);
Для чего это: Я экспериментирую с управлением драйвером светодиода TLC5940 : для этих чипов требуются внешние часы плюс сброс в конце тактового цикла.
sendSerial()
мой код, который отправляет данные через SPI: он не касаетсяTCCR
регистров (управление таймером).Ответы:
Для быстрой отладки я попытался бы сделать то же самое, используя Arduino Library для TLC5940, и посмотреть, быстро ли это или нет. Если она работает с библиотекой, вы можете проверить ее источник и сравнить с вашим. Поскольку вы знакомы с AVR, вы должны легко преобразовать исходный код Arduino в собственный AVR.
На тот случай, если вы не знаете, как загрузить скомпилированные эскизы Arduino в AVR: при компиляции эскиза создается шестнадцатеричный файл (точное местоположение файла можно увидеть, включив в настройках подробный режим). Вы можете загрузить этот гекс в свой AVR с вашим любимым программистом.
Надеюсь, поможет
источник