Author Topic: Starting with STM32 (NUCLEO-L412KB)  (Read 17794 times)

0 Members and 1 Guest are viewing this topic.

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Starting with STM32 (NUCLEO-L412KB)
« on: April 29, 2024, 06:40:01 pm »
I have purchased a NUCLEO-L412KB board with a STM32L412KBU6 microcontroller.
After installing the IDE (STM32CubeIDE 1.3.0) I don't know where to start to do something as simple as turning on a led.
Is there any way to load a sample project to work with?
This is the page of the product in st.com: https://www.st.com/en/evaluation-tools/nucleo-l412kb.html

Product data brief: https://www.st.com/resource/en/data_brief/nucleo-l412kb.pdf
« Last Edit: May 24, 2024, 12:06:00 pm by Picuino »
 

Offline coromonadalix

  • Super Contributor
  • ***
  • Posts: 6981
  • Country: ca
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #1 on: April 29, 2024, 06:51:41 pm »
you could start with arduino v2.xx and install all stm32 librairies / boards ...   download and install "stm32programmer"  ,  not the same as stm32 cube

when you have all ready, com port and port type selected ....    you can use arduino examples on stm32  without problems

i use nucleo L432KC and nucleo144-F412ZG boards like this ...


you use the integrated st-link  of the nucleo boards for the programming interface
« Last Edit: April 29, 2024, 06:53:16 pm by coromonadalix »
 
The following users thanked this post: Picuino

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #2 on: April 29, 2024, 06:52:56 pm »
For a family like STM32L4xx they have a repository with HAL drivers and that repository includes lots of sample projects. For me the repository got installed when using CubeMX to create a new project. I mean in CubeMX you can select the board you have to create a new project for that board. Then the proper repository will be downloaded and installed on the local computer. After that you can select one of the sample projects.
I don't use their IDE but EWARM. Probably you can invoke CubeMX from inside the IDE.

Regards, Dieter
 
The following users thanked this post: Picuino

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #3 on: April 29, 2024, 07:53:00 pm »
you could start with arduino v2.xx and install all stm32 librairies / boards ...   download and install "stm32programmer"  ,  not the same as stm32 cube

when you have all ready, com port and port type selected ....    you can use arduino examples on stm32  without problems

i use nucleo L432KC and nucleo144-F412ZG boards like this ...

you use the integrated st-link  of the nucleo boards for the programming interface

When uploading the program to the board it asks me for the file STM32_Programmer_CLI.exe
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #4 on: April 29, 2024, 08:02:08 pm »
I have added to the PATH the path where the application STM32_Programmer_CLI.exe was located and now it does not give the previous error.
Now it gives this error:

Code: [Select]
Sketch uses 13016 bytes (2%) of program storage space. Maximum is 524288 bytes.
Global variables use 1300 bytes (0%) of dynamic memory, leaving 162540 bytes for local variables. Maximum is 163840 bytes.
      -------------------------------------------------------------------
                       STM32CubeProgrammer v2.16.0                 
      -------------------------------------------------------------------

ST-LINK SN  : 066AFF575056716587092913
ST-LINK FW  : V2J36M26
Board       : NUCLEO-L412KB
Voltage     : 3.26V
SWD freq    : 4000 KHz
Connect mode: Under Reset
Reset mode  : Hardware reset
Error: Unable to list supported devices
Error: Cannot identify the device
Failed uploading: uploading error: exit status 1
« Last Edit: April 29, 2024, 08:06:32 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #5 on: April 30, 2024, 03:29:46 pm »
I don't understand how it can be so complicated to just plug in a simple development board.
I have tried it with Arduino and it gives me error.

I have installed all the ST tools in the latest version (STMCubeMX, STMCubeIDE, STMProgrammer and libraries for NUCLEO-L412KB) and it doesn't work either.
The error that STMCube gives me is attached:

 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #6 on: April 30, 2024, 03:41:37 pm »
The board includes a STLink USB debugger. Is that device enumerated when you plug it into your workstation? I remember downloading and installing a driver for that.
After enumeration there are three USB interfaces:
the STLink debugger "STMicroelectronics STLink dongle",
a CDC serial device "STMicroelectronics Virtual COM Port (COMnn)" and
a memory device with two files on it (DETAILS.TXT and MBED.HTM)

Regards, Dieter
 

Online pcprogrammer

  • Super Contributor
  • ***
  • Posts: 4654
  • Country: nl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #7 on: April 30, 2024, 03:48:12 pm »
You can also try with openocd. Don't know about the availability and how to use it under Windows, but on Linux I find it quite easy.

Under Linux there is no need for a driver to use the ST-link, but there is the need for setting an udev rule for it to be able to use it. For openocd a target file is needed which you can most likely find on line or setup your self based on other examples.

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #8 on: April 30, 2024, 04:00:09 pm »
The board includes a STLink USB debugger. Is that device enumerated when you plug it into your workstation? I remember downloading and installing a driver for that.
After enumeration there are three USB interfaces:
the STLink debugger "STMicroelectronics STLink dongle",
a CDC serial device "STMicroelectronics Virtual COM Port (COMnn)" and
a memory device with two files on it (DETAILS.TXT and MBED.HTM)

Regards, Dieter

I'm working in Windows 10.
* I can see the memory device with two files on it.
* The port com6 is linked to the board. Arduino debug and STM32CubeProgrammer recognize the microprocessor and the board without problems.
* In the device manager I can see ST-Link Debug

Perhaps is a problem with the project example for the board. I have selected the board well (I have checked it several times), but perhaps the example project is missing something.

Regards.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #9 on: April 30, 2024, 04:27:55 pm »
As far as i know the cube IDE is based on eclipse and with eclipse i remember that one needs to configure the make and run steps. If it doesn't work automatically, maybe because you loaded source files instead of the project file. The project file should provide the required configuration. There should be a choice of "Release" and "Debug", at least that's what i have in EWARM.

Regards, Dieter
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #10 on: April 30, 2024, 04:46:00 pm »
I think I had opened it as File instead of as Project.
Now it does not give error.


The problem now is that the program bypasses Avast's antivirus shield.
It says that ST-LINK_gdbserver.exe is infected with IDP.Generic.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #11 on: April 30, 2024, 04:55:50 pm »
Could be a false positive.
I have scanned the file on the virustotal.com website and it does not give any infection results.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #12 on: April 30, 2024, 04:59:03 pm »
Ok it is running in debug mode.

Thank you very much.
 

Offline Sacodepatatas

  • Regular Contributor
  • *
  • Posts: 107
  • Country: es
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #13 on: April 30, 2024, 05:51:49 pm »
I think I had opened it as File instead of as Project.
Now it does not give error.


The problem now is that the program bypasses Avast's antivirus shield.
It says that ST-LINK_gdbserver.exe is infected with IDP.Generic.

Avast's heuristics thinks there is something wrong with the file because the program, in order to access to the debug probe, performs external calls to a DLL that is located elsewere from where they use to be (anywhere in the path variable, current directory, or system path), because such path to the DLL is passed as command line parameter from the IDE.
 

Offline eugene

  • Frequent Contributor
  • **
  • Posts: 497
  • Country: us
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #14 on: May 01, 2024, 05:28:32 pm »
The STM32 MOOCs are a pretty good place to start. Have you seen them?

https://www.st.com/content/st_com/en/support/learning/stm32-education/stm32-moocs.html
90% of quoted statistics are fictional
 
The following users thanked this post: dietert1, tellurium

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #15 on: May 01, 2024, 08:00:13 pm »
I did not know them. Thank you very much.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #16 on: May 02, 2024, 04:05:13 pm »
Another nice learning series is "FreeRTOS TUTORIALS" on youtube by ControllersTech. They show it using CubeIDE.

Regards, Dieter
 

Offline coromonadalix

  • Super Contributor
  • ***
  • Posts: 6981
  • Country: ca
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #17 on: May 03, 2024, 04:42:08 pm »
for the STM32_Programmer_CLI.exe error

You have to install them to their default locations,  and for Arduino IDE,  i use the full install,  not the portable one 

took me hours to figure this out,   no help from ST nor Arduino   |O |O

Once you select the right board type, programming interface, com port ...  and once you install the libraries related to some projects,  mostly the FreeRTOS  ...  Timer ...
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #18 on: May 13, 2024, 06:00:53 pm »
I have not been able to turn the LD3 led back on again. I don't know what I am doing wrong. The project compiles without errors and downloads to the board. A few days ago I managed to turn on the led, so the board is fine, but now I have not managed to do it again.

The project is generated with CubeMX and modified the main.c so that the led turns on and off, but it doesn't work.

I attach the main.c program:
Code: [Select]
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* 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 ---------------------------------------------------------*/
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_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_USART2_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
     HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_SET);
     HAL_Delay(100);
     HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);
     HAL_Delay(100);
  }
  /* USER CODE END 3 */
}

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

  /** Configure the main internal regulator output voltage
  */
  if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
  {
    Error_Handler();
  }

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

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

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

/**
  * @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.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};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

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

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : PA1 */
  GPIO_InitStruct.Pin = GPIO_PIN_1;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pin : LD3_Pin */
  GPIO_InitStruct.Pin = LD3_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LD3_GPIO_Port, &GPIO_InitStruct);

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* 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 */
  __disable_irq();
  while (1)
  {
  }
  /* 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,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

EDIT:
This is my code:
Code: [Select]
  /* USER CODE BEGIN WHILE */
  while (1)
  {
     HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_SET);
     HAL_Delay(100);
     HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);
     HAL_Delay(100);
  }
« Last Edit: May 13, 2024, 06:12:38 pm by Picuino »
 

Offline 8goran8

  • Contributor
  • Posts: 30
  • Country: cs
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #19 on: May 14, 2024, 07:11:47 am »
Just increase the value for HAL_Delay.
Try with HAL_Delay(1000);
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #20 on: May 14, 2024, 07:33:09 am »
Also i would recommend to keep those

/* USER CODE BEGIN .. */

/* USER CODE END .. */

blocks created by CubeMX intact and use them properly in order to not lose my code the next time CubeMX regenerates the project.

Last week i wrote a lot of code for a STM32L432 and found it nice to work with. I used C++ and didn't have to think about RAM or Flash limits - somewhat unusual when working with a MCU. Although it has a single precision FPU i used lots of doubles.

Regards, Dieter

 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #21 on: May 14, 2024, 07:53:36 am »
Yes, it is quite a powerful microcontroller in many ways. Much more than any of the ones I've used so far, including the 16bit ones.
I've had the STM32 board in a drawer for quite some time and I've finally decided to use it.
The problem is that, for some reason, it's hard for me to start something as simple as the "hello world" of microcontrollers (blinking led).

EDIT:
When I change the pin to a different pin, it remains as an input and not as an output. Of course the output voltage value does not change either.
« Last Edit: May 14, 2024, 08:03:24 am by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #22 on: May 14, 2024, 01:49:09 pm »
I have no idea what the problem was.
I have reinstalled all the IDE programs again and now it works fine. Finally I have the led blinking.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #23 on: May 14, 2024, 03:34:32 pm »
I am testing turning a digital output on and off. The program I am using is as follows.

Code: [Select]
while (0) {
   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET);
   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);
}

On the oscilloscope I get a 470kHz square signal, which seems to me too slow for an 80MHz microcontroller.
Is there any way to write directly to the registers to turn on and off a digital output?
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #24 on: May 14, 2024, 03:42:54 pm »
I answer myself:
Code: [Select]
while (1) {
       GPIOA->BSRR |= (1<<3);  // Set PA3 (Pin A2 in the Nucleo board)
       GPIOA->BSRR |= (1<<(3+16));  // Reset PA3
}

1880kHz output (200ns High and 300ns Low)

I think the clock is set too slow.


EDIT:
After configuring the internal clock to 80MHz, the output doesn't work.

That was the mistake I had. I don't know how to set the clock properly with the external oscillator.

« Last Edit: May 14, 2024, 03:48:27 pm by Picuino »
 

Online pcprogrammer

  • Super Contributor
  • ***
  • Posts: 4654
  • Country: nl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #25 on: May 14, 2024, 03:56:45 pm »
Get yourself the reference manual from the ST website and start reading about the clock configuration.

Bare metal programming these MCU's, for me at least, makes more sense then the whole HAL crap. Some will say there is the LL crap, but it all requires learning and reading, so why not go directly to the bottom.

It is all based on setting registers with values, much like you did in your post above.

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #26 on: May 14, 2024, 04:08:15 pm »
This configuration works (with internal oscillator):



Output: 4717MHz
Improved program (more clear):

Code: [Select]
while (1) {
   GPIOA->BSRR |= (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
   GPIOA->BRR |= (1<<3);  // Reset PA3
}
« Last Edit: May 14, 2024, 04:10:21 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #27 on: May 14, 2024, 04:16:16 pm »
The MX configurator works fine with internal oscillators, the problem I think is in the input frequency of the external oscillator. It is not well configured for some reason.

For now I don't need it, but later I will need a more stable oscillator than the internal oscillator of the microcontroller.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #28 on: May 14, 2024, 04:22:33 pm »
A jumper (SB17) must be soldered on the board to connect the oscillator to the input pin of the microcontroller.
« Last Edit: May 14, 2024, 04:30:32 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #29 on: May 14, 2024, 04:40:54 pm »
Jumper soldered with a small wire. External clock working properly.

4703 kHz output in PA3. Apparently it takes 17 cycles to turn on one pin, turn off one pin and jump. An Atmega would only take 4 cycles.

I'm going to investigate code execution in RAM which is apparently even faster.

EDIT:
https://community.st.com/t5/stm32-mcus/how-to-place-and-execute-stm32-code-in-sram-memory-with/ta-p/49528
 

Online pcprogrammer

  • Super Contributor
  • ***
  • Posts: 4654
  • Country: nl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #30 on: May 14, 2024, 04:51:59 pm »
Jumper soldered with a small wire. External clock working properly.

4703 kHz output in PA3. Apparently it takes 17 cycles to turn on one pin, turn off one pin and jump. An Atmega would only take 4 cycles.

I'm going to investigate code execution in RAM which is apparently even faster.

EDIT:
https://community.st.com/t5/stm32-mcus/how-to-place-and-execute-stm32-code-in-sram-memory-with/ta-p/49528

Has to do with the FLASH configuration. There is a setting for the number of wait states it uses when fetching from FLASH. In the STM32F103 it is set with the "FLASH->ACR" register.

Then there is the speed of the different busses in the ARM system to consider. The peripheral bus the GPIO port is connected to is of influence of how fast it can switch.

It is a completely different beast then the AVR.

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #31 on: May 14, 2024, 04:53:09 pm »
The program now generates an A3 output at a lower frequency (3634 kHz).
It does not appear that running in ram speeds up this small piece of code.

Code: [Select]
/* USER CODE BEGIN PD */

static void __attribute__((section(".RamFunc"))) toggle_a3(void)  {
   while (1) {
      GPIOA->BSRR |= (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
      GPIOA->BRR |= (1<<3);  // Reset PA3
   }
}


/* USER CODE END PD */
 

Online tellurium

  • Frequent Contributor
  • **
  • Posts: 285
  • Country: ua
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #32 on: May 14, 2024, 05:29:27 pm »
Make sure your linker script (.ld file) puts RamFunc into the .data
Open source embedded network library https://github.com/cesanta/mongoose
TCP/IP stack + TLS1.3 + HTTP/WebSocket/MQTT in a single file
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #33 on: May 14, 2024, 05:56:07 pm »
Only write to BSRR/BRR, don't read-modify-write.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #34 on: May 14, 2024, 06:01:43 pm »
Only write to BSRR/BRR, don't read-modify-write.

This code runs faster (8883 kHz) in flash and have the same effect:
Code: [Select]
void toggle_a3(void)  {
   while (1) {
      GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
      GPIOA->BRR = (1<<3);  // Reset PA3
   }
}
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #35 on: May 14, 2024, 06:04:23 pm »
Make sure your linker script (.ld file) puts RamFunc into the .data

.ld file:
Code: [Select]
  /* Initialized data sections into "RAM" Ram type memory */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
    *(.RamFunc)        /* .RamFunc sections */
    *(.RamFunc*)       /* .RamFunc* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */

  } >RAM AT> FLASH


I think so, but the speed is slower.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #36 on: May 14, 2024, 06:10:34 pm »
Ok I got it. It's slower the jump!

If I write the changes in a row, the output goes to 9630kHz:

Code: [Select]
static void __attribute__((section(".RamFunc"))) toggle_a3(void)  {
   while (1) {
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3

     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
  }
}

It appears that the minimum GPIO write time is about 50ns, even though the internal cycle time is 12.5ns.
« Last Edit: May 14, 2024, 06:13:04 pm by Picuino »
 

Online pcprogrammer

  • Super Contributor
  • ***
  • Posts: 4654
  • Country: nl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #37 on: May 14, 2024, 06:36:04 pm »
The core clock differs from the peripheral clocks. Look at the clock system in the reference manual. Some timers may run at the same speed, others at half or less. The ADC runs on another clock, etc.

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15775
  • Country: fr
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #38 on: May 14, 2024, 09:04:48 pm »
And even if the GPIO is clocked from the same clock as the core, there will be synchronization with the peripheral bus, which will lead to a 1-clock delay (at least), which is consistent with what you get here.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #39 on: May 14, 2024, 09:59:01 pm »
When looking at the disassembly i see that
Code: [Select]
PortConst->BSRR = PinConst;translates into three instructions. Plus the sync cycle gives the four cycles. Same as AVR except at 80 MHz.
If i turn on optimization, the disassembly shows it can be done in one instruction instead of three (producing constants from registers).

Regards, Dieter
« Last Edit: May 14, 2024, 10:05:12 pm by dietert1 »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #40 on: May 15, 2024, 01:34:33 pm »
Optimizations were disabled.
I have now enabled them for speed.

EDIT:
Now the output pin reaches 40MHz!!!
(a bit lower due to the jump)
« Last Edit: May 15, 2024, 01:40:05 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #41 on: May 15, 2024, 01:43:22 pm »
I am now going to test serial communications and add a read and write buffer to UART2.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #42 on: May 15, 2024, 04:25:05 pm »
It works.

usart.h:
Code: [Select]
/*
 * Function declaration
 */
#include <ctype.h>

void usart_init(void);
void usart_fputs(int8_t *str);
void usart_fputc(int8_t ch);
void usart_send(void);
void usart_receive(void);
int8_t usart_kbhit(void);
int8_t usart_fgetc(void);
int16_t usart_free(int16_t init, int16_t end);


usart.c:
Code: [Select]
#include "main.h"
#include "usart.h"


/*
 * Types and variables declaration
 */
#define USART_BUFF_SIZE (255)
#define USART_EOF (-1)

struct usart_buff_t {
    char buff[USART_BUFF_SIZE + 1];
    unsigned char init;
    unsigned char end;
};

struct usart_buff_t usart_buff_in, usart_buff_out;

/*
 * Inits USART2
 */
void usart_init(void) {
    USART2->CR1 |= (USART_CR1_RE | USART_CR1_TE);  // RX, TX enable.
    USART2->CR1 |= USART_CR1_UE;                   // USART2 enable.
    USART2->CR1 |= USART_CR1_RXNEIE;   // USART2 Receive Interrupt Enable.
    USART2->CR1 |= USART_CR1_TXEIE;    // USART2 Transmit Interrupt Enable.

    // Enable interrupt from USART2 (NVIC level)
    NVIC_EnableIRQ(USART2_IRQn);
}

/*
 * Save string to USART buffer output
 */
void usart_fputs(int8_t *str) {
    while (*str) {
        usart_fputc(*str++);
    }
}

/*
 * Save char to USART buffer output
 */
void usart_fputc(int8_t ch) {
    if (usart_free(usart_buff_out.init, usart_buff_out.end) == 0) {
        return;
    }

    usart_buff_out.buff[usart_buff_out.init] = ch;
    if (++usart_buff_out.init == USART_BUFF_SIZE)
        usart_buff_out.init = 0;
    USART2->CR1 |= USART_CR1_TXEIE;    // USART2 Transmit Interrupt Enable.
}

/*
 * Test if input buffer is empty
 * return 0 if buffer empty
 */
int8_t usart_kbhit(void) {
    if (usart_buff_in.init == usart_buff_in.end)
        return 0;
    return 1;
}

/*
 * Get char from USART buffer input
 */
int8_t usart_fgetc(void) {
    int8_t ch;
    if (usart_buff_in.init == usart_buff_in.end)
        return USART_EOF;
    ch = usart_buff_in.buff[usart_buff_in.init];
    if (++usart_buff_in.init == USART_BUFF_SIZE)
        usart_buff_in.init = 0;
    return ch;
}

/*
 * Return bytes remaining in rs232 buffer
 *   0 = buffer full
 *   1 = buffer with one space remaining
 */
int16_t usart_free(int16_t init, int16_t end) {
    end -= init;
    if (end >= 0)
        return (USART_BUFF_SIZE - 1) - end;
    return 1 - end;
}

/*
 * Send char from buffer to USART
 * Callable only by Interrupt Service Routine
 */
void usart_send(void) {
    if (usart_buff_out.init == usart_buff_out.end) {  // No more char to send
        USART2->CR1 &= ~USART_CR1_TXEIE;   // USART2 Transmit Interrupt Disable.
        return;
    }

    USART2->TDR = usart_buff_out.buff[usart_buff_out.end];
    usart_buff_out.end++;
    if (usart_buff_out.end == USART_BUFF_SIZE)
        usart_buff_out.end = 0;
}

/*
 * Receive char from USART to buffer
 * Callable only by Interrupt Service Routine
 */
void usart_receive(void) {
    // If the input buffer is full, the input character is discarded.
    if ((usart_buff_in.end + 1) == usart_buff_in.init
            || (usart_buff_in.end == (USART_BUFF_SIZE - 1)
                    && usart_buff_in.init == 0)) {
        uint8_t ch = USART2->RDR;
        return;
    }

    usart_buff_in.buff[usart_buff_in.end] = USART2->RDR;
    usart_buff_in.end++;
    if (usart_buff_in.end == USART_BUFF_SIZE)
        usart_buff_in.end = 0;

    // Error handling

}

/*
 * USART2 IRQ Handler
 */
void USART2_IRQHandler(void) {

    //check if we are here because of RXNE interrupt
    if (USART1->ISR & USART_ISR_RXNE) {
        usart_receive();
    }

    //check if we are here because of TXEIE interrupt
    if (USART1->ISR & USART_ISR_TXE) {
        usart_send();
    }
}


main.c:
Code: [Select]
/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2024 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

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

#include <stdio.h>
#include <ctype.h>
#include "usart.h"


/* 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 ---------------------------------------------------------*/
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_USART2_UART_Init(void);

/* USER CODE BEGIN PFP */

void blink_led(void);
static void __attribute__((section(".RamFunc"))) blink_PA3(void);
void hello_world(void);

/* 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_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 */
        usart_fputs("Hello World!\n");    // <<<------
        HAL_Delay(1000);

    }
    /* USER CODE END 3 */
}

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

    /** Configure the main internal regulator output voltage
     */
    if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1)
            != HAL_OK) {
        Error_Handler();
    }

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

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

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

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

