Standalone Driver Test (no driver attached)

Hi all,

Love all the work being done here and the work from the community. (It might just save my group project.)

Complete beginner trying to test an STM32 F302R8 board with nothing attached.
I am effectively trying to ‘spoof’ the test as currently don’t have a driver or motor (being manufactured as we speak). I am therefore trying to test just the controller at the moment before progressing.

What inputs does the BLDCDriver3PWM test require to output the 3 PWMs (as if a driver were connected)?

Many thanks in advance, and apologies for the minimal knowledge!

Will

Hi @William_Cheese

A simple test could be to initialise the driver, and call

driver.setPwm(0.25,0.5,0.75)

You should them be able to verify the PWM waveforms on the pins using a logic analyser.
You should see nice PWM signals with 25%, 50% and 75% duty cycle.

You could then initialise the motor in open-loop mode, and see if PWM output occurs, but it can be hard to judge if the PWM corresponds to the correct commutation or not…

Also make sure you hit the correct IO pins for the STM32 motor control timers, for the STM32 F302R8 you should have at least these exposed:

BLDCDriver3PWM driver = BLDCDriver3PWM(PA8, PA9, PA10);

Without knowing the exact STM board (Nucleo part number, may be even a picture) it’s a little hard to provide more advise.

Cheers,
Valentine

This is the chip documentation, please refer to pages 35 to 47 for the pin assignments.

https://www.st.com/resource/en/datasheet/stm32f302r8.pdf

Thank you @Valentine and @runger very much for your swift help, and thanks in advance for any input on the problem I am having below…

I have now been able to run the standalone test and confirm the output of PWM on the pins you recommended (all on the same timer for my board). Amongst many problems, I was using the wrong board group so it flashed the code without an error but didn’t work. The board came as a part of the P-NUCLEO-IHM001 kit.

I am now wrestling with the encoder setup. I have an SSI RLS Orbis Magnetic Encoder and I am using the ‘generic sensor’ setup instructions. However, I am getting some very weird behaviour of the readings when implementing my code into the ‘generic sensor’ script.

This code is needed I believe as the STM32 board does not have SSI functionality and this is the only encoder I can use for our motor (picture of the CAD drawing below for interest). The lead time on an SPI version of this encoder is 6 weeks which is beyond the deadline for our project and I was advised by Reinshaw that is very susceptible to noise.

Here is the code I am using for reading the sensor in a standalone fashion and simply printing a value between 0 and 2 pi and its seems to work well.

int Data = 9;
int NUM = 0;

void setup() {
  // put your setup code here, to run once:
  RCC->AHBENR |= RCC_AHBENR_GPIOCEN;
  GPIOC->MODER = 0x55555555;
  GPIOC->OTYPER = 0x00000000;
  GPIOC->OSPEEDR = 0x55555555;
  //GPIOA->PUPDR = 0x64AAAAAA;
  GPIOC->PUPDR = 0xAAAAAAAA;
  pinMode(Data, INPUT);

  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
char bits[14];
 GPIOC->BSRR =  0x0000F000;
 for(int i = 0; i < 40; i++){
  delay(0.9);
 }
 for (int t = 0; t < 24; t++){
 //GPIOC->BSRR =  0xF0000000;
 GPIOC->BSRR =  0xF0000F00;
 for(int i = 0; i < 5; i++){
  delay(0.9);
 }
 //GPIOC->BSRR =  0x0000F000;
 GPIOC->BSRR =  0x0F00F000;
 for(int i = 0; i < 5; i++){
  delay(0.9);
 }
 if(digitalRead(Data) == 1 and t<14)
 {
   GPIOC->BSRR =  0x0F00F0F0;
  bits[t]=1;
       // GPIOC->BSRR =  0xFFFF0000;
 }
 else if(t<14)
 {
  //GPIOC->BSRR =  0x0000FFFF;
   GPIOC->BSRR =  0x0FF0F00F;
  bits[t]=0;
        //GPIOA->BSRR =  0x00000F00;
        //GPIOA->BSRR =  0xFFFF0000;
   }
 }
 NUM=0;
   for (int i = 0; i < 14; i++) {
   NUM=NUM+bits[i]*pow(2,(13-i));
  }
  Serial.println(NUM*6.283185/16383, 4);
  Serial.println("\n");
}

Here is an example output using that code on my STM32 board and turning the encoder quickly by hand

and finally here is the datasheet for that encoder:

Here is my implementation of my code into the generic sensor script;

/**
 *  Generic sensor example code 
 * This is a code intended to demonstrate how to implement the generic sensor class 
 */
