【ADXL373、ADXL372】超低功耗加速度计的驱动代码测试

 

一、概述

        前言:基于对大G值加速度传感计的开发需求,我先后接触了ADXL375、ADXL373、ADXL372,其中ADXL375的示例代码比较丰富,另外两个相对较少,所以我后续就根据数据手册对ADXL373的驱动代码进行了编写(ADXL372的寄存器和ADXL373相似度极高),最终完成了对两种芯片的驱动编写。

        目的:本文将重点介绍ADXL373在SPI通信模式下的驱动代码结构以及编写过程。当然我最希望的还是读者可以避免单纯地复制粘贴别人的代码。(虽然我之前自己就是这样……)

二、ADXL373简要介绍

        对于汽车中,ADXL373的应用场景主要集中在会出现碰撞或者是某些可以导致大G值加速度的地方,也与脑震荡或者是脑创伤有关系。

【ADXL373、ADXL372】超低功耗加速度计的驱动代码测试_第1张图片

官方资料网址:

ADXL373数据手册和产品信息 | Analog Devices

        我筛选出的一些主要特征: (都是有必要了解的)

        1、 ADXL373在200 mg/LSB比例因子时提供12位输出数据;

        2、ADXL373一款超低功耗、3轴、±400 g微机电系统(MEMS)加速度计,以2560 Hz输出数据速率(ODR)工作时功耗为19 μA;

        3、提供两种额外的较低功耗模式和中断驱动的唤醒特性,用于监控非活动期间的运动情况。在唤醒模式下,可以对加速度数据求均值以获取足够低的输出噪声,从而触发低g阈值。在即时导通模式下,ADXL373功耗为1.4 μA,同时连续监控冲击环境。当检测到冲击事件超过内部设定的阈值时,器件会切换到正常的工作模式,其速度非常快以便记录事件。

        我来简要分析一下,首先比例因子和多少位输出数据这是为了帮助我们进行数据换算的,在通过寄存器读到数据之后我们需要把寄存器内的数据转化为加速度(通常是以重力加速度g为单位);除了进行数据换算,还和我们的校准有非常大的关系,因为对于一个传感器而言如果数据不准确那么它无论有其他任何优点那么都是毫无意义的;除此之外,芯片的运行模式也是一个重点,根据手册可以知道ADXL373具备三种MODE,这在后文中会提到。

三、驱动的结构划分

        以我个人目前的理解,对于传感器的驱动开发而言分为四步:第一步,初始化启动;第二步,校准数据;第三步,读取数据;第四步,特色功能。

(一)通过SPI实现主机和传感器之间的沟通

        在这里默认已经掌握SPI的基础知识,以及完成了SPI基本库的搭建,因为这一部分几乎适用于所有会使用到SPI的地方,在本文中只涉及到与ADXL373开发的相关内容。

        我们需要先阅读datasheet里面关于SPI时序图解(SPI Timing Diagrams):

【ADXL373、ADXL372】超低功耗加速度计的驱动代码测试_第2张图片

        通过上图我们可以确定SPI的MODE,以及对于寄存器的读与写操作具体怎么完成;

        其中:SPI_MODE = 0 ,传输字节时 MSB FIRST 最高位在前。第一个字节的最后一位为1时,表示进行SPI读Read;最后一位为0时表示SPI写Write;

        到这儿我们应该可以得出两个Function:unsigned int RegisterRead(uint8_t Address);和 void RegisterWrite(uint8_t Address, uint8_t data);


unsigned int readRegister(uint8_t thisRegister)
{
  uint8_t result = 0;   

  //ADXL373 COMMUNICATE BY SPI :|ADDRESS| = | A6~A0+RW |,………………

  thisRegister = thisRegister << 1;
  uint8_t dataToSend = thisRegister | RW_R;

  //digitalWrite(CS_PIN, LOW);
  digitalWrite(CS_PIN, LOW);  //Set the Chip Select pin low to start an SPI packet

  SPI.transfer(dataToSend);  // Tell device register to read from
  result = SPI.transfer(0x00); // Binary word read

  digitalWrite(CS_PIN, HIGH);  //Set CS high to close communcation

  return result;
}

void writeRegister(uint8_t thisRegister, uint8_t thisValue)
{
  //SPI :|ADDRESS| = | A6~A0+RW |,………………
 thisRegister = thisRegister << 1;

 uint8_t dataWrite = thisRegister & RW_W; // Combine the register address and WRITE command

 digitalWrite(CS_PIN,LOW); //Set CS pin low to signal SPI packet start

 SPI.transfer(dataWrite); // Transfer the register address, RW
 SPI.transfer(thisValue); // Transfer the value to write 

 digitalWrite(CS_PIN,HIGH); //Set the Chip Select pin high to signal the end of an SPI packet.

}

注意!这只是我应用在开发项目里面的源代码的节选复制,不具备直接复制使用的功能,而且我的代码是增加了MCP23017模块的,所以需要仔细斟酌,但是编写代码的思路是一致的。 

