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


    1.概述

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

    2.总线资源结构

    IIC通信协议是一种总线通信方式,这意味着一条总线上可以挂多个符合总线通信协议的设备,DjyBus资源组织结构就是符合这样一种物理的连接方式。在如图 21资源组织结构图,总线类型 “IIC”、第n条总线“IICn”、第n条总线上面的设备“Devn”,它们都是DjyBus资源树上面的资源结点,每次向总线“IICn”上面增加一个设备,便向资源树上面增加了一个资源结点,它是“IICn”的子结点。
    在这里插入图片描述

    图 2-1 总线资源结构

    3.准备工作

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

    4.IIC 总线驱动接口

    4.1.驱动架构

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

    图 4-1 IICBus总线驱动架构

    4.2.初始化函数

    4.2.1.step1:初始化硬件

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

    4.2.2.step2:初始化参数结构体

    添加IIC总线的参数类型为struct tagIIC_Param,由函数IIC_BusAdd或IIC_BusAdd_r完成对IIC总线控制块ICB的初始化和添加IICn总线结点到DjyBus资源树。
    代码 4-1 IIC参数结构体

    //IIC初始化参数结构体
    struct tagIIC_Param
    {
    	char 				*BusName;					//总线名称,如IIC1
    	u8 	 				*IICBuf;					//总线缓冲区指针
    	u32  				IICBufLen;					//总线缓冲区大小,字节
    	ptu32_t 			SpecificFlag;				//指定标记,如IIC寄存器基址
    	WriteReadPoll      pWriteReadPoll;			//轮询或中断未开时使用
    	WriteStartFunc     pGenerateWriteStart;		//写过程启动
    	ReadStartFunc      pGenerateReadStart;		//读过程启动
    	GenerateEndFunc    pGenerateEnd;				//结束通信
    	IICBusCtrlFunc     pBusCtrl;					//控制函数
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    根据IIC总线通信的特点可知,无论IIC总线主设备有多少从设备,在同一时刻,IIC主设备与从设备的通信只能单一单向,即单点通信,单向通信,因此,接收与发送使用同一个缓冲区。
    IIC参数结构体的回调函数参数的原型如代码 42所示,其中PrivateTag就是结构体中IIC的私有指定标签,即IICn寄存器基址。
    代码 4-2 IIC回调函数类型定义

    typedef bool_t (*WriteStartFunc)(ptu32_t  SpecificFlag,u8 DevAddr,
                                           u32 MemAddr,u8 MenAddrLen, u32 Length,
                                           struct tagSemaphoreLCB *IIC_BusSemp);
    typedef bool_t (*ReadStartFunc)(ptu32_t  SpecificFlag,u8 DevAddr,
                                          u32 MemAddr,u8 MemAddrLen, u32 Length,
                                          struct tagSemaphoreLCB *IIC_BusSemp);
    typedef void (*GenerateEndFunc)(ptu32_t  SpecificFlag);
    typedef s32 (*IICBusCtrlFunc)(ptu32_t SpecificFlag,u32 cmd,
    								  ptu32_t data1,ptu32_t data2);
    typedef bool_t (*WriteReadPoll)(ptu32_t  SpecificFlag,u8 DevAddr,
    u32 MemAddr,u8 MenAddrLen,u8* Buf, 
    u32 Length,u8 WrRdFlag);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4.2.3.step3:挂载总线

    有多少IIC总线是由具体的平台决定,因此,增加IIC总线到DjyBus上是由总线驱动程序员完成,成功添加的“IICn”结点会成为“IIC”结点的子结点。
    增加IIC总线的API函数可以调用IIC_BusAdd函数或IIC_BusAdd_s函数,两者的区别在于,IIC_BusAdd只需调用者提供已初始化好的参数结构体struct tagIIC_Param,而后者更需要提供struct tagIIC_CB结构体控制块,该控制块为静态或全局变量(建议定义为本C文件内部静态变量)。

    4.3.回调函数

    4.3.1.轮询函数

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

    回调函数说明如下:
    typedef bool_t (WriteReadPoll)(ptu32_t SpecificFlag,u8 DevAddr,u32 MemAddr, u8 MenAddrLen,u8 Buf, u32 Length,u8 WrRdFlag);
    参数:
    SpecificFlag:IIC控制器寄存器基址。
    DevAddr:设备地址,低七位有效。
    MemAddr:设备内部地址,若为存储设备,则为存储地址。
    MemAddrLen:设备内部地址字节数。
    Buf:数据缓冲区。
    Length:Buf中数据字节数。
    WrRdFlag:读写标志,0为写,1为读。
    返回:true, 执行成功;false,执行失败。
    说明:轮询函数在执行前必须关闭中断,否则将执行失败。轮询函数示例代码如代码 4-3所示。

    代码 4-3 轮询函数示例
    Bool_t __IIC_WriteReadPoll((tagI2CReg *reg,u8 DevAddr,u32 MemAddr,\
                                 u8 MemAddrLen,u8* Buf, u32 Length,u8 WrRdFlag)
    {
        __IIC_IntDisable(reg);
        if(WrRdFlag == CN_IIC_WRITE_FLAG)   //写
        {
            if(Length == __IIC_WritePoll(reg,DevAddr,MemAddr,
    MemAddrLen,Buf,Length))
                return true;
            else
                return false;
        }
        else                                //读
        {
            if(Length == __IIC_ReadPoll(reg,DevAddr,MemAddr, MemAddrLen,Buf,Length))
                return true;
            else
                return false;
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    4.3.2.启动发送

    启动发送函数(WriteStartFunc)是为中断方式收发服务的,轮询方式不需要,置为NULL即可。
    启动发送的回调函数WriteStartFunc完成了发送数据时IIC的启动时序,其执行的流程为start---->发送器件地址---->发送内部地址,并开启中断,然后返回。对应在时序上,如图 42所示。
    在这里插入图片描述

    图 4-2 启动发送
    如图中所示,写时序是以主控制器发送start信号为起始条件,紧接最低位为0从器件地址表示写操作,当收到ACK信号后会将从器件的存储地址(图中地址为2字节,具体多少字节视情况而定)发送到总线,最后发送正式的正文。
    回调函数说明如下:
    typedef bool_t (*WriteStartFunc)(ptu32_t SpecificFlag,u8 DevAddr,
    u32 MemAddr,u8 MenAddrLen, u32 Length,
    struct tagSemaphoreLCB *IIC_BusSemp);
    功能:产生IIC写数据时序,并发送内部地址
    参数:
    SpecificFlag,寄存器基址
    DevAddr,器件地址的前7比特,已将内部地址所占的bit位更新,该函数需将该地址左移一位增加最后一位读/写比特;
    MemAddr,存储器内部地址,即发送到总线上的地址,该地址未包含放在设备地址上的比特位;
    MenAddrLen,存储器内部地址的长度,字节单位,未包含在设备地址里面的比特位;
    Length,发送的数据总量,最后一个字节发送完时,需产生停止时序,并释放信号量;
    IIC_BusSemp,总线控制信号量,发送完数据后需底层驱动释放;
    返回:true,启动发送过程正确,false,发生错误

    4.3.3.启动接收

    启动发送函数(ReadStartFunc)是为中断方式收发服务的,轮询方式不需要,置为NULL即可。
    启动接收的回调函数ReadStartFunc主要完成了IIC时序上面读数据时的总线控制,读时序的时序控制过程如图 43所示。该函数依次实现了写start---->器件地址(写)---->写存储地址---->start(或者restart)---->器件地址(读)的时序过程。在启动接收时序正确完成后,需使能中断(若不使用中断,则需配置接收到数据pop的事件),并配置回复ACK,在中断中接收从器件发送的数据。
    在这里插入图片描述

    图 4-3 启动接收
    如图所示的时序图中,有两个start时序,可以通过配置repeated来重新启动一次新的时序,而不产生停止位。
    回调函数说明如下:
    typedef bool_t (*ReadStartFunc)(ptu32_t SpecificFlag,u8 DevAddr,
    u32 MemAddr,u8 MemAddrLen, u32 Length,
    struct tagSemaphoreLCB *IIC_BusSemp);
    功能:完成读时序的启动,并使能中断
    参数:
    SpecificFlag,寄存器基址
    DevAddr,从器件地址的高七比特(同__IIC_GenerateWriteStart参数说明)
    MemAddr,存储器件的内部地址(同__IIC_GenerateWriteStart参数说明)
    MemAddrLen,存储器件地址长度,字节单位(同__IIC_GenerateWriteStart参数说明)
    Length,接收的数据总量,接收数据的倒数第一字节,即count-1,停止产生ACK信号,当接收的字节数为count时,产生停止时序,并释放信号量iic_buf_semp;
    IIC_BusSemp,发送完成的缓冲区信号量,告知上层,本次发送已经完成。
    返回:TRUE,启动读时序成功,FALSE失败

    4.3.4.结束传输

    结束传输的回调函数__IIC_GenerateEnd主要用于停止当前正在进行的传输,特别是在发生超时传输时,用于停止本次发送或接收,实际上,该函数调用了产生停止时序的函数,使IIC主器件停止本帧数据的传输。启动和停止时序如图 44所示。
    在这里插入图片描述

    图 4-4 IIC启动和停止时序

    4.3.5.控制函数

    目前,控制IIC的底层驱动只需要实现对IIC总线传输时钟的控制即可,相对较为简单,此处不作详细说明,请参看源码cpu_peri_iic.c。

    4.4.中断服务函数

    4.4.1.中断实现过程

    如果使用轮询方式实现驱动,则无须编写中断服务函数。
    相比轮询通信方式,中断方式的执行效率更高,对CPU的消耗更少。由于各种控制器五花八门,因此,中断的具体实现方式也不同。但是基于DjyBus设计的IIC中断方式接收与发送数据大体的框架和流程基本相似。
    IIC模块对IIC总线驱动程序在中断中需要完成的功能作如下要求:
    第一,根据中断线或中断标志判断使用的IIC控制块和静态变量参数;
    第二,发送数据中断时,调用API函数IIC_ReadPort读取需要发送的参数,并将静态变量计数器IntParam->TransCount递增;
    第三,若发送结束,即IIC_ReadPort读不到数据,且IntParam->TransCount = IntParam->TransTotalLen,则需要产生停止时序和释放信号量;
    第四,若为接收数据中断,则需调用IIC_WritePort将接收到的数据写入缓冲区,并将计数器IntParam->TransCount递增,接收到倒数第二个数据时,还需配置寄存器不发送ACK信号;
    第五,若接收到所有数据,则需产生停止时序和释放信号量。
    下面以p1020的IIC控制器连接铁电为例,简要讲解一下中断服务函数中的流程。
    在中断服务函数内部,通过寄存器判断是发送中断还是接收中断,如图 45所示。发送中断时,需要判断是否收到IIC从器件ACK信号,然后读简易缓冲区中的数据,并发送之;若缓冲区中为空,判断发送的总量count是否为零,若是,则表示该帧数据已经全部发送完毕,需产生停止时序,释放信号量IntParam->pDrvPostSemp,该信号量是__IIC_GenerateWriteStart的参数。
    在接收中断中,需要判断是否为倒数第二个接收的字节,若是,需要配置控制寄存器不发送ACK信号,使控制器接收到倒数第二个字节时不发送ACK信号,用于通知从设备接收的数据足够。接收到数据后调用IIC_PortWrite,该函数将接收到的数据保存到用户缓冲区。若接收到最后一个字节数据,则产生停止时序,并释放信号量IntParam->pDrvPostSemp,本次接收完成。
    在这里插入图片描述

    图 4-5 中断方式接收发送流程图

    4.4.2.注意事项

    使用中断方式实现IIC主设备与从设备通信,需要注意以下几点:
    1、发送中断不仅要判断中断标志位,清标志位,同时还需判断是否接收到ACK信号;
    2、正常的发送结束时,IIC_PortRead读到数据为0,计数值IntParam->TransCount 与 IntParam->TransTotalLen应该相等,若不等,则可能出现逻辑错误;
    3、读数据的倒数第二个字节时,需停止时序,因为,此时数据已经发送到总线上面;
    4、通信结束后,需释放信号量和停止时序。

    4.5.移植建议

    由于大部分的IIC控制器的设计基本相似,因此,BSP程序人员可采取下面的步骤快速的完成DJYOS驱动架构下IIC底层驱动的开发。
    1、拷贝其他工程已测试通过的IIC驱动文件cpu_peri_iic.c/cpu_peri_iic.h;
    2、添加IIC的中断号到critical.c文件下面tg_IntUsed数组;
    3、修改cpu_peri_iic.c/cpu_peri_iic.h中与具体IIC寄存器相关的部分;
    4、回调函数的具体实现和中断收发数据。
    测试驱动前,确保已经调用初始化函数ModuleInstall_DjyBus(0)和ModuleInstall_IICBus(0)。

    5.IIC器件驱动接口

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

    struct tagIIC_Device
    {
        struct tagRscNode DevNode;
        u8 DevAddr;                 //七位的器件地址
        u8 BitOfMemAddrInDevAddr;//器件地址中内部地址所占比特位数
        u8 BitOfMemAddr;  //器件内部地址寻址总bit数,包含了BitOfMemAddrInDevAddr
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    对tagIIC_Device结构体作如下详细说明:

    1)器件地址DevAddr是七个比特地址,如0x50,在总线上体现的地址为0x80/0x81;
    2) BitOfMemAddrInDevAddr是指dev_addr的低三个比特中,有多少个bit用于器件内部存储空间寻址,取值范围:0~3。
    3) BitOfMemAddr表示被操作的器件内部地址的总位数,包含器件地址上的比特位;

    举例说明:存储大小为128K的铁电,器件地址为0x50,页大小为64K,则地址范围为0x00000 ~ 0x1FFFF,若存储地址占用器件地址的1个比特,则 dev_addr为0x50,BitOfMemAddrInDevAddr为1,BitOfMemAddr为17,构成了128K的寻址空间。

    5.1.初始化过程

    添加器件到总线的过程就是将器件结点挂到相应的“IICn”总线结点的过程,同时,配置好相应的总线通信参数。
    1、定义static struct tagIIC_Device类型的静态变量;
    2、初始化该变量的各成员;
    3、调用IIC_DevAdd_s或IIC_DevAdd添加设备到总线结点。
    4、调用IIC_BusCtrl设置总线参数
    下面用FreeScale公司的CRTOUCH触摸芯片为例说明添加设备过程。如代码 52所示,将CRTOUCH芯片添加到总线“IIC0”,并命名为“IIC_Dev_CRTCH”,并配置了总线速度和采用轮询通信方式。
    代码 522 添加IIC设备实例

    ptu32_t CRT_Init(ptu32_t para)
    {
    	bool_t result = false;
    	static struct tagIIC_Device s_CRT_Dev;
    
    	//初始化IIC设备结构体
    	s_CRT_Dev.DevAddr 					= CRT_ADDRESS;
    	s_CRT_Dev.BitOfMemAddr 				= 8;
    	s_CRT_Dev.BitOfMemAddrInDevAddr  	= 0;
    
    	//添加CRTCH到IIC0总线
    	if(NULL != IIC_DevAdd_s("IIC0","IIC_Dev_CRTCH",&s_CRT_Dev))
    	{
    		ps_CRT_Dev = &s_CRT_Dev;
    		IIC_BusCtrl(ps_CRT_Dev,CN_IIC_SET_CLK,CRT_CLK_FRE,0);
    		IIC_BusCtrl(ps_CRT_Dev,CN_IIC_SET_POLL,0,0);
    		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

    6.访问器件

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

  • 相关阅读:
    比特币全节点同步加速记录(使用Bitcoin Core钱包)
    C语言中的结构体
    数据湖技术之 Hudi 集成 Flink
    FFmpeg编译参数分析
    怎么样零代码零成本搭建个人网站?
    应届女生美团 Java 岗 4 面,一次性斩 offfer,我受到了万点暴击
    我的合肥 .NET 俱乐部线下活动之旅
    【数学建模暑期培训】配送中心选址问题
    Flink 实践 | B站流式传输架构的前世今生
    主流编程语言介绍
  • 原文地址:https://blog.csdn.net/wangjianzhongfj/article/details/127937581