龙空技术网

定时器中断标志的处理,其实没那么简单

物联网全栈开发 1079

前言:

此刻兄弟们对“定时器的代码”都比较着重,看官们都想要知道一些“定时器的代码”的相关资讯。那么小编也在网摘上搜集了一些关于“定时器的代码””的相关资讯,希望你们能喜欢,同学们快快来学习一下吧!

事情回顾

话说上周调试控制器,当把导通角调整到170°以上时,由可控硅控制的白炽灯产生随机闪烁了故障现象。

过零信号的外部中断启动导通角延时的定时器中断,进入定时器中断之后立即停止定时器,并使能PWM输出,产生触发脉冲,同时再次启动定时器,以控制触发脉冲的个数,确保只触发导通一个半波;

图1. 可控硅触发波形,CH2-过零脉冲,CH4-触发脉冲串

经过一番分析,发现当导通角比较大时,CPUTimer0的周期寄存器PRDH:PRD设置的值比较小,在进入定时器中断程序并调用停止定时器的代码之前,CPUTimer的TIMH:TIM寄存器就减到了0,又自动加载RPDH:RPD寄存器加载数值到TIMH:TIM寄存器,继续递减计数,紧接着计数又减到了0;

此时CPU还在响应上一次的定时中断并进入了中断程序,但依然没执行到停止定时器的代码;

这种情况下,PIEIFR1的INTx7被置1,紧接着再次启动定时器以控制解发脉冲个数时,即使定时器中断标志位TF通过置1来清0,但是当把PIEACK置1时,定时器又立即产生了中断。

从而触发脉冲串迅速又在定时器中断程序中被关闭,甚至用示波器都观测不到触发脉冲串的踪迹。

当时的做法是在停止和启动定时器时,通过下述语句将PIEIFR1的INTx7清0。

PieCtrlRegs.PIEIFR1.bit.INTx7 = 0;

简单测了一段时间,没有再碰到闪烁的情况,于是一身轻松回了家。

一波三折

本周,客户接入变压器测试,发现检测不到闪络。

今天,我用下载工具调试代码,发现CPU运行一段时间之后就无法再进入ADC中断。

而ADC在初始化时被配置成通过EPWM3触发自动采样,将几路ADC组成一个系列,系列采样完之后,就进入中断,计算用于在定时器中断中判断闪络的二次电流,二次电压的平均值,最大、最小值。

  memset((void *)&g_Adc_stRegs, 0, sizeof(g_Adc_stRegs));	InitAdc();	SysCtrlRegs.PCLKCR0.bit.ADCENCLK = 1;	ADC_cal();	SysCtrlRegs.HISPCP.all = 3;       		 // HSPCLK = SYSCLKOUT / ADC_MODCLK = 25.0MHz	EDIS;	AdcRegs.ADCTRL1.bit.SUSMOD = 3;   		 // 非挂起模式	AdcRegs.ADCTRL1.bit.ACQ_PS = 1;   		 // 采样窗口: 4倍ADC时钟	AdcRegs.ADCTRL1.bit.CPS = 1;      		 // AD时钟: 2分频高速时钟= 25.0MHz / 2 = 12.5MHz	AdcRegs.ADCTRL1.bit.CONT_RUN = 0; 		 // 采用启/停模式	AdcRegs.ADCTRL1.bit.SEQ_CASC = 1; 		 // 级联模式	AdcRegs.ADCTRL1.bit.SEQ_OVRD = 0; 		 // 	AdcRegs.ADCMAXCONV.bit.MAX_CONV1 = 0x05; // 数据采集通道数: 6通道	AdcRegs.ADCCHSELSEQ1.bit.CONV00 = 0x0;	 // Setup ADCINA0(U1X) as 1st SEQ1 conv	AdcRegs.ADCCHSELSEQ1.bit.CONV01 = 0x1;	 // Setup ADCINA0(U2X) as 1st SEQ1 conv	AdcRegs.ADCCHSELSEQ1.bit.CONV02 = 0x2;	 // Setup ADCINA0(I1X) as 1st SEQ1 conv	AdcRegs.ADCCHSELSEQ1.bit.CONV03 = 0x3;	 // Setup ADCINA0(I2X) as 1st SEQ1 conv	AdcRegs.ADCCHSELSEQ2.bit.CONV04 = 0x5;	 // Setup ADCINA0(TMP) as 1st SEQ1 conv	AdcRegs.ADCCHSELSEQ2.bit.CONV05 = 0x7;	 // Setup ADCINA0(YK ) as 1st SEQ1 conv		AdcRegs.ADCTRL3.bit.ADCBGRFDN = 3; 	AdcRegs.ADCTRL3.bit.ADCPWDN = 1;   	AdcRegs.ADCTRL3.bit.ADCCLKPS= 1;   		 // ADCLK = HSPCLK / 2 * (CPS + 1) = 12.50MHz	AdcRegs.ADCTRL3.bit.SMODE_SEL = 0; 		 // 采样方式选择: 顺序采样	AdcRegs.ADCTRL2.bit.EPWM_SOCA_SEQ1=1;	AdcRegs.ADCTRL2.bit.INT_ENA_SEQ1 = 1;	EPwm3Regs.ETSEL.bit.SOCAEN = 1;						     // Enable SOC on A group	EPwm3Regs.ETSEL.bit.SOCASEL= 4;     				     // Select SOC form CPMA on upcount	EPwm3Regs.ETPS.bit.SOCAPRD = 1;     				     // Generate pulse on 1st event	EPwm3Regs.CMPA.half.CMPA = (ADC_SAMPLE_PERIOD_CNT / 2);  // Set compare A value	EPwm3Regs.TBPRD = (ADC_SAMPLE_PERIOD_CNT - 1);  	     // Set period for EPwm3	EPwm3Regs.TBCTL.bit.CTRMODE = 0;

在运行期间,ADC的配置没有被修改过,那怎么就进不了ADC中断呢?

图2. 在ADC中断中翻转IO口,确定无法进入中断

而如果不启动设备,则ADC中断始终都能进入,而一旦启动设备,使能过零信号中断,并调用了CPUTIMER启动或者停止代码,则过不了多久,在中断程序中翻转的IO口就测试不到电平变化,也就是CPU不再进入ADC中断。

逐一注释CPUTIMER启动或者停止的指令,最终发现执行将PIEIFR1的INTx7清0的操作导致了这一问题,也就是:

PieCtrlRegs.PIEIFR1.bit.INTx7 =0 

根据STM32处理器的开发经验,如果是通过某一个位的置位操作来清空某一位的标志位。

那这个操作就是原子位操作,可以不考虑资源互斥访问的问题。

但是PIEIFR1的INTx7的标志位的清零却是直接赋值为0。

这可能不是原子操作!

有可能做了两步操作:

第一步,将PIEIFR1的数值读到Register

第二步,与操作之后再写回到PIEIFR1寄存器。

在作这一连贯动作时,硬件模块也在操作这个寄存器。

比如该死的ADC也产生了中断,在CPU完成第一步,将要执行第二步时,将PIEFR1的INTx4置1。

当CPU把Register存储的数值写回PEIFR1寄存器之后,其中硬件置位的INTx4标志位被覆盖成0。

没有INTx4标志位,即使ADC的INT_SEQ1中断标志位置位,CPU也无法进入ADC中断。

不能进入ADC中断,INT_SEQ1不能被清零,INTx4标志位不能再次被置位,从而CPU再也不能进入ADC中断,除非手动将INT_SEQ1标志位清零。

在CPUTIMER启动或者停止代码中删去了将PIEIFR1的INTx7清0的指令,长时间测试都没有再碰到不进入ADC中断的情况。

一筹莫展

删去了将PIEIFR1的INTx7清0的指令,导通角超过170°之后,又出现了灯泡随机闪烁的故障。

翻遍了TMS320F28335的规格书和示例代码,没有找到PIEIFR1寄存器的原子位操作。

如果定时器的TMRH:TMR寄存器减到0之后,定时器能自动停止下来,闪烁的问题也能解决,但是,经过确认,free设置为0,stop设置成1或者0,定时器自动停止仅在程序仿真,CPU断点暂停时有效。

图3. 关于free和soft的说明

峰回路转

我苦苦思索...

作为有几十年工作经验的优秀IT工程师,岂能败于小小的标志位,

我灵光一闪,有了办法,

在启动定时器的代码中,当设置了PRDH:PRD寄存器,并通过置位TRB标志位将数值立即从影响寄存器加载到真实寄存器之后,把PRDH:PRD寄存器再次赋值位比较大的数值,比如65535,代码如下:

#define SPARK_TIMER_ENABLE() do{\    PieCtrlRegs.PIEIER1.bit.INTx7 = 1;\    CpuTimer0Regs.TPR.all  = 74;\    CpuTimer0Regs.TPRH.all  = 0;\    CpuTimer0Regs.TCR.bit.TRB = 1;\    CpuTimer0Regs.TCR.bit.SOFT = 1;\    CpuTimer0Regs.TCR.bit.FREE = 0;\    CpuTimer0Regs.TCR.bit.TIF = 1;\    CpuTimer0Regs.TCR.bit.TIE = 1;\    CpuTimer0Regs.PRD.all = SPARK_TIMER_INIT_COUNTER;\    CpuTimer0Regs.TCR.bit.TSS = 0;\}while(0)

“CpuTimer0Regs.PRD.all = SPARK_TIMER_INIT_COUNTER”这一条语句拯救了我。

它使得TIMH:TIM减到0,产生第一次中断后,TIMH:TIM从PRDH:PRD寄存器中加载了一个比较大的数值,从而在进入中断停止定时器之前,TIMH来不及再次减到0,再次产生中断。

改完之后测试,闪烁故障不再出现,ADC中断也能始终进入。

这真是“山重水复疑无路,柳暗花明又一村"。

标签: #定时器的代码