Урок 34. Работа с контроллером DMA через CMSIS регистры. Практический опыт использования его для передачи данных в порты ввода/вывода. Разработка многоканального генератора импульсов.

Работа с DMA STM32

В уроке будем работать над проектом многоканального генератора импульсов. На практике научимся использовать систему прямого доступа к памяти микроконтроллера STM32.

Предыдущий урок     Список уроков     Следующий урок

В первую очередь будем разбираться, как использовать DMA с периферийными устройствами, которые мы уже изучили. А именно, с портами ввода-вывода, таймерами, UART. Работу с АЦП через DMA отложим на последующие уроки.

 

Я решил начать с DMA-передачи данных между памятью и портами ввода/вывода. Этот вариант использования прямого доступа к памяти практически не встречается в примерах. Тем не менее, он востребован во многих задачах.

Давайте разработаем практическое устройство, в котором DMA-обмен данными между портами ввода/вывода и памятью будет особенно эффективен. Более того, такое устройство, созданное без использования режима ПДП, будет иметь параметры как минимум на порядок хуже.

Используя одну плату STM32F103C8T6, сделаем некий комбинированный прибор, объединяющий функции многоканальных генератора последовательности импульсов и логического анализатора.

Поставим предварительное техническое задание.

  • Оба устройства должны работать параллельно, в реальном времени. Т.е. логический анализатор сможет регистрировать сигналы собственного генератора импульсов.
  • Число каналов для входных и выходных сигналов – по 4. В принципе, работать будем с байтами. Ничего не мешает реализовать устройство с 8 каналами для генератора и столько же для анализатора. Я ограничил разрядность, чтобы упростить себе задачу разработки программы верхнего уровня. Меньше диаграмм, проще отображать на экране компьютера, проще задавать последовательности импульсов. Из этих же соображений я ограничил некоторые другие параметры устройства.
  • Число выборок анализатора – 10000.
  • Число временных градаций (интервалов) для создания сигнала генератора – 16.
  • Время дискретизации входных и выходных сигналов определим как минимально возможное. Сделаем этот параметр регулируемым в широких пределах и посмотрим на практике, что получится.
  • Временные параметры работы устройства и формы сигналов генератора будем задавать от компьютера. На нем же собираемся выводить диаграммы логического анализатора и устанавливать режимы работы устройства.

Что в результате получится – не знаю. Придут в голову новые функции и возможности – добавим. Но есть стремление создать устройство, которое может найти практическое применение.

В момент написания этих строк у меня ничего нет. Разработка только начинается. Буду последовательно создавать программу, подробно рассказывать об использовании DMA.

С контроллером DMA в этом уроке работаем через регистры CMSIS.

 

Аппаратная часть.

На этом этапе необходимо назначить выводы микроконтроллера для входных сигналов анализатора и выходных сигналов генератора.

Я решил использовать разные порты для входных и выходных сигналов. Выбрал выводы:

  • PA0, PA1, PA2, PA3 как входы логического анализатора;
  • PB4, PB5, PB6, PB7 в качестве выходов генератора импульсов.

Схема устройства

 

DMA-передача данных из памяти в порт ввода-вывода.

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

Используя STM32CubeMX, создадим проект Lesson34_1. В нем настроим только систему тактирования на максимальную частоту 72 мГц.

Конфигурируем порты ввода-вывода. Если забыли - открываете урок 7.

Разрешаем тактирование портов PA и PB.

/* USER CODE BEGIN SysInit */

// разрешение тактирования портов
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;

Разряды PA0, PA1, PA2, PA3 устанавливаем в режим входов  с подтягивающими к питанию резисторами.

  • Биты MODE = 00 (режим вход)
  • Биты CNF = 10 (подтягивающий резистор включен)
  • Биты PxODR = 1 (подтягивающий резистор к питанию).
  • В итоге CNF12[1:0], MODE12[1:0]  = 1000.

В код добавляем:

// Выводы PA0, PA1, PA2, PA3 в режим входов с подтягивающими к питанию резисторами
GPIOA->CRL &= 0xffff0000;
GPIOA->CRL |= 0x00008888;
GPIOA -> BSRR = 0xf; // подтягивающие резисторы к + 3 В

