本帖最后由 okwh 于 2017-7-3 20:29 编辑
【LPC54114双核任务二】――B、LPC54114双核调试方法
一、 LPC54114双核基本知识: 两个核的用途:一切其实都是在时间和空间上排列操作序列和数据分配,两个核有助于完成一些复杂任务,双核编程就像安排两个人协作完成任务一样,但要简单些,其关键就是依据任务的紧迫程度、响应快慢需求、各自的擅长等进行规划。比如如果你有两个任务要同时完成、或者依序完成时不满意、或者事情教复杂分给两个核以方便管理。最基本的特点就是双核方便更灵活安排事情的时序。 多核工作原理上类似软件中的进程间通讯。其核心思想是1)建立一共享内存区(或其他共享机制)以便两个或多个进程都有权存取; 2) 有某种标志用于共享区的可用状态以保持共享区不能被同时写、可同时读但不能正在读时被写。 通常这样的共享区和标志由操作系统的SDK提供,有操作系统协助(在标志变化时通知、中断各方)完成。 LPC54114的双核与之类似,可看做两个线程间如何通讯。显然两个核在一个芯片上,当然最方便的方法就是共享内存+标志。 * 共享内存可直接指定(如果事先知道要传递多少),也可动态分配、运行时传递(核间共享记录内存地址的变量),通常由编程自行实现。 * 标志是用于协调哪个核有权去使用、正在使用共享内存。 * 协调权限方法:其实就是谁给谁发个信号(通常就是送标志),最常用的方法就是查询、中断,当然管脚状态也是用查询、中断的方法,这是最基本的方法,其他其实都是在此基础上发展丰富、分层分级、特化专门化出来的。或者说事实上就是同一内存(地址或地址区)如何分时使用的问题。
LPC54114双核都可存取所有资源,并为双核间通讯准备了7个寄存器以方便邮箱式通讯(在地址0x 4008 B000)): * 两个可读写中断寄存器: IRQ0(由M0响应)和IRQ1(由M4响应),为32位4字节。 * 两对清除/设置只写操作:IR0CLR/IRQ0SET和 IR1CLR/IRQ1SET,分别用于IRQ0和IRQ2的清零复位和置1. (当然直接对IRQ0、IRQ1赋值0、1也是同样效果,但通常这两种之一。) * 一个可读写互斥标志:MUTEX,谁读到1谁有权去使用、正在使用共享内存 (若读到0就不而能使用或去等待), 使用完后,写任何值就失去权限。注意 两个核必须是每核一次的交替使用模式。
注: 也可以使用查询不用中断,也可以使用自定义的标志变量替代互斥标志寄存器的功能。 换句话说,双核能存取有关资源(内存、外设和管脚)是最基本支持,专用中断提供了可选的及时通讯能力,互斥标志提供了可选的协调授权的方便。 两个核对芯片所有资源都可使用,但注意M0+只通过其Sys总线使用资源,M4则有多种总线途径,这些总线的优先级是可设置的。(AHBMATPRIO, 在地址0x 4000 0004) 注: IRQ0SET 和 对 IRQ0直接用1赋值效果相同,但IRQ0必须写入非0值才行。IRQ1同样。所以这IRQ有两种通常用法,1) 直接传递最多32位的数据,; 2) 把这32位做为数据地址传送,或把32位做数据的16位地址和16位长度传送…, 接收方再去相应地址处理数据。 (NXP的设计师很喜欢除直接寄存器读写之外,还同时提供CLR/SET这样的基于地址用于功能的操作寄存器,却是提供了多样化的方法,不知道是否浪费资源)
二、 厂家驱动和例程 NXP在驱动mailbox_5411x.h中提供了邮箱数据结构和相关函数供使用。 在Keil的pack和NXP自己原来的LPCXpresso下提供有厂家驱动和LPCOPEN3的简单例库LPC54102和LPC54114。 随MCUXpresso的新体系中关于双核包括了lightweightimplementation of the RPMsg protocol、Embeded Remote ProcedureCall(eRPC)、Multicore Manager (MCMGR)、Low_Power低功耗管理的库、中间件和例程,支持IAR embeddedWorkbench 7.80.4、Keil MDK 5.21a、MCUXpresso。双核支持得到了很好的加强。邮箱支持在fsl_mailbox.h。新SDK还在发展中,有关资料参考http://mcuxpresso.nxp.com/api_doc/dev/174/index.html
三、 LPC54114双核编程调试 通常开发系统原来并不支持多核调试,目前的多核调试还不是很完善。通常使用三种开发系统:Keil、IAR、MCUXpresso(原LPCXpresso),仿真器使用LPC-Link2, Jlink, ULlNK2, ULlNKPro,支持CMSISDAP等,LPC54系列目前只使用SWD接口进行双核调试,而SWD有时不太稳定(特别是仿真器质量不好或电气环境不佳时)。不知道能不能同时使用两个仿真器调试,目前是通过一个仿真器,运行两个keil进行调试,MCUXpresso是一个开发环境中打开两个调试窗口会话(session或者说场景secene)。
这里介绍下keil下的调试方法: 1) Kei lV5以上软件,需要为每个核建立工程(可以在一个workplace),调试时要运行两个keill分别对应把工程选作当前工程运行调试各核。 2) 对于双核,M4的工程是芯片的主项目,或者说缺省首先开始运行,其调试和单核独立项目相同,如果你只需要单核,只开发M4就可以(SDK中的单核例程都是m4的)。需设置调试对话框(仿真调试器对话框)中的参数AP为0x00,并选中 “Reset afterConnect" 选项(它的意思大概是点击调试按钮后自动到达第一句代码处或者到达main位置), 需要同时项目设置对话框的debug页use仿真调试器选项下的”loadapplication at Startup”和”Run to main()”, 不选”Run to main()”可调试在main前执行的s文件中的启动引导初始代码。这和单核是相同的,只是单核只有一个AP无需设置,双核才需要设置主核项目的调试用AP为0x00(AP=Access Point存取点,就想象成虚拟的插座吧)。做为主核,它的IROM1起始地址0x00。 3) M0+工程是次项目,需设置调试对话框中的参数AP为0x01,不要选中 “Reset afterConnect" 选项。,不要选”loadapplication at Startup”和”Run to main()”(干脆”Run to main()”变灰就不让选了,我还不知道如何变灰的,可能在项目工程文件中或是devicepack中就已经指明了吧)。
4) 做为次核,它的代码放哪里?从哪里开始运行呢?当然肯定放在在flash中,但似乎NXP目前没有提供子和的flash算法,所以从项目无法直接下载到flash(可能需要额外工具通过Utilitiues设置使用,或者使用nxp独立的烧写工具)。NXP的例程提供是通过编译后设置在处理,在user页after Build设置Run #1, 用fromelf.exe外部程序把编译后的文件转为bin格式($K\ARM\ARMCC\bin\fromelf.exe--bincombined --bincombined_base=0x20010000 --output=$Lcore1_image.bin !L 这转换参数表明 这个bin文件的内容将设计为在0x20010000地址运行),这个bin文件被被M4工程当做数据文件,和m4自己的一起写入flash。 5) 做为次核,它的代码从哪里开始运行呢?虽然理论上代码可放在任何地址并从那里执行,当然实际产品是要进行地址分配的。通常代码可在两个地方运行:flash或者RAM或者二者配合(特殊情况下在一定支持下可从一些端口接口运行如特殊的启动过程)。目前NXP的例程提供M0代码可从flash运行也可以从RAM执行。若m0工作很简单,代码很少,可以复制到RAM从RAM执行,这种情况下,前面的bin文件下入flash后, M4的main函数中把对应的代码从flash复制RAM地址0x20010000,然后用Chip_CPU_CM0Boot(jumpAddr,stackAddr); 或新的MCMGR_StartCore(kMCMGR_Core1,CORE1_BOOT_ADDRESS, 1, kMCMGR_Start_Synchronous);让M0核开始工作。分析s文件,可看到,对于次核,判定CPU_id后立即进入休眠的,s文件还有 cpu_id EQU 0xE000ED00 /// cpu_ctrl EQU 0x40000300 /// coproc_boot EQU 0x40000304 /// coproc_stack EQU 0x40000308 这样的信息。有关多核管理的库函数是固化在芯片里的,有兴趣者可在调试状态分析一下其汇编代码。下图说明了双核的启动过程:
6) 那么怎么调试呢: 1) 只调试M4,此时M4和通常单核一样,如果不需要调试m0,只运行M4工程即可。 2) 双核都调试,先运行m4工程进入调试main并且在Chip_CPU_CM0Boot或新的MCMGR_StartCore让M0核开始工作后!!!再在新打开的keil执行m0工程(那些项目设置保证无需下载,直接试图通过同一个仿真器连接m0核,),运行等待连接上,就可以设置断点可以调试了。 3) 只调试M0, 用M4工程下载代码到flash后, 重启电路板处于运行状态,然后在新打开的keil执行m0工程,等待连接上,就可以设置断点可以调试了。 7) 总之,把双核想象成两个人配合工作,只是有主次之分,m4要负责触发m0开始,开始后就可以连山软件进行调试了。目前,NXP的m0设计似乎是不允许它独立不允许它优先,m0调试的核心是Connect withdebugger to m0+ without reset of chip and withoutcode-download!!
所以关键就是 m0调试就是 调试器是在 单片机已经在运行时 挂接的 !!! 这几乎就是和 PC机上调试动态链接库DLL的方式 一样!
进一步学习: 有兴趣的可进一步分析s文件,特别是分析linker设置中对应M4和m0的Scatter文件.scf。我不是专业人员,分析到这里已经足够使用了。
我猜测,似乎NXP有倾向把嵌入系统发展为我们熟悉的PC那样,程序exe存在什么地方或盘上,需要时装入内存即可,这样的运行方式已经并正在向嵌入系统渗透,不仅是安卓系统、嵌入Linux那样的已经是这样实现,甚至这只有几十数百k字节内存的单片机上也开始有这种迹象。从最初很少次可擦写的各种ROM到今天数十万次可擦写的flash,如果这支持eXecute In Place(XIP)的flash再提速扩容缩体强大些,也许单片机级别的功能动态重组重载就不远了,实现了,小机器也都快成半个生命了。 四、调试实践: 基于例子lpc54114\SDK_2.2.1_LPCXpresso54114\boards\lpcxpresso54114\multicore_examples\hello_world 步骤: 1) 编译m0的hello_world_cm0plus.uvmpw的hello_world_cm0plus.uvprojx生成core1_image.bin 2) 编译m4的hello_world_cm4.uvmpw的hello_world_cm4.uvprojx;里面通过incbin.s包含了core1_image.bin。
incbin.s内容:典型的只读数据汇编表达 - AREA M0CODE, DATA, READONLY, PREINIT_ARRAY, ALIGN=3
- EXPORT m0_image_start
- EXPORT m0_image_end
复制代码M4项目main中如何启动M0的代码: - #ifdef CORE1_IMAGE_COPY_TO_RAM
- /* Calculate size of the image - not required on LPCExpresso. LPCExpresso copies image to RAM during startup
- * automatically */
- uint32_t core1_image_size;
- core1_image_size = get_core1_image_size();
- PRINTF("Copy Secondary core image to address: 0x%x, size: %d\n", CORE1_BOOT_ADDRESS, core1_image_size);
- /* Copy Secondary core application from FLASH to RAM. Primary core code is executed from FLASH, Secondary from RAM
- * for maximal effectivity.*/
- memcpy(CORE1_BOOT_ADDRESS, (void *)CORE1_IMAGE_START, core1_image_size);
- #endif
- /* Initialize MCMGR before calling its API */
- MCMGR_Init();
- /* Boot Secondary core application */
- PRINTF("Starting Secondary core.\n");
- MCMGR_StartCore(kMCMGR_Core1, CORE1_BOOT_ADDRESS, 1, kMCMGR_Start_Synchronous);
复制代码在这之后就m0的调试就可以挂接上了。
m4代码修改增加: - GPIO_PinInit(BOARD_SW1_GPIO, 1, 8, &sw_config);//万利按键PB2
- GPIO_PinInit(BOARD_SW2_GPIO, 1, 9, &sw_config); //万利按键PB3
- ………………………..
- while (1)
- {
- /* Stop secondary core execution. */
- // if (!GPIO_ReadPinInput(BOARD_SW1_GPIO, BOARD_SW1_GPIO_PORT, BOARD_SW1_GPIO_PIN))
- if (!GPIO_ReadPinInput(BOARD_SW1_GPIO, 1, 8))
- {
- MCMGR_StopCore(kMCMGR_Core1);
- PRINTF("Stopped Secondary core.\n");
- delay();
- }
- /* Start core from reset vector */
- // if (!GPIO_ReadPinInput(BOARD_SW2_GPIO, BOARD_SW2_GPIO_PORT, BOARD_SW2_GPIO_PIN))
- if (!GPIO_ReadPinInput(BOARD_SW2_GPIO, 1, 9))
- {
- MCMGR_StartCore(kMCMGR_Core1, CORE1_BOOT_ADDRESS, 5, kMCMGR_Start_Synchronous);
- PRINTF("Started Secondary core.\n");
- delay();
- }
- }
复制代码
m0代码修改增加: - // LED_INIT();
- GPIO_PinInit(GPIO, 0, 15, &led_config); //D11
- GPIO_PinInit(GPIO, 0, 19, &led_config); //D10
- GPIO_PinInit(GPIO, 0, 21, &led_config); //D9
- GPIO_PinInit(GPIO, 0, 22, &led_config); //D8
-
- GPIO_PinInit(GPIO, 0, 25, &led_config); //D7
- GPIO_PinInit(GPIO, 0, 26, &led_config); //D6
- GPIO_PinInit(GPIO, 0, 29, &led_config); //D5
- GPIO_PinInit(GPIO, 0, 30, &led_config); // D4
-
- gpio_pin_config_t sw_config = {kGPIO_DigitalInput, 0};
- GPIO_PinInit(BOARD_SW1_GPIO, 1, 10, &sw_config); //万利按键PB4
- GPIO_PinInit(BOARD_SW2_GPIO, 1, 11, &sw_config); //万利按键PB5
- ……………
- while (1)
- {
- delay();
- GPIO_TogglePinsOutput(GPIO, 0, 1u << 15); //D11
- GPIO_TogglePinsOutput(GPIO, 0, 1u << 19); //D10
- GPIO_TogglePinsOutput(GPIO, 0, 1u << 25); //D7
- GPIO_TogglePinsOutput(GPIO, 0, 1u << 26); //D6
- // LED_TOGGLE();
复制代码- // LED_TOGGLE();
- if (!GPIO_ReadPinInput(BOARD_SW1_GPIO, 1, 10))
- {
- GPIO_TogglePinsOutput(GPIO, 0, 1u << 21);
- GPIO_TogglePinsOutput(GPIO, 0, 1u << 22);
- }
- if (!GPIO_ReadPinInput(BOARD_SW1_GPIO, 1, 11))
- {
- GPIO_TogglePinsOutput(GPIO, 0, 1u << 29);
- GPIO_TogglePinsOutput(GPIO, 0, 1u << 30);
- }
- }
复制代码
试验功能:
按键2/3由M4接收实现起停M0,对应M0翻转闪动绿D6/D7和红D10/D11 按键4/5由M0接收实现亮灭M0, 对应按键4翻转亮灭红D8/D9, 按键5翻转亮灭红D4/D5.
源代码文件:
hello_world.rar
(280.59 KB, 下载次数: 52)
|