天天看點

STM32(九)------- CAN

CAN

  • ​​引言​​
  • ​​介紹​​
  • ​​CAN協定的特點​​
  • ​​CAN的幀​​
  • ​​CAN的幀的類型​​
  • ​​資料幀的構成​​
  • ​​STM32的CAN​​
  • ​​CAN的發送接收​​
  • ​​CAN的發送流程​​
  • ​​CAN的接收流程​​
  • ​​CAN控制和狀态寄存器​​
  • ​​CAN主要制寄存器 (CAN_MCR)​​
  • ​​CAN 位時序寄存器(CAN_BTR)​​
  • ​​CAN 發送郵箱辨別符寄存器(CAN_TIxR)​​
  • ​​CAN 發送郵箱資料長度和時間戳寄存器 (CAN_TDTxR)​​
  • ​​CAN 發送郵箱低位元組資料寄存器 (CAN_TDLxR)​​
  • ​​CAN 接收 FIFO 郵箱辨別符寄存器 (CAN_RIxR)​​
  • ​​軟體實作​​
  • ​​CAN 的初始化配置步驟​​
  • ​​功能設計​​
  • ​​後續​​

引言

CAN 是 Controller Area Network 的縮寫(以下稱為 CAN),是 ISO 國際标準化的串行通信協定。CAN 總線是一種應用廣泛的現場總線,是近20年發展起來的新技術。在目前的汽車産業中,出于對安全性、舒适性、友善性、低公害、低成本的要求,各種各樣的電子控制系統被開發了出來。由于這些系統之間通信所用的資料類型及對可靠性的要求不盡相同,由多條總線構成的情況很多,線束的數量也随之增加。為适應“減少線束的數量”、“通過多個 LAN,進行大量資料的高速通信”的需要,1986 年德國電氣商博世公司開發出面向汽車的 CAN 通信協定。此後,CAN 通過 ISO11898 及 ISO11519 進行了标準化,現在在歐洲已是汽車網絡的标準協定。

現在,CAN 的高性能和可靠性已被認同,并被廣泛地應用于工業自動化、船舶、醫療裝置、工業裝置等方面。現場總線是當今自動化領域技術發展的熱點之一,被譽為自動化領域的計算機區域網路。它的出現為分布式控制系統實作各節點之間實時、可靠的資料通信提供了強有力的技術支援。近年來,其所具有的高可靠性和良好的錯誤檢測能力受到重視,被廣泛應用于汽車計算機控制系統和環境溫度惡劣、電磁輻射強和振動大的工業環境。

介紹

CAN協定的特點

CAN屬于總線式串行通信網絡。 由于采用了許多新技術和獨特的設計思想 ,與同類産品相比 , CAN 總線在資料通信方面具有可靠、實時和靈活的優點。

CAN 總線上用“顯性”(Dominant)和“隐性” (Recessive)兩個互補的邏輯值表示“0”和“1”。CAN-H和CAN-L為CAN總線收發器 與總線之間的兩接口引腳,信号是以兩線之間的“差 分”電壓形式出現。在隐性狀态,CNA-H和CANL被固定在平均電壓電平附近,Vdiff近似于0。顯性位以大于最小閥值的差分電壓表示。CAN 總線的通信距離最遠可達10Km(位速率為5 kbps) ,通信速率最快可達 1Mbps(此時最長通信距離為40m)。

  1. 多主要制。在總線空閑時,所有單元都可以發送消息(多主要制),而兩個以上的單元同時開始發送消息時,根據辨別符(Identifier 以下稱為 ID)決定優先級。ID 并不是表示發送的目的位址,而是表示通路總線的消息的優先級。兩個以上的單元同時開始發送消息時,對各消息 ID 的每個位進行逐個仲裁比較。仲裁獲勝(被判定為優先級最高)的單元可繼續發送消息,仲裁失利的單元則立刻停止發送而進行接收工作。
  2. 系統的柔軟性。與總線相連的單元沒有類似于“位址”的資訊。是以在總線上增加單元時,連接配接在總線上的其它單元的軟硬體及應用層都不需要改變。
  3. 通信速度較快,通信距離遠。最高 1Mbps(距離小于 40M),最遠可達10KM(速率低于 5Kbps)。
  4. 具有錯誤檢測、錯誤通知和錯誤恢複功能。所有單元都可以檢測錯誤(錯誤檢測功能),檢測出錯誤的單元會立即同時通知其他所有單元(錯誤通知功能),正在發送消息的單元一旦檢測出錯誤,會強制結束目前的發送。強制結束發送的單元會不斷反複地重新發送此消息直到成功發送為止(錯誤恢複功能)。
  5. 故障封閉功能。CAN 可以判斷出錯誤的類型是總線上暫時的資料錯誤(如外部噪聲等)還是持續的資料錯誤(如單元内部故障、驅動器故障、斷線等)。由此功能,當總線上發生持續資料錯誤時,可将引起此故障的單元從總線上隔離出去。
  6. 連接配接節點多。CAN 總線是可同時連接配接多個單元的總線。可連接配接的單元總數理論上是沒有限制的。但實際上可連接配接的單元數受總線上的時間延遲及電氣負載的限制。降低通信速度,可連接配接的單元數增加;提高通信速度,則可連接配接的單元數減少。

