Два звена. Первое звено задает температуру приточного воздуха в зависимости от температуры вытяжного. Второе звено задает мощность калорифера в зависимости от температуры приточного.
Надеюсь, что выдрал корректно.

объявление переменных:

double IFlowOut; // интегратор
double KdFlowOut; // переменные для ПИДа
double KiFlowOut; // переменные для ПИДа
double DFlowOut; // дифферинциатор
double IFlowIn; // интегратор
double KdFlowIn; // переменные для ПИДа
double KiFlowIn; // переменные для ПИДа
double DFlowIn; // дифферинциатор
double Yk; // нынешний выход
signed short Ek;
unsigned short EkDelta;
signed short Temperature;
signed short OldTemperatureFlowIn;
signed short OldTemperatureFlowOut;
signed long AddFlowOutTemperature; // прибавляемое значение температуры
signed short FlowOutTemperature; // измеренная температура обратного воздуха 0,0625
signed short FlowInTemperature; // измеренная температура притока 0,0625
unsigned short DeltaFlowOut; // зона нечуствительности для температуры вытяжки 0.0-20.0 0,0625
unsigned short DeltaFlowIn; // зона нечуствительности для температуры притока 0.0-20.0 0,0625
unsigned short DeltaHigh; // максимальное превышение температуры 0,0625
// притока относительно заданной температуры 0.0-50.0 0,0625
unsigned short DeltaLow; // макcимальное снижение температуры 0,0625
// притока относительно заданной температуры 0.0-20.0 0,0625
unsigned short TaskFlowIn; // задание Т притока 0,0625



extern void CalcVariable(void) // подсчет новых значенй для ПИДа
{
/*
Функция подсчета значений для ПИДа. Вызывается при любом изменений параметров ПИДа.
Дифференциальное звено D:
D = (Td * Kp * (OldT - T)) / Tk
Интегральное звено I:
I = (Tk * Kp * (Task - T)) / Ti
Пропорцианальное звено K:
K = Kp * (Task - T)
Выход ПИДа:
Y = K + I + D

где T - нынешняя температура
Task - заданная температура
OldT - предыдущая температура
Tk - такт ПИДа
Kp - коэффициент пропорцианальности
Ti - постоянная интегрирования
Td - постоянная дифференцирования
*/
// подсчет значений для пида вытяжки
if (Value.TdFlowOut!=0) KdFlowOut=((double)Value.KpFlowOut*Value.TdFlowOut*0.1)/Value.TkFlowOut;
else KdFlowOut=0.0;
if (Value.TiFlowOut!=0) KiFlowOut=((double)Value.TkFlowOut*Value.KpFlowOut)/Value.TiFlowOut;
else KiFlowOut=0.0;
TkFlowOut=Value.TkFlowOut;
// подсчет для пида притока
if (Value.TdFlowIn!=0) KdFlowIn=((double)Value.KpFlowIn*Value.TdFlowIn*0.1)/Value.TkFlowIn;
else KdFlowIn=0.0;
if (Value.TiFlowIn!=0) KiFlowIn=((double)Value.TkFlowIn*Value.KpFlowIn)/Value.TiFlowIn;
else KiFlowIn=0.0;
SystemFlags.CalcVariable=0; // сбрасываем флаг необходимости подсчета ПИДа
}

