Урок 38. Ардуино-контроллер элемента Пельтье. Структура программы. Измерение выходных параметров контроллера.

Ардуино-контроллер элемента Пельтье

Продолжим начатую в уроке 36 разработку контроллера элемента Пельтье. Определим структуру программы и реализуем измерение энергетических параметров на выходе контроллера.

Предыдущий урок     Список уроков     Следующий урок

Контроллер должен выполнять несколько параллельных задач в циклах с разными временами периодов. Задачи должны выполняться одновременно. Ни какую задачу приостановить нельзя. Даже кратковременное изменение времени периода любой операции может привести к неприятностям: погрешности измерения, мерцанию индикаторов, неправильной работе регуляторов, неустойчивой реакции на нажатия кнопок и т.п.

 

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

Понятно, что простой последовательностью действий, принятой при разработке программ для Ардуино, задачу не решить.

 

Разработка структуры программы.

Прежде всего, определимся какие действия и с какой периодичностью необходимо выполнять в программе. Все операции сейчас мы перечислить не сможем. Главное определить типичные для нашей разработки операции и разбить их по времени вызова.

Мне в голову пришли следующие параллельные задачи.

Операции Периодичность вызова
Измерение тока, напряжения 2 мс
Цифровая фильтрация (вычисление среднего значения) тока, напряжения, мощности 20 мс
Регулятор мощности на Элементе Пельтье 20 мс
Чтение датчиков температуры (формирование сигналов управления датчиками 1-wire) 1 сек
Регулятор температуры 1 сек
Вывод данных на дисплей 20 мс
Сканирование кнопок 2 мс
Управление индикацией, установка параметров 20 мс, 300 мс
Защитные функции 20 мс
Передача данных на компьютер 1 сек
Управление, общая логика программы 20 мс

Операции по периоду вызова можно разделить на 3 группы:

  • Операции, выполняющиеся в цикле 2 мс. Чтение аналоговых каналов, сканирование состояния кнопок, формирование сигналов датчиков температуры (интерфейс 1-wire).
  • Операции с периодом вызова 20 мс. Цифровая фильтрация значений аналоговых каналов, регулятор мощности, индикация, общее управление и т.п.
  • Медленные операции с периодом 1 сек. Чтение датчиков температуры, регулятор температуры, передача данных на компьютер.

При этом надо учитывать, что операции с периодом вызова 20 мс не должны блокировать операции в цикле 2 мс. Операции с периодом 1 секунда не должны мешать выполнению операций в циклах 2 и 20 мс.

Представьте себе, что пришло время выполнять медленный цикл (1 сек). В нем много действий, которые занимают значительное время, например 0,5 секунд. Только операции измерения температуры требуют время такого порядка. В течение этого времени операции циклов 2 и 20 мс выполняться не будут. Не будут считываться данные аналоговых каналов и кнопок, не будет работать регулятор мощности. Ничего нормально работать не будет!

Цикл 2 мс мы можем организовать за счет прерывания по таймеру. Он будет выполняться всегда.  Но есть еще цикл 20 мс, который тоже нельзя останавливать.  А может появиться необходимость еще в других циклах.

Программы из предыдущих уроков реализовывали выполнение параллельных задач на уровне: основной цикл  loop() и прерывание по таймеру. Для контролера элемента Пельтье требуется нечто более сложное.

 

Структура программы контроллера элемента Пельтье.

Я выбрал следующий вариант структуры программы.

Цикл с временем периода 2 мс реализован в обработчике прерывания по таймеру.

// обработка прерывания по таймеру 2 мс
void  timerInterupt() {

// операции цикла 2 мс

  interruptCount++; // счетчик циклов прерываний  
  if ( interruptCount >= 10 ) {  // время цикла 20 мс    
    interruptCount= 0;    
    flagReady= true;  // признак цикла 20 мс
  }
}

Цикл вызывается аппаратным прерыванием каждые 2 мс. В нем выполняются необходимые операции. Операции этого цикла приоритетные. Остановить выполнение их операции других циклов не могут.

