24438

Вывод сигнала в DAC по DMA в 1986ВЕ1Т и 1986ВЕ92У

Дата последнего изменения: 18.02.2020 15:53:26

DMA удобно использовать для вывода сигнала в ЦАП. Для того чтобы управлять шкалой времени выводимого сигнала, значения выходного сигнала должны выводиться по каким-то временным отсчетам. Для этого обычно используется таймер. Без использования таймера, DMA вывело бы весь сигнал очень быстро и остановилось при окончании цикла. Ведь DMA работает на частоте ядра и скорость "копирования" DMA никак не регулируется, она должна быть максимальна. Поэтому таймер крайне необходим, и именно по его событиям DMA будет выводить следующее значение сигнала в выходной регистр ЦАП. Сам сигнал, как правило, задается неким массивом, в котором записаны значения одного периода сигнала. Таким образом, переинициализация цикла DMA будет приводить к выводу в ЦАП следующего периода сигнала.

Рассмотрим реализацию данного режима работы,проекты для двух МК приложены к данному материалу. Если с пониманием каких-то моментов в работе DMA возникнут сложности, то можно обратиться к статье - Начальные сведения о DMA.

Проект реализован для запуска на отладочных платах для 1986ВЕ1Т и 1986ВЕ92У. Для наблюдения сигнала необходим осциллограф, который должен быть подключен к выводу используемого ЦАП (на BNC разъеме платы). Помимо вывода сигнала в ЦАП, индикация работы выведена на светодиод. Если светодиод мигает с периодом порядка секунды, то пример работает штатно.

Инициализация и тактирование

В 1986ВЕ92У проект использует DAC2, а в 1986ВЕ1Т используется DAC1. В обоих случаях используется Timer1. В целом код одинаков для обоих МК, различия заданы через макроопределения.

  • Для 1986ВЕ1Т массив с отсчетами сигнала должен лежать в области доступной для DMA - Подробнее.

Настройка ЦАП, таймера и DMA

Для настройки ЦАП требуется настроить вывод GPIO в необходимую функцию и настроить сам ЦАП. Расписывать тут особо нечего. Выбор пина GPIO передается входными параметрами в функцию BRD_DAC_PortInit(), которая является обычной функцией настройки выводов GPIO. Для ЦАП требуется минимальная настройка.

void BRD_DAC_PortInit(uint32_t Port_ClockMask, MDR_PORT_TypeDef* PORTx, uint32_t Port_PinsSel)
{
    PORT_InitTypeDef GPIOInitStruct;

 // Тактирование
    RST_CLK_PCLKcmd (Port_ClockMask, ENABLE);

 // Конфигурация линий ввода-вывода \
    PORT_StructInit (&GPIOInitStruct);
    GPIOInitStruct.PORT_Pin = Port_PinsSel;
    GPIOInitStruct.PORT_MODE = PORT_MODE_ANALOG;

 // Инициализация порта
    PORT_Init (PORTx, &GPIOInitStruct);
}
void BRD_DACs_Init(void)
{
 // Тактирование
    RST_CLK_PCLKcmd (RST_CLK_PCLK_DAC, ENABLE);
 
 // Деинициализация ЦАП
    DAC_DeInit();
 
// Простейшая настройка ЦАП
    DAC_Init(DAC_SYNC_MODE_Independent, DAC1_AVCC, DAC2_AVCC);
}

Настройки DMA стандартные, как и в демо-проектах SPL. 

Для работы с DMA реализовано две функции BRD_DMA_Init(void) - для включения блока DMA и BRD_DMA_Init_Channel(DMA_CHANNEL, &DMA_ChanCtrl) для настройки необходимого канала.

Подобное "уплотнение кода" произведено и для блока таймера. В случае с DMA и таймером, необходимая конфигурация для настройки блоков передается во входных параметрах. Функции BRD_ инкапсулируют включение тактирования, DeInit(), настройку портов и прочие необходимые вызовы, на которых заострять внимание при реализации логики приложения смысла нет.

uint32_t DMA_ChannelCtrl;
...

 // Настройка DMA
BRD_DMA_Init();

DMA_DataCtrl_Pri.DMA_SourceBaseAddr = (uint32_t)&signal;
DMA_DataCtrl_Pri.DMA_DestBaseAddr = (uint32_t)&DAC_REG_DATA;
DMA_DataCtrl_Pri.DMA_CycleSize = DATA_COUNT;
BRD_DMA_Init_Channel(DMA_CHANNEL, &DMA_ChanCtrl);

// Считываем управляющее слово канала, которое получилось после настройки
// Будет использовано в прерывании для переинициализации цикла DMA
BRD_DMA_Read_ChannelCtrl(DMA_CHANNEL, &DMA_ChannelCtrl);

 // Настройка Таймера
BRD_Timer_InitStructDef(&TimerInitStruct, SIGNAL_FREQ_HZ * DATA_COUNT, 64000); // DAC Out 1KHz
BRD_Timer_Init(&brdTimer1, &TimerInitStruct);

 После настройки таймера и канала DMA остается лишь разрешить обращения к DMA со стороны таймера и включить таймер. После этого при каждом отсчете периода таймера (событие CNT == ARR) в ЦАП будет выводиться одно значение из массива signal. Это копирование данных из ячеек массива в регистр АЦП будет осуществлять канал DMA. При выводе всех значений, указанных в цикле DMA возникнет прерывание от DMA.

#ifdef USE_MDR1986VE9x
  #define TIMER_CALL_DMA_ENA TIMER_DMACmd (MDR_TIMER1, TIMER_STATUS_CNT_ARR, ENABLE)
#elif defined (USE_MDR1986VE1T)
  #define TIMER_CALL_DMA_ENA TIMER_DMACmd (MDR_TIMER1, TIMER_STATUS_CNT_ARR, TIMER_DMA_Channel0, ENABLE)
#endif

main()
{ ...
   // Разрешение запросов к DMA со стороны Таймера
   TIMER_CALL_DMA_ENA; // Запуск таймера
   BRD_Timer_Start(&brdTimer1);
}

Разрешение запросов к DMA в таймерах используемых микроконтроллеров реализовано по разному, поэтому необходимо использовать макроопределение для этого вызова - TIMER_CALL_DMA_ENA.

Рабочий цикл

После запуска таймера будет выведен в ЦАП один период сигнала и вызовется обработчик прерывания по окончанию цикла DMA.

void DMA_IRQHandler (void)
{
// Вариант 1:
  // Восстановление управляющего слова канала
  BRD_DMA_Write_ChannelCtrl(DMA_CHANNEL, DMA_ChannelCtrl);
 // Запуск канала на обработку
  DMA_Cmd(DMA_CHANNEL, ENABLE);

// Вариант 2:
  // Переинициализация цикла DMA через SPL.
  // Медленно, дает артефакт сигнала (см. осциллографом) при PLL_MUX < RST_CLK_CPU_PLLmul3
  // DMA_Init(DMA_CHANNEL, &DMA_ChanCtrl);

 // Флаг для главного цикла, для обновления светодиода.
    IrQ_On = 1;

   NVIC_ClearPendingIRQ(DMA_IRQn);
}

При возникновении прерывания DMA необходимо переинициализировать новый цикл DMA, чтобы вывод сигнала не прерывался. На момент прерывания, в управляющем слове канала DMA обнулен счетчик передач и выставлен режим Stop. Поэтому в коде используется сохраненное на этапе инициализации начальное значение этого слова для восстановления настроек канала DMA. После этого функция DMA_Cmd(DMA_CHANNEL, ENABLE) выставляет "1" в бит канала регистра DMA_ENABLE, что активирует канал для обработки. Стоит отметить, что этот бит сбрасывается аппаратно при окончании цикла DMA.

Здесь следует учесть, что таймер мы не останавливаем чтобы сохранить равномерность выводимого сигнала. Поэтому переинициализация DMA должна произойти раньше, чем потребуется вывести следующий отсчет в ЦАП. Т.е. надо успеть настроить DMA за время периода таймера минус время на обработку прерывания. В коде представлен второй вариант с полной переинициализацией DMA. Если при реализации этого варианта посмотреть на осциллограф, то на выводимом сигнале будут присутствовать различные артефакты. Поэтому переинициализация DMA в проектах производится через восстановление управляющего слова.

while (1)
{
   if (IrQ_On)
   {
     IrQ_On = 0;
 
   if (CheckDoLedSwitch())
      BRD_LED_Switch(LED_DMA_CYCLE);
    }
}

В основном цикле считается сколько было выполнено циклов DMA и при достижении заданного периода переключается состояние светодиода на отладочной плате. Это демонстрирует внешнему наблюдателю работоспособность программы.



Сохранить статью в PDF

Файлы для скачивания

Документация

Программное обеспечение

Standard Peripherals Library + software pack для Keil MDK 5
Standard Peripherals Library + software pack для Keil MDK 5
ОФИЦИАЛЬНАЯ СБОРКА
Standard Peripherals Library – библиотека для микроконтроллеров.

Теги

Была ли статья полезной?