Arduino kamera alkalmazás

Kategória: Arduino.

OV7670

Reklámként dobta fel nekem a pár dolláros, OV7670 típusú Arduino kamerát, melynek nem tudtam ellenállni. Végül ez lett a legbonyolultabb Arduino eszköz, mellyel valaha találkoztam. A kód megértéséhez az Arduino chipjének legmélyebb bugyraiba kellett beleásnom magamat, és egy igen jelentős részét mind a mai napig nem értem.

A másik, amit érdemes rögtön az elején tisztázni: ezzel nem lehet minőségi fotókat készíteni. Élesben szinte biztosan nem tudjuk használni (pl. arra, hogy ha a mozgásérzékelő mozgást érzékel, akkor készítsen automatikusan pár fotót, amit mondjuk egy kártyára ment), és az összetettsége miatt oktatási célra is alkalmatlan. Igazából egy dologra jó csak: ha valaki szereti a bütykölős kihívásokat, annak megfelelő elfoglaltságot nyújt elég hosszú időre.

A következő leírás alapján készítettem el a projektet: https://create.arduino.cc/projecthub/techmirtz/visual-capturing-with-ov7670-on-arduino-069ebb

Bekötés

Maga az eszköz 18 lábat tartalmaz, és a bekötéshez a rengeteg kábelen kívül szükségünk van két 4,7kΩ és két 10 kΩ ellenállásra. Célszerű külön áramforrást biztosítani az eszköznek, és a külön áramforrás - pólusát összekötni az Arduino GND-vel. Bekötés:

  • OV7670 3v3 → 3,3 V
  • OV7670 GND → GND
  • OV7670 SIOC → Arduno A5
  • OV7670 SIOC → 10 kΩ → 3,3 V
  • OV7670 SIOD → Arduno A4
  • OV7670 VSYNC → Arduino D3
  • OV7670 HREF → Arduino D8
  • OV7670 PCLK → Arduino D2
  • OV7670 XCLK → 4,7 kΩ → Arduino D11
  • OV7670 XCLK → 4,7 kΩ (ez egy másik ellenállás) → GND
  • OV7670 D7 → Arduino D7
  • OV7670 D6 → Arduino D6
  • OV7670 D5 → Arduino D5
  • OV7670 D4 → Arduino D4
  • OV7670 D3 → Arduino D3
  • OV7670 D2 → Arduino D2
  • OV7670 D1 → Arduino D1
  • OV7670 D0 → Arduino D0
  • OV7670 RESET → 3,3 V
  • OV7670 PWDN → GND

Az alábbi ábra (mely mindkét fent hivatkozott oldalon megtalálható) a bekötést illusztrálja:

bekotes.jpg

Az említett oldalakon azt is megtaláljuk, hogy melyik láb micsoda.

Arduino kód

Az Arduino kódja lényegesen hosszabb, és nagyságrendekkel bonyolultabb, mint amihez hozzászoktunk, íme:

#include <stdint.h>
#include <util/twi.h>
#include <util/delay.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
 
#define F_CPU 16000000UL
#define COM7_RESET 0x80
#define COM8_AEC 0x01
#define COM8_AECSTEP 0x40
#define COM8_AGC 0x04
#define COM8_AWB 0x02
#define COM8_BFILT 0x20
#define COM8_FASTAEC 0x80
#define COM11_EXP 0x02
#define COM11_HZAUTO 0x10
#define COM13_UVSAT 0x40
#define COM15_R00FF 0xc0
#define COM16_AWBGAIN 0x08
#define REG_AEB 0x25
#define REG_AECH 0x10
#define REG_AEW 0x24
#define REG_BD50MAX 0xa5
#define REG_BD60MAX 0xab
#define REG_BLUE 0x01
#define REG_COM1 0x04
#define REG_COM2 0x09
#define REG_COM3 0x0c
#define REG_COM4 0x0d
#define REG_COM5 0x0e
#define REG_COM6 0x0f
#define REG_COM7 0x12
#define REG_COM8 0x13
#define REG_COM9 0x14
#define REG_COM10 0x15
#define REG_COM11 0x3b
#define REG_COM12 0x3c
#define REG_COM13 0x3d
#define REG_COM14 0x3e
#define REG_COM15 0x40
#define REG_COM16 0x41
#define REG_COM17 0x42
#define REG_EDGE 0x3f
#define REG_GAIN 0x00
#define REG_GFIX 0x69
#define REG_HAECC1 0x9f
#define REG_HAECC2 0xa0
#define REG_HAECC3 0xa6
#define REG_HAECC4 0xa7
#define REG_HAECC5 0xa8
#define REG_HAECC6 0xa9
#define REG_HAECC7 0xaa
#define REG_HREF 0x32
#define REG_HSTART 0x17
#define REG_HSTOP 0x18
#define REG_HSYEN 0x31
#define REG_HSYST 0x30
#define REG_MVFP 0x1e
#define REG_RED 0x02
#define REG_REG76 0x76
#define REG_RGB444 0x8c
#define REG_TSLB 0x3a
#define REG_VPT 0x26
#define REG_VREF 0x03
#define REG_VSTART 0x19
#define REG_VSTOP 0x1a
#define camAddr_RD 0x43
#define camAddr_WR 0x42
 
struct Regval {
  uint8_t reg_num;
  uint16_t value;
};
 
const struct Regval qvga_ov7670[] PROGMEM = {
  { REG_COM14, 0x19 },
  { 0x72, 0x11 },
  { 0x73, 0xf1 },
  { REG_HSTART, 0x16 },
  { REG_HSTOP, 0x04 },
  { REG_HREF, 0xa4 },
  { REG_VSTART, 0x02 },
  { REG_VSTOP, 0x7a },
  { REG_VREF, 0x0a },
  { 0xff, 0xff }
};
 
const struct Regval yuv422_ov7670[] PROGMEM = {
  { REG_COM7, 0x00 },
  { REG_RGB444, 0x00 },
  { REG_COM1, 0x00 },
  { REG_COM15, COM15_R00FF },
  { REG_COM9, 0x6A },
  { 0x4f, 0x80 },
  { 0x50, 0x80 },
  { 0x51, 0x00 },
  { 0x52, 0x22 },
  { 0x53, 0x5e },
  { 0x54, 0x80 },
  { REG_COM13, COM13_UVSAT },
  { 0xff, 0xff }
};
 
const struct Regval ov7670_default_regs[] PROGMEM = {
  { REG_COM7, COM7_RESET },
  { REG_TSLB, 0x04 },
  { REG_COM7, 0x00 },
  { REG_HSTART, 0x13 }, { REG_HSTOP, 0x01 },
  { REG_HREF, 0xb6 }, { REG_VSTART, 0x02 },
  { REG_VSTOP, 0x7a }, { REG_VREF, 0x0a },
  { REG_COM3, 0x00 }, { REG_COM14, 0x00 },
  { 0x70, 0x3a }, { 0x71, 0x35 },
  { 0x72, 0x11 }, { 0x73, 0xf0 },
  { 0xa2, 0x01 }, { REG_COM10, 0x0 },
  { 0x7a, 0x20 }, { 0x7b, 0x10 },
  { 0x7c, 0x1e }, { 0x7d, 0x35 },
  { 0x7e, 0x5a }, { 0x7f, 0x69 },
  { 0x80, 0x76 }, { 0x81, 0x80 },
  { 0x82, 0x88 }, { 0x83, 0x8f },
  { 0x84, 0x96 }, { 0x85, 0xa3 },
  { 0x86, 0xaf }, { 0x87, 0xc4 },
  { 0x88, 0xd7 }, { 0x89, 0xe8 },
  { REG_COM8, COM8_FASTAEC | COM8_AECSTEP },
  { REG_GAIN, 0x00 }, { REG_AECH, 0x00 },
  { REG_COM4, 0x40 }, { REG_COM9, 0x18 },
  { REG_BD50MAX, 0x05 }, { REG_BD60MAX, 0x07 },
  { REG_AEW, 0x95 }, { REG_AEB, 0x33 },
  { REG_VPT, 0xe3 }, { REG_HAECC1, 0x78 },
  { REG_HAECC2, 0x68 }, { 0xa1, 0x03 },
  { REG_HAECC3, 0xd8 }, { REG_HAECC4, 0xd8 },
  { REG_HAECC5, 0xf0 }, { REG_HAECC6, 0x90 },
  { REG_HAECC7, 0x94 },
  { REG_COM8, COM8_FASTAEC | COM8_AECSTEP | COM8_AGC | COM8_AEC },
  { 0x30, 0x00 }, { 0x31, 0x00 },
  { REG_COM5, 0x61 }, { REG_COM6, 0x4b },
  { 0x16, 0x02 }, { REG_MVFP, 0x07 },
  { 0x21, 0x02 }, { 0x22, 0x91 },
  { 0x29, 0x07 }, { 0x33, 0x0b },
  { 0x35, 0x0b }, { 0x37, 0x1d },
  { 0x38, 0x71 }, { 0x39, 0x2a },
  { REG_COM12, 0x78 }, { 0x4d, 0x40 },
  { 0x4e, 0x20 }, { REG_GFIX, 0x00 },
  { 0x74, 0x10 },
  { 0x8d, 0x4f }, { 0x8e, 0x00 },
  { 0x8f, 0x00 }, { 0x90, 0x00 },
  { 0x91, 0x00 }, { 0x96, 0x00 },
  { 0x9a, 0x00 }, { 0xb0, 0x84 },
  { 0xb1, 0x0c }, { 0xb2, 0x0e },
  { 0xb3, 0x82 }, { 0xb8, 0x0a },
  { 0x43, 0x0a }, { 0x44, 0xf0 },
  { 0x45, 0x34 }, { 0x46, 0x58 },
  { 0x47, 0x28 }, { 0x48, 0x3a },
  { 0x59, 0x88 }, { 0x5a, 0x88 },
  { 0x5b, 0x44 }, { 0x5c, 0x67 },
  { 0x5d, 0x49 }, { 0x5e, 0x0e },
  { 0x6c, 0x0a }, { 0x6d, 0x55 },
  { 0x6e, 0x11 }, { 0x6f, 0x9e },
  { 0x6a, 0x40 }, { REG_BLUE, 0x40 },
  { REG_RED, 0x60 },
  { REG_COM8, COM8_FASTAEC | COM8_AECSTEP | COM8_AGC | COM8_AEC | COM8_AWB },
  { 0x4f, 0x80 }, { 0x50, 0x80 },
  { 0x51, 0x00 },    { 0x52, 0x22 },
  { 0x53, 0x5e }, { 0x54, 0x80 },
  { 0x58, 0x9e },
  { REG_COM16, COM16_AWBGAIN }, { REG_EDGE, 0x00 },
  { 0x75, 0x05 }, { REG_REG76, 0xe1 },
  { 0x4c, 0x00 }, { 0x77, 0x01 },
  { REG_COM13, 0x48 }, { 0x4b, 0x09 },
  { 0xc9, 0x60 }, { 0x56, 0x40 },
  { 0x34, 0x11 }, { REG_COM11, COM11_EXP | COM11_HZAUTO },
  { 0xa4, 0x82 }, { 0x96, 0x00 },
  { 0x97, 0x30 }, { 0x98, 0x20 },
  { 0x99, 0x30 }, { 0x9a, 0x84 },
  { 0x9b, 0x29 }, { 0x9c, 0x03 },
  { 0x9d, 0x4c }, { 0x9e, 0x3f },
  { 0x78, 0x04 },
  { 0x79, 0x01 }, { 0xc8, 0xf0 },
  { 0x79, 0x0f }, { 0xc8, 0x00 },
  { 0x79, 0x10 }, { 0xc8, 0x7e },
  { 0x79, 0x0a }, { 0xc8, 0x80 },
  { 0x79, 0x0b }, { 0xc8, 0x01 },
  { 0x79, 0x0c }, { 0xc8, 0x0f },
  { 0x79, 0x0d }, { 0xc8, 0x20 },
  { 0x79, 0x09 }, { 0xc8, 0x80 },
  { 0x79, 0x02 }, { 0xc8, 0xc0 },
  { 0x79, 0x03 }, { 0xc8, 0x40 },
  { 0x79, 0x05 }, { 0xc8, 0x30 },
  { 0x79, 0x26 }, { 0xff, 0xff }
};
 
void error_led(){
  DDRB |= 32; // make sure led is output
  while (true) { //wait for reset
    PORTB ^= 32; // toggle led
    _delay_ms(100);
  }
}
 
void twiStart() {
  TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN); // send start
  while (!(TWCR & (1 << TWINT))); // wait for start to be transmitted
  if ((TWSR & 0xF8) != TW_START) {
    error_led();
  }
}
 
void twiWriteByte(uint8_t data, uint8_t type) {
  TWDR = data;
  TWCR = _BV(TWINT) | _BV(TWEN);
  while (!(TWCR & (1 << TWINT))) {}
  if ((TWSR & 0xF8) != type) {
    error_led();
  }
}
 
void twiAddr(uint8_t addr, uint8_t typeTWI){
  TWDR = addr; // send address
  TWCR = _BV(TWINT) | _BV(TWEN); // clear interrupt to start transmission
  while ((TWCR & _BV(TWINT)) == 0) {} // wait for transmission
  if ((TWSR & 0xF8) != typeTWI) {
    error_led();
  }
}
 
void wrReg(uint8_t reg, uint8_t dat){
  twiStart();
  twiAddr(camAddr_WR, TW_MT_SLA_ACK);
  twiWriteByte(reg, TW_MT_DATA_ACK);
  twiWriteByte(dat, TW_MT_DATA_ACK);
  TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
  _delay_ms(1);
}
 
static uint8_t twiRd(uint8_t nack){
  if (nack) {
    TWCR = _BV(TWINT) | _BV(TWEN);
    while ((TWCR & _BV(TWINT)) == 0);
    if ((TWSR & 0xF8) != TW_MR_DATA_NACK) {
      error_led();
    }
    return TWDR;
  } else {
    TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWEA);
    while ((TWCR & _BV(TWINT)) == 0);
    if ((TWSR & 0xF8) != TW_MR_DATA_ACK) {
      error_led();
    }
    return TWDR;
  }
}
 
uint8_t rdReg(uint8_t reg) {
  uint8_t dat;
  twiStart();
  twiAddr(camAddr_WR, TW_MT_SLA_ACK);
  twiWriteByte(reg, TW_MT_DATA_ACK);
  TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
  _delay_ms(1);
  twiStart();
  twiAddr(camAddr_RD, TW_MR_SLA_ACK);
  dat = twiRd(1);
  TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
  _delay_ms(1);
  return dat;
}
 
void wrSensorRegs8_8(const struct Regval reglist[]) {
  uint8_t reg_addr, reg_val;
  const struct Regval *next = reglist;
  while ((reg_addr != 0xff) | (reg_val != 0xff)) {
    reg_addr = pgm_read_byte(&next->reg_num);
    reg_val = pgm_read_byte(&next->value);
    wrReg(reg_addr, reg_val);
    next++;
  }
}
 
void setColor(){
  wrSensorRegs8_8(yuv422_ov7670);
}
 
void setRes(){
  wrReg(REG_COM3, 4); // REG_COM3 enable scaling
  wrSensorRegs8_8(qvga_ov7670);
}
 
void camInit(){
  wrReg(0x12, 0x80);
  _delay_ms(100);
  wrSensorRegs8_8(ov7670_default_regs);
  wrReg(REG_COM10, 32); //PCLK does not toggle on HBLANK.
}
 
void arduinoUnoInut() {
  cli(); //disable interrupts
 
  DDRB |= (1 << 3); // make pin 11 output
  ASSR &= ~(_BV(EXCLK) | _BV(AS2));
  TCCR2A = (1 << COM2A0) | (1 << WGM21) | (1 << WGM20);
  TCCR2B = (1 << WGM22) | (1 << CS20);
  OCR2A = 0;
  DDRC &= ~15; //low d0-d3 camera
  DDRD &= ~252; //d7-d4 and interrupt pins
  _delay_ms(3000);
 
  //set up twi for 100khz
  TWSR &= ~3; //disable prescaler for TWI
  TWBR = 72; //set to 100khz
 
  UBRR0H = 0; //enable serial
  UBRR0L = 1; //0 = 2M baud rate. 1 = 1M baud. 3 = 0.5M. 7 = 250k 207 is 9600 baud rate.
  UCSR0A |= 2; //double speed aysnc
  UCSR0B = (1 << RXEN0) | (1 << TXEN0); //Enable receiver and transmitter
  UCSR0C = 6; //async 1 stop bit 8bit char no parity bits
}
 
