3軸加速度センサーLIS3DHで傾斜を計測

投稿日 2014/09/29

STM社の3軸加速度センサー LIS3DHを使用して傾斜を計測してみました。このデータシートで。優秀な製品の割にはひんそなデーターシート。これじゃData SheetではなくProduct Briefレベルです。肝心なことは何も書かれていません。ブツブツ STMはまだ未熟な企業なのですかね。A社やT社のような丁寧な説明はありません。

 

先日大気圧センサーのLPS331APを使用して大気圧を計測してみましたが、加速度センサー LIS3DHも同じSTM社の製品なので、同じような感覚(レジスタ名等が似ている)で制御できます。

18f4553_lis3dh_1.jpg

写真1:秋月電子の資料 X,Y,Z軸の方向が記載されている

この矢印の方向を重力に逆らうように立てると+1gとなる

従って平面に静止させるとZ軸は常に+1g

(裏返すと-1gとなる)

 

何といっても便利なのは、出力がデジタルなのでマイコン制御の場合、外付けのADコンバータが不要な点です。I2Cでデジタル値を読み込んでそのまま計算ができます。

 

LIS3DHは、3軸(X,、Y、Z)方向に対して±2g、±4g、±8g、±16gに対応しています。デフォルトは±2gです。

今回は衝撃を計測するわけではないので±2gで計測しました。

 

接続

 

マイコンはPIC 18F4553を使用しました。

 

PIC 18F4553はRB0, RB1にI2C(SCL,SDA)の信号が出ていますので、表示用のI2C キャラクタLCD AQM0802AとLIS3DHにつなぎました。 4.7KΩでプルアップしておきます。

 

LIS3DHの電源3.3Vは、Vccが1ピン、GNDが2ピンです。

裏面の半田ジャンパーAをつないでI2C制御モードにしておきます。

 

半田ジャンパーCをつながないで、5ピンをVccにつなぐとスレーブアドレスの下位ビットが1になり、スレーブアドレスが0x32となります。(半田ジャンパーCをつなぐか、5ピンをGNDにつなげば0x30になります)今回は0x32で使いました。

 

内蔵のADコンバータや、割り込み信号は使わないので何も接続しません。

 

計測の手順

 

まず、WHO_AM_I(0x0F)レジスタを読んで、I2Cによる接続を確認します。

0x33が返ればOKです。

 

いろいろレジスタがありますが、CTRL_REG1とCTRL_REG4のみで制御可能です。

 

CTRL_REG1(0x20)に0x27を設定し、Power ON 10Hzで計測するモードにし、X、Y、Z軸をイネーブルにします。

 

CTRL_REG4(0x23)には0x08を設定して、HR(ハイレゾリューション・モード)にします。

 

その後STATUS_REG2(0x27)のZYXDAビットを見て、1になったら計測値を読み込みます。

 

計測値はX、Y、Zの3つあり、それぞれHigh ByteとLow Byte用のレジスタがあります。

 

 

OUT_X_L(0x28)

OUT_X_H(0x29)

OUT_Y_L(0x2A)

OUT_Y_H(0x2B)

OUT_Z_L(0x2C)

OUT_Z_L(0x2D)

 

レゾリューションはHRモードの場合12bitで、値は2の補数、12ビットの左寄せです。high byteのほうにLEFT Shiftされているので、Low byteの下位4bitが0000です。このため、High byteとLow byteをINT変数に組み立てたら、4bit右にシフトしておきます。

 

値は12bitの2の補数なのでプラス側が0から0x7FF(1023.)、マイナス側が0xFFF(-1.)から0x800(-1024.)となります。

 

±2gの場合、1024で割ればgが求まります。

 

地球上ではどこにいても静止している場合1gの重力加速度を受けています。

したがって、LIS3DHを平面に置いた場合、Z軸の加速度は+1g、X、Y軸は0gです。

