메뉴 전체보기

회원메뉴

PolyShot 포인트 앤 슛 카메라 > 게시판

본문 바로가기

쇼핑몰 검색

NO. 3000
제목 : PolyShot 포인트 앤 슛 카메라
2026-08-14 11:26

소개: PolyShot 포인트 앤 슛 카메라


소개: PolyShot 포인트 앤 슛 카메라


소개: PolyShot 포인트 앤 슛 카메라


소개: PolyShot 포인트 앤 슛 카메라


소개: PolyShot 포인트 앤 슛 카메라


소개: PolyShot 포인트 앤 슛 카메라


소개: PolyShot 포인트 앤 슛 카메라


여러분, 안녕하세요. 다시 찾아주셔서 반갑습니다!

재미있는 것을 소개할게요. 제가 처음부터 직접 만든 DIY 포인트 앤 슛 카메라 PolyShot을 만나 보세요.

최근 들어 컴팩트 카메라가 다시 유행하고 있습니다. 제 소셜 미디어 피드에는 오래된 Sony Cyber-shot, Canon, Nikon 카메라를 자랑하는 사람들이 가득했습니다. 다른 사람들처럼 하나를 사는 대신, 저는 직접 만들기로 했습니다.

이 프로젝트의 핵심은 ESP32-S3로 구동되며 2메가픽셀 카메라가 내장된 UNIHIKER K10 개발 보드입니다. 카메라용 맞춤형 케이스와 UNIHIKER 보드의 엣지 커넥터를 통해 연결되는 맞춤형 버튼 PCB를 설계했습니다.

UNIHIKER에는 microSD 카드 슬롯도 있어 촬영한 모든 사진이 BMP 이미지로 저장됩니다. 이 이미지는 카메라에서 바로 보거나 SD 카드를 간단히 분리해 컴퓨터로 옮길 수 있습니다.

카메라는 기본적인 컴팩트 카메라와 동일하게 작동합니다. 피사체를 향하고 셔터 버튼을 누른 뒤 몇 초 기다리면 이미지가 촬영되어 SD 카드에 저장됩니다.

이 글에서는 전체 제작 과정을 안내해 드리겠습니다. 그럼 시작해 보겠습니다!




STEP 1. 준비물


이 프로젝트에 사용한 부품입니다.

  1. 커스텀 스위치 PCB(HQ NEXTPCB 제공)
  2. Unihiker K10 개발 보드
  3. SD 카드
  4. 3D 프린팅 부품
  5. M2.5 나사
  6. M2 셀프 태핑 나사
  7. Li-ion Cell 3.7V 300mAh 1450 Package
  8. 푸시 스위치
  9. JST 배터리 커넥터




STEP 2. 컴팩트 카메라의 귀환


컴팩트 카메라의 귀환


컴팩트 카메라의 귀환


컴팩트 카메라의 귀환


컴팩트 카메라의 귀환


최근 Instagram, TikTok 또는 YouTube를 조금이라도 사용했다면, 컴팩트 디지털 카메라가 크게 다시 유행하고 있다는 사실을 눈치챘을 것입니다. Sony, Canon, Nikon, Fujifilm, Panasonic 같은 브랜드의 빈티지 컴팩트 카메라, 특히 2000년대 초반 모델이 다시 엄청난 인기를 얻고 있습니다.

이러한 인기의 일부는 독특한 화질에서 비롯됩니다. 컴퓨터 사진 기술에 크게 의존하는 최신 스마트폰과 달리, 이러한 구형 카메라는 조금 더 부드러운 이미지, 자연스러운 색감, 그리고 많은 사람들이 이제 선호하는 향수를 불러일으키는 디지털 감성 등 뚜렷한 느낌의 사진을 만들어 냅니다.

새로운 인기로 인해 가격도 상승했습니다. 한때 서랍이나 중고 매장에 방치되어 있던 카메라가 이제는 온라인에서 놀라울 정도로 높은 가격에 판매되고 있습니다.

이것이 제가 처음부터 직접 카메라를 만들고 싶었던 이유 중 하나입니다.

처음에는 작동하지 않는 카메라를 저렴하게 구해 본체 안에 제 전자 부품을 개조해 넣으려고 했습니다. 하지만 높은 수요로 인해 가격이 오르고 구하기도 어려워지면서 이 계획은 실패했습니다.

결국 케이스까지 직접 설계하고 제작해야 했습니다.




STEP 3. 하드웨어 - UNIHIKER K10


하드웨어 - UNIHIKER K10


하드웨어 - UNIHIKER K10


이 프로젝트의 주인공은 카메라 프로젝트의 두뇌 역할을 하는 UNIHIKER K10입니다.

UNIHIKER 플랫폼은 M10과 K10 두 가지 버전으로 제공됩니다.

M10은 Linux를 지원하는 프로세서를 기반으로 하며, 더 높은 수준의 애플리케이션을 위해 설계되었습니다.

반면 K10은 ESP32-S3 N16R8 모듈로 구동되며, 이번 프로젝트에서 사용하는 모델입니다.

주요 사양:

  1. 모듈: ESP32-S3 N16R8
  2. 프로세서: Xtensa® LX7 듀얼 코어 32비트 CPU
  3. 클록 속도: 최대 240 MHz
  4. SRAM: 512 KB
  5. ROM: 384 KB
  6. 플래시 메모리: 16 MB
  7. PSRAM: 8 MB
  8. RTC SRAM: 16 KB

듀얼 코어 아키텍처와 넉넉한 Flash 및 PSRAM을 갖춘 ESP32-S3는 이미지 렌더링, SD 카드 파일 액세스, 오디오 재생을 동시에 처리할 수 있습니다.

이 프로젝트에서 UNIHIKER K10을 선택한 주된 이유는 내장 카메라, 준수한 240x320 디스플레이, 그리고 SD 카드 리더가 있기 때문입니다. 이 두 가지 기능 덕분에 전체 설계가 크게 단순해졌고 추가 외부 모듈도 필요하지 않게 되었습니다.

모든 이미지는 SD 카드에 직접 촬영되고 저장됩니다.

UNIHIKER K10에 대한 자세한 내용은 위키 페이지에서 확인해 보세요.

https://www.unihiker.com/wiki/K10/




STEP 4. 스위치 보드 PCB 설계


스위치 보드 PCB 설계


스위치 보드 PCB 설계


스위치 보드 PCB 설계


이 프로젝트의 PCB 설계를 위해서는 세 개의 버튼이 있는 간단한 보드가 필요했습니다. 하나는 이미지를 촬영하는 셔터 버튼이고, 두 번째는 카메라 갤러리를 열어 촬영한 사진을 보는 버튼이며, 세 번째는 갤러리에서 다음 이미지로 이동하는 버튼입니다.

맞춤형 PCB를 설계하고 회로도를 작성했으며, 세 버튼은 모두 공통 GND 연결을 공유합니다. 각 버튼의 두 번째 단자는 3핀 커넥터(CON3)로 연결되어 UNIHIKER K10과 인터페이스합니다.

보드에는 WS2812B RGB LED도 세 개 추가했습니다. 이번 프로젝트에서는 이 보드 기능을 사용하지 않았지만, 같은 보드에 RGB LED를 추가해 사용할 예정인 향후 프로젝트를 위한 것입니다.




STEP 5. NextPCB PCB 서비스


NextPCB PCB 서비스


NextPCB PCB 서비스


PCB 설계를 완료한 후 PCB의 Gerber 데이터를 HQ NextPCB에 보내고 흰색 실크스크린이 적용된 Green 솔더 마스크 보드를 주문했습니다.

주문 후 일주일 만에 PCB를 받았으며, PCB 품질은 상당히 훌륭했습니다.

또한 여러 프로젝트를 진행하며 큰 도움을 받은 HQDFM도 소개해 드리고 싶습니다. Huaqiu의 사내 엔지니어들이 무료 제조용 설계 소프트웨어인 HQDFM을 개발했으며, 이 소프트웨어는 PCB 설계자가 자신의 설계를 시각화하고 검증하는 방식을 혁신했습니다.

NextPCB의 Accelerator 캠페인을 활용하여 혁신적인 프로젝트를 위한 RP2040 기반 조립 PCB 2개를 무료로 받아보세요.

https://www.nextpcb.com/blog/rp2040-free-pcba-prototypes-nextpcb-accelerator

이 혜택은 물류를 포함한 모든 비용을 지원하므로 아이디어를 더 쉽고 저렴하게 현실로 구현할 수 있습니다. SMT 서비스는 비용이 많이 들 수 있지만, NextPCB가 이러한 부담을 극복할 수 있도록 도와드립니다. 관련 프로젝트를 공유하기만 하면 나머지는 NextPCB가 처리합니다. 기술 창작물을 한 단계 발전시킬 수 있는 이 놀라운 기회를 놓치지 마세요!




STEP 6. HQDFM: 무료 온라인 Gerber Viewer 및 DFM 분석 도구


HQDFM: 무료 온라인 Gerber Viewer 및 DFM 분석 도구


HQDFM: 무료 온라인 Gerber Viewer 및 DFM 분석 도구


또한 NextPCB는 자체 Gerber Viewer 및 DFM 분석 소프트웨어를 제공합니다.

HQDFM 소프트웨어의 DFM 서비스로 설계를 개선할 수 있습니다. 제조업체의 DFM 보고서를 기다리는 것이 번거롭다고 느끼는 저에게는, HQDFM은 제조 전에 자체 점검을 수행하는 가장 효율적인 방법입니다.

다음은 온라인 Gerber Viewer에서 확인한 화면입니다. 빠르게 살펴보기에는 괜찮지만 완전히 명확하지는 않습니다. PCBA의 상세한 DFM 분석과 같은 모든 기능을 사용하려면 데스크톱 소프트웨어를 다운로드해야 합니다. 웹 버전에서는 기본적인 DFM 보고서만 제공합니다.

포괄적인 제조를 위한 설계(DFM) 분석 기능을 갖춘 HQDFM은 무료이면서 정교한 온라인 PCB Gerber 파일 뷰어입니다.

15년 이상의 업계 경험을 바탕으로 고급 제조 공정에 대한 유용한 통찰을 제공합니다. 합리적인 가격의 신뢰할 수 있는 PCB 서비스를 찾고 있다면 HQ NextPCB를 꼭 확인해 볼 만합니다.




STEP 7. PCB 조립 공정


PCB 조립 공정


PCB 조립 공정


PCB 조립 공정


PCB 조립 공정


스위치 보드의 조립 과정도 매우 간단하고 명확했습니다.

  1. 저는 4×4 mm 택타일 푸시 버튼 3개를 사용하여 PCB의 지정된 위치에 각각 배치했습니다.
  2. 그런 다음 보드를 뒤집고 납땜 인두를 사용해 패드를 납땜하여 고정했습니다.

