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

0 Members and 1 Guest are viewing this topic.

Offline PicuinoTopic starter

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

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

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

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

/* USER CODE END Includes */

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

/* USER CODE END PTD */

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

/* USER CODE END PD */

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

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
DAC_HandleTypeDef hdac1;
DMA_HandleTypeDef hdma_dac1_ch1;

TIM_HandleTypeDef htim6;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_DAC1_Init(void);
static void MX_TIM6_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

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

/* USER CODE END 0 */

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

    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

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

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

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

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

    /* USER CODE BEGIN SysInit */

    __disable_irq();

    /* USER CODE END SysInit */

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

    /* USER CODE END 2 */

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

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

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

        /* USER CODE BEGIN 3 */

    }
    /* USER CODE END 3 */
}

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

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

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

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

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

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

    /* USER CODE BEGIN DAC1_Init 0 */

    /* USER CODE END DAC1_Init 0 */

    DAC_ChannelConfTypeDef sConfig = { 0 };

    /* USER CODE BEGIN DAC1_Init 1 */

    /* USER CODE END DAC1_Init 1 */

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

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

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

}

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

    /* USER CODE BEGIN TIM6_Init 0 */

    /* USER CODE END TIM6_Init 0 */

    TIM_MasterConfigTypeDef sMasterConfig = { 0 };

    /* USER CODE BEGIN TIM6_Init 1 */

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

    /* USER CODE END TIM6_Init 2 */

}

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

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

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

}

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

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

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

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

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

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

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

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

Offline PicuinoTopic starter

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

I'm going to try that way.
 

Online dietert1

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

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

Offline PicuinoTopic starter

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

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

I have enough for now. I will continue later.
 

Online dietert1

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

Regards, Dieter
 

Offline PicuinoTopic starter

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

 

Offline PicuinoTopic starter

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

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

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

Online dietert1

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

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

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

Offline PicuinoTopic starter

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

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

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

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

Offline PicuinoTopic starter

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


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

Offline PicuinoTopic starter

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

Solution: declare the DAC buffer as volatile.
 

Online dietert1

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

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

Offline PicuinoTopic starter

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

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

Offline PicuinoTopic starter

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


 

Offline PicuinoTopic starter

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

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

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

Online dietert1

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

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

Offline PicuinoTopic starter

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

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

Online dietert1

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

Regards, Dieter
 

Offline PicuinoTopic starter

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

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

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

Offline PicuinoTopic starter

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

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

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

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



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


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

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

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


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


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

 

Offline gf

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

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

Offline PicuinoTopic starter

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

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


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

Offline gf

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

Offline PicuinoTopic starter

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

Online dietert1

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

Regards, Dieter
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf