打印

djyos for LM3S8962移植说明

[复制链接]
2314|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
djyos|  楼主 | 2009-10-10 08:51 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 djyos 于 2009-10-10 08:58 编辑


看本文件请参考《都江堰操作系统与嵌入式系统设计》第15章,该书在www.djyos.com可以下载。
    移植工作是在Ti赠送的EKK8962评估套件上完成的,在此对Ti的支持表示感谢。该开发套件的介绍网址(些本文时有效,不敢包永远有效):
http://www.luminarymicro.com/products/lm3s8962_can_ethernet_evaluation_kit.html
原理图下载地址:
www.djyos.com/jpg/EK-LM3S8962_EvalBoard_SCH.pdf
lm3s8962是一款cortex-m3mcu,得益于cortex-m3处理器的架构优势,lm3s8962移植过程相当简单,需要修改的地方只有:
1、  链接配置文件,在keil MDK中就是debug.scatrelease.scat文件。
2、  跟工程相关的初始化文件appinit.c和配置文件config-prj.h
3、  启动代码中系统时钟初始化部分,即initcpuc.c文件。
4、  与外设相关的代码,目前计有:按键驱动(key-hard.c)、串口驱动(uart.c)、流水灯程序(在main.c文件中)。严格来说,这些已经不属操作系统移植范畴,将在另文讲述。
以上也是移植到其他cortex-m3处理器的方法。
1.      链接配置文件
    由于cortex-m3在设计内核时,已经粗线条式地划分好了存储器配置,flash的起始地址必定是0,片内ram的起始地址必定是0x20000000。因此,不同的cortex-m3芯片,如果没有扩展片外存储器的话,debug.scatrelease.scat的修改是相当简单的,只需要把ram尺寸和flash尺寸修改一下就可以了。keil MDK使用的是realview编译环境,其说明中说在scat文件中可以用#define定义符号,如:
[pre]#define BASE_ADDRESS 0x8000
[/pre]    但我实际试了一下,keil并不支持,估计MDK带的不是realview的完整版本有关,因此修改scat文件时也稍微复杂一点。
debug.scat文件的内容如下,其中斜体粗体部分是需修改的,分号后表示注释。
ROM_LOAD 0 0x40000       0x40000为实际flash尺寸,lm3s8962256K flash
{
    text_sysload 0
    {
        initcpu.o (RESET, +FIRST)
       initcpu.o (+RO)
       * (+RO)
    }


    RW_vect 0x20000000
    {
        int.o (.vect_table_base)
    }
    rw_sysload +0                  
    {                                
        * (+RW)                     
    }


    zi_sysload +0
    {
        * (+ZI)
    }


    handle_msp 0x20010000 EMPTY -0x400 0x20010000lm3s8962 ram末地址+1
    {
    }
}
release.scat的修改也类似,这里不再重复了。
需要移植djyos到其他cm3芯片时,只要不涉及到片外扩展内存,照章办理即可。

字数限制,待续……

相关帖子

沙发
djyos|  楼主 | 2009-10-10 08:52 | 只看该作者
本帖最后由 djyos 于 2009-10-10 08:58 编辑

2.      工程初始化和配置文件
也就是指appinit.c和配置文件config-prj.h两个文件,appinit.c在目录:
djyos\si\arch\cortex-m3\board\DIYstm32\appinit 中。config-prj.h在目录:
djyos\si\prj-lm3s-ekk8962\include 中。
config-prj.h是一个工程配置文件,其内容主要包括三个方面:
1、  配置目标硬件设置,比如主频,中断数量等,比如:
#define M   1000000
#define cn_mclk         (50*M)     //主频
#define cn_fclk         cn_mclk     //cm3自由运行外设时钟
移植者按照自己目标硬件和工程需求来确定这些配置。
2、  内核运行相关的常量,比如tick周期时间,允许登记的事件类型上限,允许同时存在的、未处理完成的事件条数等。比如:
#define cn_tick_ms      1       //操作系统内核时钟脉冲长度,以毫秒为单位。
#define cn_tick_hz      1000    //内核时钟频率,单位为hz
#define cn_fine_us      1       //操作系统内核精密时钟脉冲长度,以微秒为单位。
                             //它表示从上一次tick脉冲开始至今流逝的时间量。