이로써 스위치 보드 조립이 완료되었습니다.




STEP 8. 카메라 설계


카메라 설계


카메라 설계


카메라 설계


카메라 설계


카메라 설계


카메라 설계


카메라 설계


카메라 설계


이 프로젝트를 설계할 때는 미관보다는 실용성에 중점을 두고 최대한 단순하게 유지하고 싶었습니다. 먼저 UNIHIKER 보드, 배터리, 스위치 PCB를 가장 적합한 위치에 배치한 다음 이를 중심으로 인클로저를 설계했습니다.

배터리는 카메라 왼쪽에서 편안한 그립을 형성하도록 배치되어 있어 잡기 쉽습니다. 그립 바로 위에는 스위치 PCB가 있어 왼손 엄지손가락으로 셔터 버튼에 편하게 닿을 수 있습니다. 또한 microSD 카드를 쉽게 삽입하거나 제거할 수 있도록 카메라를 분해하지 않아도 되는 개구부를 마련했습니다.

인클로저는 앞면 인클로저와 뒷면 인클로저, 두 가지 주요 부품으로 나뉩니다.

뒷면 인클로저에는 14500 Li-ion 배터리와 메인 ON/OFF 스위치가 들어가며, 렌즈 홀더를 위한 큰 원형 개구부가 있습니다. 렌즈 홀더는 UNIHIKER 카메라 모듈 주위에 장착되는 별도 부품입니다. M2.5 볼트가 UNIHIKER 보드와 렌즈 홀더를 모두 관통하여 전체를 단단히 고정합니다. 그런 다음 별도의 렌즈 커버를 렌즈 홀더에 압입하여 카메라 전면을 완성합니다.

앞면 인클로저에는 UNIHIKER 보드, 스위치 PCB, 3개의 스위치 액추에이터가 들어갑니다. 스위치 PCB는 액추에이터 바로 뒤에 장착되고, UNIHIKER 보드는 인클로저 내부에 고정됩니다. USB Type-C 포트와 microSD 카드 슬롯을 위한 개구부도 마련되어 있어 조립 후에도 두 부분에 접근할 수 있습니다.




STEP 9. 3D 프린팅 부품


3D 프린팅 부품


3D 프린팅 부품


3D 프린팅에서는 인클로저 양쪽과 렌즈 홀더를 포함한 대부분의 부품을 빨간색 Hyper PLA로 출력했습니다. 서포트 자국을 줄이고 더 매끄러운 표면 마감을 얻기 위해 인클로저를 수직 방향으로 출력했습니다.

렌즈 커버, 스위치 노브, 스위치 홀더는 모두 삼색 PLA로 출력했으며, 부품에 보라색, 노란색, 갈색을 띠는 외관을 만들어 줍니다. 이 색 조합은 빨간색 인클로저와 매우 잘 어울립니다.




STEP 10. 전원


전원


전원


전원


전원


전원


이 프로젝트의 전원으로는 LiPo 셀이 가장 이상적인 선택이었을 것입니다. 하지만 소형 14500 규격 Li-ion 셀을 가지고 있었기 때문에 이번 제작에 사용하기로 했습니다.

여기서는 3.7 V, 300 mAh Li-ion 셀을 사용합니다. 연결을 위해 배터리 JST 와이어 하니스를 양극 및 음극 단자에 스폿 용접했습니다.

이 하니스에는 전선에 니켈 스트립이 이미 부착되어 있어 셀 단자에 직접 스폿 용접하기 쉽습니다.

양극 전선을 중간에서 자르고 그 사이에 작은 ON/OFF 푸시 스위치를 추가했습니다. UNIHIKER 보드 자체에는 전용 전원 스위치가 없기 때문에 이 스위치가 배터리의 메인 전원 스위치 역할을 합니다. 이 스위치가 없으면 배터리가 연결되어 있는 동안 보드에 계속 전원이 공급됩니다.




STEP 11. UNIHIKER 및 스위치 보드 조립


UNIHIKER 및 스위치 보드 조립


이제 UNIHIKER 엣지 커넥터를 스위치 보드에 연결할 수 있습니다. 3개의 스위치는 모두 공통 GND 단자를 공유하며, 이 단자는 UNIHIKER 엣지 커넥터의 GND 핀에 연결됩니다.

다음으로 3개의 스위치 출력단을 UNIHIKER의 GPIO 핀에 연결합니다. 스위치 1은 P0(라벨 0), 스위치 2는 P1(라벨 1), 스위치 3은 P2(라벨 2)에 연결됩니다.

네 연결 모두 단심 은도금 구리선을 사용했으며, 덕분에 배선이 깔끔하고 인클로저 내부에서 정리하기 쉬웠습니다.




STEP 12. 코드


이것은 카메라의 메인 코드이며, 간단한 코드입니다.

