Angle or Torque Control for a constantly updating target angle

Hello forum, first post here!
Hopefully this is in the right location.

First of all, big thanks for the simple FOC library and this community here! This would have been extremely challenging from scratch without it. I’ve learned so much and have been lurking for some time now without creating an account until now.

I have successfully create a working setup that can control Angle very rapidly without much bounce back or resonance.

I’m using a 7pair pole low cogging torque gimbal motor with an STM32G431RB Nucleo control board.
12V supply for the motor, using a STSPIN830 shield with Low Side Current sensing on 3x 330ohm shunt resistors. Low side current sensing is working well and I can precisely position the motor with FOC_Current and Angle.
The encoder is a 14bit magnetic spi encoder AS5047P and I’m using it in SPI mode.

After tuning like a crazed mad man it’s responding very well to commands in angle mode. Snapping to target position very quickly and precisely.

I’m developing in VSC with PlatformIO and I have been using the SimpleFOC studio for tuning (life saver).


Basic project description:
I’m developing a knob control interface. This will be receiving a string of values from an external source and then I’d like the motor to track the positions being received. When it’s not receiving values and updating positions I’d like it to be moveable by hand.

I’m a fairly novice programmer but I’m learning more everyday.

Sending a fast stream of changing values doesn’t seem to be handled well without me doing some unknown processing of the stream (I may need to filter or process them in some way?).

Main Question
Basically, I don’t know if the angle mode is best for this or if I’d have better luck chasing angles with torque mode and an algorithm that compares target value with motor.shaft_angle and chases that with torque mode?

I tried to do a simple set of for loops (see code) to have the have it update the target value but I’m not sure how to handle the constant change of input data (target values) and have the motor track the constant input of target values. I’d like to be able to send a sine wave of data points and have the knob move like a sine wave for example.

If I replace the for loops with motor.move(); and send an angle command it tracks beautifully. With the for loop I just get random spinning and oscillation. I don’t know that I have thought through how to properly handle a constantly changing input. Just switching gears to developing my application and trying to make sure I’m approaching this the best way.

Any thoughts or input is appreciated!

#include <SimpleFOC.h>

// MagneticSensorSPI(MagneticSensorSPIConfig_s config, int cs)
//  config  - SPI config
//  cs      - SPI chip select pin
MagneticSensorSPI sensor = MagneticSensorSPI(AS5047_SPI, PB6);

// BLDC motor & driver instance
// float target_current = 0;
BLDCMotor motor = BLDCMotor(7, 5, 255);
// BLDCDriver3PWM(pwmA, pwmB, pwmC, (en optional))
BLDCDriver3PWM driver = BLDCDriver3PWM(PA8, PA9, PA10, PB13, PB14, PB15);

LowsideCurrentSense current_sense = LowsideCurrentSense(0.33, 1.53, PA1, PB1, PB0); // IHM16M1 manual says op-amp gain is 1.53

// include commander interface
Commander command = Commander(Serial);
bool motorCommandReceived = false;
float previousTarget = 0;
const float stepSize = 4.71 / 128;

void doMotor(char *cmd)
{
  command.motor(&motor, cmd);
  motorCommandReceived = true; // Set the flag when the command is received
}

/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
   */
  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST);

  /** Initializes the RCC Oscillators according to the specified parameters
   * in the RCC_OscInitTypeDef structure.
   */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV6;
  RCC_OscInitStruct.PLL.PLLN = 85;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV8;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
   */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }

  /** Enables the Clock Security System
   */
  HAL_RCC_EnableCSS();
}

void setup()
{

  // monitoring port
  Serial.begin(115200);

  // enable more verbose output for debugging
  // comment out if not needed
  SimpleFOCDebug::enable(&Serial);

  sensor.clock_speed = 10000000;
  // initialise magnetic sensor hardware
  sensor.init();

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

  // link the motor to the sensor
  motor.linkSensor(&sensor);

  // driver config
  // power supply voltage [V]
  driver.voltage_power_supply = 12;

  // driver.init();
  // driver init
  if (!driver.init())
  {
    Serial.println("Driver init failed!");
    return;
  }
  // link driver
  motor.linkDriver(&driver);

  // link driver to cs
  current_sense.linkDriver(&driver);

  current_sense.init();
  // current sense init hardware
  if (!current_sense.init())
  {
    Serial.println("Current sense init failed!");
    return;
  }

  // link the current sense to the motor
  motor.linkCurrentSense(&current_sense);

  // aligning current sense
  current_sense.gain_a *= -1;
  current_sense.gain_b *= -1;
  current_sense.gain_c *= -1;

  // current_sense.skip_align = true;

  // aligning voltage
  motor.voltage_sensor_align = 5;

  // control loop type and torque mode
  motor.torque_controller = TorqueControlType::foc_current;
  motor.controller = MotionControlType::angle;
  motor.motion_downsample = 100.0;

  // velocity loop PID
  motor.PID_velocity.P = 0.09;
  motor.PID_velocity.I = 0.5;
  motor.PID_velocity.D = 0.001;
  motor.PID_velocity.output_ramp = 10000.0;
  motor.PID_velocity.limit = 18.0;
  // Low pass filtering time constant
  motor.LPF_velocity.Tf = 0.2;
  // angle loop PID
  motor.P_angle.P = 20.0;
  motor.P_angle.I = 0.0;
  motor.P_angle.D = 3.4;
  motor.P_angle.output_ramp = 10000.0;
  motor.P_angle.limit = 30.0;
  // Low pass filtering time constant
  motor.LPF_angle.Tf = 0.2;
  // current q loop PID
  motor.PID_current_q.P = 4.0;
  motor.PID_current_q.I = 0.0;
  motor.PID_current_q.D = 0.001;
  motor.PID_current_q.output_ramp = 5000.0;
  motor.PID_current_q.limit = 2.0;
  // Low pass filtering time constant
  motor.LPF_current_q.Tf = 2.0;
  // current d loop PID
  motor.PID_current_d.P = 4.0;
  motor.PID_current_d.I = 0.0;
  motor.PID_current_d.D = 0.001;
  motor.PID_current_d.output_ramp = 5000.0;
  motor.PID_current_d.limit = 2.0;
  // Low pass filtering time constant
  motor.LPF_current_d.Tf = 2.0;
  // Limits
  motor.velocity_limit = 30.0;
  motor.voltage_limit = 12.0;
  motor.current_limit = 1.0;
  // sensor zero offset - home position
  motor.sensor_offset = 0.0;
  // general settings
  // motor phase resistance
  motor.phase_resistance = 6.211;
  // pwm modulation settings
  motor.foc_modulation = FOCModulationType::SinePWM;
  motor.modulation_centered = 1.0;

  motor.useMonitoring(Serial);
  //  motor.monitor_downsample = 1;                                                            // set downsampling can be even more > 100
  motor.monitor_variables = _MON_CURR_Q | _MON_CURR_D | _MON_TARGET | _MON_VEL | _MON_ANGLE; // set monitoring of d and q currents

  // initialize motor
  if (!motor.init())
  {
    Serial.println("Motor init failed!");
    return;
  }
  // align sensor and start FOC
  if (!motor.initFOC())
  {
    Serial.println("FOC init failed!");
    return;
  }

  // set the initial motor target
  motor.target = 0; // Volts?

  // add target command M
  command.add('M', doMotor, "Motor");

  // add target command T
  // command.add('T', doTarget, "target current");

  Serial.println(F("Motor ready."));
  Serial.println(F("Set the angle target using serial terminal and command M:"));
  _delay(1000);
}

void loop()
{
  motor.loopFOC();

  // Cycle the motor target from 0 to 127 steps
  for (int i = 0; i <= 127; i++)
  {
    motor.target = i * stepSize;
    motor.move();
    _delay(100); // Adjust the delay as needed
  }

  // Cycle the motor target from 127 back to 0 steps
  for (int i = 127; i >= 0; i--)
  {
    motor.target = i * stepSize;
    motor.move();
    _delay(100); // Adjust the delay as needed
  }

  // real-time monitoring calls
  motor.monitor();
  // real-time commander calls
  command.run();
}

Here’s a photo of my test setup.
I modified a model I found here to fit my motor and encoder.

Can only add one photo to a post as a new user.

Maybe I can try to processing the incoming data
with a low-pass exponential moving average filter?

filtered_position = a * new_value + (1 - a) * previous_filtered_position

Then adjust “a” to control how quickly it filters the incoming values and tune it to the motor capabilities.

I’ll mess with it and see what I can get working.