(写真1のX,Y,Z軸の各矢印が、重力と逆方向になった場合、その軸の加速度は+1gです。)

18f4553_lis3dh_2.jpg

写真2: 平面で静止

X: 0.1g Y: 0.0g Z: 1.0g

(左下の2個の気圧センサーは無視してください)

18f4553_lis3dh_3.jpg

写真3: 上向き前方に45度傾斜(手前を持ち上げる)

X: 0.1g Y:0.7g Z:0.7g

 

各傾斜角度におけるX, Y, Z軸の実測g値(静止時)

 

大雑把にどのように反応するかですが、

上向き(部品面が上)にして平面で静止しているとX、Yは0、Zは1.0gです。ひっくり返して下向き(部品面が下)にするとZのみ-1gとなります。

 

上向き平面から左を上げるとXが+方向に増加し、垂直で+1gとなります。

逆に右を上げるとXが-方向に減少し、垂直で-1gとなります。

 

上向き平面から手前を上げるとxは0のままですが、Y+方向に増加。垂直で+1gとなります。

上向き平面から手前を下げるとxは0のままですが、Yが-に減少。垂直で-1gとなります。

 

このようにX、Y、Z軸の値によってどちらに傾斜して(傾いて)いるかが分かります。

 

以下は実測値です

(X,Y,Z軸の方向は、写真1のように)、LIS3DHの1ピンある面を0時とすると、9時方向(左)がX、6時方向(手前)がY、上向きがZとなっています。)

 

上向き平面

計測値(約) X: 0.0, Y: 0.0, Z, 1.0

 

上向き右に45度傾斜

計測値(約) X: 0.76, Y: 0.00, Z, 0.71

 

上向き右に90度傾斜

計測値(約) X: 1.00, Y: 0.00, Z, -0.03

 

上向き左に45度傾斜

計測値(約) X: -0.65, Y: 0.00, Z, 0.70

 

上向き左に90度傾斜

計測値(約) X: -0.97, Y: 0.00, Z, -0.03

 

上向き前方に45度傾斜

計測値(約) X: 0.00, Y: 0.70, Z, 0.70

 

上向き前方に90度傾斜

計測値(約) X: 0.00, Y: 1.00, Z, -0.01

 

上向き後方に45度傾斜

計測値(約) X: 0.00, Y: -0.68, Z, 0.70

 

上向き後方に90度傾斜

計測値(約) X: 0.00, Y: -0.98, Z, 0.00

 

**************************************

下向き平面(裏返し)

計測値(約) X: 0.00, Y: 0.00, Z, -1.00

 

下向き右に45度傾斜

計測値(約) X: 0.76, Y: 0.00, Z, -0.71

 

下向き左に45度傾斜

計測値(約) X: -0.65, Y: 0.00, Z, -0.70

 

下向き前方方に45度傾斜

計測値(約) X: 0.00, Y: 0.70, Z, -0.70

 

下向き後方に45度傾斜

計測値(約) X: 0.00, Y: -0.68, Z, -0.70

 

45度の場合0.5であってほしいのですが.....0.7です。

これはその軸の傾き角度と加速度出力値がリニアでないことを表しています。傾斜角度と出力の関係を実測したのが下のグラフです。このように角度が±90度に近づくにつれて出力値の変化が小さく(つまり鈍く)なります。

 

出力値は、下記実測のようにきれいなSIN値となっています。(2016/04/18 ほよほよさんのご指摘により訂正)

18f4553_lis3dh_4.jpg

X軸、Y軸の各傾斜における感度(実測)

11.25度単位

傾斜が約40度位までリニアに変化しているが

それ以上は鈍化している。

 

 

ソースコード

 

main.cとi2c_lis3dh.cのみです。

i2C.c、i2c_aqm0802a.cは他の記事を参照ください。

 

main.c

 

#include <xc.h>
#include <p18f4553.h>
#include <stdio.h>

#define SW1 PORTEbits.RE0               //STOP<->CONT
#define SW2 PORTEbits.RE1               //STAGE 1<->2
#define LED PORTEbits.RE2               //Sens Time

 

#define WHO_AM_I    0x0F
#define CTRL_REG1   0x20
#define CTRL_REG4   0x23
#define STATUS_REG2 0x27
#define OUT_X_L     0x28
#define OUT_X_H     0x29
#define OUT_Y_L     0x2A
#define OUT_Y_H     0x2B
#define OUT_Z_L     0x2C
#define OUT_Z_H     0x2D

 

#pragma config PLLDIV   = 5          // (20MHz crystal)
#pragma config CPUDIV   = 0             // 1/2 Fosc = 10MHz
#pragma config USBDIV   = 2          // Clock source from 96MHz PLL/2
#pragma config FOSC     = HS
#pragma config FCMEN    = OFF
#pragma config IESO     = OFF
#pragma config PWRT     = OFF
#pragma config BOR      = ON
#pragma config BORV     = 3
#pragma config VREGEN   = ON       //USB Voltage Regulator = ON
#pragma config WDT      = OFF
#pragma config WDTPS    = 32768
#pragma config MCLRE    = ON
#pragma config LPT1OSC  = OFF
#pragma config PBADEN   = OFF
#pragma config CCP2MX   = OFF
#pragma config STVREN   = ON
#pragma config LVP      = OFF
#pragma config ICPRT    = OFF        // Dedicated In-Circuit Debug/Programming = OFF
#pragma config XINST    = OFF        // Extended Instruction Set = OFF
#pragma config CP0      = OFF
#pragma config CP1      = OFF
#pragma config CP2      = OFF
#pragma config CP3      = OFF
#pragma config CPB      = OFF
#pragma config CPD      = OFF
#pragma config WRT0     = OFF
#pragma config WRT1     = OFF
#pragma config WRT2     = OFF
#pragma config WRT3     = OFF
#pragma config WRTB     = OFF        // Boot Block Write Protection = OFF
#pragma config WRTC     = OFF
#pragma config WRTD     = OFF
#pragma config EBTR0    = OFF
#pragma config EBTR1    = OFF
#pragma config EBTR2    = OFF
#pragma config EBTR3    = OFF
#pragma config EBTRB    = OFF

 

void delay_us(int);
void delay_ms(int);

void i2c_init(void);
void i2c_enable(void);
void lcd_init(void);
void lcd_clear(void);
void lcd_write(unsigned char, unsigned char, const unsigned char*);

void LIS3DH_Put_regvalue(unsigned char, unsigned char);

unsigned char LIS3DH_Get_regvalue(unsigned char);

unsigned char interval_flag = 0;
int temp_value;

int loop_cnt, stop_cnt, total_time;
char string[10];
float temp, pressure;

void interrupt isr(void){
    interval_flag = 1;
    LED = 1;
    INTCONbits.TMR0IF = 0;   // Clear Interrupt Flag
    TMR0H = 255 - 76 + 1;               // 1s
    TMR0L = 0;
}

void init(void){
    ADCON1  = 0b00001111;             // All Digital
    CMCON   =   0b00000111;             // No Comparator
    TRISA   = 0b00000000;
    TRISB   = 0b00000000;
    TRISC   = 0b00000000;
    TRISD   = 0b00000000;
    TRISE   =   0b00000011;
    LATA    = 0b00000000;
    LATB    = 0b00000000;
    LATC    = 0b00000000;
    LATD    = 0b00000000;
    LATE    = 0b00000000;
}

