Dual Core Debugging Hints on LPC43XX
I got apps running on both the M0 and M4 core running and communicating with each other and while figuring out how to use the debugger learned something that might be of use to others.
Note: I am using a Segger J-link connected to an LPC4357 board.
Debugging in the M4 core is pretty straightforward. Just connect and go.
However debugging in the M0 project is a bit more complicated.
First, you have to load the app with the M4 project set active. Run the debugger, then disconnect from the debugger.
Now set the M0 core project as active. DO NOT try to load the executable from the M0 project. It will fail (at least the way I have my project configured).
Now attach to the debugger (do not connect to the debugger). This will fail the first time. Do it again and it should attach and the debugger will be running. Repeat until it attaches.
To run from startup, break execution, click reset then continue. You can now set breakpoints, examine variables etc in the M0 app as needed.
If you modify the M0 code, you have to return to the M4 project to compile and load both, then repeat the above process to continue debugging the M0 code.
It is a little clumsy, but it works.
-
That will take some time. I would have to create a pair of dummy hello world projects since my code is proprietary and project creation is somewhat complicated. Perhaps after I get past the next deliverable I am will look at doing this.
Hints:
- Create a solution with two projects inside it. I happen to be using Rowley Crossworks ARM.
- The M4 project is the IPC Host and is a normal project that runs from Page A. The M4 project must be the active project when you compile and load code into the processors flash memory
- The M0 project is the IPC Client as is treated as an additional load item into Page B when the M4 project is loaded.
- Inter processor communication is via shared memory buffers. A 2K Byte buffer for each core to write into and the other core only reads from the other cores buffer.
Code snippets below:
Note: The app is running on top of the uEZ-GUI SDK from Future Design Inc, so there is some specific stuff for their environment. I was also having issues with giving semaphores before they had been taken (resulting in hard faults). That is why I check to see if the previous semaphore has been consumed before giving it again.
The IPC.c file is part of both the M4 and the M0 project to manage communication.
/*-------------------------------------------------------------------------*
* File: IPC.c Copyright (C) 2018 Interface Inc
*-------------------------------------------------------------------------*
* Description:
*
*-------------------------------------------------------------------------*//*--------------------------------------------------------------------------
* uEZ(R) - Copyright (C) 2007-2018 Future Designs, Inc.
*--------------------------------------------------------------------------
* This file is part of the uEZ(R) distribution. See the included
* uEZLicense.txt or visit http://www.teamfdi.com/uez for details.
*
* *===============================================================*
* | Future Designs, Inc. can port uEZ(R) to your own hardware! |
* | We can get you up and running fast! |
* | See http://www.teamfdi.com/uez for more details. |
* *===============================================================*
*
*-------------------------------------------------------------------------*//*-------------------------------------------------------------------------*
* Includes:
*-------------------------------------------------------------------------*/
#if CORE_M0
#else
#include <uEZ.h>
#endif
#include <string.h>
#include <UEZProcessor.h>
#include <HAL/HAL.h>
#include <HAL/Interrupt.h>
#include "IPC.h"
#include <uEZRTOS.h>
#if (UEZ_PROCESSOR == NXP_LPC4357)
#include <Source/Processor/NXP/LPC43xx/LPC43xx_GPIO.h>
#else
#error "MCU not set!"
#endif
#include <HAL/GPIO.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
/*-------------------------------------------------------------------------*
* Constants:
*-------------------------------------------------------------------------*/#define IPC_BUFFER_SIZE 512
#define M4_MESSAGE_RECEIVE_ADDR 0x10004000
#define M4_MESSAGE_SEND_ADDR 0x10004800#define M0_MESSAGE_RECEIVE_ADDR M4_MESSAGE_SEND_ADDR
#define M0_MESSAGE_SEND_ADDR M4_MESSAGE_RECEIVE_ADDR#define MESSAGE_CLEAR_VALUE 0xFF
#define IPC_RETURN_VALUE_NONE 0
#define IPC_RETURN_VALUE_BUSY 1
#define IPC_RETURN_VALUE_TOO_LONG 2
/*-------------------------------------------------------------------------*
* Types:
*-------------------------------------------------------------------------*/
typedef struct {
TUInt32 iSize;
TUInt32 iData[IPC_BUFFER_SIZE];
}T_IPC_Message;/*-------------------------------------------------------------------------*
* Globals:
*-------------------------------------------------------------------------*/
//Note each core will create its own version of each of these global variables
static T_IPC_Message *G_Send;
static T_IPC_Message *G_Receive;
T_uezSemaphore G_MessageSem = 0;
static TBool volatile G_MessageSem_taken = EFalse;//Data used by each cores app.
struct IPC_MESSAGE IPC_RX_Msg;
struct IPC_MESSAGE IPC_TX_Msg;//static TaskHandle_t xHandlerTask = NULL;
/*-------------------------------------------------------------------------*
* Prototypes:
*-------------------------------------------------------------------------*/
#if CORE_M0
void Client_IPC_RxD(void);
#else
void Host_IPC_RxD(void);
#endif
/*---------------------------------------------------------------------------*
* Routine: Interrupt routines for processor communication
*---------------------------------------------------------------------------*/
/** Handle interrupts from the other core
*
* @param [in] p_choice void
*/
/*---------------------------------------------------------------------------*/
#if CORE_M0
IRQ_ROUTINE(IM0_M4CORE_IRQn)
{
LPC_CREG->M4TXEVENT = 0;
if(G_Receive->iSize == 0xFFFFFFFF)
{
//Invalid message
return;
}
//store the buffer data
IPC_RX_Msg.iSize = G_Receive->iSize;
memcpy(IPC_RX_Msg.iData, G_Receive->iData, IPC_RX_Msg.iSize);
G_Receive->iSize = 0xFFFFFFFF;
//Let app know the data is here
G_MessageSem_taken = ETrue;
}
#else
IRQ_ROUTINE(IM0APP_IRQ)
{
LPC_CREG->M0APPTXEVENT = 0;
if(G_Receive->iSize == 0xFFFFFFFF)
{
//Invalid message
return;
}
//store the buffer data
IPC_RX_Msg.iSize = G_Receive->iSize;
memcpy(IPC_RX_Msg.iData, G_Receive->iData, IPC_RX_Msg.iSize);
G_Receive->iSize = 0xFFFFFFFF;
//Let app know the data is here
G_MessageSem_taken = ETrue;
}
#endif
static TUInt32 IPC_RX_MsgTask(T_uezTask aMyTask, void *aParams)
{
//uint32_t ulEventsToProcess;while(1)
{
// G_MessageSem_taken = ETrue;
// if(UEZSemaphoreGrab(G_MessageSem, UEZ_TIMEOUT_INFINITE) == UEZ_ERROR_NONE)
while (G_MessageSem_taken == EFalse)
{
UEZTaskDelay(1);
}
G_MessageSem_taken = EFalse;
{
//Do something with the data in IPC_RX_Msg
//Copy it to a ring buffer for the application to use
#if CORE_M0
//Process_M0_data();
Client_IPC_RxD();
#else
Host_IPC_RxD();
#endif
}
}
return 0;
}TUInt32 IPC_SendMessage(struct IPC_MESSAGE * ipc_msg)
{
TUInt32 return_value = IPC_RETURN_VALUE_NONE;if (G_Send->iSize != 0xFFFFFFFF)
{
//Previous message has not been read yet
return_value = IPC_RETURN_VALUE_BUSY;
}
else if (ipc_msg->iSize > (sizeof(TUInt32) * IPC_BUFFER_SIZE))
{
//Message sent is too long. This should never happen, but check anyways
return_value = IPC_RETURN_VALUE_TOO_LONG;
}
else
{
//Load shared variables with message
G_Send->iSize = ipc_msg->iSize;
memcpy(G_Send->iData, ipc_msg->iData, G_Send->iSize);
//Trigger the interrupt in the other core
__SEV();
}
return return_value;
}
/*---------------------------------------------------------------------------*
* Routine: IPC_Init
*---------------------------------------------------------------------------*/
/** Setup the IPC memory and interrupts
*
* @param [in] p_choice void
*/
/*---------------------------------------------------------------------------*/
void IPC_Init(void)
{
static TBool haveRun = EFalse;if(!haveRun){
haveRun = ETrue;#if CORE_M0
G_Send = (T_IPC_Message*)M0_MESSAGE_SEND_ADDR;
G_Receive = (T_IPC_Message*)M0_MESSAGE_RECEIVE_ADDR;
#else
G_Send = (T_IPC_Message*)M4_MESSAGE_SEND_ADDR;
G_Receive = (T_IPC_Message*)M4_MESSAGE_RECEIVE_ADDR;
#endif
IPC_RX_Msg.iSize = 0xFFFFFFFF;
IPC_TX_Msg.iSize = 0xFFFFFFFF;memset((void*)G_Send, MESSAGE_CLEAR_VALUE, sizeof(T_IPC_Message));
memset((void*)G_Receive, MESSAGE_CLEAR_VALUE, sizeof(T_IPC_Message));UEZSemaphoreCreateBinary(&G_MessageSem);
UEZSemaphoreGrab(G_MessageSem, 0);
UEZTaskCreate((T_uezTaskFunction)IPC_RX_MsgTask, "IPC_RXTask",UEZ_TASK_STACK_BYTES(1024), (void *)0, UEZ_PRIORITY_NORMAL, 0);
#if CORE_M0
InterruptRegister(M0_M4CORE_IRQn, IM0_M4CORE_IRQn, INTERRUPT_PRIORITY_HIGH, "M4 App");
NVIC_EnableIRQ (M0_M4CORE_IRQn);
#else
InterruptRegister(M0APP_IRQn, IM0APP_IRQ, INTERRUPT_PRIORITY_HIGH, "M0 App");
NVIC_EnableIRQ (M0APP_IRQn);
#endif
}
}Finally: This is how the M4 app starts the M0 code running. Add this to your M4 startup initialization code.
/* Set start address for CM0 application */
LPC_CREG->M0APPMEMMAP = 0x1B000000;
/* Start CM0 core */
LPC_RGU->RESET_CTRL1 = 0; // in principle just set bit 24 to 0, but doing it with the whole register doesn't hurtIPC_Init();
-
Philip,
So I just realized I already have access to your project anyway. (small world)
After you guys are done with this update, and after we release uEZ 2.10, then I can look at putting out an example hello world project for this later using our project maker if we have time. I'm still in the middle of making normal fixes and updates to some of the projects for the current release.
Bill
Please sign in to leave a comment.
Comments
6 comments