Lepton Deku mod

Except for some parasitic effects, current flows in MOSFET gates during rise and fall time only. Imagine the gate as a capacitor which you are charging or discharging. Once it is charged or discharged, the current is close to zero. This is also the cause why power consumption in MOSFETS is proportional to the switching frequency.

Be aware, that this is very different to bipolar transistors, where everything is current controlled (as opposed to voltage in MOSFETs)!

/Chris

Finally got the hall sensor problem sorted. I made the new linear actuator body with sensor pockets 1.5mm farther from the center, but it still had the same problem. I think the field induced in the stator by the rotor magnets was competing with the magnets themselves. The radial placement probably was indeed a problem, but the final fix was a shorter spacer between the bearing and rotor, putting the rotor magnets axially closer to the sensors as well (the stator lip almost rubs on the sensors now).

As specified by Valentine in the original Lepton v2 thread, I’ve soldered on a couple 470uF capacitors to get above 1000 total. And some skinny little battery wires for these first tests. Plenty for this little motor, but stress testing will need fat boys like the motor wires.

I’ve also added a servo tester as an easy way to command motor position without being connected to the computer.

Still one thing I forgot to do, which is to add a short wire connecting the high voltage rail to the mid voltage, which will otherwise be unpowered without the ST Link. But after that, motor testing begins tomorrow!

1 Like

First test is a fail, but not too surprising. And partially successful too because the motor did move a few steps of the calibration sequence before it emitted a small puff of smoke. Most likely there was some damage to the winding insulation either from machining out the original stator mount or subsequent handling.

I had checked for shorts and couldn’t find any, but considering how fast it happened and how cool everything still was, I doubt it was over-current. The original winding is 2300kv, 0.3 Ohm, but I had voltage limit set to 1 and current limit set to 2 using voltage-based current limiting.

The Lepton appears to be fine, so I will re-wind the stator for lower kv and try again. It looks like the original winding is 28awg delta, so if I use 30awg wye I should get around 850kv and 1.5 Ohms. Although my spool is just about empty so I’ll probably have to buy some more.

Almost working now! Turns out I did have enough wire left. And it seems the original winding was a bit finer than 28awg because it was 29 turns per tooth, so by calculation 46 turns of 30awg would be the same total mass of copper, but in reality I could only fit 40 turns, and it came out 15% heavier. Which is good, because that means I should have about 7% higher torque capacity than before, for much less than 7% higher total weight.

I did have a slight mishap, because that extra copper made the coils stick up higher and get scratched by the rotor. I coated them with shellac to repair the insulation, clamped against a piece of wood to squash them down, and then added more shellac for good measure.

Before the squashing, I used the original longer spacer and tested with a drone ESC which worked, and then sanded the spacer down to a medium length and tried the Lepton, which went through the calibration sequence just fine but still was not getting good sensor readings.

After squashing so I can use the short spacer, I get good sensor readings, but the motor still isn’t turning on command after calibration finishes. It does seem to have some holding torque, and twitches around a bit if I turn it by force. And everything stays cool, so that’s good.

I’m pretty sure the correct sensor placement for this winding scheme is to have them centered on 3 consecutive stator teeth, but just to be sure I tried rotating the stator by half a tooth so the sensors are half way between teeth. Made no difference.

Tomorrow I’ll try fiddling with settings. For one thing, I never updated the resistance and kv for the new winding.

1 Like

Still no success, but here’s an update on the struggle so far.

I’ve made some improvements to the test setup. The STLink now has its own 9 pin connector so I can swap between it and the OLED/servo tester cable more easily. The Lepton now has a short jumper soldered between the high voltage rail and VCC instead of that long wire I had before with a connector on it.

I reworked the rotor to properly solve the problems I had before. Machined off the cap, made a new one with more clearance for the coils and a longer shaft support to hold it more steady, and glued it to the magnet ring with epoxy. I also ground down the lip on the bottom edge so the magnets can get closer to the sensors without rubbing.

At one point I was going to try linear hall sensors out of desperation, but only got more trouble for my efforts. I set it up to use the SWD pins as analog inputs (due to that little mistake of not having two analog pins on the encoder connector), but then STM32CubeProgrammer couldn’t communicate with it anymore. Apparently the reset pin on the Chinese STLink doesn’t work. The official STLink-V3Minie is cheap enough that I decided to give it a shot, and it was able to connect under hardware reset as it should and get a new program uploaded. But I prefer the Chinese one for general use because the V3Minie doesn’t supply power.

