산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

하나의 LED를 사용하여 이미지 생성

구성품 및 소모품

NEMA17 스테퍼 모터
× 2
Theremino 시스템용 스테퍼 모터용 Theremino 드라이버 DRV8825
× 2
SD 카드 리더기
× 1
Arduino Mega 2560
× 1
다양한 타이밍 벨트 및 v-휠
× 1

필요한 도구 및 기계

3D 프린터(일반)

앱 및 온라인 서비스

Arduino IDE
김프
오토데스크 퓨전 360

이 프로젝트 정보

아이디어

여러 비디오를 보고 라이트 페인팅에 대한 수많은 기사를 본 후, 나는 그것을 시도하기로 결정했습니다. 라이트 페인팅은 노출 시간이 매우 긴 카메라를 사용하여 작은 광원을 캡처하는 작업을 포함합니다. 이렇게 하면 단일 이미지에서 하나의 빛이 긴 줄무늬로 늘어납니다.

그러나 누군가가 더 자세한 그림을 만들거나 다양한 색상을 사용하고 싶다면 어떻게 해야 할까요? 이것이 내가 색상을 변경하고 이미지를 "페인트"할 수 있는 단일 RGB LED가 있는 2축 CNC 기계를 구축하는 아이디어를 생각해낸 방법입니다.

계획

이 프로젝트가 작동하려면 2축 CNC 기계, RGB LED, SD 카드 및 장노출 사진을 찍을 수 있는 카메라의 네 가지 주요 구성 요소가 필요합니다. 먼저 Arduino Mega는 SD 카드를 읽고 인쇄할 비트맵을 찾습니다.

그런 다음 이미지의 너비가 초과될 때마다 행 아래로 이동하면서 수평으로 가로질러 해당 LED를 켭니다. 마지막으로 조금 기다렸다가 다음 비트맵을 검색하고 더 이상 만들 이미지가 없을 때마다 중지합니다.

리그 구성

CNC 기계를 설계하고 제작한 경험으로 인해 이 단계는 그리 어렵지 않았습니다. 다른 프로젝트에서도 확장할 수 있는 모듈식 제품을 만들고 싶었기 때문에 평행 알루미늄 돌출부를 따라 움직이는 크로스바에 부착된 두 개의 타이밍 벨트를 사용하는 단순한 디자인으로 결정했습니다.

이를 통해 각 축의 길이를 사용자 정의할 수 있습니다. X축의 끝 부분에는 3D 인쇄된 엔드 캡이 있으며 그 중 하나에는 X축 스테퍼 모터 및 베어링용 마운트가 있습니다.

비트맵 읽기

비트맵 파일 형식은 단순함과 읽기 쉽기 때문에 선택했습니다. 파일 형식에 따라 파일 자체에 읽어야 하는 몇 가지 중요한 주소가 있습니다. 0x12(너비), 0x16(높이), 0x1C(색심도), 0xA(픽셀 데이터 위치), 마지막으로 0x36(픽셀 데이터가 일반적으로 있는 위치)입니다.

데이터는 2 또는 4바이트(16 또는 32비트)의 청크로 읽혀지며 포인터도 다음 주소로 이동합니다. 읽기 기능은 오프셋과 크기를 포함한 모든 중요한 데이터를 살펴보고 가져옵니다. 그런 다음 각 픽셀을 한 행씩 읽습니다.

이미지 준비

대부분의 카메라는 최대 30초의 노출 시간으로 제한되어 있기 때문에 해당 시간 동안 표시할 수 있는 총 픽셀 수는 약 288개로 제한됩니다. 이것은 약 18 x 16 이미지에 해당합니다. 내 이미지를 만들기 위해 김프를 로드하고 매우 간단한 픽셀 아트를 만들기 시작했습니다. 여기에는 포켓볼, 하트, 점프하는 마리오가 포함됩니다. 그런 다음 이 세 개의 이미지를 SD 카드의 루트 디렉터리에 있는 "bitmaps"라는 디렉터리에 넣었습니다. 프로그램은 이 폴더의 모든 이미지를 읽습니다.

회화 프로그램

스테퍼 모터에는 내부 위치 피드백 시스템이 없기 때문에 소프트웨어로 위치를 추적해야 합니다. 내가 작성한 프로그램은 쉽게 확장할 수 있도록 그리드 시스템으로 LED의 위치를 ​​추적합니다. Arduino Mega가 시작되면 스테퍼 위치가 0, 0으로 설정된 다음 첫 번째 이미지를 찾아서 읽습니다. 그런 다음 LED가 5번 깜박여 사진 작가에게 촬영을 시작할 시간이 거의 되었음을 알립니다. 비트맵은 먼저 각 행을 반복하여 읽고 각 행 내에서 각 열을 읽습니다. 현재 행과 열을 알면 스테퍼 모터를 동일한 위치로 이동할 수 있습니다. 각 위치에서 LED는 해당 픽셀의 색상으로 변경됩니다.

