Чытэльны даташыты 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: пры запісе адзінкі ў вывадны рэгістр ён пераходзіць у рэжым высокага супраціву (высокі імпеданс, Прывітанне-Z). Пры запісе за нуль пін у абодвух рэжымах паводзіць сябе аднолькава, як лагічна, так і электрычнаму.

У звычайным рэжыме вываду пін проста транслюе змесціва вываднога рэгістра. У "альтэрнатыўным" ім кіруе адпаведная перыферыя (гл. 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-бітным рэгістрам скіду-GPi (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 падчас update event спачатку правярае 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

Дадаць каментар