LPC43xx双核笔记

简介
本页提供了一些使用LPC43xx器件双核特性的基本信息。此页面上的信息和专题使用Keil uVision4工具,以双核工程的使用来演示。该工程初始化两个内核以运行FreeRTOS,并采用三色LED指示M0和M4内核的运行,以及双核处理器之间的通信。该项目把重点放在双核工程的细节上,力求基础和简单。
可执行映像的布局和内存使用
共享外设和内存资源
驱动程序初始化代码
通过共享内存和中断事件进行处理器间通信
双核调试
潜在问题和注意事项

本工程做什么
本工程为M4内核创建了一个可引导映像,该映像加载、设置和启动M0内核(使用嵌入M4二进制映像中或在内存中某地址的映像)。两个内核都运行FreeRTOS,其中M4内核使用Cortex M4系统定时器作为RTOS定时器,而M0内核使用定时器0。M4内核初始化所需要的大部分芯片外设——包括M0内核的定时器0。这两个内核都为FreeRTOS使用同一个1KHz的节拍率。
本工程可以使用以下两种方法中的一种进行配置:
作为一个没有M0内核队列和反馈的事件驱动(整型模式)
作为一个有M4和M0双核之间的队列和反馈的事件驱动
当无队列运行时,M4内核提供了在不同时间到M0内核的中断。在每个中断,M0内核将切换三色LED的绿色组件。 当M4内核停止时,绿色LED将立即停止闪烁。
当有队列运行时,M4内核在时间队列中提供三色LED的绿色组件。队列在M4内核一端被填满(尽可能快),而M0内核根据每个队列项的进入时间消耗队列项。当M0内核消耗了一项,它将给M4内核发送该项已被释放的信号,然后M4内核将重新填入该项。如果M4内核停止了,绿色LED将继续以队列定义的速率闪烁,直到队列为空。
为帮助确定内核正在运行,M4内核以约0.5Hz的频率闪烁红色LED,M0内核以约1Hz的频率闪烁蓝色LED。
当正常运行时,三色LED的红、蓝、绿组件以不同的速率同时切换。停止或启动一个内核(通过JTAG)将使红色或蓝色LED停止切换。重新启动任一内核,将使队列管理进程继续。当使用队列时,队列在两个内核之间、IRAM的0x10080000位置共享。当系统运行时,你可以在共享内存中看到队列正由两个内核一起更新。
该工程的配置选项位于shmem.h文件中。

代码下载地址:
注意:在Hitex 4350板的三色LED上粘贴一些磁带或文件,以降低其亮度。它非常亮!不要长时间盯着LED!
代码可在项目贡献页下载:http://www.lpcware.com/content/contribproj/lpc43xx-dual-core-freertosx2-...
关于如何编译,加载和运行该工程的具体步骤参见:http://www.lpcware.com/content/project/lpc43xx-dual-core-notes/lpc43xx-d ...
该工程的配置选项位于shmem.h文件中。

