-------------------------------------------------------------------------------------------------------------------------------------
因为这样就只需要设置OCR1A,不需要每次TOV1中断都要重新设置TCNT1的计数初值。
2、CTC模式下、OC1A和OC1B的频率由OCR1A或ICR1决定,OCR1A和OCR1B都用来控制OC1A和OC1B的相位。
CTC模式下的脉冲频率最大可达4MHz。
3、快速/高频PWM用于:功率调节、整流、ADC
当OCR1A/OCR1B超过TOP值时,依然有PWM波形输出:TCNT1=(OCR1A或OCR1B)&TOP时发生比较匹配
可以得到相当精确的PWM频率和占空比,如38KHz、1/3占空比的红外载波
可以用OC1A引脚输出类似CTC模式的方波
4、相位修正对称用于:电机控制
修改TOP值可能导致上升斜坡和下降斜坡长度不一致,导致一个周期的波形不对称。
结果就是占空比超出预期或低于预期。
如果这有影响,就应该使用相位与频率修正PWM来做TOP值/频率可变的PWM。
5、相频修正PWM,其作用与意义有待理解。
6、对于ICF1中断,还有异常,见question-001。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、使用定时器1的作为定时器使用,每次溢出后PA2的电平取反,由此可得知溢出时间。
Drv_Timer.h
typedef enum { INT_MODE_TOV = 0, INT_MODE_OCF = 1, INT_MODE_ICF = 2, INT_MODE_OCF1A = 3, INT_MODE_OCF1B = 4 } TIMER_INT_MODE; typedef enum { T1_COM_MODE_NONE = 0, T1_COM_MODE_TOGGLE = 1, T1_COM_MODE_CLEAR = 2, T1_COM_MODE_SET = 3, T1_WGM_NOMAL = 0, T1_WGM_8_PHASE_PWM = 1, T1_WGM_9_PHASE_PWM = 2, T1_WGM_10_PHASE_PWM = 3, T1_WGM_CTC = 4, T1_WGM_8_FAST_PWM = 5, T1_WGM_9_FAST_PWM = 6, T1_WGM_10_FAST_PWM = 7, T1_WGM_PHASE_FRQ_PWM_ICR1 = 8, T1_WGM_PHASE_FRQ_PWM_OCR1A = 9, T1_WGM_PHASE_PWM_ICR1 = 10, T1_WGM_PHASE_PWM_OCR1A = 11, T1_WGM_CTC_ICR1 = 12, T1_WGM_SERVED = 13, T1_WGM_FAST_PWM_ICR1 = 14, T1_WGM_FAST_PWM_OCR1A = 15, T1_CLK_SOURCE_NONE = 0, T1_CLK_SOURCE_CLK_1 = 1, T1_CLK_SOURCE_CLK_8 = 2, T1_CLK_SOURCE_CLK_64 = 3, T1_CLK_SOURCE_CLK_256 = 4, T1_CLK_SOURCE_CLK_1024 = 5, T1_CLK_SOURCE_T1_FALL = 6, T1_CLK_SOURCE_T1_RAISE = 7 } TIMER1_MODE; typedef enum { T1_ICP_FALL_EDGE = 0, T1_ICP_RAISE_EDGE = 1 } TIMER1_ICP;Drv_Timer.c
// ========================================================================================================== // 定时器1初始化 // // 参数:OCM1A_mode 通道A比较匹配/PWM输出模式选择 // OCM1B_mode 通道B比较匹配/PWM输出模式选择 // com_mode 工作模式/波形产生模式选择 // clk_source 时钟源和预分频选择 // // PWM模式下、写TCCR1A时需要清除FOC1A/B // 写TCCR1B时需要清除bit5 // // ========================================================================================================== void Drv_Timer1_init(const uint8_t com_mode, const uint8_t OCM1A_mode, const uint8_t OCM1B_mode, const uint8_t clk_source) { TCCR1A = ((OCM1A_mode & 0x03) << 6) | // 通道A比较匹配/PWM输出模式选择 ((OCM1B_mode & 0x03) << 4) | // 通道B比较匹配/PWM输出模式选择 ((com_mode & 0x03) << 0); // 工作模式/波形产生模式选择(WGM[11:10]) TCCR1B = (((com_mode & 0x0C) >> 2) << 3) | // 工作模式/波形产生模式选择(WGM[13:12]) ( (clk_source & 0x07) << 0); // 时钟源和预分频选择 } // ========================================================================================================== // TIMER1 中断使能 // // 参数:mode = INT_MODE_TOV 或 INT_MODE_ICF 或 INT_MODE_OCF1A 或 INT_MODE_OCF1B // enable = ENABLE 或 DISABLE // // 可以单独使能/禁止一种模式的中断 // // ========================================================================================================== void Drv_Timer1_INT_Enable(const uint8_t mode, const uint8_t enable) { if(INT_MODE_TOV == mode) { if(DISABLE == enable) { TIMSK &= ~(1 << TOIE1); } else { TIMSK |= (1 << TOIE1); } TIFR |= (1 << TOV1); return ; } if(INT_MODE_OCF1A == mode) { if(DISABLE == enable) { TIMSK &= ~(1 << OCIE1A); } else { TIMSK |= (1 << OCIE1A); } TIFR |= (1 << OCF1A); return ; } if(INT_MODE_OCF1B == mode) { if(DISABLE == enable) { TIMSK &= ~(1 << OCIE1B); } else { TIMSK |= (1 << OCIE1B); } TIFR |= (1 << OCF1B); return ; } if(INT_MODE_ICF == mode) { if(DISABLE == enable) { TIMSK &= ~(1 << TICIE1); } else { TIMSK |= (1 << TICIE1); } TIFR |= (1 << ICF1); } } // ========================================================================================================== // TIMER1 溢出中断服务程序 // // ========================================================================================================== ISR(TIMER1_OVF_vect) { PORTA ^= (1 << PA2); }main.c
// ========================================================================================================== // 主函数 // ========================================================================================================== #include <avr/io.h> #include <avr/interrupt.h> #include "watch_dog.h" #include "Drv_Timer.h" #include "_noinit.h" #include "system.h" #include "sys_timer.h" #include "config.h" // ========================================================================================================== // 伪中断BADISR_vect // // ========================================================================================================== ISR(BADISR_vect) { } // ========================================================================================================== // main函数 // ========================================================================================================== int main(void) { // --------- // 关全局中断 cli(); // 系统初始化 sys_init(); // PA[5:2]初始化为输出0 DDRA = (1 << DDA2) | (1 << DDA3) | (1 << DDA5) | (1 << DDA6); PORTA &= ~((1 << PA2 ) | (1 << PA3 ) | (1 << PA5 ) | (1 << PA6 )); // 定时器1 初始化:普通模式、COM1A不启用、COM1B不启用、8预分频 Drv_Timer1_init(T1_WGM_NOMAL, T1_COM_MODE_NONE, T1_COM_MODE_NONE, T1_CLK_SOURCE_CLK_8); // 使能TOV1中断 Drv_Timer1_INT_Enable(INT_MODE_TOV, ENABLE); // 开全局中断 sei(); // --------- while(1) { } return 0; }
说明:
TCNT1加计数到TCNT1 = OCR1A时,比较匹配A发生、OC1A引脚电平翻转,同时TCNT1会被清0。
TCNT1加计数到TCNT1 = OCR1B时,比较匹配B发生、 OC1B引脚电平翻转。
但是,如果OCR1A < OCR1B,那么因为在TCNT1 = OCR1A时、TCNT1会被清0,不会继续增加到更大的OCR1B,
所以OCF1B永远不会发生、OC1B引脚将永远不会翻转。
Drv_Timer.c中增加1个初值设置函数和2个中断服务函数:
// ========================================================================================================== // 设置TCNT1、OCR1A、OCR1B的值 // // (1). 在比较匹配下、OCR1A、OCR1B需要在TCNT1被设置之后设置 // 相应的,ICP1也需要在TCNT1被设置之后设置 // // ========================================================================================================== void Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(const uint16_t tcnt1, const uint16_t ocr1a, const uint16_t ocr1b, const uint16_t icr1) { TCNT1 = tcnt1; OCR1A = ocr1a; OCR1B = ocr1b; ICR1 = icr1; } // ========================================================================================================== // TIMER1 比较匹配A 中断服务程序 // // ========================================================================================================== ISR(TIMER1_COMPA_vect) { PORTA ^= (1 << PA3); } // ========================================================================================================== // TIMER1 比较匹配B 中断服务程序 // // ========================================================================================================== ISR(TIMER1_COMPB_vect) { PORTA ^= (1 << PA5); }
main.c如下:
// ========================================================================================================== // 主函数 // ========================================================================================================== #include <avr/io.h> #include <avr/interrupt.h> #include "Drv_Timer.h" #include "system.h" #include "config.h" // ========================================================================================================== // 伪中断BADISR_vect // // ========================================================================================================== ISR(BADISR_vect) { } // ========================================================================================================== // main函数 // ========================================================================================================== int main(void) { // --------- // 关全局中断 cli(); // 系统初始化 sys_init(); // PA[5:2]初始化为输出0 DDRA = (1 << DDA2) | (1 << DDA3) | (1 << DDA5) | (1 << DDA6); PORTA &= ~((1 << PA2 ) | (1 << PA3 ) | (1 << PA5 ) | (1 << PA6 )); // 定时器1 初始化:CTC模式、COM1A启用(电平翻转)、COM1B启用(电平翻转)、8预分频 Drv_Timer1_init(T1_WGM_CTC, T1_COM_MODE_TOGGLE, T1_COM_MODE_TOGGLE, T1_CLK_SOURCE_CLK_8); // 设置TCTN1=0、OCR1A=200、OCR1B=150、ICR1=0 Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 200, 150, 0); // 使能TOV1中断 Drv_Timer1_INT_Enable(INT_MODE_TOV, ENABLE); // PD[5:4](OC1B和OC1A)初始化为输出0 DDRD = (1 << DDD4) | (1 << DDD5); PORTD &= ~((1 << PD2 ) | (1 << PD5 )); // 开全局中断 sei(); // --------- while(1) { } return 0; }
示波器输出:
1、CH1通道是OC1A引脚的输出,CH2通道是 OC1B引脚的输出。
OC1A引脚和OC1B引脚都输出脉冲,电平翻转的周期是202us ,示波器测得的频率是2.46668Khz。
理论计算得到比较匹配的周期、即电平翻转周期 T = ((1.0/8000000)*1000000)*8*(200+1) = 201 us。
对应的脉冲周期是402us,频率是2487hz,两者基本一致。
2、另外、OC1A和OC1B的相位不同,原因是OCR1A=200,而OCR1B=150。
所以OC1B在TCNT1=150时先发生比较匹配,OC1B引脚的电平翻转。
等到TCNT1=200时OC1A才发生比较匹配,OC1A引脚的才翻转电平。
上图中、左边光标CurA处于0.00us的位置,正好是OC1A发生比较匹配电平翻转的时刻,此时TCNT1被清0。
而定时器1的一个时钟周期是 t = ((1.0/8000000)*1000000)*8 = 1 us 。
TCNT1从0计数到150,将到达右边光标CurB=150us的位置,此时OC1B发生比较匹配。
在等50us,TCNT1=200,OC1A才发生比较匹配。
所以、OC1A滞后OC1B50个计数周期(50us)才发生比较匹配。
也就是、OC1B的相位有OCR1B决定。
3、TCNT1没有计数溢出,所以TOV1没有发生,PA2引脚就没有电平变化。
4、使能OCF1A和OCF1B后,PA3和PA5才会有波形输出。
而且PA3在OC1A发生比较匹配时翻转引脚电平,PA5在OC1B发生比较匹配时翻转引脚电平。
示波器输出如下:
CH1是PA3的波形,CH2是PA5的波形。
PA3比PA5滞后50us发生引脚电平翻转,和OC1A滞后OC1B的情形一致。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、此时OCR1A和OCR1B都用来控制OC1A和OC1B的相位,而波形的周期有ICR1决定。
OCR1A和OCR1B都不要大于ICP1,否则就没有波形输出。
2、TOV1在TCNT1=MAX时被触发。
ICF1在TCNT1=BOTTOM时被触发。
修改main.c如下:
// 定时器1 初始化:CTC_ICR1模式、COM1A启用(电平翻转)、COM1B启用(电平翻转)、8预分频 Drv_Timer1_init(T1_WGM_CTC_ICR1, T1_COM_MODE_TOGGLE, T1_COM_MODE_TOGGLE, T1_CLK_SOURCE_CLK_8); // 设置TCTN1=0、OCR1A=20000、OCR1B=30000、ICR1=65535 Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 20000, 30000, 65535); // 使能TOV1中断、OCF1A中断,OCF1B中断 Drv_Timer1_INT_Enable(INT_MODE_TOV, ENABLE); Drv_Timer1_INT_Enable(INT_MODE_OCF1A, ENABLE); Drv_Timer1_INT_Enable(INT_MODE_OCF1B, ENABLE);
0、预测一下输出波形:
OCR1A=20000、OCR1B=30000、ICR1=65535。
每次TCNT1=20000时、OC1A发生比较匹配并翻转电平,每次TCNT1=30000时、OC1B发生比较匹配并翻转电平。
每次TCNT1=65535时清0,又重头计数,同时TOV1溢出、PA2引脚翻转电平(每次TCNT1溢出时翻转电平)。
OC1A和OC1B之间的相位相差10ms,OC1B和PA2之间的相位相差35.535ms。
OC1A、OC1B和PA2的波形产生如下:
示波器输出如下:
1、CH1是OC1A的输出,CH2是OC1B的输出,测试得他们之间相隔10ms。
2、CH1是OC1B的输出,CH2是PA2的输出,测试得他们之间相隔35.6ms。
3、他们的周期都是66ms,和理论计算基本一致。
他们互相之间的相位差也和上面预测的一致。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、CTC输出的脉冲频率最大可达4MHz。
即系统时钟clk_IO的一半。
此时OCR1A=0。
2、比较匹配时OC1A和OC1B引脚清0/置1的情况也没什么需要特别测试的。
3、强制比较匹配FOC1A和FOC1B的用途有待测试
-------------------------------------------------------------------------------------------------------------------------------------
1、TCNT1=OCR1A/OCR1B时,OC1A/OC1B引脚电平翻转一次。
接着、继续计数到TCNT1=TOP时、OC1A/OC1B引脚电平再翻转一次,完成一个周期。
也就是说、TOP值决定了周期 T = ((1.0/clk_T1)*1000000)*DIV*(TOP+1),其中DIV为分频系数。
2、TOV1在TCNT1=TOP时触发,而不是计数到0xFFFF才触发,与普通计数不同。
比如TOP=0x01FF时,TCNT1计数到0x01FF就被清0,完成一个周期,
而TOV1会被触发,虽然TCNT1没有计数到0xFFFF并溢出。
-------------------------------------------------------------------------------------------------------------------------------------
// ========================================================================================================== // 主函数 // ========================================================================================================== #include <avr/io.h> #include <avr/interrupt.h> #include "Drv_Timer.h" #include "system.h" #include "config.h" // ========================================================================================================== // 伪中断BADISR_vect // // ========================================================================================================== ISR(BADISR_vect) { } // ========================================================================================================== // main函数 // ========================================================================================================== int main(void) { // --------- // 关全局中断 cli(); // 系统初始化 sys_init(); // PA[5:2]初始化为输出0 DDRA = (1 << DDA2) | (1 << DDA3) | (1 << DDA5) | (1 << DDA6); PORTA &= ~((1 << PA2 ) | (1 << PA3 ) | (1 << PA5 ) | (1 << PA6 )); // 定时器1 初始化:快速PWM-OCR1A模式、COM1A启用(取反)、COM1B启用(清0)、8预分频 Drv_Timer1_init(T1_WGM_8_FAST_PWM, T1_COM_MODE_CLEAR, T1_COM_MODE_CLEAR, T1_CLK_SOURCE_CLK_8); // 设置TCTN1=0、OCR1A=100、OCR1B=200、ICR1=200 Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 100, 200, 200); // 使能TOV1中断、OCF1A中断,OCF1B中断 Drv_Timer1_INT_Enable(INT_MODE_TOV, ENABLE); Drv_Timer1_INT_Enable(INT_MODE_OCF1A, ENABLE); Drv_Timer1_INT_Enable(INT_MODE_OCF1B, ENABLE); // PD[5:4](OC1B和OC1A)初始化为输出0 DDRD = (1 << DDD4) | (1 << DDD5); PORTD &= ~((1 << PD4 ) | (1 << PD5 )); // 开全局中断 sei(); // --------- while(1) { } return 0; }
示波器输出如下:
1、CH1为OC1A的输出,CH2为OC1B的输出。
OC1A的高电平宽度为100us,之后OC1A被拉低,也就是TCNT1=OCR1A=100时,OC1A被拉低。
OC1B的高电平宽度为200us,波形产生的情形和OC1A一致。
波形的周期是3.87KHz,对应的周期为258us,和计算基本一致。
2、也就是说OCR1A和OCR1B定义了高电平的宽度,TOP定义了波形的周期,高电平的占空比=OCR1A/TOP和OCR1B/TOP。
3、TOV1在TCNT1=TOP值时被触发,所以PA2引脚每隔256us翻转一次。
PA3在OC1A被拉低时(发生比较匹配) 翻转电平,PA5则在OC1B被拉低时翻转电平。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、TOP=0xFF,那么比较逻辑就只和OCR1A/OCR1B的低8位比较,超过TOP的部分都被屏蔽为0。
即、在TCNT1=(OCR1A或OCR1B)&0xFF的时刻发生比较匹配。
例如OCR1A=20000=0x4E20,OCR1B=30000=0x7530。
那么、OC1A在TCNT1=(OCR1A)&0xFF=0x4E20&0xFF=0x20=32的时刻发生比较匹配。
OC1B在TCNT1=(OCR1B)&0xFF=0x7530&0xFF=0x30=48的时刻发生比较匹配。
// 设置TCTN1=0、OCR1A=20000、OCR1B=30000、ICR1=65535 Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 20000, 30000, 65535);
示波器输出如下:
1、CH1为OC1A的输出,CH2为OC1B的输出。
OC1A的高电平宽度为32us,即在TCNT1=32的时刻发生比较匹配、将OC1A引脚拉低。
OC1B的高电平宽度为48us,即在TCNT1=48的时刻发生比较匹配、将OC1B引脚拉低。
随后,TCNT1=TOP=255时,一个周期结束,OC1A和OC1B的引脚再次翻转,结束这个周期。
波形频率为3.87KHz,对应的周期为258us,和计算基本一致。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、这里在OC1A和OC1B这两个通道上分别测试两种极限值情形
// 设置TCTN1=0、OCR1A=0、OCR1B=255、ICR1=200 Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 0, 255, 200);
示波器输出如下:
1、CH1为OC1A的输出,CH2为OC1B的输出。
OC1A的高电平宽度为1us,OC1B为100%占空比的高电平。
这说明不能得到0%的占空比,只能通过将OC1A/OCR1B引进设置为普通IO,再将其拉低来实现0%的占空比。
但可以得到100%的占空比。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、OCR1A和OCR1B超过TOP值,则实际的比较匹配时刻是TCNT1=(OCR1A或OCR1B)&TOP。
比如、10位快速PWM模式下,设置OCR1A=1500,OCR1B=2000。
此时实际的比较匹配时刻是TCNT1=(OCR1A或OCR1B)&TOP=(1500或2000)&0x3FF=476us或976us。
main.c中修改定时器1的配置如下:
// 定时器1 初始化:快速PWM模式、COM1A启用(清0)、COM1B启用(清0)、8预分频 Drv_Timer1_init(T1_WGM_10_FAST_PWM, T1_COM_MODE_CLEAR, T1_COM_MODE_CLEAR, T1_CLK_SOURCE_CLK_8); // 设置TCTN1=0、OCR1A=1500、OCR1B=2000、ICR1=200 Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 1500, 2000, 200);
示波器输出如下:
1、CH1为OC1A的输出,CH2为OC1B的输出。
OC1A的高电平宽度为480us,OC1B的高电平宽度为984us,波形周期是1030us,和预测的基本一致。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、这个和8位快速PWM一样,只是分辨率由ICR1决定,TOP=ICR1,ICR1这个寄存器决定了波形周期。
由于是使用ICR1寄存器确定周期,所以可以得到更多的频率,而不像定时器2那样只能是固定的分辨率。
另外、使用OCR1A/OCR1B寄存器确定占空比,所以可以得到相当精确的占空比,比如OCR1A=20,ICR1=200时、得到精确的10%的占空比。
而要得到38KHz(周期为26us)的红外载波,就可以设置ICR1=25,得到的周期为T= ((1.0/8000000)*1000000)*8*(25+1) = 26us,没有任何误差。
2、当然,正常输出时也不要让OCR1A和OCR1B超过ICR1值,超出了ICR1值、就没有波形输出,和上面的3种固定分辨率的TOP值不一样。
因为这里的TOP值时可变的,超过TOP的部分不会被屏蔽为0。
结果是TCNT1在TCNT1=ICR1时被清0,不会再继续加1、达到比ICR1更大的值。
结果就是、OCR1A和OCR1B永远也不会和TCNT1发生比较匹配。
3、上面讨论的极限值的情况适用于这里。
4、TOV1在TCNT1=BOTTOM时被触发。
ICF1在TCNT1=BOTTOM时被触发。
main.c修改定时器1的配置如下:
// 定时器1 初始化:快速PWM-ICR1模式、COM1A启用(清0)、COM1B启用(清0)、8预分频 Drv_Timer1_init(T1_WGM_FAST_PWM_ICR1, T1_COM_MODE_CLEAR, T1_COM_MODE_CLEAR, T1_CLK_SOURCE_CLK_8); // 设置TCTN1=0、OCR1A=400、OCR1B=1600、ICR1=40000 Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 400, 1600, 4000);
示波器输出如下:
1、CH1为OC1A的输出,CH2为OC1B的输出,波形周期是4002us。
OC1A的高电平宽度为400us,占空比为精确的10%。
OC1B的高电平宽度为1600us,占空比为精确的40%。
2、其他的20%、80%等占空比都能精确的得到。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
0、ICR1设置为25,得到26us的周期,对应的频率为38KHz。
26.0/3=8.6,所以设置OCR1A=7、得到8us的高电平,对应的占空比是30.7%。
设置OCR1B=8、得到9us的高电平,对应的占空比是34.6%。
main.c修改定时器1的配置如下:
// 定时器1 初始化:快速PWM-ICR1模式、COM1A启用(清0)、COM1B启用(清0)、8预分频 Drv_Timer1_init(T1_WGM_FAST_PWM_ICR1, T1_COM_MODE_CLEAR, T1_COM_MODE_CLEAR, T1_CLK_SOURCE_CLK_8); // 设置TCTN1=0、OCR1A=7、OCR1B=8、ICR1=25 Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 7, 8, 25);
示波器输出如下:
1、CH1为OC1A的输出,CH2为OC1B的输出,波形周期26.2us,对应的频率是38.1KHz。
OC1A的高电平宽度为8us,占空比为的30.5%。
OC1B的高电平宽度为9us,占空比为精确的34.4%。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、这个模式用来在OCR1A引脚产生50%占空比的方波,和CTC模式类似,波形周期由OCR1A决定。
而OC1B引脚的COM1A[1:0]=1配置不是PWM引脚,而是普通IO。
但OC1B的COM1A[1:0]=2或COM1A[1:0]=3是可以输出PWM波形的,波形周期和OCR1A引脚的方波一致。
2、OCR1B超过TOP=OCR1A时超过TOP的部分不会被屏蔽为0,结果是OC1B引脚就没有脉冲输出。
因为TCNT1的计数值不会超过TOP=OCR1A。
3、OCR1A是双缓冲寄存器,所以更新OCR1A时,当前的OCR1A值还可以和TCNT1进行比较,不会丢失可能的一次比较匹配。
在下一个时钟周期里,才从缓冲寄存器里面更新OCR1A。
这比较适合用来产生频率变化的波形,而不会产生毛刺。
main.c修改定时器1的配置如下:
// 定时器1 初始化:快速PWM-OCR1A模式、COM1A启用(取反)、COM1B启用(清0)、8预分频 Drv_Timer1_init(T1_WGM_FAST_PWM_OCR1A, T1_COM_MODE_TOGGLE, T1_COM_MODE_CLEAR, T1_CLK_SOURCE_CLK_8); // 设置TCTN1=0、OCR1A=400、OCR1B=200、ICR1=400 Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 400, 100, 400);
示波器输出如下:
1、CH1为OC1A的输出,CH2为OC1B的输出,
OC1A引脚输出周期为2*400us的方波、占空比为50%。
OC1B 引脚输出周期为400us的PWM波形,占空比为100/400=25%。
2、OC1A的频率可变,但用于是方波。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、在配置为COM1A[1:0]=2或COM1A[1:0]=3上、OC1A引脚都没有脉冲输出。
因为OCR1A用来控制周期,那么就没有寄存器来控制OC1A引脚的占空比了。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、例如:8位模式下、COM1A[1:0]=0b10时、在升序计数时发生比较匹配将清0 OC1A,降序计数时发生比较匹配将置位 OC1A。
波形产生过程如下:
2、可以看出、2路OCR1A不同的波形,占空比不一样,高电平在TCNT1计数溢出到0的点的两侧对称。
2路输出的高电平的中点都是TCNT1=0点的位置,所以2路波形有共同的对称中心。
OC1A高电平宽度是OCR1A设置的2倍,上升沿和下降沿的宽度都是OCR1A的设置值。
3、另外、TOP值决定了波形的周期/频率,周期 T = 2*((1.0/clk_T1)*1000000)*DIV*TOP us,其中DIV为预分频系数。
一个周期的计数方式是:0-->255-->254-->1,下一个计数时钟里就计数到0溢出,所以计数次数=256+254=510。
所以8MHz、8预分频下的周期 T = ((1.0/8000000)*1000000)*8*510=510us,对应的频率是1.96KHz。
4、如果修改TOP值,最好在一个周期结束后修改。
否则可能导致上升斜坡和下降斜坡长度不一致,导致一个周期的波形不对称,结果就是在这个周期里、占空比超出预期或低于预期。
如果这对应用有影响,就应该使用相位与频率修正PWM来做TOP值可变的PWM,可以避免这种不对称波形。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、TOP值固定、也就是频率固定。
2、如果OCR1A和OCR1B的值超过TOP值时,超出部分将被屏蔽为0。
比如、8位宽度时、如果OCR1A=20000=0x4E20,那么实际比较值是0x4E20&0xFF=0x20=32。
也就是OCR1A=20000的波形和OCR1A=32的波形一致。
main.c中的定时器1配置如下:
// 定时器1 初始化:8位相位修正PWM模式、COM1A启用(清0)、COM1B启用(清0)、8预分频 Drv_Timer1_init(T1_WGM_8_PHASE_PWM, T1_COM_MODE_CLEAR, T1_COM_MODE_CLEAR, T1_CLK_SOURCE_CLK_8); // 设置TCTN1=0、OCR1A=200、OCR1B=20000、ICR1=400 Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 200, 20000, 400);
示波器输出如下:
1、CH1是OC1A的输出,CH2是OC1B的输出。
OC1A的高电平宽度是400us,是OCR1A值的2倍,而图中只给出了OC1A左边到对称中心的宽度,即200us。
加上对称的右边,就是400us,和设置的一致。对称中心的左边是计数的下降沿、而右边是计数的上升沿。
对应频率是1.944KHz,和计算的1.96KHz基本一致。
2、OC1B的高电平宽度是64us。
OCR1B=0x4E20,超过8位宽度,实际比较值是0x4E20&0xFF=0x20=32。
对应的高电平宽度是2*32us=64us,和示波器输出一致。
3、2路输出拥有共同的对称中心,该对称中心就是TCNT1计数到0的点。
在TCNT1计数到0的点上、TOV1被触发,在TOV1中断中将PA2引脚发生电平翻转。
也就是说、PA2发生电平翻转的位置,就是对称中心所在的位置。
示波器输出如下:
CH1是OC1A的输出,CH2是PA2的输出。
可以看出、PA2发生电平翻转的位置,正好就是OC1A的对称中心。其频率是1.95KHz,和计算的基本一致。
4、如果将TOP改为10位宽度,即TOP=0x3FF=1023,对应的周期是1/1023/2=488Hz。
发生比较匹配的时刻就是上升沿和下降沿的 OCR1B&0x3FF=20000&0x3FF=0x220=544 的位置。
对应的高电平宽度是2*544=1088us。
示波器输入如下:
CH1是OC1A的输出,CH2是OC1B的输出。
OC1A的输出依然是400us,OC1B的输出是1100us,和计算的1088us基本一致。
波形周期是484Hz,和计算得到的488Hz基本一致。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、这里使用OCR1A=0和OCR1B=255来测试两种极限值情况,8位的TOP(TOP=255)。
main.c中修改定时器1的配置如下:
// 定时器1 初始化:8位相位修正PWM模式、COM1A启用(清0)、COM1B启用(清0)、8预分频 Drv_Timer1_init(T1_WGM_8_PHASE_PWM, T1_COM_MODE_CLEAR, T1_COM_MODE_CLEAR, T1_CLK_SOURCE_CLK_8); // 设置TCTN1=0、OCR1A=200、OCR1B=20000、ICR1=400 Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 0, 255, 400);
示波器输出如下:
1、CH1是OC1A的输出,CH2是PA3的输出。
OC1A输出低电平、即0%占空比的PWM,OC1B输出高电平、即100%占空比的PWM。
PA3在比较匹配时翻转电平,所以虽然OC1A和OC1B没有脉冲输出,但是比较匹配确实在发生着。
且PA3的周期是512us,和TOP=255时的510us基本一致。
所以、可以得到0%和100%的占空比。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、这个模式和固定宽度模式下的情形基本一致,波形周期由ICR1决定。
不同点在于OCR1A和OCR1B超出TOP时,不再有波形输出。
2、可以得到0%和100%的占空比,和固定宽度模式下的情形一致。
3、TOP=ICR1,是可变的,但不推荐修改。
使用相位与频率修正PWM来做TOP值/频率可变的PWM。
4、TOV1在TCNT1=BOTTOM时被触发。
ICF1在TCNT1=TOP时被触发。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、波形频率由TOP=OCR1A决定。
说明:
1、OC1A引脚取反的配置下,OC1A引脚输出方波,和CTC模式下输出方波的情形一致。
2、OC1B引脚在取反的配置下、没有波形输出,此配置下的OC1B是普通IO口。
下面的测试中、OC1B/PD4引脚在CPU的控制下、用输出周期为2ms的方波。
main.c如下:
// ========================================================================================================== // 主函数 // ========================================================================================================== #include <avr/io.h> #include <avr/interrupt.h> #include "Drv_Timer.h" #include "system.h" #include "config.h" // ========================================================================================================== // 伪中断BADISR_vect // // ========================================================================================================== ISR(BADISR_vect) { } // ========================================================================================================== // main函数 // ========================================================================================================== int main(void) { // --------- // 关全局中断 cli(); // 系统初始化 sys_init(); // PA[5:2]初始化为输出0 DDRA = (1 << DDA2) | (1 << DDA3) | (1 << DDA5) | (1 << DDA6); PORTA &= ~((1 << PA2 ) | (1 << PA3 ) | (1 << PA5 ) | (1 << PA6 )); // 定时器1 初始化:TOP=OCR1A的相位修正PWM模式、COM1A启用(取反)、COM1B启用(取反)、8预分频 Drv_Timer1_init(T1_WGM_PHASE_PWM_OCR1A, T1_COM_MODE_TOGGLE, T1_COM_MODE_TOGGLE, T1_CLK_SOURCE_CLK_8); // 设置TCTN1=0、OCR1A=400、OCR1B=100、ICR1=400 Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 400, 100, 400); // 使能TOV1中断、OCF1A中断,OCF1B中断 Drv_Timer1_INT_Enable(INT_MODE_TOV, ENABLE); Drv_Timer1_INT_Enable(INT_MODE_OCF1A, ENABLE); Drv_Timer1_INT_Enable(INT_MODE_OCF1B, ENABLE); // PD[5:4](OC1B和OC1A)初始化为输出0 DDRD = (1 << DDD4) | (1 << DDD5); PORTD &= ~((1 << PD4 ) | (1 << PD5 )); // 开全局中断 sei(); // --------- while(1) { PORTD ^= (1 << PD4); delay_ms(1); } return 0; }
示波器输出如下:
1、OC1A输出方波,高电平宽度为800us=2*400,和设置的一致。
2、OC1B配置为取反模式,此模式下不作为比较匹配输出引脚,而是普通IO。
在主循环中每1ms翻转一次电平。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、OC1A在其他配置下没有波形输出,因为OCR1A用来控制频率,就没有其他寄存器用来控制占空比了。
2、OC1B引脚的其他配置有PWM输出,周期由TOP=OCR1A控制。但OCR1B超过TOP=OCR1A时没有波形输出。
3、OC1B的PWM可以得到0%和100%的占空比。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、可以得到0%和100%的占空比。
说明:
1、波形周期由ICR1决定。
2、OCR1A和OCR1B超过TOP=ICR1时、OC1A和OC1B引脚没有波形输出。
3、TOV1在TCNT1=BOTTOM时被触发。
ICF1在TCNT1=TOP时被触发。
-------------------------------------------------------------------------------------------------------------------------------------
说明:
1、波形周期由OCR1A决定。
2、OCR1B超过TOP=OCR1A时、OC1B引脚没有波形输出。
3、而OC1A只能产生方波,不能产生PWM波形,和CTC模式下的情形一致。
4、TOV1在TCNT1=BOTTOM时被触发。
ICF1在TCNT1=TOP时被触发。
|<----实际测试中ICF1的触发周期是TOP=OCR1A周期的2倍、且常常会有不被触发的时刻。
似乎仅在使用ICR1作为TOP值的时候,ICF1中断才不会有丢失的情形。
-------------------------------------------------------------------------------------------------------------------------------------
1、在初始化中、一般不在意多消耗50ms或少消耗10ms,此时可以使用自动运算替代手工设置来方便我们的编程。
下面以"定时器1生成38KHz(1/3占空比)的红外载波"为例:
// ========================================================================================================== // 红外发送初始化 @ 8MHz // // (1). 定时器1生成38KHz载波(1/3占空比) // 周期为T = ((1.0/8000000)*1000000)*8*(25+1) = 26us // = (1/8000000)*div*(N+1)(单位:秒) // F = 8000000 / (div * (N + 1))(单位:Hz) // N = 8000000 / ( freq * div ) - 1 // = 8000000 / freq / div - 1 // 占空比 = OCR1A / ICR1 = 9 / 27 // (2). 载波将在PD5(OC1A)引脚上输出 // (3). 初始化的耗时是可以接受的,所以使用除法吧 // // ========================================================================================================== void MOD_IR_send_init(void) { uint16_t div; // 16bit宽度的分频比 uint16_t freq = 38000; // 16bit宽度的频率 uint8_t duty = 33; // 33%占空比 uint8_t icr1; uint8_t ocr1a; uint8_t DIV = T1_CLK_SOURCE_DIV_8; // ------------------------------------------------ switch(DIV) { case T1_CLK_SOURCE_DIV_1: div = 1; break; case T1_CLK_SOURCE_DIV_8: div = 8; break; case T1_CLK_SOURCE_DIV_64: div = 64; break; case T1_CLK_SOURCE_DIV_256: div = 256; break; case T1_CLK_SOURCE_DIV_1024: div = 1024; break; default: return; } icr1 = SYS_OSC_FREQUENCE / freq / div - 1; // 运算结果是8位的、但分步的中间结果可能是16位或32位的 ocr1a = (icr1 * duty) / 100; // ------- // 设置参数 Drv_Timer1_init(T1_WGM_FAST_PWM_ICR1, COM_MODE_CLEAR, COM_MODE_NONE, DIV); Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, ocr1a, 0, icr1); Drv_IO_mode_bit(DDRD, DDD5, IO_OUTPUT); Drv_IO_clr_bit(PORTD, PD5); }
在需要修改波形的频率和占空比的时候、只需要修改freq和duty这两个变量即可,不必每次去手动计算。
尤其是当我们修改了芯片主频SYS_OSC_FREQUENCE的时候、我们无需再次修改这个函数。
但如果要求精确的时钟、在修改这些参数时、还是需要检验并微调函数中的参数。
0
0
0
0
0
0