Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Снова о TMR1 в PIC
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > Все остальные микроконтроллеры > PIC
stas00n
Коллеги, прошу пардону если этот вопрос уже изжеван, но не сочтите за труд - объясните для "особо одаренных". Заметил в своем девайсе на PIC16LF628A незначительное (но больше расчетного) отставание часов - решил разобраться. Часы реализованы "классически" - прерывания по переполнению TMR1 в режиме асинхронного счета от внешнего кварца 32768 Гц:
Код
volatile signed long int actualTime;

void interrupt isr (void){
//...
if    (TMR1IF && TMR1IE){                        //    TMR1 Handler
TMR1H |= 0x80;                                //Загрузка таймера для получения периода 1с.
actualTime++;
}
//...
}

Выкурил микрочиповскую еррату по этому вопросу, и выяснил, что если загружать регистры таймера в неподходящий момент, то таймер может пропустить один такт. Это понятно. Поскольку у меня в основном коде периодически запрещаются прерывания на относительно долгие промежутки времени, ясно что такие неподходящие моменты имеют место быть. Решил сделать как написано в еррате - прежде чем загружать регистры таймера, дождаться его очередного приращения. Это тоже понятно. Но хоть убей не пойму, ЗАЧЕМ дрыгать клок с внешнего на системный и обратно, зачем выключать/включать таймер:

Код
BTFSC    TMR1L,0
GOTO     $-1
BTFSS    TMR1L,0
GOTO     $-1             ;Timer has just incremented, 31 μs before next rising edge to
                         ;complete reload
Update:
BCF     T1CON,TMR1CS     ;Select system clock for Timer1
BSF     TMR1H,7          ;Timer1 high byte 0x80
BCF     T1CON,TMR1ON     ;Timer1 off
BSF     T1CON,TMR1C      ;Select external crystal
BSF     T1CON,TMR1ON     ;Timer1 on


Разве недостаточно просто делать запись в TMR1 сразу же после инкремента последнего:
Код
void interrupt isr (void){
//...
if    (TMR1IF && TMR1IE){                        //    TMR1 Handler
while (TMR1L == TMR1L){                              // Ожидание инкремента TMR1
;
}
TMR1H |= 0x80;                                //Загрузка таймера для получения периода 1с.

actualTime++;
}
//...
}

В дизассемблере это выглядит так:
Код
93:                while (TMR1L == TMR1L){
   223    1283     BCF STATUS, 0x5
   224    080E     MOVF TMR1L, W
   225    060E     XORWF TMR1L, W
   226    1903     BTFSC STATUS, 0x2
   227    2A23     GOTO 0x223
94:               ;
95:                }
96:                TMR1H |= 0x80;    
   228    178F     BSF TMR1H, 0x7


Или я морочу голову себе и людям и надо сделать точно так как в примере из ерраты?
xemul
Цитата(stas00n @ Jun 8 2011, 15:19) *
Заметил в своем девайсе на PIC16LF628A незначительное (но больше расчетного) отставание часов - решил разобраться. Часы реализованы "классически" - прерывания по переполнению TMR1 в режиме асинхронного счета от внешнего кварца 32768 Гц:
Код
volatile signed long int actualTime;

signed ?
Цитата
Поскольку у меня в основном коде периодически запрещаются прерывания на относительно долгие промежутки времени ...

Беда.
Цитата
Но хоть убей не пойму, ЗАЧЕМ дрыгать клок с внешнего на системный и обратно, зачем выключать/включать таймер:

Суть проблемы: внешний кварц асинхронен системному клоку. Если инкремент TMR1 от внешнего кварца придётся на незавершённую запись в TMR1 (н-р, от "TMR1H |= 0x80;"), то он будет пропущен. А если наоборот, то не сможет корректно выполниться "TMR1H |= 0x80;".
При тактировании таймера от системного клока такого гарантированно не случится.
Я бы только перед "TMR1CS = 0;" делал "T1CKPS1 = 1;", чтобы избежать инкремента TMR1 от системного клока, и потом, соответственно, наоборот.
И, возможно, "while (TMR1L == TMR1L) continue;" оформил бы на ассемблере, съэкономив 1 такт.sm.gif

Если Вы не укладываете контроллер в спячку, то проще сделать T1SYNC = 0. Тогда, скорее всего, будет достаточно Вашего исходного кода.
stas00n
Цитата(xemul @ Jun 8 2011, 17:21) *
signed ?

