Вітаю на моїй сторінці!

Привіт! Я Sam4uk, і ви потрапили на мою персональну сторінку, де я ділюся своїми захопленнями у світі електроніки та програмування. Ця сторінка, перш за все, створена для моїх особистих потреб – це мій цифровий блокнот з нотатками та шпаргалками, які допомагають мені в роботі, особливо у сфері Linux та C\C++.

Я завжди радий, якщо мої матеріали виявляться корисними й для когось іншого. Тут ви знайдете мої думки, технічні рішення та корисні знахідки, що накопичувалися під час роботи над різними проектами.

Мої захоплення

Електроніка

Світ електроніки для мене – це нескінченне джерело натхнення та можливостей для експериментів. Зараз на сторінці ви знайдете мою шпаргалку щодо самостійного вивчення мікроконтролерів STM8. У майбутньому я планую активно заглиблюватися у вивчення та освоєння платформи STM32, і, звісно, ділитимуся своїми успіхами та труднощами тут.

Програмування на C++ та Linux

Мови програмування C/C++ та операційна система Linux – це мої основні інструменти. Мій досвід програмування охоплює різні платформи, зокрема Raspberry Pi, Windows, Arduino, AVR, ESP32 та, звичайно, Linux. Я працював як над приватними проєктами, так і над opensource, що дозволило мені набути різнобічного досвіду.

Зосереджуюсь переважно на програмуванні для мікроконтролерів, а також створюю додатки для Linux. Інколи також займаюся розробкою кросплатформних рішень, що розширює мої горизонти у сфері розробки програмного забезпечення.

Є активним користувачем Linux Debian-базованих дистрибутивів. Як вдома, так і на роботі, я віддаю перевагу цій операційній системі та принципово відмовляюся від використання WinDOS. На відміну від WinDOS, Linux забезпечує набагато більший комфорт та безпеку для розробників будь-якого напрямку, що є для мене ключовим фактором. Це дозволяє мені повністю зануритися у світ відкритого вихідного коду та використовувати всі переваги Linux для моїх проектів.

Мої розробки для Arduino

Окремий розділ на цій сторінці присвячений документації до бібліотек для Arduino, які я розробив сам або адаптував для цієї платформи. Це результат моєї практичної роботи, який, сподіваюся, буде корисним іншим розробникам та ентузіастам.

Jun 5, 2025

Суброзділ

Про мене

ХТО я такий?


Хто я такий? Для вас я ще один чувак з інтернету, який любить писати, програмувати та робити різні речі. Я не ставлю перед собою мету комусь і щось донести через мережу. Кому потрібно сам все знайде. Інтернет відкриває широкі можливості не тільки, щоб гаяти час у соціальних мережах, а й самоудосконалюватись.

Не так давно я більш інтенсивно почав цікавитись програмуванням та операційними системами Linux, і мікроконтролерами. Все що люблю я буду розміщувати тут. Все що хоч раз мені допомогло буду тут записувати на цих сторнінках.

Це мої нотатки на полях.

Це все, що я можу зараз придумати. Сподіваюся, я додам до цього ще щось пізніше. :sweat_smile:

Вам цікаво про мене? Використовуйте лінки нижче, щоб відвідати мої профілі.

Feb 28, 2019

GNU/Linux

РОЗДІЛ З НОТАТКАМИ ПРО ЛІНУКС

Jan 29, 2026

Суброзділ GNU/Linux

csignal

У операційних системах типу Unix (Linux, macOS) сигнали — це форма міжпроцесної взаємодії (IPC). Хоча більшість сигналів мають конкретне призначення (як-от SIGKILL для зупинки процесу), існують спеціальні сигнали, зарезервовані саме для користувацьких потреб.

#NAMEC/C++htop
1HUPSIGHUPSIGHUP“Hangup” (відбій). Раніше означав розрив зв’язку з терміналом. Зараз часто використовується для того, щоб змусити фонові програми (демони) перечитати конфігураційні файли.
2INTSIGINTSIGINT“Interrupt” (переривання). Виникає, коли ви натискаєте Ctrl+C.
3QUITSIGQUITSIGQUITСхожий на INT (натискання Ctrl+\), але змушує програму створити “core dump” (знімок пам’яті) для налагодження.
4ILLSIGILLSIGILL“Illegal Instruction”. Програма намагається виконати команду, яку процесор не розуміє.
5TRAPSIGTRAPSIGTRAPВикористовується налагоджувачами (debugger) для встановлення точок зупинки (breakpoints).
6ABRTSIGSIG“Abort”. Посилається самою програмою через функцію abort(), зазвичай при критичній помилці в коді.
7BUS-SIGBUSПомилка шини. Програма намагається отримати доступ за неправильною адресою пам’яті.
8FPESIGFPESIGFPE“Floating Point Exception”. Виникає при помилковій математичній операції, наприклад, при діленні на нуль.
9KILLSIGKILLSIGKILLНайпотужніший сигнал. Він миттєво вбиває процес. Його неможливо проігнорувати або перехопити через код.
10USR1-SIGUSR1Ці сигнали не мають визначеного системою значення.
11SEGVSIGSEGVSIGSEGV“Segmentation Fault”. Найвідоміша помилка програміста — спроба звернутися до пам’яті, яка програмі не належить.
12USR2-SIGUSR2Ці сигнали не мають визначеного системою значення.
13PIPESIGPIPESIGPIPEВиникає, коли програма намагається писати в “трубу” (pipe), інший кінець якої вже закритий.
14ALRMSIGALRMSIGALRMСигнал-будильник. Використовується для таймерів.
15TERMSIGTERMSIGTERM“Terminate”. Стандартний сигнал для ввічливого прохання до програми завершити роботу. На відміну від KILL, програма може його “спіймати” і встигнути зберегти дані.
16STKFLT-SIGSTKFLTПомилка стека на співпроцесорі (зараз майже не використовується).
17CHLD-SIGCHLD“Child”. Надсилається батьківському процесу, коли його дочірній процес завершується або зупиняється.
18CONT-SIGCONT“Continue”. Наказує зупиненому процесу продовжити роботу.
19STOP-SIGSTOPЗупиняє (ставить на паузу) процес. Як і KILL, його не можна перехопити.
20TSTP-SIGTSTP“Terminal Stop”. Виникає при натисканні Ctrl+Z. Зупиняє процес, але його можна перехопити.
21SIGTTIN-SIGTTINФоновий процес хоче read() з термінала.
22SIGTTOU-SIGTTOUФоновий процес хоче write() в термінал (якщо увімкнено tostop)
23URG-SIGURGТермінові дані в мережевому сокеті.
24XCPU-SIGXCPUПрограма перевищила ліміт часу роботи процесора.
25XFSZ-SIGXFSZПрограма намагається створити файл, більший за дозволений ліміт.
26VTALRM-SIGVTALRMВіртуальний таймер (враховує тільки час, коли процес працював).
27PROF-SIGPROFВикористовується для профайлінгу (вимірювання швидкості роботи коду).
28WINCH-SIGWINCHЗміна розміру вікна термінала.
29POLL-SIGPOLLПодія введення-виведення.
30PWR-SIGPWRПомилка живлення (наприклад, коли ДБЖ повідомляє про розряд батареї).
31SYS-SIGSYSНеправильний системний виклик.
33reserved-SIGCANCELВикористовуються NPTL (Native POSIX Thread Library). Використовується бібліотекою для скасування потоків.
33reserved-SIGSETXIDВикористовуються NPTL (Native POSIX Thread Library).Використовується для синхронізації змін ідентифікаторів користувача/групи (UID/GID) між усіма потоками одного процесу.
34-SIGRTMINSIGRTMIN-
35--SIGRTMIN+1-
..--SIGRTMIN+..-
64-SIGRTMAXSIGRTMIN+30-

CMakeLists.txt

project(sig)

add_executable(alpha alpha.cpp)
add_executable(betta betta.cpp)

file(WRITE ${CMAKE_BINARY_DIR}/.gitignore "*\n*.*")

alpha.cpp

#include <csignal>
#include <fstream>
#include <iostream>
#include <unistd.h>

// Обробник сигналів з параметром
void handle_signal(int sig, siginfo_t *info, void *context) {
  std::cout << "\n[Альфа] Отримано сигнал з параметром: "
            << info->si_value.sival_int << std::endl;
}

int main() {
  pid_t my_pid = getpid();
  std::cout << "Альфа запущена. PID: " << my_pid << std::endl;

  // 1. Записуємо свій PID
  std::ofstream out("alpha.pid");
  out << my_pid;
  out.close();

  // 2. Налаштовуємо обробник
  struct sigaction sa;
  sa.sa_sigaction = handle_signal;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_SIGINFO;
  sigaction(SIGRTMIN, &sa, NULL);

  std::cout << "Чекаю на файл beta.pid та сигнали..." << std::endl;

  // 3. Цикл взаємодії
  while (true) {
    std::ifstream in("beta.pid");
    pid_t target_pid;
    if (in >> target_pid) {
      int val;
      std::cout << "Введіть число для надсилання Беті (або 0 для виходу): ";
      std::cin >> val;
      if (val == 0)
        break;

      sigval sv;
      sv.sival_int = val;
      sigqueue(target_pid, SIGRTMIN, sv);
    }
    in.close();
    sleep(1);
  }

  unlink("alpha.pid"); // Видаляємо файл при виході
  return 0;
}

betta.cpp

#include <csignal>
#include <fstream>
#include <iostream>
#include <unistd.h>

void handle_signal(int sig, siginfo_t *info, void *context) {
  std::cout << "\n[Бета] Отримано сигнал з параметром: "
            << info->si_value.sival_int << std::endl;
}

int main() {
  pid_t my_pid = getpid();
  std::cout << "Бета запущена. PID: " << my_pid << std::endl;

  // 1. Записуємо свій PID
  std::ofstream out("beta.pid");
  out << my_pid;
  out.close();

  // 2. Налаштовуємо обробник
  struct sigaction sa;
  sa.sa_sigaction = handle_signal;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_SIGINFO;
  sigaction(SIGRTMIN, &sa, NULL);

  // 3. Читаємо PID Альфи та надсилаємо вітання
  std::cout << "Шукаю Альфу..." << std::endl;
  while (true) {
    std::ifstream in("alpha.pid");
    pid_t target_pid;
    if (in >> target_pid) {
      std::cout << "Альфу знайдено (PID: " << target_pid
                << "). Насилаю '777'..." << std::endl;
      sigval sv;
      sv.sival_int = 777;
      sigqueue(target_pid, SIGRTMIN, sv);
      break;
    }
    sleep(1);
  }

  // Залишаємося працювати для прийому сигналів
  while (true)
    pause();

  unlink("beta.pid");
  return 0;
}

systemctl

systemctl є головним інструментом для керування системою ініціалізації systemd. Коли ви віддаєте команду systemctl, вона не просто “вбиває” процес, а намагається керувати ним цивілізовано.

systemctl stop \[service\]

Це найчастіша операція. Systemd діє за певним алгоритмом:

  1. SIGTERM (15). Це “ввічливе” прохання завершити роботу. Програма отримує сигнал і має час, щоб зберегти файли, закрити з’єднання з базою даних і вийти самостійно.
  2. Очікування (Timeout). Systemd чекає (за замовчуванням 90 секунд).
  3. SIGKILL (9). Якщо програма не закрилася за відведений час, systemd надсилає “контрольний постріл”, який завершує процес миттєво без збереження даних.

systemctl restart \[service\]

Працює точно так само, як stop, а після повного завершення процесу запускає його знову.

systemctl reload \[service\]

Ця команда не зупиняє програму. Вона зазвичай надсилає сигнал SIGHUP (1). Більшість серверів (як-от Nginx або Apache) налаштовані так, що при отриманні SIGHUP вони перечитують свої конфігураційні файли, не перериваючи обслуговування користувачів.

Команда systemctlСигнал за замовчуваннямМета
stopSIGTERMБезпечне завершення.
killSIGTERM(або інший вказаний)Негайне надсилання сигналу всій групі процесів.
reloadSIGHUPОновлення конфігурації без перезапуску.
(якщо завис)SIGKILLПримусове завершення після таймауту.

Налаштування сигналів у .service файлі

  • KillSignal= Вказує, який сигнал надіслати першим при зупинці (systemctl stop). За замовчуванням це SIGTERM. Ви можете змінити його, наприклад, на SIGQUIT або SIGUSR1.
  • RestartKillSignal= Який сигнал використовувати при перезавантаженні.
  • FinalKillSignal= Який сигнал надіслати, якщо програма не закрилася вчасно (за замовчуванням SIGKILL). Його неможливо змінити на щось, що можна ігнорувати.
  • ExecReload= Тут ви можете вказати команду, яка виконується при systemctl reload. Часто туди пишуть /bin/kill -HUP $MAINPID.
[Unit]
Description=Мій кастомний C++ сервіс

[Service]
ExecStart=/usr/local/bin/my_app
# Змінюємо сигнал зупинки на SIGINT (як Ctrl+C)
KillSignal=SIGINT
# Даємо програмі 20 секунд на завершення перед примусовим вбивством
TimeoutStopSec=20
# Якщо натиснули reload, надіслати сигнал реального часу 34
ExecReload=/bin/kill -34 $MAINPID

[Install]
WantedBy=multi-user.target

Як перевірити, що програма отримала сигнал від systemd?

Коли ви зміните файл сервісу, не забудьте оновити конфігурацію systemd:

sudo systemctl daemon-reload

sudo systemctl restart my_service

Тепер ви можете перевірити логи вашої програми в реальному часі:

journalctl -u my_service -f

Bash yaml parser

yb - bash парсер YAML

yb надає чисте Bash рішення для парсингу YAML.

Використання

yb [-h|-a|-c|-q|-r|-A-C-d-F-K-l-L-R-n-T] [-f <файл>|-o <об'єкт>] [-k <ключ>] [-v <значення>|-O <значення об'єкта>]

Приклади

./yb -f файл.yaml -k "ключ.дочірнійключ"

З репозиторію ви можете швидко спробувати так:

./yb -f tests/user.yaml -k "yb"

Створення ключів:

./yb -af tests/user.yaml -k "new.key"

додати вбудоване значення:

./yb -af tests/user.yaml -k "new.key" -v "one, two"

змінити його:

./yb -cf tests/user.yaml -k "new.key" -v "three"

додати значення списку:

./yb -af tests/user.yaml -k "new.- list" -v "- one - two"

додати ASCII всередині ключа каналу:

./yb -af tests/user.yaml -k "new.ascii|" -v "|> ___ _ |> \ \// |> \ / |> / / |> /_/"

Маніпулювати YAML як змінною bash:

# Рекомендується використовувати опцію '-R' raw для отримання вмісту без колірних кодів
my_YAML=$(./yb -f tests/user.yaml -k "new")
my_YAML=$(./yb -ao "${my_YAML}" -k "in_memory" -v "true")

Додати його до файлу:

./yb -af tests/user.yaml -k "new.copy" -o "${my_YAML}"

Видалити одне значення:

./yb -rf tests/user.yaml -k "new.- list" -v "- one"

або видалити все:

./yb -rf tests/user.yaml -k "new"

Встановлення

З репозиторію

