Таймер для засветки фоторезиста на Attiny13

Решил восстановить УФ лампу для засветки фоторезиста. Прикинул, что ставить ардуинку, слишком жирно. Обойдется и Attiny13. Нашел проект с индикатором на TM1637, которого нет. Зато TM1650 как у Матроскина гуталина. Проект кухонного таймера: https://github.com/SergeyVN94/timer-attiny13a?ysclid=ldkar0a44x759169601. Переписал вывод на TM1650, и буквально 3 строки для изменения алгоритма работы.
Небольшое видео:
https://youtu.be/Y6N882hlXzU
Схема:

Скетч:

#include <avr/io.h>
#define F_CPU 1200000UL
#include <util/delay.h>
#include <avr/interrupt.h>
//сделано по мотивам:
//https://arduino.ru/forum/programmirovanie/attiny13a-101-primenenie?page=19#comment-225250                    I2C
//https://github.com/SergeyVN94/timer-attiny13a?ysclid=ldkar0a44x759169601                                     таймер
//https://arduino.ru/forum/programmirovanie/kontroller-led-i-klaviatury-fd650v-kak-im-upravlyat#comment-232263 TM1650
 
/*
  DDRx  Настройка разрядов порта x на вход или выход.
  PORTx Управление состоянием выходов порта x (если соответствующий разряд настроен как выход), или подключением внутреннего pull-up резистора (если соответствующий разряд настроен как вход).
  PINx  Чтение логических уровней разрядов порта x.
*/
typedef uint8_t byte;

// настройки пинов
#define OUTPUT true
#define INPUT false
#define HIGH true
#define LOW false
#define MODE true // пин настраивается на вход или выход
#define STATE false // установка высокого или низкого уровня пина

// пины


#define _NOP() asm volatile("nop")

//массив знакогенератора 7 сегментного индикатора
byte simv[11] = {
  B00111111, //0
  B00000110, //1
  B01011011, //2   cоответствие разрядов и сегментов
  B01001111, //3
  B01100110, //4            _1_
  B01101101, //5         6 |   | 2
  B01111101, //6           |_7_|
  B00000111, //7         5 |   | 3
  B01111111, //8           |_4_|
  B01101111, //9
  B00000000, //пустота
};

// delay() для тиньки и свой нормальный, а delayMicroseconds() дурной
//а вот глобального такого дефайна может и не быть, как ни странно, так для страховки завел
//#define F_CPU 4800000UL
#define LOOP_CYCLES 8
#define us(num) (num/(LOOP_CYCLES*(1/(F_CPU/1000000.0))))

inline __attribute__((gnu_inline)) void asm_delay(uint16_t delay) {
  uint16_t u = us(delay);
  do _NOP(); while (delay--);
}

#define wdelay_us(x)  asm_delay(us(x))
#define wdelay(x) delay(x)


/*****************************************************/
//дальше мелкий вариант i2c на любых ногах

#define IICR  1
#define IICW  0

#define I2CDelay()  wdelay_us(4) // на 100КГц примерно по 4 мкс нужно держать уровни SCL 
#define PIN_SIGNAL  PB1 // pin 6
#define PIN_BUTTONS PB2 // pin 7
#define PIN_SDA     PB3 // pin 2
#define PIN_CLK     PB4 // pin 3
#define SDA_In()    (PINB & 0B00001000)
#define SCL_In()    (PINB & 0B00010000)
// кнопки
#define BTN_SET 0
#define BTN_PLUS 1
#define BTN_MINUS 2
#define BTN_NOT_SELECTED 3

// состояния программы
#define SET_MINUTES 0
#define SET_SECONDS 1
#define WORK 2
#define PAUSE 3
#define WORK_END 4

// другие настройки
#define MAX_COUNTER 75
#define SIGNAL_TIME 0 // in seconds
const byte selectedBtn = 0;
/*
  Настройка пина.
  Если 'mode' равен STATE, устанавливается логический уровень пина.
  Если 'mode' равен MODE, пин настраивается на вход или выход.
*/
void changePin(byte pin, bool state, bool mode = STATE) {
  if (mode == STATE) (state ? (PORTB |= (1 << pin)) : (PORTB &= ~(1 << pin)));
  else (state ? (DDRB |= (1 << pin)) : (DDRB &= ~(1 << pin)));
}

/******************************************************/

bool isTimerTick = false;
bool pointsOn = true;
byte PROGRAM_STATE = SET_MINUTES;
byte timerCounter = 0;
uint16_t secondsCounter = 0;

uint16_t getButtonsAnalogValue() {
  // включить ацп, тактирование в 128 раз меньше, начать измерение
  ADCSRA = (1 << ADEN) | (1) | (1 << ADSC);
  // ждать конца измерения
  while (ADCSRA & (1 << ADSC));
  return (ADCL | (ADCH << 8));
}

byte checkButtons() {
  uint16_t analogValue = getButtonsAnalogValue();

  if (analogValue >= 950 && analogValue <= 1023) return BTN_SET;
  else if (analogValue >= 470 && analogValue <= 530) return BTN_PLUS;
  else if (analogValue >= 300 && analogValue <= 400) return BTN_MINUS;
  return BTN_NOT_SELECTED;
}


void setStartState() {
  PROGRAM_STATE = SET_MINUTES;
  secondsCounter = 0;
  pointsOn = true;
  printTime();
}

ISR(TIM0_COMPA_vect) {
  if (timerCounter < MAX_COUNTER) {
    timerCounter += 1;
  } else {
    timerCounter = 0;
    isTimerTick = true;
  }
}
inline void SDA_Hi()  {
  PORTB |= 0B00001000;
  DDRB &= 0B11110111;
}
inline void SDA_Lo()  {
  PORTB &= 0B11110111;
  DDRB |= 0B00001000;
}
inline void SCL_Hi()  {
  PORTB |= 0B00010000;
  DDRB &= 0B11101111;
}
inline void SCL_Lo()  {
  PORTB &= 0B11101111;
  DDRB |= 0B00010000;
}

void I2CInit (void)
{
  SDA_Hi();
  SCL_Hi();
}


void I2CStart (void)
{
  SCL_Hi();
  while (!SCL_In());
  I2CDelay();
  SDA_Lo();
  SCL_Lo();
}

void I2CStop (void)
{
  I2CDelay();
  SCL_Hi();
  I2CDelay();
  SDA_Hi();
  I2CDelay();
}

boolean I2CWrite(byte b)
{
  byte i = 1 << 7;
  boolean ack = 0;

  while (i)
  {
    I2CDelay();
    if (b & i) SDA_Hi(); else SDA_Lo();
    I2CDelay();
    SCL_Hi();
    I2CDelay();
    SCL_Lo();

    i >>= 1;
  }
  SDA_Hi();
  I2CDelay();
  SCL_Hi();

  if ( SDA_In() == 0 ) ack = 1;
  I2CDelay();
  SCL_Lo();
  SDA_Lo();
  return ack;
}

byte I2CRead(boolean ack)
{
  byte i = 8;
  byte b = 0;

  while (i--)
  {
    I2CDelay();
    SCL_Hi();
    if (SDA_In()) b |= 1;
    b <<= 1;
    I2CDelay();
    SCL_Lo();
  }
  if (ack) SDA_Lo();
  else SDA_Hi();
  I2CDelay();
  SCL_Hi();
  I2CDelay();
  SCL_Lo();
  SDA_Lo();
  return b;
}
/*************************************/
int main(void) {

  changePin(PIN_SIGNAL, OUTPUT, MODE);
  changePin(PIN_BUTTONS, INPUT, MODE);
  // Инициализация дисплея TM1650,Подробнее https://arduino.ru/forum/programmirovanie/kontroller-led-i-klaviatury-fd650v-kak-im-upravlyat#comment-232263
  I2CStart();
  I2CWrite(0x27 << 1);
  I2CWrite(B00010001);
  I2CStop();
  // timer
  /* нужно:
    1) включить срабатывание по сравнению с регистром (режим СТС)
    2) утановить предделитель
    3) указать регистр, с которым сравнивается
    4) записать число с которым сравнивать
    5) включить прерывания

    TCCR0B, TCCR0A - регистры управления таймером/счетчиком
    TIMSK0 - регистр настрек

    TCNT0 - таймер/счетчик
    OCR0A и OCR0B - регистры сравнения
  */
  TCCR0A |= (1 << WGM01); // режим СТС (сравнение с регистром)
  TCCR0B |= (1 << CS01) | (1 << CS00); // предделитель частоты на 64
  TIMSK0 |= (1 << OCIE0A); // сравнение с регистрам А включение прерывания
  OCR0A = 250;
  TCNT0 = 0; // сброс таймера
  sei(); // включить прерывания

  // АЦП
  ADMUX = 1; // сравнение с питанием, ацп на пине PB2

  // инициализация
  byte signalCounter = SIGNAL_TIME;
  printTime();

  while (1) {

   const byte  selectedBtn = checkButtons();
      if (PROGRAM_STATE == WORK)  changePin (PIN_SIGNAL,1);
      else changePin (PIN_SIGNAL,0);
    switch (PROGRAM_STATE) {
      case SET_MINUTES: {
          if (selectedBtn == BTN_PLUS) secondsCounter = (secondsCounter < 5940) ? (secondsCounter + 60) : 0;
          if (selectedBtn == BTN_MINUS) secondsCounter = (secondsCounter >= 60) ? (secondsCounter - 60) : 5940;
          if (selectedBtn == BTN_SET) PROGRAM_STATE = SET_SECONDS;
          if (selectedBtn != BTN_NOT_SELECTED) printTime();
          break;
        }
      case SET_SECONDS: {
          if (selectedBtn == BTN_PLUS) secondsCounter = (secondsCounter < 5990) ? secondsCounter + 10 : 0;
          if (selectedBtn == BTN_MINUS) secondsCounter = (secondsCounter > 0) ? (secondsCounter - 10) : 5990;
          if (selectedBtn == BTN_SET) PROGRAM_STATE = WORK;
          if (selectedBtn != BTN_NOT_SELECTED) printTime();
          break;
        }
      case WORK: {
          if (selectedBtn == BTN_SET || selectedBtn == BTN_MINUS) setStartState();
          if (selectedBtn == BTN_PLUS) PROGRAM_STATE = PAUSE;
          break;
        }
      case PAUSE: {
          pointsOn = true;
          if (selectedBtn == BTN_PLUS) PROGRAM_STATE = WORK;
          if (selectedBtn == BTN_SET || selectedBtn == BTN_MINUS) setStartState();
          break;
        }
      case WORK_END: {
          if (isTimerTick && signalCounter > 0) signalCounter -= 1;

          byte isSignalDisable = signalCounter == 0 || selectedBtn != BTN_NOT_SELECTED;

          if (isSignalDisable) {
            setStartState();
            signalCounter = SIGNAL_TIME;
          }

          changePin(PIN_SIGNAL, isSignalDisable ? LOW : HIGH);

          break;
        }
      default: {
          setStartState();
          break;
        }
    }

    if (isTimerTick) {
      if (PROGRAM_STATE == WORK) {
        if (secondsCounter == 0) PROGRAM_STATE = WORK_END;
        else secondsCounter -= 1;
        pointsOn = !pointsOn;
        printTime();
      }

      isTimerTick = false;
    }

    _delay_ms(150);
  }
}




void printTime()
{
  byte dm = secondsCounter / 60 / 10; // данные
  byte em = (secondsCounter / 60) % 10;
  byte ds = (secondsCounter % 60) / 10;
  byte es = (secondsCounter % 60) % 10;
 
  //************ поразрядно выводим данные на индикатор *******************
  
  I2CStart();
  I2CWrite(0x34 << 1);            //выставляем номер разряда индикатора
  I2CWrite(simv[dm]);             //пишем в него символ  
  I2CStop();

  
  I2CStart();
  I2CWrite(0x35 << 1);
  I2CWrite(simv[em]+pointsOn*128);
  I2CStop();

  
  I2CStart();
  I2CWrite(0x36 << 1);
  I2CWrite(simv[ds]);
  I2CStop();

  
  I2CStart();
  I2CWrite(0x37 << 1);
  I2CWrite(simv[es]);
  I2CStop ();
  
}

/*
Режимы работы
Режим ввода минут - режим по умолчанию В этом режиме кнопки plus и minus задают число.
После нажатия кнопки set таймер переключается на выбор секунд.
Режим ввода секунд - секунды выбираются так же как и минуты. Если нажать на set, таймер запускается.
Режим работы - таймер отсчитывает время. В этом режиме кнопки set и минус сбрасывают таймер в режим
ввода минут. Кнопка plus включает паузу.
Режим паузы. Нажатие на plus продолжит работу таймера, set и минус сбрасывают таймер.
Режим окончания работы. На пине PB4 включается спикер (можно заменить на светодиод) на 5 секунд.
Нажатие любой из кнопок отключает спикер и таймер возвращается в режим ввода минут.
Использование
Кнопками plus и minus задайте минуты. Нажмите на set и введите секунды. Еще раз нажмите на set чтобы
запустить таймер.
*/
2 лайка

