Programming the Lepton from start to finish

In terms of anti-cogging, it remains to be seen what’s possible on the Lepton. At the moment our calibration code depends on in-memory tables, so it’s only really suitable for MCUs with a surplus of RAM. We’ll have see if it can fit in the Lepton.
Conceptually I think most solutions would in the end depend on some kind of look-up tables for their correction, so that’s something to consider when choosing a driver solution.

Yeah, I was afraid of that sort of thing. It’s important to have wiggle room.

Hey Runger, any success getting the AS5600 (I2C communication noise problem) or SC encoder driver working? I can buy a different encoder, but the SC encoder was identified as a particularly good one for future use, I assume that’s why Valentine recommended it.

As for the AS5600, if it can’t be gotten working due to the I2C communication issues, then we know for sure the board design needs to be changed.

Yeah, I2C is working… you have to add pull-up resistors externally as there is none on the Lepton (perhaps another very inexpensive improvement to consider?), but once I added these it worked fine.

I tested with an AS5048B as I don’t have a AS5600 lying around, but it works with the MagneticSensorI2C class too, so I don’t see any reason the AS5600 should not work either.

I’ll commit the code now.

 __ _                 _        ___  ___  ___
/ _(_)_ __ ___  _ __ | | ___  / __\/___\/ __\
\ \| | '_ ` _ \| '_ \| |/ _ \/ _\ //  // /
_\ \ | | | | | | |_) | |  __/ /  / \_// /___
\__/_|_| |_| |_| .__/|_|\___\/   \___/\____/
               |_|
   __            _                      ____
  / /  ___ _ __ | |_ ___  _ __   __   _|___ \
 / /  / _ \ '_ \| __/ _ \| '_ \  \ \ / / __) |
/ /__|  __/ |_) | || (_) | | | |  \ V / / __/
\____/\___| .__/ \__\___/|_| |_|   \_/ |_____|
          |_|

by Valentine, firmware version 1.0.0 by runger
MCU Speed: 64000000

SimpleFOC Lepton initialized.

Angle: 3.26
Its/s: 100
Angle: 3.26
Its/s: 101
Angle: 3.26
Its/s: 101
Angle: 3.26
Its/s: 101
Angle: 2.95
Its/s: 101
Angle: 1.50
Its/s: 101
Angle: -0.01
Its/s: 101
Angle: -0.31
Its/s: 101
1 Like

Ok awesome, thanks Runger, I do recall now that’s a common issue, but all the same no one else thought of it either. I don’t know why that always happens, everyone seems to thing someone else will add the resistors, lol. Classic. Definitely add that to the list of board modifications. The simplefoc shield must have had the resistors, and we should stick with that standard imo.

I will continue experimenting tomorrow. First, I need to ascertain if I can fit my code on the lepton, I don’t need much, just torque control with pwm direction and torque level in, and FG out (one pulse per rotation or whatever as an output so I can monitor fan rpm). Then I see what rpm I can get, and what it sounds like, and try to optimize if needed. I see how far I get, then decide if I should switch to a better processor or whatever. Also I will try to ascertain how long I can make the wires, I can put the lepton (or it’s successor) inside the fan, but for waterproofing I would prefer to put it farther away.

I got some good motors coming so I probably won’t need anti cogging, but it would be valuable in the future as low cogging motors are really hard to get. So I think a better MCU is definitely in the cards. Also I would like to use something that others are also using, and that means a better mcu is important.

Dang, so that was it! I figured the STM32 would use its internal pull-ups on the I2C lines. After prodding around with a multimeter, the little OLED I’ve been using has pull-ups on the back, so that explains why I didn’t have any trouble without them. This will save me some suffering when I get to my final design of a central processor communicating with a stack of Leptons via I2C.

It’s not so simple… in the end the pull-ups along with the bus capacitance are a property of the whole bus. Let’s say you put 4 devices and a controller on a bus, and each had 5K pull-ups… now as a bus you have 1kΩ effective resistance, which might be too low.
I like to put my pull-ups via solder-jumpers, which gives optionality.

I would say it can fit, but PWM-in isn’t the easiest “protocol” to implement, actually. What is the controlling side? Do you have another MCU or a RPi or something talking to it?

Internal pull-ups are usually quite weak, might be too much resistance for I2C… but one could definately try it if it can be configured - the Wire object doesn’t have such options, and indeed I am not sure MCUs support open-drain output mode (which I think is what I2C is using) and pull-ups (which are generally used in input mode) at the same time.

hm, maybe I should use the SPI port or UART instead. I would have to switch to platformIO for serial functionality, but I could do that. I was going to use pulsin, but it’s blocking apparently. On the RP2040 it’s easy, you can use the pwm peripheral or the PIO peripherals to measure input pulse lengths. Yeah I should probably use a data line. Just seemed more complicated but maybe it’s not.

It’s going to be a rbpi pico talking to it on the other side.
BTW do you have any hot ideas on getting the speed info while it’s in torque control mode? I should check the docs before asking maybe…

Since the I2C is multiplexed with other stuff I always assumed the pull-ups will be external or use internals.

Me too. Well, it does but they are I guess too weak. You need around 3k and the internal pull-ups are like 50k I think. Internals will work only with really low speed.

That’s why I didn’t add them at all because I don’t know how many you will chain up and this is better handled outside at the source, instead of hard-wiring resistors to the board. I tried to keep the board as simple as possible.

PS I was wrong, they are 40k internal pull-ups, not 50k, and yes they are on all pins, so I2C also has them. Just checked. Still 40k is way too low. @Anthony_Douglas you can still use the internal pull ups but you need to set your I2C to like 10k and do the same on the other side. The default is 100k. This is really tricky. Just add external.

I added 4.7kohm resistors, I2C appears to work now, pole pair check comes back a reliable… 5.17. Shit. It goes through initialization and then sits there oscillating by about one seventh of a rotation or something.

this is the code:

`#include <SimpleFOC.h>
#include “SoftwareSerial.h”
#include <Wire.h>
#define SS_USB_RX PA15
#define SS_USB_TX PA2
// magnetic sensor instance
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);

/** Initializes the RCC Oscillators according to the specified parameters

  • in the RCC_OscInitTypeDef structure.
    */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
    RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV1;
    RCC_OscInitStruct.PLL.PLLN = 8;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    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_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

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

MagneticSensorI2C sensor = MagneticSensorI2C(0x36, 12, 0X0C, 4);
// BLDC motor & driver instance
BLDCMotor motor = BLDCMotor(7);
BLDCDriver6PWM driver = BLDCDriver6PWM(PA8, PA7, PB3, PB0, PB6, PB1);
SoftwareSerial SoftSerialUSB(SS_USB_RX, SS_USB_TX);
// commander interface
void setup() {
Wire.setSDA(PB7);
Wire.setSCL(PB8);
Wire.begin();

SoftSerialUSB.begin(38400);
SoftSerialUSB.println(“serial test”);
// initialise magnetic sensor hardware
sensor.init();
// link the motor to the sensor
motor.linkSensor(&sensor);

// driver config
// power supply voltage [V]
driver.voltage_power_supply = 24;
driver.init();
// link driver
motor.linkDriver(&driver);

// set control loop type to be used
motor.controller = MotionControlType::torque;

// contoller configuration based on the control type
motor.PID_velocity.P = 0.2;
motor.PID_velocity.I = 20;
motor.PID_velocity.D = 0;
// default voltage_power_supply
motor.voltage_limit = 12;

// velocity low pass filtering time constant
motor.LPF_velocity.Tf = 0.01;

// angle loop controller
motor.P_angle.P = 20;
// angle loop velocity limit
motor.velocity_limit = 50;

// comment out if not needed
motor.useMonitoring( SoftSerialUSB);

// initialise motor
motor.init();
// align encoder and start FOC
motor.initFOC();

// set the inital target value
motor.target = 3;

SoftSerialUSB.println(“got this far”);

_delay(1000);
}

void loop() {

motor.loopFOC();

// iterative function setting the outter loop target
// velocity, position or voltage
// if tatget not set in parameter uses motor.target variable
motor.move();

}
`

I gotta get ready for bed now, I get tired and fry stuff by accident if I’m not careful… Will keep trying tomorrow. Ideas on what to try? I can search the forum for the symptoms and stuff I guess. The number of pole pairs is right, it’s 7 for this motor.

I think I will try to switch to platformio, I know nothing about it though so I sort of dread doing that.

Make sure the encoder is reporting correctly. Set target 0 and turn the rotor by hand, printing out the sensor reading a few times a second. If that’s good, try putting a sanity check in FOCMotor::shaftAngle() to make sure the low-pass filter isn’t getting fed a bad value. I can’t think of anything else. Torque mode really only reads the sensor and sets the phase voltage, so there’s not much that can go wrong.

Ok, that should be quite possible. I assume the Pico then takes inputs from the user using buttons or similar?

That’s no problem. As long as you’re in a closed-loop mode you can just call sensor.getVelocity() at any time. Or you can read motor.shaft_velocity to get the LPF filtered value.

yeah, that’s a no-go.

I would recommend the following, because it will give you good performance, good useability and will work based on existing code:

  • use Serial for debugging. if you can’t see any output, it will be sooooo much harder to debug everything
  • use SPI for the sensor - it will have the best performance, much better than I2C.
  • use I2C for comms with the Pico. I2CCommander code is existing, and it (just) fits in the 32kB Lepton.

I started putting it in the example, but then I tested the I2C sensor instead. I’m going to Germany now but when I get back on Sunday evening I’ll try to finish and test it.
Using I2C for inter-MCU comms would be classic anyway, and with I2CCommander you have a finished protocol & implementation to use.

I’m pretty sure that’s not the case. I still have to make the Arduino board files I promised you, but Serial should work normally with those. The reason I work in PlatformIO is that it is so much easier and faster than ArduinoIDE (at least for experienced developers) and also far more flexible in how you can configure it. So its much faster for me to test and iterate in PlatformIO. But now that Serial, SPI and I2C are all working I’ll try to set it all up in ArduinoIDE as well.

ok, awesom :). I promise I will contrib back to the community, I can pay for the next board buy and distribute boards to testers for free, as long as it’s applicable to my project as well (the lepton successor is looking promising).

In the meantime I will try to get this lepton to turn the motor so I can familarize myself a little more with the code base.

If we switch to a different MCU then your efforts will be specific to this board, I hope that’s ok for you. I really think we have to use a different mcu with more memor, sooner or later. But if it’s not too hard, someone will probably have a use for it in the future (aside from me, who would only be using it for code base testing as previously mentioned)

deku: I had checked the encoder was reporting, yes, it appeared to be ok. I can do a more thorough check. 5.17 vs 7?? wacky. Motor is connected fine, tested it all with multimeter, motor is undamaged. I don’t get why the code would work on the uno but not here. Well I’ll keep working on it. It takes 8 minutes to compile every time, and it’s not the antivirus…

:exploding_head:

Wow, that’s really long. I mean, ArduinoIDE is slow, and just in general the way the compile works is not the fastest… but on PlatformIO a complete build after clean takes something like 30-45s. An incremental build (where it only has to re-build your source files) takes about 10s.
And while I am sure ArduinoIDE would be slower, I would not expect it to take more than 2 minutes max.

What’s the hardware you’re doing this on? Is it a half-way current PC with SSD based storage? If so I would say there is some kind of problem, virus scanner is a good bet, or maybe the project somehow wound up on a network drive or something?

There used to be a Windows PowerTool called “FileMon” - with it you could trace file access - it can help uncover virus scanner problems…

As long as we stick to STM32 MCUs, most of the work is “transferrable”…
And I actually think the G071 version of the Lepton could be quite a successful format. I’m doing the G071 version at the same time (although I can’t test it right now).

A generous offer :slight_smile: but lets see, maybe the G071 version or even the G031 version will be enough for your needs? Although if you really want 3000RPM I suspect it won’t be…