#include "unihiker_k10.h"
UNIHIKER_K10 k10;
uint8_t screen_dir = 2; // Keep DMA landscape logic
int imageCounter = 1;
// --- STATE MACHINE & FLAGS ---
bool inGalleryMode = false;
int galleryIndex = 1;
// High-speed debouncing state trackers
bool lastStateP0 = HIGH;
bool lastStateP1 = HIGH;
bool lastStateP2 = HIGH;
unsigned long lastDebounceTime = 0;
const int DEBOUNCE_DELAY = 200; // 200ms lock-out after a press
// --- 1. FULL 5x7 ASCII FONT ---
const uint8_t font5x7[59][5] = {
{0x00, 0x00, 0x00, 0x00, 0x00}, // 32: Space
{0x00, 0x00, 0x2f, 0x00, 0x00}, // 33: !
{0x00, 0x07, 0x00, 0x07, 0x00}, // 34: "
{0x14, 0x7f, 0x14, 0x7f, 0x14}, // 35: #
{0x24, 0x2a, 0x7f, 0x2a, 0x12}, // 36: $
{0x23, 0x13, 0x08, 0x64, 0x62}, // 37: %
{0x36, 0x49, 0x55, 0x22, 0x50}, // 38: &
{0x00, 0x05, 0x03, 0x00, 0x00}, // 39: '
{0x00, 0x1c, 0x22, 0x41, 0x00}, // 40: (
{0x00, 0x41, 0x22, 0x1c, 0x00}, // 41: )
{0x14, 0x08, 0x3E, 0x08, 0x14}, // 42: *
{0x08, 0x08, 0x3E, 0x08, 0x08}, // 43: +
{0x00, 0x00, 0x50, 0x30, 0x00}, // 44: ,
{0x08, 0x08, 0x08, 0x08, 0x08}, // 45: -
{0x00, 0x60, 0x60, 0x00, 0x00}, // 46: .
{0x20, 0x10, 0x08, 0x04, 0x02}, // 47: /
{0x3E, 0x51, 0x49, 0x45, 0x3E}, // 48: 0
{0x00, 0x42, 0x7F, 0x40, 0x00}, // 49: 1
{0x42, 0x61, 0x51, 0x49, 0x46}, // 50: 2
{0x21, 0x41, 0x45, 0x4B, 0x31}, // 51: 3
{0x18, 0x14, 0x12, 0x7F, 0x10}, // 52: 4
{0x27, 0x45, 0x45, 0x45, 0x39}, // 53: 5
{0x3C, 0x4A, 0x49, 0x49, 0x30}, // 54: 6
{0x01, 0x71, 0x09, 0x05, 0x03}, // 55: 7
{0x36, 0x49, 0x49, 0x49, 0x36}, // 56: 8
{0x06, 0x49, 0x49, 0x29, 0x1E}, // 57: 9
{0x00, 0x36, 0x36, 0x00, 0x00}, // 58: :
{0x00, 0x56, 0x36, 0x00, 0x00}, // 59: ;
{0x08, 0x14, 0x22, 0x41, 0x00}, // 60: <
{0x14, 0x14, 0x14, 0x14, 0x14}, // 61: =
{0x00, 0x41, 0x22, 0x14, 0x08}, // 62: >
{0x02, 0x01, 0x51, 0x09, 0x06}, // 63: ?
{0x32, 0x49, 0x59, 0x51, 0x3E}, // 64: @
{0x7E, 0x11, 0x11, 0x11, 0x7E}, // 65: A
{0x7F, 0x49, 0x49, 0x49, 0x36}, // 66: B
{0x3E, 0x41, 0x41, 0x41, 0x22}, // 67: C
{0x7F, 0x41, 0x41, 0x22, 0x1C}, // 68: D
{0x7F, 0x49, 0x49, 0x49, 0x41}, // 69: E
{0x7F, 0x09, 0x09, 0x09, 0x01}, // 70: F
{0x3E, 0x41, 0x49, 0x49, 0x7A}, // 71: G
{0x7F, 0x08, 0x08, 0x08, 0x7F}, // 72: H
{0x00, 0x41, 0x7F, 0x41, 0x00}, // 73: I
{0x20, 0x40, 0x41, 0x3F, 0x01}, // 74: J
{0x7F, 0x08, 0x14, 0x22, 0x41}, // 75: K
{0x7F, 0x40, 0x40, 0x40, 0x40}, // 76: L
{0x7F, 0x02, 0x0C, 0x02, 0x7F}, // 77: M
{0x7F, 0x04, 0x08, 0x10, 0x7F}, // 78: N
{0x3E, 0x41, 0x41, 0x41, 0x3E}, // 79: O
{0x7F, 0x09, 0x09, 0x09, 0x06}, // 80: P
{0x3E, 0x41, 0x51, 0x21, 0x5E}, // 81: Q
{0x7F, 0x09, 0x19, 0x29, 0x46}, // 82: R
{0x46, 0x49, 0x49, 0x49, 0x31}, // 83: S
{0x01, 0x01, 0x7F, 0x01, 0x01}, // 84: T
{0x3F, 0x40, 0x40, 0x40, 0x3F}, // 85: U
{0x1F, 0x20, 0x40, 0x20, 0x1F}, // 86: V
{0x3F, 0x40, 0x38, 0x40, 0x3F}, // 87: W
{0x63, 0x14, 0x08, 0x14, 0x63}, // 88: X
{0x07, 0x08, 0x70, 0x08, 0x07}, // 89: Y
{0x61, 0x51, 0x49, 0x45, 0x43} // 90: Z
};
// --- 2. 견고한 그리기 엔진 ---
void drawSolidBox(int startX, int startY, int w, int h, uint32_t color) {
for(int i = 0; i < h; i++) {
k10.canvas->canvasLine(startX, startY + i, startX + w, startY + i, color);
}
}
void drawRotatedText(String text, int startX, int startY, uint32_t color, uint32_t bgColor, int scale) {
int cursorX = startX;
int cursorY = startY;
for (int i = 0; i < text.length(); i++) {
char c = toupper(text[i]);
int charIndex = c - 32;
if (charIndex < 0 || charIndex > 58) charIndex = 0;
if (bgColor != 0x000000) {
drawSolidBox(cursorX - scale, cursorY - (5 * scale), 9 * scale, 7 * scale, bgColor);
}
for (int col = 0; col < 5; col++) {
uint8_t line = font5x7[charIndex][col];
for (int row = 0; row < 7; row++) {
if (line & (1 << row)) {
int drawX = cursorX + (row * scale);
int drawY = cursorY - (col * scale);
drawSolidBox(drawX, drawY, scale, scale, color);
}
}
}
cursorY -= (6 * scale);
}
}
// --- 3. LOPAKA 스타일 부트 스플래시 화면 ---
void drawSplashScreen() {
k10.canvas->canvasClear();
drawSolidBox(0, 0, 240, 320, 0x010101);
uint32_t accentColor = 0xB4815A;
k10.canvas->canvasCircle(63, 160, 26, accentColor, 0x010101, false);
k10.canvas->canvasCircle(63, 160, 18, accentColor, 0x010101, false);
drawRotatedText("POLYSHOT", 108, 256, 0xFF0000, 0x010101, 4);
drawRotatedText("INITIALIZING", 158, 196, 0xFFFFFF, 0x010101, 1);
k10.canvas->canvasLine(183, 60, 183, 260, 0xFFFFFF);
k10.canvas->canvasLine(202, 60, 202, 260, 0xFFFFFF);
k10.canvas->canvasLine(183, 260, 202, 260, 0xFFFFFF);
k10.canvas->canvasLine(183, 60, 202, 60, 0xFFFFFF);
k10.canvas->updateCanvas();
for(int fillY = 258; fillY > 60; fillY -= 4) {
drawSolidBox(185, fillY, 16, 4, accentColor);
k10.canvas->updateCanvas();
delay(35);
}
delay(400);
}
// --- 4. PRO 카메라 UI HUD ---
void drawCameraHUD() {
k10.canvas->canvasClear();
// 시네마틱 블랙 바
drawSolidBox(0, 0, 25, 320, 0x010101); // 상단 바
drawSolidBox(215, 0, 25, 320, 0x010101); // 하단 바
// 상단 바 세부 정보
drawRotatedText("F2.8", 8, 305, 0xFFFFFF, 0x010101, 1);
drawRotatedText("ISO AUTO", 8, 250, 0xFFFFFF, 0x010101, 1);
drawRotatedText("EV 0.0", 8, 160, 0xFFFFFF, 0x010101, 1);
drawRotatedText("AWB", 8, 80, 0x00FFFF, 0x010101, 1);
// 하단 바 세부 정보
String counterText = String(imageCounter);
if(imageCounter < 10) counterText = "00" + counterText;
else if(imageCounter < 100) counterText = "0" + counterText;
drawRotatedText("SD " + counterText, 222, 305, 0xFFFF00, 0x010101, 1);
drawRotatedText("1080P", 222, 160, 0xFFFFFF, 0x010101, 1);
drawRotatedText("BATT 95%", 222, 80, 0x00FF00, 0x010101, 1);
// 중앙 초점 조준선
k10.canvas->canvasLine(90, 180, 105, 180, 0xFFFFFF);
k10.canvas->canvasLine(90, 180, 90, 165, 0xFFFFFF);
k10.canvas->canvasLine(150, 180, 135, 180, 0xFFFFFF);
k10.canvas->canvasLine(150, 180, 150, 165, 0xFFFFFF);
k10.canvas->canvasLine(90, 140, 105, 140, 0xFFFFFF);
k10.canvas->canvasLine(90, 140, 90, 155, 0xFFFFFF);
k10.canvas->canvasLine(150, 140, 135, 140, 0xFFFFFF);
k10.canvas->canvasLine(150, 140, 150, 155, 0xFFFFFF);
// 중앙의 작은 점
drawSolidBox(118, 158, 4, 4, 0xFFFFFF);
k10.canvas->updateCanvas();
}
// --- 5. 갤러리 UI 엔진 ---
void drawGalleryUI() {
k10.canvas->canvasClear();
// 이미지가 화면을 어차피 모두 덮으므로 여기서는 거대한 단색 박스 채우기를 제거했습니다!
// SD 카드 이미지 렌더링
String fileName = "S:/IMG_" + String(galleryIndex) + ".bmp";
k10.canvas->canvasDrawImage(0, 0, fileName);
// 이미지 위에 UI 요소를 오버레이합니다(전체 막대를 그리는 것보다 빠름)
drawRotatedText("GALLERY MODE", 10, 260, 0xFFFF00, 0x010101, 2);
drawRotatedText("NEXT [>]", 220, 90, 0x00FFFF, 0x010101, 1);
String fileInfo = "IMG " + String(galleryIndex) + "/" + String(imageCounter - 1);
drawRotatedText(fileInfo, 220, 200, 0xFFFFFF, 0x010101, 1);
k10.canvas->updateCanvas();
}
// --- 6. 고속 동작 함수 ---
void takePictureAction() {
if (inGalleryMode) return;
// 1. 즉시 플래시 켜기
k10.rgb->setRangeColor(0, 2, 0xFFFFFF);
// 2. 직접 저장(느린 화면 지우기와 텍스트 그리기를 모두 건너뜀!)
String fileName = "S:/IMG_" + String(imageCounter) + ".bmp";
k10.photoSaveToTFCard(fileName);
// 3. 플래시 끄기
k10.rgb->setRangeColor(0, 2, 0x000000);
imageCounter++;
// 4. SD 카드 번호만 업데이트(HUD를 다시 그리지 않으므로 매우 빠름)
String counterText = String(imageCounter);
if(imageCounter < 10) counterText = "00" + counterText;
else if(imageCounter < 100) counterText = "0" + counterText;
drawRotatedText("SD " + counterText, 222, 305, 0xFFFF00, 0x010101, 1);
k10.canvas->updateCanvas();
}
void toggleModeAction() {
inGalleryMode = !inGalleryMode;
if (inGalleryMode) {
k10.setBgCamerImage(false);
if (imageCounter > 1) {
galleryIndex = imageCounter - 1; // 최신 사진부터 시작
} else {
galleryIndex = 0;
}
drawGalleryUI();
} else {
k10.setBgCamerImage(true);
drawCameraHUD(); // 모드 전환 시에만 전체 다시 그리기
}
}
void scrollImageAction() {
if (!inGalleryMode || imageCounter <= 1) return;
galleryIndex++;
if (galleryIndex >= imageCounter) {
galleryIndex = 1; // 첫 번째 사진으로 돌아가 반복
}
drawGalleryUI();
}
// --- 7. 주요 루틴 ---
void setup() {
Serial.begin(115200);
k10.begin();
k10.initScreen(screen_dir);
k10.initBgCamerImage();
k10.setBgCamerImage(false);
k10.creatCanvas();
// K10의 전용 엣지 핀 매크로를 사용하여 입력 정의
pinMode(P0, INPUT_PULLUP);
pinMode(P1, INPUT_PULLUP);
pinMode(P2, INPUT_PULLUP);
drawSplashScreen();
drawCameraHUD();
k10.initSDFile();
k10.setBgCamerImage(true);
k10.rgb->brightness(255);
k10.rgb->setRangeColor(0, 2, 0x000000);
}
void loop() {
unsigned long currentMillis = millis();
bool currentStateP0 = digitalRead(P0);
bool currentStateP1 = digitalRead(P1);
bool currentStateP2 = digitalRead(P2);
// P0: 사진 촬영 버튼(하강 에지에서 즉시 트리거)
if (currentStateP0 == LOW && lastStateP0 == HIGH && (currentMillis - lastDebounceTime > DEBOUNCE_DELAY)) {
takePictureAction();
lastDebounceTime = currentMillis;
}
// P1: 모드 전환 버튼
if (currentStateP1 == LOW && lastStateP1 == HIGH && (currentMillis - lastDebounceTime > DEBOUNCE_DELAY)) {
toggleModeAction();
lastDebounceTime = currentMillis;
}
// P2: 다음 이미지 버튼
if (currentStateP2 == LOW && lastStateP2 == HIGH && (currentMillis - lastDebounceTime > DEBOUNCE_DELAY)) {
scrollImageAction();
lastDebounceTime = currentMillis;
}
// 다음 루프 반복을 위해 상태 저장
lastStateP0 = currentStateP0;
lastStateP1 = currentStateP1;
lastStateP2 = currentStateP2;
}


라이브러리 초기화

프로젝트는 UNIHIKER K10 라이브러리를 포함하고 보드 객체를 생성하는 것으로 시작합니다. 그런 다음 화면 방향, 이미지 번호, 갤러리 상태 및 버튼 디바운스 타이밍을 추적하기 위한 전역 변수를 몇 개 선언합니다.

#include "unihiker_k10.h"

UNIHIKER_K10 k10;

uint8_t screen_dir = 2;
int imageCounter = 1;

bool inGalleryMode = false;
int galleryIndex = 1;

bool lastStateP0 = HIGH;
bool lastStateP1 = HIGH;
bool lastStateP2 = HIGH;

unsigned long lastDebounceTime = 0;
const int DEBOUNCE_DELAY = 200;

이 변수들은 현재 작동 모드를 추적하고 버튼이 실수로 여러 번 눌리는 것을 방지하기 위해 프로그램 전체에서 사용됩니다.

사용자 지정 폰트

내장 폰트에 의존하는 대신 사용자 지정 5×7 비트맵 폰트를 포함했습니다.

const uint8_t font5x7[59][5] = {
};

각 문자는 비트맵으로 저장되므로 펌웨어가 텍스트를 픽셀 단위로 그릴 수 있습니다. 또한 카메라에 사용된 디스플레이 방향에 맞게 텍스트를 회전할 수도 있습니다.

