1. STM32 bxCAN 컨트롤러 및 SN65HVD230 기반 다중 노드 통신 구축 배경 (작성 배경 및 취지)
임베디드 시스템 및 차량용 네트워크에서 CAN(Controller Area Network) 통신은 노이즈 차단력과 비동기식 멀티마스터(Multi-Master) 제어 특성 덕분에 핵심 프로토콜로 사용됩니다. ESP32의 TWAI 드라이버 환경에서 STM32 마이크로컨트롤러 환경으로 전환하는 과정에서, STM32의 bxCAN(Basic Extended CAN) 페리페럴 제어 방식과 ST HAL 라이브러리의 구조적 차이로 인해 통신 불능 상태가 빈번하게 발생합니다.
본 포스팅에서는 STM32F4/F1 시리즈의 bxCAN IP를 활용하여 다중 노드 CAN 버스 네트워크를 하드웨어 레벨에서 구축하고, ST HAL API를 통한 프레임 송수신 구현 방법을 상세히 다룹니다. 또한 필터 은행(Filter Bank) 설정 미비나 Bit Timing 계산 오류로 인해 발생하는 수신 콜백(Callback) 미동작 및 Bus-Off(HAL_CAN_ERROR_BOF) 에러의 원인 분석 및 해결책을 제공합니다.
2. STM32 HAL_CAN 다중 노드 핵심 설정 3줄 요약 (핵심 요약)
- 하드웨어 인터페이스: STM32 PA11(CAN_RX)/PA12(CAN_TX) 핀을 SN65HVD230 트랜시버와 연결하고 양쪽 끝단 노드에 120Ω 종단 저항(Termination Resistor)을 배치합니다.
- 필터 필수 초기화: Reset 직후 모든 Filter Bank가 Disabled 상태이므로 HAL_CAN_ConfigFilter()를 통해 Filter Bank 0을 Enable하고 수락 ID를 설정하지 않으면 프레임 수신이 불가능합니다.
- 타이밍 및 시작: HAL_CAN_Init() 설정 후 반드시 HAL_CAN_Start() 및 HAL_CAN_ActivateNotification()을 호출하여 Rx FIFO Pending 인터럽트를 활성화해야 합니다.
3. STM32 bxCAN 레지스터 제어 및 HAL API 기반 송수신 구현 (본문 분석 및 구현)
3.1 하드웨어 토폴로지 및 SN65HVD230 트랜시버 연결
STM32 MCU의 CAN 컨트롤러는 차동 신호를 직접 생성하지 못하므로, 로직 레벨(3.3V TTL) 신호를 CAN_H / CAN_L 차동 신호로 변환하는 CAN 트랜시버(SN65HVD230 또는 MCP2551)가 필수적입니다.
| STM32 Pin (MCU) | SN65HVD230 Pin | CAN Bus / Signal Line | 설명 (Description) |
|---|---|---|---|
| PA12 (CAN1_TX) | TXD | - | MCU 송신 데이터 입력 |
| PA11 (CAN1_RX) | RXD | - | MCU 수신 데이터 출력 |
| GND | GND | - | 공통 그라운드 참조 전위 |
| 3.3V | VCC | - | 트랜시버 구동 전원 |
| - | CANH | CAN_H | 차동 신호 High 데이터 라인 |
| - | CANL | CAN_L | 차동 신호 Low 데이터 라인 |
버스 양쪽 최외각 노드에는 반드시 120Ω (1/4W, 1% 오차) 종단 저항을 연결하여 고주파 신호의 반사 파형(Signal Reflection)을 상쇄해야 합니다.
3.2 STM32 bxCAN Bit Timing 설정 및 계산 법칙
STM32 bxCAN의 통신 속도(Bitrate)는 APB1 Peripheral Clock 주파수를 기반으로 결정됩니다. 1 비트 시간(Bit Time)은 Nominal Bit Time(NBT) 사양에 따라 다음과 같이 분할됩니다.
$$\text{Bitrate} = \frac{\text{APB1 Clock}}{\text{Prescaler} \times (\text{SJW} + \text{BS1} + \text{BS2})}$$
- Sync_Seg: 1 Time Quanta (TQ) 고정
- BS1 (Phase Segment 1): Prop_Seg + Phase_Seg1
- BS2 (Phase Segment 2): Phase_Seg2
- Sample Point (샘플링 지점): $\frac{1 + \text{BS1}}{1 + \text{BS1} + \text{BS2}} \times 100\%$ (표준 권장치: 75% ~ 80%)
3.3 STM32 HAL CAN 수신 필터 및 메인 송신 구현 (C Code)
아래 코드는 APB1 Clock 42MHz 기준 500Kbps 통신 속도 및 Filter Bank 0 전체 수락(Accept All) 설정이 포함된 송신 코드입니다.
#include "main.h"
CAN_HandleTypeDef hcan1;
CAN_TxHeaderTypeDef txHeader;
uint8_t txData[8];
uint32_t txMailbox;
void CAN1_Init(void) {
/* APB1 = 42MHz, Prescaler = 6, BS1 = 11 TQ, BS2 = 2 TQ */
/* Bitrate = 42MHz / (6 * (1 + 11 + 2)) = 500 Kbps */
hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 6;
hcan1.Init.Mode = CAN_MODE_NORMAL;
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan1.Init.TimeSeg1 = CAN_BS1_11TQ;
hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;
hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = ENABLE;
hcan1.Init.AutoWakeUp = DISABLE;
hcan1.Init.AutoRetransmission = ENABLE;
hcan1.Init.ReceiveFifoLocked = DISABLE;
hcan1.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan1) != HAL_OK) {
Error_Handler();
}
/* Configure CAN Filter to 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 = CAN_FILTER_ENABLE;
sFilterConfig.SlaveStartFilterBank = 14;
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK) {
Error_Handler();
}
/* Start CAN Peripheral */
if (HAL_CAN_Start(&hcan1) != HAL_OK) {
Error_Handler();
}
}
void CAN1_Send_Message(uint32_t stdId, uint8_t *pData, uint8_t len) {
txHeader.StdId = stdId;
txHeader.ExtId = 0x01;
txHeader.RTR = CAN_RTR_DATA;
txHeader.IDE = CAN_ID_STD;
txHeader.DLC = len;
txHeader.TransmitGlobalTime = DISABLE;
/* Check mailbox availability and transmit */
if (HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) > 0) {
if (HAL_CAN_AddTxMessage(&hcan1, &txHeader, pData, &txMailbox) == HAL_OK) {
/* Transmission request successful */
}
}
}
3.4 STM32 HAL CAN Interrupt 수신 및 콜백 구현 (C Code)
STM32에서는 HAL_CAN_GetRxMessage() 폴링 방식보다 Rx FIFO Pending 인터럽트 핸들러를 이용하는 방식이 시스템 자원 소모를 최소화합니다.
CAN_RxHeaderTypeDef rxHeader;
uint8_t rxData[8];
void CAN1_Interrupt_Config(void) {
/* Activate Rx FIFO 0 Message Pending Interrupt */
if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK) {
Error_Handler();
}
}
/* HAL Callback triggered when new message arrives in FIFO0 */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
if (hcan->Instance == CAN1) {
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData) == HAL_OK) {
/* Process Received Frame */
uint32_t receivedId = rxHeader.StdId;
uint8_t length = rxHeader.DLC;
/* Insert Frame Handler Logic Here */
}
}
}
3.5 CAN 버스 중재 메커니즘(CSMA/CD+AMP) 및 다중 노드 충돌 분석
CAN 버스는 Non-Destructive Bitwise Arbitration(비파괴 비트 단위 중재) 방식을 사용합니다.
- Dominant Bit (우선 비트): 논리 0 (차동 전압 $\approx 2.0\text{V}$)
- Recessive Bit (열성 비트): 논리 1 (차동 전압 $\approx 0\text{V}$)
[CAN Bus Bitwise Arbitration Process]
Node A (ID: 0x120 -> 001 0010 0000) : Recessive(1) | Dominant(0) | Dominant(0) -> Wins Arbitration
Node B (ID: 0x123 -> 001 0010 0011) : Recessive(1) | Dominant(0) | Recessive(1) -> Loss (Switches to Rx)
Bus State : 0 | 0 | 0
동시 송신 시, ID 식별자 비트열에서 먼저 0(Dominant)을 출력하는 노드가 승리하며, 패배한 노드는 즉시 송신을 중단하고 수신 모드로 전환됩니다. 이에 따라 프레임 데이터 손실이나 버스 충돌 패킷 재전송에 의한 지연이 발생하지 않습니다.
4. SocketCAN 및 오실로스코프 기반 CAN 통신 디버깅 가이드 (개발을 위한 팁)
4.1 Linux SocketCAN 기반 프레임 덤프 및 검증
STM32 노드의 통신 동작을 검증하기 위해 리눅스 환경(Raspberry Pi 또는 USB-to-CAN 어댑터)에서 SocketCAN 도구를 활용하는 것이 가장 효율적입니다.
# Initialize CAN interface with 500Kbps Bitrate
sudo ip link set can0 type can bitrate 500000
sudo ip link set up can0
# Monitor received CAN messages in real-time
candump -tz can0
# Send test message frame from Linux host
cansend can0 123#11223344
4.2 오실로스코프 차동 신호 측정을 통한 하드웨어 디버깅
논리 분석기(Logic Analyzer)로 TXD/RXD 핀 신호를 모니터링함과 동시에 오실로스코프 채널 1을 CANH, 채널 2를 CANL에 연결하여 차동 전압($V_{DIFF} = V_{CANH} - V_{CANL}$)을 확인합니다.
- Recessive (논리 1): $V_{CANH} \approx 2.5\text{V}$, $V_{CANL} \approx 2.5\text{V} \implies V_{DIFF} \approx 0\text{V}$
- Dominant (논리 0): $V_{CANH} \approx 3.5\text{V}$, $V_{CANL} \approx 1.5\text{V} \implies V_{DIFF} \approx 2.0\text{V}$
5. STM32 CAN 드라이버 개발 시 흔히 하는 실수 및 예외 해결법 (흔히 하는 실수)
5.1 HAL_CAN_ConfigFilter() 미설정으로 인한 수신 콜백 미동작
- 증상: CAN 버스상에 물리적인 데이터 프레임이 전송되고 있으나 HAL_CAN_RxFifo0MsgPendingCallback() 함수가 호출되지 않음.
- 원인: STM32 bxCAN은 Reset 직후 모든 Filter Bank가 Disabled 상태로 초기화됩니다. 이 상태에서는 모든 수신 ID 프레임이 하드웨어 레벨에서 차단됩니다.
- 해결책: HAL_CAN_ConfigFilter()를 호출하여 Filter Bank 0을 활성화(CAN_FILTER_ENABLE)하고 FilterMaskIdHigh/Low를 0x0000으로 설정하여 모든 ID를 허용(Accept All)하도록 초기화해야 합니다.
5.2 Bit Timing 미스매치로 인한 HAL_CAN_ERROR_BOF (Bus-Off) 진입
- 증상: HAL_CAN_AddTxMessage() 호출 시 HAL_ERROR가 반환되며 hcan1.ErrorCode가 HAL_CAN_ERROR_BOF로 표시됨.
- 원인: Prescaler, BS1, BS2 레지스터 계산 오류로 샘플링 포인트가 표준 범위를 벗어나 노드 간 비트 동기화가 실패함. 이로 인해 Transmit Error Counter(TEC) 레지스터가 255를 초과하여 버스 보호를 위해 Bus-Off 상태로 진입합니다.
- 해결책: APB1 타이머 클럭을 정확히 측정한 후, 샘플링 포인트가 75%~80% 범위에 위치하도록 BS1과 BS2 비율(예: BS1=11 TQ, BS2=2 TQ)을 재설정하고 hcan1.Init.AutoBusOff = ENABLE로 지정하여 자동 복구를 활성화합니다.
6. STM32 bxCAN 멀티 노드 시스템 구축 결론 (마무리)
STM32 마이크로컨트롤러 환경에서 bxCAN 및 HAL 라이브러리를 활용한 다중 노드 CAN 통신 네트워크 구성 방법을 살펴보았습니다.
정확한 APB1 클럭 분주비를 통한 비트 타이밍 설정, HAL_CAN_ConfigFilter()를 통한 수신 필터 은행 초기화, 그리고 인터럽트 기반 Rx FIFO 콜백 핸들러 구현이 STM32 CAN 드라이버의 안정성을 결정짓는 핵심 요소입니다.