• 基于DJYOS的SPI驱动编写指导手册


    1.贡献者列表

    深圳市秦简计算机系统有限公司DJYOS驱动开发团队。

    2.概述

    DJYOS的DjyBus总线模型为IIC、SPI之类的器件提供统一的访问接口,SPIBUS模块是DjyBus模块的一个子模块,为SPI器件提供统一的编程接口,实现通信协议层与器件层的分离。也标准化了SPI总线和 Device驱动接口,本手册指导驱动工程师编写SPI的接口程序。
    SPI总线使用手册,请参见《都江堰操作系统用户手册》。
    局限性:DJYOSV1.1.1版本的SPI驱动只提供主设备功能。

    3.总线资源结构

    SPI通信协议是一种总线通信方式,这意味着一条总线上可以挂多个符合总线通信协议的设备,DjyBus资源组织结构就是符合这样一种物理的连接方式。如图 21所示资源组织结构图,总线类型 “SPI”、第n条总线“SPIn”、第n条总线上面的设备“Devn”,它们都是DjyBus资源树上面的资源节点,每次向总线“SPIn”上面增加一个设备,便向资源树上面增加了一个资源节点,它是“SPIn”的子节点。
    图2-1 总线资源结构

    图 2-1 总线资源结构

    4.准备工作

    在编写SPI 器件驱动前,建议完成必要的准备工作,如:
    1、认真阅读器件手册,了解通信协议、参数、操作流程等内容;
    2、熟悉spibus.h头文件中提供的API,懂得参数的使用方法;
    3、阅读SPI总线协议文档,熟练掌握SPI总线。

    5.SPI 总线驱动接口

    5.1.驱动架构

    SPIBus是DjyBus模块的一个子模块,其结构如图 41所示,它为SPI器件提供标准的、一致的应用程序编程接口,并且规范了硬件驱动接口。驱动接口分为总线控制器接口和SPI器件接口两部分,驱动的重点是总线控制器,而器件接口实际上就是配置一下该器件的物理参数。
    建议文件路径:在eclipse工程中的链接目录如下,如果是导入官方提供的example工程,那么该目录已经建立,在硬盘中添加文件后,只需要刷新工程即会自动添加进工程中。
    src->OS_code->bsp->cpudrv->src->cpu_peri_spi.c。
    相应的头文件目录为:
    src->OS_code->bsp->cpudrv-> src->cpu_peri_spi.h。
    在文件系统(硬盘)中的目录结构是:
    djysrc\bsp\cpudrv\cpu_name\src\cpu_peri_spi.c。
    djysrc\bsp\cpudrv\cpu_name\include\cpu_peri_spi.h。
    根据以上命名,可以在DJYOS官方提供的代码中,找到大量范例。
    以上文件命名并非绝对,例如LPC17xx的SPI模块,硬件被官方命名为SSP模块,DJYOS提供的源码中,其文件名就命名为cpu_peri_ssp.c。
    SPI驱动程序编写重点有:
    1、初始化SPI控制器,并且把SPI总线添加到DjyBus上。
    2、实现图 41中的5个回调函数(哪些需要实现,参考后续章节)。
    3、如果采用中断方式,须编写中断服务函数(实际上也是为4个回调函数服务)
    SPIbus总线驱动架构

    图 4-1 SPIBus总线驱动架构

    5.2.初始化函数

    5.2.1.Step1:初始化硬件

    1、SPI控制器硬件的初始化,包括传输速度、IO配置、时钟等;
    2、挂载SPI中断到中断系统,并配置中断类型,如配置为异步信号(若只采用轮询方式,则此功能可省略);

    5.2.2.Step2:初始化参数结构体

    添加SPI总线的参数类型为struct tagSPI_Param,由函数SPI_BusAdd或SPI_BusAdd_s完成对SPI总线控制块的初始化和添加SPIn总线节点到DjyBus资源树。
    代码 4-1 SPI参数结构体定义

    struct tagSPI_Param
    {
        char            *BusName;               //总线名称,如SPI1
        u8              *SPIBuf;                //总线缓冲区指针
        u32              SPIBufLen;             //总线缓冲区大小,字节
        ptu32_t         SpecificFlag;			//SPI私有标签,如控制寄存器基址
        bool_t           MultiCSRegFlag;      //SPI控制寄存器是否有多套CS配置寄存器
        TransferFunc    pTransferTxRx;		//发送接收回调函数,中断方式
        TransferPoll    pTransferPoll;		//发送接收回调函数,轮询方式
        CsActiveFunc    pCsActive;			//片选使能
        CsInActiveFunc  pCsInActive;			//片选失能
        SPIBusCtrlFunc  pBusCtrl;				//控制函数
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    SPI主设备同一时刻只能与一个从设备通信,收发同时进行,因此同一个SPI控制器中,多个片选可以共用缓冲区。
    很多的SPI控制器对每个片选都提供一套配置通信参数寄存器,例如,CS0与从设备通信采用速度5Mbit/s,字符宽度为8比特,MSB,对应的配置片选CS0对应的寄存器,而CS1的从设备采用速度10Mbit/s,字符宽度为16比特,LSB,对应的配置片选CS1对应的寄存器。这种增强型的控制器对于一主多从,参数不一的通信能大大提高通信效率,简化参数配置。
    SPI参数结构体的回调函数参数的原型如代码 42所示,其中PrivateTag就是结构体中SPI的私有标签,即SPIn寄存器基址。
    代码 4-2 SPI回调函数类型申明

    typedef ptu32_t (*TransferFunc)(ptu32_t SpecificFlag,u32 sendlen,u32 recvlen,u32 recvoff);
    typedef bool_t (*TransferPoll)(ptu32_t SpecificFlag,u8* srcaddr,u32 sendlen,u8* destaddr,u32 recvlen,u32 recvoff);
    typedef bool_t (*CsActiveFunc)(ptu32_t SpecificFlag, u8 cs);
    typedef bool_t (*CsInActiveFunc)(ptu32_t SpecificFlag, u8 cs);
    typedef ptu32_t (*SPIBusCtrlFunc)(ptu32_t SpecificFlag,u32 cmd,ptu32_t data1,ptu32_t data2);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.2.3.Step3:挂载总线

    有多少SPI总线是由具体的平台决定,因此,增加SPI总线到DjyBus上是由总线驱动程序员完成,成功添加的“SPIn”节点会成为“SPI”节点的子节点。
    增加SPI总线的API函数可以调用SPI_BusAdd函数或SPI_BusAdd_s函数,两者的区别在于,SPI_BusAdd只需调用者提供已初始化好的参数结构体struct tagSPI_Param,而后者更需要提供struct tagSPI_CB结构体控制块(建议定义为静态变量)。

    5.3.回调函数

    5.3.1.轮询函数

    如果采用轮询方式收发,5个回调函数中只需要实现这一个,其他指针置为NULL即可。
    轮询函数使用场合:
    1、收发方式被设为轮询方式,则总是用轮询函数收发数据。默认值为中断方式,可调用SPI_BusCtrl函数设为轮询方式。
    2、在禁止调度(即禁止异步信号中断)期间,强制使用轮询方式。
    3、pTransferTxRx ==NULL,则使用轮询方式收发。
    4、系统初始化未完成,多事件调度尚未启动期间。
    如果使用中断方式收发,且不考虑在2~4三种情况下收发数据,则无须实现本函数,pTransferPoll指针设为NULL即可。
    回调函数说明如下:

    typedef bool_t (*TransferPoll)(ptu32_t SpecificFlag,u8* srcaddr,u32 sendlen,
                                        u8* destaddr,u32 recvlen,u32 recvoff);
    参数:
    		SpecificFlag:寄存器基址
    		srcaddr:发送数据存储地址。
    		sendlen:发送数据字节数。
    		destaddr:接收数据存储地址。
    		recvlen:接收数据字节数。
    		recvoff:接收偏移字节数,即认为接收到recvoff字节后的数据为有效,才存储。
    返回:true,执行成功;false,执行失败。
    说明:轮询方式主要应用于系统调度未启动或对实时性要求不高的场合,这种方法能够简化编程处理,快速实现通信功能。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    5.3.2.启动收发

    启动收发是在使用中断方式收发数据必须实现的回调函数,若使用轮询方式,无须实现本函数,本函数指针设置为NULL即可。
    回调函数说明如下:
    typedef ptu32_t (*TransferFunc)(ptu32_t SpecificFlag,u32 sendlen,
    u32 recvlen,u32 recvoff);
    参数:
    SpecificFlag,寄存器基址。
    sendlen,发送数据长度,字节单位。
    recvlen,接收数据长度,字节单位。
    recvoff,接收偏移,接收到多少个字节后开始保护数据,即有用数据。
    返回:true,中断方式启动通信成功,false,失败。
    说明:该函数功能是配置SPI寄存器,并保存有关参数,使能中断,SPI总线采用的是四线的收发方式,收、发、时钟和片选。TransferFunc实质上是实现了相关的寄存器的配置,并中断使能。当然,SPI总线协议规定,操作设备前必须把对应从设备的CS线拉低。
    对于TransferFunc需要完成的功能作如下说明:
    1、保存静态变量,如发送接收数据长度,接收偏移(从接收到的第几个数据开始保存数据);
    2、配置SPI寄存器,使其处于发送和接收的状态;
    3、配置中断使能,并触发中断,在中断中将数据发送接收完成。

    5.3.3.片选使能

    若使用轮询方式,本函数指针设置为NULL即可,但使能片选的功能须实现。
    typedef bool_t (*CsActiveFunc)(ptu32_t SpecificFlag, u8 cs);
    功能:片选拉低
    参数:
    SpecificFlag,寄存器基址
    cs,片选号
    返回:是否成功
    虽然对于具体的芯片,该函数的实现过程不相同,但是功能是相同的,即拉低片选,选择CS对应的SPI从器件通信。若控制器具有硬件自动片选,硬件驱动可加以利用,提高效率。

    5.3.4.片选失能

    若使用轮询方式,本函数指针设置为NULL即可,但失能片选的功能须实现。
    typedef bool_t (*CsInActiveFunc)(ptu32_t SpecificFlag, u8 cs);
    功能:片选拉高
    参数:
    SpecificFlag,寄存器基址
    cs,片选号
    返回:是否成功

    5.3.5.控制函数

    目前,控制函数主要实现对总线的配置,如自动片选、传输速度、SPI时序配置等。应用层将通过调用SPI_Ctrl接口函数,传递不同的命令和参数,实现对总线的控制。
    如果SPI控制器针对每个片选信号都有独立的配置寄存器,则在添加设备时(SPI_DevAddr),配置好每个片选寄存器;若多个片选共用一套配置寄存器,则每次传输都必须重新配置。在结构体SPI_CB中,成员multi_cs_reg是用来标记SPI控制器是否具有多套片选寄存器,在调用ModuleInstall_SPI时,硬件驱动会作相应的标记。
    从SPI协议的时序来讲配置参数会更加的清晰。如图 42和图 43所示,SPI通信首先需产生CS片选有效,即拉低对应的CS片选。时钟信号SPI_CLK在未通信状态时的电平状态由CHOL决定,为高或者为低。而CPHA决定时序的相位,当CPHA为0时,在SPI_CLK的第一个边沿采样,第二个边沿输出数据;当CPHA为1时,在SPI_CLK的第一个边沿输出数据,第二个边沿采样。
    SPI时序CPHA=0

    图 4-2 SPI时序CPHA=0
    SPI时序CPHA=1

    图 4-3 SPI时序CPHA=1
    CHOL和CPHA两种配置组成了四种模式,分别为模式0、1、2、3,如表 41所示,部分SPI从器件只支持部分模式,需根据具体器件配置成要求的模式。
    表 4-1 SPI模式
    spi模式
    控制命令用于应用层调用spibus.h的API的控制函数SPI_Ctrl实现参数配置或通信设置,与硬件相关的命令一览表如表 42所示。
    表 4-2 SPI命令表

    5.4.中断服务函数

    5.4.1.功能描述

    如果使用轮询方式实现驱动,则无须编写中断服务函数。
    SPI接收和发送使用中断方式的好处在于,将发送任务由SPI控制器完成,节省CPU的处理负荷,因此提高了程序的运行效率,缺点在于编程相对复杂。现在绝大多数的主流CPU的中断系统都支持SPI中断,包括发送接收中断等。
    SPI模块要求在中断服务函数内部完成的功能有如下:
    1、清中断标志,处理好接收与发送数据同时进行的硬件机制;
    2、接收数据从接收到recvoff字符数据后开始存储,即调用SPI_PortWrite;
    3、发送数据从SPI_PortRead读取,若没有读到数据,则代表数据已经发送完成;若此时接收的数据还未完成,应该继续往寄存器中写数据,直到接收完成;
    4、数据传输完成时,配置相应的寄存器,使其处于初始状态(视控制器而定);

    5.4.2.中断实现过程

    下面以Atmel芯片为例,通过流程图的方式简要说明SPI中断服务函数的数据处理流程图。值得注意的是,中断服务函数中有些变量是通过__SPI_TransferTxRx传递参数到底层硬件驱动,底层驱动通过静态变量存储,并在中断服务函数中使用。如发送接收数据大小,信号量等。
    在这里插入图片描述

    图 4-4 中断服务函数流程图

    5.5.移植建议

    为了简化编程,提高工作效率, BSP程序人员可采取下面的步骤快速的完成DJYOS驱动架构下SPI底层驱动的开发。
    1、拷贝其他工程已测试通过的SPI驱动文件cpu_peri_spi.c/cpu_peri_spi.h;
    2、添加SPI的中断号到critical.c文件下面tg_IntUsed数组;
    3、修改cpu_peri_spi.c/cpu_peri_spi.h中与具体SPI寄存器相关的部分;
    4、回调函数的具体实现和中断收发数据。
    调用器件驱动程序前,确保已经调用ModuleInstall_DjyBus和ModuleInstall_SPIBus安装DjyBus和SPIBus模块。

    6.SPI器件驱动接口

    建议将器件驱动的存放目录为djysrc\bsp\chip\xxx,其中,xxx是具体芯片的文件夹名称。
    SPI总线初始化完成后,添加一个器件到总线上的过程,非常简单,就是初始化一下该器件的寻址特性参数,然后调用SPI_DevAdd_s或SPI_DevAdd函数把器件添加到总线上即可。需配置的参数,都在spibus.h文件中定义的struct tagSPI_Device中描述。struct tagSPI_Device结构定义如下:
    代码 5-1 SPI器件结构体

    //SPI总线器件结构体
    struct tagSPI_Device
    {
        struct tagRscNode DevNode;
        u8 Cs;                                   //片选信号
        bool_t AutoCs;                          //自动片选
        u8 CharLen;                             //数据长度
        u8 Mode;                                //模式选择
        u8 ShiftDir;                            //MSB or LSB
        u32 Freq;                               //速度,Hz
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    6.1.初始化过程

    添加器件到总线的过程就是将器件节点挂到相应的“SPIn”总线节点的过程,同时,配置好相应的总线通信参数。现对添加SPI器件要点作如下说明:
    1、若使用SPI_DevAdd_s挂载器件,定义static struct tagSPI_Device类型的静态变量;
    2、若使用SPI_DevAdd_s挂载器件,初始化数据struct tagSPI_Param的各成员;
    3、调用SPI_DevAdd_s或SPI_DevAdd添加设备到总线节点。
    4、调用SPI_BusCtrl设置总线参数
    SPI_DevAdd_s或SPI_DevAdd都可以把器件添加到总线上,但两者是有区别的:
    1、使用SPI_DevAdd_s的话,你需要自己准备struct tagSPI_Device结构,并且自行初始化,特别是,当操作系统的spibus模块被修改导致该结构的定义发生变化时,器件驱动程序也需要修改。
    2、使用SPI_DevAdd_s的好处是,该结构无须动态分配,符合像OSEK之类的严谨规范。
    3、使用SPI_DevAdd的好处是,驱动程序非常简单。

    下面用ATMEL公司的AT45的EEPROM芯片为例说明添加设备过程。如代码 52所示,将AT45芯片添加到总线“SPI”,并命名为“SPI_Dev_AT45”。
    代码 5-2 添加SPI设备实例

    
    bool_t AT45_HardInit(void)
    {
    	bool_t result = false;
    	if(s_AT45_InitFlag == true)
    		return true;
    	static struct tagSPI_Device s_AT45_Dev;
    
    	s_AT45_Dev.AutoCs  	= false;
    	s_AT45_Dev.CharLen 	= 8;
    	s_AT45_Dev.Cs      	= CN_AT45_SPI_CS;
    	s_AT45_Dev.Freq    	= CN_AT45_SPI_FRE;
    	s_AT45_Dev.Mode    	= SPI_MODE_1;
    	s_AT45_Dev.ShiftDir = SPI_SHIFT_MSB;
    
    	if(NULL != SPI_DevAdd_s("SPI","SPI_Dev_AT45",&s_AT45_Dev))
    	{
    		ps_AT45_Dev = &s_AT45_Dev;
    
    		if(true == _at45db321_Check_ID())	//校验芯片ID
    		{
    			_at45db321_Binary_Page_Size_512();
    			s_AT45_InitFlag = true;
    			result = true;
    		}
    	}
    	return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    7.访问器件

    器件装载到装载总线之后,可以通过访问SPI总线实现访问器件,具体就是调用spibus.h提供的API函数SPI_Transfer()。

  • 相关阅读:
    Ubuntu 22.04 编译 DPDK 19.11 igb_uio 和 kni 报错解决办法
    第二章 类与名称空间
    Java字母异位词分组leetcode_49
    【网络编程】基础知识
    100天精通Golang(基础入门篇)——第21天:Go语言面向对象(OOP)核心概念解析
    【知识总结】金九银十offer拿到手软的前端面试题——HTML篇
    有哪些可以免费下载视频剪辑素材的网站?
    C++11智能指针weak_ptr
    【软件分析第13讲-学习笔记】符号执行 Symbolic Execution
    2023_Spark_实验七:Scala函数式编程部分演示
  • 原文地址:https://blog.csdn.net/wangjianzhongfj/article/details/127829650