1. STM32 bxCAN 드라이버 및 임베디드 CAN 소프트웨어 스택 설계 배경
임베디드 시스템 및 전장 시스템 개발에서 CAN(Controller Area Network) 통신은 신뢰성 높은 차량용 및 산업용 데이터 전송을 위한 표준 프로토콜입니다. CAN 드라이버는 Microcontroller Unit(MCU)의 CAN 컨트롤러 하드웨어와 상위 레이어 애플리케이션 간 인터페이스를 제공하는 최하위 저수준(Low-Level) 소프트웨어 계층입니다.
실무 개발 환경에서 CAN 드라이버를 올바르게 구현하지 않으면 메시지 유실, 버스 오프(Bus-Off) 상태에 따른 시스템 정지, 인터럽트 교착 상태(Deadlock) 등 치명적인 결함이 발생할 수 있습니다. 본 문서에서는 ESP32 및 Zephyr 환경을 제외하고, 가장 광범위하게 쓰이는 STM32 bxCAN(HAL Library), FreeRTOS 태스크 기반 아키텍처, 그리고 Linux SocketCAN 스택 구조를 중심으로 계층별 아키텍처 분석 및 구현 가이드를 제공합니다.
2. STM32 HAL_CAN 및 Linux SocketCAN 핵심 결론 요약
- STM32 bxCAN 구현: HAL_CAN_Init() 실행 후 HAL_CAN_ConfigFilter()를 통해 최소 1개 이상의 Rx 필터 은행(Filter Bank)을 활성화해야 HAL_CAN_RxFifo0MsgPendingCallback() 인터럽트가 동작합니다.
- FreeRTOS 스택 구조: ISR 내에서는 xQueueSendFromISR()을 호출하여 수신 데이터를 버퍼링하고, 별도의 워커 태스크(Worker Task)에서 이를 수신 처리하도록 설계하여 ISR 지연을 최소화해야 합니다.
- Linux SocketCAN: Socket API 기반(AF_CAN, SOCK_RAW)으로 인터페이스를 제어하며 candump, cansend 명령어를 통해 커널 수준의 네트워크 통신을 수행합니다.
3. STM32 bxCAN 드라이버 및 임베디드 CAN 소프트웨어 스택 상세 분석
3.1 임베디드 CAN 통신 소프트웨어 아키텍처 계층
CAN 통신 스택은 하드웨어 종속성을 분리하고 모듈성을 확보하기 위해 다중 레이어로 구조화됩니다.
| 계층 (Layer) | 주요 역할 및 기능 | 구현 예시 |
|---|---|---|
| Application Layer | 사용자 로직, 진단 프로토콜(UDS, OBD-II) 처리 | Custom App, CANopen App |
| Protocol Stack Layer | 상위 통신 프로토콜 매핑 및 메시지 팩/언팩 | CANopen, J1939, AUTOSAR CanIf |
| Driver / HAL Layer | CAN 컨트롤러 레지스터 제어, TX/RX 버퍼 관리 | STM32 HAL_CAN, Linux SocketCAN |
| Hardware Layer | 온칩 CAN 컨트롤러 레지스터 및 외부 CAN Transceiver | STM32 bxCAN, MCP2515, TJA1050 |
3.2 STM32 bxCAN HAL 드라이버 초기화 및 필터 설정 코드
STM32 bxCAN IP는 Power-On Reset 시 모든 필터 은행이 비활성화(Disabled) 상태로 설정됩니다. 명시적인 필터 설정 없이는 데이터를 수신할 수 없습니다.
#include "stm32f4xx_hal.h"
CAN_HandleTypeDef hcan1;
void STM32_CAN_Init(void) {
// 1. CAN Hardware Peripheral Configuration
hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 6; // APB1 Clock 42MHz -> Bit Rate 500Kbps
hcan1.Init.Mode = CAN_MODE_NORMAL;
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan1.Init.TimeSeg1 = CAN_BS1_11TQ;
hcan1.Init.TimeSeg2 = CAN_BS2_2TQ; // Sample Point = (1 + 11) / (1 + 11 + 2) = 85.7%
hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = ENABLE; // Automatic Bus-Off Management
hcan1.Init.AutoWakeUp = DISABLE;
hcan1.Init.AutoRetransmission = ENABLE;
hcan1.Init.ReceiveFifoLocked = DISABLE;
hcan1.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan1) != HAL_OK) {
/* CAN Initialization Error Handler */
Error_Handler();
}
// 2. CAN Filter Configuration (Accept All Messages)
CAN_FilterTypeDef sFilterConfig;
sFilterConfig.FilterBank = 0;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
sFilterConfig.SlaveStartFilterBank = 14;
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK) {
/* Filter Configuration Error Handler */
Error_Handler();
}
// 3. Start CAN Controller & Activate Interrupts
if (HAL_CAN_Start(&hcan1) != HAL_OK) {
/* Start Error Handler */
Error_Handler();
}
if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK) {
/* Interrupt Activation Error Handler */
Error_Handler();
}
}
3.3 FreeRTOS 환경에서의 CAN Rx Queue 버퍼링 및 수신 처리
FreeRTOS 환경에서는 ISR(Interrupt Service Routine) 내에서 긴 처리 시간을 소모해서는 안 됩니다. ISR에서 Rx FIFO 프레임을 즉시 큐로 전달하고, 비동기 수신 태스크에서 데이터를 처리하도록 구현합니다.
#include "FreeRTOS.h"
#include "queue.h"
#include "task.h"
#include "stm32f4xx_hal.h"
extern CAN_HandleTypeDef hcan1;
QueueHandle_t xCANRxQueue = NULL;
typedef struct {
uint32_t std_id;
uint8_t dlc;
uint8_t data[8];
} CAN_Message_t;
// FreeRTOS CAN Rx Worker Task
void vCANRxTask(void *pvParameters) {
CAN_Message_t rx_msg;
for (;;) {
// Block until CAN message arrives in Queue
if (xQueueReceive(xCANRxQueue, &rx_msg, portMAX_DELAY) == pdTRUE) {
/* Process Received CAN Frame */
}
}
}
// STM32 CAN Rx FIFO0 Interrupt Callback
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
CAN_RxHeaderTypeDef rx_header;
CAN_Message_t rx_msg;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (hcan->Instance == CAN1) {
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_msg.data) == HAL_OK) {
rx_msg.std_id = rx_header.StdId;
rx_msg.dlc = rx_header.DLC;
// Send message to Queue from ISR
xQueueSendFromISR(xCANRxQueue, &rx_msg, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
}
3.4 Linux SocketCAN 네트워킹 드라이버 제어
Linux Kernel은 CAN 인터페이스를 네트워크 소켓 abstraction으로 다룹니다. SocketCAN 메커니즘을 통해 POSIX 소켓 API(socket(), bind(), write(), read())를 그대로 활용할 수 있습니다.
Linux SocketCAN CLI 명령어
# Set CAN Bitrate to 500Kbps and Bring Up Interface
sudo ip link set can0 type can bitrate 500000
sudo ip link set can0 up
# Monitor Incoming CAN Messages
candump can0
# Transmit Standard CAN Frame (ID: 0x123, Data: 11 22 33 44 55 66 77 88)
cansend can0 123#1122334455667788
Linux C SocketCAN Application Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int open_socketcan(const char *ifname) {
int s;
struct sockaddr_can addr;
struct ifreq ifr;
if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
perror("Socket creation failed");
return -1;
}
strcpy(ifr.ifr_name, ifname);
ioctl(s, SIOCGIFINDEX, &ifr);
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Bind failed");
close(s);
return -1;
}
return s;
}
4. STM32 및 Linux SocketCAN 디버깅 및 개발 팁
- STM32 Auto-Recovery 설정: hcan.Init.AutoBusOff = ENABLE; 설정을 적용하면 노이즈나 버스 에러 카운터 누적으로 인해 Bus-Off 상태로 진입했을 때 MCU가 하드웨어적으로 버스 재접속을 자동 시도합니다.
- Linux SocketCAN 가상 인터페이스(vcan) 활용: 실제 타깃 하드웨어 없이 개발 및 테스트를 수행하려면 Linux vcan(Virtual CAN) 커널 모듈을 로드하여 디버깅환경을 구축할 수 있습니다.
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0
5. CAN 드라이버 개발 시 흔히 하는 실수 및 예외 해결법
5.1 HAL_CAN_ConfigFilter() 미설정으로 인한 수신 콜백 미동작
- 증상: CAN 하드웨어 버스 상에 물리적 신호가 존재하나 STM32 MCU에서 HAL_CAN_RxFifo0MsgPendingCallback() 함수가 호출되지 않음.
- 원인: STM32 bxCAN은 Reset 시 모든 필터가 Disabled 상태가 되어 수신 프레임을 모두 차단합니다.
- 해결책: HAL_CAN_ConfigFilter()를 호출하여 Filter Bank 0을 ENABLE로 설정해야 합니다.
5.2 Bit Timing 계산 오류로 인한 HAL_CAN_ERROR_BOF (Bus-Off) 진입
- 증상: 프레임 전송 시 HAL_CAN_AddTxMessage()가 실패하고, TEC(Transmit Error Counter) 레지스터가 255를 초과하여 Bus-Off 상태 진입.
- 원인: APB1 Clock 주파수 기반 Sample Point 계산 오류로 인한 비트 동기화 실패.
- 해결책: ISO 11898 표준 가이드라인에 따라 Sample Point가 75% ~ 85% 범위 내에 오도록 Prescaler, BS1, BS2 레지스터 값을 재조정해야 합니다.
$$\text{Baud Rate} = \frac{\text{APB1 Clock}}{\text{Prescaler} \times (1 + \text{BS1} + \text{BS2})}$$
6. STM32 bxCAN 및 Linux SocketCAN 스택 마이그레이션 결론
본 문서에서는 STM32 bxCAN HAL 드라이버 구현 방식, FreeRTOS 기반 Queue 아키텍처, 그리고 Linux SocketCAN 활용 기법을 다루었습니다. MCU와 Linux OS 환경 간 시스템 구조 차이를 이해하고 적절한 드라이버 API와 버퍼링 구조를 설계하는 것이 안정적인 전장/임베디드 CAN 통신 스택 구축의 핵심입니다.