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

Interrupt driven serial

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
adamp524



Joined: 06 May 2010
Posts: 22

View user's profile Send private message

Interrupt driven serial
PostPosted: Thu Jul 29, 2010 9:42 am     Reply with quote

Hardware: PIC18F4585 with its internal oscillator at 8Mhz.

I'm writing a program to read an XML message from the serial port (PIC hardware UART). The message start and end positions are denoted by an ASCII start line character (0x02) and an end line character (0x03).

My problem is that when I run the program, and send it 1 XML message every 100ms, the PIC locks up, and the watchdog timer kicks in and resets the device. This can happen any time between a couple of seconds to a few minutes from startup.

I've posted my serial interrupt and my main() function code below.

Any help would be appreciated.

Thanks

Code:
//Global serial variables
#define SERIAL_BUFFER_SIZE 500
char SERIAL_BUFFER[SERIAL_BUFFER_SIZE] = "";
long SERIAL_BUFFER_NEXT = 0;
int1 SERIAL_PROCESS = FALSE;

#define START_LINE   0x02
#define END_LINE     0x03
#int_RDA
void RDA_isr(void)
{
   char ch;
   static int1 startFound = FALSE;
   
   disable_interrupts(INT_RDA);
   
   ch = getc();
     
   //Start of line found
   if(ch == START_LINE || startFound == TRUE)
   {
      startFound = TRUE;
     
      SERIAL_BUFFER[SERIAL_BUFFER_NEXT++] = ch;
     
      if(SERIAL_BUFFER_NEXT > SERIAL_BUFFER_SIZE)
      {
         //Buffer overflow - dump received data and start again
         
         //disable serial receiver
         output_high(RECV_EN2);
         
         disable_interrupts(INT_RDA);
         memset(SERIAL_BUFFER, '\0', SERIAL_BUFFER_SIZE);
         SERIAL_BUFFER_NEXT = 0;
         startFound = FALSE;
         
         enable_interrupts(INT_RDA);
         
         //enable serial receivers
         output_low(RECV_EN2);
      }     
     
      if(ch == END_LINE) //End of line found
      {
         //disable serial receivers
         output_high(RECV_EN2);
         
         disable_interrupts(INT_RDA);
         SERIAL_PROCESS = TRUE;
         startFound = FALSE;
         
      }
   }
   
   if(SERIAL_PROCESS == FALSE)
   {
      enable_interrupts(INT_RDA);
   }
}


main():
Code:
void main()
{
   enable_interrupts(INT_RDA);
   enable_interrupts(GLOBAL);

   setup_wdt(WDT_ON);

   while(TRUE)
   {
     
      process_serial();
      restart_wdt();
     
   }
}


process_serial():
Code:
void process_serial()
{
   if(SERIAL_PROCESS == TRUE)
   {
      //Process serial buffer

      memset(SERIAL_BUFFER, '\0', SERIAL_BUFFER_SIZE);
      SERIAL_BUFFER_NEXT = 0;
      SERIAL_PROCESS = FALSE;
     
      enable_interrupts(INT_RDA);
     
      //enable serial receivers
      output_low(RECV_EN2);
   }
}
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Thu Jul 29, 2010 8:36 pm     Reply with quote

Your code is a little bit difficult to read because you didn't follow the
normal C style convention of putting constants in upper case and
variables in lower case or mixed case. Also you didn't show your PIC
type or #fuses, etc. That's problably why there are no replies.

That said, I looked a little bit at your program and found a problem:
Quote:

#define SERIAL_BUFFER_SIZE 500
char SERIAL_BUFFER[SERIAL_BUFFER_SIZE] = "";
long SERIAL_BUFFER_NEXT = 0;
int1 SERIAL_PROCESS = FALSE;

SERIAL_BUFFER[SERIAL_BUFFER_NEXT++] = ch;

