一、CAN中断接收(线程只能非阻塞读)
线程 Open can设备时,驱动默认初始化并激活 Filter bank0:绑定到FIFO0,single 32-bit scale,mask mode,不过滤报文的任何 bit。
若使能了宏 “RT_CAN_USING_HDR”:
1、CAN2的起始Filter bank 强制设为 14
PS:应用层在配置 filter时,若要指定使用 filter n,注意使用CAN1时,n不能 >= 14,使用CAN2时,n 不能 < 14)
2、各Filter bank 强制配置为 single 32-bit scale
3、各Filter bank 强制绑定到 FIFO0
4、各Filter bank 设为 mask mode时,IDE位、RTR位 强制需要匹配
5、线程A 传入的ID、mask,都不包含IDE位、RTR位
6、线程A 设置 Filter bank_X 为 List mode时, Filter bank_X被分成 filter 0 和 filter 1,线程A 传入的 ID 写入 filter 0, 传入的 mask 写入 filter 1(即线程A 一次设置了 两个要过滤的报文)。
存在的问题:线程A 配置Filter bank 或 读CAN报文时都要设置 参数“hdr”,参数“hdr”表示 Filter bank 的编号,共有28个 Filter bank,因此定义数组can->hdr[ 28] ,并使用 filter match index (filter num ) 作为数组的索引。问题是:Filter bank的编号 是定死的,而 filter num 是FIFO0 根据绑定的各个Filter bank的配置自动设置的,二者可能不一样。只要任何一个Filter bank 被线程A设置为 List mode,则 FIFO0中的 Filter Match Index 和 Filter bank编号 不相等,线程A 读指定hdr的报文时就有问题,接收的报文保存到can->hdr[ filter_num ]时也会有问题。
解决方法:为了让 Filter bank编号 和 filter num 保持一一对应关系,将各Filter bank 强制配置为 single 32-bit scale 、mask mode,即一个Filter bank,只有一个 filter num。不管线程A配置Filter bank 是 List mode 还是 Mask mode,驱动都认为是 Mask mode(在Mask mode时,只要mask值设对,也能实现 List mode)。
线程A 在配置Filter bank时,若模式为 List mode,则 参数mask可忽略(线程A 一次只能设置 一个要过滤的报文)。驱动发现模式为 List mode时,除了把 ID、IDE位、RTR位等过滤值写入Filter bank寄存器外,也会根据 ID、IDE位、RTR位 计算出对应的 mask值,并将mask值写入Filter bank寄存器(驱动通过 Mask mode实现 List mode)。
7、缓存 msg_list_X.data 被APP读取后,将同时脱离链表 can->can_rx->uselist (rt_list_remove(&msg_list_X.list)) 和链表
can->hdr[ msg_list_X.data.hdr ].list (rt_list_remove(&msg_list_X.hdrlist)),
并插入链表 can->can_rx->freelist
(rt_list_insert_before(&can->can_rx->freelist, &msg_list_X.list)))。
8、应用层如果有多份CAN协议,每份协议都可以定义一个线程,然后每个线程设置自己的过滤器,读的话只读自己设置的过滤器过滤的报文,这样多份协议就相互独立,互不影响。
9、can->hdr[ filter_num ]: 编号为 filter_num 的过滤器 成功过滤一帧报文并保存到FIFO0时,软件在中断函数中从FIFO0读出此报文,并保存到 空闲节点msg_list_X中(链表freelist有节点则直接取出,否则把链表uselist的最老的那个节点取出来),然后把 节点msg_list_X 插入链表 can->hdr[ filter_num ].list、链表uselist。当线程A读报文时,把链表 can->hdr[ filter_num ].list(指定hdr)的首节点 or 链表uselist(不指定hdr)的首节点X 取出并将其内容复制到线程A的buff中,然后将节点X从 链表can->hdr[ filter_num ].list 和 链表uselist 中一起移除,最后把节点X插入链表freelist。
二、CAN中断发送(线程只能阻塞发送)
线程 Open can设备时,驱动默认初始化CAN:
① 禁能自动唤醒,使能bus-off自动管理,禁能自动重发,发送邮箱优先级 by the request order。
② 使能 EWGF、EPVF和BOFF中断(使能后,若 EWGF、EPVF、BOFF三个标志中任何一个置位,则ERRI标志置位);
使能ERRI中断(使能后,若ERRI标志置位则向NVIC发送CAN1_SCE中断请求);
在NVIC中使能了 CAN1_SCE中断。
PS:
存在的问题:EWGF、EPVF、BOFF等标志是只读标志,当发送、接收失败次数达到指定阈值时硬件自动置位这些标志,且必须总线恢复正常后标志才能由硬件清零。这样可能出现的问题是,当总线出现了异常,导致can1发送和接收一直失败,则EWGF、EPVF、BOFF等标志最终会置位,并且因为总线一直处于异常状态,这些标志不会由硬件清零,因此它们会一直触发 CAN1_SCE中断,导致CPU卡死在中断中,线程无法被运行。
-----总线上没有接收节点时,节点A发送报文时会卡死在CAN1_SCE中断----
-----CAN总线异常时,节点A会卡死在CAN1_SCE中断-------
解决方法:在CAN1_SCE中断函数中,EWGF、EPVF、BOFF等标志置位时,禁能其对应的中断。
1、线程配置 can->config.privmode 为 RT_CAN_MODE_NOPRIV时:驱动使用链表freelist 来指定 TX Mailbox_X 发送。
线程A 获取 tx_fifo互斥量(永久等待),从 链表freelist 取出首节点 sndbxinx_list_X 并找到与其绑定的 TX Mailbox_X,接着把报文写到 TX Mailbox_X 并请求发送此邮箱(若无法发送则把节点sndbxinx_list_X 重新插入 链表freelist,并释放 tx_fifo互斥量 ,然后返回线程A),然后永久等待完成量“sndbxinx_list_X.completion”… … 线程A被唤醒后,表明 TX Mailbox_X 发送结束,把 节点sndbxinx_list_X 重新插入 链表freelist,并释放 tx_fifo互斥量,若发送失败(sndbxinx_list_X.result = RT_CAN_SND_RESULT_ERR)则直接返回线程A,否则继续发送线程A 的下一帧报文。
在CAN1_TX中断函数中,释放完成量“sndbxinx_list_X.completion”,并通过变量“sndbxinx_list_X.result”告诉线程A 发送 TX Mailbox_X是成功还是失败 。
PS:
存在的问题:线程A 使用 TX Mailbox_X 发送时,线程A被挂起永久等待完成量“sndbxinx_list_X.completion”,这个完成量是在 标志RQCP_X置位 触发的 CAN1_TX中断里被释放的。这样可能出现的问题是,在使用自动重传功能时,如果总线异常了,导致 TX Mailbox_X 发送失败了,则 TX Mailbox_X 会重发,RQCP_X保持清零状态,不触发CAN1_TX中断,那么完成量“sndbxinx_list_X.completion”就不会被释放,线程A将一直被挂起,其他所有等待获取tx_fifo互斥量的线程也将一直被挂起。
解决方法:使用自动重传功能后,TX Mailbox_X 发送错误时,RQCP_X标志为0,TERR_X标志为1,且CAN1_SCE中断会触发,在CAN1_SCE中断函数中,遍历 TX Mailbox_0、 TX Mailbox_1、 TX Mailbox_2 三个邮箱,发现 TX Mailbox_X 的 TERR_X 标志为1 且 RQCP_X 标志为0,则说明 TX Mailbox_X 发送失败且使能了重发功能,此时软件手动取消 TX Mailbox_X 的发送(置位ABRQ_X标志),取消后,RQCP_X置位触发CAN1_TX中断,这样在 CAN1_TX中断函数中就可以释放完成量“sndbxinx_list_X.completion”。
2、线程配置 can->config.privmode 为 RT_CAN_MODE_PRIV时:驱动使用线程A指定的 TX Mailbox_X 发送。
线程A 通过设置 msg.priv 告诉驱动用哪个发送邮箱发送。驱动根据 msg.priv 找到邮箱 TX Mailbox_X(msg.priv表示邮箱编号,取值 0 ~ 2 ),接着把报文写到 TX Mailbox_X 并请求发送此邮箱(若无法发送则返回线程A),然后永久等待完成量“sndbxinx_list_X.completion”… … 线程A被唤醒后,表明 TX Mailbox_X 发送结束,若发送失败(sndbxinx_list_X.result = RT_CAN_SND_RESULT_ERR)则直接返回线程A,否则继续发送线程A 的下一帧报文。
在CAN1_TX中断函数中,释放完成量“sndbxinx_list_X.completion”,并通过变量“sndbxinx_list_X.result”告诉线程A发送 TX Mailbox_X 是成功还是失败 。
PS:线程A在 用TX Mailbox_X 发送时,若其他线程也调用 device_write() 并使用 TX Mailbox_X 发送,则在rt_completion_wait中 RT_ASSERT失败。
参考资料
[1] https://gitee.com/rtthread/rt-thread/blob/master/components/drivers/can/dev_can.c
[2] https://gitee.com/rtthread/rt-thread/blob/master/bsp/stm32/libraries/HAL_Drivers/drivers/drv_can.c