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

本例讨论ATMega16中通过定时器T2产生脉冲波形(含PWM)的具体过程,利用汇编程序实现CTC方式、快速PWM模式、相位修正PWM模式等实例。定时器T2与定时器T0一样同属于8位定时器,其基础功能与T0完全一样。但T2具有一个很特别的功能,即T2的时钟可以选择为异步方式,这是其他定时器所不具备的。也就是说,T2可以使用额外的低频晶振来提供时钟,而不使用系统晶振提供的时钟。这样就可以让只有8位宽度的T2实现较长时间的定时,也有利于产生标准的秒时钟信号。同时,正是由于T2提供了异步时钟方式,所以它取消了其他定时器具有的外部时钟输入方式,这让T2的时钟预分频选择比其他定时器多出了两项。

1、CTC模式产生脉冲

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

a)使用系统时钟,通过OC2引脚(PD7)输出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     TCCR2, TMP           ;强制OCR2输出0
    SBI     DDRD, 7              ;设置PD7为输出方向
    LDI     TMP, $1C
    OUT     TCCR2, TMP           ;设置定时器T2为64分频,CTC模式,输出引脚取反
    LDI     TMP, $3E        
    OUT     OCR2, TMP            ;设置T2的比较值,输出频率为1kHz
    CLR     TMP
    OUT     TCNT2, TMP           ;计数值清零
LOOP:
    RJMP    LOOP

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

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

.INCLUDE "M16DEF.INC"
.DEF    TMP = R16                ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作)
.DEF    CNT = R17
.CSEG                            ;以下数据放置在Code区域,即Flash中
.ORG    $0000
    JMP     RESET                ;复位的向量入口,地址为$00
.ORG    $0006
    RJMP       TIME2_OCR         ;定时器T2比较匹配中断跳转
.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     TCCR2, TMP           ;强制OCR2输出0
    SBI     DDRD, 7              ;设置PD7为输出方向
    LDI     TMP, $1C
    OUT     TCCR2, TMP           ;设置定时器T2为64分频,CTC模式,输出引脚取反
    LDI     TMP, $3E        
    OUT     OCR2, TMP            ;设置T2的比较值,输出频率为1kHz
    CLR     TMP
    OUT     TCNT2, TMP           ;计数值清零
    MOV     CNT, TMP             ;CNT值清零
    IN      TMP, TIMSK           ;获取原TIMSK的值
    SBR     TMP, $80             ;置第7位为1
    OUT     TIMSK, TMP           ;回写TIMSK,允许定时器T2的比较中断
    IN      TMP, TIFR
    SBR     TMP, $80
    OUT     TIFR, TMP            ;清除比较中断标志
    SEI                          ;打开总中断
LOOP:
    RJMP    LOOP
;Timer2的比较中断服务程序
TIME2_OCR:
    IN      TMP, SREG
    PUSH    TMP                 ;SREG的值压栈
    INC     CNT                 ;计数值加1
    CPI     CNT, 2              ;计数值与2比较
    BRNE    EXIT                ;计数值未到2时直接返回(保证一个脉冲周期的对称性)
    LDI     CNT, $20            ;设置步进值为$20
    IN      TMP, OCR2           ;获取当前比较值
    ADD     TMP, CNT            ;当前比较值加$20,增加的步进为$20
    CPI     TMP, $FE            ;当前比较值与$FE比较
    BRNE    N                   ;若比较值未加到$FE,则跳转去更新比较值
    LDI     TMP, $3E            ;若比较值已加到$FE,则比较值恢复初始值
N:  OUT     OCR2, TMP           ;更新比较值
    CLR     CNT                 ;CNT值恢复为0
EXIT:
    POP     TMP
    OUT     SREG, TMP           ;SREG的值出栈
    RETI                        ;中断返回

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

c)使用异步时钟,通过OC2引脚(PD7)输出1Hz、占空比50%的固定方波。汇编代码如下。

.INCLUDE "M16DEF.INC"
.DEF    TMP = R16                ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作)
.CSEG                            ;以下数据放置在Code区域,即Flash中
.ORG    $0000
    JMP        RESET             ;复位的向量入口,地址为$00
.ORG    $002A                    ;前面最后一个中断向量入口地址是$28,双字后刚好是$2A
RESET:
    LDI     TMP, $A0
    OUT     TCCR2, TMP           ;强制OCR2输出0
    SBI     DDRD, 7              ;设置PD7为输出方向
    LDI     TMP, $08
    OUT     ASSR, TMP            ;设置定时器2为异步时钟模式
W0: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W0                   ;等待ASSR寄存器的低三位为0               
    LDI     TMP, $1D
    OUT     TCCR2, TMP           ;设置为128分频,CTC模式,引脚取反
W1: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W1                   ;等待ASSR寄存器的低三位为0
    LDI     TMP, $80        
    OUT     OCR2, TMP            ;设置频率为1Hz
W2: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W2                   ;等待ASSR寄存器的低三位为0
    CLR     TMP
    OUT     TCNT2, TMP           ;设置计数初始值为0
W3: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W3                   ;等待ASSR寄存器的低三位为0 
LOOP:
    RJMP    LOOP

在PD7引脚上产生的脉冲波形与上面a例中的一致,只是周期变成了1秒。

d)使用异步时钟,通过匹配中断不断修改T2的比较值,实现OC2引脚(PD7)输出频率不断更新(增加)的方波,脉冲占空比仍然保持为50%。汇编代码如下。

.INCLUDE "M16DEF.INC"
.DEF    TMP = R16                ;定义临时寄存器
.DEF    CNT = R17                ;定义计数寄存器
.ORG    $0000
    JMP        RESET             ;复位的向量入口,地址为$00
.ORG    $0006
    RJMP       TIME2_OCR         ;定时器T2比较匹配中断跳转
.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, $A0
    OUT     TCCR2, TMP           ;强制OCR2输出0
    SBI     DDRD, 7              ;设置PD7为输出方向
    CLR     CNT                  ;CNT初始值为0
    LDI     TMP, $08
    OUT     ASSR, TMP            ;设置定时器2为异步时钟模式
W0: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W0                   ;等待ASSR寄存器的低三位为0               
    LDI     TMP, $1D
    OUT     TCCR2, TMP           ;设置为128分频,CTC模式,引脚取反
W1: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W1                   ;等待ASSR寄存器的低三位为0
    LDI     TMP, $80        
    OUT     OCR2, TMP            ;设置频率为1Hz
W2: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W2                   ;等待ASSR寄存器的低三位为0
    CLR     TMP
    OUT     TCNT2, TMP           ;设置计数初始值为0
W3: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W3                   ;等待ASSR寄存器的低三位为0
    IN      TMP, TIMSK           ;获取原TIMSK的值
    SBR     TMP, $80             ;置第7位为1
    OUT     TIMSK, TMP           ;回写TIMSK,允许定时器T2的比较中断
    IN      TMP, TIFR
    SBR     TMP, $80
    OUT     TIFR, TMP            ;清除比较中断标志
    SEI                          ;开启总中断
LOOP:
    RJMP    LOOP
;Timer2比较匹配中断服务程序
TIME2_OCR:
    IN      TMP, SREG
    PUSH    TMP                  ;SREG的值压栈
    INC     CNT                  ;计数值加1
    CPI     CNT, 2    
    BRNE    EXIT                 ;计数值未到2时直接返回(保证一个脉冲周期的对称性)
    CLR     CNT                  ;计数值恢复为0
    IN      TMP, OCR2            ;获取当前比较值
    SUBI    TMP, $0A             ;当前比较值减$10,减少的步进为$10
    CPI     TMP, $08            
    BRNE    NN                   ;若比较值未减到8,则更新比较值
    LDI     TMP, $80             ;若比较值已减到8,则比较值恢复初始值
NN: OUT     OCR2, TMP            ;更新比较值
W4: IN      TMP, ASSR
    CPI     TMP, $08
    BRNE    W4                   ;等待ASSR寄存器的低三位为0             
EXIT:
    POP     TMP
    OUT     SREG, TMP            ;SREG的值出栈
    RETI

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

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

2、快速PWM模式

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

a)下面的实例使用系统时钟,从OC2引脚(PD7)输出频率为488Hz,占空比可变的PWM脉冲,汇编代码如下。

.INCLUDE "M16DEF.INC"
.DEF    TMP = R16                ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作)
.DEF    CNT = R17
.DEF    FLAG = R18
.ORG    $0000
    JMP        RESET             ;复位的向量入口,地址为$00
