LIteOS学习笔记-7LiteOS启动流程与编译流程
LiteOS启动流程
1. 启动方式
目前对于多任务的实时操作系统中有两种比较流行的启动方式。
- 一种是在main函数中进行硬件初始化以及系统初始化之后先将所有任务创建完成之后,一起启动;
- 另一种方式是在main函数中进行硬件初始化以及系统初始化之后创建任务之后直接启动调度器,然后在启动任务中创建各种应用任务,当所有任务创建成功后启动任务再把自己删除。
在写裸机程序的时候我们都是在一个while无限循环内创建任务,执行任务。通过中断来响应操作。
操作系统内是如何启动的我们通过分析Lite OS来了解。
2. 启动流程
任何程序都有一个入口函数main,这里依据智慧农业的main函数进行分析。
int main(void)
{
UINT32 uwRet = LOS_OK;
HardWare_Init();
uwRet = LOS_KernelInit();
if (uwRet != LOS_OK)
{
return LOS_NOK;
}
extern void shell_uart_init(int baud);
shell_uart_init(115200);
if(!link_test())
{
LCD_ShowString(30, 170, 240, 16, 16, "Connect NET OK");
}
(void)LOS_Start();
return 0;
}
硬件初始化
首先进行了和系统无关的硬件初始化HardWare_Init()
,主要完成时钟、GPIO、UART、LCD等的初始配置。
VOID HardWare_Init(VOID)
{
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
MX_GPIO_Init();
// MX_USART1_UART_Init();
//MX_I2C1_Init();
//MX_SPI2_Init();
dwt_delay_init(SystemCoreClock);
LCD_Init();
LCD_Clear(BLACK);
POINT_COLOR = GREEN;
LCD_ShowString(10, 50, 240, 24, 24, "Welcome to BearPi!");
LCD_ShowString(20, 90, 240, 16, 16, "BearPi-IoT Develop Board");
LCD_ShowString(20, 130, 240, 16, 16, "Powerd by Huawei LiteOS!");
LCD_ShowString(30, 170, 240, 16, 16, "Connecting NET......");
}
如果使用了I2C,SPI等系统外设也将初始化函数统统放进来就好,此处在笔记2已经详细讲过。
内核初始化
SDK支持多种系统,所以需要先在配置文件中使能LiteOS,在LOS_KernelInit()
函数中,进行任务,队列,互斥锁,信号量,内存管理等模块的初始化。
调试串口初始化
使用串口1进行调试,打印调试信息。shell_uart_init(115200)
此函数作用等同于裸机程序的MX_USART1_UART_Init()
尝试进行网络连接
link_test()函数内部其实创建了一个任务,任务的入口函数为link_main()
函数,此函数在.iotlink\sdk\IoT_LINK\iot_link
下的link_main.c
中定义。
static int link_test()
{
int ret = -1;
UINT32 uwRet = LOS_OK;
UINT32 handle;
TSK_INIT_PARAM_S task_init_param;
memset (&task_init_param, 0, sizeof (TSK_INIT_PARAM_S));
task_init_param.uwArg = (unsigned int)NULL;
task_init_param.usTaskPrio = 2;
task_init_param.pcName =(char *) "link_main";
task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)link_main;
task_init_param.uwStackSize = 0x1000;
uwRet = LOS_TaskCreate(&handle, &task_init_param);
if(LOS_OK == uwRet){
ret = 0;
}
return ret;
}
static int s_link_start = 0;
int link_main(void *args)
{
///< install the RTOS kernel for the link
if(s_link_start)
{
return -1;
}
s_link_start =1;
(void)osal_init();
LINK_LOG_DEBUG("linkmain:%s",linkmain_version());
#ifdef CONFIG_STIMER_ENABLE
#include <stimer.h>
stimer_init();
#endif
#ifdef CONFIG_SHELL_ENABLE
#include <shell.h>
shell_init();
#endif
/* add loader code here */
#ifdef CONFIG_OTA_ENABLE
#include <ota_init.h>
ota_init();
#endif
///< install the driver framework
#ifdef CONFIG_DRIVER_ENABLE
#include <driver.h>
///< install the driver framework for the link
(void)los_driv_init();
#endif
///< install the at framework
#ifdef CONFIG_AT_ENABLE
#include <at.h>
(void)at_init();
#endif
///< install the cJSON, for the oc mqtt agent need the cJSON
#ifdef CONFIG_CJSON_ENABLE
#include <cJSON.h>
cJSON_Hooks hook;
hook.free_fn = osal_free;
hook.malloc_fn = osal_malloc;
cJSON_InitHooks(&hook);
#endif
// TCPIP PROTOCOL /
#ifdef CONFIG_TCPIP_AL_ENABLE
#include <sal.h>
(void)link_tcpip_init();
#endif
// DTLS PROTOCOL /
#ifdef CONFIG_DTLS_AL_ENABLE
#include <dtls_al.h>
(void)dtls_al_init();
#endif
// MQTT PROTOCOL /
#ifdef CONFIG_MQTT_AL_ENABLE
#include <mqtt_al.h>
mqtt_al_init();
#endif
// LWM2M PROTOCOL /
#ifdef CONFIG_LWM2M_AL_ENABLE
#include <lwm2m_al.h>
(void)lwm2m_al_init();
#endif
OC LWM2M /
#ifdef CONFIG_OCLWM2M_ENABLE
#include <oc_lwm2m_al.h>
oc_lwm2m_init();
#endif
#ifdef CONFIG_LINKDEMO_ENABLE
extern int standard_app_demo_main(void);
(void) standard_app_demo_main();
#endif
return 0;
}
可以看到,link_main()函数中有很多宏定义,根据宏来选择是否运行该模块的函数。这些宏在.config
和iot_config.h
文件中定义。主要实现了网络通信相关模块的初始化操作。并在最后调用了standard_app_demo_main()
函数,该函数在我们自定义工程文件中作为自建任务的启动函数。
启动任务调度
调用LOS_Start( )
启动LiteOS系统,此函数与硬件初始化函数均在los_init.c
文件中。
/*****************************************************************************
Function : LOS_Start
Description : Task start function
Input : None
Output : None
Return : LOS_OK on success or error code on failure
*****************************************************************************/
LITE_OS_SEC_TEXT_INIT UINT32 LOS_Start(VOID)
{
UINT32 uwRet;
#if (LOSCFG_BASE_CORE_TICK_HW_TIME == NO)
uwRet = osTickStart();
if (uwRet != LOS_OK)
{
PRINT_ERR("osTickStart error\n");
return uwRet;
}
#else
extern int os_timer_init(void);
uwRet = os_timer_init();
if (uwRet != LOS_OK)
{
PRINT_ERR("os_timer_init error\n");
return uwRet;
}
#endif
#if (LOSCFG_LIB_LIBC_NEWLIB_REENT == YES)
extern VOID osTaskSwitchImpurePtr(VOID);
osTaskSwitchImpurePtr();
#endif
LOS_StartToRun();
return uwRet;
}
该函数启动系统时钟与定时器,开启任务转换。这里的时钟配置,是Lite OS系统中时钟配置。类似于大家常用的桌面操作系统的时钟概念。最后调用LOS_StartToRun( )
启动Lite OS 运行。LOS_StartToRun( )
函数在\.iotlink\sdk\IoT_LINK\iot_link\os\liteos\arch\arm\arm-m\armv6-m\gcc\los_dispatch.c
中实现,是汇编形式的函数,主要作用如下。
/***************************************************************
* Function:
* VOID LOS_StartToRun(VOID);
* Description:
* Start the first task, which is the highest priority task in the priority queue.
* Other tasks are started by task scheduling.
**************************************************************/
/**
* Jump directly to the default PC of g_stLosTask.pstRunTask, the field information
* of the main function will be destroyed and will never be returned.
*/
通过这两段注释分析,第一段注释表明了该函数的作用,将优先级最高的任务作为第一个任务进行执行;第二段注释在该函数结尾处,表示,第一个任务执行完成后,直接跳转到PC of g_stLosTask.pstRunTask
处,这里的PC是程序计数器,里面存储着CPU下一次要执行的程序指令的地址。main函数现场将销毁并不再返回。
在osTaskInit()函数中,对g_stLosTask.pstRunTask
进行了赋值。g_stLosTask.pstRunTask
中存储了要执行的任务相关信息,即任务控制块(task control block structure即LOS_TASK_CB中的内容)。
在汇编语言中,将g_stLosTask.pstRunTask
中的相关信息存储到STM32L431的相关的工作寄存器中,例如R1工作寄存器。这样程序就可以执行任务了。而任务的相关信息、入口函数等都可以用C语言来编写。至此,程序可以绕开main函数,执行操作系统中的多个任务了.
多个任务的调度,主要由LOS_IntLock,LOS_IntUnlock,LOS_IntRestore,osTaskScheldule,PendSV_Handler
这些函数实现。这些函数也是用汇编语言写的。
通过以上的分析,可以看出,在Lite OS在启动的时候,是从main函数中被调用开始,而在被调用的过程中,没有让PC指针再次跳转回main函数中,而是直接跳转到执行任务的命令的地址上去,从而开启了具有多任务执行能力的操作系统功能。
LiteOS编译流程
MakeFile
Makefile是一个名为GNU-Make软件所需要的脚本文件,该脚本文件可以指导Make软件控制arm-gcc等工具链去编译工程文件最终得到可执行文件,几乎所有的Linux发行版都内置了GNU-Make软件,VScode等多种IED也内置了Make程序。
/GCC目录下有一个Makefile,这个Makefile就是主Makefile,当我们在GCC目录下执行make指令时,就会执行all作为目标的这条指令,
#NOW DO THE BUILDING
all:$(BUILD_DIR)/$(TARGET).elf
-$(HEX) $< $(BUILD_DIR)/$(TARGET).hex
-$(BIN) $< $(BUILD_DIR)/$(TARGET).bin
依次执行依赖文件需要的命令
##########################LOAD THE SOURCES INCLUDES AND DEFINES#################
include ../.config
include $(SDK_DIR)/iot_link/iot.mk
include $(MAKEFILE_DIR)/project.mk
Makefile会根据.config中的信息来选择性的包含某些组件的Makefile。如下例子。
例一、在.config
中使能LiteOS
在.config
文件中有如下语句
CONFIG_LITEOS_ENABLE=y
# CONFIG_NOVAOS_ENABLE is not set
# CONFIG_LINUXOS_ENABLE is not set
# CONFIG_MACOS_ENABLE is not set
# CONFIG_NEW_OS is not set
在OS组件中的Makefile就会进行如下判断,因为CONFIG_LITEOS_ENABLE
被设置为了y,就将os/liteos/liteos_imp.mk
文件引用进来编译。
C_SOURCES += ${wildcard $(iot_link_root)/os/osal/*.c}
C_INCLUDES += -I $(iot_link_root)/os/osal
#now support "liteos" and "linux" "macos",please select one you like to use
ifeq ($(CONFIG_LITEOS_ENABLE), y)
include $(iot_link_root)/os/liteos/liteos_imp.mk
else ifeq ($(CONFIG_LINUXOS_ENABLE), y)
include $(iot_link_root)/os/linux/linux_imp.mk
else ifeq ($(CONFIG_MACOS_ENABLE), y)
include $(iot_link_root)/os/macos/macos_imp.mk
else ifeq ($(CONFIG_NOVAOS_ENABLE), y)
include $(iot_link_root)/os/novaos/novaos_imp.mk
endif
现在我们想知道LiteOS中调用了哪些文件直接看liteos_imp.mk
文件即可。文件在.iotlink\sdk\IoT_LINK\iot_link\os\liteos
中。
1、armv7-m内核相关部分
目前LiteOS支持三类内核的单片机,由上到下“armv7-m”、“armv6-m”和“riscv32。
这部分包含的文件都是与内核密切相关的文件,例如“起始文件”、“中断向量表”、“任务调度相关文件”,我们着重看一下armv7-m内核的文件。
liteos/arch/arm/arm-m/armv7-m/*.c
:添加liteos/arch/arm/arm-m/armv7-m/
目录中的全部.c文件编译(los_exc.c
文件该文件用于处理异常相关);
liteos/arch/arm/arm-m/armv7-m/gcc/*.c
:添加了los_dispatch.c
文件进行编译,该文件主要用于通过汇编语言快速完成一些系统相关操作,例如系统启动、任务调度等;
liteos/arch/arm/arm-m/armv7-m/gcc/los_startup.S
:添加了启动代码,这个代码使用汇编写的主要处理复位中断,设置栈顶指针,清除bss段等初始化操作;
arch_inc = -I $(iot_link_root)/os/liteos/arch/arm/arm-m/armv7-m
:表示以后寻找头文件可以到该目录下寻找。
2、armv7-m和armv6-m内核公共代码部分
ifeq ($(CONFIG_ARCH_CPU_TYPE), $(filter $(CONFIG_ARCH_CPU_TYPE), "armv7-m" "armv6-m"))
:这句话的意思是CONFIG_ARCH_CPU_TYPE
等于armv7-m或者armv6-m,那就将common和cmsis下面的文件和目录添加到编译列表中和头文件包含目录列表中。
3、所有架构内核的公共代码部分
这一部分就是所有架构的内核都能使用的的源码和寻找头文件的目录,根据目录意思,大概能得知,这一部分包含了内存管理、进程间通讯等模块。
例二、在.config
中使能AT组件
在.config
文件中有如下语句
CONFIG_AT_ENABLE=y
CONFIG_AT_DEVNAME="atdev"
CONFIG_AT_OOBTABLEN=6
CONFIG_AT_RECVMAXLEN=1024
CONFIG_AT_TASKPRIOR=10
AT组件中的Makefile文件就会判断,因为CONFIG_AT_ENABLE
被配置为了y,就将AT组件中的源文件添加进来编译,并将AT组件目录作为检索头文件的目录,并定义一个宏定义CONFIG_AT_ENABLE
=1,如下。
ifeq ($(CONFIG_AT_ENABLE),y)
AT_MODULE_SRC = ${wildcard $(iot_link_root)/at/*.c}
C_SOURCES += $(AT_MODULE_SRC)
AT_MODULE_INC = -I $(iot_link_root)/at
C_INCLUDES += $(AT_MODULE_INC)
AT_MODULE_DEF = -D CONFIG_AT_ENABLE=1
C_DEFS += $(AT_MODULE_DEF)
endif
以上就是LiteOS_Lab中Makefile运行的机制了,SDK中所有的Makefile文件都不需要也不能进行修改,只需要修改工程中的三个Makefile:.config(这个不用手动修改,可以通过图形化配置进行修改),Makefile(根据目标MCU修改MCU相关的参数即可,也就是MCU这个变量的值),project.mk(根据目标MCU修改、添加或删除库文件以及用户文件以及最后的C_DEFS变量即可)。
Kconfig
上述在进行make过程中出现了很多宏在.config文件中,这个文件是怎么来的呢?是通过使用Kconfig进行生成的。
主要生成两个重要的文件:
(1)config
文件,主要是配置iot.mk的内容,控制哪些组件参与编译,哪些组件不参与编译;
(2)iot_config.h
,被各个组件引用,决定组件编译的各种详细配置
iot_config.h
文件将被iot_link/iot_link_config.h
文件所引用。
可到IoT Studio设置中–>SDK配置下进行勾选配置。里边的选项OC Cloud、Network、OS等等都是基于kconfig显示出来的,如下图,其中某一项是否使能(就是蓝色的勾选框有没有选上),是基于.config
显示的。
Kconfig可以显示出很多配置选项,我们可以通过图形化选择,并点击“应用”,这时工程中就会生成一个.config
文件,其中包含了哪些组件需要使能等,在Makefile文件中,会有一条include语句引用.config
用于控制后面的编译。
在最新版本的SDK中kconfig分布在:.iotlink\sdk\IoT_LINK\iot_link
、当前开发板工程/kconfig、以及iot_link中的每个组件下都有一个独立的kconfig,这些kconfig调用顺序为:
创建工程流程
选择硬件平台和基于示例工程创建
选择硬件平台
IoT Studio会读取SDK中的.iotlink\sdk\IoT_LINK\targets
文件夹中的所有工程模板的文件夹名称。选择STM32L431_BearPi
;
选择示例工程
读取.iotlink\sdk\IoT_LINK\targets\STM32L431_BearPi\Demos
下文件夹下所有示例工程文件夹名称。
选择工程存放路径和填写工程名称
注意格式规范,路径不要有中文和特殊字符。
SDK设置
配置选项都是IoT Studio通过读取Kconfig显示出来的
SDK配置中已经配置好了,这些配置是哪里来的?现在就要开始介绍我们Demo文件夹中的defaults.sdkconfig,每一个在Demo文件夹中的独立Demo都有一个自己的defaults.sdkconfig,其中写了使用该Demo时kconfig需要如何配置,这个文件就需要我们根据自己制作的Demo用到的组件来写了,你也可以通过在IoT Studio配置中,配置好点击应用,将生成的.config中的内容复制到你自己的Demo中的defaults.sdkconfig中。
我们现在可以测试性的修改部分配置,然后点击应用,你可以发现工程目录下的.config和iot_config.h都被修改为了相应的配置以供Makefile读取控制编译。
个性化工程配置
如果你需要自己制作你开发板的工程模板你至少需要制作以下文件
targets中的以XXX芯片开头的文件夹存放工程模板
工程模板/Demo文件夹中写入你的相应Demo和defaults.sdkconfig以及修改Demo文件下的user_demo.mk文件还有kconfig
新建工程
在Demo文件夹右击,选择新建文件夹, 新建XX_demo文件夹,用于存放实验文件:
接下来在此文件夹中新建第一个实验文件xx.c
文件,开始编写代码
编写完成之后,要将我们编写的xx.c
文件添加到makefile中,加入整个工程的编译。
这里有个较为简单的方法,直接修改Demo文件夹下的user_demo.mk配置文件,添加如下代码:
ifeq ($(CONFIG_USER_DEMO), "My_Sensor_demo")
user_demo_src = ${wildcard $(TARGET_DIR)/Demos/My_Sensor/*.c}
user_demo_inc = -I $(TARGET_DIR)/Demos/My_Sensor
user_hardware_src = ${wildcard $(TARGET_DIR)/Hardware/E53_IA1/*.c}
user_hardware_inc = -I ${wildcard $(TARGET_DIR)/Hardware/E53_IA1}
user_demo_defs = -D CONFIG_My_Sensor_ENABLE=1
endif
这段代码的意思是:
如果 CONFIG_USER_DEMO
宏定义的值是My_Sensor_demo,则将My_Sensor文件夹下文件与传感器驱动文件加入到makefile中进行编译。
CONFIG_USER_DEMO
宏定义在工程根目录下的.config
文件和iot_config.h
文件中的末尾即可配置:
.config
文件
CONFIG_Demo_None=y
# CONFIG_Demo_Helloworld is not set
# CONFIG_Demo_Streetlight is not set
# CONFIG_Demo_Agriculture is not set
# CONFIG_Demo_Track is not set
# CONFIG_Demo_Smoke is not set
# CONFIG_Demo_Cover is not set
# CONFIG_Demo_Infrared is not set
# CONFIG_Demo_OC_Cloud_Map is not set
# CONFIG_Demo_OC_MQTT is not set
CONFIG_USER_DEMO="My_Sensor_demo"
iot_config.h
文件
#define CONFIG_Demo_None 1
#define CONFIG_USER_DEMO "My_Sensor_demo"
重新编译烧录。