Статья
Версия для печати
Обсудить на форуме
Генерация широтно-импульсной модуляции (ШИМ) микроконтроллером.


Автор: Алексей1153


Регулирование напряжения с помощью ШИМ. В примерах используется микроконтроллер PIC16F84.
Опять-таки сложных терминов я не знаю и употреблять не буду.
Если коротко, то ШИМ - это цифровой сигнал, с помощью которого можно задать аналоговый сигнал. На рисунке 1 приведена временная диаграмма ШИМ с постоянной скважностью.

Рисунок 1 - Временная диаграмма ШИМ


Период ШИМ T - величина постоянная (это время). На одном периоде укладывается один единичный импульс шириной T1 и один нулевой импульс шириной T0. При этом

T = T1 + T2 = const

Скважность ШИМ G - это и есть эквивалент амплитуды аналогового сигнала:

G = T1 / T,    0 <= G <= 1

Изменением ширины единичного импульса можно регулировать средний уровень напряжения: если уровень логической единицы ШИМ Um= 5 вольт, то подав цифровой сигнал ШИМ на фильтр напряжения, на выходе фильтра можно получить аналоговое напряжение U с любой амплитудой в диапазоне от 0 до 5 вольт.

U = Um * G

В некоторых случаях применение фильтра необязательно - например, при регулировании яркости свечения светодиода, так как у него есть некоторая постоянная времени зажигания и гашения, и если период ШИМ меньше этой постоянной, то мерцания не будет. Но в некоторых случаях без фильтра не обойтись.
Естественно, чем меньше период ШИМ, тем "глаже" будет аналоговый сигнал, но уменьшение периода ведёт к тому, что увеличивается дискретность регулирования скважности (при генерации МК).
Пусть частота кварца, задающего тактовую частоту МК равна Fq. Один машинный такт в PIC выполняется за 4 периода частоты кварца. Таким время машинного такта.

Tmt = 4 / Fq

В PIC16F84 имеется восьмиразрядный таймер TMR0, содержимое которого увеличивается на единицу за каждый машинный такт (при предделителе = 0). При переполнении TMR0 в регистре STATUS устанавливается бит T0IF. И если в бит разрешения прерывания T0IE имеет значение 1, то происходит вызов обработчика прерываний. Таймер сам "перезапускается" c нуля.

Сгенерировать ШИМ можно несколькими способами.
1) T1 и T0 отмеряются количеством выполненных команд. Этот способ самый грубый и неудобный. Да, он обеспечивает минимальный период ШИМ, но это бывает нужно очень редко. Кроме того, если кроме генерации ШИМ нужно производить другие вычисления (а их нужно!), то вообще дрова. Если ещё учесть, что команды ветвления выполняются за два такта, то для стабилизации периода ШИМ все ветви программы нужно "балансировать" (вставляя NOP), чтобы все ветви выполнялись за одинаковое время.
2) Определенный флаг Fs хранит текущее состояние ШИМ - 0 или 1. Включается прерывание по таймеру (в PIC16F84 таймер только один (восьмиразрядный) - TMR0) и по каждому прерыванию от таймера флаг инвертируется, и в зависимости от нового состояния флага в таймер загружается либо T0, либо T1. Таким образом, остальные вычисления выполняются независимо от генерации ШИМ, надо только следить за сохранением контекста (рабочего регистра, регистра статуса и т.д.) при входе в обработчик прерывания и восстановлением при выходе. сбрасывать флаг прерывания лучше сразу после перезагрузки TMR0 - если таймер сработает до выхода из обработчика, то сразу после выхода снова произойдёт прерывание - таким образом, ширина периода ШИМ останется постоянной. Конечно, если сделать предделитель = 0, а обработчик будет выполняться более чем за 256 команд, то ни о каком постоянном периоде и не надо мечтать. Этот способ позволяет задать T от 0 до 512 dt. (dt - условная единица времени), T1 и T0 от 0 до 256 dt с дискретностью регулирования 1 dt, где 1 dt практически равна Tmt (с очень небольшими погрешностями).
3) В этом способе 1 dt = 256*Tmt. Регистр таймера вообще не трогается - он крутится сам по себе, сам перезапускается, и точно раз в dt генерирует прерывание. Ниже приведена программа, которая использует именно этот способ. Обратите внимание, что аппаратный предделитель не используется, хотя ничто не мешает это сделать, если нужно подзамедлить слишком шустрый сей процесс. В комментариях всё описано.

Код: (C)
#include
#include<macroZC.inc>
        __config b'11111111110010'
;------------------------------------------
;переменные
        cblock  0x0c
;для сохранения контекста
        rTempW
        rTempZ
        rTempSTATUS
;для шим
        rShimFlag       ;флаг тек.уровня шим
        rShimTMR        ;таймер шим
        rShimT0         ;ширина нуля шим
        rShimT1         ;ширина единицы шим
        rShimLevelPrescaler     ;счётчик - предделитель для уменьшения таймера
;ШИМ
        rShimSkvazhPrescaler    ;счётчик - предделитель для регулирования частоты
 ;вызова процедуры изменения скважности ШИМ
        endc
;------------------------------------------
;константы
cShimLevelPrescaler     equ     .32     ;значение пред-ля rShimLevelPrescaler
cShimSkvazhPrescaler    equ     .115    ; значение пред-ля rShimSkvazhPrescaler
cShimT1max      equ     .10     ;макс ширина 0 шим
cShimT0max      equ     .10     ;макс ширина 1 шим
cShimTPeriod    equ     cShimT0max+cShimT1max   ;период шим
;------------------------------------------
;макросы
;макрос перехода по условию Z=1
JZ      macro   lblJZ   ;переход,если Z=1
        btfsc   STATUS,Z
        goto    lblJZ
        endm
;макрос перехода по условию Z=0
JNZ     macro   lblJNZ  ;переход,если Z=0
        btfss   STATUS,Z
        goto    lblJNZ
        endm

;выбор состояния ключей по состоянию датчиков
;------------------------------------------
;вектор сброса
        org     0
        goto    start
;------------------------------------------
;вектор прерывания
        org     4
        goto    interrupt
;------------------------------------------
start   ;инициализация спец регистров
        bcf     STATUS,RP1
        bcf     STATUS,RP0
        bcf     INTCON,GIE      ;глоб запрет прерываний
        bsf     INTCON,T0IE     ;разреш прерыв от таймера
        bsf     STATUS,RP0
        movlw   b'10011000'
        movwf   OPTION_REG
        bcf     STATUS,RP0     
;------------------------------------------
;инициализация переменных
        movlw   cShimLevelPrescaler
        movwf   rShimLevelPrescaler

        ;запуск шим с минимального значения
        movlw   cShimTPeriod
        movwf   rShimT0
        movwf   rShimTMR
        clrf    rShimT1
        clrf    rShimFlag
        clrf    TMR0
        ;конец инициализации
        bsf     INTCON,GIE      ;разреш прерываний
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
; Основная программа - в ней используется флаг шим, о "существовании " прерывания
; она даже не "подозревает"
main   
        ;...
        ;...
        ;...
        ;...
        goto    main
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
;процедура изменения скважности ШИМ по определённому закону
; По сути, здесь задаётся мгновенное значение уровня аналогового напряжения.
; Процедура проверяет флаги, выставленные основной программой
; и корректирует скважность по нужному закону
ShimZakonT1T0

;Меняем T1 в пределах 0 ... cShimTPeriod
;...

;Вычисляем новое значение T0
SZ_Calc_T0
        movlw   -cShimTPeriod
        addwf   rShimT1,w
        movwf   rShimT0
        comf    rShimT0,f
        incf    rShimT0,f
        return
;----------------------------------------------------------------------------
;Обработчик прерывания
interrupt
        ;сохранение контекста
        movwf   rTempW          ;сохраняем W
        JZ      int_Z1          ;определяем и сохраняем Z
int_Z0  bcf     rTempZ,0
        goto    $+2
int_Z1  bsf     rTempZ,0
        movf    STATUS,w        ;сохраняем STATUS
        movwf   rTempSTATUS
        ;-------------------

        ;уменьшение счётчика-предделителя смены флага шим
        ; и смены флага, если переполнение
int_ShimTMR
        movf    rShimTMR,w
        JZ      int_ChangeShim
        decf    rShimTMR,f      ;уменьшение таймера шим
        goto    int_ShimEnd
        ;меняем флаг шим
int_ChangeShim
        movf    rShimFlag,w     ;проверка текущего уровня шим
        JNZ     int_Shim1to0
int_Shim0to1;нужно сделать переход с 0 в 1
        movf    rShimT1,w       ;загрузка таймера ШИМ значением T1
        JZ      int_Shim1to0    ;и если оно=0, сразу меняем флаг ШИМ опять
        movwf   rShimTMR
        bsf     rShimFlag,0
        goto    int_ShimEnd
int_Shim1to0;нужно сделать переход с 1 в 0
        movf    rShimT0,w       ;загрузка таймера ШИМ значением T0
        JZ      int_Shim0to1    ;и если оно=0, сразу меняем флаг ШИМ опять
        movwf   rShimTMR
        clrf    rShimFlag
int_ShimEnd
        ;-------------------

        ; уменьшение счётчика-предделителя счётчик для смены скважности
        ; и вызов процедуры, если переполнение
int_ShimSkvazhPrescaler
        decf    rShimSkvazhPrescaler,f
        JNZ     int_ShimSkvazhPrescaler_End
        movlw   cShimSkvazhPrescaler
        movwf   rShimSkvazhPrescaler
        call    ShimZakonT1T0
int_ShimSkvazhPrescaler_End
;;;;;
        ;-------------------
ExitInt bcf     INTCON,T0IF     ;сброс флага прерывания по таймеру
        ;восстановление контекста
        movf    rTempSTATUS,w
        movwf   STATUS
        movf    rTempW,w
        btfss   rTempZ,0
        goto    exit_Z0
exit_Z1 bsf     STATUS,Z
        retfie
exit_Z0 bcf     STATUS,Z
        retfie  ;возврат с разрешением прерываний
;------------------------------------------
        end
Версия для печати
Обсудить на форуме