В этом эксперименте мы исследуем работу ПИД-регулятора с использованием как Arduino, так и Raspberry Pi. Программа для ПИД-контроллера с использованием Arduino берется из библиотеки, а программу для ПИД-контроллера с использованием Raspberry Pi придется набирать вручную.
Оборудование
В этом эксперименте используется точно то же самое оборудование, что и в экспе рименте из разд. «Эксперимент: насколько хорош терморегулятор, основанный на включении и выключении?». Но при подключении его к Raspberry Pi, нужно про- явить особое внимание, поскольку микросхему DS18B20 и нагреватель из резистора следует запитать на Рі отдельно: микросхему DS18B20 от напряжения 3,3 В, а резисторный нагреватель от напряжения 5 В. Дело в том, что цифровой выход из DS18B20 должен повышаться до 3,3 В, а не до 5 В, чтобы не повредить входные цепи Raspberry Рі.
Программа для Arduino
В этом эксперименте используется готовая к использованию ПИД-библиотека
(
https://github.com/br3ttb/Arduino-PID-Library/), которую можно загрузить и установить. Полную документацию по этой библиотеке можно найти на веб-сайте Arduino (
http://playground.arduino.cc/Code/PIDLibrary).
Скетч, используемый в эксперименте с Arduino:
Код: Выделить всё
#include <OneWire.h>
#include <DallasTemperature.h>
#include <PID_v1.h>
const int tempPin = 2;
const int heatPin = 9;
const long period = 1000; // >750
double kp = 0.0; // (1)
double ki = 0.0;
double kd = 0.0;
OneWire oneWire(tempPin);
DallasTemperature sensors(&oneWire);
double setTemp = 0.0;
double measuredTemp = 0.0;
double outputPower = 0.0; // (2)
long lastSampleTime = 0;
PID myPID(&measuredTemp, &outputPower, &setTemp, kp, ki, kd, DIRECT); // (3)
void setup() {
pinMode(heatPin, OUTPUT);
Serial.begin(9600);
Serial.println("t30 - sets the temperature to 30");
Serial.println("k50 20 10 - sets kp, ki and kd respectively");
sensors.begin();
myPID.SetSampleTime(1000); // (4)
myPID.SetMode(AUTOMATIC);
}
void loop() {
checkForSerialCommands(); // (5)
long now = millis();
if (now > lastSampleTime + period) { // (6)
lastSampleTime = now;
measuredTemp = readTemp();
myPID.Compute();
analogWrite(heatPin, outputPower);
Serial.print(measuredTemp); // (7)
Serial.print(", ");
Serial.print(setTemp);
Serial.print(", ");
Serial.println(outputPower);
}
}
void checkForSerialCommands() { // (8)
if (Serial.available()) {
char command = Serial.read();
if (command == 't') {
setTemp = Serial.parseFloat();
Serial.print("Set Temp=");
Serial.println(setTemp);
}
if (command == 'k') {
kp = Serial.parseFloat();
ki = Serial.parseFloat();
kd = Serial.parseFloat();
myPID.SetTunings(kp, ki, kd);
Serial.print("Set Constants kp=");
Serial.print(kp);
Serial.print(" ki=");
Serial.print(ki);
Serial.print(" kd=");
Serial.println(kd);
}
}
}
double readTemp() {
sensors.requestTemperatures();
return sensors.getTempCByIndex(0);
}
Уточним некоторые моменты скетча по пунктам, воспользовавшись разметкой строк, сделанной в комментариях.
ПРИМЕЧАНИЕ
Код, относящийся к считыванию температурных данных с DS18B20, ничем не отличается от кода для эксперимента из разд. «Эксперимент: насколько хорош терморегулятор, основанный на включении и выключении?», поэтому здесь мы сосредоточим внимание на коде, относящемся к ПИД-регулированию.
1. В качестве коэффициентов масштабирования для Р-, I- и D-компонентов выхода определяются три переменные: кр, ki и kd. Использование переменных обу- словлено тем, что они могут изменяться с помощью команд в окне монитора порта в ходе выполнения программы. Тип
double использован вместо типа
float отчасти потому, что он позволяет работать с более точными числами, чем
floats, а также потому, что именно этот тип ожидается библиотекой.
2. Переменная
outputPower будет содержать коэффициент заполнения ШИМ в пределах от 0 до 255.
3. Для обращения к коду ПИД-библиотеки определена переменная myPID. Заметьте, что первые три параметра при создании переменной для обращения к ПИД-библиотеке являются именами переменных
measuredTemp,
outputPower и
setTemp, перед которыми стоит префикс
&. Это прием языка С, позволяющий ПИД-библиотеке изменять значения этих переменных, даже если они не явля-ются частью библиотеки. Если вас интересуют дополнительные сведения об этой технологии (указателях языка С), обратитесь к материалу Tutorials Point (
http://www.tutorialspoint.com/cprogramm ... inters.htm). Последний параметр (
DIRECT) устанавливает для ПИД-операции прямой режим, который в случае применения этой библиотеки означает, что выход будет пропорционален рассогласованию, и не будет инвертирован. Для удобства по умолчанию диапазон выходных значений для ШИМ устанавливается от 0 до 255.
4. Время периодичности снятия показаний нужно установить равным 1 секунде (1000 мс). ПИД-вычисления запускаются установкой режима на
AUTOMATIC.
5. Теперь проверка поступления последовательных команд находится в своей собственной функции, останавливающей цикл
looр(), что облегчает чтение кода. который ранее был слишком многословен.
6. Как только наступает время очередного замера температуры, происходит ее считывание в переменную
measuredTemp, а затем ПИД-библиотека получает предписание на обновление своих вычислений (
myРID.Compute). При этом автоматически производится обновление значения
outputPower , которое затем используется для установки коэффициента заполнения ШИМ на контакте, используемом для управления резисторным нагревателем.
7. Все значения выводятся в окне монитора порта, поскольку они нужны нам для построения графиков и отслеживания работы контроллера.
8. Функция
checkForSerialCommands проверяет выдачу команды для задания температуры, как и в эксперименте из разд. «Эксперимент: насколько хорош терморегулятор, основанный на включении и выключении?», но также проверяет и наличие команды
k, за которой следуют три числа: kp, ki kd, устанавливая параметры настройки в случае получения команды.
Загружаем и выполняем программу
Оборудование и программа, предназначенные для этого эксперимента, дают нам все необходимое, чтобы провести эксперимент с ПИД-контроллером. Мы можем изменить заданную температуру и три значения kp, ki и kd- и записать влияние, оказываемое ими на выходе. В нашем случае вполне приемлемые результаты будут получены с ПИ-регулятором, поэтому для kd может быть просто установлено значение 0.
Итак, загрузите программу в Arduino и откройте окно монитора порта.
- Использование окна монитора порта для тестирования ПИД-регулирования
Настройка контроллера требует времени. Нужно будет записать данные и построить на их основе график, чтобы увидеть, насколько приемлемо поведение системы. Сначала следует подобрать подходящее значение для кр. Давайте просто попробу ем значения 50, 500 и 5000, чтобы получить представление о системе.
Прежде всего установим параметры настройки, введя в окне монитора порта следующие значения:
k50 0 0
В результате для kp будет установлено значение 50, а для kі и kd- значения, равные нулю. Если хотите, можете ввести значения с указанием десятичной части (например, k30.0 0.0 0.0). В обоих случаях, числа будут преобразованы в формат чисел с плавающей точкой.
Теперь давайте установим заданную температуру на 30°С (это значение я выбрал как наиболее подходящее, превышающее значение температуры окружающей среды примерно на 7 или 8 градусов):
t30
Температура должна начать подниматься, что станет отображаться на экране, где появятся три столбца, показывающие фактическую температуру, заданную температуру и значение на выходе ШИМ (в диапазоне от 0 до 255);
25.06, 30.00, 246.88
25.19, 30:00, 240.63
25.31, 30.00, 234.38
25.44, 30.00, 228.13
Заметьте, что значение на ШИМ-выходе сохранено в виде числа с плавающей точкой, поэтому у него имеются цифры после десятичной точки. При вызове функции
analogWrite это значение должно быть усечено до целого числа в диапазоне от 0 до 255.
Как можно видеть, значение коэффициента заполнения ШИМ в последнем столбце при заданном для kp значении 50 начинает почти сразу же снижаться. Это означает, что значение kp выбрано слишком низким, но вы продолжайте собирать данные еще несколько минут, а затем перенесите их в текстовый файл и импортируйте этот файл в программу электронной таблицы. Я начал копировать данные, когда температура достигла 25°С. Постройте по показателям температуры график и получите примерно такую же картину, что изображена на рисунке ниже.
- График изменения температуры с течением времени для кр= 50
Похоже, что при значении kp, равном 50, температура никогда не поднимется до 30°С. Задайте температуру 0°С (t0 - в окне монитора порта) и подождите, пока система не остынет, после чего повторите процедуру сначала для kp со значением 500, а потом со значением 5000. Результаты, полученные при этих трех значениях kp. показаны на рисунке ниже.
Как мы и предполагали, для kp значение 50 слишком мало, 500 уже больше подходит, но при нем значение заданной температуры все еще не достигается, а при значении 5000 система ведет себя как термостат, работающий по принципу включение/выключение. Можно выдвинуть предположение, что для kp вполне подойдет значение 700, особенно, если система получит ускорение в подъеме температуры за счет добавления значения для I-составляющей.
В методе настройки ПИ-контроллера от Циглера-Никольса предлагается вычислять значение ki по следующей формуле:
ki = (1,2 × kр)/pu
Мы прикинули, что для kp вполне подойдет значение 700, а из рисунка следует, что значение pu составляет приблизительно 15 секунд, из чего для ка предлагается значение 56.
- График изменения температуры с течением времени для трех значений kp
- График изменения температуры с течением времени для П и ПИ-регулирования
Задайте Arduino еще один набор данных: с
kp= 700 и
k = 56. Результаты, полученные на его основе, показаны на рисунке выше, где они даны в сравнении с результатами для чисто пропорционального регулирования при значении kp равном 700. Ось Y здесь несколько растянута, чтобы можно было увидеть, насколько хороши результаты для ПИ-регулирования.
Как только кривая ПИ-графика достигнет заданной температуры, она будет изме няться на величину, слегка превышающую 0,1 градуса. Температура изменяется ступенчато, а не плавно, поскольку микросхема DS18B20 является цифровым уст ройством с фиксированным шагом.
Потратив дополнительные усилия на проведение экспериментов, можно, наверное, еще улучшить результат, но вряд ли этим стоит заниматься.
Подключение Raspberry Pi
Схема, собранная на макетной плате для Raspberry Pi, немного отличается от того, что было собрано для Arduino. Разница в том, что нам нужно, чтобы резисторный нагреватель по-прежнему работал от 5 В, а датчику температуры DЅ18В20, чтобы быть совместимым с GPIO-контактами Raspberry Pі, нужно работать от 3,3 В.
- Компоновка макетной платы для подключения к Raspberry Pi
Программа для Raspberry Pi
Чтобы микросхема DS18B20 работала с Raspberry Pi, требуется небольшая подготовка. Сначала нужно включить шину 1-Wire. Для этого отредактируйте файл /boot/config.txt, воспользовавшись следующей командой:
$ sudo nano /boot/config.txt
Добавьте к концу файла следующую строку:
dtoverlay=w1-gpio
Теперь нужно перезапустить Raspberry Pi, чтобы изменения вступили в силу. В микросхеме DS18B20 используется интерфейс в стиле текстового файла, значит, программе на языке Python придется считывать файл, а затем извлекать из него температурные измерения. Можно попытаться сделать это до запуска всей программы и посмотреть, как выглядит формат сообщения, изменив с помощью следующей команды текущий каталог на /sys/bus/w1/devices:
$ cd /sys/bus/w1/devices
Затем нужно получить список каталогов, находящихся в этой папке, воспользовавшись следующей командой:
$ 1s
28-000002ecba60 w1_bus_master1
pi@raspberrypi /sys/bus/w1/devices $
Сделайте текущим каталог, чье имя начинается с 28. В нашем случае это каталог 28-000002ecba60 (учтите, что у вас, скорее всего, он будет иметь другое имя):
$ cd 28-000002ecba60
И, наконец, запустите следующую команду для извлечения последних считанных значений температуры:
$ cat w1_slave
Ответ приходит в двух строках:
53 01 4b 46 7f ff Od 10 e9 : crc=e9 YES
53 01 46 46 71 ff od 10 e9 t=21187
pi@raspberrypi /sys/bus/w1/devices/28-000002ecba60 $
Первая часть каждой строки представляет собой уникальный идентификатор для датчика температуры, а первая строка заканчивается словом
YES, показывающим, что чтение прошло успешно. Вторая же строка заканчивается показанием температуры, выраженным в тысячных долях градуса Цельсия. В данном случае это 21 187 (или 21,187°С).
Хотя для ПИД-регулирования имеются доступные библиотеки на языке Python, использовать их сложнее, чем их двойников для Arduino, и поэтому для версии Raspberry Pi ПИД-алгоритм будет реализован с чистого листа (хотя и не совсем код был написан с оглядкой на библиотеку Arduino и с попыткой сделать две версии как можно более похожими друг на друга).
Код: Выделить всё
import os
import glob
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
heat_pin = 18
base_dir = '/sys/bus/w1/devices/' # (1)
device_folder = glob.glob(base_dir + '28*')[0]
device_file = device_folder + '/w1_slave'
GPIO.setup(heat_pin, GPIO.OUT)
heat_pwm = GPIO.PWM(heat_pin, 500)
heat_pwm.start(0)
old_error = 0 # (2)
old_time = 0
measured_temp = 0
p_term = 0
i_term = 0
d_term = 0
def read_temp_raw(): # (3)
f = open(device_file, 'r')
lines = f.readlines()
f.close()
return lines
def read_temp(): # (4)
lines = read_temp_raw()
while lines[0].strip()[-3:] != 'YES':
time.sleep(0.2)
lines = read_temp_raw()
equals_pos = lines[1].find('t=')
if equals_pos != -1:
temp_string = lines[1][equals_pos+2:]
temp_c = float(temp_string) / 1000.0
return temp_c
def constrain(value, min, max): # (5)
if value < min :
return min
if value > max :
return max
else:
return value
def update_pid(): # (6)
global old_time, old_error, measured_temp, set_temp
global p_term, i_term, d_term
now = time.time()
dt = now - old_time # (7)
error = set_temp - measured_temp # (8)
de = error - old_error # (9)
p_term = kp * error # (10)
i_term += ki * error # (11)
i_term = constrain(i_term, 0, 100) # (12)
d_term = (de / dt) * kd # (13)
old_error = error
# print((measured_temp, p_term, i_term, d_term))
output = p_term + i_term + d_term # (14)
output = constrain(output, 0, 100)
return output
set_temp = input('Enter set temperature in C ') # (15)
kp = input('kp: ')
ki = input('ki: ')
kd = input('kd: ')
old_time = time.time() # (16)
try:
while True:
now = time.time()
if now > old_time + 1 : # ()17)
old_time = now
measured_temp = read_temp()
duty = update_pid()
heat_pwm.ChangeDutyCycle(duty)
print(str(measured_temp) + ', ' + str(set_temp) + ', ' + str(duty))
finally:
GPIO.cleanup()
Уточним некоторые моменты программы по пунктам, воспользовавшись разметкой строк, сделанной в комментариях:
1. Этот код определяет каталог, в котором находится файл для DS18B20. Это делается практически так же, как и в ранее рассмотренном методе с использованием команды glob для нахождения первого каталога, начинающегося с 28.
2. Эти глобальные переменные используются ПИД-алгоритмом. Переменная
old_error служит для вычисления изменения рассогласования для D-составляющей.
3. Функция
read_temp там считывает в виде двух строк текста показания микросхемы DS18820
4. Функция
read_temp отвечает за фактическое извлечение показателя температуры с конца второй строки после проверки, что в первой строке получен ответ YES.
5. Эта вспомогательная функция ограничивает значение первого параметра, чтобы оно всегда находилось внутри диапазона, указанного вторым и третьим параметрами.
6. Функция
update_pid содержит код ПИД-вычислений.
7. Вычисление
dt (сколько времени прошло с последнего вызова функции
update_pid).
8. Вычисление рассогласования.
9. Вычисление изменения в рассогласовании
de.
10. Вычисление пропорциональной составляющей.
11. Добавление к
i_term текущего значения
error*
ki.
12. Ограничение интервала значений:
i_term тем же диапазоном, что и на выходе (от 0 до 100).
13. Вычисление
d_term.
14. Сложение всех составляющих и ограничение интервала значений в диапазоне выходных значений от 0 до 100.
15. В отличие от версии Arduino, позволяющей корректировать настроечные переменные при работе контроллера, программа на языке Python делает однократный запрос на ввод температуры,
kp, ki и kd
16. Переменная
old_time инициализируется текущим временем непосредственно перед началом основного управляющего цикла.
17. Если со времени предыдущего замера прошла 1 секунда, производится измерение температуры, а затем получение нового значения на выходе (
duty) и соответствующее изменение коэффициента заполнения ШИМ-канала.
Загружаем и выполняем программу
Одно из отличий версии программы для Arduino от версии программы для Raspberry Pi заключается в том, что у Raspberry Pi выход в диапазоне от 0 до 100, а у Arduino - от 0 до 255. Поэтому параметры
kp и ki, найденные при настройке Arduino, нуждаются в корректировке под Raspberry Рі. По сути, чтобы подогнать выход под интервал от 0 до 100, можно просто разделить значения kp и ki на 2,5. Это приведет к тому, что у kp будет значение 280, а у ki - 22.
Запустите программу, задайте температуру 30, подключите эти числа, и в результате должны быть получены данные, сходные с теми, что были при использовании версии под Arduino:
$ sudo python ex_11_pid_thermostat.py
Введите заданную температуру в градусах Цельсия 30
kp: 280
ki: 22
kd: 0
23.437, 30, 100 23.437, 30, 100
23.5, 30, 100 23.562, 30, 100
23.687, 30, 100
Построив по этим показателям с помощью электронной таблицы график, я получил результаты, показанные на рисунке. Здесь также используется растянутое представление температуры, которая регулируется довольно точно.
- Результаты ПИД-регулирования с использованием Raspberry Pi