Блог / Электронное управление фотозатвором при помощи Arduino
1 января 2025
Статистика блога показывает, что темой управления фотозатвором при помощи Arduino народ таки интересуется, так что хочу здесь поделиться некоторыми своими наработками по данному вопросу. Нужно отдать должное Mike Tanen, который, собственно, и подтолкнул меня к тому, чтобы «потрогать эту тему руками» (да и то, что управлять шторками затвора можно при помощи соленоидов, я тоже узнал от него — до этого просто не сталкивался с такой задачей в принципе 😊). Я не буду здесь как-то глубоко погружаться в теорию фотозатворов, т.к. сам разбираюсь в этом как свинья в апельсинах, но если вы хотите познакомиться с данной темой подробнее, то рекомендую вам книгу А. А. Мельникова «Теория и расчёт фотозатворов» (М., «Машиностроение», 1973 г.). Тем не менее, для лучшего понимания изложенного в заметке материала, я проиллюстрирую текст анимашками, которые очень условно показывают идеи и принципы описанного в заметке, и не являются конечным вариантом реализации механики фотозатвора.
Элементы
Как и в прошлый раз, в качестве контроллера снова выступил «Arduino Nano» (Atmega 328P) — небольшая и во многих отношениях удобная плата. Ну и я снова использовал OLED-дисплей с диагональю 0.96'' — тоже маленький, «глазастый» и вообще удобный. Кроме того, чтобы «подружить» контроллер Arduino с соленоидами, нам понадобятся: полевой транзистор TO-220AB и выпрямительный диод 1N4007. А для управления затвором и выбора нужной выдержки понадобятся три тактовые кнопки.
Основные элементы
Для чего в схеме управления соленоидом через Arduino нужны транзистор и диод, расписывать не буду, но если вы хотите разобраться в этом глубже, то подробности можно почитать тут и тут.
Полезная инфа по транзистору TO-220AB и диоду 1N4007
Я испытал соленоиды двух размеров
Ну и, как обычно, понадобятся резисторчики пары номиналов (см. схемы ниже). Я запитывал схему 9-ю вольтами — 6 батареек «АА» («Крона» по току слабовата). В общем, всё просто, ничего космического.
Фотозатвор с одной шторкой и центральный фотозатвор
Что касается одношторного затвора, то это самый примитивный тип затвора, конечно же со своими недостатками, которые есть у абсолютного каждого типа фотозатворов, и тем не менее:
Принцип прост и понятен
Подобный вариант реализации (с толкающим соленоидом) вполне может подойти и для центрального затвора, т. к. зачастую там тоже всё можно свести к работе одного рычага.
Затвор фотоаппарата «Чайка»
Собственно схема:
Схема с одним соленоидом
Все элементы и номиналы обозначены на схеме, но вторая половина магии естественно в скетче:
Показать/скрыть скетч
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define WIRE Wire
#define MINUS_PIN 2 // пин кнопки "Выдержка дольше"
#define PLUS_PIN 3 // пин кнопки "Выдержка короче"
#define SHUTTER_BTN_PIN 4 // пин кнопки "Снимок"
#define SHUTTER_PIN 5 // пин управления соленоидом
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 32, &WIRE);
unsigned long s_delay = 10; // задержка для закрытия шторки (компенсация инерционности соленоида и шторки). не знаю сколько надо, поставил 10 мс
int vals[] = {1, 2, 4, 8, 15, 30, 60, 125, 250}; // набор значений выдержек (в долях секунды)
unsigned int current_idx = 6; // индекс (нумерация начинается с 0) значения выдержки по-умолчанию (при вкл. прибора)
void showVal() {
unsigned int x, y, h1, h2, w1, w2;
String pref, title;
unsigned int val = vals[current_idx];
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
if (1 / val < 1) {
display.setTextSize(2);
pref = "1";
title = "/";
display.getTextBounds(pref, 0, 0, &x, &y, &w1, &h1);
}
else
w1 = h1 = 0;
display.setTextSize(2);
title += val;
display.getTextBounds(title, 0, 0, &x, &y, &w2, &h2);
if (pref.length() > 0) {
display.setCursor((display.width() - w1 - w2) / 2 + 4, 8);
display.setTextSize(1);
display.println(pref);
}
display.setCursor((display.width() - w2) / 2 + 4, 8);
display.setTextSize(2);
display.println(title);
if (val != vals[0])
prevLabel();
if (val != vals[sizeof(vals) / sizeof(vals[0]) - 1])
nextLabel();
display.display();
}
void prevLabel() {
display.fillTriangle(14, 12, 14, 20, 6, 16, 1);
}
void nextLabel() {
display.fillTriangle(115, 12, 115, 20, 123, 16, 1);
}
void minus() {
if (current_idx > 0)
current_idx--;
delay(200);
}
void plus() {
if (current_idx < sizeof(vals) / sizeof(vals[0]) - 1)
current_idx++;
delay(200);
}
void shoot() {
unsigned long time = round(1000 / vals[current_idx]);
//Serial.println("time " + String(time) + " msec.");
digitalWrite(SHUTTER_PIN, HIGH);
delay(time + s_delay);
digitalWrite(SHUTTER_PIN, LOW);
delay(200);
}
void setup() {
pinMode(PLUS_PIN, INPUT);
pinMode(MINUS_PIN, INPUT);
pinMode(SHUTTER_BTN_PIN, INPUT);
pinMode(SHUTTER_PIN, OUTPUT);
digitalWrite(SHUTTER_PIN, LOW);
Serial.begin(9600);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
Serial.println("SSD1306 allocation failed");
for (;;);
}
}
void loop() {
showVal();
if (digitalRead(PLUS_PIN) == HIGH)
plus();
if (digitalRead(MINUS_PIN) == HIGH)
minus();
if (digitalRead(SHUTTER_BTN_PIN) == HIGH)
shoot();
}
По сути, этот скетч представляет собой видоизменённый скетч «blink», в котором добавлена возможность выбирать привычные для фотографа значения длительности свечения светодиода выдержки (по схеме у нас вместо светодиода стоит соленоид).
Демонстрация работы схемы
Ну и самый главный вывод, который можно сделать после проведённых тестов, заключается в том, что при срабатывании соленоида его стержень из-за инерционности успевает доходить до своего крайнего положения только при импульсах продолжительностью 1/30 секунды и дольше. При более коротких импульсах он, вроде как, не успевает выскочить даже до середины максимального выноса. У соленоидов меньшего размера в этом плане дела обстоят немного лучше (т.к. масса его стержня меньше, а соответственно меньше и его инерционность), но по большому счёту, это тоже ни о чём. Отчасти эту проблему можно попытаться решить подбором значения дополнительной задержки перед снятием напряжения с соленоида (в скетче я вынес это значение в переменную s_delay, так что желающие могут легко поэкспериментировать со значениями), но думаю, что всё таки лучше использовать немного более сложную конструкцию затвора, о чём речь дальше.
Двушторный фотозатвор
В сравнении с одношторным, двушторный фотозатвор более правильный, но при этом и конструктивно более сложный.
Принцип работы фотозатвора с двумя шторками на одной оси
В схеме добавились второй соленоид, транзистор и диод.
Схема с двумя соленоидами
Ну и скетч усложнился незначительно (по сути изменились только методы shoot() и setup()):
Показать/скрыть скетч
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define WIRE Wire
#define MINUS_PIN 2 // пин кнопки "Выдержка дольше"
#define PLUS_PIN 3 // пин кнопки "Выдержка короче"
#define SHUTTER_BTN_PIN 4 // пин кнопки "Снимок"
#define SHUTTER1_PIN 5 // пин управления соленоидом первой шторки
#define SHUTTER2_PIN 6 // пин управления соленоидом второй шторки
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 32, &WIRE);
unsigned long s_delay = 10; // задержка для закрытия шторки (компенсация инерционности соленоида и шторки). не знаю сколько надо, поставил 10 мс
unsigned long s_delay2 = 500; // задержка для второй шторки
int vals[] = {1, 2, 4, 8, 15, 30, 60, 125, 250}; // набор значений выдержек (в долях секунды)
int current_idx = 6; // индекс (нумерация начинается с 0) значения выдержки по-умолчанию (при вкл. прибора)
void showVal() {
unsigned int x, y, h1, h2, w1, w2;
String pref, title;
unsigned int val = vals[current_idx];
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
if (1 / val < 1) {
display.setTextSize(2);
pref = "1";
title = "/";
display.getTextBounds(pref, 0, 0, &x, &y, &w1, &h1);
}
else
w1 = h1 = 0;
display.setTextSize(2);
title += val;
display.getTextBounds(title, 0, 0, &x, &y, &w2, &h2);
if (pref.length() > 0) {
display.setCursor((display.width() - w1 - w2) / 2 + 4, 8);
display.setTextSize(1);
display.println(pref);
}
display.setCursor((display.width() - w2) / 2 + 4, 8);
display.setTextSize(2);
display.println(title);
if (val != vals[0])
prevLabel();
if (val != vals[sizeof(vals) / sizeof(vals[0]) - 1])
nextLabel();
display.display();
}
void prevLabel() {
display.fillTriangle(14, 12, 14, 20, 6, 16, 1);
}
void nextLabel() {
display.fillTriangle(115, 12, 115, 20, 123, 16, 1);
}
void minus() {
if (current_idx > 0)
current_idx--;
delay(200);
}
void plus() {
if (current_idx < sizeof(vals) / sizeof(vals[0]) - 1)
current_idx++;
delay(200);
}
void shoot() {
unsigned long time = round(1000 / vals[current_idx]);
//Serial.println("time " + String(time) + " msec.");
digitalWrite(SHUTTER1_PIN, HIGH);
delay(time + s_delay);
digitalWrite(SHUTTER2_PIN, HIGH);
delay(s_delay2);
digitalWrite(SHUTTER1_PIN, LOW);
delay(s_delay2);
digitalWrite(SHUTTER2_PIN, LOW);
delay(200);
}
void setup() {
pinMode(PLUS_PIN, INPUT);
pinMode(MINUS_PIN, INPUT);
pinMode(SHUTTER_BTN_PIN, INPUT);
pinMode(SHUTTER1_PIN, OUTPUT);
pinMode(SHUTTER2_PIN, OUTPUT);
digitalWrite(SHUTTER1_PIN, LOW);
digitalWrite(SHUTTER2_PIN, LOW);
Serial.begin(9600);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
Serial.println("SSD1306 allocation failed");
for (;;);
}
}
void loop() {
showVal();
if (digitalRead(PLUS_PIN) == HIGH)
plus();
if (digitalRead(MINUS_PIN) == HIGH)
minus();
if (digitalRead(SHUTTER_BTN_PIN) == HIGH)
shoot();
}
Что касается того, какова самая короткая выдержка которую сможет отработать такой затвор, то что-то конкретное я сказать затрудняюсь, т.к. тут многое зависит от механики конкретной конструкции фотозатвора. Тем не менее, мне думается, что на разумном диаметре «зрачка» затвора можно попробовать вытянуть даже 1/125 секунды, но не факт что получится (назовём это проблемой полностью открытого кадра).
Итого
После этого несложного опыта стало понятно, что делая фотозатвор управляемый соленоидом, не получится реализовать выдержки дольше 1 секунды (держать соленоид под напряжением рекомендуется менее 3 секунд, иначе он может перегреться и сгореть) и короче 1/125 секунды (из-за инерционности соленоида). Так что выдержка «B» и короткие выдержки должны обеспечиваться механически (без участия соленоида). Такие дела.
Если у вас есть по данной теме вопросы, идеи или предложения — пишите в комментариях к этой заметке 🍻.
P.S. — Ceterum censeo Washington esse delendam.