1、概述
英飞凌芯片的多核启动牵涉初始化核寄存器、初始化堆栈与上下文信息、初始化RAM、同步核等步骤,思维导图如下:
2、英飞凌单片机多核启动步骤
2.1、第一步:链接文件定义入口BMI
Core0 main 之前MCU干了什么?
BMI指定,英飞凌的BMI是软件运行开始的地方,没有BMI无法启动软件运行的,BMI定义如下:
/* BMI Header to be placed at location 0xA0000000 */
typedef struct
{
const uint32 stadabm;
const uint16 bmi;
const uint16 bmhdid;
const uint32 chkstart;
const uint32 chkend;
const uint32 crcrange;
const uint32 crcrangeinv;
const uint32 crchead;
const uint32 crcheadinv;
}BMD_HDR;
通过链接文件指定到如下四个位置中的任意一个即可。
BMI的指定结构如下
具体代码如下
BMI (rx) : org = 0x80000000, len = 0x20
. BMI_HDR : ALIGN(4)
{
*(.BMI_HDR *);
} > BMI
#define BMD_HDR_START
#include "MemMap.h"
const BMD_HDR BMIHEADER =
{
};
#define BMD_HDR_STOP
#include "MemMap.h"
#elif defined (BMD_HDR_START)
#undef BMD_HDR_START #pragma section ". BMI_HDR " a #define BMD_HDR_START
#elif defined (BMD_HDR_STOP)
#undef BMD_HDR_STOP
#pragma section
2.2、第二步:定义入口ENTRY(symbol)
链接文件指定入口函数,mcu 启动的地址有很多方式,这里介绍链接文件指定启动位置。使用ENTRY 指定一个symbol
ENTRY(cstart)
程序中要执行的第一条指令称为入口点。您可以使用ENTRY链接器脚本命令来设置入口点。参数是一个符号名:
有几种方法可以设置入口点。链接器将依次尝试以下方法来设置入口点,当其中一个方法成功时停止:
这里面我们就可以在代码里定义一个函数。函数名字叫做
void cstart(void)
这里面 函数的链接位置,也是通过链接文件来指定。这里不赘述,一般都是”从头开始“。
例如如下定义
1、定义map文件
#elif defined CSTART_START_SEC_CODE
#undef CSTART_START_SEC_CODE
#pragma section ".text.cstart" ax 4
#elif defined CSTART_STOP_SEC_CODE
#undef CSTART_STOP_SEC_CODE
#pragma section
2、匹配cstart的函数到map
#define CSTART_START_SEC_CODE
#include "MemMap.h"
void cstart(void) ;
#define CSTART_STOP_SEC_CODE
#include "MemMap.h"
3、定义cstart的地址
PFLASH0_CPU0_STARTUP (rx!p) : org = 0x8000 0100, len = 0x100
4、定义链接文件
.CPU0_CSTART_CODE : ALIGN(4)
{
*(.text.cstart);
} > PFLASH0_CPU0_STARTUP
这样就能将函数放置到固定地址,链接文件有描述文章。
以英飞凌Tc27x举例如下:
BMI结构体放置在地址:0x8000 0000 ,BMI结构体的第一个元素为用户代码开始地址,例如实际项目里面的实现
ENTRY(_RESET)其中的symbol为_RESET函数,函数入口地址为0x8000 0020,也就是BMI结构体的第一个元素,
复位函数如下,其实不是复位,只是名字这样子而已,随便命名吧
void _RESET (void)
{
__asm (".global _START");
/* we must make a jump to cached segment, why trap_tab follow */
#ifdef _GNU_C_TRICORE_
__asm ("_START: ja cstart");
}
下面跳入cstart函数,此处就是用户代码开始的地方,用链接文件进行指定即可。
2.3、第三步:CSTART函数
cstart 是上电后,MCU 指定的第一个函数入口,第一步执行的语句就是
2.3.1、获取核ID
coreID = __MFCR(0xFE1C);
就是说把Core 寄存器 0xFE1C 的数 拿给CoreID这个 变量。
CoreID寄存器如下:
在多处理器系统中,每个逻辑处理器核心被赋予一个唯一的标识号。核心标识寄存器保存这个号码。
Cstart的处理如下
2.3.2、概念:一般通用寄存器
Core Registers处理,首先解释一下Core Registers,如下图所示
一般通用寄存器分为两个部分16个数据寄存器DGPRs D[0]~D[15],16个地址寄存器 A[0]~A[15]。
初始化栈指针到A10
.CPU0_PRIVATE_SDATA :
{
__CPU0_SMALL_DATA = . + 0x8000;
KEEP(*(.sdata.CPU0.Private*));
*(.sdata);
*(.sdata*);
*(.gnu.linkonce.s.*);
__CPU0_SMALL_DATA_end = .;
} > CPU0_DSPR AT > PFLASH0_CPU0_PRIVATE
地址寄存器如下:
数据寄存器如下:
2.3.3、初始化SP,也就是A10
SetReg (SP, StackAddress);
2.3.4、数据同步
DSYNC()
{
__asm__ volatile ("dsync" : : : "memory");
}
什么意思呢:就是保证所有的数据都搞定了,在开始访问下一个数据,也就是说保证做CSA前A10搞好了。
为了确保内存一致性,在任何访问活动CSA内存位置之前。必须执行DSYNC指令
2.3.5、设置PSW
下一步需要设置PSW寄存器,寄存器如下
程序状态字寄存器(PSW)是一个32位寄存器,它包含在通用寄存器值中没有捕获的特定于任务的体系结构状态。下半部保存与保护系统相关的控制值和参数,包括:
此值的设置一般为
MTCR (CPU_PSW, 0x00000980);
开启栈深度count、
开启管理者模式、
开启寄存器的读写权限。
2.3.6、设置PCXI寄存器
前一个上下文信息寄存器(PCXI)包含到前一个执行上下文的链接信息,支持中断和自动上下文切换。PCXI是任务状态信息的一部分。前一个上下文指针(PCX)保存前一个任务的CSA的地址。
其实PCXI也就是链接字。
2.3.7、概念:CSA-上下文
此处参考大佬文章介绍一下CSA。
上下文保存到内存时,占用16个字块,称为上下文保存区(csa)。
我们在调试器打开CPU 寄存器能找到PCXI,这个是链接字。这里以链接字为0x003706BE 为例。
找到此时存储上下文的RAM空间,计算方式如下
计算结果如下
找到结果如下
相关事件和说明
上下文在任务切换过程中总是保存或者读取。新任务抢占老任务,老任务的上下文会被抢占,新任务结束,继续执行老任务,老任务的上下文会被重读恢复读取使用。后进先出的过程。那么下面看一下在哪些情况下上下文会被操作。
和上面说的一样,高级context保存是系统自动保存。在中断发生,进入trap,函数调用,换句话说 当系统发生需要使用另一个context的时候。高级context就会被自动保存。然而lower context则需要相应的指令去保存。这里提一下,lower 和 upper 对应的地址,对应的链接字,大小格式,都是一样的。比如配置了64个上下文位置,那么这是lower + upper 一共可以用的空间。所以可以通过程序状态字去查看这个context具体指的是lower or upper.PSW有寄存器标志位显示的。
PCX, FCX 这两个分别的 free context List 和 Previous context List 对应的就是用过了的和可用的链接字的位置。那么问题来了,会不会存在用超了的现象,用超了会有措施吗。这里就到提到LCX 这个寄存器,指向的是最后的某个context 所以 当系统认为的 free context 和 LCX 相等时 就需要注意了。Context 马上就要超了。
好几个CSA需要存储后面文章会进行介绍。
做指令同步:ISYNC();以上所有指令均由CPU执行。然后在执行下一条指令之前刷新线程。
2.3.8、CSA初始化
第一步通过PCXI也就是链接字解析出来RAM地址,然后将最后的CSA存储在LCX里面。
MTCR (CPU_LCX, PCXI_Address_Last);
实际的的CSA地址值存储在FCX里面
MTCR (CPU_FCX, PCXI_Address_Real);
2.3.9、指令同步
ISYNC(); isync 来确保 指令的操作完整性
以上所有指令均由CPU执行。然后在执行下一条指令之前刷新线程。
2.3.10、禁狗
2.3.11、设置SMU
设置SMU属于可以选择的操作,看软件应用情况进行设置。
2.3.12、使能ICACHE
步骤如下
1、将bit位PCBYP设置为1
MTCR(CPU_PCON0,2);
2、指令同步
ISYNC();
3、将PCON1的PCINV置1
然后读取此寄存器的bit位,直到变为0
#define PCON1_PCINV 0x01
while((MFCR(CPU_PCON1) & PCON1_PCINV) == PCON1_PCINV);
以上步骤均需要解锁寄存器再操作,也就是调用
void Mcal_SetCpuENDINIT(const sint8 wdt)
4、将PCON0的PCBYP位置位为0
MTCR(CPU_PCON0,0);
2.3.13、使能DCACHE
使能DCACHE相对来说比较简单
直接使能即可
MTCR(CPU_DCON0,0);
注意一下使能的DCACHE与ICACHE之后一定需要调用ISYNC();与DSYNC();
2.3.14、给中断向量表分配栈区
在这里需要先给中断向量表分配栈区,然后在后面的任意时刻都可以初始化中断向量表。
MTCR(CPU_ISP, IStackAddress) ;
2.3.15、初始化A0\A1\A8\A9
Ifx_Ssw_setAddressReg(a0, __SDATA1(0));
Ifx_Ssw_setAddressReg(a1, __SDATA2(0));
Ifx_Ssw_setAddressReg(a8, __SDATA3(0));
Ifx_Ssw_setAddressReg(a9, __SDATA4(0));
2.3.16、对Flash的读权限等状态设置
2.3.17、RAM初始化
ram初始化主要有两个方面。一个是从对应的falsh 拿数值放到ram 里面。
一个是初始化直接是0.
Copytabble一般是链接文件的方式,代码解析如下
IFX_SSW_INLINE void Ifx_Ssw_C_InitInline(void)
{
Ifx_Ssw_CTablePtr pBlockDest, pBlockSrc;
unsigned int uiLength, uiCnt;
unsigned int *pTable;
/* clear table */
pTable = (unsigned int *)&__clear_table;
while (pTable)
{
pBlockDest.uiPtr = (unsigned int *)*pTable++;
uiLength = *pTable++;
/* we are finished when length == -1 */
if (uiLength == 0xFFFFFFFF)
{
break;
}
uiCnt = uiLength / 8;
while (uiCnt--)
{
*pBlockDest.ullPtr++ = 0;
}
if (uiLength & 0x4)
{
*pBlockDest.uiPtr++ = 0;
}
if (uiLength & 0x2)
{
*pBlockDest.usPtr++ = 0;
}
if (uiLength & 0x1)
{
*pBlockDest.ucPtr = 0;
}
}
/* copy table */
pTable = (unsigned int *)&__copy_table;
while (pTable)
{
pBlockSrc.uiPtr = (unsigned int *)*pTable++;
pBlockDest.uiPtr = (unsigned int *)*pTable++;
uiLength = *pTable++;
/* we are finished when length == -1 */
if (uiLength == 0xFFFFFFFF)
{
break;
}
uiCnt = uiLength / 8;
while (uiCnt--)
{
*pBlockDest.ullPtr++ = *pBlockSrc.ullPtr++;
}
if (uiLength & 0x4)
{
*pBlockDest.uiPtr++ = *pBlockSrc.uiPtr++;
}
if (uiLength & 0x2)
{
*pBlockDest.usPtr++ = *pBlockSrc.usPtr++;
}
if (uiLength & 0x1)
{
*pBlockDest.ucPtr = *pBlockSrc.ucPtr;
}
}
}
2.4、进入Main
思维导图如下
2.4.1、Main
Main函数原型如下
int main(void)
{
(
MTCR(0xfe04, 0x200 | (MFCR(0xfe04))););
ISYNC();
Os_StartCoreGate();
EcuM_Init();
return 0;
}
有了ECUM算是与AUTOSAR接轨了,注意一下上述代码均是在Core0里面执行的,启动从核是在Ecum里面执行的。
2.4.2、API: Os_StartCoreGate
Os_StartCoreGate();函数原型如下
FUNC(void, OS_CODE) Os_StartCoreGate(void) {
uint32 core = OS_MFCR(0xfe1c);
/* Prevent secondary cores starting before given permission in Os_Cbk_StartCore() */
while ((core == 1U) && (Os_StartBlock[0] != 0xa55a5aa5U)) {OS_NOP();}
while ((core == 2U) && (Os_StartBlock[1] != 0xa55a5aa5U)) {OS_NOP();}
}
2.4.3、API: EcuM_Prv_StartSlaveCores
ECUM里面有个配置项
启动从核
函数原型如下
void EcuM_Prv_StartSlaveCores( void )
{
/*local variables*/
StatusType dataStatus_chr = E_NOT_OK;
uint16 cntrLoopCtr_u16;
/*Starting all the OS Cores in a loop*/
for( cntrLoopCtr_u16=0; cntrLoopCtr_u16<ECUM_CFG_NUM_OS_CORES ; cntrLoopCtr_u16++ )
{
StartCore( cntrLoopCtr_u16, &dataStatus_chr);
if(dataStatus_chr != E_OK)
{
/* StartCore Failed*/
EcuM_ErrorHook(ECUM_E_START_CORE_FAILED);
}
}
}
2.4.4、API: StartCore
其中有个函数
StartCore( cntrLoopCtr_u16, &dataStatus_chr);
cntrLoopCtr_u16参数就是核号,函数原型如下
FUNC(void, OS_CODE) Os_StartCore(CoreIdType CoreID, Os_StatusRefType Status) {
*Status = E_OK; /* [$UKS 1628] */
if (CoreID >= 3U) {
*Status = E_OS_ID; /* [$UKS 1629] */
} else if (*Os_const_coreconfiguration[CoreID].state > 1U) {
*Status = E_OS_ACCESS; /* [$UKS 1631] */
} else if (*Os_const_coreconfiguration[CoreID].state != 0U) {
*Status = E_OS_STATE; /* [$UKS 1632] [$UKS 1633] */
} else {
/* OK */
}
if (*Status == E_OK) {
/* [$UKS 1634] */
(*Os_const_coreconfiguration[CoreID].state) = 1U; /* Started */
if ((CoreIdType)OS_MFCR(0xfe1c) != CoreID) {
*Status = Os_Cbk_StartCore(CoreID);
}
}
return;
} /* StartCore */
2.4.5、API:Os_Cbk_StartCore
Os_Cbk_StartCore函数是关键点
FUNC(StatusType, OS_CALLOUT_CODE) Os_Cbk_StartCore(uint16 CoreID) {
StatusType ret = E_OS_ID;
if (CoreID == 1U) {
Os_StartBlock[0] = 0xa55a5aa5U;
#ifndef _lint /* Skip lint checks on compiler-specific ornamentations */
CPU1_PC.U = (uint32)cstart;
CPU1_SYSCON.B.BHALT = 0U;
#endif
ret = E_OK;
} else if (CoreID == 2U) {
Os_StartBlock[1] = 0xa55a5aa5U;
#ifndef _lint /* Skip lint checks on compiler-specific ornamentations */
CPU2_PC.U = (uint32)cstart;
CPU2_SYSCON.B.BHALT = 0U;
#endif
else {
/* Not an expected core! */
}
return ret;
}
2.4.6、注意点
其实到最后也就是有几个核执行几次cstart函数而已,注意是在对应的核里面执行的。
另外注意一个点FUNC(void, OS_CODE) Os_StartCoreGate(void)里面不包含核0,一定要看清。