Настройка SPI на STM32: полное руководство для разработчиков

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Инженеры и разработчики встраиваемых систем, работающие с микроконтроллерами STM32
  • Студенты и специалисты, обучающиеся программированию на базе STM32 и интерфейса SPI
  • Профессионалы, стремящиеся улучшить навыки в проектировании аппаратных и программных решений для встраиваемых систем

    Интерфейс SPI — мощный инструмент для любого разработчика встраиваемых систем, а правильная его настройка на микроконтроллерах STM32 открывает двери к созданию эффективных технических решений. За 12 лет работы с STM32 я наблюдал, как многие инженеры спотыкаются на элементарных принципах конфигурации SPI, теряя драгоценное время на отладку. Эта статья — квинтэссенция практического опыта, которая избавит вас от типичных ошибок и проведет через все этапы: от понимания архитектуры до реализации полноценных проектов. 🚀

Изучив тонкости программирования микроконтроллеров STM32, вы можете значительно расширить свои карьерные перспективы. Такие навыки отлично дополняют компетенции веб-разработчика! Если вы стремитесь освоить фундаментальные принципы программирования, Обучение Python-разработке от Skypro станет идеальной стартовой площадкой. Python — универсальный язык, который позволит вам легко переходить между разными областями программирования, от веб-приложений до систем управления устройствами на STM32.

Основы SPI: архитектура и принципы работы на STM32

SPI (Serial Peripheral Interface) — синхронный последовательный интерфейс передачи данных, работающий в полнодуплексном режиме. Его четырехпроводная архитектура обеспечивает эффективный обмен данными между микроконтроллером и периферийными устройствами на высоких скоростях. В мире встраиваемых систем SPI занимает ключевое место благодаря своей простоте и производительности.

Интерфейс SPI использует следующие линии связи:

  • MOSI (Master Out Slave In) — линия передачи данных от ведущего устройства к ведомому
  • MISO (Master In Slave Out) — линия передачи данных от ведомого устройства к ведущему
  • SCK (Serial Clock) — тактовый сигнал, генерируемый ведущим устройством
  • NSS/CS (Slave Select) — линия выбора ведомого устройства (активный низкий уровень)

В микроконтроллерах STM32 модули SPI отличаются гибкостью настройки и высокой производительностью. Контроллеры серии STM32F4, например, поддерживают скорость передачи данных до 42 Мбит/с, что делает их идеальным выбором для высокоскоростных приложений.

Алексей Корнеев, ведущий инженер-разработчик встраиваемых систем

Помню свой первый проект с использованием SPI на STM32F103. Мне требовалось подключить дисплей ILI9341 и SD-карту через один интерфейс SPI. Тогда я столкнулся с классической проблемой: при переключении между устройствами возникали сбои в передаче данных. Причина оказалась банальной — я неправильно управлял линиями CS, не соблюдая временные интервалы между транзакциями. После изучения осциллограммы сигналов и корректной настройки задержек система заработала безупречно. Этот опыт научил меня внимательно относиться к временным характеристикам SPI и правильно планировать мультиплексирование устройств на одной шине.

Микроконтроллеры STM32 предлагают различные режимы работы SPI, которые определяются комбинацией параметров CPOL (полярность тактового сигнала) и CPHA (фаза тактового сигнала):

Режим SPI CPOL CPHA Состояние SCK в покое Считывание данных
Режим 0 0 0 Низкий уровень По переднему фронту
Режим 1 0 1 Низкий уровень По заднему фронту
Режим 2 1 0 Высокий уровень По заднему фронту
Режим 3 1 1 Высокий уровень По переднему фронту

Выбор правильного режима критически важен для совместимости с подключаемыми устройствами. Большинство SPI-устройств работают в режиме 0 или режиме 3, но всегда следует обращаться к документации конкретного компонента для уточнения требуемых настроек.

В архитектуре STM32 каждый модуль SPI представляет собой периферийное устройство, подключенное к шине APB (Advanced Peripheral Bus) или AHB (Advanced High-performance Bus), в зависимости от серии микроконтроллера. Это обеспечивает быстрый доступ к регистрам SPI и эффективную обработку данных.

Пошаговый план для смены профессии

Настройка оборудования SPI в проектах STM32

Грамотная аппаратная конфигурация — фундамент стабильной работы интерфейса SPI. Процесс начинается с выбора подходящих выводов микроконтроллера и правильного их соединения с периферийными устройствами. 📌

Первым шагом является определение доступных модулей SPI на выбранном микроконтроллере STM32. Большинство контроллеров семейства имеют от 2 до 6 интерфейсов SPI, каждый из которых может быть выведен на определенные группы пинов с помощью системы альтернативных функций.

Выбор пинов для SPI на STM32 осуществляется с учетом следующих факторов:

  • Доступность конкретных выводов в выбранном корпусе микроконтроллера
  • Альтернативные функции пинов (один и тот же сигнал SPI может быть выведен на разные пины)
  • Уровень нагрузочной способности выводов (некоторые периферийные устройства требуют повышенного тока)
  • Физическое расположение выводов на плате для минимизации длины соединений

При проектировании схемы подключения необходимо учитывать особенности работы SPI в разных конфигурациях:

Конфигурация Описание Применение Особенности подключения
Одно ведомое устройство Базовая конфигурация: один мастер, один слейв Простые системы с одним периферийным устройством Прямое подключение всех линий
Несколько ведомых устройств с аппаратным SS Каждое устройство имеет отдельную линию SS Системы с несколькими устройствами и необходимостью быстрого переключения Отдельный пин GPIO для каждой линии SS
Цепочка устройств (Daisy Chain) Устройства соединены последовательно: MISO одного подключен к MOSI следующего Регистры сдвига, каскадированные АЦП Общая линия SS, последовательное соединение MOSI→MISO
Двунаправленный режим Передача данных по одной линии в обоих направлениях Системы с ограниченным количеством выводов Объединение MOSI и MISO в один двунаправленный сигнал

При физическом соединении компонентов необходимо соблюдать несколько ключевых правил:

  1. Использовать подтягивающие резисторы на линиях SS (обычно 10 кОм к VDD), особенно если линия управляется программно через GPIO
  2. Минимизировать длину проводников для высокоскоростной передачи данных (свыше 10 МГц)
  3. Для длинных соединений (более 10 см) использовать согласующие резисторы на линиях SCK, MOSI и MISO
  4. При наличии нескольких устройств с разными логическими уровнями применять преобразователи уровней

Для надежной работы SPI на высоких скоростях критически важно обеспечить качественное заземление и минимизировать электромагнитные помехи. Рекомендуется использовать многослойные платы с выделенным слоем земли под сигнальными линиями SPI.

Михаил Дорофеев, инженер-схемотехник

Работая над проектом медицинского оборудования с использованием STM32F7, мы столкнулись с загадочными сбоями при считывании данных с высокоскоростного АЦП через SPI на частоте 20 МГц. Устройство работало нормально в лабораторных условиях, но давало сбои при полевых испытаниях. После недель отладки выяснилось, что проблема заключалась в длине проводников на плате — трасса SCK проходила вблизи импульсного преобразователя, что вызывало искажение фронтов сигнала. Перепроектирование платы с учетом требований к высокочастотным сигналам полностью решило проблему. С тех пор я всегда уделяю особое внимание трассировке линий SPI и защите их от помех, особенно при работе на высоких частотах.

Отдельно стоит рассмотреть вопрос тактирования SPI. В STM32 частота SPI зависит от тактовой частоты шины APB/AHB, к которой подключен соответствующий модуль. Для достижения требуемой скорости передачи данных необходимо правильно настроить предделители частоты в регистрах SPI.

Для различных серий STM32 максимальная скорость SPI варьируется:

  • STM32F0/F1: до 18 МГц
  • STM32F2/F4: до 42 МГц
  • STM32F7: до 54 МГц
  • STM32H7: до 133 МГц

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

Программирование SPI на STM32 с библиотекой HAL

После настройки аппаратной части переходим к программной реализации взаимодействия с SPI. Библиотека HAL (Hardware Abstraction Layer) предоставляет удобный и структурированный интерфейс для работы с периферийными устройствами STM32, включая модули SPI. 💻

Инициализация SPI с использованием HAL состоит из нескольких основных этапов:

  1. Включение тактирования порта GPIO и модуля SPI
  2. Настройка выводов GPIO для функций SPI
  3. Конфигурация параметров SPI
  4. Инициализация и запуск интерфейса

Ниже приведен типичный код инициализации SPI1 на микроконтроллере STM32F4xx:

c
Скопировать код
// Определение структуры для настройки SPI
SPI_HandleTypeDef hspi1;

void SPI1_Init(void)
{
// 1. Включаем тактирование портов и SPI
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_SPI1_CLK_ENABLE();

// 2. Настраиваем пины GPIO
GPIO_InitTypeDef GPIO_InitStruct = {0};

// Настройка SCK, MISO, MOSI
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // Альтернативная функция, push-pull
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1; // Альтернативная функция для SPI1
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// Настройка пина NSS (если управляем программно)
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // Обычный выход push-pull
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // Деактивируем SS (высокий уровень)

// 3. Конфигурация SPI
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER; // Режим мастера
hspi1.Init.Direction = SPI_DIRECTION_2LINES; // Двунаправленная передача
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 8-битные данные
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL = 0
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA = 0 (режим 0)
hspi1.Init.NSS = SPI_NSS_SOFT; // Программное управление NSS
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // Предделитель частоты
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // Старший бит первым
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; // Режим TI выключен
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // CRC выключен

// 4. Инициализация SPI
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
}

После инициализации SPI можно приступать к обмену данными. HAL предоставляет несколько функций для этого:

  • HALSPITransmit() — отправка данных
  • HALSPIReceive() — прием данных
  • HALSPITransmitReceive() — одновременная отправка и прием данных
  • HALSPITransmit_IT(), HALSPIReceive_IT(), HALSPITransmitReceive_IT() — версии с использованием прерываний
  • HALSPITransmit_DMA(), HALSPIReceive_DMA(), HALSPITransmitReceive_DMA() — версии с использованием DMA

Пример базового обмена данными через SPI:

c
Скопировать код
void SPI_Example(void)
{
uint8_t txData[2] = {0xAA, 0x55}; // Данные для отправки
uint8_t rxData[2] = {0}; // Буфер для приема данных

// Активируем ведомое устройство (низкий уровень на NSS)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);

// Отправка и прием данных
HAL_StatusTypeDef status = HAL_SPI_TransmitReceive(&hspi1, txData, rxData, 2, 100);

// Деактивируем ведомое устройство (высокий уровень на NSS)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);

if (status != HAL_OK)
{
// Обработка ошибки
Error_Handler();
}
}

Для повышения производительности и снижения нагрузки на процессор рекомендуется использовать режим DMA (Direct Memory Access) при работе с большими объемами данных или при требованиях к высокой скорости передачи.

Настройка и использование SPI с DMA:

c
Скопировать код
// Определение структур для DMA
DMA_HandleTypeDef hdma_spi1_tx;
DMA_HandleTypeDef hdma_spi1_rx;

void SPI1_DMA_Init(void)
{
// Включаем тактирование DMA
__HAL_RCC_DMA2_CLK_ENABLE();

// Настройка DMA для передачи
hdma_spi1_tx.Instance = DMA2_Stream3;
hdma_spi1_tx.Init.Channel = DMA_CHANNEL_3;
hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_spi1_tx.Init.PeriphInc = DMA_PERIPH_INC_DISABLE;
hdma_spi1_tx.Init.MemInc = DMA_MEMORY_INC_ENABLE;
hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi1_tx.Init.Mode = DMA_NORMAL;
hdma_spi1_tx.Init.Priority = DMA_PRIORITY_LOW;
hdma_spi1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_spi1_tx);

// Связывание DMA с SPI для передачи
__HAL_LINKDMA(&hspi1, hdmatx, hdma_spi1_tx);

// Настройка DMA для приема (аналогично)
hdma_spi1_rx.Instance = DMA2_Stream2;
// ... настройка параметров аналогично TX
HAL_DMA_Init(&hdma_spi1_rx);

// Связывание DMA с SPI для приема
__HAL_LINKDMA(&hspi1, hdmarx, hdma_spi1_rx);

// Настройка прерываний DMA
HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
}

// Пример использования DMA для передачи/приема данных
void SPI_DMA_Example(uint8_t* txBuffer, uint8_t* rxBuffer, uint16_t size)
{
// Активируем ведомое устройство
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);

// Запускаем передачу/прием через DMA
HAL_SPI_TransmitReceive_DMA(&hspi1, txBuffer, rxBuffer, size);

// Обработчики прерываний будут вызваны автоматически по завершении
}

// Обработчик прерывания по завершении передачи/приема
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1)
{
// Деактивируем ведомое устройство
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);

// Дополнительная обработка завершения передачи
}
}

При работе с SPI важно помнить о правильном управлении линией NSS (CS). Если используется программное управление (SPINSSSOFT), необходимо явно активировать/деактивировать ведомое устройство путем установки соответствующего уровня на выводе NSS:

c
Скопировать код
// Активация ведомого устройства
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // Низкий уровень

// Выполнение обмена данными...

// Деактивация ведомого устройства
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // Высокий уровень

При работе с несколькими устройствами на одной шине SPI, каждое устройство должно иметь свою линию выбора (CS/NSS), и активировать следует только одно устройство за раз:

c
Скопировать код
// Выбор первого устройства
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS1 активен
HAL_SPI_Transmit(&hspi1, data1, size1, timeout);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS1 неактивен

// Выбор второго устройства
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); // CS2 активен
HAL_SPI_Transmit(&hspi1, data2, size2, timeout);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); // CS2 неактивен

Отладка и решение типичных проблем SPI на STM32

Даже при тщательной настройке и программировании SPI, разработчики неизбежно сталкиваются с различными проблемами. Умение эффективно диагностировать и решать эти проблемы — ключевой навык для успешной работы с интерфейсом SPI на микроконтроллерах STM32. 🔍

Наиболее распространенные проблемы при работе с SPI и методы их диагностики:

Проблема Возможные причины Методы диагностики Решения
Отсутствие обмена данными – Неправильное подключение <br>- Отсутствие тактирования <br>- Неверная инициализация – Проверка сигналов осциллографом <br>- Проверка регистров тактирования – Верификация схемы подключения <br>- Проверка кода инициализации <br>- Включение тактирования портов и SPI
Искажение данных – Неверные параметры CPOL/CPHA <br>- Слишком высокая частота <br>- ЭМИ помехи – Анализ осциллограмм сигналов <br>- Тестирование на разных скоростях – Корректировка режима SPI <br>- Снижение тактовой частоты <br>- Улучшение экранирования
Таймауты при передаче – Проблемы с NSS/CS <br>- Зависание периферии <br>- Конфликт прерываний – Мониторинг состояния SPI <br>- Анализ флагов ошибок – Проверка управления линией CS <br>- Сброс модуля SPI <br>- Пересмотр приоритетов прерываний
Проблемы с DMA – Неверная конфигурация DMA <br>- Проблемы выравнивания данных <br>- Конфликты каналов DMA – Проверка регистров состояния DMA <br>- Анализ прерываний DMA – Корректная настройка каналов DMA <br>- Обеспечение выравнивания буферов <br>- Проверка приоритетов DMA

Эффективная отладка SPI требует систематического подхода. Рекомендуется выполнять диагностику в следующем порядке:

  1. Проверка аппаратных соединений (прозвонка линий, осмотр паек)
  2. Верификация настроек тактирования (проверка включения тактирования портов и модуля SPI)
  3. Анализ сигналов с помощью осциллографа или логического анализатора
  4. Проверка корректности инициализации SPI в программном коде
  5. Тестирование на минимальной скорости для исключения проблем с тактовой частотой
  6. Анализ возвращаемых статусов и флагов ошибок

При отладке особенно полезны следующие инструменты и методы:

  • Осциллограф или логический анализатор — незаменимы для визуализации сигналов SPI и определения временных параметров
  • Отладочные точки останова (breakpoints) — позволяют отследить выполнение кода и значения переменных
  • Анализ регистров периферии — помогает определить текущее состояние модуля SPI
  • Тестовые шаблоны данных — использование известных паттернов (0xAA, 0x55) для проверки целостности передачи

Особое внимание следует уделить режимам работы SPI. Несоответствие параметров CPOL и CPHA требованиям ведомого устройства — одна из самых распространенных причин проблем. При отладке рекомендуется проверить все четыре возможных режима SPI, если характеристики ведомого устройства точно не известны.

Типичный код для реализации тестирования разных режимов SPI:

c
Скопировать код
// Массив конфигураций для тестирования всех режимов SPI
const uint8_t spiModes[4][2] = {
{SPI_POLARITY_LOW, SPI_PHASE_1EDGE}, // Режим 0
{SPI_POLARITY_LOW, SPI_PHASE_2EDGE}, // Режим 1
{SPI_POLARITY_HIGH, SPI_PHASE_1EDGE}, // Режим 2
{SPI_POLARITY_HIGH, SPI_PHASE_2EDGE} // Режим 3
};

void TestAllSPIModes(void)
{
uint8_t testData = 0xA5;
uint8_t receivedData = 0;

for (int i = 0; i < 4; i++)
{
// Реконфигурация SPI для текущего режима
HAL_SPI_DeInit(&hspi1);

hspi1.Init.CLKPolarity = spiModes[i][0];
hspi1.Init.CLKPhase = spiModes[i][1];

HAL_SPI_Init(&hspi1);

// Тестовая передача данных
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS активен
HAL_SPI_TransmitReceive(&hspi1, &testData, &receivedData, 1, 100);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS неактивен

// Анализ полученных данных
printf("Mode %d: Sent 0xA5, Received 0x%02X\n", i, receivedData);

HAL_Delay(100); // Пауза между тестами
}
}

Отдельная категория проблем связана с производительностью SPI. При работе с высокими скоростями или большими объемами данных могут возникать проблемы с таймингами и загрузкой процессора. Для их решения рекомендуется:

  1. Использовать DMA для разгрузки CPU при передаче больших объемов данных
  2. Оптимизировать обработку прерываний, минимизируя время их выполнения
  3. При необходимости применять буферизацию данных для обеспечения непрерывного потока
  4. Рассмотреть возможность увеличения приоритета прерываний SPI и DMA

Для диагностики проблем с производительностью полезно использовать счетчики времени и профилирование кода:

c
Скопировать код
uint32_t startTime, endTime, elapsedTime;

startTime = HAL_GetTick();
// Выполнение операции SPI
endTime = HAL_GetTick();
elapsedTime = endTime – startTime;

printf("SPI transfer time: %lu ms\n", elapsedTime);

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

Практические проекты с использованием SPI на STM32

Теоретические знания о SPI приобретают истинную ценность только при их применении в реальных проектах. В этом разделе представлены практические примеры использования интерфейса SPI для взаимодействия с различными периферийными устройствами на базе микроконтроллеров STM32. 🛠️

Рассмотрим несколько популярных проектов с применением SPI и предоставим базовые фрагменты кода для их реализации.

1. Работа с SD-картой через SPI

SD-карты широко используются для хранения данных в встраиваемых системах. Интерфейс SPI позволяет реализовать взаимодействие с SD-картой даже на микроконтроллерах с ограниченными ресурсами.

c
Скопировать код
// Инициализация SD-карты через SPI
FATFS SDFatFs;
char SDPath[4];

void SD_Card_Init(void)
{
// Низкоуровневая инициализация SPI
SPI1_Init();

// Монтирование файловой системы
if(f_mount(&SDFatFs, (TCHAR const*)SDPath, 0) != FR_OK)
{
Error_Handler();
}

// Теперь можно использовать функции FatFS для работы с файлами
}

// Пример записи данных на SD-карту
void WriteDataToSD(void)
{
FIL file;
uint32_t bytesWritten;
char data[] = "SPI Test Data";

// Открытие/создание файла
if(f_open(&file, "test.txt", FA_CREATE_ALWAYS | FA_WRITE) != FR_OK)
{
Error_Handler();
}

// Запись данных
if(f_write(&file, data, strlen(data), (void *)&bytesWritten) != FR_OK)
{
Error_Handler();
}

// Закрытие файла
f_close(&file);
}

2. Взаимодействие с OLED-дисплеем SSD1306

OLED-дисплеи с контроллером SSD1306 популярны благодаря компактности и высокой контрастности. Подключение через SPI обеспечивает высокую скорость обновления изображения.

c
Скопировать код
// Инициализация OLED-дисплея SSD1306 через SPI
void SSD1306_Init(void)
{
// Инициализация SPI
SPI1_Init();

// Инициализация дополнительных пинов для управления дисплеем
GPIO_InitTypeDef GPIO_InitStruct = {0};

// Пин DC (Data/Command)
GPIO_InitStruct.Pin = OLED_DC_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(OLED_DC_PORT, &GPIO_InitStruct);

// Пин RES (Reset)
GPIO_InitStruct.Pin = OLED_RES_PIN;
HAL_GPIO_Init(OLED_RES_PORT, &GPIO_InitStruct);

// Сброс дисплея
HAL_GPIO_WritePin(OLED_RES_PORT, OLED_RES_PIN, GPIO_PIN_RESET);
HAL_Delay(10);
HAL_GPIO_WritePin(OLED_RES_PORT, OLED_RES_PIN, GPIO_PIN_SET);
HAL_Delay(10);

// Отправка команд инициализации
SSD1306_SendCommand(0xAE); // Выключение дисплея
SSD1306_SendCommand(0xD5); // Установка частоты обновления
SSD1306_SendCommand(0x80);
// ...другие команды инициализации...
SSD1306_SendCommand(0xAF); // Включение дисплея

// Очистка дисплея
SSD1306_Clear();
}

// Функция отправки команды
void SSD1306_SendCommand(uint8_t command)
{
HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_RESET); // Режим команд
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET); // Выбор устройства

HAL_SPI_Transmit(&hspi1, &command, 1, 100);

HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET); // Деактивация
}

// Функция отправки данных
void SSD1306_SendData(uint8_t* data, uint16_t size)
{
HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_SET); // Режим данных
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET); // Выбор устройства

HAL_SPI_Transmit(&hspi1, data, size, 1000);

HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET); // Деактивация
}

// Отрисовка пикселя
void SSD1306_DrawPixel(uint8_t x, uint8_t y, uint8_t color)
{
if(x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) return;

if(color)
SSD1306_Buffer[x + (y/8)*SSD1306_WIDTH] |= 1 << (y%8);
else
SSD1306_Buffer[x + (y/8)*SSD1306_WIDTH] &= ~(1 << (y%8));
}

// Обновление содержимого дисплея
void SSD1306_UpdateScreen(void)
{
for(uint8_t i = 0; i < 8; i++)
{
SSD1306_SendCommand(0xB0 + i); // Установка страницы
SSD1306_SendCommand(0x00); // Установка нижних 4 бит адреса
SSD1306_SendCommand(0x10); // Установка верхних 4 бит адреса

SSD1306_SendData(&SSD1306_Buffer[SSD1306_WIDTH * i], SSD1306_WIDTH);
}
}

3. Создание датчика температуры и влажности с MAX31865 и SHT31

Датчики температуры с интерфейсом SPI, такие как MAX31865 (для PT100/PT1000), предоставляют высокую точность измерений, а совместное использование с датчиком влажности SHT31 позволяет создать комплексную метеостанцию.

c
Скопировать код
// Инициализация датчика MAX31865
void MAX31865_Init(void)
{
// Инициализация SPI
SPI2_Init();

// Настройка пина CS для датчика
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = MAX_CS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(MAX_CS_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(MAX_CS_PORT, MAX_CS_PIN, GPIO_PIN_SET); // Деактивация

// Запись в конфигурационный регистр
uint8_t config = 0;
config |= MAX31865_CONFIG_BIAS; // Включение смещения
config |= MAX31865_CONFIG_MODEAUTO; // Автоматический режим
config |= MAX31865_CONFIG_3WIRE; // 3-проводной режим для PT100

MAX31865_WriteRegister(MAX31865_REG_CONFIG, config);
}

// Запись в регистр MAX31865
void MAX31865_WriteRegister(uint8_t reg, uint8_t value)
{
uint8_t tx[2];
tx[0] = reg | 0x80; // Установка бита записи
tx[1] = value;

HAL_GPIO_WritePin(MAX_CS_PORT, MAX_CS_PIN, GPIO_PIN_RESET); // Активация
HAL_SPI_Transmit(&hspi2, tx, 2, 100);
HAL_GPIO_WritePin(MAX_CS_PORT, MAX_CS_PIN, GPIO_PIN_SET); // Деактивация
}

// Чтение из регистра MAX31865
uint8_t MAX31865_ReadRegister(uint8_t reg)
{
uint8_t tx = reg & 0x7F; // Сброс бита записи
uint8_t rx;

HAL_GPIO_WritePin(MAX_CS_PORT, MAX_CS_PIN, GPIO_PIN_RESET); // Активация
HAL_SPI_Transmit(&hspi2, &tx, 1, 100);
HAL_SPI_Receive(&hspi2, &rx, 1, 100);
HAL_GPIO_WritePin(MAX_CS_PORT, MAX_CS_PIN, GPIO_PIN_SET); // Деактивация

return rx;
}

// Чтение температуры с MAX31865
float MAX31865_ReadTemperature(void)
{
// Чтение регистров RTD
uint8_t rtdRegAddrMSB = MAX31865_REG_RTD_MSB;
uint16_t rtd;
uint8_t rtdData[2];

HAL_GPIO_WritePin(MAX_CS_PORT, MAX_CS_PIN, GPIO_PIN_RESET); // Активация
HAL_SPI_Transmit(&hspi2, &rtdRegAddrMSB, 1, 100);
HAL_SPI_Receive(&hspi2, rtdData, 2, 100);
HAL_GPIO_WritePin(MAX_CS_PORT, MAX_CS_PIN, GPIO_PIN_SET); // Деактивация

rtd = (rtdData[0] << 8) | rtdData[1];
rtd >>= 1; // Удаление флага ошибки

// Расчет температуры для PT100
float temp = rtd;
temp /= 32.0;
temp -= 256.0; // Для PT100

return temp;
}

4. Работа с внешней FLASH-памятью W25Qxx

Микросхемы FLASH-памяти серии W25Qxx часто используются для хранения больших объемов данных, таких как прошивки, конфигурации или логи. Интерфейс SPI обеспечивает удобный доступ к этим микросхемам.

c
Скопировать код
// Инициализация W25Q128
void W25Q128_Init(void)
{
// Инициализация SPI
SPI3_Init();

// Настройка пина CS
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = FLASH_CS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(FLASH_CS_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_SET); // Деактивация

// Проверка ID устройства
uint32_t flashID = W25Q128_ReadID();
if((flashID & 0xFFFF0000) != 0xEF170000)
{
Error_Handler(); // Неправильный ID
}
}

// Чтение ID устройства
uint32_t W25Q128_ReadID(void)
{
uint8_t cmd[4] = {0x9F, 0, 0, 0}; // Команда JEDEC ID
uint8_t id[3];

HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_RESET); // Активация
HAL_SPI_Transmit(&hspi3, cmd, 1, 100);
HAL_SPI_Receive(&hspi3, id, 3, 100);
HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_SET); // Деактивация

return ((uint32_t)id[0] << 16) | ((uint32_t)id[1] << 8) | id[2];
}

// Чтение данных из памяти
void W25Q128_ReadData(uint32_t address, uint8_t* data, uint32_t size)
{
uint8_t cmd[4];
cmd[0] = 0x03; // Команда READ
cmd[1] = (address >> 16) & 0xFF;
cmd[2] = (address >> 8) & 0xFF;
cmd[3] = address & 0xFF;

HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_RESET); // Активация
HAL_SPI_Transmit(&hspi3, cmd, 4, 100);
HAL_SPI_Receive(&hspi3, data, size, 1000);
HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_SET); // Деактивация
}

// Запись данных в память (после стирания сектора)
void W25Q128_WritePage(uint32_t address, uint8_t* data, uint32_t size)
{
// Включение записи
HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_RESET);
uint8_t cmd = 0x06; // Write Enable
HAL_SPI_Transmit(&hspi3, &cmd, 1, 100);
HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_SET);

// Запись данных (максимум 256 байт на страницу)
HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_RESET);

cmd = 0x02; // Page Program
HAL_SPI_Transmit(&hspi3, &cmd, 1, 100);

uint8_t addrBytes[3];
addrBytes[0] = (address >> 16) & 0xFF;
addrBytes[1] = (address >> 8) & 0xFF;
addrBytes[2] = address & 0xFF;
HAL_SPI_Transmit(&hspi3, addrBytes, 3, 100);

HAL_SPI_Transmit(&hspi3, data, size, 1000);

HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_SET);

// Ожидание завершения записи
W25Q128_WaitForWriteEnd();
}

// Ожидание завершения операции записи
void W25Q128_WaitForWriteEnd(void)
{
uint8_t cmd = 0x05; // Read Status Register
uint8_t status;

do {
HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi3, &cmd, 1, 100);
HAL_SPI_Receive(&hspi3, &status, 1, 100);
HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_SET);
HAL_Delay(1);
} while ((status & 0x01) == 0x01); // Пока бит BUSY установлен
}

Эти примеры демонстрируют базовые принципы взаимодействия с различными SPI-устройствами. При разработке реальных проектов рекомендуется дополнить код обработкой ошибок, оптимизацией производительности и механизмами энергосбережения.

Для более сложных проектов имеет смысл создать абстрактные драйверы, скрывающие детали низкоуровневого взаимодействия с SPI и предоставляющие удобный API высокого уровня для работы с конкретными устройствами.

Овладение интерфейсом SPI на микроконтроллерах STM32 — это ключевой навык, открывающий возможности для интеграции множества периферийных устройств в ваши проекты. Правильная настройка аппаратной части, грамотное программирование с использованием библиотеки HAL и понимание методов диагностики проблем позволят вам создавать надежные и эффективные встраиваемые системы. Последовательно применяя принципы, описанные в этом руководстве, вы сможете значительно сократить время разработки и избежать типичных ошибок при работе с SPI на STM32.

Читайте также

Проверь как ты усвоил материалы статьи
Пройди тест и узнай насколько ты лучше других читателей
Какой интерфейс используется для обмена данными между микроконтроллерами и периферийными устройствами?
1 / 5

Загрузка...