Pin configuration (to enable SPI) on STM32 Nucleo

To set the scene, I’m pretty new to all things BLDC, not to mention all thing microprocessor and embedded engineering related, so I hope my encyclopaedic ignorance is not too frustrating for you.

I’m working on setting up my first BLDC based on the very useful resources of the Simple FOC project. My initial set-up was as follows:

  • motor: 22 pole brushless gimbal motor (Quanum)
  • sensor: AS5147P, communicating on SPI
  • driver board: SimpleFOCShield V2.0.3
  • processor: Arduino Uno

So far things went pretty well - closed loop control in each of torque, position and velocity modes all operate as expected. I also added an ADXL345 accelerometer (communicating on I2C), and set that up as a “proportional switch” to set the target torque of the motor. This works great too.

At this point, I was using 90% of the Uno memory, and since I intend to add at least 1 more motor to get my project working, I decided it was time to switch up to a more capable board. Knowing very little about these things, I decided the STM32 nucleo64 boards looked like a popular and capable choice, so I ended up with a nucleo-f446RE. At this point, based on various things I’ve read across this forum, I also decided to switch from the Arduion IDE to PlatformIO.

Whilst a bit of a step up, some serendipitous keyboard mashing was enough for me to get my program recompiled and downloaded (maybe not the right technical terms) onto the Uno from within PlatformIO, and it seems to work just as well as it did when built in the Arduino IDE.

In my naivety I hoped I could then just make a version of the project where I set the board to the F446RE in the .ini file, and press GO. Oh dear. This was about two weeks ago, and since then I’ve spent about six years (it’s non-linear) pressing buttons, reading forums, downloading softwares and engaging in heated, one-sided debates with everything from “the internet” to my coffee mug, and all to little avail.

In more detail: the first thing which I know is not working correctly with the Nucleo board is the SPI signal (that is to read the AS5147). I am attempting to use the same pins I use on the Uno (digital 10-13), which I think it the primary “SPI1” on the Nucleo. While I do get some sort of signal when I run my code, which has some vague correlation to the motor (the signal changes when I spin the motor by hand), the signal is extremely “noisy” and nothing like I was reading through the Uno.

I have come to understand that the F446RE, while it has physically a duplication of the Uno pin-out pattern on the board, does not necessarily constrain itself to using these pins in the same way the Uno does. I believe the processor and/or board and/or software (delete as appropriate) needs to be configured, so as to tell it what to use the pins for.

I downloaded the STM32CubeMX software and, after it had finished downloading a couple of GB worth of files (?!), I was able to see that in the default config for the F446RE, SPI1 is not actually available, since PA5 (i.e. old D13) is mapped as a GPIO Output, not SPI1_SCK, and also that you had to actively choose to “enable” SPI1 for the other required pins to be mapped as anything other than blank.

So the crux of my trouble is that I know some form of configuration has to be done to explain to the board what I want it to do, but for the life of me I can’t find any moron-friendly instructions which explain how this might be achieved.

My possible ideas are as follows:

  1. Use STM32CubeMX to generate a range of C files which capture the settings I might select within the STM32CubeMX graphical interface. The key sticking point here is, once generated, how do I introduce these files (all/some/just one?) to my platformIO project such that when it does its compilation wizardry, it correctly includes these configuration notes? I did find a thing on github called stm32pio, which seemed to be roughly a solution to this conundrum, but when I tried to use that I couldn’t figure out how to make it happy with all its “dependencies”, and the whole thing started to feel quite Alice-In-Wonderland.

  2. Through trawling of these forums, I’m tenuously sensing that there may be another way to explain to the Nucleo what I want it to do through some files already within the scope of PlatformIO. These may (or may not) include:
    i) “build flags” in the .ini file
    ii) something called the “variant file”
    iii) the PeripheralPins.c file
    Beyond their names, I’m not really sure what each of these is about, or how to engage with it in a useful way.

  3. Declare war on a small neutral country

As far as I can tell, each of these options has about as much chance of getting this board working as any of the others, so if anyone can guide me, you will have my eternal gratitude. I guess really what I’m looking for is a “basics-for-beginners” type introduction in how to get off the ground with the combination of PlatformIO and the Nucleo board. It’s difficult for me to stress sufficiently how little I know about what I’m actually trying to achieve here, so a really key criteria is an explanation that doesn’t assume I already know what’s going on. I’ve a horrible suspicion I will just need a couple of lines of simple code somewhere I need to say “Dear Nucleo board, please enable the SPI1 on pins XYZ, thanks a lot”, but so far two weeks of searching has failed to uncover the stone under which the necessary lines are hiding.

If anyone can point me towards any resources of this kind, or even put together a basic explanation, it would save a huge amount of (additional) stress and outrageous language, and be received with great gratitude. I’m hopeful that once I get a grasp on this configuration business, I’ll at least have a chance of being able to work my way through any subsequent problems that arise with any of the other signals (I2C, the PWMs etc.) and ultimately get back to where I was with the Uno (i.e. fully working FOC) on the Nucleo.

Hello @chrisX12

Thank you for trying out SimpleFOC.

These are the pins for the NUCLEO-F446RE board:

6 D13 PA5 SPI1_SCK
5 D12 PA6 SPI1_MISO
4 D11 PA7 TIM14_CH1 || SPI1_MOSI
3 D10 PB6 TIM4_CH1 || SPI1_CS

From your description, on a high level, it seems you are doing everything correctly.

However it’s impossible to tell without code, so it would be helpful if you post the code, in its entirety, to at least be able to help with any suggestions.

Also, please check this thread, it may help.

Cheers,
Valentine

Hi @Valentine

Thanks for your reply - yes those pins are familiar, and the ones I’m wanting to figure out how to activate / configure / tell the board to use.

I think you’re right that the linked thread is related, but through my readings of it I’ve not yet been able to filter out any anything specific that I can get any improvement out of. Though this may be just that I’m not sure what it is I’m looking at.

In terms of a code, my latest trouble shooting method has been to put together a minimal, first principles SPI reader (so without any of the FOC stuff). My thinking is that I know the FOC bit is working in general (as it works on the Uno), so just to focus on the bit that’s not working. I’ve included this below. I’ve compiled the exact same code (both times in PlatformIO) for i) arduino Uno (works no problems, good sensor reading) and ii) the F446RE (issue as originally described - very messy, inaccurate readings). On the F446RE build, I uncomment the 3 SPI.set(…) lines (which I found recommend elsewhere on this forum), but it makes no noticeable difference.

Since the issue with this code seems to be exactly what I was getting with the main FOC one, I think that once this one if fixed, the same changes will fix the main code.

#include <Arduino.h>
#include <SPI.h>

int selectPin = 10;
int angle;
int angleReal;
float angleRad;

void setup() {
  // put your setup code here, to run once:

Serial.begin(9600);

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

SPI.begin();

pinMode(selectPin, OUTPUT);

digitalWrite(selectPin, HIGH);

}

void loop() {
  // put your main code here, to run repeatedly:

SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE1));

digitalWrite(selectPin, LOW);

angle = SPI.transfer16(0xFFFF);

digitalWrite(selectPin, HIGH);

delayMicroseconds(1);

SPI.endTransaction();

angleReal = (angle & (0x3FFF));

angleRad = (float)angleReal/16384.0*2*PI;

Serial.println(angleRad);

}

Hi @chrisX12,

In the past months, I’ve been using SimpleFOC with SPI on Nucleo+Arduino, Nucleo+PlatformIO and Nucleo+STM32CubeMX. Every combination works, so don’t loose faith.

However, I strongly advise against going the Nucleo+STM32CubeMX way. It requires porting the STM32duino library to STM32CubeMX, which is tricky and undocumented.

I suggest you get back to Nucleo+Arduino, you’ll get more help on this forum and I can tell you it works, with the same SPI pins (PA5, PA6, PA7).

Plesae try the magnetic_sensor_spi example with an additional _delay(20) in the loop. Maybe the “noise” you get is just your Nucleo being too fast to provide a meaningful deltaT (see this topic).

Thank you, I was about to make a similar comment. Help with debugging ported CubeMX code on this forum is very thin. CubeMX assumes professionals and they won’t hang around here giving advice for free. I’m not being rude, just being blunt.

Your best line of action is stay on Arduino IDE.

Do you have any valid reason to switch away from Arduino IDE? Unless you are developing a closed-source product with proprietary algorithms and it’s a paid job, where is the urgency to move away?

Cheers,
Valentine

Thanks @quentin and @Valentine for your replies.

I feel like there’s a bit of confusion - probably a lot of it is mine. Please let me know if I’ve misunderstood the terminology, but as far as I can tell there are two separate things that might be meant by being “in” or “on” Arduino. One would be to be working within the Arduino IDE itself, and the second would be using the Arduino framework. By default, I believe that you’re using the Arduino framework if you’re within the Arduino IDE, and within PlatformIO, you have free choice between the available frameworks for any supported board, one of which choices (for the Nucleo F446RE) is Arduino.

