Расчет Точных Временных Задержек Возможно ли рассчитывать точные временные задержки?
#1
Отправлено 14 Октябрь 2009 - 13:35
Подскажите пожалуйста как можно рассчитать точную временную задержку на С для PICов.
#3
Отправлено 16 Октябрь 2009 - 12:13
#include <htc.h> #include <pic.h> #include <pic16f62xa.h> //config __CONFIG(WDTDIS & LVPDIS & HS);// wachdogtimer off, low voltage progr off, qartz external //function void main(void); unsigned char eeprom_read(unsigned char adr); void wait(unsigned int time_ms); //void second(second); //EEpROM DATA __EEPROM_DATA(0x7E,0x0C,0xB6,0x9f,0xcc,0xda,0xfa,0x0e);// 0,1,2,3,4,5,6,7 __EEPROM_DATA(0xfe,0xde,0x00,0x00,0x00,0x00,0x00,0x00);//8,9 №eeprom adress== numbers //variables unsigned char Hour=12,HourL=0,HourH=0; unsigned char Minute=59, MinuteL=0,MinuteH=0; unsigned char Second=0; unsigned char count=0, countT1=0, mode_counter=0; void main(){ //PORTB 1-7 sevenseg data //RA7,RA6 - oSC //RB0- button for next //ra0-ra3 - dinamic indicatin //rA4-RA5 - buttons //PORT init CMCON=0x07;// compartors off TRISA=0x10; PORTA=0x00; TRISB=0x01; PORTB=0x00; //timer 1 use for second account //timer0 use for inication // timer0 init T0CS=0; // generator internal PSA = 0;//prescaler use PS0=0; PS1=1; // prescaler 1:8 PS2=0; //timer1 init TMR1L=0xf2; TMR1H=0x0b;//таймер настроен примерно на 1 сек #asm nop;// надо для точного счета!!!!!!!!!!!!! nop nop nop nop nop nop #endasm T1OSCEN=0;//генератор - внутренний off T1CKPS1=1;// предделитель 1:8 T1CKPS0=1; TMR1CS=0;//работа от внутреннего генератора f/4 TMR1ON=1;//вкл //Interrupt Enable TMR1IE=1;//t1 int PEIE=1; GIE=1;// all int enable while(1){//here will be indication //button presed if(!RB0){ //Mode butto press wait(2000); if(!RB0){ wait(10000); if (!RB0){ wait(10000);//only for while (RB0){ PORTB=eeprom_read((Hour%0x0a)&0x0f); RA2=1;//show HourL wait(10); RA2=0; PORTB=eeprom_read((Hour/0x0a)&0x0f); RA3=1;//show HourH wait(10); RA3=0; if(RA4==0){ wait(2000); if (RA4==0){ Hour++; if(Hour==24) Hour=0; }// RA$==0 }//if(RA4==0) first pres }//while if(!RB0){ wait(10000); while(RB0){ PORTB=eeprom_read(((Minute)%0x0a)&0x0f); RA0=1;//show MinuteL wait(20); RA0=0; PORTB=eeprom_read(((Minute)/0x0a)&0x0f); RA1=1;//show MinuteH wait(20); RA1=0; if(RA4==0){ wait(2000); if (RA4==0){ Minute++; if(Minute==60) Minute=0; }// RA$==0 }//if(RA4==0) first pres }//while }//if(!RB0) Second=0; wait(10000); }//if (!RB0) long_press else { wait(1500); while(RB0){ PORTB=eeprom_read(((Second)%0x0a)&0x0f); RA0=1;//show MinuteL wait(200); RA0=0; PORTB=eeprom_read(((Second)/0x0a)&0x0f); RA1=1;//show MinuteH wait(200); RA1=0; if(RA4==0){ wait(2000); if (RA4==0){ Second=0; }// RA$==0 }//if(RA4==0) first pres } } }//if(!RB0) first short press }//if(!RB0)start press //indication PORTB=eeprom_read(((Minute)%0x0a)&0x0f); RA0=1;//show MinuteL wait(200); RA0=0; PORTB=eeprom_read(((Minute)/0x0a)&0x0f); RA1=1;//show MinuteH wait(200); RA1=0; PORTB=eeprom_read((Hour%0x0a)&0x0f); RA2=1;//show HourL wait(200); RA2=0; PORTB=eeprom_read((Hour/0x0a)&0x0f); RA3=1;//show HourH wait(200); RA3=0; } }//main static void interrupt isr(void) { if(TMR1IF){ // RA4=!RA4; TMR1L=0xdd; TMR1H=0x0b; GIE=0; TMR1IF=0; GIE=1; countT1++; if (countT1==2) { Second++;//one second is over countT1=0; } if (Second==60){ Second=0; Minute++; if (Minute==60){ Minute=0; Hour++; if (Hour==24){ Hour=0x00; }//if (Hour==24) }//(Minute==60 }//(Second==60) }//tmr1if }//isr unsigned char eeprom_read(unsigned char a) { EEADR=a;// адрес ячейки данных -> EEADR RD=1; //добро на чтение //while(RD){} //ждем return EEDATA; }//eeprom_data void wait(unsigned int time_ms){ TMR0=0x05; count=0; while(time_ms){ while(count!=0x10){ if (T0IF){ TMR0=0x05; count++; T0IF=0; //time count }//if (T0IF) }//while(count) time_ms--; }//while (time_ms) }
Может подскажете почему так?
#4
Отправлено 17 Октябрь 2009 - 19:05
2. Неправильно рассчитали временную задержку (нужно считать по формуле из даташита)
#6
Отправлено 28 Октябрь 2009 - 01:25

Реализация точных задержек на таймерах дело не простое, и требует некоторого опыта и много личного времени.
Ускорю Вам немного процесс "самоучения" и приведу пару примеров работы с таймерами. Заодно укажу на ошибки в вашей программе.
Итак, начнём с самого основного, с испольования таймера как 2-х байтной переменной (тип int).
Вовсе не обязательно "разлогать" его на байты, и обращатся к ним по отдельности. Это может сделать сам компиль.
В <b>pic16f62xa.h</b> TMR1 к сожалению не описан, но это не значит что мы сами не сможем сделать это.
Итак, пишем в начале проги (после инклудов и конфигов) такую строку
<b>static volatile near unsigned int TMR1 @ 0x00E;</b>
Тем самым мы объявили переменную TMR1 как 2-х байтную переменную находящуюся по адресу 0x00E.
Теперь мы можем к таймеру обращаться как к переменной.
Получилось у нас вот такое дело
#include <pic.h> #include <pic16f62xa.h> // Это вовсе не обязательно. Всё это есть в pic.h __CONFIG(WDTDIS & LVPDIS & HS); static volatile near unsigned int TMR1 @ 0x00E; void main() { CMCON=0x07;// compartors off while(1); }
Едем далее.
Настраиваем таймер и разрешаем прерывания
TMR1IF=0; TMR1ON=1; TMR1= 65535 - 1000; // Таймер на 1000 циклов TMR1IE=1; PEIE=1; GIE=1;Думаю тут всё понятно.
Далее, создаём обработчик.
<b>void interrupt isr(void)
{
}</b>
И в нём пишем:
<b>if(TMR1IF)
{
TMR1IF =0;
TMR1= 65535 - 1000; // Таймер на 1000 циклов
NOP(); // Для точки останова
}</b>
Ставим бркпоинт в обработчике таймера (на НОПе) и смотрим в мплабе с каким промежутком у нас он вызывается.
Посмотрели, убедились что далеко не 1000 циклов, а 1012. А нам нужно 1000.
Немного пошаманив, поизменяв число 1000, добились постоянного вызова через каждые 1000 циклов. Получилось 988 (у меня по крайней мере).
И думаем, о как хорошо, сделали таймер на 1 мс. Да ничего подобного, мы просто, тупо, методом тыко подстроили таймер.
И проблемы начинаются когда мы начинаем писать программу дальше, добавлять ещё обработчики, объявлять переменные внутри обработчика и т.д..
Пример. Добавим парочку NOP'ов до обработки флага TMR1IF
NOP();
NOP();
if(TMR1IF)
{
......
И видим что наша 1 мс. уехала на 2 цикла.
И чего, постоянно коректировать наше число, подстраивать под программу ? А если мы добавим ещё какой-нибудь обработчик, например внешнего прерывания, и во время его обработки, произойдёт переполнение таймера. И пока обработчик не закончится, программа не выдет на обработку таймера. И когда она выдет на него, пройдёт некоторое время после установки флага <b>TMR1IF</b>, и это время у нас прибавится к нашему установленному значению. Следующим вызовом таймера у нас будет пересчёт.
И что нам делать ? А всё очень просто, мы скоректируем этот пересчёт, и установим значение таймера больше на столько тактов, сколько он пересчитал.
Пишем :
TMR1= 65535 - 1000 <b>+ TMR1</b>;
И получается, что следующим вызовом он "догонит" отстающее время.
Запускаем отладку, и видим что у нас на каждом вызове проходит одно и тоже время, но оно почемуто не 1000 циклов (хотя должно быть), а 1002.
И тут мы вспоминаем добрый и полезный ДШ. Оказывается, после установки таймера 2 следующих МЦ он не работает

Корректируем эти 2 цикла :
TMR1= 6553<b>7</b> - 1000 + TMR1;
Т.е. прибавляем к таймеру 2 еденицы (уменяьшаем время отсчёта).
Собираем всё это дело в кучу
#include <pic.h> #include <pic16f62xa.h> // Это вовсе не обязательно. Всё это есть в pic.h __CONFIG(WDTDIS & LVPDIS & HS); static volatile near unsigned int TMR1 @ 0x00E; void main() { CMCON=0x07;// compartors off TMR1IF=0; TMR1ON=1; TMR1= 65535 - 1000; // Таймер на 1000 циклов TMR1IE=1; PEIE=1; GIE=1; while(1); } void interrupt isr(void) { NOP(); NOP(); if(TMR1IF) { TMR1IF =0; TMR1= 65537 - 1000 + TMR1; NOP(); } }И у нас получился таймер на 1 мс. Причём хоть до него, хоть после, пиши что угодно, он всегда будет считать по 1 мс.
На сколько бы программа не отстала до обработки флага <b>TMR1IF</b>, следующим вызовом она догонит своё полюбому.
Ну а теперь можно и секунды считать

Добавляем переменную для подсчёта миллисекунд
<b>unsigned int ms=0;</b>
и в обработчике пишем счётчик
ms++; if(ms>999) { ms=0; NOP(); // 1 секунда }
Ставим брекпоинт на НОПе, и запускаем отладчик. И что мы видим ? О чудо , у нас получилась 1 сек

Теперь о задержках.
В Вашей программе увидел функцию <b>wait</b>. Не трудно было догататься что это такое.

Хочу привестисвой пример реализации таких задержек.
Что нам нужно ?
Нам нужно создать свой таймер на основе системного (тот который мы сделали).
Для этого создаём структуру с переменными (свойства таймера) и с именем (назовём tmr1).
struct { unsigned enabled : 1; // таймер вкл./выкл. unsigned int interval; // текущее значение таймера } tmr1;
И процедуру его обработки (само тело)
void tmr1_modal() { if(tmr1.enabled) // если таймер включен { tmr1.interval--; // уменьшаем его значение на еденицу if(tmr1.interval==0) tmr1.enabled=0; // если таймер дошёл до нуля, выключаем его } }
И в обработчике системного таймера (TMR1) вызываем эту процедуру.
if(TMR1IF) { TMR1IF =0; TMR1= 65537 - 1000 + TMR1; tmr1_modal(); ..... .....
И таймер готов.
Теперь остаётся в программе, в любом месте (естественно не в обработчике прерываний) задать ему значение (interval), включить его (enabled)
и ждать когда он выключится.
Выглядет это примерно так
tmr1.interval=500; tmr1.enabled=1; while(tmr1.enabled);
Всё, задержка в 500 мс. готова

Теперь у нас и секунды будут точно щёлкать и задержки можно реализовывать в основном цикле.
Ну вот вроде бы и всё.

Прикрепляю проект, который Мы только что сделали.

Количество загрузок:: 313
Удачи и успехов !
#7
Отправлено 28 Октябрь 2009 - 10:39
Скажите каким кампилятором вы пользуетесь и может посоветуете, что почитать чтобы с С разобраться ?
#8
Отправлено 28 Октябрь 2009 - 19:37

Вместе с компилем устанавливается мануал (правда на англицком), в нём описаны все команды и функции.
#12
Отправлено 18 Январь 2011 - 20:29
Подготовительная работа :
1.) Выкидываем нафиг Протеус и другие симуляторы. Симулятором будет ваш мозг !
Распечатываем программу на принтере, ползем по листингу с карандашом и
в голове представляем что делает процессор, исполняя ту или иную команду
и что в итоге происходит.
2.) Выкидываем нафиг Си. Си - это попса. Пусть Филипп Киркоров пишет на Си.
Тяжелый рок - это ассемблер. В нем все в тысячу раз легче и в тысячу раз
проще !
3.) Выкидываем нафиг все среды разработки. Они даром не нужны.
Все, что вам надо - пакетный ассемблер, которому на вход подается текст
программы, а на выходе получается или HEX-файл или листинг с ошибками.
Проверено на личном опыте (с 1987-го года пишу на всевозможных
ассемблерах от PDP-11 до Motorola MCORE и симулятор не понадобился ни разу).
Создание таймера :
Пусть наш процессор - самый простой из всех, с самым примитивным 8-битным
таймером - PIC16F84 и пусть нам надо отсчитывать десятки миллисекунд.
В навороченных процессорах с многорежимными таймерами все выглядит, конечно,
гораздо проще, но мы возьмем самый сложный случай.
Первым делом подбираем кварц, чтобы его частота имела достаточно много
двоек среди множителей. Например, берем 4 Mhz, тогда один такт процессора
- 1 мкс. Значит нам надо сделать так, чтобы таймер тикал один раз в 10000
тактов. Делим эту 10000 на два до тех пор, пока можем
10000 /2 = 5000
10000 /2 /2 = 2500
10000 /2 /2 /2 = 1250
10000 /2 /2 /2 /2 = 625
и еще немножко :
10000 /2 /2 /2 /2 /2 = 312.5
10000 /2 /2 /2 /2 /2 /2 = 156.25
Результат деления вписался в диапазон байта : 0..255. Отлично !
Значит будем делать так :
1.) Предделитель таймера сделаем равным 2*2*2*2*2*2 = 64.
В итоге таймер будет тикать каждые 64 такта.
2.) Когда таймер тикнет и переполнится (перекинется из 255-и в 0-ль),
через 5 тактов после этого начнет выполняться обработчик прерывания
от таймера. Мы должны будем обновить счетчик таймера ДО ТОГО как
он тикнет еще раз (иначе возникнет трудноразрешимая проблема
потерянных тиков). Поскольку тикает таймер раз в 64 такта для этого
у нас есть большой запас по времени (64-5 = 59 тактов). Если мы поместим
обновление счетчика таймера в обработчике прерывания САМЫМ ПЕРВЫМ, то
мы заведомо успеем.
3.) Будем в обработчике прерывания подгружать в счетчик таймера
константу 256-156 = 100. Но каждый четвертый раз подгрузим не 100, а 99,
т.е. будем делать поправку на то, что 10000 на 64 делится с дробями.
В итоге три раза таймер будет переполняться через 156 тактов, а
один раз через 157. Что даст в среднем переполнение через 156.25 такта.
Т.е. таймер будет отсчитывать 1MHz / 64 / 156.25 = 100Hz
сотые доли секунды, т.е десятки миллисекунд
4.) В обработчике прерывания будем декрементировать некую переменную
PAUSE до нуля. Если в головной программе надо будет сделать паузу в 1
секунду, то закинем в переменную PAUSE число 100 и подождем пока PAUSE
не скинется в ноль. В процессе ожидания наша головная программа может
заниматься своими делами, например опрашивать кнопки - пауза все равно
отсчитается. Таких переменных для отсчета пауз можно сделать несколько.
5.) Кроме того, можно инкрементирвать в обработчике переменную TIME,
которая будет нашим системным временем в десятках миллисекунд.
А можно это время сделать двухбайтным TIME_H:TIME_L, а можно и
четырехбайтным.
Короче говоря, в головной программе имеем :
MOVLW B'xx0x0110';Predivisor RTCC 1:64 OPTION MOVLW (1 << GIE) | (1 << RTIE) MOVWF INTCON;Разрешение прерываний по переполнению таймера
А в обработчике прерывания имеем :
ORG 0004H ;Вектор прерывания INTERRUPT MOVWF TEMP_W ;Сохранение SWAPF STATUS,W ; W и MOVWF TEMP_STATUS; STATUS INCF TIME_L,F ;Мл.байт системного времени BTFSC STATUS,Z INCF TIME_H,F ;Ст.байт системного времени MOVLW .99 ;В одном случае из четырех BTFSC TIME_L,1 BTFSS TIME_L,0 MOVLW .100 ;В трех случаях из четырех MOVWF TMR0 ;Обновление счетчика таймера MOVF PAUSE,F BTFSS STATUS,Z ;Надо ли отсчитывать паузу ? DECF PAUSE,F ;Отсчет паузы ... SWAPF TEMP_STATUS,W ;Восстановление MOVWF STATUS ; W SWAPF TEMP_W,F ; и SWAPF TEMP_W,W ; STATUS BCF INTCON,RTIF;Сброс RTCC overflow interrupt flag RETFIE ;Возврат из прерывания
И это все ! Причем таймер выходит не плюс минус лапоть, а секунды
отсчитывает, что твой кварцевый метроном.
#13
Отправлено 19 Январь 2011 - 16:45
#14
Отправлено 19 Январь 2011 - 17:13
Alex (19.1.2011, 19:45) писал:
"Распечатываем программу на принтере, ползем по листингу с карандашом" - А это разве не новшество

Только так все радости жизни мимо пройдут, пока будете "ползать с карандашом"
P.S. Можно оспаривать чужое мнение, но не навязывать своё.....
#15
Отправлено 22 Февраль 2011 - 23:14
Во всяком случае тех людей, которые пользуются Протеусами, я с помощью карандаша обгоняю.
Т.е. если вы натренируете свой мозг прогонять программу мысленно глядя на ее листинг,
лузеров, которые пользуются эмуляторами в качестве протеза мозга вы по скорости всегда будете делать.
Когда я начинал программировать, за время пока компилируется программа можно было пообедать.
Поверьте, очень способствовало быстрому росту профессионализма.
Приучало, сначала потратить 15 минут на мысленную компиляцию глазами и карандашом, чтобы потом
не терять в пустую много больше времени из-за наляпанных ошибок.
Что в итоге и привело к убежденности, что листинг и карандаш - самый быстрый способ.
Главное соблюдать принцип - чтобы не тратить огромное время на отлавливание ошибок
надо их не делать. А чтобы их не делать нужно хорошо понять работу команд процессора
и если тогда ты, глядя на листинг, видишь, что алгоритм безупречен, то и работать будет
он сразу.
О чем там говорить, если я не раз сейчас наблюдаю как заказчик жалуется, что написанная мною
программа не симулируется. Я его спрашиваю - программа на железе работает ?
Он говорит - работает. Но не симулируется.
Так-что это именно с симулятором только время зря терять, наступая на грабли
невозможности идеальной симуляции реальных процессов.
Пока вы трахаетесь с одной программой в симуляторе, а без него программу давно
напишу и отлажу на конечном железе.
#16
Отправлено 22 Февраль 2011 - 23:22
Например отсчитывание четверти такта - вот новое.
В моем таймере каждое четвертое прерывание отчитывается на такт больше, что
дает поправку в среднем ровно на четверть такта.
Т.е. как делатся прецизионнаяя поправка к частоте на фиксированную долю такта, если кварц
не дает получить требуемую частоту сразу в чистом виде.
И нового то, как таймер делать минимумом средств (и минимумом команд процессора), сразу и точно.
А в вашем варианте вы попадали пальцем в небо :
>Посмотрели, убедились что далеко не 1000 циклов, а 1012. А нам нужно 1000.
>Немного пошаманив, поизменяв число 1000, добились постоянного вызова через каждые 1000 циклов. Получилось 988 (у меня по крайней мере).
>И думаем, о как хорошо, сделали таймер на 1 мс. Да ничего подобного, мы просто, тупо, методом тыко подстроили таймер.
и все эти подгонки методом тыка - это художественная самодеятельность какая-то.
Поправку надо вводить не по результатам сомнительных замеров, а рассчитать заранее какая она понадобится.
Если взялись делать таймер - делайте сразу образцово, а не подгоняйте алгоритм киянкой по месту.
#17
Отправлено 05 Ноябрь 2011 - 19:28
Цитата
константу 256-156 = 100
Дело в том что когда мы подгружаем в таймер константу сбрасывается его предделитель и теряются такты 5-10, сколько их пройдет с момента переполнения до загрузки константы в таймер. Поэтому будет иметь систематическую недостачу насчитанных тактов, что выльется в увеличении задержки на 5-10 маш.циклов примерно каждые 156.25*64 маш.цикла. Может это конечно и спишется в погрешность кварцевого генератора... однако имеет место быть. Для часов такой подход возможен только если предусмотрена еще какая-то коррекция хода на более высоком уровне - например пропускать или наоборот начислять по два за раз по 10мс интервалами каждую минуту. В зависимости от того сколько раз прибавим или отнимем от "счетчика времени" интервалов по 10мс за минуту можно регулировать величину коррекции хода часов. Остается найти кварц с нулевым ТКЕ или термостатировать его и можно получить довольно точные часы с точностью хода +-1 минута в год независимо от точности кварца.
Еще один принцип формирования нужных временных интервалов: пусть таймер себе считает непрерывно некоторое время, сравниваем в программе его с предыдущим значением - если оно больше на необходимый интервал(например 1000) - прибавляем к предыдущему значению 1000 - неважно на сколько больше счетчик перебрал в момент проверки, просто следующий момент коррекции произойдет немного раньше. При этом сам таймер не затронут, а значит такты не теряются. Даже если где-то в программе произойдет задержка больше чем необходимый интервал - программа это наверстает за несколько заходов.