일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- C++
- 프린터
- ESP-IDF
- 3D Printer
- SQLITE3
- 3D
- esp32
- 3d프린터
- 하드웨어
- 라즈베리파이
- 3D 프린터
- fusion360
- C언어
- 쏘카
- 퓨전360
- 리눅스
- Fusion 360
- 자작
- 아두이노
- 해커랭크
- Arduino
- ESP
- DIY
- 코딩테스트
- 설계
- 오픈소스
- Hypercube
- 오픈소스 하드웨어
- 프로젝트
- IOT
- Today
- Total
09LABS
[CAN Bus] 라즈베리파이로 CAN Bus를 해킹해보자 본문
최근 재미난 프로젝트를 하나 진행하고 있다.
취미로 하고있던 심레이싱에 기성품 핸들이 아니라 실제 차량의 핸들을 장착하고 싶어하는 동호회원이 꽤 있었다.
예전에 포르쉐 911 핸들을 작업한 적이 있었는데 버튼을 하나 하나 납땜을 해야하다보니 굉장히 힘들었던 기억이 있다.
조그 다이얼은 살리지 못했고 푸시버튼과 패들 쉬프트만 살릴 수 있었다. 제어보드도 에어백 커버 안에 넣다보니 배선이 굉장히 난잡해졌었다.
그 다음은 벤츠 W212 핸들이었는데 SMD 택트 스위치라서 납땜이 불가하여 클락션과 패들 쉬프트만 살려서 보냈었다.
그래서 생각을 해보니 여태 실패했던 이유가 CAN 신호를 취득하지 못하거나 저항값을 읽기가 매우 불편해서였는데 이번엔 CAN 신호를 사용하는 현대 차량의 핸들을 의뢰받았다.
핸들 모델과 회로도
첫 타자는 GV80 럭비공 핸들이다. 에어백 커버가 큼직해서 내부 공간도 여유가 많고 무엇보다 B-CAN 신호를 취득할 수 있도록 되어있었다.
회로도도 생각보다 간단했다. 상시전원에 12V 연결해주고 접지만 잡아준 뒤 B-CAN 신호만 받아오면 된다.
특이하게도 우측 리모콘 어셈블리가 좌측 신호와 패들시프트 신호까지 한꺼번에 처리하도록 회로가 구성되어 있다.
추가적으로 햅틱 모듈 (작은 모터)가 하나 있는데 이 기능도 살릴 수 있을 것 같았다.
두 번째 타자는 아이오닉 5N이다. 요즘 아이오닉 5에 이 핸들을 이식하거나 타 차량에도 이식하는 후기가 꽤 많이 보였다.
핸들만 대략 40~60만원 선으로 저렴한 편은 아니었다.
아이오닉 5N도 B-CAN을 사용한다. 다만 B2-CAN HS로 되어있는거로 봐서 CAN-FD일 가능성도 있었다.
부품 선정
1. CAN Module
우선 간단히 테스트만 해보기 위해 MCP2515 CAN Module을 구매했다. SPI 인터페이스를 지원하고 1Mbps까지 지원한다.
만약 CAN-FD를 테스트하려면 MCP2518 모듈을 구매해야한다. SN65HVD230은 TTL 인터페이스를 지원하고 모듈 사이즈가 작아서 임시로 2개 구매해봤다.
2. nRF24L01
nRF24L01은 SIMAGIC 휠베이스와 통신하기 위해서 구매했다. 우선 왼쪽 모듈로 테스트해보고 공간이 부족하면 우측 모듈로 교체 예정이다. SIMAGIC 휠베이스는 2.4Ghz 대역으로 통신을 하며 위 모듈로 통신이 가능하다는 Git Repository를 하나 발견했다.
https://github.com/Kegetys/simagic-arduino-poc
GitHub - Kegetys/simagic-arduino-poc: Simagic Alpha wireless protocol description and proof-of-concept Arduino code
Simagic Alpha wireless protocol description and proof-of-concept Arduino code - Kegetys/simagic-arduino-poc
github.com
3. Boost 컨버터
MT3608은 저렴하면서도 꽤 많이 사용되는 모듈이다. 다만.... 고장이 잘 난다. 우측 모듈은 아마 같은 칩을 사용할텐데 신뢰성이 괜찮았었다. 우선 MT3608은 임시로 사용할 예정이다.
라즈베리파이 배선
우선 MCP2515 모듈로 테스트를 진행하기 위해서 위 회로도를 사용했다.
GPIO CAN
HEADR SIGNAL MODULE
PIN NAME --- SIGNAL
#01 3,3V --- VCC
#02 5V --- Extra wire to Vcc TJA1050
#06 GND --- GND
#09 GND --- GND to CAN BUS (there is no GND terminal on CAN MODULE)
#19 MOSI --- SI
#21 MISO --- SO
#23 SCLK --- SCK/CLK
#24 SPI0.CE0 --- CS
라즈베리파이와 CAN 모듈은 위와 같이 연결을 해준다.
GV80의 클락 스프링 커넥터를 정면에서 바라본 핀 배열은 위와 같다.
전원부와 B-CAN만 취득하면 되니 7, 8, 13, 14번만 우선 사용한다.
7번은 파워써플라이를 사용해서 12V를 공급해줬다. 접지는 라즈베리파이 접지와 같이 연결을 해주었다.
핸들 접지와 라즈베리파이 접지를 연결하는 이유는 CAN 신호가 이상하게 들어올 수 있기 때문이다.
커넥터에는 위와 같이 점퍼 케이블을 연결해서 전원과 MCP2515 CAN 모듈에 연결해줬다.
# /boot/firmware/config.txt
dtoverlay=mcp2515-can0,oscillator=8000000,interrupt=12
dtoverlay=spi-bcm2835-overlay
그 다음 라즈베리파이에 위 내용을 /boot/firmware/config.txt에 입력해준 뒤 재부팅을 했. (# 내용은 제외)
$ sudo apt-get update
$ sudo apt-get install can-utils
이제 CAN utility를 설치해준다.
$ ls /sys/bus/spi/devices/spi0.0
driver modalias net of_node subsystem uevent
$ ls /sys/bus/spi/devices/spi0.0/net
can0
$ ls /sys/bus/spi/devices/spi0.0
driver modalias net of_node subsystem uevent
$ ls /sys/bus/spi/devices/spi0.0/net/
can0
$ ls /sys/bus/spi/devices/spi0.0/net/can0/
addr_assign_type carrier_changes duplex iflink operstate speed uevent
address device flags link_mode phys_port_id statistics
addr_len dev_id gro_flush_timeout mtu phys_port_name subsystem
broadcast dev_port ifalias name_assign_type phys_switch_id tx_queue_len
carrier dormant ifindex netdev_group queues type
dtoverlay 설정과 can-utils까지 설치가 완료되면 위와 같이 SPI 0.0 드라이버에 CAN0가 인식되는 것을 알 수 있다.
CAN 드라이버는 socketcan으로 작동하게 된다.
$ sudo ip link set can0 up type can bitrate 500000
일반적으로 현대차는 500000 bitrate를 사용한다. 위와 같이 입력을 해주면 socketcan이 활성화된다.
can0: flags=193<UP,RUNNING,NOARP> mtu 16
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 10 (UNSPEC)
RX packets 130173 bytes 1041384 (1016.9 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 460 bytes 3680 (3.5 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
eth0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
ether 1c:16:12:1e:10:11 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 112 bytes 10746 (10.4 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 112 bytes 10746 (10.4 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
위와 같이 can0가 인식되면 기본 준비는 마무리 된 것이다.
Python 및 모듈 설치
요즘 python은 pip로 모듈을 설치하면 global로 설치가 되지 않고 경고를 내뿜는다. 따라서 venv로 진행을 하는 것을 권장한다.
$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $ pip3 install python-can
위와 같이 입력을 해주면 venv가 활성화된다. 그 다음 python-can 모듈을 설치해줬다.
요즘 또 대세는 ChatGPT로 바이브 코딩을 하는 것이지 않나 생각이 되기도 하고 CAN 신호 변화를 자세히 관찰하기 위해서 몇 가지 조건을 추가해서 코드를 만들었다.
import can
import threading
import time
import curses
INTERFACE = 'can0'
TESTER_PRESENT_INTERVAL = 5 # seconds
TESTER_PRESENT_ID = 0x7DF
TESTER_PRESENT_DATA = [0x02, 0x3E, 0x00] + [0xAA] * 5
# Shared state
last_messages = {}
lock = threading.Lock()
def send_tester_present(bus):
msg = can.Message(
arbitration_id=TESTER_PRESENT_ID,
data=TESTER_PRESENT_DATA,
is_extended_id=False
)
while True:
try:
bus.send(msg)
except can.CanError:
pass
time.sleep(TESTER_PRESENT_INTERVAL)
def receive_messages(bus):
while True:
msg = bus.recv(timeout=1.0)
if msg:
with lock:
last_messages[msg.arbitration_id] = (msg.data, time.time())
def curses_display(stdscr):
curses.start_color()
curses.use_default_colors()
curses.init_pair(1, curses.COLOR_GREEN, -1)
curses.init_pair(2, curses.COLOR_RED, -1)
stdscr.nodelay(True)
prev_values = {}
while True:
stdscr.clear()
stdscr.addstr(0, 2, "CAN Monitor (Change Highlight)", curses.A_BOLD)
stdscr.addstr(1, 0, "Time ID Data")
with lock:
sorted_ids = sorted(last_messages.keys())
for idx, msg_id in enumerate(sorted_ids, start=2):
with lock:
data, timestamp = last_messages[msg_id]
time_str = time.strftime("%H:%M:%S", time.localtime(timestamp))
stdscr.addstr(idx, 0, f"{time_str} 0x{msg_id:03X} ")
prev_data = prev_values.get(msg_id, None)
for i, byte in enumerate(data):
if prev_data and i < len(prev_data) and byte != prev_data[i]:
stdscr.addstr(f"{byte:02X} ", curses.color_pair(2)) # red
else:
stdscr.addstr(f"{byte:02X} ", curses.color_pair(1)) # green
prev_values[msg_id] = data
stdscr.refresh()
time.sleep(0.1)
def main():
bus = can.interface.Bus(channel=INTERFACE, bustype='socketcan')
t1 = threading.Thread(target=send_tester_present, args=(bus,), daemon=True)
t2 = threading.Thread(target=receive_messages, args=(bus,), daemon=True)
t1.start()
t2.start()
curses.wrapper(curses_display)
if __name__ == "__main__":
main()
위 코드에서 가장 중요한 부분은 UDS 패킷인데 Tester Present 패킷을 전송하지 않으면 핸들의 CAN 모듈이 슬립모드에 들어가는 것을 알아냈다. 아무 패킷이나 전송하면 잠깐 깨어났다가 8초 정도 후에 슬립모드에 들어간다.
영상을 보면 0x1CF 패킷의 4번째 바이트가 20에서 28로 변화하는 것을 볼 수 있다.
그 다음 0x448과 0x4D5 패킷이 변화하는 것을 볼 수 있었다. 핸즈프리 버튼과 모드 버튼, 메뉴 버튼, LKAS 버튼은 0x448을 사용하고 볼륨 다이얼과 상, 하, OK 버튼은 0x448과 0x4D5에 모두 노출이 되었다.
이렇게 취득한 CAN 신호로 펌웨어만 만들면 될 것 같다.
다음 포스팅은 nRF24L01을 사용해서 SIMAGIC 휠베이스에 버튼 신호를 전송해 보는 것을 작성할 예정이다.
오늘의 삽질 끝!
'Linux > Raspberry Pi' 카테고리의 다른 글
[Network] 라즈베리파이에 Nginx Proxy Manager를 설치해보자 (0) | 2025.06.21 |
---|---|
[fluidd pi] GPIO Control, PSU Control 버튼 만들기 (0) | 2022.09.16 |
[Raspberry Pi] 프로그램 자동실행 서비스 등록하기 (2) | 2021.09.16 |
[Raspberry Pi] MySQL root 계정으로 로그인이 안될 때 (0) | 2021.09.13 |
[Raspberry Pi] octoprint 설치방법 (수동설치) (0) | 2019.01.23 |