image

Идея сделать лазерную пушку, которая наводит два луча в одну точку, появилась у меня после игры Fallout: New Vegas. Прототипом моего лазерного пистолета послужило уникальное оружие «Алгоритм Эвклида», которое наносит удар с орбитальной станции. Параллели между лазерной пушкой с двумя лучами и орбитальной станцией простые: у меня завалялось два лазерных модуля, а картинка прицела «Алгоритма Эвклида» подала мне идею сфокусировать два лазерных луча в одну точку, чтобы получить большую мощностью.

Немного пафоса


Делать «просто пушку» с фокусировкой мне не хотелось, и я решил разнообразить ее дополнительными ~~пафосными~~ опциями, как, например, озвучка стрельбы, отображение треугольника, как у «Алгоритма Эвклида» из игры, и чтобы он менялся в зависимости от дальности фокусировки двух лазеров, а также всякие мелочи вроде мониторинга напряжения питания и т.д. В итоге получилось то, что можно увидеть на фото и видео:



Фото пушки
image

image

image

Основная часть


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

image

image

Под лазерными модулями располагался ультразвуковой измеритель расстояния с максимальной дальностью до 4 метров. Конечно, это небольшое расстояние, впрочем пушка все равно не претендовала на что-то серьезнее обычной игрушки.

подробнее про работу ультразвукового датчика
Ультразвуковой измеритель расстояния работает достаточно просто, главное — запустить его и измерить длину пришедшего импульса и затем по формуле преобразовать длину импульса в расстояние.

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

Внимание! Весь код был написан на языке С в среде CVAVR 3.

//  код был написан на языке С в среде CVAVR 3 для mega128
#define HS PORTB.6 // ножка МК для управления ультразвковым датчиком 
// состояния флагов
#define _TRUE 1
#define _ FALSE 0

unsigned int distance; // сырые данные расстояния
unsigned char takt = 0; // этап измерения
unsigned char isErrorHsFlag = _FALSE; // флаг ошибки измерения (переполнение таймера)
unsigned char isFlag = _FALSE; // флаг состояния измерения (измерено или еще нет)

// нужно настроить таймер 1 так, чтобы число 0xFFFF в его счетном регистре 
// набиралось больше, чем за 38 мс.
// в данном примере для МК использовался кварц 16 МГц.

// прерывание таймера 1 по переполнению, нужно для обнаружения ошибки 
interrupt [TIM1_OVF] void timer1_ovf_isr(void) {
 isErrorHsFlag = _TRUE;
}

// внешнее прерывание 
interrupt [EXT_INT4] void ext_int4_isr(void) {
 takt++;
 switch (takt) {
 case 1:
  // обнуляем таймер
  TCNT1 = 0;
  isErrorHsFlag = _FALSE;
 break;
 case 2: 
  // получаем дистанцию
  distance = TCNT1;
  takt = 0;
  isFlag = _TRUE;
 break;
}; 
 
 if (isErrorHsFlag == _TRUE) {
  // в случае переполнения таймера была ошибка
  distance = 0xFFFF;
  isErrorHsFlag = _FALSE;
 }
}

// функция возвращает расстояние
unsigned int getDistance(void) {
 unsigned char whileTakt = 0; // для количества циклов (лимит ожидания)
 // устанавливаем ножку МК в лог. 0
 HS = 0;
 // обнуляем флаги и регистр состояния
 takt = 0;
 isFlag = _FALSE;
 isErrorHsFlag = _FALSE;
 // запускаем измерение расстояния
 HS=1;
 delay_us(10);
 HS=0; 
 whileTakt=0;
 // ждем измерения
 while (isFlag == _FALSE) {
  whileTakt++; 
  delay_ms(1);
  // если лимит времени превышен (измерение длится больше 38 мс)
  if (whileTakt > 38) {
   isFlag = _TRUE; // выходим из цикла
  };
 };  
 return distance;  
};


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

Код медианного фильтра
// для медианного фильтра
#define NULL         0
#define STOPPER   0 /* Smaller than any datum */
#define MEDIAN_FILTER_SIZE 5

// медианный фильтр
typedef struct pair{
  struct pair *point; /* Pointers forming list linked in sorted order */
  unsigned int value; /* Values to sort */
} PAIR_T;

static PAIR_T small = {NULL, STOPPER};