З репозиторію
git clone https://github.com/t0pd4wn/yb.git
cd yb
chmod +x yb
git clone https://github.com/Sam4uk/yb.git
cd yb
chmod +x yb

yb можна використовувати безпосередньо з папки репозиторію або скопіювати та використовувати як окремий файл у проекті.

З URL-адреси

Ви можете використовувати цю команду для безпосереднього завантаження скрипта yb, де це необхідно:

З URL-адреси
bash <(echo "https://gitlab.com/t0pd4wn/yb/-/raw/main/yb"|(read l; wget $l || curl $l >yb)) && chmod +x yb;
bash <(echo "http://sam4uk.github.io/yb/yb"|(read l; wget $l || curl $l >yb)) && chmod +x yb;

Якщо ви хочете, щоб yb був доступний для всієї системи, виконайте цю команду з папки репозиторію:

sudo cp yb /usr/local/bin/

Однорядковий код

Ви можете використовувати цю команду для завантаження yb у поточну папку одним рядком:

Однорядковий код
bash <(echo "https://gitlab.com/t0pd4wn/yb/-/raw/main/yb"|(read l; wget $l || curl $l >yb)) && chmod +x yb;
bash <(echo "http://sam4uk.github.io/yb/yb"|(read l; wget $l || curl $l >yb)) && chmod +x yb;

Ви можете використати цю команду для встановлення yb у вашу систему одним рядком:

Однорядковий код
bash <(echo "https://gitlab.com/t0pd4wn/yb/-/raw/main/yb"|(read l; wget $l || curl $l >yb)) && chmod +x yb && sudo cp yb /usr/local/bin && rm yb;
bash <(echo "http://sam4uk.github.io/yb/yb"|(read l; wget $l || curl $l >yb)) && chmod +x yb && sudo cp yb /usr/local/bin && rm yb;
There may be pirates

У свій форк я додав можливість завантаження парсеру у форматі deb пакету який можна завантажити за цим посиланням

Параметри

Параметри yb поділяються на 3 типи:

  • action: параметри дії виконуються з файлом і не сумісні один з одним. Вони сумісні з input, але не з format.
  • input: параметри введення є параметрами, що встановлюються користувачем, і сумісні один з одним. Вони сумісні як з action, так і з format.
  • input: параметри форматування виводять вивід різними способами. Вони сумісні один з одним, з типом input, але не з типом action.
ОпціяІм’яТипОписПрикладПримітки
-aaddactionДодає ключ(и), значення(я) або обидва.yb -f "file.yaml" -a -k "key" -v "value"
-cchangeactionЗмінює значення(я).yb -f "file.yaml" -c -k "key" -v "new_value"
-qqueryactionВиводить true або false, якщо ключ(и), значення(я) або обидва присутні чи ні.yb -f "file.yaml" -q -k "key"Для отримання значення каналу -v 'значення каналу' рекомендується використовувати одинарні лапки.
-rremoveactionВидаляє ключ(и), значення(я) або обидва.yb -f "файл.yaml" -r -k "ключ" -v "значення"Для видалення значення каналу -v 'значення каналу' рекомендується використовувати одинарні лапки.
-ffileinputшлях до файлу YAML.yb -f "файл.yaml"Файл можна представити без опції -f, як опцію $1. -f та -c несумісні один з одним.
-oobjectinputоб’єкт YAML.yb -o "${YAML_object}"Об’єкт YAML можна використовувати з усіма діями. -f та -o сумісні між собою, лише під час додавання об’єкта до файлу.
-Oobject valueinputзначення об’єкта YAML.yb -f "file.yaml" -O "${YAML_object}"Об’єкт YAML можна використовувати з дією -a.
-kkeyinputШлях вибору ключів.yb -f "file.yaml" -k "ключ"Підтримує ключі у форматі:key, key.childkey, - list-key, pipe-key|. Можна вказати кілька ключів з роздільником ..
-vvalueinputЗначення, які потрібно додати, видалити, запитати або змінити.yb -f "file.yaml" -k "ключ" -v "значення"Підтримує значення у форматі: value, - list-value, |> pipe-value.
-AarrayформатВиводить вивід у вигляді масиву bash.yb -f "file.yaml" -A -k "key"Забезпечить інше форматування, якщо використовувати з -F або -d.
-CcolorsformatПримусово використовує кольори у нетермінальному виводі.yb -Cf "file.yaml" -A -k "key"
-ddepthformatЗабезпечує вихід з оригінальною глибиною.yb -f "file.yaml" -d -k "key.childkey" -v "new_value"
-FformatformatДрукує відформатований вивід для представлення деревоподібності в рядку.yb -f "file.yaml" -F -k "key"Забезпечить інше форматування, якщо використовувати з -A або -d.
-Kkeys ключіformatДрукує лише ключі.yb -Kf "file.yaml" -k "key"
-llineformatДрукує { {line}} у кожному рядку.yb -f "file.yaml" -l -k "key"
-LlevelformatДрукує { {<номер рівня>} } у кожному рядку.yb -f "file.yaml" -L -k "key"
-RrawformatДрукує вивід без доданих кольорів, коментарів та порожніх рядків.yb -f "file.yaml" -R -k "key"
-nnumberformatДрукує { {<номер рядка>} } на кожному рядку.yb -f "file.yaml" -n -k "key"
-TtypeformatДрукує тип значення.yb -f "file.yaml" -T -k "key"Підтримує null, boolean, integer, floating number, string.
-sspacesDeprecatedВибір кількості пробілів.

Підтримка YAML

yb забезпечує базову підтримку YAML для редагування та читання YAML-файлу. Починаючи з версії 0.9, yb не підтримує розширені функції, такі як пошук на основі груп, прив’язки, псевдоніми та перевизначення.

Розробка

Повні вихідні коди доступні в папці /src/. Версія, що знаходиться на кореневому рівні, зібрана за допомогою скрипта /src/tools/dist.sh.

Тести

Простий набір тестів доступний у папці tests/. Він представляє різні варіанти використання yb.

Щоб запустити його, виконайте команду нижче з кореневого рівня репозиторію:

./tests/tests.sh

Ви можете надати додатковий параметр для вибору парсера (наприклад, під час розробки):

./tests/tests.sh "./src/yb.dev"

Дякую

Всім вам, YAML-івцям!

Зроблено t0pd4wn на Bash.

Створення власного debian пакету

Огляд

Пакет Debian є найпростішим і найефективнішим способом розповсюдження програмного забезпечення в дистрибутивах на основі Debian. Він піклується про керування залежностями та забезпечує хороший інтерфейс для операцій встановлення/оновлення/видалення.

Офіційний спосіб створення пакету включає багато кроків і процесів. У цій шпаргалці ми розглянемо простіший спосіб створення цих пакетів. Однак офіційний спосіб створення упаковки є ідеальним і рекомендованим для виробничих цілей.

Підготовка файлів

Структура пакета .deb

У дистрибутивах на основі Debian одним із способів встановлення програм є завантаження файлу пакета .deb і використання команди dpkg для його встановлення . Цей «пакет deb» — це архів двійкових і конфігураційних файлів, пов’язаних із програмним додатком. Усі файли всередині архіву зберігаються в певній структурі папок.

Під час інсталяції на цільовій машині двійкові файли та файли конфігурації переходять у подібну структуру папок із кореневої папки .

Ця інструкція про створення та інсталяцію власного debian пакету

Архітектури

Linux

ArchitectureManufacturerStatusComments
all-Supported-
AlphaHPe (formerly HP, Compaq, Digital)Unofficial
ArmHundredsDeadNetWinder, NSLU2, …
ArmelHundredsSupportedQNAP, ?SheevaPlug, Raspberry Pi 1
armhfHundredsSupportedArm v7 32-bit systems; for kernel support see DebianKernel/ARMMP
arm64HundredsSupportedArm v8 64-bit systems
hppaHPe (formerly HP)UnofficialHP Precision Architecture
i386Intel, AMD, Cyrix, NSC, Transmeta, VIASupportedThe original x86 platform. Now requires “686” class CPU.
amd64AMD, Intel, VIASupportedalso known as em64t or x86-64.
ia64Intel, HPeDeadItanium (not Intel Core series)
m68kFreescale (formerly Motorola)UnofficialAmiga, !AtariST, very old Macintoshes, some old Sun hardware (sun3)
mipsCavium, Wave Computing (formerly Imagination, MIPS)DeadBig-endian 32-bit
mipselCavium, Loongson, Wave Computing (formerly Imagination, MIPS)DeadLittle-endian 32-bit
mips64elCavium, Loongson, Wave Computing (formerly Imagination, MIPS)SupportedLittle-endian 64-bit
PowerPCIBM, Freescale (formerly Motorola)UnofficialOld Macintoshes
PowerSPEIBM, Freescale (formerly Motorola)DeadIBM “e500” cores
PPC64IBM, Freescale (formerly Motorola)UnofficialOld Macintoshes, IBM POWER systems
ppc64elIBMSupportedPOWER8, POWER9 systems
riscv64?SiFive, etc.Unofficial
s390IBMDeadBigIron - IBM mainframe platform
s390xIBMSupportedNewer BigIron - IBM mainframe platform
SH4Renesas (formerly Hitachi)Unofficial
sparc64Sun, Fujitsu, etc.Unofficial
x32AMD, Intel, VIAUnofficial32-bit ABI using x86-64 (amd64) ISA.

non-Linux

ArchitectureManufacturerStatusComments
hurd-i386see TheHurdUnofficialNot a hardware platform
hurd-amd64see TheHurdUnofficialNot a hardware platform
netbsd-i386NetBSD kernelDeadNot a hardware platform
netbsd-alphaNetBSD kernelDeadNot a hardware platform
kfreebsd-i386FreeBSD kernelDeadNot a hardware platform
kfreebsd-amd64FreeBSD kernelDeadNot a hardware platform
.
└── mypackage_1.0_all                   # Package main folder
    ├── DEBIAN
    │   ├── control                     # File with package's main info
    │   ├── postinst                    # Script executing after the install
    │   └── preinst                     # Script executing before the install
    ├── opt
    │   └── mypackage                   # Folder including our software
    │       └── open_link.sh            # Script opening browser to ubuntu.com
    └── usr
        └── share
            ├── applications
            │   └── mypackage.desktop   # File with app info in launcher
            └── icons
                └── mypackage.xpm       # Launcher app icon

Збирання

dpkg-deb --build ./mypackage_1.0_all

Інсталювання

sudo gdebi -n ./mypackage_1.0_all.deb # test (requires gdebi-core)
sudo dpkg -i ./mypackage_1.0_all.deb # install

Видалення

sudo apt autoremove mypackage
Mar 15, 2025

C/C++

Jul 7, 2025

Суброзділ C/C++

MY NAME IS main

INTRO

Мене засміяли коли я поцікавився які форми декларації головної функції main допустимі. Ніхто мені не дав відповіді, яку я хотів отримати. Тож це мої нотатки про головну функцію

My name is main

main

return

В пошуках інформації про функцію main мені на форумах та в групах понакидали лінки на різні статті в яких писалося про головну функцію програми. Так от в одній з цих статей автор писав, що писати у функціях які повертають void писати return не обов’язково. Дозволю собі не погодитися з автором. Писати це ключове слово потрібно і бажано.

return;

Писати чи не писати return у void функціях? Візьмемо для прикладу дві реалізації однієї і тієї ж функції. Одні з ключовим словом

void F(void){
  // ...
  // багато коду
  // ...
  return;
  }

, а інша без

void F(void){
  // ...
  // багато коду
  // ...
  }

Якщо потікфункції лінійний то між функціями нема ніякої різниці. І компілятор навідь ворнінга не викине. Так як у void функціях компілятор самостійно робить вихід з return перед закриваючою скобкою }, яка позначає кінець тіла функції.

Але якщо логіка функції не є лінійною то за допомогою ключового слова return ми можемо достроково завершити роботу функції, якщо буде виконана чи не виконана якась умова.

void F(void){
  // підготовка
  // код
  if(!ready)
    return; // не треба продовжувати роботи
  // ...
  // багато коду
  // ...
  return; // успішне завершення
  }

Я, наприклад, прихильник писати return у void функціях і можу не використати це ключове слово лише коли конкретно профілонив (не захтів писати).

Але як дізнатись чи успішно завершена функція чи аварійно? Потрібно якийсь результат отримати від її роботи

return 0;

Який результат має повертати int main(void)? Згідно стандарту нуль вказує на успішене виконання функції. У бібліотеці є визначений макрос який позначає EXIT_SUCCESS успішну роботу. Цей макрос рівний 0.

#include <stdlib.h>

int main(void){
  // ...
  // багато коду
  // ...
  return EXIT_SUCCESS; // успішне завершення
  }

що тотожно

#include <stdlib.h>

int main(void){
  // ...
  // багато коду
  // ...
  return 0; // успішне завершення
  }

А яке чило повертати, якщо було аварійне завершення програми? Стандартно повертають 1.

#include <stdlib.h>

int main(void){
  // ...
  // багато коду
  // ...
  if(error)
    return EXIT_FAILURE;
  // ...
  // багато коду
  // ...
  return EXIT_SUCCESS; // успішне завершення
  }

це ж те саме

#include <stdlib.h>

int main(void){
  // ...
  // багато коду
  // ...
  if(error)
    return 1;
  // ...
  // багато коду
  // ...
  return 0; // успішне завершення
  }

Але ніде не написано, що обов’язково то має бути одиничка. Нам доступний широкий діапазон

minmaxbit
-327683276716
-2,147,483,6482,147,483,64732
-9,223,372,036,854,775,8089,223,372,036,854,775,80764

Все окрім 0

Таким чином ми можемо передавати повідомлення для скрипта, що викликатиме нашу програму, для прикладу, і сповіщатимемо, як ми виконали роботу.

КодОпис
0Немає помилок
1Попередження (не фатальна помилка (и)).Наприклад, один або кілька файлів були заблоковані якоюсь іншою програмою, тому вони не стиснулися.
2Критична помилка
7Помилка командного рядка
8Недостатьньо пам’яті для роботи
255Користувач припинив процес

Які можна ловити errorlevel. Тому не обов’язково return 1; можна писати будь яке число, але задокументувати чому таке, а не 1 чи -1.

Ніби розібралися void int.

(void)

Ніби вже й розібралися який результат має повертати main функція. Спробуємо розібратися, які параметри може отримувати головні функція.

Почнемо з найпростішого і будемо поступово підвищувати ставку.

Почнемо з int main(void)

Такий формат виклику не дозволяє налаштувати початкові дані роботи функції. Дані для обробки програма може отримувати в інтерактивному форматі під час робочого потоку. Влинути на режим роботи програми до її початку не можливо

int main(void){
  return EXIT_SUCCESS; // успішне завершення
  }

(int)

Увага

Цей варіант виклику допустимий, але компілятор викидає попередження, що потрібно перевірити кількість аргументів у функції main.

int main(int count){
  switch (count){
    0:
    // цей код ніколи не виконається так як не можливо запустити програму щоб count був рівним нулю!
    break;
    1:
    // що робити при запуску без параметрів
    break;
    2:
    // це робити коли параметр один
    break;
    3:
    // ця дія якщо параметрів два
    break;
    // ...
    break;
    default:
    // цей код запуститься в інших випадках
    break;
  }
  return EXIT_SUCCESS;
  }

