STM32 tuner unit test looking good
authordrowe67 <drowe67@01035d8c-6547-0410-b346-abe4f91aad63>
Fri, 20 Feb 2015 03:09:32 +0000 (03:09 +0000)
committerdrowe67 <drowe67@01035d8c-6547-0410-b346-abe4f91aad63>
Fri, 20 Feb 2015 03:09:32 +0000 (03:09 +0000)
git-svn-id: https://svn.code.sf.net/p/freetel/code@2040 01035d8c-6547-0410-b346-abe4f91aad63

codec2-dev/stm32/inc/stm32f4_adc_tuner.h [new file with mode: 0644]
codec2-dev/stm32/src/iir_tuner.c [new file with mode: 0644]
codec2-dev/stm32/src/stm32f4_adc_tuner.c [new file with mode: 0644]

diff --git a/codec2-dev/stm32/inc/stm32f4_adc_tuner.h b/codec2-dev/stm32/inc/stm32f4_adc_tuner.h
new file mode 100644 (file)
index 0000000..d2ed444
--- /dev/null
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------------------------*\
+
+  FILE........: stm32f4_adc.h
+  AUTHOR......: David Rowe
+  DATE CREATED: 19 Feb 2015
+
+  Single channel ADC driver module for STM32F4 that samples pin PA1 at
+  2 MHz and down converts to 50 kHz, with "tuning" centred at 500 kHz.
+
+\*---------------------------------------------------------------------------*/
+
+/*
+  Copyright (C) 2015 David Rowe
+
+  All rights reserved.
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License version 2.1, as
+  published by the Free Software Foundation.  This program is
+  distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __STM32F4_ADC_TUNER__
+#define __STM32F4_ADC_TUNER__
+
+#define ADC_TUNER_M  45   /* decimation rate */
+#define ADC_TUNER_N  160
+#define ADC_TUNER_BUF_SZ  (ADC_TUNER_M*ADC_TUNER_N)
+
+void adc_open(int fifo_sz);
+int adc1_read(short buf[], int n); /* ADC1 Pin PA1 */
+
+#endif
diff --git a/codec2-dev/stm32/src/iir_tuner.c b/codec2-dev/stm32/src/iir_tuner.c
new file mode 100644 (file)
index 0000000..7e6fe10
--- /dev/null
@@ -0,0 +1,143 @@
+/*---------------------------------------------------------------------------*\
+
+  FILE........: iir_tuner.c
+  AUTHOR......: David Rowe
+  DATE CREATED: 20 Feb 2015
+
+  Filter/decimator function, broken out to this filer so we can unit
+  test easily.  
+
+  Unit testing:
+  
+    ~/codec2-dev/stm32$ gcc -D__UNITTEST__ -Iinc src/iir_tuner.c -o iir_tuner -lm -Wal
+    ~/codec2-dev/stm32$ ./iir_tuner
+
+\*---------------------------------------------------------------------------*/
+
+/*
+  Copyright (C) 2015 David Rowe
+
+  All rights reserved.
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License version 2.1, as
+  published by the Free Software Foundation.  This program is
+  distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef __UNITTEST__
+
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+
+#endif
+
+#include "stm32f4_adc_tuner.h"
+
+/* Filter coefficients of IIR tuner (BETA1) and FIR equaliser (BETA2).
+   Note neat trick to relate BETA2 to BETA1 by the decimation rate */
+
+#define BETA1                    0.999
+#define BETA2                    (1.0 - (1.0-BETA1)*ADC_TUNER_M)
+
+/* filter states - we keep them global due to the need for speed */
+
+float y_2, y_1, z_2, z_1;
+
+/*
+   ADC -> signed conversion - IIR BPF - Decimate - FIR Equaliser -> FIFO
+*/
+
+void inline iir_tuner(float dec_buf[], unsigned short adc_buf[]) {
+    int i, j, k;
+    float x, y, z;
+
+    for(i=0, j=0; i<ADC_TUNER_BUF_SZ/2; j++) {
+
+        /* IIR BPF centred at Fs/4.  All your MIPs are belong to this
+           loop. */
+
+        for(k=0; k<ADC_TUNER_M; k++,i++) {
+            x = (int)adc_buf[i] - 32768;
+            y = x - BETA1*y_2;
+            y_2 = y_1;
+            y_1 = y;
+        }
+
+        /* Equaliser FIR filter, notch at Fs/(4*ADC_TUNER_M) to smooth out 
+           IIR BF passband response */
+
+        z = y + BETA2*z_2;
+        dec_buf[j] = z;
+        z_2 = z_1;
+        z_1 = y;
+    }
+}
+
+
+#ifdef __UNITTEST__
+
+#define FS      2000000
+#define AMP_MAX 32767
+
+#define NOUT_BUFS    100
+#define NOUT         (NOUT_BUFS*ADC_TUNER_N)
+#define NIN          (NOUT*ADC_TUNER_M)
+
+void synth_line(unsigned short us[], float f, float amp, int n) {
+    float w, sam;
+    int   i;
+
+    w = 2*M_PI*f/(float)FS;
+
+    for(i=0; i<n; i++) {
+        sam = amp*AMP_MAX*cos(w*i);
+        us[i] += (unsigned short)(sam + 0.5);
+    }
+}
+
+
+int main(void) {
+    float          f1,f2,f3,f4;
+    unsigned short s[NIN];
+    float          dec_s[NOUT];
+    FILE          *f;
+    int            i,j;
+
+    f1 = 500E3;
+    f2 = f1 + 8E3;       /* wanted */
+    f3 = f1 - 7E3;       /* wanted */
+    f4 = f1 - 207E3;     /* out of band, should be greatly attenuated */
+
+    for(i=0; i<NIN; i++)
+        s[i] = 32767;
+    synth_line(s, f2, 0.1, NIN);
+    synth_line(s, f3, 0.05, NIN);
+    synth_line(s, f4, 0.1, NIN);
+    for(i=0, j=0; i<NIN; i+=ADC_TUNER_BUF_SZ/2, j+=ADC_TUNER_N/2) {
+        iir_tuner(&dec_s[j], &s[i]);
+    }
+    
+    f = fopen("iir_tuner_s.txt", "wt");  assert(f != NULL);
+    for(i=0; i<NIN; i++)
+        fprintf(f, "%d\n", s[i]);
+    fprintf(f, "\n");
+    fclose(f);
+
+    f = fopen("iir_tuner.txt", "wt");  assert(f != NULL);
+    for(i=0; i<NOUT; i++)
+        fprintf(f, "%f\n", dec_s[i]);
+    fprintf(f, "\n");
+    fclose(f);
+
+    return 0;
+}
+
+#endif
diff --git a/codec2-dev/stm32/src/stm32f4_adc_tuner.c b/codec2-dev/stm32/src/stm32f4_adc_tuner.c
new file mode 100644 (file)
index 0000000..ef7a46c
--- /dev/null
@@ -0,0 +1,249 @@
+/*---------------------------------------------------------------------------*\
+
+  FILE........: stm32f4_adc_tuner.c
+  AUTHOR......: David Rowe
+  DATE CREATED: 19 Feb 2015
+
+  Single channel ADC driver module for STM32F4 that samples pin PA1 at
+  2 MHz and down converts to 50 kHz, with "tuning" centred at 500 kHz.
+
+  See codec2-dev/octave.m for a simulation model.
+
+\*---------------------------------------------------------------------------*/
+
+/*
+  Copyright (C) 2015 David Rowe
+
+  All rights reserved.
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License version 2.1, as
+  published by the Free Software Foundation.  This program is
+  distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "stm32f4xx_adc.h"
+#include "stm32f4xx_gpio.h"
+#include "stm32f4xx_rcc.h"
+#include "codec2_fifo.h"
+#include "stm32f4_adc_tuner.h"
+#include "debugblinky.h"
+#include "iir_tuner.c"
+
+struct FIFO *adc1_fifo;
+unsigned short adc_buf[ADC_TUNER_BUF_SZ];
+float y_2, y_1;
+int adc_overflow1;
+int half,full;
+
+#define ADCx_DR_ADDRESS          ((uint32_t)0x4001204C)
+#define DMA_CHANNELx             DMA_Channel_0
+#define DMA_STREAMx              DMA2_Stream0
+#define ADCx                     ADC1
+
+#define BETA1                    0.999
+#define BETA2                    0.955
+
+void adc_configure();
+
+static void tim2_config(void);
+
+
+void adc_open(int fifo_sz) {
+    adc1_fifo = fifo_create(fifo_sz);
+    assert(adc1_fifo != NULL);
+
+    tim2_config();
+    adc_configure();
+    init_debug_blinky();
+}
+
+
+/* n signed 16 bit samples in buf[] if return != -1 */
+
+int adc1_read(short buf[], int n) {   
+    return fifo_read(adc1_fifo, buf, n);
+}
+
+
+static void tim2_config(void)
+{
+  TIM_TimeBaseInitTypeDef    TIM_TimeBaseStructure;
+
+  /* TIM2 Periph clock enable */
+  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
+  
+  /* --------------------------------------------------------
+  
+  TIM2 input clock (TIM2CLK) is set to 2 * APB1 clock (PCLK1), since
+  APB1 prescaler is different from 1 (see system_stm32f4xx.c and Fig
+  13 clock tree figure in DM0031020.pdf).
+
+     Sample rate Fs = 2*PCLK1/TIM_ClockDivision 
+                    = (HCLK/2)/TIM_ClockDivision
+                    
+  ----------------------------------------------------------- */
+
+  /* Time base configuration */
+
+  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); 
+  TIM_TimeBaseStructure.TIM_Period = 42;          
+  TIM_TimeBaseStructure.TIM_Prescaler = 0;       
+  TIM_TimeBaseStructure.TIM_ClockDivision = 0;    
+  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
+  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
+
+  /* TIM2 TRGO selection */
+
+  TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
+  
+  /* TIM2 enable counter */
+
+  TIM_Cmd(TIM2, ENABLE);
+}
+
+
+void adc_configure(){
+    ADC_InitTypeDef  ADC_init_structure; 
+    GPIO_InitTypeDef GPIO_initStructre; 
+    DMA_InitTypeDef  DMA_InitStructure;
+    NVIC_InitTypeDef NVIC_InitStructure;
+
+    // Clock configuration
+
+    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
+    RCC_AHB1PeriphClockCmd(RCC_AHB1ENR_GPIOAEN,ENABLE);
+    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
+
+    // Analog pin configuration ADC1->PA1
+
+    GPIO_initStructre.GPIO_Pin =  GPIO_Pin_1;    
+    GPIO_initStructre.GPIO_Mode = GPIO_Mode_AN;     
+    GPIO_initStructre.GPIO_PuPd = GPIO_PuPd_NOPULL; 
+    GPIO_Init(GPIOA,&GPIO_initStructre);            
+
+    // ADC structure configuration
+
+    ADC_DeInit();
+    ADC_init_structure.ADC_DataAlign = ADC_DataAlign_Left;
+    ADC_init_structure.ADC_Resolution = ADC_Resolution_12b;
+    ADC_init_structure.ADC_ContinuousConvMode = DISABLE; 
+    ADC_init_structure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
+    ADC_init_structure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
+    ADC_init_structure.ADC_NbrOfConversion = 1;
+    ADC_Init(ADCx,&ADC_init_structure);
+
+    // Select the channel to be read from
+
+    ADC_RegularChannelConfig(ADCx,ADC_Channel_1,1,ADC_SampleTime_3Cycles);
+
+    /* DMA  configuration **************************************/
+
+    DMA_DeInit(DMA_STREAMx);
+    DMA_InitStructure.DMA_Channel = DMA_CHANNELx;  
+    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADCx_DR_ADDRESS;
+    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)adc_buf;
+    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
+    DMA_InitStructure.DMA_BufferSize = ADC_TUNER_BUF_SZ;
+    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
+    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
+    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
+    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
+    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
+    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
+    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
+    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
+    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
+    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
+    DMA_Init(DMA_STREAMx, &DMA_InitStructure);
+
+    /* Enable DMA request after last transfer (Single-ADC mode) */
+
+    ADC_DMARequestAfterLastTransferCmd(ADCx, ENABLE);
+
+    /* Enable ADC1 DMA */
+
+    ADC_DMACmd(ADCx, ENABLE);
+
+    /* DMA2_Stream0 enable */
+
+    DMA_Cmd(DMA_STREAMx, ENABLE);
+
+    /* Enable DMA Half & Complete interrupts */
+
+    DMA_ITConfig(DMA2_Stream0, DMA_IT_TC | DMA_IT_HT, ENABLE);
+
+    /* Enable the DMA Stream IRQ Channel */
+
+    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
+    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
+    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
+    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
+    NVIC_Init(&NVIC_InitStructure);     
+
+    // Enable and start ADC conversion
+
+    ADC_Cmd(ADC1,ENABLE);
+    ADC_SoftwareStartConv(ADC1);
+}
+
+
+/*
+  This function handles DMA Stream interrupt request.
+*/
+
+void DMA2_Stream0_IRQHandler(void) {
+    short dec_buf[ADC_TUNER_N/2];
+
+    GPIOE->ODR = (1 << 0);
+
+    /* Half transfer interrupt */
+
+    if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_HTIF0) != RESET) {
+        half++;
+
+        iir_tuner(dec_buf, adc_buf);
+
+        /* write first half to fifo */
+
+        if (fifo_write(adc1_fifo, dec_buf, ADC_TUNER_N/2) == -1) {
+            adc_overflow1++;
+        }
+
+        /* Clear DMA Stream Transfer Complete interrupt pending bit */
+
+        DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_HTIF0);  
+    }
+
+    /* Transfer complete interrupt */
+
+    if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0) != RESET) {
+        full++;
+
+        iir_tuner(dec_buf, &adc_buf[ADC_TUNER_BUF_SZ/2]);
+
+        /* write second half to fifo */
+
+        if (fifo_write(adc1_fifo, dec_buf, ADC_TUNER_N/2) == -1) {
+            adc_overflow1++;
+        }
+
+        /* Clear DMA Stream Transfer Complete interrupt pending bit */
+
+        DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);  
+    }
+
+    GPIOE->ODR &= ~(1 << 0);
+}
+