• AVR Freaks

AnsweredHot![SOLVED] PIC32MX. Zero-cross and Timer issue

Author
seccoxiru
Junior Member
  • Total Posts : 103
  • Reward points : 0
  • Joined: 2011/10/27 13:42:59
  • Location: 0
  • Status: offline
2019/06/17 17:25:59 (permalink)
0

[SOLVED] PIC32MX. Zero-cross and Timer issue

Hi.
I am working with PIC32MX270F256D running at 40MHz, MPLAB X 5.15 and XC32 1.43
I am triggering a Triac constantly with an incandescent bulb as load. I have a change notice interrupt enabled on pin RC7 and Timer4 interrupt, both interrupt priorities are set to 7 and the their sub-priorities to 2.
How it works?
1 - Change notice detects a variation on its zero-cross pin, then here I configure the PR4 to a value where the Timer overflow is 2.35ms (cuts around 30% of the mains wave from the load at 60Hz line), and disable the change notification interrupt on RC7. I am setting a delay before trigger the Triac after zero-crossing.
2 - After 2.35ms the timer4 overflow interrupt occurs, then the Triac is triggered. Timer 4 is set here to an overflow of 200us (this will be the pulse time).
3 - After 200us the second overflow occurs and the Triac is disabled (200us pulse complete here), then I enable change notification again for pin RC7, and on the next edge of the zero cross signal all that process will repeat.

The problem is that sometimes the first overflow does not occur after 2.35ms, it take some more time to the timer overflow, around 2ms more beyond what is expected. The picture shows this delay that occur sometimes. And due to this imperfection on the pulse position, the bulb seems to flash.
What can be that variation on the first Timer4 overflow, which occurs only sometimes? The first overflow determines where the pulse start inside the sine wave.
Regards.
 
//STARTUP

//Configure PORTC7 for change notification interrupt
//prior to this TRISC7 = 1 and ANSELC7 = 0...
IPC8bits.CNIP = 7;
IPC8bits.CNIS = 2;
IFS1bits.CNCIF = 0;
IEC1bits.CNCIE = 1;
CNENCbits.CNIEC7 = 1;
CNCONCbits.ON = 1;

//-----------------------------------------------------------------------------
//INTERRUPTS. CHANGE NOTIFICATION AND TIMER4 INTERRUPTS
//PRIORITIES ARE SET TO 7 AND SUB-PRIORITIES SET TO 2

void __ISR(_TIMER_4_VECTOR, IPL7SOFT) Timer4Handler(void)
{
    TriacTimerHandler(TRIAC());
}


void __ISR(_CHANGE_NOTICE_VECTOR, IPL7SOFT) ChangeNoticeHandler(void)
{
    if (CNENCbits.CNIEC7)
    {
        PIN_ZERO_ = 1; //for tests
        TriacZeroCrossHandler(TRIAC());
        PIN_ZERO_ = 0; //for tests
    }
    
    IFS1bits.CNCIF = 0;
}

//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
void TriacZeroCrossHandler (TRIAC_t *ptr_triac)
{
    //disable interrupt from RC7 pin
    CNENCbits.CNIEC7 = 0;
    bool pin_read = PORTCbits.RC7;
 
     //TMR4 = 0
    *ptr_triac->TMR_reg = 0;
     //PR = delay before triggering the triac
    *ptr_triac->PR_reg = ptr_triac->trigger_delay;
    //clear TMR4 flag
    *ptr_triac->TMR_IF_reg &= ~(ptr_triac->TMR_IF_bit);
    Nop();
    //enable TMR4 interrupt (the first)
    *ptr_triac->TMR_IE_reg |= ptr_triac->TMR_IE_bit;

}
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
void TriacTimerHandler (TRIAC_t *ptr_triac)
{
    //if triac pin == 0
    if ( !(*ptr_triac->p_lat_reg & ptr_triac->lat_bit) )
    {
        //triac pin = 1
        *ptr_triac->p_lat_reg |= ptr_triac->lat_bit;

        //TMR4 = 0
        *ptr_triac->TMR_reg = 0;

        //PR4 value = a value to generate 200us pulse (the second timer interrupt)
        *ptr_triac->PR_reg = ptr_triac->PR_value_for_200us_pulse;
 
 
 
        Nop();
 
 
 
 
 
        //clear TMR4 flag
        *ptr_triac->TMR_IF_reg &= ~(ptr_triac->TMR_IF_bit);
    }
    
    //if triac pin == 1
    else
    {
        //triac pin = 0
        *ptr_triac->p_lat_reg &= (~ptr_triac->lat_bit);

        //disable TMR4 interrupt
        *ptr_triac->TMR_IE_reg &= ~(ptr_triac->TMR_IE_bit);
        
        //read pin and enable new interrupts on RC7
        bool pin_read = PORTCbits.RC7;
        CNENCbits.CNIEC7 = 1; 
    }
}
//-----------------------------------------------------------------------------

 
The first signal is the zero cross for change notice input
The second signal is generated by lines of code PIN_ZERO_ = 1; and PIN_ZERO_ = 0;
The third is the Triac triggering singal, which randomly change its position sometimes
The last signal is another interrupt that occur sometimes and have lower priority and takes around 5us to process. As can be seen in the second picture attached, the Timer4 had time to overflow at the correct time right after the last interrupt (last signal)
 

 
post edited by seccoxiru - 2019/06/24 16:42:43

Attached Image(s)

#1
seccoxiru
Junior Member
  • Total Posts : 103
  • Reward points : 0
  • Joined: 2011/10/27 13:42:59
  • Location: 0
  • Status: offline
Re: PIC32MX. Zero-cross and Timer4 issue 2019/06/20 12:43:34 (permalink)
0
The interrupt source relative to the last signal of the first picture, I disabled it.
Now, there are only two sources of interrupt, the change notification int and the Timer4 int.
And sometimes the bulb flash, because the position of the pulse inside the sine wave is not always the same. I don't know why. Sometimes the Timer4 seems to need more time than expected to overflow and the pulse is delayed and this can be observed watching the bulb, it seems to flash when the extra delay of the timer overflow occurs.
post edited by seccoxiru - 2019/06/20 12:46:01
#2
maxruben
Super Member
  • Total Posts : 3371
  • Reward points : 0
  • Joined: 2011/02/22 03:35:11
  • Location: Sweden
  • Status: offline
Re: PIC32MX. Zero-cross and Timer4 issue 2019/06/23 04:57:47 (permalink) ☄ Helpfulby seccoxiru 2019/06/24 16:38:01
5 (1)
Make sure to use the CLR, SET or INV offsets for the sfrs since that will be an atomic operation which will be necessary when manipulating registers with shared bits that can be changed by the hardware (like IE IF bits). Otherwise you can get Read Modify Write effects when you write back a whole 32 bit register by the software which might have just been changed by the hardware. This could lead to either missed interrupts or ISR functions executed twice depending on the priority of the interrupt sources. 
 
/Ruben
post edited by maxruben - 2019/06/24 13:20:02
#3
seccoxiru
Junior Member
  • Total Posts : 103
  • Reward points : 0
  • Joined: 2011/10/27 13:42:59
  • Location: 0
  • Status: offline
Re: PIC32MX. Zero-cross and Timer4 issue 2019/06/23 06:41:35 (permalink)
0
maxruben
Make sure to use the CLR, SET or INV offsets for the sfrs since that will be an atomic operation which will be necessary when manipulating registers with shared bits that can be changed by the hardware (like IE bits). Otherwise you can get Read Modify Write effects when you write back a whole 32 bit register by the software which might have just been changed by the hardware. This could lead to either missed interrupts or ISR functions executed twice depending on the priority of the interrupt sources. 
 
/Ruben


Hi. Could you (or somebody) explain more? I did not understood so well.
I have never used the CLR, SET or INV functions. For which lines of my code do I need to use that?
I did not understood what is Read Modify Write?
 
 
 
 
Another test I did was to change from Timer4 to Timer1 but the same issue occurs.
 
Regards.
post edited by seccoxiru - 2019/06/23 06:46:13
#4
maxruben
Super Member
  • Total Posts : 3371
  • Reward points : 0
  • Joined: 2011/02/22 03:35:11
  • Location: Sweden
  • Status: offline
Re: PIC32MX. Zero-cross and Timer4 issue 2019/06/23 09:14:27 (permalink) ☼ Best Answerby seccoxiru 2019/06/24 16:34:00
4.5 (2)
Read Modify Write means that you read a 32 bit register, manipulate it somehow and then write back all bits. What happens if the hardware decides to generate an interrupt for another hardware source using the same register as the IE bit you want to clear after you have read the IE IF register but before you have written it back? The IE IF flag will be cleared when you write back the whole 32 bit register.
 
