# 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 ,主要用於連接實用性不高的設備,例如車門控制

## 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

### STM32 Class frame

### 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解說

#### Arbitration field 波型解說

### 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 波型解說

### Classical Can Data
* Data Field - 0~8 bytes
* 真正的資料內容長度由 DLC 決定
#### Classical Can Data波型解說

### CRC Field

#### CRC Field 波型解說

### ACK

#### ACK Field

### EOF and IFS

## Can node & error count


### Can error count 情境1

### Can error count 情境2

## CAN ID 封包解說
* CAN上有三個device,為A,B,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 架構圖

## 邏輯分析儀解析解說

* 只要資料有連續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);
```