Sensor AS5047P / SPI reads 0 angle with STM32F103 (aka Blue Pill)

Sensor AS5047P hall / SPI reads 0 angle with STM32F103 (aka Blue Pill).

I can read the angle when I use my own code on exact hardware, but the Simple FOC example gives 0.

This prevents me from using the as5047 hall mounted on the motor.

Any idea why? The sensor SPI is connected to PA4 — PA7 (SPI1).

To read the sensor using my code I also used a really simple 3-rd party library, called “AS5X47 Library for Arduino” which I downloaded eons ago from github for something unrelated.

Apparently there is some issue with SimpleFOC / stm32 and SPI itself I could guess. Any ideas?

Edit1: This is the library: https://github.com/Adrien-Legrand/AS5X47

My code

// STM32F103 / AS5047P HALL sensor / SPI

#include <SimpleFOC.h>

// MagneticSensorSPI(MagneticSensorSPIConfig_s config, int cs)
//  config  - SPI config
//  cs      - SPI chip select pin 
// magnetic sensor instance - SPI
MagneticSensorSPI sensor = MagneticSensorSPI(AS5047_SPI, PA4);
// alternative constructor (chipselsect, bit_resolution, angle_read_register, )
// MagneticSensorSPI sensor = MagneticSensorSPI(10, 14, 0x3FFF);

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

  pinMode(PC13, OUTPUT);

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

  Serial2.println("Sensor ready");
  _delay(1000);
}

void loop() {
  // display the angle and the angular velocity to the terminal
  Serial2.print(sensor.getAngle());
  Serial2.print("\t");
  Serial2.println(sensor.getVelocity());
}

Reminds me of this bug

https://community.simplefoc.com/t/weird-library-bug-with-platform-io/872/6

If you have a logic analyser, I’d be curious to know if you see (slower) SPI traffic when you read 0’s.

Otherwise, try including SimpleFOC directly in your app source tree, that’s my current workaround until I’m bored enough to address this stupid bug.

Are you on platform io?

thank you for the input. Stock arduino ide. I could replace the entire spi c code with the one that works but wanted to see if i am doing something wrong first.
my hardware is at work. ill check monday but not sure is worth the effort to debug in case i already know the setup works and the code is the culprit.

Fwiw, I’m using the as5047p too, but my mcu is samd. If you find the culprit please let me know. I’m curious.

Huh, interesting, I’ll test it today and let you know.
Have you tried to the line commented out bellow, the one with the register?

What I can tell you for sure is that AS5047 was the first sensor to be working with the library after the encoders and the bluepill was the first chip after the UNO to be supported. But as it turns out it can be that we broke something in the meantime :smiley:

Oh you mean this one? Let me check.

MagneticSensorSPI sensor = MagneticSensorSPI(AS5047_SPI, PA4);
SPIClass SPI_1(PA7, PA6, PA5);

That didn’t work.

MagneticSensorSPI sensor = MagneticSensorSPI(PA4, 14, 0x3FFF);

That one seems to be working but the result needs to be verified. I need time to compare with the other working code.

Edit:

Yes I confirm reading the register does work for sure. Still puzzling why the other two methods give zero. Any ideas?

Oh!

maybe to forgot to pass SPI_1 in ini()
sensor.init(&SPI_1)

Unfortunately, I did. We really need to resolve this as it prevents us from using multiple SPI buses.
Most MCUs have more than one SPI bus so the ability to reliably communicate chip-to-chip in this situation is paramount.

It is possible I am doing something wrong, but again, it’s working with the 3-rd pary code and with accessing the resister. Why it’s zeroing on the regular one needs to be investigated.

Comment that I am using the same AS5047P with the STM32 F446RE and it is also giving me readings of 0 angle. Sometimes it works fine but in a short time it goes to 0 and in others it directly shows 0. I am using the example code:
\ Simple_FOC \ examples \ utils \ sensor_test \ magnetic_sensors \ magnetic_sensor_spi_example

Using the line:
MagneticSensorSPI sensor = MagneticSensorSPI (AS5147_SPI, 10);
and the line:
MagneticSensorSPI sensor = MagneticSensorSPI (10, 14, 0x3FFF);
the results are the same, occasionally it works, but usually it doesn’t.

With the logic analyzer I see the messages, but I don’t know how to interpret them:

With an Arudino UNO I have not encountered problems the times I have tried it.

Hi,

