본문 바로가기
전기자전거

자전거 라이트 만들기: Arduino로 방향 지시등과 주행 거리계 기능 추가하기

by 다이올 2024. 9. 13.

안녕하세요! 저는 Giovanni Aggiustatutto입니다. 오늘은 Arduino를 사용하여 자전거에 설치할 수 있는 스마트 라이트를 만드는 방법을 소개하려고 합니다. 이 라이트는 방향 지시등, 속도, 시간, 총 경로를 보여주는 주행 거리계 기능을 갖추고 있어요. 특히, 작은 마을에 살고 있는 저에게는 밤에 자전거를 탈 때 안전성을 높여주는 중요한 장치입니다.

프로젝트 개요

이 프로젝트는 다음과 같은 기능을 포함합니다:

  • 화이트 & 레드 LED 헤드라이트: 밝고 선명하게 도로를 비춥니다.
  • 노란색 방향 지시등: 주변 사람들에게 내가 가고 있는 방향을 알립니다.
  • 디스플레이: 속도, 시간, 총 킬로미터 및 기타 유용한 정보를 표시합니다.
  • 버튼 제어: 핸들바에 장착된 버튼으로 조명을 켜고 끌 수 있습니다.
  • USB-C 충전 배터리: 언제든지 쉽게 충전할 수 있습니다.
  • 도난 방지 시스템: 모든 전자 장치를 숨길 수 있는 디자인입니다.

필요한 재료

  1. Arduino 보드 (예: Arduino Uno)
  2. 화이트, 레드, 노란색 LED
  3. 저항 (220Ω)
  4. 디스플레이 모듈 (예: OLED)
  5. 버튼 스위치
  6. 배터리 (USB-C 충전 가능)
  7. 브레드보드 및 점퍼 와이어
  8. 자전거 장착용 브래킷

Arduino를 사용하여 방향을 나타내는 방향 지시등과 속도, 시간 및 총 경로를 보여주는 주행 거리계가있는 자전거 라이트를 만드는 방법을 보여 드리겠습니다. 사실, 작은 마을에 살고 있기 때문에 거의 항상 자전거로 이동하는데, 헤드라이트가 선명하게 보이기 때문에 특히 밤에 자전거를 타는 것이 훨씬 더 안전합니다. 내 디자인에는 흰색 또는 빨간색 조명 외에도 주변 사람들에게 우리가 어느 방향으로 가고 있는지 알려주는 노란색 표시기가 있는 헤드라이트가 포함됩니다. 화살표를 활성화하기 위해 핸들 바에 버튼이 있으며,이를 사용하여 디스플레이의 정보를 이동하고 조명을 켜거나 끌 수도 있습니다. 버튼 옆에는 우리가 가고 있는 속도, 시간, 총 킬로미터 및 라이딩에 대한 기타 흥미로운 정보를 보여주는 디스플레이가 있습니다. 헤드 라이트는 매우 밝으며 낮과 밤에 모두 잘 볼 수 있습니다. 전체 시스템은 Arduino에 의해 제어되며 USB-C 포트로 충전할 수 있는 배터리로 전원이 공급됩니다. 또한 시스템 도난을 방지하기 위해 자전거의 모든 전자 장치를 숨길 수 있는 방법을 찾았습니다.

 

공급

이 프로젝트를 만들기 위해 다음을 사용했습니다.

  • 아두이노 나노 보드(여기 링크)
  • 0.96인치 128x64 흑백 OLED 디스플레이(i2c 통신 포함) (여기 링크))
  • 3개의 12x12mm 촉각 스위치(여기 링크))
  • US5881 홀 효과 센서 2개(1개는 옵션, 여기 링크))
  • DS3231 실시간 클록 모듈(여기 링크))
  • IRFZ44N MOSFET 4개
  • 18650 리튬 이온 배터리 셀
  • TP4056 USB-C 충전기 보드(여기 링크)
  • 5V 500mA(또는 그 이상) 부스트 스텝업 모듈
  • 로커 스위치
  • ~ 5미터 길이의 26AWG 4심 케이블(여기 링크))
  • 퍼프보드
  • 4 220옴 저항기
  • 6 10K 옴 저항기
  • 2 2.2K 옴 저항기
  • 1 100K 옴 저항기
  • 1 20K 옴 저항기
  • 15개의 선명한 빨간색 5mm LED
  • 40개의 투명 노란색 5mm LED
  • 6개의 선명한 흰색 5mm LED
  • 27 47옴 저항기
  • 6 130옴 저항기
  • 1 330옴 저항기
  • 5개의 4핀 JST XH 2.54mm 커넥터(수 및 암, 링크 클릭))
  • 2 3핀 JST XH 2.54 커넥터(수 및 암, 링크 바로가기))
  • 맑은 빨간색 3mm LED 1개 및 투명한 파란색 3mm LED 1개
  • 2 M3 황동 나사산 인서트(여기 링크))
  • M3 12mm 길이 볼트(여기 링크))
  • M3 20mm 길이 볼트(여기 링크))
  • M3 자동 잠금 너트(여기 링크)
  • 다양한 크기의 열수축 튜브
  • 자전거 물병

이 프로젝트에 사용한 도구 :

  • 검은색 PLA 필라멘트가 있는 3D 프린터
  • 납땜
  • 뜨거운 접착제
  • 스크루드라이버, 펜치 및 기타 기본 도구

1단계: 개요

이 프로젝트는 여러 부분으로 나뉩니다. 먼저 전면과 후면에 두 개의 헤드라이트가 있으며, 기존의 빨간색과 흰색 조명 외에도 방향을 나타내는 두 개의 방향 지시등이 있습니다. 핸들 바에는 우리가 가고 있는 속도, 시간, 총 킬로미터 및 우리의 라이딩에 대한 기타 흥미로운 정보를 보여주는 디스플레이가 있기 때문에 상황이 더 흥미로워집니다. 디스플레이 옆에는 화살표를 활성화하고 디스플레이의 페이지를 변경하고 헤드 라이트를 켜거나 끌 수있는 세 개의 버튼이 있습니다. 마지막으로 속도를 계산하고 디스플레이에 표시하기 위해 자전거 바퀴의 회전을 감지하는 자기 센서가 있습니다. 이러한 모든 구성 요소는 Arduino, 실시간 클록 모듈 및 헤드라이트의 LED를 제어하는 데 사용되는 4개의 MOSFET이 포함된 회로에 연결됩니다. 전체 시스템은 USB-C 포트를 통해 충전되는 리튬 이온 배터리로 전원이 공급됩니다. 궁금하시다면... 예, 모든 전자 장치는 자전거에 장착된 물병에 숨겨져 있지만 나중에 자세히 설명합니다.

이 모든 것을 함께 디자인하는 것은 너무 복잡했기 때문에 가장 단순해 보이는 부분인 후미등부터 다소 무작위로 시작했습니다.

2단계: 후미등

후미등을 만들기 위해 후미등에는 15개의 선명한 빨간색 5mm LED를 사용하고 방향 지시등에는 28개의 선명한 노란색 5mm LED를 사용했습니다(각 지시등당 14개). 후미등은 낮에도 선명하게 보이는 것이 중요했기 때문에 LED를 너무 많이 사용했는지 알 수 있습니다.

먼저 헤드라이트용 하우징과 LED를 장착할 구멍이 있는 플레이트를 설계하고 3D 프린팅했습니다(3D 프린팅용 STL 파일은 아래에서 찾을 수 있습니다). 뒷면에는 구멍이 있는 두 개의 스페이서가 있으며, 여기에 두 개의 M3 황동 나사산 인서트를 넣고 납땜 인두를 사용하여 가열합니다. 이 나사산 인서트는 나중에 LED를 인클로저에 고정하는 데 사용됩니다.

