Программист / сисадмин
0,3
рейтинг
18 июня 2012 в 06:26

Разработка → ПИД-регулятор своими руками tutorial

I. Постановка задачи


Нужно держать температуру на заданном неком уровне и менять задание. Есть микроконтроллер, к которому прицеплены измеритель температуры, и симистор для управления мощностью. Не будем греть голову на ТАУ, ни разностными схемами, просто возьмём и сделаем «в лоб» ПИД-регулятор.

II. Теоретическая вводная


Как получается ПИД-регулятор? Берём разницу между текущей температурой и нужной, умножаем на настраиваемый коэффициент, получаем мощность, которую надо выдать в данный момент. Это пропорциональная составляющая, она работает в момент появления рассогласования — то есть моментально откликается как на изменение уставки, так и на поведение объекта. Начал подогреваться? Мощность начинает спадать. Перегрелся? Выключилось, или даже дали сигнал охлаждения. Всё хорошо, вот только в реальной жизни эффект от воздействия проявляется с запаздыванием, а на объект воздействуем не только мы, но еще и окружающая среда: разогретый реактор не только внутри горячий, но еще и остывает, отдавая тепло комнате, а потому как только выключаем мощность, он сразу начинает остывать. Поэтому чистый пропорциональный регулятор колеблется вокруг точки поддержания, и тем сильнее колеблется, чем выше воздействие окружающей среды / содержимого реактора.

Чтобы компенсировать «внешние» воздействия на реактор, в цепь добавляют интегральную составляющую. Всё рассогласование, которое было в системе, идёт на интегратор (соответственно, как только мы перегрели — сумма уменьшается, пока недогрето — сумма увеличивается). И накопленный интеграл, со своим коэффициентом, даёт свою прибавку-убавку к мощности. В результате такого подхода, при стационарном процессе, через некоторое время интеграл подбирает такой вклад в сумму с мощностью, который компенсирует потери окружающей среды, и колебания исчезают — интеграл становится стабильным, поэтому величина выдаваемой мощности становится постоянной. Причем так как при этом держится нужная температура, рассогласование отсутствует, пропорциональная составляющая не работает вообще.

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

Итак, с физическим смыслом разобрались, перейдём к основым вопросам реализации.

III. Кому пользоваться регулятором?


— Техникам.

Что из этого следует? Из этого следует, что техники понимают физическую составляющую, и имеют опыт настройки аппаратных пид регуляторов. А значит, программная реализация должна исходить из удобства настройки техниками — повторяя физическую модель. И это крайне важно! Очень часто в угоду упрощения кода коэффициенты меняют, например, на обратные — чтобы избавиться от деления. В результате, настройка превращается в ад и кошмар, и требуется опыт настройки данного конкретного регулятора, вместо понимания процесса. Отсюда получаем, что наши коэффициенты — постоянная интегрирования и постоянная дифференцирования — должны иметь размерность времени, то есть задаваться в секундах, а никак не в «1/с», как это любят делать.

IV. Область функционирования.


Мы пытаемся сделать универсальный регулятор, а значит, он должен работать как на мелких быстрых объектах, так и на мощных большущих печах. Значит, следует исходить из того, что регулируемая температура ограничена в общем-то измерителем. Наиболее часто используемые — ХА(K) и ХК(L). Их область применимости — где-то до 1200°C. Охлаждение требует более сложного оборудования (криостаты), управление доп.охлаждением (вентиляторы и открываемые дверки термошкафов) также требуется редко — значит, пока исключаем из рассмотрения. Получаем, что управляемая температура от ~15°C до ~1200°C, управляется только подача мощности.

Точность управления определяется во-1х точностью измерения: градуировочные таблицы даны через 0.1 градуса; линейность внутри таблиц в принципе достойная, поэтому точность ограничена в первую очередь усилителем и измерителем тока. В моём случае, хотелось добиться точности поддержания 0.1 градуса, поэтому измеритель настроен на 1/32 градуса: это даёт ~3 кванта на 0.1 градуса, таким образом, имея нормальный «шум» регулирования +-1 квант мы остаёмся в пределах всё тех же 0.1 градуса. Использование 1/32 позволяет работать с фиксированной точкой — 5 бит = дробная часть, остальное — целая. В 16 бит это получается представить от 0 до 2047 °. Вместо работы с отрицательными числами, мы будем работать в кельвинах вместо цельсиев, таким образом — представляется от 0 до 2047 °K, что эквивалентно от -273 до 1775 °C; с шагом в 0,03125 °.

V. Диапазон настраиваемости.


Для управления микрореактором с мощной силовой установкой может оказаться что для нагрева на 10 градусов достаточно 1% мощности, в то время как для большой инертной печи для того чтобы подогреть на градус едва-едва хватает 100% мощности подогрева. (В реальной жизни, это выглядит так — есть несколько подогревателей с ручным управлением — они включаются отдельным рубильником и производят начальный нагрев, в дальнейшем поддержание рабочей точки обеспечивает терморегулятор, управляя еще одним подогревателем, который на полной мощности выдаёт максимум +10°C к тому, что нагрели постоянно включенные). Исходя из этого, предельным коэффициентом пропорциональности логично предположить 100% мощности на 1 градус. Больше не имеет смысла, так как мы хотим получить управляемость в 0.1 градуса. Минимальный, для простоты, я взял инверсным — 1% мощности на 100 градусов.

Диапазоны временных коэффициентов вычисляются просто исходя из наших условий работы регулятора. Так как мы управляем через мощностью симистор путём вычисления задержки момента включения после прохождения через 0, предельная частота работы регулятора — 50Гц. Если мы уверены, что управляем мощностью которой пофиг плюс или минус, мы можем работать на 100Гц, но это не всегда так, и потому лучше каждый раз дозировать равное количество как положительной так и отрицательной полуволны. Для упрощения жизни, я снизил время работы до 25Гц, тем самым любое вычисленное воздействие будет действовать в течение 4 полуволн, и за это время у меня будет возможность рассчитать новое воздействие.

Таким образом, постоянные времени задаются через 1/25 сек, от 0 до ~2000 сек (2000*25 = 50000, как раз в 16бит влазит).

Ну и еще у нас есть ограничение мощности минимальное и максимальное, от 0 до 100%.

VI. Управление мощностью.


Начиная с этого момента все теоретические выкладки заканчиваются, начинается горькая практика, привязанная к конкретной реализации.

Итак, мы уже решили что управляем задержкой открывания симистора после прохождения через 0. Таким образом, задержка в 0 означает 100% мощность, бесконечная задержка = 0% мощности.

Вопрос: с какой точностью мы можем управлять мощностью? Вообще, с точностью отсчета времени нашего таймера. С другой стороны, какая нужна мощность? Мы вычисляем какой % мощности нужно подать на 0.04сек. В принципе, по опыту, управления мощностью даже с точностью в 1% на частоте в 0.1сек хватает для поддержания температуры в 1 градус. У нас управление 0.04сек (в 2.5раза быстрее). Поэтому было принято решение рассчитать таблицу мощности через 1/250 от максимума (с шагом в 0.4%). Это позволяет таблицу иметь не сильно большую (500 байт), и при этом иметь точность выше 1%. Если ваш случай требует бОльшей точности — пересчитать не так сложно.

Теперь поговорим о расчете этой самой таблицы. Во-1х следует учесть, что есть момент срабатывания сигнала прохождения через ноль. В моем случае — 12В. То есть когда входное напряжение упадёт ниже 12В, я получу сигнал прохождения через 0.

Это означает, что для 100% мощности время запуска = времени прохождения 12В.
Решим систему уравнений
; IntMoment := 12V
; Max := sqr(220*sqrt(2))
; { Sqr( Sin(Pi/2)*K ) = Max
; { Sqr( Sin(X)*K ) = IntMoment
;
; 2*k/MaxCode = 1 - cos(T*Pi)
;   cos(T*Pi) = 1-2*k/MaxCode
;   T*Pi = arccos(1-2*k/MaxCode)
;   T = arccos(1-2*k/MaxCode) / Pi


Процессор у меня работает на частоте 32786, PLL настроен на 384/2, полуволна имеет 100Гц, откуда получаем, что код для загрузки константы в таймер для времени T имеет вид:

65536-(T*(32768*384/2)/100.0 + 773)

Нам нужно рассчитать время задержки, дающее равномерное увеличение площади включенной части синусоиды. То есть нам нужно иметь отсчеты времени, дающие равномерное увеличение мощности. Полная мощность, которую мы выдаём — это интеграл по всей синусоиде. [кто знает, как на хабре формулы вставлять? никак? пишу в maple-нотации тогда].

Max = int(sqr(sin(x)), x=0..Pi)
int(sqr(sin(x)), x=0..T*Pi) = x/2 - sin(2*x)/4 + C | 0..T*PI = (T*Pi)/2 - sin(2*T*Pi)/4
(T*Pi)/2 - sin(2*T*Pi)/4 = Q*Pi/2


Таким образом, нам нужно пройтись по всем Q с заданной точностью, и для каждой из них найти T.

Я для себя это решил вот таким тупым способом:
Генератор на перле
#!/usr/bin/perl
# (T*Pi)/2 - sin(2*T*Pi)/4 = Q*Pi/2
use constant PI    => 4 * atan2(1, 1);

$T = 1;
for( $i = 250; $i >= 0; $i-- ) {
        $int = $i*PI/2/250;
        $ev = ($T*PI)/2-sin(2*$T*PI)/4;
        while( abs($ev-$int) > 0.0005 ) {
                $T -= 0.0001;
                $ev = ($T*PI)/2-sin(2*$T*PI)/4;
        }
        #print $i."\t".$T."\n";
        $code = 65536-($T*(32768*384/2)/100.0 + 773);
        printf "DB 0%02Xh, 0%02Xh ; %04Xh = $i/250 of power\n", $code%256, int($code/256), $code, $i;
}


Всё, на выходе мы получили табличку в 250 значений, соответствующих константам загрузки таймера до момента поджига после получения сигнала о прохождении через 0 (точнее, через 12В, как я говорил выше).

VII. Измерение входных данных


Я пропускаю этот вопрос, потому как он достоен отдельной большой статьи. О том, как я решал вопрос с термосопротивлением, можно найти в архиве почившего в бозе моего блога.

Главное что нам надо знать, это что мы измеряем данные с нужной нам частотой (в данном случае — 25Гц), и нужной точностью (на выходе — число от 0 до 2048 градусов кельвина через 1/32 градуса). Данные предполагаются уже нормализованные для всех дальнейших расчетов.

Если будет кому интересно — пишите в комментах, распишу в следующий раз как это делается для термопар.

VIII. Вычисление воздействия


И вот свершилось: у нас есть все данные для того, чтобы наконец-то произвести то, ради чего мы всё затевали: вычислить какую же мощность следует подать на управляющий элемент.

Вспомним еще раз формулу ПИД регулятора:

U = K * ( Err + (1/Ti)*Int + Td*dErr )

  • U — мощность, которую следует выдать;
  • K — пропорциональный коэффициент (обратите внимание — вынесен за скобки, почему — чуть ниже опишу);
  • Ti — постоянная времени интегрирования. Обратите внимание — в расчетах используется обратная величина;
  • Td — постоянная времени дифференцирования
  • Err — текущее рассогласование (разница между уставкой и измеренной температурой
  • dErr — производная рассогласования (разница между текущей и прошлой ошибкой)
  • Int — накопленный интеграл рассогласования (сумма всех Err'ов, кои мы видели)


Мы снова пришли к вопросу, который поднимался в разделе III: этим будут пользоваться техники. Поэтомоу крайне важно не допустить классической ошибки всех реализаций — «размерности коэффициентов как получится». Мы делаем прибор для управления физическим процессом, а значит, модель должна соответствовать.

Произведём вывод всех размерностей. Частично забегая вперёд я уже описал в IV, но теперь раскроем подробнее:
  • U — имеет величину в % мощности. Еще точнее — в 2/5 от % мощности, так как у нас таблица идёт через 1/250 от 100%.
  • Err — рассогласование, задаётся в градусах. Точнее — через 1/32 градуса.
  • Int — интеграл, представляет собой сумму градусов во времени — а значит, имеет размерность градус*сек. Точнее — (1/32 градуса)*(1/25 сек)
  • Ti — задаётся через 1/25 сек
  • (1/Ti)*Int — после вычисления даёт вклад, имеющий размерность (1/32 градуса).
  • dErr — производная, имеет размерность градус/сек, а точнее (1/32 градуса)/(1/25 сек)
  • Td — задаётся через 1/25 сек
  • Td*dErr — после произведения приводит вклад к размерности (1/32 градуса)
  • (...) — итак, все слагаемые под скобками приведены к размерности (1/32 градуса)
  • K — согласует U и (...), а значит имеет размерность процента-на-градус, точнее (2/5)%/(1/32 градуса)


Вот теперь хорошо видно, зачем выносится за скобки пропорциональный коэффициент — это позволяет оставить диф и инт коэффициенты просто постоянными времени, в результате оператор при настройке оперирует простыми и понятными числами — процентом на градус для пропорциональной и секундами для интегральной и дифференциальной коэффициентами.

А благодаря удобному подбору положения точек и размерностей времени, как мы сейчас увидим, все расчеты производятся практически «в лоб».

Кроме одного — у нас есть величина Ti, а для расчета требуется 1/Ti. Операция деления большой разрядности — очень дорогая. Операция умножения в разы дешевле, поэтому воспользуемся отличной статьёй Division by Invariant Integers using Multiplication. У нас ведь K / Ti / Td меняются крайне редко, а потому мы можем себе позволить как угодно извращаться с ними после их изменения, главное чтобы основной цикл расчетов работал быстро.

Таким образом, вместо Ti для расчетов мы раскладываем в набор Ti_m, Ti_sh1, Ti_sh2; и на каждом цикле производим вычисление:
T1 = MULUH(Ti_m, Int)
Q = SHR(T1+SHR(Int-T1, Ti_sh1), Ti_sh2)


Теперь производим расчет баланса разрядности. Для этого распишем полную формулу пошагово:
  1. Eo = E; Нам нужна прошла ошибка. Ошибки — по 16бит
  2. E = Y-X; Вычисляем новое рассогласование. 16bit
  3. Int = Int + (E+Eo)/2; Интегрируем ошибку. При этом считаем полусумму разности (разностная схема). 32bit = 32bit + 16bit
  4. cI = Int * (1/Ti); Считаем интегральный вклад — 32bit * 32bit => 32bit
  5. cD = Td * (E-Eo); Считаем диф вклад — 16*16 => 32bit
  6. PID = E + cI + cD; Подскобочное; 16+32+32 => 32bit
  7. U = K*PID/256; Коэфф; 32*16/8 bit => 40bit.


При всех расчетах положение точки вплоть до 7го шага остаётся на 5м справа месте. В последний момент происходит интересный финт ушами. K задаётся через 1/256, соответственно, после умножения точка сдвигается влево до 5+8=13 места, поэтому мы должны у результата отбросить младшие 8 бит. И самый нижний байт результата — нужная нам мощность через 2/5%. Это — еще одна причина, по которой мощность выровнена по шагам в 1/250 — это позволяет результат уложить в один байт и получить легко по таблице нужный результат.

Дальше, помним, что нас интересует мощность только от 0 до 250 — поэтому 7й шаг вычислений идёт очень просто, как только мы получаем отрицательное число — сразу складываем uMin. Как только выяснили что любой старший байт не ноль — сразу складываем uMax. И только если мощность складывается в диапазоне — производим проверку на меньше uMin или больше uMax.

Если вдруг кому интересно:
полная портянка расчетов
; PID управление
CalcMainEnd:
	; Вычисления, Go-Go.
CalcPid:
	;	1. Eo = E				 | 16bit
Pid1:
	MOV Err0H, ErrH
	MOV Err0L, ErrL
	;	2. E = Y-X				 | 16bit
Pid2:
	CLR C
	MOV A, SettingL
	SUBB A, ThermoL
	MOV ErrL, A
	MOV A, SettingH
	SUBB A, ThermoH
	MOV ErrH, A
	JNB  OV, Pid2Ov
	JB	ACC.7, Pid2Max
Pid2Min:
	MOV ErrL, #LOW(-500*32)
	MOV ErrH, #HIGH(-500*32)
	SJMP Pid2End
Pid2Max:
	MOV ErrL, #LOW(500*32)
	MOV ErrH, #HIGH(500*32)
	SJMP Pid2End
Pid2Ov:
	JNB ACC.7, Pid2OvP
Pid2OvN: ; Проверим на ограничение вниз
	CLR C
	MOV A, ErrL
	SUBB A, #LOW(-500*32)
	MOV A, ErrH
	SUBB A, #HIGH(-500*32)
	JNC Pid2End ; Если > -500 => всё ок
	SJMP Pid2Min
Pid2OvP:
	CLR C
	MOV A, ErrL
	SUBB A, #LOW(500*32)
	MOV A, ErrH
	SUBB A, #HIGH(500*32)
	JNC Pid2Max ; Если < 500 => всё ок
Pid2End:

	;	3. Int = Int + (E+Eo)/2  | 32bit+16bit
Pid3:
	JNB PowerReady, Pid3End ; Если нет сети -- интегральную часть не копим
	MOV A, ErrL
	ADD A, Err0L
	MOV R0, A ; временно
	MOV A, ErrH
	ADDC A, Err0H
	MOV C, ACC.7 ; Полусумма всегда влезает в 16 бит, поэтому при сдвиге надо сохранить знак
	RRC A	; Поделим без потери знака
	XCH A, R0 ; A= младшая часть, R0 - старшая часть полусуммы
	RRC A	; Доделили
	JNB  IntS, Pid3IntPos
	; Int отрицательный, изменим знак для R0:A, тем самым можно будет просто сложить с Int
	CLR C
	CPL A
	ADD A, #1
	XCH A, R0
	CPL A
	ADDC A, #0
	XCH A, R0
Pid3IntPos:
	; У Int и R0:A сейчас согласованы знаки, поэтому складываем обычным образом
	ADD A, IntLL
	MOV IntLL, A
	MOV A, IntLH
	ADDC A, R0
	MOV IntLH, A
	MOV A, R0
	JB	ACC.7, Pid3Neg ; Прибавляли отрицательную разность?
	; Если разность положительная, просто распространим перенос
	JNC jPid3End  ; Если прибавили слово и переноса небыло -- делать нам ничего не требуется.
	INC IntHL ; Распространяем перенос выше
	MOV A, IntHL
	JNZ Pid3End  ; Если перенос не ушел в 4й байт -- всё нормально
	INC IntHH ; Распространяем перенос на САМЫЙ старший байт
	MOV A, IntHH
	JNZ Pid3End  ; Если перенос не ушел еще выше -- всё нормально
	MOV IntHH, #0FFh ; Если перенс был выше -- ограничиваем интеграл потолком
	MOV IntHL, #0FFh
	MOV IntLH, #0FFh
	MOV IntLL, #0FFh
jPid3End:
	SJMP Pid3End
Pid3Neg: ; Если разность отрицательная, то надо продолжать добавлять оба раза, но FFh
	MOV A, IntHL
	ADDC A, #0FFh
	MOV IntHL, A
	MOV A, IntHH
	ADDC A, #0FFh
	MOV IntHH, A
	JC	Pid3End  ; Если тут был перенос, значит знак интеграла не изменился
	CPL IntS ; Если переноса небыло, значит у интеграла изменился знак
	CPL C		 ; Обратим знак получившегося числа
	MOV A, #0
	SUBB A, IntLL
	MOV IntLL, A
	MOV A, #0
	SUBB A, IntLH
	MOV IntLH, A
	MOV A, #0
	SUBB A, IntHL
	MOV IntHL, A
	MOV A, #0
	SUBB A, IntHH
	MOV IntHH, A
	; так как оно стало отрицательным -- то перенос тут будет всегда
Pid3End:

	;	5. cI = Int*(1/Ti)		 | 32*32=>32bit
Pid5: ; R3:R2:R1:R0 = Int*(1/Ti)
	JB Ti_sh1, Pid5Calc ; если Ti_sh1=0, то 1/Ti=1 или Ti=0. и ничего делать не надо
	MOV A, Ti_mLL
	ORL A, Ti_mLH
	ORL A, Ti_mHL
	ORL A, Ti_mHH
	JZ	Pid5Zero
	MOV R0, IntLL
	MOV R1, IntLH
	MOV R2, IntHL
	MOV R3, IntHH
	AJMP Pid5End
Pid5Zero:
	MOV A, #0
	MOV R0, A
	MOV R1, A
	MOV R2, A
	MOV R3, A
	MOV IntLL, A
	MOV IntLH, A
	MOV IntHL, A
	MOV IntHH, A
	AJMP Pid5End
Pid5Calc:
	; R7:R6:R5:R4[:R3] = MULUH(Int*Ti_m) // R3 считаем как часть для округления
	MOV R2, #0
	;; R7:R6 = IntHH*Ti_mHH
	MOV A, IntHH
	MOV B, Ti_mHH
	MUL AB
	MOV R7, B
	MOV R6, A
	; R6:R5 += IntHL*Ti_mHH
	MOV A, IntHL
	MOV B, Ti_mHH
	MUL AB
	MOV R5, A
	MOV A, R6
	ADD A, B
	MOV R6, A
	MOV A, R2 ; A=0
	ADDC A, R7
	MOV R7, A
	; R5:R4 += IntLH*Ti_mHH
	MOV A, IntLH
	MOV B, Ti_mHH
	MUL AB
	MOV R4, A
	MOV A, R5
	ADD A, B
	MOV R5, A
	MOV A, R2 ; A=0
	ADDC A, R6
	MOV R6, A
	MOV A, R2 ; A=0
	ADDC A, R7
	MOV R7, A
	; R4:R3 += IntLL*Ti_mHH
	MOV A, IntLL
	MOV B, Ti_mHH
	MUL AB
	MOV R3, A
	MOV A, R4
	ADD A, B
	MOV R4, A
	MOV A, R2 ; A=0
	ADDC A, R5
	MOV R5, A
	MOV A, R2 ; A=0
	ADDC A, R6
	MOV R6, A
	MOV A, R2 ; A=0
	ADDC A, R7
	MOV R7, A
	;; R6:R5 += IntHH*Ti_mHL
	MOV A, IntHH
	MOV B, Ti_mHL
	MUL AB
	ADD A, R5
	MOV R5, A
	MOV A, R6
	ADDC A, B
	MOV R6, A
	MOV A, R2 ; A=0
	ADDC A, R7
	MOV R7, A
	; R5:R4 += IntHL*Ti_mHL
	MOV A, IntHL
	MOV B, Ti_mHL
	MUL AB
	ADD A, R4
	MOV R4, A
	MOV A, R5
	ADDC A, B
	MOV R5, A
	MOV A, R2 ; A=0
	ADDC A, R6
	MOV R6, A
	MOV A, R2 ; A=0
	ADDC A, R7
	MOV R7, A
	; R4:R3 += IntLH*Ti_mHL
	MOV A, IntLH
	MOV B, Ti_mHL
	MUL AB
	MOV A, R3
	MOV R3, A
	MOV A, R4
	ADDC A, B
	MOV R4, A
	MOV A, R2 ; A=0
	ADDC A, R5
	MOV R5, A
	MOV A, R2 ; A=0
	ADDC A, R6
	MOV R6, A
	MOV A, R2 ; A=0
	ADDC A, R7
	MOV R7, A
	;; R5:R4 += IntHH*Ti_mLH
	MOV A, IntHH
	MOV B, Ti_mLH
	MUL AB
	ADD A, R4
	MOV R4, A
	MOV A, R5
	ADDC A, B
	MOV R5, A
	MOV A, R2 ; A=0
	ADDC A, R6
	MOV R6, A
	MOV A, R2 ; A=0
	ADDC A, R7
	MOV R7, A
	; R4:R3 += IntHL*Ti_mLH
	MOV A, IntHL
	MOV B, Ti_mLH
	MUL AB
	ADD A, R3
	MOV R3, A
	MOV A, R4
	ADDC A, B
	MOV R4, A
	MOV A, R2 ; A=0
	ADDC A, R5
	MOV R5, A
	MOV A, R2 ; A=0
	ADDC A, R6
	MOV R6, A
	MOV A, R2 ; A=0
	ADDC A, R7
	MOV R7, A
	;; R4:R3 += IntHH*Ti_mLL
	MOV A, IntHH
	MOV B, Ti_mLL
	MUL AB
	ADD A, R3
	MOV R3, A
	MOV A, R4
	ADDC A, B
	MOV R4, A
	MOV A, R2 ; A=0
	ADDC A, R5
	MOV R5, A
	MOV A, R2 ; A=0
	ADDC A, R6
	MOV R6, A
	MOV A, R2 ; A=0
	ADDC A, R7
	MOV R7, A
	;;; Если R3 > 7F --
	MOV A, R3
	JNB ACC.7, Pid5Shift ; Если R3<80 -- округление не надо
	ANL A, #7Fh
	JZ	Pid5Round ; Если = 80 -- округляем до нечетного
	MOV A, #1
	ADD A, R4
	MOV R4, A
	MOV A, R2 ; A=0
	ADDC A, R5
	MOV R5, A
	MOV A, R2 ; A=0
	ADDC A, R6
	MOV R6, A
	MOV A, R2 ; A=0
	ADDC A, R7
	MOV R7, A
	SJMP Pid5Shift
Pid5Round:
	MOV A, R4
	ORL A, #01h
	MOV R4, A
	;JMP Pid5Shift

Pid5Shift:
	; R3:R2:R1:R0 = (Int-R7:R6:R5:R4) >> 1
	CLR C
	MOV A, IntLL
	SUBB A, R4
	MOV R0, A
	MOV A, IntLH
	SUBB A, R5
	MOV R1, A
	MOV A, IntHL
	SUBB A, R6
	MOV R2, A
	MOV A, IntHH
	SUBB A, R7
	RRC A ; >>1 без потери переноса
	MOV R3, A
	MOV A, R2
	RRC A
	MOV R2, A
	MOV A, R1
	RRC A
	MOV R1, A
	MOV A, R0
	RRC A
	;MOV R0, A
	; R3:R2:R1:R0 += R7:R6:R5:R4
	;MOV A, R0
	ADD A, R4
	MOV R0, A
	MOV A, R1
	ADDC A, R5
	MOV R1, A
	MOV A, R2
	ADDC A, R6
	MOV R2, A
	MOV A, R3
	ADDC A, R7
	MOV R7, A
	; Теперь сдвинуть вправо на sh2.
	; sh2 может быть до 16 (так как у нас Ti 16разрядный; проверим необходимость сдвига на 16 бит)
	MOV A, Ti_sh2
	JNB ACC.4, Pid5ShiftUnder16
	; Надо сдвинуть >=16 -- 2 байта сдвинем mov'ами
	MOV R0, 18h+2; R2, bank 3
	MOV R1, 18h+3; R3, bank 3
	MOV R2, #0
	MOV R3, #0
Pid5ShiftUnder16:
	JNB ACC.3, Pid5ShiftUnder8
	; Надо сдвинуть на >=8 -- 1 байт сдвигаем mov'ами
	MOV R0, 18h+1; R1, bank 3
	MOV R1, 18h+2; R2, bank 3
	MOV R2, 18h+3; R3, bank 3
	MOV R3, #0
Pid5ShiftUnder8:
	ANL A, #07h
	JZ Pid5End ; Если внутри байта двигать не надо -- всё
	MOV R4, A
	SJMP Pid5ShiftRight
Pid5NextShift:
	CLR C
	; К этому моменту C у нас еще возможнозначимый старший бит!
Pid5ShiftRight:
	MOV A, R3
	RRC A
	MOV R3, A
	MOV A, R2
	RRC A
	MOV R2, A
	MOV A, R1
	RRC A
	MOV R1, A
	MOV A, R0
	RRC A
	MOV R0, A
	DJNZ R4, Pid5NextShift
	; Всё, после всех сдвигов получили результат
	; Не забываем, что у вычисленного в R3:R2:R1:R0
	; сейчас число положительное, а знак его в IntS
Pid5End:

	;	4. PID += [ cD = Td * (E-Eo) ]		| 16*16=>32bit
Pid4: ; cD = R7:R6:R5:R4; ErrD = E-Eo
	CLR C
	MOV A, ErrL
	SUBB A, Err0L
	MOV DiffL, A
	MOV A, ErrH
	SUBB A, Err0H
	MOV DiffH, A
	MOV C, ACC.7 ; Берём знак результата
	MOV DiffS, C ; Сохраним знак E-Eo
	JNC Pid4Mul
	; Diff -- орицательный, обратим знак
	MOV A, DiffL
	CPL A
	ADD A, #1
	MOV DiffL, A
	MOV A, DiffH
	CPL A
	ADDC A, #0
	MOV DiffH, A
Pid4Mul:
	; R7:R6 = DiffH*TdH
	; MOV A, DiffH = в любом случае A=DiffH
	MOV B, TdH
	MUL AB
	MOV R6, A
	MOV R7, B
	; R5:R4 = DiffL*TdL
	MOV A, DiffL
	MOV B, TdL
	MUL AB
	MOV R4, A
	MOV R5, B
	; R6:R5 += DiffH*TdL
	MOV A, DiffH
	MOV B, TdL
	MUL AB
	ADD A, R5
	MOV R5, A
	MOV A, R6
	ADD A, B
	MOV R6, A
	MOV A, R7
	ADDC A, #0
	MOV R7, A
	; R6:R5 += DiffL*TdH
	MOV A, DiffL
	MOV B, TdH
	MUL AB
	ADD A, R5
	MOV R5, A
	MOV A, R6
	ADD A, B
	MOV R6, A
	MOV A, R7
	ADDC A, #0
	MOV R7, A

	;	6. PID = E + cI + cD	 | 32bit
Pid6:	; R3:R2:R1:R0 равно cI, знак в IntS;
	; R7:R6:R5:R4 = cD; знак в DiffS
	; E в обратном дополнительном коде

	JB IntS, ChkDiffN
	JNB DiffS, Pid6Add ; Int>0, Diff>0 => Add
	SJMP Pid6Sub ; Int>0, Diff<0 => Sub
ChkDiffN:
	JNB DiffS, Pid6Sub ; Int<0, Diff>0 => Sub
	; Int<0, Diff<0 => Add
Pid6Add:
	; Одинаковый знак => складываем их с проверкой на переполнение
	MOV A, R0
	ADD A, R4
	MOV R0, A
	MOV A, R1
	ADDC A, R5
	MOV R1, A
	MOV A, R2
	ADDC A, R6
	MOV R2, A
	MOV A, R3
	ADDC A, R7
	MOV R3, A
	JNC Pid6Err ; Если нет переноса - в результате сложения переполнения небыло
	MOV R3, #0FFh
	MOV R2, #0FFh
	MOV R1, #0FFh
	MOV R0, #0FFh
	SJMP Pid6Err
Pid6Sub:
	; Знаки разные -- вычтем одно из другого и проверим знак результата
	CLR C
	MOV A, R4
	SUBB A, R0
	MOV R0, A
	MOV A, R5
	SUBB A, R1
	MOV R1, A
	MOV A, R6
	SUBB A, R2
	MOV R2, A
	MOV A, R7
	SUBB A, R3
	MOV R3, A
	JNC Pid6Err ; Если нет заимствования -- знак результата равен знаку DiffS
	CPL DiffS ; Если заимствование было, у DiffS и результата надо обратить знак
	MOV R6, #0 ; R6=0
	MOV A, R0
	CPL A
	ADDC A, R6 ; R6=0, C=1 => действие +1
	MOV R0, A
	MOV A, R1
	CPL A
	ADDC A, R6 ; +перенос
	MOV R1, A
	MOV A, R2
	CPL A
	ADDC A, R6
	MOV R2, A
	MOV A, R3
	CPL A
	ADDC A, R6
	MOV R3, A

Pid6Err:
	MOV R6, #0 ; R6=0
	; В R3:R2:R1:R0 -- лежит cI+cD; знак суммы в DiffS
	; надо прибавить/отнять Err, записанное в обратном коде
	; Приведём знак Err к DiffS
	MOV R4, ErrL
	MOV A, ErrH
	JB ACC.7, Pid6ChkDiffS
	JNB DiffS, Pid6SumErrNoInv ; Err>0, Diff>0 => NoInv
	SJMP Pid6SumErrInv
Pid6ChkDiffS:
	JNB DiffS, Pid6SumErrNoInv ; Err<0, Diff>0 => NoInv
Pid6SumErrInv:
	; У Err знак отличается от DiffS -- инвертируем
	SETB C ; Не уверен в состоянии C
	MOV A, ErrL
	CPL A
	ADDC A, R6 ; A+=R6+C, R6=0	C=1 => A+=1
	MOV R4, A ; R4=ErrL
	MOV A, ErrH
	CPL A
	ADDC A, R6
Pid6SumErrNoInv:
	MOV R5, A ; ErrH
Pid6SumErr:
	; Итак, в R5:R4 лежит Err, знак которого согласован с DiffS; но в обратно-дополнительном коде
	MOV A, R0
	ADD A, R4
	MOV R0, A
	MOV A, R5
	CLR F0
	JNB ACC.7, Pid6SubErrPos
	SETB F0
	MOV R6, #0FFh ; Добавляем отрицательное => дополняем FFами
Pid6SubErrPos:
	ADDC A, R1
	MOV R1, A
	MOV A, R2
	ADDC A, R6 ; +расширение
	MOV R2, A
	MOV A, R3
	ADDC A, R6 ; +расширение
	MOV R3, A
	MOV R6, #0
	; Надо проверить нет ли смены знака итоговой суммы
	JNC Pid6ChkF0
	JB F0, Pid7 ; Err<0, был перенос => Знак не сменился, переполнения нет
	SJMP Pid6SumOv ; Err>0, был перенос => переполнение
Pid6ChkF0:
	JNB F0, Pid7 ; Err>0, небыло переноса => нет переполнения
	;SJMP Pid6SumUf ; Err<0, небыло переноса => сменился знак
Pid6SumUf:
	; Если Err<0 и небыло переноса => сменился знак
	CPL DiffS
	MOV A, R0
	CPL A
	ADD A, #1 ; C=?, поэтому прибавляем 1 обычным методом
	MOV R0, A
	MOV A, R1
	CPL A
	ADDC A, R6
	MOV R1, A
	MOV A, R2
	CPL A
	ADDC A, R6
	MOV R2, A
	MOV A, R3
	CPL A
	ADDC A, R6
	MOV R3, A
	SJMP Pid7 ; Знак у результата и DiffS приведены в норму
Pid6SumOv:
	; Было переполнение => округляем до максимума
	MOV R0, #0FFh
	MOV R1, #0FFh
	MOV R2, #0FFh
	MOV R3, #0FFh

	;	7. U = K*PID/256		 | 32bit*16bit/8bit => 40bit,
	;					 | которые усекаются до 10bit
	;					 | при вычислениях
Pid7: ; В R3:R2:R1:R0 лежит результат PID, в DiffS его знак
	  ; Нужно вычислить K*PID/256, ограничив результат до 10бит
	  ; K всегда положительно, поэтому если PID < 0 => минимум
	JB DiffS, Pid7Umin
	; поскольку мы можем жестко ограничить сверху 16ю битами,
	; то если R3 != 0 => ставим максимум в любом случае
	MOV A, R3
	JNZ Pid7Umax
	; [R2:R1:R0 * KH:HL] = [R7:R6:R5:R4:R3]
	; вычисляем, учитывая что должно получиться R7=0 R6=0,
	; иначе переполнение, поэтому R7 и R6 вообще не трогаем
	; но проверяем результат
	; R7:R6 = R2*KH
	MOV A, R2
	JZ Pid7S1
	MOV A, KH
	JNZ Pid7Umax ; Если R2!=0 и KH!=0 => R7:R6>0 => переполнение
Pid7S1:
	; R6:R5 = R2*KL
	MOV A, R2
	MOV B, KL
	MUL AB
	MOV R5, A
	MOV A, B
	JNZ Pid7Umax ; Если R6 > 0 => переполнение
	; R6:R5 = R1*KH
	MOV A, R1
	MOV B, KH
	MUL AB
	ADD A, R5
	JC	Pid7Umax ; Если R6 > 0 => переполнение
	MOV R5, A
	MOV A, B
	JNZ Pid7Umax ; Если R6 > 0 => переполнение
	; R5:R4 = R0*KH
	MOV A, R0
	MOV B, KH
	MUL AB
	MOV R4, A
	MOV A, R5
	ADD A, B
	JC	Pid7Umax ; Если R6 > 0 => переполнение
	MOV R5, A
	; R5:R4 = R1*KL
	MOV A, R1
	MOV B, KL
	MUL AB
	ADD A, R4
	MOV R4, A
	MOV A, R5
	ADDC A, B
	JC	Pid7Umax ; Если R6 > 0 => переполнение
	MOV R5, A
	; R4:R3 = R0*KL
	MOV A, R0
	MOV B, KL
	MUL AB
	RLC A ; C = R3>=0x80, Z=R3>0x80
	MOV R3, #0FFh ; R3<>0x80 => ничего
	JNZ Pid7S2
	MOV R3, #0FEh ; R3==0x80 => округление до четного
Pid7S2:
	MOV A, R4
	ADDC A, B ; Складываем умножение, регистр, и перенос-округление
	ANL A, R3 ; А так же если округление до четного -- отбрасываем после младший бит
	MOV R4, A
	MOV A, R5
	ADDC A, R6 ; R6=0 у нас с давних пор, хоть мы туда и не складывали ничего во время перемножения
	JC	Pid7Umax ; Если R6 > 0 => переполнение
	MOV R5, A
	; R5:R4 => ограниченный в 16 бит результат
	; Теперь надо ограничить R5:R4 до Umax/Umin
	MOV A, UmaxL
	SUBB A, R4 ; C=0 на текущий момент
	MOV A, UmaxH
	SUBB A, R5
	JC Pid7Umax ; Если R5:R4>Umax => R5:R4 = Umax
	MOV A, UminL
	SUBB A, R4 ; C=0 на текущий момент
	MOV A, UminH
	SUBB A, R5
	JNC Pid7Umin ; Если R5:R4<Umin => R5:R4 = Umin
	; Мощность вычислена
	MOV UH, R5
	MOV UL, R4
	SETB UReady
	AJMP CalcExit
Pid7Umax: ; Установить максимальную мощность
	MOV UH, UmaxH
	MOV UL, UmaxL
	SETB UReady
	AJMP CalcExit
Pid7Umin: ; Установить минимальную мощность
	MOV UH, UminH
	MOV UL, UminL
	SETB UReady
	AJMP CalcExit


IX. Применение воздействия.


Итак, у нас есть рассчитанное воздействие, и наша задача — применить его. Для этого работает общий цикл работы с частотой 50Гц. На четном цикле — производится измерение и вычисление, на нечетном — применение воздействия. Таким образом, общая схема получается: выставлена мощность, через одну синусоиду производится измерение и вычисление, еще через одну — применение новой.

X. Подводные камни.


По сравнению с разностной схемой, подводных камней у прямой схемы крайне мало, вот список тех, которые я видел:
  • Учет размерностей. Самое важное, и самая частая ошибка. Нельзя просто взять U=K*(Err+Ki*Int+Kd*Diff), без оговаривания ЧТО есть K, Ki, Kd. И с какой точностью. Особенно важно для коэффициента Ki, который имеет размерность обратную времени — если операция идёт в целых числах, НЕЛЬЗЯ просто умножать на него — так как там должно быть ДЕЛЕНИЕ, а обратное число в целых числах не представимо.
  • Учет знака. Второе очень важное — учет знака. Все операции должны быть знаковыми, интеграл обязан накапливаться знаковый — так как он не только замещает пропорциональную составляющую, но и позволяет сопротивляться внешним воздействиям, например — выделению тепла самой смеси; и тогда его знак отрицательный.
  • Учет переполнения. Нам важно получить либо мощность от 0% до 100%, либо факт того, что вычисленная мощность больше 100% или меньше 0%. Нет нужды производить все вычисления, если мы получили отрицательный подскобочный результат, например. Но при этом важно учесть, что при произведении-сложении может произойти переполнение — и его нужно учесть как «больше 100%», а ни в коем образе не оставить результат после переполнения. Это чревато в первую очередь отсутствием регулирования когда требуется — объект ниже требуемой температуры, а мощность не подаётся
  • Учет времени вычислений. Необходимость великоразрядных умножений (при кривой реализации — еще и деления) требует времени, поэтому крайне важно просчитать время выполнения самого худшего варианта вычислений, и оно должно быть меньше, чем свободное время между измерениями. Невыполнение этого условия ведёт к неуправляемому объекту, который «вроде работает, но как-то не так


XI. Выводы.


В результате, прямая схема реализации не имеет тех проблем, какие имеет разностная схема, но требует больше вычислительных затрат. Однако, при правильной реализации, прямая схема вполне применима даже на дешёвых 8 битных микроконтроллерах, и даёт более предсказуемые результаты.
Anton Fedorov @datacompboy
карма
114,0
рейтинг 0,3
Программист / сисадмин
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (45)

  • –1
    Что-то, по-моему, не очень хорошая идея в качестве элементарного ПИД-регулятора микроконтроллер использовать. Ведь здесь можно элементарной аналоговой схемой обойтись. См. Титце и Шенк.
    • +5
      А элементарная аналоговая схема позволит настраивать параметры удалённо по последовательному порту?
      А элементарная аналоговая схема позволит менять параметры на лету?
      А элементарная аналоговая схема позволит реализовать автонастройку?
      • –4
        > А элементарная аналоговая схема позволит настраивать параметры удалённо по последовательному порту?

        При помощи телефона: звоните уборщице, она накручивает нужные ручки ☺

        > А элементарная аналоговая схема позволит менять параметры на лету?

        Ручки можно крутить когда угодно.

        > А элементарная аналоговая схема позволит реализовать автонастройку?

        Схема будет малость посложней.
        • 0
          В былые времена наладчики очень громко вспоминали родословную создателей до десятого колена, крутя отвертками потенциометры регуляторов. А потом настала эпоха цифровых схем.
          • 0
            Мы пользуемся аналоговыми регуляторами, для поддержания статичной температуры — например, линии подогрева газа. 1 раз настроил — и забыл про него, там использование управляемого регулятора просто не оправдано. Максимум — доп. термопара с него на ADAM-4018 для контроля и всё.
            • 0
              Если честно, промышленных, относительно серийных решений на аналоговой электронике встречал только выпуска середины 90ых, и те сносим постепенно, заменяя ПЛК. В единичных экземплярах это может быть и оправдано.
    • +2
      Не очень хорошая идея использовать аналоговую схему, если можно сделать цифровую.
      • –1
        Все зависит от требуемых скоростей и надежности. Я сомневаюсь, что при необходимости реакции в килогерцы вы будете лепить цифровую схему (скорее всего, не успеете посчитать: у вас АЦП будет дольше выполняться).
        • +3
          Мсье троллит не по теме. Рассуждаем о терморегуляторе — откуда там килогерцы?
  • 0
    кто знает, как на хабре формулы вставлять? никак? пишу в maple-нотации тогда
    Use LaTeX, Luke.
    mathurl.com/
    • 0
      О! Спасибо, перерисую.
  • +1
    Насчет формул:
    вводим в командной строке
    formulator '$\int\limits_a^b \frac{\sin^2 x - x\cdot\log x}{\tg x+\exp{x}}dx$'
    

    и получаем:

    Код скрипта formulator
    #!/bin/sh
    prefix="tmp_$$"
    if [ "$1" = "" ]; then
    	echo -e "\nUsage: $(basename $0) \"latex string\" [output filename]\n"
    	exit
    fi
    [ "$2" != "" ] && output="$2" || output=$(date +%y.%m.%d_%H:%M:%S.png)
    tex="$prefix.tex"
    cat > $tex << EOF
    \documentclass[12pt]{minimal}
    \usepackage[koi8-r]{inputenc}	% Классическая кодировка
    \usepackage[english,russian]{babel}	% Правила переноса слов
    \usepackage[intlimits]{amsmath}	% Мат. команды
    \usepackage{amsfonts}		% Шрифты
    \usepackage{amssymb}		% Спец. символы
    \usepackage{wasysym}		% Для астрономических символов типа знаков зодиака
    \pagestyle{empty}
    \parindent=0pt
    \setbox0=\hbox{
    EOF
    echo $1 >> $tex
    cat >> $tex << EOF
    }
    \textheight=\ht0
    \textwidth=\wd0
    \oddsidemargin=-1cm
    \topmargin=-1.5cm
    \advance\textheight by 1cm
    \advance\textwidth by 1cm
    \begin{document}
    \vbox{\vss\hbox{\hss\copy0\hss}\vss}
    \end{document}
    EOF
    latex $tex
    dvipng -D 600 $prefix.dvi -o $output
    rm -f $prefix*
    


  • +1
    А можно по-подробней, что за контроллер использовался? И что за острая необходимость заставила вас использовать ассемблер. Мне однажды тоже пришлось столкнуться с разработкой температурного ПИД-регулятора. МЫ тогда использовали LPC2148 и все замечательно на Си удалось написать.
    • 0
      ADuC847. Асм по причине того, что весь остальной костяк приложения уже есть на асме — мы работаем с 51м семейством давно и тесно, а стандартное 12титактное ядро довольно плотно нагружено.
      • 0
        Хардкорно… А какой термодатчик использовался, если не секрет?
        • 0
          Термосопротивление и термопары ХА и ХК.
  • 0
    А вот еще вопросик: я так понял, показания вы снимаете при помощи АЦП, а как изменяете мощность нагрева? «Цифровыми вариаторами», ШИМом или еще как?
    • 0
      В статье написано же — регулируется время поджига симистора.
      • 0
        Ну по-сути, вы тот же ШИМ сделали, как я понял…
        • 0
          Да, только обычный шим имеет линейную зависимость время=мощность, при привязке к синусоиде — зависимость нелинейная.
          • 0
            Кстати, мне немного стыдно, но я никак не пойму о какой синусоиде вы все время говорите. Что у вас по синусу изменяется?
            • 0
              Термонагреватель подключен в обычную сеть 220В. Он питается синусоидой. И именно объёмом прохождения этой синусоиды до нагревателя и регулируется.
              • 0
                А разве не проще было сделать импульсный блок питания нагревателя? Питать его прямоугольными импульсами — так ведь легче вычислять потребляемую им мощность…
                • +2
                  Сделайте импульсник киловатт так на 50 :)
                • 0
                  Синус он и есть синус, а «прямоугольник» как получится
              • 0
                В этом случае наверное 220 как-то фильтровать все-таки хорошо нужно будет. Для синхронизации на эти 220 также нужен АЦП, как я понимаю
                • +2
                  что фильтровать-то? зачем фильтровать? при прохождении через 0 (когда уровень снижается ниже 12V) — получаем сигнал, выжидаем время прохождение 12V и поджигаем.
                  • 0
                    Ну если у вас в лабораторных условиях синусоида из розетки получается красивая и ровная(и то я сомневаюсь в постоянстве этого явления), то в о многих городах нашей родины — это просто из ряда фантастики. Там могут быть и всплески и провалы, и в этом случае такой примитивный алгоритм вряд ли сработает
                    • +2
                      Нагреватель — слишком инерционная штука, чтобы реагировать на всякие всплески и провалы. А если среднее значение напряжения плавает, то можно его периодически замерять и производить коррекцию, а можно просто забить на это: ПИД-регулятор будет достаточно хорошо работать даже при небольших изменениях мощности нагревателя.
                      • 0
                        Я имел ввиду момент начала отсчета (прохождение через 0). Если в процессе работы ПИД-а можно «забить» на негдадкость синуса, то при старте системы она очень сильно критична к таким всплескам. Согласитесь, что существует отличная от нуля вероятность некоего фальстарта. И мне интересно, как этот момент отрабатывается. В принципе нехитрый фильтр на 220 решил бы эту проблему.
                        • +2
                          Не понимаю вас.
                          Период всегда один и тот же (он физически не может измениться в электросети), мелкие всплески и провалы — по барабану, даже если стартанёт в каком-то периоде не там — это ровныи счётом ни на что не повлияет.
                        • 0
                          прохождение через ноль — весьма стабильная штука. «шилья» в ноль в сети НЕ бывают по среди синусоиды. если вы где-то это видели — это проблема электропроводки, коя на заводах/в лабораториях немного лучше сделана, чем в домах 40х годов, где на 1мм алюминия вешают микроволновку в 2 киловата + чайник 2 киловата.
                    • 0
                      вот как раз частота сети — вещь _очень_ стабильная по всей стране.
                      форма синуса — тоже, как ни странно, это просто следствие схемы работы генератора.

                      дальше, в промышленности от этой сети запитано много чего — и оно всё прекрасно работает, именно на таком примитивном алгоритме :)

                      разовые всплески мало на что влияют, так как его интеграл всплеска крайне мал — это «шило», там где это критично — срезается фильтрами (но тут сам нагреватель — тот еще фильтр).

                      то, что среднее напряжение плавает туда-сюда — это отрабатывает интегральная составляющая ПИД регулятора.
                    • 0
                      Спасибо большое, разобрался
                  • +2
                    Т.е. фазовое управление.
                    Для инертных мощных нагрузок можно использовать метод пропуска полупериодов — когда открываем симистор всегда в начале полупериорда, и, подсчитывая их, получаем линейность управления как при ШИМе, и меньше импульсных помех в сеть.

                    Поясню, для тех кто не понял: контроллер считает период управления как 100 периодов сети. Первые 20 он держит нагрузку включенной, остальные 80 — выключенной, имеем 20% мощности, и.т.д.
                    • +1
                      только лучше всё-таки не полупериодов, а целых периодов — чтобы на реактивной нагрузке сумма + и — уходила таки в 0.
                • +1
                  Для детектирования нуля АЦП не нужен, достаточно простого компаратора.
  • 0
    Как раз нужен ПИД-регулятор, увидев статью обрадовался, зашел-разочаровался из-за отсутствия описания изделия, которое можно пощупать…
    • 0
      а какое именно изделие? кроме самого пид регулятора, там внутре еще (и это только программно):
      1. неонка
      2. измеритель термосопротивления
      3. измеритель термопары
      4. отработка профиля температуры
      5. сохранение параметров во флеш
      6. поддержание протокола связи с ПК
      7. периодическая передача измерения на ПК
      8. код подготовки параметров для вычислений (я пропустил его в портянкевыше, если кому-то захочется посмотреть — выложу)
      9. код формирования значений на экран
      10. код изменения параметров с кнопок

      аппаратно же:
      1. датчик температуры холодного спая (накристальный врёт так как сам кристалл греется)
      2. усилитель сигнала с термопары
      3. опторазвязка к симистору
      4. схема запитки аналоговой и цифровой части от 220 (отдельные 220 от силы)
      5. развязка канала к ПК
      6. экран + кнопки управления
  • 0
    Я совершенно не понял как постоянные времени выбирать, они ведь, насколько я понимаю, согласно характеристикам объекта управления выбираются?
    И откуда взялся диапазон постоянных времени «от 0 до ~2000 сек»?
    • +2
      Настройка коэффициентов ПИД регулятора под объект — это отдельная наука.
      Вот тут неплохо и с графиками.
      Вот тут вроде неплохо про вычисление параметров по единичному скачку.
      Если честно, у меня сейчас на низком приоритете стоит реализация автонастройки по скачку — но руки-ноги всё не доходят до реализации.

      От «0» это понятно — при 0 коэффициент не работает вообще :)
      «До 2000» — так как у нас коэффициенты привязаны к общей сетке работы времени 1/25 сек, одного байта явно мало (10.2 секунды предельное время), значит коэффициент 16битный. А 65535/25=2 621,4 сек. Ставить полный диапазон мне показалось не красивым, и 2000 сек это уже как-то дофига :)
      • 0
        Понятно что 2000с диапазон значительный, но из каких соображений 10,2с мало для постоянных времени?
        Спасибо за ссылки, возможно, после освоения материала, вопрос отпадет сам собой :)
        • 0
          10.2сек мало по причине того, что для медленного объекта интегральная должна так же быть большой, сопоставимой со временем перехода
    • 0
      Поглядел, у меня в коде вообще 1800 сек ограничение. 30 минут чтоб.
      Так как час не получился. округлил до ближайшего ровного времени.
  • 0
    Отличная статья, спасибо!

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.