(재)-이미지 생성

SD 카드를 삽입하고 모터용 12v 전원을 연결한 후 기계를 켤 시간이었습니다. 내 카메라에서는 고스트 효과를 최소화하기 위해 노출 시간 20초, 조리개 F36, ISO 100, 노출 보정 -5 스탑으로 설정했습니다. 그린 첫 번째 이미지는 여기에서 볼 수 있는 포켓볼입니다.

약간 흐릿하긴 해도 모양은 여전히 ​​선명하게 보입니다. 그런 다음 하트 비트맵을 생성했습니다.

이 이미지는 9 x 9픽셀에 불과했기 때문에 각 개별 픽셀은 훨씬 덜 정의되었습니다. 마지막으로 마리오가 점프하는 모습을 그렸습니다.

이 사진에는 주로 밝은 색상의 픽셀이 많기 때문에 고스트 현상이 심합니다.

개선을 위한 향후 아이디어

내가 만든 라이트 페인팅은 처음에 생각했던 것보다 훨씬 잘 나왔지만 여전히 개선의 여지가 있습니다. 제가 가장 하고 싶은 일은 LED가 어두워진 상태에서 움직이고 정지 상태일 때만 켜지도록 하여 블러의 양을 줄이는 것입니다. 이 기술은 재생성된 이미지의 선명도를 크게 향상시킵니다.

<섹션 클래스="섹션 컨테이너 섹션 축소 가능" id="코드">

코드

