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

news2025/4/19 5:59:33

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;				//控制函数
};

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);

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,执行失败。
说明:轮询方式主要应用于系统调度未启动或对实时性要求不高的场合,这种方法能够简化编程处理,快速实现通信功能。

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
};

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;
}

7.访问器件

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/5423.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 考试练习题 2

一、选择题 1、下列是 python 合法标识符的是( B)。 A. 2variable B. variable2 C. $anothervar D. if 2、在 python 中字符串的表示方式是(D )。 A.采用单引号包裹 B.采用双引号包裹 C.采用三重单引号包裹 D.ABC 都是 3、设有…

【浅学Linux】动态库与静态库的封装与使用

朋友们好,这里简单介绍一下LINUX学习中关于动态库与静态库的理解,以及站在封装和使用的角度去介绍是如何封装的?如何使用的? 文章目录一:动态库与静态库的理解二:静态库2.1:静态库的使用2.2&…

Sprite Editor

1、SpriteEditor SpriteEditor是精灵图片编辑器 它主要用于编辑2D游戏开发中使用的Sprite精灵图片 它可以用于编辑 图集中提取元素,设置精灵边框,设置九宫格,设置轴心(中心)点等等功能 2、Single图片编辑 Sprite Ed…

Docker原生网络、自定义网络、Docker容器通信、跨主机容器网络

Docker原生网络、自定义网络、Docker容器通信、跨主机容器网络Docker原生网络bridgeHostnoneDocker自定义网络自定义bridgeoverlaymacviandocker network所有基本命令Docker容器通信双冗余机制跨主机容器网络一些遗留错误解决错误1错误2错误3错误4Docker原生网络 docker安装时…

如何搭建node_exporter

如何搭建node_exporter 1.观看条件 1.假设你已经看过上一篇文章 《如何搭建普罗米修斯 Prometheus》 2.假设你已经会搭建普罗米修斯(promethus) 3.上面两个假设,只要满足一个。那你看这篇文章就没什么压力了 2.node_exporter是啥 node_…

UI自动化测试之selenium工具(浏览器窗口的切换)

目录 前言 方法 实例 ①示例1 ②示例2 附加知识 结语 前言 1、在浏览网页的时候,有时点击一个链接或者按钮,会弹出一个新的窗口。这类窗口也被称之为句柄(一个浏览器窗口的唯一标识符,通过句柄实现不同浏览器窗口之间的切…

C++Qt开发——文件操作

简介 QT中的IO操作通过统一的接口简化了文件与外部设备的操作方式,QT中文件被当作一种特殊的外部设备,文件操作与外部设备操作相同。IO操作的本质是连续存储空间的数据读写。 1. IO设备的类型 顺序存取设备:只能从头开始顺序读写数据&#…

python游戏库pygame经典教程

目录 一.Pygame程序基本搭建过程 1.初始化化程序 2.创建Surface对象 3.事件监听 4.游戏循环 二.Pygame Display显示模块详解 1.将Surface对象粘贴至主窗口上 2.设置窗口主窗口 3.填充主窗口背景,参数值RGB 4.设置窗口标题 5.更新屏幕内容 6.pygame.display其他方…

C# VS2022 EF6 + Mysql8.0.31 CodeFirsts使用配置

文章目录环境安装Mysql8.0.31下载Mysql连接器Net版本安装VS2022创建工程添加Nuget包修改配置文件如下准备工作,创建一个Dbcontext类,代码如下打开程序包控制台输入命令第一步第二步第三步第四步简要介绍一下如何添加一个表思路添加User实体类修改dbconte…

NodeMcu arduino ESP8266 搭建mqtt服务(然也物联)以及使用

NodeMcu arduino ESP8266 搭建mqtt服务以及使用 本文章学习借鉴于太极创客团队,以表感谢。官网http://www.taichi-maker.com/ 操作步骤:我们注册然也物联平台,申请社区版本,进行测试。 文章目录NodeMcu arduino ESP8266 搭建mqtt…

【Python基础篇021】黏包现象丨udp的socket服务

🌠前言 基于udp的socket服务有什么特点?黏包现象是什么?又是如何产生的?udp和tcp哪种会有黏包现象?看完这篇文章相信你会有所收获。 目录 🌠一、基于udp的socket服务 🌠二、TCP中的黏包现象 …

保边滤波之引导滤波与领域转换滤波

(1)引导滤波 局部窗口内输出图像O和引导图像G成线性关系OiakGibk, ∀i∈Ωk 假设输出图像O和输入图像I之间的关系为OiIi−ni,噪声最小即最小化ni,即 每个像素点i包含于多个窗口Ωk,每个窗口都会得到一个a、b值&#…

RK3399平台开发系列讲解(I/O篇)Linux最大文件数的限制机制

平台内核版本安卓版本RK3399Linux4.4Android7.1🚀返回专栏总目录 文章目录 一、Linux最大文件数的限制机制1.1、申请fd过程分析1.2、申请file内核对象过程分析沉淀、分享、成长,让自己和他人都能有所收获!😄 📢在Linux上能打开多少个文件,有两种限制: 第一种:进程级…

十、组件(8)

本章概要 递归组件异步更新队列Teleport 10.11.2 递归组件 组件可以在自己的模板中递归调用自身,但这需要使用 name 选项为组件指定一个内部调用的名称。 当调用 Vue.createApp({}).component({})全局注册组件时,这个全局的 ID 会自动设置为该组件的n…

【云原生】Docker的基本使用方法与优势

🍬Docker的基本使用方法和优势🥙一、简介🥪二、优势🌮三、基本使用🥙一、简介 Docker是基于Go语言实现的开源应用容器引擎,通过对应用组件的封装、分发、部署、运行等生命周期的管理,使用户的应…

配置Mysql与注册登录模块

后端职责可以粗浅的理解为处理各种数据,那么处理数据就可以从下面几个方面考虑: 数据的来源 根据不同的数据来源,我们探究两个方面的内容: 数据的形式 数据的操作 当然,一通操作以后,各个…

操作系统:进程与线程大解析

一文就懂进程与线程一、进程/线程相关概念进程中断并发与并行并发并行线程线程分类多进程和多线程上下文进程上下文进程上下文切换的场景线程上下文扩展:协程线程与协程的区别:协程的优势:二、进程/线程区别与关系进程和线程的区别进程和线程…

【C/C++】你知道位段吗?段位?不,是位段!

本章重点 什么是位段? 位段的内存分配 位段的跨平台问题 位段的应用 上一篇文章我详细介绍了第一种自定义类型--结构体。本章节我们认识一下另外一种自定义类型-- 位段。因为讲解位段时需要用到一点结构体的知识,所以我直接把链接放到这里可按需直接…

Swagger

一、Swagger简介 1.1、前言 前后端分离 Vue SpringBoot 当前主流的前后端分离技术栈 后端时代 前端只用管理静态页面,如html,其余的交给后端,而后端通过模板引擎,如jsp进行管理 前后端分离时代 后端:后端控制层&…

jsp健身房会员管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 健身房会员管理系统 是一套完善的web设计系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql,…