注:在CAN驅動模塊中僅CAN1調試完成,CAN2未調試
CAN 通信距離和通信速度的關系如下:
波特率/kbps 1000 500 250 125 100 50 20 10
距 離/m 40 130 270 530 620 1300 3300 6700
1、CAN總線封裝接口如下:
a、CAN端口初始化:unsigned int can_init(eCanType_t can, eGpioType_t txgpio, ePinType_t txpin, eGpioType_t rxgpio, ePinType_t rxpin, eCanBuadRateType_t baudrate, unsigned char prio)
b、CAN端口過濾器ID設置:unsigned int can_filter_id_set(eCanType_t can, unsigned char filter_id)
c、CAN1端口發送函數:unsigned int can1_write(const unsigned char *pbuf, unsigned int length, unsigned int extId)
d、CAN2端口發送函數:unsigned int can2_write(const unsigned char *pbuf, unsigned int length, unsigned int extId)
e、注冊CAN接收回調函數:unsigned int can_isr_callback_add(eCanType_t can, unsigned int (*pfunc)(const unsigned char *pbuf, unsigned char length, unsigned int extId))
2、CAN總線模塊對外開放的枚舉類型如下:
typedef enum _eCanType
{
eCAN1,
eCAN2,
eCAN_COUNT, // 注:這不是CAN端口類型,不可刪除,僅用做數量統計
}eCanType_t;
typedef enum _eCanBuadRateType
{
eCAN_BR_20K, // CAN建議最長通信距離3300米
eCAN_BR_50K, // CAN建議最長通信距離1300米
eCAN_BR_100K, // CAN建議最長通信距離620米
eCAN_BR_125K, // CAN建議最長通信距離530米
eCAN_BR_250K, // CAN建議最長通信距離270米
eCAN_BR_500K, // CAN建議最長通信距離130米
eCAN_BR_800K, // CAN建議最長通信距離70米
eCAN_BR_1000K, // CAN建議最長通信距離40米
}eCanBuadRateType_t;
3、CAN總線模塊代碼實現如下:
* 函數功能:CAN中斷優先級設置(組4),注意優先級不能超過設
定的組的范圍否則會有意想不到的錯誤。組划分如下:
組0:0位搶占優先級,4位響應優先級
組1:1位搶占優先級,3位響應優先級
組2:2位搶占優先級,2位響應優先級
組3:3位搶占優先級,1位響應優先級
組4:4位搶占優先級,0位響應優先級
搶占優先級和響應優先級的數值越小,優先級越高,處理器優
先比較搶占優先級然后再看響應優先級。
* 形 參:prio:搶占優先級(分組4的響應優先級固定為0)
channel:中斷通道
* 返 回 值:無
********************************************************/
static void can_nvic_set(unsigned char prio, unsigned char channel)
{
// unsigned int temp, temp1;
// 設置分組
unsigned char nvic_group = 4; // 0 - 4
// temp1 = (~nvic_group) & 0x07; // 取后三位
// temp1 = temp1 << 8;
// temp = SCB->AIRCR; // 讀取先前的設置
// temp &= 0x0000F8FF; // 清空先前分組
// temp |= 0x05FA0000; // 寫入鑰匙
// temp |= temp1;
// SCB->AIRCR = temp; // 設置分組
unsigned int temp;
unsigned char sub_prio = 0; // 分組4的響應優先級固定為0
// 注:優先級的設置必須要大於configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
// 否則不能在中斷服務函數中調用FreeRTOS相關的API函數
if(prio <= 5)
{
prio = 6;
}
else if(prio > 15)
{
prio = 15;
}
temp = prio << (4 - nvic_group);
temp |= sub_prio & (0x0F >> nvic_group);
temp = temp & 0xF; // 取低四位
NVIC->ISER[channel >> 5] |= 1 << (channel % 32); // 使能中斷位(要清除的話,設置ICER對應位為1即可)
NVIC->IP[channel] |= temp << 4; // 設置響應優先級和搶斷優先級
}
/********************************************************
* 函數功能:CAN初始化
* 形 參:can:CAN端口
txgpio:CAN TX 端口
txpin:CAN TX 引腳
rxgpio:CAN RX 端口
rxpin:CAN RX 引腳
baudrate:CAN波特率
prio:CAN中斷優先級(數值越小優先級越高,范圍:6 - 15)
* 返 回 值:0=成功
1=無法識別的CAN端口
2=CAN進入硬件初始化模式失敗
3=波特率錯誤
4=CAN退出硬件初始化模式失敗
********************************************************/
unsigned int can_init(eCanType_t can, eGpioType_t txgpio, ePinType_t txpin, eGpioType_t rxgpio, ePinType_t rxpin, eCanBuadRateType_t baudrate, unsigned char prio)
{
CAN_TypeDef *CANx = NULL;
unsigned short timeout = 0;
if(can != eCAN1 && can != eCAN2)
{
return 1; // 無法識別的CAN端口
}
// 找到CAN句柄
CANx = (can == eCAN1)? CAN1 : CAN2;
gpio_config(rxgpio, rxpin, eGPIO_AF_UP, 0);
gpio_config(txgpio, txpin, eGPIO_AF_UP, 0);
// 引腳復用映射配置
gpio_af_config(txgpio, txpin, eGPIO_AF_CAN1); // CAN1/CAN2的復用是一樣的,所以這里只用CAN1的就行
gpio_af_config(rxgpio, rxpin, eGPIO_AF_CAN1); // CAN1/CAN2的復用是一樣的,所以這里只用CAN1的就行
// CAN時鍾配置(42MHz)
RCC->APB1ENR |= 0x1UL << (can + 25); // 初始化CAN時鍾(使用CAN2時CAN1時鍾也必須要初始化)
RCC->APB1RSTR |= 0x1UL << (can + 25); // Enable CANx reset state
RCC->APB1RSTR &= ~(0x1UL << (can + 25)); // Release CANx from reset state
CANx->MCR &= ~(0x1UL << 1); // 退出睡眠模式
CANx->MCR |= CAN_MCR_INRQ; // 請求進入硬件自動初始化模式
while((CANx->MSR & CAN_MSR_INAK) != CAN_MSR_INAK)
{
timeout++;
if(timeout > 0xFFF0)
{
return 2; // CAN進入硬件初始化模式失敗
}
}
// 配置CAN寄存器
CANx->MCR |= 0x0UL << 7; // 關閉時間觸發通信使能
CANx->MCR |= 0x0UL << 6; // 自動離線管理,也就是如果檢測到總線上出錯,本節點自動離線
CANx->MCR |= 0x0UL << 5; // 設置需要時自動喚醒,如果DISABLE,則需要軟件喚醒
CANx->MCR |= 0x1UL << 4; // 禁止報文自動傳送
CANx->MCR |= 0x0UL << 3; // 在接收的FIFO中如果溢出,自動覆蓋原有報文
CANx->MCR |= 0x0UL << 2; // 優先級由請求順序確定(優先級由報文標識符決定)
CANx->BTR = 0x00UL; // 清除原來的設置.
CANx->BTR |= 0x0UL << 30; // 正常工作模式設置:0=普通模式,1=回環模式;
// 配置CAN波特率
switch(baudrate)
{
case eCAN_BR_20K:
CANx->BTR |= 0x00UL << 24; // 重新同步跳躍寬度:0x00 - 0x03
CANx->BTR |= 0x07UL << 16; // TBS1范圍:0x00 - 0x0F
CANx->BTR |= 0x00UL << 20; // TBS2范圍:0x00 - 0x07
CANx->BTR |= 208 - 1; // 分頻系數
break;
case eCAN_BR_50K:
CANx->BTR |= 0x00UL << 24; // 重新同步跳躍寬度:0x00 - 0x03
CANx->BTR |= 0x07UL << 16; // TBS1范圍:0x00 - 0x0F
CANx->BTR |= 0x00UL << 20; // TBS2范圍:0x00 - 0x07
CANx->BTR |= 84 - 1; // 分頻系數
break;
case eCAN_BR_100K:
CANx->BTR |= 0x00UL << 24; // 重新同步跳躍寬度:0x00 - 0x03
CANx->BTR |= 0x07UL << 16; // TBS1范圍:0x00 - 0x0F
CANx->BTR |= 0x00UL << 20; // TBS2范圍:0x00 - 0x07
CANx->BTR |= 42 - 1; // 分頻系數
break;
case eCAN_BR_125K:
CANx->BTR |= 0x00UL << 24; // 重新同步跳躍寬度:0x00 - 0x03
CANx->BTR |= 0x0DUL << 16; // TBS1范圍:0x00 - 0x0F
CANx->BTR |= 0x00UL << 20; // TBS2范圍:0x00 - 0x07
CANx->BTR |= 21 - 1; // 分頻系數
break;
case eCAN_BR_250K:
CANx->BTR |= 0x00UL << 24; // 重新同步跳躍寬度:0x00 - 0x03
CANx->BTR |= 0x0BUL << 16; // TBS1范圍:0x00 - 0x0F
CANx->BTR |= 0x00UL << 20; // TBS2范圍:0x00 - 0x07
CANx->BTR |= 12 - 1; // 分頻系數
break;
case eCAN_BR_500K:
CANx->BTR |= 0x00UL << 24; // 重新同步跳躍寬度:0x00 - 0x03
CANx->BTR |= 0x04UL << 16; // TBS1范圍:0x00 - 0x0F
CANx->BTR |= 0x07UL << 20; // TBS2范圍:0x00 - 0x07
CANx->BTR |= 6 - 1; // 分頻系數
break;
case eCAN_BR_800K:
CANx->BTR |= 0x00UL << 24; // 重新同步跳躍寬度:0x00 - 0x03
CANx->BTR |= 0x0AUL << 16; // TBS1范圍:0x00 - 0x0F
CANx->BTR |= 0x00UL << 20; // TBS2范圍:0x00 - 0x07
CANx->BTR |= 4 - 1; // 分頻系數
break;
case eCAN_BR_1000K:
CANx->BTR |= 0x00UL << 24; // 重新同步跳躍寬度:0x00 - 0x03
CANx->BTR |= 0x04UL << 16; // TBS1范圍:0x00 - 0x0F
CANx->BTR |= 0x07UL << 20; // TBS2范圍:0x00 - 0x07
CANx->BTR |= 3 - 1; // 分頻系數
break;
default: return 3; // 波特率錯誤
}
CANx->MCR &= ~(unsigned int)CAN_MCR_INRQ; // 請求退出初始化模式
while((CANx->MSR & CAN_MSR_INAK) == CAN_MSR_INAK)
{
timeout++;
if(timeout > 0xFFF0)
{
return 4; // CAN退出硬件初始化模式失敗
}
}
// 配置CAN中斷優先級
can_nvic_set(prio, (can == eCAN1)? CAN1_RX0_IRQn : CAN2_RX0_IRQn);
return 0;
}
/********************************************************
* 函數功能:CAN過濾器ID設置,與此ID相同的數據幀才會被接收
* 形 參:can:CAN端口
filter_id:過濾ID
* 返 回 值:0=成功
1=失敗,28組過濾器使用完了
2=無法識別的CAN端口
********************************************************/
unsigned int can_filter_id_set(eCanType_t can, unsigned char filter_id)
{
CAN_TypeDef *CANx = NULL;
unsigned int filter_bit = 0;
unsigned char can_filter_id = 0;
static unsigned char can1_filter_id = 0;
static unsigned char can2_filter_id = 0;
// 找到CAN句柄
if(can == eCAN1)
{
CANx = CAN1;
can_filter_id = can1_filter_id++; // CAN1的濾波器:0 - 13
if(can1_filter_id > 14)
{
return 1; // 14組過濾器使用完了
}
}
else if(can == eCAN2)
{
CANx = CAN2;
can_filter_id = 14 + can2_filter_id++; // CAN2的濾波器,:14 - 27
if(can2_filter_id > 14)
{
return 1; // 14組過濾器使用完了
}
}
else
{
return 2;
}
filter_bit = 0x1UL << can_filter_id;
CANx->FMR |= 0x00000001UL; // 過濾器組工作在初始化模式
CANx->FA1R &= ~filter_bit; // 過濾器暫不激活
CANx->FM1R &= ~filter_bit; // 過濾器工作在標識符屏蔽位模式
CANx->FS1R |= filter_bit; // 過濾器位寬為32位(16位bit需要清除對應位)
// CAN過濾器有2個32位存儲器,第一個用於存放32bit標識符,第二個用於存放掩碼
// 若掩碼的某一bit為1,則收到數據的相應bit必須與標識符對應bit相同才會被接收
// 擴展ID存放在3 - 17bit,標准ID存放在剩下的高位中,低位還有IDE\RTR位標明了這一幀報文的屬性
// IDE位標明是標准幀還是擴展幀,RTR位標明這一幀報文是數據幀還是遠程遙控幀(用於請求數據)
CANx->sFilterRegister[can_filter_id].FR1 = 0x00000000UL | (filter_id << 24); // 32位ID
// ID左移三位后(左對齊),desID處於最高八個bit,所以此處只需最高八個bit一致就允許接收
CANx->sFilterRegister[can_filter_id].FR2 = 0xFF000000UL; // 32位MASK
CANx->FFA1R &= ~filter_bit; // 過濾器關聯到FIFO0
// CANx->FFA1R |= filter_bit; // 過濾器關聯到FIFO1
CANx->FA1R |= filter_bit; // 激活過濾器
CANx->FMR &= ~0x0000001UL; // 過濾器組進入正常模式
CANx->IER |= 0x02UL; // enable FIFO 0 message pending Interrupt
can_filter_id++;
return 0;
}
/********************************************************
* 函數功能:CAN發送數據幀
* 形 參:can:CAN端口
pbuf:數據指針
length:數據字節數
extId:擴展ID
* 返 回 值:0=成功
1=發送失敗(物理連接或初始化問題導致)
2=無空郵箱,發送失敗
********************************************************/
static unsigned int can_write_msg(eCanType_t can, const unsigned char *pbuf, unsigned int length, unsigned int extId)
{
unsigned int data = 0;
unsigned int state = 0;
unsigned char mbox = 0;
unsigned short timeout = 0;
CAN_TypeDef *CANx = NULL;
// 找到CAN句柄
CANx = (can == eCAN1)? CAN1 : CAN2;
// 查找空郵箱
if((CANx->TSR & (0x1UL << 26)) != 0)
{
mbox = 0; // 郵箱0為空
state = CAN_TSR_RQCP0 | CAN_TSR_TXOK0 | CAN_TSR_TME0;
}
else if((CANx->TSR & (0x1UL << 27)) != 0)
{
mbox = 1; // 郵箱1為空
state = CAN_TSR_RQCP1 | CAN_TSR_TXOK1 | CAN_TSR_TME1;
}
else if((CANx->TSR & (0x1UL << 28)) != 0)
{
mbox = 2; // 郵箱2為空
state = CAN_TSR_RQCP2 | CAN_TSR_TXOK2 | CAN_TSR_TME2;
}
else
{
return 2; // 無空郵箱,發送失敗
}
CANx->sTxMailBox[mbox].TIR = 0; // 清除之前的設置
// extId = extId << 21; // 標准幀
extId = extId << 3; // 擴展幀
CANx->sTxMailBox[mbox].TIR |= extId;
CANx->sTxMailBox[mbox].TIR |= 0x1UL << 2; // 0=標准幀,1=擴展幀
CANx->sTxMailBox[mbox].TIR |= 0x0UL << 1; // 0=數據幀,1=遠程幀
CANx->sTxMailBox[mbox].TDTR &= ~(0x0000000F); // 先清除數據長度
CANx->sTxMailBox[mbox].TDTR |= length & 0x0FUL; // 設置數據長度
// 數據存入郵箱(小端模式)
data = (data << 8) + pbuf[7];
data = (data << 8) + pbuf[6];
data = (data << 8) + pbuf[5];
data = (data << 8) + pbuf[4];
CANx->sTxMailBox[mbox].TDHR = data;
data = (data << 8) + pbuf[3];
data = (data << 8) + pbuf[2];
data = (data << 8) + pbuf[1];
data = (data << 8) + pbuf[0];
CANx->sTxMailBox[mbox].TDLR = data;
CANx->sTxMailBox[mbox].TIR |= 0x1UL << 0; //請求發送郵箱數據
timeout = 0; // 超時變量清零
do
{
// 獲取CAN發送狀態
switch (CANx->TSR & state)
{
case 0: // CAN transmission pending
break;
case CAN_TSR_RQCP0 | CAN_TSR_TME0:
case CAN_TSR_RQCP1 | CAN_TSR_TME1:
case CAN_TSR_RQCP2 | CAN_TSR_TME2:
break; // CAN transmission failed
case CAN_TSR_RQCP0 | CAN_TSR_TXOK0 | CAN_TSR_TME0:
case CAN_TSR_RQCP1 | CAN_TSR_TXOK1 | CAN_TSR_TME1:
case CAN_TSR_RQCP2 | CAN_TSR_TXOK2 | CAN_TSR_TME2:
return 0; // CAN transmission succeeded
default: break; // CAN transmission failed
}
}while(timeout++ < 0xFFF);
return 1; // 發送失敗
}
/********************************************************
* 函數功能:CAN1端口發送數據幀
* 形 參:pbuf:數據指針
length:數據字節數
extId:擴展ID
* 返 回 值:0=成功
1=發送失敗(物理連接或初始化問題導致)
2=無空郵箱,發送失敗
********************************************************/
unsigned int can1_write(const unsigned char *pbuf, unsigned int length, unsigned int extId)
{
return can_write_msg(eCAN1, pbuf, length, extId);
}
/********************************************************
* 函數功能:CAN2端口發送數據幀
* 形 參:pbuf:數據指針
length:數據字節數
extId:擴展ID
* 返 回 值:0=成功
1=發送失敗(物理連接或初始化問題導致)
2=無空郵箱,發送失敗
********************************************************/
unsigned int can2_write(const unsigned char *pbuf, unsigned int length, unsigned int extId)
{
return can_write_msg(eCAN2, pbuf, length, extId);
}
/********************************************************
* 函數功能:注冊CAN中斷接收回調函數
* 形 參:can:指定CAN端口
pfunc:回調函數指針
* 返 回 值:0=成功
1=CAN端口錯誤
2=函數指針為NULL
* 開 發 者:王志超
* 維護日期:2020年5月7日
* 修訂日志:開發
********************************************************/
unsigned int can_isr_callback_add(eCanType_t can, unsigned int (*pfunc)(const unsigned char *pbuf, unsigned char length, unsigned int extId))
{
if(can >= eCAN_COUNT)
{
return 1;
}
if(pfunc == NULL)
{
return 2;
}
pCallBack[can] = pfunc;
return 0;
}
/********************************************************
* 函數功能:CAN中斷回調函數
* 形 參:can:指定CAN端口
* 返 回 值:無
********************************************************/
static void can_isr_callback(eCanType_t can)
{
unsigned int extId = 0;
unsigned char mbox = 0; // 0=CAN_FIFO0, 1=CAN_FIFO1
CAN_TypeDef *CANx = NULL;
// 找到CAN句柄
CANx = (can == eCAN1)? CAN1 : CAN2;
// 獲取標識符選擇位的值
extId = CANx->sFIFOMailBox[mbox].RIR & 0x04;
if(extId == 0)
{
extId = CANx->sFIFOMailBox[mbox].RIR >> 21; // 標准標識符
}
else
{
extId = CANx->sFIFOMailBox[mbox].RIR >> 3; // 擴展標識符
}
// unsigned char rtr = CANx->sFIFOMailBox[mbox].RIR & 0x02; // 獲取遠程發送請求值
unsigned char len = CANx->sFIFOMailBox[mbox].RDTR & 0x0F; // 獲取數據長度
unsigned char data[8] = {0};
// 獲取數據
data[0] = (unsigned char)(CANx->sFIFOMailBox[mbox].RDLR >> 0);
data[1] = (unsigned char)(CANx->sFIFOMailBox[mbox].RDLR >> 8);
data[2] = (unsigned char)(CANx->sFIFOMailBox[mbox].RDLR >> 16);
data[3] = (unsigned char)(CANx->sFIFOMailBox[mbox].RDLR >> 24);
data[4] = (unsigned char)(CANx->sFIFOMailBox[mbox].RDHR >> 0);
data[5] = (unsigned char)(CANx->sFIFOMailBox[mbox].RDHR >> 8);
data[6] = (unsigned char)(CANx->sFIFOMailBox[mbox].RDHR >> 16);
data[7] = (unsigned char)(CANx->sFIFOMailBox[mbox].RDHR >> 24);
// 釋放郵箱
if(mbox == 0)
{
CANx->RF0R |= 0x20; // 釋放FIFO0郵箱
}
else if(mbox == 1)
{
CANx->RF1R |= 0x20; // 釋放FIFO1郵箱
}
// 數據處理
if(pCallBack[can] != NULL)
{
pCallBack[can](data, len, extId); // 執行用戶數據處理函數
}
}
/********************************************************
* 函數功能:CAN1中斷服務函數
* 形 參:無
* 返 回 值:無
********************************************************/
void CAN1_RX0_IRQHandler(void)
{
can_isr_callback(eCAN1);
}
/********************************************************
* 函數功能:CAN1中斷服務函數
* 形 參:無
* 返 回 值:無
********************************************************/
void CAN2_RX0_IRQHandler(void)
{
can_isr_callback(eCAN2);
}
