接下来进入其他两种串行通信方式:SPI和I2C的学习,因为以后的项目中会用到这些通信方式,而且正点原子的开发板里面也有用I2C和SPI通信的传感器来做实例,分别是一个距离传感器和六轴陀螺仪,这样就可以很好的通过实例来学习了。这两个通信方式最大的区别就是速度,I2C的最高通信速度是400KHz,而SPI最高可以到几百MHz,所以在低速应用时I2C即可,到了高速的场合就必须用SPI了。
先学习I2C的应用,这里还是和以前一样,重点放在应用上,然后研究距离传感器的datasheet来尝试编写传感芯片的驱动!
I.MX6UL有4路I2C,正点原子写的I2C驱动包含了一系列的函数,这些函数其实就是对I2C相关的寄存器做一些置1或者0的操作,所以我们可以学习下常用的对寄存器按位逻辑操作的方法:
base->I2CR &= ~(1 << 7); //第7位置0,使用按位与
base->I2CR &= ~((1 << 5) | (1 << 4) | (1 << 3)); //[5:3]位同时清零
base->I2CR |= ( 1 << 7); //第7位置1,按位或
base->I2CR |= (1 << 4) | (1 << 2); //同时把第4位和第1位置1
if(base->I2SR & (1 << 5)) == 1 //即I2SR的第五位是否为1,如果为1则逻辑与的结果是1
if((base->I2CR) & (1 << 5)) == 0) 即I2SR的第五位是否为0
这里不会深入研究正点原子的I2C驱动,只需要知道如何调用函数进行I2C数据的读写即可,把重点放在开发板上的传感器芯片AP3216C上。研究它的datasheet先。
The AP3216C is an integrated ALS & PS module that includes a digital ambient light sensor [ALS], a proximity sensor [PS], and an IR LED in a single package.
所以它包含了3个模块,光传感器ALS
、距离传感器PS
和红外线LEDIR
,最常用在手机和平板上用来检测耳朵是否接触听筒,或者光传感器来检测光照强度调节屏幕亮度。
在datasheet里查到这个芯片的地址是0x1E,再研究datasheet第12页的表格,传感器的设置、数据读取和写入都是从不同的寄存器地址,所以我们先用一些宏来把寄存器地址在头文件中定义好,方便后面调用。宏的名字一定要一眼就看出意思来
#define AP3216C_ADDR 0X1E /* AP3216C器件地址 */
#define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */
#define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器 */
#define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器 */
#define AP3216C_IRDATALOW 0x0A /* IR数据低字节 */
#define AP3216C_IRDATAHIGH 0x0B /* IR数据高字节 */
#define AP3216C_ALSDATALOW 0x0C /* ALS数据低字节 */
#define AP3216C_ALSDATAHIGH 0X0D /* ALS数据高字节 */
#define AP3216C_PSDATALOW 0X0E /* PS数据低字节 */
#define AP3216C_PSDATAHIGH 0X0F /* PS数据高字节 */
先编写读写数据的函数ap3216c_write(...)
,其实就是配置i2c_transfer结构体的各个参数,这个函数的参数是设备地址、要写入的寄存器、写入的数据,返回值可以扩展,可以是0-4,对应未写入(0),I2C14写入(返回值14)
unsigned char ap3216c_write(unsigned char addr, unsigned char reg, unsigned char data)
{
unsigned char status = 0;
unsigned char writedata = data;
struct i2c_transfer masterXfer; /* 定义masterXfer结构体并配置 */
masterXfer.slaveAddress = addr; /* 设备地址 */
masterXfer.direction = kI2C_Write; /* 方向为写入 */
masterXfer.subaddress = reg; /* 要写入的寄存器地址 */
masterXfer.subaddressSize = 1; /* 地址长度一个字节 */
masterXfer.data = &writedata; /* 要写入的数据 */
masterXfer.dataSize = 1; /* 写入数据长度1个字节 */
if(i2c_master_transfer(I2C1, &masterXfer))
status=1;
return status;
}
同样的可以编写读函数ap3216c_read(...)
,参数是设备地址、要读的寄存器、返回值就是读取的数据。
unsigned char ap3216c_read(unsigned char addr,unsigned char reg)
{
unsigned char val=0;
struct i2c_transfer masterXfer;
masterXfer.slaveAddress = addr; /* 设备地址 */
masterXfer.direction = kI2C_Read; /* 读取数据 */
masterXfer.subaddress = reg; /* 要读取的寄存器地址 */
masterXfer.subaddressSize = 1; /* 地址长度一个字节 */
masterXfer.data = &val; /* 接收数据缓冲区 */
masterXfer.dataSize = 1; /* 读取数据长度1个字节 */
i2c_master_transfer(I2C1, &masterXfer);
return val;
}
根据读写的基础函数,我们可以编写读写IR,PS,ALS几个模块数据的函数ap3216c_readdata(...)
void ap3216c_readdata(unsigned short *ir, unsigned short *ps, unsigned short *als)
{
unsigned char buf[6];
unsigned char i;
/* 循环读取所有传感器数据 */
for(i = 0; i < 6; i++)
{
buf[i] = ap3216c_read(AP3216C_ADDR, AP3216C_IRDATALOW + i); /* 从0X0A到0X0F */
}
if(buf[0] & 0X80) /* IR_OF位为1,则数据无效 */
*ir = 0;
else /* 读取IR传感器的数据 */
*ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03); /* 只保留buf[0]即IR低字节,并把高字节左移两位,获取IR完整数据 */
*als = ((unsigned short)buf[3] << 8) | buf[2]; /* 读取ALS传感器的数据 */
if(buf[4] & 0x40) /* IR_OF位为1,则数据无效 */
*ps = 0;
else /* 读取PS传感器的数据 */
*ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}
至此AP3216C这个芯片的驱动就基本完成了。开发板的硬件上是通过I.MX6UL的I2C1通道和传感器连接,所以一开始需要初始化I2C1,这里就不赘述。
在main.c里的while(1)循环中读取这3个模块的数据并通过串口打印出来:
while(1)
{
ap3216c_readdata(&ir, &ps, &als);
printf("\r\n Data of IR, PS, ALS is: %d, %d, %d\r\n\r\n", ir, ps, als);
delayms(200);
}
Makefile后烧写,在测试时用手去靠近开发板上的芯片,观察输出结果,也可以用光照,看看输出结果是否和预想一致。
I2C的学习到此结束,接下去进入SPI外设的学习。
未完待续