Контакты

Хронограф оружейный своими руками. Дешёвый хронограф для пневматики своими руками. Принцип работы хронографа собственного изготовления

В своей первой публикации я хочу рассказать вам, как я собрал хронограф за пару вечеров из дешевых и доступных всем деталей. Как вы наверное уже догадались из названия, этот девайс служит для измерения скорости пули у пневматических (и не очень) винтовок и бывает полезным для контроля её технического состояния.

1. Детали и принадлежности

  • Китайский Digispark - 80 рублей на момент покупки
  • Сегментный дисплей на TM1637 - 90 рублей на момент покупки
  • ИК светодиоды и ИК фототранзисторы (10 пар) - 110 рублей на момент покупки, нам нужны 2 пары
  • Резисторы 220 Ом (100шт) - 70 рублей на момент покупки, нам нужно только 2 штуки
На этом заканчиваются детали, которые необходимо покупать. Резисторы можно не заказывать, похожие по номиналу (но не меньше!) можно выдернуть из ненужной бытовой электроники. Таким образом, суммарные затраты менее 350 рублей, это ничто по сравнению с ценой нового заводского хронографа (over 1000р за самый простой, который по факту еще примитивнее нашего сабжа). Кроме деталей нам пригодятся:
  • Провода - найти в оффлайне бесплатно не проблема
  • Кусок пластиковой водопроводной трубы длиной более 10см (диаметр по вкусу) - так же легко найти
  • Паяльные принадлежности
  • Мультиметр (опционально)
Первые 3 детали достойны отдельного рассмотрения, так как имеют свои особенности, поэтому начнем с мини-обзоров на них.

1.1. Digispark

Представляет собой простую миниатюрную Arduino-совместимую плату с ATtiny85 на борту. Как подключить к Arduino IDE читаем на официальном сайте проекта , там же можно найти драйвера для нее. Существует два основных вида этой платы: с microUSB и более брутальный с USB коннектором, разведенным прямо на плате.

Мой хронограф не имеет собственного источника питания, поэтому я выбрал первый вариант платы. Встроенная батарейка/аккумулятор сильно повысит цену, не добавив при этом практически ничего к юзабилити. Power bank и кабель для зарядки телефона валяется практически у каждого.

Характеристики само собой унаследованы от ATtiny85, его возможностей в нашем случае достаточно с головой. Фактически МК в хронографе не делает ничего, кроме опроса двух датчиков и управления дисплеем. Для тех, кто впервые сталкивается с Digispark-ом, я свёл наиболее важные особенности в таблицу:

Эту табличку я использую как шпаргалку при разработке различных девайсов на базе этой платы. Как вы наверное заметили, нумерация пинов для функции analogRead() отличается, это следует учитывать. И еще одна особенность: на третьем пине висит подтягивающий резистор на 1.5кОм, т.к. он используется в USB.

1.2. Дисплей на базе TM1637

Следующая важная деталь - цифровой дисплей, на который будет выводиться информация. Дисплей можно использовать любой, мой выбор обусловлен только дешевизной и простотой работы с ним. От дисплея в принципе можно вообще отказаться и выводить данные по кабелю на ПК, тогда девайс станет еще дешевле. Для работы понадобится библиотека DigitalTube . Сабж, на который я дал ссылку в начале поста, представляет собой клон дисплея Grove . Вид спереди:

Между цифрами расстояние одинаковое, поэтому при выключенном двоеточии числовые значения читаются нормально. Вместе со стандартной библиотекой поставляется пример, который работает с Digispark-ом без плясок с бубном:

Все, что умеет стандартная библиотека, - выводить числа 0-9 и буквы a-f, а так же менять яркость всего дисплея целиком. Значение цифры задается функцией display(int 0-3, int 0-15).

Экспресс-курс по использованию дисплея

// 1. Объявить заголовочный файл #include // 2. Задать пины #define CLK 0 #define DIO 1 // 3. Объявить объект TM1637 tm1637(CLK, DIO); // 4. Проинициализировать void setup() { tm1637.init(); tm1637.set(6); // Яркость } // 5. Использовать void loop() { // Вывод числа x на дисплей int x = 1234; tm1637.display(0, x / 1000); tm1637.display(1, x / 100 % 10); tm1637.display(2, x / 10 % 10); tm1637.display(3, x % 10); delay(500); }


Если попытаться вывести символ с кодом за границами , то дисплей показывает чушь, которая при этом не статичная, поэтому схитрить для вывода спецсимволов (градусов, минуса) без бубна не получится:

Это меня не устраивало, так как в своем хронографе я хотел предусмотреть вывод не только скорости, но и энергии пули (вычисляемой на основе заранее прописанной в скетче массы), эти два значения должны выводиться последовательно. Чтобы понять, что показывает дисплей в данный момент времени, нужно как-то разделять эти два значения визуально, например, при помощи символа «J». Конечно, можно тупо задействовать символ двоеточия как флаг-индикатор, но это же не тру и не кошерно) Поэтому я полез разбираться в библиотеку и на базе функции display сделал функцию setSegments(byte addr, byte data), которая зажигает в цифре с номером addr сегменты, закодированные в data:

Void setSegments(byte addr, byte data) { tm1637.start(); tm1637.writeByte(ADDR_FIXED); tm1637.stop(); tm1637.start(); tm1637.writeByte(addr|0xc0); tm1637.writeByte(data); tm1637.stop(); tm1637.start(); tm1637.writeByte(tm1637.Cmd_DispCtrl); tm1637.stop(); }
Кодируются сегменты предельно просто: младший бит data отвечает за самый верхний сегмент, и т.д. по часовой стрелке, седьмой бит отвечает за центральный сегмент. Например, символ "1" кодируется как 0b00000110. Восьмой, старший бит используется только во второй цифре и отвечает за двоеточие, во всех остальных цифрах он игнорируется. Чтобы облегчить себе жизнь я, как и полагается любому ленивому айтишнику, автоматизировал процесс получения кодов символов при помощи excel:

Теперь можно легко сделать так:

Let"s say HELLO

#include #define CLK 0 #define DIO 1 TM1637 tm1637(CLK, DIO); void setSegments(byte addr, byte data) { tm1637.start(); tm1637.writeByte(ADDR_FIXED); tm1637.stop(); tm1637.start(); tm1637.writeByte(addr|0xc0); tm1637.writeByte(data); tm1637.stop(); tm1637.start(); tm1637.writeByte(tm1637.Cmd_DispCtrl); tm1637.stop(); } void setup() { tm1637.init(); tm1637.set(6); } void loop() { // Вывод Hello setSegments(0, 118); setSegments(1, 121); setSegments(2, 54); setSegments(3, 63); delay(500); }

1.3. Датчики

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

Ценой одного светодиода определил, что ток больше 40мА для них смертелен, а напряжение питания должно быть ниже 3.3В. Фототранзистор немного прозрачный и реагирует на свет

2. Подготовка деталей и сборка

Схема очень простая и незамысловатая, из всех пинов digispark-a нам понадобятся только P0, P1 - для работы с дисплеем, а так же P2 - для работы с датчиками:

Как видно, один резистор ограничивает ток на светодиодах, второй - стягивает P2 к земле. Фототранзисторы соединены последовательно, поэтому прохождение пули перед любой оптопарой приводит к уменьшению напряжения на P2. Путем регистрации двух последовательных скачков напряжения и замера времени между ними мы можем определить скорость движения пули (зная расстояние между датчиками, ессно). Использование одного пина для замеров имеет еще один плюс - нет никакого требуемого направления движения пули, можно стрелять с обоих концов. Собирать будем из этой горстки деталей:

Я пошел по пути миниатюризации и решил сделать бутерброд при помощи куска макетной платы:

Весь бутерброд залил термоклеем для прочности:

Остается только разместить датчики в трубке и припаять провода:

На фото видно, что я разместил дополнительный электролит на 100мКф параллельно светодиодам, чтобы при питании от повербанка не было пульсаций ИК диодов.

Пин P2 в качестве входа был выбран не просто так. Напомню, что P3 и P4 используются в USB, поэтому использование P2 дает возможность прошивать девайс уже в собранном виде. Во-вторых, P2 - аналоговый вход, поэтому можно не использовать прерывания, а просто мерить разницу в цикле между предыдущим и текущим значением на нем, если разница выше некоторого порога - значит пуля проходит между одной из оптопар. Но есть одна программная хитрость, без которой приведенная схема не взлетит, о ней поговорим далее.

3. Прошивка

3.1. Пару слов о prescaler

Prescaler представляет собой делитель частоты, по-умолчанию в arduino-подобных платах он равен 128. От значения этой величины зависит максимальная частота опроса АЦП, по дефолту для 16 мГц контроллера получается 16/128 = 125 кГц. На каждую оцифровку уходит 13 операций, поэтому максимальная частота опроса пина - 9600 кГц (в теории, на практике реально не выше 7 кГц). Т.е. интервал между замерами примерно 120 мкс, это очень и очень много. Пуля, летящая со скоростью 300 м/с пролетит за это время 3,6 см - контроллер просто не успеет засечь факт прохождения пули через оптопару. Для нормальной работы нужен интервал между замерами как минимум 20 мкс, необходимое значение делителя для этого равно 16. Я пошел еще дальше и в своем девайсе использую делитель 8, делается это следующим образом:

#ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif void setup() { sbi(ADCSRA,ADPS2); cbi(ADCSRA,ADPS1); cbi(ADCSRA,ADPS0); ... }
Реальные замеры интервала analogRead на разных делителях:

3.2. Итоговый скетч

Я не буду подробно описывать код, он и так хорошо задокументирован. Вместо этого я в общих словах опишу алгоритм его работы. Итак, вся логика сводится к следующим этапам:
  • Первый цикл - измеряется разница между текущим и предыдущим значением на пине
  • Если разница больше заданного порога, то выходим из цикла и запоминаем текущее время (micros())
  • Второй цикл - аналогично предыдущему + счетчик времени в цикле
  • Если счетчик достиг заданной величины, то информирование об ошибке и переход к началу. Это позволяет не уходить циклу в вечность, если пуля по каким-то причинам не была замечена вторым датчиком
  • Если счетчик не переполнился и разница значений больше порога, то замеряем текущее время (micros())
  • На основе разницы во времени и расстоянии между датчиками вычисляем скорость и выводим на экран
  • Переход в начало
Это сильно упрощенная модель, в самом коде я добавил свистелок, в том числе вычисление и показ энергии пули на основе введенной заранее в коде массы пули.

Собственно, весь код

/* * Хронограф для измерения скорости движения пули, SinuX 23.03.2016 */ #include #define CLK 1 // Пин дисплея #define DIO 0 // Пин дисплея #define START_PIN 1 // Аналоговый пин старта #define END_PIN 1 // Аналоговый пин финиша #define START_LEV 50 // Порог срабатывания старта #define END_LEV 50 // Порог срабатывания финиша #define TIMEOUT 10000 // Время ожидания финиша в микросекундах #define BULLET_WEIGHT 0.00051 // Масса пули в килограммах (для вычисления энергии) #define ENCODER_DIST 0.1 // Расстояние между датчиками в метрах (10см = 0.1м) #define SHOW_DELAY 3000 // Время показа результата // Для ускорения analogRead #ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif // Служебные переменные int prevVal, curVal; unsigned long startTime, endTime; TM1637 tm1637(CLK, DIO); /* Переделанная функция TM1637::display(), которая позволяет зажигать отдельные сегменты * Нумерация сегментов: младший бит - верхний сегмент и т.д. по часовой стрелке * Центральный сегмент - старший бит */ void setSegments(byte addr, byte data) { tm1637.start(); tm1637.writeByte(ADDR_FIXED); tm1637.stop(); tm1637.start(); tm1637.writeByte(addr|0xc0); tm1637.writeByte(data); tm1637.stop(); tm1637.start(); tm1637.writeByte(tm1637.Cmd_DispCtrl); tm1637.stop(); } // Инициализация void setup() { // Устанавливаем prescaler на 8 для ускорения analogRead cbi(ADCSRA,ADPS2); sbi(ADCSRA,ADPS1); sbi(ADCSRA,ADPS0); // Инициализация дисплея tm1637.init(); tm1637.set(6); // Отображение приветствия setSegments(0, 118); setSegments(1, 121); setSegments(2, 54); setSegments(3, 63); delay(1000); } // Главный цикл void loop() { // Заставка ожидания showReady(); // Ожидание старта curVal = analogRead(START_PIN); do { prevVal = curVal; curVal = analogRead(START_PIN); } while (curVal - prevVal < START_LEV); startTime = micros(); // Ожидание финиша curVal = analogRead(END_PIN); do { prevVal = curVal; curVal = analogRead(END_PIN); // Если превышен интервал ожидания - показ ошибки и выход из цикла if (micros() - startTime >= TIMEOUT) { showError(); return; } } while (curVal - prevVal < END_LEV); endTime = micros(); // Вычисление и отображение результата showResult(); } // Отображение заставки ожидания выстрела void showReady() { setSegments(0, 73); setSegments(1, 73); setSegments(2, 73); setSegments(3, 73); delay(100); } // Вычисление и отображение скорости, энергии пули void showResult() { // Вычисление скорости пули в м/с и вывод на дисплей float bulletSpeed = ENCODER_DIST * 1000000 / (endTime - startTime); tm1637.display(0, (int)bulletSpeed / 100 % 10); tm1637.display(1, (int)bulletSpeed / 10 % 10); tm1637.display(2, (int)bulletSpeed % 10); setSegments(3, 84); delay(SHOW_DELAY); // Вычисление энергии в джоулях и вывод на дисплей float bulletEnergy = BULLET_WEIGHT * bulletSpeed * bulletSpeed / 2; tm1637.point(1); // Вместо точки ":" - костыль, но пойдет) tm1637.display(0, (int)bulletEnergy / 10 % 10); tm1637.display(1, (int)bulletEnergy % 10); tm1637.display(2, (int)(bulletEnergy * 10) % 10); setSegments(3, 30); delay(SHOW_DELAY); tm1637.point(0); } // Вывод ошибки при превышении времени ожидания пули void showError() { setSegments(0, 121); setSegments(1, 80); setSegments(2, 80); setSegments(3, 0); delay(SHOW_DELAY); }

4. Примеры работы

При правильном подключении девайс взлетел практически сразу, единственный обнаруженный недостаток - он негативно реагирует на светодиодное и люминисцентное освещение (частота пульсаций около 40 кГц), отсюда могут появляться спонтанные ошибки. Всего в девайсе предусмотрено 3 режима работы:

Приветствие после включения и переход в режим ожидания выстрела (экран заполняется полосками):

В случае ошибки - отображается «Err», и снова переход в режим ожидания:

Ну и сам замер скорости:

После выстрела сначала показывается скорость пули (с символом "n"), затем - энергия (символ "J"), причем энергия вычисляется с точностью до одного знака после запятой (на гифке видно, что при показе джоулей горит двоеточие). Корпус покрасивее найти пока не смог, поэтому просто залил все термосоплями:

Пожалуй, на этом у меня все, надеюсь, кому-то был полезен.

В своей первой публикации я хочу рассказать вам, как я собрал хронограф за пару вечеров из дешевых и доступных всем деталей. Как вы наверное уже догадались из названия, этот девайс служит для измерения скорости пули у пневматических (и не очень) винтовок и бывает полезным для контроля её технического состояния.

1. Детали и принадлежности

  • Китайский Digispark - 80 рублей на момент покупки
  • Сегментный дисплей на TM1637 - 90 рублей на момент покупки
  • ИК светодиоды и ИК фототранзисторы (10 пар) - 110 рублей на момент покупки, нам нужны 2 пары
  • Резисторы 220 Ом (100шт) - 70 рублей на момент покупки, нам нужно только 2 штуки
На этом заканчиваются детали, которые необходимо покупать. Резисторы можно не заказывать, похожие по номиналу (но не меньше!) можно выдернуть из ненужной бытовой электроники. Таким образом, суммарные затраты менее 350 рублей, это ничто по сравнению с ценой нового заводского хронографа (over 1000р за самый простой, который по факту еще примитивнее нашего сабжа). Кроме деталей нам пригодятся:
  • Провода - найти в оффлайне бесплатно не проблема
  • Кусок пластиковой водопроводной трубы длиной более 10см (диаметр по вкусу) - так же легко найти
  • Паяльные принадлежности
  • Мультиметр (опционально)
Первые 3 детали достойны отдельного рассмотрения, так как имеют свои особенности, поэтому начнем с мини-обзоров на них.