В каждом цикле переменная interruptCount (счетчик циклов прерываний) увеличивается на 1. Дальше она сравнивается с константой и при достижении ее значения формируется признак flagReady. При времени прерывания 2 мс и константе равной 10 признак flagReady вырабатывается каждые 20 мс.

В основном цикле loop() анализируется признак flagReady и при его активном состоянии выполняется программный блок ”цикл 20 мс”.

//------------------------------- основной цикл ------------------------------- void loop() {

//------------------------------- цикл 20 мс ( регулятор мощности )
  if (flagReady == true) {
    flagReady= false;

    // операции цикла 20 мс

    //--------------- выполнение распределенных операций в цикле 1 сек     cycle20mcCount++;   // счетчик циклов 20 мс
    if (cycle20mcCount >= 50) cycle20mcCount= 0;  // время цикла 1 сек

    if (cycle20mcCount == 0) {
//операции интервала 0
}

    if (cycle20mcCount == 1) {
//операции интервала 1
//инициализации измерения температуры
}

    if (cycle20mcCount == 25) {
//операции интервала 25
}

    if (cycle20mcCount == 48) {
//операции интервала 48   

       //чтение температуры
}

  }
}

В нем выполняются операции цикла 20 мс.

Для отсчета времени медленного цикла (1 сек)  переменная cycle20mcCount (счетчик циклов 20 мс) каждые 20 мс увеличивается на 1. При достижении 50, т.е. времени 1 сек, она сбрасывается в 0.

Теперь остается операции цикла 1 сек распределить (размазать) по 50 интервалам длительностью 20 мс. Каждая операция этого цикла не должна превышать время 20 мс. Надо оставить время на выполнение операций других циклов.

Для некоторых задач такая организация оказывается просто идеальной. Например, работа с датчиком температуры DS18B20 требует следующей последовательности операций:

  • инициализация измерения температуры;
  • ожидание преобразования температуры (750 мс);
  • чтение данных датчика.

В каких-нибудь интервалах начале медленного цикла мы можем выполнить инициализацию измерения, а в последних интервалах – считать результат. Пауза на преобразование температуры 750 мс отработается автоматически.

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

if (cycle20mcCount == 25) {
//операции интервала 25
}

 

Реализация программы измерения выходных параметров контроллера элемента Пельтье.

В предыдущих статьях об управлении элементом Пельтье я писал, что необходимо регулировать не ток, не напряжение, а  электрическую мощность на элементе. Чтобы что-то регулировать, необходимо это измерять. Поэтому первая задача это измерение энергетических параметров на выходе контроллера:

  • напряжения;
  • тока;
  • мощности.

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

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!

 

// контроллер элемента Пельтье

#include <MsTimer2.h>
#include <avr/wdt.h>

#define powerU  12.11     // напряжение источника питания
#define koeffU  0.01679 // массштабный коэффициент напряжения,
                        // koeffU = 1.1 / R1 * (R1 + R2) / 1024 = 1.067/820*(820 + 12000)/1024 =0.01629 В/ед. АЦП
#define koeffI  0.02148 // массштабный коэффициент тока
                        // koeffI =  1,067 / R8 / 1024 = 0.02084 А/ед. АЦП

#define MEASURE_PERIOD 10  // время периода измерения (* 2 мс)

float measureU; // измеренное напряжение на нагрузке, В
float measureI; // измеренный ток потребления регулятора, А
float measureP; // измеренная мощность на нагрузке, Вт

byte  interruptCount=0; // счетчик циклов прерываний
unsigned int  sumU, sumI; // переменные для суммирования кодов АЦП
unsigned int  averageU, averageI; // средние значения кодов АЦП
boolean flagReady;  // признак готовности данных измерения
byte  cycle20mcCount; // счетчик циклов 20 мс

