Шумодав для рацайки. Часть вторая. The hardware.

Архитектурные изыски.


В первой части мы определились с выбором аппаратной платформы. От этого и будем плясать. Посмотрим, что мы имеем:
  • CPU stm32f407 32 bit Cortex M4 with single-precision FPU on board.
  • 1M flash rom.
  • 128K + 64K on-chip RAM.
  • up to 168MHz CPU clock.
  • 3 12-bits ADC
  • 2 12-bits DAC
  • Timers and others.

Из этого нам понадобится ADC, DAC, Timers и, наверное, что-нибудь еще.
192К памяти это очень хорошо — её можно не экономить.
Наличие FPU на борту это тоже хорошо, ибо можно будет не париться с арифметикой с фиксированной точкой и все писать на «флотах».


Как я уже говорил, для экспериментов будем использовать отладочную плату stm32f4discovery. На ней много чего есть, но нам из этого почти ничего не нужно. Однако, на этой плате есть еще и ST-Link. Это очень удобно.

Дополнительная обвязка платы.

У нас есть лиц АЦП, которые регистрируют напряжения от 0В до 3В (Мы будем использовать имеющийся Vref+). Сигнал же с выхода детектора имеет размах от -300мВ до +300мВ. Значит, нам надо подтянуть выход детектора к 1.5В (половина размаха входа АЦП). Не проблема. Я это сделал на 2х резисторах по 130кОм. Выход детектора подал через кондёр 10мкФ на вход АЦП. Однако 300мВ это мало. Поэтому, все-таки будем использовать выход динамика рации — там можно получить амплитуду порядка 1.5В, что нам и надо. Однако первое измерение показало, что если амплитуда на выходе динамика больше 1В, то появляются большие искажения и смещается ноль. Ничего не поделаешь — оставляем (регулируем выход) амплитуду порядка 1В.



Здесь видно, что даже на шуме очень много всяких разных помех. Это не удивительно — у меня в одном месте сосредоточены и Алан, и комп, и осцил, и т.д. Но мы эти помехи не особо боимся.

Для приемника мы будем использовать АЦП3.

Нам еще потом понадобится второе АЦП (АЦП 2), чтобы цифровать микрофон, но это потом…

Эти оба АЦП будут использоваться в независимом режиме и каждое будет оцифровывать по одному каналу.

Еще одно АЦП (АЦП 1) нам понадобится для того, чтобы устанавливать пороги нашего шумодава и следить за RSSI — уровнем принимаемого сигнала. Это, конечно же, если мы найдем выход АРУ приемника. Нам нужно 4 канала для:
  1. Установки уровня ШП.
  2. Установки уровна ASQ/
  3. Установки профиля шума.
  4. RSSI.

Об этих параметрах я расскажу позже, а сейчас отмечу, что некоторые уровни можно задать как программно, так и напряжением на входе АЦП используя переменные резисторы. Желательно бы еще входы АЦП немножко сгладить ёмкостями, но на плату их сложно припаять. Поэтому обойдемся.

Выход DAC имеет размах от 0В до 3В. Один конденсатор между выходом DACa и входом УНЧ нам решит проблемы (если возникнут). Все!

Честно говоря, для того, чтобы смотреть что получается, я буду записывать вход и выход на компьютер. Поэтому я ко входу и выходу припаял стерео кабель. Теперь я могу записывать мои сигналы. Я буду использовать CoolEdit для этих целей.

Вот, собственно, весь мой set-up:



The software. Device drivers.

Нам прежде всего надо выбрать рабочую частоту процессора. Выбираем из таких соображений.
  • Шумодав будет обрабатывать данные кадрами. Каждый кадр должен (это я так решил) иметь длину 16мс или, при частоте дискретизации 8кГц на один кадр будет приходиться 128 отсчетов.
  • Поскольку АЦП не есть аудиокодек, то, если будем цифровать вход с частотой 8кГц, у нас будет большой алиасинг. Алиасинг это не смертельно, поскольку выход детектора имеет завал АЧХ на 3кГц, но все равно не хорошо. Тем более, если 3ая ПЧ 32кГц...
  • Тогда мы поднимем частоту оцифровки раз эдак в 16 — до 128кГц (это тоже так решил) и потом понизим частоту до 8кГц, предварительно отфильтровав вход каким-нибудь фильтром. Такая высокая частота дискретизации позволяет виртуально выиграть лишних 2 бита для АЦП — т.е. виртуально разрядность нашего входного АЦП будет аж 14 бит!
  • Для корректной работы каналы АЦП и ЦАПа должны быть синхронными! Иначе появятся на выходе щелчки. Поэтому АЦП и ЦАП должны запускаться одним триггером. Для этого подойдет Timer 2 с его выходом TRGO.
  • Т.е. Timer 2 должен делить так свою входную частоту, чтобы на выходе триггера «получалось» 128кГц.
  • Входная частота таймера 2 есть Fcpu/2.
  • Поскольку из 168кГц целым никаким делителем не получить 128кГц, то для простоты мы запустим наш CPU на частоте 128МГц — так проще считать.
  • DAC тоже будет работать на 128кГц. Нам так же понадобится интерполяционный фильтр.

