Starting with STM32 (NUCLEO-L412KB) - Page 1
Forum section: electronics / general

author: Picuino
author_rank: Super Contributor
timestamp: April 29, 2024, 06:40:01 pm
content: 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.htmlProduct data brief:https://www.st.com/resource/en/data_brief/nucleo-l412kb.pdf

author: coromonadalix
author_rank: Super Contributor
timestamp: April 29, 2024, 06:51:41 pm
content: you could start with arduino v2.xx and install all stm32 librairies / boards ... download and install "stm32programmer" , not the same as stm32 cubewhen you have all ready, com port and port type selected .... you can use arduino examples on stm32 without problemsi use nucleo L432KC and nucleo144-F412ZG boards like this ...you use the integrated st-link of the nucleo boards for the programming interface

author: dietert1
author_rank: Super Contributor
timestamp: April 29, 2024, 06:52:56 pm
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: April 29, 2024, 07:53:00 pm
content: Quote from: coromonadalix on April 29, 2024, 06:51:41 pmWhen uploading the program to the board it asks me for the file STM32_Programmer_CLI.exe

author: Picuino
author_rank: Super Contributor
timestamp: April 29, 2024, 08:02:08 pm
content: 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: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 : 066AFF575056716587092913ST-LINK FW : V2J36M26Board : NUCLEO-L412KBVoltage : 3.26VSWD freq : 4000 KHzConnect mode: Under ResetReset mode : Hardware resetError: Unable to list supported devicesError: Cannot identify the deviceFailed uploading: uploading error: exit status 1

author: Picuino
author_rank: Super Contributor
timestamp: April 30, 2024, 03:29:46 pm
content: 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:

author: dietert1
author_rank: Super Contributor
timestamp: April 30, 2024, 03:41:37 pm
content: 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)" anda memory device with two files on it (DETAILS.TXT and MBED.HTM)Regards, Dieter

author: pcprogrammer
author_rank: Super Contributor
timestamp: April 30, 2024, 03:48:12 pm
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: April 30, 2024, 04:00:09 pm
content: Quote from: dietert1 on April 30, 2024, 03:41:37 pmI'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 DebugPerhaps 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.

author: dietert1
author_rank: Super Contributor
timestamp: April 30, 2024, 04:27:55 pm
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: April 30, 2024, 04:46:00 pm
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: April 30, 2024, 04:55:50 pm
content: Could be a false positive.I have scanned the file on the virustotal.com website and it does not give any infection results.

author: Picuino
author_rank: Super Contributor
timestamp: April 30, 2024, 04:59:03 pm
content: Ok it is running in debug mode.Thank you very much.

author: Sacodepatatas
author_rank: Regular Contributor
timestamp: April 30, 2024, 05:51:49 pm
content: Quote from: Picuino on April 30, 2024, 04:46:00 pmAvast'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.

author: eugene
author_rank: Frequent Contributor
timestamp: May 01, 2024, 05:28:32 pm
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 01, 2024, 08:00:13 pm
content: I did not know them. Thank you very much.

author: dietert1
author_rank: Super Contributor
timestamp: May 02, 2024, 04:05:13 pm
content: Another nice learning series is "FreeRTOS TUTORIALS" on youtube by ControllersTech. They show it using CubeIDE.Regards, Dieter

author: coromonadalix
author_rank: Super Contributor
timestamp: May 03, 2024, 04:42:08 pm
content: for the STM32_Programmer_CLI.exe errorYou have to install them to their default locations, and for Arduino IDE, i use the full install, not the portable onetook me hours to figure this out, no help from ST nor ArduinoOnce you select the right board type, programming interface, com port ... and once you install the libraries related to some projects, mostly the FreeRTOS ... Timer ...

author: Picuino
author_rank: Super Contributor
timestamp: May 13, 2024, 06:00:53 pm
content: 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:/* 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:/* 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);}

author: 8goran8
author_rank: Contributor
timestamp: May 14, 2024, 07:11:47 am
content: Just increase the value for HAL_Delay.Try with HAL_Delay(1000);

author: dietert1
author_rank: Super Contributor
timestamp: May 14, 2024, 07:33:09 am
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 14, 2024, 07:53:36 am
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 14, 2024, 01:49:09 pm
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 14, 2024, 03:34:32 pm
content: I am testing turning a digital output on and off. The program I am using is as follows.Code: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?

author: Picuino
author_rank: Super Contributor
timestamp: May 14, 2024, 03:42:54 pm
content: I answer myself:Code: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.

author: pcprogrammer
author_rank: Super Contributor
timestamp: May 14, 2024, 03:56:45 pm
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 14, 2024, 04:08:15 pm
content: This configuration works (with internal oscillator):Output: 4717MHzImproved program (more clear):Code:while (1) {GPIOA->BSRR |= (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR |= (1<<3); // Reset PA3}

author: Picuino
author_rank: Super Contributor
timestamp: May 14, 2024, 04:16:16 pm
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 14, 2024, 04:22:33 pm
content: A jumper (SB17) must be soldered on the board to connect the oscillator to the input pin of the microcontroller.

author: Picuino
author_rank: Super Contributor
timestamp: May 14, 2024, 04:40:54 pm
content: 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

author: pcprogrammer
author_rank: Super Contributor
timestamp: May 14, 2024, 04:51:59 pm
content: Quote from: Picuino on May 14, 2024, 04:40:54 pmHas 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 14, 2024, 04:53:09 pm
content: 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:/* 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 */

author: tellurium
author_rank: Frequent Contributor
timestamp: May 14, 2024, 05:29:27 pm
content: Make sure your linker script (.ld file) puts RamFunc into the .data

author: gf
author_rank: Super Contributor
timestamp: May 14, 2024, 05:56:07 pm
content: Only write to BSRR/BRR, don't read-modify-write.

author: Picuino
author_rank: Super Contributor
timestamp: May 14, 2024, 06:01:43 pm
content: Quote from: gf on May 14, 2024, 05:56:07 pmThis code runs faster (8883 kHz) in flash and have the same effect:Code:void toggle_a3(void) {while (1) {GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3}}

author: Picuino
author_rank: Super Contributor
timestamp: May 14, 2024, 06:04:23 pm
content: Quote from: tellurium on May 14, 2024, 05:29:27 pm.ld file:Code:/* 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> FLASHI think so, but the speed is slower.

author: Picuino
author_rank: Super Contributor
timestamp: May 14, 2024, 06:10:34 pm
content: Ok I got it. It's slower the jump!If I write the changes in a row, the output goes to 9630kHz:Code: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 PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1<<3); // Reset PA3GPIOA->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.

author: pcprogrammer
author_rank: Super Contributor
timestamp: May 14, 2024, 06:36:04 pm
content: 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.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 14, 2024, 09:04:48 pm
content: 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.

author: dietert1
author_rank: Super Contributor
timestamp: May 14, 2024, 09:59:01 pm
content: When looking at the disassembly i see thatCode: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

author: Picuino
author_rank: Super Contributor
timestamp: May 15, 2024, 01:34:33 pm
content: Optimizations were disabled.I have now enabled them for speed.EDIT:Now the output pin reaches 40MHz!!!(a bit lower due to the jump)

author: Picuino
author_rank: Super Contributor
timestamp: May 15, 2024, 01:43:22 pm
content: I am now going to test serial communications and add a read and write buffer to UART2.

author: Picuino
author_rank: Super Contributor
timestamp: May 15, 2024, 04:25:05 pm
content: It works.usart.h:Code:/** 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:#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 sendUSART2->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 interruptif (USART1->ISR & USART_ISR_RXNE) {usart_receive();}//check if we are here because of TXEIE interruptif (USART1->ISR & USART_ISR_TXE) {usart_send();}}main.c:Code:/* 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 secondwith interrupts(cpu not blocked).Routines discard received data or sended data when the input or the output buffer (255 bytes) is full.

author: Picuino
author_rank: Super Contributor
timestamp: May 15, 2024, 05:52:05 pm
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 15, 2024, 07:02:52 pm
content: This program send the characters received, with interrupts.main.c:Code:/* 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 PA3GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1 << 3); // Reset PA3GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1 << 3); // Reset PA3GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1 << 3); // Reset PA3GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1 << 3); // Reset PA3GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1 << 3); // Reset PA3GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1 << 3); // Reset PA3GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1 << 3); // Reset PA3GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)GPIOA->BRR = (1 << 3); // Reset PA3GPIOA->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 sendHAL_UART_Transmit(&huart2, message, sizeof(message), 10); // Sending in normal modeHAL_Delay(1000);}/** Put char in USART transmit register*/int __io_putchar(int ch) {while (!(USART2->ISR & 0x0080)); // Wait for transfer buffer to be emptyUSART2->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:/** 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:#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;elsereturn 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 sendUSART->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 interruptif (USART->ISR & USART_ISR_RXNE) {usart_receive();}//check if we are here because of TXEIE interruptif (USART->ISR & USART_ISR_TXE) {usart_send();}// Error handling}

author: dietert1
author_rank: Super Contributor
timestamp: May 16, 2024, 06:01:09 am
content: 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. implementvoid HAL_UART_TxCplt_Callback(handle);For the interested, they have a manual about STM32L4 HAL libraries: UM1884, file: en.DM00173145.pdfShould 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

author: Picuino
author_rank: Super Contributor
timestamp: May 16, 2024, 08:08:58 am
content: Quote from: dietert1 on May 16, 2024, 06:01:09 amYesThank 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.

author: dietert1
author_rank: Super Contributor
timestamp: May 16, 2024, 08:42:55 am
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 16, 2024, 09:30:01 am
content: If they are open source I will study them.Maybe this is the code? :https://github.com/ARMmbed/mbed-hal-st-stm32cubef4/blob/master/source/stm32f4xx_hal_uart.cEDIT:Or this one? :https://github.com/ARMmbed/mbed-hal-st-stm32cubef4/blob/master/source/stm32f4xx_hal_usart.c

author: dietert1
author_rank: Super Contributor
timestamp: May 16, 2024, 10:42:47 am
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 16, 2024, 04:02:49 pm
content: 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.

author: pcprogrammer
author_rank: Super Contributor
timestamp: May 16, 2024, 05:55:42 pm
content: Quote from: Picuino on May 16, 2024, 04:02:49 pmThe disadvantage is that you have to debug someone else's code.

author: Picuino
author_rank: Super Contributor
timestamp: May 16, 2024, 06:04:54 pm
content: 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:void delay_us(uint16_t delay) {register uint16_t cycles;while (delay--) {cycles = 20;while (cycles--) {__asm__ __volatile__("");}}}

author: Picuino
author_rank: Super Contributor
timestamp: May 16, 2024, 06:13:55 pm
content: 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.

author: pcprogrammer
author_rank: Super Contributor
timestamp: May 16, 2024, 06:22:43 pm
content: Quote from: Picuino on May 16, 2024, 06:04:54 pmTo get this more or less right you can use inline assembler.Code: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.

author: Picuino
author_rank: Super Contributor
timestamp: May 16, 2024, 06:43:57 pm
content: 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: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

author: Picuino
author_rank: Super Contributor
timestamp: May 16, 2024, 06:53:00 pm
content: Code:__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

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 16, 2024, 10:04:48 pm
content: Quote from: pcprogrammer on May 16, 2024, 05:55:42 pmExactly. 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.

author: dietert1
author_rank: Super Contributor
timestamp: May 16, 2024, 11:04:36 pm
content: 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

author: pcprogrammer
author_rank: Super Contributor
timestamp: May 17, 2024, 05:40:28 am
content: Quote from: Picuino on May 16, 2024, 06:53:00 pmAh 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.

author: pcprogrammer
author_rank: Super Contributor
timestamp: May 17, 2024, 05:46:07 am
content: Quote from: dietert1 on May 16, 2024, 11:04:36 pmI 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.

author: dietert1
author_rank: Super Contributor
timestamp: May 17, 2024, 07:50:30 am
content: 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

author: pcprogrammer
author_rank: Super Contributor
timestamp: May 17, 2024, 08:42:02 am
content: Quote from: dietert1 on May 17, 2024, 07:50:30 amAh 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 17, 2024, 02:56:49 pm
content: Could someone please let me have a very simple example of how to run the ADC with DMA?My example does not work.Code:/* 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;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 filledvoid HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) {GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)}// Called when buffer is completely filledvoid 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:uint16_t adc_buf;HAL_ADC_Start_DMA(&hadc1, (uint32_t*) adc_buf, 512);Callbacks:Code:// Called when first half of buffer is filledvoid HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) {GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)}// Called when buffer is completely filledvoid HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {GPIOA->BRR = (1 << 3); // Reset PA3}

author: Picuino
author_rank: Super Contributor
timestamp: May 17, 2024, 03:22:27 pm
content: 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.Now it is and it works all the time.

author: tellurium
author_rank: Frequent Contributor
timestamp: May 17, 2024, 08:59:37 pm
content: Quote from: Picuino on May 16, 2024, 06:04:54 pmMine 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

author: tellurium
author_rank: Frequent Contributor
timestamp: May 17, 2024, 09:06:26 pm
content: Quote from: SiliconWizard on May 16, 2024, 10:04:48 pmAnd not just that. Some Cube HAL code is outright buggy - like Ethernet driver.

author: dietert1
author_rank: Super Contributor
timestamp: May 18, 2024, 06:47:28 am
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 18, 2024, 05:27:51 pm
content: 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.

author: gf
author_rank: Super Contributor
timestamp: May 18, 2024, 05:32:51 pm
content: Quote from: Picuino on May 18, 2024, 05:27:51 pmFor your purpose, look at the CMSIS library.

author: Picuino
author_rank: Super Contributor
timestamp: May 18, 2024, 05:46:50 pm
content: First attempt, in c:Code:GPIOA->BSRR = (1 << 3); // Set PA3int 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 PA3This 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:125 GPIOA->BSRR = (1 << 3); // Set PA30800078a: mov.w r8, #1207959552 @ 0x48000000131 uint64_t mac = 0;0800078e: movs r2, #0129 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 PA308000796: str.w r5, [r8, #24]131 uint64_t mac = 0;0800079a: mov r1, r2134 mul = (*org1++) * (*org2++);0800079c: ldrh.w r3, , #2080007a0: ldrh.w r12, , #2080007a4: mul.w r3, r12, r3135 mac += mul;080007a8: adds r2, r3, r2136 *dst++ = mac;080007aa: str.w r2, , #8135 mac += mul;080007ae: adc.w r1, r1, r3, asr #31133 for(i=ADC_BUFF_SIZE/2; i; i--) {080007b2: cmp r0, r7080007b4: str.w r1, [r4, #-4]080007b8: bne.n 0x800079c <main+372>138 GPIOA->BRR = (1 << 3); // Reset PA3080007ba: str.w r5, [r8, #40] @ 0x28

author: Picuino
author_rank: Super Contributor
timestamp: May 18, 2024, 05:52:30 pm
content: Quote from: gf on May 18, 2024, 05:32:51 pmhttps://arm-software.github.io/CMSIS_6/latest/General/index.htmlI can't find the advantage for what I need.

author: Picuino
author_rank: Super Contributor
timestamp: May 18, 2024, 05:57:56 pm
content: Now I need to implement IIR filter to output filtered results.* AN540: Implementing IIR Digital Filtershttps://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_responseQuote from: Wikipedia

author: Picuino
author_rank: Super Contributor
timestamp: May 18, 2024, 06:10:04 pm
content: Design of IIR filters in Python:https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.iirdesign.htmlhttps://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.iirfilter.htmlhttps://www.geeksforgeeks.org/how-to-implement-iir-bandpass-butterworth-filter-using-scipy-python/https://dsp.stackexchange.com/questions/59688/how-to-design-a-digital-filter-in-python-that-will-run-over-an-uc

author: dave j
author_rank: Regular Contributor
timestamp: May 18, 2024, 06:30:05 pm
content: Quote from: Picuino on May 18, 2024, 05:52:30 pmLook at theCMSIS DSP libraries.

author: gf
author_rank: Super Contributor
timestamp: May 18, 2024, 06:33:07 pm
content: Quote from: Picuino on May 18, 2024, 05:52:30 pmWhy 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?Quote from: Picuino on May 18, 2024, 05:57:56 pmWhat kind of IIR filter do you want to design?Try the functionsbutter,cheby1,cheby2orellipin Matlab or Octave.

author: gf
author_rank: Super Contributor
timestamp: May 18, 2024, 06:33:57 pm
content: Quote from: dave j on May 18, 2024, 06:30:05 pmYes, that's what I meant. I should have been more specific.

author: Picuino
author_rank: Super Contributor
timestamp: May 18, 2024, 06:46:25 pm
content: 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

author: dietert1
author_rank: Super Contributor
timestamp: May 18, 2024, 08:08:44 pm
content: 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

author: gf
author_rank: Super Contributor
timestamp: May 18, 2024, 08:28:28 pm
content: Quote from: Picuino on May 18, 2024, 06:46:25 pmYou 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.htmlA second order IIR needs just a single biquad stage.Note that most integer CMSIS DSP functions work withQ numbers. At the end this is just a matter of scaling, but you need to take care.QuoteI 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.QuoteDecimation 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 19, 2024, 10:28:31 am
content: 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#gaa09ea758c0b24eed9ef92b8d1e5c80c2I'm going to install CMSIS libraries to test it.

author: Picuino
author_rank: Super Contributor
timestamp: May 19, 2024, 10:49:03 am
content: 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?

author: Tation
author_rank: Regular Contributor
timestamp: May 19, 2024, 11:42:27 am
content: Quote from: Picuino on May 19, 2024, 10:49:03 amhttps://github.com/ARM-software/CMSIS-DSP/releases/tag/v1.15.0

author: jnk0le
author_rank: Regular Contributor
timestamp: May 19, 2024, 12:51:13 pm
content: Quote from: gf on May 18, 2024, 08:28:28 pmIf 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.Quote from: Picuino on May 18, 2024, 06:46:25 pmIn such scenario, it's possible to do low order IIR (2-5 taps). Might require optimized assembly again.Quote from: Picuino on May 19, 2024, 10:28:31 amThat means you don't need to filter that out of 2,5MSPS signal, which makes things easier.Quote from: Picuino on May 19, 2024, 10:28:31 amnote 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.

author: gf
author_rank: Super Contributor
timestamp: May 19, 2024, 01:00:02 pm
content: Quote from: jnk0le on May 19, 2024, 12:51:13 pmThe 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.

author: jnk0le
author_rank: Regular Contributor
timestamp: May 19, 2024, 01:24:50 pm
content: Quote from: gf on May 19, 2024, 01:00:02 pmwhich require conversion of coefficients from direct form to cascaded

author: Picuino
author_rank: Super Contributor
timestamp: May 19, 2024, 05:59:02 pm
content: I have managed to install and use the CMSIS library.https://arm-software.github.io/CMSIS-DSP/latest/group__BiquadCascadeDF1.html#ga5563b156af44d1be2a7548626988bf4ehttps://arm-software.github.io/CMSIS-DSP/latest/group__BiquadCascadeDF1.html#ga4e7dad0ee6949005909fd4fcf1249b79arm_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.

author: gf
author_rank: Super Contributor
timestamp: May 19, 2024, 09:48:27 pm
content: Quote from: Picuino on May 19, 2024, 05:59:02 pmYou 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:pkg load signalfc = 100000 % cutoff frequencyfs = 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, -551792502QuoteWithout oversampling:Code:uint16_t sample; // 0...4095q31_t qsample = (q31_t) sample - 2048 << 20;With 4x oversampling:Code:uint16_t sample; // 0...16380q31_t qsample = (q31_t) sample - 8192 << 18;

author: gf
author_rank: Super Contributor
timestamp: May 19, 2024, 10:13:14 pm
content: Quote from: gf on May 19, 2024, 09:48:27 pm@Picuino, in order to verify correctness, can you send an impulseq31_t samples = { 1073741824, /* 99x 0 */ };through this filter and print the values of the 100 resulting samples?