void main(void){
    unsigned char dummy;
    union motion_xdata {
        unsigned char x_byte[2];
        int x_int;
    } sx;

    union motion_ydata {
        unsigned char y_byte[2];
        int y_int;
    } sy;

    union motion_zdata {
         unsigned char z_byte[2];
         int z_int;
    } sz;

 

    float x_val, y_val, z_val;

 

    init();

    i2c_init();
    i2c_enable();

    lcd_init();
    lcd_clear();

    lcd_write(0, 0, "JF1VRR");

    delay_ms(3000);

    T0CON = 0x87;               //10bit mode, 1:256
    TMR0H = 255 - 76 + 1;
    TMR0L = 0;
    INTCONbits.TMR0IE = 1;

    temp = 0.0;
    interval_flag = 1;

    INTCONbits.PEIE = 1;
    INTCONbits.GIE = 1;

 

    dummy = LIS3DH_Get_regvalue(WHO_AM_I);
    if(dummy != 0x33) {
        lcd_write(0, 0, "NG LIS3 ");
        while(1);
    } else {
        lcd_write(0, 0, "OK LIS3 ");
    }

    lcd_clear();

    LIS3DH_Put_regvalue(CTRL_REG1, 0x27);
    LIS3DH_Put_regvalue(CTRL_REG4, 0x08);

 

    while(1){
        if(interval_flag){
            while((LIS3DH_Get_regvalue(STATUS_REG2) & 0x80) == 0);
            sx.x_byte[1] = LIS3DH_Get_regvalue(OUT_X_H);
            sx.x_byte[0] = LIS3DH_Get_regvalue(OUT_X_L);
            sy.y_byte[1] = LIS3DH_Get_regvalue(OUT_Y_H);
            sy.y_byte[0] = LIS3DH_Get_regvalue(OUT_Y_L);
            sz.z_byte[1] = LIS3DH_Get_regvalue(OUT_Z_H);
            sz.z_byte[0] = LIS3DH_Get_regvalue(OUT_Z_L);

 

            sx.x_int = sx.x_int >> 4;
            sy.y_int = sy.y_int >> 4;
            sz.z_int = sz.z_int >> 4;

 

            x_val = (float)sx.x_int / 1024.0;
            y_val = (float)sy.y_int / 1024.0;
            z_val = (float)sz.z_int / 1024.0;

 

            lcd_clear();
            sprintf(string, "X%2.1fY%2.1f", x_val, y_val);
            lcd_write(0, 0, string);
            sprintf(string, "Z%2.1f", z_val);
            lcd_write(0, 1, string);
   
            delay_ms(1000);
            
            interval_flag = 0;
            LED = 0;
        } //if
    } //while
} //main

 

i2c_lis3dh.c

 

#include <xc.h>
#include <p18f4553.h>

 

#define LIS3DH_I2C_ADDR 0x32
#define WRITE_MODE 0x00
#define READ_MODE 0x01

 

void delay_us(int);
void delay_ms(int);

void LIS3DH_Put_regvalue(unsigned char reg_address, unsigned char data);
unsigned char LIS3DH_Get_regvalue(unsigned char reg_address);

 

void LIS3DH_Put_regvalue(unsigned char reg_address, unsigned char byte){
    SSPCON2bits.SEN = 1;
    while(SSPCON2bits.SEN == 1);

    SSPBUF = LIS3DH_I2C_ADDR | WRITE_MODE;
    while(SSPSTATbits.BF);
    if (SSPCON2bits.ACKSTAT){
        SSPCON2bits.PEN = 1;
        while(SSPCON2bits.PEN);
        return;
     }

 

     delay_us(100);
     SSPBUF = reg_address;
     while(SSPSTATbits.BF);
     if (SSPCON2bits.ACKSTAT){
         SSPCON2bits.PEN = 1;
         while(SSPCON2bits.PEN);
         return;
      }

 

      delay_us(100);
      SSPBUF = byte;
      while(SSPSTATbits.BF);
      if (SSPCON2bits.ACKSTAT){
          SSPCON2bits.PEN = 1;
          while(SSPCON2bits.PEN);
          return;
      }

 

      delay_us(100);
      SSPCON2bits.PEN = 1;
      while(SSPCON2bits.PEN);
      return;

}

 

unsigned char LIS3DH_Get_regvalue(unsigned char reg_address){
     unsigned char reg_value;

     SSPCON2bits.SEN = 1;
     while(SSPCON2bits.SEN == 1);

 

     SSPBUF = LIS3DH_I2C_ADDR | WRITE_MODE;
     while(SSPSTATbits.BF);
     if (SSPCON2bits.ACKSTAT){
         SSPCON2bits.PEN = 1;
         while(SSPCON2bits.PEN);
         return(0);
     }

 

     delay_us(100);
     SSPBUF = reg_address;
     while(SSPSTATbits.BF);
     if (SSPCON2bits.ACKSTAT){
          SSPCON2bits.PEN = 1;
          while(SSPCON2bits.PEN);
          return(0);
      }

 

      delay_us(100);
      SSPCON2bits.RSEN = 1;
      while(SSPCON2bits.RSEN);
      SSPBUF = LIS3DH_I2C_ADDR | READ_MODE;
      while(SSPSTATbits.BF);
      if (SSPCON2bits.ACKSTAT){
          SSPCON2bits.PEN = 1;
          while(SSPCON2bits.PEN);
           return(0);
      }

 

      delay_us(10);
      SSPCON2bits.RCEN = 1;
      while(SSPCON2bits.RCEN);
      reg_value = SSPBUF;
      while(SSPSTATbits.BF);
      SSPCON2bits.ACKDT = 1;

 

      delay_us(10);
      SSPCON2bits.PEN = 1;
      while(SSPCON2bits.PEN);
      return(reg_value);
}

 

 

 

(JF1VRR)

  • 最近LIS3DHを購入したものです。最初なかなか動作してくれず、こちらに辿りつきました。原因はデバイスがレジスタの連続読み出しに対応していないことだったので、貴記事とは直接関係はありませんが、プログラムを参考にさせていただきました。御礼申し上げます。ところで一点だけ気になったので(不快でしたら削除してください)申し上げますと、角度45度のときはSIN(45)=0.707、90度のときはSIN(90)=1ですのでLIS3DHの傾きに対する加速度出力は正しいと思います。グラフもきれいなサインカーブとなっております。大変ためになる記事が多く楽しみにしております。 ほよほよ ] 2016/4/19(火) 午後 5:54

  • > ほよほよさん いやぁ、やっちゃいましたね。ほよほよさん、ご指摘ありがとうございます。リニアよりSIN値で出力した方が、そのまま角度の計算ができますな。(惨敗)2016/4/19(火) 午後 7:53

  • ブログ見させていただきました。現在、LIS3DHでの計測を試みています。そこで質問があります ±16gで使用したい場合はどうしたらよいでしょうか。[ かにかに ] 2017/3/6(月) 午後 4:11

  • > かにかにさん FSを11にして、High-Reso modeで読み込んだ値にSensitivityの12を掛けてやればよいと思うのですが、環境をばらしたので実験できません。試してみてください。(JF1VRR) 2017/3/7(火) 午後 3:52

  • ブログ、大変参考になりました。ありがとうございます。生値からG換算の計算に誤りがあると思われるのでコメントさせていただきます。STMicroのFAQにて、生値からG換算する方法が書かれていました。生値/16で得られた値がそれぞれのレンジに対応する解像度となる模様です。(mg/digit)例:15136 / 16 = 946[mg](±2Gレンジの場合)

  • 参照サイト:
    https://community.st.com/s/question/0D50X00009XkhwOSAR/lis3dhtr-data-length

  • 確かに下位4bitは0サプレスされるようにですが、16bit計算でOKのようです。間違いでしたらすみません。では。god**hrs ] 2019/5/10(金) 午後 6:23

     

  • > god**hrsさん ご指摘ありがとうございます。何分5年経つと忘却の彼方です。落ち着いて考えてみます。取り急ぎお礼まで。(JF1VRR)2019/5/10(金) 午後 7:09