I find the code in MagneticSensorSPI a bit suspicious. In particular the seemingly random delays and the fact that spi->beginTransaction(settings); is #ifdefd out for STM32 MCUs…
I have this sensor code:


I’ve used it on STM32 (F103 and F303 so far) without problems. It calls beginTransaction/endTransation, and doesn’t add any delays.

One thing to note is that you have to adjust your SPI clock speed to your system. The default is 1MHz, but I find I can usually take it up to 4MHz without problems, and that’s with 3 sensors on the same bus, each on 20cm cables and a SPI distribution hub in-between. On-PCB you can probably take it right up to 10MHz if you did your layout right.

I have written a similar driver for the AS5047D/P, which I still need to test properly. I will push it ASAP and maybe you could see if it helps with your problems?

@runger

That’d be very helpful, thank you.

Cheers,
Valentine

I think I have found the possible cause of the value 0 angle. In the arduino UNO using the logic analyzer, the clock signal corresponds to what is defined (1Mhz), and it can be changed correctly with the line:
sensor.clock_speed = 500000;

On the other hand, when I use the STM32 (F446RE) the clock line always appears as 12Mhz, even if I try to change it with sensor.clock_speed

I am testing with 20cm cables, and that is why it sometimes works, but not in many others, I think it is a very high speed for this configuration (as @runger comments)

Does anyone know how to change the SPI clock speed on the STM32?

12MHz is more than the 10MHz allowed by the sensor, so no wonder it does not work!

