Для решения данной задачи можно воспользоваться представленной далее программой или реализовать её в виде функционального блока.
Описание функционального блока:

Внешний вид функционального блока на языке CFC.
Функциональный блок PWM формирует ШИМ сигнал на выходе Output с заданным периодом (вход Period) и определённым значением заполнением (0 … 100 % ) на входе Duty_Cycle. Имеется возможность ограничить скважность минимальным и максимальными значением, который задаются на входы MIN_duty_cycle и MAX_duty_cycle соответственно (по умолчания задан диапазон 10 … 90 %).
Пример использования функционального блока в программе.
Программный код функционального блока:
FUNCTION_BLOCK PWMVAR_INPUT
EN: BOOL; // Вкл/Выкл.
Period: TIME; // Период ШИМ
MIN_duty_cycle: REAL := 0.0; // Минимальное значение коэффициента заполнения
MAX_duty_cycle: REAL := 100.0; // Максимальное значение коэффициента заполнения
Duty_Cycle: REAL; // Коэффициент заполнения, 0.0..100.0
END_VAR
VAR_OUTPUT
Output: BOOL; // Выход ШИМ
// Рекомендуется добавить диагностические выходы:
Error: BOOL; // Флаг ошибки
// ErrorID: WORD; // Код ошибки (опционально)
END_VAR
VAR
uliCurrent_Time: ULINT;
uliStart_Time: ULINT := 0;
uliPuls_Time: ULINT;
uliPeriod_Time: ULINT;
bFirstScan: BOOL := TRUE; // Флаг первого скана для инициализации
END_VAR
VAR CONSTANT
rRange: REAL := 100.0; // Диапазон величины на входе Input
END_VAR
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Сброс ошибки при неактивном блоке
IF NOT EN THEN
Output := FALSE;
Error := FALSE;
bFirstScan := TRUE; // Подготовка к следующему включению
RETURN;
END_IF
// ИНИЦИАЛИЗАЦИЯ И ПРОВЕРКА ВХОДНЫХ ДАННЫХ
// Проверка валидности периода
IF Period <= T#0s THEN
Output := FALSE;
Error := TRUE;
RETURN;
END_IF;
// Проверка валидности диапазона скважности
IF MIN_duty_cycle >= MAX_duty_cycle OR MIN_duty_cycle < 0.0 OR MAX_duty_cycle > 100.0 THEN
Output := FALSE;
Error := TRUE;
RETURN;
END_IF;
// Сбрасываем ошибку, если все проверки пройдены
Error := FALSE;
// ОСНОВНАЯ ЛОГИКА
SysTimeGetUs(uliCurrent_Time);
uliPeriod_Time := TIME_TO_ULINT(Period) * 1000; // Преобразование TIME в микросекунды
// Расчет длительности импульса с ограничением и нормализацией
uliPuls_Time := REAL_TO_ULINT(
LIMIT(MIN_duty_cycle, Duty_Cycle, MAX_duty_cycle)
* ULINT_TO_REAL(uliPeriod_Time) / rRange
);
// Инициализация таймера при первом скане или переполнении периода
IF bFirstScan OR (uliCurrent_Time - uliStart_Time >= uliPeriod_Time) THEN
uliStart_Time := uliCurrent_Time;
bFirstScan := FALSE;
END_IF
// Формирование выходного сигнала
IF (uliCurrent_Time - uliStart_Time < uliPuls_Time) AND (uliPuls_Time > 0) THEN
Output := TRUE;
ELSE
Output := FALSE;
END_IF
Пример программы прилагается.