Skip to content

# 19.1. MCU代码编译指南

## 19.1.1. 基础信息

1. MCU编译工具链目前仅支持GreenHills工具链，目前地平线内部使用测试过2021.01.04及2022.01.04版本；原则上支持ARMV8-R Cortex-R52的Greenhills版本均可使用；

2. MCAL开发遵循AUTOSAR 4.4标准；

3. MCU核为ARM R52+，可以用ARM R52 technical reference manual文档作为参考，请至ARM官网查询；

4. 关于MCU域的其他模块资料，可咨询地平线对接人员获取；

## 19.1.2. MCU编译环境安装说明

1. 如果是在windows环境：

   > 请先安装Python2.7及pip，这部分请根据自己的PC系统参考网上相关说明；

   > 再安装scons，执行如下命令：

   >     python -m pip install pywin32

   >     python -m pip install wheel

   >     python -m pip install scons==3.0.0

   > 同时请保证将工具链路径也在环境变量中导出：

2. 如果是在ubuntu系统上，请保证ubuntu上有python3.x版本，然后执行如下命令：

   >     sudo apt install scons

   > 同时请保证将工具链路径也在环境变量中导出：

   >     export GHS_PATH="工具链路径"

3. 如果 拿到地平线的源码 ，即可通过scons进行MCU镜像编译和生成：

   > - 编译product（product镜像是指完整的AUTOSAR产品镜像，需要您根据自己的工程场景自行补充BSW/RTE/SWC等相关源码）

   >     # 进入Build/Autosar目录
   >     python build_autosar.py product_matrix_A # 硬件板或者项目名，比如这个的product_matrix_A表示地平线Matrix板

   > - 编译lite的ETAS镜像版本（假如您已有RTA-OS源码）

   >     # 进入Build/Autosar目录
   >     python build_lite.py matrix_A # 硬件板或者项目名，比如这个的matrix_A表示地平线Matrix板

   > - 编译lite的FreeRtos镜像版本

   >     # 进入Build/FreeRtos目录
   >     python build_freertos.py matrix_A # 硬件板或者项目名，比如这个的matrix_A表示地平线Matrix板

4. 如果拿到的是非scons构建环境的SDK，那么SDK目录中一般会有 build，该目录下会有 build.bat ，运行它即可：

5. 生成成功会有类似如下的打印：

备注

生成的代码一般在源码包的output路径，或者在build/output路径下。

## 19.1.3. MCU编译及集成说明

注意

下面的代码目录可能会因为不同的SDK路径有所不同。同时因为Autosar的部分(包括OS和中间件)会有授权等原因，请咨询相关对接人员确定。

下面的目录说明仅供参考，用于理解功能划分。

    MCU
    ├── BootLoader           # SBL相关代码
    ├── Build                # Build系统，包含编译/链接脚本
    ├── Config               # 针对各种不同board的McalCdd模块配置
    ├── McalCdd              # 各种模块驱动代码
    ├── OpenSource           # FreeRtos开源代码仓库
    ├── Os                   # 各种OS的源码，针对不同板子配置生成不同OS源码
    ├── output               # 编译/链接生成文件的所在目录
    ├── Platform             # 平台配置相关，比如基础数据定义，各个模块的Memmap配置，此部分可以由客户自己替换
    |   ├── Compiler         # 平台配置和编译器相关的定义
    |   ├── Memmap           # 模块的memmap配置
    |   ├── Schm             # 模块驱动中可能涉及到exclusive区域定义，可能需要客户选择填充
    ├── SafetyLib            # 包含fchm驱动代码，功能安全框架代码，芯片模块的STL库
    ├── Service              # 包含地平线自研的中间服务代码，比如电源管理，OTA管理，Log/Shell等
    ├── Target               # 系统基础代码，比如启动相关，任务定义相关，中断相关等
    └── Tools                # 工具类目录，比如MCU调试用的劳德巴赫脚本

当拿到SDK后用户需要集成自己的功能，通常会遵循如下大致步骤：

1. 修改 Build 目录下脚本中的工具链路径为自身环境中的路径：

   > - 如果拿到的是基于scons构建工具的包，设置 GHS_PATH 这个环境变量即可；

   > - 如果拿到的是其他形式的包，通常会在Build下附带build.bat文件，设置这个文件中工具链对应路径即可；

2. 如果想替换Os，请修改Build下：

   > - SConstruct文件中指定OS相关目录；

   > - 如果是其他形式包，通常可以在build.bat替换os代码指定路径；

3. 如果用到了McalCdd相关模块，需要根据实际板子的情况，修改Config下对应模块的PBcfg等文件。比如使用了哪个Uart、波特率是多少等。如果有不确定，可以提供硬件相关信息后咨询地平线对接人员。

4. 一些McalCdd模块会有临界区配置要求，请在 Platform/Schm 路径下找到对应模块的文件，在其中添加实现。比如在多个核上都使用Adc，那么Adc的临界区通常需要使用spinlock；如果只有单核上使用Adc并且只有一个task使用，那么Adc的临界区实现就可以留空。总之，要根据自己的集成情况，确认临界区代码应该用什么实现方式。

5. 同理如果使用的McalCdd模块中，比如全局的变量/代码等，想根据自身项目的情况安排在不同镜像区域，那么可以修改 Platform/Memmap 下对应模块的memmap配置。比如改模块代码所在section，可以改：

6. 如果是Autosar os，通常配置生成os之后，task/中断/callback等需要用户填充代码实现。这一部分逻辑我们通常放在 Target 目录，如下是一个典型的文件及其功能：

   >     .
   >     ├── HorizonCbk.c                # 需要填充的os callback函数
   >     ├── HorizonHook.c               # 需要填充的os Hook函数
   >     ├── HorizonISR.c                # 需要填充的中断处理实现
   >     ├── HorizonTask.c               # 需要填充任务处理实现
   >     ├── main.c                              # SoC相关配置逻辑，通常在此函数中启动OS
   >     ├── startup.s                   # 基于J6X MCU的启动代码，保护对R52 MPU/栈指针等的初始化
   >     ├── Target.c                    # J6X上与SoC相关的设置，其函数通常由main.c在启动OS之前调用
   >     └── Target.h

   > 更多与MCU启动相关的细节，可以参考 最小系统开发 章节。

# 19.2. MCU镜像烧写指南

整体的镜像烧写流程(包括main域镜像)可以参考 镜像烧写工具使用 。

本小节更关注MCU的镜像烧写，提供MCU镜像快速烧写方法以及空片烧写/救砖方法。

软件工具包括dfu工具与fastboot工具。fastboot工具可以用来做uboot+fastboot的重量级刷机，也可以刷Acore相关镜像，但是需要预先做打包镜像打包等操作，较为繁琐。 如果调试或使用仅涉及MCU，推荐dfu工具。它可以用来做轻量级MCU镜像刷机，适合只刷MCU镜像，步骤简单。

注意

如下的下载形式都是non-secure烧写方式，如果板子是需要secure烧写方式，请咨询地平线相关支持人员

注意

如果板子类型是J6E Matrix，编译生成的MCU镜像是 J6M_MCU_Matrix_*.bin，需要将 J6M_MCU_Matrix_*.bin 更名为 J6E_MCU_Matrix_*.bin，再进行烧录。

## 19.2.1. DFU加载MCU镜像(调试常用)

1. 调整开发板的拨码到USB烧录模式，参考 J6X开发板系统软件用户手册 的 拨码设置 章节；

2. 板子重启之后，确认一下dfu工具是否识别到：

   > 上图红色中 name=”Device Fireware Upgreade” 即表示正确识别到；

3. 加载相关镜像并自动运行加载到sram中的MCU镜像：

   >     # 加载sbl镜像并运行
   >     dfu-util -d,3652:6610  -a 0 -D cmd_load_sbl
   >     timeout /T 2
   >     dfu-util -d,3652:6610  -a 0 -D sbl.pkg
   >     timeout /T 2
   >     dfu-util -d,3652:6610  -a 0 -D cmd_exit
   >     timeout /T 5

   >     # 加载scp和ddr镜像并运行
   >     dfu-util -d,3652:6620  -a 0 -D scp.bin -R
   >     timeout /T 5
   >     dfu-util -d,3652:6620  -a 0 -D u-boot-spl_ddr.bin -R
   >     timeout /T 5

   >     # 加载MCU镜像到sram并跳转到MCU镜像运行，请将文件改成对应你使用的
   >     dfu-util -d,3652:6620  -a 0 -D [你的MCU镜像bin文件] -R
   >     timeout /T 3

4. 最后一条命令执行完，MCU这边就能正常跑起来MCU镜像

注意

这个方式的MCU镜像是加载到sram中，断电或者reset镜像就消失了。重启之后重复上面的过程，可以重新加载MCU镜像并运行。

通常我们在调试MCU的时候会通过这个方式，效率比较高，更换镜像方便

## 19.2.2. DFU烧写flash中的MCU镜像

该模式利用dfu工具把MCU的bin镜像烧写到flash的指定位置。

烧写MCU.bin依赖 flash.py，在交付物文件夹MCU相关目录下可以找到 flash.py。

1. 调整开发板的拨码到USB烧录模式，参考 J6X开发板系统软件用户手册 的 拨码设置 章节；

2. 板子重启之后，确认一下dfu工具是否识别到：

   > 上图红色中 name=”Device Fireware Upgreade” 即表示正确识别到；

3. 下载SBL镜像:

   >     # 每执行一条命令都看一下uart的串口是否有Download OK，OK之后再执行其他命令
   >     dfu-util -d,3652:6610 -a 0 -D cmd_load_sbl
   >     dfu-util -d,3652:6610 -a 0 -D sbl.pkg
   >     dfu-util -d,3652:6610 -a 0 -D cmd_exit

   > 每一次下载成功都会有这个打印：

   > 下载完后SBL会自动运行起来；

