Блок термоконтроля для электронной сигареты на Arduino

от автора

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

Схема предназначена для работы с батареей для RC-моделей, из двух Li-ячеек. Мной был выбран аккумулятор 500 mAh 25C напряжение 7.4V. Ток и нагрузочную способность можно варьировать в широких пределах, лишь бы выдавал достаточный ток с холодной спиралью, когда ее сопротивление минимально.


Теоретически, можно переделать и под аккумулятор с одной ячейкой, но нужно подбирать соответствующие MOSFET-ключи — с низким напряжением переключения и сопротивлением в открытом состоянии. Можно будет даже убрать делители R1+R2 и R3+R4, повысив точность измерения в два раза (хотя реального эффекта будет немного).

Резисторы R5 и R6 — должны быть мощностью не менее 0.25 ватт (а лучше и больше). Выход LED — для пары белых светодиодов, чтобы мог работать как аварийный фонарик.

Отсутствует аппаратный выключатель, что приводит к довольно быстрому разряду батареи (за день или около того), несмотря на спящий режим. То ли я его неправильно использую, то ли ардуинка с регуляторами напряжения в ней и экране — потребляют слишком много даже в неактивном состоянии. Надо было, наверное, поставить еще один MOSFET-ключ на всю схему, с маленьким переключателем, для включения/отключения напряжения питания.

Корпус был напечатан на 3D принтере из PLA. Пустое место под разъемом атомайзера — для проводов батарейки, их на модельных аккумуляторах делают довольно длинными, а обрезать не хотелось.

// Temperature controller for cigarette, schematics in ard-smoke.sch // //    Copyright (C) 2015  Vasim V. // //    This program is free software: you can redistribute it and/or modify //    it under the terms of the GNU General Public License as published by //    the Free Software Foundation, either version 3 of the License, or //    (at your option) any later version. // //    This program is distributed in the hope that it will be useful, //    but WITHOUT ANY WARRANTY; without even the implied warranty of //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the //    GNU General Public License for more details. // //    You should have received a copy of the GNU General Public License //    along with this program.  If not, see <http://www.gnu.org/licenses/>.  #include <U8glib.h> #include <MsTimer2.h> #include <TimerOne.h> #include <EEPROM.h> #include <avr/sleep.h>  // Additional info to console // #define DEBUG  // Temperature ADC pin - ITEMP #define P_ITEMP 0 // Battery ADC pin - CHECK #define P_VMAIN 1 // Coil N-FET gate - OOn #define P_OON 9 // Temperature check N-FET gate - OTest #define P_OTEST 3  #define NBUTTONS 3 // Coil button #define P_BCOIL 10 // Plus button */ #define P_BPLUS 11 // Minus button */ #define P_BMINUS 12  // Delay for FET change, microseconds #define FETSW_DELAY 10  // Interrupt period (timer2), microseconds #define TIMER_PERIOD 10000  // PWM Period, microseconds #define PWM_PERIOD 1024  // Test resistor resistance, in Ohm #define RTEST 4.7f  // Temparture-resistance koefficient // Titan #define RTCHANGE 0.0035f // Kantal - not working (too low resolution) // #define RTCHANGE 0.0001f  // Default zero (when no difference between rcoil_zero and rcoil) temperature #define TEMP_ZERO 25  // Sleep timer (in timer2 cycles, seconds*100), activate if coil button was pressed for #define SLEEP_TIME 12000  // How long you should keep button pressed to change temperature, in time2 cycles (seconds*100) #define BUTTON_SENS_TIME 100  #define PID_P 60.0 #define PID_I 0.75 #define PID_D 20.0  U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_NO_ACK|U8G_I2C_OPT_FAST);  // Coil is on or not boolean coil_on; // Voltage readings from ADCs int coilv; int vmainv; // Calculated from vmainv, in volts float vbat; // Calculated from coilv, in volts and ohms float voff; float rcoil;  // Coil resistance at 25C float rcoil_zero = 0.0f;  // Current temperature in celsius double tcur;  // Cut-off temperature in celsius double tcut;  // PID regulator stuff double Output;  // Buttons debounce stuff #define I_BCOIL 0 #define I_BPLUS 1 #define I_BMINUS 2 uint8_t pbuttons[NBUTTONS] = {P_BCOIL, P_BPLUS, P_BMINUS}; uint8_t sw_check[NBUTTONS];  // Buttons state after debouncing boolean sbuttons[NBUTTONS] = {false, false, false};  // How long buttons state wasn't changed (in timer2 cycles) uint32_t idlebuttons[NBUTTONS] = {0, 0, 0};  // Ignore if buttons were pressed at startup boolean ignore_buttons = false;  // In deep sleep boolean sleeping;  double errSum, lastErr; double outputLast = 0.0; #define MAX_CHANGE 1023.0  void MyPIDStart() {   errSum = lastErr = outputLast = 0.0;   MyPIDCompute(); }  // Calculate output void MyPIDCompute() {    double timeChange = TIMER_PERIOD / 1000;    double error = tcut - tcur;    errSum += (error * timeChange);    double dErr = (error - lastErr) / timeChange;    double preOutput;       /*Compute PID Output*/    preOutput = PID_P * error + PID_I * errSum + PID_D * dErr;    if ((preOutput - outputLast) > MAX_CHANGE)      preOutput = outputLast + MAX_CHANGE;    else if ((outputLast - preOutput) > MAX_CHANGE)      preOutput = outputLast - MAX_CHANGE;       Output = preOutput - outputLast;    if (Output < 0)      Output = 0;       /*Remember some variables for next time*/    outputLast = preOutput;    lastErr = error; }  // Calculate coil resistance and temperature void update_temp() {   rcoil = (vmainv * RTEST) / (coilv * 1.0f + 1) - RTEST;   if (rcoil < 0)     rcoil = 0;   if (rcoil_zero > 0.01)      tcur = double((rcoil / rcoil_zero - 1) / RTCHANGE);   else     tcur = 0; }  void eeprom_writef(int addr, float x) {   uint8_t i;    for (i = 0; i < sizeof(float); i++) {     EEPROM.update(addr + i, *((char *) &x + i));   } }  float eeprom_readf(int addr) {   float x;   int i;    for (i = 0; i < sizeof(float); i++) {     *((char *) &x + i) = EEPROM.read(addr + i);   }   // Test if we did read float really, not "NAN"   if ((x+1.0) > x)     return x;   return 0; }  // Timer interrupt - Test buttons, check temperature and fire coil, etc void intcallback() {   uint8_t i;  // Debounce buttons   for (i = 0; i < NBUTTONS; i++) {     if (sbuttons[i] != (digitalRead(pbuttons[i]) == LOW)) {       if (sw_check[i] > 5) {         sw_check[i] = 0;         sbuttons[i] = (digitalRead(pbuttons[i]) == LOW);         idlebuttons[i] = 0;       } else {         sw_check[i]++;         idlebuttons[i]++;       }     } else {       sw_check[i] = 0;       idlebuttons[i]++;     }   }      // Measuring temperature - turning off main coil FET before   if (coil_on || ignore_buttons) {     Timer1.disablePwm(P_OON);     digitalWrite(P_OON, LOW);     delayMicroseconds(FETSW_DELAY);   }      // Don't do anything else   if (sleeping || ignore_buttons)     return;        digitalWrite(P_OTEST, HIGH);   delayMicroseconds(FETSW_DELAY);   coilv = analogRead(P_ITEMP);   vmainv = analogRead(P_VMAIN);   digitalWrite(P_OTEST, LOW);   delayMicroseconds(FETSW_DELAY);   // Measuring is done, turning on coil back   if (coil_on)     Timer1.pwm(P_OON, Output, PWM_PERIOD);   else {     Timer1.disablePwm(P_OON);     digitalWrite(P_OON, LOW);   }     // Calculate temperature   update_temp();      if (coil_on)     // PID processing (calculate output)     MyPIDCompute();  #ifdef DEBUG   Serial.print("Temp: ");   Serial.print(tcur+TEMP_ZERO);   Serial.print("C, rcoil: ");   Serial.print(rcoil);   Serial.print("Ohm, Output: ");   Serial.println(Output); #endif   // Fire coil if button pressed   if (!coil_on && sbuttons[I_BCOIL] && !ignore_buttons && (tcut >= tcur)) {     MyPIDStart();     coil_on = true;     Timer1.pwm(P_OON, Output, PWM_PERIOD);   }    // Coil shutdown if button is released or more than 50 celsius above   if (!sbuttons[I_BCOIL] || ((tcur - 5) > tcut)) {     coil_on = false;     Output = 0;     Timer1.setPwmDuty(P_OON, 0);     Timer1.disablePwm(P_OON);     digitalWrite(P_OON, LOW);   } }  void setup() {   Serial.begin(115200);    ignore_buttons = true;   sleeping = false;   // ADC pins   pinMode(P_ITEMP, INPUT);   pinMode(P_VMAIN, INPUT);   // Coil/check FETs   pinMode(P_OON, OUTPUT);   digitalWrite(P_OON, LOW);   coil_on = false;   pinMode(P_OTEST, OUTPUT);   digitalWrite(P_OTEST, LOW);   // Configuring buttons pins   pinMode(P_BCOIL, INPUT_PULLUP);   pinMode(P_BPLUS, INPUT_PULLUP);   pinMode(P_BMINUS, INPUT_PULLUP);    // Display init   // Rotate if needed   // u8g.setRot180();   // From u8g_lib example - assign default color value   if ( u8g.getMode() == U8G_MODE_R3G3B2 ) {     u8g.setColorIndex(255);     // white   }   else if ( u8g.getMode() == U8G_MODE_GRAY2BIT ) {     u8g.setColorIndex(3);         // max intensity   }   else if ( u8g.getMode() == U8G_MODE_BW ) {     u8g.setColorIndex(1);         // pixel on   }   else if ( u8g.getMode() == U8G_MODE_HICOLOR ) {     u8g.setHiColorByRGB(255,255,255);   }    // Read coil resistance at 25C from eeprom, update current values //  rcoil_zero = eeprom_readf(0);     tcut = eeprom_readf(4);   if ((tcut < 100.0) || (tcut > 300.0))     tcut = 180;    // Disable buttons until released if any of it pressed at startup   if ((digitalRead(P_BPLUS) == LOW) || (digitalRead(P_BMINUS) == LOW) || (digitalRead(P_BCOIL) == LOW)) {     int i;          ignore_buttons = true;     for (i = 0; i < NBUTTONS; i++)       sbuttons[i] = (digitalRead(pbuttons[i]) == LOW);   } else     ignore_buttons = false;    // Timers initialize   MsTimer2::set(TIMER_PERIOD / 1000, intcallback);   MsTimer2::start();      Timer1.initialize(1000);   Timer1.pwm(9, 0, PWM_PERIOD); }  static int loopcycle = 0; static int showtemp; static float showrcoil;  void draw() {   char tmpbuf[16];    if (!sleeping && idlebuttons[I_BCOIL] > SLEEP_TIME)     return;        u8g.setFont(u8g_font_helvR12);    memcpy(tmpbuf, "Batt", 4);   if (vmainv < 1022) {     dtostrf(vbat, 4, 1, tmpbuf+4);     memcpy(tmpbuf+8, "V - ", 4);   } else     memcpy(tmpbuf+4, " MAX", 5);    if (vbat > 6.3)      memcpy(tmpbuf+12, "OK", 3);   else {     if (loopcycle %  2)       u8g.setFont(u8g_font_helvB12);     memcpy(tmpbuf+12, "LOW!", 5);   }   u8g.drawStr(0, 64, tmpbuf);   u8g.setFont(u8g_font_helvR12);      if (sbuttons[I_BCOIL])     u8g.drawStr(90, 16, "C");   else     u8g.drawStr(90, 16, ".");    if (sbuttons[I_BPLUS])     u8g.drawStr(99, 16, "+");   else     u8g.drawStr(99, 16, ".");    if (sbuttons[I_BMINUS])     u8g.drawStr(108, 16, "-");   else     u8g.drawStr(108, 16, ".");    memcpy(tmpbuf, "= ", 3);   dtostrf(tcut+25.0f, 4, 0, tmpbuf+2);   u8g.drawStr(0, 48, tmpbuf);      tmpbuf[0] = 'R';   dtostrf(rcoil, 5, 2, tmpbuf+1);   memcpy(tmpbuf+6, "Ohm", 4);   u8g.drawStr(0, 32, tmpbuf);      dtostrf(float(showtemp), 4, 0, tmpbuf);   tmpbuf[4] = 'C';   tmpbuf[5] = '\0';   u8g.drawStr(18, 16, tmpbuf);   if (coil_on)     u8g.drawStr(0, 16, ">>");   else     u8g.drawStr(0, 16, "  ");    dtostrf(float(Output), 5, 0, tmpbuf);   u8g.drawStr(64, 48, tmpbuf); }  // To test if minus button was pressed 3 times in 6 seconds - exit sleep mode boolean last_b_state; int nchanges; int ncycles;  // Update EEPROM with temperature limit  boolean update_eeprom = false;  void loop() {   int i;      vbat = (10.0 * vmainv) / 1024.0;   voff = (10.0 * coilv) / 1024.0;   showtemp = tcur + TEMP_ZERO;    // Redraw screen   if (!sleeping) {     // u8g_lib picture loop     u8g.firstPage();       do {       draw();     } while( u8g.nextPage() );   }    // Change temperature limit if MINUS or PLUS pressed   if (!ignore_buttons) {     if (sbuttons[I_BMINUS] && idlebuttons[I_BMINUS] > BUTTON_SENS_TIME) {       tcut = tcut - 5;       idlebuttons[I_BMINUS] = 0;       update_eeprom = true;     }     if (sbuttons[I_BPLUS] && idlebuttons[I_BPLUS] > BUTTON_SENS_TIME) {       tcut = tcut + 5;       idlebuttons[I_BPLUS] = 0;       update_eeprom = true;     }   }      // Sleep after turning off screen   if (!sleeping && (idlebuttons[I_BCOIL] > SLEEP_TIME)) {     sleeping = true;     ignore_buttons = true;     u8g.sleepOn();   }    // Check if should wake up (MINUS button pressed few times)   if (sleeping) {     ncycles++;     if (last_b_state != sbuttons[I_BMINUS]) {       ncycles = 0;       last_b_state = sbuttons[I_BMINUS];       nchanges++;       if (nchanges >= 6) {         rcoil_zero = 0;         sleeping = false;         u8g.sleepOff();         for (i = 0; i < NBUTTONS; i++)           idlebuttons[i] = 0;       }     } else {       if (ncycles > 20) {         ncycles = 0;         nchanges = 0;       }     }   }      // Check if all buttons are released   if (ignore_buttons && !sleeping) {     int i;     boolean pressed = false;      for (i = 0; i < NBUTTONS; i++) {       if (sbuttons[i])         pressed = true;     }     if (!pressed)       ignore_buttons = false;   }   loopcycle++;    //  Serial.println(vmainv);   if (sleeping) {     ignore_buttons = true;     set_sleep_mode(SLEEP_MODE_PWR_SAVE);     sleep_enable();   }   if (((rcoil_zero < 0.01) && (rcoil > 0.01)) || (showtemp < -50)) {     rcoil_zero = rcoil; //    update_eeprom = true;   }    if (update_eeprom) {     eeprom_writef(0, rcoil_zero);     eeprom_writef(4, tcut);     update_eeprom = false;   }   delay(333); } 

ссылка на оригинал статьи http://geektimes.ru/post/268872/


Комментарии

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

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