1.1. Digispark

Представляет собой простую миниатюрную Arduino-совместимую плату с ATtiny85 на борту. Как подключить к Arduino IDE читаем на официальном сайте проекта , там же можно найти драйвера для нее. Существует два основных вида этой платы: с microUSB и более брутальный с USB коннектором, разведенным прямо на плате.

Мой хронограф не имеет собственного источника питания, поэтому я выбрал первый вариант платы. Встроенная батарейка/аккумулятор сильно повысит цену, не добавив при этом практически ничего к юзабилити. Power bank и кабель для зарядки телефона валяется практически у каждого.

Характеристики само собой унаследованы от ATtiny85, его возможностей в нашем случае достаточно с головой. Фактически МК в хронографе не делает ничего, кроме опроса двух датчиков и управления дисплеем. Для тех, кто впервые сталкивается с Digispark-ом, я свёл наиболее важные особенности в таблицу:

Эту табличку я использую как шпаргалку при разработке различных девайсов на базе этой платы. Как вы наверное заметили, нумерация пинов для функции analogRead() отличается, это следует учитывать. И еще одна особенность: на третьем пине висит подтягивающий резистор на 1.5кОм, т.к. он используется в USB.

1.2. Дисплей на базе TM1637

Следующая важная деталь - цифровой дисплей, на который будет выводиться информация. Дисплей можно использовать любой, мой выбор обусловлен только дешевизной и простотой работы с ним. От дисплея в принципе можно вообще отказаться и выводить данные по кабелю на ПК, тогда девайс станет еще дешевле. Для работы понадобится библиотека DigitalTube . Сабж, на который я дал ссылку в начале поста, представляет собой клон дисплея Grove . Вид спереди:

Между цифрами расстояние одинаковое, поэтому при выключенном двоеточии числовые значения читаются нормально. Вместе со стандартной библиотекой поставляется пример, который работает с Digispark-ом без плясок с бубном:

Все, что умеет стандартная библиотека, - выводить числа 0-9 и буквы a-f, а так же менять яркость всего дисплея целиком. Значение цифры задается функцией display(int 0-3, int 0-15).

Экспресс-курс по использованию дисплея

// 1. Объявить заголовочный файл #include // 2. Задать пины #define CLK 0 #define DIO 1 // 3. Объявить объект TM1637 tm1637(CLK, DIO); // 4. Проинициализировать void setup() { tm1637.init(); tm1637.set(6); // Яркость } // 5. Использовать void loop() { // Вывод числа x на дисплей int x = 1234; tm1637.display(0, x / 1000); tm1637.display(1, x / 100 % 10); tm1637.display(2, x / 10 % 10); tm1637.display(3, x % 10); delay(500); }


Если попытаться вывести символ с кодом за границами , то дисплей показывает чушь, которая при этом не статичная, поэтому схитрить для вывода спецсимволов (градусов, минуса) без бубна не получится:

Это меня не устраивало, так как в своем хронографе я хотел предусмотреть вывод не только скорости, но и энергии пули (вычисляемой на основе заранее прописанной в скетче массы), эти два значения должны выводиться последовательно. Чтобы понять, что показывает дисплей в данный момент времени, нужно как-то разделять эти два значения визуально, например, при помощи символа «J». Конечно, можно тупо задействовать символ двоеточия как флаг-индикатор, но это же не тру и не кошерно) Поэтому я полез разбираться в библиотеку и на базе функции display сделал функцию setSegments(byte addr, byte data), которая зажигает в цифре с номером addr сегменты, закодированные в data:

Void setSegments(byte addr, byte data) { tm1637.start(); tm1637.writeByte(ADDR_FIXED); tm1637.stop(); tm1637.start(); tm1637.writeByte(addr|0xc0); tm1637.writeByte(data); tm1637.stop(); tm1637.start(); tm1637.writeByte(tm1637.Cmd_DispCtrl); tm1637.stop(); }
Кодируются сегменты предельно просто: младший бит data отвечает за самый верхний сегмент, и т.д. по часовой стрелке, седьмой бит отвечает за центральный сегмент. Например, символ "1" кодируется как 0b00000110. Восьмой, старший бит используется только во второй цифре и отвечает за двоеточие, во всех остальных цифрах он игнорируется. Чтобы облегчить себе жизнь я, как и полагается любому ленивому айтишнику, автоматизировал процесс получения кодов символов при помощи excel:

Теперь можно легко сделать так:

Let"s say HELLO

#include #define CLK 0 #define DIO 1 TM1637 tm1637(CLK, DIO); void setSegments(byte addr, byte data) { tm1637.start(); tm1637.writeByte(ADDR_FIXED); tm1637.stop(); tm1637.start(); tm1637.writeByte(addr|0xc0); tm1637.writeByte(data); tm1637.stop(); tm1637.start(); tm1637.writeByte(tm1637.Cmd_DispCtrl); tm1637.stop(); } void setup() { tm1637.init(); tm1637.set(6); } void loop() { // Вывод Hello setSegments(0, 118); setSegments(1, 121); setSegments(2, 54); setSegments(3, 63); delay(500); }

1.3. Датчики

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

Ценой одного светодиода определил, что ток больше 40мА для них смертелен, а напряжение питания должно быть ниже 3.3В. Фототранзистор немного прозрачный и реагирует на свет

2. Подготовка деталей и сборка

Схема очень простая и незамысловатая, из всех пинов digispark-a нам понадобятся только P0, P1 - для работы с дисплеем, а так же P2 - для работы с датчиками:

Как видно, один резистор ограничивает ток на светодиодах, второй - стягивает P2 к земле. Фототранзисторы соединены последовательно, поэтому прохождение пули перед любой оптопарой приводит к уменьшению напряжения на P2. Путем регистрации двух последовательных скачков напряжения и замера времени между ними мы можем определить скорость движения пули (зная расстояние между датчиками, ессно). Использование одного пина для замеров имеет еще один плюс - нет никакого требуемого направления движения пули, можно стрелять с обоих концов. Собирать будем из этой горстки деталей:

Я пошел по пути миниатюризации и решил сделать бутерброд при помощи куска макетной платы:

Весь бутерброд залил термоклеем для прочности:

Остается только разместить датчики в трубке и припаять провода:

На фото видно, что я разместил дополнительный электролит на 100мКф параллельно светодиодам, чтобы при питании от повербанка не было пульсаций ИК диодов.

Пин P2 в качестве входа был выбран не просто так. Напомню, что P3 и P4 используются в USB, поэтому использование P2 дает возможность прошивать девайс уже в собранном виде. Во-вторых, P2 - аналоговый вход, поэтому можно не использовать прерывания, а просто мерить разницу в цикле между предыдущим и текущим значением на нем, если разница выше некоторого порога - значит пуля проходит между одной из оптопар. Но есть одна программная хитрость, без которой приведенная схема не взлетит, о ней поговорим далее.

3. Прошивка

3.1. Пару слов о prescaler

Prescaler представляет собой делитель частоты, по-умолчанию в arduino-подобных платах он равен 128. От значения этой величины зависит максимальная частота опроса АЦП, по дефолту для 16 мГц контроллера получается 16/128 = 125 кГц. На каждую оцифровку уходит 13 операций, поэтому максимальная частота опроса пина - 9600 кГц (в теории, на практике реально не выше 7 кГц). Т.е. интервал между замерами примерно 120 мкс, это очень и очень много. Пуля, летящая со скоростью 300 м/с пролетит за это время 3,6 см - контроллер просто не успеет засечь факт прохождения пули через оптопару. Для нормальной работы нужен интервал между замерами как минимум 20 мкс, необходимое значение делителя для этого равно 16. Я пошел еще дальше и в своем девайсе использую делитель 8, делается это следующим образом:

#ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif void setup() { sbi(ADCSRA,ADPS2); cbi(ADCSRA,ADPS1); cbi(ADCSRA,ADPS0); ... }
Реальные замеры интервала analogRead на разных делителях:

3.2. Итоговый скетч

Я не буду подробно описывать код, он и так хорошо задокументирован. Вместо этого я в общих словах опишу алгоритм его работы. Итак, вся логика сводится к следующим этапам:
  • Первый цикл - измеряется разница между текущим и предыдущим значением на пине
  • Если разница больше заданного порога, то выходим из цикла и запоминаем текущее время (micros())
  • Второй цикл - аналогично предыдущему + счетчик времени в цикле
  • Если счетчик достиг заданной величины, то информирование об ошибке и переход к началу. Это позволяет не уходить циклу в вечность, если пуля по каким-то причинам не была замечена вторым датчиком
  • Если счетчик не переполнился и разница значений больше порога, то замеряем текущее время (micros())
  • На основе разницы во времени и расстоянии между датчиками вычисляем скорость и выводим на экран
  • Переход в начало
Это сильно упрощенная модель, в самом коде я добавил свистелок, в том числе вычисление и показ энергии пули на основе введенной заранее в коде массы пули.

Собственно, весь код

/* * Хронограф для измерения скорости движения пули, SinuX 23.03.2016 */ #include #define CLK 1 // Пин дисплея #define DIO 0 // Пин дисплея #define START_PIN 1 // Аналоговый пин старта #define END_PIN 1 // Аналоговый пин финиша #define START_LEV 50 // Порог срабатывания старта #define END_LEV 50 // Порог срабатывания финиша #define TIMEOUT 10000 // Время ожидания финиша в микросекундах #define BULLET_WEIGHT 0.00051 // Масса пули в килограммах (для вычисления энергии) #define ENCODER_DIST 0.1 // Расстояние между датчиками в метрах (10см = 0.1м) #define SHOW_DELAY 3000 // Время показа результата // Для ускорения analogRead #ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif // Служебные переменные int prevVal, curVal; unsigned long startTime, endTime; TM1637 tm1637(CLK, DIO); /* Переделанная функция TM1637::display(), которая позволяет зажигать отдельные сегменты * Нумерация сегментов: младший бит - верхний сегмент и т.д. по часовой стрелке * Центральный сегмент - старший бит */ void setSegments(byte addr, byte data) { tm1637.start(); tm1637.writeByte(ADDR_FIXED); tm1637.stop(); tm1637.start(); tm1637.writeByte(addr|0xc0); tm1637.writeByte(data); tm1637.stop(); tm1637.start(); tm1637.writeByte(tm1637.Cmd_DispCtrl); tm1637.stop(); } // Инициализация void setup() { // Устанавливаем prescaler на 8 для ускорения analogRead cbi(ADCSRA,ADPS2); sbi(ADCSRA,ADPS1); sbi(ADCSRA,ADPS0); // Инициализация дисплея tm1637.init(); tm1637.set(6); // Отображение приветствия setSegments(0, 118); setSegments(1, 121); setSegments(2, 54); setSegments(3, 63); delay(1000); } // Главный цикл void loop() { // Заставка ожидания showReady(); // Ожидание старта curVal = analogRead(START_PIN); do { prevVal = curVal; curVal = analogRead(START_PIN); } while (curVal - prevVal < START_LEV); startTime = micros(); // Ожидание финиша curVal = analogRead(END_PIN); do { prevVal = curVal; curVal = analogRead(END_PIN); // Если превышен интервал ожидания - показ ошибки и выход из цикла if (micros() - startTime >= TIMEOUT) { showError(); return; } } while (curVal - prevVal < END_LEV); endTime = micros(); // Вычисление и отображение результата showResult(); } // Отображение заставки ожидания выстрела void showReady() { setSegments(0, 73); setSegments(1, 73); setSegments(2, 73); setSegments(3, 73); delay(100); } // Вычисление и отображение скорости, энергии пули void showResult() { // Вычисление скорости пули в м/с и вывод на дисплей float bulletSpeed = ENCODER_DIST * 1000000 / (endTime - startTime); tm1637.display(0, (int)bulletSpeed / 100 % 10); tm1637.display(1, (int)bulletSpeed / 10 % 10); tm1637.display(2, (int)bulletSpeed % 10); setSegments(3, 84); delay(SHOW_DELAY); // Вычисление энергии в джоулях и вывод на дисплей float bulletEnergy = BULLET_WEIGHT * bulletSpeed * bulletSpeed / 2; tm1637.point(1); // Вместо точки ":" - костыль, но пойдет) tm1637.display(0, (int)bulletEnergy / 10 % 10); tm1637.display(1, (int)bulletEnergy % 10); tm1637.display(2, (int)(bulletEnergy * 10) % 10); setSegments(3, 30); delay(SHOW_DELAY); tm1637.point(0); } // Вывод ошибки при превышении времени ожидания пули void showError() { setSegments(0, 121); setSegments(1, 80); setSegments(2, 80); setSegments(3, 0); delay(SHOW_DELAY); }