Выводы PB4, PB5, PB6, PB7 конфигурируем, как активные выходы с максимальной частотой тактирования 50 мГц.

  • Биты MODE = 11 (выход, 50 мГц)
  • Биты CNF = 00 (активный выход)
  • В итоге CNF12[1:0], MODE12[1:0]  = 0011

В код добавляем:

// Выводы PB4, PB5, PB6, PB7 - активный выход с частотой тактирования 50 мГц.
GPIOB->CRL &= 0x0000ffff;
GPIOB->CRL |= 0x33330000;

Теперь:

  • из регистра GPIOA->IDR всегда можно считать текущее состояние выводов PA0, PA1, PA2, PA3,
  • все, что будет загружено в регистр GPIOB->ODR окажется на выводах PB4, PB5, PB6, PB7.

Сказанное распространяется и на операции записи-чтения через DMA.

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

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

Назначаем таймеры, каналы DMA, приоритеты.

Смотрим таблицу соответствия каналов DMA периферийным устройствам.

Соответствие каналов DMA периферийным устройствам

Я выбрал:

  • таймер 2 для генерации импульсов.
  • таймер 3 для сканирования входов анализатора.

Получилось.

Канал DMA Источник запроса Приоритет Назначение
2 TIM2_UP Средний Генерация импульсов
3 TIM3_UP Высокий Сканирование анализатора
4 USART1_TX Низкий Передача данных на компьютер
5 USART1_RX Низкий Чтение данных от компьютера

Значит, надо конфигурировать таймер 2 в режим циклического счетчика. Забывчивым можно просмотреть урок 19.

Зададим частоту тактирования таймера после предделителя равной 1 мГц. Получится, что если изменять значение регистра перезагрузки от 0 до 65535, счетчик будет сбрасываться с периодом от 1 мкс до 65,536 мс. С таким же периодом он будет формировать запросы DMA.

Добавляем в исходный код:

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!

Последняя строка разрешает запрос DMA по переполнению счетчика. Это установка бита UDE в регистре TIMx_DIER. Я не приводил полный формат этого регистра в предыдущих уроках. Постепенно наведу порядок на странице ”Регистры STM32”. Распишу полностью форматы всех регистров периферийных устройств.

Теперь у нас таймер 2 циклически формирует запрос DMA, но сама передача данных не происходит. Запрещена работа контроллера DMA.

Настраиваем контроллер DMA.

Создадим массив, из которого будем передавать данные.

/* USER CODE BEGIN PV */

uint8_t generatorBuf[16]= { 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0 }; // буфер генератора

Я задал данные, формирующие диаграмму 4х разрядного двоичного счетчика.

Разрешаем тактирование DMA.

// Конфигурация DMA
RCC->AHBENR |= RCC_AHBENR_DMA1EN; // разрешение тактирования DMA1

Задаем адрес буфера для передачи, адрес регистра данных порта вывода и количество данных

DMA1_Channel2->CMAR = (uint32_t) generatorBuf; // адрес буфера
DMA1_Channel2->CPAR = (uint32_t)(&GPIOB->ODR); // адрес регистра вывода данных порта
DMA1_Channel2->CNDTR = 16; // количество данных

Остается установить конфигурацию DMA в регистре CCR.

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!

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

Осталось включить передачу данных и можно проверять работу генератора.

DMA1_Channel2->CCR |= 1 << DMA_CCR_EN_Pos; // разрешение передачи данных

У меня работает. Это 2 младших канала генератора.

Осциллограмма выходных импульсов

Дискретность 10 мкс, как и задано. Второй канал подключен шнуром без делителя. Поэтому такие выбросы.

 

Функции (программные) управления генератором импульсов.

В нашем проекте надо будет включать генератор, выключать, задавать временные параметры. Давайте разработаем 3 очевидные функции для управления генератором.

Первая запускает генератор в однократном режиме, если аргумент нулевой, и в циклическом режиме при ненулевом аргументе.

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!