代码概述
该工程包含两个KEIL uVision4工程——一个M0内核工程,一个M4内核工程。在本工程中,M4内核将M0的映像嵌入到它的可执行文件中,虽然这并不是必须的方式。
M4代码序列
M4初始化
M4内核代码以如下方式初始化系统:
系统校时和配置(CGU)
引脚多路复用(三色LED)
调试框架
设置定时器0为1kHz速率。可以将定时器0设置为M0内核初始化序列的一部分,但添加定时器驱动程序会使M0映像变大。定时器驱动程序还依赖于CGU驱动程序(可能还有其他驱动),所以加入这样一个简单的驱动程序可能会显著增加映像的大小,因为这样一来两个内核都有了驱动程序的副本。另一个要考虑的是驱动程序如何初始化硬件。因为定时器驱动程序需要CGU驱动,而CGU驱动的一些数据在运行时进行初始化(即,通过cgu_init()方法),当CGU驱动在M4内核中被初始化后再在M0内核被初始化,这可能会改变系统的CGU设置——导致M4内核中的CGU数据状态可能不准确。
在系统定时器为1KHz时启动FreeRTOS,初始任务将启动
M4初始任务
M4内核中FreeRTOS的初始任务执行以下操作:
清除共享内存队列
创建一个同步信号用于M0内核的中断事件
准备并启动M0内核
将M0内核置于复位位置
启动M0内核的定时器(以M4内核的速度)
设置M0APPMEMMAP寄存器指向M0基础代码
释放M0内核的复位键
设置并启用M0到M4内核的事件中断
创建并启动进程间的通信任务
以约0.5Hz的频率持续切换三色LED的红色部分
M4进程间通信任务
M4内核中FreeRTOS的进程间通信任务执行以下操作:
如果不以队列模式建立,重复下列动作
在M0内核中产生一个中断事件
等待.333秒
如果以队列模式建立,重复下列动作
持续将事件推入共享内存队列直到队列满
等待一个从M0内核发来的事件,该事件发出至少有1个队列项空闲的信号
M4内核使用二进制信号从M0到M4的中断事件中启动进程间通信任务。在每个事件中,队列项根据需要重新声明和填充。请注意,一个计数信号可以用来计数事件,但如果M0向M4内核快速发送了若干事件,但M4内核太忙以致不能处理两个事件,就会将其作为一个事件。随着时间的推移,这可能会导致计数信号丢失数值。例如,如果M4内核正在处理定时器中断,M0到M4的内核中断正在等待,但被一个更高优先级的中断抢先,在M4内核能够处理它之前M0内核又发送了一个事件,这两个从M0内核发送的事件对M4内核来说将成为1个事件。
M0代码序列
M0初始化
M0内核不做任何初始化,因为它们都由M4内核来执行!它直接启动FreeRTOS并开始初始任务。
M0初始任务
M0内核中FreeRTOS的初始任务执行以下操作:
为FreeRTOS的定时器启用定时器0的中断(已从M4内核启动)
从M4内核的中断事件创建一个用于中断事件的信号同步
创建并启动进程间的通信任务
以约1Hz的频率持续切换三色LED的蓝色部分
M0进程间通信任务
M0内核中FreeRTOS的进程间通信任务执行以下操作:
如果不以队列模式建立,重复下列动作
等待从M4内核发来的事件
切换绿色LED的状态
等待.333秒
如果以队列模式建立,重复下列动作
等待从M4内核发来的事件
从队列中取出事件
根据事件设置绿色LED的状态
根据事件延迟任务
发送信号给M4内核,表示事件已完成


双内核调试的JTAG设置
Hitex LPC4350板上的Keil ULINK2 JTAG调试器使用Keil uVision4 IDE。板使用10针JTAG连接器。当IDE通过ULINK2扫描JTAG总线时,它将检测到2个内核。对于每一个内核的工程,JTAG需要为它将使用的内核进行设置。每个内核可以用它的uVision4进行独立调试。
对于M0工程,JTAG的配置如以下两图所示。M0的调试将一直试图在不改变系统或内核内容的情况下附加到正在运行的映像中。

对于M4工程,JTAG的配置如以下两图所示。

JTAG映像设置脚本
当在M4内核中加载映像,以同时运行M0和M4内核时,M0内核应在下载之前保持重置。可以在下载前通过JTAG初始化脚本做到这一点。M0和M4内核中断也应在CREG M0TXEVENT和M4TXEVENT寄存器中被禁用,以防止程序开始时的伪中断。
可执行映像的布局和内存使用——不同的IRAM
M0内核的启动地址有所限制。当系统在复位后第一次启动时,M0内核处于闲置状态。M4内核首先启动,从而设置了M0APPMEMMAP寄存器——它将指向M0启动代码的基础地址——然后启动M0内核。由于M0APPMEMMAP寄存器中的11...0位必须为0,M0内核只能从以64K边界代码启动。
下表显示了用于基于IRAM的工程的布局。基于IRAM的工程可以通过JTAG或DFU引导加载和启动。
区域 内核 地址范围 说明 M4代码/数据区域 M4 0x10000000 - 0x1000FFFF 包括多至64K的代码和数据 M0代码/数据区域 M0 0x10010000 - 0x10017FFF 包括多至32K的代码和数据 额外IRAM/空闲 0x10018000 - 0x1001FFFF 共享内存区域 M0/M4 0x10080000 - 0x10083FFF M0和M4内核之间的共享内存区域,用于队列 M0内核的良好功能之一是,它可以和M4内核有(大部分)相同的内存映射。所以,你可以将M0的映像链接在系统的FLASH地址的SPI FLASH或NOR FLASH以外运行。当您使用M0APPMEMMAP寄存器将地址0x00000000映射至地址i的FLASH时,M0内核只需要使用地址0x00000000作为原始栈值和PC计数器的负载,而PC计数器会指向FLASH中的一个地址(即0x1Cxxxxxx)。
M0工程——IRAM版本
在M0的IRAM版本的工程中,代码被编译为运行在地址0x10010000中,其最大空间为32K(代码,数据,堆栈和堆)。如果M0运行时映像的内存总使用额超过32K,链接步骤将失败。当从IRAM运行时,它将映像置于以64K为边界的128K的IRAM内存范围内。M0映像前的64K和M0映像后的32K可供M4内核使用。以下是M0的代码和数据的链接脚本设置——其中包括堆栈和堆——所有的代码,数据,堆分配和栈将位于此范围内。
ER_RO 0x10010000 NOCOMPRESS 0x8000 {
startup_LPC43xx_M0.o (RESET, +FIRST)
*.o (+RO)
*.o (+RW, +ZI)
} 注意NOCOMPRESS属性的使用。如果M0映像嵌入到M4映像中,并且没有一个静态的大小,数据可能在链接的映像中被压缩,然后在运行时解压缩。如果M4的代码或数据直接位于嵌入的M0映像之后,M4的代码可能会在解压时被重写。NOCOMPRESS属性可以存储解压缩的预初始化数据,以防止此类现象发生。
M4工程——IRAM版本
在M4的IRAM版本中,代码被编译为运行在地址0x10010000中,其最大空间为64K。M0映像位于地址0x10010000(匹配M0映像的链接地址),最大空间为32K和64K队列。通过预先将映像置于链接的M0启动地址,M4内核在运行时就不需要将映像复制到其正确的位置,M0APPMEMMAP可以直接指向位于内存中的映像。
M4链接脚本如下。请注意,M4映像被填充为0x0——这将保持M0映像的数据处于正确的位置,从而有助于生成一个二进制映像—— 这在创建hex文件时并不是问题……除非增加文件的大小。另外注意到M0映像被填充,虽然在此设置中并不必要,但如果将数据或代码直接置于M0映像之后,这将保证数据不在M0内存区中。
ER_RO 0x10000000 PADVALUE 0x0 0x10000 {
startup_lpc43xx.o (RESET, +FIRST)
*.o (+RO)
*.o (+RW, +ZI)
} ER_RO2 0x10010000 FIXED PADVALUE 0x0 0x8000 {
incm0.o (+RO)
} M0/M4共享内存区域
位于地址0x10080000的IRAM用于共享内存队列。虽然M0和M4都在这一区域的队列进行读写,但对队列中的相同部分都不同时具有读和写的权限。由M4内核修改的数据值只对M0内核可读,而由M0内核修改的数据值只对M4内核可读。使用这种方法,共享数据区域就不需要被作为一个关键区域来管理。
可执行映像的布局和内存使用——不同的FLASH
这些工程的不同FLASH与IRAM版本相似,但有以下区别:
两个内核的映像代码和只读数据,在FLASH中存储和执行
预初始化的数据区域在使用前必须复制到IRAM
下表显示了基于Flash的工程的布局。数据段将在运行时被复制和初始化,但两个内核将在FLASH之外执行代码。
区域 内核 地址范围 说明 M4代码区域 M4 0x1C000000 - 0x1C00FFFF 包括多至64K的代码和数据 M0代码区域 M0 0x1C010000 - 0x10007FFF 包括多至32K的代码和数据 M4数据区域 M4 0x10000000 - 0x1000FFFF 预初始化的RW数据,ZI数据,堆,栈,最大64K M0数据区域 M0 0x10001000 - 0x10017FFF 预初始化的RW数据,ZI数据,堆,栈,最大32K 额外IRAM/空闲 0x10018000 - 0x1001FFFF 共享内存区域 M0/M4 0x10080000 - 0x10083FFF M0和M4内核之间的共享内存区域,用于队列 