<울>
  • 라이트 페인팅 프로그램
  • 라이트 페인팅 프로그램C/C++
    //Adafruit에서 부분적으로 비트맵 읽기 기능#include #include #include "DRV8825.h#define MOTOR_STEPS 200#define RPM 150#define MICROSTEPS 4//핀 정의#define // STEPPER_X_DIR 7#define STEPPER_X_STEP 6#define STEPPER_X_EN 8#define STEPPER_Y_DIR 4#define STEPPER_Y_STEP 5#define STEPPER_Y_EN 12#define X 0#define Y 1#define X_DIR_FLAG -1 //1 또는 FL로 뒤집기 1 또는 -1 방향 반전#define STEPS_PER_MM (3.75 * MICROSTEPS) //1mm 이동에 필요한 단계#define SPACE_BETWEEN_POSITIONS 5 //이동당 5mm#define R A0#define G A1#define B A2#define SD_CS 22int currentPositions[] ={0, 0};DRV8825 스테퍼X(MOTOR_STEPS, STEPPER_X_DIR, STEPPER_X_STEP, STEPPER_X_EN);DRV8825 스테퍼Y(MOTOR_STEPS, STEPPER_Y_DIR, STEPPER_Y_STEP, STEPPER_Y_EN); 무효 설정(20)1; 직렬 init_steppers(); SD.begin(SD_CS); 생성 비트맵(); 스테퍼X.disable(); 스테퍼Y.disable(); while(1);}void 루프() {}void createBitmaps(){ 파일 디렉토리 =SD.open("비트맵"); while(true){ 파일 비트맵 =dir.openNextFile(); if(! 비트맵){ 휴식; } 페인트 비트맵(비트맵); 지연(15000); } }#define BUFFPIXEL 20void paintBitmap(파일 bmpFile){ int bmpWidth, bmpHeight; uint8_t bmpDepth; uint32_t bmpImageOffset; uint32_t 행 크기; // 항상 그런 것은 아님 =bmpWidth; 패딩이 있을 수 있음 uint8_t sdbuffer[3 * BUFFPIXEL]; // 픽셀 버퍼(픽셀당 R+G+B) uint8_t buffidx =sizeof(sdbuffer); // sdbuffer의 현재 위치 부울 goodBmp =false; // 유효한 헤더 구문 분석에서 true로 설정 부울 flip =true; // BMP는 아래에서 위로 저장됩니다. int w, h, row, col; uint8_t r, g, b; uint32_t 위치 =0, 시작 시간 =밀리(); 직렬.println(); Serial.print("이미지 불러오기 '"); Serial.print(bmpFile.name()); Serial.println('\''); // SD 카드에서 요청된 파일 열기 // BMP 헤더 구문 분석 if (read16(bmpFile) ==0x4D42) { // BMP 서명 Serial.print("파일 크기:"); Serial.println(read32(bmp파일)); (무효)read32(bmp파일); // 작성자 바이트 읽기 및 무시 bmpImageOffset =read32(bmpFile); // 이미지 데이터 시작 Serial.print("이미지 오프셋:"); Serial.println(bmpImageOffset, DEC); // DIB 헤더 읽기 Serial.print("헤더 크기:"); Serial.println(read32(bmp파일)); bmp폭 =read32(bmp파일); bmp높이 =read32(bmp파일); if (read16(bmpFile) ==1) { // # 평면 -- '1'이어야 함 bmpDepth =read16(bmpFile); // 픽셀당 비트 수 Serial.print("Bit Depth:"); Serial.println(bmpDepth); if ((bmpDepth ==24) &&(read32(bmpFile) ==0)) { // 0 =압축되지 않은 goodBmp =true; // 지원되는 BMP 형식 -- 계속하십시오! Serial.print("이미지 크기:"); Serial.print(bmpWidth); Serial.print('x'); Serial.println(bmp높이); // BMP 행은 (필요한 경우) 4바이트 경계로 채워집니다. rowSize =(bmpWidth * 3 + 3) &~3; // bmpHeight가 음수이면 이미지는 하향식 순서입니다. // 이것은 표준이 아니지만 야생에서 관찰되었습니다. if (bmpHeight <0) { bmpHeight =-bmpHeight; 뒤집기 =거짓; } // 로드할 자르기 영역 w =bmpWidth; h =bmp 높이; if(bmpWidth*bmpHeight>290){ //너무 큼 Serial.println("파일이 너무 커서 인쇄할 수 없습니다."); 반품; } for(uint8_t i=0; i<5;i++){ analogWrite(R, 150); 지연(500); analogWrite(R, 0); 지연(500); } for (row =0; row=sizeof(sdbuffer)) { // 실제로 bmpFile.read(sdbuffer, sizeof(sdbuffer)); 버피드x =0; // 인덱스를 시작으로 설정 } // 픽셀을 BMP에서 TFT 형식으로 변환하고 푸시하여 표시 b =sdbuffer[buffidx++]; g =sdbuffer[버피드x++]; r =sdbuffer[버피드x++]; moveToPosition(열, 행); 활성화 LED(r,g,b); // 최적화! //tft.pushColor(tft.Color565(r,g,b)); } // 끝 픽셀 analogWrite(R, 0); analogWrite(G, 0); analogWrite(B, 0); } // 스캔라인 종료 Serial.print("Loaded in "); Serial.print(millis() - 시작 시간); Serial.println("밀리초"); } // goodBmp 종료 } } bmpFile.close(); 이동 위치(0,0); if (!goodBmp) Serial.println("BMP 형식이 인식되지 않습니다.");}uint16_t read16(파일 f) { uint16_t 결과; ((uint8_t *)&결과)[0] =f.read(); // LSB((uint8_t *)&결과)[1] =f.read(); // MSB 반환 결과;}uint32_t read32(파일 f) { uint32_t 결과; ((uint8_t *)&결과)[0] =f.read(); // LSB((uint8_t *)&결과)[1] =f.read(); ((uint8_t *)&결과)[2] =f.read(); ((uint8_t *)&결과)[3] =f.read(); // MSB 반환 결과;}void activateLED(int r, int g, int b){ Serial.print(F("LED의 값:")); Serial.print(r); Serial.print(", "); Serial.print(g); Serial.print(", "); Serial.println(b); analogWrite(R, r); analogWrite(G, g); analogWrite(B, b);} 무효 moveToPosition(int x, int y){ int newPosX =(x-currentPositions[X])*STEPS_PER_MM*X_DIR_FLAG*SPACE_BETWEEN_POSITIONS; int newPosY =(y-currentPositions[Y])*STEPS_PER_MM*Y_DIR_FLAG*SPACE_BETWEEN_POSITIONS; stepperX.move(newPosX); stepperY.move(newPosY); 현재 위치[X] =x; 현재 위치[Y] =y; Serial.print("스테퍼 위치:"); Serial.print(현재 위치[X]); Serial.print(", "); Serial.println(currentPositions[Y]);} 무효 init_steppers(){ stepperX.begin(RPM); 스테퍼X.setEnableActiveState(낮음); 스테퍼X.enable(); stepperX.setMicrostep(MICROSTEPS); 스테퍼Y.begin(RPM); 스테퍼Y.setEnableActiveState(낮음); 스테퍼Y.enable(); stepperY.setMicrostep(MICROSTEPS);}

    맞춤형 부품 및 인클로저

    회로도


    제조공정

    1. 제품 디자인에 천연 소재를 다시 사용하게 된 이유는 무엇입니까?
    2. Raspberry Pi를 사용한 모션 센서
    3. FMEA 생성 및 사용에 대한 유지 관리 관리자 안내서
    4. Firebase를 사용하여 Arduino 간에 센서 데이터 보내기
    5. 세 번째 변수를 사용하지 않고 두 개의 Python 변수 바꾸기
    6. 관춤 멜로디
    7. 오래된 리모컨 재활용
    8. Firmata 및 Xbox One 컨트롤러를 사용하여 Arduino Rover 제어
    9. 사운드에 의한 8x LED 조명
    10. Arduino Quadruped