I haven’t gotten back to testing the linear halls yet, but I’d prefer to use digital anyway. I set up a test where I could step slowly through the trapezoid120 commutation sequence and print out the corresponding hall state, and it appears that each trapezoid sector holds the motor right near a hall transition point. If I jiggle the rotor back and forth a bit, the hall state changes. And sometimes advancing the trapezoid sector does not change the hall state. This is what I had in mind when I tried rotating the stator by half a tooth before, and after drawing up a diagram I can see why it made no difference. The yellow dots are the original sensor locations, and green is offset by half a tooth. In either case, there’s one sensor centered on a red magnet. To get opposite state change timing, I need the same rotor position to have one sensor exactly half way between two magnets. So I machined another stator mount with sensors in the locations of the cyan dots, which are rotated half a rotor magnet (360/12/2 = 15 degrees) from yellow (which is also equivalent to rotating 5 degrees from green), and that seems to have worked. Now when stepping the motor in trapezoid mode, each sector holds strongly to the center of its corresponding hall state. I can wiggle the rotor back and forth a bit by hand and the hall state remains stable.

But closed loop control still doesn’t work well. I’m in voltage-torque mode with trapezoid modulation. At low voltage, the motor spins slowly. As voltage is increased, it continues spinning slowly but also vibrates, becoming quite violent at 2 volts (1.2 ohm resistance so ~1.7 amps). If held in the vise it’s enough to rattle the magnets against the stator. If held in my fingers I can barely keep a grip on it. The sensation is much like using a reciprocating saw.

So now I need to come up with some way to diagnose what’s happening.




1 Like

You need to hold the reset button down while uploading. This is called “connect under reset” in STM32 if you use the SWD pins for alternative I/O functions. When you press and hold the reset button the MCU enters a special programming mode and you can re-flash it regardless of what you programmed. This is a “last resort” method if you accidentally f****d up your code.

This is interesting. Thanks for the update.

Cheers,
Valentine

Dang, you’re right. I could have just wired up a manual reset button instead of buying the V3Minie. Oh well, I guess it’s nice to have a spare programmer.

For my first attempt at diagnosis, I’ve got it set up to record the history of hall state changes using the onSectorChange callback. It uses a circular array of 512 values, recording the sector and time interval since last change. I removed the OLED from the mix and added an FTDI adapter so I can dump the history to serial monitor on the computer.

But when I went to test it, now the motor spins pretty smoothly even at 2V. It was the time spent writing to the OLED that was causing the trouble!

Basically the OLED update was creating a minimum time interval between stator field changes. No problem at low voltage when the natural time between hall state changes was longer than the OLED time, but at high voltage the rotor would quickly advance and settle in place where the stator field was pulling, and then sit and wait while the OLED updates, then loopFOC would run and change the stator field, and the rotor would quickly jump to the next position, and so on. Hence the fixed rotation speed but increasing vibration with voltage.

So it’s much better, but still not fully smooth, and one direction is better than the other. The history plot is fairly noisy. Running in forward direction at 2V, the time intervals range from 575us to 1753us. Sector 3 seems to have the shortest interval, being at most 991us. Sector 2 seems to be the longest, with no entries below 1290us. The rest are mostly around 1000us, but still a lot of variation. Holding the motor in my fingers (not touching the rotor), the motion feels mostly smooth but with occasional skips.

Running in reverse direction, times vary from 707us to 3183us. The shortest time is from sector 1, but it also has entries as high as 1885us. The longest entry is from sector 0, but it has entries as low as 2136us. Holding in my fingers, the motion feels rough.

I’m pretty sure those outlying values are from the same two sensor changes, just showing as different sectors due to commutation direction. The placement of the sensors is about as perfect as it’s physically possible to get, but upon close examination it looks like one of them tilted by about 5 degrees when I was gluing them in place, so that’s probably what’s giving more time to one sector and less time to its neighbor. I’ll see if I can pop it out, scrape the glue off, and get it seated properly.

I’ve also made what I think is a more accurate sensor timing diagram. Energizing the red and blue coils should pull the rotor to this sort of position where two magnets are attracted to the two energized arms, and the idle green arms are pointed at gaps between magnets. This has the rotor turned 5 degrees from how I had it before, so now one cyan dot is centered on a magnet. That makes sense, because now that sensor won’t change state anytime soon, and the other two won’t change state either until the rotor has moved 10 degrees one way or the other.

1 Like

Ok, the tilted sensor was not the problem. All sensors are perfect as can be, and no change in performance.

CPU speed is still a problem. I added another variable to the history plot, which is the number of loopFOC calls since the last hall state change, and I’m missing a lot of updates. It looks like it takes about 1ms to run motor.move and loopFOC. But even when running around 500RPM (~3ms per sector), the intervals are not consistent like they should be. Loop count varies from 1 to 6, staying mostly in the 2 to 4 range.

It may just be a feedback effect due to the variable time delay between the sensor change and when the PWM output is changed. I need to get it so loopFOC is called ASAP after the sector change. I could try to put it in the sector change callback, but that’s during an interrupt and would probably cause trouble. Better to have the main loop mostly sit idle and wait for it. Maybe if no sensor change comes for 10ms or something, then go ahead and do a motor.move/loopFOC anyway.

I also need to make sure that I’m actually running at the maximum CPU speed. The datasheet says “up to 64MHz”, but I’m not sure if that’s the default or how to change it or read the current setting. I’ll go digging tomorrow.

EDIT: Found that F_CPU gives the current frequency, and it is 16MHz. Unsure whether it’s possible to increase it. Pretty sure it’s using the 16MHz HSI16 internal oscillator, which can only be further reduced by the HSIDIV prescaler. The datasheet says it can use an external oscillator 4-48MHz, but that still wouldn’t get to the full 64MHz, so hopefully there’s some other setting I haven’t found yet.

Are you sure of your assumption that the oscillator frequency equals the core frequency is correct?

No. But the name F_CPU certainly implies that it is. F_CPU is defined as SystemCoreClock, and if you look in the function SystemCoreClockUpdate in packages\STMicroelectronics\hardware\stm32\2.3.0\system\Drivers\CMSIS\Device\ST\STM32G0xx\Source\Templates\system_stm32g0xx.c it looks like it is adjusting for all the multipliers and dividers. It looks like the PLL (phase-locked loop) is what does frequency multiplication. Here’s my current diagnostic code:

  RCC_OscInitTypeDef osc;
  HAL_RCC_GetOscConfig(&osc);
  Serial.print("OscillatorType "); Serial.println(osc.OscillatorType);
  Serial.print("HSIState "); Serial.println(osc.HSIState);
  Serial.print("HSIDiv "); Serial.println(osc.HSIDiv);
  Serial.print("PLLState "); Serial.println(osc.PLL.PLLState);
  Serial.print("PLLSource "); Serial.println(osc.PLL.PLLSource);
  Serial.print("PLLM "); Serial.println(osc.PLL.PLLM);
  Serial.print("PLLN "); Serial.println(osc.PLL.PLLN);
  Serial.print("PLLP "); Serial.println(osc.PLL.PLLP);
  Serial.print("PLLQ "); Serial.println(osc.PLL.PLLQ);
  Serial.print("PLLR "); Serial.println(osc.PLL.PLLR);
  Serial.println();

  RCC_ClkInitTypeDef clk;
  uint32_t latency;
  HAL_RCC_GetClockConfig(&clk, &latency);
  Serial.print("SYSCLKSource "); Serial.println(clk.SYSCLKSource);
  Serial.print("latency "); Serial.println(latency);

And here’s what it prints out:

OscillatorType 15
HSIState 256
HSIDiv 0
PLLState 1
PLLSource 0
PLLM 0
PLLN 16
PLLP 0
PLLQ 0
PLLR 0

SYSCLKSource 0
latency 0

The relevant functions and settings are in packages\STMicroelectronics\hardware\stm32\2.3.0\system\Drivers\STM32G0xx_HAL_Driver\Inc\stm32g0xx_hal_rcc.h

RCC_SYSCLKSOURCE_HSI is 0, and RCC_PLL_OFF is 1, and RCC_PLLSOURCE_NONE is 0. so it’s currently using the 16MHz oscillator directly and PLL is turned off.

Can you increase it to 4 and check F_CPU again? This would add to 64.

Trying, but not succeeding. The multiplier PLLN has a comment saying it can be 8 to 86. First I tried leaving it at the default 16 and setting PLLM to RCC_PLLM_DIV4, which should give a total multiplication factor of 4. Result is the serial printing goes into garbled text and cuts off in the middle. Then I tried PLLN 8, PLLM DIV8 to give 16MHz, and same result. Same with PLLN 16, PLLP DIV16. It dies when the system clock source is changed to PLL by the call to HAL_RCC_ClockConfig. With that commented out, it runs like before with F_CPU reporting 16MHz (as I would expect).

   RCC_OscInitTypeDef osc;
  HAL_RCC_GetOscConfig(&osc);
  Serial.print("Osc Type "); Serial.println(osc.OscillatorType);
  Serial.print("HSIState "); Serial.println(osc.HSIState);
  Serial.print("HSIDiv "); Serial.println(osc.HSIDiv);
  Serial.print("PLLState "); Serial.println(osc.PLL.PLLState);
  Serial.print("PLLSource "); Serial.println(osc.PLL.PLLSource);
  Serial.print("PLLM "); Serial.println(osc.PLL.PLLM);
  Serial.print("PLLN "); Serial.println(osc.PLL.PLLN);
  Serial.print("PLLP "); Serial.println(osc.PLL.PLLP);
  Serial.print("PLLQ "); Serial.println(osc.PLL.PLLQ);
  Serial.print("PLLR "); Serial.println(osc.PLL.PLLR);
  osc.PLL.PLLState = RCC_PLL_ON;
  osc.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  osc.PLL.PLLM = RCC_PLLM_DIV8;
  osc.PLL.PLLN = 8;
  if (HAL_RCC_OscConfig(&osc)!= HAL_OK)
    Serial.println("HAL_RCC_OscConfig Failed");
  Serial.println();

  RCC_ClkInitTypeDef clk;
  uint32_t latency;
  HAL_RCC_GetClockConfig(&clk, &latency);
  Serial.print("SYSCLKSource "); Serial.println(clk.SYSCLKSource);
  Serial.print("latency "); Serial.println(latency);
  clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  if (HAL_RCC_ClockConfig(&clk, latency)!= HAL_OK)
    Serial.println("HAL_RCC_ClockConfig Failed");
  Serial.println();
  
  Serial.println(F_CPU);

I will try to reach out to STM and check about this. My assumption would be if we use the internal RC instead of an external oscillator, we should be able to get the chip to perform at 64mhz.

Cheers,
Valentine

It just occurred to me that it may only be the serial that’s failing due to changing CPU frequency after initialization. I tried calling Serial.begin after HAL_RCC_ClockConfig and initially got garbled text, but with baud rate 9600 it is working with PLLN 16, PLLM DIV4, which should be 64MHz. But F_CPU still reports 16MHz.

I also tried connecting the motor and it still goes through calibration. But it’s a bit weird. The PWM is in audible range. I turned my servo tester knob and the motor started spinning at a fairly high constant speed, and wouldn’t respond to the knob any further. Had to pull the plug to stop it.

@dekutree64

How are you setting the parameters, are you using the STM32Cube tool or you are just guessing? I’m a little baffled that your numbers can’t produce the frequency, which is telling me you are probably guessing?

Cheers,
Valentine

PS this is the 64 MHZ core solution for G031 running on internal HSI 16 MHz RC clock.

Yes, I’m just guessing at values.

I’ve just gotten back to working on it, and F_CPU is reporting 64MHz now via the OLED. I’ve been having trouble with the serial connection all day and now can’t get it to work at all, so that one time the 16MHz did show up after calling HAL_RCC_ClockConfig was probably a mistake of some sort.

So I’m pretty sure I’m running at full speed now, but still need to sort out the PWM frequency and servo tester reading issues. I would guess micros() is ticking 4x faster now, so the pulse duration from the servo tester looks wrong. Not sure about the audible PWM. I’ll dig into the SimpleFOC driver code and see how it’s ultimately set to the hardware and if it could be getting divided by 4 as a result of the changed SystemCoreClock.

This is my current configuration code:

  RCC_OscInitTypeDef osc;
  HAL_RCC_GetOscConfig(&osc);
  osc.PLL.PLLState = RCC_PLL_ON;
  osc.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  osc.PLL.PLLM = RCC_PLLM_DIV4;
  osc.PLL.PLLN = 16;
  HAL_RCC_OscConfig(&osc);

  RCC_ClkInitTypeDef clk;
  uint32_t latency;
  HAL_RCC_GetClockConfig(&clk, &latency);
  clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  HAL_RCC_ClockConfig(&clk, latency);

