Embedded HandbookПриложения
Все разделы › XXVI. От макета к изделию
STM32F103, STM32G474 · уровень средний · 24 мин чтения
Открыть в интерактивном справочнике

208б. Нагрузка 230 В от микроконтроллера: реле, симистор, твердотельное реле

Раздел 208 провёл сеть внутрь прибора и поставил барьер изоляции, 208а собрал прибор от промышленных 24 В. Остался последний шаг: вывод микроконтроллера на 3,3 В должен включить нагреватель, лампу или двигатель на 230 В. Способов три — реле, симистор через оптосимистор и твердотельное реле, — и у каждого своя физика: катушка, которая не хочет отпускать, симистор, который гаснет только в нуле тока, детектор нуля, который врёт на миллисекунду. Здесь всё это посчитано, а в конце таймер сам ловит ноль сети и открывает симистор на нужном угле.

Опоры: гасящий диод — 51.2, реле и твердотельное реле в общих чертах — 52.6, оптрон и его коэффициент передачи — 54.2 и 54.3, зазоры и барьер — 208.3 и 208.4, захват и одноимпульсный режим таймера — 13.6 и 13.8, фильтр входа — 13.10, действующее значение — 57.2, помехи — 203. Числа раздела — модель на ПК _verify/load208b/load208b.py (прогон — load208b_host.txt), код — load208b.c, собранный armclang для F103 и G474.

Как это называется в документации

Оригинал Перевод
triac симистор: двунаправленный тиристор
opto-triac, random-phase / zero-crossing оптосимистор: без детектора нуля / с детектором нуля
solid-state relay (SSR) твердотельное реле
latching current / holding current ток включения (защёлкивания) / ток удержания
snubber снаббер: RC-цепь поперёк ключа
zero-crossing detector детектор перехода через ноль
phase-angle control фазовое управление
burst-fire, integral-cycle control пакетное управление: целыми периодами
Trigger Mode режим запуска: счётчик стартует по фронту входа
One pulse mode одноимпульсный режим
PWM mode 2 ШИМ, режим 2: выход активен, пока CNT ≥ CCR
fSAMPLING=fDTS/32, N=8 самый длинный фильтр входа: 8 отсчётов на fDTS/32

208б.1 Реле: катушка отпускает дольше, чем кажется

Схема катушки уже есть в 52.6: транзисторный ключ и гасящий диод параллельно обмотке (51.2). Диод спасает ключ, но у него есть цена — медленное отпускание. После выключения ключа ток катушки не исчезает, а спадает по закону L/R, и пока он велик, якорь держится.

Модель — катушка из 51.2: 12 В, 30 мА, около 100 мГн. Сопротивление 400 Ом, постоянная времени L/R = 0,25 мс, запасено 45 мкДж. Ток спадает до нуля за t = (L/R) · ln(1 + I·R/Uогр), где Uогр — напряжение ограничителя; численное интегрирование дало те же числа:

Ограничитель Спад тока На ключе Съел ограничитель
диод 0,62 мс 13,1 В 6 мкДж из 45
диод + стабилитрон 24 В 0,098 мс 37,1 В 34 мкДж из 45
диод + стабилитрон 47 В 0,056 мс 60,1 В 39 мкДж из 45

Стабилитрон на 24 В последовательно с диодом ускоряет спад в 6,3 раза. Ту же роль играет ограничитель TVS (173). Плата — напряжение на ключе: 37,1 В вместо 13,1 В, и транзистор выбирают с запасом над этим числом. С одним диодом энергия уходит в сопротивление обмотки, со стабилитроном — в стабилитрон. Это спад тока, а не время отпускания якоря: механика добавляет своё, и её модель не считает — смотрят осциллографом по контакту.

Почему скорость важна. Контакты расходятся под током, между ними загорается дуга. На переменном токе она гаснет сама в ближайшем нуле тока, то есть не позже чем через 10 мс, но каждая дуга выжигает металл контакта. Медленное отпускание — дольше путь в зоне дуги.

Ресурс считается делением (52.6): 100 000 циклов. Термостат, щёлкающий раз в 60 с, съедает его за 69,4 суток. Пакетное управление с периодом 1 с (208б.5) — за 27,8 часа, то есть за 1,2 суток. Вывод: реле — для редких включений, для частых нужен симистор.

Включение в нуле реле не даётся. Время срабатывания реле — около 10 мс (52.6), и оно плавает. Если подать команду по детектору нуля с поправкой на среднее время, контакт замкнётся не в нуле, а с разбросом:

Разброс срабатывания Угол от нуля Напряжение на контакте при замыкании
±0,5 мс ±9° до 51 В
±1 мс ±18° до 101 В
±2 мс ±36° до 191 В

Без синхронизации — до пиковых 325 В. Выигрыш реален, только если разброс конкретного реле мерили, а не взяли из головы; разброс в модели — параметр, не паспорт.


208б.2 RC-снаббер: расчёт

Поперёк контакта или симистора ставят последовательную цепь R и C — снаббер. Он делает две вещи: гасит искру на контакте и ограничивает скорость нарастания напряжения на симисторе после выключения. Слишком быстрое нарастание открывает симистор снова, без импульса затвора. Пример — 100 Ом и 100 нФ.

Закрытый ключ течёт. На 50 Гц у 100 нФ реактивное сопротивление 31,8 кОм, и через снаббер течёт 7,23 мА даже при выключенной нагрузке. Для нагревателя это ничто, а маленькая нагрузка — светодиодная лампа, катушка сетевого реле — от этого тока может светиться или не отпускать. В резисторе при этом всего 5,2 мВт. Конденсатор стоит последовательно с нагрузкой поперёк сети, поэтому он — класса X (208.4).

Резистор нельзя убрать. При включении на вершине синусоиды заряженный конденсатор разряжается прямо в ключ; 100 Ом ограничивают пик до 3,25 А.

Индуктивная нагрузка — худший случай. Пример: 230 В, 0,5 А, коэффициент мощности 0,6. Это |Z| = 460 Ом: 276 Ом активных и 1,171 Гн, ток отстаёт на 53,1°. Симистор гаснет в нуле тока, а напряжение сети в этот момент — 260 В, и оно скачком ложится на закрытый ключ. Модель (метод Рунге — Кутты, нагрузка плюс снаббер):

Снаббер Пик на ключе Наибольшая скорость Затухание
100 Ом + 100 нФ 534 В 0,75 В/мкс 0,05
470 Ом + 100 нФ 498 В 0,70 В/мкс 0,11
1000 Ом + 100 нФ 459 В 0,65 В/мкс 0,19
100 Ом + 22 нФ 529 В 1,61 В/мкс 0,03

Два вывода. Меньшая ёмкость — быстрее нарастание: 22 нФ дают 1,61 В/мкс против 0,75. И снаббер вместе с индуктивностью нагрузки — колебательный контур: напряжение на ключе перелетает пиковые 325 В до 534 В, больший резистор сильнее гасит колебание и снижает пик до 459 В. Допустимую скорость нарастания и класс напряжения берут из даташита симистора; даташита на диске нет, поэтому здесь — только сравнение вариантов. В модели нет потерь в железе нагрузки, реальный пик ниже, но запас по напряжению закладывают на модельный.


208б.3 Симистор, оптосимистор, твердотельное реле

Симистор по углу: сеть через два резистора по 100 кОм питает оптрон детектора нуля, его выход с подтяжкой 10 кОм к 3,3 В идёт на вход PA6 таймера TIM3, выход PB0 через оптосимистор с резистором 360 Ом открывает симистор с нагрузкой и снаббером; барьер изоляции проходит через оптрон и оптосимистор; внизу — полупериод сети, импульс детектора шириной w вокруг нуля и импульс 200 мкс на угле 90 градусов

Симистор открывается импульсом тока затвора и дальше держится сам, пока ток нагрузки не упадёт ниже тока удержания, — то есть до ближайшего нуля тока. Поэтому на переменном токе он гаснет сам каждый полупериод, а на постоянном не выключится никогда (52.6). Второе условие — ток включения: пока идёт импульс затвора, ток нагрузки должен успеть его набрать, иначе после импульса симистор закроется. Числа обоих токов — из даташита конкретного прибора; на диске его нет, поэтому здесь их нет. Отсюда длина импульса в листинге — 200 мкс. Если открывать в самом нуле, к концу импульса сеть поднимется всего до 20,4 В: нагреватель на 2000 Вт (26,45 Ом) наберёт 0,77 А. Маломощная нагрузка может не набрать ток включения — тогда импульс удлиняют или открывают чуть позже нуля.

Оптосимистор переносит команду через барьер: светодиод на стороне микроконтроллера, маленький симистор на сетевой стороне. Его симистор через резистор подаёт ток в затвор силового. Резистор ограничивает бросок: с 360 Ом при открывании на вершине — 0,90 А пика, и этот бросок оптосимистор должен выдерживать по своему даташиту. Оптосимисторы бывают двух видов:

Нагрев. Падение на открытом симисторе описывают порогом V0 и сопротивлением r: P = V0 · Iср + r · Iд², где Iср — средний ток по модулю, Iд — действующий. Параметры примера — V0 = 1 В, r = 20 мОм; это не даташит, а порядок величины для расчёта:

Режим Iд Iср Потери
нагреватель 2000 Вт, полностью 8,70 А 7,83 А 9,34 Вт
тот же, угол 90° 6,15 А 3,91 А 4,67 Вт

Пик тока — 12,30 А. Упрощённая оценка «1 В × ток» даёт 8,70 Вт — близко, но меньше. Потери — 0,47 % мощности нагрузки: мало для нагрузки, много для корпуса. Радиатор считается от перегрева: при кристалле не выше 125 °C и воздухе 50 °C (тоже параметры примера) на 9,34 Вт допустимо всего 8,03 К/Вт. Минус переход — корпус 1,5 К/Вт и корпус — радиатор 0,5 К/Вт (пример) — радиатору остаётся 6,03 К/Вт. Это уже не пятачок меди на плате, а настоящий радиатор. И проверить по даташиту, соединён ли теплоотвод корпуса с выводом: если да, радиатор под сетью, и на него распространяются зазоры 208.3.

Твердотельное реле — оптосимистор и силовой симистор в одном корпусе, с детектором нуля или без. Какое брать, решает нагрузка. Модель:

Нагрузка Включение в нуле напряжения Включение на вершине
ёмкость 1 мкФ через 10 Ом пик тока 0,102 А пик тока 32,5 А
индуктивность без насыщения пик потока 2,00 от рабочего пик потока 1,00

Для нагревателя и ёмкости — реле с переходом через ноль: нет скачка тока, меньше помех. Для трансформатора и двигателя наоборот: включение в нуле напряжения удваивает поток в сердечнике, сердечник насыщается и берёт бросок тока. Их включают на вершине — реле без детектора нуля с командой по детектору 208б.4. Для фазового управления годится только реле без детектора нуля.


208б.4 Детектор перехода через ноль

Классический детектор — оптрон со встречными светодиодами (54.2), подключённый к сети через два резистора по 100 кОм. Выход с подтяжкой 10 кОм к 3,3 В. Вдали от нуля ток светодиода велик, транзистор открыт, на выходе ноль; около нуля ток мал, транзистор закрывается, на выходе единица. Получается импульс вокруг каждого нуля сети.

Резисторы. Вместе они греются на 0,262 Вт, по 0,131 Вт на каждый, и каждый держит 163 В пика — та же логика двух корпусов, что у разряда X-конденсатора (208.5). Пик тока светодиода — 1,62 мА.

Где фронт. Выход переходит через половину питания, когда ток фототранзистора уже не может удержать подтяжку. Этот порог зависит от коэффициента передачи тока, а он гуляет от 50 до 600 % и стареет вдвое (54.2). Модель по трём значениям — 25 % (минимум 50 %, делённый пополам), 100 % и 600 %:

Коэффициент Порог по сети Фронт раньше нуля Ширина импульса
25 % 133,2 В 1,343 мс = 24,2° 2,69 мс
100 % 34,2 В 0,335 мс = 6,0° 0,67 мс
600 % 6,7 В 0,066 мс = 1,2° 0,13 мс

Фронт — плохой ноль: между экземплярами он плавает на 1,3 мс. Зато импульс симметричен относительно нуля, и середина импульса — ноль сети при любом коэффициенте. Её и берут: ширину w мерят, ноль — через w/2 после фронта. Напряжение сети тоже двигает фронт: при коэффициенте 100 % и сети −10 % фронт уходит раньше на 0,037 мс (+0,67°), при +10 % — позже на 0,031 мс (−0,55°); середина остаётся на месте.

Цена микросекунды на 50 Гц — 0,018°. Фильтр входа таймера (13.10) в самом длинном варианте — 8 отсчётов на fDTS/32 при CKD = ÷4 — задерживает каждый фронт на 14,2 мкс у F103 (72 МГц) и на 6,0 мкс у G474 (170 МГц). Оба фронта задерживаются одинаково, середина не сдвигается. Чего модель не знает — разных времён включения и выключения самого оптрона (54.3): с большой подтяжкой спад и фронт несимметричны, и середина уползает. Это меряют осциллографом на готовой плате.

Вход — захват таймера, а не EXTI. Захват ставит метку времени аппаратно (13.6), и задержка прерывания на неё не влияет. EXTI тоже работает, но тогда в угол попадает всё, что задерживает прерывание.

Резисторный детектор без изоляции — 1 МОм прямо на вывод, и защитный диод вывода ограничивает напряжение. Пик тока в диод — 0,325 мА, порог 1,65 В проходит через 16,1 мкс после нуля — 0,29°. Точно и дёшево, но только если вся плата сама живёт на сети без изоляции, со всеми последствиями 208.7: никакого отладчика. И вывод должен допускать втекающий ток. У F103 PA6 — обычный вывод, даташит разрешает ±5 мА. У G474 PA6 — вывод TT_a, и для таких выводов даташит пишет, что положительный ток впрыска невозможен (−5/0 мА): там нужен внешний диод.


208б.5 Фазовое и пакетное управление

Фаза. Симистор открывают с задержкой t после нуля; угол α = t / 10 мс · 180°. Для активной нагрузки мощность — среднее квадрата напряжения на открытой части полупериода:

P(α) / Pmax = 1 − α/π + sin(2α) / (2π)

Модель сверила формулу с численным интегрированием во всех точках:

Угол Задержка от нуля P / Pmax Действующее напряжение
0° 0 мкс 1,0000 230,0 В
30° 1667 мкс 0,9712 226,7 В
45° 2500 мкс 0,9092 219,3 В
60° 3333 мкс 0,8045 206,3 В
90° 5000 мкс 0,5000 162,6 В
120° 6667 мкс 0,1955 101,7 В
135° 7500 мкс 0,0908 69,3 В
150° 8333 мкс 0,0288 39,1 В
170° 9444 мкс 0,0011 7,7 В
180° 10000 мкс 0 0 В

Зависимость нелинейна: первые 30° почти не убавляют мощность, последние 30° почти ничего не дают. Поэтому в прошивку кладут обратную таблицу — задержку для каждой ступени мощности; модель решила уравнение делением отрезка пополам:

Мощность 10 % 20 % 30 % 40 % 50 % 60 % 70 % 80 % 90 %
угол 133,4° 119,5° 108,6° 99,1° 90,0° 80,9° 71,4° 60,5° 46,6°
задержка, мкс 7411 6637 6036 5504 5000 4496 3964 3363 2589

Цена фазы — гармоники. При 90° ток нагрузки — обрезанная синусоида: первая гармоника 0,593 от полной амплитуды, третья 0,318 (54 % первой), пятая 0,106 (18 %). Каждое открывание — ещё и скачок тока с крутым фронтом. Это помехи в сеть и в эфир (203), поэтому нагреватели фазой не регулируют; фаза нужна там, где пакеты не годятся, — яркость ламп накаливания, скорость коллекторного двигателя.

Две ошибки угла. Если взять за ноль фронт детектора, а не середину, при коэффициенте 25 % задуманные 90° станут 65,8°, и мощность выйдет 0,753 вместо 0,500. Если частота сети на 1 % ниже (49,5 Гц), задержка 5000 мкс, рассчитанная на 50 Гц, даёт 89,1° и мощность 0,5100 вместо 0,5000 — это терпимо, но для точности полупериод мерят по соседним нулям.

Пакеты. Симистор открывают в нуле целыми периодами, мощность задаёт доля включённых. Именно периодами, а не полупериодами: если включать полупериоды через один, все они окажутся одной полярности, и в нагрузке появится постоянная составляющая 103,5 В (Uпик/π), которую сеть и трансформаторы не любят. Целыми периодами — 0 В.

Шаг мощности задаёт окно: 50 периодов (1 с) — 2 %, 100 периодов (2 с) — 1 %. Как раскладывать включённые периоды внутри окна — вопрос пульсаций. Блоком («первые N из 50») — длинные паузы; накопителем (прибавлять уровень к сумме и включать период при переполнении через 100) — ровно:

Уровень Накопитель: включено из 100 Длиннейшая пауза Блок в окне 50: пауза
10 % 10 9 периодов, 180 мс 45 периодов, 900 мс
30 % 30 3 периода, 60 мс 35 периодов, 700 мс
50 % 50 1 период, 20 мс 25 периодов, 500 мс
90 % 90 1 период, 20 мс 5 периодов, 100 мс

Нагревателю пульсации безразличны — у него тепловая инерция; лампа на пакетах мигает. Накопитель в листинге — функция next_delay_us.


208б.6 Код: TIM3 ловит ноль и сам открывает симистор

Идея — не дать процессору участвовать во времени. Вход детектора — PA6, канал 1 TIM3; выход на светодиод оптосимистора — PB0, канал 3. На F103 это их основные функции, на G474 — альтернативная функция AF2 у обоих. Тик — 1 мкс: TIM3 получает 72 МГц на F103 (APB1 36 МГц, при делителе APB1 не 1 таймер тактируется вдвое чаще) и 170 МГц на G474 (APB1 без делителя).

Задержку ограничивает сверху запас до следующего фронта: импульс должен кончиться раньше. При самой широкой ширине 2700 мкс (коэффициент 25 %) наибольшая задержка — 8350 мкс, то есть 150,3° и P/Pmax 0,0280; меньше этого фаза не даёт, 10 % с их 7411 мкс помещаются. Это и проверяет _Static_assert вместе с масками регистров — компилятор не соберёт код, если числа разойдутся.

void triac_init(void)
{
#if defined(STM32F10X_MD)
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;
    RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
    GPIOA->CRL = (GPIOA->CRL & ~(0xFu << 24)) | (0x4u << 24);   /* PA6: вход без подтяжки */
    GPIOB->CRL = (GPIOB->CRL & ~0xFu) | 0xAu;                    /* PB0: альтернативная функция, 2 МГц */
#else
    RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOBEN;
    RCC->APB1ENR1 |= RCC_APB1ENR1_TIM3EN;
    GPIOA->MODER = (GPIOA->MODER & ~(3u << 12)) | (2u << 12);    /* PA6: AF2 — TIM3_CH1 */
    GPIOA->AFR[0] = (GPIOA->AFR[0] & ~(0xFu << 24)) | (2u << 24);
    GPIOB->MODER = (GPIOB->MODER & ~3u) | 2u;                    /* PB0: AF2 — TIM3_CH3 */
    GPIOB->AFR[0] = (GPIOB->AFR[0] & ~0xFu) | 2u;
#endif
    TIM3->PSC = TIM_CLK_HZ / TICK_HZ - 1u;
    TIM3->CR1 = TIM_CR1_CKD_1 | TIM_CR1_ARPE | TIM_CR1_OPM;     /* tDTS = 4 такта; стоп на обновлении */
    TIM3->CCMR1 = TIM_CCMR1_CC1S_0 | TIM_CCMR1_IC1F             /* IC1 ← TI1, фильтр fDTS/32, N = 8 */
                | TIM_CCMR1_CC2S_1;                             /* IC2 ← тот же TI1 */
    TIM3->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_0 | TIM_CCMR2_OC3PE;
    TIM3->CCER = TIM_CCER_CC2E | TIM_CCER_CC2P | TIM_CCER_CC3E;  /* IC2 — по спаду */
    TIM3->SMCR = TIM_SMCR_TS_2 | TIM_SMCR_TS_0 | TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1;
    TIM3->ARR = W_MAX_US + GUARD_US;
    TIM3->CCR3 = 0xFFFFu;                       /* до первого измерения ширины — не открывать */
    TIM3->EGR = TIM_EGR_UG;                     /* теневые ARR и CCR3 ← предзагрузка */
    TIM3->SR = 0u;
    TIM3->DIER = TIM_DIER_CC2IE;
    NVIC_EnableIRQ(TIM3_IRQn);
}

Пока ширина не измерена, CCR3 = 0xFFFF больше ARR, и выход не включается ни разу — симистор закрыт с первого полупериода. Обработчик:

/* спад импульса детектора: CCR2 — его ширина w, ноль был через w/2 после фронта */
void TIM3_IRQHandler(void)
{
    uint32_t w, d, d_max, start, stop;

    if (!(TIM3->SR & TIM_SR_CC2IF))
        return;
    w = TIM3->CCR2;                             /* чтение снимает флаг */
    d = next_delay_us();
    if (d >= HALF_US || w > W_MAX_US) {         /* пропустить: сравнение за пределом ARR */
        TIM3->ARR = W_MAX_US + GUARD_US;
        TIM3->CCR3 = 0xFFFFu;
        return;
    }
    d_max = HALF_US - w / 2u - PULSE_US - GUARD_US;
    if (d > d_max)
        d = d_max;                              /* импульс кончается до следующего фронта */
    start = w / 2u + d + 1u;                    /* CCR3 > 0: иначе импульса не будет */
    stop = start + PULSE_US;
    if (stop < w + GUARD_US)
        stop = w + GUARD_US;                    /* счёт идёт, пока не пойман спад */
    TIM3->CCR3 = start;                         /* обе записи вступят на следующем запуске */
    TIM3->ARR = stop;
}

next_delay_us в фазовом режиме берёт задержку из таблицы 208б.5 (k_delay_us в файле — те же 11 чисел от 10000 до 0), в пакетном — накопитель: первый полупериод пары решает за оба, поэтому включаются только целые периоды. Ширина w берётся из текущего полупериода, а угол применится в следующем — ширина меняется медленно, это честный обмен. Код собран armclang для F103 и G474 с -Wall -Wextra без предупреждений: имена регистров и битов у TIM3 на обоих одинаковы, различается только включение портов.


208б.7 Грабли

Реле отпускает с опозданием, контакты чернеют. Один гасящий диод растягивает спад тока катушки до 0,62 мс, контакты расходятся медленно, дуга дольше. Стабилитрон 24 В последовательно с диодом ускоряет спад в 6,3 раза, но поднимает напряжение на ключе до 37,1 В (208б.1).

Реле в пакетном управлении умерло за выходные. Ресурс 100 000 циклов при периоде 1 с — 27,8 часа. Пакеты — только симистором (208б.1).

Выключенная лампа тлеет. Снаббер 100 Ом + 100 нФ пропускает 7,23 мА при закрытом ключе; маленькой нагрузке этого хватает, чтобы светиться. Уменьшать ёмкость, помня, что 22 нФ поднимают скорость нарастания до 1,61 В/мкс (208б.2).

С индуктивной нагрузкой симистор сам открывается после нуля. Ток отстаёт от напряжения, в нуле тока на ключ скачком ложится 260 В. Без снаббера нарастание ограничено только собственной ёмкостью симистора. Снаббер ограничивает скорость, но с нагрузкой звенит до 534 В — брать симистор с запасом по напряжению (208б.2).

Мощность у двух одинаковых плат разная. За ноль взят фронт детектора, а он плавает с коэффициентом оптрона: при 25 % — на 1,343 мс раньше нуля, 90° превращаются в 65,8°, мощность 0,753 вместо 0,500. Ноль — середина импульса детектора (208б.4).

Фаза не регулируется: нагрузка горит на полную или никак. Оптосимистор или твердотельное реле с детектором нуля открываются только около нуля: команда на 90° будет исполнена в следующем нуле. Для фазы — приборы без детектора нуля (208б.3).

Трансформатор через реле «с нулём» выбивает автомат. Включение в нуле напряжения удваивает поток в сердечнике (2,00 от рабочего против 1,00 на вершине), сердечник насыщается. Для трансформатора — включение на вершине (208б.3).

