B001-Atmega16-定时器1-(ques=1)_Manon_des_sources的博客-程序员ITS304

技术标签: CTC  PWM  相位修正PWM  B001-Atmega16-BSP-C语言  Atmega16-定时器1  相位频率修正PWM  

Atmega16-定时器1的使用 step by step。
之前完成了定时器2的各项功能的测试后,这里就很容易做了。


编译环境:AVR Studio 4.19 +avr-toolchain-installer-3.4.1.1195-win32.win32.x86
芯片型号:ATmega16
芯片主频:8MHz


测试说明:

1、 OC1AOC1B引脚输出比较匹配的波形
2、 PA2TOV1中断时取反
3、 PA3OCF1A中断时取反
4、 PA5OCF1B中断时取反
5、 PA6ICF1中断时取反


-------------------------------------------------------------------------------------------------------------------------------------

小结:

1、最好使用 CTC模式来做定时功能。

      因为这样就只需要设置OCR1A,不需要每次TOV1中断都要重新设置TCNT1的计数初值。
2、CTC模式下、OC1AOC1B的频率由OCR1AICR1决定,OCR1AOCR1B都用来控制OC1AOC1B的相位。
      CTC模式下的脉冲频率最大可达4MHz
3、快速/高频PWM用于:功率调节、整流、ADC
      当OCR1A/OCR1B超过TOP值时,依然有PWM波形输出:TCNT1=(OCR1A或OCR1B)&TOP时发生比较匹配
      可以得到相当精确的PWM频率和占空比,如38KHz1/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;
}

测试结果:

1、 PA2引脚电平每个 66ms翻转一次。
      定时器1溢出周期为 T = ((1.0/8000000)*1000000) * 8 * 65536 / 1000 = 65.536 ms
      两者基本一致。


-------------------------------------------------------------------------------------------------------------------------------------

第二步: CTC模式

1、使用OCR1A作为TOP值:WGM[13:10] = 0100

说明:

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、另外、OC1AOC1B的相位不同,原因是OCR1A=200,而OCR1B=150

      所以OC1BTCNT1=150时先发生比较匹配,OC1B引脚的电平翻转。

      等到TCNT1=200OC1A才发生比较匹配,OC1A引脚的才翻转电平。

     上图中、左边光标CurA处于0.00us的位置,正好是OC1A发生比较匹配电平翻转的时刻,此时TCNT1被清0。

     而定时器1的一个时钟周期是 t = ((1.0/8000000)*1000000)*8 = 1 us 。

     TCNT10计数到150,将到达右边光标CurB=150us的位置,此时OC1B发生比较匹配。

     在等50usTCNT1=200OC1A才发生比较匹配。

     所以、OC1A滞后OC1B50个计数周期(50us)才发生比较匹配。

     也就是、OC1B的相位有OCR1B决定。

3、TCNT1没有计数溢出,所以TOV1没有发生,PA2引脚就没有电平变化。

4、使能OCF1AOCF1B后,PA3PA5才会有波形输出。

      而且PA3OC1A发生比较匹配时翻转引脚电平,PA5OC1B发生比较匹配时翻转引脚电平。

      示波器输出如下:


      CH1PA3的波形,CH2PA5的波形。

      PA3PA5滞后50us发生引脚电平翻转,和OC1A滞后OC1B的情形一致。


-------------------------------------------------------------------------------------------------------------------------------------

2、使用ICR1作为TOP值(WGM[13:10] = 1100)

说明:

1、此时OCR1AOCR1B都用来控制OC1AOC1B的相位,而波形的周期有ICR1决定。

      OCR1AOCR1B都不要大于ICP1,否则就没有波形输出。

2、TOV1TCNT1=MAX时被触发。

       ICF1TCNT1=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溢出时翻转电平)。

      OC1AOC1B之间的相位相差10msOC1BPA2之间的相位相差35.535ms

      OC1AOC1BPA2的波形产生如下:


示波器输出如下:


1、CH1是OC1A的输出,CH2OC1B的输出,测试得他们之间相隔10ms



2、CH1OC1B的输出,CH2PA2的输出,测试得他们之间相隔35.6ms

3、他们的周期都是66ms,和理论计算基本一致。

      他们互相之间的相位差也和上面预测的一致。


-------------------------------------------------------------------------------------------------------------------------------------

3、其他测试:

说明:

1、CTC输出的脉冲频率最大可达4MHz

      即系统时钟clk_IO的一半。

      此时OCR1A=0

2、比较匹配时OC1AOC1B引脚清0/置1的情况也没什么需要特别测试的。

3、强制比较匹配FOC1AFOC1B的用途有待测试

|<----待测试-question-001

-------------------------------------------------------------------------------------------------------------------------------------

第三步: 快速PWM模式

0、PWM如何产生


1、TCNT1=OCR1A/OCR1B时,OC1A/OC1B引脚电平翻转一次。

      接着、继续计数到TCNT1=TOP时、OC1A/OC1B引脚电平再翻转一次,完成一个周期。

      也就是说、TOP值决定了周期 T = ((1.0/clk_T1)*1000000)*DIV*(TOP+1),其中DIV为分频系数。

2、TOV1TCNT1=TOP时触发,而不是计数到0xFFFF才触发,与普通计数不同。

      比如TOP=0x01FF时,TCNT1计数到0x01FF就被清0,完成一个周期,

      而TOV1会被触发,虽然TCNT1没有计数到0xFFFF并溢出。


-------------------------------------------------------------------------------------------------------------------------------------

1、8位快速PWM( TOP = 0xFF )

1-1、正常输出

说明:
1、 TOP=255,所以周期 T= ((1.0/8 000000)*1000000)*8*(255+1) = 256us
      一个定时器1时钟周期就是 1us
2、 PWM的占空比 =OCR1A /TOPOCR1B/TOP
3、下面的测试用来产生周期为 256us,占空比为 100/256%200%256%PWM波形。

测试代码:

// ==========================================================================================================
// 主函数
// ==========================================================================================================
#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、CH1OC1A的输出,CH2OC1B的输出。

      OC1A的高电平宽度为100us,之后OC1A被拉低,也就是TCNT1=OCR1A=100时,OC1A被拉低。

      OC1B的高电平宽度为200us,波形产生的情形和OC1A一致。

      波形的周期是3.87KHz,对应的周期为258us,和计算基本一致。

2、也就是说OCR1AOCR1B定义了高电平的宽度,TOP定义了波形的周期,高电平的占空比=OCR1A/TOPOCR1B/TOP

3、TOV1TCNT1=TOP值时被触发,所以PA2引脚每隔256us翻转一次。

      PA3OC1A被拉低时(发生比较匹配) 翻转电平,PA5则在OC1B被拉低时翻转电平。


-------------------------------------------------------------------------------------------------------------------------------------

1-2、OCR1AOCR1B大于TOP,依然有PWM输出

说明:

1、TOP=0xFF,那么比较逻辑就只和OCR1A/OCR1B低8位比较,超过TOP的部分都被屏蔽为0。

      即、在TCNT1=(OCR1A或OCR1B)&0xFF的时刻发生比较匹配。

      例如OCR1A=20000=0x4E20OCR1B=30000=0x7530

      那么、OC1ATCNT1=(OCR1A)&0xFF=0x4E20&0xFF=0x20=32的时刻发生比较匹配。

      OC1BTCNT1=(OCR1B)&0xFF=0x7530&0xFF=0x30=48的时刻发生比较匹配。

测试代码:

Drv_Timer.c不变, main.c修改定时器1的配置如下:

    // 设置TCTN1=0、OCR1A=20000、OCR1B=30000、ICR1=65535
    Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 20000, 30000, 65535);

测试结果:

示波器输出如下:


1、CH1OC1A的输出,CH2OC1B的输出。

      OC1A的高电平宽度为32us,即在TCNT1=32的时刻发生比较匹配、将OC1A引脚拉低。

      OC1B的高电平宽度为48us,即在TCNT1=48的时刻发生比较匹配、将OC1B引脚拉低。

      随后,TCNT1=TOP=255时,一个周期结束,OC1AOC1B的引脚再次翻转,结束这个周期。

     波形频率为3.87KHz,对应的周期为258us,和计算基本一致。