(二)初始化启动——运行模式

        初始化也就是我们见得非常多的 void XXX_init(void);函数,那么其实在我们自己编写驱动的时候往往可以给其附上参数:void  XXX_init(uint8_t Mode);可以根据自己的需要自由编排,怎么顺手怎么来。

        先搞清楚ADXL373具体有哪些MODE呢?具体模式又应该怎么配置呢?具体的得需要去仔细阅读数据手册,在手册里面详细记录了所有寄存器的信息以及配置要求。

        在这里我简要展示一下我写的 void init(void);其中“mcp”是因为我选用了MCP23017作为拓展IO进行SPI的片选,所以在初始化的时候调用的是MCP上的引脚。

void ADXL373_init(void)
{

  SPI.begin(); 
  SPI.setDataMode(SPI_MODE0); 
  SPI.setBitOrder(MSBFIRST);   

  mcp.pinMode(CS_ADXL373,OUTPUT); 

  // set the ADXL373 in the Measure Mode
  mcp.digitalWrite(CS_ADXL373, LOW); 

  adxl373.writeRegister(MEASR_CTRL, 0x04);     // Set Measurement Mode to 2560Hz bandwidth (0x04)
  adxl373.writeRegister(TIME_CTRL, 0x80);      // Set ODR to 5120 Hz
  
  adxl373.writeRegister(FIFO_CTL,0x02);
  adxl373.writeRegister(FIFO_SAMPLES,0xFF);  

  adxl373.writeRegister(REG_HPF, 0x00);


  adxl373.writeRegister(POWER_CTRL, 0x1F);     // Set full bandwidth measurement mode, HPF disabled (0x07

  byte ID_Check = adxl373.readRegister(0x02);       //√√√
  byte offset = adxl373.readRegister(0x20);

  mcp.digitalWrite(CS_ADXL373, HIGH);  
}

(三)获取数据 

        根据数据手册的内容,ADXL373的Data存储在六个连续的寄存器内:(黄色框里面是偏移寄存器,用来校准的

【ADXL373、ADXL372】超低功耗加速度计的驱动代码测试_第3张图片

        那么在知道寄存器在的具体位置之后,只需要用SPI去读寄存器的内容就好了:(注意芯片是把每一个轴的数据都分成了两部分,一部分叫做高位H,另一部分叫做低位L,分别从这两个寄存器内读取数据之后,我们需要先把它们合并成一个12bit数,在对这个12bit的数进行正负号判定,因为它是以补码的形式储存的)

int16_t ADXL373_MCP23017::getValue(int axis)
{
  int16_t AccelData = 0;    //adxl373's data is 12bit
  int high, low; 

  if (axis == x_axis)
  {
    high = readRegister(XDATA_H);
    low = readRegister(XDATA_L);
  }
  else if (axis == y_axis)
  {
    high = readRegister(YDATA_H);
    low = readRegister(YDATA_L);
  }
  else if (axis == z_axis)
  {
    high = readRegister(ZDATA_H);
    low = readRegister(ZDATA_L);
  }

  /* according to the data_H and data_L, get the acceldata in 12bit */  
  AccelData = (high << 4) & 0x0FF0;
  low = low >>4;
  AccelData |= low;

  /* Make a sign judgment 12bit */
  if(AccelData & 0x800){
    AccelData = ~(AccelData);
    AccelData &= 0x0FFF;
    AccelData +=1;
    AccelData = -AccelData;
  }

  return AccelData;
}

(四)设置自动校准 

        对于ADXL373的校准主要是基于它的偏移曲线:

【ADXL373、ADXL372】超低功耗加速度计的驱动代码测试_第4张图片

        这条曲线包含了三个轴的变化情况,千万不要觉得这几条曲线的总体趋势都差不多呀,有什么需要注意的呢?

        其实在我们最开始的启动过程中,大部分是水平校准也就是(加速度 X: 0g,Y: 0g,Z: 1g),所以我们假设芯片水平静止放置时,我们先把OFFSET的寄存器全部设置为0x00,然后读取一次数据(或者读取多次取平均值,目的都是为了知道在没有偏移量的情况下,我们传感器的测量情况)。

        假设是X: a, Y: b, Z: c 单位都是g,那么我们为了校准是不是就应该让每一个X轴的测量值都 - a ,每一个Y轴的测量值都 -b,每一个Z轴的测量值都 + (1- c)。而这个作用于每一个测量值,让它们都多同样的一截,或者都少同样的一截的这个量就是偏移量产生的作用。

        现在再回到偏移曲线图,横坐标代表OFFSET Register 内的值,纵坐标代表对应的偏移量(以LSB为单位),而我们在最开始的介绍中就提到 ADXL373的比例因子是200mg/LSB,把它带入到曲线内,我们可以得出纵坐标每一格对应的数值就是4g,0.2g*20=4g,所以OFFSET Register 内的值每+1,那么对应的数值就会加2g(注意我这里写的是横坐标加一)。

        为了实现自动校准的过程,我的思路是,先启动AXL373,再把OFFSET Register的值设置为0x00,然后再连续读取33个样本数据,算出平均值,在根据平均值设置OFFSET的偏移量:

void ADXL373_MCP23017::auto_Calibration(void)
{
  int n = 0;
  int16_t x373, y373, z373;

  //G_offset -> the flog ensure the process is started only once at a time
  if(G_offset){
    int16_t x, y, z;
    uint8_t X_OFFSET,Y_OFFSET,Z_OFFSET;

    /* first set the OFFSET into 0, 0, 0  */
    setOFFSET(0x00, 0x00, 0x00);
    
    /*Read 33 groups of data continuously*/
    while(n < offset_samples){
      x = getValue(x_axis);  //get the data of LSB--int16_t--12bit
      y = getValue(y_axis);
      z = getValue(z_axis);

      x373 += x;
      y373 += y;
      z373 += z;
      n++;   

      delay(10);   
    }

    /* Calculate their average value, and convert to offset value*/
    int16_t X = -(x373/offset_samples);   //turn to 0g
    int16_t Y = -(y373/offset_samples);   // .......0g
    int16_t Z = 5-(z373/offset_samples);  //........1g, 5LSB == 1g

    /* Use the X Y Z above to get the OFFSET */
    X_OFFSET = OFFSET_4bit(X, offsetCurve_X);
    Y_OFFSET = OFFSET_4bit(Y, offsetCurve_Y);
    Z_OFFSET = OFFSET_4bit(Z, offsetCurve_Z);

    /* reset the OFFSET register */
    setOFFSET(X_OFFSET,Y_OFFSET,Z_OFFSET);

    // Serial.println(OFFSET_4bit(X, offset_X));
    // Serial.println(OFFSET_4bit(Y, offset_Y));
    // Serial.println(OFFSET_4bit(Z, offset_Z));

    G_offset = false;
    
    delay(100);
  }
}

        其中OFFSET_4bit(X,offsetCurve_X);是我根据偏移曲线写的一个函数,它的作用就是输入一个X,也就是测得的未偏移的加速度大小,在对应的X的偏移量曲线下输出它的对应的OFFSETRegister_Value。在这里我没有找到合适的换算公式去完成换算,所以使用了数组去把曲线上对应的点描述出来,然后再把输入的X的值与数组的元素依次作比较,最后得出哪一个OFFSET_VALUE最理想。

/* Data reference points from the offset curve in the datasheet */
const int16_t offsetCurve_X[16] = {0,10,15,25,30,40,50,60,-60,-50,-40,-35,-30,-15,-10,-10};
const int16_t offsetCurve_Y[16] = {0,10,10,20,30,40,45,50,-60,-55,-50,-40,-30,-20,-20,-10};
const int16_t offsetCurve_Z[16] = {0,10,15,25,30,35,40,50,-60,-50,-40,-30,-25,-20,-15,-10};

uint8_t ADXL373_MCP23017::OFFSET_4bit(int16_t numberLSB, const int16_t *offset_array)
{
  uint8_t result = 0;
  if(numberLSB <= offset_array[8]){
    result = 0x08;
    return result;
  }
  if (numberLSB >= offset_array[7]){
    result = 0x07;
    return result;
  }
  for(uint8_t i = 0;i < 15; i++){
    if(numberLSB == offset_array[i]){
        result = i ;
        return result;
    }
    else if((numberLSB > offset_array[i])&&(numberLSB < offset_array[i+1])){
        result = i ;
        return result;      
    }      
  }
}

        OK,那么到这里的话也就完成了整个主体部分的讲解,由于我之前也没有找到直接适合Arduino的ADXL373的库,所以也给不了参考链接,但是主题思路已经展示完了。使用这些代码就可以实现基本的ADXL373获取数据。

四、ADXL373的实验测试

        对于ADXL373的测试我只用了跌落机进行操作,没有用冲击台,所以没办法得到很好的对比数据,只能通过跌落之后所测得的Z轴曲线来初步分析ADXL373的稳定性以及精准性。

        这里我展示其中一个,从一米的高度跌落,跌落至刚性材料的地面,测得的加速度曲线。请教了老师傅,这个加速度范围是合理的,峰值能达到60g左右。

      【ADXL373、ADXL372】超低功耗加速度计的驱动代码测试_第5张图片

五、总结

        对于我而言,ADXL373的驱动代码是我第一个真正意义上自己写的驱动,虽然是被迫的,因为没找到可以借用的库……但是也算是一种突破吧,也切实感受到自己写驱动可以带来的好处:如果习惯调用寄存器的话是可以自己很快编写出一个库的,而且代码可以做到尽可能的简洁,这有利于后续的开发与维护。

        挖坑:作为本篇文章的延续,计划把ADXL375、ADXL372以及它们的相关的实验测试数据给写一篇总结性的文章。

        如果您对我所介绍的内容有任何改进的建议也欢迎告诉我!如果本文对你有帮助的话,不妨点个赞。欢迎留言讨论问题,一起讨论问题、解决问题。

        

        

你可能感兴趣的:(单片机,嵌入式硬件,模块测试,github)