AS5048A encoder with the SimpleFOC Shield v2.0.4 on an NUCLEO 64 F446RE

Hi ! I am stuck since a while with this set up : AS5048A encoder (SPI communication) with the SimpleFOC Shield v2.0.4 on an NUCLEO 64 F446RE. I want to control a brushless GM5208-12 , for the moment I have no error with the following code but just a 0.0 degree output from the encoder …

The code :
#include <SimpleFOC.h>
// #include <AS5048A.h>
// MagneticSensorSPI(int cs, float _cpr, int _angle_register)
// cs - SPI chip select pin
// bit_resolution - sensor resolution
// angle_register - (optional) angle read register - default 0x3FFF
MagneticSensorSPI as5048a = MagneticSensorSPI(10, 14, 0x3FFF);
// or quick config
//MagneticSensorSPI as5047u = MagneticSensorSPI(10, AS4147_SPI);

void setup() {
// monitoring port
Serial.begin(115200);

// initialise magnetic sensor hardware
as5048a.init();

Serial.println(“as5048a ready”);
_delay(1000);
}

void loop() {
// IMPORTANT - call as frequently as possible
// update the sensor values
as5048a.update();
// display the angle and the angular velocity to the terminal
Serial.print(as5048a.getAngle());
Serial.print(“\t”);
Serial.println(as5048a.getVelocity());
}

Thx for helping me or if you have any advise :slight_smile:

On the Arduino UNO R3 works well
MagneticSensorSPI as5048a = MagneticSensorSPI(AS5048_SPI, cs);
where cs can be A0, A1… or D2, D3…
with connections:
Enc Arduino
MISO 12
MOSI 5V
CLK 13
GND GND
CS one of the above (A0,…)

If you try, please let me know if it works… I am waiting for the delivery of a Nucleo to implement the same.
Regards
fdurante

Hi @vivi ,

Which pins are you using for SPI on the Nucleo?
Your code looks ok so it seems like the SPI communication isn’t working.

Hi again @fdurante, @runger ! I want to add that BitBang approach is working well with the following code/pins, but I need to use the SimpleFOC library to control the motor afterwards … ://

const int CS_PIN = 10; // Chip Select pin
const int MOSI_PIN = PA7; // MOSI pin
const int MISO_PIN = PA6; // MISO pin
const int SCK_PIN = PA5; // SCK pin

void setup() {
Serial.begin(115200);

pinMode(CS_PIN, OUTPUT);
pinMode(MOSI_PIN, OUTPUT);
pinMode(MISO_PIN, INPUT);
pinMode(SCK_PIN, OUTPUT);

digitalWrite(CS_PIN, HIGH);   // Set CS high (disable the encoder)
digitalWrite(SCK_PIN, LOW);    // Set SCK low initially

Serial.println("Setup complete.");

}

uint8_t SPIBitBangTransfer(uint8_t dataOut) {
uint8_t dataIn = 0;

for (int i = 7; i >= 0; i--) {
    digitalWrite(MOSI_PIN, (dataOut >> i) & 1);

    digitalWrite(SCK_PIN, HIGH);
    delayMicroseconds(1);

    dataIn <<= 1;
    dataIn |= digitalRead(MISO_PIN);

    digitalWrite(SCK_PIN, LOW);
    delayMicroseconds(1);
}

return dataIn;

}

uint16_t readEncoderAngleBitBang() {
uint16_t angle = 0;

digitalWrite(CS_PIN, LOW);  // Select the encoder
delayMicroseconds(5);

// Send the read angle command (0x3FFF) using bit-banging
uint8_t highByte = SPIBitBangTransfer(0xFF);
uint8_t lowByte = SPIBitBangTransfer(0xFF);

digitalWrite(CS_PIN, HIGH);  // Deselect the encoder

// Combine high and low bytes and mask to 14 bits
angle = ((highByte << 8) | lowByte) & 0x3FFF;

return angle;

}

void loop() {
uint16_t angle = readEncoderAngleBitBang();

Serial.print("Raw Angle: ");
Serial.println(angle);

float positionDeg = (float)angle * 360.0 / 16383.0;  // Convert to degrees
Serial.print("Position (deg): ");
Serial.println(positionDeg);

delay(100);  // Delay to avoid flooding the serial monitor

}

Hi @runger, wiring is : Encoder - Nucleo - (Arduino equivalent) : 5V - 5V - 5V / GND - GND - GND / MISO - PA6 - D12 / MOSI - PA7 - D11 / SCL - PA5 - D13 / SDA - PB6 - D10

You can’t run the sensor with 5V on the Nucleo board. You have to reconfigure it for 3.3V and power it with 3.3V.

@runger @fdurante as the SimpleFOC library is still not working I use the following code (hard way) that control the motor in position using as input the IMU. It is a PID that compare and correct the pitch to stay horizontal. But the problem now is that the Shield switch on and off when the code is running, I have the feedback of both encoder and IMU but the motor just move as impulse (blue light of the simpleFOC shield switch on and off). I am using a 12V power supply connected to the shield. The code :

#include “I2Cdev.h”
#include “MPU6050_6Axis_MotionApps20.h”
#include <Wire.h>
#include <SimpleFOC.h>

MPU6050 mpu;
bool dmpReady = false;
uint8_t devStatus;
uint16_t packetSize;
uint16_t fifoCount;
uint8_t fifoBuffer[64];
Quaternion q;
VectorFloat gravity;
float ypr[3];

const int CS_PIN = 10;
const int MOSI_PIN = PA7;
const int MISO_PIN = PA6;
const int SCK_PIN = PA5;

BLDCMotor motor = BLDCMotor(7);
BLDCDriver3PWM driver = BLDCDriver3PWM(9, 5, 6, 8);

float target_pitch = 0;
float Kp = 0.0001, Ki = 0.0002, Kd = 0;
float integral = 0, previous_error = 0;

Commander command = Commander(Serial);
void doLimit(char* cmd) { command.scalar(&motor.voltage_limit, cmd); }

const int printInterval = 100000; // 100 ms in microseconds
unsigned long lastPrintTime = 0;

// SPI bit-banging functions for encoder reading
uint8_t SPIBitBangTransfer(uint8_t dataOut) {
uint8_t dataIn = 0;
for (int i = 7; i >= 0; i–) {
digitalWrite(MOSI_PIN, (dataOut >> i) & 1);
digitalWrite(SCK_PIN, HIGH);
delayMicroseconds(1); // Ideally replace this with non-blocking timer logic if necessary
dataIn <<= 1;
dataIn |= digitalRead(MISO_PIN);
digitalWrite(SCK_PIN, LOW);
delayMicroseconds(1);
}
return dataIn;
}

uint16_t readEncoderAngleBitBang() {
uint16_t angle = 0;
digitalWrite(CS_PIN, LOW);
delayMicroseconds(5);
uint8_t highByte = SPIBitBangTransfer(0xFF);
uint8_t lowByte = SPIBitBangTransfer(0xFF);
digitalWrite(CS_PIN, HIGH);
angle = ((highByte << 8) | lowByte) & 0x3FFF;
return angle;
}

void setup() {
Serial.begin(115200);
Wire.begin();
Wire.setClock(400000);

Serial.println("Initializing MPU6050...");
mpu.initialize();
Serial.println(mpu.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed");

Serial.println("Initializing DMP...");
devStatus = mpu.dmpInitialize();
if (devStatus == 0) {
    mpu.CalibrateAccel(6);
    mpu.CalibrateGyro(6);
    mpu.PrintActiveOffsets();
    mpu.setDMPEnabled(true);
    packetSize = mpu.dmpGetFIFOPacketSize();
    dmpReady = true;
    Serial.println("DMP ready!");
} else {
    Serial.print("DMP Initialization failed (code ");
    Serial.print(devStatus);
    Serial.println(")");
}

pinMode(CS_PIN, OUTPUT);
pinMode(MOSI_PIN, OUTPUT);
pinMode(MISO_PIN, INPUT);
pinMode(SCK_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH);
digitalWrite(SCK_PIN, LOW);

driver.voltage_power_supply = 12;
driver.voltage_limit = 12;
if (!driver.init()) {
    Serial.println("Driver init failed!");
    return;
}
motor.linkDriver(&driver);
motor.voltage_limit = 12;
motor.controller = MotionControlType::velocity_openloop;
if (!motor.init()) {
    Serial.println("Motor init failed!");
    return;
}

command.add('L', doLimit, "voltage limit");

Serial.println("Setup complete!");
delay(1000);

}

void loop() {
if (!dmpReady) return;

unsigned long currentMicros = micros(); // Capture micros once for consistency

fifoCount = mpu.getFIFOCount();
if (fifoCount == 1024) {
    mpu.resetFIFO();
    Serial.println("FIFO overflow!");
} else if (fifoCount >= packetSize) {
    mpu.getFIFOBytes(fifoBuffer, packetSize);
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetGravity(&gravity, &q);
    mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
    
    float pitch = radiansToDegrees(ypr[1]); // Convert only once with helper function

    float error = target_pitch - pitch;
    integral += error;
    float derivative = error - previous_error;
    float velocity_command = calculateVelocityCommand(error, integral, derivative);

    motor.move(velocity_command); // Move motor with calculated command
    previous_error = error;

    // Read Encoder Position
    uint16_t encoder_angle = readEncoderAngleBitBang();
    float current_position = (float)encoder_angle * 360.0 / 16383.0; // Convert to degrees

    // Print at interval
    if (currentMicros - lastPrintTime > printInterval) {
        printStatus(currentMicros - lastPrintTime, pitch, velocity_command, current_position);
        lastPrintTime = currentMicros;
    }
    
    mpu.resetFIFO();
}

command.run();

}

// Helper functions
float radiansToDegrees(float rad) {
return rad * 180.0 / M_PI;
}

float calculateVelocityCommand(float error, float& integral, float derivative) {
float velocity_command = Kp * error + Ki * integral + Kd * derivative;
if (velocity_command > 2 || velocity_command < -2) {
velocity_command = 0;
integral = 0; // Reset integral if command is out of bounds
}
return velocity_command;
}

void printStatus(unsigned long loopTime, float pitch, float velocity_command, float current_position) {
char buffer[10];
Serial.print("Loop Time: “);
Serial.print(loopTime);
Serial.print(” us | Pitch: “);
Serial.print(dtostrf(pitch, 6, 1, buffer));
Serial.print(” | Velocity command: “);
Serial.print(dtostrf(velocity_command, 6, 1, buffer));
Serial.print(” | Encoder Position: ");
Serial.print(dtostrf(current_position, 6, 1, buffer));
Serial.println(“°”);
}

Hey @vivi ,

I don’t think you can use the BitBang SPI in combination with SimpleFOC… you can try it, but I think it will be too slow compared to “real” SPI.

And there is no reason really to use BitBang SPI on the Nucleo, it has perfectly good SPI peripherals.

I think it is a matter of using the correct pins.

On the Nucleo F446RE, you can use the following pins for SPI:

MOSI / MISO / SCLK
PA7 / PA6 / PA5

Of course, these same pins cannot be used for other purposes if you’re using them for SPI…

You can initialize the SPI like this:

SPI.setMOSI(PA7);
SPI.setMISO(PA6);
SPI.setSCLK(PA5);
sensor.init(&SPI);

If PA7, PA6, PA5 are not good pins for you, you can also use many other pins for SPI with the Nucleo board. In this case you can use the Nucleo’s other headers rather than the Arduino header pins.

@runger Is it possible to have a complete example of utilisation of the AS5048A encoder in SPI communication ? Since the usual way with the SimpleFOC library is not working for me (i.e. my first post)

@runger the following code return a solid zero for example:
#include <SimpleFOC.h>
#include <SPI.h>

const int CS_PIN = 10; // Chip Select pin for AS5048A

MagneticSensorSPI sensor = MagneticSensorSPI(AS5048_SPI, CS_PIN);

void setup() {
Serial.begin(115200);

// Set SPI pins
SPI.setMOSI(PA7);
SPI.setMISO(PA6);
SPI.setSCLK(PA5);

sensor.init(&SPI);

Serial.println("Encoder setup complete.");

}

void loop() {
// Read the angle from the encoder
float angle = sensor.getAngle();
Serial.print("Encoder Angle: ");
Serial.println(angle, 3);

delay(100); 

}

Please also add a
sensor.update() to your main loop.

If you are calling loopFOC() then it calls sensor.update() for you. If you use only the sensor and no motor, you have to call sensor.update() yourself.