r1on
7 лет назадUV матрица для засветки фоторезиста
Добрый День друзья платформы "Голос".
Предисловие
В данном проекте открыты схемы, шаблоны плат, точные названия элементов и окончательный программный код контроллера. Делаю это для того чтобы каждый желающий мог повторить данный проект, усовершенствовать его, изменить по собственным требованиям. А мне будет приятно прочитать строгие комментария, предложения и ответить на вопросы.
Цель проекта
В большинстве своих проектов я пользуюсь технологией ЛУТ (лазерно-утюжная технология) для создания своих собственных плат, однако после изучения тонны литературы, просмотра множества роликов других радиолюбителей, возник интерес создания плат фоторезистивным методом. Плюс этого метода заключается в том, что платы получаются намного качественнее, по сравнению с технологией ЛУТ. Одним и важных элементов данного метода является ультрафиолетовая лампа для засветки фоторезиста. Можно конечно воспользоваться обычной ультрафиолетовой лампочкой для настольной лампы, но возникло большое желание создания матрицы из ультрафиолетовых светодиодов, под управлением контроллера, для точного задания времени засветки.
Подготовка к сборке
В моем распоряжение были сто штук пяти миллиметровых светодиодов фирмы Chanzon, с длиной волны 395-400 нм, Uпр = 3.1 В, Iпр = 20 мА; источник питания 12 В. Для начала необходимо было определится с расположением светодиодов на плате, номиналом токоограничительных резисторов, мощностью блока питания.
В одном ряду схемы располагается три последовательные группы светодиодов с резистором, соединенные параллельно.
Расчет резистора:
U1 = 3*3.1 = 9.3 В, U2 = 12 - 9.3 = 2.7 В,
R = 2.7/0.02 = 135 Ом, близкий к этому значение номинал это 150 Ом.
Расчет потребляемой мощности:
Pрез = U2 * I = 0.054 Вт, Pобщ = Uпр * Iпр + Pрез = 0.116 Вт,
Pпот = 99 * 0.116 = 11.484 Вт
В состав схемы управления входит минимальная сборка микроконтроллера на базе микросхемы Atmega328P (более подробно Arduino минимум - https://golos.io/arduino/@r1on/arduino-minimum), энкодер EC11 c аппаратным подавлением дребезга с помощью инвертирующего триггера Шмитта на базе микросхемы PC74HC14P (более подробно Инвертирующий триггер Шмитта - https://golos.io/ru--obuchenie/@r1on/invertiruyushii-trigger-shmitta), схема понижения напряжения на базе стабилизатора L7805, ключ подачи напряжения на матрицу на базе полевого транзистора JRF540N (более подробно Mosfet - https://golos.io/ru--znaniya/@r1on/mosfet), четырех разрядный семи сегментный индикатор 5462BS. Да и для тех, кто пока еще не знаком со схемой понижения напряжения до пяти вольт, выглядит она так.
В схеме подключения энкодера используются RC - цепочки для совместной работы с микросхемой PC74HC14P для аппаратного подавления дребезга.
К контактам индикатора 5462BS подключаются токоограничительные резисторы номиналом 220 Ом.
Сигнал, проходящий через резистор 1 кОм и далее к затвору полевого транзистора, является отпирающим, при котором через нагрузку R (в нашем случае светодиодная матрица) начинает проходить ток.
Контакты, отмеченные на выше перечисленных схемах, подключаются к соответствующим контактам микросхемы Atmega328P.
Шаблоны нарисованы в программе Sprint-Layout. Плату схемы управления было решено сделать двусторонней, для компактности расположения элементов и уменьшения размеров. Отметки T1, T2, T3 это контрольные отверстия для совмещения шаблонов.
Изготовление и сборка
После травления плат и их лужения, они покрыты лаком для защиты. В промышленном производстве соединение контактов двух сторон платы осуществляется с помощью гильз, в данном же случае осуществлялось, либо самими ножками элементов, либо с помощью тонких жил проводов.
Далее обе платы cобираются в один "бутерброд" и поднимается на стойки (в моем случае винты), для лучшего рассеивания света от светодиодов, так как угол их рассеивания 30°.
Составление скетча
Далее приведена прошивка Atmega328P с максимально возможными комментариями (прошивка осуществляется с помощью средств указанных в посте Arduino минимум -
https://golos.io/arduino/@r1on/arduino-minimum).
// Проект UV лампа
// Настройка пинов
int pinA = 2; // Пин прерывания
int pinB = 4; // Любой другой пин
int pinButton = 3; // Пин кнопки
volatile int count = 0; // Счетчик оборотов
int actualcount = 0; // Временная переменная определяющая изменение основного счетчика
int numberclicking = 0; // Переменная определяющая выбранный сегмент или счетчик нажатий
volatile int state = 0; // Переменная хранящая статус вращения
volatile int clicking = 0; // Переменная хранящая статус нажатия
volatile int pinAValue = 0; // Переменные хранящие состояние пина, для экономии времени
volatile int pinBValue = 0; // Переменные хранящие состояние пина, для экономии времени
volatile int pinButtonValue = 0; // Переменные хранящие состояние пина кнопки
volatile long timeButtonDown = 0; // Переменная хранящая время нажатия кнопки
unsigned long timeButtonPressed = 1500; // Переменная хранящая предел времени долгго удержания кнопки
volatile bool isButtonDown = false; // Переменная хранящая флаг нажатия кнопки
volatile bool flagButtonLong = false; // Переменная хранящая флаг
unsigned long OldTime = 0; // Временнвая переменная для таймера
int anodPins[] = {A3, A2, A1, A4}; // Задаем пины для кажого разряда
int segmentsPins[] = {5, 6, 7, 8, 9, 10, 11, 12}; // Задаем пины для каждого сегмента (из 7 + 1(точка))
int numb[4] = {0, 0, 0, 0}; // Массив отправляемых чисел
int array[4] = {0, 0, 0, 0}; // Временный массив
int seg[10][8] = // Массив сегментов {A, B, C, D, E, F, G, DP}
{
{1, 1, 1, 0, 1, 0, 1, 1}, // Цифра 0
{0, 1, 0, 0, 1, 0, 0, 0}, // Цифра 1
{0, 1, 1, 1, 0, 0, 1, 1}, // Цифра 2
{0, 1, 1, 1, 1, 0, 1, 0}, // Цифра 3
{1, 1, 0, 1, 1, 0, 0, 0}, // Цифра 4
{1, 0, 1, 1, 1, 0, 1, 0}, // Цифра 5
{1, 0, 1, 1, 1, 0, 1, 1}, // Цифра 6
{0, 1, 1, 0, 1, 0, 0, 0}, // Цифра 7
{1, 1, 1, 1, 1, 0, 1, 1}, // Цифра 8
{1, 1, 1, 1, 1, 0, 1, 0} // Цифра 9
};
int segdone[4][8] =
{
{0, 1, 0, 1, 1, 0, 1, 1}, // Буква d
{0, 0, 0, 1, 1, 0, 1, 1}, // Буква о
{0, 0, 0, 1, 1, 0, 0, 1}, // Буква n
{1, 0, 1, 1, 0, 0, 1, 1}, // Буква E
};
void setup()
{
pinMode(13, OUTPUT);
pinMode(pinA, INPUT); // Пины в режим приема INPUT
pinMode(pinB, INPUT);
pinMode(pinButton, INPUT);
for (int i = 0; i < 4; i++) // Аноды в режите вывода
{
pinMode(anodPins[i], OUTPUT);
}
for (int j = 0; j < 8; j++) // Сегменты в режиме вывода
{
pinMode(segmentsPins[j], OUTPUT);
}
attachInterrupt(0, A, CHANGE); // Настраиваем обработчик прерываний по изменению сигнала на линии А
attachInterrupt(1, Button, CHANGE); // Настраиваем обработчик прерываний по изменению сигнала на кнопке
// Serial.begin(9600); // Включаем Serial
}
void loop()
{
if (actualcount != count) // Чтобы не загружать ненужным выводом в Serial, выводим состояние
{ // счетчика только в момент изменения
actualcount = count;
// Serial.println(actualcount);
}
if (clicking == 1) // Если кнопка была нажата
{
numberclicking ++; // Увеличивает счетчик нажаний
clicking = 0;
if (numberclicking == 4) numberclicking = 0; // Ограничиваем счетчик нажатий по количеству разрядов индикатора
// Serial.println(numberclicking);
}
Filling(); // Заполняем массив отправляемых чисел
Sending(); // Отправляем массив в индикатор
Start(); // Ожидаем долгого нажатия
}
void A() // Функция вызываемая прерыванием линией А
{
pinAValue = digitalRead(pinA); // Получаем состояние пинов A и B
pinBValue = digitalRead(pinB);
cli(); // Запрещаем обработку прерываний
if (!pinAValue && pinBValue) state = 1; // Если при спаде линии А на линии B лог. единица, то вращение в одну сторону
if (!pinAValue && !pinBValue) state = -1; // Если при спаде линии А на линии B лог. ноль, то вращение в другую сторону
if (pinAValue && state != 0)
{
if (state == 1 && !pinBValue || state == -1 && pinBValue) // Если на линии А снова единица, фиксируем шаг
{
count += state;
state = 0;
}
}
sei(); // Разрешаем обработку прерываний
}
void Button() // Функция вызываемая прерыванием кнопкой
{
if (millis() - timeButtonDown < 50) return; // Если время с предыдущего нажатия меньше 50 мс возвращаемся
pinButtonValue = digitalRead(pinButton); // Получаем состояние кнопки
cli(); // Запрещаем обработку прерываний
timeButtonDown = millis(); // Запоминаем время нажатия
if (!pinButtonValue) // Если есть инверсный сигнал
{
isButtonDown = true; // Устанавливаем флаг нажатия кнопки (для алгоритла долгого нажатия кнопки)
}
else if (isButtonDown)
{
clicking = 1; // Меняем статус срабатывания
isButtonDown = false;
}
sei(); // Разрешаем обработку прерываний
}
void Filling() // Функция заполнения массива
{
for (int m = 1; m < 4; m = m + 2) // Заполнение второго и четвертого сегмента
{
if (numberclicking == m) // Если нажали кнопку первый или третий раз...
{ // переход от первого сегмента ко второму или от третьего к четвертому
if (actualcount == 10) count = 0; // Ограничиваем максимум до девяти
if (actualcount == -1) count = 9; // Ограничиваем минимум до нуля
numb[m] = actualcount; // Вписываем значение счетчика в массив
}
} // numb[4] = {0, actualcount, 0, actualcount};
for (int l = 0; l < 3; l = l + 2) // Заполнение первого и третьего сегмента
{
if (numberclicking == l) // Если кнопку не нажимали или нажали второй раз (не нажимали numberclicking = 0)
{ // начало на первом сегменте или переход от второго к третьему сегменту
if (actualcount > 6) count = 0; // Ограничиваем максимум до шести
if (actualcount == -1) count = 6; // Ограничиваем минимум до нуля
numb[l] = actualcount; // Вписываем значение счетчика в массив
}
} // numb[4] = {actualcount, 0, actualcount, 0};
}
void Sending() // Функция отправки значений массива в индикатор
{
for (int o = 0; o < 4; o++) // Каждый разряд по очереди
{
for (int k = 0; k < 8; k++) // Каждый сегмент по очереди - исходя из заданного состояния счетчика
{
digitalWrite(segmentsPins[k], ((seg[numb[o]][k] == 1) ? LOW : HIGH)); // Тернарный оператор при условии равенства
// условия выполняется LOW иначе HIGH
}
digitalWrite(anodPins[o], HIGH); // Включить сегмент
delay(1); // Выждать для восприятия
digitalWrite(anodPins[o], LOW); // Отключить сегмент
}
}
void Start() // Функция определения долгого нажатия
{
if (millis() - timeButtonDown > timeButtonPressed && isButtonDown) // Если пришло время больше timeButtonPressed с момента нажатия...
{
flagButtonLong = true; // Установить флаг
// Serial.println("flagButtonLong");
}
if (flagButtonLong && isButtonDown)
{
if (!digitalRead(pinButton) && millis() - timeButtonDown > timeButtonPressed) // Если по истечению времени больше timeButtonPressed кнопка нажата...
{
// Serial.println("Long_Press");
Ex(); // Есть долгое нажатие запустить отсчет времени
}
isButtonDown = false; // Сбрасываем флаги
flagButtonLong = false;
}
}
void Ex() // Функция отсчета массива numb и отправки значений в индикатор
{
digitalWrite(13, HIGH);
// Serial.println("Start");
int sec = (60 * ((numb[0] * 10) + numb[1])) + (numb[2] * 10) + numb[3]; // Перевод массива в количество секунд
for (int s = sec; s > 0; --s) // Каждую секунду
{
OldTime = millis(); // Переменная для таймера
while ((millis() - OldTime) < 1000) // В течении секунды...
{ // ... пересчитать в зависимости от уменьшения количества секунд s массив array
array[0] = s / 600; // Десятки минут
array[1] = (s / 60) % 10; // Минуты
array[2] = (s % 60) / 10; // Десятки секунд
array[3] = (s % 60) % 10; // Секунды
/* for (int p = 0; p <= 3; p++)
{
Serial.print(array[p]);
}
Serial.println();*/
for (int d = 0; d < 4; d++) // Каждый разряд по очереди
{
for (int w = 0; w < 8; w++) // Каждый сегмент по очереди - исходя из заданного состояния счетчика
{
digitalWrite(segmentsPins[w], ((seg[array[d]][w] == 1) ? LOW : HIGH)); // Тернарный оператор при условии равенства
// условия выполняется LOW иначе HIGH
}
digitalWrite(anodPins[d], HIGH); // Включить сегмент
delay(1); // Выждать для восприятия
digitalWrite(anodPins[d], LOW); // Отключить сегмент
}
}
}
digitalWrite(13, LOW);
OldTime = millis();
while ((millis() - OldTime) < 3000) // В течении заданного времени пишем "donE" в индикатор
{
Done();
}
count = 0; // Обнуляем счетчик
for (int b = 0; b <= 3; b++) // Обнуляем массив numb для следующего цикла
{
numb[b] = 0;
}
// Serial.println("Done");
}
void Done() // Функция отправки слова "donE" в индикатор
{
for (int g = 0; g < 4; g++) // Каждый разряд по очереди
{
for (int f = 0; f < 8; f++) // Каждый сегмент по очереди - исходя из заданного состояния счетчика
{
digitalWrite(segmentsPins[f], ((segdone[g][f] == 1) ? LOW : HIGH)); // Тернарный оператор при условии равенства
// условия выполняется LOW иначе HIGH
}
digitalWrite(anodPins[g], HIGH); // Включить сегмент
delay(1); // Выждать для восприятия
digitalWrite(anodPins[g], LOW); // Отключить сегмент
}
}
Итог
После тестирования UV матрицы, фоторезист засвечивается в диапазоне от одной до двух минут. Можно конечно было добавить схему "пищалки", чтобы каждое нажатие или поворот энкодера сопровождался звуковым сопровождением или по окончанию засветки издавался звук определенной тональности, но это если вам необходимо. Из минусов могу сказать, что качество платы получилось далеко от идеала, скорее всего из-за использования китайского стеклотекстолита, имеются внутренние вкрапления. В итоге матрица выполняет свою функцию на все сто процентов.
Комплектация
№ | Наименование | Количество |
---|---|---|
1 | Стеклотекстолит двусторонний 110х68 | 1 |
2 | Стеклотекстолит односторонний 120х122 | 1 |
3 | Светодиоды Chanzon ультрафиолетовые | 100 |
4 | Микросхема Atmega328P | 1 |
5 | Микросхема PC74HC14P | 1 |
6 | Стабилизатор L7805 | 1 |
7 | Транзистор JRF540N | 1 |
8 | Четырех разрядный семи сегментный индикатор 5462BS | 1 |
9 | Энкодер EC11 | 1 |
10 | Разъем 5.5x2.1 | 1 |
11 | Штекер 2.1x5.5 | 1 |
12 | Тактовая кнопка 6x6x4.3 | 1 |
13 | Кварцевый резонатор CREC16.000 | 1 |
14 | Резистор 150 Ом | 33 |
15 | Резистор 10 кОм | 8 |
16 | Резистор 220 Ом | 8 |
17 | Резистор 1 кОм | 1 |
18 | Конденсатор 22 пФ | 2 |
19 | Конденсатор 10 нФ | 3 |
20 | Конденсатор 0.33 мкФ | 1 |
21 | Конденсатор 0.1 мкФ | 1 |
22 | Клеммник винтовой двухконтактный | 2 |
23 | Радиатор алюминиевый | 2 |
24 | Винт 3х25 с гайкой | 4 |
25 | Панелька для микросхемы 28 контактов | 1 |
26 | Панелька для микросхемы 14 контактов | 1 |