CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to support@ccsinfo.com

UART stops answering (with Timer and INT_EXT)
Goto page 1, 2, 3  Next
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
Jean FOUGERON



Joined: 30 Nov 2012
Posts: 110
Location: France

View user's profile Send private message Visit poster's website

UART stops answering (with Timer and INT_EXT)
PostPosted: Mon Jan 07, 2013 4:59 am     Reply with quote

Hi friends
I wish you all a very good year 2013.
I have a problem with my project, that I do not know how to solve.
I use a PIC18F2520 running at 20Mhz
My application shares datas under ModBus protocol and I use INT_RDA (38400 bdx) for it.
It also is linked to a set, measuring delays between positive and negative edges on INT_EXT (# 14ms).
At least it also uses INT_TIMER1 (58ms) and INT_TIMER3 (250 µs) to proceed a reference Time base

The RS232 works OK, until ... it stops. I mean the INT_RDA does not fire after either 2mn or 1 hour, without explaination !
I am afraid of a coincidence betwen INT_EXT and _INT_RDA, but I'm not sure
I have added (via the Timer3) a watchdog which empties the RS232 buffer and reset my input routine, but no result.
Have anyone an idea to help me find a solution ? My customer of course seems in a hurry (always !)
Thanks for all
Jean FOUGERON



Joined: 30 Nov 2012
Posts: 110
Location: France

View user's profile Send private message Visit poster's website

PostPosted: Mon Jan 07, 2013 5:11 am     Reply with quote

Oh, I have forgotten to mention that I have a priority order :
#PRIORITY TIMER3,EXT
Timer3 to have a precise time base (it is not so precise anyway) and EXT to correctly measure.
I suppose that if a character arrives during these INTs, it is store in the 3 char buffer of the PIC and will not be lost ?
Ttelmah



Joined: 11 Mar 2010
Posts: 19338

View user's profile Send private message

PostPosted: Mon Jan 07, 2013 5:48 am     Reply with quote

Have you got 'ERRORS' in your RS232 declaration?.
This is _required_, unless you are handling UART errors yourself. Without it, if a UART overrun occurs, or a byte is received that is 'malformed', the UART _will_ stop receiving, till the error is cleared.
There is only a 1.9999 character buffer, not '3 characters'. The byte actually being received, and one stored character, till the 'moment' the received character is complete and wants to transfer to the buffer.
PRIORITY, only affects the order the interrupts are tested when the handler is called. It does not allow one interrupt to interrupt another (for this you need HIGH_INTS=TRUE, and to flag one interrupt as HIGH. However you can't use this, since you are using INT_EXT, and if this is enabled, INT_EXT is always a high priority interrupt (feature of the hardware....).
It almost certainly implies that a combination of one or more of the interrupts is taking too long in the handler for the RS232 to be handled. So long as your code can recover from a single character being lost, adding ERRORS should fix it.

Best Wishes
Ttelmah



Joined: 11 Mar 2010
Posts: 19338

View user's profile Send private message

PostPosted: Mon Jan 07, 2013 7:59 am     Reply with quote

As a further comment, if you are designing the hardware, then move whatever is connected to INT_EXT, to INT_EXT2.

Then add the #DEVICE HIGH_INTS=TRUE, and set timer3 as HIGH.
Then modify your priority statement (or the order you declare your interrupts, which does the same thing) to have INT_RDA first (don't include TIMER3 in this), so it'd be: #PRIORITY INT_RDA,INT_EXT,INT_TIMER1. Add ERRORS, and the chances of things working goes up a lot.

Look five times at the code in your interrupts. Repeat the mantra 'keep handlers short', for a couple of days, and see if you can reduce the timings involved. There are several 'caveat' ones that may get you - arithmetic avoid anything other than addition and subtraction in the interrupts. If you are using '%' in the serial handler, unless your buffer is a 'binary' size like 16 bytes, don_t. Use a test and load instead (several threads here about this). Be aware of how slow const accesses are, and array handling. Sometimes quite small changes can make big differences to execution. For instance, a 'switch' statement without a 'default', with several values, is much faster than the same statement with a default (the former uses a jump table).

Best Wishes
Jean FOUGERON



Joined: 30 Nov 2012
Posts: 110
Location: France

View user's profile Send private message Visit poster's website

PostPosted: Mon Jan 07, 2013 8:25 am     Reply with quote

Thanks Ttelmah for your quick answer.
I already have #DEVICE HIGH_INTS=TRUE
As I need a precise counting with Timer3 I have #PRIORITY TIMER3,EXT
And #INT_TIMER3 high and #INT_EXT high (that you told me is unusefull, I maybe can suppress the "high")

The modbus query is 9 bytes long, includinc CRC16, it is not so long.

/**********************************************/
The INT_EXT duration is 250 s (it only set orn reset flags which are used in main afterward)

Code:
#INT_EXT high
void Boucle()
{
   L_Compteur_Flanc=L_Compteur;
   Flanc_Pulse();   
}


with Flanc_Pulse :
Code:
void Flanc_pulse()
{   
   if(I_Pas_de_Signal)
   {
      if(I_Pas_de_Signal==CYCLE_No_Signal) SFLS.Changed=TRUE;
      I_Pas_de_Signal=0;
   }
   
   if(B_Rising)                                 // flanc montant
   {   
      if (Proximite(L_Compteur,I_Intervalle_14))
      {            
         if(I_LED_en_cours<SFLS.length)
         {
            I_LED_en_cours++;
         }
         else
         {
            I_LED_en_cours=1;                   //pour si il n'y avait pas de pulse de 5 ms
         }
         LED(I_LED_en_cours, TRUE, VERT);
         Clear_Alarme(I_Led_en_cours); 
         //SFLS.duree[I_LED_en_cours]=L_Compteur;
      }
      else
      {
         if(I_LED_en_cours>=SFLS.length)
         {
            // on est au début a SFLS.length, on le spécifiera à 5ms
            I_LED_en_cours=0;
         }
         else
         {      
            if(L_Compteur>I_Intervalle_14)
            {
               if(I_LED_en_cours<SFLS.length)   //Soit c'est un défaut, soit on n'a pas vu le 15ms ?
               {
                  int I,J,K;
                  // soit c'est une erreur   
                  // ceux d'avant sont rouges
                  I=(L_Compteur+4)/I_Intervalle_14-1;
                  //I est le nombre de LED rouge à allumer

                  J=SFLS.length-I_LED_en_cours;
                  if (I>J) I=J;
                  //Affiche un nombre I de led rouge à partir de la led en cours
                  for(K=1;K<=I;K++)
                  {
                     Set_Alarme(++I_LED_en_cours);
                     //SFLS.duree[I_LED_en_cours]=I_Intervalle_14;   // on simule un temps pour la moyenne
                  }            
                  // celui là est vert à priori
                  LED(++I_LED_en_cours,TRUE,VERT);
               }   
            }
            else
            {
               //bizarre, plus court !
            }   
         }
      }
      L_Compteur=0;
      ext_int_edge(0,H_TO_L);   //prochain mode d'interruption : descendant
      B_Rising=FALSE;
      
      set_timer3(L_Period);      // réinitialise le compteur
   }
   else                         // flanc descendant
   {   
      LED (I_LED_en_cours, FALSE, VERT);   //éteindre la verte
      if (Proximite(L_Compteur,I_Intervalle_5))
      {
         I_Intervalle_5=L_Compteur;
         I_LED_en_cours=0;      // même si on n'avait pas vu SFLS.last
      }
      else
      {
         if (Proximite(L_Compteur,I_Intervalle_10))   
         {
            // normal
            I_Intervalle_10=L_Compteur;
         }
         else
         {
            if (Proximite(L_Compteur,I_Intervalle_15))
            {
               //dernier
               I_Intervalle_15=L_Compteur;
               I_LED_en_cours=SFLS.length;   
            }
            else
            {
               //erreur >15 ne devrait pas exister
            }
         }
      }
      ext_int_edge(0,L_TO_H);   //prochain mode d'interruption : montant
      B_Rising=TRUE;
   }
    L_Compteur_Flanc=0;
}


/*********************************************/
The INT_RDA duration is 4µs

Code:
#INT_RDA
void Receive()
{
char c;
   c=getc();
   if(I_UART_IN_POS==0 && c==I_NumeroEsclave)         // on commence à prendre en compte si c'est bien la bonne adresse
   {
      INStr[I_UART_IN_POS++]=c;
      B_UART_OK=FALSE;
   }   
   else if(I_UART_IN_POS<UART_MAX)
     {
        INStr[I_UART_IN_POS++]=c;
        B_UART_OK=(I_UART_IN_POS>=UART_MAX);
   }
   L_Cycle_UART=1;
}


/******************************************************/
The INT_Timer1 is 440 µs

Code:
#INT_TIMER1                                    
void Affiche_Defaut()
{
int i;
Boolean B_Alarme;

   if(++I_Timer1>2)
   {   
      I_Timer1=0;
      
      if(!I_Pas_de_Signal)
      {
         B_Alarme=FALSE;
         for (i=0; i<SFLS.length;i++)
         {   
            if(SFLS.defaut[i]>0)                     //défaut
            {      
               if(SFLS.defaut[i]<I_DELAI_LAMPE_ALARME_MAX)
               {
                  SFLS.defaut[i]++;                  // délai d'affichage      
               }
               else
               {   
                  SFLS.defaut[i]=I_DELAI_LAMPE_ALARME_MAX;
                  LED(i+1, TRUE, ROUGE);            // affichage
                  B_Alarme=TRUE;
               }      
            }
            else
            {
               LED(i+1, FALSE, ROUGE);            // extinction
            }      
         }   
         if(B_Alarme)
         {
            if(I_Delai_Alarme<I_DELAI_ALARME_MAX)
            {
               I_Delai_Alarme++;
               if(SFLS.Alarm) {SFLS.Alarm=FALSE; SFLS.Changed=TRUE;}
            }
            else
            {
               I_Delai_Alarme=I_DELAI_ALARME_MAX;
               if(!SFLS.Alarm) {SFLS.Alarm=TRUE; SFLS.Changed=TRUE;}
            }   
         }
         else
         {
            I_Delai_Alarme=0;
            if(SFLS.Alarm) {SFLS.Alarm=FALSE; SFLS.Changed=TRUE;}
         }      
      }
   }
}


/*****************************************************/
The INT_Timer3 (critical regularity) is 5µs

Code:
#INT_TIMER3 high
void Comptage()            
{
int I;
   //doit donc battre le quart de milliseconde
     L_Compteur++;
     
     if(L_Cycle_UART && I_UART_IN_POS) L_Cycle_UART++;
   if (L_Cycle_UART>CYCLE_250)      // au bout de 250ms
   {;
      while(kbhit()) getc();
      L_Cycle_UART=0;
      I_UART_IN_POS=0;         //répare l'UART
      B_UART_OK=FALSE;
   }
      
     if(++L_Cycle_Seconde>CYCLE_1000)               // toute les secondes
     {        
        L_Cycle_Seconde=0;
      if(++I_Cycle_5_Secondes>CYCLE_5)            // au bout de 5 secondes
      {
         I_Cycle_5_Secondes=0;   
      }

      if(++I_Pas_de_Signal>CYCLE_No_Signal)         // Au bout de 3 secondes
      {
         I_Pas_de_Signal=CYCLE_No_Signal;
         
         B_Pas_de_Signal=!B_Pas_de_Signal;
         if (B_Pas_de_Signal)
         {
            for(I=2; I<SFLS.Length; I++)               //problème si on commence I à 1
            {
               SFLS.defaut[I]=B_Pas_de_Signal;
               LED(I++,B_Pas_de_Signal,ROUGE);
            }
         }
         else Efface_Tout();
         SFLS.Alarm=TRUE;
         SFLS.Changed=TRUE;
      }
     }
                                                   
     set_timer3(L_Period);                 
}


I will add ERRORS, try to understand how it works, and come back to you
Regards
Jean FOUGERON



Joined: 30 Nov 2012
Posts: 110
Location: France

View user's profile Send private message Visit poster's website

PostPosted: Mon Jan 07, 2013 8:26 am     Reply with quote

The INT_EXT duration is 250 µs (it only set orn reset flags which are used in main afterward) SORRY
Ttelmah



Joined: 11 Mar 2010
Posts: 19338

View user's profile Send private message

PostPosted: Mon Jan 07, 2013 9:34 am     Reply with quote

There is a potential logic problem I can see with:
Code:

      while(kbhit()) getc();

Problem is that if kbhit has gone 'true' the INT_RDA flag _will_ have been set. When Timer3 then exits, INT_RDA will be called, and no character will be available.

I'd add:
Code:

     while(kbhit()) {
        getc();
        clear_interrupt(INT_RDA);
     }


Best Wishes
ckielstra



Joined: 18 Mar 2004
Posts: 3680
Location: The Netherlands

View user's profile Send private message

PostPosted: Mon Jan 07, 2013 10:14 am     Reply with quote

Just a minor remark on your Timer3:
Code:
set_timer3(L_Period);
This line is at the end of your interrupt routine. Up till this point your ISR has executed a variable number of instructions, depending on the program flow. Also there is the fixed delay of about 30 instruction times required for saving all working registers before your interrupt handler is entered. And then there is the possible extra delay when another (high level) interrupt is already active and has to finish first.
All-in-all, the fixed time you set before the next timer interrupt is fired is not as exact as you believe it is.
Much better:
Code:
set_timer3(get_timer3() + L_Period);


Another tiny comment:
Code:
   else if(I_UART_IN_POS<UART_MAX)
     {
        INStr[I_UART_IN_POS++]=c;
        B_UART_OK=(I_UART_IN_POS>=UART_MAX);

The last line is equal to
Code:
        B_UART_OK= TRUE;
Jean FOUGERON



Joined: 30 Nov 2012
Posts: 110
Location: France

View user's profile Send private message Visit poster's website

PostPosted: Mon Jan 07, 2013 10:48 am     Reply with quote

I understand the Timer3 duration topic
I so set_timer3(get_timer3() + L_Period);, I put set_timer3(L_Period); in the beginning of the procedure (I will re-calculate the correct value) Am I right ?

Regarding The last line is equal to
Code:
B_UART_OK= TRUE;

I think it is not the case. It is TRUE when I_UART_IN_POS>=UART_MAX
Suppose I_UART_IN_POS=2, I_UART_IN_POS++ << UART_MAX (which is 9)
So B_UART_OK will be TRUE when all the bytes will be received
No ?
Thanks and good evening
asmboy



Joined: 20 Nov 2007
Posts: 2128
Location: albany ny

View user's profile Send private message AIM Address

PostPosted: Mon Jan 07, 2013 1:18 pm     Reply with quote

I might add that Ttelmahs suggestion is excellent based on what you show,
Code:

 while(kbhit()) {
        getc();
        clear_interrupt(INT_RDA);
     }

BUT!!!
IMHO is is a HUGE logical mistake to service the uart inside the TIMER ISR
and even with the fix proposed above -

this aspect alone could have hard to predict consequences, though my bet would be that THIS exact aspect is why the RDA ISR eventually fails.

Why DO you attempt to read the UART from the timer isr anyway?? is there that much latency in MAIN -if you try to take a high level action based on a uart mediated vector /command ?
ckielstra



Joined: 18 Mar 2004
Posts: 3680
Location: The Netherlands

View user's profile Send private message

PostPosted: Mon Jan 07, 2013 1:39 pm     Reply with quote

Jean FOUGERON wrote:
I understand the Timer3 duration topic
I so set_timer3(get_timer3() + L_Period);, I put set_timer3(L_Period); in the beginning of the procedure (I will re-calculate the correct value) Am I right ?
If you did calculate the value for L_Period the first time instead of trial & error, then your original value will be OK. No need for recalculation, that's part of the beauty of this solution.
For example you had calculated a period time of 5000 ticks, then L_Period would have become 65536 - 5000 = 60536.
It doesn't matter where in your ISR you do set the Timer3 value. If it is early in the code, then for example the value of Timer3 will have increased to 10. Putting it at the end of the ISR the Timer3 value could have increased, for example, to 15 but this doesn't matter. Because you add your L_Period to the timer value it will overflow a bit sooner, but that is exactly what you want to.

Quote:
Regarding The last line is equal to
Code:
B_UART_OK= TRUE;

I think it is not the case. It is TRUE when I_UART_IN_POS>=UART_MAX
Suppose I_UART_IN_POS=2, I_UART_IN_POS++ << UART_MAX (which is 9)
So B_UART_OK will be TRUE when all the bytes will be received
No ?
You are right and I was wrong. B_UART_OK will be set to TRUE when you have received UART_MAX characters.

asmboy wrote:
IMHO is is a HUGE logical mistake to service the uart inside the TIMER ISR
and even with the fix proposed above -
I think this code is there only to restart the receiver in the case of a message timeout. A timer is started when the StartOfMessage character is seen and then 250ms later he restarts the receiver state machine. A little bit of inline documentation would have helped here.
Ttelmah



Joined: 11 Mar 2010
Posts: 19338

View user's profile Send private message

PostPosted: Mon Jan 07, 2013 2:05 pm     Reply with quote

Key is that all of his interrupts take significantly longer than he thinks. It takes perhaps 7uSec to actually arrive at the interrupt code (for the 'high' interrupts), and at least a uSec longer for the 'low' ones. Then he gives 'times' for the interrupts, but this varies with the logic tests, and in some cases could be longer than he thinks.

The there are also some odd logic values that seem inverted. For instance:
Code:

   {
      INStr[I_UART_IN_POS++]=c;
      B_UART_OK=FALSE;
   }   
   else if(I_UART_IN_POS<UART_MAX)
     {
        INStr[I_UART_IN_POS++]=c;
        B_UART_OK=(I_UART_IN_POS>=UART_MAX);
   }
   L_Cycle_UART=1;

Which sets 'B_UART_OK' to 'true' when the buffer is overflowing?....

Seems rather the opposite of logic.

There also doesn't seem to be anything actually ensuring the buffer does not physically overflow.

I also don't like a lot of his names. INSTR, is a function name in several languages to find one string in another, while he uses upper case in some places for variable names, and in others for presumably #defined values. There is a 'standard' in C, to reserve 'ALL CAPS' for #defined values. Minor, but it makes understanding the code harder.

Best Wishes
Jean FOUGERON



Joined: 30 Nov 2012
Posts: 110
Location: France

View user's profile Send private message Visit poster's website

PostPosted: Tue Jan 08, 2013 1:59 am     Reply with quote

Dear Ttelmah
Quote:
it makes understanding the code harder
: OK I will do an effort to satisfy

Quote:
Seems rather the opposite of logic.
I do not understand what you mean. This routine works to
Quote:
B_UART_OK will be set to TRUE when you have received UART_MAX characters
. No ?

Yes ckielstra,
Quote:
this code is there only to restart the receiver in the case of a message timeout
because I cannot take the risk of no communication with the set.

The solution of Ttelmah to do
Code:
 #use RS232(BAUD=38400,BITS=8,PARITY=N,STOP=1,ENABLE=PIN_C5, UART1, ERRORS)
is a super solution, as I tried this night and only had 105 retry of query in 15h26mn (1 query every 1s). Before I had a blocking situation (continuously retry) after 30' or 1h !

So maybe this makes the
Quote:
message timeout
routine in Timer3 unusefull ?
I today will try without
Code:
while(kbhit())
{
     getc();
     clear_interrupt(INT_RDA);
}

expecting that the ERRORS parameter does the same (resetting UART)

(I also will do my best to improve
Quote:
inline documentation
)

Thanks to all of you for your help
ckielstra



Joined: 18 Mar 2004
Posts: 3680
Location: The Netherlands

View user's profile Send private message

PostPosted: Tue Jan 08, 2013 2:02 am     Reply with quote

Ttelmah wrote:
There also doesn't seem to be anything actually ensuring the buffer does not physically overflow.
Yes, there is a protection against overflow but this is not marked as a possible error.

I do agree there is a strange logic in the code. Looking again at the INT_RDA, it will miss some messages.
Code:
   c=getc();
   if(I_UART_IN_POS==0 && c==I_NumeroEsclave)         // on commence à prendre en compte si c'est bien la bonne adresse
   {
      INStr[I_UART_IN_POS++]=c;
      B_UART_OK=FALSE;
   }   
   else if(I_UART_IN_POS<UART_MAX)
     {
        INStr[I_UART_IN_POS++]=c;
        B_UART_OK=(I_UART_IN_POS>=UART_MAX);
   }
When it is out of sync and the first received character doesn't match the expected slave address (I_NumeroEsclave), then it will still save the received byte and increment I_UART_IN_POS. From that moment on all data will be read until the buffer is full or 250ms have passed.
Because of the 250ms time out I guess the system will recover eventually, but that requires a larger than 250ms gap. On a busy system there will be messages getting lost.

The message receive state machine needs to be redesigned. And it would be advisable to have the messages start with a unique start character so it will be easier to get 'in sync' again.
Ttelmah



Joined: 11 Mar 2010
Posts: 19338

View user's profile Send private message

PostPosted: Tue Jan 08, 2013 3:29 am     Reply with quote

Yes. My problem is that without knowing the sizes involved, several things could potentially go wrong with the tests as implemented. Depends on the relationship between UART_MAX, the size of I_UART_IN_POS, and the size of the buffer. I have the nasty visualisation of buffers at 256 bytes, and then the counter will have wrapped before the test.....
The logic on B_UART_OK, is that in fact it is nothing to do with the UART, but in fact a 'message received' flag.

Best Wishes
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Goto page 1, 2, 3  Next
Page 1 of 3

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group