/**
 * @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_9B;
    huart2.Init.StopBits = UART_STOPBITS_1;
    huart2.Init.Parity = UART_PARITY_EVEN;
    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.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
    if (HAL_UART_Init(&huart2) != HAL_OK) {
        Error_Handler();
    }
    /* USER CODE BEGIN USART2_Init 2 */
    usart_init();

    /* 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 };
    /* USER CODE BEGIN MX_GPIO_Init_1 */
    /* USER CODE END MX_GPIO_Init_1 */

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

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);

    /*Configure GPIO pin : PA3 */
    GPIO_InitStruct.Pin = GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /*Configure GPIO pin : LD3_Pin */
    GPIO_InitStruct.Pin = LD3_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(LD3_GPIO_Port, &GPIO_InitStruct);

    /* USER CODE BEGIN MX_GPIO_Init_2 */
    /* USER CODE END MX_GPIO_Init_2 */
}

/* 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 */
    __disable_irq();
    while (1) {
    }
    /* 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,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */



Outputs "Hello World!" every second with interrupts (cpu not blocked).

Routines discard received data or sended data when the input or the output buffer (255 bytes) is full.

 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #43 on: May 15, 2024, 05:52:05 pm »
The receive doesn't work.

It gets blocked in the interrupt routine, going in over and over again, even though I have completely reset USART1->ISR = 0;


EDIT:
I have written USART1 instead of USART2. It works now.
« Last Edit: May 15, 2024, 05:59:43 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #44 on: May 15, 2024, 07:02:52 pm »
This program send the characters received, with interrupts.

main.c:
Code: [Select]
/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2024 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <ctype.h>
#include "usart.h"

/* 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 ---------------------------------------------------------*/
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_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */

void blink_led(void);
static void __attribute__((section(".RamFunc"))) blink_PA3(void);
void hello_world(void);

/* 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_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 */
        while (usart_kbhit()) {
            char ch = usart_fgetc();
            usart_fputc(ch);
        }
    }
  /* USER CODE END 3 */
}

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

  /** Configure the main internal regulator output voltage
  */
  if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
  {
    Error_Handler();
  }

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

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

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

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

/**
  * @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.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */
    usart_init();

  /* 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};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

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

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : PA3 */
  GPIO_InitStruct.Pin = GPIO_PIN_3;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pin : LD3_Pin */
  GPIO_InitStruct.Pin = LD3_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LD3_GPIO_Port, &GPIO_InitStruct);

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

static void __attribute__((section(".RamFunc"))) blink_PA3(void) {
    while (1) {
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
    }
}

void blink_led(void) {
    HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_SET);
    HAL_Delay(1000);
    HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);
    HAL_Delay(100);
}

void hello_world(void) {
    uint8_t message[] = "Hello World!\n"; //Data to send
    HAL_UART_Transmit(&huart2, message, sizeof(message), 10); // Sending in normal mode
    HAL_Delay(1000);
}

/*
 * Put char in USART transmit register
 */
int __io_putchar(int ch) {
    while (!(USART2->ISR & 0x0080))
        ; // Wait for transfer buffer to be empty
    USART2->TDR = (ch & 0xFF);
    return ch;
}

/* 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 */
    __disable_irq();
    while (1) {
    }
  /* 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,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */


usart.h:
Code: [Select]
/*
 * Function declaration
 */
#include <ctype.h>

void usart_init(void);
void usart_fputs(char *str);
void usart_fputc(char ch);
void usart_send(void);
void usart_receive(void);
char usart_kbhit(void);
char usart_fgetc(void);
int16_t usart_free(int16_t init, int16_t end);


usart.c:
Code: [Select]
#include "main.h"
#include "usart.h"

/*
 * Types and variables declaration
 */
#define USART USART2
#define USART_BUFF_SIZE (250)
#define USART_EOF (-1)

struct usart_buff_t {
    char buff[USART_BUFF_SIZE + 1];
    uint16_t init;
    uint16_t end;
};

struct usart_buff_t usart_buff_in, usart_buff_out;

/*
 * Inits USART
 */
void usart_init(void) {
    USART->CR1 |= USART_CR1_RE;  // RX enable.
    USART->CR1 |= USART_CR1_TE;  // TX enable.
    USART->CR1 |= USART_CR1_UE;  // USART enable.
    USART->CR1 |= USART_CR1_RXNEIE;   // USART Receive Interrupt Enable.

    /* Disable the UART Parity Error Interrupt */
    USART->CR1 &= ~(USART_CR1_PEIE | USART_CR1_CMIE);
    /* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
    USART->CR3 &= ~(USART_CR3_EIE);

    // Enable interrupt from USART (NVIC level)
    NVIC_EnableIRQ(USART2_IRQn);
}

/*
 * Save string to USART buffer output
 */
void usart_fputs(char *str) {
    while (*str) {
        usart_fputc(*str++);
    }
}

/*
 * Save char to USART buffer output
 */
void usart_fputc(char ch) {
    if (usart_free(usart_buff_out.init, usart_buff_out.end) == 0) {
        return;
    }

    usart_buff_out.buff[usart_buff_out.init] = ch;
    if (++usart_buff_out.init == USART_BUFF_SIZE)
        usart_buff_out.init = 0;
    USART->CR1 |= USART_CR1_TXEIE;    // USART Transmit Interrupt Enable.
}

/*
 * Test if input buffer is empty
 * return 0 if buffer empty
 */
char usart_kbhit(void) {
    if (usart_buff_in.init == usart_buff_in.end)
        return 0;
    return 1;
}

/*
 * Get char from USART buffer input
 */
char usart_fgetc(void) {
    int8_t ch;
    if (usart_buff_in.init == usart_buff_in.end)
        return USART_EOF;
    ch = usart_buff_in.buff[usart_buff_in.init];
    if (++usart_buff_in.init == USART_BUFF_SIZE)
        usart_buff_in.init = 0;
    return ch;
}

/*
 * Return bytes remaining in rs232 buffer
 *   0 = buffer full
 *   1 = buffer with one space remaining
 */
int16_t usart_free(int16_t init, int16_t end) {
    end -= init;
    if (end >= 0)
        return (USART_BUFF_SIZE - 1) - end;
    else
        return 1 - end;
}

/*
 * Send char from buffer to USART
 * Callable only by Interrupt Service Routine
 */
void usart_send(void) {
    if (usart_buff_out.init == usart_buff_out.end) {  // No more char to send
        USART->CR1 &= ~USART_CR1_TXEIE;   // USART Transmit Interrupt Disable.
        return;
    }

    USART->TDR = usart_buff_out.buff[usart_buff_out.end];
    usart_buff_out.end++;
    if (usart_buff_out.end == USART_BUFF_SIZE)
        usart_buff_out.end = 0;
}

/*
 * Receive char from USART to buffer
 * Callable only by Interrupt Service Routine
 */
void usart_receive(void) {
    // If the input buffer is full, the input character is discarded.
    if (usart_free(usart_buff_in.init, usart_buff_in.end) == 0) {
        char ch = USART->RDR;
        return;
    }

    usart_buff_in.buff[usart_buff_in.end] = USART->RDR;
    usart_buff_in.end++;
    if (usart_buff_in.end == USART_BUFF_SIZE)
        usart_buff_in.end = 0;
}

/*
 * USART IRQ Handler
 */
void USART2_IRQHandler(void) {
    //check if we are here because of RXNE interrupt
    if (USART->ISR & USART_ISR_RXNE) {
        usart_receive();
    }

    //check if we are here because of TXEIE interrupt
    if (USART->ISR & USART_ISR_TXE) {
        usart_send();
    }

    // Error handling
}
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #45 on: May 16, 2024, 06:01:09 am »
Apparently you are having fun.

Anyway let's mention the HAL libraries. E.g. interrupt-based UART transmission is just few lines:
status=HAL_UART_Transmit_IT(handle,data,size);
and e.g. implement
void HAL_UART_TxCplt_Callback(handle);

For the interested, they have a manual about STM32L4 HAL libraries: UM1884, file: en.DM00173145.pdf
Should be easy to find. A huge file so one can only try and understand their naming conventions and then use what is needed. Looking at example projects also helps using existing driver libraries.

Regards, Dieter
 
The following users thanked this post: Picuino

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #46 on: May 16, 2024, 08:08:58 am »
Apparently you are having fun.
Yes  ;D

Thank you very much, it will be useful to get started in many cases.
On the other hand for now I prefer to use my own libraries and adjust the behavior by hand. For example, what should happen when a data arrives and the receive buffer is full, is the data discarded or do I store it in the buffer discarding the first one stored?

Later I will need to control the peripherals very precisely to synchronize them. I come from programming 8-bit and 16-bit micros where it is normal to directly control the registers and I like to have that kind of direct control of the microcontroller.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #47 on: May 16, 2024, 08:42:55 am »
The nice thing about the STM supplied driver libraries is they are open source. One can debug into the code and see what it does.
The host interface in my applications uses the HAL_UART_Receive_IT() function to start one initial receive request, with a buffer large enough for some messages, like 1 KByte. Then the main application loop polls the receive status and handles the input data. About 10 C statements (with interrupt disabled) extract the data from the receive buffer and let the receive operation continue.
As far as i remember the STM32 uart can detect a given character in the input stream, so one could get a callback once a complete line or packet was received. That may be more efficient.

Regards, Dieter
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
« Last Edit: May 16, 2024, 09:38:59 am by Picuino »
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #49 on: May 16, 2024, 10:42:47 am »
When i configure a project with CubeMX and generate the code, it copies all used driver sources and their inc files into my project folder. This copying may be optional. I guess the files come from the STM32L4 repository i mentioned above, in my case version 17.2 in C:\users\<userid>\STM32Cube\Repository.
During debugging there is no difference between those vendor supplied drivers and my own sources.

Regards, Dieter
 
The following users thanked this post: Picuino

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #50 on: May 16, 2024, 04:02:49 pm »
The advantage of HAL libraries is that you don't have to debug your own code.

Attached is the improved code after removing a bug in usart routines.

« Last Edit: May 16, 2024, 05:32:45 pm by Picuino »
 

Online pcprogrammer

  • Super Contributor
  • ***
  • Posts: 4654
  • Country: nl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #51 on: May 16, 2024, 05:55:42 pm »
The advantage of HAL libraries is that you don't have to debug your own code.

The disadvantage is that you have to debug someone else's code.  :-DD

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #52 on: May 16, 2024, 06:04:54 pm »
Yes, you are right.

The advantage of all this is that I have been able to test the STM32 debugging system and it is quite good. You can even see the assembler instructions.

On another subject, I tried to make a microsecond delay routine and it was almost impossible to get the times I wanted. If I set the loop counter to a low amount (16), the assembler becomes a series of instructions in a row with no loop and takes a short time. If I set a higher amount to the loop counter (20) then the loop does execute and takes too long.
I don't know how to tell the compiler not to optimize that piece of code, to get a stable timming:

Code: [Select]
void delay_us(uint16_t delay) {
    register uint16_t cycles;
    while (delay--) {
        cycles = 20;
        while (cycles--) {
            __asm__ __volatile__("");
        }
    }
}
« Last Edit: May 16, 2024, 06:06:46 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #53 on: May 16, 2024, 06:13:55 pm »
I have already tested the operation of the interrupts and the ADC, which works correctly.

Now it is time to move on to handling the DMA to capture data from the ADC to memory.
 

Online pcprogrammer

  • Super Contributor
  • ***
  • Posts: 4654
  • Country: nl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #54 on: May 16, 2024, 06:22:43 pm »
Yes, you are right.

The advantage of all this is that I have been able to test the STM32 debugging system and it is quite good. You can even see the assembler instructions.

On another subject, I tried to make a microsecond delay routine and it was almost impossible to get the times I wanted. If I set the loop counter to a low amount (16), the assembler becomes a series of instructions in a row with no loop and takes a short time. If I set a higher amount to the loop counter (20) then the loop does execute and takes too long.
I don't know how to tell the compiler not to optimize that piece of code, to get a stable timming:

Code: [Select]
void delay_us(uint16_t delay) {
    register uint16_t cycles;
    while (delay--) {
        cycles = 20;
        while (cycles--) {
            __asm__ __volatile__("");
        }
    }
}

To get this more or less right you can use inline assembler.

Code: [Select]
  uint32 loops = usec * 54;

  __asm__ __volatile__ ("1:\n" "subs %0, %1, #1\n"  "bne 1b":"=r"(loops):"0"(loops));

I use the above in my scope project and might not be valid for the STM32L412 due to it probably using thumb instructions, but the idea of passing parameters will be the same.

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #55 on: May 16, 2024, 06:43:57 pm »
You have given me a good idea. This code works, except for the value 1 which, I don't know why, produces no delay.


Code: [Select]
void delay_us(uint16_t delay) {
    delay *= 16;
    while (delay--) {
        __asm__ __volatile__("");
    }
}

EDIT:
That kind of inline assembler seems cryptic to me. I don't understand it. If at some point I have to do some function in assembler, I hope to be able to do it another way.

I would prefer this other way: https://community.st.com/t5/stm32cubeide-mcus/how-to-run-assembly-code-in-stm32-cube-ide-nucleo-f446re/td-p/139119
« Last Edit: May 16, 2024, 06:48:08 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #56 on: May 16, 2024, 06:53:00 pm »
Code: [Select]
__asm__ __volatile__ (
   "1:\n"
   "subs %0, %1, #1\n"
   "bne 1b"
   :"=r"
   (loops)
   :"0"
   (loops)
);

What value do %0 and %1 take?

What is "=r"?

EDIT:
* http://www.ethernut.de/en/documents/arm-inline-asm.html
* https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
* https://gcc.gnu.org/onlinedocs/gcc/extensions-to-the-c-language-family/how-to-use-inline-assembly-language-in-c-code.html
« Last Edit: May 17, 2024, 07:06:32 am by Picuino »
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15775
  • Country: fr
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #57 on: May 16, 2024, 10:04:48 pm »
The advantage of HAL libraries is that you don't have to debug your own code.

The disadvantage is that you have to debug someone else's code.  :-DD

Exactly. The advantage of using vendor HALs IMO is definitely, and absolutely not that.
It's getting started in a much shorter amount of time (usually), and having reasonable portability between MCUs of the same series, or even among different series of the same vendor. Which is a plus.

If you don't care about those points or can afford not to care, then they are just a waste of time, especially in the long run. And in some fields, having to deal with third-party code that doesn't follow your own set of rules is also a problem.

At least, with ST, the HAL is entirely provided as source code, so you can audit it. They claim compliance with MISRA, which is supposed to (at least partially) adress my last point above, but they comply in a way that makes the code very bloated and not pretty to read. So, yeah.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #58 on: May 16, 2024, 11:04:36 pm »
Probably there has been some development going on and STM32 Cube improved in comparison to the first years. If you search the web for HAL problems, a lot of the threads in their online forum date from 2017 or 2018.
When i look into HAL sources, most of the time it happens in order to avoid searching the MCU reference manual. It's something nice to have.

Regards, Dieter
 

Online pcprogrammer

  • Super Contributor
  • ***
  • Posts: 4654
  • Country: nl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #59 on: May 17, 2024, 05:40:28 am »
Code: [Select]
__asm__ __volatile__ (
   "1:\n"
   "subs %0, %1, #1\n"
   "bne 1b"
   :"=r"
   (loops)
   :"0"
   (loops)
);

What value do %0 and %1 take?

What is "=r"?

EDIT:
http://www.ethernut.de/en/documents/arm-inline-asm.html

Ah you found the manual. I wrote that code based on an example and did not look into it that deep. Needed it working fast and it did. Never looked back on it.  :)

Most of the time I don't need inline assembler.

Online pcprogrammer

  • Super Contributor
  • ***
  • Posts: 4654
  • Country: nl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #60 on: May 17, 2024, 05:46:07 am »
Probably there has been some development going on and STM32 Cube improved in comparison to the first years. If you search the web for HAL problems, a lot of the threads in their online forum date from 2017 or 2018.
When i look into HAL sources, most of the time it happens in order to avoid searching the MCU reference manual. It's something nice to have.

Regards, Dieter

I did play with it in the beginning and most of the things I tried (USB related) did not work. Searching through the code is a drag. I rather read the reference manual, even though they are not always that easy to grasp either. So maybe it improved by now, but I got used to doing it bare metal and don't need the portability of it either.

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #61 on: May 17, 2024, 07:50:30 am »
Last year i got the USB stack working for CDC within some hours after using CubeMX to prepare everything. The only mod i did some days later was a few lines of C to provide re-enumeration at each debugger launch. Something described in the web. Otherwise one had to pull and re-insert the plug each time.
That USB solution has been working flawlessly since then.

Regards, Dieter
 

Online pcprogrammer

  • Super Contributor
  • ***
  • Posts: 4654
  • Country: nl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #62 on: May 17, 2024, 08:42:02 am »
Last year i got the USB stack working for CDC within some hours after using CubeMX to prepare everything. The only mod i did some days later was a few lines of C to provide re-enumeration at each debugger launch. Something described in the web. Otherwise one had to pull and re-insert the plug each time.
That USB solution has been working flawlessly since then.

Regards, Dieter

Ah well, I wrote my own USB implementation and use that for my projects. Even wrote my own Linux driver for a specific application. Interesting learning experiences.

USB does not like interruptions, that is for sure. Found that you can't debug the USB driver code as it will kill the connection every time you halt it. Used wireshark on the host PC to do the development.

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #63 on: May 17, 2024, 02:56:49 pm »
Could someone please let me have a very simple example of how to run the ADC with DMA?
My example does not work.

Code: [Select]
/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2024 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <ctype.h>
#include <uart.h>
#include "adc.h"

/* 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;
DMA_HandleTypeDef hdma_adc1;

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_DMA_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_ADC1_Init(void);
/* USER CODE BEGIN PFP */

void blink_led(void);
void blink_PA3(uint16_t microseconds);
void delay_us(uint16_t delay);

/* 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_DMA_Init();
    MX_USART2_UART_Init();
    MX_ADC1_Init();
    /* USER CODE BEGIN 2 */

    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */

    uint16_t adc_buf[1024];
   
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*) adc_buf, 512);

    while (1) {
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */

        // printf("ADC=%d\r\n", (int) adc1_read());
        HAL_Delay(1);
        // blink_PA3(10);
    }
    /* USER CODE END 3 */
}

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

    /** Configure the main internal regulator output voltage
     */
    if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1)
            != HAL_OK) {
        Error_Handler();
    }

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

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

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

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

/**
 * @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_MultiModeTypeDef multimode = { 0 };
    ADC_ChannelConfTypeDef sConfig = { 0 };

    /* USER CODE BEGIN ADC1_Init 1 */

    /* USER CODE END ADC1_Init 1 */

    /** Common config
     */
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV1;
    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.ContinuousConvMode = ENABLE;
    hadc1.Init.NbrOfConversion = 1;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc1.Init.DMAContinuousRequests = ENABLE;
    hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
    hadc1.Init.OversamplingMode = DISABLE;
    if (HAL_ADC_Init(&hadc1) != HAL_OK) {
        Error_Handler();
    }

    /** Configure the ADC multi-mode
     */
    multimode.Mode = ADC_MODE_INDEPENDENT;
    if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK) {
        Error_Handler();
    }

    /** Configure Regular Channel
     */
    sConfig.Channel = ADC_CHANNEL_10;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
    sConfig.SingleDiff = ADC_SINGLE_ENDED;
    sConfig.OffsetNumber = ADC_OFFSET_NONE;
    sConfig.Offset = 0;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
        Error_Handler();
    }
    /* USER CODE BEGIN ADC1_Init 2 */

    /* USER CODE END ADC1_Init 2 */

}

/**
 * @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.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
    if (HAL_UART_Init(&huart2) != HAL_OK) {
        Error_Handler();
    }
    /* USER CODE BEGIN USART2_Init 2 */
    uart_init();

    /* USER CODE END USART2_Init 2 */

}

/**
 * Enable DMA controller clock
 */
static void MX_DMA_Init(void) {

    /* DMA controller clock enable */
    __HAL_RCC_DMA1_CLK_ENABLE();

    /* DMA interrupt init */
    /* DMA1_Channel1_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);

}

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

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

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);

    /*Configure GPIO pin : PA3 */
    GPIO_InitStruct.Pin = GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /*Configure GPIO pin : LD3_Pin */
    GPIO_InitStruct.Pin = LD3_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(LD3_GPIO_Port, &GPIO_InitStruct);

    /* USER CODE BEGIN MX_GPIO_Init_2 */
    /* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

// Called when first half of buffer is filled
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) {
    GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
}

// Called when buffer is completely filled
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
    GPIOA->BRR = (1 << 3);  // Reset PA3
}

void blink_PA3(uint16_t microseconds) {
    GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
    delay_us(microseconds);
    GPIOA->BRR = (1 << 3);  // Reset PA3
}

void delay_us(uint16_t delay) {
    delay *= 16;
    while (delay--) {
        __asm__ __volatile__("");
    }
}

void blink_led(void) {
    HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_SET);
    HAL_Delay(5000);
    HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);
    HAL_Delay(500);
}

/* 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 */
    __disable_irq();
    while (1) {
    }
    /* 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,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */


Init:
Code: [Select]
    uint16_t adc_buf[1024];
   
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*) adc_buf, 512);


Callbacks:
Code: [Select]
// Called when first half of buffer is filled
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) {
    GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
}

// Called when buffer is completely filled
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
    GPIOA->BRR = (1 << 3);  // Reset PA3
}
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #64 on: May 17, 2024, 03:22:27 pm »
It turns out that it works, but only once.
Once the buffer is full (even though it is configured as circular) it does not fill up again.


EDIT:
Correction: it was not configured as circulating. :palm:
Now it is and it works all the time.   :)
« Last Edit: May 17, 2024, 03:37:17 pm by Picuino »
 

Online tellurium

  • Frequent Contributor
  • **
  • Posts: 285
  • Country: ua
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #65 on: May 17, 2024, 08:59:37 pm »
The advantage of all this is that I have been able to test the STM32 debugging system and it is quite good. You can even see the assembler instructions.

Mine impressions are very different. Cube's debugger seldom works reliably for me. Always acting - suddenly jumps around the sources, adds/removes breakpoints, etc etc. On the other hand, it's exciting: you never know what happens next
Open source embedded network library https://github.com/cesanta/mongoose
TCP/IP stack + TLS1.3 + HTTP/WebSocket/MQTT in a single file
 

Online tellurium

  • Frequent Contributor
  • **
  • Posts: 285
  • Country: ua
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #66 on: May 17, 2024, 09:06:26 pm »
At least, with ST, the HAL is entirely provided as source code, so you can audit it. They claim compliance with MISRA, which is supposed to (at least partially) adress my last point above, but they comply in a way that makes the code very bloated and not pretty to read. So, yeah.

And not just that. Some Cube HAL code is outright buggy - like Ethernet driver.
Open source embedded network library https://github.com/cesanta/mongoose
TCP/IP stack + TLS1.3 + HTTP/WebSocket/MQTT in a single file
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #67 on: May 18, 2024, 06:47:28 am »
Which version of the STM32L4 repository are you referring to? Could you tell us one or two of the bugs you found?

Last year i tried to use a W5500 ethernet interface as i had heard about how much work the STM32 ethernet driver gives. But also with the W5500 i had quite some difficulties and it doesn't work until now. Appears like ethernet is a bit difficult, although i also remember implementing a web interface (small html server) on a MSP430 about 15 years ago, based on ENC28J60 and a small library from the web.

Regards, Dieter
« Last Edit: May 18, 2024, 06:58:56 am by dietert1 »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #68 on: May 18, 2024, 05:27:51 pm »
Now I need to control quick mathematical operations. Multiplication and addition with 16-bit, 32-bit and 64-bit numbers, both with integers and floating point numbers.
I may also need to use iniline assembler to optimize the result.
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #69 on: May 18, 2024, 05:32:51 pm »
Now I need to control quick mathematical operations. Multiplication and addition with 16-bit, 32-bit and 64-bit numbers, both with integers and floating point numbers.
I may also need to use iniline assembler to optimize the result.

For your purpose, look at the CMSIS library.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #70 on: May 18, 2024, 05:46:50 pm »
First attempt, in c:

Code: [Select]
        GPIOA->BSRR = (1 << 3); // Set PA3

        int i;
        uint16_t *org1 = adc1_buff;
        uint16_t *org2 = adc2_buff;
        uint64_t *dst = mac_buff;
        uint32_t mul;
        uint64_t mac = 0;

        for(i=ADC_BUFF_SIZE/2; i; i--) {
            mul = (*org1++) * (*org2++);
            mac += mul;
            *dst++ = mac;
        }

        GPIOA->BRR = (1 << 3);  // Reset PA3

This code takes 138us to execute for a half buffer size = 1000 positions.
This is 138ns/sample compared to 188ns for ADC samples.
For a first attempt it is not bad at all.

Dissasembly:
Code: [Select]
125               GPIOA->BSRR = (1 << 3); // Set PA3
0800078a:   mov.w   r8, #1207959552 @ 0x48000000
131               uint64_t mac = 0;
0800078e:   movs    r2, #0
129               uint64_t *dst = mac_buff;
08000790:   ldr     r4, [pc, #84]   @ (0x80007e8 <main+448>)
128               uint16_t *org2 = adc2_buff;
08000792:   ldr     r6, [pc, #88]   @ (0x80007ec <main+452>)
127               uint16_t *org1 = adc1_buff;
08000794:   ldr     r0, [pc, #88]   @ (0x80007f0 <main+456>)
125               GPIOA->BSRR = (1 << 3); // Set PA3
08000796:   str.w   r5, [r8, #24]
131               uint64_t mac = 0;
0800079a:   mov     r1, r2
134                   mul = (*org1++) * (*org2++);
0800079c:   ldrh.w  r3, [r0], #2
080007a0:   ldrh.w  r12, [r6], #2
080007a4:   mul.w   r3, r12, r3
135                   mac += mul;
080007a8:   adds    r2, r3, r2
136                   *dst++ = mac;
080007aa:   str.w   r2, [r4], #8
135                   mac += mul;
080007ae:   adc.w   r1, r1, r3, asr #31
133               for(i=ADC_BUFF_SIZE/2; i; i--) {
080007b2:   cmp     r0, r7
080007b4:   str.w   r1, [r4, #-4]
080007b8:   bne.n   0x800079c <main+372>
138               GPIOA->BRR = (1 << 3);  // Reset PA3
080007ba:   str.w   r5, [r8, #40]   @ 0x28
« Last Edit: May 18, 2024, 05:55:37 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #71 on: May 18, 2024, 05:52:30 pm »
Now I need to control quick mathematical operations. Multiplication and addition with 16-bit, 32-bit and 64-bit numbers, both with integers and floating point numbers.
I may also need to use iniline assembler to optimize the result.

For your purpose, look at the CMSIS library.

https://arm-software.github.io/CMSIS_6/latest/General/index.html
I can't find the advantage for what I need.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #72 on: May 18, 2024, 05:57:56 pm »
Now I need to implement IIR filter to output filtered results.

* AN540: Implementing IIR Digital Filters
   https://ww1.microchip.com/downloads/aemDocuments/documents/MCU08/ApplicationNotes/ApplicationNotes/00540c.pdf
* https://stackoverflow.com/questions/50588879/how-to-implement-iir-filter-in-c
* https://en.wikipedia.org/wiki/Infinite_impulse_response

Quote from: Wikipedia
IIR vs FIR Advantages and disadvantages:
The main advantage digital IIR filters have over FIR filters is their efficiency in implementation, in order to meet a specification in terms of passband, stopband, ripple, and/or roll-off. Such a set of specifications can be accomplished with a lower order (Q in the above formulae) IIR filter than would be required for an FIR filter meeting the same requirements. If implemented in a signal processor, this implies a correspondingly fewer number of calculations per time step; the computational savings is often of a rather large factor.

On the other hand, FIR filters can be easier to design, for instance, to match a particular frequency response requirement. This is particularly true when the requirement is not one of the usual cases (high-pass, low-pass, notch, etc.) which have been studied and optimized for analog filters. Also FIR filters can be easily made to be linear phase (constant group delay vs frequency)—a property that is not easily met using IIR filters and then only as an approximation (for instance with the Bessel filter). Another issue regarding digital IIR filters is the potential for limit cycle behavior when idle, due to the feedback system in conjunction with quantization.
« Last Edit: May 18, 2024, 06:03:23 pm by Picuino »
 


Offline dave j

  • Regular Contributor
  • *
  • Posts: 140
  • Country: gb
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #74 on: May 18, 2024, 06:30:05 pm »
I'm not David L Jones. Apparently I actually do have to point this out.
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #75 on: May 18, 2024, 06:33:07 pm »
For your purpose, look at the CMSIS library.

https://arm-software.github.io/CMSIS_6/latest/General/index.html
I can't find the advantage for what I need.

Why not? It can do FIR filtering, IIR filtering, and various other stuff. It does exactly what you need. And it seems that the code is not just written naively, but tries to benefit from SIMD and DSP instructions, etc. Are you sure that you can do it faster?

Now I need to implement IIR filter to output filtered results.

What kind of IIR filter do you want to design?
Try the functions butter, cheby1, cheby2 or ellip in Matlab or Octave.
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #76 on: May 18, 2024, 06:33:57 pm »
For your purpose, look at the CMSIS library.

https://arm-software.github.io/CMSIS_6/latest/General/index.html
I can't find the advantage for what I need.

Look at the CMSIS DSP libraries.

Yes, that's what I meant. I should have been more specific.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #77 on: May 18, 2024, 06:46:25 pm »
I want to implement a simple second order Butterworth filter. But I think I will have to implement it with fixed point operations (with integers) to gain speed.
Mixing integers and floats has not given me good results in the test I have done. The results are slower than I expected.

I have about 30 CPU cycles per ADC sample (at 2666kHz ADC), so the operations have to be very fast.

Another thing I can do is to group data and apply the filter to aggregate data, not to all samples. But I am not clear how to do it. In that case I could use the CMSIS libraries:

https://arm-software.github.io/CMSIS-DSP/latest/group__groupFilters.html
« Last Edit: May 18, 2024, 06:49:23 pm by Picuino »
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #78 on: May 18, 2024, 08:08:44 pm »
Block size should be small if the filter is inside a control loop. Otherwise one can decide depending on available memory.
And if you want more speed, you could use a higher clock frequency. E.g. STM32G4xx MCUs are very similar except they run up to 170 MHz and power consumption will be three times higher.
I happened to get STM32F103 bluepill modules with fake MCUs and replaced them with STM32L433. They are (almost) pin compatible.

Regards, Dieter
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #79 on: May 18, 2024, 08:28:28 pm »
I want to implement a simple second order Butterworth filter. But I think I will have to implement it with fixed point operations (with integers) to gain speed.

You want either arm_biquad_cascade_df1_q15() or biquad_cascade_df1_q31(), for 16-bit and 32-bit integer.
See https://arm-software.github.io/CMSIS-DSP/latest/group__BiquadCascadeDF1.html
A second order IIR needs just a single biquad stage.

Note that most integer CMSIS DSP functions work with Q numbers. At the end this is just a matter of scaling, but you need to take care.

Quote
I have about 30 CPU cycles per ADC sample (at 2666kHz ADC), so the operations have to be very fast.

I have doubts that 30 cycles per sample are enough for a biquad stage. But I may be wrong. You need to measure. I'd measure all three, q15, q31 and float. Don't forget optimization -O2. You can also try if -O3 is even faster.

Quote
Another thing I can do is to group data and apply the filter to aggregate data, not to all samples.

Decimation with a 1st order boxcar filter is likely the fastest you can do. Just add-up (say) 16 adjacent samples and replace the 16 samples with a single sample containing the sum, and so on. I guess this fits into 5 cycles/sample, so that you get (30-5)*16=400 cycles for filtering each decimated sample. However, a 1st order boxcar filter is not a good anti-aliasing filter at all. This may or may not matter, depending on the frequency of a potential undesired (picked-up) interfering signal. If the frequency happens to be folded by the downsampling into the frequency band of interest, then it matters.

What cutoff frequency do you have in mind for the lowpass?

EDIT: The larger the sample rate to cutoff frequency ratio, the higher precision is required for the coefficients and the calculation. Then it can happen that Q15 (16-bit) is not sufficient.
« Last Edit: May 18, 2024, 09:07:52 pm by gf »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #80 on: May 19, 2024, 10:28:31 am »
For the project I am thinking about now (a Lock-in amplifier) the output frequencies should be in the range from 10kHz (audio output) to 0.1Hz, selectable by software.


EDIT:
Perhaps this is a good election?
arm_biquad_cascade_df1_fast_q31()
https://arm-software.github.io/CMSIS-DSP/latest/group__BiquadCascadeDF1.html#gaa09ea758c0b24eed9ef92b8d1e5c80c2

I'm going to install CMSIS libraries to test it.
« Last Edit: May 19, 2024, 10:33:13 am by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #81 on: May 19, 2024, 10:49:03 am »
How are the CMSIS libraries downloaded and installed?
From the GitHub page is complicated. There is no download button anywhere.
Are they downloaded from the Eclipse environment?
 

Online Tation

  • Regular Contributor
  • *
  • Posts: 106
  • Country: pt
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #82 on: May 19, 2024, 11:42:27 am »
How are the CMSIS libraries downloaded and installed?
From the GitHub page is complicated. There is no download button anywhere.
Are they downloaded from the Eclipse environment?

https://github.com/ARM-software/CMSIS-DSP/releases/tag/v1.15.0
 
The following users thanked this post: Picuino

Offline jnk0le

  • Regular Contributor
  • *
  • Posts: 103
  • Country: pl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #83 on: May 19, 2024, 12:51:13 pm »
Quote
I have about 30 CPU cycles per ADC sample (at 2666kHz ADC), so the operations have to be very fast.

I have doubts that 30 cycles per sample are enough for a biquad stage. But I may be wrong. You need to measure. I'd measure all three, q15, q31 and float. Don't forget optimization -O2. You can also try if -O3 is even faster.
If we are talking about latency then it is possible, but not with generic libraries. (TDF2 has O(1) latency)

For such sample rates stm32g4 would be a better choice. It also has a dedicated FIR/IIR hardware.

Another thing I can do is to group data and apply the filter to aggregate data, not to all samples.
In such scenario, it's possible to do low order IIR (2-5 taps). Might require optimized assembly again.

For the project I am thinking about now (a Lock-in amplifier) the output frequencies should be in the range from 10kHz (audio output) to 0.1Hz, selectable by software.
That means you don't need to filter that out of 2,5MSPS signal, which makes things easier.

EDIT:
Perhaps this is a good election?
arm_biquad_cascade_df1_fast_q31()
https://arm-software.github.io/CMSIS-DSP/latest/group__BiquadCascadeDF1.html#gaa09ea758c0b24eed9ef92b8d1e5c80c2
note that the biquad is just second order IIR (2 taps).

- 1 tap iir can be implemented by zoroing 2nd order coeffs
- higher order IIR coefficients (as in the book examples) cannot be applied directly to biquad cascade.
« Last Edit: May 19, 2024, 01:15:34 pm by jnk0le »
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #84 on: May 19, 2024, 01:00:02 pm »
Quote
arm_biquad_cascade_df1_fast_q31()
https://arm-software.github.io/CMSIS-DSP/latest/group__BiquadCascadeDF1.html#gaa09ea758c0b24eed9ef92b8d1e5c80c2
note that biquad is just second order IIR (2 taps).

- 1 tap iir can be implemented by zoroing 2nd order coeffs
- higher order IIR coefficients (as in the book examples) cannot be applied directly to biquad cascade.

The arm_biquad_cascade_xxx() functions implement N cascaded biquad stages, where the output of the first stage is connected to the input of the 2nd stage, and so on. N can be 1, for 2nd order.
 

Offline jnk0le

  • Regular Contributor
  • *
  • Posts: 103
  • Country: pl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #85 on: May 19, 2024, 01:24:50 pm »
Quote
arm_biquad_cascade_df1_fast_q31()
https://arm-software.github.io/CMSIS-DSP/latest/group__BiquadCascadeDF1.html#gaa09ea758c0b24eed9ef92b8d1e5c80c2
note that biquad is just second order IIR (2 taps).

- 1 tap iir can be implemented by zoroing 2nd order coeffs
- higher order IIR coefficients (as in the book examples) cannot be applied directly to biquad cascade.

The arm_biquad_cascade_xxx() functions implement N cascaded biquad stages, where the output of the first stage is connected to the input of the 2nd stage, and so on. N can be 1, for 2nd order.
which require conversion of coefficients from direct form to cascaded
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #86 on: May 19, 2024, 05:59:02 pm »
I have managed to install and use the CMSIS library.
   https://arm-software.github.io/CMSIS-DSP/latest/group__BiquadCascadeDF1.html#ga5563b156af44d1be2a7548626988bf4e
   https://arm-software.github.io/CMSIS-DSP/latest/group__BiquadCascadeDF1.html#ga4e7dad0ee6949005909fd4fcf1249b79

arm_biquad_cascade_df1_q31():
With one second order filter, applied on a 500-sample buffer takes 404us @ 80MHz to run on the STM32L412KB.

I'm going to wait until I receive the STM32G431KB this week to see how its speed increases thanks to a higher clock speed and IIR specific instructions.


EDIT:
The CMSIS library seems fast enough to use it without problems with samples decimated at 1.333MHz.

What I am not clear yet is:
  1. How to generate the 5 filter coefficients (I am now using random ones).
  2. If the ADC data need some kind of treatment to convert them to Q31 format or if they can be used as they are.
« Last Edit: May 19, 2024, 06:09:15 pm by Picuino »
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #87 on: May 19, 2024, 09:48:27 pm »
How to generate the 5 filter coefficients (I am now using random ones).

You can calcuate the transfer function coefficients with "butter".
For 2nd order, the transfer function coefficients are the biquad coefficients.
[ For higher order you would need to decompose them into biquad coefficients for multiple stages. ]

In the coefficients for arm_biquad_cascade_df1_init_q31(), the first entry of a is omitted (always 1), that's why there are only 5 and not 6.

I'm a bit unsure regarding the scaling, but I think the scaling below in conjunction with postShift=1 should work.

Code: [Select]
pkg load signal
fc = 100000    % cutoff frequency
fs = 1333000  % sample rate
[b,a] = butter(2,fc/(fs/2))
coeff = floor([ b -a(2:3) ] / 2 * 2**31 + 0.5)
printf("%d, %d, %d, %d, %d\n", coeff)
% ==> 44314902, 88629804, 44314902, 1448274717, -551792502

Quote
If the ADC data need some kind of treatment to convert them to Q31 format or if they can be used as they are.

Without oversampling:
Code: [Select]
uint16_t sample;  // 0...4095
q31_t qsample = (q31_t) sample - 2048 << 20;

With 4x oversampling:
Code: [Select]
uint16_t sample;  // 0...16380
q31_t qsample = (q31_t) sample - 8192 << 18;
« Last Edit: May 20, 2024, 03:21:11 pm by gf »
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #88 on: May 19, 2024, 10:13:14 pm »
...
I'm a bit unsure regarding the scaling, but I think the scaling below in conjunction with postShift=1 should work.

@Picuino, in order to verify correctness, can you send an impulse

    q31_t samples[100] = { 1073741824, /* 99x 0 */ };

through this filter and print the values of the 100 resulting samples?
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #89 on: May 20, 2024, 09:35:50 am »
arm_biquad_cascade_df1_q31():
With one second order filter, applied on a 500-sample buffer takes 404us @ 80MHz to run on the STM32L412KB.

Hmm, that's about 65 cycles per sample. That seems like a lot to me.

I copied the relevant fragment of the source file of this function to Compiler Explorer: https://godbolt.org/z/nfaM1Yq3P
The (4x unrolled) innermost loop (.L3) has 54 instructions.
According to the table in https://developer.arm.com/documentation/ddi0439/b/CHDDIGAC
this should be about 75 cycles for 4 samples, or less than 20 cycles per sample.
Even if I assume an additional overhead of 200 cycles for the function, it still should be not more than 20 cycles/sample when 500 samples are passed to the function.

Which compiler options did you use to compile CMSIS DSP?

I noticed, btw, that -O3 emits even more instrucions than -O2, so I don't think that -O3 is better here.
[Sorry, mistake. It's 54 vs 55 instructions, so almost the same, but they are arranged differently.]
« Last Edit: May 20, 2024, 09:50:44 am by gf »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #90 on: May 20, 2024, 02:56:09 pm »
I have tried a smaller and more realistic buffer (size = 100 samples):

Program:

Code: [Select]
        // Define variables
        q31_t pSrc[100];
        q31_t pDst[100];
        arm_biquad_casd_df1_inst_q31 S;
        q31_t pCoeffs[] = { 0x02A43116, 0x0548622C, 0x02A43116, 0xA9AD14E3,
                0x20E3AF76 };
        q31_t pState;

        uart_init();
        while (1) {
            // Initialize buffer pSrc
            for (int i = 0; i < 100; i++)
                pSrc[i] = 0;
            pSrc[0] = 0x40000000;

            // Process data
            arm_biquad_cascade_df1_init_q31(&S, 1, pCoeffs, &pState, 0);
            GPIOA->BSRR = (1 << 3); // Set PA3
            arm_biquad_cascade_df1_q31(&S, pSrc, pDst, 100);
            GPIOA->BRR = (1 << 3);  // Reset PA3

            // Output data
            for (int i = 0; i < 100; i++) {
                printf("0x%08lX\r\n", pDst[i]);
                HAL_Delay(2);
            }
            HAL_Delay(2000);
        }


Hex output:
Code: [Select]
0x0152188B
0x01C02D93
0x007AB721
0x00206613
0x0009AE7F
0x0001CBA3
0x000146DD
0xFFFF99AA
0x00009900
0xFFFF7E85
0x00007EA2
0xFFFF8954
0x00007092
0xFFFF9596
0x000064B0
0xFFFFA0C0
0x00005A1B
0xFFFFAAC2
0x000050A3
0xFFFFB3B7
0x0000482A
0xFFFFBBBB
0x00004095
0xFFFFC2E7
0x000039CC
0xFFFFC952
0x000033BA
0xFFFFCF10
0x00002E4B
0xFFFFD434
0x0000296E
0xFFFFD8CE
0x00002514
0xFFFFDCEC
0x0000212F
0xFFFFE09B
0x00001DB2
0xFFFFE3E8
0x00001A93
0xFFFFE6DC
0x000017C8
0xFFFFE980
0x00001548
0xFFFFEBDD
0x0000130C
0xFFFFEDFB
0x0000110B
0xFFFFEFE0
0x00000F41
0xFFFFF191
0x00000DA7
0xFFFFF315
0x00000C38
0xFFFFF470
0x00000AEF
0xFFFFF5A7
0x000009C9
0xFFFFF6BD
0x000008C2
0xFFFFF7B6
0x000007D7
0xFFFFF895
0x00000704
0xFFFFF95C
0x00000647
0xFFFFFA0F
0x0000059E
0xFFFFFAAF
0x00000507
0xFFFFFB3E
0x00000480
0xFFFFFBBE
0x00000407
0xFFFFFC30
0x0000039B
0xFFFFFC96
0x0000033A
0xFFFFFCF2
0x000002E3
0xFFFFFD44
0x00000295
0xFFFFFD8E
0x00000250
0xFFFFFDCF
0x00000212
0xFFFFFE0A
0x000001DA
0xFFFFFE3F
0x000001A8
0xFFFFFE6E
0x0000017C
0xFFFFFE98
0x00000154
0xFFFFFEBE
0x00000130
0xFFFFFEE0
0x00000110
0xFFFFFEFE
0x000000F3
0xFFFFFF19

Decimal output:
Code: [Select]
22157451
29371795
8042273
2123283
634495
117667
83677
-26198
39168
-33147
32418
-30380
28818
-27242
25776
-24384
23067
-21822
20643
-19529
18474
-17477
16533
-15641
14796
-13998
13242
-12528
11851
-11212
10606
-10034
9492
-8980
8495
-8037
7602
-7192
6803
-6436
6088
-5760
5448
-5155
4876
-4613
4363
-4128
3905
-3695
3495
-3307
3128
-2960
2799
-2649
2505
-2371
2242
-2122
2007
-1899
1796
-1700
1607
-1521
1438
-1361
1287
-1218
1152
-1090
1031
-976
923
-874
826
-782
739
-700
661
-626
592
-561
530
-502
474
-449
424
-402
380
-360
340
-322
304
-288
272
-258
243
-231
« Last Edit: May 20, 2024, 03:01:28 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #91 on: May 20, 2024, 03:06:41 pm »
Response to a step.

Program change:
Code: [Select]
            // Initialize buffer pSrc
            for (int i = 0; i < 100; i++)
                pSrc[i] = 0;
            for (int i = 20; i < 60; i++)
                pSrc[i] = 0x10000;

Decimal output:
Code: [Select]
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1352
3145
3635
3766
3803
3812
3815
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
3816
2464
671
180
51
11
5
-1
1
-1
0
-1
0
-1
0
-1
0
-1
0
-1
0
-1
0
-1
0
-1
0
-1
0
-1
0
-1
0
-1
0
-1
0
-1
0
-1
0

EDIT:
There is a scale error. The output should reach the value 65536.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #92 on: May 20, 2024, 03:14:16 pm »
Optimizing with -O3, filtering 100 samples takes 42us (34 clock cycles per sample @ 80MHz)
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #93 on: May 20, 2024, 03:16:45 pm »
I noticed in the meantime that I was mistaken regarding the sign of the a coefficient. These terms must be subtracted from the accumulator, but the function adds all terms, which means that the a coefficients must be negated.

Please try with coefficients 44314902, 88629804, 44314902, 1448274717, -551792502

The attached plot shows how the impulse response should look like.

EDIT: Corrected commands:
Code: [Select]
pkg load signal
fc = 100000    % cutoff frequency
fs = 1333000  % sample rate
[b,a] = butter(2,fc/(fs/2))
coeff = floor([ b -a(2:3) ] / 2 * 2**31 + 0.5)
printf("%d, %d, %d, %d, %d\n", coeff)
% ==> 44314902, 88629804, 44314902, 1448274717, -551792502


Optimizing with -O3, filtering 100 samples takes 42us (34 clock cycles per sample @ 80MHz)

 :-+
« Last Edit: May 20, 2024, 03:23:59 pm by gf »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #94 on: May 20, 2024, 03:23:17 pm »
Step response:
Code: [Select]
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1352
4968
8412
9806
9861
9540
9309
9236
9246
9271
9286
9289
9288
9286
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
9285
7933
4316
872
-521
-576
-255
-24
49
39
13
-2
-5
-3
-1
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0


Scale error is maintained.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #95 on: May 20, 2024, 03:26:15 pm »
Impulse response:
Code: [Select]
22157451
59258008
56428053
22829136
896995
-5260972
-3778510
-1196450
163989
418020
239778
54298
-24992
-30807
-14355
-1766
2497
2137
799
-11
-213
-141
-41
8
15
8
1
-2
-2
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
« Last Edit: May 20, 2024, 03:28:50 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #96 on: May 20, 2024, 03:48:13 pm »
Equivalent script in Python:

Code: [Select]
import scipy
import math

fc = 100000    # Cutoff frequency
fs = 1333000   # Sample rate

b, a = scipy.signal.butter(2, fc, btype='low', analog=False, output='ba', fs=fs)
coeff = list(b) + list(-a)[1:]
coeff = [round(c * (2 ** 30)) for c in coeff]
for c in coeff:
    if c < 0 :
        c += 0x100000000;
    print("  0x%08X," % c)


Coefficients:
Code: [Select]
  0x02A43116,
  0x0548622C,
  0x02A43116,
  0x5652EB1D,
  0xDF1C508A,
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #97 on: May 20, 2024, 03:49:47 pm »
Impulse response: ...

Which coefficient? Not my latest ones? (44314902, 88629804, 44314902, 1448274717, -551792502 with postShift=1)
What was the height of the impulse?
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #98 on: May 20, 2024, 03:55:00 pm »
Coefficients:
Code: [Select]
        q31_t pCoeffs[] = {44314902, 88629804, 44314902, 1448274717, -551792502};
Height of impulse:
Code: [Select]
pSrc[0]= 0x40000000;
0x40000000 = 1073741824 decimal
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #99 on: May 20, 2024, 03:58:46 pm »
I had set the postShift to zero.

I change it and try again.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #100 on: May 20, 2024, 04:01:02 pm »
Impulse response:
Code: [Select]
44314902
148402228
221708163
222778963
186551583
137137416
89104182
49710214
21259325
3128889
-6704820
-10651461
-10921222
-9256912
-6873443
-4513878
-2556128
-1128066
-207962
299208
510446
534733
458937
344221
228442
131230
59608
12961
-13151
-24399
-26152
-22736
-17228
-11554
-6731
-3142
-779
563
1159
1273
1121
857
579
340
161
42
-27
-59
-66
-59
-46
-32
-20
-11
-5
-2
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #101 on: May 20, 2024, 04:04:27 pm »
Step (of value = 100000) response:

Code: [Select]
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
4127
17947
38594
59341
76715
89487
97786
102416
104396
104687
104062
103070
102053
101191
100551
100131
99893
99788
99769
99797
99844
99893
99935
99967
99988
100000
100006
100008
100007
100005
100003
100001
99999
99998
99997
99996
99996
99996
99996
99996
95869
82048
61400
40652
23278
10506
2208
-2421
-4401
-4692
-4067
-3075
-2058
-1196
-556
-136
102
207
226
198
150
100
57
25
4
-8
-13
-14
-13
-11
-9
-7
-5
-4
-3
-2
-2
-2
-2
-2

Now the scale is OK.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #102 on: May 20, 2024, 04:05:29 pm »
STM32 program:

Code: [Select]
        // Define variables
        q31_t pSrc[100];
        q31_t pDst[100];
        arm_biquad_casd_df1_inst_q31 S;
        q31_t pCoeffs[] = {44314902, 88629804, 44314902, 1448274717, -551792502};
               // { 0x02A43116, 0x0548622C, 0x02A43116, 0xA9AD14E3, 0x20E3AF76 };
        q31_t pState;

        uart_init();
        while (1) {
            // Initialize buffer pSrc
            for (int i = 0; i < 100; i++)
                pSrc[i] = 0;
            //pSrc[0]= 0x40000000;
            for (int i = 20; i < 60; i++)  pSrc[i] = 100000;

            // Process data
            arm_biquad_cascade_df1_init_q31(&S, 1, pCoeffs, &pState, 1);
            GPIOA->BSRR = (1 << 3); // Set PA3
            arm_biquad_cascade_df1_q31(&S, pSrc, pDst, 100);
            GPIOA->BRR = (1 << 3);  // Reset PA3

            // Output data
            for (int i = 0; i < 100; i++) {
                printf("%ld\r\n", pDst[i]);
                HAL_Delay(2);
            }
            HAL_Delay(2000);
        }

The process time is maintained at 42us for 100 samples.
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #103 on: May 20, 2024, 04:26:22 pm »
Gain is basically correct now, but there is still a problem with a full step (-2147483648 -> 2147483647). Then the overshoot will exceed the q31 range, and the response will go crazy :scared:. If you want the filter to withstand a full step, the gain would need to be reduced. This could be done by scaling down all three b parameters with the same factor (say 0.8 or whatsoever is necessary to bring the overshoot into a -1...1 range).

EDIT:

And keep in mind: The smaller the signal, the larger the (relative) numerical error.
Note that your step amplitude of 100000 in Q31 corresponds to only 4.6566e-05.
That's less than one digital ADCl count after scaling the ADC full-scale range to Q31.
« Last Edit: May 20, 2024, 04:41:13 pm by gf »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #104 on: May 20, 2024, 05:05:14 pm »
No problem.
I will multiply the value of two ADCs (12bits x 12bits = 24bits) and perhaps add several samples (16) before applying the filter. This produces 28-bit values (worst case), which will not saturate the filter and, I hope, will be sufficiently large values.

The problem now is to downsample from 1.333MHz or higher, to an output frequency of 10khz at most. In slower cases, I will need an output frequency lower than 1Hz and I don't know how the filter will behave in that case. Perhaps I may need to sum several blocks of input samples before applying the output filter.
« Last Edit: May 20, 2024, 05:07:48 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #105 on: May 20, 2024, 05:37:20 pm »
As the filter has a lower cutoff frequency, the coefficients become smaller and smaller and, therefore, resolution is lost.
There comes a time when the coefficients are very small and become almost zero.

Code: [Select]
   // Cutoff frequency = 10000
   coeff = { 0x0008CE29, 0x00119C53, 0x0008CE29, 0x7BBC3A97, 0xC4208CC3 };

   // Cutoff frequency = 1000
   coeff = { 0x00001738, 0x00002E70, 0x00001738, 0x7F92C8E9, 0xC06CDA36 };

   // Cutoff frequency = 100
   coeff = { 0x0000003C, 0x00000077, 0x0000003C, 0x7FF51415, 0xC00AEAFD };

   // Cutoff frequency = 10
   coeff = { 0x00000001, 0x00000001, 0x00000001, 0x7FFEE868, 0xC0011795 };

   // Cutoff frequency = 1
   coeff = { 0x00000000, 0x00000000, 0x00000000, 0x7FFFE40A, 0xC0001BF6 };


Script:
Code: [Select]
#
# Python script to calculate Butterworth filter coefficients
#
import scipy
import math

fc = 1000      # Cutoff frequency
fs = 1333000   # Sample rate

for fc in [10000, 1000, 100, 10, 1]:
    b, a = scipy.signal.butter(2, fc, btype='low', analog=False, output='ba', fs=fs)
    coeff = list(b) + list(-a)[1:]
    coeff = [round(c * (2 ** 30)) for c in coeff]
    coeff = [c if c >= 0 else c + 0x100000000 for c in coeff]
    print()
    print("   // Cutoff frequency = %d" % fc)
    print("   coeff = { 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X };" % tuple(coeff))
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #106 on: May 20, 2024, 06:26:23 pm »
High Precision Q31 Biquad Cascade Filter
https://arm-software.github.io/CMSIS-DSP/latest/group__BiquadCascadeDF1__32x64.html

Code: [Select]
arm_biquad_cas_df1_32x64_q31 (const arm_biquad_cas_df1_32x64_ins_q31 *S, const q31_t *pSrc, q31_t *pDst, uint32_t blockSize)
Quote
This function implements a high precision Biquad cascade filter which operates on Q31 data values. The filter coefficients are in 1.31 format and the state variables are in 1.63 format. The double precision state variables reduce quantization noise in the filter and provide a cleaner output. These filters are particularly useful when implementing filters in which the singularities are close to the unit circle. This is common for low pass or high pass filters with very low cutoff frequencies.

EDIT:

Python script with postShift manage:
Code: [Select]
#
# Python script to calculate Butterworth filter coefficients
#
import scipy

fs = 1333000   # Sample rate
fc = 100000      # Cutoff frequency
postShift = 0  # Post shift gain

b, a = scipy.signal.butter(2, fc, btype='low', analog=False, output='ba', fs=fs)
coeff = list(b) + list(-a)[1:]
while max(coeff) > 1.0 or min(coeff) < -1.0:
    postShift += 1
    coeff = [c/2 for c in coeff]
coeff = [round(c * (2 ** (31-postShift))) for c in coeff]
coeff = [c if c >= 0 else c + 0x100000000 for c in coeff]
print("   // Sample rate = %0.2f" % fs)
print("   // Cutoff frequency = %0.2f" % fc)
print("   postShift = %d;" % postShift)         
print("   q31_t pCoeffs = { 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X };" % tuple(coeff))
« Last Edit: May 20, 2024, 06:42:11 pm by Picuino »
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15775
  • Country: fr
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #107 on: May 20, 2024, 09:29:23 pm »
Sorry if you discussed it before - but the doesn't the STM32L4 have an FPU? Are you sure any of these fixed-point implementations will be faster than using floating point?
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #108 on: May 20, 2024, 10:15:13 pm »
Sorry if you discussed it before - but the doesn't the STM32L4 have an FPU? Are you sure any of these fixed-point implementations will be faster than using floating point?

I would not be sure either that fixed point is really faster.

Floating point normalization certainly solves scaling issues.
But I have doubts that (single precision) FP solves IIR precision issues, since mantissa precision is only 24 bits.
« Last Edit: May 20, 2024, 11:10:14 pm by gf »
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #109 on: May 21, 2024, 05:11:52 am »
For a lock-in one uses a direct receiver, that is signal detection by mixing the input signal with the carrier of known frequency and phase. This mixing with a synthetic carrier of adjustable phase happens at the ADC sampling rate. One multiplication per ADC sample.
The output of that mixer is bandwidth limited at the carrier frequency. Any meaningful sampling rate won't be higher than the carrier frequency, except nyquist factor 2. So there should be some down sampling, e.g. using a boxcar.
As far as i understand the filter design discussed here is to reduce detection bandwidth even further, e.g. to 1 Hz with a 1 KHz carrier. In this case i see the need for precision but i don't see the need for high speed. At 10 KHz carrier frequency the rate will be 20 KHz (50 usec).
For a IIR filter i would try to use the FPU. If the dynamic range is a problem, one can get a F7 or H7 with the double FPU. Others get the precision by using two or three filter stages running at ever lower rates, where each stage reduces bandwidth and rate by at most a factor 10 or so. As far as i remember Cortex M supports filters with double precision accumulator (32 * 32 to 64 multiply and add into 64 bit accu).

Regards, Dieter
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #110 on: May 21, 2024, 07:58:47 am »
Yes, what I am going to do is to multiply some 32 or 64 samples of the two ADCs and add the result in an accumulator. The output will be a buffer with the data from the accumulators, which should come out of the DAC at a rate of about 100kHz.
To that 100kHz buffer is where I should apply the filter or filters and, in the case of slower outputs, reduce the number of points.

What is not clear to me is how to reduce the number of points of the DAC after a filter. Do I add several points again to generate a single point?
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #111 on: May 21, 2024, 09:00:53 am »
Yes, what I am going to do is to multiply some 32 or 64 samples of the two ADCs and add the result in an accumulator. The output will be a buffer with the data from the accumulators, which should come out of the DAC at a rate of about 100kHz.

What you have in mind is basically boxcar filter (moving average filter) with 32...64 taps, applied to the stream of multiplied samples, followed by downsampling from 1333kSa/s to ~100kSa/s (factor 13x) by picking only every 13th sample and discarding the samples in between. And obviously you want a filter length which is larger than the decimation factor.

If the number of boxcar taps is an integer multiple of the decimation factor, then the cheapest way to do that is a CIC decimator (e.g. 13x5 -> 65 taps, or 16x4 -> 64 taps for fsout=fs/16=83kSa/s). See https://www.dsprelated.com/showarticle/1337.php.

EDIT:

Attached is the frequency response plot of a 64-tap boxcar filter @1333kSa/s. Check yourself what this means for the suppression of the carrier and carrier harmonics if the carrier frequency can be arbitrary. Also keep in mind that any frequencies which pass through the filter (with more or less attenuation) are folded down to freqencies < fs/R/2 by the downsampling (where fs is the original sample rate and R is the downsampling factor). If the carrier frequency happens to be "unfavorable", the folded frequencies can even fall into the 0...10kHz region of interest.

[ With your milliohm meter, the length of the boxcar was an integer mutiple of the carrier period. Then the carrier and carrier harmonics fall into zeros of the filter's frequency response and are rejected completely. But this is no longer granted if the carrier frequency can be arbitrary. ]

For comparison I also added a plot with the frequency response of a "proper" FIR downsampling filter which avoids aliasing (with a stopband attenuation of ~65dB; more that that is possible, too, with more taps). 9x13=117 taps means that 9 taps must be calculated for each source sample when the downsampling factor is 13. With CMSIS, you could use arm_fir_decimate_q31() to do the filtering and decimation.
« Last Edit: May 21, 2024, 10:05:24 am by gf »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #112 on: May 21, 2024, 10:56:14 am »
There is a very simple detail that I don't quite understand.
It seems that a Boxcar filter or a CIC filter is a filter that adds the previous N samples, so the algorithm would be something like this:
Code: [Select]
out[10] =                            in[10] + in[9] + in[8] + in[7]
out[11] =                   in[11] + in[10] + in[9] + in[8]
out[12] =          in[12] + in[11] + in[10] + in[9]
out[13] = in[13] + in[12] + in[11] + in[10]
and so on...

This means that the number of output samples is equal to the number of input samples, but filtered.

However what I need is to reduce the number of samples (for example from 1333kHz to 133kHz).

The only way I can think of to do this is to sum blocks:
Code: [Select]
out[1] =                                     in[10] + in[9] + in[8] + in[7]
out[2] = in[14] + in[13] + in[12] + in[11]

Is there any other way to make decimation?
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #113 on: May 21, 2024, 11:18:34 am »
There is a very simple detail that I don't quite understand.
It seems that a Boxcar filter or a CIC filter is a filter that adds the previous N samples, so the algorithm would be something like this:
Code: [Select]
out[10] =                            in[10] + in[9] + in[8] + in[7]
out[11] =                   in[11] + in[10] + in[9] + in[8]
out[12] =          in[12] + in[11] + in[10] + in[9]
out[13] = in[13] + in[12] + in[11] + in[10]
and so on...

This means that the number of output samples is equal to the number of input samples, but filtered.

And the next step after filtering is downsampling, i.e. you keep only every (say) 10th sample of out[] and discard the 9 samples in between. Of course you can optimize: You do not need to calculate those filtered samples, which are discarded in the next step.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #114 on: May 21, 2024, 11:25:21 am »
https://arm-software.github.io/CMSIS_5/DSP/html/group__FIR__decimate.html#ga6a19d62083e85b3f5e34e8a8283c1ea0
https://arm-software.github.io/CMSIS_5/DSP/html/group__FIR__decimate.html#ga27c05d7892f8a327aab86fbfee9b0f29

Thank you very much for your help.
Do you know what would be the way to obtain the FIR filter coefficients?


Code: [Select]
arm_status arm_fir_decimate_init_q31 (
arm_fir_decimate_instance_q31 *  S,
uint16_t  numTaps,
uint8_t  M,
const q31_t *  pCoeffs,
q31_t *  pState,
uint32_t  blockSize
)

Parameters
    [in,out] S points to an instance of the Q31 FIR decimator structure
    [in] numTaps number of coefficients in the filter
    [in] M decimation factor
    [in] pCoeffs points to the filter coefficients
    [in] pState points to the state buffer
    [in] blockSize number of input samples to process

Returns
    execution status

        ARM_MATH_SUCCESS : Operation successful
        ARM_MATH_LENGTH_ERROR : blockSize is not a multiple of M

Details
    pCoeffs points to the array of filter coefficients stored in time reversed order:

        {b[numTaps-1], b[numTaps-2], b[N-2], ..., b[1], b[0]}

    pState points to the array of state variables. pState is of length numTaps+blockSize-1 words where blockSize is the number of input samples passed to arm_fir_decimate_q31(). M is the decimation factor.

« Last Edit: May 21, 2024, 11:27:11 am by Picuino »
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #115 on: May 21, 2024, 11:49:44 am »
https://arm-software.github.io/CMSIS_5/DSP/html/group__FIR__decimate.html#ga6a19d62083e85b3f5e34e8a8283c1ea0
https://arm-software.github.io/CMSIS_5/DSP/html/group__FIR__decimate.html#ga27c05d7892f8a327aab86fbfee9b0f29

Thank you very much for your help.
Do you know what would be the way to obtain the FIR filter coefficients?

For 10x decimation, try this one:

Code: [Select]
pkg load signal
R = 10      % decimation factor
BW = 10     % end of passband (start of transition band), khz
fs =  1333  % sample rate, kSa/s
ntaps = 80  % number of taps
h = remez(ntaps-1,[0 BW fs/2/R fs/2]/(fs/2),[1 1 0 0]);
% plot frequency response
[H,f] = freqz(h,1,10000,fs);
plot(f,20*log10(abs(H)))
grid on
ylim([-70 0])
% scale h to Q31
int32(round(h*2^32))

Code: [Select]
     1943208
      464978
      305641
      -49046
     -638438
    -1497324
    -2650941
    -4109235
    -5862456
    -7878098
   -10098245
   -12435856
   -14770057
   -16950103
   -18809941
   -20144286
   -20744894
   -20388173
   -18853952
   -15933574
   -11440900
    -5223349
     2827034
    12763159
    24575302
    38187347
    53451404
    70147061
    87988766
   106625888
   125658165
   144643853
   163116013
   180598380
   196623003
   210748070
   222574464
   231761399
   238041130
   241228017
   241228017
   238041130
   231761399
   222574464
   210748070
   196623003
   180598380
   163116013
   144643853
   125658165
   106625888
    87988766
    70147061
    53451404
    38187347
    24575302
    12763159
     2827034
    -5223349
   -11440900
   -15933574
   -18853952
   -20388173
   -20744894
   -20144286
   -18809941
   -16950103
   -14770057
   -12435856
   -10098245
    -7878098
    -5862456
    -4109235
    -2650941
    -1497324
     -638438
      -49046
      305641
      464978
     1943208

The question is, how fastst this function is.

[ If it is too slow, the decimation could be split into several stages, using a half-band filters for the first stages. Only for the last stage, the stopband must start below Nyquist. For the above case, the first decimation-by-2 stage would need only 6 taps or so. The function for the first stage could also be hand-optimized in order to avoid some of the overhead of the generic CMSIS function. ]
« Last Edit: May 21, 2024, 12:23:31 pm by gf »
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #116 on: May 21, 2024, 12:19:55 pm »
Let's say i want to use an exponential running average as low pass filter before down sampling. E.g.
Yn = 0.000 002 * Xn + 0.999 998 * Yn-1
If Xn is a 12 bit ADC value i will be adding zeros. But i can rewrite the formula as
Yn = Xn + Yn-1 - Yn-1 / 500 000
Is a 32 bit integer unit good enough to do this? I think it can work and the operation can be done at ADC rate. Maybe one should use a right shift instead of the division, using a power of 2 instead of an arbitrary number.

If one wants to input a 12 * 12 product into the filter, one can extend arithmetics to 64 bit using the same idea. Twice the number of operations per cycle but can still run at ADC rate.

Regards, Dieter
« Last Edit: May 21, 2024, 12:45:39 pm by dietert1 »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #117 on: May 21, 2024, 01:14:16 pm »
Within a week I will receive the board with the other micro (STM32G431KB), which is the one I am going to use in the end.
This other model has a maximum speed of 4Msps.

In practice I will set the main clock speed to 170MHz and the ADC clock speed to 1/4, which gives me a conversion speed of 170/4/15 = 2.833Msps.

I am not going to do hardware oversampling because that only serves to increase the number of bits of resolution and I already checked that when taking data, with all the noise produced by the instrumentation amplifier, the results do not improve by increasing the number of bits of the ADC.
I prefer to take many samples per second and filter after multiplying the two signals.
This sampling speed is too large to apply a filter with so many parameters (80), which is very slow.

To start I will try to multiply the two ADC signals and add 8 results to decimate the sampling rate at 354kHz.
I will try to apply the filter at this lower frequency.

I have no idea about the speed of the other processor (STM32G431KB). In principle it has more clock speed and also has instructions to accelerate the digital filters. Until it arrives to me (around the 29th) I can't test it.
« Last Edit: May 21, 2024, 01:16:02 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #118 on: May 21, 2024, 01:26:08 pm »
I'm going to try programming on my current board (STM32L412KB) just to get a rough idea of the timing.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #119 on: May 21, 2024, 01:42:53 pm »
Input buffer = 1000 samples
Output buffer = 100 samples

process time = 936us @ 80MHz with STM32L412KB

Program:
Code: [Select]
        // Define variables
        q31_t pSrc[1000];
        q31_t pDst[1000];
        q31_t pCoeffs[] = { 1943208, 464978, 305641, -49046, -638438, -1497324,
                -2650941, -4109235, -5862456, -7878098, -10098245, -12435856,
                -14770057, -16950103, -18809941, -20144286, -20744894,
                -20388173, -18853952, -15933574, -11440900, -5223349, 2827034,
                12763159, 24575302, 38187347, 53451404, 70147061, 87988766,
                106625888, 125658165, 144643853, 163116013, 180598380,
                196623003, 210748070, 222574464, 231761399, 238041130,
                241228017, 241228017, 238041130, 231761399, 222574464,
                210748070, 196623003, 180598380, 163116013, 144643853,
                125658165, 106625888, 87988766, 70147061, 53451404, 38187347,
                24575302, 12763159, 2827034, -5223349, -11440900, -15933574,
                -18853952, -20388173, -20744894, -20144286, -18809941,
                -16950103, -14770057, -12435856, -10098245, -7878098, -5862456,
                -4109235, -2650941, -1497324, -638438, -49046, 305641, 464978,
                1943208, };
        q31_t pState[1000 + 80];
        arm_fir_decimate_instance_q31 S;

        uart_init();
        while (1) {
            // Initialize buffer pSrc
            for (int i = 0; i < 1000; i++)
                pSrc[i] = 0;
            for (int i = 0; i < 500; i++)
                pSrc[i] = 10000000;

            // Process data
            arm_fir_decimate_init_q31(&S, 80, 10, pCoeffs, pState, 1000);
            GPIOA->BSRR = (1 << 3); // Set PA3
            arm_fir_decimate_q31(&S, pSrc, pDst, 1000);
            GPIOA->BRR = (1 << 3);  // Reset PA3

            // Output data
            for (int i = 0; i < 100; i++) {
                printf("%ld\r\n", pDst[i]);
                while(uart_sending());
            }
            HAL_Delay(2000);
        }


Attached: output response to a step signal

Code: [Select]
9048
-140025
-933846
1473627
11115434
19592287
20790631
20040573
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19984258
19975209
20124282
20918103
18510631
8868823
391970
-806373
-56316
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
« Last Edit: May 21, 2024, 01:46:48 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #120 on: May 21, 2024, 01:49:07 pm »
The other microcontroller may be able to reduce 2.833Msps in real time.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #121 on: May 21, 2024, 01:58:16 pm »
Yet another test:

Code: [Select]
pkg load signal
R = 10      % decimation factor
BW = 20     % end of passband (start of transition band), khz
fs =  2833  % sample rate, kSa/s
ntaps = 70 % number of taps
h = remez(ntaps-1,[0 BW fs/2/R fs/2]/(fs/2),[1 1 0 0]);
% plot frequency response
[H,f] = freqz(h,1,10000,fs);
plot(f,20*log10(abs(H)))
grid on
ylim([-70 0])
% scale h to Q31
int32(round(h*2^32))

Program:
Code: [Select]
        // Define variables
        q31_t pSrc[1000];
        q31_t pDst[1000];
        q31_t pCoeffs[] = { -3102388, -3113797, -4515965, -6180048, -8075456,
                -10141397, -12290802, -14416199, -16366203, -17980278,
                -19065273, -19421132, -18834989, -17096251, -14007364, -9390118,
                -3103346, 4955244, 14832503, 26518003, 39934516, 54936181,
                71309499, 88773820, 106991524, 125572769, 144091117, 162094330,
                179121976, 194721956, 208466551, 219971072, 228905848,
                235012496, 238111387, 238111387, 235012496, 228905848,
                219971072, 208466551, 194721956, 179121976, 162094330,
                144091117, 125572769, 106991524, 88773820, 71309499, 54936181,
                39934516, 26518003, 14832503, 4955244, -3103346, -9390118,
                -14007364, -17096251, -18834989, -19421132, -19065273,
                -17980278, -16366203, -14416199, -12290802, -10141397, -8075456,
                -6180048, -4515965, -3113797, -3102388, };
        q31_t pState[1000 + 70];
        arm_fir_decimate_instance_q31 S;

        uart_init();
        while (1) {
            // Initialize buffer pSrc
            for (int i = 0; i < 1000; i++)
                pSrc[i] = 0;
            for (int i = 0; i < 500; i++)
                pSrc[i] = 10000000;

            // Process data
            arm_fir_decimate_init_q31(&S, 70, 10, pCoeffs, pState, 1000);
            GPIOA->BSRR = (1 << 3); // Set PA3
            arm_fir_decimate_q31(&S, pSrc, pDst, 1000);
            GPIOA->BRR = (1 << 3);  // Reset PA3

            // Output data
            for (int i = 0; i < 100; i++) {
                printf("%ld\r\n", pDst[i]);
                while (uart_sending())
                    ;
            }
            HAL_Delay(2000);
        }

Process time: 836us (less)

 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #122 on: May 21, 2024, 02:45:48 pm »
Code: [Select]
#
# Python script to calculate
# FIR coefficients with remez algorithm
#

import numpy as np
from scipy import signal
import matplotlib.pyplot as plt

fs = 2833000   # Sample rate, Hz
cutoff = 20000 # Desired cutoff frequency, Hz
R = 10         # Decimation factor
numtaps = 70   # Size of the FIR filter.

def plot_response(fs, w, h, title):
    plt.figure()
    plt.plot(0.5*fs*w/np.pi, 20*np.log10(np.abs(h)))
    plt.ylim(-100, 5)
    plt.xlim(0, 0.1*fs)
    plt.grid(True)
    plt.xlabel('Frequency (Hz)')
    plt.ylabel('Gain (dB)')
    plt.title(title)
    plt.show()


bands = [0, cutoff, 0.5*fs/R, 0.5*fs]
gains = [1, 0]

taps = signal.remez(numtaps, bands, gains, fs=fs)

q31_taps =[round(t*2**31) for t in taps]
q31_taps =[t if t>0 else t+0x100000000 for t in q31_taps]
q31_taps =[f"0x{t:08X}" for t in q31_taps]
print(", ".join(q31_taps))


w, h = signal.freqz(taps, [1], worN=2000)
plot_response(fs, w, h, "Low-pass Filter")

Python equivalent code for calculating FIR coefficients and frequency response.

EDIT: Q31 conversion corrected.
« Last Edit: May 21, 2024, 03:27:42 pm by Picuino »
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #123 on: May 21, 2024, 03:15:13 pm »
Code: [Select]
q31_taps =[round(t*2**32) for t in taps]

Sorry, my mistake. I got that wrong too. When converting to Q31, the scaling is 2**31, not 2**32.
Basically, the sum of the taps should be 1, in order that the DC gain of the filter becomes 1.
[ But I noticed that remez does not always produce a sum of exactly 1, it can be slightly off. ]
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #124 on: May 21, 2024, 03:41:36 pm »
Input buffer = 1000 samples
Output buffer = 100 samples
process time = 936us @ 80MHz with STM32L412KB

Yet another test:
Process time: 836us (less)

Both are about 9.5 cycles per output sample per tap.
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #125 on: May 21, 2024, 09:34:12 pm »
Input buffer = 1000 samples
Output buffer = 100 samples
process time = 936us @ 80MHz with STM32L412KB

Yet another test:
Process time: 836us (less)

Both are about 9.5 cycles per output sample per tap.

@Picuino, could you please also check how fast arm_fir_decimate_q31() runs with 6 taps and decimation factor 2, with 1000 samples input and 500 samples output?
taps = [ -97330866, 172200328, 998872362, 998872362, 172200328, -97330866 ].

And for comparison, this one would be interesting, too: https://godbolt.org/z/G3Gr1r5xc
I wonder if this hard-coded special case can beat the generic implementation. In fact, I'm not sure if it really can.

EDIT: A sinc3 filter with 4 taps seems to be fine for the first stage, too: https://godbolt.org/z/bP9WrdGqq Should be even faster.

The first decimation stage is certainly the most time-critical one. Subsequent stages run at a lower sample rate. They need to process fewer samples per time unit, and the lower sample rate also leads to fewer taps than required for a single-stage decimator. Decimate-by-2 stages need the smallest number of taps, so I guess it might be beneficial to cascade multiple divide-by-2 stages.



Quote
I have no idea about the speed of the other processor (STM32G431KB). In principle it has more clock speed and also has instructions to accelerate the digital filters.

What instructions does it have that the STM32L4 does not have? Aren't both Cortex M4? I may be wrong, but my understanding is that the STM32L4 also supports the Cortex M4 DSP instruction set.

EDIT:

Quote from: datasheet
The STM32L412xx devices are ultra-low-power microcontrollers based on the high-performance Arm® Cortex®-M4 32-bit RISC core operating at a frequency of up to 80 MHz. The Cortex-M4 core features a Floating point unit (FPU) single precision that supports all Arm® single-precision data-processing instructions and data types. It also implements a full set of DSP instructions...
« Last Edit: May 22, 2024, 02:08:50 pm by gf »
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #126 on: May 22, 2024, 05:44:37 am »
To start with the STM32G4xx runs at about twice the processor clock (6 nsec cycle).

It implements an additional DSP unit they call FMAC that includes a local memory for an array of filter coefficients and address generation logic to speed up FIR calculation. It works independent of the M4 core, similar to DMA. It implements a 16 x 16 -> 26 bit MAC. That means it will probably do about 10 or 20 taps within 300 nsec.
They demonstrate HAL support for the FMAC unit in their AN 5305.

Regards, Dieter
« Last Edit: May 22, 2024, 06:55:53 am by dietert1 »
 
The following users thanked this post: gf

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-G431KB)
« Reply #127 on: May 22, 2024, 02:17:59 pm »
I thought I wouldn't get the order from Mouser until next week, but it just got to me, so I'm going to set up the STM32G431KB to test directly with this other microcontroller.

Don't forget to join jumper 17 to have the external clock available!!
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-G431KB)
« Reply #128 on: May 22, 2024, 02:48:18 pm »
On this board it is necessary to link jumper SB13 so that the external clock comes from the STLINK.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #129 on: May 22, 2024, 04:07:14 pm »
I had to update the firmware of the STLINK integrated in the development board.

Apparently it consumes quite a lot of current (or for some other reason I don't know) and I was not able to update through the USB HUB I have on my desk.

I had to directly connect the NUCLEO-G431KB board with a short USB cable to the computer to be able to do the firmware update.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #130 on: May 22, 2024, 04:27:56 pm »
I have made the update with the option:
MCO = 1/5 (5MHz).
Because the STLINK has a frequency of 25MHz and that way it sends 5MHz through jumper SB13 to the PF0-OSC-IN input of the STM32G431KB microcontroller.

I am still having connection problems through the cable connected to the USB HUB
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #131 on: May 22, 2024, 04:40:31 pm »
Fortunately, I found a long (150cm) microUSB cable with which, at last, the development board works.

The STM32G431KB definitely does not like connections to the computer via a USB HUB.

The maximum consumption that I have been able to measure is 38mA for the whole board (STLINK + STM32) running at full speed. It doesn't seem too much current, but, for some reason, it didn't work before.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #132 on: May 22, 2024, 05:05:06 pm »
@Picuino, could you please also check how fast arm_fir_decimate_q31() runs with 6 taps and decimation factor 2, with 1000 samples input and 500 samples output?
taps = [ -97330866, 172200328, 998872362, 998872362, 172200328, -97330866 ].

Well, STM32G431KB running at 170MHz, optimization -O3, filtering 1000 samples with decimation at 500 samples output.
Slow function arm_fir_decimate_q31().
Running time: 318us

EDIT:
Fast function arm_fir_decimate_fast_q31().
Running time: 354us

It is not very logical. I'm going to try optimizing for speed.
« Last Edit: May 22, 2024, 05:08:14 pm by Picuino »
 
The following users thanked this post: gf

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #133 on: May 22, 2024, 05:09:42 pm »
Optimizing for speed -Ofast, the fast function maintains a long execution time (354us).
And the slow function maintains a short runtime (318us).
« Last Edit: May 22, 2024, 05:12:08 pm by Picuino »
 

Online Tation

  • Regular Contributor
  • *
  • Posts: 106
  • Country: pt
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #134 on: May 22, 2024, 05:46:28 pm »
The maximum consumption that I have been able to measure is 38mA for the whole board (STLINK + STM32) running at full speed. It doesn't seem too much current, but, for some reason, it didn't work before.

Nucleo boards do have a hand-removable jumper on the underside (near the reset button) allowing you to measure current consumption for the MCU without the ST-link stuff.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #135 on: May 22, 2024, 08:35:42 pm »
Yes, I know it. I just wanted to measure the total current to check if there was any consumption problem due to which the USB cable that I used to use before without problem and now does not work with this new board.



Changing the subject, I am configuring the TIMER2 for PWM output and everything seems to work fine, except the period which is little bit larger than expected.
The clock is 170 MHz, so I have set the prescaler to 170, the period to 1000 and the duty cycle to 500.

The period is 50% as I expected, but it is worth 1008us, 8us more than expected.
I have done checks and the micro is working with external clock which has a frequency of 4.99986MHz on my oscilloscope.
I can't explain this discrepancy.
« Last Edit: May 23, 2024, 07:47:28 am by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #136 on: May 22, 2024, 08:50:44 pm »
Solution:
Prescaler by 169 so that it divides by 170.
Counter period in 999 to divide by 1000.

Now, PWM output has 999.972 Hz
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #137 on: May 22, 2024, 10:07:50 pm »
@Picuino, could you please also check how fast arm_fir_decimate_q31() runs with 6 taps and decimation factor 2, with 1000 samples input and 500 samples output?
taps = [ -97330866, 172200328, 998872362, 998872362, 172200328, -97330866 ].

Well, STM32G431KB running at 170MHz, optimization -O3, filtering 1000 samples with decimation at 500 samples output.
Slow function arm_fir_decimate_q31().
Running time: 318us

EDIT:
Fast function arm_fir_decimate_fast_q31().
Running time: 354us

It is not very logical. I'm going to try optimizing for speed.

That's a bit disappointing for the 2x faster µC :(

Could you also try to copy my function into your program and test the same? https://godbolt.org/z/bP9WrdGqq
It's hardcoded for 4 taps and 2x decimation - no unnecessary generic sctuff. If it also does not run faster then :-//
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #138 on: May 23, 2024, 03:57:19 am »
If you look into the CubeMX clock setup page, you will likely see that flash memory clock is less than CPU clock, maybe a factor two. In order to get that factor two you may have to run code from RAM. On the first page of the G4 datasheet they mention a special 32K block "CCM SRAM" to be used as routine booster.
There is an application note AN4296 on how to get this going.

Regards, Dieter
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #139 on: May 23, 2024, 07:40:59 am »
YES, in fact I had already tested that functionality to turn a pin on and off very fast.
https://www.eevblog.com/forum/microcontrollers/starting-with-stm32-(nucleo-l412kb)/msg5499019/#msg5499019

Thanks, I will try with RAM execution.
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #140 on: May 23, 2024, 08:27:45 am »
If you look into the CubeMX clock setup page, you will likely see that flash memory clock is less than CPU clock, maybe a factor two.

I did take a closer look to the datasheet and the reference manual (RM0440).

The datasheet claims

Quote from: datasheet
Arm ® 32-bit Cortex®-M4 CPU with FPU, Adaptive real-time accelerator (ART Accelerator) allowing 0-wait-state execution from Flash memory, frequency up to 170 MHz with 213 DMIPS

According to the reference manual, 4 wait states (= 5 CPU cycles latency) must be programmed for the flash @170MHz. OTOH, the width of the G431 flash is 64 bits, so four 16-bit instructions can be fetched with one access.

In order to avoid a pipeline stall due to wait states every 4 instructions, prefetch must be enabled (figure 9 in RM0440). Depending on the executed instruction mix I guess it may come close to the promised "0-wait-state execution" if prefetch and I-cache are enabled. The reference manual sais that I-cache is enabled by default, but prefetch is off by default.

EDIT:

Found these slides which give more insight into how the ART Accelerator works:
https://www.st.com/resource/en/product_training/STM32G4-Memory-Flash_FLASH.pdf

My conclusion is that small loops (which fit entirely into the I-cache) can benefit from I-cache alone (except for the first loop pass), while sequential code requires prefetch in order to mitigate the wait states. When prefetch is enabled, the I-cache degenerates mostly to a cache for instructions at branch targets.

What I still do not understand:
The slides say that 7 wait states are required @170Mhz, while RM0440 only ask for 4 wait states @170MHz?
« Last Edit: May 23, 2024, 11:11:27 am by gf »
 
The following users thanked this post: gpr

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #141 on: May 23, 2024, 01:43:48 pm »
Input buffer = 1000 samples
Output buffer = 500 samples
FIR coefficients = 6

arm_fir_decimate_q31() running in RAM: 318us

arm_fir_decimate_fast_q31() running in RAM: 354us

Exactly the same times as running in flash.
 
The following users thanked this post: gf

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #142 on: May 23, 2024, 01:52:19 pm »
decimate_2_sinc3() with 4 coefficients, running in flash: 80us
 
The following users thanked this post: gf

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #143 on: May 23, 2024, 01:55:52 pm »
I am going to start up the milliohm meter that I already had built, with the STM32. I will have to be careful with the voltages (5v for the milliohm meter and 3.3V for the microcontroller).
This will allow me to get the STM32 working before designing and assembling a specific stage for it with the INA826 and the OPA2325.

I need to start up:
  1. Two ADC @ 2.833MHZ synchronized to sample at the same time.
  2. DMA for 2 ADC
  3. One DAC for output reference, with DMA.
  4. One DAC for output signal, with DMA and synchronized with ADC.
  5. A digital FIR filter with decimation.

EDIT:
When I finish getting all the STM32 peripherals up and running, I will continue the thread on the main project: building a Homebrew Lock-In amplifier.
I hope it is not too confusing. I have preferred to separate it because it is one thing to learn how to program the STM32 and another thing what I am going to do with it.
« Last Edit: May 23, 2024, 04:18:38 pm by Picuino »
 
The following users thanked this post: mskeete

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #144 on: May 23, 2024, 07:49:20 pm »
I can't get the DAC to extract values from the buffer with DMA.
I have only been able to get it to output individual values without DMA.
ChatGPT does not help either.

dac.c:
Code: [Select]
#include <ctype.h>
#include "main.h"
#include "dac.h"

uint16_t dac1_buff[DAC1_BUFF_SIZE];
uint16_t dac2_buff[DAC2_BUFF_SIZE];

void dac1_dma_init(void) {
    /*
     * DAC Initialization
     */

    // Configure PA4 for analog output
    GPIO_InitTypeDef GPIO_InitStruct = { 0 };
    __HAL_RCC_GPIOA_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_4;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);


    // Configure DAC1
    __HAL_RCC_DAC1_CLK_ENABLE();

    DAC_HandleTypeDef hdac1;
    hdac1.Instance = DAC1;
    if (HAL_DAC_Init(&hdac1) != HAL_OK) {
        dac_Error_Handler();
    }

    /*
     *  DAC channel OUT1 config
     */
    DAC_ChannelConfTypeDef sConfig = { 0 };
    sConfig.DAC_HighFrequency = DAC_HIGH_FREQUENCY_INTERFACE_MODE_AUTOMATIC;
    sConfig.DAC_DMADoubleDataMode = DISABLE;
    sConfig.DAC_SignedFormat = DISABLE;
    sConfig.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE;
    sConfig.DAC_Trigger = DAC_TRIGGER_T2_TRGO;
    sConfig.DAC_Trigger2 = DAC_TRIGGER_NONE;
    sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
    sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_EXTERNAL;
    sConfig.DAC_UserTrimming = DAC_TRIMMING_FACTORY;
    HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CHANNEL_1);

    // Enable the DAC channel
    HAL_DAC_Start(&hdac1, DAC_CHANNEL_1);

    // Configure DMA1 Channel 3
    DMA_HandleTypeDef hdma1_dac1;
    __HAL_RCC_DMA1_CLK_ENABLE();

    hdma1_dac1.Instance = DMA1_Channel3;
    hdma1_dac1.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma1_dac1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma1_dac1.Init.MemInc = DMA_MINC_ENABLE;
    hdma1_dac1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma1_dac1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma1_dac1.Init.Mode = DMA_CIRCULAR;
    hdma1_dac1.Init.Priority = DMA_PRIORITY_HIGH;

    HAL_DMA_Init(&hdma1_dac1);


    // Enable interrupts of full transfer and half transfer
    __HAL_DMA_ENABLE_IT(&hdma1_dac1, DMA_IT_TC | DMA_IT_HT);

    // Link DMA to DAC
    __HAL_LINKDMA(&hdac1, DMA_Handle1, hdma1_dac1);

    // Configure NVIC for DMA
    HAL_NVIC_SetPriority(DMA1_Channel3_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn);

    HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 2048);

    // Start DAC with DMA
    HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t*) dac1_buff,
    DAC1_BUFF_SIZE * sizeof(uint16_t) / sizeof(uint32_t),
    DAC_ALIGN_12B_R);
}

void DMA1_Channel3_IRQHandler(void) {
    // Half transfer
    if (DMA1->ISR & DMA_ISR_HTIF3) {
        DMA1->IFCR = DMA_IFCR_CHTIF3;

    }

    // Full transfer
    if (DMA1->ISR & DMA_ISR_TCIF3) {
        DMA1->IFCR = DMA_IFCR_CTCIF3;

    }
}

void dac_Error_Handler(void) {

}


timer.c:
Code: [Select]
#include <ctype.h>
#include "timer.h"
#include "stm32g4xx_hal.h"

void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim);
void timer_Error_Handler(void);

void timer2_init(void) {
    TIM_ClockConfigTypeDef sClockSourceConfig = { 0 };
    TIM_MasterConfigTypeDef sMasterConfig = { 0 };
    TIM_OC_InitTypeDef sConfigOC = { 0 };

    // Initialize TIM2 instance
    TIM_HandleTypeDef htim2;
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 17 - 1;  // Set prescaler. Divide by 17
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 10 - 1;  // Set period to 10
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;

    // Base initialization
    if (HAL_TIM_Base_Init(&htim2) != HAL_OK) {
        timer_Error_Handler();
    }

    // Configure clock source
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) {
        timer_Error_Handler();
    }

    // Initialize PWM
    if (HAL_TIM_PWM_Init(&htim2) != HAL_OK) {
        timer_Error_Handler();
    }

    // Configure master synchronization
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig)
            != HAL_OK) {
        timer_Error_Handler();
    }

    // Configure PWM channel
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 5;  // Set pulse width to 5
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_4)
            != HAL_OK) {
        timer_Error_Handler();
    }

    // Post-initialize TIM2 (configure GPIO)
    HAL_TIM_MspPostInit(&htim2);

    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
}

void timer_Error_Handler(void) {

}

Timer2 is running with PWM output at PA10.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #145 on: May 23, 2024, 08:18:47 pm »
No error condition?
Why this expression: DAC1_BUFF_SIZE * sizeof(uint16_t) / sizeof(uint32_t)
in HAL_DAC_Start_DMA(&hdac1,...
Shouldn't this be the number of DMA cycles = DAC1_BUFF_SIZE?
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #146 on: May 23, 2024, 08:27:43 pm »
Changed. The DAC output remains at 0 Volts.

Does anyone have an example of a DAC working with DMA that I can adapt?
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #147 on: May 24, 2024, 05:33:25 am »
The G4xx repository provides 16 examples, among them DMA and waveform generation. I noticed that they use Timer 6 for DAC_Trigger2.
Another description is here: https://www.theengineeringprojects.com/2021/12/using-dac-with-stm32.html
They also use Timer 6. It seems to be for F303.
Last year i ordered some STM32G474RCT in order to have a look at their high resolution timer. The chips were mounted to SMD adapters and are waiting for a test..

Regards, Dieter
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #148 on: May 24, 2024, 08:09:19 am »
Thank you.
I'm going to start a Cube MX project from scratch to get the DAC up and running and from there I'll take the code to the other project where I'll put everything together.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #149 on: May 24, 2024, 11:19:26 am »
The detailed reference manual is also a good source of details for programming the microcontroller.
It lists all possible synchronization sources and which register to program them with.
This CubeMX and HAL libraries thing is giving me more problems in the end than looking for detailed information of each register and programming them directly.
Since I don't have to make programs for many different micros, portability doesn't matter so much to me.

https://www.st.com/resource/en/reference_manual/rm0440-stm32g4-series-advanced-armbased-32bit-mcus-stmicroelectronics.pdf
« Last Edit: May 24, 2024, 12:15:29 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #150 on: May 24, 2024, 06:26:00 pm »
I must be missing something because I can't get DAC1 to start up with DMA.
Without DMA DAC1 does work. And Timer 2 works with PWM without problem.

I have tried generating two new projects with Cube MX, one with timer 2 as trigger and one with timer 6 as trigger. I have also tried to make software triggers and nothing.
I am stuck without knowing what to do.

EDIT: The main.c
Code: [Select]
/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2024 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* 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 ---------------------------------------------------------*/
DAC_HandleTypeDef hdac1;
DMA_HandleTypeDef hdma_dac1_ch1;

TIM_HandleTypeDef htim6;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_DAC1_Init(void);
static void MX_TIM6_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 */

    __disable_irq();

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_DAC1_Init();
    MX_TIM6_Init();
    /* USER CODE BEGIN 2 */

    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    uint16_t dac1_buff[1000];
    for (int i = 0; i < 1000; i++) {
        dac1_buff[i] = i * 4;
    }

    HAL_TIM_Base_Start(&htim6);
   
    HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t*) dac1_buff, 500,
    DAC_ALIGN_12B_R);

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

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

    /** Initializes the RCC Oscillators according to the specified parameters
     * in the RCC_OscInitTypeDef structure.
     */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV1;
    RCC_OscInitStruct.PLL.PLLN = 68;
    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_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

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

/**
 * @brief DAC1 Initialization Function
 * @param None
 * @retval None
 */
static void MX_DAC1_Init(void) {

    /* USER CODE BEGIN DAC1_Init 0 */

    /* USER CODE END DAC1_Init 0 */

    DAC_ChannelConfTypeDef sConfig = { 0 };

    /* USER CODE BEGIN DAC1_Init 1 */

    /* USER CODE END DAC1_Init 1 */

    /** DAC Initialization
     */
    hdac1.Instance = DAC1;
    if (HAL_DAC_Init(&hdac1) != HAL_OK) {
        Error_Handler();
    }

    /** DAC channel OUT1 config
     */
    sConfig.DAC_HighFrequency = DAC_HIGH_FREQUENCY_INTERFACE_MODE_ABOVE_160MHZ;
    sConfig.DAC_DMADoubleDataMode = DISABLE;
    sConfig.DAC_SignedFormat = DISABLE;
    sConfig.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE;
    sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO;
    sConfig.DAC_Trigger2 = DAC_TRIGGER_SOFTWARE;
    sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
    sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_EXTERNAL;
    sConfig.DAC_UserTrimming = DAC_TRIMMING_FACTORY;
    if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CHANNEL_1) != HAL_OK) {
        Error_Handler();
    }
    /* USER CODE BEGIN DAC1_Init 2 */

    HAL_DAC_MspInit(&hdac1);
    /* USER CODE END DAC1_Init 2 */

}

/**
 * @brief TIM6 Initialization Function
 * @param None
 * @retval None
 */
static void MX_TIM6_Init(void) {

    /* USER CODE BEGIN TIM6_Init 0 */

    /* USER CODE END TIM6_Init 0 */

    TIM_MasterConfigTypeDef sMasterConfig = { 0 };

    /* USER CODE BEGIN TIM6_Init 1 */

    /* USER CODE END TIM6_Init 1 */
    htim6.Instance = TIM6;
    htim6.Init.Prescaler = 16;
    htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim6.Init.Period = 99;
    htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    if (HAL_TIM_Base_Init(&htim6) != HAL_OK) {
        Error_Handler();
    }
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig)
            != HAL_OK) {
        Error_Handler();
    }
    /* USER CODE BEGIN TIM6_Init 2 */

    /* USER CODE END TIM6_Init 2 */

}

/**
 * Enable DMA controller clock
 */
static void MX_DMA_Init(void) {

    /* DMA controller clock enable */
    __HAL_RCC_DMAMUX1_CLK_ENABLE();
    __HAL_RCC_DMA1_CLK_ENABLE();

    /* DMA interrupt init */
    /* DMA1_Channel1_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);

}

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

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

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);

    /*Configure GPIO pin : LD2_Pin */
    GPIO_InitStruct.Pin = LD2_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);

    /* USER CODE BEGIN MX_GPIO_Init_2 */
    /* USER CODE END MX_GPIO_Init_2 */
}

/* 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 */
    __disable_irq();
    while (1) {
    }
    /* 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,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
« Last Edit: May 24, 2024, 06:28:31 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #151 on: May 24, 2024, 06:31:52 pm »
The G4xx repository provides 16 examples, among them DMA and waveform generation. I noticed that they use Timer 6 for DAC_Trigger2.

I'm going to try that way.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #152 on: May 24, 2024, 07:00:02 pm »
I got a STM32G474 up and running with the STLink of another Nucleo 64 board. It runs at 150 MHz from HSI and takes 35 mA supply current at 3.3 V. It got a LED blinking, the RTC is running with a 32,768 KHz crystal and the backup battery. The 16 MHz HSI RC generator runs about 1 % fast, good enough for the UART. UART2 is connected to the STLink CDC interface and there is a host interactive in the firmware that supports setting date and time from the host using text messages.

Regards, Dieter
« Last Edit: May 24, 2024, 07:02:12 pm by dietert1 »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #153 on: May 24, 2024, 07:04:45 pm »
I have tried to start the “DAC_DualConversionFromDMA” project from the “STM32G474QETX” repository and have not been able to start it.

I'm sure I'm getting something simple wrong, but I can't figure out what it is.

I have enough for now. I will continue later.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #154 on: May 24, 2024, 09:47:19 pm »
For me the example worked. I collected everything in one C file.

Regards, Dieter
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #155 on: May 25, 2024, 07:18:30 am »
Thank you very much. Finally it works!!!

 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #156 on: May 25, 2024, 12:21:04 pm »
I've figured out what the problem is.
Even if you work with a single DAC channel, you need to configure the DMA as HALFWORD in memory, but as WORD in the peripheral:

Code: [Select]
        hdma_dac1_ch1.Instance = DMA1_Channel1;
        hdma_dac1_ch1.Init.Request = DMA_REQUEST_DAC1_CHANNEL1;
        hdma_dac1_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;
        hdma_dac1_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_dac1_ch1.Init.MemInc = DMA_MINC_ENABLE;
        hdma_dac1_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
        hdma_dac1_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
        hdma_dac1_ch1.Init.Mode = DMA_CIRCULAR;
        hdma_dac1_ch1.Init.Priority = DMA_PRIORITY_LOW;
        if (HAL_DMA_Init(&hdma_dac1_ch1) != HAL_OK) {
            Error_Handler();
        }

With this instruction, doesn't work:
Code: [Select]
hdma_dac1_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #157 on: May 25, 2024, 01:52:57 pm »
Yes, that's a proactive design choice to reserve a 32 bit word for the DAC value when the hardware has only 12 bits.

Today i found a minor problem with the RTC. I put a 1 KOhm resistor between battery plus and the VBat pin in order to measure battery currents. Today i added a 470 nF buffer capacitor on the VBat pin to make that work. Charge current is about 6 uA at 3.11 V battery voltage, discharge current about 0.7 uA.
Now the 16 MHz HSI RC generator is only 0.1 % off. At 170 MHz and with the dual DAC/DMA running supply current became 47 mA.

Regards, Dieter
« Last Edit: May 25, 2024, 02:47:13 pm by dietert1 »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #158 on: May 25, 2024, 06:45:25 pm »
I have programmed a small loop to generate a sinusoidal signal that starts with the value 2047 and ranges from 0 to 4094 in a 256-position buffer.
I have done this so that the DAC outputs a sine wave.
The surprise I found is that the first value (dac1_buff[0]) is zero instead of 2047.

Debugging the loop I find that the first value taken by the index i is 1, not 0.

I don't understand anything, is it a compiler bug?

Code: [Select]
    for (int i = 0; i < DAC1_BUFF_SIZE; i++) {
        dac1_buff[i] = 2047 + 2047 * sin(i * 2.0 * PI / DAC1_BUFF_SIZE);
    }
« Last Edit: May 25, 2024, 06:50:38 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #159 on: May 25, 2024, 06:53:34 pm »
If I rewrite the zero position, then everything works fine.


Code: [Select]
    for (int i = 0; i < DAC1_BUFF_SIZE; i++) {
        dac1_buff[i] = 2047 + 2047 * sin(i * 2.0 * PI / DAC1_BUFF_SIZE);
    }
    dac1_buff[0] = 2047;
    while (1);
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #160 on: May 25, 2024, 07:43:26 pm »
Problem discovered:
The DAC buffer is used only by the DMA, so it is possible for the compiler to optimize the allocation of values knowing that they will not be used later in the program.

Solution: declare the DAC buffer as volatile.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #161 on: May 26, 2024, 12:34:37 pm »
Yesterday i looked into the G4 FMAC unit and found it can't be used very well for mixing the two ADC streams as the unit needs to stop and start in order to change its coefficients.
Today i wrote a little study, simulating the two ADC streams, subtracting their DC component, mixing and filtering them with a second order IIR filter. The procedure uses 22 cycles per ADC sample, that is 88 MHz at 4 MHz ADC rate. I think handwritten assembly using Cortex DSP instructions could reduce that to 10 or 12 cycles in order to run it on a 80 MHz L4 MCU. The filter has a time constant of 1024 cycles for both stages and serves for downsampling by a factor 1000.
The simulated measurement signal is of low amplitude with gaussian noise added in. The reference signal is clean with full amplitude. The diagram shows the last 1024 sample data block in the simulation, with 180° phase shift. The simulation starts with 500 blocks of 90° phase shift for zero output signal. The second diagram shows the downsampled output signal for 1000 blocks. This would then feed into a more sophisticated filter to limit output bandwidth as necessary.

Regards, Dieter
« Last Edit: May 26, 2024, 01:09:09 pm by dietert1 »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #162 on: May 26, 2024, 01:34:31 pm »
Thanks dietert1.
Now I am still setting up the peripherals.
The two DACs are already running with DMA, independently with a frequency of 708kHz (1/4 the frequency of the ADCs, which is worth 2833kHz).
The DACs and the DMA I have configured them with "Double data mode" which means that writes of two values are done at the same time (32bits total). That saves DMA transfers and saturates less the RAM memory bus.

At this moment I am starting up the ADCs. In a previous program they worked without problem with registers, but now I am trying with HAL libraries.
The truth is that I'm having a hard time choosing between the two worlds (HAL vs registers).
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #163 on: May 26, 2024, 02:18:01 pm »
I have already set it up:
  DAC1 and DAC2 with DMA (with timer2 timebase)
  ADC1 and ADC2 with DMA (not syncronized)
  UART2


 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #164 on: May 26, 2024, 08:47:54 pm »
I have already synchronized the start of the conversion of the two ADCs thanks to a synchronization signal from Timer 3.

Attached is the program as it is going for now.
I am not using Cube MX in this project. I have another parallel project where I configure the peripherals with Cube MX and then I pass the changes I am interested in to my main project.

It's more work, but it gives me a greater sense of control.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #165 on: May 28, 2024, 08:36:07 am »
Meanwhile i tested a little more. I implemented the two ADCs of the G474 in multimode DMA configuration (one DMA channel with both ADC results in one 32 bit word). This worked reliably at 150 MHz core clock, so the ADC sampling rate is 2.5 MHz. Maybe with a HSE crystal it can also work at 170 MHz. The G474 errata sheet mentions an issue when using the ADCs with one DMA channel in interleaved mode.
Then i wired the dual DAC from DMA to generate the two test signals, this time both full amplitude without additional noise, at 90° or 180° relative phase like in the simulation. Carrier frequency is 55 KHz.
Today i got DAC2 going to output the downsampled result. DAC2 was missing in CubeMX and in the include files. Output rate is 2.5 MHz / 1024 = 2.44 KHz. Output signal looks as expected with a phase change every 10 cycles. The 90° phase case appears in the middle of the DAC range at about 1.25 V, the 180° case (negative result) was scaled such as to fit.
The G474 still handles the host console without visible disruptions of signal processing. I put the filtering into an application specific HAL_Delay() implementation, polling flags set by the ADC half complete and complete callback routines. All pretty standard.

Regards, Dieter
« Last Edit: May 28, 2024, 03:37:29 pm by dietert1 »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #166 on: May 28, 2024, 09:36:01 am »
... polling flags set by the ADC half complete and complete callback routines. All pretty standard.
That's what I'm missing. I'm filtering within the interrupt routine and that takes a long time for an interrupt.

As soon as I have results I will post it. For now a simple filtering of multiplication and addition takes 20% of the microcontroller processing time.
Another 20% to do something as simple as adding the ADC measurements to calculate the average and then subtracting it.
I am working at 170MHz clock and 2833kHz ADC sampling.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #167 on: May 28, 2024, 04:07:46 pm »
The problem with using separate DMA channels for each ADC is that you need to assert the two DMAs remain in sync. I mean the half-complete and complete interrupts of one channel don't really indicate the state of the other channel. I remembered this problem from a digital audio solution on a MCU that did not support using one DMA channel for both stereo channels. It needed a supervisor component in the firmware to detect a possible problem and solve it by some kind of restart.
The multimode DMA in the STM32 saves this, but at 170 MHz it would fail after 100 000 to 500 000 DMA cycles, leaving the second ADC stuck without any further trigger. At 150 MHz there is no such problem.

Regards, Dieter
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #168 on: May 28, 2024, 04:21:35 pm »
I will keep this in mind. I will go down to 150MHz.

In my case the DAC2 should be in sync with the ADCs, but not much.
First the ADCs capture a signal to the ADC buffer. Then from the ADC buffer a filter and decimation operation is done to another DAC buffer. Once the DAC buffer is written, the DAC can start operating with that data. The DAC works with a delay with respect to the ADC, but its frequency will be equal to the ADC frequency divided by the decimation (which in principle will be 1/64 in two steps of 1/8 and 1/8).

The DAC clock is the Timer2 and the ADC clock is the system clock divided by 4. The DMA in principle serves them both as they request transfers.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #169 on: May 29, 2024, 08:08:26 am »
I will implement this FIR filter with decimation:

Code: [Select]
#
# Python script to calculate
# FIR coefficients with remez algorithm
#

import numpy as np
from scipy import signal
import matplotlib.pyplot as plt

fs = 2500000/8  # Sample rate, Hz
cutoff = 5000   # Desired cutoff frequency, Hz
R = 8           # Decimation factor
numtaps = 128   # Size of the FIR filter.



def plot_response(fs, w, h, title):
    plt.figure()
    plt.plot(0.5*fs*w/np.pi, 20*np.log10(np.abs(h)))
    plt.ylim(-100, 5)
    plt.xlim(0, 0.1*fs)
    plt.grid(True)
    plt.xlabel('Frequency (Hz)')
    plt.ylabel('Gain (dB)')
    plt.title(title)
    plt.show()


bands = [0, cutoff, 0.5*fs/R, 0.5*fs]
gains = [1, 0]

taps = signal.remez(numtaps, bands, gains, fs=fs)

q31_taps =[round(t*2**31) for t in taps]
q31_taps =[t if t>0 else t+0x100000000 for t in q31_taps]
q31_taps =[f"0x{t:08X}" for t in q31_taps]
print(", ".join(q31_taps))


w, h = signal.freqz(taps, [1], worN=2000)
plot_response(fs, w, h, "Low-pass Filter")


Coefficients:
Code: [Select]
0x000049C7, 0x0000772C, 0x0000CB3A, 0x00013881, 0x0001BB5F, 0x00024B04,
0x0002D885, 0x00034EBB, 0x0003929D, 0x000384B1, 0x0003039C, 0x0001EFAF,
0x00002F49, 0xFFFDB3F3, 0xFFFA7FA7, 0xFFF6A9B3, 0xFFF2627B, 0xFFEDF586,
0xFFE9C926, 0xFFE65B27, 0xFFE43A12, 0xFFE3FB19, 0xFFE62CEB, 0xFFEB480A,
0xFFF39DB4, 0xFFFF46B9, 0x000E13FE, 0x001F827C, 0x0032B476, 0x004671A1,
0x00592F84, 0x006922CC, 0x007459B4, 0x0078DEB1, 0x0074E1CF, 0x0066E66C,
0x004DF219, 0x0029B90B, 0xFFFAC421, 0xFFC28C93, 0xFF8389B0, 0xFF412DC7,
0xFEFFD05B, 0xFEC4850D, 0xFE94DFFB, 0xFE76A9FE, 0xFE6F8879, 0xFE849DEF,
0xFEBA295F, 0xFF132B0E, 0xFF91159F, 0x003391C1, 0x00F85A01, 0x01DB32D5,
0x02D6012C, 0x03E0FFE0, 0x04F31216, 0x06022E96, 0x0703DE4B, 0x07EDC657,
0x08B6356D, 0x0954AB63, 0x09C25247, 0x09FA6101, 0x09FA6101, 0x09C25247,
0x0954AB63, 0x08B6356D, 0x07EDC657, 0x0703DE4B, 0x06022E96, 0x04F31216,
0x03E0FFE0, 0x02D6012C, 0x01DB32D5, 0x00F85A01, 0x003391C1, 0xFF91159F,
0xFF132B0E, 0xFEBA295F, 0xFE849DEF, 0xFE6F8879, 0xFE76A9FE, 0xFE94DFFB,
0xFEC4850D, 0xFEFFD05B, 0xFF412DC7, 0xFF8389B0, 0xFFC28C93, 0xFFFAC421,
0x0029B90B, 0x004DF219, 0x0066E66C, 0x0074E1CF, 0x0078DEB1, 0x007459B4,
0x006922CC, 0x00592F84, 0x004671A1, 0x0032B476, 0x001F827C, 0x000E13FE,
0xFFFF46B9, 0xFFF39DB4, 0xFFEB480A, 0xFFE62CEB, 0xFFE3FB19, 0xFFE43A12,
0xFFE65B27, 0xFFE9C926, 0xFFEDF586, 0xFFF2627B, 0xFFF6A9B3, 0xFFFA7FA7,
0xFFFDB3F3, 0x00002F49, 0x0001EFAF, 0x0003039C, 0x000384B1, 0x0003929D,
0x00034EBB, 0x0002D885, 0x00024B04, 0x0001BB5F, 0x00013881, 0x0000CB3A,
0x0000772C, 0x000049C7

 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #170 on: May 29, 2024, 09:00:40 am »
I will implement this FIR filter with decimation:

Only the final stage needs to reject frequencies >= fs/R/2 (almost) completely. For the first decimation stage (fs=2,500,000 MSa/s), you can use a halfband filter with a softer transition band like fs/128...fs/8-fs/128, leading to a lower number of taps (say 40 or 48, depending on the desired stop band attenuation).
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #171 on: May 29, 2024, 09:42:04 am »
The first stage I am going to do something very simple, because there is not enough time to do more processing:

Code: [Select]
accumulator = 0;
accumulator += adc1_buff[0] * adc2_buff[0];
accumulator += adc1_buff[1] * adc2_buff[1];
accumulator += adc1_buff[2] * adc2_buff[2];
accumulator += adc1_buff[3] * adc2_buff[3];
accumulator += adc1_buff[4] * adc2_buff[4];
accumulator += adc1_buff[5] * adc2_buff[5];
accumulator += adc1_buff[6] * adc2_buff[6];
accumulator += adc1_buff[7] * adc2_buff[7];
dac1_buff[0] = accumulator;


A simple sum of all 8 values to produce one.
The next stage is the FIR filter, which again divides the samples by 8, arriving at an output sampling frequency of 39062.5 Hz.
« Last Edit: May 29, 2024, 09:44:49 am by Picuino »
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #172 on: May 29, 2024, 09:48:57 am »
Retracted my last post. IIRC, you desire carrier frequencies up to 100 kHz. The mixing product also includes 2*fc = 200kHz, and that would no longer be < 156.25 kHz.
« Last Edit: May 29, 2024, 10:57:57 am by gf »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #173 on: May 29, 2024, 11:42:48 am »
Anyway I will try both methods (the one I described above and the ADC hardware decimate method) to see the results of each.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #174 on: May 29, 2024, 05:07:22 pm »
You will get better results if you mix the ADC data at 2.5 MHz and then filter and downsample.
Above i posted the code "lockin.c". If you define BuffSize as 8 and ExpAverScale as 3, the filter state variables llOut1 and llOut can be int32_t, as 12 + 12 + 6 = 30 bit. The mixer+filter will need about 15 cycles per combined ADC1/ADC2 sample when compiled with optimizer. The IIR low pass filter is superior to a boxcar filter.
When trying to process with 8 sample ADC buffers you may have to bypass the HAL style DMA interrupt and callback scheme in order to avoid the overhead. I mean if the delay between ADC input and DAC output matters.

Regards, Dieter
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #175 on: May 29, 2024, 07:51:13 pm »
You will get better results if you mix the ADC data at 2.5 MHz and then filter and downsample.
Above i posted the code "lockin.c". If you define BuffSize as 8 and ExpAverScale as 3, the filter state variables llOut1 and llOut can be int32_t, as 12 + 12 + 6 = 30 bit. The mixer+filter will need about 15 cycles per combined ADC1/ADC2 sample when compiled with optimizer.

With 32-bit accumulator it is indeed not very expensive.

Quote
The IIR low pass filter is superior to a boxcar filter.

Yes, your 2nd order IIR is better than a 1st order boxcar. However, a 2nd order boxcar has even more attenuation for the frequency ranges that matter for the first decimation stage (fs/8 +- 20kHz, fs/4 +- 20kHz, 3*fs/8 +- 20kHz, fs/2 - 20kHz ... fs/2), and implemented as a CIC decimator, I think it needs even a few cycles less than your 2nd order IIR.

EDIT: https://godbolt.org/z/3vs554f49
« Last Edit: May 29, 2024, 08:38:09 pm by gf »
 
The following users thanked this post: dietert1

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #176 on: May 30, 2024, 05:21:05 am »
When i think about those integrators in the CIC decimator: What is a good method to prevent them from accumulation of residual DC input and overflow? Some feedback scheme?
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #177 on: May 30, 2024, 07:03:23 am »
When i think about those integrators in the CIC decimator: What is a good method to prevent them from accumulation of residual DC input and overflow? Some feedback scheme?

There is no need to prevent the integrators from overflow but you deliberately let them wrap around. They only need to have enough bits.
See https://www.dsprelated.com/showarticle/1337.php for more details.

At the end, the CIC decimator in my example is exactly equivalent to a FIR filter with a 15-tap triangular kernel (which is again equivalent to two cascaded 8-tap boxcar filters), followed by 8x downsampling. If you feed DC in, you get DC out. It's a lowpass - there is no DC accumulation at the output. DC gain is also 64 [ like the gain of two cascaded IIR filters with transfer funtion 1/(1-0.875*z-1) ].

It is rather an (undesired) property of fixed point IIR filters that they don't decay to zero when the input goes to zero, but retain a small residual DC value at the output.
« Last Edit: May 30, 2024, 08:20:21 am by gf »
 
The following users thanked this post: dietert1

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #178 on: May 30, 2024, 08:01:50 am »
I had not seen the program, thanks I will try it.

I am finding that the filters take up a large amount of time during which the microcontroller locks up. Later on I would like to attend commands by UART and send information. I think it would be desirable to split the filter into several stages and return control back to the micro every so often to attend to other tasks.
I would like to use a cooperative RTOS for this, with its yield() command for context switching.
Would FreeRTOS be ok or is there a reliable alternative?
 

Online Tation

  • Regular Contributor
  • *
  • Posts: 106
  • Country: pt
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #179 on: May 30, 2024, 08:47:18 am »
Wouldn't it be easier to use a preemptive RTOS to automatically switch out from the filter code? FreeRTOS is perfectly able to do it (or work in cooperative mode, if you want).
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #180 on: May 30, 2024, 10:57:59 am »
How is it easier to control the time of each routine? it seemed to me that with the cooperative mode it would be easier, but maybe I'm wrong.

One of the things I want to do in idle time is to calculate the sine function to generate the output reference signal, to change the output frequency. It is a time-consuming operation and can not block the microcontroller, because the input filtering is a priority.
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #181 on: May 30, 2024, 11:03:12 am »
One of the things I want to do in idle time is to calculate the sine function to generate the output reference signal, to change the output frequency. It is a time-consuming operation and can not block the microcontroller, because the input filtering is a priority.

What exactly do you have in mind? Wouldn't you pre-calculate the table before you start measurements and run the whole ADC/DAC/filter stuff?

EDIT: Or do you want arbitrary frequency (which is not necessarily an integer fraction of the sample rate)? Then you need a DDS.
« Last Edit: May 30, 2024, 11:06:26 am by gf »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #182 on: May 30, 2024, 11:05:50 am »
I am thinking of being able to generate several output frequencies and having them all pre-calculated would take up a lot of memory.
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #183 on: May 30, 2024, 11:11:44 am »
I am thinking of being able to generate several output frequencies and having them all pre-calculated would take up a lot of memory.

But you do not need to measure while you change the frequency, do you?
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #184 on: May 30, 2024, 11:15:56 am »
Yes, I don't need to measure while changing the frequency.
I don't know what I was thinking.  :-[

I want to learn how to use RTOS anyway. It can come in handy and it's a utility I haven't used before on other smaller micros.
 

Online Tation

  • Regular Contributor
  • *
  • Posts: 106
  • Country: pt
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #185 on: May 30, 2024, 11:40:30 am »
How is it easier to control the time of each routine? it seemed to me that with the cooperative mode it would be easier, but maybe I'm wrong.

You do not control that. Each task runs until: a) done (for now), then yields; b) runs until the RTOS decides to switch to another task, then it is interrupted and continued, at the same instruction, in the future.

If you need to synchronize tasks (e. g.: apply the filter only when some buffer is valid), the RTOS provides mechanisms for tasks to send signals to another tasks.

One of the things I want to do in idle time is to calculate the sine function to generate the output reference signal, to change the output frequency. It is a time-consuming operation and can not block the microcontroller, because the input filtering is a priority.

Look for the state-variable approach to generate samples of a sine. No need for tables and each sample requires, if my memory serves me well, just 2 + and 2 *.
 

Online Tation

  • Regular Contributor
  • *
  • Posts: 106
  • Country: pt
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #186 on: May 30, 2024, 11:54:40 am »
For the "state variable" oscillator: https://www.njohnson.co.uk/pdf/drdes/Chap7.pdf
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #187 on: May 30, 2024, 12:51:05 pm »
For the "state variable" oscillator: https://www.njohnson.co.uk/pdf/drdes/Chap7.pdf

If I understand correctly, the STM32G4 has a CORDIC accelerator unit.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #188 on: May 30, 2024, 01:52:10 pm »
For the "state variable" oscillator: https://www.njohnson.co.uk/pdf/drdes/Chap7.pdf

Great!!!
I had no idea you could make such an accurate digital oscillator with so few calculations. It can be implemented in real time for the lower frequencies.


If I understand correctly, the STM32G4 has a CORDIC accelerator unit.

Yes, but I don't know how to use it yet.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #189 on: May 30, 2024, 04:33:21 pm »
Yes, your 2nd order IIR is better than a 1st order boxcar. However, a 2nd order boxcar has even more attenuation for the frequency ranges that matter for the first decimation stage (fs/8 +- 20kHz, fs/4 +- 20kHz, 3*fs/8 +- 20kHz, fs/2 - 20kHz ... fs/2), and implemented as a CIC decimator, I think it needs even a few cycles less than your 2nd order IIR.

EDIT: https://godbolt.org/z/3vs554f49

The problem with this code is that I don't actually know the DC voltage level of the signals. This level has to be calculated in real time with the average of the sampled values, applying a good low pass filter to each of the two signals (ADC1 and ADC2).

What I had started to program was a boxcar filter that multiplies the two values with DC voltage and then subtracts the DC voltage value.

Code: [Select]
for (i=0; i<N; i++) {
    acc += ADC1 * ADC2;
    mean_ADC1 += ADC1;
    mean_ADC2 += ADC2;
}

out = acc / N  - mean_ADC1 / N * mean_ADC2 / N;
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #190 on: May 30, 2024, 04:38:55 pm »
It is assumed to work for a large number of samples because the variable terms cancel:

Sum( [VAC1 + VDC1] * [VAC2 + VDC2] ) =
Sum( VAC1 * VAC2 + VAC2 * VDC1 + VAC1 * VDC2 + VDC1 * VDC2] ) =
Sum( VAC1 * VAC2 + VDC1 * VDC2] )

Because:  VAC2 * VDC1 + VAC1 * VDC2 tends to cancel out and is worth zero.


EDIT:
It would be better to have a real-time estimate of the average of the two signals, but that means averaging with low-pass filtering the two signals separately in real time.
« Last Edit: May 30, 2024, 04:41:10 pm by Picuino »
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #191 on: May 30, 2024, 05:35:28 pm »
I thought that one could build the input circuitry with a high pass and assign the DC level about in the middle of the ADC input range. One could measure and then subtract the residual VDC1*VDC2 offset by turning off modulation for short periods, let's say once per second or once every ten seconds.

Regards, Dieter
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #192 on: May 31, 2024, 09:28:14 am »
It is assumed to work for a large number of samples because the variable terms cancel:

Sum( [VAC1 + VDC1] * [VAC2 + VDC2] ) =
Sum( VAC1 * VAC2 + VAC2 * VDC1 + VAC1 * VDC2 + VDC1 * VDC2] ) =
Sum( VAC1 * VAC2 + VDC1 * VDC2] )

Because:  VAC2 * VDC1 + VAC1 * VDC2 tends to cancel out and is worth zero.

Let's look at the mixing product of two sine wave signals (same frequency and phase) with DC offsets:

    (DC1+A1*sin(x)) * (DC2+A2*sin(x)) = 0.5*A1*A2 + DC1*DC2 + (A2*DC1+A1*DC2)*sin(x) - 0.5*A1*A2*cos(2*x)

We get a DC component (blue), a sine wave with carrier frequency and amplitude A2*DC1+A1*DC2, and a cosine wave with 2x carrier frequency and amplitude 0.5*A1*A2.

The DC value we want to measure is 0.5*A1*A2, but it is biased by DC1*DC2, i.e. we measure 0.5*A1*A2 + DC1*DC2 instead of the true value. The purple terms are AC components, and they must be filtered out by the lowpass filter after the mixer. This leads to the following objectives:

1) We want to get rid of the bias DC1*DC2

2) The lowpass filter must be able to remove the AC components (or at least attenuate them sufficiently).
     Here I'd like to distinguish two cases:

2a) If the lowpass filter has zeros at the carrier frequency and 2x carrier frequncy, then it can eliminate them completely

2b) If 2a is not granted, then the removal of the AC components relies on the filter's stop band attenuation (which is limited)

Why do I distinguish 2a and 2b? Because it makes big difference whether you allow arbitrary carrier frequencies or not. Arbitrary carrier frequencies imply case 2b, which means that you require a computationally expensive filter with high selectivity and high stop band attenuation. OTOH, for case 2a, even a trivial 1st order boxcar filter can eliminate the carrier and all its harmonics completely if it is  granted that the length of the boxcar is an exact integer multiple of the carrier period.

[ Let's reconsider your AVR milliohmmeter. Why did it work pretty well although it was much simpler? Because you had a frequency plan. The carrier frequency was exactly 1/16 of the sample rate and the integration period was an exact integer multiple of the carrier period. Furthermore, the reference signal was numerically generated and had a DC offset of exactly zero. Under these conditions, the bias DC1*DC2 becomes zero as well, and the boxcar is able to eliminate the carrier and its harmonics completely. So it did not matter if there was an uncompensated DC offset in the signal being measured. ]

Back to case 2b: Here we also must take care that the amplitude A2*DC1+A1*DC2 of the sin(x) term of the mixing product does not become too large, because the stopband attenuation of the lowpass filter after the mixer is limited. After attenuation by the filter, the residual amplitude of the sin(x) term should be well below the level of the signal we want to measure (which is 0.5*A1*A2). There are two ways to accomplish that: Either the residual DC offsets must be suffiently low, or the filter must be designed with an even higher stop band attenuation, making it even more computationally expensive.

Quote
It would be better to have a real-time estimate of the average of the two signals, but that means averaging with low-pass filtering the two signals separately in real time.

Yes, it means exactly that. You could do it in principle, but it is computationally expensive (possibly too expensive).
« Last Edit: May 31, 2024, 09:38:20 am by gf »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #193 on: May 31, 2024, 01:56:09 pm »
Yes, having an external frequency and not being able to synchronize with it gives a lot of problems.

After several problems and difficulties, I have managed to get FreeRTOS up and running, so now I can devote time to making the filters and we will see what can be achieved with each technique (boxcar, IIR, etc).


EDIT:
I'll start by doing a simple BOXCAR to reduce the samples by a ratio of 1/8, followed by an IIR filter for each of the three signals (sum([ADC1-DC1]*[ADC2-DC2]), sum(ADC1) and sum(ADC2)).
   DC1 = sum(ADC1) + IIR (previous)
   DC2 = sum(ADC2) + IIR (previous)
There may be better solutions, but this one has the advantage of being simple for me and leaving enough processor time to perform the filtering at lower sample rates.
« Last Edit: May 31, 2024, 02:02:34 pm by Picuino »
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #194 on: May 31, 2024, 03:45:56 pm »
What is the lowest carrier frequency you want to support?
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #195 on: May 31, 2024, 05:17:19 pm »
Good question. Because that's going to determine the low pass filter of the DC component detector.

Another solution I am thinking of is to set the DC component at the ADC input=2048 (actually the DC component is going to be very close to that number) and add a small correction limited to +-100 points with a low pass filter.

In any case, the DC detection low pass filter has to be stabilized very accurately within a few seconds. So I will try to give it an approximate response time tao=1 second.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #196 on: May 31, 2024, 05:56:28 pm »
My first attempt:

Code: [Select]
#include "main.h"
#include "filter.h"

#define ADC_MEAN  (2048)
#define ADC_MEAN_MAX_DEVIATION (1024*8)

volatile uint32_t adc_acc[FILTER_BUFF_SIZE];
volatile int32_t adc1_mean;
volatile int32_t adc2_mean;

volatile uint32_t mul;
volatile uint32_t *p_adc_acc;
volatile uint16_t *p1;
volatile uint16_t *p2;

void filter_adc_decimate_init(void) {
    adc1_mean = 0;
    adc2_mean = 0;
}

void filter_adc_decimate(uint16_t init, uint16_t end, uint16_t dest) {
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);

    p1 = &adc1_buff[init];
    p2 = &adc2_buff[init];
    p_adc_acc = &adc_acc[dest];
    int16_t adc1_acc = 0;
    int16_t adc2_acc = 0;
    int16_t adc1_dc = ADC_MEAN + (int16_t) (adc1_mean >> 19);
    int16_t adc2_dc = ADC_MEAN + (int16_t) (adc2_mean >> 19);
    for (int i = init; i < end; i += 8) {
        mul = 0;
       
        mul += (*p1 - adc1_dc) * (*p2 - adc2_dc);
        adc1_acc += *p1++ - adc1_dc;
        adc2_acc += *p2++ - adc2_dc;

        mul += (*p1 - adc1_dc) * (*p2 - adc2_dc);
        adc1_acc += *p1++ - adc1_dc;
        adc2_acc += *p2++ - adc2_dc;

        mul += (*p1 - adc1_dc) * (*p2 - adc2_dc);
        adc1_acc += *p1++ - adc1_dc;
        adc2_acc += *p2++ - adc2_dc;

        mul += (*p1 - adc1_dc) * (*p2 - adc2_dc);
        adc1_acc += *p1++ - adc1_dc;
        adc2_acc += *p2++ - adc2_dc;

        mul += (*p1 - adc1_dc) * (*p2 - adc2_dc);
        adc1_acc += *p1++ - adc1_dc;
        adc2_acc += *p2++ - adc2_dc;

        mul += (*p1 - adc1_dc) * (*p2 - adc2_dc);
        adc1_acc += *p1++ - adc1_dc;
        adc2_acc += *p2++ - adc2_dc;

        mul += (*p1 - adc1_dc) * (*p2 - adc2_dc);
        adc1_acc += *p1++ - adc1_dc;
        adc2_acc += *p2++ - adc2_dc;

        mul += (*p1 - adc1_dc) * (*p2 - adc2_dc);
        adc1_acc += *p1++ - adc1_dc;
        adc2_acc += *p2++ - adc2_dc;

        *p_adc_acc++ = mul;
       
        if (adc1_acc > ADC_MEAN_MAX_DEVIATION) {
            adc1_acc = ADC_MEAN_MAX_DEVIATION;
        }
        if (adc1_acc < -ADC_MEAN_MAX_DEVIATION) {
            adc1_acc = -ADC_MEAN_MAX_DEVIATION;
        }
        if (adc2_acc > ADC_MEAN_MAX_DEVIATION) {
            adc2_acc = ADC_MEAN_MAX_DEVIATION;
        }
        if (adc2_acc < -ADC_MEAN_MAX_DEVIATION) {
            adc2_acc = -ADC_MEAN_MAX_DEVIATION;
        }
        adc1_mean += -(adc1_mean >> 16) + adc1_acc;
        adc2_mean += -(adc2_mean >> 16) + adc2_acc;
    }

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
}


First order IIR:

a1 = 1 - 1/65536

Impulse response after one second = a1^(2500000/8) = 0.00849


EDIT:
Process time = 54% of CPU time
« Last Edit: May 31, 2024, 06:10:52 pm by Picuino »
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #197 on: May 31, 2024, 05:58:56 pm »
Another solution I am thinking of is to set the DC component at the ADC input=2048 (actually the DC component is going to be very close to that number) and add a small correction limited to +-100 points with a low pass filter.

I think the ADC can also be configured to return signed samples (-2048...2047), where 0 corresponds to Vref/2.
« Last Edit: May 31, 2024, 07:19:54 pm by gf »
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #198 on: May 31, 2024, 06:01:39 pm »
When i tested the signal chain, i was using that four step "triangle" wave from the ST example. The two neutral states came out to be near 2024 in the ADC capture and that value was pretty stable. The DAC introduced visible shifts due to a difference in rise and fall times. With a sine wave output signal of 100 KHz or less this should not happen though.
Meanwhile i started to test the CIC decimator with a decimation ratio of 64. So at 2.5 MHz sampling its output rate is about 40 KHz - enough for 10 KHz bandwidth. And it uses very few cycles, so one could think about implementing two mixers in order to upgrade the R meter to a RCL meter. Arm Cortex can certainly do this with everything in registers:
1     ADC read pointer
2     termination pointer
3     constant 2048 for DC subtraction
4     Q phase read pointer
5     I phase read pointer
6     Integrator 1 Q
7     Integrator 1 I
8,9  Integrator 2 Q
10,11 Integrator 2 I

So i would generate the two reference phases numerically and use the second ADC channel only for calibration/supervision.

Regards, Dieter
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #199 on: June 01, 2024, 04:08:43 pm »
My first attempt:

Code: [Select]
...

First order IIR: a1 = 1 - 1/65536
Impulse response after one second = a1^(2500000/8) = 0.00849

Process time = 54% of CPU time

Here's an optimized and corrected version: https://godbolt.org/z/G34oKdv4r
I guess it should run almost twice as fast. And if you manage to configure the ADC to return signed samples, you can set ADC_MEAN to 0, which saves additionaly 22 instructions in the loop. You should not declare variabels global if they are only used inside the function (except for large buffers, to save stack space). And avoid volatile whenever possible, it also prevents some compiler optimizations. One key for speed is to keep all (or as many as possible) variables (or better say values in the sense of SSA) inside the innermost loop in registers. Spilling to memory and reloading costs extra instructions and cycles.

With your IIR filter and 1kHz carrier of almost full-scale amplitude, the estimated DC offset has a residual ripple of 3 ADC counts. See the output of the included test in the bottom right Output window. And with lower carrier frequency, the estimation variability becomes larger, of course.
« Last Edit: June 01, 2024, 04:27:22 pm by gf »
 
The following users thanked this post: Picuino

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #200 on: June 01, 2024, 05:01:13 pm »
... so one could think about implementing two mixers in order to upgrade the R meter to a RCL meter.

That's basically how Picuino's previous project (AVR-based millivoltmeter) was working. The excitation signal was generated by the µC, too, (square wave + analog filtering, since no DAC available), and it was phase-synchronized to the quadrature NCO. Frequency and sample rate was of course lower, due to the limited power of the AVR.
« Last Edit: June 01, 2024, 05:10:35 pm by gf »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #201 on: June 01, 2024, 05:56:40 pm »
Here's an optimized and corrected version: https://godbolt.org/z/G34oKdv4r

Process time = 24% of CPU time (almost twice as fast)
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #202 on: June 01, 2024, 06:04:08 pm »
... so one could think about implementing two mixers in order to upgrade the R meter to a RCL meter.

That's basically how Picuino's previous project (AVR-based millivoltmeter) was working. The excitation signal was generated by the µC, too, (square wave + analog filtering, since no DAC available), and it was phase-synchronized to the quadrature NCO. Frequency and sample rate was of course lower, due to the limited power of the AVR.

This time I want to make a Lock-In amplifier to amplify the signal coming from a D2 led diode.
Another led D1 should emit about 30kHz towards a crystal and the signal received by D2 should show the vibrations of the crystal at lower audio frequencies.
The original project showed how it could be used to “listen” to what is spoken near a window. It could also be used to analyze the vibrations of an inaccessible machine.

https://www.holographyforum.org/data/pdf/aa-Collection_a_k/aa-Laser/aa-lockin/Homebrew_lockin_amplifier.html


I do not need to calculate the components in phase and the components rotated 90º.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #203 on: June 01, 2024, 06:13:28 pm »
In my case I will use normal infrared LEDs (I will try to have the emitter somewhat collimated with two holes in line) and the project itself is an excuse to learn about the STM32, instrumentation amplifiers and synchronous amplifiers.

In the other project I did, a simple Atmega328 could measure R, L and C with precision of about 50000 counts (every second) simply with its 10 bit ADC at 10kHz.

In this case I want to measure at higher frequencies, without calculating phase shift and without as much precision to get an audio output.
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #204 on: June 01, 2024, 07:03:32 pm »
Synchronous demodulation is certainly useful for this use case. But don't expect wonders. Keep in mind that a lockin amplifier's ability to dig a small signal deep out of the noise floor relies on a very narrow bandwidth. A wide bandwidth (say 10kHz in your case) defeats this ability, i.e. the signal you feed into the demodulator should already have a sufficient a priori SNR.
« Last Edit: June 01, 2024, 07:13:56 pm by gf »
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #205 on: June 01, 2024, 08:39:08 pm »
We have a Polytec HLV 1000 for laser based audio measurements. As far as i know it uses interference, i.e. mixing at optical frequency to detect motion as a Doppler signal. It works with red light in order to facilitate pointing the laser head at the target.
As it measures velocity one needs a differentiator in order to use it as a microphone.

Regards, Dieter
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #206 on: June 01, 2024, 09:53:27 pm »
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #207 on: June 02, 2024, 06:21:56 am »
Yes. At the time Polytec called it HLV = Hearing Laser Vibrometer. It works well when used with a reflective target. I remember ordering special self-adhesive reflector sheets they offered and some powder made of glas particles.
With a system measuring displacement (position), one needs two differentiators to make a microphone. This may cause a noise problem.

Regards, Dieter
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #208 on: June 02, 2024, 08:22:18 am »
That device is beyond my intentions. But it is very interesting. I didn't know there were such detectors.

Now we are in exam season and I will have to reduce the time dedicated to this project. I hope to be able to devote much more time to it in July. For now I will continue in my spare time little by little.



Compiling the previous example, a warning has appeared about the variables that catches my attention.

../Core/Src/filter.c:20:20: warning: initialization discards 'volatile' qualifier from pointer target type [-Wdiscarded-qualifiers]

In the following line of code:
Code: [Select]
    uint16_t *p1 = &adc1_buff[init];
adc1_buff is volatile. Perhaps the compiler is asking for the pointer to a volatile to be volatile as well?
I hope this does not cause problems. On other occasions compiler optimizations have led to not updating the buffer by not declaring it volatile.
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #209 on: June 02, 2024, 08:49:36 am »
Yes. At the time Polytec called it HLV = Hearing Laser Vibrometer. It works well when used with a reflective target. I remember ordering special self-adhesive reflector sheets they offered and some powder made of glas particles.
With a system measuring displacement (position), one needs two differentiators to make a microphone. This may cause a noise problem.

Hmm. Isn't Doppler frequency offset approimately proportional to velocity, and position the integral of velocity? Wouldn't this rather suggest FM demodulation + integrator in order to measure position?


 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2472
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #210 on: June 02, 2024, 09:39:00 am »
With analog audio the voltage represents acceleration (proportional to force of air pressure).
To go from the velocity measurement of a Doppler system to audio one needs one differentiator.
To convert a position measurement to analog audio one needs two differentiators to first derive speed and then acceleration. At high frequencies a position measurement doesn't work well.
But is has been done, for example in the famous active speakers of Backes & Müller. They built kind of a capacitor microphone into the tweeter to get the dome position. Capacitance was small and the first derivative happened by measuring capacitive displacement currents.

Regards, Dieter
« Last Edit: June 02, 2024, 10:26:27 am by dietert1 »
 
The following users thanked this post: gf

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #211 on: June 02, 2024, 11:58:21 am »
Compiling the previous example, a warning has appeared about the variables that catches my attention.

../Core/Src/filter.c:20:20: warning: initialization discards 'volatile' qualifier from pointer target type [-Wdiscarded-qualifiers]

In the following line of code:
Code: [Select]
    uint16_t *p1 = &adc1_buff[init];
adc1_buff is volatile. Perhaps the compiler is asking for the pointer to a volatile to be volatile as well?

Yes, if adc1_buff[] is volatile, then it expects a pointer to a volatile destination, otherwise the volatile qualifier is lost and and the accesses of the function to adc1_buff[] are not volatile. Therefore the warning.

Unfortunately, gcc emits more instructions if the pointer destination is volatile :(

But I do not see a stringent need for declaring adc1_buff[] volatile. I can hardly imagine a situation where the generated code would not reload the buffer contents from memory each time the function is executed. To be on the safe side when the function happens to be inlined in another function multiple times in a row or in a loop, you can add a compiler barrier to the very beginning of the function.

Quote
On other occasions compiler optimizations have led to not updating the buffer by not declaring it volatile.

I think when you encountered this problem, you had declared the buffer as a local variable inside the function.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #212 on: June 02, 2024, 04:48:29 pm »
The other time I had a problem was with the output DAC buffer. First the buffer was defined as global, but not volatile. A loop is in charge of updating the buffer contents to generate a sine wave, but the compiler detects that the program does nothing with the buffer contents and the first position was not written:

https://www.eevblog.com/forum/microcontrollers/starting-with-stm32-(nucleo-l412kb)/msg5516341/#msg5516341

After defining the buffer as volatile, the problem disappeared.

I will look into adding a compiler barrier.

 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1425
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #213 on: June 02, 2024, 06:17:02 pm »
The other time I had a problem was with the output DAC buffer. First the buffer was defined as global, but not volatile. A loop is in charge of updating the buffer contents to generate a sine wave, but the compiler detects that the program does nothing with the buffer contents and the first position was not written:

https://www.eevblog.com/forum/microcontrollers/starting-with-stm32-(nucleo-l412kb)/msg5516341/#msg5516341

After defining the buffer as volatile, the problem disappeared.

See https://godbolt.org/z/MdqTYMqoP
I cannot see a problem in the emitted assembly code, IMO it does what it should do.
Unfortunately you did not provide enough context, but only the 3 lines of the loop. So I can't see what you have really done.

A couple of posts earlier (https://www.eevblog.com/forum/microcontrollers/starting-with-stm32-(nucleo-l412kb)/msg5515009/#msg5515009) you definitively did define dac1_buff[] as local variable inside main(), and not global.

Code: [Select]
int main(void) {
    ...
    uint16_t dac1_buff[1000];
    for (int i = 0; i < 1000; i++) {
        dac1_buff[i] = i * 4;
    }
    ...
}
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1072
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #214 on: June 02, 2024, 07:10:01 pm »
Yes, in that case the variable was declared as local. Now it is declared as global in the dac.c file. I don't remember when I changed it, I think it was before I had that problem, but I don't know.

The best way to check if there is a problem with this is to run the program and see its output. If it is as expected, then I guess all is well.

Anyway I have re-declared the variables as volatile and with the code enhancement it is still very fast.

As soon as I get the second filter with decimation up and running, I'll post the code.

For now I post the provisional code as it is at the moment.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf