Skocz do zawartości
Kamilekkk

Implementacja filtru dolnoprzepustowego na STM32F303VCT6

Pomocna odpowiedź

Dzień dobry,

Piszę program do swojego mikrokontrolera STM32F303VCT6, który wysyła sygnał sinusoidalny na rampie do sterowania laserem. Dodatkowo, ten sam mikrokontroler mierzy napięcie na fotodetektorze oraz mnoży te dwa sygnały - sinusoidalny i odbierany. Do tej pory wygląda na to, że wszystko działa. Potrzebuję jednak zaimplementować jeszcze filtr dolnoprzepustowy, który po wymnożeniu tych dwóch sygnałów, przefiltruje mi sygnał tak, aby na innym pinie skonfigurowanym pod przetwornik cyfrowo-analogowy "obetnie" mi żądane częstotliwości. Szukając w internecie implementacji takiego filtra nie uzyskałem satysfakcjonującej mnie odpowiedzi. Czy ktoś mógłby dokładnie wyjaśnić jak to zrobić? A może jest jakaś tego typu funkcja przy wykorzystaniu bibliotek HAL? Z góry dziękuję za pomoc!

Udostępnij ten post


Link to post
Share on other sites
(edytowany)
46 minut temu, Kamilekkk napisał:

wyjaśnić jak to zrobić?

Jak co zrobić? Jak szukać czegoś o filtrach cyfrowych? Zacznij od dobrych pytań, np: "FIR filter design" albo "FIR filter calculator". Zapewniam Cię, że pierwsza strona wyników to będą w większości dobre trafienia. Przykłady pozyskane w 10 sekund:

https://www.arc.id.au/FilterDesign.html

https://dspguru.com/dsp/links/filter-design-tools/

http://www.iowahills.com/5FIRFiltersPage.html

https://iowegian.com/download/

A jeśli pytasz jak zrobić filtr to napisz najpierw jaki. Bo zdanie "obetnie mi żądane częstotliwości" jest zbyt ogólne. Nie znamy Twoich wymagań - może są trywialne a może megatrudne do spełnienia a nie każdy filtr nada się do każdego zastosowania. Nawiasem mówiąc za DACem także potrzebujesz filtra, tylko analogowego. Jeśli już zabierasz się za cyfrowe przetwarzanie sygnałów, to warto coś poczytać. Czy możesz napisać coś więcej? Jaka jest częstotliwość próbkowania, co chcesz wycinać i jak dobrze? Bity, herce, decybele - generalnie liczby., bez lania wody typu "tylko trochę" albo "najlepiej jak się da".

EDIT: A implementacje filtrów masz m.in. w bibliotece cmsis:

https://arm-software.github.io/CMSIS_5/DSP/html/index.html

Edytowano przez marek1707
  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

@marek1707 dziękuję za szybką odpowiedź oraz podesłane linki! Na pewno poczytam dzięki temu więcej o filtrach. Wspomniałeś abym napisał coś więcej - Chodzi o FDP, ogólny filtr dolnoprzepustowy. Nie wiem do końca jak to nazwać, ale mam wiele różnych częstotliwości - załóżny od 20Hz do 2000Hz i potrzebuję, aby przez filtr przeszły tylko sygnały o częstotliwości nie większej niż 500Hz, a reszta została "wytłumiona" - aby nie została brana pod uwagę. Btw z góry dziękuję za pomoc i przepraszam jeżeli czegoś nie do końca rozumiem, bądź niejasno się wyrażam - jestem początkujący w temacie 

Udostępnij ten post


Link to post
Share on other sites

Filtry cyfrowe pracują w czasie dyskretnym więc najważniejszym parameterem względem którego liczy się wszystko inne jest częstotliwość próbkowania. Od tego wychodzisz i na jej podstawie określasz wszelkie paramtery częstotliwościowe, np. częstotliwości odcięcia lub środkowe. Filtr cyfrowy przecież "nie wie" co się dzieje w rzeczywistym świecie. To de facto jakaś funkcja, którą karmisz swoim sygnałem a która oddaje (zwykle) jedną próbkę na wyjściu za jedną na wejściu. Przykładowo jeśli policzysz współczynniki filtra dolnoprzepustowego dla F0=200Hz i z założenia taktowanego FS=8kHz a potem zmienisz FS na 10kHz to filtr będzie obcinał na 250Hz, bo jego F0 w obliczeniach przyjąłeś jako 0.025*FS. Dlatego FS to podstawa całego systemu DSP. Napisz jak ten sinus generujesz, jak często wrzucasz go do DACa, jak często próbkujesz sygnał z fotodetektora i czego oczekujesz od filtra. Sama informacja o 500Hz nic nie daje bez wiedzy o FS.

Generalnie jedna struktura filtra cyfrowego np. typu FIR (czyli jedna funkcja w programie) może realizować zupełnie dowolne filtry (lowpass, highpass, bandpass, notch, itd) w granicach swoich naturalnych ograniczeń (liczba bitów - czyli dynamika sygnału, długość filtra - mówi o jego "jakości" i rzecz jasna FS z jaką go taktujesz) a kształt ch-ki częstotliwościowej (względem FS) zależy jedynie od wartości współczynników tzw. filter core. Filtry tego typu mają dobrze opracowaną i szeroko znaną teorię, czego najlepszym przykładem są darmowe kalkulatory w których możesz praktycznie dowolny filtr zaprojektować w 10 sekund. Oczywiście zawsze można napisać własne oprogramowanie a nawet odpowiedni algorytm zaszyć we własnym kodzie. Wtedy procesor może sam (na podstawie jakichś cech sygnału lub Twojej komendy) policzyć nowy filtr dla nowych wymagań. Jeśłi masz trochę ciekawości i lubisz wyzwania polecam biblię DSP czyli wydane po polsku "Cyfrowe przetwarzanie sygnałów" Smitha - bardzo praktyczny podręcznik dla inżynierów i nie tylko 🙂 

  • Lubię! 2

Udostępnij ten post


Link to post
Share on other sites

@marek1707 nie ukrywam że jestem dosyć zielony w programowaniu mikrokontrolera, a kod mam napsiany, jeżeli mogę to tak ująć "na pałę". W Cube skonfigurowałem HSE na Crystal/Ceramic Resonator po czym w konfiguracji zegarów ustawiłem maksymalną możliwą częstotiwość: klok.thumb.PNG.382d27dff34739af56c3669a725901c4.PNG Dalej, kod programu wygląda następująco (praktycznie wszytsko dzieje się w pętli while):

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

/* Includes ------------------------------------------------------------------*/
#include "main.h"

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

#include "math.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;

DAC_HandleTypeDef hdac;

UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_tx;

/* USER CODE BEGIN PV */

uint16_t i, j, k;
uint8_t buffin[128], buffout[4096];
uint16_t PomiarADC;
float vsense;

/* 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_ADC1_Init(void);
static void MX_DAC_Init(void);
static void MX_USART1_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_DMA_Init();
  MX_ADC1_Init();
  MX_DAC_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  for (i = 0; i <= 128; i++) buffin[i] = (int)(5*sin(6.28*i/128))+128; // sinus razy 20

  HAL_DAC_Start(&hdac, DAC_CHANNEL_1);
  HAL_DAC_Start(&hdac, DAC_CHANNEL_2);

  HAL_ADC_Start(&hadc1);
  //const float v25 = 1.43;
  const float supplyvoltage = 3.0;
  const float adcresolution = 4096.0;

  /* USER CODE END 2 */

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

  j = 0;
  k = 0;
  i = 0;

  while (1)
  {
	  if (k == 128) //128
	  {
		  j++;
		  k = 0;
	  }
	  if (j == 100) j = 0; //100
	  if (i == 128) i = 0;
	  HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_8B_R, (buffin[i] + (j/3)) - 120); // -120 aby obnizyc sygnal - zwiazane z +128 w generacji sinusa

	  PomiarADC = HAL_ADC_GetValue(&hadc1);
	  vsense = (supplyvoltage*PomiarADC)/adcresolution;

	  HAL_DAC_SetValue(&hdac, DAC_CHANNEL_2, DAC_ALIGN_8B_R, (buffin[i] + j) * vsense);
	  i++;
	  k++;

	  //if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)
	  //{
		  //PomiarADC = HAL_ADC_GetValue(&hadc1);
		  //vsense = (supplyvoltage*PomiarADC)/adcresolution;

		  //HAL_ADC_Start(&hadc1);
	  //}

	  //HAL_DAC_SetValue(&hdac, DAC_CHANNEL_2, DAC_ALIGN_8B_R, buffin[i] + j/*(buffin[i] + j) * 0*/);
	  HAL_ADC_Start(&hadc1);

    /* USER CODE END WHILE */

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

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

  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses 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_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_ADC12;
  PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
  PeriphClkInit.Adc12ClockSelection = RCC_ADC12PLLCLK_DIV1;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

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

  /* USER CODE BEGIN ADC1_Init 0 */

  /* USER CODE END ADC1_Init 0 */

  ADC_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_ASYNC_DIV1;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.LowPowerAutoWait = DISABLE;
  hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
  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_3;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.SamplingTime = ADC_SAMPLETIME_601CYCLES_5;
  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 DAC Initialization Function
  * @param None
  * @retval None
  */
static void MX_DAC_Init(void)
{

  /* USER CODE BEGIN DAC_Init 0 */

  /* USER CODE END DAC_Init 0 */

  DAC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN DAC_Init 1 */

  /* USER CODE END DAC_Init 1 */
  /** DAC Initialization 
  */
  hdac.Instance = DAC;
  if (HAL_DAC_Init(&hdac) != HAL_OK)
  {
    Error_Handler();
  }
  /** DAC channel OUT1 config 
  */
  sConfig.DAC_Trigger = DAC_TRIGGER_NONE;
  sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
  if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  /** DAC channel OUT2 config 
  */
  if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN DAC_Init 2 */

  /* USER CODE END DAC_Init 2 */

}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

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

  /* USER CODE END USART1_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_Channel4_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{

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

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

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

  /* USER CODE END Error_Handler_Debug */
}

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

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

No i generalnie jak sprawdzałem na oscyloskopie ta rampa ma ok 20Hz, natomiast sinus znajdujący się na tej rampie około 2000Hz. Nie wiem natomiast jaka jest częstotliwość samego próbkowania, bo rozumiem, że skoro to dzieje się wszystko po kolei w pętli while, to jest to zależne od czasu potrzebnego do wygenerowania danej wartości nadawanego sygnału oraz czasu odczytu napięcia? A może jestem w toalnym błędzie? Tak czy tak,  to nie jestem w stanie na chwilę obecną z moją wiedzą powiedzieć nic o częstotliwości próbkowania, jeżeli to wynika z kodu w jakiś sposób to pewnie Ty szybciej znajdziesz odpowiedź na to pytanie niż zielony ja. 

Udostępnij ten post


Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.


×
×
  • Utwórz nowe...