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

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

В данной версии скетча не реализовано управление нагревателем посредством ШИМ-сигнала. Аппаратная возможность, тем не менее, была предусмотрена на этапе разработки схемы и пин ардуины (PB1), на котором "висит" затвор мосфета, может быть выходом ШИМ-канала (OC1A).

Сразу кромсать теущий скетч на предмет внедрения туда ШИМа -- это довольно трудоемкая задача, а вот написать отдельный маленький скетчик, который будет выдавать ШИМ-сигнал с малым коэффициентом заполнением на затвор мосфета, видится занятием попроще. Например, что-то вот такое:

void setup() {
  // Timer/Counter 1 initialization
  // Clock source: System Clock
  // Clock value: 16000,000 kHz
  // Mode: Fast PWM top=0x01FF
  // OC1A output: Non-Inverted PWM
  // OC1B output: Disconnected
  // Noise Canceler: Off
  // Input Capture on Falling Edge
  // Timer Period: 0,032 ms
  // Output Pulse(s):
  // OC1A Period: 0,032 ms Width: 0,31311 us
  // Timer1 Overflow Interrupt: Off
  // Input Capture Interrupt: Off
  // Compare A Match Interrupt: Off
  // Compare B Match Interrupt: Off
  TCCR1A=(1<<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);
}

void loop() {
  OCR1AL=5 * 1;       // set 1% duty cycle ratio
  delay(1000);
  OCR1AL=5 * 3;       // set 3% duty cycle ratio
  delay(1000);
  OCR1AL=5 * 5;       // set 5% duty cycle ratio
  delay(1000);
}

Скетч выдает ШИМ-сигнал на затвор мосфета с частотой 31250 Гц. Работоспособность этого кода я не проверял, но, по идее, этого должно хватить, чтобы проверить БП, как он переживет ШИМ-преднагрев с малыми коэффициентами заполнения. Если блок питания рассчитан на 3А, то по идее он должен выдерживать коэффициенты заполения до 20-30 процентов. Я в вышеприведенном скетче специально выбрал маленькие значения (и толком греть паяльник оно, скорее всего, не будет), чтобы проверить саму идею, будет ли БП держать, но если переписать секцию loop так

void loop() {
  OCR1AL=5 * 10;      // set 10% duty cycle ratio
  delay(1000);
  OCR1AL=5 * 20;      // set 20% duty cycle ratio
  delay(1000);
  OCR1AL=5 * 30;      // set 30% duty cycle ratio
  delay(1000);
}

то шим будет выдаваться с 10-и, 20-и и 30-и процентным заполнением, что уже будет заметно по температуре паяльника. Попробуйте, а дальше будет видно, стоит ли с этим БП вообще иметь дело. Потом можно будет и в основном скетче что-то придумать.

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

DDRB = (1 << DDB1);

в любом месте в процедуре setup().

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

Попробовал:

void setup() {
  TCCR1A=(1<<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);
  DDRB = (1 << DDB1);
}

void loop() {
  OCR1AL=5 * 100; // на нагреватель подается 11В
  delay(1000);
}

Температура поднимается, на паяльник отдается 11В.

Используется Pro Mini, Atmega168 (5V, 16Mhz)

 

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

OCR1AL -- это восьмибитная переменная (младший байт регистра сравнения) и значение "5 * 100" туда никак не записать вследствие ограничения разрядности.  В вышеприведенном случае происходит отбрасывание старшего разряда и присваиваемое значение становится равным 244, что в итоге выливается в коэффициент заполнения равный 48.8%.

Если есть намерение задавать коэффициент выше 50%, то процедура установки должна выглядеть как-то так:

int temp = 5 * 85  // 85% duty cycle ratio

OCR1AH = temp >> 8;    // assign high byte

OCR1AL = temp & 0xFF; // assign low byte

 

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

a5021, компилятор сам раскладывает по байтикам, можно не парится :)  OCR1A=500 и всё :)

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

a5021 пишет:

OCR1AL -- это восьмибитная переменная (младший байт регистра сравнения) и значение "5 * 100" туда никак не записать вследствие ограничения разрядности.  В вышеприведенном случае происходит отбрасывание старшего разряда и присваиваемое значение становится равным 244, что в итоге выливается в коэффициент заполнения равный 48.8%.

Если есть намерение задавать коэффициент выше 50%, то процедура установки должна выглядеть как-то так:

int temp = 5 * 85  // 85% duty cycle ratio

OCR1AH = temp >> 8;    // assign high byte

OCR1AL = temp & 0xFF; // assign low byte

При 85% уходит в защиту. Работает с int temp = 5 * 80. На паяльник отдается около 18В. Хороший результат для этого БП. Хотелось бы ограничивать только при старте паяльника, а потом подавать 24В и записать все это в основной скетч

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

dimax пишет:

a5021, компилятор сам раскладывает по байтикам, можно не парится :)  OCR1A=500 и всё :)

Вы правы. Как-то я упустил, что в ардуиновских объявлениях есть отдельно шестнадцатибитное определение OCR1A. Код для инициализации периферии я генерю с помощью визарда CodeVisionAVR, а там только две восьмибитных половинки. Вобщем, спасибо за замечание по существу.

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

Dm77 пишет:

При 85% уходит в защиту. Работает с int temp = 5 * 80. На паяльник отдается около 18В. Хороший результат для этого БП. Хотелось бы ограничивать только при старте паяльника, а потом подавать 24В и записать все это в основной скетч

А вы попробуйте при включении холодного (это важно) паяльника дать 75% на одну-две секунды, потом 80% на тот же период, потом 85% и т.д. до 100%.

for (int i = 75; i <= 100; i += 5) {

   OCR1A = 5 * i;

   delay(1500);
}

По идее, оно так быстрее разогреет, чем просто 85% держать.

В основной скетч эту фишку, безусловно, стоит внедрить, но это не сделать с наскока. Оно должно вписаться в функционал скетча, а там потребуется немного подумать, как это сделать аккуратно. Первый таймер используется для генерации звука, т.ч. для ШИМ-разогрева таймер нужно будет переинициализировать, а после опять переводить в CTC-режим.

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

Попробовал: на холодном пояльнике работает начиная с 65%. Напряжение растет каждые 1,5сек до 24В. Блок в защиту не уходит.

Ок. Буду очень ждать Ваши правки в скетче. Без них придется греть от 12В БП.

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

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

Хех. нашел пока "костыль". Делать преднагрев от блока 12В 2А, а потом переключаться на 24В. Колхоз полный, но работает :))) 

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

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

Я набросал процедурку преднагрева, но попробовать как-то все не с руки. Если возьметесь протестировать, то могу выложить. 

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

Конечно возьмусь. Проведу замеры, какие скажете

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

Может идея и не очень изящная, но чтобы запилить нужный функционал хоть как-нибудь, была просто добавлена процедура preHeat(), которая после нажатия кнопки включения тупо выдает ШИМ-сигнал с заполением 60% и увеличивает его на 5% каждую секунду. Процедура прекращает работу по достижению 100% или превышению порога LOW_TEMP_MARGIN.

 

/*
**
**      ARDUINO Soldering Station TH v1.01 (c) a5021
**              march - july 2015
**
**  http://arduino.ru/forum/proekty/payalnaya-stantsiya-iz-arduino
**
**  HISTORY
**  =======
**    july 2015 -- version 1.01:
**      - added preheat procedure;
**    may 2015  -- version 1.00:
**      - first public release
*/

#include <avr/sleep.h>

// 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 ADC_CALIBRATION_DATA_LOW     1658 
#define ADC_CALIBRATION_DATA_HIGH    2373
#define CALIBRATION_TEMP_LOW         2950
#define CALIBRATION_TEMP_HIGH        18300

  // low temperature margin  (185C)
#define LOW_TEMP_MARGIN              2382
  // low temperature degree (multiplied by 100)
#define LOW_TEMP_DEGREE              18500  
  // high temperature margin (460C)			 
#define HIGH_TEMP_MARGIN             3661
  // low temperature degree (multiplied by 100)
#define HIGH_TEMP_DEGREE             46000  

  // 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

  // heater control macros
#define   TURN_HEATER_ON   sbi(PORTB, PORTB1)
#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  0xE0
#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
                       currentDigit = 0;  // active digit at display
                       
volatile unsigned int  softTimer16_1 = 0, // software timer 1 downcounter 
                       softTimer16_2 = 0, // software timer 2 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)
    // low power mode 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 skipMarker,                   // every 3rd call's counter
                       buttonState,                  // current button state
                       prevButtonState;              // saved button state
  static unsigned int  keypressDuration;             // button press duration

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

  //================================================================================================
  // Soft timers handling
  //================================================================================================
  if (softTimer16_1 != 0) softTimer16_1--;           // decrease software timer 1
  if (softTimer16_2 != 0) softTimer16_2--;           // decrease software timer 2

  if (++skipMarker == 3) {                           // on every 3rd call
    skipMarker = 0;                                  // reset counter

  //================================================================================================
  // 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
  }
}

void setSoftTimer(unsigned char tNo, unsigned int tValue) {
  // set 'tNo' soft-timer value
  switch (tNo) {
    case 1:
      cli();
      softTimer16_1 = tValue;
      sei();
      break;
    case 2:
      cli();
      softTimer16_2 = tValue;
      sei();    
      break;
  }
}

unsigned int getSoftTimer(unsigned char tNo) {
  // return specified soft timer coounter value
  unsigned int t;
  
  switch (tNo) {
    case 1:
      cli();
      t = softTimer16_1;
      sei();
      break;
    case 2:
      cli();
      t = softTimer16_2;
      sei();    
      break;
    default:
      t = 0;
      break;
  }
  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
  OCR1AH = highByte(pitch);        // set the new counter restart value high byte
  OCR1AL = lowByte(pitch);         // set the new counter restart value low byte
  cli();                           // disable interrupts to make atomic assignment
  toneDuration = duration;         // set the new tone duration value
  sei();                           // enable interrupst
  SET_EVENT(EVENT_BEEPER_REQ);     // send signal to the timer's ISR 
}
  
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;
 }


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);
  TCNT0=0x00;
  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);
  TCNT1H=0x00;
  TCNT1L=0x00;
  ICR1H=0x00;
  ICR1L=0x00;
  OCR1AH=0x1F;
  OCR1AL=0x3F;
  OCR1BH=0x00;
  OCR1BL=0x00;

  // ADC initialization
  // ADC Clock frequency: 125,000 kHz
  // ADC Voltage Reference: Internal 1.1v
  // ADC Auto Trigger Source: Timer0 Compare Match
  // 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) | (0<<ADLAR)) | 7;
  ADCSRA=(1<<ADEN) | (0<<ADSC) | (1<<ADATE) | (0<<ADIF) | (0<<ADIE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
  ADCSRB=(0<<ADTS2) | (1<<ADTS1) | (1<<ADTS0);

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

unsigned long int getOversampled(unsigned char ch) {
  unsigned int sSum = 0;
  unsigned char adc_low, adc_high, i = 0;
  /*
  **    AD conversions are started (triggered) by TIMER0 COMPARE MATCH A
  **    The routine waits for ADC data has become available, sums it 
  **    and returns averaged result in 12 bit resolution
  */

  ADMUX=((1<<REFS1) | (1<<REFS0) | (0<<ADLAR)) | ch; // select 1.1v internal reference and set the input channel

  // to avoid some wrong data it needs to skip several ADC cycles

    // ignore two first ADC results
  do {
    loop_until_bit_is_set(ADCSRA, ADIF);   // wait for EoC flag
    sbi(ADCSRA, ADIF);                     // clear EoC flag
    adc_low  = ADCL;                       // get low byte
    adc_high = ADCH;                       // get high byte
  } while (++i == 2);                      // repeat it two times

 
    // get 64 samples after the channel has become stable
  for (i = 0; i < 64; i++) {
    loop_until_bit_is_set(ADCSRA, ADIF);
    sbi(ADCSRA, ADIF);
    
    // read ADCL first; doing so locks both ADCL
    // and ADCH until ADCH is read.  reading ADCL second would
    // cause the results of each conversion to be discarded,
    // as ADCL and ADCH would be locked when it completed.
    adc_low  = ADCL;
    adc_high = ADCH;
    // sum all samples
    sSum += (adc_high << 8) | adc_low;
  }
    
  return sSum >> 4;         // return averaged ADC result in 12 bit representation
} 

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

#ifdef USE_DEBUG_CODE

float dV = REFERENCE_1V1 / 4096.0;

float getVoltage(unsigned char ch) {
  // return voltage in channel 'ch;
  unsigned int v = getOversampled(ch);
  
  return v * dV;
}

float getSensorResistance(void) {
  // get soldering iron's probe resistance
  float v = getVoltage(7);
  
  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

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


void loop() {

  unsigned int displayValue,                    // data to be send to display
               currentTemperature;              // soldering iron temperature

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


  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(2, 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(2)); 
    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
    while (toneDuration != 0);            // wait untill beep ends
    preHeat();
  }   
  // ======================= End of STANDBY mode routine =======================================
  
  sensorData = getOversampled(7);         // get data from termal probe

  if (++skipMarker == 3) {                // every 3rd loop
    skipMarker = 0;                       // reset loop counter
      // reflect potentiometer's wiper position to temperature scale
    wiperPosition = map(getOversampled(6), 0, 4095, LOW_TEMP_MARGIN, HIGH_TEMP_MARGIN);
      // check if pot's data is different enough from target temperature
    if (findDifference(targetTemperature, wiperPosition) > 5) {
      OCR0B = DEFAULT_BRIGHTNESS;         // increase display brightness
      displayMode = 0;                    // set display mode
      tickCounter = 0;                    // reset countdown var
      targetTemperature = wiperPosition;  // set new target temperature
      setSoftTimer(2, 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;

  switch (displayMode) {
    case 0:
      if (!CHECK_EVENT(EVENT_TEMP_CHANGE_REQ)) {                          // if no changing temperature flag is set
        if (findDifference(sensorData, targetTemperature) < 10) {         // 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) < 7  ) {
              displayValue = LOW_TEMP_DEGREE / 10;                        // display fixed low margin degree value
            } else {
              displayValue = currentTemperature;                          // show real measured temperature
            }
          } else {                                                        // in main mode
            displayValue = currentTemperature;                            // show real measured temperature
          }
        }
      } else {                                                            // in heating up or cooling down stage
        if (!getSoftTimer(2)) {                                           // if target temperature is not changed recently
          displayValue = currentTemperature;                              // display real measured temperature
        } 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) < 5) {
      CLEAR_EVENT(EVENT_TEMP_CHANGE_REQ);
      resetTimeout(sensorData);  // recalculate timeout
      playTone(4000, 50);        // play 'temperature matched' sound mark
    }
  }
  
}

void preHeat(void) {
    // check if current temperatue low enough
  if (getOversampled(7) < 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=(1<<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);
  
    for (int i = 60; i <= 100; i =+ 5) {
      OCR1A = 5 * i;      // set new duty cycle ratio (i is equal duty cycle)
      unsigned int temp = getOversampled(7);  // get current temperature
      if (temp > LOW_TEMP_MARGIN) break;      // go away if preheating target is reached
      for (int j = 0; j < 10; j++) {          // 
        fillDisplayBuffer(temp, ~SIGN_BLANK); // display current temperature
           // show preheating blink
        TURN_LED_ON;
        delay(50);
        TURN_LED_OFF;
        delay(50);
      }
    }
    TURN_LED_OFF;
      // restore timer settings
    TCCR1A = sTCCR1A;
    TCCR1B = sTCCR1B;
    OCR1AL = sOCR1AL;
    OCR1AH = sOCR1AH;
    
    TURN_HEATER_OFF;
  }
}

 

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

Значит следующее:

1. Блок в защиту не уходит
2. Постоянно мигает LED ардуины
3. Температура на экране прыгает в районе 230гр. Хотя выставлена резистором на 185гр.
4. Не реагирует на кнопку выключения
5. Температура на жале медленно ползет вверх по 1-2гр.

Если закомментировать preHeat, то все работает нормально

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

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

/*
**
**      ARDUINO Soldering Station TH v1.02 (c) a5021
**              march - july 2015
**
**  http://arduino.ru/forum/proekty/payalnaya-stantsiya-iz-arduino
**
**  HISTORY
**  =======
**    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>

  // 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 ADC_CALIBRATION_DATA_LOW     1658 
#define ADC_CALIBRATION_DATA_HIGH    2373
#define CALIBRATION_TEMP_LOW         2950
#define CALIBRATION_TEMP_HIGH        18300

  // low temperature margin  (185C)
#define LOW_TEMP_MARGIN              2382
  // low temperature degree (multiplied by 100)
#define LOW_TEMP_DEGREE              18500  
  // high temperature margin (460C)			 
#define HIGH_TEMP_MARGIN             3661
  // low temperature degree (multiplied by 100)
#define HIGH_TEMP_DEGREE             46000  

  // 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

  // heater control macros
#define   TURN_HEATER_ON   sbi(PORTB, PORTB1)
#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  0xE0
#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
                       currentDigit = 0;  // active digit at display
                       
volatile unsigned int  softTimer16_1 = 0, // software timer 1 downcounter 
                       softTimer16_2 = 0, // software timer 2 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)
    // low power mode 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 skipMarker,                   // every 3rd call's counter
                       buttonState,                  // current button state
                       prevButtonState;              // saved button state
  static unsigned int  keypressDuration;             // button press duration

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

  //================================================================================================
  // Soft timers handling
  //================================================================================================
  if (softTimer16_1 != 0) softTimer16_1--;           // decrease software timer 1
  if (softTimer16_2 != 0) softTimer16_2--;           // decrease software timer 2

  if (++skipMarker == 3) {                           // on every 3rd call
    skipMarker = 0;                                  // reset counter

  //================================================================================================
  // 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
  }
}

void setSoftTimer(unsigned char tNo, unsigned int tValue) {
  // set 'tNo' soft-timer value
  switch (tNo) {
    case 1:
      cli();
      softTimer16_1 = tValue;
      sei();
      break;
    case 2:
      cli();
      softTimer16_2 = tValue;
      sei();    
      break;
  }
}

unsigned int getSoftTimer(unsigned char tNo) {
  // return specified soft timer coounter value
  unsigned int t;
  
  switch (tNo) {
    case 1:
      cli();
      t = softTimer16_1;
      sei();
      break;
    case 2:
      cli();
      t = softTimer16_2;
      sei();    
      break;
    default:
      t = 0;
      break;
  }
  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
  OCR1AH = highByte(pitch);        // set the new counter restart value high byte
  OCR1AL = lowByte(pitch);         // set the new counter restart value low byte
  cli();                           // disable interrupts to make atomic assignment
  toneDuration = duration;         // set the new tone duration value
  sei();                           // enable interrupst
  SET_EVENT(EVENT_BEEPER_REQ);     // send signal to the timer's ISR 
}
  
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;
 }


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);
  TCNT0=0x00;
  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);
  TCNT1H=0x00;
  TCNT1L=0x00;
  ICR1H=0x00;
  ICR1L=0x00;
  OCR1AH=0x1F;
  OCR1AL=0x3F;
  OCR1BH=0x00;
  OCR1BL=0x00;

  // ADC initialization
  // ADC Clock frequency: 125,000 kHz
  // ADC Voltage Reference: Internal 1.1v
  // ADC Auto Trigger Source: Timer0 Compare Match
  // 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) | (0<<ADLAR)) | 7;
  ADCSRA=(1<<ADEN) | (0<<ADSC) | (1<<ADATE) | (0<<ADIF) | (0<<ADIE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
  ADCSRB=(0<<ADTS2) | (1<<ADTS1) | (1<<ADTS0);

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

unsigned long int getOversampled(unsigned char ch) {
  unsigned int sSum = 0;
  unsigned char adc_low, adc_high, i = 0;
  /*
  **    AD conversions are started (triggered) by TIMER0 COMPARE MATCH A
  **    The routine waits for ADC data has become available, sums it 
  **    and returns averaged result in 12 bit resolution
  */

  ADMUX=((1<<REFS1) | (1<<REFS0) | (0<<ADLAR)) | ch; // select 1.1v internal reference and set the input channel

  // to avoid some wrong data it needs to skip several ADC cycles

    // ignore two first ADC results
  do {
    loop_until_bit_is_set(ADCSRA, ADIF);   // wait for EoC flag
    sbi(ADCSRA, ADIF);                     // clear EoC flag
    adc_low  = ADCL;                       // get low byte
    adc_high = ADCH;                       // get high byte
  } while (++i == 2);                      // repeat it two times

 
    // get 64 samples after the channel has become stable
  for (i = 0; i < 64; i++) {
    loop_until_bit_is_set(ADCSRA, ADIF);
    sbi(ADCSRA, ADIF);
    
    // read ADCL first; doing so locks both ADCL
    // and ADCH until ADCH is read.  reading ADCL second would
    // cause the results of each conversion to be discarded,
    // as ADCL and ADCH would be locked when it completed.
    adc_low  = ADCL;
    adc_high = ADCH;
    // sum all samples
    sSum += (adc_high << 8) | adc_low;
  }
    
  return sSum >> 4;         // return averaged ADC result in 12 bit representation
} 

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

#ifdef USE_DEBUG_CODE

float dV = REFERENCE_1V1 / 4096.0;

float getVoltage(unsigned char ch) {
  // return voltage in channel 'ch;
  unsigned int v = getOversampled(ch);
  
  return v * dV;
}

float getSensorResistance(void) {
  // get soldering iron's probe resistance
  float v = getVoltage(7);
  
  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

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


void loop() {

  unsigned int displayValue,                    // data to be send to display
               currentTemperature;              // soldering iron temperature

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


  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(2, 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(2)); 
    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
#ifdef PREHEAT_ENABLE
    while (toneDuration != 0);            // wait untill beep ends
    preHeat();
#endif
    CLEAR_EVENT(EVENT_BUTTON_LONG | EVENT_BUTTON_PRESSED);
  }   
  // ======================= End of STANDBY mode routine =======================================
  
  sensorData = getOversampled(7);         // get data from termal probe

  if (++skipMarker == 3) {                // every 3rd loop
    skipMarker = 0;                       // reset loop counter
      // reflect potentiometer's wiper position to temperature scale
    wiperPosition = map(getOversampled(6), 0, 4095, LOW_TEMP_MARGIN, HIGH_TEMP_MARGIN);
      // check if pot's data is different enough from target temperature
    if (findDifference(targetTemperature, wiperPosition) > 5) {
      OCR0B = DEFAULT_BRIGHTNESS;         // increase display brightness
      displayMode = 0;                    // set display mode
      tickCounter = 0;                    // reset countdown var
      targetTemperature = wiperPosition;  // set new target temperature
      setSoftTimer(2, 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;

  switch (displayMode) {
    case 0:
      if (!CHECK_EVENT(EVENT_TEMP_CHANGE_REQ)) {                          // if no changing temperature flag is set
        if (findDifference(sensorData, targetTemperature) < 10) {         // 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) < 7  ) {
              displayValue = LOW_TEMP_DEGREE / 10;                        // display fixed low margin degree value
            } else {
              displayValue = currentTemperature;                          // show real measured temperature
            }
          } else {                                                        // in main mode
            displayValue = currentTemperature;                            // show real measured temperature
          }
        }
      } else {                                                            // in heating up or cooling down stage
        if (!getSoftTimer(2)) {                                           // if target temperature is not changed recently
          displayValue = currentTemperature;                              // display real measured temperature
        } 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) < 5) {
      CLEAR_EVENT(EVENT_TEMP_CHANGE_REQ);
      resetTimeout(sensorData);  // recalculate timeout
      playTone(4000, 50);        // play 'temperature matched' sound mark
    }
  }
  
}

#ifdef PREHEAT_ENABLE

void preHeat(void) {
    // check if current temperatue low enough
  if (getOversampled(7) < 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=(1<<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);
  
    for (int i = 60; i <= 100; i + 5) {
      OCR1A = 5 * i;      // set new duty cycle ratio (i is equal duty cycle)
      unsigned int temp = getOversampled(7);  // get current temperature
      if (temp > LOW_TEMP_MARGIN) break;      // go away if preheating target is reached
      for (int j = 0; j < 10; j++) {          // 
        fillDisplayBuffer(map(temp,
                             ADC_CALIBRATION_DATA_LOW,
                             ADC_CALIBRATION_DATA_HIGH,
                             CALIBRATION_TEMP_LOW,
                             CALIBRATION_TEMP_HIGH) / 10,
                          ~SIGN_BLANK); // display current temperature
           // show preheating blink
        TURN_LED_ON;
        delay(10);
        TURN_LED_OFF;
        delay(90);
      }
    }
    TURN_LED_OFF;
      // restore timer settings
    TCCR1A = sTCCR1A;
    TCCR1B = sTCCR1B;
    OCR1AL = sOCR1AL;
    OCR1AH = sOCR1AH;
    
    TURN_HEATER_OFF;
  }
}
#endif

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

/* #define PREHEAT_ENABLE  1   */

С закоментированной строчкой скетч работает точно так же, как в самой первой версии, т.е. без ШИМ-преднагрева.

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

Вроде работает, буду тестировать. Спасибо!

Из замеченого: при вколючении на холодную и с ф-цией преднагрева на дисплее начинают быстро отображаться пару секунд значения 9XX, 8XX, 7XX. Потом начинает расти температура как обычно.

И еще вопрос. При калибровке как Вы выводите значения ф-ции getOversampled(), в Serial? (просто заметил USE_DEBUG_CODE)

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

При включении, когда температура нагревателя ниже ста градусов, на индикатор идет вывод значений в виде деятичной дроби с одним знаком после зпятой. То, что вы увидели, как 9ХХ, на самом деле является 9X.X, т.е., например, 93.2 градуса цельсия.

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

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

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

Я Ваш тестер. Спасибо!

Ну конечно хочется меньший опрос индикатора. Чтобы цыфты не так сильно скакали (было как на 702 ликей). Но это может аппаратно.

Еще оставить для тех кто повторять будет, в скетче только значения калибровки. Без HIGH_TEMP_MARGIN, LOW_TEMP_MARGIN. Ну или наоборот без ADC_CALIBRATION_DATA_XXX

т.е тупо измеряешь температуру на жале и вводишь значения с getOversampled() . В общем в идеале для универсального скетча пару настроек. (но это, еще раз повторяюсь ИМХО). Или вообще лафа: вводишть замеряные Омы с паяльника и впуть (не знаю возможно ли такое ввобще)

Ganster41
Offline
Зарегистрирован: 03.11.2014

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

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

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

Dm77 пишет:

Еще оставить для тех кто повторять будет, в скетче только значения калибровки. Без HIGH_TEMP_MARGIN, LOW_TEMP_MARGIN. Ну или наоборот без ADC_CALIBRATION_DATA_XXX

Калибровочные данные тут никак не убрать, а вот вычисление границ я осилил с помощью макро-определений. Вид имеет жутковатый, но считает правильно.

/*
**      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)

  // 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)

 

Чуть-чуть убавил верхнюю границу максимального нагрева. Было 460 градусов, стало 458. Связано это с тем, что в борьбе за точность измерений пришлось добавить функцию фильтрации значений оцифровки переменника. Чтобы получилась полная шкала, надо 4095 значений оцифровки разделить нацело. Диапазон 185-460 градусов составляет 275 дискрет и если на него разделить 4095, то получается дробное значение. Зато 4095 делится нацело, если делитель равен 273. Таким образом на один градус приходится пятнадцать осчетов АЦП (4095 / 273 = 15).

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

Немного потестирую и скоро выложу.

 

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

Dm77 пишет:

т.е тупо измеряешь температуру на жале и вводишь значения с getOversampled() . В общем в идеале для универсального скетча пару настроек. (но это, еще раз повторяюсь ИМХО). Или вообще лафа: вводишть замеряные Омы с паяльника и впуть (не знаю возможно ли такое ввобще)

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

karl2233
Offline
Зарегистрирован: 05.07.2015

это не теоретически, это практически так и выглядит - таящий лед(0 градусов) и кипящая вода(100)

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

Доброго времени!

Таки собрал первую половину своей паяльной станции (т.е. только паяльник без фена, фен в планах если доллар еще раза в 2 не скаканет).

Вышло чудо-юдо конечно.

Ручку взял такую - http://www.ebay.com/itm/New-Soldering-Station-Iron-Handle-for-HAKKO-907-ESD-907-936-937-928-926-/121281474488?hash=item1c3cf06fb8 (с терморезистором).

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

Нагреватель рулится через IRFZ44N - http://www.ebay.com/itm/251543309059?_trksid=p2057872.m2749.l2649&ssPageName=STRK%3AMEBIDX%3AIT

Вроде без нареканий, во время работы едва теплый.

Собирал на макетках - http://www.ebay.com/itm/371018178163?_trksid=p2057872.m2749.l2649&ssPageName=STRK%3AMEBIDX%3AIT

Использовал 2 шт, на первой экранчик и кнопки управления, на второй собственно все остальное.

 

Управляется все Ардуино Про Мини совместимой доской - http://www.ebay.com/itm/1PCS-New-Pro-Mini-atmega328-Board-5V-16M-Arduino-Compatible-Nano-NEW-/381189097928?hash=item58c0a409c8

 

Экран взял такой - http://www.ebay.com/itm/400684713153?_trksid=p2057872.m2749.l2649&ssPageName=STRK%3AMEBIDX%3AIT

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

 

Управляю 3-я кнопками из набора - http://www.ebay.com/itm/Electronic-Parts-Pack-KIT-for-ARDUINO-component-Resistors-Switch-Button-HM-/181496980423?hash=item2a421023c7

Одна для выбора пункта меню и две для изменения выбранного параметра.

 

Блок питания взял от сгоревшего ноутбука ровербук не помню какой модели. В общем на выходе, по заверениям этикетки с китайскими закорючками, 19В 3.42А постоянного тока.

 

Понижаю до 5в одним L7805CV из той-же страны, что и все остальное - сколько ни читал даташиты, вроде до 30в подавать можно, но с моим БП греется зараза до 100 градусов (если замерять на подошве) даже с прикрученным кусочком алюминия с пупырышками вместо ребер (примерно 2х1 см) - это кусочек от радиатора добытого из того-же ровербука, в общем что нашел под рукой, конденсаторы отсутствуют в виду моей лени и отсутствия новых конденсаторов в наличии (это же надо их от куда-то выпаять, а потом еще и умудриться на макетке разместить). В таком режиме долго не гонял, но других стабилизаторов у меня под рукой нет, зато этих еще 9 штук осталось. От сюда вопрос. Каике могут быть последствия перегрева L7805, кроме выхода из строя самого стабилизатора? 19в на ардуину не утечёт?

Все это временно запихано в коробку из под китайского смартОфона Prestigio :)

Собственно похвастался только чтобы узнать о последствиях перегрева стабилизатора.

Т.к. это моя первая поделка, то выглядит все ужастно, но по чему-то работает.

P.S.: Там где синяя изолента, должен был быть разъем - http://www.ebay.com/itm/281532771442?_trksid=p2057872.m2749.l2649&ssPageName=STRK%3AMEBIDX%3AIT но шуруп на корпусе оказался сорван. Крутится на одном месте и ни туда ни сюда и подцепить эту мелочь ни как. Если удастся его победить, поставлю, если нет, придется еще 1 разъем дозаказать.

Клапауций 998
Offline
Зарегистрирован: 12.08.2015

dobrman пишет:

фен в планах если доллар еще раза в 2 не скаканет).

блин. довёл обама-обезьяна россиян до звиздеца.

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

dobrman пишет:
Каике могут быть последствия перегрева L7805, кроме выхода из строя самого стабилизатора? 19в на ардуину не утечёт?

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

Если нет возможности подцепить радиатор, можно прикрутить регулятор прямо на плату. Это не лучшее ре шение с точки зрения организации теплоотвода, но намного лучше, чем оставлять регулятор без теплоотвода вообще или с теплоотводом недостаточного размера.

Еще есть смысл уменьшить энергопотребление самого устройства. В вашем случе можно уменьшать яркость экрана или гасить его совсем по истечении какого-то времени после установки рабочей температуры.

 

karl2233
Offline
Зарегистрирован: 05.07.2015

dobrman пишет:
Каике могут быть последствия перегрева L7805, кроме выхода из строя самого стабилизатора? 19в на ардуину не утечёт?

т.к. намечается тепловой пробой, то вероятность такого чуть больше 80%.

на LM рассеивается нефиговая мощность: 19-5=14. ток потребления от LM пусть 100мА.

5*0,1=0,5Ватта, да плюс рассеивание от 14 Вольт(14*0,1)... там радиатор нужен 10кв.д.

у меня, в самодельном блоке питания для Ардуинок с 12 до 5, (всего 7Вольт!)  и LM стоит на радиаторе от комьютерного БП с температурой 50 С.

 

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

Зарание прошу прощение за ламерские посты, я не далек в электронике, это моя первая поделка, баловство с Ардуино+светодиоды или ESP+Реле не в счет (стоит у меня в комнате wi-fi реле на ESP-01, глючное, отложил исправление до лучших времен). Да и физику учил в школе 15 лет назад и уже 14 лет как забыл ее напрочь.

a5021 пишет:

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

Это и пугает, возможности замерить точную температуру нет, китайская цешка показывает 100 градусов (+-5) на металической части стабилизатора при выключеном паяльнике и градусов на 10 больше при нагревании (хотя это может быть показалось, значения скачат постоянно), в общем порог то рядом. Если капнуть слюну на стык радиатора-пластинки и стабилизатора, то она начинает кипеть и испаряться. Долго в рабочем режиме пока не гонял, максимум минут 10.

karl2233 пишет:

т.к. намечается тепловой пробой, то вероятность такого чуть больше 80%.

на LM рассеивается нефиговая мощность: 19-5=14. ток потребления от LM пусть 100мА.

5*0,1=0,5Ватта, да плюс рассеивание от 14 Вольт(14*0,1)... там радиатор нужен 10кв.д.

у меня, в самодельном блоке питания для Ардуинок с 12 до 5, (всего 7Вольт!)  и LM стоит на радиаторе от комьютерного БП с температурой 50 С.

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

Так как я уже писал, что не силен в физике, то не могу понять, не ужели 2Ватта это так много, вроде современные процессоры в десятки раз больше рассеивают? Или дело в площади?

В общем буду пробовать побольше железку подобрать в качастве радиатора, может половинку от радиатора чипа материнской платы прикрутить получится. Если первым сгорит стабилизатор, то можно будет как вариант отдельный БП 220в->5в для ардуины прикрутить (все равно на фен 220 понадобится заводить).

karl2233
Offline
Зарегистрирован: 05.07.2015

процессор рассеивает, но он с радиатором.

 физика нужна в объёме средней школы - Закон Ома и всё.

тут три варианта (с учётом того, что сама Ардуина потребляет не так много и на ней есть свой стабилизатор):

1. поставить две LM последовательно: с 19 до 12 и с 12 до 5, но радиатор нужен по любому на обе.

2. поставить импульсный DC-DC (на ибее полно понижающих и цена копеечная). тут радиатор не нужен.

3. оставить как есть, но на LM навесить радиатор что б температура на х.х. была не выше 50С.

 

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

Взял кусок радиатора побольше (примерно 1х3см), температура в районе 75 градусов.

Видимо это правильное направление, заказал на ебеи нормальные радиаторы, пока с этим гоняю, 30 минут - полет нормальный.

 

Еще одна странность, у меня подсветка экрана была прокинута на +5в (как бы не нужна мне регулировка и не стал лишний ШИМ вывод занимать на ардуине), для эксперимента отрезал проводник, думал без подсветки проверить нагрев стабилизатора... В итоге подсветка стала тусклее, но не выключилась. Замерил напряжение на ножке LED - 4.2 вольта, от куда не понял О_о , нужно схему экрана глянуть будет, вроде китаец давал на скачку её.

Ganster41
Offline
Зарегистрирован: 03.11.2014

Может лучше заказать какой-нибудь нормальный импульсный Step-Down, и не обогревать помещение КРЕНкой?

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

Ganster41 пишет:

Может лучше заказать какой-нибудь нормальный импульсный Step-Down, и не обогревать помещение КРЕНкой?

Отличный вариант, но ждать месяц-два доставки, а я уже во всю пользуюсь этим паяльником :)

В будущем же, планируется фен подключать, соответственно БП от ноутбука встроится в корпус паяльной станции (какой уж он будет не знаю, или от ATX БП возьму или сам изготовлю), ну а на ардуину отдельный БП поставить 220в->5в можно будет. Так не надежнее будет?

boban_
Offline
Зарегистрирован: 09.10.2013

dobrman пишет:

 

Это и пугает, возможности замерить точную температуру нет, китайская цешка показывает 100 градусов (+-5) на металической части стабилизатора при выключеном паяльнике и градусов на 10 больше при нагревании (хотя это может быть показалось, значения скачат постоянно), в общем порог то рядом. Если капнуть слюну на стык радиатора-пластинки и стабилизатора, то она начинает кипеть и испаряться. Долго в рабочем режиме пока не гонял, максимум минут 10.

 

 

Прислони паяльник - покажет температуру :)

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

boban_ пишет:

Прислони паяльник - покажет температуру :)

По логике ни чего из этого не выйдет, часть тепла рассеется пока дойдет от жала до терморезистора. Но блин идея заманчивая, почти 3 часа ночи, встал ребенка покормить, теперь сижу как дурак жду когда прогреется стабилизатор :)

Spyer
Offline
Зарегистрирован: 07.04.2014

Добрый день!

Собираю себе паяльную станцию. Собрал пока блак управления паяльником, все хорошо только одно НО!!!!. Включаю нагрев паяльника к примеру на 250гр, через 10 сек на экране показывается что паяльник нагрелся до 250гр, проверяю термопорай от мультиметра температуру на жале, а там градусов 60-80. я конечно понимаю что передать нагрев от нагревателя на кончик жала надо время, но всетаки может есть у кого опыт в решении данной задачи?

Заранее спасибо.

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

a5021 пишет:

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

Немного потестирую и скоро выложу.

Как идет тестирование? 

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

Вы правы, надо было давно выложить. :)

Вот версия 1.2, где полностью переработан механизм удержания температуры:

/*
**
**      ARDUINO Soldering Station TH v1.2 (c) a5021
**              march - august 2015
**
**  http://arduino.ru/forum/proekty/payalnaya-stantsiya-iz-arduino
**
**  HISTORY
**  =======
**    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>

  // 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

  // heater control macros
#define   TURN_HEATER_ON   sbi(PORTB, PORTB1)
#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 
}
  
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;
 }

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);
}

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
} 

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
#ifdef PREHEAT_ENABLE
    DELAY(600)
    preHeat();
    playTone(6000, 20);                  // play greeting
    DELAY(150);
    playTone(6000, 20);                  // play greeting
#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
              displayValue = currentTemperature;                            // show real measured temperature
              /*
              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
          } 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

Попробуйте, отпишитесь.

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

Залил и подставил свои значения 

#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)

Чисто визуально ничего не изменилось. Как и в предыдущей версии цифры на индикаторе постоянно меняются, что не очень информативно. Видео: https://youtu.be/70Aoi2jpj10

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

Какая-то уж очень сильная нестабильность. У вас нет осциллографа, чтобы посмотреть стабильность напряжения по линии 5 вольт?

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

Нет к сожалению. Попробую снять плату с блока питания, может наводки от него

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

Можно в качестве эксперимента сделать вот что: закомментировать строку 689 и раскомментировать строки 691-695. Данная правка приведет к тому, что в обычном режиме на индикаторе будет не измеренная температура, а та, что задается переменником. Она уж точно должна стоять, как влитая.

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

a5021 пишет:

Можно в качестве эксперимента сделать вот что: закомментировать строку 689 и раскомментировать строки 691-695. Данная правка приведет к тому, что в обычном режиме на индикаторе будет не измеренная температура, а та, что задается переменником. Она уж точно должна стоять, как влитая.

Попробовал этот эксперимент. При достижении температуры цыфры перестают меняться. Если снять плату с БП, то "дребезг" значений становится 1-2 градуса (а не десятки как у меня).

В общем нужно либо снимать плату с БП, либо как то ее экранировать

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

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

Для уменьшения негативного влияния имульсных помех, экранирование будет безусловно полезным. Еще могу посоветовать увеличить значение емкости C1 до 22-47 нанофарад (0.022-0.047мкф). Но без фанатизма. Чем выше эта емкость, тем датчик становится более инертным и его показания начинают запаздывать.

Ganster41
Offline
Зарегистрирован: 03.11.2014

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

Spyer
Offline
Зарегистрирован: 07.04.2014

После твоей публикации, продавец получил сразу больше 120 заказав за этот день ))))))

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

a5021 пишет:

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

Для уменьшения негативного влияния имульсных помех, экранирование будет безусловно полезным. Еще могу посоветовать увеличить значение емкости C1 до 22-47 нанофарад (0.022-0.047мкф). Но без фанатизма. Чем выше эта емкость, тем датчик становится более инертным и его показания начинают запаздывать.

Я никогда не занимался экранированием, подразумеваю сделать пластину между платой и БП. Из чего лучше?

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

Можно не пластину, а спаять что-то типа поддона из того же стеклотекстолита. Вот по такому принципу:

Внутренняя поверхность (экран) не должна иметь электрического соединения ни с чем больше, кроме "массы" платы паялки. Фольгированный текстолит прекрасно режется кровельными ножницами, т.ч. сложностей с обработкой особых не должно быть.

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

Vadim4eG
Offline
Зарегистрирован: 04.09.2015

Здравствуйте, уважаемый a5021!

Прочитал всю тему, восхищён проделанной работой, проект получился просто потрясающий! 

a5021 пишет:

Только обратите внимание, что схема дана для индикатора с общим анодом. Для общего катода подключение будет отличаться.

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

Спасибо.

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

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

Как можно видеть, PNP транзисторы заменены на NPN и их эмиттеры посажены на землю, вместо плюса.

Vadim4eG
Offline
Зарегистрирован: 04.09.2015

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

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

Ребят. схема протестирована. Может заказать печатки в Китае?

Производство 10-и штук 15-20$, могу взять техпроцесс на себя. Доставка из Москвы + почта (ваша). Ничего не зарабатываем, идеи в массы. (Автор, если не против)

От себя добавлю

Минусы: 

Нужно делать калибровку в коде. В соседней ветке "крендель" делает с переменниками

Плюсы:

ОЧЕНЬ грамотное программирование МК. т.е. не 30 строк тупого кода, а именно вдумчивое управление МК.

Автору, СПАСИБО! 

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

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

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

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

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