4. 烧写MCU镜像

   >     # 注意需要将 J6_MCU_DEBUG.bin放在flash.py同级目录
   >     # 将J6_MCU_DEBUG.bin烧写到flash对应MCU镜像位置
   >     python flash.py

5. 之后将拨码切换回flash启动，启动加载的就会是新的MCU镜像

## 19.2.3. 空片下载或者救砖

1. 调整开发板的拨码到USB烧录模式，参考 J6X开发板系统软件用户手册 的 拨码设置 章节；

2. 板子重启之后，确认一下dfu工具是否识别到：

   > 上图红色中 name=”Device Fireware Upgreade” 即表示正确识别到；

3. 此时我们通过usb加载包括MCU镜像、Acore uboot等镜像：

   >     # 加载sbl镜像并运行
   >     dfu-util -d,3652:6610  -a 0 -D cmd_load_sbl
   >     timeout /T 2
   >     dfu-util -d,3652:6610  -a 0 -D sbl.pkg
   >     timeout /T 2
   >     dfu-util -d,3652:6610  -a 0 -D cmd_exit
   >     timeout /T 5

   >     # 加载scp和ddr镜像并运行
   >     dfu-util -d,3652:6620  -a 0 -D scp.bin -R
   >     timeout /T 5
   >     dfu-util -d,3652:6620  -a 0 -D u-boot-spl_ddr.bin -R
   >     timeout /T 5

   >     # 加载MCU镜像到sram并跳转到MCU镜像运行，请将文件改成对应你使用的
   >     dfu-util -d,3652:6620  -a 0 -D J6_MCU_DEBUG.bin -R
   >     timeout /T 20

   >     # 下载uboot等启动acore需要的镜像
   >     dfu-util -d,3652:6625  -a 0 -D hobot-j6e-bl31.dtb
   >     timeout /T 5
   >     dfu-util -d,3652:6625  -a 1 -D bl31.bin
   >     timeout /T 5
   >     dfu-util -d,3652:6625  -a 2 -D tee-pager_v2.bin
   >     timeout /T 5
   >     dfu-util -d,3652:6625  -a 3 -D u-boot.bin -R
   >     timeout /T 3

4. 上述流程执行完，从Acore的串口就可以看到uboot启动，在Acore串口 CTRL+C 可以进入命令行。

5. 在Acore串口命令行中输入：

   >     dcache off
   >     fastboot 0

6. 此时Acore会进入fastboot模式，接着找到发布版本中的整镜像包(包含了MCU和Acore镜像)，在img_packages目录中：

执行红框的脚本即可自动完整烧写flash镜像。

7. 烧写完成后断电，切换拨码回flash启动模式，然后上电即可正常从flash加载镜像并启动。

# 19.3. MCU调试器使用说明

如下是在地平线J6X Matrix板上的调试器连接示例：

在拿到的源码或者SDK包中，会有一个Tools目录，如下有这三份劳德巴赫脚本：

>     Tools
>     ├── Lauterbach_Script
>     │   ├── j6e_hsm_auth.cmm
>     │   ├── j6e_attatch_mcu.cmm
>     │   └── j6e_direct_attatch_mcu_master_core.cmm

## 19.3.1. MCU已经运行起来

针对这种情况，调试器连接不能影响当前MCU的运行，需要通过 j6e_hsm_auth.cmm 和 j6e_attatch_mcu.cmm 两个脚本完成：

1. 先让trace32软件和调试器硬件连上，这一部分不涉及到和J6X MCU的连接：

2. 运行 j6e_hsm_auth.cmm 进行前期准备：

   > trace32软件状态栏会有如下显示：

3. 再运行 j6e_attatch_mcu.cmm 让调试器和MCU连接：

   > 会有如下状态显示：

   > 这表示此时调试器已经和MCU连上，接下来就可以正常进行调试了。

4. 一般调试，我们可能会需要人习惯阅读的代码或者符号，可以在调试器窗口输入如下命令：

   >     # 表示让调试器只加载elf中的符号表
   >     data.load.elf [bin对应的elf] /nocode /noregister

## 19.3.2. MCU还未运行起来

这种情况，也是可以通过调试器运行脚本直接加载MCU镜像运行，可以不依赖DFU下载镜像，也是一个调试时候的补充手段。

注意

该方式由于没有运行SBL，MCU一些外设模块可能是未上电的。此方式只适用于调试MCU R52+ core本身相关一些功能，比如OS代码调试这种不依赖外部模块的情况。

1. 先让trace32软件和调试器硬件连上，这一部分不涉及到和J6X MCU的连接：

2. 运行 j6e_hsm_auth.cmm 进行前期准备：

   > trace32软件状态栏会有如下显示：

3. 再运行 j6e_direct_attatch_mcu_master_core.cmm 直接让MCU进入可运行状态：

   > 运行该脚本后，调试会有如下显示，表示MCU连接OK：

4. 此时我们可以直接通过调试加载elf并运行：

   >     # 在调试器软件窗口输入

   >     data.load.elf [对应的elf]

5. 接下来用户就可以根据自己的需求调试MCU。

备注

当MCU运行起来之后，j6e_attatch_mcu.cmm 这个脚本可以随时重新运行，它不会影响MCU本身的运行。这个脚本能够自动识别当前启动的core并连接，从而可以进行多核调试。

## 19.3.3. 对J6X进行重启

如果想通过劳德巴赫对J6X进行reset，可以在劳德巴赫中设置如下几个寄存器：

    Data.Set HD:0x230d040C %LE %Long 0x1
    Data.Set HD:0x230d050C %LE %Long 0x1
    Data.Set HD:0x230d0708 %LE %Long 0x1
    Data.Set HD:0x230d0720 %LE %Long 0x1

如果是usb下载模式，可以在mcu串口看到有如下等待下载的打印：

如果是flash启动模式并且flash中有镜像，则会自动从flash加载镜像运行。

# 19.4. MCU运行状态查询

如果是使用 地平线发布的镜像版本 ，那么可以利用remoteproc，通过Acore侧查询MCU的运行状态。在Acore Linux系统上，查询节点目录位于： /sys/class/remoteproc/remoteproc0/ 。

1. 判断MCU各核是否正常：

   >     cat alive

   > 查看各核 counter计数，可得到各核的10ms任务计数，每10ms，计数自增1。连续两次执行 cat alive，如果计数增加，则表示MCU对应的核正常运行。如果计数没有增加，则表示MCU对应的核没有正常工作。

2. CPU负载：

   >     cat cpuloads # 依次显示各cpu的loads和最大loads

3. taskcounter节点：

   >     cat taskcounter # 获取各task的计数。

# 19.5. MCU最小系统开发

## 19.5.1. MCU启动过程

如下是简化版本的MCU启动过程：

蓝色框表示MCU核上运行，浅黄框表示HSM核上运行。

注意

这里说的最小系统开发，通常指的是MCU镜像部分的开发。

## 19.5.2. 启动代码说明

MCU的启动主要代码startup.s，启动代码主要进行MCU CPU必要的初始化。

### 19.5.2.1. 硬件注意事项

1. J6E上的MCU核是ARM R52+核，默认上电处于ARM状态；

2. MCU的地址空间，包括GIC等寄存器区域，和ARM默认的MPU background region属性不同，需要重配，否则访问寄存器空间可能会导致异常；

3. 支持硬件单精度浮点，不支持SIMD指令；

### 19.5.2.2. 功能说明

1. 启动代码第一条指令是A32指令，然后通过跳转指令切换到T32指令；

   >     .section ".os_boot", "ax"
   >     .arm

   >     .global os_test_target_arm_start
   >     .global os_test_target_thumb_start
   >     .align   32 ; Necessary alignment seen on first hardware
   >     os_test_target_arm_start:
   >     ldr     lr, .L1
   >     bx  lr
   >     .L1:
   >     .data.w os_test_target_thumb_start + 1

2. 在做其他操作前，通过MPU配置后续可能用到的各个地址空间，在 `MPU_Init` 这个标号；

   >     MPU_Init:
   >             //.....其他省略

   >             /*-----region 1 MCU sram示例配置，根据自己需求修改-------*/
   >             /* normal memory attribute */
   >             ldr r0, =1                /* Region 1 */
   >             mcr p15, 4, r0, c6, c2, 1 /* Write HPRSELR */
   >             mcr p15, 0, r0, c6, c2, 1 /* Write PRSELR */

   >             ldr r0, =0x0C800000       /* Start address */
   >             orr r0, r0, #0x2          /* SH=0, AP=1, XN=0*/
   >             mcr p15, 4, r0, c6, c3, 0 /* Write HPRBAR */
   >             mcr p15, 0, r0, c6, c3, 0 /* Write PRBAR */

   >             ldr r0, =0x0CDFFFFF      /* End address */
   >             and r0, r0, #0xFFFFFFC0
   >             orr r0, r0, #0x3          /* AttrIndex=1, non-cacheable, enable region */
   >             mcr p15, 4, r0, c6, c3, 1 /* Write HPRLAR */
   >             mcr p15, 0, r0, c6, c3, 1 /* Write PRLAR */

   >             /*------region 5 internal gic & peripheral，寄存器地址空间配置示例-------*/
   >             /* device memory attribute            */
   >             ldr r0, =5                /* Region 5 */
   >             mcr p15, 4, r0, c6, c2, 1 /* Write HPRSELR */
   >             mcr p15, 0, r0, c6, c2, 1 /* Write PRSELR */

   >             ldr r0, =0x22000000       /* Start address */
   >             orr r0, r0, #0x13         /* SH=2, AP=1, XN=1*/
   >             mcr p15, 4, r0, c6, c3, 0 /* Write HPRBAR */
   >             mcr p15, 0, r0, c6, c3, 0 /* Write PRBAR */

   >             ldr r0, =0x223FFFFF       /* End address */
   >             sub r0, r0, #1
   >             and r0, r0, #0xFFFFFFC0
   >             orr r0, r0, #0x7          /* AttrIndex=3, device memory, enable region */
   >             mcr p15, 4, r0, c6, c3, 1 /* Write HPRLAR */
   >             mcr p15, 0, r0, c6, c3, 1 /* Write PRLAR */

3. MPU region1是MCU sram区域， 用户可以按照自己的需求，切分sram，并配置各个切分区域的属性 ，比如sharebility/non-cache等等；启动代码对这一部分的配置仅供参考；

4. 其他区域都是寄存器的地址空间，MPU配置时， 寄存器所在区域必须配置成device memory ；

5. 启动代码接下来做了 `enable_prefetch/enable_peri_secure/使能VFP/配置SYSCNT寄存器` 等操作，建议客户保留这些代码；

6. 接下来是做栈的初始化，每个core都有自己的一段栈区域。 启动代码只初始化了R52+上abort/undefined/system mode的栈寄存器 ，并没有初始化配置irq/fiq mode的栈寄存器。客户需要根据自己OS的情况，确定R52+各个模式的栈寄存器是否需要初始化；

   >     dostack:
   >             msr     sp_abt, r2                  ; Allow 256 bytes for abort stack
   >             sub     r2, r2, #0x100

   >             msr     sp_und, r2                  ; Allow 256 bytes for undef stack
   >             sub     r2, r2, #0x100              ; prepare stack starting point for hyp context

   >             mov     sp, r2                      ; set stack pointer for hyp

7. 紧接着让当前core从hyper模式跳转到el1的system mode，对应关键代码在 `enter_el1_mode` ；

   >     enter_el1_mode:
   >             mrs     r0, cpsr    ; read cpsr
   >             mov     r1, #0x3f
   >             bfi     r0, r1, #0, #6 ; svc bits,AIF mask
   >             msr     spsr_hyp, r0

   >             // location to branch to when leaving hypervisor
   >             adr     r0, continue_in_el1_mode
   >             msr     elr_hyp, r0

   >             mov     r0, r13
   >             dsb
   >             isb
   >             eret

   >             ; note this must be 16byte boundray or cpu will run in problem
   >             .balign 32
   >     continue_in_el1_mode:
   >             mov     r13, r0

8. 之后对于主核，会跳转到greenhills工具链的 `_start` 函数，区域对应.bss这样段的清零；对于从核，直接跳转到main。 用户也可以自己实现清零区域的操作，从而减少对工具链的函数的依赖 ；

   >     finalize_crt0:
   >             ; Ensure all writes to system registers have taken place
   >                     dsb
   >                     isb

   >             ; Check if master core or slave core
   >             cmp   r5, #0
   >             beq   crt_start

   >     .need main
   >             b     main

   >     crt_start:
   >     .need _start
   >             b _start  ; CRT startup - initializes memory. Master core only

   >     .end

9. 主核启动后，就可以通过主核来启动从核，这个要符合一定的流程，详细解释在 从核启动 章节。

## 19.5.3. 从核启动

以J6E为例，cluster1上有两个核，这两个核可以配置成split模式，此时呈现出来就是两个核；也可以配置成lock-step模式，此时呈现出来就是一个核。 硬件上电默认是split模式。

### 19.5.3.1. split模式启动从核

    uint32 i = 0;
    uint32 flag = 0;
    // let saftyboot，ramproten keep as default as 1
    // *(volatile uint32 *)(0x230f0078) &= ~(0x00000380);

    // we set slave core's vectortable
    // cluster1 core0 and cluster1 core1
    // cluster1 core0
    *(volatile uint32 *)(0x230f0070) = (uint32)&os_test_target_arm_start;
    // cluster1 core1
    *(volatile uint32 *)(0x230f0074) = (uint32)&os_test_target_arm_start;
    OS_DMB();
    OS_ISB();

    // enable cluster1 clk ,set cmg_cluster1_en bit, bit15=1
    i = *(volatile uint32 *)(0x23100020);
    i = i | 0x8000; // set bit15=1
    *(volatile uint32 *)(0x23100020) = i;
    OS_DMB();
    OS_ISB();
    //wakeup req en, bit7
    // cluster1 core0
    *(volatile uint32 *)(0x22241374) |= 0x80;
    // cluster1 core1
    *(volatile uint32 *)(0x22241378) |= 0x80;
    OS_DMB();
    OS_ISB();

    // wakeup req, bit0
    // cluster1 core0
    *(volatile uint32 *)(0x22241374) |= 0x01;
    // cluster1 core1
    *(volatile uint32 *)(0x22241378) |= 0x01;
    OS_DMB();
    OS_ISB();

    // check power status,
    // bit7 for cluster1 core0 run status, bit12 for cluster1 core1 run status
    i = *(volatile uint32 *)(0x22241384);
    // check cluster1 core0 and cluster1 core1 are in runmode before change clk to 1.2G
    while (flag == 0)
    {
        if ((i & 0x1080) == 0x1080)
        {
            // cluster1 core0 and cluster1 core1 in run mode
            flag = 1;
        }
        i = *(volatile uint32 *)(0x22241384);
    }

    // relase slave cores halt
    // cluster1 core0 and cluster1 core1 will run code
    *(volatile uint32 *)(0x230f0078) &= ~(0x03);
    OS_DMB();
    OS_ISB();

    // change cpu clk to 1.2G
    // set bit0=1, bit4=0
    i = *(volatile uint32 *)(0x23100020);
    i = i | 0x01; // bit0=1
    i = i & 0xFFFFFFEF; // bit4=0
    *(volatile uint32 *)(0x23100020) = i;
    OS_DMB();
    OS_ISB();

### 19.5.3.2. lock-step模式启动从核

    uint32 i = 0;
    uint32 flag = 0;
    // cfg cluster1 as lock-step, change bit6 to 0
    *(volatile uint32 *)(0x230f0078) &= ~(0x00000040);
    // we set slave core's vectortable, this time only one core in cluster1
    // cluster1 core0
    *(volatile uint32 *)(0x230f0070) = (uint32)&os_test_target_arm_start;
    OS_DMB();
    OS_ISB();
    // enable cluster1 clk ,set cmg_cluster1_en bit, bit15=1
    i = *(volatile uint32 *)(0x23100020);
    i = i | 0x8000; // set bit15=1
    *(volatile uint32 *)(0x23100020) = i;
    OS_DMB();
    OS_ISB();
    // wakeup req en, bit7
    // cluster1 core0
    *(volatile uint32 *)(0x22241374) |= 0x80;
    OS_DMB();
    OS_ISB();
    while (i < 10000) i++;
    // wakeup req, bit0
    // cluster1 core0
    *(volatile uint32 *)(0x22241374) |= 0x01;
    OS_DMB();
    OS_ISB();
    // check power status,
    // bit7 for cluster1 core0 run status
    i = *(volatile uint32 *)(0x22241384);
    // check cluster1 core0 in runmode before change clk to 1.2G
    while (flag == 0) {
    if ((i & 0x0080) == 0x0080) {
            // cluster1 core0 in run mode
            flag = 1;
    }
    i = *(volatile uint32 *)(0x22241384);
    }
    // relase slave cores halt
    // cluster1 core0 will run code
    *(volatile uint32 *)(0x230f0078) &= ~(0x02);
    OS_DMB();
    OS_ISB();
    // change cpu clk to 1.2G
    // set bit0=1, bit4=0
    i = *(volatile uint32 *)(0x23100020);
    i = i | 0x01; // bit0=1
    i = i & 0xFFFFFFEF; // bit4=0
    *(volatile uint32 *)(0x23100020) = i;
    OS_DMB();
    OS_ISB();

## 19.5.4. MCU镜像layout

区域名称 | 起始地址 | 占用大小 | 作用
FLASH_STARTUP | 0xC80_0000 | 0x4000/16K | 启动代码位置，第一条指令一定要放在0xc800000 ，否则从SBL跳转到MCU镜像执行会异常
FIXVARADDR_Reserved | 0xC80_4000 | 0x6000/24K | 地平线发布的版本中用于debug的区域， 固定地址的一些变量使用，客户如果使用自己的版本， 此空间可以随意使用
SBIST1_Rerserved | 0xC80_A000 | 0x8C00/35K | R52 sbist需要空间，如果版本没有safetylib， 那么此空间用户可以随意使用
SBIST2_Rerserved | 0xC81_2C00 | 0x1CC00/115K | R52 sbist需要空间，如果版本没有safetylib， 那么此空间用户可以随意使用
FLASH/RAM | 0xC82_F800 | 0x4B3C00/4815K | 地平线版本中代码、栈等所在区域，客户版本可随意使用
APP_HEAP_Reserved | 0xCCE_3400 | 0x8000/32K | 地平线发布的版本上层应用动态分配buffer需要的空间， 如果没有使用地平线给的上层应用，用户可以随意使用
SCMI_IPC_Reserved | 0xCCE_B400 | 0x3000/12K | SCMI IPC通信需要的空间，用于buffer及关键数据
PMCFG_Reserved | 0xCCE_E400 | 0x800/2K | partitionManagement预留数据区域，目前暂未使用
IPC_Reserved | 0xCCE_EC00 | 0x40000/256K | IPC需要空间，IPC的buffer及关键数据存放位置
REGMAP_Reserved | 0xCD2_EC00 | 0x400/1K | 和Acore握手数据，如果该区域被覆写， 会导致Acore侧regmap读写不采用mcu代理而是本地mmio的形式 ，但是由于开启firewall后acore无读写相关寄存器权限， 将导致Acore出现SError
CRYPTO_Reserved | 0xCD2_F000 | 0x8000/32K | HSM、MCU crypto和fusa需要空间
HSM_Reserved | 0xCD3_7000 | 0x5000/20K | HSM和opentee镜像需要空间，给HSM核专用
LOG_HSM_Reserved | 0xCD3_C000 | 0x2000/8K | HSM log需要空间，log会循环覆盖
LOG_SHARE_Reserved | 0xCD3_E000 | 0x2000/8K | MCU log存放的空间，log会循环覆盖
SCP_Reserved | 0xCD4_0000 | 0x80000/512K | SCP镜像存放空间，SCP主要是power相关的操作
SBL_Reserved | 0xCDC_0000 | 0x40000/256K | SBL镜像及运行需要使用的sram空间，当SBL执行完成后， 此空间可以用作它用

