can接口相對是一種常用的串行接口,但是不像spi、i2c、uart等接口都有主從的關系,can可以任何一個節點主動發送數據,並且假如出現總線沖突會有硬件來處理。
can和rs485又有些類似,都是把ttl信號轉換成了差分信號。所以在stm32 使用can的時候會有一個can收發器。
STM32 CAN 發送的簡單測試
從電路上看起來也很簡單,stm32也是通過can tx、rx兩根線和收發器相連。所以假如我們要測試can的發送,是不是只接can tx腳就可以了?
我最開始也以為這樣就可以,但是深究can的總線沖突檢測原理就會發現這樣行不通的。因為can 在發送數據的時候也會同時接收發送的數據,通過把接收的數據和內部發送寄存器的數據做對比,是不是一致就知道總線有沒有沖突。所以在正常情況(這里意味着非正常情況下也可以)下can rx不接就到這發送出去的數據無法收到從而硬件自動判斷為發送失敗。
所以要保證發送數據成功,can tx腳和can rx腳要都接上,並且確保can收發器供電正常。
硬件上就這些主要注意點,接下來就主要是軟件的配置了。
一般stm32 配置can有以下幾大步驟:
can的初始化(cubemx直接可以生成代碼)
can的啟動
can濾波器的設置(用來接收的,發送的時候可以不用配置它)
can執行發送數據請求
我們只測試can的發送,所以就只用關系1、2、4步驟就可以了。
第一步,配置stm32cubemx
STM32 CAN 發送的簡單測試
如上圖所示,最關鍵主要配置如下三個參數,分頻數我這里配置48,下面的time Quantum值就會自動計算出來。因為can時鍾是48mhz經過48分頻后,一個單位時間就是1us=1000ns。
因為我想要100k波特率,然后填寫下面的Time segment1(簡稱 Tbs1 )和Time segment2 (簡稱 Tbs2) 為5和4。那么具體波特率該怎么計算還是要看看官方手冊的描述:
STM32 CAN 發送的簡單測試
根據如上描述,能決定波特率的也就是三個參數:分頻值、Tbs1、Tbs2。需要注意的是,這個SYNC_SEG的1tq是固定值。和stm32cubemx中的jump width不要弄混淆了。jump width這個時間參數是作為補償時間的上線,當時間有偏差的時候,就會自動補償,最長時間不能超過該參數設定值。
第二步,stm32cubemx生成的CAN代碼是不帶過濾器的,需要自己手動添加。
typedef struct
{
uint32_t mailbox;
CAN_TxHeaderTypeDef hdr;
uint8_t payload[8];
}CAN_TxPacketTypeDef;
typedef struct
{
CAN_RxHeaderTypeDef hdr;
uint8_t payload[8];
}CAN_RxPacketTypeDef;
/// CAN過濾器寄存器位寬類型定義
typedef union
{
__IO uint32_t value;
struct
{
uint8_t REV : 1; ///< [0] :未使用
uint8_t RTR : 1; ///< [1] : RTR(數據幀或遠程幀標志位)
uint8_t IDE : 1; ///< [2] : IDE(標准幀或擴展幀標志位)
uint32_t EXID : 18; ///< [21:3] : 存放擴展幀ID
uint16_t STID : 11; ///< [31:22]: 存放標准幀ID
} Sub;
} CAN_FilterRegTypeDef;
#define CAN_BASE_ID 0 ///< CAN標准ID,最大11位,也就是0x7FF
#define CAN_FILTER_MODE_MASK_ENABLE 1 ///< CAN過濾器模式選擇:=0:列表模式 =1:屏蔽模式
#define CAN_ID_TYPE_STD_ENABLE 1 ///< CAN過濾ID類型選擇:=1:標准ID,=0:擴展ID
void CAN_Filter_Config(void)
{
CAN_FilterTypeDef sFilterConfig;
CAN_FilterRegTypeDef IDH = {0};
CAN_FilterRegTypeDef IDL = {0};
#if CAN_ID_TYPE_STD_ENABLE
IDH.Sub.STID = (CAN_BASE_ID >> 16) & 0xFFFF; // 標准ID高16位
IDL.Sub.STID = (CAN_BASE_ID & 0xFFFF); // 標准ID低16位
#else
IDH.Sub.EXID = (CAN_BASE_ID >> 16) & 0xFFFF; // 擴展ID高16位
IDL.Sub.EXID = (CAN_BASE_ID & 0xFFFF); // 擴展ID低16位
IDL.Sub.IDE = 1; // 擴展幀標志位置位
#endif
sFilterConfig.FilterBank = 0; // 設置過濾器組編號
#if CAN_FILTER_MODE_MASK_ENABLE
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // 屏蔽位模式
#else
sFilterConfig.FilterMode = CAN_FILTERMODE_IDLIST; // 列表模式
#endif
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32位寬
sFilterConfig.FilterIdHigh = IDH.value; // 標識符寄存器一ID高十六位,放入擴展幀位
sFilterConfig.FilterIdLow = IDL.value; // 標識符寄存器一ID低十六位,放入擴展幀位
sFilterConfig.FilterMaskIdHigh = IDH.value; // 標識符寄存器二ID高十六位,放入擴展幀位
sFilterConfig.FilterMaskIdLow = IDL.value; // 標識符寄存器二ID低十六位,放入擴展幀位
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // 過濾器組關聯到FIFO0
sFilterConfig.FilterActivation = ENABLE; // 激活過濾器
sFilterConfig.SlaveStartFilterBank = 14; // 設置從CAN的起始過濾器編號,本單片機只有一個CAN,顧此參數無效
if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
}
uint8_t CAN_Transmit(CAN_TxPacketTypeDef* packet)
{
if(HAL_CAN_AddTxMessage(&hcan, &packet->hdr, packet->payload, &packet->mailbox) != HAL_OK)
return 1;
return 0;
}
void CAN_Init(void)
{
MX_CAN_Init();
CAN_Filter_Config();
HAL_CAN_Start(&hcan);
HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING); // 使能CAN接收中斷
}
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *canHandle)
{
static CAN_RxPacketTypeDef packet;
// CAN數據接收
if (canHandle->Instance == hcan.Instance)
{
if (HAL_CAN_GetRxMessage(canHandle, CAN_RX_FIFO0, &packet.hdr, packet.payload) == HAL_OK) // 獲得接收到的數據頭和數據
{
printf("\r\n\r\n\r\n################### CAN RECV ###################\r\n");
printf("STID:0x%X\r\n",packet.hdr.StdId);
printf("EXID:0x%X\r\n",packet.hdr.ExtId);
printf("DLC :%d\r\n", packet.hdr.DLC);
printf("DATA:");
for(int i = 0; i < packet.hdr.DLC; i++)
{
printf("0x%02X ", packet.payload[i]);
}
HAL_CAN_ActivateNotification(canHandle, CAN_IT_RX_FIFO0_MSG_PENDING); // 再次使能FIFO0接收中斷
}
}
}
CAN_TxPacketTypeDef g_CanTxPacket;
void CAN_SetTxPacket(void)
{
g_CanTxPacket.hdr.StdId = 0x321; // 標准ID
// g_CanTxPacket.hdr.ExtId = 0x10F01234; // 擴展ID
g_CanTxPacket.hdr.IDE = CAN_ID_STD; // 標准ID類型
// g_CanTxPacket.hdr.IDE = CAN_ID_EXT; // 擴展ID類型
g_CanTxPacket.hdr.DLC = 8; // 數據長度
g_CanTxPacket.hdr.RTR = CAN_RTR_DATA; // 數據幀
// g_CanTxPacket.hdr.RTR = CAN_RTR_REMOTE; // 遠程幀
g_CanTxPacket.hdr.TransmitGlobalTime = DISABLE;
for(int i = 0; i < 8; i++)
{
g_CanTxPacket.payload[i] = i;
}
}
int main()
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
CAN_Init();
printf("----------------------------------------\r\n");
CAN_SetTxPacket();
while(1)
{
if(CAN_Transmit(&g_CanTxPacket) != 0)
printf("failed\r\n");
HAL_Delay(1000);
}
}