STM32F407VET6 底层驱动之CAN总线寄存器封装
注:在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);
}