EDIT: Not much progress, but I did run a test where I print out millis()/1000 and micros()/1000000 and they tick once every 2 seconds, not 4 times per second like I expected. I tried setting CPU frequency to 32MHz and the result is the same.

1 Like

I think with the Lepton you are using a “Generic G0” board description? Or have you made a custom one?

If using the generic board descriptions, you have to supply your own clock setup function. Otherwise you end up with a very conservative clock configuration that doesn’t use what your CPU can do.

F_CPU should return the CPU clock speed in Hz, in my experience… so if you get 16MHz here, that’s what the CPU is running at and you can increase it by up to 4x :slight_smile:

Oh no, don’t do that! The STM32 clock configuration is more complex than the proverbial Gordian knot… As @Valentine showed in his screenshot, the way to make things work is this:

  • use STM32CubeIDE to create a new project for your exact MCU type
  • use the clock configuration screen like in Valentine’s screenshot to set up a valid configuration. The tool will help you and warn you about mistakes.
  • save the MCU configuration in CubeIDE, and choose “Yes” when it asks you about generating code.
  • it will generate a file in the Core/src/ directory of the project called “main.c”, and this will contain a function called “SystemClock_Config”
  • copy this function into your Arduino project, either into your .ino file or in its own .cpp file.
  • you don’t need to call the function, Arduino framework for STM32 will call it automatically
  • if using platformIO, make sure you have the option lib_archive = false in your platformio.ini

That should get you running with a working clock, but really there should be a board variant file for the Lepton which describes its setup details, then it will be easier to work with :slight_smile:

3 Likes

@runger

Thank you. We should probably create a FAQ about such issues because we keep seeing similar questions popping up and can save people a lot of time and frustration. They obviously have nothing to do with SimpleFOC, however, they would contribute to resolving certain “my performance is poor and my RPM is low” moments.

Cheers,
Valentine

PS.

@dekutree64 this is the file @runger was talking about, I generated it based on the configuration above:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;

I2C_HandleTypeDef hi2c1;
I2C_HandleTypeDef hi2c2;

SPI_HandleTypeDef hspi1;

TIM_HandleTypeDef htim1;

UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);
static void MX_I2C1_Init(void);
static void MX_I2C2_Init(void);
static void MX_SPI1_Init(void);
static void MX_TIM1_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_I2C1_Init();
  MX_I2C2_Init();
  MX_SPI1_Init();
  MX_TIM1_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {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 = 12;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV3;
  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();
  }
  /** Initializes the peripherals clocks
  */
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_I2C1|RCC_PERIPHCLK_ADC
                              |RCC_PERIPHCLK_TIM1;
  PeriphClkInit.I2c1ClockSelection = RCC_I2C1CLKSOURCE_PCLK1;
  PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_SYSCLK;
  PeriphClkInit.Tim1ClockSelection = RCC_TIM1CLKSOURCE_PCLK1;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief ADC1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_ADC1_Init(void)
{

  /* USER CODE BEGIN ADC1_Init 0 */

  /* USER CODE END ADC1_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC1_Init 1 */

  /* USER CODE END ADC1_Init 1 */
  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.LowPowerAutoWait = DISABLE;
  hadc1.Init.LowPowerAutoPowerOff = DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc1.Init.SamplingTimeCommon1 = ADC_SAMPLETIME_1CYCLE_5;
  hadc1.Init.SamplingTimeCommon2 = ADC_SAMPLETIME_1CYCLE_5;
  hadc1.Init.OversamplingMode = DISABLE;
  hadc1.Init.TriggerFrequencyMode = ADC_TRIGGER_FREQ_HIGH;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLINGTIME_COMMON_1;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */

  /* USER CODE END ADC1_Init 2 */

}

/**
  * @brief I2C1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2C1_Init(void)
{

  /* USER CODE BEGIN I2C1_Init 0 */

  /* USER CODE END I2C1_Init 0 */

  /* USER CODE BEGIN I2C1_Init 1 */

  /* USER CODE END I2C1_Init 1 */
  hi2c1.Instance = I2C1;
  hi2c1.Init.Timing = 0x10707DBC;
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Analogue filter
  */
  if (HAL_I2CEx_ConfigAnalogFilter(&hi2c1, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Digital filter
  */
  if (HAL_I2CEx_ConfigDigitalFilter(&hi2c1, 0) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2C1_Init 2 */

  /* USER CODE END I2C1_Init 2 */

}

/**
  * @brief I2C2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2C2_Init(void)
{

  /* USER CODE BEGIN I2C2_Init 0 */

  /* USER CODE END I2C2_Init 0 */

  /* USER CODE BEGIN I2C2_Init 1 */

  /* USER CODE END I2C2_Init 1 */
  hi2c2.Instance = I2C2;
  hi2c2.Init.Timing = 0x10707DBC;
  hi2c2.Init.OwnAddress1 = 0;
  hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c2.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c2.Init.OwnAddress2 = 0;
  hi2c2.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
  hi2c2.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c2.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c2) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Analogue filter
  */
  if (HAL_I2CEx_ConfigAnalogFilter(&hi2c2, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Digital filter
  */
  if (HAL_I2CEx_ConfigDigitalFilter(&hi2c2, 0) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2C2_Init 2 */

  /* USER CODE END I2C2_Init 2 */

}

/**
  * @brief SPI1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  /* SPI1 parameter configuration*/
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_HARD_INPUT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 7;
  hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
  hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */

}

/**
  * @brief TIM1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM1_Init(void)
{

  /* USER CODE BEGIN TIM1_Init 0 */

  /* USER CODE END TIM1_Init 0 */

  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  /* USER CODE BEGIN TIM1_Init 1 */

  /* USER CODE END TIM1_Init 1 */
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 0;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 65535;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.BreakFilter = 0;
  sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT;
  sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
  sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
  sBreakDeadTimeConfig.Break2Filter = 0;
  sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM1_Init 2 */

  /* USER CODE END TIM1_Init 2 */
  HAL_TIM_MspPostInit(&htim1);

}

/**
  * @brief USART2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART2_UART_Init(void)
{

  /* USER CODE BEGIN USART2_Init 0 */

  /* USER CODE END USART2_Init 0 */

  /* USER CODE BEGIN USART2_Init 1 */

  /* USER CODE END USART2_Init 1 */
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart2.Init.ClockPrescaler = UART_PRESCALER_DIV1;
  huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */

  /* USER CODE END USART2_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin : PC15 */
  GPIO_InitStruct.Pin = GPIO_PIN_15;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.Alternate = GPIO_AF1_OSC;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

@dekutree64

If you manage to get time to create a board variant for Lepton I will include it in a documentation about this board. Of course you get full credits, as you could tell I’m more interested in the hardware design and leave the software to the whoever is more into this.

Most people look at the STM32 clock configuration as some kind of a black-box mystery affaire and this would certainly help others. In fact most people don’t even realize their STM32 MCUs are running at sub-optimal frequency to begin with and not using them to the maximum.

Cheers,
Valentine

lol, it sure seems that way. This morning I installed STM32CubeIDE and generated the configuration as you described, and the result was slightly different than the one Valentine has since posted. Nonetheless they both produce 64MHz and work properly.

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();
  }
}

I think we can conclude that mine is 16Mhz * 8 / 2 = 64MHz, and Valentine’s is 16MHz * 12 / 3 = 64MHz. PLLM is set to 1 in both cases so no effect, and PLLP and PLLQ are set to 2 but apparently don’t apply to the main clock.

So that’s the CPU speed done. Motor runs smooth one direction, rough the other.

Then I added this bit of code before the call to loopFOC, so the PWM change is done in response to the sensor change rather than the two updating on independent schedules:

  int8_t oldHallState = sensor.hall_state;
  static uint32_t lastUpdateTime = 0;
  // Wait for sensor change, but give up after 10ms
  while(sensor.hall_state == oldHallState && millis() < lastUpdateTime + 10) {}
  lastUpdateTime = millis();

That improved the consistency of the sensor change times a little bit in smooth running direction (intervals are now mostly in the area of 600us, varying from a little under 500 to a little over 700), but rough direction is still rough. Times vary from about 1000 to 2300us. However there is a pattern to it which repeats every 6 electrical revolutions, which is one full turn of the rotor. And it looks like the speed is pulsating. Adding up the average sensor state times, the average electrical revolution times are 9600, 8000, 8000, 7700, 7800, 9000.

It may be another mechanical problem, because my modified rotor isn’t perfectly balanced. The gap between the sensors and the rotor lip varies by about 0.1mm throughout the turn. But it is strange that it would only affect it in one direction.

1 Like