CC2541对AT24CXX系列存储器的支持



    前阵子,网上看到有人提问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 &= ~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); \
)

起始信号的产生需要操作I2CCFG寄存器下的STA位来产生起始信号;停止信号通过操作I2CCFG下的STO位来产生停止信号;而读写操作只要读取I2CDATA数据和向I2CDATA写入数据就可以实现了。     

 hal_i2c.c中还给出了发送起始信号与设备地址以及控制读写的函数:i2cMstStrt(),如下:

static uint8 i2cMstStrt(uint8 RD_WRn)
{
 I2C_STRT();

 if (I2CSTAT == mstStarted) /* A start condition has been transmitted */
 {
 I2C_WRITE(i2cAddr | RD_WRn);
 }

 return I2CSTAT;
}

它有一个参数:RD_WRn,用来决定数据的读写,RD_WRn=1表示从i2c总线读取数据,RD_WRn=0则从i2c总线上写数据。     

 为了从i2c总线上读取数据,hal_i2c.c中给出了HalI2CRead()函数,代码如下:

uint8 HalI2CRead(uint8 len, uint8 *pBuf)
{
 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;
}

这个函数带两个参数:len和pbuf。len是要读取的数据长度,而pbuf则用于保存在读取到的数据。先要调用i2cMstStrt(I2C_MST_RD_BIT)函数,发送起始信号以及设备地址,控制读写位为读。然后在while(1)中,调用I2C_READ()读取指定数量的字节。最后再发送一个停止信号。     

 为了向i2c总线上写数据,hal_i2c.c给出了HalI2CWrite()函数,代码如下:

uint8 HalI2CWrite(uint8 len, uint8 *pBuf)
{
 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;
}

这个函数带两个参数:lne和pBuf。len是要写的数据长度,pBuf则是要写的数据指针。跟HalI2CRead()函数差不多,先调用i2cMstStrt(I2C_MST_RD_BIT)函数发送起始信号以设备地址,选择读写位为写操作。然后在while()中调用I2C_WRITE()写入指定长度的数据。最后在发送一位停止信号。     


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)
{
 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);
}

可以发现,这两个函数的参数中的addr的类型是uint8,对应的1个字节的地址。它们的读写过程如下:     

 对于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)
{
 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);
}

需要注意的是,上面的这两函数的参数addr的数据类型改成了uint16,所以需要在这两个函数的代码中将这16位的地址拆成高8为地址与低8为地址,分别定义addrH和addrL保存。在发送2个8位地址时,高8位地址在前,低8为地址在后。它们的读写过程如下:     

  建议再在hal_sensor.c中编写一个halSensorInit()函数,调用hal_i2c.c中的HalI2CInit(),配置CC2541的i2c,代码如下:     

void halSensorInit(void)
{
 HalI2CInit(0x50, i2cClock_533KHZ); 
}

其中0x50指得是i2c设备的地址。对于AT24CXX系列对于AT24C01~AT24C16来说它们的设备地址如下:     

 对于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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值