Skip to content

Latest commit

 

History

History
2079 lines (1594 loc) · 60.5 KB

4.TencentOS-tiny开发指南.md

File metadata and controls

2079 lines (1594 loc) · 60.5 KB

TencentOS tiny开发指南

目录

[TOC]

1. 概述

TencentOS tiny是面向物联网(IOT)领域的操作系统,由一个实现精简的实时操作系统(RTOS)内核,以及丰富的物联网组件组成。

1.1 基础内核组件

  • 系统管理

系统管理模块,主要提供了内核的初始化、内核运行启动,中断进入/退出流程托管、系统调度锁定及解锁等功能。

  • 任务管理

提供了任务的创建、删除、睡眠、取消睡眠、挂起、恢复、优先级修改、主动放弃CPU等功能。

  • 任务间通信

提供互斥量、信号量、队列、事件等常用任务间通信机制。

  • 内存管理

提供了基于堆的动态内存管理,以及静态内存块管理机制。

  • 时间管理

提供了获取/设置系统时钟滴答数、系统时钟滴答数与墙上时钟时间转换、基于墙上时钟时间的任务睡眠等机制。

  • 软件定时器

提供了软件定时器的创建、删除、启动、停止等机制。

  • 时间片轮转机制

TencentOS tiny内核在提供可抢占式调度内核基础上,还提供了按时间片轮转的robin机制。

  • 内核基础组件

提供了消息队列、字符流先入先出队列等机制。

  • 功耗管理

提供了CPU低功耗运行模式设置、低功耗设备注册、板级唤醒闹钟设置等机制。

2. 基础内核

2.1 系统管理

概述

系统管理模块提供了几个接口,用以初始化/启动TencentOS tiny内核、锁定/解锁系统调度等。

API讲解

k_err_t tos_knl_init(void);

初始化内核。

k_err_t tos_knl_start(void);

启动运行内核,开始第一个任务调度。

int tos_knl_is_running(void);

判断内核是否已启动运行。

void tos_knl_irq_enter(void);

此函数应该在中断调用函数的最前端被调用。

void tos_knl_irq_leave(void);

此函数应该在中断调用函数的尾端被调用。

k_err_t tos_knl_sched_lock(void);

锁定系统调度,此函数被调用并返回K_ERR_NONE时,系统调度会被锁定,系统调度器不再进行任务的切换。

k_err_t tos_knl_sched_unlock(void);

解锁系统调度,允许任务切换。

编程实例

运行效果

2.2 任务管理

概述

TencentOS tiny内核是单地址空间的可抢占式实时内核,TencentOS tiny内核不提供进程模型,任务对应线程的概念,是最小的调度运行体,也是最小的资源持有单位。

任务的本质是一个拥有独立栈空间的可调度运行实体,用户可以在任务的入口函数中编写自己的业务逻辑;多个任务之间可以通过系统提供的任务间通信机制进行同步或者信息传递等操作;每个任务都有优先级,高优先级任务可以抢占低优先级任务的运行。

API讲解

创建任务的系统api接口为tos_task_create,接口原型如下:

k_err_t tos_task_create(k_task_t *task,
                        char *name,
                        k_task_entry_t entry,
                        void *arg,
                        k_prio_t prio,
                        k_stack_t *stk_base,
                        size_t stk_size,
                        k_timeslice_t timeslice);

这里详细讲解此api参数意义:

  • task

    这是一个k_task_t类型的指针,k_task_t是内核的任务结构体类型。注意:task指针,应该指向生命周期大于待创建任务体生命周期的k_task_t类型变量,如果该指针指向的变量生命周期比待创建的任务体生命周期短,譬如可能是一个生命周期极端的函数栈上变量,可能会出现任务体还在运行而k_task_t变量已被销毁,会导致系统调度出现不可预知问题。

  • name

    指向任务名字符串的指针。注意:同task,该指针指向的字符串生命周期应该大于待创建的任务体生命周期,一般来说,传入字符串常量指针即可。

  • entry

    任务体运行的函数入口。当任务创建完毕进入运行状态后,entry是任务执行的入口,用户可以在此函数中编写业务逻辑。

  • arg

    传递给任务入口函数的参数。

  • prio

    任务优先级。prio的数值越小,优先级越高。用户可以在tos_config.h中,通过TOS_CFG_TASK_PRIO_MAX来配置任务优先级的最大数值,在内核的实现中,idle任务的优先级会被分配为TOS_CFG_TASK_PRIO_MAX - 1,此优先级只能被idle任务使用。因此对于一个用户创建的任务来说,合理的优先级范围应该为[0, TOS_CFG_TASK_PRIO_MAX - 2]。另外TOS_CFG_TASK_PRIO_MAX的配置值必需大于等于8。

  • stk_base

    任务在运行时使用的栈空间的起始地址。注意:同task,该指针指向的内存空间的生命周期应该大于待创建的任务体生命周期。stk_base是k_stack_t类型的数组起始地址。

  • stk_size

    任务的栈空间大小。注意:因为stk_base是k_stack_t类型的数组指针,因此实际栈空间所占内存大小为stk_size * sizeof(k_stack_t)。

  • timeslice

    时间片轮转机制下当前任务的时间片大小。当timeslice为0时,任务调度时间片会被设置为默认大小(TOS_CFG_CPU_TICK_PER_SECOND / 10),系统时钟滴答(systick)数 / 10。

编程实例

1、在tos_config.h中,配置最大任务优先级TOS_CFG_TASK_PRIO_MAX:

#define TOS_CFG_TASK_PRIO_MAX 10u

2、配置每秒钟的系统滴答数TOS_CFG_CPU_TICK_PER_SECOND:

#define TOS_CFG_CPU_TICK_PER_SECOND 1000u

3、编写main.c示例代码:

#include "tos.h"		// 添加TencentOS tiny内核接口头文件
#include "mcu_init.h"	// 包含mcu初始化头文件,里面有board_init等板级启动代码函数原型声明

#define STK_SIZE_TASK_PRIO4      512	// 优先级为4的任务栈大小为512
#define STK_SIZE_TASK_PRIO5      1024	// 优先级为5的任务栈大小为1024

k_stack_t stack_task_prio4[STK_SIZE_TASK_PRIO4];	// 优先级为4的任务栈空间
k_stack_t stack_task_prio5[STK_SIZE_TASK_PRIO5];	// 优先级为5的任务栈空间

k_task_t task_prio4;	// 优先级为4的任务体
k_task_t task_prio5;	// 优先级为5的任务体

extern void entry_task_prio4(void *arg);	// 优先级为4的任务体入口函数
extern void entry_task_prio5(void *arg);	// 优先级为5的任务体入口函数

uint32_t arg_task_prio4_array[3] = {	// 优先级为4的任务体入口函数入参
    1, 2, 3,
};

char *arg_task_prio5_string = "arg for task_prio5";	// 优先级为5的任务体入口函数入参

static void dump_uint32_array(uint32_t *array, size_t len)
{
    size_t i = 0;

    for (i = 0; i < len; ++i) {
        printf("%d\t", array[i]);
    }
    printf("\n\n");

}

void entry_task_prio4(void *arg)
{
    uint32_t *array_from_main = (uint32_t *)arg;	// 捕获调用者传入的参数

    printf("array from main:\n");
    dump_uint32_array(array_from_main, 3);	// dump传入的参数(数组)

    while (K_TRUE) {
        printf("task_prio4 body\n");	// 任务运行体,不断打印这条信息
        tos_task_delay(1000);	// 睡眠1000个系统时钟滴答(以下记作systick),因为TOS_CFG_CPU_TICK_PER_SECOND为1000,也就是一秒钟会有1000个systick,因此睡眠1000个systick就是睡眠了1秒。
    }
}

void entry_task_prio5(void *arg)
{
    int i = 0;
    char *string_from_main = (char *)arg;
    printf("string from main:\n");
    printf("%s\n\n", string_from_main);	// 打印出调用者传入的字符串参数

    while (K_TRUE) {
        if (i == 2) {
            printf("i = %d\n", i);	// i为2时,挂起task_prio4,task_prio4停止运行
            tos_task_suspend(&task_prio4);
        } else if (i == 4) {
            printf("i = %d\n", i);	// i为4时,恢复task_prio4的运行
            tos_task_resume(&task_prio4);
        } else if (i == 6) {
            printf("i = %d\n", i);	// i为6时,删除task_prio4,task_prio4不再运行
            tos_task_destroy(&task_prio4);
        }
        printf("task_prio5 body\n");
        tos_task_delay(1000);
        ++i;
    }
}