Програма в такому вигляді працездатна, але дещо “кастрована”.

Ми може дізнатися кількість переданих у програму параметрів. Змінювати її поведінку у залежності від кількості аргументів, але всі ті аргументи… З програми дізнатися, що їй сказали нема можливості. Їй абсолютно “до лампочки” що написано у комнадному рядку.

На такий

myprog -h

, і такий

myprog --help

, і такий

myprog --version

програма реагуватиме однаково.

myprog foo bar

Програма може реагувати тільки на різну кількість параметрів. Це не найзручніший спосіб керування програмою.

Тож переходимо до наступного варіанту

(int, char**)

Наступна форма, яка є стандартизованою це

  #include <stdlib.h>

int main(int int_value, char** char_pointer){
  // ...
  // багато коду
  // ...
  return 0; // успішне завершення
  }

Перший параметр типу int передає кількість переданих параметрів нашій програмі. Цей параметр НІКОЛИ не може бути рівним нулю (0). Змінна int_value може мати значення в проміжку [1INT_MAX].

Гадаю вам відомо, що в мовах C та C++, і в інших відомих мені мовах програмувння починають рахувати з нуля. Тобто під нульовим параметром ми маємо шлях до виконуваного вайлу.

А як ми можемо отримати значення передані програмі з командного рядка? Оскільки розглядаємо main з двома параметрами то мабуть у другому параметрі. Другий параметр є вказівником на масив нультермінальних рядків з якого ми можемо прочитати всі параметри передані нашій програмі, першим елементом якого є шлях до виконуваного файлу.

  #include <stdlib.h>
  #include <stdio.h>

int main(int, char** pointer){
  printf("My path: %s\n",*pointer);
  return EXIT_SUCCESS; // успішне завершення
  }

Цей коди виведе в консоль повідомлення з шляхом переданим у приграму при запиуску

./a.out

виведе:

My path: ./a.out

А

$(pwd)/a.out

виведе повний шлях до виконуваного файлу 1

/home/Sam4uk.site/main/a.out
  #include <stdlib.h>
  #include <cstdio>

int main(int, char** pointer){
  for(auto it{pointer}; *it!=nullptr; it++)
    if (it == argv)
      printf("Programm path or param[0]: %s\n", *it);
    else
      printf("\tparam [%d]: %s\n", it - pointer,*it);
  return EXIT_SUCCESS; // успішне завершення
  }

Якщо запустити програму

./a.out one two foo bar end

в результаті її роботи буде виведено приблизно такий текст

Programm path or param[0]: ./a.out
        param [1]: one
        param [2]: two
        param [3]: foo
        param [4]: bar
        param [5]: end

Тобто не обов’язково звертатись до першого пареметру в якому передано їх кількість аргументів командного рядка.

  #include <stdlib.h>
  #include <cstdio>

int main(int count, char** pointer){
  for(auto it{0}; it!=count; it++)
    if (0 == it)
      printf("Programm path or param[0]: %s\n", *pointer);
    else
      printf("\tparam [%d]: %s\n", it, pointer[it]);
  return EXIT_SUCCESS; // успішне завершення
  }

Цей код тотожний попередньому (результат виконання однаковий).

Тепер програма може отримувати та реагувати на різні параметри, та різну їх кількість. Лишається лише їх розпарсити але це вже інша тема

(int, char**, char**)

Йдемо далі? Додамо ще один “документований” параметр?

Мені траплялися фрагменти кодів, де main має три параметри. Саме це мене підштовхуло шукати всі можливі та умовно можливі варіанти функції.

Якщо попередній варіант int main(int, char**) ми могли прочитати кількість c-string з першого параметру, то наступний третій параметр… Щоб отримати кількість елементів нам потрібно “вручну” рахувати

  #include <сstdlib>
  #include <cstdio>

int main(int, char**, char** e){
  int i{0};
  for(auto c{e}; *c != nullptr; c++,i++)
    printf("\t%s \n",*c);
  printf("total: %d", i);
  return EXIT_SUCCESS; // успішне завершення
  }

На цьому ноуті, який я використовую цей код нарахував 81 рядок змінних

GJS_DEBUG_TOPICS=JS ERROR;JS LOG 
        LANGUAGE=uk_UA 
        USER=sam4uk.site 
        LC_TIME=uk_UA.UTF-8 
# ......................................................
# ......................................................
        TERM=xterm-256color 
        P9K_TTY=old 
        _P9K_TTY=/dev/pts/2 
        LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36: 
total: 81

Деякі рядки я видалив (Не потрібно всім знати, яке сміття в моїй системі. Це моє сміття).

З вихлопу зрозуміло, що це змінні оточення. Цікаво а в динаміці можна їх читати? Хочу підглянути адреси де знаходяться параметри.

  #include <сstdlib>
  #include <cstdio>

int main(int, char**, char** e){
   printf("int    @: %ld\nchar ** @:%ld\nchar ** @:%ld", &p1, p2, p3);
  return EXIT_SUCCESS; // успішне завершення
}

І кілька разів поспіль запустимо виконуваний файл. Ми помітимо, що кожного разу адреси різні. Тож ми отримуємо копію змінних середовища на момент пуску програми. Від моменту пуску до моменту закінчення роботи програми ці значення можуть змінитися. Актуальні значення змінних середовища потрібно отримувти іншими методами.

(…)

int main(...){}

Я не знаю як з таким кодом працювати, але оце компілюється. Компілюється з ворнінгом, але ще й запускається.

Мені ще є що вчити. Одже продовження буде.

Ставлю трикрапик тут, а не в кінці

Замість резюме

Я довідався про стандартні mainи.

  1. int main() int main(void)
  2. int main(int, char**)
  3. int main(int, char**, char**)

І про (хочеться написати слово нестандартні, але за це можна й отрмати гнилими помідорами по пиці) ось такі варіанти. На ці варіанти компілятор матюкається, але все ж збирає життєздатні бінарні файли

  1. int main(int)
  2. int main(int, ...)
  3. int main(int, char**, ...)
  4. int main(int, char**, char**, ...)

Те що компілятор дозволяє “скріпя зубами” зібрати згадані мейни не значить що їх треба використовувати. Синтаксис мови дозволяє так писати, а бездушний компілятор попереджає, що за свої діяння він не несе відповідальності - його попросили так зробити.

Про імена

Історично склалось, що параметри функції main мають наступні імена

#nametype
1.argcintКількість параметрів передених з командного рядка
2.argvchar**Параметри передані через командний рядок
3.envchar**Змінні оточнення

Також дозволено використовувати фіктивну декларацію параметів (оголосити що функція отримуватиме параметр, але не оголосити ім’я змінної).

Про приклади

Не варто кидати помідорами та камінням за те як я написав приклади кодів. Десь я навмисне знущався зі стилю. Десь навмисне відходив від стандарту. Десь через неуважність. Десь через не знання. Я не намагаюсь когось навчити. Я сам вчусь.

Але якщо маєте слушні зауваження, я з радістю їх вислухаю.

BONUS

Коза на всі випадки життя.

#include <iostream>
#include <cstdio>


void ParseCLI(int argc, char **argv){
  return;
}


/** WINDOS */
#if defined(_WIN32)
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)

/** Mac */
#elif defined(__APPLE__)
int main(int argc, char **argv)

/** Tux */
#elif defined(__linux)
int main(int argc, char **argv, char **env)

/** Other */
#else
int main(int argc, char **argv)
#endif
    try {

#if defined(WIN32)
  ParseCLI(__argc, __argv);
  if (__argc == 0)
#else
  ParseCLI(argc, argv);
  if (argc == 0)
#endif
    throw;
  
  return EXIT_SUCCESS;
}