共享外设和内存资源
M0和M4内核共享相同的内存映射,并且可以访问相同的系统资源。关于内存访问和事件共享,以下指南可能会有所帮助。
避免使用处理器间中断事件来产生计数事件。如果接收事件的处理器正忙(即正在处理更高优先级的中断),并且不能马上处理内核的中断,它会挂起该事件,直到它可以处理它。那时,其他内核可能会产生另一个事件,造成原始事件丢失。
当为每个内核建立映像到一个特定的内存布局时,确保链接脚本监控映像的大小——代码和数据——以防止代码/数据在运行时重叠。只是因为映像链接在某地址并不意味着它将在同一区域运行!链接脚本与NOCOMPRESS、PADVALUE/FILL及区域大小属性同时使用,这将有助于加强布局,并将任何可能的内存冲突在其链接时通知您。

为一个内核考虑使用最少的驱动程序。一个看似简单的驱动程序可能依赖于其他驱动,并可以显著增加映像的大小。
当在两个内核中使用驱动程序时记住初始化驱动。例如,如果您在两个内核中使用CGU驱动程序,最后调用CGU_init()方法的驱动程序会重写其他内核设置的CGU配置。
如果你真的需要一个由关键区域锁保护的共享内存区域,你可以使用Cortex原子'负载和存储(LDREX,STREX)指令“测试和设置共享内存锁。M0和M4不会交错访问到共享区域。或者,可以使用位带。
我可以在两个内核之间共享代码吗?
使用箭头展开或折叠本节 可以,但代码需要在M0指令集内编译,因为M0内核只有一个可用于M4内核指令的子集。
通过共享内存和中断事件进行处理器间通信
每个内核都有一个中断,它可被其他内核(或者它自己的内核)触发。对两个内核来说,这是NVIC中断1号。要为其他内核生成一个中断,你可以使用两种方法中的一种。
方法1:SEV指令
设置事件指令将产生一个其他内核的中断。如果该指令在M4内核中执行,它将会在M0内核中产生中断。如果在M0内核中执行,将在M4内核中产生中断。
void signal_other_core(void)
{
__DSB();
__sev();
} 方法2:通过CREG调用
CREG M0TXEVENT和M4TXEVENT寄存器可以用于直接生成事件。和SEV指令不同,它们可以用于在其他内核或自己的内核中产生一个中断。
在M0内核中产生一个中断(从任一内核),使用
LPC_CREG->M4TXEVENT = 0x1;
在M4内核中产生一个中断(从任一内核),使用
LPC_CREG->M0TXEVENT = 0x1; 
在这两种内核中,将M0TXEVENT或M4TXEVENT的值设置为0将清除中断事件。
在M0内核中,
LPC_CREG->M4TXEVENT = 0x0; 在M4内核中,
LPC_CREG->M0TXEVENT = 0x0;

posted @ 2015-09-02 17:51  IAmAProgrammer  阅读(1732)  评论(0编辑  收藏  举报