이제 LED를 장착하고 연결해야 합니다. LED는 마운팅 플레이트의 구멍에 삽입할 수 있으며 중앙에는 빨간색 LED, 측면에는 노란색 LED를 사용합니다. LED는 두 개의 직렬로 연결되며 각 시리즈에는 47옴 저항이 있습니다. 그런 다음 두 개의 모든 시리즈가 병렬로 연결되어 중앙 빨간색과 좌회전 및 우회전 신호에 대해 세 그룹으로 분리됩니다. 3개의 LED 그룹은 +5V 핀을 공유하고, 3개의 접지는 빨간색 표시등과 2개의 노란색 표시기를 별도로 제어하는 데 사용됩니다. 연결을 만들기 위해 LED와 저항의 핀을 구부리고 납땜하기만 하면 됩니다. 이 기술을 사용하면 회로가 매우 복잡해 보일 수 있지만 실제로는 여러 번 반복되는 간단한 연결일 뿐입니다. 어쨌든 아래에서 연결하는 동안 따를 수 있는 회로도를 찾을 수 있습니다(백라이트는 4페이지에 있음). LED를 두 개로 직렬로 연결하면 15개의 LED가 있는 중앙 빨간색 조명에 하나의 추가 LED가 있다는 점에 유의하는 것이 중요합니다. 이 문제를 해결하기 위해 330 Ohm 저항을 양극 핀에 연결하고 다른 빨간색 LED와 병렬로 연결했습니다.

그런 다음 후면 조명을 제어 회로에 연결하는 데 사용할 4핀 케이블을 인클로저에 삽입했습니다. 한 전선을 공통 양극에 납땜하고 다른 세 전선을 세 LED 그룹의 음극에 납땜했습니다. 조명을 닫기 전에 모든 LED가 올바르게 작동하는지 확인하기 위해 벤치 전원 공급 장치로 테스트를 수행했습니다. 그런 다음 전선 사이의 단락을 방지하기 위해 LED 뒷면의 연결부를 뜨거운 접착제로 덮었습니다.

라이트 하우징에서 볼트 머리를 맞을 수 있는 육각형 공간이 있는 인클로저 뒷면의 구멍에 두 개의 M5 육각 볼트를 삽입했습니다. 이 볼트는 후미등을 자전거에 부착하는 데 사용됩니다. 마지막으로 두 개의 나사산 인서트에 두 개의 M3 볼트를 나사로 고정하여 조명을 닫았습니다.

케이블의 다른 쪽 끝에는 조명을 주 회로 기판에 연결하는 데 사용할 4핀 JST 커넥터를 연결했습니다.

 

Schematics.pdf
0.44MB
Back+light+LED+panel.stl
1.79MB
Back+light+enclosure.stl
0.18MB

3단계: 디스플레이

후미등이 완성되면 자전거 핸들 바에 표시 될 디스플레이를 관리하여 속도 및 총 경로와 같은 흥미로운 정보를 볼 수 있습니다. 제가 선택한 디스플레이는 i2c 통신이 가능한 0.96인치 128x64픽셀 흑백 OLED 화면입니다. i2c 통신을 사용하면 5V 전원(+5V 및 GND)용 2개, 데이터(SDA) 및 클럭(SCL)용 2개, 총 4개의 전선만 사용하여 디스플레이를 Arduino에 직접 연결할 수 있습니다. 디스플레이를 더 컴팩트하게 만들기 위해 PCB에 납땜된 점퍼 헤더를 제거했습니다. 디스플레이를 Arduino에 연결하려면 후미등에 사용한 것과 동일한 4핀 케이블을 사용합니다. 이번에는 연결이 매우 간단합니다: 디스플레이 PCB에 있는 +5V, GND, SDA, SCL용 4개의 패드에 케이블의 4개 와이어를 납땜해야 했습니다.

핸들바에 디스플레이를 장착하기 위해 핸들바에 고정할 수 있는 부품과 디스플레이를 장착할 작은 인클로저를 설계하고 3D 프린팅했습니다. 나는 디스플레이를 인클로저에 붙이고 개구부에 조심스럽게 정렬했습니다. 그런 다음 베이스의 구멍에서 케이블을 빼냈습니다. 디스플레이가 있는 인클로저는 3D 프린팅 부품에 설계된 스냅 핏 덕분에 베이스에 고정할 수 있습니다. 마지막으로 마운트의 개구부에 두 개의 M3 자동 잠금 너트를 삽입했는데, 이 너트는 핸들바의 디스플레이를 나사로 고정하는 데 사용됩니다.

후미등에서와 마찬가지로 케이블의 다른 쪽 끝에 디스플레이를 주 회로 기판에 연결하는 데 사용할 4핀 JST 커넥터를 연결했습니다.

Display+lid.stl
0.18MB
Display+base.stl
0.25MB
Display+enclosure.stl
0.19MB
Bracket.stl
0.16MB

Schematics (1).pdf
0.44MB

4 단계 : 버튼

디스플레이가 완성되면 방향 지시등을 활성화하고 디스플레이를 제어하는 데 사용할 버튼에 대해 생각했습니다. 우리는 세 개의 버튼을 가질 것입니다 : 두 개는 좌회전 및 우회전 신호용이고 다른 하나는 디스플레이의 페이지를 탐색하는 데 사용할 수 있는 다른 버튼으로, 내 프로젝트에서 "SET"으로 레이블을 지정했습니다. 다양한 유형의 버튼을 살펴본 후 매우 작고 저렴하기 때문에 12x12mm 촉각 스위치를 사용하기로 결정했습니다.

버튼을 자전거에 고정하기 위해 자전거에 고정할 수 있는 부품과 인클로저로 구성된 디스플레이 중 하나와 매우 유사한 마운트를 3D 프린팅했습니다. 3D 프린팅 마운트에 버튼을 끼우기 전에 핀을 곧게 펴고 양쪽 중 하나에서 같은 높이로 자릅니다(한쪽의 핀은 다른 쪽에 미러링됨). 그런 다음 3D 프린팅 마운트에 버튼을 넣고 양면 테이프로 임시로 누르고 있습니다.

또한이 부분의 경우 연결이 매우 간단합니다. 각 버튼은 공통 GND와 Arduino의 핀 중 하나 사이에 연결됩니다. 다른 부품과 마찬가지로 버튼을 Arduino에 연결하기 위해 4핀 케이블을 사용했습니다. 사진에서 볼 수 있듯이 4 개의 와이어 중 하나를 스위치의 공통 접지에 납땜하고 다른 3 개는 각 버튼의 여유 핀에 납땜했습니다. 연결을 확인한 후 뜨거운 접착제로 버튼을 마운트에 고정했습니다. 그런 다음 회색 PLA를 사용하여 세 개의 버튼 각각에 삽입한 작은 캡을 3D 프린팅했습니다. 마지막으로 3D 프린팅 부품에 설계된 스냅 핏으로 인클로저를 마운트에 장착했습니다. 디스플레이와 마찬가지로 마운트의 개구부에 두 개의 M3 자동 잠금 너트를 삽입했는데, 이 너트는 핸들바의 디스플레이를 나사로 고정하는 데 사용됩니다.

다른 부품과 마찬가지로 케이블의 다른 쪽 끝에는 조명을 주 회로 기판에 연결하는 데 사용할 4핀 JST 커넥터를 연결했습니다.

 

Schematics (2).pdf
0.44MB
Buttons+enclosure.stl
0.21MB
Buttons+base.stl
0.25MB
Bracket (1).stl
0.16MB
Button+cap.stl
0.09MB

 

5 단계 : 전면 조명

이제 후면 조명과 매우 유사한 방식으로 제작된 전면 조명을 만들 차례였습니다. 실제로 전면 조명은 두 개의 별도 조명으로 나뉩니다. 이들은 디스플레이와 버튼에 사용 된 것과 동일한 시스템으로 자전거 핸들 바에 장착됩니다. 두 개의 조명 각각에는 주 조명에 대한 3개의 흰색 LED와 왼쪽 또는 오른쪽 표시기에 대한 6개의 노란색 LED가 있습니다.

다시 말하지만, 나는 3D 프린팅으로 일부 조각을 디자인했습니다. 리어 라이트와 마찬가지로 두 개의 전면 조명 각각에는 자전거 핸들바에 고정할 수 있는 마운트(디스플레이 및 버튼에 사용된 것과 동일한 개념)와 LED를 삽입할 수 있는 구멍이 있는 패널이 포함된 인클로저가 있습니다. 먼저 패널의 구멍에 LED를 삽입하여 흰색 LED 한 열과 노란색 LED 두 열을 만들었습니다. 그런 다음 연결을 만들 때였습니다. 노란색 LED는 각 시리즈에 대해 47옴 저항과 함께 2개로 직렬로 연결됩니다. 그런 다음 3개의 노란색 LED 시리즈가 후미등에서와 같이 병렬로 연결됩니다. 반면에 백색 LED는 더 높은 전압이 필요하므로 각각에 130옴 저항을 넣은 다음 세 개를 모두 병렬로 연결했습니다. 연결을 만들기 위해 LED와 저항의 핀을 구부리고 납땜하기만 하면 됩니다. 두 개의 헤드라이트를 만들어야 하기 때문에 LED 색상이 미러링된 두 개의 동일한 회로를 만들었습니다(최종 어셈블리에서 흰색 LED는 내부에 있고 노란색 LED는 외부에 있음).

또한 전면 조명의 경우 연결에 4핀 케이블을 사용했습니다. 두 조명의 3D 프린팅 인클로저는 거울에 비친 것처럼 보이지만 그 중 하나에는 케이블용 구멍이 두 개 있고 다른 하나에는 하나만 있다는 점에 유의하는 것이 중요합니다. 전면 조명을 메인 컨트롤러에 연결할 케이블을 두 개의 구멍이 있는 인클로저에 삽입했습니다. 짧은 케이블을 사용하여 첫 번째 인클로저를 두 번째 인클로저에 연결했습니다. 연결은 매우 간단합니다. 후미등의 경우 모든 LED는 공통 양극 연결을 공유하는 반면 음극은 개별 LED 그룹을 제어하는 데 사용됩니다. 그래서 주 회로에서 나오는 +5V 와이어를 첫 번째 조명의 LED 양극과 두 번째 조명으로 가는 와이어 중 하나에 납땜한 다음 LED의 공통 양극에도 납땜했습니다. 백색 LED의 GND도 마찬가지이며, 이는 두 전면 조명에 공통적입니다. 우회전 신호의 GND는 첫 번째 조명의 노란색 LED의 음극에 직접 납땜됩니다. 왼쪽 표시기의 GND는 먼저 두 번째 표시등으로 가는 케이블의 전선 중 하나에 납땜된 다음 노란색 LED의 음극에 납땜됩니다.

LED를 테스트 한 후 뜨거운 접착제로 연결을 보호했습니다. 마지막으로 두 LED 패널을 인클로저에 스냅 장착했습니다. 디스플레이와 버튼에 대해 그랬던 것처럼 두 개의 M3 자동 잠금 너트를 각 라이트 마운트의 개구부에 삽입했으며, 나중에 나사로 핸들바의 디스플레이를 고정하는 데 사용할 것입니다.

또한 여기에서 케이블의 다른 쪽 끝에는 조명을 주 회로 기판에 연결하는 데 사용할 4핀 JST 커넥터를 연결했습니다.

Schematics.pdf
0.44MB
Right+front+light+enclosure.stl
0.27MB
Front+light+LED+support+(same+part+for+left+and+right+light).stl
0.36MB
Left+front+light+enclosure.stl
0.26MB
Bracket+(same+part+for+left+and+right+light).stl
0.16MB

6단계: 속도 센서

앞서 말했듯이 디스플레이에서 우리가 타는 속도도 볼 수 있습니다. 속도를 측정하기 위해 GPS와 같은 더 복잡한 것들을 버리고 가장 간단한(그리고 아마도 가장 정확한) 시스템, 즉 자전거 뒷바퀴 근처에 장착된 자기 센서를 선택했습니다. 이 센서는 바퀴 자체에 부착된 자석을 감지합니다. 이렇게 하면 휠이 회전할 때마다 센서가 트리거됩니다. 정의된 시간 간격(제 경우에는 5초)에 센서가 트리거되는 횟수를 읽음으로써 Arduino는 휠의 분당 회전수를 계산한 다음 휠의 둘레를 알고 이를 시간당 킬로미터 단위의 속도로 변환할 수 있습니다.

제가 선택한 센서는 US5881 홀 효과 센서입니다. 이 센서에는 3개의 핀, +5V 및 전원용 접지 및 출력 핀이 있습니다. 센서를 Arduino에 연결하기 위해 다른 구성 요소에 사용한 것과 동일한 4핀 케이블을 사용했습니다. 4개의 와이어 중 3개를 센서의 3개의 핀에 납땜했는데 물론 1개의 와이어는 비워 두었습니다. 그런 다음 열수축 튜브로 센서와 개별 연결부를 보호했습니다. 케이블의 다른 쪽 끝에는 조명을 주 회로 기판에 연결하는 데 사용할 fthree-pin JST 커넥터를 연결했습니다.

센서를 자전거에 장착하기 위해 작은 3D 프린팅 스탠드를 만들었습니다. 센서 와이어를 마운트의 구멍에 삽입한 다음 두 개의 지퍼 타이를 사용하여 센서 자체를 고정할 수 있습니다. 센서가 있는 마운트는 지퍼 타이를 사용하여 자전거 바퀴 중 하나 근처에 고정됩니다.

센서를 작동시키기 위해 나는 이미 자전거 바퀴에 고정해 놓은 자석을 사용했는데, 이 자석은 오래된 자석으로 구동되는 자전거 라이트에서 회수되었습니다. 그러나 약 1cm 거리에서 센서를 안정적으로 트리거할 수 있을 만큼 강한 자석을 사용하여 휠에 고정할 수 있습니다.

Schematics (1).pdf
0.44MB
Wheel+sensor+bracket.stl
0.09MB

7단계: 제어 보드 - Arduino 및 RTC

이제 시스템의 모든 구성 요소가 있으므로 연결할 Arduino로 보드를 만들어야 합니다. 그것을 만들기 위해 약 55x90mm의 퍼프 보드 조각으로 시작하여 점퍼 헤더를 사용하여 Arduino Nano를 납땜했습니다. Arduino 외에도 DS3231 Real Time Clock 모듈을 보드에 배치했는데, 이는 Arduino가 꺼져 있어도 디스플레이에 표시되는 시간을 유지하는 데 사용됩니다. 이를 위해 일반적으로 RTC는 3V 코인 셀에 의해 전원이 공급되며, 이는 낮은 전력 소비로 몇 년 동안 지속될 수 있습니다. 이 프로젝트에서는 전체 시스템에 전원을 공급할 충전식 리튬 이온 배터리가 이미 있을 것이며 주기적으로 교체해야 하는 두 번째 배터리를 갖는 것은 의미가 없습니다. 그래서 주 배터리에서 직접 전원을 공급하기 위해 RTC 모듈을 수정하기로 결정했습니다. 가장 먼저 한 일은 PCB의 SCL 핀 위에 있는 작은 저항을 제거하는 것이었습니다: 이 저항은 일반적으로 RTC에 넣는 코인 셀을 천천히 충전하는 데 사용되며, 이는 외부 배터리로는 원하지 않는 것입니다. 그런 다음 공간을 절약하기 위해 코인 셀 홀더를 납땜 제거했습니다. 마지막으로 코인 셀의 양극이 연결된 패드에 와이어 조각을 납땜했습니다 (제 경우에는 상태 LED 근처에 있었지만 멀티 미터로 확인하는 것이 좋습니다). 이 와이어는 주 18650 리튬 이온 배터리의 양극에 연결하기만 하면 됩니다.

 

Schematics (2).pdf
0.44MB

8단계: 제어 보드 - MOSFET, I2C 및 전압 분배기

RTC가 설치되면 디스플레이와 버튼용 4핀 JST 커넥터 2개, 휠 회전 센서용 JST 커넥터 2개, 브레이크를 밟을 때 감지하는 또 다른 자기 홀 효과 센서를 보드에 납땜했는데 결국 설치하지 않았습니다. 관심이 있다면 이 마지막 자기 센서는 브레이크를 밟을 때 후미등의 밝기를 높이는 데 사용되었을 것입니다. 두 개의 자기 센서의 경우 신호 핀과 +5V 전원 사이에 10K Ohm 풀업 저항을 넣었습니다. 이러한 저항을 배치하는 가장 좋은 장소는 커넥터 근처입니다.

Arduino 핀의 헤드 라이트 LED를 제어하기 위해 IRFZ44N 개의 mosfet을 사용했는데, 좌우 방향 지시등에 2 개, 전방 및 후방 조명 용 2 개였습니다. 각 MOSFET에 대해 게이트와 GND 사이에 10K Ohm 저항을 배치했습니다. 각 MOSFET의 게이트는 그 사이에 220옴 저항이 있는 하나의 Arduino 핀에 연결됩니다. 후면 및 전면 조명을 연결하기 위해 두 개의 4핀 JST 커넥터를 보드에 납땜하여 +5V 전원과 4개의 MOSFET 드레인에서 나오는 다른 조명의 접지를 가져왔습니다.

디스플레이 및 RTC 모듈은 핀 A2 및 A4를 사용하여 i5c를 통해 Arduino에 연결됩니다. 두 i2c 라인에는 각 i2c 라인과 +5V 전원 사이에 연결된 2.2K 옴 풀업 저항을 넣었습니다.

배터리 전압을 측정하고 디스플레이에 표시하기 위해 Arduino의 아날로그 핀을 사용하여 배터리 전압을 Arduino의 1.1V 내부 기준 값보다 낮은 값으로 가져오기 위해 두 개의 저항(100K Ohm 1개, 20K Ohm 1개)으로 구성된 전압 분배기를 연결했습니다.

모든 구성 요소가 보드에 납땜되면 아래에서 찾을 수 있는 배선도에 따라 보드 아래에 연결했습니다. 연결을 만들기 위해 단선 구리선과 실리콘 피복이 있는 전선을 조합하여 사용했습니다. 이 작업은 분명히 시간이 좀 걸렸지만 마침내 보드를 완성할 수 있었습니다. 원하는 경우 회로도에 따라 제조할 PCB를 직접 설계할 수 있습니다.

 

Schematics (3).pdf
0.44MB

 

9단계: 아두이노 코드

시스템을 설치하기 전에 Arduino에 코드를 업로드해야 합니다. 코드 작성은 아마도 이 프로젝트에서 가장 길고 복잡한 부분이었을 것입니다. 확실히 코드는 잘 작성되지 않았지만 작동하며 그것이 중요한 것입니다. 이 코드는 속도 측정, RTC에서 시간 읽기, 조명 및 방향 지시등 제어, 디스플레이에 인터페이스 표시를 담당합니다. 많은 일이 일어나고 있습니다. 좋은 소식은 원하지 않는 경우 코드를 수정할 필요가 없다는 것입니다: 모든 설정은 디스플레이의 인터페이스에서 직접 수행할 수 있습니다. 물론 이러한 설정은 EEPROM에 저장되므로 시스템 전원이 꺼진 경우에도 유지됩니다.

코드를 업로드하기 전에 보드에서 Arduino의 플러그를 뽑는 것이 좋습니다. 코드는 Adafruit_SSD1306 라이브러리와 RTClib 라이브러리를 설치한 후 Arduino IDE에서 컴파일하고 업로드할 수 있습니다.

아래에서 다운로드할 코드를 찾을 수 있습니다. 원하는 경우 여기에서 직접 복사하여 Arduino IDE에 붙여넣을 수 있습니다.

 

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
//#include <Adafruit_SH1106.h>
#include "RTClib.h"
#include <EEPROM.h>


#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels


#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
//#define OLED_RESET     12 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
//#define i2c_Address 0x3c
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
//Adafruit_SH1106 display = Adafruit_SH1106(OLED_RESET);


RTC_DS3231 rtc;


float wheelCircumference = 2.00; //meters (can be set from the settings page of the device)
const float speedUpdateInterval = 5000; //calculate speed every N milliseconds


const long turnSignalBlink = 500; //speed at which the turn signals blink in milliseconds
const int turnSignalMaxCycles = 12; //number of turn signal blinks before they turn off automatically


const int wheelSensor = 2; //pin to which the wheel hall effect sensor is connected


const int brakeSensor = 7; //pin to which the brake hall effect sensor is connected
const bool invertBrakeSensor = false; //true: brake detected when the magnet is far from the sensor


const int LEFTbutton = 3; //pins to which the three buttons are connected
const int RIGHTbutton = 4;
const int SETbutton = 5;


const int LEFTled = 9; //pins to which the lights are connected
const int RIGHTled = 10;
const int REDled = 11;
const int WHITEled = 6;


float totalKm = 0; //variable that stores total distance
float speed = 0; //variable that stores current speed
float maxSpeed = 0; //variable that stores maximum speed
float avgSpeed = 0; //variable that stores average speed
long activeTime = 0; //variable that stores active time


float speedMillis = 0; //milliseconds from the last time speed was calculated


bool frontLights = false; //state of front lights
bool backLights = true; //state of back lights
bool brakeLights = false; //state of brake lights
bool left = false; //state of left turn signal
bool right = false; //state of right turn signal
bool turnSignalState = true;
bool lastLeftState = true;
bool lastRightState = true;


bool leftReleased = true;
bool rightReleased = true;
bool setReleased = true;


int lightsCycle = 0;
int turnSignalCycle = 0;
long previousMillis = 0;
long releaseMillis = 0;
long lightsMillis = 0;


int displayPage = 0; //0: home screen - 1: info screen - 2: control screen
int menuSelection = 0;
bool pageUpdated = false;


int batteryState = 0;
const int batteryReadingDeadband = 10;


int pulses = 0;


void setup() {
  pinMode(wheelSensor, INPUT);
  pinMode(brakeSensor, INPUT);
  pinMode(LEFTbutton, INPUT_PULLUP);
  pinMode(RIGHTbutton, INPUT_PULLUP);
  pinMode(SETbutton, INPUT_PULLUP);
  pinMode(LEFTled,  OUTPUT);
  pinMode(RIGHTled, OUTPUT);
  pinMode(REDled, OUTPUT);
  pinMode(WHITEled, OUTPUT);


  analogReference(INTERNAL);


  //Serial.begin(9600);


  display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
  //display.begin(SH1106_SWITCHCAPVCC, 0x3C);


  rtc.begin();


  //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));


  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);


  for(int i = -20; i <= 24; i++) {
    display.fillRect(0, 0, 128, 64, BLACK);
    display.setCursor(28, i);
    display.print("Hello!");
    display.display();
    delay(2);
  }
  delay(500);


  display.clearDisplay();
  delay(200);

  bool settings = false;
  bool settingsChanged = false;
  int settingsPage = 0;
  int selection = 0;


  DateTime now = rtc.now();
  int h = now.hour();
  int m = now.minute();


  EEPROM.get(0, wheelCircumference);


  //enter settings when the SET button is pressed during startup
  if(digitalRead(SETbutton) == 0 || isnan(wheelCircumference)) {
    setReleased = false;
    delay(100);
    display.clearDisplay();
    settings = true;
    //remain into the settings
    while(settings == true) {
      //detect if the SET button has been released after being pressed
      if(digitalRead(SETbutton) == 1 && setReleased == false) {
        setReleased = true;
        delay(100);
      }


      //main settings menu
      if(settingsPage == 0) {
        display.fillRect(0, 0, 128, 64, BLACK);


        //move between the settings using the LEFT and RIGHT buttons
        if(digitalRead(RIGHTbutton) == 0) {
          selection++;
          if(selection > 3) selection = 0;
          delay(200);
        }


        if(digitalRead(LEFTbutton) == 0) {
          selection--;
          if(selection < 0) selection = 3;
          delay(200);
        }


        display.setTextColor(WHITE);
        display.setTextSize(2);
        display.setCursor(15, 0);
        display.print("Settings");


        display.drawLine(0, 28, 127, 28, WHITE);
        display.drawLine(0, 52, 127, 52, WHITE);
        display.setCursor(0, 33);
        display.print("<");
        display.setCursor(116, 33);
        display.print(">");


        if(selection == 0) {
          display.setCursor(18, 33);
          display.print("Exit");


          //exit from the settings if "Exit" is selected
          if(digitalRead(SETbutton) == 0 && setReleased == true) {
            settings = false;
            delay(200);
          }
        }


        if(selection == 1) {
          display.setCursor(18, 33);
          display.print("Time");
        }


        if(selection == 2) {
          display.setCursor(18, 33);
          display.print("Wheel");
        }


        if(selection == 3) {
          display.setCursor(18, 33);
          display.print("Sensors");


        }


        //if SET is pressed, go into the menu that has been chosen
        if(digitalRead(SETbutton) == 0 && setReleased == true && selection != 0) {
          settingsPage = selection;
          selection = 0;
          setReleased = false;
          display.clearDisplay();
          delay(200);
        }
        //display.display();
      }


      display.display();

      //settings page for entering the current time
      if(settingsPage == 1) {
        display.fillRect(0, 0, 128, 64, BLACK);


        //move between the settings using the SET button
        if(digitalRead(SETbutton) == 0 && setReleased == true) {
          selection++;
          if(selection > 2) selection = 0;
          setReleased = false;


          delay(200);


          //Serial.println(selection);
        }

        if(selection == 0) {
          display.fillRect(0, 0, 128, 18, WHITE);
          display.setTextColor(BLACK);


          if(digitalRead(RIGHTbutton) == 0 || digitalRead(LEFTbutton) == 0) {
            settingsPage = 0;
            if(settingsChanged == true) {
              rtc.adjust(DateTime(2024, 1, 1, h, m, 0)); //set date-time manually: yr, mo, dy, hr, mn, sec
              settingsChanged = false;
              settingsConfirmation();
              delay(1200);
              display.clearDisplay();
            }
            delay(200);
          }
        }


        else {
          display.fillRect(0, 0, 128, 18, BLACK);
          display.setTextColor(WHITE);
        }

        display.setTextSize(2);
        display.setCursor(0, 2);
        display.print("<");
        display.setCursor(18, 2);
        display.print("Set time");


        display.setTextSize(3);


        if(selection == 1) {
          display.fillRect(14, 29, 41, 25, WHITE);
          display.setTextColor(BLACK);


          if(digitalRead(RIGHTbutton) == 0) {
            h++;
            if(h > 23) h = 0;
            settingsChanged = true;
            delay(200);
          }


          if(digitalRead(LEFTbutton) == 0) {
            h--;
            if(h < 0) h = 23;
            settingsChanged = true;
            delay(200);
          }
        }


        else {
          display.fillRect(14, 29, 41, 25, BLACK);
          display.setTextColor(WHITE);
        }


        display.setCursor(18, 31);
        if(h < 10) display.print(0);
        display.print(h);


        display.setTextColor(WHITE);
        display.print(":");


        if(selection == 2) {
          display.fillRect(68, 29, 41, 25, WHITE);
          display.setTextColor(BLACK);


          if(digitalRead(RIGHTbutton) == 0) {
            m++;
            if(m > 59) m = 0;
            settingsChanged = true;
            delay(200);
          }


          if(digitalRead(LEFTbutton) == 0) {
            m--;
            if(m < 0) m = 59;
            settingsChanged = true;
            delay(200);
          }
        }


        else {
          display.fillRect(68, 29, 41, 25, BLACK);
          display.setTextColor(WHITE);
        }


        if(m < 10) display.print(0);
        display.print(m);


        //display.display();
      }


      if(settingsPage == 2) {
        display.fillRect(0, 0, 128, 64, BLACK);


        if(isnan(wheelCircumference)) wheelCircumference = 2.0;


        if(digitalRead(SETbutton) == 0 && setReleased == true) {
          selection++;
          if(selection > 1) selection = 0;
          setReleased = false;


          delay(200);
        }

        if(selection == 0) {
          display.fillRect(0, 0, 128, 18, WHITE);
          display.setTextColor(BLACK);


          if(digitalRead(RIGHTbutton) == 0 || digitalRead(LEFTbutton) == 0) {
            settingsPage = 0;
            if(settingsChanged == true) {
              EEPROM.put(0, wheelCircumference);
              settingsChanged = false;
              settingsConfirmation();
              delay(1200);
              display.clearDisplay();
            }
            delay(200);
          }
        }


        else {
          display.fillRect(0, 0, 128, 18, BLACK);
          display.setTextColor(WHITE);
        }

        display.setTextSize(2);
        display.setCursor(0, 2);
        display.print("<");
        display.setCursor(18, 2);
        display.print("Set wheel");


        display.setTextSize(3);


        if(selection == 1) {
          display.fillRect(6, 27, 113, 29, WHITE);
          display.setTextColor(BLACK);


          if(digitalRead(RIGHTbutton) == 0) {
            wheelCircumference += 0.01;
            if(wheelCircumference > 5.00) wheelCircumference = 5.00;
            settingsChanged = true;
            delay(150);
          }


          if(digitalRead(LEFTbutton) == 0) {
            wheelCircumference -= 0.01;
            if(wheelCircumference < 0.10) wheelCircumference = 0.10;
            settingsChanged = true;
            delay(150);
          }
        }


        else {
          display.fillRect(6, 27, 113, 29, BLACK);
          display.setTextColor(WHITE);
        }


        display.setCursor(10, 31);
        display.print(wheelCircumference);
        display.setTextSize(2);
        display.setCursor(93, 38);
        display.print("mt");


       // display.display();
      }


      if(settingsPage == 3) {
        display.fillRect(0, 0, 128, 64, BLACK);

        if(digitalRead(RIGHTbutton) == 0 || digitalRead(LEFTbutton) == 0) {
          settingsPage = 0;
          delay(200);
        }


        display.fillRect(0, 0, 128, 18, WHITE);
        display.setTextColor(BLACK);
        display.setTextSize(2);
        display.setCursor(0, 2);
        display.print("<");
        display.setCursor(18, 2);
        display.print("Sensors");


        display.setTextColor(WHITE);
        display.setCursor(0, 26);
        display.print("Wheel: ");
        if(digitalRead(wheelSensor) == 0) {
          display.print("ON");
        }
        else {
          display.print("OFF");
        }
        display.setCursor(0, 48);
        display.print("Brake: ");
        if(digitalRead(brakeSensor) == 0) {
          display.print("ON");
        }
        else {
          display.print("OFF");
        }
      }  
    }
  }


  EEPROM.get(0, wheelCircumference);


  //Serial.println(wheelCircumference);


  attachInterrupt(digitalPinToInterrupt(wheelSensor), interruptFunction, RISING);
}


void loop() {
  //update the display every 250ms
  if(millis() % 250 == 0) {
    update();
  }


  if(millis() - lightsMillis >= 180) {


    lightsMillis = millis();


    lightsCycle++; //0 to 1: red bright - 2 to 5: red dim
    if(lightsCycle > 5) lightsCycle = 0;
  }


  if(millis() - speedMillis >= speedUpdateInterval) {


    calculateSpeed();


    speedMillis = millis();
  }


  if(frontLights == true) {    
    if(left == true || right == true) {
      analogWrite(WHITEled, 20);
    }
    else {
      analogWrite(WHITEled, 180);
    }
  }


  if(frontLights == false) {
    analogWrite(WHITEled, 0);
  }


  //blinking red headlight
  if(backLights == true) {
    if(lightsCycle < 1 && left == false && right == false && brakeLights == false) {
      analogWrite(REDled, 200);
    }
    if((lightsCycle >= 1 || (lightsCycle < 1 && (left == true || right == true))) && brakeLights == false) {
      analogWrite(REDled, 20);
    }

    if(brakeLights == true) {
      analogWrite(REDled, 255);
    }
  }


  if(backLights == false) {
    analogWrite(REDled, 0);
  }


  if(digitalRead(brakeSensor) == invertBrakeSensor) {
    brakeLights = true;
  }
  else {
    brakeLights = false;
  }

  //turn LEFT turn signal ON
  if(digitalRead(LEFTbutton) == 0 && left == false && right == false && displayPage == 0 && leftReleased == true) {
    left = true;
    turnSignalState = true;
    turnSignalCycle = 0;


    leftReleased = false;
    releaseMillis = millis();
  }


  //turn RIGHT turn signal ON
  if(digitalRead(RIGHTbutton) == 0 && right == false && left == false && displayPage == 0 && rightReleased == true) {
    right = true;
    turnSignalState = true;
    turnSignalCycle = 0;


    rightReleased = false;
    releaseMillis = millis();
  }


  //turn signals OFF when LEFT button is pressed
  if(digitalRead(LEFTbutton) == 0 && leftReleased == true && displayPage == 0) {
    if(left == true) {
      left = false;
      digitalWrite(LEFTled, LOW);
    }


    if(right == true) {
      right = false;
      digitalWrite(RIGHTled, LOW);
    }


    leftReleased = false;
    releaseMillis = millis();
  }


  //turn signals OFF when RIGHT button is pressed
  if(digitalRead(RIGHTbutton) == 0 && rightReleased == true && displayPage == 0) {
    if(left == true) {
      left = false;
      digitalWrite(LEFTled, LOW);
    }


    if(right == true) {
      right = false;
      digitalWrite(RIGHTled, LOW);
    }


    rightReleased = false;
    releaseMillis = millis();
  }


  //turn signals OFF when SET button is pressed
  if(digitalRead(SETbutton) == 0 && (left == true || right == true) && setReleased == true) {
    if(left == true) {
      left = false;
      digitalWrite(LEFTled, LOW);
    }


    if(right == true) {
      right = false;
      digitalWrite(RIGHTled, LOW);
    }


    setReleased = false;
    releaseMillis = millis();
  }


  if(digitalRead(SETbutton) == 0 && left == false && right == false && setReleased == true) {
    displayPage++;


    if(displayPage > 2) displayPage = 0;


    menuSelection = 0;


    pageUpdated = false;


    setReleased = false;
    releaseMillis = millis();
  }


  if(digitalRead(LEFTbutton) == 0 && displayPage == 2 && leftReleased == true) {
    menuSelection++;

    if(menuSelection > 2) menuSelection = 0;


    leftReleased = false;
    releaseMillis = millis();
  }


  if(digitalRead(RIGHTbutton) == 0 && displayPage == 2 && rightReleased == true) {
    bool clicked = false;


    if(menuSelection == 0) {
      if(frontLights == false && backLights == false && clicked == false) {
        frontLights = true;
        backLights = true;


        clicked = true;
      }

      if((frontLights == true || backLights == true) && clicked == false) {
        frontLights = false;
        backLights = false;


        clicked = true;
      }
    }


    if(menuSelection == 1) {
      frontLights = !frontLights;
    }


    if(menuSelection == 2) {
      backLights = !backLights;
    }


    rightReleased = false;
    releaseMillis = millis();
  }


  if(millis() - releaseMillis > 10000 && displayPage != 0) {
    displayPage = 0;
  }


  //detect if the buttons have been released
  if(digitalRead(SETbutton) == 1 && millis() - releaseMillis >= 200) {
    setReleased = true;
  }


  if(digitalRead(RIGHTbutton) == 1 && millis() - releaseMillis >= 200) {
    rightReleased = true;
  }


  if(digitalRead(LEFTbutton) == 1 && millis() - releaseMillis >= 200) {
    leftReleased = true;
  }


  //LEFT turn signal
  if(left == true) {


    if (millis() - previousMillis >= turnSignalBlink) {
      // save the last time you blinked the LED
      previousMillis = millis();


      digitalWrite(LEFTled, turnSignalState);


      turnSignalState = !turnSignalState;
      turnSignalCycle++;
    }


    if(turnSignalCycle > (turnSignalMaxCycles*2 - 1)) {
      left = false;
      digitalWrite(LEFTled, LOW);
    }
  }


  //RIGHT turn signal
  if(right == true) {


    if (millis() - previousMillis >= turnSignalBlink) {
      // save the last time you blinked the LED
      previousMillis = millis();


      digitalWrite(RIGHTled, turnSignalState);


      turnSignalState = !turnSignalState;
      turnSignalCycle++;
    }


    if(turnSignalCycle > (turnSignalMaxCycles*2 - 1)) {
      right = false;
      digitalWrite(RIGHTled, LOW);
    }
  }


  //delay(1);
}


void interruptFunction() {
  pulses++;
}


void calculateSpeed() {
  float staticPulses = pulses;
  pulses = 0;

  totalKm = totalKm + (staticPulses * wheelCircumference) / 1000.0;

  speed = (staticPulses * wheelCircumference * 3.6) / (speedUpdateInterval / 1000.0);


  if(speed > 2) activeTime += speedUpdateInterval;


  if(speed > maxSpeed) maxSpeed = speed;


  //Serial.println(speed);
}


void update() {
  //detachInterrupt(2);


  DateTime now = rtc.now();


  display.clearDisplay();

  if(displayPage == 0) {
    //display speed
    display.setTextSize(3);
    display.setCursor(1, 0);
    if(speed < 10) display.print(0);
    display.print(speed, 0);
    display.setTextSize(2);
    display.print(" Km/h");


    //display time
    display.setTextSize(2);
    display.setCursor(1, 29);
    if(now.hour() < 10) display.print(0);
    display.print(now.hour());
    display.print(":");
    if(now.minute() < 10) display.print(0);
    display.print(now.minute());

    //display total space
    display.setCursor(1, 50);
    if(totalKm < 10) display.print(0);
    display.print(totalKm);
    display.print(" Km");


    //display turn signals icons
    if(left == true && turnSignalState == true) {
      display.fillRect(110, 27, 18, 11, WHITE);
      display.fillTriangle(110, 20, 110, 44, 98, 32, WHITE);
    }
    if(right == true && turnSignalState == true) {
      display.fillRect(98, 27, 18, 11, WHITE);
      display.fillTriangle(115, 20, 115, 44, 127, 32, WHITE);
    }


    checkBattery();


    display.display();
  }


  if(displayPage == 1 && pageUpdated == false) {
    pageUpdated = true;


    display.drawLine(0, 31, 127, 31, WHITE);
    display.drawLine(63, 0, 63, 63, WHITE);

    //display total time
    display.setTextSize(1);
    display.setCursor(1, 0);
    display.print("TOTAL");
    display.setCursor(0, 12);
    display.setTextSize(2);
    if((millis()/60000)/60 < 10) display.print(0);
    display.print((millis()/60000)/60);
    display.print(":");
    if((millis()/60000)%60 < 10) display.print(0);
    display.print((millis()/60000)%60);


    //display active time
    display.setTextSize(1);
    display.setCursor(69, 0);
    display.print("ACTIVE");
    display.setCursor(68, 12);
    display.setTextSize(2);
    if((activeTime/60000)/60 < 10) display.print(0);
    display.print((activeTime/60000)/60);
    display.print(":");
    if((activeTime/60000)%60 < 10) display.print(0);
    display.print((activeTime/60000)%60);


    //display max speed
    display.setTextSize(1);
    display.setCursor(1, 35);
    display.print("MAX");
    display.setCursor(30, 52);
    display.print("Km/h");
    display.setTextSize(2);
    display.setCursor(0, 46);    
    if(maxSpeed < 10) display.print(0);
    display.print(maxSpeed, 0);


    //display average speed
    display.setTextSize(1);
    display.setCursor(69, 35);
    display.print("AVERAGE");
    display.setCursor(98, 52);
    display.print("Km/h");
    display.setTextSize(2);    
    display.setCursor(68, 46);
    if(totalKm == 0.0 || activeTime == 0) {
      display.print(0); display.print(0);
    }
    else {
      if(totalKm/(activeTime/3600000.0) < 10) display.print(0);
      display.print(totalKm/(activeTime/3600000.0), 0);  
    }



    display.display();
  }


  if(displayPage == 2) {
    display.setTextSize(2);

    if(menuSelection == 0) {
      display.setCursor(0, 2);
      display.print(">");
    }


    if(menuSelection == 1) {
      display.setCursor(0, 24);
      display.print(">");
    }


    if(menuSelection == 2) {
      display.setCursor(0, 46);
      display.print(">");
    }

    display.setCursor(16, 2);
    display.print("All");
    display.setCursor(16, 24);
    display.print("Front");
    display.setCursor(16, 46);
    display.print("Back");

    if(frontLights == true || backLights == true) {
      display.fillRect(87, 0, 39, 18, WHITE);
      display.setTextColor(BLACK);
      display.setCursor(95, 2);
      display.print("ON");
    }


    if(frontLights == false && backLights == false) {
      display.setTextColor(WHITE);
      display.setCursor(89, 2);
      display.print("OFF");
    }


    if(frontLights == true) {
      display.fillRect(87, 22, 39, 18, WHITE);
      display.setTextColor(BLACK);
      display.setCursor(95, 24);
      display.print("ON");
    }


    if(frontLights == false) {
      display.setTextColor(WHITE);
      display.setCursor(89, 24);
      display.print("OFF");
    }


    if(backLights == true) {
      display.fillRect(87, 44, 39, 18, WHITE);
      display.setTextColor(BLACK);
      display.setCursor(95, 46);
      display.print("ON");
    }


    if(backLights == false) {
      display.setTextColor(WHITE);
      display.setCursor(89, 46);
      display.print("OFF");
    }

    display.setTextColor(WHITE);
    display.display();
  }


  //attachInterrupt(digitalPinToInterrupt(wheelSensor), calculateSpeed, RISING);
}


void checkBattery() {
  int batValue = analogRead(A0);


  //value above maximum threshold
  if ((605 + batteryReadingDeadband) < batValue) {  // more than 3.9V (605)
    batteryState = 3;
  }


  //value between two thresholds (middle region)
  if (((559 + batteryReadingDeadband) < batValue) && (batValue <= (605 - batteryReadingDeadband))) {  // 3.6V (559) to 3.9V (605)
    batteryState = 2;
  }


  //value between two thresholds (middle region)
  if (((513 + batteryReadingDeadband) < batValue) && (batValue <= (559 - batteryReadingDeadband))) {  // 3.3V (513) to 3.6V (559)       
    batteryState = 1;
  }


  //value below minimum threshold
  if (batValue < (513 - batteryReadingDeadband)) {  // less than 3.3V (513)
    batteryState = 0;    
  }


  display.drawLine(112, 55, 126, 55, WHITE);
  display.drawLine(112, 63, 126, 63, WHITE);
  display.drawLine(112, 56, 112, 62, WHITE);
  display.drawLine(126, 56, 126, 57, WHITE);
  display.drawLine(126, 61, 126, 62, WHITE);
  display.drawLine(127, 57, 127, 61, WHITE);

  if(batteryState == 0) {  // less than 3.3V
    display.fillRect(114, 57, 3, 5, BLACK);
    display.fillRect(118, 57, 3, 5, BLACK);
    display.fillRect(122, 57, 3, 5, BLACK);
  } 
  if(batteryState == 1) { // 3.3V to 3.6V
    display.fillRect(114, 57, 3, 5, WHITE);
    display.fillRect(118, 57, 3, 5, BLACK);
    display.fillRect(122, 57, 3, 5, BLACK);
  }
  if(batteryState == 2) { // 3.6V to 3.9V
    display.fillRect(114, 57, 3, 5, WHITE);
    display.fillRect(118, 57, 3, 5, WHITE);
    display.fillRect(122, 57, 3, 5, BLACK);
  }
  if(batteryState == 3) { // more than 3.9V
    display.fillRect(114, 57, 3, 5, WHITE);
    display.fillRect(118, 57, 3, 5, WHITE);
    display.fillRect(122, 57, 3, 5, WHITE);
  }


  //Serial.println(batValue);
}


void settingsConfirmation() {
  //display.fillTriangle(58, 40, 48, 30, 127, 0, WHITE);
  display.clearDisplay();
  display.drawRect(11, 5, 107, 53, WHITE);
  display.setTextColor(WHITE);
  display.setTextSize(2);
  display.setCursor(23, 14);
  display.print("Changes");
  display.setCursor(31, 35);
  display.print("saved!");
  display.display();
}

 

Arduino_Bike.ino
0.02MB

10단계: 배터리 회로

앞서 말했듯이 전체 시스템은 18650 리튬 배터리로 구동됩니다. 장착하기 위해 간단한 배터리 홀더를 3D 프린팅했습니다. 배터리를 연결하기 위해 오래된 크리스마스 조명 배터리 홀더에서 스프링 접점과 플랫 접점을 복구했습니다. 두 개의 접점에 두 개의 와이어를 납땜하고 3D 프린팅 배터리 홀더의 두 끝에 붙였습니다.

배터리를 충전하고 보호하기 위해 배터리 충전을 위한 USB-C 포트가 있는 TP4056 모듈을 사용하기로 했습니다. 배터리 홀더에서 나오는 전선을 PCB의 B+ 및 B- 패드에 연결했습니다. 모듈에는 충전 상태를 나타내는 두 개의 LED가 있으며, 모듈을 장착할 때 숨겨집니다. 그래서 나는 그것들을 납땜을 제거하고 몇 가지 짧은 전선을 사용하여 두 개의 투명한 3mm LED를 작은 패드에 연결했습니다. 이 LED는 USB 포트 옆에 배치됩니다.

충전 회로의 두 출력 단자에 스텝 업 모듈을 연결하여 배터리 전압을 5V로 높였습니다. 내가 가지고 있던 스텝 업 모듈에는 USB 포트가 있었는데, 납땜을 제거하고 출력 양극 및 음극 핀에 연결된 두 개의 전선으로 교체했습니다. 충분한 전류 출력이 있는 모든 스텝 업 모듈을 사용할 수 있으며, 입력의 음극이 출력의 음극과 직접 연결되어야 한다는 점을 명심하십시오. 마지막으로 충전 모듈에서 승압 모듈로 연결되는 양극선을 차단하는 로커 스위치를 넣었습니다. 이 스위치는 전체 시스템을 켜거나 끄는 데 사용됩니다. 아래에서 회로의 이 섹션에 대한 회로도를 찾을 수 있습니다.

18650+battery+holder.stl
0.13MB
Schematics (4).pdf
0.44MB

11 단계 : 자전거의 전자 장치 숨기기

이제 자전거의 배터리와 함께 Arduino와 함께 보드를 장착할 장소를 찾아야 했습니다. 지난 몇 달 동안 자전거 라이트를 세 번이나 도난당했기 때문에 이 시스템이 같은 방식으로 끝나는 것을 방지하고 싶었습니다. 그래서 나는 여러 해 동안 자전거에 가지고 있었지만 한 번도 사용한 적이 없는 물병에 모든 것을 숨기기로 결정했습니다. 병에는 완벽한 선택이 될 수 있는 몇 가지 특성이 있습니다: Arduino와 배터리가 있는 보드에 맞을 만큼 충분히 크고, 비가 오는 것을 방지하며, 가장 중요한 것은 아무도 안에 무엇이 들어 있는지 알려주지 않는다는 것입니다.

먼저 톱을 사용하여 물병의 바닥을 잘라내고 한쪽 면에 케이블을 통과시킬 구멍을 만들었습니다. 그런 다음 자전거에 물병을 장착하기 위한 홀더와 배터리 홀더, 충전 회로, 스위치 및 Arduino로 보드를 장착할 다른 부품을 3D 프린팅했습니다. 충전 포트를 위한 공간이 있는 쪽에는 배터리 충전 모듈을 뜨거운 접착제로 고정했습니다. 그런 다음 4개의 M3x12mm 볼트와 자동 잠금 너트를 사용하여 배터리 홀더를 장착했습니다. 다른 쪽에는 M3x12mm 볼트와 자동 잠금 너트, 3D 프린팅 스페이서를 사용하여 Arduino로 보드를 장착했습니다. 보드를 장착하기 위한 구멍은 3D 파일에 포함되어 있지 않으므로 보드의 구멍과 일치하도록 올바른 위치에 구멍을 뚫을 수 있습니다. 마지막으로, 스텝 업 컨버터의 +5V 전원과 GND를 보드에 연결하고, 배터리 양극에서 직접 나오는 전선 하나를 연결하여 RTC에 전원을 공급하고 다른 하나는 승압 컨버터의 입력에서 나오는 전선을 연결하여 Arduino가 전압 분배기를 통해 배터리 전압을 읽을 수 있도록 했습니다.

 

11 단계 : 자전거의 전자 장치 숨기기

이제 자전거의 배터리와 함께 Arduino와 함께 보드를 장착할 장소를 찾아야 했습니다. 지난 몇 달 동안 자전거 라이트를 세 번이나 도난당했기 때문에 이 시스템이 같은 방식으로 끝나는 것을 방지하고 싶었습니다. 그래서 나는 여러 해 동안 자전거에 가지고 있었지만 한 번도 사용한 적이 없는 물병에 모든 것을 숨기기로 결정했습니다. 병에는 완벽한 선택이 될 수 있는 몇 가지 특성이 있습니다: Arduino와 배터리가 있는 보드에 맞을 만큼 충분히 크고, 비가 오는 것을 방지하며, 가장 중요한 것은 아무도 안에 무엇이 들어 있는지 알려주지 않는다는 것입니다.

먼저 톱을 사용하여 물병의 바닥을 잘라내고 한쪽 면에 케이블을 통과시킬 구멍을 만들었습니다. 그런 다음 자전거에 물병을 장착하기 위한 홀더와 배터리 홀더, 충전 회로, 스위치 및 Arduino로 보드를 장착할 다른 부품을 3D 프린팅했습니다. 충전 포트를 위한 공간이 있는 쪽에는 배터리 충전 모듈을 뜨거운 접착제로 고정했습니다. 그런 다음 4개의 M3x12mm 볼트와 자동 잠금 너트를 사용하여 배터리 홀더를 장착했습니다. 다른 쪽에는 M3x12mm 볼트와 자동 잠금 너트, 3D 프린팅 스페이서를 사용하여 Arduino로 보드를 장착했습니다. 보드를 장착하기 위한 구멍은 3D 파일에 포함되어 있지 않으므로 보드의 구멍과 일치하도록 올바른 위치에 구멍을 뚫을 수 있습니다. 마지막으로, 스텝 업 컨버터의 +5V 전원과 GND를 보드에 연결하고, 배터리 양극에서 직접 나오는 전선 하나를 연결하여 RTC에 전원을 공급하고 다른 하나는 승압 컨버터의 입력에서 나오는 전선을 연결하여 Arduino가 전압 분배기를 통해 배터리 전압을 읽을 수 있도록 했습니다.

 

Spacer (1).stl
0.10MB
Bottle+holder (1).stl
0.76MB
Electronics+mount (1).stl
0.24MB

12 단계 : 자전거에 시스템 설치

2개월간의 작업 끝에 마침내 전체 시스템을 자전거에 장착할 수 있었습니다. 저는 인클로저에 삽입한 두 개의 M5 볼트와 두 개의 자동 잠금 너트를 함께 사용하여 백라이트를 설치하는 것으로 시작했습니다. 그런 다음 3D 프린팅 브래킷과 M3x20mm 나사를 사용하여 버튼과 디스플레이를 자전거 핸들바에 고정했습니다. 두 개의 프론트 라이트는 디스플레이 및 버튼과 같은 방식으로 핸들바 중앙 근처에 장착할 수 있습니다. 케이블 타이로 케이블을 정리하여 Arduino와 배터리가 장착될 위치로 가져왔습니다. 그런 다음 3D 프린팅 마운트가 있는 휠 자기 센서를 뒷바퀴 근처에 장착하여 휠에 부착된 자석이 트리거할 수 있도록 했습니다. 마지막으로 대부분의 자전거에 있는 두 개의 나사산에 병 홀더를 장착하여 케이블이 바닥의 구멍을 통과하도록 했습니다. 그런 다음 다른 구성 요소의 모든 JST 커넥터를 보드에 연결했습니다. 병 홀더 내부에 두 개의 M3 나사와 자동 잠금 너트를 사용하여 모든 전자 장치와 배터리가 있는 마운트를 고정했습니다. 마지막으로 전자 장치가 있는 마운트 위로 미끄러지기만 하면 되는 병을 홀더에 삽입하고 두 개의 구멍을 뚫은 후 나사로 고정했습니다. 물론 결국에는 일반 물병이 아니라는 것을 조금이라도 알 수 있지만 요즘 전원 스위치가 있는 물병이 없는 사람이 어디 있겠습니까?

 

13 단계 : 설정

전체 시스템을 처음 시작할 때 디스플레이에 몇 가지 정보를 입력해야 합니다. 이렇게하려면 SET 버튼을 누른 상태에서 전원 스위치를 켭니다. 이런 식으로 시스템 설정으로 들어갑니다. 왼쪽 및 오른쪽 키를 사용하여 메뉴 항목을 이동할 수 있으며 SET를 눌러 현재 설정을 확장 할 수 있습니다. 먼저 시간을 설정해야 합니다. 해당 페이지에 들어간 후 SET를 눌러 시간을 선택하고 왼쪽 및 오른쪽 버튼을 사용하여 값을 늘리거나 줄일 수 있습니다. SET를 다시 누르면 시간처럼 조정할 수 있는 분이 선택됩니다. SET를 다시 누른 다음 왼쪽 또는 오른쪽 키를 눌러 시간을 설정하고 시간 설정을 종료합니다. 우리가 입력하는 시간은 시간 설정을 종료하는 순간의 현재 시간으로 간주된다는 점에 유의하는 것이 중요합니다. 물론 시간은 RTC 모듈에 의해 유지 관리됩니다. 휠 설정에서 휠 둘레(지름이 아님)를 미터 단위로 설정해야 합니다. 절차는 시간을 설정하는 절차와 동일하며 왼쪽 및 오른쪽 버튼을 사용하여 1cm 값을 수정합니다. 설정 페이지를 종료하면 값이 Arduino의 EEPROM 메모리에 직접 저장되며 시스템 전원을 꺼도 손실되지 않습니다. Sensors라는 마지막 설정 페이지를 사용하면 휠 자기 센서가 휠에 장착한 자석을 감지하는지 여부를 확인할 수 있습니다. 이것은 자석을 센서에 올바르게 정렬하는 데 유용합니다. 자석이 센서 위를 통과할 때 상태가 꺼짐에서 켜짐으로 여러 번 일관되게 변경되는 것을 발견하면 자석에 실제로 여러 자석이 포함되어 있기 때문입니다. 이 경우 바퀴 둘레를 자석의 수로 나누고 새 값을 설정하십시오. 그렇지 않으면 속도와 총 킬로미터 값이 두 배 또는 세 배가 됩니다. 설정 메뉴에서 종료를 선택하면 짐작할 수 있듯이 설정을 종료할 수 있습니다.

14단계: 시스템 사용

이제 시스템이 모두 설정되었으므로 주행하는 동안 디스플레이에서 속도, 시간 및 총 킬로미터를 볼 수 있습니다. SET를 누르면 평균 및 최대 속도, 총 시간 및 실제로 주행한 시간과 같은 다른 데이터와 함께 두 번째 페이지로 이동합니다(예: 신호등에서 정지한 시간 제외). SET를 다시 누르면 전면 또는 후면 조명 또는 둘 다를 켜거나 끌 수 있는 페이지로 이동합니다. 이 페이지에 있을 때 왼쪽 화살표 버튼을 눌러야만 모든 조명(전면 및 후면) 또는 전면 또는 후면 조명에서 작동하도록 선택할 수 있습니다. 선택한 조명을 켜거나 끄려면 오른쪽 화살표 버튼을 사용할 수 있습니다. 디스플레이는 10초 후에 자동으로 기본 페이지로 돌아갑니다.

좌회전 및 우회전 신호를 활성화하려면 두 개의 해당 버튼을 사용할 수 있으며 끄려면 아무 버튼이나 누를 수 있습니다. 방향 지시등은 메인 디스플레이 페이지에 있을 때만 활성화할 수 있습니다. 방향 지시등이 활성화되면 디스플레이에 표시기가 표시되며 이는 매우 유용한 기능입니다.

조명은 매우 밝고 낮과 밤에 잘 볼 수 있습니다. 배터리는 몇 시간 동안 지속되며 배터리가 부족하면 USB-C 포트로 충전 할 수 있습니다.

15 단계 : 완료되었습니다!

전반적으로 이 프로젝트의 결과에 매우 만족합니다. 나는 지난 몇 주 동안 그것을 사용해 왔으며 디스플레이에 표시된 데이터가 매우 흥미롭다는 것을 알았습니다. 조명은 밤에 안전하게 타기 위해 매우 중요합니다. 항상 그렇듯이 이 가이드가 흥미롭고 유용하기를 바랍니다.