#define cn_fine_hz      1000000 //内核精密时钟频率,是cn_fine_us的倒数。
OS的内存需求与这些配置密切相关,应该按目标应用和硬件实配内存数量来确定这些配置。
3、  组件裁剪,djyos可裁剪组件的去留就是在这里实现的。例如:
#define cfg_keypad          1   //是否包含键盘驱动
#define cfg_djyfs            0   //是否包含文件系统
#define cfg_djyfs_flash       0   //是否包含flash文件系统的驱动
把你的工程中需要的组件定义为1,不需要的定义为0就可以了。
appinit.c则按照前述配置,决定是否调用可裁剪模块的初始化函数。例如:
#if cfg_keypad == 1
    module_init_keyboard();
#endif
3.      时钟初始化
虽然时钟初始化是cpu启动代码的一部分,但它是用c语言写成的,在initcpu.c中。时钟初始化函数cpu_init不长,全部列出如下:
void cpu_init(void)
{
    switch(pg_scb_reg->CPUID)
    {
        case cn_revision_r0p0:break;    //市场没有版本0的芯片
        case cn_revision_r1p0:
            pg_scb_reg->CCR |= bm_scb_ccr_stkalign;
            break;
        case cn_revision_r1p1:
            pg_scb_reg->CCR |= bm_scb_ccr_stkalign;
            break;
        case cn_revision_r2p0:break;    //好像没什么要做的
    }
    if(((pg_sysctl_reg->RCC & cn_rcc_check_msk ) == cn_rcc_test_value )
        &&((pg_sysctl_reg->RCC2 & (u32)cn_rcc2_check_msk ) == (u32)cn_rcc2_test_value ))
    {
            return ;    //时钟已经初始化好
    }
    //开始初始化时钟
    //step1:旁路pll和系统时钟分频器
    bb_sysctl_rcc_bypass = 1;
    bb_sysctl_rcc_ensysdiv = 0;
    bb_sysctl_rcc2_bypass2 = 1;


    //step2:设置rccrcc2寄存器
    pg_sysctl_reg->RCC = cn_rcc_set_step2;
    pg_sysctl_reg->RCC2 = (u32)cn_rcc2_set_step2;
   
    pg_sysctl_reg->RCC = cn_rcc_set_step3;
    pg_sysctl_reg->RCC2 = (u32)cn_rcc2_set_step3;


    //step3:等待pll ready
    while(bb_sysctl_ris_plllris == 0);
   
    //step4:接通pll和系统时钟分频器
    bb_sysctl_rcc_bypass = 0;
    bb_sysctl_rcc2_bypass2 = 0;
}
这里首先检查cpu版本,根据cpu版本做了相应的设置,代码很简单。
    接下来是时钟初始化,单片机经常用在实时控制中,这种系统要求快速启动,尤其是系统灾难恢复时,更加要求缩短系统失效时间。有硬件经验的人都知道,晶体起振和PLL稳定是需要比较长的时间的,在灾难恢复中,灾难事件往往没有破坏系统的时钟部分,这种情况就无需重新初始化时钟,以节约时间。所以在代码的开头,我们首先判断时钟状态,也就是判断RCCRCC2两个寄存器中跟主时钟相关的位值是否等于我们的设定值。如果正常的话,则直接返回,否则把他们复位后再重新初始化。
初始化的过程非常简单,就是按照lm3sdatasheet6.2节的1~5步设置寄存器即可,设置完了直接就可以工作了。

使用特权

评论回复
板凳
djyos|  楼主 | 2009-10-10 08:53 | 只看该作者
本帖最后由 djyos 于 2009-10-10 08:59 编辑

4.      固件库与位带操作
    移植操作系统,就不得不跟cpu的时钟发生器和外设模块打交道,得赞一下流明cm3的外设设计,功能简洁实用,寄存器配置合理,方便易用,尤其是文档,每个功能模块都有“初始化和配置”一节,英文版叫“Initialization and Configuration”,step by step地教你如何初始化和使用相关外设,真是体贴到家了,用起来很是省心。
    美中不足的是,每个芯片的datasheet都只讲本芯片的外设,没有一个归纳性的文档,每篇datasheet都重复许多相同的内容,但想获得全系列的资料又不得,这点还是stm32想得周到。此次移植,虽然以lm3s8962为目标板,但移植OS,或者一个有长远计划的开发项目,当然希望自己的代码能兼容更多的芯片。比如uart,不同的lm3s芯片,1~3个串口的都有,那么移植OS,就应该考虑全部3uart,而不是lm3s89622个。要考虑第三个uart,就要找到它的寄存器地址,你可以在流明的芯片选择表中找一个有3串口的芯片的datasheet来看。这里我介绍大家一个捷径,以uart为例,安装流明的库后,在C:\DriverLib(安装目录)中,找到"hw_uart.h"文件并打开,里面定义了lm3s所有型号可能存在的串口的寄存器定义,其他设备亦照章办理即可。用相同的方法,djyos定义了8fpio端口,而不是lm3s89627个。