#include <SimpleFOC.h>
int Data = 9;
int NUM = 0;

// sensor reading function example
//  for the magnetic sensor with analog communication
// returning an angle in radians in between 0 and 2PI
float readSensor(){
  
char bits[14];
 GPIOC->BSRR =  0x0000F000;
 for(int i = 0; i < 40; i++){
  delay(0.9);
 }
 for (int t = 0; t < 24; t++){
 //GPIOC->BSRR =  0xF0000000;
 GPIOC->BSRR =  0xF0000F00;
 for(int i = 0; i < 5; i++){
  delay(0.9);
 }
 //GPIOC->BSRR =  0x0000F000;
 GPIOC->BSRR =  0x0F00F000;
 for(int i = 0; i < 5; i++){
  delay(0.9);
 }
 if(digitalRead(Data) == 1 and t<14)
 {
   GPIOC->BSRR =  0x0F00F0F0;
  bits[t]=1;
       // GPIOC->BSRR =  0xFFFF0000;
 }
 else if(t<14)
 {
  //GPIOC->BSRR =  0x0000FFFF;
   GPIOC->BSRR =  0x0FF0F00F;
  bits[t]=0;
        //GPIOA->BSRR =  0x00000F00;
        //GPIOA->BSRR =  0xFFFF0000;
   }
 }
 NUM=0;
   for (int i = 0; i < 14; i++) {
   NUM=NUM+bits[i]*pow(2,(13-i));
  }
  return (NUM*6.283185/16383);
}

// sensor intialising function // pin assigment
void initSensor(){
  RCC->AHBENR |= RCC_AHBENR_GPIOCEN;
  GPIOC->MODER = 0x55555555;
  GPIOC->OTYPER = 0x00000000;
  GPIOC->OSPEEDR = 0x55555555;
  //GPIOA->PUPDR = 0x64AAAAAA;
  GPIOC->PUPDR = 0xAAAAAAAA;
  pinMode(Data, INPUT);
}

// generic sensor class contructor
// - read sensor callback 
// - init sensor callback (optional)
GenericSensor sensor = GenericSensor(readSensor, initSensor);

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

  // if callbacks are not provided in the constructor 
  // they can be assigned directly: 
  //sensor.readCallback = readSensor;
  //sensor.initCallback = initSensor;

  sensor.init();

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