author: gf
author_rank: Super Contributor
timestamp: May 20, 2024, 09:35:50 am
content: Quote from: Picuino on May 19, 2024, 05:59:02 pmHmm, 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/nfaM1Yq3PThe (4x unrolled) innermost loop (.L3) has 54 instructions.According to the table inhttps://developer.arm.com/documentation/ddi0439/b/CHDDIGACthis 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.]

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 02:56:09 pm
content: I have tried a smaller and more realistic buffer (size = 100 samples):Program:Code:// Define variablesq31_t pSrc;q31_t pDst;arm_biquad_casd_df1_inst_q31 S;q31_t pCoeffs[] = { 0x02A43116, 0x0548622C, 0x02A43116, 0xA9AD14E3,0x20E3AF76 };q31_t pState;uart_init();while (1) {// Initialize buffer pSrcfor (int i = 0; i < 100; i++)pSrc = 0;pSrc = 0x40000000;// Process dataarm_biquad_cascade_df1_init_q31(&S, 1, pCoeffs, &pState, 0);GPIOA->BSRR = (1 << 3); // Set PA3arm_biquad_cascade_df1_q31(&S, pSrc, pDst, 100);GPIOA->BRR = (1 << 3); // Reset PA3// Output datafor (int i = 0; i < 100; i++) {printf("0x%08lX\r\n", pDst);HAL_Delay(2);}HAL_Delay(2000);}Hex output:Code:0x0152188B0x01C02D930x007AB7210x002066130x0009AE7F0x0001CBA30x000146DD0xFFFF99AA0x000099000xFFFF7E850x00007EA20xFFFF89540x000070920xFFFF95960x000064B00xFFFFA0C00x00005A1B0xFFFFAAC20x000050A30xFFFFB3B70x0000482A0xFFFFBBBB0x000040950xFFFFC2E70x000039CC0xFFFFC9520x000033BA0xFFFFCF100x00002E4B0xFFFFD4340x0000296E0xFFFFD8CE0x000025140xFFFFDCEC0x0000212F0xFFFFE09B0x00001DB20xFFFFE3E80x00001A930xFFFFE6DC0x000017C80xFFFFE9800x000015480xFFFFEBDD0x0000130C0xFFFFEDFB0x0000110B0xFFFFEFE00x00000F410xFFFFF1910x00000DA70xFFFFF3150x00000C380xFFFFF4700x00000AEF0xFFFFF5A70x000009C90xFFFFF6BD0x000008C20xFFFFF7B60x000007D70xFFFFF8950x000007040xFFFFF95C0x000006470xFFFFFA0F0x0000059E0xFFFFFAAF0x000005070xFFFFFB3E0x000004800xFFFFFBBE0x000004070xFFFFFC300x0000039B0xFFFFFC960x0000033A0xFFFFFCF20x000002E30xFFFFFD440x000002950xFFFFFD8E0x000002500xFFFFFDCF0x000002120xFFFFFE0A0x000001DA0xFFFFFE3F0x000001A80xFFFFFE6E0x0000017C0xFFFFFE980x000001540xFFFFFEBE0x000001300xFFFFFEE00x000001100xFFFFFEFE0x000000F30xFFFFFF19Decimal output:Code:22157451293717958042273212328363449511766783677-2619839168-3314732418-3038028818-2724225776-2438423067-2182220643-1952918474-1747716533-1564114796-1399813242-1252811851-1121210606-100349492-89808495-80377602-71926803-64366088-57605448-51554876-46134363-41283905-36953495-33073128-29602799-26492505-23712242-21222007-18991796-17001607-15211438-13611287-12181152-10901031-976923-874826-782739-700661-626592-561530-502474-449424-402380-360340-322304-288272-258243-231

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 03:06:41 pm
content: Response to a step.Program change:Code:// Initialize buffer pSrcfor (int i = 0; i < 100; i++)pSrc = 0;for (int i = 20; i < 60; i++)pSrc = 0x10000;Decimal output:Code:000000000000000000001352314536353766380338123815381638163816381638163816381638163816381638163816381638163816381638163816381638163816381638163816381638163816381638163816381638163816246467118051115-11-10-10-10-10-10-10-10-10-10-10-10-10-10-10-10-10EDIT:There is a scale error. The output should reach the value 65536.

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 03:14:16 pm
content: Optimizing with -O3, filtering 100 samples takes 42us (34 clock cycles per sample @ 80MHz)