int main(void)
{
    board_init();	// 执行板级初始化代码,初始化串口等外设。
    tos_knl_init();	// 初始化TencentOS tiny内核
    // 创建一个优先级为4的任务
    (void)tos_task_create(&task_prio4, "task_prio4", entry_task_prio4,
                          (void *)(&arg_task_prio4_array[0]), 4,
                          stack_task_prio4, STK_SIZE_TASK_PRIO4, 0);
    // 创建一个优先级为5的任务
    (void)tos_task_create(&task_prio5, "task_prio5", entry_task_prio5,
                          (void *)arg_task_prio5_string, 5,
                          stack_task_prio5, STK_SIZE_TASK_PRIO5, 0);
    // 开始内核调度
    tos_knl_start();
}

运行效果

array from main: 1 2 3

task_prio4 body string from main: arg for task_prio5

task_prio5 body task_prio4 body task_prio5 body task_prio4 body i = 2 task_prio5 body task_prio5 body i = 4 task_prio4 body task_prio5 body task_prio4 body task_prio5 body task_prio4 body i = 6 task_prio5 body task_prio5 body task_prio5 body task_prio5 body task_prio5 body task_prio5 body task_prio5 body

[实例代码](./code/2.2 task manager/main.c)

2.3 任务间通信

2.3.1 互斥量

概述

互斥量又称互斥锁,一般用于共享资源的互斥排他性访问保护。

互斥量在任意时刻处于且仅会处于解锁或锁定状态,当一个任务获取到一把锁后(互斥量锁定),其他任务再尝试获得这把锁时会失败或进入阻塞状态,当该任务释放持有的锁时(互斥量解锁),会唤醒一个正阻塞等待此互斥量的任务,被唤醒的任务将会获取这把锁。

在多任务运行环境中,有些共享资源不具有多线程可重入性,对于这类不希望被多任务同时访问的资源(临界资源),可以采用互斥量来进行保护,后面的编程实例章节会演示这一编程范式。

API讲解
编程实例

1、在tos_config.h中,配置互斥量组件开关TOS_CFG_MUTEX_EN:

#define TOS_CFG_MUTEX_EN 1u

2、编写main.c示例代码:

#include "tos.h"
#include "mcu_init.h"

#define STK_SIZE_TASK_WRITER        512
#define STK_SIZE_TASK_READER        512

k_stack_t stack_task_writer[STK_SIZE_TASK_WRITER];
k_stack_t stack_task_reader[STK_SIZE_TASK_READER];

k_task_t task_writer;
k_task_t task_reader;

extern void entry_task_writer(void *arg);
extern void entry_task_reader(void *arg);

k_mutex_t critical_resource_locker;

// 一片临界区内存
static uint32_t critical_resource[3];

static void write_critical_resource(int salt)
{
    size_t i = 0;
	// 此函数每次向共享内存中按递增顺序写入三个无符号整数
    printf("writting critical resource:\n");
    for (i = 0; i < 3; ++i) {
        printf("%d\t", salt + i);
        critical_resource[i] = salt + i;
    }
    printf("\n");
}

void entry_task_writer(void *arg)
{
    size_t salt = 0;
    k_err_t err;

    while (K_TRUE) {
        // 在向临界区写入数据之前,先尝试获取临界区保护锁
        err = tos_mutex_pend(&critical_resource_locker);
        if (err == K_ERR_NONE) {
            // 成功获取锁之后,向临界区写入数据
            write_critical_resource(salt);
            // 写完数据后,释放互斥锁
            tos_mutex_post(&critical_resource_locker);
        }
        tos_task_delay(1000);
        ++salt;
    }
}

static void read_critical_resource(void)
{
    size_t i = 0;

    // 从临界区读取数据
    printf("reading critical resource:\n");
    for (i = 0; i < 3; ++i) {
        printf("%d\t", critical_resource[i]);
    }
    printf("\n");
}

void entry_task_reader(void *arg)
{
    k_err_t err;

    while (K_TRUE) {
        // 读取临界区数据之前,先尝试获取临界区保护锁
        err = tos_mutex_pend(&critical_resource_locker);
        if (err == K_ERR_NONE) {
            // 成功获取锁之后,从临界区读取数据
            read_critical_resource();
            // 读取数据完毕后,释放互斥锁
            tos_mutex_post(&critical_resource_locker);
        }
        tos_task_delay(1000);
    }
}

int main(void)
{
    board_init();
    tos_knl_init();
    // 创建临界区保护互斥锁
    tos_mutex_create(&critical_resource_locker);
    (void)tos_task_create(&task_writer, "writer", entry_task_writer, NULL,
                            4, stack_task_writer, STK_SIZE_TASK_WRITER, 0);
    (void)tos_task_create(&task_reader, "reader", entry_task_reader, NULL,
                            4, stack_task_reader, STK_SIZE_TASK_READER, 0);
    tos_knl_start();
}
运行效果

writting critical resource: 0 1 2 reading critical resource: 0 1 2 writting critical resource: 1 2 3 reading critical resource: 1 2 3 writting critical resource: 2 3 4 reading critical resource: 2 3 4 writting critical resource: 3 4 5 reading critical resource: 3 4 5 writting critical resource: 4 5 6 reading critical resource: 4 5 6 writting critical resource: 5 6 7 reading critical resource: 5 6 7 writting critical resource: 6 7 8 reading critical resource: 6 7 8 writting critical resource: 7 8 9 reading critical resource: 7 8 9

[实例代码](./code/2.3.1 mutex/main.c)

2.3.2 信号量

概述

信号量是一种实现任务间同步的机制,一般用于多个任务间有限资源竞争访问。

通常来说,一个信号量中持有一个整形数值,用以表示可用资源的数量。当一个信号量的可用资源数量大于0时,任务尝试获取该信号量成功,信号量的可用资源数减一;当一个信号量的可用资源数等于0时,任务尝试获取该信号量失败或进入阻塞状态。信号量的这一模式,当可用资源数为1时,可将其用于资源的互斥访问;或者解决生产者-消费者问题中的资源生产-消费问题。编程实例章节会演示生产者-消费者问题的解决范式。

API讲解
编程实例

1、在tos_config.h中,配置信号量组件开关TOS_CFG_SEM_EN:

#define TOS_CFG_SEM_EN 1u

2、编写main.c示例代码:

#include "tos.h"
#include "mcu_init.h"

#define STK_SIZE_TASK_PRODUCER      512
#define STK_SIZE_TASK_CONSUMER      512

k_stack_t stack_task_producer[STK_SIZE_TASK_PRODUCER];
k_stack_t stack_task_consumer[STK_SIZE_TASK_CONSUMER];

k_task_t task_producer;
k_task_t task_consumer;

extern void entry_task_producer(void *arg);
extern void entry_task_consumer(void *arg);

k_mutex_t buffer_locker;
k_sem_t full;
k_sem_t empty;

#define RESOURCE_COUNT_MAX      3

struct resource_st {
    int cursor;
    uint32_t buffer[RESOURCE_COUNT_MAX];
} resource = { 0, {0} };

static void produce_item(int salt)
{
    printf("produce item:\n");

    printf("%d", salt);
    resource.buffer[resource.cursor++] = salt;
    printf("\n");
}

void entry_task_producer(void *arg)
{
    size_t salt = 0;
    k_err_t err;

    while (K_TRUE) {
        err = tos_sem_pend(&empty, TOS_TIME_FOREVER);
        if (err != K_ERR_NONE) {
            continue;
        }
        err = tos_mutex_pend(&buffer_locker);
        if (err != K_ERR_NONE) {
            continue;
        }

        produce_item(salt);

        tos_mutex_post(&buffer_locker);
        tos_sem_post(&full);
        tos_task_delay(1000);
        ++salt;
    }
}

static void consume_item(void)
{
    printf("cosume item:\n");
    printf("%d\t", resource.buffer[--resource.cursor]);
    printf("\n");
}

void entry_task_consumer(void *arg)
{
    k_err_t err;

    while (K_TRUE) {
        err = tos_sem_pend(&full, TOS_TIME_FOREVER);
        if (err != K_ERR_NONE) {
            continue;
        }
        tos_mutex_pend(&buffer_locker);
        if (err != K_ERR_NONE) {
            continue;
        }

        consume_item();

        tos_mutex_post(&buffer_locker);
        tos_sem_post(&empty);
        tos_task_delay(2000);
    }
}

int main(void)
{
    board_init();
    tos_knl_init();
    tos_mutex_create(&buffer_locker);
    tos_sem_create(&full, 0);
    tos_sem_create(&empty, RESOURCE_COUNT_MAX);
    (void)tos_task_create(&task_producer, "producer", entry_task_producer, NULL,
                            4, stack_task_producer, STK_SIZE_TASK_PRODUCER, 0);
    (void)tos_task_create(&task_consumer, "consumer", entry_task_consumer, NULL,
                            4, stack_task_consumer, STK_SIZE_TASK_CONSUMER, 0);
    tos_knl_start();
}
运行效果

produce iterm: 0 cosume iterm: 0 produce iterm: 1 produce iterm: 2 cosume iterm: 2 produce iterm: 3 produce iterm: 4 cosume iterm: 4 produce iterm: 5 cosume iterm: 5 produce iterm: 6 cosume iterm: 6 produce iterm: 7 cosume iterm: 7 produce iterm: 8 cosume iterm: 8 produce iterm: 9 cosume iterm: 9 produce iterm: 10

[实例代码](./code/2.3.2 semaphore/main.c)

2.3.3 事件

概述

事件提供了一种任务间实现同步和信息传递的机制。一般来说,一个事件中包含了一个旗标,这个旗标的每一位表示一个“事件”。

一个任务可以等待一个或者多个“事件”的发生,其他任务在一定的业务条件下可以通过写入特定“事件”唤醒等待此“事件”的任务,实现一种类似信号的编程范式。

API讲解
编程实例

1、在tos_config.h中,配置事件组件开关TOS_CFG_EVENT_EN:

#define TOS_CFG_EVENT_EN 1u

2、编写main.c示例代码:

#include "tos.h"
#include "mcu_init.h"

#define STK_SIZE_TASK_LISTENER      512
#define STK_SIZE_TASK_TRIGGER       512

k_stack_t stack_task_listener1[STK_SIZE_TASK_LISTENER];
k_stack_t stack_task_listener2[STK_SIZE_TASK_LISTENER];
k_stack_t stack_task_trigger[STK_SIZE_TASK_TRIGGER];

k_task_t task_listener1;
k_task_t task_listener2;
k_task_t task_trigger;

extern void entry_task_listener1(void *arg);
extern void entry_task_listener2(void *arg);
extern void entry_task_trigger(void *arg);

const k_event_flag_t event_eeny     = (k_event_flag_t)(1 << 0);
const k_event_flag_t event_meeny    = (k_event_flag_t)(1 << 1);
const k_event_flag_t event_miny     = (k_event_flag_t)(1 << 2);
const k_event_flag_t event_moe      = (k_event_flag_t)(1 << 3);

k_event_t event;

void entry_task_listener1(void *arg)
{
    k_event_flag_t flag_match;
    k_err_t err;

    while (K_TRUE) {
        // 此任务监听四个事件,因为使用了TOS_OPT_EVENT_PEND_ALL选项,因此必须是四个事件同时到达此任务才会被唤醒
        err = tos_event_pend(&event, event_eeny | event_meeny | event_miny | event_moe,
                                &flag_match, TOS_TIME_FOREVER, TOS_OPT_EVENT_PEND_ALL | TOS_OPT_EVENT_PEND_CLR);
        if (err == K_ERR_NONE) {
            printf("entry_task_listener1:\n");
            printf("eeny, meeny, miny, moe, they all come\n");
        }
    }
}

void entry_task_listener2(void *arg)
{
    k_event_flag_t flag_match;
    k_err_t err;

    while (K_TRUE) {
        // 此任务监听四个事件,因为使用了TOS_OPT_EVENT_PEND_ANY选项,因此四个事件任意一个到达(包括四个事件同时到达)任务就会被唤醒
        err = tos_event_pend(&event, event_eeny | event_meeny | event_miny | event_moe,
                                &flag_match, TOS_TIME_FOREVER, TOS_OPT_EVENT_PEND_ANY | TOS_OPT_EVENT_PEND_CLR);
        if (err == K_ERR_NONE) {
            printf("entry_task_listener2:\n");
            // 有事件到达,判断具体是哪个事件
            if (flag_match == event_eeny) {
                printf("eeny comes\n");
            }
            if (flag_match == event_meeny) {
                printf("meeny comes\n");    
            }
            if (flag_match == event_miny) {
                printf("miny comes\n");
            }
            if (flag_match == event_moe) {
                printf("moe comes\n");
            }
            if (flag_match == (event_eeny | event_meeny | event_miny | event_moe)) {
                printf("all come\n");
            }
        }
    }
}

void entry_task_trigger(void *arg)
{
    int i = 1;

    while (K_TRUE) {
        if (i == 2) {
            printf("entry_task_trigger:\n");
            printf("eeny will come\n");
            // 发送eeny事件,task_listener2会被唤醒
            tos_event_post(&event, event_eeny);
        }
        if (i == 3) {
            printf("entry_task_trigger:\n");
            printf("meeny will come\n");
            // 发送eeny事件,task_listener2会被唤醒
            tos_event_post(&event, event_meeny);
        }
        if (i == 4) {
            printf("entry_task_trigger:\n");
            printf("miny will come\n");
            // 发送eeny事件,task_listener2会被唤醒
            tos_event_post(&event, event_miny);
        }
        if (i == 5) {
            printf("entry_task_trigger:\n");
            printf("moe will come\n");
            // 发送eeny事件,task_listener2会被唤醒
            tos_event_post(&event, event_moe);
        }
        if (i == 6) {
            printf("entry_task_trigger:\n");
            printf("all will come\n");
            // 同时发送四个事件,因为task_listener1的优先级高于task_listener2,因此这里task_listener1会被唤醒
            tos_event_post(&event, event_eeny | event_meeny | event_miny | event_moe);
        }
        tos_task_delay(1000);
        ++i;
    }
}

int main(void)
{
    board_init();
    tos_knl_init();
    tos_event_create(&event, (k_event_flag_t)0u);
    // 这里task_listener1的优先级比task_listener2高,因此在task_trigger发送所有事件时,task_listener1会被唤醒
    // 读者可以尝试将task_listener1优先级修改为5(比task_listener2低),此设置下,在task_trigger发送所有事件时,task_listener2将会被唤醒。
    (void)tos_task_create(&task_listener1, "listener1", entry_task_listener1, NULL,
                            3, stack_task_listener1, STK_SIZE_TASK_LISTENER, 0);
    (void)tos_task_create(&task_listener2, "listener2", entry_task_listener2, NULL,
                            4, stack_task_listener2, STK_SIZE_TASK_LISTENER, 0);
    (void)tos_task_create(&task_trigger, "trigger", entry_task_trigger, NULL,
                            4, stack_task_trigger, STK_SIZE_TASK_TRIGGER, 0);
    tos_knl_start();
}
运行效果

entry_task_trigger: eeny will come entry_task_listener2: eeny comes entry_task_trigger: meeny will come entry_task_listener2: meeny comes entry_task_trigger: miny will come entry_task_listener2: miny comes entry_task_trigger: moe will come entry_task_listener2: moe comes entry_task_trigger: all will come entry_task_listener1: eeny, meeny, miny, moe, they all come

[实例代码](./code/2.3.3 event/main.c)

2.3.4 队列

概述

队列提供了一种任务间实现同步和数据传递的机制。事件只能用于任务间传递某类“事件”是否发生的信号,无法传递更为复杂的数据,队列弥补了事件的这一不足,可以在任务间传递不定长度的消息。

API讲解
编程实例

1、在tos_config.h中,配置队列组件开关TOS_CFG_QUEUE_EN:

#define TOS_CFG_QUEUE_EN 1u

2、在tos_config.h中,配置消息队列组件开关TOS_CFG_MSG_EN:

#define TOS_CFG_MSG_EN 1u

3、编写main.c示例代码:

#include "tos.h"
#include "mcu_init.h"

#define STK_SIZE_TASK_RECEIVER      512
#define STK_SIZE_TASK_SENDER        512

#define PRIO_TASK_RECEIVER_HIGHER_PRIO      4
#define PRIO_TASK_RECEIVER_LOWER_PRIO       (PRIO_TASK_RECEIVER_HIGHER_PRIO + 1)

k_stack_t stack_task_receiver_higher_prio[STK_SIZE_TASK_RECEIVER];
k_stack_t stack_task_receiver_lower_prio[STK_SIZE_TASK_RECEIVER];
k_stack_t stack_task_sender[STK_SIZE_TASK_SENDER];

k_task_t task_receiver_higher_prio;
k_task_t task_receiver_lower_prio;
k_task_t task_sender;

k_queue_t queue;

extern void entry_task_receiver_higher_prio(void *arg);
extern void entry_task_receiver_lower_prio(void *arg);
extern void entry_task_sender(void *arg);

void entry_task_receiver_higher_prio(void *arg)
{
    k_err_t err;
    void *msg_received;
    size_t msg_size;

    while (K_TRUE) {
        err = tos_queue_pend(&queue, &msg_received, &msg_size, TOS_TIME_FOREVER);
        if (err == K_ERR_NONE) {
            printf("entry_task_receiver_higher_prio:\n");
            printf("message body: %s\n", (char *)msg_received);
            printf("message size: %d\n", msg_size);
        }
    }
}

void entry_task_receiver_lower_prio(void *arg)
{
    k_err_t err;
    void *msg_received;
    size_t msg_size;

    while (K_TRUE) {
        err = tos_queue_pend(&queue, &msg_received, &msg_size, TOS_TIME_FOREVER);
        if (err == K_ERR_NONE) {
            printf("entry_task_receiver_lower_prio:\n");
            printf("message body: %s\n", (char *)msg_received);
            printf("message size: %d\n", msg_size);
        }
    }
}

void entry_task_sender(void *arg)
{
    int i = 1;
    char *msg_to_one_receiver = "message for one receiver[with highest priority]";
    char *msg_to_all_receiver = "message for all receivers";

    // 此任务不断通过队列queue发送消息
    while (K_TRUE) {
        if (i == 2) {
            printf("entry_task_sender:\n");
            printf("send a message to one receiver, and shoud be the highest priority one\n");
            // 发送消息并唤醒一个等待任务,唤醒的应该是等待任务中优先级最高的
            tos_queue_post(&queue, msg_to_one_receiver, strlen(msg_to_one_receiver));
        }
        if (i == 3) {
            printf("entry_task_sender:\n");
            printf("send a message to all recevier\n");
            // 发送消息并唤醒所有正在等待的任务
            tos_queue_post_all(&queue, msg_to_all_receiver, strlen(msg_to_all_receiver));
        }
        if (i == 4) {
            printf("entry_task_sender:\n");
            printf("send a message to one receiver, and shoud be the highest priority one\n");
            // 发送消息并唤醒一个等待任务,唤醒的应该是等待任务中优先级最高的
            tos_queue_post(&queue, msg_to_one_receiver, strlen(msg_to_one_receiver));
        }
        if (i == 5) {
            printf("entry_task_sender:\n");
            printf("send a message to all recevier\n");
            // 发送消息并唤醒所有正在等待的任务
            tos_queue_post_all(&queue, msg_to_all_receiver, strlen(msg_to_all_receiver));
        }
        tos_task_delay(1000);
        ++i;
    }
}

int main(void)
{
    board_init();
    tos_knl_init();
    tos_queue_create(&queue);
    // task_receiver_higher_prio任务的优先级较高
    (void)tos_task_create(&task_receiver_higher_prio, "receiver_higher_prio",
                            entry_task_receiver_higher_prio, NULL, PRIO_TASK_RECEIVER_HIGHER_PRIO,
                            stack_task_receiver_higher_prio, STK_SIZE_TASK_RECEIVER, 0);
    // task_receiver_lower_prio任务的优先级较低
    (void)tos_task_create(&task_receiver_lower_prio, "receiver_lower_prio",
                            entry_task_receiver_lower_prio, NULL, PRIO_TASK_RECEIVER_LOWER_PRIO,
                            stack_task_receiver_lower_prio, STK_SIZE_TASK_RECEIVER, 0);
    (void)tos_task_create(&task_sender, "sender", entry_task_sender, NULL,
                            4, stack_task_sender, STK_SIZE_TASK_SENDER, 0);
    tos_knl_start();
}
运行效果

entry_task_trigger: send a message to one receiver, and shoud be the highest priority one entry_task_receiver_higher_prio: message body: message for one receiver[with highest priority] message size: 47 entry_task_trigger: send a message to all recevier entry_task_receiver_higher_prio: message body: message for all receivers message size: 25 entry_task_receiver_lower_prio: message body: message for all receivers message size: 25 entry_task_trigger: send a message to one receiver, and shoud be the highest priority one entry_task_receiver_higher_prio: message body: message for one receiver[with highest priority] message size: 47 entry_task_trigger: send a message to all recevier entry_task_receiver_higher_prio: message body: message for all receivers message size: 25 entry_task_receiver_lower_prio: message body: message for all receivers message size: 25

[实例代码](./code/2.3.4 queue/main.c)

2.4 内存管理

2.4.1 动态内存

概述

动态内存管理模块,提供了一套动态管理系统内存的机制,支持用户动态的申请、释放不定长内存块。

API讲解
编程实例

1、在tos_config.h中,配置动态内存组件开关TOS_CFG_MMHEAP_EN:

#define TOS_CFG_MMHEAP_EN 1u

2、在tos_config.h中,配置动态内存池大小:

#define TOS_CFG_MMHEAP_POOL_SIZE 0x2000

3、编写main.c示例代码:

#include "tos.h"
#include "mcu_init.h"

#define STK_SIZE_TASK_DEMO      512

k_stack_t stack_task_demo[STK_SIZE_TASK_DEMO];

k_task_t task_demo;

extern void entry_task_demo(void *arg);

void entry_task_demo(void *arg)
{
    void *p = K_NULL, *p_aligned = K_NULL;
    int i = 0;

    while (K_TRUE) {
        if (i == 1) {
            p = tos_mmheap_alloc(0x30);
            if (p) {
                printf("alloc: %x\n", (cpu_addr_t)p);
            }
        } else if (i == 2) {
            if (p) {
                printf("free: %x\n", p);
                tos_mmheap_free(p);
            }
        } else if (i == 3) {
            p = tos_mmheap_alloc(0x30);
            if (p) {
                printf("alloc: %x\n", (cpu_addr_t)p);
            }            
        } else if (i == 4) {
            p_aligned = tos_mmheap_aligned_alloc(0x50, 16);
            if (p_aligned) {
                printf("aligned alloc: %x\n", (cpu_addr_t)p_aligned);
                if ((cpu_addr_t)p_aligned % 16 == 0) {
                    printf("%x is 16 aligned\n", (cpu_addr_t)p_aligned);
                } else {
                    printf("should not happen\n");
                }
            }
        } else if (i == 5) {
            p = tos_mmheap_realloc(p, 0x40);
            if (p) {
                printf("realloc: %x\n", (cpu_addr_t)p);
            }
        } else if (i == 6) {
            if (p) {
                tos_mmheap_free(p);
            }
            if (p_aligned) {
                tos_mmheap_free(p_aligned);
            }
        }

        tos_task_delay(1000);
        ++i;
    }
}

int main(void)
{
    board_init();
    tos_knl_init();
    (void)tos_task_create(&task_demo, "receiver_higher_prio", entry_task_demo, NULL,
                            4, stack_task_demo, STK_SIZE_TASK_DEMO, 0);
    tos_knl_start();
}
运行效果

alloc: 20000c8c free: 20000c8c alloc: 20000c8c aligned alloc: 20000cc0 20000cc0 is 16 aligned realloc: 20000d14

[实例代码](./code/2.4.1 mmheap/main.c)

2.4.2 静态内存

概述

静态内存管理模块,提供了一套管理静态内存块的机制,支持用户申请、释放定长的内存块。

API讲解

创建静态内存池接口:

k_err_t tos_mmblk_pool_create(k_mmblk_pool_t *mbp, void *pool_start, size_t blk_num, size_t blk_size);

这里详细讲解此api参数意义:

  • mbp

    静态内存池句柄。

  • pool_start

    静态内存池起始地址。

  • blk_num

    内存池将要划分的内存块个数。

  • blk_size

    每个内存块的大小。

编程实例

1、在tos_config.h中,配置静态内存组件开关TOS_CFG_MMBLK_EN:

#define TOS_CFG_MMBLK_EN 1u

2、编写main.c示例代码:

#include "tos.h"
#include "mcu_init.h"

#define STK_SIZE_TASK_DEMO      512

k_stack_t stack_task_demo[STK_SIZE_TASK_DEMO];

k_task_t task_demo;

#define MMBLK_BLK_NUM       5
#define MMBLK_BLK_SIZE      0x20

k_mmblk_pool_t mmblk_pool;

// 需要管理的静态内存池
uint8_t mmblk_pool_buffer[MMBLK_BLK_NUM * MMBLK_BLK_SIZE];

// 记录从内存池中分配到的地址
void *p[MMBLK_BLK_NUM] = { K_NULL };

extern void entry_task_demo(void *arg);

void entry_task_demo(void *arg)
{
    void *p_dummy = K_NULL;
    k_err_t err;
    int i = 0;

    printf("mmblk_pool has %d blocks, size of each block is 0x%x\n", 5, 0x20);
    // 从内存池中获取所有的block
    for (; i < MMBLK_BLK_NUM; ++i) {
        err = tos_mmblk_alloc(&mmblk_pool, &p[i]);
        if (err == K_ERR_NONE) {
            printf("%d block alloced: 0x%x\n", i, p[i]);
        } else {
            printf("should not happen\n");
        }
    }

    // 前文逻辑已经将所有可用block分配完毕,继续分配会返回K_ERR_MMBLK_POOL_EMPTY错误码
    err = tos_mmblk_alloc(&mmblk_pool, &p_dummy);
    if (err == K_ERR_MMBLK_POOL_EMPTY) {
        printf("blocks exhausted, all blocks is alloced\n");
    } else {
        printf("should not happen\n");
    }

    // 将前文分配得到的所有block归还到池中
    for (i = 0; i < MMBLK_BLK_NUM; ++i) {
        err = tos_mmblk_free(&mmblk_pool, p[i]);
        if (err != K_ERR_NONE) {
            printf("should not happen\n");
        }
    }
    // 前文的归还动作中已经将所有的block归还到池中,继续规范会返回K_ERR_MMBLK_POOL_FULL错误码
    err = tos_mmblk_free(&mmblk_pool, p[0]);
    if (err == K_ERR_MMBLK_POOL_FULL) {
        printf("pool is full\n");
    } else {
        printf("should not happen\n");
    }
}

int main(void)
{
    board_init();
    tos_knl_init();
    // 创建静态内存池
    tos_mmblk_pool_create(&mmblk_pool, mmblk_pool_buffer, MMBLK_BLK_NUM, MMBLK_BLK_SIZE);
    (void)tos_task_create(&task_demo, "receiver_higher_prio", entry_task_demo, NULL,
                            4, stack_task_demo, STK_SIZE_TASK_DEMO, 0);
    tos_knl_start();
}
运行效果

mmblk_pool has 5 blocks, size of each block is 0x20 0 block alloced: 0x20000974 1 block alloced: 0x20000994 2 block alloced: 0x200009b4 3 block alloced: 0x200009d4 4 block alloced: 0x200009f4 blocks exhausted, all blocks is alloced pool is full

[实例代码](./code/2.4.2 mmblk/main.c)

2.5 时间管理

概述

时间管理,提供了一族与时间相关的函数,可以获取/设置系统时钟滴答数(systick)、systick与毫秒单位之间互相转化、按毫秒、墙上时钟等单位进行任务睡眠的功能。

API讲解

编程实例

1、配置每秒钟的系统滴答数TOS_CFG_CPU_TICK_PER_SECOND:

#define TOS_CFG_CPU_TICK_PER_SECOND 1000u

2、编写main.c示例代码:

#include "tos.h"
#include "mcu_init.h"

#define STK_SIZE_TASK_DEMO      512

k_stack_t stack_task_demo[STK_SIZE_TASK_DEMO];

k_task_t task_demo;

extern void entry_task_demo(void *arg);

void entry_task_demo(void *arg)
{
    k_time_t ms;
    k_tick_t systick, after_systick;

    // 因为TOS_CFG_CPU_TICK_PER_SECOND为1000,也就是一秒钟会有1000个systick,因此1000个systick等于1000毫秒。
    systick = tos_millisec2tick(2000);
    printf("%d millisec equals to %lld ticks\n", 2000, systick);

    ms = tos_tick2millisec(1000);
    printf("%lld ticks equals to %d millisec\n", (k_tick_t)1000, ms);

    systick = tos_systick_get();
    printf("before sleep, systick is %lld\n", systick);

    tos_msleep(2000);

    after_systick = tos_systick_get();
    printf("after sleep %d ms, systick is %lld\n", 2000, after_systick);

    printf("milliseconds sleep is about: %d\n", tos_ticks2millisec(after_systick - systick));
}

int main(void)
{
    board_init();
    tos_knl_init();
    (void)tos_task_create(&task_demo, "receiver_higher_prio", entry_task_demo, NULL,
                            4, stack_task_demo, STK_SIZE_TASK_DEMO, 0);
    tos_knl_start();
}

运行效果

2000 millisec equals to 2000 ticks 1000 ticks equals to 1000 millisec before sleep, systick is 7 after sleep 2000 ms, systick is 2009 milliseconds sleep is about: 2002

[实例代码](./code/2.5 time/main.c)

2.6 软件定时器

概述

软件定时器提供了一套从软件层次实现的定时器机制,相对应的概念是硬件定时器。用户可以创建一系列的软件定时器,并指定软件定时器到期的条件以及执行回调,当软件定时器到期时会执行注册的回调。

通常来说,用户注册的软件定时器回调中很可能包含延迟动作或同步等待操作,或者回调函数本身逻辑复杂执行耗时较长,因此系统将软件定时器管理逻辑设计成一个任务,在这个任务中扫描定时器是否过期并执行定时器回调。但是如你所知,创建一个任务是需要消耗系统内存资源的(任务的栈、任务句柄本身的内存空间等等),而如果用户注册的软件定时器回调中并不包含延迟动作也不包含同步等待操作,或者回调本身执行耗时很短,这种情况下软件定时器管理逻辑无需被设计成任务,而是可以被设计成时钟中断中被调用的一个函数。当软件定时器管理逻辑被设计成一个函数时,就可以节省创建任务所需的资源。

系统默认采用的实现是将定时器管理逻辑设计为任务,当用户的定时器回调都是耗时极短的操作时,用户可以通过将软件定时器管理逻辑配置为函数来节省内存资源。通过在tos_config.h中打开TOS_CFG_TIMER_AS_PROC开关来讲软件定时器管理逻辑配置为函数:

#define TOS_CFG_TIMER_AS_PROC 1u

API讲解

k_err_t tos_timer_create(k_timer_t *tmr,
                         k_tick_t delay,
                         k_tick_t period,
                         k_timer_callback_t callback,
                         void *cb_arg,
                         k_opt_t opt)

这里详细讲解此api参数意义:

  • tmr

    软件定时器句柄。

  • delay

    该定时器延迟多久后执行。

  • period

    一个定时器的执行周期。

  • callback

    定时器到期后的执行回调。

  • cb_arg

    执行回调的入参。

  • opt

    此opt的传入主要是界定tmr的属性,如果传入的是TOS_OPT_TIMER_ONESHOT,表明此tmr是一次性的,当delay时间到期,tmr的执行回调调用完毕后此tmr生命周期就结束了;如果传入的是TOS_OPT_TIMER_PERIODIC,表明此tmr是周期性定时器,当tmr定时时间到期,tmr的执行回调调用完毕后,系统会重新按period参数为到期时间将tmr加入到定时队列,开启下一个周期。

编程实例

1、在tos_config.h中,配置软件定时器组件开关TOS_CFG_TIMER_EN:

#define TOS_CFG_TIMER_EN 1000u

2、编写main.c示例代码:

#include "tos.h"
#include "mcu_init.h"

#define STK_SIZE_TASK_DEMO      512

k_stack_t stack_task_demo[STK_SIZE_TASK_DEMO];

k_task_t task_demo;

extern void entry_task_demo(void *arg);

void oneshot_timer_cb(void *arg)
{
    printf("this is oneshot timer callback, current systick: %lld\n", tos_systick_get());
}

void periodic_timer_cb(void *arg)
{
    printf("this is periodic timer callback, current systick: %lld\n", tos_systick_get());
}

void entry_task_demo(void *arg)
{
    k_timer_t oneshot_tmr;
    k_timer_t periodic_tmr;

    // 这是一个一次性的timer,且超时时间是3000个tick之后
    tos_timer_create(&oneshot_tmr, 3000, 0, oneshot_timer_cb, K_NULL, TOS_OPT_TIMER_ONESHOT);
    // 这是一个周期性的timer,第一次超时时间是2000个tick之后,之后按3000个tick为周期执行回调
    tos_timer_create(&periodic_tmr, 2000, 3000, periodic_timer_cb, K_NULL, TOS_OPT_TIMER_PERIODIC);

    printf("current systick: %lld\n", tos_systick_get());
    tos_timer_start(&oneshot_tmr);
    tos_timer_start(&periodic_tmr);

    while (K_TRUE) {
        tos_task_delay(1000);
    }
}

int main(void)
{
    board_init();
    tos_knl_init();
    (void)tos_task_create(&task_demo, "receiver_higher_prio", entry_task_demo, NULL,
                            4, stack_task_demo, STK_SIZE_TASK_DEMO, 0);
    tos_knl_start();
}

运行效果

current systick: 0 this is periodic timer callback, current systick: 2001 this is oneshot timer callback, current systick: 3001 this is periodic timer callback, current systick: 5001 this is periodic timer callback, current systick: 8001 this is periodic timer callback, current systick: 11001 this is periodic timer callback, current systick: 14001

[实例代码](./code/2.6 timer/main.c)

2.7 时间片轮转机制

概述

TencentOS tiny操作系统内核是一个抢占式内核,抢占式内核的特点是,如果最高优先级的任务不放弃CPU(调用tos_task_delay、tos_task_yeild等主动放权,或者任务间同步通信机制的pend接口等),那么CPU将会一直被此任务独占。

假设这样一种场景:系统中包含多个同等优先级的任务,且这几个任务体中都没有放弃CPU的行为,则会出现的情况是,这几个任务始终只有第一个被得到调度的那个在运行,因为第一个得到调度的任务体中不会主动放弃CPU,而其他任务优先级上与其相等无法抢占。此种场景下,其他任务会因得不到CPU而陷入饥饿状态。

时间片轮转机制提供了按时间片占用调度的策略,可以解决上述场景下的任务饥饿问题。

API讲解

编程实例

1、在tos_config.h中,配置时间片轮转组件开关TOS_CFG_ROUND_ROBIN_EN:

#define TOS_CFG_ROUND_ROBIN_EN 1u

2、编写main.c示例代码:

#include "tos.h"
#include "mcu_init.h"

#define STK_SIZE_TASK_DEMO      512
#define STK_SIZE_TASK_SAMPLE    512

/*
此代码中创建了两个同等优先级(PRIO_TASK_DEMO)的任务task_demo1、task_demo2,两个任务体中做的操作是不断对各自的计数器(demo1_counter、demo2_counter)做自增操作(没有放弃CPU的操作),时间片分别为timeslice_demo1、timeslice_demo2。
同时创建了一个优先级比task_demo1、task_demo2较高的采样任务task_sample,此任务不间歇的对两个计数器进行采样。在开启时间片轮转的情况下,task_demo1、task_demo2得到运行的时间比例应该是timeslice_demo1与timeslice_demo2的比例,那么demo1_counter和demo2_counter值的比例应该也差不多是timeslice_demo1与timeslice_demo2的比例。
*/

// task_demo1和task_demo2的优先级
#define PRIO_TASK_DEMO          4
// 采样任务的优先级
#define PRIO_TASK_SAMPLE        (PRIO_TASK_DEMO - 1)

// task_demo1的时间片,在tos_task_create时传入
const k_timeslice_t timeslice_demo1 = 10;
// task_demo2的时间片,在tos_task_create时传入
const k_timeslice_t timeslice_demo2 = 20;

k_stack_t stack_task_demo1[STK_SIZE_TASK_DEMO];
k_stack_t stack_task_demo2[STK_SIZE_TASK_DEMO];
k_stack_t stack_task_sample[STK_SIZE_TASK_SAMPLE];

k_task_t task_demo1;
k_task_t task_demo2;
k_task_t task_sample;

extern void entry_task_demo1(void *arg);
extern void entry_task_demo2(void *arg);
extern void entry_task_sample(void *arg);

uint64_t demo1_counter = 0;
uint64_t demo2_counter = 0;

void entry_task_demo1(void *arg)
{
    while (K_TRUE) {
        ++demo1_counter;
    }
}

void entry_task_demo2(void *arg)
{
    while (K_TRUE) {
        ++demo2_counter;
    }
}

void entry_task_sample(void *arg)
{
    while (K_TRUE) {
        ++demo2_counter;
        printf("demo1_counter: %lld\n", demo1_counter);
        printf("demo2_counter: %lld\n", demo2_counter);
        printf("demo2_counter / demo1_counter = %f\n",
            (double)demo2_counter / demo1_counter);
        printf("should almost equals to:\n");
        printf("timeslice_demo2 / timeslice_demo1 = %f\n\n", (double)timeslice_demo2 / timeslice_demo1);
        tos_task_delay(1000);
    }
}

int main(void)
{
    board_init();
    tos_knl_init();
    // 配置robin机制参数
    tos_robin_config(TOS_ROBIN_STATE_ENABLED, (k_timeslice_t)500u);
    (void)tos_task_create(&task_demo1, "demo1", entry_task_demo1, NULL,
                            PRIO_TASK_DEMO, stack_task_demo1, STK_SIZE_TASK_DEMO,
                            timeslice_demo1);
    (void)tos_task_create(&task_demo2, "demo2", entry_task_demo2, NULL,
                            PRIO_TASK_DEMO, stack_task_demo2, STK_SIZE_TASK_DEMO,
                            timeslice_demo2);
    (void)tos_task_create(&task_sample, "sample", entry_task_sample, NULL,
                            PRIO_TASK_SAMPLE, stack_task_sample, STK_SIZE_TASK_SAMPLE,
                            0);
    tos_knl_start();
}

运行效果

demo1_counter: 0 demo2_counter: 1 demo2_counter / demo1_counter = 0.000000 should almost equals to: timeslice_demo2 / timeslice_demo1 = 2.000000

demo1_counter: 1915369 demo2_counter: 3720158 demo2_counter / demo1_counter = 1.942267 should almost equals to: timeslice_demo2 / timeslice_demo1 = 2.000000

demo1_counter: 3774985 demo2_counter: 7493508 demo2_counter / demo1_counter = 1.985043 should almost equals to: timeslice_demo2 / timeslice_demo1 = 2.000000

demo1_counter: 5634601 demo2_counter: 11266858 demo2_counter / demo1_counter = 1.999584 should almost equals to: timeslice_demo2 / timeslice_demo1 = 2.000000

demo1_counter: 7546896 demo2_counter: 14987015 demo2_counter / demo1_counter = 1.985852 should almost equals to: timeslice_demo2 / timeslice_demo1 = 2.000000

demo1_counter: 9406512 demo2_counter: 18759340 demo2_counter / demo1_counter = 1.994293 should almost equals to: timeslice_demo2 / timeslice_demo1 = 2.000000

demo1_counter: 11266128 demo2_counter: 22531664 demo2_counter / demo1_counter = 1.999947 should almost equals to: timeslice_demo2 / timeslice_demo1 = 2.000000

demo1_counter: 13177398 demo2_counter: 26251821 demo2_counter / demo1_counter = 1.992185 should almost equals to: timeslice_demo2 / timeslice_demo1 = 2.000000

demo1_counter: 15037014 demo2_counter: 30023632 demo2_counter / demo1_counter = 1.996649 should almost equals to: timeslice_demo2 / timeslice_demo1 = 2.000000

demo1_counter: 16896630 demo2_counter: 33795443 demo2_counter / demo1_counter = 2.000129 should almost equals to: timeslice_demo2 / timeslice_demo1 = 2.000000

demo1_counter: 18807900 demo2_counter: 37515600 demo2_counter / demo1_counter = 1.994672 should almost equals to: timeslice_demo2 / timeslice_demo1 = 2.000000

[实例代码](./code/2.7 robin/main.c)

2.8 内核基础组件

2.8.1 消息队列

概述

消息队列提供了一种同步的传递/收取消息的机制,与队列(tos_queue)不同的是,tos_queue基于消息队列封装了一层异步的机制,实际上tos_queue的底层消息管理采用的就是消息队列。

API讲解
编程实例

1、在tos_config.h中,配置消息队列组件开关TOS_CFG_MSG_EN:

#define TOS_CFG_MSG_EN 1u

2、在tos_config.h中,配置消息队列池大小TOS_CFG_MSG_POOL_SIZE:

#define TOS_CFG_MSG_POOL_SIZE 3u

这是消息队列池中可以承载的最大消息数量

3、编写main.c示例代码:

#include "tos.h"
#include "mcu_init.h"

#define STK_SIZE_TASK_DEMO      512 

#define PRIO_TASK_DEMO          4
 
k_stack_t stack_task_demo[STK_SIZE_TASK_DEMO]; 

k_task_t task_demo;

k_msg_queue_t msg_queue;

struct msg_st {
    char *msg;
    size_t size;
} msgs[TOS_CFG_MSG_POOL_SIZE] = {
    { "msg 0", 6 },
    { "msg 1", 6 },
    { "msg 2", 6 },
};

struct msg_st dummy_msg = { "dummy msg", 10 };

extern void entry_task_demo(void *arg);  

void fifo_opt(void) {
    k_err_t err;
    int i = 0;
    char *msg_received = K_NULL;
    size_t msg_size = 0;

    for (; i < TOS_CFG_MSG_POOL_SIZE; ++i) {
        printf("msg put: %s\n", msgs[i].msg);
        err = tos_msg_queue_put(&msg_queue, (void *)msgs[i].msg, msgs[i].size, TOS_OPT_MSG_PUT_FIFO);
        if (err != K_ERR_NONE) {
            printf("should never happen\n");
        }
    }

    err = tos_msg_queue_put(&msg_queue, (void *)dummy_msg.msg, dummy_msg.size, TOS_OPT_MSG_PUT_FIFO);
    if (err == K_ERR_MSG_QUEUE_FULL) {
        printf("msg queue is full\n");
    } else {
        printf("should never happen\n");
    }
    
    for (i = 0; i < TOS_CFG_MSG_POOL_SIZE; ++i) {
        err = tos_msg_queue_get(&msg_queue, (void **)&msg_received, &msg_size);
        if (err == K_ERR_NONE) {
            printf("msg received: %s\n", msg_received);
            printf("msg size: %d\n", msg_size);
        } else {
            printf("should never happen\n");
        }
    }
    err = tos_msg_queue_get(&msg_queue, (void **)&msg_received, &msg_size);
    if (err == K_ERR_MSG_QUEUE_EMPTY) {
        printf("msg queue is empty\n");
    } else {
        printf("should never happen\n");
    }
}

void lifo_opt(void) {
    k_err_t err;
    int i = 0;
    char *msg_received = K_NULL;
    size_t msg_size = 0;

    for (; i < TOS_CFG_MSG_POOL_SIZE; ++i) {
        printf("msg put: %s\n", msgs[i].msg);
        err = tos_msg_queue_put(&msg_queue, (void *)msgs[i].msg, msgs[i].size, TOS_OPT_MSG_PUT_LIFO);
        if (err != K_ERR_NONE) {
            printf("should never happen\n");
        }
    }
    err = tos_msg_queue_put(&msg_queue, (void *)dummy_msg.msg, dummy_msg.size, TOS_OPT_MSG_PUT_LIFO);
    if (err == K_ERR_MSG_QUEUE_FULL) {
        printf("msg queue is full\n");
    } else {
        printf("should never happen\n");
    }
    
    for (i = 0; i < TOS_CFG_MSG_POOL_SIZE; ++i) {
        err = tos_msg_queue_get(&msg_queue, (void **)&msg_received, &msg_size);
        if (err == K_ERR_NONE) {
            printf("msg received: %s\n", msg_received);
            printf("msg size: %d\n", msg_size);
        } else {
            printf("should never happen\n");
        }
    }
    err = tos_msg_queue_get(&msg_queue, (void **)&msg_received, &msg_size);
    if (err == K_ERR_MSG_QUEUE_EMPTY) {
        printf("msg queue is empty\n");
    } else {
        printf("should never happen\n");
    }
}

void entry_task_demo(void *arg)
{ 
    tos_msg_queue_create(&msg_queue);

    printf("max msg in pool: %d\n", TOS_CFG_MSG_POOL_SIZE);
    printf("msg queue using TOS_OPT_MSG_PUT_FIFO\n");
    fifo_opt();

    printf("msg queue using TOS_OPT_MSG_PUT_LIFO\n");
    lifo_opt();
}

int main(void)
{
    board_init();
    tos_knl_init(); 
    (void)tos_task_create(&task_demo, "demo1", entry_task_demo, NULL,
                            PRIO_TASK_DEMO, stack_task_demo, STK_SIZE_TASK_DEMO,
                            0); 
    tos_knl_start();
}
运行效果

max msg in pool: 3 msg queue using TOS_OPT_MSG_PUT_FIFO msg put: msg 0 msg put: msg 1 msg put: msg 2 msg queue is full msg received: msg 0 msg size: 6 msg received: msg 1 msg size: 6 msg received: msg 2 msg size: 6 msg queue is empty msg queue using TOS_OPT_MSG_PUT_LIFO msg put: msg 0 msg put: msg 1 msg put: msg 2 msg queue is full msg received: msg 2 msg size: 6 msg received: msg 1 msg size: 6 msg received: msg 0 msg size: 6 msg queue is empty

[实例代码](./code/2.8.1 msg_queue/main.c)

2.8.2 字符流先入先出队列

概述

字符流先入先出队列,提供的是一个面向字符操作的环形队列实现,提供了基本的字符流入队出队操作。

API讲解
编程实例

1、编写main.c示例代码:

#include "tos.h"
#include "mcu_init.h"

#define STK_SIZE_TASK_DEMO      512 

#define PRIO_TASK_DEMO          4
 
k_stack_t stack_task_demo[STK_SIZE_TASK_DEMO]; 

k_task_t task_demo;

#define FIFO_BUFFER_SIZE        5
uint8_t fifo_buffer[FIFO_BUFFER_SIZE];

k_fifo_t fifo;

extern void entry_task_demo(void *arg);

void char_push(void)
{
    k_err_t err;
    int i = 0;
    uint8_t data;

    // 往fifo中压入FIFO_BUFFER_SIZE个字符,分别是a、b、c、d、e
    for (i = 0; i < FIFO_BUFFER_SIZE; ++i) {
        printf("char pushed: %c\n", 'a' + i);
        err = tos_fifo_push(&fifo, 'a' + i);
        if (err != K_ERR_NONE) {
            printf("should never happen\n");
        }
    }

    // fifo最多包含FIFO_BUFFER_SIZE个字符,上文逻辑中已经压入了最大的字符量,此时继续压入字符会返回K_ERR_FIFO_FULL(fifo已满)
    err = tos_fifo_push(&fifo, 'z');
    if (err == K_ERR_FIFO_FULL) {
        printf("fifo is full: %s\n", tos_fifo_is_full(&fifo) ? "TRUE" : "FALSE");
    } else {
        printf("should never happen\n");
    }

    // 从fifo中把所有的字符都弹出来
    for (i = 0; i < FIFO_BUFFER_SIZE; ++i) {
        err = tos_fifo_pop(&fifo, &data);
        if (err == K_ERR_NONE) {
            printf("%d pop: %c\n", i, data);
        } else {
            printf("should never happen\n");
        }
    }
    // 此时继续弹出字符,会返回K_ERR_FIFO_EMPTY(fifo已空)
    err = tos_fifo_pop(&fifo, &data);
    if (err == K_ERR_FIFO_EMPTY) {
        printf("fifo is empty: %s\n", tos_fifo_is_empty(&fifo) ? "TRUE" : "FALSE");
    } else {
        printf("should never happen\n");
    }
}

void stream_push(void)
{
    int count = 0, i = 0;
    uint8_t stream[FIFO_BUFFER_SIZE] = { 'a', 'b', 'c', 'd', 'e' };
    uint8_t stream_dummy[1] = { 'z' };
    uint8_t stream_pop[FIFO_BUFFER_SIZE];

    // 压入字符流,字符流的长度是5,不超过fifo的最大长度FIFO_BUFFER_SIZE,会压入成功,并返回压入字符流的长度5
    count = tos_fifo_push_stream(&fifo, &stream[0], FIFO_BUFFER_SIZE);
    if (count != FIFO_BUFFER_SIZE) {
        printf("should never happen\n");
    }

    // 继续压入字符流(即使是长度为1的字符流),因fifo已满无法继续压入,返回长度0(压入失败)
    count = tos_fifo_push_stream(&fifo, &stream_dummy[0], 1);
    if (count == 0) {
        printf("fifo is full: %s\n", tos_fifo_is_full(&fifo) ? "TRUE" : "FALSE");
    } else {
        printf("should never happen\n");
    }

    // 将前文中压入的字符流全部弹出,返回前文压入的字符流长度5(弹出的字符流长度)
    count = tos_fifo_pop_stream(&fifo, &stream_pop[0], FIFO_BUFFER_SIZE);
    if (count == FIFO_BUFFER_SIZE) {
        printf("stream popped:\n");
        for (i = 0; i < FIFO_BUFFER_SIZE; ++i) {
            printf("%c", stream_pop[i]);
        }
        printf("\n");
    } else {
        printf("should never happen\n");
    }

    // 继续弹出,因fifo已空,返回0
    count = tos_fifo_pop_stream(&fifo, &stream_pop[0], 1);
    if (count == 0) {
        printf("fifo is empty: %s\n", tos_fifo_is_empty(&fifo) ? "TRUE" : "FALSE");
    } else {
        printf("should never happen\n");
    }
}

void entry_task_demo(void *arg)
{
    // 创建了一个最多包含FIFO_BUFFER_SIZE个字符的fifo
    tos_fifo_create(&fifo, &fifo_buffer[0], FIFO_BUFFER_SIZE);

    printf("fifo, dealing with char\n");
    char_push();

    printf("fifo, dealing with stream\n");
    stream_push();
}

int main(void)
{
    board_init();
    tos_knl_init(); 
    (void)tos_task_create(&task_demo, "demo1", entry_task_demo, NULL,
                            PRIO_TASK_DEMO, stack_task_demo, STK_SIZE_TASK_DEMO,
                            0); 
    tos_knl_start();
}
运行效果

fifo, dealing with char char pushed: a char pushed: b char pushed: c char pushed: d char pushed: e fifo is full: TRUE 0 pop: a 1 pop: b 2 pop: c 3 pop: d 4 pop: e fifo is empty: TRUE fifo, dealing with stream fifo is full: TRUE stream popped: abcde fifo is empty: TRUE

[实例代码](./code/2.8.2 fifo/main.c)

2.9 功耗管理

2.9.1 低功耗

概述

TencentOS tiny提供了多级低功耗管理框架。初级低功耗的方案是,当系统处于“空闲”状态,也即进入idle任务时,系统调用处理器(目前支持的架构是arm v7m)低功耗接口进入短暂的睡眠模式。

API讲解
编程实例

对于初级低功耗模式,无需用户编写任何代码,直接通过在tos_config.h打开TOS_CFG_PMR_MGR_EN开关即可:

#define TOS_CFG_PWR_MGR_EN 1u

运行效果

2.9.2 tickless

概述

TencentOS tiny的tickless机制提供了一套非周期性时钟的方案,在系统无需systick驱动调度的情况下,停掉systick。

初级功耗管理方案下,因为还有系统systick的存在,因此系统进入idle任务后,并不会在睡眠模式下停留太久。要想进入到更极致的低功耗状态,需要暂停systick。

arm架构提供三级低功耗模式,sleep、stop、standby模式,三种模式运行功耗逐次降低,standby模式最低。TencentOS tiny的内核提供了简洁清晰的接口来管理各级模式。

API讲解
void tos_tickless_wkup_alarm_install(k_cpu_lpwr_mode_t mode, k_tickless_wkup_alarm_t *wkup_alarm);

此接口用以安装各低功耗模式下的唤醒闹钟。当内核进入tickless模式下后,systick以及停止了,因此需要其他计时器来将CPU从低功耗模式下唤醒。

根据arm v7m的芯片规格,三种模式下的唤醒源分别为:

  • sleep

    CPU进入sleep模式后,可以由systick、硬件timer、RTC时钟唤醒(wakeup/alarm中断)。

  • stop

    CPU进入stop模式后,可以由RTC时钟(wakeup/alarm中断)唤醒。

  • standby

    CPU进入standby模式后,只可由RTC时钟的alarm中断唤醒(还可以通过外部管脚唤醒,但这不属于TencentOS tiny内核机制设计的范畴)。

k_tickless_wkup_alarm_t定义如下:

typedef struct k_tickless_wakeup_alarm_st {
    int         (*init)(void);
    int         (*setup)(k_time_t millisecond);
    int         (*dismiss)(void);
    k_time_t    (*max_delay)(void); /* in millisecond */
} k_tickless_wkup_alarm_t;

一个唤醒闹钟有四个成员方法:

  • init

    闹钟初始化函数。

  • setup

    闹钟设定函数,入参为闹钟到期时间(单位毫秒)。此闹钟在设定完毕后的millisecond毫秒时来中断。

  • dismiss

    闹钟解除函数,执行完后闹钟中断不会再来。

  • max_delay

    此闹钟最长的到期时间(单位为毫秒)。

k_err_t tos_tickless_wkup_alarm_init(k_cpu_lpwr_mode_t mode);

此函数用来初始化特定模式下的唤醒闹钟(实际上调用的是tos_tickless_wkup_alarm_install接口中安装的k_tickless_wkup_alarm_t的init方法)。

k_err_t tos_pm_cpu_lpwr_mode_set(k_cpu_lpwr_mode_t cpu_lpwr_mode);

设置内核在tickless模式下进入的CPU低功耗模式。

编程实例

1、在tos_config.h中,配置低功耗组件开关TOS_CFG_PWR_MGR_EN:

#define TOS_CFG_PWR_MGR_EN 1u

2、在tos_config.h中,配置tickless组件开关TOS_CFG_TICKLESS_EN:

#define TOS_CFG_TICKLESS_EN 1u

3、编写main.c示例代码:

#include "tos.h"
#include "mcu_init.h"

#define STK_SIZE_TASK_DEMO      512 

#define PRIO_TASK_DEMO          4
 
k_stack_t stack_task_demo[STK_SIZE_TASK_DEMO]; 

k_task_t task_demo;

extern void entry_task_demo(void *arg);

void timer_callback(void *arg)
{
    printf("timer callback: %lld\n", tos_systick_get());
}

void entry_task_demo(void *arg)
{
    k_timer_t tmr;

    // 创建一个软件定时器,每6000个tick触发一次
    tos_timer_create(&tmr, 0u, 6000u, timer_callback, K_NULL, TOS_OPT_TIMER_PERIODIC);
    tos_timer_start(&tmr);

    // 此任务体内每3000个tick运行一次
    while (K_TRUE) {
        printf("entry task demo: %lld\n", tos_systick_get());
        tos_task_delay(3000);
    }
}

int main(void)
{
    board_init();
    tos_knl_init(); 
    (void)tos_task_create(&task_demo, "demo1", entry_task_demo, NULL,
                            PRIO_TASK_DEMO, stack_task_demo, STK_SIZE_TASK_DEMO,
                            0); 
    tos_knl_start();
}

4、实现tos_bsp_tickless_setup回调(参考board\TOS_tiny_EVK_STM32L431CBT6\BSP\Src\tickless\bsp_pwr_mgr.c、board\TOS_tiny_EVK_STM32L431CBT6\BSP\Src\tickless\bsp_tickless_alarm.c):

#include "tos.h"
#include "tickless/bsp_pm_device.h"
#include "tickless/bsp_tickless_alarm.h"

int tos_bsp_tickless_setup(void)
{
#if TOS_CFG_TICKLESS_EN > 0u
    // sleep模式下的唤醒源,基本定时器
    tos_tickless_wkup_alarm_install(TOS_LOW_POWER_MODE_SLEEP, &tickless_wkup_alarm_tim);
    // 初始化唤醒源闹钟
    tos_tickless_wkup_alarm_init(TOS_LOW_POWER_MODE_SLEEP);
    // 设置tickless状态时进入sleep模式
    tos_pm_cpu_lpwr_mode_set(TOS_LOW_POWER_MODE_SLEEP);
#endif
}

5、为了观察在tickless时是否确实没有systick中断,在tos_sys.c的idle任务体内加一句调试代码:

__STATIC__ void knl_idle_entry(void *arg)
{
    arg = arg; // make compiler happy

    while (K_TRUE) {
        // 这里在idle任务体内加上一句打印,如果systick正常开启,在没有用户任务运行时,此调试信息会不断打印;如果是tickless状态,此调试信息应该只会第一次进入idle任务时,或在用户任务等待到期,或用户的软件定时器到期时,才打印一次。
        printf("idle entry: %lld\n", tos_systick_get());
#if TOS_CFG_PWR_MGR_EN > 0u
        pm_power_manager();
#endif
    }
}
运行效果

entry task demo: 0 idle entry: 2 entry task demo: 3002 idle entry: 3002 timer callback: 6000 idle entry: 6000 entry task demo: 6002 idle entry: 6002 entry task demo: 9002 idle entry: 9002 timer callback: 12000 idle entry: 12000 entry task demo: 12002 idle entry: 12002 entry task demo: 15002 idle entry: 15002 timer callback: 18000 idle entry: 18000 entry task demo: 18002 idle entry: 18002