В данной версии скетча не реализовано управление нагревателем посредством ШИМ-сигнала. Аппаратная возможность, тем не менее, была предусмотрена на этапе разработки схемы и пин ардуины (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 Заметил досадное упущение, а именно -- пин, на который производится вывод ШИМ-сигнала не проинициализован, как выход. Исправить это можно добавив строчку
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
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, компилятор сам раскладывает по байтикам, можно не парится :) OCR1A=500 и всё :)
Вы правы. Как-то я упустил, что в ардуиновских объявлениях есть отдельно шестнадцатибитное определение OCR1A. Код для инициализации периферии я генерю с помощью визарда CodeVisionAVR, а там только две восьмибитных половинки. Вобщем, спасибо за замечание по существу.
При 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-режим.
Может идея и не очень изящная, но чтобы запилить нужный функционал хоть как-нибудь, была просто добавлена процедура 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;
}
}
1. Блок в защиту не уходит
2. Постоянно мигает LED ардуины
3. Температура на экране прыгает в районе 230гр. Хотя выставлена резистором на 185гр.
4. Не реагирует на кнопку выключения
5. Температура на жале медленно ползет вверх по 1-2гр.
Если закомментировать preHeat, то все работает нормально
Да, там оказалось сразу несколько довольно тупых ошибок. Пришлось мне все-таки достать этот проект и допилить, чтобы все работало. В доработанном виде это пока выглядит так:
/*
**
** 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 */
С закоментированной строчкой скетч работает точно так же, как в самой первой версии, т.е. без ШИМ-преднагрева.
Из замеченого: при вколючении на холодную и с ф-цией преднагрева на дисплее начинают быстро отображаться пару секунд значения 9XX, 8XX, 7XX. Потом начинает расти температура как обычно.
И еще вопрос. При калибровке как Вы выводите значения ф-ции getOversampled(), в Serial? (просто заметил USE_DEBUG_CODE)
При включении, когда температура нагревателя ниже ста градусов, на индикатор идет вывод значений в виде деятичной дроби с одним знаком после зпятой. То, что вы увидели, как 9ХХ, на самом деле является 9X.X, т.е., например, 93.2 градуса цельсия.
В секциях USE_DEBUG_CODE нет вывода в последовательный порт. Там переопределяется, что выводить на индикатор. В одном случае это может быть измеренное напряжение на термодатчике в вольтах или вычисленное значение сопротивления этого термодатчика в омах. Данные цифры могут оказаться полезными при калибровке.
Раз уж опять достал этот проект, то сяду доводить до ума индикацию, чтобы циферки не скакали. Советую посматривать за этой веткой, т.к. скорее всего будут и новые версии скетча. Если у кого-то есть предложения по новым фичам, то с интересом с ними ознакомлюсь.
Ну конечно хочется меньший опрос индикатора. Чтобы цыфты не так сильно скакали (было как на 702 ликей). Но это может аппаратно.
Еще оставить для тех кто повторять будет, в скетче только значения калибровки. Без HIGH_TEMP_MARGIN, LOW_TEMP_MARGIN. Ну или наоборот без ADC_CALIBRATION_DATA_XXX
т.е тупо измеряешь температуру на жале и вводишь значения с getOversampled() . В общем в идеале для универсального скетча пару настроек. (но это, еще раз повторяюсь ИМХО). Или вообще лафа: вводишть замеряные Омы с паяльника и впуть (не знаю возможно ли такое ввобще)
Можно попробовать сделать как на оригинальном Hakko: калибровка по нажатию кнопки, когда паяльник нагрет до определенной "эталонной" температуры. Правда все равно есть далеко не нулевая вероятность, что у разных нагревателей будет разный ТКС, и следовательно ширина диапазона изменения сопротивления терморезистора...
Если параметры термодатчика не отличаются от экземпляра к экземпляру, то достаточно калиброваться по одной точке. В случае с китайскими нагревателями, так не выйдет. Там нужно знать начальное смещение и наклон графика зависимости сопротивления от температуры. С учетом, что график линейный, двух точек должно быть достаточно.
Dm77 пишет:
Еще оставить для тех кто повторять будет, в скетче только значения калибровки. Без HIGH_TEMP_MARGIN, LOW_TEMP_MARGIN. Ну или наоборот без ADC_CALIBRATION_DATA_XXX
Калибровочные данные тут никак не убрать, а вот вычисление границ я осилил с помощью макро-определений. Вид имеет жутковатый, но считает правильно.
Чуть-чуть убавил верхнюю границу максимального нагрева. Было 460 градусов, стало 458. Связано это с тем, что в борьбе за точность измерений пришлось добавить функцию фильтрации значений оцифровки переменника. Чтобы получилась полная шкала, надо 4095 значений оцифровки разделить нацело. Диапазон 185-460 градусов составляет 275 дискрет и если на него разделить 4095, то получается дробное значение. Зато 4095 делится нацело, если делитель равен 273. Таким образом на один градус приходится пятнадцать осчетов АЦП (4095 / 273 = 15).
В изначальном алгоритме, если даныне оцифровки переменника не стабильны, то это приводит к тому, целевая температура нагрева начинает гулять вслед за переменником. Теперь любое значение из диапазона 15 отсчетов означает установку одной и той же температуры. Стабильность удержания заметно возрасла.
т.е тупо измеряешь температуру на жале и вводишь значения с getOversampled() . В общем в идеале для универсального скетча пару настроек. (но это, еще раз повторяюсь ИМХО). Или вообще лафа: вводишть замеряные Омы с паяльника и впуть (не знаю возможно ли такое ввобще)
Процудуру калибровки паяльника я вообще вижу такой, чтобы совсем не трогать исходники. Торетически, это может выглядеть так: включаем питание с зажатой кнопкой -- паялка вместо обычного своего функционала переходит в режим калибровки. При вращении ручки "от края до края", на индикаторе просто отображается значение, скажем, от 10 до 500. Выставляем холодную точку: смотрим температуру на градуснике и выставляем такую же цифру на индикаторе поворотом ручки. Нажимаем кнопку -- значения оцифровки с термодатчика и цифра с индикатора записываются в энергонезависимую память (EEPROM) ардуины. Теперь приступаем к калибровке горячей точки, для чего опускаем паяльник с градусником в кипяток. Опять выставляем переменником цифру на индикаторе, соответствующую температуре градусника и нажимаем кнопку второй раз. Вторая пара калибровочных значений записывается в EEPROM. На этом калибровка завершена. Передергиваем питание и паялка включается в своем обычном режиме, но уже будучи откалиброванной.
Датчик считывал с помощью делителя напряжения, параметры для map подбирал опытным путем, нагревая и измеряя температуру на жале с помощью китайского тестера. Думаю погрешность тут не так важна, мне олово плавить, а не термоядерную реакцию контролировать, но в общем, при выставленной температуре 185 олово едва едва начинает плавиться.
Одна для выбора пункта меню и две для изменения выбранного параметра.
Блок питания взял от сгоревшего ноутбука ровербук не помню какой модели. В общем на выходе, по заверениям этикетки с китайскими закорючками, 19В 3.42А постоянного тока.
Понижаю до 5в одним L7805CV из той-же страны, что и все остальное - сколько ни читал даташиты, вроде до 30в подавать можно, но с моим БП греется зараза до 100 градусов (если замерять на подошве) даже с прикрученным кусочком алюминия с пупырышками вместо ребер (примерно 2х1 см) - это кусочек от радиатора добытого из того-же ровербука, в общем что нашел под рукой, конденсаторы отсутствуют в виду моей лени и отсутствия новых конденсаторов в наличии (это же надо их от куда-то выпаять, а потом еще и умудриться на макетке разместить). В таком режиме долго не гонял, но других стабилизаторов у меня под рукой нет, зато этих еще 9 штук осталось. От сюда вопрос. Каике могут быть последствия перегрева L7805, кроме выхода из строя самого стабилизатора? 19в на ардуину не утечёт?
Все это временно запихано в коробку из под китайского смартОфона Prestigio :)
Собственно похвастался только чтобы узнать о последствиях перегрева стабилизатора.
Т.к. это моя первая поделка, то выглядит все ужастно, но по чему-то работает.
Каике могут быть последствия перегрева L7805, кроме выхода из строя самого стабилизатора? 19в на ардуину не утечёт?
Этого исключить нельзя, но сжечь 7805 тоже нужно еще постараться. Рабочая температура у него по заявлениям производителей до 125 градусов, стало быть, тепловое разрушение начнется выше этой точки. Когда именно, заранее знать не возможно, но мне попадались живые регуляторы (7805) при распайке старой электроники, которые имели отчетливые признаки долгой работы при высоких температурах -- сильное окисление теплоотвода с разноцветными разводами, похожими на цвета побежалости.
Если нет возможности подцепить радиатор, можно прикрутить регулятор прямо на плату. Это не лучшее ре шение с точки зрения организации теплоотвода, но намного лучше, чем оставлять регулятор без теплоотвода вообще или с теплоотводом недостаточного размера.
Еще есть смысл уменьшить энергопотребление самого устройства. В вашем случе можно уменьшать яркость экрана или гасить его совсем по истечении какого-то времени после установки рабочей температуры.
Зарание прошу прощение за ламерские посты, я не далек в электронике, это моя первая поделка, баловство с Ардуино+светодиоды или 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 понадобится заводить).
Взял кусок радиатора побольше (примерно 1х3см), температура в районе 75 градусов.
Видимо это правильное направление, заказал на ебеи нормальные радиаторы, пока с этим гоняю, 30 минут - полет нормальный.
Еще одна странность, у меня подсветка экрана была прокинута на +5в (как бы не нужна мне регулировка и не стал лишний ШИМ вывод занимать на ардуине), для эксперимента отрезал проводник, думал без подсветки проверить нагрев стабилизатора... В итоге подсветка стала тусклее, но не выключилась. Замерил напряжение на ножке LED - 4.2 вольта, от куда не понял О_о , нужно схему экрана глянуть будет, вроде китаец давал на скачку её.
Может лучше заказать какой-нибудь нормальный импульсный Step-Down, и не обогревать помещение КРЕНкой?
Отличный вариант, но ждать месяц-два доставки, а я уже во всю пользуюсь этим паяльником :)
В будущем же, планируется фен подключать, соответственно БП от ноутбука встроится в корпус паяльной станции (какой уж он будет не знаю, или от ATX БП возьму или сам изготовлю), ну а на ардуину отдельный БП поставить 220в->5в можно будет. Так не надежнее будет?
Это и пугает, возможности замерить точную температуру нет, китайская цешка показывает 100 градусов (+-5) на металической части стабилизатора при выключеном паяльнике и градусов на 10 больше при нагревании (хотя это может быть показалось, значения скачат постоянно), в общем порог то рядом. Если капнуть слюну на стык радиатора-пластинки и стабилизатора, то она начинает кипеть и испаряться. Долго в рабочем режиме пока не гонял, максимум минут 10.
По логике ни чего из этого не выйдет, часть тепла рассеется пока дойдет от жала до терморезистора. Но блин идея заманчивая, почти 3 часа ночи, встал ребенка покормить, теперь сижу как дурак жду когда прогреется стабилизатор :)
Собираю себе паяльную станцию. Собрал пока блак управления паяльником, все хорошо только одно НО!!!!. Включаю нагрев паяльника к примеру на 250гр, через 10 сек на экране показывается что паяльник нагрелся до 250гр, проверяю термопорай от мультиметра температуру на жале, а там градусов 60-80. я конечно понимаю что передать нагрев от нагревателя на кончик жала надо время, но всетаки может есть у кого опыт в решении данной задачи?
В изначальном алгоритме, если даныне оцифровки переменника не стабильны, то это приводит к тому, целевая температура нагрева начинает гулять вслед за переменником. Теперь любое значение из диапазона 15 отсчетов означает установку одной и той же температуры. Стабильность удержания заметно возрасла.
Вот версия 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
Чисто визуально ничего не изменилось. Как и в предыдущей версии цифры на индикаторе постоянно меняются, что не очень информативно. Видео: https://youtu.be/70Aoi2jpj10
Можно в качестве эксперимента сделать вот что: закомментировать строку 689 и раскомментировать строки 691-695. Данная правка приведет к тому, что в обычном режиме на индикаторе будет не измеренная температура, а та, что задается переменником. Она уж точно должна стоять, как влитая.
Можно в качестве эксперимента сделать вот что: закомментировать строку 689 и раскомментировать строки 691-695. Данная правка приведет к тому, что в обычном режиме на индикаторе будет не измеренная температура, а та, что задается переменником. Она уж точно должна стоять, как влитая.
Попробовал этот эксперимент. При достижении температуры цыфры перестают меняться. Если снять плату с БП, то "дребезг" значений становится 1-2 градуса (а не десятки как у меня).
В общем нужно либо снимать плату с БП, либо как то ее экранировать
Входная цепь с термодатчика на плате довольно длинная и она может неплохо "ловить" помехи от импульсных блоков питания, особенно, если с качеством у последних не очень. Для того, чтобы значение на индикаторе изменилось на 1 градус, напряжение на измерительном входе ардуины должно измениться примерно на 3 милливольта. Наводки с размахом 30мв в непосредственной близости от импульсника не выглядят чем-то из ряда вон выходящим, что скорее всего и является объяснением наблюдаемой картины.
Для уменьшения негативного влияния имульсных помех, экранирование будет безусловно полезным. Еще могу посоветовать увеличить значение емкости C1 до 22-47 нанофарад (0.022-0.047мкф). Но без фанатизма. Чем выше эта емкость, тем датчик становится более инертным и его показания начинают запаздывать.
Не сочтите за рекламу, но если кто вдруг, как и я, никак не может определиться с корпусом для паялки, есть, имхо, отличный вариант по акции, в комплекте с экранчиком)
Входная цепь с термодатчика на плате довольно длинная и она может неплохо "ловить" помехи от импульсных блоков питания, особенно, если с качеством у последних не очень. Для того, чтобы значение на индикаторе изменилось на 1 градус, напряжение на измерительном входе ардуины должно измениться примерно на 3 милливольта. Наводки с размахом 30мв в непосредственной близости от импульсника не выглядят чем-то из ряда вон выходящим, что скорее всего и является объяснением наблюдаемой картины.
Для уменьшения негативного влияния имульсных помех, экранирование будет безусловно полезным. Еще могу посоветовать увеличить значение емкости C1 до 22-47 нанофарад (0.022-0.047мкф). Но без фанатизма. Чем выше эта емкость, тем датчик становится более инертным и его показания начинают запаздывать.
Я никогда не занимался экранированием, подразумеваю сделать пластину между платой и БП. Из чего лучше?
Можно не пластину, а спаять что-то типа поддона из того же стеклотекстолита. Вот по такому принципу:
Внутренняя поверхность (экран) не должна иметь электрического соединения ни с чем больше, кроме "массы" платы паялки. Фольгированный текстолит прекрасно режется кровельными ножницами, т.ч. сложностей с обработкой особых не должно быть.
Чтобы оценить, насколько такое решение может быть эффективно, можно провести эксперимент, склеив поддон из картона и выложив его изнутри пищевой фольгой. Только опять же не забываем, что "экран" нужно соединить с массой платы и желательно только в одной точке. Разумеется, проводники платы не должны касаться экрана.
Прочитал всю тему, восхищён проделанной работой, проект получился просто потрясающий!
a5021 пишет:
Только обратите внимание, что схема дана для индикатора с общим анодом. Для общего катода подключение будет отличаться.
Имеется идикатор с ОК, собственно и хочу спросить каким образом его можно подключить? Изначально возникла мысь изменить написанный код, но уровень владения языком с/с++ оставляет желать лучшего...
Для подключения индикатора с общим катодом необходимо внести одну небольшую правку в схему и, соответственно, в плату. Измененное место в схеме будет выглядеть так:
Как можно видеть, PNP транзисторы заменены на NPN и их эмиттеры посажены на землю, вместо плюса.
Да, безусловно, как посадить катоды на землю прекрасно понимаю, а будет ли так работать, имею ввиду без програмной смены полярности на выходах мк (порты 0-7) ?
Ребят. схема протестирована. Может заказать печатки в Китае?
Производство 10-и штук 15-20$, могу взять техпроцесс на себя. Доставка из Москвы + почта (ваша). Ничего не зарабатываем, идеи в массы. (Автор, если не против)
От себя добавлю
Минусы:
Нужно делать калибровку в коде. В соседней ветке "крендель" делает с переменниками
Плюсы:
ОЧЕНЬ грамотное программирование МК. т.е. не 30 строк тупого кода, а именно вдумчивое управление МК.
Да, безусловно, как посадить катоды на землю прекрасно понимаю, а будет ли так работать, имею ввиду без програмной смены полярности на выходах мк (порты 0-7) ?
Скетч править -- это само собой. Сейчас глянул, всего придется вносить изменения в три строки. Одна строка (номер 207) в обработчике прерывания COMPA, и две (261-262) в COMPB. Смысл простой -- там, где для общего анода бит выставляется, для общего катода он должен сбрасываться и наоборот.
В данной версии скетча не реализовано управление нагревателем посредством ШИМ-сигнала. Аппаратная возможность, тем не менее, была предусмотрена на этапе разработки схемы и пин ардуины (PB1), на котором "висит" затвор мосфета, может быть выходом ШИМ-канала (OC1A).
Сразу кромсать теущий скетч на предмет внедрения туда ШИМа -- это довольно трудоемкая задача, а вот написать отдельный маленький скетчик, который будет выдавать ШИМ-сигнал с малым коэффициентом заполнением на затвор мосфета, видится занятием попроще. Например, что-то вот такое:
Скетч выдает ШИМ-сигнал на затвор мосфета с частотой 31250 Гц. Работоспособность этого кода я не проверял, но, по идее, этого должно хватить, чтобы проверить БП, как он переживет ШИМ-преднагрев с малыми коэффициентами заполнения. Если блок питания рассчитан на 3А, то по идее он должен выдерживать коэффициенты заполения до 20-30 процентов. Я в вышеприведенном скетче специально выбрал маленькие значения (и толком греть паяльник оно, скорее всего, не будет), чтобы проверить саму идею, будет ли БП держать, но если переписать секцию loop так
то шим будет выдаваться с 10-и, 20-и и 30-и процентным заполнением, что уже будет заметно по температуре паяльника. Попробуйте, а дальше будет видно, стоит ли с этим БП вообще иметь дело. Потом можно будет и в основном скетче что-то придумать.
UPD Заметил досадное упущение, а именно -- пин, на который производится вывод ШИМ-сигнала не проинициализован, как выход. Исправить это можно добавив строчку
в любом месте в процедуре setup().
Попробовал:
Температура поднимается, на паяльник отдается 11В.
Используется Pro Mini, Atmega168 (5V, 16Mhz)
OCR1AL -- это восьмибитная переменная (младший байт регистра сравнения) и значение "5 * 100" туда никак не записать вследствие ограничения разрядности. В вышеприведенном случае происходит отбрасывание старшего разряда и присваиваемое значение становится равным 244, что в итоге выливается в коэффициент заполнения равный 48.8%.
Если есть намерение задавать коэффициент выше 50%, то процедура установки должна выглядеть как-то так:
a5021, компилятор сам раскладывает по байтикам, можно не парится :) OCR1A=500 и всё :)
OCR1AL -- это восьмибитная переменная (младший байт регистра сравнения) и значение "5 * 100" туда никак не записать вследствие ограничения разрядности. В вышеприведенном случае происходит отбрасывание старшего разряда и присваиваемое значение становится равным 244, что в итоге выливается в коэффициент заполнения равный 48.8%.
Если есть намерение задавать коэффициент выше 50%, то процедура установки должна выглядеть как-то так:
При 85% уходит в защиту. Работает с int temp = 5 * 80. На паяльник отдается около 18В. Хороший результат для этого БП. Хотелось бы ограничивать только при старте паяльника, а потом подавать 24В и записать все это в основной скетч
a5021, компилятор сам раскладывает по байтикам, можно не парится :) OCR1A=500 и всё :)
Вы правы. Как-то я упустил, что в ардуиновских объявлениях есть отдельно шестнадцатибитное определение OCR1A. Код для инициализации периферии я генерю с помощью визарда CodeVisionAVR, а там только две восьмибитных половинки. Вобщем, спасибо за замечание по существу.
При 85% уходит в защиту. Работает с int temp = 5 * 80. На паяльник отдается около 18В. Хороший результат для этого БП. Хотелось бы ограничивать только при старте паяльника, а потом подавать 24В и записать все это в основной скетч
А вы попробуйте при включении холодного (это важно) паяльника дать 75% на одну-две секунды, потом 80% на тот же период, потом 85% и т.д. до 100%.
По идее, оно так быстрее разогреет, чем просто 85% держать.
В основной скетч эту фишку, безусловно, стоит внедрить, но это не сделать с наскока. Оно должно вписаться в функционал скетча, а там потребуется немного подумать, как это сделать аккуратно. Первый таймер используется для генерации звука, т.ч. для ШИМ-разогрева таймер нужно будет переинициализировать, а после опять переводить в CTC-режим.
Попробовал: на холодном пояльнике работает начиная с 65%. Напряжение растет каждые 1,5сек до 24В. Блок в защиту не уходит.
Ок. Буду очень ждать Ваши правки в скетче. Без них придется греть от 12В БП.
П.С. если будет проще, то можно отключить таймеры звука или стендбая
Хех. нашел пока "костыль". Делать преднагрев от блока 12В 2А, а потом переключаться на 24В. Колхоз полный, но работает :)))
Сам бы переделал скетч если бы он был на "простом ардуиновском" языке. В СИ для контроллеров вообще не силин, ничего не понятно.
Я набросал процедурку преднагрева, но попробовать как-то все не с руки. Если возьметесь протестировать, то могу выложить.
Конечно возьмусь. Проведу замеры, какие скажете
Может идея и не очень изящная, но чтобы запилить нужный функционал хоть как-нибудь, была просто добавлена процедура preHeat(), которая после нажатия кнопки включения тупо выдает ШИМ-сигнал с заполением 60% и увеличивает его на 5% каждую секунду. Процедура прекращает работу по достижению 100% или превышению порога LOW_TEMP_MARGIN.
Значит следующее:
1. Блок в защиту не уходит
2. Постоянно мигает LED ардуины
3. Температура на экране прыгает в районе 230гр. Хотя выставлена резистором на 185гр.
4. Не реагирует на кнопку выключения
5. Температура на жале медленно ползет вверх по 1-2гр.
Если закомментировать preHeat, то все работает нормально
Да, там оказалось сразу несколько довольно тупых ошибок. Пришлось мне все-таки достать этот проект и допилить, чтобы все работало. В доработанном виде это пока выглядит так:
Механизм преднагрева включен в код с помощью директив условной компиляции. Для того, чтобы его задействовать, надо убрать комментарии в строке 18:
/* #define PREHEAT_ENABLE 1 */
С закоментированной строчкой скетч работает точно так же, как в самой первой версии, т.е. без ШИМ-преднагрева.
Вроде работает, буду тестировать. Спасибо!
Из замеченого: при вколючении на холодную и с ф-цией преднагрева на дисплее начинают быстро отображаться пару секунд значения 9XX, 8XX, 7XX. Потом начинает расти температура как обычно.
И еще вопрос. При калибровке как Вы выводите значения ф-ции getOversampled(), в Serial? (просто заметил USE_DEBUG_CODE)
При включении, когда температура нагревателя ниже ста градусов, на индикатор идет вывод значений в виде деятичной дроби с одним знаком после зпятой. То, что вы увидели, как 9ХХ, на самом деле является 9X.X, т.е., например, 93.2 градуса цельсия.
В секциях USE_DEBUG_CODE нет вывода в последовательный порт. Там переопределяется, что выводить на индикатор. В одном случае это может быть измеренное напряжение на термодатчике в вольтах или вычисленное значение сопротивления этого термодатчика в омах. Данные цифры могут оказаться полезными при калибровке.
Раз уж опять достал этот проект, то сяду доводить до ума индикацию, чтобы циферки не скакали. Советую посматривать за этой веткой, т.к. скорее всего будут и новые версии скетча. Если у кого-то есть предложения по новым фичам, то с интересом с ними ознакомлюсь.
Я Ваш тестер. Спасибо!
Ну конечно хочется меньший опрос индикатора. Чтобы цыфты не так сильно скакали (было как на 702 ликей). Но это может аппаратно.
Еще оставить для тех кто повторять будет, в скетче только значения калибровки. Без HIGH_TEMP_MARGIN, LOW_TEMP_MARGIN. Ну или наоборот без ADC_CALIBRATION_DATA_XXX
т.е тупо измеряешь температуру на жале и вводишь значения с getOversampled() . В общем в идеале для универсального скетча пару настроек. (но это, еще раз повторяюсь ИМХО). Или вообще лафа: вводишть замеряные Омы с паяльника и впуть (не знаю возможно ли такое ввобще)
Можно попробовать сделать как на оригинальном Hakko: калибровка по нажатию кнопки, когда паяльник нагрет до определенной "эталонной" температуры. Правда все равно есть далеко не нулевая вероятность, что у разных нагревателей будет разный ТКС, и следовательно ширина диапазона изменения сопротивления терморезистора...
Если параметры термодатчика не отличаются от экземпляра к экземпляру, то достаточно калиброваться по одной точке. В случае с китайскими нагревателями, так не выйдет. Там нужно знать начальное смещение и наклон графика зависимости сопротивления от температуры. С учетом, что график линейный, двух точек должно быть достаточно.
Еще оставить для тех кто повторять будет, в скетче только значения калибровки. Без HIGH_TEMP_MARGIN, LOW_TEMP_MARGIN. Ну или наоборот без ADC_CALIBRATION_DATA_XXX
Калибровочные данные тут никак не убрать, а вот вычисление границ я осилил с помощью макро-определений. Вид имеет жутковатый, но считает правильно.
Чуть-чуть убавил верхнюю границу максимального нагрева. Было 460 градусов, стало 458. Связано это с тем, что в борьбе за точность измерений пришлось добавить функцию фильтрации значений оцифровки переменника. Чтобы получилась полная шкала, надо 4095 значений оцифровки разделить нацело. Диапазон 185-460 градусов составляет 275 дискрет и если на него разделить 4095, то получается дробное значение. Зато 4095 делится нацело, если делитель равен 273. Таким образом на один градус приходится пятнадцать осчетов АЦП (4095 / 273 = 15).
В изначальном алгоритме, если даныне оцифровки переменника не стабильны, то это приводит к тому, целевая температура нагрева начинает гулять вслед за переменником. Теперь любое значение из диапазона 15 отсчетов означает установку одной и той же температуры. Стабильность удержания заметно возрасла.
Немного потестирую и скоро выложу.
т.е тупо измеряешь температуру на жале и вводишь значения с getOversampled() . В общем в идеале для универсального скетча пару настроек. (но это, еще раз повторяюсь ИМХО). Или вообще лафа: вводишть замеряные Омы с паяльника и впуть (не знаю возможно ли такое ввобще)
Процудуру калибровки паяльника я вообще вижу такой, чтобы совсем не трогать исходники. Торетически, это может выглядеть так: включаем питание с зажатой кнопкой -- паялка вместо обычного своего функционала переходит в режим калибровки. При вращении ручки "от края до края", на индикаторе просто отображается значение, скажем, от 10 до 500. Выставляем холодную точку: смотрим температуру на градуснике и выставляем такую же цифру на индикаторе поворотом ручки. Нажимаем кнопку -- значения оцифровки с термодатчика и цифра с индикатора записываются в энергонезависимую память (EEPROM) ардуины. Теперь приступаем к калибровке горячей точки, для чего опускаем паяльник с градусником в кипяток. Опять выставляем переменником цифру на индикаторе, соответствующую температуре градусника и нажимаем кнопку второй раз. Вторая пара калибровочных значений записывается в EEPROM. На этом калибровка завершена. Передергиваем питание и паялка включается в своем обычном режиме, но уже будучи откалиброванной.
это не теоретически, это практически так и выглядит - таящий лед(0 градусов) и кипящая вода(100)
Доброго времени!
Таки собрал первую половину своей паяльной станции (т.е. только паяльник без фена, фен в планах если доллар еще раза в 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 разъем дозаказать.
фен в планах если доллар еще раза в 2 не скаканет).
блин. довёл обама-обезьяна россиян до звиздеца.
Этого исключить нельзя, но сжечь 7805 тоже нужно еще постараться. Рабочая температура у него по заявлениям производителей до 125 градусов, стало быть, тепловое разрушение начнется выше этой точки. Когда именно, заранее знать не возможно, но мне попадались живые регуляторы (7805) при распайке старой электроники, которые имели отчетливые признаки долгой работы при высоких температурах -- сильное окисление теплоотвода с разноцветными разводами, похожими на цвета побежалости.
Если нет возможности подцепить радиатор, можно прикрутить регулятор прямо на плату. Это не лучшее ре шение с точки зрения организации теплоотвода, но намного лучше, чем оставлять регулятор без теплоотвода вообще или с теплоотводом недостаточного размера.
Еще есть смысл уменьшить энергопотребление самого устройства. В вашем случе можно уменьшать яркость экрана или гасить его совсем по истечении какого-то времени после установки рабочей температуры.
т.к. намечается тепловой пробой, то вероятность такого чуть больше 80%.
на LM рассеивается нефиговая мощность: 19-5=14. ток потребления от LM пусть 100мА.
5*0,1=0,5Ватта, да плюс рассеивание от 14 Вольт(14*0,1)... там радиатор нужен 10кв.д.
у меня, в самодельном блоке питания для Ардуинок с 12 до 5, (всего 7Вольт!) и LM стоит на радиаторе от комьютерного БП с температурой 50 С.
Зарание прошу прощение за ламерские посты, я не далек в электронике, это моя первая поделка, баловство с Ардуино+светодиоды или ESP+Реле не в счет (стоит у меня в комнате wi-fi реле на ESP-01, глючное, отложил исправление до лучших времен). Да и физику учил в школе 15 лет назад и уже 14 лет как забыл ее напрочь.
Рабочая температура у него по заявлениям производителей до 125 градусов, стало быть, тепловое разрушение начнется выше этой точки.
Это и пугает, возможности замерить точную температуру нет, китайская цешка показывает 100 градусов (+-5) на металической части стабилизатора при выключеном паяльнике и градусов на 10 больше при нагревании (хотя это может быть показалось, значения скачат постоянно), в общем порог то рядом. Если капнуть слюну на стык радиатора-пластинки и стабилизатора, то она начинает кипеть и испаряться. Долго в рабочем режиме пока не гонял, максимум минут 10.
т.к. намечается тепловой пробой, то вероятность такого чуть больше 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 понадобится заводить).
процессор рассеивает, но он с радиатором.
физика нужна в объёме средней школы - Закон Ома и всё.
тут три варианта (с учётом того, что сама Ардуина потребляет не так много и на ней есть свой стабилизатор):
1. поставить две LM последовательно: с 19 до 12 и с 12 до 5, но радиатор нужен по любому на обе.
2. поставить импульсный DC-DC (на ибее полно понижающих и цена копеечная). тут радиатор не нужен.
3. оставить как есть, но на LM навесить радиатор что б температура на х.х. была не выше 50С.
Взял кусок радиатора побольше (примерно 1х3см), температура в районе 75 градусов.
Видимо это правильное направление, заказал на ебеи нормальные радиаторы, пока с этим гоняю, 30 минут - полет нормальный.
Еще одна странность, у меня подсветка экрана была прокинута на +5в (как бы не нужна мне регулировка и не стал лишний ШИМ вывод занимать на ардуине), для эксперимента отрезал проводник, думал без подсветки проверить нагрев стабилизатора... В итоге подсветка стала тусклее, но не выключилась. Замерил напряжение на ножке LED - 4.2 вольта, от куда не понял О_о , нужно схему экрана глянуть будет, вроде китаец давал на скачку её.
Может лучше заказать какой-нибудь нормальный импульсный Step-Down, и не обогревать помещение КРЕНкой?
Может лучше заказать какой-нибудь нормальный импульсный Step-Down, и не обогревать помещение КРЕНкой?
Отличный вариант, но ждать месяц-два доставки, а я уже во всю пользуюсь этим паяльником :)
В будущем же, планируется фен подключать, соответственно БП от ноутбука встроится в корпус паяльной станции (какой уж он будет не знаю, или от ATX БП возьму или сам изготовлю), ну а на ардуину отдельный БП поставить 220в->5в можно будет. Так не надежнее будет?
Это и пугает, возможности замерить точную температуру нет, китайская цешка показывает 100 градусов (+-5) на металической части стабилизатора при выключеном паяльнике и градусов на 10 больше при нагревании (хотя это может быть показалось, значения скачат постоянно), в общем порог то рядом. Если капнуть слюну на стык радиатора-пластинки и стабилизатора, то она начинает кипеть и испаряться. Долго в рабочем режиме пока не гонял, максимум минут 10.
Прислони паяльник - покажет температуру :)
Прислони паяльник - покажет температуру :)
По логике ни чего из этого не выйдет, часть тепла рассеется пока дойдет от жала до терморезистора. Но блин идея заманчивая, почти 3 часа ночи, встал ребенка покормить, теперь сижу как дурак жду когда прогреется стабилизатор :)
Добрый день!
Собираю себе паяльную станцию. Собрал пока блак управления паяльником, все хорошо только одно НО!!!!. Включаю нагрев паяльника к примеру на 250гр, через 10 сек на экране показывается что паяльник нагрелся до 250гр, проверяю термопорай от мультиметра температуру на жале, а там градусов 60-80. я конечно понимаю что передать нагрев от нагревателя на кончик жала надо время, но всетаки может есть у кого опыт в решении данной задачи?
Заранее спасибо.
В изначальном алгоритме, если даныне оцифровки переменника не стабильны, то это приводит к тому, целевая температура нагрева начинает гулять вслед за переменником. Теперь любое значение из диапазона 15 отсчетов означает установку одной и той же температуры. Стабильность удержания заметно возрасла.
Немного потестирую и скоро выложу.
Как идет тестирование?
Вы правы, надо было давно выложить. :)
Вот версия 1.2, где полностью переработан механизм удержания температуры:
Попробуйте, отпишитесь.
Залил и подставил свои значения
Чисто визуально ничего не изменилось. Как и в предыдущей версии цифры на индикаторе постоянно меняются, что не очень информативно. Видео: https://youtu.be/70Aoi2jpj10
Какая-то уж очень сильная нестабильность. У вас нет осциллографа, чтобы посмотреть стабильность напряжения по линии 5 вольт?
Нет к сожалению. Попробую снять плату с блока питания, может наводки от него
Можно в качестве эксперимента сделать вот что: закомментировать строку 689 и раскомментировать строки 691-695. Данная правка приведет к тому, что в обычном режиме на индикаторе будет не измеренная температура, а та, что задается переменником. Она уж точно должна стоять, как влитая.
Можно в качестве эксперимента сделать вот что: закомментировать строку 689 и раскомментировать строки 691-695. Данная правка приведет к тому, что в обычном режиме на индикаторе будет не измеренная температура, а та, что задается переменником. Она уж точно должна стоять, как влитая.
Попробовал этот эксперимент. При достижении температуры цыфры перестают меняться. Если снять плату с БП, то "дребезг" значений становится 1-2 градуса (а не десятки как у меня).
В общем нужно либо снимать плату с БП, либо как то ее экранировать
Входная цепь с термодатчика на плате довольно длинная и она может неплохо "ловить" помехи от импульсных блоков питания, особенно, если с качеством у последних не очень. Для того, чтобы значение на индикаторе изменилось на 1 градус, напряжение на измерительном входе ардуины должно измениться примерно на 3 милливольта. Наводки с размахом 30мв в непосредственной близости от импульсника не выглядят чем-то из ряда вон выходящим, что скорее всего и является объяснением наблюдаемой картины.
Для уменьшения негативного влияния имульсных помех, экранирование будет безусловно полезным. Еще могу посоветовать увеличить значение емкости C1 до 22-47 нанофарад (0.022-0.047мкф). Но без фанатизма. Чем выше эта емкость, тем датчик становится более инертным и его показания начинают запаздывать.
Не сочтите за рекламу, но если кто вдруг, как и я, никак не может определиться с корпусом для паялки, есть, имхо, отличный вариант по акции, в комплекте с экранчиком)
После твоей публикации, продавец получил сразу больше 120 заказав за этот день ))))))
Входная цепь с термодатчика на плате довольно длинная и она может неплохо "ловить" помехи от импульсных блоков питания, особенно, если с качеством у последних не очень. Для того, чтобы значение на индикаторе изменилось на 1 градус, напряжение на измерительном входе ардуины должно измениться примерно на 3 милливольта. Наводки с размахом 30мв в непосредственной близости от импульсника не выглядят чем-то из ряда вон выходящим, что скорее всего и является объяснением наблюдаемой картины.
Для уменьшения негативного влияния имульсных помех, экранирование будет безусловно полезным. Еще могу посоветовать увеличить значение емкости C1 до 22-47 нанофарад (0.022-0.047мкф). Но без фанатизма. Чем выше эта емкость, тем датчик становится более инертным и его показания начинают запаздывать.
Я никогда не занимался экранированием, подразумеваю сделать пластину между платой и БП. Из чего лучше?
Можно не пластину, а спаять что-то типа поддона из того же стеклотекстолита. Вот по такому принципу:
Внутренняя поверхность (экран) не должна иметь электрического соединения ни с чем больше, кроме "массы" платы паялки. Фольгированный текстолит прекрасно режется кровельными ножницами, т.ч. сложностей с обработкой особых не должно быть.
Чтобы оценить, насколько такое решение может быть эффективно, можно провести эксперимент, склеив поддон из картона и выложив его изнутри пищевой фольгой. Только опять же не забываем, что "экран" нужно соединить с массой платы и желательно только в одной точке. Разумеется, проводники платы не должны касаться экрана.
Здравствуйте, уважаемый a5021!
Прочитал всю тему, восхищён проделанной работой, проект получился просто потрясающий!
Только обратите внимание, что схема дана для индикатора с общим анодом. Для общего катода подключение будет отличаться.
Имеется идикатор с ОК, собственно и хочу спросить каким образом его можно подключить? Изначально возникла мысь изменить написанный код, но уровень владения языком с/с++ оставляет желать лучшего...
Спасибо.
Для подключения индикатора с общим катодом необходимо внести одну небольшую правку в схему и, соответственно, в плату. Измененное место в схеме будет выглядеть так:
Как можно видеть, PNP транзисторы заменены на NPN и их эмиттеры посажены на землю, вместо плюса.
Да, безусловно, как посадить катоды на землю прекрасно понимаю, а будет ли так работать, имею ввиду без програмной смены полярности на выходах мк (порты 0-7) ?
Ребят. схема протестирована. Может заказать печатки в Китае?
Производство 10-и штук 15-20$, могу взять техпроцесс на себя. Доставка из Москвы + почта (ваша). Ничего не зарабатываем, идеи в массы. (Автор, если не против)
От себя добавлю
Минусы:
Нужно делать калибровку в коде. В соседней ветке "крендель" делает с переменниками
Плюсы:
ОЧЕНЬ грамотное программирование МК. т.е. не 30 строк тупого кода, а именно вдумчивое управление МК.
Автору, СПАСИБО!
Скетч править -- это само собой. Сейчас глянул, всего придется вносить изменения в три строки. Одна строка (номер 207) в обработчике прерывания COMPA, и две (261-262) в COMPB. Смысл простой -- там, где для общего анода бит выставляется, для общего катода он должен сбрасываться и наоборот.
А получилось что-то с экранированием?