正是因為 CAN 協定的這些特點,使得 CAN 特别适合工業過程監控裝置的互連,是以,越來越受到工業界的重視,并已公認為最有前途的現場總線之一。

CAN的幀

CAN的幀的類型

CAN 協定是通過以下 5 種類型的幀進行的:

  • 資料幀
  • 遙控幀
  • 錯誤幀
  • 過載幀
  • 間隔幀

另外,資料幀和遙控幀有标準格式和擴充格式兩種格式。标準格式有 11 個位的辨別符(ID),擴充格式有 29 個位的 ID。

幀類型 幀用途
資料幀 用于發送單元向接收單元傳送資料的幀
遙控幀 用于接收單元向具有相同 ID 的發送單元請求資料的幀
錯誤幀 用于當檢測出錯誤時向其它單元通知錯誤的幀
過載幀 用于接收單元通知其尚未做好接收準備的幀
間隔幀 用于将資料幀及遙控幀與前面的幀分離開來的幀

資料幀的構成

  1. 幀起始。表示資料幀開始的段。
  2. 仲裁段。表示該幀優先級的段。
  3. 控制段。表示資料的位元組數及保留位的段。
  4. 資料段。資料的内容,一幀可發送 0~8 個位元組的資料。
  5. CRC 段。檢查幀的傳輸錯誤的段。
  6. ACK 段。表示确認正常接收的段。
  7. 幀結束。表示資料幀結束的段。

STM32的CAN

STM32F1 自帶的是 bxCAN,即基本擴充 CAN。它支援 CAN 協定 2.0A 和 2.0B。它的設計目标是,以最小的 CPU 負荷來高效處理大量收到的封包。它也支援封包發送的優先級要求(優先級特性可軟體配置)。

STM32F1 的 bxCAN 的主要特點有:

  • 支援 CAN 協定 2.0A 和 2.0B 主動模式
  • 波特率最高達 1Mbps
  • 支援時間觸發通信
  • 具有 3 個發送郵箱
  • 具有 3 級深度的 2 個接收 FIFO
  • 可變的過濾器組(最多 28 個)

低速CAN總線為開環,高速CAN總線為閉環,總線由CAN_H和CAN_L兩根線組成,總線上可以挂多個節點裝置。每個節點裝置由CAN控制器和CAN收發器組成,CAN控制器通常作為外設內建在MPU/MCU上,而CAN收發器則需要外圍添加晶片電路。

CAN的發送接收

CAN的發送流程

CAN 發送流程為:程式選擇 1 個空置的郵箱(TME=1)→設定辨別符(ID),資料長度和發送資料→設定 CAN_TIxR 的 TXRQ 位為 1,請求發送→郵箱挂号(等待成為最高優先級)→預定發送(等待總線空閑)→發送→郵箱空置。

CAN的接收流程

CAN 接收到的有效封包,被存儲在 3 級郵箱深度的 FIFO 中。FIFO 完全由硬體來管理,進而節省了 CPU 的處理負荷,簡化了軟體并保證了資料的一緻性。應用程式隻能通過讀取FIFO輸出郵箱,來讀取 FIFO 中最先收到的封包。這裡的有效封包是指那些正确被接收的(直到 EOF都沒有錯誤)且通過了辨別符過濾的封包。前面我們知道 CAN 的接收有 2 個 FIFO,我們每個濾波器組都可以設定其關聯的 FIFO,通過 CAN_FFA1R 的設定,可以将濾波器組關聯到FIFO0/FIFO1。

CAN 接收流程為:FIFO 空 → 收到有效封包 → 挂号_1(存入 FIFO 的一個郵箱,這個由硬體控制,我們不需要理會)→ 收到有效封包 → 挂号_2 → 收到有效封包→挂号_3→收到有效封包→溢出。

這個流程裡面,我們沒有考慮從 FIFO 讀出封包的情況,實際情況是:我們必須在 FIFO 溢出之前,讀出至少 1 個封包,否則下個封包到來,将導緻 FIFO 溢出,進而出現封包丢失。每讀出 1 個封包,相應的挂号就減 1,直到 FIFO 空。

FIFO 接收到的封包數,我們可以通過查詢 CAN_RFxR 的 FMP 寄存器來得到,隻要 FMP不為 0,我們就可以從 FIFO 讀出收到的封包。

CAN控制和狀态寄存器

CAN主要制寄存器 (CAN_MCR)

STM32(九)------- CAN

對于我們正常的開發來說,INRQ 位是最重要的一位,該位用來控制初始化請求。

軟體對該位清 0,可使 CAN 從初始化模式進入正常工作模式:當 CAN 在接收引腳檢測到連續的 11 個隐性位後,CAN 就達到同步,并為接收和發送資料作好準備了。為此,硬體相應地對 CAN_MSR 寄存器的 INAK 位清’0’。

軟體對該位置 1 可使 CAN 從正常工作模式進入初始化模式:一旦目前的 CAN 活動(發送或接收)結束,CAN 就進入初始化模式。相應地,硬體對 CAN_MSR 寄存器的 INAK 位置’1’。

是以我們在 CAN 初始化的時候,先要設定該位為 1,然後進行初始化(尤其是 CAN_BTR的設定,該寄存器,必須在 CAN 正常工作之前設定),之後再設定該位為 0,讓 CAN 進入正常工作模式。

CAN 位時序寄存器(CAN_BTR)

當CAN處于初始化模式時,該寄存器隻能由軟體通路。

STM32(九)------- CAN

該寄存器用于設定分頻、Tbs1、Tbs2以及 Tsjw 等非常重要的參數,直接決定了 CAN 的波特率。另外該寄存器還可以設定 CAN 的工作模式。

STM32(九)------- CAN

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-yxMOS17t-1655804525641)(http://rchcrq0dc.hb-bkt.clouddn.com/20220621115905.png)]

STM32 提供了兩種測試模式,環回模式和靜默模式,當然他們組合還可以組合成環回靜默模式。

在環回模式下,bxCAN 把發送的封包當作接收的封包并儲存(如果可以通過接收過濾)在接收郵箱裡。也就是環回模式是一個自發自收的模式。

環回模式可用于自測試。為了避免外部的影響,在環回模式下 CAN 核心忽略确認錯誤(在資料/遠端幀的确認位時刻,不檢測是否有顯性位)。在環回模式下,bxCAN 在内部把 Tx 輸出回饋到 Rx 輸入上,而完全忽略 CANRX 引腳的實際狀态。發送的封包可以在CANTX 引腳上檢測到。

CAN 發送郵箱辨別符寄存器(CAN_TIxR)

  1. 當其所屬的郵箱處在等待發送的狀态時,該寄存器是寫保護的
  2. 該寄存器實作了發送請求控制功能(第0位)-複位值為0
STM32(九)------- CAN

該寄存器主要用來設定辨別符(包括擴充辨別符),另外還可以設定幀類型,通過 TXRQ值 1,來請求郵箱發送。因為有 3 個發送郵箱,是以寄存器CAN_TIxR 有 3 個。

CAN 發送郵箱資料長度和時間戳寄存器 (CAN_TDTxR)

當郵箱不在空置狀态時,該寄存器的所有位為寫保護。

STM32(九)------- CAN
STM32(九)------- CAN

該寄存器我們本章僅用來設定資料長度,即最低 4 個位。

CAN 發送郵箱低位元組資料寄存器 (CAN_TDLxR)

STM32(九)------- CAN
STM32(九)------- CAN

該寄存器用來存儲将要發送的資料,這裡隻能存儲低 4 個位元組,另外還有一個寄存器CAN_TDHxR,該寄存器用來存儲高 4 個位元組,這樣總共就可以存儲 8 個位元組。CAN_TDHxR的各位描述同 CAN_TDLxR 類似。

CAN 接收 FIFO 郵箱辨別符寄存器 (CAN_RIxR)

STM32(九)------- CAN

該寄存器各位描述同 CAN_TIxR 寄存器幾乎一模一樣,隻是最低位為保留位,該寄存器用于儲存接收到的封包辨別符等資訊,我們可以通過讀該寄存器擷取相關資訊。

同樣的,CAN 接收 FIFO 郵箱資料長度和時間戳寄存器 (CAN_RDTxR) 、CAN 接收 FIFO郵 箱 低 字 節 數 據 寄 存 器 (CAN_RDLxR) 和 CAN 接 收 FIFO 郵 箱 高 字 節 數 據 寄 存 器(CAN_RDHxR) 分别和發送郵箱的:CAN_TDTxR、CAN_TDLxR 以及 CAN_TDHxR 類似。

軟體實作

HAL 庫 中 CAN 相 關 的 函 數 在 文 件 stm32f1xx_hal_can.c 和 對 應 的 頭 文 件stm32f1xx_hal_can.h 中。

CAN 的初始化配置步驟

  1. 配置相關引腳的複用功能,使能 CAN 時鐘。

    我們要用 CAN,第一步就要使能 CAN 的時鐘,CAN 的時鐘通過 APB1ENR 的第 25 位來設定。其次要設定 CAN 的相關引腳為複用輸出,這裡我們需要設定 PA11 為上拉輸入(CAN_RX引腳)PA12 為複用輸出(CAN_TX 引腳),并使能 PA 口的時鐘。CAN 發送接受引腳是哪些口,可以在中文參考手冊引腳表裡面查找。

GPIO_InitTypeDef GPIO_Initure;
    
    __HAL_RCC_CAN1_CLK_ENABLE();                //使能CAN1時鐘
    __HAL_RCC_GPIOA_CLK_ENABLE();               //開啟GPIOA時鐘
    
    GPIO_Initure.Pin=GPIO_PIN_12;               //PA12
    GPIO_Initure.Mode=GPIO_MODE_AF_PP;          //推挽複用
    GPIO_Initure.Pull=GPIO_PULLUP;              //上拉
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;    //高速
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);         //初始化   
    GPIO_Initure.Pin=GPIO_PIN_11;               //PA11
    GPIO_Initure.Mode=GPIO_MODE_AF_INPUT;       //推挽複用
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);         //初始化      
  1. 設定 CAN 工作模式及波特率等。

    這一步通過先設定 CAN_MCR 寄存器的 INRQ 位,讓 CAN 進入初始化模式,然後設定CAN_MCR 的其他相關控制位。再通過 CAN_BTR 設定波特率和工作模式(正常模式/環回模式)等資訊。 最後設定 INRQ 為 0,退出初始化模式。

    在庫函數中,提供了函數 HAL_CAN_Init 用來初始化 CAN 的工作模式以及波特率,HAL_CAN_Init 函數體中,在初始化之前,會設定 CAN_MCR 寄存器的 INRQ 為 1 讓其進入初始化模式,然後初始化 CAN_MCR 寄存器和 CRN_BTR 寄存器之後,會設定 CAN_MCR 寄存器的 INRQ 為 0 讓其退出初始化模式。是以我們在調用這個函數的前後不需要再進行初始化模式設定。

    初始化執行個體為:

CAN_HandleTypeDef   CAN1_Handler;  //CAN1句柄
    CAN_InitTypeDef     CAN1_InitConf;
    CAN1_Handler.Instance=CAN1;
    CAN1_Handler.Init = CAN1_InitConf;  
    CAN1_Handler.Init.Prescaler=brp;                //分頻系數(Fdiv)為brp+1
    CAN1_Handler.Init.Mode=mode;                    //模式設定 
    CAN1_Handler.Init.SyncJumpWidth=tsjw;           //重新同步跳躍寬度(Tsjw)為tsjw+1個時間機關 CAN_SJW_1TQ~CAN_SJW_4TQ
    CAN1_Handler.Init.TimeSeg1=tbs1;                //tbs1範圍CAN_BS1_1TQ~CAN_BS1_16TQ
    CAN1_Handler.Init.TimeSeg2=tbs2;                //tbs2範圍CAN_BS2_1TQ~CAN_BS2_8TQ
    CAN1_Handler.Init.TimeTriggeredMode=DISABLE;    //非時間觸發通信模式 
    CAN1_Handler.Init.AutoBusOff=DISABLE;           //軟體自動離線管理
    CAN1_Handler.Init.AutoWakeUp=DISABLE;           //睡眠模式通過軟體喚醒(清除CAN->MCR的SLEEP位)
    CAN1_Handler.Init.AutoRetransmission=ENABLE;    //禁止封包自動傳送 
    CAN1_Handler.Init.ReceiveFifoLocked=DISABLE;    //封包不鎖定,新的覆寫舊的 
    CAN1_Handler.Init.TransmitFifoPriority=DISABLE; //優先級由封包辨別符決定 
    if(HAL_CAN_Init(&CAN1_Handler)!=HAL_OK)         //初始化
        return 1;
    return 0;      

HAL 庫通用提供了 MSP 初始化回調函數,CAN 回調函數為:

void HAL_CAN_MspInit(CAN_HandleTypeDef* hcan);      

該回調函數一般用來編寫時鐘使能,IO 初始化以及 NVIC 等配置。

3. 設定濾波器。

我們将使用濾波器組 0,并工作在 32 位辨別符屏蔽位模式下。先設定 CAN_FMR的 FINIT 位,讓過濾器組工作在初始化模式下,然後設定濾波器組 0 的工作模式以及辨別符 ID和屏蔽位。最後激活濾波器,并退出濾波器初始化模式。

在 HAL 庫中,提供了函數 HAL_CAN_ConfigFilter 用來初始化 CAN 的濾波器相關參數。

HAL_CAN_ConfigFilter 函數體中,在初始化濾波器之前,會設定 CAN_FMR 寄存器的 FINIT位為 1 讓其進入初始化模式,然後初始化 CAN 濾波器相關的寄存器之後,會設定 CAN_FMR寄存器的 FINIT 位為 0 讓其退出初始化模式。是以我們在調用這個函數的前後不需要再進行初始化模式設定。

CAN_FilterTypeDef  sFilterConfig;
  /*配置CAN過濾器*/
  sFilterConfig.FilterBank = 0;                     //過濾器0
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
  sFilterConfig.FilterIdHigh = 0x0000;              //32位ID
  sFilterConfig.FilterIdLow = 0x0000;
  sFilterConfig.FilterMaskIdHigh = 0x0000;          //32位MASK
  sFilterConfig.FilterMaskIdLow = 0x0000;
  sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;//過濾器0關聯到FIFO0
  sFilterConfig.FilterActivation = ENABLE;          //激活濾波器0
  sFilterConfig.SlaveStartFilterBank = 14;
  //過濾器配置
  if (HAL_CAN_ConfigFilter(&CAN1_Handler, &sFilterConfig) != HAL_OK)
  {
    while(1){}
  }      
  1. 發送接收消息

    在初始化 CAN 相關參數以及過濾器之後,接下來就是發送和接收消息了。HAL 庫中提供了發送和接受消息的函數。

    發送消息的函數是:

HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox)      
  • 入口參數 hcan為 CAN_HandleTypeDef 結構體指針類型
  • pHeader 為發送的指針
  • aData 是待發送資料
  • pTxMailbox 為發送郵箱指針

    接收消息的函數是:

HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[])      

第一個入口參數為 CAN 句柄,第二個為 FIFO 号,然後是接收指針及資料存放的位址。我們接受之後,隻需要讀取 pHeader 便可擷取接收資料和相關資訊。

至此,CAN 就可以開始正常工作了。如果用到中斷,就還需要進行中斷相關的配置。

功能設計

  1. CAN 的初始化
CAN初始化
//tsjw:重新同步跳躍時間單元.範圍:CAN_SJW_1TQ~CAN_SJW_4TQ
//tbs2:時間段2的時間單元.   範圍:CAN_BS2_1TQ~CAN_BS2_8TQ;
//tbs1:時間段1的時間單元.   範圍:CAN_BS1_1TQ~CAN_BS1_16TQ
//brp :波特率分頻器.範圍:1~1024; tq=(brp)*tpclk1
//波特率=Fpclk1/((tbs1+tbs2+1)*brp); 其中tbs1和tbs2我們隻用關注辨別符上标志的序号,例如CAN_BS2_1TQ,我們就認為tbs2=1來計算即可。
//mode:CAN_MODE_NORMAL,普通模式;CAN_MODE_LOOPBACK,回環模式;
//Fpclk1的時鐘在初始化的時候設定為36M,如果設定CAN1_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_MODE_LOOPBACK);
//則波特率為:36M/((8+9+1)*4)=500Kbps
//傳回值:0,初始化OK;
//    其他,初始化失敗; 
u8 CAN1_Mode_Init(u32 tsjw,u32 tbs2,u32 tbs1,u16 brp,u32 mode)
{
  CAN_InitTypeDef   CAN1_InitConf;
  CAN1_Handler.Instance=CAN1;
  CAN1_Handler.Init = CAN1_InitConf;
  
    CAN1_Handler.Init.Prescaler=brp;        //分頻系數(Fdiv)為brp+1
    CAN1_Handler.Init.Mode=mode;          //模式設定 
    CAN1_Handler.Init.SyncJumpWidth=tsjw;     //重新同步跳躍寬度(Tsjw)為tsjw+1個時間機關 CAN_SJW_1TQ~CAN_SJW_4TQ
    CAN1_Handler.Init.TimeSeg1=tbs1;        //tbs1範圍CAN_BS1_1TQ~CAN_BS1_16TQ
    CAN1_Handler.Init.TimeSeg2=tbs2;        //tbs2範圍CAN_BS2_1TQ~CAN_BS2_8TQ
    CAN1_Handler.Init.TimeTriggeredMode=DISABLE;  //非時間觸發通信模式 
    CAN1_Handler.Init.AutoBusOff=DISABLE;     //軟體自動離線管理
    CAN1_Handler.Init.AutoWakeUp=DISABLE;     //睡眠模式通過軟體喚醒(清除CAN->MCR的SLEEP位)
    CAN1_Handler.Init.AutoRetransmission=ENABLE;  //禁止封包自動傳送 
    CAN1_Handler.Init.ReceiveFifoLocked=DISABLE;  //封包不鎖定,新的覆寫舊的 
    CAN1_Handler.Init.TransmitFifoPriority=DISABLE; //優先級由封包辨別符決定 
    if(HAL_CAN_Init(&CAN1_Handler)!=HAL_OK)     //初始化
    return 1;
    return 0;
}      

該函數帶有 5 個參數,可以設定 CAN 通信的波特率和工作模式等.

2. CAN 封包的發送

主要是設定辨別符 ID等資訊,寫入資料長度和資料,并請求發送,實作一次封包的發送。

//can發送一組資料(固定格式:ID為0X12,标準幀,資料幀)   
//len:資料長度(最大為8)                     
//msg:資料指針,最大為8個位元組.
//傳回值:0,成功;
//       其他,失敗;
u8 CAN1_Send_Msg(u8* msg,u8 len)
{   
    u8 i=0;
    u32 TxMailbox;
    u8 message[8];
    TxHeader.StdId=0X12;        //标準辨別符
    TxHeader.ExtId=0x12;        //擴充辨別符(29位)
    TxHeader.IDE=CAN_ID_STD;    //使用标準幀
    TxHeader.RTR=CAN_RTR_DATA;  //資料幀
    TxHeader.DLC=len;                
    for(i=0;i<len;i++)
    {
        message[i]=msg[i];
    }
    if(HAL_CAN_AddTxMessage(&CAN1_Handler, &TxHeader, message, &TxMailbox) != HAL_OK)//發送
    {
        return 1;
    }
    while(HAL_CAN_GetTxMailboxesFreeLevel(&CAN1_Handler) != 3) {}
    return 0;
}      
  1. CAN接收函數

    用來接受資料并且将接受到的資料存放到 buf 中。

//can口接收資料查詢
//buf:資料緩存區;     
//傳回值:0,無資料被收到;
//       其他,接收的資料長度;
u8 CAN1_Receive_Msg(u8 *buf)
{
    u32 i;
    u8  RxData[8];

    if(HAL_CAN_GetRxFifoFillLevel(&CAN1_Handler, CAN_RX_FIFO0) != 1)
    {
        return 0xF1;
    }

    if(HAL_CAN_GetRxMessage(&CAN1_Handler, CAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK)
    {
        return 0xF2;
    }
    for(i=0;i<RxHeader.DLC;i++)
    buf[i]=RxData[i];
    return RxHeader.DLC;
}      
  1. 主函數

我們通過按鍵選擇 CAN 的工作模式(正常模式/環回模式),然後通過 KEY0 控制資料發送,并通過查詢的辦法,将接收到的資料顯示在序列槽上(選擇非CAN占用的口)。如果是環回模式,我們不需要 2 個開發闆。如果是正常模式,我們就需要 2 個開發闆,并且将他們的 CAN 接口對接起來,然後一個開發闆發送資料,另外一個開發闆将接收到的資料顯示在序列槽上。

int main(void)
{ 
    u8 key;
  u8 i=0,t=0;
  u8 cnt=0;
  u8 canbuf[8];
  u8 res;
  u8 mode=1;  
  HAL_Init();                       //初始化HAL庫    
  Stm32_Clock_Init(RCC_PLL_MUL9);     //設定時鐘,72M
  delay_init(72);                   //初始化延時函數
  uart_init(115200);          //初始化序列槽
  usmart_dev.init(84);          //初始化USMART 
  LED_Init();             //初始化LED  
  KEY_Init();             //初始化按鍵
  CAN1_Mode_Init(CAN_SJW_1TQ,CAN_BS2_8TQ,CAN_BS1_9TQ,4,CAN_MODE_LOOPBACK); //CAN初始化,波特率500Kbps      
  CAN_Config();   
  printf("LoopBack Mode");   
  printf("KEY0:Send WK_UP:Mode");//顯示提示資訊     
  printf("Count:");     //顯示目前計數值 
  printf("Send Data:");   //提示發送的資料 
  printf("Receive Data:");  //提示接收到的資料    
    while(1)
    {
        key=KEY_Scan(0);
    if(key==KEY0_PRES)//KEY0按下,發送一次資料
    {
      for(i=0;i<8;i++)
      {
        canbuf[i]=cnt+i;//填充發送緩沖區
        if(i<4)printf("%d\r\n",canbuf[i]);  //顯示資料
        else printf("%d\r\n",canbuf[i]);  //顯示資料
      }
      res=CAN1_Send_Msg(canbuf,8);//發送8個位元組 
      if(res)printf("Failed");    //提示發送失敗
      else printf("OK    ");      //提示發送成功                   
    }else if(key==WKUP_PRES)//WK_UP按下,改變CAN的工作模式
    {    
      mode=!mode;
            if(mode==0)  CAN1_Mode_Init(CAN_SJW_1TQ,CAN_BS2_8TQ,CAN_BS1_9TQ,4,CAN_MODE_NORMAL);        //回環模式,波特率500Kbps
            else if(mode==1) CAN1_Mode_Init(CAN_SJW_1TQ,CAN_BS2_8TQ,CAN_BS1_9TQ,4,CAN_MODE_LOOPBACK);  //回環模式,波特率500Kbps
      CAN_Config();
      if(mode==0)//普通模式,需要2個開發闆
      {
        printf("Nnormal Mode ");      
      }else //回環模式,一個開發闆就可以測試了.
      {
        printf("LoopBack Mode");
      }
    }    
    key=CAN1_Receive_Msg(canbuf);
    if(key < 9)//接收到有資料
    {     

      for(i=0;i<key;i++)
      {                     
        if(i<4)printf("%d\r\n",canbuf[i]);  //顯示資料
        else printf("%d\r\n",canbuf[i]);  //顯示資料
      }
    }
    t++; 
    delay_ms(10);
    if(t==20)
    {
      LED0=!LED0;//提示系統正在運作 
      t=0;
      cnt++;
      printf("%d\r\n",cnt); //顯示資料
    }      
  } 
}
    }    
    key=CAN1_Receive_Msg(canbuf);
    if(key < 9)//接收到有資料
    {     

      for(i=0;i<key;i++)
      {                     
        if(i<4)printf("%d\r\n",canbuf[i]);  //顯示資料
        else printf("%d\r\n",canbuf[i]);  //顯示資料
      }
    }
    t++; 
    delay_ms(10);
    if(t==20)
    {
      LED0=!LED0;//提示系統正在運作 
      t=0;
      cnt++;
      printf("%d\r\n",cnt); //顯示資料
    }      
  } 
}      

後續

繼續閱讀