From cf57d3afeb9f1078523bc78655de0e885bad0728 Mon Sep 17 00:00:00 2001 From: Nikita Kulikov Date: Wed, 25 Dec 2024 17:28:15 +0300 Subject: [PATCH 1/3] reuse, polymorphism, macro config --- embedded_c.en.md | 97 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/embedded_c.en.md b/embedded_c.en.md index b8b5864..bf95307 100644 --- a/embedded_c.en.md +++ b/embedded_c.en.md @@ -294,6 +294,31 @@ if (cond1) { Часто бывает что устройство имеет несколько входов/выходов/шин/светодиодов/кнопок итд, абсолютно всегда все что потенциально может быть больше чем в одном экземпляре должно обрабатываться через индексы и циклы. +Плохой пример: +```C + #define INPUT_1 { GPIOA, 10 } + #define INPUT_2 { GPIOA, 11 } + + ... + + gpio_init(INPUT_1); + gpio_init(INPUT_2); +``` + +В таком виде трудно вносить изменения, легко допустить ошибку, поправить одно и забыть другое. + +Лушче написать так: +```C + #define INPUTS_NUMBER 2 + #define INPUTS { { GPIOA, 10 }, { GPIOA, 11 } } + + ... + + for (int i = 0; i < INPUTS_NUMBER; i++) { + gpio_init(INPUTS[i]); + } +``` + ## Исправление ошибок в master Ранее допущенные ошибки форматирования исправляются в отдельной ветке с PR согласно кодстайлу. @@ -395,3 +420,75 @@ enum w1_protocol_transaction { W1_PROTOCOL_TRANSACTION_RECEIVE } ``` +# switch case +Старайтесь не использовать switch case. Если вы делаете несколько конструкций switch case по одной и той же переменной то это признак того что вам нужно использовать полиморфизм. Объедините в структуру все сущности, зависящие от этой переменной. Создайте массив таких структур и используйте переменную в качестве индекса для доступа к текущей структуре. + +Например вместо: +```C +void input_handler(unsigned input) { + switch (input) { + case SIGNAL_IRQ: + signal_irq_handler(); + break; + case SIGNAL_ERROR: + signal_error_handler(); + break; + case SIGNAL_READY: + signal_ready_handler(); + break; + default: + break; + } +} +``` + +Лучше использовать: +```C + +void input_handler(unsigned input) { + static const void (*handlers[])(void) = { + [SIGNAL_IRQ] = signal_irq_handler, + [SIGNAL_ERROR] = signal_error_handler, + [SIGNAL_READY] = signal_ready_handler, + }; + handlers[input](); +} +``` + +Такой код компактнее, быстрее выполняется. Также он обеспечивает консистентность, так как по этим же индексам у нас наверняка будет описан список портов GPIO на который подключены эти сигналы, итд. + +# Не плодите макросы конфигурации +Часто макросами в коде выбирается какая переферия используется в данном варианте, вариантов обычно не много, а переферии наоборот много. Вместо того чтобы прописывать макрос на каждый регистр или значение используйте структуры. + +Вместо такого: +```C + #define MODBUS_HW_USART USART1 + #define MODBUS_HW_USART_IRQn USART1_IRQn + #define MODBUS_HW_DMA_TX_CH DMA1_Channel2 +``` + +Лучше написать так: +```C + struct modbus_hw { + USART_TypeDef *usart; + IRQn_Type irqn; + DMA_Channel_TypeDef *dma_tx_ch; + }; + + #define MODBUS_USART_HW { \ + .usart = USART1, \ + .irqn = USART1_IRQn, \ + .dma_tx_ch = DMA1_Channel2 \ + } + +``` + +# Переиспользование +Повторно использовать код. Не писать код если он уже написан. +Чтобы не оставлять ошибки в дубликатах, экономить силы программиста и прочее. Читать подробнее в [Википедии](https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%BE%D0%B5_%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BA%D0%BE%D0%B4%D0%B0). +Если есть 2 одинаковые строчки, подумайте, не оформить ли это в функцию, библиотеку или использовать в уже существующую. +Для использования кода с другого устройства - сделайте его библиотекой. + + + + From a583ad8c4ff4f404aa55cea2802d1db3a0481817 Mon Sep 17 00:00:00 2001 From: Nikita Kulikov Date: Thu, 26 Dec 2024 14:22:41 +0300 Subject: [PATCH 2/3] other recomendations --- embedded_c.en.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/embedded_c.en.md b/embedded_c.en.md index bf95307..80f7da5 100644 --- a/embedded_c.en.md +++ b/embedded_c.en.md @@ -484,11 +484,36 @@ void input_handler(unsigned input) { ``` # Переиспользование -Повторно использовать код. Не писать код если он уже написан. +Нужно использовать код повторно. Не нужно писать код если он уже написан. Если чтото существующее подходит не нужно делать чтото новое. Чтобы не оставлять ошибки в дубликатах, экономить силы программиста и прочее. Читать подробнее в [Википедии](https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%BE%D0%B5_%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BA%D0%BE%D0%B4%D0%B0). Если есть 2 одинаковые строчки, подумайте, не оформить ли это в функцию, библиотеку или использовать в уже существующую. Для использования кода с другого устройства - сделайте его библиотекой. +# Поллинг +Не должно быть периодически вызываемых функций с проверкой флагов. Поллинг это плохо. Тратится процессорное время и память для флагов которые проверяются. Также не понятно кто этот флаг ставит, кто его проверяет. +У любого кода есть причина выполнения, вот в обработке причины и нужно вызывать выполнение кода, или запланировать его выполнение. +Используйте инструменты организации кода - корутины и таймменеджер. В прерываниях планируйте задачу с обработкой в бэкграунде. Не используйте флаги. + +# Не пишите лишний код, не выполняйте не нужные действия в коде +Пример: если файл с настройками не найден, прошивка использует дефолтные настройки. Не нужно сохранять файл с дефолтными настройками. Файл нужно записывать только если пользователь чтото поменял. +Чтение файла с дефолтными настройками ничем не отличается от отсутствия файла. По этому можно избавиться от лишней операции записи, расхода места иизноса флеш памяти, кода который эту запись делает. + +# Не нужно делать в run time все что можно сделать в compile time +Все вычисления констант, генерацию макросов итд, то что не меняется в ходе работы программы нужно вычислять препроцессором на этапе сборки и записывать readonly константами: ресурсы ограничены, и их нужно экономить! Пусть мощный компьютер подготовит все необходимые данные. +По этой же причине следует выделять память статически и не использовать кучу. + +# Не нужно делать в контексте прерывания все, что можно сделать в контексте background +Синхронизация асинхронных процессов это источник проблем. У нас есть разные инструменты для организации такой архитектуры. Тайм менеджер, корутины. Мы всегда должны знать что выполняется в прерываниях ВО ВСЕЙ ПРОШИВКЕ, чтобы быть уверенными что там нет ничего лишнего. +Например: в прерывании вызывать только планирование таски, а не само её выполнение. + +# Не усложняйте код +Не нужно рассчитывать на тонкости стандартов языка. Не нужно заворачивать сложные конструкции. Код должен прочитать школьник который осилил только первую главу книжки по Си. +Подумайте можно ли сделать ваш код проще ? Часто можно выделить функции, чтото перегруппировать, распутать. +Помните про бритву Оккама. + +# Делайте сразу хорошо если понимаете как +Не нужно писать TODO и FIXME. Если в код можно сделать компактнее, алгоритм быстрее, и вы знаете как, то сделайте сразу хорошо. Не надо лениться и аргументировать почему и так пойдет. +Плохо аргументировать фразами "жалко чтоли ? там не много". Такая аргументация не учитывает всю систему целиком, все возможные комбинации и многообразие кейсов применения. From cbe8972d54969eff8b01190a480a59609277168c Mon Sep 17 00:00:00 2001 From: Nikita Kulikov Date: Tue, 9 Sep 2025 12:49:29 +0300 Subject: [PATCH 3/3] fixes --- embedded_c.en.md | 51 ++++++------------------------------------------ 1 file changed, 6 insertions(+), 45 deletions(-) diff --git a/embedded_c.en.md b/embedded_c.en.md index 80f7da5..6506661 100644 --- a/embedded_c.en.md +++ b/embedded_c.en.md @@ -309,12 +309,12 @@ if (cond1) { Лушче написать так: ```C - #define INPUTS_NUMBER 2 #define INPUTS { { GPIOA, 10 }, { GPIOA, 11 } } ... - for (int i = 0; i < INPUTS_NUMBER; i++) { + input_t input_list = INPUTS; + for (int i = 0; i < ARRAY_SIZE(input_list); i++) { gpio_init(INPUTS[i]); } ``` @@ -420,42 +420,6 @@ enum w1_protocol_transaction { W1_PROTOCOL_TRANSACTION_RECEIVE } ``` -# switch case -Старайтесь не использовать switch case. Если вы делаете несколько конструкций switch case по одной и той же переменной то это признак того что вам нужно использовать полиморфизм. Объедините в структуру все сущности, зависящие от этой переменной. Создайте массив таких структур и используйте переменную в качестве индекса для доступа к текущей структуре. - -Например вместо: -```C -void input_handler(unsigned input) { - switch (input) { - case SIGNAL_IRQ: - signal_irq_handler(); - break; - case SIGNAL_ERROR: - signal_error_handler(); - break; - case SIGNAL_READY: - signal_ready_handler(); - break; - default: - break; - } -} -``` - -Лучше использовать: -```C - -void input_handler(unsigned input) { - static const void (*handlers[])(void) = { - [SIGNAL_IRQ] = signal_irq_handler, - [SIGNAL_ERROR] = signal_error_handler, - [SIGNAL_READY] = signal_ready_handler, - }; - handlers[input](); -} -``` - -Такой код компактнее, быстрее выполняется. Также он обеспечивает консистентность, так как по этим же индексам у нас наверняка будет описан список портов GPIO на который подключены эти сигналы, итд. # Не плодите макросы конфигурации Часто макросами в коде выбирается какая переферия используется в данном варианте, вариантов обычно не много, а переферии наоборот много. Вместо того чтобы прописывать макрос на каждый регистр или значение используйте структуры. @@ -485,21 +449,18 @@ void input_handler(unsigned input) { # Переиспользование Нужно использовать код повторно. Не нужно писать код если он уже написан. Если чтото существующее подходит не нужно делать чтото новое. -Чтобы не оставлять ошибки в дубликатах, экономить силы программиста и прочее. Читать подробнее в [Википедии](https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%BE%D0%B5_%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BA%D0%BE%D0%B4%D0%B0). +Чтобы не оставлять ошибки в дубликатах, экономить силы программиста и прочее. Читать подробнее в [Википедии](https://ru.wikipedia.org/wiki/Повторное_использование_кода). Если есть 2 одинаковые строчки, подумайте, не оформить ли это в функцию, библиотеку или использовать в уже существующую. Для использования кода с другого устройства - сделайте его библиотекой. - - -# Поллинг -Не должно быть периодически вызываемых функций с проверкой флагов. Поллинг это плохо. Тратится процессорное время и память для флагов которые проверяются. Также не понятно кто этот флаг ставит, кто его проверяет. - +# Старайтесь не использовать поллинг У любого кода есть причина выполнения, вот в обработке причины и нужно вызывать выполнение кода, или запланировать его выполнение. Используйте инструменты организации кода - корутины и таймменеджер. В прерываниях планируйте задачу с обработкой в бэкграунде. Не используйте флаги. +Аналогично прерываниям такой подход работает быстрее и понятнее. Периодически вызываемые функции с проверкой флагов расходуют процессорное время и память, усложняют отладку, так как не понятно кто этот флаг ставит, кто его проверяет. Непонятно на ком ответственность за выполнение и сколько времени выполнется функция do_periodic_work. # Не пишите лишний код, не выполняйте не нужные действия в коде Пример: если файл с настройками не найден, прошивка использует дефолтные настройки. Не нужно сохранять файл с дефолтными настройками. Файл нужно записывать только если пользователь чтото поменял. -Чтение файла с дефолтными настройками ничем не отличается от отсутствия файла. По этому можно избавиться от лишней операции записи, расхода места иизноса флеш памяти, кода который эту запись делает. +Чтение файла с дефолтными настройками ничем не отличается от отсутствия файла. Поэтому, можно избавиться от лишней операции записи, расхода места и износа флеш памяти, кода который эту запись делает. # Не нужно делать в run time все что можно сделать в compile time Все вычисления констант, генерацию макросов итд, то что не меняется в ходе работы программы нужно вычислять препроцессором на этапе сборки и записывать readonly константами: ресурсы ограничены, и их нужно экономить! Пусть мощный компьютер подготовит все необходимые данные.