教你通过SPI接MCU实现拓展CAN接口(第一部分:SOC侧基于SPI的CAN)(附程序源代码)

本文参考:

1、【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81.pdf

首先科普一下:

1、SPI

SPI是什么?SPI即Serial Peripheral Interface—串行外设接口,我刚接触它的时候想过:为什么它叫这个名字?是不是当时世界上没有串行外设接口呢?

其实并不是,在SPI推出之前,已经存在其他类型的串行接口,例如,RS-232和I²C(Inter-Integrated Circuit)都是在SPI之前开发和使用的串行通信协议。

2、CAN

CAN是什么?CAN即Controller Area Network—控制器局域网,它由Bosch(博世)公司在20世纪80年代开发,旨在解决汽车电子控制单元(ECU)之间的通信问题。

目前CAN广泛应用于汽车电子系统、工业自动化、医疗设备、工程机械等领域。

需求:

本人从事的领域内CAN基本上是必需的,基本每个产品都会用到并且很依赖它,而一些SOC原生只支持1路或2路CAN,数量达不到要求,更有甚者原生CAN本身有问题,会丢帧或错帧,或是压根就没有CAN。

遇到这个问题,我们自己有一套解决方案来应对,那就是本文的主题:通过SPI接MCU实现拓展CAN接口,接下来就让我跟大家详细描述下我们是如何实现的吧!

硬件平台:rockchip(瑞芯微)rv1126

一、创建spi设备

需要先配置设备树devicetree,可以看到kernel/arch/arm/boot/dts/rv1126.dtsi里已经有spi控制器的设备树配置了,我们只需要在对应的dts里进行适配修改即可。

假设我用到的是spi0,它在rv1126.dtsi里的内容如下:

在dts里进行适配,如下:

详细分析下适配部分:

如果需要使用该设备节点,status必须设成okay,

然后创建1个spi0节点的子节点,即spidev@00,其中spidev为节点名称,00为设备地址,

子节点的内容有3项,第1个是compatible = "rockchip,spidev";

1)compatible属性也叫做“兼容性”属性,这是非常重要的一个属性!compatible属性的值是一个字符串列表,内核加载时,会根据这个compatible值在内核里匹配和它一致的驱动。

2)reg属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息,它的值一般是(address,length)对,且受到父节点#address-cells和#size-cells这2个参数值的限制,#address-cells的值意为reg属性中address这个数据所占用的字长,#size-cells的值意为reg属性中length这个数据所占用的字长,所以reg=<0x0>;的意思是spidev@00这个设备的首地址相对于父节点(spi@ff450000)的首地址偏移量是0,也就是0xff450000。

3)spi-max-frequency属性的值即为该spi设备的最大工作频率为0x4c4b40,转换成十进制为5000000(5M)。

二、编写SPI驱动代码

上面就是设备树的配置内容,接下来就是内核驱动了,Linux下SPI的驱动框架有分层,包括主机驱动和设备驱动,可以这样理解:对于裸机MCU来说SPI驱动是很简单的,通过调用SPI库函数实现对SPI控制器的配置,再根据SPI外设情况匹配对应的读写接口函数,SPI驱动就全部写完了。在SOC上要做的其实也是一样的事情,只是内核把“通过调用SPI库函数实现对SPI控制器的配置”归为主机驱动,把“根据SPI外设情况匹配对应的读写接口函数”归为设备驱动而已。

SDK已经默认有了主机驱动,代码路径在kernel/drivers/spi/,其中spi.c为SPI核心层代码,spi-xxx.c为具体平台对应的SPI控制器驱动程序,在本项目工程代码中spicandev.c即为SPI设备驱动文件。

三、基于SPI协议继续封装

在这一层(spicanprotocol)我们将spi的数据封装成了SOC与MCU之间spi-can通讯协议要求的样子,对于MCU这一侧来说最上层就是SPI,而对于SOC这一侧来说最上层是CAN,所以SOC端还需要继续封装,于是便有了CanManager这一层,用于将spi协议数据封成API接口给应用调用。

为了模拟CAN数据的收发,我们应该从硬件层面出发,比较SPI与CAN的不同:CAN存在一组发送/接收的缓冲区,故可以在内存中开辟一块区域作为发送/接收缓冲区,并且缓冲区施行的是“先进先出”策略,当缓冲区满的时候,替换最早进入缓冲区的数据。最开始我们想到的是用循环链表来实现这个模型,为什么采用循环链表?因为方便实现“当缓冲区满的时候,替换最早进入缓冲区的数据”这一需求。

假设有一个容量为200帧CAN数据的内存区域,将其用循环链表组织在一起用作接收缓冲区,设定2个指针A、B,A指针指向最早进入缓冲区的待接收数据,B指针指向最晚进入缓冲区的待接收数据,存在一个无限循环的CAN接收/发送线程,不停地轮询请求数据,并将数据被放入接收缓冲区,原本共同指向同一个默认节点的A、B指针中的B指针朝前挪了若干“步”,“有进也有出”,系统也不停地从接收缓冲区接收数据,意味着A指针也在不停地朝前挪,若进的速度比出的速度大,那可能就会造成数据被覆盖,此时在B的“逼迫”下,A不得不加快朝前挪。而CAN发送没有设置缓冲区的概念。

有了设计构思,于是我们便开始写代码实现它。结果,按照该方案构建的SPI-CAN接口在实际使用中出现了丢帧的情况,于是我们不得不更改设计。在领导的指点下,我们用了kfifo即linux内核无锁队列,不用自己造轮子,直接用成熟方案真是香,后面稍微调整下,spi-can就能稳定使用了。

如上内容相关程序源码已上传BAIDU网pan,有需要请通过链接查看:

链接:https://pan.baidu.com/s/1dwJKySWPLMScBdTaBeQhDA 

提取码: 78tr

如果觉得本文对您有帮助,欢迎点赞+收藏+关注哈!谢谢大家!

作者:大卡车司机

物联沃分享整理
物联沃-IOTWORD物联网 » 教你通过SPI接MCU实现拓展CAN接口(第一部分:SOC侧基于SPI的CAN)(附程序源代码)

发表回复