SPI (Serial Pheripheral Interface) – последовательный синхронный интерфейс для обмена данными между микросхемами. Этот интерфейс подразумевает четкую иерархию – всегда есть ведущее устройство “Master” и подчиненное устройство “Slave” (или несколько устройств на одной шине). Master выбирает подчиненное устройство, с которым будет производить обмен данными в текущий момент. Подчиненные устройства Slave ожидают команды от Master для начала обмена данными. Сигнал синхронизации генерирует Master и, далее, ведущее и подчиненное устройства начинают обмен данными. Ведущим устройством очень часто является микроконтроллер, а подчиненными устройствами могут быть различные микросхемы: память, АЦП и ЦАП, микроконтроллеры, и другие.
Простейшая схема подключения при использовании интерфейса SPI с одним ведущим и одним подчиненным устройством приведена на рисунке.
На этом рисунке, как уже упоминалось выше, Master – ведущее устройство, Slave – подчиненное. Как видно из рисунка, одноименные выводы соединены друг с другом. Рассмотрим подробнее назначение используемых выводов.
- MOSI (Master Output Slave Input) – в зависимости от режима работы это вход или выход. Если устройство является ведущим (Master), то вывод будет являться выходом для передачи данных подчиненному устройству (Master Output Slave Input). В обратном случае, когда устройство является подчиненным (Slave), этот вывод будет входом для приема данных от ведущего устройства (Master Output Slave Input).
- MISO (Master Input Slave Output) – этот вывод также служит для передачи или приема данных, но в обратном направлении, чем предыдущий. То есть, если устройство является ведущим (Master), вывод является входом для приема данных от подчиненного устройства (Master Input Slave Output). А для подчиненного устройства (Slave) этот вывод – выход для передачи данных ведущему устройству (Master Input Slave Output). Стрелки на рисунке у выводов MOSI и MISO отображают направление потока данных.
- SCK (Serial Clock) – тактовый сигнал. Генерируется ведущим устройством (Master).
- NSS (Slave Select) – выбор подчиненного устройства. Низкий уровень на этом входе у подчиненного устройства означает, что оно выбрано ведущим устройством для обмена данными.
Это краткая информация о назначении выводов при использовании данного интерфейса.
С выводами MOSI и MISO все однозначно – это передача и прием данных.
Для
тактового сигнала (линия SCK) имеются несколько комбинаций полярности
сигнала (высокий или низкий уровень сигнала в состоянии ожидания, когда
нет обмена данными) и фазы сигнала синхронизации. Под фазой сигнала
синхронизации имеется в виду привязка моментов выдачи данных на шину и
чтения данных с шины к соответствующим моментам изменения уровней
тактового сигнала. Для устройств Master и Slave настройки режимов
тактовой линии SCK должны совпадать. В документации на конкретное
периферийное устройство можно найти информацию о полярности и фазе
тактового сигнала, используемых при чтении и передаче данных.
Производителем микросхемы может быть предусмотрен всего один из
возможных режимов, поэтому при настройке ведущего устройства
(микроконтроллера), эту информацию необходимо учитывать.
Вывод
NSS у микроконтроллеров STM32 тоже может быть сконфигурирован по
разному. Все возможные варианты настроек линий SCK и NSS рассмотрим
подробней, но чуть позже.
Пока же начнем с внутренней структуры модуля SPI и принципов его функционирования.
На этом рисунке изображены основные элементы модулей SPI ведущего и подчиненного устройств, задействованные при обмене данными. Генератор ведущего устройства (SPI clock generator) формирует тактовый сигнал, управляющий регистрами сдвига обоих устройств. Регистр сдвига ведущего устройства Master с каждым тактовым импульсом сдвигает свое содержимое, при этом на выход MOSI последовательно выдаются данные, а со входа MISO считываются данные и загружаются в тот же регистр сдвига, но с другой стороны. Когда подчиненное устройство начинает принимать данные на вход MOSI, эти данные последовательно заносятся в его регистр сдвига. Одновременно, с приходом каждого нового тактового импульса, с другой стороны регистра сдвига данные “выталкиваются” на выход MISO, эти данные передаются ведущему устройству. Перед началом передачи данных между устройствами, эти данные должны быть загружены в регистры сдвига обоих устройств. Прямого доступа к регистру сдвига нет, для записи и чтения данных существует регистр SPI_DR, который взаимодействует с регистром сдвига. Перед началом передачи данных, эти данные необходимо записать в регистр SPI_DR, а принятые данные считываются из этого же регистра. Никаких противоречий здесь нет, пользователю виден только один регистр SPI_DR, но фактически этот регистр разделен на два отдельных буферных регистра: Tx buffer и Rx buffer. При операции записи в регистр SPI_DR происходит запись в Tx buffer и в регистр сдвига, эти данные предназначены для передачи. После окончания приема данных, содержимое регистра сдвига переносится в Rx buffer. При операции чтения из регистра SPI_DR, происходит обращение уже к Rx buffer и оттуда считываются принятые данные.
Модуль SPI STM32 может оперировать с данными размерностью 8 или 16 разрядов. Направление передачи данных может быть разным: первым может передаваться старший значащий бит (MSB) или младший значащий бит (LSB).
На рисунке приведена общая блок-схема модуля SPI.
Кроме уже знакомых регистров Shift register, Rx buffer, Tx buffer, здесь изображены следующие компоненты:
- 2 регистра управления – SPI_CR1, SPI_CR2, а также регистр статуса SPI_SR.
- Генератор тактового сигнала – Baud rate generator. Скорость обмена данными (частота сигнала на выходе SCK) задается с помощью разрядов BR[2:0] регистра SPI_CR1. Частота может задаваться от Fpclk/2 до Fpclk/256, где Fpclk – входная частота тактирования модуля SPI.
- Управляющая логика – Master control logic. Управляет блоком передачи/приема устройства (выводы MOSI/MISO) в зависимости от заданных настроек. Могут использоваться различные режимы работы модуля SPI: полнодуплексный режим (передача и прием данных осуществляются через разные выводы); режим передачи и приема с использованием одной двунаправленной линии; режим работы только на прием данных (передатчик отключен).
- Блок Communication control, как видно из рисунка может выставить некоторые флаги ошибок в регистре статуса SPI_SR, например, потери данных OVR (когда в приемный буфер поступили новые данные, а предыдущие еще не были считаны, соответственно, они будут перезаписаны и потеряны); MODF – флаг сигнализирует, что при работе устройства в режиме Master на вход NSS поступил сигнал низкого уровня и устройство должно быть переведено в режим Slave; CRCERR – при проверке контрольной суммы выявлена ошибка.
- Мультиплексор выбора режимов работы вывода NSS, изображен вот таким блоком на рисунке.
Рассмотрим подробней режимы конфигурации вывода NSS модуля SPI. Дополнительный вывод NSS предназначен для выбора подчиненного устройства подачей на его вход SS (Slave Select) сигнала низкого уровня. Этот вывод позволяет ведущему устройству Master поддерживать связь с конкретным подчиненным устройством, не создавая конфликтов на шине данных. В ведущем устройстве Master вывод NSS может использоваться в качестве выхода (установлен бит SSOE) и управлять ведомым устройством. Если в режиме Master вывод NSS сконфигурирован как вход, то на нем должен быть высокий уровень. В противном случае, низкий уровень на входе NSS ведущего устройства Master вызовет переход модуля SPI в состояние “ошибка режима Master”, при этом устройство автоматически перейдет в режим Slave.
Управление NSS может быть программным или аппаратным. Выбор режима зависит от состояния бита SSM регистра SPI_CR1.
- SSM = 1 — программное управление NSS. Уведомление о выборе Slave Select формируется внутри модуля SPI. Логический уровень сигнала на выводе NSS в данном случае игнорируется, вместо него определяющим становится значение бита SSI, которым и заменяется состояние входа NSS. Напомню, если устройство Master, то SSI должен быть установлен, что аналогично высокому уровню на входе NSS. Иначе, модуль перейдет в режим Slave, просигнализировав об “ошибке режима Master” установкой бита MODF и может сгенерировать прерывание, если это разрешено. В этом режиме внешний вывод NSS освобождается и может быть использован в приложении для других целей как обычный GPIO.
- SSM = 0 – аппаратное управление NSS. В этом режиме вывод NSS
используется модулем SPI в двух разных конфигурациях, в зависимости от
состояния разряда SSOE регистра SPI_CR2, который разрешает использовать
этот вывод в качестве выхода или же нет.- Когда NSS разрешено работать в
качестве выхода (SSM = 0, SSOE = 1), эта конфигурация используется
только в режиме Master. Когда ведущее устройство начинает соединение,
NSS переводится в состояние низкого уровня, и поддерживается в этом
состоянии до момента деактивации модуля SPI.
— В другой конфигурации NSS запрещено использовать как выход (SSM = 0, SSOE = 0). Это позволяет использовать “мультимастерный” режим для нескольких ведущих устройств Master. Для устройств Slave вход NSS так и остается входом выбора устройства для обмена данными, управляемым низким уровнем.
Для управления выбором подчиненных устройств Slave select, в ведущем устройстве Master вместо вывода NSS можно задействовать любые другие выводы портов микроконтроллера. Подача низкого уровня на вход Slave select подчиненного устройства может быть воспринята некоторыми микросхемами как сигнал к началу обмена данными, поэтому принудительно подключать вход NSS к низкому уровню можно не у всех микросхем (у STM32 так можно делать).
Теперь рассмотрим различные комбинации полярности и фазы тактового сигнала. Всего тут возможны 4 режима работы, которые задаются битами CPOL и CPHA регистра SPI_CR1. Бит CPOL (clock polarity) определяет уровень сигнала синхронизации (высокий или низкий) на линии SCK в состоянии ожидания. При CPOL = 0 в состоянии ожидания будет низкий уровень сигнала на выходе SCK, а при CPOL = 1 – высокий уровень. Состояние бита CPHA задает перепад сигнала (переход из высокого уровня в низкий или наоборот), который будет являться стробирующим при фиксации данных на выводах MOSI и MISO. В зависимости от полярности тактового сигнала, заданной в разряде CPOL, бит CPHA задает первый или второй перепад тактового сигнала (falling edge или rising edge) в качестве стробирующего. 4 различных комбинации бит CPOL и CPHA и соответствующие им режимы приведены на рисунке ниже. На этом рисунке Capture strobe – моменты фиксации данных.
Теперь обратимся к регистрам модуля SPI. В микроконтроллерах STM32F, начиная с устройств high density, модуль SPI может содержать регистры для работы с интерфейсом I2S. То есть платы STM32L-DISCOVERY и STM32VL-DISCOVERY не поддерживают этот протокол. Далее при рассмотрении регистров будут описаны только те из них, которые относятся непосредственно к интерфейсу SPI. Интерфейс I2S предназначен для систем передачи аудиоданных, он более специализированный и, в данный момент, рассматриваться не будет.
Регистр управления SPI_CR1:
Бит 15 — BIDIMODE — разрешает или запрещает использование двунаправленного режима обмена данными по одной линии
0: Режим 2-х однонаправленных линий данных
1: Режим 1-ой двунаправленной линии данных
Бит 14 — BIDIOE — в режиме двунаправленного обмена данными по одной линии, разрешает или запрещает передачу данных
0: Выход неактивен (только прием данных)
1: Выход активен (только передача данных)
В режиме Master используется вывод MOSI, в режиме Slave – MISO.
Биты 13 и 12 — CRCEN и CRCNEXT задействуют подсчет контрольной суммы данных.
Бит 11 — DFF – формат данных
0: 8 бит
1: 16 бит
Бит 10 — RXONLY – только прием. В комбинации с битом BIDIMODE задает направление передачи данных.
0: Полнодуплексный режим (передача и прием данных)
1: Выход отключен (только прием данных)
Бит 9 — SSM – программное управление выбором устройства (NSS). Когда этот бит установлен, вместо уровня на входе NSS контролируется состояние бита SSI.
0: Программный режим отключен
1: Программный режим включен
Бит 8 — SSI – состояние этого бита воздействует на устройство только при установленном бите SSM (при программном управлении NSS). Значение этого бита принудительно заменяет состояние входа NSS, которое при этом игнорируется
Бит 7 — LSBFIRST – порядок передачи данных
0: Первым передается старший значащий разряд – MSB
1: Первым передается младший значащий разряд – LSB
Бит 6 — SPE – включение модуля SPI
0: модуль отключен
1: модуль включен
Биты 5:3 — BR[2:0] – выбор скорости обмена
000: Fpclk/2
001: Fpclk/4
……………
111: Fpclk/256
Бит 2 – MSTR – выбор режима Master
0: Режим Slave
1: Режим Master
Бит 1 – CPOL – полярность тактового сигнала
0: Низкий уровень в режиме ожидания на выводе SCK
1: Высокий уровень в режиме ожидания на выводе SCK
Бит 0 – CPHA – фаза тактового сигнала
0: Строб данных происходит по первому перепаду тактового сигнала
1: Строб данных происходит по второму перепаду тактового сигнала
Регистр управления SPI_CR2:
Бит 7 – TXEIE – разрешение прерывания при опустошении буфера передачи Tx buffer
0: Прерывание запрещено
1: Разрешена генерация прерывания при установке флага TXE в регистре SPI_SR
Бит 6 – RXNEIE – разрешение прерывания при поступлении данных в буфер приемника Rx buffer
0: Прерывание запрещено
1: Разрешена генерация прерывания при установке флага RXNE в регистре SPI_SR
Бит 5 – ERRIE – разрешение прерывания при возникновении ошибки. Установленные флаги ошибок CRCERR, OVR, MODF вызовут генерацию прерывания об ошибке.
0: Прерывание запрещено
1: Прерывание разрешено
Бит 2 – SSOE – разрешение использования вывода NSS в качестве выход
0: Выход неактивен
1: Выход активен
Бит 1 – TXDMAEN – активация/деактивация запроса DMA для буфера передачи Tx buffer. Когда этот бит установлен, запрос DMA формируется при установке флага TXE в регистре SPI_SR
0: Запрос DMA запрещен
1: Запрос DMA разрешен
Бит 0 – RXDMAEN – активация/деактивация запроса DMA для буфера приема Rx buffer. Когда этот бит установлен, запрос DMA формируется при установке флага RXNE в регистре SPI_SR
0: Запрос DMA запрещен
1: Запрос DMA разрешен
Регистр статуса SPI_SR:
Бит 7 – BSY – флаг занятости. Установлен, когда происходит обмен данными и буфер передатчика содержит данные
Бит 6 – OVR – Устанавливается при потере данных. Поступили новые данные, которые перезаписали предыдущие в регистре данных.
Бит 5 – MODF – сбой режима работы. Этот флаг может быть установлен, например, при работе устройства в режиме Master, когда при этом на его вход NSS поступил сигнал низкого уровня, переводящий устройство в режим Slave.
Бит 4 – CRCERR – этот флаг устанавливается при ошибке, возникшей при проверке контрольной суммы CRC.
Бит 1 – TXE – устанавливается при опустошении буфера передатчика
Бит 0 – RXNE – устанавливается когда буфер приемника содержит данные, то есть не пустой.
Регистр данных SPI_DR – 16 младших разрядов этого регистра предназначены для записи передаваемых или чтения принятых данных.
Регистры, задействованные для подсчета контрольных сумм — SPI_CRCPR, SPI_RXCRCR, SPI_TXCRCR. Их в данный момент рассматривать не будем.
Перейдем к практической части. Поскольку, для примера работы необходимо организовать связь хотя бы между одним ведущим и одним подчиненным устройством, для этих целей задействуем два модуля SPI одного микроконтроллера. Модуль SPI1 будет работать в режиме Master, а модуль SPI2 — в режиме Slave.
Порядок настройки модуля в режим Master:
- Устанавливаем скорость обмена в разрядах BR[2:0] регистра SPI_CR1.
- Устанавливаем полярность и фазу тактового сигнала с помощью разрядов CPOL, CPHA регистра SPI_CR1.
- Задаем размерность данных (бит DFF).
- Задаем порядок передачи бит данных (бит LSBFIRST).
- Если вывод NSS будет использоваться как вход в аппаратном режиме, то необходимо подать на него высокий уровень. Если вывод будет использоваться в программном режиме, то необходимо установить биты SSM и SSI. Если вывод NSS при этом будет использоваться в качестве выхода, то необходимо установить бит SSOE.
- Установить биты MSTR и SPE.
Порядок настройки модуля в режим Slave:
- Задаем размерность данных (бит DFF) как у ведущего устройства.
- Устанавливаем полярность и фазу тактового сигнала с помощью разрядов CPOL, CPHA регистра SPI_CR1. Эти настройки должны совпадать с настройками ведущего устройства.
- Задаем порядок передачи бит данных (бит LSBFIRST). Эти настройки должны совпадать с настройками ведущего устройства.
- В аппаратном режиме вывод NSS должен быть соединен с низким уровнем во время обмена данными. Если используется программный режим, то необходимо установить следующую комбинацию разрядов – SSM = 1, SSI = 0.
- Очистить бит MSTR и установить бит SPE.
Для модулей SPI будем использовать следующие настройки:
- программное управление NSS у ведущего устройства, аппаратное у подчиненного.
- 8 – разрядный размер данных
- порядок передачи данных – первым идет старший бит MSB
- полярность и фаза тактового сигнала CPOL = 0, CPHA = 0
- скорость обмена – Fpclk/256
На плате STM32VL-DISCOVERY соединим одноименные выводы модулей SPI1 и SPI2 (MOSI, MISO, SCK, NSS).
- MOSI: PA7 <-> PB15
- MISO: PA6 <-> PB14
- SCK: PA5 <-> PB13
- NSS: PA4 <-> PB12
Расположение выводов приведено для платы STM32VL-DISCOVERY.
Алгоритм программы будет следующим:
Настраиваем модуль SPI1 в
режим Master, а модуль SPI2 в режим Slave. Оба модуля одновременно
передают друг другу по одному байту данных, после каждого сеанса обмена
байтами введена программная задержка.
Сначала записываем данные в
буфер передатчика модуля SPI2 (Slave), при этом используются только 2
возможных числа: 0x01 или 0x03.
Затем записываем значение в
буфер передатчика модуля SPI1 (Master). Сначала пишем в буфер
подчиненного устройства, а затем уже ведущего, потому что запись в буфер
ведущего сразу же даст старт обмену данными. В буфер SPI1 (Master)
можно записать любое значение (у меня в программе это число 0x0F),
анализироваться будут только значения, передаваемые от SPI2 (Slave).
Как только оба модуля обменяются байтом данных, считывается
значение из приемного буфера модуля SPI1 и анализируется. В зависимости
от значения принятых данных (0x01 или 0x03), зажигается один из двух
светодиодов. Как видите, алгоритм упрощен до минимума, а светодиоды
задействованы для визуального контроля значения принятых величин.
Далее приведен текст программы с комментариями для серии STM32F. Код проверен на плате STM32VL-DISCOVERY.
#include "stm32f10x.h"
uint8_t temp, send_data = 0x01;
//Инициализация GPIO
void gpio_init()
{
//Линии SPI1 (Master)
RCC->APB2ENR |= (RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN); //Тактирование портов А, В, C и альтернативных функций
GPIOA->CRL &= ~(GPIO_CRL_CNF7 | GPIO_CRL_CNF5 | GPIO_CRL_CNF4); //Очистка бит выбора режима
GPIOA->CRL |= GPIO_CRL_CNF7_1 | GPIO_CRL_CNF5_1 | GPIO_CRL_CNF4_1; //PA7(MOSI), PA5(SCK), PA4(NSS) - AF, Output, PP
GPIOA->CRL |= GPIO_CRL_MODE7 | GPIO_CRL_MODE5 | GPIO_CRL_MODE4; //Выходы MOSI, SCK, NSS - 50MHz
GPIOA->CRL &= ~GPIO_CRL_MODE6; //PA5(MISO) - Input
GPIOA->CRL |= GPIO_CRL_CNF6_0; //PA5(MISO) - Input floating
//Линии SPI2 (Slave)
GPIOB->CRH &= ~(GPIO_CRH_CNF14 | GPIO_CRH_CNF15 | GPIO_CRH_CNF13 | GPIO_CRH_CNF12); //Очистка бит выбора режима
GPIOB->CRH |= GPIO_CRH_CNF14_1; //PB14(MISO) - AF, Output, PP
GPIOB->CRH |= GPIO_CRH_MODE14; //Выход MISO - 50MHz
GPIOB->CRH &= ~(GPIO_CRH_MODE15 | GPIO_CRH_MODE13 | GPIO_CRH_MODE12); //PB15(MOSI), PB13(SCK), PB12(NSS) - Input
GPIOB->CRH |= GPIO_CRH_CNF15_0 | GPIO_CRH_CNF13_0 | GPIO_CRH_CNF12_1; //PB15(MOSI), PB13(SCK) - Input floating, PB12(NSS) - Input pull-up
//Led
GPIOC->CRH &= ~(GPIO_CRH_CNF8 | GPIO_CRH_CNF9); //PC8, PC9 - Output, PP
GPIOC->CRH |= GPIO_CRH_MODE8 | GPIO_CRH_MODE9; //PC8, PC9 - 50MHz
}
//Инициализация SPI1, SPI2
void spi_init()
{
/*Настройка SPI1 (Master)
8 бит данных, MSB передается первым, программный режим управления NSS
вывод NSS (PA4) разрешено использовать в качестве выхода*/
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; //Тактирование модуля SPI1
SPI1->CR1 |= SPI_CR1_BR; //Baud rate = Fpclk/256
SPI1->CR1 &= ~SPI_CR1_CPOL; //Полярность тактового сигнала
SPI1->CR1 &= ~SPI_CR1_CPHA; //Фаза тактового сигнала
SPI1->CR1 &= ~SPI_CR1_DFF; //8 бит данных
SPI1->CR1 &= ~SPI_CR1_LSBFIRST; //MSB передается первым
SPI1->CR1 |= SPI_CR1_SSM; //Программный режим NSS
SPI1->CR1 |= SPI_CR1_SSI; //Аналогично состоянию, когда на входе NSS высокий уровень
SPI1->CR2 |= SPI_CR2_SSOE; //Вывод NSS - выход управления slave select
SPI1->CR1 |= SPI_CR1_MSTR; //Режим Master
SPI1->CR1 |= SPI_CR1_SPE; //Включаем SPI1
/*Настройка SPI2 (Slave)
8 бит данных, MSB передается первым, аппаратный режим управления NSS
вывод NSS (PB12) - вход*/
RCC->APB1ENR |= RCC_APB1ENR_SPI2EN; //Тактирование модуля SPI2
SPI2->CR1 &= ~SPI_CR1_DFF; //8 бит данных
SPI2->CR1 &= ~SPI_CR1_CPOL; //Полярность тактового сигнала
SPI2->CR1 &= ~SPI_CR1_CPHA; //Фаза тактового сигнала
SPI2->CR1 &= ~SPI_CR1_LSBFIRST; //MSB передается первым
SPI2->CR1 &= ~SPI_CR1_SSM; //Аппаратное управление входом NSS
SPI2->CR1 &= ~SPI_CR1_MSTR; //Режим Slave
SPI2->CR1 |= SPI_CR1_SPE; //Включаем SPI2
}
/*Функция обмена данными между модулями SPI1 и SPI2.
В качестве аргумента при вызове функции передаются числа 0x01 или 0x03,
которые затем записываются в буфер передатчика SPI2.
Значение, записываемое в буфер передатчика SPI1 неважно, в данном случае 0x0F
После обмена байтами данных, значение, принятое модулем SPI1 считывается из
регистра данных SPI1_DR в переменную temp*/
void spi_exchange(uint8_t send_data)
{
SPI2->DR = send_data; //Пишем в буфер передатчика SPI2
SPI1->DR = 0x0F; //Пишем в буфер передатчика SPI1. После этого стартует обмен данными
while(!(SPI1->SR & SPI_SR_RXNE)); //Ожидаем окончания приема данных модулем SPI1 (RXNE =1 - приемный буфер содержит данные)
temp = SPI1->DR;//Считываем данные из приемного буфера SPI1. При этой операции происходит очистка буфера и сброс флага RXNE
}
//Основной цикл программы
int main()
{
gpio_init(); //Вызов функции инициализации портов
spi_init(); //Вызов функции инициализации модулей SPI
while(1)
{
/*Вызов функции обмена данными.
В качестве аргумента поочередно передаются значения 0x01 или 0x03*/
spi_exchange(send_data);
switch(temp)
{
/*Если SPI1 принял 0x01, зажигаем соответствующий светодиод,
и меняем значение аргумента, передаваемого функции обмена данными
при следующем вызове на 0x03*/
case 0x01:
GPIOC->BSRR |= GPIO_BSRR_BS8 | GPIO_BSRR_BR9;
send_data = 0x03;
break;
/*Если SPI1 принял 0x03, зажигаем соответствующий светодиод,
и меняем значение аргумента, передаваемого функции обмена данными
при следующем вызове на 0x01*/
case 0x03:
GPIOC->BSRR |= GPIO_BSRR_BR8 | GPIO_BSRR_BS9;
send_data = 0x01;
break;
}
//Временная задержка между вызовами функции обмена данными
for(uint32_t i=0; i<0x001FFFFF; i++);
}
}
Для платы STM32L-DISCOVERY отличия в настройках будут связаны с различиями в структуре регистров GPIO между сериями STM32F и STM32L, другими выводами управления светодиодами, другой системной тактовой частотой. Для серии STM32L можно использовать те же выводы микроконтроллера. Но на плате STM32L-DISCOVERY выводы PA7 (MOSI) и PA6 (MISO) не выведены на внешний разъем. Поэтому вместо них задействованы выводы PA12 (MOSI) и PA11 (MISO), остальные выводы те же самые. При настройке выводов GPIO STM32L для работы в режиме альтернативной функции, в частности для использования вывода модулем SPI или другой периферией, в регистрах альтернативных функций GPIO нужной линии порта надо присвоить соответствующую альтернативную функцию. В составе регистров порта ввода-вывода для этой цели предназначены два регистра: AFRL – для линий порта 0..7, AFRH – для линий порта 8..15. У серии STM32L, чтобы модуль SPI управлял определенным выводом порта, то есть использовал этот вывод в качестве одной из своих линий (MOSI, MISO…) необходимо сделать следующие настройки. Сначала для вывода порта нужно задать режим работы с альтернативной функцией. А затем, в регистрах альтернативных функций задать для соответствующих выводов порта номер альтернативной функции. Для SPI1 это альтернативная функция AFIO5. На рисунке ниже приведены регистры альтернативных функций порта GPIOA, где выделенные поля соответствуют выводам порта А, используемым для работы с модулем SPI1. В них и нужно записать число 0x05 для работы этих выводов в режиме альтернативной функции №5 (SPI1).
Для выводов, работающих с SPI2, альтернативная функция в данном случае будет тоже AFIO5. Это выводы PB15(MOSI), PB14(MISO), PB13(SCK), PB12(NSS). Для платы STM32L-DISCOVERY необходимо соединить выводы в таком порядке:
- MOSI: PA12 <-> PB15
- MISO: PA11 <-> PB14
- SCK: PA5 <-> PB13
- NSS: PA4 <-> PB12
Настройки непосредственно модулей SPI для серий STM32F и STM32L абсолютно одинаковы, разница в кодах программ присутствует только в функции инициализации портов GPIO, а также управления светодиодами. Ниже приведен текст программы для серии STM32L, который проверен на плате STM32L-DISCOVERY.
#include "stm32l1xx.h"
uint8_t temp, send_data = 0x01;
//Инициализация GPIO
void gpio_init()
{
RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN; //Тактирование портов A, B
//Линини SPI1 (Master)
//PA12(MOSI), PA11(MISO), PA5(SCK), PA4(NSS) - AF, Push-Pull, AF5(SPI1)
GPIOA->MODER |= GPIO_MODER_MODER12_1 | GPIO_MODER_MODER11_1 | GPIO_MODER_MODER5_1 | GPIO_MODER_MODER4_1; //Alternate function
GPIOA->OTYPER &= ~(GPIO_OTYPER_OT_12 | GPIO_OTYPER_OT_11 | GPIO_OTYPER_OT_5 | GPIO_OTYPER_OT_4); //Push-Pull
GPIOA->AFR[1] |= (5<<16 | 5<<12); //PA12 = AF5, PA11 = AF5
GPIOA->AFR[0] |= (5<<20 | 5<<16); //PA5 = AF5, PA4 = AF5
//Линии SPI2 (Slave)
//PB15(MOSI), PB14(MISO), PB13(SCK), PB12(NSS) - AF, Push-Pull, AF5(SPI1)
GPIOB->MODER |= GPIO_MODER_MODER15_1 | GPIO_MODER_MODER14_1 | GPIO_MODER_MODER13_1 | GPIO_MODER_MODER12_1; //Alternate function
GPIOB->OTYPER &= ~(GPIO_OTYPER_OT_15 | GPIO_OTYPER_OT_14 | GPIO_OTYPER_OT_13 | GPIO_OTYPER_OT_11); //Push-Pull
GPIOB->AFR[1] |= (5<<28 | 5<<24 | 5<<20 | 5<<16); //PB15, PB14, P13, PB12 = AF5
//LED
GPIOB->MODER |= GPIO_MODER_MODER6_0 | GPIO_MODER_MODER7_0; //PB6, PB7 - GP Output
GPIOB->OTYPER &= ~(GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7); //PB6, PB7 - Push-Pull
GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6 | GPIO_OSPEEDER_OSPEEDR7; //40 MHz
GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPDR6 | GPIO_PUPDR_PUPDR7); //No pull
}
//Инициализация SPI1, SPI2
void spi_init()
{
/*Настройка SPI1 (Master)
8 бит данных, MSB передается первым, программный режим управления NSS,
вывод NSS (PA4) разрешено использовать в качестве выхода*/
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; //Тактирование модуля SPI1
SPI1->CR1 |= SPI_CR1_BR; //Baud rate = Fpclk/256
SPI1->CR1 &= ~SPI_CR1_CPOL; //Полярность тактового сигнала
SPI1->CR1 &= ~SPI_CR1_CPHA; //Фаза тактового сигнала
SPI1->CR1 &= ~SPI_CR1_DFF; //8 бит данных
SPI1->CR1 &= ~SPI_CR1_LSBFIRST; //MSB передается первым
SPI1->CR1 |= SPI_CR1_SSM; //Программный режим NSS
SPI1->CR1 |= SPI_CR1_SSI; //Аналогично состоянию, когда на входе NSS высокий уровень
SPI1->CR2 |= SPI_CR2_SSOE; //Вывод NSS - выход управления slave select
SPI1->CR1 |= SPI_CR1_MSTR; //Режим Master
SPI1->CR1 |= SPI_CR1_SPE; //Включаем SPI1
/*Настройка SPI2 (Slave)
8 бит данных, MSB передается первым, аппаратный режим управления NSS,
вывод NSS (PB12) - вход*/
RCC->APB1ENR |= RCC_APB1ENR_SPI2EN; //Тактирование модуля SPI2
SPI2->CR1 &= ~SPI_CR1_DFF; //8 бит данных
SPI2->CR1 &= ~SPI_CR1_CPOL; //Полярность тактового сигнала
SPI2->CR1 &= ~SPI_CR1_CPHA; //Фаза тактового сигнала
SPI2->CR1 &= ~SPI_CR1_LSBFIRST; //MSB передается первым
SPI2->CR1 &= ~SPI_CR1_SSM; //Аппаратное управление входом NSS
SPI2->CR1 &= ~SPI_CR1_MSTR; //Режим Slave
SPI2->CR1 |= SPI_CR1_SPE; //Включаем SPI2
}
/*Функция обмена данными между модулями SPI1 и SPI2.
В качестве аргумента при вызове функции передаются числа 0x01 или 0x03,
которые затем записываются в буфер передатчика SPI2.
Значение, записываемое в буфер передатчика SPI1 неважно, в данном случае 0x0F.
После обмена байтами данных, значение, принятое модулем SPI1 считывается из
регистра данных SPI1_DR в переменную temp*/
void spi_exchange(uint8_t send_data)
{
SPI2->DR = send_data; //Пишем в буфер передатчика SPI2
SPI1->DR = 0x0F; //Пишем в буфер передатчика SPI1. После этого стартует обмен данными
while(!(SPI1->SR & SPI_SR_RXNE)); //Ожидаем окончания приема данных модулем SPI1 (RXNE =1 - приемный буфер содержит данные)
temp = SPI1->DR;//Считываем данные из приемного буфера SPI1. При этой операции происходит очистка буфера и сброс флага RXNE
}
//Основной цикл программы
int main()
{
gpio_init(); //Вызов функции инициализации портов
spi_init(); //Вызов функции инициализации модулей SPI
while(1)
{
/*Вызов функции обмена данными.
В качестве аргумента поочередно передаются значения 0x01 или 0x03*/
spi_exchange(send_data);
switch(temp)
{
/*Если SPI1 принял 0x01, зажигаем соответствующий светодиод,
и меняем значение аргумента, передаваемого функции обмена данными
при следующем вызове на 0x03*/
case 0x01:
GPIOB->BSRRH |= GPIO_BSRR_BS_7;
GPIOB->BSRRL |= GPIO_BSRR_BS_6;
send_data = 0x03;
break;
/*Если SPI1 принял 0x03, зажигаем соответствующий светодиод,
и меняем значение аргумента, передаваемого функции обмена данными
при следующем вызове на 0x01*/
case 0x03:
GPIOB->BSRRH |= GPIO_BSRR_BS_6;
GPIOB->BSRRL |= GPIO_BSRR_BS_7;
send_data = 0x01;
break;
}
//Временная задержка между вызовами функции обмена данными
for(uint32_t i=0; i<0x0001FFFF; i++);
}
}
При отладке в “железе” обнаружен небольшой “баг”. Пошаговое выполнение программы приводит к зависанию на строке
while(!(SPI1->SR & SPI_SR_RXNE));
Это происходит, если для просмотра в отладчике открыты регистры SPI1. С чем связано не знаю, могу предположить, что при этом происходит чтение данных из SPI1_DR отладчиком IAR, а в результате сброс бита RXNE еще до вызова его проверки. В этом случае он так и не устанавливается. Если закрыть панель с содержимым регистров SPI1, все работает стабильно.
Осциллограммы на выводах.
На осциллограммах фазы сигналов MOSI, MISO не привязаны к фазам сигнала SCK, поскольку каждая осциллограмма записана в ждущем режиме отдельно, без привязки к другим сигналам. Поэтому проверять по этим осциллограммам соответствие фаз сигналов режиму, установленному разрядом CPHA модулей, не стоит.
Другие статьи:
Здравствуйте. Хотел спросить был ли опыт работы с модулем SPI на STM32L152? Судя по описанию отличий между ними нет. Однако у меня МК не хочет управлять NSS в режиме hard. Если я правильно понимаю он должен аппаратно сбрасывать сигнал NSS перед началом передачи и устанавливать после завершения передачи. Вместо этого NSS постоянно в нуле. Выводы SPI сконфигурированы как AFIO5.
Я столкнулся с той же проблемой, и решения пока не нашел. Действительно, NSS не управляется как должен. Пробовал различные варианты настроек SPI, но так же не видел изменений уровней на этом выводе. Читаю документацию и думаю…
Непонятно: для проца STM32L152 при выборе другой ноги PA12 на (MOSI) , просто нужно указать, что нога PA12 альтернативная и в регистре альтернативных ф-й задать режим SPI (0x05)? И процессор уже автоматом назначит ногу РА12 на MOSI?
Да.
не совсем понятно с этим NSS. Если у меня STM32 мастер, он управляет по SPI внешней микросхемой. Что нужно сделать с NSS? Можно его просто в воздухе оставить и не подключать к микросхеме?
У микросхемы скорее всего этот вывод должен быть задействован, если она работает Slave. Надо документацию смотреть.
Запустил Ваш проект с замкнутыми друг на друга MISO и MOSI. Убрал инициализацию второго SPI, добавил USART.
Все работает. Но как только добавляю к проекту файл
startup_stm32l1xx_md.s
так сразу хрень в USART прет.
как это влияет?
тут проект
http://files.mail.ru/2B5417EE688C412E99C0E9CB966A7569
Я чуть позже проверю, завтра постараюсь ответить. Еще не понял всей задумки, что в итоге работает? USART1 и SPI1 только на передачу, так?
все просто- без файла
startup_stm32l1xx_md.s , включенного в проект USART корректно работает и все передает.
стоит его только включить, сразу чушь ползет. То -ли по SPI гадость лезет, либо по USART.
но это он 100%
Попробовал все варианты, результат один и тот же, что с этим файлом, что без него:
http://chipspace.ru/picture/02.gif
Такой должен быть результат?
Еще в препроцессоре добавлял пути к файлам:
$PROJ_DIR$\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32L1xx\startup\iar\
$PROJ_DIR$\Libraries\STM32L1xx_StdPeriph_Driver\src\
Результат тот же. Кстати, почему в проекте не прописаны эти пути, а IAR не выдает ошибки, что не находит эти файлы? Я в проекте глубоко не разбирался, может они где-то прописаны?
При запуске в отладчике, в дизассемблере происходит остановка на строке:
0x80005ec: 0xbeab BKPT #0xab
В терминале результат тот же.
Мне кажется, что проще создать новый пустой проект и туда постепенно добавлять все эти функции и подключать библиотеки, проверяя все ПОЭТАПНО. Разбираться откуда берутся проблемы в этом проекте будет намного сложнее и дольше.
Как видите, мой результат несколько отличается, я не заметил различий между работой проектов с этим файлом или без него. Возможно, дело вовсе не в файле startup_stm32l1xx_md.s, который отвечает за низкоуровневую инициализацию контроллера. Но его подключение иногда, но не всегда, может дать какой-то эффект. У меня этот проект вообще ведет себя странно, ему неважно подключен этот файл или нет, прописаны пути к библиотекам или нет.
Я бы не советовал отказываться от этого файла, не могу сказать будут ли последствия. Поэтому советую не лезть в «дебри», а пойти путем, который указал в предыдущем посте.
Кстати, сравнил в Total Commander файл startup_stm32l1xx_md.s из проекта с тем же файлом, но из библиотек на своем компьютере. Полностью идентичны.
А версия IAR 6.50 действительно не ругается на отсутствие путей
$PROJ_DIR$\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32L1xx\startup\iar\
$PROJ_DIR$\Libraries\STM32L1xx_StdPeriph_Driver\src\
Попробовал на новом проекте.
Я то по привычке все прописываю.
Давайте еще раз.
Замыкаем на плате L выводы PA11 PA12.
результат
http://s019.radikal.ru/i614/1302/e5/08fb185635ba.jpg
проект
http://files.mail.ru/CEF255840A1D4BAD883C4C1C20C38254
как только добавляю файл в проект startup_stm32l1xx_md.s
результат
http://i062.radikal.ru/1302/e4/ec52cbe4584f.jpg
т.е. хрень)
Пишу тут статейку, задействовал библиотеки. Заметил, что переменная SystemCoreClock = 32МГЦ, хотя работаю от генератора MSI с частотой 2.097МГЦ.
Попробуйте обновить ее командой SystemCoreClockUpdate(); в самом начале основного цикла main(). У меня после этого значение стало 2.097МГц, то есть равно частоте MSI, как и положено. Может с этим и связаны ваши проблемы.
SPI принимает с ошибками. Пока не нашел почему, но проблему локализовал.
Еще переменная data объявлена как глобальная в main.c и как локальная одновременно в void Usart1_Send_symbol(), хранится в двух регистрах R0 и R4. Именно здесь это не вызывает ошибок, но это неправильно.
Меняй файл system_stm32l1xx.c на родной библиотечный, с ним все работает как надо. Будем разбираться что не так с этой утилитой от ST, про которую я писал здесь
http://chipspace.ru/stm32-system-clock-configuration/
Итак, родной библиотечный файл- все работает в ажуре. Я имею ввиду файл system_stm32l1xx.c
Что же этот файл нам дает? Он дает запуск от кварца 8 МГц и частотой системной 32 МГц. Если кварца нет (а на плату L он не запаян), благодаря этому файлу, частота шины MSI 2 с копейками МГц. Автоматика, да и только. Так вот- кварца нет, все работает. Как только запаял кварц, кондеры, резистор(на схеме есть), сразу хрень.
удалил файл, частота шины стала 2,092 МГц.- вывод в терминалку нормальный.
т.е. упорно не хочет выводить корректно при частоте шины 32 МГц, хоть с кварцем, хоть RC.
SystemCoreClockUpdate(); //обновили частоту
не помогло
На 32МГц не работает, значит. Настройки флэш-памяти тоже сделал, как тут описано
http://chipspace.ru/stm32l-max-sysclk/
иначе бы не работало.
Я поэкспериментирую как работает модуль SPI на максимальных частотах. Быстро не обещаю.
В общем, изменение baud rate не влияет, я уже попробовал, тут и так задана минимальная скорость. Модуль SPI должен работать до 16Мбит/сек, можно задавать делителем коэффициент от минимального 2 на тактовой 32МГц, а у нас 256.
Надо посмотреть сигналы, как доберусь до осциллографа (на работе) — займусь дальше.
И, по-моему, строка
while(!(SPI1->SR & SPI_SR_TXE))
;
как то не на своем месте
А ведь у тебя регистр DR закольцован, работает и на передачу и на прием. В нужный ли момент в таком случае флаг окончания приема RXNE выставляется? Попробуй все же задействовать в качестве приемника второй модуль SPI.
Подтверди пож-ста, что на данной плате, с моим проектом ты получаешь тот же результат? PA11 на PA12 замкнуты.
я вторым SPI задействовал микросхему W5100. И получал результат не очень. Данные от W5100 шли в мой SPI и не корректно воспринимались. Начал копать и вышел на такую странность, как в проекте. Данные от w5100 на осцилле были одни, а воспринимались как другие.
Ага, при 32МГц тактовой неправильно принимает, подтверждаю :). И все же попробуй, как я предлагал, может в этом дело. Чисто ради эксперимента.
Пока на этом остановился, хочу на осциллографе глянуть.
Пока не получилось глянуть осциллограммы, у меня ST-LINK/V2 с Дискавери на работе не хочет определяться, систему недавно переставили, что-то глючит. Попробую завтра внешниий ST-LINK принести и подключиться.
Как и следовало ожидать, без файла startup_stm32l1xx_md.s никакой начальной инициализации у тебя просто нет. Ниже скриншоты работы с файлом и без. Переменная SystemCoreClock — значение тактовой частоты, что ясно из названия. Обновляем это значение функцией SystemCoreClockUpdate(); и видим результат.
Тактовая частота с файлом инициализации
http://chipspace.ru/picture/03.gif
А теперь без него
http://chipspace.ru/picture/04.gif
В этом случае тактируется по дефолту от MSI.
Вот ответ на вопрос — как влияет подключение файла инициализации.
Осталось разобраться с модулями SPI.
Снова советую разомкнуть PA11, PA12 и проверить работу на двух модулях SPI, а не на одном. По datasheet должно работать до 16МБит/сек.
в общем, пришел к такому выводу:
при настройке в утилите тактовой частоты PCLK2 на 8 МГц через делитель APB2 prescaler, при этом сама частота шины = 32 МГц, вне зависимости от кварца или RC, то все работает.
Так же поднимал частоту системную до 12 МГц- все работало.
Получается, что наш SPI не хочет тактироваться от частот выше 12 МГц (а при частоте шины 32 МГц больше 8 МГц и не выставить)
решил проблему.
нужно в инициализацию портов добавлять скорость порта, т.е.
GPIOA->OSPEEDR |=GPIO_OSPEEDER_OSPEEDR5 | GPIO_OSPEEDER_OSPEEDR11 | GPIO_OSPEEDER_OSPEEDR12; //ноги 40 МГц
SPI на 16 МГц работает?
да, все четко. сейчас проверил.
Длина имеет значение !
Сделал такой же длинный шлейф из 4-х проводов как на фото…. залил прошивку…. ни один светодиод не горит
В отладке видно, что temp возвращает 0
добавил в SWITCH горение 2-х светодиодов на этот случай…
case 0x00:
GPIOC->BSRR |= GPIO_BSRR_BS8 | GPIO_BSRR_BS9; // горение 2-х светодиодов на случай отсутствия связи
send_data = 0x03;
break;
решил всё таки проверить влияние длины и формы шлейфа — просто смял его в небольшой комочек и прижал к плате…. замигали поочерёдно светодиоды…
расправил шлейф — горят оба = SPI ничего поймать не может из-за помех…
взял алюминиевый скотч (фольга с липким слоем), обмотал шлейф, к земле не подключал… замигали поочерёдно светодиоды… даже в расправленном положении.
А как в данном примере управляется линия NSS? Со стороны слейва понятно — аппаратный режим и он просто ожидает низкого уровня на NSS. А вот вывод NSS мастера в программном режиме в режиме выхода и установлен бит SSI регистра CR1 т.е. на выводе NSS высокий уровень? в функции передачи по SPI нет кода который бы выставил низкий уровень на NSS(SSI=0). Получается на NSS Всегда высокий уровень? Как тогда принимает данные слейв?
Здесь несколько режимов управления выводом NSS, я попробую написать о них подробней, но придется немного подождать. Я уже сразу не вспомню, надо почитать документацию. А по логике вещей уровень на NSS должен «опускаться» перед началом передачи мастера, то есть сразу после записи числа в его регистр данных.
Запустил пример на STM32L152RBT6 discovery, пробовал и аппартатный и програмный режимы управления линией NSS, в обоих случаях на нем низкий уровень: 0.32В до строчки SPI1->CR1 |= SPI_CR1_SPE; //Включаем SPI1 в функции инициализации spi, после же этой строчки уровень 0.12В Может неисправна моя плата….
Если правильно понял референс мануал, NSS прижимается к земле мастером при начале обмена данными(т.е. до обмена NSS=1 ) и остается на низком уровне до ОТКЛЮЧЕНИЯ модуля SPI — это в аппартаном режиме, данный алгоритм не соответствует нормальной логике работы линии Chip select… неправильный NSS получается
Если же выбран программный режим управления NSS в режиме мастера,NSS в режиме выхода — то в мануале нет информации(SSM=1 SSOE=1).Или в этом случае не актуален прогррамный режим и сигнал на NSS нельзя выставить программно битом SSI и выход ведет себя также как при режиме аппаратногоо управления
//Инициализация SPI1, SPI2
void spi_init()
{
/*Настройка SPI1 (Master)
8 бит данных, MSB передается первым, программный режим управления NSS
вывод NSS (PA4) разрешено использовать в качестве выхода*/
….
….
….
SPI1->CR1 |= SPI_CR1_SSM; //Программный режим NSS
SPI1->CR1 |= SPI_CR1_SSI; //Аналогично состоянию, когда на входе NSS высокий уровень
SPI1->CR2 |= SPI_CR2_SSOE; //Вывод NSS — выход управления slave select
Программный режим мастера (SSI=1), Тогда вывод NSS отключается и используется для других целей. А как же тогда ведомое устр-во переходит в режим Slave ?
если :….
/*Настройка SPI2 (Slave)
SPI2->CR1 &= ~SPI_CR1_SSM; //Аппаратное управление входом NSS ——>> NSS подключается к линии и ждет низкого сигнала для приема в то время как вывод от мастера отключен.
SPI2->CR1 &= ~SPI_CR1_MSTR; //Режим Slave
Программно.