注意

0x0C800000是MCU SRAM的起始地址，MCU镜像启动代码第一条指令(startup.s文件中)会放在该地址。请保证该位置为MCU启动第一条指令位置，否则从SBL跳转到MCU镜像运行时会有异常。如果想修改MCU镜像的启动第一条指令所在位置，需要同时修改SBL代码中跳转MCU运行的地址，编译出新的SBL镜像，再配合自己修改过MCU启动地址的镜像运行。

下面是地平线版本中的链接文件，解释了链接脚本中提供的一些变量作用：

    MEMORY {
        FLASH_STARTUP       : org = 0x0C800000, len = 16K   # startup code
        FIXVARADDR_Reserved : org = 0x0C804000, len = 24K   # for debug
        SBIST1_Reserved     : org = 0x0C80A000, len = 35K   # for R52 cpu core safety test running
        SBIST2_Reserved     : org = 0x0C812C00, len = 115K  # for R52 cpu core safety test bin and heap
        FLASH               : org = 0x0C82F800, len = 4815K # for OS/APP etc.
        APP_HEAP_Reserved   : org = 0x0CCE3400, len = 32K   # for app malloc
        SCMI_IPC_Reserved   : org = 0x0CCEB400, len = 12K   # for scmi
        PMCFG_Reserved      : org = 0x0CCEE400, len = 2K    # for partionmanagement
        IPC_Reserved        : org = 0x0CCEEC00, len = 256K  # for ipc
        REGMAP_Reserved     : org = 0x0CD2EC00, len = 1K    # for regmap
        CRYPTO_Reserved     : org = 0x0CD2F000, len = 32K   # for crypo
        HSM_Reserved        : org = 0x0CD37000, len = 20K   # for hsm
        LOG_HSM_Reserved    : org = 0x0CD3C000, len = 8K    # for hsm log
        LOG_SHARE_Reserved  : org = 0x0CD3E000, len = 8K    # for log
        SCP_Reserved        : org = 0x0CD40000, len = 512K  # for scp img
        SBL_Reserved        : org = 0x0CDC0000, len = 256K  # for sbl
    }
    # 如上的memory block与上述前文的表单是对应的

    SECTIONS {

        # 如下是地平线版本的section安排，客户自己的版本不必遵循
        # 只要保证前文表单中SCMI_IPC_Reserved及其之后Reserved出来的空间不挪作他用
        # 保证链接脚本中提供给代码的变量不变即可
        # 如何想修改Reserved的空间请参考后文
        .startup_text : {
            *(.startup_text)
            *(.os_boot)
        } > FLASH_STARTUP

        # 地平线版本调试数据存放位置对应section，如果不使用可删除
        .varptrtab_rodata ALIGN(4) :
        {
            "*(SORT(.rodata_varptrtab))"
        } > FIXVARADDR_Reserved

        # R52 cpu core safety test相关区域，如果没有使用该功能，可删除
        .sbist1 ALIGN(64): {
        } > SBIST1_Reserved

        .sbist2 ALIGN(64): {
            *(.sbist)
        } > SBIST2_Reserved

        # 下面的这些section是代码/数据等存放位置
        .shellCommand : {
            *(shellCommand)
        } > FLASH

        .mcal_text : {
            *(.mcal_text)
        } > .

        // .......中间省略很多代码/数据section，请参考发布版本中的文件

        .common_text : {
            *(.common_text)
        } > .

        // ........省略部分section

        # GHS needed
        .secinfo ALIGN(32): > .
        .syscall ALIGN(32): > .
        .intercall ALIGN(32): > .

        // ....省略部分section

            # 各个core的栈空间
        .stack NOLOAD ALIGN(64) MIN_SIZE(16384) : {
        *(.stack)
        } > .

        .stack_0 NOLOAD ALIGN(64) MIN_SIZE(16384): {
            *(.stack_0)
        } > .

        .stack_1 NOLOAD ALIGN(64) MIN_SIZE(16384): {
            *(.stack_1)
        } > .

        .stack_2 NOLOAD ALIGN(64) MIN_SIZE(16384): {
            *(.stack_2)
        } > .

        # Physical core 3 Logical core 3
        .stack_3 NOLOAD ALIGN(64) MIN_SIZE(16384): {
            *(.stack_3)
        } > .

        # Physical core 4 Logical core 4
            .stack_4 NOLOAD ALIGN(64) MIN_SIZE(16384): {
            *(.stack_4)
        } > .

        .bss CLEAR NOLOAD ALIGN(64): {
            *(.bss)
            *(.os_bss)
        } > .

        // .....省略部分section

        # 以上区域是实际MCU镜像的区域
        # 由FLASH_STARTUP+FIXVARADDR_Reserved+FIXVARADDR_Reserved区域组成

        # 在启动代码中会使用，用于设置SBIST所在的MPU区域
        # 如果没有使用sbist，可以删除，同时修改启动代码对它引用
        __SBIST_START           =   ADDR(.sbist1);
        __SBIST_END             =   ENDADDR(.sbist2);

        # 不会占用MCU镜像空间，占位用于提示该区域是保留的
        .ipc_shared CLEAR NOLOAD ALIGN(4096): {
            *(.ipc_shared)
        } > IPC_Reserved

        # 不会占用MCU镜像空间，占位用于提示该区域是保留的
        .SCP CLEAR NOLOAD: {
            *(.SCP)
        } > SCP_Reserved

        # 不会占用MCU镜像空间，占位用于提示该区域是保留的
        .scmi_ipc CLEAR NOLOAD ALIGN(4096): {
            *(.scmi_ipc)
        } > SCMI_IPC_Reserved

        # 不会占用MCU镜像空间，占位用于提示该区域是保留的
        .hsm CLEAR NOLOAD ALIGN(4096): {
            *(.hsm)
        } > HSM_Reserved

        # 不会占用MCU镜像空间，占位用于提示该区域是保留的
        .crypto CLEAR NOLOAD ALIGN(4096): {
            *(.crypto)
        } > CRYPTO_Reserved

        # 不会占用MCU镜像空间，占位用于提示该区域是保留的
        .log CLEAR NOLOAD: {
            *(.log)
        } > LOG_SHARE_Reserved

        # 不会占用MCU镜像空间，占位用于提示该区域是保留的
        .PMcfg CLEAR NOLOAD: {
            *(.PMcfg)
        } > PMCFG_Reserved

        # 这些定义在启动代码startup.s中会使用
        __STACK_CORE00_START    =   ADDR(.stack ); # Physical core 0 Logical core 0
        __STACK_CORE10_START    =   ADDR(.stack_1); # Physical core 1 Logical core 1
        __STACK_CORE11_START    =   ADDR(.stack_2); # Physical core 2 Logical core 2
        __STACK_CORE20_START    =   ADDR(.stack_3); # Physical core 3 Logical core 3
        __STACK_CORE21_START    =   ADDR(.stack_4); # Physical core 4 Logical core 4
        __STACK_CORE00_END      =   ENDADDR(.stack ); # Physical core 0 Logical core 0
        __STACK_CORE10_END      =   ENDADDR(.stack_1); # Physical core 1 Logical core 1
        __STACK_CORE11_END      =   ENDADDR(.stack_2); # Physical core 2 Logical core 2
        __STACK_CORE20_END      =   ENDADDR(.stack_3); # Physical core 3 Logical core 3
        __STACK_CORE21_END      =   ENDADDR(.stack_4); # Physical core 4 Logical core 4
        __COMMON_TEXT_END       =   ENDADDR(.common_text);

        # 地平线版本上层代码会使用，如果没有使用地平线的上层代码，则可去掉
        __ghsbegin_heap         =   ADDR(APP_HEAP_Reserved);
        __ghsend_heap           =   ENDADDR(APP_HEAP_Reserved);

        # 这些定义在McalCdd/Common/Power/src/Powerlib.c中会使用
        __SCP_START_ADDR        =   ADDR(SCP_Reserved);

        __SCMI_IPC_START_ADDR   =   ADDR(SCMI_IPC_Reserved);

        # 下面这些数据都是在Target.c的PreOS_Firewall_Config函数中会使用
        # 用于指示firewall需要给Acore可读写权限的开始区域
        NON_SECURE_START_ADDR   =   ADDR(SCMI_IPC_Reserved);
        # 表示firewall需要给Acore只读权限的起始地址
        SRAM_START_ADDR         =   ADDR(FLASH_STARTUP);
        # 表示SRAM的大小
        provide(SRAM_SIZE = 0x600000);
    }

在上述SRAM的排布中， 强烈不建议客户修改SCMI_IPC_Reserved及其之后的区域 ，这些部分是一些功能模块依赖关键数据位置，修改可能导致异常。如果要修改，请先咨询地平线相关支持人员。

除此之外的FLASH_STARTUP/FIXVARADDR_Reserved/SBIST1_Reserved/SBIST2_Reserved/FLASH/APP_HEAP_Reserved这个区域，用户可根据自己的实际情况随意使用。

比如，如果客户使用自己的版本，也没有用地平线提供的R52 cpu core safety test，那么上述区域可以合并使用共5037K。

同时SBL_Reserved区域在MCU镜像运行之后就可以随意使用，一些MCU镜像运行起来之后才会用到的比如栈/堆就可以使用SBL_Reserved这段空间。

## 19.5.5. Cache/MPU使用说明

R52+上对于一段内存空间的instruction cache和data cache属性，受到多个MPU配置参数影响，本小节主要总结都有哪些参数会对cache属性造成哪些影响。相关内容来自ARM文档，具体细节可查询ARM对应R52+的technical reference manual文档。

1. SCTLR寄存器

SCTLR寄存器的第2位对应全局d-cache的配置，第12位对应全局i-cache的配置，配置为1使能cache。

    mrc p15, 0, r0, c1, c0, 0
    orr r0, r0, #(0x1 << 12)    /*i-cache enable*/
    orr r0, r0, #(0x1 << 2)     /*d-cache enable*/
    mcr p15, 0, r0, c1, c0, 0

2. PRBAR寄存器

PRBAR(Protection Region Base Address Register)，即基地址设置寄存器，为32位寄存器。提供直接(PRBARn)和间接(PRBAR)两种访问机制，其中间接访问需要PRSELR寄存器配合使用。

使用方法：

直接访问：

    MRC p15, 0, <Rt>, c6, c8+n[3:1], 4n[0] ; Read PRBARn into Rt
    MCR p15, 0, <Rt>, c6, c8+n[3:1], 4*n[0] ; Write Rt into PRBARn

间接访问：

首先设置PRSELR寄存器：

    ldr r0, =0                /* Region 0 */
    mcr p15, 0, r0, c6, c2, 1 /* Write PRSELR */

接着写PRLAR寄存器：

    MRC p15, 0, <Rt>, c6, c3, 1 ; Read PRLAR into Rt
    MCR p15, 0, <Rt>, c6, c3, 1 ; Write Rt to PRLAR

寄存器各位的定义

Bits | Name | Function
--- | --- | ---
[31:6] | BASE | 对应区域开始地址的前26位，后6位地址默认为0，无法设置
[5] | None | 保留位
[4:3] | SH[1:0] | sharebility属性设置位
[2:1] | AP[2:1] | 访问权限设置位
[0] | XN | 是否永远不执行

normal memory下SH的编码含义

SH[1:0] | Normal memory | Node
--- | --- | ---
0b00 | Non-shareable | 不可共享
0b01 | UNPREDICTABLE | 保留位
0b10 | Outer Shareable | 此内存可以被Inner Shareable和Outer Shareable域共享
0b11 | Inner Shareable | 只可以在此域共享

对于任何类型的Device memory或normal Non-cacheable memory，SH的字段被忽略。

注意

对于d-cache，对应的内存属性只有配置为Non-shareable相应区域的d-cache才使能，否则其属性为non-cacheable，这是r52内存的默认性质。

3. PRLAR寄存器

PRLAR(Protection Region Limit Address Register)，即限制地址设置寄存器，与PRBAR类似，也有直接和间接访问两种方式访问机制。

使用方法

直接访问：

    MRC p15, 0, <Rt>, c6, c8+n[3:1], 4n[0]+1 ; Read PRLARn into Rt
    MCR p15, 0, <Rt>, c6, c8+n[3:1], 4*n[0]+1 ; Write Rt into PRLARn

间接访问：

首先设置PRSELR寄存器：

    ldr r0, =0                /* Region 0 */
    mcr p15, 0, r0, c6, c2, 1 /* Write PRSELR */

接着写PRLAR寄存器：

    MRC p15, 0, <Rt>, c6, c3, 1 ; Read PRLAR into Rt
    MCR p15, 0, <Rt>, c6, c3, 1 ; Write Rt to PRLAR

寄存器各位的定义

Bits | Name | Function
--- | --- | ---
[31:6] | LIMIT | 对应区域结束地址的前26位，后6位地址默认为1，无法设置
[5:4] | None | 保留位
[3:1] | AttrIndx[2:0] | 设置一个区域号，对应MAIRx寄存器的区域地址
[0] | EN | 区域使能位

4. MAIR寄存器

MAIR(Memory Attribute Indirection Register)，即内存属性间接设置寄存器，包括MAIR0与MAIR1，其位示意图为：

使用方法

设置MAIR0：

    MRC p15, 0, <Rt>, c10, c2, 0 ; Read MAIR0 into Rt
    MCR p15, 0, <Rt>, c10, c2, 0 ; Write Rt to MAIR0

设置MAIR1：

    MRC p15, 0, <Rt>, c10, c2, 1 ; Read MAIR1 into Rt
    MCR p15, 0, <Rt>, c10, c2, 1 ; Write Rt to MAIR1

Attr<n>[7:4]的定义

Bits | Name
--- | ---
0b0000 | Device memory. See The following table for the type of Device memory
0b00RW, RW not 00 | Normal Memory, Outer Write-Through transient
0b0100 | Normal Memory, Outer Non-Cacheable
0b01RW, RW not 00 | Normal Memory, Outer Write-Back transient
0b10RW | Normal Memory, Outer Write-Through non-transient.
0b11RW | Normal Memory, Outer Write-Back non-transient

Attr<n>[3:0]的定义

Bits | Description when Attr<n>[7:4] is 0000 | Description when Attr<n>[7:4] is not 0000
--- | --- | ---
0b0000 | Device-nGnRnE memory | UNPREDICTABLE
0b00RW, RW not 00 | UNPREDICTABLE | Normal Memory, Inner Write-Through transient
0b0100 | Device-nGnRnE memory | Normal memory, Inner Non-Cacheable
0b01RW, RW not 00 | UNPREDICTABLE | Normal Memory, Inner Write-Back transient
0b1000 | Device-nGnRnE memory | Normal Memory, Inner Write-Through non-transient (RW=00)
0b10RW, RW not 00 | UNPREDICTABLE | Normal Memory, Inner Write-Through non-transient
0b1100 | Device-nGnRnE memory | Normal Memory, Inner Write-Back non-transient (RW=00)
0b11RW, RW not 00 | UNPREDICTABLE | Normal Memory, Inner Write-Back non-transient

注意

所有cacheable时的内存配置都为write-through，不存在Write-Back属性，这是r52内存的默认性质。

5. 将一段内存设置为cacheable或non-cacheable

        /*---------------SET MAIR/HMAIR-------------------*/
    ldr r0, =0x44FF            /* attr0(FF): normal memory, write-back non-transient */
                               /* attr1(44): normal memory, non-cacheable */
                               /* attr2~3(00): Device-nGnRnE memory */
    mcr p15, 4, r0, c10, c2, 0 /* Write HMAIR0 */
    mcr p15, 0, r0, c10, c2, 0 /* Write MAIR0 */

    ldr r0, =0x04              /* attr4~7(04/00): Device-nGnRnE memory */
    mcr p15, 4, r0, c10, c2, 1 /* Write HMAIR1 */
    mcr p15, 0, r0, c10, c2, 1 /* Write MAIR1 */

以上代码将attr0设置为normal memory, write-back non-transient，将attr1设置为normal memory, non-cacheable，其余区域设置为Device-nGnRnE memory。

        /*---------------region 2 mcu sram---------------*/
    /* normal memory attribute */
    ldr r0, =2                /* Region 2 */
    mcr p15, 4, r0, c6, c2, 1 /* Write HPRSELR */
    mcr p15, 0, r0, c6, c2, 1 /* Write PRSELR */

    ldr r0, =__COMMON_TEXT_END/* Start address */
    sub r0, r0, #0x1
    add r0, r0, #0x40         /* Align it 64 bit*/
    and r0, r0, #0xFFFFFFC0
    orr r0, r0, #0x2          /* SH=0, AP=1, XN=0*/
    mcr p15, 4, r0, c6, c3, 0 /* Write HPRBAR */
    mcr p15, 0, r0, c6, c3, 0 /* Write PRBAR */

    ldr r0, =__STACK_CORE00_START      /* End address */
    sub r0, r0, #0x40         /* Align it 64 bit*/
    and r0, r0, #0xFFFFFFC0
    orr r0, r0, #0x3          /* AttrIndex=1, non-cacheable, enable region */
    mcr p15, 4, r0, c6, c3, 1 /* Write HPRLAR */
    mcr p15, 0, r0, c6, c3, 1 /* Write PRLAR */

    /*---------------region 3 mcu sram---------------*/
    /* normal memory attribute */
    ldr r0, =3                /* Region 3 */
    mcr p15, 4, r0, c6, c2, 1 /* Write HPRSELR */
    mcr p15, 0, r0, c6, c2, 1 /* Write PRSELR */

    ldr r0, =__STACK_CORE00_START       /* Start address */
    and r0, r0, #0xFFFFFFC0
    orr r0, r0, #0x2          /* SH=0, AP=1, XN=0*/
    mcr p15, 4, r0, c6, c3, 0 /* Write HPRBAR */
    mcr p15, 0, r0, c6, c3, 0 /* Write PRBAR */

    ldr r0, =__STACK_CORE21_END      /* End address */
    sub r0, r0, #0x1
    and r0, r0, #0xFFFFFFC0
    orr r0, r0, #0x1          /* AttrIndex=0, cacheable, enable region */
    mcr p15, 4, r0, c6, c3, 1 /* Write HPRLAR */
    mcr p15, 0, r0, c6, c3, 1 /* Write PRLAR */

以上代码将region2的属性设置为Non-shareable，并将属性对齐为attr1的属性，因此是non-cacheable的，该区域任何情况下d-cache都为非使能状态，将region3的属性设置为Non-shareable，并将属性对齐为attr0的属性，当SCTLR寄存器d-cache对应的位（第2位）置1时，该区域对应的d-cache为使能状态。

## 19.5.6. TCM使用说明

J6X上MCU各个core都有一段TCM空间，以J6E来说：

TCM | 外部访问地址 | size
--- | --- | ---
clu0c0 ATCM | 0x09000000 | 64KB
clu0c0 BTCM | 0x09100000 | 32KB
clu0c0 CTCM | 0x09200000 | 32KB
clu1c0 ATCM | 0x0A000000 | 64KB
clu1c0 BTCM | 0x0A100000 | 32KB
clu1c0 CTCM | 0x0A200000 | 32KB
clu1c1 ATCM | 0x0A400000 | 64KB
clu1c1 BTCM | 0x0A500000 | 32KB
clu1c1 CTCM | 0x0A600000 | 32KB

举例来说，对于cluster0 core0，假设它配置了寄存器让TCM映射从0地址开始。那么core0访问0地址就是访问自己的ATCM，此时如果cluster1 core1访问0x09000000这个外部访问地址，也访问的是cluster0 core0的ATCM区域。

使用TCM请遵循如下步骤：

1. 配置IMP_ATCMREGIONR, IMP_BTCMREGIONR, and IMP_CTCMREGIONR这三个R52+寄存器；这三个寄存器主要是设置core看到自己TCM地址，是core自己私有的访问地址。这样配置之后core就可以直接通过私有地址来访问TCM。这个示例里是把私有地址和外部访问地址设置成一样：

    # core0 TCM寄存器配置
    ldr r0, =0x0900001F
    mcr p15, 0, r0, c9, c1, 0 ; write IMP_ATCMREGIONR 64KB
    ldr r0, =0x0910001B
    mcr p15, 0, r0, c9, c1, 1 ; write IMP_BTCMREGIONR 32KB
    ldr r0, =0x0920001B
    mcr p15, 0, r0, c9, c1, 2 ; write IMP_CTCMREGIONR 32KB

    # core1 TCM寄存器配置
    ldr r0, =0x0a00001F
    mcr p15, 0, r0, c9, c1, 0 ; write IMP_ATCMREGIONR
    ldr r0, =0x0a10001B
    mcr p15, 0, r0, c9, c1, 1 ; write IMP_BTCMREGIONR
    ldr r0, =0x0a20001B
    mcr p15, 0, r0, c9, c1, 2 ; write IMP_CTCMREGIONR

    # core2 TCM寄存器配置
    ldr r0, =0x0a40001F
    mcr p15, 0, r0, c9, c1, 0 ; write IMP_ATCMREGIONR
    ldr r0, =0x0a50001B
    mcr p15, 0, r0, c9, c1, 1 ; write IMP_BTCMREGIONR
    ldr r0, =0x0a60001B
    mcr p15, 0, r0, c9, c1, 2 ; write IMP_CTCMREGIONR

2. 配置IMP_MEMPROTCTLR寄存器，最低bit设置成0，关闭ram protection功能；

    // 请参考ARM文档了解寄存器的含义
    mrc p15, 1, r0, c9, c1, 2 ;
    bic r0, r0, #0x1
    mcr p15, 1, r0, c9, c1, 2 ;

注意

如果不想关闭ram protection功能用TCM，请参考ARM technical reference manual中的”Preloading TCMs with ECC“，需要进行一些额外的配置，请参考ARM文档说明。

注意

记得增加一个MPU region配置，将TCM的地址空间加入。否则core访问映射的地址会出现异常。例如上面的TCM寄存器配置后，对应的MPU region配置如下：

    # 这里把之前配置的tcm的地址都加入到mpu region0配置中
    # 配置成normal memory
    ldr r0, =0                /* Region 0 */
    mcr p15, 4, r0, c6, c2, 1 /* Write HPRSELR */
    mcr p15, 0, r0, c6, c2, 1 /* Write PRSELR */

    ldr r0, =0x09000000       /* Start address */
    orr r0, r0, #0x2          /* SH=0, AP=1, XN=0*/
    mcr p15, 4, r0, c6, c3, 0 /* Write HPRBAR */
    mcr p15, 0, r0, c6, c3, 0 /* Write PRBAR */

    ldr r0, =0x0AFFFFFF      /* End address */
    and r0, r0, #0xFFFFFFC0
    orr r0, r0, #0x3          /* AttrIndex=1, non-cacheable, enable region */
    mcr p15, 4, r0, c6, c3, 1 /* Write HPRLAR */
    mcr p15, 0, r0, c6, c3, 1 /* Write PRLAR */

    MPU相关寄存器及含义，请参考前文或者ARM文档说明。

## 19.5.7. 核间中断使用说明

在J6X上，对于多cluster的MCU，不同cluster之间GICD是独立的，所以如果需要跨cluster发中断，就需要通过SoC上的Int Router模块。如下是一个多cluster的示意图：

蓝色部分表示R52+上Arm的设置，绿色部分为Int router

从上图可以看到，MCU这边有部分SPI中断是接到Int Router上的，可以通过配置Int Router触发中断递给不同Cluster里的core，从而达成核间中断的功能。

以J6E举例，我们预留了如下中断给核间中断使用：

中断ID | INT_ROUTER_SELx addr
--- | ---
234 | 0x23151300U/SEL192
235 | 0x23151304U/SEL193
236 | 0x23151308U/SEL194
237 | 0x2315130CU/SEL195
238 | 0x23151310U/SEL196
239 | 0x23151314U/SEL197
240 | 0x23151318U/SEL198

上面的表格中，中断ID和INT_ROUTER_SELx是硬件上就绑定的，无法变更。

软件触发中断用的寄存器如下表格：

INT_ROUTER_SELx寄存器需写入的值 | INT_ROUTER_SW_TRIGx addr
--- | ---
512 | 0x23154000U/TRIG0
513 | 0x23154004U/TRIG1
514 | 0x23154008U/TRIG2
515 | 0x2315400CU/TRIG3
516 | 0x23154010U/TRIG4
517 | 0x23154014U/TRIG5
518 | 0x23154018U/TRIG6

这个表格中的写入值和INT_ROUTER_SW_TRIGx是绑定的，用户可以配置通过哪个软件触发寄存器触发对应的中断。

也就是说，如果我们往INT_ROUTER_SEL0寄存器写入512，那么INT_ROUTER_SW_TRIG0寄存器就可以触发中断234；

如果我们往INT_ROUTER_SEL1写入512，那么通过INT_ROUTER_SW_TRIG0寄存器也可以触发中断235；

如果我们往INT_ROUTER_SEL1写入513，那么通过INT_ROUTER_SW_TRIG1寄存器也可以触发中断235；

注意

不推荐使用其他模块共用的中断用于核间中断。比如Uart的中断在Uart模块通信时可能会使用，如果用于核间中断会导致功能冲突。

通过INT_ROUTER_SW_TRIGx触发的中断， 会同时传递给所有cluster的GICD 。所以如果对应中断不是core需要响应的，请在GICD中屏蔽对应中断。

使用步骤：

1. 往INT_ROUTER_SELx寄存器中写入上面表格中第三列的值；

2. 写入之后，对应的中断ID和INT_ROUTER_SW_TRIGx就绑定了，往INT_ROUTER_SW_TRIGx写入1就表示触发对应ID的中断；写入0表示清除触发的对应中断；

3. 每个core响应自己需要的中断；

如下是配置和触发中断的代码示例（只用了三个中断作为示例）：

    #define OS_J6E_INT_ROUTER_BASE  0x23150000U
    #define OS_J6E_INT_ROUTER_SEL0  0x23151000U
    #define OS_J6E_INT_ROUTER_SEL192  *((volatile uint32 *)(OS_J6E_INT_ROUTER_SEL0 + 0x0300U))
    #define OS_J6E_INT_ROUTER_SEL193  *((volatile uint32 *)(OS_J6E_INT_ROUTER_SEL0 + 0x0304U))
    #define OS_J6E_INT_ROUTER_SEL194  *((volatile uint32 *)(OS_J6E_INT_ROUTER_SEL0 + 0x0308U))
    #define OS_J6E_INT_ROUTER_SW_TRIG_INT0  *((volatile uint32 *)(OS_J6E_INT_ROUTER_BASE + 0x4000U))
    #define OS_J6E_INT_ROUTER_SW_TRIG_INT1  *((volatile uint32 *)(OS_J6E_INT_ROUTER_BASE + 0x4004U))
    #define OS_J6E_INT_ROUTER_SW_TRIG_INT2  *((volatile uint32 *)(OS_J6E_INT_ROUTER_BASE + 0x4008U))

    // 初始化core对应的核间中断，主要是配置sel寄存器，把trig寄存器和ID绑定
    void InitializeCrossCoreISR(int core) {
            switch (core) {
                    case 0: /* Logical core 0, physical core 0, INTID237 */
                            OS_J6E_INT_ROUTER_SEL192 = 512U;
                            break;
                    case 1: /* Logical core 1, physical core 2, INTID238 */
                            OS_J6E_INT_ROUTER_SEL193 = 513U;
                            break;
                    case 2: /* Logical core 2, physical core 3, INTID239 */
                            OS_J6E_INT_ROUTER_SEL194 = 514U;
                            break;
                    default:
                            /* Core not in use */
                            break;
            }
    }

    // 触发一个核间中断
    void RaiseCrossCoreISR(int core) {
            switch (core) {
                    case 0: /* Logical core 0, physical core 0, INTID237 */
                            OS_J6E_INT_ROUTER_SW_TRIG_INT0 = 0x1;
                            break;
                    case 1: /* Logical core 1, physical core 2, INTID238 */
                            OS_J6E_INT_ROUTER_SW_TRIG_INT1 = 0x1;
                            break;
                    case 2: /* Logical core 2, physical core 3, INTID239 */
                            OS_J6E_INT_ROUTER_SW_TRIG_INT2 = 0x1;
                            break;
                    default:
                            /* Core not in use */
                            break;
            }
            OS_DSB();
    }

    // 清除对应中断
    void ClearCrossCoreISR0(void)
    {
            OS_J6E_INT_ROUTER_SW_TRIG_INT0 = 0x0;
            OS_DSB();
    }

    void ClearCrossCoreISR1(void)
    {
            OS_J6E_INT_ROUTER_SW_TRIG_INT1 = 0x0;
            OS_DSB();
    }

    void ClearCrossCoreISR2(void)
    {
            OS_J6E_INT_ROUTER_SW_TRIG_INT2 = 0x0;
            OS_DSB();
    }

## 19.5.8. 定时器使用说明

### 19.5.8.1. Arm R52+ timer使用

