Driver for AK7451 Angle Senor(SPI).

Fork of AK7451 by AKM Development Platform

ak7451.cpp

Committer:
masahikofukasawa
Date:
2017-04-28
Revision:
3:1e0cfed43467
Parent:
1:536cf25b0eb4

File content as of revision 3:1e0cfed43467:

#include "ak7451.h"

// OPCODE Specification
#define AK7451_OPCODE_WRITE_EEPROM          0x01    // Able to write a data on EEPROM
#define AK7451_OPCODE_READ_EEPROM           0x02    // Able to read an EEPROM
#define AK7451_OPCODE_WRITE_REGISTER        0x03    // Able to write a data on register
#define AK7451_OPCODE_READ_REGISTER         0x04    // Able to read a register
#define AK7451_OPCODE_CHANGE_MODE           0x05    // Able to change mode
#define AK7451_OPCODE_ANGLE_MEASURE_CODE    0x08    // Able to read angle measure in user mode
#define AK7451_OPCODE_READ_ANGLE            0x09    // Able to read an angle data

// Register Address Map
#define AK7451_REG_ANG                      0x00    // 12bit angle data
#define AK7451_REG_MAG                      0x01    // Magnetic flux density strength(1LSB/mT)
#define AK7451_REG_CHMD                     0x02    // For mode state
#define AK7451_REG_ERRMON                   0x03    // This register will show what kind of error is.
#define AK7451_REG_ID1                      0x04    // For ID data (EEPROM Only)
#define AK7451_REG_ID2                      0x05    // For ID data (EEPROM Only)
#define AK7451_REG_ZP                       0x06    // For set up angle zero point
#define AK7451_REG_RDABZ                    0x07    // For set up "Rotation direction",
#define AK7451_REG_MLK                      0x08    // For memory lock
#define AK7451_REG_ABNRM                    0x09    // For set up abnormal detection disable
#define AK7451_REG_UVW                      0x0A    // For set up "UVW output enable disable"

#define AK7451_READ_ANGLE_STATE_MD          0x80
#define AK7451_READ_ANGLE_STATE_P1          0x40
#define AK7451_READ_ANGLE_STATE_P2          0x20
#define AK7451_READ_ANGLE_STATE_ER          0x10

#define AK7451_BIT_MASK_ABZ_E               0x80    // ABZ output enable
#define AK7451_BIT_MASK_UVW_E               0x40    // UVW output enable

#define AK7451_ABNORMAL_STATE_NORMAL        0x03    // 

/**
 * Constructor.
 *
 */
AK7451::AK7451(){
    _spi = NULL;
    _cs = NULL;
    AK7451::operationMode = AK7451::AK7451_NORMAL_MODE;
}

/**
 * Destructor.
 *
 */
AK7451::~AK7451(){
    if (_spi) delete _spi;
    if(_cs) delete _cs;
}

/**
 * begin
 *
 * @param *spi pointer to SPI instance
 * @param *cs pointer to DigitalOut instance for CS
 */
void AK7451::begin(SPI *spi, DigitalOut *cs){
    if (_spi) delete _spi; 
    _spi=spi;
    if (_cs) delete _cs;
    _cs=cs;
    _cs->write(1);
}

/**
 * Check the connection. 
 *
 * @note Connection check is performed by reading a register which has a fixed value and verify it.
 *
 * @return Returns SUCCESS when succeeded, otherwise returns another code.
 */
AK7451::Status AK7451::checkConnection() {
    AK7451::Status status;
    
    status = AK7451::setOperationMode(AK7451::AK7451_USER_MODE);
    if(status != AK7451::SUCCESS) return status;

    char data[2];
    // read current mode
    data[0] = 0x00;
    data[1] = 0x00;
    status = AK7451::readRegister(AK7451_REG_CHMD, data);  
    if(status != AK7451::SUCCESS) return status;
    if(data[0] != 0x05 || data[1] != 0x0F) return AK7451::ERROR;
    return AK7451::SUCCESS;
}

/** 
 * Writes data to EEPROM on the device. 
 * @param address EEPROM address
 * @param data data to be written
 * @return Returns SUCCESS when succeeded, otherwise returns another.
 */
AK7451::Status AK7451::writeEEPROM(char address, const char *data){
    if(AK7451::operationMode != AK7451::AK7451_USER_MODE) return AK7451::ERROR_IN_NORMAL_MODE;
    AK7451::Status status = AK7451::write(AK7451_OPCODE_WRITE_EEPROM, address, data);
    wait(0.005);    // Wait Wt=5msec(MAX)
    return status;
}
/** 
 *  Reads data from EEPROM on the device. 
 * @param address EEPROM address
 * @param data data to read
 * @return Returns SUCCESS when succeeded, otherwise returns another.
 */
