반응형
리눅스 네트워크 프로그래밍 - CAN(Controller Area Network) 통신
본 글에서는 차량 내부에서 사용되는 CAN(Controller Area Network) 통신을 수행하는 리눅스 어플리케이션 프로그램을 작성하는 방법을 설명한다.
환경은 다음과 같다.
- CAN 인터페이스에 대한 디바이스 드라이버 등은 BSP 레벨에서 이미 포팅이 되어 있다.
- CAN 네트워크에서 장치 간 공통으로 사용되는 baudrate 등 타이밍 관련 설정은 완료되어 있다.
- 어플리케이션 프로그램은 C 언어로 작성된다.
CAN 인터페이스 초기화
C 언어로 작성된 어플리케이션 프로그램 상에서 CAN 인터페이스를 초기화하는 절차는 다음과 같다.
- socket() 함수로 CAN 인터페이스에 대한 소켓 파일 디스크립터를 연다
- ioctl() 함수의 SIOCGIFINDEX 명령을 통해 CAN 인터페이스의 인터페이스 식별번호를 가져온다.
- bind() 함수로 CAN 인터페이스를 바인드한다.
CAN 프레임 전송
C 언어로 작성된 어플리케이션 프로그램 상에서 CAN 프레임을 전송하는 절차는 다음과 같다.
- struct can_frame 구조체에 전송 데이터를 채운다.
- write() 함수로 struct can_frame 구조체를 전달하여 프레임을 전송한다.
CAN 프레임 수신
C 언어로 작성된 어플리케이션 프로그램 상에서 CAN 프레임을 수신하는 절차는 다음과 같다.
- read() 함수로 데이터를 수신한다 → struct can_frame 구조체 포인터를 파라미터로 전달하여 데이터를 받아 온다.
- struct can_frame 구조체 내의 정보에 따라 수신 프레임을 처리한다.
여기서 read() 함수는 기본적으로 블로킹 함수이므로 프로그램 구조에 따라 수신 쓰레드를 별도로 생성하여 read() 함수를 호출하거나, CAN 소켓 속성을 논블로킹으로 변경 후 호출해야 할 수도 있다.
프로그램 예제 코드
CAN 인터페이스를 초기화하고, CAN 프레임을 전송 및 수신하는 어플리케이션 프로그램의 예제 코드는 다음과 같다.
참고로 아래 코드를 빌드하기 위해 별도의 라이브러리를 링크할 필요는 없다.
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
/**
* @brief CAN 인터페이스를 초기화한다.
* @param[in] ifname CAN 인터페이스 이름
* @retval 양수: CAN 소켓 디스크립터
* @retval -1: 실패
*/
int InitCanInterface(const char *ifname)
{
/*
* CAN 소켓을 생성한다.
*/
int sock = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (sock == -1) {
printf("Fail to create can socket for %s - %m\n", ifname);
return -1;
}
printf("Success to create can socket for %s\n", ifname);
/*
* CAN 인터페이스 식별번호를 획득한다.
*/
struct ifreq ifr;
strcpy(ifr.ifr_name, ifname);
int ret = ioctl(sock, SIOCGIFINDEX, &ifr);
if (ret == -1) {
perror("Fail to get can interface index -");
return -1;
}
printf("Success to get can interface index: %d\n", ifr.ifr_ifindex);
/*
* CAN 소켓을 바인딩한다.
*/
struct sockaddr_can addr;
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
if (ret == -1) {
perror("Fail to bind can socket -");
return -1;
}
printf("Success to bind can socket\n");
return sock;
}
/**
* @brief CAN 프레임을 전송한다.
* @param[in] sock CAN 소켓 디스크립터
* @param[in] id CAN id (11비트 또는 29비트 길이)
* @param[in] data 전송할 CAN 프레임 데이터
* @param[in] data_len 전송할 CAN 프레임 데이터의 길이
* @retval 0: 성공
* @retval -1: 실패
*/
int TransmitCanFrame(const int sock, const uint32_t id, const uint8_t *data, const size_t data_len)
{
/*
* 전송할 CAN 프레임을 설정한다.
*/
struct can_frame frame;
frame.can_id = id & 0x1fffffff;
frame.can_id |= (1 << 31);
memcpy(frame.data, data, data_len);
frame.can_dlc = data_len;
/*
* 전송한다.
*/
int tx_bytes = write(sock, &frame, sizeof(frame));
if (tx_bytes == -1) {
perror("Fail to transmit can frame -");
return -1;
}
printf("Success to transmit can frame - %d bytes is transmitted\n", tx_bytes);
return 0;
}
/// CAN 프레임 최대 길이
#define CAN_FRAME_MAX_LEN 8
/**
* @brief CAN 프레임을 수신한다.
* @param[in] 프레임을 수신한 CAN 소켓 디스크립터
* @retval 0: 성공
* @retval -1: 실패
*/
int ReceiveCanFrame(const int sock)
{
/*
* CAN 프레임을 수신한다.
*/
struct can_frame frame;
int rx_bytes = read(sock, &frame, sizeof(frame));
if (rx_bytes < 0) {
perror("Fail to receive can frame - ");
return -1;
} else if (rx_bytes < (int)sizeof(struct can_frame)) {
printf("Incomplete can frame is received - rx_bytes: %d\n", rx_bytes);
return -1;
} else if (frame.can_dlc > CAN_FRAME_MAX_LEN) {
printf("Invalid dlc: %u\n", frame.can_dlc);
return -1;
}
/*
* 프레임 유형에 따라 처리한다.
*/
if (((frame.can_id >> 29) & 1) == 1) {
printf("Error frame is received\n");
} else if (((frame.can_id >> 30) & 1) == 1) {
printf("RTR frame is received\n");
} else {
if (((frame.can_id >> 31) & 1) == 1) {
printf("11bit long std can frame is received\n");
} else {
printf("29bit long ext can frame is received\n");
}
// TODO: 프레임 처리
}
return 0;
}
/// CAN ID
#define CAN_ID 0x13e
/**
* @brief 테스트 어플리케이션 메인함수
*/
int main(void)
{
/*
* CAN 인터페이스를 초기화한다.
*/
int sock = InitCanInterface("can0");
if (sock < 0) {
return -1;
}
/*
* CAN 데이터 프레임을 송신한다.
*/
uint8_t can_data[CAN_FRAME_MAX_LEN] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
TransmitCanFrame(sock, CAN_ID, can_data, sizeof(can_data));
/*
* CAN 프레임을 수신한다. (별도의 수신 쓰레드 내에서 호출 가능)
*/
ReceiveCanFrame(sock);
return 0;
}
파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음
'프로그래밍 > 리눅스 프로그래밍' 카테고리의 다른 글
프로세스 간 통신(IPC) 프로그래밍 - 메시지큐 (0) | 2021.01.19 |
---|---|
GPSd를 이용한 GNSS 프로그래밍 (0) | 2020.12.24 |
리눅스 프로그래밍 - 프로세스 종료 신호(SIGINT, SIGTERM) 후킹하기 (0) | 2020.08.21 |
리눅스 타이머 프로그래밍 (0) | 2020.07.31 |
리눅스 네트워크 프로그래밍 - UDP6(UDP over IPv6) 송수신 (0) | 2020.03.04 |
리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 비활성화하기 (0) | 2020.02.16 |
리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 활성화하기 (0) | 2020.02.15 |
리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 MAC 주소 확인하기 (0) | 2020.02.15 |