extern void TaskFlowInChange(void)
{
unsigned char N;
// задание температуры приточного воздуха в зависимости от разницы температур между вытяжным и заданным
TkFlowOut=Value.TkFlowOut; // востанавливаем время фильтра
// запрещаем прерывания
N=__save_interrupt();
__disable_interrupt();
Temperature=(signed short)(AddFlowOutTemperature/TkFlowOut); // высчитываем среднеарифметическую температуру притока
Ek=TaskFlowOut-Temperature; // вычисляем разницу приточного и заданного
AddFlowOutTemperature=0; // обнуляем накопленую температуру приточного воздуха
// разрешаем прерывания
__restore_interrupt(N);
EkDelta=abs(Ek); // абсолютная разница
if (EkDelta<DeltaFlowOut) Ek=0; // если абсолютная разница меньше величины гистерезиса, то разницу обнуляем
DFlowOut=KdFlowOut*(OldTemperatureFlowOut-Temperature); // высчитываем Д звено
Yk=(double)Ek*Value.KpFlowOut+IFlowOut; // складываем И звено с разницей умноженной на П
if((Yk<MaxTaskFlowIn) & (Yk>0.0)) IFlowOut+=Ek*KiFlowOut; // если подсчитанное значение выхода превышает ограничение,
// то И звено не интегрируем. Ограничение задается в variable.h как MaxTaskFlowIn
Yk=Yk+DFlowOut; // к выходу ПИД прибавляем D звено
if (Yk>MaxTaskFlowIn) Yk=MaxTaskFlowIn; else if (Yk<0.0) Yk=0.0; // выход обрезаем сверху MaxTaskFlowIn, снизу 0
Ek=DeltaHigh+DeltaLow; // в Ek - границы задания
if (Yk!=0.0) Yk=((double)Ek*Yk)/MaxTaskFlowIn; // высчитываем значение заданной температуры в пределах ограниченном DeltaHigh и DeltaLow
if (HighError.Start==1)
{
Yk=TaskFlowOut+(double)DeltaHigh/2; // если запущена настройка ПИДа, то задание высчитываем как половина от DeltaHigh
}
else
{
Yk=Yk+(TaskFlowOut-DeltaLow); // если не запущена настройка ПИДа, то переводим задание из значения подсчитанного Yk
}
TaskFlowIn=(unsigned short)Yk; // запоминаем значение
Value.TaskFlowIn=(unsigned short)Yk/1.6; // запоминаем значение
// обновление переменных
OldTemperatureFlowOut=Temperature;
}

extern void FazaChange(void)
{
// задание мощности калорифера в зависимости от разницы температур между приточным воздухом и заданным

unsigned char N;
unsigned char __flash *AdrFaza;

N=__save_interrupt();
__disable_interrupt();
Temperature=FlowInTemperature;
__restore_interrupt(N);
Ek=TaskFlowIn-Temperature; // в Ек разница температур между заданным значением и измеренным
EkDelta=abs(Ek); // абсолютная разница
if (EkDelta<DeltaFlowIn) Ek=0; // если абсолютная разница меньше величины гистерезиса, то разницу обнуляем
DFlowIn=(KdFlowIn*(OldTemperatureFlowIn-Temperature)); // высчитываем Д звено
Yk=(double)Ek*Value.KpFlowIn+IFlowIn; // складываем И звено с разницей температур умноженной на П
if((Yk<MaxPower) & (Yk>0.0)) IFlowIn+=Ek*KiFlowIn;// если подсчитанное значение выхода превышает ограничение,
// то И звено не интегрируем. Ограничение задается в variable.h как MaxPower
Yk=Yk+DFlowIn; // к выходу ПИД прибавляем D звено
if (Yk>MaxPower) Yk=MaxPower; else if (Yk<0) Yk=0; // выход обрезаем сверху значением MaxPower, снизу 0
Value.FazaValue=(unsigned short)(Yk/100.0); // обновляем значение мощности калорифера в процентах
// в озу формируем новую последовательность ШИМ для А канала
AdrFaza=&TableFazaA[0]+(7*Value.FazaValue);
for (N=0; N<7; N++) FazaA[N]=*(AdrFaza+N);
// в озу формируем новую последовательность ШИМ для В канала
AdrFaza=&TableFazaB[0]+(7*Value.FazaValue);
for (N=0; N<7; N++) FazaB[N]=*(AdrFaza+N);
// обновление переменных
OldTemperatureFlowIn=Temperature;
}