.ORG    $0006
    RJMP       TIME2_OCR         ;定时器T2比较匹配中断跳转
.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     TCCR2, TMP           ;强制OCR2输出1
    SBI     DDRD, 7              ;设置PD7为输出方向
    LDI     TMP, $6C
    OUT     TCCR2, TMP           ;设置定时器T2为64分频,快速PWM模式,匹配置0,溢出置1
    LDI     TMP, $80             
    OUT     OCR2, TMP            ;设置初始占空比为50%
    IN      TMP, TIMSK           ;获取原TIMSK的值
    SBR     TMP, $80             ;置第7位为1
    OUT     TIMSK, TMP           ;回写TIMS,允许定时器T2的比较中断
    IN      TMP, TIFR
    SBR     TMP, $80
    OUT     TIFR, TMP            ;清除比较中断标志
    SEI                          ;开启总中断
    CLR     CNT                  ;计数值清零
    MOV     FLAG, CNT            ;标志位清零
LOOP:
    RJMP    LOOP
;Timer2比较匹配中断服务程序
TIME2_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, OCR2            ;获取当前比较值
    DEC     TMP                  ;减1
    CPI     TMP, $10             ;是否减到$10
    BRNE    NEXT                 ;未到$10则更新比较值后返回
    LDI     FLAG, $00            ;已到$10则表示到最底,置标志位为0
    OUT     OCR2, TMP            ;更新比较值一次比较值后返回
    POP     TMP
    OUT     SREG, TMP            ;SREG的值出栈
    RETI
AHEAD:
    IN      TMP, OCR2            ;获取当前比较值
    INC     TMP                  ;加1
    CPI     TMP, $F0             ;是否加到$F0
    BRNE    NEXT                 ;未到$F0则更新比较值后返回
    LDI     FLAG, $01            ;已到$F0则表示到最高,置标志位为1
NEXT:
    OUT     OCR2, TMP            ;更新比较值
EXIT:
    POP     TMP
    OUT     SREG, TMP            ;SREG的值出栈
    RETI

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

b)下面的实例使用异步时钟,从OC2引脚(PD7)输出频率为128Hz,占空比可变的PWM脉冲,汇编代码如下。

.INCLUDE "M16DEF.INC"
.DEF    TMP = R16                ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作)
.DEF    CNT = R17
.DEF    FLAG = R18
.ORG    $0000
    JMP        RESET             ;复位的向量入口,地址为$00
.ORG    $0006
    RJMP       TIME2_OCR         ;定时器T2比较匹配中断跳转
.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     TCCR2, TMP           ;强制OCR2输出1
    SBI     DDRD, 7              ;设置PD7为输出方向
    LDI     TMP, $08
    OUT     ASSR, TMP            ;设置定时器2为异步时钟模式
W0: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W0                   ;等待ASSR寄存器的低三位为0               
    LDI     TMP, $69
    OUT     TCCR2, TMP           ;设置定时器T2为1分频,快速PWM模式,匹配置0,溢出置1
W1: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W1                   ;等待ASSR寄存器的低三位为0
    LDI     TMP, $80        
    OUT     OCR2, TMP            ;设置初始占空比为50%
W2: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W2                   ;等待ASSR寄存器的低三位为0
    CLR     TMP
    OUT     TCNT2, TMP           ;设置计数初始值为0
W3: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W3                   ;等待ASSR寄存器的低三位为0
    IN      TMP, TIMSK           ;获取原TIMSK的值
    SBR     TMP, $80             ;置第7位为1
    OUT     TIMSK, TMP           ;回写TIMS,允许定时器T2的比较中断
    IN      TMP, TIFR
    SBR     TMP, $80
    OUT     TIFR, TMP            ;清除比较中断标志
    SEI                          ;开启总中断
    CLR     CNT                  ;计数值清零
    MOV     FLAG, CNT            ;标志位清零
LOOP:
    RJMP    LOOP
;Timer2比较匹配中断服务程序
TIME2_OCR:
    IN      TMP, SREG
    PUSH    TMP                  ;SREG的值压栈
    INC     CNT
    CPI     CNT, $02             
    BRNE    EXIT                 ;计数不到10次则直接返回,用于占空比变化速度控制
    CLR     CNT                  ;到10次先清零
    CPI     FLAG, $01
    BRNE    AHEAD                ;标志位为1则执行减操作(减小占空比),为0则执行加操作(增加占空比)
    IN      TMP, OCR2            ;获取当前比较值
    DEC     TMP                  ;减1
    CPI     TMP, $10             ;是否减到$10
    BRNE    NEXT                 ;未到$10则更新比较值后返回
    LDI     FLAG, $00            ;已到$10则表示到最底,置标志位为0
    OUT     OCR2, TMP            ;更新比较值一次比较值后返回
    POP     TMP
    OUT     SREG, TMP            ;SREG的值出栈
    RETI
AHEAD:
    IN      TMP, OCR2            ;获取当前比较值
    INC     TMP                  ;加1
    CPI     TMP, $F0             ;是否加到$F0
    BRNE    NEXT                 ;未到$F0则更新比较值后返回
    LDI     FLAG, $01            ;已到$F0则表示到最高,置标志位为1
NEXT:
    OUT     OCR2, TMP            ;更新比较值
EXIT:
    POP     TMP
    OUT     SREG, TMP            ;SREG的值出栈
    RETI

在PD7引脚上产生的PWM波形与上面使用系统时钟的一致,只是周期变长了。

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

3、相位修正PWM模式

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

a)下面的实例使用系统时钟,从OC2引脚(PD7)输出频率约为245Hz,占空比可变的PWM脉冲,汇编代码如下。

.INCLUDE "M16DEF.INC"
.DEF    TMP = R16                ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作)
.DEF    CNT = R17
.DEF    FLAG = R18
.ORG    $0000
    JMP        RESET             ;复位的向量入口,地址为$00
.ORG    $0008
    RJMP       TIME2_OVF         ;定时器T2溢出中断跳转
.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     TCCR2, TMP           ;强制OCR2输出1
    SBI     DDRD, 7              ;设置PD7为输出方向
    LDI     TMP, $64
    OUT     TCCR2, TMP           ;设置定时器T2为64分频,相位修正PWM模式,上升匹配置0,下降匹配置1
    LDI     TMP, $10             
    OUT     OCR2, TMP            ;设置初始占空比为50%
    IN      TMP, TIMSK           ;获取原TIMSK的值
    SBR     TMP, $40             ;置第6位为1
    OUT     TIMSK, TMP           ;回写TIMS,允许定时器T2的溢出中断
    IN      TMP, TIFR
    SBR     TMP, $40
    OUT     TIFR, TMP            ;清除比较中断标志
    SEI                          ;开启总中断
    CLR     CNT                  ;计数值清零
    MOV     FLAG, CNT            ;标志位清零
LOOP:
    RJMP    LOOP
;Timer2溢出中断服务程序
TIME2_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, OCR2           ;获取当前比较值
    DEC     TMP                 ;减1
    CPI     TMP, $10            ;是否减到$10
    BRNE    NEXT                ;未到$10则更新比较值后返回
    LDI     FLAG, $00           ;已到$10则表示到最底,置标志位为0
    OUT     OCR2, TMP           ;更新比较值一次比较值后返回
    POP     TMP
    OUT     SREG, TMP           ;SREG的值出栈
    RETI
AHEAD:
    IN      TMP, OCR2           ;获取当前比较值
    INC     TMP                 ;加1
    CPI     TMP, $F0            ;是否加到$F0
    BRNE    NEXT                ;未到$F0则更新比较值后返回
    LDI     FLAG, $01           ;已到$F0则表示到最高,置标志位为1
NEXT:
    OUT     OCR2, TMP           ;更新比较值
EXIT:
    POP     TMP
    OUT     SREG, TMP           ;SREG的值出栈
    RETI

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

b)下面的实例使用异步时钟,从OC2引脚(PD7)输出频率为64Hz,占空比可变的PWM脉冲,汇编代码如下。

.INCLUDE "M16DEF.INC"
.DEF    TMP = R16                ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作)
.DEF    FLAG = R17
.DEF    NUM = R18
.DEF    CNT = R19
.ORG    $0000
    JMP        RESET             ;复位的向量入口,地址为$00
.ORG    $0008
    RJMP       TIME2_OVF         ;定时器T2溢出中断跳转
.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     TCCR2, TMP           ;强制OCR2输出1
    SBI     DDRD, 7              ;设置PD7为输出方向
    LDI     TMP, $08
    OUT     ASSR, TMP            ;设置定时器2为异步时钟模式
