ЭЛЕКТРОНИКА "Лампа" с таймером для засветки фоторезиста

Какие проекты интереснее: с использванием готовых модулей или разработка всего устройства с нуля.

  • Интереснее проекты из готовых моделей с использванием Arduino, ArduinoIDE.

    Голосов: 9 50.0%
  • Интереснее проекты в которых все сделано с нуля, программирование на ASM и С

    Голосов: 9 50.0%

  • Всего проголосовало
    18

AstreyRize

★✩✩✩✩✩✩
31 Июл 2018
7
18
используйте спойлеры!
Данный проект представляет собой лампу на основе светодиодной УФ ленты с таймером. Диапазон таймера от 1 до 9999 секунд (~2.8 часа). Как показала практика для засветки фоторезиста вполне хватает 90-120 секунд. Что получилось можно посмотреть на youtube.

Для проекта понадобится:

1533057315175.png

Некоторые замечания:

  1. Обратите внимание, что для работы нужен индикатор конкретной модели: kem-5461ar. Если индикатора для данной модели нет придется переопределить цифры в коде, как это сделать см. "Разбор кода"
  2. Так же лучше взять не очень высокие электролиты, так как их можно "положить" на плату как можно увидеть на фото ниже.
  3. Микроконтроллер прошивается после распайки всех компонентов на плату, для этого предусмотрены контакты: MISO, SCK, MOSI
Принцип работы:

Питание "лампы" 12V. Вся логика работы завязана на МК atmega8а. Питание для микроконтроллера и индикатора 3.3V, подается через стабилизатор напряжения AMS1117 3.3V.
С помощью энкодера задается время экспонирования, затем по нажатию нижней кнопки запускается процесс засветки при этом управление через энкодер отключается. При истечении времени засветка прекращается. Верхняя кнопки - сброс. Сброс реализован просто замыканием контакта reset на землю.

Процесс разработки:

Вклеиваем ленту в рамку для фотографий:

4 УФ лента вклеена в рамку.jpg

Прототип я собирал на базе atmega8515 и все кнопки обрабатывались внешними прерываниями, но с переходом на младшую модель пришлось отказаться от одного прерывания, т.к. у atmega8 их 2 против 3 у 8515.

1 Прототип на макетной плате.jpg

Проверка прототипа на обычной ленте:

2 Пробую подключать светодиодную ленту.jpg

Готовая плата:

7 Готовая плата.jpg

8 Плата с просверленным отверстьями.jpg

С процессом разработки все стандартно: травим плату, сверлим отверстия, распаиваем компоненты начиная с SMD и заканчивая экраном и энкодером. Дополнительно на энкодер припеваем конденсаторы 104 (100nF) для того, что бы избежать дребезга контактов при срабатывании кнопок.

12 Готовое устройство верх.jpg

13 Готовое устройство низ.jpg

Прошиваю чип:

9 Заливаю прошивку 1.jpg

10 Заливаю прошивку 2.jpg

11 Заливаю прошивку 3.jpg

Разбор кода:

Проект можно скачать с github. Проект написан на C с использованием CVAVR.
Итак, если нужный индикатор найти не удалось необходимо изменить значения в данном массиве:

C:
// Цифры для kem-5461ar
unsigned char numbers[11] =
{
    //PB7...PB0
    //FBGCDpDEA
    0b11010111, //0
    0b01010000, //1
    0b01100111, //2
    0b01110101, //3
    0b11110000, //4
    0b10110101, //5
    0b10110111, //6
    0b01010001, //7
    0b11110111, //8
    0b11110101, //9
    0b00100000  //-
};
Указанный массив представляет собой маску для порта B. Как можно понять из комментария к коду, здесь биты расположены от пина7 порта B до пина 0 порта B (//PB7...PB0). Так же в комментарии указано, какой пин какой сегмент зажигает (//FBGCDpDEA): 7-F, 6-B и т.д. Включение сегмента осуществляется подачей 5v на ногу. На примере "0" видно, что не горят сегменты G и Dp (точка). Порт B конфигурируем как выход:

C:
// Port B initialization
DDRB=(1<<DDB7) | (1<<DDB6) | (1<<DDB5) | (1<<DDB4) | (1<<DDB3) | (1<<DDB2) | (1<<DDB1) | (1<<DDB0);
PORTB=(1<<PORTB7) | (1<<PORTB6) | (1<<PORTB5) | (1<<PORTB4) | (1<<PORTB3) | (1<<PORTB2) | (1<<PORTB1) | (1<<PORTB0);
За переключение разрядов отвечают биты 0-3 порта C. Конфигурируем порты следующим образом:

C:
// Port C initialization
DDRC=(0<<DDC6) | (0<<DDC5) | (0<<DDC4) | (1<<DDC3) | (1<<DDC2) | (1<<DDC1) | (1<<DDC0);
PORTC=(0<<PORTC6) | (0<<PORTC5) | (0<<PORTC4) | (1<<PORTC3) | (1<<PORTC2) | (1<<PORTC1) | (1<<PORTC0);
Создаем маску для включения разряда:

C:
// Разряды.
unsigned char digit[4] =
{
    0b11111101, // 1 разряд слева.
    0b11111011, // 2 разряд слева.
    0b11110111, // 3 разряд слева.
    0b11111110  // 4 разряд слева.
};
Теперь что бы отображать все 4 числа на индикаторе нужно просто каждый цикл передавать на порт C один из элементов массива digit, например: PORTC = digit[step];, где step разряд, который нужно зажечь, а на порт B подать элемент нужный элемент массива numbers: PORTB = numbers[digitByNumbers], где digitByNumbers число от 0 до 10 - цифра, 11 - знак дефиса.

У микроконтроллера atmega8a есть возможность обрабатывать два внешних прерывания. Для этого нужно подключиться к ногам PD2, PD3. Внешние прерывания используются для работы с энкодером. На PD2 подключен контакт энкодера отвечающий за поворот. Срабатывание этого прерывания означает что энкодер был повернут. Что бы определить в какую сторону был повернут энкодер считываем значение с другого контакта. высокий или низкий уровень на этом контакте говорит о направлении вращения:

C:
// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
{
    // Считываем значения порта D4 и если уровень высокий,
    // отнимаем единицу, если низкий, прибавляем единицу.
    if(PIND.4)
    {
        if(digitByNumbers[digitNumber] < 9)
        {
            digitByNumbers[digitNumber]++;
        }
    }
    else
    {
        if(digitByNumbers[digitNumber] > 0)
        {
            digitByNumbers[digitNumber]--;
        }
    }
}
Второе прерывание отвечает за кнопку на энкодере и двигает разряды позволяя задавать 4-х значные числа. Переменная digitNumber в данном случае номер разряда:

C:
// External Interrupt 1 service routine
interrupt [EXT_INT1] void ext_int1_isr(void)
{
    if(digitNumber == 0)
    {
        digitNumber = 3;
    }
    else
    {
        digitNumber--;
    }
}
Последние, что нужно сделать, включить внешние прерывания прерывания и разрешить их #asm("sei") . Включаем прерывания устанавливая в регистры GICR, MCUCR, GIFR следующие значения:

C:
// External Interrupt(s) initialization
// INT0: On
// INT0 Mode: Rising Edge
// INT1: On
// INT1 Mode: Falling Edge
GICR|=(1<<INT1) | (1<<INT0);
MCUCR=(1<<ISC11) | (0<<ISC10) | (1<<ISC01) | (1<<ISC00);
GIFR=(1<<INTF1) | (1<<INTF0);
И наконец прерывание по таймеру. Таймер включается при нажатии на кнопку старт. Т.к. для обработки кнопки старт внешних прерываний не хватило, проверяем постоянно уровень на ноге микроконтроллера и в случае его изменения включаем таймер:

C:
            // Если кнопка нажата.
            if(PIND.5 == 1  && timerIsStart == 0){
                timerIsStart = 1;

                //Выключаем прерывания энкодера
                GICR = (0<<INT1) | (0<<INT0);

                // Прерывание при переполнении: Вкл.
                TIMSK |= (1 << TOIE0);

                // Предустановленное значение: 0
                TCNT0 = 0;

                // Делитель: Clk/1024
                TCCR0 = (1<<CS02) | (0<<CS01) | (1<<CS00);

                PORTD.1 = 1;
}
Т.к. таймер 8-ми битный, что бы отсчитать одну секунду ему понадобиться отработать 30 раз:

2018-07-31_19-41-52.png

Проверяем, что таймер переполнился 30 раз после чего уменьшаем количество секунд на одну, а если досчитали до 0, останавливаем таймер и снова готовимся к работе:

C:
// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
    // Reinitialize Timer 0 value
    TCNT0 = 0;

    OverflowsRemain--;

    if(OverflowsRemain == 0)
    {
        OverflowsRemain = 30;

        if(digitByNumbers[3] > 0)
        {
            digitByNumbers[3]--;
            return;
        }
        if(digitByNumbers[2] > 0)
        {
            digitByNumbers[2]--;
            digitByNumbers[3] = 9;
            return;
        }
        if(digitByNumbers[1] > 0)
        {
            digitByNumbers[1]--;
            digitByNumbers[2] = 9;
            digitByNumbers[3] = 9;
            return;
        }
        if(digitByNumbers[0] > 0)
        {
            digitByNumbers[0]--;
            digitByNumbers[1] = 9;
            digitByNumbers[2] = 9;
            digitByNumbers[3] = 9;
            return;
        }

        // Тут таймер досчитал до нуля.
        PORTD.1 = 0;

        // Выключаем таймер.
        TCCR0=(0<<CS02) | (0<<CS01) | (0<<CS00);

        //Включаем прерывания энкодера
        GICR |= (1<<INT1) | (1<<INT0);

        // Предустановленное значение: 0
        TCNT0 = 0;

        timerIsStart = 0;
    }
}
Схема устройства и схема платы:

Schematic_PhotoresistLight_New_20180731200828.png


48e5da03-5ef9-4b28-aed8-9f0a5d73f203.png

475ec3f2-2169-43be-b3a8-1d6450726bb0.png
 
Изменено:

AlexGyver

★★★★★★✩
Команда форума
30 Июл 2018
359
567
для таймера жестковато! Но именно так используют микроконтроллеры взрослые дяди
 
  • Лойс +1
Реакции: AstreyRize и qbaddev

qbaddev

✩✩✩✩✩✩✩
23 Апр 2020
54
8
22
[email protected]
t.me
Данный проект представляет собой лампу на основе светодиодной УФ ленты с таймером. Диапазон таймера от 1 до 9999 секунд (~2.8 часа). Как показала практика для засветки фоторезиста вполне хватает 90-120 секунд. Что получилось можно посмотреть на youtube.

Для проекта понадобится:

Посмотреть вложение 76

Некоторые замечания:

  1. Обратите внимание, что для работы нужен индикатор конкретной модели: kem-5461ar. Если индикатора для данной модели нет придется переопределить цифры в коде, как это сделать см. "Разбор кода"
  2. Так же лучше взять не очень высокие электролиты, так как их можно "положить" на плату как можно увидеть на фото ниже.
  3. Микроконтроллер прошивается после распайки всех компонентов на плату, для этого предусмотрены контакты: MISO, SCK, MOSI
Принцип работы:

Питание "лампы" 12V. Вся логика работы завязана на МК atmega8а. Питание для микроконтроллера и индикатора 3.3V, подается через стабилизатор напряжения AMS1117 3.3V.
С помощью энкодера задается время экспонирования, затем по нажатию нижней кнопки запускается процесс засветки при этом управление через энкодер отключается. При истечении времени засветка прекращается. Верхняя кнопки - сброс. Сброс реализован просто замыканием контакта reset на землю.

Процесс разработки:

Вклеиваем ленту в рамку для фотографий:

Посмотреть вложение 59

Прототип я собирал на базе atmega8515 и все кнопки обрабатывались внешними прерываниями, но с переходом на младшую модель пришлось отказаться от одного прерывания, т.к. у atmega8 их 2 против 3 у 8515.

Посмотреть вложение 60

Проверка прототипа на обычной ленте:

Посмотреть вложение 61

Готовая плата:

Посмотреть вложение 64

Посмотреть вложение 65

С процессом разработки все стандартно: травим плату, сверлим отверстия, распаиваем компоненты начиная с SMD и заканчивая экраном и энкодером. Дополнительно на энкодер припеваем конденсаторы 104 (100nF) для того, что бы избежать дребезга контактов при срабатывании кнопок.

Посмотреть вложение 66

Посмотреть вложение 67

Прошиваю чип:

Посмотреть вложение 68

Посмотреть вложение 69

Посмотреть вложение 70

Разбор кода:

Проект можно скачать с github. Проект написан на C с использованием CVAVR.
Итак, если нужный индикатор найти не удалось необходимо изменить значения в данном массиве:

C:
// Цифры для kem-5461ar
unsigned char numbers[11] =
{
    //PB7...PB0
    //FBGCDpDEA
    0b11010111, //0
    0b01010000, //1
    0b01100111, //2
    0b01110101, //3
    0b11110000, //4
    0b10110101, //5
    0b10110111, //6
    0b01010001, //7
    0b11110111, //8
    0b11110101, //9
    0b00100000  //-
};
Указанный массив представляет собой маску для порта B. Как можно понять из комментария к коду, здесь биты расположены от пина7 порта B до пина 0 порта B (//PB7...PB0). Так же в комментарии указано, какой пин какой сегмент зажигает (//FBGCDpDEA): 7-F, 6-B и т.д. Включение сегмента осуществляется подачей 5v на ногу. На примере "0" видно, что не горят сегменты G и Dp (точка). Порт B конфигурируем как выход:

C:
// Port B initialization
DDRB=(1<<DDB7) | (1<<DDB6) | (1<<DDB5) | (1<<DDB4) | (1<<DDB3) | (1<<DDB2) | (1<<DDB1) | (1<<DDB0);
PORTB=(1<<PORTB7) | (1<<PORTB6) | (1<<PORTB5) | (1<<PORTB4) | (1<<PORTB3) | (1<<PORTB2) | (1<<PORTB1) | (1<<PORTB0);
За переключение разрядов отвечают биты 0-3 порта C. Конфигурируем порты следующим образом:

C:
// Port C initialization
DDRC=(0<<DDC6) | (0<<DDC5) | (0<<DDC4) | (1<<DDC3) | (1<<DDC2) | (1<<DDC1) | (1<<DDC0);
PORTC=(0<<PORTC6) | (0<<PORTC5) | (0<<PORTC4) | (1<<PORTC3) | (1<<PORTC2) | (1<<PORTC1) | (1<<PORTC0);
Создаем маску для включения разряда:

C:
// Разряды.
unsigned char digit[4] =
{
    0b11111101, // 1 разряд слева.
    0b11111011, // 2 разряд слева.
    0b11110111, // 3 разряд слева.
    0b11111110  // 4 разряд слева.
};
Теперь что бы отображать все 4 числа на индикаторе нужно просто каждый цикл передавать на порт C один из элементов массива digit, например: PORTC = digit[step];, где step разряд, который нужно зажечь, а на порт B подать элемент нужный элемент массива numbers: PORTB = numbers[digitByNumbers], где digitByNumbers число от 0 до 10 - цифра, 11 - знак дефиса.

У микроконтроллера atmega8a есть возможность обрабатывать два внешних прерывания. Для этого нужно подключиться к ногам PD2, PD3. Внешние прерывания используются для работы с энкодером. На PD2 подключен контакт энкодера отвечающий за поворот. Срабатывание этого прерывания означает что энкодер был повернут. Что бы определить в какую сторону был повернут энкодер считываем значение с другого контакта. высокий или низкий уровень на этом контакте говорит о направлении вращения:

C:
// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
{
    // Считываем значения порта D4 и если уровень высокий,
    // отнимаем единицу, если низкий, прибавляем единицу.
    if(PIND.4)
    {
        if(digitByNumbers[digitNumber] < 9)
        {
            digitByNumbers[digitNumber]++;
        }
    }
    else
    {
        if(digitByNumbers[digitNumber] > 0)
        {
            digitByNumbers[digitNumber]--;
        }
    }
}
Второе прерывание отвечает за кнопку на энкодере и двигает разряды позволяя задавать 4-х значные числа. Переменная digitNumber в данном случае номер разряда:

C:
// External Interrupt 1 service routine
interrupt [EXT_INT1] void ext_int1_isr(void)
{
    if(digitNumber == 0)
    {
        digitNumber = 3;
    }
    else
    {
        digitNumber--;
    }
}
Последние, что нужно сделать, включить внешние прерывания прерывания и разрешить их #asm("sei") . Включаем прерывания устанавливая в регистры GICR, MCUCR, GIFR следующие значения:

C:
// External Interrupt(s) initialization
// INT0: On
// INT0 Mode: Rising Edge
// INT1: On
// INT1 Mode: Falling Edge
GICR|=(1<<INT1) | (1<<INT0);
MCUCR=(1<<ISC11) | (0<<ISC10) | (1<<ISC01) | (1<<ISC00);
GIFR=(1<<INTF1) | (1<<INTF0);
И наконец прерывание по таймеру. Таймер включается при нажатии на кнопку старт. Т.к. для обработки кнопки старт внешних прерываний не хватило, проверяем постоянно уровень на ноге микроконтроллера и в случае его изменения включаем таймер:

C:
            // Если кнопка нажата.
            if(PIND.5 == 1  && timerIsStart == 0){
                timerIsStart = 1;

                //Выключаем прерывания энкодера
                GICR = (0<<INT1) | (0<<INT0);

                // Прерывание при переполнении: Вкл.
                TIMSK |= (1 << TOIE0);

                // Предустановленное значение: 0
                TCNT0 = 0;

                // Делитель: Clk/1024
                TCCR0 = (1<<CS02) | (0<<CS01) | (1<<CS00);

                PORTD.1 = 1;
}
Т.к. таймер 8-ми битный, что бы отсчитать одну секунду ему понадобиться отработать 30 раз:

Посмотреть вложение 58

Проверяем, что таймер переполнился 30 раз после чего уменьшаем количество секунд на одну, а если досчитали до 0, останавливаем таймер и снова готовимся к работе:

C:
// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
    // Reinitialize Timer 0 value
    TCNT0 = 0;

    OverflowsRemain--;

    if(OverflowsRemain == 0)
    {
        OverflowsRemain = 30;

        if(digitByNumbers[3] > 0)
        {
            digitByNumbers[3]--;
            return;
        }
        if(digitByNumbers[2] > 0)
        {
            digitByNumbers[2]--;
            digitByNumbers[3] = 9;
            return;
        }
        if(digitByNumbers[1] > 0)
        {
            digitByNumbers[1]--;
            digitByNumbers[2] = 9;
            digitByNumbers[3] = 9;
            return;
        }
        if(digitByNumbers[0] > 0)
        {
            digitByNumbers[0]--;
            digitByNumbers[1] = 9;
            digitByNumbers[2] = 9;
            digitByNumbers[3] = 9;
            return;
        }

        // Тут таймер досчитал до нуля.
        PORTD.1 = 0;

        // Выключаем таймер.
        TCCR0=(0<<CS02) | (0<<CS01) | (0<<CS00);

        //Включаем прерывания энкодера
        GICR |= (1<<INT1) | (1<<INT0);

        // Предустановленное значение: 0
        TCNT0 = 0;

        timerIsStart = 0;
    }
}
Схема устройства и схема платы:

Посмотреть вложение 73


Посмотреть вложение 74

Посмотреть вложение 75
Этому таймеру уделили больше внимания, чем всем нам в детстве!
 
  • Лойс +1
Реакции: AstreyRize