My Arduino IDE builds take about 20 seconds. I’m using a single-core 4GB virtualized Windows Server 2016, with real-time antivirus turned off. Rebuilds are just a few seconds. On a laptop with Intel i9.

Well I don’t know if x or y will be enough, I haven’t been able to try anything at all.
I don’t see why 3000 rpm is a problem. The encoder doesn’t need to be read very fast, and there is no control loop that needs to be run at high frequency. As long as the waveform is smooth and on schedule there are no strict requirements with moving at 3000 rpm. The inertia of the rotor allows one to assume the position of the rotor just with time elapsed and the previous readings extrapolated, mostly.

The rpm doesn’t need to be regulated. Things only need to be approximate, as long as the phase of the waveform is fed to the motor with about the right angle relative to the rotor position, I don’t see any major problem arising.

I have found that the code I posted above only runs the motor control loop at abou1 kHz on this processor. I timed it using the following code, I just took a stopwatch and it takes 25 seconds to print ten times. 2000 loops per print statement, that’s like less than 1 khz. The clock speed has been set correctly. I don’t know what the problem is.

I also realized that apparently the system is trying to control the angle, to regulate it at 1.52 radians for some reason. It’s supposed to be in torque control mode ?? The oscillations are some kind of PID controller oscillation, apparently. If I hold the fan still they will stop, but will come back again if I disturb the rotor position by poking it.

I think I might have figured out what’s up to some degree. When I was using the uno I used the commander to get torque control mode enabled and then set the torque. That worked. The lepton can’t run the commander so I had to try to set it in the code. That’s not working for some reason.

The examples use the commander.

I try to look at the documentation and it’s just a pile of 404 error messages.

IDFK.

#include <SimpleFOC.h>
#include "SoftwareSerial.h"
#include <Wire.h>
#define SS_USB_RX  PA15
#define SS_USB_TX  PA2
// magnetic sensor instance
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);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV1;
  RCC_OscInitStruct.PLL.PLLN = 8;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  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_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

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

MagneticSensorI2C sensor = MagneticSensorI2C(AS5600_I2C);
// BLDC motor & driver instance
BLDCMotor motor = BLDCMotor(7);
BLDCDriver6PWM driver = BLDCDriver6PWM(PA8, PA7, PB3, PB0, PB6, PB1);
SoftwareSerial SoftSerialUSB(SS_USB_RX, SS_USB_TX);
// commander interface
void setup() {
Wire.setSDA(PB7); 
Wire.setSCL(PB8);
Wire.begin();
  
  SoftSerialUSB.begin(38400);
  SoftSerialUSB.println("serial test");
  // initialise magnetic sensor hardware
  sensor.init();
  // link the motor to the sensor
  motor.linkSensor(&sensor);

  // driver config
  // power supply voltage [V]
  driver.voltage_power_supply = 24;
  driver.init();
  // link driver
  motor.linkDriver(&driver);

  // set control loop type to be used
  motor.controller = MotionControlType::torque;

  // default voltage_power_supply
  motor.voltage_limit = 12;

  // velocity low pass filtering time constant
  motor.LPF_velocity.Tf = 0.01;


  // comment out if not needed
  motor.useMonitoring( SoftSerialUSB);

  // initialise motor
  motor.init();
  // align encoder and start FOC
  motor.initFOC();

  // set the inital target value
  motor.target = 3;

 SoftSerialUSB.println("got this far");
  
  _delay(1000);
}


void loop() {
for (int i = 0;  i<2000; i++){
  motor.loopFOC();

  // iterative function setting the outter loop target
  // velocity, position or voltage
  // if tatget not set in parameter uses motor.target variable
  motor.move();
  }
  SoftSerialUSB.println(sensor.getAngle());
  SoftSerialUSB.println("TEST SSERIAL_USB");
  SoftSerialUSB.println(F_CPU);
  sensor.update();
}

I don’t know what to do here. This is taking forever. I’m willing to hire someone to help me out but I’m no moneybags and that generally doesn’t work very well for me.

