Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: InvertedPendulum2017
Revision 0:551dbbd41a10, committed 2017-09-15
- Comitter:
- bluefish
- Date:
- Fri Sep 15 14:54:49 2017 +0000
- Child:
- 1:5334e111af53
- Commit message:
- ???????
Changed in this revision
| Lib_MPU9250_SPI.cpp | Show annotated file Show diff for this revision Revisions of this file |
| Lib_MPU9250_SPI.h | Show annotated file Show diff for this revision Revisions of this file |
diff -r 000000000000 -r 551dbbd41a10 Lib_MPU9250_SPI.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib_MPU9250_SPI.cpp Fri Sep 15 14:54:49 2017 +0000
@@ -0,0 +1,275 @@
+#include "Lib_MPU9250_SPI.h"
+
+MPU9250::MPU9250( PinName cs, PinName mosi, PinName miso, PinName sck ) : _cs(cs), _spi(mosi, miso, sck){
+ _cs = 1;
+ _spi.format( 8, 0 );
+ _spi.frequency( 20000000 );
+
+ _accscale = MPU9250A_2G;
+ _gyroscale = MPU9250G_250DPS;
+ return;
+}
+
+MPU9250::~MPU9250(){
+ return;
+}
+
+// スタート
+void MPU9250::begin(){
+ // initialize state
+ _writeRegister( PWR_MGMT_1, 0x01 );
+ _writeRegister( PWR_MGMT_2, 0x00 );
+ _writeRegister( USER_CTRL, 0x30 );
+ _writeRegister( I2C_MST_CTRL, 0x0D ); // I2C clock speed : 400kHz
+
+ // reset AK8963
+ _writeRegister( I2C_SLV0_ADDR, AK8963_ADDR );
+ _writeRegister( I2C_SLV0_REG, CNTL2 );
+ _writeRegister( I2C_SLV0_DO, 0x01 );
+ _writeRegister( I2C_SLV0_CTRL, 0x81 );
+
+ // AK8963 mode : 100Hz sampling mode
+ _writeRegister( I2C_SLV0_ADDR, AK8963_ADDR );
+ _writeRegister( I2C_SLV0_REG, CNTL1 );
+ _writeRegister( I2C_SLV0_DO, 0x16 );
+ _writeRegister( I2C_SLV0_CTRL, 0x81 );
+ return;
+}
+
+// リセット
+void MPU9250::reset(){
+ _writeRegister( PWR_MGMT_1, BIT_H_RESET );
+ return;
+}
+
+// 加速度センサのレンジ設定
+void MPU9250::setAccelRange( MPU9250BIT range ){
+ switch( range ){
+ case BITS_FS_2G:
+ _accscale = MPU9250A_2G;
+ break;
+ case BITS_FS_4G:
+ _accscale = MPU9250A_4G;
+ break;
+ case BITS_FS_8G:
+ _accscale = MPU9250A_8G;
+ break;
+ case BITS_FS_16G:
+ _accscale = MPU9250A_16G;
+ break;
+ default:
+ return;
+ }
+ _writeRegister( ACCEL_CONFIG, range );
+ return;
+}
+
+// ジャイロセンサのレンジ設定
+void MPU9250::setGyroRange( MPU9250BIT range ){
+ switch( range ){
+ case BITS_FS_250DPS:
+ _gyroscale = MPU9250G_250DPS;
+// _gyroscalerad = MPU9250G_250DPS_RAD;
+ break;
+ case BITS_FS_500DPS:
+ _gyroscale = MPU9250G_500DPS;
+// _gyroscalerad = MPU9250G_500DPS_RAD;
+ break;
+ case BITS_FS_1000DPS:
+ _gyroscale = MPU9250G_1000DPS;
+// _gyroscalerad = MPU9250G_1000DPS_RAD;
+ break;
+ case BITS_FS_2000DPS:
+ _gyroscale = MPU9250G_2000DPS;
+// _gyroscalerad = MPU9250G_2000DPS_RAD;
+ break;
+ default:
+ return;
+ }
+ _writeRegister( GYRO_CONFIG, range );
+ return;
+}
+
+// ローパスフィルタ設定
+void MPU9250::setDLPF( MPU9250BIT range ){
+ return;
+}
+
+// 6軸分の生データを取得
+void MPU9250::read6AxisRaw( int16_t* ax, int16_t* ay, int16_t* az, int16_t* gx, int16_t* gy, int16_t* gz ){
+ uint8_t buf[14];
+ _readBuffer( ACCEL_XOUT_H, 14, buf );
+ *ax = (int16_t)( ( buf[0] << 8 ) | buf[1] );
+ *ay = (int16_t)( ( buf[2] << 8 ) | buf[3] );
+ *az = (int16_t)( ( buf[4] << 8 ) | buf[5] );
+ *gx = (int16_t)( ( buf[8] << 8 ) | buf[9] );
+ *gy = (int16_t)( ( buf[10] << 8 ) | buf[11] );
+ *gz = (int16_t)( ( buf[12] << 8 ) | buf[13] );
+ return;
+}
+
+// 9軸分の生データを取得
+void MPU9250::read9AxisRaw( int16_t* ax, int16_t* ay, int16_t* az, int16_t* gx, int16_t* gy, int16_t* gz, int16_t* mx, int16_t* my, int16_t* mz ){
+ uint8_t buf[21];
+ _writeRegister( I2C_SLV0_ADDR, AK8963_ADDR | 0x80 ); // Set the I2C slave addres of AK8963 and set for read.
+ _writeRegister( I2C_SLV0_REG, HXL ); // I2C slave 0 register address from where to begin data transfer
+ _writeRegister( I2C_SLV0_CTRL, 0x87 ); // Read 7 bytes from the magnetomete
+ _readBuffer( ACCEL_XOUT_H, 21, buf );
+ *ax = (int16_t)( ( buf[0] << 8 ) | buf[1] );
+ *ay = (int16_t)( ( buf[2] << 8 ) | buf[3] );
+ *az = (int16_t)( ( buf[4] << 8 ) | buf[5] );
+ *gx = (int16_t)( ( buf[8] << 8 ) | buf[9] );
+ *gy = (int16_t)( ( buf[10] << 8 ) | buf[11] );
+ *gz = (int16_t)( ( buf[12] << 8 ) | buf[13] );
+ *mx = (int16_t)( ( buf[15] << 8 ) | buf[14] );
+ *my = (int16_t)( ( buf[17] << 8 ) | buf[16] );
+ *mz = (int16_t)( ( buf[19] << 8 ) | buf[18] );
+ return;
+}
+
+// 加速度生データの取得
+void MPU9250::readAccelRaw( int16_t* ax, int16_t* ay, int16_t* az ){
+ uint8_t buf[6];
+ _readBuffer( ACCEL_XOUT_H, 6, buf );
+ *ax = (int16_t)( ( buf[0] << 8 ) | buf[1] );
+ *ay = (int16_t)( ( buf[2] << 8 ) | buf[3] );
+ *az = (int16_t)( ( buf[4] << 8 ) | buf[5] );
+ return;
+}
+
+// ジャイロ生データの取得
+void MPU9250::readGyroRaw( int16_t* gx, int16_t* gy, int16_t* gz ){
+ uint8_t buf[6];
+ _readBuffer( GYRO_XOUT_H, 6, buf );
+ *gx = (int16_t)( ( buf[0] << 8 ) | buf[1] );
+ *gy = (int16_t)( ( buf[2] << 8 ) | buf[3] );
+ *gz = (int16_t)( ( buf[4] << 8 ) | buf[5] );
+ return;
+}
+
+// 地磁気生データの取得
+void MPU9250::readMagRaw( int16_t* mx, int16_t* my, int16_t* mz ){
+ uint8_t buf[7];
+ _writeRegister( I2C_SLV0_ADDR, AK8963_ADDR | 0x80 ); // Set the I2C slave addres of AK8963 and set for read.
+ _writeRegister( I2C_SLV0_REG, HXL ); // I2C slave 0 register address from where to begin data transfer
+ _writeRegister( I2C_SLV0_CTRL, 0x87 ); // Read 7 bytes from the magnetomete
+ _readBuffer( EXT_SENS_DATA_00, 7, buf );
+ *mx = (int16_t)( ( buf[1] << 8 ) | buf[0] );
+ *my = (int16_t)( ( buf[3] << 8 ) | buf[2] );
+ *mz = (int16_t)( ( buf[5] << 8 ) | buf[4] );
+ return;
+}
+
+// 温度生データの取得
+int16_t MPU9250::readTempRaw(){
+ uint8_t buf[2];
+ _readBuffer( TEMP_OUT_H, 2, buf );
+ return (int16_t)( ( buf[0] << 8 ) | buf[1] );
+}
+
+// 6軸データの取得
+void MPU9250::read6Axis( float* ax, float* ay, float* az, float* gx, float* gy, float* gz ){
+ int16_t a_x, a_y, a_z, g_x, g_y, g_z;
+ read6AxisRaw( &a_x, &a_y, &a_z, &g_x, &g_y, &g_z );
+ *ax = (float)a_x * _accscale;
+ *ay = (float)a_y * _accscale;
+ *az = (float)a_z * _accscale;
+ *gx = (float)g_x * _gyroscale;
+ *gy = (float)g_y * _gyroscale;
+ *gz = (float)g_z * _gyroscale;
+ return;
+}
+
+// 9軸データの取得
+void MPU9250::read9Axis( float* ax, float* ay, float* az, float* gx, float* gy, float* gz, float* mx, float* my, float* mz ){
+ int16_t a_x, a_y, a_z, g_x, g_y, g_z, m_x, m_y, m_z;
+ read9AxisRaw( &a_x, &a_y, &a_z, &g_x, &g_y, &g_z, &m_x, &m_y, &m_z );
+ *ax = (float)a_x * _accscale;
+ *ay = (float)a_y * _accscale;
+ *az = (float)a_z * _accscale;
+ *gx = (float)g_x * _gyroscale;
+ *gy = (float)g_y * _gyroscale;
+ *gz = (float)g_z * _gyroscale;
+ *mx = (float)m_x * MPU9250M_4800uT;
+ *my = (float)m_y * MPU9250M_4800uT;
+ *mz = (float)m_z * MPU9250M_4800uT;
+ return;
+}
+
+// 加速度データの取得
+void MPU9250::readAccel( float* ax, float* ay, float* az ){
+ int16_t x, y ,z;
+ readAccelRaw( &x, &y, &z );
+ *ax = (float)x * _accscale;
+ *ay = (float)y * _accscale;
+ *az = (float)z * _accscale;
+ return;
+}
+
+// ジャイロデータの取得
+void MPU9250::readGyro( float* gx, float* gy, float* gz ){
+ int16_t x, y ,z;
+ readGyroRaw( &x, &y, &z );
+ *gx = (float)x * _gyroscale;
+ *gy = (float)y * _gyroscale;
+ *gz = (float)z * _gyroscale;
+ return;
+}
+
+// 地磁気データの取得
+void MPU9250::readMag( float* mx, float* my, float* mz ){
+ int16_t x, y ,z;
+ readMagRaw( &x, &y, &z );
+ *mx = (float)x * MPU9250M_4800uT;
+ *my = (float)y * MPU9250M_4800uT;
+ *mz = (float)z * MPU9250M_4800uT;
+ return;
+}
+
+// 温度生データの取得
+float MPU9250::readTemp(){
+ int16_t tmp;
+ tmp = readTempRaw();
+ return (float)tmp * MPU9250T_85degC;
+}
+
+// レジスタの読み込み
+uint8_t MPU9250::_readRegister( MPU9250REG addr ){
+ uint8_t ret;
+ _cs = 0;
+ ret = _spi.write( addr | 0x80 ); // send address
+ ret = _spi.write( 0x00 );
+ _cs = 1;
+ return ret;
+}
+
+// レジスタへ書き込み
+uint8_t MPU9250::_writeRegister( MPU9250REG addr, uint8_t data ){
+ _cs = 0;
+ _spi.write( addr );
+ _spi.write( data );
+ _cs = 1;
+ return 0;
+}
+
+// レジスタの読み込み(バッファ)
+uint8_t MPU9250::_readBuffer( MPU9250REG addr, uint8_t len, uint8_t* buf ){
+ _cs = 0;
+ _spi.write( addr | 0x80 ); // send address
+ while( len-- ){
+ *(buf++) = _spi.write( 0x00 ); // read data
+ }
+ _cs = 1;
+ return 0;
+}
+
+// レジスタの書き込み(バッファ)
+uint8_t MPU9250::_writeBuffer( MPU9250REG addr, uint8_t len, uint8_t* buf ){
+ _cs = 0;
+ _spi.write( addr ); // send address
+ while( len-- ){
+ _spi.write( *(buf++) ); // send data
+ }
+ _cs = 1;
+ return 0;
+}
diff -r 000000000000 -r 551dbbd41a10 Lib_MPU9250_SPI.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib_MPU9250_SPI.h Fri Sep 15 14:54:49 2017 +0000
@@ -0,0 +1,256 @@
+#ifndef __LIBRARY_MPU9250_SPI_H__
+#define __LIBRARY_MPU9250_SPI_H__
+
+#include "mbed.h"
+
+#define MPU9250_FREQ_DEFAULT (4000000) // default clock speed
+#define AK8963_ADDR (0x0C) // magnetometer address
+
+#define MPU9250A_2G ((float)0.000061035156f) // 0.000061035156 g/LSB
+#define MPU9250A_4G ((float)0.000122070312f) // 0.000122070312 g/LSB
+#define MPU9250A_8G ((float)0.000244140625f) // 0.000244140625 g/LSB
+#define MPU9250A_16G ((float)0.000488281250f) // 0.000488281250 g/LSB
+
+#define MPU9250G_250DPS ((float)0.007633587786f) // 0.007633587786 dps/LSB
+#define MPU9250G_500DPS ((float)0.015267175572f) // 0.015267175572 dps/LSB
+#define MPU9250G_1000DPS ((float)0.030487804878f) // 0.030487804878 dps/LSB
+#define MPU9250G_2000DPS ((float)0.060975609756f) // 0.060975609756 dps/LSB
+
+/*
+#define MPU9250G_250DPS_RAD ((float)0.00013323124f) // 0.00013323124 rad/LSB
+#define MPU9250G_500DPS_RAD ((float)0.00026646248f) // 0.00026646248 rad/LSB
+#define MPU9250G_1000DPS_RAD ((float)0.00053211257f) // 0.00053211257 rad/LSB
+#define MPU9250G_2000DPS_RAD ((float)0.00106422515f) // 0.00106422515 rad/LSB
+*/
+
+#define MPU9250M_4800uT ((float)0.6f) // 0.6 uT/LSB
+#define MPU9250T_85degC ((float)0.002995177763f) // 0.002995177763 degC/LSB
+
+#define MPU9250G_DEG2RAD ((float)0.01745329251f) // degree to radian
+
+// enum
+typedef enum _MPU9250REG{
+ // gyro and accel
+ SELF_TEST_X_GYRO = 0x00,
+ SELF_TEST_Y_GYRO = 0x01,
+ SELF_TEST_Z_GYRO = 0x02,
+
+ SELF_TEST_X_ACCEL = 0x0D,
+ SELF_TEST_Y_ACCEL = 0x0E,
+ SELF_TEST_Z_ACCEL = 0x0F,
+
+ XG_OFFSET_H = 0x13,
+ XG_OFFSET_L = 0x14,
+ YG_OFFSET_H = 0x15,
+ YG_OFFSET_L = 0x16,
+ ZG_OFFSET_H = 0x17,
+ ZG_OFFSET_L = 0x18,
+
+ SMPLRT_DIV = 0x19,
+ CONFIG = 0x1A,
+ GYRO_CONFIG = 0x1B,
+ ACCEL_CONFIG = 0x1C,
+ ACCEL_CONFIG2 = 0x1D,
+ LP_ACCEL_ODR = 0x1E,
+ WOM_THR = 0x1F,
+
+ FIFO_EN = 0x23,
+
+ I2C_MST_CTRL = 0x24,
+ I2C_SLV0_ADDR = 0x25,
+ I2C_SLV0_REG = 0x26,
+ I2C_SLV0_CTRL = 0x27,
+ I2C_SLV1_ADDR = 0x28,
+ I2C_SLV1_REG = 0x29,
+ I2C_SLV1_CTRL = 0x2A,
+ I2C_SLV2_ADDR = 0x2B,
+ I2C_SLV2_REG = 0x2C,
+ I2C_SLV2_CTRL = 0x2D,
+ I2C_SLV3_ADDR = 0x2E,
+ I2C_SLV3_REG = 0x2F,
+ I2C_SLV3_CTRL = 0x30,
+ I2C_SLV4_ADDR = 0x31,
+ I2C_SLV4_REG = 0x32,
+ I2C_SLV4_DO = 0x33,
+ I2C_SLV4_CTRL = 0x34,
+ I2C_SLV4_DI = 0x35,
+ I2C_MST_STATUS = 0x36,
+
+ INT_PIN_CFG = 0x37,
+ INT_ENABLE = 0x38,
+ INT_STATUS = 0x3A,
+
+ ACCEL_XOUT_H = 0x3B,
+ ACCEL_XOUT_L = 0x3C,
+ ACCEL_YOUT_H = 0x3D,
+ ACCEL_YOUT_L = 0x3E,
+ ACCEL_ZOUT_H = 0x3F,
+ ACCEL_ZOUT_L = 0x40,
+
+ TEMP_OUT_H = 0x41,
+ TEMP_OUT_L = 0x42,
+
+ GYRO_XOUT_H = 0x43,
+ GYRO_XOUT_L = 0x44,
+ GYRO_YOUT_H = 0x45,
+ GYRO_YOUT_L = 0x46,
+ GYRO_ZOUT_H = 0x47,
+ GYRO_ZOUT_L = 0x48,
+
+ EXT_SENS_DATA_00 = 0x49,
+ EXT_SENS_DATA_01 = 0x4A,
+ EXT_SENS_DATA_02 = 0x4B,
+ EXT_SENS_DATA_03 = 0x4C,
+ EXT_SENS_DATA_04 = 0x4D,
+ EXT_SENS_DATA_05 = 0x4E,
+ EXT_SENS_DATA_06 = 0x4F,
+ EXT_SENS_DATA_07 = 0x50,
+ EXT_SENS_DATA_08 = 0x51,
+ EXT_SENS_DATA_09 = 0x52,
+ EXT_SENS_DATA_10 = 0x53,
+ EXT_SENS_DATA_11 = 0x54,
+ EXT_SENS_DATA_12 = 0x55,
+ EXT_SENS_DATA_13 = 0x56,
+ EXT_SENS_DATA_14 = 0x57,
+ EXT_SENS_DATA_15 = 0x58,
+ EXT_SENS_DATA_16 = 0x59,
+ EXT_SENS_DATA_17 = 0x5A,
+ EXT_SENS_DATA_18 = 0x5B,
+ EXT_SENS_DATA_19 = 0x5C,
+ EXT_SENS_DATA_20 = 0x5D,
+ EXT_SENS_DATA_21 = 0x5E,
+ EXT_SENS_DATA_22 = 0x5F,
+ EXT_SENS_DATA_23 = 0x60,
+
+ I2C_SLV0_DO = 0x63,
+ I2C_SLV1_DO = 0x64,
+ I2C_SLV2_DO = 0x65,
+ I2C_SLV3_DO = 0x66,
+
+ I2C_MST_DELAY_CTRL = 0x67,
+ SIGNAL_PATH_RESET = 0x68,
+ MOT_DETECT_CTRL = 0x69,
+ USER_CTRL = 0x6A,
+ PWR_MGMT_1 = 0x6B,
+ PWR_MGMT_2 = 0x6C,
+
+ FIFO_COUNTH = 0x72,
+ FIFO_COUNTL = 0x73,
+ FIFO_R_W = 0x74,
+ WHO_AM_I = 0x75,
+
+ XA_OFFSET_H = 0x77,
+ XA_OFFSET_L = 0x78,
+ YA_OFFSET_H = 0x7A,
+ YA_OFFSET_L = 0x7B,
+ ZA_OFFSET_H = 0x7D,
+ ZA_OFFSET_L = 0x7E,
+} MPU9250REG;
+
+typedef enum _AK8963REG{
+ // magnetometer
+ WIA = 0x00,
+ INFO = 0x01,
+ ST1 = 0x02,
+
+ HXL = 0x03,
+ HXH = 0x04,
+ HYL = 0x05,
+ HYH = 0x06,
+ HZL = 0x07,
+ HZH = 0x08,
+
+ ST2 = 0x09,
+ CNTL1 = 0x0A,
+ CNTL2 = 0x0B,
+ ASTC = 0x0C,
+ TS1 = 0x0D,
+ TS2 = 0x0E,
+ I2CDIS = 0x0F,
+
+ ASAX = 0x10,
+ ASAY = 0x11,
+ ASAZ = 0x12,
+} AK8963REG;
+
+typedef enum _MPU9250BIT{
+ BIT_SLEEP = 0x40,
+ BIT_H_RESET = 0x80,
+ BITS_CLKSEL = 0x07,
+ MPU_CLK_SEL_PLLGYROX = 0x01,
+ MPU_CLK_SEL_PLLGYROZ = 0x03,
+ MPU_EXT_SYNC_GYROX = 0x02,
+ BITS_FS_250DPS = 0x00,
+ BITS_FS_500DPS = 0x08,
+ BITS_FS_1000DPS = 0x10,
+ BITS_FS_2000DPS = 0x18,
+ BITS_FS_2G = 0x00,
+ BITS_FS_4G = 0x08,
+ BITS_FS_8G = 0x10,
+ BITS_FS_16G = 0x18,
+ BITS_FS_MASK = 0x18,
+ BITS_DLPF_CFG_256HZ_NOLPF2 = 0x00,
+ BITS_DLPF_CFG_188HZ = 0x01,
+ BITS_DLPF_CFG_98HZ = 0x02,
+ BITS_DLPF_CFG_42HZ = 0x03,
+ BITS_DLPF_CFG_20HZ = 0x04,
+ BITS_DLPF_CFG_10HZ = 0x05,
+ BITS_DLPF_CFG_5HZ = 0x06,
+ BITS_DLPF_CFG_2100HZ_NOLPF = 0x07,
+ BITS_DLPF_CFG_MASK = 0x07,
+ BIT_INT_ANYRD_2CLEAR = 0x10,
+ BIT_RAW_RDY_EN = 0x01,
+ BIT_I2C_IF_DIS = 0x10
+} MPU9250BIT;
+
+class MPU9250{
+ public:
+ MPU9250( PinName cs, PinName mosi, PinName miso, PinName sck ); // constructor
+ ~MPU9250(); // destructor
+
+ void begin( void );
+ void reset( void );
+
+ void setAccelRange( MPU9250BIT range );
+ void setGyroRange( MPU9250BIT range );
+ void setDLPF( MPU9250BIT range );
+
+ void read6AxisRaw(
+ int16_t* ax, int16_t* ay, int16_t* az,
+ int16_t* gx, int16_t* gy, int16_t* gz );
+ void read9AxisRaw(
+ int16_t* ax, int16_t* ay, int16_t* az,
+ int16_t* gx, int16_t* gy, int16_t* gz,
+ int16_t* mx, int16_t* my, int16_t* mz );
+ void readAccelRaw( int16_t* ax, int16_t* ay, int16_t* az );
+ void readGyroRaw( int16_t* gx, int16_t* gy, int16_t* gz );
+ void readMagRaw( int16_t* mx, int16_t* my, int16_t* mz );
+ int16_t readTempRaw( void );
+
+ void read6Axis(
+ float* ax, float* ay, float* az,
+ float* gx, float* gy, float* gz );
+ void read9Axis(
+ float* ax, float* ay, float* az,
+ float* gx, float* gy, float* gz,
+ float* mx, float* my, float* mz );
+ void readAccel( float* ax, float* ay, float* az );
+ void readGyro( float* gx, float* gy, float* gz );
+ void readMag( float* mx, float* my, float* mz );
+ float readTemp( void );
+
+ private:
+ DigitalOut _cs;
+ SPI _spi;
+
+ float _accscale;
+ float _gyroscale;
+// float _gyroscalerad;
+
+ uint8_t _readRegister( MPU9250REG addr );
+ uint8_t _writeRegister( MPU9250REG addr, uint8_t data );
+ uint8_t _readBuffer( MPU9250REG addr, uint8_t len, uint8_t* buf );
+ uint8_t _writeBuffer( MPU9250REG addr, uint8_t len, uint8_t* buf );
+};
+
+#endif