Почему бы не сделать себе станцию с паяльным феном, подумал я? И начал потихоньку собирать материал, читать про готовые устройства, собирать комплектующие. В итоге — получилось устройство, о котором я хочу разсказать.
Будет несколько фото, радиолюбительства, программирования ардуино и самодельщины, или DIY.
И еще: я понимаю, что купить готовое — намного дешевле, чем сделать самому.
Кого не пугает, кто любит делать руками и программировать микроконтроллеры АВР — добро пожаловать :)
Сам фен, или рукоятка, продавался в украинском интернет магазине, как запасной для китайской станции с труднозапоминаемым названием. Там же была куплена подставка со встроеным магнитом. Цена была очень приятная, похоже сбывали остатки. Сейчас его нет в наличии, но такое же есть на али,
тут и
тут, и еще подставка:
раз и
два.
В рукоятке находится нагреватель с датчиком температуры, вентилятор турбинка и геркон. Я только добавил кнопочку с резистором, но об этом — ниже. На али куплен такой
набор насадок.
Контролер собирал сам. Он содержит ПИД регулятор температуры, позволяет работать с двумя уставками, регулировать обороты вентилятора. Температура или скорость вентилятора выводятся на цифровой дисплей. На подставке фен удерживается на 100 градусах. При снятии — за 2..3 секунды выходит на режим и формируется короткий звуковой сигнал. При нахождении на подставке более 10 минут — фен выключается, на 8-й и 9-й минуте формируются звуковые сигналы. Температура и скорость вентилятора задаются с помощью энкодера. Коэфициенты ПИД регулятора — в диалоговом окне монитора порта, при подключении к компьютеру.
Органы управления
На передней панели находятся тумблер питания и энкодер, которым осуществляется регулировка параметра. Короткое нажатие на энкодер переключает установки температуры или оборотов вентилятора. Удержание до 4 сек. — переключает режим установки первого или второго заданий температуры. При выключении тумблера осуществляется охлаждение фена и потом отключение устройства от сети.
При работе с феном, дисплей отображает поточную темературу. При нахождении на подставке — задание или поточную температуру, это выбирается удержанием кнопки энкодера более 4 сек.
Кнопка, смонтированая на самом фене, позволяет работать с другим заданием температуры, пока она нажата. Например, если нужно кратковременно «прожарить» более высокой температурой.
Ссылки на али:
разъем на 9 пин,
энкодер.
Индикация
Элементы индикации: трехрозрядный цифровой индикатор и четыре лампочки. Первая лампочка: на дисплее отображается температура. Вторая — отображается второе задание. третья (зеленая) — отображается задание температуры, четвертая (синяя) — отображается производительность вентилятора.
Ссылки на али:
неоновые лампочки,
зеленые и
синие.
Корпус
Корпус Z2P куплен в
украинском интернет магазине. Рисунок передней панели сделан в Corel draw. Там же нарисован эскиз для вырезания отверстий. Порезка осуществлялась лазером на ЧПУ. Детальнее описано в конце
этого обзора.
Передняя панель распечатана на обычной бумаге. После вырезания отверстий — прогнана через ламинатор и прихвачена двусторонним скотчем к пластику.
Комплектующие, сборка
Донором индикаторов послужила часть неизвестного творения инженеров СССР.
Устройство было куплено на блошином рынке, несколько лет назад. Один индикатор уже был разбит, но за него просили суму, эквивалентную стоимости бутылки водки. У меня лично электронная не бытовая промышленость СССР вызывает восхищение.
С помощью двигателя шпинделя от ЧПУ с фрезой, я акуратненько вырезал три индикатора с дешифраторами.
Еще фото устройства индикации
Радиодетали и комплектующие приобретались на али и в украинских интернет магазинах. Большинство — было найдено в закромах.
Ссылки на али:
разъемы питания и нагревателя 2 пин, сигнальные разъемы
XH и
Dupont
В качестве мозгов — выбрана Arduino pro. Она не дорогая и справляется со всеми функциями. Приобретена в Украине, вот
здесь.
Печатная плата нарисована в Sprint layout, вытравлена в перекиси с лимонной кислотой. Сверление и фрезеровка осуществлены с помощью
ЧПУ станка.
Печатная плата
Источник питания 24 вольт — выдран с какого-то адаптера.
Собраная плата
Платы в корпусе, пробный запуск
Собранное устройство
Схема
Принципиальная схема устройства
Устройство индикации
Программа
Текст управляющей программы
// Hot air gun temperature controller
// Arduino pro mini
// 5V; 16 MHz; 328P
// Settings
#define FAN_MINIMAL 20
#define SV_MINIMAL 100
#define SV_MAXIMAL 450
#include <EEPROM.h>
// Display
#define HV 4
#define DATA 5
#define LATCH 11
#define CLK 18
byte regL, regH;
// Control
#define ENC_CLK 3
#define DIRECT 7
#define BUTTON 8
#define POW_SW 9
int bt_timer;
bool bt_tap, bt_hold, bt_hold_long;
byte SV_show_timer;
volatile bool power_control;
bool instrument_connected;
bool device_hot;
unsigned int sleep_timer;
bool sleeping;
byte eeprom_timer;
bool eeprom_store;
bool show_SV_on_iddle;
// Inputs
#define INSTRUMENT 13
#define TEMP_PV 0
#define USE 1
int PV, PV_buf [8];
byte PV_buf_index;
bool reg_action;
int disp_PV, disp_PV_buf[5];
byte disp_PV_buf_index;
volatile int SV, SV2;
int task;
bool SV2_mode;
float error, deltaPV, prevPV, integr;
byte use_value;
byte use_state_temp, use_state_temp_old, use_state_stable_count;
volatile byte use_state;
// 0: instrument on holder
// 1: instrument in use, button pressed
// 2: instrument in use, button released
volatile bool stabilized;
byte stabile_timer;
// Outputs
#define HEAT 10
#define FAN 6
#define RELAY 12
#define SUPPLY 17
#define BEEP 19
volatile bool triac_control;
volatile int power;
float task_power;
float kp, ki, kd;
byte fan_speed;
volatile byte set_fan_speed;
bool relay_state, relay_state_old;
volatile bool let_work;
byte start_timer;
byte beep_timer;
byte display_mode;
// 0: PV
// 1: SV
// 2: Fan
//enum {PV,SV,FAN} display_mode; //https://alexgyver.ru/lessons/variables-types/
// Serial dialog
String inData;
char inChar[15];
float float_var;
byte inSymbol;
void setup() {
Serial.begin(9600);
Serial.println("Hello");
print_help();
pinMode(HV,OUTPUT);
pinMode(DATA,OUTPUT);
pinMode(LATCH,OUTPUT);
pinMode(CLK,OUTPUT);
pinMode(ENC_CLK,INPUT_PULLUP);
pinMode(DIRECT,INPUT);
pinMode(BUTTON,INPUT);
pinMode(POW_SW,INPUT);
pinMode(INSTRUMENT,INPUT);
pinMode(HEAT,OUTPUT);
pinMode(FAN,OUTPUT);
pinMode(RELAY,OUTPUT);
pinMode(SUPPLY,OUTPUT);
pinMode(BEEP,OUTPUT);
TCCR1A = 0;
//TCCR1B = 1<<WGM12 | 1<<CS11 | 1<<CS10; // enable CTC, prescale 64
TIMSK1 = 1<<OCIE1A; // enable compare interrupt
//OCR1A = 1250;
// 250: 1mS for max power
// 2250: 9mS for min power
// 25: 0.1ms for open triac
TCCR2A = 0; //TMR2 normal mode
TCCR2B = 1<<CS22 | 1<<CS21 | 1<<CS20; // prescale 1024, overflow each 1/(16000000/1024/256=61Hz) = 16.4mS
TIMSK2 = 1<<TOIE2; // enable overflow interrupt
TCNT2 = 0; // reset counter
EICRA = 1<<ISC11 | 1<<ISC01; // Falling edge generate interrupt
EIMSK = 1<<INT1 | 1<<INT0; // Enable interrupt
SV = 0; // = 300;
power = 0;
start_timer = 0;
sleep_timer = 0;
TCCR0B = 1; // default is 3 (prescaler 64)
eeprom_timer = 255;
analogReference(INTERNAL);
GetSV();
GetPIDkoef();
print_PID();
}
void loop() {
power_control = !(digitalRead(POW_SW));
instrument_connected = (digitalRead(INSTRUMENT));
digitalWrite(HV,(!(sleeping)));
if (PV>80) device_hot = 1; if (PV<50) device_hot = 0;
if (power_control) digitalWrite(SUPPLY,1);
else digitalWrite(SUPPLY,(device_hot && instrument_connected));
if (use_state > 0) sleep_timer = 0;
if (use_state > 0)
{
if (SV2_mode) task = SV2; else task = SV;
}
else {if (sleeping) task = 0; else task = 100;}
if (use_state > 0) {SV2_mode = (use_state == 1);}
relay_state = (PV < 480)&&(power_control)&&(!(sleeping));
if ( relay_state_old != relay_state )
{
let_work = 0; //Serial.print("Let work: "); Serial.println(let_work);
delay(3200); //500*64
relay_state_old = relay_state;
digitalWrite(RELAY,relay_state); //Serial.print("Relay: "); Serial.println(relay_state);
delay(3200); //500*64
let_work = 1; //Serial.print("Let work: "); Serial.println(let_work);
}
if (reg_action)
{
reg_action = 0;
PV = 0;
for (byte i = 0; i < 4; i++)
{
PV = PV + PV_buf[i];
}
PV = PV >> 2;
if (instrument_connected) PV = map(PV,0,500,25,500); else PV = 0;
error = task - PV;
deltaPV = prevPV - PV;
prevPV = PV;
if ( (task_power>0) && (task_power<1000) ) integr = integr + (ki * error);
task_power = ((kp * error) + integr + (kd * deltaPV));
power = constrain(task_power, 0, 1000);
//Serial.print("Power: "); Serial.print(task_power); Serial.print(", "); Serial.println(power);
disp_PV_buf[disp_PV_buf_index] = PV;
disp_PV_buf_index++;
if (disp_PV_buf_index>4)
{
disp_PV_buf_index = 0;
disp_PV = 0;
for (byte i = 0; i < 4; i++)
{
disp_PV = disp_PV + disp_PV_buf[i];
}
disp_PV = disp_PV >> 2; //Serial.println(use_state);
}
stabilized = ((abs(PV - task)) < 5) && (use_state > 0);
}
if (power_control)
{
if (sleeping)
{
if (device_hot) fan_speed = 30; else fan_speed = 0;
}
else
{
if (use_state > 0) fan_speed = set_fan_speed; else fan_speed = 20;
}
}
else fan_speed = 50;
if (fan_speed < 80) analogWrite(FAN, map(fan_speed,0,80,0,100)); else analogWrite(FAN, map(fan_speed,80,100,100,255));
if (eeprom_store)
{
eeprom_store = 0;
//beep_timer = 12; // 200 mS tone
StoreSV();
}
switch (display_mode)
{
case 0: DisplayValue(disp_PV); break;
case 1: if (SV2_mode) DisplayValue(SV2); else DisplayValue(SV); break;
case 2: DisplayValue(set_fan_speed); break;
}
if (bt_tap)
{
bt_tap = 0;
sleep_timer = 0;
if ( (power_control)&&(instrument_connected) ) {if (display_mode == 2) display_mode = 0; else (display_mode = 2);}
}
if (bt_hold)
{
bt_hold = 0;
sleep_timer = 0;
if ( (power_control)&&(instrument_connected) ) {if (use_state == 0) SV2_mode = !(SV2_mode);}
}
if (bt_hold_long)
{
bt_hold_long = 0;
if (use_state == 0) show_SV_on_iddle = !(show_SV_on_iddle);
}
SetLamp(0,( ((display_mode == 0)||(display_mode == 1))&& instrument_connected ) );
SetLamp(1,SV2_mode);
SetLamp(2,((display_mode == 1)));
SetLamp(3,(display_mode == 2));
if(Serial.available() >0)
{
inData = "";
while(Serial.available() > 0)
{
delay(1280); //20*64
inSymbol = Serial.read();
inData += char(inSymbol);
}
inData.toCharArray(inChar, inData.length() +1);
float_var = atof(inChar);
//Serial.println(inData);
if (inData.indexOf("help") > -1)
{
print_help();
}
else if (inData.indexOf(" sv1") > -1)
{
SV = int(float_var);
Serial.print("New SV is ");
Serial.println(SV);
eeprom_timer = 0;
}
else if (inData.indexOf(" sv2") > -1)
{
SV2 = int(float_var);
Serial.print("New SV2 is ");
Serial.println(SV2);
eeprom_timer = 0;
}
else if (inData.indexOf(" fan") > -1)
{
set_fan_speed = byte(float_var);
Serial.print("New FAN value is ");
Serial.println(set_fan_speed);
eeprom_timer = 0;
}
else if (inData.indexOf("list") > -1)
{
print_PID();
}
else if (inData.indexOf("store") > -1)
{
StorePIDkoef();
Serial.println("Stored");
}
else if (inData.indexOf(" p") > -1)
{
kp = float_var;
Serial.print("kp: ");
Serial.println(kp);
}
else if (inData.indexOf(" i") > -1)
{
ki = float_var;
Serial.print("ki: ");
Serial.println(ki);
}
else if (inData.indexOf(" d") > -1)
{
kd = float_var;
Serial.print("kd: ");
Serial.println(kd);
}
else print_help();
}
}
ISR(TIMER2_OVF_vect) //timer 2 interrupt
{
// temperatute measuring
PV_buf[PV_buf_index] = analogRead(TEMP_PV)>>1;
//Serial.print(PV_buf[PV_buf_index]); Serial.print(" "); Serial.println(PV_buf_index);
PV_buf_index ++;
if (PV_buf_index > 4) {PV_buf_index = 0; reg_action = 1;}
// use state recognize
use_value = analogRead(USE)>>2; // 0, 142, 255
if (use_value < 70) use_state_temp = 0;
else if (use_value < 190) use_state_temp = 1;
else use_state_temp = 2;
if ( use_state_temp_old == use_state_temp ) {if (use_state_stable_count < 255) use_state_stable_count++;}
else {use_state_stable_count = 0; use_state_temp_old = use_state_temp;}
if ( use_state_stable_count == 20 ) use_state = use_state_temp;
// button manage
if (digitalRead(BUTTON)) if ( (bt_timer>6) && (bt_timer<62) ) {bt_tap = 1; bt_hold = 0; bt_hold_long = 0;}// >100mS, <1000mS
if (bt_timer==62) {bt_tap = 0; bt_hold = 1; bt_hold_long = 0;}// = 1000mS
if (bt_timer==244) {bt_tap = 0; bt_hold = 1; bt_hold_long = 1;}// = 4000mS
if (!(digitalRead(BUTTON))) {if (bt_timer<32768) bt_timer++;} else bt_timer = 0;
// stabilization beep
if (stabilized) {if (stabile_timer < 255) stabile_timer ++;}
else stabile_timer = 0;
if (stabile_timer == 182) beep_timer = 12; // after 3000 ms, 200 mS tone
// SV show
if (power_control)
{
if ( (use_state == 0) && (show_SV_on_iddle) ) { if (display_mode == 0) display_mode = 1;} else
{ if (SV_show_timer < 122) {SV_show_timer++;} else {if (display_mode ==1) display_mode = 0;} }
}
else display_mode = 0;
// eeprom store timer
if (eeprom_timer == 250) eeprom_store = 1; // 4.1 sec
if (eeprom_timer < 255) eeprom_timer++;
// initial start delay
if (start_timer < 255) start_timer++;
if (start_timer == 122) let_work = 1;
// sleep
if (sleep_timer < 65500) sleep_timer++;
if (sleep_timer == 29268) beep_timer = 122; // 2000mS // about 480s, 8min
if (sleep_timer == 32927) beep_timer = 244; // 4000mS // about 540s, 9min
sleeping = (sleep_timer > 36585); // about 600s, 10min
// beep
if (beep_timer > 0) beep_timer--;
digitalWrite(BEEP,(beep_timer > 0));
}
ISR(TIMER1_COMPA_vect)
{
if (triac_control)
{
TCCR1B = 0;
digitalWrite(HEAT,LOW);
triac_control = 0;
}
else
{
TCNT1 = 0; //TCCR1B = 0;
digitalWrite(HEAT,HIGH); //delayMicroseconds(100); digitalWrite(HEAT,LOW);
OCR1A = 25;
triac_control = 1;
}
}
ISR(INT0_vect) //INT0 interrupt, Zero cross
{
triac_control = 0;
//OCR1A = 1250;
// 250: 1mS for max power
// 2250: 9mS for min power
// 25: 0.1ms for open triac
TCNT1 = 0;
if (power > 0)
{
OCR1A = map(power,1,1000,2250,250);
TCCR1B = 1<<WGM12 | 1<<CS11 | 1<<CS10; // enable CTC, prescale 64
}
else digitalWrite(HEAT,LOW);
}
ISR(INT1_vect) //INT1 interrupt, encoder
{
if ( ((display_mode == 0) || (display_mode == 1)) && let_work && power_control && instrument_connected)
{
if (SV2_mode)
{
if (digitalRead(DIRECT)) {if (SV2 < SV_MAXIMAL) SV2 = SV2 + 5;}
else {if (SV2 > SV_MINIMAL) SV2 = SV2 - 5;}
}
else
{
if (digitalRead(DIRECT)) {if (SV < SV_MAXIMAL) SV = SV + 5;}
else {if (SV > SV_MINIMAL) SV = SV - 5;}
}
eeprom_timer = 0;
SV_show_timer = 0;
display_mode = 1;
Serial.print("SV: "); Serial.print(SV); Serial.print("; SV2: "); Serial.println(SV2);
}
if (display_mode == 2)
{
if (digitalRead(DIRECT)) {if (set_fan_speed < 100) set_fan_speed = set_fan_speed + 2;}
else {if (set_fan_speed > FAN_MINIMAL) set_fan_speed = set_fan_speed - 2;}
eeprom_timer = 0;
Serial.print("Fan: "); Serial.println(set_fan_speed);
}
sleep_timer = 0;
//beep_timer = 31; // 500mS
//digitalWrite(BEEP, HIGH); delay(5000); digitalWrite(BEEP, LOW);
}
void StoreSV()
{
EEPROM_int_write(0,SV); // 0,1
EEPROM_int_write(2,SV2); // 2,3
EEPROM.update(4,set_fan_speed); // 4
EEPROM.update(5,show_SV_on_iddle); // 5
}
void GetSV()
{
SV = EEPROM_int_read(0); // 0,1
if (SV > SV_MAXIMAL) SV = 0;
SV2 = EEPROM_int_read(2); // 2,3
if (SV2 > SV_MAXIMAL) SV2 = 0;
set_fan_speed = EEPROM.read(4); // 4
show_SV_on_iddle = EEPROM.read(5); // 5
}
void GetPIDkoef()
{
kp = EEPROM_float_read(5); // 5,6,7,8
ki = EEPROM_float_read(9); // 9,10,11,12
kd = EEPROM_float_read(13); // 13,14,15,16
}
void StorePIDkoef()
{
EEPROM_float_write(5,kp); // 5,6,7,8
EEPROM_float_write(9,ki); // 9,10,11,12
EEPROM_float_write(13,kd); // 13,14,15,16
}
void print_PID()
{
Serial.print("p: ");
Serial.print(kp);
Serial.print("; i: ");
Serial.print(ki);
Serial.print("; d: ");
Serial.print(kd);
Serial.println(";");
}
int EEPROM_int_read(int addr) {
byte raw[2];
for(byte i = 0; i < 2; i++) raw[i] = EEPROM.read(addr+i);
int &num = (int&)raw;
return num;
}
void EEPROM_int_write(int addr, int num) {
byte raw[2];
(int&)raw = num;
for(byte i = 0; i < 2; i++) EEPROM.update(addr+i, raw[i]);
}
float EEPROM_float_read(int addr) {
byte raw[4];
for(byte i = 0; i < 4; i++) raw[i] = EEPROM.read(addr+i);
float &num = (float&)raw;
return num;
}
void EEPROM_float_write(int addr, float num) {
byte raw[4];
(float&)raw = num;
for(byte i = 0; i < 4; i++) EEPROM.update(addr+i, raw[i]);
}
void DisplayValue(int data)
{
byte dig1,dig2,dig3;
if (data < 100) dig3 = 0x0F; else dig3 = (data/100)%10; //Serial.print(dig3); Serial.print(" ");
if (data < 10) dig2 = 0x0F; else dig2 = (data/10)%10; //Serial.print(dig2); Serial.print(" ");
dig1 = (data%10); //Serial.print(dig1); Serial.print(" ");
regH = regH & 0xF0;
regH = regH + dig3; //Serial.print(regH,BIN); Serial.print(" ");
regL = dig2;
regL = regL << 4;
regL = regL + dig1; //Serial.print(regL,BIN); Serial.print(" ");
Update595();
}
void SetLamp(byte lamp, bool mode)
{
//Serial.print(lamp); Serial.print(" ");
switch (lamp)
{
case 0: if (mode) regH = regH | 0b00010000; else regH = regH & 0b11101111; break;
case 1: if (mode) regH = regH | 0b00100000; else regH = regH & 0b11011111; break;
case 2: if (mode) regH = regH | 0b01000000; else regH = regH & 0b10111111; break;
case 3: if (mode) regH = regH | 0b10000000; else regH = regH & 0b01111111; break;
}
//Serial.print(regH,BIN); Serial.print(" "); Serial.print(regL,BIN); Serial.println(" ");
Update595();
}
void Update595()
{
byte i, regH_, regL_;
regH_ = regH;
regL_ = regL;
digitalWrite(LATCH,LOW);
digitalWrite(CLK,LOW);
for (i = 0; i < 8; i++)
{
if (regH_ & 0x80)
{
digitalWrite(DATA,HIGH);
//Serial.print("Y ");
}
else
{
digitalWrite(DATA,LOW);
//Serial.print("N ");
}
digitalWrite(CLK,HIGH);
digitalWrite(CLK,LOW);
regH_ = regH_ << 1;
}
for (i = 0; i < 8; i++)
{
if (regL_ & 0x80)
{
digitalWrite(DATA,HIGH);
//Serial.print("Y ");
}
else
{
digitalWrite(DATA,LOW);
//Serial.print("N ");
}
digitalWrite(CLK,HIGH);
digitalWrite(CLK,LOW);
regL_ = regL_ << 1;
}
digitalWrite(LATCH,HIGH);
digitalWrite(LATCH,LOW);
}
void print_help()
{
Serial.println("***********************************");
Serial.println("Commands");
Serial.println("help: shows help");
Serial.println("XX sv1: set new SV");
Serial.println("XX sv2: set new SV2");
Serial.println("XX fan: set new fan speed");
Serial.println("XX.XX p: set new P value");
Serial.println("XX.XX i: set new I value");
Serial.println("XX.XX d: set new D value");
Serial.println("list: show PID parameters");
Serial.println("store: save PID parameters to FLASH");
Serial.println("***********************************");
}
Регулировка температуры осуществляется по ПИД алгоритму. Подробнее о нем можно почитать
здесь и
здесь
Я делал по принципу библиотеки от
AlexGyver.
Настройка коефициентов регулятора осуществляется вро=учную через монитор порта Arduino. Список доступных команд выводится командой help.
Регулировка мощности нагревателя осуществляется фазоимпульсным методом. Отслеживается переход синусоиды через ноль и формировка задержки подачи имульса открывания на симистор.
Осциллограмма сигнала на входе прерывания 0
Формированием задержки занимается таймер1
TCCR1A = 0;
//TCCR1B = 1<<WGM12 | 1<<CS11 | 1<<CS10; // enable CTC, prescale 64
TIMSK1 = 1<<OCIE1A; // enable compare interrupt
//OCR1A = 1250;
// 250: 1mS for max power
// 2250: 9mS for min power
// 25: 0.1ms for open triac
На таймере2 реализовано перрывание каждые 16,4 миллисекунд
TCCR2A = 0; //TMR2 normal mode
TCCR2B = 1<<CS22 | 1<<CS21 | 1<<CS20; // prescale 1024, overflow each 1/(16000000/1024/256=61Hz) = 16.4mS
TIMSK2 = 1<<TOIE2; // enable overflow interrupt
TCNT2 = 0; // reset counter
Кроме того, задействованы аппаратные прерывания от энкодера и детектора нуля
EICRA = 1<<ISC11 | 1<<ISC01; // Falling edge generate interrupt
EIMSK = 1<<INT1 | 1<<INT0; // Enable interrupt
ШИМ вентилятора реализован на таймере0. По умолчанию, он тактируется через делитель на 64 и формирует частоту 976 Гц.
Вентилятор при этом сильно шумит и греется транзистор VT5. Избавится от дросселя L2 и других элекентов не желательно. В своем
блоке питания я сталкивался с проблемой, когда вентилятор не хотел запускаться при питании ШИМом. Еще провода питания вентилятора проходят рядом с проводами от термопары, и лишние помехи тут ни к чему.
Я отключил этот предделитель, чтобы избавится от писка вентилятора, теперь частота ШИМ составляет 62.5 кГц
TCCR0B = 1; // default is 3 (prescaler 64)
Но так как delay работает от этого таймера, необходимо не забывать умножать желаемое время в delay на 64.
Для аналоговых входов задействован внутренний опорный источник 1,1 В
analogReference(INTERNAL);
Одну проблему я так и не поборол: при срабатывании реле вызывалось прерывание от энкодера. Сильно помог конденсатор на 470пФ непосредственно на выводах энкодера. Экранировка проводов к энкодеру — тоже ничего не дала. Пришлось сделать костыль в виде переменной let_work, которая выключает функции в обработчике прерываний энкодера.
if ( relay_state_old != relay_state )
{
let_work = 0; //Serial.print("Let work: "); Serial.println(let_work);
delay(3200); //500*64
relay_state_old = relay_state;
digitalWrite(RELAY,relay_state); //Serial.print("Relay: "); Serial.println(relay_state);
delay(3200); //500*64
let_work = 1; //Serial.print("Let work: "); Serial.println(let_work);
}
В прерывании таймера 2 осуществляется замер температуры и внесение даных в буфер. После взятия 4-х измерений — выставляется флаг reg_action. А уже в основном цикле — выщитывается температура и управляющее значение для регулятора мощности.
Один раз на 4 срабатывания флага — обновляется отображаемое значение температуры.
if (reg_action)
{
reg_action = 0;
PV = 0;
for (byte i = 0; i < 4; i++)
{
PV = PV + PV_buf[i];
}
PV = PV >> 2;
if (instrument_connected) PV = map(PV,0,500,25,500); else PV = 0;
error = task - PV;
deltaPV = prevPV - PV;
prevPV = PV;
if ( (task_power>0) && (task_power<1000) ) integr = integr + (ki * error);
task_power = ((kp * error) + integr + (kd * deltaPV));
power = constrain(task_power, 0, 1000);
//Serial.print("Power: "); Serial.print(task_power); Serial.print(", "); Serial.println(power);
disp_PV_buf[disp_PV_buf_index] = PV;
disp_PV_buf_index++;
if (disp_PV_buf_index>4)
{
disp_PV_buf_index = 0;
disp_PV = 0;
for (byte i = 0; i < 4; i++)
{
disp_PV = disp_PV + disp_PV_buf[i];
}
disp_PV = disp_PV >> 2; //Serial.println(use_state);
}
stabilized = ((abs(PV - task)) < 5) && (use_state > 0);
}
Переменная display_mode задает, что будет отображаться на дисплее в даный момент
switch (display_mode)
{
case 0: DisplayValue(disp_PV); break;
case 1: if (SV2_mode) DisplayValue(SV2); else DisplayValue(SV); break;
case 2: DisplayValue(set_fan_speed); break;
}
0 — поточное значение температуры
1 — задание температуры.
2 — задание производительности вентилятора.
Переменная use_state принимает три значения:
0 — инструмент находится на подставке.
1 — инструмент снят с подставки, нажата кнопка.
2 — инструмент снят с подставки, кнопка не нажата.
В прерывании от таймера 2 считывается значение аналогово входа 1, защита от дребезга сигнала и установка значения use_state
// use state recognize
use_value = analogRead(USE)>>2; // 0, 142, 255
if (use_value < 70) use_state_temp = 0;
else if (use_value < 190) use_state_temp = 1;
else use_state_temp = 2;
if ( use_state_temp_old == use_state_temp ) {if (use_state_stable_count < 255) use_state_stable_count++;}
else {use_state_stable_count = 0; use_state_temp_old = use_state_temp;}
if ( use_state_stable_count == 20 ) use_state = use_state_temp;
В этом же прерывании обрабатывается нажатие кнопки энкодера, формируются 3 логических переменных: bt_tap, bt_hold, bt_hold_long
// button manage
if (digitalRead(BUTTON)) if ( (bt_timer>6) && (bt_timer<62) ) {bt_tap = 1; bt_hold = 0; bt_hold_long = 0;}// >100mS, <1000mS
if (bt_timer==62) {bt_tap = 0; bt_hold = 1; bt_hold_long = 0;}// = 1000mS
if (bt_timer==244) {bt_tap = 0; bt_hold = 1; bt_hold_long = 1;}// = 4000mS
if (!(digitalRead(BUTTON))) {if (bt_timer<32768) bt_timer++;} else bt_timer = 0;
В прерывании сравнения таймера 1 (TIMER1_COMPA_vect) осуществляется формирование импульса управления симистором. Формируется задержка от 1 мС до 9 мС на включение и длительность управляющего импульса 0.1 мС
В прерывании от детектора нуля (INT0_vect) выщитывается необходимая задержка, в зависимости от значения power
В прерывании от энкодера (INT1_vect) определяется направление вращения и изменяется соответствующий параметр.
Функция DisplayValue(int data) формирует 2 байта, которые будут отправлены в сдвиговые регистры. Число разбивается на сотни, десятки, единицы. Для гашения разряда, на все 4 входа К155ИД1 подается единица (число F).
Функция SetLamp(byte lamp, bool mode) отвечает за включение и выключение лампочек путем изменения битов 7..4 в старшем байте, отправляемом на индикацию.
Функция Update595() отправляет 2 байта в регистры сдвига, для индикации.
А тут ещё и с дешифраиорами.
Дорого/нет — все относительно.
у меня дорога на работу — с работы в день обходится дороже
Но тратить индикаторы на фен такое себе, плюс если по герберам заказывать ПП, то она довольно большого размера выходит. На стм32 намного дешевле собрать по этому проекту и прошивка доступна с исходниками. Притом прошивка есть и для паяльника (старой версии v1.4 с таобао что ли + форк на современную стм32, которая у всех уже), так что поменять шрифт или еще что-то будет несложно. Плюс комплектуха и зачастую флешка-прошивальщик с ф101-103 тоже имеется в наличии.
А индикаторы отлично подойдут к часам или ЦАПу, а не к фену, который где-то подальше стоит.
Ардуину на атмеге знает «каждый студент политеха». Да и документации на русском — тоже полно, например, тот же Евстифеев. В STM чуть выше порог вхождения
Но сам фен того не заслуживает, ну лично моё мнение. Трехкнопочного «контроллера» 8858 ему за глаза.
К слову, китайским жалам Т12 по $2.7 STM32 тоже на фиг не сдался.
STC вполне достаточно.
сам по себе 8858 нормальный фен, недостаток в трехкнопочной системе управления. Не оперативная регулировка потока воздуха. Я свой переделывал, температура кнопками, а поток потенциометром.
Как покупатель готового на STC и «собиратель» на STM32, считаю лучшим STM32
О, успели поставить минус, приверженцы теплых ламповых технологий. Ну не люблю лампы, хоть и вырос на них.
100 градусов — дежурный режим, что бы быстрее выходить на заданную температуру.
10 минут, если забыть выключить тумблер.
Возможно, они кому-то и нравятся, но этот кто-то явно не был вынужден наблюдать их годами.
Учитывая, что сейчас IPS или OLED дисплей стоит дешевле лампового индикатора и по всем параметрам его превосходит, занятие лампами считаю тупиковым.
Реально круто вышло!
А на неонках — часы. Тем более, что неонки классические, с правильной цифрой «5».
Как говорит дядька Максим: «чётко! в натуре класс!»
Тип индикации — на любителя.
А так — все отлично получилось.
Уважаю людей с прямыми руками.
И переделывать пришлось, чтобы питался от одного напряжения.
Первая, что нашел поиском:
https://aliexpress.ru/item/item/32903214068.html,searchweb201602_,searchweb201603_
Или собрать самому ;-)
radiokot.ru/forum/viewtopic.php?f=25&t=8505&start=10420