TencentOS-tiny/doc/04.Development_Manual.md

2806 lines
84 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TencentOS tiny 开发指南
目录
- [TencentOS tiny 开发指南](#tencentos-tiny开发指南)
- [1. 概述](#1-概述)
- [1.1 基础内核组件](#11-基础内核组件)
- [2. 基础内核](#2-基础内核)
- [2.1 系统管理](#21-系统管理)
- [概述](#概述)
- [API 讲解](#api讲解)
- [编程实例](#编程实例)
- [运行效果](#运行效果)
- [2.2 任务管理](#22-任务管理)
- [概述](#概述-1)
- [API 讲解](#api讲解-1)
- [编程实例](#编程实例-1)
- [运行效果](#运行效果-1)
- [2.3 任务间通信](#23-任务间通信)
- [2.3.1 互斥量](#231-互斥量)
- [概述](#概述-2)
- [API 讲解](#api讲解-2)
- [编程实例](#编程实例-2)
- [运行效果](#运行效果-2)
- [2.3.2 信号量](#232-信号量)
- [概述](#概述-3)
- [API 讲解](#api讲解-3)
- [编程实例](#编程实例-3)
- [运行效果](#运行效果-3)
- [2.3.3 事件](#233-事件)
- [概述](#概述-4)
- [API 讲解](#api讲解-4)
- [编程实例](#编程实例-4)
- [运行效果](#运行效果-4)
- [2.3.4 完成量](#234-完成量)
- [概述](#概述-5)
- [API 讲解](#api讲解-5)
- [编程实例](#编程实例-5)
- [运行效果](#运行效果-5)
- [2.3.5 计数锁](#235-计数锁)
- [概述](#概述-6)
- [API 讲解](#api讲解-6)
- [编程实例](#编程实例-6)
- [运行效果](#运行效果-6)
- [2.3.6 栅栏](#236-栅栏)
- [概述](#概述-7)
- [API 讲解](#api讲解-7)
- [编程实例](#编程实例-7)
- [运行效果](#运行效果-7)
- [2.3.7 消息队列](#237-消息队列)
- [概述](#概述-8)
- [API 讲解](#api讲解-8)
- [编程实例](#编程实例-8)
- [运行效果](#运行效果-8)
- [2.3.8 邮箱队列](#238-邮箱队列)
- [概述](#概述-9)
- [API 讲解](#api讲解-9)
- [编程实例](#编程实例-9)
- [运行效果](#运行效果-9)
- [2.3.9 优先级消息队列](#239-优先级消息队列)
- [概述](#概述-10)
- [API 讲解](#api讲解-10)
- [编程实例](#编程实例-10)
- [运行效果](#运行效果-10)
- [2.3.10 优先级邮箱队列](#2310-优先级邮箱队列)
- [概述](#概述-11)
- [API 讲解](#api讲解-11)
- [编程实例](#编程实例-11)
- [运行效果](#运行效果-11)
- [2.4 内存管理](#24-内存管理)
- [2.4.1 动态内存](#241-动态内存)
- [概述](#概述-12)
- [API 讲解](#api讲解-12)
- [编程实例](#编程实例-12)
- [运行效果](#运行效果-12)
- [2.4.2 静态内存](#242-静态内存)
- [概述](#概述-13)
- [API 讲解](#api讲解-13)
- [编程实例](#编程实例-13)
- [运行效果](#运行效果-13)
- [2.5 时间管理](#25-时间管理)
- [概述](#概述-14)
- [API 讲解](#api讲解-14)
- [编程实例](#编程实例-14)
- [运行效果](#运行效果-14)
- [2.6 软件定时器](#26-软件定时器)
- [概述](#概述-15)
- [API 讲解](#api讲解-15)
- [编程实例](#编程实例-15)
- [运行效果](#运行效果-15)
- [2.7 时间片轮转机制](#27-时间片轮转机制)
- [概述](#概述-16)
- [API 讲解](#api讲解-16)
- [编程实例](#编程实例-16)
- [运行效果](#运行效果-16)
- [2.8 内核基础组件](#28-内核基础组件)
- [2.8.1 环形队列](#281-环形队列)
- [概述](#概述-17)
- [API 讲解](#api讲解-17)
- [编程实例](#编程实例-17)
- [运行效果](#运行效果-17)
- [2.8.2 字符流先入先出队列](#282-字符流先入先出队列)
- [概述](#概述-18)
- [API 讲解](#api讲解-18)
- [编程实例](#编程实例-18)
- [运行效果](#运行效果-18)
- [2.8.3 二项堆](#283-二项堆)
- [概述](#概述-19)
- [2.8.4 优先级队列](#284-优先级队列)
- [概述](#概述-20)
- [API 讲解](#api讲解-19)
- [编程实例](#编程实例-19)
- [运行效果](#运行效果-19)
- [2.9 功耗管理](#29-功耗管理)
- [2.9.1 低功耗](#291-低功耗)
- [概述](#概述-21)
- [API 讲解](#api讲解-20)
- [编程实例](#编程实例-20)
- [运行效果](#运行效果-20)
- [2.9.2 tickless](#292-tickless)
- [概述](#概述-22)
- [API 讲解](#api讲解-21)
- [编程实例](#编程实例-21)
- [运行效果](#运行效果-21)
## 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接口原型如下
```c
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 示例代码:
```c
#include "tos_k.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_prio4task_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_prio4task_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%20task%20manager/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 示例代码:
```c
#include "tos_k.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%20mutex/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 示例代码:
```c
#include "tos_k.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%20semaphore/main.c)
#### 2.3.3 事件
##### 概述
事件提供了一种任务间实现同步和信息传递的机制。一般来说,一个事件中包含了一个旗标,这个旗标的每一位表示一个“事件”。
一个任务可以等待一个或者多个“事件”的发生,其他任务在一定的业务条件下可以通过写入特定“事件”唤醒等待此“事件”的任务,实现一种类似信号的编程范式。
##### API 讲解
##### 编程实例
1、在 tos_config.h 中,配置事件组件开关 TOS_CFG_EVENT_EN
`#define TOS_CFG_EVENT_EN 1u`
2、编写 main.c 示例代码:
```c
#include "tos_k.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%20event/main.c)
#### 2.3.4 完成量
##### 概述
完成量是一种简单的任务间通信机制,用以在任务间同步某一事件是否已“完成”的信息。
##### API 讲解
##### 编程实例
1、编写 main.c 示例代码:
```c
/*
这个例子里创建了两个任务,一个任务 task_wait 等待完成量完成,另一个任务负责触发完成量(让完成量完成)
*/
#include "tos_k.h"
#include "mcu_init.h"
#define STK_SIZE_TASK_WAIT 512
#define STK_SIZE_TASK_TRIGGER 512
k_stack_t stack_task_wait[STK_SIZE_TASK_WAIT];
k_stack_t stack_task_trigger[STK_SIZE_TASK_TRIGGER];
k_task_t task_wait;
k_task_t task_trigger;
k_completion_t completion;
void entry_task_wait(void *arg)
{
printf("wait: I won't go further until someone do the trigger(make it 'complete')\n");
tos_completion_pend(&completion);
printf("wait: someone has made it complete, so I'm here\n");
}
void entry_task_trigger(void *arg)
{
printf("trigger: I'm the one who make complete, anyone waitting for the complete won't go further until I do the trigger\n");
tos_completion_post(&completion);
printf("trigger: I have done the completion\n");
}
int main(void)
{
board_init();
tos_knl_init();
tos_completion_create(&completion);
(void)tos_task_create(&task_wait, "wait", entry_task_wait, NULL,
3, stack_task_wait, STK_SIZE_TASK_WAIT, 0);
(void)tos_task_create(&task_trigger, "trigger", entry_task_trigger, NULL,
4, stack_task_trigger, STK_SIZE_TASK_TRIGGER, 0);
tos_knl_start();
}
```
##### 运行效果
> wait: I won't go further until someone do the trigger(make it 'complete')
> trigger: I'm the one who make complete, anyone waitting for the complete won't go further until I do the trigger
> wait: someone make it complete, so I'm here
> trigger: I have done the completion
[实例代码](code/2.3.4%20completion/main.c)
#### 2.3.5 计数锁
##### 概述
计数锁提供了一种“计数信息”同步的概念,计数锁创建的时候会指定一个计数值,每当有任务执行 tos_countdownlatch_post 时,该计数锁的计数值减一,直到计数锁的计数值为零时,等待此计数锁的任务才会被唤醒。
##### API 讲解
##### 编程实例
1、编写 main.c 示例代码:
```c
/*
假设有这样的业务场景,共有三个勇士,此三个勇士分头去寻找三个武器的碎片,只有这三个勇士都找到碎片后,法师才能将三个碎片合成为武器。用代码看具体如何使用计数锁来完成这个模型。
*/
#include "tos_k.h"
#include "mcu_init.h"
#define STK_SIZE_TASK_WIZARD 512
#define STK_SIZE_TASK_WARRIOR 512
k_stack_t stack_task_wizard[STK_SIZE_TASK_WIZARD];
k_stack_t stack_task_warrior_0[STK_SIZE_TASK_WARRIOR];
k_stack_t stack_task_warrior_1[STK_SIZE_TASK_WARRIOR];
k_stack_t stack_task_warrior_2[STK_SIZE_TASK_WARRIOR];
k_task_t task_wizard;
k_task_t task_warrior_0;
k_task_t task_warrior_1;
k_task_t task_warrior_2;
k_countdownlatch_t countdownlatch;
void entry_task_warrior_0(void *arg)
{
printf("warrior 0: I have done my job\n");
tos_countdownlatch_post(&countdownlatch);
}
void entry_task_warrior_1(void *arg)
{
printf("warrior 1: I have done my job\n");
tos_countdownlatch_post(&countdownlatch);
}
void entry_task_warrior_2(void *arg)
{
printf("warrior 2: I have done my job\n");
tos_countdownlatch_post(&countdownlatch);
}
void entry_task_wizard(void *arg)
{
printf("wizard: I will set 3 warriors to find the fragments\n");
tos_countdownlatch_create(&countdownlatch, 3);
(void)tos_task_create(&task_warrior_0, "warrior_0", entry_task_warrior_0, NULL,
4, stack_task_warrior_0, STK_SIZE_TASK_WARRIOR, 0);
(void)tos_task_create(&task_warrior_1, "warrior_1", entry_task_warrior_1, NULL,
4, stack_task_warrior_1, STK_SIZE_TASK_WARRIOR, 0);
(void)tos_task_create(&task_warrior_2, "warrior_2", entry_task_warrior_2, NULL,
4, stack_task_warrior_2, STK_SIZE_TASK_WARRIOR, 0);
printf("wizard: now warriors are on their way, I will wait here until they all done the job\n");
tos_countdownlatch_pend(&countdownlatch);
printf("wizard: the warriors all have done their jobs, let's make the weapon\n");
}
int main(void)
{
board_init();
tos_knl_init();
(void)tos_task_create(&task_wizard, "wizard", entry_task_wizard, NULL,
3, stack_task_wizard, STK_SIZE_TASK_WIZARD, 0);
tos_knl_start();
}
```
##### 运行效果
> wizard: I will set 3 warriors to find the fragments
> wizard: now warriors are on their way, I will wait here until they all done the job
> warrior 0: I have done my job
> warrior 1: I have done my job
> warrior 2: I have done my job
> wizard: the warriors all have done their jobs, let's make the weapon
[实例代码](code/2.3.5%20countdownlatch/main.c)
#### 2.3.6 栅栏
##### 概述
栅栏提供了一种设置任务阻塞屏障的机制,栅栏创建的时候会指定一个计数值,每当有任务执行 tos_barrier_pend 时,该计数锁的计数值减一,直到计数锁的计数值为零时,所有阻塞在 tos_barrier_pend 点上的任务才可以往下运行。
##### API 讲解
##### 编程实例
1、编写 main.c 示例代码:
```c
/*
假设有这样的业务场景,共有三个勇士,此三个勇士分头去寻找三个武器的碎片,任意勇士找到自己的那块武器碎片时,都在原地等待,直到所有的小伙伴都找到了自己的武器碎片后,才各自采取下一步行动。用代码看具体如何使用栅栏来完成这个模型。
*/
#include "tos_k.h"
#include "mcu_init.h"
#define STK_SIZE_TASK_WARRIOR 512
k_stack_t stack_task_warrior_0[STK_SIZE_TASK_WARRIOR];
k_stack_t stack_task_warrior_1[STK_SIZE_TASK_WARRIOR];
k_stack_t stack_task_warrior_2[STK_SIZE_TASK_WARRIOR];
k_task_t task_warrior_0;
k_task_t task_warrior_1;
k_task_t task_warrior_2;
k_barrier_t barrier;
void entry_task_warrior_0(void *arg)
{
printf("warrior 0: I'm searching the fragment\n");
tos_task_delay(1000);
printf("warrior 0: I have done my job, waitting other buddies done their job\n");
tos_barrier_pend(&barrier);
printf("warrior 0: all buddies find their fragment, do my next job\n");
}
void entry_task_warrior_1(void *arg)
{
printf("warrior 1: I'm searching the fragment\n");
tos_task_delay(1500);
printf("warrior 1: I have done my job, waitting other buddies done their job\n");
tos_barrier_pend(&barrier);
printf("warrior 1: all buddies find their fragment, do my next job\n");
}
void entry_task_warrior_2(void *arg)
{
printf("warrior 2: I'm searching the fragment\n");
tos_task_delay(2000);
printf("warrior 2: I have done my job, waitting other buddies done their job\n");
tos_barrier_pend(&barrier);
printf("warrior 2: all buddies find their fragment, do my next job\n");
}
int main(void)
{
board_init();
tos_knl_init();
tos_barrier_create(&barrier, 3);
(void)tos_task_create(&task_warrior_0, "warrior_0", entry_task_warrior_0, NULL,
4, stack_task_warrior_0, STK_SIZE_TASK_WARRIOR, 0);
(void)tos_task_create(&task_warrior_1, "warrior_1", entry_task_warrior_1, NULL,
4, stack_task_warrior_1, STK_SIZE_TASK_WARRIOR, 0);
(void)tos_task_create(&task_warrior_2, "warrior_2", entry_task_warrior_2, NULL,
4, stack_task_warrior_2, STK_SIZE_TASK_WARRIOR, 0);
tos_knl_start();
}
```
##### 运行效果
> warrior 0: I'm searching the fragment
> warrior 1: I'm searching the fragment
> warrior 2: I'm searching the fragment
> warrior 0: I have done my job, waitting other buddies done their job
> warrior 1: I have done my job, waitting other buddies done their job
> warrior 2: I have done my job, waitting other buddies done their job
> warrior 2: all buddies find their fragment, do my next job
> warrior 0: all buddies find their fragment, do my next job
> warrior 1: all buddies find their fragment, do my next job
[实例代码](code/2.3.6%20barrier/main.c)
#### 2.3.7 消息队列
##### 概述
消息队列提供了任务间传递指针数据的机制,所谓的“消息“就是指针。消息本身如何解析使用,由传递消息的两个任务自行规定,消息队列不对消息本身做任何规定和限制,消息队列仅承担指针数据的传递义务。
##### API 讲解
##### 编程实例
1、在 tos_config.h 中,配置消息队列组件开关 TOS_CFG_MESSAGE_QUEUE_EN
`#define TOS_CFG_MESSAGE_QUEUE_EN 1u`
2、编写 main.c 示例代码:
```c
/*
这里演示如何使用消息队列在 sender 和 receiver 任务之间传递消息(一个指针,此案例中这个指针信息指向的是一个字符串)
*/
#include "tos_k.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)
#define MESSAGE_MAX 10
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];
uint8_t msg_pool[MESSAGE_MAX * sizeof(void *)];
k_task_t task_receiver_higher_prio;
k_task_t task_receiver_lower_prio;
k_task_t task_sender;
k_msg_q_t msg_q;
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;
while (K_TRUE) {
err = tos_msg_q_pend(&msg_q, &msg_received, TOS_TIME_FOREVER);
if (err == K_ERR_NONE) {
printf("higher: msg incoming[%s]\n", (char *)msg_received);
}
}
}
void entry_task_receiver_lower_prio(void *arg)
{
k_err_t err;
void *msg_received;
while (K_TRUE) {
err = tos_msg_q_pend(&msg_q, &msg_received, TOS_TIME_FOREVER);
if (err == K_ERR_NONE) {
printf("lower: msg incoming[%s]\n", (char *)msg_received);
}
}
}
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";
while (K_TRUE) {
if (i == 2) {
printf("sender: send a message to one receiver, and shoud be the highest priority one\n");
tos_msg_q_post(&msg_q, msg_to_one_receiver);
}
if (i == 3) {
printf("sender: send a message to all recevier\n");
tos_msg_q_post_all(&msg_q, msg_to_all_receiver);
}
if (i == 4) {
printf("sender: send a message to one receiver, and shoud be the highest priority one\n");
tos_msg_q_post(&msg_q, msg_to_one_receiver);
}
if (i == 5) {
printf("sender: send a message to all recevier\n");
tos_msg_q_post_all(&msg_q, msg_to_all_receiver);
}
tos_task_delay(1000);
++i;
}
}
int main(void)
{
board_init();
tos_knl_init();
tos_msg_q_create(&msg_q, msg_pool, MESSAGE_MAX);
(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);
(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();
}
```
##### 运行效果
> sender: send a message to one receiver, and shoud be the highest priority one
> higher: msg incoming[message for one receiver(with highest priority)]
> sender: send a message to all recevier
> higher: msg incoming[message for all receivers]
> lower: msg incoming[message for all receivers]
> sender: send a message to one receiver, and shoud be the highest priority one
> higher: msg incoming[message for one receiver(with highest priority)]
> sender: send a message to all recevier
> higher: msg incoming[message for all receivers]
> lower: msg incoming[message for all receivers]
[实例代码](code/2.3.7%20message%20queue/main.c)
#### 2.3.8 邮箱队列
##### 概述
消息队列传递的是指针,邮箱队列传递的是大片的内存数据。
##### API 讲解
##### 编程实例
1、在 tos_config.h 中,配置邮箱队列组件开关 TOS_CFG_MAIL_QUEUE_EN
`#define TOS_CFG_MAIL_QUEUE_EN 1u`
2、编写 main.c 示例代码:
```c
/*
这里演示如何使用邮箱队列在 sender 和 receiver 任务之间传递邮箱(此案例中邮件,也就是邮箱要传递的内存数据为一个 mail_t 类型的结构体,从此案例中可以看出来,邮箱队列相对消息队列来说,可以传递更为复杂的内存块数据)
*/
#include "tos_k.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)
#define MAIL_MAX 10
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];
typedef struct mail_st {
char *message;
int payload;
} mail_t;
uint8_t mail_pool[MAIL_MAX * sizeof(mail_t)];
k_task_t task_receiver_higher_prio;
k_task_t task_receiver_lower_prio;
k_task_t task_sender;
k_mail_q_t mail_q;
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;
mail_t mail;
size_t mail_size;
while (K_TRUE) {
err = tos_mail_q_pend(&mail_q, &mail, &mail_size, TOS_TIME_FOREVER);
if (err == K_ERR_NONE) {
TOS_ASSERT(mail_size == sizeof(mail_t));
printf("higher: msg incoming[%s], payload[%d]\n", mail.message, mail.payload);
}
}
}
void entry_task_receiver_lower_prio(void *arg)
{
k_err_t err;
mail_t mail;
size_t mail_size;
while (K_TRUE) {
err = tos_mail_q_pend(&mail_q, &mail, &mail_size, TOS_TIME_FOREVER);
if (err == K_ERR_NONE) {
TOS_ASSERT(mail_size == sizeof(mail_t));
printf("lower: msg incoming[%s], payload[%d]\n", mail.message, mail.payload);
}
}
}
void entry_task_sender(void *arg)
{
int i = 1;
mail_t mail;
while (K_TRUE) {
if (i == 2) {
printf("sender: send a mail to one receiver, and shoud be the highest priority one\n");
mail.message = "1st time post";
mail.payload = 1;
tos_mail_q_post(&mail_q, &mail, sizeof(mail_t));
}
if (i == 3) {
printf("sender: send a message to all recevier\n");
mail.message = "2nd time post";
mail.payload = 2;
tos_mail_q_post_all(&mail_q, &mail, sizeof(mail_t));
}
if (i == 4) {
printf("sender: send a message to one receiver, and shoud be the highest priority one\n");
mail.message = "3rd time post";
mail.payload = 3;
tos_mail_q_post(&mail_q, &mail, sizeof(mail_t));
}
if (i == 5) {
printf("sender: send a message to all recevier\n");
mail.message = "4th time post";
mail.payload = 4;
tos_mail_q_post_all(&mail_q, &mail, sizeof(mail_t));
}
tos_task_delay(1000);
++i;
}
}
int main(void)
{
board_init();
tos_knl_init();
tos_mail_q_create(&mail_q, mail_pool, MAIL_MAX, sizeof(mail_t));
(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);
(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,
5, stack_task_sender, STK_SIZE_TASK_SENDER, 0);
tos_knl_start();
}
```
##### 运行效果
> sender: send a mail to one receiver, and shoud be the highest priority one
> higher: msg incoming[1st time post], payload[1]
> sender: send a message to all recevier
> higher: msg incoming[2nd time post], payload[2]
> lower: msg incoming[2nd time post], payload[2]
> sender: send a message to one receiver, and shoud be the highest priority one
> higher: msg incoming[3rd time post], payload[3]
> sender: send a message to all recevier
> higher: msg incoming[4th time post], payload[4]
> lower: msg incoming[4th time post], payload[4]
[实例代码](code/2.3.8%20mail%20queue/main.c)
#### 2.3.9 优先级消息队列
##### 概述
优先级消息队列相对消息队列来说,给消息附加了一个优先级的概念,较高优先级的消息会比较低优先级的消息更快地被其他任务收到(本质上,消息队列的底层数据容器是环形队列,优先级消息队列的底层数据容器是优先级队列)。
##### API 讲解
##### 编程实例
1、在 tos_config.h 中,配置优先级消息队列组件开关 TOS_CFG_PRIORITY_MESSAGE_QUEUE_EN
`#define TOS_CFG_PRIORITY_MESSAGE_QUEUE_EN 1u`
2、编写 main.c 示例代码:
```c
#include "tos_k.h"
#include "mcu_init.h"
#define STK_SIZE_TASK_RECEIVER 512
#define STK_SIZE_TASK_SENDER 512
#define MESSAGE_MAX 10
k_stack_t stack_task_receiver[STK_SIZE_TASK_RECEIVER];
k_stack_t stack_task_sender[STK_SIZE_TASK_SENDER];
uint8_t msg_pool[MESSAGE_MAX * sizeof(void *)];
k_task_t task_receiver;
k_task_t task_sender;
k_prio_msg_q_t prio_msg_q;
extern void entry_task_receiver(void *arg);
extern void entry_task_sender(void *arg);
void entry_task_receiver(void *arg)
{
k_err_t err;
void *msg_received;
while (K_TRUE) {
err = tos_prio_msg_q_pend(&prio_msg_q, &msg_received, TOS_TIME_FOREVER);
if (err == K_ERR_NONE) {
printf("receiver: msg incoming[%s]\n", (char *)msg_received);
}
}
}
void entry_task_sender(void *arg)
{
char *msg_prio_0 = "msg with priority 0";
char *msg_prio_1 = "msg with priority 1";
char *msg_prio_2 = "msg with priority 2";
printf("sender: post a message with priority 2\n");
tos_prio_msg_q_post(&prio_msg_q, msg_prio_2, 2);
printf("sender: post a message with priority 1\n");
tos_prio_msg_q_post(&prio_msg_q, msg_prio_1, 1);
printf("sender: post a message with priority 0\n");
tos_prio_msg_q_post(&prio_msg_q, msg_prio_0, 0);
}
int main(void)
{
board_init();
tos_knl_init();
tos_prio_msg_q_create(&prio_msg_q, msg_pool, MESSAGE_MAX);
(void)tos_task_create(&task_receiver, "receiver", entry_task_receiver, NULL,
5, stack_task_receiver, 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();
}
```
##### 运行效果
> sender: post a message with priority 2
> sender: post a message with priority 1
> sender: post a message with priority 0
> receiver: msg incoming[msg with priority 0]
> receiver: msg incoming[msg with priority 1]
> receiver: msg incoming[msg with priority 2]
[实例代码](code/2.3.9%20priority%20message%20queue/main.c)
#### 2.3.10 优先级邮箱队列
##### 概述
优先级邮箱队列相对邮箱队列来说,给邮件附加了一个优先级的概念,较高优先级的邮件会比较低优先级的邮件更快地被其他任务收到(本质上,邮箱队列的底层数据容器是环形队列,优先级邮箱队列的底层数据容器是优先级队列)。
##### API 讲解
##### 编程实例
1、在 tos_config.h 中,配置优先级邮箱队列组件开关 TOS_CFG_PRIORITY_MAIL_QUEUE_EN
`#define TOS_CFG_PRIORITY_MAIL_QUEUE_EN 1u`
2、编写 main.c 示例代码:
```c
/*
这里演示了优先级邮箱队列的使用,从 sender 任务中的逻辑可以看出来,依次 post 了三个 mail优先级按时间顺序依次为 2、1、0数值越高优先级越低。如果是传统的邮箱队列那个 receiver 应该是依次收到优先级为 2、1、0 的邮件;但这是优先级邮箱队列,因而 receiver 会按优先级顺序收到这三个邮件,也就是依次收到优先级为 0、1、2 的邮件。
*/
#include "tos_k.h"
#include "mcu_init.h"
#define STK_SIZE_TASK_RECEIVER 512
#define STK_SIZE_TASK_SENDER 512
#define MAIL_MAX 10
k_stack_t stack_task_receiver[STK_SIZE_TASK_RECEIVER];
k_stack_t stack_task_sender[STK_SIZE_TASK_SENDER];
typedef struct mail_st {
char *message;
int payload;
} mail_t;
uint8_t mail_pool[MAIL_MAX * sizeof(mail_t)];
k_task_t task_receiver;
k_task_t task_sender;
k_prio_mail_q_t prio_mail_q;
extern void entry_task_receiver(void *arg);
extern void entry_task_sender(void *arg);
void entry_task_receiver(void *arg)
{
k_err_t err;
mail_t mail;
size_t mail_size;
while (K_TRUE) {
err = tos_prio_mail_q_pend(&prio_mail_q, &mail, &mail_size, TOS_TIME_FOREVER);
if (err == K_ERR_NONE) {
TOS_ASSERT(mail_size == sizeof(mail_t));
printf("receiver: msg incoming[%s], payload[%d]\n", mail.message, mail.payload);
}
}
}
void entry_task_sender(void *arg)
{
mail_t mail_0, mail_1, mail_2;
printf("sender: post a mail with priority 2\n");
mail_2.message = "priority 2";
mail_2.payload = 2;
tos_prio_mail_q_post(&prio_mail_q, &mail_2, sizeof(mail_t), 2);
printf("sender: post a mail with priority 1\n");
mail_1.message = "priority 1";
mail_1.payload = 1;
tos_prio_mail_q_post_all(&prio_mail_q, &mail_1, sizeof(mail_t), 1);
printf("sender: post a mail with priority 0\n");
mail_0.message = "priority 0";
mail_0.payload = 0;
tos_prio_mail_q_post(&prio_mail_q, &mail_0, sizeof(mail_t), 0);
}
int main(void)
{
board_init();
tos_knl_init();
tos_prio_mail_q_create(&prio_mail_q, mail_pool, MAIL_MAX, sizeof(mail_t));
(void)tos_task_create(&task_receiver, "receiver", entry_task_receiver, NULL,
6, stack_task_receiver, STK_SIZE_TASK_RECEIVER, 0);
(void)tos_task_create(&task_sender, "sender", entry_task_sender, NULL,
5, stack_task_sender, STK_SIZE_TASK_SENDER, 0);
tos_knl_start();
}
```
##### 运行效果
> sender: post a mail with priority 2
> sender: post a mail with priority 1
> sender: post a mail with priority 0
> receiver: msg incoming[priority 0], payload[0]
> receiver: msg incoming[priority 1], payload[1]
> receiver: msg incoming[priority 2], payload[2]
[实例代码](code/2.3.10%20priority%20mail%20queue/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 示例代码:
```c
#include "tos_k.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%20mmheap/main.c)
#### 2.4.2 静态内存
##### 概述
静态内存管理模块,提供了一套管理静态内存块的机制,支持用户申请、释放定长的内存块。
##### API 讲解
创建静态内存池接口:
```c
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、编写 main.c 示例代码:
```c
#include "tos_k.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%20mmblk/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 示例代码:
```c
#include "tos_k.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%20time/main.c)
### 2.6 软件定时器
#### 概述
软件定时器提供了一套从软件层次实现的定时器机制,相对应的概念是硬件定时器。用户可以创建一系列的软件定时器,并指定软件定时器到期的条件以及执行回调,当软件定时器到期时会执行注册的回调。
通常来说,用户注册的软件定时器回调中很可能包含延迟动作或同步等待操作,或者回调函数本身逻辑复杂执行耗时较长,因此系统将软件定时器管理逻辑设计成一个任务,在这个任务中扫描定时器是否过期并执行定时器回调。但是如你所知,创建一个任务是需要消耗系统内存资源的(任务的栈、任务句柄本身的内存空间等等),而如果用户注册的软件定时器回调中并不包含延迟动作也不包含同步等待操作,或者回调本身执行耗时很短,这种情况下软件定时器管理逻辑无需被设计成任务,而是可以被设计成时钟中断中被调用的一个函数。当软件定时器管理逻辑被设计成一个函数时,就可以节省创建任务所需的资源。
系统默认采用的实现是将定时器管理逻辑设计为任务,当用户的定时器回调都是耗时极短的操作时,用户可以通过将软件定时器管理逻辑配置为函数来节省内存资源。通过在 tos_config.h 中打开 TOS_CFG_TIMER_AS_PROC 开关来讲软件定时器管理逻辑配置为函数:
`#define TOS_CFG_TIMER_AS_PROC 1u`
#### API 讲解
```c
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 示例代码:
```c
#include "tos_k.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%20timer/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 示例代码:
```c
#include "tos_k.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_default_timeslice_config((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%20robin/main.c)
### 2.8 内核基础组件
#### 2.8.1 环形队列
##### 概述
环形队列本质上就是支持先入先出操作的环形 buffer是系统的一个基础组件通常用来作为实现上层机制的底层数据容器。
##### API 讲解
##### 编程实例
1、编写 main.c 示例代码:
```c
#include "tos_k.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;
typedef struct item_st {
int a;
int b;
int c;
} item_t;
#define RING_QUEUE_ITEM_MAX 5
uint8_t ring_q_buffer[RING_QUEUE_ITEM_MAX * sizeof(item_t)];
k_ring_q_t rinq_q;
void entry_task_demo(void *arg)
{
k_err_t err;
int i = 0;
item_t item;
size_t item_size;
tos_ring_q_create(&rinq_q, ring_q_buffer, RING_QUEUE_ITEM_MAX, sizeof(item_t));
for (i = 0; i < RING_QUEUE_ITEM_MAX; ++i) {
printf("enqueue: %d %d %d\n", i, i, i);
item.a = i;
item.b = i;
item.c = i;
err = tos_ring_q_enqueue(&rinq_q, &item, sizeof(item_t));
if (err != K_ERR_NONE) {
printf("should never happen\n");
}
}
err = tos_ring_q_enqueue(&rinq_q, &item, sizeof(item_t));
if (err == K_ERR_RING_Q_FULL) {
printf("ring queue is full: %s\n", tos_ring_q_is_full(&rinq_q) ? "TRUE" : "FALSE");
} else {
printf("should never happen\n");
}
for (i = 0; i < RING_QUEUE_ITEM_MAX; ++i) {
err = tos_ring_q_dequeue(&rinq_q, &item, &item_size);
if (err == K_ERR_NONE) {
printf("dequeue: %d %d %d\n", item.a, item.b, item.c);
} else {
printf("should never happen\n");
}
}
err = tos_ring_q_dequeue(&rinq_q, &item, &item_size);
if (err == K_ERR_RING_Q_EMPTY) {
printf("ring queue is empty: %s\n", tos_ring_q_is_empty(&rinq_q) ? "TRUE" : "FALSE");
} else {
printf("should never happen\n");
}
}
int main(void)
{
board_init();
tos_knl_init();
(void)tos_task_create(&task_demo, "demo", entry_task_demo, NULL,
PRIO_TASK_DEMO, stack_task_demo, STK_SIZE_TASK_DEMO,
0);
tos_knl_start();
}
```
##### 运行效果
> enqueue: 0 0 0
> enqueue: 1 1 1
> enqueue: 2 2 2
> enqueue: 3 3 3
> enqueue: 4 4 4
> ring queue is full: TRUE
> dequeue: 0 0 0
> dequeue: 1 1 1
> dequeue: 2 2 2
> dequeue: 3 3 3
> dequeue: 4 4 4
> ring queue is empty: TRUE
[实例代码](code/2.8.1%20ring%20queue/main.c)
#### 2.8.2 字符流先入先出队列
##### 概述
字符流先入先出队列,提供的是一个面向字符操作的环形队列实现,提供了基本的字符流入队出队操作。本质上就是环形队列中元素为字符(单字节长度)时的特例,实际上字符流先出先出队列底层的实现就是环形队列。
##### API 讲解
##### 编程实例
1、编写 main.c 示例代码:
```c
#include "tos_k.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_chr_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_chr_fifo_push(&fifo, 'a' + i);
if (err != K_ERR_NONE) {
printf("should never happen\n");
}
}
// fifo 最多包含 FIFO_BUFFER_SIZE 个字符,上文逻辑中已经压入了最大的字符量,此时继续压入字符会返回 K_ERR_FIFO_FULLfifo 已满)
err = tos_chr_fifo_push(&fifo, 'z');
if (err == K_ERR_RING_Q_FULL) {
printf("fifo is full: %s\n", tos_chr_fifo_is_full(&fifo) ? "TRUE" : "FALSE");
} else {
printf("should never happen\n");
}
// 从 fifo 中把所有的字符都弹出来
for (i = 0; i < FIFO_BUFFER_SIZE; ++i) {
err = tos_chr_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_EMPTYfifo 已空)
err = tos_chr_fifo_pop(&fifo, &data);
if (err == K_ERR_RING_Q_EMPTY) {
printf("fifo is empty: %s\n", tos_chr_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_chr_fifo_push_stream(&fifo, &stream[0], FIFO_BUFFER_SIZE);
if (count != FIFO_BUFFER_SIZE) {
printf("should never happen\n");
}
// 继续压入字符流(即使是长度为 1 的字符流),因 fifo 已满无法继续压入,返回长度 0压入失败
count = tos_chr_fifo_push_stream(&fifo, &stream_dummy[0], 1);
if (count == 0) {
printf("fifo is full: %s\n", tos_chr_fifo_is_full(&fifo) ? "TRUE" : "FALSE");
} else {
printf("should never happen\n");
}
// 将前文中压入的字符流全部弹出,返回前文压入的字符流长度 5弹出的字符流长度
count = tos_chr_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_chr_fifo_pop_stream(&fifo, &stream_pop[0], 1);
if (count == 0) {
printf("fifo is empty: %s\n", tos_chr_fifo_is_empty(&fifo) ? "TRUE" : "FALSE");
} else {
printf("should never happen\n");
}
}
void entry_task_demo(void *arg)
{
// 创建了一个最多包含 FIFO_BUFFER_SIZE 个字符的 fifo
tos_chr_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, "demo", 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%20char%20fifo/main.c)
#### 2.8.3 二项堆
##### 概述
此组件用来内部实现优先级队列,不推荐用户使用。
#### 2.8.4 优先级队列
##### 概述
提供了基于优先级的队列管理。环形队列的入队出队规则是先入队的先出队first in, first out优先级队列的出队顺序是按照优先级来的优先级较高的元素先出队。
##### API 讲解
##### 编程实例
1、编写 main.c 示例代码:
```c
/*
此案例展示了优先级队列的出队规则:依次入队优先级为 5、4、3、2、1 的元素,出队时的顺序是按照优先级来的,也就是按优先级从高到低(数值越大,优先级越小),依次出队优先级为 1、2、3、4、5 元素。
*/
#include "tos_k.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;
typedef struct item_st {
int a;
int b;
int c;
} item_t;
#define PRIO_QUEUE_ITEM_MAX 5
uint8_t ring_q_buffer[PRIO_QUEUE_ITEM_MAX * sizeof(item_t)];
uint8_t mgr_pool[TOS_PRIO_Q_MGR_ARRAY_SIZE(PRIO_QUEUE_ITEM_MAX)];
k_prio_q_t prio_q;
void entry_task_demo(void *arg)
{
k_err_t err;
int i = 0;
item_t item;
k_prio_t prio;
size_t item_size;
tos_prio_q_create(&prio_q, mgr_pool, ring_q_buffer, PRIO_QUEUE_ITEM_MAX, sizeof(item_t));
for (i = PRIO_QUEUE_ITEM_MAX; i > 0; --i) {
printf("enqueue: %d %d %d\n", i, i, i);
item.a = i;
item.b = i;
item.c = i;
err = tos_prio_q_enqueue(&prio_q, &item, sizeof(item_t), i);
if (err != K_ERR_NONE) {
printf("should never happen\n");
}
}
err = tos_prio_q_enqueue(&prio_q, &item, sizeof(item_t), i);
if (err == K_ERR_PRIO_Q_FULL) {
printf("priority queue is full: %s\n", tos_prio_q_is_full(&prio_q) ? "TRUE" : "FALSE");
} else {
printf("should never happen\n");
}
for (i = 0; i < PRIO_QUEUE_ITEM_MAX; ++i) {
err = tos_prio_q_dequeue(&prio_q, &item, &item_size, &prio);
if (err == K_ERR_NONE) {
printf("dequeue: %d %d %d, prio: %d\n", item.a, item.b, item.c, prio);
} else {
printf("should never happen\n");
}
}
err = tos_prio_q_dequeue(&prio_q, &item, &item_size, &prio);
if (err == K_ERR_PRIO_Q_EMPTY) {
printf("priority queue is empty: %s\n", tos_prio_q_is_empty(&prio_q) ? "TRUE" : "FALSE");
} else {
printf("should never happen\n");
}
}
int main(void)
{
board_init();
tos_knl_init();
(void)tos_task_create(&task_demo, "demo", entry_task_demo, NULL,
PRIO_TASK_DEMO, stack_task_demo, STK_SIZE_TASK_DEMO,
0);
tos_knl_start();
}
```
##### 运行效果
> enqueue: 5 5 5
> enqueue: 4 4 4
> enqueue: 3 3 3
> enqueue: 2 2 2
> enqueue: 1 1 1
> priority queue is full: TRUE
> dequeue: 1 1 1, prio: 1
> dequeue: 2 2 2, prio: 2
> dequeue: 3 3 3, prio: 3
> dequeue: 4 4 4, prio: 4
> dequeue: 5 5 5, prio: 5
> priority queue is empty: TRUE
[实例代码](code/2.8.4%20priority%20queue/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 讲解
```c
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 定义如下:
```c
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
此闹钟最长的到期时间(单位为毫秒)。
```c
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 方法)。
```c
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 示例代码:
```c
#include "tos_k.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
```c
#include "tos_k.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 任务体内加一句调试代码:
```c
__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