Читаємо датішити 2: SPI на STM32; ШИМ, таймери та переривання на STM8
В першій частині я спробував розповісти хобі-електронщикам, які виросли зі штанців Ардуїно, як і навіщо їм варто читати даташіти та іншу документацію до мікроконтролерів. Текст вийшов великий, тож я пообіцяв практичні приклади показати в окремій статті. Ну що ж, назвався груздею...
Сьогодні я покажу, як за допомогою даташитів вирішити досить прості, але необхідні для багатьох проектів завдання на контролерах STM32 (Blue Pill) і STM8. Всі демо-проекти присвячені моїм улюбленим світлодіодам, запалювати ми їх будемо у великих кількостях, для чого доведеться задіяти будь-яку цікаву периферію.
Текст знову вийшов величезний, тому для зручності роблю зміст:
Дисклеймер: я не інженер, не претендую на глибокі знання в електроніці, стаття призначена для таких же як я любителів. Насправді як цільову аудиторію я розглядав самого себе дворічної давності. Якби мені хтось тоді розповів, що даташити на незнайомий чіп читати не страшно, я б не витратив купу часу на вишукування якихось шматків коду в інтернеті та винахід милиць з ножицями та лейкопластирем.
У центрі цієї статті — датішити, а не проекти, тому код може бути не надто зачесаний і часто милий. Самі проекти дуже прості, хоч і придатні для першого знайомства з новим чіпом.
Сподіваюся, що моя стаття допоможе комусь на схожому етапі занурення у хобі.
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 висновків:
Джерела постійного струму, що втікає (відкритий стік)
Мийка / Вихід з відкритим стоком - Стік; джерело струму; вихід, в активному стані, підключений до землі, – світлодіоди до драйвера підключаються катодами. Електрично це, звичайно, ніякий не «відкритий стік» (open drain), але в даташитах таке позначення висновків у режимі стоку зустрічається часто.
Зовнішні резистори між REXT та GND для встановлення значення вихідного струму
Між піном REXT та землею встановлюється референсний резистор, що контролює внутрішній опір виходів, див. графік на стор. 9 даташита. У DM634 цим опором можна також керувати програмно, встановлюючи загальну яскравість (глобальна яскравість); у цій статті вдаватися до подробиць не буду, просто поставлю сюди резистор на 2.2 – 3 кОм.
Щоб зрозуміти, як керувати чіпом, подивимося на опис інтерфейсу пристрою:
Ага, ось він, китайська англійська у всій красі. Перекласти це проблематично, зрозуміти за бажання можна, але є інший шлях - подивитися, як описується підключення в датасіті до функціонально близького TLC5940:
… Для введення даних у пристрій потрібно лише три піни. Передній фронт сигналу 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, до нього є два корисні документи:
специфікація для мікроконтролерів STM32F103x8 та STM32F103xB;
Pinouts - розпинування чіпів - на той випадок, якщо ми вирішимо робити плати самі;
Memory Map – картка пам'яті конкретного чіпа. У Reference Manual є карта для всієї лінійки, у ній згадані регістри, яких немає на нашому.
Таблиця Pin Definitions – перерахування основних та альтернативних функцій пінів; для «синьої пігулки» в інтернеті можна знайти зручніші картинки зі списком пінів та їх функціями. Тому негайно гуглимо Blue Pill pinout і тримаємо ось таку картинку під рукою:
NB: на картинці з інтернету була помилка, помічена в коментарях, за що дякую. Картинка замінена, але це урок – інформацію не з даташитів краще перевіряти.
Даташит прибираємо, відкриваємо Reference Manual, відтепер користуємося лише ним.
Порядок дій: розбираємося зі стандартним введенням/виводом, налаштовуємо SPI, включаємо потрібну периферію.
Ввід вивід
На Atmega328 введення-виведення реалізовано дуже легко, через що безліч опцій STM32 може спантеличити. Зараз нам потрібні лише висновки, але навіть їх є чотири варіанти:
висновок з відкритим стоком, висновок «тяни-штовхай», альтернативний «тягни-штовхай», альтернативний відкритий стік
«Тягни-штовхай» (двотактний) – звичний висновок з Ардуїни, пін може набувати значення або HIGH, або LOW. А ось із «відкритим стоком» виникають складності, хоча насправді тут все просто:
Конфігурація виводу / коли порт призначений на висновок: / увімкнено буфер виводу: / – режим відкритого стоку: «0» у вивідному регістрі активує N-MOS, «1» у вивідному регістрі залишає порт у режимі Hi-Z (P-MOS не активується ) / – режим «тягни-штовхай»: «0» у вивідному регістрі активує N-MOS, «1» у вивідному регістрі активує P-MOS.
Вся відмінність відкритого стоку (open drain) від «тягни-штовхай» (двотактний) полягає в тому, що в першому пін не може прийняти стан HIGH: при записі одиниці у вивідний регістр він переходить у режим високого опору (високий імпеданс, Привіт-З). При записі нуля пін в обох режимах поводиться однаково, як логічно, і електрично.
У звичайному режимі виведення пін просто транслює вміст вивідного регістру. В "альтернативному" ним керує відповідна периферія (див. 9.1.4):
Якщо біт порту налаштований як висновок альтернативної функції, вивідний регістр відключається, а пін підключається до сигналу виведення периферії
Альтернативний функціонал кожного піна описаний у Визначення штифтів даташита і є на завантаженому малюнку. На питання, що робити, якщо у піна кілька альтернативних функцій, відповідь дає виноску в датасіті:
Якщо кілька периферійних блоків використовують один і той же пін, щоб уникнути конфлікту між альтернативними функціями, одночасно слід використовувати лише один периферійний блок, перемикаючись за допомогою біта активації тактування периферії (у відповідному регістрі 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 односпрямований потік даних):
У цьому режимі програма використовує SPI або в режимі передачі, або тільки прийому. / Режим тільки передачі схожий на дуплексний режим: дані передаються по передавальному піну (MOSI в режимі майстра або MISO в режимі слейва), а приймаючий пін (MISO або MOSI відповідно) може використовуватися як звичайний пін введення-виводу. У цьому випадку додатку достатньо ігнорувати буфер Rx (якщо його прочитати, там не буде передано даних).
Добре, пін MISO у нас звільнився, підключимо до нього сигнал LAT. Розберемося зі Slave Select, яким на STM32 можна управляти програмно, що дуже комфортно. Читаємо однойменний абзац розділу 25.3.1 SPI General Description:
Програмне керування NSS (SSM = 1) / Інформація про вибір слейва міститься в біті регістру SSI SPI_CR1. Зовнішній пін NSS залишається вільним для інших потреб програми.
Час писати в регістри. Я вирішив використовувати SPI2, шукаємо в даташіті його базову адресу - в розділі 3.3 Memory Map (Карта пам'яті):
Реєстри зібрані в однойменному розділі reference manual. Зсув адреси (Address offset) у CR1 - 0x00, за замовчуванням всі біти скинуті (Скинути значення 0x0000):
Біти BR встановлюють дільник тактової частоти контролера, визначаючи таким чином частоту, на якій працюватиме SPI. Частота STM32 у нас буде 72 МГц, LED-драйвер, згідно з його даташитом, працює з частотою до 25 МГц, таким чином ділити треба на чотири (BR[2:0] = 001).
2. Встановіть біти CPOL та CPHA, щоб визначити відносини між передачею даних та тактуванням послідовного інтерфейсу (див. схему на сторінці 240).
Оскільки ми читаємо даташит, а чи не розглядаємо схеми, давайте краще вивчимо текстовий опис бітів CPOL і CPHA на стор. 704 (SPI General Description):
Фаза та полярність тактового сигналу
За допомогою бітів 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 має сенс поставити в одиницю:
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 майстром і включаємо його:
SPI налаштований, давайте відразу напишемо функції, що відправляють байти драйверу. Продовжуємо читати 25.3.3 "Налаштування SPI в режимі майстер":
Порядок передачі
Передача починається коли буфер Tx записується байт.
Байт даних завантажується в зсувний регістр паралельному режимі (з внутрішньої шини) під час передачі першого біта, після чого передається в послідовному режимі піну MOSI, першим або останнім бітом вперед в залежності від установки біта LSBFIRST у регістрі CPI_CR1. Прапор TXE встановлюється після передачі даних з буфера Tx в зсувний регістр, а також створюється переривання, якщо встановлено біт TXEIE у регістрі CPI_CR1.
Я виділив кілька слів у перекладі, щоб звернути увагу на одну особливість реалізації SPI у контролерах STM. На Атмезі прапор TXE (Tx Empty, Tx порожній і готовий приймати дані) встановлюється лише після того, як весь байт відправився назовні. А тут цей прапор встановлюється після того, як байт виявився засунутим у внутрішній зсувний регістр. Оскільки він пхається туди всіма бітами одночасно (паралельно), а далі дані передаються послідовно, TXE встановлюється до того, як байт повністю відправиться. Це важливо, т.к. у разі нашого LED-драйвера нам треба смикнути пін LAT після відправки всіх даних, тобто. лише прапора TXE нам буде недостатньо.
А це означає, що нам потрібний ще якийсь прапор. Подивимося в 25.3.7 – «Прапори статусів»:
<...>
Підкреслити BUSY
Прапор BSY встановлюється та скидається апаратно (запис у нього ні на що не впливає). Прапор BSY показує стан SPI комунікативного шару.
Він скидається:
коли передача завершена (крім режиму майстра, якщо передача безперервна)
коли SPI вимкнено
коли відбувається помилка режиму майстра (MODF=1)
Якщо передача не безперервна, прапор BSY скинутий між кожною передачею даних
Окей, знадобиться. З'ясовуємо, де знаходиться буфер Tx. Для цього читаємо "Регістр даних SPI":
Біти 15:0 DR[15:0] Регістр даних
Отримані дані чи дані передачі.
Регістр даних поділено на два буфери – один для запису (буфер передачі) та другий для читання (буфер прийому). Запис у регістр даних пише буфер Tx, а читання з регістра даних поверне значення, що міститься в буфері Rx.
Ну і регістр статусів, де знайдуться прапори TXE та BSY:
Ну а оскільки нам треба передати 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:
Кожен із портів введення/виведення загального призначення (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 біт якого тільки скидають пін . Мені до вподоби другий варіант. Ці регістри важливі тим, що забезпечують атомарний доступ до пін:
Атомарна установка або скидання
Не потрібно вимикати переривання при програмуванні GPIOx_ODR на бітовому рівні: можна змінювати один або кілька бітів однією атомарною операцією запису APB2. Це досягається записом «1» у регістр установки/скидання (GPIOx_BSRR або, тільки для скидання, GPIOx_BRR) біта, який потрібно змінити. Інші біти залишаться незмінними.
Регістри даних мають назви, що цілком говорять - IDR = вхід Direction Register, регістр введення; ODR = Вихід Direction Register, регістр виведення. У нинішньому проекті вони нам не потрібні.
Ну і, нарешті, регістри, що управляють. Оскільки нам цікаві піни другого SPI, а саме PB13, PB14 та PB15, відразу дивимося на CRH:
І бачимо, що треба буде щось написати в біти з 20-го до 31-го.
Ми вже вище розібралися з тим, що ми хочемо від пінів, тому тут я обійдуся без скріншота, просто скажу, що MODE задає напрямок (введення, якщо обидва біти виставлені в 0) і швидкість піна (нам потрібно 50MHz, тобто обидва піна в «1»), а CNF задає режим: звичайний «тягни-штовхай» – 00, «альтернативний» – 10. За замовчуванням, як ми бачимо вище, у всіх пінів прописаний третій знизу біт (CNF0), він встановлює їх у режим floating input.
Оскільки я планую щось робити з цим чіпом, я для простоти задефайнив взагалі всі можливі значення MODE і CNF як для нижнього, так і для верхнього контрольних регістрів.
(LAT_low просто за інерцією, як завжди було, нехай собі залишиться)
Тепер все вже добре, тільки не працює. Тому що це STM32, тут економлять електрику, а отже, треба включити тактування потрібної периферії.
Включаємо тактування
За тактування відповідають годинники, вони ж Clock. І ми вже могли помітити абревіатуру RCC. Шукаємо її в документації: це Reset and Clock Control (Керування скиданням та тактуванням).
Як вище було сказано, на щастя, найскладніше з теми тактування за нас зробили люди з STM, за що їм велике спасибі (ще раз дам посилання на сайт Di Halt'а, щоб було зрозуміло, наскільки це заморочено). Нам потрібні лише регістри, відповідальні включення тактування периферії (Peripheral Clock Enable Registers). Для початку знайдемо базову адресу RCC, вона на самому початку «Карти пам'яті»:
А далі або клікнути за посиланням, де намагатися в табличці щось знайти, або, краще, пробігтися за описами включають регістрів з розділів про enable registers. Де ми знайдемо RCC_APB1ENR та RCC_APB2ENR:
І в них, відповідно, біти, що включають тактування SPI2, IOPB (I/O Port B) та альтернативних функцій (AFIO).
Якщо є можливість і бажання потішити, підключаємо DM634 так: DAI до PB15, DCK до PB13, LAT до PB14. Живимо драйвер від 5 вольт, не забуваємо об'єднати землі.
STM8 PWM
ШИМ на STM8
Коли я тільки планував цю статтю, я вирішив для прикладу спробувати освоїти якийсь функціонал незнайомого мені чіпа за допомогою лише даташита, щоб не виходив шевець без чобіт. STM8 на цю роль підходив ідеально: по-перше, у мене була пара китайських плат з STM8S103, а по-друге, вона не надто популярна, а тому спокуса шанувати і знайти рішення в інтернеті упирається у відсутність цих рішень.
За замовчуванням STM8 працює на частоті 2 МГц, це треба одразу виправити.
Тактовий сигнал HSI (швидкісний внутрішній)
Тактовий сигнал HSI утворюється від внутрішнього 16-МГц RC-генератора з програмованим дільником (від 1 до 8). Він задається у регістрі дільника тактового сигналу (CLK_CKDIVR).
На старті провідним джерелом тактового сигналу вибирається HSI RC-генератор з дільником 8.
Знаходимо адресу регістру в датасіті, опис в refman і бачимо, що регістр треба очистити:
Оскільки ми збираємося запускати ШИМ та підключати світлодіоди, дивимося розпинування:
Чіп маленький, багато функцій підвішені на ті самі піни. Те, що у квадратних дужках – «альтернативний функціонал», він перемикається «байтами опцій» (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 МГц
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 має важливу перевагу над ШИМ Атмегі:
ШИМ із вирівнюванням по кордону
Конфігурація рахунку знизу догори
Рахунок знизу вгору активний, якщо біт 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):
110: Перший режим ШІМ – при рахунку знизу вгору перший канал активний, поки TIMx_CNT < TIMx_CCR1. Інакше перший канал неактивний. [далі в документі помилковий копіпаст з таймера 1] 111: Другий режим ШІМ – при рахунку знизу вгору перший канал неактивний, поки TIMx_CNT < TIMx_CCR1. Інакше перший канал активний.
Оскільки світлодіоди підключені до МК катодами, нам підходить другий режим (перший теж, але ми поки що цього не знаємо).
Біт 3 OC1PE: Включити попереднє завантаження виводу 1
0: Реєстр передзавантаження TIMx_CCR1 вимкнено. Писати TIMx_CCR1 можна в будь-який час. Нове значення працює одразу.
1: Увімкнено реєстр передзавантаження на TIMx_CCR1. Операції читання/запису звертаються до регістру передзавантаження. Передзавантажене значення TIMx_CCR1 завантажується в тіньовий регістр під час кожної події оновлення.
*Примітка: для правильної роботи режиму ШИМ регістри передзавантаження повинні бути включені. Це необов'язково в режимі одиночного сигналу (у регістрі TIMx_CR1 встановлено біт OPM).
Окей включаємо все, що потрібно, для трьох каналів другого таймера:
Другий таймер вміє рахувати тільки знизу-вгору, вирівнювання по кордоні, міняти нічого не треба. Встановимо дільник частоти, наприклад, 256. У другого таймера дільник виставляється в регістрі TIM2_PSCR і являє собою ступінь двійки:
Залишилося увімкнути висновки і сам другий таймер. Перше завдання вирішується регістрами Capture/Compare включити: їх два, три канали по них розкидані несиметрично Тут ми можемо дізнатися, що можна змінювати полярність сигналу, тобто. в принципі можна було використовувати і PWM Mode 1. Пишемо:
Напишемо простенький аналог AnalogWrite(), який передаватиме таймеру власне значення для порівняння. Регістри передбачувано називаються Capture/Compare registers, їх по два на кожен канал: молодші 8 біт у TIM2_CCRxL і старші у TIM2_CCRxH. Оскільки ми завели 8-бітний ШІМ, достатньо писати лише молодші біти:
Уважний читач зауважить, що у нас вийшов трохи бракований ШІМ, нездатний видати 100% заповнення (при максимальному значенні 255 сигнал інвертується на один цикл таймера). Для світлодіодів це не відіграє ролі, а уважний читач уже сам здогадується, як це виправити.
ШИМ на другому таймері працює, переходимо до першого.
Перший таймер має ті ж біти в таких же регістрах (просто ті біти, що залишалися «зарезервовані» у другому таймері, в першому активно використовуються для будь-яких просунутих штук). Тому достатньо знайти адреси цих же регістрів у датасіті і скопіювати код. Та й змінити значення дільника частоти, т.к. перший таймер хоче отримати не ступінь двійки, а точне 16-бітове значення у два регістри Prescaler High и низький. Все робимо і перший таймер не працює. В чому справа?
Вирішити проблему можна лише шляхом перегляду всього розділу про керуючі регістри таймера 1, де шукаємо той, якого немає другого таймера. Знайдеться 17.7.30 Break register (TIM1_BKR), де є такий біт:
Третій міні-проект полягає в тому, щоб підключити до другого таймера в режимі ШИМ вісім 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 нам знадобиться розпинування і ось ця шпаргалка:
H = високий рівень напруги, L = низький рівень напруги, X – однаково
Підключаємо E2 та E1 до землі, E3, A0, A1 та A3 до пін PD5, PC3, PC4 та PC5 STM8. Оскільки таблиця вище містить низький, і високий рівні, налаштовуємо ці піни як push-pull висновки.
ШИМ
ШИМ на другому таймері налаштовується так само, як у попередній історії, з двома відмінностями:
По-перше, нам треба включити переривання на Оновити подію (UEV), яке викликатиме функцію, що перемикає активний LED. Робиться це зміною біта Update Interrupt Enable у регістрі з назвою, що говорить
Друга відмінність пов'язана з таким явищем мультиплексингу, як привид - Паразитне світіння діодів. У нашому випадку воно може з'явитися через те, що таймер, викликавши переривання на UEV, йде цокати далі, і обробник переривання не встигає переключити LED, перш ніж таймер вже почне щось писати у висновки. Для боротьби з цим доведеться інвертувати логіку (0 = максимальна яскравість, 255 = нічого не горить) і не допускати крайніх значень шпаруватості. Тобто. добитися того, щоб після UEV світлодіоди повністю гасли на один такт ШІМ.
Уникаємо установки r, g і b 255 і не забуваємо їх інвертувати при використанні.
Переривання
Суть переривання у цьому, що з певних обставин чіп припиняє виконувати основну програму і викликає якусь зовнішню функцію. Переривання виникають через зовнішні або внутрішні впливи, у тому числі від таймера.
Коли ми вперше створили проект у ST Visual Develop, то крім main.c ми отримали вікно із загадковим файлом stm8_interrupt_vector.c, автоматично включеним у проект. У цьому файлі на кожне переривання прив'язана функція NonHandledInterrupt. Нам треба прив'язати свою функцію до потрібного переривання.
У датасіті є таблиця векторів переривань, де ми знаходимо потрібні:
Ну і, нарешті, прописати цю функцію у своєму 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 - Вимикає переривання. Їх треба відключати на час запису нових значень у «відеопам'ять», щоб викликане в невдалий момент переривання не зіпсувало масив.