4. Примеры работы

При правильном подключении девайс взлетел практически сразу, единственный обнаруженный недостаток - он негативно реагирует на светодиодное и люминисцентное освещение (частота пульсаций около 40 кГц), отсюда могут появляться спонтанные ошибки. Всего в девайсе предусмотрено 3 режима работы:

Приветствие после включения и переход в режим ожидания выстрела (экран заполняется полосками):

В случае ошибки - отображается «Err», и снова переход в режим ожидания:

Ну и сам замер скорости:

После выстрела сначала показывается скорость пули (с символом "n"), затем - энергия (символ "J"), причем энергия вычисляется с точностью до одного знака после запятой (на гифке видно, что при показе джоулей горит двоеточие). Корпус покрасивее найти пока не смог, поэтому просто залил все термосоплями:

Пожалуй, на этом у меня все, надеюсь, кому-то был полезен.

Хронограф является универсальным прибором, способным проводить замер скорости полета предметов малого размера. Настройку и тестирование пневматики удобнее всего проводить с хронографами рамочного типа. Они могут улавливать движение пуль, болтов арбалета, стрел, скоб рогатки. Хронограф для пневматики можно изготовить своими руками или приобрести в специализированных магазинах.

Типы хронографов

Измерение стартовой скорости пули при помощи хронографа, позволяет выявить мощность пистолета или винтовки, подобрать подходящие пули, рассчитать баллистические поправки, провести сравнение скорости в начале и после модернизации оружия.

Существуют различные типы хронографов . Надульная модель занимает мало места и без труда помещается в кармане чехла, а также она меньше тратит энергию. Для конкретного типа оружия может потребоваться переходник . Такой вид не зависит от освещения и удобен в использовании на природе. Прицельную стрельбу можно вести вместе с прибором. Для СО2 такая модель не подходит.

При обладании внушительным арсеналом, лучше приобрести рамочный хронограф , чтобы не закупать большое количество переходников. Этот тип прибора хорошо работает с СО2, имеет разъем для внешнего источника питания. Броня позволяет проводить измерение показателей на различной дистанции, не опасаясь повредить механизм. Наличие дополнительного экрана помогает оперативно получать результаты.

Существуют также рамочные модели большого размера, расширяющие число возможностей. Такой вариант подходит для использования с любыми видами оружия, удобен при стационарном подключении к сети. В качестве альтернативы, хронограф может получать питание от батарей класса АА в количестве восьми штук. В отличие от модели малого размера, большой аппарат обладает встроенным индикатором фронтального типа . Можно дополнительно установить съемный экран. При помощи USB-адаптера можно переносить данные измерения с устройства на компьютер.

Покупка хронографа для пневматики

Купить в Москве и Санкт-Петербурге различные типы хронографов можно в следующих магазинах:

  • Airgun Store - по цене от 3500 до 24 тыс. р.;
  • Diada Arms - по цене от 4 тыс. до 13 тыс. р.;
  • Pnevmat 24 - по цене от 4 тыс. до 7 тыс. р.;
  • Oxotnika.net - по цене от 3 тыс. до 20 тыс. р.

В этих магазинах также предлагаются различные комплектующие и аксессуары для хронографов. Можно приобрести более бюджетную модель на AliExpress по цене от 3 тыс. р. или купить б/у, например, на портале Guns.ru или Avito по цене от 1500 р.

Хронограф рамочного типа для пневматики своими руками

Хронограф фиксирует время пролета пули между несколькими датчиками и рассчитывает ее скорость. Устройство состоит из трех частей:

  • рабочей зоны, пропускающей через себя пулю;
  • схемы, проводящей вычисления;
  • дисплея, показывающего рассчитанные результаты.

Схемы для хронографа могут быть различны по стоимости, функциональности и дизайну. Простейшие датчики считывают падающий на них свет, интенсивность которого изменяется по мере перемещения пули, отбрасывающей тень. Чувствительные к свету элементы являются частью многих хронографов, сделанных в домашних условиях и в заводских моделях.