if(SERIAL_BUFFER_NEXT > SERIAL_BUFFER_SIZE)
{
SERIAL_BUFFER_NEXT = 0;

In the code above, you are checking for the end of the buffer by
comparing the index to the buffer size (500). In order for the if() test
to be true, the index must be 501. This means that the previous line
wrote 'ch' to index 500. Then the index was post-incremented to 501.
But there is no index of 500. The array only contains elements in
the range 0 to 499. Arrays start at 0.

Notice how CCS checks for buffer wrap in Ex_usb_serial2.c, in this
routine. They do something very similar to what you're doing, but
notice how they use '>=' to do the test:
Code:

char g_PutcBuffer[350];
int16 g_PutcNextIn=0, g_PutcNextOut=0;

void putc_tbe(char c)
{
   g_PutcBuffer[g_PutcNextIn++] = c;
   if (g_PutcNextIn >= sizeof(g_PutcBuffer))
      g_PutcNextIn = 0;
}




Quote:

//disable serial receiver
output_high(RECV_EN2);

disable_interrupts(INT_RDA);
memset(SERIAL_BUFFER, '\0', SERIAL_BUFFER_SIZE);
SERIAL_BUFFER_NEXT = 0;
startFound = FALSE;

enable_interrupts(INT_RDA);

Another point. There are no nested interrupts. You are not going to
get interrupted by another #int_rda interrupt in the middle of this
routine. So you don't have to disable interrupts and re-enable them
in the middle of the routine. Or at the start of the routine.
adamp524



Joined: 06 May 2010
Posts: 22

View user's profile Send private message

PostPosted: Mon Aug 09, 2010 7:37 am     Reply with quote

Thanks PCM Programmer

I tried changing the SERIAL_BUFFER_NEXT check to '>=' but this doesn't solve my problem - the WDT still catches the program somewhere and resets the PIC.

The reason for my disabling the serial interrupt was to ensure that the process_serial() function had finished before accepting any more serial data. If not, my program could had received more data and changed the buffer that would currently be processing.

Any other ideas?

Thanks
ckielstra



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

View user's profile Send private message

PostPosted: Mon Aug 09, 2010 9:31 am     Reply with quote

You are making excessive use of enabling/disabling the RDA interrupt. This is not an error but makes your code a lot more difficult to read.
I understand why you want to disable the interrupt, but there is only one location where you have to do this, and that is after receiving the END marker.

In the fragment below I have marked the superfluous lines with a '//' in the first column.
Code:
#int_RDA
void RDA_isr(void)
{
   char ch;
   static int1 startFound = FALSE;
   
//   disable_interrupts(INT_RDA);
   
   ch = getc();
     
   //Start of line found
   if(ch == START_LINE || startFound == TRUE)
   {
      startFound = TRUE;
     
      SERIAL_BUFFER[SERIAL_BUFFER_NEXT++] = ch;
     
      if(SERIAL_BUFFER_NEXT > SERIAL_BUFFER_SIZE)
      {
         //Buffer overflow - dump received data and start again
         
         //disable serial receiver
         output_high(RECV_EN2);
         
//         disable_interrupts(INT_RDA);
         memset(SERIAL_BUFFER, '\0', SERIAL_BUFFER_SIZE);
         SERIAL_BUFFER_NEXT = 0;
         startFound = FALSE;
         
//         enable_interrupts(INT_RDA);
         
         //enable serial receivers
         output_low(RECV_EN2);
      }     
     
      if(ch == END_LINE) //End of line found
      {
         //disable serial receivers
         output_high(RECV_EN2);
         
         disable_interrupts(INT_RDA);
         SERIAL_PROCESS = TRUE;
         startFound = FALSE;
         
      }
   }
   
//   if(SERIAL_PROCESS == FALSE)
//   {
//      enable_interrupts(INT_RDA);
//   }
}


A real error is that in the small chance when receiving the END marker is also creating a buffer overflow, you are handling this combination wrong. The buffer is cleared but also the SERIAL_PROCESS flag is set.

There are no obvious errors in the code that could create a watchdog error. Without a complete program demonstrating your problem we can only guess. One thing that comes to mind is that you are missing the 'ERRORS' directive in the #use rs232 line. Without this directive the UART will stall after a receive buffer overflow (after 3 characters).
bkamen



Joined: 07 Jan 2004
Posts: 1611
Location: Central Illinois, USA

View user's profile Send private message

PostPosted: Mon Aug 09, 2010 9:49 am     Reply with quote

ckielstra wrote:
You are making excessive use of enabling/disabling the RDA interrupt. This is not an error but makes your code a lot more difficult to read.
I understand why you want to disable the interrupt, but there is only one location where you have to do this, and that is after receiving the END marker.



I agree.

You NEVER want to really disable the interrupt because of the potential for overflows locking the unit (although ERRORs does clear)...

One way I do it (like with GPS's) is when gathering receive data in an interrupt, it gets stored to a buffer with a state machine that throws away successive received chars until the buffer is processed.

I make the process routine as fast as possible to clear that "toss" flag so that ISR can go back on its merry way.

I never disable the INT_RDA inside the ISR. (although I can see why someone else might want to, and not that it's wrong. I just don't do it.)

Yikes. Just toss chars away.

-Ben
_________________
Dazed and confused? I don't think so. Just "plain lost" will do. :D
adamp524



Joined: 06 May 2010
Posts: 22

View user's profile Send private message

PostPosted: Tue Aug 10, 2010 7:18 am     Reply with quote

Thanks everyone for your replies - sadly I'm still having problems with the serial!

ckielstra - I changed my serial buffer code from an 'if' to an 'else if' for the End of line found code - no change

I have also tried changing the #use directive for rs232 to include RESTART_WDT, ERRORS and DISABLE_INTS

e.g. #use rs232(stream=serial, baud=9600, UART1, bits=8, stop=1, RESTART_WDT, ERRORS, DISABLE_INTS)

bkamen - I've also tried dumping the data on the serial port when the buffer is being processed rather than disabling interrupts - no change.

I'll post my code in full below, see if that helps:

main.c:
Code:
#include "main.h"

//Serial defines
#define START_LINE   0x02
#define END_LINE     0x03

//Global serial variables
char SERIAL_BUFFER[SERIAL_BUFFER_SIZE] = "";
long SERIAL_BUFFER_NEXT = 0;
int1 SERIAL_PROCESS = FALSE;

//Initialise device address
int DEVICE_ADDR=255;

int TIMER1_OVERFLOW = 0;

//Serial RX ISR
#int_RDA
void RDA_isr(void)
{
   char ch;
   static int1 startFound = FALSE;
   
   ch = getc();
     
   //Start of line found
   if(ch == START_LINE || startFound == TRUE)
   {
      startFound = TRUE;
     
      SERIAL_BUFFER[SERIAL_BUFFER_NEXT++] = ch;
     
      if(SERIAL_BUFFER_NEXT >= SERIAL_BUFFER_SIZE)
      {
         //Buffer overflow - dump received data and start again
         fprintf(debug, "Buffer overflow\r\n");
         //disable serial receiver
         output_high(RECV_EN2);
         
         disable_interrupts(INT_RDA);
         
         memset(SERIAL_BUFFER, '\0', SERIAL_BUFFER_SIZE);
         SERIAL_BUFFER_NEXT = 0;
         startFound = FALSE;
         
         enable_interrupts(INT_RDA);
         
         //enable serial receivers
         output_low(RECV_EN2);
      }     
     
      else if(ch == END_LINE) //End of line found
      {
         //disable serial receivers
         output_high(RECV_EN2);
         
         disable_interrupts(INT_RDA);
         SERIAL_PROCESS = TRUE;
         startFound = FALSE;
      }
   }
}

void startup()
{
   //tri-state serial drivers and receivers
   output_high(RECV_EN1);
   output_high(RECV_EN2);
   output_low(DRIV_EN1);
   output_low(DRIV_EN1);
   
   //Allow spare IO to float
   output_float(SPARE_IO1);
   output_float(SPARE_IO2);
   
   //Initialise LCD pins
   output_low(LCD_RS);
   output_low(LCD_RW);
   output_low(LCD_E);
   output_low(LCD_DB4);
   output_low(LCD_DB5);
   output_low(LCD_DB6);
   output_low(LCD_DB7);
     
   //Enable and configure timers and interrupts
   enable_interrupts(INT_EXT);
   enable_interrupts(INT_TIMER1);
   enable_interrupts(INT_RDA);
   enable_interrupts(GLOBAL);
   ext_int_edge(H_TO_L);
   setup_timer_1(T1_INTERNAL|T1_DIV_BY_4);
   
   //Enable serial drivers and receivers
   output_low(RECV_EN1);
   output_low(DRIV_EN1);
   
   output_low(RECV_EN2);
   output_low(DRIV_EN2);
   
   //Print ready to debug serial port
   fprintf(debug, "\n\rReady - DeviceAddr:%u\n\r",DEVICE_ADDR);
   
   setup_wdt(WDT_ON);
}

void main()
{
   startup();
   
   while(TRUE)
   {
      if(TIMER1_OVERFLOW >= 3)
      {
         /* Flash CS LED1
         if(flag == 0)
         {
            io_exp_output(GPIOA_ADDR, CS_LED1_ADDR, ON);
            flag = 1;
         }
         else
         {
            io_exp_output(GPIOA_ADDR, CS_LED1_ADDR, OFF);
            flag = 0;
         }
         */
       
         TIMER1_OVERFLOW = 0;
         //fprintf(debug, "a"); //show alive
         
      }

      process_serial();
      restart_wdt();
   }
}

void process_serial()
{
   if(SERIAL_PROCESS == TRUE)
   {
      //fprintf(debug, "Buf:%s", SERIAL_BUFFER);
      //parse_xml(SERIAL_BUFFER);
      delay_ms(50);
      memset(SERIAL_BUFFER, '\0', SERIAL_BUFFER_SIZE);
      SERIAL_BUFFER_NEXT = 0;
      SERIAL_PROCESS = FALSE;
     
      enable_interrupts(INT_RDA);
     
      //enable serial receivers
      output_low(RECV_EN2);
   }
}


main.h
Code:
#include <18F4585.h>

//#FUSES NOWDT                    //No Watch Dog Timer
#FUSES WDT1024
#FUSES INTRC_IO                 //Internal RC Osc, no CLKOUT
#FUSES NOPROTECT                //Code not protected from reading
#FUSES BROWNOUT                 //Reset when brownout detected
#FUSES MCLR                     //Master Clear pin enabled
#FUSES NOLVP                    //No Low Voltage Programming
#FUSES NOXINST                  // Extended set extension and Indexed Addressing mode disabled (Legacy mode)

#device PASS_STRINGS=IN_RAM

#use delay(clock=8000000)
#use rs232(stream=serial, baud=9600, UART1, bits=8, stop=1, RESTART_WDT, ERRORS, DISABLE_INTS)
#use rs232(stream=debug, baud=19200, xmit=PIN_C2, rcv=PIN_C5, bits=8, stop=1)
#use i2c(MASTER, scl=PIN_C3, sda=PIN_C4, FAST, FORCE_HW)

#define FIRMWARE_VERSION "KpdDisp v1.0"

//LCD IO
#define LCD_DB4   PIN_A3
#define LCD_DB5   PIN_A4
#define LCD_DB6   PIN_A5
#define LCD_DB7   PIN_A6

#define LCD_RS    PIN_A1
#define LCD_RW    PIN_A2
#define LCD_E     PIN_A0

#define LCD_BL    PIN_A7

//Cardswipe IO
//Note: CS Clock set on interrupt pin
#define CS_DATA_PIN PIN_E2

//Keypad IO
//Note: pullup resistors must be placed on all rows
#define row0      PIN_D0
#define row1      PIN_D1
#define row2      PIN_D2
#define row3      PIN_D3
#define row4      PIN_D4
#define col0      PIN_D5
#define col1      PIN_D6
#define col2      PIN_D7
#define col3      PIN_E0
#define KPD_TMPR  PIN_E1

//I2C
//IO Expander: Microchip MCP23017
#define IO_RST    PIN_C0
#define IO_WRITE  0x40
#define IO_READ   IO_WRITE+1

//Serial IO
#define RECV_EN1  PIN_B1
#define DRIV_EN1  PIN_B2
#define RECV_EN2  PIN_B3
#define DRIV_EN2  PIN_B4

//Spare IO
#define SPARE_IO1 PIN_B5
#define SPARE_IO2 PIN_C1

void process_serial(void);
void startup(void);


Thanks
ckielstra



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

View user's profile Send private message

PostPosted: Tue Aug 10, 2010 1:28 pm     Reply with quote

Code:
   enable_interrupts(INT_EXT);
   enable_interrupts(INT_TIMER1);
You enable the interrupts without an interrupt handler function being present. When the interrupt now occurs it is never cleared and the program forever hangs in the interrupt. That's why the watchdog kicks in.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
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