/**************************************************************************//**
* @file main.c
* @version V1.00
* @brief Perform Fast Fourier Transform with ADC samples.
*
* @copyright (C) 2019 Nuvoton Technology Corp. All rights reserved.
*****************************************************************************/
#include <stdio.h>
#include <math.h>
#include "NuMicro.h"
#include "fft_lut.h"
/* Select the macro FFT_N_POINT_SEL defined in fft_lut.h.
* The look-up table for fft algorithm requires user indicates specific data length.
*/
/* System clock frequency */
#define PLL_CLOCK (48000000)
/* The parameter SAMPLE_RATE is configurable. It is based on your application.
* The unit of SAMPLE_RATE is hertz.
*/
#define SAMPLE_RATE (1024)
/* The parameter SAMPLE_DATA_RSHIFT is configurable, the purpose is to truncate
* the data size by right shifting the raw ADC data, and it can prevent the buffer
* from overflow when calculating a large amount N point of FFT. The longer data
* length is selected, the higher SAMPLE_DATA_RSHIFT is required. If the spectrum
* buffer is overflow, try to increase the SAMPLE_DATA_RSHIFT parameter.
*/
#define SAMPLE_DATA_RSHIFT (5)
#define SHRINK(x) ((x) >> SAMPLE_DATA_RSHIFT)
/* Macros to simplify the math presentations for FFT algorithm */
#define COS(angle_index) (g_ai16cos[angle_index])
#define SIN(angle_index) (g_ai16sin[angle_index])
#define REV(origin_index) (g_ai16rev[origin_index])
/*----------------------------------------------------------------------------------------------------------*/
/* Define Function Prototypes */
/*----------------------------------------------------------------------------------------------------------*/
void SYS_Init(void);
void UART0_Init(void);
void TIMER0_Init(void);
void ADC_Init(void);
void Remove_DC_Component(int *pi32DataBuffer, uint32_t u32Len);
void NuFFT(int *pi32RealBuffer, int *pi32ImageBuffer, uint32_t u32Len);
void Display_Spectrum(int *pi32Spectrum, uint32_t u32Len, uint32_t u32SampleRate);
void Display_AdcData(int *pi32Data, uint32_t u32Len);
/*----------------------------------------------------------------------------------------------------------*/
/* Define global variables and constants */
/*----------------------------------------------------------------------------------------------------------*/
const uint8_t g_u8AdcChannel = 2;
/* Look-up table for FFT twiddle factors and the bit-reversal index */
const int16_t g_ai16cos[SEQUENCE_LEN] = COS_LUT;
const int16_t g_ai16sin[SEQUENCE_LEN] = SIN_LUT;
const int16_t g_ai16rev[SEQUENCE_LEN] = REV_LUT;
/* The data buffer for storing the ADC data samples and the output spectrum */
int g_i32RealBuffer [SEQUENCE_LEN];
int g_i32ImageBuffer[SEQUENCE_LEN];
int g_i32Spectrum [SEQUENCE_LEN];
/* Global variables for handing ADC conversions */
volatile uint32_t g_u32AdcConvNum = 0;
/*----------------------------------------------------------------------------------------------------------*/
/* Function: SYS_Init */
/* Parameters: None. */
/* Returns: None. */
/* Description: System and periphial module clock setting. */
/*----------------------------------------------------------------------------------------------------------*/
void SYS_Init(void)
{
/* Enable HIRC clock (Internal RC 48MHz) */
CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk);
/* Enable HIRC clock (Internal RC 48MHz) */
CLK_EnableXtalRC(CLK_PWRCTL_HXTEN);
/* Wait for HIRC clock ready */
CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk);
CLK_WaitClockReady(CLK_STATUS_HXTSTB_Msk);
/* Set core clock as PLL_CLOCK from PLL (From HXT if HXT is enabled)*/
CLK_SetCoreClock(PLL_CLOCK);
/* Enable UART module clock */
CLK_EnableModuleClock(UART0_MODULE);
/* Select UART module clock source as HIRC/2 and UART module clock divider as 1 */
CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UARTSEL_HIRC_DIV2, CLK_CLKDIV0_UART(1));
/* Enable TMR0 module clock */
CLK_EnableModuleClock(TMR0_MODULE);
/* Set TMR0 module clock source as HIRC/2 */
CLK_SetModuleClock(TMR0_MODULE, CLK_CLKSEL1_TMR0SEL_HXT, 0);
/* Enable ADC module clock */
CLK_EnableModuleClock(ADC_MODULE);
/* Set ADC clock source to PCLK0=48MHz, set divider to 3, ADC clock will be 16 MHz */
CLK_SetModuleClock(ADC_MODULE, CLK_CLKSEL1_ADCSEL_PCLK0, CLK_CLKDIV0_ADC(3));
/* Update core clock */
SystemCoreClockUpdate();
/* Set GPB multi-function pins for UART0 RXD and TXD */
SYS->GPB_MFPL = SYS_GPB_MFPL_PB0MFP_UART0_RXD | SYS_GPB_MFPL_PB1MFP_UART0_TXD;
/* Set PD.2 and PD.3 to input mode */
PD->MODE &= ~(GPIO_MODE_MODE2_Msk | GPIO_MODE_MODE3_Msk);
/* Set PD2 and PD3 to ADC mode for ADC input channel 2 and 3 */
SYS->GPD_MFPL &= ~(SYS_GPD_MFPL_PD2MFP_Msk | SYS_GPD_MFPL_PD3MFP_Msk);
SYS->GPD_MFPL |= (SYS_GPD_MFPL_PD2MFP_ADC_CH2 | SYS_GPD_MFPL_PD3MFP_ADC_CH3);
/* Disable the digital input paths of ADC analog pins */
GPIO_DISABLE_DIGITAL_PATH(PD, BIT2 | BIT3);
}
/*----------------------------------------------------------------------------------------------------------*/
/* Function: UART0_Init */
/* Parameters: None. */
/* Returns: None. */
/* Description: Initialize UART0 module, setting buadrate is 115200 bps */
/*----------------------------------------------------------------------------------------------------------*/
void UART0_Init(void)
{
/* Reset UART0 */
SYS_ResetModule(UART0_RST);
/* Configure UART0 and set UART0 baud rate */
UART_Open(UART0, 115200);
}
/*----------------------------------------------------------------------------------------------------------*/
/* Function: TIMER0_Init */
/* Parameters: None. */
/* Returns: None. */
/* Description: Initialize TIMER0 module. Setup timeout trigger ADC. */
/*----------------------------------------------------------------------------------------------------------*/
void TIMER0_Init(void)
{
uint32_t u32Freq;
/* Set TIMER0 working frequency to SAMPLE_RATE */
u32Freq = TIMER_Open(TIMER0, TIMER_PERIODIC_MODE, SAMPLE_RATE);
if (u32Freq != SAMPLE_RATE)
{
printf("# Warning. Sample rate does not equal to the pre-defined setting.\n\r");
}
/* Enable timer timeout interrupt trigger ADC */
TIMER0->CTL |= TIMER_TRGSRC_TIMEOUT_EVENT | TIMER_TRG_TO_ADC | TIMER_CTL_INTEN_Msk;
printf(" CTL:0x%08X\n\r", TIMER0->CTL);
printf(" CMP:0x%08X\n\r", TIMER0->CMP);
}
/*----------------------------------------------------------------------------------------------------------*/
/* Function: ADC_Init */
/* Parameters: None. */
/* Returns: None. */
/* Description: Initialize ADC module channel 2. Enable TIMER trigger ADC function. */
/*----------------------------------------------------------------------------------------------------------*/
void ADC_Init(void)
{
/* Set the ADC operation mode as single, input mode as single-end and enable the analog input channel 2 */
ADC_Open(ADC, ADC_ADCR_DIFFEN_SINGLE_END, ADC_ADCR_ADMD_SINGLE, 0x1 << g_u8AdcChannel);
/* Enable TIMER Trigger ADC */
ADC_EnableTimerTrigger(ADC, ADC_ADCR_TRGS_TIMER, 0);
/* Power on ADC module */
ADC_POWER_ON(ADC);
/* Clear the A/D interrupt flag for safe */
ADC_CLR_INT_FLAG(ADC, ADC_ADF_INT);
/* Enable the ADC interrupt */
ADC_EnableInt(ADC, ADC_ADF_INT);
NVIC_EnableIRQ(ADC_IRQn);
}
/*----------------------------------------------------------------------------------------------------------*/
/* Function: Remove_DC_Component */
/* Parameters: pi32DataBuffer is the raw data buffer pointer. */
/* Parameters: u32Len is the length of the raw data buffer. */
/* Returns: None. */
/* Description: None. */
/*----------------------------------------------------------------------------------------------------------*/
void Remove_DC_Component(int *pi32DataBuffer, uint32_t u32Len)
{
uint32_t i, u32Mean, u32Accumulate = 0;
for (i = 0; i < u32Len; i++)
{
u32Accumulate += pi32DataBuffer[i];
}
u32Mean = u32Accumulate / u32Len;
for (i = 0; i < u32Len; i++)
{
pi32DataBuffer[i] -= u32Mean;
}
}
/*----------------------------------------------------------------------------------------------------------*/
/* Function: NuFFT */
/* Parameters: pi32RealBuffer is the pointer to the real part of the data buffer. */
/* Parameters: pi32ImageBuffer is the pointer to the image part of the data buffer. */
/* Parameters: u32Len indicates the length of the data sequence. */
/* Returns: None. */
/* Description: Fast Fourier Transform Algorithm. Transform the time-domain data */
/* sequence into a frequency-domain spectrum. */
/*----------------------------------------------------------------------------------------------------------*/
void NuFFT(int *pi32RealBuffer, int *pi32ImageBuffer, uint32_t u32Len)
{
const uint32_t N = u32Len;
const uint32_t u32TotalStage = log(N) / log(2);
uint32_t u32Stage, u32Span, u32Node, u32Twiddle, u32Angle;
int32_t i32X1Real, i32X1Image, i32X2Real, i32X2Image;
/* Iterations for log2(N) FFT butterfly stages */
for (u32Stage = 0; u32Stage < u32TotalStage; u32Stage++)
{
/* Span indicates the buffer index for constituting a butterfly matrix */
u32Span = pow(2, u32Stage);
for (u32Twiddle = 0; u32Twiddle < u32Span; u32Twiddle++)
{
u32Angle = (N >> 1) / u32Span * u32Twiddle;
for (u32Node = u32Twiddle; u32Node < N; u32Node += 2 * u32Span)
{
/* Get the real and image part of the input variable X1 */
i32X1Real = pi32RealBuffer [u32Node];
i32X1Image = pi32ImageBuffer[u32Node];
/* Get the real and image part of the input variable X2 */
i32X2Real = pi32RealBuffer [u32Node + u32Span];
i32X2Image = pi32ImageBuffer[u32Node + u32Span];
/* Y1 = X1 + X2 * twiddle factor
* The real part is real * cos() + image * sin()
* The image part is -real * sin() + image * cos()
*/
pi32RealBuffer [u32Node] = i32X1Real + RESCALE(i32X2Real * COS(u32Angle)) + RESCALE(i32X2Image * SIN(u32Angle));
pi32ImageBuffer[u32Node] = i32X1Image - RESCALE(i32X2Real * SIN(u32Angle)) + RESCALE(i32X2Image * COS(u32Angle));
/* Y2 = X1 - X2 * twiddle factor
* The real part is -real * cos() - image * sin()
* The image part is real * sin() - image * cos()
*/
pi32RealBuffer [u32Node + u32Span] = i32X1Real - RESCALE(i32X2Real * COS(u32Angle)) - RESCALE(i32X2Image * SIN(u32Angle));
pi32ImageBuffer[u32Node + u32Span] = i32X1Image + RESCALE(i32X2Real * SIN(u32Angle)) - RESCALE(i32X2Image * COS(u32Angle));
}
}
}
}
/*----------------------------------------------------------------------------------------------------------*/
/* Function: Display_Spectrum */
/* Parameters: pi32Spectrum is the spectrum buffer pointer. */
/* Parameters: u32Len is the length of the spectrum buffer. */
/* Parameters: u32SampleRate is the frequency of TIMER trigger EADC. */
/* Returns: none. */
/* Description: Show the detail informations from the spectrum bffer. */
/*----------------------------------------------------------------------------------------------------------*/
void Display_Spectrum(int *pi32Spectrum, uint32_t u32Len, uint32_t u32SampleRate)
{
const float fconstResolution = (float)u32SampleRate / (float)u32Len;
int i, i32Base, i32Accumulate = 0;
printf("+-----------------------------------------------------------+\n\r");
printf("Spectrum Parameters \n\r");
printf("# Sample rate: %d Hz \n\r", u32SampleRate);
printf("# Data length: %d samples \n\r", u32Len);
printf("# Resolution : %.2f Hz \n\r", fconstResolution);
printf("+-----------------------------------------------------------+\n\r");
printf("#Index #Frequency(Hz) #Amplitude #Ratio \n\r");
for (i = 0; i < u32Len; i++)
{
i32Accumulate += pi32Spectrum[i];
}
for (i = 0; i < u32Len; i++)
{
i32Base = (i <= u32Len / 2) ? (i) : (i - u32Len);
printf(" %-7d %-15.2f %-11d %.2f%%\n\r", i, (float)(fconstResolution * i32Base), pi32Spectrum[i], (float)pi32Spectrum[i] / (float)i32Accumulate * 100);
}
printf("+-----------------------------------------------------------+\n\r");
printf("FFT complete\n\r");
}
/*----------------------------------------------------------------------------------------------------------*/
/* Function: Display_AdcData */
/* Parameters: pi32Spectrum is the Time Domian Data buffer pointer. */
/* Parameters: u32Len is the length of the Data. */
/* Returns: none. */
/* Description: Show the detail informations from the spectrum bffer. */
/*----------------------------------------------------------------------------------------------------------*/
void Display_AdcData(int *pi32Data, uint32_t u32Len)
{
int i;
printf("+-----------------------------------------------------------+\n\r");
printf("| Display Time-Domain Data +\n\r");
printf("+-----------------------------------------------------------+\n\r");
printf("#Index before reversal #Index after reversal #ADC Data \n\r");
for (i = 0; i < u32Len; i++)
{
printf(" %-22d %-21d %d\n\r", i, REV(i), pi32Data[REV(i)]);
}
}
/*----------------------------------------------------------------------------------------------------------*/
/* Function: ADC_IRQHandler */
/* Parameters: None. */
/* Returns: None. */
/* Description: Interrupt service routine for EADC interrupt. */
/*----------------------------------------------------------------------------------------------------------*/
void ADC_IRQHandler(void)
{
/* clear the A/D conversion flag */
ADC_CLR_INT_FLAG(ADC, ADC_ADF_INT);
/* Toggle PB.0 to indicate an ADC data conversion is complete */
PB0 ^= 1;
/* Place the time-domain signal into the real part buffer */
g_i32RealBuffer [REV(g_u32AdcConvNum)] = SHRINK(ADC_GET_CONVERSION_DATA(ADC, g_u8AdcChannel));
/* The image part buffer is initialized to zeros */
g_i32ImageBuffer[REV(g_u32AdcConvNum)] = 0;
/* Increase the buffer index by 1 */
g_u32AdcConvNum++;
}
/*----------------------------------------------------------------------------------------------------------*/
/* Function: main */
/* Parameters: None. */
/* Returns: None. */
/* Description: main routine of the example. */
/*----------------------------------------------------------------------------------------------------------*/
int32_t main(void)
{
int i;
/* Unlock protected registers */
SYS_UnlockReg();
/* Init System, peripheral clock and multi-function I/O */
SYS_Init();
/* Lock protected registers */
SYS_LockReg();
/* Init UART0 for printf */
UART0_Init();
/* Init TIMER0 to trigger ADC sampling later. */
TIMER0_Init();
/* Init ADC channel 2. */
ADC_Init();
printf("\n");
printf("+-----------------------------------------------------------+\n\r");
printf("| Fast Fourier Transform Example Code |\n\r");
printf("+-----------------------------------------------------------+\n\n\r");
printf("# Demonstrate the decimate-in-time(DIT) radix-2 FFT \n\r");
printf("# This example code supports 3 kinds of data length: \n\r");
printf(" * 128 samples \n\r");
printf(" * 256 samples \n\r");
printf(" * 512 samples \n\r");
printf("# Please confirm the selected length of fft sequence is [%d].\n\r", SEQUENCE_LEN);
printf(" Otherwise, change the fft sequence length settings and \n\r");
printf(" re-compile again to acquire the correct look-up table. \n\r");
printf("# Connect PD.2 to the signal source. \n\r");
printf("# Press any key to start ADC capturing....\n\n\r");
getchar();
/* Reset the ADC numbers of conversion and Start A/D conversion */
g_u32AdcConvNum = 0;
PB0 = 0;
GPIO_SetMode(PB, BIT0, GPIO_MODE_OUTPUT);
/* Start capturing data. */
TIMER_Start(TIMER0);
while (g_u32AdcConvNum != SEQUENCE_LEN)
{
/* Wait for the number of ADC data samples reach SEQUENCE_LEN samples. */
__NOP();
}
/* Stop TIMER trigger EADC */
TIMER_Stop(TIMER0);
/* Disable EDC module */
ADC_Close(ADC);
/* Disable ADC IP clock */
CLK_DisableModuleClock(ADC_MODULE);
/* Disable TIMER0 IP clock */
CLK_DisableModuleClock(TMR0_MODULE);
/* Disable External Interrupt */
NVIC_DisableIRQ(ADC_IRQn);
Display_AdcData(g_i32RealBuffer, SEQUENCE_LEN);
/* Remove the DC component from the spectrum by zero-mean the time-domain signal before
performing FFT. Otherwise, it will remain a high DC component at the 0 Hz point. */
Remove_DC_Component(g_i32RealBuffer, SEQUENCE_LEN);
/* Transform the time domain signal into frequency domain */
NuFFT(g_i32RealBuffer, g_i32ImageBuffer, SEQUENCE_LEN);
for (i = 0; i < SEQUENCE_LEN ; i++)
{
/* The amplitude of the spectrum could be presented as the sum of square of the real part and the complex part. */
g_i32Spectrum[i] = (((g_i32RealBuffer[i] * g_i32RealBuffer[i]) + (g_i32ImageBuffer[i] * g_i32ImageBuffer[i])));
}
Display_Spectrum(g_i32Spectrum, SEQUENCE_LEN, SAMPLE_RATE);
while (1);
}