<?xml version="1.0"?>
<rss version="2.0"><channel><title>Articles: IThub &#x2014; &#x421;&#x442;&#x430;&#x442;&#x44C;&#x438; &#x43F;&#x43E; &#x44D;&#x43B;&#x435;&#x43A;&#x442;&#x440;&#x43E;&#x43D;&#x438;&#x43A;&#x435; &#x438; &#x44D;&#x43B;&#x435;&#x43A;&#x442;&#x440;&#x438;&#x43A;&#x435;</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/?d=1</link><description>Articles: IThub &#x2014; &#x421;&#x442;&#x430;&#x442;&#x44C;&#x438; &#x43F;&#x43E; &#x44D;&#x43B;&#x435;&#x43A;&#x442;&#x440;&#x43E;&#x43D;&#x438;&#x43A;&#x435; &#x438; &#x44D;&#x43B;&#x435;&#x43A;&#x442;&#x440;&#x438;&#x43A;&#x435;</description><language>en</language><item><title>&#x42D;&#x43B;&#x435;&#x43A;&#x442;&#x440;&#x43E;&#x43D;&#x438;&#x43A;&#x430; &#x438; &#x44D;&#x43B;&#x435;&#x43A;&#x442;&#x440;&#x438;&#x43A;&#x430;: &#x43F;&#x43E;&#x43B;&#x43D;&#x43E;&#x435; &#x43F;&#x43E;&#x433;&#x440;&#x443;&#x436;&#x435;&#x43D;&#x438;&#x435;, &#x43E;&#x442; &#x449;&#x435;&#x43B;&#x447;&#x43A;&#x430; &#x432;&#x44B;&#x43A;&#x43B;&#x44E;&#x447;&#x430;&#x442;&#x435;&#x43B;&#x44F; &#x434;&#x43E; &#x443;&#x43C;&#x43D;&#x44B;&#x445; &#x443;&#x441;&#x442;&#x440;&#x43E;&#x439;&#x441;&#x442;&#x432;, &#x43A;&#x43E;&#x442;&#x43E;&#x440;&#x44B;&#x435; &#x434;&#x443;&#x43C;&#x430;&#x44E;&#x442; &#x437;&#x430; &#x43D;&#x430;&#x441;</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/%D1%8D%D0%BB%D0%B5%D0%BA%D1%82%D1%80%D0%BE%D0%BD%D0%B8%D0%BA%D0%B0-%D0%B8-%D1%8D%D0%BB%D0%B5%D0%BA%D1%82%D1%80%D0%B8%D0%BA%D0%B0-%D0%BF%D0%BE%D0%BB%D0%BD%D0%BE%D0%B5-%D0%BF%D0%BE%D0%B3%D1%80%D1%83%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BE%D1%82-%D1%89%D0%B5%D0%BB%D1%87%D0%BA%D0%B0-%D0%B2%D1%8B%D0%BA%D0%BB%D1%8E%D1%87%D0%B0%D1%82%D0%B5%D0%BB%D1%8F-%D0%B4%D0%BE-%D1%83%D0%BC%D0%BD%D1%8B%D1%85-%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B9%D1%81%D1%82%D0%B2-%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D1%8B%D0%B5-%D0%B4%D1%83%D0%BC%D0%B0%D1%8E%D1%82-%D0%B7%D0%B0-%D0%BD%D0%B0%D1%81-r5/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_01/jelektrotehnika-i-jelektrotehnika.webp.4811307f0bfbeaa43d34c3451a979552.webp" /></p>
<h1>Электроника и электрика: полное погружение</h1><p><em>От щелчка выключателя до умных устройств, которые думают за нас</em></p><hr><h2>Введение: почему электроника и электрика — это важно</h2><p>Мы живём в мире, который буквально <strong>питается электричеством</strong>. Свет в комнате, смартфон в руке, интернет, автомобили, заводы, «умные» дома — всё это работает благодаря двум тесно связанным дисциплинам:</p><ul><li><p><strong>электрике</strong> — науке и практике передачи и распределения электроэнергии;</p></li><li><p><strong>электронике</strong> — управлению электричеством с помощью компонентов и схем.</p></li></ul><p>Эта статья — не сухой учебник и не набор формул. Это <strong>глубокое, но понятное путешествие</strong>: от базовых принципов до современных технологий, с логикой, примерами и пониманием <em>почему всё работает именно так</em>.</p><hr><h2>Электрика и электроника: в чём разница</h2><p>Начнём с путаницы, которая есть почти у всех новичков.</p><h3>Электрика</h3><p>Электрика отвечает за:</p><ul><li><p>передачу <strong>энергии</strong>;</p></li><li><p>высокие токи и напряжения;</p></li><li><p>питание домов, зданий, оборудования.</p></li></ul><p>Примеры:</p><ul><li><p>розетки;</p></li><li><p>автоматы и УЗО;</p></li><li><p>кабели;</p></li><li><p>трансформаторы;</p></li><li><p>электродвигатели.</p></li></ul><h3>Электроника</h3><p>Электроника отвечает за:</p><ul><li><p><strong>управление</strong> электричеством;</p></li><li><p>обработку сигналов;</p></li><li><p>логику, вычисления, автоматизацию.</p></li></ul><p>Примеры:</p><ul><li><p>резисторы и транзисторы;</p></li><li><p>микросхемы;</p></li><li><p>микроконтроллеры;</p></li><li><p>датчики;</p></li><li><p>платы Arduino, ESP32, Raspberry Pi.</p></li></ul><blockquote class="tmiQuote" cite="" data-tmiquote=""><div class="tmiQuote_contents" data-tmitruncate=""><p>Коротко: <strong>электрика даёт силу, электроника — мозг</strong>.</p></div></blockquote><hr><h2>Основы, без которых никуда</h2><h3>Электрический ток</h3><p><strong>Ток</strong> — это движение электронов.</p><p>Как вода в трубе:</p><ul><li><p>напряжение — давление;</p></li><li><p>ток — количество воды;</p></li><li><p>сопротивление — узость трубы.</p></li></ul><h3>Напряжение (Вольты)</h3><p>Напряжение показывает, <em>насколько сильно электроны хотят двигаться</em>.</p><ul><li><p>1.5 В — батарейка</p></li><li><p>5 В — USB</p></li><li><p>220–230 В — розетка (опасно!)</p></li></ul><h3>Сопротивление (Омы)</h3><p>Сопротивление ограничивает ток.</p><p>Без сопротивления:</p><ul><li><p>перегрев;</p></li><li><p>короткое замыкание;</p></li><li><p>дым (тот самый, на котором работает электроника).</p></li></ul><hr><h2>Закон Ома — фундамент всего</h2><p><strong>Закон Ома</strong> связывает три ключевые величины:</p><blockquote class="tmiQuote" cite="" data-tmiquote=""><div class="tmiQuote_contents" data-tmitruncate=""><p><strong>I = U / R</strong></p></div></blockquote><p>Где:</p><ul><li><p>I — ток</p></li><li><p>U — напряжение</p></li><li><p>R — сопротивление</p></li></ul><p>Если ты понял закон Ома — ты понял половину электроники.</p><hr><h2>Основные электронные компоненты</h2><h3>Резисторы</h3><ul><li><p>ограничивают ток;</p></li><li><p>делят напряжение;</p></li><li><p>защищают схемы.</p></li></ul><p>Без них электроника превращается в фейерверк.</p><h3>Конденсаторы</h3><ul><li><p>накапливают заряд;</p></li><li><p>сглаживают напряжение;</p></li><li><p>фильтруют шумы.</p></li></ul><p>Используются в питании, таймерах, аудиосхемах.</p><h3>Диоды</h3><ul><li><p>пропускают ток только в одну сторону;</p></li><li><p>защищают от переполюсовки;</p></li><li><p>выпрямляют переменный ток.</p></li></ul><h3>Транзисторы</h3><p>Сердце электроники.</p><p>Транзистор может:</p><ul><li><p>усиливать сигнал;</p></li><li><p>работать как электронный выключатель;</p></li><li><p>управлять большими токами малыми.</p></li></ul><blockquote class="tmiQuote" cite="" data-tmiquote=""><div class="tmiQuote_contents" data-tmitruncate=""><p>Без транзисторов не было бы компьютеров.</p></div></blockquote><hr><h2>Электронные схемы: как читать и понимать</h2><p>Схема — это язык электроники.</p><p>Хорошая схема:</p><ul><li><p>показывает логику;</p></li><li><p>упрощает ремонт;</p></li><li><p>позволяет повторить устройство.</p></li></ul><p>Важно научиться:</p><ul><li><p>читать обозначения;</p></li><li><p>понимать путь тока;</p></li><li><p>видеть функциональные блоки.</p></li></ul><hr><h2>Источники питания</h2><h3>Линейные блоки питания</h3><ul><li><p>простые;</p></li><li><p>надёжные;</p></li><li><p>тяжёлые и горячие.</p></li></ul><h3>Импульсные блоки питания</h3><ul><li><p>компактные;</p></li><li><p>эффективные;</p></li><li><p>сложные.</p></li></ul><p>Именно они в зарядках, компьютерах и бытовой технике.</p><hr><h2>Микроконтроллеры: мозг современных устройств</h2><p>Микроконтроллер — это маленький компьютер на одной микросхеме.</p><p>Он умеет:</p><ul><li><p>читать датчики;</p></li><li><p>управлять выходами;</p></li><li><p>выполнять программы.</p></li></ul><h3>Популярные платформы</h3><ul><li><p>Arduino — идеально для старта</p></li><li><p>ESP8266 / ESP32 — Wi‑Fi и IoT</p></li><li><p>STM32 — промышленный уровень</p></li></ul><blockquote class="tmiQuote" cite="" data-tmiquote=""><div class="tmiQuote_contents" data-tmitruncate=""><p>Микроконтроллер — это точка, где электроника встречается с программированием.</p></div></blockquote><hr><h2>Автоматизация и умные системы</h2><p>Электроника давно вышла за пределы плат.</p><p>Сегодня она управляет:</p><ul><li><p>освещением;</p></li><li><p>климатом;</p></li><li><p>безопасностью;</p></li><li><p>производством;</p></li><li><p>транспортом.</p></li></ul><p>Основные элементы автоматизации:</p><ul><li><p>датчики;</p></li><li><p>контроллеры;</p></li><li><p>исполнительные механизмы.</p></li></ul><hr><h2>Безопасность: самое важное</h2><p>Работа с электричеством требует уважения.</p><p>Основные правила:</p><ul><li><p>не работать под напряжением;</p></li><li><p>использовать защиту;</p></li><li><p>проверять прибором;</p></li><li><p>понимать, что делаешь.</p></li></ul><blockquote class="tmiQuote" cite="" data-tmiquote=""><div class="tmiQuote_contents" data-tmitruncate=""><p>Электричество не прощает ошибок, но уважает знания.</p></div></blockquote><hr><h2>Как развиваться дальше</h2><h3>Для новичков</h3><ul><li><p>базовая теория;</p></li><li><p>простые схемы;</p></li><li><p>макетные платы;</p></li><li><p>Arduino.</p></li></ul><h3>Для продолжающих</h3><ul><li><p>расчёт схем;</p></li><li><p>PCB-дизайн;</p></li><li><p>силовая электроника;</p></li><li><p>микроконтроллеры.</p></li></ul><h3>Для профессионалов</h3><ul><li><p>промышленная автоматизация;</p></li><li><p>встраиваемые системы;</p></li><li><p>высокочастотная электроника;</p></li><li><p>энергоэффективность.</p></li></ul><hr><h2>Заключение</h2><p>Электроника и электрика — это не просто профессия или хобби. Это <strong>способ понимать мир</strong>, в котором мы живём.</p><p>Каждая схема — это логика.<br>Каждый провод — это путь энергии.<br>Каждое устройство — результат инженерного мышления.</p><p>Если ты начал разбираться в этом — значит, ты уже сделал первый шаг в мир, который управляет будущим.</p><p><em>И да, если что-то не заработало с первого раза — значит, ты всё делаешь правильно.</em></p>]]></description><guid isPermaLink="false">5</guid><pubDate>Mon, 12 Jan 2026 23:57:42 +0000</pubDate></item><item><title>&#x41D;&#x435;&#x43B;&#x438;&#x43D;&#x435;&#x439;&#x43D;&#x430;&#x44F; &#x444;&#x438;&#x43B;&#x44C;&#x442;&#x440;&#x430;&#x446;&#x438;&#x44F; &#x432;&#x440;&#x435;&#x43C;&#x435;&#x43D;&#x43D;&#x43E;&#x439; &#x441;&#x442;&#x440;&#x443;&#x43A;&#x442;&#x443;&#x440;&#x44B; &#x441;&#x43B;&#x43E;&#x436;&#x43D;&#x44B;&#x445; &#x441;&#x438;&#x433;&#x43D;&#x430;&#x43B;&#x43E;&#x432; &#x434;&#x43B;&#x44F; &#x432;&#x44B;&#x44F;&#x432;&#x43B;&#x435;&#x43D;&#x438;&#x44F; &#x430;&#x43D;&#x43E;&#x43C;&#x430;&#x43B;&#x438;&#x439; &#x432; &#x43F;&#x440;&#x43E;&#x442;&#x44F;&#x436;&#x451;&#x43D;&#x43D;&#x44B;&#x445; &#x441;&#x440;&#x435;&#x434;&#x430;&#x445;</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%B0%D1%8F-%D1%84%D0%B8%D0%BB%D1%8C%D1%82%D1%80%D0%B0%D1%86%D0%B8%D1%8F-%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9-%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D1%8B-%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D1%8B%D1%85-%D1%81%D0%B8%D0%B3%D0%BD%D0%B0%D0%BB%D0%BE%D0%B2-%D0%B4%D0%BB%D1%8F-%D0%B2%D1%8B%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F-%D0%B0%D0%BD%D0%BE%D0%BC%D0%B0%D0%BB%D0%B8%D0%B9-%D0%B2-%D0%BF%D1%80%D0%BE%D1%82%D1%8F%D0%B6%D1%91%D0%BD%D0%BD%D1%8B%D1%85-%D1%81%D1%80%D0%B5%D0%B4%D0%B0%D1%85-r66/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_02/img_1_full.webp.8971d079c2f5b22877e34e7ca9942070.webp" /></p>
<h2>1. Введение</h2><p>Анализ сигналов, распространяющихся в протяжённых средах (например, атмосфере или океане), осложняется наличием:</p><ul><li><p>фоновых шумов;</p></li><li><p>квазирегулярных структурных неоднородностей;</p></li><li><p>случайных флуктуаций;</p></li><li><p>локальных аномалий, нарушающих равновесное состояние среды.</p></li></ul><p>Классические методы линейной фильтрации ориентированы на подавление шума за счёт сглаживания сигнала. Однако при этом часто теряется информация о локальных скачках — именно тех особенностях, которые соответствуют аномальным неоднородностям.</p><p>Предлагаемый подход основан на <strong>нелинейной обработке дискретизированного сигнала</strong>, при которой:</p><ul><li><p>периодические и квазирегулярные структуры сглаживаются;</p></li><li><p>локальные разовые перепады усиливаются;</p></li><li><p>обеспечивается высокая чувствительность к границам аномалий.</p></li></ul><p>Метод устраняет противоречие между необходимостью подавления фона и сохранением (или усилением) диагностически значимых перепадов сигнала.</p><hr><h2>2. Цель работы</h2><p>Представить результаты численного моделирования оригинального метода нелинейной фильтрации временной структуры сложных негармонических сигналов, предназначенного для выявления аномалий в протяжённых средах.</p><hr><h2>3. Научная новизна</h2><p>Предложен алгоритм нелинейной фильтрации, позволяющий:</p><ul><li><p>выявлять аномальные неоднородности в частично организованной структуре сигнала;</p></li><li><p>определять положение границ таких неоднородностей;</p></li><li><p>обеспечивать устойчивость к шумам и псевдорегулярным структурам.</p></li></ul><p>Метод сочетает свойства медианных, рекурсивных, итеративных и робастных фильтров, однако обладает рядом принципиальных отличий.</p><hr><h2>4. Методология</h2><h3>4.1. Общая идея алгоритма</h3><p>Пусть задан дискретный сигнал</p><p>S={s1,s2,...,sN}S = \{s_1, s_2, ..., s_N\}S={s1,s2,...,sN}</p><p>Алгоритм включает последовательность операций накопления, сравнения и логарифмического преобразования интегральных характеристик сигнала.</p><p>Основной принцип — сравнение поведения сигнала на симметричных участках трассы с использованием скользящих накопленных сумм.</p><hr><h3>4.2. Базовый алгоритм фильтрации</h3><h4>Этап 1. Деление текущего интервала</h4><p>Текущий временной интервал регистрации сигнала делится пополам с шагом, равным шагу дискретизации.</p><h4>Этап 2. Логарифм отношения</h4><p>Для нарастающих интервалов вычисляется:</p><p>ln⁡(S1S2)\ln \left( \frac{S_1}{S_2} \right)ln(S2S1)</p><p>где S1S_1S1 и S2S_2S2 — интегральные значения сигнала на соответствующих полуинтервалах.</p><p>Шаг приращения равен удвоенному шагу дискретизации.</p><h4>Этап 3. Нормировка</h4><p>Полученный ряд умножается на отношение:</p><p>Nn\frac{N}{n}nN</p><p>где</p><ul><li><p>NNN — общее число отсчётов,</p></li><li><p>nnn — число отсчётов в текущем интервале.</p></li></ul><h4>Этап 4. Выделение аномалии</h4><p>Аномалия определяется как:</p><ul><li><p>выраженный провал во временном ходе отфильтрованного сигнала;</p></li><li><p>чем уже и глубже провал — тем контрастнее неоднородность.</p></li></ul><hr><h2>5. Формирование модельной структуры неоднородности</h2><p>Для точного определения положения границ используется более детальная процедура.</p><h3>5.1. Формирование двух накопительных последовательностей</h3><p>Из исходных отсчётов формируются:</p><ul><li><p><strong>Последовательность типа A</strong> — накопление от начала к концу.</p></li><li><p><strong>Последовательность типа B</strong> — накопление от конца к началу.</p></li></ul><p>Шаг приращения соответствует требуемому разрешению.</p><hr><h3>5.2. Формирование дополнительных отсчётов</h3><p>Для каждой накопительной величины:</p><ul><li><p>для типа A формируется AD — сумма последующих отсчётов;</p></li><li><p>для типа B формируется BD — сумма предшествующих отсчётов.</p></li></ul><hr><h3>5.3. Нормированное логарифмическое преобразование</h3><p>Вычисляется:</p><p>ln⁡(min⁡(AD,A)max⁡(AD,A))\ln \left( \frac{\min(AD, A)}{\max(AD, A)} \right)ln(max(AD,A)min(AD,A))</p><p>Аналогично для пары B и BD.</p><p>Результат нормируется на число исходных отсчётов, формируя последовательности:</p><p>IAn,IBnIA_n, \quad IB_nIAn,IBn</p><p>Эти величины пропорциональны средним коэффициентам ослабления среды на соответствующих участках.</p><hr><h3>5.4. Поиск общей части Δ</h3><p>Рассматриваются отношения:</p><p>IA1IBn,IA2IBn−1,…,IAnIB1\frac{IA_1}{IB_n}, \frac{IA_2}{IB_{n-1}}, \dots, \frac{IA_n}{IB_1}IBnIA1,IBn−1IA2,…,IB1IAn</p><p>при условии, что соответствующие участки имеют общую часть:</p><p>Δ=12 шага дискретизации\Delta = \frac{1}{2} \text{ шага дискретизации}Δ=21 шага дискретизации</p><p>Находится глобальный минимум:</p><p>IAk/IBn−k+1IA_k / IB_{n-k+1}IAk/IBn−k+1</p><hr><h3>5.5. Формирование основной цифровой последовательности</h3><p>Основная последовательность строится:</p><ul><li><p>из IAnIA_nIAn при n&gt;kn &gt; kn&gt;k;</p></li><li><p>из IBnIB_nIBn при n≤kn \le kn≤k.</p></li></ul><hr><h3>5.6. Определение границ аномалии</h3><ul><li><p>Максимальный перепад основной последовательности → ближняя граница.</p></li><li><p>Вторичный перепад → дальняя граница.</p></li></ul><hr><h2>6. Численное моделирование</h2><p>Проведено сравнение предложенной фильтрации с:</p><ul><li><p>линейной фильтрацией (LINF),</p></li><li><p>медианной фильтрацией (MEDF).</p></li></ul><p>Моделирование выполнялось для экспоненциально спадающего сигнала длиной 50 отсчётов с перепадом в центре выборки.</p><p>Использовались три отношения сигнал : перепад : шум:</p><ul><li><p>SIG1 — 10:1:1</p></li><li><p>SIG2 — 10:1:5</p></li><li><p>SIG3 — 10:1:10</p></li></ul><hr><h2>7. Критерий эффективности</h2><p>Использовалось отношение дисперсий:</p><p>σфильтр2σисходный2\frac{\sigma^2_{\text{фильтр}}}{\sigma^2_{\text{исходный}}}σисходный2σфильтр2</p><p>Отдельно анализировались участки до и после перепада.</p><p>Для предложенной фильтрации (GRAN):</p><ul><li><p>значения DISP1 и DISP2 существенно превышают 1;</p></li><li><p>это указывает на нелинейное усиление локального перепада;</p></li><li><p>чувствительность возрастает при увеличении шумовой компоненты.</p></li></ul><p>Линейная и медианная фильтрации не обеспечивают сопоставимого выделения перепада при высоком уровне шума.</p><hr><h2>8. Сопоставление с известными методами</h2><p>Предлагаемая фильтрация имеет общие черты со следующими методами:</p><h3>8.1. Медианная фильтрация</h3><ul><li><p>подавление повторяющихся выбросов;</p></li><li><p>сохранение одиночных перепадов.</p></li></ul><h3>8.2. Рекурсивная фильтрация</h3><ul><li><p>прогнозирование гладкости;</p></li><li><p>зависимость от порядка фильтра.</p></li></ul><h3>8.3. Итеративная обработка</h3><ul><li><p>последовательное изменение порога дискриминации.</p></li></ul><h3>8.4. Робастные методы</h3><ul><li><p>слабая чувствительность к малым возмущениям входных данных.</p></li></ul><hr><h2>9. Преимущества предложенного метода</h2><ol><li><p>Исключается усиление случайных мелких перепадов.</p></li><li><p>Порядок фильтрации автоматически возрастает с длиной интервала.</p></li><li><p>Используется адаптивный порог выявления перепадов.</p></li><li><p>Отсутствуют отрицательные значения сигнала.</p></li><li><p>Не требуется частотное подавление шумов.</p></li><li><p>Робастность возрастает от центра выборки к её краям.</p></li><li><p>Слабо зависит от длины выборки.</p></li><li><p>Прост в аппаратурной реализации.</p></li><li><p>Обеспечивает высокое быстродействие.</p></li></ol><hr><h2>10. Выводы</h2><p>Численное моделирование показало, что предложенный метод нелинейной фильтрации:</p><ul><li><p>эффективно выделяет локальные перепады сигнала;</p></li><li><p>устойчив к шуму;</p></li><li><p>подавляет псевдорегулярную структуру;</p></li><li><p>превосходит линейные и медианные фильтры в условиях высокого уровня шума.</p></li></ul><p>Метод особенно перспективен для анализа сигналов обратного рассеяния в протяжённых средах, включая атмосферные приложения.</p><hr><h2>11. Литература</h2><ol><li><p>Полканов Ю.А. Способ определения положения оптической неоднородности атмосферы. Авт. свид-во СССР №1448907, кл. G01W1/00, 1988.</p></li><li><p>Полканов Ю.А. Выявление аномальной неоднородности на фоне псевдорегулярной структуры сложного спадающего сигнала. Вестник БГУ, Серия 1, 1991.</p></li><li><p>Полканов Ю.А. Об одной возможности выделения аномальной неоднородности атмосферы (Метод нелинейной фильтрации). Оптика атмосферы и океана, 1992.</p></li></ol>]]></description><guid isPermaLink="false">66</guid><pubDate>Fri, 27 Feb 2026 20:07:07 +0000</pubDate></item><item><title>&#x427;&#x442;&#x43E; &#x442;&#x430;&#x43A;&#x43E;&#x435; &#x418;&#x41A;-&#x43F;&#x440;&#x438;&#x435;&#x43C;&#x43D;&#x438;&#x43A;: &#x443;&#x441;&#x442;&#x440;&#x43E;&#x439;&#x441;&#x442;&#x432;&#x43E;, &#x43F;&#x440;&#x438;&#x43D;&#x446;&#x438;&#x43F; &#x440;&#x430;&#x431;&#x43E;&#x442;&#x44B; &#x438; &#x43E;&#x431;&#x43B;&#x430;&#x441;&#x442;&#x438; &#x43F;&#x440;&#x438;&#x43C;&#x435;&#x43D;&#x435;&#x43D;&#x438;&#x44F;</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/%D1%87%D1%82%D0%BE-%D1%82%D0%B0%D0%BA%D0%BE%D0%B5-%D0%B8%D0%BA-%D0%BF%D1%80%D0%B8%D0%B5%D0%BC%D0%BD%D0%B8%D0%BA-%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF-%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D1%8B-%D0%B8-%D0%BE%D0%B1%D0%BB%D0%B0%D1%81%D1%82%D0%B8-%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F-r69/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_02/08.jpg.461c9f82aa2eb523e18da7c66190def1.jpg" /></p>
<p>Сегодня сложно представить электронику без инфракрасных (ИК) технологий: пульты дистанционного управления, системы безопасности, промышленная автоматизация, медицинские приборы и робототехника — во всех этих решениях применяются ИК-приемники. Разберем подробно, что такое инфракрасный приемник, как он устроен, по какому принципу работает и как диагностировать его неисправности.</p><hr><h2>Что такое ИК-приемник</h2><p><strong>ИК-приемник (инфракрасный приемник)</strong> — это электронное устройство, предназначенное для приема и декодирования сигналов, передаваемых в виде инфракрасного излучения от источника (обычно ИК-светодиода).</p><p>Классический пример — пульт дистанционного управления телевизором или кондиционером. Передатчик формирует последовательность модулированных ИК-импульсов, а приемник:</p><ol><li><p>Улавливает инфракрасное излучение.</p></li><li><p>Преобразует его в электрический сигнал.</p></li><li><p>Демодулирует и передает данные в управляющую схему устройства.</p></li></ol><hr><h2>Где применяются инфракрасные приемники</h2><h3>1. Бытовая электроника</h3><ul><li><p>Пульты ДУ (телевизоры, аудиосистемы, кондиционеры).</p></li><li><p>Медиаплееры и приставки.</p></li><li><p>Ранее — передача данных в мобильных телефонах и ПК (до распространения Bluetooth и Wi-Fi, скорость достигала ~4 Мбит/с).</p></li></ul><h3>2. Освещение и автоматизация зданий</h3><ul><li><p>Датчики движения в подъездах, коридорах, санузлах.</p></li><li><p>Автоматическое включение/выключение освещения.</p></li><li><p>Энергосберегающие системы «умного дома».</p></li></ul><h3>3. Системы безопасности</h3><ul><li><p>Охранные датчики движения.</p></li><li><p>Инфракрасные барьеры периметра.</p></li><li><p>Контроль несанкционированного доступа.</p></li></ul><h3>4. Бесконтактные устройства</h3><ul><li><p>Диспенсеры мыла и воды.</p></li><li><p>Сенсорные смесители.</p></li><li><p>Бесконтактные мусорные контейнеры.</p></li></ul><h3>5. Медицинская техника и носимая электроника</h3><ul><li><p>Бесконтактные термометры.</p></li><li><p>Пульсометры и фитнес-трекеры (анализ кровотока по отраженному ИК-сигналу).</p></li></ul><hr><h2>Применение ИК-приемников в промышленности</h2><p>Простота, надежность и низкая стоимость делают ИК-датчики востребованными в промышленной среде.</p><h3>Конвейерные линии</h3><ul><li><p>Детекция присутствия объекта.</p></li><li><p>Контроль положения детали.</p></li><li><p>Синхронизация этапов сборки и упаковки.</p></li></ul><h3>Системы безопасности</h3><ul><li><p>Инфракрасные барьеры.</p></li><li><p>Контроль складов и производственных зон.</p></li><li><p>Защита оборудования.</p></li></ul><h3>Контроль качества</h3><ul><li><p>Инфракрасная спектроскопия для анализа состава материалов.</p></li><li><p>Выявление дефектов.</p></li><li><p>Контроль смесей в химическом и пищевом производстве.</p></li></ul><h3>Энергетика и металлургия</h3><ul><li><p>Пирометры и тепловизоры.</p></li><li><p>Контроль температуры печей, трубопроводов, реакторов.</p></li><li><p>Предотвращение перегрева и аварий.</p></li></ul><h3>Логистика</h3><ul><li><p>Системы учета и сортировки.</p></li><li><p>Взаимодействие со сканерами штрихкодов и RFID.</p></li></ul><h3>Машиностроение и робототехника</h3><ul><li><p>Обнаружение препятствий.</p></li><li><p>Навигация автономных систем.</p></li><li><p>Системы точного позиционирования.</p></li></ul><hr><h2>Принцип работы инфракрасного приемника</h2><p>ИК-приемники работают в диапазоне длин волн <strong>700 нм – 1 мм</strong>, однако в бытовых и промышленных системах чаще используется диапазон <strong>850–950 нм</strong>.</p><h3>Основные этапы работы:</h3><ol><li><p><strong>Прием излучения</strong><br>Чувствительный элемент (фотодиод или фототранзистор) реагирует на ИК-свет.</p></li><li><p><strong>Преобразование в электрический сигнал</strong><br>При попадании излучения генерируется ток.</p></li><li><p><strong>Фильтрация</strong><br>Оптический фильтр подавляет помехи (солнечный свет, лампы).</p></li><li><p><strong>Модуляция и демодуляция</strong><br>Сигнал передается импульсами (обычно 36–38 кГц).<br>Приемник выделяет именно эту частоту, игнорируя фон.</p></li><li><p><strong>Передача в контроллер</strong><br>Демодулированный сигнал поступает в микроконтроллер.</p></li></ol><hr><h2>Устройство ИК-приемника</h2><p>Типовой ИК-модуль включает:</p><ul><li><p>фотодиод;</p></li><li><p>усилитель;</p></li><li><p>полосовой фильтр;</p></li><li><p>демодулятор;</p></li><li><p>формирователь цифрового сигнала.</p></li></ul><h3>Основные характеристики</h3><div class="tmiRichText__table-wrapper"><table style="width: 616px;"><colgroup><col style="width:337px;"><col style="width:279px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Параметр</p></th><th colspan="1" rowspan="1"><p>Описание</p></th></tr><tr><td colspan="1" rowspan="1"><p>Длина волны</p></td><td colspan="1" rowspan="1"><p>850–950 нм (типично)</p></td></tr><tr><td colspan="1" rowspan="1"><p>Частота модуляции</p></td><td colspan="1" rowspan="1"><p>36–38 кГц (иногда 56 кГц)</p></td></tr><tr><td colspan="1" rowspan="1"><p>Чувствительность</p></td><td colspan="1" rowspan="1"><p>Минимальная мощность сигнала (мкВт)</p></td></tr><tr><td colspan="1" rowspan="1"><p>Угол обзора</p></td><td colspan="1" rowspan="1"><p>30–90°</p></td></tr><tr><td colspan="1" rowspan="1"><p>Дальность</p></td><td colspan="1" rowspan="1"><p>Зависит от мощности передатчика</p></td></tr><tr><td colspan="1" rowspan="1"><p>Время отклика</p></td><td colspan="1" rowspan="1"><p>От миллисекунд</p></td></tr><tr><td colspan="1" rowspan="1"><p>Тип выхода</p></td><td colspan="1" rowspan="1"><p>Аналоговый или цифровой</p></td></tr></tbody></table></div><hr><h2>Пример: Vishay TSOP4456</h2><p>Рассмотрим популярный модуль — TSOP4456 компании Vishay Intertechnology.</p><h3>Основные параметры:</h3><ul><li><p>Питание: 2,5–5,5 В</p></li><li><p>Частота модуляции: 56 кГц</p></li><li><p>Потребляемый ток: ~0,4 мА</p></li><li><p>Угол обзора: ~45°</p></li><li><p>Дальность приема: до 45 м</p></li><li><p>Температура: –25…+85 °C</p></li><li><p>Выход: цифровой (активный низкий уровень)</p></li></ul><p>Модуль применяется в системах дистанционного управления, совместим с протоколами RCA и другими распространенными стандартами.</p><hr><h2>Факторы, влияющие на работу ИК-приемника</h2><p>В промышленной эксплуатации необходимо учитывать:</p><ul><li><p>запыленность;</p></li><li><p>задымленность;</p></li><li><p>влажность;</p></li><li><p>вибрации;</p></li><li><p>температурные перепады;</p></li><li><p>электромагнитные помехи.</p></li></ul><p>В сложных условиях применяются модули в герметичных корпусах с промышленным исполнением.</p><hr><h2>Устранение неисправностей ИК-приемника</h2><p>Диагностика проводится поэтапно.</p><h3>1. Проверка передатчика</h3><p>Навести пульт на камеру смартфона и нажать кнопку — мигающий свет указывает на исправность ИК-диода.</p><h3>2. Осмотр приемника</h3><ul><li><p>Проверить загрязнение линзы.</p></li><li><p>Осмотреть корпус на механические повреждения.</p></li></ul><h3>3. Проверка питания</h3><ul><li><p>Измерить напряжение мультиметром.</p></li><li><p>Проверить цепь питания и предохранители.</p></li></ul><h3>4. Проверка пайки</h3><ul><li><p>Осмотреть контакты.</p></li><li><p>При необходимости перепаять соединения.</p></li></ul><h3>5. Проверка сигнала</h3><ul><li><p>Использовать осциллограф.</p></li><li><p>Убедиться в наличии корректных импульсов.</p></li></ul><h3>6. Диагностика контроллера</h3><p>Если приемник исправен, проблема может быть в микроконтроллере или основной плате.</p><hr><h2>Преимущества ИК-приемников</h2><ul><li><p>Низкая стоимость</p></li><li><p>Простота интеграции</p></li><li><p>Энергоэффективность</p></li><li><p>Высокая помехоустойчивость (при модуляции)</p></li><li><p>Надежность</p></li><li><p>Широкий диапазон рабочих температур</p></li></ul><p>Несмотря на развитие Bluetooth, Wi-Fi и других беспроводных технологий, ИК-приемники остаются экономически и технически оправданным решением во множестве задач.</p>]]></description><guid isPermaLink="false">69</guid><pubDate>Fri, 27 Feb 2026 20:14:04 +0000</pubDate></item><item><title>&#x418;&#x43D;&#x442;&#x435;&#x440;&#x43D;&#x435;&#x442; &#x432;&#x435;&#x449;&#x435;&#x439; (IoT) &#x438; &#x43F;&#x440;&#x43E;&#x43C;&#x44B;&#x448;&#x43B;&#x435;&#x43D;&#x43D;&#x44B;&#x439; &#x438;&#x43D;&#x442;&#x435;&#x440;&#x43D;&#x435;&#x442; &#x432;&#x435;&#x449;&#x435;&#x439; (IIoT): &#x442;&#x435;&#x445;&#x43D;&#x43E;&#x43B;&#x43E;&#x433;&#x438;&#x438;, &#x430;&#x440;&#x445;&#x438;&#x442;&#x435;&#x43A;&#x442;&#x443;&#x440;&#x430; &#x438; &#x43F;&#x440;&#x430;&#x43A;&#x442;&#x438;&#x447;&#x435;&#x441;&#x43A;&#x43E;&#x435; &#x43F;&#x440;&#x438;&#x43C;&#x435;&#x43D;&#x435;&#x43D;&#x438;&#x435;</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%BD%D0%B5%D1%82-%D0%B2%D0%B5%D1%89%D0%B5%D0%B9-iot-%D0%B8-%D0%BF%D1%80%D0%BE%D0%BC%D1%8B%D1%88%D0%BB%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9-%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%BD%D0%B5%D1%82-%D0%B2%D0%B5%D1%89%D0%B5%D0%B9-iiot-%D1%82%D0%B5%D1%85%D0%BD%D0%BE%D0%BB%D0%BE%D0%B3%D0%B8%D0%B8-%D0%B0%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80%D0%B0-%D0%B8-%D0%BF%D1%80%D0%B0%D0%BA%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5-%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-r72/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_02/cloud_computing_concept_image_double_exposure_digitally_enhanced_smart_city_concept_cloud_computing_52014e25aa.webp.793a33447c8327eb4b00138a74998abe.webp" /></p>
<p>Интернет вещей (IoT, Internet of Things) в промышленной среде получил отдельное направление — промышленный интернет вещей (IIoT, Industrial Internet of Things). Это комплекс аппаратных и программных решений, который объединяет датчики, оборудование, контроллеры и ИТ-системы в единую цифровую среду с возможностью обмена данными в режиме, близком к реальному времени.</p><p>Внедрение IIoT позволяет:</p><ul><li><p>осуществлять непрерывный мониторинг оборудования;</p></li><li><p>переходить от планового к прогнозирующему обслуживанию (Predictive Maintenance);</p></li><li><p>снижать внеплановые простои;</p></li><li><p>оптимизировать энергопотребление;</p></li><li><p>повышать прозрачность производственных процессов;</p></li><li><p>интегрировать производство с ERP- и MES-системами.</p></li></ul><hr><h2>Что такое IIoT и какие задачи он решает</h2><p><strong>Промышленный интернет вещей (IIoT)</strong> — это совокупность технологий, обеспечивающих подключение оборудования и датчиков к сети для сбора, передачи, обработки и анализа данных.</p><p>Ключевая задача IIoT — переход от реактивной модели эксплуатации оборудования («сломалось — ремонтируем») к проактивной модели управления активами.</p><h3>Проблема традиционного подхода</h3><p>На предприятии с большим количеством сложных машин:</p><ul><li><p>оборудование обслуживается по регламенту, а не по фактическому состоянию;</p></li><li><p>поломки часто возникают внезапно;</p></li><li><p>возникают внеплановые простои;</p></li><li><p>требуется держать избыточный склад запчастей;</p></li><li><p>увеличиваются эксплуатационные расходы (OPEX).</p></li></ul><h3>Как IIoT меняет модель управления</h3><p>Каждая единица оборудования оснащается датчиками, которые фиксируют:</p><ul><li><p>температуру,</p></li><li><p>вибрацию,</p></li><li><p>давление,</p></li><li><p>ток и напряжение,</p></li><li><p>скорость вращения,</p></li><li><p>другие технологические параметры.</p></li></ul><p>Данные передаются на локальные серверы или в облачную инфраструктуру, где автоматически анализируются. При отклонении параметров от нормативных значений система:</p><ul><li><p>уведомляет оператора;</p></li><li><p>формирует заявку на обслуживание;</p></li><li><p>может инициировать автоматическое регулирование.</p></li></ul><p>Это позволяет:</p><ul><li><p>предотвратить аварии,</p></li><li><p>сократить downtime,</p></li><li><p>снизить стоимость владения оборудованием (TCO).</p></li></ul><hr><h2>Прогнозирующее обслуживание: практический пример</h2><p>Один из наиболее показательных кейсов — авиационная промышленность. Производители двигателей устанавливают независимые каналы связи, предназначенные исключительно для передачи телеметрии наземным службам.</p><p>На основе массивов данных строятся аналитические модели, которые прогнозируют:</p><ul><li><p>остаточный ресурс узлов;</p></li><li><p>вероятность отказа;</p></li><li><p>оптимальное время замены деталей.</p></li></ul><p>В промышленности аналогичный подход применяет, например, Siemens. На производственных площадках компания внедряет системы датчиков, которые контролируют:</p><ul><li><p>механические параметры станков,</p></li><li><p>энергопотребление,</p></li><li><p>режимы загрузки линий.</p></li></ul><p>Если линия простаивает, система автоматически снижает энергопотребление, что напрямую сокращает операционные затраты.</p><hr><h2>История развития IoT и становление IIoT</h2><p>Концептуальные предпосылки IoT появились еще в 1980-х годах с развитием сетевых технологий.</p><p>Термин «Internet of Things» в 1999 году предложил Kevin Ashton. Он рассматривал применение RFID-меток для отслеживания товаров в цепочках поставок.</p><p>Активная фаза развития IoT началась в 2010-х годах благодаря:</p><ul><li><p>удешевлению сенсоров;</p></li><li><p>распространению беспроводной связи;</p></li><li><p>развитию облачных платформ;</p></li><li><p>росту вычислительных мощностей.</p></li></ul><p>В промышленности это привело к формированию концепции IIoT и «умных фабрик» (Smart Factory) в рамках парадигмы Industry 4.0.</p><hr><h2>Архитектура IIoT: уровни и требования</h2><p>Типовая архитектура IIoT представляет собой многоуровневую систему.</p><h3>1. Уровень сенсоров и устройств (Device Layer)</h3><p>Физический уровень включает:</p><ul><li><p>датчики,</p></li><li><p>исполнительные механизмы,</p></li><li><p>контроллеры,</p></li><li><p>производственное оборудование.</p></li></ul><p>Критические требования:</p><ul><li><p>устойчивость к температуре, пыли, влажности;</p></li><li><p>виброустойчивость;</p></li><li><p>низкое энергопотребление;</p></li><li><p>промышленная степень защиты (IP, EMI/EMC).</p></li></ul><hr><h3>2. Сеть передачи данных (Communication Layer)</h3><p>Обеспечивает передачу информации между устройствами, edge-узлами и центрами обработки.</p><p>Используются:</p><ul><li><p>Ethernet;</p></li><li><p>Wi-Fi;</p></li><li><p>4G/5G;</p></li><li><p>LPWAN;</p></li><li><p>промышленные протоколы.</p></li></ul><p>Требования:</p><ul><li><p>высокая пропускная способность;</p></li><li><p>минимальная задержка;</p></li><li><p>отказоустойчивость;</p></li><li><p>защищенность передачи данных.</p></li></ul><p>Оборудование промышленного уровня поставляют такие компании, как:</p><ul><li><p>Schneider Electric</p></li><li><p>Allied Telesis</p></li><li><p>Moxa</p></li><li><p>Hirschmann Automation and Control</p></li><li><p>B&amp;R</p></li></ul><p>Выбор оборудования должен учитывать реальные условия эксплуатации — перепады температур, влажность, вибрации и электромагнитные помехи.</p><hr><h3>3. Граничные устройства (Edge Layer)</h3><p>Edge-устройства обрабатывают данные непосредственно на объекте.</p><p>Их задачи:</p><ul><li><p>локальная фильтрация и агрегация данных;</p></li><li><p>анализ в реальном времени;</p></li><li><p>снижение нагрузки на облако;</p></li><li><p>обеспечение автономной работы при потере связи.</p></li></ul><p>Обычно поддерживаются протоколы MQTT, OPC UA и другие индустриальные стандарты.</p><p>Важно разграничивать:</p><ul><li><p><strong>Edge</strong> — анализ и предварительная обработка данных;</p></li><li><p><strong>PLC/контроллеры</strong> — управление оборудованием.</p></li></ul><hr><h3>4. Уровень управления (Control Layer)</h3><p>Включает:</p><ul><li><p>PLC;</p></li><li><p>SCADA-системы;</p></li><li><p>MES;</p></li><li><p>интеграцию с ERP.</p></li></ul><p>Системы класса SCADA обеспечивают диспетчеризацию и визуализацию процессов, а MES — управление производственными операциями.</p><hr><h3>5. Обработка данных и аналитика (Data &amp; Analytics Layer)</h3><p>На этом уровне используются:</p><ul><li><p>Big Data;</p></li><li><p>машинное обучение;</p></li><li><p>предиктивная аналитика;</p></li><li><p>цифровые двойники (Digital Twin).</p></li></ul><p>Цель — выявление закономерностей, оптимизация процессов и стратегическое управление активами.</p><hr><h2>Применение IIoT в различных отраслях</h2><h3>Промышленность и машиностроение</h3><ul><li><p>мониторинг станков;</p></li><li><p>оптимизация производственных линий;</p></li><li><p>снижение брака;</p></li><li><p>предиктивное обслуживание.</p></li></ul><p>Особое значение имеют edge-вычисления и отказоустойчивые сети.</p><hr><h3>Логистика и транспорт</h3><ul><li><p>отслеживание транспорта в реальном времени;</p></li><li><p>контроль состояния грузов;</p></li><li><p>автоматическое пополнение запасов.</p></li></ul><p>Ключевую роль играют мобильные сети и устойчивость связи при перемещении между зонами покрытия.</p><hr><h3>Энергетика</h3><ul><li><p>мониторинг генерации и распределения;</p></li><li><p>контроль подстанций;</p></li><li><p>управление распределенными энергоресурсами;</p></li><li><p>интеграция ВИЭ.</p></li></ul><p>Системы должны быть масштабируемыми и устойчивыми к тяжелым условиям эксплуатации.</p><hr><h3>Сельское хозяйство</h3><ul><li><p>мониторинг влажности почвы;</p></li><li><p>контроль микроклимата;</p></li><li><p>управление сельхозтехникой;</p></li><li><p>автоматизация полива.</p></li></ul><p>Основной акцент — энергоэффективные беспроводные технологии и автономность.</p><hr><h2>Преимущества внедрения IIoT</h2><p>Внедрение промышленного интернета вещей позволяет:</p><ul><li><p>снизить внеплановые простои;</p></li><li><p>сократить издержки на обслуживание;</p></li><li><p>уменьшить энергопотребление;</p></li><li><p>повысить прозрачность процессов;</p></li><li><p>улучшить управляемость цепочек поставок;</p></li><li><p>повысить общую операционную эффективность предприятия.</p></li></ul><p>IIoT становится фундаментом цифровой трансформации промышленности и ключевым элементом конкурентоспособности в условиях Industry 4.0.</p>]]></description><guid isPermaLink="false">72</guid><pubDate>Fri, 27 Feb 2026 20:18:10 +0000</pubDate></item><item><title>&#x41A;&#x43E;&#x43D;&#x434;&#x435;&#x43D;&#x441;&#x430;&#x442;&#x43E;&#x440;&#x44B;: &#x432;&#x438;&#x434;&#x44B;, &#x43D;&#x435;&#x438;&#x441;&#x43F;&#x440;&#x430;&#x432;&#x43D;&#x43E;&#x441;&#x442;&#x438; &#x438; &#x43F;&#x440;&#x43E;&#x432;&#x435;&#x440;&#x43A;&#x430; &#x43C;&#x443;&#x43B;&#x44C;&#x442;&#x438;&#x43C;&#x435;&#x442;&#x440;&#x43E;&#x43C;</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/%D0%BA%D0%BE%D0%BD%D0%B4%D0%B5%D0%BD%D1%81%D0%B0%D1%82%D0%BE%D1%80%D1%8B-%D0%B2%D0%B8%D0%B4%D1%8B-%D0%BD%D0%B5%D0%B8%D1%81%D0%BF%D1%80%D0%B0%D0%B2%D0%BD%D0%BE%D1%81%D1%82%D0%B8-%D0%B8-%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0-%D0%BC%D1%83%D0%BB%D1%8C%D1%82%D0%B8%D0%BC%D0%B5%D1%82%D1%80%D0%BE%D0%BC-r75/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_02/kak-proverit-kondensatory-multimetrom-3.jpg.3447d0696ffd9d97f79cc99fccba1e33.jpg" /></p>
<p>Конденсаторы — обязательные компоненты большинства электронных устройств: от бытовой техники до промышленной автоматики. Они выполняют функции накопления и отдачи энергии, фильтрации помех, формирования частоты и сглаживания пульсаций напряжения.</p><p>Во многих случаях отказ оборудования связан именно с неисправностью конденсатора. Поэтому важно понимать, как корректно проверить его работоспособность с помощью мультиметра и какие дефекты встречаются чаще всего.</p><hr><h1>Что такое конденсатор и как он работает</h1><p>Конденсатор — это пассивный электронный компонент, состоящий из двух проводящих обкладок, разделённых диэлектриком. Его ключевые параметры:</p><ul><li><p><strong>Ёмкость (Ф, мкФ, нФ, пФ)</strong> — способность накапливать заряд</p></li><li><p><strong>Рабочее напряжение (В)</strong> — максимально допустимое напряжение</p></li><li><p><strong>Тип диэлектрика</strong> — влияет на стабильность и область применения</p></li><li><p><strong>Полярность</strong> — у некоторых типов строго соблюдается</p></li></ul><hr><h1>Основные виды конденсаторов</h1><h2>1. По назначению</h2><h3>Высоковольтные</h3><p>Используются в силовой электронике и высоковольтном оборудовании.<br>Бывают керамические, масляные, вакуумные. Доступ к ним часто ограничен требованиями безопасности.</p><h3>Пусковые</h3><p>Применяются в электродвигателях для увеличения пускового момента.</p><h3>Подстроечные (переменные)</h3><p>Позволяют изменять ёмкость регулировкой положения подвижной пластины.</p><h3>Импульсные</h3><p>Формируют короткие пики напряжения для передачи сигналов.</p><h3>Помехоподавляющие (X и Y-класса)</h3><p>Стабилизируют работу чувствительных устройств, подавляя электромагнитные помехи.</p><hr><h2>2. По типу диэлектрика</h2><ul><li><p>Бумажные</p></li><li><p>Плёночные</p></li><li><p>Керамические</p></li><li><p>Слюдяные</p></li><li><p>Электролитические (алюминиевые, танталовые)</p></li><li><p>Стеклокерамические</p></li><li><p>Оксидно-полупроводниковые</p></li></ul><hr><h2>3. По полярности</h2><h3>Полярные</h3><ul><li><p>Электролитические</p></li><li><p>Танталовые<br>Имеют маркировку минусового вывода. Нарушение полярности приводит к выходу из строя.</p></li></ul><h3>Неполярные</h3><ul><li><p>Керамические</p></li><li><p>Плёночные</p></li><li><p>Слюдяные</p></li></ul><p>Не требуют соблюдения полярности при подключении.</p><hr><h1>Типичные неисправности конденсаторов</h1><h2>1. Короткое замыкание (КЗ)</h2><p>Причины:</p><ul><li><p>пробой изоляции</p></li><li><p>превышение рабочего напряжения</p></li><li><p>перегрев</p></li><li><p>механические повреждения</p></li></ul><p>Симптом: сопротивление близко к нулю.</p><hr><h2>2. Обрыв</h2><p>Потеря электрического контакта с обкладкой.<br>Ёмкость становится равной нулю.</p><p>Часто встречается:</p><ul><li><p>в электролитических</p></li><li><p>в помехоподавляющих Y-конденсаторах (конструктивно защищены от КЗ)</p></li></ul><hr><h2>3. Потеря ёмкости</h2><p>Особенно характерна для электролитических конденсаторов из-за высыхания электролита.</p><hr><h2>4. Повышенная утечка</h2><p>Элемент не удерживает заряд. Часто наблюдается у:</p><ul><li><p>электролитических</p></li><li><p>танталовых</p></li></ul><hr><h1>Подготовка к проверке мультиметром</h1><p>Перед измерениями необходимо:</p><ol><li><p><strong>Отключить устройство от сети</strong></p></li><li><p><strong>Разрядить конденсатор</strong><br>Замкнуть выводы металлическим предметом (для мощных — через резистор)</p></li><li><p><strong>Осмотреть корпус</strong></p><ul><li><p>вздутие</p></li><li><p>потёки</p></li><li><p>трещины</p></li><li><p>обугливание</p></li></ul></li><li><p><strong>Определить полярность</strong></p></li><li><p><strong>Выпаять элемент</strong></p></li></ol><p>Проверка на плате часто даёт некорректные результаты из-за влияния других компонентов.</p><hr><h1>Как проверить полярный (электролитический) конденсатор</h1><h2>Проверка на КЗ или обрыв</h2><ol><li><p>Разрядить элемент</p></li><li><p>Установить мультиметр в режим:</p><ul><li><p>«прозвонка»</p></li><li><p>«сопротивление»</p></li></ul></li><li><p>Подключить:</p><ul><li><p>«+» к плюсу</p></li><li><p>«−» к минусу</p></li></ul></li></ol><h3>Интерпретация результатов:</h3><div class="tmiRichText__table-wrapper"><table style="width: 577px;"><colgroup><col style="width:357px;"><col style="width:220px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Показание</p></th><th colspan="1" rowspan="1"><p>Диагноз</p></th></tr><tr><td colspan="1" rowspan="1"><p>0 Ом</p></td><td colspan="1" rowspan="1"><p>Короткое замыкание</p></td></tr><tr><td colspan="1" rowspan="1"><p>1 без изменений</p></td><td colspan="1" rowspan="1"><p>Обрыв</p></td></tr><tr><td colspan="1" rowspan="1"><p>Сопротивление растёт</p></td><td colspan="1" rowspan="1"><p>Исправен</p></td></tr></tbody></table></div><p>Рост сопротивления означает заряд конденсатора от батареи мультиметра.</p><hr><h1>Как проверить неполярный конденсатор</h1><ol><li><p>Установить режим измерения сопротивления (МОм)</p></li><li><p>Подключить щупы без соблюдения полярности</p></li></ol><h3>Результат:</h3><ul><li><p>Более 2 МОм → исправен</p></li><li><p>Менее 2 МОм → вероятна неисправность</p></li></ul><hr><h1>Проверка на короткое замыкание</h1><h2>В режиме прозвонки</h2><p>Если мультиметр постоянно издаёт звуковой сигнал — присутствует КЗ.</p><h2>В режиме сопротивления</h2><p>Сопротивление близкое к 0 Ом — короткое замыкание.</p><hr><h1>Проверка на обрыв</h1><h2>Метод 1 — прозвонка</h2><p>Кратковременный щелчок → исправен<br>Отсутствие реакции → возможен обрыв</p><h2>Метод 2 — измерение сопротивления</h2><p>На максимальном диапазоне сопротивление должно плавно увеличиваться.</p><hr><h1>Проверка остаточного напряжения</h1><p>Самый чувствительный способ:</p><ol><li><p>В режиме сопротивления зарядить конденсатор 2–3 секунды</p></li><li><p>Переключить мультиметр на измерение постоянного напряжения</p></li><li><p>Подключить снова</p></li></ol><p>Если прибор показывает остаточное напряжение — элемент исправен.</p><p>Подходит для большинства типов, кроме сверхмалых ёмкостей (&lt;470 пФ).</p><hr><h1>Как измерить ёмкость мультиметром</h1><p>Если прибор имеет функцию измерения ёмкости:</p><ol><li><p>Выбрать режим Cx</p></li><li><p>Установить диапазон</p></li><li><p>Подключить выводы</p></li><li><p>Сравнить значение с номиналом</p></li></ol><p>Допустимое отклонение обычно:</p><ul><li><p>±5–20% для большинства типов</p></li><li><p>±10% для электролитических</p></li></ul><hr><h1>Проверка пускового конденсатора</h1><ol><li><p>Обесточить оборудование</p></li><li><p>Выпаять и разрядить элемент</p></li><li><p>Измерить ёмкость</p></li><li><p>Сравнить с номиналом</p></li></ol><p>Если расхождение превышает допустимое — требуется замена.</p><hr><h1>Проверка керамического конденсатора</h1><p>Керамические — неполярные.</p><ol><li><p>Режим измерения сопротивления</p></li><li><p>Предел — МОм</p></li><li><p>Показание &gt;2 МОм → исправен</p></li></ol><p>Для точной оценки ёмкости нужен специализированный прибор.</p><hr><h1>Можно ли проверять без выпаивания?</h1><p>В большинстве случаев — нет.</p><p>Причины:</p><ul><li><p>параллельные цепи искажают показания</p></li><li><p>диоды и транзисторы могут симулировать КЗ</p></li><li><p>измеряется суммарная ёмкость</p></li></ul><p>Допустимо проверять:</p><ul><li><p>электролитические &gt;1 мкФ</p></li><li><p>только на КЗ или обрыв</p></li></ul><p>Для точной диагностики рекомендуется выпаивание.</p><hr><h1>Когда лучше заменить, чем проверять?</h1><p>Если наблюдаются:</p><ul><li><p>вздутие</p></li><li><p>утечка электролита</p></li><li><p>сильный перегрев</p></li><li><p>значительное отклонение ёмкости</p></li></ul><p>В таких случаях замена быстрее и надёжнее ремонта.</p><hr><h1>Вывод</h1><p>Проверка конденсатора мультиметром — эффективный способ диагностики короткого замыкания, обрыва и грубых отклонений ёмкости.</p><p>Для точных измерений малых ёмкостей и оценки ESR требуется специализированное оборудование.</p>]]></description><guid isPermaLink="false">75</guid><pubDate>Fri, 27 Feb 2026 20:23:48 +0000</pubDate></item><item><title>&#x41F;&#x440;&#x438;&#x43D;&#x446;&#x438;&#x43F; &#x440;&#x430;&#x431;&#x43E;&#x442;&#x44B; LiDAR (&#x43B;&#x438;&#x434;&#x430;&#x440;&#x430;)</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/%D0%BF%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF-%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D1%8B-lidar-%D0%BB%D0%B8%D0%B4%D0%B0%D1%80%D0%B0-r78/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_02/4l61eza8b31cxfwfpq648m8d810sulrw.jpg.ae17a57e74dfd4dc367242b09fd12fd0.jpg" /></p>
<p><strong>LiDAR (Light Detection and Ranging)</strong> — это технология измерения расстояния с помощью лазерного излучения. Метод основан на регистрации времени пролёта лазерного импульса (TOF, Time of Flight) от источника до объекта и обратно к приёмнику.</p><p>По сути, лидар — это высокоточный лазерный дальномер, формирующий <strong>трёхмерную модель окружающего пространства</strong> в виде облака точек.</p><hr><h2>История технологии</h2><p>Концепция лидара была предложена в 1930 году британским физиком Edward Hutchinson Synge, который рассматривал возможность исследования атмосферы с помощью мощных световых источников.</p><p>Сегодня LiDAR широко применяется в:</p><ul><li><p>метеорологии,</p></li><li><p>геодезии,</p></li><li><p>автономном транспорте,</p></li><li><p>сельском хозяйстве,</p></li><li><p>робототехнике,</p></li><li><p>космических исследованиях.</p></li></ul><hr><h2>Как работает LiDAR</h2><p>Принцип работы включает несколько этапов:</p><ol><li><p><strong>Генерация импульса</strong><br>Лазерный излучатель (часто 905 нм или 1550 нм) формирует короткий импульс света.</p></li><li><p><strong>Отражение от объекта</strong><br>Луч достигает поверхности (здание, автомобиль, дерево, человек) и отражается.</p></li><li><p><strong>Регистрация сигнала</strong><br>Отражённый свет фиксируется фотоприёмником.</p></li><li><p><strong>Расчёт расстояния</strong><br>Система измеряет время пролёта импульса и вычисляет расстояние по формуле:</p></li></ol><p>D=c⋅t2D = \frac{c \cdot t}{2}D=2c⋅t</p><p>где:</p><ul><li><p><em>D</em> — расстояние,</p></li><li><p><em>c</em> — скорость света,</p></li><li><p><em>t</em> — время между отправкой и приёмом импульса.</p></li></ul><ol start="5"><li><p><strong>Формирование облака точек</strong><br>Миллионы измерений объединяются в трёхмерную карту сцены.</p></li></ol><hr><h2>Основные компоненты лидара</h2><p>Типовая система LiDAR включает:</p><ul><li><p>лазерный модуль (часто VCSEL);</p></li><li><p>оптическую систему (линзы, фильтры);</p></li><li><p>фотоприёмник (SiPM или лавинный фотодиод);</p></li><li><p>АЦП (аналогово-цифровой преобразователь);</p></li><li><p>вычислительный модуль (FPGA, AI-процессор);</p></li><li><p>систему сканирования.</p></li></ul><hr><h2>Типы лидаров</h2><h3>1. Механические лидары (360°)</h3><p>Имеют вращающуюся платформу с излучателем и приёмником.</p><p><strong>Особенности:</strong></p><ul><li><p>горизонтальный обзор 360°</p></li><li><p>высокая точность</p></li><li><p>сложная механика</p></li><li><p>более высокая стоимость</p></li></ul><p>Применяются в автономных автомобилях и картографировании.</p><hr><h3>2. Твердотельные лидары</h3><p>Не содержат вращающихся элементов. Сканирование выполняется с помощью MEMS-зеркал или фазированных решёток.</p><p><strong>Преимущества:</strong></p><ul><li><p>компактность</p></li><li><p>устойчивость к вибрациям</p></li><li><p>меньшая стоимость</p></li><li><p>высокая надёжность</p></li></ul><p>Широко применяются в ADAS, дронах и робототехнике.</p><hr><h2>Методы измерения скорости</h2><p>LiDAR может измерять скорость объектов:</p><ul><li><p><strong>Доплеровский метод</strong> — по изменению частоты отражённого сигнала.</p></li><li><p><strong>Последовательные измерения</strong> — по изменению расстояния во времени.</p></li></ul><p>Используется для определения скорости:</p><ul><li><p>ветра,</p></li><li><p>транспортных средств,</p></li><li><p>движущихся объектов.</p></li></ul><hr><h1>Области применения LiDAR</h1><h2>1. Автономный транспорт и ADAS</h2><p>LiDAR — ключевой сенсор в системах автопилотирования.</p><p>Пример промышленного внедрения — разработки компании Waymo и беспилотные проекты Яндекс.</p><p><strong>Функции:</strong></p><ul><li><p>обнаружение препятствий</p></li><li><p>распознавание пешеходов</p></li><li><p>адаптивный круиз-контроль</p></li><li><p>экстренное торможение</p></li><li><p>удержание полосы</p></li></ul><hr><h2>2. Геодезия и картография</h2><p>Аэро-лидары устанавливаются на самолёты и БПЛА.</p><p>Используются совместно с:</p><ul><li><p>GPS</p></li><li><p>инерциальными системами (IMU)</p></li></ul><p>Результат — цифровая модель рельефа (ЦМР).</p><hr><h2>3. Метеорология и атмосферные исследования</h2><p>Позволяют измерять:</p><ul><li><p>аэрозольную нагрузку</p></li><li><p>концентрацию метана</p></li><li><p>параметры облаков</p></li><li><p>скорость ветра</p></li><li><p>интенсивность осадков</p></li></ul><hr><h2>4. Сельское хозяйство</h2><p>Применение:</p><ul><li><p>построение топографии полей</p></li><li><p>анализ зон урожайности</p></li><li><p>обнаружение сорняков (с применением ML)</p></li><li><p>навигация сельхозтехники без GPS</p></li><li><p>мониторинг виноградников и садов</p></li></ul><hr><h2>5. Археология</h2><p>LiDAR позволяет выявлять объекты под густым лесным покровом.</p><p>Пример — обнаружение древних сооружений в регионе Ла-Москития (Гондурас) и исследование древнего города Махендрапарвата.</p><hr><h2>6. Космос</h2><p>Лазерная альтиметрия применяется для картографирования планет.</p><p>Пример — марсианский альтиметр MOLA на орбитальном аппарате NASA.</p><p>Также лидар используется при сближении космических аппаратов и посадке на поверхность.</p><hr><h1>Российский рынок лидаров</h1><h3>Производство</h3><p>В 2025 году компания Радар ММС запустила производство модулей воздушно-лазерного сканирования для автомобилей и дронов.</p><h3>Исследования</h3><p>В 2022 году МТУСИ совместно с ИОФ РАН протестировали мобильный лидар в рамках научной установки беспроводной подводной лазерной связи.</p><hr><h1>Мировой рынок LiDAR</h1><h2>Автомобильный сегмент</h2><p>По данным Fortune Business Insights:</p><ul><li><p>2024 год — $3,72 млрд</p></li><li><p>2026 год — $4,16 млрд</p></li><li><p>прогноз к 2032 году — $9,54 млрд</p></li><li><p>CAGR — 12,6%</p></li></ul><p>Крупные игроки:</p><ul><li><p>Luminar Technologies</p></li><li><p>Valeo S.A.</p></li><li><p>Innoviz Technologies</p></li><li><p>Continental</p></li><li><p>Aeva Technologies</p></li><li><p>Ouster</p></li><li><p>Hesai Technology</p></li><li><p>RoboSense</p></li></ul><hr><h2>Рынок лидаров для смартфонов</h2><ul><li><p>2023 год — $2,03 млрд</p></li><li><p>2024 год — $2,42 млрд</p></li><li><p>прогноз к 2032 году — $10 млрд</p></li><li><p>CAGR — 19,38%</p></li></ul><p>Рост обеспечен развитием AR/VR и мобильной съёмки.</p><hr><h1>Преимущества и ограничения технологии</h1><h2>Преимущества</h2><ul><li><p>высокая точность (до сантиметров)</p></li><li><p>независимость от освещённости</p></li><li><p>формирование 3D-моделей в реальном времени</p></li><li><p>высокая дальность (до 300+ м)</p></li></ul><h2>Ограничения</h2><ul><li><p>чувствительность к погодным условиям</p></li><li><p>высокая стоимость (для высокодальних систем)</p></li><li><p>необходимость обработки больших массивов данных</p></li></ul><hr><h1>Перспективы развития</h1><p>Тренды отрасли:</p><ul><li><p>переход от механических к твердотельным решениям</p></li><li><p>интеграция с AI</p></li><li><p>снижение стоимости модулей</p></li><li><p>рост применения в умных городах</p></li><li><p>развитие FMCW-лидаров</p></li><li><p>интеграция в потребительскую электронику</p></li></ul>]]></description><guid isPermaLink="false">78</guid><pubDate>Fri, 27 Feb 2026 20:31:32 +0000</pubDate></item><item><title>PSRAM &#x432; ESP32: &#x430;&#x440;&#x445;&#x438;&#x442;&#x435;&#x43A;&#x442;&#x443;&#x440;&#x430;, &#x43E;&#x433;&#x440;&#x430;&#x43D;&#x438;&#x447;&#x435;&#x43D;&#x438;&#x44F; &#x438; &#x43F;&#x440;&#x430;&#x43A;&#x442;&#x438;&#x447;&#x435;&#x441;&#x43A;&#x43E;&#x435; &#x438;&#x441;&#x43F;&#x43E;&#x43B;&#x44C;&#x437;&#x43E;&#x432;&#x430;&#x43D;&#x438;&#x435; &#x432; ESP-IDF</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/psram-%D0%B2-esp32-%D0%B0%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80%D0%B0-%D0%BE%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%87%D0%B5%D0%BD%D0%B8%D1%8F-%D0%B8-%D0%BF%D1%80%D0%B0%D0%BA%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%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%B2-esp-idf-r81/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_02/doc-psram-focus-in-esp32-wrover-module.jpg.a9162f7199092c7b7d64c483d6f9c9eb.jpg" /></p>
<h2>1. Зачем нужна PSRAM в ESP32</h2><p>Микроконтроллеры семейства ESP32 имеют сотни килобайт встроенной SRAM, размещённой на том же кристалле, что и CPU, периферия и контроллеры. Для задач вроде:</p><ul><li><p>обработки графики (LVGL, дисплеи),</p></li><li><p>буферизации аудио,</p></li><li><p>работы с большими JSON,</p></li><li><p>сетевых стеков,</p></li><li><p>ML-моделей,</p></li></ul><p>этого объёма часто недостаточно.</p><p>Поэтому архитектура предусматривает подключение внешней <strong>PSRAM (Pseudo-Static RAM)</strong> — псевдо-статической оперативной памяти, которая расширяет доступный heap.</p><hr><h1>2. Что такое PSRAM</h1><h2>2.1 Терминология</h2><p>В документации Espressif используются как взаимозаменяемые:</p><ul><li><p>PSRAM</p></li><li><p>SPI RAM</p></li><li><p>SPIRAM</p></li></ul><p>Во всех случаях речь идёт об одном типе внешней памяти.</p><hr><h2>2.2 Почему «Pseudo-Static»</h2><p>PSRAM сочетает в себе:</p><h3>Внутренняя структура: DRAM</h3><ul><li><p>Ячейки динамические (как в DRAM)</p></li><li><p>Требуется refresh</p></li></ul><h3>Внешний интерфейс: как у SRAM</h3><ul><li><p>Встроенная логика регенерации</p></li><li><p>CPU работает с ней как с обычной RAM</p></li><li><p>Внешний контроллер refresh не нужен</p></li></ul><p>Именно поэтому — Pseudo-Static.</p><hr><h1>3. Аппаратная архитектура доступа к PSRAM</h1><h2>3.1 Подключение</h2><p>PSRAM подключается:</p><ul><li><p>через SPI / QSPI / OPI</p></li><li><p>по той же шине, что и Flash</p></li><li><p>с отдельной линией Chip Select</p></li></ul><p>В модулях типа WROVER чип PSRAM обычно установлен внутри металлического экрана.</p><p>В новых сериях возможна:</p><ul><li><p>in-package PSRAM (в одном корпусе с SoC)</p></li><li><p>но архитектурно она остаётся «внешней»</p></li></ul><hr><h2>3.2 Как CPU обращается к PSRAM</h2><p>Доступ НЕ прямой.</p><p>Схема:</p><p>CPU → Cache → MMU → SPI → PSRAM</p><h3>Алгоритм:</h3><ol><li><p>CPU обращается к виртуальному адресу.</p></li><li><p>MMU отображает его в физический адрес PSRAM.</p></li><li><p>Cache:</p><ul><li><p>cache hit → мгновенно</p></li><li><p>cache miss → чтение через SPI</p></li></ul></li></ol><p>В новых сериях используется write-back cache.</p><hr><h2>3.3 Главное ограничение</h2><p>PSRAM <strong>всегда медленнее внутренней SRAM</strong>, потому что:</p><ul><li><p>последовательная шина</p></li><li><p>работа через кэш</p></li><li><p>латентность SPI</p></li></ul><p>Следствие: стек задач и DMA-буферы по умолчанию остаются во внутренней памяти.</p><hr><h1>4. Важные аппаратные ограничения</h1><h2>4.1 Напряжение</h2><p>PSRAM бывает:</p><ul><li><p>1.8 В</p></li><li><p>3.3 В</p></li></ul><p>Оно должно совпадать с Flash.</p><p>Выбор задаётся:</p><ul><li><p>strapping pins</p></li><li><p>eFuse</p></li></ul><p>Ошибка может:</p><ul><li><p>отключить память</p></li><li><p>повредить чип</p></li></ul><hr><h2>4.2 DMA</h2><p>На старых ESP32:</p><ul><li><p>DMA напрямую с PSRAM невозможен</p></li></ul><p>На новых сериях:</p><ul><li><p>возможен</p></li><li><p>но требует контроля когерентности кэша</p></li></ul><hr><h2>4.3 Стек FreeRTOS</h2><p>По умолчанию:</p><ul><li><p>стек задач → внутренняя RAM</p></li></ul><p>Технически можно разместить в PSRAM (через xTaskCreateStatic), но это не рекомендуется.</p><hr><h1>5. Особенности разных серий ESP32</h1><h2>5.1 Классический ESP32</h2><ul><li><p>QSPI (4 линии)</p></li><li><p>максимум 4 МБ отображаемого окна</p></li><li><p>кэш 32 КБ на ядро</p></li><li><p>при 8 МБ требуется Himem API (bank switching)</p></li></ul><p>Поддерживаются режимы MMU:</p><ul><li><p>Normal</p></li><li><p>Low-High</p></li><li><p>Even-Odd</p></li></ul><p>DMA напрямую не работает.</p><hr><h2>5.2 ESP32-S2</h2><ul><li><p>независимые ICache и DCache</p></li><li><p>до 10.5 МБ виртуального адресного пространства</p></li><li><p>возможно выполнение кода из PSRAM</p></li><li><p>настраиваемый размер кэша</p></li></ul><hr><h2>5.3 ESP32-S3</h2><ul><li><p>Quad / Octal SPI</p></li><li><p>поддержка XTS-AES</p></li><li><p>до 32 МБ отображаемого пространства</p></li><li><p>общий кэш для двух ядер</p></li></ul><p>Octal PSRAM заметно быстрее Quad.</p><hr><h2>5.4 ESP32-C5 и ESP32-C61</h2><ul><li><p>поддержка PSRAM есть</p></li><li><p>до 32 МБ отображения</p></li><li><p>доступ через кэш и GDMA</p></li></ul><p>Ранние C-серии (C2, C3, C6) PSRAM не поддерживают.</p><hr><h2>5.5 ESP32-P4</h2><p>Наиболее производительная архитектура:</p><ul><li><p>до 64 МБ PSRAM</p></li><li><p>интерфейсы OPI и HPI</p></li><li><p>двухуровневый кэш (L1 + L2)</p></li><li><p>частоты до 200 МГц</p></li><li><p>аппаратное шифрование</p></li></ul><hr><h1>6. Использование PSRAM в ESP-IDF</h1><p>Основной компонент: <strong>esp_psram</strong></p><p>В новых версиях ESP-IDF его нужно явно добавить в зависимости:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>idf_component_register(
    SRCS "main.c"
    INCLUDE_DIRS "."
    REQUIRES esp_psram
)</code></pre><p>После этого появляется меню:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Component config → ESP PSRAM</code></pre><hr><h1>7. Ключевые параметры menuconfig</h1><h2>7.1 CONFIG_SPIRAM_BOOT_INIT</h2><p>Автоматическая инициализация при старте.</p><p>Рекомендуется включать.</p><hr><h2>7.2 CONFIG_SPIRAM_IGNORE_NOTFOUND</h2><p>Позволяет загружаться без PSRAM.</p><p>Полезно для универсальных прошивок.</p><hr><h2>7.3 CONFIG_SPIRAM_MEMTEST</h2><p>Тест памяти при старте.</p><p>≈ 1 секунда на 4 МБ.</p><hr><h2>7.4 CONFIG_SPIRAM_USE</h2><p>Определяет стратегию интеграции:</p><h3>1) MEMMAP</h3><p>Просто отображение в адресное пространство.<br> Вы сами управляете памятью.</p><h3>2) CAPS_ALLOC</h3><p>Использование через:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>heap_caps_malloc(size, MALLOC_CAP_SPIRAM);</code></pre><p>Наиболее управляемый способ.</p><h3>3) MALLOC (по умолчанию)</h3><p>PSRAM объединяется с общей кучей.</p><p>malloc() автоматически выбирает регион.</p><hr><h2>7.5 CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL</h2><p>Порог (по умолчанию 16 КБ):</p><ul><li><p>меньше → внутренняя RAM</p></li><li><p>больше → PSRAM</p></li></ul><hr><h2>7.6 CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL</h2><p>Резервирует внутреннюю память под:</p><ul><li><p>DMA</p></li><li><p>стеки задач</p></li><li><p>критические участки</p></li></ul><p>Очень важная опция для стабильности.</p><hr><h2>7.7 Перенос кода и данных</h2><ul><li><p>CONFIG_SPIRAM_FETCH_INSTRUCTIONS</p></li><li><p>CONFIG_SPIRAM_RODATA</p></li><li><p>CONFIG_SPIRAM_XIP_FROM_PSRAM</p></li></ul><p>Позволяют:</p><ul><li><p>выполнять код из PSRAM</p></li><li><p>разгрузить Flash</p></li><li><p>ускорить систему (в Octal-режиме)</p></li></ul><hr><h1>8. API esp_psram</h1><p>Функции:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>esp_psram_init();
esp_psram_is_initialized();
esp_psram_get_size();</code></pre><p>Практически используется только:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>esp_psram_get_size();</code></pre><hr><h1>9. Выделение памяти</h1><h2>9.1 Автоматический режим</h2><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>void *ptr = malloc(size);</code></pre><p>Работает при CONFIG_SPIRAM_USE_MALLOC.</p><hr><h2>9.2 Явное выделение в PSRAM</h2><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>void *ptr = heap_caps_malloc(size, MALLOC_CAP_SPIRAM);</code></pre><p>Освобождение — обычный free().</p><hr><h2>9.3 Когда использовать PSRAM</h2><p>Подходит для:</p><ul><li><p>больших JSON</p></li><li><p>framebuffer</p></li><li><p>аудиобуферов</p></li><li><p>кешей</p></li><li><p>временных массивов</p></li></ul><p>Не подходит для:</p><ul><li><p>DMA</p></li><li><p>ISR</p></li><li><p>стека задач</p></li><li><p>структур с высокой частотой доступа</p></li></ul><hr><h1>10. Практическая стратегия</h1><p>Оптимальный подход для production:</p><ol><li><p>CONFIG_SPIRAM_USE_CAPS_ALLOC</p></li><li><p>CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL</p></li><li><p>WiFi/LWIP → в PSRAM</p></li><li><p>Стек, DMA → внутренняя RAM</p></li></ol><p>Это даёт предсказуемую производительность.</p><hr><h1>11. Итог</h1><p>PSRAM — это:</p><ul><li><p>дешёвый способ расширить RAM</p></li><li><p>возможность работать с графикой и ML</p></li><li><p>разгрузка внутренней памяти</p></li></ul><p>Но:</p><p>– всегда медленнее внутренней SRAM<br> – требует грамотной конфигурации<br> – может вызывать проблемы когерентности</p><p>Для серьёзных проектов рекомендуется:</p><ul><li><p>ESP-IDF</p></li><li><p>ручное управление аллокацией</p></li><li><p>контроль DMA-буферов</p></li><li><p>резерв внутренней памяти</p></li></ul>]]></description><guid isPermaLink="false">81</guid><pubDate>Fri, 27 Feb 2026 20:38:36 +0000</pubDate></item><item><title>STM32 &#x441; &#x43D;&#x443;&#x43B;&#x44F;: &#x43F;&#x440;&#x430;&#x43A;&#x442;&#x438;&#x447;&#x435;&#x441;&#x43A;&#x43E;&#x435; &#x440;&#x443;&#x43A;&#x43E;&#x432;&#x43E;&#x434;&#x441;&#x442;&#x432;&#x43E; &#x434;&#x43B;&#x44F; &#x442;&#x435;&#x445;, &#x43A;&#x442;&#x43E; &#x432;&#x44B;&#x440;&#x43E;&#x441; &#x438;&#x437; Arduino</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/stm32-%D1%81-%D0%BD%D1%83%D0%BB%D1%8F-%D0%BF%D1%80%D0%B0%D0%BA%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5-%D1%80%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%B4%D0%BB%D1%8F-%D1%82%D0%B5%D1%85-%D0%BA%D1%82%D0%BE-%D0%B2%D1%8B%D1%80%D0%BE%D1%81-%D0%B8%D0%B7-arduino-r98/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/stm32-kurs.jpg.08de2a71f685acf36759a19cd2ef079a.jpg" /></p>
<h2>Почему STM32, а не продолжать на Arduino</h2><p>Arduino — отличный старт. Но в какой-то момент вы упираетесь в потолок: скорость 16 МГц не хватает, Flash/RAM заканчивается, нужны возможности которых у AVR нет — несколько UART, USB Device, Ethernet MAC, криптоускоритель, DSP-инструкции.</p><p>STM32 — это семейство 32-битных микроконтроллеров от STMicroelectronics на базе ядер ARM Cortex-M. Характеристики даже бюджетного STM32F103C8T6 ("Blue Pill"):</p><div class="tmiRichText__table-wrapper"><table style="width: 962px;"><colgroup><col style="width:238px;"><col style="width:284px;"><col style="width:440px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Параметр</p></th><th colspan="1" rowspan="1"><p>Arduino Uno (ATmega328P)</p></th><th colspan="1" rowspan="1"><p>STM32F103C8T6</p></th></tr><tr><td colspan="1" rowspan="1"><p>Ядро</p></td><td colspan="1" rowspan="1"><p>AVR 8-бит</p></td><td colspan="1" rowspan="1"><p>ARM Cortex-M3 32-бит</p></td></tr><tr><td colspan="1" rowspan="1"><p>Тактовая частота</p></td><td colspan="1" rowspan="1"><p>16 МГц</p></td><td colspan="1" rowspan="1"><p>72 МГц</p></td></tr><tr><td colspan="1" rowspan="1"><p>Flash</p></td><td colspan="1" rowspan="1"><p>32 КБ</p></td><td colspan="1" rowspan="1"><p>64 КБ</p></td></tr><tr><td colspan="1" rowspan="1"><p>RAM</p></td><td colspan="1" rowspan="1"><p>2 КБ</p></td><td colspan="1" rowspan="1"><p>20 КБ</p></td></tr><tr><td colspan="1" rowspan="1"><p>GPIO</p></td><td colspan="1" rowspan="1"><p>23</p></td><td colspan="1" rowspan="1"><p>37</p></td></tr><tr><td colspan="1" rowspan="1"><p>ADC</p></td><td colspan="1" rowspan="1"><p>6 × 10-бит</p></td><td colspan="1" rowspan="1"><p>10 × 12-бит</p></td></tr><tr><td colspan="1" rowspan="1"><p>Таймеры</p></td><td colspan="1" rowspan="1"><p>3</p></td><td colspan="1" rowspan="1"><p>7</p></td></tr><tr><td colspan="1" rowspan="1"><p>SPI / I2C / UART</p></td><td colspan="1" rowspan="1"><p>1 / 1 / 1</p></td><td colspan="1" rowspan="1"><p>2 / 2 / 3</p></td></tr><tr><td colspan="1" rowspan="1"><p>USB</p></td><td colspan="1" rowspan="1"><p>Нет</p></td><td colspan="1" rowspan="1"><p>Full-Speed USB 2.0</p></td></tr><tr><td colspan="1" rowspan="1"><p>Цена</p></td><td colspan="1" rowspan="1"><p>~$3–5 (оригинал)</p></td><td colspan="1" rowspan="1"><p>~$0.8–2</p></td></tr></tbody></table></div><p>И это самый простой STM32. Линейки F4, F7, H7 — ещё на порядок мощнее.</p><hr><h2>Семейства STM32: как не запутаться</h2><p>STM32 делится на несколько линеек по ядру и позиционированию:</p><p><strong>STM32F0/F1/F3 — Базовые (Cortex-M0/M3/M4)</strong></p><ul><li><p>F103: самый популярный, "Blue Pill", 72 МГц — идеален для старта</p></li><li><p>F303: F3 с матфлоатом и операционными усилителями внутри</p></li><li><p>F030/F042: ультрадешёвые, от $0.3, для массового производства</p></li></ul><p><strong>STM32F4 — Производительные (Cortex-M4F с FPU)</strong></p><ul><li><p>F401/F411: 84–100 МГц, USB, хороший баланс</p></li><li><p>F407/F429: 168 МГц, Ethernet MAC, FMC для внешней SDRAM, камеры</p></li><li><p>Популярны для DSP-задач, аудио, обработки изображений</p></li></ul><p><strong>STM32F7/H7 — Высокопроизводительные (Cortex-M7)</strong></p><ul><li><p>H743: 480 МГц, двойная точность float, L1-кэш, умереть не встать</p></li><li><p>Используются в промышленных системах реального времени</p></li></ul><p><strong>STM32L — Низкое энергопотребление (Low Power)</strong></p><ul><li><p>L051/L071: ток в sleep &lt; 1 мкА, для батарейных устройств</p></li></ul><p><strong>STM32G/U — Новые серии (2019–2022)</strong></p><ul><li><p>G431/G474: отличные для силовой электроники (Timer1 с мёртвым временем)</p></li><li><p>U5: Cortex-M33 с TrustZone, IoT-безопасность</p></li></ul><p><strong>Рекомендации для старта:</strong> STM32F103C8T6 (Blue Pill) или STM32G031 для новых проектов.</p><hr><h2>Настройка среды разработки</h2><h3>STM32CubeIDE + STM32CubeMX</h3><p>STM32CubeIDE — официальная бесплатная среда от ST. Включает:</p><ul><li><p>Eclipse-based IDE</p></li><li><p>Компилятор GCC ARM</p></li><li><p>OpenOCD для программирования/отладки</p></li><li><p>Встроенный STM32CubeMX для генерации кода инициализации</p></li></ul><p><strong>Установка:</strong></p><ol><li><p>Скачать STM32CubeIDE с сайта <a rel="external nofollow" href="https://st.com">st.com</a> (требует регистрации, бесплатно)</p></li><li><p>Установить, выбрать пакеты для нужных семейств</p></li><li><p>Подключить программатор ST-Link V2 ($3–5 на AliExpress)</p></li></ol><h3>Первое подключение (Blue Pill → ST-Link V2):</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>ST-Link V2    Blue Pill
SWDIO    →   PA13
SWCLK    →   PA14
GND      →   GND
3.3V     →   3V3
</code></pre><p><strong>Важно:</strong> На оригинальных Blue Pill загрузчик прошит неправильно. Для работы с ST-Link через SWD это не проблема — программируем напрямую в flash.</p><hr><h2>HAL vs LL: что выбрать</h2><p>STMicroelectronics предоставляет два уровня библиотек:</p><p><strong>HAL (Hardware Abstraction Layer):</strong></p><ul><li><p>Высокоуровневый, максимально переносимый код</p></li><li><p>Автоматически генерируется CubeMX</p></li><li><p>Проще в использовании, больше overhead</p></li><li><p>Рекомендуется для большинства проектов</p></li></ul><p><strong>LL (Low Layer):</strong></p><ul><li><p>Тонкие обёртки над регистрами, почти без overhead</p></li><li><p>Максимальная производительность и предсказуемость</p></li><li><p>Нужно хорошее знание периферии</p></li><li><p>Для критичного по времени кода</p></li></ul><p><strong>Смешанный подход</strong> (лучший для опытных):</p><ul><li><p>HAL для инициализации (CubeMX генерирует)</p></li><li><p>LL для критичных по времени операций в прерываниях</p></li></ul><hr><h2>GPIO: мигаем светодиодом правильно</h2><p>CubeMX генерирует такой код инициализации GPIO:</p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Автосгенерированный код CubeMX
static void MX_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    __HAL_RCC_GPIOC_CLK_ENABLE();  // Включаем тактирование порта C
    
    // Настройка PC13 как выход (встроенный LED на Blue Pill)
    GPIO_InitStruct.Pin   = GPIO_PIN_13;
    GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;   // Push-Pull выход
    GPIO_InitStruct.Pull  = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;   // 2 МГц, достаточно для LED
    HAL_GPIO_Init(GPIOC, &amp;GPIO_InitStruct);
    
    // Входной сигнал на PA0 (кнопка)
    GPIO_InitStruct.Pin  = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;  // Внутренний pull-up
    HAL_GPIO_Init(GPIOA, &amp;GPIO_InitStruct);
}
</code></pre><p>Управление GPIO в программе:</p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Включить / выключить / переключить
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);  // LED on (active low)
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);    // LED off
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);                 // Toggle

// Читать состояние входа
GPIO_PinState btn = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
if (btn == GPIO_PIN_RESET) {  // Кнопка нажата (pull-up, нажатие — к GND)
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
}
</code></pre><h3>GPIO режимы:</h3><ul><li><p><code>GPIO_MODE_OUTPUT_PP</code> — Push-Pull выход (стандартный)</p></li><li><p><code>GPIO_MODE_OUTPUT_OD</code> — Open-Drain (для I2C, совместимость 5В)</p></li><li><p><code>GPIO_MODE_INPUT</code> — Вход</p></li><li><p><code>GPIO_MODE_IT_RISING/FALLING/RISING_FALLING</code> — Вход с прерыванием</p></li><li><p><code>GPIO_MODE_AF_PP</code> — Альтернативная функция (UART, SPI, Timer...)</p></li><li><p><code>GPIO_MODE_ANALOG</code> — Аналоговый режим (для ADC/DAC)</p></li></ul><hr><h2>UART: последовательная связь</h2><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Инициализация UART1 на PA9(TX)/PA10(RX), 115200 бод
static void MX_USART1_UART_Init(void)
{
    huart1.Instance          = USART1;
    huart1.Init.BaudRate     = 115200;
    huart1.Init.WordLength   = UART_WORDLENGTH_8B;
    huart1.Init.StopBits     = UART_STOPBITS_1;
    huart1.Init.Parity       = UART_PARITY_NONE;
    huart1.Init.Mode         = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl    = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    HAL_UART_Init(&amp;huart1);
}

// Отправка данных (блокирующий режим)
char msg[] = "Hello STM32!\r\n";
HAL_UART_Transmit(&amp;huart1, (uint8_t*)msg, strlen(msg), 100);  // timeout 100ms

// Приём (с таймаутом)
uint8_t rx_buf[64];
uint16_t bytes_received;
HAL_StatusTypeDef status = HAL_UART_Receive(&amp;huart1, rx_buf, 64, 1000);

// printf через UART (настройка retarget)
// В файле syscalls.c добавить:
int __io_putchar(int ch)
{
    HAL_UART_Transmit(&amp;huart1, (uint8_t*)&amp;ch, 1, 100);
    return ch;
}
// После этого можно использовать printf!
printf("Температура: %.2f°C\r\n", temperature);
</code></pre><h3>Приём через прерывания (правильный подход):</h3><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Буфер приёма и флаги
#define RX_BUFFER_SIZE 256
static uint8_t rx_buffer[RX_BUFFER_SIZE];
static uint8_t rx_byte;          // Один байт для приёма по прерыванию
static uint16_t rx_index = 0;
static volatile uint8_t line_ready = 0;

// Запускаем прием одного байта в прерывании
// (вызвать после инициализации и после каждого приёма)
void UART_StartReceive(void)
{
    HAL_UART_Receive_IT(&amp;huart1, &amp;rx_byte, 1);
}

// Callback — вызывается автоматически при приёме байта
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart-&gt;Instance == USART1) {
        if (rx_byte == '\n' || rx_index &gt;= RX_BUFFER_SIZE - 1) {
            rx_buffer[rx_index] = '\0';
            rx_index = 0;
            line_ready = 1;  // Сигнализируем что строка готова
        } else if (rx_byte != '\r') {
            rx_buffer[rx_index++] = rx_byte;
        }
        
        // Запускаем следующий приём
        HAL_UART_Receive_IT(&amp;huart1, &amp;rx_byte, 1);
    }
}

// В главном цикле:
if (line_ready) {
    line_ready = 0;
    printf("Получено: %s\r\n", rx_buffer);
    process_command((char*)rx_buffer);
}
</code></pre><hr><h2>Таймеры: ШИМ и точное время</h2><p>Таймеры STM32 — мощнейшая периферия. Используются для ШИМ, измерения частоты, генерации прерываний, управления сервоприводами.</p><h3>ШИМ (PWM) для управления яркостью/скоростью:</h3><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// TIM3, Channel 1, PA6, частота 1 кГц
// Настройка через CubeMX, затем в коде:

// Запуск ШИМ
HAL_TIM_PWM_Start(&amp;htim3, TIM_CHANNEL_1);

// Изменение скважности (0–999 для ARR=999)
// 500 = 50% скважности
__HAL_TIM_SET_COMPARE(&amp;htim3, TIM_CHANNEL_1, 500);

// Плавное изменение яркости LED
for (int i = 0; i &lt;= 999; i++) {
    __HAL_TIM_SET_COMPARE(&amp;htim3, TIM_CHANNEL_1, i);
    HAL_Delay(1);
}
for (int i = 999; i &gt;= 0; i--) {
    __HAL_TIM_SET_COMPARE(&amp;htim3, TIM_CHANNEL_1, i);
    HAL_Delay(1);
}
</code></pre><h3>Управление сервоприводом (50 Гц, 1–2 мс):</h3><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// TIM2, 50 Гц (период 20 мс = 20000 тиков при предделителе 72-1)
// ARR = 19999, PSC = 71 → 1 тик = 1 мкс

#define SERVO_MIN_US  1000  // 1 мс = левый предел
#define SERVO_MAX_US  2000  // 2 мс = правый предел
#define SERVO_MID_US  1500  // 1.5 мс = центр

void servo_set_angle(int angle_degrees)  // 0–180 градусов
{
    // Линейное масштабирование угла → ширина импульса
    uint32_t pulse = SERVO_MIN_US + 
                     (uint32_t)(angle_degrees * (SERVO_MAX_US - SERVO_MIN_US) / 180);
    __HAL_TIM_SET_COMPARE(&amp;htim2, TIM_CHANNEL_1, pulse);
}

// Использование:
servo_set_angle(0);   // Левый предел
HAL_Delay(1000);
servo_set_angle(90);  // Центр
HAL_Delay(1000);
servo_set_angle(180); // Правый предел
</code></pre><h3>Прерывание по таймеру (точный период):</h3><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Прерывание каждые 1 мс от TIM6 (базовый таймер)
// Запуск:
HAL_TIM_Base_Start_IT(&amp;htim6);

// Callback:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim-&gt;Instance == TIM6) {
        system_tick_ms++;   // Собственный миллисекундный счётчик
        
        // Задачи каждые 10 мс
        if (system_tick_ms % 10 == 0) {
            adc_trigger_conversion();
        }
        
        // Задачи каждые 1000 мс
        if (system_tick_ms % 1000 == 0) {
            led_heartbeat_toggle();
        }
    }
}
</code></pre><hr><h2>АЦП: чтение аналоговых сигналов</h2><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Одиночное преобразование (блокирующий режим)
uint32_t adc_read_single(void)
{
    HAL_ADC_Start(&amp;hadc1);
    HAL_ADC_PollForConversion(&amp;hadc1, 10);  // Ждём не более 10 мс
    uint32_t value = HAL_ADC_GetValue(&amp;hadc1);
    HAL_ADC_Stop(&amp;hadc1);
    return value;  // 0–4095 для 12-бит АЦП
}

// Перевод в напряжение (опорное 3.3В):
float adc_to_voltage(uint32_t raw)
{
    return raw * 3.3f / 4095.0f;
}

// Перевод в температуру для NTC-термистора (10кОм, B=3950):
float ntc_to_celsius(uint32_t raw_adc)
{
    float voltage = adc_to_voltage(raw_adc);
    float resistance = 10000.0f * voltage / (3.3f - voltage);  // Делитель с 10кОм
    
    // Уравнение Стейнхарта-Харта (упрощённое)
    float steinhart;
    steinhart  = resistance / 10000.0f;     // R/Rnom
    steinhart  = logf(steinhart);           // ln(R/Rnom)
    steinhart /= 3950.0f;                   // / B
    steinhart += 1.0f / (25.0f + 273.15f); // + 1/T0
    steinhart  = 1.0f / steinhart;         // Инверсия
    
    return steinhart - 273.15f;             // Кельвин → Цельсий
}
</code></pre><h3>АЦП с DMA (несколько каналов, без участия CPU):</h3><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Настройка: ADC + DMA, Continuous mode, 4 канала (PA0-PA3)
#define ADC_CHANNELS 4
static uint16_t adc_dma_buffer[ADC_CHANNELS];

// Запуск:
HAL_ADC_Start_DMA(&amp;hadc1, (uint32_t*)adc_dma_buffer, ADC_CHANNELS);

// DMA автоматически обновляет буфер!
// В основном цикле просто читаем:
float temp = ntc_to_celsius(adc_dma_buffer[0]);
float pressure = adc_dma_buffer[1] * 3.3f / 4095.0f;

// Callback при завершении преобразований всех каналов:
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    // Все 4 канала обновлены
    adc_data_ready = 1;
}
</code></pre><hr><h2>I2C: подключение датчиков</h2><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Пример: датчик давления/температуры BMP280

#define BMP280_ADDR   0x76 &lt;&lt; 1  // 7-бит адрес, сдвиг влево для HAL

// Читать регистр
uint8_t BMP280_ReadReg(uint8_t reg)
{
    uint8_t value;
    HAL_I2C_Mem_Read(&amp;hi2c1,
                     BMP280_ADDR,   // Адрес устройства
                     reg,           // Адрес регистра
                     I2C_MEMADD_SIZE_8BIT,
                     &amp;value,        // Буфер
                     1,             // Количество байт
                     100);          // Таймаут
    return value;
}

// Записать регистр
void BMP280_WriteReg(uint8_t reg, uint8_t value)
{
    HAL_I2C_Mem_Write(&amp;hi2c1, BMP280_ADDR, reg,
                      I2C_MEMADD_SIZE_8BIT, &amp;value, 1, 100);
}

// Читать несколько байт подряд
void BMP280_ReadBurst(uint8_t reg, uint8_t *buf, uint8_t len)
{
    HAL_I2C_Mem_Read(&amp;hi2c1, BMP280_ADDR, reg,
                     I2C_MEMADD_SIZE_8BIT, buf, len, 100);
}

// Инициализация BMP280
void BMP280_Init(void)
{
    // Проверка ID (должен быть 0x60)
    uint8_t id = BMP280_ReadReg(0xD0);
    if (id != 0x60) {
        printf("BMP280 не найден! ID=0x%02X\r\n", id);
        return;
    }
    
    // Нормальный режим, oversampling ×4 для давления и температуры
    BMP280_WriteReg(0xF4, 0x97);  // ctrl_meas: осsp×4, osst×4, normal mode
    BMP280_WriteReg(0xF5, 0xA0);  // config: t_sb=1000мс, filter=16
}

// Чтение данных (упрощённо, без компенсации)
typedef struct {
    float temperature;
    float pressure;
} BMP280_Data;

BMP280_Data BMP280_ReadData(void)
{
    uint8_t raw[6];
    BMP280_ReadBurst(0xF7, raw, 6);  // press_msb, press_lsb, press_xlsb, temp×3
    
    int32_t raw_press = ((int32_t)raw[0] &lt;&lt; 12) | ((int32_t)raw[1] &lt;&lt; 4) | (raw[2] &gt;&gt; 4);
    int32_t raw_temp  = ((int32_t)raw[3] &lt;&lt; 12) | ((int32_t)raw[4] &lt;&lt; 4) | (raw[5] &gt;&gt; 4);
    
    // Реальный код должен использовать калибровочные коэффициенты из регистров!
    // Это упрощение для иллюстрации
    BMP280_Data data;
    data.temperature = raw_temp / 5120.0f;   // Очень грубо!
    data.pressure    = raw_press / 25600.0f; // Очень грубо!
    return data;
}
</code></pre><hr><h2>SPI: быстрая связь с периферией</h2><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// SPI — быстрее I2C, до 45 МГц на STM32F4
// Пример: дисплей ST7735 (128×160 пикселей)

// CS-пин вручную (NSS в software-режиме)
#define LCD_CS_LOW()   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)
#define LCD_CS_HIGH()  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)
#define LCD_DC_LOW()   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET)  // Command
#define LCD_DC_HIGH()  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET)    // Data

void LCD_SendCommand(uint8_t cmd)
{
    LCD_DC_LOW();
    LCD_CS_LOW();
    HAL_SPI_Transmit(&amp;hspi1, &amp;cmd, 1, 100);
    LCD_CS_HIGH();
}

void LCD_SendData(uint8_t *data, uint16_t len)
{
    LCD_DC_HIGH();
    LCD_CS_LOW();
    HAL_SPI_Transmit(&amp;hspi1, data, len, 1000);
    LCD_CS_HIGH();
}

// Быстрая передача через DMA (не блокирует CPU)
void LCD_SendDataDMA(uint8_t *data, uint16_t len)
{
    LCD_DC_HIGH();
    LCD_CS_LOW();
    HAL_SPI_Transmit_DMA(&amp;hspi1, data, len);
    // CS поднимется в callback после завершения DMA
}

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if (hspi-&gt;Instance == SPI1) {
        LCD_CS_HIGH();
        dma_complete = 1;
    }
}
</code></pre><hr><h2>Структура хорошего проекта на STM32</h2><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>project/
├── Core/
│   ├── Inc/
│   │   ├── main.h
│   │   └── stm32f1xx_hal_conf.h
│   └── Src/
│       ├── main.c              — Только инициализация + main loop
│       ├── stm32f1xx_it.c      — Обработчики прерываний
│       └── syscalls.c          — printf retarget
├── Drivers/
│   ├── CMSIS/                  — ARM заголовки, системный файл
│   └── STM32F1xx_HAL_Driver/   — HAL библиотека (не трогать)
├── App/                        — ВАШ КОД здесь!
│   ├── sensors/
│   │   ├── bmp280.c / .h
│   │   └── ntc.c / .h
│   ├── control/
│   │   ├── pid.c / .h
│   │   └── state_machine.c / .h
│   ├── comm/
│   │   ├── modbus_slave.c / .h
│   │   └── protocol.c / .h
│   └── app.c                   — Главная логика приложения
└── CMakeLists.txt / .ioc       — Конфигурация проекта
</code></pre><p><strong>Принцип:</strong> <code>main.c</code> содержит только вызов <code>App_Init()</code> и <code>App_Run()</code>. Вся логика — в директории <code>App/</code>.</p><hr><h2>Типичные ошибки и как их избежать</h2><p><strong>1. Забыли включить тактирование периферии</strong></p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Без этого HAL_GPIO_Init ничего не сделает!
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_SPI1_CLK_ENABLE();
// CubeMX делает это автоматически — используйте его для инициализации
</code></pre><p><strong>2. Неправильные Alternate Functions</strong> STM32F1 имеет фиксированный маппинг пинов. STM32F4+ — гибкий (GPIO_AF1_..., GPIO_AF7_...). Смотрите datasheet, раздел "Alternate function mapping".</p><p><strong>3. Блокирующие задержки в прерываниях</strong></p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// НЕЛЬЗЯ! HAL_Delay использует SysTick-прерывание низшего приоритета
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    HAL_Delay(100);  // Зависнет если прерывание выше приоритета SysTick!
}
</code></pre><p><strong>4. Запись в HAL-переменную напрямую</strong> Не изменяйте поля структур <code>huart1</code>, <code>htim2</code> вручную в рантайме — используйте API функции.</p><p><strong>5. Stack Overflow</strong> STM32F103 имеет только 20 КБ RAM. Большие массивы на стеке — прямой путь к Hard Fault. Объявляйте большие буферы как глобальные или статические.</p><hr><h2>Отладка: когда всё идёт не так</h2><h3>Hard Fault Handler — ваш лучший друг</h3><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>void HardFault_Handler(void)
{
    // Получаем регистры состояния
    __asm volatile("TST lr, #4\n"
                   "ITE EQ\n"
                   "MRSEQ r0, MSP\n"
                   "MRSNE r0, PSP\n"
                   "B HardFault_HandlerC");
}