author: gf
author_rank: Super Contributor
timestamp: May 20, 2024, 03:16:45 pm
content: 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, -551792502The attached plot shows how the impulse response should look like.EDIT: Corrected commands:Code:pkg load signalfc = 100000 % cutoff frequencyfs = 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, -551792502Quote from: Picuino on May 20, 2024, 03:14:16 pm

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 03:23:17 pm
content: Step response:Code:00000000000000000000135249688412980698619540930992369246927192869289928892869285928592859285928592859285928592859285928592859285928592859285928592859285928592859285928592859285928579334316872-521-576-255-24493913-2-5-3-100000000000000000000000000Scale error is maintained.

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 03:26:15 pm
content: Impulse response:Code:22157451592580085642805322829136896995-5260972-3778510-119645016398941802023977854298-24992-30807-14355-176624972137799-11-213-141-4181581-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

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 03:48:13 pm
content: Equivalent script in Python:Code:import scipyimport mathfc = 100000 # Cutoff frequencyfs = 1333000 # Sample rateb, 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:0x02A43116,0x0548622C,0x02A43116,0x5652EB1D,0xDF1C508A,

author: gf
author_rank: Super Contributor
timestamp: May 20, 2024, 03:49:47 pm
content: Quote from: Picuino on May 20, 2024, 03:26:15 pmWhich coefficient? Not my latest ones? (44314902, 88629804, 44314902, 1448274717, -551792502 with postShift=1)What was the height of the impulse?

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 03:55:00 pm
content: Coefficients:Code:q31_t pCoeffs[] = {44314902, 88629804, 44314902, 1448274717, -551792502};Height of impulse:Code:pSrc= 0x40000000;0x40000000 = 1073741824 decimal

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 03:58:46 pm
content: I had set the postShift to zero.I change it and try again.

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 04:01:02 pm
content: Impulse response:Code:443149021484022282217081632227789631865515831371374168910418249710214212593253128889-6704820-10651461-10921222-9256912-6873443-4513878-2556128-1128066-2079622992085104465347334589373442212284421312305960812961-13151-24399-26152-22736-17228-11554-6731-3142-77956311591273112185757934016142-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

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 04:04:27 pm
content: Step (of value = 100000) response:Code:00000000000000000000412717947385945934176715894879778610241610439610468710406210307010205310119110055110013199893997889976999797998449989399935999679998810000010000610000810000710000510000310000199999999989999799996999969999699996999969586982048614004065223278105062208-2421-4401-4692-4067-3075-2058-1196-556-13610220722619815010057254-8-13-14-13-11-9-7-5-4-3-2-2-2-2-2Now the scale is OK.

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 04:05:29 pm
content: STM32 program:Code:// Define variablesq31_t pSrc;q31_t pDst;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 pSrcfor (int i = 0; i < 100; i++)pSrc = 0;//pSrc= 0x40000000;for (int i = 20; i < 60; i++) pSrc = 100000;// Process dataarm_biquad_cascade_df1_init_q31(&S, 1, pCoeffs, &pState, 1);GPIOA->BSRR = (1 << 3); // Set PA3arm_biquad_cascade_df1_q31(&S, pSrc, pDst, 100);GPIOA->BRR = (1 << 3); // Reset PA3// Output datafor (int i = 0; i < 100; i++) {printf("%ld\r\n", pDst);HAL_Delay(2);}HAL_Delay(2000);}The process time is maintained at 42us for 100 samples.

