Содержание
[TOC]
TencentOS tiny — это операционная система, ориентированная на сферу Интернета вещей (IoT), состоящая из упрощенного ядра реального времени (RTOS) и богатого набора компонентов для IoT.
Модуль управления системой предоставляет функции инициализации ядра, запуска ядра, управления входом/выходом из прерываний, блокировки/разблокировки системного планировщика и другие.
Предоставляет функции создания, удаления, сна, пробуждения, приостановки, восстановления, изменения приоритета и отказа задач от использования процессора.
Предоставляет механизмы межзадачной коммуникации, такие как мьютексы, семафоры, очереди и события.
Предоставляет механизмы динамического управления памятью на основе кучи и статического управления блоками памяти.
Предоставляет механизмы получения/установки системного времени, преобразования системного времени в реальное время, а также управления временем сна задач.
Предоставляет механизмы создания, удаления, запуска и остановки программных таймеров.- Механизм чередования времени
Ядро TencentOS tiny, помимо предоставления планировщика с возможностью прерывания, также предлагает механизм чередования времени Round Robin.
Предоставляет механизмы очередей сообщений и очередей FIFO для символьных потоков.
Предоставляет механизмы управления низким энергопотреблением процессора, регистрации низкого энергопотребления устройств и настройки будильников для пробуждения.
Модуль управления системой предоставляет несколько интерфейсов для инициализации/запуска ядра TencentOS tiny, блокировки/разблокировки системного планировщика и других функций.
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);
Разблокировка системного планировщика, позволяющая задачам переключаться.
Ядро TencentOS tiny является прерываемым реального времени ядром с одноранговым адресным пространством. Ядро TencentOS tiny не предоставляет модель процессов, задачи соответствуют понятию потока, являясь минимальной единицей для планирования и минимальной единицей для владения ресурсами.
Сущность задачи — это исполняемая единица с независимым пространством стека, которую можно планировать. Пользователи могут написать свою бизнес-логику в функции входа задачи; между несколькими задачами можно использовать механизмы синхронизации и передачи информации, предоставляемые системой; каждая задача имеет приоритет, и задача с высоким приоритетом может прервать выполнение задачи с низким приоритетом.
Системный 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
: указатель на структуру задачи.
name
: имя задачи.
entry
: указатель на функцию входа задачи.
arg
: указатель на аргументы, передаваемые функции входа задачи.
prio
: приоритет задачи.
stk_base
: указатель на базу стека задачи.
stk_size
: размер стека задачи.
timeslice
: размер среза времени задачи. Это указатель типа k_task_t
, где k_task_t
— это тип структуры задачи ядра. Важно: указатель task
должен указывать на переменную типа k_task_t
, которая имеет жизненный цикл, превышающий жизненный цикл создаваемой задачи. Если указатель указывает на переменную с жизненным циклом, меньшим, чем у создаваемой задачи, например, на переменную, созданную на стеке функции, это может привести к непредсказуемым проблемам планирования системы.
имя
Указатель на строку имени задачи. Важно: как и для task
, строка, на которую указывает указатель name
, должна иметь жизненный цикл, превышающий жизненный цикл создаваемой задачи. Обычно можно передать указатель на константную строку.
входная_функция
Входная функция для выполнения задачи. После завершения создания задачи и перехода в состояние выполнения, entry
является входной точкой для выполнения задачи, где пользователи могут написать свою бизнес-логику.
аргумент
Параметр, передаваемый в функцию входа задачи.
приоритет Приоритет задачи. Чем меньше значение prio
, тем выше приоритет. Пользователи могут настроить максимальное значение приоритета задачи через TOS_CFG_TASK_PRIO_MAX
в файле tos_config.h
. В реализации ядра приоритет задачи idle
устанавливается как TOS_CFG_TASK_PRIO_MAX - 1
, этот приоритет может использоваться только задачей idle
. Таким образом, для задачи, созданной пользователем, разумный диапазон значений приоритета должен быть [0, TOS_CFG_TASK_PRIO_MAX - 2]
. В дополнение, значение TOS_CFG_TASK_PRIO_MAX
должно быть не меньше 8. Начальный адрес стека, используемого задачей во время выполнения. Внимание: как и для задачи, указатель должен ссылаться на область памяти, которая живёт дольше, чем жизненный цикл задачи. 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), то есть количество системных тиков / 10.
#define TOS_CFG_TASK_PRIO_MAX 10u
#define TOS_CFG_CPU_TICK_PER_SECOND 1000u
#include "tos.h" // Добавьте заголовочный файл с интерфейсами ядра TencentOS tiny
#include "mcu_init.h" // Включите заголовочный файл инициализации MCU, содержащий объявления функций инициализации
#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
``````c
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); // Выведите переданные параметры (массив)
}
``````markdown
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
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\n");
tos_task_delay(1000);
++i;
}
}```c
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
### 2.3 Коммуникация между задачами
#### 2.3.1 Мьютекс
##### Обзор
Мьютекс, также известный как мьютекс-блокировка, обычно используется для защиты совместного доступа к ресурсам, обеспечивая взаимоисключение.
```Мьютекс в любой момент времени находится либо в разблокированном, либо в заблокированном состоянии. Когда одна задача получает блокировку (мьютекс заблокирован), другие задачи, пытаясь получить ту же блокировку, будут неудачны или войдут в состояние ожидания. Когда задача освобождает блокировку (мьютекс разблокирован), она пробуждает задачу, ожидающую эту блокировку, и та получает блокировку.В многозадачной среде некоторые совместно используемые ресурсы не являются многопоточными, повторно используемыми. Для таких ресурсов, которые не должны быть одновременно доступны нескольким задачам (критические ресурсы), можно использовать мьютекс для защиты. Примеры программирования, показанные в последующих разделах, демонстрируют этот подход.
##### Описание API
##### Пример программирования
1. В файле `tos_config.h` настройте переключатель компонента мьютекса `TOS_CFG_MUTEX_EN`:
```c
#define TOS_CFG_MUTEX_EN 1u
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("Writing 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;
}```c
while (K_TRUE) {
// Before writing to the critical section, attempt to obtain the critical section lock
err = tos_mutex_pend(&critical_resource_locker);
if (err == K_ERR_NONE) {
// After successfully obtaining the lock, write the data to the critical section
write_critical_resource(salt);
// After writing the data, release the mutex
tos_mutex_post(&critical_resource_locker);
}
tos_task_delay(1000);
++salt;
}
}
``````markdown
##### Чтение критических ресурсов
``````c
static void read_critical_resource(void)
{
size_t i = 0;
// Чтение данных из критического ресурса
printf("чтение критического ресурса:\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,
"телевизор", 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();
}
чтение критического ресурса: 0 1 2 чтение критического ресурса: 0 1 2 чтение критического ресурса: 1 2 3 чтение критического ресурса: 1 2 3 чтение критического ресурса: 2 3 4 чтение критического ресурса: 2 3 4 чтение критического ресурса: 3 4 5 чтение критического ресурса: 3 4 5 чтение критического ресурса: 4 5 6 чтение критического ресурса: 4 5 6 чтение критического ресурса: 5 6 7 чтение критического ресурса: 5 6 7 чтение критического ресурса: 6 7 8 чтение критического ресурса: 6 7 8 чтение критического ресурса: 7 8 9 чтение критического ресурса: 7 8 9 [Пример кода](./code/2.3.1 mutex/main.c)
Сигнальные переменные — это механизм, используемый для синхронизации задач, обычно для доступа к ограниченным ресурсам несколькими задачами. В общем случае, семафор содержит целочисленное значение, которое указывает на количество доступных ресурсов. Когда количество доступных ресурсов в семафоре больше 0, задача успешно получает семафор, и количество доступных ресурсов уменьшается на 1. Когда количество доступных ресурсов равно 0, задача не может успешно получить семафор и либо получает ошибку, либо переходит в состояние ожидания. В этом режиме работы семафора, когда количество доступных ресурсов равно 1, его можно использовать для взаимоисключения доступа к ресурсу; или для решения задачи производителя-потребителя. Примеры программирования будут демонстрировать методы решения задачи производителя-потребителя.
tos_config.h
настройте переключатель компонента семафора TOS_CFG_SEM_EN
:#define TOS_CFG_SEM_EN 1u
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;
``````##### Результат выполнения
> produce item:
> 0
> consume item:
> 0
> produce item:
> 1
> produce item:
> 2
> consume item:
> 2
> produce item:
> 3
> produce item:
> 4
> consume item:
> 4
> produce item:
> 5
> consume item:
> 5
> produce item:
> 6
> consume item:
> 6
> produce item:
> 7
> consume item:
> 7
> produce item:
> 8
> consume item:
> 8
> produce item:
> 9
> consume item:
> 9
> produce item:
> 10
```[Пример кода](./code/2.3.2 semaphore/main.c)
#### 2.3.3 Событие
##### Описание
Событие предоставляет механизм для синхронизации и передачи информации между задачами. Обычно событие содержит флаг, каждый бит которого представляет собой отдельное "событие".
Задача может ждать одно или несколько "событий", другие задачи могут в определённых условиях активировать эти "события", тем самым пробуждая задачи, ожидающие эти "события". Это позволяет реализовать программную модель, похожую на сигналы.
##### Описание API
##### Пример кода
1. В файле `tos_config.h` настройте переключатель компонента событий `TOS_CFG_EVENT_EN`:
```c
#define TOS_CFG_EVENT_EN 1u
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;
}```c
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");
}
}
}
``````markdown
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");
}
}
}
}```c
void entry_task_trigger(void *arg)
{
int i = 1;
}
```c
while (K_TRUE) {
if (i == 2) {
printf("entry_task_trigger:\n");
printf("eeny будет отправлен\n");
// Отправка события eeny, task_listener2 будет активировано
tos_event_post(&event, event_eeny);
}
if (i == 3) {
printf("entry_task_trigger:\n");
printf("meeny будет отправлен\n");
// Отправка события meeny, task_listener2 будет активировано
tos_event_post(&event, event_meeny);
}
if (i == 4) {
printf("entry_task_trigger:\n");
printf("miny будет отправлен\n");
// Отправка события miny, task_listener2 будет активировано
tos_event_post(&event, event_miny);
}
if (i == 5) {
printf("entry_task_trigger:\n");
printf("moe будет отправлен\n");
// Отправка события moe, task_listener2 будет активировано
tos_event_post(&event, event_moe);
}
if (i == 6) {
printf("entry_task_trigger:\n");
printf("все события будут отправлены\n");
// Отправка четырех событий одновременно, так как приоритет task_listener1 выше, чем у task_listener2, task_listener1 будет активировано
tos_event_post(&event, event_eeny | event_meeny | event_miny | event_moe);
}
tos_task_delay(1000);
++i;
}
}
```c
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 будет здесь
> entry_task_listener2:
> eeny здесь
> entry_task_trigger:
> meeny будет здесь
> entry_task_listener2:
> meeny здесь
> entry_task_trigger:
> miny будет здесь
> entry_task_listener2:
> miny здесь
> entry_task_trigger:
> moe будет здесь
> entry_task_listener2:
> moe здесь
> entry_task_trigger:
> all будут здесь
> entry_task_listener1:
> eeny, meeny, miny, moe, все здесь
[Пример кода](./code/2.3.3 event/main.c)
#### 2.3.4 Завершение задач
##### Описание
Механизм завершения задач представляет собой простую систему для синхронизации задач, используемую для передачи информации о том, был ли определенный процесс завершен.
##### Описание API
##### Пример кода
1. В файле конфигурации `tos_config.h` включите компонент завершения задач `TOS_CFG_COMPLETION_EN`:
```c
#define TOS_CFG_COMPLETION_EN 1u
main.c
:/*
В этом примере создаются две задачи: одна задача `task_wait` ожидает завершения задачи, а другая задача отвечает за активацию завершения задачи (делает задачу завершенной).
*/
#include "tos.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;
``````c
void entry_task_wait(void *arg)
{
printf("ожидание: я не могу продолжить, пока кто-то не активирует завершение задачи\n");
tos_completion_pend(&completion);
printf("ожидание: кто-то активировал завершение задачи, поэтому я здесь\n");
}
``````c
void entry_task_trigger(void *arg)
{
printf("триггер: я тот, кто завершает, любой, кто ждет завершения, не сможет продолжить, пока я не выполню триггер\n");
tos_completion_post(&completion);
printf("триггер: я завершил завершение\n");
}
``````c
int main(void)
{
board_init();
tos_knl_init();
tos_completion_create(&completion);
(void)tos_task_create(&task_wait, "ожидание", entry_task_wait, NULL,
3, stack_task_wait, STK_SIZE_TASK_WAIT, 0);
(void)tos_task_create(&task_trigger, "триггер", entry_task_trigger, NULL,
4, stack_task_trigger, STK_SIZE_TASK_TRIGGER, 0);
tos_knl_start();
}
ожидание: Я не смогу продолжить, пока кто-то не выполнит триггер (завершит) триггер: Я тот, кто завершает, любой, кто ждет завершения, не сможет продолжить, пока я не выполню триггер ожидание: кто-то завершил, так что я здесь триггер: Я завершил завершение
[Пример кода](./code/2.3.4 completion/main.c)
Счетный блок предоставляет концепцию синхронизации "счетной информации". При создании счетного блока указывается счетное значение. Каждый раз, когда задача выполняет tos_countdownlatch_post
, счетное значение блока уменьшается на единицу. Задачи, ожидающие этот счетный блок, будут разблокированы только тогда, когда счетное значение станет нулевым.
tos_config.h
настройте переключатель компонента счетного блока TOS_CFG_COUNTDOWNLATCH_EN
:#define TOS_CFG_COUNTDOWNLATCH_EN 1u
main.c
:/*
Предположим, что есть такая бизнес-ситуация: есть три воина, которые должны найти три части оружия. Только когда все три воина найдут свои части, маг сможет собрать оружие. Давайте посмотрим, как использовать счетный блок для реализации этой модели.
*/
``````c
#include "tos.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_воин_0(void *arg)
{
printf("воин 0: Я выполнил свою работу\n");
tos_countdownlatch_post(&countdownlatch);
}
void entry_task_воин_1(void *arg)
{
printf("воин 1: Я выполнил свою работу\n");
tos_countdownlatch_post(&countdownlatch);
}
void entry_task_воин_2(void *arg)
{
printf("воин 2: Я выполнил свою работу\n");
tos_countdownlatch_post(&countdownlatch);
}
void entry_task_волшебник(void *arg)
{
printf("волшебник: Я поставлю трех воинов на поиски фрагментов\n");
tos_countdownlatch_create(&countdownlatch, 3);
(void)tos_task_create(&task_воин_0, "воин_0", entry_task_воин_0, NULL,
4, stack_task_воин_0, STK_SIZE_TASK_ВОЛШЕБНИК, 0);
(void)tos_task_create(&task_воин_1, "воин_1", entry_task_воин_1, NULL,
4, stack_task_воин_1, STK_SIZE_TASK_ВОЛШЕБНИК, 0);
(void)tos_task_create(&task_воин_2, "воин_2", entry_task_воин_2, NULL,
4, stack_task_воин_2, STK_SIZE_TASK_ВОЛШЕБНИК, 0);
printf("волшебник: Теперь воины в пути, я буду ждать здесь, пока они не выполнят свою работу\n");
tos_countdownlatch_pend(&countdownlatch);
printf("волшебник: Воины выполнили свою работу, давайте создадим оружие\n");
}
int main(void)
{
board_init();
tos_knl_init();
(void)tos_task_create(&task_волшебник, "волшебник", entry_task_волшебник, NULL,
3, stack_task_волшебник, STK_SIZE_TASK_ВОЛШЕБНИК, 0);
tos_knl_start();
}
```##### Результат выполнения
> Волшебник: Я поставлю трёх воинов на поиск фрагментов.
> Волшебник: Теперь воины в пути, я буду ждать здесь, пока они не выполнят свою работу.
> Воин 0: Я выполнил свою работу.
> Воин 1: Я выполнил свою работу.
> Воин 2: Я выполнил свою работу.
> Волшебник: Воины выполнили свою работу, давайте создадим оружие.
[Пример кода](./code/2.3.5 countdownlatch/main.c)
#### 2.3.6 Сообщения
##### Описание
Сообщения представляют собой механизм для передачи указателей данных между задачами. Сообщение — это указатель, который передаётся между задачами. Как интерпретировать и использовать сообщение, определяется задачами, которые его передают и получают. Сам механизм сообщений не задаёт никаких правил или ограничений на содержимое сообщения, он просто передаёт указатель данных.
##### Описание API
##### Пример программирования
1. В файле `tos_config.h` настройте переключатель компонента очереди сообщений `TOS_CFG_MESSAGE_QUEUE_EN`:
```c
#define TOS_CFG_MESSAGE_QUEUE_EN 1u
main.c
:/*
Здесь показано, как использовать очередь сообщений для передачи сообщений между задачами sender и receiver (один указатель, в данном случае указатель указывает на строку).
*/
#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)
```#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("высший приоритет: входящее сообщение[%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("низший приоритет: входящее сообщение[%s]\n", (char *)msg_received);
}
}
}```c
void entry_task_sender(void *arg)
{
int i = 1;
char *msg_to_one_receiver = "сообщение для одного получателя (с наивысшим приоритетом)";
char *msg_to_all_receiver = "сообщение для всех получателей";
}
``````markdown
while (K_TRUE) {
if (i == 2) {
printf("отправитель: отправить сообщение одному получателю, и это должно быть сообщение с наивысшим приоритетом\n");
tos_msg_q_post(&msg_q, msg_to_one_receiver);
}
if (i == 3) {
printf("отправитель: отправить сообщение всем получателям\n");
tos_msg_q_post_all(&msg_q, msg_to_all_receiver);
}
if (i == 4) {
printf("отправитель: отправить сообщение одному получателю, и это должно быть сообщение с наивысшим приоритетом\n");
tos_msg_q_post(&msg_q, msg_to_one_receiver);
}
if (i == 5) {
printf("отправитель: отправить сообщение всем получателям\n");
tos_msg_q_post_all(&msg_q, msg_to_all_receiver);
}
tos_task_delay(1000);
++i;
}
}
``````c
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: отправка сообщения одному получателю, и он должен иметь наивысший приоритет
> higher: входящее сообщение[сообщение для одного получателя (с наивысшим приоритетом)]
> sender: отправка сообщения всем получателям
> higher: входящее сообщение[сообщение для всех получателей]
> lower: входящее сообщение[сообщение для всех получателей]
> sender: отправка сообщения одному получателю, и он должен иметь наивысший приоритет
> higher: входящее сообщение[сообщение для одного получателя (с наивысшим приоритетом)]
> sender: отправка сообщения всем получателям
> higher: входящее сообщение[сообщение для всех получателей]
> lower: входящее сообщение[сообщение для всех получателей]
[Пример кода](./code/2.3.6 message queue/main.c)
#### 2.3.7 Почтовая очередь
##### Описание
Почтовая очередь передает блоки памяти, в то время как очередь сообщений передает указатели.
##### Описание API
##### Пример кода
1. В файле `tos_config.h` настройте переключатель компонента почтовой очереди `TOS_CFG_MAIL_QUEUE_EN`:
```c
#define TOS_CFG_MAIL_QUEUE_EN 1u
main.c
:/*
Здесь показано, как использовать почтовую очередь для передачи почты (в данном примере почта, то есть данные, передаваемые через очередь, представляет собой структуру типа `mail_t`, из этого примера видно, что почтовая очередь позволяет передавать более сложные блоки данных по сравнению с очередью сообщений).
*/
#include "tos.h"
#include "mcu_init.h"
``````markdown
#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)
``````markdown
#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("высший приоритет: входящее сообщение[%s], нагрузка[%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("низший приоритет: входящее сообщение[%s], нагрузка[%d]\n", mail.message, mail.payload);
}
}
}
``````c
void entry_task_sender(void *arg)
{
int i = 1;
mail_t mail;
}
``````markdown
while (K_TRUE) {
if (i == 2) {
printf("sender: отправить письмо одному получателю, и оно должно быть с наивысшим приоритетом\n");
mail.message = "первое сообщение";
mail.payload = 1;
tos_mail_q_post(&mail_q, &mail, sizeof(mail_t));
}
if (i == 3) {
printf("sender: отправить сообщение всем получателям\n");
mail.message = "второе сообщение";
mail.payload = 2;
tos_mail_q_post_all(&mail_q, &mail, sizeof(mail_t));
}
if (i == 4) {
printf("sender: отправить сообщение одному получателю, и оно должно быть с наивысшим приоритетом\n");
mail.message = "третье сообщение";
mail.payload = 3;
tos_mail_q_post(&mail_q, &mail, sizeof(mail_t));
}
if (i == 5) {
printf("sender: отправить сообщение всем получателям\n");
mail.message = "четвертое сообщение";
mail.payload = 4;
tos_mail_q_post_all(&mail_q, &mail, sizeof(mail_t));
}
tos_task_delay(1000);
++i;
}
}
``````c
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();
}
```##### Результат выполнения
> отправитель: отправка письма одному получателю, и это должно быть письмо с наивысшим приоритетом
> выше: входящее сообщение[первое сообщение], полезная нагрузка[1]
> отправитель: отправка сообщения всем получателям
> выше: входящее сообщение[второе сообщение], полезная нагрузка[2]
> ниже: входящее сообщение[второе сообщение], полезная нагрузка[2]
> отправитель: отправка сообщения одному получателю, и это должно быть письмо с наивысшим приоритетом
> выше: входящее сообщение[третье сообщение], полезная нагрузка[3]
> отправитель: отправка сообщения всем получателям
> выше: входящее сообщение[четвертое сообщение], полезная нагрузка[4]
> ниже: входящее сообщение[четвертое сообщение], полезная нагрузка[4]
[Пример кода](./code/2.3.7 mail queue/main.c)
#### 2.3.8 Очередь сообщений с приоритетом
##### Описание
Очередь сообщений с приоритетом добавляет концепцию приоритета к сообщениям, таким образом, сообщения с более высоким приоритетом будут получены другими задачами быстрее, чем сообщения с более низким приоритетом (в основе своей, нижележащая структура данных очереди сообщений является кольцевой очередью, а для очереди сообщений с приоритетом — это очередь с приоритетом).
##### Описание API
##### Пример кода
1. В файле `tos_config.h` настройте переключатель компонента очереди сообщений с приоритетом `TOS_CFG_PRIORITY_MESSAGE_QUEUE_EN`:```c
#define TOS_CFG_PRIORITY_MESSAGE_QUEUE_EN 1u
main.c
:#include "tos.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: сообщение входящее[%s]\n", (char *)msg_received);
}
}
}
void entry_task_sender(void *arg)
{
char *msg_prio_0 = "сообщение с приоритетом 0";
char *msg_prio_1 = "сообщение с приоритетом 1";
char *msg_prio_2 = "сообщение с приоритетом 2";
printf("sender: отправка сообщения с приоритетом 2\n");
tos_prio_msg_q_post(&prio_msg_q, msg_prio_2, 2);
printf("sender: отправка сообщения с приоритетом 1\n");
tos_prio_msg_q_post(&prio_msg_q, msg_prio_1, 1);
printf("sender: отправка сообщения с приоритетом 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: отправка сообщения с приоритетом 2 sender: отправка сообщения с приоритетом 1 sender: отправка сообщения с приоритетом 0 receiver: сообщение входящее[сообщение с приоритетом 0] receiver: сообщение входящее[сообщение с приоритетом 1] receiver: сообщение входящее[сообщение с приоритетом 2]Пример кода
Параметризованная очередь сообщений представляет собой очередь сообщений, где каждое сообщение имеет приоритет. Высокоприоритетные сообщения будут обрабатываться раньше, чем низкоприоритетные сообщения (в основе своей, очередь сообщений использует кольцевую очередь, а параметризованная очередь сообщений использует приоритетную очередь).
tos_config.h
настройте параметризованную очередь сообщений компонент TOS_CFG_PRIORITY_MAIL_QUEUE_EN
: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: сообщение входящее[%s]\n", (char *)msg_received);
}
}
}
void entry_task_sender(void *arg)
{
char *msg_prio_0 = "сообщение с приоритетом 0";
char *msg_prio_1 = "сообщение с приоритетом 1";
char *msg_prio_2 = "сообщение с приоритетом 2";
printf("sender: отправка сообщения с приоритетом 2\n");
tos_prio_msg_q_post(&prio_msg_q, msg_prio_2, 2);
printf("sender: отправка сообщения с приоритетом 1\n");
tos_prio_msg_q_post(&prio_msg_q, msg_prio_1, 1);
printf("sender: отправка сообщения с приоритетом 0\n");
tos_prio_msg_q_post(&prio_msg_q, msg_prio_0, 0);
}
``````c
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();
}
`#define TOS_CFG_PRIORITY_MAIL_QUEUE_EN 1u`
```1. Пример кода main.c:
```c
/*
В этом примере демонстрируется использование очереди сообщений с приоритетом. Из логики задачи sender видно, что последовательно отправляются три сообщения, приоритет которых по времени составляет 2, 1 и 0 (чем больше значение, тем ниже приоритет). Если бы это была традиционная очередь сообщений, получатель получил бы сообщения в порядке приоритета 2, 1 и 0. Однако это очередь сообщений с приоритетом, поэтому получатель получает эти три сообщения в порядке их приоритета, то есть последовательно получает сообщения с приоритетом 0, 1 и 2.
*/
#include "tos.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("получатель: входящее сообщение[%s], нагрузка[%d]\n", mail.message, mail.payload);
}
}
}
void entry_task_sender(void *arg)
{
mail_t mail_0, mail_1, mail_2;
printf("отправитель: отправка сообщения с приоритетом 2\n");
mail_2.message = "приоритет 2";
mail_2.payload = 2;
tos_prio_mail_q_post(&prio_mail_q, &mail_2, sizeof(mail_t), 2);
}
``` printf("отправитель: отправка сообщения с приоритетом 1\n");
mail_1.message = "приоритет 1";
mail_1.payload = 1;
tos_prio_mail_q_post_all(&prio_mail_q, &mail_1, sizeof(mail_t), 1);
printf("отправитель: отправка сообщения с приоритетом 0\n");
mail_0.message = "приоритет 0";
mail_0.payload = 0;
tos_prio_mail_q_post(&prio_mail_q, &mail_0, sizeof(mail_t), 0);
}
``````c
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();
}
```##### Результат выполнения
> отправитель: отправляет письмо с приоритетом 2
> отправитель: отправляет письмо с приоритетом 1
> отправитель: отправляет письмо с приоритетом 0
> получатель: входящее сообщение[приоритет 0], полезная нагрузка[0]
> получатель: входящее сообщение[приоритет 1], полезная нагрузка[1]
> получатель: входящее сообщение[приоритет 2], полезная нагрузка[2]
[Пример кода](./code/2.3.9 priority mail queue/main.c)
### 2.4 Управление памятью
#### 2.4.1 Динамическое управление памятью
##### Описание
Модуль динамического управления памятью предоставляет механизмы для динамического управления системной памятью, позволяя пользователям динамически выделять и освобождать блоки памяти произвольного размера.
##### Описание API
##### Примеры программирования
1. В файле `tos_config.h` настройте переключатель компонента динамического управления памятью `TOS_CFG_MMHEAP_EN`:```c
#define TOS_CFG_MMHEAP_EN 1u
tos_config.h
настройте размер памяти пула:#define TOS_CFG_MMHEAP_POOL_SIZE 0x2000
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("выделено: %x\n", (cpu_addr_t)p);
}
} else if (i == 2) {
if (p) {
printf("освобождено: %x\n", p);
tos_mmheap_free(p);
}
} else if (i == 3) {
p = tos_mmheap_alloc(0x30);
if (p) {
printf("выделено: %x\n", (cpu_addr_t)p);
}
} else if (i == 4) {
p_aligned = tos_mmheap_aligned_alloc(0x50, 16);
if (p_aligned) {
printf("выделено с выравниванием: %x\n", (cpu_addr_t)p_aligned);
if ((cpu_addr_t)p_aligned % 16 == 0) {
printf("%x выровнено по 16\n", (cpu_addr_t)p_aligned);
} else {
printf("не должно произойти\n");
}
}
} else if (i == 5) {
p = tos_mmheap_realloc(p, 0x40);
if (p) {
printf("переустановлено: %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;
}
}
```c
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();
}
> free: 20000c8c
> alloc: 20000c8c
> aligned alloc: 20000cc0
> 20000cc0 is 16 aligned
> realloc: 20000d14
[Пример кода](./code/2.4.1 mmheap/main.c)
#### 2.4.2 Статическая память
##### Описание
Модуль управления статической памятью предоставляет механизмы для управления статическими блоками памяти. Поддерживает запрос на выделение и освобождение фиксированного размера памяти.
##### Описание API
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);
Подробное описание параметров:
mbp
Объект пула статической памяти.
pool_start
Начальный адрес пула памяти.
blk_num
Количество блоков памяти в пуле.
blk_size
Размер каждого блока памяти.
tos_config.h
настройте компонент управления статической памятью TOS_CFG_MMBLK_EN
:#define TOS_CFG_MMBLK_EN 1u
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 имеет %d блоков, размер каждого блока 0x%x\n", 5, 0x20);
// Получение всех блоков из пула памяти
for (; i < MMBLK_BLK_NUM; ++i) {
err = tos_mmblk_alloc(&mmblk_pool, &p[i]);
if (err == K_ERR_NONE) {
printf("%d блок выделен: 0x%x\n", i, p[i]);
} else {
printf("не должно произойти\n");
}
}
} // Все доступные блоки уже выделены, следующая попытка выделения вернет ошибку K_ERR_MMBLK_POOL_EMPTY
err = tos_mmblk_alloc(&mmblk_pool, &p_dummy);
if (err == K_ERR_MMBLK_POOL_EMPTY) {
printf("блоки исчерпаны, все блоки выделены\n");
} else {
printf("не должно произойти\n");
}
}
``````md
// Все ранее выделенные блоки возвращаются в пул
for (i = 0; i < MMBLK_BLK_NUM; ++i) {
err = tos_mmblk_free(&mmblk_pool, p[i]);
if (err != K_ERR_NONE) {
printf("не должно произойти\n");
}
}
// Все блоки уже возвращены в пул, следующий вызов tos_mmblk_free вернёт ошибку K_ERR_MMBLK_POOL_FULL
err = tos_mmblk_free(&mmblk_pool, p[0]);
if (err == K_ERR_MMBLK_POOL_FULL) {
printf("пул заполнен\n");
} else {
printf("не должно произойти\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 имеет 5 блоков, размер каждого блока 0x20 0 блок выделен: 0x20000974 1 блок выделен: 0x20000994 2 блок выделен: 0x200009b4 3 блок выделен: 0x200009d4 4 блок выделен: 0x200009f4 блоки исчерпаны, все блоки выделены пул заполнен
[Пример кода](./code/2.4.2 mmblk/main.c)
Управление временем предоставляет набор функций, связанных с временем, включая получение/установку системного счётчика сystick, преобразование между сystick и миллисекундами, а также функции задержки задач на миллисекунды или по часам.#### API объяснение
#define TOS_CFG_CPU_TICK_PER_SECOND 1000u
#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 тиков, то 1000 тиков равно 1000 миллисекундам.
systick = tos_millisec2tick(2000);
printf("%d миллисекунд равно %lld тикам\n", 2000, systick);
ms = tos_tick2millisec(1000);
printf("%lld тиков равно %d миллисекундам\n", (k_tick_t)1000, ms);
}
2000 миллисекунд равны 2000 тикам 1000 тиков равны 1000 миллисекундам перед сном, systick равен 7 после сна 2000 мс, systick равен 2009 миллисекундный сон составляет примерно: 2002
[Пример кода](./code/2.5 time/main.c)
Программные таймеры предоставляют механизм таймера на уровне программного обеспечения, в отличие от аппаратных таймеров. Пользователи могут создавать множество программных таймеров и устанавливать условия истечения таймера и вызовы обратного вызова. Когда программный таймер истекает, выполняется зарегестрированный обратный вызов.В общем случае, пользовательский обратный вызов программного таймера может содержать задержки или синхронные ожидания, или сам обратный вызов может быть сложным и длительным по времени. Поэтому система была спроектирована таким образом, чтобы управлять логикой программных таймеров в виде задачи, в которой сканируются таймеры на истечение и выполняются обратные вызовы. Однако, как известно, создание задачи требует использования системных ресурсов (стек задачи, память для хранения задачи и т.д.). Если пользовательский обратный вызов программного таймера не содержит задержек или синхронных ожиданий, или сам обратный вызов выполняется очень быстро, то логика управления программными таймерами не требует реализации в виде задачи, а может быть реализована в виде функции, вызываемой в прерывании таймера. В этом случае можно сэкономить ресурсы, используемые для создания задачи.По умолчанию система реализована таким образом, что логика управления таймерами реализована в виде задачи. Когда пользовательские обратные вызовы таймеров выполняются очень быстро, пользователи могут настроить логику управления программными таймерами в виде функции для экономии ресурсов памяти. Это можно сделать, открытием опции TOS_CFG_TIMER_AS_PROC в файле конфигурации tos_config.h:
#define TOS_CFG_TIMER_AS_PROC 1u
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
. Если opt
установлен в TOS_OPT_TIMER_ONESHOT
, это означает, что tmr
является одноразовым таймером, и его жизненный цикл заканчивается после истечения задержки и выполнения функции обратного вызова. Если opt
установлен в TOS_OPT_TIMER_PERIODIC
, это означает, что tmr
является периодическим таймером, и после истечения задержки и выполнения функции обратного вызова, таймер будет заново добавлен в очередь таймеров с периодом, указанным в параметре period
, и будет запущен следующий цикл.#### Пример программирования
#define TOS_CFG_TIMER_EN 1000u
#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("Это одноразовый таймер, обратный вызов: %lld\n", tos_systick_get());
}
void periodic_timer_cb(void *arg)
{
printf("Это периодический таймер, обратный вызов: %lld\n", tos_systick_get());
}
void entry_task_demo(void *arg)
{
k_timer_t oneshot_tmr;
k_timer_t periodic_tmr;
// Это одноразовый таймер, который истекает через 3000 тиков
tos_timer_create(&oneshot_tmr, 3000, 0, oneshot_timer_cb, K_NULL, TOS_OPT_TIMER_ONESHOT);
// Это периодический таймер, который истекает через 2000 тиков, а затем выполняет обратный вызов каждые 3000 тиков
tos_timer_create(&periodic_tmr, 2000, 3000, periodic_timer_cb, K_NULL, TOS_OPT_TIMER_PERIODIC);
printf("Текущий systick: %lld\n", tos_systick_get());
tos_timer_start(&oneshot_tmr);
tos_timer_start(&periodic_tmr);
}
это периодический таймер обратный вызов, текущий systick: 2001 это одноразовый таймер обратный вызов, текущий systick: 3001 это периодический таймер обратный вызов, текущий systick: 5001 это периодический таймер обратный вызов, текущий systick: 8001 это периодический таймер обратный вызов, текущий systick: 11001 это периодический таймер обратный вызов, текущий systick: 14001[Пример кода](./code/2.6 timer/main.c)
Ядро TencentOS tiny представляет собой прерывательное ядро, что означает, что если задача с наивысшим приоритетом не откажется от процессора (вызов tos_task_delay, tos_task_yield и т.д. для активного отказа или интерфейсы синхронизации задач, таких как pend), процессор будет продолжать работать только для этой задачи.
Представьте такую ситуацию: система содержит несколько задач с одинаковым приоритетом, и ни одна из этих задач не отказывается от процессора. В этом случае только первая задача, которая была запланирована, будет работать, так как первая запланированная задача не будет отказываться от процессора, а остальные задачи с таким же приоритетом не смогут его захватить. В такой ситуации остальные задачи могут оказаться в состоянии голода.
Механизм времени ожидания предоставляет стратегию использования процессора по времени, что позволяет решить проблему голода задач в такой ситуации.
#define TOS_CFG_ROUND_ROBIN_EN 1u
#include "tos.h"
#include "mcu_init.h"
``````c
#define STK_SIZE_TASK_DEMO 512
#define STK_SIZE_TASK_SAMPLE 512
/* В этом коде создаются две задачи с равным приоритетом (PRIO_TASK_DEMO): task_demo1 и task_demo2. Тела этих задач выполняют операции по непрерывному увеличению своих счетчиков (demo1_counter и demo2_counter) без передачи процессорного времени другим задачам. Время выполнения для каждой задачи задается параметрами timeslice_demo1 и timeslice_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
// приоритет для задачи sampling
#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: %llu\n", demo1_counter);
printf("demo2_counter: %llu\n", demo2_counter);
printf("demo2_counter / demo1_counter = %f\n",
(double)demo2_counter / demo1_counter);
printf("должен быть почти равен:\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
> должна быть почти равна:
> timeslice_demo2 / timeslice_demo1 = 2.000000
>
> demo1_counter: 1915369
> demo2_counter: 3720158
> demo2_counter / demo1_counter = 1.942267
> должна быть почти равна:
> timeslice_demo2 / timeslice_demo1 = 2.000000
>
> demo1_counter: 3774985
> demo2_counter: 7493508
> demo2_counter / demo1_counter = 1.985043
> должна быть почти равна:
> timeslice_demo2 / timeslice_demo1 = 2.000000
>
> demo1_counter: 5634601
> demo2_counter: 11266858
> demo2_counter / demo1_counter = 1.999584
> должна быть почти равна:
> timeslice_demo2 / timeslice_demo1 = 2.000000
>
> demo1_counter: 7546896
> demo2_counter: 14987015
> demo2_counter / demo1_counter = 1.985852
> должна быть почти равна:
> timeslice_demo2 / timeslice_demo1 = 2.000000
>
> demo1_counter: 9406512
> demo2_counter: 18759340
> demo2_counter / demo1_counter = 1.994293
> должна быть почти равна:
> timeslice_demo2 / timeslice_demo1 = 2.000000
>
> demo1_counter: 11266128
> demo2_counter: 22531664
> demo2_counter / demo1_counter = 1.999947
> должна быть почти равна:
> timeslice_demo2 / timeslice_demo1 = 2.000000
>
> demo1_counter: 13177398
> demo2_counter: 26251821
> demo2_counter / demo1_counter = 1.992185
> должна быть почти равна:
> timeslice_demo2 / timeslice_demo1 = 2.000000
>
> demo1_counter: 15037014
> demo2_counter: 30023632
> demo2_counter / demo1_counter = 1.996649
> должна быть почти равна:
> timeslice_demo2 / timeslice_demo1 = 2.000000
>
> demo1_counter: 16896630
> demo2_counter: 33795443
> demo2_counter / demo1_counter = 2.000129
> должна быть почти равна:
> timeslice_demo2 / timeslice_demo1 = 2.000000
>
> demo1_counter: 18807900
> demo2_counter: 37515600
> demo2_counter / demo1_counter = 1.994672
> должна быть почти равна:
> timeslice_demo2 / timeslice_demo1 = 2.000000
[Пример кода](./code/2.7 robin/main.c)### 2.8 Основные компоненты ядра
#### 2.8.1 Кольцевые очереди
##### Обзор
Кольцевая очередь представляет собой кольцевой буфер, поддерживающий операции FIFO (первым пришёл — первым ушёл). Это базовый компонент системы, который обычно используется как нижний уровень данных для реализации более сложных механизмов.
##### Описание API
##### Пример программирования
1. Написание примера кода main.c:
```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;
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
> кольцевая очередь заполнена: TRUE
> dequeue: 0 0 0
> dequeue: 1 1 1
> dequeue: 2 2 2
> dequeue: 3 3 3
> dequeue: 4 4 4
> кольцевая очередь пуста: TRUE
[Пример кода](./code/2.8.1 ring queue/main.c)
#### 2.8.2 Символьная очередь с FIFO
##### Описание
Символьная очередь с FIFO предоставляет реализацию кольцевой очереди, ориентированную на операции с символами. Это специальный случай кольцевой очереди, где элементами являются символы (одно байтовые значения). Внутренняя реализация символьной очереди FIFO основана на кольцевой очереди.
##### Описание API
##### Примеры программирования
1. Написание примера кода main.c:
```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_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_BUFFER_SIZE символов в FIFO, это будут 'a', 'b', 'c', 'd', 'e'
for (i = 0; i < FIFO_BUFFER_SIZE; ++i) {
printf("символ добавлен: %c\n", 'a' + i);
err = tos_chr_fifo_push(&fifo, 'a' + i);
if (err != K_ERR_NONE) {
printf("не должно произойти\n");
}
}
}
``` // FIFO может содержать до FIFO_BUFFER_SIZE символов, в предыдущем цикле мы добавили максимальное количество символов, следующий добавленный символ вернет K_ERR_FIFO_FULL (FIFO заполнен)
err = tos_chr_fifo_push(&fifo, 'z');
if (err == K_ERR_RING_Q_FULL) {
printf("FIFO заполнен: %s\n", tos_chr_fifo_is_full(&fifo) ? "ИСТИНА" : "ЛОЖЬ");
} else {
printf("не должно произойти\n");
}```c
// Извлекаем все символы из FIFO
for (i = 0; i < FIFO_BUFFER_SIZE; ++i) {
err = tos_chr_fifo_pop(&fifo, &data);
if (err == K_ERR_NONE) {
printf("%d извлечен: %c\n", i, data);
} else {
printf("не должно произойти\n");
}
}
// Дальнейшее извлечение символов вернет K_ERR_FIFO_EMPTY (FIFO пуст)
err = tos_chr_fifo_pop(&fifo, &data);
if (err == K_ERR_RING_Q_EMPTY) {
printf("FIFO пуст: %s\n", tos_chr_fifo_is_empty(&fifo) ? "TRUE" : "FALSE");
} else {
printf("не должно произойти\n");
}
}
``````markdown
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_BUFFER_SIZE, вставка будет успешной, и будет возвращена длина вставленного потока 5
count = tos_chr_fifo_push_stream(&fifo, &stream[0], FIFO_BUFFER_SIZE);
if (count != FIFO_BUFFER_SIZE) {
printf("не должно произойти\n");
}
// Продолжаем вставлять символьный поток (даже если длина потока составляет 1), поскольку FIFO заполнен, вставка не будет возможна, и будет возвращена длина 0 (вставка не удалась)
count = tos_chr_fifo_push_stream(&fifo, &stream_dummy[0], 1);
if (count == 0) {
printf("FIFO заполнен: %s\n", tos_chr_fifo_is_full(&fifo) ? "TRUE" : "FALSE");
} else {
printf("не должно произойти\n");
}
}
``` // Вытаскиваем все вставленные ранее символьные потоки, возвращается длина вставленного потока 5 (длина вытащенного потока)
count = tos_chr_fifo_pop_stream(&fifo, &stream_pop[0], FIFO_BUFFER_SIZE);
if (count == FIFO_BUFFER_SIZE) {
printf("поток вытаскивается:\n");
for (i = 0; i < FIFO_BUFFER_SIZE; ++i) {
printf("%c", stream_pop[i]);
}
printf("\n");
} else {
printf("не должно произойти\n");
} // Продолжаем вытаскивать, поскольку FIFO пуст, возвращается 0
count = tos_chr_fifo_pop_stream(&fifo, &stream_pop[0], 1);
if (count == 0) {
printf("FIFO пуст: %s\n", tos_chr_fifo_is_empty(&fifo) ? "Пусто" : "Не пусто");
} else {
printf("не должно произойти\n");
}
}
void entry_task_demo(void *arg)
{
// Создается FIFO, который может содержать до FIFO_BUFFER_SIZE символов
tos_chr_fifo_create(&fifo, &fifo_buffer[0], FIFO_BUFFER_SIZE);
printf("FIFO, работа с символами\n");
char_push();
printf("FIFO, работа со строками\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, работа с символами
> символ вставлен: a
> символ вставлен: b
> символ вставлен: c
> символ вставлен: d
> символ вставлен: e
> FIFO заполнен: TRUE
> 0 вытащен: a
> 1 вытащен: b
> 2 вытащен: c
> 3 вытащен: d
> 4 вытащен: e
> FIFO пуст: TRUE
> FIFO, работа со строками
> FIFO заполнен: TRUE
> строка вытаскивается:
> abcde
> FIFO пуст: TRUE
```[Пример кода](./code/2.8.2_char_fifo/main.c)
#### 2.8.3 Двоичные кучи
##### Описание
Этот компонент используется для внутренней реализации очереди с приоритетами и не рекомендуется для использования пользователями.
#### 2.8.4 Очередь с приоритетами
##### ОписаниеПредоставляет управление очередью на основе приоритетов. В кольцевой очереди элементы добавляются и извлекаются в порядке FIFO (first in, first out), в то время как в очереди с приоритетами элементы извлекаются в порядке убывания приоритета, то есть элементы с более высоким приоритетом извлекаются первыми.##### Описание API
##### Пример программирования
1. Написание примера кода main.c:
```c
/*
Этот пример демонстрирует правило извлечения элементов из очереди с приоритетами: элементы с приоритетами 5, 4, 3, 2, 1 добавляются в очередь по порядку, а извлекаются в порядке убывания приоритета, то есть сначала извлекаются элементы с приоритетом 1, затем 2, 3, 4 и 5.
*/
#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;
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");
}
}
}
```
##### Запуск примера> 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, priority: 1
> dequeue: 2 2 2, priority: 2
> dequeue: 3 3 3, priority: 3
> dequeue: 4 4 4, priority: 4
> dequeue: 5 5 5, priority: 5
> priority queue is empty: TRUE[Пример кода](./code/2.8.4 priority queue/main.c)
### 2.9 Управление энергопотреблением
#### 2.9.1 Экономия энергии
##### Описание
TencentOS tiny предоставляет многоуровневую систему управления низким энергопотреблением. На начальном уровне экономии энергии, когда система находится в состоянии "простоя", то есть при входе в задачу idle, система вызывает интерфейсы низкого энергопотребления процессора (в настоящее время поддерживаются архитектуры arm v7m) для вхождения в режим кратковременного сна.
##### Описание API
##### Пример программирования
Для начального уровня экономии энергии, пользователю не требуется писать дополнительный код, достаточно включить переключатель TOS_CFG_PWR_MGR_EN в файле конфигурации tos_config.h:
`#define TOS_CFG_PWR_MGR_EN 1u`
##### Результат запуска
#### 2.9.2 Безтикетный режим
##### Описание
Механизм безтикетного режима TencentOS tiny предлагает решение без периодического тика часов, которое позволяет остановить 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);
```
Этот интерфейс используется для установки будильников пробуждения для каждого уровня низкого энергопотребления. Когда ядро входит в безтикетный режим, systick останавливается, поэтому требуется другой таймер для пробуждения процессора из режима низкого энергопотребления.
В соответствии с спецификацией чипов ARM v7M, источники пробуждения для трех режимов следующие:
- sleep
После того как процессор переходит в режим sleep, его можно пробудить с помощью systick, аппаратного таймера или RTC-часов (wakeup/alarm прерывания).
- stop
После того как процессор переходит в режим stop, его можно пробудить с помощью RTC-часов (wakeup/alarm прерывания).
- standby
После того как процессор переходит в режим standby, его можно пробудить только с помощью alarm прерывания RTC-часов (или внешним сигналом на пине, что не относится к механизму ядра 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); /* в миллисекундах */
} 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);
```
Этот метод используется для инициализации устройства для пробуждения для определенного режима (на самом деле вызывает метод init, установленный в tos_tickless_wkup_alarm_install интерфейсе).
```c
k_err_t tos_pm_cpu_lpwr_mode_set(k_cpu_lpwr_mode_t cpu_lpwr_mode);
```
Этот метод используется для установки режима низкого энергопотребления процессора в режиме безтик (tickless).
##### Пример программирования
1. В файле `tos_config.h` настройте переключатель компонента низкого энергопотребления TOS_CFG_PWR_MGR_EN:
```c
#define TOS_CFG_PWR_MGR_EN 1u
```
2. В файле `tos_config.h` настройте переключатель компонента безтик TOS_CFG_TICKLESS_EN:
```c
#define TOS_CFG_TICKLESS_EN 1u
```
3. Напишите пример кода в файле `main.c`:
```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 тиков
tos_timer_create(&tmr, 0u, 6000u, timer_callback, K_NULL, TOS_OPT_TIMER_PERIODIC);
tos_timer_start(&tmr);
}
```
##### Описание задачи### Основная задача
В теле задачи каждые 3000 тиков выполняется определенная операция.
```c
while (K_TRUE) {
printf("entry task demo: %lld\n", tos_systick_get());
tos_task_delay(3000);
}
```
### Основная функция main
Основная функция `main` выполняет инициализацию доски, инициализацию ядра RTOS и создание задачи `demo1`.
```c
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();
}
```
### Реализация функции tos_bsp_tickless_setup
Функция `tos_bsp_tickless_setup` инициализирует режим безтиксов (tickless), что позволяет системе экономить энергию, отключая таймеры, когда они не используются.
```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
// Установка источника пробуждения в режиме сна
tos_tickless_wkup_alarm_install(TOS_LOW_POWER_MODE_SLEEP, &tickless_wkup_alarm_tim);
// Инициализация источника пробуждения
tos_tickless_wkup_alarm_init(TOS_LOW_POWER_MODE_SLEEP);
// Установка режима сна при включении режима безтиксов
tos_pm_cpu_lpwr_mode_set(TOS_LOW_POWER_MODE_SLEEP);
#endif
}
```
### Добавление отладочной информации в функцию knl_idle_entry
Для отслеживания работы системы в режиме безтиксов, добавлена отладочная информация в функцию `knl_idle_entry`.
```c
__STATIC__ void knl_idle_entry(void *arg)
{
arg = arg; // make compiler happy
while (K_TRUE) {
// Добавление отладочной информации в функцию 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
```
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )