最近发现一个很奇怪的现象,如标题,为此写了一个简单的程序来验证这个问题,下面是部分代码:

 1 void InitEPwm2Gpio(void)
 2 {
 3     EALLOW;
 4     GpioCtrlRegs.GPAPUD.bit.GPIO2 = 0;    // 使能上拉
 5     GpioCtrlRegs.GPAMUX1.bit.GPIO2 = 1;   // 将GPIO2配置为EPWM2A
 6     EDIS;
 7 }
 8 
 9 void DCMotor_ePWM2_Init(void)
10 {
11     EALLOW;
12     SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0;   // PWM模块时基时钟同步使能
13     SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1;  // ePWM2时钟使能
14     EDIS;
15 
16     InitEPwm2Gpio();
17 
18     EALLOW;  // This is needed to write to EALLOW protected registers
19     PieVectTable.EPWM2_INT = &epwm2_isr;
20     EDIS;    // This is needed to disable write to EALLOW protected registers
21 
22     // 设置时间基准的时钟信号(TBCLK)
23     EPwm2Regs.TBCTL.bit.CTRMODE = TB_COUNT_UP; // 递增计数模式
24     EPwm2Regs.TBPRD = 9374;                      // 设置定时器周期,得到PWM频率为100Hz
25     EPwm2Regs.TBCTL.bit.PHSEN = TB_ENABLE;        // 使能相位加载
26     EPwm2Regs.TBPHS.half.TBPHS = 0x0000;           // 时基相位寄存器的值赋值0
27     EPwm2Regs.TBCTR = 0x0000;                   // 时基计数器清零
28 
29     EPwm2Regs.TBCTL.bit.HSPCLKDIV = 5;   // 10分频
30     EPwm2Regs.TBCTL.bit.CLKDIV = 4;        //16分频,得到时基计数器的频率为0.9375MHz
31 
32     // 设置比较寄存器的阴影寄存器加载条件:时基计数到0
33     EPwm2Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW;    //CMPA开启影子寄存器
34     EPwm2Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW;    //CMPB开启影子寄存器
35     EPwm2Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO;
36     EPwm2Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO;
37 
38 
39     // 设置比较寄存器的值
40     EPwm2Regs.CMPA.half.CMPA = 5000;     // 设置比较寄存器A的值
41 
42     // 设置动作限定;首先默认为转动方向为正转,这时只有PWM1A输出占空比;
43     EPwm2Regs.AQCTLA.bit.ZRO = AQ_CLEAR;                // 计数到0时PWM1A输出低电平
44     EPwm2Regs.AQCTLA.bit.PRD = AQ_NO_ACTION;            // 无动作
45     EPwm2Regs.AQCTLA.bit.CAU = AQ_SET;                  // 递增计数时,发生比较寄存器A匹配时PWM1A输出高电平
46     EPwm2Regs.AQCTLA.bit.CAD = AQ_NO_ACTION;              // 无动作
47     EPwm2Regs.AQCTLA.bit.CBU = AQ_NO_ACTION;              // 无动作
48     EPwm2Regs.AQCTLA.bit.CBD = AQ_NO_ACTION;              // 无动作
49 
50     EPwm2Regs.ETSEL.bit.INTSEL = ET_CTR_ZERO;     // 选择0匹配事件中断
51     EPwm2Regs.ETSEL.bit.INTEN = 1;                // 使能事件触发中断
52     EPwm2Regs.ETPS.bit.INTPRD = 1;                 // 1次事件产生中断请求
53 
54     EALLOW;
55     SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 0;  // ePWM2时钟禁能
56     SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1;
57     EDIS;
58 
59     IER |= M_INT3;
60     PieCtrlRegs.PIEIER3.bit.INTx2 = 1;
61 
62     EINT;   // Enable Global interrupt INTM
63     ERTM;   // Enable Global realtime interrupt DBGM
64 }
65 
66 interrupt void epwm2_isr(void)
67 {
68     static Uint16 cnt = 0;
69 
70     cnt++;
71     if (cnt == 5) {
72         cnt = 0;
73         EALLOW;
74         SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 0;  // ePWM2时钟禁能
75         EDIS;
76     }
77     GpioDataRegs.GPCTOGGLE.bit.GPIO68=1;
78 
79     // 清除这个定时器的中断标志位
80     EPwm2Regs.ETCLR.bit.INT = 1;
81     // 清除PIE应答寄存器的第三位,以响应组3内的其他中断请求;
82     PieCtrlRegs.PIEACK.all = PIEACK_GROUP3;
83 }

 

  对以上代码进行一个简单的解释,使能ePWM2,PWM周期为100Hz,并且每次ePWM计数器为0时产生中断,中断里是对GPIO68进行翻转,用逻辑分析仪测量到以下波形:

  

   到这里暂时没什么问题。

  接下来改一下需求,引入按键,在按键按下的时候关掉ePWM时钟,再次按下的时候开启ePWM时钟,这样就能通过按键控制ePWM输出了,为了方便得知按键按下去的实际,在每次按下按键的时候让GPIO67翻转,关键代码如下:

 1     while(1)
 2     {
 3         key=KEY_Scan(0);    //有消抖
 4 
 5         if(key == KEY1_PRESS) {
 6             if (flag == 1) {
 7                 flag = 0;
 8                 EALLOW;
 9                 SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1;  // ePWM2时钟使能
10                 EDIS;
11             } else {
12                 flag = 1;
13                 EALLOW;
14                 SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 0;  // ePWM2时钟禁能
15                 EDIS;
16             }
17             GpioDataRegs.GPCTOGGLE.bit.GPIO67=1;
18         }
19     }

  逻辑分析仪测量结果如下图:

  

   可见,可以通过按键控制ePWM时钟的开关,进而控制PWM的输出,同时中断也是正常的。

  接下来就要出幺蛾子了,再改一下需求,每次按下按键后输出5个脉冲。修改思路也很简单,就是在按键按下之后开启ePWM时钟,中断里计数,计够5个之后关ePWM时钟,关键代码如下:

  main函数:

 1     while(1)
 2     {
 3         key=KEY_Scan(0);    //有消抖
 4 
 5         if(key == KEY1_PRESS) {
 6 
 7             EALLOW;
 8             SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1;  // ePWM2时钟使能
 9             EDIS;
10 
11             GpioDataRegs.GPCTOGGLE.bit.GPIO67=1;
12         }
13     }

  中断函数:

 1 interrupt void epwm2_isr(void)
 2 {
 3     static Uint16 cnt = 0;
 4 
 5     cnt++;
 6     if (cnt == 5) {
 7         cnt = 0;
 8         EALLOW;
 9         SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 0;  // ePWM2时钟禁能
10         EDIS;
11     }
12     GpioDataRegs.GPCTOGGLE.bit.GPIO68=1;
13 
14     // 清除这个定时器的中断标志位
15     EPwm2Regs.ETCLR.bit.INT = 1;
16     // 清除PIE应答寄存器的第三位,以响应组3内的其他中断请求;
17     PieCtrlRegs.PIEACK.all = PIEACK_GROUP3;
18 }

  测试结果如下:

  

   看现象,第一次按键按下后,输出了5个脉冲,进入了5次中断,之后关闭了PWM输出,这符合预期。第二次按下按键后,PWM输出了,但是没有停,GPIO68也没翻转,这说明第二次按下按键后ePWM时钟虽然开启,但是没有进中断。程序设定的是当计数器为0的时候就要进中断,既然输出了PWM波,那就说明肯定有计数器为0的时候,这个现象显然不符合预期。是因为中断标志没清,所以进不了中断?但是在中断函数里面已经清除中断了呀。为了验证这个问题,我直接在按键按下后加一行清中断的代码,然后运行结果如下:

  

   这说明确实是没清中断。这意思难道是关ePWM时钟的时候,又触发了一次中断?是因为关时钟这个操作会直接进中断?还是说关掉时钟之后会让时基计数器为0,进而触发中断?为了验证这个问题,将中断触发条件设置为匹配CMPA值时进中断,并且去掉按下按键后清中断那一句,如果关ePWM时钟会导致时基计数器变为0,这样不会触发两次中断,输出应该是正常的。测试结果如下:

   这似乎说明只要关ePWM时钟就会触发中断

   带着这个思路再来研究一下下面这段代码:

 1 interrupt void epwm2_isr(void)
 2 {
 3     static Uint16 cnt = 0;
 4 
 5     cnt++;
 6     if (cnt == 5) {
 7         cnt = 0;
 8         EALLOW;
 9         SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 0;  // ePWM2时钟禁能
10         EDIS;
11     }
12     GpioDataRegs.GPCTOGGLE.bit.GPIO68=1;
13 
14     // 清除这个定时器的中断标志位
15     EPwm2Regs.ETCLR.bit.INT = 1;
16     // 清除PIE应答寄存器的第三位,以响应组3内的其他中断请求;
17     PieCtrlRegs.PIEACK.all = PIEACK_GROUP3;
18 }

   如果是因为关时钟直接触发了中断,意味着第5次中断结束以后,中断标志没彻底清除,导致无法再次进入中断。带着这个思路,把代码稍微改一下,把清中断的代码放到关时钟之前,如下:

 1 interrupt void epwm2_isr(void)
 2 {
 3     static Uint16 cnt = 0;
 4 
 5     // 清除这个定时器的中断标志位
 6     EPwm2Regs.ETCLR.bit.INT = 1;
 7     // 清除PIE应答寄存器的第三位,以响应组3内的其他中断请求;
 8     PieCtrlRegs.PIEACK.all = PIEACK_GROUP3;
 9 
10     cnt++;
11     if (cnt == 5) {
12         cnt = 0;
13         EALLOW;
14         SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 0;  // ePWM2时钟禁能
15         EDIS;
16     }
17     GpioDataRegs.GPCTOGGLE.bit.GPIO68=1;
18 }

  如果上面的思路是正确的,那么第一次按下按键之后,第5次进入中断就立刻清除中断标志了,随后关时钟再触发中断,那么GPIO68会翻转6次,然鹅,改完代码后的测试结果如下:

   GPIO68只翻转了5次,但是后续的波形如下图:

   前面一段放大一点看:

   在位置A发生了匹配事件,进入了中断,关闭了时钟,但是PWM并没有拉高,而在位置B,开启时钟之后PWM直接被拉高了,这看起来是将PWM拉高这个动作被挂起了,等到下次时钟开启的时候就直接把PWM拉高了。但是这次实验并没有说明关时钟会直接导致中断这个问题(如果是的话,那么GPIO68应该翻转6次)。

  接下来我又进行了一个猜想:并不是关时钟会直接进中断,而是关时钟之后,就无法进行清中断的动作

  再次修改代码进行验证:

  主函数:

 1     while(1)
 2     {
 3         key=KEY_Scan(0);    //有消抖
 4 
 5         if(key == KEY1_PRESS) {
 6 
 7             EPwm2Regs.ETCLR.bit.INT = 1;    //清中断放在开时钟之前
 8 
 9             EALLOW;
10             SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1;  // ePWM2时钟使能
11             EDIS;
12 
13             GpioDataRegs.GPCTOGGLE.bit.GPIO67=1;
14         }
15     }

  中断函数:

 1 interrupt void epwm2_isr(void)
 2 {
 3     static Uint16 cnt = 0;
 4 
 5     cnt++;
 6     if (cnt == 5) {
 7         cnt = 0;
 8         EALLOW;
 9         SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 0;  // ePWM2时钟禁能
10         EDIS;
11     }
12     GpioDataRegs.GPCTOGGLE.bit.GPIO68=1;
13 
14     // 清除这个定时器的中断标志位
15     EPwm2Regs.ETCLR.bit.INT = 1;
16 
17     // 清除PIE应答寄存器的第三位,以响应组3内的其他中断请求;
18     PieCtrlRegs.PIEACK.all = PIEACK_GROUP3;
19 }

  那么预测一下,当第一次按键按下后,开启时钟了,进入了5次中断,GPIO68会翻转5次,但是因为第5次中断是先关的时钟,后清的中断,所以实际上第5次中断没被清掉。而第2次按键按下之后,是先清的中断,后开的时钟,所以中断依然没被清掉。但是接下来时钟就打开了。所以第2次按键按下后,会输出PWM波,但是依然不会进中断。第3次按键按下后,因为此时时钟是打开的,因此可以清中断,之后就会再进5次中断,如此往复,看实验结果:

   与预期相符。

  总结,得出以下结论:

  1. TMS320F28335的ePWM如果不清中断标志,那么不会导致反复进中断,而是导致下次无法进中断;

  2. 关时钟之后清中断是无效的;

  3. 匹配事件会比中断滞后,未完成的动作会被挂起,直到时钟打开。

  最后将程序进行修改:

  1 interrupt void epwm2_isr(void);
  2 
  3 void main()
  4 {
  5     unsigned char key=0;
  6 
  7     InitSysCtrl();
  8 
  9     InitPieCtrl();
 10     IER = 0x0000;
 11     IFR = 0x0000;
 12     InitPieVectTable();
 13 
 14     LED_Init();
 15 
 16     KEY_Init();
 17     DCMotor_ePWM2_Init();
 18 
 19     while(1)
 20     {
 21         key=KEY_Scan(0);    //有消抖
 22 
 23         if(key == KEY1_PRESS) {
 24 
 25             EALLOW;
 26             SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1;  // ePWM2时钟使能
 27             EDIS;
 28 
 29             GpioDataRegs.GPCTOGGLE.bit.GPIO67=1;
 30         }
 31     }
 32 }
 33 
 34 
 35 void InitEPwm2Gpio(void)
 36 {
 37     EALLOW;
 38     GpioCtrlRegs.GPAPUD.bit.GPIO2 = 0;    // 使能上拉
 39     GpioCtrlRegs.GPAMUX1.bit.GPIO2 = 1;   // 将GPIO2配置为EPWM2A
 40     EDIS;
 41 }
 42 
 43 void DCMotor_ePWM2_Init(void)
 44 {
 45     EALLOW;
 46     SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0;   // PWM模块时基时钟同步使能
 47     SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1;  // ePWM2时钟使能
 48     EDIS;
 49 
 50     InitEPwm2Gpio();
 51 
 52     EALLOW;  // This is needed to write to EALLOW protected registers
 53     PieVectTable.EPWM2_INT = &epwm2_isr;
 54     EDIS;    // This is needed to disable write to EALLOW protected registers
 55 
 56     // 设置时间基准的时钟信号(TBCLK)
 57     EPwm2Regs.TBCTL.bit.CTRMODE = TB_COUNT_UP; // 递增计数模式
 58     EPwm2Regs.TBPRD = 9374;                      // 设置定时器周期,得到PWM频率为100Hz
 59     EPwm2Regs.TBCTL.bit.PHSEN = TB_ENABLE;        // 使能相位加载
 60     EPwm2Regs.TBPHS.half.TBPHS = 0x0000;           // 时基相位寄存器的值赋值0
 61     EPwm2Regs.TBCTR = 0x0000;                   // 时基计数器清零
 62 
 63     EPwm2Regs.TBCTL.bit.HSPCLKDIV = 5;   // 10分频
 64     EPwm2Regs.TBCTL.bit.CLKDIV = 4;        //16分频,得到时基计数器的频率为0.9375MHz
 65 
 66     // 设置比较寄存器的阴影寄存器加载条件:时基计数到0
 67     EPwm2Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW;    //CMPA开启影子寄存器
 68     EPwm2Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW;    //CMPB开启影子寄存器
 69     EPwm2Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO;
 70     EPwm2Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO;
 71 
 72 
 73     // 设置比较寄存器的值
 74     EPwm2Regs.CMPA.half.CMPA = 5000;     // 设置比较寄存器A的值
 75     EPwm2Regs.CMPB = 8000;                  // 设置比较寄存器B的值
 76 
 77     // 设置动作限定;首先默认为转动方向为正转,这时只有PWM1A输出占空比;
 78     EPwm2Regs.AQCTLA.bit.ZRO = AQ_NO_ACTION;            // 无动作
 79     EPwm2Regs.AQCTLA.bit.PRD = AQ_NO_ACTION;            // 无动作
 80     EPwm2Regs.AQCTLA.bit.CAU = AQ_SET;                  // 递增计数时,发生比较寄存器A匹配时PWM1A输出高电平
 81     EPwm2Regs.AQCTLA.bit.CAD = AQ_NO_ACTION;              // 无动作
 82     EPwm2Regs.AQCTLA.bit.CBU = AQ_CLEAR;                  // 递增计数时,发生比较寄存器B匹配时PWM1A输出低电平
 83     EPwm2Regs.AQCTLA.bit.CBD = AQ_NO_ACTION;              // 无动作
 84 
 85     EPwm2Regs.ETSEL.bit.INTSEL = ET_CTR_PRD;         // 等于周期值时触发中断
 86     EPwm2Regs.ETSEL.bit.INTEN = 1;                // 使能事件触发中断
 87     EPwm2Regs.ETPS.bit.INTPRD = 1;                 // 1次事件产生中断请求
 88 
 89     EALLOW;
 90     SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 0;  // ePWM2时钟禁能
 91     SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1;
 92     EDIS;
 93 
 94     IER |= M_INT3;
 95     PieCtrlRegs.PIEIER3.bit.INTx2 = 1;
 96 
 97     EINT;   // Enable Global interrupt INTM
 98     ERTM;   // Enable Global realtime interrupt DBGM
 99 }
100 
101 interrupt void epwm2_isr(void)
102 {
103     static Uint16 cnt = 0;
104 
105     // 清除这个定时器的中断标志位
106     EPwm2Regs.ETCLR.bit.INT = 1;
107 
108     // 清除PIE应答寄存器的第三位,以响应组3内的其他中断请求;
109     PieCtrlRegs.PIEACK.all = PIEACK_GROUP3;
110 
111     cnt++;
112     if (cnt == 5) {
113         cnt = 0;
114         EALLOW;
115         SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 0;  // ePWM2时钟禁能
116         EDIS;
117     }
118     GpioDataRegs.GPCTOGGLE.bit.GPIO68=1;
119 }

  测试结果如下:

   问题解决。