--- /dev/null
+/*---------------------------------------------------------------------------*\
+
+ 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
--- /dev/null
+/*---------------------------------------------------------------------------*\
+
+ 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);
+}
+