基于ATMega16定时器T0产生PWM的实例(汇编)

本例讨论ATMega16中通过定时器T0产生脉冲波形(含PWM)的具体过程,利用汇编程序实现CTC方式、快速PWM模式、相位修正PWM模式等实例。

1、CTC模式产生脉冲

CTC方式通过对计数值的比较来实现引脚电平的变化。当计数值增加到与比较值相等时,可触发中断,并可在OC0引脚(PB3)上实现电平的变低、变高或取反。下面两个实例分别实现固定频率脉冲(无中断)输出和可变频率脉冲(使用中断)输出。

a)通过OC0引脚(PB3)输出1kHz、占空比50%的固定方波。汇编代码如下。

.INCLUDE "M16DEF.INC"
.DEF    TMP = R16                ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作)
.CSEG                            ;以下数据放置在Code区域,即Flash中
.ORG    $0000
    JMP     RESET                ;复位的向量入口,地址为$00
.ORG    $002A                    ;避开中断向量入口地址,主程序入口地址为$2A
RESET:
    LDI     TMP, $A0
    OUT     TCCR0, TMP           ;强制OCR0输出0
    SBI     DDRB, 3              ;设置PB3为输出方向
    LDI     TMP, $1B
    OUT     TCCR0, TMP           ;设置定时器T0为64分频,CTC模式,输出引脚取反
    LDI     TMP, $3E        
    OUT     OCR0, TMP            ;设置T0的比较值,输出频率为1kHz
    CLR     TMP
    OUT     TCNT0, TMP           ;计数值清零 
LOOP:
    RJMP    LOOP

在PB3引脚上产生的脉冲波形如下图所示。

b)通过匹配中断不断修改T0的比较值,实现OC0引脚(PB3)输出频率不断更新(降低)的方波,脉冲占空比仍然保持为50%。汇编代码如下。

.INCLUDE "M16DEF.INC"
.DEF    TMP = R16                ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作)
.DEF    CNT = R17
.CSEG                            ;以下数据放置在Code区域,即Flash中
.ORG    $0000
    JMP     RESET                ;复位的向量入口,地址为$00
.ORG    $0026
    RJMP       TIME0_OCR         ;定时器T0比较匹配中断跳转
.ORG    $002A                    ;避开中断向量入口地址,主程序入口地址为$2A
RESET:
    LDI     TMP, HIGH(RAMEND)    ;获取RAM空间的最大地址高字节(ATMega16为$04)
    OUT     SPH, TMP             ;高字节送SP高位
    LDI     TMP, LOW(RAMEND)     ;获取RAM空间的最大地址低字节(ATMega16为$5F)
    OUT     SPL, TMP             ;低字节送SP低位
    LDI     TMP, $A0
    OUT     TCCR0, TMP           ;强制OCR0输出0
    SBI     DDRB, 3              ;设置PB3为输出方向
    LDI     TMP, $1B
    OUT     TCCR0, TMP           ;设置定时器T0为64分频,CTC模式,输出引脚取反
    LDI     TMP, $3E        
    OUT     OCR0, TMP            ;设置T0的比较值,输出频率为1kHz
    CLR     TMP
    OUT     TCNT0, TMP           ;计数值清零
    MOV     CNT, TMP             ;CNT值清零
    IN      TMP, TIMSK           ;获取原TIMSK的值
    SBR     TMP, $02             ;置第1位为1
    OUT     TIMSK, TMP           ;回写TIMSK,允许定时器T0的比较中断
    IN      TMP, TIFR
    SBR     TMP, $02
    OUT     TIFR, TMP            ;清除比较中断标志
    SEI                          ;打开总中断
LOOP:
    RJMP    LOOP
;Timer0的比较中断服务程序
TIME0_OCR:
    IN      TMP, SREG
    PUSH    TMP                 ;SREG的值压栈
    INC     CNT                 ;计数值加1
    CPI     CNT, 2              ;计数值与2比较
    BRNE    EXIT                ;计数值未到2时直接返回(保证一个脉冲周期的对称性)
    LDI     CNT, $20            ;设置步进值为$20
    IN      TMP, OCR0           ;获取当前比较值
    ADD     TMP, CNT            ;当前比较值加$20,增加的步进为$20
    CPI     TMP, $FE            ;当前比较值与$FE比较
    BRNE    N                   ;若比较值未加到$FE,则跳转去更新比较值
    LDI     TMP, $3E            ;若比较值已加到$FE,则比较值恢复初始值
N:  OUT     OCR0, TMP           ;更新比较值
    CLR     CNT                 ;CNT值恢复为0
EXIT:
    POP     TMP
    OUT     SREG, TMP           ;SREG的值出栈
    RETI                        ;中断返回

在PB3引脚上产生的脉冲波形如下图所示。

下面对程序中的相关部分进行一下说明。
1)在CTC模式下,定时器T0通过OCR0寄存器预设比较值,当定时器计数值(TCNT0中的值)达到比较值时,发生匹配事件。这时可实现对引脚PB3上的电平进行操作,具体的操作方式由寄存器TCCR0来配置,可以有引脚置低电平、引脚置高电平和引脚电平取反等3种形式。
2)OC0引脚固定为T0的输出引脚,它复用在PB3引脚上,不能使用其他引脚。
3)计数值匹配之后TCNT0会自动清零,反复循环,若不使用比较中断,整个CTC过程无需CPU干预。
4)当不使用比较中断时,由于无法更改比较值,所以只能实现固定频率的脉冲输出。使用比较中断时,可在中断服务程序中更新比较值,从而实现对输出频率的更改。
5)在初始化阶段,通过强制写TCCR0寄存器的第7位(FOC0),并配置为匹配时输出低电平的方式,把OCR0强制置低电平,即脉冲的起始为低电平(也可为高电平,看需求设置)。
6)在初始化阶段,应该先配置OCR0的输出电平,再配置PB3为输出方向。
7)在比较匹配中断服务程序中,对状态寄存器SREG的值采取了栈保护(一般中断程序都应如此)。
8)为了可以看到明显的脉冲宽度变化,上述程序中,对比较值的变化步长取$20(即32),一共可比较6次,在引脚PB3上可看到6个不同宽度的脉冲。
9)为了保证输出脉冲在每个完整周期内的占空比均为50%,在中断服务程序中加入了比较判断,以保证每两次比较中断之后才更改一次比较值。所以严格来说,CTC并不属于PWM方式,但这种方式更灵活。

2、快速PWM模式

快速PWM模式也是通过对计数值的比较来实现引脚电平的变化,但与CTC方式不同的是,CTC方式在比较匹配后计数值就清零了,而快速PWM模式在比较匹配之后,计数值依然增加,直到溢出后才清零。在匹配时输出引脚置低(或高)电平,在溢出时输出引脚置高(或低)电平。这样每次计数值从初始值到溢出就成为一个完整的脉冲周期,而比较值可在初始值到溢出值之间移动,方便更改脉冲的占空比。

下面的实例从OC0引脚(PB3)输出频率约为488Hz,占空比可变的PWM脉冲,汇编代码如下。

.INCLUDE "M16DEF.INC"
.DEF    TMP = R16                ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作)
.DEF    CNT = R17
.DEF    FLAG = R18
.ORG    $0000
    JMP        RESET             ;复位的向量入口,地址为$00
.ORG    $0026
    RJMP       TIME0_OCR         ;定时器T0比较匹配中断跳转
.ORG    $002A                    ;前面最后一个中断向量入口地址是$28,双字后刚好是$2A
RESET:
    LDI     TMP, HIGH(RAMEND)    ;获取RAM空间的最大地址高字节(ATMega16为$04)
    OUT     SPH, TMP             ;高字节送SP高位
    LDI     TMP, LOW(RAMEND)     ;获取RAM空间的最大地址低字节(ATMega16为$5F)
    OUT     SPL, TMP             ;低字节送SP低位
    LDI     TMP, $B0             
    OUT     TCCR0, TMP           ;强制OCR0输出1
    SBI     DDRB, 3              ;设置PB3为输出方向
    LDI     TMP, $6B
    OUT     TCCR0, TMP           ;设置定时器T0为64分频,快速PWM模式,匹配置0,溢出置1
    LDI     TMP, $80             
    OUT     OCR0, TMP            ;设置初始占空比为50%
    IN      TMP, TIMSK           ;获取原TIMSK的值
    SBR     TMP, $02             ;置第1位为1
    OUT     TIMSK, TMP           ;回写TIMS,允许定时器T0的比较中断
    IN      TMP, TIFR
    SBR     TMP, $02
    OUT     TIFR, TMP            ;清除比较中断标志
    SEI                          ;开启总中断
    CLR     CNT                  ;计数值清零
    MOV     FLAG, CNT            ;标志位清零
LOOP:
    RJMP    LOOP
