Паяльная станция из ардуино

Dm77
Offline
Зарегистрирован: 18.07.2015

a5021 пишет:

Vadim4eG пишет:
Да, безусловно, как посадить катоды на землю прекрасно понимаю, а будет ли так работать, имею ввиду без програмной смены полярности на выходах мк (порты 0-7) ?

Скетч править -- это само собой. Сейчас глянул, всего придется вносить изменения в три строки. Одна строка (номер 207) в обработчике прерывания COMPA, и две (261-262) в COMPB. Смысл простой -- там, где для общего анода бит выставляется, для общего катода он должен сбрасываться и наоборот.

Dm77 пишет:
Ребят. схема протестирована.

А получилось что-то с экранированием?

Не стал городить поддоны. В принципе кроме как дрожания символов паяние не страдает. А это главное. Думаю просто вынести плату в отдельный маленький корпус (благо не греется ничего) и питаль от ноутбучного БП.

a5021
Offline
Зарегистрирован: 07.07.2013

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

dobrman
Offline
Зарегистрирован: 19.01.2015

Подскажите какой мощности резисторы в цепи управления нагревателем фена в схеме ? - http://arduino.ru/forum/proekty/payalnaya-stantsiya-iz-arduino?page=1#comment-86493

keefa
Offline
Зарегистрирован: 19.06.2015

Я поставил 0,125Вт, работает. Цвет не поменяли, рукой подлезть потрогать не подлезть. Эти сопротивления работают в короткий промежуток времени , пока не включится симистор, а это происходит в начале полупериода. Так что все гут!

a5021
Offline
Зарегистрирован: 07.07.2013

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

Сверху от паялки можно наблюдать плату преобразователя (DC-DC) 12 вольт на 24 вольта. Так как питание от настольного компа у меня основное, а после замеров скорости нагрева выяснилось, что разогрев с 12 вольт получается долгий, то решил дополнить устройство бустером:

Данная повышайка собрана на контроллере UC3843 по схеме близкой к референсной. Дроссель намотан на кольце со старой материнской платы. Материал №52, цвет салатовый с синим, размеры 15,5 х 8,5 х 6 мм. Использовался сложенный вдвое провод диаметром 0,5мм, который наматывался без предварительного расчета из соображений "сколько влезет". Измеренная индуктивность в итоге составила 22мкГн, что вполне устроило.  Изначально были представления, что кольцо должно быть существенно больше размерами, т.к. планировалось снимать с него более 50 Ватт мощности, но эксперимент показал, что и это двольно компактное кольцо вполне работоспособно при нагреве до 50-60 градусов во время работы.

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

Мосфет с допустимым током до 80 ампер выдран с той же материнки. Большинство деталей надерганы с плат из ящика с радиохламом. В общем, получилось довольно бюджетно и приемлемо по параметрам. Если у кого-то возникнет желание повторить, могу выложить схему и чертеж платы.

shakalby
Offline
Зарегистрирован: 26.04.2014

Будьте добры выслать схему и чертеж на мыло shakalby@mail.ru

Спасибо большое заранее!!!

a5021
Offline
Зарегистрирован: 07.07.2013

Проще будет прямо здесь разместить. Вот схема:

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

Полуваттные резисторы R6-R9 включены в параллель, чтобы набрать общее сопротивление 0.025 ома. Единичных сопротивлений столь малой величины среди запасов и в продаже отыскать не удалось, т.ч. пришлось параллелить то, что оказалось доступно. В принципе, там можно поставить одно сопротивление 0.025 ома мощностью 1-2 ватта. Столь экзотический резистор в схеме присутствует для ограничения максимального тока через ключ. Если поставить резистор с большим номиналом, то преобразователь будет давать просадку напряжения при пиковых нагрузках, если номинал выбрать ниже, то увеличится нагрузка на источник.

Диод D2 -- это Шоттки с рабочим током не ниже 3 ампер (например, B340, SS34, B34, SK34 и т.п.). На самом деле пиковые нагрузки на этот диод в данной схеме существенно выше, но у большинства производителей данные приборы держат в пике по 80-120 ампер, т.ч. их использование вполне тут оправдано. В принципе, величина тока в большинстве случаев ограничивается тепловыделением кристала и если обеспечить должный теплоотвод, то рабочий ток можно смело задирать.

Мосфет лучше выбирать с минимальным значением сопротивления канала в открытом состоянии (Rds) и на напряжение не ниже 30 вольт. Хорошо себя показали мощные мосфеты с материнских плат.

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

Выходное напряжение определяется величинами R10, R11+R12 в резиситвном делителе. Можно их рассчитать по формуле V(out) = 2.5 * (1 + R10/(R11 + R12)), но я как-то привык пользоваться онлайн-калькулятором. Поле "Input Voltage" оставляем пустым, в поле "Output voltage" заносим константу 2.5, а R1 и R2 -- это R10 и R11+R12 с вышеприведенной схемы. "Играя" значениями в полях R1 и R2 можно подобрать номиналы делителя на желаемое выходное напряжение.

Если вникать в тонкости схемы нет желания, а просто хочется повторить устройство, то вот вариант схемы и платы в формате протеуса. Размеры платы специально сделаны попросторнее, т.к. пустые участки меди служат радиатором, отводя тепло от греющихся элементов -- диода и мосфета.

 

Andrey123m
Offline
Зарегистрирован: 17.09.2015

Здравствуйте. Собрал себе паяльную станцию по проекту a5021 (за что ему отдельное большое спасибо). Споткнулся на калибровке. Не могу понять, как (или где) увидеть значения  getOversampled(). Может я задаю глупый вопрос, но до этого проекта с ардуиной не имел дела. А после прочтения форума появилось желание для начала просто повторить. Поэтому, если не трудно разъясните более подробно. И после того, как я выясню значения  getOversampled(), нужно заново заливать скетч в ардуину?

a5021
Offline
Зарегистрирован: 07.07.2013

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

О том, как проще выяснить, что там getOversampled() возвращает: мне в голову не пришло ничего лучше, как сделать довольно грубый хак, чтобы скетч показывал эти значения на основном индикаторе. Это не самое лучшее решение, но зато быстрое. В общем, если раскомментировать определение CALIBRATION_BUILD в тексте скетча, что я прикладываю ниже, то после его сборки и заливки паялка потеряет почти весь свой функционал, перестанет включать нагреватель вообще, но зато будет показывать на дисплее три последние цифры значения, возвращаемого функцией getOversampled(). Старшая цифра без сильного нагрева всегда 1, а если греть дальше, то можно отследить смену старшего разряда по переходу значений на индикаторе через ноль.

Тут подумалось, что для калибровки, возможно, удобно будет воспользоваться медицинским термометром, как прибором достаточно точным в рамках некоторого диапазона. Процедура может быть следующей: заливаем в паялку скетч с раскомментированным определением CALIBRATION_BUILD. Наливаем в некую посудину теплой воды, по температуре близкой к температуре человеческого тела. Посудину лучше чем-нибудь укутать, чтобы быстро не остывала. Раскручиваем паяльник, чтобы нагреватель торчал наружу и помещаем его (нагреватель) в воду. В эту же воду помещаем медицинский термометр. Включаем паялку. Ждем некоторое время, пока все прогреется и записываем значения с термометра и дисплея паялки на бумажку. Выливаем воду и убираем медицинский термометр. Теперь берем другой термометр, с помощью которого можно измерять температуту горячей воды, наливаем в посудину кипяток из чайника и схоожим образом фиксируем новые значения. Переносим числа с бумажки в текст скетча, выносим CALIBRATION_BUILD в комментарии и перезаливаем скетч. Вытираем нагреватель, скручиваем паяльник. Все, паялка откалибрована и готова к работе.

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

/*
**
**      ARDUINO Soldering Station TH v1.21 (c) a5021
**              march - september 2015
**
**  http://arduino.ru/forum/proekty/payalnaya-stantsiya-iz-arduino
**
**  HISTORY
**  =======
**    september 2015 -- version 1.21:
*       - added CALIBRATION_BUILD conditional compilation macro;
**    august 2015 -- version 1.2:
**      - improved temperature keeping stability;
**      - ADC procedure rewritten;
**      - several code improvements;
**    july 2015 -- version 1.02:
**      - fixed some stupid errors in preHeat() pprocedure;
**    july 2015 -- version 1.01:
**      - added preheat procedure;
**    may 2015  -- version 1.00:
**      - first public release
*/

#include <avr/sleep.h>

//#define CALIBRATION_BUILD 1

  // use PREHEAT_ENABLE in case the PSU is not powerful enough
  // this force PWM-mode preheating when soldering iron is cold

#define PREHEAT_ENABLE  1

// ohmic value of the upper resistor in the voltage divider
#define R1   220.0
// external voltage reference (TL431)
#define V_REF  2.495
// calibrated atmega's internal voltage reference
#define REFERENCE_1V1    1.087

/*
**      soldering iron's thermal probe calibration data definitions
**/

#define CALIBRATION_TEMP_LOW         ((unsigned int) 2950)
#define CALIBRATION_TEMP_HIGH        ((unsigned int) 18300)
#define ADC_CALIBRATION_DATA_LOW     ((unsigned int) 1658)
#define ADC_CALIBRATION_DATA_HIGH    ((unsigned int) 2373)

/*
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
*/

  // low temperature degree (multiplied by 100)
#define LOW_TEMP_DEGREE              ((unsigned int)18500)
  // high temperature degree (multiplied by 100)
#define HIGH_TEMP_DEGREE             ((unsigned int)45800)
#define HIGH_TEMP_MARGIN             ((unsigned long int)(HIGH_TEMP_DEGREE - CALIBRATION_TEMP_LOW) * \
                                      (ADC_CALIBRATION_DATA_HIGH - ADC_CALIBRATION_DATA_LOW) \
                                      / (CALIBRATION_TEMP_HIGH  - CALIBRATION_TEMP_LOW) \
                                      + ADC_CALIBRATION_DATA_LOW)
#define LOW_TEMP_MARGIN              ((unsigned long int)(LOW_TEMP_DEGREE - CALIBRATION_TEMP_LOW) * \
                                      (ADC_CALIBRATION_DATA_HIGH - ADC_CALIBRATION_DATA_LOW) \
                                      / (CALIBRATION_TEMP_HIGH  - CALIBRATION_TEMP_LOW) \
                                      + ADC_CALIBRATION_DATA_LOW)

  // min timeout in seconds
#define MIN_TIMEOUT_SEC              15
  // max timeout  in seconds
#define MAX_TIMEOUT_SEC              (60 * 60)
  // low power mode duration in seconds
#define LOW_POWER_MODE_DURATION      (15 * 60)
  // num of beeps before go standby
#define TICK_TOCK_COUNT              10
  // time before the display to be dimmed
#define TIME_BEFORE_DIM              10

#ifndef CALIBRATION_BUILD
    // heater control macros
  #define   TURN_HEATER_ON   sbi(PORTB, PORTB1)
#else
  #define   TURN_HEATER_ON   cbi(PORTB, PORTB1)
#endif
 
#define   TURN_HEATER_OFF  cbi(PORTB, PORTB1)
  // LED signaling
#define   TURN_LED_ON      sbi(PORTB, PORTB5)
#define   TURN_LED_OFF     cbi(PORTB, PORTB5)


  // display brightness constants
#define    MIN_BRIGHTNESS  0xEF
#define    DEFAULT_BRIGHTNESS 0xB0

  // where segment's cathodes are connected to
#define  SEG_A  (1 << PORTD5)
#define  SEG_B  (1 << PORTD7)
#define  SEG_C  (1 << PORTD0)
#define  SEG_D  (1 << PORTD3)
#define  SEG_E  (1 << PORTD4)
#define  SEG_F  (1 << PORTD6)
#define  SEG_G  (1 << PORTD1)
#define  SEG_DP (1 << PORTD2)

  // compose digits using separate segments
#define  SIGN_8  (0xFF - SEG_DP)
#define  SIGN_0  (SIGN_8 - SEG_G)
#define  SIGN_1  (SEG_B + SEG_C)
#define  SIGN_2  (SIGN_8 - SEG_C - SEG_F)
#define  SIGN_3  (SIGN_8 - SEG_E - SEG_F)
#define  SIGN_4  (SIGN_1 + SEG_F + SEG_G)
#define  SIGN_6  (SIGN_8 - SEG_B)
#define  SIGN_5  (SIGN_6 - SEG_E)
#define  SIGN_7  (SIGN_1 + SEG_A)
#define  SIGN_9  (SIGN_8 - SEG_E)
#define  SIGN_BLANK  0

  // compose some letters
#define SIGN_E   (SIGN_6 - SEG_C)
#define SIGN_F   (SIGN_E - SEG_D)
#define SIGN_r   (SEG_G + SEG_E)
#define SIGN_n   (SIGN_r + SEG_E)

  // bit manipulation macros
#ifndef cbi
  #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif

#ifndef sbi
  #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

  // number-to-7seg code translation table
const unsigned char codeTable[] = {~SIGN_0, ~SIGN_1, ~SIGN_2, 
                                   ~SIGN_3, ~SIGN_4, ~SIGN_5, 
                                   ~SIGN_6, ~SIGN_7, ~SIGN_8, 
                                                    ~SIGN_9};
                            
  // signs to be displayed      
volatile unsigned char displayBuffer[] = {~SIGN_0, ~SIGN_F, ~SIGN_F},
                       globalEvent = 0,   // bitset for event handling
                       displayUpdateReq = 0,
                       readPotReq = 0,
                       currentDigit = 0;  // active digit at display
                       
volatile unsigned int  softTimer = 0, // software timer downcounter 
                       dimCounter = TIME_BEFORE_DIM, // dim downcounter
                       toCounter1,        // time before low power mode
                       toCounter2,        // low power mode downcounter
                       toneDuration = 0;  // sound duration counter
                      
/*
**   application's event flags
*/

    // keypress flag                      
#define EVENT_BUTTON_PRESSED    (1<<0)
    // button pressed for a long time
#define EVENT_BUTTON_LONG       (1<<1)
    // request tone generation
#define EVENT_BEEPER_REQ        (1<<2)
    // warming up or cooling down request
#define EVENT_TEMP_CHANGE_REQ   (1<<3)
    // audio alarm request
#define EVENT_TICK_TOCK_REQ     (1<<4)
    // attention request
#define EVENT_ATTN_REQ          (1<<5)
    // low power mode flag
#define EVENT_LOW_POWER_MODE    (1<<6)
    // go standby flag
#define EVENT_STANDBY_MODE      (1<<7)

/*
**  flag manipulation macros
*/

#define SET_EVENT(EVENT)     cli(); globalEvent |= (EVENT); sei()
#define CLEAR_EVENT(EVENT)   cli(); globalEvent &= ~(EVENT); sei()
#define CHECK_EVENT(EVENT)   (globalEvent & (EVENT))


void resetTimeout(unsigned int temperature) {
  unsigned int m;
  // calculate new timeout value based on current temperature
  
  OCR0B = DEFAULT_BRIGHTNESS;                  // restore display brightness
  if (!CHECK_EVENT(EVENT_TEMP_CHANGE_REQ)) {   // check if warming up flag is active
    globalEvent = 0;                           // reset all event flags
  } else {                                     // when warming up or cooling dowm process is active
    globalEvent = EVENT_TEMP_CHANGE_REQ;       // reset all event flags excluding that
  }

  /* the time before go standby and soldering iron's temperature have an inverse relationship:
     the higher termerature is, the less time it is allowed. */

  m = map(temperature, LOW_TEMP_MARGIN, HIGH_TEMP_MARGIN, MAX_TIMEOUT_SEC, MIN_TIMEOUT_SEC);

  cli();
    // set values for timeout variables
  dimCounter = TIME_BEFORE_DIM;
  toCounter1 = m;
  toCounter2 = LOW_POWER_MODE_DURATION;
  sei();
}

ISR (PCINT0_vect)  {
  // dummy interrupt service routine
  // called when one of pins D8 to D13 has changed
}

ISR(TIMER0_COMPA_vect){
  static unsigned char mSec,                      // millisecond counter   
                       quartSec;                  // 1/4 second counter

  PORTC |= (1<<currentDigit);                     // turn off current LED tube
  // ==========================================================================================
  //  Timeout counter routine
  // ==========================================================================================
        
  if (!(globalEvent & EVENT_TEMP_CHANGE_REQ)) {   // count if no TEMP_CHANGE_REQ active
    if (++mSec == 250) {                          // check for millisecond counter overflow
      mSec = 0;                                   // reset msec counter
      if (++quartSec == 4) {                      // check for 1/4 sec counter overflow
        quartSec = 0;                             // reset 1/4 sec counter
        
        // timeout counting
        
        // =======================[ TICK-TOCK Signaling ]==============================================        
        if (dimCounter > 0) if (--dimCounter == 0)  OCR0B = MIN_BRIGHTNESS;  // set display brightness to min.
        
        if (toCounter1 > 0) {            //decrease timout counter till 0
          if (--toCounter1 != 0) {
               // rise tick-tock flag
            if (toCounter1 <= TICK_TOCK_COUNT) globalEvent |= EVENT_TICK_TOCK_REQ;
          } else {
               // rise 'go low power mode' flag
            globalEvent |= EVENT_LOW_POWER_MODE | EVENT_ATTN_REQ;
          }
        } else {                         // in case low power mode is active
          if (toCounter2 > 0) {          // if timeout counter is greater than zero
            if (--toCounter2 != 0) {     // decrease timeout counter
              if (toCounter2 <= TICK_TOCK_COUNT) globalEvent |= EVENT_TICK_TOCK_REQ;
            } else {                     // if timeout counter is equal zero
              globalEvent |= EVENT_STANDBY_MODE;  // rise 'go stamdby' flag
            }
          }
        }
        // ==========================================================================================     
      }
    }
  }
}

ISR(TIMER0_COMPB_vect){
  //
  // displaying data, debouncing the button, tone generation
  //
  
  static unsigned char buttonState,                  // current button state
                       prevButtonState;              // saved button state
                       
  static unsigned int  keypressDuration,             // button press duration
                       mSec;                         // millisecond counter   

  //================================================================================================
  // display routine
  //================================================================================================
  if (++currentDigit == 3) currentDigit = 0;         // for every digit...
  PORTD = displayBuffer[currentDigit];               // send value
  PORTC &= ~(1<<currentDigit);                       // and switch on

  if (softTimer != 0) softTimer--;                   // decrease software timer

  if (currentDigit == 0) {                           // on every 3rd call
  //================================================================================================
  // debouncing routine
  //================================================================================================
    buttonState <<= 1;                               // prepare to get a new button state bit
    if ((PINB & (1<<PINB0)) == 0) buttonState |= 1;  // check the button state and save it
    if (buttonState == 0 || buttonState == 0xFF) {   // check the button pressed or released
      if (buttonState != prevButtonState) {          // check the button state changed
        if (buttonState == 0xFF) {                   // is it pressed?
          globalEvent |= EVENT_BUTTON_PRESSED;       // rise 'button pressed' flag
        }
      }
      if (buttonState == 0xFF) {                     // when the button is pressed
        if ((!(globalEvent & EVENT_STANDBY_MODE))    // and device is not in standby
             && (++keypressDuration == 650)) {       // and the button is pressed long enough
          globalEvent |= EVENT_STANDBY_MODE | EVENT_BUTTON_LONG; // rise standby flag
          keypressDuration = 0;                      // reset keypress duration counter
        }
      } else {
        keypressDuration = 0;                        // reset keypress duration counter
      }
    }
    prevButtonState = buttonState;                   // save current button state
  }
  //================================================================================================
  // Tone generation
  //================================================================================================
  if (bit_is_set(TCCR1A, COM1B0)) {                  // check if the tone generation is active
    if (toneDuration > 0) {                          // check if duration counter is above zero
      toneDuration--;                                // decrease the counter
    } else {                                         // in case of durationCounter == 0
      cbi(TCCR1A, COM1B0);                           // stop the tone generation
    }
  }
    
  if (CHECK_EVENT(EVENT_BEEPER_REQ) != 0) {         // check the tone generation request is appeared
    CLEAR_EVENT(EVENT_BEEPER_REQ);                  // reset request flag
    sbi(TCCR1A, COM1B0);                            // enable tone signal output
  }

  if (dimCounter == 0) {
    if (++mSec == 150) {
      readPotReq = 1;
    } else if (mSec > 299) {
        mSec = 0;
        displayUpdateReq = 1;
    }
  } else {
    if (++mSec == 50) {
      readPotReq = 1;
    } else if (mSec > 99) {
        mSec = 0;
        displayUpdateReq = 1;
    }
  }
}

inline void setSoftTimer(unsigned int tValue) {
  cli();
  softTimer = tValue;
  sei();
}

inline unsigned int getSoftTimer(void) {
  // return soft timer downcounter value
  unsigned int t;
  cli();
  t = softTimer;
  sei();
  return t;
}

void playTone(unsigned int pitchHz, unsigned int duration) {
  // make sound
  unsigned int pitch = (F_CPU / pitchHz - 2) / 2;   // calculate timer settings to generate desired pitch
  cli();                           // disable interrupts to make atomic assignment
  OCR1A = pitch;                   // set the new counter restart value
  toneDuration = duration;         // set the new tone duration value
  sei();                           // enable interrupst
  SET_EVENT(EVENT_BEEPER_REQ);     // send signal to the timer's ISR 
}


#ifndef CALIBRATION_BUILD  
  void fillDisplayBuffer(int value, unsigned char padChar) {
    // convert int to string and fill the display buffer
    unsigned char i = 3;             // digits at display
    unsigned int dp_req = 0;         // decimal point required flag
    div_t res;                       // conversion divider
  
      // check if it is the special value to shows the error message
    if (-9999 == value) {
      displayBuffer[0] = ~SIGN_E;    
      displayBuffer[1] = ~SIGN_r;    
      displayBuffer[2] = ~SIGN_r;    
      return;
    }
  
      // if passed value is smaller than 1000
    if (value < 1000) {
      dp_req = 1;        // dot required
    } else {
      value /= 10;       // devide value to fit to display
    }
    
      // padding left
    if (value < 100) displayBuffer[0] = padChar;
    if (value < 10) displayBuffer[1] = padChar;
  
      // convert integer to string and place it to the buffer
    do {
      res =  div(value, 10);
      displayBuffer[--i] = codeTable[res.rem];
      value = res.quot;
    } while (value != 0 && i != 0);
      // insert dot if required
    if (dp_req) displayBuffer[1]  &= ~SEG_DP;
   }
#else
  void fillDisplayBuffer(int value, unsigned char padChar) {
    // convert int to string and fill the display buffer
    unsigned char i = 3;             // digits at display
    unsigned int dp_req = 0;         // decimal point required flag
    div_t res;                       // conversion divider
  
      // convert integer to string and place it to the buffer
    do {
      res =  div(value, 10);
      displayBuffer[--i] = codeTable[res.rem];
      value = res.quot;
    } while (value != 0 && i != 0);
   }
#endif


void setup() {

  // Input/Output Ports initialization
  // Port B initialization
  // Function: Bit7=In Bit6=In Bit5=Out Bit4=In Bit3=In Bit2=Out Bit1=Out Bit0=In 
  DDRB=(0<<DDB7) | (0<<DDB6) | (1<<DDB5) | (0<<DDB4) | (0<<DDB3) | (1<<DDB2) | (1<<DDB1) | (0<<DDB0);
  // State: Bit7=T Bit6=T Bit5=0 Bit4=T Bit3=T Bit2=1 Bit1=0 Bit0=P 
  PORTB=(0<<PORTB7) | (0<<PORTB6) | (0<<PORTB5) | (0<<PORTB4) | (0<<PORTB3) | (1<<PORTB2) | (0<<PORTB1) | (1<<PORTB0);

  // Port C initialization
  // Function: Bit6=In Bit5=In Bit4=In Bit3=In Bit2=Out Bit1=Out Bit0=Out 
  DDRC=(0<<DDC6) | (0<<DDC5) | (0<<DDC4) | (0<<DDC3) | (1<<DDC2) | (1<<DDC1) | (1<<DDC0);
  // State: Bit6=T Bit5=P Bit4=P Bit3=T Bit2=0 Bit1=1 Bit0=1 
  PORTC=(0<<PORTC6) | (1<<PORTC5) | (1<<PORTC4) | (0<<PORTC3) | (0<<PORTC2) | (1<<PORTC1) | (1<<PORTC0);

  // Port D initialization
  // Function: Bit7=Out Bit6=Out Bit5=Out Bit4=Out Bit3=Out Bit2=Out Bit1=Out Bit0=Out 
  DDRD=(1<<DDD7) | (1<<DDD6) | (1<<DDD5) | (1<<DDD4) | (1<<DDD3) | (1<<DDD2) | (1<<DDD1) | (1<<DDD0);
  // State: Bit7=1 Bit6=1 Bit5=1 Bit4=1 Bit3=1 Bit2=1 Bit1=1 Bit0=1 
  PORTD=(1<<PORTD7) | (1<<PORTD6) | (1<<PORTD5) | (1<<PORTD4) | (1<<PORTD3) | (1<<PORTD2) | (1<<PORTD1) | (1<<PORTD0);

  // Timer/Counter 0 initialization
  // Clock source: System Clock
  // Clock value: 250,000 kHz
  // Mode: CTC top=OCR0A
  // OC0A output: Disconnected
  // OC0B output: Disconnected
  // Timer Period: 1 ms
  TCCR0A=(0<<COM0A1) | (0<<COM0A0) | (0<<COM0B1) | (0<<COM0B0) | (1<<WGM01) | (0<<WGM00);
  TCCR0B=(0<<WGM02) | (0<<CS02) | (1<<CS01) | (1<<CS00);
  OCR0A=0xF9;
  OCR0B = DEFAULT_BRIGHTNESS;

  // Timer/Counter 0 Interrupt(s) initialization
  TIMSK0=(1<<OCIE0B) | (1<<OCIE0A) | (0<<TOIE0);

  // Timer/Counter 1 initialization
  // Clock source: System Clock
  // Clock value: 16000,000 kHz
  // Mode: CTC top=OCR1A
  // OC1A output: Disconnected
  // OC1B output: Disconnected
  // Noise Canceler: Off
  // Input Capture on Falling Edge
  // Timer Period: 0,5 ms
  // Output Pulse(s):
  // Timer1 Overflow Interrupt: Off
  // Input Capture Interrupt: Off
  // Compare A Match Interrupt: Off
  // Compare B Match Interrupt: Off
  TCCR1A=(0<<COM1A1) | (0<<COM1A0) | (0<<COM1B1) | (0<<COM1B0) | (0<<WGM11) | (0<<WGM10);
  TCCR1B=(0<<ICNC1) | (0<<ICES1) | (0<<WGM13) | (1<<WGM12) | (0<<CS12) | (0<<CS11) | (1<<CS10);
  OCR1AH=0x1F;
  OCR1AL=0x3F;

  // ADC initialization
  // ADC Clock frequency: 125,000 kHz
  // ADC Voltage Reference: AREF pin
  // ADC Auto Trigger Source: ADC Stopped
  // Digital input buffers on ADC0: On, ADC1: On, ADC2: On, ADC3: Off
  // ADC4: On, ADC5: On
  DIDR0=(0<<ADC5D) | (0<<ADC4D) | (1<<ADC3D) | (0<<ADC2D) | (0<<ADC1D) | (0<<ADC0D);
  ADMUX=(1<<REFS1) | (1<<REFS0) | 7;
  ADCSRA=(1<<ADEN) | (0<<ADSC) | (0<<ADATE) | (0<<ADIF) | (0<<ADIE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

  // go standby as soon as power is applied
  SET_EVENT(EVENT_STANDBY_MODE);
}

#ifndef CALIBRATION_BUILD
  unsigned int getOversampled(void) {
    unsigned int sSum = 0;
      // get 64 samples after the channel has become stable
    for (int i = 0; i < 64; i++) {
      sbi(ADCSRA, ADSC);                     // Start the AD conversion (it also clears ADIF flag as a side effect of 'sbi')
      loop_until_bit_is_set(ADCSRA, ADIF);   // wait for conversion complete
      sSum += ADCW;
    }
    return sSum >> 4;         // return averaged ADC result in 12 bit representation
  } 
#else
  unsigned int getOversampled(void) {
    unsigned int sSum;
    unsigned long res = 0;
    for (int i = 0; i < 16; i++) {
        // get 64 samples after the channel has become stable
      sSum = 0;
      for (int j = 0; j < 64; j++) {
        sbi(ADCSRA, ADSC);                     // Start the AD conversion (it also clears ADIF flag as a side effect of 'sbi')
        loop_until_bit_is_set(ADCSRA, ADIF);   // wait for conversion complete
        sSum += ADCW;
      }
      res += sSum;
    }
    return res >> 8;         // return averaged ADC result in 12 bit representation
  } 
#endif

unsigned int filterPot(unsigned int rawPot) {
  static unsigned char potPrev;

  if (rawPot == 0) {
    potPrev = 0;
    return 0;
  }

  unsigned char quot = rawPot / 15;
    // check if pot's value is nearly the same
  if (potPrev == quot) {
    return quot + 1;
  }

  unsigned char  rem = rawPot % 15;

  if (rem > 4 && rem < 12) {
    potPrev = quot;
    return quot + 1;
  }
     // moving right
  if (quot == potPrev + 1) {
    if (rem < 5) {
      return quot;
    }
  }
     // moving left
  if (potPrev = quot + 1) {
    if (rem > 11) {
      return potPrev + 1;
    }
  }
  
  return quot + 1;
}


/*******************************************************************
***
***   DEBUG-ONLY SECTION
***   NOT USED IN NORMAL CODE FLOW
***
*******************************************************************/

#ifdef USE_DEBUG_CODE

float dV = REFERENCE_1V1 / 4096.0;

float getVoltage(void) {
  return getOversampled() * dV;
}

float getSensorResistance(void) {
  // get soldering iron's probe resistance
  float v = getVoltage();
  return R1 / (V_REF / v - 1.0);
} 

#endif
/*********************** END OF DEBUG-ONLY SECTION ****************/

unsigned int findDifference(unsigned int a, unsigned int b) {
  // compare 'a' and 'b' and return the difference
  
  if (a == b) return 0;        // return zerro in case a = b
  if (a > b) return a - b;     // self-exlplained code :)
  return b - a;
}

unsigned char displayMode = 0,    // data display selector
              tickCounter = TICK_TOCK_COUNT, // countdown stage counter
              skipMarker = 0;        // skip counter

unsigned int  sensorData,            // soldering iron's temperature probe data
              targetTemperature,     // the temperature the soldering iron must be heated up to
              wiperPosition,         // ADC value from pot's middle contact
              wPosPrev = 0,          // temp wiperPosition var
              displayValue,          // data to be send to display
              currentTemperature;    // soldering iron temperature

#ifdef USE_DEBUG_CODE               
float        voltage;                         // the var for voltage measurement
#endif  

#define     DELAY(MS)    cli(); softTimer = MS; sei(); while(softTimer != 0)

/*
**
**      Main program loop
**
*/

void loop() {

  if (CHECK_EVENT(EVENT_TICK_TOCK_REQ)) {       // check the countdown flag is risen
    tickCounter++;                              // increase stage counter
    CLEAR_EVENT(EVENT_TICK_TOCK_REQ);           // take down the flag
    if (tickCounter < 5) {                      // check it is a first half of countdown
      playTone(8000, 5);                        // play short high tone sound
    } else {                                    // increase loudness
      if (tickCounter != TICK_TOCK_COUNT) {     // check if it isn't last tick
        displayMode = 3;                        // set show tick display mode
        OCR0B = DEFAULT_BRIGHTNESS;             // increase brightness
        playTone(1000, 100);                    // make load beep
      } else {                                  // if it is last tick               
        playTone(1000, 800);                    // play the final beep
      }
    }
  }

  // ======================= STANDBY mode routine ==========================================
  if (CHECK_EVENT(EVENT_STANDBY_MODE)) {  // check the standby flag is risen
    TURN_HEATER_OFF;                      // turn heater off
    TURN_LED_OFF;                         // turn LED off
    displayBuffer[0] = ~SIGN_BLANK;       // blank display
    displayBuffer[1] = ~SIGN_BLANK;
    displayBuffer[2] = ~SIGN_BLANK;

    if (CHECK_EVENT(EVENT_BUTTON_LONG)) {
      playTone(1000, 800);                // play long beep
      setSoftTimer(800);                  // set soft timer to wait for signaling
    }

    tickCounter = 0;                      // reset tickCounter

    // wait for the button is released and time is out
    while ((PINB & (1<<PINB0)) || getSoftTimer()); 
    TCCR0B &= ~0b111;                     // No clock source (Timer/Counter stopped)
    cbi(TCCR1A, COM1B0);                  // disable speaker output
    toneDuration = 0;                     // reset tone duration counter
    PCMSK0 |= bit (PCINT0);               // set pin 8 as interupt source
    PCIFR  |= bit (PCIF0);                // clear any outstanding interrupts
    PCICR  |= bit (PCIE0);                // enable pin change interrupts for D8 to D13    
    set_sleep_mode (SLEEP_MODE_PWR_DOWN); // set powerdown sleep mode
    sleep_enable();                       // allow enter into sleep mode

    unsigned long int t = 0;              // local keypress duration counter
        
    while(1) {                            // go to endless standby loop
      if ((PINB & (1<<PINB0)) == 0) {     // check for button pressed
        if (++t == 0x00300000) {          // count keyperss duration and check it is long enough
          PCMSK0 &= ~(1<<PCINT0);         // disable pin 8 interrupt
          PCICR  &= ~(1<<PCIE0);          // disable pin change interrupts for D8 to D13 
          break;                          // leave loop
        }
      } else {                            // if no kepress detected
        t = 0;                            // reset keypress duration counter
        sleep_cpu ();                     // go Sleep and wait for the interrupt   
      }
    }
    TCCR0B |= (1<<CS01) | (1<<CS00);      // enable TIMER0 clocking
    resetTimeout(LOW_TEMP_MARGIN);        // reset timeout counters
    displayMode = 0;                      // set display mode
    targetTemperature = 0;                // reset target temperature
    playTone(1000, 600);                  // play greeting
#ifndef CALIBRATION_BUILD
  #ifdef PREHEAT_ENABLE
    DELAY(600)
    preHeat();
    playTone(6000, 20);                  // play greeting
    DELAY(150);
    playTone(6000, 20);                  // play greeting
  #endif
#endif

    CLEAR_EVENT(EVENT_BUTTON_LONG | EVENT_BUTTON_PRESSED);
  }   
  // ======================= End of STANDBY mode routine =======================================
  
  sensorData = getOversampled();          // get data from termal probe

  if (readPotReq == 1) {                  // every 0.1 second
    readPotReq = 0;
      // reflect potentiometer's wiper position to temperature scale
    ADMUX=(1<<REFS1) | (1<<REFS0) | 6;    // set pot's ADC channel
    
    //wiperPosition = map(getOversampled(), 0, 4095, LOW_TEMP_MARGIN, HIGH_TEMP_MARGIN);
    wiperPosition = map(filterPot(getOversampled()) + LOW_TEMP_DEGREE / 100, 
                        LOW_TEMP_DEGREE / 100, HIGH_TEMP_DEGREE / 100, 
                        LOW_TEMP_MARGIN, HIGH_TEMP_MARGIN);
    ADMUX=(1<<REFS1) | (1<<REFS0) | 7;    // restore thermal probe ADC channel
      // check if pot's data is different enough from target temperature
    if (findDifference(targetTemperature, wiperPosition) > 7) {
      OCR0B = DEFAULT_BRIGHTNESS;         // increase display brightness
      displayMode = 0;                    // set display mode
      tickCounter = 0;                    // reset countdown var
      targetTemperature = wiperPosition;  // set new target temperature
      setSoftTimer(1500);                 // set period for displaying target temperature instead of
                                          // measured one
      CLEAR_EVENT(EVENT_LOW_POWER_MODE);  // leave low power mode
      SET_EVENT(EVENT_TEMP_CHANGE_REQ);   // set temperature changing flag
    }
  }
  
  if (CHECK_EVENT(EVENT_BUTTON_PRESSED)) {// check keypress flag is set
    displayMode = 0;                      // force display's main mode
    CLEAR_EVENT(EVENT_BUTTON_PRESSED);    // resed keypress flag
    OCR0B = DEFAULT_BRIGHTNESS;           // set display brighter
    tickCounter = 0;                      // reset tickCounter
    resetTimeout(sensorData);             // re-calc timeout period
    playTone(8000, 20);                   // make short audible 'click'
  }
  
    // represent current soldering iron temperature using the calibration data
  currentTemperature = map(sensorData,
                           ADC_CALIBRATION_DATA_LOW,
                           ADC_CALIBRATION_DATA_HIGH,
                           CALIBRATION_TEMP_LOW,
                           CALIBRATION_TEMP_HIGH) / 10;
                           
  if (displayUpdateReq == 1) {            // every 0.1 second                           
    displayUpdateReq = 0;                 // reset flag
    switch (displayMode) {
      case 0:
        if (!CHECK_EVENT(EVENT_TEMP_CHANGE_REQ)) {                          // if no changing temperature flag is set
          if (findDifference(sensorData, targetTemperature) < 8) {         // check the temperature is in the allowed boundaries
            displayValue = map(targetTemperature, 
                 	             LOW_TEMP_MARGIN, 
                               HIGH_TEMP_MARGIN, 
                               LOW_TEMP_DEGREE / 10,
                               HIGH_TEMP_DEGREE / 10);                      // use target temperature for displaying
          } else {                                                          // in the case of temperature drift is high enough
            if (CHECK_EVENT(EVENT_LOW_POWER_MODE)) {                        // if low power mode is active
              if (findDifference(sensorData, LOW_TEMP_MARGIN) < 10  ) {
                displayValue = LOW_TEMP_DEGREE / 10;                        // display fixed low margin degree value
              } else {
                displayValue = currentTemperature;                          // show real measured temperature
              }
            } else {                                                        // in main mode
#ifndef CALIBRATION_BUILD
              displayValue = currentTemperature;                            // show real measured temperature
#else
              displayValue = sensorData - 1000;
#endif
              /*
              displayValue = map(targetTemperature, 
                                 LOW_TEMP_MARGIN, 
                                 HIGH_TEMP_MARGIN, 
                                 LOW_TEMP_DEGREE / 10,
                                 HIGH_TEMP_DEGREE / 10);                      // use target temperature for displaying
             */
            }
          }
        } else {                                                            // in heating up or cooling down stage
          if (!getSoftTimer()) {                                           // if target temperature is not changed recently
            //displayValue = currentTemperature;                              // display real measured temperature
#ifndef CALIBRATION_BUILD
              displayValue = currentTemperature;                            // show real measured temperature
#else
              displayValue = sensorData - 1000;
#endif            
          } else {                                                          // target temperature just changed 
            displayValue = map(wiperPosition, 
            	                 LOW_TEMP_MARGIN, 
                               HIGH_TEMP_MARGIN, 
                               LOW_TEMP_DEGREE / 10,
                               HIGH_TEMP_DEGREE / 10);      // show target temperature
          }
        }
        break;
  /******************************************************************
  ***
  ***
  ***  DEBUG ONLY SECTION
  ***
  ***      
  *******************************************************************/
  #ifdef  USE_DEBUG_CODE
      case 1:
        displayValue = (getVoltage(3) - 1.0) * 10000;         
        break;
      case 2:
        displayValue = (getSensorResistance()) * 10;
        break;
  #endif
  /********************* END OF DEBUG CODE **************************/
      case 3:
        displayValue = TICK_TOCK_COUNT - tickCounter; // show countdown values
        break;
      default:
        break;
    }
  
    fillDisplayBuffer(displayValue, ~SIGN_BLANK);    // send data to the display
  }
  if (CHECK_EVENT(EVENT_LOW_POWER_MODE)) {         // if low power mode active
    if (CHECK_EVENT(EVENT_ATTN_REQ)) {             // check if it is first occurrence
      CLEAR_EVENT(EVENT_ATTN_REQ);                 // down flag
      tickCounter = 0;                             // reset tick counter
      OCR0B = MIN_BRIGHTNESS;                      // lower display brightness
      displayMode = 0;                             // set main display mode
    }
    wiperPosition = LOW_TEMP_MARGIN;    // force targed temperature to 185C degree
  }

  /*****************************************************************
  **
  **  HEATER CONTROL SECTION
  **
  *****************************************************************/

  if (sensorData < wiperPosition) {                // check if current temperature is lower than target one                     
    TURN_HEATER_ON;                                // force heating up
    TURN_LED_ON;                                   // toggle LED on
  } else{                                          // the current temperature is equal to target one or above
    TURN_HEATER_OFF;                               // disable heating up
    TURN_LED_OFF;                                  // toggle LED off
  }
  /*****************************************************************/


    // check if it is temperature changing mode
  if (CHECK_EVENT(EVENT_TEMP_CHANGE_REQ) != 0) {
      // check if temperature target has reached
    if (findDifference(sensorData, wiperPosition) < 4) {
      CLEAR_EVENT(EVENT_TEMP_CHANGE_REQ);
      resetTimeout(sensorData);  // recalculate timeout
      playTone(4000, 50);        // play 'temperature matched' sound mark
    }
  }
  
}

#ifdef PREHEAT_ENABLE

  void preHeat(void) {
    unsigned int sData;
      // check if current temperatue low enough
    if (getOversampled() < LOW_TEMP_MARGIN) {
      // save current TIM1 settings
      unsigned int sTCCR1A = TCCR1A;
      unsigned int sTCCR1B = TCCR1B;
      // unsigned int sOCR1AL = OCR1AL;
      // unsigned int sOCR1AH = OCR1AH;
      
         // reinit timer in PWM mode
      TCCR1A=(0<<COM1A1) | (0<<COM1A0) | (0<<COM1B1) | (0<<COM1B0) | (1<<WGM11) | (0<<WGM10);
      TCCR1B=(0<<ICNC1) | (0<<ICES1) | (0<<WGM13) | (1<<WGM12) | (0<<CS12) | (0<<CS11) | (1<<CS10);
      
      sbi(ADMUX, 1);

      for (int i = 20; i <= 100; i++) {
        cbi(TCCR1A, COM1A1);  // disable PWM output
        DELAY(2);             // wait till the probe becomes stable
        sData = getOversampled();

        OCR1A = 5 * i;        // set new duty cycle ratio (i is equal duty cycle)
        sbi(TCCR1A, COM1A1);  // enable PWM output

        if (sData > LOW_TEMP_MARGIN) break;      // go away if preheating target is reached
        fillDisplayBuffer(map(sData,
                              ADC_CALIBRATION_DATA_LOW,
                              ADC_CALIBRATION_DATA_HIGH,
                              CALIBRATION_TEMP_LOW,
                              CALIBRATION_TEMP_HIGH) / 10,
                              ~SIGN_BLANK); // display current temperature
        for (int k = 0; k < 10; k++) {
          // show preheating blink
          TURN_LED_ON;
          DELAY(3);
          TURN_LED_OFF;
          DELAY(7);
        }
      }
      TURN_LED_OFF;
        // restore timer settings
      OCR1A = 0;
      TCCR1A = sTCCR1A;
      TCCR1B = sTCCR1B;
      // OCR1AL = sOCR1AL;
      // OCR1AH = sOCR1AH;
      
      TURN_HEATER_OFF;
    }
  }
#endif

 

 

Andrey123m
Offline
Зарегистрирован: 17.09.2015

Спасибо, a5021. Все очень доходчиво. Попробовал откалибровать с обычным термометром. На температурах до 300 градусов присутствует погрешность. Ближе к 400 градусов погрешности почти нет. Видимо из-за того, что калибровал обычным термометром. Попробую найти лабораторный термометр с десятыми градусов и еще раз откалибровать.

kang2k
Offline
Зарегистрирован: 15.05.2015

Давно не заходил на форум. А тут уже и новая прошивка.

Прошил, проверил. Стабильнее показывать стало. Раньше в пределах градуса-трех менялось, сейчас стоит как вкопанное.

Спасибо a5021.

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

Питание от БП ноубука, 19В 4.5A

Frolv
Offline
Зарегистрирован: 31.08.2015

Добрый день. Собираю такую станцию и есть просьба - кто-нибуть развел печатную плату под SMD? Не поделитесь?

a5021
Offline
Зарегистрирован: 07.07.2013

Я задумывался над тем, чтобы развести еще и smd-версию, но остановило опасение, что она не будет востребована. Трассировка хоть и интересный, но довольно длительный процесс, а времени, как обычно, ни на что не хватает. В принципе, если общественность проявит более активный интерес, может и сподоблюсь. Но тут важным становится вопрос, в каких размерах это все делать. Для себя я делаю все максимально мелко и тонко, но это требует определенной квалификации при изготовлении платы, что может вызвать трудности у начинающих. Еще вопрос конструктивов и размерносей компонентов не совсем однозначен. Если я разведу под свои компоненты, то это наверняка приведет к трудностям с поиском того же самого при повторении кем-то другим.

Dm77
Offline
Зарегистрирован: 18.07.2015

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

a5021
Offline
Зарегистрирован: 07.07.2013

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

Dm77
Offline
Зарегистрирован: 18.07.2015

a5021 пишет:

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

А почему сложно? термопарой мультиметра например отлично меряется. Соответственно у каждого жала при одной и тойже температуре разные калибровочные значения. Хранить можно прям в скетче массивом. Выбирать (например) удержанием кнопки + потенциомерт вправо - влево

a5021
Offline
Зарегистрирован: 07.07.2013

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

Dm77
Offline
Зарегистрирован: 18.07.2015

Ну тогда ладно :)

Frolv
Offline
Зарегистрирован: 31.08.2015

Большое спасибо a5021 за данный проект, сделал, запустил (не без проблем, но это из-за своих ошибок) - доволен как слон. НО! В языке программирования я очень начинающий и не смог понять как "раскоментировать" и соответственно не могу откалибровать паяльник. Не мог бы кто нибуть выложить скетч с раскомментированным определением CALIBRATION_BUILD. Буду премного благодарен!

Еще пара вопросов: в каких случаях издается звуковой сигнал?  При питании данной схемы от БП компьютера можно обойтись без кренки и питать от +5 вольт БП?

a5021
Offline
Зарегистрирован: 07.07.2013

Раскомментировать -- значит убрать знак(и) комментария. В данном случае речь идет о строке № 26. Уберите два слеша ("//") в начале строки и директива будет раскомментирована. Чтобы закомментировать обратно, эти два слеша надо вернуть. Вот и вся премудрость.

Без кренки обойтись можно, но подаваемые +5в от БП не должны иметь больших пульсаций. Шум по линии +5в может плохо влиять на точность измерений температуры и, как следствие, на стабильность показаний на индикаторе.

Подать +5в напрямую от БП можно, если не запаивать эту самую кренку, а припаять провод прямо вместо ее вывода №3. Весьма не лишним в этом случае будет увеличить емкость конденсатора С5 из расчета, чем больше, тем лучше.

Kolchugin
Offline
Зарегистрирован: 15.12.2013

a5021, у меня к вам вопрос по схеме.  Зачем нужен диод D2 в цепи динамика? И нужен ли он там вообще?

a5021
Offline
Зарегистрирован: 07.07.2013

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

Если по каким-то соображениям вы не хотите устанавливать диод, можно не устанавливать. Непосредственно на функционал это не влияет. Диод выполняет защитные функции и только лишь в некоторых случаях.

Kolchugin
Offline
Зарегистрирован: 15.12.2013

Благодарю за развёрнутый ответ.

Frolv
Offline
Зарегистрирован: 31.08.2015

Добрый день. Сделал каллибровку по вашей методике, получил значения 36,2 (градуса) - 993 (на дисплее), и 85 - 213. В какие строки кода внести это значения?

a5021
Offline
Зарегистрирован: 07.07.2013

Значения нужно занести в строки 44 - 47. Предполагаю, что в вашем случае они будут выглядеть так:

#define CALIBRATION_TEMP_LOW         ((unsigned int) 3650)
#define CALIBRATION_TEMP_HIGH        ((unsigned int) 8500)
#define ADC_CALIBRATION_DATA_LOW     ((unsigned int) 1993)
#define ADC_CALIBRATION_DATA_HIGH    ((unsigned int) 2213)

 

Frolv
Offline
Зарегистрирован: 31.08.2015

Огромнейшее спасибо!

a5021
Offline
Зарегистрирован: 07.07.2013

Расскажите потом, что получилось, если не сложно.

trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011

Помогите " коллегам"  ;)

http://we.easyelectronics.ru/Tools/lukey-702-pechalka-.html

Frolv
Offline
Зарегистрирован: 31.08.2015

Все прошилось, по личным ощущениям температура жала стало грется сильнее, но точнее сказать пока не могу - завтра возьму у знакомого дистанционный термометр - измерю точнее. Есть такая" шероховатость"  -при крайнем положении переменика температура на индикаторе скачком снижается к минимальной. Переменик 12 ком. Это можно исправить?

Frolv
Offline
Зарегистрирован: 31.08.2015

И неплохо бы увеличить время перехода паяльника в "сон", у меня оно около 5 минут. Попутно вопрос - переход в сон происходит когда регулировку не трогаем или при отсутствии резких изменений температуры жала? Типа не паяем - температура почти постоянная, изменения в пару градусов. 

Frolv
Offline
Зарегистрирован: 31.08.2015

Извиняюсь - переменик 1 (один) кОм!!!

a5021
Offline
Зарегистрирован: 07.07.2013

С переменником вы сурово поступили. При такой замене вы отрезали 9/10 диапазона регулирования в верхней части. Чтобы использовать переменник величиной 1ком, резисторы R20 и R21 должны быть 270ом и 1ком соответственно.

Переход в сон зависит от температуры жала и составляет 15 секунд при крайне правом положении переменника и один час при крайне левом. Промежуточные положения "размазаны" пропорционально. Крайние значиния для диапазона заданы определениями в тексте скетча:

  // min timeout in seconds
#define MIN_TIMEOUT_SEC              15
  // max timeout  in seconds
#define MAX_TIMEOUT_SEC              (60 * 60)

Условием для перехода в "сон" является отсутствие воздействия на органы управления (переменник, кнопка) или резких изменений температуры. Последнее не является режимом детектирования пайки. Паялка уйдет в сон не зависимо от того, происходит пайка или нет.

Frolv
Offline
Зарегистрирован: 31.08.2015

Приношу свои извинения, неправильно посчитал значение переменика, на нем обозначение В103 - это все таки 10кОм. Но температура при приближении к крайнему максимальному положению падает до минимальной. Напряжение на среднем выводе переменика меняется от 0 вольт до 0.98. 

a5021
Offline
Зарегистрирован: 07.07.2013

Для идеального случая оно должно меняться с нуля до 1.1 вольта (величина внутреннего опорного напряжения атмеги), но так как идеального ничего не существует, а существует разброс в величинах сопротивлений R20, R21 и самого переменника, то 0.98 -- это, пусть и не лучший, но вполне допустимый результат. Чуть снизится верхний предел регулировки. А вот почему падает температура на правом краю, тут пока загадка. Покопаюсь на досуге в исходниках. Есть нехорошее подозрение, что это может оказаться переполнением какой-то переменной.

Frolv
Offline
Зарегистрирован: 31.08.2015

А как ФАКТИЧЕСКИ, не отображаемую на индикаторе, увеличить температуру жала? Т.е. на индикаторе у меня 439 градусов (больше не устанавливается - сбрасывается) а по ощущениям намного меньше. Измерить температуру времено не могу - приятель только на днях термометр принесет. 

dobrman
Offline
Зарегистрирован: 19.01.2015

Извините что не по теме, но хочу задать один вопрос в сило своей не грамотности в области электроники.

Я что-то не догоняю или использование ШИМ в регулировке температуры фена по схеме  Ильи c MOC3061 не возможно?

Просто на основе похожей схемы собрал диммер к ESP8266. В режиме реле работает отлично, а вот диммировать лампу накаливания не хочет (хотя замерял сопротивление через BTA16 изменяется от 5кОм и выше). Или MOC3061 с его детектором перехода через 0 тут ни при чем и я просто сделал что-то не так?

hmark
Offline
Зарегистрирован: 02.10.2015

По поводу БП - можно взять такой http://www.aliexpress.com/item/New-Arrival-AC-Universal-100V-240V-for-DC-24V-4A-96W-Power-Supply-Charger-Converter-Adapter/32281525290.html

Тест на нем проводил - последовательно 2 лампы 12В 100Вт. Зажглись, ток выдал в районе 3,96А, в защиту не ушел, но хорошо гудел. Для паяльника должен подойти (для чего я собственно и заказывал).

Второй вопрос. Ардуины на борту имеют стабилизатор, на моей Мини Про LG50 - 500ма, до 12В. Вместе например с КРЕН 9В можно распределить тепло и уменьшить количество радиаторов. Токи вроде небольшие.

a5021
Offline
Зарегистрирован: 07.07.2013

БП симпатичный и по деньгам вполне неплохо. Если он держит заявленные характеристики, то это как раз то, что нужно. Даже если будет срабатывать защита при холодном включении, теперь есть ШИМ-разогрев, который купирует эту проблему.

Насчет набортного регулятора ардуины -- использовать его по предложенной вами схеме проблематично. Помимо самого ардуины есть еще остальная схема, которая так же расчитана на питание от 5 вольт. Если туда подать 9 вольт, то это напряжение через светодиоды индикатора окажется на ногах ардуины и даже если не сожжет сразу, то начнет "поднимать" его, т.к. выходы внутри атмеги через защитные диоды соеденины с шиной +5v.

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

вписывается сюда без каких бы то нибыло вопросов.

dobrman пишет:

Я что-то не догоняю или использование ШИМ в регулировке температуры фена по схеме  Ильи c MOC3061 не возможно?

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

Frolv пишет:

А как ФАКТИЧЕСКИ, не отображаемую на индикаторе, увеличить температуру жала?

Если честно, не понял вопроса.

hmark
Offline
Зарегистрирован: 02.10.2015

По стабилизатору на борту я имел в виду, что даем 9В (можно даже 12В) на порт Vin или RAW (в зависимости от модели Ардуино), а с VCC отй же Ардуины получаем те же стабилизированные 5В до 500ма. Не знаю, как по току дисплей, но ток делителя напряжения примерно 2ма, регулятора еще меньше.

Но со step-down конвертером конечно же будет лучше. Только его еще заказать и дождаться нужно.

hmark
Offline
Зарегистрирован: 02.10.2015

Мне интересный вопрос - не думали над защитой от зависания Ардуины при открытом полевике? Например, глобальный сброс при достижении критического сопротивления термистора.

Кстати, вспомнил еще один вопрос - зачем использовать опорный источник напряжения, если можно измерить фактическое напряжение приходящих 5В?

a5021
Offline
Зарегистрирован: 07.07.2013

hmark пишет:

а с VCC отй же Ардуины получаем те же стабилизированные 5В до 500ма. Не знаю, как по току дисплей, но ток делителя напряжения примерно 2ма, регулятора еще меньше.

Ток через дисплей в пике составляет 7сегментов * 20ма = 140ма. Сам ардуино кушает 30-40ма. Добавим потребление прочих элементов схемы и в сумме получится где-то 200ма. Ток кажется вполне приемлемым. Но это только до того, как начнем считать мощность выделяемую в тепло на линейном стабилизаторе ардуины. А она будет выглядеть так: P= (Vin - Vout) * Iout. Для наших цифр это получится (12-5) * 0.2 = 1.4W, что является трижды запредельной величиной для того типа корпуса, в котором регулятор распаян на ардуине. Регулятору в таких условиях скорее всего захочется выпаяться (с этим у него проблем не возникнет) и убежать. :)

 

Цитата:
Мне интересный вопрос - не думали над защитой от зависания Ардуины при открытом полевике? Например, глобальный сброс при достижении критического сопротивления термистора.

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

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

Если кому-то захочется повышенной надежности, то можно добавить в схему компаратор с порогом 1.2в и подключить его параллельно входу АЦП ардуины. На выходе компаратора будет формироваться аварийный сигнал в случае, если нагрев паяльника превысил допустимый диапазон.

Цитата:
Кстати, вспомнил еще один вопрос - зачем использовать опорный источник напряжения, если можно измерить фактическое напряжение приходящих 5В?

В этом случае мы поимеем сразу несколько минусов по сравнению с нынешней схемой. Имеются достаточно значимые основания, чтобы так не делать, но чтобы еще больше не загромождать объяснение, просто встаньте осциллографом на шину +5в и посмотрите, что на ней твориться во время работы схемы. Если это употребить в качестве опорного напряжения, то о точности удержания температуры лучше 10-20 градусов можно даже и не заикаться.

 

Frolv
Offline
Зарегистрирован: 31.08.2015

Попробую объяснит - это про температуру. У меня по индикатору температура не устанавливается выше 439 градусов. По моим ощущениям (возможно ошибочным) это не соответствует действительности - температура на жале паяльника ниже. Калибровку по вашей методике делал - не сильно помогло. Можно ли поправить код таким образом что бы поднять температуру? Т.е. по индикатору предположим 300, а на жале - 350.

По переходу паяльника в "сон". Как то странно это происходит. На максимальной температуре паяльник лежит на подставке несколько десятка два минут - в "сон" не переключается, начал пользоваться (паять) плату - через полминуты запищал сигнал начала перехода в "сон" Может лучше режим "сон" назначить на кнопку? Нажал - паяльник уснул, через несколько минут (предположим минут 10-15) нагрев вообще выключился.

a5021
Offline
Зарегистрирован: 07.07.2013

Мой совет, сначала все-таки установите сколь нибудь более достоверно, что ошибка индикации температуры имеет место. Я бы вот на глаз вряд ли смог различать температуры пяльника 440 и 380 градусов, скажем. И что вы, если не секрет, паяете при такой температуре жала? Я, например, уже не помню, когда последний раз что-то паял с температурой больше 320 градусов.

Если вам непременно хочется, чтобы паяльник грел сильнее, уменьшите значение ADC_CALIBRATION_DATA_HIGH

Режим сна задуман, как средство против забывчивости. Оставлять паяльник включенным на долгое время все-таки не лучшая идея. Если в сон уходить только по кнопке, то велик шанс получить ситуацию, когда и на кнопку забудешь нажать, а паяльник будет сутки барабанить, дожигая жало.

Woorhees
Woorhees аватар
Offline
Зарегистрирован: 18.09.2015

Достаточно ли будет данного транзистора для управления питанием паяльника?

keefa
Offline
Зарегистрирован: 19.06.2015

Woorhees пишет:

Достаточно ли будет данного транзистора для управления питанием паяльника?

не могу прочитать что на нем написано. 

Woorhees
Woorhees аватар
Offline
Зарегистрирован: 18.09.2015

CEP603AL

keefa
Offline
Зарегистрирован: 19.06.2015

Woorhees пишет:

CEP603AL

напряжение исток сток 30В. желательно с запасом ставить. Но работать будет. как долго не могу сказать. по остальным параметрам вопросов нет. 

Woorhees
Woorhees аватар
Offline
Зарегистрирован: 18.09.2015

Подскажите правильно ли будет подключить корпус паяльника на общий минус и потом уже через конденсатор к общему минусу подключить заземляющий контур?

a5021
Offline
Зарегистрирован: 07.07.2013

Что такое "заземляющий контур" ?

keefa
Offline
Зарегистрирован: 19.06.2015

Woorhees пишет:

Подскажите правильно ли будет подключить корпус паяльника на общий минус и потом уже через конденсатор к общему минусу подключить заземляющий контур?

Что такое заземление?

это защита от поражения электрическим током человека.

Что подключается к заземлению?  

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

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