大家读了djyos的代码后就会发现,djyos并没有使用流明提供的外设库,stm32版本也一样。我并不是排斥固件库,毕竟它是厂商优化并测试过的,不排除以后写usb或者网络协议的时候会使用固件库。嵌入式编程中,大多数时间是花在读外设使用说明书以及设计程序上,编写外设寄存器读写函数和宏的时间其实很少,所以使用固件库节省的时间其实是很有限的。但使用固件库的副作用也很明显,因为每一个系统都有其编码风格,作为希望在许多平台间移植的操作系统,应该在不同平台间保持风格一致。而使用厂家的外设库,你可能就要将就它的编码风格,不同厂家的编码风格是不同的,同属cortex-m3LuminaryST,库的风格截然不同。所以,djyos不使用流明的外设库,但库源码是一份不可多得的参考资料。

嵌入式系统可能要控制有严格时序要求的外设,这时候,IO语句的高效就非常重要了,cortex-m3有位带功能,可以帮助我们实现高效的IO控制。外设的控制寄存器中,大多数控制功能是由寄存器中的某一个bit完成的,正是cortex-m3位带功能大显身手的地方。下面以uart外设的LCRH寄存器为例说明一下djyos利用位带的方法。
uart.h文件中,有如下的红定义:
#define cn_uart_lcrh_set        ((0<<bo_uart0_lcrh_brk)\
                                             +(0<<bo_uart0_lcrh_pen)\
                                             +(0<<bo_uart0_lcrh_stp2)\
                                             +(1<<bo_uart0_lcrh_fen)\
                                             +(3<<bo_uart0_lcrh_wlen)\
                                             +(0<<bo_uart0_lcrh_sps))
//uart0 LCRH寄存器位定义
#define bb_uart0_lcrh_base  (0x42000000 + 0xc02c*32)            //位带基址
#define bb_uart0_lcrh_brk   (*(vu32*)(bb_uart0_lcrh_base+4*0))
#define bo_uart0_lcrh_brk   0   //1=当前字符发送完后,发送终止位
#define bb_uart0_lcrh_pen   (*(vu32*)(bb_uart0_lcrh_base+4*1))
#define bo_uart0_lcrh_pen   1   //1=使能奇偶校验
#define bb_uart0_lcrh_eps   (*(vu32*)(bb_uart0_lcrh_base+4*2))
#define bo_uart0_lcrh_eps   2   //0=奇校验,1=偶校验
#define bb_uart0_lcrh_stp2  (*(vu32*)(bb_uart0_lcrh_base+4*3))
#define bo_uart0_lcrh_stp2  3   //1=发送两个停止位
#define bb_uart0_lcrh_fen   (*(vu32*)(bb_uart0_lcrh_base+4*4))
#define bo_uart0_lcrh_fen   4   //1=fifo使能
#define bm_uart0_lcrh_wlen  0x00000060
#define bo_uart0_lcrh_wlen  5   //字长,0~3=5~8
#define bb_uart0_lcrh_sps   (*(vu32*)(bb_uart0_lcrh_base+4*6))
#define bo_uart0_lcrh_sps   7   //必须和pen位相同


bb_开头的宏定义,定义了单个bit成员的位带地址指针,代码中这样使用:
    bb_uart0_ctl_en = 0;        //停止uart0模块
bm_开头的宏,定义了多个bit组成的成员的掩码。
bo_开头的宏,定义了bit成员的字内偏移量。
这三个宏,独立使用和组合使用,能完成各种寄存器控制,例如定义LRCH寄存器的初始值cn_uart_lrch_set
cn_uart_lcrh_set定义了LCRH寄存器的初始设置值,用于初始化文件中:
    pg_uart0_reg->LCRH = cn_uart_lcrh_set;  

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

60

主题

454

帖子

1

粉丝