基于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,只是少了外部时钟,多了两个时钟分频值而已。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】博客园携手 AI 驱动开发工具商 Chat2DB 推出联合终身会员
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个适用于 .NET 的开源整洁架构项目模板
· API 风格选对了,文档写好了,项目就成功了一半!
· 【开源】C#上位机必备高效数据转换助手
· .NET 9.0 使用 Vulkan API 编写跨平台图形应用
· MyBatis中的 10 个宝藏技巧!
2020-02-06 Web服务组件boa的安装与配置
2020-02-06 在eagle中如何实现开窗与过孔盖油
2020-02-06 在eagle中如何实现拼板