Skip to content

Commit 9d723b7

Browse files
committed
upd
1 parent 0b2558e commit 9d723b7

File tree

13 files changed

+935
-12
lines changed

13 files changed

+935
-12
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
Лёгкий псевдо-многопоточный событийно-ориентированный фреймворк для Arduino
1313
- Более удобная организация программы
1414
- Разделение на виртуальные потоки
15+
- Полноценные потоки с параллельным выполнением и асинхронным ожиданием
1516
- Статическое и динамическое создание задач
1617
- Вызов задач с заданным периодом
1718
- Отправка событий и данных между задачами
1819
- Общее выполнение в одном потоке без нужды в мьютексах
19-
- Удобный API для создания своих задач и интеграции с другими классами
20+
- Удобный ООП API для создания своих задач и интеграции с другими классами
2021
- Легко отвязывается от Arduino ядра (заменить две функции)
2122
- Без зависимостей от других библиотек
2223
- Лёгкий вес: ядро занимает 1 кБ флешки и 30 Б оперативки (AVR)
@@ -68,6 +69,7 @@ void loop() {
6869

6970
## Версии
7071
- v1.0
72+
- v1.1.0 - добавлены потоки с параллельным выполнением и асинхронным ожиданием
7173

7274
<a id="install"></a>
7375
## Установка

docs/0.main.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,31 @@ LP_TICKER_(id, func)
3636
// создать статический таймер
3737
LP_TIMER(ms, func)
3838
LP_TIMER_(id, ms, func)
39+
40+
// создать статический поток
41+
LP_THREAD(body)
42+
LP_THREAD_(id, body)
43+
LP_THREAD_DATA(T, data, data_arg, body)
44+
LP_THREAD_DATA_(id, T, data, data_arg, body)
45+
46+
// начало и конец потока для создания вручную
47+
LP_THREAD_BEGIN()
48+
LP_THREAD_END()
49+
50+
// асинхронно ждать события в потоке
51+
LP_WAIT_EVENT(cond)
52+
53+
// асинхронно ждать условия в потоке
54+
LP_WAIT(cond)
55+
56+
// асинхронно ждать время в мс в потоке
57+
LP_SLEEP(ms)
58+
59+
// освободить семафор
60+
LP_SEM_SIGNAL(sem)
61+
62+
// ждать семафор
63+
LP_SEM_WAIT(sem)
3964
```
4065
4166
### `Looper`
@@ -330,6 +355,29 @@ LoopListenerData(hash_t id, T* data, DataCallback callback);
330355
// подключить новые данные
331356
void setData(T* data);
332357

358+
// получить данные
359+
T* getData();
360+
```
361+
362+
### LoopThread
363+
Тикер, наследует `LoopTicker`
364+
```cpp
365+
LoopThread(TaskCallback callback, bool states = true, bool events = true);
366+
LoopThread(const char* id, TaskCallback callback, bool states = true, bool events = true);
367+
LoopThread(hash_t id, TaskCallback callback, bool states = true, bool events = true);
368+
```
369+
370+
### LoopThreadData<T>
371+
Поток с данными
372+
373+
```cpp
374+
LoopThreadData(T* data, DataCallback callback);
375+
LoopThreadData(const char* id, T* data, DataCallback callback);
376+
LoopThreadData(hash_t id, T* data, DataCallback callback);
377+
378+
// подключить новые данные
379+
void setData(T* data);
380+
333381
// получить данные
334382
T* getData();
335383
```

docs/1.start.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ void loop() {
3737
- `LoopTicker` - задача-тикер, обработчик вызывается постоянно в loop
3838
- `LoopTimer` - задача-таймер, обработчик вызывается с заданным периодом из loop
3939
- `LoopListener` - задача-обработчик событий, обработчик вызывается только при получении события
40+
- `LoopThread` - задача-поток с асинхронными задержками и ожиданиями
4041
- Текущая задача - задача, в обработчике которой сейчас выполняется код программы
4142
4243
### Обработчик задачи
@@ -136,12 +137,12 @@ LP_TIMER_(IDs::timer0, 500, []() {
136137
- `tState::Event` - вызывается при поступлении события
137138
138139
Стандартные настройки для всех типов задач:
139-
| | `LoopTicker` | `LoopTimer` | `LoopListener` |
140-
| --------------- | :----------: | :---------: | :------------: |
141-
| `tState::Setup` | ✔ | | |
142-
| `tState::Loop` | ✔ | ✔ | |
143-
| `tState::Exit` | ✔ | | |
144-
| `tState::Event` | ✔ | | ✔ |
140+
| | `LoopTicker` | `LoopThread` | `LoopTimer` | `LoopListener` |
141+
| --------------- | :----------: | :----------: | :---------: | :------------: |
142+
| `tState::Setup` | ✔ | ✔ | | |
143+
| `tState::Loop` | ✔ | ✔ | ✔ | |
144+
| `tState::Exit` | ✔ | ✔ | | |
145+
| `tState::Event` | ✔ | ✔ | | ✔ |
145146
146147
> Иными словами: тикер вызывается со всеми статусами, таймер только с `Loop`, а обработчик событий - только с событиями
147148
@@ -200,16 +201,19 @@ LP_TIMER(1000, []() {
200201
```
201202
202203
### LoopListener
203-
Обработчик событий по умолчанию реагирует только на события
204+
Обработчик событий по умолчанию реагирует только на события:
204205
205206
```cpp
206207
LP_LISTENER_("lisn", []() {
207208
Serial.println("EVENT!");
208209
});
209210
```
210211

212+
### LoopThread
213+
Рассмотрены отдельно в следующей главе.
214+
211215
### Взаимодействие
212-
Задачи могут взаимодействовать между собой напрямую или по ID, также это может делать и основная программа. Например можно получить задачу и отключить её
216+
Задачи могут взаимодействовать между собой напрямую или по ID, также это может делать и основная программа. Например можно получить задачу и отключить её:
213217

214218
```cpp
215219
// через getTask
@@ -231,7 +235,7 @@ LP_TIMER_("tmr", 100, []() {
231235
});
232236
```
233237
234-
### Многофайловый проект
238+
## Многофайловый проект
235239
Глобальный диспетчер задач позволяет создавать задачи в отдельных исполняемых файлах и не тащить их в main, но они всё равно будут вызываться из общего loop в main. Например
236240
237241
```cpp

docs/2.threads.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
## LoopThread
2+
Внутри этой задачи-потока можно использовать "асинхронные" задержки и ожидания условий и событий, почти как в RTOS системах, что позволяет задачам выполняться по-настоящему одновременно и параллельно. Потоки реализованы на переходах к метке, без сохранения стека функции как в RTOS, поэтому не используют дополнительной памяти, но имеют ряд ограничений и особенностей. Создать поток можно вручную как тикер `LoopTicker`, но нужно дополнительно вызвать `LP_THREAD_BEGIN()` в начале и `LP_THREAD_END()` в конце обработчика:
3+
4+
```cpp
5+
LoopThread th_manual([]() {
6+
LP_THREAD_BEGIN();
7+
8+
// код
9+
10+
LP_THREAD_END();
11+
});
12+
```
13+
14+
Либо использовать макрос, который сам создаст имя для функции и обернёт код в begin-end:
15+
16+
```cpp
17+
LP_THREAD({
18+
// код
19+
});
20+
21+
LP_THREAD_("thread_id", {
22+
// код
23+
});
24+
```
25+
26+
> Внутри обработчика точно так же можно обращаться к задаче через `thisTask` и делать всё то же самое, что с `LoopTicker` (остановить, исключить из loop, отправить-поймать событие и так далее).
27+
28+
### Ожидание времени
29+
Поток может асинхронно ждать указанное время, это ожидание **не блокирует выполнение программы**:
30+
31+
```cpp
32+
LP_THREAD({
33+
LP_SLEEP(500);
34+
Serial.println("test!");
35+
});
36+
```
37+
38+
Например вот так выглядит классический blink:
39+
40+
```cpp
41+
LP_THREAD({
42+
digitalWrite(LED_BUILTIN, 1);
43+
LP_SLEEP(500);
44+
digitalWrite(LED_BUILTIN, 0);
45+
LP_SLEEP(500);
46+
});
47+
```
48+
49+
Поток также позволяет обернуть часть кода в бесконечный цикл, чтобы организовать setup-loop как это обычно делается в таких случаях:
50+
51+
```cpp
52+
LP_THREAD({
53+
pinMode(LED_BUILTIN, OUTPUT); // выполнится один раз
54+
55+
while (true) {
56+
digitalWrite(LED_BUILTIN, 1);
57+
LP_SLEEP(500);
58+
digitalWrite(LED_BUILTIN, 0);
59+
LP_SLEEP(500);
60+
}
61+
});
62+
```
63+
64+
### Ожидание условия
65+
Поток также может асинхронно ждать наступления указанного условия, например:
66+
67+
```cpp
68+
bool flag = 0;
69+
70+
LP_THREAD({
71+
LP_WAIT(flag); // ждём пока флаг станет 1
72+
73+
Serial.println("flag!");
74+
flag = 0;
75+
});
76+
```
77+
78+
Если где-то в программе поднимется flag, то выполнение кода продолжится.
79+
80+
### Ожидание события
81+
Также есть готовый макрос ожидания события:
82+
83+
```cpp
84+
LP_THREAD_("thread", {
85+
Serial.println("wait event");
86+
LP_WAIT_EVENT(); // ждём, когда кто то отправит событие
87+
Serial.println("event!");
88+
});
89+
90+
// отправляем событие по таймеру
91+
LP_THREAD({
92+
LP_SLEEP(2000);
93+
Looper.pushEvent("thread");
94+
});
95+
96+
// или так
97+
LP_TIMER(1000, []() {
98+
Looper.pushEvent("thread");
99+
});
100+
```
101+
102+
## Ограничения
103+
Реализация потоков очень простая и лёгкая, поэтому есть ограничения. По сути все макросы ожидания создают метку перехода, а весь код находится внутри `switch`, это означает что:
104+
- После макроса ожидания будет выход из функции без сохранения стека, т.е. объявленные выше локальные переменные потеряют значения
105+
- Нельзя создавать внутри потока локальные переменные, которые "живут" между вызовами макросов ожидания
106+
- Для создания "стека" используем статические переменные внутри функции
107+
108+
Например цикл с задержкой делать вот так **нельзя**:
109+
```cpp
110+
LP_THREAD({
111+
for (int i = 0; i < 10; i++) {
112+
Serial.println(i);
113+
LP_SLEEP(500);
114+
// тут мы выйдем из функции и потеряем значение i
115+
}
116+
});
117+
```
118+
119+
Вот так - **можно** и нужно:
120+
121+
```cpp
122+
LP_THREAD({
123+
static int i;
124+
125+
for (i = 0; i < 10; i++) {
126+
Serial.println(i);
127+
LP_SLEEP(500);
128+
}
129+
});
130+
```
131+
132+
## Семафоры
133+
Реализована простенькая поддержка семафоров для синхронизации потоков. Например есть два потока, один забирает семафор на какое то время для условного получения значения, второй поток блокируется до освобождения семафора и выводит это значение:
134+
135+
```cpp
136+
LP_SEM sem = 1; // начальное значение 1, чтобы какой-нибудь поток мог забрать себе семафор
137+
int val;
138+
139+
LP_THREAD({
140+
LP_SEM_WAIT(sem); // ждём тут, пока семафор занят
141+
// тут мы забираем семафор себе, другие потоки будут заблокированы
142+
143+
Serial.println(val);
144+
145+
LP_SEM_SIGNAL(sem); // освобождаем семафор
146+
});
147+
148+
LP_THREAD({
149+
LP_SEM_WAIT(sem); // ждём тут, пока семафор занят
150+
// тут мы забираем семафор себе, другие потоки будут заблокированы
151+
152+
// имитация бурной деятельности
153+
LP_SLEEP(500);
154+
val = random(100);
155+
156+
LP_SEM_SIGNAL(sem); // освобождаем семафор
157+
});
158+
```
159+
160+
### Потоки с данными
161+
Тут всё по аналогии с другими типами задач
162+
163+
```cpp
164+
// вручную
165+
LoopThreadData<int> thread_data(new int(3), [](int* data) {
166+
LP_THREAD_BEGIN();
167+
168+
LP_SLEEP(500);
169+
Serial.println(*data);
170+
*data += 1;
171+
172+
LP_THREAD_END();
173+
});
174+
175+
// макрос
176+
LP_THREAD_DATA(int, new int(3), int* data, {
177+
LP_SLEEP(500);
178+
Serial.println(*data);
179+
*data += 1;
180+
});
181+
```

0 commit comments

Comments
 (0)