Cum se configurează smartphone-uri și PC-uri. Portal informativ

EasySTM32 - Porturi pentru microcontroler. STM32

Înțeleg că articolul s-a dovedit deja a fi destul de lung și toată lumea vrea să scrie rapid un fel de program care să facă măcar ceva util și să îl încarce în controler, dar se întâmplă că controlerele STM32 sunt ceva mai complicate decât cele cele mai simple piese hardware de opt biți, așa că acum vom vorbi din nou, așa cum scriu unii cititori nerăbdători, „nu despre asta”.

În această parte vom vorbi despre ceea ce trebuie să operăm cel mai adesea atunci când lucrăm cu controlerul - despre registrele de lucru ale nucleului Cortex-M3, despre modurile sale de funcționare și cum pornește controlerul.

Deci, există 13 registre în nucleul Cortex-M3 scop generalR0..R12, registrul folosit pentru a stoca indicatorul de stivă este R13, registrul comunicațiilor - R14, contor de comenzi - R15și 5 registre cu destinație specială.

Registrele de uz general sunt împărțite în registre joase - R0..R7Și registre înalte - R8..R12. Diferența dintre ele este că unele instrucțiuni thumb-2 pe 16 biți pot funcționa numai cu registre joase, dar nu pot funcționa cu cele înalte.

De fapt, există două registre R13, nu unul. Primul se numește MSP - indicator pentru stiva principală, și al doilea PSP - indicator de stivă de proces. Cu toate acestea, doar unul dintre aceste registre este disponibil în orice moment. Care este determinat într-unul din registrele cu destinație specială. De ce este necesar acest lucru? Acest lucru se face pentru a putea proteja sistemul de operare (da, puteți instala un sistem de operare pe acest controler dacă doriți) de programele de aplicații strâmbe. MSP este folosit de handleri situatii exceptionaleși toate programele care utilizează un nivel de execuție privilegiat (de exemplu, nucleul sistemului de operare), iar PSP este utilizat de programe care nu necesită un nivel de execuție privilegiat (de exemplu, programe de aplicație de care vrem să protejăm nucleul OS). Indicatorii de stivă trebuie să fie întotdeauna aliniați la o limită de cuvânt de 32 de biți, de exemplu. cei doi biți cei mai puțin semnificativi ai lor trebuie să fie întotdeauna resetati la zero.

Se apelează registrul R14 LR (registru de legături) - registru de legăturiși este folosit pentru a reține adresa de retur atunci când apelați subrutine.

Se apelează registrul R15 PC (contor de programe) - contor de programeși este folosit pentru a stoca adresa comenzii care se execută în prezent.

Acum despre registrele speciale.

Registrul xPSR conține steaguri pentru rezultatele operațiilor aritmetice și logice, starea de execuție a programului și numărul întreruperii în curs de procesare. Uneori se scrie despre acest registru plural. Acest lucru se face deoarece cele trei părți ale sale pot fi accesate ca registre separate. Aceste „subregistre” se numesc: APSR - Registrul de stare a aplicației(aici sunt stocate steagurile), IPSR - Registrul de stare de întrerupere(conține numărul întreruperii în curs de procesare) și EPSR - Execution Status Register. Structura completă a registrului xPSR este prezentată în figura de mai jos.

Steagurile din registrul APSR sunt standard:

  1. N (steagul negativ) - rezultat negativ al operației
  2. Z (steagul zero) — rezultatul zero al operației
  3. C (drapel de transport) - steag de transport/împrumut
  4. V (steagul de depășire) - steag de depășire
  5. Q (steagul de saturație) - steag de saturație

Registrul PRIORITY MASK folosește doar bitul zero (PRIMASK), care, atunci când este setat la unu, dezactivează toate întreruperile cu o prioritate configurabilă. După pornire, bitul PRIMASK este resetat la zero - toate întreruperile sunt activate.

Registrul FAULT MASK folosește, de asemenea, doar bitul zero (FAULTMASK), care, atunci când este setat la unu, dezactivează toate întreruperile și excepțiile, cu excepția întreruperii nemascabile (NMI). După pornire, bitul FAULTMASK este resetat la zero - toate întreruperile sunt activate.

Registrul BASEPRI este folosit pentru a dezactiva toate întreruperile a căror valoare de prioritate este mai mare sau egală cu cea înregistrată în acest registru. Aici trebuie spus că cu cât valoarea este mai mică, cu atât nivelul de prioritate este mai mare. Registrul BASEPRI folosește doar cei 8 biți inferiori.

Registrul CONTROL este folosit pentru a controla unul dintre modurile procesorului - modul thread. Bitul zero al acestui registru (nPRIV) determină nivelul de execuție (privilegiat - Privilegiat sau neprivilegiat - Neprivilegiat), iar primul bit (SPSEL) determină indicatorul de stivă de utilizat (MSP sau PSP). Diferența dintre nivelurile de execuție privilegiate și neprivilegiate este că pentru nivelul privilegiat sunt disponibile toate zonele de memorie și toate instrucțiunile procesorului, în timp ce pentru nivelul neprivilegiat unele zone de memorie sunt închise (de exemplu, registrele cu destinație specială, cu excepția APSR, zona de sistem) și , în consecință, accesul instrucțiunilor în aceste zone este interzis. Încercarea de a executa comenzi ilegale care încearcă să acceseze zonele de memorie privată determină generarea unei excepții de eșec.

Acum despre modurile de funcționare a procesorului.

Procesorul Cortex-M3 are două moduri de operare: modul thread (Thread) și modul handler (Handle). Modul Handle este folosit pentru a gestiona excepțiile, iar modul Thread este folosit pentru a executa orice alt cod. Comutarea de la un mod la altul are loc automat. Așa cum am spus deja când ne-am ocupat de registrul CONTROL, în modul Thread procesorul poate folosi atât un nivel de execuție privilegiat, cât și cel neprivilegiat, iar în modul Handle - doar unul privilegiat. În mod similar, modul Thread poate folosi atât stiva principală (MSP) cât și stiva de proces (PSP), în timp ce modul Handle poate folosi doar stiva principală.

Este important de înțeles că, de exemplu, dacă trecem de la un nivel privilegiat la un nivel neprivilegiat în modul Thread, vom pierde accesul la registrul CONTROL și vom putea comuta înapoi doar în modul Handle. În modul Handle, bitul nPRIV al registrului CONTROL este de citire/scriere, dar nu afectează modul de execuție curent. Acest lucru vă permite să schimbați nivelul de execuție pe care programul îl va avea atunci când procesorul iese din modul handler în modul thread. Bitul SPSEL în modul Handle nu poate fi scris și este întotdeauna citit ca zero și este restaurat automat la ieșirea din modul handler în modul stream. Toate opțiunile pentru tranzițiile între diferite moduri și niveluri de execuție sunt ilustrate de graficul direcționat prezentat în figura de mai jos:

Controlerul pornește întotdeauna pe oscilatorul intern, la o frecvență de 8 MHz. De unde să obțineți semnalul de ceas în viitor, cu cât să îl înmulțiți sau să îl împărțiți, este configurat în program. Dacă acest lucru nu se face în program, atunci închideți cel puțin zece cuarț extern, controlerul va funcționa în continuare de la oscilatorul intern de 8 MHz.

La pornire, controlerul analizează combinația de niveluri pe cele două picioare ale sale - BOOT0, BOOT1 și, în funcție de această combinație, începe să se încarce fie din memoria flash, fie din RAM, fie din zona sistemului memorie. Acest lucru se face folosind mecanismul de pseudonimizare pe care l-am descris deja mai devreme. În teorie, descărcarea începe întotdeauna de la adresa zero, doar în funcție de
combinațiile de pe picioare BOOT0, BOOT1 adresele de memorie de pornire sunt atribuite ca alias uneia dintre cele trei zone: flash, RAM încorporată sau zonă de sistem. În dreapta este o placă care indică ce zonă este proiectată în adresele de memorie de pornire în funcție de combinația de picioare BOOT0, BOOT1.

În același timp, în zona de sistem producătorul este protejat program special(bootloader), care vă permite să programați memoria flash. Dar mai multe despre asta mai târziu.

În primul rând, controlerul citește un cuvânt de 32 de biți la adresa 0x00000000 și îl plasează în registrul R13 (pointer de stivă). Apoi, citește un cuvânt de 32 de biți la adresa 0x00000004 și îl plasează în registrul R15 (contor de programe). Ultima acțiune determină o tranziție la începutul codului programului și apoi începe execuția programului.

Cuvântul de la adresa 0x00000004 (adresa de început a programului principal) se numește vector de resetare. În general, în memoria controlerului, după pointerul stivei de la adresa 0x00000000, începând de la adresa 0x00000004, ar trebui să existe un tabel de vectori de excepție și întrerupere, primul vector în care este vectorul de resetare, iar vectorii rămași sunt adresele de proceduri pentru manipulatorii diverselor excepții și întreruperi. În cele mai simple programe, dacă nu intenționați să gestionați excepțiile și întreruperile, toți ceilalți vectori, cu excepția vectorului de resetare, pot lipsi. Aș dori să vă atrag atenția asupra faptului că în tabelul vectorial sunt indicate adresele de la începutul handlerelor și nu comenzile pentru a merge la acești handlere, ca, de exemplu, în vârfurile de 8 biți sau atmels.

O zi buna! Astăzi vom afla despre GPIO! Și, în primul rând, să ne uităm la ce moduri pot funcționa porturile I/O din STM32F10x. Și există o mare de aceste moduri, și anume:

  • Intrare plutitoare
  • Tragere de intrare
  • Intrare-pull-down
  • Analogic
  • Ieșire cu scurgere deschisă
  • Ieșire push-pull
  • Funcție alternativă push-pull
  • Funcție alternativă open-drain

Și dacă în opinia noastră, atunci când lucrați la intrare:

  • Autentificare – Hi-Z
  • Intrare – trageți în sus
  • Intrare – trageți în jos
  • Intrare – analogică

Când operăm portul de ieșire, avem următoarele opțiuni:

  • Ieșire - colector deschis
  • Ieșire – push-pull
  • Funcții alternative - Ieșire colector deschis
  • Funcții alternative - ieșire push-pull

Apropo, aici este documentația pentru STM32F103CB -

Fișa de date are un tabel impresionant care arată ce funcții alternative are un anumit pin.

Iată, de exemplu, concluziile PA9, PA10:

În coloană Mod implicit vedem ce funcții vor îndeplini acești pini atunci când sunt configurați să funcționeze Funcție alternativă. Adică, setând acești pini în consecință, ei se vor transforma doar din PA9 și PA10 în RxȘi Tx pentru USART1. Pentru ce este atunci coloana? Remapează? Și asta nu este altceva decât foarte caracteristică utilă remaparea portului. Mulțumită remapării, Tx USARTA 'A, de exemplu, se poate muta de la pinul PA9 la PB6. Destul de des, această caracteristică se dovedește a fi al naibii de utilă.

Ei bine, totul pare să fie mai mult sau mai puțin clar cu modurile, este timpul să aruncăm o privire la registrele care controlează porturile I/O.

Deoarece tocmai am discutat în ce moduri pot exista pinii STM32F10x, să vedem imediat cum pot fi traduși de fapt în modul dorit. Și pentru aceasta sunt alocate două registre - CRL și CRH. În primul, pinii de la 0 la 7 sunt configurați, în al doilea, de la 8 la 15. Registrele, după cum vă amintiți, sunt pe 32 de biți. Adică există 32 de biți pe 8 pini - adică 4 biți pe pini. Deschideți fișa de date și vedeți:

De exemplu, trebuie să configuram piciorul PB5. Mergem la registrul GPIOB->CRL și setăm biții corespunzători după cum ne dorim (în imagine există un registru CRL de 32 de biți). Pentru PB5, aceștia sunt biții:

După opt biți, totul poate părea destul de complicat și cumva stângaci, dar de fapt totul este implementat destul de elegant =). Să vedem ce mai este acolo.

Registrul de ieșire GPIOx_ODR - seamănă cu registrul PORTx din AVR. Tot ceea ce intră în acest registru ajunge imediat în lumea exterioară. Registrul este pe 32 de biți și există doar 16 picioare.La ce crezi că sunt folosite cele 16 rămase? Totul este foarte simplu, biții de registru de la 15 la 31 nu sunt folosiți deloc)

Registrul de intrare GPIOx_IDR este un analog al PINx în AVR. Structura sa este similară cu structura ODR menționată. Orice apare la intrarea microcontrolerului ajunge imediat în registrul de intrare IDR.

Încă două registre utile sunt GPIOx_BSSR și GPIOx_BRR. Ele vă permit să modificați valorile biților din registrul ODR direct, fără a utiliza măștile obișnuite de biți. Adică vreau, de exemplu, să setez al cincilea bit de ODR la unu. Scriu unul în al cincilea bit din GPIOx_BSSR și gata, scopul este atins. Dintr-o dată am vrut să resetez al cincilea bit de ODR - o unitate de 5 biți GPIOx_BRR și atât.

Deci, ne-am uitat la registrele principale, dar, de fapt, în exemplele noastre vom face totul diferit, folosind Biblioteca periferică standard. Așa că hai să ne plimbăm prin bibliotecă. Fișierele sunt responsabile pentru GPIO în SPL stm32f10x_gpio.hȘi stm32f10x_gpio.c. Le deschidem pe amândouă și vedem o mulțime de numere, litere, pictograme, etc. de neînțeles.

De fapt, totul este foarte simplu și clar. Structura este responsabilă pentru configurația portului GPIO_InitTypeDef.

typedef struct (uint16_t GPIO_Pin; // Specifică pinii GPIO care urmează să fie configurați. Acest parametru poate fi orice valoare a lui @ref GPIO_pins_define */ GPIOSpeed_TypeDef GPIO_Speed; // Specifică viteza pentru pinii selectați. Acest parametru poate fi o valoare a @ref GPIOSpeed_TypeDef */ GPIOMode_TypeDef GPIO_Mode; // Specifică modul de operare pentru pinii selectați. Acest parametru poate fi o valoare a @ref GPIOMode_TypeDef */ ) GPIO_InitTypeDef;

Vedem că structura are trei câmpuri: GPIO_PIN, GPIO_SpeedȘi GPIO_Mode. Este ușor de ghicit că primul este responsabil pentru numărul pinului portului pe care vrem să-l configuram, al doilea este pentru viteza portului și al treilea, de fapt, pentru modul de operare. Deci, pentru a personaliza ieșirea, trebuie doar să declarăm variabilă de tip structurați și completați câmpurile acestuia valorile cerute. Toate valorile de câmp posibile sunt chiar acolo - în stm32f10x_gpio.h. De exemplu,

typedef enum ( GPIO_Mode_AIN = 0x0 , GPIO_Mode_IN_FLOATING = 0x04 , GPIO_Mode_IPD = 0x28 , GPIO_Mode_IPU = 0x48 , GPIO_Mode_Out_OD = 0x14 =, GPO_Mode_IPD , GPIO_Mode_0x48 _ OD = 0x1C , GPIO_Mode_AF_PP = 0x18 ) GPIOMode_TypeDef;

Toate valorile au fost deja calculate de creatorii SPL, așa că pentru a configura orice ieșire să funcționeze în modul Ieșire push-pull trebuie doar să setați câmpul în structura corespunzătoare: GPIO_Mode = GPIO_Mode_Out_PP.

Ei bine, structura a fost declarată, câmpurile au fost completate la nevoie, ce urmează? La urma urmei, tocmai am creat o variabilă. Ce legătură au registrele, microcontrolerele și electronicele în general? Să intrăm în dosar stm32f10x_gpio.c iar acolo găsim un nor diverse funcții pentru lucrul cu STM32 GPIO. Luați în considerare funcția GPIO_Init()(Nu voi da codul, totul este în fișierul bibliotecii). Deci, această funcție conectează precis structura noastră creată și registrele specifice de controler. Adică, trecem o variabilă acestei funcții, în conformitate cu care sunt setați biții necesari ai registrelor necesare microcontrolerului. Totul este foarte simplu, dar nu mai puțin genial. Consultați mai multe fișiere din bibliotecă. Există funcții pentru orice ocazie) Apropo, este foarte convenabil - înainte de funcție există o descriere a variabilelor pe care le acceptă și le returnează, precum și o descriere a ceea ce ar trebui să facă această funcție. Deci, nu este dificil să-ți dai seama, dar trebuie să fii puțin fluent în engleză. Deși nu te poți descurca fără el ;)

Să luăm o pauză de la porturile I/O pentru un moment și să discutăm un punct destul de subtil. Pentru a utiliza porturi sau orice alte periferice, TREBUIE să activați ceasul. Atât porturile, cât și perifericele sunt inițial deconectate de la ceas, așa că fără această acțiune nimic nu va porni. Programul se va compila, dar nimic nu va funcționa de fapt. Fișierele sunt responsabile pentru sincronizarea în SPL stm32f10x_rcc.cȘi stm32f10x_rcc.h. Nu uitați să le adăugați în proiect.

Să trecem la programare. După cum se obișnuiește, vom face dioda să clipească) Pentru a înțelege mai bine Biblioteca periferică standard, vom complica ușor clipirea obișnuită a diodei - vom sonda butonul și, dacă este apăsat, dioda se aprinde, altfel merge afară. Lansăm Keil, creăm un proiect, adăugăm toate fișierele necesare, nu uita de CMSIS. De la SPL pentru acest proiect vom avea nevoie de 4 fișiere, deja menționate mai sus. Crearea unui nou proiect este descrisă în articolul anterior curs de pregatire. De asemenea, puteți găsi link-uri către biblioteci acolo)

Deci, codul:

/****************************gpio.c******************** ***************/ //Conectează toate fișierele necesare#include „stm32f10x.h” #include „stm32f10x_rcc.h” #include „stm32f10x_gpio.h” //Aici va fi intreaga initializare a tuturor perifericelor folosite void initAll() ( //Declară o variabilă de port de tip GPIO_InitTypeDef Port GPIO_InitTypeDef; //Aceasta este o funcție din fișierul stm32f10x_rcc.c, activează tactarea pe GPIOA //GPIOA se află pe autobuzul APB2 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE) ; //Voi scrie mai jos despre această funcție GPIO_StructInit(& port) ; //Umpleți câmpurile de structură cu valorile necesare //Prima ieșire este intrarea pentru procesarea unei apăsări de buton - PA1 port.GPIO_Mode = GPIO_Mode_IPD; port.GPIO_Pin = GPIO_Pin_1; port.GPIO_Speed ​​​​= GPIO_Speed_2MHz; //Și am vorbit deja despre această funcție //Să notăm doar unul dintre parametri - un pointer(!) către//structura noastră GPIO_Init(GPIOA, & port) ; //Configurați pinul de care se va atârna dioda – PA0 port.GPIO_Mode = GPIO_Mode_Out_PP; port.GPIO_Pin = GPIO_Pin_0; port.GPIO_Speed ​​​​= GPIO_Speed_2MHz; GPIO_Init(GPIOA, & port) ; ) /*******************************************************************/ int main() ( //Declară o variabilă pentru a stoca starea butonului uint8_t buttonState = 0 ; initAll() ; în timp ce (1) ( //Folosind o funcție din SPL citim din lumea exterioară //starea butonului buttonState = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) ; if (buttonState == 1 ) ( GPIO_SetBits(GPIOA, GPIO_Pin_0) ; ) else ( GPIO_ResetBits(GPIOA, GPIO_Pin_0) ; ) ) ) /**************************** Sfârșitul fișierului******************** *********/

Apropo, poate că cineva va acorda atenție prezenței parantezelor ( ), în ciuda unei singure instrucțiuni în corp dacăȘi altfel. Și acesta este deja un obicei) Este foarte recomandat să scrieți așa, mai ales când dezvoltați proiecte mari. Când adăugați/corectați un program, un programator neatent poate să nu acorde atenție absenței parantezelor și să adauge o a doua instrucțiune, care, după cum înțelegeți, va fi deja în întregul bloc dacă sau altfel. Aceeași problemă cu ciclurile. Atunci când sunt mulți oameni care lucrează la un proiect, nu există garanții că cineva nu va fi neatent, așa că pentru a nu pierde minute/ore în căutările ulterioare ale unui gheț, recomand să puneți întotdeauna aceste paranteze) Deși poate cineva va nu sunt de acord cu aceasta logica.

Apăsăm F7, compilați, iar acum primul nostru program pentru STM este gata. Se pare că codul este comentat destul de detaliat, așa că voi explica doar câteva puncte.

Funcţie GPIO_StructInit(&port)– ia adresa unei variabile ca argument port.

Această funcție umple câmpurile structurii care i-au fost transmise ca argument cu valori implicite. Acest lucru nu este necesar, dar pentru a evita orice blocuri imprevizibile, este mai bine să apelați întotdeauna această funcție.

Încă două funcții pe care le-am folosit:

  • GPIO_SetBits(GPIOA, GPIO_Pin_0);
  • GPIO_ResetBits(GPIOA, GPIO_Pin_0);

Ei bine, ai ghicit pentru ce sunt acestea 😉

Așa că am terminat să ne uităm la porturile I/O STM32. În următorul articol ne vom familiariza cu instrumentele de depanare ale lui Keil.

Registrele principale ale portului I/O al microcontrolerului STM32

Un port se referă la un anumit set numit de picioare de microcontroler. În microcontrolerele STM se numesc GPIOA, GPIOB, GPIOC etc. Porturile I/O ale microcontrolerelor STM32 au de obicei 16 linii (picioare). O linie se referă la unul sau altul picior al microcontrolerului. Fiecare linie de port poate fi configurată într-un mod specific și poate îndeplini următoarele funcții:

  • Intrare digitală;
  • Ieșire digitală;
  • intrare de întrerupere externă;
  • Funcția I/O a altor module de microcontroler.

Puteți configura portul pentru modul de operare dorit folosind registrele microcontrolerului. Aceste registre pot fi accesate direct sau pot fi folosite metode speciale din biblioteca periferică.

Să ne uităm la registrele principale necesare pentru a lucra cu porturile I/O.

Registrele responsabile pentru configurarea porturilor

Înainte de a începe să lucrați cu un port de ieșire, trebuie să îl configurați pentru a se potrivi nevoilor dvs.

Registrele de configurare sunt responsabile pentru setarea sau configurarea unui port. În microcontrolerele din familiile STM32F302xx, STM32F303xx și STM32F313xx, acestea sunt următoarele registre:

  • GPIOx_MODER;
  • GPIOx_OTYPER;
  • GPIOx_OSPEEDR;
  • GPIOx_PUPDR.

Înregistrați GPIOx_MODER (unde x=A...F)

Acesta este pe 32 de biți Registrul este responsabil pentru modul de funcționare al liniei. Necesită 2 biți pentru a configura o anumită linie. Sunt posibile următoarele combinații:

  • 00 - linia este configurată pentru intrare;
  • 01 - ieșire;
  • 10 - mod alternativ de funcționare;
  • 11 - modul analogic.

Înregistrați GPIOx_TYPER (unde x=A...F)


Acest registru este folosit pentru a configura tipul de operare pe linie; folosește 16 biți în funcționare, restul de 16 sunt rezervați. Acceptă următoarele valori:

  • 0 - modul push-pull;
  • 1 - scurgere deschisă

Înregistrați GPIOx_PUPDR (unde x=A...F)


Registrul de date este responsabil pentru strângere. Acceptă următoarele valori:

  • 00 - fără lift
  • 01 - tragere la sursa de alimentare plus
  • 10 - tragere la sol

Înregistrați GPIOx_SPEEDR (unde x=A...F)


Registrul de setare a vitezei liniei.

  • 00 - 2 MHz;
  • 01 - 10 MHz;
  • 10 - 50MHz.

Registrul de ieșire (registrul de ieșire) GPIOx_ODR (unde x=A…F) – registru de date de ieșire


Acest registru este folosit pentru a scoate date către port. Când scrieți anumite valori la acest registru o valoare similară este setată pe linie (picior). Deoarece avem 16 linii și registrul are 32 de biți, sunt folosiți doar primii 16 biți (inferioare).

Registrul de intrare (starea portului sau registrul de date de intrare) GPIOx_IDR (unde x=A…F) – registru de date de intrare


Acest registru este doar pentru citire. Un fel de indicator de stare a portului. Analog de PINx în microcontrolerele din seria AVR.

Set de biți portului de ieșire Registrul GPIOx_BSRR


Acest registru stabilește starea pinului în portul de ieșire bit cu bit.

Mai multe informații despre toate registrele unui anumit microcontroler pot fi găsite în manualul de referință.pdf care poate fi descărcat de pe site-ul oficial www.st.com

Configurarea unui port de microcontroler folosind biblioteca

Portul poate fi configurat și folosind o bibliotecă specială care conține metode diferite pentru lucrul cu registrele I/O, iar variabilele speciale sunt declarate. Această bibliotecă eliberează programatorul de a trebui să calculeze „manual” ce valoare trebuie scrisă într-un anumit registru.

Să ne uităm la un exemplu de cod care aprinde un LED

#include „stm32f30x.h” // Antetul dispozitivului #include „stm32f30x_rcc.h” #include „stm32f30x_gpio.h” void init_led(void) ( RCC_APB2PeriphClockCmd (GPIOE,ENABLE); GPIOE_Init_Struture_GPIO_Init_Init_Init_Init .GPIO_Speed ​​= GPIO_Speed_2MHz; // Setați viteza maximă GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //Modul de ieșire GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //Indicați pin-ul la care este atașat LED-ul GPIO_Init(GPIOE,GPIO_InitStructure/InitStructure) (în structura principală) ; (); GPIO_SetBits( GPIOE,GPIO_Pin_8); //Setați pinul la o stare ridicată în timp ce(1) ( _NOP(); ) )

Pentru a economisi energie, toate perifericele microcontrolerului sunt dezactivate. Pentru a „activa” unul sau altul periferic, trebuie mai întâi să îi aplicați semnale de ceas.

Lucrăm cu portul GPIOE, așa că trebuie să activăm sincronizarea folosind metoda

RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);

Care are două valori: prima este portul real pe care trebuie să-l activăm, iar a doua este starea a acestui port, pornit sau oprit.

Când are loc un eveniment, controlerul de întrerupere întrerupe automat execuția programului principal și apelează funcția corespunzătoare de gestionare a întreruperilor. După ce funcția de gestionare a întreruperilor iese, programul continuă execuția din punctul în care a avut loc întreruperea. Totul se întâmplă automat (dacă NVIC-ul este configurat corect, dar mai multe despre asta mai jos).

Din numele în sine este clar că controlerul NVIC acceptă imbricarea întreruperilor și prioritățile. Fiecărei întreruperi i se atribuie propria sa prioritate la configurarea NVIC. Dacă apare o întrerupere cu prioritate ridicată în timp ce este procesată o întrerupere cu prioritate scăzută, aceasta va întrerupe, la rândul său, operatorul de întrerupere cu prioritate scăzută.

Cum functioneaza?

Această postare nu pretinde a fi complet complet; vă sfătuiesc să studiați secțiunea de întrerupere din Manualul de referință tehnică Cortex™-M3. Deoarece această parte a nucleului nu a suferit modificări, descrierea ei este dată în prima revizuire a r1p1 pentru nucleul Cortex-M3.
Intrarea și ieșirea dintr-o întrerupere
Când este inițiată o întrerupere, NVIC comută nucleul în modul de procesare întrerupere. După trecerea la modul de procesare întrerupere, registrele nucleului sunt plasate pe stivă. Direct când valorile registrului sunt scrise în stivă, adresa de pornire a funcției de gestionare a întreruperilor este preluată.

Registrul de stare a programului ( Registrul de stare program (PSR)), contor de programe ( Contor de programe (PC)) și registrul de comunicații ( Registrul de legături (LR)). O descriere a registrelor nucleului este dată în Ghidul general al utilizatorului Cortex-M4. Datorită acestui fapt, starea în care se afla miezul înainte de a intra în modul de procesare întrerupere este reținută.

Sunt salvate și registrele R0 - R3 și R12. Acești registre sunt utilizați în instrucțiuni pentru a trece parametrii, astfel încât împingerea lor pe stivă le face posibilă folosirea într-o funcție de serviciu de întrerupere, iar R12 acționează adesea ca un registru de lucru al programului.

La finalizarea procesării întreruperii, toate acțiunile vor fi efectuate în ordine inversă: Conținutul stivei este afișat și, în paralel, este preluată adresa de retur.

Din momentul in care întreruperea este inițiată și până când este executată prima comandă a operatorului de întrerupere, întreruperea durează 12 cicluri de ceas, același timp este necesar pentru reluarea programului principal după finalizarea procesării întreruperii.

Întrerupeți cuibărirea
După cum am menționat mai sus, NVIC acceptă întreruperi cu priorități diferite, care se pot întrerupe reciproc. În acest caz, pot apărea diverse situații, a căror prelucrare este optimizată diferit.

1. Suspendați întrerupere cu prioritate scăzută
În această situație, procesarea întreruperii cu prioritate scăzută este oprită. În următoarele 12 cicluri, un nou set de date este salvat în stivă și începe procesarea întreruperii cu prioritate ridicată. După ce este procesat, conținutul stivei este automat scos și procesarea întreruperilor cu prioritate scăzută reia.
Nu există diferențe majore față de întreruperea programului principal.

2. Procesarea continuă a întreruperii
Această situație poate apărea în două cazuri: dacă două întreruperi au aceeași prioritate și apar simultan, dacă apare o întrerupere cu prioritate scăzută în timp ce se procesează o întrerupere cu prioritate mare.
În acest caz, nu se efectuează operații intermediare pe stivă. Este încărcată doar adresa handler-ului de întrerupere cu prioritate scăzută și are loc trecerea la execuția acestuia. Eliminarea operațiunilor de stivă salvează 6 cicluri de ceas. Trecerea la următoarea întrerupere nu are loc în 12 cicluri de ceas, ci doar în 6.

3. Întârziere de întrerupere cu prioritate ridicată
Situația apare dacă o întrerupere cu prioritate mare are loc în timpul tranziției la procesarea uneia cu prioritate scăzută (în timpul acelorași 12 cicluri de ceas). În acest caz, trecerea la o întrerupere cu prioritate ridicată va avea loc la cel puțin 6 cicluri de ceas din momentul în care apare (timpul necesar pentru a încărca adresa operatorului de întrerupere și a trece la aceasta). Revenirea la prioritate scăzută a fost deja descrisă mai sus.

Întrerupeți prioritățile
Pe lângă pur și simplu setarea priorității de întrerupere, NVIC implementează capacitatea de a grupa prioritățile.
Întreruperi într-un grup cu mai mult de prioritate ridicată gestionatorii de întreruperi ai grupurilor cu prioritate mai mică pot fi întrerupți. întreruperile din același grup, dar cu priorități diferite în cadrul grupului, nu se pot întrerupe reciproc. Prioritatea în cadrul unui grup determină doar ordinea în care este apelat handlerul atunci când ambele evenimente au fost declanșate.

Valoarea priorității întreruperii este setată în registre Întreruperea registrelor prioritare(Consultați Ghidul utilizatorului generic Cortex-M4). În acest caz, unii biți sunt responsabili pentru prioritatea grupului în care se află întreruperea, iar unii sunt responsabili pentru prioritatea în cadrul grupului.
Setarea distribuției de biți pentru prioritatea grupului sau prioritatea în cadrul unui grup se realizează folosind registrul Întreruperea aplicației și resetarea registrului de control(ATENȚIE!!! consultați Ghidul utilizatorului generic Cortex-M4).

După cum probabil ați observat, Ghidul general al utilizatorului Cortex-M4 afirmă că setările și grupările de priorități sunt specifice implementării. implementare definită.
Dar ceea ce urmează nu este un lucru prea plăcut. Aproape că nu există informații despre NVIC în STM32F407 MK. Dar există un link către un document separat. Pentru a înțelege implementarea NVIC în STM32, va trebui să citiți un alt document -. În general, vă sfătuiesc să studiați cu atenție acest documentși în toate celelalte probleme, descrie funcționarea nucleului mai detaliat decât în ​​documentația de la ARM.
În el puteți găsi deja:

Un nivel de prioritate programabil de 0-15 pentru fiecare întrerupere. Un nivel superior îi corespunde a
prioritate mai mică, astfel încât nivelul 0 este cea mai mare prioritate de întrerupere

Din cei 8 biți de prioritate posibili, sunt utilizați doar 4. Dar acest lucru este suficient pentru majoritatea sarcinilor.
Întrerupeți mascarea
Să presupunem că avem sarcina de a lansa un vehicul de lansare atunci când butonul roșu este apăsat, dar numai dacă cheia este răsucită.
Nu are absolut nici un rost să generezi o întrerupere pentru rotirea cheii. Dar vom avea nevoie de o întrerupere pentru apăsarea butonului roșu. Pentru a activa/dezactiva diferiți vectori de întrerupere, există mascarea întreruperii.
Mascarea întreruperii se face folosind registre Întreruperea set-activare registre .
Dacă o întrerupere este mascată, aceasta nu înseamnă că perifericul nu generează evenimente! Doar că NVIC nu cheamă un handler pentru acest eveniment.
Întreruperea tabelului vectorial
Toate întreruperile posibile suportate de NVIC sunt înregistrate în tabelul vector de întreruperi. În esență, tabelul vector de întrerupere nu este altceva decât o listă de adrese ale funcțiilor de gestionare a întreruperilor. Numărul din listă corespunde numărului de întrerupere.
Creați un tabel vectorial și plasați-l în locul potrivit
Pentru ca tabelul de vectori cu adrese corecte funcțiile de gestionare a întreruperilor au fost localizate la începutul memoriei flash a MK, vom crea și vom conecta fișierul startup.c la proiect.

Conținutul fișierului

// Activați extensiile IAR pentru acest fișier sursă. #pragma language=extended #pragma segment="CSTACK" // Declarația înainte a gestionatorilor de erori implicite. void ResetISR(void); static void NmiSR(void); static void FaultISR(void); static void IntDefaultHandler(void); // Punctul de intrare pentru codul de pornire al aplicației. extern void __iar_program_start(void); extern void EXTI_Line0_IntHandler(void); extern void EXTI_Line6_IntHandler(void); // O uniune care descrie intrările din tabelul vectorial. Unirea este necesară // deoarece prima intrare este indicatorul de stivă, iar restul sunt pointeri de funcție //. typedef union ( void (*pfnHandler)(void); void * ulPtr; ) uVectorEntry; // Tabelul vectorial. Rețineți că constructele adecvate trebuie plasate pe aceasta pentru a // să vă asigurați că ajunge la adresa fizică 0x0000.0000. __root const uVectorEntry __vector_table @ ".intvec" = ( ( .ulPtr = __sfe("CSTACK") ), // Indicatorul de stivă inițial ResetISR, // Managerul de resetare NmiSR, // Handerul NMI FaultISR, // Defecțiunea gravă Handler IntodeFaulthandler, // MPU FAULT HANDLER IntDEFULTHANDER, // Bus Fault Handler IntDEFULTHANDER, // UTILIZARE FAULT HANDLER INTDEFULTHANDER, // DSERVEDDD Intodefaulthandler, // Reserved IntdefaultHANDER, // Reserved Intodefaulthandler, // Reserved Intodefaulthandler, // Reserved Intodefaulthandler, // nder, // Depanare Monitor Handler IntDefaultHandler, // Rezervat IntDefaultHandler, // PendSV Handler IntDefaultHandler, // SysTick Handler // Interrupții externe IntDefaultHandler, // Window WatchDog IntDefaultHandler, // PVD prin Tampere și EXTTI Line Default, //DefaultHandler, //DefaultHandler Linia EXTI IntDefaultHandler, // RTC Wakeup prin linia EXTI IntDefaultHandler, // FLASH IntDefaultHandler, // RCC EXTI_Line0_IntHandler, // EXTI Line0 IntDefaultHandler, // EXTI Line1 IntDefaultHandler, //EXTI_Line0_IntHandler, //EXTI Line0 IntDefaultHandler, //EXTI Line1 IntDefaultHandler, //EXTIHandler3 , // EXTI Line4 IntDefaultHandler, // DMA1 Stream 0 IntDefaultHandler, // DMA1 Stream 1 IntDefaultHandler, // DMA1 Stream 2 IntDefaultHandler, // DMA1 Stream 3 IntDefaultHandler, // DMA1 Stream 1 IntDefaultHandler //DMA1 Stream 4 IntDefaultHandler //DMA1 Stream 3 IntDefaultHandler / DMA1 Stream 6 IntDefaultHandler, // ADC1, ADC2 și ADC3s IntDefaultHandler, // CAN1 TX IntDefaultHandler, // CAN1 RX0 IntDefaultHandler, // CAN1 RX1 IntDefaultHandler, // CAN1 SCE EXTI_Line6_Line6, /Int/DefaultHandler, // Int/DefaultHandler și TIM9 IntDefaultHandler, // Actualizare TIM1 și Tim10 Intdefaulthaner, // Tim1 Trigger și Comunicare și TIM11 IntDEFAULTHANDER, // TIM1 Capture Comparee IntefaultHander, // TIM2 IntDE Faulthandler, // TIM3 IntDEFAULTHANDER, // TIM4 IntDEFULTHANDER, // I2C I2C, Event, //I2C IntDefaultHandler, // Eveniment I2C2 IntDefaultHandler, // Eroare I2C2 IntDefaultHandler, // SPI1 IntDefaultHandler, // SPI2 IntDefaultHandler, // USART1 IntDefaultHandler, // USART2 IntDefaultHandler //USARTHandler IntDefault/LineDefault3 External Handler, // Alarmă RTC ( A și B) prin EXTI Line IntDefaultHandler, // USB OTG FS Wakeup prin EXTI line IntDefaultHandler, // TIM8 Break și TIM12 IntDefaultHandler, // TIM8 Update și TIM13 IntDefaultHandler, // TIM8 Trigger and Commutation și TIM14 IntDefaultHandler, // TIM14 IntDefaultHandler Comparați IntDefaultHandler, // DMA1 Stream7 IntDefaultHandler, // FSMC IntDefaultHandler, // SDIO IntDefaultHandler, // TIM5 IntDefaultHandler, // SPI3 IntDefaultHandler, // UART4 IntDefaultHandler, // UARTHandler, //TIM5/TIM5&D, //TIM5 IntDefaultHandler, // erori de subîncărcare IntDef aultHandler, / / TIM7 IntDEFAULTHANDER, // DMA2 Stream 0 IntdefaultHandler, // DMA2 Stream 1 IntDEFULTHANDER, // DMA2 Stream 2 IntdefaultHandler, // DMA2 Stream 3 IntdefaultHandler , // DMA2 Stream 4 IntodefaultHandler, Ethernet Throught Intodefault, // Ethernet IntodefaultHandler Line IntDefaultHandler, // CAN2 TX IntDefaultHandler, // CAN2 RX0 IntDefaultHandler, // CAN2 RX1 IntDefaultHandler, // CAN2 SCE IntDefaultHandler, // USB OTG FS IntDefaultHandler, // DMA2 Stream IntDefaultHandler, /Fault Stream DMAH5 și Default Stream ler, // DMA2 Stream 7 IntDefaultHandler, // USART6 IntDefaultHandler, // eveniment I2C3 IntDefaultHandler, // eroare I2C3 IntDefaultHandler, // USB OTG HS End Point 1 Out IntDefaultHandler, // USB OTG HS End Point, / USB OTG HS End Point/USB OTG HS Intrare / 1HTG Intrare prin EXTI IntDefaultHandler, // USB OTG HS IntDefaultHandler, // DCMI IntDefaultHandler, // CRYP crypto IntDefaultHandler, // Hash și Rng IntDefaultHandler, // FPU ); // Acesta este codul care este apelat când procesorul începe prima execuție // în urma unui eveniment de resetare. Se efectuează numai setul absolut necesar, // după care este apelată rutina entry() furnizată de aplicație. Orice acțiuni // fanteziste (cum ar fi luarea deciziilor bazate pe registrul de resetare a cauzelor și // resetarea biților din acel registru) sunt lăsate exclusiv în mâinile // aplicației

Utilizare
@ ".intvec" Plasează __vector_table la începutul secțiunii declarate în fișierul linker. Fișierul în sine poate fi vizualizat aici:

Secțiunea în sine este specificată la început memorie ROM. Adresele pot fi vizualizate (un document care descrie adresarea memoriei flash STM32):

Combinație dintre Directiva IAR și Funcția specială IAR:
#pragma segment="CSTACK" __sfe("CSTACK") Scrie un indicator către partea de sus a stivei la începutul flash-ului.

Tabelul în sine este plin cu adrese de funcții care implementează o buclă eternă. Se face o excepție doar pentru funcțiile care ne interesează:
extern void EXTI_Line0_IntHandler(void); extern void EXTI_Line6_IntHandler(void);
În funcția numită la pornire, se face pur și simplu o tranziție la
extern void __iar_program_start(void); Această funcție este main(). Simbolul în sine poate fi redefinit dacă se dorește:

Să mergem la fișierul principal
Mai întâi, să scriem și să redefinim toate adresele și câmpurile de biți de care avem nevoie.

Listare

//Definiții pentru registrul SCB_AIRCR #define SCB_AIRCR (*(unsigned volatile long*)0xE000ED0C) //acces la SCB_AIRCR #define SCB_AIRCR_GROUP22 0x05FA0500 //modificarea datelor de prioritate //Definiții pentru RCC_AHB1_ENR registrul RCC_AHB1_ENR long*unsigned *unsigned long_ENR_AHBfine_ENR_GROUP22 0x05FA0500 (0x40023830)) //acces la RCC_AHB1ENR reg #define RCC_AHB1_ENR_GPIOA 0x1 //GPIOA bitfield #define RCC_AHB1_ENR_GPIOC 0x4 //GPIOC bitfield #define RCC_AHB1_ENR_GPIOA 0x1 //GPIOA bitfield /RCC_AHB1_ENR_GPIOC 0x4 //GPIOC bitfield #define RCC_AHB1_ENR_GPIOA 0x1 //RCC_AHB1_ENR_ENR_GPIOD_ENR0x40023830) Registrul R # definește RCC_APB2_ENR (*(nesemnat volatil lung * )(0x40023844)) //acces la RCC_APB2ENR reg #define RCC_APB2_ENR_SYSCFG 0x4000 //câmp de biți SYSCFG //Definiții pentru registrele GPIO MODE #define GPIOA_MODER (*(unsigned volatile long*)//0 GPIO_acces400023844) definiți GPIOC_ MODER (*(unsigned volatile long*)(0x40020800)) //acces la regul GPIOC_MODER #define GPIOD_MODER (*(unsigned volatile long*)(0x40020C00)) //acces la regul GPIOD_MODER //definiția registrului GPIO ODR #definire GPIOD (GPIOD_ODR) * (unsigned volatile long*)(0x40020C14)) //acces la regul GPIOD_MODER #define GPIO_ODR_13PIN 0x2000 #define GPIO_ODR_14PIN 0x4000 //Definiții de câmpuri de biți #define GPIO_ODR_13PIN 0x2000 #define GPIO_ODR_0PIN 0x4000 //Definiții de câmpuri de biți #define GPIO_MODER_0BITS #în modul GPIO_MODER_0BITS #0xfine 0BITS_0 IN 0x0 //Mod de intrare Pin 0 # definește GPIO_MODER_6BITS 0x300 //Pin 6 biți de mod #define GPIO_MODER_6IN 0x000 //Pin 6 mod de intrare #define GPIO_MODER_13BITS 0xC000000 //Pin 13 biți de mod #define GPIO_MODER_0/130 biți de ieșire #define GPIO_MODER0 /130 de ieșire #define 0 /130 GPIO_MODER _14BITS 0x30000000 //Pin 14 biți de mod #define GPIO_MODER_14OUT 0x10000000 //Modul de ieșire Pin 14 //Definiția registrului GPIOC_PUPDR #define GPIOC_PUPDR (*(unsigned volatile long*)(0x4002080C)) //acces la GPIOC_PUPDR reg #define GPIOC_PUPDR //define GPIOC_PUPDR bit/define GPIOCBITS_P0 bit/define GPIOCBITS PIOC_PUPDR_6 PU 0x1000 / /PC6 pull-up enable //SYSCFG_EXTIx înregistrează definiții #define SYSCFG_EXTICR1 (*(unsigned volatile long*)0x40013808) //SYSCFG_EXTICR1 acces #define SYSCFG_EXTICR1_0BITS 0xF //#EXTI TIC/EXTICR1_0BITS 0xF //EXTI/EXTICR100 bit 0 - portul A #define SYSCFG_EXTICR2(* (unsigned volatile long*)0x4001380C) //SYSCFG_EXTICR2 acces #define SYSCFG_EXTICR2_6BITS 0xF00 //EXTI 6 biti #define SYSCFG_EXTICR2_6PC 0xTI/EXTICR2_definition port #/EXTI/EXTIR206 - definiție (nesemnat volatil long*) 0x40013C 00) / /EXTI_IMR reg acces #define EXTI_LINE0 0x1 //LINE 0 definition #define EXTI_LINE6 0x40 //LINE 6 definition #define EXTI_RTSR (*(unsigned volatile long*)0x40013C08) //EXTI_RTSR reg _acces #define EXTI_RTSR_ unsigned volatile long* )0x40013C0C) //EXTI_FTSR reg acces #define EXTI_PR (*(unsigned volatile long*)0x40013C14) //EXTI_PR reg acces //NVIC registre și definiții de biți #define NVIC_ISER0_REG (*(unsigned volatile long*)100 volatile) //NVIC_ISER0 reg acces #define NVIC_ISER0_6VECT 0x40 //vect 6 definition #define NVIC_ISER0_23VECT 0x800000 //vect 30 definition #define NVIC_IPR0_ADD (0xE000E400E400) #define NVIC_ISER0_23VECT (volatile*NVICRE) C_IPR0_ADD + 23) ) #define NVIC_IPR6_REG ( *(caracter volatil nesemnat*)(NVIC_IPR0_ADD + 6))

Vă rugăm să rețineți că valorile registrelor speciale MK sunt declarate ca volatil. Acest lucru este necesar pentru ca compilatorul să nu încerce să optimizeze operațiunile de acces la ele, deoarece acestea nu sunt doar locații de memorie, iar valorile lor se pot schimba fără participarea nucleului.

Configurarea grupării prioritare
În primul rând, merită să configurați gruparea cu prioritate de întrerupere: SCB_AIRCR = SCB_AIRCR_GROUP22; .Această acțiune trebuie efectuată o singură dată. În proiectele complexe care folosesc biblioteci terțe, merită verificat Acest lucru. Schimbarea defalcării priorităților în grupuri poate duce la operare incorectă firmware.
Activarea ceasului perifericelor folosite
Permiteți-mi să vă reamintesc că înainte de a începe să lucrați cu unitățile periferice, trebuie să activați sincronizarea acestora:
//Activați SYSCFG , porturile GPIO A și D de tactare RCC_AHB1_ENR |= RCC_AHB1_ENR_GPIOA|RCC_AHB1_ENR_GPIOC|RCC_AHB1_ENR_GPIOD; RCC_APB2_ENR |= RCC_APB2_ENR_SYSCFG;
Nu puteți lucra cu SYSCFG imediat; trebuie să așteptați câteva cicluri de ceas. Dar nu vom face. Să începem inițializarea GPIO.
Inițializare GPIO
LED-urile sunt inițializate la fel ca data trecută:
//Inițializare LED3 și LED5 GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_13BITS)) | GPIO_MODER_13OUT; GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_14BITS)) | GPIO_MODER_14OUT;
Butonul PA0 și pinul PC7 sunt inițializate ca intrare:
//Inițializare pini PA0 și PC6 GPIOA_MODER = (GPIOA_MODER & (~GPIO_MODER_0BITS)) | GPIO_MODER_0IN; GPIOC_MODER = (GPIOC_MODER & (~GPIO_MODER_6BITS)) | GPIO_MODER_6IN;
Dar pentru pinul PC6 trebuie să porniți sursa de alimentare. Tragerea este activată folosind registrul GPIOC_PUPDR:
//Activați extragerea PC6 GPIOC_PUPDR = (GPIOC_PUPDR & (~GPIOC_PUPDR_7BITS)) | GPIOC_PUPDR_6PU;
Configurarea EXTI
Și astfel, trebuie să configurați următorii parametri - activați întreruperile pentru liniile 0 și 6, pentru linia 0 întreruperea pe o margine ascendentă, pentru linia 6 - întrerupere pe o margine descendentă:
//Configurați EXTI EXTI_RTSR |= EXTI_LINE0; EXTI_FTSR |= EXTI_LINE6; EXTI_IMR = EXTI_LINE0|EXTI_LINE6;
Tot ce rămâne este să configurați pinii ai căror porturi sunt conectate la linia EXTI (o soluție ciudată, de exemplu, MCU-urile stellaris pot genera o întrerupere cu orice combinație de pini, acest lucru este mai dificil pentru STM32):
//EXTI la portul conexiunii SYSCFG_EXTICR1 = (SYSCFG_EXTICR1&(~SYSCFG_EXTICR1_0BITS)) | SYSCFG_EXTICR1_0PA; SYSCFG_EXTICR2 = (SYSCFG_EXTICR2&(~SYSCFG_EXTICR2_6BITS)) | SYSCFG_EXTICR2_6PC;
Configurarea NVIC
Tot ce rămâne este să configurați prioritățile de întrerupere și să le mascați pentru a iniția procesarea. Vă rugăm să rețineți că se înregistrează NVIC_IPR sunt disponibile pentru acces octet-octet, ceea ce simplifică foarte mult accesul doar la octeții prioritari necesari ai vectorilor de întrerupere individuali. Este suficient doar să faceți o schimbare cu valoarea numărului vectorului de întrerupere (vezi lista de definiții). Să ne amintim încă o dată că linia EXTI 0 este numărul 6 în tabelul vectorial, iar linia EXTI 5_9 este numărul 23. Pentru STM32, doar cei 4 biți cu cea mai mare prioritate sunt semnificativi:
//Setați prioritatea întreruperilor NVIC_IPR6_REG = 0xF0; NVIC_IPR23_REG = 0x0;
Pentru demonstrație, prioritățile sunt stabilite diferit.
Acum puteți activa întreruperile:
//Activați întreruperi NVIC_ISER0_REG |= NVIC_ISER0_6VECT | NVIC_ISER0_23VECT;
De acum înainte, apăsarea butonului și scurtcircuitarea PC6 și GND va duce la apelarea funcțiilor de gestionare a întreruperilor EXTI_Line0_IntHandler și, respectiv, EXTI_Line6_IntHandler.
Manevrarea întreruperii
În funcțiile de gestionare a întreruperilor, întreruperea trebuie mai întâi ștearsă, după care LED-urile pot fi aprinse. Pentru a demonstra prioritățile de întrerupere, a fost adăugată o buclă eternă la unul dintre handlere. Dacă prioritatea unei întreruperi cu o buclă eternă este mai mică decât prioritatea celei de-a doua, atunci nu poate fi apelată. În caz contrar, îl va putea întrerupe pe primul. Vă sugerez să încercați dvs. diferite valori ale priorității întreruperii și să vedeți clar la ce duce acest lucru ( ATENȚIE - nu uitați de grupurile de întrerupere!).
void EXTI_Line0_IntHandler(void) ( //Șterge întrerupere EXTI_PR = EXTI_LINE0; //Aprinde LED-ul 3 GPIOD_ODR |= GPIO_ODR_13PIN; ) void EXTI_Line6_IntHandler(void) ( //Șterge întrerupere GPIOD_ODR |= GPIO_GPI_4; ) void IO_ODR_14P IN; în timp ce (1); )

În loc de o concluzie

Pentru orice eventualitate, voi oferi o listă completă a programului rezultat.

Listare

//Definiții pentru registrul SCB_AIRCR #define SCB_AIRCR (*(unsigned volatile long*)0xE000ED0C) //acces la SCB_AIRCR #define SCB_AIRCR_GROUP22 0x05FA0500 //modificarea datelor de prioritate //Definiții pentru RCC_AHB1_ENR registrul RCC_AHB1_ENR long*unsigned *unsigned long_ENR_AHBfine_ENR_GROUP22 0x05FA0500 (0x40023830)) //acces la RCC_AHB1ENR reg #define RCC_AHB1_ENR_GPIOA 0x1 //GPIOA bitfield #define RCC_AHB1_ENR_GPIOC 0x4 //GPIOC bitfield #define RCC_AHB1_ENR_GPIOA 0x1 //GPIOA bitfield /RCC_AHB1_ENR_GPIOC 0x4 //GPIOC bitfield #define RCC_AHB1_ENR_GPIOA 0x1 //RCC_AHB1_ENR_ENR_GPIOD_ENR0x40023830) Registrul R # definește RCC_APB2_ENR (*(nesemnat volatil lung * )(0x40023844)) //acces la RCC_APB2ENR reg #define RCC_APB2_ENR_SYSCFG 0x4000 //câmp de biți SYSCFG //Definiții pentru registrele GPIO MODE #define GPIOA_MODER (*(unsigned volatile long*)//0 GPIO_acces400023844) definiți GPIOC_ MODER (*(unsigned volatile long*)(0x40020800)) //acces la regul GPIOC_MODER #define GPIOD_MODER (*(unsigned volatile long*)(0x40020C00)) //acces la regul GPIOD_MODER //definiția registrului GPIO ODR #definire GPIOD (GPIOD_ODR) * (unsigned volatile long*)(0x40020C14)) //acces la regul GPIOD_MODER #define GPIO_ODR_13PIN 0x2000 #define GPIO_ODR_14PIN 0x4000 //Definiții de câmpuri de biți #define GPIO_ODR_13PIN 0x2000 #define GPIO_ODR_0PIN 0x4000 //Definiții de câmpuri de biți #define GPIO_MODER_0BITS #în modul GPIO_MODER_0BITS #0xfine 0BITS_0 IN 0x0 //Mod de intrare Pin 0 # definește GPIO_MODER_6BITS 0x300 //Pin 6 biți de mod #define GPIO_MODER_6IN 0x000 //Pin 6 mod de intrare #define GPIO_MODER_13BITS 0xC000000 //Pin 13 biți de mod #define GPIO_MODER_0/130 biți de ieșire #define GPIO_MODER0 /130 de ieșire #define 0 /130 GPIO_MODER _14BITS 0x30000000 //Pin 14 biți de mod #define GPIO_MODER_14OUT 0x10000000 //Modul de ieșire Pin 14 //Definiția registrului GPIOC_PUPDR #define GPIOC_PUPDR (*(unsigned volatile long*)(0x4002080C)) //acces la GPIOC_PUPDR reg #define GPIOC_PUPDR //define GPIOC_PUPDR bit/define GPIOCBITS_P0 bit/define GPIOCBITS PIOC_PUPDR_6 PU 0x1000 / /PC6 pull-up enable //SYSCFG_EXTIx înregistrează definiții #define SYSCFG_EXTICR1 (*(unsigned volatile long*)0x40013808) //SYSCFG_EXTICR1 acces #define SYSCFG_EXTICR1_0BITS 0xF //#EXTI TIC/EXTICR1_0BITS 0xF //EXTI/EXTICR100 bit 0 - portul A #define SYSCFG_EXTICR2(* (unsigned volatile long*)0x4001380C) //SYSCFG_EXTICR2 acces #define SYSCFG_EXTICR2_6BITS 0xF00 //EXTI 6 biti #define SYSCFG_EXTICR2_6PC 0xTI/EXTICR2_definition port #/EXTI/EXTIR206 - definiție (nesemnat volatil long*) 0x40013C 00) / /EXTI_IMR reg acces #define EXTI_LINE0 0x1 //LINE 0 definition #define EXTI_LINE6 0x40 //LINE 6 definition #define EXTI_RTSR (*(unsigned volatile long*)0x40013C08) //EXTI_RTSR reg _acces #define EXTI_RTSR_ unsigned volatile long* )0x40013C0C) //EXTI_FTSR reg acces #define EXTI_PR (*(unsigned volatile long*)0x40013C14) //EXTI_PR reg acces //NVIC registre și definiții de biți #define NVIC_ISER0_REG (*(unsigned volatile long*)100 volatile) //NVIC_ISER0 reg acces #define NVIC_ISER0_6VECT 0x40 //vect 6 definition #define NVIC_ISER0_23VECT 0x800000 //vect 30 definition #define NVIC_IPR0_ADD (0xE000E400E400) #define NVIC_ISER0_23VECT (volatile*NVICRE) C_IPR0_ADD + 23) ) #define NVIC_IPR6_REG ( *(unsigned volatile char*)(NVIC_IPR0_ADD + 6)) void EXTI_Line0_IntHandler(void); void EXTI_Line6_IntHandler(void); void main() ( //NVIC SCB_AIRCR = SCB_AIRCR_GROUP22; //Activează SYSCFG , porturile GPIO A, C și D tactarea RCC_AHB1_ENR |= RCC_AHB1_ENR_GPIOA|RCC_AHB1_ENR_GPIOC|RCC_AHB1_ENR_GPIOC|RCC_AHB1_ENR_GPIOC|RCC_AHB1_ENR_GPIOC|RCC_AHB1_ENR_GPIOC|RCC_AHB1_ENR_GPIOC|RCC_AHB1_ENR_GPIOC | _SYSC FG; //Inițializare LED3 și LED5 GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_13BITS)) | GPIO_MODER_13OUT; GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_14BITS)) | GPIO_MODER_14OUT; //Inițializare pini PA0 și PC6 GPIOA_MODER = (GPIOA_MODER_MODER) (~GPIOA_MODER_14BITS)) | _MODER = (GPIOC_MODER & (~GPIO_MODER_6BITS ) ) | GPIO_MODER_6IN; //Activați opțiunea PC7 GPIOC_PUPDR = (GPIOC_PUPDR & (~GPIOC_PUPDR_6BITS)) | GPIOC_PUPDR_6PU; //Configurați EXTI EXTI_RTSR |= EXTI_LINE0; EXTI_FTLINE =; EXTI_0; TI la conexiune port SYSCFG_EXTICR1 = (SYSCFG_EXTICR1&(~SYSCFG_EXTICR1_0BITS)) | SYSCFG_EXTICR1_0PA; SYSCFG_EXTICR2 = (SYSCFG_EXTICR2&(~SYSCFG_EXTICR2_6BITS))) | _IPR6 _REG = 0xF0;NVIC_IPR23_REG = 0x00; //Activați întreruperi NVIC_ISER0_REG |= NVIC_ISER0_6VECT | NVIC_ISER0_23VECT; while(1) ( ) ) void EXTI_Line0_IntHandler(void) ( //Șterge întrerupere EXTI_PR = EXTI_LINE0; //Aprinde LED-ul 3 GPIOD_ODR |= GPIO_ODR_13PIN; ) void EXTI_Line6_IntHandler(void) (void) /_/CPR întrerupere EXTI_/CPR = EXTTI6; LED4 GPIOD_ODR |= GPIO_ODR_14PIN; în timp ce(1); )


Pentru a verifica efectul priorităților de întrerupere și al priorităților grupului de întreruperi, încercați să schimbați prioritățile și să observați ce se întâmplă (doi biți - prioritate intra-grup, 2 biți - prioritate grup).

Etichete:

  • STM32
  • Cortex-M
  • BRAŢ
  • microcontrolere
Adaugă etichete

Să aprindem LED-ul!

Deoarece microcontrolerele STM32 sunt adevărate nuclee ARM pe 32 de biți, acest lucru nu va fi ușor de realizat. Totul aici este foarte diferit de metodele obișnuite din PIC sau AVR, unde a fost suficient să configurați portul de ieșire cu o linie și să imprimați valoarea în el cu a doua linie - dar este cu atât mai interesant și mai flexibil.

Arhitectura STM32

Arhitectura microcontrolerelor este descrisă în detaliu în articol, dar permiteți-mi să vă reamintesc principalele puncte care sunt interesante pentru noi acum.

Miezul este tactat de cuarț, de obicei printr-un PLL. Acest - viteza ceasului de bază, sau SYSCLK. Placa STM32VLDiscovery are un cristal de 8 MHz, iar PLL în cele mai multe cazuri este configurat ca un multiplicator de 3 - i.e. SYSCLK de pe placa STM32VLDiscovery este de obicei de 24 MHz.

Un autobuz se extinde din miez AHB, care are propria frecvență de ceas - puteți seta un anumit prescaler în raport cu SYSCLK, dar îl puteți lăsa egal cu unul. Această magistrală este similară cu magistrala dintre procesor și puntea de nord a computerului - în același mod, servește la conectarea nucleului ARM și a procesorului periferic, precum și a memoriei și, desigur, a controlerului DMA.

Două autobuze periferice sunt conectate la magistrala AHB - APB1Și APB2. Sunt echivalente, doar servesc diferite controlere de interfață. Frecvențele ambelor autobuze APB1 și APB2 pot fi setate cu propriile lor prescalere în raport cu AHB, dar pot fi și lăsate egal cu unu. În mod implicit, după pornirea microcontrolerului, toate perifericele sunt pe magistralele APB1 și APB2 dezactivat pentru a economisi energie.

Controlerele de porturi I/O care ne interesează sunt suspendate pe magistrala APB2.

Model periferic în STM32

Toate perifericele microcontrolerelor STM32 sunt configurate conform unei proceduri standard.

  1. Pornirea temporizării controlerului corespunzător - literalmente, furnizarea acestuia cu un semnal de ceas de la magistrala APB;
  2. Setări specifice unui anumit periferic - scriem ceva în registrele de control;
  3. Selectarea surselor de întrerupere - fiecare bloc periferic poate genera întreruperi din diverse motive. Puteți alege „motive” specifice;
  4. Atribuirea unui handler de întrerupere;
  5. Lansarea controlerului.

Dacă nu sunt necesare întreruperi, pașii 3 și 4 pot fi săriți.

Iată, de exemplu, inițializarea temporizatorului (sunt indicați pașii din secvență):

/* 1 */ RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; /* 2 */ TIM6->PSC = 24000; TIM6->ARR = 1000; /* 3 */ TIM6->DIER |= TIM_DIER_UIE; /* 4 */ NVIC_EnableIRQ(TIM6_DAC_IRQn); /* 5 */ TIM6->CR1 |= TIM_CR1_CEN;

Controler porturi I/O

În sfârșit ajungem la subiectul principal al articolului.

Iată cum este aranjat un picior I/O al microcontrolerului STM32F100:

Pare mai complicat decât în ​​PIC sau AVR Dar, de fapt, e în regulă.

Există diode de protecție la intrare care împiedică potențialul piciorului să cadă sub pământ sau să-l ridice deasupra tensiunii de alimentare. În continuare, sunt instalate rezistențe de tracțiune controlate - dacă se dorește, piciorul poate fi tras la sol sau la sursa de alimentare. Cu toate acestea, trebuie să rețineți că aceste bretele sunt destul de slabe.

Intrare

Să luăm în considerare „intrarea”. Semnalul merge direct la linia „Analogic”, iar dacă pinul este configurat ca intrare la un ADC sau comparator - și dacă aceste blocuri sunt pe acest pin - semnalul merge direct la ele. Pentru a lucra cu semnale digitale, este instalat un declanșator Schmitt (acesta este cel cu histerezis), iar ieșirea sa intră în registrul de blocare a datelor de intrare - acum starea pinului poate fi citită în program citind acest registru (prin se numește IDR - registru de date de intrare). Pentru a asigura funcționarea perifericelor non-GPIO agățate de acest picior ca intrare, a fost făcută o atingere numită „Intrare funcție alternativă”. Acest periferic poate fi UART/USART, SPI, USB și multe alte controlere.

Este important să înțelegeți că toate aceste robinete sunt deschise și funcționează simultan, este posibil să nu existe nimic conectat la ele.

Ieșire

Acum „ieșiți”. Datele digitale scrise pe port ca ieșire sunt situate în registrul ODR - registrul de date de ieșire. Este disponibil atât pentru scris, cât și pentru citit. Când citiți din ODR, nu citiți starea piciorului ca intrare! Ai citit ce ai scris în ea.

Aici este ieșirea de la periferice non-GPIO, numită „Ieșire funcție alternativă”, și intrăm în driverul de ieșire. Modul de funcționare al ieșirii din punct de vedere al designului circuitului este configurat aici - puteți face o ieșire push-pull (linia este atrasă rigid de pământ sau putere), o ieșire de colector deschis (tragem linia la puterea, iar pământul este asigurat de ceva extern atârnat de contact) sau opriți complet ieșirea. După driver, ieșirea analogică de la DAC, comparator sau op-amp intră în linie și ajungem din nou cu rezistențe și diode pull-up.

Driverul de ieșire digitală are, de asemenea, controlul pantei sau rata de creștere a tensiunii. Puteți seta abruptul maxim și puteți să vă smuciți piciorul la o frecvență de 50 MHz - dar în acest fel vom obține și interferențe electromagnetice puternice din cauza fronturilor de sunete ascuțite. Puteți seta panta minimă, cu frecventa maxima„doar” 2 MHz - dar și reducerea semnificativă a interferențelor radio.

În imagine puteți vedea un alt registru, „Bit set/reset registers”. Ideea este că puteți scrie direct în registrul ODR sau puteți utiliza registrele BRR/BSRR. De fapt, aceasta este o caracteristică foarte cool, despre care voi vorbi în continuare.

Posibilitati

Acum totul a devenit ca un haos - nu este clar cum să gestionezi toate aceste posibilități. Cu toate acestea, nu, controlerul de port monitorizează posibilele moduri de funcționare a ieșirii și elimină combinațiile incorecte - de exemplu, nu va permite atât driverului de ieșire digitală, cât și ieșirii analogice să funcționeze simultan pe aceeași linie de ieșire. Dar prezența atâtor setări oferă posibilități extinse.

De exemplu, în seriile mai vechi, puteți configura ieșirea cu un colector deschis și porniți pull-up la pământ. Acesta se dovedește a fi exact ceea ce este necesar pentru un autobuz cu 1 fir. Cu toate acestea, în seria STM32F1xx nu există o astfel de opțiune și trebuie să instalați un rezistor extern de pull-up.

Operații atomice

La microcontrolerele vechi, a apărut adesea o situație - dacă doream să schimbăm niște biți din port (și de fapt doar să pornim sau să dezactivăm un pin) - trebuia să citim întregul registru al portului, să setăm/resetăm biții necesari în și scrie înapoi. Totul a fost bine până în momentul în care această operațiune a fost întreruptă la mijloc de o întrerupere. Dacă handlerul pentru această întrerupere a făcut ceva cu același port, a apărut o eroare extrem de subtilă. Acest lucru a fost rezolvat prin diferite mijloace, de exemplu, au interzis la nivel global întreruperile în timp ce portul era procesat - dar trebuie să recunoașteți că aceasta este un fel de opțiune de cârjă.

În STM32, această problemă este rezolvată în hardware - aveți registre setate și resetate de biți (BSRR și BRR), iar aici trei păsări sunt ucise simultan:

  1. nu este nevoie să citiți portul pentru a lucra cu el
  2. pentru a afecta pini specifici, trebuie să lucrați cu biți specifici, mai degrabă decât să încercați să schimbați întregul port
  3. aceste operatii sunt atomice – au loc intr-un singur ciclu si nu pot fi intrerupte la mijloc.

Mai multe detalii despre „biți specifici” - fiecare ciclu de ceas APB2, registrele BSRR și BRR sunt citite, iar conținutul lor este aplicat imediat registrului ODR, iar aceste registre în sine sunt șterse. Astfel, dacă trebuie să setați biții 3 și 5 în portul, scrieți cuvântul 10100 în BSRR și totul este instalat cu succes.

Blocarea unei configurații

Dacă doriți, puteți bloca configurația oricărui pin de la modificări ulterioare - orice încercare de a scrie în registrul de configurare va eșua. Acest lucru este potrivit pentru aplicațiile critice în care trecerea accidentală, de exemplu, de la modul de scurgere deschis la modul push-pull, va arde tot ce este conectat la acest pin sau pinul în sine. Registrul LCKR este destinat să permită blocarea, doar că este echipat cu protecție împotriva scrierii accidentale neintenționate - pentru ca modificările să aibă efect, trebuie să trimiteți o secvență specială la bitul LCKK.

Registre de control

Tot controlul controlerului GPIO este concentrat în registrele de 32 de biți GPIOx_RRR, unde x este numărul portului și RRR este numele registrului.

Registrul de configurare scăzut GPIOx_CRL

Configura primele 8 picioare, numerotate 0..7. Fiecare picior are doi parametri, MODE și CNF.

MOD este responsabil pentru modul de intrare/ieșire și rata de variare a semnalului.

00 - intrare (mod implicit)

01 - ieșire la o viteză de 10 MHz

10 - ieșire la viteza de 2 MHz

11 - iesire la viteza de 50 MHz

CNF este responsabil pentru configurația pinului.

  • În modul de intrare (MODE=00):

    00 - modul analogic

    01 - intrare flotantă (implicit)

    10 - intrare cu tragere la masă sau putere

    11 - rezervat

  • În modul de ieșire (MODE=01, 10 sau 11):

    00 - Ieșire GPIO Push-pull

    01 - Ieșire GPIO Scurgere deschisă

    10 - ieșirea funcției alternative Push-pull

    11 - ieșirea funcției alternative Open dren

Registrul de configurare ridicată GPIOx_CRH

Configurează al doilea 8 picioare, cu numerele 8..15. Totul este similar cu GPIOx_CRL.

Registrul de date de intrare GPIOx_IDR

Fiecare bit IDRy conține starea pinului I/O corespunzător. Numai citire.

Registrul de date de intrare GPIOx_ODR

Fiecare bit ODRy conține starea pinului I/O corespunzător. Puteți scrie date și acestea vor apărea la ieșirea portului, puteți citi date citind valoarea scrisă anterioară.

Setarea/resetarea bitului de ieșire atomică registrul GPIOx_BSRR

Cei mai semnificativi 16 biți sunt pentru resetarea pinilor corespunzători la 0. 0 nu face nimic, 1 resetează bitul corespunzător. Cei 16 biți inferiori sunt pentru setarea biților la 1. În mod similar, scrierea unui „0” nu face nimic, scrierea unui „1” setează bitul corespunzător la 1.

GPIOx_BRR Registrul de resetare a biților de date de ieșire atomică

Cei 16 biți inferiori sunt pentru resetarea pinii corespunzători. 0 - nu face nimic, 1 - resetează bitul corespunzător.

Registrul este doar pentru scriere - este resetat la zero la fiecare ciclu de ceas APB2.

Registrul de blocare a configurației GPIOx_LCKR

Fiecare bit LCKy blochează biții MODE/CNF corespunzători ai registrelor CRL/CRH, astfel încât configurația pinului nu poate fi modificată până la repornire. Pentru a activa blocarea, trebuie să scrieți secvența de blocare în bitul LCKK: 1, 0, 1, citiți 0, citiți 1. Citirea bitului LCKK raportează starea curentă de blocare: 0 - fără blocare, 1 - da.

Lucrați în diferite moduri

Modul de introducere

  • Driverul de ieșire este dezactivat
  • Rezistoarele de tragere sunt pornite în funcție de setările dvs., una dintre cele trei stări - „intrare trasă la pământ”, „intrare trasă la putere” sau „intrare plutitoare”
  • Semnalul de intrare este eșantionat la fiecare ciclu de ceas al magistralei APB2 și scris în registrul IDR, iar citirea acestui registru raportează starea pinului.

Mod de ieșire

  • Driverul de ieșire este activat și acționează astfel:

    În modul „Push-Pull” funcționează ca o jumătate de punte, incluzând tranzistorul superior în cazul „1” și cel inferior în cazul „0”,

    În modul „Open drain”, pornește tranzistorul inferior în cazul „0”, iar în cazul „1” lasă linia neconectată (adică în a treia stare).

  • Declanșatorul Schmitt este activat
  • Rezistoarele de tragere sunt oprite

Mod funcție alternativă (periferice non-GPIO)

  • Driver de ieșire - în modul Push-Pull (de exemplu, așa funcționează piciorul TX al modulului USART) sau Open drain, în funcție de cerințele controlerului
  • Driverul de ieșire este controlat de semnale periferice, nu de registrul ODR
  • Declanșatorul Schmitt este activat
  • Rezistoarele de tragere dezactivate
  • Semnalul de ieșire este eșantionat la fiecare ciclu de ceas al magistralei APB2 și scris în registrul IDR, iar citirea acestui registru raportează starea pinului în modul Open drain.
  • Citirea registrului ODR raportează ultima stare scrisă în modul Push-Pull.

Modul analogic

  • Driver de ieșire dezactivat
  • Declanșatorul Schmitt este complet dezactivat pentru a nu afecta tensiunea de intrare
  • Rezistoarele de tragere dezactivate
  • Registrul IDR este întotdeauna 0.

Toate perifericele analogice interne au impedanță de intrare mare, astfel încât pinul în sine va avea impedanță de intrare mare în raport cu restul circuitului.

În cele din urmă aprind LED-ul

Acum știm totul pentru a aprinde acest LED! Să mergem de la bun început.

Trebuie să activați sincronizarea portului GPIO. Deoarece folosim LED-ul de pe placa Discovery, vom selecta verde - este conectat la portul PC9. Adică, trebuie să activați ceasul GPIOC.

Acum vorbim despre ieșirea Push-pull. Aceasta corespunde cu 00 în registrul CNF.

Ei bine, să fiu sincer, asta-i tot. În cele din urmă, o listă a LED-ului care clipește

#include "stm32f10x.h" int main(void) ( RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; GPIOC->CRH &= !(GPIO_CRH_CNF9_0 | GPIO_CRH_CNF9_1); GPIOC->CRH |= uGPIO_CRH_CRH |= uGPIO_CRH_0_0; ; while (1) ( GPIOC->BSRR |= GPIO_BSRR_BS9; i=0; while(i++) BRR |= GPIO_BRR_BR9; i=0; în timp ce(i++

biblioteca itacone

Și totuși asta nu este tot. Pentru a simplifica tot felul de setări, fac biblioteca itacone. În prezent, implementează lucrul cu pini GPIO și câteva funcții de utilizare generală - dar munca continuă.

Cele mai bune articole pe această temă