DJYOS的DjyBus总线模型为IIC、SPI之类的器件提供统一的访问接口,IICBUS模块是DjyBus模块的一个子模块,为IIC器件提供统一的编程接口,实现通信协议层与器件层的分离。也标准化了IIC总线和 Device驱动接口,本手册指导驱动工程师编写IIC的接口程序。
IIC总线使用手册,请参见《都江堰操作系统用户手册》。
IIC通信协议是一种总线通信方式,这意味着一条总线上可以挂多个符合总线通信协议的设备,DjyBus资源组织结构就是符合这样一种物理的连接方式。在如图 21资源组织结构图,总线类型 “IIC”、第n条总线“IICn”、第n条总线上面的设备“Devn”,它们都是DjyBus资源树上面的资源结点,每次向总线“IICn”上面增加一个设备,便向资源树上面增加了一个资源结点,它是“IICn”的子结点。
图 2-1 总线资源结构
在编写IIC 器件驱动前,建议完成必要的准备工作,如:
1、认真阅读器件手册,了解通信协议、参数、操作流程等内容;
2、熟悉iicbus.h头文件中提供的API,懂得参数的使用方法;
3、阅读IIC总线协议文档,熟练掌握IIC总线。
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总线驱动架构
1、IIC控制器硬件的初始化,包括传输速度、IO配置等;
2、挂载IIC中断到中断系统,并配置中断类型,如配置为异步信号(若只采用轮询方式,则此功能可省略);
添加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; //控制函数
};
根据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);
有多少IIC总线是由具体的平台决定,因此,增加IIC总线到DjyBus上是由总线驱动程序员完成,成功添加的“IICn”结点会成为“IIC”结点的子结点。
增加IIC总线的API函数可以调用IIC_BusAdd函数或IIC_BusAdd_s函数,两者的区别在于,IIC_BusAdd只需调用者提供已初始化好的参数结构体struct tagIIC_Param,而后者更需要提供struct tagIIC_CB结构体控制块,该控制块为静态或全局变量(建议定义为本C文件内部静态变量)。
如果采用轮询方式收发,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;
}
}
启动发送函数(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,发生错误
启动发送函数(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失败
结束传输的回调函数__IIC_GenerateEnd主要用于停止当前正在进行的传输,特别是在发生超时传输时,用于停止本次发送或接收,实际上,该函数调用了产生停止时序的函数,使IIC主器件停止本帧数据的传输。启动和停止时序如图 44所示。
图 4-4 IIC启动和停止时序
目前,控制IIC的底层驱动只需要实现对IIC总线传输时钟的控制即可,相对较为简单,此处不作详细说明,请参看源码cpu_peri_iic.c。
如果使用轮询方式实现驱动,则无须编写中断服务函数。
相比轮询通信方式,中断方式的执行效率更高,对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 中断方式接收发送流程图
使用中断方式实现IIC主设备与从设备通信,需要注意以下几点:
1、发送中断不仅要判断中断标志位,清标志位,同时还需判断是否接收到ACK信号;
2、正常的发送结束时,IIC_PortRead读到数据为0,计数值IntParam->TransCount 与 IntParam->TransTotalLen应该相等,若不等,则可能出现逻辑错误;
3、读数据的倒数第二个字节时,需停止时序,因为,此时数据已经发送到总线上面;
4、通信结束后,需释放信号量和停止时序。
由于大部分的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)。
建议将器件驱动的存放目录为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
};
对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的寻址空间。
添加器件到总线的过程就是将器件结点挂到相应的“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;
}
器件装载到IIC总线之后,可以通过访问IIC总线实现访问器件,具体就是调用iicbus.h提供的API函数IIC_Write()和IIC_Read()。