最近发现一个很奇怪的现象,如标题,为此写了一个简单的程序来验证这个问题,下面是部分代码:
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 }
测试结果如下:
问题解决。