Hello China操作系统STM32移植指南(二)

移植步骤详解

下面就以MDK 4.72为开发环境,详细说明Hello China内核向STM32的移植过程。MDK 4.72评估版只支持32K代码的编译,这对Hello China的内核来说,裁剪掉一些非核心功能,也足够了。如果您希望体验更多功能,请使用非评估版。您可以花钱买,也可以通过其它途径获得,具体不细说,你懂的。

首先建立一个新的项目,注意要指定一个项目所在目录,并选择合适的STM32芯片。我选择的是STM32F103R8,如下图:

Hello China操作系统STM32移植指南(二)

点击OK后,MDK会提示是否拷贝startup_stm32f10x_md.S到项目中,选择“是”。这样MDK会自动生成一个启动文件,到所选目录下。

假设项目所在的目录是HXOS03,则进入该目录,创建下列几个文件夹:

Hello China操作系统STM32移植指南(二)

其中HXOS存放Hello China操作系统源文件,Obj&Lst存放MDK编译生成的过程和目标文件,Startup存放启动文件(把上图中startup_stm32f10x_md.s拷贝到Startup文件夹),User则存放用户应用程序源文件。

然后把Hello China V1.76的源文件拷贝到HXOS目录下。下面是拷贝之后的结构(HXOS目录下所包含的文件夹):

Hello China操作系统STM32移植指南(二)

其中有几个文件夹,在STM32下是不需要的,可以把它删除掉。主要有fs/shell两个目录。其中fs存放的是文件系统代码,我们的移植目标板上没有存储系统,故不需要该文件夹内容。Shell是面向PC的一个命令行界面,在STM32下也不需要。

再进入MDK,建立与上述目录基本对应的文件夹架构,如下图:

Hello China操作系统STM32移植指南(二)

然后把文件夹中的文件,逐一添加到项目中。需要注意的是,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

如下图:

Hello China操作系统STM32移植指南(二)

即可把PC机的COM3接口与MDK所虚拟的STM32的USART1链接起来(波特率是115200,无校验,8位数据位,1位停止位)。

这时候使用诸如VSPD等串口虚拟软件,把COM3(链接了STM32的USART1)和COM4环接起来,则使用诸如超级终端等串口软件(指定COM4串口,并配置与COM3相同的参数),即可与STM32虚拟机交互了。下图是交互的输出:

Hello China操作系统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.\r\n");

return 0;

}

SER_PutString("_HCNMain:Open device USART successfully.\r\n");

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开发环境附带的例子(位于Keil\ARM\Examples目录下)中拷贝过来的。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

上一篇:关于STM32位带操作随笔


下一篇:104. Maximum Depth of Binary Tree(C++)