STM32 stuck at MOT: Align sensor

Hi guys, thanks for this library! There’s so much here that’s useful.

I’m trying to get a BLDC motor spinning with this hardware:
STM32L452RE Nucleo-64 development board
STSPIN230 motor driver board
AS5147 magnetic sensor board

I’m using the minimal library, and compiled together code from the stm32_bldc_encoder and esp32_bldc_magnetic_spi example projects.

Oddly, I’ve never been successful programming the STM32 directly from the Arduino IDE, so my workflow has been to generate the binary from Arduino, and actually program the STM32 using STM32CubeProgrammer.

I compiled these tables from the datasheets:

Connector Pin Arduino Label Morpho Label Function
CN5 6 D13 PA5 SPI1_SCK
5 D12 PA6 SPI1_MISO
4 D11 PA7 TIM17_CH1 or SPI1_MOSI
3 D10 PB6 TIM16_CH1N or SPI1_CS

and

Connector Pin Signal Morpho Label Arduino Label
CN10 21 V_High PA9 D8
23 U_High PA8 D7
24 W_Low PB1 -
28 V_Low PB14 -
29 Enable PB5 D4
30 U_Low PB13 -
33 W_High PA10 D2

From that info, I changed these lines like so:

// SPI magnetic sensor instance
//MagneticSensorSPI sensor = MagneticSensorSPI(10, 14, 0x3FFF);
MagneticSensorSPI sensor = MagneticSensorSPI(PB6, 14, 0x3FFF);

// BLDC motor & driver instance
BLDCMotor motor = BLDCMotor(11);
//BLDCDriver6PWM driver = BLDCDriver6PWM(7, 2, 6, 3, 5, 4, 8);
BLDCDriver6PWM driver = BLDCDriver6PWM(PA8, PB13, PA9, PB14, PA10, PB1, PB5);

When I run the program though, this is the only output I get over serial:

MOT: Monitor enabled!
MOT: Initialise variables.
MOT: Enable driver.
MOT: Align sensor.

The serial interface is unresponsive to commands, and the motor does not move at all. Any idea what I’m doing wrong here? I assume that the program might be waiting for something over SPI, but I don’t know how to debug that if so.

Also, in case it matters, I have no idea how to tell Simple FOC which pins to use for SPI communications. The STM32L452RE has three separate sets of pins ready for SPI, so I picked the set labeled 1 out of 3.

I think you are trying to do too many things at once.

Before trying closed loop you should be comfortable that openloop movement of motor works and that you can do a sensor test. There are examples of both these.

I think you are going to need to get a bit familiar with your board variant files, particularly peripheralpins.c


If you scroll down that file you’ll see that PB13 is on TIM13 and PB1 is on TIM3. You need to uncommented/comment to move them over to TIM1.

Once you have the motor moving, then move on to SPI

I’m a little bit confused about coding in Arduino but downloading using cube. Have you considered Platformio? It’ll install the stm32 toolchain for you. It takes a bit of time to set up your first project but it’s great after that.

Thank you for the reply and the info! I take your point; I’ll try to do fewer things at once and see how I fare.

I have a couple known unknowns I’d like to ask, and probably have a couple unknown unknowns too that will appear.

First, I’m looking for stripped-down examples and found the openloop one: https://github.com/simplefoc/Arduino-FOC/tree/minimal/minimal_project_examples/atmega328_bldc_openloop I have not found a sensor test example though. Could you point me to that?

Second, what does TIM refer to? I assume from the context that it’s a designation closer to the actual pin on the processor, and the PB designation is closer to the header on the eval board? I am not sure where to go to learn more about this.

Also, I think I’ve found the peripheralpins.c file you referred to, but I am not sure because it differs a bit. Is this the file in question? https://github.com/stm32duino/Arduino_Core_STM32/blob/master/variants/NUCLEO_L452RE/PeripheralPins.c

Reason I’m asking is I found these lines, but neither of them refer to TIM13:

//  {PB_13, TIM1,   STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF1_TIM1, 1, 1)}, // TIM1_CH1N
  {PB_13, TIM15,  STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF14_TIM15, 1, 1)}, // TIM15_CH1N

I haven’t heard of Platformio yet. I’ll check it out. Thanks for the help so far!

I can confirm you have to use PlatformIO for SimpleFOC on STM32 at the moment. With ArduinoIDE it is not using the STM32-native code, and hence not using the timers, and your PWM won’t work as expected…

on the other hand, if you use PlatformIO, everything will compile correctly, and it should work “out of the box”.

1 Like

Sorry, you have the right file. It is TIM15 i meant to say

Yeh TIM is short for timers. In stm32 the pins and timers (there are lots) are mapped in a default way but can have ‘alternate functions/mappings’

In your case you want 6PWM pins, and each of those pins are mapped to a timer. Your job is to ensure that they are all on the same timer, I recommend TIM1.

4 of your pins are on TIM1 and 2 are not. So i was suggesting you comment lines 133 and 124 and then uncomment 132 and 123.

I’d only suggest using minimal example for crappy low memory mpus like arduino uno. Your board has lots of memory so you don’t have to go ‘minimal’.

Here is an example of sensor test

1 Like

Its the kind of thing that one winds up doing because ArdunioIDE is sooo confusing and bad… in theory it is possible to do it from ArduinoIDE, but it depends on board and bootloader and USB circuitry if it will work via USB. STLink usually works though. For people on Mac, since Catalina you have the added nightmare of having to manually “bless” (execute enable) all the binary utilities used by ArduinoIDE to do the uploading, since Catalina forbids their execution by default.
It’s horrible.

PlatformIO is really much better than ArduinoIDE, but maybe less accessible to beginners?

I have a question for you, @Owen_Williams - is it an accident that the STM32 support doesn’t compile under ArdunioIDE? I found that out last night while playing with the Storm32 board…
Normally I use Sloeber - Arduino development in Eclipse IDE. I’m not saying that’s better than PlatformIO, but I like Eclipse because I’m old and it is old, and we’ve done a lot of coding together :wink:
Anyway, Sloeber is “closer” to ArduinoIDE, since it uses the same board managers and board packages. So after activating the STM32 native code by changing some include logic, I was surprised to see that the code pretty much won’t compile at all… seems there’s a large amount of differences at the board level wiring code…
What gives? I thought PlatformIO was “Arduino Framework” (as well as others)? Is it basing it off of the mbed STM32?

It works fine in platformio. When you create a project in Platformio you decide whether to use arduino framework or something else (e.g mbed). If you do then you are using the same boardmanagers.

There are difference in .cpp vs .ino which only effects examples, i.e methods need to be “higher up” when used by other methods.

You also switch out Arduino menu configurations (e.g define type of serial) for platformio.ini config.

I’ve done a getting started with Platformio and simplefoc vid a few months ago.

1 Like

Ok, then I have gone wrong somewhere else… I’ll look into it some more. PlatformIO works like a charm with “Arduino Framework” selected. And it really only took 5 minutes to set up the project, renaming the file from .ino to .cpp was the only change I had to make. So I’d recommend using that anyways.

But in Sloeber (and I assume Arduino IDE is the same based on a couple of posts in the forum), if you select the native STM32 board definitions “Generic STM32F103R series” in my case) then you get “generic_mcu” and no STM32 hardware support at all. As a consequence nothing works since the generic PWM doesn’t work right on the STM32.

The #define used to select STM32 hardware support isn’t even defined… hence my questions about what the STM32 support in PlatformIO is based off, since it does not appear to be the same board definitions as ArdunioIDE is using…

Simplefoc is looking for STM32_DEF which is defined here:

Have you added the boardmanager json described in “getting stated” here

A lot of guides out there describe using the older maple core (roger clark melbourne), which (probably) won’t work with simplefoc.

Ok, I got it to compile under ArduinoIDE, thanks to your hints!

No idea how or why, but there were some older STM32 board definitions hanging around my “hardware” folder, without matching Board-Manager URL entries. Seems it was using those rather than the correct ones. Probably they are the older ones you mention. Thanks a lot, @Owen_Williams

Anyway, deleting those made things work as expected again. I wonder if that was the problem I had with the BluePill a few weeks ago? I shall have to try again.

Another question to the STM32 experts - the bootloader to use, is it still the rogerclarkmelbourne one? Or is there a newer bootloader as well?

I’ve never used rogerclark. Simplefoc uses HAL_TIM functions which i suspect are only part of the stm provided cores.

If you have a rogerclark and stm32 boardmanager json url configured in arduino then that would get confusing!

I put a link to a page (above) that has a getting started section for the ST cores.

I’d be curious if anyone has used rogerclark, I suspect it won’t work out of the box due to missing define and lack of HAL libs but as I’ve not tested I might be misleading you.

