1. 概述

DJYOSDjyBus总线模型为IICSPI之类的器件提供统一的访问接口,IICBUS模块是DjyBus模块的一个子模块,为IIC器件提供统一的编程接口,实现通信协议层与器件层的分离。也标准化了IIC总线和 Device驱动接口,本手册指导驱动工程师编写IIC的接口程序。

IIC总线使用手册,请参见《都江堰操作系统用户手册》。

2. 总线资源结构

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

21 总线资源结构

3. 准备工作

在编写IIC 器件驱动前,建议完成必要的准备工作,如:

1、  认真阅读器件手册,了解通信协议、参数、操作流程等内容;

2、  熟悉iicbus.h头文件中提供的API,懂得参数的使用方法;

3、  阅读IIC总线协议文档,熟练掌握IIC总线。

4. IIC 总线驱动接口

4.1. 驱动架构

IICBusDjyBus模块的一个子模块,其结构如 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个回调函数服务)

41 IICBus总线驱动架构

4.2. 初始化函数

4.2.1. step1:初始化硬件

1、  IIC控制器硬件的初始化,包括传输速度、IO配置等;

2、  挂载IIC中断到中断系统,并配置中断类型,如配置为异步信号(若只采用轮询方式,则此功能可省略);

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

添加IIC总线的参数类型为struct tagIIC_Param,由函数IIC_BusAddIIC_BusAdd_r完成对IIC总线控制块ICB的初始化和添加IICn总线结点到DjyBus资源树。

代码 41 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;                 //控制函数

};

根据IIC总线通信的特点可知,无论IIC总线主设备有多少从设备,在同一时刻,IIC主设备与从设备的通信只能单一单向,即单点通信,单向通信,因此,接收与发送使用同一个缓冲区。

IIC参数结构体的回调函数参数的原型如代码 42所示,其中PrivateTag就是结构体中IIC的私有指定标签,即IICn寄存器基址。

代码 42 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);

 

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、    pGenerateReadStart==NULL,则使用轮询方式接收;pGenerateWriteStart==NULL,则使用轮询方式发送。

4、    系统初始化未完成,多事件调度尚未启动期间。

如果使用中断方式收发,且不考虑在2~4三种情况下收发数据,则无须实现本函数,WriteReadPoll指针设为NULL即可。

 

回调函数说明如下:

typedef bool_t (*WriteReadPoll)(ptu32_t  SpecificFlag,u8 DevAddr,u32 MemAddr, u8 MenAddrLen,u8* Buf, u32 Length,u8 WrRdFlag);

参数:

    SpecificFlagIIC控制器寄存器基址。

    DevAddr:设备地址,低七位有效。

    MemAddr:设备内部地址,若为存储设备,则为存储地址。

    MemAddrLen:设备内部地址字节数。

    Buf:数据缓冲区。

    LengthBuf中数据字节数。

    WrRdFlag:读写标志,0为写,1为读。

返回:true, 执行成功;false,执行失败。

说明:轮询函数在执行前必须关闭中断,否则将执行失败。轮询函数示例代码如代码 43所示。

代码 43 轮询函数示例

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;

}

}

 

4.3.2. 启动发送

启动发送函数WriteStartFunc是为中断方式收发服务的,轮询方式不需要,置为NULL即可。

启动发送的回调函数WriteStartFunc成了发送数据时IIC的启动时序,其执行的流程为start---->发送器件地址---->发送内部地址,并开启中断,然后返回。对应在时序上,如 4‑2所示。

42 启动发送

如图中所示,写时序是以主控制器发送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时序上面读数据时的总线控制,读时序的时序控制过程如 4‑3所示。该函数依次实现了写start---->器件地址(写)---->写存储地址---->start(或者restart---->器件地址(读)的时序过程。在启动接收时序正确完成后,需使能中断(若不使用中断,则需配置接收到数据pop的事件),并配置回复ACK,在中断中接收从器件发送的数据。

43 启动接收

如图所示的时序图中,有两个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所示。

44 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信号;

第五,   若接收到所有数据,则需产生停止时序和释放信号量。

下面以p1020IIC控制器连接铁电为例,简要讲解一下中断服务函数中的流程。

在中断服务函数内部,通过寄存器判断是发送中断还是接收中断,如 45所示。发送中断时,需要判断是否收到IIC从器件ACK信号,然后读简易缓冲区中的数据,并发送之;若缓冲区中为空,判断发送的总量count是否为零,若是,则表示该帧数据已经全部发送完毕,需产生停止时序,释放信号量IntParam->pDrvPostSemp,该信号量是__IIC_GenerateWriteStart的参数。

在接收中断中,需要判断是否为倒数第二个接收的字节,若是,需要配置控制寄存器不发送ACK信号,使控制器接收到倒数第二个字节时不发送ACK信号,用于通知从设备接收的数据足够。接收到数据后调用IIC_PortWrite,该函数将接收到的数据保存到用户缓冲区。若接收到最后一个字节数据,则产生停止时序,并释放信号量IntParam->pDrvPostSemp,本次接收完成。

45 中断方式接收发送流程图

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_sIIC_DevAdd函数把器件添加到总线上即可。需配置的参数,都在iicbus.h文件中定义的struct tagIIC_Device中描述。struct tagIIC_Device结构定义如下:

代码 51 IIC器件结构体

//IIC总线器件结构体

struct tagIIC_Device

{

    struct tagRscNode DevNode;

    u8 DevAddr;                 //七位的器件地址

    u8 BitOfMemAddrInDevAddr;//器件地址中内部地址所占比特位数

    u8 BitOfMemAddr//器件内部地址寻址总bit数,包含了BitOfMemAddrInDevAddr

};

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

l  器件地址DevAddr是七个比特地址,如0x50,在总线上体现的地址为0x80/0x81

l  BitOfMemAddrInDevAddr是指dev_addr的低三个比特中,有多少个bit用于器件内部存储空间寻址,取值范围:0~3

l  BitOfMemAddr表示被操作的器件内部地址的总位数,包含器件地址上的比特位;

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

5.1. 初始化过程

添加器件到总线的过程就是将器件结点挂到相应的“IICn”总线结点的过程,同时,配置好相应的总线通信参数。

1、  定义static struct tagIIC_Device类型的静态变量;

2、  初始化该变量的各成员;

3、  调用IIC_DevAdd_sIIC_DevAdd添加设备到总线结点。

4、  调用IIC_BusCtrl设置总线参数

下面用FreeScale公司的CRTOUCH触摸芯片为例说明添加设备过程。如代码 5‑2所示,将CRTOUCH芯片添加到总线“IIC0”,并命名为“IIC_Dev_CRTCH”,并配置了总线速度和采用轮询通信方式。

代码 52 添加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;

 

    //添加CRTCHIIC0总线

    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;

}

 

6. 访问器件

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

 

powered by GitbookFile Modify: 2023-12-25 10:02:13

results matching ""

    No results matching ""