移植步骤详解
下面就以MDK 4.72为开发环境,详细说明Hello China内核向STM32的移植过程。MDK 4.72评估版只支持32K代码的编译,这对Hello China的内核来说,裁剪掉一些非核心功能,也足够了。如果您希望体验更多功能,请使用非评估版。您可以花钱买,也可以通过其它途径获得,具体不细说,你懂的。
首先建立一个新的项目,注意要指定一个项目所在目录,并选择合适的STM32芯片。我选择的是STM32F103R8,如下图:
点击OK后,MDK会提示是否拷贝startup_stm32f10x_md.S到项目中,选择“是”。这样MDK会自动生成一个启动文件,到所选目录下。
假设项目所在的目录是HXOS03,则进入该目录,创建下列几个文件夹:
其中HXOS存放Hello China操作系统源文件,Obj&Lst存放MDK编译生成的过程和目标文件,Startup存放启动文件(把上图中startup_stm32f10x_md.s拷贝到Startup文件夹),User则存放用户应用程序源文件。
然后把Hello China V1.76的源文件拷贝到HXOS目录下。下面是拷贝之后的结构(HXOS目录下所包含的文件夹):
其中有几个文件夹,在STM32下是不需要的,可以把它删除掉。主要有fs/shell两个目录。其中fs存放的是文件系统代码,我们的移植目标板上没有存储系统,故不需要该文件夹内容。Shell是面向PC的一个命令行界面,在STM32下也不需要。
再进入MDK,建立与上述目录基本对应的文件夹架构,如下图:
然后把文件夹中的文件,逐一添加到项目中。需要注意的是,MDK只支持二级目录,即不能继续在HXOS目录下再创建子目录。由于HXOS存放的是Hello China源代码,分好几个子目录存放,因此在添加到项目中的时候,实际目录结构就消失了,所有文件都统一显示在MDK的HXOS目录下。这有点不方便,主要是寻找某个文件的时候,但好消息是,操作系统内核需要修改的地方不多,而且一劳永逸,这个不方便也可以克服。
完成文件的添加后,在MDK中对项目做一些配置。打开项目选项对话框(为了简便,就不贴图了。MDK中按下ALT+F7键,即可看到),主要有以下几点设置:
1. 把编译生成的中间和目标文件,以及list文件,存放到创建的Obj&Lst目录下。选择对话框中的output和listing选项,分别点击“select folder for objects…”和“select folder for listings…”,即可设置;
2. 选择项目选项对话框中的“C/C++“页签,在”Define“编辑框中,输入”STM32F10X_MD“。这是一个预定义宏,告诉编译器,我们的目标STM32平台是中等密度的(因为我们选择的目标是STM32F103RB)。至于什么是STM32 MD,HD,LD等,请参阅相关资料;
配置完成后,点击OK。
接下来,需要修改几个地方的代码,主要有:
1. 启动文件中,需要增加链接中断的代码。打开startup_stm32f10x_md.s文件,找到下列代码:
………
USART1_IRQHandler
USART2_IRQHandler
USART3_IRQHandler
EXTI15_10_IRQHandler
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler
B .
ENDP
ALIGN
把上述代码中的“B .“一行删除,修改为下列代码(斜体部分):
………
EXTI15_10_IRQHandler
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler
IMPORT Int_Entry_Wrapper
LDR R0,=Int_Entry_Wrapper
BX R0
ENDP
ALIGN
这样就实现了硬件中断和Hello China操作系统中断机制的链接。
2. 内存配置。找到HXOS目录下的mem_scat.c文件,找到下列代码:
__MEMORY_REGION SystemMemRegion[] = {
{(LPVOID)KMEM_ANYSIZE_START_ADDRESS,0x00100000},
{(LPVOID)(KMEM_ANYSIZE_START_ADDRESS+ 0x00100000),0x00100000},
{(LPVOID)(KMEM_ANYSIZE_START_ADDRESS+ 0x00200000),0x00100000},
{(LPVOID)(KMEM_ANYSIZE_START_ADDRESS+ 0x00300000),0x00100000},
//Please add more memoryregions here.
//The last entry must beNULL and zero,to indicate the end of this array.
{NULL,0}
};
修改为下列内容:
__MEMORY_REGION SystemMemRegion[] = {
{(LPVOID)0x20003000,0x00002000},
//Please add more memoryregions here.
//The last entry must beNULL and zero,to indicate the end of this array.
{NULL,0}
};
这告诉操作系统的内存管理机制,空闲内存是从0x20003000开始,长度是8K。这里直接使用了固定编码的方式,告诉操作系统空闲内存起始地址。这种处理方式是不合适的,因为如何确定空闲内存起始地址,是个问题。这里的0x20003000是通过分析编译输出的map文件,推算出来的。但是要实现自动的空闲内存设定,则需要修改编译器的散列文件(sct文件),比较复杂。因此暂时先用这种方式替代。如有复杂的场景,再考虑修改sct文件来实现空闲内存的自动界定。如果读者朋友有更好的方法,也欢迎反馈。
3. 接下来是最后一步,就是修改config.h文件。这是操作系统的核心配置文件,相关功能模块都在这个文件中进行配置。具体来说,注释掉某些宏定义,并打开针对STM32的一些宏定义。下列是修改之后,该文件的大致内容(删除了注释):
#define __CONFIG_H__ //Include switch.
#define __CFG_CPU_LE
//CPU types,the following definitions are exclusive.
//#define __I386__
//#define __ARM7__
//#define __ARM9__
//#define __ARM11__
#define __STM32__
#define SYSTEM_TIME_SLICE 50
#define MIN_STACK_SIZE 128
#define MAX_INTERRUPT_VECTOR 128
#define DEFAULT_STACK_SIZE 0x00000400 //1k space for kernel threadstack.
//#define __CFG_SYS_IS
//#define __CFG_SYS_VMM
//#define __CFG_SYS_BM
#define __CFG_SYS_MMFBL
//#define __CFG_SYS_MMTFA
#define __CFG_SYS_DDF
//Include CPU statistics functions in OS.
//#define __CFG_SYS_CPUSTAT
//#define __CFG_SYS_SHELL
//#define __CFG_SYS_CONSOLE
//#define __CFG_DRV_IDE
//#define __CFG_DRV_COM
//Include USART driver in OS,specific for STM32 or ARM platform.
#define __CFG_DRV_USART
//#define __CFG_DRV_MOUSE
//#define __CFG_DRV_KEYBOARD
//#define __CFG_FS_FAT32
//#define __CFG_FS_NTFS
//#define __CFG_FS_RAM
//#define __CFG_FS_FLASH
//#define __CFG_NET_IPV4
//#define __CFG_NET_IPV6
#define __CFG_USE_EOS
//User entry point thread's priority if used as EOS.
#define __HCNMAIN_PRIORITY PRIORITY_LEVEL_NORMAL
//User entry point thread's name.
#define __HCNMAIN_NAME "HCN_Main"
从中可以看出Hello China操作系统的裁剪配置思路。只要把对应的宏定义打开,相应的功能就包含在Hello China内核里面了。注释掉之后,相应的功能就被裁剪掉,内核尺寸就会变小。
完成上述所有修改和配置之后,直接在MDK中按F7键,即可编译链接了。会有一些告警提示,忽略即可。如无意外,应该会编译成功,生成目标文件。
这时候,可以使用MDK内置的模拟器进行调试。由于我们实现了USART1的驱动程序,也随之实现了一个简单的交互界面(在user文件夹下的usermain.c文件中),因此如果用串口链接STM32的USART1接口,输入“m”,即可看到内存使用情况的统计输出。
下面介绍一个简单的调试方法。在进入调试状态后(选择“debug”菜单,选择“start/stop debug session”选项),在MDK左下角弹出的command窗口中,输入下列命令:
mode com3 115200,0,8,1
assign com3 <s1in> s1out
如下图:
即可把PC机的COM3接口与MDK所虚拟的STM32的USART1链接起来(波特率是115200,无校验,8位数据位,1位停止位)。
这时候使用诸如VSPD等串口虚拟软件,把COM3(链接了STM32的USART1)和COM4环接起来,则使用诸如超级终端等串口软件(指定COM4串口,并配置与COM3相同的参数),即可与STM32虚拟机交互了。下图是交互的输出:
用户编程模型
Hello China在初始化完成之后,如果发现定义了__CFG_USE_EOS宏,则试图创建一个核心线程,入口函数名称是_HCNMain。用户应用程序代码需要在这个函数中实现。因此,在成功移植Hello China之后,用户需要在user目录下,创建自己的源文件,并实现_HCNMain函数。这个函数与缺省的main函数一样,是用户功能代码的入口。在_HCNMain函数中,用户可以调用Hello China提供的一系列系统服务,比如创建核心线程,申请/释放内存,读写文件等。下面是系统附带的一个实现,这个实现很简单,就是不断读取USART1的输入,并回显出来。如果发现输入是“m”,则调用ShowMemory函数,输出内存分配情况。
下面是_HCNMain函数的源代码,贴到这里供参考。如果用户想实现自己的独特功能,可以直接修改_HCNMain函数:
DWORD _HCNMain(LPVOID pData)
{
__COMMON_OBJECT* hUsart =NULL;
DWORD dwWriteLen = 0;
CHAR chCmd;
CHAR strInfo[64];
hUsart =IOManager.CreateFile(
(__COMMON_OBJECT*)&IOManager,
"\\.\USART1",
0,
0,
NULL);
if(NULL == hUsart)
{
SER_PutString("_HCNMain:Can not open USART object. ");
return 0;
}
SER_PutString("_HCNMain:Open device USART successfully. ");
while(TRUE)
{
if(IOManager.ReadFile(
(__COMMON_OBJECT*)&IOManager,
hUsart,
1,
&chCmd,
NULL))
{
IOManager.WriteFile(
(__COMMON_OBJECT*)&IOManager,
hUsart,
1,
&chCmd,
&dwWriteLen);
if('m' ==chCmd)
{
ShowMemory(hUsart);
}
}
}
//return 1;
}
从上述代码中可以看出,_HCNMain可以调用Hello China操作系统的API,来实现相应功能。对物理设备的操作也大大简化,所有设备,都是按照文件的操作接口来统一操作。这样的好处是,不用关心底层的驱动程序实现,只需要聚焦应用的实现即可。这种模式适合大型软件的开发,可以把整个任务分为两个部分:驱动代码和应用代码,并分别由不同的团队承接,相互不干扰,有利于提升开发效率。
其它相关问题的说明
接下来,对操作系统移植过程中的其它相关问题做一番说明。首先是CPU复位后的执行入口点问题。在startup文件中,MDK定义了一个入口点函数Reset_Handler,Hello China的实现方式是,在osadapt.s中重新定义一个Reset_Handler函数,替代MDK定义的这个。因为MDK在定义的时候,使用了[weak]修饰,因此只要在osadapt.S中定义的Reset_Handler不使用weak修饰即可。这样CPU复位后,将跳转到osadapt.S文件定义的Reset_Handler处开始执行。这时候会首先调用SystemInit函数,这个函数是一个缺省实现,从MDK开发环境附带的例子(位于KeilARMExamples目录下)中拷贝过来的。SystemInit函数完成系统时钟的配置等工作,然后再跳转到MDK定义的__main函数。这个函数内嵌了一些全局数据重定位代码,把全局变量搬迁到0x200000000开始处(即RAM开始处),然后再调用main函数。Main函数在os_Entry.c文件中实现,调用了操作系统初始化函数_OS_Entry。_OS_Entry完成Hello China操作系统的初始化,并创建用户线程_HCNMain。_HCNMain得到调度的时候,就正式进入用户程序了。需要说明的是,在main函数中,调用_OS_Entry之前,首先对Systick进行了初始化,这是产生系统tick的机制。
USART驱动程序在Hello China初始化过程中被加载。驱动程序首先初始化USART1相关的控制器,开启USART1功能,然后调用ConnectInterrupt函数,把USART1的中断处理函数链接到Hello China的中断处理机制。这样USART1就可以被用户程序通过文件API访问了。具体例子,可参考上述_HCNMain的实现案例。
通过合适的裁剪,可以把Hello China的内核大小控制在10~20K之间。当然,如果进一步增加其它功能,比如FS,网络,shell,则内核尺寸会超过40K。如果进一步增加GUI,则需要额外内存支持了。
最后,再把Hello China的扩展机制总结一下,因为Hello China的应用目标是物联网操作系统,需要面对各种规格的硬件配置,因此操作系统的伸缩性是非常关键的。Hello China的伸缩性,大致表现在下列方面:
1. 内核线程的数量无限制,可以随便创建,仅受限于物理内存大小;
2. 采用统一的文件API,访问文件和设备。这样可以把用户功能代码和底层的硬件驱动程序隔离开,方便大型软件的开发;
3. 采用链式中断机制,对中断的数量无限制,而且可以动态添加和删除中断处理程序;
4. 内嵌灵活扩展的驱动程序管理框架,要增加驱动程序,只需要按照Hello China的驱动程序框架编写代码,然后在驱动程序加载列表中注册一项即可。操作系统在初始化过程中,会加载驱动程序加载列表中的所有驱动程序;
5. 内存管理机制灵活,可以在mem_scat.c文件中添加多个内存块,比如外部RAM等,实现统一管理;
6. 实现统一的文件系统框架,用户通过统一的API接口访问文件,不管文件的底层存储格式是什么。同时,文件系统可动态添加或删除。在动态添加存储设备的时候,文件系统会得到通知,如果是可识别的文件系统,会实时挂接到系统中,呈现给用户。这种机制十分适合外设丰富的应用场景。
总之,通过添加驱动程序,核心线程,文件系统/GUI/网络功能等模块,可以快速扩展HelloChina的功能,而且这种功能对用户来说是完全透明的。相反,通过裁减不需要的功能,也可以把Hello China的尺寸控制在很小的范围(小于10K)。这样一种可伸缩性的特征,符合物联网操作系统多样化的需求。
欢迎对物联网操作系统感兴趣的同仁投入,继续开发。物联网已经被广泛看好,支撑物联网发展的操作系统,必定会越来越受到重视。对参与者个人来说,物联网操作系统正处于开发的初始阶段,这个时候参与进来,持续跟进甚至主导,逐渐积累,在该领域成为权威,会比其它领域更加容易,效果也会更好。物联网操作系统给所有系统软件爱好者提供了一个难得的机会,真诚希望我们都能够抓住。
同时也欢迎物联网领域的企业或单位,能够应用Hello China操作系统。我们会提供更加周到的服务。
任何问题,请联系:
QQ/微信:89007638
Email:garryxin@gmail.com