이전에 CAN 통신의 특징과 프레임 구조에 대해서 알아봤다. 아두이노를 이용해서 CAN 통신 프로토콜 송수신을 테스트할 수 있다고 하여, 동아리 방이 있는 아두이노로 간단하게 실습해본 내용을 정리하고자 한다.
먼저, CAN 패킷을 보내 data 필드의 값을 송수신해보았고, 스위치 모듈을 누르면 CAN 통신을 통해 LED가 켜지도록 실습을 진행했다.
실습 장비 목록
- Arduino UNO R3 SMD 2개
- MCP2515 (CAN통신 모듈) 2개
- 점퍼 케이블 / 저항(220옴)
- 브레드보드 2개
- 스위치 모듈 1개
- LED(red color) 2개
Arduino UNO R3 SMD
동아리 방에 있는 아두이노 모델은 UNO R3 SMD이었으며, 아래 사진과 같은 핀맵은 위 링크의 PDF 파일에서 확인할 수 있다.
아두이노 보드에서는 기본적으로 CAN 통신을 지원하지 않기 때문에 추가적인 모듈이 필요하다. 따라서, CAN 컨트롤러 IC인 MCP2515를 연결하여 CAN 통신을 실습했다.
Arduino IDE
아두이노에서 코드를 실행시키위한 IDE를 설치해준다.
설치된 아두이노 IDE를 실행하면, 위와 같은 화면을 확인할 수 있다.
[Tools] → [Board] → [Arduino AVR Boards] 에서 해당하는 아두이노 모델을 선택한다.
[Tools] → [Port] 에서 아두이노와 PC와 연결된 시리얼 포트를 선택한다. 이때, 아두이노와 PC가 연결되어야 한다. Arduino UNO R3 SMD 모델에서는 USB A to B 케이블로 연결할 수 있었다.
[Sketch] → [Include Library] → [Add .ZIP Library] 로 아두이노에 라이브러리를 추가할 수 있다.
CAN interface library
CAN 인터페이스 라이브러리이다. 해당 라이브러리를 아두이노에 올리기 위해서, github에서 ZIP 파일로 다운로드받아 앞서 아두이노 IDE에서 추가해준다.
CAN 통신 - 메시지 송수신
두 개의 아두이노가 각각 CAN 패킷을 송신하고, 수신하는 코드이다. 아두이노에는 MCP2515 모듈이 연결되어 있어야 하며, 두 MCP2515의 CAN HIGH와 CAN LOW가 연결되어 있어야 한다.
can_send.c
#include <SPI.h>
#include <mcp2515.h>
struct can_frame canMsg;
MCP2515 mcp2515(10); // SPI CS Pin 10
int i = 0;
int j = 0;
int send_cnt = 0;
void setup()
{
while (!Serial)
;
Serial.begin(115200);
SPI.begin();
mcp2515.reset();
mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ);
mcp2515.setNormalMode();
}
void loop()
{
Serial.print("sending : ");
Serial.println(send_cnt);
canMsg.can_id = 0x25;
canMsg.can_dlc = 8;
canMsg.data[0] = i;
canMsg.data[1] = j;
canMsg.data[2] = 0x00;
canMsg.data[3] = 0x00;
canMsg.data[4] = 0x00;
canMsg.data[5] = 0x00;
canMsg.data[6] = 0x00;
canMsg.data[7] = 0x00;
i++;
j = i * 2;
send_cnt++;
mcp2515.sendMessage(&canMsg);
delay(1000);
}
mcp2515 라이브러리를 이용해서 CAN 패킷을 송신하는 코드이다. bitrate와 mode를 설정하고 can_frame 구조체에 id와 데이터를 설정하여 송신하게 된다.
can_receive.c
#include <SPI.h>
#include <mcp2515.h>
struct can_frame canMsg;
MCP2515 mcp2515(10); // SPI CS Pin 10
int error_cnt = 0;
void setup()
{
Serial.begin(115200);
SPI.begin();
delay(3000);
mcp2515.reset();
mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ);
mcp2515.setNormalMode(); // Sets CAN at normal mode
}
void loop()
{
if (mcp2515.readMessage(&canMsg) == MCP2515::ERROR_OK) // To receive data (Poll Read)
{
Serial.print("Message received with ID: 0x");
Serial.println(canMsg.can_id, HEX);
Serial.print("Message received with Lengtg: ");
Serial.println(canMsg.can_dlc);
Serial.print("Data: ");
for (int i = 0; i < canMsg.can_dlc; i++)
{
Serial.print(canMsg.data[i], HEX);
Serial.print(" ");
}
Serial.println();
delay(1000);
}
else
{
error_cnt++;
Serial.print("MCP2515 ERROR : ");
Serial.println(error_cnt);
delay(1000);
}
}
mcp2515 라이브러리를 이용해서 CAN 패킷을 수신하는 코드이다. 송신과 동일하게 bitrate와 mode를 설정하며 readMessage 함수로 can_frame 구조체에 데이터를 넣어준다.
앞서 코드를 아두이노에 각각 실행하면, can_receive.c 코드가 돌아가는 아두이노 콘솔 창에서 CAN 패킷을 확인할 수 있다.
CAN 통신 - LED ON/OFF
CAN 패킷에 데이터를 보내고, 보낸 데이터의 값을 통해 LED를 키고 끌 수 있다.
can_send_led.c
#include <SPI.h>
#include <mcp2515.h>
struct can_frame canMsg;
MCP2515 mcp2515(10); // SPI CS Pin 10
int Last = 0;
void setup()
{
while (!Serial)
;
Serial.begin(115200);
SPI.begin();
mcp2515.reset();
mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ);
mcp2515.setNormalMode();
pinMode(2, OUTPUT);
pinMode(3, INPUT_PULLUP);
}
void loop()
{
int Now = digitalRead(3); // button status - GPIO 3
if (Last != Now)
{
delay(100);
Last = Now;
if (Now == 0)
{
Serial.println("Button OFF!");
digitalWrite(2, LOW);
canMsg.can_id = 0x25;
canMsg.can_dlc = 8;
canMsg.data[0] = 0;
mcp2515.sendMessage(&canMsg);
}
else if (Now == 1)
{
Serial.println("Button ON!");
digitalWrite(2, HIGH);
canMsg.can_id = 0x25;
canMsg.can_dlc = 8;
canMsg.data[0] = 1;
mcp2515.sendMessage(&canMsg);
}
}
}
위 코드는 GPIO 3에 연결된 버튼의 상태를 계속 감지하고, 버튼의 상태가 바뀔 경우에 CAN 패킷을 전송하는 코드이다. 디바운스를 위해 100ms 지연을 준다.
can_receive_led.c
#include <SPI.h>
#include <mcp2515.h>
long unsigned int rxId;
struct can_frame canMsg;
MCP2515 mcp2515(10); // SPI CS Pin 10
void setup()
{
Serial.begin(115200);
SPI.begin();
delay(3000);
mcp2515.reset();
mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ);
mcp2515.setNormalMode();
pinMode(2, INPUT); // Interrupt - GPIO 2
pinMode(3, OUTPUT);
}
void loop()
{
if (!digitalRead(2)) // data receive : GPIO 2 -> LOW
{
mcp2515.readMessage(&canMsg);
rxId = canMsg.can_id;
Serial.print("ID: ");
Serial.print(rxId, HEX);
Serial.print(", Data[0]: ");
Serial.println(canMsg.data[0], HEX);
if (canMsg.data[0] == 1)
{
Serial.println("LED ON!");
digitalWrite(3, HIGH);
}
else if (canMsg.data[0] == 0)
{
Serial.println("LED OFF!");
digitalWrite(3, LOW);
}
}
}
송신 코드에서 보낸 패킷을 수신하는 코드이다. CAN 메시지 수신을 감지하면, 송신에서 보낸 Data[0] 의 값을 LED를 ON/OFF 한다.
Button OFF → LED OFF
전체 구성은 위와 같다. 왼쪽 아두이노 보드에는 수신측 코드가 돌아가고 있으며, 오른쪽 보드에는 송신측 코드가 돌아가고 있다.
Button ON → LED ON
버튼을 누르면 오른쪽 아두이노가 CAN 패킷을 날려, 왼쪽 아두이노에 LED가 켜지는 것을 볼 수 있다.
CAN 프로토콜을 공부하면서 아두이노 2개로 송수신하는 글을 보게 되었다.. 마침 동아리 방에 있던 아두이노가 생각났고, 모듈을 구매해서 실습 할 수 있었다.(스위치를 딸각 딸각 중독됨 ㅎㅋ)
OBD-II 어댑터랑 CAN 트랜시버가 있으면 실제 차량에서 CAN 패킷을 덤프뜰 수 있다는데,, 기회가 된다면 재밌는 실습이 될 것 같다.
Ref