Да. Уж не помню из каких соображений. Девайс работает с компом, считает unix time, а оно знаковое, потому сделал так же. Но до 2038 года это не принципиально sm.gif
Цитата(xemul @ Jun 8 2011, 17:21) *
Беда.

Почему? Насчет "длительных интервалов" я несколько утрировал - это десятки, максимум сотни микросекунд, прерывания запрещаются на время действий с многобайтовыми volatile переменными (к примеру, с тем же временем), так что прерывание пропущено не будет, просто отработает с некоторой задержкой.

Цитата(xemul @ Jun 8 2011, 17:21) *
если инкремент от внешнего кварца TMR1 придётся на незавершённую запись в TMR1 (н-р, от "TMR1H |= 0x80;"), то он будет пропущен.

Насколько я понял, в еррате сказано, что инкремент будет пропущен, если запись в TMR1 произвести как бы во время низкого уровня тактового сигнала, - то есть, если первый фронт тактового сигнала после события записи - положительный. Чтобы этого не происходило, они и рекомендуют дождаться инкремента, (он идет по положительному фронту), и немедленно делать запись в таймер. По моим подсчетам, на это есть примерно 15 мкс или чуть меньше. При системной тактовой 4 МГц на установку бита в TMR1H уйдет микросекунда, ну плюс еще парочка на "детектирование момента", то есть, по идее времени достаточно, чтобы первый фронт после записи был отрицательным, так? Зачем остальные телодвижения? Единственное мое предположение - для медленного системного такта (если, к примеру используется LP генератор?)
Цитата(xemul @ Jun 8 2011, 17:21) *
А если наоборот, то не сможет корректно выполниться "TMR1H |= 0x80;".
При тактировании таймера от системного клока такого гарантированно не случится.

Немного не понял, что значит "наоборот"?

Цитата(xemul @ Jun 8 2011, 17:21) *
Если Вы не укладываете контроллер в спячку, то проще сделать T1SYNC = 0. Тогда, скорее всего, будет достаточно Вашего исходного кода.

Спячка как раз-таки используется, причем 99.9% времени, так что синхронный режим не катит...
xemul
Цитата(stas00n @ Jun 8 2011, 19:48) *
Насколько я понял, в еррате сказано, что инкремент будет пропущен, если запись в TMR1 произвести как бы во время низкого уровня тактового сигнала, - то есть, если первый фронт тактового сигнала после события записи - положительный. Чтобы этого не происходило, они и рекомендуют дождаться инкремента, (он идет по положительному фронту), и немедленно делать запись в таймер. По моим подсчетам, на это есть примерно 15 мкс или чуть меньше. При системной тактовой 4 МГц на установку бита в TMR1H уйдет микросекунда, ну плюс еще парочка на "детектирование момента",

До 8 мкс в Вашем варианте (в текущей интерпретации компилятора), до 4 мкс в эрратином.
Цитата
то есть, по идее времени достаточно, чтобы первый фронт после записи был отрицательным, так? Зачем остальные телодвижения? Единственное мое предположение - для медленного системного такта (если, к примеру используется LP генератор?)

По идее - достаточно. Попробуйте, может и сработает.
Цитата
Немного не понял, что значит "наоборот"?

Я, когда строил RTC на пике (16Cкакой-то, давно было), столкнулся с другой веселухой: "bsf TMR1H, 7" иногда давала причудливые результаты на выходе. Последовательными обрезаниями пришёл к выводу, что приключалось оно при попадании / перепада клока между фазами Q2 и Q4 исполнения команды. Эрратин воркэраунд от этого тоже спасает.
Возможно, сейчас такой траблы уже и нет. Тогда же просто вынес RTC в какой-то мелкий пик (вспомнил - 12C508) и завёл его генератор на часовом кварце.
stas00n
В общем, помудохался я немного и решил что не фиг изобретать велосипед, вставлю-ка я в код пример из ерраты и все. Сроки по проекту поджимают.
Сейчас посмотрел на ассемблерный код своей конструкции while(TMR1L == TMR1L){} - просто идиотизм sad.gif срабатывать будет в лучшем случае через раз, в худшем - вообще все нафиг зависнет прямо в прерывании... xemul, спасибо за консультацию!
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.