18. Шина SPI
После I²C кажется отдыхом: проще устроен, работает на порядок быстрее, подтяжек не требует. Плата за это — больше проводов и по отдельной линии выбора на каждое устройство. Раздел разбирает режимы такта на уровне фронтов — с моделью, которая показывает, во что превращается байт при неверном режиме, — скорости F103 и F407, и три устройства, которые встречаются почти в каждом приборе: флеш-память, SD-карту и цепочку сдвиговых регистров.
Как это называется в документации
Оригинал Перевод master / slave ведущий / ведомый clock polarity ( CPOL)полярность такта clock phase ( CPHA)фаза такта slave select ( NSS/CS)выбор ведомого full duplex / half duplex полный / полудуплексный обмен baud rate prescaler предделитель скорости busy flag ( BSY)признак незавершённого обмена data frame format ( DFF)разрядность посылки MSB / LSB first старшим / младшим битом вперёд setup / hold time время установки / удержания данных JEDEC ID идентификатор производителя и микросхемы daisy chain цепочка: выход одного — вход следующего latch ( RCLK)защёлка: перенос сдвигового регистра на выходы overrun ( OVR)приём поверх непрочитанного (переполнение приёма)
18.1 Четыре провода
| Линия | Назначение |
|---|---|
| SCK | такт, формирует ведущий |
| MOSI | данные от ведущего к ведомому |
| MISO | данные от ведомого к ведущему |
| CS (NSS) | выбор устройства, активный уровень низкий |
Первые три общие для всех устройств на шине, а линия выбора — своя
у каждого. Три микросхемы потребуют шесть выводов: три общих плюс три
отдельных CS.
Главное свойство, которое надо усвоить сразу: обмен всегда двусторонний.
Внутри стоит кольцевой сдвиговый регистр — с каждым тактом бит уходит
из ведущего и одновременно бит приходит от ведомого. В обычном
двунаправленном режиме отдельной операции «только принять» нет. (Режим
RXONLY есть, но в нём ведущий гонит такт без остановки, пока блок включён,
и выключить его ровно после нужного байта — отдельная задача.)
Отсюда идиома, которая поначалу озадачивает:
uint8_t spi1_read(void)
{
return spi1_xfer(0xFFu);
}
Такт формирует ведущий, поэтому чтобы ведомый что-то выдал, нужно
что‑то отправить. Значение не важно — обычно шлют 0xFF или нули.
На F103 и F407 блок SPI устроен одинаково: те же биты CR1, те же флаги
SR. Различаются выводы и тактирование: у F407 альтернативная функция
выбирается номером в AFR — для SPI1 это 5.
18.2 Четыре режима: CPOL и CPHA
Два бита определяют, как выглядит такт и когда защёлкиваются данные:
| Режим | CPOL |
CPHA |
Такт в покое | Данные читаются |
|---|---|---|---|---|
| 0 | 0 | 0 | низкий | по нарастающему фронту |
| 1 | 0 | 1 | низкий | по спадающему |
| 2 | 1 | 0 | высокий | по спадающему |
| 3 | 1 | 1 | высокий | по нарастающему |
CPHA = 0: первый бит выставлен ещё до первого фронта, читается по
первому фронту, меняется по второму. CPHA = 1: бит выставляется по
первому фронту, читается по второму. Ходовые — 0 и 3: в обоих данные
читаются по нарастающему фронту, поэтому многие микросхемы понимают
любой из них. Режимы 1 и 2 встречаются редко.
Что будет при неверном режиме — модель. Ведущий передаёт байт в своём режиме, ведомый читает в своём; на фронте, где ведущий меняет данные, ведомый успевает прочитать старое значение — так и есть у настоящих микросхем, это время удержания. Итог для байта 0x9F: ведущий в режиме 1, а ведомый в режиме 0 или 3 читает 0x4F вместо 0x9F — каждый бит опоздал на такт, первым прочитан ноль с линии. А ведущий в режиме 0 прочитан верно всеми четырьмя ведомыми: читают они по «своему» фронту или по фронту смены, данные в этот момент ещё прежние.
⚠️ Неверный режим не даёт ошибки и может не проявиться сразу. Обмен идёт, байты приходят — просто не те, или те, но только в одну сторону и только на низкой скорости. Типичный вид — сдвиг на один бит или устойчивый мусор, похожий на данные. Если микросхема не отвечает идентификатором, который обязана вернуть, первым делом проверьте режим.
18.3 Скорость
Задаётся предделителем от 2 до 256, то есть быстрее половины частоты шины не бывает. Сверху есть и второй предел — из описания камня: у F103 такт SPI не выше 18 МГц и у ведущего, и у ведомого.
| Шина | Максимум | |
|---|---|---|
SPI1 (F1) |
APB2, 72 МГц | 18 МГц — предел из описания; делитель 2 дал бы 36 МГц |
SPI2 (F1) |
APB1, 36 МГц | 18 МГц |
SPI1 (F4) |
APB2, 84 МГц | 42 МГц |
SPI2, SPI3 (F4) |
APB1, 42 МГц | 21 МГц |
Как и у UART, порты сидят на разных шинах, и один и тот же делитель даст
разные скорости. На F407 выводам SPI на высокой скорости нужен и быстрый
фронт: поле OSPEEDR — иначе при 21–42 МГц фронты заваливаются.
Практические ориентиры:
- макетная сборка на проводах — не выше 8–10 МГц, иначе помехи;
- дисплей на короткой плате — 20–40 МГц, чтобы кадры успевали;
- инициализация SD‑карты — не быстрее 400 кГц, потом переключение на рабочую скорость (раздел 41). Наибольший делитель 256 даёт 281 кГц у SPI1 на F1 (328 кГц у SPI1 на F4) — как раз укладывается.
18.4 Выбор устройства: аппаратный или ручной
Линией CS можно управлять двумя способами, и почти всегда выбирают второй.
Аппаратный — контроллер сам дёргает вывод NSS. У F1 и F4 он опускается,
когда ведущий начинает обмен, и поднимается, только когда блок выключат
(SPE = 0). Поэтому он годится для одного ведомого, а конец транзакции
приходится отмечать выключением SPI. У блоков нового образца есть ещё импульс
между посылками (NSSP, 18.12) — он как раз снимает выбор между байтами
и ломает многобайтные команды.
Программный — вы объявляете CS обычным выходом и управляете им сами.
Так делают почти всегда: это предсказуемо и позволяет держать выбор
на протяжении всей транзакции.
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | (br << 3); /* режим 0, 8 бит */
Оба бита обязательны: SSM переводит управление в программный режим,
а SSI задаёт внутренний уровень. Забыли SSI — контроллер решит, что его
самого выбрал другой ведущий, и уйдёт из режима ведущего.
18.5 🔧 Обмен блоком данных
void spi1_transfer_block(const uint8_t *tx, uint8_t *rx, uint32_t len)
{
CS_LOW();
for (uint32_t i = 0; i < len; i++) {
uint8_t in = spi1_xfer(tx ? tx[i] : 0xFFu);
if (rx) {
rx[i] = in;
}
}
/* RXNE говорит лишь о принятом байте; последний бит может ещё уходить. */
while (SPI1->SR & SPI_SR_BSY) { }
CS_HIGH();
}
⚠️ Перед снятием
CSдождитесь сбросаBSY. ФлагRXNEозначает, что принятый байт лежит в регистре, но последние биты могут ещё идти по линии. Снимете выбор раньше — обрежете хвост посылки. Проявляется это как «иногда работает»: на низкой скорости успевает, на высокой нет.
18.6 SPI и DMA
Для потоковых задач — вывода на дисплей, чтения флеш‑памяти, приёма с АЦП — обмен байт за байтом в цикле съедает процессор целиком. Кадр дисплея 240×320 в 16 бит — это 150 килобайт, которые надо перегнать как можно быстрее.
Здесь DMA обязателен (раздел 9). Для двустороннего обмена нужны два
потока — один на передачу, другой на приём. Если приём не нужен (вывод
на дисплей), хватит одного потока на передачу: флаг переполнения приёма
OVR взведётся, но передачу он не останавливает — теряются только
принятые байты. Поэтому после передачи прочитайте DR и SR по разу —
это сбрасывает RXNE и OVR, иначе следующий приём вернёт старый байт.
18.7 Что ещё умеет блок
16‑битные посылки. Бит DFF переключает разрядность. Для дисплеев,
где цвет кодируется 16 битами, это вдвое сокращает число операций
и нагрузку на шину. Менять DFF можно только при выключенном блоке:
SPI1->CR1 &= ~SPI_CR1_SPE;
SPI1->CR1 |= SPI_CR1_DFF;
SPI1->CR1 |= SPI_CR1_SPE;
Полудуплекс. Одна линия данных вместо двух, направление переключается программно. Экономит вывод, применяется в некоторых датчиках и дисплеях.
Режим ведомого. Микроконтроллер может сам быть ведомым — например, подчинённым модулем в большой системе. Тогда такт формирует внешний ведущий, а вы обязаны успевать за ним, что почти всегда означает DMA. Сколько на это времени и как строят протокол — раздел 17а.
Аппаратный расчёт CRC. Блок умеет считать контрольную сумму передаваемых данных сам — полезно в шумной среде.
I²S на F407. SPI2 и SPI3 умеют звуковой интерфейс I²S — это тот же
блок в другом режиме: цифровой звук для кодеков и микрофонов.
18.8 Дисплей: время кадра
Кадр 240 × 320 точек по 16 бит — 1 228 800 бит. Предел по скорости SPI:
- F103,
SPI1на 18 МГц (предел из описания, делитель 4): 68,27 мс, не больше 14,6 кадра в секунду; - F407,
SPI1на 42 МГц (делитель 2): 29,26 мс, не больше 34,2 кадра в секунду.
Это время без единой паузы между словами. Цикл с опросом TXE на такой
скорости паузы оставляет — процессор не успевает класть слово в тот же
такт, когда регистр освободился, — и реальная частота кадров ниже.
Поэтому для дисплея — 16-битные посылки и DMA, а процессор тем временем
готовит следующий кусок кадра. Если нужно быстрее — перерисовывать
только изменившиеся области: у дисплейных контроллеров для этого есть
команды окна.
Заливка цветом в 16-битном режиме, без приёма:
while (!(SPI1->SR & SPI_SR_TXE)) { }
SPI1->DR = color;
18.9 🔧 Идентификатор флеш-памяти JEDEC
Первое, что делают с новой SPI-флеш: читают идентификатор. Команда 0x9F, в ответ три байта — производитель (0xEF у Winbond), тип памяти и ёмкость в виде степени двойки. Если пришли 0xFF — нет ответа (MISO висит в единице), если 0x00 — вывод прижат к земле или микросхема не выбрана. Функция одна для F103 и F407:
void flash_jedec_id(uint8_t id[3])
{
CS_LOW();
spi1_xfer(0x9F);
id[0] = spi1_xfer(0xFF); /* производитель: 0xEF — Winbond */
id[1] = spi1_xfer(0xFF); /* тип памяти */
id[2] = spi1_xfer(0xFF); /* ёмкость: 2^id[2] байт */
spi1_done();
CS_HIGH();
}
Ответ на первый байт — 0x9F — пропускается: пока команда уходит, микросхема ещё ничего не знает и отдаёт мусор. Верный идентификатор — лучшая проверка режима, скорости и разводки сразу: запись и стирание (раздел 41) пробуют только после него.
Так чтение идентификатора выглядит на экране анализатора:
18.10 SD-карта: кадр команды и CRC7
В режиме SPI SD-карта принимает команды кадрами по 6 байт: 01 и номер команды, 32 бита аргумента, CRC7 и стоповая единица. После перехода в режим SPI (CMD0 при опущенном CS) проверка CRC выключена — кроме CMD8: её карта проверяет всегда. Поэтому верный CRC нужен двум командам, CMD0 и CMD8. Считается он так:
uint8_t sd_crc7(const uint8_t *p, int n)
{
uint8_t crc = 0;
int i, b;
for (i = 0; i < n; i++)
for (b = 7; b >= 0; b--) {
int in = ((p[i] >> b) & 1) ^ ((crc >> 6) & 1);
crc = (uint8_t)((crc << 1) & 0x7F);
if (in)
crc ^= 0x09;
}
return (uint8_t)((crc << 1) | 1);
}
Проверка — два кадра, которые знает каждый, кто оживлял SD-карту: CMD0 — 0x40 00 00 00 00 95, CMD8 с аргументом 0x1AA — 0x48 00 00 01 AA 87. Перед CMD0 карта должна получить не меньше 74 тактов при снятом выборе — это десять посылок 0xFF, и всё это на скорости до 400 кГц (18.3); полный порядок запуска карты — в разделе 41.
18.11 🔧 Цепочка сдвиговых регистров 74HC595
Когда выводов не хватает, выходы наращивают сдвиговыми регистрами
74HC595: у каждого 8 выходов, выход последнего бита соединяют со входом
следующего, и три микросхемы дают 24 выхода на трёх проводах — такт,
данные и защёлку RCLK. Приёма нет, MISO не нужен. Сама микросхема и её входы — в разделе 22.1.
Порядок байтов обратный интуитивному: первый отправленный байт проталкивается дальше всех. Модель цепочки: отправлены C3, 5A, 81 — в ближнем к микроконтроллеру регистре 81, в дальнем C3. Поэтому байт для дальнего регистра уходит первым:
void hc595_write3(const uint8_t v[3])
{
spi1_xfer(v[2]);
spi1_xfer(v[1]);
spi1_xfer(v[0]);
spi1_done(); /* последний бит должен дойти до регистра */
RCLK_HIGH(); /* фронт защёлки — все 24 выхода меняются разом */
RCLK_LOW();
}
Защёлка — главное удобство: пока байты едут по цепочке, выходы стоят
как стояли, и все 24 меняются одним фронтом. Дёрнуть её до сброса BSY —
значит защёлкнуть сдвинутое не до конца.
18.12 F0 и G0: разрядность в DS и байт в DR
Регистры SPI у F0 и G0 лежат там же, где у F1: CR1 0x00, CR2 0x04,
SR 0x08, DR 0x0C. Режимы CPOL/CPHA, делитель BR, программный
NSS через SSM/SSI — всё то же. Отличий два, и оба про разрядность.
Нет бита DFF. Разрядность посылки задаёт четырёхбитное поле DS
в CR2: любое значение от 4 до 16 бит, число минус один — 0111 для
8 бит, 1111 для 16. Код с SPI_CR1_DFF на F0 не собирается:
use of undeclared identifier 'SPI_CR1_DFF'.
SPI1->CR2 = (7u << SPI_CR2_DS_Pos) | SPI_CR2_FRXTH; /* 8 бит; RXNE — как только есть байт */
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_1; /* /8 */
SPI1->CR1 |= SPI_CR1_SPE;
У DR есть очередь. За регистром данных стоят очереди на четыре байта
в каждую сторону; их заполнение видно в SR — поля FTLVL и FRLVL.
Отсюда правило: при посылках до 8 бит к DR обращаются байтом.
Обычная запись SPI1->DR = b — 16-битная, и блок кладёт в очередь два байта:
на линию уйдут b и ноль, а приём собьётся на один байт. Бит FRXTH
заставляет RXNE подниматься уже при одном принятом байте, а не при двух:
uint8_t spi1_xfer_f0(uint8_t out)
{
*(volatile uint8_t *)&SPI1->DR = out; /* именно байт: 16-битная запись кладёт в очередь два */
while (!(SPI1->SR & SPI_SR_RXNE)) {
}
return *(volatile uint8_t *)&SPI1->DR;
}
В объектном файле это видно: обмен собирается в STRB и LDRB, а не
в STRH/LDRH. Обмен блоком из 18.5 и все рецепты дальше переносятся
на F0/G0 заменой одной этой функции.
Остальное — по таблице:
| F1 | F0 | G0 | |
|---|---|---|---|
| такт SPI1 | APB2ENR |
APB2ENR |
APBENR2 |
SCK/MISO/MOSI на PA5/PA6/PA7 |
AF по умолчанию | AF0 | AF0 |
PCLK для SPI1 при максимуме |
72 МГц | 48 МГц | 64 МГц |
SCK при делителе 8 |
9 МГц | 6 МГц | 8 МГц |
Ещё у нового блока есть импульс NSS между посылками (NSSP в CR2)
и аппаратный CRC любой длины (CRCL); ни то ни другое для дисплеев
и флеш-памяти из этого раздела не нужно.
18.13 Грабли
Микросхема не отвечает. Проверьте по порядку: режим (CPOL/CPHA),
не перепутаны ли MOSI и MISO, дошёл ли CS до нужного вывода,
есть ли питание и общая земля. Идентификатор JEDEC (18.9) — быстрый тест.
Не выставлен SSI. Контроллер выходит из режима ведущего.
Данные обрезаются на высокой скорости. CS снят до сброса BSY.
Сдвиг на один бит. Почти всегда неверная фаза такта: в модели 0x9F превращается в 0x4F.
Мусор при нескольких устройствах. Два CS активны одновременно, либо
неактивные устройства не отпускают MISO. Приличные микросхемы переводят
MISO в высокоимпедансное состояние при снятом выборе, дешёвые — не всегда.
SD-карта отпускает MISO только после такта при поднятом CS: после каждой
транзакции с ней шлют один пустой байт 0xFF с уже поднятым CS.
Помехи на длинных проводах. SPI не имеет ни подтяжек, ни контроля ошибок: искажённый бит просто становится другим байтом. На макетке снижайте скорость.
Дисплей работает, флешка нет (или наоборот) на одной шине. Устройствам нужны разные режимы или разные скорости — перенастраивайте контроллер перед обращением к каждому.
После вывода на дисплей приём сломан. Передавали без чтения, взвёлся
OVR. Прочитать DR и SR.
SD-карта не отвечает на CMD0. Неверный CRC, мало тактов перед командой или скорость выше 400 кГц.
На выходах 74HC595 байты перепутаны. Отправлены в прямом порядке — первым должен уходить байт дальнего регистра.
18.14 Итоги
- Обмен всегда двусторонний: чтобы принять байт, надо передать любой.
- Режим задают
CPOLиCPHA; ходовые — 0 и 3. Неверный режим не даёт ошибки: в модели 0x9F читается как 0x4F, а иногда и верно — до поры. - Скорость — не выше половины частоты шины и предела из описания камня:
SPI1— 18 МГц на F103 и 42 МГц на F407. - Выбором устройства управляйте программно, включив оба бита
SSMиSSI. - Перед снятием
CSи перед защёлкой дожидайтесь сбросаBSY. - Кадр дисплея 240 × 320 × 16 бит — 68,27 мс на F103 и 29,26 мс на F407 без пауз; дальше — только DMA и частичная перерисовка.
- Идентификатор JEDEC — первая проверка флеш-памяти; кадр SD-команды с CRC7: CMD0 заканчивается на 95.
- Каждому устройству — своя линия выбора; общие только
SCK,MOSI,MISO.
Полные исходники
spi18.c — SPI1 ведущим на F103 и F407: идентификатор флеш-памяти, 74HC595, команда SD-карты, посылки дисплею.
/* Раздел 18 (обогащение): SPI1 ведущим на F103 и F407 — идентификатор флеш-памяти,
* цепочка 74HC595, кадр команды SD-карты, 16-битные посылки для дисплея.
* Собирается armclang с -include stm32f10x.h (STM32F10X_MD) или stm32f4xx.h (STM32F407xx). */
#include <stdint.h>
#if defined(STM32F10X_MD)
#define PCLK2_HZ 72000000u /* SPI1 на APB2 */
#define PCLK1_HZ 36000000u /* SPI2 на APB1 */
#else
#define PCLK2_HZ 84000000u
#define PCLK1_HZ 42000000u
#endif
#define CS_LOW() (GPIOA->BSRR = 1u << (4 + 16))
#define CS_HIGH() (GPIOA->BSRR = 1u << 4)
#define RCLK_LOW() (GPIOA->BSRR = 1u << (3 + 16))
#define RCLK_HIGH() (GPIOA->BSRR = 1u << 3)
/* PA5 SCK, PA6 MISO, PA7 MOSI — SPI1; PA4 — выбор флеш-памяти, PA3 — защёлка 74HC595. */
void spi1_init(uint32_t br)
{
#if defined(STM32F10X_MD)
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN | RCC_APB2ENR_IOPAEN;
GPIOA->CRL = (GPIOA->CRL & 0x0000FFFFu)
| (0x3u << 12) /* PA3: выход 50 МГц */
| (0x3u << 16) /* PA4: выход 50 МГц */
| (0xBu << 20) /* PA5: альтернативная функция, push-pull */
| (0x4u << 24) /* PA6: вход без подтяжки */
| (0xBu << 28); /* PA7: альтернативная функция, push-pull */
#else
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
GPIOA->MODER = (GPIOA->MODER & ~0x0000FFC0u)
| (1u << 6) | (1u << 8) /* PA3, PA4 — выходы */
| (2u << 10) | (2u << 12) | (2u << 14); /* PA5–PA7 — альтернативная функция */
GPIOA->OSPEEDR |= 0x0000FC00u; /* PA5–PA7 — высокая скорость фронтов */
GPIOA->AFR[0] = (GPIOA->AFR[0] & 0x000FFFFFu) | (5u << 20) | (5u << 24) | (5u << 28); /* AF5 = SPI1 */
#endif
CS_HIGH();
RCLK_LOW();
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | (br << 3); /* режим 0, 8 бит */
SPI1->CR1 |= SPI_CR1_SPE;
}
uint8_t spi1_xfer(uint8_t data)
{
while (!(SPI1->SR & SPI_SR_TXE)) { }
*(volatile uint8_t *)&SPI1->DR = data;
while (!(SPI1->SR & SPI_SR_RXNE)) { }
return (uint8_t)SPI1->DR;
}
static void spi1_done(void)
{
while (SPI1->SR & SPI_SR_BSY) { }
}
/* Идентификатор JEDEC: команда 0x9F, в ответ производитель, тип и ёмкость. */
void flash_jedec_id(uint8_t id[3])
{
CS_LOW();
spi1_xfer(0x9F);
id[0] = spi1_xfer(0xFF); /* производитель: 0xEF — Winbond */
id[1] = spi1_xfer(0xFF); /* тип памяти */
id[2] = spi1_xfer(0xFF); /* ёмкость: 2^id[2] байт */
spi1_done();
CS_HIGH();
}
/* Три 74HC595 в цепочке: v[0] — ближний к микроконтроллеру, поэтому первым уходит v[2]. */
void hc595_write3(const uint8_t v[3])
{
spi1_xfer(v[2]);
spi1_xfer(v[1]);
spi1_xfer(v[0]);
spi1_done(); /* последний бит должен дойти до регистра */
RCLK_HIGH(); /* фронт защёлки — все 24 выхода меняются разом */
RCLK_LOW();
}
/* CRC7 команды SD: многочлен x^7 + x^3 + 1, в конце сдвиг и единица стопового бита. */
uint8_t sd_crc7(const uint8_t *p, int n)
{
uint8_t crc = 0;
int i, b;
for (i = 0; i < n; i++)
for (b = 7; b >= 0; b--) {
int in = ((p[i] >> b) & 1) ^ ((crc >> 6) & 1);
crc = (uint8_t)((crc << 1) & 0x7F);
if (in)
crc ^= 0x09;
}
return (uint8_t)((crc << 1) | 1);
}
/* Кадр команды SD: 01 + номер, 32 бита аргумента, CRC7 и стоповая единица — 6 байт. */
void sd_frame(uint8_t cmd, uint32_t arg, uint8_t out[6])
{
out[0] = (uint8_t)(0x40 | cmd);
out[1] = (uint8_t)(arg >> 24);
out[2] = (uint8_t)(arg >> 16);
out[3] = (uint8_t)(arg >> 8);
out[4] = (uint8_t)arg;
out[5] = sd_crc7(out, 5);
}
/* Дисплей: 16-битные посылки — пиксель RGB565 одной записью в DR. Менять DFF только при SPE = 0. */
void spi1_pixels(uint16_t color, uint32_t n)
{
SPI1->CR1 &= ~SPI_CR1_SPE;
SPI1->CR1 |= SPI_CR1_DFF;
SPI1->CR1 |= SPI_CR1_SPE;
while (n--) {
while (!(SPI1->SR & SPI_SR_TXE)) { }
SPI1->DR = color;
}
spi1_done();
(void)SPI1->DR; /* принятое при передаче не нужно: сбросить RXNE и OVR */
(void)SPI1->SR;
}
/* ── проверка расчётов ────────────────────────────────────────────────── */
_Static_assert(SPI_CR1_DFF == (1u << 11), "DFF — бит 11 у обеих серий");
_Static_assert(SPI_CR1_BR == (0x7u << 3), "делитель — биты 3–5");
_Static_assert(SPI_CR1_SSM == (1u << 9) && SPI_CR1_SSI == (1u << 8), "программный выбор");
_Static_assert(PCLK2_HZ / 2u == (PCLK2_HZ == 84000000u ? 42000000u : 36000000u), "SPI1 — половина APB2");
_Static_assert(PCLK1_HZ / 2u == (PCLK1_HZ == 42000000u ? 21000000u : 18000000u), "SPI2 — половина APB1");
/* Кадр 240 × 320 × 16 бит при делителе 2: миллисекунды на передачу без пауз. */
_Static_assert(240u * 320u * 16u / (PCLK2_HZ / 2u / 1000u) == (PCLK2_HZ == 84000000u ? 29u : 34u), "кадр, мс");
int main(void)
{
uint8_t id[3], frame[6];
static const uint8_t leds[3] = { 0xC3, 0x5A, 0x81 };
spi1_init(0u); /* делитель 2 */
flash_jedec_id(id);
hc595_write3(leds);
sd_frame(0, 0, frame);
spi1_pixels(0xF800u, 240u * 320u);
for (;;) { }
}
spi_f0.c — SPI на STM32F072: поле DS и 8-битный доступ к DR.
/* Раздел 18.13: SPI на STM32F072 — SPI1 на PA5/PA6/PA7 (AF0), 8-битные посылки
* через поле DS, обмен байтом через 8-битный доступ к DR. */
#include <stddef.h>
#include "stm32f0xx.h"
_Static_assert(offsetof(SPI_TypeDef, DR) == 0x0Cu && offsetof(SPI_TypeDef, SR) == 0x08u, "регистры как у F1");
_Static_assert(SPI_CR2_DS_Pos == 8u && (7u << SPI_CR2_DS_Pos) == 0x0700u, "DS = 0111 — 8 бит");
/* PCLK 48 МГц, делитель 8 (BR = 010) — 6 МГц на SCK */
#define PCLK_HZ 48000000u
_Static_assert(PCLK_HZ / 8u == 6000000u, "6 МГц");
void spi1_init_f0(void)
{
RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
GPIOA->AFR[0] &= ~((0xFu << 20) | (0xFu << 24) | (0xFu << 28)); /* AF0 */
GPIOA->MODER = (GPIOA->MODER & ~((3u << 10) | (3u << 12) | (3u << 14)))
| (2u << 10) | (2u << 12) | (2u << 14);
SPI1->CR2 = (7u << SPI_CR2_DS_Pos) | SPI_CR2_FRXTH; /* 8 бит; RXNE — как только есть байт */
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_1; /* /8 */
SPI1->CR1 |= SPI_CR1_SPE;
}
uint8_t spi1_xfer_f0(uint8_t out)
{
*(volatile uint8_t *)&SPI1->DR = out; /* именно байт: 16-битная запись кладёт в очередь два */
while (!(SPI1->SR & SPI_SR_RXNE)) {
}
return *(volatile uint8_t *)&SPI1->DR;
}
spi_g0.c — тот же SPI на STM32G071.
/* Раздел 18.13: тот же SPI на STM32G071 — SPI1 на PA5/PA6/PA7 (AF0), такт с APBENR2. */
#include <stddef.h>
#include "stm32g0xx.h"
_Static_assert(offsetof(SPI_TypeDef, DR) == 0x0Cu, "блок тот же, что у F0");
_Static_assert((7u << SPI_CR2_DS_Pos) == 0x0700u, "DS = 0111 — 8 бит");
/* PCLK 64 МГц, делитель 8 — 8 МГц на SCK */
#define PCLK_HZ 64000000u
_Static_assert(PCLK_HZ / 8u == 8000000u, "8 МГц");
void spi1_init_g0(void)
{
RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
RCC->APBENR2 |= RCC_APBENR2_SPI1EN; /* у G0 — APBENR2 */
GPIOA->AFR[0] &= ~((0xFu << 20) | (0xFu << 24) | (0xFu << 28));
GPIOA->MODER = (GPIOA->MODER & ~((3u << 10) | (3u << 12) | (3u << 14)))
| (2u << 10) | (2u << 12) | (2u << 14);
SPI1->CR2 = (7u << SPI_CR2_DS_Pos) | SPI_CR2_FRXTH;
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_1;
SPI1->CR1 |= SPI_CR1_SPE;
}
uint8_t spi1_xfer_g0(uint8_t out)
{
*(volatile uint8_t *)&SPI1->DR = out;
while (!(SPI1->SR & SPI_SR_RXNE)) {
}
return *(volatile uint8_t *)&SPI1->DR;
}
Дальше: раздел 18а «Звук по I2S: кадр, форматы и точная частота отсчётов» — второй режим модулей SPI.