Самостоятельно изготовленный прибор имеет несколько преимуществ:

Наряду с этим, у аппарата есть и свои недостатки :

  • громоздкость конструкции;
  • потребность в защите от попадания для лицевой стороны рабочей зоны;
  • влияние погодных условий и освещения на работу;
  • чувствительность схемы оптики к значительным механическим воздействиям, включающим попадания пулевых осколков и рикошеты;
  • вывод ложных показаний при появлении в камере посторонних предметов, таких как снег, насекомые или механические осколки;
  • влияние траектории полета на фиксируемую скорость пули (движение объекта по диагонали снижает показатель).

Компоненты и материалы для сборки

Общее количество деталей и их сложность зависят от уровня навыков проектирования и установки схем у пользователя. Некоторые компоненты являются обязательными при любом виде сборки:

  • светодиоды для создания искусственного источника света;
  • паяльник с флюсом и припоем для закрепления проводов и установки микросхемы;
  • оптические приемники для считывания уровня освещенности во время пролета пули через светодиоды;
  • микросхема для определения времени полета пули и расчета скорости;
  • дисплей для отображения результатов замеров;
  • прямоугольный полый корпус, закрытый с четырех сторон (лучше выбирать изделие из цельного металла, которое будет устойчиво к ударам).

Этапы монтажа хронографа

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

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

На следующем этапе устанавливается и подключается к датчикам плата, размечаются секции под введение питания. Для самостоятельного составления схемы можно использовать рис. 1.

Рис. 1 Микросхема хронографа

Когда основные узлы будут собраны, схему нужно будет защитить от механических воздействий и влаги. Для этой задачи подойдет коробок из пластмассы для печатной платы, который будет иметь выходы к батарее, дисплею и датчикам.

Принцип работы хронографа собственного изготовления

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

Процесс замера скорости проходит три этапа:

  • пуля проходит через ось начального датчика, обнуляя счетчик времени в микропроцессоре;
  • после пересечения пулей оси следующего датчика, время останавливается и данные передаются для проведения расчетов;
  • микропроцессор проводит вычисления и выводит показатели скорости на дисплей.

Наглядно работу хронографа рамочного типа можно увидеть на рис. 2.

Рис. 2 Схема работы хронографа

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


В этой статье мы рассмотрим, как можно сделать простой хронограф из недорогих и доступных деталей. Приспособление необходимо для того, чтобы измерять скорость полета пули у винтовки. Эти цифры нужны для того, чтобы определить, в каком состоянии находится винтовка, ведь со временем некоторые узлы пневматики изнашиваются и требуют замены.

Подготавливаем необходимые материалы и инструменты:
- китайский Digispark (обошелся на момент покупки в 80 рублей);
- дисплей сегментного типа на TM1637 (обошелся при покупке в 90 рублей);
- инфракрасные светодиоды и фототранзисторы (10 пар) - стоимость составила 110 рублей;
- сто резисторов на 220 Ом обошлись в 70 рублей, но из них будут нужны только два.

Вот и все, это весь список элементов, которые нужно будет купить. Кстати резисторы тоже можно найти в старой бытовой технике. Можно ставить и больше по номиналу, но не меньше. В итоге можно уложиться в 350 рублей, а ведь это не так много, учитывая, что заводской хронограф обойдется как минимум в 1000 рублей, да и сборка там куда хуже нашей самоделки .

Помимо всего прочего, нужно запастись такими деталями как:
- провода;
- кусок трубы длиной не менее 10 см (подойдет пластиковая водопроводная);
- все для пайки;
- мультиметр (желательно).


Первые описанные три детали имеют свои нюансы, поэтому каждую из них нужно рассмотреть отдельно

Digispark
Этот элемент представляет собой миниатюрную плату, которая совместима с Arduino , на борту она имеет ATtiny85. Как подключить этот элемент к Arduino IDE, можно почитать на , еще там можно скачать для нее драйвера.
У этой платы есть несколько вариантов, в одной используется microUSB, а другая оборудована USB-коннектором, который разведен прямо на плате. В связи с тем, что самоделка не имеет индивидуального блока питания, автор выбрал первый вариант платы. Если установить в самоделку батарею или аккумулятор, это сильно повысит ее цену, причем не сильно повлияет на практичность. А кабель для зарядки мобильного и Power bank есть почти у каждого.


Что касается характеристик, то они подобны ATtiny85, здесь его возможностей хватает с избытком. Микроконтроллер в хронографе всего лишь опрашивает датчики и управляет дисплеем.
Если вы еще ни разу не встречались с Digispark-ом, наиболее важные нюансы можно посмотреть в таблице.


Важно учитывать тот факт, что нумерация пинов для функции analogRead() имеет отличия. А еще на третьем пине находится подтягивающий резистор номиналом 1.5кОм, поскольку он применяется в USB.

Пару слов о дисплее
Дисплей для самоделки можно использовать любой, но автор остановил свой выбор на дешевом варианте. Чтобы сделать устройство еще дешевле, от дисплея можно отказаться совсем. Данные просто можно через кабель выводить на компьютер. Здесь будет нужна . Рассмотренный дисплей является копией дисплея .
Как выглядит дисплей спереди и сзади можно увидеть на фото.




Поскольку расстояния между цифрами одинаковые, то при выключенном двоеточии цифры читаются без проблем. Стандартная библиотека способна выводить числа в диапазоне 0-9. буквы в диапазоне a-f, а еще есть возможность для изменения яркости всего дисплея. Значения цифры можно задать, используя функцию display(int 0-3, int 0-15).


Как использовать дисплей