Therefore while I’m trying to get into using the PlatformIO environment, I’m still using the Arduino framework, and I have no plan or motivation to move away from this. I’m happy to find if I’m wrong, but my current understanding is that using the Arduino framework within PlatformIO should be functionally equivalent to working directly in the Arduino IDE. By the way, my motivation to use PlatformIO is that I’ve come across numerous endorsements of it (including on these forums!), and I thought it might be useful to get familiar with it so that I can take advantage of the broader set of tools it offers compared to Arduino IDE as my capabilities with this type of development grow.

My mention of the STM32CubeMX software was just a description of one of the routes I’ve been exploring to try to get to understand how the pins, clocks and so forth can be configured on the Nucleo board, rather than an attempt to work in the STM32Cube framework itself. What I’d been hoping was that someone could advise on the a) necessity and b) practical implementation of configuring the pins, clocks etc of the Nucleo board while working with the SimpleFOC library within the Arduino framework.

However, as I say all these terms and concepts are fairly foreign to me, so I may be asking the wrong questions in the wrong ways.

Based on your suggestion @quentin, I’ve run a few test with the magnetic_sensor_spi example. My code is as follows:

#include <SimpleFOC.h>

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

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

  sensor.spi_mode = SPI_MODE1; // spi mode - OPTIONAL
  sensor.clock_speed = 5000000; // spi clock frequency - OPTIONAL

SPI.setMISO(PA6);
SPI.setMOSI(PA7);
SPI.setSCLK(PA5);

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

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

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

First, I started with the three SPI.setXYZ() lines commented out, and also with the new _delay(20) commented out. I ran this on the Uno board, and got exactly the result I expected with good sensor functionality, as below:

As you see, I was just rotating the motor back and forth by hand, through about 90 degrees. I use the same motion in all the following tests.

I then ran the exact same code on the Nucleo (by the way, I did all this back in the Arduino IDE, just to avoid any possible other effects). The result form this is as follows:

{On trying to post this, I was informed that new users may only embed one file in a post. Therefore I’ll post the image in a separate post below (if it lets me).}

Next, I added the recommended _delay(20), and achieved this:

{Ditto - image now below}

I would tenuously call this an improvement, but I feel there’s something a bit more fundamentally wrong somewhere. Note that I’m still just rotating the motor back and forth over the same 90 degree path.

Finally I ran with both the new _delay(20) active, and also the three SPI.setXYZ() lines active, so as to be sure the board is definitely using the right pins. The result follows:

{Ditto - image now below}

I’d say there’s little to choose between this and the previous test, so I guess the board was, by default, using the correct pins anyway.

My current suspicion is that there’s something fundamentally “missing” in how I’m setting up the board for SPI comms. i.e. there’s something I should be configuring, or setting, but that I’m not. This is why I moved on to the first principles SPI reader I posted above - I wanted to remove as many other variable as possible to narrow down where the issue lies. Since that code (the minimal SPI one) gives similar performance to the ones in this post, and since that one is fully independent of the SimpleFOC library, that’s why I’m thinking there’s something basic that I’ve missed that must be done in order to configure the board to successfully use the SPI1 interface.

Hopefully this all makes sense, but happy to clarify anything that’s not. Also, please do correct me if I’ve misunderstood the whole business of working “in” Arduino in any way.

I don’t think there’s something more to set up on the Nucleo compared to the Uno.

Your screenshots look like those in here. Hence my questions: did you check you SPI wires and connectors? Are you using the exact same wires and connectors on both boards?

Even if you are, maybe the Nucleo has something different in how it handles jumper wires. As for me, I completely banned wires with male connectors connected to the Arduino (female) headers. It caused too many issues, especially with SPI. I now use only wires with female connectors, connected to the Nucleo (male) Morpho headers.

There definitely appears to be some similarity to the issue you describe in the linked thread, although I think there are also noticeable differences (e.g. I don’t have the “deadband” rotation segment where no signal change at all is observed). Also, I don’t have the Simple FOC shield installed at the moment, so all my connections are directly onto the relevant board (either Uno or Nucleo).

Did you ever get to the bottom of why there was only satisfactory performance with the alternative sensor code rather than the built in Simple FOC one? I guess it must have been handling something differently in the background - if we knew what it was, it might be turn out to be related in some way.

I think it’s still a valid idea to double check connections, so I just have. I can confirm:

  • I’m using the exact same wires on each of the Uno and Nucleo (I just swap them from one to the other board, leaving connected at the sensor end)
  • the wires are connected to the correct pins on the sensor (always worth a double check! although since the Uno works fine, I guess that’s implied anyway)
  • I tried your suggestion of avoiding the female connectors on the Nucleo, and plugging to the corresponding male pins instead. Unfortunately, this made no difference in my case.

Thanks for the suggestions though - if there’s anything else you can think of please let me know.

Got it. I was under the impression the way you wrote it, that you got good results on Arduino and bad results when you ported to Cube.