There is a lot of stuff wrong here. Arduino is extremely slow, there are errors in the library, the MCU is way too small, the only thing that’s going right is that the pcb guys did a great job making things perfectly. But we aren’t leveraging their magic very well.

Actually, I might be more comfortable paying someone to fix the documentation so I could have a hope of getting things to work myself, and if the errors are crippling, to fix the code base errors I cannot fix myself.

I thought it sounded like PID oscillation too, but the line motor.controller = MotionControlType::torque; should mean that none of the PID controllers are running. The velocity and angle PID updates are in BLDCMotor::move inside the switch(controller) statement. But MotionControlType::torque should just set voltage.q = target and constrain to voltage limit.

Well, the control loop is what generates that waveform… so 3000RPM on an 11PP motor is 33000 electrical RPM, or 550 electrical revolutions per second. Each electrical revolution should have the shape of a nice sine-wave. With 6 samples per electrical revolution you might get a waveform that resembles trapezoidal commutation, you can imagine that it would take several more samples to get a smooth sine wave. Maybe 20 per e-revolution?

So that would mean 550 x 20 = 11000 samples per second that the MCU has to output to PWM to generate a smooth sine wave. Since there is one sensor read and one PWM update per loop iteration (call to loopFOC()), this means you’ll need an MCU capable of reading a sensor and executing the control loop at 11kHz to do 3000RPM with smooth commutation.

This is only partly the processor. Check the loop speed again when using an SPI sensor…

as mentioned you need to read the sensor once per iteration to obtain the current rotor position. The I2C sensors limit the loop to 1-2kHz, because I2C is slow, and reading the sensor is taking most of the time.

Note that this also introduces large latencies into the system, i.e. the rotor has moved by the time you receive the value, the position used is actually out-of-date. This is always the case, but for I2C latencies are large, and the problem is worse the faster the motor turns.

The code you posted will use torque-voltage mode, and the target you set is in volts.

No, torque-voltage mode does not use any PIDs… what you describe sounds more like the sensor not working, or the pole-count being wrong…

Well, I can’t try SPI until I get a sensor and driver pair that works… But I will try to do that.

I tried the bluepill today, took about 7 hours. I tried to use darlington transistors for the output stage, but that apparently can’t work. I can’t get a PWM stage, so I can’t try that either.

The code that prints the sensor values appears to be fine. The process of estimating pole pairs seems to be seriously mucked up, it didn’t work with the uno and shield either, I remember that. I did eventually get it working though, the code for interfacing with the sensor was wrong I think. I can revisit that maybe.

I think that the commander was doing something that I am not getting right in the code somehow. That’s my latest hypothesis. The pole count is definitely right, it’s 7 pole pairs. The sensor with the uno and shield so it’s not the sensor.

What I’m saying about the processor and update speed is that the position doesn’t actually have to be updated that often for continuous rotation. Maybe that’s how SimpleFoc does it, but it doesn’t need to be that way in this type of situation. Maybe I can tweak things so it only updates the sensor a couple times per rotation, but updates the sine wave at higher frequency than that.

From my stuff with stepper motors, you need more like 254 voltage levels to get a low noise sine wave. That’s what the so called silent stepper drivers use. 64 does not appear to be enough. In our case there might be more smoothing due to inductance or something, hopefully. Another good argument for the better of the two MCU candidates, then.

Basically I guess I might need one additional layer between the sensor and the rest of SimpleFOC then, which fudges sensor readings to feed to the lower levels, based on the assumed known velocity of the rotor from the last two checks, and extrapolate from there during the actual rotation, if it is called at higher frequency than the sensor can be updated. The turning must go on, and smoothly and quietly as practical.

It happily and reliably stays stuck in the same position, and if I rotate it it always snaps back to the same position, within 0.05 radians or something. It’s definitely some kind of regulatory loop trying to keep the rotor in a fixed position. There are two, not one, weak areas where the force goes down when I rotate it, but only one of them is where the motor will come to rest.

I tried to look at the commander code to try to mimic what the commander does, but it is totally baffling.

Where exactly is that code for the lepton yo ucommitted? I want to comb through it and compare my code to see if it sheds light on where mine is going wrong.