Пакеты по полупериодам гудят в трансформаторе. Полупериоды через один — одной полярности, постоянная составляющая 103,5 В. Включать целыми периодами (208б.5).

На G474 резисторный детектор портит вход. PA6 у G474 — вывод TT_a, положительный ток впрыска на нём невозможен по даташиту. Внешний диод к питанию или оптронный детектор; у F103 PA6 допускает ±5 мА (208б.4).

Во время стирания Flash симистор ведёт себя странно. Стирание страницы занимает 20…40 мс у F103 и 22,02…24,47 мс у G474 по даташитам — 2…4 полупериода. Чтение того же банка Flash во время стирания останавливает шину (так пишет RM0440), и обработчик прерывания из этого банка ждёт. Таймер при этом продолжает выдавать импульсы по последним CCR3 и ARR: фаза держит прежний угол, а пакет замирает в прошлом решении — включённым или выключенным. Стирать, когда нагрузка выключена, или переносить обработчик в ОЗУ.

Радиатор симистора ударил током. У многих корпусов теплоотвод соединён с выводом, и радиатор оказывается под сетью. Смотреть даташит, ставить изолирующую прокладку или соблюдать зазоры 208.3 (208б.3).


208б.8 Итоги


Полные исходники

load208b.c — симистор по углу: TIM3 ловит детектор нуля и сам выдаёт импульс.

/* 208б: симистор по углу — TIM3 ловит импульс детектора нуля и сам выдаёт
   импульс на оптосимистор в одноимпульсном режиме.
   PA6 — TIM3_CH1, вход от детектора; PB0 — TIM3_CH3, выход на светодиод оптосимистора.
   Собирается armclang: -DSTM32F10X_MD (stm32f10x.h) или -DSTM32G474xx (stm32g4xx.h). */
#include <stdint.h>

#if defined(STM32F10X_MD)
#define TIM_CLK_HZ 72000000u            /* APB1 36 МГц, таймер получает ×2 */
#else
#define TIM_CLK_HZ 170000000u
#endif
#define TICK_HZ    1000000u             /* тик 1 мкс */
#define HALF_US    10000u               /* полупериод сети 50 Гц */
#define PULSE_US   200u                 /* импульс на светодиод оптосимистора */
#define GUARD_US   100u                 /* запас до следующего фронта детектора */
#define W_MAX_US   2700u                /* самый широкий импульс детектора (CTR 25 %) */
#define D10_US     7411u                /* задержка для 10 % мощности */
#define MODE_PHASE 0u
#define MODE_BURST 1u

static const uint16_t k_delay_us[11] = {        /* мощность 0, 10 … 100 % → задержка от нуля */
    HALF_US, D10_US, 6637, 6036, 5504, 5000, 4496, 3964, 3363, 2589, 0};

_Static_assert(TIM_CLK_HZ % TICK_HZ == 0u && TIM_CLK_HZ / TICK_HZ - 1u <= 0xFFFFu, "PSC: целое, 16 бит");
_Static_assert(sizeof k_delay_us / sizeof k_delay_us[0] == 11u, "ступени 0…100 % через 10");
_Static_assert(W_MAX_US + 2u * GUARD_US < HALF_US, "спад детектора ловится до следующего фронта");
_Static_assert(HALF_US - W_MAX_US / 2u - PULSE_US - GUARD_US >= D10_US, "10 % помещается при самом широком импульсе");
_Static_assert((TIM_SMCR_TS_2 | TIM_SMCR_TS_0 | TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1) == 0x56u, "TS=101 TI1FP1, SMS=110 запуск");
_Static_assert((TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_0 | TIM_CCMR2_OC3PE) == 0x78u, "CH3: ШИМ 2, предзагрузка");
_Static_assert((TIM_CCMR1_CC1S_0 | TIM_CCMR1_IC1F | TIM_CCMR1_CC2S_1) == 0x2F1u, "IC1 и IC2 от TI1, фильтр 1111");

static volatile uint8_t g_mode, g_level;        /* режим и мощность 0…100 % */

void triac_init(void)
{
#if defined(STM32F10X_MD)
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;
    RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
    GPIOA->CRL = (GPIOA->CRL & ~(0xFu << 24)) | (0x4u << 24);   /* PA6: вход без подтяжки */
    GPIOB->CRL = (GPIOB->CRL & ~0xFu) | 0xAu;                    /* PB0: альтернативная функция, 2 МГц */
#else
    RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOBEN;
    RCC->APB1ENR1 |= RCC_APB1ENR1_TIM3EN;
    GPIOA->MODER = (GPIOA->MODER & ~(3u << 12)) | (2u << 12);    /* PA6: AF2 — TIM3_CH1 */
    GPIOA->AFR[0] = (GPIOA->AFR[0] & ~(0xFu << 24)) | (2u << 24);
    GPIOB->MODER = (GPIOB->MODER & ~3u) | 2u;                    /* PB0: AF2 — TIM3_CH3 */
    GPIOB->AFR[0] = (GPIOB->AFR[0] & ~0xFu) | 2u;
#endif
    TIM3->PSC = TIM_CLK_HZ / TICK_HZ - 1u;
    TIM3->CR1 = TIM_CR1_CKD_1 | TIM_CR1_ARPE | TIM_CR1_OPM;     /* tDTS = 4 такта; стоп на обновлении */
    TIM3->CCMR1 = TIM_CCMR1_CC1S_0 | TIM_CCMR1_IC1F             /* IC1 ← TI1, фильтр fDTS/32, N = 8 */
                | TIM_CCMR1_CC2S_1;                             /* IC2 ← тот же TI1 */
    TIM3->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_0 | TIM_CCMR2_OC3PE;
    TIM3->CCER = TIM_CCER_CC2E | TIM_CCER_CC2P | TIM_CCER_CC3E;  /* IC2 — по спаду */
    TIM3->SMCR = TIM_SMCR_TS_2 | TIM_SMCR_TS_0 | TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1;
    TIM3->ARR = W_MAX_US + GUARD_US;
    TIM3->CCR3 = 0xFFFFu;                       /* до первого измерения ширины — не открывать */
    TIM3->EGR = TIM_EGR_UG;                     /* теневые ARR и CCR3 ← предзагрузка */
    TIM3->SR = 0u;
    TIM3->DIER = TIM_DIER_CC2IE;
    NVIC_EnableIRQ(TIM3_IRQn);
}

void triac_set(uint8_t mode, uint8_t level)
{
    g_mode = mode;
    g_level = level > 100u ? 100u : level;
}

/* задержка от нуля на следующий полупериод; HALF_US — не открывать */
static uint32_t next_delay_us(void)
{
    static uint8_t half, on;
    static uint8_t acc;

    if (g_mode == MODE_PHASE)
        return k_delay_us[g_level / 10u];
    half ^= 1u;
    if (half) {                                 /* первый полупериод пары решает за оба */
        acc += g_level;
        on = acc >= 100u;
        if (on)
            acc -= 100u;
    }
    return on ? 0u : HALF_US;
}

/* спад импульса детектора: CCR2 — его ширина w, ноль был через w/2 после фронта */
void TIM3_IRQHandler(void)
{
    uint32_t w, d, d_max, start, stop;

    if (!(TIM3->SR & TIM_SR_CC2IF))
        return;
    w = TIM3->CCR2;                             /* чтение снимает флаг */
    d = next_delay_us();
    if (d >= HALF_US || w > W_MAX_US) {         /* пропустить: сравнение за пределом ARR */
        TIM3->ARR = W_MAX_US + GUARD_US;
        TIM3->CCR3 = 0xFFFFu;
        return;
    }
    d_max = HALF_US - w / 2u - PULSE_US - GUARD_US;
    if (d > d_max)
        d = d_max;                              /* импульс кончается до следующего фронта */
    start = w / 2u + d + 1u;                    /* CCR3 > 0: иначе импульса не будет */
    stop = start + PULSE_US;
    if (stop < w + GUARD_US)
        stop = w + GUARD_US;                    /* счёт идёт, пока не пойман спад */
    TIM3->CCR3 = start;                         /* обе записи вступят на следующем запуске */
    TIM3->ARR = stop;
}
Открыть в интерактивном справочнике