-
概述
-
背景
ARC Core是一个符合AUTOSAR标准的嵌入式开发平台,它包含三个组件:支持代码(Arctic Core)、开发平台(Arctic Studio)和配置工具(BSW Builder)。
-
Arctic Core
Arctic Core是一个开放源码(GPL)的嵌入式系统平台,它基于模块化设计,符合AUTOSAR标准,目前实现了以下内容:
- 符合OSEK标准的实时内核
- LIN和CAN总线的通信栈
- 外设:ADC, PWM, digital I/O
其实现路线图如下(图 11):
图 11 Arctic Core路线图
已经实现
在Arctic Studio Pro中实现
2010年3月实现
2010年5月实现
2010年9月实现
没有计划
-
Arctic Studio
Arctic Studio是一个集成了gcc编译器的开发环境(powerpc-eabispe),同时包括了C语言编辑器CDT,在Professional版本中包含ECU的配置工具。
Arctic Studio对源代码进行统一管理,它会自动创建用于构建Arctic Core的Makefile的基本模板,但用户仍需要编辑自己工程内的Makefile。构建系统模型如图 12所示。
图 12 构建系统
-
BSW Builder
BSW Builder是一个付费的AUTOSAR中BSW的配置工具,它支持图形化的ECU级编辑(如图 13所示)和操作系统级编辑(如图 14所示)。
图 13 BSW Builder
图 14 BSW Builder操作系统编辑
通过基于arxml数据的配置,可以生成和C语言代码的文件。
-
目标
本文档的目的在于分析Arctic Core结构,从而获得它的运行方式及RTE与OS的关系,为RTE的实现做准备。
-
方法
分析主要由以下方式进行:
- 准备阶段:Arctic Core的获取、编译与调试。
- 静态分析:项目的依赖关系及目录结构。
- 动态分析:Arctic Core的运行过程,OSEK OS与RTE在运行时的关系。
-
-
Arctic Core的获取、编译与调试
-
获取
-
Clone Arctic Core
- 安装过程会附带安装cygwin和mercurial,需要确保它们被正确安装。
- 在File->Import中选择Mercurial版本库。并在URL中填入:
http://arccore.com/hg/arc-stable(稳定版)
http://arccore.com/hg/arc(孵化版) - 并选择check out as a project(s) in the workspace完成导入。
-
Convert to a C/C++ Project
- 由于导入完成后项目为一般项目,没有用CDT建立类型和变量索引,所以源码不能进行编译。需要在Project Explorer视图中选择项目,并将其转换为C项目。如图 21所示。
-
图 21 将检出项目转换为C项目
- 在转换选项中选择Makefile Project,Toolchain为Empty Toolchain。如图 22所示。
图 22 转换为C/C++项目
-
使用
-
建立自己的项目
- 在菜单中选择File à New à C Project
- 新建对话框中Project Type填入Empty Arctic Core Project。Toolchain为Core Builder Cygwin Toolchain。
- 在Arctic Core source path中填入:${workspace_loc:/arc-stable},其中arc-stable是检出时填写的项目名称。
- 在Advanced settings…中检查下面三个环境变量是否配置正确(C/C++ Build / Environment):
-
含义
变量
编译器位置
CROSS_COMPILE=/opt/powerpc-eabispe/bin/powerpc-eabispe-
板级支持包位置
BOARDDIR=<arc-stable/boards/任意一个目录名
需要注意的是:如果想以后要仿真调试,需要选择带有sim的目标板(如mpc5554sim)
目录外编译位置
BDIR=../${ProjName}
- 在C/C++ Build / Settings下Binary选项卡中勾选Elf-Parser,编译成功后目录中的elf文件就可以被自动解析。
- 在Project Preferences下Project reference中勾选Arctic Core项目(如arc-stable),编写代码时源代码就可以被自动搜索关联。
- 建立C语言文件,并编写代码。(或者也可以拷贝源码库中example下的代码,再进行修改,但要注意的是只拷贝目录下的文件,不要拷贝目录。如图 23所示)
图 23 拷贝版本库中的示例
-
使用BSW Builder编写源代码
配置信息(如Os_Cfg.c)可以使用手工填写,也可以使用BSW Builder生成。下面的例子就是用BSW Builder写一个简单的OSEK操作系统应用:
- 首先新建一个C语言项目
图 24 新建C语言项目
- 建立新的ECU配置文件,这个文件是arxml文件。
图 25 New Ecu Configuration
- Mcu选择MPC551x并确定。
- 添加一个OS对象:
图 26 添加OS对象
- 双击OS进入其配置页面。
- 勾选所有的hook并添加1个Alarm、1个Counter、一个Event和1个Task。填入OS tick frequency为1000Hz,也就是说1ms运行一次。
- 修改Counter的Tick per base unit为1,Counter unit为OsTick,与系统时钟同步。Max Allowed Value为1000,也就是说1秒回卷一次。Min allowed ticks cyclic alarm 为10,则表示alarm最短也要10ms才能出发一次。
- 修改Event的Event Mask为1,表示一号事件。
- 修改Task类型为Extended,优先级为1并且Autostart。
- 修改Alarm的Alarm Counter为刚刚创建的Counter_1,并且Autostart。Alarm time为100表示0.1秒触发一次。Alarm cycle也为100,类型为"相对"。
- 添加set Event action,并Set Event为Event_1,Set Event Task为Task_1。
- 完成后选择Generate Stubs…产生Task.c
- 回到Overview,在Generator output directory中填入工程目录下的config并在workflow中选择Generator产生Os_Cfg.c和Os_Cfg.h。
-
打开Task.c文件,在文件顶部填入调试输出信息支持头文件:
#define USE_DEBUG
#include "Trace.h"
#define USE_SIMPLE_PRINTF
#include "simple_printf.h"
-
在Task_1中填入简单的调试信息:
// Tasks
void Task_1(void)
{
simple_printf("Task_1 Started!\n");
for (;;)
{
WaitEvent(EVENT_MASK_Event_1);
ClearEvent(EVENT_MASK_Event_1);
simple_printf("Hello World\n");
}
}
-
在所有支持的系统Hook中加入相应的调试信息:
// Hooks
void ErrorHook(StatusType Error)
{
dbg_printf("Error Code = %d\n", Error);
}
void PostTaskHook(void)
{
TaskType currTask;
GetTaskID(&currTask);
TickType alarmTick;
GetAlarm(ALARM_ID_Alarm_1, &alarmTick);
dbg_printf("<%d> Task %d Switch finished\n", alarmTick, currTask);
}
void PreTaskHook(void)
{
TaskType currTask;
GetTaskID(&currTask);
dbg_printf("[%d] Task %d Get in\n", GetOsTick(), currTask);
}
ProtectionReturnType ProtectionHook(StatusType FatalError)
{
dbg_printf("Error Code = %d\n", FatalError);
return PRO_KILLAPPL;
}
void ShutdownHook(StatusType Error)
{
dbg_printf("OS Shutdown!\n");
}
void StartupHook(void)
{
dbg_printf("OS Startup!\n");
}
-
编译
代码编写完成后,检查makefile是否包含了config目录中的全部代码,如下所示:
# Our object files
obj-y += simple_main.o
# OS object files.
obj-y += Os_Cfg.o
并检查build_config.mk文件中是否包含了所有用到的库文件。如下所示:
MOD_USE+=KERNEL MCU T32_TERM RAMLOG SIMPLE_PRINTF RAMLOG
完成后使用Build Project进行编译。
图 27 构建项目
整个编译过程会显示在Console视图中。下面我们分析make的全过程。
首先是提示信息:
行号
内容
1
**** Build of configuration Default for project test ****
2
make all
3
Building for system/kernel ../test
4
BOARDDIR: mpc5516it
5
ARCH_FAM/ARCH: /
6
cygwin warning:
MS-DOS style path detected: E:\codes\ARCStudio\arc-stable
Preferred POSIX equivalent is: /cygdrive/e/codes/ARCStudio/arc-stable
CYGWIN environment variable option "nodosfilewarning" turns off this warning.
Consult the user's guide for more details about POSIX paths:
http://cygwin.com/cygwin-ug-net/using.html#using-pathnames
7
8
9
10
11
然后将各模块C语言原文件编译成目标文件:
12
==========[ system/kernel ]===========
13
make
-r (使用内建build规则)
-C system/kernel/obj_mpc5516it (切换目录到obj_mpc5516it)
-f /cygdrive/e/codes/ARCStudio/arc-stable/scripts/rules.mk
(使用rules.mk作为一个Makefile)
ROOTDIR=/cygdrive/e/codes/ARCStudio/arc-stable
SUBDIR=system/kernel
all
14
make[1]: Entering directory
`/cygdrive/e/codes/ARCStudio/arc-stable/system/kernel/obj_mpc5516it'
(make[1]开始)
15
>> CC event.c
16
/opt/powerpc-eabispe/bin/powerpc-eabispe-gcc
-c (编译)
-B/libexec/gcc:/opt (指定编译器cc1的位置)
-mno-eabi (Embedded Applications Binary Interface:堆栈16位对齐,不从main开始执行)
-msdata=none (把所有全局初始化数据放在.data节,非初始化全局数据放在.bss节)
-mmultiple (使用多字指令)
-msoft-float (使用浮点库)
-mcpu=8540 (cpu名)
-mstrict-align (内存必须对齐)
-gdwarf-2 (使用DWARF v2作为调试标准)
-D_PPC (前缀)
-O0 (不优化)
-std=gnu99 (c语言格式=gnu99)
-MMD (不连接)
-Wall (所有警告)
-Winline (inline警告)
-fno-strict-aliasing
-fno-builtin (不识别内建类型)
-o event.o (生成event.o)
-I .
-I /cygdrive/e/codes/ARCStudio/arc-stable/drivers/include
-I /cygdrive/e/codes/ARCStudio/arc-stable/arch/ppc/mpc55xx/kernel
-I /cygdrive/e/codes/ARCStudio/arc-stable/arch/ppc/mpc55xx/drivers
-I /cygdrive/e/codes/ARCStudio/arc-stable/arch/ppc/mpc55xx/config
-I /cygdrive/e/codes/ARCStudio/arc-stable/arch/mpc55xx
-I /cygdrive/e/codes/ARCStudio/arc-stable/include/ppc
-I /cygdrive/e/codes/ARCStudio/arc-stable/include
-I /cygdrive/e/codes/ARCStudio/arc-stable/include/ppc
-I ../include
-DUSE_DBG_PRINTF
-DUSE_KERNEL
-DUSE_MCU
-DCFG_PPC
-DCFG_BOOKE
-DCFG_E200Z1
-DCFG_MPC55XX
-DCFG_MPC5516
-DCFG_BRD_MPC5516IT
-Dmpc55xx
-Dppc
-Dmpc5516
-DCC_KERNEL
/cygdrive/e/codes/ARCStudio/arc-stable/system/kernel/event.c
17
>> CC init.c
19
>> CC trusted.c
21
>> CC arch.c
23
>> CC task.c
25
>> CC task_i.c
27
>> CC resource.c
29
>> CC swap.c
31
>> CC alarm.c
33
>> CC sched_table.c
35
>> CC counter.c
37
>> CC com_internal.c
39
>> CC create.c
41
>> CC Frt.c
43
>> CC stack.c
45
>> CC isr.c
47
>> CC int_ctrl.c
将编译成功后产生的所有目标文件压缩到库中:
49
>> AR /cygdrive/e/codes/ARCStudio/arc-stable/libs/libkernel_mpc5516.a
50
/opt/powerpc-eabispe/bin/powerpc-eabispe-ar (将前面编译的.o文件压缩成.a文件)
-r
-o /cygdrive/e/codes/ARCStudio/arc-stable/libs/libkernel_mpc5516.a
arch_krn.o
event.o
init.o
trusted.o
arch.o
task.o
task_i.o
resource.o
swap.o
alarm.o
sched_table.o
counter.o
com_internal.o
create.o
Frt.o
stack.o
isr.o
int_ctrl.o
2> /dev/null
51
`/cygdrive/e/codes/ARCStudio/arc-stable/libs/libkernel_mpc5516.a'
->
`/cygdrive/e/codes/ARCStudio/arc-stable/binaries/libkernel_mpc5516.a'
(拷贝)
52
make[1]: Leaving directory
`/cygdrive/e/codes/ARCStudio/arc-stable/system/kernel/obj_mpc5516it'
(make[1]完成)
编译目录外用户定义目标文件:
53
==========[ ../test ]===========
54
make -r -C ../test/obj_mpc5516it
-f /cygdrive/e/codes/ARCStudio/arc-stable/scripts/rules.mk
ROOTDIR=/cygdrive/e/codes/ARCStudio/arc-stable SUBDIR=../test
all
55
make[1]: Entering directory
`/cygdrive/e/codes/ARCStudio/test/obj_mpc5516it'
(make开始)
56
>> CC simple_main.c
57
/opt/powerpc-eabispe/bin/powerpc-eabispe-gcc
-c
-B/libexec/gcc:/opt
-mno-eabi
-msdata=none
-mmultiple
-msoft-float
-mcpu=8540
-mno-eabi
-mstrict-align
-gdwarf-2
-D_PPC
-O0
-std=gnu99
-MMD
-Wall
-Winline
-fno-strict-aliasing
-fno-builtin
-o simple_main.o
-I ../config/mpc5516it
-I ../config
-I ../config
-I /cygdrive/e/codes/ARCStudio/arc-stable/system/kernel/obj_mpc5516it
-I /cygdrive/e/codes/ARCStudio/arc-stable/system/kernel/include
-I /cygdrive/e/codes/ARCStudio/arc-stable/system/EcuM
-I /cygdrive/e/codes/ARCStudio/arc-stable
/arch/ppc/mpc55xx/delivery/mpc5500_h7f/include
-I /cygdrive/e/codes/ARCStudio/arc-stable/communication/ComM
-I /cygdrive/e/codes/ARCStudio/arc-stable/include
-I /cygdrive/e/codes/ARCStudio/arc-stable/kernel/test
-I /cygdrive/e/codes/ARCStudio/arc-stable/kernel/include
-I /cygdrive/e/codes/ARCStudio/arc-stable/arch/ppc/mpc55xx/kernel
-I /cygdrive/e/codes/ARCStudio/arc-stable/arch/ppc/mpc55xx/drivers
-I /cygdrive/e/codes/ARCStudio/arc-stable/boards/mpc5516it/config
-I /cygdrive/e/codes/ARCStudio/arc-stable/drivers/Dem
-I /cygdrive/e/codes/ARCStudio/arc-stable/drivers/test
-I /cygdrive/e/codes/ARCStudio/arc-stable/boards/generic
-I /cygdrive/e/codes/ARCStudio/arc-stable/include
-I /cygdrive/e/codes/ARCStudio/arc-stable/include/ppc
-I ../include
-DUSE_DBG_PRINTF
-DUSE_KERNEL
-DUSE_MCU
-DUSE_KERNEL
-DUSE_MCU
-DUSE_T32_TERM
-DUSE_RAMLOG
-DUSE_SIMPLE_PRINTF
-DUSE_RAMLOG
-DCFG_PPC
-DCFG_BOOKE
-DCFG_E200Z1
-DCFG_MPC55XX
-DCFG_MPC5516
-DCFG_BRD_MPC5516IT
-Dmpc55xx
-Dppc
-Dmpc5516
/cygdrive/e/codes/ARCStudio/test/simple_main.c
58
>> CC Os_Cfg.c
60
>> CC EcuM.c
62
>> CC EcuM_Cfg.c
64
>> CC EcuM_Callout_template.c
66
>> CC Mcu.c
68
>> CC Mcu_Cfg.c
70
>> CC Det.c
72
>> CC arc.c
74
>> CC newlib_port.c
将目录外目标文件和库文件进行连接:
77
/opt/powerpc-eabispe/bin/powerpc-eabispe-ld (链接)
-T /cygdrive/e/codes/ARCStudio/arc-stable/arch/ppc/mpc55xx/scripts/linkscript_gcc.ldf
(使用linkscript_gcc.ldf作为链接脚本)
-o simple.elf (输出为elf文件)
(设置符号搜索路径)
-L"/opt/powerpc-eabispe/lib/gcc/powerpc-eabispe/4.1.2/http://www.cnblogs.com/http://www.cnblogs.com/powerpc-eabispe/lib"
-L"/opt/powerpc-eabispe/lib/gcc/powerpc-eabispe/4.1.2"
--start-group (设置链接文件)
simple_main.o
Os_Cfg.o
crt0.o
EcuM.o
EcuM_Cfg.o
EcuM_Callout_template.o
Mcu.o
Mcu_Cfg.o
Det.o
xtoa.o
arc.o
ramlog.o
printf.o
newlib_port.o
Mcu_Exceptions.o
-lgcc
-lc
/cygdrive/e/codes/ARCStudio/arc-stable/libs/libkernel_mpc5516.a
--end-group
-M > simple.map(输出映射文件)
78
Image size: (decimal)
text:91596 bytes
data:2680 bytes
bss :31812 bytes
ROM: ~94276 bytes
RAM: ~34492 bytes
79
80
81
82
83
84
>>>>>>> DONE <<<<<<<<<
85
`simple.elf'
->
`/cygdrive/e/codes/ARCStudio/arc-stable/binaries/simple.elf'
86
make[1]: Leaving directory
`/cygdrive/e/codes/ARCStudio/test/obj_mpc5516it'
-
调试
调试有多种方式,最好的是把程序直接下载到目标板,使用OCD方式进行。如果没有目标板,只有使用仿真器进行。
-
Lauterbach T32 Simulator(Trace32)
- 在 http://www.lauterbach.com/frames.html?download.html 上下载PowerPC的仿真器simppc.zip。
(注意:下载的仿真器是评估版,只能调试60分钟。如果需要调试超过60分钟,请双击下面的图标"T32.rar"下载)
- 解压simppc.zip,并把t32mppc.exe, config.t32, helpdemo.t32, men8xx.men, men405.men, t32.men, t32.cmm这几个文件拷贝到arc-stable\tools\t32\下。如果使用的是上面的压缩包,则不需要拷贝,可解压后直接运行,并跳过4-7步。
- 运行t32mppc.exe
- 选择FileàRun Batchfile…,并打开start.cmm
- 在Config对话框中选择合适的目标CPU(与编译有关)和工程目录。
- 此时菜单栏会出现Autosar菜单项,选择AutosaràLoad,然后打开编译好的目标文件。
- 选择AutosaràRestore Layout整理界面,弹出终端对话框。
- 进行调试。
-
-
静态分析
-
目录结构
Arctic Core目录结构分为半,一半为标准的OS和外设支持文件,另一半为用户定制项目的配置文件和任务及其支持文件。如图 31所示:
图 31 Arctic Core项目目录结构
-
标准库
标准库中个目录详细信息如下:
目录名
概述
所包含的内容
arch
CPU构架所支持的基本数据结构、内部驱动和启动代码
arm,ppc
boards
目标板对于CPU构架的集成配置情况,每一块目标板都有自己的目录与之对应
et_stm32_stamp,generic,mpc5516it,mpc551xsim,mpc5554sim,mpc5567qrtech
common
最基本的库,字符串处理,内核调试支持和状态解释文件
arc.c,newlib_port.c,printf.c,ramlog.c,strace.c,xtoa.c
communication
基本通信模块,支持Can和Lin总线
CanIf,Com,ComM,Lin,PduR
components
包含各个应用特有的AUTOSAR构件,由于构件在库文件中,所以可以复用
每个应用特有一个或多个
diagnostic
目前诊断模块中只有Det(Development Error Tracer)驱动可用,它操纵detCbk_t跟踪错误
Dem,Det
include
基本的类型文件和所有函数的原型
memory
内存管理,目前没有实现
NvmM
peripherals
特定目标板的外设控制
Fls_SST25xx.c
rte
运行时环境支持,目前没有实现,但是可以从include/rte.h看到所有函数的原型
rte.c
system
操作系统,用于管理ECU,内存和实时内核的基本代码
EcuM,kernel,mm,SchM,WdgM
-
用户目录
用户目录中结构详细信息如下:
目录名
概述
所包含的内容
makefile
本项目中所有需要编译和链接的目标文件的编译信息,整个项目的链接信息
obj-y(目标文件),inc-y(包含头文件),libitem-y(库文件),ldcmdfile-y(link文件),build-exe-y(可执行文件)
build_config.mk
在整个项目编译过程中,库文件需要包含的目标文件
如MOD_USE+=KERNEL T32_TERM SIMPLE_PRINTF
Task.c
任务体的实现
各个任务的任务体函数
Hook.c
钩子的实现
config
对于操作系统和外设的配置
Rte
运行时环境(任务体直接调用运行时环境的主函数)
rte.c,rte.h
-
依赖关系
整个项目依赖关系如图 32所示,从makefile中我们也可以看出整个编译首先是从用户目录开始的。
基于AUTOSAR的项目不需要自己编写的Task,而是由Rte中的Runnable实现。整个操作系统启动后首先进入自动生成的Task,再由Task调用Runnable的任务体。
任务和资源的管理在Task中由Rte自动生成实现,Alarm和Event都被封装起来自动调用,Runnable中的程序只能调用Rte提供的或BSW的函数,通信由Rte层进行转换。Rte是如何封装任务API的将在后面内容中说明。
操作系统和通信又基于目标板端口读写和中断实现,它们全部基于特定的CPU构架。
图 32 Arctic Core项目依赖关系
-
-
动态分析
-
硬件系统启动
程序的二进制布局由linker file描述,在Arctic Core中linker file被放在了arch\ppc\ mpc55xx\scripts\linkscript_gcc.ldf文件中。需要了解程序从何处开始运行,就需要找到程序入口,在linker file中程序入口由ENTRY(symbol)指令进行指定。
在linkscript_gcc.ldf中入口点为:
ENTRY(_start)
_start符号在\arch\ppc\crt0.sx第24行。它的代码如下:
_start:
// Set up the reserved registers in EABI: r1,r2 and r13()
// r1, stack pointer
lis r1,__SP_INIT@h
ori r1,r1,__SP_INIT@l
// r13, base of .sdata
lis r13,_SDA_BASE_@h
ori r13,r13,_SDA_BASE_@l
// r2, base of .sdata2 and .sbss2
lis r2,_SDA2_BASE_@h
ori r2,r2,_SDA2_BASE_@l
// make space for initial backchain..
subi r1,r1,16
// Copy initialized data from ROM to RAM
lis r3,__DATA_ROM@h
ori r3,r3,__DATA_ROM@l
lis r4,__DATA_RAM@h
ori r4,r4,__DATA_RAM@l
lis r5,__DATA_END@h
ori r5,r5,__DATA_END@l
cmplw r3,r4
beq skip_data
cmplw r4,r5
beq skip_data
subi r3,r3,1
subi r4,r4,1
1:
lbzu r6,1(r3)
stbu r6,1(r4)
cmplw r4,r5
bne+ 1b
skip_data:
# Clear uninitialized data( holds both bss and sbss )
lis r3,__BSS_START@h
ori r3,r3,__BSS_START@l
lis r4,__BSS_END@h
ori r4,r4,__BSS_END@l
cmplw r3,r4
beq 3f
li r0,0
subi r3,r3,1
2:
stbu r0,1(r3)
cmplw r3,r4
bne+ 2b
3:
# Call main() with argc set to 1 and argv ignored
li r3,1
bl main
# Call exit() with the return value from main() as argument
b exit
.globl _exit
_exit:
b _exit
.end
这段代码主要完成了以下工作:
- 初始化微处理器寄存器:r1=堆栈基始地址,r13=.sdata基地址,r2=.sdata2和.sbss2基地址。
- 初始化内存,把程序中的静态数据从rom拷贝到ram。
- 调用main函数。
其流程如图 41所示:
图 41 CPU初始化流程
在此,必须要对目标cpu的内存结构进行一个说明。
MEMORY
{
rcw(R) : ORIGIN = 0x00000000, LENGTH = 0x8
flash(R) : ORIGIN = 0x00000008, LENGTH = 0x100000
ram(RW) : ORIGIN = 0x40000000, LENGTH = 0x100000
}
整个内存被分为三个部分,rcw存放启动时的基本数据,占8个字节,内容如下:
.section ".rcw","ax"
.global _resetconfiguration
_resetconfiguration:
1 .byte 0x00 #no watchdog
2 .byte 0x5A #Boot identifier
3 .byte 0x00
4 .byte 0x00
5-8 .long _start
然后是flash区,放到flash区的信息有以下几个节:
节名
所存放的内容
.text
代码
.fls_rom
flash布局静态变量
__FLS_ERASE_ROM__
__FLS_WRITE_ROM__
__FLS_END_ROM__
.exception_tbl
异常表(中断)
.rodata
只读数据
各段的基地址
_SDA2_BASE_
__TEXT_END
__DATA_ROM
__DATA_RAM
__SDATA_START__
_SDA_BASE_
__DATA_END
最后是1MB的ram区,用于存放数据和堆栈,.data,.sdata,.bss和.sbss这几个节的内容都放在ram区中,但要注意的是.bss是没有初始值的数据,需要在程序中进行初始化,而上面那段启动程序把所有未初始化的数据都初始化为0。这个过程叫做flash解压。
-
操作系统启动
硬件系统启动后,调用main()函数,这个函数在\system\kernel\init.c第238行,它的函数体如下:
int main(void)
{
EcuM_Init();
}
仅调用了一个函数,而且没有return任何内容。
EcuM_Init()函数启动Ecu管理程序,它在system\EcuM\EcuM.c第51行,其函数体如下:
void EcuM_Init( void )
{
internal_data.current_state = ECUM_STATE_STARTUP_ONE;
// Initialize drivers that are needed to determine PostBuild configuration
EcuM_AL_DriverInitZero();
// Initialize the OS
InitOS();
// Enable interrupts
IntCtrl_Init();
// Determine PostBuild configuration
internal_data.config = EcuM_DeterminePbConfiguration();
// Check consistency of PB configuration
// TODO
// Initialize drivers needed before the OS-starts
EcuM_AL_DriverInitOne(internal_data.config);
// Determine the reset/wakeup reason
// TODO Mcu_ResetType type = Mcu_GetResetReason();
// Set default shutdown target
internal_data.shutdown_target = internal_data.config->EcuMDefaultShutdownTarget;
internal_data.shutdown_mode = internal_data.config->EcuMDefaultShutdownMode;
// Set default application mode
internal_data.app_mode = internal_data.config->EcuMDefaultAppMode;
internal_data.initiated = TRUE;
// Start this baby up
StartOS(internal_data.app_mode);
}
它主要做了以下几件事情:
- 初始化全局变量internal_data。initernal_data可以看作是ECU的控制块,它是一个EcuM_GobalType结构体,定义如下:
名称
类型
内容
initiated;
boolean
ECU是否已经启动
config;
EcuM_ConfigType*
ECU上各个硬件部分的控制信息,如: Ecu Default Shutdown Target,Ecu Default Shutdown Mode, Ecu Default App Mode
shutdown_target;
EcuM_StateType
是一个枚举变量,有以下选项:
ECUM_STATE_APP_RUN = 0x32,
ECUM_STATE_SHUTDOWN = 0x40,
ECUM_STATE_WAKEUP = 0x20,
ECUM_SUBSTATE_MASK = 0x0F,
ECUM_STATE_WAKEUP_WAKESLEEP = 0x25,
ECUM_STATE_WAKEUP_ONE = 0x21,
ECUM_STATE_OFF = 0x80,
ECUM_STATE_STARTUP = 0x10,
ECUM_STATE_PREP_SHUTDOWN = 0x44,
ECUM_STATE_RUN = 0x30,
ECUM_STATE_STARTUP_TWO = 0x12,
ECUM_STATE_WAKEUP_TTII = 0x26,
ECUM_STATE_WAKEUP_VALIDATION = 0x22,
ECUM_STATE_GO_SLEEP = 0x49,
ECUM_STATE_STARTUP_ONE = 0x11,
ECUM_STATE_WAKEUP_TWO = 0x24,
ECUM_STATE_SLEEP = 0x50,
ECUM_STATE_WAKEUP_REACTION = 0x23,
ECUM_STATE_APP_POST_RUN = 0x33,
ECUM_STATE_GO_OFF_TWO = 0x4e,
ECUM_STATE_RESET = 0x90,
ECUM_STATE_GO_OFF_ONE = 0x4d
shutdown_mode;
uint8
关闭模式
app_mode;
AppModeType
应用模式,操作系统会根据该模式启动不同的功能。
current_state;
EcuM_StateType
Ecu当前状态
- 启动调试模块(Development Error Tracer)
-
启动操作系统InitOS(),完成以下工作:
3.1) 初始化全局变量sys_t os_sys,这个变量用于表示操作系统内部的状态信息,它的类型如下:
名称
类型
内容
curr_application;
app_t*
由于OSEK操作系统可以支持多个应用程序,所以需要标识当前应用
curr_pcb;
pcb_t*
当前pcb
pcb_list;
pcb_t*
pcb表(链表)
int_nest_cnt;
uint32
中断嵌套计数
int_stack;
void*
中断堆栈
tick;
TickType
当前时钟tick值
scheduler_lock;
int
是否可以调度
*hooks
struct os_conf_global_hooks_s
所有钩子的指针
param1;
uint32_t
用于 错误提示的参数
param2;
uint32_t
-
param3;
uint32_t
-
serviceId;
uint32_t
-
task_cnt;
uint32_t
任务数量
pcb_head;
TAILQ_HEAD(tailq2,pcb_s)
pcb头部
ready_head;
TAILQ_HEAD(tailq,pcb_s)
ready队列头部(每次取ready队列第一个值)
3.2) 初始化计数器
3.3) 初始化调度表
3.4) 在ram中建立pcb,并把rom中的pcb解压到ram中,并串接成链。
4) 建立中断表:
IntCtrl_Init()函数通过spr(Special Purpose Register)指定中断表的位置。异常表在\arch\ppc\mpc55xx\drivers\Mcu_Exceptions.sx中,其内容如下:
# Force this jump table to this address to match the
# value written to z1 IVPR
.section ".exception_tbl","ax"
.balign 0x0800 //TODO: 1000 eller 800?
.global exception_tbl
# The .skip directive aligns the branch instructions
# to the irq vector offsets
exception_tbl:
b exception_IVOR0
.skip +0xc
b exception_IVOR1
.skip +0xc
b exception_IVOR2
.skip +0xc
b exception_IVOR3
.skip +0xc
b exception_IVOR4
.skip +0xc
b exception_IVOR5
.skip +0xc
b exception_IVOR6
.skip +0xc
b exception_IVOR7
.skip +0xc
b exception_IVOR8
.skip +0xc
b exception_IVOR9
.skip +0xc
b dec_exception
//b exception_IVOR10
.skip +0xc
b exception_IVOR11
.skip +0xc
b exception_IVOR12
.skip +0xc
b exception_IVOR13
.skip +0xc
b exception_IVOR14
.skip +0xc
b bad_int
从表中可以看出所有的异常都没有指定跳转的函数,而是只指定了符号,所以IntCtrl_Init()的作用就是填充完整这个异常处理表。填充过程为:不断调用类似于下面语句的宏来指定异常处理程序的位置。
asm volatile (" mtspr " "63" ",%[_val]" : : [_val] "r" ((uint32)exception_tbl))
- 调用StartOS启动操作系统。
在StartOS函数中,应该根据不同的App模式来启动调度。但是ArcticCore 2.0并没有实现App模式的检查,其函数体如下:
void StartOS(AppModeType Mode)
{
/* Check link file */
if (TEST_DATA != test_data)
{
noooo();
}
if (test_bss != 0)
{
noooo();
}
os_start();
/** @req OS424 */
assert(0);
}
这里有两个小技巧,第一是检查数据段是否初始化正确,test_data和test_bss的定义如下:
#define TEST_DATA 12345
int test_data = TEST_DATA;
int test_bss = 0;
如果没有初始化正确,就进入了noooo()死循环,从而触发看门狗reset。
二是检查os_strat()是否被退出,如果退出,则会调用assert(0)从而引发可捕获的异常。
StratOS()这个系统API调用了os_start()内部函数,它在\system\kernel\init.c第170行,函数体如下:
static void os_start(void)
{
pcb_t *tmp_pcb;
assert(init_os_called);
/* find highest prio process and run it */
tmp_pcb = os_find_top_prio_proc();
/* TODO: fix ugly */
/* Call the startup hook */
extern struct os_conf_global_hooks_s os_conf_global_hooks;
os_sys.hooks = &os_conf_global_hooks;
if (os_sys.hooks->StartupHook != NULL)
{
os_sys.hooks->StartupHook();
}
/* handle autostart */
for (int j = 0; j < Oil_GetAlarmCnt(); j++)
{
alarm_obj_t *alarmPtr;
alarmPtr = Oil_GetAlarmObj(j);
if (alarmPtr->autostart.active)
{
alarm_autostart_t *autoPtr = &alarmPtr->autostart;
SetAbsAlarm(j, autoPtr->alarmtime, autoPtr->cycletime);
}
}
// Activate the systick interrupt
{
uint32_t sys_freq = McuE_GetSystemClock();
Frt_Init();
Frt_Start(sys_freq / OsTickFreq);
}
// Swap in prio proc.
{
// FIXME: Do this in a more structured way.. setting os_sys.curr_pcb manually is not the way to go..
os_sys.curr_pcb = tmp_pcb;
// NOTE! We don't go for os_swap_context() here..
// first arg(NULL) is dummy only
os_swap_context_to(NULL, tmp_pcb);
// We should not return here
assert(0);
}
}
这个函数首先获得处于ready队列优先级最高的任务,然后切换到该任务,其间又调用了用户的StartupHook(),启动Alarm、Counter和系统Tick。
首次任务切换后操作系统就开始正式运行,至此系统启动完毕。
-
操作系统运行
操作系统的运行核心就在于如何组织任务抢占CPU的时间片。在OSEK OS中,任务被触发执行可能的情况有四种:
- 被其他任务、中断服务程序调用SetEvent()或ActivateTask()触发。任务还可以调用ChainTask()触发切换。
- 其他任务释放资源。
- 被报警器(Alarm)触发。
- 通信通知。
其中第3种情况是最为常见的,因为任务通常是以周期执行的形式体现。
-
周期任务的执行
操作系统在启动时将7号中断(时钟中断)的服务程序设置为了一个叫做OsTick的函数,这个过程由在前文中提到的Frt_Init()函数内完成,其函数体如下:
void Frt_Init(void)
{
TaskType tid;
tid = Os_CreateIsr(OsTick, 6/*prio*/, "OsTick");
IntCtrl_AttachIsr2(tid, NULL, 7);
}
系统每过一段时间,时钟计数器达到一个固定的值之后,就会触发一个时钟中断,并调用OsTick()。
OsTick()将各个计数器加一个增量,再检查基于计数器的报警器是否需要报警,如果需要,则触发相应的事件,然后切换任务。它的函数体如下:
void OsTick(void)
{
// if not used, os_tick_counter < 0
if (Os_Arc_OsTickCounter >= 0)
{
OsCounterType *c_p = Oil_GetCounter(Os_Arc_OsTickCounter);
os_sys.tick++;
IncCounter(c_p);
check_alarms(c_p);
Os_SchTblCheck(c_p);
}
}
-
RTE与操作系统的关系
含有RTE应用程序的操作系统可分为五个部分:内核、任务体、构件、RTE和基础软件,RTE在Arctic Core中是一个API的转换层,构件只能运行自己的算法或调用Rte中的函数,每一个项目都有用户自己定义的Rte模块将构件的调用转换为BSW的API。整个过程如图 42所示:
图 42 RTE与操作系统关系
内核调度器先分派一个任务开始运行,这个任务首先运行RTE跟踪和诊断程序,在Arctic Core中只实现了KickDog,然后调用构件的runnable入口函数。与如下代码类似:
void bTask25(void)
{
dbg_printf("task25\n");
WdgM_UpdateAliveCounter(WDBG_ALIVE_LOOP_BLINK_COMPONENT);
blinker_component_main();
TerminateTask();
}
进入到Component中,执行一些算法,这些算法可以是自己写的,也可以用matlab生成。算法中如果需要调用底层硬件或发送接收数据,则需要调用RTE提供的函数。如下列代码所示:
int blinker_component_main(void)
{
// 转换小灯状态
if (blinkerStatus == STD_LOW)
{
blinkerStatus = STD_HIGH;
}
else
{
blinkerStatus = STD_LOW;
}
// 通过RTE将信号送给小灯
RTE_blinker_blink(blinkerStatus);
return 1;
}
RTE函数再调用BSW函数,从而实现对硬件的调用和管理。如下所示:
void RTE_blinker_blink(uint8 arg)
{
Dio_WriteChannel(LED_CHANNEL, arg);
}
转换过程还应该实现对硬件资源的互斥使用和诊断管理。但是Arctic Core目前没有实现。
-
-
总结
OSEK OS对AUTOSAR运行体的运行提供了很大程度上的支持。它提供了任务、计数器、报警器、事件、资源、中断和通信这6个基本对象。
因为与本文无关,系统中还有很多重要的数据结构如pcb的分析和OIL语言的使用分析没有放到文章中。
Arctic Core对于RTE的支持还处于初步阶段,我们从前文看到的RTE只是AUTOSAR RTE的一部分。