lm3s8962是一款cortex-m3的mcu,得益于cortex-m3处理器的架构优势,lm3s8962移植过程相当简单,需要修改的地方只有:
1、 链接配置文件,在keil MDK中就是debug.scat和release.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.scat和release.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尺寸,lm3s8962有256K 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 ;0x20010000是lm3s8962 的ram末地址+1。
{
}
}
release.scat的修改也类似,这里不再重复了。
需要移植djyos到其他cm3芯片时,只要不涉及到片外扩展内存,照章办理即可。
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:设置rcc和rcc2寄存器
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稳定是需要比较长的时间的,在灾难恢复中,灾难事件往往没有破坏系统的时钟部分,这种情况就无需重新初始化时钟,以节约时间。所以在代码的开头,我们首先判断时钟状态,也就是判断RCC和RCC2两个寄存器中跟主时钟相关的位值是否等于我们的设定值。如果正常的话,则直接返回,否则把他们复位后再重新初始化。
初始化的过程非常简单,就是按照lm3s的datasheet第6.2节的1~5步设置寄存器即可,设置完了直接就可以工作了。
4. 固件库与位带操作
移植操作系统,就不得不跟cpu的时钟发生器和外设模块打交道,得赞一下流明cm3的外设设计,功能简洁实用,寄存器配置合理,方便易用,尤其是文档,每个功能模块都有“初始化和配置”一节,英文版叫“Initialization and Configuration”,step by step地教你如何初始化和使用相关外设,真是体贴到家了,用起来很是省心。
美中不足的是,每个芯片的datasheet都只讲本芯片的外设,没有一个归纳性的文档,每篇datasheet都重复许多相同的内容,但想获得全系列的资料又不得,这点还是stm32想得周到。此次移植,虽然以lm3s8962为目标板,但移植OS,或者一个有长远计划的开发项目,当然希望自己的代码能兼容更多的芯片。比如uart,不同的lm3s芯片,1~3个串口的都有,那么移植OS,就应该考虑全部3个uart,而不是lm3s8962的2个。要考虑第三个uart,就要找到它的寄存器地址,你可以在流明的芯片选择表中找一个有3串口的芯片的datasheet来看。这里我介绍大家一个捷径,以uart为例,安装流明的库后,在C:\DriverLib(安装目录)中,找到"hw_uart.h"文件并打开,里面定义了lm3s所有型号可能存在的串口的寄存器定义,其他设备亦照章办理即可。用相同的方法,djyos定义了8个fpio端口,而不是lm3s8962的7个。
大家读了djyos的代码后就会发现,djyos并没有使用流明提供的外设库,stm32版本也一样。我并不是排斥固件库,毕竟它是厂商优化并测试过的,不排除以后写usb或者网络协议的时候会使用固件库。嵌入式编程中,大多数时间是花在读外设使用说明书以及设计程序上,编写外设寄存器读写函数和宏的时间其实很少,所以使用固件库节省的时间其实是很有限的。但使用固件库的副作用也很明显,因为每一个系统都有其编码风格,作为希望在许多平台间移植的操作系统,应该在不同平台间保持风格一致。而使用厂家的外设库,你可能就要将就它的编码风格,不同厂家的编码风格是不同的,同属cortex-m3的Luminary和ST,库的风格截然不同。所以,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;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
看本文件请参考《都江堰操作系统与嵌入式系统设计》第15章,该书在www.djyos.com可以下载。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
上面是DJYOS的移植方法,有兴趣的可以试试,DJYOS还是一个很优秀的操作系统的