完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第92章 STM32H7的FDCAN總線應用之雙FDCAN實現(支持經典CAN)
本章節為大家講解STM32H7的帶兩個FDCAN控制器使用方法。CAN FD中的FD含義就是flexible data,靈活數據通信,且波特率可以和仲裁階段波特率不同。
92.1 初學者重要提示
92.2 FDCAN硬件接口設計
92.3 FDCAN基礎知識
92.4 FDCAN驅動代碼實現
92.5 雙FDCAN測試的接線盒跳線帽說明
92.6 開發板和H7-TOOL的FDCAN助手測試
92.7 實驗例程設計框架
92.8 實驗例程說明(MDK)
92.9 實驗例程說明(IAR)
92.10 總結
92.1 初學者重要提示
1、 FDCAN相關知識點可以看第90和91章。
2、 CAN菊花鏈組網時,在兩端接分別接120 Ω的終端電阻。
http://www.armbbs.cn/forum.php?mod=viewthread&tid=104793 。
3、 FDCAN控制器外接的PHY芯片輸出的是差分信號,組網接線時,注意是CANL接CANL,CANH接CANH。
4、 經典CAN每幀最大8字節,FDCAN每幀最大64字節。
5、 CAN不接外置PHY芯片,通信測試方法:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=104684 。
6、 關於CAN總線是否需要供地的問題:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=104704
7、 CAN組網只有一個節點的情況下,接示波器看不到FDCAN數據幀波形。
8、 特別推薦瑞薩的CAN入門中英文手冊,做的非常好:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=14546
9、 帶隔離功能的FDCAN芯片搜集:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=90420
10、 除了本章提供的基於ST HAL庫實現的雙FDCAN通信,再提供個基於CMSIS-Driver的:
基於STM32H7的CMSIS-Driver驅動實現雙CAN FD和雙經典CAN兩種方式案例發布
http://www.armbbs.cn/forum.php?mod=viewthread&tid=105369
92.2 FDCAN硬件接口設計
STM32H7帶了兩個FDCAN控制器,然后外接物理層PHY芯片就可以使用了。FDCAN1和FDCAN2外接芯片原理圖如下:
使用的PHY芯片SN65HVD230即支持經典CAN,也支持FDCAN。PHY芯片輸出的是差分信號,邏輯0或者邏輯1的電平效果如下:http://www.armbbs.cn/forum.php?mod=viewthread&tid=30855
92.3 FDCAN基礎知識
FDCAN的基礎知識在第90已經做了詳細說,這里補充些本章要用到的知識點。
92.3.1 經典CAN和FDCAN的區別
CAN-FD的開發可以滿足需要更高帶寬的通信網絡需求。每幀最多具有64個字節的CAN-FD以及將比特率提高到最大的可能性,使數據階段要快8倍,在第二個仲裁階段要恢復到正常的比特率。通過以下方式確保數據傳輸的完整性:
(1)17級多項式對最大16字節的有效載荷進行CRC。
(2)21級多項式對16到64字節之間的有效載荷進行校驗。
標准幀和CAN FD的區別:
標識符后,CAN 2.0和CAN-FD具有不同的作用:
(1)CAN 2.0發送RTR位以精確確定幀類型:數據幀(RTR為主要)或遠程幀(RTR)是隱性的)。
(2)由於CAN-FD僅支持數據幀,因此始終發送占優勢的RRS(保留)。
IDE位保持在相同位置,並以相同的動作來區分基本格式(11位標識符)。請注意,在擴展格式的情況下,IDE位以顯性或隱性方式傳輸(29位標識符)。
與CAN 2.0相比,在CAN-FD幀中,在控制字段中添加了三個新位:
(1)擴展數據長度(EDL)位:隱性表示幀為CAN-FD,否則該位為顯性(稱為R0)在CAN 2.0幀中。
(2)比特率切換(BRS):指示是否啟用兩個比特率(例如,當數據階段位以不同的比特率傳輸到仲裁階段)。
(3)錯誤狀態指示器(ESI):指示節點處於錯誤活動模式還是錯誤被動模式。
控制字段的最后一部分是數據長度代碼(DLC),它具有相同的位置和相同的長度(4位),用於CAN 2.0和CAN-FD。 DLC功能在CAN-FD和CAN 2.0中相同,但CAN-FD有很小變化(下表中的詳細信息)。 CAN-FD擴展幀允許單個消息中發送64個數據字節,而CAN 2.0有效負載數據最多可以發送8個字節。
通過增加有效載荷數據的數據字段來改善網絡帶寬,因為需要更少的包處理。 同時,通過為CRC添加更多位來增強消息完整性:
(1)如果有效載荷數據最多為16個字節,則CRC以17位編碼。
(2)如果有效載荷數據大於20(16)個字節,則CRC以21位編碼。
另外,為了確保CAN-FD幀的魯棒性,填充位機制支持CRC字段。下表總結了CAN-FD和CAN 2.0之間的主要區別。 提供的主要功能與CAN 2.0相比,CAN FD的改進之處在於數據有效負載的增加和速度的提高由CAN-FD中可用的BRS,EDL和ESI位來確保。
下表可幫助用戶簡化將STM32設備中的CAN 2.0協議升級到CAN-FD協議的過程。該表還指定了FDCAN的改進。與傳統的BxCAN(基本擴展CAN)相比,FDCAN具有許多優勢,包括更快的數據傳輸速度。速率和數據字節數的擴展,減少了幀開銷。 總線負載也可以減少。 傳輸和接收中消息數量的增加要求RAM存儲器的改進:
鑒於BxCAN的兼容性,BxCAN開發人員可以輕松地遷移到FDCAN,因為FDCAN可以無需對整個系統設計進行修訂即可實施。 FDCAN包含所有BxCAN功能,並滿足新應用程序的要求。
92.3.2 STM32H7的FDCAN1和FDCAN2的區別
僅FDCAN1支持TTCAN時間觸發通信,而FDCAN2不支持。
92.3.3 FDCAN支持的最高速度
經典CAN是1Mbps,CAN FD最高2Mbps,CAN FD-SiC是5-8Mbps,CAN XL是10Mbps。
92.3.4 FDCAN的主時鍾選擇
FDCAN1和FDCAN2支持三種時鍾源HSE,PLL1Q和PLL2Q,我們這里選擇的PLL2Q輸出20MHz。
92.3.5 FDCAN仲裁階段和通信階段波特率配置(重要)
CAN FD中的FD含義就是flexible data,靈活數據通信,且波特率可以和仲裁階段波特率不同。看下面的圖,意思就是紅色部分可以是一個波特率,藍色部分也可以是一個波特率。
波特率計算公式,看如下的位時間特性圖:
1bit的CAN FD數據需要時間由Sync_Seg + Pro_Seg + Phase_Seg1 + Phase_Seg2組成。
- 仲裁階段波特率對應的HAL庫配置如下:
/* CAN時鍾分配設置,一般設置為1即可,全部由PLL配置好,tq = NominalPrescaler x (1/ fdcan_ker_ck) */ hfdcan2.Init.NominalPrescaler = 0x1; /* 特別注意這里的Seg1,這里是兩個參數之和,對應位時間特性圖的 Pro_Seg + Phase_Seg1 */ hfdcan2.Init.NominalTimeSeg1 = 0x1F; /* 對應位時間特性圖的 Phase_Seg2 */ hfdcan2.Init.NominalTimeSeg2 = 0x8; /* 用於動態調節 Phase_Seg1和 Phase_Seg1,所以不可以比Phase_Seg1和 Phase_Seg1大 */ hfdcan2.Init.NominalSyncJumpWidth = 0x8;
其中:
Sync_Seg是固定值 = 1 Pro_Seg + Phase_Seg1 = DataTimeSeg1 hase_Seg2 = DataTimeSeg2
FDCAN時鍾是20MHz時,仲裁階段的波特率就是
FDCAN Freq / (Sync_Seg + Pro_Seg + Phase_Seg1 + Phase_Seg2)
= 20MHz / (1+0x1F + 8)
= 0.5Mbps
- 數據階段波特率對應的HAL庫配置如下:
/* CAN時鍾分配設置,一般設置為1即可,全部由PLL配置好,tq = NominalPrescaler x (1/ fdcan_ker_ck) */ hfdcan2.Init.DataPrescaler = 0x1; /* 特別注意這里的Seg1,這里是兩個參數之和,對應位時間特性圖的 Pro_Seg + Phase_Seg1 */ hfdcan2.Init.DataTimeSeg1 = 0x5; /* 對應位時間特性圖的 Phase_Seg2 */ hfdcan2.Init.DataTimeSeg2 = 0x4; /* 用於動態調節 Phase_Seg1和 Phase_Seg1,所以不可以比Phase_Seg1和 Phase_Seg1大 */ hfdcan2.Init.DataSyncJumpWidth = 0x4;
其中:
Sync_Seg是固定值 = 1 Pro_Seg + Phase_Seg1 = DataTimeSeg1 hase_Seg2 = DataTimeSeg2
FDCAN時鍾是20MHz時,數據階段的波特率就是
CAN FD Freq / (Sync_Seg + Pro_Seg + Phase_Seg1 + Phase_Seg2)
= 20MHz / (1+5+ 4)
= 2Mbps
STM32H7在400MHz情況下,PLL配置輸出20MHz的CAN FD時鍾(大家可以使用STM32CubeMX來配置的):
/* 選擇PLL2Q作為FDCANx時鍾 */ PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_FDCAN; PeriphClkInitStruct.PLL2.PLL2M = 5; PeriphClkInitStruct.PLL2.PLL2N = 80; PeriphClkInitStruct.PLL2.PLL2P = 2; PeriphClkInitStruct.PLL2.PLL2Q = 20; PeriphClkInitStruct.PLL2.PLL2R = 2; PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_2; PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE; PeriphClkInitStruct.PLL2.PLL2FRACN = 0; PeriphClkInitStruct.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL2; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { Error_Handler(__FILE__, __LINE__); }
92.3.6 FDCAN的采樣點位置推薦設置為85% - 90%
FDCAN每個bit的時間組成如下:
為了實現更好的穩定性,在滿足指定波特率的情況下,讓采樣點的位置滿足85%到90%是推薦的方式。主要用到HAL庫的如下兩個成員:
hfdcan2.Init.NominalTimeSeg1
hfdcan2.Init.NominalTimeSeg2
SyncSeg是固定1,也就是:
(SyncSeg + NominalTimeSeg1)/ (SyncSeg + NominalTimeSeg1 + NominalTimeSeg2)滿足這個條件。
92.3.7 FDCAN的2560字RAM空間分配問題
關於FDCAN的2560字RAM空間在本教程第90章的第5小節有詳細說明。本章配套程序將前1280字分配給FDCAN1,后1280字分配給FDCAN2。
92.3.8 FDCAN過濾器常用的范圍過濾器和經典的位屏蔽過濾器
關於FDCAN的過濾器類型在本教程第90章的第6小節有詳細說明,我們這里重點了解范圍過濾器和經典位屏蔽過濾器。
- 范圍過濾器
范圍過濾器比較簡單,也比較好理解,對應的HAL庫配置代碼如下:
sFilterConfig2.IdType = FDCAN_STANDARD_ID; sFilterConfig2.FilterIndex = 0; sFilterConfig2.FilterType = FDCAN_FILTER_RANGE; sFilterConfig2.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; sFilterConfig2.FilterID1 = 0x111; sFilterConfig2.FilterID2 = 0x555; HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig2);
FilterType類型配置為FDCAN_FILTER_RANGE表示范圍過濾器。
FilterID1 = 0x111和FilterID2 = 0x555表示僅接收 ≥0x111且 ≤ 0x555的ID。
- 經典的位屏蔽過濾
對應的HAL庫配置代碼如下:
sFilterConfig2.IdType = FDCAN_STANDARD_ID; sFilterConfig2.FilterIndex = 0; sFilterConfig2.FilterType = FDCAN_FILTER_MASK; sFilterConfig2.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; sFilterConfig2.FilterID1 = 0x222; sFilterConfig2.FilterID2 = 0x7FF; HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig2);
FilterType類型配置為FDCAN_FILTER_MASK表示經典的位屏蔽過濾。
FilterID1 = filter 表示ID。
FilterID2 = mask 表示ID屏蔽位,mask每個bit含義:
0: 表示FilterID1相應bit不關心,該位不用於比較。
1: 表示FilterID1相應bit必須匹配,即接收到的ID位必須與FilterID1的相應位一致。
我們這里FilterID1 = 0x222,FilterID2 = 0x7FF 表示僅接收ID為0x222的FDCAN幀。
92.4 FDCAN驅動代碼實現
這里以FDCAN1為例進行說明,FDCAN2的驅動程序在本章配套例子里面也提供了。
92.4.1 FDCAN配置
代碼里面對每個成員都進行了詳細注釋說明:
/* ********************************************************************************************************* * 函 數 名: bsp_InitCan1 * 功能說明: 初始CAN1 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_InitCan1(void) { /* 位時間特性配置 Bit time parameter | Nominal | Data ---------------------------|--------------|---------------- fdcan_ker_ck | 20 MHz | 20 MHz Time_quantum (tq) | 50 ns | 50 ns Synchronization_segment | 1 tq | 1 tq Propagation_segment | 23 tq | 1 tq Phase_segment_1 | 8 tq | 4 tq Phase_segment_2 | 8 tq | 4 tq Synchronization_Jump_width | 8 tq | 4 tq Bit_length | 40 tq = 2us | 10 tq = 0.5us Bit_rate | 0.5 MBit/s | 2 MBit/s */ hfdcan1.Instance = FDCAN1; /* 配置FDCAN1 */ hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_BRS; /* 配置使用FDCAN可變波特率 */ hfdcan1.Init.Mode = FDCAN_MODE_NORMAL; /* 配置使用正常模式 */ hfdcan1.Init.AutoRetransmission = ENABLE; /*使能自動重發 */ hfdcan1.Init.TransmitPause = DISABLE; /* 配置禁止傳輸暫停特性 */ hfdcan1.Init.ProtocolException = ENABLE; /* 協議異常處理使能 */ /* 配置仲裁階段波特率 CAN時鍾20MHz時,仲裁階段的波特率就是 CAN FD Freq / (Sync_Seg + Pro_Seg + Phase_Seg1 + Phase_Seg2) = 20MHz / (1+0x1F + 8) = 0.5Mbps 其中Sync_Seg是固定值 = 1 , Pro_Seg + Phase_Seg1 = NominalTimeSeg1, Phase_Seg2 = NominalTimeSeg2 */ /* CAN時鍾分配設置,一般設置為1即可,全部由PLL配置好,tq = NominalPrescaler x (1/ fdcan_ker_ck) */ hfdcan1.Init.NominalPrescaler = 0x01; /* 用於動態調節 Phase_Seg1和 Phase_Seg1,所以不可以比Phase_Seg1和 Phase_Seg1大 */ hfdcan1.Init.NominalSyncJumpWidth = 0x08; /* 特別注意這里的Seg1,這里是兩個參數之和,對應位時間特性圖的 Pro_Seg + Phase_Seg1 */ hfdcan1.Init.NominalTimeSeg1 = 0x1F; /* 對應位時間特性圖的 Phase_Seg2 */ hfdcan1.Init.NominalTimeSeg2 = 0x08; /* 配置數據階段波特率 CAN時鍾20MHz時,數據階段的波特率就是 CAN FD Freq / (Sync_Seg + Pro_Seg + Phase_Seg1 + Phase_Seg2) = 20MHz / (1+5+ 4) = 2Mbps 其中Sync_Seg是固定值 = 1 , Pro_Seg + Phase_Seg1 = DataTimeSeg1, Phase_Seg2 = DataTimeSeg2 */ /* CAN時鍾分配設置,一般設置為1即可,全部由PLL配置好,tq = NominalPrescaler x (1/ fdcan_ker_ck), 范圍1-32 */ hfdcan1.Init.DataPrescaler = 0x01; /* 用於動態調節 Phase_Seg1和 Phase_Seg1,所以不可以比Phase_Seg1和 Phase_Seg1大,范圍1-16 */ hfdcan1.Init.DataSyncJumpWidth = 0x04; /* 特別注意這里的Seg1,這里是兩個參數之和,對應位時間特性圖的 Pro_Seg + Phase_Seg1,范圍 */ hfdcan1.Init.DataTimeSeg1 = 0x05; /* 對應位時間特性圖的 Phase_Seg2 */ hfdcan1.Init.DataTimeSeg2 = 0x04; hfdcan1.Init.MessageRAMOffset = 0; /* CAN1和CAN2共享2560個字, 這里CAN1分配前1280字 */ hfdcan1.Init.StdFiltersNbr = 1; /* 設置標准ID過濾器個數,范圍0-128 */ hfdcan1.Init.ExtFiltersNbr = 0; /* 設置擴展ID過濾器個數,范圍0-64 */ hfdcan1.Init.RxFifo0ElmtsNbr = 2; /* 設置Rx FIFO0的元素個數,范圍0-64 */ /* 設置Rx FIFO0中每個元素大小,支持8,12,16,20,24,32,48或者64字節 */ hfdcan1.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_8; hfdcan1.Init.RxFifo1ElmtsNbr = 0; /* 設置Rx FIFO1的元素個數,范圍0-64 */ /* 設置Rx FIFO1中每個元素大小,支持8,12,16,20,24,32,48或者64字節 */ hfdcan1.Init.RxFifo1ElmtSize = FDCAN_DATA_BYTES_8; hfdcan1.Init.RxBuffersNbr = 0; /* 設置Rx Buffer個數,范圍0-64 */ /* 設置Rx Buffer中每個元素大小,支持8,12,16,20,24,32,48或者64字節 */ hfdcan1.Init.RxBufferSize = 0; hfdcan1.Init.TxEventsNbr = 0; /* 設置Tx Event FIFO中元素個數,范圍0-32 */ hfdcan1.Init.TxBuffersNbr = 0; /* 設置Tx Buffer中元素個數,范圍0-32 */ hfdcan1.Init.TxFifoQueueElmtsNbr = 2; /* 設置用於Tx FIFO/Queue的Tx Buffers個數。范圍0到32 */ hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; /* 設置FIFO模式或者QUEUE隊列模式 */ /* 設置Tx Element中的數據域大小,支持8,12,16,20,24,32,48或者64字節 */ hfdcan1.Init.TxElmtSize = FDCAN_DATA_BYTES_8; HAL_FDCAN_Init(&hfdcan1); /* 配置過濾器, 過濾器主要用於接收,這里采樣屏蔽位模式。 FilterID1 = filter FilterID2 = mask FilterID2的mask每個bit含義 0: 不關心,該位不用於比較; 1: 必須匹配,接收到的ID必須與濾波器對應的ID位相一致。 舉例說明: FilterID1 = 0x111 FilterID2 = 0x7FF 表示僅接收ID為0x111的FDCAN幀。 */ sFilterConfig1.IdType = FDCAN_STANDARD_ID; /* 設置標准ID或者擴展ID */ /* 用於過濾索引,如果是標准ID,范圍0到127。如果是擴展ID,范圍0到64 */ sFilterConfig1.FilterIndex = 0; sFilterConfig1.FilterType = FDCAN_FILTER_MASK; /* 過濾器采樣屏蔽位模式 */ sFilterConfig1.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; /* 如果過濾匹配,將數據保存到Rx FIFO 0 */ sFilterConfig1.FilterID1 = 0x111; /* 屏蔽位模式下,FilterID1是消息ID */ sFilterConfig1.FilterID2 = 0x7FF; /* 屏蔽位模式下,FilterID2是消息屏蔽位 */ HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig1); /* 配置過濾器 */ /* 設置Rx FIFO0的wartermark為1 */ HAL_FDCAN_ConfigFifoWatermark(&hfdcan1, FDCAN_CFG_RX_FIFO0, 1); /* 激活RX FIFO0的watermark通知中斷,位開啟Tx Buffer中斷*/ HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_WATERMARK, 0); /* 啟動FDCAN */ HAL_FDCAN_Start(&hfdcan1); }
92.4.2 FDCAN的GPIO,NVIC等配置
主要配置了FDCAN的GPIO,GPIO時鍾,FDCAN時鍾和NVIC:
/* ********************************************************************************************************* * 函 數 名: HAL_FDCAN_MspInit * 功能說明: 配置CAN gpio * 形 參: hfdcan * 返 回 值: 無 ********************************************************************************************************* */ void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* hfdcan) { GPIO_InitTypeDef GPIO_InitStruct; RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0}; if (hfdcan == &hfdcan1) { /*##-1- 使能外設這個GPIO時鍾 #################################*/ FDCAN1_TX_GPIO_CLK_ENABLE(); FDCAN1_RX_GPIO_CLK_ENABLE(); /* 選擇PLL2Q作為FDCANx時鍾 */ PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_FDCAN; PeriphClkInitStruct.PLL2.PLL2M = 5; PeriphClkInitStruct.PLL2.PLL2N = 80; PeriphClkInitStruct.PLL2.PLL2P = 2; PeriphClkInitStruct.PLL2.PLL2Q = 20; PeriphClkInitStruct.PLL2.PLL2R = 2; PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_2; PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE; PeriphClkInitStruct.PLL2.PLL2FRACN = 0; PeriphClkInitStruct.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL2; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } __HAL_RCC_FDCAN_CLK_ENABLE(); GPIO_InitStruct.Pin = FDCAN1_TX_PIN; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = FDCAN1_TX_AF; HAL_GPIO_Init(FDCAN1_TX_GPIO_PORT, &GPIO_InitStruct); GPIO_InitStruct.Pin = FDCAN1_RX_PIN; GPIO_InitStruct.Alternate = FDCAN1_RX_AF; HAL_GPIO_Init(FDCAN1_RX_GPIO_PORT, &GPIO_InitStruct); HAL_NVIC_SetPriority(FDCAN1_IT0_IRQn, 2, 0); HAL_NVIC_SetPriority(FDCAN1_IT1_IRQn, 2, 0); HAL_NVIC_SetPriority(FDCAN_CAL_IRQn, 2, 0); HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn); HAL_NVIC_EnableIRQ(FDCAN1_IT1_IRQn); HAL_NVIC_EnableIRQ(FDCAN_CAL_IRQn); } }
92.4.3 FDCAN的數據發送
FDCAN每幀數據最大64字節:
/* ********************************************************************************************************* * 函 數 名: can1_SendPacket * 功能說明: 發送一包數據 * 形 參:_DataBuf 數據緩沖區 * _Len 數據長度, 支持8,12,16,20,24,32,48或者64字節 * 返 回 值: 無 ********************************************************************************************************* */ void can1_SendPacket(uint8_t *_DataBuf, uint8_t _Len) { FDCAN_TxHeaderTypeDef TxHeader = {0}; /* 配置發送參數 */ TxHeader.Identifier = 0x222; /* 設置接收幀消息的ID */ TxHeader.IdType = FDCAN_STANDARD_ID; /* 標准ID */ TxHeader.TxFrameType = FDCAN_DATA_FRAME; /* 數據幀 */ TxHeader.DataLength = (uint32_t)_Len << 16; /* 發送數據長度 */ TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE; /* 設置錯誤狀態指示 */ TxHeader.BitRateSwitch = FDCAN_BRS_ON; /* 開啟可變波特率 */ TxHeader.FDFormat = FDCAN_FD_CAN; /* FDCAN格式 */ TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;/* 用於發送事件FIFO控制, 不存儲 */ TxHeader.MessageMarker = 0; /* 用於復制到TX EVENT FIFO的消息Maker來識別消息狀態,范圍0到0xFF */ /* 添加數據到TX FIFO */ HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, _DataBuf); }
這里特別注意兩點:
- 發送的數據幅值給DataLength,需要右移16bit。
- 要發送的數據會被復制到TX FIFO硬件緩存中。
92.4.4 FDCAN的中斷服務程序處理
這里FDCAN中斷主要用於數據接收:
/* ********************************************************************************************************* * 函 數 名: FDCAN1_IT0_IRQHandler * 功能說明: CAN中斷服務程序 * 形 參: hfdcan * 返 回 值: 無 ********************************************************************************************************* */ void FDCAN1_IT0_IRQHandler(void) { HAL_FDCAN_IRQHandler(&hfdcan1); } void FDCAN1_IT1_IRQHandler(void) { HAL_FDCAN_IRQHandler(&hfdcan1); } void FDCAN_CAL_IRQHandler(void) { HAL_FDCAN_IRQHandler(&hfdcan1); } /* ********************************************************************************************************* * 函 數 名: HAL_FDCAN_RxFifo0Callback * 功能說明: CAN中斷服務程序-回調函數 * 形 參: hfdcan * 返 回 值: 無 ********************************************************************************************************* */ void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { if (hfdcan == &hfdcan1) { if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_WATERMARK) != RESET) { /* 從RX FIFO0讀取數據 */ HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &g_Can1RxHeader, g_Can1RxData); /* 激活Rx FIFO0 watermark notification */ HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO0_WATERMARK, 0); if (g_Can1RxHeader.Identifier == 0x111 && g_Can1RxHeader.IdType == FDCAN_STANDARD_ID) { bsp_PutMsg(MSG_CAN1_RX, 0); /* 發消息收到數據包,結果在g_Can1RxHeader, g_Can1RxData */ } } } }
92.5 雙FDCAN測試的接線和跳線帽說明
大家可以直接使用板載的兩個FDCAN進行通信測試。跳線帽設置如下:
雙CAN FD接線:
92.6 開發板和H7-TOOL的FDCAN助手測試
H7-TOOL的CAN/CANFD助手支持以太網,USB和WiFi三種通信方式。大家可以用使用本章配套的雙FDCAN例子與H7-TOOL的FDCAN助手進行通信。
92.6.1 接線說明
H7-TOOL和STM32H7的FDCAN2進行通信,注意是CANH接CANH, CANL接CANL
92.6.2 啟動H7-TOOL上位機
打開最新版的H7-TOOL上位機,使用USB,以太網或者WiFi方式均支持。選擇左側的CAN助手 -> 啟動CAN助手:
參數介紹:
(1)幀類型 :0 - 經典CAN,最大收發8字節
1 - CAN FD , 仲裁段和數據點波特率相同,最大收發64字節
2 - CAN FD 雙波特率,仲裁段和數據點波特率不同,最大收發64字節
(2)仲裁段和數據段波特率 :除了提供常用的波特率,還提供了用戶可自定義配置模式,需要用戶選擇如下選項,這樣就可以配置右側的“CAN波特率高級配置”
我們這里選擇CAN FD雙波特率,仲裁段設置為500Kbps,數據段設置為2Mbps,最大數據設置為8字節, CAN解碼器設置為none_decoder.lua
92.6.3 H7-TOOL的FDCAN接收數據測試
第2步設置完畢參數后,按下幾次STM32H7板子的 K1, 可以看到H7-TOOL上位機接收到了數據:
92.6.4 H7-TOOL的FDCAN發送數據測試
如果有多種發送格式,用戶可以在快捷面板里面發送,這個面板也支持用戶加載專門的配置文件,不用每次都設置。
如下的配置,點擊發送,可以控制STM32H7板子的LED1點亮:
如下的配置,點擊發送,可以控制STM32H7板子的LED1熄滅:
92.7 實驗例程設計框架
通過程序設計框架,讓大家先對配套例程有一個全面的認識,然后再理解細節,本次實驗例程的設計框架如下:
第1階段,上電啟動階段:
- 這部分在第14章進行了詳細說明。
第2階段,進入main函數:
- 第1步,硬件初始化,主要是MPU,Cache,HAL庫,系統時鍾,滴答定時器,LED和串口。
- 第2步,FDCAN應用程序設計部分。
92.8 實驗例程說明(MDK)
配套例子:
V7-069_雙CAN FD之間通信
實驗目的:
- 學習雙CAN FD的實現。
實驗內容:
- K1按鍵按下,CAN2發送消息給CAN1,蜂鳴器鳴響4次。
- K2按鍵按下,CAN1發送消息給CAN2,點亮LED1。
- K2按鍵按下,CAN1發送消息給CAN2,熄滅LED1。
注意事項:
- 接線方式是CAN1的CANL1和CAN2的CANL2連接,CAN1的CANH2和CAN2的CANH2連接,具體接線看本工程Doc文件中的截圖。
- 啟用CAN1,需要將V7主板上的J12跳線帽短接PA11,J13跳線帽短接PA12。
- 啟用CNA2,硬件無需跳線,要禁止使用以太網功能(有引腳復用)。
上電后串口打印的信息:
波特率 115200,數據位 8,奇偶校驗位無,停止位 1
程序設計:
系統棧大小分配:
RAM空間用的AXI SRAM:
硬件外設初始化
硬件外設的初始化是在 bsp.c 文件實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鍾: - 調用函數HAL_InitTick,初始化滴答時鍾中斷1ms。 - 設置NVIV優先級分組為4。 */ HAL_Init(); /* 配置系統時鍾到400MHz - 切換使用HSE。 - 此函數會更新全局變量SystemCoreClock,並重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用於代碼執行時間測量,MDK5.25及其以上版本才支持,IAR不支持。 - 默認不開啟,如果要使能此選項,務必看V7開發板用戶手冊第xx章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder並開啟 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */ bsp_InitTimer(); /* 初始化滴答定時器 */ bsp_InitUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC總線74HC574擴展IO. 必須在 bsp_InitLed()前執行 */ bsp_InitLed(); /* 初始化LED */ }
MPU配置和Cache配置:
數據Cache和指令Cache都開啟。配置了AXI SRAM區和FMC的擴展IO區。
/* ********************************************************************************************************* * 函 數 名: MPU_Config * 功能說明: 配置MPU * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); #if 0 /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); #else /* 當前是采用下面的配置 */ /* 配置AXI SRAM的MPU屬性為NORMAL, NO Read allocate,NO Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); #endif /* 配置FMC擴展IO的MPU屬性為Device或者Strongly Ordered */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x60000000; MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER1; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } /* ********************************************************************************************************* * 函 數 名: CPU_CACHE_Enable * 功能說明: 使能L1 Cache * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void CPU_CACHE_Enable(void) { /* 使能 I-Cache */ SCB_EnableICache(); /* 使能 D-Cache */ SCB_EnableDCache(); }
主功能:
主程序實現如下操作:
- 上電啟動了一個軟件定時器,每100ms翻轉一次LED2。
- K1按鍵按下,CAN2發送消息給CAN1,蜂鳴器鳴響4次。
- K2按鍵按下,CAN1發送消息給CAN2,點亮LED1。
- K2按鍵按下,CAN1發送消息給CAN2,熄滅LED1。
/* ********************************************************************************************************* * 函 數 名: DemoCANFD * 功能說明: CAN測試 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void DemoCANFD(void) { uint8_t ucKeyCode; /* 按鍵代碼 */ can_Init(); /* 初始化CAN */ bsp_StartAutoTimer(0, 500); /* 啟動1個500ms的自動重裝的定時器 */ while (1) { { MSG_T msg; if (bsp_GetMsg(&msg)) { switch (msg.MsgCode) { case MSG_CAN1_RX: /* 接收到CAN設備的應答 */ printf("CAN1接收到消息\r\n"); can1_Analyze(); break; case MSG_CAN2_RX: /* 接收到CAN設備的應答 */ printf("CAN2接收到消息\r\n"); can2_Analyze(); break; } } } /* 判斷定時器超時時間 */ if (bsp_CheckTimer(0)) { /* 每隔500ms 進來一次 */ bsp_LedToggle(2); } /* 按鍵濾波和檢測由后台systick中斷服務程序實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */ ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下 */ printf("CAN2發送消息給CAN1,蜂鳴器鳴響4次\r\n"); can_BeepCtrl(1, 4); break; case KEY_DOWN_K2: /* K2鍵按下 */ printf("CAN1發送消息給CAN2,點亮LED1\r\n"); can_LedOn(1, 1); break; case KEY_DOWN_K3: /* K3鍵按下 */ printf("CAN1發送消息給CAN2,熄滅LED1\r\n"); can_LedOff(1, 1); break; default: /* 其它的鍵值不處理 */ break; } } } }
92.9 實驗例程說明(IAR)
配套例子:
V7-069_雙CAN FD之間通信
實驗目的:
- 學習雙CAN FD的實現。
實驗內容:
- K1按鍵按下,CAN2發送消息給CAN1,蜂鳴器鳴響4次。
- K2按鍵按下,CAN1發送消息給CAN2,點亮LED1。
- K2按鍵按下,CAN1發送消息給CAN2,熄滅LED1。
注意事項:
- 接線方式是CAN1的CANL1和CAN2的CANL2連接,CAN1的CANH2和CAN2的CANH2連接,具體接線看本工程Doc文件中的截圖。
- 啟用CAN1,需要將V7主板上的J12跳線帽短接PA11,J13跳線帽短接PA12。
- 啟用CNA2,硬件無需跳線,要禁止使用以太網功能(有引腳復用)。
上電后串口打印的信息:
波特率 115200,數據位 8,奇偶校驗位無,停止位 1
程序設計:
系統棧大小分配:
RAM空間用的AXI SRAM:
硬件外設初始化
硬件外設的初始化是在 bsp.c 文件實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鍾: - 調用函數HAL_InitTick,初始化滴答時鍾中斷1ms。 - 設置NVIV優先級分組為4。 */ HAL_Init(); /* 配置系統時鍾到400MHz - 切換使用HSE。 - 此函數會更新全局變量SystemCoreClock,並重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用於代碼執行時間測量,MDK5.25及其以上版本才支持,IAR不支持。 - 默認不開啟,如果要使能此選項,務必看V7開發板用戶手冊第xx章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder並開啟 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */ bsp_InitTimer(); /* 初始化滴答定時器 */ bsp_InitUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC總線74HC574擴展IO. 必須在 bsp_InitLed()前執行 */ bsp_InitLed(); /* 初始化LED */ }
MPU配置和Cache配置:
數據Cache和指令Cache都開啟。配置了AXI SRAM區和FMC的擴展IO區。
/* ********************************************************************************************************* * 函 數 名: MPU_Config * 功能說明: 配置MPU * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); #if 0 /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); #else /* 當前是采用下面的配置 */ /* 配置AXI SRAM的MPU屬性為NORMAL, NO Read allocate,NO Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); #endif /* 配置FMC擴展IO的MPU屬性為Device或者Strongly Ordered */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x60000000; MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER1; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } /* ********************************************************************************************************* * 函 數 名: CPU_CACHE_Enable * 功能說明: 使能L1 Cache * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void CPU_CACHE_Enable(void) { /* 使能 I-Cache */ SCB_EnableICache(); /* 使能 D-Cache */ SCB_EnableDCache(); }
主功能:
主程序實現如下操作:
- 上電啟動了一個軟件定時器,每100ms翻轉一次LED2。
- K1按鍵按下,CAN2發送消息給CAN1,蜂鳴器鳴響4次。
- K2按鍵按下,CAN1發送消息給CAN2,點亮LED1。
- K2按鍵按下,CAN1發送消息給CAN2,熄滅LED1。
/* ********************************************************************************************************* * 函 數 名: DemoCANFD * 功能說明: CAN測試 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void DemoCANFD(void) { uint8_t ucKeyCode; /* 按鍵代碼 */ can_Init(); /* 初始化CAN */ bsp_StartAutoTimer(0, 500); /* 啟動1個500ms的自動重裝的定時器 */ while (1) { { MSG_T msg; if (bsp_GetMsg(&msg)) { switch (msg.MsgCode) { case MSG_CAN1_RX: /* 接收到CAN設備的應答 */ printf("CAN1接收到消息\r\n"); can1_Analyze(); break; case MSG_CAN2_RX: /* 接收到CAN設備的應答 */ printf("CAN2接收到消息\r\n"); can2_Analyze(); break; } } } /* 判斷定時器超時時間 */ if (bsp_CheckTimer(0)) { /* 每隔500ms 進來一次 */ bsp_LedToggle(2); } /* 按鍵濾波和檢測由后台systick中斷服務程序實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */ ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下 */ printf("CAN2發送消息給CAN1,蜂鳴器鳴響4次\r\n"); can_BeepCtrl(1, 4); break; case KEY_DOWN_K2: /* K2鍵按下 */ printf("CAN1發送消息給CAN2,點亮LED1\r\n"); can_LedOn(1, 1); break; case KEY_DOWN_K3: /* K3鍵按下 */ printf("CAN1發送消息給CAN2,熄滅LED1\r\n"); can_LedOff(1, 1); break; default: /* 其它的鍵值不處理 */ break; } } } }
92.10 總結
本章節就為大家講解這么多,FDCAN涉及到的知識點非常多,建議先將例子熟練運用,然后再深入了解。