Сегодня мы попробуем с помощью библиотеки CMSIS поработать с шиной SPI (Serial peripheral interface) в контроллере STM32F1. С данной шиной мы работаем постоянно, но реализация её с помощью функционала библиотеки CMSIS нами ещё не рассматривалась. Поэтому изучать подробно работу шины SPI, её протокол, режимы её работы, мы не будем. Также аппаратная реализация шины SPI в контроллере STM32F1 нами также рассматривалась при работе с ней с использованием библиотеки LL, начиная с урока 152.
Подключить по данной шине к контроллеру STM32 мы попробуем восьмиразрядный семисегментный индикатор, динамическая индикация которого реализована на микросхеме-драйвере MAX7219. Данная микросхема общается с контроллером именно по шине SPI. Мы неоднократно уже работали с данной микросхемой с использованием других контроллеров: в уроке 28 мы подключали её к микроконтроллеру семейства AVR, в уроке 16 — к микроконтроллеру PIC. К контроллеру STM32 мы её также неоднократно подключали, в частности в уроке 26. В том же самом уроке 152 мы также работали с ней с использованием библиотеки LL. Теперь же попробуем реализовать отправку данных для индикации этой микросхеме с помощью функционала библиотеки CMSIS.
Схема нашего урока будет практически такая же, как и в уроке по LL, все ножки остались те же, только макетную плату я использовать не стал ввиду ненадёжности её соединений и соединил плату с индикатором, а также с логическим анализатором
И, раз уж мы знаем устройство и тонкости работы микросхемы, а также все необходимые регистры и их биты в модуле SPI МК STM32F1, а также как их использовать, то можно будет сразу же приступить и к нашему проекту. Единственной проблемой будет то, что, как и другую периферию, шину SPI инициализировать нам придётся вручную в отличие от использования библиотеки LL, когда данную прерогативу на себя брал проектогенератор Cube MX. Но, тем не менее, так как инициализацию, которая была сгенерирована автоматически, мы полностью исследовали в коде, то нам будет это не так сложно.
Проект был выполнен из проекта урока 172 с именем CMSIS_I2C_EEPROM и назван был CMSIS_LED7219.
Откроем наш новый проект в Keil, удалим файл led.c из дерева проекта, а затем физически из папки проекта, также из папки удалим файл led.h.
Из проекта урока 164 с именем LL_USART_DMA скопируем в соответствующие папки нашего нового проекта файлы max7219.h и max7219.c и подключим файл max7219.c к дереву проекта.
Откроем файл main.c и исправим подключение файла led.h на max7219.h
#include "max7219.h"
Из файла main.c удалим вот эти макросы
#define I2C_OWNADDRESS1_7BIT 0x00004000U
#define I2C_MODE_I2C 0x00000000U
#define SLAVE_OWN_ADDRESS 0xA0
#define I2C_REQUEST_WRITE 0x00
#define I2C_REQUEST_READ 0x01
Эти макросы, в принципе, тоже можно удалить
#define TIM_EnableIT_UPDATE(TIMx) SET_BIT(TIMx->DIER, TIM_DIER_UIE)
#define TIM_EnableCounter(TIMx) SET_BIT(TIMx->CR1, TIM_CR1_CEN)
#define TIM_DisableCounter(TIMx) CLEAR_BIT(TIMx->CR1, TIM_CR1_CEN)
Вот эти объявления также удалим
__IO uint8_t tim2_count = 0;
extern uint8_t R1,R2,R3,R4;
extern uint16_t num_gl;
uint8_t rd_value[20] = {0};
uint8_t wr_value[20] = {0x14,0x13,0x12,0x11,0x10,
0x0F,0x0E,0x0D,0x0C,0x0B,
0x0A,0x09,0x08,0x07,0x06,
0x05,0x04,0x03,0x02,0x01};
Функции AT24C_WriteBytes и AT24C_ReadBytes, TIM2_Init, I2C_Init и TIM2_IRQHandler удалим вместе с телами.
Из функции main() удалим вызов функции инициализации таймера
TIM2_Init();
Также удалим вот этот участок кода
I2C_Init();
TIM_EnableIT_UPDATE(TIM2);
TIM_EnableCounter(TIM2);
AT24C_WriteBytes (0x008A, wr_value, 20);
//AT24C_ReadBytes (0x008A, rd_value, 20);
for(i=0;i<20;i++)
{
ledprint(wr_value[i]);
//ledprint(rd_value[i]);
delay_ms(1000);
}
Также в теле функции GPIO_Init удалим весь код и напишем инициализацию только ножки PA4, которая выполняет функции программного Chip Select нашей шины SPI, настроив её на выход
1 2 3 4 5 |
static void GPIO_Init(void) { SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN); MODIFY_REG(GPIOA->CRL, GPIO_CRL_CNF4_1 | GPIO_CRL_CNF4_0 | GPIO_CRL_MODE4_1, GPIO_CRL_MODE4_0); } |
Выше функции main() добавим функцию инициализации SPI
1 2 3 4 5 |
//---------------------------------------------------------- static void SPI_Init(void) { } //---------------------------------------------------------- |
Настроим ножки, участвующие в SPI, на альтернативный режим
1 2 3 4 5 6 |
static void SPI_Init(void) { //SPI1 GPIO MODIFY_REG(GPIOA->CRL, GPIO_CRL_CNF5_0 | GPIO_CRL_CNF6_1 | GPIO_CRL_CNF7_0 | GPIO_CRL_MODE6, GPIO_CRL_CNF5_1 | GPIO_CRL_CNF6_0 | GPIO_CRL_CNF7_1 | GPIO_CRL_MODE5 | GPIO_CRL_MODE7); SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN); |
Хоть ножка PA6, которая настроена как MISO, и не будет участвовать в передачи данных, так как мы будем работать с шиной только на передачу, мы её все равно настроили по соображениям универсальности функции.
Включим тактирование модуля SPI
1 2 3 4 5 |
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN); //RCC peripheral clock enabling SET_BIT(RCC->APB2ENR, RCC_APB2ENR_SPI1EN); //Delay after an RCC peripheral clock enabling tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_SPI1EN); |
Настроим периферию SPI на работу в 8-разрядном режиме со скоростью передачи данных 4,5 мбпс, а также режим передачи данных ей установим 0:0
1 2 3 4 5 6 |
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_SPI1EN); //8 bit 0:0 MODIFY_REG(SPI1->CR1, SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_CRCEN | SPI_CR1_CRCNEXT | \ SPI_CR1_DFF | SPI_CR1_RXONLY | SPI_CR1_LSBFIRST | \ SPI_CR1_BR_2 | SPI_CR1_CPOL | SPI_CR1_CPHA , \ SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_1 | SPI_CR1_BR_0 | SPI_CR1_MSTR); |
Очистим все младшие биты регистра CR2
1 2 |
SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_1 | SPI_CR1_BR_0 | SPI_CR1_MSTR); CLEAR_BIT(SPI1->CR2, 0x00FF); |
Вот и вся инициализация SPI. Вызовем нашу функцию в main()
1 2 |
GPIO_Init(); SPI_Init(); |
В файле max7219.h немного удалим подключение вот этих заголовочных файлов
#include «stm32f1xx_ll_gpio.h»
#include «stm32f1xx_ll_spi.h»
#include «main.h»
Вместо них подключим вот этот
1 |
#include "stm32f10x.h" |
Перейдём в файл max7219.c и исправим там сначала макросы управления состоянием ножки CS
#define cs_set() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR4)
#define cs_reset() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR4)
В теле функции Send_7219 также внесём некоторые исправления в свете требований библиотеки CMSIS, очистив его сначала полностью
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void Send_7219 (uint8_t rg, uint8_t dt) { cs_set(); //8 bit while(!(READ_BIT(SPI1->SR, SPI_SR_TXE) == (SPI_SR_TXE))) {} SPI1->DR = (uint16_t)rg; while(!(READ_BIT(SPI1->SR, SPI_SR_RXNE) == (SPI_SR_RXNE))) {} (void) SPI1->DR; while(!(READ_BIT(SPI1->SR, SPI_SR_TXE) == (SPI_SR_TXE))) {} SPI1->DR = (uint16_t)dt; while(!(READ_BIT(SPI1->SR, SPI_SR_RXNE) == (SPI_SR_RXNE))) {} (void) SPI1->DR; cs_reset(); |
Возвращаемся в файл main.c и добавим там макросы управления ножкой CS, а также макрос запуска модуля SPI1, так как в функции инициализации мы его только настраиваем, но не запускаем
1 2 3 4 5 6 |
#define SYSCLOCK 72000000U //---------------------------------------------------------- #define CS_RESET() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR4) #define CS_SET() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR4) #define SPI1_ENABLE() SET_BIT(SPI1->CR1, SPI_CR1_SPE); //---------------------------------------------------------- |
В функции main() после небольшой задержки опустим и поднимем CS, запустим модуль SPI, вызовем функцию инициализации драйвера индикатора и отобразим там длинное число на весь индикатор
1 2 3 4 5 6 7 8 |
SPI_Init(); delay_ms(100); CS_RESET(); CS_SET(); SPI1_ENABLE(); Init_7219(); Clear_7219(); Number_7219(87654321); |
Соберём код, прошьём контроллер и посмотрим, как работает наш индикатор
Всё отлично!
Также посмотрим обмен данными в программе логического анализа (думаю, как там настраивать шину SPI в различных режимах, показывать не нужно)
Наша шина прекрасно работает.
Теперь запустим наши счётчики. сначала подождём 2 секунды
1 2 |
Number_7219(87654321); delay_ms(2000); |
Затем в бесконечном цикле вставим вот такой код, который будет инкрементировать числа в правой половине индикатора и декрементировать в левой
1 2 3 4 5 6 7 |
while(1) { i++; if(i>9999) i=0; NumberR_7219(i); NumberL_7219(9999-i); delay_ms(100); |
Проверим, как это работает
Отлично!
Давайте теперь попробуем поработать с шиной в 16-разрядном режиме, так как у нас всегда передаются по 2 8-битных значения и нам нет смысла передавать их отдельно. Благодаря этому временной промежуток между передаваемыми данными должен будет исчезнуть.
В функции SPI_Init закомментируем участок кода, отвечающий за настройку модуля для работы в 8-разрядный режиме
1 2 3 4 5 |
//8 bit 0:0 // MODIFY_REG(SPI1->CR1, SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_CRCEN | SPI_CR1_CRCNEXT | \ // SPI_CR1_DFF | SPI_CR1_RXONLY | SPI_CR1_LSBFIRST | \ // SPI_CR1_BR_2 | SPI_CR1_CPOL | SPI_CR1_CPHA , \ // SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_1 | SPI_CR1_BR_0 | SPI_CR1_MSTR); |
И теперь настроим модуль в 16-разрядный режим
1 2 3 4 5 6 |
// SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_1 | SPI_CR1_BR_0 | SPI_CR1_MSTR); //16 bit 0:0 MODIFY_REG(SPI1->CR1, SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_CRCEN | SPI_CR1_CRCNEXT | \ SPI_CR1_RXONLY | SPI_CR1_LSBFIRST | \ SPI_CR1_BR_2 | SPI_CR1_CPOL | SPI_CR1_CPHA , \ SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_1 | SPI_CR1_DFF | SPI_CR1_BR_0 | SPI_CR1_MSTR); |
Также в файле max7219.c в функции Send_7219 тоже закомментируем обмен в 8-разрядном режиме
1 2 3 4 5 6 7 8 9 10 11 |
//8 bit /* while(!(READ_BIT(SPI1->SR, SPI_SR_TXE) == (SPI_SR_TXE))) {} SPI1->DR = (uint16_t)rg; while(!(READ_BIT(SPI1->SR, SPI_SR_RXNE) == (SPI_SR_RXNE))) {} (void) SPI1->DR; while(!(READ_BIT(SPI1->SR, SPI_SR_TXE) == (SPI_SR_TXE))) {} SPI1->DR = (uint16_t)dt; while(!(READ_BIT(SPI1->SR, SPI_SR_RXNE) == (SPI_SR_RXNE))) {} (void) SPI1->DR; */ |
И добавим код обмена в 16-разрядном режиме
1 2 3 4 5 6 7 |
(void) SPI1->DR; */ //16 bit while(!(READ_BIT(SPI1->SR, SPI_SR_TXE) == (SPI_SR_TXE))) {} SPI1->DR = (uint16_t)rg << 8 | dt; while(!(READ_BIT(SPI1->SR, SPI_SR_RXNE) == (SPI_SR_RXNE))) {} (void) SPI1->DR; |
Соберём код, прошьём контроллер.
У нас по-прежнему индикатор отлично работает.
Посмотрим результат в программе логического анализа, предварительно настроив шину также в 16-битный режим (хотя это не обязательно, будет всё также, только промежутки уйдут)
Перезагрузим контроллер и посмотрим, как теперь обстоят дела с передачей данных по шине
Отлично! Промежутки исчезли.
Теперь давайте попробуем настроить SPI на режим работы 1:1, так как в таком режиме наш драйвер индикатора тоже умеет работать.
Для этого в функции SPI_Init теперь закомментируем участок кода, отвечающий за настройку модуля для работы в 16-разрядный режиме в режиме 0:0, а добавим настройку 16-разрядного режима в режиме 1:1
1 2 3 4 5 6 7 8 9 10 |
//16 bit 0:0 // MODIFY_REG(SPI1->CR1, SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_CRCEN | SPI_CR1_CRCNEXT | \ // SPI_CR1_RXONLY | SPI_CR1_LSBFIRST | \ // SPI_CR1_BR_2 | SPI_CR1_CPOL | SPI_CR1_CPHA , \ // SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_1 | SPI_CR1_DFF | SPI_CR1_BR_0 | SPI_CR1_MSTR); //16 bit 1:1 MODIFY_REG(SPI1->CR1, SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_CRCEN | SPI_CR1_CRCNEXT | \ SPI_CR1_RXONLY | SPI_CR1_LSBFIRST | SPI_CR1_BR_2, \ SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_1 | SPI_CR1_DFF | SPI_CR1_BR_0 | \ SPI_CR1_MSTR | SPI_CR1_CPOL | SPI_CR1_CPHA); |
В функции передачи данных в драйвер ничего менять не надо.
Соберём код и прошьём контроллер. У нас также всё прекрасно работает.
Но если мы ничего не перенастроим в программе логического анализа, у нас обмен не распознается
Поэтому отредактируем настройки шины
Сохраним их, и у нас всё распознается, даже не надо заново сканировать шину
Таким образом, на данном уроке мы научились работать с шиной SPI с использованием библиотеки CMSIS в режиме MASTER, что, благодаря нашим ранее накопленным знаниям, помогло нам реализовать работу с шиной без особого труда.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Индикатор светодиодный семиразрядный с драйвером MAX7219
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий