Продолжаем работу с передачей данных по шине SPI между двумя контроллерами STM32F1. И на данном уроке мы попробуем воспользоваться механизмом прерываний, организованным в периферии SPI в данном контроллере.
Такую задачу мы с вами уже выполняли, только использовали мы при этом возможности других библиотек — HAL и LL. Чтобы получить ещё большую гибкость и контроль над передачей, мы теперь воспользуемся библиотекой CMSIS.
Как работать с прерываниями, мы знаем, каким образом они используются с модулем SPI, мы тоже знакомы, поэтому теоретической части у нас не будет, в связи с этим урок получится не такой скучный.
Как обычно, для того, чтобы попробовать, как работают прерывания в SPI контроллера STM32F1, мы будем передавать сразу большие порции данных с одного контроллера в другой, а также обратно.
Схема будет использоваться та же, что и в уроке 175
И займёмся мы сначала ведущим устройством, проект для которого мы сделаем из проекта того же урока с именем CMSIS_SPI_MASTER и назовём его CMSIS_SPI_MASTER_INT.
Подключим к ПК наше ведущее устройство, ведомое пока не подключаем.
Откроем наш проект в Keil и в файле main.c добавим два буфера, один для передачи, другой для приёма, а также несколько глобальных переменных для счёта и отслеживания событий
1 2 3 4 5 6 |
__IO uint32_t SysTick_CNT = 0; uint16_t src_buf[128] = {0}; uint16_t dst_buf[128] = {0}; uint16_t dst_cnt=0; uint32_t full_cnt=0; uint8_t fl=0; |
В функции SPI1_Init поднимем в самое начало функции включение тактирования порта
1 2 |
//SPI1 GPIO SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN); |
Включим глобальные прерывания от модуля SPI1
1 2 3 |
GPIO_CRL_CNF5_1 | GPIO_CRL_CNF6_0 | GPIO_CRL_CNF7_1 | GPIO_CRL_MODE5 | GPIO_CRL_MODE7); //SPI1 interrupt Init NVIC_EnableIRQ(SPI1_IRQn); |
С локальными прерываниями мы будем заниматься уже в функции main(), в которой мы сначала изменим имя локальной переменной
uint16_t i, n;
Удалим вызов макроса старта модуля
SPI1_ENABLE();
Заполним буфер передачи инкрементирующимися значениями
1 2 3 4 5 6 |
Init_7219(); //fill src buffer for(i=0;i<128;i++) { src_buf[i] = i; } |
После 2-секундной задержки опустим ножку выбора и запустим модуль
1 2 3 |
delay_ms(2000); CS1_SET(); SPI1_ENABLE(); |
Включим пока прерывания только по ошибке передачи
1 2 3 |
SPI1_ENABLE(); //Enable error interrupt SET_BIT(SPI1->CR2, SPI_CR2_ERRIE); |
Дальше работаем в бесконечном цикле, из которого пока удалим весь код.
Заполним буфер следующими значениями
1 2 3 4 5 6 7 |
while(1) { //fill src buffer for(i=0;i<128;i++) { src_buf[i] = full_cnt + i; } |
Затем передадим самое первое полуслово буфера
1 2 3 4 |
src_buf[i] = full_cnt + i; } while(!(READ_BIT(SPI1->SR, SPI_SR_TXE) == (SPI_SR_TXE))) {} WRITE_REG(SPI1->DR, full_cnt); |
Включим прерывания по заполнению приёмного и опустошению передающего буферов
1 2 3 4 5 |
WRITE_REG(SPI1->DR, full_cnt); //Enable Rx buffer not empty interrupt SET_BIT(SPI1->CR2, SPI_CR2_RXNEIE); //Enable Tx buffer empty interrupt SET_BIT(SPI1->CR2, SPI_CR2_TXEIE); |
Добавим в самом низу файла обработчик прерываний от модуля SPI1
1 2 3 4 5 |
//---------------------------------------------------------- void SPI1_IRQHandler(void) { } //---------------------------------------------------------- |
В теле данного обработчика добавим три вида условий, соответствующим флагам событий, обработку прерываний которых мы включили
1 2 3 4 5 6 7 8 9 10 11 12 |
void SPI1_IRQHandler(void) { if(READ_BIT(SPI1->SR, SPI_SR_RXNE) == (SPI_SR_RXNE)) { } else if(READ_BIT(SPI1->SR, SPI_SR_TXE) == (SPI_SR_TXE)) { } else if(READ_BIT(SPI1->SR, SPI_SR_OVR) == (SPI_SR_OVR)) { __NOP(); } |
Так как мы не будем писать никакие реакции на события ошибки передачи, то в тело данного условия мы добавили только инструкцию nop.
В случае, если мы попали в обработчик по событию опустошению буфера передачи, то проинкрементируем сначала позицию передачи в пакете
1 2 3 |
else if(READ_BIT(SPI1->SR, SPI_SR_TXE) == (SPI_SR_TXE)) { full_cnt++; |
Если мы достигли последней позиции, то отключим прерывания по опустошению буфера передачи и установим пользовательский флаг
1 2 3 4 5 6 7 |
full_cnt++; if(!(full_cnt%128)) { //Disable Tx buffer empty interrupt CLEAR_BIT(SPI1->CR2, SPI_CR2_TXEIE); fl=1; } |
А если ещё не достигли, то передадим следующее полуслово
1 2 3 4 5 6 |
fl=1; } else { WRITE_REG(SPI1->DR, src_buf[full_cnt%128]); } |
Теперь обработаем заполнение приёмного буфера.
В теле данного условия мы заберём принятое число из буфера приёма и запишем его в соответствующую позицию приёмного буфера
1 2 3 |
if(READ_BIT(SPI1->SR, SPI_SR_RXNE) == (SPI_SR_RXNE)) { dst_buf[dst_cnt] = READ_REG(SPI1->DR); |
Проинкрементируем приёмную позицию
1 2 |
dst_buf[dst_cnt] = READ_REG(SPI1->DR); dst_cnt++; |
Если достигли последней позиции, то запретим прерывания по заполнению приёмного буфера и поднимем ножку выбора
1 2 3 4 5 6 7 |
dst_cnt++; if(dst_cnt == 128) { //Disable Rx buffer not empty interrupt CLEAR_BIT(SPI1->CR2, SPI_CR2_RXNEIE); CS1_RESET(); } |
Вернёмся в бесконечный цикл функции main(), в котором дождёмся установки пользовательского флага и обнулим его
1 2 3 |
SET_BIT(SPI1->CR2, SPI_CR2_TXEIE); while(!fl) {} fl=0; |
Покажем принятые и переданные числа на индикаторе
1 2 3 4 5 6 7 |
fl=0; for(n=0;n<128;n++) { NumberL_7219(dst_buf[n]); NumberR_7219(src_buf[n]); delay_ms(50); } |
Подождём 1 секунду
1 2 3 |
delay_ms(50); } delay_ms(1000); |
Вообщем, передавать и принимать мы будем пакеты по 128 полуслов, таких пакетов у нас будет 64, числа в каждом следующем пакете будут инкрементироваться дальше и так до 8191, поэтому, если мы достигнем такого значения, то обнуляем счётчик и после этого передача продолжится, но числа будут опять идти с нуля
1 2 |
delay_ms(1000); if(full_cnt>=8191) full_cnt = 0; |
Обнулим счётчик позиций приёма и опустим ножку выбора
1 2 3 |
if(full_cnt>=8191) full_cnt = 0; dst_cnt = 0; CS1_SET(); |
Вот и весь код для ведущего устройства. Соберём проект и прошьём контроллер.
Индикатор начнёт показывать передаваемые числа, принимаемых пока нет, так как у нас ведомый даже не включен, поэтому слева будет 0
Казалось бы, почему мы в каждой итерации бесконечного цикла включаем прерывания по заполнению приёмного буфера, а нигде их потом не запрещаем, как в случае с буфером передачи? А потому что в самой первой итерации мы должны их включить именно здесь. Вот поэтому остальные итерации тоже страдают. Но а из-за того, что данное действие много времени не займёт, мы не стали проверять, что это именно первая итерация, заводить какой-то флаг, так что не факт, что на проверку уйдёт времени не меньше. Поэтому пока оставим так.
Отключим от ПК программатор ведущего устройства, запитаем его от независимого источника, а к ПК подключим ведомое.
Для ведомого устройства проект сделаем из проекта того же урока 175 с именем CMSIS_SPI_SLAVE и назовём его, соответственно, CMSIS_SPI_SLAVE_INT.
Откроем данный проект в Keil и также добавим 2 буфера и несколько глобальных переменных
1 2 3 4 5 |
__IO uint32_t SysTick_CNT = 0; uint16_t src_buf[128] = {0}; uint16_t dst_buf[128] = {0}; uint8_t cnt, fl=0; uint32_t full_cnt=0; |
В функции SPI1_Init также включим глобальные прерывания
1 2 3 |
GPIO_CRL_MODE4 | GPIO_CRL_MODE5 | GPIO_CRL_MODE6); //SPI1 interrupt Init NVIC_EnableIRQ(SPI1_IRQn); |
В функции main() также изменим имя локальной переменной
uint16_t i, n;
Инициализацию локальных переменных удалим
i=0; n=0;
Включим сразу все 3 вида локальных прерываний
1 2 3 4 5 6 7 |
delay_ms(100); //Enable Rx buffer not empty interrupt SET_BIT(SPI1->CR2, SPI_CR2_RXNEIE); //Enable Tx buffer empty interrupt SET_BIT(SPI1->CR2, SPI_CR2_TXEIE); //Enable error interrupt SET_BIT(SPI1->CR2, SPI_CR2_ERRIE); |
Также пока удалим вызов макроса старта периферии
SPI1_ENABLE();
Удалим задержку
delay_ms(2000);
Запустим модуль SPI1
1 2 |
Number_7219(87654321); SPI1_ENABLE(); |
Из бесконечного цикла пока всё удалим
Добавим обработчик прерываний от модуля SPI1 в самом низу файла сразу с кодом, так как код подобен коду ведущего устройства
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
//---------------------------------------------------------- void SPI1_IRQHandler(void) { if(READ_BIT(SPI1->SR, SPI_SR_RXNE) == (SPI_SR_RXNE)) { dst_buf[full_cnt-1] = READ_REG(SPI1->DR); } else if(READ_BIT(SPI1->SR, SPI_SR_TXE) == (SPI_SR_TXE)) { full_cnt++; if(full_cnt>127) { //Disable Tx buffer empty interrupt CLEAR_BIT(SPI1->CR2, SPI_CR2_TXEIE); fl=1; } else { WRITE_REG(SPI1->DR, src_buf[full_cnt]); } } else if(READ_BIT(SPI1->SR, SPI_SR_OVR) == (SPI_SR_OVR)) { __NOP(); } } //---------------------------------------------------------- |
Также добавим вот такой код в бесконечный цикл функции main()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
while(1) { while(!fl) {} fl=0; //fill src buffer for(i=0;i<128;i++) { src_buf[i] = 8192 - dst_buf[i]; } for(n=0;n<128;n++) { NumberR_7219(dst_buf[n]); NumberL_7219(src_buf[n]); delay_ms(50); } full_cnt = 0; WRITE_REG(SPI1->DR, src_buf[0]); //Enable Tx buffer empty interrupt SET_BIT(SPI1->CR2, SPI_CR2_TXEIE); |
Здесь мы сначала ждём установки пользовательского флага, обнуляем его, затем готовим буфер значениями, вычтенными из 8192, чтобы отличался от приёмного, потом отображаем принятые и переданные числа на индикаторе, обнуляем счётчик, отправляем в буфер передачи первое число пакета и включаем прерывания по событию опустошению буфера передачи.
Соберём код, прошьём контроллер.
Посмотрим, как идёт обмен, по показаниям индикаторов
Показания приёмного индикатора на ведущем будут всегда отставать на 128 от ведомого, так как мы там отображаем предыдущий пакет.
Посмотрим теперь обмен в программе логического анализа
Мы видим, что передаваемые числа следуют в пакетах друг за другом непрерывно.
А здесь мы видим, что у нас ничего не пропускается и не выпадает
А вот здесь мы видим, что ножке по окончанию пакета теперь поднимается вовремя, хотя мы не использовали никаких задержек, оказывается, её нужно было поднимать в теле условия по событию приёма, а не передачи
Таким образом, в данном уроке мы произвели обмен между двумя устройствами на контроллерах STM32F1 посредством шины SPI, используя возможности библиотеки CMSIS, а также механизм прерываний по событиям передачи и приёма по интерфейсу SPI, что позволило нам добиться лучшего контроля за обменом данных по данному интерфейсу.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Исходный код для ведущего устройства (MASTER)
Исходный код для ведомого устройства (SLAVE)
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Индикатор светодиодный семиразрядный с драйвером MAX7219
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Добавить комментарий