---
# System prepended metadata

title: CAN 2.0B 通訊協定深度解析：從硬體設計、波形原理到 STM32 實作開發
tags: ['protocols ']

---

# CAN 2.0B 通訊協定深度解析：從硬體設計、波形原理到 STM32 實作開發

* 本篇以CAN 2.0B為主

* CAN BUS架構基本上都是以 CAN 2.0A and CAN 2.0B為延伸
    * CAN Protocols: CAN 2.0A and CAN 2.0B.
    
* 多主多從（multi-master）
* 優先權由 CAN ID 決定：數值越小優先權越高（因為 0 代表 dominant）
* Error handling：CAN 有強大的錯誤檢測（Stuff Error, CRC Error, ACK Error 等）
* CAN: CRC是由IC處理而非軟體處理，這樣CAN的執行效率才會比較好
* CAN BUS 是差動雙線通訊（CAN_H / CAN_L），差動訊號範圍約為 ±2V 以上


* [CAN sample point調整及算法](https://hackmd.io/FArNADHMSgq1rSV8ZFvqYA)

## CAN SPEED 
* CAN SPEED : 125kbs、250kbs、500kbs、1Mbps(CAN2.0B最多到1MHZ，且不能向上支援CANFD，傳統 CAN 控制器無法解讀 CAN FD 的資料段)

* ISO 11898-1 定義Bit Time CAN (Sample point)
* ISO 11898-2 定義高速CAN，主要用於連接較高的設備，例如ECU
* ISO 11898-3 定義了低速CAN ，主要用於連接實用性不高的設備，例如車門控制


![image](https://hackmd.io/_uploads/HJLWd0aNbe.png)

## CAN 硬體

1. 收發器（Transceiver）的角色
    * CAN Bus 確實需要 收發器 (Transceiver) 作為硬體媒介。
    * 功能： 它負責將微控制器 (MCU) 的數位訊號（TTL 電位）轉換為匯流排上的差動訊號（Differential Signal），反之亦然。
2. 協定分析與軟體層
    * CAN Bus 的通訊協定可以透過軟體進行深度分析。
    * 運作： 軟體層負責處理資料編框 (Framing)、錯誤檢查 (CRC)、仲裁機制 (Arbitration) 以及過濾 ID。
    * 工具： 工程師通常使用 CAN Analyzer（如 CANoe, PCAN）或邏輯分析儀，配合軟體來解碼封包內容。
3. 終端電阻（Termination Resistor）的奧秘
    * 在 CAN_H 與 CAN_L 之間並聯電阻是必要的，標準做法是在匯流排的兩端各加上一顆 120Ω 的電阻（並聯後等效電阻為 60Ω）。
    * 為什麼是 120Ω？為什麼範圍在 60-120Ω 之間？
這與硬體上的訊號反射 (Signal Reflection) 與 阻抗匹配 (Impedance Matching) 有關

## CAN 外部震盪器

* CAN Spec 有提出精準度誤差需要在1.58%內所以需要外部震盪器
* 若沒有外部震盪器使用RC震盪器會造成底下幾個影響
#### CAN 規範中，由於存在位元填充 (Bit Stuffing) 與重新同步 (Re-synchronization) 機制，硬體對時序的容忍度極低。

1. 資料偏差 (Data Drift / Phase Error)

    * 物理影響： RC 震盪器容易受溫度和電壓波動影響（頻率漂移）。
    * 結果： 當發送端認為一秒鐘走了 1M 個位元，而接收端的時鐘慢了 2%，接收端就會在錯誤的時間點採樣（Sampling Point），導致將 0 誤判為 1，引發 CRC Error 或 Form Error。

2. 抗雜訊能力下降 (Degraded Noise Immunity)
    * 物理影響： RC 震盪器的抖動（Jitter）較大。
    * 結果： CAN Bus 靠著多次採樣來抵抗電磁雜訊。如果時鐘本身就不穩，採樣窗口會變得模糊（Time Segment 偏移），這會讓原本在電磁干擾環境下能修正的訊號，變成無法辨識的雜訊。

3. 相容性與互操作性差 (Interoperability Issues)
    * 物理影響： 每一台設備的 RC 電路誤差方向不同。
    * 結果： 在實驗室單對單通訊可能沒事，但當整車或整個工業網路（Multi-node）連起來時，累積的誤差會讓某些節點完全無法加入通訊（Bus Off），導致系統整合困難。

#### 這不是一個隨機的數字。在 CAN 協定中，為了確保訊號同步，節點會在偵測到「隱性位元轉顯性位元」時進行調整。

* 若誤差超過 1.58%，即使有重新同步機制，採樣點仍會偏移到下一個位元，導致整幀資料報廢。因此，高性能的 CAN 控制器通常強制要求使用 石英晶體 (Crystal) 或 陶瓷諧振器 (Resonator)。

## CAN 2.0B Class frame

![image](https://hackmd.io/_uploads/rJRGdRpVWl.png)

### STM32 Class frame
![image](https://hackmd.io/_uploads/BkwfYnbcJg.png)

### Arbitration field

* 擴展幀（29-bit ID）
    * Base ID（11 bit）
    * Extended ID ( 18 bit )
    * SRR（Substitute Remote Request）
        * 1（高優先權）
    * IDE:（Identifier Extension）
        * 1（表示是擴展幀）
* RTR （remote transmission request，RTR），實際應用都設定為0
    * 0：Data Frame（有資料）。
    * 1：Remote Frame（請求資料）。
* RTR解說
![image](https://hackmd.io/_uploads/BkpuqFESZg.png)

#### Arbitration field 波型解說
![image](https://hackmd.io/_uploads/rJJocY4SWx.png)


### Control field
* 控制欄位（Control field，6 bit）
    * ID 擴展（identifier extension，IDE）
        * 0: Standard frame
        * 1: Extended frame
    * R0保留位元 （reserved）
        * 預設為位元 0。
    * DLC資料長度編碼（data length code，DLC）
        * 使用 4 bit 表示後續的資料長度。表示資料長度（0~8 bytes）

#### Control field 波型解說
![image](https://hackmd.io/_uploads/S1ONjFVBWl.png)


### Classical Can Data

* Data Field - 0~8 bytes
    * 真正的資料內容長度由 DLC 決定

#### Classical Can Data波型解說

![image](https://hackmd.io/_uploads/BJeYjYEBWx.png)

### CRC Field

![image](https://hackmd.io/_uploads/BJcasK4BZg.png)

#### CRC Field 波型解說

![image](https://hackmd.io/_uploads/SkvJnYEr-x.png)

### ACK

![image](https://hackmd.io/_uploads/Bye9hFEB-l.png)

#### ACK Field 

![image](https://hackmd.io/_uploads/Sydnht4SZe.png)

### EOF and IFS

![image](https://hackmd.io/_uploads/HyrlptEr-x.png)


## Can node & error count 

![image](https://hackmd.io/_uploads/rkJ46KNr-e.png)

![image](https://hackmd.io/_uploads/r1FNpFVSbe.png)

### Can error count 情境1
![image](https://hackmd.io/_uploads/SyqUaFVSZe.png)

### Can error count 情境2
![image](https://hackmd.io/_uploads/HyqdatVBWl.png)


## CAN ID 封包解說

* CAN上有三個device，為A，Ｂ，C。

* A device有接收ID：202，302；發送ID：101，102；

* B device有接收ID：101，303；發送ID：201，202；

* C device有接收ID：101，102，201；發送ID：302，303；

* 如果A device發送了ID為101的Commad，因為B device和C device都有接收為101的ID，那麼B device和C device都可以接收到這條Commad。

* 如果B device發送了ID為202的Commad，因為A device有接收為202的ID，那麼A device可以接收到這條Commad。

* 如果A device發送了ID為102的Commad，因為C device有接收為102的ID，那麼A device可以接收到這條Commad。



## CAN Transceiver: Master and Slave 架構圖
![image](https://hackmd.io/_uploads/B1ilLFErbe.png)


## 邏輯分析儀解析解說

![22F31326](https://hackmd.io/_uploads/BJRiafN9yx.png)

* 只要資料有連續5個0或連續5個1 就會塞入一個stuff bit
    - 如果是5個0 就塞一個 1 , 5個1 就塞一個0
    - CAN控制器 會自己移除這個bit，但是邏輯分析儀上面會捕捉到這個bit

## STM32G4上實現CAN BUS
### main
```
unsigned char CANBUS_105[8] = {0xE0, 0x5F, 0x2B, 0x91, 0xC7, 0x08, 0xD4, 0x3E};
unsigned char CANBUS_125[8] = {0x55, 0xE7, 0x0C, 0x3D, 0xA8, 0x92, 0x6F, 0x1B};
unsigned char CANBUS_135[8] = {0x55, 0xAA, 0xB9, 0x98, 0x23, 0xF1, 0x6D, 0x1B};
unsigned char CANBUS_145[8] = {0xF0, 0x1D, 0x73, 0x8A, 0x9E, 0x64, 0x2C, 0xD7};
unsigned char CANBUS_155[8] = {0x39, 0xBF, 0x27, 0x54, 0xCE, 0x6A, 0x11, 0x90};
unsigned char CANBUS_165[8] = {0xCD, 0xAF, 0x67, 0x94, 0xEB, 0x92, 0x72, 0xAF};
unsigned char CANBUS_185[8] = {0x6E, 0x14, 0xA1, 0xC3, 0x59, 0x0F, 0xDB, 0x80};
unsigned char CANBUS_195[8] = {0xEE, 0x4B, 0xD2, 0x88, 0x9C, 0xFA, 0x75, 0x31};

int main(void)
{

	HAL_Init();

	SystemClock_Config();
	Init_PeripheralClock();
	Init_GPIO();
	Init_CAN();
	Init_Interrupt();

	while (1)
	{
		if (Extended_CAN_Transmit(0x6fc105, CANBUS_105, 8) == 0)
		  HAL_Delay(2);

		if (Extended_CAN_Transmit(0x6fc125, CANBUS_125, 8) == 0)
		  HAL_Delay(2);

		if (Extended_CAN_Transmit(0x6fc135, CANBUS_135, 8) == 0)
		  HAL_Delay(2);

		if (Extended_CAN_Transmit(0x6fc145, CANBUS_145, 8) == 0)
		  HAL_Delay(2);

		if (Extended_CAN_Transmit(0x6fc155, CANBUS_155, 8) == 0)
		  HAL_Delay(2);

		if (Extended_CAN_Transmit(0x6fc165, CANBUS_165, 8) == 0)
		  HAL_Delay(2);

		if (Extended_CAN_Transmit(0x6fc185, CANBUS_185, 8) == 0)
		  HAL_Delay(2);

		if (Extended_CAN_Transmit(0x6fc195, CANBUS_195, 8) == 0)
		  HAL_Delay(2);
	}

}
```
### Init_Interrupt 
```
void Init_Interrupt(void)
{
    uint32_t encodePriority = NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 1, 0);
    NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

    NVIC_SetPriority(FDCAN1_IT0_IRQn, encodePriority);
    NVIC_EnableIRQ(FDCAN1_IT0_IRQn);
}
```
### Init_GPIO 
```
/***************************************************************************************************
Function Name:
    void Init_GPIO(void)
Input:
    NULL
Output:
    NULL
Comment:
    Initialize GPIO.
***************************************************************************************************/
void Init_GPIO(void)
{
	GPIO_InitTypeDef CAN_MUX = {0};
	RCC_PeriphCLKInitTypeDef PeriphClkCANInit = {0};

	// FDCAN clock setting PCLK1（170MHz）
	PeriphClkCANInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN;
	PeriphClkCANInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PCLK1;
	HAL_RCCEx_PeriphCLKConfig(&PeriphClkCANInit);

	/**FDCAN1 GPIO Configuration
	PA11     ------> FDCAN1_RX
	PA12     ------> FDCAN1_TX
	*/
	CAN_MUX.Pin = GPIO_PIN_11 | GPIO_PIN_12;
	CAN_MUX.Mode = GPIO_MODE_AF_PP;
	CAN_MUX.Pull = GPIO_NOPULL;
	CAN_MUX.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	CAN_MUX.Alternate = GPIO_AF9_FDCAN1;
	HAL_GPIO_Init(GPIOA, &CAN_MUX);
}
```
### Init_CAN
```
FDCAN_HandleTypeDef hfdcan1;
FDCAN_RxHeaderTypeDef RxHeader;
unsigned char RxData[8];

const unsigned char dlc_to_len[16] =
{
    0, 1, 2, 3, 4, 5, 6, 7,
    8, 12, 16, 20, 24, 32, 48, 64
};

CAN_DataBuffer can_rx_table[MAX_SUPPORTED_CAN_IDS] =
{
    {0x006FC197, {0}, 0},
    {0x006FC200, {0}, 0},
    {0x006FC100, {0}, 0},
    {0x006DC197, {0}, 0},
    {0x006EE100, {0}, 0},
};

void Init_CAN(void)
{
	HAL_FDCAN_Init(&hfdcan1);

	Init_CAN_BAUDRATE();

	if(HAL_FDCAN_Start(&hfdcan1)!=HAL_OK) Error_Handler();

//	HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, FDCAN_RX_FIFO0);
	HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE | FDCAN_IT_RX_FIFO1_NEW_MESSAGE, FDCAN_RX_FIFO0);

}
```
### Init_CAN_BAUDRATE
```
/**************************************************************************************************
Function Name:
    void Init_CAN_BAUDRATE(void)
Input:
    None.
Output:
    None.
Comment:
    CAN CAN_BAUDRATE Setting.
**************************************************************************************************/
void Init_CAN_BAUDRATE(void)
{
	hfdcan1.Instance = FDCAN1;
	hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV1;
	hfdcan1.Init.FrameFormat = FDCAN_FRAME_CLASSIC;
	hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
	hfdcan1.Init.AutoRetransmission = ENABLE;
	hfdcan1.Init.TransmitPause = DISABLE;
	hfdcan1.Init.ProtocolException = DISABLE;

//	  hfdcan1.Init.NominalPrescaler = (MY_CAN_BITRATE_CONFIG >> 16) & 0xFF;
//	  hfdcan1.Init.NominalSyncJumpWidth = (MY_CAN_BITRATE_CONFIG >> 8) & 0x7F;
//	  hfdcan1.Init.NominalTimeSeg1 = (MY_CAN_BITRATE_CONFIG >> 0) & 0x7F;
//	  hfdcan1.Init.NominalTimeSeg2 = (MY_CAN_BITRATE_CONFIG >> 24) & 0x7F;

//	Nominal: 500 kbps with 170 MHz clock ,80% Sample Point
	hfdcan1.Init.NominalPrescaler = 10;
	hfdcan1.Init.NominalSyncJumpWidth = 0;
	hfdcan1.Init.NominalTimeSeg1 = 27;
	hfdcan1.Init.NominalTimeSeg2 = 6;

//	// Nominal: 500 kbps with 170 MHz clock, approx. 50% Sample Point
//	// FDCAN Clock must be 170 MHz
//	hfdcan1.Init.NominalPrescaler = 0;
//	hfdcan1.Init.NominalSyncJumpWidth = 8;
//	hfdcan1.Init.NominalTimeSeg1 = 85;
//	hfdcan1.Init.NominalTimeSeg2 = 85;

//	hfdcan1.Init.NominalPrescaler = 20;
//	hfdcan1.Init.NominalSyncJumpWidth = 4;
//	hfdcan1.Init.NominalTimeSeg1 = 13;
//	hfdcan1.Init.NominalTimeSeg2 = 3;

// Nominal: 1MHZ with 170 MHz clock 80% Sample Point
//	hfdcan1.Init.NominalPrescaler = 10;
//	hfdcan1.Init.NominalSyncJumpWidth = 4; // Synch Jump Width <= TimeSeg2
//	hfdcan1.Init.NominalTimeSeg1 = 13;
//	hfdcan1.Init.NominalTimeSeg2 = 3;

	hfdcan1.Init.StdFiltersNbr = 1;
	hfdcan1.Init.ExtFiltersNbr = 1;
	hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;

	FDCAN_FilterTypeDef RxConfig = {0};
	RxConfig.IdType = FDCAN_EXTENDED_ID;
	RxConfig.FilterIndex = 0;
	RxConfig.FilterType = FDCAN_FILTER_MASK;
	RxConfig.FilterConfig = FDCAN_RX_FIFO0;
	RxConfig.FilterID1 = 0x00000000;//0x006FC197;
	RxConfig.FilterID2 = 0xFFFFFFFF;

	FDCAN_FilterTypeDef TxConfig = {0};
	TxConfig.IdType = FDCAN_EXTENDED_ID;
	TxConfig.FilterIndex = 0;
	TxConfig.FilterType = FDCAN_FILTER_MASK;
	TxConfig.FilterConfig = FDCAN_RX_FIFO1;
	TxConfig.FilterID1 = 0x00000000;
	TxConfig.FilterID2 = 0xFFFFFFFF;

    if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK)
    {
        Error_Handler();
    }
    if (HAL_FDCAN_ConfigFilter(&hfdcan1, &RxConfig) != HAL_OK)
    {
        Error_Handler();
    }
    if (HAL_FDCAN_ConfigFilter(&hfdcan1, &TxConfig) != HAL_OK)
    {
        Error_Handler();
    }
    if (HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_REJECT, FDCAN_REJECT, FDCAN_REJECT_REMOTE, FDCAN_REJECT_REMOTE) != HAL_OK)
    {
        Error_Handler();
    }
}
```
### GetCAN_DLC
```
/**************************************************************************************************
Function Name:
    unsigned char GetCAN_DLC(unsigned char len)
Input:
    len: CAN data long, for Classic CAN.
Output:
    None.
Comment:
    CAN Data long.
**************************************************************************************************/
unsigned char GetCAN_DLC(unsigned char len)
{
    switch (len)
    {
        case 0: return FDCAN_DLC_BYTES_0;
        case 1: return FDCAN_DLC_BYTES_1;
        case 2: return FDCAN_DLC_BYTES_2;
        case 3: return FDCAN_DLC_BYTES_3;
        case 4: return FDCAN_DLC_BYTES_4;
        case 5: return FDCAN_DLC_BYTES_5;
        case 6: return FDCAN_DLC_BYTES_6;
        case 7: return FDCAN_DLC_BYTES_7;
        case 8: return FDCAN_DLC_BYTES_8;
        default: return FDCAN_DLC_BYTES_8;
    }
    return 0;
}
```
### Extended_CAN_Transmit
```
/**************************************************************************************************
Function Name:
    unsigned char Extended_CAN_Transmit(unsigned long id, unsigned char *data, unsigned char len)
Input:
    id        - CAN identifier.
    data	  - CAN data be 0  ~ 7
    len       - Data long be 0 ~ 7
Output:
    None.
Comment:
    CAN reception function. This function will transfer the received data packet address to
buffer.
**************************************************************************************************/
unsigned char Extended_CAN_Transmit(unsigned long id, unsigned char *data, unsigned char len)
{
	FDCAN_TxHeaderTypeDef TxHeader;
	TxHeader.Identifier = id;
	TxHeader.IdType = FDCAN_EXTENDED_ID;
	TxHeader.TxFrameType = FDCAN_DATA_FRAME;
	TxHeader.DataLength = GetCAN_DLC(len);
	TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
	TxHeader.BitRateSwitch = FDCAN_BRS_OFF;
	TxHeader.FDFormat = FDCAN_CLASSIC_CAN;
	TxHeader.TxEventFifoControl = FDCAN_STORE_TX_EVENTS;
	TxHeader.MessageMarker = 0;

    uint32_t timeout = 1000;
    uint32_t start_tick = HAL_GetTick();

    while (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, data) != HAL_OK)
    {
        if ((HAL_GetTick() - start_tick) > timeout)
        {
            HAL_FDCAN_Stop(&hfdcan1);
            HAL_FDCAN_Start(&hfdcan1);
            return 0;
        }
        HAL_Delay(1);
    }
	return 0;
}
```
### Extended_CAN_Receive
```
/**************************************************************************************************
Function Name:
    void Extended_CAN_Receive(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
Input:
    id        - CAN identifier.
    RxFifo0ITs	  - CAN data be 0  ~ 7
Output:
    None.
Comment:
    CAN reception function. This function will transfer the received data packet address to
buffer.
**************************************************************************************************/
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
	if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != 0)
    {
        if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK)
        {
        	Error_Handler();
            return;
        }
		unsigned char length = dlc_to_len[RxHeader.DataLength & 0xF];
//		unsigned char length = (RxHeader.DataLength <= 8) ? RxHeader.DataLength : 8;

		for (int i = 0; i < MAX_SUPPORTED_CAN_IDS; i++)
		{
			if (can_rx_table[i].can_id == RxHeader.Identifier)
			{
				memcpy(can_rx_table[i].data, RxData, length);
//				for (size_t j = 0; j < length; j++)
//				{
//					can_rx_interrupt_counter ++;
//				    can_rx_table[i].data[j] = RxData[j];
//				}
				can_rx_table[i].length = length;
				break;
			}
		}
	}
}
```

```
void HAL_FDCAN_TxEventFifoCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t eventFifoITs)
{

}
```

### CAN.h 設計
```
#define CAN_NBTP_125kHz              0x064F0C02UL
#define CAN_NBTP_250kHz              0x06270C02UL
#define CAN_NBTP_500kHz              0x06130C02UL
#define CAN_NBTP_1MHz                0x06090C02UL

#define CAN_DATA 					8

#define MAX_SUPPORTED_CAN_IDS 		10   // 根據需求調整

typedef struct
{
	unsigned long can_id;
	unsigned char data[CAN_DATA];
	unsigned char length;
}CAN_DataBuffer;

void Init_CAN_IO(void);
void Init_CAN(void);
void Init_CAN_BAUDRATE(void);
void Init_Interrupt(void);
unsigned char GetCAN_DLC(unsigned char len);
unsigned char Extended_CAN_Transmit(unsigned long id, unsigned char *data, unsigned char len);
void Extended_CAN_Receive(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs);
```