void setup() {
  arduinoUnoInut();
  camInit();
  setRes();
  setColor();
  wrReg(0x11, 10);
}
 
void loop() {
  char *str = PSTR("*RDY*");
  do {
    while (!(UCSR0A & (1 << UDRE0)));
    UDR0 = pgm_read_byte_near(str);
    while (!(UCSR0A & (1 << UDRE0)));
  } while (pgm_read_byte_near(++str));
 
  while (!(PIND & 8));
  while ((PIND & 8));
 
  int y = 240;
  while (y > 0) {
    y--;
    int x = 320;
    while (x > 0) {
      x--;
      while (PIND & 4);
      UDR0 = (PINC & 15) | (PIND & 240);
      while (!(UCSR0A & (1 << UDRE0)));
      while (!(PIND & 4));
      while (PIND & 4);
      while (!(PIND & 4));
    }
  }
  _delay_ms(100);
}

A kódban nagyon sok hardver szintű kódelem található: a megszokott Arduino függvényhívások helyett közvetlenül állítja a regisztereket. Ezt a kódot én magam sem értem teljes egészében: nem tudom, a program elején található hexadecimális kódok mit jelentenek. A kód többi részét komoly nehézségek árán valamelyest sikerült megértenem, melyről a Hardverközeli Arduino programozás oldalon olvashatunk részletesen.

Az eredeti forrás ennek több mint kétszerese; ennyire sikerült leredukálnom a hosszát. Bizonyos részeket megpróbáltam átírni a szokásos Arduino függvényhívásokra, viszont azt tapasztaltam, hogy az eredmény fekete foltokat tartalmazott. Ebből arra következtetek, hogy mindenképpen a legalacsonyabb szintű hozzáféréssel tudjuk csak programozni, mert különben túl lassú lesz.

Számítógépen futó kód

A tesztelés során az Arduino-nak össze kell lennie kötve a számítógéppel, és a számítógépen egy Java kódot kell futtatnunk. Persze ez sem olyan egyszerű…

  • Ellenőrizzük, hogy van-e a számítógépünkön Java fordító (javac -version) és Java futtató (java -version). Ha nincs, töltsük le és telepítsük fel a JDK-t. Fontos, hogy 32 bites legyen, és a leírásban 1.8-as szerepel, így a letöltő oldal a következő: https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html. Letöltés és feltelepítés után a Java bin könyvtárát adjuk hozzá a PATH környezeti változóhoz: Edit the system environment variables -> Environment Variables … -> alul Path -> Edit … -> adjuk hozzá a megfelelő könyvtárat, pl. c:\Program Files (x86)\Java\jdk1.8.0_211\bin.
  • Töltsük le ezt a fájlt: [[extra.rar]].
  • A benne található win32com.dll fájlt másoljuk a JDK bin könyvtárába, pl. c:\Program Files (x86)\Java\jdk1.8.0_211\bin\.
  • A lib\comm.jar fájt másoljuk a JRE lib\ext könyvtárába, pl. c:\Program Files (x86)\Java\jre1.8.0_211\lib\ext\.
  • A lib\javax.comm.properties fájlt másoljuk a JRE lib könyvtárába, pl. c:\Program Files (x86)\Java\jre1.8.0_211\lib\.

A tömörített fájlt tartalmaz Java kódot is, ezt viszont a következőre egyszerűsítettem. A fájl neve legyen SimpleRead.java.

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
 
import javax.comm.CommPortIdentifier;
import javax.comm.SerialPort;
 