void setup() {
 // установка ШИМ 8 разрядов, 62,5 кГц
  TCCR1A = TCCR1A & 0xe0 | 1;
  TCCR1B = TCCR1B & 0xe0 | 0x09; 

  Serial.begin(19200);  // инициализируем последовательный порт, скорость 19200
  analogReference(INTERNAL); // опорное напряжение 1,1 В
  MsTimer2::set(2, timerInterupt); // прерывания по таймеру с периодом 2 мс
  MsTimer2::start();              // разрешение прерывания 
  wdt_enable(WDTO_15MS); // разрешение работы сторожевого таймера, тайм-аут 15 мс   
}

//------------------------------- основной цикл -------------------------------
void loop() {

//------------------------------- цикл 20 мс ( регулятор мощности )
  if (flagReady == true) {
    flagReady= false; 

    //---------------- вычисление измеренных значений
    // вычисление напряжения
    measureU = powerU - (float)(averageU / MEASURE_PERIOD) * koeffU;
    if (measureU < 0) measureU=0;
       
    // вычисление тока
    measureI = (float)(averageI / MEASURE_PERIOD) * koeffI;
        
    // вычисление мощности
    measureP = powerU * measureI;

    analogWrite(9, 191); // на выводе 9 ШИМ= 75%

    //--------------- выполнение распределенных операций в цикле 1 сек ---------------
    cycle20mcCount++;   // счетчик циклов 20 мс
    if (cycle20mcCount >= 50) cycle20mcCount= 0;  // время цикла 1 сек

    if (cycle20mcCount == 25) {
      //--------------------------- интервал 25, передача информации на компьютер
      Serial.print("U="); Serial.print(measureU, 2);  // напряжение
      Serial.print(" I="); Serial.print(measureI, 2);  // ток
      Serial.print(" P="); Serial.print(measureP, 2);  // мощность
      Serial.println(" ");
    }
 
  }
}

//------------------------------ обработка прерывания по таймеру 2 мс
void  timerInterupt() {
  wdt_reset();  // сброс сторожевого таймера 
  interruptCount++; // счетчик циклов прерываний

  // измерение напряжения и тока
  sumU += analogRead(A1);  // суммирование выборок АЦП
  sumI += analogRead(A0);  // суммирование выборок АЦП
  // проверка числа выборок усреднения
  if ( interruptCount >= MEASURE_PERIOD ) {
    interruptCount= 0;   
    averageU = sumU; // перегрузка среднего значения
    averageI = sumI; // перегрузка среднего значения    
    sumU= 0;
    sumI= 0;
    flagReady= true;  // признак результат измерений готов
  } 
}

Программа организована согласно структуре описанной выше.

Задано прерывание по таймеру 2 с периодом 2 мс. В нем происходит чтение аналоговых входов и суммирование кодов АЦП в переменных SumU и SumI. Через каждые 20 мс (10 выборок) переменные перегружаются в averageU и averageI. В этих переменах содержатся средние значения результатов измерения за 20 мс и умноженные на 10. Перегрузка сопровождается установкой признака flagReady в активное состояние. По такому принципу мы делали вольтметр в уроке 13.

В основном цикле loop() выделены программные блоки:

  • цикл 20 мс;
  • выполнение распределенных операций в цикле 1 сек.

В цикле 20 мс происходит:

  • вычисление напряжения;
  • вычисление тока;
  • вычисление мощности.

Вычисление тока:

measureI = (float)(averageI / MEASURE_PERIOD) * koeffI;

Среднее значение кода АЦП делим на 10, явно преобразовываем в float и умножаем на масштабный коэффициент. Коэффициент вычисляется по формуле:

koeffI =  Uион / R8 / 1024 А/ед. АЦП

  • Uион – напряжение источника опорного напряжения. Задано 1,1 В, но можно измерить и задать точнее.
  • R8 – сопротивление токового шунта R8. В моей схеме равно 0,05 Ом.

Коэффициент показывает, какой ток соответствует одной единице кода АЦП.

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

Вычисление напряжения:

measureU = powerU - (float)(averageU / MEASURE_PERIOD) * koeffU;

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

koeffU = Uион  / R1 * (R1 + R2) / 1024 В/ед. АЦП

  • R1 и R2 – сопротивления резисторов делителя напряжения. В моей схеме это 820 и 1200 Ом.

Коэффициент показывает, какое напряжение соответствует одной единице кода АЦП.

Мощность вычисляется как произведение напряжения источника питания на потребляемый регулятором ток:

measureP = powerU * measureI;

Считаем, что КПД регулятора равен 1, а это значит, что вычисленное значение равно мощности на элементе Пельтье. Я писал об этом в уроке 36.

Эти измеренные значения measureU, measureI и measureP будут использоваться в других программных блоках. Ради них мы и написали эту часть программы. Мы должны проверить, отладить модуль измерения выходных параметров контроллера и забыть об АЦП, кодах, коэффициентах. Дальше будем работать с напряжением в В, с током в А, с мощностью в Вт.

 

Передача данных на компьютер.

Видеть данные на мониторе компьютера необходимо не только для отладки программы, но и для отладки всей системы охлаждения. Т.е. для адаптации контроллера к конкретным объектам, под конкретную задачу. Поэтому мы будем выводить все, что может потребоваться.

Отлаживать программу будем через монитор последовательного порта Arduino IDE. Когда закончим разработку резидентной программы контроллера я напишу программу на компьютер. Это будет программа верхнего уровня – аналог моей программы Монитор контроллера элемента Пельтье. В ней данные будут отображаться в ”человеческом виде”, будет реализована регистрация данных с отображением на графиках. Сейчас я не знаю, какие параметры будем передавать на компьютер.

Необходимо выбрать протокол передачи данных. В уроке 31 я затронул тему протоколов. Не буду повторять. Если мы собираемся использовать для отладки стандартный монитор последовательного порта, значит, протокол должен быть текстовым.

Осталось определить формат. Чтобы сократить количество передаваемых данных я передаю минимум служебной информации.

Номер символа в пакете Символ Назначение
0 U (пример) Буква, символическое обозначение параметра
1 = Символ присвоения
2 … 5 7.23 Значение параметра
6 пробел Разделитель
7 I (пример) Буква, символическое обозначение параметра
8 = Символ присвоения
9 … 12 12.45 Значение параметра
13 пробел Разделитель
. . . . . . . .
\r возврат каретки, код 13
\n перевод строки, код 10

Каждый параметр передается в формате:

  • буква, символическое обозначение параметра;
  • символ “=”;
  • числовое значение параметра;
  • пробел.

Символическое обозначение параметра может быть любым символом. Символы могут повторяться. Пакет данных завершается управляющими символами \r и \n.

Я решил пока передавать данные на компьютер начиная с 25 интервала:

    if (cycle20mcCount == 25) {
      //-------- интервал 25, передача информации на компьютер
      Serial.print("U="); Serial.print(measureU, 2);  // напряжение
      Serial.print(" I="); Serial.print(measureI, 2);  // ток
      Serial.print(" P="); Serial.print(measureP, 2);  // мощность
      Serial.println(" ");
    }

В результате будет выведена строка:

Строка параметров

Проверка программы.

Я собрал схему контроллера из урока 36. Подключил нагрузку – два последовательно соединенных резистора по 10 Ом.

Ардуино-контроллер элемента Пельте

Лишний раз проверьте схему. В уроке 36 я написал, как сжег плату Ардуино.

В программе временно поставил строчку, которая задает ШИМ с коэффициентом заполнения 75%:

analogWrite(9, 191); // на выводе 9 ШИМ= 75%

Загрузил скетч:

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!

Запустил монитор последовательного порта.

Обратите внимание, что необходимо установить скорость обмена 19200 бод. Во всех предыдущих уроках мы использовали 9600.

При ШИМ = 75% и нагрузке 20 Ом монитор последовательного порта показал.

Измеренные контроллером параметры

Замерил вольтметром напряжение на нагрузке. Получилось:

  • 8,97 – измерено контроллером;
  • 8,91 – показания вольтметра.

