IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录

概述

本实验基于LiteOS Studio 工具进行物联网终端的开发,使用LiteOS操作系统进行物联网开发板的控制。实验主要目的:

  • 掌握LiteOS Studio的使用
  • 掌握LiteOS操作系统任务的使用
  • 掌握LiteOS操作系统内存管理的使用
  • 掌握LiteOS操作系统互斥量和信号量使用
  • 熟悉LCD屏幕的使用 (在实验4中)
  • 熟悉开发板的LED和按键使用 (在实验4中)
  • OSAL接口使用
  • CMSIS接口使用(CMSIS任务和消息队列接口使用等)

@History

在进行本实验前,请先阅读 #<IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 LiteOS Studio + GCC + JLink>#,文中较详细的介绍了LiteOS Studio的搭建和使用方法,文中我们也提及了LiteOS工程(LiteOS_Lab_HCIP),但没有使用它,而是直接使用了BearPi-IoT_Std_LiteOS 源码。为了贴合实验指导书的步骤,我们在这里选用 LiteOS_Lab_HCIP源码。

在写本文前,我已经尝试了不同形式的物联网开发IDE,分别如下几篇文章中:
#<IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 LiteOS Studio + GCC + JLink>#
#<IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 VSCode + IoT Link 插件>#
#<IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 VSCode + GitBash + GCC工具>#

如果主要目标是为了完成HCIP-IOT实验,我是建议选用指导书中指定的LiteOS Studio开发环境,不仅是因为贴合指导书中的步骤,减少不必要的麻烦。华为云IoTDA中提及的VSCode + IoT Link 模式,由于要使用低版本的VSCode,让人很不爽,配置和调试操作都体验不咋地。相比较而言,LiteOS Studio 虽然是基于VSCode的,但其与你已经安装的VSCode不会产生环境变量或软件安装层次的冲突,算是优势吧。

HelloWorld 工程

请参见 #<IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 LiteOS Studio + GCC + JLink># 完成此部分实验。

C/C++配置

c_cpp_properties.json 是 VSCode 中 C/C++ 扩展的核心配置文件,主要用于配置代码分析和开发环境,确保 IntelliSense(智能代码补全、错误检查等)能够准确理解项目结构和编译器行,需要作出如下修改,

编译器主配置

关于目标板卡配置、组件配置、编译器,参见前文提到的 #<IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 LiteOS Studio + GCC + JLink>#,不再赘述。编译器配置下的Makefile脚本选择H:\HuaWeiYun\LiteOS_Lab_HCIP\targets\STM32L431_BearPi\GCC下的Makefile文件。

Makefile脚本

这块在HCIP-IoT实验手册中并没有提及。Makefile(脚本)最终编译哪个示例工程,是由LiteOS_Lab_HCIP\targets\STM32L431_BearPi.config文件决定的。

最简单的改变该文件的方法是,从\Demos各示例程序文件夹下拷贝defaults.sdkconfig全部内容,当然,也可以借助menuconfig和genconfig生成,那就稍微复杂些了,可以参考本专栏下其他文章

烧录器主配置

烧录器、调试器配置,参见前文提到的 #<IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 VSCode + IoT Link 插件>#,采用ST-Link+OpenOCD模式。烧录文件要编译成功后才有哦。但还是建议使用JLink模式。

但还是建议使用JLink模式。这一节使用ST-Link+OpenOCD,体验很差劲。

运行结果

小熊派开发板的这个串口,感觉不太顶用,我就拔插过两三次,感觉就有接触不良的情况偶发啦。

程序调用栈

LiteOS_Lab_HCIP\targets\STM32L431_BearPi\Src\main.c

cpp 复制代码
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);

    link_test();  //第一步

    (void)LOS_Start();
    return 0;
}

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;
}

LiteOS_Lab_HCIP\iot_link\link_main.c

cpp 复制代码
int link_main(void *args) {
	...
#ifdef CONFIG_LINKDEMO_ENABLE
    extern int standard_app_demo_main(void);
    (void) standard_app_demo_main();   //第三步
#endif
    ...
}

LiteOS_Lab_HCIP\targets\STM32L431_BearPi\Demos\hello_world_demo\hello_world_demo.c

cpp 复制代码
//任务入口函数
static int app_hello_world_entry()   {
    while (1)    {
        printf("Hello World! This is BearPi!\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;
}

任务管理实验

新添加任务入口函数并创建任务

cpp 复制代码
void *task1 = NULL, *task2 = NULL; int num = 0;
//添加任务2
static int hcip_iot_task(void) {
    while (1)    {
        printf("This is task2!\r\n");
        #if 1
        if (num == 3) {
            osal_task_kill(task1);
        }
        #endif
        osal_task_sleep(4*1000);
    }
}

//宏控制的本项目下的任务创建接口/每个示例下该函数声明相同
int standard_app_demo_main() {
    task1 = osal_task_create("helloworld",app_hello_world_entry,NULL,0x400,NULL,2);
    //新创建任务
    task2 = osal_task_create("task2",hcip_iot_task,NULL,0x400,NULL,2);
    return 0;
}

实验结果

任务添加测试, 两个任务同时运行,

如上,任务1和任务2同时执行,同时打印输出。

任务删除测试,

如上,任务1被关闭后,周任务2在执行。

几个注意事项:

1、在编译或重新编译前前,要先关闭串口,否则可能使得IDE卡主,不能执行编译过程,必须得重启软件。

2、在烧录器配置烧录.hex文件时,遇到过显示烧录成功(且复位过)但是不生效的情况,可以多尝试几次或配置烧录.bin文件。

osal 系统适配层

在这里我们简单看看osal适配层的实现方法,后期我们会单独讲解。

个人觉得osal与cmsis有些类似,在上述os目录下,osal和linux/ucos_ii等系统目录平级,cmsis目录在liteos之下,如下图,

OSAL定义统一的系统调用接口(如线程管理、通信),使应用无需关注底层内核(如LiteOS与Linux的差异)。例如,鸿蒙通过OSAL层兼容Linux与LiteOS内核。 CMSIS 为Cortex处理器提供标准外设访问API(如寄存器映射)及RTOS接口(CMSIS-RTOS2),确保代码可跨RTOS(如FreeRTOS、RTX5)复用。

osal_task_create

cpp 复制代码
//osal 操作系统适配层函数
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;
}

osal_task_create 函数调用堆栈分析,

其他实验

互斥锁、内存管理、信号量实验,统一参见如下代码。

实验源码

cpp 复制代码
#define SWITCH_TEST_HELLO  0
#define SWITCH_TEST_TASK   0
#define SWITCH_TEST_MUTEX  1
#define SWITCH_TEST_MEM    0
#define SWITCH_TEST_SEMP   0

//任务句柄
void *task1 = NULL, *task2 = NULL; int num = 0;

#if SWITCH_TEST_MUTEX
//需要保护的公共资源
uint32_t public_value = 0;
//互斥锁
osal_mutex_t public_value_mutex;
#endif
//信号量实验
osal_semp_t sync_semp = NULL;

#if SWITCH_TEST_HELLO
static int app_hello_world_entry()
{
    while (1)
    {
        printf("Hello World! This is BearPi!\r\n");
        osal_task_sleep(4*1000);
    }
}
#endif

#if SWITCH_TEST_TASK
static int hcip_iot_task(void) {
    while (1)    {
        printf("This is task2, num:%d \r\n", ++num);
        #if 1
        if (num == 3) {
            osal_task_kill(task1);
        }
        #endif
        osal_task_sleep(4*1000);
    }
}
#endif

#if SWITCH_TEST_MUTEX
//互斥锁/任务1入口
static int mutex_task1_entry() {
    while (1) {
        if (true == osal_mutex_lock(public_value_mutex)) {
            printf("task1: lock a mutex.\r\n");
            public_value += 10;
            printf("task1: public_value = %ld.\r\n", public_value);
            printf("task1: sleep...\r\n");
            osal_task_sleep(10); //ms
            printf("task1: continue...\r\n");
            printf("task1: unlock a mutex.\r\n");
            osal_mutex_unlock(public_value_mutex);
            if (public_value > 60) break; 
        } //if
    } //while
    return 0;
}

//互斥锁/任务2入口
static int mutex_task2_entry() {
    while (1) {
        if (true == osal_mutex_lock(public_value_mutex)) {
            printf("task2: lock a mutex.\r\n");
            public_value += 5;
            printf("task2: public_value = %ld.\r\n", public_value);
            printf("task2: unlock a mutex.\r\n");
            osal_mutex_unlock(public_value_mutex);
            if (public_value > 50) break; 
            #if 0  //task2 not sleep
            osal_task_sleep(10); //ms
            #endif
        } //if
    } //while
    return 0;
}
#endif

#if SWITCH_TEST_MEM
//内存管理实验/任务1
static int mem_access_task_entry() {
    uint32_t i = 0;          //for look
    size_t mem_size = 0;     //
    uint8_t *mem_ptr = NULL; //内存块指针
    //loop
    while (1) {
        //每次循环申请的块大小扩一倍
        mem_size = 1 << i++;
        //执行申请操作
        mem_ptr = osal_malloc(mem_size);
        //success
        if (NULL != mem_ptr) {
            printf("access %d bytes memory success!\r\n", mem_size);
            osal_free(mem_ptr);
            mem_ptr = NULL;
            printf("free memory success!\r\n");
        }
        else {
            printf("access %d bytes memory failed!\r\n", mem_size);
            return 0;
        }
    }
    
    return 0;
}
#endif

//信号量实验
#if SWITCH_TEST_SEMP
//信号量实验/任务1
static int semp_task1_entry() {
    printf("task1: post a semp.\r\n");
    osal_semp_post(sync_semp);
    printf("task1: end.\r\n");
}

//信号量实验/任务1
static int semp_task2_entry() {
    printf("task2: watting for a semp...\r\n");
    osal_semp_pend(sync_semp);
    printf("task2: access a semp.\r\n");
}
#endif

//示例初始化函数
int standard_app_demo_main() {
//原HelloWorld
#if SWITCH_TEST_HELLO
    osal_task_create("helloworld",app_hello_world_entry,NULL,0x400,NULL,2);
#endif

//互斥锁实验
#if SWITCH_TEST_MUTEX
    //创建互斥锁
    osal_mutex_create(&public_value_mutex);
    //创建任务 //const char *name,int (*task_entry)(void *args), void *args,int stack_size,void *stack,int prior
    task1 = osal_task_create("mutex_task1", mutex_task1_entry, NULL, 0x400, NULL, 12);
    //创建任务 //const char *name,int (*task_entry)(void *args), void *args,int stack_size,void *stack,int prior
    task2 = osal_task_create("mutex_task2", mutex_task2_entry, NULL, 0x400, NULL, 11);
#endif

//内存实验
#if SWITCH_TEST_MEM
    //创建任务
    task2 = osal_task_create("mem_task", mem_access_task_entry, NULL, 0x400, NULL, 11);
#endif

//信号量实验
#if SWITCH_TEST_SEMP
    //创建信号量 /数量1初始值0
    osal_semp_create(&sync_semp, 1, 0);
    //任务1优先级低,负责释放信号量
    task1 = osal_task_create("semp_task1", semp_task1_entry, NULL, 0x400, NULL, 12);
    //任务2优先级高,先进入等待/申请信号量的状态
    task2 = osal_task_create("semp_task2", semp_task2_entry, NULL, 0x400, NULL, 11);
#endif

    return 0;
}

内存管理实验

互斥锁实验

如上实验结果,如果高优先级的任务不睡眠,则低优先级任务必要要等到高优先级任务退出后才有机会执行。

信号量实验

这里,semp_task2_entry优先级高,会先执行,进入pend等待信号量状态,函数会使得当前任务进入阻塞状态,从而让出 CPU 资源给其他任务使用,如这里优先级稍低的semp_task1_entry任务。待task1释放信号量后,task2被唤醒继续执行。

CMISIS接口实验

与FreeRTOS一样,LiteOS也支持CMSIS,这简直是福利 。在以下目录 LiteOS_Lab_HCIP\iot_link\os\liteos\cmsis

前面几个小实验,纯粹是为了体验OpenOCD模式,但真的很难用啊。在进行CMSIS实验时,哈哈也不知道是咋鼓捣的,基于STLink+OpenOCD的调试环境,它之间罢工了。在配置文件、OpenOCD版本等方向尝试修复无果。

于是乎,我又乖乖的将板载的STLink刷成了JLink,该过程参考 #<IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 LiteOS Studio + GCC + JLink># 文章。这个二次烧录Jlink固件的过程,也很崎岖...

1、在 #<IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 VSCode + IoT Link 插件>#文中,提到了使用 STLinkReflash 将JLink 刷回 STLink 也不顺利。

2、在第一步中,通过STM32 ST-LINK Utility升级了调试器固件。

3、在上述两步基础上,再尝试使用 STLinkReflash 将STLink刷成JLink是失败的。后来,我重新安装了 STlink驱动、更换了usb接口、重启过电脑,等一系列组合拳下来,竟然成功啦。哈哈,不想再尝试了。就按照Studio官方建议,这么用吧。

4、J-Link 固件内置 J-Link GDB Server,可直接与调试工具(如 LiteOS Studio 的 GDB 客户端)通信,无需中间层协议转换。这种直接集成减少了调试链路的复杂性,提高运行效率。刷写后的 ST-Link(J-Link OB)可实现高达 1.8MHz 的下载速率,显著快于 OpenOCD + ST-Link 的组合。ST-Link没有内置GDB服务,因此要借助外部的openocd.exe做GDB服务器。

CMSIS 简介

随着 32 位处理器在嵌入式市场需求量逐渐增多,各家芯片公司推出新型芯片,伴随而

来的是开发工具、软件兼容以及代码移植等问题。在这种情况下,各个硬件平台的供应商都

寻求易于使用且高效的解决方案,其中,ARM 与 Atmel、IAR、KEIL、SEGGER 和 ST 等诸

多芯片和软件工具厂商合作,发布了一套 CMSIS 标准。

CMSIS(Cortex Microcontroller Software Interface Standard),即 ARM Cortex 微控制器软

件接口标准。CMSIS 标准提供了内核和外围设备、实时操作系统和中间组件之间的通用 API

接口,从而简化了软件的重复使用,缩短了微控制器开发人员的学习时间,并缩短了新设备

的上市时间。下图是 ARM 公司的 CMSIS 标准结构框图:

其中,CMSIS-CORE 层定义了 Cortex-M 以及 Cortex-A 处理器(Cortex-A5/A7/A9)内核

和外围设备的标准化 API。CMSIS-Pack 层包含了 CMSIS-Driver 驱动框架、CMSIS-DSP 相关

库、CMSIS-RTOS 操作系统 API、中间件 API 和 Peripheral HAL 层 API 等。根据 CMSIS 的标准,ARM 公司整合并提供了 CMSIS 软件包模板。基于 ARM 提供的 CMSIS 软件包模板,ST 官方结合自己芯片的差异进行了修改,并将其整合到了 STM32Cube 固件包中的 CMSIS 文件夹里。

LiteOS->CMSIS

除了ST的HAL支持外,LiteOS也要提供支持,以osThreadNew为例,

cpp 复制代码
//cmsis_liteos2.c /定义在cmsis_os2.h中
osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr) {
	...
	uwRet = LOS_TaskCreate(&uwTid, &stTskInitParam);
	...
}

上述 LOS_TaskCreate 实现在 LiteOS_Lab_HCIP\iot_link\os\liteos\base\core 内核中。在osal层的任务创建函数 osal_task_create,其最后也要调用上述 LOS_TaskCreate 内核实现。 该函数的实现,我们不再深入。

任务间消息交互

cpp 复制代码
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <osal.h>
#include <cmsis_os.h> //CMSIS_OS_VER==2

#define SWITCH_TEST_HELLO  0
#define SWITCH_TEST_TASK   0
#define SWITCH_TEST_MUTEX  0
#define SWITCH_TEST_MEM    0
#define SWITCH_TEST_SEMP   0
#define SWITCH_TEST_CMSIS  1

//cmsis接口
#if SWITCH_TEST_CMSIS
//消息队列句柄 /void*
osMessageQueueId_t cmsis_queue;
//消息队列消息
typedef struct cmsis_msg {
    int a;
    int b;
} TMsg;

///typedef void (*osThreadFunc_t) (void *argument);
//任务1 /发送消息 
static void cmsis_task1_entry(void *argument) {
    TMsg tMsg = {0, 0};
    while (1) {
        //
        tMsg.a += 1;
        tMsg.b += 2;
        //(osMessageQueueId_t mq_id, const void *msg_ptr, uint8_t msg_prio, uint32_t timeout)
        osMessageQueuePut(cmsis_queue, &tMsg, 0, 10);
        //打印发送的消息
        printf("Send Msg a:%d b:%d\r\n", tMsg.a, tMsg.b);
        //睡眠
        osal_task_sleep(1*1000);
        //任务退出
        if (tMsg.a > 100) break;
    }
}

///typedef void (*osThreadFunc_t) (void *argument);
//任务2 /接收消息
static void cmsis_task2_entry(void *argument) {
    TMsg tMsg; uint8_t msg_prio = 0;
    while (1) {
        //osStatus_t osMessageQueueGet (osMessageQueueId_t mq_id, void *msg_ptr, uint8_t *msg_prio, uint32_t timeout)
        osMessageQueueGet(cmsis_queue, (void*)&tMsg, &msg_prio, osWaitForever);
        //打印收到的消息
        printf("Recv Msg a:%d b:%d\r\n", tMsg.a, tMsg.b);
    }
}
#endif

int standard_app_demo_main() {
//cmsis接口
#if SWITCH_TEST_CMSIS
    //创建消息队列/osMessageQueueId_t osMessageQueueNew (uint32_t msg_count, uint32_t msg_size, const osMessageQueueAttr_t *attr);
    cmsis_queue = osMessageQueueNew (5, sizeof(TMsg), NULL);
    const osThreadAttr_t thread_attr1 = {
    .name = "MyThread1",         // 线程名称(调试用)
    .stack_size = 1024,         // 栈大小(字节)
    .priority = osPriorityAboveNormal5  
    };
    const osThreadAttr_t thread_attr2 = {
        .name = "MyThread2",         // 线程名称(调试用)
        .stack_size = 1024,         // 栈大小(字节)
        .priority = osPriorityAboveNormal5
    };
    //任务1/发送消息 /osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr)
    osThreadId_t thread1 = osThreadNew (cmsis_task1_entry, NULL, &thread_attr1); 
    //任务2接收消息 /osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr)
    osThreadId_t thread2 = osThreadNew (cmsis_task2_entry, NULL, &thread_attr2);
    //
    printf("Hello cmsis!\r\n");
#endif

    return 0;
}

执行结果

其他

怎么说呢?挺不顺的,一定要有耐心。

相关推荐
AD钙奶-lalala2 小时前
Mac版本Android Studio配置LeetCode插件
android·ide·android studio
远创智控研发五部4 小时前
边缘计算网关提升水产养殖尾水处理的远程运维效率
物联网·远程监控·工业自动化·边缘计算网关·无线数传模块
敲敲敲-敲代码4 小时前
【Visual Studio 2022】卸载安装,ASP.NET
ide·visual studio
The Kite5 小时前
MPLAB X IDE 软件安装与卸载
ide·c#·嵌入式
AI视觉网奇5 小时前
pycharm F2 修改文件名 修改快捷键
ide·python·pycharm
WilliamCHW5 小时前
Pycharm 配置解释器
ide·python·pycharm
Ll13045252986 小时前
JsonCpp 库如何集成到Visual studio
ide·visual studio
我又来搬代码了7 小时前
【Android】Android Studio项目代码异常错乱问题处理(2020.3版本)
android·ide·android studio
WarPigs8 小时前
Visual Studio问题记录
ide·windows·visual studio
杰哥技术分享8 小时前
IDEA 打开文件乱码
java·ide·intellij-idea