FMAC 디지털 필터 구현 방법 (with the STM32 G4 MCU Package)(3) - FIR filter

FIR filter

FIR 필터는 기본적으로 필터 계수와 신호의 컨볼루션(회선)입니다. 계수의 수는 신호 대역폭에 대한 최소 노치 크기를 결정합니다. 계수가 많으면 협대역 노이즈 신호의 정확한 노칭이 가능하므로 손실되는 정보의 양이 최소화됩니다.

그러나 N개의 계수를 계산하려면 일반적으로 N2개의 연산 순서가 필요하므로 계수의 수는 처리 가능한 리소스에 의해 제한됩니다.

이 예에서는 그림 7과 같이 51탭 FIR 필터를 사용합니다.

그림 7

필터 계수는 대부분 부동 소수점 형식으로 계산되며 16비트 고정 소수점으로 변환해야 합니다.

FMAC에서 사용하는 형식은 q1.15이며, 비트 0에서 14는 소수 자릿수(즉, 이진 소수점 오른쪽)를 나타내고 비트 15는 부호/정수를 나타냅니다. 따라서 Q1.15 형식은 -1.0(0x8000)에서 +0.999969482421875(0x7FFF) 범위의 숫자를 나타낼 수 있습니다.

적절하게 설계된 Adaptive 알고리즘은 1보다 작거나 같은 계수 절대값만 생성해야 하므로 모든 계수는 q1.15 숫자 범위 내에 있어야 합니다. 그렇지 않은 경우 모든 계수를 가장 큰 계수 값 또는 더 큰 값으로 나누어야 합니다.

부동 소수점에서 고정 소수점으로 변환하려면 모든 계수에 32768(0x8000)을 곱하고 int16으로 변환해야 합니다.

 value_q15 = (int16_t)(value_f32*0x8000)

 

cortex-M4에서 위의 작업은 프로세서 부동 소수점 장치(FPU)를 사용하고 8클럭 사이클이 걸립니다. 따라서 51개의 계수를 변환하려면 ~408 클럭 사이클이 필요합니다.

이 예에서 생성된 부동 소수점 및 고정 소수점 필터 계수는 각각 표 1과 표 2에 나열되어 있습니다(프레임 1의 경우).

표 1

표 2

사용 가능한 계수의 비트 수가 줄어들기 때문에(단정밀도 부동 소수점 형식의 경우 24 대신 16) 필터의 성능이 영향을 받습니다. FMAC 사용 여부를 고려할 때 이 점을 고려해야 합니다.

정밀도 손실로 인해 허용할 수 없는 성능 저하가 발생하면 소프트웨어 구현이 더 적절할 수 있습니다(부동 소수점 또는 32비트 고정 소수점).

이 예에서 고정 소수점 필터 스펙트럼은 0.007dB 이내로 부동 소수점과 동일합니다(그림 8).

그림 8

FMAC configuration

FMAC는 STM32CubeG4 MCU 패키지의 HAL 드라이버를 사용하여 구성할 수 있습니다. FMAC 레지스터에 액세스하기 전에 FMAC 클록을 활성화해야 합니다.

__HAL_RCC_FMAC_CLK_ENABLE();
 

계수를 위해 시스템 메모리 영역을 할당해야 합니다.

/* Declare an array to hold the filter coefficients */
static int16_t aFilterCoeffB[51];
 

또한 FMAC 매개변수를 포함하는 구조체를 선언해야 합니다.

FMAC_HandleTypeDef hfmac;
 

이제 HAL_FMAC_FilterConfig() 함수를 사용하여 FMAC를 구성할 수 있습니다.

/* declare a filter configuration structure */
FMAC_FilterConfigTypeDef sFmacConfig;
/* Set the coefficient buffer base address */
sFmacConfig.CoeffBaseAddress = 0;
/* Set the coefficient buffer size to the number of coeffs */
sFmacConfig.CoeffBufferSize = 51;
/* Set the Input buffer base address to the next free address */
sFmacConfig.InputBaseAddress = 51;
/* Set the input buffer size greater than the number of coeffs */
sFmacConfig.InputBufferSize = 100;
/* Set the input watermark to zero since we are using DMA */
sFmacConfig.InputThreshold = 0;
/* Set the Output buffer base address to the next free address */
sFmacConfig.OutputBaseAddress = 151;
/* Set the output buffer size */
sFmacConfig.OutputBufferSize = 100;
/* Set the output watermark to zero since we are using DMA */
sFmacConfig.OutputThreshold = 0;
/* No A coefficients since FIR */
sFmacConfig.pCoeffA = NULL;
sFmacConfig.CoeffASize = 0;
/* Pointer to the coefficients in memory */
sFmacConfig.pCoeffB = aFilterCoeffB;
/* Number of coefficients */
sFmacConfig.CoeffBSize = 51;
/* Select FIR filter function */
sFmacConfig.Filter = FMAC_FUNC_CONVO_FIR;
/* Enable DMA input transfer */
sFmacConfig.InputAccess = FMAC_BUFFER_ACCESS_DMA;
/* Enable DMA output transfer */
sFmacConfig.OutputAccess = FMAC_BUFFER_ACCESS_DMA;
/* Enable clipping of the output at 0x7FFF and 0x8000 */
sFmacConfig.Clip = FMAC_CLIP_ENABLED;
/* P parameter contains number of coefficients */
sFmacConfig.P = 51;
/* Q parameter is not used */
sFmacConfig.Q = FILTER_PARAM_Q_NOT_USED;
/* R parameter contains the post-shift value (none) */
sFmacConfig.R = 0;
/* Configure the FMAC */
if (HAL_FMAC_FilterConfig(&hfmac, &sFmacConfig) != HAL_OK)
  /* Configuration Error */
  Error_Handler();
 

HAL_FMAC_FilterConfig() 함수는 구성 및 제어 레지스터를 프로그래밍하고 계수를 FMAC 로컬 메모리(X2 버퍼)에 로드합니다.

 

DMA configuration

여기에서는 3개의 DMA 채널을 구성합니다.

 

채널 1 - 프리로드(Preload) 채널. 프리로드 채널은 샘플로 FMAC 입력 버퍼를 초기화하는 데 사용됩니다. 계수가 변경될 때마다 FMAC를 중지했다가 다시 시작해야 합니다.

 

FMAC는 입력 버퍼가 채워질 때까지 처리를 시작하지 않습니다. 따라서 샘플 손실을 방지하기 위해 이전 프레임의 마지막 51개 샘플을 사용하여 필터가 중지되었을 때와 동일한 상태로 필터를 초기화합니다. 이것은 소프트웨어로 수행할 수 있지만 이 예에서는 DMA를 사용합니다.

 

채널 2 - 쓰기채널. 쓰기 채널은 FMAC 입력 버퍼가 가득 차지 않을 때마다 메모리에서 FMAC로 입력 샘플을 전송합니다.

 

채널 3 - 읽기 채널. 읽기 채널은 FMAC 출력 버퍼에 읽지 않은 샘플이 있을 때마다 FMAC의 출력 샘플을 메모리로 전송합니다.

 

다음 코드는 DMA 채널을 구성하는 방법을 보여줍니다. 채널 매개변수를 보유할 세 가지 구조체를 선언합니다.

DMA_HandleTypeDef hdma_fmac_preload; /* Preload channel */
DMA_HandleTypeDef hdma_fmac_read; /* Read channel */
DMA_HandleTypeDef hdma_fmac_write; /* Write channel */
 

그런 다음 HAL_DMA_Init() 함수에 전달되는 구조를 초기화합니다.

/* Preload channel initialisation */
hdma_fmac_preload.Instance = DMA1_Channel1;
hdma_fmac_preload.Init.Request = DMA_REQUEST_MEM2MEM;
hdma_fmac_preload.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_fmac_preload.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_fmac_preload.Init.MemInc = DMA_MINC_DISABLE;
hdma_fmac_preload.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_fmac_preload.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_fmac_preload.Init.Mode = DMA_NORMAL;
hdma_fmac_preload.Init.Priority = DMA_PRIORITY_HIGH;
if (HAL_DMA_Init(&hdma_fmac_preload) != HAL_OK)
  Error_Handler();
/* Connect the DMA channel to the FMAC handle */
__HAL_LINKDMA(hfmac,hdmaPreload,hdma_fmac_preload);
/* Write channel initialisation */
hdma_fmac_write.Instance = DMA1_Channel2;
hdma_fmac_write.Init.Request = DMA_REQUEST_FMAC_WRITE;
hdma_fmac_write.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_fmac_write.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_fmac_write.Init.MemInc = DMA_MINC_ENABLE;
hdma_fmac_write.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_fmac_write.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_fmac_write.Init.Mode = DMA_NORMAL;
hdma_fmac_write.Init.Priority = DMA_PRIORITY_HIGH;
if (HAL_DMA_Init(&hdma_fmac_write) != HAL_OK)
  Error_Handler();
/* Connect the DMA channel to the FMAC handle */
__HAL_LINKDMA(hfmac,hdmaIn,hdma_fmac_write);
/* Read channel initialisation */
hdma_fmac_read.Instance = DMA1_Channel3;
hdma_fmac_read.Init.Request = DMA_REQUEST_FMAC_READ;
hdma_fmac_read.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_fmac_read.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_fmac_read.Init.MemInc = DMA_MINC_ENABLE;
hdma_fmac_read.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_fmac_read.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_fmac_read.Init.Mode = DMA_NORMAL;
hdma_fmac_read.Init.Priority = DMA_PRIORITY_HIGH;
if (HAL_DMA_Init(&hdma_fmac_read) != HAL_OK)
  Error_Handler();
/* Connect the DMA channel to the FMAC handle */
__HAL_LINKDMA(hfmac,hdmaIn,hdma_fmac_read);
 

읽기 채널 및 사전 로드 채널에 대한 인터럽트를 활성화해야 전송이 완료될 때 소프트웨어에 알릴 수 있습니다.

 

Running the filter

필터는 필터링할 준비가 된 새 샘플의 프레임 값이 있을 때마다 소프트웨어에 의해 트리거됩니다. 새 샘플은 시스템 메모리에 이중 배열로 저장됩니다.

static int16_t aInputValues[2][2048];
int CurrentInputArraySize = 2048;
 

이중 배열을 사용하면 이전 프레임이 처리되는 동안 샘플의 새 프레임을 수신하고 메모리에 저장할 수 있습니다. 현재 사용 중인 프레임을 선택하려면 인덱스가 필요합니다.

int Frame = 1;
 

또한 출력 데이터를 저장할 배열이 필요합니다.

static int16_t aCalculatedFilteredData[2048];
int ExpectedCalculatedFilteredDataSize = 2048;
 

소프트웨어는 이전 프레임의 마지막 51개 샘플의 사전 로드를 트리거합니다. 이것은 FMAC가 중지된 후 필터 상태를 복원하기 위한 것입니다.

if (HAL_FMAC_FilterPreload_DMA(&hfmac, &aInputValues[CurrentInputArray][1997], 51, NULL, 0) != HAL_OK)
  Error_Handler();
/* Switch frames */
Frame ? Frame=0 : Frame=1;
 

사전 로드가 완료되면(DMA 채널 1 터미널 카운트 인터럽트에 의해 신호됨) 소프트웨어는 현재 프레임에서 샘플 전송을 시작하기 위해 DMA 쓰기 채널을 트리거합니다.

if (HAL_FMAC_AppendFilterData(&hfmac, &aInputValues[CurrentInputArray][0], &CurrentInputArraySize) != HAL_OK)
 Error_Handler();
 

소프트웨어가 FMAC를 시작합니다.

if (HAL_FMAC_FilterStart(&hfmac, aCalculatedFilteredData, &ExpectedCalculatedFilteredDataSize) != HAL_OK)
 Error_Handler();
 

FMAC는 출력 샘플 계산을 시작하고 출력 버퍼에 씁니다. 각각의 새 샘플에서 FMAC는 읽기 채널에서 DMA 요청을 생성하고 DMA는 새 샘플을 시스템 메모리로 전송합니다.

 

이것은 전체 프레임이 처리될 때까지 소프트웨어 개입 없이 계속되며, 이 시점에서 DMA 읽기 채널은 프로세서에 터미널 카운트 인터럽트를 생성합니다. 프로세서가 FMAC를 중지합니다.

if (HAL_FMAC_FilterStop(&hfmac) != HAL_OK)
 Error_Handler();
 

소프트웨어는 이제 필요한 경우 HAL_FMAC_FilterConfig()를 다시 호출하여 계수를 업데이트할 수 있습니다. 새 계수는 이전에 FilterCoeffB[]에 저장되어 있어야 합니다.

 

다음 프레임을 처리하기 위해 프리로드(HAL_FMAC_FilterPreload_DMA())부터 시작하여 위의 절차를 반복하기만 하면 됩니다.

 

<계속>