基于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方式。