Reputation: 9072
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:
0xAAAAAAAA
indicates the start of the CAN IRQ.0xBBBBBBBB
indicates the end of the CAN IRQ.0x88888888
indicates that a CAN message is about to be processed by interpretMessage
within the timer IRQ.0x99999999
indicates that we've returned from interpretMessage
.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
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