void HardFault_HandlerC(uint32_t *stack_frame)
{
    printf("=== HARD FAULT ===\r\n");
    printf("PC  = 0x%08lX\r\n", stack_frame[6]);  // Адрес проблемной инструкции
    printf("LR  = 0x%08lX\r\n", stack_frame[5]);
    printf("CFSR= 0x%08lX\r\n", SCB-&gt;CFSR);       // Причина fault
    while(1);
}
</code></pre><h3>SWO (Serial Wire Output) — printf без UART</h3><p>Если UART занят, используйте SWO для отладочного вывода. В STM32CubeIDE включается за 5 кликов, вывод идёт в консоль IDE без дополнительного кабеля.</p><hr><h2>Заключение</h2><p>STM32 — это настоящий профессиональный инструмент. Первые шаги сложнее чем с Arduino: нужно настроить тактирование, разобраться с HAL, понять концепцию прерываний и DMA. Но это окупается многократно: производительность, периферия, потребление, цена в серийном производстве.</p><p>Рекомендуемый путь: STM32F103 Blue Pill + ST-Link V2 + STM32CubeIDE. Первый проект — мигание LED через прерывание таймера. Второй — чтение кнопки с антидребезгом. Третий — датчик по I2C с выводом в UART. К четвёртому проекту вы уже будете чувствовать себя уверенно.</p><p>Документация ST: datasheet, Reference Manual, Programming Manual — читайте их. Они написаны хорошо и содержат ответы на все вопросы.</p>]]></description><guid isPermaLink="false">98</guid><pubDate>Sat, 21 Mar 2026 16:47:04 +0000</pubDate></item><item><title>Arduino &#x432; &#x440;&#x435;&#x430;&#x43B;&#x44C;&#x43D;&#x44B;&#x445; &#x43F;&#x440;&#x43E;&#x435;&#x43A;&#x442;&#x430;&#x445;: &#x43E;&#x442; &#x43C;&#x438;&#x433;&#x430;&#x43D;&#x438;&#x44F; &#x441;&#x432;&#x435;&#x442;&#x43E;&#x434;&#x438;&#x43E;&#x434;&#x43E;&#x43C; &#x434;&#x43E; &#x43F;&#x440;&#x43E;&#x43C;&#x44B;&#x448;&#x43B;&#x435;&#x43D;&#x43D;&#x43E;&#x433;&#x43E; &#x43F;&#x440;&#x43E;&#x442;&#x43E;&#x442;&#x438;&#x43F;&#x430;</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/arduino-%D0%B2-%D1%80%D0%B5%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D1%85-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B0%D1%85-%D0%BE%D1%82-%D0%BC%D0%B8%D0%B3%D0%B0%D0%BD%D0%B8%D1%8F-%D1%81%D0%B2%D0%B5%D1%82%D0%BE%D0%B4%D0%B8%D0%BE%D0%B4%D0%BE%D0%BC-%D0%B4%D0%BE-%D0%BF%D1%80%D0%BE%D0%BC%D1%8B%D1%88%D0%BB%D0%B5%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE-%D0%BF%D1%80%D0%BE%D1%82%D0%BE%D1%82%D0%B8%D0%BF%D0%B0-r101/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/proekty-na-arduino.jpg.db5e5411be036f601128d8104d1b7514.jpg" /></p>
<h2>Честный разговор об Arduino</h2><p>Arduino получил репутацию "игрушки для начинающих". Это несправедливо. Arduino — это доступная платформа с огромной экосистемой, которая используется в реальных устройствах, промышленных прототипах и мелкосерийном производстве.</p><p>Да, у неё есть ограничения: 8-битный AVR, 16 МГц, 2 КБ RAM. Но для огромного класса задач этого более чем достаточно. Умный контроллер теплицы, анализатор вибраций, узел сбора данных, промышленный таймер — Arduino справится.</p><p>Проблема не в платформе. Проблема в том, как большинство людей пишут код для Arduino. Сегодня покажу как писать Arduino-код правильно — как настоящие инженеры.</p><hr><h2>Главная ошибка: delay()</h2><p><code>delay()</code> — это яд для Arduino. Пока выполняется <code>delay(1000)</code>, микроконтроллер ничего не делает. Не обрабатывает кнопки. Не читает датчики. Не обновляет выходы. Он просто спит.</p><p>В реальном устройстве с несколькими задачами это неприемлемо.</p><h3>Правильный подход: millis() и конечный автомат</h3><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>// ПЛОХО — как делают 90% новичков:
void loop() {
    digitalWrite(LED1, HIGH);
    delay(500);
    digitalWrite(LED1, LOW);
    delay(500);
    
    // Пока мигает LED1 — ничего другого не происходит!
}

// ХОРОШО — неблокирующий код:
class BlinkTask {
private:
    uint8_t pin;
    uint32_t interval;
    uint32_t lastToggle;
    bool state;
    
public:
    BlinkTask(uint8_t _pin, uint32_t _interval)
        : pin(_pin), interval(_interval), lastToggle(0), state(false) {}
    
    void begin() {
        pinMode(pin, OUTPUT);
    }
    
    void update() {
        uint32_t now = millis();
        if (now - lastToggle &gt;= interval) {
            lastToggle = now;
            state = !state;
            digitalWrite(pin, state);
        }
    }
};

BlinkTask led1(13, 500);  // LED на пине 13, период 500 мс
BlinkTask led2(12, 200);  // LED на пине 12, период 200 мс

void setup() {
    led1.begin();
    led2.begin();
}

void loop() {
    led1.update();   // Каждый вызов занимает микросекунды
    led2.update();   // Оба LED мигают независимо!
    // Здесь можно добавить ещё десятки задач
}
</code></pre><hr><h2>Антидребезг кнопок: правильная реализация</h2><p>Физическая кнопка при нажатии/отпускании "дребезжит" — за несколько миллисекунд создаёт 10–50 ложных переключений. Простой <code>delay(50)</code> тут не поможет — он заблокирует программу.</p><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>class Button {
private:
    uint8_t pin;
    uint8_t currentState;
    uint8_t lastRawState;
    uint32_t lastChangeTime;
    uint32_t debounceTime;
    
    // Callbacks
    void (*onPress)();
    void (*onRelease)();
    void (*onLongPress)();
    
    uint32_t pressTime;
    uint32_t longPressTime;
    bool longPressTriggered;
    
public:
    Button(uint8_t _pin, uint32_t _debounce = 50, uint32_t _longPress = 1000)
        : pin(_pin), currentState(HIGH), lastRawState(HIGH),
          lastChangeTime(0), debounceTime(_debounce),
          onPress(nullptr), onRelease(nullptr), onLongPress(nullptr),
          pressTime(0), longPressTime(_longPress), longPressTriggered(false) {}
    
    void begin() {
        pinMode(pin, INPUT_PULLUP);  // Внутренняя подтяжка, нажатие → LOW
    }
    
    void setOnPress(void (*cb)())    { onPress = cb; }
    void setOnRelease(void (*cb)())  { onRelease = cb; }
    void setOnLongPress(void (*cb)()){ onLongPress = cb; }
    
    bool isPressed() { return currentState == LOW; }
    
    void update() {
        uint32_t now = millis();
        uint8_t rawState = digitalRead(pin);
        
        // Антидребезг: состояние должно держаться debounceTime мс
        if (rawState != lastRawState) {
            lastChangeTime = now;
            lastRawState = rawState;
        }
        
        if (now - lastChangeTime &gt;= debounceTime) {
            if (rawState != currentState) {
                currentState = rawState;
                
                if (currentState == LOW) {  // Нажата
                    pressTime = now;
                    longPressTriggered = false;
                    if (onPress) onPress();
                } else {  // Отпущена
                    if (onRelease) onRelease();
                }
            }
        }
        
        // Проверка длинного нажатия
        if (currentState == LOW &amp;&amp; !longPressTriggered) {
            if (now - pressTime &gt;= longPressTime) {
                longPressTriggered = true;
                if (onLongPress) onLongPress();
            }
        }
    }
};

// Использование:
Button btn1(2);
Button btn2(3);

int counter = 0;

void onBtn1Press()     { Serial.println("Кнопка 1 нажата"); counter++; }
void onBtn1LongPress() { Serial.println("Длинное нажатие! Сброс"); counter = 0; }
void onBtn2Press()     { Serial.println("Кнопка 2 нажата"); counter--; }

void setup() {
    Serial.begin(115200);
    btn1.begin();
    btn2.begin();
    btn1.setOnPress(onBtn1Press);
    btn1.setOnLongPress(onBtn1LongPress);
    btn2.setOnPress(onBtn2Press);
}

void loop() {
    btn1.update();
    btn2.update();
    // ... другие задачи
}
</code></pre><hr><h2>Конечный автомат (State Machine): управление процессом</h2><p>Конечный автомат — правильный способ описывать сложное поведение без вложенных if-else и флагов.</p><p>Пример: автоматическая стиральная машина (упрощённо):</p><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>enum WasherState {
    STATE_IDLE,
    STATE_FILL_WATER,
    STATE_WASH,
    STATE_DRAIN,
    STATE_SPIN,
    STATE_COMPLETE,
    STATE_ERROR
};

enum WasherEvent {
    EVENT_START,
    EVENT_WATER_FULL,
    EVENT_WASH_DONE,
    EVENT_DRAIN_DONE,
    EVENT_SPIN_DONE,
    EVENT_ERROR,
    EVENT_RESET
};

class WashingMachine {
private:
    WasherState state;
    uint32_t stateEnterTime;
    uint32_t washDuration;
    uint32_t spinDuration;
    
    // Пины
    static const uint8_t PIN_VALVE     = 4;   // Клапан залива воды
    static const uint8_t PIN_PUMP      = 5;   // Насос откачки
    static const uint8_t PIN_MOTOR     = 6;   // Двигатель барабана
    static const uint8_t PIN_LED_RUN   = 7;   // Индикатор работы
    static const uint8_t PIN_SENSOR_FULL = 8; // Датчик полного бака
    
    void enterState(WasherState newState) {
        state = newState;
        stateEnterTime = millis();
        
        // Действия при входе в состояние
        switch (state) {
            case STATE_IDLE:
                allOff();
                Serial.println("Ожидание...");
                break;
                
            case STATE_FILL_WATER:
                digitalWrite(PIN_VALVE, HIGH);
                Serial.println("Заполнение водой...");
                break;
                
            case STATE_WASH:
                digitalWrite(PIN_VALVE, LOW);
                digitalWrite(PIN_MOTOR, HIGH);
                Serial.println("Стирка...");
                break;
                
            case STATE_DRAIN:
                digitalWrite(PIN_MOTOR, LOW);
                digitalWrite(PIN_PUMP, HIGH);
                Serial.println("Слив воды...");
                break;
                
            case STATE_SPIN:
                digitalWrite(PIN_PUMP, LOW);
                // Быстрое вращение для отжима
                analogWrite(PIN_MOTOR, 200);  // 78% мощности
                Serial.println("Отжим...");
                break;
                
            case STATE_COMPLETE:
                allOff();
                Serial.println("Стирка завершена!");
                break;
                
            case STATE_ERROR:
                allOff();
                Serial.println("АВАРИЯ!");
                break;
        }
    }
    
    void allOff() {
        digitalWrite(PIN_VALVE, LOW);
        digitalWrite(PIN_PUMP, LOW);
        digitalWrite(PIN_MOTOR, LOW);
    }
    
public:
    WashingMachine(uint32_t _washMin = 30, uint32_t _spinMin = 5)
        : state(STATE_IDLE),
          washDuration(_washMin * 60000UL),
          spinDuration(_spinMin * 60000UL) {}
    
    void begin() {
        pinMode(PIN_VALVE, OUTPUT);
        pinMode(PIN_PUMP, OUTPUT);
        pinMode(PIN_MOTOR, OUTPUT);
        pinMode(PIN_LED_RUN, OUTPUT);
        pinMode(PIN_SENSOR_FULL, INPUT_PULLUP);
        allOff();
    }
    
    void sendEvent(WasherEvent event) {
        switch (state) {
            case STATE_IDLE:
                if (event == EVENT_START)  enterState(STATE_FILL_WATER);
                break;
                
            case STATE_FILL_WATER:
                if (event == EVENT_WATER_FULL) enterState(STATE_WASH);
                if (event == EVENT_ERROR)      enterState(STATE_ERROR);
                break;
                
            case STATE_WASH:
                if (event == EVENT_WASH_DONE)  enterState(STATE_DRAIN);
                if (event == EVENT_ERROR)      enterState(STATE_ERROR);
                break;
                
            case STATE_DRAIN:
                if (event == EVENT_DRAIN_DONE) enterState(STATE_SPIN);
                if (event == EVENT_ERROR)      enterState(STATE_ERROR);
                break;
                
            case STATE_SPIN:
                if (event == EVENT_SPIN_DONE)  enterState(STATE_COMPLETE);
                break;
                
            case STATE_ERROR:
                if (event == EVENT_RESET)      enterState(STATE_IDLE);
                break;
                
            default:
                break;
        }
    }
    
    // Вызывать в loop() каждый цикл
    void update() {
        uint32_t elapsed = millis() - stateEnterTime;
        
        // Индикатор работы
        bool running = (state != STATE_IDLE &amp;&amp; state != STATE_COMPLETE 
                                             &amp;&amp; state != STATE_ERROR);
        digitalWrite(PIN_LED_RUN, running ? (millis() % 500 &lt; 250) : LOW);
        
        switch (state) {
            case STATE_FILL_WATER:
                // Проверяем датчик уровня
                if (digitalRead(PIN_SENSOR_FULL) == LOW) {
                    sendEvent(EVENT_WATER_FULL);
                }
                // Таймаут заполнения 10 минут
                if (elapsed &gt; 600000UL) {
                    sendEvent(EVENT_ERROR);
                }
                break;
                
            case STATE_WASH:
                if (elapsed &gt;= washDuration) {
                    sendEvent(EVENT_WASH_DONE);
                }
                break;
                
            case STATE_DRAIN:
                // 3 минуты на слив
                if (elapsed &gt;= 180000UL) {
                    sendEvent(EVENT_DRAIN_DONE);
                }
                break;
                
            case STATE_SPIN:
                if (elapsed &gt;= spinDuration) {
                    sendEvent(EVENT_SPIN_DONE);
                }
                break;
                
            default:
                break;
        }
    }
    
    WasherState getState() { return state; }
};

WashingMachine washer(30, 5);  // 30 мин стирка, 5 мин отжим
Button startBtn(2);

void setup() {
    Serial.begin(115200);
    washer.begin();
    startBtn.begin();
    startBtn.setOnPress([]() {
        washer.sendEvent(EVENT_START);
    });
}

void loop() {
    washer.update();
    startBtn.update();
}
</code></pre><hr><h2>ПИД-регулятор: управление температурой</h2><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>class PIDController {
private:
    float kp, ki, kd;
    float setpoint;
    float integral;
    float prevError;
    float integralLimit;
    float outputMin, outputMax;
    uint32_t lastTime;
    
public:
    PIDController(float _kp, float _ki, float _kd,
                  float _outMin = 0.0f, float _outMax = 100.0f)
        : kp(_kp), ki(_ki), kd(_kd),
          setpoint(0), integral(0), prevError(0),
          integralLimit(50.0f),
          outputMin(_outMin), outputMax(_outMax),
          lastTime(0) {}
    
    void setSetpoint(float sp) { setpoint = sp; }
    void reset() { integral = 0; prevError = 0; }
    
    float compute(float measured) {
        uint32_t now = millis();
        float dt = (now - lastTime) / 1000.0f;  // Секунды
        lastTime = now;
        
        if (dt &lt;= 0 || dt &gt; 1.0f) dt = 0.1f;  // Защита от аномальных dt
        
        float error = setpoint - measured;
        
        // Интегральная составляющая с ограничением (anti-windup)
        integral += error * dt;
        integral = constrain(integral, -integralLimit, integralLimit);
        
        // Производная (фильтруем шум — берём производную измерения, не ошибки)
        float derivative = -(measured - prevError) / dt;  // -d(PV)/dt
        prevError = measured;
        
        float output = kp * error + ki * integral + kd * derivative;
        return constrain(output, outputMin, outputMax);
    }
};

// Применение: ПИД-термостат с твёрдотельным реле (SSR)
const uint8_t PIN_HEATER   = 9;    // ШИМ-выход на SSR
const uint8_t PIN_TEMP_SCL = A4;   // I2C — датчик температуры
const uint8_t PIN_TEMP_SDA = A5;

PIDController tempPID(2.0f, 0.5f, 1.0f, 0.0f, 255.0f);

float readTemperature() {
    // Здесь чтение датчика DS18B20 или термопары MAX6675
    // Возвращаем демо-значение:
    return 25.0f + random(-10, 10) / 10.0f;
}

void setup() {
    Serial.begin(115200);
    pinMode(PIN_HEATER, OUTPUT);
    tempPID.setSetpoint(75.0f);  // Уставка 75°C
}

void loop() {
    static uint32_t lastPID = 0;
    
    if (millis() - lastPID &gt;= 500) {  // ПИД каждые 500 мс
        lastPID = millis();
        
        float temp = readTemperature();
        float output = tempPID.compute(temp);
        
        analogWrite(PIN_HEATER, (uint8_t)output);
        
        Serial.print("T="); Serial.print(temp, 1);
        Serial.print("°C  SP=75°C  OUT=");
        Serial.print((int)output * 100 / 255);
        Serial.println("%");
    }
}
</code></pre><hr><h2>Modbus RTU Slave на Arduino</h2><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>#include &lt;SoftwareSerial.h&gt;

// RS-485 через MAX485
SoftwareSerial rs485(10, 11);  // RX, TX
const uint8_t DE_RE_PIN = 4;   // Driver Enable / Receiver Enable

// Данные устройства (10 Holding Registers)
uint16_t holdingRegs[10] = {0};
uint16_t inputRegs[10]   = {0};

const uint8_t DEVICE_ADDRESS = 1;

uint16_t crc16(uint8_t *buf, uint16_t len) {
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i &lt; len; i++) {
        crc ^= buf[i];
        for (uint8_t j = 0; j &lt; 8; j++) {
            crc = (crc &amp; 1) ? ((crc &gt;&gt; 1) ^ 0xA001) : (crc &gt;&gt; 1);
        }
    }
    return crc;
}

void rs485Send(uint8_t *data, uint8_t len) {
    digitalWrite(DE_RE_PIN, HIGH);  // Режим передачи
    delayMicroseconds(100);
    rs485.write(data, len);
    rs485.flush();
    delayMicroseconds(100);
    digitalWrite(DE_RE_PIN, LOW);   // Режим приёма
}

void processModbus(uint8_t *req, uint8_t len) {
    if (req[0] != DEVICE_ADDRESS) return;
    
    uint16_t rxCRC = (req[len-1] &lt;&lt; 8) | req[len-2];
    if (crc16(req, len-2) != rxCRC) return;
    
    uint8_t resp[64];
    uint8_t rlen = 0;
    uint8_t fc   = req[1];
    uint16_t addr  = (req[2] &lt;&lt; 8) | req[3];
    uint16_t count = (req[4] &lt;&lt; 8) | req[5];
    
    resp[rlen++] = DEVICE_ADDRESS;
    resp[rlen++] = fc;
    
    if (fc == 0x03 &amp;&amp; addr + count &lt;= 10) {  // Read Holding Registers
        resp[rlen++] = count * 2;
        for (uint16_t i = 0; i &lt; count; i++) {
            resp[rlen++] = holdingRegs[addr + i] &gt;&gt; 8;
            resp[rlen++] = holdingRegs[addr + i] &amp; 0xFF;
        }
    }
    else if (fc == 0x04 &amp;&amp; addr + count &lt;= 10) {  // Read Input Registers
        resp[rlen++] = count * 2;
        for (uint16_t i = 0; i &lt; count; i++) {
            resp[rlen++] = inputRegs[addr + i] &gt;&gt; 8;
            resp[rlen++] = inputRegs[addr + i] &amp; 0xFF;
        }
    }
    else if (fc == 0x06 &amp;&amp; addr &lt; 10) {  // Write Single Register
        holdingRegs[addr] = (req[4] &lt;&lt; 8) | req[5];
        memcpy(resp + 2, req + 2, 4);
        rlen += 4;
    }
    else {
        resp[1] |= 0x80;
        resp[rlen++] = (fc == 0x03 || fc == 0x04 || fc == 0x06) ? 0x02 : 0x01;
    }
    
    uint16_t c = crc16(resp, rlen);
    resp[rlen++] = c &amp; 0xFF;
    resp[rlen++] = c &gt;&gt; 8;
    
    rs485Send(resp, rlen);
}

void setup() {
    Serial.begin(115200);
    rs485.begin(9600);
    pinMode(DE_RE_PIN, OUTPUT);
    digitalWrite(DE_RE_PIN, LOW);  // Режим приёма по умолчанию
}

void loop() {
    // Обновляем Input Registers реальными данными
    inputRegs[0] = analogRead(A0);           // Аналоговый вход 0-1023
    inputRegs[1] = (uint16_t)(millis() / 1000); // Uptime в секундах
    
    // Принимаем Modbus-запросы
    static uint8_t rxBuf[64];
    static uint8_t rxLen = 0;
    static uint32_t lastByte = 0;
    
    while (rs485.available()) {
        if (rxLen &lt; sizeof(rxBuf)) {
            rxBuf[rxLen++] = rs485.read();
        }
        lastByte = millis();
    }
    
    // Конец фрейма — пауза 3.5 символа (при 9600 бод ~4 мс)
    if (rxLen &gt; 0 &amp;&amp; millis() - lastByte &gt; 4) {
        processModbus(rxBuf, rxLen);
        rxLen = 0;
    }
}
</code></pre><hr><h2>Работа с несколькими датчиками I2C</h2><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>#include &lt;Wire.h&gt;
#include &lt;Adafruit_BMP280.h&gt;
#include &lt;Adafruit_SHT31.h&gt;
#include &lt;RTC_DS3231.h&gt;

Adafruit_BMP280 bmp;
Adafruit_SHT31 sht;
RTC_DS3231 rtc;

struct SensorData {
    float temperature_bmp;
    float pressure_hpa;
    float temperature_sht;
    float humidity;
    uint32_t timestamp;
    bool bmp_ok;
    bool sht_ok;
};

SensorData readAllSensors() {
    SensorData data = {0};
    data.timestamp = rtc.now().unixtime();
    
    // BMP280: давление и температура
    if (bmp.begin(0x76)) {
        data.temperature_bmp = bmp.readTemperature();
        data.pressure_hpa    = bmp.readPressure() / 100.0f;
        data.bmp_ok = true;
    }
    
    // SHT31: температура и влажность
    if (sht.begin(0x44)) {
        data.temperature_sht = sht.readTemperature();
        data.humidity        = sht.readHumidity();
        data.sht_ok = (!isnan(data.temperature_sht));
    }
    
    return data;
}

void printSensorData(const SensorData&amp; d) {
    Serial.print("T1="); Serial.print(d.temperature_bmp, 1); Serial.print("°C ");
    Serial.print("P=");  Serial.print(d.pressure_hpa, 1);    Serial.print("гПа ");
    Serial.print("T2="); Serial.print(d.temperature_sht, 1); Serial.print("°C ");
    Serial.print("H=");  Serial.print(d.humidity, 1);        Serial.println("%");
}

void setup() {
    Serial.begin(115200);
    Wire.begin();
    Wire.setClock(400000);  // Fast mode 400 кГц
    
    if (!bmp.begin(0x76)) Serial.println("BMP280 не найден!");
    if (!sht.begin(0x44)) Serial.println("SHT31 не найден!");
    if (!rtc.begin())     Serial.println("DS3231 не найден!");
}

void loop() {
    static uint32_t lastRead = 0;
    
    if (millis() - lastRead &gt;= 5000) {  // Каждые 5 секунд
        lastRead = millis();
        SensorData data = readAllSensors();
        printSensorData(data);
    }
}
</code></pre><hr><h2>Запись данных на SD-карту</h2><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>#include &lt;SPI.h&gt;
#include &lt;SD.h&gt;

const uint8_t SD_CS_PIN = 10;

bool sdAvailable = false;

void sdInit() {
    sdAvailable = SD.begin(SD_CS_PIN);
    if (!sdAvailable) {
        Serial.println("SD-карта не найдена!");
    } else {
        Serial.println("SD-карта готова.");
    }
}

void logData(const String&amp; filename, float temp, float humidity, 
             uint32_t timestamp) {
    if (!sdAvailable) return;
    
    File file = SD.open(filename, FILE_WRITE);
    if (!file) {
        Serial.println("Ошибка открытия файла!");
        return;
    }
    
    // CSV-формат
    file.print(timestamp);
    file.print(',');
    file.print(temp, 2);
    file.print(',');
    file.println(humidity, 2);
    
    file.close();
}

// Создание нового файла каждый день
String getDailyFilename(uint32_t unixtime) {
    // Простой расчёт дня
    uint32_t day = unixtime / 86400;
    return "LOG_" + String(day) + ".CSV";
}
</code></pre><hr><h2>Советы по надёжности Arduino-проектов</h2><h3>1. Сторожевой таймер (Watchdog)</h3><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>#include &lt;avr/wdt.h&gt;

void setup() {
    wdt_enable(WDTO_2S);  // Сброс если loop() не выполняется 2 секунды
}

void loop() {
    wdt_reset();  // Сброс таймера — "я ещё живой"
    // ... ваш код
}
</code></pre><h3>2. EEPROM для сохранения настроек</h3><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>#include &lt;EEPROM.h&gt;

struct Settings {
    float setpoint;
    uint16_t interval;
    uint8_t mode;
    uint16_t checksum;
};

void saveSettings(const Settings&amp; s) {
    Settings toSave = s;
    // Простая контрольная сумма
    toSave.checksum = (uint16_t)(s.setpoint * 100) + s.interval + s.mode;
    EEPROM.put(0, toSave);
}

bool loadSettings(Settings&amp; s) {
    EEPROM.get(0, s);
    uint16_t expected = (uint16_t)(s.setpoint * 100) + s.interval + s.mode;
    return (s.checksum == expected);  // Данные корректны?
}
</code></pre><h3>3. Ограничение частоты Serial</h3><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>// НЕ ПИШИТЕ В Serial КАЖДЫЙ ЦИКЛ LOOP!
// При 115200 бод запись 100 байт занимает ~8мс

void printPeriodic(float value) {
    static uint32_t lastPrint = 0;
    if (millis() - lastPrint &gt;= 200) {  // Максимум 5 раз в секунду
        lastPrint = millis();
        Serial.println(value);
    }
}
</code></pre><hr><h2>Заключение</h2><p>Arduino — это не учебная игрушка, это платформа. Разница между "поделкой" и "устройством" — не в железе, а в качестве кода.</p><p>Ключевые принципы: никогда не используйте <code>delay()</code> в итоговом устройстве, структурируйте код как набор независимых задач через <code>millis()</code>, используйте конечные автоматы для сложной логики, добавляйте watchdog timer и защиты, документируйте код.</p><p>С этими принципами Arduino становится полноценным инструментом для создания надёжных устройств — от умного дома до промышленных узлов сбора данных.</p>]]></description><guid isPermaLink="false">101</guid><pubDate>Sat, 21 Mar 2026 16:49:01 +0000</pubDate></item><item><title>&#x41F;&#x418;&#x414;-&#x440;&#x435;&#x433;&#x443;&#x43B;&#x44F;&#x442;&#x43E;&#x440;: &#x442;&#x435;&#x43E;&#x440;&#x438;&#x44F;, &#x43D;&#x430;&#x441;&#x442;&#x440;&#x43E;&#x439;&#x43A;&#x430; &#x438; &#x440;&#x435;&#x430;&#x43B;&#x44C;&#x43D;&#x44B;&#x435; &#x43F;&#x440;&#x438;&#x43C;&#x435;&#x440;&#x44B;</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/%D0%BF%D0%B8%D0%B4-%D1%80%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%82%D0%BE%D1%80-%D1%82%D0%B5%D0%BE%D1%80%D0%B8%D1%8F-%D0%BD%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0-%D0%B8-%D1%80%D0%B5%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D1%8B-r104/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/cta2006-4pr_page69_pic9.jpg.473bc3deb7737cfc7b29e40894a862fa.jpg" /></p>
<h2>Что такое ПИД и почему он везде</h2><p>ПИД-регулятор (Пропорционально-Интегрально-Дифференциальный) — самый распространённый алгоритм автоматического управления в промышленности. По различным оценкам, более 90% всех промышленных регуляторов используют ПИД или его вариации.</p><p>Термостат в вашей духовке. Круиз-контроль в автомобиле. Система стабилизации квадрокоптера. Регулятор давления в насосной станции. Система автопилота самолёта. Везде — ПИД.</p><p>Почему? Потому что он:</p><ul><li><p><strong>Прост в понимании</strong> — три компоненты, три параметра</p></li><li><p><strong>Работает</strong> для подавляющего большинства объектов управления</p></li><li><p><strong>Хорошо изучен</strong> — 80 лет теории и практики</p></li><li><p><strong>Легко реализуется</strong> — 10–20 строк кода</p></li></ul><hr><h2>Математика: просто о сложном</h2><p>Пусть у нас есть:</p><ul><li><p><strong>SP (Setpoint)</strong> — желаемое значение (уставка)</p></li><li><p><strong>PV (Process Variable)</strong> — измеренное значение</p></li><li><p><strong>e(t) = SP – PV</strong> — ошибка регулирования</p></li><li><p><strong>u(t)</strong> — управляющий сигнал (выход регулятора)</p></li></ul><p>Формула ПИД:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>u(t) = Kp × e(t)  +  Ki × ∫e(t)dt  +  Kd × de(t)/dt
          │                │                    │
    Пропорциональная  Интегральная        Дифференциальная
    составляющая      составляющая         составляющая
</code></pre><p>Разберём каждую составляющую на примере: нагреватель, цель — держать 80°C.</p><h3>P — Пропорциональная составляющая</h3><p>Управляющий сигнал <strong>пропорционален текущей ошибке</strong>. Температура 60°C, уставка 80°C, ошибка 20°C → включить нагрев на 20×Kp%.</p><p>Проблема чистого P-регулятора: <strong>статическая ошибка (offset)</strong>. При стабильном нагреве нагрев должен компенсировать теплопотери, значит ошибка никогда не станет нулём — иначе нагрев выключится и температура упадёт. Система "зависает" с постоянной небольшой ошибкой.</p><h3>I — Интегральная составляющая</h3><p>Суммирует ошибку со временем. Даже маленькая постоянная ошибка, накапливаясь, создаёт всё больший управляющий сигнал — и <strong>в конечном счёте устраняет статическую ошибку</strong>.</p><p>Проблема: <strong>интегральное насыщение (windup)</strong>. Если выход ограничен (0–100%), а ошибка долго накапливается (например, нагрев не справляется при открытом окне), интеграл "разгоняется" до огромного значения. Потом, когда ошибка уменьшается, нужно много времени чтобы интеграл "разрядился" — система перерегулирует.</p><h3>D — Дифференциальная составляющая</h3><p>Реагирует на <strong>скорость изменения ошибки</strong>. Если ошибка быстро уменьшается (температура быстро растёт к уставке) — D-составляющая "притормаживает" для предотвращения перерегулирования.</p><p>Проблема: <strong>шум</strong>. Производная усиливает высокочастотный шум измерений. Любой дёрг датчика превращается в огромный кратковременный скачок управляющего сигнала. Поэтому D всегда применяется с фильтром.</p><hr><h2>Дискретная реализация (для цифровых систем)</h2><p>В реальных системах регулятор выполняется с дискретным шагом Ts (время выборки). Непрерывная формула преобразуется в разностное уравнение:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>P = Kp × e[k]
I = I[k-1] + Ki × e[k] × Ts
D = Kd × (e[k] - e[k-1]) / Ts

u[k] = P + I + D
</code></pre><p>Но правильная реализация сложнее. Полная профессиональная реализация:</p><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>class PIDController:
    """
    Промышленный ПИД-регулятор с:
    - Anti-windup (ограничение интеграла)
    - Производная по измерению (не по ошибке)
    - Фильтр производной
    - Ограничение выхода
    - Bump-less transfer (безударное переключение авто/ручной)
    """
    
    def __init__(self,
                 kp: float,
                 ki: float,
                 kd: float,
                 ts: float,              # Период дискретизации, секунды
                 out_min: float = 0.0,
                 out_max: float = 100.0,
                 filter_coeff: float = 0.1):  # Коэффициент фильтра D
        
        self.kp = kp
        self.ki = ki
        self.kd = kd
        self.ts = ts
        self.out_min = out_min
        self.out_max = out_max
        self.filter_coeff = filter_coeff  # Nf: чем меньше, тем сильнее фильтрация
        
        # Состояние
        self.setpoint  = 0.0
        self.integral  = 0.0
        self.prev_measurement = None
        self.filtered_deriv   = 0.0
        self.output    = 0.0
        
        # Режим
        self.auto_mode = True
        
    def set_setpoint(self, sp: float):
        self.setpoint = sp
    
    def set_manual(self, output: float):
        """Ручной режим — задать выход напрямую"""
        self.auto_mode = False
        self.output = max(self.out_min, min(self.out_max, output))
        # При переходе обратно в авто — интеграл синхронизируется
        
    def set_auto(self):
        """Переход в автоматический режим (bump-less)"""
        if not self.auto_mode:
            # Инициализируем интеграл текущим выходом
            # чтобы не было скачка при переключении
            self.integral = self.output
            self.auto_mode = True
    
    def compute(self, measurement: float) -&gt; float:
        """
        Вычислить управляющий сигнал.
        measurement — текущее значение регулируемой величины.
        Вызывать строго с периодом ts!
        """
        if not self.auto_mode:
            return self.output
        
        if self.prev_measurement is None:
            self.prev_measurement = measurement
        
        error = self.setpoint - measurement
        
        # === Пропорциональная составляющая ===
        p_term = self.kp * error
        
        # === Интегральная составляющая с anti-windup ===
        # Предварительно вычисляем интеграл
        integral_new = self.integral + self.ki * error * self.ts
        
        # === Дифференциальная составляющая ===
        # Производная по ИЗМЕРЕНИЮ (не по ошибке) — избегаем derivative kick
        # при изменении уставки
        raw_deriv = -(measurement - self.prev_measurement) / self.ts
        
        # Фильтр производной (экспоненциальный)
        self.filtered_deriv = (self.filter_coeff * raw_deriv + 
                               (1 - self.filter_coeff) * self.filtered_deriv)
        
        d_term = self.kd * self.filtered_deriv
        
        # === Предварительный выход ===
        output_raw = p_term + integral_new + d_term
        
        # === Ограничение выхода ===
        self.output = max(self.out_min, min(self.out_max, output_raw))
        
        # === Anti-windup: обновляем интеграл только если не насыщены ===
        # Или используем "back-calculation anti-windup"
        if self.output == output_raw:
            # Нет насыщения — обновляем интеграл нормально
            self.integral = integral_new
        else:
            # Насыщение — не накапливаем интеграл дальше
            # Back-calculation: уменьшаем интеграл на величину насыщения
            saturation = output_raw - self.output
            self.integral = integral_new - saturation
        
        self.prev_measurement = measurement
        
        return self.output
    
    def get_components(self) -&gt; dict:
        """Для отладки и мониторинга"""
        error = self.setpoint - (self.prev_measurement or 0)
        return {
            'setpoint':   self.setpoint,
            'error':      error,
            'p_term':     self.kp * error,
            'i_term':     self.integral,
            'd_term':     self.kd * self.filtered_deriv,
            'output':     self.output,
        }
</code></pre><hr><h2>Методы настройки коэффициентов</h2><h3>Метод 1: Инженерная настройка (руками)</h3><p>Алгоритм по шагам:</p><ol><li><p>Ki = 0, Kd = 0. Постепенно увеличиваем Kp до появления устойчивых колебаний (нарастающих) — это Kc. Уменьшаем Kp до 0.5 × Kc.</p></li><li><p>Медленно увеличиваем Ki. Он должен устранить остаточную ошибку. Если появились колебания — Ki велик.</p></li><li><p>Если нужно улучшить реакцию и уменьшить перерегулирование — добавляем Kd. Часто тепловые объекты обходятся без D.</p></li></ol><h3>Метод 2: Циглера-Николса (классика)</h3><ol><li><p>Установить Ki = 0, Kd = 0</p></li><li><p>Увеличивать Kp до возникновения незатухающих колебаний</p></li><li><p>Записать: Ku — критический коэффициент, Tu — период колебаний</p></li></ol><div class="tmiRichText__table-wrapper"><table style="min-width: 80px;"><colgroup><col style="min-width:20px;"><col style="min-width:20px;"><col style="min-width:20px;"><col style="min-width:20px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Тип</p></th><th colspan="1" rowspan="1"><p>Kp</p></th><th colspan="1" rowspan="1"><p>Ki</p></th><th colspan="1" rowspan="1"><p>Kd</p></th></tr><tr><td colspan="1" rowspan="1"><p>Только P</p></td><td colspan="1" rowspan="1"><p>0.5 × Ku</p></td><td colspan="1" rowspan="1"><p>—</p></td><td colspan="1" rowspan="1"><p>—</p></td></tr><tr><td colspan="1" rowspan="1"><p>PI</p></td><td colspan="1" rowspan="1"><p>0.45 × Ku</p></td><td colspan="1" rowspan="1"><p>0.54 × Ku / Tu</p></td><td colspan="1" rowspan="1"><p>—</p></td></tr><tr><td colspan="1" rowspan="1"><p>ПИД</p></td><td colspan="1" rowspan="1"><p>0.6 × Ku</p></td><td colspan="1" rowspan="1"><p>1.2 × Ku / Tu</p></td><td colspan="1" rowspan="1"><p>0.075 × Ku × Tu</p></td></tr></tbody></table></div><p><strong>Результат:</strong> Обычно агрессивный, дающий ~25% перерегулирования. Хорош как стартовая точка.</p><h3>Метод 3: Ступенчатый отклик (Step Response)</h3><p>Более безопасный метод — не нужно доводить до автоколебаний:</p><ol><li><p>Перевести систему в устойчивое состояние</p></li><li><p>Скачком изменить управляющий сигнал на 10–20%</p></li><li><p>Записать переходный процесс</p></li></ol><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit

def fit_first_order_plus_delay(t, data, step_size):
    """
    Аппроксимация объекта управления моделью первого порядка с запаздыванием:
    G(s) = K × exp(-L×s) / (T×s + 1)
    """
    # Нормализация данных
    y_norm = (data - data[0]) / step_size
    
    # Начальные параметры: K, T, L
    K_init = y_norm[-1]         # Статический коэффициент усиления
    T_init = t[np.argmin(np.abs(y_norm - 0.63 * K_init))]
    L_init = t[np.argmin(np.abs(y_norm - 0.05 * K_init))]
    
    def model(t, K, T, L):
        result = np.zeros_like(t)
        for i, ti in enumerate(t):
            if ti &gt; L:
                result[i] = K * (1 - np.exp(-(ti - L) / T))
        return result
    
    try:
        popt, _ = curve_fit(model, t, y_norm,
                            p0=[K_init, T_init, max(L_init, 0.001)],
                            bounds=([0, 0, 0], [np.inf, np.inf, np.inf]))
        K, T, L = popt
        return K, T, L
    except RuntimeError:
        return K_init, T_init, max(L_init, 0.001)

def imc_tuning(K, T, L, lambda_factor=1.0):
    """
    IMC (Internal Model Control) настройка — лучший метод для объектов
    с запаздыванием.
    lambda_factor: чем больше — тем медленнее, но стабильнее (рекомендуется L...3L)
    """
    lambda_c = lambda_factor * max(L, 0.1 * T)
    
    Kp = (2 * T + L) / (2 * K * (lambda_c + L))
    Ti = T + L / 2  # Постоянная интегрирования
    Td = T * L / (2 * T + L)  # Постоянная дифференцирования
    
    Ki = Kp / Ti
    Kd = Kp * Td
    
    return {
        'Kp': round(Kp, 4),
        'Ki': round(Ki, 4),
        'Kd': round(Kd, 4),
        'Ti': round(Ti, 4),
        'Td': round(Td, 4),
    }

# Пример использования:
# K=2.0 (усиление объекта), T=30с (постоянная времени), L=5с (запаздывание)
params = imc_tuning(K=2.0, T=30.0, L=5.0, lambda_factor=1.0)
print(f"Kp={params['Kp']}, Ki={params['Ki']}, Kd={params['Kd']}")
</code></pre><hr><h2>Симуляция и проверка настройки</h2><p>Перед применением в реальной системе — всегда симулируйте:</p><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>import numpy as np
import matplotlib.pyplot as plt

def simulate_pid(kp, ki, kd, setpoint, sim_time=200, ts=0.1,
                 K=2.0, T=30.0, L=5.0, noise_std=0.1):
    """
    Симуляция ПИД с объектом первого порядка + запаздывание.
    Возвращает массивы времени, выходного значения, управляющего сигнала.
    """
    
    steps = int(sim_time / ts)
    delay_steps = int(L / ts)
    
    t       = np.zeros(steps)
    pv      = np.zeros(steps)
    u       = np.zeros(steps)
    sp      = np.zeros(steps)
    
    pid = PIDController(kp, ki, kd, ts, out_min=0.0, out_max=100.0)
    
    # Ступенчатое изменение уставки
    sp[:steps//4] = 0
    sp[steps//4:] = setpoint
    
    # Буфер запаздывания
    u_delayed = np.zeros(delay_steps + 1)
    
    for i in range(1, steps):
        t[i] = i * ts
        sp_now = sp[i]
        pid.set_setpoint(sp_now)
        
        # Добавляем шум измерения
        noise = np.random.normal(0, noise_std)
        
        # Управляющий сигнал
        u[i] = pid.compute(pv[i-1] + noise)
        
        # Обновляем буфер запаздывания
        u_delayed = np.roll(u_delayed, 1)
        u_delayed[0] = u[i]
        
        # Объект управления: первый порядок с запаздыванием
        # dy/dt = (K × u_delayed - y) / T
        pv[i] = pv[i-1] + ts * (K * u_delayed[-1] - pv[i-1]) / T
    
    return t, pv, u, sp

# Сравнение разных настроек
fig, axes = plt.subplots(2, 1, figsize=(12, 8))

configs = [
    {'kp': 0.5, 'ki': 0.01, 'kd': 2.0, 'label': 'IMC (lambda=1)', 'color': 'blue'},
    {'kp': 1.5, 'ki': 0.05, 'kd': 5.0, 'label': 'Агрессивный', 'color': 'red'},
    {'kp': 0.2, 'ki': 0.005, 'kd': 0.5, 'label': 'Консервативный', 'color': 'green'},
]

for cfg in configs:
    t, pv, u, sp = simulate_pid(
        kp=cfg['kp'], ki=cfg['ki'], kd=cfg['kd'],
        setpoint=60.0
    )
    axes[0].plot(t, pv, label=cfg['label'], color=cfg['color'])
    axes[1].plot(t, u, color=cfg['color'], alpha=0.7)

axes[0].plot(t, sp, 'k--', label='Уставка', linewidth=2)
axes[0].set_ylabel('Температура, °C')
axes[0].set_title('Сравнение настроек ПИД-регулятора')
axes[0].legend()
axes[0].grid(True)

axes[1].set_xlabel('Время, с')
axes[1].set_ylabel('Управляющий сигнал, %')
axes[1].grid(True)

plt.tight_layout()
plt.savefig('pid_comparison.png', dpi=150)
</code></pre><hr><h2>Специальные случаи и решения</h2><h3>Каскадный ПИД (Cascade Control)</h3><p>Используется когда одного ПИД недостаточно. Классический пример: температурный реактор с нагревателем и теплоносителем.</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Внешний ПИД (медленный):
    SP температуры → [ПИД_темп] → SP расхода теплоносителя

Внутренний ПИД (быстрый):
    SP расхода → [ПИД_расход] → Управление клапаном
</code></pre><p>Внутренний контур в 5–10 раз быстрее внешнего. Преимущество: внутренний контур компенсирует возмущения по теплоносителю до того, как они повлияют на температуру.</p><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>class CascadePID:
    """Каскадный регулятор с двумя ПИД"""
    
    def __init__(self, outer_pid: PIDController, inner_pid: PIDController):
        self.outer = outer_pid
        self.inner = inner_pid
    
    def compute(self, outer_measurement: float, 
                       inner_measurement: float) -&gt; float:
        """
        outer_measurement — медленная переменная (температура)
        inner_measurement — быстрая переменная (расход)
        """
        # Внешний контур вырабатывает уставку для внутреннего
        inner_setpoint = self.outer.compute(outer_measurement)
        
        # Ограничиваем уставку внутреннего контура
        inner_setpoint = max(0.0, min(100.0, inner_setpoint))
        self.inner.set_setpoint(inner_setpoint)
        
        # Внутренний контур управляет исполнительным устройством
        return self.inner.compute(inner_measurement)
</code></pre><h3>ПИД с ограничением скорости изменения выхода (Rate Limiting)</h3><p>Некоторые исполнительные устройства не любят резких скачков (например, регулирующие клапана — быстрое перемещение вызывает гидроудары):</p><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>def rate_limited_output(new_output: float, prev_output: float,
                        max_rate: float, ts: float) -&gt; float:
    """
    Ограничивает скорость изменения выходного сигнала.
    max_rate — максимальное изменение в секунду (например, 10%/сек)
    """
    max_change = max_rate * ts
    change = new_output - prev_output
    change = max(-max_change, min(max_change, change))
    return prev_output + change
</code></pre><h3>ПИД для нестационарных объектов (Gain Scheduling)</h3><p>Если параметры объекта меняются в зависимости от рабочей точки (например, нагрев при низкой температуре работает иначе чем при высокой):</p><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>class GainSchedulingPID:
    """ПИД с переключением коэффициентов в зависимости от уставки"""
    
    def __init__(self):
        # Коэффициенты для разных диапазонов температур
        self.schedules = [
            # (max_sp, kp, ki, kd)
            (50.0,  2.0, 0.05, 1.0),   # Низкая температура
            (100.0, 1.5, 0.03, 0.8),   # Средняя
            (200.0, 1.0, 0.02, 0.5),   # Высокая (объект другой!)
        ]
        
        self.pid = PIDController(kp=2.0, ki=0.05, kd=1.0, ts=1.0)
    
    def update_gains(self, setpoint: float):
        for max_sp, kp, ki, kd in self.schedules:
            if setpoint &lt;= max_sp:
                self.pid.kp = kp
                self.pid.ki = ki
                self.pid.kd = kd
                break
    
    def compute(self, setpoint: float, measurement: float) -&gt; float:
        self.update_gains(setpoint)
        self.pid.set_setpoint(setpoint)
        return self.pid.compute(measurement)
</code></pre><hr><h2>Реализация на C для встраиваемых систем</h2><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>#include &lt;stdint.h&gt;
#include &lt;float.h&gt;

typedef struct {
    float kp;
    float ki;
    float kd;
    float ts;           // Период дискретизации, секунды
    float out_min;
    float out_max;
    float filter_coeff; // Коэффициент фильтра производной (0.01..0.2)
    
    // Состояние
    float setpoint;
    float integral;
    float prev_measurement;
    float filtered_deriv;
    float output;
    
    uint8_t first_run;
} PIDState;

void PID_Init(PIDState *pid, float kp, float ki, float kd, float ts,
              float out_min, float out_max)
{
    pid-&gt;kp = kp;
    pid-&gt;ki = ki;
    pid-&gt;kd = kd;
    pid-&gt;ts = ts;
    pid-&gt;out_min = out_min;
    pid-&gt;out_max = out_max;
    pid-&gt;filter_coeff = 0.1f;
    
    pid-&gt;setpoint          = 0.0f;
    pid-&gt;integral          = 0.0f;
    pid-&gt;prev_measurement  = 0.0f;
    pid-&gt;filtered_deriv    = 0.0f;
    pid-&gt;output            = 0.0f;
    pid-&gt;first_run         = 1;
}

void PID_SetSetpoint(PIDState *pid, float sp)
{
    pid-&gt;setpoint = sp;
}

void PID_Reset(PIDState *pid)
{
    pid-&gt;integral         = 0.0f;
    pid-&gt;filtered_deriv   = 0.0f;
    pid-&gt;first_run        = 1;
}

float PID_Compute(PIDState *pid, float measurement)
{
    float error, p_term, d_term, output_raw;
    float raw_deriv, integral_new;
    
    if (pid-&gt;first_run) {
        pid-&gt;prev_measurement = measurement;
        pid-&gt;first_run = 0;
    }
    
    error = pid-&gt;setpoint - measurement;
    
    // Пропорциональная
    p_term = pid-&gt;kp * error;
    
    // Интегральная (предварительный расчёт)
    integral_new = pid-&gt;integral + pid-&gt;ki * error * pid-&gt;ts;
    
    // Дифференциальная по измерению
    raw_deriv = -(measurement - pid-&gt;prev_measurement) / pid-&gt;ts;
    
    // Фильтр производной (IIR первого порядка)
    pid-&gt;filtered_deriv = pid-&gt;filter_coeff * raw_deriv +
                          (1.0f - pid-&gt;filter_coeff) * pid-&gt;filtered_deriv;
    d_term = pid-&gt;kd * pid-&gt;filtered_deriv;
    
    // Суммируем
    output_raw = p_term + integral_new + d_term;
    
    // Ограничение выхода
    if (output_raw &gt; pid-&gt;out_max)
        pid-&gt;output = pid-&gt;out_max;
    else if (output_raw &lt; pid-&gt;out_min)
        pid-&gt;output = pid-&gt;out_min;
    else
        pid-&gt;output = output_raw;
    
    // Anti-windup: обновляем интеграл только если нет насыщения
    if (pid-&gt;output == output_raw) {
        pid-&gt;integral = integral_new;
    } else {
        // Back-calculation
        float saturation = output_raw - pid-&gt;output;
        pid-&gt;integral = integral_new - saturation;
    }
    
    pid-&gt;prev_measurement = measurement;
    
    return pid-&gt;output;
}

// Использование на STM32/Arduino (в прерывании таймера):
PIDState temp_pid;

void PID_ISR(void)  // Вызывается каждые 100 мс
{
    float measured_temp = read_temperature_sensor();
    float heater_power = PID_Compute(&amp;temp_pid, measured_temp);
    
    // Перевести в ШИМ 0-255
    uint8_t pwm = (uint8_t)(heater_power * 255.0f / 100.0f);
    set_heater_pwm(pwm);
}
</code></pre><hr><h2>Диагностика плохой работы ПИД</h2><h3>Симптом: Медленный выход на уставку, большая статическая ошибка</h3><ul><li><p>Мало Kp и/или Ki</p></li><li><p>Проверьте нет ли механических ограничений (клапан не открывается полностью)</p></li><li><p>Проверьте масштабирование — правильно ли в % интерпретируется выход?</p></li></ul><h3>Симптом: Сильные устойчивые колебания</h3><ul><li><p>Kp слишком велик</p></li><li><p>Ki слишком велик (самая частая причина!)</p></li><li><p>Период дискретизации слишком большой относительно динамики объекта</p></li></ul><h3>Симптом: Большое перерегулирование при изменении уставки</h3><ul><li><p>Велико Kp, увеличьте Kd</p></li><li><p>Используйте "setpoint weighting": P-составляющую считайте как Kp × (w × SP – PV), где w &lt; 1</p></li></ul><h3>Симптом: Шумный выход, дёргает исполнительный механизм</h3><ul><li><p>Шум датчика, увеличьте filter_coeff для фильтрации D</p></li><li><p>Уменьшите Kd</p></li><li><p>Добавьте мёртвую зону: не реагировать если |error| &lt; deadband</p></li></ul><hr><h2>Заключение</h2><p>ПИД-регулятор — это не просто формула, это искусство. Одни и те же Kp, Ki, Kd дадут отличные результаты на одном объекте и катастрофу на другом. Понимание физики объекта управления важнее знания формул.</p><p>Практический совет: начинайте с IMC-метода настройки — он даёт предсказуемые результаты и физический смысл у параметра lambda (желаемая скорость реакции). Используйте симуляцию перед внедрением. Добавляйте anti-windup всегда — без него реальная система будет вести себя непредсказуемо после насыщения.</p><p>И помните: 80% задач управления решается PI-регулятором (без D). D добавляйте только когда PI действительно недостаточно и когда шум измерений не является проблемой.</p>]]></description><guid isPermaLink="false">104</guid><pubDate>Sat, 21 Mar 2026 16:50:46 +0000</pubDate></item><item><title>CAN Bus: &#x43F;&#x440;&#x43E;&#x442;&#x43E;&#x43A;&#x43E;&#x43B;, &#x43A;&#x43E;&#x442;&#x43E;&#x440;&#x44B;&#x439; &#x434;&#x435;&#x440;&#x436;&#x438;&#x442; &#x430;&#x432;&#x442;&#x43E;&#x43C;&#x43E;&#x431;&#x438;&#x43B;&#x438; &#x438; &#x437;&#x430;&#x432;&#x43E;&#x434;&#x44B;</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/can-bus-%D0%BF%D1%80%D0%BE%D1%82%D0%BE%D0%BA%D0%BE%D0%BB-%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D1%8B%D0%B9-%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D1%82-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE%D0%B1%D0%B8%D0%BB%D0%B8-%D0%B8-%D0%B7%D0%B0%D0%B2%D0%BE%D0%B4%D1%8B-r110/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/Pros-Cons-of-CAN-Bus-in-Industrial-Automation-Strengths.webp.6fe2d5a75292f731178051f5282c3f8b.webp" /></p>
<h2>Почему CAN Bus? История одного протокола</h2><p>1983 год. Инженеры Bosch смотрят на жгут проводки в Mercedes-Benz W126 и понимают, что так продолжаться не может. 1000+ метров провода, сотни коннекторов — система ненадёжна, дорога и тяжела. Им нужна шина данных, по которой все блоки управления могут общаться.</p><p>В 1986 году появляется CAN (Controller Area Network). В 1991 году Mercedes внедряет CAN в S-класс. Сегодня нет ни одного автомобиля без CAN. И не только автомобиля: промышленные роботы, строительная техника, медицинское оборудование, поезда, самолёты.</p><p>Секреты успеха:</p><ul><li><p><strong>Надёжность</strong> — дифференциальный сигнал, устойчив к помехам</p></li><li><p><strong>Детерминизм</strong> — приоритетная схема без коллизий</p></li><li><p><strong>Простота</strong> — только 2 провода (CANH и CANL)</p></li><li><p><strong>Скорость</strong> — до 1 Мбит/с (Classic CAN), до 8 Мбит/с (CAN FD)</p></li></ul><hr><h2>Физический уровень: как это работает</h2><p>CAN использует <strong>дифференциальную пару</strong>: два провода CANH и CANL. Информация кодируется <strong>разностью напряжений</strong>, а не абсолютным значением.</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Рецессивный бит (логическая 1):
CANH ≈ 2.5В, CANL ≈ 2.5В → разность ≈ 0В

Доминантный бит (логический 0):
CANH ≈ 3.5В, CANL ≈ 1.5В → разность ≈ 2В
</code></pre><p>Дифференциальный сигнал нечувствителен к синфазным помехам — если на оба провода наводится шум, разность остаётся неизменной. Именно поэтому CAN работает в двигательном отсеке автомобиля рядом с высоковольтной проводкой зажигания.</p><h3>Топология</h3><p>Строго <strong>линейная шина</strong> с терминаторами 120 Ом на концах:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>[Узел A]──────[Узел B]──────[Узел C]──────[Узел D]
    120Ом                                     120Ом
</code></pre><p>Максимальная длина зависит от скорости:</p><div class="tmiRichText__table-wrapper"><table style="width: 349px;"><colgroup><col style="width:197px;"><col style="width:152px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Скорость</p></th><th colspan="1" rowspan="1"><p>Макс. длина</p></th></tr><tr><td colspan="1" rowspan="1"><p>1 Мбит/с</p></td><td colspan="1" rowspan="1"><p>25 м</p></td></tr><tr><td colspan="1" rowspan="1"><p>500 Кбит/с</p></td><td colspan="1" rowspan="1"><p>100 м</p></td></tr><tr><td colspan="1" rowspan="1"><p>250 Кбит/с</p></td><td colspan="1" rowspan="1"><p>250 м</p></td></tr><tr><td colspan="1" rowspan="1"><p>125 Кбит/с</p></td><td colspan="1" rowspan="1"><p>500 м</p></td></tr><tr><td colspan="1" rowspan="1"><p>10 Кбит/с</p></td><td colspan="1" rowspan="1"><p>5000 м</p></td></tr></tbody></table></div><hr><h2>Структура CAN-фрейма (Standard 11-bit)</h2><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>SOF│ Identifier (11 бит) │RTR│IDE│r0│ DLC (4) │ Data (0-8 байт) │ CRC │ACK│ EOF
 1       11               1   1   1      4           0-64           15   2    7
</code></pre><p><strong>SOF (Start of Frame):</strong> 1 доминантный бит — все узлы синхронизируются.</p><p><strong>Identifier (ID, 11 бит):</strong> Идентифицирует <strong>тип сообщения</strong>, а не адрес отправителя/получателя. Одновременно определяет приоритет — чем меньше ID, тем выше приоритет. ID=0 — наивысший приоритет.</p><p><strong>RTR (Remote Transmission Request):</strong> Запрос данных от другого узла (редко используется).</p><p><strong>DLC (Data Length Code):</strong> Количество байт данных, 0–8.</p><p><strong>Data:</strong> Полезные данные, 0–8 байт.</p><p><strong>CRC (15 бит):</strong> Контрольная сумма для обнаружения ошибок.</p><p><strong>ACK:</strong> Все узлы, успешно принявшие фрейм, устанавливают доминантный бит в поле ACK. Отправитель проверяет — если ACK не получен, повторяет передачу.</p><h3>Extended Frame (29-bit ID)</h3><p>Для приложений где 2048 идентификаторов мало (J1939, CANopen):</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>SOF│ ID_A (11) │SRR│IDE=1│ ID_B (18 бит) │RTR│...
</code></pre><p>29-битный ID даёт 536 870 912 возможных идентификаторов.</p><hr><h2>Битовый арбитраж: без коллизий</h2><p>Самая элегантная часть CAN. Когда два узла начинают передачу одновременно — нет коллизии, как в Ethernet. Побеждает тот, у кого ID меньше (приоритетнее).</p><p>Механизм: каждый передающий узел одновременно читает шину. Пока он видит то, что передаёт — продолжает. Как только видит расхождение (отправил рецессивный 1, а на шине доминантный 0 — значит другой узел передаёт доминантный бит с более высоким приоритетом) — немедленно прекращает передачу и переходит в режим приёма.</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Узел A: 0  0  0  1  0  ... (ID = 0b00010...)
Узел B: 0  0  0  0  1  ... (ID = 0b00001...)

Бит 4:
  Узел A передаёт рецессивный (1)
  Узел B передаёт доминантный (0)
  Шина показывает доминантный (0)
  → Узел A видит расхождение и ОСТАНАВЛИВАЕТСЯ
  → Узел B продолжает передачу

Узел B выиграл арбитраж! Нет потерянных данных, нет задержек.
</code></pre><hr><h2>Обработка ошибок и состояния узла</h2><p>CAN имеет развитую систему самодиагностики. Каждый узел ведёт два счётчика:</p><ul><li><p><strong>TEC</strong> (Transmit Error Counter)</p></li><li><p><strong>REC</strong> (Receive Error Counter)</p></li></ul><p>Состояния узла:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Error Active (TEC&lt;128, REC&lt;128) — Нормальная работа
           ↓ TEC или REC ≥ 128
Error Passive (TEC≥128 или REC≥128) — Узел работает, но:
           - Не посылает Active Error Flags
           - Ждёт 8 рецессивных бит между передачами
           ↓ TEC ≥ 256
Bus Off — Узел ОТКЛЮЧЁН от шины
           (требует программного сброса или 128×11 рецессивных бит)
</code></pre><p>Это важно: неисправный узел не "разваливает" шину, а сначала становится пассивным, затем отключается — остальные продолжают работать.</p><hr><h2>STM32: встроенный CAN-контроллер</h2><p>STM32F103 имеет встроенный bxCAN (Basic Extended CAN). Пины: PA11/PA12 или PB8/PB9.</p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>#include "stm32f1xx_hal.h"

CAN_HandleTypeDef hcan;

// ===== ИНИЦИАЛИЗАЦИЯ CAN 500 Кбит/с =====
void CAN_Init_500kbps(void)
{
    hcan.Instance = CAN1;
    
    // Тайминг для 500 Кбит/с при тактовой 36 МГц
    // Bit time = Prescaler × (1 + BS1 + BS2)
    // 36 МГц / 4 / (1+7+2) = 900 Кбит/с ... нет, подберём:
    // 36 МГц / 9 / (1+3+2) = 500 Кбит/с ← правильно
    hcan.Init.Prescaler  = 9;
    hcan.Init.Mode       = CAN_MODE_NORMAL;   // CAN_MODE_LOOPBACK для теста!
    hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
    hcan.Init.TimeSeg1   = CAN_BS1_3TQ;      // BS1 = 3 TQ
    hcan.Init.TimeSeg2   = CAN_BS2_2TQ;      // BS2 = 2 TQ
    hcan.Init.TimeTriggeredMode   = DISABLE;
    hcan.Init.AutoBusOff          = ENABLE;   // Автоматический выход из Bus-Off
    hcan.Init.AutoWakeUp          = DISABLE;
    hcan.Init.AutoRetransmission  = ENABLE;   // Автоповтор при ошибках
    hcan.Init.ReceiveFifoLocked   = DISABLE;
    hcan.Init.TransmitFifoPriority= DISABLE;
    
    HAL_CAN_Init(&amp;hcan);
    
    // ===== ФИЛЬТР ПРИЁМА =====
    CAN_FilterTypeDef filter = {0};
    
    // Принимать ВСЕ сообщения (маска 0 — все биты любые)
    filter.FilterBank           = 0;
    filter.FilterMode           = CAN_FILTERMODE_IDMASK;
    filter.FilterScale          = CAN_FILTERSCALE_32BIT;
    filter.FilterIdHigh         = 0x0000;
    filter.FilterIdLow          = 0x0000;
    filter.FilterMaskIdHigh     = 0x0000;  // Маска 0 = принимать всё
    filter.FilterMaskIdLow      = 0x0000;
    filter.FilterFIFOAssignment = CAN_RX_FIFO0;
    filter.FilterActivation     = ENABLE;
    
    HAL_CAN_ConfigFilter(&amp;hcan, &amp;filter);
    
    // Активировать прерывания приёма
    HAL_CAN_ActivateNotification(&amp;hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
    
    HAL_CAN_Start(&amp;hcan);
}

// ===== ОТПРАВКА СООБЩЕНИЯ =====
HAL_StatusTypeDef CAN_SendMessage(uint32_t id, uint8_t *data, uint8_t len)
{
    CAN_TxHeaderTypeDef txHeader;
    uint32_t txMailbox;
    
    txHeader.StdId              = id;        // 11-битный ID
    txHeader.ExtId              = 0;
    txHeader.IDE                = CAN_ID_STD;
    txHeader.RTR                = CAN_RTR_DATA;
    txHeader.DLC                = len;
    txHeader.TransmitGlobalTime = DISABLE;
    
    return HAL_CAN_AddTxMessage(&amp;hcan, &amp;txHeader, data, &amp;txMailbox);
}

// ===== ПРИЁМ ЧЕРЕЗ ПРЕРЫВАНИЕ =====
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan_ptr)
{
    CAN_RxHeaderTypeDef rxHeader;
    uint8_t rxData[8];
    
    if (HAL_CAN_GetRxMessage(hcan_ptr, CAN_RX_FIFO0, &amp;rxHeader, rxData) == HAL_OK) {
        
        uint32_t id = (rxHeader.IDE == CAN_ID_STD) ? rxHeader.StdId : rxHeader.ExtId;
        
        // Обработка по ID
        switch (id) {
            case 0x100:  // Состояние узла 1
                process_node1_status(rxData, rxHeader.DLC);
                break;
                
            case 0x200:  // Измерения температуры
                process_temperature_data(rxData, rxHeader.DLC);
                break;
                
            default:
                // Неизвестное сообщение
                break;
        }
    }
}

// Пример обработки температурных данных
// Договорённость: 2 байта = температура × 10 (signed)
void process_temperature_data(uint8_t *data, uint8_t len)
{
    if (len &lt; 2) return;
    
    int16_t raw = (int16_t)((data[0] &lt;&lt; 8) | data[1]);
    float temperature = raw / 10.0f;
    
    if (temperature &gt; 80.0f) {
        // Высокая температура — принять меры
        activate_cooling();
    }
}

// ===== ПРИМЕР: ПЕРИОДИЧЕСКАЯ ОТПРАВКА ДАННЫХ УЗЛА =====
void CAN_SendNodeStatus(void)
{
    uint8_t data[8];
    
    // Байт 0: статусные биты
    data[0] = 0x00;
    if (motor_running)     data[0] |= 0x01;
    if (fault_active)      data[0] |= 0x02;
    if (io_ready)          data[0] |= 0x04;
    
    // Байты 1-2: скорость двигателя (об/мин × 10, unsigned)
    uint16_t speed_raw = (uint16_t)(motor_speed_rpm * 10.0f);
    data[1] = speed_raw &gt;&gt; 8;
    data[2] = speed_raw &amp; 0xFF;
    
    // Байты 3-4: ток (А × 100, signed)
    int16_t current_raw = (int16_t)(motor_current_a * 100.0f);
    data[3] = current_raw &gt;&gt; 8;
    data[4] = current_raw &amp; 0xFF;
    
    // Байты 5-6: температура (°C × 10, signed)
    int16_t temp_raw = (int16_t)(temperature_c * 10.0f);
    data[5] = temp_raw &gt;&gt; 8;
    data[6] = temp_raw &amp; 0xFF;
    
    // Байт 7: номер пакета (для обнаружения потерь)
    static uint8_t packet_num = 0;
    data[7] = packet_num++;
    
    CAN_SendMessage(0x100, data, 8);
}
</code></pre><hr><h2>Arduino + MCP2515: добавляем CAN</h2><p>Arduino не имеет встроенного CAN. Используем MCP2515 — внешний CAN-контроллер с SPI.</p><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>#include &lt;SPI.h&gt;
#include &lt;mcp2515.h&gt;  // Библиотека arduino-mcp2515

MCP2515 mcp2515(10);  // CS pin

struct can_frame rxMsg, txMsg;

void setup() {
    Serial.begin(115200);
    SPI.begin();
    
    mcp2515.reset();
    mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ);  // 500 Кбит/с, кварц 8 МГц
    mcp2515.setNormalMode();  // или setLoopbackMode() для теста без шины
    
    Serial.println("CAN Bus Ready");
}

// Отправка измерений температуры
void sendTemperature(float temp_celsius) {
    txMsg.can_id  = 0x200;  // ID нашего сообщения
    txMsg.can_dlc = 2;      // 2 байта данных
    
    // Упаковываем: температура × 10, int16
    int16_t raw = (int16_t)(temp_celsius * 10.0f);
    txMsg.data[0] = raw &gt;&gt; 8;
    txMsg.data[1] = raw &amp; 0xFF;
    
    mcp2515.sendMessage(&amp;txMsg);
}

void loop() {
    // Приём сообщений
    if (mcp2515.readMessage(&amp;rxMsg) == MCP2515::ERROR_OK) {
        Serial.print("ID: 0x");
        Serial.print(rxMsg.can_id, HEX);
        Serial.print(", DLC: ");
        Serial.print(rxMsg.can_dlc);
        Serial.print(", Data: ");
        
        for (int i = 0; i &lt; rxMsg.can_dlc; i++) {
            Serial.print("0x");
            Serial.print(rxMsg.data[i], HEX);
            Serial.print(" ");
        }
        Serial.println();
        
        // Обработка сообщения статуса узла
        if (rxMsg.can_id == 0x100 &amp;&amp; rxMsg.can_dlc &gt;= 3) {
            bool running = rxMsg.data[0] &amp; 0x01;
            bool fault   = rxMsg.data[0] &amp; 0x02;
            uint16_t speed_raw = (rxMsg.data[1] &lt;&lt; 8) | rxMsg.data[2];
            float speed = speed_raw / 10.0f;
            
            Serial.print("Узел 1: ");
            Serial.print(running ? "Работает" : "Стоит");
            if (fault) Serial.print(" [АВАРИЯ]");
            Serial.print(", Скорость: ");
            Serial.print(speed);
            Serial.println(" об/мин");
        }
    }
    
    // Отправка своих данных каждые 100 мс
    static uint32_t lastSend = 0;
    if (millis() - lastSend &gt;= 100) {
        lastSend = millis();
        float temp = 25.0f + analogRead(A0) * 0.05f;
        sendTemperature(temp);
    }
}
</code></pre><hr><h2>Linux SocketCAN: мощный инструментарий</h2><p>На Linux (Raspberry Pi, промышленные PC) CAN работает через SocketCAN — часть ядра с 2009 года.</p><pre spellcheck="" class="tmiCode language-bash" data-language="Bash"><code># Настройка CAN интерфейса
# Если есть USB-CAN адаптер (Peak PCAN, Kvaser, SeedStudio)
sudo ip link set can0 up type can bitrate 500000

# Или через модуль ядра для MCP2515 на Raspberry Pi
# /boot/config.txt:
# dtoverlay=mcp2515-can0,oscillator=8000000,interrupt=25

# Просмотр трафика
candump can0

# Отправка сообщения: ID=0x100, 3 байта
cansend can0 100#010203

# Фильтрация - только ID 0x100-0x1FF
candump can0 100~1FF

# Статистика ошибок
ip -details -statistics link show can0
</code></pre><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code># Python + python-can
# pip install python-can

import can
import struct
import time

# Создаём интерфейс
bus = can.interface.Bus(channel='can0', bustype='socketcan')

# Отправка
def send_temperature(temp: float):
    raw = struct.pack('&gt;h', int(temp * 10))  # big-endian signed short
    msg = can.Message(arbitration_id=0x200, data=raw, is_extended_id=False)
    bus.send(msg)

# Приём с фильтрацией по ID
bus.set_filters([
    {"can_id": 0x100, "can_mask": 0x7FF, "extended": False},  # Только 0x100
    {"can_id": 0x200, "can_mask": 0x7FF, "extended": False},  # И 0x200
])

print("Ожидаем CAN-сообщения...")
for msg in bus:
    if msg.arbitration_id == 0x100:
        status  = msg.data[0]
        speed   = struct.unpack('&gt;H', bytes(msg.data[1:3]))[0] / 10.0
        current = struct.unpack('&gt;h', bytes(msg.data[3:5]))[0] / 100.0
        temp    = struct.unpack('&gt;h', bytes(msg.data[5:7]))[0] / 10.0
        
        print(f"Узел 100: {'Работает' if status &amp; 1 else 'Стоит'}, "
              f"n={speed} об/мин, I={current}А, T={temp}°C")
</code></pre><hr><h2>Протоколы высшего уровня</h2><h3>J1939 — для грузовой техники и дизелей</h3><p>J1939 — стандарт SAE для коммуникации в коммерческом транспорте и тяжёлой технике. Работает поверх CAN с 29-битными ID.</p><p>Структура J1939 ID (29 бит):</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Приоритет (3б) │ Reserved (1б) │ Data Page (1б) │ PGN (8б) │ Source Address (8б)
</code></pre><p>Популярные PGN (Parameter Group Number):</p><ul><li><p><code>PGN 0xF004 (EEC1)</code> — данные двигателя: обороты, момент, нагрузка</p></li><li><p><code>PGN 0xFEF1 (CCVS)</code> — скорость, круиз-контроль</p></li><li><p><code>PGN 0xFEE5 (HOURS)</code> — моточасы</p></li><li><p><code>PGN 0xFEE6 (TIME)</code> — время и дата</p></li></ul><h3>CANopen — для промышленной автоматизации</h3><p>CANopen — стандарт для промышленного оборудования: частотники, серводрайвы, I/O модули. Определяет:</p><ul><li><p><strong>Словарь объектов (Object Dictionary)</strong> — структурированное хранилище всех параметров устройства</p></li><li><p><strong>PDO (Process Data Object)</strong> — быстрая передача данных реального времени (заменяет аналог 4-20мА)</p></li><li><p><strong>SDO (Service Data Object)</strong> — медленная конфигурация и чтение параметров</p></li><li><p><strong>NMT (Network Management)</strong> — управление состоянием узлов</p></li><li><p><strong>Heartbeat / Node Guarding</strong> — контроль жизнеспособности узлов</p></li></ul><p>Пример: частотник с CANopen. Через SDO читаем/пишем параметры (коэффициент разгона, макс. частота). Через PDO каждые 10 мс обмениваемся уставкой и текущими значениями.</p><hr><h2>Диагностика сети CAN</h2><h3>Признаки проблем:</h3><div class="tmiRichText__table-wrapper"><table style="width: 712px;"><colgroup><col style="width:346px;"><col style="width:366px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Симптом</p></th><th colspan="1" rowspan="1"><p>Вероятная причина</p></th></tr><tr><td colspan="1" rowspan="1"><p>Bus Off у одного узла</p></td><td colspan="1" rowspan="1"><p>Неисправный узел, помехи на кабеле</p></td></tr><tr><td colspan="1" rowspan="1"><p>Много ошибок CRC</p></td><td colspan="1" rowspan="1"><p>Неправильный биттайминг, плохая линия</p></td></tr><tr><td colspan="1" rowspan="1"><p>Узел не видит свои сообщения в ACK</p></td><td colspan="1" rowspan="1"><p>Он один на шине, некому подтверждать</p></td></tr><tr><td colspan="1" rowspan="1"><p>Периодические потери сообщений</p></td><td colspan="1" rowspan="1"><p>Нет терминаторов или два на одном конце</p></td></tr><tr><td colspan="1" rowspan="1"><p>Все узлы в Bus Off</p></td><td colspan="1" rowspan="1"><p>Неисправная нагрузка на шине</p></td></tr></tbody></table></div><h3>Инструменты:</h3><ul><li><p><strong>PEAK PCAN-USB</strong> (~€80) + <strong>PCAN-View</strong> (бесплатно) — лучший бюджетный вариант</p></li><li><p><strong>Kvaser Leaf Light</strong> — популярен с CANalyzer</p></li><li><p><strong>Vector CANalyzer</strong> — профессиональный инструмент для автомобильных применений</p></li><li><p><strong>Linux candump / cansniffer</strong> — бесплатно, для socketcan-интерфейсов</p></li><li><p><strong>Wireshark</strong> с плагином SocketCAN — анализ на Linux</p></li></ul><hr><h2>CAN FD: следующее поколение</h2><p>CAN FD (Flexible Data-rate) — развитие стандарта 2012 года. Нет обратной совместимости на физическом уровне, но концептуально тот же подход.</p><p>Отличия от Classic CAN:</p><ul><li><p><strong>До 64 байт</strong> данных в одном фрейме (против 8)</p></li><li><p><strong>До 8 Мбит/с</strong> в поле данных (при сохранении 1 Мбит/с для арбитража)</p></li><li><p>Обязательная CRC 21-бит для надёжности при высоких скоростях</p></li></ul><p>CAN FD активно внедряется в новых автомобилях (все автомобили с AUTOSAR) и промышленной автоматизации. STM32G4, STM32H7 имеют встроенный FDCAN-контроллер.</p><hr><h2>Практический чеклист для CAN-системы</h2><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>□ Терминаторы 120 Ом на ОБОИХ концах шины (и только там)
□ Экранированная витая пара (для помехонагруженных сред)
□ Максимальная длина ответвлений (stub) &lt; 0.3 м
□ Все узлы имеют ОДИНАКОВУЮ скорость и биттайминг
□ Адреса узлов (если используются) уникальны
□ Заземление: один общий провод + заземление экрана в одной точке
□ Проверить напряжение CANH/CANL (рецесс.: оба ~2.5В, домин.: разность ~2В)
□ Подключить анализатор и убедиться в отсутствии ошибок
□ Задокументировать все ID и значения данных
</code></pre><hr><h2>Заключение</h2><p>CAN Bus — это элегантное инженерное решение, выдержавшее испытание десятилетиями. Детерминизм без коллизий, встроенная обработка ошибок, устойчивость к помехам — всё это делает CAN первым выбором для распределённых систем управления с жёсткими требованиями к надёжности.</p><p>Для старта: MCP2515 + Arduino даёт минимальный стенд за $5. SocketCAN на Raspberry Pi — бесплатный анализатор. PCAN-USB — профессиональный инструмент за разумные деньги.</p><p>Знание CAN открывает двери в automotive-разработку, промышленную автоматизацию и встраиваемые системы. Это инвестиция, которая окупается.</p>]]></description><guid isPermaLink="false">110</guid><pubDate>Sat, 21 Mar 2026 17:24:59 +0000</pubDate></item><item><title>FreeRTOS: &#x43E;&#x43F;&#x435;&#x440;&#x430;&#x446;&#x438;&#x43E;&#x43D;&#x43D;&#x430;&#x44F; &#x441;&#x438;&#x441;&#x442;&#x435;&#x43C;&#x430; &#x440;&#x435;&#x430;&#x43B;&#x44C;&#x43D;&#x43E;&#x433;&#x43E; &#x432;&#x440;&#x435;&#x43C;&#x435;&#x43D;&#x438; &#x434;&#x43B;&#x44F; &#x432;&#x441;&#x442;&#x440;&#x430;&#x438;&#x432;&#x430;&#x435;&#x43C;&#x44B;&#x445; &#x441;&#x438;&#x441;&#x442;&#x435;&#x43C;</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/freertos-%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%B0%D1%8F-%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0-%D1%80%D0%B5%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE-%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%B8-%D0%B4%D0%BB%D1%8F-%D0%B2%D1%81%D1%82%D1%80%D0%B0%D0%B8%D0%B2%D0%B0%D0%B5%D0%BC%D1%8B%D1%85-%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC-r122/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/free-rtos-featured-1.jpeg.a376a3687c0f994e3d77a65ce027efbd.jpeg" /></p>
<h2>Зачем RTOS на микроконтроллере</h2><p>Простой проект — один <code>while(1)</code> цикл. Всё хорошо: считали датчик, обновили дисплей, проверили кнопку. Но что если:</p><ul><li><p>Нужно принять UART-пакет точно за 10 мс, иначе потеряем байты</p></li><li><p>Одновременно управлять тремя независимыми ПИД-контурами</p></li><li><p>Обрабатывать CAN-сообщения с задержкой не более 5 мс</p></li><li><p>И параллельно вести логирование на SD-карту</p></li></ul><p>Суперцикл (<code>while(1)</code>) ломается: длинная операция блокирует всё остальное. Прерывания помогают, но сложная логика в прерываниях — путь к хаосу.</p><p><strong>FreeRTOS решает это элегантно:</strong> каждая задача — отдельный "поток" со своим стеком и приоритетом. Планировщик переключает их так быстро (обычно каждые 1 мс), что кажется будто они работают одновременно. Задача с высоким приоритетом всегда получает процессор раньше.</p><hr><h2>Ключевые концепции FreeRTOS</h2><h3>Task (Задача)</h3><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Прототип задачи — бесконечный цикл!
void vTaskFunction(void *pvParameters)
{
    // Инициализация задачи
    int *param = (int *)pvParameters;
    
    for (;;)  // Никогда не выходит!
    {
        // Работа задачи...
        
        // Уступить процессор (обязательно в каждом цикле!)
        vTaskDelay(pdMS_TO_TICKS(100));  // Пауза 100 мс
    }
    
    vTaskDelete(NULL);  // Никогда не достигается, но хорошая практика
}

// Создание задачи:
TaskHandle_t xTaskHandle = NULL;

xTaskCreate(
    vTaskFunction,       // Функция задачи
    "TaskName",          // Имя (для отладки)
    configMINIMAL_STACK_SIZE * 4,  // Размер стека в словах
    NULL,                // Параметр (pvParameters)
    tskIDLE_PRIORITY + 2, // Приоритет (выше = важнее)
    &amp;xTaskHandle         // Хендл задачи
);
</code></pre><h3>Приоритеты</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>configMAX_PRIORITIES = 7 (типично)

Приоритет 6: КРИТИЧЕСКИЙ (ISR-уровень, прерывания)
Приоритет 5: Коммуникации реального времени (CAN, UART)
Приоритет 4: Управление (ПИД-контроллеры)
Приоритет 3: Мониторинг, аварийная логика
Приоритет 2: UI, дисплей, кнопки
Приоритет 1: Логирование, некритичные задачи
Приоритет 0: Idle task (только когда все остальные ждут)
</code></pre><hr><h2>Архитектура многозадачного приложения</h2><p>Реальный пример: контроллер насосной станции на STM32F4.</p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// ===== ЗАГОЛОВКИ =====
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "timers.h"

// ===== ГЛОБАЛЬНЫЕ ОБЪЕКТЫ FreeRTOS =====
QueueHandle_t   xSensorQueue;       // Данные датчиков
QueueHandle_t   xCommandQueue;      // Команды управления
QueueHandle_t   xLogQueue;          // Сообщения лога
SemaphoreHandle_t xI2CMutex;        // Защита I2C шины
SemaphoreHandle_t xUARTMutex;       // Защита UART (printf)
TimerHandle_t   xHeartbeatTimer;    // Мигание LED watchdog

// ===== СТРУКТУРЫ ДАННЫХ =====

typedef struct {
    float    temperature;
    float    pressure;
    float    flow;
    uint32_t timestamp_ms;
    uint8_t  quality;   // 0=BAD, 1=UNCERTAIN, 2=GOOD
} SensorData_t;

typedef enum {
    CMD_START,
    CMD_STOP,
    CMD_SET_SETPOINT,
    CMD_RESET_FAULT,
} CommandType_t;

typedef struct {
    CommandType_t type;
    float         value;
    uint8_t       source;  // 0=HMI, 1=Modbus, 2=Auto
} Command_t;

typedef struct {
    char     message[80];
    uint8_t  level;   // 0=DEBUG, 1=INFO, 2=WARN, 3=ERROR
    uint32_t timestamp_ms;
} LogMessage_t;

// ===== ВСПОМОГАТЕЛЬНЫЙ МАКРОС ДЛЯ PRINTF =====
// Потокобезопасный printf через мьютекс
#define LOG(level, fmt, ...) do { \
    LogMessage_t msg; \
    msg.level = (level); \
    msg.timestamp_ms = xTaskGetTickCount(); \
    snprintf(msg.message, sizeof(msg.message), fmt, ##__VA_ARGS__); \
    xQueueSend(xLogQueue, &amp;msg, 0); \
} while(0)

#define LOG_INFO(fmt,...)  LOG(1, fmt, ##__VA_ARGS__)
#define LOG_WARN(fmt,...)  LOG(2, "[WARN] " fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt,...) LOG(3, "[ERR!] " fmt, ##__VA_ARGS__)

// ===== ЗАДАЧА 1: ЧТЕНИЕ ДАТЧИКОВ (Приоритет 4) =====
static void vSensorTask(void *pvParam)
{
    SensorData_t data;
    TickType_t   xLastWakeTime = xTaskGetTickCount();
    const TickType_t xPeriod = pdMS_TO_TICKS(100);  // 10 Гц
    
    LOG_INFO("Sensor task started");
    
    for (;;)
    {
        // Ждём ровно 100 мс от последнего пробуждения
        // vTaskDelayUntil гарантирует точный период!
        vTaskDelayUntil(&amp;xLastWakeTime, xPeriod);
        
        data.timestamp_ms = xTaskGetTickCount();
        
        // Захватываем I2C шину
        if (xSemaphoreTake(xI2CMutex, pdMS_TO_TICKS(50)) == pdTRUE)
        {
            data.temperature = BMP280_ReadTemperature();
            data.pressure    = BMP280_ReadPressure();
            data.quality     = 2;  // GOOD
            xSemaphoreGive(xI2CMutex);
        }
        else
        {
            // I2C занята дольше 50 мс — что-то пошло не так
            data.quality = 0;  // BAD
            LOG_WARN("I2C timeout in sensor task");
        }
        
        // Читаем расходомер через 4-20мА
        data.flow = ADC_ReadFlow();
        
        // Отправляем данные в очередь (не блокируем — если полная, пропускаем)
        if (xQueueSend(xSensorQueue, &amp;data, 0) != pdTRUE)
        {
            LOG_WARN("Sensor queue full!");
        }
    }
}

// ===== ЗАДАЧА 2: ПИД-УПРАВЛЕНИЕ (Приоритет 5) =====
static void vControlTask(void *pvParam)
{
    SensorData_t sensorData;
    Command_t    command;
    
    float setpoint = 5.0f;   // Уставка давления, бар
    float output   = 0.0f;
    bool  running  = false;
    bool  fault    = false;
    
    // ПИД параметры
    float kp = 2.0f, ki = 0.5f, kd = 0.1f;
    float integral = 0.0f, prevError = 0.0f;
    const float TS = 0.1f;  // Совпадает с периодом датчиков
    
    LOG_INFO("Control task started");
    
    for (;;)
    {
        // Ждём новые данные датчиков (блокирующий ждём до 200 мс)
        if (xQueueReceive(xSensorQueue, &amp;sensorData, pdMS_TO_TICKS(200)) == pdTRUE)
        {
            // Проверяем входящие команды (неблокирующий)
            while (xQueueReceive(xCommandQueue, &amp;command, 0) == pdTRUE)
            {
                switch (command.type)
                {
                    case CMD_START:
                        running = true;
                        fault   = false;
                        integral = 0;
                        LOG_INFO("Pump STARTED by source %d", command.source);
                        break;
                    
                    case CMD_STOP:
                        running = false;
                        output  = 0;
                        LOG_INFO("Pump STOPPED by source %d", command.source);
                        break;
                    
                    case CMD_SET_SETPOINT:
                        setpoint = command.value;
                        LOG_INFO("Setpoint changed to %.1f bar", setpoint);
                        break;
                    
                    case CMD_RESET_FAULT:
                        fault = false;
                        LOG_INFO("Fault reset");
                        break;
                }
            }
            
            // Защиты
            if (sensorData.quality == 0)
            {
                running = false;
                fault   = true;
                output  = 0;
                LOG_ERROR("Sensor fault! Emergency stop.");
            }
            
            if (sensorData.pressure &gt; 12.0f)
            {
                running = false;
                fault   = true;
                output  = 0;
                LOG_ERROR("High pressure! %.2f bar &gt; 12.0 bar", sensorData.pressure);
            }
            
            if (sensorData.temperature &gt; 90.0f)
            {
                running = false;
                fault   = true;
                output  = 0;
                LOG_ERROR("Motor overtemp! %.1f°C &gt; 90°C", sensorData.temperature);
            }
            
            // ПИД вычисление
            if (running &amp;&amp; !fault)
            {
                float error = setpoint - sensorData.pressure;
                
                integral += ki * error * TS;
                integral  = fmaxf(-50.0f, fminf(50.0f, integral));  // Anti-windup
                
                float derivative = -(sensorData.pressure - prevError) / TS;
                prevError = sensorData.pressure;
                
                output = kp * error + integral + kd * derivative;
                output = fmaxf(0.0f, fminf(100.0f, output));
            }
            else
            {
                output  = 0.0f;
                integral = 0.0f;
            }
            
            // Применяем управляющий сигнал
            VFD_SetFrequency(output * 0.5f);  // 0-100% → 0-50 Гц
        }
        else
        {
            // Таймаут ожидания данных датчика — авария
            LOG_ERROR("Sensor data timeout!");
            running = false;
            output  = 0;
            VFD_SetFrequency(0);
        }
    }
}

// ===== ЗАДАЧА 3: MODBUS SLAVE (Приоритет 3) =====
static void vModbusTask(void *pvParam)
{
    uint8_t rxBuf[64];
    uint8_t rxLen = 0;
    
    LOG_INFO("Modbus task started");
    
    for (;;)
    {
        // Ждём байт из UART (через семафор от прерывания)
        if (UART_WaitForData(rxBuf, &amp;rxLen, pdMS_TO_TICKS(100)))
        {
            // Обрабатываем Modbus запрос
            if (Modbus_ProcessRequest(rxBuf, rxLen))
            {
                // Если команда — отправляем в очередь управления
                Command_t cmd;
                if (Modbus_ExtractCommand(&amp;cmd))
                {
                    xQueueSend(xCommandQueue, &amp;cmd, pdMS_TO_TICKS(10));
                }
            }
        }
    }
}

// ===== ЗАДАЧА 4: ЛОГИРОВАНИЕ (Приоритет 1, самый низкий) =====
static void vLoggingTask(void *pvParam)
{
    LogMessage_t msg;
    const char *levelNames[] = {"DBG", "INF", "WRN", "ERR"};
    
    for (;;)
    {
        // Ждём сообщение из очереди
        if (xQueueReceive(xLogQueue, &amp;msg, portMAX_DELAY) == pdTRUE)
        {
            // Пишем в UART (захватываем мьютекс)
            if (xSemaphoreTake(xUARTMutex, pdMS_TO_TICKS(50)) == pdTRUE)
            {
                printf("[%6lu][%s] %s\r\n",
                       (unsigned long)msg.timestamp_ms,
                       levelNames[msg.level % 4],
                       msg.message);
                xSemaphoreGive(xUARTMutex);
            }
            
            // Пишем на SD-карту (низкий приоритет = не мешаем критичным задачам)
            // SD_AppendLog(&amp;msg);
        }
    }
}

// ===== ТАЙМЕР HEARTBEAT =====
static void vHeartbeatCallback(TimerHandle_t xTimer)
{
    // Мигаем светодиодом — система жива
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}

// ===== ИНИЦИАЛИЗАЦИЯ =====
void App_Init(void)
{
    // Создаём очереди
    xSensorQueue  = xQueueCreate(5,  sizeof(SensorData_t));
    xCommandQueue = xQueueCreate(10, sizeof(Command_t));
    xLogQueue     = xQueueCreate(20, sizeof(LogMessage_t));
    
    // Создаём мьютексы
    xI2CMutex  = xSemaphoreCreateMutex();
    xUARTMutex = xSemaphoreCreateMutex();
    
    // Создаём задачи
    xTaskCreate(vSensorTask,  "Sensors",  512, NULL, 4, NULL);
    xTaskCreate(vControlTask, "Control",  1024, NULL, 5, NULL);
    xTaskCreate(vModbusTask,  "Modbus",   512, NULL, 3, NULL);
    xTaskCreate(vLoggingTask, "Logging",  256, NULL, 1, NULL);
    
    // Создаём таймер heartbeat (500 мс)
    xHeartbeatTimer = xTimerCreate("Heartbeat", pdMS_TO_TICKS(500),
                                    pdTRUE, NULL, vHeartbeatCallback);
    xTimerStart(xHeartbeatTimer, 0);
    
    // Запуск планировщика
    vTaskStartScheduler();
    
    // Никогда не должно дойти сюда!
    for (;;);
}
</code></pre><hr><h2>Очереди: безопасная передача данных между задачами</h2><p>Очередь — это основной механизм коммуникации в FreeRTOS. Thread-safe, FIFO, блокирующий.</p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Создание очереди на 10 элементов типа uint32_t
QueueHandle_t xQueue = xQueueCreate(10, sizeof(uint32_t));

// Отправка (из задачи)
uint32_t value = 42;
xQueueSend(xQueue, &amp;value, pdMS_TO_TICKS(100));  // Ждём 100мс если полная

// Отправка с высоким приоритетом (в начало очереди)
xQueueSendToFront(xQueue, &amp;value, 0);

// Отправка из прерывания (другая функция!)
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(xQueue, &amp;value, &amp;xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);  // Уступить если нужно

// Приём (блокирует до данных или таймаута)
uint32_t received;
if (xQueueReceive(xQueue, &amp;received, portMAX_DELAY) == pdTRUE) {
    // Данные получены
}

// "Подсмотреть" без извлечения
xQueuePeek(xQueue, &amp;received, 0);

// Мониторинг
UBaseType_t count = uxQueueMessagesWaiting(xQueue);  // Сколько элементов
UBaseType_t space = uxQueueSpacesAvailable(xQueue);  // Сколько свободно
</code></pre><hr><h2>Семафоры и мьютексы: защита разделяемых ресурсов</h2><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// ===== МЬЮТЕКС (Mutual Exclusion) =====
// Для защиты ресурсов (I2C, SPI, UART, глобальные переменные)

SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();

// Правильный паттерн:
void safe_i2c_read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t len)
{
    if (xSemaphoreTake(xMutex, pdMS_TO_TICKS(100)) == pdTRUE)
    {
        HAL_I2C_Mem_Read(&amp;hi2c1, addr, reg, 1, buf, len, 100);
        xSemaphoreGive(xMutex);
    }
    else
    {
        // Таймаут — логируем, возвращаем ошибку
    }
}

// ===== ДВОИЧНЫЙ СЕМАФОР (уведомление о событии) =====
// Задача ждёт событие от ISR

SemaphoreHandle_t xDataReadySemaphore = xSemaphoreCreateBinary();

// В прерывании (данные готовы):
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    BaseType_t xWoken = pdFALSE;
    xSemaphoreGiveFromISR(xDataReadySemaphore, &amp;xWoken);
    portYIELD_FROM_ISR(xWoken);
}

// В задаче (ждём данные):
void vProcessingTask(void *pvParam)
{
    for (;;) {
        // Эффективное ожидание — задача не потребляет CPU!
        xSemaphoreTake(xDataReadySemaphore, portMAX_DELAY);
        
        // Данные готовы — обрабатываем
        process_uart_data();
    }
}

// ===== СЧЁТНЫЙ СЕМАФОР (ограничение конкурентного доступа) =====
// Пример: максимум 3 одновременных подключения

SemaphoreHandle_t xConnectionSlots = xSemaphoreCreateCounting(3, 3);

void handle_new_connection() {
    if (xSemaphoreTake(xConnectionSlots, pdMS_TO_TICKS(5000)) == pdTRUE)
    {
        // Слот получен
        serve_client();
        xSemaphoreGive(xConnectionSlots);  // Освободить слот
    }
    else
    {
        // Нет свободных слотов
        send_busy_response();
    }
}
</code></pre><hr><h2>Программные таймеры</h2><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Таймер однократный (one-shot) vs периодический
TimerHandle_t xOneShotTimer;
TimerHandle_t xPeriodicTimer;

void vTimerCallback(TimerHandle_t xTimer)
{
    // pvTimerGetTimerID позволяет использовать один callback для многих таймеров
    uint32_t timerID = (uint32_t)pvTimerGetTimerID(xTimer);
    
    switch (timerID) {
        case 1:
            // Однократный таймер — отключить нагреватель через 30 сек
            Heater_Off();
            break;
        case 2:
            // Периодический — опрос watchdog
            External_WDT_Kick();
            break;
    }
}

void setup_timers(void)
{
    // One-shot таймер (не перезапускается автоматически)
    xOneShotTimer = xTimerCreate(
        "Heater",
        pdMS_TO_TICKS(30000),  // 30 секунд
        pdFALSE,               // pdFALSE = one-shot
        (void *)1,             // ID таймера
        vTimerCallback
    );
    
    // Периодический таймер
    xPeriodicTimer = xTimerCreate(
        "WDT",
        pdMS_TO_TICKS(500),    // 500 мс
        pdTRUE,                // pdTRUE = периодический
        (void *)2,
        vTimerCallback
    );
    
    xTimerStart(xPeriodicTimer, 0);
    
    // Запустить один-шот когда нужно:
    // xTimerStart(xOneShotTimer, 0);
    
    // Сбросить периодический (перезапустить отсчёт):
    // xTimerReset(xPeriodicTimer, 0);
    
    // Изменить период на лету:
    // xTimerChangePeriod(xPeriodicTimer, pdMS_TO_TICKS(1000), 0);
}
</code></pre><hr><h2>Управление памятью и отладка</h2><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Мониторинг стека задачи (важно для нахождения переполнений!)
void vCheckStackTask(void *pvParam)
{
    for (;;) {
        vTaskDelay(pdMS_TO_TICKS(5000));
        
        // Минимальный остаток стека (в словах) с начала работы
        UBaseType_t hwm = uxTaskGetStackHighWaterMark(NULL);
        
        if (hwm &lt; 50) {  // Меньше 50 слов — опасно!
            printf("WARNING: Task '%s' stack low! HWM=%lu words\r\n",
                   pcTaskGetName(NULL), (unsigned long)hwm);
        }
    }
}

// Вывод информации о всех задачах (для отладки)
void vPrintTaskStats(void)
{
    char buffer[512];
    vTaskList(buffer);  // Требует configUSE_TRACE_FACILITY=1
    printf("Task Name\t\tState\tPrio\tStack\tNum\r\n%s", buffer);
    
    // Загрузка CPU по задачам (требует configGENERATE_RUN_TIME_STATS=1)
    vTaskGetRunTimeStats(buffer);
    printf("\r\nTask\t\t\tTime\t\t%%\r\n%s", buffer);
}

// Обработчик нехватки памяти
void vApplicationMallocFailedHook(void)
{
    taskDISABLE_INTERRUPTS();
    printf("FATAL: malloc failed! Heap exhausted.\r\n");
    for (;;);
}

// Переполнение стека задачи
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
    taskDISABLE_INTERRUPTS();
    printf("FATAL: Stack overflow in task '%s'!\r\n", pcTaskName);
    for (;;);
}
</code></pre><hr><h2>FreeRTOS на ESP32</h2><p>ESP-IDF (официальный SDK ESP32) использует FreeRTOS как основу. На двухядерном ESP32 задачи можно привязывать к конкретному ядру:</p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// ESP32-специфичное создание задачи с указанием ядра
xTaskCreatePinnedToCore(
    vWiFiTask,       // Функция
    "WiFi",          // Имя
    8192,            // Стек (в байтах для ESP32!)
    NULL,            // Параметры
    5,               // Приоритет
    &amp;xWiFiHandle,    // Хендл
    0                // Ядро: 0 = Protocol CPU, 1 = Application CPU
);

// WiFi и Bluetooth — всегда на ядре 0 (Protocol CPU)
// Ваш код — лучше на ядре 1 (Application CPU)
// Это разделяет сетевой стек и бизнес-логику

// Встроенный мониторинг задач ESP-IDF:
void print_esp_task_info(void)
{
    printf("Free heap: %u bytes\r\n", esp_get_free_heap_size());
    printf("Min free heap: %u bytes\r\n", esp_get_minimum_free_heap_size());
}
</code></pre><hr><h2>Типичные ошибки FreeRTOS</h2><p><strong>1. Вызов обычных функций из ISR</strong></p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// НЕПРАВИЛЬНО — заблокирует прерывание!
void HAL_GPIO_EXTI_Callback(uint16_t pin) {
    xQueueSend(xQueue, &amp;data, portMAX_DELAY);  // ОШИБКА: блокирует ISR!
}

// ПРАВИЛЬНО — FromISR версии функций:
void HAL_GPIO_EXTI_Callback(uint16_t pin) {
    BaseType_t xWoken = pdFALSE;
    xQueueSendFromISR(xQueue, &amp;data, &amp;xWoken);
    portYIELD_FROM_ISR(xWoken);
}
</code></pre><p><strong>2. Бесконечная задача без yield</strong></p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// НЕПРАВИЛЬНО — монополизирует процессор!
void vBadTask(void *p) {
    for (;;) {
        do_something();
        // Нет vTaskDelay или блокирующего ожидания!
    }
}

// ПРАВИЛЬНО:
void vGoodTask(void *p) {
    for (;;) {
        do_something();
        vTaskDelay(pdMS_TO_TICKS(10));  // Уступаем хотя бы 10 мс
    }
}
</code></pre><p><strong>3. Доступ к глобальным данным без защиты</strong></p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// НЕПРАВИЛЬНО — race condition!
float g_temperature = 0;

void vSensor(void *p) { g_temperature = read_sensor(); }
void vControl(void *p) { if (g_temperature &gt; 80) alarm(); }

// ПРАВИЛЬНО — через очередь или мьютекс
</code></pre><hr><h2>Заключение</h2><p>FreeRTOS превращает микроконтроллер из последовательного автомата в полноценную многозадачную систему. Это не усложнение ради усложнения — это решение реальных проблем: независимость задач, чёткие интерфейсы через очереди, защита ресурсов через мьютексы.</p><p>Начните с малого: замените суперцикл двумя задачами — одна читает датчик, другая управляет выходом, общаются через очередь. Это уже даст почувствовать преимущества.</p><p>FreeRTOS поддерживается на STM32, ESP32, Arduino (с ограничениями), Raspberry Pi Pico и десятках других платформ. Документация на <a rel="external nofollow" href="https://freertos.org">freertos.org</a> — отличная, с примерами и объяснениями.</p>]]></description><guid isPermaLink="false">122</guid><pubDate>Sat, 21 Mar 2026 17:35:00 +0000</pubDate></item><item><title>&#x421;&#x438;&#x43B;&#x43E;&#x432;&#x430;&#x44F; &#x44D;&#x43B;&#x435;&#x43A;&#x442;&#x440;&#x43E;&#x43D;&#x438;&#x43A;&#x430;: &#x442;&#x438;&#x440;&#x438;&#x441;&#x442;&#x43E;&#x440;&#x44B;, IGBT &#x438; &#x443;&#x43F;&#x440;&#x430;&#x432;&#x43B;&#x435;&#x43D;&#x438;&#x435; &#x43C;&#x43E;&#x449;&#x43D;&#x44B;&#x43C;&#x438; &#x43D;&#x430;&#x433;&#x440;&#x443;&#x437;&#x43A;&#x430;&#x43C;&#x438;</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/%D1%81%D0%B8%D0%BB%D0%BE%D0%B2%D0%B0%D1%8F-%D1%8D%D0%BB%D0%B5%D0%BA%D1%82%D1%80%D0%BE%D0%BD%D0%B8%D0%BA%D0%B0-%D1%82%D0%B8%D1%80%D0%B8%D1%81%D1%82%D0%BE%D1%80%D1%8B-igbt-%D0%B8-%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BC%D0%BE%D1%89%D0%BD%D1%8B%D0%BC%D0%B8-%D0%BD%D0%B0%D0%B3%D1%80%D1%83%D0%B7%D0%BA%D0%B0%D0%BC%D0%B8-r128/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/1568796732_6.jpg.a595771fd0391e82537220a926d6911d.jpg" /></p>
<h2>Силовая электроника: между схемотехникой и энергетикой</h2><p>Силовая электроника — область, где электроника управляет реальной мощностью: сотнями ампер, тысячами вольт, мегаваттами. Это регуляторы яркости, частотные преобразователи, зарядные станции для электромобилей, солнечные инверторы, промышленные нагреватели.</p><p>Ключевое отличие от малосигнальной электроники: <strong>КПД критически важен</strong>. 99% КПД в источнике 100 кВт означает 1 кВт тепла на радиаторах — и это допустимо. 90% КПД — уже 10 кВт тепловых потерь, требующих серьёзного охлаждения.</p><hr><h2>Тиристор (SCR): управляемый диод</h2><p>Тиристор (Silicon Controlled Rectifier, SCR) — четырёхслойный PNPN прибор. Включается коротким импульсом на управляющий электрод (Gate), выключается только при уменьшении тока ниже тока удержания (обычно — переходом через ноль в сети AC).</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Анод (A) ─── P ─── N ─── P ─── N ─── Катод (K)
                    │
               Управляющий электрод (G)
</code></pre><p><strong>Характеристики:</strong></p><ul><li><p>Включается: короткий импульс IG &gt; IGT (обычно 10–100 мА)</p></li><li><p>Выключается: ток анода &lt; IH (ток удержания), обычно при переходе AC через ноль</p></li><li><p>Прямое напряжение в открытом состоянии: 1–3В (значительные потери!)</p></li><li><p>Применение: однофазные и трёхфазные выпрямители, регуляторы мощности</p></li></ul><h3>Фазовое управление тиристором</h3><p>Основной метод управления мощностью с тиристором в сети AC:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Угол включения α=0°:  Полная мощность (100%)
Угол включения α=90°: Половинная мощность (~50%)
Угол включения α=150°: Малая мощность (~6%)
Угол включения α=180°: Минимальная мощность (0%)

Средняя мощность ≈ P_max × (1 + cos α) / 2
</code></pre><p>Схема фазового управления на Arduino/STM32:</p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Детектор перехода через ноль (Zero-Crossing Detector)
// Подключён к INT0 (PD2) через оптопару
// При каждом переходе через ноль — прерывание

volatile bool zero_cross  = false;
volatile uint8_t power_pct = 50;  // 0-100%

// Прерывание от детектора нуля
ISR(INT0_vect)  // AVR / адаптируй под STM32/ESP32
{
    zero_cross = true;
}

void setup()
{
    // Выход на оптотиристор/оптосимистор (через ограничительный резистор ~300Ом)
    pinMode(9, OUTPUT);
    digitalWrite(9, LOW);
    
    // Прерывание INT0 по фронту (или обоим — зависит от схемы)
    attachInterrupt(0, zero_cross_ISR, RISING);
    
    // Таймер 1: генерирует прерывание через N микросекунд после нуля
    // Для сети 50 Гц: полупериод = 10 000 мкс
    // Угол 90° = 5 000 мкс задержка
}

void zero_cross_ISR()
{
    zero_cross = true;
}

void loop()
{
    if (zero_cross)
    {
        zero_cross = false;
        
        // Рассчитываем задержку включения
        // power_pct = 100 → delay = 0 мкс (включить немедленно)
        // power_pct = 50  → delay = 5000 мкс (угол 90°)
        // power_pct = 0   → delay = 10000 мкс (не включать)
        
        uint32_t delay_us = (100 - power_pct) * 100;  // 0-10000 мкс
        
        if (power_pct &gt; 0 &amp;&amp; power_pct &lt; 100)
        {
            delayMicroseconds(delay_us);
            
            // Короткий импульс управления (100-200 мкс достаточно)
            digitalWrite(9, HIGH);
            delayMicroseconds(100);
            digitalWrite(9, LOW);
        }
        else if (power_pct &gt;= 100)
        {
            digitalWrite(9, HIGH);  // Постоянно включено
        }
        else
        {
            digitalWrite(9, LOW);   // Постоянно выключено
        }
    }
}
</code></pre><p><strong>Проблемы фазового управления:</strong></p><ul><li><p>Генерирует гармоники в сети (помехи)</p></li><li><p>Вызывает мигание освещения</p></li><li><p>Создаёт радиопомехи (EMI)</p></li></ul><p><strong>Решение для нагревателей: управление по полным полупериодам (Burst Firing)</strong></p><hr><h2>Симистор (TRIAC): для двунаправленного управления</h2><p>Симистор = два тиристора включённых встречно-параллельно. Проводит ток в ОБОИХ направлениях — идеален для управления нагрузкой переменного тока без выпрямления.</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>        MT2
         │
   ── P ─┤
   ── N ─┤── Gate (G)
   ── P ─┤
   ── N ─┤
         │
        MT1
</code></pre><h3>Управление по нулю (Zero-Crossing Control / Burst Firing)</h3><p>Вместо фазового управления — включаем нагреватель на N полных периодов из M:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Мощность 33%: ██░░██░░██░░██░░  (1 из 3 периодов включён)
Мощность 50%: ████░░░░████░░░░  (2 из 4 периодов)
Мощность 75%: ██████░░██████░░  (3 из 4 периодов)
Мощность 100%: ████████████████  (все периоды)
</code></pre><p>Преимущества: нет гармоник, нет EMI, нет щелчков в контакторах. Недостатки: медленнее регулирование (минимальный шаг — полпериода = 10 мс).</p><p><strong>Оптимально для:</strong> промышленные нагреватели, печи сопротивления, ИК-нагреватели.</p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// Burst Firing контроллер
class BurstController {
private:
    uint8_t window_size;    // Размер окна в полупериодах (например, 20 = 200мс)
    uint8_t on_count;       // Сколько периодов включено
    uint8_t current_period; // Счётчик текущего периода
    
public:
    BurstController(uint8_t window = 20) : window_size(window), on_count(0), current_period(0) {}
    
    void setPower(float power_pct) {
        on_count = (uint8_t)(power_pct / 100.0f * window_size + 0.5f);
        on_count = constrain(on_count, 0, window_size);
    }
    
    // Вызвать при каждом переходе через ноль
    bool onZeroCross() {
        bool turn_on = (current_period &lt; on_count);
        current_period = (current_period + 1) % window_size;
        return turn_on;
    }
};

BurstController burster(20);  // Окно 20 полупериодов = 200 мс

void zero_cross_handler() {
    bool should_be_on = burster.onZeroCross();
    digitalWrite(TRIAC_PIN, should_be_on ? HIGH : LOW);
}
</code></pre><hr><h2>Твёрдотельное реле (SSR): простое решение</h2><p>SSR (Solid-State Relay) — готовый модуль с тиристором/симистором и оптической развязкой внутри. Управление: 3–32В DC сигнал (совместимо с Arduino/ПЛК), нагрузка: до 40А/480В AC.</p><p><strong>Выбор SSR:</strong></p><div class="tmiRichText__table-wrapper"><table style="min-width: 471px;"><colgroup><col style="min-width:20px;"><col style="width:451px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Параметр</p></th><th colspan="1" rowspan="1"><p>Рекомендация</p></th></tr><tr><td colspan="1" rowspan="1"><p>Ток нагрузки</p></td><td colspan="1" rowspan="1"><p>Выбирать с запасом ×2 (40А SSR для 20А нагрузки)</p></td></tr><tr><td colspan="1" rowspan="1"><p>Тип управления</p></td><td colspan="1" rowspan="1"><p>DC Control (3-32V) для ПЛК, AC Control (90-280V) для PID-регулятора с выходом AC</p></td></tr><tr><td colspan="1" rowspan="1"><p>Тип коммутации</p></td><td colspan="1" rowspan="1"><p>Zero-Cross для нагревателей, Random Fire для двигателей и трансформаторов</p></td></tr><tr><td colspan="1" rowspan="1"><p>Напряжение нагрузки</p></td><td colspan="1" rowspan="1"><p>24-380В AC (проверьте соответствие!)</p></td></tr><tr><td colspan="1" rowspan="1"><p>Охлаждение</p></td><td colspan="1" rowspan="1"><p>ОБЯЗАТЕЛЬНО радиатор! 0.5°C/Вт для алюминиевого радиатора</p></td></tr></tbody></table></div><p><strong>Тепловой расчёт SSR:</strong></p><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>def calculate_ssr_heatsink(load_current_a: float, ambient_temp_c: float = 40.0,
                            max_case_temp_c: float = 80.0) -&gt; dict:
    """
    Расчёт требуемого теплового сопротивления радиатора для SSR.
    
    SSR: прямое напряжение ~1.2В (Fotek, Crydom)
    """
    
    # Тепловыделение SSR
    vf       = 1.2    # В, прямое падение на симисторе
    p_loss   = vf * load_current_a  # Вт
    
    # Тепловое сопротивление корпус-радиатор (junction-to-case): ~0.5°C/Вт
    rth_jc = 0.5  # °C/W (из datasheet)
    
    # Максимальная температура p-n перехода обычно 125°C
    t_junction_max = 125.0
    
    # Требуемое тепловое сопротивление радиатор-воздух
    # T_ambient + P × (Rth_jc + Rth_heatsink) = T_case_max
    rth_heatsink = (max_case_temp_c - ambient_temp_c) / p_loss - rth_jc
    
    # Размер алюминиевого радиатора (грубая оценка):
    # R_th ≈ 50 / (площадь_см²) для вертикального расположения
    heatsink_area_cm2 = 50.0 / rth_heatsink if rth_heatsink &gt; 0 else float('inf')
    
    return {
        'load_current_a':  load_current_a,
        'power_loss_w':    round(p_loss, 1),
        'rth_heatsink':    round(rth_heatsink, 2),
        'heatsink_area_cm2': round(heatsink_area_cm2, 0),
        'safe_operation':  rth_heatsink &gt; 0,
    }

# Пример: SSR 25А нагрузки при T_окр=40°C
result = calculate_ssr_heatsink(25.0)
print(f"Потери: {result['power_loss_w']} Вт")
print(f"Требуется радиатор: {result['heatsink_area_cm2']} см²")
# Результат: 30 Вт потерь, нужен радиатор ~150 см²
</code></pre><hr><h2>IGBT: для высоких частот и больших мощностей</h2><p>IGBT (Insulated Gate Bipolar Transistor) — гибрид MOSFET и биполярного транзистора. Управляется напряжением (как MOSFET), но имеет низкое напряжение насыщения при больших токах (как биполярный).</p><p><strong>Где используется:</strong></p><ul><li><p>Частотные преобразователи (инвертор моста)</p></li><li><p>ИБП и стабилизаторы</p></li><li><p>Сварочные аппараты</p></li><li><p>Индукционные нагреватели</p></li><li><p>Зарядные станции для электромобилей</p></li></ul><h3>Ключевые характеристики при выборе IGBT:</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Vce_max    — максимальное напряжение коллектор-эмиттер (выбирать ×2 от напряжения шины!)
Ic_max     — максимальный ток (с учётом теплового сопротивления!)
Vce(sat)   — напряжение насыщения (потери в открытом состоянии)
Eoff/Eon   — энергия переключения (потери на коммутацию, растут с частотой!)
toff       — время выключения (ограничивает максимальную частоту ШИМ)
</code></pre><p><strong>Расчёт потерь IGBT:</strong></p><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>def calculate_igbt_losses(vce_sat: float, ic_rms: float,
                           e_on_j: float, e_off_j: float,
                           fsw_hz: float, duty: float) -&gt; dict:
    """
    Расчёт потерь IGBT.
    
    vce_sat: напряжение насыщения, В (из datasheet при Ic и Tj)
    ic_rms:  действующий ток коллектора, А
    e_on_j:  энергия включения, Дж (из datasheet)
    e_off_j: энергия выключения, Дж
    fsw_hz:  частота коммутации, Гц
    duty:    скважность ШИМ (0..1)
    """
    
    # Потери проводимости (conduction losses)
    # P_cond = Vce_sat × Ic_avg
    ic_avg  = ic_rms * duty  # Упрощение для прямоугольного тока
    p_cond  = vce_sat * ic_avg
    
    # Потери переключения (switching losses)
    # P_sw = (E_on + E_off) × fsw
    p_sw = (e_on_j + e_off_j) * fsw_hz
    
    # Суммарные потери
    p_total = p_cond + p_sw
    
    return {
        'conduction_w':  round(p_cond, 2),
        'switching_w':   round(p_sw, 2),
        'total_w':       round(p_total, 2),
        'efficiency_pct': round((1 - p_total / (vce_sat * ic_rms + p_total)) * 100, 1),
    }

# Пример: IGBT 1200В/50А в инверторе 400В
# Частотник 11 кВт: Idc ≈ 30А, fsw=8кГц, d=0.8
losses = calculate_igbt_losses(
    vce_sat = 2.0,     # В при 125°C
    ic_rms  = 30.0,    # А
    e_on_j  = 0.8e-3,  # 0.8 мДж
    e_off_j = 1.2e-3,  # 1.2 мДж
    fsw_hz  = 8000,    # 8 кГц
    duty    = 0.8
)
print(f"Потери проводимости: {losses['conduction_w']} Вт")
print(f"Потери переключения: {losses['switching_w']} Вт")
print(f"Итого: {losses['total_w']} Вт")
# При 6 IGBT в трёхфазном мосте: ×6 = итоговые потери инвертора
</code></pre><hr><h2>Снабберные цепи: защита от перенапряжений</h2><p>При выключении IGBT/тиристора индуктивная нагрузка создаёт выброс напряжения: V_spike = L × dI/dt. Без защиты — мгновенная смерть транзистора.</p><h3>RC-снаббер (для симистора):</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Нагрузка
    │
[Симистор]
    │
 ─────────  ← RC снаббер параллельно симистору
│         │
[R ~47 Ом] [C ~47 нФ, 630В]
│         │
 ─────────
    │
   GND
</code></pre><p>Расчёт RC-снаббера:</p><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>def calculate_rc_snubber(load_inductance_h: float, 
                          switch_current_a: float,
                          supply_voltage_v: float) -&gt; dict:
    """
    Расчёт RC-снаббера для тиристора/симистора.
    
    Критерий: выброс напряжения ≤ 2 × Vsupply
    """
    import math
    
    # Пиковое напряжение без снаббера
    # V_peak ≈ V_supply + I × sqrt(L/C_parasitic)
    # С снаббером ограничиваем до 2×V_supply
    
    v_max = 2 * supply_voltage_v
    
    # Ёмкость снаббера (минимальная для ограничения выброса)
    # C ≥ L × I² / (V_max - V_supply)²
    delta_v = v_max - supply_voltage_v
    c_min = load_inductance_h * switch_current_a**2 / delta_v**2
    c_snubber = c_min * 2  # Запас × 2, нормализуем до стандартного ряда E12
    
    # Стандартный ряд конденсаторов (нФ)
    e12 = [10, 12, 15, 18, 22, 27, 33, 39, 47, 56, 68, 82]
    c_nf = c_snubber * 1e9
    c_standard_nf = min(e12, key=lambda x: abs(x - c_nf))
    # Если нет в ряду — берём следующий больший
    for val in sorted(e12):
        if val &gt;= c_nf:
            c_standard_nf = val
            break
    
    c_actual = c_standard_nf * 1e-9
    
    # Сопротивление снаббера
    # R ≈ sqrt(L/C) для критического затухания
    r_critical = math.sqrt(load_inductance_h / c_actual)
    r_snubber  = r_critical  # Или немного больше для надёжности
    
    # Мощность резистора
    # P = 0.5 × C × V² × f  (для каждого переключения)
    # Для AC 50Гц: f = 100 (2 перехода через ноль)
    p_resistor = 0.5 * c_actual * supply_voltage_v**2 * 100
    
    return {
        'C_nF':    c_standard_nf,
        'C_voltage': f"{int(v_max * 1.5 / 100) * 100}В",  # Округляем до стандарта
        'R_ohm':   round(r_snubber, 0),
        'R_watts': round(p_resistor * 2, 1),  # Запас × 2
    }

# Пример: управление нагревателем 5 кВт через симистор
# Нагреватель = почти чисто активная нагрузка, но есть монтажная индуктивность ~10 мкГн
result = calculate_rc_snubber(10e-6, 22.7, 220)
print(f"Снаббер: R={result['R_ohm']} Ом/{result['R_watts']} Вт, "
      f"C={result['C_nF']} нФ/{result['C_voltage']}")
</code></pre><hr><h2>Трёхфазное управление нагревателями</h2><p>Для трёхфазных печей и нагревателей используют 3 SSR (по одному на каждую фазу) или специализированные трёхфазные тиристорные модули:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>               L1 ──[SSR_A]──┐
               L2 ──[SSR_B]──┼── Нагреватели (треугольник или звезда)
               L3 ──[SSR_C]──┘
                   ↑  ↑  ↑
               Сигналы управления с ПЛК/контроллера

Управление: все три SSR получают одинаковый сигнал (одновременно вкл/выкл)
            → симметричная нагрузка, нет перекоса фаз

Или: поочерёдное включение фаз (phase rotation) → снижает пиковый ток пуска
</code></pre><p>Алгоритм ПИД для температуры печи с трёхфазным нагревателем:</p><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>class FurnaceController:
    """Контроллер температуры трёхфазной печи"""
    
    def __init__(self, kp=5.0, ki=0.1, kd=2.0, max_power=100.0):
        self.pid = PIDController(kp, ki, kd, ts=1.0, out_min=0, out_max=max_power)
        self.setpoint = 0.0
        
        # Защиты
        self.max_temp = 1200.0   # °C максимум печи
        self.fault    = False
    
    def control_cycle(self, temp_actual: float) -&gt; dict:
        """Один цикл управления (вызывать каждую секунду)"""
        
        # Защита по превышению температуры
        if temp_actual &gt; self.max_temp:
            self.fault = True
        
        if self.fault:
            return {'ssr_output': 0.0, 'fault': True, 'temp': temp_actual}
        
        # ПИД
        self.pid.set_setpoint(self.setpoint)
        power_pct = self.pid.compute(temp_actual)
        
        # Мощность → количество периодов включения (Burst Fire)
        # Окно 10 полупериодов = 100 мс
        on_periods = int(power_pct / 100 * 10 + 0.5)
        
        return {
            'ssr_output':   power_pct,
            'on_periods':   on_periods,  # из 10
            'setpoint':     self.setpoint,
            'temp_actual':  temp_actual,
            'error':        self.setpoint - temp_actual,
            'fault':        False,
        }
</code></pre><hr><h2>Тепловой расчёт: как не сжечь компоненты</h2><p><strong>Тепловая цепь</strong> аналогична электрической:</p><ul><li><p>Температура <span class="tmiEmoji" title="">↔</span> Напряжение</p></li><li><p>Мощность потерь <span class="tmiEmoji" title="">↔</span> Ток</p></li><li><p>Тепловое сопротивление Rth <span class="tmiEmoji" title="">↔</span> Электрическое сопротивление</p></li></ul><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>T_junction = T_ambient + P_loss × (Rth_j-c + Rth_c-hs + Rth_hs-a)

Где:
Rth_j-c   — тепловое сопротивление кристалл→корпус (из datasheet)
Rth_c-hs  — корпус→радиатор (зависит от термопасты, ~0.1–0.5 °C/Вт)
Rth_hs-a  — радиатор→воздух (зависит от размера и обдува)
</code></pre><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>def thermal_check(p_loss_w: float, t_ambient_c: float,
                   rth_jc: float, rth_chs: float, rth_hsa: float,
                   t_junction_max: float = 125.0) -&gt; dict:
    """
    Проверка теплового режима силового прибора.
    """
    rth_total = rth_jc + rth_chs + rth_hsa
    
    t_junction = t_ambient_c + p_loss_w * rth_total
    t_case     = t_ambient_c + p_loss_w * rth_hsa  # Температура корпуса
    
    margin = t_junction_max - t_junction
    safe   = margin &gt; 10.0  # Запас минимум 10°C
    
    return {
        'T_junction_c': round(t_junction, 1),
        'T_case_c':     round(t_case, 1),
        'margin_c':     round(margin, 1),
        'safe':         safe,
        'warning':      not safe,
    }

# Проверка SSR 30А:
result = thermal_check(
    p_loss_w   = 1.2 * 25,  # 1.2В × 25А = 30 Вт
    t_ambient_c = 40.0,
    rth_jc     = 0.5,        # из datasheet Fotek SSR-40DA
    rth_chs    = 0.2,        # хорошая термопаста
    rth_hsa    = 1.5,        # алюминиевый радиатор 150 см²
)
print(f"Температура перехода: {result['T_junction_c']}°C")
print(f"Запас: {result['margin_c']}°C — {'OK' if result['safe'] else 'ОПАСНО!'}")
</code></pre><hr><h2>Типичные ошибки в силовой электронике</h2><ol><li><p><strong>Нет снаббера на индуктивной нагрузке</strong> → выброс напряжения → смерть тиристора</p></li><li><p><strong>SSR без радиатора</strong> → перегрев за несколько минут при токе &gt; 5А</p></li><li><p><strong>Управляющий сигнал без оптической развязки</strong> → 220В на Arduino/ПЛК = конец</p></li><li><p><strong>Не проверена полярность тиристора</strong> → не переключается или горит сразу</p></li><li><p><strong>Фазовое управление на трансформаторную нагрузку</strong> → насыщение сердечника</p></li><li><p><strong>IGBT с Vce_max = Vsupply</strong> → первый же выброс убивает → минимум ×2 запас</p></li></ol><hr><h2>Заключение</h2><p>Силовая электроника — область, где цена ошибки высока: сгоревший IGBT, пожар, травма. Всегда работайте с полной изоляцией от сети, используйте изолирующие трансформаторы при разработке, не экономьте на снабберах и радиаторах.</p><p>Для начала: освойте управление нагревателем через SSR и ПИД-регулятор — это самая распространённая и безопасная задача. Потом — изучите теорию тиристорного управления. После этого — IGBT в H-мостах для двигателей. И только с хорошей теоретической базой — трёхфазные инверторы.</p><p>Измерительный осциллограф с изолированными щупами и клещи-амперметр — ваши обязательные инструменты в этой области. Без возможности видеть что происходит на осциллографе — работать в силовой электронике вслепую.</p>]]></description><guid isPermaLink="false">128</guid><pubDate>Sat, 21 Mar 2026 17:38:47 +0000</pubDate></item><item><title>ESP32: &#x433;&#x43B;&#x443;&#x431;&#x43E;&#x43A;&#x43E;&#x435; &#x43F;&#x43E;&#x433;&#x440;&#x443;&#x436;&#x435;&#x43D;&#x438;&#x435; &#x2014; WiFi, BLE, &#x434;&#x432;&#x443;&#x445;&#x44A;&#x44F;&#x434;&#x435;&#x440;&#x43D;&#x43E;&#x441;&#x442;&#x44C; &#x438; &#x43F;&#x435;&#x440;&#x438;&#x444;&#x435;&#x440;&#x438;&#x44F;</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/esp32-%D0%B3%D0%BB%D1%83%D0%B1%D0%BE%D0%BA%D0%BE%D0%B5-%D0%BF%D0%BE%D0%B3%D1%80%D1%83%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5-wifi-ble-%D0%B4%D0%B2%D1%83%D1%85%D1%8A%D1%8F%D0%B4%D0%B5%D1%80%D0%BD%D0%BE%D1%81%D1%82%D1%8C-%D0%B8-%D0%BF%D0%B5%D1%80%D0%B8%D1%84%D0%B5%D1%80%D0%B8%D1%8F-r137/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/09d83c7e59adee7eb06d422e5cb3c5a862692353_original.jpeg.8b291e03e1e65195caad718665dc657b.jpeg" /></p>
<h2>ESP32: почему он стал стандартом IoT</h2><p>ESP32 от Espressif Systems вышел в 2016 году и быстро стал самым популярным Wi-Fi/BT чипом для IoT. Причины:</p><ul><li><p><strong>240 МГц Xtensa LX6</strong> (два ядра!) — серьёзная вычислительная мощь</p></li><li><p><strong>Wi-Fi 802.11 b/g/n + Bluetooth 4.2/BLE</strong> — встроено в один чип</p></li><li><p><strong>520 КБ SRAM + внешняя Flash</strong> — достаточно для реальных приложений</p></li><li><p><strong>Богатая периферия:</strong> 18 каналов ADC, 2 DAC, 3 UART, 2 SPI, 2 I2C, I2S, CAN, Touch, Hall</p></li><li><p><strong>Цена $2–5</strong> (модуль ESP32-WROOM-32)</p></li><li><p><strong>FreeRTOS в основе SDK</strong> — готовая RTOS "из коробки"</p></li></ul><p>Семейство ESP32 сегодня:</p><ul><li><p><strong>ESP32</strong> — оригинал, Xtensa LX6 240 МГц, Wi-Fi + BT Classic + BLE</p></li><li><p><strong>ESP32-S2</strong> — одно ядро, USB OTG, нет BT, дешевле</p></li><li><p><strong>ESP32-S3</strong> — два ядра, USB OTG, AI-расширения (ML)</p></li><li><p><strong>ESP32-C3</strong> — RISC-V одно ядро, Wi-Fi + BLE, ультрадешёвый (~$1)</p></li><li><p><strong>ESP32-C6</strong> — RISC-V, Wi-Fi 6, BLE 5, Thread/Zigbee (Matter)</p></li><li><p><strong>ESP32-H2</strong> — только BLE 5 + Thread (802.15.4), без Wi-Fi</p></li></ul><hr><h2>Архитектура: два ядра и их назначение</h2><p>ESP32 имеет два ядра Xtensa LX6 с именами <strong>PRO_CPU (ядро 0)</strong> и <strong>APP_CPU (ядро 1)</strong>:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>PRO_CPU (Protocol CPU, Core 0):
  - Wi-Fi/Bluetooth стек (работает здесь)
  - Системные задачи FreeRTOS
  - Обработка прерываний от периферии

APP_CPU (Application CPU, Core 1):
  - Ваш прикладной код
  - Бизнес-логика
  - Задачи реального времени приложения
</code></pre><p>При использовании Arduino framework — код в <code>loop()</code> выполняется на APP_CPU.</p><p>В ESP-IDF — вы явно указываете ядро при создании задачи:</p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// ESP-IDF: создание задач с привязкой к ядру

void wifi_task(void *pvParam)
{
    // Эта задача работает на PRO_CPU — ближе к WiFi-стеку
    while (1) {
        // Сетевые операции
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void sensor_task(void *pvParam)
{
    // Эта задача на APP_CPU — изолирована от WiFi-шумов
    while (1) {
        float adc_val = read_adc();
        run_pid(adc_val);
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

void app_main(void)
{
    // PRO_CPU (Core 0) — WiFi, сетевые задачи
    xTaskCreatePinnedToCore(wifi_task,   "WiFi",   4096, NULL, 5, NULL, 0);
    
    // APP_CPU (Core 1) — приложение
    xTaskCreatePinnedToCore(sensor_task, "Sensor", 4096, NULL, 4, NULL, 1);
}
</code></pre><p><strong>Важно:</strong> WiFi-стек использует PRO_CPU интенсивно во время передачи. Задачи реального времени лучше держать на APP_CPU чтобы WiFi не вызывал джиттер.</p><hr><h2>WiFi: три режима работы</h2><h3>Station Mode (STA) — подключение к роутеру</h3><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>// Arduino framework
#include &lt;WiFi.h&gt;

const char* SSID     = "MyNetwork";
const char* PASSWORD = "MyPassword";

void wifi_connect() {
    WiFi.mode(WIFI_STA);
    WiFi.begin(SSID, PASSWORD);
    
    // Статический IP (не DHCP) — обязательно для production!
    IPAddress local_ip(192, 168, 1, 200);
    IPAddress gateway(192, 168, 1, 1);
    IPAddress subnet(255, 255, 255, 0);
    IPAddress dns1(192, 168, 1, 1);
    WiFi.config(local_ip, gateway, subnet, dns1);
    
    Serial.print("Подключение к WiFi");
    
    uint32_t timeout = millis() + 30000;  // 30 секунд таймаут
    while (WiFi.status() != WL_CONNECTED) {
        if (millis() &gt; timeout) {
            Serial.println("\nОшибка подключения! Перезагрузка...");
            ESP.restart();
        }
        delay(500);
        Serial.print(".");
    }
    
    Serial.printf("\nПодключено! IP: %s, RSSI: %d dBm\n",
                  WiFi.localIP().toString().c_str(),
                  WiFi.RSSI());
}

// Мониторинг соединения в loop():
void check_wifi_reconnect() {
    static uint32_t lastCheck = 0;
    
    if (millis() - lastCheck &lt; 5000) return;
    lastCheck = millis();
    
    if (WiFi.status() != WL_CONNECTED) {
        Serial.println("WiFi потерян, переподключение...");
        WiFi.disconnect();
        WiFi.begin(SSID, PASSWORD);
    }
}
</code></pre><h3>Access Point Mode (AP) — ESP32 как точка доступа</h3><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>void start_access_point() {
    WiFi.mode(WIFI_AP);
    
    // SSID, пароль, канал, скрытый?, макс клиентов
    WiFi.softAP("ESP32-Config", "setup12345", 6, false, 4);
    
    // Настройка IP точки доступа
    IPAddress ap_ip(192, 168, 4, 1);
    IPAddress ap_netmask(255, 255, 255, 0);
    WiFi.softAPConfig(ap_ip, ap_ip, ap_netmask);
    
    Serial.printf("AP запущен: %s, IP: %s\n",
                  WiFi.softAPSSID().c_str(),
                  WiFi.softAPIP().toString().c_str());
}
</code></pre><h3>STA+AP (одновременно!) — для конфигурации устройства</h3><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>void start_sta_ap_mode() {
    WiFi.mode(WIFI_AP_STA);
    
    // AP для настройки (пока устройство не настроено)
    WiFi.softAP("ESP32-Setup");
    
    // STA для рабочего подключения
    if (has_credentials()) {
        WiFi.begin(saved_ssid, saved_password);
    }
}
</code></pre><hr><h2>BLE: Bluetooth Low Energy в деталях</h2><p>BLE в ESP32 реализован через NimBLE (более лёгкий стек, рекомендуется) или Bluedroid.</p><h3>GATT Server — ESP32 как BLE периферия</h3><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>#include &lt;NimBLEDevice.h&gt;

// UUID сервисов и характеристик (генерируйте свои на uuidgenerator.net)
#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define TEMPERATURE_UUID    "beb5483e-36e1-4688-b7f5-ea07361b26a8"
#define CONTROL_UUID        "beb5483e-36e1-4688-b7f5-ea07361b26a9"

NimBLEServer*         pServer = nullptr;
NimBLECharacteristic* pTempChar = nullptr;
NimBLECharacteristic* pCtrlChar = nullptr;
bool deviceConnected = false;

// Callbacks для событий подключения
class ServerCallbacks : public NimBLEServerCallbacks {
    void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) override {
        deviceConnected = true;
        Serial.printf("BLE: клиент подключён, addr: %s\n",
                      NimBLEAddress(desc-&gt;peer_ota_addr).toString().c_str());
        
        // Обновляем параметры соединения для лучшей производительности
        pServer-&gt;updateConnParams(desc-&gt;conn_handle, 6, 6, 0, 100);
    }
    
    void onDisconnect(NimBLEServer* pServer) override {
        deviceConnected = false;
        Serial.println("BLE: клиент отключился");
        // Перезапускаем рекламу
        NimBLEDevice::startAdvertising();
    }
};

// Callbacks для записи характеристики управления
class ControlCallbacks : public NimBLECharacteristicCallbacks {
    void onWrite(NimBLECharacteristic* pChar) override {
        std::string value = pChar-&gt;getValue();
        if (value.length() &gt; 0) {
            uint8_t command = value[0];
            Serial.printf("BLE: команда получена: 0x%02X\n", command);
            
            switch (command) {
                case 0x01: relay_on();  break;
                case 0x00: relay_off(); break;
            }
        }
    }
};

void ble_init() {
    NimBLEDevice::init("ESP32-Sensor");
    NimBLEDevice::setMTU(185);  // Увеличиваем MTU для больших пакетов
    
    pServer = NimBLEDevice::createServer();
    pServer-&gt;setCallbacks(new ServerCallbacks());
    
    // Создаём сервис
    NimBLEService* pService = pServer-&gt;createService(SERVICE_UUID);
    
    // Характеристика температуры (только чтение + уведомления)
    pTempChar = pService-&gt;createCharacteristic(
        TEMPERATURE_UUID,
        NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
    );
    pTempChar-&gt;setValue(0.0f);
    
    // Характеристика управления (запись)
    pCtrlChar = pService-&gt;createCharacteristic(
        CONTROL_UUID,
        NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR  // NR = No Response (быстрее)
    );
    pCtrlChar-&gt;setCallbacks(new ControlCallbacks());
    
    pService-&gt;start();
    
    // Настройка рекламы
    NimBLEAdvertising* pAdv = NimBLEDevice::getAdvertising();
    pAdv-&gt;addServiceUUID(SERVICE_UUID);
    pAdv-&gt;setScanResponse(true);
    pAdv-&gt;setMinPreferred(0x06);
    pAdv-&gt;start();
    
    Serial.println("BLE запущен, ожидаем подключения...");
}

// Обновление данных температуры (вызывать периодически)
void ble_update_temperature(float temperature) {
    if (!deviceConnected) return;
    
    // Отправляем float как 4 байта
    uint8_t data[4];
    memcpy(data, &amp;temperature, 4);
    pTempChar-&gt;setValue(data, 4);
    pTempChar-&gt;notify();  // Push уведомление подключённому клиенту
}
</code></pre><hr><h2>NVS: хранение настроек во Flash</h2><p>NVS (Non-Volatile Storage) — key-value хранилище в Flash памяти ESP32. Пережи вает перезагрузки и обновления прошивки:</p><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>#include &lt;Preferences.h&gt;  // Arduino framework

Preferences prefs;

struct DeviceConfig {
    char    mqtt_host[64];
    uint16_t mqtt_port;
    char    device_id[32];
    float   setpoint;
    bool    auto_mode;
};

DeviceConfig config;

void config_load_defaults() {
    strlcpy(config.mqtt_host, "192.168.1.100", sizeof(config.mqtt_host));
    config.mqtt_port  = 1883;
    strlcpy(config.device_id, "esp32_001", sizeof(config.device_id));
    config.setpoint   = 25.0f;
    config.auto_mode  = true;
}

bool config_load() {
    prefs.begin("config", true);  // true = read-only
    
    if (!prefs.isKey("mqtt_host")) {
        prefs.end();
        return false;  // Первый запуск — нет сохранённых настроек
    }
    
    prefs.getString("mqtt_host", config.mqtt_host, sizeof(config.mqtt_host));
    config.mqtt_port = prefs.getUShort("mqtt_port", 1883);
    prefs.getString("device_id", config.device_id, sizeof(config.device_id));
    config.setpoint  = prefs.getFloat("setpoint", 25.0f);
    config.auto_mode = prefs.getBool("auto_mode", true);
    
    prefs.end();
    return true;
}

void config_save() {
    prefs.begin("config", false);  // false = read-write
    
    prefs.putString("mqtt_host",  config.mqtt_host);
    prefs.putUShort("mqtt_port",  config.mqtt_port);
    prefs.putString("device_id",  config.device_id);
    prefs.putFloat("setpoint",    config.setpoint);
    prefs.putBool("auto_mode",    config.auto_mode);
    
    prefs.end();
    Serial.println("Конфигурация сохранена в NVS");
}

void config_reset() {
    prefs.begin("config", false);
    prefs.clear();
    prefs.end();
    Serial.println("NVS очищен, перезагрузка...");
    ESP.restart();
}

// Использование:
void setup() {
    if (!config_load()) {
        Serial.println("Первый запуск, загружаем дефолты");
        config_load_defaults();
        config_save();
    }
    
    Serial.printf("MQTT: %s:%d\n", config.mqtt_host, config.mqtt_port);
}
</code></pre><hr><h2>OTA: обновление прошивки по воздухуESP32: глубокое погружение</h2><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>#include &lt;ArduinoOTA.h&gt;
#include &lt;Update.h&gt;

// ===== ПРОСТОЕ OTA ЧЕРЕЗ Arduino IDE =====
void ota_init_arduino() {
    ArduinoOTA.setHostname("esp32-gateway-001");
    ArduinoOTA.setPassword("ota_secret_password");
    
    ArduinoOTA.onStart([]() {
        String type = ArduinoOTA.getCommand() == U_FLASH ? "прошивку" : "файловую систему";
        Serial.printf("OTA: начало обновления %s\n", type.c_str());
        
        // Останавливаем критичные задачи перед обновлением
        mqtt_stop();
        modbus_stop();
    });
    
    ArduinoOTA.onEnd([]() {
        Serial.println("OTA: обновление завершено, перезагрузка...");
    });
    
    ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
        static uint8_t last_pct = 0;
        uint8_t pct = progress * 100 / total;
        if (pct != last_pct &amp;&amp; pct % 10 == 0) {
            Serial.printf("OTA: %u%%\n", pct);
            last_pct = pct;
        }
    });
    
    ArduinoOTA.onError([](ota_error_t error) {
        const char* errors[] = {"Auth Failed", "Begin Failed", "Connect Failed",
                                "Receive Failed", "End Failed"};
        Serial.printf("OTA Ошибка[%u]: %s\n", error,
                      error &lt;= 4 ? errors[error] : "Unknown");
    });
    
    ArduinoOTA.begin();
}

// В loop() добавить:
// ArduinoOTA.handle();

// ===== HTTP OTA: скачивание прошивки с сервера =====
#include &lt;HTTPUpdate.h&gt;
#include &lt;WiFiClientSecure.h&gt;

void ota_update_from_server(const char* server_url) {
    Serial.printf("OTA: загрузка с %s\n", server_url);
    
    WiFiClient client;
    // Или WiFiClientSecure для HTTPS (настройте сертификат!)
    
    httpUpdate.setLedPin(LED_BUILTIN, LOW);
    
    // Callback прогресса
    httpUpdate.onProgress([](int current, int total) {
        Serial.printf("OTA: %d/%d bytes (%d%%)\n",
                      current, total, current * 100 / total);
    });
    
    t_httpUpdate_return ret = httpUpdate.update(client, server_url);
    
    switch (ret) {
        case HTTP_UPDATE_FAILED:
            Serial.printf("OTA ошибка (%d): %s\n",
                         httpUpdate.getLastError(),
                         httpUpdate.getLastErrorString().c_str());
            break;
        case HTTP_UPDATE_NO_UPDATES:
            Serial.println("OTA: нет обновлений");
            break;
        case HTTP_UPDATE_OK:
            Serial.println("OTA: успех, перезагрузка...");
            break;
    }
}

// Проверка обновлений по расписанию:
void check_for_updates() {
    static uint32_t lastCheck = 0;
    const uint32_t CHECK_INTERVAL = 3600000UL;  // 1 час
    
    if (millis() - lastCheck &lt; CHECK_INTERVAL) return;
    lastCheck = millis();
    
    // Проверяем версию на сервере
    String server_version = http_get_json("/api/firmware/version")["version"];
    
    if (server_version != FIRMWARE_VERSION) {
        Serial.printf("Доступна новая версия: %s (текущая: %s)\n",
                      server_version.c_str(), FIRMWARE_VERSION);
        ota_update_from_server("http://server/firmware/latest.bin");
    }
}
</code></pre><hr><h2>Deep Sleep: энергосбережение</h2><p>ESP32 в active mode потребляет ~80–240 мА. В deep sleep — 10 мкА!</p><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>#include &lt;esp_sleep.h&gt;
#include &lt;esp_wifi.h&gt;

// Типы пробуждения:
// - Таймер (RTC таймер)
// - GPIO (кнопка, сигнал)
// - Touch (сенсорные входы)
// - ULP (Ultra-Low Power co-processor)
// - UART (RXD0)
// - BT (в режиме light sleep)

void go_to_deep_sleep(uint32_t sleep_seconds) {
    Serial.printf("Уходим в сон на %u секунд...\n", sleep_seconds);
    Serial.flush();
    
    // Закрываем WiFi перед сном (экономит время пробуждения)
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
    
    // Настраиваем пробуждение по таймеру
    esp_sleep_enable_timer_wakeup((uint64_t)sleep_seconds * 1000000ULL);
    
    // Пробуждение от GPIO4 (нажатие кнопки)
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_4, 0);  // 0 = LOW уровень
    
    // Входим в deep sleep
    esp_deep_sleep_start();
    // Код после этой строки не выполнится!
}

void setup() {
    Serial.begin(115200);
    
    // Определяем причину пробуждения
    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
    
    switch (cause) {
        case ESP_SLEEP_WAKEUP_TIMER:
            Serial.println("Пробуждение: таймер");
            send_sensor_data();  // Отправляем данные и снова в сон
            break;
            
        case ESP_SLEEP_WAKEUP_EXT0:
            Serial.println("Пробуждение: кнопка");
            // Пользователь нажал кнопку — полная работа
            full_operation_mode();
            return;
            
        case ESP_SLEEP_WAKEUP_UNDEFINED:
            Serial.println("Первый запуск или reset");
            first_boot_setup();
            break;
            
        default:
            Serial.printf("Причина: %d\n", cause);
    }
    
    // Снова в сон через 60 секунд
    go_to_deep_sleep(60);
}

// RTC Memory: данные переживают deep sleep!
RTC_DATA_ATTR int boot_count = 0;
RTC_DATA_ATTR float last_temperature = 0.0f;
RTC_DATA_ATTR uint32_t error_count = 0;

void setup_with_rtc_memory() {
    boot_count++;
    Serial.printf("Загрузка #%d, последняя T=%.1f°C\n",
                  boot_count, last_temperature);
    
    // Читаем датчик, сохраняем в RTC memory
    last_temperature = read_temperature();
    
    go_to_deep_sleep(300);  // 5 минут
}
</code></pre><hr><h2>ADC: правильная работа с АЦП</h2><p>АЦП ESP32 имеет репутацию "неточного". Это правда — и вот почему и как с этим работать:</p><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>#include &lt;esp_adc/adc_oneshot.h&gt;
#include &lt;esp_adc/adc_cali.h&gt;
#include &lt;esp_adc/adc_cali_scheme.h&gt;

// Калиброванный ADC на ESP-IDF (точность ±5мВ вместо ±50мВ)

adc_oneshot_unit_handle_t adc1_handle;
adc_cali_handle_t cali_handle;

void adc_init_calibrated() {
    // Инициализация ADC
    adc_oneshot_unit_init_cfg_t init_config = {
        .unit_id  = ADC_UNIT_1,
        .ulp_mode = ADC_ULP_MODE_DISABLE,
    };
    ESP_ERROR_CHECK(adc_oneshot_new_unit(&amp;init_config, &amp;adc1_handle));
    
    // Настройка канала (GPIO34 = ADC1 Channel 6)
    adc_oneshot_chan_cfg_t chan_config = {
        .bitwidth = ADC_BITWIDTH_12,
        .atten    = ADC_ATTEN_DB_12,  // 0-3.3В диапазон
    };
    ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle,
                                               ADC_CHANNEL_6, &amp;chan_config));
    
    // Калибровка (Line Fitting или Curve Fitting)
    adc_cali_line_fitting_config_t cali_config = {
        .unit_id  = ADC_UNIT_1,
        .atten    = ADC_ATTEN_DB_12,
        .bitwidth = ADC_BITWIDTH_12,
    };
    ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&amp;cali_config, &amp;cali_handle));
}

float adc_read_voltage_mv() {
    int raw;
    ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, ADC_CHANNEL_6, &amp;raw));
    
    int voltage_mv;
    ESP_ERROR_CHECK(adc_cali_raw_to_voltage(cali_handle, raw, &amp;voltage_mv));
    
    return (float)voltage_mv;
}

// Oversampling для повышения точности (16x → +2 бита)
float adc_read_averaged(int samples = 64) {
    int64_t sum = 0;
    for (int i = 0; i &lt; samples; i++) {
        int raw;
        adc_oneshot_read(adc1_handle, ADC_CHANNEL_6, &amp;raw);
        sum += raw;
        delayMicroseconds(100);
    }
    
    int avg_raw = sum / samples;
    int voltage_mv;
    adc_cali_raw_to_voltage(cali_handle, avg_raw, &amp;voltage_mv);
    return (float)voltage_mv;
}

// Важные ограничения ADC ESP32:
// - GPIO36, 37, 38, 39: только вход, без pullup/pulldown в кристалле
// - ADC2 нельзя использовать одновременно с WiFi!
// - Нелинейность вблизи 0В и 3.3В — оставайтесь в диапазоне 100мВ..3.1В
// - Для точных измерений: внешний АЦП MCP3208 по SPI
</code></pre><hr><h2>Практический проект: промышленный IoT узел</h2><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>// Полная архитектура ESP32 IoT узла

#include &lt;Arduino.h&gt;
#include &lt;WiFi.h&gt;
#include &lt;PubSubClient.h&gt;
#include &lt;ArduinoJson.h&gt;
#include &lt;Preferences.h&gt;

// ===== КОНФИГУРАЦИЯ =====
#define FIRMWARE_VERSION  "1.2.3"
#define DEVICE_MODEL      "ESP32-IoT-Node"
#define PUBLISH_INTERVAL_MS  5000
#define WATCHDOG_TIMEOUT_MS  30000

// ===== ГЛОБАЛЬНОЕ СОСТОЯНИЕ =====
struct State {
    bool    wifi_connected   = false;
    bool    mqtt_connected   = false;
    float   temperature      = 0;
    float   humidity         = 0;
    float   pressure         = 0;
    uint32_t uptime_sec      = 0;
    uint32_t publish_count   = 0;
    uint32_t error_count     = 0;
};

State state;
WiFiClient   wifiClient;
PubSubClient mqtt(wifiClient);

// ===== МНОГОЗАДАЧНОСТЬ =====
QueueHandle_t  sensorQueue;
SemaphoreHandle_t stateMutex;

// Задача: чтение датчиков (Core 1)
void task_sensors(void *pv) {
    for (;;) {
        // Здесь: читаем датчики
        float t = 25.0 + random(-10, 10) / 10.0;  // Имитация
        float h = 50.0 + random(-5, 5) / 10.0;
        
        // Обновляем состояние через мьютекс
        xSemaphoreTake(stateMutex, portMAX_DELAY);
        state.temperature = t;
        state.humidity    = h;
        xSemaphoreGive(stateMutex);
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// Задача: MQTT публикация (Core 0, рядом с WiFi)
void task_mqtt(void *pv) {
    for (;;) {
        if (!mqtt.connected()) {
            if (WiFi.isConnected()) {
                if (mqtt.connect("esp32-node", "user", "pass",
                                 "nodes/esp32-001/status", 1, true, "{\"online\":false}")) {
                    mqtt.publish("nodes/esp32-001/status", "{\"online\":true}", true);
                    mqtt.subscribe("nodes/esp32-001/commands");
                }
            }
        }
        
        mqtt.loop();
        
        // Публикация данных
        static uint32_t lastPublish = 0;
        if (millis() - lastPublish &gt;= PUBLISH_INTERVAL_MS) {
            lastPublish = millis();
            
            StaticJsonDocument&lt;256&gt; doc;
            
            xSemaphoreTake(stateMutex, portMAX_DELAY);
            doc["temperature"] = state.temperature;
            doc["humidity"]    = state.humidity;
            doc["uptime"]      = state.uptime_sec;
            doc["errors"]      = state.error_count;
            doc["version"]     = FIRMWARE_VERSION;
            doc["rssi"]        = WiFi.RSSI();
            doc["free_heap"]   = ESP.getFreeHeap();
            xSemaphoreGive(stateMutex);
            
            char payload[256];
            serializeJson(doc, payload);
            mqtt.publish("nodes/esp32-001/telemetry", payload);
            state.publish_count++;
        }
        
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

// Задача: watchdog и uptime (Core 1)
void task_system(void *pv) {
    for (;;) {
        state.uptime_sec++;
        
        // Проверка heap (memory leak detection)
        if (ESP.getFreeHeap() &lt; 10000) {
            Serial.println("КРИТИЧНО: мало памяти! Перезагрузка...");
            ESP.restart();
        }
        
        // Heartbeat LED
        digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void setup() {
    Serial.begin(115200);
    Serial.printf("\nESP32 IoT Node v%s\n", FIRMWARE_VERSION);
    Serial.printf("Chip: %s, Rev: %d, Cores: %d\n",
                  ESP.getChipModel(), ESP.getChipRevision(), ESP.getChipCores());
    
    // Инициализация
    stateMutex  = xSemaphoreCreateMutex();
    sensorQueue = xQueueCreate(10, sizeof(float));
    
    // WiFi
    WiFi.mode(WIFI_STA);
    WiFi.begin("SSID", "PASSWORD");
    
    // MQTT
    mqtt.setServer("192.168.1.100", 1883);
    mqtt.setBufferSize(1024);
    
    // Запускаем задачи на разных ядрах
    xTaskCreatePinnedToCore(task_sensors, "Sensors", 4096, NULL, 3, NULL, 1);
    xTaskCreatePinnedToCore(task_mqtt,    "MQTT",    8192, NULL, 4, NULL, 0);
    xTaskCreatePinnedToCore(task_system,  "System",  2048, NULL, 1, NULL, 1);
    
    Serial.println("Задачи запущены");
}

void loop() {
    // loop() работает на Core 1 с низким приоритетом
    // Можно использовать для некритичных задач или оставить пустым
    vTaskDelay(pdMS_TO_TICKS(1000));
}
</code></pre><hr><h2>Выбор инструментария: Arduino vs ESP-IDF</h2><div class="tmiRichText__table-wrapper"><table style="width: 840px;"><colgroup><col style="width:327px;"><col style="width:307px;"><col style="width:206px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Критерий</p></th><th colspan="1" rowspan="1"><p>Arduino Framework</p></th><th colspan="1" rowspan="1"><p>ESP-IDF (native)</p></th></tr><tr><td colspan="1" rowspan="1"><p>Порог входа</p></td><td colspan="1" rowspan="1"><p>Низкий</p></td><td colspan="1" rowspan="1"><p>Высокий</p></td></tr><tr><td colspan="1" rowspan="1"><p>Документация</p></td><td colspan="1" rowspan="1"><p>Обширная, много примеров</p></td><td colspan="1" rowspan="1"><p>Официальная, полная</p></td></tr><tr><td colspan="1" rowspan="1"><p>Производительность</p></td><td colspan="1" rowspan="1"><p>Достаточная</p></td><td colspan="1" rowspan="1"><p>Максимальная</p></td></tr><tr><td colspan="1" rowspan="1"><p>Доступ к периферии</p></td><td colspan="1" rowspan="1"><p>Через библиотеки</p></td><td colspan="1" rowspan="1"><p>Прямой</p></td></tr><tr><td colspan="1" rowspan="1"><p>Размер бинарника</p></td><td colspan="1" rowspan="1"><p>Больше</p></td><td colspan="1" rowspan="1"><p>Меньше</p></td></tr><tr><td colspan="1" rowspan="1"><p>RTOS</p></td><td colspan="1" rowspan="1"><p>Доступен (FreeRTOS через задачи)</p></td><td colspan="1" rowspan="1"><p>Нативный</p></td></tr><tr><td colspan="1" rowspan="1"><p>Время разработки</p></td><td colspan="1" rowspan="1"><p>Быстрее</p></td><td colspan="1" rowspan="1"><p>Медленнее</p></td></tr><tr><td colspan="1" rowspan="1"><p>Production-ready</p></td><td colspan="1" rowspan="1"><p>Да (если делать правильно)</p></td><td colspan="1" rowspan="1"><p>Да</p></td></tr><tr><td colspan="1" rowspan="1"><p>Рекомендация</p></td><td colspan="1" rowspan="1"><p>Прототипы, несложные задачи</p></td><td colspan="1" rowspan="1"><p>Серийное производство</p></td></tr></tbody></table></div><hr><h2>Заключение</h2><p>ESP32 — один из лучших выборов для промышленных IoT-узлов с умеренными требованиями к реальному времени. Двухъядерность позволяет изолировать WiFi-стек от прикладного кода, богатая периферия закрывает большинство интерфейсных задач, встроенный FreeRTOS — для многозадачности.</p><p>Ключевые принципы надёжного ESP32-устройства: статический IP вместо DHCP, watchdog timer, NVS для конфигурации, OTA для обновлений, RTC memory для данных через sleep, мониторинг heap и перезагрузка при критичном уровне.</p><p>ESP32 — это не замена промышленному ПЛК. Но как edge-узел сбора данных, шлюз протоколов или умный датчик — идеальный выбор.</p>]]></description><guid isPermaLink="false">137</guid><pubDate>Sat, 21 Mar 2026 20:48:31 +0000</pubDate></item><item><title>&#x420;&#x430;&#x437;&#x440;&#x430;&#x431;&#x43E;&#x442;&#x43A;&#x430; &#x43F;&#x435;&#x447;&#x430;&#x442;&#x43D;&#x44B;&#x445; &#x43F;&#x43B;&#x430;&#x442;: &#x43E;&#x442; &#x441;&#x445;&#x435;&#x43C;&#x44B; &#x434;&#x43E; &#x43F;&#x440;&#x43E;&#x438;&#x437;&#x432;&#x43E;&#x434;&#x441;&#x442;&#x432;&#x430;</title><link>https://ithub.uno/statiarticles/2_ithub-articles-on-electronics-and-electrical-engineering/%D1%80%D0%B0%D0%B7%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0-%D0%BF%D0%B5%D1%87%D0%B0%D1%82%D0%BD%D1%8B%D1%85-%D0%BF%D0%BB%D0%B0%D1%82-%D0%BE%D1%82-%D1%81%D1%85%D0%B5%D0%BC%D1%8B-%D0%B4%D0%BE-%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%B0-r140/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/PCB-from-Schematic.webp.c9650a39c0f44a3ec500f5b20e2ddfa1.webp" /></p>
<h2>PCB-дизайн: мост между схемой и устройством</h2><p>Отличная схема в плохом PCB-дизайне — это источник помех, нестабильная работа, проблемы с EMC и перегрев. Хороший PCB-дизайн — это такая же инженерная дисциплина, как схемотехника.</p><p>Современные инструменты доступны бесплатно (KiCad), производство быстрое и дешёвое (JLCPCB, PCBWay, OSHPark — 5 плат за $2 с доставкой за 2 недели). Барьер для входа в PCB-разработку никогда не был ниже.</p><p>Но количество "тонких мест" не уменьшилось. Сегодня — практика без воды.</p><hr><h2>Инструменты: что выбрать</h2><h3>KiCad (бесплатно, открытый исходный код)</h3><ul><li><p>Версия 7/8 — функционально близка к коммерческим решениям</p></li><li><p>Отличный Schematic Editor и PCB Editor</p></li><li><p>SPICE-симуляция, 3D-просмотр</p></li><li><p>Огромная библиотека компонентов, активное сообщество</p></li><li><p><strong>Рекомендация:</strong> для большинства проектов вполне достаточно</p></li></ul><h3>Altium Designer</h3><ul><li><p>Промышленный стандарт в телекоме и аэрокосмосе</p></li><li><p>$8 000+/год лицензия</p></li><li><p>Нельзя просто взять и попробовать</p></li><li><p>CircuitMaker (бесплатная версия Altium) — сильно урезана</p></li></ul><h3>Eagle (Autodesk)</h3><ul><li><p>Бесплатно до 2 слоёв и 80 см²</p></li><li><p>Хорошая экосистема библиотек SparkFun/Adafruit</p></li><li><p>Интегрируется с Fusion 360</p></li></ul><h3>EasyEDA / LCEDA</h3><ul><li><p>Браузерный, бесплатный</p></li><li><p>Прямая интеграция с JLCPCB и их библиотекой компонентов</p></li><li><p>Быстрый старт для простых проектов</p></li></ul><hr><h2>Процесс разработки: от идеи до платы</h2><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>1. Спецификация → 2. Схема → 3. Выбор корпуса → 4. Разводка
→ 5. DRC/проверки → 6. Генерация Gerber → 7. Заказ → 8. Пайка → 9. Отладка
</code></pre><h3>Шаг 1: Схема (Schematic)</h3><p>Правила хорошей схемы:</p><ul><li><p><strong>Читается слева направо:</strong> сигнал течёт от входа к выходу</p></li><li><p><strong>VCC сверху, GND снизу:</strong> стандартная конвенция</p></li><li><p><strong>Все пины обозначены:</strong> нет "hanging pins" без назначения</p></li><li><p><strong>Развязочные конденсаторы:</strong> рядом с каждой микросхемой на схеме (не просто в угол!)</p></li><li><p><strong>Комментарии:</strong> номиналы, допуски, критичные параметры</p></li></ul><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Обязательные компоненты для питания:
- Входной конденсатор: электролит 100 мкФ/16В (bulk capacitor)
- Bypass конденсатор: керамика 100 нФ X7R рядом с каждым VCC пином IC
- Bypass конденсатор: 10 нФ дополнительно для высокочастотных IC
- Ferrite bead (если нужна изоляция аналоговой и цифровой земли)
</code></pre><h3>Шаг 2: Footprint и 3D-модели</h3><p>Выбор правильного корпуса компонента критически важен:</p><ul><li><p>Проверьте datasheet производителя — landing pattern (рекомендуемый footprint)</p></li><li><p>IPC-7351 — стандарт land pattern для SMD</p></li><li><p>Предпочитайте компоненты из основных серий (0402, 0603, 0805 — легко заказать)</p></li></ul><hr><h2>Слои PCB: понимание стека</h2><h3>Двухслойная PCB (2-layer):</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>─── Copper Top (компоненты, сигналы)
─── Core (диэлектрик FR4, 1.6 мм)
─── Copper Bottom (земля, сигналы)
</code></pre><p>Дёшево ($2–5 за 10 плат), достаточно для большинства низкочастотных проектов.</p><h3>Четырёхслойная PCB (4-layer):</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>─── Layer 1: Copper Top (сигналы, компоненты)
─── Prepreg (диэлектрик)
─── Layer 2: Ground Plane (сплошная земля!)
─── Core
─── Layer 3: Power Plane (питание)
─── Prepreg
─── Layer 4: Copper Bottom (сигналы)
</code></pre><p>Дороже (~$15–30 за 10 плат), но:</p><ul><li><p>Слой земли под каждым сигнальным слоем — контролируемый импеданс</p></li><li><p>Чистое питание (мало помех)</p></li><li><p>Лучшая EMC</p></li><li><p><strong>Обязательна при F &gt; 50 МГц или быстрых фронтах</strong></p></li></ul><hr><h2>Земляной полигон (Ground Plane): основа всего</h2><p>Это самое важное правило PCB-дизайна. Сплошной медный полигон на слое GND:</p><p><strong>Почему это важно:</strong></p><ol><li><p>Низкоиндуктивный путь возврата тока для каждого сигнала</p></li><li><p>Экранирование сигнальных слоёв</p></li><li><p>Тепловая масса для компонентов</p></li><li><p>Референс для импеданса сигналов</p></li></ol><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>ПРАВИЛЬНО: земля под каждым сигнальным трэком
─Signal──────────────────────────────── Layer 1
─────────────────────────────────────── Layer 2 (GND plane)
Ток сигнала течёт по трэку,
ток возврата — прямо под ним по плоскости (минимальная петля!)

НЕПРАВИЛЬНО: нет плоскости, возврат по произвольному пути
─Signal──────────────────────────────── Layer 1
──────────GND wire──────────────────── Layer 2
Ток возврата ищет произвольный путь → большая петля → EMI!
</code></pre><h3>Критические правила полигона:</h3><p><strong>Не разрезайте плоскость без необходимости!</strong></p><p><code><span class="tmiEmoji" title="">❌</span>  Плохо: прорезь делит плоскость на два острова ─────────────────────────────────────────────────          ╔═══════════════════╗  ← Прорезь! ─────────╝                   ╚──────────────────── Ток возврата вынужден огибать прорезь → большая петля → EMI  </code></p><p><code><span class="tmiEmoji" title="">✅</span> Хорошо: полигон цельный ─────────────────────────────────────────────────────────────          (никаких прорезей без веской причины) ───────────────────────────────────────────────────────────── </code></p><p><strong>Via stitching — соединение полигонов между слоями:</strong> Размещайте заземляющие виа равномерно по всей плате (через каждые 1–2 см). Это снижает индуктивность плоскости.</p><hr><h2>Импеданс трэков: для высокоскоростных сигналов</h2><p>При частоте выше ~100 МГц или временах нарастания фронта &lt;2нс — трэки нужно рассматривать как длинные линии. Импеданс трэка должен совпадать с импедансом источника и нагрузки (обычно 50 Ом для одиночного трэка или 100 Ом для дифференциальной пары).</p><h3>Формула для микрополосковой линии (Microstrip):</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Трэк на поверхностном слое над плоскостью земли:

Z0 ≈ (87 / √(εr + 1.41)) × ln(5.98 × h / (0.8 × w + t))

Где:
εr = диэлектрическая проницаемость (FR4: 4.2–4.5)
h  = расстояние от трэка до плоскости, мкм
w  = ширина трэка, мкм
t  = толщина меди, мкм (стандарт 1oz = 35 мкм)

Для FR4, 4-слойная плата, h=200 мкм:
50 Ом → w ≈ 450 мкм (0.45 мм)
75 Ом → w ≈ 200 мкм (0.20 мм)
</code></pre><p><strong>Практически:</strong> используйте онлайн-калькуляторы (Saturn PCB Toolkit, Polar Si9000) или параметр stackup от производителя платы (JLCPCB публикует точные параметры своего FR4).</p><hr><h2>Дифференциальные пары</h2><p>USB, LVDS, HDMI, Ethernet, SerDes — все используют дифференциальные пары. Правила трассировки:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Правила дифференциальных пар:
1. Одинаковая длина обоих трэков (skew &lt; 5 мил/пс сигнала)
2. Одинаковое расстояние между трэками по всей длине (coupling)
3. Расстояние между трэками пары: 2-3 толщины диэлектрика
4. Без прямых углов (45° или радиусы)
5. Пересечение плоскости GND: только перпендикулярно, не вдоль щели

USB FS (12 Мбит/с): Z_diff = 90 Ом, зазор 150 мкм
USB HS (480 Мбит/с): Z_diff = 90 Ом, контроль длины ±0.1 мм
Ethernet 100M: Z_diff = 100 Ом через трансформатор
LVDS: Z_diff = 100 Ом
</code></pre><hr><h2>Декупплинг конденсаторы: где и какие</h2><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Стратегия декупплинга (от источника питания к IC):

[Источник] → [100 мкФ electrolytic] → [10 мкФ MLCC] → [100 нФ MLCC] → [IC]
               (bulk, далеко)          (medium, ближе)  (bypass, вплотную)

Расположение на плате:
┌────────────────────────────────────┐
│   ┌──────┐                         │
│   │  IC  │  ← 100нФ вплотную к VCC пину
│   └──────┘  ← 10нФ рядом
│       ...                           │
│              [100мкФ]               │
└────────────────────────────────────┘

Расстояние:
- 100 нФ: ≤ 1 мм от VCC пина IC
- 10 нФ:  ≤ 3 мм
- 100 мкФ: ≤ 10 мм

НЕПРАВИЛЬНО: конденсатор в угол платы далеко от IC
Эффективность падает экспоненциально с расстоянием!
</code></pre><h3>Выбор диэлектрика конденсатора:</h3><div class="tmiRichText__table-wrapper"><table style="min-width: 60px;"><colgroup><col style="min-width:20px;"><col style="min-width:20px;"><col style="min-width:20px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Диэлектрик</p></th><th colspan="1" rowspan="1"><p>Применение</p></th><th colspan="1" rowspan="1"><p>Зависимость от V/T</p></th></tr><tr><td colspan="1" rowspan="1"><p>X7R</p></td><td colspan="1" rowspan="1"><p>Bypass, фильтры (100 пФ – 10 мкФ)</p></td><td colspan="1" rowspan="1"><p>Умеренная</p></td></tr><tr><td colspan="1" rowspan="1"><p>X5R</p></td><td colspan="1" rowspan="1"><p>Bulk bypass (1 мкФ – 47 мкФ)</p></td><td colspan="1" rowspan="1"><p>Значительная при V</p></td></tr><tr><td colspan="1" rowspan="1"><p>C0G/NP0</p></td><td colspan="1" rowspan="1"><p>Точные цепи, LC-фильтры</p></td><td colspan="1" rowspan="1"><p>Минимальная</p></td></tr><tr><td colspan="1" rowspan="1"><p>Y5V</p></td><td colspan="1" rowspan="1"><p>Не использовать в серьёзных проектах</p></td><td colspan="1" rowspan="1"><p>Огромная (-80%!)</p></td></tr></tbody></table></div><p><strong>Важно:</strong> MLCC конденсатор 10 мкФ X5R 6.3В при напряжении 5В теряет <strong>60% ёмкости</strong> из-за DC bias! Проверяйте даташит.</p><hr><h2>Тепловой дизайн на PCB</h2><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Тепловое сопротивление медной области:
R_th = L / (λ × A) = L / (380 Вт/(м·К) × ширина × толщина)

Для FR4 (плохой теплопроводник, λ=0.3 Вт/(м·К)):
Тепло течёт ПО МЕДИ, не через диэлектрик!

Рекомендации:
1. Thermal vias под горячими компонентами:
   ┌──────────────────────────┐
   │   IC (рассеивает 2 Вт)  │
   │  ●●●●●●●●●●●●●●●●●●●  │ ← via array к Cu plane
   └──────────────────────────┘
   Диаметр via: 0.3 мм, шаг: 0.8–1.0 мм

2. Copper pour (медный полигон) рядом с горячим компонентом
3. Radiator pad: обнажённая медь сверху для конвекции

Расчёт температуры:
T_junction = T_ambient + P × (R_th_jc + R_th_board + R_th_air)
                                                      ↑
                               Это то, на что влияет PCB-дизайн
</code></pre><hr><h2>EMC: электромагнитная совместимость</h2><p>Плохой PCB-дизайн — главная причина проблем с EMC-сертификацией.</p><h3>Три правила EMC для PCB:</h3><p><strong>1. Минимизировать площадь токовых петель:</strong></p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Высокочастотный ток протекает:
Source → трэк → нагрузка → возврат по плоскости GND (под трэком)

Площадь петли = длина трэка × расстояние до плоскости

Уменьшить расстояние = 4-слойка со сплошной GND плоскостью
</code></pre><p><strong>2. Разделить аналоговую и цифровую землю правильно:</strong></p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Популярный МИФ: "нужно разделить AGND и DGND полностью"
РЕАЛЬНОСТЬ: один сплошной полигон GND, аналоговые компоненты в одном углу,
цифровые в другом. Соединяйте земли в ОДНОЙ точке под ADC/DAC.

Разрезать полигон почти никогда не нужно и часто вредно!
</code></pre><p><strong>3. Развязка питания IC:</strong> Уже разобрали выше — 100нФ вплотную к каждому VCC пину.</p><h3>Дополнительные меры EMC:</h3><ul><li><p>Ferrite bead в линии питания шумных цифровых блоков</p></li><li><p>Common-mode фильтры на интерфейсных линиях</p></li><li><p>Guard ring (защитное кольцо) вокруг аналоговых блоков</p></li><li><p>Минимизировать длину высокочастотных трэков</p></li></ul><hr><h2>Правила трассировки: шпаргалка</h2><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Ширина трэков:
- Питание (до 1А): 0.5 мм
- Питание (до 2А): 1.0 мм
- Питание (до 5А): 2.5 мм
- Сигналы: 0.15–0.25 мм (минимум производства: обычно 0.1 мм)

Зазоры:
- Сигнал-сигнал: ≥ 0.15 мм (производственный минимум)
- 100В AC: ≥ 1 мм (по воздуху), 2 мм (по поверхности)
- 250В AC: ≥ 2 мм / 4 мм

Переходные отверстия (via):
- Стандарт: диаметр 0.6 мм (отверстие 0.3 мм)
- Micro via: 0.2 мм — дорого, только если необходимо
- Технологические отверстия (крепёжные): нет меди, 3.2 мм (под M3)

Углы трэков:
</code></pre><p><code><span class="tmiEmoji" title="">✅</span> 45° (стандарт) </code></p><p><code><span class="tmiEmoji" title="">✅</span> Радиусы (лучше для высоких частот) </code></p><p><code><span class="tmiEmoji" title="">❌</span> 90° прямые углы (устарелая проблема, но лучше избегать) </code></p><hr><h2>Подготовка к производству: Gerber файлы</h2><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Набор файлов для производства:

Gerber файлы:
  .GTL - Top Copper (верхний слой меди)
  .GBL - Bottom Copper
  .GTS - Top Solder Mask (маска верхнего слоя)
  .GBS - Bottom Solder Mask
  .GTO - Top Silkscreen (маркировка)
  .GBO - Bottom Silkscreen
  .GKO - Board Outline (контур платы)
  .GM1..N - Inner layers (внутренние слои, если есть)

Drill файл:
  .DRL или .XLN - координаты и размеры отверстий

BOM (Bill of Materials):
  .CSV - список компонентов с номиналами, производителем, part number

Pick and Place:
  .CSV - координаты и ориентация SMD компонентов (для PCBA)
</code></pre><hr><h2>JLCPCB: заказ платы и PCBA-сборки</h2><p>JLCPCB — наиболее популярный среди разработчиков производитель:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Параметры стандартного заказа:
  Количество: 5 штук
  Слои: 2
  Размер: ≤ 100×100 мм
  Цена: $2 + доставка
  Срок: 2 дня производство + 1-2 недели доставка

PCBA (сборка компонентов):
  Выбор "SMT Assembly" при оформлении заказа
  Загрузить: Gerber + BOM + Pick&amp;Place CSV
  Компоненты: из их склада (Basic Parts бесплатно; Extended Parts - $3/тип)
  Нюанс: минимальный заказ PCBA - 2 платы, некоторые компоненты не доступны
</code></pre><hr><h2>Типичные ошибки новичков</h2><p><strong>1. Слишком тонкие трэки питания</strong> Трэк 0.15 мм, ток 500мА → нагрев, падение напряжения, деградация.</p><p><strong>2. Конденсаторы декупплинга далеко от IC</strong> Декупплинг работает только при минимальной индуктивности пути.</p><p><strong>3. Разрезанный земляной полигон</strong> Трэки проходят через полигон, создавая "острова" — петли, помехи.</p><p><strong>4. Несоответствие footprint реальному корпусу</strong> Проверьте в 3D-просмотре ДО заказа! Footprint 0402 vs 0603 — разные!</p><p><strong>5. Нет тестовых точек</strong> Как отлаживать плату без точек для щупа? Добавьте testpad на каждый критичный сигнал.</p><p><strong>6. Не проверена DRC (Design Rule Check)</strong> KiCad/EasyEDA имеют встроенную проверку. Всегда запускайте перед экспортом.</p><hr><h2>Заключение</h2><p>PCB-дизайн — навык, который приходит с практикой. Сделайте свою первую плату, закажите, спаяйте, найдите ошибки, сделайте вторую лучше. Итерационный процесс.</p><p>KiCad 8 — отличная бесплатная точка входа. Пройдите официальные туториалы на kicandhw.io. Изучите IPC-2221 (Generic Standard on Printed Board Design) — документ объёмный, но содержит ответы на большинство вопросов по правилам разводки.</p><p>Инвестируйте в понимание физики: как ток возвращается к источнику, что такое импеданс трэка, как работает декупллинг. С этим пониманием большинство решений по разводке становятся очевидными.</p>]]></description><guid isPermaLink="false">140</guid><pubDate>Sat, 21 Mar 2026 20:50:55 +0000</pubDate></item></channel></rss>
