Lepton Deku mod

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

Awesome, glad it’s working out.

Correct, there are multiple solutions. The reason is that this allows you to fine tune the clocks of the various sub-systems of the MCU, as you found out by messing the UART timing.

Cheers,
Valentine

Just out of curiosity I tried one of the old sensor placements again (green dots on the diagram), and whaddya know, it works! The usual preferred direction spins about 5% faster, but both are equally smooth and have similar scatter in the sensor intervals (around 630-830us and 660-860us).

But I do find it interesting that the cyan placement resulted in 20-25% higher top speed in the good direction (500-700us). I wonder if it would be better to stick with how I have it now, or try the other again and advance the electrical angle by 60 degrees when going the bad direction. Maybe it will smooth out and run fast too. But it may be performing some crude field weakening, getting that high speed at the cost of requiring more current to produce the same torque.

On to tuning velocity mode. First attempt with P=0.2, I=2, D=0 from the example vibrated horribly and wouldn’t stop. Second attempt with my “wait for sensor change” code removed (so it’s running motor.move/loopFOC at the highest possible rate now) wouldn’t move at all for a while, then vibrated for a while, then went back to not moving.

EDIT: Yep, I’m pretty sure yellow/green is the correct sensor placement and cyan is field weakening in one direction. I tried converting those microsecond times into RPM, and got this (I’m running at 2 volts, so kv is RPM/2):
600us = 2778RPM = 1388kv
730us = 2283RPM = 1141kv
760us = 2193RPM = 1096kv

The motor was originally listed as 2300kv, and I rewound it from 29 turns delta to 36 turns wye, so it should theoretically be 2300 * 29 / 36 / sqrt(3) = 1070kv.

1 Like

@runger I’ve found a tricky bug.

float HallSensor::getVelocity(){
  if (pulse_diff == 0 || ((long)(_micros() - pulse_timestamp) > pulse_diff) ) { // last velocity isn't accurate if too old
    return 0;
  } else {
    return direction * (_2PI / (float)cpr) / (pulse_diff / 1000000.0f);
  }
}

Sometimes it returns infinity due to divide by zero, because pulse_diff is volatile and can be changed by interrupt. It needs to be cached in a local variable before checking if it’s equal to zero.

Thanks for finding this!
I’ve made a ticket: HallSensor.cpp volatile accèss bug · Issue #244 · simplefoc/Arduino-FOC · GitHub
and we’ll solve it for the next release…

While researching online, I did find companies that make bus bars suitable for automated assembly. None were cheap. Some can be soldered with paste in an oven and some used through holes.

Google “solderable bus bar” and you get lots of hits.

As for increasing current carrying ability by placing solder on traces. It does not work well. Generic solder has only on order of 10% the conductivity of copper. It can be as good as 40% if you use solder with a high silver content. But you likely want the lowest melting solder you can find for this

A simple solution, I have a vacuum tube based guitar amplifier made by Fender. It is a newer design that uses a PCB. Finder used a twisted pair of normal vinyl insulated wire for a high current path. It is loose with plenty of airspace under it.

Your solution seems ideal if you are making a few dozen PCBs,

I wanted what Tesla does? They make many thousands of cars with 100+ amp BLDC motor drivers.

The right solution for a few dozen boards will probably not be the right solution for thousands of boards. If you really are planning for ‘tesla’ scale you should probably start talks with a couple of PCB manufacturers and have your engineers hash out what each of them can do for your currents and at what price. About the wire solution as used in your amp, as far as I understand it (so check it with an actual engineer) these solder connection can degrade due to shock and vibe so it might no be the best solution for a robot.

I have also been thinking about high current capacity boards and what I can do within the limits of LCSC/JLCPCB (2oz copper). There is a nice thread at eevblog on high current capacity traces. I’m torn between soldering wire on top of the trace, getting plated slots and soldering in a ‘busbar’ (cut up some copper sheets myself) or just keeping the traces very short and having board to wire connections for each of the phases instead of using traces to distribute power.

With Tesla’s style of vertical integration, you could just make whatever size of bus bars you need in-house and load them on your pick-and-place machine :slight_smile:

One thing I’ve been thinking of trying is to replace those solder blobs with little copper blocks, maybe 1.5x1x3mm. I’m not sure if I could solder them with an iron, but probably could with low temp solder paste and a hot plate. I could even do the bars on the back at the same time, though I may have to give up the ceramic capacitors.

On the software side of things, I finally have angle control! 5 long years I’ve struggled with this. I feel like I need to write an academy award speech thanking everyone for your various contributions :stuck_out_tongue:

My velocity PID values are 0.1, 1.5, 0. Low-pass filter is 0.05, and angle P is 8. I still have a little more investigation to do because the voltage-based current limit doesn’t seem to be working, but that’s not essential.

One more trick I used is to restrict the target angle to cogging steps. Otherwise it jitters between them instead of settling.

target_angle = floor(target_angle / (_PI_3/motor.pole_pairs)) * (_PI_3/motor.pole_pairs);

Next I will try a 2208 motor that I already have set up with hall sensors which are probably in good locations, so hopefully it will be no trouble. That should allow testing up to 10-15 amps, and get a feel for how much the copper bars boost the current carrying capacity compared to one of the other boards without them.

EDIT: Turns out the current limit was working, I just wasn’t changing it on the fly properly. motor.current_limit only works before initialization, afterward you have to change motor.PID_velocity.limit. So now I can crank up the speed. I currently have it set to a fairly conservative current limit 1.5, voltage limit 4 and velocity limit 5000. I’ve also got it set up to skip calibration and initialize the sensor electric_rotations and electric_sector to the values I get when the servo tester is all the way to the left, so there’s no more jumping at startup. Now I can actually put this little linear actuator to use :slight_smile:

3 Likes

Did you have an angle sensor in this setup or only hall sensors? Because it should be able to settle at any position if you have an angle sensor.