Приемлемый результат. Особенно с учетом того, что я использовал в качестве R1 и R2 резисторы точностью 5%. Точность повысится, если измерить сопротивление резисторов R1 и R2 и при вычислении koeffU  задать их точные значения. Можно просто подкорректировать коэффициент, чтобы значения измеренные контроллером и вольтметром совпадали.

Измеренный контроллером ток оказался на 80 мА ниже реального. Повлияла погрешность сопротивления резистора R8 и ошибка смещения нуля АЦП Ардуино. Эта ошибка  показывает значение напряжения на входе АЦП при нулевом выходном коде. Для напряжения источника опорного напряжения 4 В она нормируется на уровне 2%. При снижении опорного напряжения ошибка смещения нуля увеличивается. Можно ее скорректировать программными или аппаратными средствами, но я решил, что погрешность смещения 80 мА вполне допустима.

При уменьшении сопротивления нагрузки до 10 Ом показания монитора последовательного порта выглядят так.

Измеренные контроллером параметры

При ШИМ равном 50 %.

Измеренные контроллером параметры

Я хочу резюмировать, что мы сделали в этом уроке.

  • Создали скелет программы со всеми временными циклами.
  • Реализовали и проверили модуль измерения выходных параметров:
    • measureU – напряжение на нагрузке;
    • measureI – ток потребления регулятора;
    • measureP – мощность на нагрузке.
  • Определили протокол передачи данных на компьютер и реализовали передачу измеренных выходных параметров.

 

В следующем уроке будем разрабатывать регулятор мощности на элементе Пельтье.

 

Предыдущий урок     Список уроков     Следующий урок

1

Автор публикации

не в сети 3 дня

Эдуард

279
Комментарии: 1936Публикации: 197Регистрация: 13-12-2015

10 комментариев на «Урок 38. Ардуино-контроллер элемента Пельтье. Структура программы. Измерение выходных параметров контроллера.»

  1. Эдуард,добрый день.
    Очень понравился алгоритм структуры программы обработки прерывания по таймеру.К сожалению у меня не работает на плате Мега библиотека {MsTimer2.h}.Как адаптировать библиотеку под Мега?

    0
    • Здравствуйте! Я не знаю. У меня Меги нет. Вроде она должна работать. Посмотрите в Интернете. Попробуйте использовать библиотеку для таймера 1 TimerOne, в уроке 29 она описана.

      0
  2. У меня вот такая ошибка прикомпиляции
    exit status 1
    Ошибка компиляции для платы Arduino/Genuino Uno.
    Подскажите пожалуйста как исправить?

    0
  3. теперь проблема другая)
    без нагрузки показывает
    =4.15 I=0.41 P=4.94 p=24.79
    U=4.15 I=0.41 P=4.94 p=24.93
    U=4.17 I=0.43 P=5.20 p=24.87
    U=4.15 I=0.41 P=4.94 p=24.92
    а под нагрузкой

    U=0.81 I=0.39 P=4.68 p=38.05
    U=0.81 I=0.41 P=4.94 p=38.04
    U=0.81 I=0.43 P=5.20 p=38.03
    U=0.81 I=0.41 P=4.94 p=38.04
    и так постоянно
    В чем может быть проблема?

    0
  4. Объясните пожалуйста, почему сброс watchdog выполняется в обработчике прерывания таймера 2? Ведь если глюк произойдёт в цикле loop, таймера-счётчик во многих случаях всё равно будет работать и инициирует прерывание, где watchdog будет обнулён и всё вернётся в исходную глючную точку.

    0
    • Здравствуйте!
      Это решает разработчик программы. Я уже точно не помню, но скорее всего, моя логика была такой. Основной цикл зависнуть не может. Он всегда что-то проверяет, меняет ветки, но доходит до конца. А вот зависание прерывания по таймеру более вероятно: неправильно вышли из прерывания, сбойнул флаг разрешения прерывания и т.п. Поэтому я и разместил сброс сторожевого таймера в обработчике прерывания по таймеру.

      0

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".