author: gf
author_rank: Super Contributor
timestamp: May 20, 2024, 04:26:22 pm
content: 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. 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 05:05:14 pm
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 05:37:20 pm
content: 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:// Cutoff frequency = 10000coeff = { 0x0008CE29, 0x00119C53, 0x0008CE29, 0x7BBC3A97, 0xC4208CC3 };// Cutoff frequency = 1000coeff = { 0x00001738, 0x00002E70, 0x00001738, 0x7F92C8E9, 0xC06CDA36 };// Cutoff frequency = 100coeff = { 0x0000003C, 0x00000077, 0x0000003C, 0x7FF51415, 0xC00AEAFD };// Cutoff frequency = 10coeff = { 0x00000001, 0x00000001, 0x00000001, 0x7FFEE868, 0xC0011795 };// Cutoff frequency = 1coeff = { 0x00000000, 0x00000000, 0x00000000, 0x7FFFE40A, 0xC0001BF6 };Script:Code:## Python script to calculate Butterworth filter coefficients#import scipyimport mathfc = 1000 # Cutoff frequencyfs = 1333000 # Sample ratefor 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))

author: Picuino
author_rank: Super Contributor
timestamp: May 20, 2024, 06:26:23 pm
content: High Precision Q31 Biquad Cascade Filterhttps://arm-software.github.io/CMSIS-DSP/latest/group__BiquadCascadeDF1__32x64.htmlCode: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)QuoteEDIT:Python script with postShift manage:Code:## Python script to calculate Butterworth filter coefficients#import scipyfs = 1333000 # Sample ratefc = 100000 # Cutoff frequencypostShift = 0 # Post shift gainb, 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 += 1coeff = [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))

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 20, 2024, 09:29:23 pm
content: 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?

author: gf
author_rank: Super Contributor
timestamp: May 20, 2024, 10:15:13 pm
content: Quote from: SiliconWizard on May 20, 2024, 09:29:23 pmI 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.

author: dietert1
author_rank: Super Contributor
timestamp: May 21, 2024, 05:11:52 am
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 21, 2024, 07:58:47 am
content: 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?

author: gf
author_rank: Super Contributor
timestamp: May 21, 2024, 09:00:53 am
content: Quote from: Picuino on May 21, 2024, 07:58:47 amWhat 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). Seehttps://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 bearbitrary. 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 rejectedcompletely. 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 21, 2024, 10:56:14 am
content: 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:out = in + in + in + inout = in + in + in + inout = in + in + in + inout = in + in + in + inand 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:out = in + in + in + inout = in + in + in + inIs there any other way to make decimation?

author: gf
author_rank: Super Contributor
timestamp: May 21, 2024, 11:18:34 am
content: Quote from: Picuino on May 21, 2024, 10:56:14 amAnd 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 21, 2024, 11:25:21 am
content: https://arm-software.github.io/CMSIS_5/DSP/html/group__FIR__decimate.html#ga6a19d62083e85b3f5e34e8a8283c1ea0https://arm-software.github.io/CMSIS_5/DSP/html/group__FIR__decimate.html#ga27c05d7892f8a327aab86fbfee9b0f29Thank you very much for your help.Do you know what would be the way to obtain the FIR filter coefficients?Code:arm_status arm_fir_decimate_init_q31 (arm_fir_decimate_instance_q31 *S,uint16_tnumTaps,uint8_tM,const q31_t *pCoeffs,q31_t *pState,uint32_tblockSize)Parameters[in,out]Spoints to an instance of the Q31 FIR decimator structurenumTapsnumber of coefficients in the filterMdecimation factorpCoeffspoints to the filter coefficientspStatepoints to the state bufferblockSizenumber of input samples to processReturnsexecution statusARM_MATH_SUCCESS : Operation successfulARM_MATH_LENGTH_ERROR : blockSize is not a multiple of MDetailspCoeffs points to the array of filter coefficients stored in time reversed order:{b[numTaps-1], b[numTaps-2], b[N-2], ..., b, b}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.

author: gf
author_rank: Super Contributor
timestamp: May 21, 2024, 11:49:44 am
content: Quote from: Picuino on May 21, 2024, 11:25:21 amFor 10x decimation, try this one:Code:pkg load signalR = 10 % decimation factorBW = 10 % end of passband (start of transition band), khzfs = 1333 % sample rate, kSa/sntaps = 80 % number of tapsh = 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 onylim([-70 0])% scale h to Q31int32(round(h*2^32))Code:1943208464978305641-49046-638438-1497324-2650941-4109235-5862456-7878098-10098245-12435856-14770057-16950103-18809941-20144286-20744894-20388173-18853952-15933574-11440900-522334928270341276315924575302381873475345140470147061879887661066258881256581651446438531631160131805983801966230032107480702225744642317613992380411302412280172412280172380411302317613992225744642107480701966230031805983801631160131446438531256581651066258888798876670147061534514043818734724575302127631592827034-5223349-11440900-15933574-18853952-20388173-20744894-20144286-18809941-16950103-14770057-12435856-10098245-7878098-5862456-4109235-2650941-1497324-638438-490463056414649781943208The 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. ]

author: dietert1
author_rank: Super Contributor
timestamp: May 21, 2024, 12:19:55 pm
content: 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-1If Xn is a 12 bit ADC value i will be adding zeros. But i can rewrite the formula asYn = Xn + Yn-1 - Yn-1 / 500 000Is 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

author: Picuino
author_rank: Super Contributor
timestamp: May 21, 2024, 01:14:16 pm
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 21, 2024, 01:26:08 pm
content: I'm going to try programming on my current board (STM32L412KB) just to get a rough idea of the timing.

author: Picuino
author_rank: Super Contributor
timestamp: May 21, 2024, 01:42:53 pm
content: Input buffer = 1000 samplesOutput buffer = 100 samplesprocess time = 936us @ 80MHz with STM32L412KBProgram:Code:// Define variablesq31_t pSrc;q31_t pDst;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 pSrcfor (int i = 0; i < 1000; i++)pSrc = 0;for (int i = 0; i < 500; i++)pSrc = 10000000;// Process dataarm_fir_decimate_init_q31(&S, 80, 10, pCoeffs, pState, 1000);GPIOA->BSRR = (1 << 3); // Set PA3arm_fir_decimate_q31(&S, pSrc, pDst, 1000);GPIOA->BRR = (1 << 3); // Reset PA3// Output datafor (int i = 0; i < 100; i++) {printf("%ld\r\n", pDst);while(uart_sending());}HAL_Delay(2000);}Attached: output response to a step signalCode:9048-140025-933846147362711115434195922872079063120040573199842581998425819984258199842581998425819984258199842581998425819984258199842581998425819984258199842581998425819984258199842581998425819984258199842581998425819984258199842581998425819984258199842581998425819984258199842581998425819984258199842581998425819984258199842581998425819984258199842581998425819984258199842581998425819984258199752092012428220918103185106318868823391970-806373-56316000000000000000000000000000000000000000000

