STM Урок 158. LL. SPI. Interrupt



На прошлом занятии мы познакомились с механизмом обработки прерываний шины SPI контроллера STM32F1, а также потом немного попрактиковались в данном вопросе.

Только всем этим мы занимались с использованием возможностей библиотеки HAL, а в данном занятии мы уже обратимся к помощи библиотеки LL, которая, как известно, такой простотой программирования, как HAL, похвастаться не может, зато она позволяет нам более гибко подходить к вопросам настройки и передачи данных.

Так как с аппаратной составляющей шины SPI мы уже полностью знакомы, так как все этом мы уже изучили в уроке 152, за исключением конечно работы с интерфейсом I2S, то можем смело приступить сразу к практической части.

Схема урока остаётся полностью та же как и в уроке 154

 

 

Логический анализатор также подключим

 

 

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

Проект для ведущего устройства был сделан из проекта LL_SPI_MASTER урока 154 и назван был, соответственно, LL_SPI_MASTER_INT.

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

 

 

Давайте также перейдём на стандартный режим обмена SPI0:0, чтобы нам постоянно в программе логического анализа не переключать режимы и мы уже проверили, что режим SPI1:1 также отлично работает

 

 

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

Откроем файл main.c и посмотрим, что изменилось в инициализации шины SPI1, не считая режима SPI0:0.

Произошла настройка приоритетов и включения глобальных прерываний

 

 

Больше, в принципе, никаких изменений не произошло. Локальные прерывания мы включим позже.

Объявим глобальные буферы приёма и передачи, а также переменные для флага и счётчиков

 

 

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

 

uint16_t i, n;

 

Удалим инициализацию несуществующей переменной

 

i=0; r=0;

 

Включение шины SPI1 пока тоже удалим, мы её включим чуть позже

 

LL_SPI_Enable(SPI1);

 

Включим три вида прерываний на шине SPI1 с помощью функций LL, которые установят биты RXNEIE, TXEIE и ERRIE в регистре SPI_CR2

 

 

Заполним буфер передачи

 

Опустим ножку выбора, дождёмся освобождения буфера передачи, дадим команду на передачу первого полуслова, так как оно равно 0, то просто передадим ноль не из буфера, и напоследок установим бит SPE, тем самым дадим команду на начало передачи

 

 

Из бесконечного цикла пока удалим весь пользовательский код.

Передавать и принимать мы будем по кругу наши 1024 полуслова порциями по 128 полуслов, поэтому ножку выбора мы поднимем после передачи 128-го полуслова.

Отследим мы это в обработчике прерываний.

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

 

 

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

 

 

Также добавим аналогичную функцию для обработки прерываний приёма, пока пустотелую

 

 

Добавим прототипы на наши функции в файле stm32f1xx_it.c

 

 

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

 

 

Вернёмся в функцию обработки прерывания по передаче в файл main.c и в том случае, если мы передали полуслово, кратное 128, отключим прерывания по передаче

 

 

Немного подождём и поднимем ножку Chip Select, так как иначе ножка поднимется рановато

 

 

Затем установим наш пользовательский флаг

 

 

Во всех других случаях попадания в обработчик (когда передали не последнее полуслово) мы дадим команду на передачу следующего

 

 

А в обработчике приёма запишем принятое полуслово в соответствующую ячейку приёмного буфера

 

 

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

 

 

Установим значение локального счётчика на начало нашего пакета

 

 

Отобразим наши значения из обоих буферов на индикаторе

 

 

Обнулим значение глобального счётчика, если он досчитал до конца

 

 

Подождём секунду, опустим ножку выбора шины, отправим первое полуслово в регистр и включим прерывание по передаче

 

 

Вот, в принципе, и весь наш код. Соберём его, прошьём контроллер и пока отключим его от ПК.

Подключим к ПК ведомое устройство и займёмся теперь его проектом, который сделаем из проекта LL_SPI_SLAVE урока 154 и присвоим ему имя LL_SPI_SLAVE_INT.

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

 

 

Также перейдём на стандартный режим обмена SPI0:0

 

 

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

 

 

Объявим глобальные буфер передачи, буфер приёма, в котором нам достаточно будет 128 элементов, переменные для двух счётчиков и флага

 

 

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

 

uint16_t i, n;

 

Удалим инициализацию несуществующей переменной

 

i=0; r=0;

 

Включение шины SPI1 пока тоже удалим, мы её включим чуть позже

 

LL_SPI_Enable(SPI1);

 

Заполним буфер передачи, передавать мы будем полуслова от 1024 до 1

 

 

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

 

LL_mDelay(2000);

 

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

 

 

Из бесконечного цикла пока удалим весь пользовательский код.

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

 

 

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

 

 

В файле stm32f1xx_it.c добавим прототипы на наши функции

 

 

Обработаем флаги в функции-обработчике прерываний шины SPI1

 

 

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

 

 

узнаем номер элемента и присвоим его переменной

 

 

Покажем значения элементов буферов передачи и приёма на индикаторе

 

 

Произведём синхронизацию номеров элементов буферов ведущего и ведомого устройств и сбросим счётчик приёмного буфера

 

 

Обнулим значение глобального счётчика, если он досчитал до конца

 

 

Отправим в буфер передачи шины следующее полуслово и включим наши прерывания

 

 

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

 

Обмен проходит отлично и всё синхронизируется.

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

 

 

 

 

Здесь также всё отлично и правильно.

Таким образом, сегодня мы произвели обмен между двумя устройствами на контроллерах STM32F1 посредством шины SPI, используя возможности библиотеки LL, а также механизм прерываний по событиям передачи и приёма по интерфейсу SPI.

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

 

 

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

 

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

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

 

 

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

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

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

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

 

 

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

 

STM LL. SPI. Interrupt

6 комментариев на “STM Урок 158. LL. SPI. Interrupt
  1. Андрей:

    Здравствуйте, Владимир.

    Не нашел в файле В файле stm32f1xx_it.c прототипы функций

    /* USER CODE BEGIN PFP */
    void SPI1_TX_Callback(void);
    void SPI1_RX_Callback(void);

    хотя в теле урока так прямо и сказано «В файле stm32f1xx_it.c добавим прототипы на наши функции», а сами тела функций в файле main.c

    • Здравствйте!
      Так мы их сами же написали.

      • Андрей:

        Но Вы же обращаетесь к функции SPI1_RX_Callback() в файле stm32f1xx_it.с, а сама функция «телом» помещена в main.c и более НИГДЕ не объявлена. Как SPI1_IRQHandler(void) при вызове SPI1_RX_Callback() находит ее местоположение если прототипа ее нигде нет?

        Или так можно?

        • Мы же прописали прототип в файле stm32f1xx_it.с, вот и стало из него её видно. По функциям СИ очень скоро будет урок. Только там пока без модулей. Модульное программирование чуть позже. Для того я и начал курс уроков по СИ, чтобы подобные вопросы отпали.

  2. Андрей:

    Все делаю по Вашим урокам за исключением того, что соединяю в SPI FULL-DUPLEX STM32F103C6(MASTER) и STM32L053C6(SLAVE) и индикаторов у меня нет так что на мастере организован USB-SPI и вот у меня странный эффект возникает: при пересылке из мастера в слейв 5-ти 16-разрядных слов и ожидания в ответ 5 подготовленных слейвом во входном буфере слейва происходит явное сложение с исходящими данными слейва. Эффект пропадает если в функции SPI1_IRQHandler(void) закоментировать

    // else if(LL_SPI_IsActiveFlag_TXE(SPI1))
    // {
    // SPI1_TX_Callback();
    // }

    а без закоментирования все выглядит так:

    MASTER отправляе {512, 1024, 2048, 4096, 8192} — проверено логическим анализатором
    SLAVE отправляет {123, 456, 789, 258, 369} — проверено логическим анализатором
    SLAVE получает {968, 1813, 2306, 4465, 10053} — последнее значение мусор, но в остальном со сдвигом на одну позицию явное сложение.

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

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

*