Обратите внимание, что при каждом запуске необходимо заново устанавливать количество данных для передачи. При передаче каждого данного содержимое этого регистра уменьшается на 1 и по завершению передачи будет равно 0.

Еще не забывайте, что загрузка регистра CNDTR возможна только после отключения канала. В предыдущем уроке об этом написано подробно.

Вторая функция останавливает работу генератора.

// остановка генератора
void stopGenerator(void) {
  DMA1_Channel2->CCR &= ~ (1 << DMA_CCR_EN_Pos); // запрет передачи данных
}

И последняя, устанавливает период дискретизации сигналов генератора в микросекундах.

// период дискретизации генератора
void setPeriodGenerator (uint16_t period) {
  TIM2->ARR = period - 1;
}

Строку разрешения передачи данных после конфигурации DMA следует убрать. Теперь работа генератора начнется только после вызова  startGenerator().

Проект на этом этапе разработки можно загрузить по ссылке.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Генератор создан, работает. Осталось научиться управлять им.

 

Реализация управления генератором от компьютера.

Для управления генератором через UART я разработал простой цифровой протокол. Особенно не задумывался. Контрольные коды самые простые, но вполне достаточные при таком небольшом количестве данных.

  • Компьютер посылает код операции команды, т.е. сообщает что делать.
  • Дальше следуют данные, если нужны.
  • Весь пакет от компьютера сопровождается контрольным кодом.
  • В случае успешно полученной команды устройство посылает компьютеру ответ.
Код операции Последующие байты Ответ Команда
1 -> 1 ^ 0xe5 -> <- 1 ^ 0xa3 Запуск генератора
однократный
2 -> 2 ^ 0xe5 -> <-2 ^  0xa3 Запуск генератора
циклический
3 -> 3 ^ 0xe5 -> <-3 ^  0xa3 Остановка генератора
4 -> Period мл. байт ->
Period ст. байт ->
Сумма всех байтов  ^ 0xe5 ->
<-4 ^  0xa3 Установка периода дискретизации
(в микросекундах)
5 -> D0 ->
. . .
D15 ->
Сумма всех байтов  ^ 0xe5 ->
<-5 ^  0xa3 Загрузка диаграммы
выходных сигналов

Я реализую протокол обмена с компьютером самым простым способом – опросом состояния UART в основном цикле программы, без прерываний, в блокирующем режиме.

Конфигурируем UART.

Этот блок я взял из урока 20. Только установил скорость 115200 и запретил прерывания UART.

// инициализация UART1
RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // включаем тактирование UART1
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // разрешаем тактирование порта GPIOA

// настройка вывода PA9 (TX1) на режим альтернативной функции с активным выходом
// Биты CNF = 10, ,биты MODE = X1
GPIOA->CRH &= (~GPIO_CRH_CNF9_0);
GPIOA->CRH |= (GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9);

// настройка вывода PA10 (RX1) на режим входа с подтягивающим резистором
// Биты CNF = 10, ,биты MODE = 00, ODR = 1
GPIOA->CRH &= (~GPIO_CRH_CNF10_0);
GPIOA->CRH |= GPIO_CRH_CNF10_1;
GPIOA->CRH &= (~(GPIO_CRH_MODE10));
GPIOA->BSRR |= GPIO_ODR_ODR10;

// конфигурация UART1
USART1->CR1 = USART_CR1_UE; // разрешаем USART1, сбрасываем остальные биты
USART1->BRR = 625; // скорость 115200 бод
USART1->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE; // разрешаем приемник и передатчик
USART1->CR2 = 0;
USART1->CR3 = 0;

Дальше, в основном цикле реализовал блок обмена с компьютером. Посмотрите его в коде проекта. Логика этого блока проста и понятна.

Загрузить окончательный вариант проекта генератора можно здесь.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Основная программа все время тратит на ожидание команды от компьютера. Но это нисколько не отражается на работе генератора. Формирование импульсов происходит даже не в фоновом режиме, а в аппаратном блоке DMA.

Проверил работу обмена с компьютером с помощью терминала CoolTerm. Сформировал несколько команд вручную. Убедился, что приходят правильные ответные коды.

Отладка обмена

Убедился с помощью осциллографа, что выходные сигналы изменяются и соответствуют заданным.

 

Программа верхнего уровня для управления генератором импульсов.

Загрузить можно по ссылке GeneratorAnalyzer_1

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Основное окно выглядит так.

Основное окно программы управления устройством

  • Кнопки:
    • Пуск однократный - запускает последовательность из 16 ти интервалов.
    • Пуск циклический – запускает последовательность импульсов в цикле.
    • Стоп – останавливает цикл.
    • Время дискретн. – загружает в устройство параметр - длительность одного интервала, заданный слайдером ниже. Т.е. изменяет время развертки выходного сигнала генератора.
  • Поле с диаграммами позволяет задавать последовательность импульсов. При нажатии на цифру инвертируется полярность импульса. Одновременно данные для формирования импульсов загружаются в контроллер. Т.е. управление последовательностями импульсов происходит в реальном времени.
  • Квадратик с надписью “Обмен” индицирует результат обмена компьютера с контроллером. Успешную операцию обмена он отмечает зеленым цветом. Если произошла ошибка – становится красным.

Вот небольшой фильм о работе программы управления генератором импульсов.

Все работает, как было задумано.

Минимальное время дискретности, при которой работает генератор на практике, получилось 2 мкс. При периоде 1 мкс выходные сигналы перестают формироваться. Я не думаю, что это плохой результат. Возможно, таймер перестает вырабатывать запросы, когда в регистр перезагрузки заносится 0. Можно попробовать  уменьшить значение предделителя.

TIM2->PSC = 71; // 1 мкс

Надеюсь, устройство найдет практическое применение. Может с доработками. Пока просто приятно нажимать мышью по диаграмме на мониторе компьютера и видеть, что сигналы на экране осциллографа изменяются.

 

В следующем уроке добавим к устройству логический анализатор. Данные будем передавать из порта ввода/вывода в память. Потом посылать на компьютер. Все через прямой доступ к памяти.

 

Предыдущий урок     Список уроков     Следующий урок

2

Автор публикации

не в сети 2 недели

Эдуард

256
Комментарии: 1839Публикации: 183Регистрация: 13-12-2015

4 комментария на «Урок 34. Работа с контроллером DMA через CMSIS регистры. Практический опыт использования его для передачи данных в порты ввода/вывода. Разработка многоканального генератора импульсов.»

  1. Спасибо, очень рад продолжению отличной серии! Не пояснить, почему так получается при использовании связки UART-DMA, когда я пытаюсь реализовать отправку по DMA массива переменной длины: в прерывания HT DMA Rx канала ( DMA1_Channel6) я пытаюсь посмотреть, сколько осталось переслать
    uint8_t buf[64];
    sprintf(buf, «%d\r\n», (uint32_t)DMA1_Channel6->CNDTR);
    uint8_t len = strlen(buf);
    for(uint8_t k = 0; kDR = buf[k];
    }
    Получаю в терминале значение при каждой перезагрузке разное и похожее на указатель, несколько сотен тысяч. Когда делаю то же самое в TC прерывания, та же фигня, совсем не 0, как ожидалось. При этом при настройке DMA регистр устанавливал в 128.

    PS можете как-то сделать, чтоб можно было донатить по карте? А то оплатить на месяц легко, а задонатить — через три гроба.

    0
    • Здравствуйте!
      Нехорошо в обработчике прерывания использовать «длинные» функции, такие, как sprintf.
      Попробуйте передавать значение CNDTR через переменную volatile и выводить в UART в основном цикле программы.
      Еще попробуйте считать и вывести значение CNDTR в основном цикле.
      Донатить можно по карте Сбербанка 4817 7602 2483 9435. Спасибо.

      0
      • Разобрался. Пример выше рабочий, но упрощенный — я делал враппер над sprintf (преобразование и отправка в одной функции) и естессно налажал с передачей va_list. Вместо sprintf в таких случаях нужно использовать vsprintf, а ещё лучше его выкинуть и использовать что-то попроще и полегче, см «Преобразуем в строку» на easyelectronics.

        0

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".