我花了一些时间把基于dsPIC30F6014A以及STM32F103处理器实现的单相静电除尘控制算法改到TMS320F28335平台,用比较工具逐一确认修改的代码,然后用白炽灯作为负载调试程序。
用示波器测试并确认过零脉冲、触发脉冲串等波形之后,我将导通道从最小的20°慢慢调大,在调整过程中观测各个波形以及灯泡发光情况。
当导通角上升到170°时,灯泡出现了随机闪烁的情况。
开始分析
做产品开发最怕碰到这样的概率性问题,解决此类问题通常需要熟悉产品的软件以及硬件设计,有清晰的逻辑思维,善于设计一些跟踪、调试方法, 从一些蛛丝马迹中总结规律、寻找线索。
所以说解决问题最能体现开发人员技术水平和能力。
我第一时间想到了可能跟过零信号有关,或许是加在光耦初级的交流模拟电压幅度太低,边沿不够陡峭,在导通电压附近存在波动,而光耦没有迟滞效果,在次极产生高频干扰脉冲。
图1.过零检测电路仿真
由此可见在模拟信号转数字信号时,迟滞比较以及施密特输入的重要性。
为了验证是否为此原因,我在检测过零信号的外部中断程序中翻转用于测试的GPIO口,同时在导通角延时和触发脉冲串延时的定时器中断程序中翻转另一个用于测试的另外一个GPIO口。
根据我的测算,当把示波器整屏的扫描时间秒级以上(以1秒为例)时,人眼能对波形的变化做出反应。
而整屏的数据采样点数可能大概是2000个,此时采样率可以达到2000Hz,可以还原1000Hz的信号。
正常情况下,在过零信号中断程序中翻转的电平信号的频率为100Hz,在定时器中断程序翻转的电平信号的脉冲宽度为触发脉冲串的输出时间,当导通角是170°时,大概为3ms,采样率达到300Hz以上,就可以捕捉到。
因此,用示波器观测这两个信号,在灯泡发生闪烁时,足以在示波器上观测到波形的异常。
把黄色波形的通道1接到定时器中断翻转的IO口上,
把紫色波形的通道3接到过零信号中断翻转的IO口上,
把绿色波形的通道4接到输出触发脉冲信号的IO口上,
调到自动触发模式,目不转晴盯着示波器屏幕。
果不其然,看到了下图的异常波形。
图3.示波器捕捉到的异常波形
一波三析
对我迅速停下的滚动波形展开分析,发现过零信号并没有高频脉冲干扰,而定时器中断却出现一个半波的丢失。
而触发脉冲串却没有看到波形,需要说明的是,这并不是因为程序有BUG,没有输出,而是因为示波器时基相比脉冲串的脉宽过长,采样率不够,无法正常捕捉脉冲串的波形。
既然不是过零脉冲导致问题,但又会有什么原因导致定时器中断的丢失呢?
我陷入的沉思...
是因为过零信号中断没有正确启动定时器,或者启动了定时器但是没能正确进入中断程序,
还是虽然正确进入定时器中断,但是没能正确执行程序翻转测试的IO口以及输出触发脉冲串。
再三确认,未能正常启动定时器的概率比较低,
我定义了一个全局变量,在过零中断启动定时器之前置1。
在定时器中断程序中将该变量清0。
同时,在一进入过零中断就判断如果该变量为1,则翻转测试IO口,并在该代码处设置断点。
然后,我全速运行程序。
当再次反复发生闪烁时,程序并没有在断点处停下。
而如果不能正常进入定时器中断程序,在进入过零中断时,usTestFlag必须为1,发生闪烁时,程序必然停在断点处。
volatile unsigned char usTestFlag = 0;//测试用全局变量
void SCRControl_TimerISR(void){//定时器中断
usTestFlag = 0;//清0
....
}
void SCRControl_SyncISR(void){
if(1 == usTestFlag){
Toggle_IO(DEBUG_IO_PORT, DEBUG_IO_PIN);//在该代码入设置断点
}
...
usTestFlag = 1;//置1
SCRControl_TimerEnable();//启动定时器,并使能定时器中断
...
}
复制代码出现转机
这又是怎么回事呢?
我想到了在示波器上看不到波形的触发脉冲串?
因为过零中断之后,程序会先后两次进入定时器中断,翻转两个测试IO口;
在导通角延时之后开启触发脉冲串的PWM输出,在输出一段时间的脉冲串之后停止PWM输出。
难道是这两次进入定时器中断的时间间隔太短,导致IO口翻转的时间短,翻转的测试IO口波形以及触发脉冲串波形都无法被正常捕捉到?
我进一步改进测试代码,定义一个2048个长度的数组以及一个指针变量;
unsigned U16 usTestBuffPointer = 0;
unsigned U16 usTestBuff[2048];
void SCRControl_TimerISR(void){//定时器中断
usTestBuffPointer &= 2047;
usTestBuff[usTestBuffPointer] = 0x55;
....
}
void SCRControl_SyncISR(void){
if(1 == usTestFlag){
Toggle_IO(DEBUG_IO_PORT, DEBUG_IO_PIN);//在该代码入设置断点
}
...
usTestBuffPointer &= 2047;
usTestBuff[usTestBuffPointer] = 0xAA;
SCRControl_TimerEnable();//启动定时器,并使能定时器中断
...
}
复制代码之后,全速运行代码,当发生闪烁时,立即停止运行。
在watch窗口,通过select all->copy将usTestBuff存储的数据导入到excel,通过usTestBuffPointer当前值重新按时间排序数据。一堆操作下来,发现0xAA都跟随两个0x55。
再一次通过memory窗口确认了这一情况。
图4. 确认进入两次定时器中断
由此,可以确定两个定时器的时间间隔并不是几ms的脉冲串的输出时间,而是非常短的时间,短到示波器都捕捉不到翻转的电平波形。
会不会是因为在第一次定时器中断之后,定时器的中断标志位没有被清零,再次使能定时器中断并启动定时器之后,立即进入中断程序?
可以程序已经按照规格书的说明,通过下述语句清零了中断标志位。
CpuTimer0Regs.TCR.bit.TIF = 1;\\清零中断标志
CpuTimer0Regs.TCR.bit.TIE = 1;\\使能定时器中断
CpuTimer0Regs.TCR.bit.TSS = 0;\\启动定时器
复制代码我仔细了解TMS320F28335的中断结构,
由于CPU没有能力处理所有外设级的中断请求, 因此F28335的 CPU让出了 12 个中断线交给PIE模块进行复用管理。
中断采用的是3级中断机制,分别是外设级中断、 PIE级中断和CPU级中断,
最内核部分为CPU级中断,即CPU只能响应从CPU中断线上过来的中断请求,
但F28335中断源很多,CPU没有那么多中断线, 在有限中断线的情况下,只能安排中断线进行复用, 其复用管理就有了中间层的PIE级中断, 外设要能够成功产生中断响应, 就要首先经外设级中断允许, 然后经 PIE允许, 最终 CPU做出响应。
认真阅读规格书中关于中断的章节,我发现了PIEIFRx寄存器及其说明;
图5.PIEIFRx寄存器及其说明
难道是因为当导通角比较高时,从过零中断到定时器中断时间非常短,基本上为0,CPUTimer的TimerxPRD/TimerxRPDH寄存器值非常小;
启动定时器之后,定时器TimerxTim/TimerxTimH定时器减到0,立即产生中断,并由PIE中断传递到CPU中断;
此外,TimerxTim/TimerxTimH又再一次加载TimerxPRD/TimerxRPDH寄存器的数值,
在执行到中断程序的停止定时器的指令之前,又一次减到0,产生了中断并缓存到了PIE中断,将PIEIRx寄存器的相应位置1;
而程序在再次启动定时器只清零了TCR寄存器中的CPU中断标志位,并没有将PIEIRx寄存器的相应PIE中断标志清零。
我立即在启动定时器之前,加上了清空该标志位的语句。
PieCtrlRegs.PIEIER1.bit.INTx7 = 1;//连接PIE和CPU中断
CpuTimer0Regs.TPR.all = 74;//定时器时钟分频到2MHz
CpuTimer0Regs.TPRH.all = 0;
CpuTimer0Regs.TCR.bit.TRB = 1;//加载设置
CpuTimer0Regs.TCR.bit.SOFT = 0;
CpuTimer0Regs.TCR.bit.FREE = 0;//自由运行
PieCtrlRegs.PIEIFR1.bit.INTx7 = 0;//清空缓存在PIE的中断
CpuTimer0Regs.TCR.bit.TIF = 1;//清空CPU中断标志位
CpuTimer0Regs.TCR.bit.TIE = 1;//使能中断
CpuTimer0Regs.TCR.bit.TSS = 0;//启动定时器
复制代码一点反思
有一点还是不明白,在规格书中说道,
图6. 规格书中关于soft和free标志位的说明
明明在启动定时器时把free和soft都清零,为什么在TIM寄存器减到0之后,定时器并没有自动停止而是再次加载TPR寄存器定时,并再次产生中断缓存至PIE。
客户反馈的在现场使用时出现误检测闪络的情况,或者闪络判断以及信号处理都没有问题,只是跟这个闪烁问题一样,是因为没有清空缓存在PIE中断标位志所导致,如果是这样,那一行代码造成巨大损失,实在冤枉得很。