// 1. Объявить заголовочный файл
#include
// 2. Задать пины
#define CLK 0
#define DIO 1
// 3. Объявить объект
TM1637 tm1637(CLK, DIO);
// 4. Проинициализировать
void setup() {
tm1637.init();
tm1637.set(6); // Яркость
}
// 5. Использовать
void loop() {
// Вывод числа x на дисплей
int x = 1234;
tm1637.display(0, x / 1000);
tm1637.display(1, x / 100 % 10);
tm1637.display(2, x / 10 % 10);
tm1637.display(3, x % 10);
delay(500);
}

Если попробовать выйти за пределы значений , то дисплей будет показывать неразбериху, которая плюс ко всему еще и является не статичной. Поэтому для вывода спецсимволов, таких как градусы, минусы и пр., придется повозиться.


Автор хотел, чтобы на дисплее выводилась и готовая энергия полета пули, что вычислялось бы в зависимости от скорости пули и ее массы. Значения по задумке должны были выводиться последовательно, а чтобы понять, где какое, их нужно как-то отметить, к примеру, с помощью буквы «J». В крайнем случае, можно просто задействовать двоеточие, но автора это не устроило, и он полез в библиотеку. В итоге на базе функции display была сделана функция setSegments(byte addr, byte data), она зажигает в цифре с номером addr сегменты, которые закодированы в dаta:


{
tm1637.start();
tm1637.stop();
tm1637.start();
tm1637.writeByte(addr|0xc0);
tm1637.writeByte(data);
tm1637.stop();
tm1637.start();
tm1637.stop();
}

Кодируются такие сегменты довольно просто, за верхний сегмент несет ответственность младший бит data, ну а далее по часовой стрелке, 7-ой бит несет ответственность за средний сегмент. Символ «1» при кодировке выглядит как 0b00000110. За двоеточие отвечает восьмой старший бит, он используется во второй цифре, а во всех других игнорируется. Впоследствии автор автоматизировал процесс получения кодов, используя Exсel.


Что же в итоге вышло, можно увидеть на фото




#include
#define CLK 0
#define DIO 1
TM1637 tm1637(CLK, DIO);

void setSegments(byte addr, byte data)
{
tm1637.start();
tm1637.writeByte(ADDR_FIXED);
tm1637.stop();
tm1637.start();
tm1637.writeByte(addr|0xc0);
tm1637.writeByte(data);
tm1637.stop();
tm1637.start();
tm1637.writeByte(tm1637.Cmd_DispCtrl);
tm1637.stop();
}

void setup() {
tm1637.init();
tm1637.set(6);
}

void loop() {
// Вывод Hello
setSegments(0, 118);
setSegments(1, 121);
setSegments(2, 54);
setSegments(3, 63);
delay(500);
}



Ну и наконец, датчики

О датчиках точной информации не предоставлено, известно только, что они имеют длину волны 940 нм. В ходе экспериментов было выяснено, что датчики не способны выдерживать ток более 40 мА. Что касается напряжения питания, то оно не должно быть выше 3.3В. Что касается фототранзистора, то он имеет немного прозрачный корпус и реагирует на свет.


Приступаем к сборке и настройке самоделки:

Шаг первый. Сборка

Собирается все по очень простой схеме. Из всех пинов будут нужны всего Р0, Р1 и Р2. Первые два используются для дисплея, а Р2 нужен для работы датчиков.
Как можно заметить, один резистор используется для того, чтобы ограничить ток для светодиодов, ну а второй стягивает Р2 на землю. В связи с тем что, фототранзисторы подключаются параллельно, то когда пуля будет проходить перед любой оптопарой, напряжение на Р2 будет падать. Чтобы определить скорость полета пули, нужно знать расстояние между датчиками, замерить два скачка напряжения и определить время, за которое они произошли.
В связи с тем, что будет использоваться только один пин, не имеет значения, с какой стороны стрелять. Фототранзисторы в любом случае заметят пулю.










Собирается все из деталей, которые видно на фото. Чтобы все собрать, автор решил использовать макетную доску. Потом вся конструкция для прочности была залита термоклеем. Датчики размещаются на трубе и к ним припаиваются провода.
Чтобы диоды не пульсировали при питании от повербанка, автор установил параллельно светодиодам электролит на 100 мКф.




Еще важно отметить, что пин Р2 был выбран не просто так, дело в том, что Р3 и Р4 применяются в USB, поэтому теперь с помощью Р2 есть возможность прошить самоделку уже после сборки.
Еще Р2 является аналоговым входом, поэтому использовать прерывание нет надобности. Можно просто измерять показания между текущем и предыдущем значением, если разница становится выше определенного порога, значит, в этот момент пуля как раз проходит возле оптопары.

Шаг второй. Прошивка

Prescaler является делителем частоты, в стандартных случаях в платах наподобие Arduino он равен 128. Эта цифра влияет на то, как часто идет опрос АЦП. То есть для дефолтных 16 мГц выходит 16/128 = 125 кГц. Каждая оцифровка состоит из 13 операций, поэтому пин может максимально опрашиваться со скоростью 9600 кГц. На практике же это не более 7 кГц. В итоге интервал между замерами составляет 120 мкс, что слишком много для работы самоделки. Если пуля будет лететь со скоростью 300 м/с, она преодолеет за это время путь в 3.6 см, то есть контроллер просто не сможет ее заметить. Чтобы все работало нормально, интервал между замерами должен быть как минимум 20 мкс. Для этого значение делителя должно быть равно 16-ти. Автор же сделал делитель 8, как это сделать, можно увидеть ниже.
Понравилась статья? Поделитесь ей