;Timer0比较匹配中断服务程序
TIME0_OCR:
    IN      TMP, SREG
    PUSH    TMP                 ;SREG的值压栈
    INC     CNT
    CPI     CNT, $0A
    BRNE    EXIT                ;计数不到10次则直接返回,用于占空比变化速度控制
    CLR     CNT                 ;到10次先清零
    CPI     FLAG, $01
    BRNE    AHEAD               ;标志位为1则执行减操作(减小占空比),为0则执行加操作(增加占空比)
    IN      TMP, OCR0           ;获取当前比较值
    DEC     TMP                 ;减1
    CPI     TMP, $10            ;是否减到$10
    BRNE    NEXT                ;未到$10则更新比较值后返回
    LDI     FLAG, $00           ;已到$10则表示到最底,置标志位为0
    OUT     OCR0, TMP           ;更新比较值一次比较值后返回
    POP     TMP
    OUT     SREG, TMP           ;SREG的值出栈
    RETI
AHEAD:
    IN      TMP, OCR0           ;获取当前比较值
    INC     TMP                 ;加1
    CPI     TMP, $F0            ;是否加到$F0
    BRNE    NEXT                ;未到$F0则更新比较值后返回
    LDI     FLAG, $01           ;已到$F0则表示到最高,置标志位为1
NEXT:
    OUT     OCR0, TMP           ;更新比较值
EXIT:
    POP     TMP
    OUT     SREG, TMP           ;SREG的值出栈
    RETI

在PB3引脚上产生的PWM波形如下所示,图片为占空比变化截取的其中两张,可明显看出占空比的不同(频率相同)。

下面对程序中的相关部分进行一下说明。
1)在快速PWM模式下,定时器T0通过OCR0寄存器来设置比较值,用于调整脉冲的占空比。当定时器计数值(TCNT0中的值)达到比较值时,发生匹配事件,这时可对引脚PB3上的电平进行操作。然后计数值继续增加直到溢出,溢出时也可对引脚PB3上的电平进行操作。引脚电平具体的操作方式由寄存器TCCR0来配置,可以选择在匹配时引脚置低电平、溢出时引脚置高电平,或相反。
2)OC0引脚固定为T0的PWM输出引脚,它复用在PB3引脚上,不能使用其他引脚。
3)计数值匹配之后TCNT0会继续增加直到溢出,如此反复循环,若不使用比较中断和溢出中断,整个PWM过程无需CPU干预。
4)虽然可以在溢出中断中重载TCNT0的值,但并不建议使用这种方法来调整PWM的周期(频率),因为在对TCNT0进行写入后,会屏蔽一个比较周期,这会导致PWM出现杂散波形。事实上T0在快速PWM模式下,只能通过改变时钟分频值来改变PWM的周期,也即在分频确定后,PWM的周期就被固定了。
5)如果要更新比较值(OCR0的值),操作可放在比较中断服务程序中进行,但更新的值不会立即生效,而是要等到此次计数溢出之后,才会生效,这样能保证波形的对称性。
6)为了可以在示波器上看到明显的脉冲占空比的变化,上述程序中,对比较值的更新设置在触发比较中断10次(即$0A)之后再进行。这样可有效地减慢占空比的变化过程,在引脚PB3上可看到PWM的占空比不继增加,又不继减小,如此往复循环。

3、相位修正PWM模式 

相位修正PWM模式也是通过对计数值的比较来实现引脚电平的变化,但与快速PWM模式不同的是,相位修正PWM模式不仅使用了计数的增加部分(上升段),同时使用了计数的减小部分(下降段)。即当TCNT0的值增加到最大值($FF)后,计数值并不清零,而是又逐渐减小,直到最小值($00),然后又增加,如此循环。而在上升段和下降段均可与OCR0进行比较匹配,匹配时引脚PB3上的电平都可进行操作。在计数回到最小值($00)时,可触发溢出中断。在比较匹配时,可触发比较中断。

下面的实例从OC0引脚(PB3)输出频率约为244Hz,占空比可变的PWM脉冲,汇编代码如下。

.INCLUDE "M16DEF.INC"
.DEF    TMP = R16                ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作)
.DEF    CNT = R17
.DEF    FLAG = R18
.ORG    $0000
    JMP        RESET             ;复位的向量入口,地址为$00
.ORG    $0012
    RJMP       TIME0_OVF         ;定时器T0溢出中断跳转
.ORG    $002A                    ;前面最后一个中断向量入口地址是$28,双字后刚好是$2A
RESET:
    LDI     TMP, HIGH(RAMEND)    ;获取RAM空间的最大地址高字节(ATMega16为$04)
    OUT     SPH, TMP             ;高字节送SP高位
    LDI     TMP, LOW(RAMEND)     ;获取RAM空间的最大地址低字节(ATMega16为$5F)
    OUT     SPL, TMP             ;低字节送SP低位
    LDI     TMP, $B0
    OUT     TCCR0, TMP           ;强制OCR0输出1
    SBI     DDRB, 3              ;设置PB3为输出方向
    LDI     TMP, $63
    OUT     TCCR0, TMP           ;设置定时器T0为64分频,相位修正PWM模式,上升匹配置0,下降匹配置1
    LDI     TMP, $10             
    OUT     OCR0, TMP            ;设置占空比为50%
    IN      TMP, TIMSK           ;获取原TIMSK的值
    SBR     TMP, $01             ;置第0位为1
    OUT     TIMSK, TMP           ;回写TIMS,允许定时器T0的溢出中断
    IN      TMP, TIFR
    SBR     TMP, $01
    OUT     TIFR, TMP            ;清除比较中断标志
    SEI                          ;开启总中断    
    CLR     CNT                  ;计数值清零
    MOV     FLAG, CNT            ;标志位清零
LOOP:
    RJMP    LOOP
;Timer0溢出中断服务程序
TIME0_OVF:
    IN      TMP, SREG
    PUSH    TMP                 ;SREG的值压栈
    INC     CNT
    CPI     CNT, $04
    BRNE    EXIT                ;计数不到4次则直接返回,用于占空比变化速度控制
    CLR     CNT                 ;到4次先清零
    CPI     FLAG, $01
    BRNE    AHEAD               ;标志位为1则执行减操作(减小占空比),为0则执行加操作(增加占空比)
    IN      TMP, OCR0           ;获取当前比较值
    DEC     TMP                 ;减1
    CPI     TMP, $10            ;是否减到$10
    BRNE    NEXT                ;未到$10则更新比较值后返回
    LDI     FLAG, $00           ;已到$10则表示到最底,置标志位为0
    OUT     OCR0, TMP           ;更新比较值一次比较值后返回
    POP     TMP
    OUT     SREG, TMP           ;SREG的值出栈
    RETI
AHEAD:
    IN      TMP, OCR0           ;获取当前比较值
    INC     TMP                 ;加1
    CPI     TMP, $F0            ;是否加到$F0
    BRNE    NEXT                ;未到$F0则更新比较值后返回
    LDI     FLAG, $01           ;已到$F0则表示到最高,置标志位为1
NEXT:
    OUT     OCR0, TMP           ;更新比较值
EXIT:
    POP     TMP
    OUT     SREG, TMP           ;SREG的值出栈
    RETI

在PB3引脚上产生的PWM波形如下所示,图片为占空比变化截取的其中两张,可明显看出占空比的不同(频率相同)。对比快速PWM模式的图片可以看出,相位修正PWM模式的周期要更长一些,即频率要低一些。 

下面对程序中的相关部分进行一下说明。
1)在相位修正PWM模式下,定时器计数值(TCNT0中的值)从0开始逐渐增加到最大值($FF),然后又逐渐减小,直到最小值0。即在相位修正模式下,计数过程形成了“双斜坡”,所以产生的PWM频率要比快速PWM模式的低。
2)在斜坡的上升段,如果计数值(TCNT0中的值)与比较值(OCR0中的值)匹配成功,可对引脚PB3上的电平进行操作。在斜坡的下降段,如果计数值(TCNT0中的值)与比较值(OCR0中的值)匹配成功,也可对引脚PB3上的电平进行操作。引脚电平具体的操作方式由寄存器TCCR0来配置,可以选择在上升段匹配时引脚置低电平,在下降段匹配时引脚置高电平,或者相反。所以,在一个双斜坡周期内,会有两次比较,可通过比较值来调整PWM的占空比。
3)OC0引脚固定为T0的PWM输出引脚,它复用在PB3引脚上,不能使用其他引脚。
4)在匹配成功时可触发比较中断,在计数值减小到0时可触发溢出中断。若不使用比较中断和溢出中断,则整个PWM过程无需CPU干预。
5)和快速PWM模式下一样,相位修正PWM模式也不能修改PWM的周期,PWM的周期只取决于时钟的分频值。
6)如果要更新比较值(OCR0的值),操作可放在溢出中断服务程序中进行,但更新的值不会立即生效,而是要等到此次计数值达到最大值($FF)之后,才会更新,这样能保证PWM波形的对称性。
7)所谓的相位修正,其实就是相位可调,由于OCR0的值可以更改,所以脉冲的相位也就随之更改了。但如果只是单个PWM输出,则相位修正没有太大的实际意义,可认为仅仅比快速模式降低了频率而已。
8)为了可以在示波器上看到明显的脉冲占空比的变化,上述程序中,对比较值的更新设置在触发比较中断4次之后再进行。这样可减慢占空比的变化过程,在引脚PB3上可看到PWM的占空比不继增加,又不继减小,如此往复循环。

下面来讨论一下以上所有程序代码中用到的寄存器。

本部分使用到了AVR中I/O空间的7个寄存器,即SPH、SPL、DDRB、PORTB、TCNT0、TCCR0和TIMSK。其中SPH、SPL、DDRB、PORTB等4个寄存器的介绍可参考“基于ATMega16的流水灯实例”一文,TIMSK寄存器可参考“基于ATMega16的数码管动态扫描实例”一文,TCNT0寄存器可参考“基于ATMega16的数码管时钟显示实例”一文。
下面来讨论一下TCCR0寄存器,它是定时器T0的控制寄存器,初始值为全0。这个寄存器在“基于ATMega16的数码管时钟显示实例”一文中提到过,这里整体来看一下,如下表所示。

TCCR0的最高位(FOC0)是强制输出比较位,用来在非PWM模式(CTC模式)下强制形成一次匹配事件,在PWM模式下该位必须写0。T0的时钟的分频由低3位(CS00~CS02)来确定,它与定时器T1中的CS10~CS12完全一样,具体可参考“基于ATMega16的数码管动态扫描实例”一文中的相关部分。TCCR0的第3、6两位(WGM01 、WGM00)用来确定T0的工作模式,具体如下表所示。

本章节重点讨论定时器T0的模式1、2、3,具体用法可对照着前面的内容查看。TCCR0的第4、5两位(COM00、COM01)用来确定OCR0引脚的输出电平,共分三种情况,下面3张表分别给出了在CTC模式下、快速PWM模式下及相位修正PWM模式下这两位的配置情况,具体可对照前面的内容查看。

本章节中的汇编程序一共使用到了18种指令,其中的JMP、RJMP、LDI、OUT、DEC、BRNE、PUSH、POP等8条指令可参考“基于ATMega16的流水灯实例”一文,SEI、CLR、INC、RETI、ADD等5条指令可参考“基于ATMega16的数码管动态扫描实例”一文,IN、CPI等2条指令可参考“基于ATMega16的数码管时钟显示实例”一文。剩余3条指令解释如下。

1)I/O寄存器指定位置位
  SBI   A, b    0 ≤ A ≤ 31,0 ≤ b ≤ 7
说明:将指定的I/O寄存器某一位置1,这条指令只用于低32位的I/O寄存器操作,I/O寄存器地址为0~31。
操作:I/O(A, b) ←1    PC ← PC + 1   16位机器码:1001 1010 AAAA Abbb
2)置寄存器位
  SBR   Rd, K    16 ≤ d ≤ 31,0 ≤ K ≤ 255
说明:将寄存器Rd指定位置1,将寄存器Rd的内容和常量掩码K取逻辑或,并将结果保存到目的寄存器Rd中。
操作:Rd ← Rd Ⅴ K    PC ← PC + 1   16位机器码:0110 KKKK dddd KKKK
3)工作寄存器间传递数据
  MOV   Rd, Rr    0 ≤ d ≤ 31,0 ≤ r ≤ 31
说明:将一个寄存器中的数据传送至另一个寄存器中,源寄存器中的数据不变,目的寄存器中则复制了源寄存器中的数据。
操作:Rd ← Rr    PC ← PC + 1   16位机器码:0010 11rd dddd rrrr

另外,程序中用到的伪指令.INCLUDE、.DEF、.ORG可参考“基于ATMega16的流水灯实例”一文。

最后,总的来归纳一下定时器T0产生PWM波形的情况。

1)如果只产生脉冲波形,而不用调整占空比,或要产生一些非标准的脉冲波形,选用CTC方式较为方便。
2)如果只产生占空比可调整的PWM波,则选择快速PWM或相位修正PWM模式较为方便。如果要求PWM的频率较低可选择相位修正PWM方式。

posted @ 2024-02-03 23:59  fxzq  阅读(100)  评论(0编辑  收藏  举报