Я разрабатываю приложение для микроконтроллера NXP LPC1788 с использованием IAR Embedded Workbench IDE, и одним из требований является получение CAN-сообщений от порта CAN1 как можно быстрее. Моя проблема заключается в том, что, по-видимому, существует задержка ~ 270 микросекунд между началом одного вызова CAN IRQ и началом следующего, хотя кажется, что для управления прерыванием требуется только микроконтроллер ~ 30 микросекунд.Как я могу улучшить скорость, с которой CAN IRQ вызывается для микроконтроллера LPC1788?
В дополнение к этому прерыванию у меня также есть регулярные прерывания таймера, запланированные каждые 50 микросекунд, которые обрабатывают принятые сообщения CAN и USB. Тем не менее, эта процедура прерывания занимает ~ 3 микросекунды, если нет сообщений CAN или USB для обработки и только около ~ 80 микросекунд, если они есть.
Из-за этого мне интересно, почему CAN IRQ не способен запускать быстрее, чем каждые 270 микросекунд, и что я могу сделать, чтобы исправить это.
USB-прерывания оказывают незначительное влияние на производительность, поскольку сообщения USB поступают гораздо реже, чем сообщения CAN, и я решил, что эта проблема возникает даже тогда, когда это прерывание никогда не запускается.
В настоящее время мое приложение, похоже, может обрабатывать сообщения CAN со средним временем между приходами ~ 300 us (проверено на 3 сообщения, сгенерированные с интервалом 1 мс, с обработкой ~ 500 000 сообщений и скоростью снижения 0%) ,
Этот код инициализирует 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");
}
Это используется для запуска 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);
}
Это код CAN IRQ:
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);
}
COMMS_NotifyCANMessageReceived
код:
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);
}
Это мой таймер код прерывания, который запускается каждые 50 микросекунд:
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);
}
Ниже приводится выдержка из типичного журнала событий во время работы приложения со средним временем между поступлениями между сообщениями 200 нами:
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
Где:
0xAAAAAAAA
указывает начальный O f CAN IRQ.0xBBBBBBBB
указывает конец CAN IRQ.0x88888888
указывает, что сообщение CAN должно обрабатыватьсяinterpretMessage
в таймере IRQ.0x99999999
указывает, что мы вернулись сinterpretMessage
.
Вы можете видеть, что, несмотря на то, что сообщения поступают каждые 200 лет, CAN IRQ обслуживает только каждые 270 пользователей. Это приводит к тому, что очередь приема будет нарастать до тех пор, пока сообщения не начнут падать.
Любая помощь будет оценена по достоинству.
EDIT 1
я, возможно, следует также отметить, что у меня есть мои CAN периферийные устройства запрограммированы на работу со скоростью 500 кбод. Кроме того, мое приложение включает в себя вторя CAN получили сообщения, поэтому любые сообщения, поступающие на порт 1, повторно передаются на порт 2.
EDIT 2
CAN-и TIMER_0 прерывания процедуры имеют одинаковый приоритет:
NVIC_SetPriority(USB_IRQn, 1);
NVIC_SetPriority(CAN_IRQn, 1);
NVIC_SetPriority(TIMER0_IRQn, 1);
EDIT 3
Я могу подтвердить, что проблема остается, и не мало/без изменений ~ 270 нас interva l между прерываниями, даже когда я отключил прерывания таймера и отключил CAN IRQ, но не скопировал полученное сообщение CAN в ОЗУ и освободил буфер приема.
Частота прерываний определяется только скоростью передачи в шине CAN. Единственное, что вы можете сделать неправильно, - это не быть в курсе, что явно не проблема. –
@HansPassant Спасибо, есть способ рассчитать ожидаемую скорость прерывания, основанную на скорости передачи? Мой бод - 500 000, и для этого я установил часы для периферии CAN на 12 МГц. Поэтому каждый тик составляет около 83 наносекунд, поэтому между каждым прерыванием должно быть ~ 3000 тиков (судя по задержке 270 микросекунд)? – Tagc
@ HansPassant Извините, я просто кровавый идиот. При 500 kBaud потребуется 271 us для CAN-сообщения со стандартными (11-разрядными) идентификационными и максимальными битами для передачи. Я понимаю, что вы имеете в виду сейчас. – Tagc