Реле и одна кнопка, которая стартует процесс. Постоянно подкручивать время нет смысла.

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

Да даже если отлажен, все равно менять время придется. По этому у меня есть “ячейки памяти”, куда нужное время заносится. Так удобнее - рядом “шпаргалка”, ага в 5 ячейке время для УФ-маски. Выбрал и вперед. А с учетом того, что мою маску (УФ) нужно засвечивать минут 40 - тыкать секунды за долбаешься каждый раз :smile:

1 лайк

Идея с созданием профилей в памяти логичная, надо будет проработать. Хотя скетч занимает 990 байт, но это, я просто тупо объединил два скетча и не разбирался с нюансами. Спасибо за идею.

40 минут? Что то прям за гранью разумного. Стоит задуматься о смене лампы, явно светит не в том диапазоне. У меня длинная лампа F15T8/BL, время засветки 30 секунд с любым фоторезистом, хоть старый хоть новый. Соответссно и таймер не нужен.

2 лайка

У меня для засветки однокомпонентной маски раньше были внутренности от 500 ваттной ДРЛки (осталась со времен УФ стираемых ПЗУ, за 5 минут мухи теряли ориентацию и шкура на заднице покрывалась волдырями). Время около 1 мин. Засвека фоторезиста от 15 сМ Китайской УФ ленты 3-5 мин. Жду 10 шт 3 ваттых светодиодов. Надеюсь, что с ними все варианты будут в пределах разумного.

Главное, что б светили в диапазоне 350нм ± немного. А то получится шило на мыло…)

2 лайка

Да нормальная лампа, фоторезист через бумагу засвечивает 8 минут, через пленку - 3 минуты. А вот УФ-маска так называемая (ее еще ремонтной называют) - она твердеет минут через ~15-20, но я свечу 40 минут, чтобы на верняка (все равно это просто маска, пересветы исключены) !!! )))

ЗЫ: Лампа обычная черная на 220В лет 17 назад на строительном рынке купленная.

1 лайк

А, я в нескольких местах встречал 405 нМ. Получается немного промахнулся? Хртя предыдущие вроде работают.

По идее в описании конкретного фоторезиста должны быть рекомендуемая длина волны у.ф. 405нм наверно уже многовато, стирать может и будет, но точно не за 30 секунд. Кстати эта цифра -30 секунд тоже была прописана в какой-то инструкции от фоторезиста.

1 лайк

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

Заменил светодиодные ленты (1 Вт) на 9 трех ваттных СД 405 нМ. Эффект очень хороший https://youtu.be/lH-LPwOpE4s

Вместо графита можно купить аэрозольку Density Toner. Стоит недёшево, но для любительских применений одного баллона хватит на всю жизнь)

Смотря какой принтер используется. Вот у Вас какой?
У меня китайский Pantum и его тонеру что Density Toner, что пары ацетона/растворителя и тд. и тп. (я пробовал ооочень много всяких вариантов) до одного места. Какой-то не прошибаемый тонер. :frowning:
Мой вариант - шаблон из двух сложенных вместе (бетербродом). Так быстрее, хотя и графит и фломастер для досок тоже помогает.

ЗЫ: Исключать, что я не все попробовал еще тоже не могу.

У меня “буханка” Samsung ml-1865 Картридж с тонером от фирмы “кактус” кажется. Даже если выставить в настройках максимальную плотность печати всё равно немножко не хватает “черноты”, но вот Density Toner к счастью полностью решает проблему.

Кактус это совсем не айс однако, подтверждаю

У меня МФУшка HP Laserjet 3055, картридж родной, но им уже лет 15 минимум. Пары ацетона в принципе помогают, но на маленьких платах с графитной пылью быстрее и без запаха. Возможно на специальной пленке будут оставаться следы от графита, а на рукаве для запекания все отлично.

Я как-то пробовал на рукаве, заминается (в смысле поверхность немного не ровная получается). Как от этого избавились?

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