This happens because the entire function is not atomic - it can be interrupted by an ISR or by the hardware. Atomic functions can't and they operate only on the specified bits.
 
Even disabling interrupt won't help since that only stops another interrupt from running, it does not stop the hardware from setting an IE IF bit in a register that shares several IE bits.
 
For instance:
IFS1bits.CNCIF = 0;

Translates to operations that first reads the entire IFS1 register then clears the CNCIF bit and then writes back the entire register whereas
CLF IFS1_CNCIF_BIT = 0; // Not sure if this is the correct macro name though - Check the header file for the processor you are using.

is atomic and will only operate on a single bit in IFS1. The code cannot affect another bit in IFS1.

/Ruben
 
Edit: IE above should of course be IF.
post edited by maxruben - 2019/06/24 13:18:56
#5
seccoxiru
Junior Member
  • Total Posts : 103
  • Reward points : 0
  • Joined: 2011/10/27 13:42:59
  • Location: 0
  • Status: offline
Re: PIC32MX. Zero-cross and Timer4 issue 2019/06/24 07:10:20 (permalink)
0
Thanks maxruben for the help.
 
I realized that what is occuring here is that the first overflow of the Timer4 is set to 2.35ms, and when the bulb does flash, what occur is that the timer overflow is 4.7ms, double of the expected time. Does somebody know why this can be occuring? I will show this with pictures attached to my post...
 
This is my current code, with some comments.
 
With the code below, the problem still persists.
 
INTERRUPTS
//-----------------------------------------------------------------------------
//TRIAC - ZERO CROSS ON CHANGE NOTICE PIN (PRI:7, SUB:2)
//-----------------------------------------------------------------------------
void __ISR(_CHANGE_NOTICE_VECTOR, IPL7SOFT) ChangeNoticeHandler(void)
{
    ZERO_ = 1; //for tests
    TriacHandleZeroCross(TRIAC());
    ZERO_ = 0; //for tests
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//TRIAC - TIMER (PRI:7, SUB:2)
//-----------------------------------------------------------------------------
void __ISR(_TIMER_4_VECTOR, IPL7SOFT) Timer4Handler(void)
{
    TIMER_ = 1; //for tests
    TriacHandleTimer(TRIAC());
    TIMER_ = 0; //for tests
}
//-----------------------------------------------------------------------------

 
ZERO CROSS HANDLER
//-----------------------------------------------------------------------------
void TriacHandleZeroCross (TRIAC_t *ptr_triac)
{
    ///////////////////////////////////////////////////////////////////////////
    //Clear CNCIF bit
    //*ptr_triac->CN_IF_reg &= ~(ptr_triac->CN_IF_bit);
    IFS1CLR = ptr_triac->CN_IF_bit;
    ///////////////////////////////////////////////////////////////////////////
    
    ///////////////////////////////////////////////////////////////////////////
    //Do a read of pin RC7
    bool pin_read = *ptr_triac->p_zero_cross_port_reg & ptr_triac->zero_cross_port_bit;
    ///////////////////////////////////////////////////////////////////////////
    
    ///////////////////////////////////////////////////////////////////////////
    //Same as: if (CNENCbits.CNIEC7 == 0) return;
    if ( ! (*ptr_triac->CN_EN_reg & ptr_triac->CN_EN_bit) )
    {
        return;
    }
    ///////////////////////////////////////////////////////////////////////////
    //Disable change notice interrupt on pin RC7
    //Same as CNENCbits.CNIEC7 = 0...
    //*ptr_triac->CN_EN_reg &= ~(ptr_triac->CN_EN_bit);
    CNENCCLR = ptr_triac->CN_EN_bit;
    ///////////////////////////////////////////////////////////////////////////
    //Clear timer reg. Same as TMRx = 0;
    *ptr_triac->TMR_reg = 0;
    //TMR4CLR = 0xFFFF; //That does not worked
    ///////////////////////////////////////////////////////////////////////////
    //Set PR4 = "atraso_de_disparo_us" = trigger_delay_us = 2350us = 2.35ms
    *ptr_triac->PR_reg = ptr_triac->atraso_de_disparo_us;
    //PR4SET = ptr_triac->atraso_de_disparo_us; //That does not worked
    ///////////////////////////////////////////////////////////////////////////
    //Clear timer flag
    //*ptr_triac->TMR_IF_reg &= ~(ptr_triac->TMR_IF_bit);
    IFS0CLR = ptr_triac->TMR_IF_bit;
    ///////////////////////////////////////////////////////////////////////////
    Nop();
    ///////////////////////////////////////////////////////////////////////////
    //Enable timer interrupt
    //*ptr_triac->TMR_IE_reg |= ptr_triac->TMR_IE_bit;
    IEC0SET = ptr_triac->TMR_IE_bit;
    ///////////////////////////////////////////////////////////////////////////
}
//-----------------------------------------------------------------------------

 
TIMER HANDLER
//-----------------------------------------------------------------------------
void TriacHandleTimer (TRIAC_t *ptr_triac)
{

    //When the first overflow of the timer occurs (2.35ms):
    //PIN_TRIAC will be in 0, then it is set to 1, and the timer is set to a new overflow after 200us
    //This if is equivalent to: if (PIN_TRIAC == 0)
    if ( ! (*ptr_triac->p_triac_lat_reg & ptr_triac->triac_lat_bit) )
    {
        ///////////////////////////////////////////////////////////////////////////
        //Enable trigger pin. PIN_TRIAC = 1...
        *ptr_triac->p_triac_lat_reg |= ptr_triac->triac_lat_bit;
        ///////////////////////////////////////////////////////////////////////////
        //Clear timer reg. Same as TMRx = 0;
        *ptr_triac->TMR_reg = 0;
        //TMR4CLR = 0xFFFF; //That does not worked
        ///////////////////////////////////////////////////////////////////////////
        //Set PR4 = "valor_PR_para_gerar_pulso" = PR_value_for_200us_overflow_or_200us_pulse
        *ptr_triac->PR_reg = ptr_triac->valor_PR_para_gerar_pulso;
        //PR4SET = ptr_triac->valor_PR_para_gerar_pulso; //That does not worked
        ///////////////////////////////////////////////////////////////////////////
        Nop();
        ///////////////////////////////////////////////////////////////////////////
        //Clear timer flag
        //*ptr_triac->TMR_IF_reg &= ~(ptr_triac->TMR_IF_bit);
        IFS0CLR = ptr_triac->TMR_IF_bit;
        ///////////////////////////////////////////////////////////////////////////
    }
    
    //When the second overflow of the timer occurs (200us):
    //PIN_TRIAC will be in 1, then it is set to 0. At this point the 200us trigger pulse ends
    //this if is equivalent to: if (PIN_TRIAC == 1)
    else
    {
        ///////////////////////////////////////////////////////////////////////////
        //Disable trigger pin. PIN_TRIAC = 0...
        *ptr_triac->p_triac_lat_reg &= (~ptr_triac->triac_lat_bit);
        ///////////////////////////////////////////////////////////////////////////
        //Disable timer interrupt
        //*ptr_triac->TMR_IE_reg &= ~(ptr_triac->TMR_IE_bit);
        IEC0CLR = ptr_triac->TMR_IE_bit;
        ///////////////////////////////////////////////////////////////////////////
        //Re-enable change notice interrupt on pin RC7, for a new cycle...
        //Same as CNENCbits.CNIEC7 = 1...
        //*ptr_triac->CN_EN_reg |= ptr_triac->CN_EN_bit;
        CNENCSET = ptr_triac->CN_EN_bit;
        ///////////////////////////////////////////////////////////////////////////
    }
}
//-----------------------------------------------------------------------------

 
 
 
Regards.
post edited by seccoxiru - 2019/06/24 07:21:28

Attached Image(s)

#6
seccoxiru
Junior Member
  • Total Posts : 103
  • Reward points : 0
  • Joined: 2011/10/27 13:42:59
  • Location: 0
  • Status: offline
Re: PIC32MX. Zero-cross and Timer4 issue 2019/06/24 10:43:33 (permalink)
4.5 (2)
I have found the problem.
There was another source of interrupt. Timer5 was set to overflow each 1ms (it is a 1ms counter for use on the program for delays and other temporizations), and inside of the interrupt handler of Timer5 there was this instruction (that is not atomic): IFS0bits.T5IF = 0; Then I changed this line to IFS0CLR = (1 << 24); and the problem is solved.
That was pointed by maxruben, to use CLR and SET registers because they are atomic manipulation of the bits, mainly on IF bits. Now I know what is atomicity. And that's great, a new knowledge about PIC32 for me, I will have to change many things in my lib now.
 
My new codes of timer and change notice handlers for Triac triggering are as follow:
 
ZERO-CROSS
//-----------------------------------------------------------------------------
void TriacHandleZeroCross (TRIAC_t *ptr_triac)
{
    //Clear CNCIF bit
    *ptr_triac->CN_IF_CLR_reg = ptr_triac->CN_IF_bit;
    //Do a read of pin RC7
    uint16_t pin_read = *ptr_triac->p_zero_cross_port_reg & ptr_triac->zero_cross_port_bit;
    //Same as: if (CNENCbits.CNIEC7 == 0) return;
    if ( ! (*ptr_triac->CN_EN_reg & ptr_triac->CN_EN_bit) )
    {
        return;
    }
    
    ///////////////////////////////////////////////////////////////////////////
    //Disable change notice interrupt on pin RC7
    //Same as CNENCbits.CNIEC7 = 0...
    *ptr_triac->CN_EN_CLR_reg = ptr_triac->CN_EN_bit;
    ///////////////////////////////////////////////////////////////////////////
    //Clear timer reg. Same as TMRx = 0;
    *ptr_triac->TMR_reg = 0;
    //Set PR4 = "atraso_de_disparo_us" = trigger_delay_us = 2350us = 2.35ms
    *ptr_triac->PR_reg = ptr_triac->atraso_de_disparo_us;
    //Clear timer flag
    *ptr_triac->TMR_IF_CLR_reg = ptr_triac->TMR_IF_bit;
    //Enable timer interrupt
    *ptr_triac->TMR_IE_SET_reg = ptr_triac->TMR_IE_bit;
}
//-----------------------------------------------------------------------------

 
TIMER
//-----------------------------------------------------------------------------
void TriacHandleTimer (TRIAC_t *ptr_triac)
{
    //When the first overflow of the timer occurs (2.35ms):
    //PIN_TRIAC will be in 0, then it is set to 1, and the timer is set to a new overflow after 200us
    //This if is equivalent to: if (PIN_TRIAC == 0)
    if ( ! (*ptr_triac->p_triac_lat_reg & ptr_triac->triac_lat_bit) )
    {
        //Enable trigger pin. PIN_TRIAC = 1...
        *ptr_triac->p_triac_lat_reg |= ptr_triac->triac_lat_bit;
        //Clear timer reg. Same as TMRx = 0;
        *ptr_triac->TMR_reg = 0;
        //Set PR4 = "valor_PR_para_gerar_pulso" = PR_value_for_200us_overflow_or_200us_pulse
        *ptr_triac->PR_reg = ptr_triac->valor_PR_para_gerar_pulso;
        //Clear timer flag
        *ptr_triac->TMR_IF_CLR_reg = ptr_triac->TMR_IF_bit;
    }
    
    //When the second overflow of the timer occurs (200us):
    //PIN_TRIAC will be in 1, then it is set to 0. At this point the 200us trigger pulse ends
    //this if is equivalent to: if (PIN_TRIAC == 1)
    else
    {
        //Disable trigger pin. PIN_TRIAC = 0...
        *ptr_triac->p_triac_lat_reg &= (~ptr_triac->triac_lat_bit);
        //Disable timer interrupt
        *ptr_triac->TMR_IE_CLR_reg = ptr_triac->TMR_IE_bit;
        //Re-enable change notice interrupt on pin RC7, for a new cycle...
        //Same as CNENCbits.CNIEC7 = 1...
        *ptr_triac->CN_EN_SET_reg = ptr_triac->CN_EN_bit;
    }
}
//-----------------------------------------------------------------------------

 
 
 
Regards.
 
 
 
 
 
post edited by seccoxiru - 2019/06/24 10:47:01

Attached Image(s)

#7
maxruben
Super Member
  • Total Posts : 3371
  • Reward points : 0
  • Joined: 2011/02/22 03:35:11
  • Location: Sweden
  • Status: offline
Re: PIC32MX. Zero-cross and Timer4 issue 2019/06/24 13:14:42 (permalink)
0
Glad you found it and especially that you understand it. This is a common source of "hard to find" problems, problems that occur so seldom and in so small time window that it is very hard to figure out with normal debugging techniques.
 
/Ruben
#8
Jump to:
© 2019 APG vNext Commercial Version 4.5