static PAIR_T buffer[MEDIAN_FILTER_SIZE] = {0};


/* Pointer into circular buffer of data */
static PAIR_T *datpoint = buffer;
/* Chain stopper */

/* Pointer to head (largest) of linked list.*/
static PAIR_T big = {&small, 0};

/* Pointer to successor of replaced data item */
struct pair *successor;
/* Pointer used to scan down the sorted list */
struct pair *scan;
/* Previous value of scan */
struct pair *scanold;
/* Pointer to median */
struct pair *median;
unsigned int i;



unsigned int MedianFilter(unsigned int datum)
{


if (datum == STOPPER){
   datum = STOPPER + 1; /* No stoppers allowed. */
}

if ( (++datpoint - buffer) >= MEDIAN_FILTER_SIZE){
   datpoint = buffer; /* Increment and wrap data in pointer.*/
}

datpoint->value = datum; /* Copy in new datum */
successor = datpoint->point; /* Save pointer to old value's successor */
median = &big; /* Median initially to first in chain */
scanold = NULL; /* Scanold initially null. */
scan = &big; /* Points to pointer to first (largest) datum in chain */

/* Handle chain-out of first item in chain as special case */
if (scan->point == datpoint){
   scan->point = successor;
}

scanold = scan; /* Save this pointer and */
scan = scan->point ; /* step down chain */

/* Loop through the chain, normal loop exit via break. */
for (i = 0 ; i < MEDIAN_FILTER_SIZE; ++i){
  /* Handle odd-numbered item in chain */
  if (scan->point == datpoint){
    scan->point = successor; /* Chain out the old datum.*/
  }

  if (scan->value < datum){ /* If datum is larger than scanned value,*/
    datpoint->point = scanold->point; /* Chain it in here. */
    scanold->point = datpoint; /* Mark it chained in. */
    datum = STOPPER;
  };

  /* Step median pointer down chain after doing odd-numbered element */
  median = median->point; /* Step median pointer. */
  if (scan == &small){
    break; /* Break at end of chain */
  }
  scanold = scan; /* Save this pointer and */
  scan = scan->point; /* step down chain */

  /* Handle even-numbered item in chain. */
  if (scan->point == datpoint){
    scan->point = successor;
  }

  if (scan->value < datum){
    datpoint->point = scanold->point;
    scanold->point = datpoint;
    datum = STOPPER;
  }

  if (scan == &small){
    break;
  }

  scanold = scan;
  scan = scan->point;
}

return median->value;
}


Код для вычисления угла поворота сервопривода
#define CONST_RAD 5092.95817  //константа множителя для углов
unsigned int ac,bc,rad; // катеты ac bc и угол поворота
float bck; // коэффициент для катета bc

// катет AC это расстояние до цели
ac = MedianFilter(getDistance());
if (ac > 50000) ac = 50000; // ограничение дальности
// чтобы уменьшить погрешность, считаем сразу без перевода в сантиметры
// катет bc - это расстояние между лазерами
rad = atan(ac / (bc * bck)); 
// устанавливаем угол поворота сервопривода
pwmServo(CONST_RAD*rad);


Звук «пиу-пиу»


Конечно, реальный лазер не звучит, однако смотреться пушка будет куда эффектнее, если добавить звук и еще озвучку некоторых функций, поэтому я решил реализовать WAV-плеер внутри МК, подключив к нему flash-карту памяти на 4 Гб. Вывод звука осуществлялся через ШИМ, при этом сигнал ШИМ'а управлял транзистором, которой уже управлял током через динамик. Сами звуки я сделал в программе Fruity Loops 9 для создания музыки.

Кривой код WAV-плеера
// код был написан на языке С в среде CVAVR 3 для mega128
// для работы с SD картой и файловой системой FAT необходимы библиотеки:
// #include <sdcard.h>
// #include <ff.h>

//для работы с SD картой
static FRESULT f_err_code; // FRESULT для функций модуля
static FATFS FATFS_Obj; // структура - логический раздел
unsigned int ByteRead = 255; //количество релаьно считанных байт основного файла
FIL fil_obj; //структура файла, с которым работаем
char var[127]; //буфер, сюда мы поместим то, что считаем из основного файла.

// функция открывает трек по его номеру
// для каждого номера прописывается название файла.
// Для работы функции нужно иметь два таймера, таймер 1 и таймер 2. 
// таймер 2 нужен для вывода ШИМ сигнала, частота ШИМ - максимальная. Настройка - "быстрый ШИМ"
// в данном примере для МК использовался кварц 16 МГц.
void openSnd(unsigned char nSnd) {
 switch (nSnd) {
 case 0:
  f_err_code = f_open(&fil_obj, "but_1.wav", FA_READ);  //пытаемся открыть файл "but_1.wav"
 break; 
 case 1:
  f_err_code = f_open(&fil_obj, "but_2.wav", FA_READ); 
 break;
 case 2:
  f_err_code = f_open(&fil_obj, "but_no.wav", FA_READ); 
 break;
 case 3:
  f_err_code = f_open(&fil_obj, "but_ok.wav", FA_READ);
 break;
 case 4:
  f_err_code = f_open(&fil_obj, "but_sa.wav", FA_READ); 
 break; 
 case 5:
  f_err_code = f_open(&fil_obj, "but.wav", FA_READ); 
 break;
 case 6:
 f_err_code = f_open(&fil_obj, "warning.wav", FA_READ); 
 break;
 case 7:
  f_err_code = f_open(&fil_obj, "on.wav", FA_READ); /
 break;
 case 8:
  f_err_code = f_open(&fil_obj, "laz_sys.wav", FA_READ); 
 break;
 case 9:
  f_err_code = f_open(&fil_obj, "laz_act.wav", FA_READ);
 break;
 case 10:
  f_err_code = f_open(&fil_obj, "laz_actk.wav", FA_READ);
 break;
 case 11:
  f_err_code = f_open(&fil_obj, "voice_s.wav", FA_READ);
 break;
 case 12:
  f_err_code = f_open(&fil_obj, "bat_full.wav", FA_READ); 
 break; 
 case 13:
  f_err_code = f_open(&fil_obj, "bat_at.wav", FA_READ); 
 break;
 case 14:
  f_err_code = f_open(&fil_obj, "Bat_a.wav", FA_READ); 
 break;
 case 15:
  f_err_code = f_open(&fil_obj, "zel_no.wav", FA_READ);
 break;
 case 16:
  f_err_code = f_open(&fil_obj, "new_1.wav", FA_READ);
 break;
 case 17:
  f_err_code = f_open(&fil_obj, "new_2.wav", FA_READ); 
 break;
 case 18:
  f_err_code = f_open(&fil_obj, "new_3.wav", FA_READ); 
 break;
 case 19:
  f_err_code = f_open(&fil_obj, "new_4.wav", FA_READ); 
 break;
 case 20:
  f_err_code = f_open(&fil_obj, "new_5.wav", FA_READ);
 break;
};  
     
 // сообщаем об ошибках
 if (f_err_code &  FR_OK) puts("FR_OK\r\n");
 else
 if (f_err_code & FR_NO_FILE ) puts("FR_NO_FILE\r\n");
 else
 if (f_err_code & FR_NO_PATH ) puts("FR_NO_PATH\r\n");
 else
 if (f_err_code & FR_INVALID_NAME ) puts("FR_INVALID_NAME\r\n");
 else
 if (f_err_code & FR_INVALID_DRIVE  ) puts("FR_INVALID_DRIVE\r\n");
 else
 if (f_err_code & FR_EXIST   ) puts("FR_EXIST\r\n");
 else
 if (f_err_code & FR_DENIED  ) puts("FR_DENIED\r\n");
 else
 if (f_err_code &  FR_NOT_READY  ) puts("FR_NOT_READY\r\n");
 else
 if (f_err_code & FR_WRITE_PROTECTED   ) puts("FR_WRITE_PROTECTED\r\n");
 else
 if (f_err_code & FR_DISK_ERR   ) puts("FR_DISK_ERR\r\n");
 else
 if (f_err_code & FR_INT_ERR   ) puts("FR_INT_ERR\r\n");
 else
 if (f_err_code & FR_NOT_ENABLED  ) puts("FR_NOT_ENABLED\r\n");
 else
 if (f_err_code & FR_NO_FILESYSTEM  ) puts("FR_NO_FILESYSTEM\r\n");

 // если ошибок нет, начинаем воспроизведение
 if (f_err_code == 0) {
   //пытаемся читать 1 байт с начала файла в переменную var
  f_err_code = f_read(&fil_obj,var,44,&ByteRead);
  //надо настроить таймер 1 на частоту дискретизации
  // Timer/Counter 1 initialization
  // Clock source: System Clock
  // Clock value: 16000,000 kHz
  TCCR1A = (0<<COM1A1) | (0<<COM1A0) | (0<<COM1B1) | (0<<COM1B0) | (0<<COM1C1) | (0<<COM1C0) | (0<<WGM11) | (0<<WGM10);
  TCCR1B = (0<<ICNC1) | (0<<ICES1) | (0<<WGM13) | (0<<WGM12) | (0<<CS12) | (0<<CS11) | (1<<CS10);
  DDRB.7 = 1; // настройка 7 ножки порта B на выход
  // тут начинается обработка сэмпла
  while(_TRUE) {
   TCNT1 = 0; // обнумяем счетный регистр
   f_err_code = f_read(&fil_obj,var,1,&ByteRead);
   OCR2 = var[0]; // загружаем в шим
   if (ByteRead == 0) break; // если конец файла, выходим
   while (TCNT1 < 1000); // задержка, которая зависит от частоты дискретизации.
  }
 }
 DDRB.7=0; // отключаем звук. Иначе ШИМ будет греть динамик.
 f_err_code = f_close(&fil_obj); // закрываем файл
 // настраиваем таймер 1 для работы с датчиком расстояния
 // Timer/Counter 1 initialization
 // Clock source: System Clock
 // Clock value: 2000,000 kHz
 TCCR1A=(0<<COM1A1) | (0<<COM1A0) | (0<<COM1B1) | (0<<COM1B0) | (0<<COM1C1) | (0<<COM1C0) | (0<<WGM11) | (0<<WGM10);
 TCCR1B=(0<<ICNC1) | (0<<ICES1) | (0<<WGM13) | (0<<WGM12) | (0<<CS12) | (1<<CS11) | (0<<CS10);
 };


LCD Экран


Так как когда-то друг мне подарил LCD-экран LPH8731-3C, то именно его я и решил использовать. Вообще, это был мой первый опыт использования в своих проектах LCD-экрана. Вся информация по данному экрану и библиотека для работы с ним были найдены тут.

Питание


Так как сервопривод и микроконтроллер требовали питания 5 вольт, нужен был повышающий DC-DC, так как запитывать пушку я планировал от одного Li-Pol аккумулятора. DC-DC у меня уже были готовы (на микросхеме LM2621), когда-то зачем-то я их сделал в виде модулей, залитых в эпоксидную смолу:




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

Схема


Когда уже было ясно, что с чем паять, я решил в итоге собрать всю схему в виде многослойного торта:



Этот «тортик» умел проигрывать WAV, выводить данные на LCD, опрашивать ультразвуковой датчик расстояния, опрашивать напряжение на Li-Pol аккумуляторе, опрашивать кнопки, опрашивать потенциометр, управлять работой лазеров и управлять сервоприводом.

Корпус


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

«Кишки пушки»


image

В итоге


Все заработало: пушка стреляла, фокусировалась в точку при хорошей настройке параметров. Также я в нее добавил двоичный пароль при включении, настройки для всех параметров фокусировки и настройки режимов, которые сохраняются в ПЗУ микроконтроллера, а также график разряда батареи (на котором кстати было видно, как батарея проседает во время включения лазеров). Для лазеров я сделал также дополнительные два режима работы, когда работает один лазер, и когда работают два лазера с сохранением определенного расстояния между ними равное расстоянию между глазами противника на дистанции до цели.

Немного истории, связанной с пушкой


У меня был один заказчик, которому я в итоге отказал в разработке его заказа. Он еще долго пытался мне объяснить, что я потерял очень могое, отказавшись от его предложения работать за 20 тысяч в месяц над GPS-трекером для отмывания денег для автомобилей. Однажды он даже провел со мной разъяснительную беседу на тему, чего я хочу добиться в жизни, и чтобы я «переспал с его идеей». Собственно, когда я выложил новость о пушке у себя на странице, он написал под фото, что я занимаюсь всякой фигней и в принципе он прав. А позже, благодаря лазерной пушке меня нашел заказчик с квестами, и в итоге я стал фрилансером, пока не переехал в другой город и не нашел себе хорошую работу. В общем, спасибо пушке за рекламу.
Поделиться с друзьями
-->

