Apr 12, 2025

Суброзділ

Про мене

ХТО я такий?


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

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

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

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

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

Feb 28, 2019

Others

Apr 12, 2025

Суброзділ Others

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

GNU/Linux

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

Mar 15, 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();                                  ///< Починаємо з нового рядка
}
Деталі

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

C/C++

Jul 19, 2022

Суброзділ 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**Змінні оточнення

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

Про приклади

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

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


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