Tagc
Tagc

Reputation: 9072

How can I improve the rate at which the CAN IRQ is called for the LPC1788 microcontroller?

I'm developing an application for the NXP LPC1788 microcontroller using the IAR Embedded Workbench IDE and one of the requirements is to receive CAN messages off of the CAN1 port as fast as possible. My issue is that there seems to be a delay of ~270 microseconds between the start of one invocation of the CAN IRQ and the start of the next even though it only seems to take the microcontroller ~30 microseconds to handle the interrupt.

In addition to this interrupt, I also have regular timer interrupts scheduled every 50 microseconds which process received CAN and USB messages. However, this interrupt routine takes ~3 microseconds if there are no CAN or USB messages to process and only around ~80 microseconds if there are.

Because of this, I'm wondering why the CAN IRQ is not able to fire faster than every 270 microseconds and what I can do to fix that.

USB interrupts have negligible impact on performance since USB messages arrive much more infrequently than CAN messages, and I've determined that this problem occurs even when this interrupt is never fired.

Currently, my application seems to be able to handle CAN messages with an average inter-arrival time of ~300 us (tested for 3 messages generated at 1 ms intervals, with ~500,000 messages processed and a 0% drop rate).

This code initialises the CAN:

void CANHandlerInit()
{   
  // Configure CAN pins.
  PINSEL_ConfigPin(0, 0, 1); // RD1.
  PINSEL_ConfigPin(0, 1, 1); // TD1.
  PINSEL_ConfigPin(0, 4, 2); // RD2.
  PINSEL_ConfigPin(0, 5, 2); // TD2.

  CAN_Init(CAN_1, CAN_BAUD_RATE);
  CAN_Init(CAN_2, CAN_BAUD_RATE);

  //printf("CAN Handler initialised\n");
}

This is used to run the CAN:

void CANHandlerRun()
{  
  // Enter reset mode.
  LPC_CAN1->MOD |= 0x1;
  LPC_CAN2->MOD |= 0x1;

#if CAN_SOURCE_PORT == CAN_PORT_1
  SFF_GPR_Table[0].controller1 = SFF_GPR_Table[0].controller2 = CAN1_CTRL;
  SFF_GPR_Table[0].disable1 = SFF_GPR_Table[0].disable2 = MSG_ENABLE;
  SFF_GPR_Table[0].lowerID = 0x0;
  SFF_GPR_Table[0].upperID = 0x7FF;
#else
  SFF_GPR_Table[0].controller1 = SFF_GPR_Table[0].controller2 = CAN2_CTRL;
  SFF_GPR_Table[0].disable1 = SFF_GPR_Table[0].disable2 = MSG_ENABLE;
  SFF_GPR_Table[0].lowerID = 0x0;
  SFF_GPR_Table[0].upperID = 0x7FF;
#endif

  AFTable.FullCAN_Sec = NULL;
  AFTable.FC_NumEntry = 0;

  AFTable.SFF_Sec = NULL;
  AFTable.SFF_NumEntry = 0;

  AFTable.SFF_GPR_Sec = &SFF_GPR_Table[0];
  AFTable.SFF_GPR_NumEntry = 1;

  AFTable.EFF_Sec = NULL;
  AFTable.EFF_NumEntry = 0;

  AFTable.EFF_GPR_Sec = NULL;
  AFTable.EFF_GPR_NumEntry = 0;

  if(CAN_SetupAFLUT(&AFTable) != CAN_OK) printf("AFLUT error\n");

  LPC_CANAF->AFMR = 0;

  // Re-enter normal operational mode.
  LPC_CAN1->MOD &= ~0x1;
  LPC_CAN2->MOD &= ~0x1;

  // Enable interrupts on transmitting and receiving messages.
#if CAN_SOURCE_PORT == CAN_PORT_1
  LPC_CAN1->IER |= 0x1; /* RIE */
  LPC_CAN1->IER |= (1 << 8); /* IDIE */
#else
  LPC_CAN2->IER |= 0x1;
  LPC_CAN2->IER |= (1 << 8);
#endif

  NVIC_EnableIRQ(CAN_IRQn);
}

This is the CAN IRQ code:

void CAN_IRQHandler(void)
{
  ITM_EVENT32_WITH_PC(1, 0xAAAAAAAA);

#if CAN_SOURCE_PORT == CAN_PORT_1
  if(LPC_CAN1->SR & 0x1 /* RBS */)
  {
    CAN_ReceiveMsg(CAN_1, &recMessage);
    COMMS_NotifyCANMessageReceived();
    CAN_SetCommand(CAN_1, CAN_CMR_RRB);
  }
#else
  if(LPC_CAN2->SR & 0x1)
  {
    CAN_ReceiveMsg(CAN_2, &recMessage);
    COMMS_NotifyCANMessageReceived();
    CAN_SetCommand(CAN_2, CAN_CMR_RRB);
  }
#endif

  ITM_EVENT32_WITH_PC(1, 0xBBBBBBBB);
}

The COMMS_NotifyCANMessageReceived code:

void COMMS_NotifyCANMessageReceived()
{
   COMMS_BUFFER_T commsBuffer;

#if MAX_MSG_QUEUE_LENGTH > 0
  uint32_t length;

  LIST_AcquireLock(canMessageList);
  length = LIST_GetLength(canMessageList);
  LIST_ReleaseLock(canMessageList);

  if(length >= MAX_MSG_QUEUE_LENGTH)
  {
    ITM_EVENT32_WITH_PC(2, 0x43214321);
    return;
  }
#endif 

  commsBuffer.flags = COMMS_MODE_CAN;

  COMMS_GetData(&commsBuffer);

  LIST_AcquireLock(canMessageList);
  LIST_AddItem(canMessageList, &commsBuffer);
  LIST_ReleaseLock(canMessageList);
}

This is my timer interrupt code which is triggered every 50 microseconds:

void TIMER0_IRQHandler(void)
{
  uint32_t canMessageCount, usbMessageCount;

  if(TIM_GetIntStatus(LPC_TIM0, TIM_MR0_INT) == SET)
  {
    //ITM_EVENT32_WITH_PC(4, 0);

    LIST_AcquireLock(canMessageList);
    canMessageCount = LIST_GetLength(canMessageList);
    LIST_ReleaseLock(canMessageList);

    LIST_AcquireLock(usbMessageList);
    usbMessageCount = LIST_GetLength(usbMessageList);
    LIST_ReleaseLock(usbMessageList);

    if(canMessageList != NULL && canMessageCount > 0)
    {
      LIST_AcquireLock(canMessageList);

      if(!LIST_PopAtIndex(canMessageList, 0, &outCommsBuffer))
      {
        LIST_ReleaseLock(canMessageList);
        goto TIMER_IRQ_END;
      }

      LIST_ReleaseLock(canMessageList);

      ITM_EVENT32_WITH_PC(4, 0x88888888);
      interpretMessage(outCommsBuffer);
      ITM_EVENT32_WITH_PC(4, 0x99999999);
    }
    else if(usbMessageList != NULL && usbMessageCount > 0)
    {
      LIST_AcquireLock(usbMessageList);

      if(!LIST_PopAtIndex(usbMessageList, 0, &outCommsBuffer))
      {
        LIST_ReleaseLock(usbMessageList);
        goto TIMER_IRQ_END;
      }

      LIST_ReleaseLock(usbMessageList);

      ITM_EVENT32_WITH_PC(4, 0xCCCCCCCC);
      interpretMessage(outCommsBuffer);
      ITM_EVENT32_WITH_PC(4, 0xDDDDDDDD);
    }

    //ITM_EVENT32_WITH_PC(4, 1);
  }

TIMER_IRQ_END:
  TIM_ClearIntPending(LPC_TIM0, TIM_MR0_INT);
}

Below is an excerpt of a typical event log while running the application with an average inter-arrival time between messages of 200 us:

4s 718164.69 us     0x00005E82  0xAAAAAAAA              
4s 718175.27 us     0x00005EC4  0xBBBBBBBB              
4s 718197.10 us     0x000056C4              0x88888888  
4s 718216.50 us     0x00005700              0x99999999  
4s 718438.69 us     0x00005E82  0xAAAAAAAA              
4s 718449.40 us     0x00005EC4  0xBBBBBBBB              
4s 718456.42 us     0x000056C4              0x88888888  
4s 718476.56 us     0x00005700              0x99999999  
4s 718707.04 us     0x00005E82  0xAAAAAAAA              
4s 718717.54 us     0x00005EC4  0xBBBBBBBB              
4s 718747.15 us     0x000056C4              0x88888888  
4s 718768.00 us     0x000056C4              0x99999999  

Where:

You can see that, despite the fact that messages are arriving every 200 us, the CAN IRQ is only servicing them every ~270 us. This causes the receive queue to build up until eventually messages start getting dropped.

Any help would be appreciated.

EDIT 1

I should perhaps also mention that I have my CAN peripherals programmed to operate at a rate of 500 kBaud. In addition, my application involves echoing received CAN messages, so any messages arriving on port 1 are re-transmitted on port 2.

EDIT 2

The CAN and TIMER_0 interrupt routines have equal priority:

NVIC_SetPriority(USB_IRQn, 1);
NVIC_SetPriority(CAN_IRQn, 1);
NVIC_SetPriority(TIMER0_IRQn, 1);

EDIT 3

I can confirm that the problem remains and there is little/no change to the ~270 us interval between interrupts even when I disable timer interrupts and have the CAN IRQ do nothing but copy the received CAN message to RAM and release the receive buffer.

Upvotes: 2

Views: 364

Answers (1)

Tagc
Tagc

Reputation: 9072

I'm an idiot. At 500 kBaud, it will take 271 us for a CAN message with an 11-bit standard identifier and max stuff bits to be transmitted. There's no apparent problem in my code, the bottleneck is simply how fast CAN messages can be transmitted on the bus at that point.

Upvotes: 1

Related Questions