AK7451::Status AK7451::readEEPROM(char address, char *data){
    if(AK7451::operationMode != AK7451::AK7451_USER_MODE) return AK7451::ERROR_IN_NORMAL_MODE;
    AK7451::Status status = read(AK7451_OPCODE_READ_EEPROM, address, data);
    return status;
}

/** 
 * Writes data to register on the device. 
 * @param address register address
 * @param data data to be written
 * @return Returns SUCCESS when succeeded, otherwise returns another.
 */
AK7451::Status AK7451::writeRegister(char address, const char *data){
    if(AK7451::operationMode != AK7451::AK7451_USER_MODE) return AK7451::ERROR_IN_NORMAL_MODE;
    AK7451::Status status = AK7451::write(AK7451_OPCODE_WRITE_REGISTER, address, data);
    return status;
}

/** 
 *  Reads data from register on the device. 
 * @param address register address
 * @param data data to read
 * @return Returns SUCCESS when succeeded, otherwise returns another.
 */
AK7451::Status AK7451::readRegister(char address, char *data){
    if(AK7451::operationMode != AK7451::AK7451_USER_MODE) return AK7451::ERROR_IN_NORMAL_MODE;
    AK7451::Status status = AK7451::read(AK7451_OPCODE_READ_REGISTER, address, data);
    return status;
}

/**
 * Sets device operation mode.
 *
 * @param mode device opration mode
 *
 * @return Returns SUCCESS when succeeded, otherwise returns another code.
 */
AK7451::Status AK7451::setOperationMode(AK7451::OperationMode mode){
    char command[2];
    command[0] = (char)(0x0F&(mode>>8));
    command[1] = (char)(0x00FF&mode);

    AK7451::operationMode = mode;
    AK7451::Status status = AK7451::write(AK7451_OPCODE_CHANGE_MODE, AK7451_REG_CHMD, command);    
    wait(0.01);
    return status;
}

/**
 * Gets device operation mode.
 *
 * @return Returns OperationMode.
 */
AK7451::OperationMode AK7451::getOperationMode(){
    return AK7451::operationMode;
}

/**
 * Reads angle data from the device.
 *
 * @param angle pointer to read angle data buffer
 *
 * @return Returns SUCCESS when succeeded, otherwise returns another code.
 */
AK7451::Status AK7451::readAngle(char *angle) {

    char command[2]={0x00,0x00};
    AK7451::read(AK7451_OPCODE_READ_ANGLE, 0x00, command);    
    
    // status check
    if( (command[0] & AK7451_READ_ANGLE_STATE_MD) != 0 ){
//        AK7451::setOperationMode(AK7451::AK7451_NORMAL_MODE);
        return AK7451::ERROR_IN_USER_MODE;
    }else if( (command[0] & AK7451_READ_ANGLE_STATE_ER) == 0 ){
        return AK7451::ERROR_ABNORMAL_STRENGTH;
    }
    // parity check
    if( AK7451::parityCheck(command) != AK7451::SUCCESS ) return AK7451::ERROR_PARITY;
    
    // status check passed, set angle data
    angle[0] = command[0]&0x0F;
    angle[1] = command[1];
    
    return AK7451::SUCCESS;
}

/**
 * Measures and reads angle, magnetic flux density and abnormal state code while in the user mode.
 *
 * @param angle pointer to angle data 
 * @param density magnetic flux density
 * @param abnormal_state abnormal state
 *
 * @return Returns SUCCESS when succeeded, otherwise returns another code.
 */
AK7451::Status AK7451::readAngleMeasureCommand(char *angle, char *density, char *abnormal_state) {
    if(AK7451::operationMode != AK7451::AK7451_USER_MODE) return AK7451::ERROR_IN_NORMAL_MODE;
    AK7451::Status status;
    char data[2] = {0x00,0x00};
    
    // perform measurement
    status = AK7451::write(AK7451_OPCODE_ANGLE_MEASURE_CODE, AK7451_REG_ANG, data);
    if(status != AK7451::SUCCESS) return status;
    
    wait(0.01);
    
    // angle read
    status = AK7451::readRegister(AK7451_REG_ANG, data);
    if(status != AK7451::SUCCESS) return status;
    angle[0] = data[0]&0x0F;
    angle[1] = data[1];
    
    // mag density read
    status = AK7451::readRegister(AK7451_REG_MAG, data);
    if(status != AK7451::SUCCESS) return status;
    *density = data[1];

    // abnormal_state code read
    status = AK7451::readRegister(AK7451_REG_ERRMON, data);
    if(status != AK7451::SUCCESS) return status;
    *abnormal_state = data[1]&0x03;
    
    return AK7451::SUCCESS;
}

/**
 * Measures current angle and sets the value to EEPROM as zero angle position.
 *
 * @return Returns SUCCESS when succeeded, otherwise returns another code.
 */
 AK7451::Status AK7451::setAngleZero(){
        
    AK7451::Status status = AK7451::SUCCESS;
        
    char angle[2] = {0x00,0x00};
    char density;
    char abnormal_state;

    // store the original mode
    AK7451::OperationMode org = AK7451::operationMode;

    // set to user mode
    status = AK7451::setOperationMode(AK7451::AK7451_USER_MODE);
    if(status != AK7451::SUCCESS) return status;        
    
    // initialize ZP register. Set ZP value to zero.
    status = AK7451::writeRegister(0x06,angle);
    if(status != AK7451::SUCCESS) return status;
    
    // read angle data with zero ZP offset
    status = AK7451::readAngleMeasureCommand(angle, &density, &abnormal_state);
    if(status != AK7451::SUCCESS) return status;
    if(abnormal_state != AK7451_ABNORMAL_STATE_NORMAL) return AK7451::ERROR_ABNORMAL_STRENGTH;

    // set read angle to ZP EEPROM
    status = AK7451::setAngleZero(angle);
    if(status != AK7451::SUCCESS) return status;

    // back to the original mode
    status = AK7451::setOperationMode(org);
    if(status != AK7451::SUCCESS) return status;        

    return status;
}

/**
 * Sets the value to EEPROM as zero angle position.
 *
 * @param angle zero angle position
 *
 * @return Returns SUCCESS when succeeded, otherwise returns another code.
 */
AK7451::Status AK7451::setAngleZero(const char *angle){
//    if(AK7451::operationMode != AK7451::AK7451_USER_MODE) return AK7451::ERROR_IN_NORMAL_MODE;
    AK7451::writeRegister(AK7451_REG_ZP, angle);
    return AK7451::writeEEPROM(AK7451_REG_ZP, angle);
}

////////////////////////////////////////////////////////
// private methods
////////////////////////////////////////////////////////


/** 
 * Reads data from device. 
 * @param operation_code OPCODE 
 * @param address memory/register addredd
 * @param *data pointer to the read buffer. length=2 fixed.
 * @return Returns SUCCESS when succeeded, otherwise returns another.
 */
AK7451::Status AK7451::read(char operation_code, char address, char *data) {
    char command[3];
    command[0] = (operation_code<<4) | (address>>3);
    command[1] = 0xF0&(address<<5);
    command[2] = 0x00;
    
    _cs->write(0);
    _spi->write(command[0]);
    for(int i=0; i<2; i++){
        data[i] = _spi->write(command[i+1]);
    }
    _cs->write(1);
    
    return AK7451::SUCCESS;
}

/** 
 * Writes data to the device. 
 * @param operation_code OPCODE 
 * @param address memory/register addredd
 * @param *data pointer to the read buffer. length=2 fixed.
 * @return Returns SUCCESS when succeeded, otherwise returns another.
 */
AK7451::Status AK7451::write(char operation_code, char address, const char *data) {
    char command[3];
    command[0] = (operation_code<<4) | (address>>3);
    command[1] = (0xF0&(address<<5)) | (0x0F&data[0]);
    command[2] = data[1];
    
    _cs->write(0);
    for(int i=0; i<3; i++){
        _spi->write(command[i]);
    }
    _cs->write(1);
    
    return AK7451::SUCCESS;
}

/** 
 * Checks parity bits sub function
 * @param data data 12bit read data
 * @param parity parity bit status
 * @param error error bit status
 * @return Returns SUCCESS when succeeded, otherwise returns another.
 */
AK7451::Status AK7451::parityCheckSub(const char data, const char parity, const char error){
    char sum = parity + error;
    for(int i=0; i<6; i++){
        if( ((data>>i) & 0x01) !=0 )sum++;
    }
    if( (sum & 0x01) == 0) return AK7451::SUCCESS;
    else return AK7451::ERROR_PARITY;
}

/** 
 * Checks parity bits
 * @param data 2 byte read data to chek parity
 * @return Returns SUCCESS when succeeded, otherwise returns another.
 */
AK7451::Status AK7451::parityCheck(const char *data){
    char p1 = 0x01 & (data[0]>>6);
    char p2 = 0x01 & (data[0]>>5);
    char er = 0x01 & (data[0]>>4);
    char hi = ( (data[0]&0x0F)<<2 ) | ((data[1] & 0xC0)>>6);    // Data[11:6]
    char lo = data[1] & 0x3F;                                   // Data[5:0]
    AK7451::Status status;
    status = AK7451::parityCheckSub(hi, p1, er);
    if( status != AK7451::SUCCESS) return AK7451::ERROR_PARITY;
    status = parityCheckSub(lo, p2, er);
    if( status != AK7451::SUCCESS) return AK7451::ERROR_PARITY;
    return SUCCESS;
}