Під час запуску годинника prescaler 64 на ATmega328, один з моїх таймерів прискорюється з невідомих причин у певний час виконання.
Я використовую два таймери на ATmega328, щоб генерувати тактову частоту, необхідну TLC5940 (див. Нижче про те, чому це не має значення). TIMER0
генерує тактовий сигнал, використовуючи швидку ШІМ увімкнену, 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 ( код на пастібін заради стислості ). Моє питання полягає в тому, що після 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
реєстрів (управління таймером).