그래픽 엔진

디스플레이에 그림을 그리는 작업을 간소화하기 위해 프로젝트 전반에서 재사용되는 몇 가지 헬퍼 함수를 만들었습니다.

첫 번째 헬퍼 함수는 수평선을 반복해서 그려 채워진 사각형을 그립니다.

void drawSolidBox(int startX, int startY, int w, int h, uint32_t color) {
for(int i = 0; i < h; i++) {
k10.canvas->canvasLine(startX, startY + i,
startX + w,
startY + i,
color);
}
}

두 번째 헬퍼 함수는 사용자 지정 비트맵 폰트를 사용해 회전된 텍스트를 렌더링합니다.

void drawRotatedText(String text,
int startX,
int startY,
uint32_t color,
uint32_t bgColor,
int scale)
{
...
}

이 헬퍼 함수들은 프로젝트 전반에서 모든 화면과 UI 요소를 만드는 데 사용됩니다.

스플래시 화면

카메라 전원이 켜지면 먼저 사용자 지정 부트 애니메이션을 표시합니다.

void drawSplashScreen() {
}

이 함수는 PolyShot 로고, 로딩 텍스트 및 애니메이션 진행률 표시줄을 그린 후 라이브 카메라 피드로 전환합니다.

카메라 사용자 인터페이스

카메라가 시작되면 라이브 카메라 미리보기 위에 사용자 지정 HUD가 그려집니다.

void drawCameraHUD() {
}

이 인터페이스에는 다음이 포함됩니다:

  1. 카메라 설정
  2. 이미지 카운터
  3. SD 카드 표시기
  4. 배터리 상태
  5. 초점 레티클

이 UI는 프로젝트에 실제 소형 디지털 카메라와 같은 외관을 제공합니다.

갤러리 인터페이스

촬영한 이미지는 별도의 갤러리 모드를 사용해 확인합니다.

void drawGalleryUI() {
}

갤러리 모드를 열 때마다 선택한 BMP 이미지를 microSD 카드에서 직접 불러와 화면에 표시하며, 몇 가지 탐색 레이블도 함께 표시합니다.

이미지 캡처

셔터 버튼이 이미지 캡처 루틴을 호출합니다.

void takePictureAction() {
}

이 함수는 네 가지 작업을 수행합니다:

  1. 카메라 플래시를 시뮬레이션하기 위해 RGB LED를 깜박입니다.
  2. 현재 카메라 프레임을 캡처합니다.
  3. 이미지를 microSD 카드에 BMP 파일로 저장합니다.
  4. 디스플레이에 표시되는 이미지 카운터를 업데이트합니다.

전체 이미지 캡처 과정에는 약 3초가 걸립니다.

카메라 및 갤러리 모드 전환

카메라에는 카메라 모드와 갤러리 모드, 두 가지 작동 모드가 있습니다.

void toggleModeAction() {
}

Gallery 버튼을 누르면 이 두 모드 사이가 전환됩니다. 갤러리 모드로 들어가면 가장 최근에 캡처한 이미지가 자동으로 표시됩니다. 카메라 모드로 돌아가면 라이브 카메라 미리 보기가 복원되고 카메라 HUD가 다시 그려집니다.

이미지 탐색

갤러리 모드에서는 세 번째 버튼을 사용하여 이전에 캡처한 이미지를 탐색할 수 있습니다.

void scrollImageAction() {
}

버튼을 누를 때마다 microSD 카드에서 다음 BMP 이미지가 로드됩니다. 마지막 이미지에 도달하면 갤러리는 다시 첫 번째 이미지로 돌아갑니다.

설정 함수

setup() 함수는 카메라가 작동을 시작하기 전에 모든 하드웨어를 초기화합니다.

void setup() {
}

초기화 과정에서 펌웨어는 다음 작업을 수행합니다:

  1. UNIHIKER K10 시작
  2. 디스플레이 초기화
  3. 카메라 시작
  4. 그래픽 캔버스 생성
  5. microSD 카드 초기화
  6. RGB LED 구성
  7. 세 개의 GPIO 버튼 구성
  8. 스플래시 화면 표시
  9. 카메라 인터페이스 그리기

이후 라이브 카메라 미리 보기가 활성화됩니다.

메인 루프

loop() 함수는 UNIHIKER에 연결된 세 개의 버튼을 계속 모니터링합니다.

void loop() {
...
}

유효한 버튼 입력이 감지되면 펌웨어는 해당 함수를 호출합니다:

  1. P0 → takePictureAction() → 이미지 캡처
  2. P1 → toggleModeAction() → 카메라 모드와 갤러리 모드 간 전환
  3. P2 → scrollImageAction() → 다음 이미지 표시

200ms 소프트웨어 디바운싱을 적용하여 각 입력이 한 번만 등록되도록 하며, 실수로 여러 번 트리거되는 것을 방지합니다.





STEP 13. 전면 인클로저 조립


전면 인클로저 조립


전면 인클로저 조립


전면 인클로저 조립


전면 인클로저 조립


전면 인클로저 조립


  1. 전면 인클로저 조립은 세 개의 스위치 액추에이터를 해당 위치에 배치하는 것부터 시작합니다.
  2. 그다음 UNIHIKER 보드를 인클로저 안에 넣고 압입하여 고정합니다. 이어서 스위치 PCB를 스위치 액추에이터 바로 위에 배치합니다.
  3. 사용 중 모든 부품이 단단히 고정되도록 UNIHIKER 보드와 스위치 PCB 양쪽에 소량의 핫 글루를 발랐습니다. 이렇게 하면 조립 과정을 간단하게 유지하면서도 부품이 움직이는 것을 방지할 수 있습니다.




STEP 14. 후면 인클로저 조립


후면 인클로저 조립


후면 인클로저 조립


후면 인클로저 조립


후면 인클로저 조립


  1. 후면 인클로저에서는 먼저 외부에서 스위치 홀더를 해당 위치에 끼웁니다.
  2. 그다음 배터리 수납부에 핫 글루를 바르고 리튬 셀을 지정된 위치에 놓은 뒤, 핫 글루로 고정합니다.
  3. 이후 내부에서 ON/OFF 스위치를 스위치 홀더에 삽입합니다. 스위치는 압입하여 고정하며, 액추에이터는 카메라 외부에서 조작할 수 있도록 남겨 둡니다.




