硬件十万个为什么

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

搜索
热搜: 活动 交友 discuz
查看: 949|回复: 2

原RTEMS.CN wtcat会员:rtems 移植到 IAR6.3

[复制链接]

7

主题

10

帖子

48

积分

版主

Rank: 7Rank: 7Rank: 7

积分
48
发表于 2017-6-22 12:04:42 | 显示全部楼层 |阅读模式
已在STM32F4 上验证过。 本人的网盘链接: http://pan.baidu.com/s/1sktKTyP
回复

使用道具 举报

7

主题

10

帖子

48

积分

版主

Rank: 7Rank: 7Rank: 7

积分
48
 楼主| 发表于 2017-6-22 12:05:08 | 显示全部楼层
我移植的原则是如果没有非常非常的必要, 不要动头文件(如果你随便动了, 那可能就陷入了泥潭)。 那gcc的头文件咋整? 当然是照搬, 而且最好连RTEMS本身的头文件路径也不要改变,这样会减少很多工作量。
    首先在Linux环境下搭建Rtems开发环境,直接用Rtems-source_build来安装, 比较简单。 搭建好以后选择编一个bsp(你手上对应的开发板), 我用的是STM32F439, 所以我直接编译STM32F4的bsp就可以了, 编译以后安装, 然后找到安装的目录,直接把那个目录的头文件目录include复制, 同时还要arm-rtems4.11-gcc的include目录, 有了这两个目录我们就可以开工了。
    然后在用IAR创建一个工程, rtems4.11源码 + 两个include目录拷贝的新建的工程下   分别放在rtems-include 和 gcc-include两个目录下。 下面是代码移植顺序。
如下图
千万不要将代码一个一个添加到工程,因为那样不便于管理。 我们可以用一个C文件来模拟一个类似Makefile.am文件,因为这样可以很方便的来管理编译文件
例如:
score_makefile.c
/*We only build multiprocessing related files if HAS_MP was defined */
#ifdefHAS_MP
#include"src/mpci.c"
#include"src/smpmulticastaction.c"
#include"src/cpuset.c"
#include"src/cpusetprintsupport.c"
#endif
/*CORE_APIMUTEX_C_FILES */
#include"src/apimutex.c"
#include"src/apimutexlock.c"
#include"src/apimutexislocked.c"
#include"src/apimutexunlock.c"
当然这也做也有缺点, 那就是相当于把所有模块并入到一个文件中,可能会出现变量重定义, 修改一个文件那么所有文件都要重新编译, 以及一个隐藏的但很重要的问题。 下面我主要说一下这个问题
在rtems源码中有一些文件专门用来定义变量, 举个例子, Msgdata.c文件
的内容如下:
#ifHAVE_CONFIG_H
#include"config.h"
#endif
#defineRTEMS_MESSAGE_EXTERN
#include<rtems/system.h>
#include<rtems/rtems/messageimpl.h>
rtems/rtems/messageimpl.h文件中有如下定义:
RTEMS_MESSAGE_EXTERNObjects_Information  _Message_queue_Information;
显然只要去掉RTEMS_MESSAGE_EXTERN 就能定义变量了。
   
    按照我们上面的score_makefile.c文件的组织方式, 如果说有一个文件在Msgdata.c文件的上面, 且它包含了rtems/rtems/messageimpl.h头文件,那编译的时候将出现_Message_queue_Information未定义。 因为一般头文件都会定义一个包哨位来防止头文件被重复包含。所以在Msgdata.c中将不会包含rtems/rtems/messageimpl.h头文件,从而导致没有定义该变量。 所以最好的解决办法是将所有以这种方式定义变量的文件单独添加到工程(数量很少)。
Cpukit/score/src,cpukit/sapi, cpukit/rtems/ 目录下的代码编译器来错误不多,主要是gcc的编译器的属性IAR不支持:如__attribute__((unused))来表示某个变量或参数未使用, 我们可以直接将其去掉, 另一种方式是自己定义一个宏:
    #define  __attribute__(…)
下面我们来重点说一下cpukit/cpu/arm 目录下的东东, 这个目录包含了和目标CPU异常处理和任务切换等等方面的代码,总而言之, CPU任务调度的机制就在这里实现。 我用的是stm32f4,属于ARMV7M架构
主要有几个函数:
void__attribute__((naked)) _CPU_Context_restore(
  Context_Control*heir
)
void__attribute__((naked)) _CPU_Context_switch(
  Context_Control*executing,
  Context_Control*heir
)
staticvoid __attribute__((naked)) _ARMV7M_Thread_dispatch( void )
void__attribute__((naked)) _ARMV7M_Exception_default( void )
__attribute__((naked))的属性阻止编译器生成任何函数入口或退出代码
意思就是说调用这些函数时不用生成压栈和出栈的代码
但这个属性IAR6.3并不支持, 但我们可以使用IAR的一个属性__stackless来代替。
回复 支持 反对

使用道具 举报

7

主题

10

帖子

48

积分

版主

Rank: 7Rank: 7Rank: 7

积分
48
 楼主| 发表于 2017-6-22 12:05:34 | 显示全部楼层

