При написании программ часто требуется реализовать определенную временную задержку, например чтобы помигать светодиодом. Паузу можно организовать программным путем или с помощью внутренних таймеров микроконтроллера. Программный способ заключается в многократном выполнении циклического кода, при этом время рассчитывается исходя из количества выполненных инструкций и продолжительности машинного цикла.
Рассмотрим пример организации программной паузы, код представлен ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Различные подпрограммы для реализации программной паузы, примем частоту ;тактового генератора в 4 МГц, длительность машинного цикла составит 1 мкс ;регистры Sec, Sec1, Sec2 используются как временные счетчики ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;пауза на 300 мкс плюс несколько микросекунд pause movlw .100 ;запись числа 100 в регистр Sec movwf Sec p1 decfsz Sec,F ;декремент с условием регистра Sec goto p1 ;регистр Sec не равен нулю: переход на метку p1 return ;регистр Sec равен нулю: выход из подпрограммы ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;пауза примерно на 1 сек pause1 movlw .6 ;запись числа 6 в регистр Sec2 movwf Sec2 p4 movlw .218 ;запись числа 218 в регистр Sec1 movwf Sec1 p3 movlw .255 movwf Sec ;запись числа 255 в регистр Sec p2 decfsz Sec,F ;декремент с условием регистра Sec goto p2 ;регистр Sec не равен нулю: переход на метку p2 decfsz Sec1,F ;регистр Sec равен нулю: декремент с условием ;регистра Sec1 goto p3 ;регистр Sec1 не равен нулю: переход на метку p3 decfsz Sec2,F ;регистр Sec1 равен нулю: декремент с условием ;регистра Sec2 goto p4 ;регистр Sec2 не равен нулю: переход на метку p4 return ;регистр Sec2 равен нулю: выход из подпрограммы ; |
Перейдя в подпрограмму pause, сначала записываем число 100 в регистр Sec, который будет использоваться в качестве счетчика. Далее декрементируем (с условием) данный регистр, если содержимое регистра не равно нулю, выполняется переход на метку p1, где снова производится декремент. Этот цикл будет повторяться до тех пор, пока значение регистра Sec не станет равным нулю, после чего происходит выход из подпрограммы. В данном случае цикл, состоящий из 2-ух инструкций decfsz и goto повториться 100 раз, при этом время выполнения инструкции decfsz составит один машинный цикл (один такт), а инструкция goto выполняется за два такта. Получаем 3 такта по 100 раз, пусть продолжительность машинного цикла составляет 1 мкс (при этом частота тактового генератора должна быть равна 4 МГц), в итоге время выполнения подпрограммы (пауза) составит 300 мкс. Но это не совсем точное значение, еще надо учесть время записи числа в регистр (2 такта), время перехода (инструкция call – 2 такта) и выхода (инструкция return – 2 такта) из подпрограммы. Кроме этого инструкция decfsz при обнулении регистра выполняется за 2 такта, то есть следующая команда (goto p1) пропускается, вместо нее выполняется пустой цикл, называемый nop. Тем самым получим 300+2+2+2-1 = 305 мкс, это все мелочи по сравнению с задержкой, которая нам требуется, поэтому считаю, что нет смысла рассчитывать точное время. Достаточно умножить число из регистра Sec на 3 (ведь основной многократно выполняемый цикл длиться 3 такта), затем результат умножить на продолжительность машинного цикла, вот и все.
Если требуется большая пауза, можно использовать несколько регистров, при входе в подпрограмму pause1, записываем числа сразу в 3 регистра. После обнуления регистра Sec, декрементируем регистр Sec1, если он не равен нулю, снова записываем число в регистр Sec, и заново начинаем круговой цикл декремента Sec. То есть внутренний цикл (в 3 такта) декремента регистра Sec от 255 до 0, повториться еще 218 раз, пока не обнулится регистр Sec1, после чего декрементируем регистр Sec2. Таким образом, внутренняя конструкция декремента Sec и Sec1 повториться еще 6 раз. В итого время выполнения подпрограммы составит 3х255х218х6 = 1000620 мкс = 1,00062 сек, то есть внутренний цикл (3 такта по 255 раз) повториться 218 раз, а все это вместе повториться еще 6 раз. Опять же это не совсем точное время, если разбираться, запись числа в регистр Sec (2 такта) повториться 218х6 = 1308 раз, запись в регистр Sec1 повториться 6 раз, это еще плюс 2х1308+2х6 = 2628 мкс, но это не существенно по сравнению с 1 сек.
Программная пауза отнимает процессорное время, при уходе в подпрограмму паузы микроконтроллер больше ничем не может заниматься, кроме прерываний конечно. Если прерывания возникают строго регулярно, то стоит учесть что время выполнения подпрограммы паузы увеличиться.
Другой способ организации паузы это использование внутренних таймеров микроконтроллера. В этом случае процессорное время не растрачивается и может быть направлено на решение других задач, что является несомненным преимуществом перед программной паузой. PIC16 имеют 3 таймера: TMR0, TMR1, TMR2, которые также могут работать в режиме счетчика. Таймер TMR0 является 8-ми разрядным, с программируемым предделителем, настраивается в регистре OPTION_REG. Таймер TMR1 16-ти разрядный, также имеет программируемый предделитель, настраивается в регистре T1CON. Таймер TMR2 8-ми разрядный, кроме предделителя имеет программируемый выходной делитель, регистры настройки T2CON и PR2. Таймеры TMR0, TMR1 можно настроить на внешний тактовый сигнал с выводов T0CKI и T1CKI соответственно (режим счета), или использовать внутренний тактовый сигнал Fosc/4 (режим таймера), при этом приращение (икремент) значений таймеров выполняется за каждый машинный цикл. TMR2 работает только от внутреннего тактового сигнала.
Для организации паузы будем использовать прерывания по переполнению таймера. Частоту тактового генератора примем за 4 МГц, машинный цикл соответственно 1 мкс. Ниже представлен пример кода для организации паузы в 10 мс при помощи таймера TMR0:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;пример организации паузы (примерно 10 мс) с помощью таймера TMR0, частота тактового ;генератора 4 МГц, машинный цикл равен 1 мкс org 0000h ;начать выполнение программы с адреса 0000h goto Start ;переход на метку Start ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;подрограмма обработки прерываний org 0004h ;начать выполнение программы с адреса 0004h movwf W_temp ;сохранение значений ключевых регистров swapf STATUS,W clrf STATUS movwf STATUS_temp ...................... ...................... ;выполняем требуемые операции или устанавливаем ...................... ;флаг в дополнительном регистре, который ...................... ;опрашиваем в основной программе ...................... bcf INTCON,T0IE ;запрет прерываний по переполнению TMR0 swapf STATUS_temp,W ;восстановление значений ключевых регистров movwf STATUS swapf W_temp,F swapf W_temp,W retfie ;выход из подпрограммы обработки прерывания ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Основная программа Start ....................... ;здесь происходит первоначальная настройка ....................... ;регистров специального назначения ....................... bsf STATUS,RP0 ;запись двоичного числа 11010101 в регистр movlw b'11010101' ;OPTION_REG, тем самым устанавливаем внутренний movwf OPTION_REG ;источник тактового сигнала для TMR0 bcf STATUS,RP0 ;включаем предделитель перед TMR0 ;устанавливаем коэффициент предделителя 1:64 movlw .100 ;запись числа 100 в регистр таймера TMR0 movwf TMR0 bсf INTCON,T0IF ;сброс флага прерывания по переполнению TMR0 bsf INTCON,T0IE ;разрешение прерываний по переполнению TMR0 bsf INTCON,GIE ;разрешение глобальных прерываний ....................... ....................... ....................... END ;конец всей программы ; |
В основной программе необходимо настроить таймер TMR0, для этого изменяем значения битов регистра OPTION_REG, записывая в него двоичное число 11010101 (в десятеричной форме 213), так как регистр расположен в 1-ом банке, перед записью переключаем банки. Записывая число в OPTION_REG,выбираем внутренний источник тактового сигнала для TMR0, включаем предделитель перед TMR0 (после сброса микроконтроллера при включении питания предделитель подключен к выходу сторожевого таймера WDT, а TMR0 тактируется сигналом Fosc/4), устанавливаем значение коэффициента предделителя равным 1:64. Таймер TMR0 нельзя включить или выключить отдельным битом в отличии от TMR1 и TMR2, поэтому при выборе внутреннего тактового сигнала таймер сразу начнет инкрементироваться. Надеюсь всем понятно, что без предделителя TMR0 переполнится за 256 машинных цикла (при условии, что начальное значение таймера равно 0), с коэффициентом 1:64 таймер переполнится за 16384 машинных цикла или 16384 мкс.
Далее записываем число 100 в регистр таймера TMR0, почему именно 100? А для того чтобы получить паузу примерно в 10 мс. Давайте посчитаем: при инкременте таймера от числа 100 до переполнения, необходимо 256-100 = 156 машинных цикла или 156 мкс, а учитывая предделитель получим 156х64 = 9984 мкс = 9,984 мс. После записи числа в таймер, сбрасываем флаг прерывания по переполнению TMR0, разрешаем прерывания по переполнению TMR0 и наконец, разрешаем глобальные прерывания. Приращение таймера начинается через 2 машинных цикла после записи нового значения, это без предделителя. С учетом предделителя при коэффициенте 1:64, приращение начнется через 64х2 = 128 машинных цикла или 128 мкс, думаю, это несущественно по сравнению с длительностью паузы в 10 мс, поэтому я обычно не заморачиваюсь точными расчетами.
По прошествии примерно 10 мс таймер переполнится, что вызовет прерывание и переход в подпрограмму обработки прерываний. Здесь после сохранения значений ключевых регистров (об этом я писал в статье про прерывания) выполняем требуемые операции (то для чего и нужна была пауза) или устанавливаем флаг в дополнительном регистре, который опрашиваем в основной программе. После чего запрещаем прерывания по переполнению TMR0, восстанавливаем значения ключевых регистров и выходим из подпрограммы обработки прерываний. Если снова потребуется пауза то в основной программе выполняем почти те же действия что и при первом запуске, записываем TMR0, сбрасываем флаг и разрешаем прерывания по переполнению.
Иногда требуется пауза, которую таймер не сможет обеспечить даже с максимальным коэффициентом предделителя, для TMR0 этот предел равен 256х256 = 65536 машинных цикла, или 65,536 мс (машинный цикл 1 мкс). В этом случае можно запускать таймер несколько раз. Нам потребуется дополнительный регистр var, который будет выступать в качестве счетчика запусков таймера. Код представлен ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;пример организации паузы (примерно 150 мс) с помощью таймера TMR0, с применением ;дополнительного регистра var, частота тактовогогенератора 4 МГц, машинный цикл равен 1 мкс org 0000h ;начать выполнение программы с адреса 0000h goto Start ;переход на метку Start ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;подрограмма обработки прерываний org 0004h ;начать выполнение программы с адреса 0004h movwf W_temp ;сохранение значений ключевых регистров swapf STATUS,W clrf STATUS movwf STATUS_temp decfsz var,F ;декремент с условием регистра var goto met1 ;регистр var не равен нулю: переход на метку met1 bcf INTCON,T0IE ;регистр var равен нулю: запрет прерываний по ;переполнению TMR0 ...................... ...................... ;выполняем требуемые операции или устанавливаем ...................... ;флаг в дополнительном регистре, который ...................... ;опрашиваем в основной программе ...................... goto vihod ;переход на метку vihod met1 bсf INTCON,T0IF ;сброс флага прерывания по переполнению TMR0 movlw .22 ;запись числа 22 в регистр таймера TMR0 movwf TMR0 vihod swapf STATUS_temp,W ;восстановление значений ключевых регистров movwf STATUS swapf W_temp,F swapf W_temp,W retfie ;выход из подпрограммы обработки прерывания ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Основная программа Start ....................... ;здесь происходит первоначальная настройка ....................... ;регистров специального назначения ....................... bsf STATUS,RP0 ;запись двоичного числа 11010101 в регистр movlw b'11010110' ;OPTION_REG, тем самым устанавливаем внутренний movwf OPTION_REG ;источник тактового сигнала для TMR0 bcf STATUS,RP0 ;включаем предделитель перед TMR0 ;устанавливаем коэффициент предделителя 1:128 movlw .5 ;запись числа 5 в регистр var, который служит movwf var ;в качестве дополнительного счетчика movlw .22 ;запись числа 22 в регистр таймера TMR0 movwf TMR0 bсf INTCON,T0IF ;сброс флага прерывания по переполнению TMR0 bsf INTCON,T0IE ;разрешение прерываний по переполнению TMR0 bsf INTCON,GIE ;разрешение глобальных прерываний ....................... ....................... ....................... END ;конец всей программы ; |
Перед запуском таймера записываем число 5 в регистр var, в подрограмме обработки прерываний первым делом декрементируем регистр var, если он не равен нулю, сбрасываем флаг прерывания, записываем число в таймер и выходим из подпрограммы обработки прерываний. Как только регистр var обнулится (после 5-го запуска таймера), выполняем требуемые операции, запрещаем прерывания и выходим из обработчика. В данном коде пауза составит примерно 150 мс, таймер отсчитывает (256-22)х128 = 29952 мкс, и запускается 5 раз 29952х5 = 149760 мкс = 149,760 мс. Варьируя коэффициентом предделителя, значением в регистре TMR0 и var, можно настроить временную задержку в широких пределах. Желательно настраивать таймер так, чтобы уменьшить количество прерываний, ведь желаемую паузу можно получить разными настройками, например, поставить малый коэффициент предделителя и большое число в var, тем самым увеличивая количество прерываний.
Для таймера TMR1, справедливы те же действия, здесь надо учесть, что таймер 16-ти разрядный, запись значения производится в 2 регистра TMR1H (старший) и TMR1L (младший), кроме этого таймер можно включать и выключать битом TMR1ON в регистре T1CON, что очень удобно. В подпрограмме обработки прерываний можно не запрещать прерывания по переполнению, а просто остановить таймер. Перед записью нового значения, таймер желательно останавливать. Ниже представлены коды для организации паузы в 30 мс, и 3 сек с помощью дополнительного регистра var:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;пример организации паузы в 30 мс с помощью таймера TMR1, частота тактового ;генератора 4 МГц, машинный цикл равен 1 мкс org 0000h ;начать выполнение программы с адреса 0000h goto Start ;переход на метку Start ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;подрограмма обработки прерываний org 0004h ;начать выполнение программы с адреса 0004h movwf W_temp ;сохранение значений ключевых регистров swapf STATUS,W clrf STATUS movwf STATUS_temp ...................... ...................... ;выполняем требуемые операции или устанавливаем ...................... ;флаг в дополнительном регистре, который ...................... ;опрашиваем в основной программе ...................... bcf T1CON,TMR1ON ;выключаем таймер TMR1 swapf STATUS_temp,W ;восстановление значений ключевых регистров movwf STATUS swapf W_temp,F swapf W_temp,W retfie ;выход из подпрограммы обработки прерывания ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Основная программа Start ....................... ;здесь происходит первоначальная настройка ....................... ;регистров специального назначения ....................... movlw .0 ;запись числа 0 в регистр T1CON movwf T1CON ;тем самым устанавливаем внутренний ;источник тактового сигнала для TMR1 ;устанавливаем коэффициент предделителя 1:1 ;выключаем TMR1, впринципе данное действие ;можно не выполнять, так как при сбросе во ;время подачи питания значение регистра равно 0 movlw .208 ;запись числа 35536 в таймера TMR1 movwf TMR1L ; movlw .138 ; movwf TMR1Н ; bсf PIR1,TMR1IF ;сброс флага прерывания по переполнению TMR1 bsf STATUS,RP0 ; bsf PIE1,TMR1IE ;разрешение прерываний по переполнению TMR1 bcf STATUS,RP0 ; bsf INTCON,PEIE ;разрешение прерываний периферийных модулей bsf INTCON,GIE ;разрешение глобальных прерываний bsf T1CON,TMR1ON ;включаем таймер TMR1 ....................... ....................... ....................... END ;конец всей программы ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;пример организации паузы в 3 сек с помощью таймера TMR1, с применением ;дополнительного регистра var, частота тактовогогенератора 4 МГц, машинный цикл равен 1 мкс org 0000h ;начать выполнение программы с адреса 0000h goto Start ;переход на метку Start ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;подрограмма обработки прерываний org 0004h ;начать выполнение программы с адреса 0004h movwf W_temp ;сохранение значений ключевых регистров swapf STATUS,W clrf STATUS movwf STATUS_temp decfsz var,F ;декремент с условием регистра var goto met1 ;регистр var не равен нулю: переход на метку met1 bcf T1CON,TMR1ON ;регистр var равен нулю: выключаем TMR1 ...................... ...................... ;выполняем требуемые операции или устанавливаем ...................... ;флаг в дополнительном регистре, который ...................... ;опрашиваем в основной программе ...................... goto vihod ;переход на метку vihod met1 bcf T1CON,TMR1ON ;выключаем таймер TMR1 bсf PIR1,TMR1IF ;сброс флага прерывания по переполнению TMR1 movlw .220 ;запись числа 3036 в таймера TMR1 movwf TMR1L ; movlw .11 ; movwf TMR1Н ; bsf T1CON,TMR1ON ;включаем таймер TMR1 vihod swapf STATUS_temp,W ;восстановление значений ключевых регистров movwf STATUS swapf W_temp,F swapf W_temp,W retfie ;выход из подпрограммы обработки прерывания ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Основная программа Start ....................... ;здесь происходит первоначальная настройка ....................... ;регистров специального назначения ....................... movlw b'00110000' ;запись двоичного числа 00110000 в регистр movwf T1CON ;T1CON, тем самым устанавливаем внутренний ;источник тактового сигнала для TMR1 ;устанавливаем коэффициент предделителя 1:8 ;выключаем TMR1 movlw .6 ;запись числа 6 в регистр var, который служит movwf var ;в качестве дополнительного счетчика movlw .220 ;запись числа 3036 в таймера TMR1 movwf TMR1L ; movlw .11 ; movwf TMR1Н ; bсf PIR1,TMR1IF ;сброс флага прерывания по переполнению TMR1 bsf STATUS,RP0 ; bsf PIE1,TMR1IE ;разрешение прерываний по переполнению TMR1 bcf STATUS,RP0 ; bsf INTCON,PEIE ;разрешение прерываний периферийных модулей bsf INTCON,GIE ;разрешение глобальных прерываний bsf T1CON,TMR1ON ;включаем таймер TMR1 ....................... ....................... ....................... END ;конец всей программы ; |
В первом случае таймер отсчитывает 65536-35536 = 30000 мкс = 30 мс, коэффициент предделителя равен 1:1. Во втором, таймер отсчитывает (65536-3036)х8 = 500000 мкс = 500 мс = 0,5 сек, (коэффициент предделителя 1:8), таймер запускается 6 раз, итого пауза равна 0,5х6 = 3 сек.
КЮ
18 Июл 2024Ну Вы и гоните. Вы вообще даташит читали? Предделитель в OPTION_REG сбрасывается при записи в сам таймер, следовательно, его надо настраивать после этого. Просветители, чёрт побери.