基于ATMega16定时器T1产生PWM的实例(汇编)
本例讨论ATMega16中通过定时器T1产生脉冲波形(含PWM)的具体过程,利用汇编程序实现CTC方式、快速PWM模式、相位修正PWM模式和相频修正PWM模式等实例。定时器T1与定时器T0、T2不一样,它具有16位结构,除了能实现更长时间的定时外,它还具有很多附加功能,比T0、T2要复杂一些。另外,T1还有一个很特别的地方,就是它具有两个独立输出的匹配单元,可同时产生两路不同占空比的PWM波。
1、CTC模式产生脉冲
CTC方式通过对计数值的比较来实现引脚电平的变化。当计数值增加到与比较值相等时,可触发中断,并可在OC1A引脚(PD5)、OC1B引脚(PD4)上实现电平的变低、变高或取反。下面2个实例分别实现了使用系统时钟的固定频率脉冲(无中断)输出和可变频率脉冲(使用中断)输出。
a)通过OC1A引脚(PD5)和OC1B(PD4)输出1kHz、占空比50%的固定方波。汇编代码如下。
.INCLUDE "M16DEF.INC" .DEF TMP = R16 ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作) .DEF TMP1 = R17 .ORG $0000 JMP RESET ;复位的向量入口,地址为$00 .ORG $002A ;避开中断向量入口地址,主程序入口地址为$2A RESET: LDI TMP, $08 LDI TMP1, $AC OUT TCCR1B, TMP OUT TCCR1A, TMP1 ;OC1A及OC1B强制输出0 SBI DDRD, 4 ;设置PD4为输出方向 SBI DDRD, 5 ;设置PD5为输出方向 LDI TMP, $50 LDI TMP1, $1B ; LDI TMP1, $09 OUT TCCR1A, TMP ;设置输出引脚取反 OUT TCCR1B, TMP1 ;设置定时器T1为64分频,CTC模式 CLR TMP LDI TMP1, $3E ;设置定时器T1的初始比较值为$3E,即脉冲初始频率为1kHz OUT ICR1H, TMP OUT ICR1L, TMP1 ; OUT OCR1AH, TMP ; OUT OCR1AL, TMP1 CLR TMP OUT TCNT1H, TMP OUT TCNT1L, TMP ;计数值清零 LOOP: RJMP LOOP
在PD5和PD4引脚上产生的脉冲波形如下图所示。
b)通过匹配中断不断修改T1的比较值,实现OC1A引脚(PD5)和OC1B(PD4)输出频率不断更新(降低)的方波,脉冲占空比仍然保持为50%。汇编代码如下。
.INCLUDE "M16DEF.INC" .DEF TMP = R16 ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作) .DEF TMP1 = R17 .DEF CNT = R18 .CSEG ;以下数据放置在Code区域,即Flash中 .ORG $0000 JMP RESET ;复位的向量入口,地址为$00 ;.ORG $000A ; RJMP TIME1_OCR ;ICR1比较匹配中断跳转 .ORG $000C RJMP TIME1_OCR ;OCR1A比较匹配中断跳转 .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, $08 LDI TMP1, $AC OUT TCCR1B, TMP OUT TCCR1A, TMP1 ;OC1A及OC1B强制输出0 SBI DDRD, 4 ;设置PD4为输出方向 SBI DDRD, 5 ;设置PD5为输出方向 LDI TMP, $50 ;设置输出引脚取反 LDI TMP1, $09 ;设置定时器T1为64分频,OCR1A比较的CTC模式 ; LDI TMP, $50 ;设置输出引脚取反 ; LDI TMP1, $19 ;设置定时器T1为64分频,ICR1比较的CTC模式 OUT TCCR1A, TMP OUT TCCR1B, TMP1 CLR TMP LDI TMP1, $3E ;设置定时器T1的初始比较值为$3E,即脉冲初始频率为1kHz OUT OCR1AH, TMP OUT OCR1AL, TMP1 ; OUT ICR1H, TMP ; OUT ICR1L, TMP1 CLR TMP OUT TCNT1H, TMP OUT TCNT1L, TMP ;计数值清零 IN TMP, TIMSK ;获取原TIMSK的值 SBR TMP, $10 ;置第4位为1 ; SBR TMP, $20 ;置第5位为1 OUT TIMSK, TMP ;回写TIMSK,允许OCR1A比较中断 IN TMP, TIFR SBR TMP, $10 ; SBR TMP, $20 OUT TIFR, TMP ;清除比较中断标志 SEI ;打开总中断 CLR CNT LOOP: RJMP LOOP ;OCR1A或ICR1比较中断服务程序 TIME1_OCR: IN TMP, SREG PUSH TMP ;SREG的值压栈 INC CNT ;计数值加1 CPI CNT, 2 ;计数值与2比较 BRNE EXIT ;计数值未到2时直接返回(保证一个脉冲周期的对称性) LDI CNT, $20 ;设置步进值为$20 IN TMP, OCR1AL ;获取当前OCR1A比较值 ; IN TMP, ICR1L ;获取当前ICR1的比较值 ADD TMP, CNT ;当前比较值加$20,增加的步进为$20 CPI TMP, $FE ;当前比较值与$FE比较 BRNE N ;若比较值未加到$FE,则跳转去更新比较值 LDI TMP, $3E ;若比较值已加到$FE,则比较值恢复初始值 N: OUT OCR1AL, TMP ;更新OCR1A的比较值 ;N: OUT ICR1L, TMP ;更新ICR1的比较值 CLR CNT ;CNT值恢复为0 EXIT: POP TMP OUT SREG, TMP ;SREG的值出栈 RETI ;中断返回
在PD5引脚上产生的脉冲波形如下图所示。
下面对程序中的相关部分进行一下说明。
1)定时器T1为16位定时器,所以其TCNT1、OCR1A、ICR1等寄存器均为16位(分为高、低两个8位),在写这些寄存器时,应先写高位再写低位,在读这些寄存器时,应先读低位再读高位。
2)在CTC模式下,定时器T1有两种输出模式,即通过OCR1A寄存器或ICR1寄存器预设比较值,当定时器计数值(TCNT1中的值)达到比较值时,发生匹配事件。这时可实现对引脚PD5、PD4上的电平进行操作,具体的操作方式由寄存器TCCR1A来配置,可以有引脚置低电平、引脚置高电平和引脚电平取反等3种形式。
3)脉冲输出引脚固定在PD5、PD4两个引脚上,不能使用其他引脚。在CTC模式下,两个引脚输出同样的脉冲波形。
4)计数值匹配之后TCNT1会自动清零,反复循环,若不使用比较中断,整个CTC过程无需CPU干预。
5)当不使用比较中断时,由于无法更改比较值,所以只能实现固定频率的脉冲输出。使用比较中断时,可在中断服务程序中更新比较值,从而实现对输出频率的更改。
6)在初始化阶段,通过强制写TCCR1A寄存器的第3位(FOC1A),并配置为匹配时输出低电平的方式,把PD5、PD4强制置低电平,即脉冲的起始为低电平(也可为高电平,看需求设置)。
7)在初始化阶段,应该先配置OC1A的输出电平,再配置PD5、PD4为输出方向。
8)在比较匹配中断服务程序中,对状态寄存器SREG的值采取了栈保护(一般中断程序都应如此)。
9)为了可以看到明显的脉冲宽度变化,在使用系统时钟的程序中,对比较值的变化步长取$20(即32),一共可比较6次,在引脚PD5、PD4上可看到6个不同宽度的脉冲。
10)为了保证输出脉冲在每个完整周期内的占空比均为50%,在中断服务程序中加入了比较判断,以保证每两次比较中断之后才更改一次比较值。所以严格来说,CTC并不属于PWM方式,但这种方式更灵活。
11)上述程序是通过OCR1A的比较输出方式,把程序中注释的语句释放,再注释掉相邻的相关语句,就可转变成通过ICR1的比较输出方式。
2、快速PWM模式
快速PWM模式也是通过对计数值的比较来实现引脚电平的变化,但与CTC方式不同的是,CTC方式在比较匹配后计数值就清零了,而快速PWM模式在比较匹配之后,计数值依然增加,直到溢出后才清零。在匹配时输出引脚置低(或高)电平,在溢出时输出引脚置高(或低)电平。这样每次计数值从初始值到溢出就成为一个完整的脉冲周期,而比较值可在初始值到溢出值之间移动,方便更改脉冲的占空比。特别地,在定时器T1中,可通过设置两个比较值(OCR1A、OCR1B)来同时匹配输出两路脉冲波形。另外,还可以选择固定频率和可变频率两种方式来产生PWM波。
a)下面的实例采用固定频率方式,同时从OC1A引脚(PD5)、OC1B(PD4)输出频率为固定的488Hz,占空比不同且可变的PWM脉冲,汇编代码如下。
.INCLUDE "M16DEF.INC" .DEF TMP = R16 ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作) .DEF TMP1 = R17 .DEF CNT = R18 .DEF CNT1 = R19 .DEF FLAG = R20 .ORG $0000 JMP RESET ;复位的向量入口,地址为$00 .ORG $000C RJMP TIME1_OCR1A ;定时器T1的OCR1A比较匹配中断跳转 .ORG $000E RJMP TIME1_OCR1B ;定时器T1的OCR1B比较匹配中断跳转 .ORG $002A ;避开中断向量入口地址,主程序入口地址为$2A RESET: LDI TMP, HIGH(RAMEND) ;获取RAM空间的最大地址高字节(ATMega16为$04) LDI TMP1, LOW(RAMEND) ;获取RAM空间的最大地址低字节(ATMega16为$5F) OUT SPH, TMP ;高字节送SP高位 OUT SPL, TMP1 ;低字节送SP低位 OUT TCCR1B, TMP OUT TCCR1A, TMP1 ;OC1A及OC1B强制输出0 SBI DDRD, 4 ;设置PD4为输出方向 SBI DDRD, 5 ;设置PD5为输出方向 LDI TMP, $A1 LDI TMP1, $0B OUT TCCR1A, TMP OUT TCCR1B, TMP1 ;设置定时器T1为64分频,8位快速PWM模式,OC1A、OC1B都是匹配置0溢出置1 IN TMP, TIMSK ;获取原TIMSK的值 SBR TMP, $18 ;置第3、4位为1 OUT TIMSK, TMP ;回写TIMSK,允许OCR1A、OCR1B比较中断 IN TMP, TIFR SBR TMP, $18 OUT TIFR, TMP ;清除比较中断标志 CLR TMP1 LDI TMP, $10 OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;给OCR1A赋初值 CLR TMP1 LDI TMP, $80 OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;给OCR1B赋初值 CLR CNT ;计数值清零 CLR CNT1 ;计数值清零 CLR FLAG ;标志位清零 SEI ;开启总中断 LOOP: RJMP LOOP ;OCR1A匹配中断服务程序 TIME1_OCR1A: IN TMP, SREG PUSH TMP ;SREG的值压栈 INC CNT CPI CNT, $6 BRNE EXIT ;计数不到6次则直接返回,用于占空比变化速度控制 CLR CNT ;到6次先清零 SBRS FLAG, $0 ;标志位为1则执行减操作(减小占空比),为0则执行加操作(增加占空比) RJMP FORWARD IN TMP, OCR1AL ;获取当前比较值 DEC TMP ;减1 CPI TMP, $10 ;是否减到$10 BRNE NEXT ;未到$10则更新比较值后返回 CBR FLAG, $1 ;已到$10则表示到最底,置标志位为0 OUT OCR1AL, TMP ;更新比较值一次比较值后返回 RJMP EXIT FORWARD: IN TMP, OCR1AL ;获取当前比较值 INC TMP ;加1 CPI TMP, $80 ;是否加到$80 BRNE NEXT ;未到$80则更新比较值后返回 SBR FLAG, $1 ;已到$80则表示到最高,置标志位为1 NEXT: OUT OCR1AL, TMP ;更新比较值 EXIT: POP TMP OUT SREG, TMP ;SREG的值出栈 RETI ;OCR1B匹配中断服务程序 TIME1_OCR1B: IN TMP, SREG PUSH TMP ;SREG的值压栈 INC CNT1 CPI CNT1, $6 BRNE EXIT1 ;计数不到6次则直接返回,用于占空比变化速度控制 CLR CNT1 ;到6次先清零 SBRS FLAG, $1 ;标志位为1则执行减操作(减小占空比),为0则执行加操作(增加占空比) RJMP FORWARD1 IN TMP, OCR1BL ;获取当前比较值 DEC TMP ;减1 CPI TMP, $80 ;是否减到$80 BRNE NEXT1 ;未到$80则更新比较值后返回 CBR FLAG, $2 ;已到$80则表示到最底,置标志位为0 OUT OCR1BL, TMP ;更新比较值一次比较值后返回 RJMP EXIT1 FORWARD1: IN TMP, OCR1BL ;获取当前比较值 INC TMP ;加1 CPI TMP, $F0 ;是否加到$F0 BRNE NEXT1 ;未到$F0则更新比较值后返回 SBR FLAG, $2 ;已到$F0则表示到最高,置标志位为1 NEXT1: OUT OCR1BL, TMP ;更新比较值 EXIT1: POP TMP OUT SREG, TMP ;SREG的值出栈 RETI
在PD5和PD4引脚上产生的PWM波形如下面两张图片所示,图片为占空比变化截取的其中两张,上半部分为OCR1A的波形,下半部分为OCR1B的波形。可明显看出占空比的不同(频率相同)。
b)下面的实例采用可变频率方式,同时从OC1A引脚(PD5)、OC1B(PD4)输出频率为1kHz,占空比不同且可变的PWM脉冲,汇编代码如下。
.INCLUDE "M16DEF.INC" .DEF TMP = R16 ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作) .DEF TMP1 = R17 .DEF CNT = R18 .DEF CNT1 = R19 .DEF FLAG = R20 .ORG $0000 JMP RESET ;复位的向量入口,地址为$00 .ORG $000C RJMP TIME1_OCR1A ;定时器T1的OCR1A比较匹配中断跳转 .ORG $000E RJMP TIME1_OCR1B ;定时器T1的OCR1B比较匹配中断跳转 .ORG $002A ;避开中断向量入口地址,主程序入口地址为$2A RESET: LDI TMP, HIGH(RAMEND) ;获取RAM空间的最大地址高字节(ATMega16为$04) LDI TMP1, LOW(RAMEND) ;获取RAM空间的最大地址低字节(ATMega16为$5F) OUT SPH, TMP ;高字节送SP高位 OUT SPL, TMP1 ;低字节送SP低位 OUT TCCR1B, TMP OUT TCCR1A, TMP1 ;OC1A及OC1B强制输出0 SBI DDRD, 4 ;设置PD4为输出方向 SBI DDRD, 5 ;设置PD5为输出方向 LDI TMP, $A2 LDI TMP1, $1B OUT TCCR1A, TMP OUT TCCR1B, TMP1 ;设置定时器T1为64分频,8位快速PWM模式,OC1A、OC1B都是匹配置0溢出置1 IN TMP, TIMSK ;获取原TIMSK的值 SBR TMP, $18 ;置第3、4位为1 OUT TIMSK, TMP ;回写TIMSK,允许OCR1A、OCR1B比较中断 IN TMP, TIFR SBR TMP, $18 OUT TIFR, TMP ;清除比较中断标志 CLR TMP1 LDI TMP, $10 OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;给OCR1A赋初值 CLR TMP1 LDI TMP, $3E OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;给OCR1B赋初值 CLR TMP1 LDI TMP, $7D OUT ICR1H, TMP1 OUT ICR1L, TMP ;给ICR1赋初值 CLR CNT ;计数值清零 CLR CNT1 ;计数值清零 CLR FLAG ;标志位清零 SEI ;开启总中断 LOOP: RJMP LOOP ;OCR1A匹配中断服务程序 TIME1_OCR1A: IN TMP, SREG PUSH TMP ;SREG的值压栈 INC CNT CPI CNT, $10 BRNE EXIT ;计数不到16次则直接返回,用于占空比变化速度控制 CLR CNT ;到16次先清零 SBRS FLAG, $0 ;标志位为1则执行减操作(减小占空比),为0则执行加操作(增加占空比) RJMP FORWARD IN TMP, OCR1AL ;获取当前比较值 DEC TMP ;减1 CPI TMP, $10 ;是否减到$10 BRNE NEXT ;未到$10则更新比较值后返回 CBR FLAG, $1 ;已到$10则表示到最底,置标志位为0 OUT OCR1AL, TMP ;更新比较值一次比较值后返回 RJMP EXIT FORWARD: IN TMP, OCR1AL ;获取当前比较值 INC TMP ;加1 CPI TMP, $3E ;是否加到$3E BRNE NEXT ;未到$3E则更新比较值后返回 SBR FLAG, $1 ;已到$3E则表示到最高,置标志位为1 NEXT: OUT OCR1AL, TMP ;更新比较值 EXIT: POP TMP OUT SREG, TMP ;SREG的值出栈 RETI ;OCR1B匹配中断服务程序 TIME1_OCR1B: IN TMP, SREG PUSH TMP ;SREG的值压栈 INC CNT1 CPI CNT1, $10 BRNE EXIT1 ;计数不到16次则直接返回,用于占空比变化速度控制 CLR CNT1 ;到16次先清零 SBRS FLAG, $1 ;标志位为1则执行减操作(减小占空比),为0则执行加操作(增加占空比) RJMP FORWARD1 IN TMP, OCR1BL ;获取当前比较值 DEC TMP ;减1 CPI TMP, $40 ;是否减到$40 BRNE NEXT1 ;未到$40则更新比较值后返回 CBR FLAG, $2 ;已到$40则表示到最底,置标志位为0 OUT OCR1BL, TMP ;更新比较值一次比较值后返回 RJMP EXIT1 FORWARD1: IN TMP, OCR1BL ;获取当前比较值 INC TMP ;加1 CPI TMP, $70 ;是否加到$70 BRNE NEXT1 ;未到$70则更新比较值后返回 SBR FLAG, $2 ;已到$70则表示到最高,置标志位为1 NEXT1: OUT OCR1BL, TMP ;更新比较值 EXIT1: POP TMP OUT SREG, TMP ;SREG的值出栈 RETI
在PD5和PD4引脚上产生的PWM波形如下面两张图片所示,图片为占空比变化截取的其中两张,上半部分为OCR1A的波形,下半部分为OCR1B的波形。可明显看出占空比的不同(频率相同)。
下面对程序中的相关部分进行一下说明。
1)在快速PWM模式下,定时器T1通过OCR1A和OCR1B两个寄存器来设置比较值,用于调整脉冲的占空比。当定时器计数值(TCNT1中的值)达到比较值时,发生匹配事件,这时可对引脚PD5和PD4上的电平进行操作。然后计数值继续增加直到溢出,溢出时也可对引脚PD5和PD4上的电平进行操作。引脚电平具体的操作方式由寄存器TCCR1A来配置,可以选择在匹配时引脚置低电平、溢出时引脚置高电平,或相反。
2)脉冲输出引脚固定在PD5、PD4两个引脚上,不能使用其他引脚。在CTC模式下,两个引脚输出同样的脉冲波形。
3)计数值匹配之后TCNT1会继续增加直到溢出,如此反复循环,若不使用比较中断和溢出中断,整个PWM过程无需CPU干预。
4)T1在快速PWM模式下,可以选择固定频率(计数最大值分别为8位、9位或10位三种)方式,也可选择可变频率(计数最大值分别由寄存器OCR1A和ICR1来确定)方式。若选择可变频率方式,则会占用一项T1的其他功能(程序中使用ICR1,占用了T1的输入捕获功能)。
5)如果要更新比较值(OCR1A、OCR1B的值),操作可放在比较中断服务程序中进行,但更新的值不会立即生效,而是要等到此次计数溢出之后,才会生效,这样能保证波形的对称性。
6)为了可以在示波器上看到明显的脉冲占空比的变化,上述程序中,对比较值的更新设置在触发比较中断6次(或16次)之后再进行。这样可有效地减慢占空比的变化过程,在引脚PD5、PD4上可看到PWM的占空比不继增加,又不继减小,如此往复循环。
3、相位修正PWM模式
相位修正PWM模式也是通过对计数值的比较来实现引脚电平的变化,但与快速PWM模式不同的是,相位修正PWM模式不仅使用了计数的增加部分(上升段),同时使用了计数的减小部分(下降段)。即当TCNT1的值增加到最大值(固定或可变)后,计数值并不清零,而是又逐渐减小,直到最小值($0000),然后又增加,如此循环。而在上升段和下降段均可与OCR1A或OCR1B进行比较匹配,匹配时引脚PD5或PD4上的电平都可进行操作。在计数回到最小值($0000)时,可触发溢出中断。在比较匹配时,可触发比较中断。
a)下面的实例采用固定频率方式,同时从OC1A引脚(PD5)、OC1B(PD4)输出频率为固定的244Hz,占空比不同且可变的PWM脉冲,汇编代码如下。
.INCLUDE "M16DEF.INC" .DEF TMP = R16 ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作) .DEF TMP1 = R17 .DEF CNT = R18 .DEF CNT1 = R19 .DEF FLAG = R20 .ORG $0000 JMP RESET ;复位的向量入口,地址为$00 .ORG $0010 RJMP TIME1_OVF ;定时器T1的溢出中断跳转 .ORG $002A ;避开中断向量入口地址,主程序入口地址为$2A RESET: LDI TMP, HIGH(RAMEND) ;获取RAM空间的最大地址高字节(ATMega16为$04) LDI TMP1, LOW(RAMEND) ;获取RAM空间的最大地址低字节(ATMega16为$5F) OUT SPH, TMP ;高字节送SP高位 OUT SPL, TMP1 ;低字节送SP低位 LDI TMP, $00 LDI TMP1, $FC OUT TCCR1B, TMP OUT TCCR1A, TMP1 ;OC1A及OC1B强制输出1 SBI DDRD, 4 ;设置PD4为输出方向 SBI DDRD, 5 ;设置PD5为输出方向 LDI TMP, $A1 LDI TMP1, $03 OUT TCCR1A, TMP OUT TCCR1B, TMP1 ;设置定时器T1为64分频,8位相位修正PWM模式,OC1A、OC1B都是上升匹配置0,下降匹配置1 IN TMP, TIMSK ;获取原TIMSK的值 SBR TMP, $4 ;置第2位为1 OUT TIMSK, TMP ;回写TIMSK,允许T1溢出中断 IN TMP, TIFR SBR TMP, $4 OUT TIFR, TMP ;清除溢出中断标志 CLR TMP1 LDI TMP, $10 OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;给OCR1A赋初值 CLR TMP1 LDI TMP, $80 OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;给OCR1B赋初值 CLR CNT ;计数值清零 CLR CNT1 ;计数值清零 CLR FLAG ;标志位清零 SEI ;开启总中断 LOOP: RJMP LOOP ;T1溢出中断服务程序 TIME1_OVF: IN TMP, SREG PUSH TMP ;SREG的值压栈 INC CNT CPI CNT, $04 BRNE EXIT ;计数不到4次则直接返回,用于占空比变化速度控制 CLR CNT ;到4次先清零 SBRS FLAG, $0 ;标志位为1则执行减操作(减小占空比),为0则执行加操作(增加占空比) RJMP FORWARD IN TMP, OCR1AL ;获取当前比较值 DEC TMP ;减1 CPI TMP, $10 ;是否减到$10 BRNE NEXT ;未到$10则更新比较值后返回 CBR FLAG, $1 ;已到$10则表示到最底,置标志位为0 OUT OCR1AL, TMP ;更新比较值一次比较值后返回 RJMP EXIT FORWARD: IN TMP, OCR1AL ;获取当前比较值 INC TMP ;加1 CPI TMP, $80 ;是否加到$80 BRNE NEXT ;未到$80则更新比较值后返回 SBR FLAG, $1 ;已到$80则表示到最高,置标志位为1 NEXT: OUT OCR1AL, TMP ;更新比较值 EXIT: INC CNT1 CPI CNT1, $04 BRNE EXIT1 ;计数不到4次则直接返回,用于占空比变化速度控制 CLR CNT1 ;到4次先清零 SBRS FLAG, $1 ;标志位为1则执行减操作(减小占空比),为0则执行加操作(增加占空比) RJMP FORWARD1 IN TMP, OCR1BL ;获取当前比较值 DEC TMP ;减1 CPI TMP, $80 ;是否减到$80 BRNE NEXT1 ;未到$80则更新比较值后返回 CBR FLAG, $2 ;已到$80则表示到最底,置标志位为0 OUT OCR1BL, TMP ;更新比较值一次比较值后返回 RJMP EXIT1 FORWARD1: IN TMP, OCR1BL ;获取当前比较值 INC TMP ;加1 CPI TMP, $F0 ;是否加到$F0 BRNE NEXT1 ;未到$F0则更新比较值后返回 SBR FLAG, $2 ;已到$F0则表示到最高,置标志位为1 NEXT1: OUT OCR1BL, TMP ;更新比较值 EXIT1: POP TMP OUT SREG, TMP ;SREG的值出栈 RETI
在PD5和PD4引脚上产生的PWM波形如下面两张图片所示,图片为占空比变化截取的其中两张,上半部分为OCR1A的波形,下半部分为OCR1B的波形。可明显看出占空比的不同(频率相同)。对比快速PWM模式的图片可以看出,相位修正PWM模式的周期要更长一些,即频率要低一些。
b)下面的实例采用可变频率方式,同时从OC1A引脚(PD5)、OC1B(PD4)输出频率为1kHz,占空比不同且可变的PWM脉冲,汇编代码如下。
.INCLUDE "M16DEF.INC" .DEF TMP = R16 ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作) .DEF TMP1 = R17 .DEF CNT = R18 .DEF CNT1 = R19 .DEF FLAG = R20 .DEF ONE = R21 .ORG $0000 JMP RESET ;复位的向量入口,地址为$00 .ORG $0010 RJMP TIME1_OVF ;定时器T1的溢出中断跳转 .ORG $002A ;避开中断向量入口地址,主程序入口地址为$2A RESET: LDI TMP, HIGH(RAMEND) ;获取RAM空间的最大地址高字节(ATMega16为$04) LDI TMP1, LOW(RAMEND) ;获取RAM空间的最大地址低字节(ATMega16为$5F) OUT SPH, TMP ;高字节送SP高位 OUT SPL, TMP1 ;低字节送SP低位 LDI TMP, $00 LDI TMP1, $FC OUT TCCR1B, TMP OUT TCCR1A, TMP1 ;OC1A及OC1B强制输出1 SBI DDRD, 4 ;设置PD4为输出方向 SBI DDRD, 5 ;设置PD5为输出方向 LDI TMP, $A2 LDI TMP1, $12 OUT TCCR1A, TMP OUT TCCR1B, TMP1 ;设置定时器T1为8分频,ICR1为TOP的相位修正PWM模式,OC1A、OC1B都是上升匹配置0,下降匹配置1 IN TMP, TIMSK ;获取原TIMSK的值 SBR TMP, $4 ;置第2位为1 OUT TIMSK, TMP ;回写TIMSK,允许T1溢出中断 IN TMP, TIFR SBR TMP, $4 OUT TIFR, TMP ;清除比较中断标志 CLR TMP1 LDI TMP, $20 OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;给OCR1A赋初始比较值 CLR TMP1 LDI TMP, $FA OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;给OCR1B赋初始比较值 LDI TMP1, $01 LDI TMP, $F4 OUT ICR1H, TMP1 OUT ICR1L, TMP ;给ICR1赋值,确定PWM的频率(1kHz) CLR CNT ;计数值清零 CLR CNT1 ;计数值清零 CLR FLAG ;标志位清零 LDI ONE, $01 ;赋值1,用于累加(减) SEI ;开启总中断 LOOP: RJMP LOOP ;T1溢出中断服务程序 TIME1_OVF: IN TMP, SREG PUSH TMP ;SREG的值压栈 INC CNT CPI CNT, $0A BRNE EXIT ;计数不到10次则直接返回,用于占空比变化速度控制 CLR CNT ;到10次先清零 SBRS FLAG, $0 ;标志位为1则执行减操作(减小占空比),为0则执行加操作(增加占空比) RJMP FORWARD IN TMP, OCR1AL ;获取当前比较值 IN TMP1, OCR1AH DEC TMP ;减1 CPI TMP, $20 ;是否减到$20 BRNE NEXT ;未到$20则更新比较值后返回 CBR FLAG, $1 ;已到$20则表示到最底,置标志位为0 OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;更新比较值一次比较值后返回 RJMP EXIT FORWARD: IN TMP1, OCR1AH IN TMP, OCR1AL ;获取当前比较值 INC TMP ;加1 CPI TMP, $FA ;是否加到$FA BRNE NEXT ;未到$FA则更新比较值后返回 SBR FLAG, $1 ;已到$FA则表示到最高,置标志位为1 NEXT: OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;更新比较值 EXIT: INC CNT1 CPI CNT1, $0A BRNE EXIT1 ;计数不到10次则直接返回,用于占空比变化速度控制 CLR CNT1 ;到10次先清零 SBRS FLAG, $1 ;标志位为1则执行减操作(减小占空比),为0则执行加操作(增加占空比) RJMP FORWARD1 IN TMP, OCR1BL ;获取当前比较值 IN TMP1, OCR1BH SUB TMP, ONE ;减1 BRCS M1 ;进/借位为1 CPI TMP1, $00 BREQ M2 OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;更新比较值 RJMP EXIT1 M1: CLC ;清进/借位 CLR TMP1 OUT OCR1BH, TMP1 OUT OCR1BL, TMP RJMP EXIT1 M2: CPI TMP, $FA BREQ M3 OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;更新比较值 RJMP EXIT1 M3: CBR FLAG, $2 ;已到$FA则表示到最底,置标志位为0 OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;更新比较值 RJMP EXIT1 FORWARD1: IN TMP, OCR1BL ;获取当前比较值 IN TMP1, OCR1BH ADD TMP, ONE ;加1 BRCS N1 ;进/借位为1 CPI TMP1, $01 BREQ N2 OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;更新比较值 RJMP EXIT1 N1: CLC ;清进/借位 LDI TMP1, $01 CLR TMP OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;给OCR1B赋初值 RJMP EXIT1 N2: CPI TMP, $D4 BREQ N3 OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;更新比较值 RJMP EXIT1 N3: SBR FLAG, $2 ;已到$1D4则表示到最高,置标志位为1 OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;更新比较值 EXIT1: POP TMP OUT SREG, TMP ;SREG的值出栈 RETI
在PD5和PD4引脚上产生的PWM波形形式上与前面例a(固定频率)的一致,只是频率设置成了1kHz。
下面对程序中的相关部分进行一下说明。
1)在相位修正PWM模式下,定时器计数值(TCNT1中的值)从0开始逐渐增加到最大值(固定或可变),然后又逐渐减小,直到最小值0。即在相位修正模式下,计数过程形成了“双斜坡”,所以产生的PWM频率要比快速PWM模式的低。
2)在斜坡的上升段,如果计数值(TCNT1中的值)与比较值(OCR1A、OCR1B的值)匹配成功,可对引脚PD5和PD4上的电平进行操作。在斜坡的下降段,如果计数值与比较值匹配成功,也可对引脚PD5和PD4上的电平进行操作。引脚电平具体的操作方式由寄存器TCCR1A来配置,可以选择在上升段匹配时引脚置低电平,在下降段匹配时引脚置高电平,或者相反。所以,在一个双斜坡周期内,会有两次比较,可通过比较值来调整PWM的占空比。
3)脉冲输出引脚固定在PD5、PD4两个引脚上,不能使用其他引脚。
4)在匹配成功时可触发比较中断,在计数值减小到0时可触发溢出中断。若不使用比较中断和溢出中断,则整个PWM过程无需CPU干预。
5)T1在相位修正PWM模式下,可以选择固定频率(计数最大值分别为8位、9位或10位三种)方式,也可选择可变频率(计数最大值分别由寄存器OCR1A和ICR1来确定)方式。若选择可变频率方式,则会占用一项T1的其他功能(程序中使用ICR1,占用了T1的输入捕获功能)。
6)如果要更新比较值(OCR1A、OCR1B的值),操作可放在溢出中断服务程序中进行,但更新的值不会立即生效,而是要等到此次计数值达到最大值(TOP)之后,才会更新,这样能保证PWM波形的对称性。
7)如果要更新TOP的值(即改变频率),虽然OCR1A或ICR1寄存器都可以做到,但若要经常动态的改变频率,还是推荐使用OCR1A寄存器,因为它具有双缓冲结构,而ICR1寄存器不具备。
8)所谓的相位修正,其实就是相位可调,由于OCR1A、OCR1B的值的值可以更改,所以脉冲的相位也就随之更改了。但如果只是单个PWM输出,则相位修正没有太大的实际意义,可认为仅仅比快速模式降低了频率而已。
9)为了可以在示波器上看到明显的脉冲占空比的变化,上述程序中,对比较值的更新设置在触发比较中断4次(或10次)之后再进行。这样可减慢占空比的变化过程,在引脚PD5和PD4上可看到PWM的占空比不继增加,又不继减小,如此往复循环。
4、相频修正PWM模式
相频修正PWM模式即相位频率修正PWM模式,它是在相位修正PWM模式的基础上,通过把匹配值的更新放在计数的最低处,从而保证了脉冲波形在双斜坡两边始终保持对称,这对于要经常需要改变最高点的值时,保持波形的精度很有利。如果最高点保持不变,那它与相位修正模式没有什么区别。相频修正PWM模式是定时器T1所特有的模式。
下面的实例采用OCR1A为TOP,通过OC1B(PD4)输出连续频率变化的PWM脉冲,汇编代码如下。
.INCLUDE "M16DEF.INC" .DEF TMP = R16 ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作) .DEF TMP1 = R17 .DEF FLAG = R18 .DEF ONE = R19 .ORG $0000 JMP RESET ;复位的向量入口,地址为$00 .ORG $000C RJMP TIME1_OCR1A ;OCR1A比较中断跳转 .ORG $002A ;避开中断向量入口地址,主程序入口地址为$2A RESET: LDI TMP, HIGH(RAMEND) ;获取RAM空间的最大地址高字节(ATMega16为$04) LDI TMP1, LOW(RAMEND) ;获取RAM空间的最大地址低字节(ATMega16为$5F) OUT SPH, TMP ;高字节送SP高位 OUT SPL, TMP1 ;低字节送SP低位 LDI TMP, $00 LDI TMP1, $34 OUT TCCR1B, TMP OUT TCCR1A, TMP1 ;OC1B强制输出1 SBI DDRD, 4 ;设置PD4为输出方向 LDI TMP, $21 LDI TMP1, $12 OUT TCCR1A, TMP OUT TCCR1B, TMP1 ;设置定时器T1为8分频,OC1A为TOP的相频修正PWM模式,OC1B都是上升匹配置0,下降匹配置1 IN TMP, TIMSK ;获取原TIMSK的值 SBR TMP, $10 ;置第4位为1 OUT TIMSK, TMP ;回写TIMSK,允许OCR1A比较中断 IN TMP, TIFR SBR TMP, $10 OUT TIFR, TMP ;清除比较中断标志 LDI TMP1, $09 LDI TMP, $FF OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;给OCR1A赋初值 LDI TMP1, $04 LDI TMP, $FF OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;给OCR1B赋初值 LDI FLAG, $01 ;标志位置1 LDI ONE, $01 SEI ;开启总中断 LOOP: RJMP LOOP ;OCR1A比较中断服务程序 TIME1_OCR1A: IN TMP, SREG PUSH TMP SBRS FLAG, $0 ;标志位为1则执行减操作(减小占空比),为0则执行加操作(增加占空比) RJMP FORWARD IN TMP, OCR1AL ;获取当前TOP值 IN TMP1, OCR1AH SUB TMP, ONE ;减1 BRCS M1 ;进/借位为1 CPI TMP1, $00 BREQ M2 OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;更新TOP值 RCALL DIV2 ;更新比较值 RJMP EXIT M1: CLC ;清进/借位 SUB TMP1, ONE OUT OCR1AH, TMP1 OUT OCR1AL, TMP RCALL DIV2 ;更新比较值 RJMP EXIT M2: CPI TMP, $F0 BREQ M3 OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;更新TOP值 LSR TMP OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;更新比较值 RJMP EXIT M3: CBR FLAG, $1 ;已到$FA则表示到最底,置标志位为0 OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;更新TOP值 LSR TMP OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;更新比较值 RJMP EXIT FORWARD: IN TMP, OCR1AL ;获取当前比较值 IN TMP1, OCR1AH ADD TMP, ONE ;加1 BRCS N1 ;进/借位为1 CPI TMP1, $09 BREQ N2 OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;更新TOP值 RCALL DIV2 ;更新比较值 RJMP EXIT N1: CLC ;清进/借位 ADD TMP1, ONE CLR TMP OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;给OCR1B赋初值 RCALL DIV2 ;更新比较值 RJMP EXIT N2: CPI TMP, $FF BREQ N3 OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;更新TOP值 RCALL DIV2 RJMP EXIT N3: SBR FLAG, $1 ;已到$4E2则表示到最高,置标志位为1 OUT OCR1AH, TMP1 OUT OCR1AL, TMP ;更新TOP值 RCALL DIV2 ;更新比较值 EXIT: POP TMP OUT SREG, TMP ;SREG的值出栈 RETI ;比较值为TOP值的一半 DIV2: CLC ;进位位清零 ROR TMP1 ;高8位带进位位右移 ROR TMP ;低8位带进位位右移 OUT OCR1BH, TMP1 OUT OCR1BL, TMP ;更新比较值 RET
在PD4引脚上产生的PWM波形如下面两张图片所示,上张为频率较低的波形,下张为频率较高的波形。可明显看出周期的变化。
下面对程序中的相关部分进行一下说明。
1)相位频率修正PWM模式为定时器T1所特有,其他定时器不具备该功能,使用它可连续的更改PWM的频率。
2)在相位频率修正PWM模式下,定时器计数值(TCNT1中的值)从0开始逐渐增加到最大值(TOP),然后又逐渐减小,直到最小值0。即在相位频率修正模式下,计数过程形成了“双斜坡”,所以产生的PWM频率要比快速PWM模式的低。
3)在斜坡的上升段,如果计数值(TCNT1中的值)与比较值(OCR1A、OCR1B的值)匹配成功,可对引脚PD5和PD4上的电平进行操作。在斜坡的下降段,如果计数值与比较值匹配成功,也可对引脚PD5和PD4上的电平进行操作。引脚电平具体的操作方式由寄存器TCCR1A来配置,可以选择在上升段匹配时引脚置低电平,在下降段匹配时引脚置高电平,或者相反。所以,在一个双斜坡周期内,会有两次比较,可通过比较值来调整PWM的占空比。
4)相位频率修正PWM模式的频率只能是可变的,由寄存器OCR1A和ICR1来决定TOP的值。其中,OCR1A具有双缓冲结构,特别适用于经常更改频率的场合,但会失去了一个比较匹配的功能。使用ICR1时,一般会让其频率固定。
5)脉冲输出引脚固定在PD4或PD5引脚上,不能使用其他引脚。
6)在匹配成功时可触发比较中断(包括使用OCR1A作为TOP时),在计数值减小到0时可触发溢出中断。若要不断地更改TOP值和比较值,建议放在TOP进行(OCR1A或ICR1中断程序中),更新会在BUTTOM(底部)时发生,这样能让输出的波形保持对称。若不使用比较中断和溢出中断,则整个PWM过程无需CPU干预。
7)上述程序中,为了让输出的脉冲始终保持50%的占空比,比较值OCR1B取TOP值的一半。但由于AVR并没有提供除法指令,所以程序采取了整体(16位)右移1位的形式。当高8位为0时,低8位逻辑右移1位即可。当高8位不为0时,先进行高8位右移,并把最低位移入到进位位C中(最高位置0),然后低8位再进行带进位位的右移即可。
下面来讨论一下以上所有程序代码中用到的寄存器。
本部分使用到了AVR中I/O空间的8个寄存器,即SPH、SPL、DDRD、PORTD、TCNT1、TCCR1A、TCCR1B和TIMSK。其中SPH、SPL、DDRD、PORTD等4个寄存器的介绍可参考“基于ATMega16的流水灯实例”一文,TCNT1寄存器可参考“基于ATMega16的数码管动态扫描实例”一文。下面来看TCCR1A和TCCR1B两个寄存器,它们同属于定时器T1的控制寄存器,初始值为全0,具体如下表所示。
在TCCR1A中,高4位分别用来确定两个匹配输出引脚(OC1A、OC1B)的电平,其配置与定时器T0的一样,具体可参考“基于ATMega16定时器T0产生PWM的实例”一文。第3、2两位(FOC1A、FOC1B)是两个匹配输出引脚的强制输出比较位,用来在非PWM模式(CTC模式)下强制形成一次匹配事件,在PWM模式下该位必须写0。第1、0两位和TCCR1B中的第4、3两位一共组成了4位(WGM13~WGM10),用来选择T1的工作模式,一共有15种模式,具体如下表所示。
在TCCR1B中的低3位(CS12~CS10)用来选择时钟的分频值,具体可参考“基于ATMega16的数码管动态扫描实例”一文。
接下来看TIMSK寄存器,它是定时器的中断屏蔽寄存器,初始值为全0,具体如下表所示。
表中的第5~2位分别是定时器T1的输入捕获中断使能位、OCR1A匹配中断使能位、OCR1B匹配中断使能位和溢出中断使能控制位,把相应的位置1就可以使能对应的中断(同时还要使能总中断)。
最后看TIFR寄存器,它是中断标志寄存器,为T0、T1和T2所共有,初始值为全0,具体如下表所示。
其中的第5~2位分别用来标志T1的输入捕获中断、OCR1A匹配中断、OCR1B匹配中断和溢出中断,当定时器有相应的中断发生时,对应的位会被硬件置1 ,中断响应后会自动清零,写1将强制清零该位。
本章节中的汇编程序一共使用到了25种指令,其中的JMP、RJMP、LDI、OUT、DEC、BRNE、PUSH、POP等8条指令可参考“基于ATMega16的流水灯实例”一文,SEI、CLR、INC、RETI、ADD等5条指令可参考“基于ATMega16的数码管动态扫描实例”一文,IN、CPI、SBRS、BREQ等4条指令可参考“基于ATMega16的数码管时钟显示实例”一文。SBI、SBR等2条指令可参考“基于ATMega16定时器T0产生PWM的实例”一文。剩余6条指令解释如下。
1)不带进位位减法
SUB Rd, Rr 0 ≤ d ≤ 31,0 ≤ r ≤ 31
说明:将两个寄存器中的数据相减,结果放在目的寄存器Rd中。
操作:Rd ← Rd - Rr PC ← PC + 1 16位机器码:0001 10rd dddd rrrr
2)进位标志位C为1跳转
BRCS k -64 ≤ k ≤ 63
说明:测试进位标志C,如果C位被置位,则相对PC值跳转k个字。k 为7位带符号数,最多可向前跳63个字,向后跳64个字。这条指令相当于指令BRBS 0,k。
操作:If C = 1 then PC ← PC + k + 1, else PC ← PC + 1 16位机器码:1111 00kk kkkk k000
3)清进位位
CLC
说明:清零SREG状态寄存器中的进位标志C。
操作:C ← 0 PC ← PC + 1 16位机器码:1001 0100 1000 1000
4)寄存器位清零
CBR Rd, K 16 ≤ d ≤ 31,0 ≤ K ≤ 255
说明:清除寄存器Rd中的指定位,利用寄存器Rd的内容与常数表征码K的补码相与,其结果放在寄存器Rd中。
操作:Rd ← Rd · ($FF - K) PC ← PC + 1 16位机器码:0111 KKKK dddd KKKK
5)寄存器逻辑右移
LSR Rd 0 ≤ d ≤ 31
说明:寄存器 Rd中所有位右移1 位,第7 位被清0,第0位移到SREG 中的C标志。这个运算实现了无符号数更有效率的除2 操作,C标志用于结果的舍入。
操作:0 → b7--------b0 → C PC ← PC + 1 16位机器码:1001 010d dddd 0110
6)带进位位的寄存器循环右移
ROR Rd 0 ≤ d ≤ 31
说明:寄存器 Rd的所有位右移一位,C标志被移到Rd的第7 位,Rd的第0 位被移到C标志位。
操作:C → b7--------b0 → C PC ← PC + 1 16位机器码:1001 010d dddd 0111
此外,程序中用到的伪指令.INCLUDE、.DEF、.ORG可参考“基于ATMega16的流水灯实例”一文。
最后,总的来归纳一下定时器T1产生PWM波形的情况。
1)如果只产生脉冲波形,而不用调整占空比,或要产生一些非标准的脉冲波形,选用CTC方式较为方便。
2)如果只产生占空比可调整的PWM波,则选择快速PWM或相位修正PWM模式较为方便。如果要求PWM的频率可变,则选择相位频率修正PWM方式较为合适。
3)由于T1是16位结构,因此可以获得较低频率的PWM波形,但在操作时要注意寄存器高低两个8位的顺序。