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

범선용 자동 조종 장치(자동 조향 시스템)

구성품 및 소모품

Arduino UNO
× 1
Arduino Nano R3
× 1

이 프로젝트 정보

머리말:

나는 혼자 항해하는 것을 좋아합니다. 한 남자가 범선을 타고 바다에 있을 때 더 높은 수준으로 진화하는 데 필요한 모든 것을 얻었기 때문입니다. 날씨가 좋지 않은 원시 바다에서 항해하는 것은 매우 힘들 수 있지만 태양과 바람이 좋은 날씨가 좋은 날을 선택하면 최대로 즐길 수 있습니다.

행복은 무한한 지평, 완벽한 운동 기술, 최적의 선택, 뿐만 아니라 좋은 음료와 맛있는 샌드위치와 같은 인간적인 것을 의미합니다! 바로 이 시간에 자동 조종 장치가 도움이 됩니다. 바다에서 오후 5시 차와 비스킷을 먹는 동안 사용자 대신 작동합니다. :-)

Autopilot이 당신을 위해 할 수 있는 일:

범선은 엔진이 없고 항구에서 해변까지 프로그램된 경로를 따라 갈 수 없으며 낚시터로 가고 등대를 돌았다가 다시 돌아올 수 없습니다.

전체 작업은 선원이 수행합니다. 이 시점에서 돛을 다듬고, 날씨와 바람의 근원/속도를 제어하고, 로프를 단단하게 하거나 풀고, 다른 보트와의 교통 상황을 고려하고, 방향과 조향을 결정하는 등의 작업을 이해해야 합니다. 선원은 휴식을 취하기로 결정하고 10초 또는 몇 분(유명한 "티 타임")으로 자동 조종 장치를 켭니다. 몇 초 만에 GPS가 보트의 위치, 속도 및 방향을 파악하고 방향(경로)을 유지할 수 있습니다. 방향타에 연결된 막대기인 조향 시스템은 일반적으로 숙련된 선원의 손에 의해 움직이며, 이제 도르래와 로프로 연결된 스테퍼 모터를 통해 자동 조종 장치에 의해 제어됩니다.

방향타 제어 미세 조정 또는 총 조정의 지속적인 작업입니다. 더 작고(가벼울수록) 보트에 영향을 미치는 방향 요인의 변화가 더 클 것입니다:파도, 바람의 방향 및 압력, 선원의 움직임에 의한 선상의 무게 이동, 해류. 그러나 Sailor는 자동 조종 장치가 켜져 있어도 항상 깨어 있어 원격 제어를 통해 실제 경로를 변경합니다. :그 위에 +1 -1 +10 -10으로 레이블된 4개의 버튼이 있으며, 값을 높이거나 낮추는 도의 작거나 큰 변화를 나타냅니다. 이 버튼은 Autopilot에 있습니다 녹색(오른쪽)과 빨간색(왼쪽)도 마찬가지입니다. 파란색 버튼(가운데)은 일시 정지인 Autopilot을 활성화 또는 비활성화하기 위한 것입니다. 매개변수 설정을 위한 검은색 버튼이기도 합니다. 메모리에.

회로:

주요 처리는 MCU Arduino Uno에 의해 수행됩니다. . 다른 MCU, Arduino Nano , 는 감시자입니다. Uno 내부에 일종의 감시자가 존재한다는 것을 알고 있지만 독립적인 외부 마이크로컨트롤러와 함께 하는 것을 좋아했습니다. 평생의 꿈이었습니다. 지금 행복합니다! Uno는 핀 3 -> A0을 통해 Nano에 2.5초마다 최소 한 번씩 high/low, 5/0볼트를 공급해야 합니다(feedingInterval). 그렇지 않은 경우 Uno가 "잠자기" 또는 "차단됨"을 의미하고 Nano가 Uno를 재설정합니다... 아직 발생한 적이 없습니다. 믿을 수 있습니까?

인기 디스플레이로 사용됩니다. i2c 회로 변환기와 함께 둘 다 함께 납땜되어 마지막으로 단 4개의 와이어를 사용하여 Uno와 통신할 수 있는 디지털 핀을 크게 절약할 수 있습니다. 버튼과 리모컨을 연결하는 방법도 저항 전압 분배기는 가능한 한 적은 수의 MCU 포트를 사용하려는 목표에 도달하기 위해 수행됩니다. 나는 1% 정밀 저항기를 선택했으며 아날로그 비교 값은 코드에 입력한 값 사이여야 합니다. 다른 종류의 저항을 선택하여 일부 버튼을 인식하지 못하는 경우 상수도 약간 변경하십시오("checkRfRC()" 및 "checkHWButtons()"에서 코드 수정). RF 433Mhz 원격 제어(RC) 회로가 잘 작동합니다. 거리 범위와 성공 가능성을 높이기 위해 구리선으로 직접 만들 수 있는 코일 안테나를 추가했습니다. 10m 떨어진 곳에서 테스트했지만 20m 이상에서도 작동 할 수 있다고 생각합니다. Autopilot 테스트에 사용한 대상 범선의 길이가 4.20m에 불과하다는 점을 고려하면 충분합니다.

GPS 장치의 경우 나는 처음에 좋은 EM406A를 사용했지만 불행히도 Week-Rollover-Bug가 있다는 것을 발견했습니다. 너무 오래되어 훌륭하고 인기있는 Beitian BN-220T로 교체해야했습니다. 구성 소프트웨어를 사용하여 "$GNRMC" NMEA 직렬 문장에 필요한 초당 2회(2Hz) "뱉어내"도록 설정하십시오. GPS는 (TX) 직렬 데이터를 Uno의 핀 0(RX)으로 보냅니다. 데이터에는 날짜, 시간, 위치 위도 및 경도, 실제 경로, 속도 및 위성 수정의 유효성과 같이 모터에서 수행할 수정을 계산하는 데 사용되는 모든 항법 데이터가 포함됩니다. Arduino의 IDE 프로그래밍도 핀 0(RX) 포트를 사용하기 때문에 이 작업 중에 임시 GPS 연결을 해제하는 것을 기억하십시오...

내 또 다른 꿈은 EEPROM을 사용하는 것이었습니다. . IC 2404는 이 메모리 칩에서 스테퍼 모터 동작에 대한 일부 매개변수를 읽고 쓰는 데 사용한 아름다운 512바이트 i2c 집적 회로입니다. 나중에 "소프트웨어" 단락에서 설명하겠습니다.

구성 요소 목록:

<울>
  • MCU로서의 아두이노 우노
  • <울>
  • 워치독으로서의 Arduino Nano
  • <울>
  • Beitian BN-220T GPS
  • <울>
  • 스테퍼 모터, 모델 23LM, 54단계 =회전의 1/4
  • <울>
  • 모터용 컨트롤러 키 L298
  • <울>
  • RF433Mhz RC XD-YK04 + 4개 버튼 리모컨 + 코일 안테나
  • <울>
  • 6 버튼 정상 열림(2x빨간색, 2x녹색, 1x검정 및 1x파란색)
  • <울>
  • 전원 켜기/끄기 스위치(흰색)
  • <울>
  • 외부 스테퍼 모터용 암 + 수 6핀 원형 커넥터
  • <울>
  • 부저
  • <울>
  • 디스플레이 LCD1602 2x16 문자 + i2c 변환기 회로
  • <울>
  • 3개의 LED(빨간색, 파란색 및 노란색))
  • <울>
  • IC 24c04 i2c eeprom
  • <울>
  • IC 4051 멀티플렉서
  • <울>
  • 배터리 LiPo 2s 7.4v 2600mA
  • <울>
  • IC 7805 전압 조정기 + 방열기
  • <울>
  • 서미스터 NTC MF52-103 10k
  • <울>
  • 재설정 가능한 퓨즈 2A
  • <울>
  • 6x 1N4148 다이오드(D1-D6)
  • <울>
  • 파워 실드의 저항기(R1-R4=10k, R5=100k)
  • <울>
  • 자동 조종 장치 실드의 저항기(R1=330, R2=1k, R3=2k, R4=5.1k, R5=1k, R6/R7/R14=330, R8-R13=10k, R15=10M)
  • <울>
  • 커패시터(C1=470uF 16v, C2=100n)
  • <울>
  • 2W 0.22옴 저항(R6)
  • <울>
  • 남성 핀
  • <울>
  • 암컷 긴 핀 헤더
  • <울>
  • 투명 케이스 및 "방수"
  • 센서가 몇 개 있습니다 IC 4051 멀티플렉서의 도움으로 Arduino Uno에 모두 연결된 회로에서 . 서미스터 입니다. 제어 전압 조정기 가열 분산기 온도를 취하기 위해 2W 저항기 암페어를 전력 소비로 계산하기 위한 전압 분배기로 4x10k 전체 회로의. 또한 배터리 전압 통제 하에 놓임:LiPo는 단일 요소가 3.3v 미만으로 방전될 때 중요한 것으로 알려져 있습니다. 이 회로는 저전압(7.0v 이하)의 경우 부저의 경우 하나의 패키지에 2개의 소자(2S) LiPo가 있습니다. 짧은 빠른 신호음으로 알려줍니다. 전원을 끌 때까지 너무 오래 기다리지 말고 곧 재충전하십시오! 주도자 :1Hz로 깜박이는 노란색은 WatchDog가 작동 중임을 알려줍니다. 파란색은 Autopilot이 켜져 있을 때 켜져 있고 일시 중지된 경우 꺼져 있습니다. 리모콘 버튼 중 하나를 누르면 빨간색 LED가 깜박입니다.

    모든 회로는 LiPo 2S 7.4v 2600mA/h 배터리 및 IC 7805 전압 조정기에서 공급하는 5.0v에서 작동합니다. . 전류는 800mA보다 크지 않아야 하지만 일반적으로 약 100-450mA입니다. 발열제를 착용하세요. . 그 위에 서미스터를 올려 놓고 온도가 50°C를 초과하면 부저가 울립니다.

    PCB 인쇄 회로 기판 및 어셈블리:

    단면 PCB 사용 그런 이유로 전체 회로의 경로를 해결하기 위해 몇 개의 와이어 점퍼(점선 점퍼)를 포함해야 했습니다. 여기에는 구성 요소 면이 표시되지만 아래에는 "노란색" 또는 "파란색" 시트에 레이저 프린터를 통해 다운로드 및 인쇄할 수 있도록 미러링된 모든 파일, 구성 요소 및 솔더 면이 있습니다. 나는 노란색을 사용했지만 파란색이 더 낫다고 합니다(그러나 가격은 훨씬 더 비쌉니다). 인쇄할 때 토너 절약 설정을 비활성화하는 것을 잊지 말고 대신 1200dpi 해상도를 사용하여 깊은 실제 검은색 결과를 얻으십시오. 매직 시트에서 PCB로의 토너 전송 프로세스는 뜨거운 다리미를 사용하여 이루어집니다. 양면 인쇄는 구성 요소 면에도 적용되므로 항목의 위치를 ​​쉽게 인식할 수 있으며 프로젝트를 "전문적"으로 만들기도 합니다.

    두 PCB 모두 다른 Arduino Uno 위에 스택으로 맞도록 크기가 조정됩니다. :첫 번째 전원 장치 다음 자동 조종 장치 전체입니다.

    내 선택은 PCB, MCU, RC, 모터 드라이버 회로, 배터리, GPS, 버튼, 스위치, 전선, 커넥터 등 모든 것을 하나로 묶는 것이었습니다. 언젠가는 재사용할 생각입니다. 강한>중고 헤더 및 인기 있는 Dupont 전선/연결부 대신에. 약 200개의 납땜되지 않은 연결이 있으며, 이는 예기치 않은 원치 않는 오작동 또는 때때로 회로의 다른 동작이 발생할 수 있음을 의미하며 이는 정상입니다. 모든 것을 납땜하는 것이 좋습니다. 보다 안정적인 회로를 위해!

    매개변수 설정 및 디스플레이 센서 값:

    검은색 버튼 누르기 상자 측면에 설정 모드로 들어갑니다.; 이것은 활성 탐색 중에도 수행할 수 있으므로 먼저 일시 중지를 입력할 필요가 없습니다. 디스플레이의 첫 번째 페이지는 배터리 전압(V=7.83), 소비 전력(mA=177) 및 소산자 근처의 써미스터 센서 온도(38°C)를 보여줍니다. 검은색 버튼을 반복해서 누르면 다음 페이지로 이동합니다. 두 번째, 세 번째, 네 번째 및 다섯 번째 페이지에는 아래 나열된 매개변수가 표시되며 -1 및 +1 버튼을 사용하여 이 값을 변경할 수 있습니다. 6페이지에 "업데이트 중..."이 표시되며, 변경한 값은 EEPROM 메모리에 저장됩니다.

    <울>
  • 간격: 즉, 2000 mSec는 스테퍼 모터가 "H" 방향을 "R" 경로로 복원하고 방향타 스틱을 오른쪽 또는 왼쪽으로 이동하는 시도와 다른 시도 사이의 시간입니다.
  • <울>
  • 최소: 즉, 2°는 자동 조종 장치가 개입할 수 있는 경로를 벗어난 최소 각도입니다. 이 값까지 방향타는 중앙 위치에 안정적으로 유지됩니다.
  • <울>
  • 최대: 즉, 40°는 스테퍼 모터에 의한 한 번에 최대 조향 변경입니다. 계산이 50° 변경에 대한 것이라면 실제로 스테퍼는 40°만 이동합니다.
  • <울>
  • 계수: 즉, 1.50 x °는 한 번에 조향 변경에 대한 계수입니다. 계산이 40° 변경에 대한 것이라면 실제로 스테퍼 모터는 (40 x 1.50)=60°로 이동합니다.
  • 이 매개변수는 Autopilot을 미세 조정하는 데 필요합니다. 범선에 설치된 경우. 응답성, 감도 및 부드러움은 풀리의 지름, 풀리의 수, 스테퍼 모터의 메인 풀리 지름, 방향타의 감도, 방향타 스틱이 연결된 길이 등에 따라 달라집니다. 모든 것을 설치하고 기내에서 경험을 해보세요. 물론 모든 테스트 단계에서 바람이 약하고 맑고 아름다운 날을 선택하세요!

    "라이브" 작동 방식:

    당신은 바다, 호수 또는 항구 주변을 항해하고 있습니다. 지금은 티타임이고 당신의 콜라와 좋아하는 샌드위치가 주머니에 기다리고 있습니다. 다음은 자동 조종 장치 전환입니다. 켜기 위성 GPS를 수정하면 이제 디스플레이에서 실제 속도(노트, 시계 및 진행 방향), 즉 H270°(R=따라야 할 경로, H=실제 방향)를 도 단위로 읽어야 합니다(180°=남쪽, 270° 기억 =서쪽, 360° 또는 0°=북쪽 및 90°=동쪽). R 및 H 값은 일시 중지 모드(STOP이 표시됨)에 있을 때 동일합니다. 이제 스테퍼 모터에서 방향타 스틱까지 스티어링 로프를 연결하고 파란색 버튼을 눌러 자동 조종 장치 스티어링을 시작하세요.; 이 시점에서 Autopilot은 R=경로 방향을 유지하고 H=방향으로 발생하는 상황을 제어합니다. 제목 번호가 확실히 변경됨 , 우리가 이미 이야기한 기상 조건에 따라 천천히 또는 빠르게. 그런 다음 자동 조종 장치는 R=경로 방향으로 복원을 시도합니다. H 값이 R 값과 같아질 때까지 수정(예:-10°, +5° 등) . Route에서 일부 변경 사항을 결정할 수 있으며 장치의 빨간색 및 녹색 버튼(-1 -10 +1 +10)을 사용하거나 원격 제어를 통해 번호를 수정할 수 있습니다. 통제권을 되찾기 위해 조향 파란색 일시 중지 버튼을 누르고 방향타 스틱에서 로프를 분리하여 손으로 작업을 계속하면 됩니다. 잘했습니다.

    소프트웨어 측면:

    코드가 꽤 길지만 이해하기 쉽도록 명확했으면 합니다. 어쨌든 나는 그것이 어떻게되고 있는지 설명 할 것입니다. 스케치는 프로그램의 약 65%와 메모리의 약 45%를 사용합니다. 주로 Serial NMEA 문장 조작을 위해 String 클래스를 사용하더라도 전체 정교화 흐름이 안정적이고 견고합니다. "serialEvent()"를 사용하여 GPS에서 초당 두 번 데이터를 수신합니다. , "nmeaExtractData()"를 호출하는 것보다 마지막으로 "nmea0183_checksum()을 사용하여 데이터 패킷을 확인하여 데이터 무결성을 확인합니다. 다른 제조사 및 모델 GPS를 사용하는 경우 문장이 동일한 구조인지 확인하거나 여기에서 일부 변경을 해야 합니다. 예를 들어 EM406A는 "$GPRMC" 패킷 ID를 사용합니다. , BT220은 대신 "$GNRMC"를 사용합니다... 이름만 약간 변경하면... 체크섬 테스트에 유용한 링크가 도움이 될 수 있습니다. https://nmeachecksum.eqth.net - 여기에 완전한 NMEA 문장의 예가 포함되어 있습니다. :id, 시간, 유효성, 위도, 경도, 속도, 실제 코스, 날짜, 변동 및 체크섬.

    $GPRMC, 095836.000, A, 4551.9676, N, 01328.7118, E, 2.09, 341.84, 280519,, *08

    "Setup()" 동안 EEPROM이 확인됩니다 :새롭거나 알 수 없는 경우 초기화(포맷)됩니다. 메모리의 매개변수는 바이트로 읽기/쓰기가 가능합니다. 0=0x29, 1=0x00, 2-3=간격, 4-5=최소, 6-7=최대, 8-11=계수(byte, byte, int, int, 뜨다). EEPROM r/w 작업을 조심스럽게 처리했습니다. 아마도 너무 방어적인 것 같습니다... 10초마다 센서를 확인합니다. 멀티플렉서에 의한 "readMuxSensors()"를 통해 배터리가 낮거나 온도가 높을 경우 알람을 유발할 수 있습니다. 소비 전력의 분해능은 약 40mA의 단계로 낮습니다. 하드웨어 및 RC 버튼 지속적으로 확인됩니다. 그들이 하는 일은 "IsSetup" 부울 값과 디스플레이 "RefreshDisplay()"가 하는 일에 따라 다릅니다. . 코드의 핵심은 스테퍼를 앞뒤로 이동하기 위해 "gomotor()" 함수를 호출하는 STEERING CONTROL 섹션입니다.; 예, 방향타를 오른쪽으로 10° 이동할 수 있으며 간격 값 후에 다시 0 방향타 위치로 이동하는 식으로 새로운 계산 라운드 후에 계속됩니다. 이미 말했듯이 스티어링 작업은 몇 개의 버튼과 디스플레이 동작에만 영향을 미치기 때문에 설정 중에도 수행됩니다. 왓치독 먹이주기 매우 간단하지만 중요합니다. 가능한 한 빨리 핀을 켜십시오.

    범선에 설치하는 방법:

    아래 그림에서 볼 수 있듯이 Autopilot과 Stepper Motor를 선미에 두기로 선택했습니다. 둘 다 볼트 등으로 잘 고정되어 있습니다. 직경 6mm의 로프가 메인 모터 풀리에서 시작하여 양쪽에 배치된 두 개의 다른 풀리를 돌고 있습니다. 이 2개의 도르래는 로프의 장력을 약간 유지하기 위해 2개의 번지 고리를 통해 보트에 "고정"되어야 합니다. 이 시점에서 마지막으로 방향타 막대에 로프를 연결하는 방법(임시 연결)을 결정해야 합니다. Autopilot이 작동하고 연결하기 쉽고 연결 해제하기 쉬운 동안 연결되어야 합니다. Autopilot 시스템을 물에서 멀리 유지하십시오! :-)

    뉴스 및 업데이트:

    <울>
  • 2020년 5월 10일, 스테퍼 풀리(저) 및 마운팅 플레이트(Andrew Barney)에 대한 .STEP 3D CAD 프로젝트 파일과 3D 미리보기 사진을 다운로드하기 위해 추가되었습니다.
  • 면책 조항 및 경고:

    여기서 하는 게임이라고 가정해 보겠습니다. 아무 것도 아닙니다. ~으로 진지하게! 몇 년 전 나는 범선을 타고 16개월이라는 긴 여행을 갔습니다. 우리는 실제 자동 조종 장치로 광범위하게 탐색했습니다(NOT 이것 하나!) 모든 기상 조건에서, 심지어 악천후 조건에서도. 진정한 자동 조종 장치는 무엇인가 매우 강한 하드웨어와 소프트웨어 모두신뢰해야 많이. 대신 이 Arduino Autopilot 할 수 있는 환상적인 게임입니다 함께 재미있게 시간을 보내세요.

    즐거운 시간 보내세요!

    마르코 존카

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

    코드

    <울>
  • 자동 조종 장치 스케치(Uno용)
  • WatchDog 스케치(Nano용)
  • Autopilot 스케치(Uno용)Arduino
    /* 이 스케치는 소형 범선의 자동 조종 장치 역할을 합니다. Marco Zonca, 2019 Arduino UNO as CPU, Arduino Nano as watchdog, GPS BT-220 nmea, 스테퍼 모터 + 컨트롤러, rf433Mhz RC, 6 버튼, 버저, i2c 디스플레이, 2개의 led, i2c 24c04 eeprom, 센서용 Mux 4051, lipo 2s 7.4v 2600mA, 7805 전압 조정기, 서미스터;*/#include #include #include  포함 문자열 입력 문자열 =""; 문자열 nm_time ="00:00:00"; 문자열 nm_validity ="V"; 문자열 nm_latitude ="ddmm.mmmm'N"; 문자열 nm_longitude ="dddmm.mmmm'E "; 문자열 nm_knots ="0.0kn"; float nmf_knots =0.0; 문자열 nm_truecourse ="360"; float nmf_truecourse =360; 문자열 nm_date ="dd/mm/yyyy"; 문자열 nm_routetofollow ="000";float nmolf_route unsigned long previousStearingMillis =0; unsigned long currentStearingMillis =0; unsigned long prevCheckSensorsMillis =0; unsigned long currCheckSensorsMillis =0;int CheckSensorsInterval =10000;bool stringComplete =false;bool isfirstfix =true;bool ispause =true;bool isStearing =false;bool isSetup =false;int s=0;int y=0;int z=0;int d=0;int rfRemoteControlValue =0;int HWButtonValue =0;int SetupParameter =0;float calcmove =0;float cm =0;float Stearing =0;float prevStearing =0;float t =0;int EEdisk =0x50;int EEid1 =0x29;int EEid2 =0x00;unsigned int EEaddress =int 0;unsigned =12;byte EEdata[12];byte EEbytedata;int EEerr =0;float SensorVBatt=0;float SensorVres=0;float SensorTemp=0;float SensormAmp=0;// 다음 매개변수는 기본값이지만 읽기/쓰기가 가능합니다. eeprom// eeprom은 주소 0과 1에서 내용이 다른 경우 초기화됩니다. 주소 len 유형 notes// 0x50 EEdisk에서 0-255바이트, 0x51에서 256-512바이트(사용되지 않음) ---------- -------------------------------------------------- ---// 0 1B 바이트 01001001(자동조종 프로젝트 id1로 0x29)// 1 1B 바이트 00000000(0x00 " " id2)int StearingInterval =2000; // try와 back 사이의 밀리초 2 2B int StearingInterval 1000-5000 steps 100int StearingMinToMove =2; // compass_degrees 4 2B int StearingMinToMove 0-20 단계 1int StearingMaxMove =40; // compass_degrees 6 2B int StearingMaxMove 10-45 단계 1float StearingCoeffMove =1.5; // (compass_degrees * coeff)로 사용됨 8 4B float StearingCoeffMove 0.1-4 steps 0.1// 12 free//byte bStearingInterval[sizeof(int)];byte bStearingMinToMove[sizeof(int)];byte bStearingMaxMove[sizeof(int)];byte bStearingCoeffMove[sizeof(float)];int prev_StearingInterval=0;int prev_StearingMinToMove=0;int prev_StearingMaxMove=0;float prev_StearingCoeffMove=0;const int ledpausePin =2;const int watchDogPin=2;const int watchDogPin=// 00=Vin 01=Vbatt 10=Tempconst int MuxSelBit1Pin =6; // const int motorsABenablePin =13;const int MuxIOPin =14;const int ButtonsPin =15;const int rfRemoteControlPin =16;const int speakerPin =17;const int RCleftbutton =201; const int RCright10button =204; const int HWleftbutton =101; const int HWrightbutton =102; const int HWpausebutton =103; const int HWsetupbutton =104;const int HWleft10button =105;const int HWright // 모델 23LM의 경우 200, 54단계 =회전의 1/4LiquidCrystal_I2C lcd (0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);스테퍼 모터(motorStepsPerRevolution, 9, 112, 11, ); 무효 설정() { Serial.begin(4800); lcd.begin(16,2); Wire.begin(); motor.setSpeed(60); inputString.reserve(200); 핀모드(모터ABenablePin, OUTPUT); 핀모드(MuxSelBit0Pin, 출력); 핀모드(MuxSelBit1Pin, 출력); digitalWrite(motorsABenablePin, LOW); digitalWrite(MuxSelBit0Pin, LOW); digitalWrite(MuxSelBit1Pin, LOW); 핀모드(ledpause핀, 출력); pinMode(watchDogPin, 출력); digitalWrite(ledpausePin, LOW); digitalWrite(watchDogPin, LOW); // 읽기+EEPROM 확인(새롭거나 식별되지 않은 경우 포맷) lcd.clear(); lcd.setCursor(0,0); lcd.print("메모리 검사..."); lcd.setCursor(0,1); (s =0, s =CheckSensorsInterval) { readMuxSensors(); if ((SensorVBatt <=7.0) || (SensorTemp>
    =50)) { lcd.clear(); lcd.setCursor(0,0); lcd.print("알람 센서! "); lcd.setCursor(1,1); lcd.print("V="); lcd.print(센서VBatt); lcd.print(" "); lcd.print(int(센서 온도)); lcd.write(0xDF); lcd.print("C"); NewTone(스피커 핀, 10); 지연(1000); noNewTone(); } prevCheckSensorsMillis =currCheckSensorsMillis; } // STEARING 제어 ---------------- currentStearingMillis =millis(); if (currentStearingMillis - previousStearingMillis>=StearingInterval) { if (isStearing ==false &&ispause ==false) { // 시도 이동(스티어링 이동) calcmove =nmf_routetofollow - nmf_truecourse; if (calcmove <(-180)) { calcmove =calcmove + 360; } else { if (calcmove> (+180)) { calcmove =calcmove - 360; } } if (abs(calcmove)>=StearingMinToMove) { if (abs(calcmove)>=StearingMaxMove) { if (calcmove <0) { cm =(StearingMaxMove * -1); calcmove =cm; } else { cm =(StearingMaxMove * 1); calcmove =cm; } } 스테어링 =(calcmove * StearingCoeffMove); gomotor(int((스티어링 * 216) / 360)); // 54단계 =회전의 1/4 prevSearing =Stearing; isStearing =사실; } } else { // 뒤로 이동(stearing을 "0" 위치로 이동) if (isStearing ==true) { Stearing =(prevStearing * -1); gomotor(int((스티어링 * 216) / 360)); // 54단계 =회전의 1/4 Stearing =0; prev스티어링 =0; isStearing =거짓; } } 이전스티어링밀리 =현재스티어링밀리스; } // RC RF 버튼 ------------------ rfRemoteControlValue =checkRfRC(); if (rfRemoteControlValue) { switch (rfRemoteControlValue) { case RCleftbutton:// 왼쪽 RC 버튼 goleft(); 부서지다; case RCrightbutton:// 오른쪽 RC 버튼 goright(); 부서지다; case RCleft10button:// 왼쪽-10 RC 버튼 goleft10(); 부서지다; case RCright10button:// Right+10 RC 버튼 goright10(); 부서지다; } } // 버튼 ------------------------ HWButtonValue =checkHWButtons(); if (HWButtonValue) { switch (HWButtonValue) { case HWleftbutton:// 왼쪽(-1) HW 버튼 if (isSetup ==false) { goleft(); } else { setupMinus(); } 부서지다; case HWrightbutton:// Right(+1) HW 버튼 if (isSetup ==false) { goright(); } else { setupPlus(); } 부서지다; case HWpausebutton:// HW 버튼 일시 중지 gopause(); 부서지다; case HWsetupbutton:// HW 버튼 설정 gosetup(); 부서지다; case HWleft10button:// Left(-10) HW 버튼 goleft10(); 부서지다; case HWright10button:// Right(+10) HW 버튼 goright10(); 부서지다; } } // GPS NMEA ------------------ if (stringComplete ==true) { // 직렬 포트 RX로 nmea 문장을 수신함 bool ret; ret =nmeaExtractData(); 입력 문자열 =""; 문자열 완성 =거짓; if (ret ==true) { RefreshDisplay(); } } // 감시 먹이주기 ---------------- if (digitalRead(watchDogPin) ==LOW) { digitalWrite(watchDogPin, HIGH); } else { digitalWrite(watchDogPin, LOW); }}// 멀티플렉서 보이드에서 센서 읽기 readMuxSensors() { float Vo =0; 부동 소수점 n =0; 부동 소수점 n1 =0; 부동 v1ad =0; 부동 v2ad =0; 부동 오류 =0; 부동 소수점 R1 =10000; 부동 logR2 =0; 부동 소수점 R2 =0; 부동 소수점 T =0; 플로트 c1 =1.009249522e-03; float c2 =2.378405444e-04; float c3 =2.019202697e-07; digitalWrite(MuxSelBit0Pin, LOW); // 00=Vbatt digitalWrite(MuxSelBit1Pin, LOW); n =analogRead(MuxIOPin); v1ad=n; n1=(((10.00 * n) / 1023.00)); 센서VBatt=(n1 + ((n1 * 0.0) /100)); // 임의 수정(비활성 =0.0%) digitalWrite(MuxSelBit0Pin, LOW); // 01=Vres digitalWrite(MuxSelBit1Pin, HIGH); n =analogRead(MuxIOPin); v2ad=n; n1=(((10.00 * n) / 1023.00)); 센서VRes=(n1 + ((n1 * 0.0) /100)); // 임의 수정(비활성 =0.0%) digitalWrite(MuxSelBit0Pin, HIGH); // 10=NTC 온도 digitalWrite(MuxSelBit1Pin, LOW); Vo =analogRead(MuxIOPin); R2 =R1 * (1023.0 / Vo - 1.0); logR2 =log(R2); T =(1.0 / (c1 + c2 * logR2 + c3 * logR2 * logR2 * logR2)); 센서 온도 =T - 273.15; // 섭씨 n =(v1ad - v2ad); n1 =(n / 0.22) * 1000.00; SensormAmp =(((10.00 * n1) / 1023.00));}// nmea에서 데이터 추출 inputStringbool nmeaExtractData() { bool ret =false; //nmea 문장 =$GNRMC이고 유효한 CHKSUM인 경우 true if ((inputString.substring(0,6) =="$GNRMC") &&(inputString.substring(inputString.length()-4,inputString.length()- 2) ==nmea0183_checksum(inputString))) { y=0; (s =1; s <11; s ++) { y=inputString.indexOf(",",y); switch(s) { 경우 1://시간 z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_time=inputString.substring(y+1,y+2+1)+":"+inputString.substring(y+1+2,y+4+1)+" :"+inputString.substring(y+1+4,y+6+1); } y=z; 부서지다; 사례 2://유효성 z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_validity=inputString.substring(y+1,y+1+1); } y=z; 부서지다; 사례 3://위도 z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_latitude=inputString.substring(y+1,y+2+1)+""+inputString.substring(y+1+2,y+10+1)+"' "; } y=z; 부서지다; 경우 4://북쪽/남쪽 z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_latitude=nm_latitude + inputString.substring(y+1,y+1+1); } y=z; 부서지다; 사례 5://경도 z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_longitude=inputString.substring(y+1,y+3+1)+""+inputString.substring(y+1+3,y+11+1)+"' "; } y=z; 부서지다; 사례 6://동/서 z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_longitude=nm_longitude + inputString.substring(y+1,y+1+1); } y=z; 부서지다; 사례 7://속도 매듭 z=inputString.indexOf(",",y+1); if (z>(y+1)) { nmf_knots=inputString.substring(y+1,z).toFloat(); t=roundOneDec(nmf_knots); nm_knots=문자열(t,1)+"kn"; } y=z; 부서지다; 사례 8://참 코스 z=inputString.indexOf(",",y+1); if (z>(y+1)) { nmf_truecourse=inputString.substring(y+1,z).toFloat(); d=nmf_truecourse; nm_truecourse=d; } y=z; 부서지다; 사례 9://날짜 z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_date=inputString.substring(y+1,y+2+1)+"/"+inputString.substring(y+1+2,y+4+1)+" /20"+inputString.substring(y+1+4,y+6+1); } y=z; 부서지다; 사례 10:// 명령문 break; 기본값:// 명령문 중단; } } if ((isfirstfix ==true) || (ispause ==true)) { nm_routetofollow=nm_truecourse; nmf_routetofollow=nmf_truecourse; isfirstfix=거짓; } 렛=참; } return ret;}// setupvoid setupPlus() { switch (SetupParameter) { case 2://interval StearingInterval =(StearingInterval + 100); if (StearingInterval> 5000) { StearingInterval =5000; } 부서지다; 사례 3://분. 이동하려면 StearingMinToMove =(StearingMinToMove + 1); if (StearingMinToMove> 20) { StearingMinToMove =20; } 부서지다; 사례 4://최대. 이동 StearingMaxMove =(StearingMaxMove + 1); if (StearingMaxMove> 45) { StearingMaxMove =45; } 부서지다; 사례 5://계수 StearingCoeffMove =(StearingCoeffMove + 0.1); if (StearingCoeffMove> 4) { StearingCoeffMove =4; } 부서지다; } 지연(200); RefreshDisplay();}// setupvoid setupMinus() { switch (SetupParameter) { case 2://interval StearingInterval =(StearingInterval - 100); if (StearingInterval <1000) { StearingInterval =1000; } 부서지다; 사례 3://분. 이동하려면 StearingMinToMove =(StearingMinToMove - 1); if (StearingMinToMove <0) { StearingMinToMove =0; } 부서지다; 사례 4://최대. 이동 StearingMaxMove =(StearingMaxMove - 1); if (StearingMaxMove <10) { StearingMaxMove =10; } 부서지다; 사례 5://계수 StearingCoeffMove =(StearingCoeffMove - 0.1); if (StearingCoeffMove <0.1) { StearingCoeffMove =0.1; } 부서지다; } 지연(200); RefreshDisplay();}// 모터 제어(+)=앞으로(-)=backwardsvoid gomotor(int stepsToMove) { digitalWrite(motorsABenablePin, HIGH); motor.step(stepsToMove); digitalWrite(motorsABenablePin, LOW);}// refresh data on displayvoid RefreshDisplay() { if (isSetup ==false) { //---------normal lcd.clear(); lcd.setCursor(0,0); lcd.print("R"+nm_routetofollow); lcd.write(0xDF); lcd.print(" H"+nm_truecourse); lcd.write(0xDF); if (ispause ==true) { lcd.print(" STOP"); } else { if (Stearing> 0) { lcd.print(" +"); } if (Stearing ==0) { lcd.print(" "); } if (Stearing <0) { lcd.print(" "); } lcd.print(int(Stearing)); } lcd.setCursor(0,1); lcd.print(nm_time+" "+nm_knots); } if (isSetup ==true) { //-----------setup lcd.clear(); lcd.setCursor(0,0); lcd.print("setup:"); switch (SetupParameter) { case 1://display sensors readMuxSensors(); lcd.print("V="); lcd.print(SensorVBatt); lcd.setCursor(1,1); lcd.print("mA="); lcd.print(int(SensormAmp)); lcd.print(" "); lcd.print(int(SensorTemp)); lcd.write(0xDF); lcd.print("C"); break; case 2://interval lcd.print("interval"); lcd.setCursor(7,1); lcd.print(StearingInterval); lcd.print(" mSec"); break; case 3://min. to move lcd.print("minimum"); lcd.setCursor(7,1); lcd.print(StearingMinToMove); lcd.write(0xDF); break; case 4://max. move lcd.print("max"); lcd.setCursor(7,1); lcd.print(StearingMaxMove); lcd.write(0xDF); break; case 5://coefficient lcd.print("coeffic."); lcd.setCursor(7,1); lcd.print(StearingCoeffMove); lcd.print(" x "); lcd.write(0xDF); break; } }}/* SerialEvent occurs whenever a new data comes in the hardware serial RX. This routine is run between each time loop() runs, so using delay inside loop can delay response. Multiple bytes of data may be available.*/void serialEvent() { while (Serial.available()) { char inChar =(char)Serial.read(); inputString +=inChar; // if the incoming character is a newline, set a flag so the main loop can // do something about it if (inChar =='\n') { stringComplete =true; } } }//calculate checksum of nmea sentenceString nmea0183_checksum(String nmea_data) { int crc =0; String chSumString =""; int i; // ignore the first $ sign, checksum in sentence for (i =1; i <(nmea_data.length()-5); i ++) { // remove the - 5 if no "*" + cksum + cr + lf are present crc ^=nmea_data[i]; } chSumString =String(crc,HEX); if (chSumString.length()==1) { chSumString="0"+chSumString.substring(0,1); } chSumString.toUpperCase(); return chSumString;}//check RC which button is pressedint checkRfRC() { int n =0; int res =0; n =analogRead(rfRemoteControlPin); if ((n>350) and (n<460)) { // button A res =RCleftbutton; } if ((n> 90) and (n<190)) { // button B res =RCrightbutton; } if ((n>540) and (n<640)) { // button C res =RCleft10button; } if ((n>225) and (n<325)) { // button D res =RCright10button; } return res; }//check HW which button is pressedint checkHWButtons() { int n =0; int res =0; n =analogRead(ButtonsPin); //Serial.println(n); if ((n>465) and (n<565)) { // button left res =HWleftbutton; } if ((n>290) and (n<390)) { // button right res =HWrightbutton; } if ((n>130) and (n<220)) { // button pause res =HWpausebutton; } if ((n>625) and (n<725)) { // button setup res =HWsetupbutton; } if ((n>975) and (n<1075)) { // button left-10 res =HWleft10button; } if ((n>800) and (n<900)) { // button right+10 res =HWright10button; } return res; }void gosetup() { // setup button if (isSetup ==false) { SetupParameter =1; isSetup =true; } else { if (SetupParameter <5) { SetupParameter ++; } else { if (prev_StearingInterval !=StearingInterval || prev_StearingMinToMove !=StearingMinToMove || prev_StearingMaxMove !=StearingMaxMove || prev_StearingCoeffMove !=StearingCoeffMove) { lcd.clear(); lcd.setCursor(0,0); lcd.print("updating... "); 지연(1000); goupdateEEPROM(); if (EEerr) { lcd.print("E="); lcd.print(EEerr); 지연(1000); } prev_StearingInterval =StearingInterval; prev_StearingMinToMove =StearingMinToMove; prev_StearingMaxMove =StearingMaxMove; prev_StearingCoeffMove =StearingCoeffMove; } isSetup =false; } } NewTone (speakerPin,2000); delay(200); noNewTone(); RefreshDisplay();}void goupdateEEPROM() { EEaddress =0; //id1 EEdata[0] =EEid1; EEbytedata =EEid1; writeEEPROM (EEdisk, EEaddress, EEbytedata); EEaddress =1; //id2 EEdata[1] =EEid2; EEbytedata =EEid2; writeEEPROM (EEdisk, EEaddress, EEbytedata); memcpy(bStearingInterval, &StearingInterval, sizeof(int)); memcpy(bStearingMinToMove, &StearingMinToMove, sizeof(int)); memcpy(bStearingMaxMove, &StearingMaxMove, sizeof(int)); memcpy(bStearingCoeffMove, &StearingCoeffMove, sizeof(float)); memcpy(EEdata+2,bStearingInterval,sizeof(int)); memcpy(EEdata+4,bStearingMinToMove,sizeof(int)); memcpy(EEdata+6,bStearingMaxMove,sizeof(int)); memcpy(EEdata+8,bStearingCoeffMove,sizeof(float)); for (s =2; s  360) { nmf_routetofollow =1; } d=nmf_routetofollow; nmf_routetofollow=d; nm_routetofollow=d; NewTone (speakerPin,800); delay(200); noNewTone(); } else { NewTone (speakerPin,1000); delay(50); noNewTone(); } RefreshDisplay();}void goright10() { // right 10x button/RC if (ispause ==false) { for (s =1; s <11; s ++) { nmf_routetofollow ++; if (nmf_routetofollow> 360) { nmf_routetofollow =1; } } d=nmf_routetofollow; nmf_routetofollow=d; nm_routetofollow=d; NewTone (speakerPin,800); delay(200); noNewTone(); } else { NewTone (speakerPin,1000); delay(50); noNewTone(); } RefreshDisplay();}void gopause() { // pause button/RC if (ispause ==true) { ispause=false; digitalWrite(ledpausePin, HIGH); NewTone (speakerPin,50); delay(200); NewTone (speakerPin,200); delay(800); noNewTone(); } else { ispause=true; digitalWrite(ledpausePin, LOW); NewTone (speakerPin,200); delay(200); NewTone (speakerPin,50); delay(800); noNewTone(); } RefreshDisplay();}// reading eeprombyte readEEPROM (int diskaddress, unsigned int memaddress) { byte rdata =0x00; Wire.beginTransmission (diskaddress); Wire.write (memaddress); if (Wire.endTransmission () ==0) { Wire.requestFrom (diskaddress,1); if (Wire.available()) { rdata =Wire.read(); } else { EEerr =1; //"READ no data available" } } else { EEerr =2; //"READ eTX error" } Wire.endTransmission (true); return rdata;}// writing eepromvoid writeEEPROM (int diskaddress, unsigned int memaddress, byte bytedata) { Wire.beginTransmission (diskaddress); Wire.write (memaddress); Wire.write (bytedata); if (Wire.endTransmission () !=0) { EEerr =3; //"WRITING eTX error" } Wire.endTransmission (true); delay(5); }// round zero decimalfloat roundZeroDec(float f) { float y, d; y =f*1; d =y - (int)y; y =(float)(int)(f*1)/1; if (d>=0.5) { y +=1; } else { if (d <-0.5) { y -=1; } } return y;}// round one decimalfloat roundOneDec(float f) { float y, d; y =f*10; d =y - (int)y; y =(float)(int)(f*10)/10; if (d>=0.5) { y +=0.1; } else { if (d <-0.5) { y -=0.1; } } return y;}// round two decimalfloat roundTwoDec(float f) { float y, d; y =f*100; d =y - (int)y; y =(float)(int)(f*100)/100; if (d>=0.5) { y +=0.01; } else { if (d <-0.5) { y -=0.01; } } return y;}
    WatchDog sketch (for Nano)Arduino
    /* * This sketch is a Watchdog to keep CLIENT under control, on Arduino NANO 3.0 by Marco Zonca * CLIENT must feed Whatcdog sooner then feedingInterval otherwise will be forced to restart * */const int feedingPin =14;const int ledPin =15;const int restartPin =16;const int buzzerPin =17;const long ledInterval =1000;const long feedingInterval =2500;const long timeForClientStart =16000;int ledState =LOW;int previousFeedingState =LOW;int feedingState =LOW;unsigned long previousLedMillis =0;unsigned long previousFeedingMillis =0;void setup() { digitalWrite(restartPin, HIGH); // LOW will force CLIENT to restart pinMode(ledPin, OUTPUT); pinMode(buzzerPin, OUTPUT); pinMode(restartPin, OUTPUT); pinMode(feedingPin, INPUT); delay(timeForClientStart); // let time to CLIENT to start...}void loop() { unsigned long currentMillis =millis(); // BLINK LED ------------------- if (currentMillis - previousLedMillis>=ledInterval) { previousLedMillis =currentMillis; if (ledState ==LOW) { ledState =HIGH; } else { ledState =LOW; } digitalWrite(ledPin, ledState); } // CHECK THE FEEDING ------------------- feedingState =digitalRead(feedingPin); // CLIENT must set pin HIGH -> LOW frequently to prove it's alive if (feedingState ==HIGH) { if (previousFeedingState ==LOW) { previousFeedingMillis =currentMillis; } previousFeedingState =HIGH; } else { previousFeedingState =LOW; } if (currentMillis - previousFeedingMillis> feedingInterval) { // CLIENT is sleeping ledState =HIGH; digitalWrite(ledPin, ledState); tone(buzzerPin,1500); delay(500); digitalWrite(restartPin, LOW); //restart CLIENT tone(buzzerPin,1500); delay(500); digitalWrite(restartPin, HIGH); tone(buzzerPin,1500); delay(timeForClientStart); // let CLIENT time to restart... noTone(buzzerPin); currentMillis =millis(); previousFeedingState =LOW; previousFeedingMillis =currentMillis; previousLedMillis =currentMillis; }}

    맞춤형 부품 및 인클로저

    23lm-stepper-plate-v2_PlvJaff9Hl.step 23lm-stepper-pulley-56_UhsbaWbiBt.step

    회로도


    제조공정

    1. 라즈베리 파이용 DIY 적외선 모션 센서 시스템
    2. 드론을 위한 탄도 낙하산 회수 시스템 구축
    3. 지속적인 모니터링 시스템이 귀하에게 적합합니까?
    4. 로봇용 자동 공구 교환기
    5. 시스템이 IoT를 사용할 준비가 되었습니까?
    6. 자동 선반용 폴리곤 커터
    7. 정밀 부품용 CNC 자동 선반
    8. 극초음속 비행을 위한 추진 시스템
    9. 수동 및 자동 변속기 시스템의 작동 원리
    10. 자동변속기의 이해