同时这些函数都是由gcc内联汇编写成,虽然IAR6.3也支持和gcc类似的内联汇编, 但还不支持常量的输入, 如下
__asmvolatile (
       “ldr  r0, [r0, %[VAL_1]]\n”
       :
       :[VAL_1]”I” (__VAL)
);
这样IAR就无法这样的指令, 不过我看了IAR7.3好像支持了, 但使用了一下编译一个正确的工程却运行出错,我还没找的原因, 所以我就还是选择了6.3版本。
鉴于这个原因,所以我干脆用汇编重写这个函数,这样就不存在这个问题了。
static void __attribute__((naked))_ARMV7M_Thread_dispatch( void )
{
  __asm__volatile (
    "bl_Thread_Dispatch\n"
    /*FIXME: SVC, binutils bug */
    ".short0xdf00\n"
    "nop\n"
  );
}
这个函数是个重点, 这看起来是个画蛇添足,因为明明可以直接调用_Thread_Dispatch 为什么还要用_ARMV7M_Thread_dispatch来间接调用?
我开始没注意到这个东西, 后来我调试的时候发现任务在切换1次后就不能切换了, 最后我注意到_ISR_Nest_level = 1,这当然不能切换了, 因为任务不能再中断内切换。 就这样追根溯源我找到了两个函数
void_ARMV7M_Pendable_service_call( void )
{
xxxxxxx
  _ISR_Nest_level= 1;
    xxxxxxx
}
void_ARMV7M_Supervisor_call( void )
{
xxxxxxx
  _ISR_Nest_level= 0;
    xxxxxxx
}
调用过程我大致说一下, 在OS时钟中断退出时会将 _ISR_Nest_level和_Thread_Dispatch_disable_level分别减一, 如果满足条件
_ISR_Nest_level= 0,
_Thread_Dispatch_disable_level= 0,
_Thread_Dispatch_necessary= true
这就会调用_ARMV7M_Pendable_service_call函数这样就使得_ISR_Nest_level= 1,如果在这个函数结束时没有调用_ARMV7M_Supervisor_call函数那么, _ISR_Nest_level永远将不会等于0, 即系统不会再进行任务切换。
那么关键就在这里
  __asm__volatile (
    "bl_Thread_Dispatch\n"
    /*FIXME: SVC, binutils bug */
    ".short0xdf00\n"
    "nop\n"
  );
.short0xdf00 这显然是在定义一个常量,值为0xdf00, 再看他的注释“FIXME: SVC, binutils bug”, 这应该是和SVC指令有关的东东, 没错其实这不是个随机值, 而是CPU的机器码, 其对应的汇编指令为 SVC #0, 这样就对了,通过SVC #0 指令触发软中断调用_ARMV7M_Supervisor_call函数从而使得_ISR_Nest_level变为0
下面来说一下libBSP部分的移植
Bsp部分引用了许多连接脚本定义的变量, 但在IAR下不能像那样定义,所以我们必须想点法子。
和TLS(线程本地存储)相关的变量:
Externchar _TLS_Data_begin[]
Externchar _TLS_BSS_begin[]
Externchar _TLS_Data_end[]
Externchar _TLS_BSS_end[]
Externchar _TLS_Data_size[]
Externchar _TLS_BSS_size[]
Externchar _TLS_Size[]
Externchar _TLS_Alignment[]
和workarea初始化相关的变量量
Externchar WorkAreaBase[]
Externchar RamBase[]
Externchar RamSize[]
我们不在连接脚本内定义, 所以把类型全部换成uint32_t类型。 因为我没用TLS,所以我将_TLS_Size设为0 ,即_TLS_BSS_end- _TLS_Data_begin = 0
RamBase= 0x20000000, RamSize = 0x20000
IAR数据存放形式
  
Readwrite
  
CSTACK
HEAP
而WorkAreaBase在HEAP段, 所以我们只需要 将其值设为sfe(HEAP)(表示堆的结尾), 同时将在HEAP段大小设为0, 即用WorkAreaBase来指示HEAP段
最后还有几个和中断向量表有关的变量,因为RTEMS将向量表重定位到RAM中, 所以需要用一些变量来表示向量表的起始位置,
bsp_start_vector_table_begin====》  __vector_table
bsp_start_vector_table_end==》向量表结尾
bsp_vector_table_size= bsp_start_vector_table_end –
bsp_start_vector_table_begin
然后在定义一个向量段
uint32_tbsp_vector_table_begin[16 + BSP_INTERRUPT_VECTOR_MAX + 1];
这里有一个很重要的问题, bsp_vector_table_begin这样定义是否可行? 我当初就是没有注意到这个问题, 导致出现了一个奇怪的问题就是当我使用串口中断的时候会出现异常错误。调试了整整两天,才发现原因。
在GNU的连接脚本里, 这个向量段是定义在RAM的起始地址也就是0x20000000,而这样定义却没法保证bsp_vector_table_begin在这个地址, _ARMV7M_SCB->vtor这个向量表地址寄存器并不是赋给它任何地址都OK的, 这点非常重要,比如说我调试的时候明明赋给它的是0x200013AC, 但它实际上却是0x20001380, 所以这个向量表应该是字节对齐的。
所以最好的办法是将bsp_vector_table_begin定义在RAM其实地址。 在IAR下我们可以这样做:
#pragmalocation=0x20000000
__no_init static uint32_tbsp_vector_table_begin[16 + BSP_INTERRUPT_VECTOR_MAX + 1];
这样连接器就会将变量放在指定地址。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|硬件十万个为什么

GMT+8, 2018-7-18 01:00 , Processed in 0.050787 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表