外设驱动库开发笔记22:ADXL345三轴数字加速度计驱动
移动设备的广泛应用增加对移动过程中各种参数的检测需求。ADXL345三轴数字加速度计可以用来检测加速度、进而测量倾斜角度等。在这一篇中,我们将讨论ADXL345三轴数字加速度计驱动程序的设计与实现。
1、功能概述
ADXL345是一款小而薄的超低功耗3轴加速度计,分辨率高(13位),测量范围达±16 g。数字输出数据为16位二进制补码格式,可通过SPI(3线或4线)或I2C数字接口访问。采用SPI通讯接口时,最大SPI时钟速度为5 MHz,时序方案按照时钟极性(CPOL)= 1、时钟相位(CPHA)= 1执行。采用I2C通讯接口时,ALT ADDRESS引脚处于高电平,器件的7位I2C地址是0x1D,随后为R / W位。这转化为0x3A写入,0x3B读取。通过ALT ADDRESS引脚(引脚12)接地,可以选择备用I2C地址0x53(随后为R / W位)。这转化为0xA6写入,0xA7读取。引脚定义及封装如下:
ADXL345非常适合移动设备应用。它可以在倾斜检测应用中测量静态重力加速度,还可以测量运动或冲击导致的动态加速度。其高分辨率(3.9 mg/LSB),能够测量不到1.0°的倾斜角度变化。
该器件提供多种特殊检测功能。活动和非活动检测功能通过比较任意轴上的加速度与用户设置的阈值来检测有无运动发生。敲击检测功能可以检测任意方向的单振和双振动作。自由落体检测功能可以检测器件是否正在掉落。这些功能可以独立映射到两个中断输出引脚中的一个。正在申请专利的集成式存储器管理系统采用一个32级先进先出(FIFO)缓冲器,可用于存储数据,从而将主机处理器负荷降至最低,并降低整体系统功耗。
低功耗模式支持基于运动的智能电源管理,从而以极低的功耗进行阈值感测和运动加速度测量。
ADXL345是一款完整的3轴加速度测量系统,可选择的测量范围有±2 g,±4 g,±8 g或±16 g。既能测量运动或冲击导致的动态加速度,也能测量静止加速度,例如重力加速度,使得器件可作为倾斜传感器使用。
2、驱动设计与实现
我们对ADXL345驱动设计与其它设备一样。我们先抽象对象类型并考虑对对象的初始化和操作。
2.1、对象定义
基于对象的操作至少要包括3方面内容:对象的定义,对象的初值以及对象的操作。接下来我们就从这里个方面入手设计并实现ADXL345的驱动。
2.1.1、抽象对象类型
对于ADXL345也同时支持SPI接口通讯和I2C接口通讯。所以我们在抽象ADXL345对象类型时将接口类型作为属性以区别不同的特性。在使用I2C时,设备有地址以区别不同的设备,所以我们将I2C设备地址也定义为属性。而使用SPI时,没有设备地址但有片选信号,我们将对片选的操作定义为对象的操作函数。
1 /*定义ADXL345三轴数据结构*/ 2 typedef struct Adxl345Object { 3 uint8_t devAddress; 4 uint8_t devID; 5 Adxl345PortType port; 6 int16_t incidence_X; 7 int16_t incidence_Y; 8 int16_t incidence_Z; 9 10 float incidence_Xf; 11 float incidence_Yf; 12 float incidence_Zf; 13 14 void (*ReadBytes)(struct Adxl345Object *adxl,uint8_t regAdd,uint8_t *rData,uint16_t rSize); //读ADXL345寄存器操作 15 void (*WriteBytes)(struct Adxl345Object *adxl,uint8_t regAdd,uint8_t *wData,uint16_t wSize);//写ADXL345寄存器操作 16 void (*ChipSelect)(Adxl345CSType en); //使用SPI接口时,片选操作 17 void (*Delayus)(volatile uint32_t nTime); /*实现us延时操作*/ 18 }Adxl345ObjectType;
2.1.2、对象初始化函数
一个对象必须赋初值方可使用,所以我们还需要一个初始化函数来对对象初始化。初始化函数除了为对象属性赋初始值和给操作指定函数指针外,还需要检测参数的合法性以及对硬件设备做必要的配置。基于此我们设计ADXL345的初始化函数如下:
1 /*对ADXL345进行初始化配置*/ 2 void Adxl345Initialization(Adxl345ObjectType *adxl,uint8_t devAdd, 3 Adxl345PortType port, 4 Adxl345ReadBytesType read, 5 Adxl345WriteBytesType write, 6 Adxl345ChipSelectType cs, 7 Adxl345DelayType delay) 8 { 9 uint8_t devID=0; 10 uint8_t setValue=0; 11 12 if((adxl==NULL)||(read==NULL)||(write=NULL)||(delay==NULL)) 13 { 14 return; 15 } 16 17 if(port==SPI) 18 { 19 if(cs==NULL) 20 { 21 return; 22 } 23 24 adxl->ChipSelect=cs; 25 adxl->devAddress=0x00; 26 } 27 else 28 { 29 if((devAdd==0xA6)||(devAdd==0x3A)) 30 { 31 adxl->devAddress=devAdd; 32 } 33 else if((devAdd==0x53)||(devAdd==0x1D)) 34 { 35 adxl->devAddress=(devAdd<<1); 36 } 37 else 38 { 39 adxl->devAddress=0x00; 40 } 41 adxl->ChipSelect=NULL; 42 } 43 44 adxl->port=port; 45 adxl->devID=0xE5; 46 adxl->incidence_X=0; 47 adxl->incidence_Xf=0.0; 48 adxl->incidence_Y=0; 49 adxl->incidence_Yf=0.0; 50 adxl->incidence_Z=0; 51 adxl->incidence_Zf=0.0; 52 53 adxl->ReadBytes=read; 54 adxl->WriteBytes=write; 55 adxl->Delayus=delay; 56 57 /*读取设备ID,在每次操作前读一次*/ 58 devID=Adxl345ReadRegister(adxl,REG_DEVID); 59 if(adxl->devID!=devID) 60 { 61 return; 62 } 63 adxl->Delayus(300); 64 65 /*配置数据格式*/ 66 setValue = 0x2B; 67 Adxl345WriteRegister(adxl,REG_DATA_FORMAT,setValue); 68 adxl->Delayus(50); 69 70 /*配置数据速率及功率模式*/ 71 setValue = 0x0A; 72 Adxl345WriteRegister(adxl,REG_BW_RATE,setValue); 73 adxl->Delayus(50); 74 75 /*配置电源特性*/ 76 setValue = 0x28; 77 Adxl345WriteRegister(adxl,REG_POWER_CTL,setValue); 78 adxl->Delayus(50); 79 80 /*配置中断使能*/ 81 setValue = 0; 82 Adxl345WriteRegister(adxl,REG_INT_ENABLE,setValue); 83 adxl->Delayus(50); 84 85 /*配置X轴偏移*/ 86 Adxl345WriteRegister(adxl,REG_OFSX,setValue); 87 adxl->Delayus(50); 88 89 /*配置Y轴偏移*/ 90 Adxl345WriteRegister(adxl,REG_OFSY,setValue); 91 adxl->Delayus(50); 92 93 /*配置Z轴偏移*/ 94 Adxl345WriteRegister(adxl,REG_OFSZ,setValue); 95 adxl->Delayus(500); 96 }
2.2、对象操作
我们定义一个对象的目的是操作这个对象,这也是驱动程序的主要内容。接下来我们就来实现对ADXL345对象的操作函数。
2.2.1、写数据操作
对ADXL345对象的写操作因为使用的接口不同其数据帧格式也会有不同。使用SPI接口时,其数据帧格式如下:
而使用I2C接口时,可以同时写多个寄存器,其数据帧格式如下:
根据上述的数据帧格式和时序图我们可以编写写ADXL345的寄存器函数:
1 /* 写ADXL345的寄存器 */ 2 static void Adxl345WriteRegister(Adxl345ObjectType *adxl,uint8_t regAdd,uint8_t wData) 3 { 4 5 if(adxl->port==SPI) 6 { 7 adxl->ChipSelect(ADXL345CS_Enable); 8 adxl->Delayus(50); 9 adxl->WriteBytes(adxl,regAdd,&wData,1); 10 adxl->Delayus(50); 11 adxl->ChipSelect(ADXL345CS_Disable); 12 } 13 else 14 { 15 adxl->WriteBytes(adxl,regAdd,&wData,1); 16 } 17 }
2.2.2、读数据操作
对ADXL345对象的读操作也同样在使用不同的接口时拥有不同的数据帧结构。使用SPI接口时,其数据帧格式如下:
而在使用I2C接口时,可以实现一个或多个寄存器的读操作,其数据帧格式如下:
根据以上的数据帧格式和时序图我们可以开发读取ADXL345的寄存器操作函数:
1 /* 读取ADXL345的寄存器 */ 2 static uint8_t Adxl345ReadRegister(Adxl345ObjectType *adxl,uint8_t regAdd) 3 { 4 uint8_t regValue=0; 5 6 if(adxl->port==SPI) 7 { 8 adxl->ChipSelect(ADXL345CS_Enable); 9 adxl->Delayus(50); 10 adxl->ReadBytes(adxl,regAdd,®Value,1); 11 adxl->Delayus(50); 12 adxl->ChipSelect(ADXL345CS_Disable); 13 } 14 else 15 { 16 adxl->ReadBytes(adxl,regAdd,®Value,1); 17 } 18 19 return regValue; 20 }
2.2.3、测量数据输出
我们操作ADXL345对象的目的当然是获取我们想要的数据。最基本的,我们开发从ADXL345获取3个坐标数据。
1 /*读取数据值,分辨率(3.9 mg/LSB)*/ 2 void GetValueFromAdxl345(Adxl345ObjectType *adxl) 3 { 4 uint8_t devID = 0; 5 uint8_t dataTemp[6]; 6 7 /*读取设备ID,在每次操作前读一次*/ 8 devID=Adxl345ReadRegister(adxl,REG_DEVID); 9 if(adxl->devID!=devID) 10 { 11 return; 12 } 13 adxl->Delayus(200); 14 15 /*读取三轴数据值*/ 16 Adxl345ReadMultiReg(adxl,REG_DATAX0,dataTemp,6); 17 18 /*数据解析*/ 19 adxl->incidence_X = (int16_t)(dataTemp[0] + ((uint16_t)dataTemp[1] << 8)); 20 adxl->incidence_Y = (int16_t)(dataTemp[2] + ((uint16_t)dataTemp[3] << 8)); 21 adxl->incidence_Z = (int16_t)(dataTemp[4] + ((uint16_t)dataTemp[5] << 8)); 22 23 adxl->incidence_Xf = (float)(adxl->incidence_X * 0.0039); 24 adxl->incidence_Yf = (float)(adxl->incidence_Y * 0.0039); 25 adxl->incidence_Zf = (float)(adxl->incidence_Z * 0.0039); 26 }
3、驱动的使用
完成了驱动的设计开发,我们还要使用驱动实现ADXL345的应用。与其它外设一样,我们也按照实际项目的使用流程来验证之。
3.1、声明并初始化对象
首先我们使用前面定义的Adxl345ObjectType类型声明一个ADXL345对象变量。如:Adxl345ObjectType adxl345;
声明对象变量后还需要调用Adxl345Initialization初始化函数对ADXL345对象变量进行初始化。当然在调用初始化函数前需要考虑传入的参数。特别是几个函数指针需要实现响应的函数。需要实现的函数类型如下:
typedef void (*Adxl345ReadBytesType)(struct Adxl345Object *adxl,uint8_t regAdd,uint8_t *rData,uint16_t rSize); //读ADXL345寄存器操作
typedef void (*Adxl345WriteBytesType)(struct Adxl345Object *adxl,uint8_t regAdd,uint8_t *wData,uint16_t wSize);//写ADXL345寄存器操作
typedef void (*Adxl345ChipSelectType)(Adxl345CSType en); //使用SPI接口时,片选操作
typedef void (*Adxl345DelayType)(volatile uint32_t nTime); /*实现us延时操作*/
定义这几个函数后,就可以将器函数指针作为实参传递给初始化函数。调用如下:
Adxl345Initialization(&adxl345,devAdd,port,read,write,cs,delay);
其中adxl345为需要初始化的ADXL345对象。devAdd为使用I2C通讯时的设备地址,使用SPI时无用。port为通讯端口的类型,SPI或者I2C。read读操作函数指针,是对硬件层的封装。write为写操作函数指针,是对硬件层的封装。cs为使用SPI接口时,片选操作函数指针。delay为延时函数的指针。
3.2、基于对象进行操作
对象初始化完成后就可进行相应的操作。ADXL345的操作比较简单就是调用GetValueFromAdxl345函数获取我们需要的数据。具体的调用样式如下:
GetValueFromAdxl345(&adxl345);
这个使用比较简单,因为我们在初始化时将数据格式、数据速率及功率模式、电源特性、中断使能、各轴的数据偏移量等都按我们的需要在初始化时作了配置。如果需要不同配置则需要做相应的修改。
4、应用总结
在我们的应用中,我们将其设置为全分辨率,±16g的测量范围,读取数据与预期一致。
使用I2C接口时,设备地址使用7位输入或8位输入都没问题,地址一共有4种可能。其他的都为非法地址,在地址输入不符合要求时,会被默认初始化为广播地址。
在使用SPI接口时,如果是通过软件操作片选信号则需要实现操作函数并将函数指针传递给初始化函数。如果硬件上采取永久选中的形式则可将NULL作为参数传入。
欢迎关注:
如果阅读这篇文章让您略有所得,还请点击下方的【好文要顶】按钮。
当然,如果您想及时了解我的博客更新,不妨点击下方的【关注我】按钮。
如果您希望更方便且及时的阅读相关文章,也可以扫描上方二维码关注我的微信公众号【木南创智】