Продолжаем подробное изучение шины SPI в контроллере STM32. И на данном уроке мы попробуем воспользоваться механизмом прерываний, организованным в периферии SPI в данном контроллере.
Использовать сегодня мы будем возможности библиотеки HAL, которые позволят нам также легко отследить передачу большой порции данных по шине SPI.
Для того, чтобы попробовать, как работают прерывания в SPI контроллера STM32F1, мы будем передавать сразу большие порции данных с одного контроллера в другой, а также обратно.
Схема будет использоваться та же, что и в уроке 153
С битами, отвечающими за включение прерываний в SPI, а также с битами флагов этих прерываний, мы уже знакомились в уроке 152, когда начинали работать с шиной SPI с использованием библиотеки LL. Поэтому мы сразу смело можем приступить к нашим проектам.
Проект для ведущего устройства сделан был из проекта уроке 153 с именем SPI_MASTER и имя ему было присвоено SPI_MASTER_INT.
Откроем проект в Cube MX и включим прерывания в SPI1
Сгенерируем проект и откроем его в Keil.
Настроим программатор на автоперезагрузку, отключим оптимизацию и подключим файл работы с индикатором max7219.c.
Откроем файл main.c и удалим вот этот глобальный массив
uint8_t RxBuf[2] = {0};
Вместо него добавим два больших буфера, один для приёма, другой для передачи, а также переменную для флага. Ещё добавим переменную для счётчика, вернее она у нас уже была в функции main(), теперь мы её сделаем глобальной. И добавим переменную ещё для одного счётчика
1 2 3 4 5 |
/* USER CODE BEGIN PV */ uint16_t src_buf[1024] = {0}; uint16_t dst_buf[1024] = {0}; uint8_t fl=0; uint16_t i; |
А в функции main() мы в локальной переменной изменим имя
uint8_t n;
До вывода большого числа заполним буфер источника числами от 1 до 1024
1 2 3 4 5 6 |
Init_7219(); //fill src buffer for(i=0;i<1024;i++) { src_buf[i] = i; } |
В бесконечном цикле удалим пока весь пользовательский код, так как там исправлять придётся слишком много.
Сначала там сбросим счётчик, если его значение достигло порога
1 2 |
/* USER CODE BEGIN 3 */ if(i>1023) i=0; |
Опустим ножку выбора и передадим порцию данных в количестве 128 слов, а также примем порцию данных в таком же количестве и поместим в приёмный буфер. Если быть более точными, то мы здесь только дадим команду на передачу. Программа не будет здесь ждать, пока данные передадутся, поэтому здесь нет таймаута. И поэтому нам надо будет затем как-то отследить тот момент, когда все данные передадутся
1 2 3 |
if(i>1023) i=0; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_TransmitReceive_IT(&hspi1, (uint8_t*) (src_buf+i), (uint8_t *) (dst_buf+i), 128); |
Отобразим принятые данные на индикаторе, подождём секунду и добавим к нашему счётчику сразу 128.
1 2 3 4 5 6 7 8 9 |
HAL_SPI_TransmitReceive_IT(&hspi1, (uint8_t*) (src_buf+i), (uint8_t *) (dst_buf+i), 128); for(n=0;n<128;n++) { NumberR_7219(src_buf[n+i]); NumberL_7219(dst_buf[n+i]); HAL_Delay(50); } HAL_Delay(1000); i+=128; |
Ножку обратно мы здесь поднимать не будем, это всё будет в другом месте — в обработчике переывания.
Теперь нам надо отследить окончание передачи всех данных, причём обработчик прерываний не позволяет этого делать
Добавим функцию для обработки прерываний, в которой узнаем, что у нас передан именно последний элемент данных, для этого есть специальный счётчик обратного отчсёта, реализованный в библиотеке HAL
1 2 3 4 5 6 7 8 9 10 11 12 |
/* USER CODE BEGIN 4 */ void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi == &hspi1) { if(hspi1.TxXferCount==0) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); fl=1; } } } |
Также в бесконечном цикле функции main() дождёмся установки нашего флага и сбросим его
1 2 3 |
HAL_SPI_TransmitReceive_IT(&hspi1, (uint8_t*) (src_buf+i), (uint8_t *) (dst_buf+i), 128); while(!fl) {} fl=0; |
Соберём проект, прошьём контроллер ведущей платы и пока ничего не проверяем.
Отключим пока контроллер Мастера и займёмся проектом ведомого устройства, который был сделан из проекта ведомого устройства урока 153 с именем SPI_SLAVE и присвоим ему имя SPI_SLAVE_INT.
Откроем проект в Cube MX и также включим прерывания в SPI1
Сгенерируем проект и откроем его в Keil.
Настроим программатор на автоперезагрузку, отключим оптимизацию и подключим файл работы с индикатором max7219.c.
Откроем файл main.c и тоже удалим вот этот глобальный массив
uint8_t RxBuf[2] = {0};
Вместо него добавим массивы приёмника и источника, также глобальный счётчик и флаг
1 2 3 4 5 |
/* USER CODE BEGIN PV */ uint16_t src_buf[1024] = {0}; uint16_t dst_buf[128] = {0}; uint16_t i; uint8_t fl=0; |
В функции main() также исправим имя локальной переменной
uint8_t n;
Удалим вот эту задержку
HAL_Delay(2000);
До вывода на индикатор большого числа наполним наш буфер декрементированными данными от 1024 до 1
1 2 3 4 5 6 |
Init_7219(); //fill src buffer for(i=0;i<1024;i++) { src_buf[i] = 1024-i; } |
И тут же мы вызываем функцию приёма и отправки данных
1 2 |
Number_7219(87654321); HAL_SPI_TransmitReceive_IT(&hspi1, (uint8_t*) src_buf, (uint8_t *)dst_buf, 128); |
Пока данные не придут с ведущего устройства, приём на SLAVE не начнётся по определению, так как тактируется шина с ведущего.
В бесконечном цикле пока всё удалим.
Давайте, наоборот сначала займёмся обработкой прерываний.
Добавим для этого функцию, в которой по полному окончанию приёма и передачи всех данных установим наш флаг
1 2 3 4 5 6 7 8 9 10 11 |
/* USER CODE BEGIN 4 */ void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi == &hspi1) { if(!hspi1.TxXferCount && !fl) { fl=1; } } } |
Вернёмся в бесконечный цикл функции main() и дождёмся взведенного флага, который будет гласить о том, что мы полностью обменялись порцией всех данных
1 2 |
/* USER CODE BEGIN 3 */ while(!fl) {} |
Сохраним значение самой первой ячейки приёмного буфера
1 2 |
while(!fl) {} i = dst_buf[0]; |
Это и будет значение счётчика, чтобы шло всё синхронно с ведущим устройством.
Отобразим данные из приёмника и источника на индикаторе
1 2 3 4 5 6 7 |
i = dst_buf[0]; for(n=0;n<128;n++) { NumberR_7219(dst_buf[n]); NumberL_7219(src_buf[n+i]); HAL_Delay(50); } |
Сбросим флаг, добавим к счётчику 128, обнулим его, если превышение, и дадим команду на приём новых данных
1 2 3 4 5 6 |
HAL_Delay(50); } fl=0; i+=128; if(i>1023) i=0; HAL_SPI_TransmitReceive_IT(&hspi1, (uint8_t*) (src_buf+i), (uint8_t *)dst_buf, 128); |
Соберём код, прошьём контроллер ведомого устройства, подключим ведущий от отдельного устройства, и наши устройства, если всё правильно мы написали начнут обмен данными
Посмотрим также наш обмен в программе логического анализа
Посмотрим начало обмена порциями данных
Также посмотрим окончание обмена
Таким образом, мы сегодня смогли обменяться информацией между двумя контроллерами STM32 по шине SPI, используя при этом механизм обработки прерываний от шины и возможности библиотеки HAL.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Исходный код для ведущего устройства (MASTER)
Исходный код для ведомого устройства (SLAVE)
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Индикатор светодиодный семиразрядный с драйвером MAX7219
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК (нажмите на картинку)
Спасибо
Сделал все по Вашему уроку. Программа не попадает в функцию HAL_SPI_TxRxCpltCallback. Прерывания включены на SPI. Да и чего бы ей вызываться если USE_HAL_SPI_REGISTER_CALLBACKS, прописанный в stm32l0xx_hal_spi.h (у меня контроллер на stm32l053c6t6) деактивирован. А именно в нем прописана строка
void (* TxRxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI TxRx Half Completed callback */
Где активировать USE_HAL_SPI_REGISTER_CALLBACKS?
Всё должно попадать. Начинайте идти от печки, из стартапа, там прописано имя функции в векторе, дальше ищите функцию и т.д.
Именно так и сделал — шел из стартапа и наткнулся на USE_HAL_SPI_REGISTER_CALLBACKS в stm32l0xx_hal_spi.с там TxRxHalfCpltCallback деактивирован т.к. весь макрос
#if (USE_HAL_SPI_REGISTER_CALLBACKS == 1U)
void (* TxCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Tx Completed callback */
void (* RxCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Rx Completed callback */
void (* TxRxCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI TxRx Completed callback */
void (* TxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Tx Half Completed callback */
void (* RxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Rx Half Completed callback */
void (* TxRxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI TxRx Half Completed callback */
void (* ErrorCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Error callback */
void (* AbortCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Abort callback */
void (* MspInitCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Msp Init callback */
void (* MspDeInitCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Msp DeInit callback */
#endif /* USE_HAL_SPI_REGISTER_CALLBACKS */
дективирован в структуре SPI_HandleTypeDef
а в заголовке файла stm32l0xx_hal_spi.с есть такая запись:
Callback registration:
(#) The compilation flag USE_HAL_SPI_REGISTER_CALLBACKS when set to 1U
allows the user to configure dynamically the driver callbacks.
Use Functions HAL_SPI_RegisterCallback() to register an interrupt callback.
Function HAL_SPI_RegisterCallback() allows to register following callbacks:
(+) TxCpltCallback : SPI Tx Completed callback
(+) RxCpltCallback : SPI Rx Completed callback
(+) TxRxCpltCallback : SPI TxRx Completed callback
(+) TxHalfCpltCallback : SPI Tx Half Completed callback
(+) RxHalfCpltCallback : SPI Rx Half Completed callback
(+) TxRxHalfCpltCallback : SPI TxRx Half Completed callback
(+) ErrorCallback : SPI Error callback
(+) AbortCpltCallback : SPI Abort callback
(+) MspInitCallback : SPI Msp Init callback
(+) MspDeInitCallback : SPI Msp DeInit callback
This function takes as parameters the HAL peripheral handle, the Callback ID
and a pointer to the user callback function.
Андрей. А трассировали код?
Вы точно попадаете? может условие не подходит?
Тоже столкнулся с этим, зачем-то решил прикрутить прерывание к маленьким пакетам.
Понизьте частоту SPI, обработчик не успевает за шиной.
У меня 2 байта заработали только на частоте 250 и ниже.
Может кому-то пригодится 🙂