Термостат с PWM и индикацией

 
    18.12.2021

Причины разработки новой версии

    Главный недостаток предыдущей версии было резкое включение мощного вентилятора. Из-за высокого уровня постороннего шума при работе мощного вентилятора на малых оборотах, мне пришлось добавить постоянно работающий тихий маломощный вентилятор. После перебора нескольких видов вентиляторов с алиэкспресс (рассматривались только модели 120*120*38) нашёлся супервентилятор San Ace 9SG1212P1G06:

Цена порядка 800 рублей, корпус из алюминия. Из его минусов то, что при PWM 0% его обороты 1000, в идеале бы хотелось 800, тогда это было бы вообще идеальное решение. Главное, что он издает минимум шума на малых оборотах. Вентиляторы известных брендов из местных магазинов не рассматривались, так как подобные решения как правило толщиной 25 мм, и при использовании противопылевых фильтров они менее эффективны в сравнении с вентиляторами 1,5 дюйма, или 38 мм.

   

Проблемы c (не)запуском скетча Digispark

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

    Под подключение tm1637 я выделил под неё выводы 1 и 3. С первым проблем не было, а вот 3й используется при подключении к USB. В итоге при подключении к разным источникам питанияс присоединенным tm1637 скетч иногда зависал при включении и не стартовал. Причина в том, что digispark 4 секунды ждёт подключения к компьютеру, а tm1637 данные не только принимает, но и передаёт. В итоге загрузчик думает, что его сейчас прошивать будут и не запускает основной код. Решение логическая развязка:

С одним транзистором выйдет инверсный сигнал, это поправляется в коде, если поправить невозможно, то нужна схема с двумя транзисторами. Со стороны входа полевой транзистор выглядит как конденсатор, соответственно никакого сигнала от нагрузки обратно не передается. У меня на момент локализации проблемы не обнаружилось маломощного транзистора, а с мощным полевым транзистором, управляемым digispark на частоте порядка 400 кГц выходной сигнал получался таким:
Емкость входа очень большая и digispasrk не способен управлять мощным полевым транзистором на высоких частотах. В итоге вместо схемы с двумя транзисторами применил драйвер tc4420, но это экономически мягко говоря не очень целесообразно, его цена при мелком опте с китая менее 50 рублей, в чип и дип стоит более 100 руб. штука.

    Следующая проблема запуска digispark в том, что на время ожидания прошивки - 4 сек - его выходы находятся в подвешенном состоянии, не притянуты ни к земле, ни к 5 Вольтам. В чём проявилась проблема - вентиляторы крутятся на минимальных оборотах при входе PWM, притянутом к земле, в результат при включении вентиляторы за 4 сек пытались раскрутиться до максимума, что немного шумновато :) Решение та же логическая развязка. Сперва попробовал вариант с инверсией. К тому времени пришли с китая маломощные полевики 2sk3019, попробовал собрать на нём:

Однако вариант с инверсией не прокатил, по итогу поставил вторую tc4420. Дочего мелкий транзистор, это пипец 1.6 * 1.6 мм ...

    Ещё была проблема, что tm1637 не работал, пока я с платы digispark не выпаял резистор подтягивающий выход 3 к 5 Вольтам, но это ошибочный путь, более правильный логическая развязка.

   

Хард

    Поскольку планировалось ставить несколько мощных вентиляторов 4 шт * 12 Вольт * 4 Ампера = 192 Ватта :) силовой части пришлось уделить дополнительное внимание. Да конечно, большую часть времени вентиляторы работают практически на ХХ, но если вдруг приспичит развить максимальную мощность не хотелось бы фейерверков. Начал с разьёма подключения вентилятора, заказал тройники с алиэкспресса:

Вот такой при токе в 4 Ампера сильно греется, сфоткал на тепловизор, но лень фотку искать. Пришлось в днс купить разветвитель для вентиляторов, где жила цельная.

    Вместо подключения питания от SATA разъёма, который сам по себе небольшого сечения, да ещё и бюджет линии БП не резиновый, взял провод подачи питания на видеокарту 6 pin. Разъём можно или взять с б\у видеокарты с авито, или закать на али:

Разъемы расположены по бокам от входного разъёма, чтобы снизить нагрузку на дорожки на плате. В итоге теперь при желании все 4 вентилятора могут работать на полную мощность без каких-либо ограничений и слабых мест.

    Схема выглядит следующим образом:

Драйверы были добавлены по ходу отладки платы, оптимально конечно применить схему логической развязки на двух маломощных полевиках. В случае выхода на tm1637 можно и с инверсией на одном транзисторе, тогда в коде надо будет по соответствующей линии поменять 1 и 0.

    Плата до добавления двух tc4420 выглядела так:

В собранном виде:
Для корпуса взял фрагмент dvd бокса, плату к нему примотал термоскотчем:

    При практической эксплутации индикатора на максимальной яркости плата digispark сильно грелась, причина была в высокой нагрузке на преобразователь 12 Вольт -> 5 Вольт, отчего пришлось программно убавить яркость индикатора. В общем при большом количестве потребителей 5 Вольт желательно предусмотреть отдельную линию.

   

Софт

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

#include <core_adc.h>

#define CLK 3
#define DIO 1
#define tm1637bit(b) (1 << (b))
#define _dash 0x40 //-
#define _degree 0x63
#define _low_degree 0x5c

#define avr_iterations_pow2  12 //1 = 2, 10 = 1024

const byte tm1637translator[17] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
	0x7f, 0x6f, 0x77, 0x7f, 0x39, 0x3f, 0x79, 0x71, 0};

const unsigned char lm19translator[380] = { 254, 254, 254, 254, 254, 254, 254, 254,
 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 253,
 252, 251, 251, 250, 249, 248, 248, 247, 246, 245, 244, 244, 243, 242, 241, 241,
 240, 239, 238, 237, 237, 236, 235, 234, 233, 233, 232, 231, 230, 229, 229, 228,
 227, 226, 226, 225, 224, 223, 222, 222, 221, 220, 219, 218, 218, 217, 216, 215,
 214, 214, 213, 212, 211, 211, 210, 209, 208, 207, 207, 206, 205, 204, 203, 203,
 202, 201, 200, 199, 199, 198, 197, 196, 195, 195, 194, 193, 192, 191, 191, 190,
 189, 188, 187, 187, 186, 185, 184, 183, 183, 182, 181, 180, 179, 179, 178, 177,
 176, 175, 175, 174, 173, 172, 171, 171, 170, 169, 168, 167, 167, 166, 165, 164,
 163, 163, 162, 161, 160, 159, 159, 158, 157, 156, 155, 155, 154, 153, 152, 151,
 150, 150, 149, 148, 147, 146, 146, 145, 144, 143, 142, 142, 141, 140, 139, 138,
 138, 137, 136, 135, 134, 133, 133, 132, 131, 130, 129, 129, 128, 127, 126, 125,
 125, 124, 123, 122, 121, 120, 120, 119, 118, 117, 116, 116, 115, 114, 113, 112,
 111, 111, 110, 109, 108, 107, 107, 106, 105, 104, 103, 102, 102, 101, 100, 99,
 98, 98, 97, 96, 95, 94, 93, 93, 92, 91, 90, 89, 88, 88, 87, 86, 85, 84, 84, 83,
 82, 81, 80, 79, 79, 78, 77, 76, 75, 74, 74, 73, 72, 71, 70, 69, 69, 68, 67, 66,
 65, 64, 64, 63, 62, 61, 60, 59, 59, 58, 57, 56, 55, 54, 54, 53, 52, 51, 50, 49,
 49, 48, 47, 46, 45, 44, 44, 43, 42, 41, 40, 39, 39, 38, 37, 36, 35, 34, 34, 33,
 32, 31, 30, 29, 28, 28, 27, 26, 25, 24, 23, 23, 22, 21, 20, 19, 18, 18, 17, 16,
 15, 14, 13, 12, 12, 11, 10, 9, 8, 7, 7, 6, 5, 4, 3, 2};
byte old_pwm;

void CLKDIO11(){
//    *(byte*)0x38 |= tm1637bit(CLK) + tm1637bit(DIO);
    CLK1();
    DIO1();
}

void CLK1(){//inverse
    *(byte*)0x38 |= tm1637bit(CLK);
}

void DIO1(){
    *(byte*)0x38 |= tm1637bit(DIO);
}

void CLK0(){//inverse
    *(byte*)0x38 &= 255 - tm1637bit(CLK);
}

void DIO0(){
    *(byte*)0x38 &= 255 - tm1637bit(DIO);
}

void tm1637send(byte v){
  for(byte i = 0; i < 8; i++){
    CLK0();
    if( v & 1 ){
      DIO1();
    }else{
      DIO0();
    }
    
    v >>= 1;
    CLK1();
  }
  
  CLK0();
  CLKDIO11();
  DIO0();
}

void tm1637out(byte tablo[4], byte howmuch){
  CLKDIO11();
  DIO0();
  CLK0();

  tm1637send(    0xc0);
  tm1637send(tablo[0]);
  tm1637send(tablo[1]);
  tm1637send(tablo[2]);
  tm1637send(tablo[3]);
  
  DIO0();
  CLK0();  
  CLKDIO11();
  
  CLKDIO11();
  
  CLKDIO11();
  DIO0();
  CLK0();
  
  tm1637send(0x88 + (howmuch & 7));
  
  DIO0();
  CLK0();
  CLKDIO11();
  
  CLKDIO11();
}

void setup() {
  pinMode(0, OUTPUT);
  digitalWrite(0, LOW);//pwm
  pinMode(CLK, OUTPUT);
  pinMode(2,  INPUT);//t in
  pinMode(DIO, OUTPUT);
  pinMode(4,  INPUT);//t out

  byte tablo[4] = {_dash, _dash, _dash, _dash};
  tm1637out(tablo, 7);
}

void loop() {
  int t_in, t_out, diff;
  word new_pwm;
  long avr_t_in, avr_t_out;

  avr_t_in = 0;
  avr_t_out = 0;

  for(int c = 0; c < (1 << avr_iterations_pow2); c++){

    ADC_SetInputChannel((adc_ic_t) 1);
    ADC_StartConversion();
    while( ADC_ConversionInProgress() );
    avr_t_in += ADC_GetDataRegister();
  
    ADC_SetInputChannel((adc_ic_t) 2);
    ADC_StartConversion();
    while( ADC_ConversionInProgress() );
    avr_t_out += ADC_GetDataRegister();
  }

  t_in  = avr_t_in  >> avr_iterations_pow2;
  t_out = avr_t_out >> avr_iterations_pow2;

  if( t_in  >= 379 ){ t_in  = 379; }
  t_in = lm19translator[t_in];

  if( t_out >= 379 ){ t_out = 379; }
  t_out = lm19translator[t_out];

  diff = (t_out - t_in);

  if( diff <= 20 ){
    new_pwm = 0;
  }else{
    if( diff >= 70 ){
      new_pwm = 100;
    }else{
      new_pwm = diff - 20;
      new_pwm = new_pwm << 1;
    }
  }

  if( new_pwm >= 100 ) new_pwm = 254;

  if( new_pwm < old_pwm ){
    new_pwm = old_pwm - ((old_pwm - new_pwm) >> 3) - 1;
  }

  analogWrite(0, new_pwm);
  old_pwm = new_pwm;

  byte tablo[4] = {0, 0, 0, 0};
  word x = 0;
  byte x100 = 0, x10 = 0;

  x = t_in;
  if( (x & 1) > 0 ){  tablo[2] = tm1637translator[5]; }
  else{               tablo[2] = tm1637translator[0]; }
  x >>= 1;
  x100 = x10 = 0;
  if( x > 999 ) x = 999;
  while( x >= 100 ){ x -= 100; x100++; } if( x100 == 0 ) x100 = 17;
  while( x >=  10 ){ x -=  10;  x10++; }
  tablo[0] = tm1637translator[x10];
  tablo[1] = tm1637translator[x] + 128;
  tablo[3] = _low_degree;  
  tm1637out(tablo, 5);
  delay(1000);

  x = t_out;
  if( (x & 1) > 0 ){  tablo[2] = tm1637translator[5]; }
  else{               tablo[2] = tm1637translator[0]; }
  x >>= 1;
  x100 = x10 = 0;
  if( x > 999 ) x = 999;
  while( x >= 100 ){ x -= 100; x100++; } if( x100 == 0 ) x100 = 17;
  while( x >=  10 ){ x -=  10;  x10++; }
  tablo[0] = tm1637translator[x10];
  tablo[1] = tm1637translator[x] + 128;
  tablo[3] = _degree;
  tm1637out(tablo, 5);
  delay(1000);

  tablo[0] = 0;
  x = new_pwm;
  x100 = x10 = 0;
  if( x > 999 ) x = 999;
  while( x >= 100 ){ x -= 100; x100++; } if( x100 == 0 ) x100 = 17;
  while( x >=  10 ){ x -=  10;  x10++; }
  
  tablo[1] = tm1637translator[x100];
  tablo[2] = tm1637translator[x10];
  tablo[3] = tm1637translator[x];
  tm1637out(tablo, 5);
}

    Пояснения по коду - отражается температура на входе, потом на выходе, затем уровень PWM сигнала, доли от 256. Алгоритм расчета уровня PWM - вычитаем из выходной температуры входящую, из разницы вычитаем 10, если больше 0, то умножаем на 4. Замер температуры идёт с максимальной точностью - 0.5 градуса. Из приколов сперва мерял температуру один раз, разброс был до трёх градусов:

Затем вместо одного замера и бесполезной паузы в 1 секунду стал мерять 4096 раз и брать среднее арифметическое от результата :).

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

    По итогу устройство выполняет все необходимые функции и при необходимости легко перепрошивается под новые условия.

   

Фотогалерея