- PVSM.RU - https://www.pvsm.ru -

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 1

В первой части [1] я попробовал рассказать хобби-электронщикам, выросшим из штанишек Ардуино, как и зачем им стоит читать даташиты и прочую документацию к микроконтроллерам. Текст получился большой, поэтому я пообещал практические примеры показать в отдельной статье. Ну что же, назвался груздем...

Сегодня я покажу, как с помощью даташитов решить довольно простые, но необходимые для множества проектов задачи на контроллерах STM32 (Blue Pill) и STM8. Все демо-проекты посвящены моим любимым светодиодам, зажигать мы их будем в больших количествах, для чего придется задействовать всякую интересную периферию.

Текст опять получился огромный, поэтому для удобства делаю содержание:

STM32 Blue Pill: 16 светодиодов с драйвером DM634 [2]
STM8: Настраиваем шесть выводов ШИМ [3]
STM8: 8 RGB-светодиодов на трех пинах, прерывания [4]

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

В центре этой статьи — даташиты, а не проекты, поэтому код может быть не слишком причесан и часто костылен. Сами проекты очень простые, хотя и годные для первого знакомства с новым чипом.

Надеюсь, что моя статья поможет кому-то на похожем этапе погружения в хобби.

STM32

16 светодиодов c DM634 и SPI

Небольшой проект с использованием Blue Pill (STM32F103C8T6) и светодиодного драйвера DM634. С помощью даташитов разберемся с драйвером, IO-портами STM и настроим SPI.

DM634

Тайваньский чип с 16-ю 16-битными ШИМ-выходами, можно соединять в цепочки. Младшая 12-битная модель известна по отечественному проекту Lightpack [5]. В свое время, выбирая между DM63x и хорошо известным TLC5940, остановился на DM по нескольким причинам: 1) TLC на Алиэкспрессе точно поддельный, а этот – нет; 2) у DM автономный ШИМ со своим генератором частоты; 3) его можно было недорого купить в Москве, а не ждать посылки с Али. Ну и, конечно, было интересно самому научиться управлять чипом, а не использовать готовую библиотеку. Чипы сейчас в основном представлены в корпусе SSOP24, их несложно припаять на переходник.

Поскольку производитель тайваньский, даташит [6] к чипу написан на китайском английском, а значит, будет весело. Сперва смотрим на распиновку (Pin Connection), чтобы понять, к какой ноге что подключать, и описание пинов (Pin Description). 16 выводов:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 2
Источники втекающего постоянного тока (открытый сток)

Sink / Open-drain output – сток; источник втекающего тока; выход, в активном состоянии подключенный к земле, – светодиоды к драйверу подключаются катодами. Электрически это, конечно, никакой не «открытый сток» (open drain), но в даташитах такое обозначение для выводов в режиме стока встречается часто.

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 3
Внешние резисторы между REXT и GND для установки значения выходного тока

Между пином REXT и землей устанавливается референсный резистор, контролирующий внутреннее сопротивление выходов, см. график на стр. 9 даташита. В DM634 этим сопротивлением можно также управлять программно, устанавливая общую яркость (global brightness); в этой статье вдаваться в подробности не буду, просто поставлю сюда резистор на 2.2 – 3 кОм.

Чтобы понять, как управлять чипом, посмотрим на описание интерфейса устройства:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 4

Ага, вот он, китайский английский во всей красе. Перевести это проблематично, понять при желании можно, но есть другой путь – взглянуть, как описывается подключение в даташите к функционально близкому TLC5940:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 5
… Для ввода данных в устройство требуются только три пина. Передний фронт сигнала SCLK сдвигает данные с пина SIN во внутренний регистр. После того, как все данные загружены, короткий высокий сигнал XLAT фиксирует последовательно переданные данные во внутренних регистрах. Внутренние регистры – срабатывающие по уровню сигнала XLAT задвижки. Все данные передаются старшим битом вперед.

Latch – задвижка/защелка/фиксатор.
Rising edge – передний фронт импульса
MSB first – старшим (крайним левым) битом вперед.
to clock data – передавать данные последовательно (побитно).

Слово latch часто встречается в документации к чипам и переводится разнообразно, поэтому для понимания позволю себе

небольшой ликбез

LED-драйвер – по сути сдвиговый регистр. «Сдвиг» (shift) в названии – побитное перемещение данных внутри устройства: каждый новый засунутый внутрь бит пихает всю цепочку перед собой вперед. Поскольку во время сдвига никто не хочет наблюдать хаотичное мигание светодиодов, процесс происходит в буферных регистрах, отделенных от рабочих заслонкой (latch) – это своего рода предбанник, где биты выстраиваются в нужную последовательность. Когда все готово, заслонка открывается, и биты отправляются работать, заменяя предыдущую партию. Слово latch в документации к микросхемам почти всегда подразумевает такую заслонку, в каких бы сочетаниях оно ни использовалось.

Итак, передача данных в DM634 осуществляется так: выставляем вход DAI в значение старшего бита дальнего светодиода, дергаем DCK вверх-вниз; выставляем вход DAI в значение следующего бита, дергаем DCK; и так далее, пока все биты не будут переданы (clocked in), после чего дергаем LAT. Это можно сделать вручную (bit-bang), но лучше воспользоваться специально под это заточенным интерфейсом SPI, благо он представлен на нашем STM32 в двух экземплярах.

Синяя Таблетка STM32F103

Вводные: контроллеры STM32 – значительно сложнее Atmega328, чем могут пугать. При этом из соображений энергосбережения на старте у них отключена почти вся периферия, а тактовая частота составляет 8 МГц от внутреннего источника. К счастью, программисты STM написали код, доводящий чип до «расчетных» 72 МГц, а авторы всех известных мне IDE включили его в процедуру инициализации, поэтому тактировать нам не нужно (но можно, если очень хочется [7]). А вот включить периферию придется.

Документация: на Blue Pill установлен популярный чип STM32F103C8T6, к нему есть два полезных документа:

  • Data Sheet [8] для микроконтроллеров STM32F103x8 и STM32F103xB;
  • Reference Manual [9] для всей линейки STM32F103 и не только.

В даташите нам могут быть интересны:

  • Pinouts – распиновки чипов – на тот случай, если мы решим делать платы сами;
  • Memory Map – карта памяти для конкретного чипа. В Reference Manual есть карта для всей линейки, в ней упомянуты регистры, которых нет на нашем.
  • Таблица Pin Definitions – перечисление основных и альтернативных функций пинов; для «синей таблетки» в интернете можно найти более удобные картинки со списком пинов и их функциями. Поэтому немедленно гуглим Blue Pill pinout и держим вот такую картинку под рукой:
    Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 6

Даташит убираем, открываем Reference Manual, отныне пользуемся только им.
Порядок действий: разбираемся со стандартным вводом/выводом, настраиваем SPI, включаем нужную периферию.

Ввод-вывод

На Atmega328 ввод-вывод реализован предельно просто, из-за чего обилие опций STM32 может сбить с толку. Сейчас нам нужны только выводы, но даже их имеется четыре варианта:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 7
вывод с открытым стоком, вывод «тяни-толкай», альтернативный «тяни-толкай», альтернативный открытый сток

«Тяни-толкай» (push-pull) – привычный вывод с Ардуины, пин может принимать значение либо HIGH, либо LOW. А вот с «открытым стоком» возникают сложности [10], хотя на самом деле тут все просто:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 8
Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 9
Конфигурация вывода / когда порт назначен на вывод: / включен буфер вывода: / – режим открытого стока: «0» в выводном регистре активирует N-MOS, «1» в выводном регистре оставляет порт в режиме Hi-Z (P-MOS не активируется) / – режим «тяни-толкай»: «0» в выводном регистре активирует N-MOS, «1» в выводном регистре активирует P-MOS.

Все отличие открытого стока (open drain) от «тяни-толкай» (push-pull) состоит в том, что в первом пин не может принять состояние HIGH: при записи единицы в выводной регистр он переходит в режим высокого сопротивления (high impedance, Hi-Z). При записи нуля пин в обоих режимах ведет себя одинаково, как логически, так и электрически.

В обычном режиме вывода пин просто транслирует содержимое выводного регистра. В «альтернативном» им управляет соответствующая периферия (см. 9.1.4):

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 10
Если бит порта сконфигурирован как вывод альтернативной функции, выводной регистр отключается, а пин подключается к выводному сигналу периферии

Альтернативный функционал каждого пина описан в Pin Definitions даташита и есть на скачанной картинке. На вопрос, что делать, если у пина несколько альтернативных функций, ответ дает сноска в даташите:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 11
Если несколько периферийных блоков используют один и тот же пин, во избежание конфликта между альтернативными функциями одновременно следует использовать только один периферийный блок, переключаясь с помощью бита активации тактирования периферии (в соответствующем регистре RCC).

Наконец, у пинов в режиме вывода есть еще скорость тактирования. Это еще одна фишка энергосбережения, в нашем случае просто ставим на максимум и забываем.

Итак: мы используем SPI, значит, два пина (с данными и с тактовым сигналом) должны быть «альтернативная функция тяни-толкай», а еще один (LAT) – «обычный тяни-толкай». Но прежде, чем их назначать, разберемся со SPI.

SPI

Еще небольшой ликбез

SPI или Serial Peripherial Interface (последовательный периферийный интерфейс) – простой и весьма эффективный интерфейс для связи МК с другими МК и вообще внешним миром. Принцип его работы уже описан выше, там, где про китайский LED-драйвер (в reference manual см раздел 25). SPI может работать в режиме мастера («хозяина») и слейва («раба»). У SPI есть четыре базовых канала, из которых задействованы могут быть не все:

  • MOSI, Master Output / Slave Input: этот пин в режиме мастера отдает, а в режиме слейва принимает данные;
  • MISO, Master Input / Slave Output: наоборот, в мастере принимает, в слейве – отдает;
  • SCK, Serial Clock: задает частоту передачи данных в мастере или принимает тактовый сигнал в слейве. По сути, отбивает биты;
  • SS, Slave Select: с помощью этого канала слейв узнает, что от него что-то хотят. На STM32 называется NSS, где N = negative, т.е. контроллер становится слейвом, если в этом канале земля. Хорошо комбится с режимом Open Drain Output, но это другая история.

Как и все остальное, SPI на STM32 богат функционалом, что несколько осложняет его понимание. Например, он умеет работать не только SPI, но и I2S-интерфейсом, причем в документации их описания идут вперемешку, надо своевременно отсекать лишнее. У нас же задача крайне простая: надо всего лишь отдавать данные, задействуя только MOSI и SCK. Идем в раздел 25.3.4 (half-duplex communication, полудуплексная связь), где находим 1 clock and 1 unidirectional data wire (1 тактовый сигнал и 1 однонаправленный поток данных):

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 12
В этом режиме приложение использует SPI либо в режиме только передачи, либо только приема. / Режим только передачи похож на дуплексный режим: данные передаются по передающему пину (MOSI в режиме мастера или MISO в режиме слейва), а принимающий пин (MISO или MOSI соответственно) может использоваться как обычный пин ввода-вывода. В этом случае приложению достаточно игнорировать буфер Rx (если его прочитать, там не будет переданных данных).

Отлично, пин MISO у нас освободился, подключим к нему сигнал LAT. Разберемся со Slave Select, которым на STM32 можно управлять программно, что необычайно удобно. Читаем одноименный абзац раздела 25.3.1 SPI General Description:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 13
Программное управление NSS (SSM = 1) / Информация о выборе слейва содержится в бите SSI регистра SPI_CR1. Внешний пин NSS остается свободным для других нужд приложения.

Пора писать в регистры. Я решил использовать SPI2, ищем в даташите его базовый адрес – в разделе 3.3 Memory Map (Карта памяти):

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 14

Ну и начинаем:

#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset)))