-------------------------------------------------------------------------------------------------------------------------------------

1-3、极限值情形( OCR1A = 0,OCR1B = 255 )

说明:

1、这里在OC1A和OC1B这两个通道上分别测试两种极限值情形

测试代码:

main.c中修改定时器1的配置如下:
    // 设置TCTN1=0、OCR1A=0、OCR1B=255、ICR1=200
    Drv_Timer1_set_TCNT1_OCR1A_OCR1B_ICR1(0, 0, 255, 200);

测试结果:

示波器输出如下:


1、CH1OC1A的输出,CH2OC1B的输出。

      OC1A的高电平宽度为1usOC1B100%占空比的高电平。

      这说明不能得到0%占空比,只能通过将OC1A/OCR1B引进设置为普通IO,再将其拉低来实现0%占空比

      但可以得到100%的占空比。

-------------------------------------------------------------------------------------------------------------------------------------

2、9位快速PWM 和 10位快速PWM ( TOP = 0x01FF 和 0x03FF )

说明:
1、这个和 8位快速PWM一样,只是分辨率变成了 9位10位TOP=0x01FF0x03FF,波形周期变成了 512us1024us

2-1、OCR1A和OCR1B大于TOP的PWM

说明:

1、OCR1A和OCR1B超过TOP值,则实际的比较匹配时刻TCNT1=(OCR1A或OCR1B)&TOP

      比如、10位快速PWM模式下,设置OCR1A=1500,OCR1B=2000

      此时实际的比较匹配时刻TCNT1=(OCR1A或OCR1B)&TOP=(15002000)&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、CH1OC1A的输出,CH2OC1B的输出。

      OC1A的高电平宽度为480usOC1B的高电平宽度为984us,波形周期是1030us,和预测的基本一致。


-------------------------------------------------------------------------------------------------------------------------------------

3、TOP = ICR1的快速PWM

说明:

1、这个和8位快速PWM一样,只是分辨率由ICR1决定,TOP=ICR1ICR1这个寄存器决定了波形周期。

      由于是使用ICR1寄存器确定周期,所以可以得到更多的频率,而不像定时器2那样只能是固定的分辨率。

      另外、使用OCR1A/OCR1B寄存器确定占空比,所以可以得到相当精确的占空比,比如OCR1A=20,ICR1=200时、得到精确的10%的占空比

      而要得到38KHz(周期为26us)的红外载波,就可以设置ICR1=25,得到的周期为T= ((1.0/8000000)*1000000)*8*(25+1) = 26us,没有任何误差。

2、当然,正常输出时也不要让OCR1AOCR1B超过ICR1值,超出了ICR1值、就没有波形输出,和上面的3种固定分辨率TOP值不一样。

      因为这里的TOP值时可变的,超过TOP的部分不会被屏蔽为0。

      结果是TCNT1在TCNT1=ICR1时被清0,不会再继续加1、达到比ICR1更大的值。

      结果就是、OCR1AOCR1B永远也不会和TCNT1发生比较匹配。

3、上面讨论的极限值的情况适用于这里。

4、TOV1TCNT1=BOTTOM时被触发。

       ICF1TCNT1=BOTTOM时被触发。


3-1、产生精确的占空比

测试代码:

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、CH1OC1A的输出,CH2OC1B的输出,波形周期是4002us

      OC1A的高电平宽度为400us,占空比为精确的10%

      OC1B的高电平宽度为1600us,占空比为精确的40%

2、其他的20%80%等占空比都能精确的得到。


-------------------------------------------------------------------------------------------------------------------------------------

3-2、产生38KHz30%占空比的红外载波

说明:

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、CH1OC1A的输出,CH2OC1B的输出,波形周期26.2us,对应的频率是38.1KHz

      OC1A的高电平宽度为8us,占空比为的30.5%

      OC1B的高电平宽度为9us,占空比为精确的34.4%


-------------------------------------------------------------------------------------------------------------------------------------

4、TOP = OCR1A的快速PWM

说明:

1、这个模式用来在OCR1A引脚产生50%占空比的方波,和CTC模式类似,波形周期由OCR1A决定。

      而OC1B引脚的COM1A[1:0]=1配置不是PWM引脚,而是普通IO

      但OC1BCOM1A[1:0]=2COM1A[1:0]=3是可以输出PWM波形的,波形周期和OCR1A引脚的方波一致。

2、OCR1B超过TOP=OCR1A时超过TOP的部分不会被屏蔽为0,结果是OC1B引脚就没有脉冲输出。

      因为TCNT1的计数值不会超过TOP=OCR1A

3、OCR1A双缓冲寄存器,所以更新OCR1A时,当前的OCR1A值还可以和TCNT1进行比较,不会丢失可能的一次比较匹配。

      在下一个时钟周期里,才从缓冲寄存器里面更新OCR1A。

      这比较适合用来产生频率变化的波形,而不会产生毛刺

4-1、OCR1A产生方波OCR1B产生PWM

说明:

1、COM1A[1:0]=1的配置下、OC1A产生方波。

测试代码:

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、CH1OC1A的输出,CH2OC1B的输出,

      OC1A引脚输出周期为2*400us方波、占空比为50%

      OC1B 引脚输出周期为400usPWM波形,占空比为100/400=25%

2、OC1A的频率可变,但用于是方波


-------------------------------------------------------------------------------------------------------------------------------------

4-2、OC1A引脚的其他配置

说明:

1、在配置为COM1A[1:0]=2COM1A[1:0]=3上、OC1A引脚都没有脉冲输出。

      因为OCR1A用来控制周期,那么就没有寄存器来控制OC1A引脚的占空比了。


-------------------------------------------------------------------------------------------------------------------------------------

第四步: 相位修正PWM

4-0、PWM如何产生

说明:

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

      所以8MHz8预分频下的周期 T = ((1.0/8000000)*1000000)*8*510=510us,对应的频率是1.96KHz

4、如果修改TOP值,最好在一个周期结束后修改。

      否则可能导致上升斜坡下降斜坡长度不一致,导致一个周期的波形不对称,结果就是在这个周期里、占空比超出预期或低于预期。

      如果这对应用有影响,就应该使用相位与频率修正PWM来做TOP值可变的PWM,可以避免这种不对称波形。


-------------------------------------------------------------------------------------------------------------------------------------

4-1、TOP值固定的PWM

说明:

1、TOP值固定、也就是频率固定。

2、如果OCR1AOCR1B的值超过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、CH1OC1A的输出,CH2OC1B的输出。

      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发生电平翻转的位置,就是对称中心所在的位置。

      示波器输出如下:


      CH1OC1A的输出,CH2PA2的输出。

      可以看出、PA2发生电平翻转的位置,正好就是OC1A对称中心。其频率是1.95KHz,和计算的基本一致。

4、如果将TOP改为10位宽度,即TOP=0x3FF=1023,对应的周期是1/1023/2=488Hz

      发生比较匹配的时刻就是上升沿下降沿 OCR1B&0x3FF=20000&0x3FF=0x220=544 的位置。

      对应的高电平宽度是2*544=1088us

      示波器输入如下:


      CH1OC1A的输出,CH2OC1B的输出。

      OC1A的输出依然是400usOC1B的输出是1100us,和计算的1088us基本一致。

      波形周期是484Hz,和计算得到的488Hz基本一致。


-------------------------------------------------------------------------------------------------------------------------------------

4-2、极限值情形

说明:

1、这里使用OCR1A=0OCR1B=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、CH1OC1A的输出,CH2PA3的输出。

      OC1A输出低电平、即0%占空比的PWMOC1B输出高电平、即100%占空比的PWM

      PA3比较匹配时翻转电平,所以虽然OC1AOC1B没有脉冲输出,但是比较匹配确实在发生着。

      且PA3的周期是512us,和TOP=255时的510us基本一致。

      所以、可以得到0%100%的占空比。


-------------------------------------------------------------------------------------------------------------------------------------

4-3、TOP等于ICR1的PWM

说明:

1、这个模式和固定宽度模式下的情形基本一致,波形周期由ICR1决定。

      不同点在于OCR1AOCR1B超出TOP时,不再有波形输出。

2、可以得到0%100%的占空比,和固定宽度模式下的情形一致。

3、TOP=ICR1,是可变的,但不推荐修改。

      使用相位与频率修正PWM来做TOP值/频率可变的PWM

4、TOV1TCNT1=BOTTOM时被触发。

       ICF1TCNT1=TOP时被触发。



-------------------------------------------------------------------------------------------------------------------------------------

4-4、TOP等于OCR1A的PWM

说明:

1、波形频率由TOP=OCR1A决定。

4-4-1、OC1A和OC1B引脚取反

说明:

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翻转一次电平。


-------------------------------------------------------------------------------------------------------------------------------------

4-4-2、OC1A引脚和OC1B引脚的其他配置

说明:

1、OC1A在其他配置下没有波形输出,因为OCR1A用来控制频率,就没有其他寄存器用来控制占空比了。

2、OC1B引脚的其他配置有PWM输出,周期由TOP=OCR1A控制。但OCR1B超过TOP=OCR1A时没有波形输出。

3、OC1BPWM可以得到0%100%的占空比。



-------------------------------------------------------------------------------------------------------------------------------------

第五步: 相位与频率修正PWM

说明:

1、可以得到0%100%的占空比。

5-1、TOP等于ICR1的PWM

说明:

1、波形周期由ICR1决定。

2、OCR1AOCR1B超过TOP=ICR1时、OC1AOC1B引脚没有波形输出。

3、TOV1TCNT1=BOTTOM时被触发。

       ICF1TCNT1=TOP时被触发。


-------------------------------------------------------------------------------------------------------------------------------------

5-2、TOP等于OCR1A的PWM

说明:

1、波形周期由OCR1A决定。

2、OCR1B超过TOP=OCR1A时、OC1B引脚没有波形输出。

3、而OC1A只能产生方波,不能产生PWM波形,和CTC模式下的情形一致。

4、TOV1TCNT1=BOTTOM时被触发。

       ICF1TCNT1=TOP时被触发。

|<----实际测试中ICF1的触发周期是TOP=OCR1A周期的2倍、且常常会有不被触发的时刻。

        question-001

似乎仅在使用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);
}

      在需要修改波形的频率占空比的时候、只需要修改freqduty这两个变量即可,不必每次去手动计算

      尤其是当我们修改了芯片主频SYS_OSC_FREQUENCE的时候、我们无需再次修改这个函数。

      但如果要求精确的时钟、在修改这些参数时、还是需要检验并微调函数中的参数。

0

0

0

0

0

0


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Manon_des_source/article/details/51757791

智能推荐

Oracle弃用Nashorn JavaScript引擎_糖糖糖糖糖糖糖糖糖糖糖糖糖糖糖糖糖糖的博客-程序员ITS304

Oracle通过JDK增强提案(JEP)355宣布弃用Nashorn JavaScript引擎,最终将从未来所有的JDK中删除。ECMAScript的语言结构变化太快,Oracle发现,维护Nashorn JavaScript引擎变得非常困难。\\Nashorn最初是在JDK 8中引入的,用于取代Rhino脚本引擎。当其发布时,Nashorn是ECMAScript-262 5.1的完整实现,增强了...

pajek06-09:中心度_qq_34322002的博客-程序员ITS304

1、局部中心度2、整体中心度3、Network&gt;Create Vector&gt;Centrality&gt;Degre&gt;All4、中心度:点的中心度中心势:网络的中心度

7.(开发工具篇kibana)Kibana安装与启动_地图之家家长的博客-程序员ITS304_kibana开发工具

听老人家说:多看美女会长寿前言:Kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板(dashboard)实时显示Elasticsearch查询动态。1、Kibana官方下载地址https://www.elastic.co/cn/downloads/kibana一定要装与El

性能优化——内存泄漏(3)代码分析篇_GitLqr的博客-程序员ITS304

一、简述在上一篇《性能优化——内存泄漏(2)工具分析篇》中,介绍了如何使用工具帮助我们检查APP中是否存在内存泄漏、及如何定位到内存泄漏,但项目并不能完全依赖工具来检查,毕竟定位内存泄漏比较麻烦,还不如在开发时就考虑到内存泄漏问题,尽可能减少内存泄漏,后续优化才不会那么痛苦。下面就来看看开发中,哪些代码可能造成内存泄漏,及避免内存泄漏的对应解决方案。二、代码分析1、静态变量引起的内存泄露1)错误示例