STEP 15. 최종 조립


최종 조립


최종 조립


최종 조립


최종 조립


최종 조립


최종 조립


최종 조립


최종 조립


최종 조립


  1. 이제 최종 조립을 진행하며, 먼저 배터리의 JST 커넥터를 UNIHIKER 보드의 배터리 커넥터에 다시 연결합니다.
  2. 그다음 전면 인클로저와 후면 인클로저를 결합합니다. 두 부품은 단단히 맞물리며, 양쪽을 견고하게 고정하기 위해 왼쪽과 오른쪽에 M2 셀프 태핑 나사를 하나씩, 총 2개 설치합니다.
  3. 전면에서 렌즈 홀더를 제 위치에 놓고 장착 구멍을 UNIHIKER 보드의 장착 스탠드오프에 맞춘 다음, M2.5 볼트를 사용하여 렌즈 홀더를 UNIHIKER 보드에 고정합니다.

이로써 카메라 조립 과정이 완료되었습니다.




STEP 16. 결과


결과


결과


결과


결과


결과


결과


결과


이제 이 제작물의 결과를 보여드리겠습니다: PolyShotDIY 오픈 소스 포인트 앤 슛 카메라입니다.

메인 ON/OFF 버튼을 누르면 카메라가 켜지고 PolyShot 로고와 로딩 바가 있는 사용자 지정 시작 화면이 표시됩니다. 부팅 과정이 완료되면 카메라는 메인 카메라 HUD로 들어갑니다. 렌즈 캡을 간단히 제거하면 사진을 촬영할 준비가 끝납니다.

셔터 버튼을 누르면 이미지가 촬영됩니다. 카메라가 사진을 처리하고 저장하는 데 약 3초가 걸립니다. 그런 다음 두 번째 버튼을 눌러 갤러리를 열고, 세 번째 버튼을 사용해 촬영한 이미지를 탐색할 수 있습니다. microSD 카드를 꺼내 컴퓨터에서 열면 모든 사진이 BMP 형식으로 저장되어 있는 것을 확인할 수 있습니다.

카메라의 조작 방식은 의도적으로 단순하게 설계되었습니다. 피사체를 향해 카메라를 겨누고 셔터 버튼을 누른 뒤 몇 초간 기다리면, 클래식 포인트 앤 슛 카메라처럼 이미지가 촬영되어 microSD 카드에 저장됩니다.




STEP 17. 결론


결론


결론


결론


전반적으로 이 프로젝트는 성공했다고 생각합니다. 완전히 작동하는 DIY 포인트 앤 슛 카메라를 만드는 것이 목표였고, PolyShot은 정확히 그런 카메라가 되었습니다. 사진을 촬영하고 microSD 카드에 저장할 수 있으며, 촬영한 이미지를 기기에서 바로 미리 볼 수도 있습니다.

그렇지만 아직 개선할 부분이 많습니다. 가장 큰 부분은 이미지 품질입니다. 원래 제 아이디어는 독특한 레트로 감성을 지닌 아주 저해상도의 "더미 카메라" 또는 픽셀 카메라를 만드는 것이었습니다. 하지만 프로젝트가 진행되면서 보다 전통적인 디지털 카메라로 발전했습니다. 결과에는 만족하지만, 향후 버전에서는 다양한 센서와 이미지 처리 기법을 실험해 볼 여지가 아직 있다고 생각합니다.

배터리 수명도 개선할 수 있는 부분입니다. 현재 프로토타입은 한 번 충전으로 약 3시간 작동하는데, 프로토타입으로는 충분히 사용할 만하지만 다음 버전에서는 확실히 더 나아질 수 있습니다.

PolyShot V2에서는 UNIHIKER K10에서 완전히 벗어나고 싶습니다. 개발 보드를 중심으로 제작하는 대신, ESP32-S3 모듈, 전용 카메라 센서, 디스플레이, 전원 관리 회로 및 모든 보조 전자 부품을 하나의 맞춤형 PCB에 통합해 처음부터 설계할 계획입니다. 프로젝트를 완전히 오픈 소스로 유지하면서 훨씬 더 작고 깔끔하며 양산 제품에 가까운 카메라를 만드는 것이 목표입니다.

이 프로젝트는 소형 디지털 카메라의 부활과 단순히 하나를 구매하는 대신 무언가 독특한 것을 만들어 보자는 생각에서 영감을 받았습니다. 설계하고 제작하고 문서화하는 과정이 즐거웠으며, 완성된 결과에 정말 만족합니다.

현재로서는 PolyShot이 공식적으로 완성되었습니다. 함께해 주셔서 감사드리며, 직접 하나 만들어 보신다면 여러분의 버전도 꼭 보고 싶습니다!

평화.


원문: https://www.instructables.com/PolyShot-Point-and-Shoot-Camera/

좋아요 0
게시판

회사명 공방유니언 주소 각 공방 주소 참조
사업자 등록번호 0000 대표 공방유니언 전화 019-60105846 팩스 0000 이메일 gongbangunion@gmail.com
통신판매업신고번호 0000 개인정보관리책임자 공방유니언
Copyright © 2017 공방유니언. All Rights Reserved.