Library for SPI communication with the AMS AS5048 rotary sensor

Dependents:   heros_leg_readout_torque_addition heros_leg_readout_torque_addition heros_leg_readout_torque_addition_V3

As5048.h

Committer:
megrootens
Date:
2016-05-11
Revision:
0:723d48642d5c
Child:
1:94b48453d13a

File content as of revision 0:723d48642d5c:

#ifndef _AS5048_H_
#define _AS5048_H_

#include "mbed.h"

const int kNumSensorBits        = 14;      // 14-bits sensor
const uint16_t kCountsPerRev    = 0x4000;  // 2**NUM_SENSOR_BITS
const uint16_t kMask            = 0x3FFF;  // 2**NUM_SENSOR_BITS - 1
const int kParity               = 1;       // even parity

const int kSpiFrequency         = 1000000; // AS5048 max 10 MHz
const int kSpiBitsPerTransfer   = 8;
const int kSpiMode              = 1;

const float kDegPerRev          = 360.0f;  // 360 degrees/rev
const float kRadPerRev          = 6.28318530718f; // 2*pi rad/rev

// AS5048 flags
typedef enum {
    AS_FLAG_PARITY          = 0x8000,
    AS_FLAG_READ            = 0x4000,
} As5048Flag;

// AS5048 commands
typedef enum {
    AS_CMD_NOP              = 0x0000,
    AS_CMD_ERROR            = 0x0001 | AS_FLAG_READ,   // Reads error register of sensor and clear error flags
    AS_CMD_DIAGNOSTICS      = 0x3FFD | AS_FLAG_READ,   // Reads automatic gain control and diagnostics info
    AS_CMD_MAGNITUDE        = 0x3FFE | AS_FLAG_READ,
    AS_CMD_ANGLE            = 0x3FFF | AS_FLAG_PARITY | AS_FLAG_READ,
} As5048Command;

// AS5048 diagnostics
typedef enum {
    AS_DIAG_CORDIC_OVERFLOW = 0x0200,
    AS_DIAG_HIGH_MAGNETIC   = 0x0400,
    AS_DIAG_LOW_MAGNETIC    = 0x0800,
} As5048Diagnostics;




/**
 * Interfacing with the AMS AS5048A magnetic rotary sensor using SPI protocol
 * AS5048 uses 16-bit transfer;
 * We use two 8-bit transfers for compatibility with 8-bit SPI master devices
 * SPI protocol:
 *   Mode = 1: 
 *   clock polarity = 0 --> clock pulse is high
 *   clock phase = 1 --> sample on falling edge of clock pulse
 * Code was succesfully tested on the FRDM KL25Z and K22F. The same code fails
 * on the K64F for some reason. Sampling using a logic analyzer does however
 * show the same results for al three boards.
 */
class As5048 {

public:

    /**
     * Creates an object of num_sensors daisy chained AS5048 sensors;
     * default number of sensors in chain is 1
     * @param mosi: pinname of the mosi pin of the spi communication
     * @param miso: pinname of the miso pin of the spi communication
     * @param sck: pinname of the clock pin of the spi communication
     * @param cs: pinname of the chip select pin of the spi communication
     * @param num_sensors = 1: number of sensors in daisy chain
     */
    As5048(PinName mosi, PinName miso, PinName sck, PinName cs, int num_sensors = 1):
        kNumSensors_(num_sensors),
        chip_(cs),
        spi_(mosi, miso, sck) 
    {
        DeselectChip();
        
        spi_.format(kSpiBitsPerTransfer, kSpiMode);
        spi_.frequency(kSpiFrequency);
        
        read_buffer_  = new uint16_t[kNumSensors_];
        angle_buffer_ = new uint16_t[kNumSensors_];
        angle_offset_ = new uint16_t[kNumSensors_];
        
        for (int i=0; i<kNumSensors_; ++i) {
            read_buffer_[i] = 0;
            angle_buffer_[i] = 0;
            angle_offset_[i] = 0;
        }
        
        last_command_ = AS_CMD_NOP;
    }

    
    /**
     * Destructor, memory deallocation
     */
    ~As5048() {
        delete [] read_buffer_;
        delete [] angle_buffer_;
        delete [] angle_offset_;
    }
        
    /**
     * Parity check
     * @param n: integer to check
     * @return: true if ok
     */
    static bool CheckParity(int n) {
        int parity = n;
        for(int i=1; i <= kNumSensorBits+1; ++i) {
            n >>= 1;
            parity ^= n;
        }
        return (parity & kParity) == 0;
    }
    
    /**
     * Update the buffer with angular measurements
     * NOTE 1:
     *  If the last command sent through Transfer was *not* AS_CMD_ANGLE
     *  then we need an additional Transfer; this takes more time!
     *  This should not occur, since Transfer is not *yet* used elsewhere.
     * NOTE 2:
     *  We run a parity check on the results from the transfer. We only 
     *  update the angle_buffer_ with values that pass the parity check.
     * Measurement using Timer on K64F  for last_command_ == AS_CMD_ANGLE
     * shows this function takes 87 or 88 us.
     */
    void UpdateAngleBuffer() {
        // ensure that the new results indeed will be angles
        if (last_command_ != AS_CMD_ANGLE) {
            Transfer(AS_CMD_ANGLE);
        }
        
        // update the read buffer
        Transfer(AS_CMD_ANGLE); 
        
        // update the angle buffer with parity checked values
        for (int i=0; i<kNumSensors_; ++i) {
            if (CheckParity(read_buffer_[i])) {
                // only update angles when parity is correct
                angle_buffer_[i] = read_buffer_[i];
            }
        }
    }
    
    /**
     * @return: pointer to read_buffer_
     */
    const uint16_t* get_read_buffer()  { return read_buffer_; }
    
    /**
     * @return: pointer to angle_buffer_
     */
    const uint16_t* get_angle_buffer() { return angle_buffer_; }
    
    /**
     * @return: pointer to angle_offet_
     */
    const uint16_t* get_angle_offset() { return angle_offset_; }
    
    /**
     * You get the angles from two UpdateAngleBuffer() calls before
     * @return: 14 bits absolute position
     */
    int getAngle(int i_sensor=0){ 
        return ((int) (angle_buffer_[i_sensor] & kMask)) - angle_offset_[i_sensor]; 
    }
    
    /**
     * You get the angles from two UpdateAngleBuffer() calls before
     * @return: revolution ratio in [0,1]
     */
    float getAngleRatio(int i_sensor=0)      { return (float) getAngle(i_sensor) / kCountsPerRev; }
    
    /**
     * You get the angles from two UpdateAngleBuffer() calls before
     * @return: angle in degrees
     */
    float getAngleDegrees(int i_sensor=0)    { return getAngleRatio(i_sensor) * kDegPerRev; }
    
    /**
     * You get the angles from two UpdateAngleBuffer() calls before
     * @return: angle in radians
     */
    float getAngleRadians(int i_sensor=0)    { return getAngleRatio(i_sensor) * kRadPerRev; }
    
    /**
     * Set offset for a sensor
     * @param i_sensor: id of sensor for which the offset is to be set
     * @param offset: offset in counts [0,2**14-1]
     * @return: true if i_sensor in [0,kNumSensor_)
     */
    bool setOffset(int i_sensor, uint16_t offset) {
        if (i_sensor>-1 and i_sensor<kNumSensors_) {
            angle_offset_[i_sensor] = offset;
            return true;
        }
        return false;
    }
    
    /**
     * Set offset for the first sensor
     * @param offset: offset in counts [0,2**14-1]
     * @return: true if i_sensor in [0,kNumSensor_)
     */
    bool setOffset(uint16_t offset) { return setOffset(0,offset); }
    
    /**
     * Set offset for a sensor
     * @param i_sensor: id of sensor for which the offset is to be set
     * @param offset_ratio: offset in ratio in [0,1]
     * @return: true if i_sensor in [0,kNumSensor_)
     */
    bool setOffsetRatio (int i_sensor, float offset_ratio) {
        return setOffset(i_sensor,offset_ratio*kCountsPerRev);
    }
    
    /**
     * Set offset for the first sensor
     * @param offset_ratio: offset in ratio in [0,1]
     * @return: true if i_sensor in [0,kNumSensor_)
     */
    bool setOffsetRatio(float offset_ratio) { 
        return setOffsetRatio(0,offset_ratio); 
    }
    
    /**
     * Set offset for a sensor
     * @param i_sensor: id of sensor for which the offset is to be set
     * @param offset_degrees: offset in degrees in [0,360]
     * @return: true if i_sensor in [0,kNumSensor_)
     */
    bool setOffsetDegrees(int i_sensor, float offset_degrees) {
        return setOffsetRatio(i_sensor,offset_degrees / kDegPerRev);
    }
    
    /**
     * Set offset for the first sensor
     * @param offset_degrees: offset in degrees in [0,360]
     * @return: true if i_sensor in [0,kNumSensor_)
     */
    bool setOffsetDegrees(float offset_degrees) {
        return setOffsetDegrees(0, offset_degrees);
    }
    
    /**
     * Set offset for a sensor
     * @param i_sensor: id of sensor for which the offset is to be set
     * @param offset_radians: offset in radians in [0,2*pi]
     * @return: true if i_sensor in [0,kNumSensor_)
     */
    bool setOffsetRadians(int i_sensor, float offset_radians) {
        return setOffsetRatio(i_sensor, offset_radians / kRadPerRev);
    }
    
    /**
     * Set offset for the first sensor
     * @param offset_radians: offset in radians in [0,2*pi]
     * @return: true if i_sensor in [0,kNumSensor_)
     */
    bool setOffsetRadians(float offset_radians) {
        return setOffsetRadians(0, offset_radians);
    }
   

protected:

    /**
     * Select (low) chip, and wait 1 us (at least 350 ns)
     */
    void SelectChip()   { chip_.write(0); wait_us(1); }
    
    /**
     * Deselect (high) chip, and wait 1 us (at least 350 ns)
     */
    void DeselectChip() { chip_.write(1); wait_us(1); }

    /**
     * SPI transfer between each of the daisy chained sensors
     * @param cmd: Command to send
     */
    void Transfer(As5048Command cmd) {
        SelectChip();
        for(int i=0; i<kNumSensors_; ++i){
            read_buffer_[i]  = spi_.write(cmd>>8) << 8;
            read_buffer_[i] |= spi_.write(cmd & 0x00FF);
        }
        DeselectChip();
        last_command_ = cmd;
    }

    const int kNumSensors_;     // number of sensors in daisy chain
    DigitalOut chip_;           // chip select port
    SPI spi_;                   // mbed spi communiation object
    
    uint16_t* read_buffer_;     // buffer for results from last transfer
    uint16_t* angle_buffer_;    // buffer for angle results from last transfer
    uint16_t* angle_offset_;    // offset array for each sensor
    
    As5048Command last_command_;// command sent during last Transfer
    
};
#endif