Берем утилиту от ST и генерим файл system_stm32f4xx.c. Я не буду его разбирать отдельно — там и так все просто и понятно.

Итого наш проц работает на частоте 128МГц. Много это или мало — время покажет. В любом случае у нас есть некоторый запас по скорости и, подключив кварц, скажем 4096 кГц мы можем разогнаться до почти 164МГц.

Создаем проект. Для работы в основном я использую FreeBSD и Vi, но в данном случае буду работать с Keil, ибо в нем можно использовать SWO и печатать прямо в отладчик, а не через УАРТ, что удобно.

При создании проекта Keil создаст несколько необходимых файлов, а нам надо лишь написать свою конфигурацию оборудования, обработчики прерываний и, собственно, обработку. Я так же буду использовать стандартную библиотеку от ST — STM32F4xx_DSP_StdPeriph_Lib_V1.0.1, которая бесплатно раздается на их сайте. Кстати, там есть много примеров, основываясь на которых я конфигурировал свое оборудование.

Начинаем. Для начала нам понадобится написать несколько необходимых процедур для ввода-вывода через отладчик. Вот они:


// ============================================================================
#define    DWT_CYCCNT    *(volatile uint32_t *)0xE0001004
#define    DWT_CONTROL   *(volatile uint32_t *)0xE0001000
#define    SCB_DEMCR     *(volatile uint32_t *)0xE000EDFC


static void 
DWT_Init()
{
   SCB_DEMCR  |= 0x01000000;
   DWT_CYCCNT  = 0;
   DWT_CONTROL|= 1; // enable the counter
}

#define ITM_Port8(n)    (*((volatile unsigned char *)(0xE0000000+4*n)))
#define ITM_Port16(n)   (*((volatile unsigned short*)(0xE0000000+4*n)))
#define ITM_Port32(n)   (*((volatile unsigned long *)(0xE0000000+4*n)))

#define DEMCR           (*((volatile unsigned long *)(0xE000EDFC)))
#define TRCENA          0x01000000

struct __FILE { int handle; };
FILE __stdout;
FILE __stdin;

int 
fputc(int ch, FILE *f) 
{
  if (DEMCR & TRCENA) 
	{
    while (ITM_Port32(0) == 0);
    ITM_Port8(0) = ch;
  }
  return(ch);
}
// ============================================================================



Функция DWT_Init() запускает счетчик циклов. Это нам, наверное понадобится, чтобы считать производительность наших функций.

Функция fputc() выводит символ в окно отладчика. Это для printf().

Далее мы конфигурим SysTick. Не самый необходимый для нас таймер, но вдруг…

  if (SysTick_Config(SystemCoreClock / 1000))	// 1ms interrupt
  { 
    /* Capture error */ 
    while (1);
  }

и его обработчики прерываний.

/**
  * @brief  Inserts a delay time.
  * @param  nTime: specifies the delay time length, in milliseconds.
  * @retval None
  */
static __IO uint32_t TimingDelay;
void Delay(__IO uint32_t nTime)
{ 
  TimingDelay = nTime;
  while(TimingDelay != 0);
}

/**
  * @brief  Decrements the TimingDelay variable.
  * @param  None
  * @retval None
  */
void TimingDelay_Decrement(void)
{
  if (TimingDelay != 0x00)
  { 
    TimingDelay--;
  }
}

/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */
void SysTick_Handler(void)
{
	TimingDelay_Decrement();
}


Нам так же понадобятся несколько ножек для управления SQUELSH ну и для отладки. Для этих целей мы будем использовать порт D и его пины 12 и 13. Тут все просто:



void deb_port_init(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;
  
  /* Enable the GPIO_LED Clock */
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);

  /* Configure the GPIO_LED pin */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOD, &GPIO_InitStructure);
}

void
deb_port_on(void)
{
	GPIOD->BSRRL = GPIO_Pin_12;
}

void
deb_port_off(void)
{
	GPIOD->BSRRH = GPIO_Pin_12;
}

void
sql_gate_control(int x)
{
	if (x)
		GPIOD->BSRRL = GPIO_Pin_13;
	else
		GPIOD->BSRRH = GPIO_Pin_13;
}


Далее мы конфигурим таймер 2, но пока что не стартуем его.



void timer2_config(void)
{
  TIM_TimeBaseInitTypeDef    TIM_TimeBaseStructure;
	
  /* TIM2 Periph clock enable */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

  /* Time base configuration */
  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
  TIM_TimeBaseStructure.TIM_Period = 500-1;//875 - 1;
  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);
}

void
timer2_start(void)
{
	TIM_Cmd(TIM2, ENABLE);
}


Таймер 2 «сидит» на APB1 и тактируется Fclk/2 == 64МГц. Поэтому, для того чтобы заставить счетчик таймера переполняться с частотой 128кГц, запишем в него значение 499. Как я уже говорил, задача таймера только запускать АЦП и ЦАП. Поэтому мы его выходом делаем TRGO: TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);

Никаких прерываний от этого таймера нам не нужно!

Дальше начинается самое интересное — сконфигурить АЦП и ЦАП таким образом, чтобы данные не пропадали и работе не мешали.
Поскольку у нас кадр 16мс, то на 128кГц это 2048 отсчетов сигнала. Значит для АЦП нам понадобится буфер размером 2048 16и битных слов (полуслов??? ), т.е. 4096 байт. Не проблема — память пока что есть.

Однако достаточно ли это? Нет! если мы будем для нашей обработки использовать только один входной буфер, то мы или потеряем данные или испортим данные в нём. Поэтому стандартным способом является использование двойной буферизации — АЦП читает в один буфер, а данные из второго используются для обработки. Потом эти буферы меняются местами. То же самое касается ЦАПа. Итого мы должны распределить 8192 байта под данные АЦП и столько же для ЦАПа.

И ЦАП и АЦП зацепим за DMA. DMA нашего проца умеет генерировать массу прерываний, их которых нам нужны 2 — конец записи и половина записи завершена. По этим прерываниям мы будем отслеживать в какой буфер что пишется.
Замечу, что поскольку DMA у stm32f4 умеет сам делать двойную буферизацию, то можно обойтись не 4я буферами по 4К, а только тремя. Но тогда понадобится по окончании записи в буфер переназначать их адреса вручную. Пока что этого делать не будем, ибо памяти хватает.

Собственно код:

#define BUFFER_SIZE         (128*16)
#define ADC_DATA_SIZE	    (BUFFER_SIZE)// 128 samples per ms * 16ms * twice

static int16_t adc3_data[ADC_DATA_SIZE];

int16_t *
get_adc3_buffer(void)
{
	return adc3_data;
}

#define ADC3_DR_ADDRESS     ((uint32_t)0x4001224C)

void ADC3_CH12_DMA_Config(void)
{
  ADC_InitTypeDef       ADC_InitStructure;
  ADC_CommonInitTypeDef ADC_CommonInitStructure;
  DMA_InitTypeDef       DMA_InitStructure;
  GPIO_InitTypeDef      GPIO_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

  /* Enable ADC3, DMA2 and GPIO clocks ****************************************/
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 | RCC_AHB1Periph_GPIOC, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, 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); 
	
  /* DMA2 Stream0 channel0 configuration **************************************/
  DMA_InitStructure.DMA_Channel = DMA_Channel_2;  
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC3_DR_ADDRESS;
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&adc3_data;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
  DMA_InitStructure.DMA_BufferSize = ADC_DATA_SIZE;
  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(DMA2_Stream0, &DMA_InitStructure);
  DMA_Cmd(DMA2_Stream0, ENABLE);
	DMA_ITConfig(DMA2_Stream0, DMA_IT_TC | DMA_IT_HT, ENABLE);


  /* Configure ADC3 Channel12 pin as analog input ******************************/
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  /* ADC Common Init **********************************************************/
  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
  ADC_CommonInit(&ADC_CommonInitStructure);

  /* ADC3 Init ****************************************************************/
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising; 
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  ADC_InitStructure.ADC_NbrOfConversion = 1;
  ADC_Init(ADC3, &ADC_InitStructure);

  /* ADC3 regular channel12 configuration *************************************/
  ADC_RegularChannelConfig(ADC3, ADC_Channel_12, 1, ADC_SampleTime_3Cycles);

 /* Enable DMA request after last transfer (Single-ADC mode) */
  ADC_DMARequestAfterLastTransferCmd(ADC3, ENABLE);

  /* Enable ADC3 DMA */
  ADC_DMACmd(ADC3, ENABLE);

  /* Enable ADC3 */
  ADC_Cmd(ADC3, ENABLE);
}

static char adc_buffer_index = 0;
static char adc_data_avl = 0;

int
adc_index(void)
{
	return adc_buffer_index;
}

int
adc_data_available(void)
{
	return adc_data_avl;
}

void
adc_mark_data_used(void)
{
	adc_data_avl = 0;
}

void
DMA2_Stream0_IRQHandler(void)
{
  /* Test on DMA Stream Transfer Complete interrupt */
  if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0))
  {
    /* Clear DMA Stream Transfer Complete interrupt pending bit */
    DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);  
    adc_buffer_index = 1;
    adc_data_avl = 1;
  }
  else if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_HTIF0))
  {
    /* Clear DMA Stream Transfer Complete interrupt pending bit */
    DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_HTIF0);  
    adc_buffer_index = 0;
		adc_data_avl = 1;
  }		
}


Данные АЦП у нас 12и битные — значения от 0 до 0xfff. Значит где-то нам нужно будет вычесть 0x1000/2 чтобы избавиться от постоянной составляющей.

Для ЦАПа код такой:


#define DAC_DHR12L2_ADDRESS    0x40007418

#define DAC_DATA_SIZE							BUFFER_SIZE	// 128 samples per ms * 16ms * twice

static int16_t dac_data[DAC_DATA_SIZE];

int16_t *
get_dac_buffer(void)
{
	return dac_data;
}


void
dac1_config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
	DMA_InitTypeDef DMA_InitStructure;
	DAC_InitTypeDef  DAC_InitStructure;
	/* Preconfiguration before using DAC----------------------------------------*/

  /* DMA1 clock and GPIOA clock enable (to be used with DAC) */
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1 | RCC_AHB1Periph_GPIOA, ENABLE);

  /* DAC Periph clock enable */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);

  /* DAC channel 1 & 2 (DAC_OUT1 = PA.4)(DAC_OUT2 = PA.5) configuration */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	DAC_DeInit(); 
  /* DAC channel2 Configuration */
  DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO;
  DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
  DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
  DAC_Init(DAC_Channel_2, &DAC_InitStructure);
	DAC_Init(DAC_Channel_1, &DAC_InitStructure);

  /* DMA1_Stream6 channel7 configuration **************************************/
	DMA_DeInit(DMA1_Stream6);
  DMA_InitStructure.DMA_Channel = DMA_Channel_7;  
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)DAC_DHR12L2_ADDRESS;
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&dac_data;
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
  DMA_InitStructure.DMA_BufferSize = DAC_DATA_SIZE;
  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(DMA1_Stream6, &DMA_InitStructure);
	
  DMA_Cmd(DMA1_Stream6, ENABLE);
	
  /* Enable DAC Channel2 */
  DAC_Cmd(DAC_Channel_2, ENABLE);

  /* Enable DMA for DAC Channel2 */
  DAC_DMACmd(DAC_Channel_2, ENABLE);
}


У нас ЦАП сконфигурен таким образом, что на вход принимает 12и битные данные сдвинутые влево на границу 16и бит. Т.е. если мы возьмем число от 0 до 65535 и запишем его в наш ЦАП, то на выходе «получим» его же с обрубленными младшими 4я битами. Запомним это.

Поскольку АЦП и ЦАП у нас синхронны, то ЦАПу мы не назначаем никаких прерываний. Все прерывания и флаги будет устанавливать АЦП.

АЦП2 конфигурируется так же как и АЦП1, но без части которая относится к прерываниям. Код приводить здесь не буду.

АЦП 1 у нас отвечает за параметры нашего ШП. Параметров всего четыре. Значит нам надо последовательно цифровать 4 канала. При этом скорость оцифровки не важна — раз в 16мс нас вполне устроит. Поскольку частота цифровки мала, мы не будет заморачиваться с прерываниями. Просто будем запускать АЦП 1 программно раз в 16мс. Код такой:


#define ADC1_DR_ADDRESS     ((uint32_t)0x4001204C)

struct supl_s {
    int16_t nrv, det, sql, rssi;
};

static struct supl_s adc1_data;

struct supl_s *
get_supinfo(void)
{
	return &adc1_data;
}

void ADC1_CH_0_to_3_Config(void)
{
  ADC_InitTypeDef ADC_InitStructure;
	ADC_CommonInitTypeDef ADC_CommonInitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	DMA_InitTypeDef DMA_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 | RCC_AHB1Periph_GPIOA, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	 /* ADC Common Init */
  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
  ADC_CommonInit(&ADC_CommonInitStructure);


  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  ADC_InitStructure.ADC_ScanConvMode = ENABLE;
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  ADC_InitStructure.ADC_NbrOfConversion = 4;
  ADC_Init(ADC1, &ADC_InitStructure);

  /* ADC1 regular channels 0, 1, 2 and 3 configuration */ 
  ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_480Cycles);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_480Cycles);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_480Cycles);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_480Cycles);
	
	ADC_Cmd(ADC1, ENABLE);
		
  DMA_InitStructure.DMA_Channel = DMA_Channel_0; 
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&adc1_data;
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_ADDRESS;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
  DMA_InitStructure.DMA_BufferSize = 4;
  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_Medium;
  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(DMA2_Stream4, &DMA_InitStructure);
  DMA_Cmd(DMA2_Stream4, ENABLE);
		
  ADC_DMACmd(ADC1, ENABLE);
  ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
}

void ADC1_Start_Conv(void)
{
	ADC_SoftwareStartConv(ADC1);
}


Собственно все. Осталось все это запустить :)


{
	short *adc_data, *add;
	short *dac_data, *daa;

	adc_data = (short *)get_adc3_buffer();
	dac_data = (short *)get_dac_buffer();

	deb_port_init();	
	
	timer2_config();		
	ADC1_CH_0_to_3_Config();
	ADC3_CH12_DMA_Config();
	ADC2_CH13_DMA_Config();
	dac1_config();
        //
        // now start data transfer!!!
        //
	timer2_start();

	while(1)
	{
	    int idx;
		
            while (!adc_data_available()) /* __WFI() -- wait for interrupt */;
			
	    idx = adc_index();
	    adc_mark_data_used();
	
	    add = adc_data + (idx ? (FRAME_SIZE) : 0);
	    daa = dac_data + (idx ? (FRAME_SIZE) : 0);
		
	    deb_port_on();	
	    frame_process(mem, add, daa);
  	    deb_port_off();
	
	    ADC1_Start_Conv();		
	}


Переменная «mem» это чуть больше 8К байт памяти, необходимой для работы шумодава, выделенной ранее.
Вот. Из кода выше понятно, что все самое интересное происходит в функции frame_process()
  • +4
  • 26 декабря 2012, 13:20
  • diwil

Комментарии (8)

RSS свернуть / развернуть
Неплохо. Попробуйте поиграться с сигналом в Mathlab'e. На выходе можно получить потрясающие результаты и математическую модель именно под ваши нужды. Ну и конечно, это варварство, так использовать stm32=)
0
Попробуйте поиграться с сигналом в Mathlab'e
не умею.

Ну и конечно, это варварство, так использовать stm32=)
то, что под рукой и доступно.
Еще у меня под рукой есть imx53, sh2a, kalimba, blackfinn, ceva-dsp, coolflux, tmsXX. Вот на них это было бы варварством :) ибо не всем доступно.
+1
Интересно.
А будет описание той самой frame_process?
0
  • avatar
  • ploop
  • 27 декабря 2012, 08:50
В следущей части, по видимому.
0
Кстати, неплохо было бы перенести ваши статьи в тематические блоги.
0
  • avatar
  • ploop
  • 27 декабря 2012, 08:56
Жду третьей части
0
Отличная статья!
0
По-прежнему очень интересно! А вот вопрос почти оффтопик:

Для работы в основном я использую FreeBSD и Vi
А как ты собрал тулчейн для кортексов под FreeBSD и, что ещё более важно (общие-то binutils и gcc из портов ставятся) как туда запинал библиотеки от ST? У меня второе не получилось системным образом :(
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.