W0: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W0                   ;等待ASSR寄存器的低三位为0
    LDI     TMP, $61
    OUT     TCCR2, TMP           ;设置定时器T2为1分频,相位修正PWM模式,上升匹配置0,下降匹配置1
W1: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W1                   ;等待ASSR寄存器的低三位为0
    LDI     TMP, $10             
    OUT     OCR2, TMP            ;设置占空比为50%
W2: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W2                   ;等待ASSR寄存器的低三位为0
    IN      TMP, TIMSK           ;获取原TIMSK的值
    SBR     TMP, $40             ;置第6位为1
    OUT     TIMSK, TMP           ;回写TIMS,允许定时器T2溢出中断
    SEI                          ;开启总中断
    CLR     FLAG                 ;标志位清零
    CLR     CNT
    LDI     NUM, $10             ;增减步长为$10
LOOP:
    RJMP    LOOP
;Timer2溢出中断服务程序
TIME2_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, OCR2           ;获取当前比较值
    SUB     TMP, NUM            ;减$10
    CPI     TMP, $10            ;是否减到$10
    BRNE    NEXT                ;未到$10则更新比较值后返回
    LDI     FLAG, $00           ;已到$10则表示到最底,置标志位为0
    OUT     OCR2, TMP           ;更新比较值一次比较值后返回
W3: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W3                  ;等待ASSR寄存器的低三位为0
    POP     TMP
    OUT     SREG, TMP            ;SREG的值出栈
    RETI
AHEAD:
    IN      TMP, OCR2           ;获取当前比较值
    ADD     TMP, NUM            ;加$10
    CPI     TMP, $F0            ;是否加到$F0
    BRNE    NEXT                ;未到$F0则更新比较值后返回
    LDI     FLAG, $01           ;已到$F0则表示到最高,置标志位为1
NEXT:
    OUT     OCR2, TMP           ;更新比较值
W4: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W4                  ;等待ASSR寄存器的低三位为0
EXIT:
    POP     TMP
    OUT     SREG, TMP            ;SREG的值出栈
    RETI

在PD7引脚上产生的PWM波形与上面使用系统时钟的一致,只是周期变长了。 

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

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

本部分使用到了AVR中I/O空间的8个寄存器,即SPH、SPL、DDRD、PORTD、TCNT2、TCCR2、ASSR和TIMSK。其中SPH、SPL、DDRD、PORTD等4个寄存器的介绍可参考“基于ATMega16的流水灯实例”一文,TIMSK寄存器可参考“基于ATMega16的数码管动态扫描实例”一文。TCNT2寄存器与TCNT0寄存器的位结构完全一样,可参考“基于ATMega16的数码管时钟显示实例”一文,ASSR寄存器也可参考该文。TCCR2寄存器与TCCR0寄存器的位结构基本一样,只是其低3位的配置(分频配置)情况稍有不同,具体如下所示。

与T0的主要区别是,在T2中取消了外部时钟输入,增加了32和128两个时钟分频值。

本章节中的汇编程序一共使用到了18种指令,其中的JMP、RJMP、LDI、OUT、DEC、BRNE、PUSH、POP等8条指令可参考“基于ATMega16的流水灯实例”一文,SEI、CLR、INC、RETI、ADD等5条指令可参考“基于ATMega16的数码管动态扫描实例”一文,IN、CPI等2条指令可参考“基于ATMega16的数码管时钟显示实例”一文。SBI、SBR、MOV等3条指令可参考“基于ATMega16定时器T0产生PWM的实例”一文。此外,程序中用到的伪指令.INCLUDE、.DEF、.ORG也可参考“基于ATMega16的流水灯实例”一文。

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

1)如果只产生脉冲波形,而不用调整占空比,或要产生一些非标准的脉冲波形,选用CTC方式较为方便。
2)如果只产生占空比可调整的PWM波,则选择快速PWM或相位修正PWM模式较为方便。如果要求PWM的频率较低可选择相位修正PWM方式,如果要求PWM的频率可调则选择快速PWM方式。
3)如果不使用异步时钟功能,则T2相较于T0,只是少了外部时钟,多了两个时钟分频值而已。

posted @ 2024-02-06 00:00  fxzq  阅读(80)  评论(0编辑  收藏  举报