public class SimpleRead {
    private static final int WIDTH = 320;
    private static final int HEIGHT = 240;
 
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("Usage: SimpleRead port outputdir");
            System.out.println("E.g.: SimpleRead COM3 c:/out/");
            return;
        }
 
        Enumeration portList = CommPortIdentifier.getPortIdentifiers();
        CommPortIdentifier portId = null;
 
        while (portList.hasMoreElements()) {
            portId = (CommPortIdentifier) portList.nextElement();
            if (portId.getName().equals(args[0])) {
                break;
            }
        }
        if (portId != null) {
            int[][] rgb = new int[HEIGHT][WIDTH];
            int[][] rgbTranspose = new int[WIDTH][HEIGHT];
 
            try {
                SerialPort serialPort = (SerialPort) portId.open("SimpleReadApp", 1000);
                InputStream inputStream = serialPort.getInputStream();
                serialPort.setSerialPortParams(1000000, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
                int counter = 0;
                while (true) {
                    System.out.println("Looking for image");
                    boolean isImageStart = false;
                    while (!isImageStart) {
                        isImageStart = true;
                        char[] command = {'*', 'R', 'D', 'Y', '*'};
                        for (int index = 0; index < command.length; index++) {
                            if (!(command[index] == (char)inputStream.read())) {
                                isImageStart = false;
                                break;
                            }
                        }
                    }
                    System.out.println("Found image: " + counter);
                    for (int y = 0; y < HEIGHT; y++) {
                        for (int x = 0; x < WIDTH; x++) {
                            int result = (char)inputStream.read();
                            rgb[y][x] = ((result & 0xFF) << 16) | ((result & 0xFF) << 8) | (result & 0xFF);
                        }
                    }
                    for (int y = 0; y < HEIGHT; y++) {
                        for (int x = 0; x < WIDTH; x++) {
                            rgbTranspose[x][y] = rgb[y][x];
                        }
                    }
                    saveBMP(args[1] + (counter++) + ".bmp", rgbTranspose);
                    System.out.println("Saved image: " + counter);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("Port " + args[0] + " not found.");
        }            
    }
 
    public static void saveBMP(String filename, int[][] rgbValues) throws IOException {
        byte offset = 54;
 
        FileOutputStream fos = new FileOutputStream(new File(filename));
        byte[] bytes = new byte[54 + 3 * rgbValues.length * rgbValues[0].length];
 
        bytes[0] = 'B';
        bytes[1] = 'M';
 
        bytes[5] = (byte)bytes.length;
        bytes[4] = (byte)(bytes.length >> 8);
        bytes[3] = (byte)(bytes.length >> 16);
        bytes[2] = (byte)(bytes.length >> 24);
 
        bytes[10] = offset;            
        bytes[14] = 40;
 
        int width = rgbValues[0].length;
        bytes[18] = (byte)width;
        bytes[19] = (byte)(width >> 8);
        bytes[20] = (byte)(width >> 16);
        bytes[21] = (byte)(width >> 24);
 
        int height = rgbValues.length;
        bytes[22] = (byte)height;
        bytes[23] = (byte)(height >> 8);
        bytes[24] = (byte)(height >> 16);
        bytes[25] = (byte)(height >> 24);
 
        bytes[26] = 1;
        bytes[28] = 24;
 
        for (int row = 0; row < height; row++) {
            for (int column = 0; column < width; column++) {
                int rgb = rgbValues[row][column];
                int index = offset + 3 * (column + width * row);
                bytes[index + 2] = (byte)(rgb >> 16);
                bytes[index + 1] = (byte)(rgb >> 8);
                bytes[index] = (byte)rgb;
            }
        }
 
        fos.write(bytes);
        fos.close();
    }
}

Ez - azon túl, hogy egy fájlból áll - paraméterezhető: megadható neki egyrészt, hogy melyik portról olvassa az adatokat (a fenti példákban 5 lehetséges portra van "bedrótozva"), másrészt a kimeneti könyvtár is beállítható. Fordítás:

javac SimpleRead.java

Teszt

Hozzuk létre a megfelelő könyvtárat. Majd indítsuk el az olvasó programot a következőképpen:

java SimpleRead COM3 c:/out/

A portot állítsuk be a megfelelőre, amit az Arduino IDE-ből is ki tudunk olvasni, és a kimeneti könyvtárat is állítsuk be megfelelően. Az Arduino legyen rákapcsolva a számítógép soros portjára.

Ha mindent jól csináltunk, akkor a kimeneti könyvtárban megjelennek az elkészült képek. Ezek várhatóan először igen elmosódottak lesznek. Próbáljuk meg az objektív elcsavarásával és/vagy a távolság megváltoztatásával módosítani a fókuszt. A megvilágítás legyen megfelelő. Valamint türelmesnek kell lennünk: idővel adott beállításokkal is javulhat az eredmény. Túl nagy csodára sajnos nem számíthatunk; az alábbi képet elég sok próbálkozást követő tudtam elkészíteni:

fenykep.bmp
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License