В предыдущей части занятия мы познакомились с аппаратной реализацией шины SPI в контроллере STM32F1 и настроили проект урока.
И по нашей славной традиции посмотрим, каким образом происходит у нас инициализация шины SPI.
Для этого зайдём в функцию MX_SPI1_Init и посмотрим, что же там происходит.
Сначала, как обычно, объявляются структуры, включается тактирование на шины и настраиваются ножки портов.
Затем заполняется определёнными значениями из наших настроек структура шины SPI_InitStruct.
Далее идёт вызов LL_SPI_Init, в которую мы зайдём и посмотрим код там.
Там сначала, как всегда, проверяется наличие параметров, а затем при условии, что шина у нас ещё не включена, заполняется управляющий регистр CR1. В нём сначала с помощью маски SPI_CR1_CLEAR_MASK обнуляются все биты, кроме SPE, а затем эти биты включаются или не включаются в зависимости от настроек в параметрах структуры
1 2 3 4 5 6 |
MODIFY_REG(SPIx->CR1, SPI_CR1_CLEAR_MASK, SPI_InitStruct->TransferDirection | SPI_InitStruct->Mode | SPI_InitStruct->DataWidth | SPI_InitStruct->ClockPolarity | SPI_InitStruct->ClockPhase | SPI_InitStruct->NSS | SPI_InitStruct->BaudRate | SPI_InitStruct->BitOrder | SPI_InitStruct->CRCCalculation); |
В результате наших настроек у нас включатся биты SSM, SSI, а поле BR примет двоичное значение 011, что означает деление частоты на 16, как мы и настраивали.
Далее идёт инициализация регистра CR2
1 |
MODIFY_REG(SPIx->CR2, SPI_CR2_SSOE, (SPI_InitStruct->NSS >> 16U)); |
У нас не включится ни один бит в данном регистре, так как прерывания мы никакие не включали, и NSS у нас программный.
Настройка CRC пропускается, так как мы этот механизм не задействовали.
Вот, в принципе, и вся инициализация шины. То есть шина у нас настроена, но ещё не включена, так как бит SPE в регистре CR1 сброшен. Поэтому нам затем надо будет не забыть его включить.
Уберём сначала все глобальные макросы в файле main.c.
Затем добавим локальную переменную для счёта в функции main()
1 2 |
/* USER CODE BEGIN 1 */ uint16_t i; |
Также удалим инициализацию ножек
LED1_OFF();LED2_OFF();LED3_OFF();LED4_OFF();LED5_OFF();
LED6_OFF();LED7_OFF();LED8_OFF();LED9_OFF();LED10_OFF();
Удалим ещё весь пользовательский код из бесконечного цикла.
Обнулим переменную, немножко подождём, дёрнем ножкой CS, и запустим нашу шину SPI
1 2 3 4 5 6 |
/* USER CODE BEGIN 2 */ i=0; LL_mDelay(100); LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_4); LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_4); LL_SPI_Enable(SPI1); |
Далее нам потребуется библиотека для индикатора. Чтобы нам ничего лишнего заново не сочинять, то мы возьмём файлы max7219.c и max7219.h из проекта урока 115 по NRF для первого приёмника с именем NRF24_RX_00 и скопируем их в подобающие папки нашего проекта.
Подключим файл max7219.c к нашему проекту и подключим нашу библиотеку в файле main.c
1 2 |
/* USER CODE BEGIN Includes */ #include "max7219.h" |
В функции main() вызовем сразу инициализацию нашего дисплея
1 2 |
LL_SPI_Enable(SPI1); Init_7219(); |
Перейдём в файл max7219.h и удалим подключение файла библиотеки HAL
#include «stm32f1xx_hal.h»
Вместо подключенного файла подключим требуемые файлы библиотеки LL
1 2 |
#include "stm32f1xx_ll_gpio.h" #include "stm32f1xx_ll_spi.h" |
Затем перейдём в файл и удалим буфер, а также подключение структуры шины
uint8_t aTxBuf[1]={0};
extern SPI_HandleTypeDef hspi2;
Следующие директивы также приведём в подобающий вид
#define cs_set() LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_4)
#define cs_reset() LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_4)
В теле функции Send_7219 удалим код передачи данных
aTxBuf[0]=rg;
HAL_SPI_Transmit (&hspi2, (uint8_t*)aTxBuf, 1, 5000);
aTxBuf[0]=dt;
HAL_SPI_Transmit (&hspi2, (uint8_t*)aTxBuf, 1, 5000);
Вместо этого мы сначала дождёмся готовности шины путём проверки установки флага TXE в регистре SR
1 2 |
cs_set(); while(!LL_SPI_IsActiveFlag_TXE(SPI1)) {} |
То есть мы будем крутиться в цикле постоянно, пока флаг не установится.
При установке флага мы проваливаемся по коду вниз и передадим сначала адрес регистра микросхемы
1 2 |
while(!LL_SPI_IsActiveFlag_TXE(SPI1)) {} LL_SPI_TransmitData8 (SPI1, rg); |
Далее нам аналогично нужно дождаться установки флага RXNE, который гласит, что приёмный буфер у нас заполнился, так как шина SPI у нас кольцевая и одновременно с передачей при наличии устройства на шине обязательно будет и приём
1 2 |
LL_SPI_TransmitData8 (SPI1, rg); while(!LL_SPI_IsActiveFlag_RXNE(SPI1)) {} |
Читаем регистр DR вникуда (на самом деле он прочитается в регистр АЛУ r1)
1 2 |
while(!LL_SPI_IsActiveFlag_RXNE(SPI1)) {} (void) SPI1->DR; |
Аналогичным образом, как и адрес регистра микросхемы, передадим данные в этот регистр
1 2 3 4 5 |
(void) SPI1->DR; while(!LL_SPI_IsActiveFlag_TXE(SPI1)) {} LL_SPI_TransmitData8 (SPI1, dt); while(!LL_SPI_IsActiveFlag_RXNE(SPI1)) {} (void) SPI1->DR; |
Соберём код, прошьём контроллер и посмотрим результат нашего труда пока в программе логического анализа, так как мы ещё ничего не выводили на индикатор.
В программе настроим шину SPI следующим образом
Триггер установим на ножку CS (хотя можно и на любую, также характер изменения сигнала неважен, так как все равно диаграмма выводится с запасом)
Запустим анализ и перезагрузим контроллер. Результат будет вот такой
Наша шина отлично работает.
Теперь идём в файл main.c и в функции main() сначала очистим дисплей, а затем попробуем вывести какое-нибудь длинное число
1 2 3 |
Init_7219(); Clear_7219(); Number_7219(87654321); |
Соберём код, прошьём контроллер и посмотрим результат теперь уже не в программе, а на настоящем индикаторе
Вернёмся в файл max7219.c и немного подправим функцию вывода числа в левую половину индикатора NumberL_7219, чтобы не выводились лидирующие нули при малых значениях
1 2 3 4 |
uint8_t i=4; if(n<1000) Send_7219(8,0xF);//символ пустоты if(n<100) Send_7219(7,0xF);//символ пустоты if(n<10) Send_7219(6,0xF);//символ пустоты |
Добавим аналогичную функцию вывода числа в правую половину индикатора
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//------------------------------------------------------- void NumberR_7219 (volatile int n) { uint8_t ng=0;//переменная для минуса if(n<0) { ng=1; n*=-1; } uint8_t i=0; if(n<1000) Send_7219(4,0xF);//символ пустоты if(n<100) Send_7219(3,0xF);//символ пустоты if(n<10) Send_7219(2,0xF);//символ пустоты do { Send_7219(++i,n%10);//символ цифры n/=10; } while(n); if(ng) { Send_7219(i+1,0x0A);//символ - } } //------------------------------------------------------- |
Создадим для данной функции прототип в заголовочном файле, перейдём в файл main.c в функцию main() и после вывода цифры на экран подождём 2 секунды. Дисплей нам очищать не надо, так как мы всё лишнее теперь гасим в самих функциях вывода чисел в тетрады индикатора
1 2 |
Number_7219(87654321); LL_mDelay(2000); |
А в бесконечном цикле функции main() добавим код, который будет в одной половине наращивать счёт, а в другой, наоборот, убавлять результат
1 2 3 4 5 6 |
/* USER CODE BEGIN 3 */ i++; if(i>9999) i=0; NumberR_7219(i); NumberL_7219(9999-i); LL_mDelay(100); |
Соберём код, прошьём контроллер и посмотрим результат работы кода на индикаторе
Всё отлично!
Казалось бы, пора уже и закругляться, но вы же знаете, я ж без бонусов не могу никак.
Меня постоянно напрягал промежуток времени между передачами старшего и младшего байта и я этот вопрос урегулировал следующим образом.
Заходим в Cube MX и выставим теперь разрядность передаваемых (соответственно, и принимаемых) данных 16 бит
Сгенерируем проект, откроем его в Keil вернёмся в файл max7219.c и в функции Send_7219 закомментируем код передачи данных (теперь уже для истории)
1 2 3 4 5 6 7 8 9 10 |
/* while(!LL_SPI_IsActiveFlag_TXE(SPI1)) {} LL_SPI_TransmitData8 (SPI1, rg); while(!LL_SPI_IsActiveFlag_RXNE(SPI1)) {} (void) SPI1->DR; while(!LL_SPI_IsActiveFlag_TXE(SPI1)) {} LL_SPI_TransmitData8 (SPI1, dt); while(!LL_SPI_IsActiveFlag_RXNE(SPI1)) {} (void) SPI1->DR; */ |
А вместо него у нас теперь будет передача 16-битного числа в сразу в один приём
1 2 3 4 5 |
*/ while(!LL_SPI_IsActiveFlag_TXE(SPI1)) {} LL_SPI_TransmitData16 (SPI1, (uint16_t)rg << 8 | dt); while(!LL_SPI_IsActiveFlag_RXNE(SPI1)) {} (void) SPI1->DR; |
Если мы посмотрим в отладке регистр CR1 шины, то увидим, что кроме тех битов, которые у нас устанавливались и ранее, у нас установился также бит DFF.
Соберём код, прошьём контроллер и увидим на индикаторе, что у нас также всё работает, как и ранее.
Если мы теперь отследим процесс передачи данных в программе логического анализа, то у нас там уже не будет наших промежутков между переданными байтами
Вот так мы оптимизировали и передачу данных. Попробуем ещё передать данные несколько в другом режиме работы шины. Сейчас мы передаём в режиме SPI 0:0, попробуем передать в режиме SPI 1:1. Для этого закроем проект в Keil, и в Cube MX в настройках шины изменим полярность и фазу
Сгенерируем проект, откроем его в Keil, соберём проект, прошьём контроллер и увидим, что у нас также будет всё отлично работать.
Только вот программа логического анализа нас не поймёт
А всё потому, что в ней также надо настроить режим 1:1
Теперь после сохранения у нас будет всё снова прекрасно определяться
Таким образом, на данном уроке мы научились работать с шиной SPI при помощи возможностей библиотеки LL, также мы подробно изучили её аппаратную реализацию в контроллере STM32F1.
Благодарю всех за внимание!
Предыдущая часть Программирование МК STM32 Следующий урок
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Индикатор светодиодный семиразрядный с драйвером MAX7219
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК (нажмите на картинку)
А в SW4STM32 не получается бонуса. После установки DATA SIZE 16 Bits генерируем проект, собираем и файл .elf меняет паука на треугольник и становится в 10 раз меньше. Отладчик не запускается, говорит Error in final launch sequence
Failed to execute MI command:
load D:\\WorkCube\\Indicator1\\Debug\\Indicator1.elf
Error message from debugger back end:Load failed