author: Picuino
author_rank: Super Contributor
timestamp: May 21, 2024, 01:49:07 pm
content: The other microcontroller may be able to reduce 2.833Msps in real time.

author: Picuino
author_rank: Super Contributor
timestamp: May 21, 2024, 01:58:16 pm
content: Yet another test:Code:pkg load signalR = 10 % decimation factorBW = 20 % end of passband (start of transition band), khzfs = 2833 % sample rate, kSa/sntaps = 70 % number of tapsh = 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 onylim([-70 0])% scale h to Q31int32(round(h*2^32))Program:Code:// Define variablesq31_t pSrc;q31_t pDst;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 pSrcfor (int i = 0; i < 1000; i++)pSrc = 0;for (int i = 0; i < 500; i++)pSrc = 10000000;// Process dataarm_fir_decimate_init_q31(&S, 70, 10, pCoeffs, pState, 1000);GPIOA->BSRR = (1 << 3); // Set PA3arm_fir_decimate_q31(&S, pSrc, pDst, 1000);GPIOA->BRR = (1 << 3); // Reset PA3// Output datafor (int i = 0; i < 100; i++) {printf("%ld\r\n", pDst);while (uart_sending());}HAL_Delay(2000);}Process time: 836us (less)

author: Picuino
author_rank: Super Contributor
timestamp: May 21, 2024, 02:45:48 pm
content: Code:## Python script to calculate# FIR coefficients with remez algorithm#import numpy as npfrom scipy import signalimport matplotlib.pyplot as pltfs = 2833000 # Sample rate, Hzcutoff = 20000 # Desired cutoff frequency, HzR = 10 # Decimation factornumtaps = 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, , worN=2000)plot_response(fs, w, h, "Low-pass Filter")Python equivalent code for calculating FIR coefficients and frequency response.EDIT: Q31 conversion corrected.

author: gf
author_rank: Super Contributor
timestamp: May 21, 2024, 03:15:13 pm
content: Quote from: Picuino on May 21, 2024, 02:45:48 pmSorry, 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. ]

author: gf
author_rank: Super Contributor
timestamp: May 21, 2024, 03:41:36 pm
content: Quote from: Picuino on May 21, 2024, 01:42:53 pmBoth are about 9.5 cycles per output sample per tap.

author: gf
author_rank: Super Contributor
timestamp: May 21, 2024, 09:34:12 pm
content: Quote from: gf on May 21, 2024, 03:41:36 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 ].And for comparison, this one would be interesting, too:https://godbolt.org/z/G3Gr1r5xcI 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/bP9WrdGqqShould 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.QuoteWhat 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

author: dietert1
author_rank: Super Contributor
timestamp: May 22, 2024, 05:44:37 am
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 22, 2024, 02:17:59 pm
content: 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!!

author: Picuino
author_rank: Super Contributor
timestamp: May 22, 2024, 02:48:18 pm
content: On this board it is necessary to link jumper SB13 so that the external clock comes from the STLINK.

author: Picuino
author_rank: Super Contributor
timestamp: May 22, 2024, 04:07:14 pm
content: 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 wasnot able to update through the USB HUBI 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 22, 2024, 04:27:56 pm
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 22, 2024, 04:40:31 pm
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 22, 2024, 05:05:06 pm
content: Quote from: gf on May 21, 2024, 09:34:12 pmWell, STM32G431KB running at 170MHz, optimization -O3, filtering 1000 samples with decimation at 500 samples output.Slow function arm_fir_decimate_q31().Running time: 318usEDIT:Fast function arm_fir_decimate_fast_q31().Running time: 354usIt is not very logical. I'm going to try optimizing for speed.

author: Picuino
author_rank: Super Contributor
timestamp: May 22, 2024, 05:09:42 pm
content: Optimizing for speed -Ofast, the fast function maintains a long execution time (354us).And the slow function maintains a short runtime (318us).

author: Tation
author_rank: Regular Contributor
timestamp: May 22, 2024, 05:46:28 pm
content: Quote from: Picuino on May 22, 2024, 04:40:31 pmNucleo 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 22, 2024, 08:35:42 pm
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 22, 2024, 08:50:44 pm
content: 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

author: gf
author_rank: Super Contributor
timestamp: May 22, 2024, 10:07:50 pm
content: Quote from: Picuino on May 22, 2024, 05:05:06 pmThat's a bit disappointing for the 2x faster µCCould you also try to copy my function into your program and test the same?https://godbolt.org/z/bP9WrdGqqIt's hardcoded for 4 taps and 2x decimation - no unnecessary generic sctuff. If it also does not run faster then

author: dietert1
author_rank: Super Contributor
timestamp: May 23, 2024, 03:57:19 am
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 23, 2024, 07:40:59 am
content: 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/#msg5499019Thanks, I will try with RAM execution.

author: gf
author_rank: Super Contributor
timestamp: May 23, 2024, 08:27:45 am
content: Quote from: dietert1 on May 23, 2024, 03:57:19 amI did take a closer look to the datasheet and the reference manual (RM0440).The datasheet claimsQuote from: datasheetAccording 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.pdfMy 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?

author: Picuino
author_rank: Super Contributor
timestamp: May 23, 2024, 01:43:48 pm
content: Input buffer = 1000 samplesOutput buffer = 500 samplesFIR coefficients = 6arm_fir_decimate_q31() running in RAM: 318usarm_fir_decimate_fast_q31() running in RAM: 354usExactly the same times as running in flash.

author: Picuino
author_rank: Super Contributor
timestamp: May 23, 2024, 01:52:19 pm
content: decimate_2_sinc3() with 4 coefficients, running in flash: 80us

author: Picuino
author_rank: Super Contributor
timestamp: May 23, 2024, 01:55:52 pm
content: 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 ADC3. 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 23, 2024, 07:49:20 pm
content: 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:#include <ctype.h>#include "main.h"#include "dac.h"uint16_t dac1_buff;uint16_t dac2_buff;void dac1_dma_init(void) {/** DAC Initialization*/// Configure PA4 for analog outputGPIO_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 channelHAL_DAC_Start(&hdac1, DAC_CHANNEL_1);// Configure DMA1 Channel 3DMA_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 DMAHAL_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 DMAHAL_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 transferif (DMA1->ISR & DMA_ISR_HTIF3) {DMA1->IFCR = DMA_IFCR_CHTIF3;}// Full transferif (DMA1->ISR & DMA_ISR_TCIF3) {DMA1->IFCR = DMA_IFCR_CTCIF3;}}void dac_Error_Handler(void) {}timer.c:Code:#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 instanceTIM_HandleTypeDef htim2;htim2.Instance = TIM2;htim2.Init.Prescaler = 17 - 1; // Set prescaler. Divide by 17htim2.Init.CounterMode = TIM_COUNTERMODE_UP;htim2.Init.Period = 10 - 1; // Set period to 10htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;// Base initializationif (HAL_TIM_Base_Init(&htim2) != HAL_OK) {timer_Error_Handler();}// Configure clock sourcesClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) {timer_Error_Handler();}// Initialize PWMif (HAL_TIM_PWM_Init(&htim2) != HAL_OK) {timer_Error_Handler();}// Configure master synchronizationsMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig)!= HAL_OK) {timer_Error_Handler();}// Configure PWM channelsConfigOC.OCMode = TIM_OCMODE_PWM1;sConfigOC.Pulse = 5; // Set pulse width to 5sConfigOC.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.

author: dietert1
author_rank: Super Contributor
timestamp: May 23, 2024, 08:18:47 pm
content: 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?

author: Picuino
author_rank: Super Contributor
timestamp: May 23, 2024, 08:27:43 pm
content: Changed. The DAC output remains at 0 Volts.Does anyone have an example of a DAC working with DMA that I can adapt?

author: dietert1
author_rank: Super Contributor
timestamp: May 24, 2024, 05:33:25 am
content: 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.htmlThey 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

author: Picuino
author_rank: Super Contributor
timestamp: May 24, 2024, 08:09:19 am
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 24, 2024, 11:19:26 am
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 24, 2024, 06:26:00 pm
content: 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.cCode:/* 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;for (int i = 0; i < 1000; i++) {dac1_buff = 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 */

author: Picuino
author_rank: Super Contributor
timestamp: May 24, 2024, 06:31:52 pm
content: Quote from: dietert1 on May 24, 2024, 05:33:25 amI'm going to try that way.

author: dietert1
author_rank: Super Contributor
timestamp: May 24, 2024, 07:00:02 pm
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 24, 2024, 07:04:45 pm
content: 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.

author: dietert1
author_rank: Super Contributor
timestamp: May 24, 2024, 09:47:19 pm
content: For me the example worked. I collected everything in one C file.Regards, Dieter

author: Picuino
author_rank: Super Contributor
timestamp: May 25, 2024, 07:18:30 am
content: Thank you very much. Finally it works!!!

author: Picuino
author_rank: Super Contributor
timestamp: May 25, 2024, 12:21:04 pm
content: 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: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:hdma_dac1_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;

author: dietert1
author_rank: Super Contributor
timestamp: May 25, 2024, 01:52:57 pm
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 25, 2024, 06:45:25 pm
content: 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) 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:for (int i = 0; i < DAC1_BUFF_SIZE; i++) {dac1_buff = 2047 + 2047 * sin(i * 2.0 * PI / DAC1_BUFF_SIZE);}

author: Picuino
author_rank: Super Contributor
timestamp: May 25, 2024, 06:53:34 pm
content: If I rewrite the zero position, then everything works fine.Code:for (int i = 0; i < DAC1_BUFF_SIZE; i++) {dac1_buff = 2047 + 2047 * sin(i * 2.0 * PI / DAC1_BUFF_SIZE);}dac1_buff = 2047;while (1);

author: Picuino
author_rank: Super Contributor
timestamp: May 25, 2024, 07:43:26 pm
content: 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 asvolatile.

author: dietert1
author_rank: Super Contributor
timestamp: May 26, 2024, 12:34:37 pm
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 26, 2024, 01:34:31 pm
content: 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).

author: Picuino
author_rank: Super Contributor
timestamp: May 26, 2024, 02:18:01 pm
content: I have already set it up:DAC1 and DAC2 with DMA (with timer2 timebase)ADC1 and ADC2 with DMA (not syncronized)UART2

author: Picuino
author_rank: Super Contributor
timestamp: May 26, 2024, 08:47:54 pm
content: 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.

author: dietert1
author_rank: Super Contributor
timestamp: May 28, 2024, 08:36:07 am
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 28, 2024, 09:36:01 am
content: Quote from: dietert1 on May 28, 2024, 08:36:07 amThat'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.

author: dietert1
author_rank: Super Contributor
timestamp: May 28, 2024, 04:07:46 pm
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: May 28, 2024, 04:21:35 pm
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 29, 2024, 08:08:26 am
content: I will implement this FIR filter with decimation:Code:## Python script to calculate# FIR coefficients with remez algorithm#import numpy as npfrom scipy import signalimport matplotlib.pyplot as pltfs = 2500000/8 # Sample rate, Hzcutoff = 5000 # Desired cutoff frequency, HzR = 8 # Decimation factornumtaps = 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, , worN=2000)plot_response(fs, w, h, "Low-pass Filter")Coefficients:Code: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

author: gf
author_rank: Super Contributor
timestamp: May 29, 2024, 09:00:40 am
content: Quote from: Picuino on May 29, 2024, 08:08:26 amOnly 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).

author: Picuino
author_rank: Super Contributor
timestamp: May 29, 2024, 09:42:04 am
content: The first stage I am going to do something very simple, because there is not enough time to do more processing:Code:accumulator = 0;accumulator += adc1_buff * adc2_buff;accumulator += adc1_buff * adc2_buff;accumulator += adc1_buff * adc2_buff;accumulator += adc1_buff * adc2_buff;accumulator += adc1_buff * adc2_buff;accumulator += adc1_buff * adc2_buff;accumulator += adc1_buff * adc2_buff;accumulator += adc1_buff * adc2_buff;dac1_buff = 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.

author: gf
author_rank: Super Contributor
timestamp: May 29, 2024, 09:48:57 am
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 29, 2024, 11:42:48 am
content: Anyway I will try both methods (the one I described above and the ADC hardware decimate method) to see the results of each.

author: dietert1
author_rank: Super Contributor
timestamp: May 29, 2024, 05:07:22 pm
content: 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

author: gf
author_rank: Super Contributor
timestamp: May 29, 2024, 07:51:13 pm
content: Quote from: dietert1 on May 29, 2024, 05:07:22 pmWith 32-bit accumulator it is indeed not very expensive.QuoteYes, 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

author: dietert1
author_rank: Super Contributor
timestamp: May 30, 2024, 05:21:05 am
content: 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?

author: gf
author_rank: Super Contributor
timestamp: May 30, 2024, 07:03:23 am
content: Quote from: dietert1 on May 30, 2024, 05:21:05 amThere is no need to prevent the integrators from overflow but you deliberately let them wrap around. They only need to have enough bits.Seehttps://www.dsprelated.com/showarticle/1337.phpfor 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 30, 2024, 08:01:50 am
content: 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?

author: Tation
author_rank: Regular Contributor
timestamp: May 30, 2024, 08:47:18 am
content: 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).

author: Picuino
author_rank: Super Contributor
timestamp: May 30, 2024, 10:57:59 am
content: 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.

author: gf
author_rank: Super Contributor
timestamp: May 30, 2024, 11:03:12 am
content: Quote from: Picuino on May 30, 2024, 10:57:59 amWhat 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 30, 2024, 11:05:50 am
content: I am thinking of being able to generate several output frequencies and having them all pre-calculated would take up a lot of memory.

author: gf
author_rank: Super Contributor
timestamp: May 30, 2024, 11:11:44 am
content: Quote from: Picuino on May 30, 2024, 11:05:50 amBut you do not need to measure while you change the frequency, do you?

author: Picuino
author_rank: Super Contributor
timestamp: May 30, 2024, 11:15:56 am
content: 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.

author: Tation
author_rank: Regular Contributor
timestamp: May 30, 2024, 11:40:30 am
content: Quote from: Picuino on May 30, 2024, 10:57:59 amYou 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.Quote from: Picuino on May 30, 2024, 10:57:59 amLook 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 *.

author: Tation
author_rank: Regular Contributor
timestamp: May 30, 2024, 11:54:40 am
content: For the "state variable" oscillator:https://www.njohnson.co.uk/pdf/drdes/Chap7.pdf

author: gf
author_rank: Super Contributor
timestamp: May 30, 2024, 12:51:05 pm
content: Quote from: Tation on May 30, 2024, 11:54:40 amIf I understand correctly, the STM32G4 has aCORDIC accelerator unit.

author: Picuino
author_rank: Super Contributor
timestamp: May 30, 2024, 01:52:10 pm
content: Quote from: Tation on May 30, 2024, 11:54:40 amGreat!!!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.Quote from: gf on May 30, 2024, 12:51:05 pmYes, but I don't know how to use it yet.

author: Picuino
author_rank: Super Contributor
timestamp: May 30, 2024, 04:33:21 pm
content: Quote from: gf on May 29, 2024, 07:51:13 pmThe 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:for (i=0; i<N; i++) {acc += ADC1 * ADC2;mean_ADC1 += ADC1;mean_ADC2 += ADC2;}out = acc / N - mean_ADC1 / N * mean_ADC2 / N;

author: Picuino
author_rank: Super Contributor
timestamp: May 30, 2024, 04:38:55 pm
content: 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.

author: dietert1
author_rank: Super Contributor
timestamp: May 30, 2024, 05:35:28 pm
content: 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

author: gf
author_rank: Super Contributor
timestamp: May 31, 2024, 09:28:14 am
content: Quote from: Picuino on May 30, 2024, 04:38:55 pmLet'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*DC22) 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 themcompletely2b) 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 harmonicscompletelyif it is granted that the length of the boxcar is anexactinteger multipleof 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.QuoteYes, it means exactly that. You could do it in principle, but it is computationally expensive (possibly too expensive).

author: Picuino
author_rank: Super Contributor
timestamp: May 31, 2024, 01:56:09 pm
content: 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.

author: gf
author_rank: Super Contributor
timestamp: May 31, 2024, 03:45:56 pm
content: What is the lowest carrier frequency you want to support?

author: Picuino
author_rank: Super Contributor
timestamp: May 31, 2024, 05:17:19 pm
content: 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.

author: Picuino
author_rank: Super Contributor
timestamp: May 31, 2024, 05:56:28 pm
content: My first attempt:Code:#include "main.h"#include "filter.h"#define ADC_MEAN (2048)#define ADC_MEAN_MAX_DEVIATION (1024*8)volatile uint32_t adc_acc;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;p2 = &adc2_buff;p_adc_acc = &adc_acc;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/65536Impulse response after one second = a1^(2500000/8) = 0.00849EDIT:Process time = 54% of CPU time

author: gf
author_rank: Super Contributor
timestamp: May 31, 2024, 05:58:56 pm
content: Quote from: Picuino on May 31, 2024, 05:17:19 pmI think the ADC can also be configured to return signed samples (-2048...2047), where 0 corresponds to Vref/2.

author: dietert1
author_rank: Super Contributor
timestamp: May 31, 2024, 06:01:39 pm
content: 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 pointer2 termination pointer3 constant 2048 for DC subtraction4 Q phase read pointer5 I phase read pointer6 Integrator 1 Q7 Integrator 1 I8,9 Integrator 2 Q10,11 Integrator 2 ISo i would generate the two reference phases numerically and use the second ADC channel only for calibration/supervision.Regards, Dieter

author: gf
author_rank: Super Contributor
timestamp: June 01, 2024, 04:08:43 pm
content: Quote from: Picuino on May 31, 2024, 05:56:28 pmHere's an optimized and corrected version:https://godbolt.org/z/G34oKdv4rI 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.

author: gf
author_rank: Super Contributor
timestamp: June 01, 2024, 05:01:13 pm
content: Quote from: dietert1 on May 31, 2024, 06:01:39 pmThat'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.

author: Picuino
author_rank: Super Contributor
timestamp: June 01, 2024, 05:56:40 pm
content: Quote from: gf on June 01, 2024, 04:08:43 pmProcess time = 24% of CPU time (almost twice as fast)

author: Picuino
author_rank: Super Contributor
timestamp: June 01, 2024, 06:04:08 pm
content: Quote from: gf on June 01, 2024, 05:01:13 pmThis 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.htmlI do not need to calculate the components in phase and the components rotated 90º.

author: Picuino
author_rank: Super Contributor
timestamp: June 01, 2024, 06:13:28 pm
content: 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.

author: gf
author_rank: Super Contributor
timestamp: June 01, 2024, 07:03:32 pm
content: 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 avery 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.

author: dietert1
author_rank: Super Contributor
timestamp: June 01, 2024, 08:39:08 pm
content: 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

author: gf
author_rank: Super Contributor
timestamp: June 01, 2024, 09:53:27 pm
content: Something like this?https://en.wikipedia.org/wiki/Laser_Doppler_vibrometer

author: dietert1
author_rank: Super Contributor
timestamp: June 02, 2024, 06:21:56 am
content: 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

author: Picuino
author_rank: Super Contributor
timestamp: June 02, 2024, 08:22:18 am
content: 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:uint16_t *p1 = &adc1_buff;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.

author: gf
author_rank: Super Contributor
timestamp: June 02, 2024, 08:49:36 am
content: Quote from: dietert1 on June 02, 2024, 06:21:56 amHmm. 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?

author: dietert1
author_rank: Super Contributor
timestamp: June 02, 2024, 09:39:00 am
content: 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

author: gf
author_rank: Super Contributor
timestamp: June 02, 2024, 11:58:21 am
content: Quote from: Picuino on June 02, 2024, 08:22:18 amYes, 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 volatileBut 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.QuoteI think when you encountered this problem, you had declared the buffer as a local variable inside the function.

author: Picuino
author_rank: Super Contributor
timestamp: June 02, 2024, 04:48:29 pm
content: 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/#msg5516341After defining the buffer as volatile, the problem disappeared.I will look into adding a compiler barrier.

author: gf
author_rank: Super Contributor
timestamp: June 02, 2024, 06:17:02 pm
content: Quote from: Picuino on June 02, 2024, 04:48:29 pmSeehttps://godbolt.org/z/MdqTYMqoPI 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:int main(void) {...uint16_t dac1_buff;for (int i = 0; i < 1000; i++) {dac1_buff = i * 4;}...}

author: Picuino
author_rank: Super Contributor
timestamp: June 02, 2024, 07:10:01 pm
content: 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.