S3C2440A RISC微处理器可以支持多主设备IIC总线串行接口。专用串行总线(SDA)和串行时钟线(SCL)承载总线主机设备和连接IIC总线的外围设备之间的信息。SDA和SCL线都是双向的。本章采用TQ2440开发板进行分析,我们先来看看其硬件电路图;
从这里可以看的出 TQ2440 采用的是AT24C02A IIC器件,其中I2CSCL和I2CSDA分别表示时钟线和数据线。接下来看看IIC寄存器的相关结构体;
typedef struct _I2C_CONTEXT {
DWORD Sig; // Signature
volatile S3C2440A_IICBUS_REG *pI2CReg; // I2C Registers
volatile S3C2440A_IOPORT_REG *pIOPReg; // GPIO Ports
volatile S3C2440A_CLKPWR_REG *pCLKPWRReg; // Clock / Power
CRITICAL_SECTION RegCS; // Register CS
I2C_MODE Mode; // State
I2C_STATE State;
int Status;
FLAGS Flags;
// Data
PUCHAR Data; // pointer to R/W data buffer
int DataCount; // nBytes to R/W to/from data buffer
UCHAR WordAddr; // slave word address
UCHAR RxRetAddr; // returned slave address on Rx
DWORD SlaveAddress; // Our I2C Slave Address
HANDLE DoneEvent; // I/O Done Event
HANDLE ISTEvent; // IST Event
HANDLE IST; // IST Thread
DWORD OpenCount;
DWORD LastError;
HANDLE hProc;
CEDEVICE_POWER_STATE Dx;
} I2C_CONTEXT, *PI2C_CONTEXT;
在这个结构体中pI2CReg、pIOPReg和pCLKPWRReg都扮演着非常重要的角色,如下来看看如何实现这上个寄存器初始化工作,后面将介绍IIC驱动的几个关键部分代码;
bMapReturn = VirtualCopy( pVMem,(LPVOID)(S3C2440A_BASE_REG_PA_IICBUS>>8), PAGE_SIZE,PAGE_READWRITE | PAGE_NOCACHE |PAGE_PHYSICAL);
pI2C->pI2CReg = (volatile S3C2440A_IICBUS_REG*)(pVMem);
pVMem += PAGE_SIZE;
VirtualCopy(pVMem,(LPVOID)(S3C2440A_BASE_REG_PA_IOPORT>>8),
PAGE_SIZE,PAGE_READWRITE | PAGE_NOCACHE |PAGE_PHYSICAL);
pI2C->pIOPReg = (volatile S3C2440A_IOPORT_REG*)(pVMem);
pVMem += PAGE_SIZE;
VirtualCopy(pVMem,(LPVOID)(S3C2440A_BASE_REG_PA_CLOCK_POWER>>8),
PAGE_SIZE,PAGE_READWRITE | PAGE_NOCACHE |PAGE_PHYSICAL);
pI2C->pCLKPWRReg = (volatile S3C2440A_CLKPWR_REG*)(pVMem);
中断模式处理
在Windows CE当中中断处理的常用的几个函数有KernelIoControl、InterruptInitialize、InterruptDisable和InterruptDone函数,如下所示是其处理过程。
Irq = IRQ_IIC;
KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &Irq, sizeof(UINT32), &gIntrIIC, sizeof(UINT32), NULL))
// initialize the interrupt
InterruptInitialize(gIntrIIC, pI2C->ISTEvent, NULL, 0) ) ;
InterruptDone(gIntrIIC);
// create the IST
pI2C->IST = CreateThread(NULL, 0, I2C_IST, (LPVOID)pI2C, 0, NULL));
CeSetThreadPriority(pI2C->IST, I2C_THREAD_PRIORITY));
IST函数
PI2C_CONTEXT pI2C = (PI2C_CONTEXT)Context;
DWORD i2cSt;
BOOL bDone = FALSE;
do {
if (pI2C->Mode == INTERRUPT) {
DWORD we;
bDone = FALSE;
we = WaitForSingleObject(pI2C->ISTEvent, INFINITE);
// clear the interrupt here because we re-arm another below
InterruptDone(gIntrIIC);
switch(pI2C->State)
{
case OFF:
DEBUGMSG(ZONE_IST|ZONE_TRACE,(TEXT("I2C_IST: ExitThread /r/n")));
ExitThread(ERROR_SUCCESS);
break;
case IDLE:
DEBUGMSG(ZONE_IST|ZONE_TRACE,(TEXT("I2C_IST: IDLE /r/n")));
continue;
break;
default:
if (pI2C->State != WRITE_ACK &&
pI2C->State != RESUME &&
pI2C->DataCount == INVALID_DATA_COUNT) {
continue;
}
break;
}
}
// EnterCriticalSection(&pI2C->RegCS);
__try {
switch(pI2C->State)
{
case IDLE:
case SUSPEND:
continue;
break;
case RESUME:
InitRegs(pI2C);
pI2C->LastError = ERROR_OPERATION_ABORTED;
SetEvent(pI2C->DoneEvent);
break;
case SET_READ_ADDR:
if ( (pI2C->DataCount--) == 0 )
{
bDone = TRUE;
break;
}
// write word address
// For setup time of SDA before SCL rising edge, rIICDS must be written
// before clearing the interrupt pending bit.
if (pI2C->Flags.WordAddr) {
rIICDS = pI2C->WordAddr;
// clear interrupt pending bit (resume)
rIICCON = RESUME_IIC_CON;
pI2C->Flags.WordAddr = FALSE;
}
break;
case READ_DATA:
ASSERT(pI2C->Data);
if ( (pI2C->DataCount--) == 0 )
{
bDone = TRUE;
*pI2C->Data = (UCHAR)rIICDS;
pI2C->Data++;
rIICSTAT = MRX_STOP;
rIICCON = RESUME_IIC_CON; // resume operation.
break;
}
// Drop the returned Slave WordAddr?
if ( pI2C->Flags.DropRxAddr )
{
pI2C->RxRetAddr = (UCHAR)rIICDS;
pI2C->Flags.DropRxAddr = FALSE;
} else {
*pI2C->Data = (UCHAR)rIICDS;
pI2C->Data++;
}
// The last data is read with no ack.
if ( pI2C->DataCount == 0 ) {
rIICCON = RESUME_NO_ACK; // resume operation with NOACK.
DEBUGMSG(ZONE_READ|ZONE_TRACE,(TEXT("R1:0x%X /r/n"), r));
} else {
rIICCON = RESUME_IIC_CON; // resume operation with ACK
DEBUGMSG(ZONE_READ|ZONE_TRACE,(TEXT("R2:0x%X /r/n"), r));
}
break;
case WRITE_DATA:
ASSERT(pI2C->Data);
if ( (pI2C->DataCount--) == 0 )
{
bDone = TRUE;
rIICSTAT = MTX_STOP;
rIICCON = RESUME_IIC_CON; // resume operation.
//The pending bit will not be set after issuing stop condition.
break;
}
rIICDS = (UCHAR)*pI2C->Data;
pI2C->Data++;
}
rIICCON = RESUME_IIC_CON; // resume operation.
break;
}
} _except(EXCEPTION_EXECUTE_HANDLER) {
rIICSTAT = (pI2C->State == READ_DATA) ? MRX_STOP : MTX_STOP;
rIICCON = RESUME_IIC_CON;
pI2C->DataCount = INVALID_DATA_COUNT;
pI2C->LastError = GetExceptionCode();
}
if (bDone) {
DEBUGMSG(ZONE_IST, (TEXT("SetEvent DONE/r/n")));
SetEvent(pI2C->DoneEvent);
}
} while (pI2C->Mode == INTERRUPT);
return ERROR_SUCCESS;
}
读取数据操作
驱动采用的是中断方式读取数据,其中数据指针保存在pI2C->pData当中,其中为了保证用户区缓冲和驱动内核区缓冲一致,还必须调用GetCallerProcess()、 MapPtrToProcess()和GetCurrentProcessID()函数,这几个函数的具体用法可以查询MSDN帮助即可,这部分代码如下;
pI2C->State = WRITE_DATA;
pI2C->DataCount = 1 + Count; // slave word address + data
pI2C->WordAddr = WordAddr;
pI2C->Flags.WordAddr = TRUE;
pI2C->Data = pData;
// write slave address
rIICDS = (UCHAR)SlaveAddr;
rIICSTAT = MTX_START;
// IST writes the slave word address & data
if (WAIT_OBJECT_0 != SyncIst(pI2C, TX_TIMEOUT)) {
goto _done;
}
写数据操作
写操作和读操作方法很类似,是其反过程,代码很简单,这里就不多讲了,具体在S3C2440A BSP中可以看到。
这个IIC驱动是一个典型的Windows CE流接口驱动程序,是一个很好的学习范例,特写至此,希望对来客有写帮助。