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-08-25
Revision:
5:9df31d15f3fa
Parent:
As5048.h@ 4:56d59ce73270

File content as of revision 5:9df31d15f3fa:

#ifndef _AS5048_H_
#define _AS5048_H_

#include "mbed.h"
/**
 * 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:

    static const int kNumSensorBits        = 14;      // 14-bits sensor
    static const uint16_t kCountsPerRev    = 0x4000;  // 2**NUM_SENSOR_BITS
    static const uint16_t kMask            = 0x3FFF;  // 2**NUM_SENSOR_BITS - 1
    static const int kParity               = 1;       // even parity
    
    static const int kSpiFrequency         = 1000000; // AS5048 max 10 MHz
    static const int kSpiBitsPerTransfer   = 8;
    static const int kSpiMode              = 1;
    
    static const float kDegPerRev          = 360.0f;  // 360 degrees/rev
    static 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;

    /**
     * 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_];
        directions_ = new bool[kNumSensors_];
        
        for (int i=0; i<kNumSensors_; ++i) {
            read_buffer_[i] = 0;
            angle_buffer_[i] = 0;
            angle_offset_[i] = 0;
            directions_[i] = true;
        }
        
        last_command_ = AS_CMD_NOP;
    }

    
    /**
     * Destructor, memory deallocation
     */
    ~As5048() 
    {
        delete [] read_buffer_;
        delete [] angle_buffer_;
        delete [] angle_offset_;
        delete [] directions_;
    }
        
    /**
     * 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_; }
    
    /**
     * @return: pointer to directions_
     */
    const bool * get_directions_() { return directions_;}
    
    /**
     * You get the angles from two UpdateAngleBuffer() calls before
     * @return: 14 bits absolute position
     */
    int getAngle(int i_sensor=0)
    { 
        int ans = ((int) (angle_buffer_[i_sensor] & kMask)) - angle_offset_[i_sensor];
        return directions_[i_sensor]?ans:-ans;
    }
    
    /**
     * 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 direction for a sensor
     * @param i_sensor: id of sensor for which the offset is to be set
     * @param dir: true positive, false negative
     * @return: true if i_sensor in [0,kNumSensor_)
     */
    bool setDirection(int i_sensor, bool dir) 
    {
        if (i_sensor>-1 and i_sensor<kNumSensors_) {
            directions_[i_sensor] = dir;
            return true;
        }
        return false;
    }
    
    /**
     * Set direction for the first sensor
     * @param dir: true positive, false negative
     * @return: true if i_sensor in [0,kNumSensor_)
     */
    bool setDirection(bool dir) 
    {
        return setDirection(0,dir);
    }
    
    
    /**
     * 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
    bool* directions_;          // direction true positive, false negative
    
    As5048Command last_command_;// command sent during last Transfer
    
};
#endif