A suggestion I would have:
Could you try activating the line 175 in MagneticSensorSPI.cpp, where it says:
spi->beginTransaction(settings);
(by commenting out the #if and #endif around it)
And also line 200, where it does spi->endTransaction()…

AFAICT this is where the SPI speed would be set.
I can’t really understand why it is deactivated by #if for STM32s. As I note above, my version of the sensor code uses the spi->beginTransaction() and works fine on STM32…

Doing what you say, the CLK goes from 12Mhz to 705.9KHz. If I put the line sensor.clock_speed = 500000; It drops me to half 352.9Khz.

Apart from the fact that it does not correspond to the fixed values (prescalar?), I have noticed that the pulses are not constant, I always find a smaller one:

By doing this, I am reading the angles correctly, albeit at a lower frequency.

Ok, so now the clock speed doesn’t match - I’ve seen that problem as well.
But this is good because it is only a problem of the pre-scaler, I think.

What happens if you comment out the line 81 - where it says:
spi->setClockDivider(SPI_CLOCK_DIV8);

I think this might solve your problem, if I remember correctly…

I think the not-quite-regular pulses are ok. I see them too sometimes. I am not sure if they are really part of the signal or just inaccuracies of my (not very accurate) logic analyser. I would not worry about those unless you keep having problems.

Doing what you say, the CLK does not vary, it remains at 705.9KHz.
I have been doing some tests, but I have not found the solution.

Hey, I did a quick test setup:

  • SimpleFOC 2.1.1 master branch (current normal version of the library)
  • BluePill STM32F103C8
  • AS5047P sensor
  • PlatformIO, STM32 support v13.0
  • Motor not connected, just the sensor

I can report the following:
It actually works out of the box, with no modifications to MagneticSensorSPI.cpp.
The resulting speed is too fast for my crappy logic analyser, the oscilloscope shows the clock line is at 9MHz:

That makes sense since we set the pre-scaler to 8, and 72MHz system clock / 8 = 9MHz SPI clock in this case.

Surprisingly, this is working fine even via the SPI hub and all the cables:

image

With my suggested modifications to MagneticSensorSPI, the picture changes to:

So now we don’t set the pre-scaler, but rather allow beginTransaction() to apply the clock_speed as specified in the settings object.
So we get 1MHz SPI clock, as specified in the settings.

To me this confirms the following:

  • the standard settings kind of work with STM32. But the bus speed does not get set, via the settings, resulting in a MCU-dependent default bus speed via the pre-scaler setting of 8, which is often much too high. I’m surprised my setup works at 9MHz, and I think if there were a BLDC driver and motor running chances are it wouldn’t work well any more.

  • the current code prevents the bus speed from being set correctly on STM32, so as a result, many people will have trouble with the current code under STM32 (as we see in this thread).

So I will fix the code for the next release of SimpleFOC, and in the meantime I encourage users to modify MagneticSensorSPI.cpp as described above.

A separate question:

Why do you see 705.9 KHz?

  1. I guess there is some inaccuracies in the timing estimation - the logic analyser, even at 24MHz, doesn’t hit the transitions directly.
  2. I think the SPI speed you set via clock_speed is the time for one on or off pulse, not the time from on-pulse to on-pulse. So the SPI clock rate is half the clock speed. I think. Its a bit unclear from the docs.

For reference, here is the code I was running:

#include "Arduino.h"
#include "SPI.h"
#include "SimpleFOC.h"


MagneticSensorSPIConfig_s AS5047P_SPI = {
  .spi_mode = SPI_MODE1,
  .clock_speed = 1000000,
 .bit_resolution = 14,
 .angle_register = 0x3FFF,
 .data_start_bit = 13, 
 .command_rw_bit = 14,
 .command_parity_bit = 15

};

#define nCS_PIN PA15
MagneticSensorSPI sensor(AS5047P_SPI, nCS_PIN);


void setup() {
    Serial.begin(115200);
    while (!Serial);
    sensor.init();
}

void loop() {
    delay(100);
    Serial.println(sensor.getAngle(), 4);
}

And here is the modified MagneticSensor.cpp:

#ifndef TARGET_RP2040

#include "MagneticSensorSPI.h"

/** Typical configuration for the 14bit AMS AS5147 magnetic sensor over SPI interface */
MagneticSensorSPIConfig_s AS5147_SPI = {
  .spi_mode = SPI_MODE1,
  .clock_speed = 1000000,
  .bit_resolution = 14,
  .angle_register = 0x3FFF,
  .data_start_bit = 13, 
  .command_rw_bit = 14,
  .command_parity_bit = 15
};
// AS5048 and AS5047 are the same as AS5147
MagneticSensorSPIConfig_s AS5048_SPI = AS5147_SPI;
MagneticSensorSPIConfig_s AS5047_SPI = AS5147_SPI;

/** Typical configuration for the 14bit MonolithicPower MA730 magnetic sensor over SPI interface */
MagneticSensorSPIConfig_s MA730_SPI = {
  .spi_mode = SPI_MODE0,
  .clock_speed = 1000000,
  .bit_resolution = 14,
  .angle_register = 0x0000,
  .data_start_bit = 15, 
  .command_rw_bit = 0,  // not required
  .command_parity_bit = 0 // parity not implemented
};


// MagneticSensorSPI(int cs, float _bit_resolution, int _angle_register)
//  cs              - SPI chip select pin 
//  _bit_resolution   sensor resolution bit number
// _angle_register  - (optional) angle read register - default 0x3FFF
MagneticSensorSPI::MagneticSensorSPI(int cs, float _bit_resolution, int _angle_register){
  
  chip_select_pin = cs; 
  // angle read register of the magnetic sensor
  angle_register = _angle_register ? _angle_register : DEF_ANGLE_REGISTER;
  // register maximum value (counts per revolution)
  cpr = pow(2,_bit_resolution);
  spi_mode = SPI_MODE1;
  clock_speed = 1000000;
  bit_resolution = _bit_resolution;
  
  command_parity_bit = 15; // for backwards compatibilty
  command_rw_bit = 14; // for backwards compatibilty
  data_start_bit = 13; // for backwards compatibilty
}

MagneticSensorSPI::MagneticSensorSPI(MagneticSensorSPIConfig_s config, int cs){
  chip_select_pin = cs; 
  // angle read register of the magnetic sensor
  angle_register = config.angle_register ? config.angle_register : DEF_ANGLE_REGISTER;
  // register maximum value (counts per revolution)
  cpr = pow(2, config.bit_resolution);
  spi_mode = config.spi_mode;
  clock_speed = config.clock_speed;
  bit_resolution = config.bit_resolution;
  
  command_parity_bit = config.command_parity_bit; // for backwards compatibilty
  command_rw_bit = config.command_rw_bit; // for backwards compatibilty
  data_start_bit = config.data_start_bit; // for backwards compatibilty
}

void MagneticSensorSPI::init(SPIClass* _spi){

  spi = _spi;

	// 1MHz clock (AMS should be able to accept up to 10MHz)
	settings = SPISettings(clock_speed, MSBFIRST, spi_mode);

	//setup pins
	pinMode(chip_select_pin, OUTPUT);
  
	//SPI has an internal SPI-device counter, it is possible to call "begin()" from different devices
	spi->begin();
#ifndef ESP_H // if not ESP32 board
	spi->setBitOrder(MSBFIRST); // Set the SPI_1 bit order
	spi->setDataMode(spi_mode) ;
//	spi->setClockDivider(SPI_CLOCK_DIV8);
#endif

	digitalWrite(chip_select_pin, HIGH);
	// velocity calculation init
	angle_prev = 0;
	velocity_calc_timestamp = _micros(); 

	// full rotations tracking number
	full_rotation_offset = 0;
	angle_data_prev = getRawCount();  
}

//  Shaft angle calculation
//  angle is in radians [rad]
float MagneticSensorSPI::getAngle(){
  // raw data from the sensor
  float angle_data = getRawCount(); 

  // tracking the number of rotations 
  // in order to expand angle range form [0,2PI] 
  // to basically infinity
  float d_angle = angle_data - angle_data_prev;
  // if overflow happened track it as full rotation
  if(abs(d_angle) > (0.8*cpr) ) full_rotation_offset += d_angle > 0 ? -_2PI : _2PI; 
  // save the current angle value for the next steps
  // in order to know if overflow happened
  angle_data_prev = angle_data;

  // return the full angle 
  // (number of full rotations)*2PI + current sensor angle 
  return full_rotation_offset + ( angle_data / (float)cpr) * _2PI;
}

// Shaft velocity calculation
float MagneticSensorSPI::getVelocity(){
  // calculate sample time
  unsigned long now_us = _micros();
  float Ts = (now_us - velocity_calc_timestamp)*1e-6;
  // quick fix for strange cases (micros overflow)
  if(Ts <= 0 || Ts > 0.5) Ts = 1e-3; 

  // current angle
  float angle_c = getAngle();
  // velocity calculation
  float vel = (angle_c - angle_prev)/Ts;

  // save variables for future pass
  angle_prev = angle_c;
  velocity_calc_timestamp = now_us;
  return vel;
}


// function reading the raw counter of the magnetic sensor
int MagneticSensorSPI::getRawCount(){
	return (int)MagneticSensorSPI::read(angle_register);
}

// SPI functions 
/**
 * Utility function used to calculate even parity of word
 */
byte MagneticSensorSPI::spiCalcEvenParity(word value){
	byte cnt = 0;
	byte i;

	for (i = 0; i < 16; i++)
	{
		if (value & 0x1) cnt++;
		value >>= 1;
	}
	return cnt & 0x1;
}

  /*
  * Read a register from the sensor
  * Takes the address of the register as a 16 bit word
  * Returns the value of the register
  */
word MagneticSensorSPI::read(word angle_register){

  word command = angle_register;

  if (command_rw_bit > 0) {
    command = angle_register | (1 < 0) {
   	//Add a parity bit on the the MSB
  	command |= ((word)spiCalcEvenParity(command) < < command_parity_bit); // space between < symbols should be removed! It is inserted for the forum
  }

//#if !defined(_STM32_DEF_) // if not stm chips
  //SPI - begin transaction
  spi->beginTransaction(settings);
//#endif

  //Send the command
  digitalWrite(chip_select_pin, LOW);
  digitalWrite(chip_select_pin, LOW);
  spi->transfer16(command);
  digitalWrite(chip_select_pin,HIGH);
  digitalWrite(chip_select_pin,HIGH);
  
#if defined( ESP_H ) // if ESP32 board
  delayMicroseconds(50);
#else
  delayMicroseconds(10);
#endif
  
  //Now read the response
  digitalWrite(chip_select_pin, LOW);
  digitalWrite(chip_select_pin, LOW);
  word register_value = spi->transfer16(0x00);
  digitalWrite(chip_select_pin, HIGH);
  digitalWrite(chip_select_pin,HIGH);

//#if !defined(_STM32_DEF_) // if not stm chips
  //SPI - end transaction
  spi->endTransaction();
//#endif
  
  register_value = register_value >> (1 + data_start_bit - bit_resolution);  //this should shift data to the rightmost bits of the word

  const static word data_mask = 0xFFFF >> (16 - bit_resolution);

	return register_value & data_mask;  // Return the data, stripping the non data (e.g parity) bits
}

/**
 * Closes the SPI connection
 * SPI has an internal SPI-device counter, for each init()-call the close() function must be called exactly 1 time
 */
void MagneticSensorSPI::close(){
	spi->end();
}


#endif