2-канальный DDS генератор на STM32F100

С управлением по RS232.

Контроллер тактируется от кварца 24М, работает на 32М.

ЦАПы обновляются на частоте 1МГц, по каналам DMA1.2/1.3 с тактированием от TIM3 по событиям TIM_TRGOSource_Update и TIM_TRGOSource_OC3Ref. Для загрузки формы сигнала доступно по 512 отсчетов на канал.

Код настройки (подробно расписано тут: chipspace.ru/stm32-dac-3/ ):

    //GPIO
    GPIO_InitTypeDef gpio;
    GPIO_StructInit(&gpio);
    gpio.GPIO_Pin   = GPIO_Pin_4 | GPIO_Pin_5;
    gpio.GPIO_Mode  = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &gpio);

    //ЦАП
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
    DAC_InitTypeDef dac;
    DAC_StructInit(&dac);
    dac.DAC_Trigger        = DAC_Trigger_None;
    dac.DAC_OutputBuffer   = DAC_OutputBuffer_Enable;
    dac.DAC_WaveGeneration = DAC_WaveGeneration_None;
    DAC_Init(DAC_Channel_1, &dac);
    DAC_Cmd(DAC_Channel_1, ENABLE);
    DAC_Init(DAC_Channel_2, &dac);
    DAC_Cmd(DAC_Channel_2, ENABLE);

    //Таймер 3 и ШИМ
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    TIM_TimeBaseInitTypeDef tim;
    TIM_TimeBaseStructInit(&tim);
    tim.TIM_Prescaler     = 0;
    tim.TIM_Period        = F_CPU / F_DDS - 1;
    tim.TIM_ClockDivision = 0;
    tim.TIM_CounterMode   = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &tim);
    TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
    TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE);

    TIM_OCInitTypeDef oc;
    oc.TIM_OCMode       = TIM_OCMode_PWM2;
    oc.TIM_OCIdleState  = TIM_OCIdleState_Reset;
    oc.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
    oc.TIM_OutputNState = TIM_OutputNState_Disable;
    oc.TIM_OCNPolarity  = TIM_OCPolarity_High;
    oc.TIM_OutputState  = TIM_OutputState_Disable;
    oc.TIM_OCPolarity   = TIM_OCPolarity_Low;
    oc.TIM_Pulse        = 0;
    TIM_OC3Init(TIM3, &oc);
    TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_OC3Ref);
    TIM_DMACmd(TIM3, TIM_DMA_CC3, ENABLE);

    //DMA и прерывания
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    DMA_InitTypeDef dma;
    dma.DMA_PeripheralBaseAddr = (u32)&DAC->DHR12R1;
    dma.DMA_MemoryBaseAddr     = (u32)&dds[0].samples_out[0];
    dma.DMA_DIR                = DMA_DIR_PeripheralDST;
    dma.DMA_BufferSize         = DDS_SAMPLES_OUT;
    dma.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
    dma.DMA_MemoryInc          = DMA_MemoryInc_Enable;
    dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    dma.DMA_MemoryDataSize     = DMA_MemoryDataSize_HalfWord;
    dma.DMA_Mode               = DMA_Mode_Circular;
    dma.DMA_Priority           = DMA_Priority_High;
    dma.DMA_M2M                = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel3, &dma);

    dma.DMA_PeripheralBaseAddr = (u32)&DAC->DHR12R2;
    dma.DMA_MemoryBaseAddr     = (u32)&dds[1].samples_out[0];
    DMA_Init(DMA1_Channel2, &dma);

    DMA_ITConfig(DMA1_Channel3, DMA_IT_TC, ENABLE);
    DMA_ITConfig(DMA1_Channel3, DMA_IT_HT, ENABLE);
    NVIC_EnableIRQ(DMA1_Channel3_IRQn);

    DMA_Cmd(DMA1_Channel3, ENABLE);
    DMA_Cmd(DMA1_Channel2, ENABLE);

    TIM_Cmd(TIM3, DISABLE);


Карта тактирования DMA:


В прерывании половины и окончания передачи DMA выполняется обновление следующей половины буфера семплов DDS:
void handler_DMA1_Channel3_IRQn() {
	if (DMA_GetITStatus(DMA1_IT_TC3)) {
		DMA_ClearITPendingBit(DMA1_IT_TC3);
		dds_update(DDS_SAMPLES_OUT/2);
	} else if (DMA_GetITStatus(DMA1_IT_HT3)) {
		DMA_ClearITPendingBit(DMA1_IT_HT3);
		dds_update(0);
	}
    }
void dds_update(u32 start) {
	for (u32 s=DDS_SAMPLES_OUT/2; s; s--,start++) {
		for (u32 c=0; c<DDS_CHNS; c++) {
			dds[c].samples_out[start] = dds[c].samples_in[(dds[c].phase + dds[c].phase_shift) >> (32 - DDS_PHASE_BITS)];
			dds[c].phase += dds[c].phase_inc;
		}
	}
}


Настройка — через RS232 (38400, 8n1). Протокол — текстовый, key=value\n.

Доступные параметры:
  • en — вкл/выкл генерации (1/0);
  • chns — количество каналов (2);
  • samples — количество отсчетов в буфере сигнала (512);
  • freq — частота обновления ЦАПов (1000000Гц);
  • ampl — макс.амплитуда сигнала (4095);
  • frq0/1 — частота генерации канала 0/1, мГц (0..100000000);
  • ph0/1 — фаза сигнала, мГр (0..359999);
  • buf0/1 — вкл/выкл буфера ЦАПа (1/0);
  • cnt0/1 — установка счетчика очередного семпла (0..511);
  • sam0/1 — запись нового семпла с инкрементом счетчика.

Софт работает в браузере, с любым количеством каналов. Возможно вычисление формы по формуле, или загрузка в виде массива:


Для запуска нужна Java7.

Внизу прикреплен софт и прошивка с исходниками.
  • +7
  • 03 ноября 2013, 02:08
  • reptile
  • 2

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

RSS свернуть / развернуть
frq0/1 — частота генерации канала 0/1, мГц (0..100000000);
в количестве нулей нет ошибки? даже если в герцах при тактовой 32МГц цифры не реальные.
0
24М
0
миллиГерцы
0
добавлю свою лепту.
в своё время работал с Timer+DMA+DAC и опытным путём установил, что синхронно всё начинает работать, когда
timer_freq = apb1_freq / 8
т.е., когда timer_period = 15.
(т.к. AHB_freq=72MHz, APB1_prescaler=2, на таймер подяётся 72МГц)
если timer_period ставить меньше, то получаются какие-то левые частоты, типа 50МГц, в то время, когда APB1 36МГц
0
вроде нет такого. Уточните что значит «синхронно»?
есть небольшое ограничение минимума/максимума с включенным буфером, решается уменьшением амплитуды.
0
синхронно- это когда на таймере, от которого идут реквесты на DMA, частота, скажем, 20МГц, длина таблицы 1000, и я вижу на осциллографе синус с частотой 20КГц.

Если частота таймера больше, чем APB1_freq/16, то я например вижу не (не буду вспоминать реальные числа) 20КГц, а 50КГц. Т.е. частоты идут от балды и так пока не уменьшишь частоту на таймере до APB1_freq/16: после этого они совпадают с расчётными.
Такого поведения в даташите я не нашёл, как не нашёл и ограничения на частоту запросов DMA при работе с ЦАП.
Думаю, цифра 16 связана с внутренней работой шин.
0
Для улучшения ситуации можно dma генерировать dac, а tim использовать как trigger для dac.
0
так у меня и сделано. таймер триггерит DAC, DAC пинает DMA, DMA копирует из таблицы в DAC:

void dac_init_dma() {
        DAC_InitTypeDef dac;
        GPIO_InitTypeDef port;
        TIM_TimeBaseInitTypeDef    timer;
        RCC_HCLKConfig(RCC_SYSCLK_Div1);
        RCC_PCLK1Config(RCC_HCLK_Div2);
        RCC_PCLK2Config(RCC_HCLK_Div1);
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
        GPIO_StructInit(&port);
        port.GPIO_Mode = GPIO_Mode_AIN;
        port.GPIO_Pin = GPIO_Pin_4;
        port.GPIO_Speed = GPIO_Speed_2MHz;
        GPIO_Init(GPIOA, &port);
        TIM_TimeBaseStructInit(&timer);
        timer.TIM_Period = 72000000 / SIN_FREQ / SIN_SAMPLES - 1;
        timer.TIM_Prescaler = 0;
        timer.TIM_ClockDivision = TIM_CKD_DIV1;
        timer.TIM_CounterMode = TIM_CounterMode_Up;
        timer.TIM_RepetitionCounter = 0;
        TIM_TimeBaseInit(TIM6, &timer);
        TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);
        TIM_DMACmd(TIM6, TIM_DMA_Update, ENABLE);
        dac1_dma.DMA_PeripheralBaseAddr = (uint32_t)&DAC->DHR12R1;
        dac1_dma.DMA_MemoryBaseAddr = (uint32_t)&sin_table;
        dac1_dma.DMA_DIR = DMA_DIR_PeripheralDST;
        dac1_dma.DMA_BufferSize = SIN_SAMPLES;
        dac1_dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
        dac1_dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
        dac1_dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
        dac1_dma.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
        dac1_dma.DMA_Mode = DMA_Mode_Circular;
        dac1_dma.DMA_Priority = DMA_Priority_High;
        dac1_dma.DMA_M2M = DMA_M2M_Disable;
        DMA_Init(DMA2_Channel3, &dac1_dma);
        DMA_Cmd(DMA2_Channel3, ENABLE);
        dac.DAC_Trigger = DAC_Trigger_T6_TRGO;
        dac.DAC_WaveGeneration = DAC_WaveGeneration_None;
        dac.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
        DAC_Init(DAC_Channel_1, &dac);
        DAC_Cmd(DAC_Channel_1, ENABLE);
        int i;
        for(i = 0; i < SIN_SAMPLES; i++) {
                sin_table[i] = 2048 + (int)(2047.0 * sin(3.14159265358 * 2.0 / (float)SIN_SAMPLES * (float) i) );
        }
        TIM_Cmd(TIM6, ENABLE);
        port.GPIO_Pin = GPIO_Pin_8;
        port.GPIO_Speed = GPIO_Speed_50MHz;
        port.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_Init(GPIOA, &port);
        RCC_MCOConfig(RCC_MCO_SYSCLK);
}
0
возможно у Вас превышена максимальная частота преобразования DAC
Max frequency for a correct DAC_OUT change when
small variation in the input code (from code i to i+1LSB) 1 MS/s
0
и насколько меньше джиттер по сравнению с тактированием DMA?
0
= джиттеру клока. Потому что данные успеют записаться в dac заранее.
0
ну это ерунда. Синус 100кГц джиттер в несколько тактов даже не заметит.
0
24мегаерца CPU — 1мегагерц DAC. Сдвиг на 1 такт — это 1/24 от правильного положения. При неудачном стечении обстоятельств на 1-2 процента увеличатся искажения. Терпеть это или нет — Ваш выбор. Также применять внутренний буфер или нет — тоже Ваш выбор.
0
как/чем замеряли несинхронность?
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.