Комментарии (24)


  1. Abiboss
    15.08.2016 10:08
    +18

    в итоге я стал фрилансером, пока не переехал в другой город и не нашел себе хорошую работу
    Наконец-то я увидел историю о фрилансе с хеппи-эндом в конце! xD


  1. Kasatich
    15.08.2016 10:23

    А мне сначала подумалось что тут будет такая вундервафля — слабый лазер на пушке для наведения и более мощный лазер на станине поворотной, которая бы выпускала заряд в ту же точку, что и младший брат) Размещаешь мощный лазер у себя на плече и вперед — ты уже инквизитор!)


    1. Jamato
      15.08.2016 10:40
      +1

      Сколиоз гарантирован.


      1. Kasatich
        15.08.2016 10:53

        Есесно экзоскелет потребуется!) Зато какой простор для разработки! Высший класс имхо наводить плечевую турель прямо взглядом, через какой-нибудь гуглоглас.


        1. dbanet
          15.08.2016 11:57

          Мне очень, очень нравится твоя идея.


        1. alex4321
          15.08.2016 12:00
          +1

          image


          1. Kasatich
            15.08.2016 12:08

            Экак( А у этого парня разве постоянно пушка трекает положение глаз? Я вообще отталкивался от снаряжение инквизитора из Вархаммера — у них там пси-пушка постоянно следует за взглядом носителя и потому дает 100% вероятность поражения любой хрени в зоне видимости XD


            1. alex4321
              15.08.2016 12:45

              Опа, что за пушечка?


              1. Valdei
                16.08.2016 16:17

                http://warhammer40k.wikia.com/wiki/Psycannon


                1. alex4321
                  16.08.2016 18:03

                  Да, некорректно задал вопрос. Я скорее о комплекте в целом, а не самой по себе психипушке.
                  Впрочем — http://warhammer40k.wikia.com/wiki/Mind_Impulse_Unit


                  1. Valdei
                    16.08.2016 18:56

                    Вот ведь хотел сначала ответить «обычная пушка, только спуск на нейроинтерфейс завязан», но подумал, что не все в теме вахи. :)
                    По идее реализовать что-то подобное и сейчас можно — сочетанием отслеживания зрачков и собственно использованием нейоинтерфейса для спуска.


        1. nafikovr
          15.08.2016 21:35
          +1

          главное при турели на левом плече не выстрелить посмотрев направо, иначе будут не самые приятные последствия.


          1. Alexeyslav
            16.08.2016 02:58

            У турели есть заданная зона безопасной работы, она просто не выстрелит.


      1. SnowLoKu
        15.08.2016 10:59

        Пришлось бы разрабатывать экоскелет для пушки, а там уже и до робота рукой подать…


      1. MaxKitsch
        17.08.2016 14:09

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


  1. ice2heart
    15.08.2016 10:48

    А как с защитой глаз?


  1. izzholtik
    15.08.2016 10:54
    +4

    Совет не смотреть в лазер оставшимся глазом уже неактуален?


  1. Fullmoon
    15.08.2016 11:36
    +2

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


  1. GennPen
    15.08.2016 11:39
    +1

    Ах, эти МП3-1, до сих пор работают. =)


  1. Electrohedgehog
    15.08.2016 11:51

    Серьёзная работа. Уровня, скорее, хабра.
    Сколько времени у вас заняло всё это? Если можно вычислить, то по частям — механика с корпусом, электроника, программирование.
    Если есть другие подобные игрушки — пишите, обязательно, ещё.


  1. Capacitor10n
    15.08.2016 12:00

    Прикольная штука, жаль что «без урона».
    А если объединить с этим: https://geektimes.ru/post/258416/
    можно не только игрушку получить но и полезный инструмент)
    «ПИУ», «Цель на расстоянии 11 метров поражена»


  1. engine9
    15.08.2016 12:15
    +1

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

    Если сделать лазеры послабее и безопаснее будет улётная детская (да и взрослая) игрушка.


  1. potan
    15.08.2016 15:43

    А ведь так можно и десяток-другой лазеров объединить…


    1. Sysman
      15.08.2016 16:09

      Такое уже делали