In that case, please connect the sensor directly to Nucleo, ground everything to a real ground, skip SimpleFOC shield, plug directly in Nucleo, and as @quentin pointed out make sure your connectors are solid. Also experiment with different ways of talking to the sensor, experiment with all calls including using the address directly

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

just make sure you set the correct pins there.

Keep the wires short. I had a case where I had similar poor results, but when I made sure the setup is clean, short wires and no extra interference, nothing between the sensor and the board, I got good signal when I called the SPI directly with the address.

Posting a picture of your setup may also help people to spot any issues.

No, I didn’t. Now I can run the built-in sensor code without any problem.

At this stage I would solder the wires on the pins. I know this sounds crazy, but I had so many strange issues with wires (especially the cheap jumper wires) that I’ve become paranoid.

Hey guys,
Nucleo f466re, if I remember right and arduino UNO should use the same code and the same pinout for the SPI communication with the AS5047. I am almost 100% sure because the code was in the most part written for the as5047 and those two microcontrollers :smiley:

Now what I see here as a potential issue is that as5047 has both 3.3V and 5V logic. Since the code works on Arudino UNO, I suppose you’re using the 5V logic. And I have had this issue before with my stm32 boards which are 3.3V so make sure that you are using the 3.3V logic on the as5047.

This is easy to do, just check the solder bridge connection on back side of the board. It should be soldered for 3.3V and unsoldered for 5V. But it might be the other way around :smiley:

2 Likes

Hi @Antun_Skuric,
Thank you so much - you’ve solved it! :joy: I don’t even want to guess how many hours I’ve spent pouring over codes and SPI timing diagrams, and all the time it was just a voltage level mismatch. That was a painful lesson!
But anyway, I’m really grateful - now I can get cracking with the actual interesting stuff again - I very much owe you one!

Hello Antun,
Maybe you can help me. I’ve been using the simpleFOC SPI magnetic sensor on Arduino UNO but then I move to Nucleo64 STM32F446 board. I’m not able to compilate the example program provided with simpleFOC library or the example posted just above. I get the following error messages:
C:\users\jda\Work Folders\My Documents\Arduino\libraries\Simple_FOC\src\drivers\hardware_specific\stm32_mcu.cpp: In function ‘int findIndexOfFirstPinMapEntry(int)’:
C:\users\jda\Work Folders\My Documents\Arduino\libraries\Simple_FOC\src\drivers\hardware_specific\stm32_mcu.cpp:409:10: error: ‘PinMap_TIM’ was not declared in this scope; did you mean ‘PinMap_PWM’?
409 | while (PinMap_TIM[i].pin!=NC) {
| ^~~~~~~~~~
| PinMap_PWM
C:\users\jda\Work Folders\My Documents\Arduino\libraries\Simple_FOC\src\drivers\hardware_specific\stm32_mcu.cpp: In function ‘int findIndexOfLastPinMapEntry(int)’:
C:\users\jda\Work Folders\My Documents\Arduino\libraries\Simple_FOC\src\drivers\hardware_specific\stm32_mcu.cpp:421:10: error: ‘PinMap_TIM’ was not declared in this scope; did you mean ‘PinMap_PWM’?
421 | while (PinMap_TIM[i].pin!=NC) {
| ^~~~~~~~~~
| PinMap_PWM
C:\users\jda\Work Folders\My Documents\Arduino\libraries\Simple_FOC\src\drivers\hardware_specific\stm32_mcu.cpp:422:45: error: ‘ALTX_MASK’ was not declared in this scope
422 | if ( pinName == (PinMap_TIM[i].pin & ~ALTX_MASK)
| ^~~~~~~~~
C:\users\jda\Work Folders\My Documents\Arduino\libraries\Simple_FOC\src\drivers\hardware_specific\stm32_mcu.cpp: In function ‘int findBestTimerCombination(int, int, int*, PinMap**)’:
C:\users\jda\Work Folders\My Documents\Arduino\libraries\Simple_FOC\src\drivers\hardware_specific\stm32_mcu.cpp:449:36: error: ‘PinMap_TIM’ was not declared in this scope; did you mean ‘PinMap_PWM’?
449 | searchArray[index] = (PinMap*)&PinMap_TIM[i];
| ^~~~~~~~~~
| PinMap_PWM
exit status 1
Erreur de compilation pour la carte Nucleo-64

Do you have any idea where it comes from?
Thank you and once again congrates for this fantastik work.
Kind regards

Hello. By reading again and again the SimpleFoc documentation, i’ve found another STM32 package for Arduino IDE compatibility (the previous one I was using was provided by ST). Now It works. Compilation is very sloooow, but at least I can now read my SPI sensor.