正则校验金额_程序原SpringCloud的博客-程序员ITS304_正则校验金额

只能正数金额:(^[1-9](\d+)?(\.\d{1,2})?$)|(^0$)|(^\d\.\d{1,2}$)允许负数金额:(^-?[1-9](\d+)?(\.\d{1,2})?$)|(^-?0$)|(^-?\d\.\d{1,2}$)@ApiModelProperty(value = "最高收取的手续费(单位 元)")@NotNull(message = "最高收取的手续费不能为空")@Digits(integer = 5, fraction = 2, message = "amo

linux mint下安装hostapd并配置802.1X认证_代码狙击者的博客-程序员ITS304

        本实验的目的是在Linux Mint18的环境下安装hostapd来实现802.1x,其中mint作为路由的功能,即将hostapd设置为radius客户端。而要入网的是一部android手机,认证的radius服务器是运行在centos6.8上的freeradius。【hostapd简介】         hostapd 是用户控件的守护进程用于无线接入点(AP)和授权服...

随便推点

ffmpeg代码分析(1)--编译裁剪_茜茜她老爹的博客-程序员ITS304_ffmpeg 裁剪编译

我们很少使用到ffmpge完整的功能库,大部分时候,只需要特定的编解码 传输协议,那么该怎么裁剪ffmpeg呢?答案就是configure命令。configure 是一个文本文件,打开以后我们会发现它有很多命令,如下Help options:  --help                   print this message  --list-decoders        

Unity项目常见Lua解决方案性能比较_weixin_33946605的博客-程序员ITS304

测试说明Unity不支持热更新这事情一直是谜一样的痛点,特别是在作者第一个项目上线之后,发现每次更新代价实在太大,可惜官方Roadmap上迟迟没有出现这个功能。UWA之前分享过 Android平台热更新解决方案,直接替换dll是一种解决方式(但iOS上因为使用IL2CPP故而无法实现,而且这事本质上为商业原因而非技术问题)。除此之外还有一个比较常见的...

使用Git连接Gitee进行Unity开发项目_funny囧t的博客-程序员ITS304_gitee unity

将项目上传到Gitee进行协同开发如图建立一个仓库下载并安装Git,可看网上的教程在所需上传到Gitee的项目文件目录右键并选择Git Bash输入git init,将该文件目录作为本地git库,此时会生成两个隐藏文件,目录出现master分支输入git.remote add Project https://gitee.com/rookiestudios/Project(这个Project是你在Gitee新建的仓库名称).git,将git关联到仓库并且命名输入git pull Project

算法竞赛入门经典——高效算法——巨人与鬼_qq_27559181的博客-程序员ITS304

巨人与鬼:题目:一组n个巨人正与n个鬼进行战斗,每个巨人的武器是一个质子炮, 它可以把一串质子流射中鬼而把鬼消灭。质子流沿直线行进,在击中鬼时就终止。巨人决定采取下述策略。他们寻找鬼配对,以形成n个巨人─鬼对,。然后每个巨人同时向他选取的鬼射出一串质子流。我们知道,让质子流互相交叉是很危险的。因此巨人选择的配对方式应该使质子流都不会交叉。假定每个巨人和每个鬼的位置都是平面上的一个固定点,并且没有三...

ofbiz项目编译及idea启动_东泽312的博客-程序员ITS304_ofbiz启动

idea2021版启动ofbiz项目1 进入项目根目录2 通过ant清理项目3 通过ant编译项目4 需要确保编译成功5 idea引入ofibz项目记录下来idea是如何导入和启动ofbiz项目的1 进入项目根目录2 通过ant清理项目3 通过ant编译项目4 需要确保编译成功5 idea引入ofibz项目确认下根路径没有问题几个要修改的地方添加vm选项-server -XX:PermSize=512M -XX:MaxPermSize=1024m编译完成后

推荐文章

热门文章

相关标签