catch (...) {
  std::cerr << "Oh, NOOOO!\n";
  return EXIT_FAILURE;
}

  1. Виведення дійсне для Linux систем ↩︎

SDL2

SDL2 SDL2

Створення простого SDL2-додатку з нуля

На цій сторінці я покажу, як з нуля визначити та створити графічний додаток SDL2, використовуючи лише улюблений текстовий редактор. Документ написаний для у Linux, оскільки це найкраща платформа для вивчення програмування.

Створення зразка програми

Тепер нам потрібно створити файл вихідного коду та файл визначення збірки cmake. Ми взагалі не будемо використовувати SDL, а почнемо з простої програми, яка лише друкує певний текст. Як тільки вона запрацює, ми зможемо розширити її для відображення графіки. Вихідний код зберігається у файлі main.cpp та має такий вміст:

#include <iostream>

int main(int argc, char **argv) {
  std::cout<<"App is running.\n";
  return 0;
}

Визначення збірки зберігається у файлі з назвою CMakeLists.txt та виглядає так:

cmake_minimum_required(VERSION 3.11)

project(sdlprog)

add_executable(${PROJECT_NAME} src/main.cpp)

Після цього ми можемо розпочати збірку за допомогою наступної команди:

cmake -B builddir -G="Unix Makefiles"

Ось builddir каталог збірки , все, що генерується під час збірки, поміщається в цей каталог. Після запуску це має виглядати ось так.

IMG

Програма компілюється з такою командою:

cmake --build builddir -j$(nproc)

Програма буде знаходитися в каталозі збірки та може бути запущена таким чином:

./builddir/sdlprog

Вихід має виглядати ось так.

IMG

Оновлення програми для використання SDL

Код, необхідний для запуску SDL, трохи складніший, і ми не будемо вдаватися в деталі його роботи. Просто замінимо вміст main.cpp на наступне:

#if defined(__linux)
#include <SDL2/SDL.h>

#elif defined(WIN32)
#include <SDL.h>
#endif

#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>

struct SDL_Guard {
  SDL_Guard(Uint32 flags) {
    if (0 > SDL_Init(flags))
      throw std::runtime_error("Couldn't initialize SDL:" +
                               std::string(SDL_GetError()));
  }
  ~SDL_Guard() { SDL_Quit(); }
};

using UniqueWindow = std::unique_ptr<SDL_Window, void (*)(SDL_Window *)>;

#if defined(_WIN32)
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
#elif defined(__APPLE__)
int main(int argc, char **argv)

#elif defined(__linux)
int main(int argc, char **argv, char **env)

#else
int main(int argc, char **argv)
#endif
    try {
  SDL_Guard sdl_guart(SDL_INIT_EVERYTHING);

  const int WINDOW_WIDTH = {320}, WINDOWS_HEIGHT = {240};

  UniqueWindow the_window{SDL_CreateWindow("Example", SDL_WINDOWPOS_UNDEFINED,
                                           SDL_WINDOWPOS_UNDEFINED,
                                           WINDOW_WIDTH, WINDOWS_HEIGHT,
                                           SDL_WINDOW_SHOWN),
                          SDL_DestroyWindow};

  if (!the_window)
    throw std::runtime_error("Error creating window: " +
                             std::string(SDL_GetError()));

  SDL_Surface *winSurface{SDL_GetWindowSurface(the_window.get())};

  if (!winSurface)
    throw std::runtime_error("Error getting surface: " +
                             std::string(SDL_GetError()));

  SDL_FillRect(winSurface, NULL,
               SDL_MapRGB(winSurface->format, 0x00, 0x00, 0x00));

  SDL_UpdateWindowSurface(the_window.get());

  SDL_Event e;

  bool quit = {false};

  do {
    SDL_UpdateWindowSurface(the_window.get());
    while (SDL_PollEvent(&e)) {
      switch (e.type) {
      case SDL_QUIT:
        quit = true;
        break;
      default:
        break;
      }
    }
  } while (!quit);

  return EXIT_SUCCESS;
}

catch (const std::exception &e) {
  std::cout << e.what() << std::endl;
  return EXIT_FAILURE;
}

catch (...) {
  std::cout << "Something wrong\n";
  return -EXIT_FAILURE;
}
cmake_minimum_required(VERSION 3.11)

project(sdlprog)

find_package(SDL2 REQUIRED)

add_executable(${PROJECT_NAME} src/main.cpp)

target_link_libraries(${PROJECT_NAME} ${SDL2_LIBRARIES})
Jul 5, 2025

Суброзділ SDL2

Заняття №2

01_hello_SDL

01_hello_SDL.cpp

/**
 * @file 01_hello_SDL.cpp
 * @author Sam4uk (sam4uk.site@gmail.com)
 * @date 2025-05-03
 * 
 * @copyright Copyright © Sam4uk 2025 (Sam4uk.site@gmail.com) 
 * 
 */
#if defined(__linux)
#include <SDL2/SDL.h>
#elif defined(WIN32)
#include <SDL.h>
#endif

#include <exception>
#include <iostream>
#include <memory>
#include <stdexcept>

/** Screen dimension constants */
const int SCREEN_WIDTH{640}, SCREEN_HEIGHT{480};

int main(int argc, char* args[]) try {
  /** Initialize SDL */
  struct SDL_Guard {
    SDL_Guard(Uint32 flags) {
      if (0 > SDL_Init(flags))
        throw std::runtime_error("SDL could not initialize! SDL_Error:" +
                                 std::string(SDL_GetError()));
    }
    ~SDL_Guard() { SDL_Quit(); }
  } sdl_guart(SDL_INIT_VIDEO);

  /** The window we'll be rendering to */
  std::unique_ptr<SDL_Window, void (*)(SDL_Window*)> window{
      SDL_CreateWindow("C++ SDL blank by Sam4uk", SDL_WINDOWPOS_UNDEFINED,
                       SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT,
                       SDL_WINDOW_SHOWN),
      [](SDL_Window* w) {
        if (w) SDL_DestroyWindow(w);
      }};

  if (!window)
    throw std::runtime_error("Window could not be created! SDL_Error:\n" +
                             std::string(SDL_GetError()));

  /** The surface contained by the window */
  SDL_Surface* screenSurface{SDL_GetWindowSurface(window.get())};

  /** Fill the surface white */
  SDL_FillRect(screenSurface, NULL,
               SDL_MapRGB(screenSurface->format, 0xFF, 0xFF, 0xFF));

  SDL_Event e;

  bool quit{false};

  do {
    // Update the surface
    SDL_UpdateWindowSurface(window.get());
    while (SDL_PollEvent(&e)) {
      switch (e.type) {
        case SDL_QUIT:
          quit = true;
          break;
        default:
          break;
      }
    }
  } while (!quit);

  return EXIT_SUCCESS;
}

catch (const std::exception& e) {
  std::cout << e.what() << std::endl;
  return EXIT_FAILURE;
}

catch (...) {
  std::cout << "Something wrong\n";
  return -EXIT_FAILURE;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.11)
project(01_hello_SDL)

add_executable(${PROJECT_NAME} 01_hello_SDL.cpp)

target_link_libraries(${PROJECT_NAME} SDL2)

Збирання проекту

cmake -B build -G="Unix Makefiles"
cmake --build ./build -j$(nproc)

Заняття №1 Налаштування та робота з вікнами

Заняття №1

01_hello_SDL.cpp

#if defined(__linux)
#include <SDL2/SDL.h>

#elif defined(WIN32)
#include <SDL.h>
#endif

#include <iostream>

