很多车载GPS,车载MP3等上都装有一个FM发射装备。目前比较流行的FM发射芯片有昆天科的QN8006,昆腾微的KT0801A等。本文介绍采用KT0801A在WINCE上的驱动的移植。
KT0801A的使用很简单,在正常上电复位后,只用设置发射频率,增益,以及PA即可。不过设备的前提就是I2C必须通信上。
三星的S3C6410在WINCE平台上做好了I2C底层通信驱动,音频接口就是调用的该驱动实现的I2C通信。不过读设备不正常,而且只支持标准I2C设备。下面以两种I2C的通信方法介绍针对KT0801A的使用。
方法一:调用I2C驱动接口
首先需要打开I2C驱动。代码如下:
DWORD FMR_I2C_Init()
{
RETAILMSG(ZONE_DEBUG,(TEXT("FMR_I2C_Init-------------------/r/n")));
DWORD dwErr = ERROR_SUCCESS, bytes;
BOOL bRet;
UINT32 uiIICDelay;
// 打开I2C驱动
hIIC = CreateFile(L"IIC0:",
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, 0);
if ( hIIC == INVALID_HANDLE_VALUE )
{
dwErr = GetLastError();
RETAILMSG(ZONE_ERROR, (TEXT("[FMR] Error %d opening device '%s' /r/n"), dwErr, L"IIC0:" ));
}
// 设置I2C延时
uiIICDelay = Clk_0;
bRet = DeviceIoControl(hIIC,
IOCTL_IIC_SET_DELAY,
&uiIICDelay, sizeof(UINT32),
NULL, 0,
&bytes, NULL);
if (bRet == FALSE)
{
RETAILMSG(ZONE_ERROR, (TEXT("[FMR] I2C0: Device Set Delay Failed./n")));
dwErr = GetLastError();
}
return dwErr;
}
写寄存器函数如下:
DWORD FMR_WriteRegisters(PUCHAR pBuff, // 寄存器值
UCHAR StartReg, // 寄存器起始地址
DWORD nRegs // 寄存器数目
)
{
DWORD dwErr=0,bytes;
UCHAR buff[2];
IIC_IO_DESC IIC_Data;
buff[0] = StartReg;
buff[1] = pBuff[0];
IIC_Data.SlaveAddress = KT0801A_WRITE;
IIC_Data.Data = buff;
IIC_Data.Count = 2;
RETAILMSG(1,(_T("[FMR] WriteRegisters address:0x%x data:0x%x/n"),buff[0],buff[1]));
// use iocontrol to write
if ( !DeviceIoControl(hIIC,
IOCTL_IIC_WRITE,
&IIC_Data, sizeof(IIC_IO_DESC),
NULL, 0,
&bytes, NULL) )
{
dwErr = GetLastError();
}
if ( dwErr )
{
RETAILMSG(1,(_T("[FMR] I2CWrite ERROR: %u /r/n"), dwErr));
}
return dwErr;
}
读寄存器函数如下:
DWORD FMR_ReadRegisters(PUCHAR pBuff, // 寄存器值
UCHAR StartReg, // 寄存器起始地址
DWORD nRegs // 寄存器数目
)
{
DWORD dwErr=0;
DWORD bytes;
IIC_IO_DESC IIC_AddressData, IIC_Data;
// static UCHAR pBuff[1];// added by lqm.
IIC_AddressData.SlaveAddress = KT0801A_READ;
IIC_AddressData.Data = &StartReg;
IIC_AddressData.Count = 1;
IIC_Data.SlaveAddress = KT0801A_READ;
IIC_Data.Data = pBuff;
IIC_Data.Count = 1;
RETAILMSG(1,(_T("[FMR] HW_ReadRegisters()/r/n")));
// use iocontrol to read
if ( !DeviceIoControl(hIIC,
IOCTL_IIC_READ,
&IIC_AddressData, sizeof(IIC_IO_DESC),
&IIC_Data, sizeof(IIC_IO_DESC),
&bytes, NULL) )
{
dwErr = GetLastError();
}
if ( dwErr )
{
RETAILMSG(1,(_T("[FMR] I2CRead ERROR: %u /r/n"), dwErr));
}
return dwErr;
}
至此,就可以通过上面的读写寄存器函数操作从设备的寄存器了。但是通过上面的方法不能正常读。为此,这里采用第二种方法。
方法二:模拟I2C接口
由于和默认I2C接口共用一组IO口,如果直接改为模拟IO口,那么其他使用该接口的驱动就无法正常使用了。所以在使用之前需备份它之前的属性。这里给出其读写函数。
读函数如下:
int S3C6410_GPIORdI2C(unsigned char RegAddr, unsigned char *lpData)
{
EnterCriticalSection(&gpio_Lock);
unsigned long savereg[3];
if(GPIO_I2C_Init())
{
unsigned char READ_DATA;
//保存初始状态
savereg[0] = s6410IOP->GPBCON &((0xf<<20)|(0xf<<24));
savereg[2] = s6410IOP->GPBPUD &((3<<10)|(3<<12));
CFG_WRITE(SIO_C);
CFG_WRITE(SIO_D);
mdelay(10);
READ_DATA = GPIO_I2C_receivebyte(RegAddr );
*lpData=READ_DATA;
RETAILMSG(0,(TEXT("[IIC_GPIO] read RegAddr[0x%x]= 0x%x/r/n"),RegAddr,*lpData));
//还原原来的GPIO配置
s6410IOP->GPBCON=(s6410IOP->GPBCON &~((0xf<<20)|(0xf<<24)));
s6410IOP->GPBCON|=savereg[0];
s6410IOP->GPBPUD =(s6410IOP->GPBPUD &~((3<<10)|(3<<12))) ;
s6410IOP->GPBPUD|=savereg[2] ;
LeaveCriticalSection(&gpio_Lock);
return TRUE;
}
LeaveCriticalSection(&gpio_Lock);
return FALSE;
}
写函数如下:
int S3C6410_GIPOWrI2C(unsigned char RegAddr, unsigned char Data)
{
EnterCriticalSection(&gpio_Lock);
unsigned long savereg[3];
if(GPIO_I2C_Init())
{
//保存初始状态
savereg[0] = s6410IOP->GPBCON &((0xf<<20)|(0xf<<24));
savereg[2] = s6410IOP->GPBPUD &((3<<10)|(3<<12));
CFG_WRITE(SIO_C);
CFG_WRITE(SIO_D);
GPIO_I2C_sendbyte(RegAddr , Data );
RETAILMSG(0,(TEXT("write addr RegAddr[0x%x]= *lpData[0x%x]/r/n"),RegAddr,Data));
//还原原来的GPIO配置
s6410IOP->GPBCON=(s6410IOP->GPBCON &~((0xf<<20)|(0xf<<24)));
s6410IOP->GPBCON|=savereg[0];
s6410IOP->GPBPUD =(s6410IOP->GPBPUD &~((3<<10)|(3<<12))) ;
s6410IOP->GPBPUD|=savereg[2] ;
LeaveCriticalSection(&gpio_Lock);
return TRUE;
}
LeaveCriticalSection(&gpio_Lock);
return FALSE;
}
从上面的程序可以看出,在更改寄存器之前,都是先保存其寄存器的属性,使用完马上还原。这样不会影响到像音频驱动这些使用该组I2C总线的器件。而且针对不同协议的I2C接口,可以灵活的变动。
KT0801A的上电函数如下:
BOOL FMR_KT0801_Power_Set(BOOL Poweron)
{
RETAILMSG(ZONE_DEBUG,(TEXT("<FMR> FMR_KT0801_Power_Set--%d!!/r/n"),Poweron));
//FMEN----GPK14 FMRST----GPK15
S3C6410IOP->GPKCON1 =(S3C6410IOP->GPKCON1 & ~(0xff<<24)) | (0x11<<24);
S3C6410IOP->GPKPUD = S3C6410IOP->GPKPUD & ~(0xf<<28) ;
if(Poweron)
{
S3C6410IOP->GPKDAT |= (1<<14); //上电
S3C6410IOP->GPKDAT &= ~(1<<15); //复位
Sleep(200);
S3C6410IOP->GPKDAT |= (1<<15); //复位清除
Sleep(150);
}
else
{
S3C6410IOP->GPKDAT &= ~(1<<14); //power down
}
return TRUE;
}
该函数比较重要的就是复位的时间了。上面的两个Sleep()相当重要,处理不当,芯片将无法复位,那么以后再正确的操作也不会使它正常工作起来。
频率设置函数如下:
BOOL FMR_KT0801_Frequent_Set(PBYTE pBufOut)
{
FMR_Freq_Buffer *pArg = (FMR_Freq_Buffer *)pBufOut;
if((pArg->dwFreqBuffer>=OMITFREQUENT_MIN) & (pArg->dwFreqBuffer<=OMITFREQUENT_MAX))
{
RETAILMSG(1, (TEXT("<FMR> Set KT0801_Frequent:%d!/r/n"),pArg->dwFreqBuffer));
S3C6410_GIPOWrI2C(0x00,(unsigned char)(pArg->dwFreqBuffer & 0xff));
S3C6410_GIPOWrI2C(0x01,(unsigned char)((pArg->dwFreqBuffer>>8) & 0x7 | 0xC0));// PGA Gain:0dB
S3C6410_GIPOWrI2C(0x02,0x40);//清掉CHSEL[0],使用step=100K.RFGAIN:112.5dBuV
S3C6410_GIPOWrI2C(0x13,0x84);
S3C6410_GIPOWrI2C(0x0B,0x00);//打开PA
// 通知音频驱动,FM已经打开
SetEventData(g_hEventFMOpen,1);
SetEvent(g_hEventFMOpen);
}
else
{
RETAILMSG(1, (TEXT("<FMR> Not support Frequent:%d!/r/n"),pArg->dwFreqBuffer));
S3C6410_GIPOWrI2C(0x0B,0x20);//关闭PA
FMR_KT0801_Power_Set(FALSE);
// 通知音频驱动,FM已经关闭
SetEventData(g_hEventFMOpen,0);
SetEvent(g_hEventFMOpen);
FMR_KT0801_Deinit();
return TRUE;
}
return TRUE;
}
上面的函数用于KT0801A的寄存器操作,设置发射频率,PA等。在后面通过SetEventData函数通知音频驱动,现在正在使用FM发射,音频驱动将音频通道切换至FM通道。所有这些函数都通过IOCTL控制,实现频率的设置,模块的睡眠模式,工作模式,打开,关闭,PA设置等。