Урок 178. CMSIS. STM32F1. SPI. DMA



Продолжаем работу с передачей данных по шине SPI между двумя контроллерами STM32F1. И на данном уроке мы объединим наши знания по шине SPI и периферии DMA в контроллере STM32 и попробуем применить технологию DMA при передаче данных по интерфейсу SPI. Хотя мы тем же самым занимались в уроке 159 и уроке 160, но только применяли мы при этом библиотеки HAL и LL, а сегодня уже будем применять библиотеку CMSIS, которая, как известно, простотой программирования также, как и LL, не блещет, к тому же инициализацию периферии нам тоже придётся писать самим, но зато она позволяет нам ещё более гибко настроить периферию, а также более гибко пользоваться аппаратной частью в процессе работы написания кода программы.

С DMA мы уже знакомы неплохо, мы с ней работали в любых режимах, причём с применением библиотеки CMSIS тоже работали в уроке 176, только в режиме MEM2MEM, в остальных режимах работали с применением библиотек HAL и LL.

Тем не менее, когда мы работали с SPI и DMA с применением библиотеки LL, инициализацию, автоматически сгенерированную при помощи Cube MX, мы всю просматривали и анализировали, поэтому нам не составит много труда проделать это и с применением библиотеки CMSIS.

Схема урока осталась та же самая, что и в прошлом уроке

 

 

Сначала мы займёмся ведущим устройством, проект для которого мы сделаем из проекта урока 175 с именем CMSIS_SPI_MASTER и назовём его CMSIS_SPI_MASTER_DMA.

Подключим к ПК наше ведущее устройство, ведомое пока не подключаем.

Откроем наш проект в Keil и в файле main.c добавим два буфера, один для передачи, другой для приёма, а также парочку глобальных переменных, одну — для счёта и другую — для отслеживания событий

 

 

В функции SPI1_Init поднимем в самое начало функции включение тактирования порта

 

 

Так же, как и в уроках с использованием библиотек HAL и LL, мы будем работать с DMA1 и её каналами 2 и 3, которые, соответственно, будут отвечать за приём и передачу данных.

Данные мы также будем передавать пакетами по 128 полуслов.

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

 

 

Вернёмся в функцию SPI1_Init, в которой у нас и будет находиться основная инициализация каналов 2 и 3.

Начнём с канала 2, отвечающего за приём данных, а вернее за передачу данных из периферии SPI в оперативную память с помощью DMA.

Выберем направление передачи данных

 

 

Настроим приоритет

 

 

Выберем обычный режим (не циклический)

 

 

Периферию не инкрементируем

 

 

Память инкрементируем

 

 

Размер пакета (ширина) — полуслово и для источника и для приёмника

 

 

Аналогично настроим и канал 3, который будет отвечать за передачу данных из оперативной памяти в буфер SPI (за приём данных по шине SPI). Разница будет только в настройке направления передачи

 

 

В функции main() мы изменим имя локальной переменной

 

uint16_t i, n;

 

Вызовем функцию инициализации DMA1

 

 

Удалим вызов макроса старта модуля

 

SPI1_ENABLE();

 

Отключим пока оба канала DMA

 

 

Очистим флаги всех прерываний каналов

 

 

Разрешим запросы от каналов 2 и 3 DMA1 для SPI1

 

 

 

Разрешим прерывания по событиям ошибки и окончания передачи для каналов DMA

 

 

Включим SPI1

 

 

Из бесконечного цикла всё удалим и заполним буфер передачи значениями

 

 

На время отключим пока каналы DMA

 

 

Занесём в соответствующие регистры каналов количество полуслов, которое мы хотим передать через данные каналы

 

 

Занесём адреса приёмника и источника в соответствующие регистры каналов

 

 

Опустим ножку выбора шины SPI

 

 

Включим каналы DMA

 

 

Добавим в самом низу файла обработчик прерываний от канала 2, отвечающего за приём данных, нашего DMA, в котором мы в случае установленного флага окончания передачи данных сбросим данный флаг и поднимем ножку выбора шины SPI, а в случае установленного флага ошибки вызовем инструкцию nop

 

 

Добавим аналогичный обработчик прерываний от канала 3, в котором, в отличии от предыдущего обработчика, мы уже не будем поднимать ножку выбора, а установим наш пользовательский флаг

 

 

Вернёмся в бесконечный цикл функции main() и дождёмся там установки нашего флага и заново его обнулим

 

 

Затем отобразим на индикаторе принятые и переданные данные

 

 

Подождём 1 секунду

 

 

Добавим 128 к нашему счётчику переданных полуслов

 

 

 

Обнулим его при достижении им порога значений

 

 

Вот и весь код для устройства MASTER. Соберём его, прошьём контроллер, отключим от ПК наше ведущее устройство, подключим его к независимому источнику энергии, а к ПК подключим ведомое устройство.

Проект для ведомого устройства был сделан из из проекта того же урока 175 с именем CMSIS_SPI_SLAVE и назовём его, соответственно, CMSIS_SPI_SLAVE_DMA.

Откроем данный проект в Keil и также добавим 2 буфера и переменную для пользовательского флага

 

 

Также добавим выше функции main() функцию инициализации DMA1, точь в точь такую же как и в проекте ведущего устройства

 

 

В функции SPI1_Init проинициализируем каналы 2 и 3 периферии DMA1, код инициализации такой же как и в проекте для устройства MASTER

 

 

В функции main() изменим имя локальной переменной

 

uint16_t i, n;

 

Вызовем функцию инициализации DMA1

 

 

Удалим вот этот код

 

i=0; r=0;

 

Также удалим вызов макроса старта модуля

 

SPI1_ENABLE();

 

Удалим задержку

 

delay_ms(2000);

 

Заполним пока буфер передачи какими-нибудь значениями, например числами от 128 до 1 в обратном порядке

 

 

Настроим каналы DMA точно так же, как и в проекте ведущего устройства, и включим периферию SPI1

 

 

Из бесконечного цикла пока удалим весь код и произведём там настройку каналов таким же образом как и в проекте устройства MASTER, только здесь мы не опускаем ножку выбора, она у нас управляется другим устройством

 

 

В самом низу файла добавим обработчики прерываний от каналов DMA

 

 

Код обработчиков практически такой же как и для устройства MASTER, только здесь мы не поднимаем ножку выбора в обработчике канала 2.

В бесконечном цикле функции main() дождёмся установки пользовательского флага и снова его сбросим

 

 

Заполним буфер для передачи ведущему устройствами значениями приёмного буфера, вычтенными из 8192

 

 

Отобразим на индикаторе принятые и переданные числа

 

 

Соберём код, прошьём контроллер и посмотрим результат обмена данными между устройствами

 

 

Показания приёмного индикатора на ведущем будут всегда отставать на 128 от ведомого, так как мы там отображаем предыдущий пакет.

Посмотрим теперь обмен в программе логического анализа

 

 

Мы видим, что передаваемые числа следуют в пакетах друг за другом непрерывно.

А здесь мы видим, что у нас ничего не пропускается и не выпадает

 

 

А вот здесь мы видим, что ножке по окончанию пакета теперь поднимается вовремя

 

 

Итак, на данном уроке мы отработали механизм передачи данных по шине SPI с применением технологии DMA. Зная уже всю аппаратную часть, нам гораздо легче было написать код, чем если бы начинали с нуля. Также на данном уроке мы заканчиваем цикл уроков по шине SPI контроллера STM32F1 с применением библиотеки CMSIS. Конечно, это вовсе не означает, что мы в дальнейшем перестанем пользоваться в уроках данной шиной и технологией DMA. Просто нам теперь будет гораздо легче ориентироваться в написании кода для данных немаловажных видов периферии контроллера. Думаю, что нам очень скоро это пригодится, так как нам предстоит вскоре поработать с дисплеем LCD TFT под управлением контроллера ILI9341, подключенного именно по шине SPI. Такой дисплей у меня наконец-то появился. Подключим мы его к плате STM32F4-Discovery, с которой мы когда-то очень долго работали, причём работали с таким же дисплеем, но подключенным по 8-битной шине. Не каждому удалось заиметь такой дисплей, многие по ошибке, а многие и не по ошибке приобрели дисплей, именно подключенный по шине SPI и просили меня сделать такой урок. Конечно же на такие массовые просьбы я просто не мог не откликнуться, мне прислали именно такой дисплей и мы теперь с ним поработаем, начнём с библиотеки HAL, причём сначала без DMA, потом применим DMA для того, чтобы сравнить, что нам даст эта периферия. А потом, возможно применим библиотеки LL и CMSIS, с которыми мы пока ещё с контроллером F4 ни разу не работали. Вот заодно и сравним разницу применения данных библиотек по отношению к разным контроллерам.

Всем спасибо за внимание!

 

 

Предыдущий урок Программирование МК STM32 Следующий урок

 

Исходный код для ведущего устройства (MASTER)

Исходный код для ведомого устройства (MASTER)

 

 

Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

Индикатор светодиодный семиразрядный с драйвером MAX7219

Логический анализатор 16 каналов можно приобрести здесь

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM CMSIS. STM32F1. SPI. DMA

3 комментария на “Урок 178. CMSIS. STM32F1. SPI. DMA
  1. Дмитрий:

    Здравствуйте!
    Спасибо за уроки!
    У меня вот какой вопрос. Я настроил размер передаваемых данных в SPI как SPI_DataSize_16b. Когда (в ручном режиме) записываю двухбайтное число в SPI1->DR, данные благополучно уходят как нужно (режим SPI_FirstBit_MSB). Как пример, отправляю 0x4020, получаю 0x4020 (смотрю по логическому анализатору). А вот когда настраиваю SPI с DMA где DMA_PeripheralDataSize_HalfWord и DMA_PeripheralDataSize_HalfWord и указываю 1 посылку (т.е те же 2 байта) происходят чудеса (по таблице выравниваний которая в Reference Manual, п 13.3.4, все должно быть ок), если отправляю 0x4020 то получаю 0x0020. Перепробовал все что знаю. Не подскажите, пожалуйста, в чем причина?

    • Дмитрий:

      Забавно, сейчас прочитал и понял в чем ошибка. Я указал размер периферии и памяти как — DMA_PeripheralDataSize_HalfWord.

  2. Timur:

    Будет ли урок по работе в CMSIS c I2S через DMA?

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

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

*