STM Урок 160. LL. SPI. DMA



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

С аппаратной составляющей шины SPI мы уже прекрасно знакомы из предыдущих уроков, так как мы уже довольно-таки давно с данной шиной работаем, также с аппаратной составляющей DMA мы также знакомы. Библиотеку LL мы также использовали при работе с DMA в уроке 156, правда, пользовались мы режимом передачи данных из памяти в память (MEM2MEM), а, так как сегодня мы используем DMA для шины SPI, которая является периферией, то использовать мы будем уже другие два режима передачи: из периферии в память (Peripheral-to-memory) и из памяти в периферию (Memory-to-peripheral). Каких-то сильных отличий от режима передачи из памяти в память они не имеют, поэтому мы легко сегодня с этими режимами справимся.

Режим передачи из периферии в память организован в контроллере STM32 следующим образом (взято из документации на немного другой контроллер, поэтому наименования регистров могут отличаться)

 

 

А режим передачи из памяти в периферию организован вот таким образом

 

 

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

Схема остаётся та же, что и в ряде предыдущих уроков

 

 

Проект для ведущего устройства был сделан из проекта урока 158 с именем LL_SPI_MASTER_INT и был назван LL_SPI_MASTER_DMA. В новом проекте удалим файл stm32f1xx_it.c, чтобы нам не удалять потом из него прототипы несуществующих функций и чтобы он сгенерировался новый.

Запустим наш проект в Cube MX и отключим прерывания на шине SPI1

 

 

Включим каналы DMA на приём и передачу, настройки все остаются по умолчанию

 

 

Запомним, что на приём у нас задействован канал 2 периферии DMA1, а на передачу — канал 3. Это нам пригодится для написания кода в дальнейшем.

Также включим использование библиотеки LL для DMA1

 

 

Сгенерируем проект, откроем его в Keil, подключим к дереву проекта файл max7219.c, настроим программатор на автоперезагрузку и отключим оптимизацию.

Перейдём в файл max7219.c и в функции инициализации добавим код, который отключит режим тестирования у драйвера индикатора

 

 

Перейдём теперь в файл main,c и посмотрим, что у нас изменилось в инициализации шины, а также посмотрим инициализацию периферии DMA.

Начнём с DMA.

В теле функции инициализации DMA MX_DMA_Init сначала идёт включение тактирования периферии DMA1, далее устанавливаются приоритет и субприоритет канала 2 DMA1, а затем и канала 3.

На этом в данной функции вся инициализация DMA заканчивается, скорее всего вся настройка её будет в функции инициализации SPI1 MX_SPI1_Init, в которую мы и перейдём.

В теле данной функции сначала включается тактирование шины, устанавливаются режим работы ножек портов, а далее и начинается инициализация DMA1 и её двух каналов. Всю инициализацию DMA мы рассматривать не будем, так как мы её уже смотрели в уроке 156, мы лишь посмотрим отличие настройки DMA с использованием периферии.

Сразу же мы видим различие в вызове функции LL_DMA_SetDataTransferDirection, так как у нас изменилось направление передачи. Если посмотреть тело данной функции, то теперь у нас просто не включится бит 14 MEM2MEM в регистре DMA_CCR2. Это для канала 2. А для канала 3 также бит 14 у регистра DMA_CCR3 не включится, зато включится бит 4 этого же регистра. Этот бит — DIR.

Также различие будет в вызове функции LL_DMA_SetPeriphIncMode, так как адрес периферии у нас инкрементироваться не должен. Поэтому бит 6 PINC в регистрах DMA_CCR2 и DMA_CCR3 у нас не включается.

Больше никаких отличий настройки каналов периферии DMA1 не будет.

Теперь по поводу настройки шины SPI1. Соответственно, здесь уже не включаются прерывания и не устанавливаются их приоритеты. А больше никаких изменений здесь нет. Вся привязка каналов DMA к шине будет в пользовательском коде и ложится на плечи разработчика.

Для начала удалим объявление вот этой переменной

 

uint8_t cnt, fl=0;

 

и вот этой

 

uint32_t full_cnt=0;

 

В функции main() удалим вот этот участок кода

 

LL_SPI_EnableIT_RXNE(SPI1);

LL_SPI_EnableIT_TXE(SPI1);

LL_SPI_EnableIT_ERR(SPI1);

 

 

Вот этот участок кода после большой задержки также удалим

 

LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_4);

while(!LL_SPI_IsActiveFlag_TXE(SPI1)) {}

LL_SPI_TransmitData16 (SPI1, 0);

LL_SPI_Enable(SPI1);

 

Для начала отключим каналы DMA (хоть их и никто не включал, но таков порядок)

 

 

Тем самым отключаются биты EN в регистрах CCR каналов.

Потом сбросим флаги TC и TE каналов при помощи занесения логических единиц в соответствующие им биты регистра DMA_IFCR

 

 

В регистре периферии SPI1 CR2 включим биты TXDMAEN и SPI_CR2_RXDMAEN с помощью специальных функций для того, чтобы шина SPI1 могла формировать запросы к каналам DMA при установке флагов TXE и RXNE

 

 

Затем мы включаем прерывания каналов DMA с помощью установки TCIE и TEIE в соответствующих каналам регистрах DMA_CCRx

 

 

И затем включаем шину SPI1

 

 

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

 

 

Теперь нам нужно задать количество передаваемых данных через SPI1 и DMA и также задать адреса передачи и приёма. Для этого мы сначала должны отключить каналы DMA

 

 

Задаём количество элементов передачи (в нашем случае полуслов)

 

 

Тем самым мы занесли значение 128 в соответствующие нашим каналам регистры DMA_CNTDRx.

Затем мы покажем адреса передачи и приёма для каналов

 

 

умножение на 2 применяется, так как у нас элементы двухбайтные. А источник и приёмник при передаче мы меняем местами по сравнению с приёмом, так как у нас идёт передача уже в обратную сторону. Для того. чтобы определить адрес периферии, мы применяем специальную функцию библиотеки LL — LL_SPI_DMA_GetRegAddr, которая получает адрес регистра DR.

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

 

 

А потом включаем каналы DMA

 

 

Это ещё не всё для бесконечного цикла, но давайте пока займёмся обработкой прерываний от DMA.

Функции SPI1_TX_Callback и SPI1_RX_Callback удалим вместе с их телами. А добавим теперь функции следующие для наших каналов DMA

 

 

В функции обработки окончания передачи канала 2, который отвечает за приём данных, мы сбросим флаг GI, который сбросит флаги всех прерываний, поднимем ножку выбора шины SPI1 и включим наш пользовательский флаг, а в функции канала 3, отвечающего за передачу данных, мы только сбросим флаг глобальных прерываний.

Только в эти функции мы автоматически не попадём, их надо вызвать, для чего мы сначала добавим на них прототипы в файле stm32f1xx_it.c

 

 

Перейдём в обработчик прерывания 2 канала DMA1_Channel2_IRQHandler и вызовем там нашу функцию, а в случае установки флага по ошибке, просто сбросим его

 

 

 

Аналогично поступим и с функцией канала 3 DMA1_Channel3_IRQHandler

 

 

Теперь прерывания по окончанию приёма и передачи всех данных, проходящих через каналы 2 и 3 DMA1, своевременно обработаются.

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

 

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

 

 

Вот в принципе весь код для ведущего устройства.

Подключим его к компьютеру по USB, соберём наш код и прошьём его контроллер.

Затем отключим его и займёмся ведомым устройством.

Проект для ведомого устройства был создан также из проекта урока 158 с именем LL_SPI_SLAVE_INT и был назван LL_SPI_SLAVE_DMA. В новом проекте также удалим файл stm32f1xx_it.c.

Откроем новый проект в Cube MX и также отключим прерывания на шине SPI1

 

 

А в разделе DMA мы также подключим каналы для передачи и приёма

 

 

Задействуем для DMA библиотеку LL

 

 

Сгенерируем проект, откроем его в Keil, подключим к дереву проекта файл max7219.c, настроим программатор на автоперезагрузку и отключим оптимизацию.

Перейдём в файл max7219.c и в функции инициализации также отключим режим тестирования у драйвера индикатора

 

 

Перейдём в файл main.c и удалим следующие глобальные переменные

 

uint16_t dst_cnt=0;

uint32_t full_cnt=0;

 

Перейдём в функцию main() и удалим вот эти строки с кодом

 

LL_SPI_TransmitData16 (SPI1, 1024);

LL_SPI_EnableIT_RXNE(SPI1);

LL_SPI_EnableIT_TXE(SPI1);

LL_SPI_EnableIT_ERR(SPI1);

 

И проделаем те же самые настройки. что и в ведущем устройстве

 

 

Из бесконечного цикла удалим весь код и вставим вот этот, аналогичный коду ведущего устройства, только порог мы отследим в конце тела цикла (в принципе, разницы никакой). Также отличие в том, что принятые данные отображаются на дисплее справа, а отправленные — слева в отличие от ведущего устройства

 

 

Также удалим функции SPI1_TX_Callback и SPI1_RX_Callback, а вместо них добавим эти, точь в точь такие же, как и в ведущем устройстве

 

 

Добавим на них прототипы в файле stm32f1xx_it.c

 

 

Обработаем прерывания канала 2

 

 

и канала 3

 

 

Вот и весь код.

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

Мы должны будем увидеть вот такой процесс обмена на индикаторах

 

 

Также посмотрим, как идёт процесс обмена, в программе логического анализа

 

 

 

 

Всё отлично!

Итак, на данном уроке мы отработали механизм передачи данных по шине SPI с применением технологии DMA. Зная уже всю аппаратную часть, нам гораздо легче было написать код, как если бы начинали с нуля. Также на данном уроке мы заканчиваем цикл уроков по шине SPI контроллера STM32F1 с применением библиотек HAL и LL. Конечно, это вовсе не означает, что мы в дальнейшем перестанем пользоваться в уроках данной шиной и технологией DMA. Просто нам теперь будет гораздо легче ориентироваться в написании кода для данных немаловажных видов периферии контроллера.

 

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

 

 

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

 

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

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

 

 

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

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

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

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

 

 

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

 

STM LL. SPI. DMA

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

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

*