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

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

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

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

Текст знову вийшов величезний, тому для зручності роблю зміст:

STM32 Blue Pill: 16 світлодіодів з драйвером DM634
STM8: Налаштовуємо шість висновків ШІМ
STM8: 8 RGB-світлодіодів на трьох пінах, переривання

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

У центрі цієї статті — датішити, а не проекти, тому код може бути не надто зачесаний і часто милий. Самі проекти дуже прості, хоч і придатні для першого знайомства з новим чіпом.

Сподіваюся, що моя стаття допоможе комусь на схожому етапі занурення у хобі.

STM32

16 світлодіодів c DM634 та SPI

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

DM634

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

Оскільки виробник тайванський, даташіт до чіпа написаний китайською англійською, а значить, буде весело. Спершу дивимося на розпинання (Pin Connection), щоб зрозуміти, до якої ноги що підключати, та опис пінів (Опис штифта). 16 висновків:

Читаємо датішити 2: SPI на STM32; ШИМ, таймери та переривання на STM8
Джерела постійного струму, що втікає (відкритий стік)

Мийка / Вихід з відкритим стоком - Стік; джерело струму; вихід, в активному стані, підключений до землі, – світлодіоди до драйвера підключаються катодами. Електрично це, звичайно, ніякий не «відкритий стік» (open drain), але в даташитах таке позначення висновків у режимі стоку зустрічається часто.

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

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

Щоб зрозуміти, як керувати чіпом, подивимося на опис інтерфейсу пристрою:

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

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

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

Засувка - Засувка / клямка / фіксатор.
Висхідний край - Передній фронт імпульсу
MSB спочатку - Старшим (крайнім лівим) бітом вперед.
to clock data - Передавати дані послідовно (побітно).

Слово засувка часто зустрічається в документації до чіпів і перекладається різноманітно, тому для розуміння дозволю собі

невеликий лікнепLED-драйвер – насправді зсувний регістр. «Зсув» (зсув) у назві - побитное переміщення даних всередині пристрою: кожен новий засунутий всередину біт пхає весь ланцюжок перед собою вперед. Оскільки під час зсуву ніхто не хоче спостерігати хаотичне миготіння світлодіодів, процес відбувається в буферних регістрах, відокремлених від робочих заслінкою (засувка) - це свого роду передбанник, де біти вишиковуються в потрібну послідовність. Коли все готове, заслінка відкривається, і біти вирушають працювати, замінюючи попередню партію. Слово засувка в документації до мікросхем майже завжди має на увазі таку заслінку, в яких би поєднаннях воно не використовувалося.

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

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

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

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

У датасіті нам можуть бути цікаві:

  • Pinouts - розпинування чіпів - на той випадок, якщо ми вирішимо робити плати самі;
  • Memory Map – картка пам'яті конкретного чіпа. У Reference Manual є карта для всієї лінійки, у ній згадані регістри, яких немає на нашому.
  • Таблиця Pin Definitions – перерахування основних та альтернативних функцій пінів; для «синьої пігулки» в інтернеті можна знайти зручніші картинки зі списком пінів та їх функціями. Тому негайно гуглимо Blue Pill pinout і тримаємо ось таку картинку під рукою:

Читаємо датішити 2: SPI на STM32; ШИМ, таймери та переривання на STM8
NB: на картинці з інтернету була помилка, помічена в коментарях, за що дякую. Картинка замінена, але це урок – інформацію не з даташитів краще перевіряти.

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

Ввід вивід

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

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

«Тягни-штовхай» (двотактний) – звичний висновок з Ардуїни, пін може набувати значення або HIGH, або LOW. А ось із «відкритим стоком» виникають складності, хоча насправді тут все просто:

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

Вся відмінність відкритого стоку (open drain) від «тягни-штовхай» (двотактний) полягає в тому, що в першому пін не може прийняти стан HIGH: при записі одиниці у вивідний регістр він переходить у режим високого опору (високий імпеданс, Привіт-З). При записі нуля пін в обох режимах поводиться однаково, як логічно, і електрично.

У звичайному режимі виведення пін просто транслює вміст вивідного регістру. В "альтернативному" ним керує відповідна периферія (див. 9.1.4):

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

Альтернативний функціонал кожного піна описаний у Визначення штифтів даташита і є на завантаженому малюнку. На питання, що робити, якщо у піна кілька альтернативних функцій, відповідь дає виноску в датасіті:

Читаємо датішити 2: SPI на STM32; ШИМ, таймери та переривання на STM8
Якщо кілька периферійних блоків використовують один і той же пін, щоб уникнути конфлікту між альтернативними функціями, одночасно слід використовувати лише один периферійний блок, перемикаючись за допомогою біта активації тактування периферії (у відповідному регістрі 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
У цьому режимі програма використовує SPI або в режимі передачі, або тільки прийому. / Режим тільки передачі схожий на дуплексний режим: дані передаються по передавальному піну (MOSI в режимі майстра або MISO в режимі слейва), а приймаючий пін (MISO або MOSI відповідно) може використовуватися як звичайний пін введення-виводу. У цьому випадку додатку достатньо ігнорувати буфер Rx (якщо його прочитати, там не буде передано даних).

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

Читаємо датішити 2: SPI на STM32; ШИМ, таймери та переривання на STM8
Програмне керування NSS (SSM = 1) / Інформація про вибір слейва міститься в біті регістру SSI SPI_CR1. Зовнішній пін NSS залишається вільним для інших потреб програми.

Час писати в регістри. Я вирішив використовувати SPI2, шукаємо в даташіті його базову адресу - в розділі 3.3 Memory Map (Карта пам'яті):

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

Ну і починаємо:

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

Відкриваємо розділ 25.3.3 з назвою «Налаштування SPI в режимі майстер»:

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

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

Реєстри зібрані в однойменному розділі reference manual. Зсув адреси (Address offset) у CR1 - 0x00, за замовчуванням всі біти скинуті (Скинути значення 0x0000):

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

Біти 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
Фаза та полярність тактового сигналу
За допомогою бітів 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
Порядок передачі
Передача починається коли буфер 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
<...>
Читаємо датішити 2: SPI на STM32; ШИМ, таймери та переривання на STM8
Підкреслити BUSY
Прапор BSY встановлюється та скидається апаратно (запис у нього ні на що не впливає). Прапор BSY показує стан SPI комунікативного шару.
Він скидається:
коли передача завершена (крім режиму майстра, якщо передача безперервна)
коли SPI вимкнено
коли відбувається помилка режиму майстра (MODF=1)
Якщо передача не безперервна, прапор BSY скинутий між кожною передачею даних

Окей, знадобиться. З'ясовуємо, де знаходиться буфер Tx. Для цього читаємо "Регістр даних SPI":

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

Ну і регістр статусів, де знайдуться прапори TXE та BSY:

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

Пишемо:

#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
Кожен із портів введення/виведення загального призначення (GPIO) володіє двома 32-бітними регістрами конфігурації (GPIOx_CRL і GPIOx_CRH), двома 32-бітними регістрами даних (GPIOx_IDR і GPIOx_ODR), 32-бітовим регістром установки/скидання (GPIOx_BSRR), 16-бітовим регістром скиданням (GPIOx_BSRR), 32-бітним регістром скидання (GPIOx_BSRR) (GPIOx_LCKR).

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

Для простоти розпочнемо з кінця списку.

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

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

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

Регістри даних мають назви, що цілком говорять - IDR = вхід Direction Register, регістр введення; ODR = Вихід Direction Register, регістр виведення. У нинішньому проекті вони нам не потрібні.

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

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

І бачимо, що треба буде щось написати в біти з 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'а, щоб було зрозуміло, наскільки це заморочено). Нам потрібні лише регістри, відповідальні включення тактування периферії (Peripheral Clock Enable Registers). Для початку знайдемо базову адресу RCC, вона на самому початку «Карти пам'яті»:

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

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

А далі або клікнути за посиланням, де намагатися в табличці щось знайти, або, краще, пробігтися за описами включають регістрів з розділів про enable registers. Де ми знайдемо RCC_APB1ENR та RCC_APB2ENR:

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

І в них, відповідно, біти, що включають тактування 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;

Фінальний код можна знайти тут.

Якщо є можливість і бажання потішити, підключаємо DM634 так: DAI до PB15, DCK до PB13, LAT до PB14. Живимо драйвер від 5 вольт, не забуваємо об'єднати землі.

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

STM8 PWM

ШИМ на STM8

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

До чіпа також є даташіт и reference manual RM0016, у першому розпинування та адреси регістрів, у другому – все інше. Програмується STM8 на C у страшній IDE ST Visual Develop.

Тактування та введення-виведення

За замовчуванням STM8 працює на частоті 2 МГц, це треба одразу виправити.

Читаємо датішити 2: SPI на STM32; ШИМ, таймери та переривання на STM8
Тактовий сигнал 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

Чіп маленький, багато функцій підвішені на ті самі піни. Те, що у квадратних дужках – «альтернативний функціонал», він перемикається «байтами опцій» (option bytes) – щось на зразок фьюзів Атмегі. Змінювати їх значення можна програмно, але не потрібно, тому що. активується новий функціонал лише після перезавантаження. Простіше скористатися ST Visual Programmer (качається разом з Visual Develop), що вміє змінювати ці байти. У розпинанні видно, що висновки 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

Налаштування ШІМ

Спочатку визначимося з термінами:

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

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

ШІМ на STM8 має важливу перевагу над ШИМ Атмегі:

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

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

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

Ми вже прочитали про якісь ШІМ-режимтому знаходимо потрібний регістр другого таймера пошуком у reference manual за цією фразою (18.6.8 – TIMx_CCMR1):

Читаємо датішити 2: SPI на STM32; ШИМ, таймери та переривання на STM8
110: Перший режим ШІМ – при рахунку знизу вгору перший канал активний, поки TIMx_CNT < TIMx_CCR1. Інакше перший канал неактивний. [далі в документі помилковий копіпаст з таймера 1] 111: Другий режим ШІМ – при рахунку знизу вгору перший канал неактивний, поки TIMx_CNT < TIMx_CCR1. Інакше перший канал активний.

Оскільки світлодіоди підключені до МК катодами, нам підходить другий режим (перший теж, але ми поки що цього не знаємо).

Читаємо датішити 2: SPI на STM32; ШИМ, таймери та переривання на STM8
Біт 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 включити: їх два, три канали по них розкидані несиметрично Тут ми можемо дізнатися, що можна змінювати полярність сигналу, тобто. в принципі можна було використовувати і 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

#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 и низький. Все робимо і перший таймер не працює. В чому справа?

Вирішити проблему можна лише шляхом перегляду всього розділу про керуючі регістри таймера 1, де шукаємо той, якого немає другого таймера. Знайдеться 17.7.30 Break register (TIM1_BKR), де є такий біт:

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

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

Ось тепер точно все, код там же.

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

STM8 Multiplex

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

Третій міні-проект полягає в тому, щоб підключити до другого таймера в режимі ШИМ вісім RGB-світлодіодів та змусити їх показувати різні кольори. В основі – концепція LED-мультиплексингу, яка полягає в тому, що якщо дуже-дуже швидко запалювати та гасити світлодіоди, нам здаватиметься, що вони горять постійно.стійкість зору, Інерція зорового сприйняття). Колись я робив щось таке на Ардуїні.

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

  • підключили анод першого 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 нам знадобиться розпинування і ось ця шпаргалка:

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

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

ШИМ

ШИМ на другому таймері налаштовується так само, як у попередній історії, з двома відмінностями:

По-перше, нам треба включити переривання на Оновити подію (UEV), яке викликатиме функцію, що перемикає активний LED. Робиться це зміною біта Update Interrupt Enable у регістрі з назвою, що говорить

Читаємо датішити 2: SPI на STM32; ШИМ, таймери та переривання на STM8
Реєстр включення переривань

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

Друга відмінність пов'язана з таким явищем мультиплексингу, як привид - Паразитне світіння діодів. У нашому випадку воно може з'явитися через те, що таймер, викликавши переривання на UEV, йде цокати далі, і обробник переривання не встигає переключити LED, перш ніж таймер вже почне щось писати у висновки. Для боротьби з цим доведеться інвертувати логіку (0 = максимальна яскравість, 255 = нічого не горить) і не допускати крайніх значень шпаруватості. Тобто. добитися того, щоб після UEV світлодіоди повністю гасли на один такт ШІМ.

Змінюємо полярність:

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

Уникаємо установки r, g і b 255 і не забуваємо їх інвертувати при використанні.

Переривання

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

Коли ми вперше створили проект у ST Visual Develop, то крім main.c ми отримали вікно із загадковим файлом stm8_interrupt_vector.c, автоматично включеним у проект. У цьому файлі на кожне переривання прив'язана функція NonHandledInterrupt. Нам треба прив'язати свою функцію до потрібного переривання.

У датасіті є таблиця векторів переривань, де ми знаходимо потрібні:

Читаємо датішити 2: SPI на STM32; ШИМ, таймери та переривання на STM8
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 - Шукати її доведеться в Посібник із програмування:

//enable interrupts
_asm("rim");

Інша асемблерна команда – sim - Вимикає переривання. Їх треба відключати на час запису нових значень у «відеопам'ять», щоб викликане в невдалий момент переривання не зіпсувало масив.

Весь код – на Гітхабі.

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

Якщо хоч комусь ця стаття стане в нагоді, значить, я не дарма її писав. Буду радий коментарям та зауваженням, постараюся відповісти на все.

Джерело: habr.com

Додати коментар або відгук