R52+每个core本身都有一个generic timer，针对于只需要一个定时器的场景，比如OS的调度tick，建议使用该定时器便于提高代码可移植性。

如下参考代码将会启动generic timer，并每隔1ms发出一个中断：

    #define SYSCNT_BASE                     (0x23110000U) /**< May vary on different SoCs and needs to be confirmed */
    #define SYSCNT                          ((SYSCNT_Type *)SYSCNT_BASE)
    #define SYSCNT_CTRL_OSCON_MASK          (0x1U)
    #define SYSCNT_CTRL_OSCON_SHIFT         (0U)
    #define SYSCNT_CTRL_OSCON_WIDTH         (1U)
    #define SYSCNT_CTRL_OSCON(x)            (((uint32_t)(((uint32_t)(x)) << SYSCNT_CTRL_OSCON_SHIFT)) & SYSCNT_CTRL_OSCON_MASK)
    #define SYSCNT_CTRL_HALTONDEBUG_MASK    (0x2U)
    #define SYSCNT_CTRL_HALTONDEBUG_SHIFT   (1U)
    #define SYSCNT_CTRL_HALTONDEBUG_WIDTH   (1U)
    #define SYSCNT_CTRL_HALTONDEBUG(x)      (((uint32_t)(((uint32_t)(x)) << SYSCNT_CTRL_OSCON_SHIFT)) & SYSCNT_CTRL_OSCON_MASK)
    #define SOC_TYPE_SYSCNT_CLK             (40000000U) /**< syscnt clk 40M Hz, May vary on different SoCs and needs to be confirmed */
    #define SYSCNT_FID0_FREQUENCY           SOC_TYPE_SYSCNT_CLK
    #define configTICK_RATE_HZ              ((1000U )) // every tick is 1ms
    #define TICK_INTERVAL                   ((SYSCNT_FID0_FREQUENCY / configTICK_RATE_HZ ) - 1)

    #define OS_CORE_ID_MASTER (0U)

    /** SYSCNT - Register Layout Typedef */
    typedef struct {
            volatile uint32 SYSCNT_CTRL;    /**< SYSCNT Control Register, offset: 0x0 */
            volatile uint32 SYSCNT_STAT;    /**< Oscillator Status Register, offset: 0x4 */
            volatile uint32 SYSCNT_CNTCVL;  /**< Oscillator Status Register, offset: 0x4 */
            volatile uint32 SYSCNT_CNTCVU;  /**< Oscillator Status Register, offset: 0x4 */
            uint8 Reserved[16];
            volatile uint32 SYSCNT_CNTFID0;
    } SYSCNT_Type;

    static inline uint64 SYSTIMER_Read_CNTPCT(void)
    {
            uint32 Rd, Rd2;
            uint64 retVal;

            __asm(" mrrc p15, 0, %0, %1, c14 ": "=r"(Rd), "=r"(Rd2): );
            retVal = ((uint64)Rd2);
            retVal = (retVal << 32);
            retVal |= Rd;
            return retVal;
    }

    static inline void SYSTIMER_Write_CNTP_CVAL(uint32 val, uint32 val2)
    {
            __asm(" mcrr p15, 2, %0, %1, c14 ": : "r"(val), "r"(val2));
    }

    static inline void SYSTIMER_Write_CNTP_CTL(uint32 val)
    {
            __asm(" mcr p15, 0, %0, c14, c2, 1 ": : "r"(val));
    }

    // Enable the core's generic timer
    void InitAndRunGenericTimer(void)
    {
            uint32 val, val2;
            uint64 cnt_val;

            // syscnt is all core share, master core configure only
    // OS API to get current coreid, needs to be confirmed with your os vendor*/
            if (GetCoreID() == OS_CORE_ID_MASTER)
            {
                    SYSCNT->SYSCNT_CNTFID0 = SYSCNT_FID0_FREQUENCY;
                    SYSCNT->SYSCNT_CTRL |= SYSCNT_CTRL_OSCON_MASK;
            }
            cnt_val = SYSTIMER_Read_CNTPCT();
            cnt_val += (uint64)TICK_INTERVAL;
            val = cnt_val;
            val2 = (cnt_val >> 32);
            SYSTIMER_Write_CNTP_CVAL(val, val2);
            SYSTIMER_Write_CNTP_CTL(ENABLE);
    }

    // reload compare value
    void ReloadGenericTimer(void)
    {
            uint64 cnt_val = SYSTIMER_Read_CNTPCT();
            uint32 val, val2;
            cnt_val += (uint64)TICK_INTERVAL;
            val = cnt_val;
            val2 = (cnt_val >> 32);
            SYSTIMER_Write_CNTP_CVAL(val, val2);
    }

### 19.5.8.2. J6X外设timer使用

J6X上MCU会有多个外设定时器可供使用，这里将以J6E举例说明外设定时器如何使用。

注意

这里提供寄存器读写的方式配置。如果拿到带McalCdd的SDK，可以参考 GPT用户手册 中提供的接口。

GPT的时钟可能不同项目有不同配置，如有疑问可咨询相关对接人员。

目前J6E有6个instance，每个instance有4个32bit计数的channel，每个channel都是一个定时器，可用于定时等功能。

由于Autosar的predef timer需求，因此instance0的channel0和channel1被占用， 因此建议从instance1开始用。

各个timer寄存器基址如下：

编号 | 基址
--- | ---
instance1 | 0x2232_0000U
instance2 | 0x2233_0000U
instance3 | 0x2234_0000U
instance4 | 0x2235_0000U
instance5 | 0x2236_0000U

instance1的channel0中断ID为83，channel1中断ID为84，依次类推。寄存器计算一般可以使用如下的宏：

    /* 每个instance的大小 */
    #define GPT_LLD_INSTANCE_SIZE                 0x10000

    /* 每个channel的大小 */
    #define GPT_LLD_CHANNEL_SIZE                  (0x14U)

    /* 每个instance的起始地址 */
    #define GPT_LLD_BASE_OFFSET(n)                (0X22310000 + GPT_LLD_INSTANCE_SIZE * n)

    /* 每个channel的起始地址 */
    #define GPT_LLD_CHANNEL_OFFEST(n, m)          (GPT_LLD_BASE_OFFSET(n) + GPT_LLD_CHANNEL_SIZE * m)

可以通过直接写寄存器的方式进行配置，如下是使用instance1 channel0的示例：

    // 设置计数时长
    write(0X22320000, tick_value); // 假设每隔tick是5ns时间，这里设置定时时间为tick_value*5ns

    // 设置工作模式及启动
    // BIT0：置1打开timer
    // BIT1：置1开启user-defined模式（否则将从0XFFFFFFFF往下计数）
    // BIT2：置0打开中断（否则将无法上报中断）
    write(0X22320008, value); // value是上面的bit组合

    // BIT0：置0关闭timer
    write(0X22320008, value); // value需要bit0指令

    // 在中断函数中读清中断状态，否则下一次中断无法上报
    read(0x2232000c)

只支持向下计数；无法支持设置一个比较值然后计数比较的硬件功能； 默认工作在continue模式下 ，即每次都会自动重装tick_value；

如果需要one-shot的形式，则需要在中断函数中停止timer，然后自己根据需要重配tick_value；

# 19.6. MCU Lite镜像说明

MCU Lite镜像分为两种：基于ETAS RTA-OS的编译方式，基于FreeRtos的编译方式。两种镜像拥有相同的功能，以地平线J6X MCU平台为基础，在保证基本功能的前提下，尽力做到轻量化。

Lite镜像是地平线提供的参考编译工程。目的在于提供一个最基础的项目工程，加快客户开发速度。在使用MCU系统软件的过程中，客户可以根据自己的项目工程管理方式和镜像编译方式，基于Lite镜像做加法，进行功能添加，自行修改完善。即在拥有基础功能的基础上只添加需要的功能，以提高开发的效率。

## 19.6.1. 编译说明

参考 MCU编译环境安装说明 章节。

## 19.6.2. 已有功能函数说明

该镜像只包含基本的功能：基础外设初始化、Firewall、Crypto、Ipc、Ota、电源管理、mcu初始化、Shell交互、看门狗。默认不包含CAN、ETH等功能，如有必要可以自行按需添加。

### 19.6.2.1. 基础外设相关功能函数如下：

main.c或HorizonTask.c的初始化task中

    Port_Init(NULL);
    Uart_Init();
    I2c_Init(NULL_PTR);
    TCA9539_Port_Init();

### 19.6.2.2. Firewall相关功能函数如下：

main.c中

    PreOS_Firewall_Config();

### 19.6.2.3. Crypto相关功能函数如下：

HorizonTask.c中

    //初始化函数，放在初始化task中
    Crypto_Init(NULL_PTR);

HorizonISR.c中

    Ipc_Driver_HSMIpc1Ch4Isr(); //Ipc_HsmIpc1Ch4Isr的中断回调函数中
    Ipc_Driver_HSMIpc1Ch5Isr(); //Ipc_HsmIpc1Ch5Isr的中断回调函数中

### 19.6.2.4. Ota所使用Ipc相关功能函数如下：

HorizonTask.c中

    //初始化函数，放在初始化task中
    OtaFlashIpc_Init();
    Ipc_Init(&Ipc_PrivShmCfgInstance0, IpcConf_Priv_IpcInstance_IpcInstance_0);

    //功能函数，放在循环task中
    OtaFlashIpc_OpenInstance();

HorizonISR.c中

    Ipc_CpuIpc0Chan11HandlerforOtaFlash(); //ota所使用ipc通道Ipc_CpuIpc0Ch11Isr的中断回调函数中

### 19.6.2.5. Ota相关功能函数如下：

HorizonTask.c中

    //初始化函数，放在初始化task中
    Fls_Init(NULL_PTR);

    //功能函数，放在循环task中
    Fls_MainFunction();
    OtaFlashIpc_OpenInstance();
    OtaFlash_MainFunction();

### 19.6.2.6. 电源管理相关功能函数如下：

HorizonTask.c中

    //初始化函数，放在初始化task中
    PowerLib_FuncRegister(&Powerlib_Func);
    Pmic_Init(NULL);
    PwrIf_Init();
    Scmi_Service_Init();
    ExtIo_Pmic_En();
    SysPower_McuCoreEnterLowPower();

    //功能函数，放在循环task中
    ScmiProcess(NULL);

HorizonISR.c中

    ScmiIpc_Irq0Handler(); //Scmi所使用ipc通道Ipc_CpuIpc1Ch0Isr的中断回调函数中
    ScmiIpc_Irq1Handler(); //Scmi所使用ipc通道Ipc_CpuIpc1Ch1Isr的中断回调函数中
    ScmiIpc_Irq2Handler(); //Scmi所使用ipc通道Ipc_CpuIpc1Ch2Isr的中断回调函数中

### 19.6.2.7. mcu初始化相关函数如下：

HorizonTask.c中

    McuFunc_ModuleInit(void)

上述函数展开后的实际调用包括：

    Mcu_Init(NULL_PTR);
    Mcu_InitRamSection(0);
    Mcu_SetMode(MCU_MODE_INIT_CONFIG_ID_0);
    Mcu_InitClock(MCU_CMU_INIT_CONFIG_ID_0);
    Mcu_DistributePllClock();

### 19.6.2.8. Shell相关函数如下：

main.c中

    Uart_Init();
    Log_Init();
    Shell_Init();

HorizonTask.c中

    //功能函数，放在循环task中
    Shell_Handler();

## 19.6.3. 必要函数说明

软件中只要包含Firewall、基础外设初始化、电源管理函数与mcu初始化函数，acore就可以正常启动到kernel，具体对应的函数如下：

    PreOS_Firewall_Config();
    Uart_Init();
    Log_Init();
    McuFunc_ModuleInit();
    Port_Init(NULL);
    I2c_Init(NULL_PTR);
    Pmic_Init(NULL);
    PwrIf_Init();
    Scmi_Service_Init();

## 19.6.4. 功能添加说明

若要在Lite镜像的基础上添加部分功能，首先需要将相关功能所在的文件添加到scons编译路径中，其次将相关功能函数加以调用。

### 19.6.4.1. 添加文件到scons编译路径

打开对应的Sconstruct文件，例如SConstruct_lite_matrix_A，需要找到如下代码：

第一部分：

    mcal_build_dir          = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'McalCdd')
    mcal_gen_build_dir      = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Config', 'McalCdd', 'gen_matrix_A')
    platform_build_dir      = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Platform')
    bootloader_dir          = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'BootLoader', 'Xmodem')
    os_build_dir            = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Os', 'RTA-OS-hobot')  #diff

    service_power_build_dir = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Service', 'PowerManagement')
    log_build_dir           = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Service', 'Log')
    shell_build_dir         = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Service', 'Shell')
    partition_build_dir     = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Service', 'HouseKeeping', 'PartitionManagement')
    otaflash_build_dir      = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Service', 'HouseKeeping', 'OtaFlash')
    boardpower_build_dir    =  os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Service', 'Board', 'BoardPower')
    ecuabs_build_dir        = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Service', 'EcuAbs', 'IoHwAb_Dio')

    common_basemap_build_dir    = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Common', 'Stubs', 'BaseMemmap')  #diff
    common_cryif_build_dir      = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Common', 'Stubs','CryIf')
    common_ethif_build_dir      = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Common', 'Stubs','EthIf')
    common_mcubase_build_dir    = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Common', 'Stubs','mcu_base')
    common_canif_build_dir      = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Common', 'Stubs','CanIf')
    common_det_build_dir        = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Common', 'Stubs','Det')
    common_memif_build_dir      = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Common', 'Stubs','MemIf')
    common_os_build_dir         = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Common', 'Stubs','os')
    common_comstack_build_dir   = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Common', 'Stubs','ComStack')
    common_wdgif_build_dir      = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Common', 'Stubs','WdgIf')
    common_rte_build_dir        = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Common', 'Stubs','rte')
    common_safe_build_dir       = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Common', 'Stubs','SafetyLib')

    target_build_dir            = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Target', 'Target-hobot-lite')

第二部分：

    mcal_path               = os.path.join('McalCdd/SConscript')
    mcal_gen_path           = os.path.join('Config/McalCdd/gen_matrix_A/SConscript')
    platform_path           = os.path.join('Platform/SConscript')
    bootloader_path         = os.path.join('BootLoader/Xmodem/SConscript')
    os_path                 = os.path.join('Os/RTA-OS-hobot/SConscript')

    service_power_path      = os.path.join('Service/PowerManagement/SConscript')
    log_path                = os.path.join('Service/Log/SConscript')
    shell_path              = os.path.join('Service/Shell/SConscript')
    partition_path          = os.path.join('Service/HouseKeeping/PartitionManagement/SConscript')
    otaflash_path           = os.path.join('Service/HouseKeeping/OtaFlash/SConscript')
    boardpower_path         = os.path.join('Service/Board/BoardPower/SConscript')
    ecuabs_path             = os.path.join('Service/EcuAbs/IoHwAb_Dio/SConscript')

    common_basemap_path     = os.path.join('Common/Stubs/BaseMemmap/SConscript')
    common_cryif_path       = os.path.join('Common/Stubs/CryIf/SConscript')
    common_ethif_path       = os.path.join('Common/Stubs/EthIf/SConscript')
    common_mcubase_path     = os.path.join('Common/Stubs/mcu_base/SConscript')
    common_canif_path       = os.path.join('Common/Stubs/CanIf/SConscript')
    common_det_path         = os.path.join('Common/Stubs/Det/SConscript')
    common_memif_path       = os.path.join('Common/Stubs/MemIf/SConscript')
    common_os_path          = os.path.join('Common/Stubs/os/SConscript')
    common_comstack_path    = os.path.join('Common/Stubs/ComStack/SConscript')
    common_wdgif_path       = os.path.join('Common/Stubs/WdgIf/SConscript')
    common_rte_path         = os.path.join('Common/Stubs/rte/SConscript')
    common_safe_path        = os.path.join('Common/Stubs/SafetyLib/SConscript')

    target_path             = os.path.join('Target/Target-hobot-lite/SConscript')

第三部分：

    objects += SConscript(service_power_path, exports='env', variant_dir=service_power_build_dir, duplicate=0)
    objects += SConscript(log_path, exports='env', variant_dir=log_build_dir, duplicate=0)
    objects += SConscript(shell_path, exports='env', variant_dir=shell_build_dir, duplicate=0)
    objects += SConscript(partition_path, exports='env', variant_dir=partition_build_dir, duplicate=0)
    objects += SConscript(otaflash_path, exports='env', variant_dir=otaflash_build_dir, duplicate=0)
    objects += SConscript(boardpower_path, exports='env', variant_dir=boardpower_build_dir, duplicate=0)
    objects += SConscript(ecuabs_path, exports='env', variant_dir=ecuabs_build_dir, duplicate=0)

    objects += SConscript(common_basemap_path, exports='env', variant_dir=common_basemap_build_dir, duplicate=0)
    objects += SConscript(common_cryif_path, exports='env', variant_dir=common_cryif_build_dir, duplicate=0)
    objects += SConscript(common_ethif_path, exports='env', variant_dir=common_ethif_build_dir, duplicate=0)
    objects += SConscript(common_mcubase_path, exports='env', variant_dir=common_mcubase_build_dir, duplicate=0)
    objects += SConscript(common_canif_path, exports='env', variant_dir=common_canif_build_dir, duplicate=0)
    objects += SConscript(common_det_path, exports='env', variant_dir=common_det_build_dir, duplicate=0)
    objects += SConscript(common_memif_path, exports='env', variant_dir=common_memif_build_dir, duplicate=0)
    objects += SConscript(common_os_path, exports='env', variant_dir=common_os_build_dir, duplicate=0)
    objects += SConscript(common_comstack_path, exports='env', variant_dir=common_comstack_build_dir, duplicate=0)
    objects += SConscript(common_wdgif_path, exports='env', variant_dir=common_wdgif_build_dir, duplicate=0)
    objects += SConscript(common_rte_path, exports='env', variant_dir=common_rte_build_dir, duplicate=0)
    objects += SConscript(common_safe_path, exports='env', variant_dir=common_safe_build_dir, duplicate=0)

    objects += SConscript(mcal_path, exports='env', variant_dir=mcal_build_dir, duplicate=0)
    objects += SConscript(mcal_gen_path, exports='env', variant_dir=mcal_gen_build_dir, duplicate=0)
    objects += SConscript(platform_path, exports='env', variant_dir=platform_build_dir, duplicate=0)
    objects += SConscript(bootloader_path, exports='env', variant_dir=bootloader_dir, duplicate=0)
    objects += SConscript(target_path, exports='env', variant_dir=target_build_dir, duplicate=0)
    objects += SConscript(os_path, exports='env', variant_dir=os_build_dir, duplicate=0)

以添加Autosar/Bsw/src目录为例，在第一部分中添加文件目录路径：

    bsw_build_dir           = os.path.join(env.subst('$OUTPUT_DIR'), 'objs', 'Autosar', 'Bsw','src')

在第二部分中添加SConscript文件路径（该文件存储在第一部分所属的目录路径当中）：

    bsw_path                = os.path.join('Autosar/Bsw/src/SConscript')

在第三部分中将目录路径与SConscript文件路径添加到objects中：

    objects += SConscript(bsw_path, exports='env', variant_dir=bsw_build_dir, duplicate=0)

这样就可以将Autosar/Bsw目录添加到编译当中，可以调用该目录中的功能函数，其他目录同理。

### 19.6.4.2. 添加功能函数

在所需代码添加到编译路径后，只需进行声明或调用相应的.h文件，即可调用对应的函数，包括地平线提供的驱动函数与自己编辑的功能函数。