Открываем раздел 25.3.3 с говорящим названием «Настройка SPI в режиме мастер»:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 15

1. Установите тактовую частоту последовательного интерфейса битами BR[2:0] в регистре SPI_CR1.

Регистры собраны в одноименном разделе reference manual. Сдвиг адреса (Address offset) у CR1 – 0x00, по умолчанию все биты сброшены (Reset value 0x0000):

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 16

Биты BR устанавливают делитель тактовой частоты контроллера, определяя таким образом частоту, на которой будет работать SPI. Частота STM32 у нас будет 72 МГц, LED-драйвер, согласно его даташиту, работает с частотой до 25 МГц, таким образом, делить надо на четыре (BR[2:0] = 001).

#define _SPI_CR1 0x00

#define BR_0        0x0008
#define BR_1        0x0010
#define BR_2        0x0020

_SPI2_ (_SPI_CR1) |= BR_0;// pclk/4

2. Установите биты CPOL и CPHA, чтобы определить отношения между передачей данных и тактированием последовательного интерфейса (см. схему на стр 240)

Поскольку мы тут читаем даташит, а не рассматриваем схемы, давайте лучше изучим текстовое описание битов CPOL и CPHA на стр. 704 (SPI General Description):

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 17
Фаза и полярность тактового сигнала
С помощью битов CPOL и CPHA регистра SPI_CR1 можно программно выбрать четыре варианта отношений таймингов. Бит CPOL (полярность тактового сигнала) управляет состоянием тактового сигнала, когда данные не передаются. Этот бит управляет режимами мастер и слейв. Если CPOL сброшен, пин SCK в режиме покоя находится в низком уровне. Если бит CPOL установлен, пин SCK в режиме покоя находится в высоком уровне.
Если установлен бит CPHA (фаза тактового сигнала), стробом-ловушкой старшего бита выступает второй фронт сигнала SCK (нисходящий, если CPOL сброшен, или восходящий, если CPOL установлен). Данные фиксируются по второму изменению тактового сигнала. Если бит CPHA сброшен, стробом-ловушкой старшего бита выступает передний фронт сигнала SCK (нисходящий, если CPOL установлен, или восходящий, если CPOL сброшен). Данные фиксируются по первому изменению тактового сигнала.

Вкурив в эти знания, приходим к выводу, что оба бита должны остаться нулями, т.к. нам надо, чтобы сигнал SCK оставался низким, когда не используется, а данные передавались по переднему фронту импульса (см. Rising Edge в даташите DM634).

Кстати, здесь мы впервые столкнулись с особенностью лексики в даташитах ST: в них фраза «сбросить бит в ноль» – пишется to reset a bit, а не to clear a bit, как, например, у Атмеги.

3. Установите бит DFF для определения 8-битного или 16-битного формата блока данных

Я специально взял 16-битный DM634, чтобы не заморачиваться с передачей 12-битных данных ШИМ, как у DM633. DFF имеет смысл поставить в единицу:

#define DFF         0x0800

_SPI2_ (_SPI_CR1) |= DFF; // 16-bit mode

4. Сконфигурируйте бит LSBFIRST в регистре SPI_CR1 для определения формата блока

LSBFIRST, как видно из его названия, настраивает передачу младшим битом вперед. Но DM634 хочет получать данные, начиная со старшего бита. Поэтому оставляем сброшенным.

5. В аппаратном режиме, если требуется ввод с пина NSS, подавайте на пин NSS высокий сигнал во время всей последовательности передачи байтов. В программном режиме NSS установите биты SSM и SSI в регистре SPI_CR1. Если пин NSS должен работать на вывод, надо установить только бит SSOE.

Устанавливаем SSM и SSI, чтобы забыть про аппаратный режим NSS:

#define SSI         0x0100
#define SSM         0x0200

_SPI2_ (_SPI_CR1) |= SSM | SSI; //enable software control of SS, SS high

6. Должны быть установлены биты MSTR и SPE (они остаются установленными только если на NSS подается высокий сигнал)

Собственно, этими битами мы назначаем наш SPI мастером и включаем его:

#define MSTR        0x0004
#define SPE         0x0040

_SPI2_ (_SPI_CR1) |= MSTR; //SPI master
//когда все готово, включаем SPI
_SPI2_ (_SPI_CR1) |= SPE;

SPI настроен, давайте сразу напишем функции, отправляющие байты драйверу. Продолжаем читать 25.3.3 «Настройка SPI в режиме мастер»:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 18
Порядок передачи данных
Передача начинается когда в буфер Tx записывается байт.
Байт данных загружается в сдвиговый регистр в параллельном режиме (из внутренней шины) во время передачи первого бита, после чего передается в последовательном режиме пину MOSI, первым или последним битом вперед в зависимости от установки бита LSBFIRST в регистре CPI_CR1. Флаг TXE устанавливается после передачи данных из буфера Tx в сдвиговый регистр, а также создается прерывание, если установлен бит TXEIE в регистре CPI_CR1.

Я выделил несколько слов в переводе, чтобы обратить внимание на одну особенность реализации SPI в контроллерах STM. На Атмеге флаг TXE (Tx Empty, Tx пуст и готов принимать данные) устанавливается только после того, как весь байт отправился наружу. А здесь этот флаг устанавливается после того, как байт оказался засунут во внутренний сдвиговый регистр. Поскольку пихается он туда всеми битами одновременно (параллельно), а дальше данные передаются последовательно, TXE устанавливается до того, как байт полностью отправится. Это важно, т.к. в случае нашего LED-драйвера нам надо дернуть пин LAT после отправки всех данных, т.е. только флага TXE нам будет недостаточно.

А это значит, что нам нужен еще какой-то флаг. Посмотрим в 25.3.7 – «Флаги статусов»:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 19
<...>
Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 20
Флаг BUSY
Флаг BSY устанавливается и сбрасывается аппаратно (запись в него ни на что не влияет). Флаг BSY показывает состояние коммуникативного слоя SPI.
Он сбрасывается:
когда передача завершена (кроме режима мастера, если передача непрерывна)
когда SPI отключен
когда происходит ошибка режима мастера (MODF=1)
Если передача не непрерывна, флаг BSY сброшен между каждой передачей данных

Окей, пригодится. Выясняем, где находится буфер Tx. Для этого читаем «Регистр данных SPI»:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 21
Биты 15:0 DR[15:0] Регистр данных
Полученные данные или данные для передачи.
Регистр данных разделен на два буфера – один для записи (буфер передачи) и второй для чтения (буфер приема). Запись в регистр данных пишет в буфер Tx, а чтение из регистра данных вернет значение, содержащееся в буфере Rx.

Ну и регистр статусов, где найдутся флаги TXE и BSY:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 22

Пишем:

#define _SPI_DR  0x0C
#define _SPI_SR  0x08

#define BSY         0x0080
#define TXE         0x0002

void dm_shift16(uint16_t value)
{
    _SPI2_(_SPI_DR) = value; //send 2 bytes
    while (!(_SPI2_(_SPI_SR) & TXE)); //wait until they're sent
}

Ну а поскольку нам надо передать 16 раз по два байта, по числу выходов LED-драйвера, то как-то так:

void sendLEDdata()
{
    LAT_low();
    uint8_t k = 16;
    do
    {   k--;
        dm_shift16(leds[k]);
    } while (k);

    while (_SPI2_(_SPI_SR) & BSY); // finish transmission

    LAT_pulse();
}

Но мы пока не умеем дергать пин LAT, поэтому вернемся в I/O.

Назначаем пины

У STM32F1 регистры, отвечающие за состояние пинов, довольно необычны. Понятно, что их больше, чем у Атмеги, но они еще и отличаются от других чипов STM. Раздел 9.1 Общее описание GPIO:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 23
Каждый из портов ввода/вывода общего назначения (GPIO) обладает двумя 32-битными регистрами конфигурации (GPIOx_CRL и GPIOx_CRH), двумя 32-битными регистрами данных (GPIOx_IDR и GPIOx_ODR), 32-битным регистром установки/сброса (GPIOx_BSRR), 16-битным регистром сброса (GPIOx_BRR) и 32-битным блокирующим регистром (GPIOx_LCKR).

Необычны, а также довольно неудобны, здесь первые два регистра, потому что 16 пинов порта разбросаны по ним в формате «по четыре бита на брата». Т.е. пины с нулевого по седьмой сидят в CRL, а остальные – в CRH. При этом остальные регистры успешно умещают в себя биты всех пинов порта – часто оставаясь наполовину «зарезервированными».

Для простоты начнем с конца списка.

Блокирующий регистр нам не потребуется.

Регистры установки и сброса довольно забавны тем, что частично дублируют друг друга: можно все писать только в BSRR, где старшие 16 битов будут сбрасывать пин в ноль, а младшие – устанавливать в 1, либо использовать также BRR, младшие 16 битов которого только сбрасывают пин. Мне по душе второй вариант. Эти регистры важны тем, что обеспечивают атомарный доступ к пинам:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 24
Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 25
Атомарная установка или сброс
Не нужно отключать прерывания при программировании GPIOx_ODR на битовом уровне: можно изменять один или несколько битов одной атомарной операцией записи APB2. Это достигается записью «1» в регистр установки/сброса (GPIOx_BSRR или, только для сброса, в GPIOx_BRR) бита, который требуется изменить. Прочие биты останутся неизменными.

Регистры данных имеют вполне говорящие названия – IDR = Input Direction Register, регистр ввода; ODR = Output Direction Register, регистр вывода. В нынешнем проекте они нам не потребуются.

Ну и, наконец, управляющие регистры. Поскольку нам интересны пины второго SPI, а именно PB13, PB14 и PB15, сразу смотрим на CRH:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 26

И видим, что надо будет что-то написать в биты с 20-го по 31-й.

Мы уже выше разобрались с тем, что мы хотим от пинов, поэтому тут я обойдусь без скриншота, просто скажу, что MODE задает направление (ввод, если оба бита выставлены в 0) и скорость пина (нам нужно 50MHz, т.е. оба пина в «1»), а CNF задает режим: обычный «тяни-толкай» – 00, «альтернативный» – 10. По умолчанию, как мы видим выше, у всех пинов прописан третий снизу бит (CNF0), он устанавливает их в режим floating input.

Поскольку я планирую что-то еще делать с этим чипом, я для простоты задефайнил вообще все возможные значения MODE и CNF как для нижнего, так и для верхнего контрольных регистров.

Ну вот как-то так

#define CNF0_0 0x00000004
#define CNF0_1 0x00000008
#define CNF1_0 0x00000040
#define CNF1_1 0x00000080
#define CNF2_0 0x00000400
#define CNF2_1 0x00000800
#define CNF3_0 0x00004000
#define CNF3_1 0x00008000
#define CNF4_0 0x00040000
#define CNF4_1 0x00080000
#define CNF5_0 0x00400000
#define CNF5_1 0x00800000
#define CNF6_0 0x04000000
#define CNF6_1 0x08000000
#define CNF7_0 0x40000000
#define CNF7_1 0x80000000
#define CNF8_0 0x00000004
#define CNF8_1 0x00000008
#define CNF9_0 0x00000040
#define CNF9_1 0x00000080
#define CNF10_0 0x00000400
#define CNF10_1 0x00000800
#define CNF11_0 0x00004000
#define CNF11_1 0x00008000
#define CNF12_0 0x00040000
#define CNF12_1 0x00080000
#define CNF13_0 0x00400000
#define CNF13_1 0x00800000
#define CNF14_0 0x04000000
#define CNF14_1 0x08000000
#define CNF15_0 0x40000000
#define CNF15_1 0x80000000

#define MODE0_0 0x00000001
#define MODE0_1 0x00000002
#define MODE1_0 0x00000010
#define MODE1_1 0x00000020
#define MODE2_0 0x00000100
#define MODE2_1 0x00000200
#define MODE3_0 0x00001000
#define MODE3_1 0x00002000
#define MODE4_0 0x00010000
#define MODE4_1 0x00020000
#define MODE5_0 0x00100000
#define MODE5_1 0x00200000
#define MODE6_0 0x01000000
#define MODE6_1 0x02000000
#define MODE7_0 0x10000000
#define MODE7_1 0x20000000
#define MODE8_0 0x00000001
#define MODE8_1 0x00000002
#define MODE9_0 0x00000010
#define MODE9_1 0x00000020
#define MODE10_0 0x00000100
#define MODE10_1 0x00000200
#define MODE11_0 0x00001000
#define MODE11_1 0x00002000
#define MODE12_0 0x00010000
#define MODE12_1 0x00020000
#define MODE13_0 0x00100000
#define MODE13_1 0x00200000
#define MODE14_0 0x01000000
#define MODE14_1 0x02000000
#define MODE15_0 0x10000000
#define MODE15_1 0x20000000

Наши пины находятся на порту B (базовый адрес – 0x40010C00), код:

#define _PORTB_(mem_offset) (*(volatile uint32_t *)(0x40010C00 + (mem_offset)))

#define _BRR  0x14
#define _BSRR 0x10
#define _CRL  0x00
#define _CRH  0x04

//используем стандартный SPI2: MOSI на B15, CLK на B13
//LAT пусть будет на неиспользуемом MISO – B14

//очищаем дефолтный бит, он нам точно не нужен
_PORTB_ (_CRH) &= ~(CNF15_0 | CNF14_0 | CNF13_0 | CNF12_0);

//альтернативные функции для MOSI и SCK
_PORTB_ (_CRH) |= CNF15_1 | CNF13_1;

//50 МГц, MODE = 11
_PORTB_ (_CRH) |= MODE15_1 | MODE15_0 | MODE14_1 | MODE14_0 | MODE13_1 | MODE13_0;

И, соответственно, можно написать дефайны для LAT, который будет дергаться регистрами BRR и BSRR:

/*** LAT pulse – high, then low */
#define LAT_pulse() _PORTB_(_BSRR) = (1<<14); _PORTB_(_BRR) = (1<<14)

#define LAT_low() _PORTB_(_BRR) = (1<<14)

(LAT_low просто по инерции, как-то всегда было, пусть себе останется)

Теперь все уже здорово, только не работает. Потому что это STM32, тут экономят электричество, а значит, надо включить тактирование нужной периферии.

Включаем тактирование

За тактирование отвечают часики, они же Clock. И мы уже могли заметить аббревиатуру RCC. Ищем ее в документации: это Reset and Clock Control (Управление сбросом и тактированием).

Как выше было сказано, к счастью, самое сложное из темы тактирования за нас сделали люди из STM, за что им большое спасибо (еще раз дам ссылку на сайт Di Halt'а [7], чтобы было понятно, насколько это заморочено). Нам нужны всего лишь регистры, отвечающие за включение тактирования периферии (Peripheral Clock Enable Registers). Для начала найдем базовый адрес RCC, он в самом начале «Карты памяти»:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 27

#define _RCC_(mem_offset) (*(volatile uint32_t *)(0x40021000 + (mem_offset)))

А дальше либо кликнуть по ссылке, где пытаться в табличке что-то найти, либо, гораздо лучше, пробежаться по описаниям включающих регистров из разделов про enable registers. Где мы найдем RCC_APB1ENR и RCC_APB2ENR:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 28
Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 29

И в них, соответственно, биты, включающие тактирование SPI2, IOPB (I/O Port B) и альтернативных функций (AFIO).

#define _APB2ENR 0x18
#define _APB1ENR 0x1C

#define IOPBEN 0x0008
#define SPI2EN 0x4000
#define AFIOEN 0x0001

//включаем тактирование порта B и альт. функций
_RCC_(_APB2ENR) |= IOPBEN | AFIOEN;

//включаем  тактирование SPI2
_RCC_(_APB1ENR) |= SPI2EN;

Финальный код можно найти тут [11].

Если есть возможность и желание потестить, то подключаем DM634 так: DAI к PB15, DCK к PB13, LAT к PB14. Питаем драйвер от 5 вольт, не забываем объединить земли.

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 30

STM8 PWM

ШИМ на STM8

Когда я только планировал эту статью, я решил для примера попробовать освоить какой-нибудь функционал незнакомого мне чипа с помощью только даташита, чтобы не получался сапожник без сапог. STM8 на эту роль подходил идеально: во-первых, у меня была пара китайских плат с STM8S103, а во-вторых, он не слишком популярен, а потому соблазн почитить и найти решение в интернете упирается в отсутствие этих самых решений.

К чипу также есть даташит [12] и reference manual RM0016 [13], в первом распиновка и адреса регистров, во втором – все остальное. Программируется STM8 на C в страшненькой IDE ST Visual Desktop [14].

Тактирование и ввод-вывод

По умолчанию STM8 работает на частоте 2 МГц, это надо сразу исправить.

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 31
Тактовый сигнал HSI (скоростной внутренний)
Тактовый сигнал HSI получается от внутреннего 16-МГц RC-генератора с программируемым делителем (от 1 до 8). Он задается в регистре делителя тактового сигнала (CLK_CKDIVR).
Примечание: на старте ведущим источником тактового сигнала выбирается HSI RC-генератор с делителем 8.

Находим адрес регистра в даташите, описание в refman и видим, что регистр надо очистить:

#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6

CLK_CKDIVR &= ~(0x18);

Поскольку мы собираемся запускать ШИМ и подключать светодиоды, смотрим распиновку:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 32

Чип маленький, многие функции подвешены на одни и те же пины. То, что в квадратных скобках – «альтернативный функционал», он переключается «байтами опций» (option bytes) – что-то вроде фьюзов Атмеги. Менять их значения можно программно, но не нужно, т.к. активируется новый функционал только после перезагрузки. Проще воспользоваться ST Visual Programmer (качается вместе с Visual Desktop), умеющим менять эти байты. В распиновке видно, что выводы CH1 и CH2 первого таймера спрятаны в квадратные скобки; надо в STVP проставить биты AFR1 и AFR0, причем второй также перенесет вывод CH1 второго таймера с PD4 на PC5.

Таким образом, управлять светодиодами будут 6 пинов: PC6, PC7 и PC3 для первого таймера, PC5, PD3 и PA3 для второго.

Настройка самих пинов ввода-вывода на STM8 проще и логичнее, чем на STM32:

  • знакомый по Atmega регистр направления данных DDR (Data Direction Register): 1 = вывод;
  • первый контрольный регистр CR1 при выводе задает режим «тяни-толкай» (1) или открытый сток (0); поскольку я подключаю светодиоды к чипу катодами, оставляю тут нули;
  • второй контрольный регистр CR2 при выводе задает скорость тактирования: 1 = 10 МГц

#define PA_DDR     *(volatile uint8_t *)0x005002
#define PA_CR2     *(volatile uint8_t *)0x005004
#define PD_DDR     *(volatile uint8_t *)0x005011
#define PD_CR2     *(volatile uint8_t *)0x005013
#define PC_DDR     *(volatile uint8_t *)0x00500C
#define PC_CR2     *(volatile uint8_t *)0x00500E

PA_DDR = (1<<3); //output
PA_CR2 |= (1<<3); //fast
PD_DDR = (1<<3); //output
PD_CR2 |= (1<<3); //fast
PC_DDR = ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //output
PC_CR2 |= ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //fast

Настройка ШИМ

Для начала определимся с терминами:

  • PWM Frequency – частота, с которой тикает таймер;
  • Auto-reload, AR – автозагружаемое значение, до которого будет считать таймер (период импульса);
  • Update Event, UEV – событие, случающееся, когда таймер досчитал до AR;
  • PWM Duty Cycle – коэффициент заполнения ШИМ, часто называют «скважностью»;
  • Capture/Compare Value – значение для захвата/сравнения, досчитав до которого таймер что-то сделает (в случае ШИМ – инвертирует выходной сигнал);
  • Preload Value – предзагруженное значение. Compare value не может меняться, пока таймер тикает, иначе цикл ШИМ поломается. Поэтому новые передаваемые значения помещаются в буфер и вытаскиваются оттуда, когда таймер достигает конца отсчета и сбрасывается;
  • Edge-aligned и Center-aligned modes – выравнивание по границе и по центру, то же, что атмеловские Fast PWM и Phase-correct PWM.
  • OCiREF, Output Compare Reference Signal – референсный выводной сигнал, собственно, то, что в режиме ШИМ оказывается на соответствующем пине.

Как уже ясно из распиновки, возможности ШИМ есть у двух таймеров – первого и второго. Оба 16-битные, первый обладает массой дополнительных фич (в частности, умеет считать и вверх, и вниз). Нам надо, чтобы оба работали одинаково, поэтому я решил начать с заведомо более бедного второго, чтобы случайно не использовать что-то, чего в нем нет. Некоторая проблема состоит в том, что описание функционала ШИМ всех таймеров в reference manual находится в главе про первый таймер (17.5.7 PWM Mode), поэтому приходится все время прыгать туда-сюда по документу.

ШИМ на STM8 обладает важным преимуществом над ШИМ Атмеги:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 33
ШИМ с выравниванием по границе
Конфигурация счета снизу вверх
Счет снизу вверх активен, если бит DIR в регистре TIM_CR1 сброшен
Пример
Пример использует первый режим ШИМ. Референсный сигнал ШИМ OCiREF удерживается в высоком уровне, пока TIM1_CNT < TIM1_CCRi. Иначе он принимает низкий уровень. Если значение для сравнение в регистре TIM1_CCRi больше, чем автозагружаемое значение (регистр TIM1_ARR), сигнал OCiREF удерживается в 1. Если значение для сравнения равно 0, OCiREF удерживается на нуле.

Таймер STM8 во время update event сперва проверяет compare value, и лишь потом выдает референсный сигнал. У Атмеги таймер сперва шарашит, а потом сравнивает, в результате чего при compare value == 0 на выходе получается игла, с которой надо как-то бороться (например, программно инвертируя логику).

Итак, что мы хотим сделать: 8-битный ШИМ (AR == 255), считаем снизу вверх, выравнивание по границе. Поскольку лампочки подключены к чипу катодами, ШИМ должен выдавать 0 (LED горит) до compare value и 1 после.

Мы уже прочитали про некие PWM mode, поэтому находим нужный регистр второго таймера поиском в reference manual по этой фразе (18.6.8 – TIMx_CCMR1):

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 34
110: Первый режим ШИМ – при счете снизу вверх, первый канал активен, пока TIMx_CNT < TIMx_CCR1. В противном случае первый канал неактивен. [дальше в документе ошибочный копипаст из таймера 1]
111: Второй режим ШИМ – при счете снизу вверх, первый канал неактивен, пока TIMx_CNT < TIMx_CCR1. В противном случае первый канал активен.

Поскольку светодиоды подключены к МК катодами, нам подходит второй режим (первый тоже, но мы пока этого не знаем).

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 35
Бит 3 OC1PE: Включить предзагрузку вывода 1
0: Регистр предзагрузки на TIMx_CCR1 выключен. Писать в TIMx_CCR1 можно в любое время. Новое значение работает сразу.
1: Регистр предзагрузки на TIMx_CCR1 включен. Операции чтения/записи обращаются к регистру предзагрузки. Предзагруженное значение TIMx_CCR1 загружается в теневой регистр во время каждого события обновления.
*Примечание: для правильной работы режима ШИМ регистры предзагрузки должны быть включены. Это необязательно в режиме одиночного сигнала (в регистре TIMx_CR1 установлен бит OPM).

Окей, включаем все, что нужно, для трех каналов второго таймера:

#define TIM2_CCMR1 *(volatile uint8_t *)0x005307
#define TIM2_CCMR2 *(volatile uint8_t *)0x005308
#define TIM2_CCMR3 *(volatile uint8_t *)0x005309

#define PWM_MODE2   0x70 //PWM mode 2, 0b01110000
#define OCxPE       0x08 //preload enable

TIM2_CCMR1 = (PWM_MODE2 | OCxPE);
TIM2_CCMR2 = (PWM_MODE2 | OCxPE);
TIM2_CCMR3 = (PWM_MODE2 | OCxPE);

AR состоит из двух восьмибитных регистров, тут все просто:

#define TIM2_ARRH  *(volatile uint8_t *)0x00530F
#define TIM2_ARRL  *(volatile uint8_t *)0x005310

TIM2_ARRH = 0;
TIM2_ARRL = 255;

Второй таймер умеет считать только снизу-вверх, выравнивание по границе, менять ничего не надо. Установим делитель частоты, например, в 256. У второго таймера делитель выставляется в регистре TIM2_PSCR и представляет собой степень двойки:

#define TIM2_PSCR  *(volatile uint8_t *)0x00530E

TIM2_PSCR = 8;

Осталось включить выводы и сам второй таймер. Первая задача решается регистрами Capture/Compare Enable: их два, три канала по ним разбросаны несимметрично. Здесь мы также можем узнать, что можно менять полярность сигнала, т.е. в принципе можно было использовать и PWM Mode 1. Пишем:

#define TIM2_CCER1 *(volatile uint8_t *)0x00530A
#define TIM2_CCER2 *(volatile uint8_t *)0x00530B

#define CC1E  (1<<0) // CCER1
#define CC2E  (1<<4) // CCER1
#define CC3E  (1<<0) // CCER2

TIM2_CCER1 = (CC1E | CC2E);
TIM2_CCER2 = CC3E;

Ну и, наконец, запускаем таймер в регистре TIMx_CR1:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 36

#define TIM2_CR1   *(volatile uint8_t *)0x005300

TIM2_CR1 |= 1;

Напишем простенький аналог AnalogWrite(), который будет передавать таймеру собственно значения для сравнения. Регистры предсказуемо называются Capture/Compare registers, их по два на каждый канал: младшие 8 бит в TIM2_CCRxL и старшие в TIM2_CCRxH. Поскольку мы завели 8-битный ШИМ, достаточно писать только младшие биты:

#define TIM2_CCR1L *(volatile uint8_t *)0x005312
#define TIM2_CCR2L *(volatile uint8_t *)0x005314
#define TIM2_CCR3L *(volatile uint8_t *)0x005316

void setRGBled(uint8_t r, uint8_t g, uint8_t b)
{
    TIM2_CCR1L = r;
    TIM2_CCR2L = g;
    TIM2_CCR3L = b;
}

Внимательный читатель заметит, что у нас получился слегка бракованный ШИМ, неспособный выдать 100% заполнение (при максимальном значении 255 сигнал инвертируется на один цикл таймера). Для светодиодов это не играет роли, а внимательный читатель уже сам догадывается, как это исправить.

ШИМ на втором таймере работает, переходим к первому.

Первый таймер обладает ровно теми же битами в таких же регистрах (просто те биты, что оставались «зарезервированы» во втором таймере, в первом активно используются для всяких продвинутых штук). Поэтому достаточно найти адреса этих же регистров в даташите и скопировать код. Ну и поменять значение делителя частоты, т.к. первый таймер хочет получить не степень двойки, а точное 16-битное значение в два регистра Prescaler High и Low. Все делаем и… первый таймер не работает. В чем дело?

Решить проблему можно только путем просмотра всего раздела про управляющие регистры таймера 1, где ищем тот, которого нет у второго таймера. Найдется 17.7.30 Break register (TIM1_BKR), где есть такой бит:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 37
Включить главный вывод

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

Вот теперь точно все, код там же [11].

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 38

STM8 Multiplex

Мультиплексинг на STM8

Третий мини-проект состоит в том, чтобы подключить к второму таймеру в режиме ШИМ восемь RGB-светодиодов и заставить их показывать разные цвета. В основе – концепция LED-мультиплексинга, состоящая в том, что если очень-очень быстро зажигать и гасить светодиоды, нам будет казаться, что они горят постоянно (persistence of vision, инерция зрительного восприятия). Когда-то я делал что-то такое на Ардуине [15].

Алгоритм работы выглядит так:

  • подключили анод первого RGB LED;
  • зажгли его, подав нужные сигналы на катоды;
  • дождались конца цикла ШИМ;
  • подключили анод второго RGB LED;
  • зажгли его...

Ну и т.д. Разумеется, для красивой работы требуется, чтобы подключение анода и «зажигание» светодиода происходили одновременно. Ну или почти. В любом случае, нам надо написать код, который будет в три канала второго таймера выдавать значения, при достижении UEV менять их и одновременно менять активный в данный момент RGB-светодиод.

Поскольку переключение LED выполняется автоматически, нужно создать «видеопамять», откуда обработчик прерывания будет получать данные. Это простой массив:

uint8_t colors[8][3];

Для того, чтобы поменять цвет конкретного светодиода, достаточно будет записать в этот массив нужные значения. А за номер активного светодиода будет отвечать переменная

uint8_t cnt;

Демукс

Для правильного мультиплексинга нам потребуется, как ни странно, демультиплексор CD74HC238. Демультиплексор – чип, аппаратно реализующий оператор <<. Через три входных пина (биты 0, 1 и 2) мы скармливаем ему трехбитное число X, а он в ответ активирует выход номер (1<<X). Остальные входы чипа используются для масштабирования всей конструкции. Этот чип нам нужен не только для сокращения числа занятых пинов микроконтроллера, но и для безопасности – чтобы случайно не врубить больше светодиодов, чем можно, и не сжечь МК. Чип стоит копейки, его стоит держать в домашней аптечке всегда.

CD74HC238 у нас будет отвечать за то, чтобы подавать напряжение к аноду нужного светодиода. В полноценном мультиплексе он бы подавал напряжение на столбец через P-MOSFET, но в этом демо можно и напрямую, т.к. он тянет 20 мА, согласно absolute maximum ratings в даташите. Из даташита CD74HC238 [16] нам потребуется распиновка и вот эта шпаргалка:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 39
H = высокий уровень напряжения, L = низкий уровень напряжения, X – все равно

Подключаем E2 и E1 к земле, E3, A0, A1 и A3 к пинам PD5, PC3, PC4 и PC5 STM8. Поскольку таблица выше содержит и низкий, и высокий уровни, настраиваем эти пины как push-pull выводы.

ШИМ

ШИМ на втором таймере настраивается так же, как в предыдущей истории, с двумя отличиями:

Во-первых, нам надо включить прерывание на Update Event (UEV), которое будет вызывать функцию, переключающую активный LED. Делается это изменением бита Update Interrupt Enable в регистре с говорящим названием

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 40
Регистр включения прерываний

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

Второе отличие связано с таким явлением мультиплексинга, как ghosting – паразитное свечение диодов. В нашем случае оно может появитсья из-за того, что таймер, вызвав прерывание на UEV, идет тикать дальше, и обработчик прерывания не успевает переключить LED прежде чем таймер уже начнет что-то писать в выводы. Для борьбы с этим придется инвертировать логику (0 = максимальная яркость, 255 = ничего не горит) и не допускать крайних значений скважности. Т.е. добиться того, чтобы после UEV светодиоды полностью гасли на один такт ШИМ.

Меняем полярность:

//set polarity 
    TIM2_CCER1 |= (CC1P | CC2P);
    TIM2_CCER2 |= CC3P;

Избегаем установки r, g и b в 255 и не забываем их инвертировать при использовании.

Прерывания

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

Когда мы в первый раз создали проект в ST Visual Desktop, то кроме main.c мы получили окно с загадочным файлом stm8_interrupt_vector.c, автоматически включенным в проект. В этом файле на каждое прерывание привязана функция NonHandledInterrupt. Нам надо привязать свою функцию к нужному прерыванию.

В даташите есть таблица векторов прерываний, где мы находим нужные:

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 41
13 TIM2 обновление/переполнение
14 TIM2 захват/сравнение

Нам надо менять LED при UEV, так что нужно прерывание №13.

Соответственно, во-первых, в файле stm8_interrupt_vector.c меняем имя функции, отвечающей за прерывание №13 (IRQ13) по умолчанию на свое:

{0x82, TIM2_Overflow}, /* irq13 */

Во-вторых, нам придется создать файл main.h такого содержания:

#ifndef __MAIN_H
#define __MAIN_H

@far @interrupt void TIM2_Overflow (void);
#endif

Ну и, наконец, прописать эту функцию в своем main.c:

@far @interrupt void TIM2_Overflow (void)
{
    PD_ODR &= ~(1<<5); // вырубаем демультиплексор
    PC_ODR = (cnt<<3); // записываем в демультиплексор новое значение
    PD_ODR |= (1<<5); // включаем демультиплексор

    TIM2_SR1 = 0; // сбрасываем флаг Update Interrupt Pending

    cnt++; 
    cnt &= 7; // двигаем счетчик LED

    TIM2_CCR1L = ~colors[cnt][0]; // передаем в буфер инвертированные значения
    TIM2_CCR2L = ~colors[cnt][1]; // для следующего цикла ШИМ
    TIM2_CCR3L = ~colors[cnt][2]; // 

    return;
}

Осталось включить прерывания. Делается это ассемблерной командой rim – искать ее придется в Programming Manual [17]:

//enable interrupts
_asm("rim");

Другая ассемблерная команда – sim – выключает прерывания. Их надо отключать на время записи новых значений в «видеопамять», чтобы вызванное в неудачный момент прерывание не испортило массив.

Весь код – на Гитхабе [11].

Читаем даташиты 2: SPI на STM32; ШИМ, таймеры и прерывания на STM8 - 42

Если хоть кому-то эта статья пригодится, значит, я не зря ее писал. Буду рад комментариям и замечаниям, постараюсь ответить на все.

Автор: Ontaelio

Источник [18]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/stm32/320859

Ссылки в тексте:

[1] первой части: https://habr.com/ru/company/skyeng/blog/449624/

[2] STM32 Blue Pill: 16 светодиодов с драйвером DM634: #stm32

[3] STM8: Настраиваем шесть выводов ШИМ: #stm8-pwm

[4] STM8: 8 RGB-светодиодов на трех пинах, прерывания: #stm8-multiplex

[5] Lightpack: https://lightpack.tv/

[6] даташит: http://www.siti.com.tw/product/spec/LED/SP-DM634-A.004.pdf

[7] можно, если очень хочется: http://easyelectronics.ru/arm-uchebnyj-kurs-taktovyj-generator-stm32.html

[8] Data Sheet: https://www.st.com/resource/en/datasheet/stm32f103c8.pdf

[9] Reference Manual: https://www.st.com/resource/en/reference_manual/cd00171190.pdf

[10] сложности: https://habr.com/ru/post/437234/

[11] тут: https://github.com/Ontaelio/Habra1905

[12] даташит: https://www.st.com/resource/en/datasheet/stm8s103f2.pdf

[13] reference manual RM0016: https://www.st.com/resource/en/reference_manual/cd00190271.pdf

[14] ST Visual Desktop: https://www.st.com/en/development-tools/stvd-stm8.html

[15] что-то такое на Ардуине: https://www.instructables.com/id/LED-Multiplexing-101-6-and-16-RGB-LEDs-With-Just-a/

[16] даташита CD74HC238: http://www.ti.com/lit/ds/schs147i/schs147i.pdf

[17] Programming Manual: https://www.st.com/resource/en/programming_manual/cd00161709.pdf

[18] Источник: https://habr.com/ru/post/456094/?utm_campaign=456094&utm_source=habrahabr&utm_medium=rss