Куб 4x4x4 на восьми 74HC595, который я обламывался собрать в уроке про shiftOut: один слой горит, остальные погашены, кадр надо переобновлять тысячу раз в секунду, иначе глаз ловит стробы. Бюджет кадра — миллисекунда. shiftOut выкатывал восемь байтов примерно за восемьсот микросекунд: восемьдесят процентов бюджета уходило на дёрганье ногами, и loop() едва дышал. Переезд на аппаратный SPI занял вечер и отнял те самые восемьсот микросекунд: тот же кадр стал стоить шестнадцать. Дальше — новая проводка, честный замер micros(), каскад и грабли переезда.
Схема при этом не меняется ни на резистор: шкала из огней как в уроке про бегущие огни, только вместо восьми пинов платы — чип. Меняются два провода и вся механика вывода: shiftOut — программа, ногами имитирующая шину; SPI.transfer — вызов аппаратного блока, который делает то же самое в сотню раз быстрее и освобождает процессор на время передачи.
Проводка: DATA остаётся, CLOCK переезжает
Аппаратная периферия диктует пины, и тут ничего не выбрать. DATA (SER чипа) садится на MOSI — D11, где и жила в shiftOut-версии. CLOCK (SRCLK) переезжает с D12 на SCK — D13: такт теперь даёт железо, а не программа. LATCH (RCLK) — на D10, он же SS: классическое место выбора чипа на шине. Протокол кадра прежний по смыслу: в покое HIGH, на время передачи LOW, в конце короткий HIGH — этим фронтом защёлка выкидывает готовый байт на выходы Q0-Q7. MISO у чипа нет — 595 умеет только слушать, поэтому D12 остаётся свободной. Служебные ножки не трогались: MR к 5 В, OE к GND, керамика 0,1 мкФ у питания каждого корпуса. На Nano и Mega аппаратные пины те же — семейство ATmega держит расклад, так что скетч переезжает между платами без правок.
Каждый кадр — одна транзакция: beginTransaction выставляет восемь мегагерц и MSBFIRST, transfer кладёт байт на шину за пару микросекунд, endTransaction отдаёт настройки. Скорость здесь не для гордости: чип принимает биты хоть на килогерце, но каждую сэкономленную сотню микросекунд loop() тратит на кнопки и датчики, а не на ожидание.
// 74HC595 на аппаратном SPI: SER -> D11 (MOSI), SRCLK -> D13 (SCK),// RCLK -> D10 (CS). MR чипа - к 5 В, OE - к GND.#include <SPI.h> const int PIN_LATCH = 10;unsigned long tStart; void vyvestiKadr(byte b1, byte b2) { // кадр на два каскадных чипа SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0)); digitalWrite(PIN_LATCH, LOW); // выходы заморожены SPI.transfer(b2); // дальнему чипу - первым SPI.transfer(b1); // ближнему - вторым digitalWrite(PIN_LATCH, HIGH); // оба чипа показали кадр разом SPI.endTransaction();} void setup() { pinMode(PIN_LATCH, OUTPUT); digitalWrite(PIN_LATCH, HIGH); SPI.begin(); Serial.begin(9600); tStart = micros(); // замер: 1000 байтов в шину for (int i = 0; i < 1000; i = i + 1) { SPI.transfer(0b10110010); } unsigned long dt = micros() - tStart; Serial.print("1000 bait SPI za "); Serial.print(dt); // на моей Uno: 1900-2100 мкс Serial.println(" mks");} void loop() { byte kadry[8] = {0b00000001, 0b00000010, 0b00000100, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b10000000}; for (int i = 0; i < 8; i = i + 1) { // ближний чип - огонёк, дальний - инверсия того же кадра vyvestiKadr(kadry[i], 0b11111111 - kadry[i]); delay(120); }}Мой замер на живой Uno печатает 1900-2100 мкс на тысячу байтов — около двух микросекунд на байт, из которых большая часть — накладные расходы вызова, а не такт. shiftOut на том же стенде брал порядка сотни: «в сотню раз» — не маркетинг, а арифметика двух замеров. В чужих замерах по сети гуляют числа от полутора десятков до двух сотен с лишним раз: зависит от того, с чем сравнивали — с голым digitalWrite-битбангом или с вылизанным кодом. Замеряйте сами: micros() вокруг тысячи вызовов — и у вас своя честная цифра вместо чужой.
Контрольный замер для скептиков делается тем же скелетом: тот же цикл, тот же байт, но вместо transfer — shiftOut(D11, D12, MSBFIRST, 0b10110010). На моей Uno тысяча вызовов уложилась в сотню с лишним миллисекунд против двух — цифры с разницей в два порядка на одной и той же плате в один и тот же вечер. Отсюда и практический вывод, который я вынес из куба: быстрый кадр бережёт не только проценты загрузки, но и опрос кнопок с чтением датчиков, который больше не ждёт в очереди. Массив кадров в скетче выше — приёмы из урока про массивы и журналы: кадры живут отдельно от логики вывода.
| shiftOut | SPI.transfer | |
|---|---|---|
| Байт на выходе | порядка 100 мкс: два digitalWrite на каждый бит | около 2 мкс при такте 8 МГц |
| Пины | любые цифровые | только D11 и D13: такт не перенести |
| Порядок битов | константа в вызове: MSBFIRST или LSBFIRST | задаётся один раз в SPISettings |
| Каскад | два shiftOut и одна защёлка | то же самое: два transfer и одна защёлка |
| Занятость CPU | программа крутится весь кадр | железо шлёт, loop() почти свободен |
Каскад: тот же поезд, другой локомотив
Каскад переезда не заметил. Два transfer подряд — и байты едут тем же поездом, что в shiftOut-версии: первые восемь тактов проталкивают первый байт сквозь ближний чип во второй, вторые — садятся в ближний. Порядок прежний: дальнему чипу — первый байт, защёлка одна на оба корпуса, кадр встаёт на оба шкафа одновременно. Разница только в масштабе: два transfer стоят четыре микросекунды, и восемь чипов куба обновляются за шестнадцать — против восьмисот у shiftOut. Именно эта разница превращает мультиплексирование на тысяче кадров в секунду из недостижимой мечты в обычный цикл.
Третий, четвёртый, восьмой чип едут тем же составом без новых пинов: QH* каждого — в SER следующего, такт и защёлка общие. Практический потолок на макетке ставит не протокол, а провод: к восьмому корпусу такт доезжает с уже заметным звоном, и если дальние чипы ловят лишние фронты — снижайте частоту, а не перепаивайте. И следите за питанием: восемь корпусов — восемь керамик по 0,1 мкФ, экономия на одной из них однажды проявится как самопроизвольная перезагрузка всей шины.
Порядок битов теперь живёт в SPISettings: MSBFIRST — старший бит уезжает первым и садится на Q7, картинка совпадает с интуицией. Огни побежали задом наперёд — паяльник не нужен: меняйте константу в настройках или зеркальте байт сдвигами из урока про битовые операции. Отдельный козырь, о котором редко пишут: на одной шине с регистрами живут и другие чипы — каждому свой CS, а такт и MOSI общие. Так я однажды подвесил к шине с 595 модуль SD-карты: правило одно — менять частоту через beginTransaction перед каждым чипом, регистрам хватает и одного мегагерца, а карта просит восемь.
Справедливости ради: shiftOut не мусор. На статичной индикации — пара чипов, мигающих раз в секунду, — сотня микросекунд на байт ничего не стоит, а пины остаются свободными; я до сих пор гоняю так комнатный индикатор. Мой водораздел простой: статика — shiftOut, всё, что мультиплексируется, анимируется или делит шину с быстрым соседом, — SPI. Бонус от переезда заметен и на кнопках: кадр перестал глушить опрос, и нажатия ловятся на каждом проходе loop(), как в уроке про кнопку, — раньше на длинных каскадах я терял касания во время выкатки.
Бонус помельче, но тоже приятный: короткий кадр почти не мешает прерываниям. transfer перекрывает обработчик максимум на пару микросекунд, тогда как стомикросекундный shiftOut на фронте с энкодера ронял каждый десятый щелчок. Если у вас в проекте есть attachInterrupt и каскад регистров одновременно — это не сценарий «или-или», просто переведите выкатку на SPI и они перестанут воевать.
- Проводка на аппаратный SPI: SER на D11 (MOSI), SRCLK на D13 (SCK), RCLK на D10 (CS); MISO чип не имеет, D12 остаётся свободной
- SPI.transfer выкатывает байт примерно за 2 мкс против сотни у shiftOut — на тысячах кадров в секунду это разница между «едва дышит» и «обычный цикл»
- Каскад не меняется: дальнему чипу первый байт, transfer вместо shiftOut, одна защёлка на все корпуса
- Порядок битов задаётся в SPISettings, а не в вызове; зеркальная шкала лечится константой, не паяльником
- LATCH — не OE: притянутая к земле защёлка замораживает выходы навсегда; керамика 0,1 мкФ и MR к 5 В остаются обязательными
- 1. Сколько примерно стоит выкатка одного байта через SPI.transfer на Uno при такте 8 МГц?
- 2. Что меняется в проводке 595 при переходе с shiftOut на аппаратный SPI?
- 3. Порядок байтов в каскаде из двух 74HC595 на SPI?
- 4. Замер micros() вокруг 1000 вызовов SPI.transfer показал 1,9 мс. Сколько стоит один байт?
- 5. В SPISettings стоит MSBFIRST, а огни на шкале бегут в обратную сторону. Что править?
Частые вопросы по теме урока
- Сколько примерно стоит выкатка одного байта через SPI.transfer на Uno при такте 8 МГц?
- Восемь бит на 8 МГц — микросекунда на проводе, плюс накладные расходы вызова. Мой замер тысячи байтов: 1900-2100 мкс, то есть около 2 мкс на байт. около 2 мкс: восемь бит по такту плюс накладные вызова
- Что меняется в проводке 595 при переходе с shiftOut на аппаратный SPI?
- Такт теперь даёт аппаратный SCK на D13, а защёлка становится классическим CS на D10. Линия данных и так сидела на MOSI — D11. DATA остаётся на D11, CLOCK переезжает на D13, LATCH — на D10
- Порядок байтов в каскаде из двух 74HC595 на SPI?
- Первые восемь тактов проталкивают первый байт во второй чип, вторые оседают в первом. Импульс защёлки один — оба корпуса обновляются одновременно. первым байтом — дальнему чипу, вторым — ближнему, защёлка одна на оба
- Замер micros() вокруг 1000 вызовов SPI.transfer показал 1,9 мс. Сколько стоит один байт?
- 1900 мкс на тысячу байтов — примерно 2 мкс на каждый. Именно от этой цифры «в сотню раз против shiftOut» и отталкиваются замеры. около 2 мкс
- В SPISettings стоит MSBFIRST, а огни на шкале бегут в обратную сторону. Что править?
- Битовый порядок задаётся в SPISettings и меняется одной константой. Готовый приём — зеркалирование байта сдвигами из урока про битовые операции. порядок в настройках или зеркальный байт в коде — схему не трогать