Буду старатися використовувати розумний менеджмент пам’яті. Тому підключаємо необхідні для цього заголовки.

#include <memory>

Створимо структуру яка при вихоід з області видимоті автоматично викличе SDL_Quit(). Це обов’язково. А для початку треба ініціалізувати все, що нам потрібно функцією SDL_Init(Uint32), яка проініціалізує потрібні нам системи.

SDL_INIT_TIMERtimer subsystem
SDL_INIT_AUDIOaudio subsystem
SDL_INIT_VIDEOvideo subsystem; automatically initializes the events subsystem
SDL_INIT_JOYSTICKjoystick subsystem; automatically initializes the events subsystem
SDL_INIT_HAPTIChaptic (force feedback) subsystem
SDL_INIT_GAMECONTROLLERcontroller subsystem; automatically initializes the joystick subsystem
SDL_INIT_EVENTSevents subsystem
SDL_INIT_EVERYTHINGall of the above subsystems

Можна ініціалізувати всі потрібні системи одразу, або згодом при необхідності SDL_InitSubSystem(Uint32) ініціалізувати конкретну систему, або зупинити SDL_QuitSubSystem(Uint32) вже не потрібну систему. При виклику SDL_Quit() зупиняються всі системи.

struct SDL_Guard {
  SDL_Guard(Uint32 flags) {
    if (0 > SDL_Init(flags))
      throw std::runtime_error("SDL could not initialize! SDL_Error:" +
                               std::string(SDL_GetError()));
  }
  ~SDL_Guard() { SDL_Quit(); }
};

Для вікна ми використаємо розумний вказівник який розглянемо трохи пізніше.

using UniqueWindow = std::unique_ptr<SDL_Window, void (*)(SDL_Window*)>;

Оголосимо main функцію для різних операційних систем

#if defined(_WIN32)
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
#elif defined(__APPLE__)
int main(int argc, char** argv)

#elif defined(__linux)
int main(int argc, char** argv, char** env)

#else
int main(int argc, char** argv)
#endif
    try {

Нарешті тут ініціалізуємо всі системи у “розумні структурі”

  SDL_Guard sdl_guart(SDL_INIT_EVERYTHING);

  const int WINDOW_WIDTH{680}, WINDOWS_HEIGHT{480};

Створюємо вікно функцією SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags). Перший параметр це заголовок (назва) вікна і є собою c-string параметром. Інші параметри вказують на позицію та розміри вікна.

--
--
--
--
--
SDL_WINDOW_FULLSCREENfullscreen window
SDL_WINDOW_OPENGLwindow usable with OpenGL context
SDL_WINDOW_SHOWNwindow is visible
SDL_WINDOW_HIDDENwindow is not visible
SDL_WINDOW_BORDERLESSno window decoration
SDL_WINDOW_RESIZABLEwindow can be resized
SDL_WINDOW_MINIMIZEDwindow is minimized
SDL_WINDOW_MAXIMIZEDwindow is maximized
SDL_WINDOW_MOUSE_GRABBEDwindow has grabbed mouse input
SDL_WINDOW_INPUT_FOCUSwindow has input focus
SDL_WINDOW_MOUSE_FOCUSwindow has mouse focus
SDL_WINDOW_FULLSCREEN_DESKTOP
SDL_WINDOW_FOREIGNwindow not created by SDL
SDL_WINDOW_ALLOW_HIGHDPIwindow should be created in high-DPI mode if supported. On macOS NSHighResolutionCapable must be set true in the application’s Info.plist for this to have any effect.
SDL_WINDOW_MOUSE_CAPTUREwindow has mouse captured (unrelated to MOUSE_GRABBED)
SDL_WINDOW_ALWAYS_ON_TOPwindow should always be above others
SDL_WINDOW_SKIP_TASKBARwindow should not be added to the taskbar
SDL_WINDOW_UTILITYwindow should be treated as a utility window
SDL_WINDOW_TOOLTIPwindow should be treated as a tooltip
SDL_WINDOW_POPUP_MENUwindow should be treated as a popup menu
SDL_WINDOW_KEYBOARD_GRABBEDwindow has grabbed keyboard input
SDL_WINDOW_VULKANwindow usable for Vulkan surface
SDL_WINDOW_METALwindow usable for Metal view
SDL_WINDOW_INPUT_GRABBEDequivalent to SDL_WINDOW_MOUSE_GRABBED for compatibility
  UniqueWindow the_window{
      SDL_CreateWindow("Example", SDL_WINDOWPOS_UNDEFINED,
                       SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOWS_HEIGHT,
                       SDL_WINDOW_SHOWN),

Завжди створене вікно по завершеннютреба знищувати SDL_DestroyWindow(SDL_Window*)

      SDL_DestroyWindow};

Переред початком роботи з вікном потрібно перевірити чи вдалося його створити.

  if (!the_window)
    throw std::runtime_error("Error creating window: " +
                             std::string(SDL_GetError()));

Отримуємо вкзівник на поверхню по якій бужемо малювати

  SDL_Surface* winSurface{SDL_GetWindowSurface(the_window.get())};

Перевіряємо чи вдалося отримати вказіник на поверхню

  if (!winSurface)
    throw std::runtime_error("Error getting surface: " +
                             std::string(SDL_GetError()));

  SDL_FillRect(winSurface, NULL,
               SDL_MapRGB(winSurface->format, 0xFF, 0xFF, 0xFF));

  SDL_Event e;

  bool quit{false};

  do {
    SDL_UpdateWindowSurface(the_window.get());
    while (SDL_PollEvent(&e)) {
      switch (e.type) {
        case SDL_QUIT:
          quit = true;
          break;
        default:
          break;
      }
    }
  } while (!quit);

  return EXIT_SUCCESS;
}

catch (const std::exception& e) {
  std::cout << e.what() << std::endl;
  return EXIT_FAILURE;
}

catch (...) {
  std::cout << "Something wrong\n";
  return -EXIT_FAILURE;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.11)

project(Lesson01)

add_executable(${PROJECT_NAME} main.cpp)

target_link_libraries(${PROJECT_NAME} SDL2)

Збирання проекту

cmake -B build -G="Unix Makefiles"
cmake --build ./build -j$(nproc)
./Lesson01

Заняття №2

Other

Apr 12, 2025

Суброзділ Other

videotestsrc

 gst-launch-1.0 videotestsrc pattern=snow ! autovideosink 

smpte (0) SMPTE 100% color bars SMPTE 100% color bars snow (1) Random (television snow) Random (television snow) black (2) 100% Black 100% Black white (3) 100% White 100% White red (4) Red Red green (5) Green Green blue (6) Blue Blue checkers-1 (7) Checkers 1px Checkers 1px checkers-2 (8) Checkers 2px Checkers 2px checkers-4 (9) Checkers 4px Checkers 4px checkers-8 (10) Checkers 8px Checkers 8px circular (11) Circular Circular blink (12) Blink Blink smpte75 (13) SMPTE 75% color bars SMPTE 75% color bars zone-plate (14) Zone plate Zone plate gamut (15) Gamut checkers Gamut checkers chroma-zone-plate (16) Chroma zone plate Chroma zone plate solid-color (17) Solid color Solid color ball (18) Moving ball Moving ball smpte100 (19) SMPTE 100% color bars SMPTE 100% color bars bar (20) Bar Bar pinwheel (21) Pinwheel Pinwheel spokes (22) Spokes Spokes gradient (23) Gradient Gradient colors (24) Colors Colors smpte-rp-219 (25) SMPTE test pattern, RP 219 conformant SMPTE test pattern, RP 219 conformant

Apr 12, 2025

Libraries

May 22, 2024

Суброзділ Libraries

WD.Easy

WD.Easy

Ця бібліотека призначена для керування режимами роботи сторожового таймера, поки тільки мікроконтролерів серії AVR.

Особливості

Я не писав її в стилі класу синглтон (сторожовий таймер то один). Все рівно скільки об’єктів не було б створено всі вони працюватимуть з одним регістом. Перед собою поставив завдання, щоб бібліотека була максимально легкою та можна використовувати для будь якої ардуїни та не тільки.

Основна вимога щоб ТАЙМЕР МАЄ ПОЧАТИ ВІДЛІК ЩЕ ДО СТАРТУ ФУНКЦІЇ setup, а ще краще до початку main.

Здається вийшло

Методи доступні у таймеру

МетодОПИСрезультат
setTimeOut(uint8_t)Задає інтервал спрацьовувань таймеруvoid
setMode(uint8_t)Задає дію при спрацьовуванні таймеруvoid
setTask(ptr*)Задати функцію обробник перериванняvoid
getTimeOut()Отримати періодичність спрацьовуаньuint8_t
getMode()Отримати режим роботи таймеруuint8_t
isEnable()Чи активний таймерbool
reset()Почати новий відлікvoid

Інтервали

У таблиці наведені параметри які приймає метод setTimeOut(uint8_t) та повертає getTimeOut().

defineчас c
WD_15MS0.015
WD_30MS0.030
WD_60MS0.060
WD_120MS0.120
WD_250MS0.250
WD_500MS0.500
WD_1S1
WD_2S2
WD_4S4
WD_8S8

Примітка Час приблизний і не підходить для вимірювання точних інтервалів, оскільки сторожевий таймер тактується від внутрішнього низькочатоного генератора, який “пливе” при зміні напруги живлення та температури

Дії

defineОзначення
DISABLEDТаймер зупинено
INTERRUPTПри спрацьовуванні буде викликано обробник переривання
SYSTEMRESETПри спрацьовуванні буде виконано перезавантаження мікроконтроллера
INTERUPTANDRESETПри спрацьовуванні буде викликано обробник переривання через такий же інтервали виконається перезавантаження контролеру

Обробник переривань

За замовчуванням обробникпереривань не вказаний (nullptr).

Приклади

Приклад перший

void WD_tick() {
Serial.print("WD_tick()");
Serial.println(millis());
}

WatchDogEasy WD(WatchDogEasy::WD_1S, WatchDogEasy::INTERRUPT, WD_tick);

void setup() { Serial.begin(115200); }
void loop() {}

Тут ми ініціюємо таймер на спрацьовування кожну секунду, при спрацьовуванні генеруватиметься переривання, яке буде оброблятися функцією WD_tick().

В цьому прикладі таймер починає відлік ще до початку роботи функції setup(). Це зручно якщо при ініціації контролеру є ризик завиcнути

Приклад другий

void WD_tick() {
Serial.print("WD_tick()");
Serial.println(millis());
}

WatchDogEasy WD;

void setup() {
Serial.begin(115200);
WD.setTimeOut(WatchDogEasy::WD_1S);
WD.setMode(WatchDogEasy::INTERRUPT);
WD.setTask(WD_tick);
}
void loop() {}

Тут все те саме, але таймер починає відлік після останнього сеттера.

Всі сетери скидають таймер на новий відлік. Геттери цього не роблять (Будьте уважні). Під час виконання геттеру може збігти час і виконатися дія запрограмованна у таймері.

Приклад третій

WatchDogEasy WD(WatchDogEasy::WD_1S, WatchDogEasy::SYSTEMRESET, nullptr);

int main(){
for(;;){

}
}

Тут таймер ініціалізується ще до входження у main(). На мій погляд це супер зручно

“Системні вимоги”

Таймер працюватиме на будь якому мікроконтролери серії Ардуїно та AVR

Місця в флеш займе додаткових 198 байт та 2 байти в оперативній пам’яті

Найголовніше

Встановлення

  1. Можна завантажити zip архів WD.Easy v1.0 І розпакувати вміст у потрібний каталог
  2. Бібліотека доступна для встановлення через менеджер бібліотек Arduino IDE. Шукайте в категрії
  3. Можна завантажити zip архів WD.Easy prerelise. Це гілка з тестовим не відлагодженим та найсвіжішім кодом. Встанволюється як перший пункт даного списку

Потрібно підключити

 #include <WD.Easy.hpp>

та не забувати скадати таймер, щоб він не скинув програму

CRSF

/**
 *  Для того щоб почативикористовувати мінімальний функціонал бібліотеки
 * Потрібно її заінклюдити
 */
#include <CrsfSerial.h>
/**
 *  В даному прикладі розглядається підключення приймача CRSF до Serial3
 * Arduino Mega 2560 або до  Serial якщо у вас інша "доска".
 * 
 * Будь ласка не використовуйте програмні реалізації серійного порту.
 * Використовуйте тільки апаратні серійні інтерфейси оскільки протокол 
 * CRSF використовує нестандартну швидкість передачі даних яка визначена
 * в константі CRSF_BAUDRATE. Якщо користуєтесь редактором Arduino IDE 
 * то ви не знайдете у моніторі серійного порту значення швидкості 420000
 * boud.
 *
 * По друге. Переконайтеся що ваша плата може працювати на такій швидкості
 * послідовного інтерфейсу. Ваш мікроеонтролер можливо не може працювати на
 * такій високій частоті через апаратні обмеження (не достатня частота тактового
 * генератора), софтверні обмеження (Не можливість вказати нестандартну швидкість)
 */
#if defined(__AVR_ATmega2560__)
/**
 * Тут ми створюємо об'єкт crsf класу CrsfSerial, який слухатиме Serial3.
 * 
 * В конструкторі можна задати іншу швидкість роботи, але для цього потрібно
 * переналаштовувати приймач, якщо він підтримує таку кастомізацію. Перевірте
 * ваш модуль на наявність такої опції
 */
CrsfSerial crsf(Serial3, CRSF_BAUDRATE);
#else
/**
 * Для плат які зібрані не на ATmega2560
 */
// CrsfSerial crsf(Serial, CRSF_BAUDRATE); ///< Цей
// CrsfSerial crsf(Serial);                ///< і цей записи тотожні
                                           /// за замовчуванням crsf працює 
                                           /// на швидкості 420000 бод
#error NOT MEGA2560
#endif


void packetChannels(); ///< Тут ми оголошуємо функцію яку реалізуємо пізніше
                       /// Ця функція буде виконуватися коли на порт "прилетить"
                       /// пакет з командами

/**
 *  Цей приклад написано для Ардуїно Мега і призначений для тестування пульта та
 * роботи скетчу.
 *
 * Скетч очікує що модуль приймача вісить на Serial3 а вся інформація для відладки
 * вилітає у стандартний серійний порт у монітор порта
*/

void setup() {
  Serial.begin(115200);                    ///< Ініціюємо черійний порт для відладкт
  crsf.begin();                            ///< Починаємо слухати приймач
  crsf.onPacketChannels = &packetChannels; ///< Вказуємо яка функція "витягає" команди
}


void loop() { crsf.loop(); }  ///< Тут живе програма

void packetChannels() {                              ///< Функція яка обробляє пакет
  for (auto ch{1}; ch <= CRSF_NUM_CHANNELS; ++ch) {  ///< Проходимо по всіх каналах
    Serial.print(crsf.getChannel(ch));               ///< Друкуємо
    if (ch != CRSF_NUM_CHANNELS) Serial.print(", "); ///< Ствимо розділові знаки
  }                                                  ///< Повторюємо
  Serial.println();                                  ///< Починаємо з нового рядка
}
Деталі

При виявленні неточностей чи помилок буду вдячний за фітбек