I think I’m still deeply confused about something. I did have old board-manager stuff in the hardware folder, which I got rid of and that fixed all my compilation problems in STM32. I really think that was the source of a lot of confusion and problems I’ve been having with the STM32 chips.

But there is also the question of the Bootloader? The URL you sent is for the framework/board manager files? Before I can do anything with the Storm32 board, I have to give it an “Arduino compatible” bootloader of some kind, I thought? And that’s what I was getting from here.

Is there a better bootloader I should be using? Which do you use, if I may ask?

I’ll try to describe how i think they work but I’m only beginning to understand them.

In general boatloaders describe where to load code from and where to listen for new code downloads.

The standard ST bootloader will load code from a well known location on flash and will also listen for new code over swd i.e using stlink protocol.

You may alternatively want to use the dfuse bootloader that supports dfu protocol. This one shows up as a mountable usb drive and you can copy your dfu binary using a file manager.

Or you could find (or write) a serial or can bus bootloader which can be programmed over serial or can bus.

Rogerclark does have its own bootloader and i think it has some unique features (a bit like dfuse?)

From my experience with platformio, I typically choose stlink protocol and attempt to upload ‘normally’. If that doesn’t work i use the boot/reset shuffle (hold boot button, press reset button, release boot) then try to upload again. This typically nukes custom bootloader with ‘something’ that works nicely. I suspect it is bootloader 2 described here

As an example. My vesc, when i bought it, showed up as a mountable device (custom bootloader?) but after I first programmed vesc with platformio i lost its ability to mount which to me indicates I’d switched bootloader.

Vesc tool (a gui) can then no longer upload (as it used usb) until i use stmprogrammer to download the original bootloader.

Finally a bootloader, i believe can load the binary from different places in flash and some boards have boot0 and boot1 pins which can be used to select which bootloader to run.

ST provide a read only system/default bootloader, so as long as boot0 and boot1 pins + swdio and swclk pins are available, i think it is impossible to ‘brick’ your mcu.

This is how I think it works but Reality might disagree :wink:

1 Like

Hi @Owen_Williams, thanks for your video on setting up PlatformIO. It was really helpful. I have it up and running and can program my board with it now.

I put getting the motor spinning on hold, and instead focussed on getting data from the magnetic sensor. Unfortunately, I’m stuck there now. My magnetic_sensor_spi_example.ino hangs on sensor.init(). I don’t know how to use the debugger yet, so I peppered in a bunch of Serial.println() statements to drill down further; it looks like execution actually hangs at spi_init( , , , ) in SPI.cpp.

I’m having a hard time determining if the example code uses SPI1. I assume it does, based on the CS pin (10 in the code vs D10 in the datasheet).

I found where SPI pins are declared in the board variant file. MOSI, MISO, and SCLK seemed correct, but CS differs from the datasheet. I tried to correct it on line 270 like so:

#ifdef HAL_SPI_MODULE_ENABLED
WEAK const PinMap PinMap_SPI_SSEL[] = {
  //{PA_4,  SPI1, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF5_SPI1)},
  {PB_6,  SPI1, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF5_SPI1)},

But, it was to no avail. I still don’t get any anything over serial after the spi_init( , , , ) in SPI.cpp. Any suggestions on where I should go from here?

Ok, update: I pulled the STSPIN230 hat off the Nucleo, and now code successfully falls through to the SPI test. I think there might have been a pin conflict between the hat and some part of the code. Not 100% sure yet what it was, but I suspect it has to do with the hat wanting to provide Back EMF data on one of the pins that is maybe used in the SPI scheme.

I say maybe because I still can’t tell which pins I need to be using for SPI. I tried looking in STM32CubeMX, and that gave me different pins than the Nucleo datasheet.

STM32CubeMX:

Nucleo datasheet:

Any idea what could account for the discrepancy there?

Either way, I have tried both sets of options for Chip Select and Clock pins, and neither of them changes the output that I get. Both times, I just get a whole lot of this:

6.28    0.00
6.28    0.00
6.28    0.00

I don’t think I’m getting any data from the sensor at all because that 6.28 number stays the same even when I pull the MISO line and power cycle the Nucleo.

The way I read it, the two PIN number 3s are on different connectors…
CN8 (that’s the connector) has pins 2 & 3 for ADC_IN
CN5 has pins 3-6 for SPI
But the MCU pins are PA1,PA4 for ADC_IN, and PA5, PA6, PA7, PB6 for SPI.
So no conflicts.

In STM32CubeIDE you can see alternative assignments for the pins by clicking on them while holding ctrl:

So PA5 is an alternative pin for SPI1_SCK. So looks like the SPI bus you want is indeed SPI1.

The CS line can be changed to any digital pin you like, normally. So if that’s the conflict it is easily solved. Based on the tables you post above, it does not look like there is any conflict on the MCU pins… You show all the driver board’s inputs as connecting to CN10 - I guess that’s the STSPIN’s connector? do you know what pins that is hitting on the Arduino headers?

Can you Serial.println(HAL_SPI_MODULE_ENABLED)
It should return 1.

I agree with @runger that you should be able to use any pin for CS.

At the moment neither you nor SimpleFOC are telling the mcu which pin to use for mosi, miso, sclk. And in addition the board variant has a bunch to choose from. Which will it chose?

So I’d either call the following:

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

before sensor.init()

or remove all the wrong options from PeripheralPins e.g.

#ifdef HAL_SPI_MODULE_ENABLED
WEAK const PinMap PinMap_SPI_MOSI[] = {
  {PA_7,  SPI1, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF5_SPI1)}
};
#endif

#ifdef HAL_SPI_MODULE_ENABLED
WEAK const PinMap PinMap_SPI_MISO[] = {
  {PA_6,  SPI1, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF5_SPI1)}
};
#endif

#ifdef HAL_SPI_MODULE_ENABLED
WEAK const PinMap PinMap_SPI_SCLK[] = {
  {PA_5,  SPI1, STM_PIN_DATA(STM_MODE_AF_PP, GPIO_PULLUP, GPIO_AF5_SPI1)}
};
#endif

@Owen_Williams Hmm. Calling Serial.println(HAL_SPI_MODULE_ENABLED) before and after sensor.init() just gives a blank line each time.

I added the lines you suggested above sensor.init() and I put a cheap little logic analyzer on PA5, PA6, and PA7. They stay tied high.

Edit: just for kicks, I removed everything else from PeripheralPins.c too. No change in behavior.


@runger sorry, I missed your comment my first go-around. CN5 and CN10 share some pins, but not all. Does that matter though? I’m not using an actual Arduino board, just the libraries. I assume that all pins are addressable even if they’re not mapped to a physical Arduino header. Here is the breakdown for the board for reference:

The pins that map onto the Arduino header I marked in the table in my first post. The ones that don’t map onto it, I marked with a -.

Here is the pin list for the STSPIN230:

I highlighted the one that I was concerned about conflicting. I thought this because CN10 pin 15 == PA7. I am not so sure about this now though because it seems like SPI might not even be enabled, and if it was enabled, it might not have been mapped to those pins.

I still think there was a conflict because execution would hang with the hat attached, but I have no idea where to look for what that conflict actually is.

Ok, I’ve made some progress. I created a sensor test using the minimal branch, uploaded it with the Ardunio IDE, and at least SPI seems to be working now. Does this data seem less than ideal though? Here’s a sample output while the magnet is sitting still:

1.55	0.00
1.51	-0.37
1.51	23.90
1.55	-23.65
3.90	23.65
1.53	0.00
3.90	0.00
1.53	0.00
1.55	0.03
1.53	-0.03
1.53	0.00

Moving the magnet through many rotations, I have found that real angle of the magnet does not seem to correlate with the number that sensor.getAngle() returns. Rotating it one direction through many turns increases the number up to a maximum of about 20, and rotating it the other way through many turns gradually decreases it deeply into the negatives (I reached -80 after many turns).

This leaves me wondering, does sensor.getAngle() return something like total radians the magnet has turned through? Now that I write that out, it makes sense, actually. I was just expecting it to return the angle mod 2pi radians.

Yes - sensor returns total radians - are you getting 6.28 rad per revolution? Looking at your data above it looks steady apart from those two odd 3.90 readings. The velocity (2nd column) looks a bit twitchy.

The angle should be super smooth when you turn the magnet, assuming it is properly being rotated at the center of sensor. If you are doing it by hand then those results above are not too unexpected.

We’ve had quite a few people who think they have purchased a diametric magnet but it is a normal magnet. This can give odd values like above. Try turning your magnet on its side and rotate it to see if you get better angle results, if you do then you have the wrong type of magnet.