void loop() {
  // iterative function updating the sensor internal variables
  // it is usually called in motor.loopFOC()
  sensor.update();

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

and here is an example of the output;

|13:16:50.365 -> 2.09|0.00|
|13:16:50.365 -> 2.09|0.00|
|13:16:50.419 -> 2.09|0.00|
|13:16:50.419 -> 2.09|0.00|
|13:16:50.419 -> 2.09|0.00|
|13:16:50.419 -> 2.09|0.00|
|13:16:50.465 -> 2.09|0.00|
|13:16:50.465 -> 2.09|0.00|
|13:16:50.465 -> 2.09|0.00|
|13:16:50.465 -> 2.05|-0.73|
|13:16:50.519 -> 2.04|-4.65|
|13:16:50.519 -> 2.03|-5.12|
|13:16:50.519 -> 2.03|-5.87|
|13:16:50.519 -> 1.95|-8.42|
|13:16:50.519 -> 5.77|64.44|
|13:16:50.566 -> 5.68|-61.44|
|13:16:50.566 -> 5.58|-59.88|
|13:16:50.566 -> 5.49|-60.20|
|13:16:50.619 -> 5.39|-58.37|
|13:16:50.619 -> 2.94|-36.83|
|13:16:50.619 -> 2.89|-29.24|
|13:16:50.619 -> 2.85|-29.24|
|13:16:50.666 -> 2.80|-29.01|
|13:16:50.666 -> 2.76|-29.60|
|13:16:50.666 -> 2.36|-29.16|
|13:16:50.666 -> 1.12|-26.02|
|13:16:50.719 -> 1.08|-23.27|
|13:16:50.719 -> 1.05|-22.47|
|13:16:50.719 -> 1.01|-23.24|
|13:16:50.719 -> 0.69|-20.99|
|13:16:50.766 -> -0.10|-16.81|
|13:16:50.766 -> -0.12|-13.12|
|13:16:50.766 -> -0.14|-12.36|
|13:16:50.819 -> -0.16|-11.89|
|13:16:50.819 -> -0.32|-10.25|
|13:16:50.819 -> -0.65|-7.11|
|13:16:50.819 -> -0.66|-5.60|
|13:16:50.866 -> -0.66|-5.10|
|13:16:50.866 -> -0.76|-4.08|
|13:16:50.866 -> -0.77|-2.68|
|13:16:50.919 -> -0.81|-1.08|
|13:16:50.919 -> -0.81|-0.24|
|13:16:50.919 -> -0.81|0.00|
|13:16:50.919 -> -0.81|-0.02|
|13:16:50.966 -> -0.81|0.00|
|13:16:50.966 -> -0.81|0.00|
|13:16:50.966 -> -0.81|0.00|
|13:16:50.966 -> -0.81|0.00|
|13:16:51.019 -> -0.81|0.00|
|13:16:51.019 -> -0.81|0.00|
|13:16:51.019 -> -0.81|0.00|
|13:16:51.019 -> -0.81|0.00|
|13:16:51.066 -> -0.81|0.00|
|13:16:51.066 -> -0.81|0.24|
|13:16:51.066 -> -0.81|0.01|
|13:16:51.066 -> -0.81|0.00|
|13:16:51.119 -> -0.81|0.00|

The issue here is that the position can take on a negative value. It also can continuously count up i.e. that multiple rotations create a number higher than 2pi.

To fix this I have tried various maths modulo operators and creating new variables with various maths libraries but cannot seem to find anything that works. I have also tried performing said operations in various places but the only one that ‘works’ is inside the print operation which will not work for the wider FOC operation of my motor

I was wondering if either of you has any experience with this or know of any fixes.

Many thanks again and sorry for the long post!!! Pictures of motor design from CAD are below. Being machined as I write this. Hope its interesting!

Hey @William_Cheese,
have you tried the normalisation function from the library itself?

You might need to include the foc_utils.h file in addition to the SimpleFOC.h

#include <SimpleFOC.h>
#include "common/foc_utils.h"

Thanks, @Antun_Skuric! I hadn’t noticed that function.

I tried this normalisation function in my code, as well as implementing the “fmod” modulo operator and other basic maths directly in my code. I should also add I tried many different methods of implementing these e.g. new variables for each stage of maths operations as well as different variable types for ‘fmod’ and ‘fmodf’ functions from the math.h library

However, I now think that the issue of the output from the sensor ‘counting up’ (not returning to 0 after 2pi) is being created elsewhere in the code.

The simplest version of my code that displays this incorrect behaviour is as follows;

/**
 *  Generic sensor example code 
 * This is a code intended to demonstrate how to implement the generic sensor class 
 */
#include <SimpleFOC.h>
#include "common/foc_utils.h"

int Data = 9; 
float NUM = 0.0;

// sensor reading function example
//  for the magnetic sensor with analog communication
// returning an angle in radians in between 0 and 2PI
float readSensor(){
  
char bits[14];
 GPIOC->BSRR =  0x0000F000;
 for(int i = 0; i < 40; i++){
  delay(0.9);
 }
 for (int t = 0; t < 24; t++){
 GPIOC->BSRR =  0xF0000F00;
 for(int i = 0; i < 5; i++){
  delay(0.9);
 }
 GPIOC->BSRR =  0x0F00F000;
 for(int i = 0; i < 5; i++){
  delay(0.9);
 }
 if(digitalRead(Data) == 1 and t<14)
 {
   GPIOC->BSRR =  0x0F00F0F0;
  bits[t]=1;
 }
 else if(t<14)
 {
   GPIOC->BSRR =  0x0FF0F00F;
  bits[t]=0;
   }
 }
  NUM = 0.0; 
   for (int i = 0; i < 14; i++) {
   NUM=NUM+bits[i]*pow(2,(13-i));
  } 
  NUM = ((NUM*_2PI/16383));
  Serial.println(NUM);
  return NUM;
}

// sensor intialising function // pin assigment
void initSensor(){
  RCC->AHBENR |= RCC_AHBENR_GPIOCEN;
  GPIOC->MODER = 0x55555555;
  GPIOC->OTYPER = 0x00000000;
  GPIOC->OSPEEDR = 0x55555555;
  GPIOC->PUPDR = 0xAAAAAAAA;
  pinMode(Data, INPUT);
}

// generic sensor class contructor
// - read sensor callback 
// - init sensor callback (optional)
GenericSensor sensor = GenericSensor(readSensor, initSensor);

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

  // if callbacks are not provided in the constructor 
  // they can be assigned directly: 
  //sensor.readCallback = readSensor;
  //sensor.initCallback = initSensor;
  sensor.init();
  Serial.println("Sensor ready");
  _delay(1000);
}

void loop() {
  // iterative function updating the sensor internal variables
  // it is usually called in motor.loopFOC()
  sensor.update();

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

and here is an example of the output after turning the encoder greater than one revolution;

image

It can be seen that the print function from inside the ‘readsensor’ function returns the current angle correctly whereas the printed value from the ‘sensor.getAngle’ function returns the current angle as well as the sum of the previous turns.

I am a beginner at coding so wasn’t able to make sense of much of the deeper library code but did try looking at these functions’ header files and C code to see what they do. I was not able to identify any possible issues though. Is my implementation of the class assignment correct for example? Is this even an issue for moving forward with this library, i.e. will code elsewhere later in the SimpleFOC implementation normalise this behaviour or will it cause further issues.

Any input on this would be extremely appreciated and thanks again for your input already!!!

Will

Hi @William_Cheese ,

I think your GenericSensor implementation isn’t quite correct…

  • the readSensor() function should return an angle between 0 and 2PI, the Sensor superclass will take care of counting the full rotations on this basis
  • the readSensor() function has to be fast – you can’t have a delay() in there. The readSensor() is called once per sensor.update() which is called at least once per FOC loop iteration. This iteration speed has to be fast! 1kHz should be considered minimum, and 10kHz would be better. So calling delay(0.9) 74 times adds up to a total of 66.6ms of delays, meaning your main loop is now running no faster than 15Hz - no way to do motor control at that iteration speed, that’s waaaay too slow!

So basically, if you can’t read the encoder any faster than this, then you can’t use it.
And when reading from the encoder you need to use some kind of MCU built-in peripheral, like the SPI or SERCOM/USART peripherals most MCUs have. Then you can do the communications in a way that is asynchronous to the main loop and doesn’t incur these delays.
As a general rule, you can’t “bit-bang” the communications in scenarios where you’re trying to do real-time control. Communications have to be off-loaded to the MCU’s peripherals, which can handle the protocols and their timings independently of the main process.

Your encoder can do SPI, SSI and Serial, all of which would be suitable, but probably SPI or SSI would be most simple to get working. However SSI is only 500kHz so I would go for the SPI protocol, which is 4MHz and should be fast enough even if making synchronous calls.

Thank you very much for your reply @runger !

I believe the timings are okay as the delay(0.9) isn’t actually a 0.9ms delay (I have previously used maths as a delay). This script runs much faster than it appears as when measured on an oscilloscope the whole exchange happens within 200us which would give us the 5khz needed. It can operate faster when running but printing the output slows it down.

Here is a picture of the oscilloscope output where the top yellow line is the controller output (to check that it matches the encoder for the 1st 14 bits) and the bottom green is the encoder output to the controller after the correct clock sequence is given.

Regardless our problem is still the issues with the counting up of multiple rotations of the encoder on the output when the function is called. The encoder itself was the only one they had in stock of this shaft size and is only SSI. A special order of an SPI version is a 6 week lead time.

Many thanks again!

Will

So 200µs/5kHz will be fine…

So then what remains is that readSensor() has to return 0 to 2PI… if that is the case, and sensor.update() is called regularly, then things should work.

If readSensor() is returning values outside the range 0 to 2PI, then all bets are off…

As for sensor.getAngle(), its return value is an absolute position in radians, including the full turns. See the Sensor.h class for comments describing the different functions.

        /**
         * Get current position (in rad) including full rotations and shaft angle.
         * Base implementation uses the values returned by update() so that the same
         * values are returned until update() is called again.
         * Note that this value has limited precision as the number of rotations increases,
         * because the limited precision of float can't capture the large angle of the full 
         * rotations and the small angle of the shaft angle at the same time.
         */
        virtual float getAngle();

So your output actually looks correct to me.

If you want to read just the shaft angle, with no full rotations and at better precision, use sensor.getMechanicalAngle()

Well that was a week spent trying to fix something that ain’t broke… If only I had found that Sensor.h file in the base_classes folder with all of those useful descriptions.

Yes @runger you are completely right the output is correct and the function sensor.getAngle was returning what it promised on the tin.

sensor.getMechanicalAngle works exactly as I thought sensor.getAngle was meant to work, returning a value between 0 and 2pi which returns to 0 after each rotation, and again works perfectly.

A small note in the “testing the sensor” step of the “getting started” section as well as the “generic sensor” setup page, stating that this function will count up full rotations might be a good idea.

Thank you very much again for your help you saved me many more hours of headache!!!

Will

1 Like