前阵子,网上看到有人提问CC2541的I2C是否支持AT24CXX系列的存储器。正好项目需要,所以买了两块AT24C256进行 测试了下。毫无疑问,当然是可以支持的。首先,需要强调的是CC2541具有I2C外设,而CC2540不具备,这也是它们的区别之一。所以,CC2451可以完美驱动带I2C的设备。
如果CC2541的程序是跑OSAL架构的,那么可以可以将它提供的i2c的源码直接拿来用,而不需做任何修改。下面就是基于OSAL架构的来讲的。
支持i2c的代码可以在BLE协议栈安装目录下的BLE-CC254x-1.3.2\Components\hal\target\CC2541ST目录下找到。在这个目录下,找到hal_i2c.c、hal_i2c.h、hal_sensor.c和hal_sensor.h这4个文件,拷贝到你的工程目录下,然后天添加到工程中。其中hal_i2c.c与hal_i2c.h两个文件支持了CC2541的i2c的基本实现函数,hal_sensor.c与hal_sensor.h这两个文件则是为具体的i2c设备提供了读写接口。下面就来仔细讲讲。
1、hal_i2c.c与hal_i2c.h的代码实现
要想使用i2c,当然要对i2c协议有一定的了解。如下图说所示,可以总结出来i2c必须要的几部分是:起始信号、设备地址、读写选择位,应答信号,以及停止位。
在使用i2c之前,当然需要先配置下i2c。在hal_i2c.c下,有 HalI2CInit()函数,用来配置CC2541的i2c,代码如下:
static uint8 i2cAddr;
void HalI2CInit(uint8 address, i2cClock_t clockRate)
{
i2cAddr = address << 1;
I2C_WRAPPER_DISABLE();
I2CADDR = 0; // no multi master support at this time
I2C_CLOCK_RATE(clockRate);
I2C_ENABLE();
}上面代码中看起来像函数的语句其实是宏定义,用来配置相关的寄存器,他们在hal_i2c.c的最开始就宏定义过,这里就不细讲,自己看代码。这里定义了一个i2cAddr变量,用于保存i2c设备的地址。I2C_WRAPPER_DISABLE()关闭写保护,这样的话就可以支持读写操作了,I2C_CLOCK_RATE()则设置i2c的时钟速率,最后I2C_ENABLE()使能i2c。这样的话,i2c就初始化完成了。
i2c的起始信号和停止信号以及读写数据都是通过操作寄存器操作的。hal_i2c.c中给出了他们的宏定义:
#define I2C_STRT() st ( \
起始信号的产生需要操作I2CCFG寄存器下的STA位来产生起始信号;停止信号通过操作I2CCFG下的STO位来产生停止信号;而读写操作只要读取I2CDATA数据和向I2CDATA写入数据就可以实现了。
I2CCFG &= ~I2C_SI; \
I2CCFG |= I2C_STA; \
while ((I2CCFG & I2C_SI) == 0); \
I2CCFG &= ~I2C_STA; \
)
// Must set STO before clearing SI.
#define I2C_STOP() st ( \
I2CCFG |= I2C_STO; \
I2CCFG &= ~I2C_SI; \
while ((I2CCFG & I2C_STO) != 0); \
)
// Stop clock-stretching and then read when it arrives.
#define I2C_READ(_X_) st ( \
I2CCFG &= ~I2C_SI; \
while ((I2CCFG & I2C_SI) == 0); \
(_X_) = I2CDATA; \
)
// First write new data and then stop clock-stretching.
#define I2C_WRITE(_X_) st ( \
I2CDATA = (_X_); \
I2CCFG &= ~I2C_SI; \
while ((I2CCFG & I2C_SI) == 0); \
)hal_i2c.c中还给出了发送起始信号与设备地址以及控制读写的函数:i2cMstStrt(),如下:
static uint8 i2cMstStrt(uint8 RD_WRn)
它有一个参数:RD_WRn,用来决定数据的读写,RD_WRn=1表示从i2c总线读取数据,RD_WRn=0则从i2c总线上写数据。
{
I2C_STRT();
if (I2CSTAT == mstStarted) /* A start condition has been transmitted */
{
I2C_WRITE(i2cAddr | RD_WRn);
}
return I2CSTAT;
}为了从i2c总线上读取数据,hal_i2c.c中给出了HalI2CRead()函数,代码如下:
uint8 HalI2CRead(uint8 len, uint8 *pBuf)
这个函数带两个参数:len和pbuf。len是要读取的数据长度,而pbuf则用于保存在读取到的数据。先要调用i2cMstStrt(I2C_MST_RD_BIT)函数,发送起始信号以及设备地址,控制读写位为读。然后在while(1)中,调用I2C_READ()读取指定数量的字节。最后再发送一个停止信号。
{
uint8 cnt = 0;
if (i2cMstStrt(I2C_MST_RD_BIT) != mstAddrAckR)
{
len = 0;
}
if (len > 1)
{
I2C_SET_ACK();
}
while (len > 0)
{
// slave devices require NACK to be sent after reading last byte
if (len == 1)
{
I2C_SET_NACK();
}
// read a byte from the I2C interface
I2C_READ(*pBuf++);
cnt++;
len--;
if (I2CSTAT != mstDataAckR)
{
if (I2CSTAT != mstDataNackR)
{
// something went wrong, so don't count last byte
cnt--;
}
break;
}
}
I2C_STOP();
return cnt;
}为了向i2c总线上写数据,hal_i2c.c给出了HalI2CWrite()函数,代码如下:
uint8 HalI2CWrite(uint8 len, uint8 *pBuf)
这个函数带两个参数:lne和pBuf。len是要写的数据长度,pBuf则是要写的数据指针。跟HalI2CRead()函数差不多,先调用i2cMstStrt(I2C_MST_RD_BIT)函数发送起始信号以设备地址,选择读写位为写操作。然后在while()中调用I2C_WRITE()写入指定长度的数据。最后在发送一位停止信号。
{
if (i2cMstStrt(0) != mstAddrAckW)
{
len = 0;
}
for (uint8 cnt = 0; cnt < len; cnt++)
{
I2C_WRITE(*pBuf++);
if (I2CSTAT != mstDataAckW)
{
if (I2CSTAT == mstDataNackW)
{
len = cnt + 1;
}
else
{
len = cnt;
}
break;
}
}
I2C_STOP();
return len;
}
2、hal_sensor.c与hal_sensor.h文件
如果对于AT24CXX系列中的AT2401、AT2402、AT2404、AT2408、AT2416这几种存储器,hal_sensor.c和hal_sensor.h可以不做任何修改就可以完全支持。但是如果对于大于16K的AT24CXX些列存储器则需要对这两个文件进行修改。为什么会存在这样的差别呢?原因在于他们的片内地址的范围。对于1K位,2K位,4K位,8K位,16K位芯片采用一个8位长的字节地址码,对于32K位以上的采用2个8位长的字节地址码直接寻址,而4K位,8K位,16K位配合页面地址来寻址的。hal_sensor给出的代码用的就是支持8为地址的。所以对于16k以上的存储器需要将hal_sensor的代码修改成支持2个8位地址。下面就分成上面两种情况进行说明。
对于AT2401、AT2402、AT2404、AT2408、AT2416这几种存储器,hal_sensor的代码可以不做任何修改,但是可以将除了HalSensorReadReg()和HalSensorWriteReg()这两个函数之外的其他函数全部去掉,因为都是不相关的代码。HalSensorReadReg()与HalSensorWriteReg()分别用向AT24CXX存储器指定的地址上读写指定的数数据。代码如下:
bool HalSensorReadReg(uint8 addr, uint8 *pBuf, uint8 nBytes)
{
uint8 i = 0;
/* Send address we're reading from */
if (HalI2CWrite(1,&addr) == 1)
{
/* Now read data */
i = HalI2CRead(nBytes,pBuf);
}
return i == nBytes;
}
bool HalSensorWriteReg(uint8 addr, uint8 *pBuf, uint8 nBytes)
可以发现,这两个函数的参数中的addr的类型是uint8,对应的1个字节的地址。它们的读写过程如下:
{
uint8 i;
uint8 *p = buffer;
/* Copy address and data to local buffer for burst write */
*p++ = addr;
for (i = 0; i < nBytes; i++)
{
*p++ = *pBuf++;
}
nBytes++;
/* Send address and data */
i = HalI2CWrite(nBytes, buffer);
if ( i!= nBytes)
HAL_TOGGLE_LED2();
return (i == nBytes);
}
对于AT2432、AT2464、AT24128、AT24256这几种存在器的片内地址需要2个8为字节的数据需要将上面给出的HalSensorReadReg()与HalSensorWriteReg()修改成支持16为地址,代码如下:
bool HalSensorReadReg(uint16 addr, uint8 *pBuf, uint8 nBytes)
{
uint8 i = 0;
uint8 addrH, addrL;
addrL = addr & 0xff;
addrH = addr >> 8;
/* Send address we're reading from */
if (HalI2CWrite(1, &addrH) == 1)
if (HalI2CWrite(1, &addrL) == 1)
{
/* Now read data */
i = HalI2CRead(nBytes,pBuf);
}
return i == nBytes;
}
bool HalSensorWriteReg(uint16 addr, uint8 *pBuf, uint8 nBytes)
需要注意的是,上面的这两函数的参数addr的数据类型改成了uint16,所以需要在这两个函数的代码中将这16位的地址拆成高8为地址与低8为地址,分别定义addrH和addrL保存。在发送2个8位地址时,高8位地址在前,低8为地址在后。它们的读写过程如下:
{
uint8 i;
uint8 *p = buffer;
uint8 addrL = 0, addrH = 0;
addrL = (uint8)(addr & 0xff);
addrH = (uint8)(addr >> 8);
/* Copy address and data to local buffer for burst write */
*p++ = addrH;
*p++ = addrL;
for (i = 0; i < nBytes; i++)
{
*p++ = *pBuf++;
}
nBytes += 2;
/* Send address and data */
i = HalI2CWrite(nBytes, buffer);
if ( i!= nBytes)
HAL_TOGGLE_LED2();
return (i == nBytes);
}建议再在hal_sensor.c中编写一个halSensorInit()函数,调用hal_i2c.c中的HalI2CInit(),配置CC2541的i2c,代码如下:
void halSensorInit(void)
其中0x50指得是i2c设备的地址。对于AT24CXX系列对于AT24C01~AT24C16来说它们的设备地址如下:
{
HalI2CInit(0x50, i2cClock_533KHZ);
}对于AT24C32及以上的存储器来说,它的设备地址如下:
可以发现,不同AT24CXX的地址需要根据上面的bit1~bit3决定,bit4~bit7都固定为0b1010。bit1~bit3跟存储器的硬件接线相关,对于我买的AT24C256模块,A0与A1引脚全部接地,则它的地址为:0b1010000,即0x50。
3、AT24CXX的读写
在应用代码中,调用上面给出的halSensorInit()函数初始化CC2541的i2c外设,然后调用HalSensorWriteReg()指定地址写入数据,然后再调用HalSensorReadReg()函数将该地址数据的上的值读取出来,实例代码如下:
static uint8 buffer[32] = {0};
halSensorinit();//初始化CC2541的i2c
halWriteReg(0x0000, "abcdefghij", 10);//向AT24C256的0x0000地址处写入数据
halReadReg(0x0000, buffer, 10);//从AT24C256的0x0000地址处读取10个字节数据
HalLcdWriteString(buffer, HAL_LCD_LINE_4);//将读取到的数据显示在LCD上
转载自 http://ziye334.lofter.com/post/2435a3_2a2e1b4