-
请问LiteOS的los_cpup.ph文件具体内容是啥? los_task.c文件宏定义中有如下语句#if (LOSCFG_BASE_CORE_CPUP == YES)#include "los_cpup.ph"但是设置LOSCFG_BASE_CORE_CPUP为YES后,提示找不到los_cpup.ph,源码文件夹也搜不到这个文件。
-
如题,我一般进行编程开发是在Linux环境下进行的,现在的嵌入式开发也没有说非要在Windows下进行开发的,所以作为自由开源引力者的华为在LiteOS的发展下是不是也要给小众开发者一个选择呢?因为在Linux下也可以进行挺多的嵌入式编程开发了,包括arm和stm32等,可能我是要求太多了,但是也是有这方面的诉求,或许技术难度很复杂,可是希望能得到官方的关注,加油!鸿蒙!加油!华为。impressionyang 发表于2019-12-11 20:42:09 2019-12-11 20:42:09 最后回复 impressionyang 2019-12-11 20:49:022339 1
-
stm32 移植LiteOS后如何使用IAP?
-
LiteOS\arch\arm\arm-m\src\los_hwi.c(97): warning: #223-D: function "__get_IPSR" declared implicitlyError: L6218E: Undefined symbol __get_IPSR (referred from los_hwi.o).按照移植步骤执行到最后有错误。
-
>本文首发于公众号『小熊派开源社区』,关注即可接收小熊派IoT教程的最新推送! ![小熊派开源社区](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/201911/29/170332bgfp8bvmjqojrssj.png) # 1. LiteOS内核的内存管理 ## 1.1. 内存管理 在系统运行的过程中,一些内存空间**大小是不确定的**,比如一些数据缓冲区,所以系统需要提供内存空间的管理能力,用户可以在使用的时候申请需要的内存空间,使用完毕释放该空间,以便再次利用。 Huawei LiteOS 的内存管理模块通过对内存的申请/释放操作,来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。 ## 1.2. 动态内存管理 动态内存管理,即在内存资源充足的情况下,从系统配置的一块比较大的连续内存(内存池),根据用户需求,分配任意大小的内存块。当用户不需要该内存块时,又可以释放回系统供下一次使用。 **与静态内存相比,动态内存管理的好处是按需分配,缺点是内存池中容易出现碎片**。 LiteOS动态内存支持 DLINK 和 BEST LITTLE 两种标准算法。 ### 1.2.1. DLINK 动态内存管理算法 DLINK动态内存管理结构如下图所示: ![](http://mculover666.cn/blog/20191125/bk7I2KqKqbFN.png?imageslim) - 第一部分 堆内存(也称内存池)的起始地址及堆区域总大小。 - 第二部分 本身是一个数组,每个元素是一个双向链表,所有free节点的控制头都会被分类挂在这个数组的双向链表中。 ![](http://mculover666.cn/blog/20191203/UvMdzVje7oqh.png?imageslim) - 第三部分 占用内存池极大部分的空间,是**用于存放各节点的实际区域**。 ### 1.2.2. BEST LITTLE 算法(重点) LiteOS 的动态内存分配支持最佳适配算法,即 BEST LITTLE,每次分配时选择内存池中最小最适合的内存块进行分配。 **LiteOS 动态内存管理在最佳适配算法的基础上加入了 SLAB 机制,用于分配固定大小的内存块,进而减小产生内存碎片的可能性**。 LiteOS 内存管理中的 SLAB 机制支持**可配置的 SLAB CLASS 数目及每个 CLASS 的最大空间**。 现以 SLAB CLASS 数目为 4,每个 CLASS 的最大空间为 512 字节为例说明 SLAB 机制: 在内存池中有 4 个 SLAB CLASS,每个 SLAB CLASS 的总共可分配大小为 512 字节,第一个 SLAB CLASS 被分为 32 个16 字节的 SLAB 块,第二个 SLAB CLASS 被分为 16 个 32字节的 SLAB 块,第三个 SLAB CLASS 被分为 8 个 64 字节的 SLAB 块,第四个 SLAB CLASS 被分为 4 个 128 字节的 SLAB 块。这 4 个 SLAB CLASS 是从内存池中按照最佳适配算法分配出来的。 初始化内存管理时,首先初始化内存池,然后在初始化后的内存池中按照最佳适配算法申请 4 个 SLAB CLASS,再逐个按照 SLAB 内存管理机制初始化 4 个 SLAB CLASS。 每次申请内存时,先在满足申请大小的最佳 SLAB CLASS 中申请,(比如用户申请 20 字节内存,就在 SLAB 块大小为 32 字节的 SLAB CLASS 中申请),如果申请成功,就将 SLAB 内存块整块返回给用户,释放时整块回收。如果满足条件的 SLAB CLASS 中已无可以分配的内存块,则继续向内存池按照最佳适配算法申请。需要注意的是,如果当前的 SLAB CLASS 中无可用 SLAB 块了,则直接向内存池申请,而不会继续向有着更大 SLAB 块空间的 SLAB CLASS 申请。 释放内存时,先检查释放的内存块是否属于 SLAB CLASS,如果是 SLAB CLASS 的内存块,则还回对应的 SLAB CLASS 中,否则还回内存池中。 ![](http://mculover666.cn/blog/20191125/2cye0OVS2t8k.png?imageslim) ### 1.2.3. 两种动态内存管理方法的选择 LiteOS动态内存管理的方法使用**宏定义**的方法使能,在用户工程目录下的`OS_CONFIG`中的`target_config.h`文件中配置。 在该文件中,找到下面这两项宏定义,置为 YES 则表示使能: - 开启BEST LITTLE 算法 ``` #define LOSCFG_MEMORY_BESTFIT YES ``` - 开启SLAB机制 ``` #define LOSCFG_KERNEL_MEM_SLAB YES ``` ## 1.3. 动态内存管理的应用场景 内存管理的主要工作是动态的划分并管理用户分配好的内存区间。 动态内存管理主要是在用户需要使用大小不等的内存块的场景中使用。当用户需要分配内存时,可以通过操作系统的动态内存申请函数索取指定大小内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用。 # 2. 动态内存管理API Huawei LiteOS 系统中的内存管理模块管理系统的内存资源,主要提供内存的初始化、分配以及释放功能。 Huawei LiteOS 系统中提供的内存管理 API 都是以 LOS 开头,但是这些 API 使用起来比较复杂,所以本文中我们使用 Huawei IoT Link SDK 提供的统一API接口进行实验,**这些接口底层已经使用 LiteOS 提供的API实现,对用户而言更为简洁**,API列表如下: osal的api接口声明在中,使用相关的接口需要包含该头文件,关于函数的详细参数请参考该头文件的声明。 相关的接口定义在osal.c中,基于LiteOS的接口实现在 liteos_imp.c文件中: | 接口名 | 功能描述 | | :---------------:| :--------:| | osal_malloc | 按字节申请分配动态内存空间 | | osal_free | 释放已经分配的动态内存空间 | | osal_zalloc | 按字节申请分配动态内存空间,分配成功则初始化这块内存所有值为0 | | osal_realloc | 重新申请分配动态内存空间 | | osal_calloc | 申请分配num个长度为size的动态内存空间 | >无论选择使用哪种动态内存管理算法,都使用该API。 ## 2.1. osal_malloc `osal_malloc`接口用于按字节申请分配动态内存空间,其接口原型如下: ```c void *osal_malloc(size_t size) { void *ret = NULL; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->malloc)) { ret = s_os_cb->ops->malloc(size); } return ret; } ``` 该接口的参数说明如下表: |参数|描述| |:----:|:----:| |size|申请分配的内存大小,单位Byte| |返回值|分配成功 - 返回内存块指针| ||分配失败 - 返回NULL| ## 2.2. osal_free `osal_free`接口用于释放已经分配的动态内存空间,其接口原型如下: ```c void osal_free(void *addr) { if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->free)) { s_os_cb->ops->free(addr); } return; } ``` >内存块free之后,记得使内存块指针为NULL,否则会成为野指针! 该接口的参数说明如下表: |参数|描述| |:----:|:----:| |addr|动态分配内存空间的指针| |返回值|无返回值| ## 2.3. osal_zalloc `osal_zalloc`接口用于按字节申请分配动态内存空间,分配成功则初始化这块内存所有值为0,其接口原型如下: ```c void *osal_zalloc(size_t size) { void *ret = NULL; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->malloc)) { ret = s_os_cb->ops->malloc(size); if(NULL != ret) { memset(ret,0,size); } } return ret; } ``` 该接口的参数说明如下表: |参数|描述| |:----:|:----:| |size|申请分配的内存大小,单位Byte| |返回值|分配成功 - 返回内存块指针| ||分配失败 - 返回NULL| ## 2.4. osal_realloc `osal_realloc`接口用于重新申请分配动态内存空间,其接口原型如下: ```c void *osal_realloc(void *ptr,size_t newsize) { void *ret = NULL; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->realloc)) { ret = s_os_cb->ops->realloc(ptr,newsize); } return ret; } ``` 该接口的参数说明如下表: |参数|描述| |:----:|:----:| |ptr|已经分配了内存空间的指针| |newsize|申请分配的新的内存大小,单位Byte| |返回值|分配成功 - 返回内存块指针| ||分配失败 - 返回NULL| ## 2.5. osal_calloc `osal_calloc`接口用于申请分配num个长度为size的动态内存空间,其接口原型如下: ```c void *osal_calloc(size_t n, size_t size) { void *p = osal_malloc(n * size); if(NULL != p) { memset(p, 0, n * size); } return p; } ``` 该接口的参数说明如下表: |参数|描述| |:----:|:----:| |n|申请分配内存块的数目| |size|申请分配的每个内存块的内存大小,单位Byte| |返回值|分配成功 - 返回内存块指针| ||分配失败 - 返回NULL| # 3. 动手实验 —— 测试动态内存分配的最大字节 ## 实验内容 本实验中将创建一个任务,从最小字节开始,不停的申请分配内存,释放分配的内存,直到申请失败,串口终端中观察可以申请到的最大字节。 ## 实验代码 首先打开上一篇使用的 HelloWorld 工程,基于此工程进行实验。 在Demo文件夹右击,新建文件夹`osal_kernel_demo`用于存放内核的实验文件(如果已有请忽略这一步)。 接下来在此文件夹中新建一个实验文件 `osal_mem_demo.c`,开始编写代码: ![](http://mculover666.cn/blog/20191203/Lt1KqFljGRaH.png?imageslim) ```c /* 使用osal接口需要包含该头文件 */ #include <osal.h> /* 任务入口函数 */ static int mem_access_task_entry() { uint32_t i = 0; //循环变量 size_t mem_size; //申请的内存块大小 uint8_t* mem_ptr = NULL; //内存块指针 while (1) { /* 每次循环将申请内存的大小扩大一倍 */ mem_size = 1 << i++; /* 尝试申请分配内存 */ mem_ptr = osal_malloc(mem_size); /* 判断是否申请成功 */ if(mem_ptr != NULL) { /* 申请成功,打印信息 */ printf("access %d bytes memory success!\r\n", mem_size); /* 释放申请的内存,便于下次申请 */ osal_free(mem_ptr); /* 将内存块指针置为NULL,避免称为野指针 */ mem_ptr = NULL; printf("free memory success!\r\n"); } else { /* 申请失败,打印信息,任务结束 */ printf("access %d bytes memory failed!\r\n", mem_size); return 0; } } } /* 标准demo启动函数,函数名不要修改,否则会影响下一步实验 */ int standard_app_demo_main() { /* 创建任务,任务优先级为11,shell任务的优先级为10 */ osal_task_create("mem_access_task",mem_access_task_entry,NULL,0x400,NULL,11); return 0; } ``` 编写完成之后,要将我们编写的 osal_mem_demo.c文件添加到makefile中,加入整个工程的编译: 这里有个较为简单的方法,直接修改Demo文件夹下的user_demo.mk配置文件,添加如下代码: ``` #example for osal_mem_demo ifeq ($(CONFIG_USER_DEMO), "osal_mem_demo") user_demo_src = ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/osal_kernel_demo/osal_mem_demo.c} endif ``` 添加位置如图: ![](http://mculover666.cn/blog/20191203/CGwrudCIS2cH.png?imageslim) 这段代码的意思是: 如果 CONFIG_USER_DEMO 宏定义的值是`osal_mem_demo`,则将`osal_mem_demo.c`文件加入到makefile中进行编译。 那么,如何配置 CONFIG_USER_DEMO 宏定义呢?在工程根目录下的`.sdkconfig`文件中的末尾即可配置: ![](http://mculover666.cn/blog/20191203/o5eFibO9i4oc.png?imageslim) 因为我们修改了mk配置文件,所以点击**重新编译按钮**进行编译,编译完成后点击下载按钮烧录程序。 ## 实验现象 程序烧录之后,即可看到程序已经开始运行,在串口终端中可看到实验的输出内容: ``` **Link**main:V1.2.1 AT 11:30:59 ON Nov 28 2019 WELCOME TO IOT_LINK SHELL LiteOS:/>access 1 bytes memory success! free memory success! access 2 bytes memory success! free memory success! access 4 bytes memory success! free memory success! access 8 bytes memory success! free memory success! access 16 bytes memory success! free memory success! access 32 bytes memory success! free memory success! access 64 bytes memory success! free memory success! access 128 bytes memory success! free memory success! access 256 bytes memory success! free memory success! access 512 bytes memory success! free memory success! access 1024 bytes memory success! free memory success! access 2048 bytes memory success! free memory success! access 4096 bytes memory success! free memory success! access 8192 bytes memory success! free memory success! access 16384 bytes memory success! free memory success! access 32768 bytes memory failed! ``` 可以看到,系统启动后,首先打印版本号,串口shell的优先级为10,最先打印shell信息,接下来内存申请任务创建开始执行,在该芯片上最大能申请的空间为 `16384` 字节。
-
>本文首发于公众号『小熊派开源社区』,关注即可接收小熊派IoT教程的最新推送! ![小熊派开源社区](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/201911/29/170332bgfp8bvmjqojrssj.png) # 1. LiteOS的互斥锁 ## 1.1. 互斥锁 在多任务环境下,往往存在多个任务竞争同一共享资源的应用场景,互斥锁可被用于对共享资源的保护从而实现独占式访问。互斥锁(mutex)又称互斥型信号量,是一种**特殊的二值信号量**,用于实现对共享资源的独占式处理。另外,Huawei LiteOS提供的互斥锁通过优先级继承算法,解决了优先级翻转问题。 ## 1.2. 互斥锁的使用方式 多任务环境下会存在**多个任务访问同一公共资源**的场景,而有些**公共资源是非共享的**,需要任务进行**独占式处理**。 互斥锁怎样来避免这种冲突呢? 在任意时刻,互斥锁的状态只有两种:开锁和闭锁。 **当有任务持有时,互斥锁处于闭锁状态**,这个任务获得该互斥锁的所有权。**当该任务释放它时,该互斥锁被开锁**,任务失去该互斥锁的所有权。当一个任务持有互斥锁时,其他任务将不能再对该互斥锁进行开锁或持有。 那么,当一个互斥锁为加锁状态时,此时**其他任务如果想访问这个公共资源则会被阻塞**,直到互斥锁被持有该锁的任务释放后,其他任务才能重新访问该公共资源,此时互斥锁再次上锁,如此确保同一时刻只有一个任务正在访问这个公共资源,保证了公共资源操作的完整性。 ## 1.3. 互斥锁的使用场景 互斥锁可以提供任务之间的互斥机制,用来防止两个任务在同一时刻访问相同的共享资源。 除此之外,互斥锁还可以被用于防止多任务同步时造成优先级翻转的问题。 # 2. 互斥锁API Huawei LiteOS 系统中的互斥锁模块为用户提供创建/删除互斥锁、获取/释放互斥锁的功能。 Huawei LiteOS 系统中提供的互斥锁 API 都是以 LOS 开头,但是这些 API 使用起来比较复杂,所以本文中我们使用 Huawei IoT Link SDK 提供的统一API接口进行实验,**这些接口底层已经使用 LiteOS 提供的API实现,对用户而言更为简洁**,API列表如下: osal的api接口声明在中,使用相关的接口需要包含该头文件,关于函数的详细参数请参考该头文件的声明。 相关的接口定义在osal.c中,基于LiteOS的接口实现在 liteos_imp.c文件中: | 接口名 | 功能描述 | | :---------------:| :--------:| | osal_mutex_create | 创建互斥锁 | | osal_mutex_del | 删除互斥锁 | | osal_mutex_lock | 获取互斥锁(上锁) | | osal_mutex_unlock | 释放互斥锁(解锁) | ## 2.1. osal_mutex_create `osal_mutex_create`接口用于创建一个互斥锁,其接口原型如下: ```c bool_t osal_mutex_create(osal_mutex_t *mutex) { bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->mutex_create)) { ret = s_os_cb->ops->mutex_create(mutex); } return ret; } ``` 该接口的参数说明如下表: |参数|描述| |:----:|:----:| |mutex|互斥锁索引ID的地址| |返回值|false - 创建失败| |返回值|true - 创建成功| ## 2.2. osal_mutex_del `osal_mutex_del`接口用于删除一个互斥锁,其接口原型如下: ```c bool_t osal_mutex_del(osal_mutex_t mutex) { bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->mutex_del)) { ret = s_os_cb->ops->mutex_del(mutex); } return ret; } ``` 该接口的参数说明如下表: |参数|描述| |:----:|:----:| |mutex|互斥锁索引ID| |返回值|false - 删除失败| |返回值|true - 删除成功| ## 2.3. osal_mutex_lock `osal_mutex_lock`接口用于获取一个互斥锁,其接口原型如下: ```c bool_t osal_mutex_lock(osal_mutex_t mutex) { bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->mutex_lock)) { ret = s_os_cb->ops->mutex_lock(mutex); } return ret; } ``` |参数|描述| |:----:|:----:| |mutex|互斥锁索引ID| |返回值|false - 申请失败| |返回值|true - 申请成功| ## 2.4. osal_mutex_unlock `osal_mutex_unlock`接口用于释放一个互斥锁,如果有任务阻塞等待该互斥锁,则唤醒最早被阻塞的任务,该任务进入就绪态,并进行调度。 其接口原型如下: ```c bool_t osal_mutex_unlock(osal_mutex_t mutex) { bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->mutex_unlock)) { ret = s_os_cb->ops->mutex_unlock(mutex); } return ret; } ``` 该接口的参数说明如下表: |参数|描述| |:----:|:----:| |mutex|互斥锁索引ID| |返回值|false - 释放失败| |返回值|true - 释放成功| # 3. 动手实验 —— 使用互斥锁进行资源保护 ## 实验内容 本实验中将创建两个任务,一个低优先级任务task1,一个高优先级任务task2,两个任务之间依次对共享资源上锁、操作、解锁,在串口终端中观察两个任务的运行情况。 ## 实验代码 首先打开上一篇使用的 HelloWorld 工程,基于此工程进行实验。 在Demo文件夹右击,新建文件夹`osal_kernel_demo`用于存放内核的实验文件(如果已有请忽略这一步)。 接下来在此文件夹中新建一个实验文件 `osal_mutex_demo.c`,开始编写代码: ![](http://mculover666.cn/blog/20191202/Sfv4WhjpYVjO.png?imageslim) ```c /* 使用osal接口需要包含该头文件 */ #include /* 任务优先级宏定义(shell任务的优先级为10) */ #define USER_TASK1_PRI 12 //低优先级 #define USER_TASK2_PRI 11 //高优先级 /* 共享资源 */ uint32_t public_value = 0; /* 互斥锁索引ID */ osal_mutex_t public_value_mutex; /* 任务task1入口函数 */ static int user_task1_entry() { while(1) { /* 尝试获取互斥锁 */ if(true == osal_mutex_lock(public_value_mutex)) { /* 获取到互斥锁,对共享资源进行操作 */ printf("\r\ntask1: lock a mutex.\r\n"); public_value += 10; printf("task1: public_value = %ld.\r\n", public_value); /* 对共享资源操作完毕,释放互斥锁 */ printf("task1: unlock a mutex.\r\n\r\n"); osal_mutex_unlock(public_value_mutex); /* 满足条件则结束任务 */ if(public_value > 100) break; } } /* while(1)会执行结束,所以需要返回值 */ return 0; } /* 任务task2入口函数 */ static int user_task2_entry() { while (1) { /* 尝试获取互斥锁 */ if(true == osal_mutex_lock(public_value_mutex)) { /* 获取到互斥锁,对共享资源进行操作 */ printf("\r\ntask2: lock a mutex.\r\n"); public_value += 5; printf("task2: public_value = %ld.\r\n", public_value); /* 对共享资源操作完毕,释放互斥锁 */ printf("task2: unlock a mutex.\r\n\r\n"); osal_mutex_unlock(public_value_mutex); /* 满足条件则结束任务 */ if(public_value > 90) break; /* 优先级较高,需要挂起一下,让task1获取到互斥锁,否则task2再次上锁,形成死锁 */ osal_task_sleep(10); } } /* while(1)会执行结束,所以需要返回值 */ return 0; } /* 标准demo启动函数,函数名不要修改,否则会影响下一步实验 */ int standard_app_demo_main() { /* 创建互斥锁public_value_mutex */ osal_mutex_create(&public_value_mutex); /* 创建任务task1 */ osal_task_create("user_task1",user_task1_entry,NULL,0x400,NULL,USER_TASK1_PRI); /* 创建任务task2 */ osal_task_create("user_task2",user_task2_entry,NULL,0x400,NULL,USER_TASK2_PRI); return 0; } ``` 编写完成之后,要将我们编写的 osal_mutex_demo.c文件添加到makefile中,加入整个工程的编译: 这里有个较为简单的方法,直接修改Demo文件夹下的user_demo.mk配置文件,添加如下代码: ``` #example for osal_mutex_demo ifeq ($(CONFIG_USER_DEMO), "osal_mutex_demo") user_demo_src = ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/osal_kernel_demo/osal_mutex_demo.c} endif ``` 添加位置如图: ![](http://mculover666.cn/blog/20191202/Dgmoi202ST90.png?imageslim) 这段代码的意思是: 如果 CONFIG_USER_DEMO 宏定义的值是`osal_mutex_demo`,则将`osal_mutex_demo.c`文件加入到makefile中进行编译。 那么,如何配置 CONFIG_USER_DEMO 宏定义呢?在工程根目录下的`.sdkconfig`文件中的末尾即可配置: ![](http://mculover666.cn/blog/20191202/VnRXnNBJjTOd.png?imageslim) 因为我们修改了mk配置文件,所以点击**重新编译按钮**进行编译,编译完成后点击下载按钮烧录程序。 ## 实验现象 程序烧录之后,即可看到程序已经开始运行,在串口终端中可看到实验的输出内容: ``` **Link**main:V1.2.1 AT 11:30:59 ON Nov 28 2019 WELCOME TO IOT_LINK SHELL LiteOS:/> task2: lock a mutex. task2: public_value = 5. task2: unlock a mutex. task1: lock a mutex. task1: public_value = 15. task1: unlock a mutex. task1: lock a mutex. task1: public_value = 25. task1: unlock a mutex. task2: lock a mutex. task2: public_value = 30. task2: unlock a mutex. task1: lock a mutex. task1: public_value = 40. task1: unlock a mutex. task1: lock a mutex. task1: public_value = 50. task1: unlock a mutex. task2: lock a mutex. task2: public_value = 55. task2: unlock a mutex. task1: lock a mutex. task1: public_value = 65. task1: unlock a mutex. task1: lock a mutex. task1: public_value = 75. task1: unlock a mutex. task2: lock a mutex. task2: public_value = 80. task2: unlock a mutex. task1: lock a mutex. task1: public_value = 90. task1: unlock a mutex. task1: lock a mutex. task1: public_value = 100. task1: unlock a mutex. task2: lock a mutex. task2: public_value = 105. task2: unlock a mutex. ``` 可以看到,系统启动后,首先打印版本号,串口shell的优先级为10,最先打印shell信息,接下来task1先创建,但是优先级较低,所以后创建的task2抢占执行,task2获取到互斥锁,对共享资源进行操作,操作完毕解锁,然后主动挂起,task1获取到互斥锁,对共享资源进行另一个操作,操作完毕解锁,在task1操作的时候,task2早已挂起完毕,但是获取不到互斥锁,所以挂起等待,在task1解锁后,堵塞的task2被唤醒开始执行。
-
9 启动流程摘要:本文通过对代码的解析对LiteOS的启动流程做了一个较为详细的介绍,希望对你有所帮助。1 启动方式目前对于多任务的实时操作系统中有两种比较流行的启动方式,一种是在main函数中进行硬件初始化以及系统初始化之后先将所有任务创建完成之后,一起启动,如移植代码中的主函数图1所示。图1 启动方式一另一种方式是在main函数中进行硬件初始化以及系统初始化之后创建任务之后直接启动调度器,然后在启动任务中创建各种应用任务,当所有任务创建成功后启动任务再把自己删除。具体如图2所示。图2 启动方式二2 启动流程2.1 Reset_Handler函数一般在程序下载到开发板之后,我们首先会按一下Reset按键进行开发板的启动,因此在开发板上电后第一个执行的就是Reset_Handler函数,其位于los_startup_gcc.s文件中,该文件可以在LiteOS源码中的targets示例工程中找到。该函数主要进行了以下工作。(1)初始化栈指针;(2)初始化 PC 指针;(3)初始化中断向量表;(4)配置系统时钟;(5)调用 C 库函数_main。具体如图3所示,位于los_startup_keil.s中。图3 los_startup_keil.s 汇编程序如图4所示图4 汇编程序2.2 LiteOS的初始化进入主函数之后首先进行的是LiteOS的初始化。即LOS_KernelInit函数,其具体如下代码所示。LITE_OS_SEC_TEXT_INIT UINT32 LOS_KernelInit(VOID) { UINT32 uwRet; osRegister(); m_aucSysMem0 = OS_SYS_MEM_ADDR; uwRet = osMemSystemInit(); if (uwRet != LOS_OK) { PRINT_ERR("osMemSystemInit error %d\n", uwRet);/*lint !e515*/ return uwRet; } #if (LOSCFG_PLATFORM_HWI == YES) { sHwiInit(); } #endif #if (LOSCFG_PLATFORM_EXC == YES) { osexcInit(MAX_EXC_MEM_SIZE); } #endif uwRet =osTaskInit(); if (uwRet != LOS_OK) { PRINT_ERR("osTaskInit error\n"); return uwRet; } #if (LOSCFG_BASE_CORE_TSK_MONITOR == YES) { osTaskMonInit(); } #endif #if (LOSCFG_BASE_CORE_CPUP == YES) { uwRet = osCpupInit(); if (uwRet != LOS_OK) { PRINT_ERR("osCpupInit error\n"); return uwRet; } } #endif #if (LOSCFG_BASE_IPC_SEM == YES) { uwRet = osSemInit(); if (uwRet != LOS_OK) { return uwRet; } } #endif #if (LOSCFG_BASE_IPC_MUX == YES) { uwRet = osMuxInit(); if (uwRet != LOS_OK) { return uwRet; } } #endif #if (LOSCFG_BASE_IPC_QUEUE == YES) { uwRet = osQueueInit(); if (uwRet != LOS_OK) { PRINT_ERR("osQueueInit error\n"); return uwRet; } } #endif #if (LOSCFG_BASE_CORE_SWTMR == YES) { uwRet = osSwTmrInit(); if (uwRet != LOS_OK) { PRINT_ERR("osSwTmrInit error\n"); return uwRet; } } #endif #if(LOSCFG_BASE_CORE_TIMESLICE == YES) osTimesliceInit(); #endif uwRet = osIdleTaskCreate(); if (uwRet != LOS_OK) { return uwRet; } #if (LOSCFG_TEST == YES) uwRet = los_TestInit(); if (uwRet != LOS_OK) { PRINT_ERR("los_TestInit error\n"); return uwRet; } #endif return LOS_OK; } #ifdef __cplusplus #if __cplusplus } #endif /* __cpluscplus */ #endif /* __cpluscplus */具体说明如图5所示。图5 内核函数示意图下面对上图其中的几个宏以及函数(1、2、3、4、10)做一个较为详细的介绍:1:根据 target_config.h 中的 LOSCFG_BASE_CORE_TSK_LIMIT 来配置最大支持的任务个数,默认为 LOSCFG_BASE_CORE_TSK_LIMIT+1,包括空闲任务IDLE。2:初始化 LiteOS 管理的内存模块,系统管理的内存大小为OS_SYS_MEM_SIZE。3 :如果在 target_config.h 中使用了 LOSCFG_PLATFORM_HWI 这个宏定义,则进行硬件中断模块的初始化。则表示 LiteOS 接管了系统的中断,使用时需要注册中断,否则无法响应中断,而如果不使用 LOSCFG_PLATFORM_HWI 这个宏定义,系中断将由硬件响应,系统不接管中断的操作与裸机基本是差不多的。4 :初始化任务模块相关的函数,进行分配任务内存,初始化相关链表,为后面创建任务做准备。10:在 target_config.h 中使用 LOSCFG_BASE_CORE_SWTMR 软件定时器的宏,所以,需要对软件定时器的使用进行相关初始化,当使用软件定时器的时候,系统还会创建一个软件定时器任务,并且必须使用消息队列。之后直接开启任务调度,配置SysTick,以及配置 SysTick 与 PendSVd 的优先级即可,通过上述步骤即完成了LiteOS的启动流程。目录主博文:https://bbs.huaweicloud.com/blogs/124244
-
>本文首发于公众号『小熊派开源社区』,关注即可接收小熊派IoT教程的最新推送! ![小熊派开源社区](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/201911/29/170332bgfp8bvmjqojrssj.png) # 1. LiteOS内核的信号量 ## 1.1.信号量 在多任务操作系统中,不同的任务之间需要同步运行,信号量功能可以为用户提供这方面的支持。信号量(Semaphore)是一种实现任务间通信的机制,实现任务之间同步或临界资源的互斥访问。 ## 1.2. 信号量的使用方式 信号量可以被任务获取或者申请,不同的信号量通过信号量索引号来唯一确定,每个信号量都有一个计数值和任务队列。 通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数,其值的含义分两种情况: - 0:表示没有积累下来的 Post 操作,且有可能有在此信号量上阻塞的任务; - 正值:表示有一个或多个 Post 下来的释放操作; 当任务申请(Pend)信号量时,如果申请成功,则信号量的计数值递减,如若申请失败,则挂起在该信号量的等待任务队列上,一旦有任务释放该信号量,则等待任务队列中的任务被唤醒开始执行。 ## 1.3. 信号量的使用场景 信号量是一种非常灵活的同步方式,可以运用在多种场合中,实现锁、同步、资源计数等功能,也能方便的用于任务与任务,中断与任务的同步中。 - 互斥锁 用作互斥时,信号量创建后记数是满的,在需要使用临界资源时,先申请信号量,使其变空,这样其他任务需要使用临界资源时就会因为无法申请到信号量而阻塞,从而保证了临界资源的安全。 - 任务间同步 用作同步时,信号量在创建后被置为空,任务1申请信号量而阻塞,任务2在某种条件发生后,释放信号量,于是任务1得以进入 READY 或 RUNNING 态,从而达到了两个任务间的同步。 - 资源计数 用作资源计数时,信号量的作用是一个特殊的计数器,可以递增或者递减,但是值永远不能为负值,典型的应用场景是生产者与消费者的场景。 - 中断与任务的同步 用作中断与任务的同步时,可以在中断未触发时将信号量的值置为0,从而堵塞断服务处理任务,一旦中断被触发,则唤醒堵塞的中断服务处理任务进行中断处理。 # 2. 信号量API Huawei LiteOS 系统中的信号量模块为用户提供创建/删除信号量、申请/释放信号量的功能。 Huawei LiteOS 系统中提供的信号量 API 都是以 LOS 开头,但是这些 API 使用起来比较复杂,所以本文中我们使用 Huawei IoT Link SDK 提供的统一API接口进行实验,**这些接口底层已经使用 LiteOS 提供的API实现,对用户而言更为简洁**,API列表如下: osal的api接口声明在中,使用相关的接口需要包含该头文件,关于函数的详细参数请参考该头文件的声明。 相关的接口定义在osal.c中,基于LiteOS的接口实现在 liteos_imp.c文件中: | 接口名 | 功能描述 | | :---------------:| :--------:| | osal_semp_create | 信号量创建 | | osal_semp_del | 信号量删除 | | osal_semp_pend | 信号量申请 | | osal_semp_post | 信号量释放 | ## 2.1. osal_semp_create `osal_semp_create`接口用于创建一个信号量,其接口原型如下: ```c bool_t osal_semp_create(osal_semp_t *semp,int limit,int initvalue) { bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->semp_create)) { ret = s_os_cb->ops->semp_create(semp,limit,initvalue); } return ret; } ``` 该接口的参数说明如下表: |参数|描述| |:----:|:----:| |semp|信号量索引ID的地址| |limit|信号量计数值的最大值| |initvalue|信号量计数值的初始值| |返回值|false - 创建失败| |返回值|true - 创建成功| ## 2.2. osal_semp_del `osal_semp_del`接口用于删除一个信号量,其接口原型如下: ```c bool_t osal_semp_del(osal_semp_t semp) { bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->semp_del)) { ret = s_os_cb->ops->semp_del(semp); } return ret; } ``` 该接口的参数说明如下表: |参数|描述| |:----:|:----:| |semp|信号量索引ID| |返回值|false - 删除失败| |返回值|true - 删除成功| ## 2.3. osal_semp_pend `osal_semp_pend`接口用于申请一个信号量,其接口原型如下: ```c bool_t osal_semp_pend(osal_semp_t semp,int timeout) { bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->semp_pend)) { ret = s_os_cb->ops->semp_pend(semp,timeout); } return ret; } ``` 该接口的参数说明如下表: |参数|描述| |:----:|:----:| |semp|信号量索引ID| |timeout|32位值,具体见下文| |返回值|false - 申请失败| |返回值|true - 申请成功| **信号量有三种申请模式:非阻塞模式、永久阻塞模式、定时阻塞模式,用 `timeout` 参数的值选择**。 - 非阻塞模式(0): 任务需要申请信号量,若当前信号量的任务数没有到信号量设定的上限,则申请成功。否则,立即返回申请失败 - 永久阻塞模式(cn_osal_timeout_forever或0xFFFFFFFF): 任务需要申请信号量,若当前信号量的任务数没有到信号量设定的上限,则申请成功。否则,该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该信号量,阻塞任务才会重新得以执行 - 定时阻塞模式(任意定时值,32bit): 任务需要申请信号量,若当前信号量的任务数没有到信号量设定的上限,则申请成功。否则,该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,指定时间超时前有其他任务释放该信号量,或者用户指定时间超时后,阻塞任务才会重新得以执行 >由于中断不能被阻塞,因此在申请信号量时,阻塞模式不能在中断中使用。 ## 2.4. osal_semp_post `osal_semp_post`接口用于释放一个信号量,如果有任务阻塞于该信号量,则唤醒该信号量阻塞队列上的第一个任务,该任务进入就绪态,并进行调度。 其接口原型如下: ```c bool_t osal_semp_post(osal_semp_t semp) { bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->semp_post)) { ret = s_os_cb->ops->semp_post(semp); } return ret; } ``` 该接口的参数说明如下表: |参数|描述| |:----:|:----:| |semp|信号量索引ID| |返回值|false - 释放失败| |返回值|true - 释放成功| # 3. 动手实验 —— 使用信号量进行任务间同步 ## 实验内容 本实验中将创建两个任务,一个低优先级任务task1,一个高优先级任务task2,两个任务之间使用信号量同步运行,在串口终端中观察两个任务的运行情况。 ## 实验代码 首先打开上一篇使用的 HelloWorld 工程,基于此工程进行实验。 在Demo文件夹右击,新建文件夹`osal_kernel_demo`用于存放内核的实验文件(如果已有请忽略这一步)。 接下来在此文件夹中新建一个实验文件 `osal_semp_demo.c`,开始编写代码: ![](http://mculover666.cn/blog/20191130/cM1WwzqR4Kod.png?imageslim) ```c /* 使用osal接口需要包含该头文件 */ #include <osal.h> /* 任务优先级宏定义(shell任务的优先级为10) */ #define USER_TASK1_PRI 12 //低优先级 #define USER_TASK2_PRI 11 //高优先级 /* 信号量索引ID */ osal_semp_t sync_semp; /* 任务task1入口函数 */ static int user_task1_entry() { while(1) { /* 在串口打印信息 */ printf("task 1 post a semp!\r\n"); /* 打印完毕释放信号量 */ osal_semp_post(sync_semp); /* 任务主动挂起2s */ osal_task_sleep(2*1000); } } /* 任务task2入口函数 */ static int user_task2_entry() { while (1) { /* 优先级高,抢占执行打印信息 */ printf("task2 is waiting for a semp...\r\n"); /* 申请信号量,申请失败则挂起等待 */ osal_semp_pend(sync_semp, cn_osal_timeout_forever); /* 一旦申请到信号量,则恢复执行 */ printf("task 2 access a semp!\r\n"); } } /* 标准demo启动函数,函数名不要修改,否则会影响下一步实验 */ int standard_app_demo_main() { /* 创建信号量sync_semp */ osal_semp_create(&sync_semp, 1, 0); printf("sync_semp semp create success.\r\n"); /* 创建任务task1 */ osal_task_create("user_task1",user_task1_entry,NULL,0x400,NULL,USER_TASK1_PRI); /* 创建任务task2 */ osal_task_create("user_task2",user_task2_entry,NULL,0x400,NULL,USER_TASK2_PRI); return 0; } ``` 编写完成之后,要将我们编写的`osal_semp_demo`文件添加到makefile中,加入整个工程的编译: 这里有个较为简单的方法,直接修改`Demo`文件夹下的`user_demo.mk`配置文件,添加如下代码: ``` #example for osal_semp_demo ifeq ($(CONFIG_USER_DEMO), "osal_semp_demo") user_demo_src = ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/osal_kernel_demo/osal_semp_demo.c} user_demo_defs = -D CONFIG_OSAL_SEMP_DEMO_ENABLE=1 endif ``` 添加位置如图: ![](http://mculover666.cn/blog/20191130/omrE4xkCYNfK.png?imageslim) 这段代码的意思是: 如果 CONFIG_USER_DEMO 宏定义的值是`osal_semp_demo`,则将`osal_semp_demo.c`文件加入到makefile中进行编译。 那么,如何配置 CONFIG_USER_DEMO 宏定义呢?在工程根目录下的`.sdkconfig`文件中的末尾即可配置: ![](http://mculover666.cn/blog/20191129/d3M6u3Fh5YBI.png?imageslim) ![](http://mculover666.cn/blog/20191130/9IehpCqyLuHR.png?imageslim) 因为我们修改了mk配置文件,所以点击重新编译按钮![](http://mculover666.cn/blog/20191121/CfUb4vtVJ0pJ.png?imageslim)进行编译,编译完成后点击下载按钮烧录程序。 >编译之后会有警告,提示user_task1_entry和user_task2_entry函数没有返回值,这里使用了while(1),不会返回,所以不用管。 ## 实验现象 程序烧录之后,即可看到程序已经开始运行,在串口终端中可看到实验的输出内容: ``` **Link**main:V1.2.1 AT 11:30:59 ON Nov 28 2019 sync_semp semp create success. WELCOME TO IOT_LINK SHELL LiteOS:/>task2 is waiting for a semp... task 1 post a semp! task 2 access a semp! task2 is waiting for a semp... task 1 post a semp! task 2 access a semp! task2 is waiting for a semp... task 1 post a semp! task 2 access a semp! …… ``` 可以看到,系统启动后,首先打印版本号,由于串口shell的优先级为10,所以接下来打印shell信息,接下来task1先创建,但是优先级较低,所以后创建的task2抢占执行,task2未等待到信号量,打印之后挂起,然后低优先级任务task1继续执行,打印信息之后释放信号量,释放之后task2任务被唤醒,恢复执行。
-
>本文首发于公众号『小熊派开源社区』,关注即可接收小熊派IoT教程的最新推送! ![小熊派开源社区](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/201911/29/171129bumc6cltvv3gh4sg.png) # 1. LiteOS内核的任务管理 Huawei LiteOS 内核提供任务的创建、删除、延迟、挂起、恢复等功能,以及锁定和解锁任务调度,支持任务按优先级高低的抢占调度及同优先级时间片轮转调度。 ## 1.1. 任务 在 LiteOS 中,一个任务就是一个线程,多个任务按照优先级进行抢占式调度,达到多个任务“同时”运行的目的。 ## 1.2. 任务的状态 Huawei LiteOS 系统中的**每个任务都有多种运行状态**。当系统初始化完成并启动调度器后,系统中所有创建的任务就由内核进行调度,在不同运行状态之间切换,同时在系统中竞争一定的资源。 任务的状态有以下四种: - 就绪(Ready):该任务在就绪列表中,只等待 CPU; - 运行(Running):该任务正在执行; - 阻塞(Blocked):该任务不在就绪列表中。包含任务被挂起、任务被延时、任务正在等待信号量、读写队列或者等待读写事件等; - 退出态(Dead):该任务运行结束,等待系统回收资源。 ## 1.3. 任务ID 任务 ID **在任务创建时通过参数返回给用户,作为任务的一个非常重要的标识**。 用户可以通过任务ID对指定任务进行任务挂起、任务恢复、查询任务名等操作。 ## 1.4. 任务优先级 优先级表示**任务执行的优先顺序**。任务的优先级决定了在发生任务切换时即将要执行的任务,在就绪列表中的最高优先级的任务将得到执行。 Huawei LiteOS 的任务一共有 32 个优先级 (0-31),最高优先级为 0,最低优先级为 31。 因为是LiteOS的内核是抢占式调度内核,所以: **高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度**。 ## 1.5. 任务入口函数 任务入口函数是**每个新任务得到调度后将执行的函数**,该函数由用户实现,在任务创建时,通过任务创建结构体指定。 ## 1.6. 多任务运作背后的机制 在多任务操作系统的内核中,为了方便对每个任务进行管理,每一个任务都有一个任务控制块(TCB),其中包含了任务上下文栈指针(stack pointer)、任务状态、任务优先级、任务ID、任务名、任务栈大小等信息,**TCB 相当于每个任务在内核中的身份证,可以反映出每个任务运行情况**。 那么,操作系统中这么多的任务,它们依靠TCB被系统统一管理,那么又是如何被系统执行的呢? 其实,每个任务相当于一个裸机程序,每个任务之间相互独立,这个“独立”指的是每个任务都有自己的运行环境 —— 栈空间,称为**任务栈**,栈空间里保存的信息包含局部变量、寄存器、函数参数、函数返回地址等。 可是,系统中只有一个CPU,即使每个任务的任务栈是独立的,可是多个任务都需要被同一个CPU所执行,CPU的资源是共用的吧。 对的,CPU的资源是多个任务共用的,这些CPU的寄存器**只有在任务执行的时候被使用**,称为**任务上下文**,因此,内核在任务切换时会将切出任务的上下文信息保存在自身的任务栈空间里面,以便任务恢复时还原现场,从而在任务恢复后在切出点继续开始执行。 用户创建任务时,系统会先申请任务控制块需要的内存空间,申请成功后,系统会将任务栈进行初始化,预置上下文。此外,系统还会将“任务入口函数”地址放在相应位置。这样在任务第一次启动进入运行态时,将会执行“任务入口函数”。 # 2. 任务管理API Huawei LiteOS 任务管理模块提供任务创建、任务删除、任务延时、任务挂起和任务恢复、更改任务优先级、锁任务调度和解锁任务调度、根据任务控制块查询任务 ID、根据 ID 查询任务控制块信息功能。 Huawei LiteOS 任务管理提供的 API 都是以 `LOS` 开头,但是这些 API 使用起来比较复杂,所以本文中我们使用 Huawei IoT Link SDK 提供的统一API接口进行实验,这些接口底层已经使用 LiteOS 提供的API实现,对用户而言更为简洁,API列表如下: osal的api接口声明在中,使用相关的接口需要包含该头文件,关于函数的详细参数请参考该头文件的声明。 任务相关的接口定义在`osal.c`中,基于LiteOS的接口实现在 `liteos_imp.c`文件中: | 接口名 | 功能描述 | | :----------------: | :------------------------------------: | | osal_task_create | 创建任务 | | osal_task_kill | 删除任务(非自身) | | osal_task_exit | 任务退出 | | osal_task_sleep | 任务休眠 | ## 2.1. osal_task_create `osal_task_create`的接口用于创建一个任务,其接口原型如下: ```c void* osal_task_create(const char *name,int (*task_entry)(void *args),\ void *args,int stack_size,void *stack,int prior) { void *ret = NULL; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->task_create)) { ret = s_os_cb->ops->task_create(name, task_entry,args,stack_size,stack,prior); } return ret; } ``` 该接口的参数说明如下表: |参数名称|参数说明| |:-----:|:-----:| |name|任务名称| |tsak_entry|任务入口函数的函数指针| |args|任务入口函数的参数列表| |stack_size|任务栈大小| |stack|任务栈地址| |prior|任务优先级| |返回值|任务ID| ## 2.2. osal_task_kill `osal_task_kill`用于删除某个其他任务(非自身),其接口原型如下: ```c int osal_task_kill(void *task) { int ret = -1; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->task_kill)) { ret = s_os_cb->ops->task_kill(task); } return ret; } ``` 该接口的参数说明如下表: |参数名称|参数说明| |:-----:|:-----:| |task|任务ID| |返回值|0-删除成功| |返回值|-1-删除失败| ## 2.3. osal_task_exit `osal_task_exit`接口用于任务退出(自身),**目前暂未支持,可以直接return退出**。 ## 2.4. osal_task_sleep `osal_task_sleep`接口用于任务主动休眠,单位是ms,其接口原型如下: ```c void osal_task_sleep(int ms) { if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->task_sleep)) { s_os_cb->ops->task_sleep(ms); } return ; } ``` # 3. 动手实验 —— 体验任务的创建与切换 ## 实验内容 本实验中将创建两个任务,一个低优先级任务task1,一个高优先级任务task2,两个任务都会每隔2s在串口打印自己的任务id号,在串口终端中观察两个任务的运行情况。 ## 实验代码 首先打开之前创建的 HelloWorld 工程,基于此工程进行实验。 在`Demo`文件夹右击,选择新建文件夹: ![](http://mculover666.cn/blog/20191129/yzGlJYzR2oeP.png?imageslim) 新建`osal_kernel_demo`文件夹,用于存放内核的实验文件: ![](http://mculover666.cn/blog/20191121/tHvJHeO7yD4e.png?imageslim) 接下来在此`osal_kernel_demo`文件夹中新建第一个实验文件`osal_task_demo.c`文件,开始编写代码: ![](http://mculover666.cn/blog/20191129/F6QOPkC2A1Wn.png?imageslim) ```c /* 使用osal接口需要包含该头文件 */ #include /* 任务优先级宏定义(shell任务的优先级为10) */ #define USER_TASK1_PRI 12 //低优先级 #define USER_TASK2_PRI 11 //高优先级 /* 任务ID */ uint32_t user_task1_id = 0; uint32_t user_task2_id = 0; /* 任务task1入口函数 */ static int user_task1_entry() { int n = 0; /* 每隔2s在串口打印一次,打印5次后主动结束 */ for(n = 0; n < 5; n++) { printf("task1: my task id is %ld, n = %d!\r\n", user_task1_id, n); /* 任务主动挂起2s */ osal_task_sleep(2*1000); } printf("user task 1 exit!\r\n"); /* 任务结束 */ return 0; } /* 任务task2入口函数 */ static int user_task2_entry() { /* 每隔2s在串口打印一次,不结束 */ while (1) { printf("task 2: my task id is %ld!\r\n", user_task2_id); /* 任务主动挂起2s */ osal_task_sleep(2*1000); } } /* 标准demo启动函数,函数名不要修改,否则会影响下一步实验 */ int standard_app_demo_main() { /* 创建任务task1 */ user_task1_id = osal_task_create("user_task1",user_task1_entry,NULL,0x400,NULL,USER_TASK1_PRI); /* 创建任务task2 */ user_task2_id = osal_task_create("user_task2",user_task2_entry,NULL,0x400,NULL,USER_TASK2_PRI); return 0; } ``` 编写完成之后,要将我们编写的`osal_task_demo.c`文件添加到makefile中,加入整个工程的编译: 这里有个较为简单的方法,直接修改`Demo`文件夹下的`user_demo.mk`配置文件,添加如下代码: ``` #example for osal_task_demo ifeq ($(CONFIG_USER_DEMO), "osal_task_demo") user_demo_src = ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/osal_kernel_demo/osal_task_demo.c} user_demo_defs = -D CONFIG_OSAL_TASK_DEMO_ENABLE=1 endif ``` 添加位置如图: ![](http://mculover666.cn/blog/20191129/g3Tez1mvUzNR.png?imageslim) 这段代码的意思是: 如果 CONFIG_USER_DEMO 宏定义的值是`osal_task_demo`,则将`osal_task_demo.c`文件加入到makefile中进行编译。 那么,如何配置 CONFIG_USER_DEMO 宏定义呢?在工程根目录下的`.sdkconfig`文件中的末尾即可配置: ![](http://mculover666.cn/blog/20191129/d3M6u3Fh5YBI.png?imageslim) ![](http://mculover666.cn/blog/20191129/IguOlDWr4M4W.png?imageslim) 因为我们修改了mk配置文件,所以点击重新编译按钮![](http://mculover666.cn/blog/20191121/CfUb4vtVJ0pJ.png?imageslim)进行编译,编译完成后点击下载按钮烧录程序。 ## 实验现象 程序烧录之后,即可看到程序已经开始运行,在串口终端中可看到实验的输出内容: ``` **Link**main:V1.2.1 AT 11:30:59 ON Nov 28 2019 WELCOME TO IOT_LINK SHELL LiteOS:/>task 2: my task id is 5! task1: my task id is 4, n = 0! task 2: my task id is 5! task1: my task id is 4, n = 1! task 2: my task id is 5! task1: my task id is 4, n = 2! task 2: my task id is 5! task1: my task id is 4, n = 3! task 2: my task id is 5! task1: my task id is 4, n = 4! task 2: my task id is 5! user task 1 exit! task 2: my task id is 5! …… ``` 可以看到,系统启动后,首先打印版本号,串口shell的优先级为10,最先打印shell信息,接下来task1先创建,但是优先级较低,所以后创建的task2抢占执行,task2打印后主动挂起2s,这时task1开始执行,依次执行5次后task1结束,task2一直保持运行。
-
>本文首发于公众号『小熊派开源社区』,关注即可接收小熊派IoT教程的最新推送! ![小熊派开源社区](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/201911/29/171129bumc6cltvv3gh4sg.png) # 1. 创建HelloWorld工程 打开IoT Studio,点击创建IoT Studio工程: ![Studio初始界面](http://mculover666.cn/blog/20191120/snP54NGx9tww.png?imageslim) 填写工程设置,需要注意一下几点: - 工程名称和目录中不可以有中文或者空格 - SDK版本选择**最新**的IoT_LINK版本,当前最新1.0.0 - 硬件平台选择`STM32L431RC_BearPi` - 示例工程选择`hello_world_demo` ![Studio创建工程](http://mculover666.cn/blog/20191120/bzuV1TB6Esqk.png?imageslim) 创建完成之后进入IoT Studio界面如图: ![Studio项目界面](http://mculover666.cn/blog/20191120/TudkEq7309I5.png?imageslim) # 2. IoT_LINK SDK 在创建工程时,会选择 SDK 版本,如图: ![](http://mculover666.cn/blog/20191127/K46eO76bSHbk.png?imageslim) ## Huawei IoT **Link** SDK Huawei IoT **Link** SDK(下文统一简称SDK)是部署在具备广域网能力、对功耗/存储/计算资源有苛刻限制的终端设备上的轻量级互联互通中间件,您只需调用API接口,便可实现设备快速接入到物联网平台以及数据上报和命令接收等功能。 ![](http://mculover666.cn/blog/20191127/FmvWENMVJEmA.png?imageslim) SDK提供端云协同能力,集成了MQTT、LwM2M、CoAP、mbedtls、LwIP 全套 IoT 互联互通协议栈,且在这些协议栈的基础上,提供了开放 API,用户只需关注自身的应用,而不必关注协议内部实现细节,直接使用SDK封装的API,通过连接、数据上报、命令接收和断开四个步骤就能简单快速地实现与华为OceanConnect云平台的安全可靠连接。使用SDK,用户可以大大减少开发周期,聚焦自己的业务开发,快速构建自己的产品。 ## SDK和LiteOS的关系 Huawei IoT **Link** SDK 的底层拥有OS适配层,可以适配Linux、MacOS、LiteOS,以及其它第三方OS(需要自行适配),**本教程中所使用的SDK底层所适配的OS是LiteOS**。 ## SDK和IoT Studio的关系 为了用户开发方便,SDK的代码不会出现在 IoT Studio 中,**IoT Studio中只有用户的目标工程代码(target)**,但是,SDK的代码会在编译的时候被编译进工程。 那么,SDK的代码在哪里?如何查看SDK的代码? SDK的代码在`C:\Users\Administrator\.icode\sdk\IoT_LINK_1.0.0`目录下,其中 Administrator 是我的用户名,在实际情况中会是你当前的用户名,SDK 的代码较多,所以建议使用VS Code 直接打开 `IoT_LINK_1.0.0` 文件夹查看。 ## 如何更新SDK SDK不断的进行更新,可以在 IoT Studio 的工程中进入“文件 -> 首选项”,选择“SDK 管理”,如果有新的版本被检测到,状态会变为“可更新”,更新方法为:选中该SDK,然后点击“安装/更新”即可: ![](http://mculover666.cn/blog/20191127/NXRlowzMSvkG.png?imageslim) >每次打开工程时都要看看SDK是否有更新,最近的一次更新为2019-11-28(今天),另外,更新 SDK 后,需要重新创建工程以刷新配置。 # 3. HelloWorld Demo HelloWorld的示例代码在 `Demos/hello_world_demo/hello_world_demo.c`文件中,示例代码中创建了一个名称为 helloworld,优先级为 2 的任务,该任务每隔 4s 在串口打印一次数据,代码如下: ```c #include <osal.h> static int app_hello_world_entry() { while (1) { printf("Hello World! This is LiteOS!\r\n"); osal_task_sleep(4*1000); } } int standard_app_demo_main() { osal_task_create("helloworld",app_hello_world_entry,NULL,0x400,NULL,2); return 0; } ``` # 4.编译 IoT Studio使用 arm-none-eabi-gcc 工具链进行编译,使用 make 工具构建编译,使用`*.mk` 文件留给用户配置一些 makefile 中的选项。 ## 设置编译器和make工具路径 还记得在上一篇文章中安装IoT Studio时查看的开发工具路径吗?没错,接下来我们就需要配置这两个路径: - `arm-none-eabi-`编译器路径 编译器的路径在:`C:\Users\Administrator\openSourceTools\GNU Tools Arm Embedded\7 2018-q2-update\bin`,其中 Administrator 是我的用户名,更换为你的用户名即可。 在IoT Studio中进入“工程 -> 工程配置”,选择“编译器”,配置编译器路径: ![](http://mculover666.cn/blog/20191127/NigyIYBgt61g.png?imageslim) - `make`构建器路径 make构建器的路径在`C:\Users\Administrator\openSourceTools\GNU MCU Eclipse\Build Tools\2.11-20180428-1604\bin`,其中 Administrator 是我的用户名,更换为你的用户名即可。 在IoT Studio中进入“工程 -> 工程配置”,选择“编译器”,配置make路径: ![](http://mculover666.cn/blog/20191127/oHMDUqU8xBUB.png?imageslim) ## 设置工程的makefile 整个工程的 makefile 在 `GCC` 目录下,在编译之前,要确保在IoT Studio中指定该makefile: ![makefile选择界面](http://mculover666.cn/blog/20191120/bghY3jUnUxmv.png?imageslim) ## 编译工程 指定 makefile 之后,点击编译按钮![](http://mculover666.cn/blog/20191120/PD3cG58XRxkd.png?imageslim)即可编译整个工程,所有的编译输出信息会在控制台打印: ![控制台界面](http://mculover666.cn/blog/20191128/XoeTI2iChhs9.png?imageslim) >目前SDK中的警告不用管,再次点击编译即可。 # 5. 烧录 IoT Studio 支持使用 J**Link** 或者 ST-Link 下载程序,**小熊派开发板板载ST-Link下载器,并且是STLink-v2.1版本**,所以使用 OpenOCD 通过ST-Link 进行下载,下载设置如图: ![](http://mculover666.cn/blog/20191127/TUaC0YoJre68.png?imageslim) >OpenOCD参数中st-**Link**-v2.cfg 和 st-**Link**-v2-1.cfg 是不同的! 设置完成之后,连接小熊派开发板到PC,点击下载按钮![](http://mculover666.cn/blog/20191120/AWTxPF1FWqx9.png?imageslim)即可,控制台会打印出下载信息: ![控制台界面](http://mculover666.cn/blog/20191128/frH69Fs6DCVO.png?imageslim) 烧录完成之后,开发板自动复位,程序开始运行,可以看到LCD屏幕显示如下字样: ``` Welcome to IoTCluB! BearPi IoT Develop Board Powerd by Huawei LiteOS! Please wait for system init! ``` # 6. 查看串口输出结果 IoT Studio 集成了串口终端,可以很方便的查看串口输出信息。 在使用之前首先进行串口的设置: ![串口终端设置界面](http://mculover666.cn/blog/20191120/YnjwXWegoIn2.png?imageslim) **连接小熊派开发板,并且确保小熊派开发板右上角的串口选择开关拨到AT-MCU一端**。 然后点击查看按钮![](http://mculover666.cn/blog/20191120/72nETGhjpMnP.png?imageslim),选择串口终端: ![串口终端查看界面](http://mculover666.cn/blog/20191120/iINUhvCLXxTj.png?imageslim) 在控制台会显示串口终端界面,点击打开串口按钮即可,可以看到Demo程序在串口的输出如图: ![串口终端控制台界面](http://mculover666.cn/blog/20191128/PiXnCgd4YOpi.png?imageslim)
-
请问版主,我使用的是liteos 的atiny组件进行的数据上报,总是在上报了一段时间后就出现ATINY_RESOURCE_NOT_ENOUGH-6系统资源不足检查系统资源,比如信号量,套接字个数等,是否配置过少,或是否有泄漏这个错误,在使用小熊派的demo和配套开发板测试后也会出现这个错误。我在代码中将上报的消息数和上报的错误打印了出来,在上报到290条时发生了错误,只能通过复位重新连接平台才可以再次成功上报。这里有这么几个问题:1、导致错误的根本原因(我使用的小熊派demo,选择使用组件接口atiny)2、atiny上报的机制是什么跪求版主解答呀,谢谢
-
>本文首发于公众号『小熊派开源社区』,关注即可接收小熊派IoT教程的最新推送! ![小熊派开源社区](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/201911/29/171129bumc6cltvv3gh4sg.png) # 1. 物联网一站式开发工具 —— IoT Studio IoT Studio 是支持 LiteOS 嵌入式系统软件开发的工具,提供了代码编辑、编译、烧录 及调试等一站式开发体验,支持 C、C++、汇编等多种开发语言,让您快速,高效地进 行物联网开发。 ![](http://mculover666.cn/blog/20191126/YgAhJmD5VPd3.png?imageslim) # 2. IoT Studio 支持的硬件 IoT Studio 目前支持 Cortex-M0,Cortex-M4,Cortex-M7,Cortex-A7,ARM926EJ-S,RISC-V 等芯片架构。 IoT Studio 目前已经适配了多种开发板,主流支持小熊派IoT开发套件,另外还支持 GD、ST、HiSilicon、Fudan Microelectronics 等主流厂商的开发板。 - ST:STM32F429IG,STM32F411RE,STM32L431RB,STM32L431RC,STM32F746ZG - GD:GD32VF103V-EVAL - HiSilicon:Hi3516CV300,Hi3516CV500,Hi3516EV200,Hi3516EV300,Hi3518EV300 - Fudan Microelectronics:FM33A04xx IoT Studio 支持新增 MCU 列表,以满足用户其他开发板的业务需求。 # 3. IoT Studio安装 ## 3.1. 下载IoT Studio: IoT Studio下载地址如下: > https://developer.obs.cn-north-4.myhuaweicloud.com/idea/IoT-Studio.zip 下载会得到一份压缩包,其中包含一份安装指南,详细的安装教程可以参考该文档。 ## 3.2. 一键安装IoT Studio 使用一键安装方式,无须再手动安装其他工具,安装步骤如下: 双击运行`IoT-Studio_0.3.5.exe`文件开始安装。 选择`我同意此协议`: ![](http://mculover666.cn/blog/20191126/vLihLkwqijwQ.png?imageslim) 选择安装路径,可以改动,不要有中文和空格,这里我保持默认: ![](http://mculover666.cn/blog/20191126/I8gJCK4eRiYm.png?imageslim) 确认设置,开始安装: ![](http://mculover666.cn/blog/20191126/TbiGMHtXbEQq.png?imageslim) 安装完成,选择是否安装ST-Link和J**Link**驱动和工具,推荐全选: ![](http://mculover666.cn/blog/20191126/EWrFmcEoCVno.png?imageslim) ## 3.3. 安装develop tools IoT Studio安装成功之后会自动运行,在首次运行时检测到没有开发工具包,选择“是”,软件会调用浏览器开始下载,下载好之后双击运行`developTools.exe`。 软件会自动将需要用到的开发工具解压到`C:\Users\Administrator\openSourceTools`目录: ![](http://mculover666.cn/blog/20191126/T1utRzBrQgjm.png?imageslim) >这里我是Administrator,不同的用户目录名称不同! 之后软件会自动开始安装`ST-Link`和`J-**Link**`,根据提示安装即可。 打开工具目录,里面有两个路径需要我们在IoT Studio中配置: ![](http://mculover666.cn/blog/20191126/wswqfdHlUKF0.png?imageslim) - `GNU Tools Arm Embedded`:存放`arm-gnone-eabi-gcc`编译器的路径,用来编译程序 - `GNU MCU Eclipse`:存放`make`工具的路径,用来构建编译; >这两个路径仅作了解即可,下一节创建项目时会说明如何在IoT Studio中配置。 ## 3.4. 启动IoT Studio 安装完develop Tools之后,再次启动IoT Studio,无提示说明开发工具安装成功,IoT Studio的主界面如下: ![](http://mculover666.cn/blog/20191126/IbKAJj4fYehp.png?imageslim) # 4. IoT Studio使用指南 在IoT Studio启动页面,选择`用户指导文档`,即可打开 IoT Studio 完整的使用指南,在以后使用的过程中遇到任何问题,都可以在此文档中找到答案: ![](http://mculover666.cn/blog/20191126/eC7dHp3Ce1Uh.png?imageslim)
-
8 LiteOS 移植摘要:本文主要从为什么移植,以及移植的分类,开发环境、移植流程以及测试5个角度对采用非接管中断的方式将LiteOS移植到GD32450i-EVAL(2019)开发板上的详细流程介绍,希望对你有所帮助。1 为什么移植?嵌入式设备的芯片型号和外设的差异较大,资源有限。而RTOS无法适配集成所有的驱动,因此会先适配部分开发板,然后通过移植使得适配更多的开发板。可移植性是嵌入式操作系统与普通操作系统的显著区别之一,而所谓移植就是通过一定的代码修改使得该操作系统适配自己的开发板,以使得自己的开发板可以运行一些手头开发板没有配套的编译工程。2 移植的分类移植通常分为系统移植和驱动移植,驱动移植需要依赖具体的外设,本文主要介绍操作系统的移植。采用的主要方案是硬中断接管和不接管中断中的更加简便快捷的不接管中断方式。3 开发环境软件环境:Windows系统、Keil5、J-Link对应驱动;硬件环境:GD32450i-EVAL开发板、J-Link下载器、串口线、数据线;注意:上述环境也可根据自己需求进行修改,如使用IAR、GCC等。4 移植流程4.1 准备工作(1)下载LiteOS源码在github上下载最新的LiteOS源码,地址:https://github.com/LiteOS/LiteOS,下载任意版本皆可,其源码核心一致,我这里使用的是dev-deserted,其工程目录详情如图1所示。 图1 LiteOS源码工程目录以及对应描述(2)提取LiteOS核心移植文件对LiteOS源码做一个简单的提取便于后续操作的简洁性,当然也可以不提取,新建文件夹命名为LiteOS,将 LiteOS源码中的arch文件、components/cmsis文件、kernel文件、以及targets文件中的示例工程中拷贝两个OS_CONFIG文件,其中一个为接管中断(如Standard_GD32F103C_START工程中的OS_CONFIG文件)一个为非接管中断(如Standard_STM32F103VC_TAIBI工程中的OS_CONFIG文件)。具体如下表1所示。表1 LiteOS核心文件提取一级目录一级目录一级目录描述archarmarm-mM 核中断、调度、tick相关代码commonarm核公用的 cmsis core 接口cmsisLiteOS 提供的 cmsis os接口实现kernelbasecoreLiteOS 基础内核代码文件,包括队列、task 调度、软 timer、时间片等功能OM与错误处理相关的文件IncludeLiteOS 内核内部使用的头文件ipcLiteOS 中 ipc 通讯相关的代码文件,包括事件、信号量、消息队列、互斥锁等memLiteOS 中的内核内存管理的相关代码misc内存对齐功能以及毫秒级休眠 sleep 功能includeLiteOS 开源内核头文件extendedtickless低功耗框架代码OS_CONFIG非接管中断OS_CONFIG_Take接管中断(3)创建裸机工程使用前面的串口示例代码作为裸机工程,当然也可以使用keil自己创建裸机工程,自己创建的裸机工程一定要进行简单的功能测试。将步骤(2)中的LiteOS文件拷贝到裸机工程根目录下,具体如图2所示。图2 LiteOS目录概图4.2 添加内核源码在裸机工程中,添加LiteOS相关文件夹,分别命名为LiteOS/cmsis、LiteOS/arch、LiteOS/kernel、LiteOS/config(可有可无,便于后期操作而已),并分别将cmsis os代码、kernel代码、arch代码、OS_CONFIG文件等添加到对应的文件夹,具体如图3所示。图3 添加工程分组其中需要添加的源码以及对应的文件所在位置具体如下表所示。添加后的文件目录详情,具体如图4所示。图4 添加Lite OS源码4.3 添加头文件添加源码之后,需要添加对应应用的头文件,具体如图5所示。图5 添加头文件4.4 修改target.h文件由于采用非接管中断方式移植LiteOS,因此修改文件较少。首先修改target.h文件的头文件应用,其头文件如图6所示。第43行,需要修改为开发板相对应的系列头文件。图6 target.h头文件修改在target.h文件中的开发板内存模块配置的位置,找到下列代码,其中一个是参数是起始地址,一个是开发板实际的SRAM的大小,但是该值一般设置略小于实际SRAM大小,因为工程存储需要占用一定空间。我们可以使用keil的pack Installer查看开发板的SRAM大小,具体如图7所示。可见GD32450i-EVAL(2019)其SRAM大小为256K。#define BOARD_SRAM_START_ADDR 0x20000000 #define BOARD_SRAM_SIZE_KB 128图7 查看开发板SRAM大小target.h文件具体修改方式如图8所示。注意其中的起始地址设置要与工程配置中的IRAM起始地址保持一致。图8 修改内存起始地址4.5 注释回调函数代码由于PendSV_Handler以及SysTick_Handler(void)函数在LiteOS源码以及裸机工程中的gd32f4xx_it.c文件中都有定义,因此如果直接编译会出现重定义的错误,如图9所示。此时我们需要将对应gd32f4xx_it.c中代码注释掉或删除即可。图9 运行错误详情5 测试上面已经将移植工作基本完成,后面只需要做一下测试即可,具体包括硬件测试以及LiteOS测试。5.1 硬件启动测试对于测试,首先需要将串口调试好,便于后续的调试,此处我以串口函数以及LED灯进行测试。在使用LiteOS系统之前首先需要将开发板启动起来,具体如下所示。但是需要注意的是初始化的时候不要调用delay函数,因为SysTick_Handler(void)函数被注释掉了,如果调用该函数会导致,程序卡死在delay处。测试的时候一定要注意main函数中要有while循环使其卡停在此处。#include "gd32f4xx.h" #include "gd32f450i_eval.h" #include "systick.h" #include <stdio.h> void Hardware_Init(void); void led_init(void); int main() { Hardware_Init(); printf("\r\n USART printf example: please press the Tamper key \r\n"); while(1){ gd_eval_led_on(LED1); } } void Hardware_Init(void) { led_init(); systick_config(); gd_eval_com_init(EVAL_COM1); } void led_init(void) { gd_eval_led_init(LED1); } int fputc(int ch, FILE *f) { usart_data_transmit(EVAL_COM1, (uint8_t)ch); while(RESET == usart_flag_get(EVAL_COM1, USART_FLAG_TBE)); return ch; }5.2 移植功能测试接下来只需要对Lite OS移植进行测试,对主函数进行修改实现任务的创建于调用即可,主函数具体如下所示。#include "gd32f4xx.h" #include "gd32f450i_eval.h" #include "systick.h" #include <stdio.h> #include "los_sys.h " #include "los_typedef.h" #include "los_task.ph" UINT32 g_TskHandle; void Hardware_Init(void); void led_init(void); UINT32 creat_task1(); UINT32 creat_task2(); int main(){ Hardware_Init(); printf("\r\n USART printf example: please press the Tamper key \r\n"); UINT32 uwRet = 0; uwRet = LOS_KernelInit(); if (uwRet != LOS_OK){ return LOS_NOK; } uwRet = creat_task1(); if (uwRet != LOS_OK){ return LOS_NOK; } uwRet = creat_task2(); if (uwRet != LOS_OK){ return LOS_NOK; } LOS_Start(); while(1){ gd_eval_led_on(LED1); } } void Hardware_Init(void){ led_init(); systick_config(); gd_eval_com_init(EVAL_COM1); } void led_init(void){ gd_eval_led_init(LED1); } int fputc(int ch, FILE *f){ usart_data_transmit(EVAL_COM1, (uint8_t)ch); while(RESET == usart_flag_get(EVAL_COM1, USART_FLAG_TBE)); return ch; } void task1(void){ int count = 1; while(1){ printf("This is task1, count is %d\r\n", count++); LOS_TaskDelay(1000); } } UINT32 creat_task1(){ UINT32 uwRet = LOS_OK; TSK_INIT_PARAM_S task_init_param; task_init_param.usTaskPrio = 0; task_init_param.pcName = "task1"; task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)task1; task_init_param.uwStackSize = 0x200; uwRet = LOS_TaskCreate(&g_TskHandle, &task_init_param); if(LOS_OK != uwRet){ return uwRet; } return uwRet; } void task2(void){ int count = 1; while (1){ printf("This is task2,count is %d\r\n",count++); LOS_TaskDelay(2000); } } UINT32 creat_task2(){ UINT32 uwRet = LOS_OK; TSK_INIT_PARAM_S task_init_param; task_init_param.usTaskPrio = 1; task_init_param.pcName = "task2"; task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)task2; task_init_param.uwStackSize = 0x200; uwRet = LOS_TaskCreate(&g_TskHandle, &task_init_param); if(LOS_OK != uwRet){ return uwRet; } return uwRet; }运行结果如图10所示,可以看见两个任务在交替打印。移植完成,本例程使用的是非接管中断的方式,如果使用接管中断方式的,需要拷贝结果中断的OS_CONFIG文件,同时对于target.h文件需要额外的修改,同时修改.sct文件(在keil工程下的Objects文件夹下),而不是使用自动生成的.sct文件.图10 Lite OS移植成功运行结果目录主博文:https://bbs.huaweicloud.com/blogs/124244
-
API参考界面没有相应的内容
-
7 Timer摘要:本文主要介绍了GD32450i-EVAL开发板的Timer定时器,首先介绍了其定时器的数目以及分类,并对不同分类的定时器做了一定的对比,之后对于定时器的一些常用功能做了简要的介绍,并使用通用定时器L1进行了呼吸灯实验,并展示实验效果。希望对大家有所帮助。1 简介Timer即定时器,与SysTick定时器不同的是,并非ARM Cortex-M3/M4处理器都有,而是某一些开发板特有的,且数量和类别存在一定差别。GD32450i-EVAL开发板共有14个定时器,分别为TIMER0-TIMER13,其中有2个高级定时器,10个通用定时器,2个基本定时器,具体如表1所示。表1 GD32450i-EVAL开发板定时器定时器编号0、71、2、3、48、119、10、12、135、6类型高级通用L0通用L1通用L2基本预分频位数1616161616计数器位数1632(1、4)16(2、3)161616捕获/比较通道数44210计数模式向上、中央对齐、向下向上、中央对齐、向下向上向上向上2 定时器分类定时器按照分类分为基本定时器、通用定时器、高级定时器,其中通用定时器包括通用定时器L0、L1、L2。定时器都具有16位可编程预分频器、16位无符号计数器、自动重载计数器。2.1 基本定时器基本定时器结构较为简单,主要包括两个功能,(1)基本定时功能,生成时基;(2)用于驱动数模转换器,定时器5和定时器6,功能完全一样,但是所用资源彼此完全独立,可以同时使用。基本定时器具有以下特性:时钟源:内部时钟;可产生DMA请求,TRGO连接到DAC;触发DAC的同步电路;中断/DMA请求:更新事件;其结构框图如图1 所示。图1 基本定时器结构框图2.2 通用定时器通用定时器可被同步形成一个更大的定时器。通用定时器包括通用定时器L0、通用定时器L1、通用定时器L2。除上述表1中提到的,三者存在相同之处也存在一定区别。2.2.1 通用寄存器共同特性时钟源可选:内部时钟、内部触发、外部输入、外部触发。每个通道可配置:输入捕获模式,输出比较模式,可编程的PWM模式,单脉冲模式。2.2.1 通用寄存器特性差异(1)通用定时器L04通道定时器,支持输入捕获、输出比较、产生PWM信号控制电机和电源管理;正交编码器接口:被用来追踪运动和分辨旋转方向和位置;霍尔传感器接口:用来做三相电机控制;中断输出和DMA请求:更新事件,触发事件,比较/捕获事件;多个定时器的菊链使得一个定时器可以同时启动多个定时器。图2 通用计时器L0结构框图(2)通用定时器L1中断输出:更新事件,触发事件,比较/捕获事件和中止事件;多个定时器的菊链使得一个定时器可以同时启动多个定时器;定时器的同步允许被选择的定时器在同一个时钟周期开始计数;定时器主/从模式控制器。图3 通用计时器L1结构框图(3)通用定时器L2中断输出:更新事件,触发事件,比较/捕获事件和中止事件。图4 通用计时器L2结构框图 2.3 高级定时器高级寄存器相对于基本定时器以及高级定时器具有更强大的功能。具体如下所示:正交编码器接口:被用来追踪运动和分辨旋转方向和位置;霍尔传感器接口:用来做三相电机控制;每个通道可配置:输入捕获模式,输出比较模式,可编程的PWM模式,单脉冲模式;可编程的死区时间;可编程的计数器重复功能;中止输入功能使定时器按照用户的配置输出;中断输出和DMA请求:更新事件,触发事件,比较/捕获事件和中止事件;多个定时器的菊链使得一个定时器可以同时启动多个定时器;定时器的同步允许被选择的定时器在同一个时钟周期开始计数;定时器主/从模式控制器。其结构如图5所示。图5 高级定时器结构框图3 功能描述可见定时器具有很多功能,下面仅对几个常见的基础功能加以介绍。3.1 时基时基即时间基准,就是一个时间显示的基本单位。时基单元包括计数器寄存器,预分频器寄存器,自动重载寄存器。计数器、自动重载寄存器和预分频器寄存器都可以通过软件进行读写,即使在计数器运行时。3.2 时钟源对于基本定时器时钟源有内部时钟源提供,对于通用定时器和高级寄存器,其时钟源可以有内部时钟源或者由从模式寄存器(SMC)控制复用的时钟源驱动。包括内部时钟源、外部时钟模式0和外部时钟模式1。3.3 预分频器计数器的时钟可以通过预分频器进行1到65536之间任意值分频,分频后的时钟驱动计数器计数,分频系数受预分频寄存器控制,其带有缓冲器,可以在运行时被改变。新的预分频器的参数在下一次更新事件到来时被使用。3.4 计数模式计数模式共分为三种,包括向上计数模式、向下计数模式、中央对齐模式,对于不同类别的计数器,其具有的计数器模式各不相同,具体如表1所示。(1)向上计数模式向上计数模式即计数器从0开始向上连续计数到自动加载值(定义在自动重载寄存器中),并生成计数器上溢事件,当计数到自动重载值时,会重新从0开始计数。每次计数器上溢时会生成更新事件。但是如果设置了重复计数器,在(TIMERx_CREP+1)次上溢后产生更新事件。当产生更新事件时,所有寄存器都将被更新。(2)向下计数模式与向上计数方式相反,该模式下,计数器从自动重载值向下连续计数到0(倒计时),并生成计数器下溢事件,当计数器到0时,会重新开始计数,每次计数器下溢时会产生更新事件。但是如果设置了重复计数器,在(TIMERx_CREP+1)次上溢后产生更新事件。当产生更新事件时,所有寄存器都将被更新。(3)中央对齐模式与前两者不同,该模式下,计数器交替的从0开始向上计数到自动加载值,然后在向下计数到0。向上计数时,定时器模块在计数器计数到自动加载值-1时产生一个上溢事件;向下计数时定时器在计数器计数到1时产生一个下溢事件。在中央计数模式中,寄存器中的计数方向控制位设置为只读。计数方向被硬件自动更新。将SWEVG寄存器的UPG位置1时可以初始化计数值为0,并产生一个更新事件。3.5 重复计数器重复计数器是用来在N+1个计数器周期之后产生更新事件,更新定时器的寄存器。向上计数模式下,重复计数器在每次计数器上溢时递减;向下计数模式下,冲反复计数器在每次计数器下溢时递减;中央对齐模式下,重复计数器在计数器上溢和下溢时递减。3.6 捕获/比较输出通道定时器的通道用于捕获输入或比较输出是否匹配,通道围绕一个通道捕获比较寄存器简历,包括一个输入级、通道控制器和输出级。捕获模式允许通道测量一个波形时序、频率、周期、占空比等,输入级包括一个数字滤波器、一个通道极性选择、边沿检测和一个通道预分频器。如果在输入引脚上出现被选择的边沿,TIMERx_CHxCV寄存器会捕获计数器当前的值,同时CHxIF位被置1,如果CHxIE = 1则产生通道中断。通道输入信号CIx先被TIMER_CK信号同步,然后经过数字滤波器采样,产生一个被滤波后的信号。通过边沿检测器,可以选择检测上升沿或者下降沿。通过配置CHxP选择使用上升沿或者下降沿。配置CHxMS.,可以选择其他通道的输入信号,内部触发信号。配置IC预分频器,使得若干个输入事件后才产生一个有效的捕获事件。捕获事件发生,CxC存储计数器的值。配置主要包括5步:滤波器配置、边沿选择、捕获源选择、中断使能、捕获使能。在输出比较模式,TIMERx可以产生时控脉冲,其位置,极性,持续时间和频率都是可编程的。当一个输出通道的CxCV寄存器与计数器的值匹配时,根据CHxCOMCTL的配置,这个通道的输出可以被置高电平,被置低电平或者反转。当计数器的值与CxCV寄存器的值匹配时,CHxIF位被置1,如果CHxIE = 1则会产生中断,如果CxCDE=1则会产生DMA请求。配置过程主要包括5步:时钟配置、比价模式配置、中断或DMA使能、使能定时器。3.7 PWM模式在PWM输出模式下,根据TIMERx_CAR寄存器和TIMERx_CHxCV寄存器的值,输出PWM波形。根据计数模式,我们可以分为两种PWM波:EAPWM(边沿对齐PWM)和CAPWM(中央对齐PWM)。EAPWM 的周期由 TIMERx_CAR 寄存器值决定,占空比由 TIMERx_CHxCV 寄存器值决定。CAPWM 的周期由(2*TIMERx_CAR 寄存器值)决定,占空比由(2*TIMERx_CHxCV 寄存器值)决定。3.8 调试模式与SysTick定时器不同的是,当MCU进入调试模式时,计数器会根据DBG模块中的DBG_CTL2寄存器TIMERx_HOLD配置位选择继续工作与否,而不是直接伴随着内核的停止而终止,该位置1计数停止。4 示例4.1 程序主函数主要包括GPIO配置和时钟配置,以及延时函数用到的SysTick配置,之后就是对于呼吸灯的循环变量控制。下面主要对定时器配置加以介绍,主要包括对于两个结构体的初始化,定时器比较输出结构体以及定时器捕获输入。实现对于PWM模式0的配置,最终对于通道2实现PWM模式0,占空比为25%。void timer_config(void) { timer_oc_parameter_struct timer_ocintpara; //定时器比较输出初始化结构体 timer_parameter_struct timer_initpara; // 定时器捕获输入初始化结构体 rcu_periph_clock_enable(RCU_TIMER1); // 使用TIM1时钟 rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); //配置定时器时钟和预分频器选择 timer_deinit(TIMER1); timer_initpara.prescaler = 119; //时钟预分频器 timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //计数器触发方式 timer_initpara.counterdirection = TIMER_COUNTER_UP; //计数器模式选择向上计数 timer_initpara.period = 500; // 定时器周期个数 自动重载值 timer_initpara.clockdivision = TIMER_CKDIV_DIV1; //时钟分频因子 timer_initpara.repetitioncounter = 0; //重复计数值 timer_init(TIMER1,&timer_initpara); //初始化计数器 timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; // 有效电平的极性,可以将有效电平设置成高电平或者低电平 timer_ocintpara.outputstate = TIMER_CCX_ENABLE; //配置比较输出模式状态(使能或禁止输出) timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH; //互补输出有效极性 timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE; //互补比较输出状态 timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW; //空闲状态下定时器输出引脚状态 timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW; //空闲状态定时器互补输出引脚状态 timer_channel_output_config(TIMER1,TIMER_CH_2,&timer_ocintpara); //配置定时器通道输出功能 timer_channel_output_pulse_value_config(TIMER1,TIMER_CH_2,0); //配置定时器通道输出脉冲值 timer_channel_output_mode_config(TIMER1,TIMER_CH_2,TIMER_OC_MODE_PWM0); //配置定时器通道输出比较模式 timer_channel_output_shadow_config(TIMER1,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE); //配置定时器通道输出影子寄存器 timer_auto_reload_shadow_enable(TIMER1); timer_enable(TIMER1); }4.2 硬件操作首先使用杜邦线将Timer_CH2(PB10)和 LED 1(PE2)连接到一起,然后将程序下载到开发板,按复位键后可见LED灯由暗变亮,往复循环,如人的呼吸一样,因此称为呼吸灯。通过本实验可以学习到如何使用定时器输出PWM波,以及更新定时器通道寄存器的值。具体实验效果如图6以及附件视频所示。图6 呼吸灯效果目录主博文:https://bbs.huaweicloud.com/blogs/124244
上滑加载中
推荐直播
-
华为云AI入门课:AI发展趋势与华为愿景
2024/11/18 周一 18:20-20:20
Alex 华为云学堂技术讲师
本期直播旨在帮助开发者熟悉理解AI技术概念,AI发展趋势,AI实用化前景,了解熟悉未来主要技术栈,当前发展瓶颈等行业化知识。帮助开发者在AI领域快速构建知识体系,构建职业竞争力。
去报名 -
华为云软件开发生产线(CodeArts)10月新特性解读
2024/11/19 周二 19:00-20:00
苏柏亚培 华为云高级产品经理
不知道产品的最新特性?没法和产品团队建立直接的沟通?本期直播产品经理将为您解读华为云软件开发生产线10月发布的新特性,并在直播过程中为您答疑解惑。
去报名
热门标签