Sunteți pe pagina 1din 39

Laborator 13

Utilizarea generatorului de cod de initializare Cube MX


Exemplificari pentru Timere.

Procesul laborios de construire a contextului de fisiere al proiectului si a initializarii


tuturor componentelor poate fi usurat foarte mult prin generarea automata a structurilor si
codului, folosind un mediu grafic realizat de STMicroelectronics, Cube MX.
Asa cum se va constata in continuare, acesta poate genera zeci sau sute de linii de cod
in numai cateva minute, in urma unor setari grafice foarte facile, facandu-se astfel o economie
de timp foarte mare.
Totusi, exista si unele dezavantaje, dintre care principalul este decizia
STMicroelectronics de a retrage suportul pentru Stantard Peripheral Libraries (SPL) in mediul
Cube MX, si promovarea driverelor de tip Hardware Abstraction Layer (HAL) pentru
periferice. Desi driverele HAL au fost considerate de catre foarte multi utilizatori un proiect
imperfect, s-a considerat ca acest pas este favorabil cresterii portabilitatii programelor pentru
zeci de tipuri de microcontrolere ARM de la STMicroelectronics. Ca urmare, unul dintre cei
mai importanti producatori de medii de programare pentru microcontrolere, Keil, a retras
complet pachetul de drivere pentru SPL-API, inlocuindu-le cu drivere HAL-API. Celalalt
producator, IAR, a mentinut insa suportul pentru SPL, motiv pentru care mediul sau, EWARM,
a fost folosit in acesta lucrare. Absenta suportului producatorului STMicroelectronics devine
insa deranjanta la tipurile de microcontrolere dezvoltate de acesta dupa 2016.
In urma numeroaselor sesizari si critici ale utilizatorilor, STMicroelectronics a recurs in
anul 2017 la un compromis, prin crearea driverelor Low Level (LL), utilizabile in mediul
STMCubeL care incearca sa preia si sa reuneasca punctele pozitive de la celelalte doua seturi.
In toate cazurile in care se folosesc functii API apar insa doua probleme principiale:
a) Setul de functii API nu poate fi complet, deoarece numarul acestora ar fi prea
mare pentru a putea fi invatat si manuit comod de catre utilizatori – in prezent documentatia
acestora are peste 2000 de pagini. Aceasta situatie se va accentua in viitor datorita cresterii
continue a numarului si complexitatii blocurilor microcontrolerelor.
b) Functiile API, in special cele care incearca o mai mare portabilitate (cum ar fi
HAL-API), trebuie sa testeze in interior foarte multe conditii pentru incadrarea in contextul
hardware si software in care sunt utilizate, ceea ce le mareste, uneori foarte mult, volumul, cu
scaderea proprtionala a performantei de viteza. Aceasta se incadreaza in principiul general ca,
odata cu cresterea nivelului instructiunilor, creste usurinta utilizarii dar scad si performantele
de viteza.
In aceste conditii, pentru aplicatii industriale de performanta, care cer viteza si
stabilitate, programatorii profesionisti prefera in continuare programarea directa in registre,
eventual mixata cu blocuri de initializare API, care pot fi generate automat. Aceasta mixare este
tolerabila avand in vedere ca secventa de initializare este in general parcursa o singura data, la
pornire. Eventual, in faza de dezvoltare, se poate realiza initial aplicatia folosind SPL sau HAL,
dupa care se poate incerca inlocuirea treptata cu portiuni de cod cu programare prin registre,
deoarece la aplicatii industriale nu se pune in general problema portabilitatii.
Pentru aplicatii foarte complexe, care ar necesita un efort prea mare chiar si pentru
programarea API, se impune uneori utilizarea unui sistem de operare in timp real (RTOS)
specific mediului de programare. Alte sisteme de operare, standarad, cum ar fi Android sau
chiar Linux, pot fi folosite doar pentru aplicatii din sfera bunurilor de larg consum, unde
caracterul lor nedeterminist si stabilitatea redusa pot fi tolerate.

1. Instalarea mediului de generare grafica Cube MX

1. Intr-o prima etapa, se cauta si se decarca varianta curenta a mediului CubeMX de


la adresa www.st.com/stm32cubemx
2. Se extrage (unzip) continutul fisierului stm32cubemx.zip in aceeasi directoare
3. Daca exista drepuri de administrator se da duble click pe fisierul
SetupSTM32CubeMX-VERSION.exe lansand instalarea aplicatiei.
4. Dupa instalare, un icon STM32CubeMX apare pe desktop iar aplicatia este
disponibila si din meniul All Apps
Pentru o buna aprofundare a generatorului de cod Cube, se recomanda consultarea
documentului UM1718.pdf, „STM32CubeMX for STM32 configuration and initialization C
code generation”.
Pentru o initiere rapida in utilizarea acestuia se vor prezenta in continuare o serie de
exemple pentru unele dintre perifericele deja studiate. Se va observa ca acum programatorul nu
mai trebuie sa scrie decat foarte putine linii de cod, in principal secvente C standard, si poate
utiliza functii HAL-API, similare celor SPL-API. Totusi, deoarece unele functii lipsesc, se vor
utiliza fie macro-uri si definitii din fisierele header asociate, fie programare directa prin registre.
Dupa lansare, se alege New Project si este afisata o pagina de selectie. Cel mai comod
este sa se activeze tab-ul Board selector si sa se aleaga placa – in acest caz de tip
STM32F4Discovery, cu microcontroler din seria STM32F407VGTx.
Apare in acest moment in fereastra din stanga meniul Pinout cu diversele blocuri ale
microcontrolerului, iar in fereastra din dreapta microcontrolerul cu diversele functii ale pinilor
in starea normala.
In general este posibil sa se ignore conexiunile deja existente pe placa, cu unele exceptii
care vor fi aratate unde este cazul. Pentru acesta se da Pinout->Clear Pinouts.
Este de asemenea recomandabil sa se cupleze oscilatorul extern cu cuart si sa se rezolve
bucla PLL si prescalerele pentru functionarea la frecventele maxime ale diverselor blocuri.
Aceasta se realizeaza prin selectarea in fereastra stanga RCC->High Speed Clock (HSE)-
>Crystal/Ceramic Resonator. Apoi se intra pe tabul Clock Configuration, se cupleaza HSE si
se modifica HCLK la valoarea 168 MHz, validand rezolvarea automata a coeficientilor PLL,
ca in figura 13.3

Figura 13.3. Cuplarea HSE si setarea HCLK la 168 MHz

Pasii descrisi pana in acest moment sunt valabili practic pentru toate proiectele si vor fi
parcursi la fiecare nou proiect astfel ca nu vor mai fi mentionati in continuare, considerandu-se
ca fiind impliciti.
2. Timer in modul bazic

In continuare se va realiza un proiect care sa foloseasca Timer 4 in modul bazic, cuplat


la oscilatorul intern si al carui continut sa fie afisat pe cele 4 leduri user numere. Se va stabili
un interval de schimbare a continutului de 1 secunda, si se va numara direct pana la 16.
In tab-ul Pinout se va selecta in timer, in cazul de fata TIM4, la care se valideaza Clock
Source->Internal Clock.
In fereastra microcontrolerului se da click pe PD12 si se bifeaza functia GPIO_Output.
Se repeta aceasta operatie pentru PD13, PD14 si PD15, ca in figura 3.14.

Figura 3.14. Selectarea blocurilor si pinilor implicati

Evident, se va asigura ceasul pentru sistem asa cum s-a prezentat in capitolul anterior
(RCC, HSE, HCLK).
Se trece acum la tab-ul Configuration, unde se observa ca a aparut modulul TIM4. Se
da click pe acesta si se stabilesc Parameter Settings, ca in figura 13.5
Figura 13.5. Setarea parametrilor TIM4

Se observa ca deoarece se doreste afisarea numarului pe pinii PD12-PD15, deci


schimbarea bitilor 12-15 ai numarului la 1 secunda, ceasul numaratorului va trebui divizat cu
212=4096. Pornind de la ceasul busului (care are frecventa de 84 MHz conform figurii 13.3), se
introduce valoarea prescalerului 84000000/4096.
Se lasa modul de numarare Up si se stabileste Counter Period (Auto Reload Register)
la capacitatea maxima pe 16 biti, deci 216=655636=0xFFFF.
Deocamdata se lasa ceilalti parametri neschimbati, si nu se schimba nici in celelalte 3
tab-uri ale TIM4.
Se poate da acum Project->Generate Code. Se stabileste calea si numele proiectului (va
fi creata automat o directoare cu numele respectiv) si se alege un Toolchain. Se poate observa
in figura 13.6 ca este posibil sa se foloseasca o gama larga de compilatoare de uz industrial sau
freeware. De remarcat ca cele doua compilatoare industriale, Keil si IAR, pot fi utilizate gratuit
cu o limitare a dimensiunii codului (32k) sau cu o limita de timp (30 de zile), dar prezinta preturi
de ordinul a cateva mii de dolari pentru varianta completa. Compilatoarele freeware au unele
mici lipsuri si posibile bug-uri, dar in majoritatea situatiilor sunt satisfacatoare.
Fereastra pentu setarile proiectului poate fi accesata si fara generarea de cod pentru
unele modificari anterioare/ulterioare si din meniul Project->Settings.
In tab-urile Code Generator si Advanced Settings se pot face unele schimbari, dar in
general se pot folosi cele implicite, deci pot fi lasate neschimbate.
Figura 13.6. Setarile principale ale proiectului

Atunci cand se lanseaza generarea primului cod de proiect apare o fereastra in care se
semnaleaza absenta pachetului firmware, ca in figura 13.7. Se accepta download si instalare a
acestuia, care se va face implicit in directoarea: C:/Users/Nume_User/STM32Cube/Repository/

Figura 13.7. Fereastra de instalare a pachetului firmware STM32Cube_FW

Dupa generare, se deschide proiectul, care va avea toate optiunile, structura necesara si
codurile de initializare pentru toate blocurile implicate.
Se observa ca structura proiectului contine de fisierele necesare ordonate in mai multe
grupuri care initial nu sunt deschise (pentru a nu complica inutil aceasta zona).
In grupul Application exista subgrupul EWARM care contine fisierul
startup_stm32f407.s, iar in grupul Drivers->CMSIS se afla fisierul system_stm32f407.c,
ambele pregatite corespunzator.
Pentru inceput, din grupul Application -> User se da dublu click pe main.c. In fereastra
aparuta este afisat continutul acestui fisier. Se observa ca exista deja toate liniile de cod necesare
initializarii blocurilor implicate. Alte setari s-ar putea observa si in fisierele insotitoare din acest
grup: stm32f4xx_hal_msp.c, care contine parametrii si functiile MCU Specific Package si
stm32f4xx_it.c care contine prototipurile unor handlere de intreruperi, dintre care intotdeauna
este operant HAL_SYSTICK_IRQHandler(); Acesta asigura intreruperea de 1 ms generata
hardware de catre blocul SysTick folosita pentru intarzieri prin functia generala HAL_Delay().
In toate fisierele generate, inclusiv in main.c exista de asemenea mai multe portiuni
marcate cu comentarii /* User CODE BEGIN... */ respectiv /* User CODE END... */. Atentie,
toate liniile de cod care vor fi scrise de catre programator vor trebui sa fie plasate intre doi astfel
de delimitatori (ca in figura 13.9)! In caz contrar, daca se face din nou o generare de cod pentru
acelasi proiect, tot ceea ce este adaugat de programator in afara acestor portiuni va fi sters
automat!
La folosirea generatorului de cod Cube MX functiile API din SPL sunt inlocuite de
functii in mare masura omoloage HAL_API disponibile in fisierele sursa corespunzatoare
perifericelor respective stm32f4xx_hal_....c care se pot gasi in grupul Drivers
STM32F4xxHAL_DRIVER. Pentru aceasta se deschide cu dublu click fisierul respectiv si se
da click pe simbolul f() din dreapta sus. Din meniul drop-down care apare se cauta si se da click
pe functia necesara.

Figura 13.8. Selectarea prototipului unei functii HAL


De retinut ca generatorul de cod initializeaza diversele blocuri implicate, dar nu le si
porneste. De aceea, in acest caz prima instructiune care trebuie adaugata de utilizator este cea
de start in modul basic a timerului vizat.
Se copiaza prototipul functiei (portiunea incadrata in figura 13.8) si se lipeste in
portiunea corespunzatoare din main.c. Aici se fac modificari in conformitate cu descrierea
functiei afisata deasupra prototipului, ca in figura 13.9. Astfel, deoarece parametrul formal
„TIM_HandleTypeDef *htim” este descris ca fiind un pointer, parametrul actual din main va
invoca adresa prin semnul & si numele acestuia , asa cum este declarat el la inceputul fisierului
main.c, in linia 42, adica „htim4”.
In continuare, intr-o bucla infinita ar trebui citita valoarea numaratorului si scrisa in
registrul de date de iesire al GPIOD.
Analizand multitudinea de functii HAL al e TIM se constata ca nu exista o functie de
citire directa a continutului TIM (din considerente legate de alegerea facuta de
STMicroelectronics). In astfel de cazuri se pot folosi macrouri existente in fisierele header
asociate, cere pot fi gasite expandand oricare din grupurile stm32f4xx_hal...c, sau se va folosi
programarea directa prin registre, asa cum se va exemplifica in continuare.
Pentru gasirea unei functii corespunzatoare se cauta in HAL User Manual (documentul
DM00105879.pdf) cu Ctrl+F „TIM exported macros”. Intre acestea, la pagina 914 se gaseste
functia weak „__HAL_TIM_GetCounter” avand ca parametru un poiter catre handlerul de TIM
necesar. Valoarea obtinuta de acesteasta se poate atribui (ca in linia 91 din figura 13.9) unei
variabile c definita in blocul „USER CODE Private Variables” ca find de tip uint16_t.

Figura 13.9. Inserarea codului user pentru Tim Basic

Atentie, functiile macro incep cu dublu underscore!


Alternativ, s-ar putea folosi programarea directa prin registre, de exemplu cu o
instructiune c=TIM4->CNT.
Pentru afisarea bitilor superiori ai lui c pe leduri, se poate de asemenea observa ca nu
exista o functie HAL care sa scrie in registrul de date al GPIO. Cel mai convenabil este aici sa
se foloseasca programarea directa in registre cu o instructiune ca in linia 92:
GPIOD->ODR=c;
Programul poate fi acum compilat si testat, observandu-se numararea pe cele 4 leduri,
la intervale de 1 secunda.
Desi in ansamblu programul pare complicat, trebuie observat ca utilizatorul nu a
trebuit sa scrie decat 3 instructiuni si o declaratie de tip. Aceasta reprezinta un mare avantaj
al utilizarii generatorului de cod Cube MX, astfel ca este posibil ca in viitor sa apara asemenea
instrumente software si pentru microcontrolerele altor producatori. Totusi, reamintim ca viteza
de executie a functiilor HAL este in general scazuta, astfel ca ele pot fi recomandate doar in
portiunile de initializare (care se parcurg doar o data), ale aplicatiilor industriale.

3. Utilizarea blocului timer ca numarator.

Dupa cum arata schema bloc a timer-ului, intrarea sa poate fi cuplata sau la generatorul
de ceas intern sau la un pin extern pe care se aplica impulsuri care vor fi numarate.Timerele
dispun de intrari de numarare TIx, iar unele dintre ele (TIM2, TIM3 si TIM4) dispun si de
intrare de trigerare externa, ETR.
Dupa cum s-a aratat, pinul PA0, la care este cuplat butonul User, poate fi folosit ca
intrare cu trigerare externa pentru timerul TIM2. Fiind un contact mecanic, acesta este afectat
de fenomenul de multicontact, dar poate ilustra functionalitatea numaratorului.
Exemplificam in continuare aceasta functie cu un program care sa numere pe led-uri
impulsurile aplicate pe PA0. Avand in vedere ca led-urile sunt plasate pe pinii PD12-PD15,
variabila in care se citeste continutul timerului trebuie deplasata cu 12 biti spre stanga pentru a
putea fi afisati si bitii inferiori.

Figura 13.10. Selectia PA0 si ETR pentru TIM2


Vom folosi aici proiectul Cube MX anterior (sau eventual o copie a intregii directoare
a acestuia) si il vom modifica corespunzator.
Vom deschide proiectul Tim Basic si in fereastra microcontrolerului din tab-ul Pinout
vom da click pe butonul PA0 (stanga jos). Se observa ca printre functiile acestui pin se poate
selecta TIM2_ETR, adica trigger extern pentru TIM2. Dupa aceasta selectie se merge la blocul
TIM2 si se selecteaza ca sursa de clock ETR2, ca in figura 13.10. Deoarece folosim acum TIM2,
se poate dezactiva TIM4, prin click pe Clock Source->Disable.
In continuare se merge la tab-ul Configuration, unde a disparut TIM4 dar a aparut TIM2.
Se seteaza pentru acesta valoarea maxima 15, avand in vedere ca avem doar 4 leduri pentru
afisarea numarului.
In principiu se recomanda o filtrare a semnalului pe aceasta intrare, conform paginii 623
din Reference Manual, deci in Cube MX se poate seta la Clock Filter una din valorile intre 0 si
15.
Se poate da acum generare de cod si daca proiectul nu este deja deschis se confirma
deschiderea proiectului. Daca proiectul era deja deschis, se da Close, si se trece la fereastra
IAR. Aici a aparut un mesaj care arata ca proiectul a fost modificat de catre Cube MX si se
confirma reincarcarea sa.

Se va observa ca toate instructiunile plasate corect in interiorul blocurilor User au ramas


neschimbate. Daca unele au disparut, inseamna ca nu fusesera plasate corect si se rescriu.
Evident, initializarea s-a facut acum pentru TIM2 astfel ca instructiunile User trebuie
modificate ca sa il contina pe acesta.
In plus, deoarece se numara numai pe cei 4 biti inferiori 0-3, pentru afisarea acestora in
pozitiile PD12-PD15 trebuie facuta deplasarea valorii citite cu 12 biti spre stanga:
c=__HAL_TIM_GetCounter(&htim2)<<12;
Acum se poate compila si testa programul. Prin actionarea PA0 se va observa numararea
pe leduri, precum si fenomenul de multicontact.
Laborator 14
Timere in intreruperi si PWM

Folosind generatorul de cod Cube MX, programatorul este scutit de procesul complex
de initializare astfel ca se poate concentra asupra algoritmului propriu-zis si poate dezvolta
aplicatia intr-un timp mult mai scurt.
Asa cum s-a aratat insa, in buclele repetitive si critice din punct de vedere al vitezei nu
se recomanda folosirea functiilor HAL si in general a limbajului de nivel inalt, ci a programarii
directe in registre, care necesita cunostinte hardware. Folosirea definitiilor standard ale
registrelor si bitilor asigura si in acest caz o buna portabilitate si usureaza mult efortul de
migrare la un nou tip de microcontroler. Chiar si functiile macro, care „infasoara” intr-un mod
simplu instructiuni care folosesc registrele, sunt destul de performante si suficient de abstracte
pentru a facilita o programare mai formala.
O modalitate importanta pentru marirea vitezei o constituie utilizarea intreruperilor.
Acestea reprezinta un mecanism prin care nucleul procesor este degrevat de sarcini de polling,
ocupandu-se preponderent de un program principal, intrerupt doar cand se produc evenimente.

1. Timer cu intreruperi de depasire

Ne propunem sa realizam un program care sa activeze TIM4 care sa numere impulsurile


de la ceasul intern, iar la fiecare depasire a capacitatii maxime sa genereza o intrerupere. In
subrutina de deservire a intreruperii (Interrupt Service Routine –ISR) se va face toggle la ledul
user de pe PD15.

Modul de lucru.

a) Pentru inceput se va crea un nou proiect in Cube MX, selectand placa STM32F4
Discovery.
b) Se va da Clear Pinout, se va activa RCC ->HSE->Quartz Resonator si se va
stabili frecventa nominala de 168 MHz ca in lucrarea precedenta.
c) Se stabilesc pinii PD12, PD13, PD14 si PD15 ca GPIO_Output pentru a se putea
folosi ledurile user.
d) Se activeaza TIM4 cu Clock Source -> Internal Clock.
e) In tabul de configuratie se va da click pe TIM4 si se va deschide fereastra de
configurare a acestuia. La Parameter Settings se va stabili Precaller =1000 si ARR=0xFFFF, iar
la NVIC Settings se va bifa TIM4 Global Interrupt.
f) Se va da generare cod, se va stabili o directoare si un nume de proiect (de
exemplu TIM4 intrerupere depasire), se va genera codul si se va deschide proiectul in IAR.
g) Se vor deschide in fereastra principala fisierele main.c, stm32f4xx_it.c,
stm32f4xx_hal_gpio.c si stm32f4xx_hal_tim.c.
h) Pentru pornirea timerului se va copia din stm32f4xx_hal_tim.c prototipul
functiei HAL_TIM_Base_Start_IT in main.c in interiorul blocului „USER CODE 2”. Se va
scrie parametrul actual sub forma &htim4.
i) In fisierul stm32f4xx_it.c se cauta ISR pentru TIM4, si anume
TIM4_IRQHandler. Aici, intre /* USER CODE BEGIN TIM4_IRQn 0 */ si /* USER CODE
END TIM4_IRQn 0 */, se introduce instructiunea de toggle pentru pinul PD15, cu prototipul
copiat adin fisierul stm32f4xx_hal_gpio.c: HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_15);
ca in figura 14.1, linia 103.
j) Se compileaza si se testeaza programul. De remarcat ca nucleul proceor este
aproape mereu liber, deoarece in bucla infinita nu s-a folosit nici o instructiune.

Figura 14.1. Subrutina de deservire a intreruperii pentru TIM4

2. Timer cu intreruperi de comparatie si depasire

Timerele, ca si alte blocuri periferice, pot genera mai multe surse de intrerupere. Pe
langa intreruperea de update (reinitializare a continutului) folosita anterior, se mai pot genera
intreruperi de egalitate de comparatie si de intrerupere de captura pe fiecare dintre cele 4 canale
de comparatie/captura asociate.
Pentru a se realiza actiuni specifice pentru fiecare tip de intrerupere, avand in vedere ca
se genereaza o singura subrutina de intrerupere pentru un timer, in interiorul acesteia va trebui
sa se testeze fiecare dintre flagurile intreruperilor utilizate, pentru a se decide care este
evenimentul care a generat intreruperea de la momentul respectiv.
In continuare, se va realiza un program care sa genereze, pe langa intreruperea de
depasire, inca doua intreruperi de comparatie pentru doua dintre canalele de comparatie/captura
ale TIM4. Astfel, canalul CC1 se va incarca cu o anumita valoare apropiata de limita maxima,
de exemplu 0xE000, iar canalul CC2 cu o valoare apropiata de limita minima, de exemplu
0x1000. Se va numara direct si apoi invers (mod Center-aligned), egalitatea cu primul canal va
bascula ledul PD14, iar egalitatea cu al doilea canal va bascula ledul PD13. Se va mentine
bascularea de update pe PD15.

Modul de lucru.

a) Se intra din nou in proiectul anterior Cube MX si se deschide tab-ul


Configuration apoi TIM4->Parameter Settings. In Counter Mode, pentru ca bascularea sa se
faca si pe panta crescatoare si pe panta descrescatoare, conform manualului de referinta, pagina
584, trebuie ales modul Center-aligned Mode 3.
b) Se da generare cod si se intra in proiectul IAR. Se poate observa la linia 164 ca
modul de lucru al timerului s-a schimbat la Center-aligned 3.
c) Se poate compila si rula programul in aceasta varianta incipienta. Se observa ca
desi numararea se face cu o perioada dubla, cu doua pante – crescator si descrescator, perioada
de basculare a ledului PD15 nu s-a schimbat. Aceasta se datoreaza faptului ca, in conformitate
cu documentatia intreruperea de depasire se activeaza si la sfarsitul pantei crescatoare cand se
ajunge la Max si la sfarsitul perioadei descrescatoare cand se ajunge la 0.
Pentru a se genera intreruperi de comparatie, trebuie validate si aceste intreruperi pentru
canalele alese. Pentru aceasta se deschide fisierul stm32f4xx_hal_tim.c si se da click pe
simbolul f(). Se deruleaza lista in jos si se gaseste functia HAL_TIM_OC_Start_IT, care va
activa intreruperile de comparatie pentru un canal. Ea va fi folosita de doua ori, atat pentru
canalul 1 cat si pentru canalul 2, in zona de initializare USER CODE 2 ca in figura 14.2.
d) Registrul de comparatie al fiecarui canal implicat trebuie incarcat cu valoarea
necesara. In fisierul cu functii TIM se observa ca nu exista o functie API care sa faca aceasta
scriere direct, ci numai printr-o structura de configurare a canalului, ceea ce este destul de
ineficient. De aceea, scrierea se poate face prin programare directa in registre, sau cautand o
functie exported macro in manualul de utilizare HAL.

Figura 14.2. Startul si initializarea canalelor de comparatie pentru TIM4


Deoarece aici nu se cere modificarea acestei valori, scrierea ei se poate face in secventa
de initializare, tot in zona USER CODE 2. Din considerente didactice, vom scrie ambele
variante mentionate mai sus.
e) Se trece la subrutina de intrerupere si se modifica pentru actiuni specifice
fiecarui flag. Deoarece nu exista functii HAL care sa testeze direct flagurile, se va cauta in
functiile exported macro pentru TIM si se gaseste functia __HAL_TIM_GET_FLAG. Optiunile
pentru parametrii acesteia se gasesc la pagina 810, si se folosesc in ISR ca in figura 14.3. Din
considerente didactice s-a folosit pentru CC2 si varianta cu registre, prin care se investigheaza
bitul TIM_SR_CC2IF din registrul TIM4->SR si se face XOR pe pinul PD13.
f) Se compileaza si se testeaza programul. Se va observa ca PD15 face blink cu
factor de umplere 1/2, PD14 cu factor de umplere 14/16, iar PD13 cu factor de umplere 1/16.
se recomanda sa se deseneze formele de unda si sa se observe momentele in care apar
intreruperile si corespondenta cu semnalizarile ledurilor.

Figura 14.3. Subrutina de servire a intreruperilor de update si comparatie pentru TIM4

3. Timer in modul PWM

Asa cum s-a aratat anterior, TIM4 poate functiona ca generator de unda PWM pe patru
canale cu posibilitatea ca iesirile respective sa fis cuplate ca functii alternate la pinii dotati cu
leduri user, PD12-PD15. Realizarea unui astfel de program este deosebit de simpla folosind
mediul Cube MX.

Modul de lucru.

a) Se deschide Cube MX si se creeaza un nou proiect pentru STM32f4Discovery


se da Clear Pinout si se genereaza clock HSE pe 168 MHz.
b) In tab-ul Pinout se deschide TIM4 si se da Clock Source->Internal Clock si
Channel 4->PWM Generation CH4. Se observa ca in dreapta apare pe PD15 functia
TIM4_CH4.
c) Se trece la tab-ul Configuration si se deschide TIM4. In Parameter settings se da
Prescaller=1000, ARR=0xFFFF si Pulse 0x2000.
d) Se da generare cod, se alege o directoare si un nume, si se deschide proiectul in
IAR, cu afisarea main.c si stm32f4xx_hal_tim.c.
e) Singurul lucru care trebuie facut este sa se porneasca timerul in modul PWM.
Pentru aceasta se deschide se da click pe f() si se deruleaza in jos pana se gaseste functia
HAL_TIM_PWM_Start si se copiaza prototipul acesteia in main.c in zona de initializare, USER
CODE 2, cu parametrii actuali copiati din descrierea prototipului ca in figura 14.4.

Figura 14.4. Initializarea TIM4 pentru generarea PWM pe PD15

f) Se poate verifica faptul ca atat TIM4 cat si GPIOD sunt initializati conform
proiectului si se poate compila si testa programul. Se va observa ca ledul clipeste cu un factor
de unda 0x2000/0xFFFF adica 1/8.

4. Modificarea automata a factorului de umplere PWM

Factorul de umplere este dat de raportul intre durata pulsului si valoarea maxima, ARR
care au fost setate initial prin Cube MX la 0x2000 si respectiv 0xFFFF. Programul poate sa
necesite in anumite situatii modificarea acestui factor de umplere, de regula prin schimbarea
duratei pulsului.
Din pacate, prin functii HAL acest lucru nu se poate face direct ci printr-o structura de
tip sConfig, dupa cum se poate observa prin reverse engeneering. Ce de obicei, exista doua
solutii: prin programarea in registre cu instructiunea TIMx->CCR4 = PulseVal (pagina 629 din
Reference Manual) sau cu o functie exported macro __HAL_TIM_SET_COMPARE (pagina
913 din HAL Manual).
Ne propunem sa modificam programul anterior pentru ca factorul de umplere sa creasca
automat in bucla infinita pana la o valoare maxima si apoi sa reia procesul de la 0. Pentru a se
putea observa usor factorul de umplere pe led se va mari de 100 de ori frecventa de clipire,
astfel incat ochiul va integra luminozitatea, facand-o proportionala cu durata pulsului.
Modul de lucru:
a) Se modifica in Cube MX->Configuration->TIM4->Parameter Settings
-> Prescaller=10.
b) Se genereaza codul si se reintra in IAR
c) Se declara variabila PulseVal ca unsigned integer pe 16 biti pentru Pulse cu
instructiunea uint16_t PulseVal;
d) In bucla principala se introduc instructiunile de modificare a registrului de
comparare pentru TIM4_CH4 si de incrementare la fiecare parcurgere a buclei a acestei valori,
cu o mica intarziere pentru reducerea vitezei de schimbare, ca in figura 14.5.

Figura 14.5. Modificarea automata a factorului de umplere PWM pe PD15

5. Modificarea prin EXTI a factorului de umplere PWM

Se va executa in continuare un program care sa permita cresterea factorului de umplere


la fiecare apasare a butonului User. Pentru aceasta se poate folosi intreruperea externa de pe
linia 0, functie alternata a butonului de pe PA0, prin modificarea programului precedent.
Modul de lucru.

a) Se va reintra in proiectul Cube MX si in tab-ul Pinout se va da click pe PA0 si se va


selecta GPIO_EXTI0.
b) Se intra in tab-ul Configuration si se da click pe NVIC. Aici se va bifa EXTI Line0
Interrupt si se va stabili o prioritate diferita de 0, care este deja rezervata pentru System tick
timer.
c) Se genereaza codul si se deschide proiectul in IAR.
d) Instructiunile care erau in bucla infinita while(1) din main.c se taie si se introduc in
stm32f4xx_it.c in EXTI0_IRQHandler in zona USER CODE EXTI0_IRQn 1, ca in figura
14.6.
e) In zona USER CODE 0 a fisierului stm32f4xx_it.c se introduc variabilele externe
invocate de ISR, si care vor fi transmise catre main.c :

/* USER CODE BEGIN 0 */


extern uint16_t PulseVal;
extern TIM_HandleTypeDef htim4;
/* USER CODE end 0 */

Figura 14.6. Modificarea prin EXTI a factorului de umplere PWM pe PD15

f) Se compileaza si se testeaza programul. Se va observa ca prin apasarea butonului user


luminozitatea ledului PD15 creste pana la o valoare maxima iar apoi reincepe de la minim.
Fenomenul de multicontact face ca sa se sara peste mai multe trepte la fiecare apasare astfel ca
cresterea nu va fi chiar uniforma.
g) Se va modifica programul folosind algoritmul de eliminare a multicontactului prezentat
anterior si se va seta marimea treptei astfel incat sa existe numai 8 sau 16 trepte de luminozitate.
Laborator 15
Intrari analogice cu transmitere seriala (ADC-USART)

Pentru achizitia de date prin esantionarea si converisa numerica a unui semnal analogic,
cea mai eficienta metoda de initializare a sistemului o constituie utilizarea mediului grafic Cube
MX. Rezultatele conversiei rezultatele se pot transmite serial prin USART (eventual se pot
stoca initial local in memorie prin DMA circular), I2C, SPI sau CAN-bus. Pentru obtinerea
vitezei maxime, se poate lucra in intreruperi atat la conversie cat si la transmisie.

1. Conversia analog-numerica si transmiterea prin USART

Vom realiza in continuare un program care sa faca conversia anaolg numerica a unui
semnal aplicat pe intrarea 10 a ADC1 (pinul PC0) si sa transmita prin USART rezultatul cu 4
cifre zecimale si terminator (CR+LF). ADC va fi initial pornit in finalul secventa de initializare
si functiona in regim de conversie continua cu generare de intrerupere EOC, care va determina
citirea rezultatului intr-o variabila globala. La terminarea unei transmisii, subrutina de
intrerupere USART va converti in zecimal valoarea actuala a rezultatului conversiei si il
transmite serial. Activarea primei transmisii se va face la finalul secventei de initializare.
Acest mod de lucru presupune si conversie si transmisie continua. In cazul in conversia
este mai rapida, unele dintre rezultatele acesteia nu mai apuca sa fie transmise, de aceea este
recomandat ca viteza ADC sa se aleaga cat mai redusa (suficienta insa pentru aplicatia concreta
si pentru a nu depasi timpul de transmisie), deoarece aceasta imbunatateste precizia conversiei.
De exemplu, daca rata de baud este 115200 bps, se vor transmite aproximativ 10000
caractere pe secunda, deci sub 1700 de rezultate ale conversiei pe secunda. Prin urmare, se pot
folosi chiar si cele mai mici viteze pentru ADC integrat in microcontrolerul STM32F407VG.
Programul ar putea fi modificat astfel ca sfarsitul unei transmisii sa declanseze o noua
conversie analog-numerica, avand in vedere ca durata acesteia poate fi considerata neglijabila
(0.4 microsecunde, tipic de 1000 de ori mai mica decat a transmisiei). Totusi, trebuie tinut cont
si de durata conversiei din binar in zecimal care poate fi destul de mare daca se foloseste o
functie C universala (de tip „sprintf”).

Modul de lucru

Se lanseaza mediul Cube MX si se genereaza un nou proiect folosind placa STM32f4


Discovery. In tab-ul Pinout se da Pinout->Clear, se valideaza RCC->High Speed Clock(HSE)
->Crystal/Ceramic Resonator, dupa care se intra in tab-ul Clock Configuration, se bifeaza HSE,
se scrie in HCLK (MHz) valoarea maxima de 168 si se da Enter pentru autoajustare.
Revenind in tab-ul Pinout, alegem ca intrare analogica ca exemplu PC0. Cu click pe
acest pin se alege functia ADC1-IN10, ceea ce va activa intrarea IN10 a primului ADC, dupa
cum se poate verifica in fereastra din stanga.
De asemenea, se va selecta pentru PD7 functia USART1-RX si pentru PD6 functia
USART1-TX, dupa care se va activa in fereastra din stanga USRT1->Asynchronous, astfel ca
pinout-ul va arata ca in figura 15.1.
Figura 15.1. Pinout pentru ADC-USART

In tab-ul Configuration se intra mai inatai la USART1 si se bifeaza in NVIC setarea


USART1 global interrrupt. Pentru inceput se va lucra fara DMA, deci ceilalti parametri se pot
lasa deocamdata neschimbati.
Se intra acum in ADC1, iar aici, in NVIC Settings se bifeaza „ADC1, ADC2 and ADC3
global interrupt”. Retul parametrilor se vor lasa deocamdata neschimbati.
Se intra in final in NVIC si se stabilesc prioritati diferite de 0 pentru USART si ADC
(de exemplu 1, respectiv 2), avand in vedere ca prioritatea o este alocata SysTick.
Se da Generate Code si se alege o directoare si un nume de proiect, dupa care se intra
in IAR si se deschide Application->User->main.c. Se deschide de asemenea din grupul Drivers
STM32F4xx_HAL_Driver fisierul sursa stm32f4xx_hal_adc.c. Din functiile acestuia se
copiaza prototipul functiei HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc) si se introduce
in main.c in zona USER CODE 2, cu parametrul &hadc1.
Se deschide acum fisierul sursa stm32f4xx_hal_uart.c, iar din functiile acestuia se
copiaza prototipul functiei HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t
*pData, uint16_t Size) si se introduce in main.c in zona USER CODE 2 cu parametrii actuali
ca in figura 15.2.
Se observa ca se poate porni transmisia trimitand un sir de caractere initial, sau se poate
activa pur si simplu flagul TC (transmission complete) cu functia de tip exported macro
__HAL_UART_ENABLE_IT(&huart1,UART_IT_TC), care se poate gasi in headerul
stm32f4xx_hal_uart.h.
Figura 15.2. Fisierul main.c cu secventa de start ADC si USART

Se deschide acum fisierul stm32f4xx_it.c care contin rutinele de tratare a intreruperilor


si se modifica comform figurii 15.3.
Din fisierul sursa stm32f4xx_hal_adc.c se copiaza prototipul functiei de citire a
rezultatului converisei, HAL_ADC_GetValue(ADC_HandleTypeDef* hadc), se seteaza
parametrul &hadc1 si se plaseaza in zona USER CODE 0 din rutina ADC_IRQHandler.
Rezultatul este alocat unei variabile declarata in zona USER CODE 0 a fisierului, in forma:
uint8_t c;
De asemenea, deoarece functia HAL_ADC_IRQHandler(&hadc1) opreste intreruperile
convertorului, dupa aceasta trebuie reintrodusa instructiunea de pornire a acestuia,
HAL_ADC_Start_IT(&hadc1).
Din fisierul sursa stm32f4xx_hal_uart.c se copiaza prototipul functiei de transmisie in
intreruperi, HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData,
uint16_t Size), se seteaza primul parametru &huart1, al doilea este un pointer catre un buffer
care contine cele 4 cifre ale rezultatului si cei doi terminatori, iar al treilea parametru este
dimensiunea acestui buffer. Instructiunea se plaseaza in zona USER CODE 1 din rutina
USART1_IRQHandler, iar bufferul se declara in zona USER CODE 0 a fisierului, in forma:
char buff[6];
Conversia din formatul binar in string se poate face cu o instructiune sprintf cu
parametrii ca in figura 15.3. De remarcat ca aceasta adauga la codul obiect toata biblioteca
<stdio.h>, avand circa 8000 octeti, ceea ce teoretic micsoreaza viteza. Totusi, viteza nu este
critica aici, fiind limitata mult de catre transmisia seriala. O alternativa mult mai eficienta ar fi
utilizarea in locul functiei „sprintf” a unei secvente de tipul prezentat anterior:
buff[0]=c/1000+0x30;
buff[1]=c%1000/100+0x30;
buff[2]=c%100/10+0x30;
buff[3]=c%10+0x30;
buff[4]=0x0D;
buff[5]=0x0A;

Figura 15.3. Fisierul stm32f4xx_it.c cu rutinele de tratare a intreruperilor pentru ADC


si USART

Observatie asupra ordinii instructiunilor in rutinele IRQ_Handle. Dupa cum se observa


in figura 15.3, instructiunea de transmisie a fost plasata in zona USER CODE 1, dupa
instructiunea HAL_UART_IRQHandler(&huart1). Aceasta instructiune activeaza intreruperea
TC numai dupa ce se termina de transmis un caracter, ceea ce nu s-ar putea produce daca nu
plasam instructiunea HAL_UART_Transmit_IT dupa aceasta. Eventual am putea plasa aici o
instructiune care sa reactiveze intreruperile TC, cum ar fi USART1->CR1 |=
USART_CR1_TCIE sau __HAL_UART_ENABLE_IT(&huart1,UART_IT_TC).
Instructiunea de citire a valorii conversiei analog-numerice poate fi plasata sau in prima
sau in a doua zona USER CODE din subrutina de intreruperi a ADC .
Laborator 16
Interfata SPI. Cuplarea cu accelerometrul LIS3DH

Interfata seriala cu periferice (SPI) este o modalitate simpla si foarte raspandita pentru
cuplarea unor dispozitive care detin un astfel de modul, cu viteze si distante nu foarte mari.
Comunicatia este seriala sincrona, suporta modul master-slave sau multimaster si se realizeaza
pe 4 fire:
• Master In / Slave Out (MISO). Acest pin receptioneaza datele in modul master si
transmite date in modul Slave;
• Master Out Slave In (MOSI). Acest pin transmite date in modul Master si
receptioneaza datele in modul Slave;
• Serial Clock (SCK).Acest pin este iesire de ceas in modul Master si intrare de ceas in
modul Slave.
• Slave Select (NSS). Acest pin se poate folosi pentru a selecta un dispozitiv Slave. El
actioneaza ca un „chip select” pentru a permite dispozitivului Master sa comunice individual
cu un Slave cu evitarea coliziunilor pe liniile de date. El poate fi util de asemenea in sistemele
multimaster, putand fi configurat si ca intrare pentru a determina trecerea unui Master in starea
Slave.
Interconectarea între un dispozitiv master şi unul slave este prezentată în figura 16.1. Pinul
SCK este ieşire de ceas generata de dispzitivul master şi intrare de ceas pentru dispozitivul slave.
Scrierea în registrul de date al SPI din master porneşte generatorul de ceas al SPI şi apoi datele
scrise sunt scoase prin deplasare de pe pinul MOSI, întrând pe pinul MOSI al dispozitivului slave.
După transmiterea unui octet, ceasul SPI se opreşte şi setează flagul de sfârşit de transmisie TXE.
Dacă este activat bitul de validare a întreruperilor de la SPI (SPIE), apare o cerere de întrerupere.

Figura 16.1. Interconectarea modulelor master şi slave prin SPI

Cele două registre de deplasare din master şi slave pot fi considerate ca un singur
registru de deplasare circular de 16 biţi distribuit, după cum se arată în figura 3.30. Când
datele sunt deplasate din registrul master către slave, datele sunt, de asemenea, deplasate
simultan în direcţie opusă. Aceasta înseamnă că într-un ciclu de deplasare, datele din
master şi slave sunt interschimbate.
In general exista unul sau mai multe registre tampon la transmisie şi la recepţie.
Totusi, nu se recomanda sa se scrie un octet nou în registrul de date al SPI înainte de a se
termina un ciclu complet de deplasare. La recepţie, se recomanda ca un octet care a fost
deja recepţionat trebuie citit din registrul de date al SPI înainte ca următorul octet să fie
complet recepţionat.
Moduri de transfer de date SPI la microcontrolere STM32F4

Semnalul de ceas SCK poate sa fie initial in 0 (CPOL=0) sau in 1 (CPOL=1), iar
esantionarea se poate face pe primul front (CPHA=0) sau pe al doilea front (CPHA=1).
Există deci patru combinaţii de fază şi polarităţi ale SCK în raport cu datele seriale,
determinate acesti biţi de control CPHA şi CPOL astfel ca formatul transferurilor de date
este ca în figura 16.2. şi 16.3.

Figura 16.2. Formatul transferurilor de date in functie de CPOL si CPHA

Functionarea in modul Master

In modul Master cesul este generat pe pinul SCK, datele sunt emise pe pinul MOSI
si sunt receptionate pe pinul MISO.
Initializarea modulului presupune urmatorii pasi:
• Se selecteaza viteza (rata de baud) prin bitii BR[2:0] din registrul de control CR1
• Se alege configuratia CPOL si CPHA daca se foloseste modul implicit (Motorola).
Acest pas nu este necesar daca se alege standardul Texas Instruments (TI).
• Se alege formatul 8 sau 16 biti prin bitul DFF
• Se alege formatul cadrului prin bitul LSBFIRST din CR1
• Pinul NSS va fi conectat la 1 logic in modul hardware pe toata durata transmisiei.
In modul software se seteaza bitii SSM si SSI in registrul CR1.

Transmisia. Secventa de transmisie este pornita printr-o scriere in TxBuffer, ceea


ce determina inceperea serializarii prin registrul de deplasare si trecerea flagului TXE in
0. In momentul in care s-a terminat aceasta etapa, bitul TXE din registrul de stare (SR) este
setat de harware, permitand o noua scriere in buffer. Daca bitul Transmitter Empty
Interrupt Enable (TXEIE) din registrul CR2 este setat, se genereaza o cerere de intrerupere.
Receptia. La ultima esantionare din cadru a liniei de intrare, datele din registrul de
deplasare al receptorului sunt transferate in bufferul de receptie, iar flagul RXNE (Receiver
Not Empty) din SR este setat de catre hardware. Daca bitul Receiver Not Empty Interrupt
Enable (RXNEIE) din registrul CR2 este setat, se genereaza o intrerupere. Bitul RXNE
este sters atunci cand se citeste registrul de date (DR).
Alte informatii despre blocul de interfata SPI (in special registrele implicate) din
microcontrolerele STM32F4 se gasesc in manualul de referinta RM0090.

Accelerometrul LIS3DH

Placa STM32F4 Discovery este dotata cu un circuit integrat cu senzor de acceleratie


in 3 axe, LIS3DH. Acesta este cuplat la microcontrolerul STM32F407 prin SPI, pe pinii
PA5, PA6 si PA7 cu semnalele SCK, MISO si respectiv MOSI, iar activarea circuitului
este realizata prin cuplarea intrarii sale CS la pinul PE3 al microcontrolerului, conform
schemei placii din documentului UM1472 User manual, pagina 40. Cele doua iesiri de
cerere de intrerupere sunt de asemenea cuplate la pinii PE0 si PE1.
Vom folosi in continuare acest senzor pentru exemplificarea programarii si
functionarii interfetei SPI.
Descrierea completa a accelerometrului LIS3DH se afla in documentul
CD00274221.pdf.
Diagramele de timp ale functionarii acestuia sunt cele din figura 16.3. Se observa
ca ceasul trebuie sa aiba polaritatea cu 0 logic in starea activa (deci CPOL=0), iar
esantionarea se face pe frontul crescator, al doilea, al ceasului (deci CPHA=1).
De asemenea, se observa ca formatul datelor cuprinde doua cuvinte pe 8 biti, fiecare
cu MSB primul. Primul octet contine adresa, iar al doilea datele de scris sau citit. In octetul
de adresa, primul bit trebuie sa fie in 1 pentru citire si in 0 pentru scriere, iar al doilea
trebuie sa fie in 1 daca circuitul este Master si in 0 daca circuitul este Slave.

Figura 16.3. Diagramele de timp ale interfetei SPI din accelerometrul LIS3DH
Registrele cele mai importante ale circuitului sunt descrise in continuare.

1. CTRL_REG1 (20h) - Registrul de control 1.


• Bitii 7-4, numiti ODR[3:0] seteaza frecventa de citire a senzorului;
• Bitul 3, numit LPen valideaza modul de lucru Low Power;
• Bitii 2-0, numiti Zen, Yen si Xen, valideaza azele Z, Y si respectiv X.

2. OUT_X_L (28h), OUT_X_H (29h) – Registrul de date pentru acceleratia pe axa X;

3. OUT_Y_L (2Ah), OUT_Y_H (2Bh) – Registrul de date pentru acceleratia pe axa Y;

4. OUT_Z_L (2Ch), OUT_Z_H (2Dh) – Registrul de date pentru acceleratia pe axa Z.

Vom realiza in continuare un program care sa citeasca prin SPI datele de la doua axe ale
accelerometrului (X si Y), si sa se afiseze pe ledurile user depasirea unei anumite valori. Astfel, daca se
inclina placa intr-o directie, depasind pragul de 10/256 se va aprinde ledul de pe directia respectiva.

Modul de lucru

Se deschide generatorul de cod Cube MX, se cere un proiect nou si se alege placa STM32F4
Discovery. In tab-ul Pinout se da Clear Pinout si se alege in RCC->HSE->Crystal/Ceramic Resonator.
Se intra in tab-ul Clock Configuration, se bifeaza HSE si se cere frecventa 168 MHz.
Se revine la tab-ul Pinout si se activeaza SPI1 in modul Full-Duplex Master, observandu-se ca
sunt automat activati pinii PA5,PA6 si PA7 in modul SPI1_SCK, SPI1_MISO si respectiv SPI1_MOSI.
Pentru activarea circuitului LIS3D trebuie setat de asemenea, asa cum s-a aratat, pinul PE3 in modul
GPIO_Output.
Tot in modul GPIO_Output trebuie setati si pinii PD12, PD13, PD14 si PD15, pentru vizualizare
pe ledurile user.
Se trece in continuare in tab-ul Configuration si se da click pe SPI1. Setarile pot sa ramana in
starea implicita: format Motorola, Data Size 8 bit, First Bit MSB, Prescaler 2, CPOL Low, CPHA 1
Edge, CRC Disabled, NSS Software. Se poate da acum generare cod, specificand numele si calea.
Observatie: CPOL Low pare in contradictie cu diagramele de timp, dar aceasta este totusi
setarea corecta. Aceasta setare trebuie interpretata in sensul ca tranzitia intre biti are loc la trecerea spre
0 logic a liniei SCK, dupa cum se specifica in documentatie.
Se intra acum in mediul IAR si se deschide fisierul main.c.
Dupa initializarea microcontrolerului va trebui facuta initializarea accelerometrului, prin
scrierea unor anumiti octeti in unele registre al acestuia. Deoarece accelerometrul are nevoie de un timp
de reset hardware, este recomandabil sa se lase o intarziere de 1 secunda inainte de prima scriere in
registrele sale. In continuare trebuie scris cel putin registrul CR1 al accelerometrului pentru stabilirea
vitezei de citire a senzorilor si validarea directiilor necesare. Alegand o viteza medie, de 100 de citiri pe
secunda, si validand toate directiile, rezulta ca in acest registri, avand adresa 0x20 trebuie scris octetul
0x57.
Pentru cazul in care se doresc si alte scrieri pentru modificari ulterioare ale setarilor, se va realiza
o rutina de transmisie de la microcontroler, care va fi plasata in zona USER CODE 0 a fisierului main.c.
De asemenea, tot aici se va plasa o rutina de receptie de la accelerometru, care de regula va fi folosita in
mod repetat.
Dupa cum arata diagrama de timp din figura 16.3, la transmisie trebuie trimisa mai intai adresa,
cu primul bit in 0 (Write) si al doilea tot in 0 (Slave). In continuare se trimite un octet de date. Evident,
atunci cand este folosit SPI, pinul CS trebuie sa fie, conform documentatiei (pagina 24), in 0 logic, deci
la inceputul acestor rutine trebuie pus in 0 pinul PE3 iar la sfarsit acesta trebuie pus in 1.
Mai intai se va scrie o rutina de transmisie SPI_Tx avand doua argumente de tip intreg pe 8 biti
fara semn: adresa si datele.
Deschizand fisierul „stm32f4xx_hal_gpio.c” se alege pentru scrierea pinului PE3 functia
HAL_GPIO_WritePin. Aceasta se va copia la inceputul si sfarsitul rutinei, prima oara cu pozitionare in
RESET iar a doua oara in SET.
Din fisierul „stm32f4xx_hal_spi.c” se va copia prototipul functiei HAL_SPI_Transmit, si
conform prezentarii acesteia, se va lua ca prim parametru un pointer catre handlerul spi1, al doilea
parametru va fi un pointer catre octetul transmis, al treilea numarul de octeti transmisi (in cazul de fata
1) si ultimul va fi o valoare pentru time-out. Conform descrierii formelor de unda, mai intai se va
transmite octetul de adresa si apoi cel de date, astfel ca rutina SPI_Tx va fi ca in figura 16.4.
Pentru receptie se va scrie acum o subrutina care va returna un intreg pe 8 biti, avand ca
argument adresa registrului vizat din dispozitivul slave. Conform diagramei de timp a circuitului
LIS3DH, octetul de adresa, care va fi si aici transmis mai intai, va trebuie sa aiba primul bit in 1. De
aceea, se face un SAU intre octetul de adresa si 0x80 si apoi se transmite, tot cu functia
HAL_SPI_Transmit. Apoi se va apela functia HAL-API de receptie, al carei prototip se ia din fisierul
sursa „stm32f4xx_hal_spi.c”, si care va avea ca prim parametru un pointer catre handlerul spi1, iar al
doilea parametru va fi un pointer catre o variabila in care se va stoca octetul receptionat –rezultatul
returnat de subrutina. Prin urmare, aceasta va arata ca in figura 16.4

Figura 16.4. Subrutinele de transmisie si receptie I2C

In functia main(void), dupa initializarile intruduse de generatorul Cube MX, in zona


USR CODE 2, dupa cum s-a aratat, trebuie trimis octetul de programare 0x57 la adresa lui CR1,
0x20.
Acum, in bucla infinita while(1) se pot citi datele de la accelerometru si li se poate da o
utilizare. Din documentatia LIS3DH, se poate vedea ca datele pentru x se gasesc in registrele
de la adresa 0x29 (octetul superior) si 0x28 (octetul inferior). Vom folosi rutina de receptie
scrisa anterior pentru a citi doar octetul superior al lui x, considerand ca sunt suficiente aici 256
de trepte. La fel, de la adresa 0x2B vom citi octetul superior al lui y.
Cele trei variabile folosite: c din functia de receptie, x si y din bucla infinita, vor fi
desigur declarate in zona initiala de USER CODE PV ca intregi fara semna de 8 biti, unit8_t.
Datele respective pot fi in continuare utilizate conform descrierii programului. Astfel,
daca pe o directie rezultatul este mai mic decat -10, se va aprinde ledul din directia respectiva,
iar daca este mai mare decat 10 se va aprinde ledul din directia opusa. Deoarece nu avem la
dispozitie decat 4 leduri user, nu se vor citi in acest program si datele de pe axa z, dar ele pot fi
tratate absolut similar.
Prin urmare, aceasta parte din fisierul main arata ca in figura 16.5.

Figura 16.5. Zona de initializare, citire si utilizare a datelor de la LIS 3DH

Se compileaza, se incarca programul in memorie si se ruleaza. Prin inclinarea sau


miscarea accelerata intr-o directie sau alta se poate observa indicatia ledurilor user.
Programul poate fi completat cu citirea datelor pentru axa z, si transmiterea acestora
catre o aplicatie grafica pentru afisare.
Laborator 17
Interfata Inter-Integrated Circuit - I2C

Interfata si protocolul Inter-Integrated Circuit – I2C, este folosita destul de des pentru
cuplarea unei mari varietati de dispozitive la distante reduse (pe acceasi placa) si cu viteze medii
(pana la 3,4Mbps): convertoare analog-numerice si numeric analogice integrate, memorii CD-
card, sisteme de afisaj, diversi senzori etc. Dezvoltata initial de Philips (in prezent NXP), ea a
fost adoptata de multi producatori de dispozitive destinate sa comunice prin putini pini, si sa
permita lucrul in retele multi-master. Se folosesc numai doua fire de semnal si un protocol
destul de sofisticat de arbitrare a drepturilor dispozitivelor, dar care s-a dovedit foarte fiabil in
ultimele doua decenii. Totusi, deoarece nu sunt prevazute masuri deosebite de imunitate la
zgomot, ea tinde sa fie inlocuita in aplicatii pentru medii puternic perturbate de alte interfete,
cum ar fi CAN-bus (Computer Array Network).
Microcontrolerele din seria STM32F4 sunt dotate cu trei interfete de uz general I2C care
pot functiona atat ca Master si ca Slave, cu adrese pe 7 sau 10 biti (in modul Slave pot fi doua
adrese), viteze de 100/400kHz si compatibilitate PMBus/SMBus.
Functionarea si caracteristicile complete sunt prezentate in manualul de referinta al
microcontrolerului, iar conectarea la pini este prezentata in Datasheet (DocID022152, pag. 61-
69).

17.1. Prezentare generală a protocolului I2C

Pricipalele caracteristici ale acestui standard sunt următoarele:


• Sunt necesare numai două fire de comunicaţie: o linie serială pentru date (SDA)
si o linie serială pentru ceas (SCL);
• Fiecare dispozitiv cuplat la magistrală are o adresă proprie unică;
• Pot exista mai multe dispozitive slave şi mai multe dispozitive master, ambele
categorii putând să transmită şi să recepţioneze. Numărul total de dispozitive este limitat de
spaţiul de adrese (în mod normal 128) şi de caracteristicile electrice ale magistralei;
• Pentru sisteme cu mai multe dispozitive master există posibilitate de detecţie a
coliziunilor şi arbitrare;
• Datele se transmit serial, bidirecţional, pe 8 biţi cu viteze de până la 100 Kbit/s
în modul standard ăi până la 400 kbit/s în modul Fast. Exista microcontrolere care au si blocuri
de filtrare a semnalelor parazite tranzitorii (spikes).

Principalele avantaje ale magistralei I2C sunt următoarele:


• Se pot adăuga noi blocuri sau pot fi eliminate unele dintre cele prezente fără a
afecta celelate blocuri;
• Adresarea software şi protocolul de transfer a datelor permit definirea şi
modificarea software a sistemului;
• Diagnosticarea şi depanarea sunt foarte simple, disfuncţionalităţile putând fi
urmărite şi localizate usor;
• Realizarea practică cu numai două fire reduce costurile şi permite testarea şi
punerea la punct simplă folosind un computer.
17.2. Funcţionarea magistralei I2C

Magistrala este construită din două fire, SDA şi SCL, fiecare conectat la Vcc prin câte
o rezistenţă. Dispozitivele se cuplează la acestea cu pinii omologi SDA şi SCL ca în figura 17.1,
masa lor fiind comună.

Figura 17.1. Structura magistralei I2C

Fiecare dispozitiv are ieşirile către aceşti pini de tip drenă în gol. Aceasta conduce la o
schema de tip SI-cablat, care permite ca starea unei linii să fie în 1 logic numai dacă toate
dispozitivele au ieşirea respectivă în 1 logic. Dacă cel puţin un dispozitiv are ieşirea în 0 logic,
atunci toată linia va fi în starea 0 logic, fiind trasă catre masă de dispozitivul care absoarbe
curent prin rezistenta cuplata la Vcc. După cum rezulta din descrierea protocolului I2C, o astfel
de structură permite rezolvarea simplă a arbitrarii în sistemele cu mai multe dispozitive master.
Informaţia se transferă prin linia SDA, în pachete formate din cuvinte de 8 biţi.
Sincronizarea şi semnalizările suplimentare se efectuază prin intermediul liniei SCL. Fiecare
bit util de informaţie de pe lina SDA trebuie să fie stabil în perioada în care linia SCL este în 1
logic, ca în figura 17.2, pentru a se asigura o citire corectă a acesteia.

Figura 17.2. Formele de unda si validarea informatiei pe magistrala I2C

Dacă apare o tranziţie din 1 în 0 logic pe linia SDA in timp ce SCL este în 1
logic, aceasta semnalează începutul transmisiei unui bloc, situaţie care va fi numită în
continuare condiţie de start. Dacă apare o tranziţie din 0 în 1 logic pe linia SDA în timp ce
SCL este in 1 logic, aceasta semnalează sfârşitul transmisiei unui bloc, situaţie care va fi numită
în continuare condiţie de stop. După o condiţie de start începe transmisia unui pachet, iar dupa
transmiterea întregului pachet poate să apară o condiţie de stop sau o altă condiţie de start. Acest
ultim caz, cănd o condiţie de start urmează unei condiţii de start anterioare fără ca între ele să
fi apărut o condiţie de stop, va fi denumit în continuare condiţie de start repetat. Ea apare
atunci când se doreşte transmiterea unui nou pachet fără a se ceda controlul magistarelei. De
exemplu, atunci când se citeşte dintr-o memorie prin intermediul magistralei pe două fire, se va
transmite mai intâi adresa locaţiei de la care se va face citirea şi apoi se va receptiona continutul
locaţiei respective. Dacă între timp ar intra un alt dispozitiv transmiţător pe magistrală, care se
adresează aceleiaşi memorii dar la altă locaţie, recepţia de către primul dispozitiv s-ar face
ulterior de la noua locaţie a memoriei. Acesta impune ca dispozitivul iniţial să nu cedeze
magistrala între faza de transmisie şi cea de recepţie, deci în loc de Stop după transmiterea
adresei locaţiei, va da un Start repetat pentru a continua imediat cu recepţia.
Astfel de situaţii sunt prezentate în figura 17.3, exemplificate pentru transmisii de la
master cu adresare pe 7 si 10 biti.

Figura 17.3. Exemple de succesiune a evenimentelor si pachetelor pe magistrala I2C

Transmiterea unei secvenţe de informaţie începe cu transmiterea de către master a unui


pachet de adresă şi apoi transmisia sau receptia unuia sau mai multor pachete de date.
Pachetul de adresă, transmis întotdeauna de master, este format din noua biti, ca în
figura 17.4. Primii 7 biţi formează adresa unui dispozitiv slave (SLA), bitul cel mai
semnificativ, MSB, fiind transmis primul. Bitul 8 specifică sensul (bitul R/W) - recepţie de
către master dacă este 1 logic, sau transmisie de către master dacă este 0 logic. Bitul 9 este un
bit de acceptare transmis de către slave: dacă este 0 logic (ACK) recepţia adresei este validată,
iar dacă este în 1 logic (NACK) recepţia nu a fost posibilă, din diverse motive. Vom nota în
continuare aceste informatii ca SLA+R pentru citire, respectiv SLA+W pentru scriere.

Figura 17.4. Formatul pachetului de adresa


Atunci când este trimisă o adresă cu 7 biti (MSB este transmis primul), bitul 8
semnalizeaza sensul (scriere sau citire), astfel ca adresa specificata in datasheet la unele
dispozitive trebuie deplasata cu 1 bit spre stanga (inmultita cu 2) inainte de a fi transmisa.
Dispozitivul slave care are alocată adresa respectivă va transmite după bitul 8 trimis de master
un bit de acceptare 0 logic. In caz că acest dispozitiv nu poate accepta transferul, linia SDA va
rămâne în 1 logic în al nouălea ciclu de ceas al cuvântului de adresă, iar dispozitivul master va
trebui sa transmita o condiţie de Stop sau Start repetat pentru a încerca din nou transferul.
Pachetele de date sunt formate de asemenea din 9 biţi, primii 8 formând octetul de date
iar al nouălea fiind bitul de acceptare (Acknowledge - ACK). Bitul cel mai semnificativ de date
(MSB) este transmis primul si in acest caz. Bitul al noăulea este transmis de către receptor (care
poate fi dispozitivul master sau cel slave), ca 0 logic (ACK) dacă acesta a putut recepţiona
pachetul sau ca 1 logic (NACK) dacă nu a putut recepţiona. De asemenea, un bit NACK se
transmite şi atunci când s-au terminat de transmis toţi octeţii de date sau receptorul nu mai
poate, din diverse motive, sa continue recepţia de octeţi.
Dispozitivul slave fiind cel care transmite ultimul bit, ACK, al fiecărui pachet, poate să
prelungeasca durata acestui bit atât cât îi trebuie ca să proceseze informaţia dorită. Prin această
prelungire, numita clock stretching, viteza de transmitere pe ansamblu a informaţiei este redusă
de slave pentru a se asigura compatibilitatea de viteză de procesare cu unitatea master.

17.3. Cuplarea a doua placi STM32F4 Discovery prin I2C

Se va realiza o comunicatie prin interfete I2C intre doua placi STM32F4 Discovery
dintre care una este master iar cealalta este slave. Pe fiecare placa se va putea modifica valoarea
unei variabile, unit8_t c, prin incrementare la apasarea butonului user. Placa master transmite
periodic valoarea acestei variabile catre slave, care o afiseaza pe user led si transmite catre
master propria valoare a variabilei. Masterul afiseaza de asemenea aceasta valoare pe ledurile
sale user si reia procesul. In acest fel, o modificare pe placa master este afisata de slave si
viceversa.
Deoarece placile au functii diferite, va trebui ca fiecare sa aiba un program propriu, deci
se vor realiza doua proiecte.
Folsind Cube MX, se realizeaza mai intai proiectul master. Se selecteaza tipul de placa
si se da Clear Pinout, dupa care se activeaza in RCC HSE->Crystal/Ceramic Resonator. Se
stabileste ceasul HSE de 168 MHz, dupa care se activeaza pinii PD12, PD13, PD14 si PD15 ca
GPIO_Output.
Se da click pe PB6 si se alege aici I2C1_SCL, apoi PB7 cu selectie I2C1_SDA. Se
activeaza apoi in fereastra din stanga I2C1->I2C.
Pentru modificarea variabilei prin butonul user se va lucra cu intreruperi externe, deci
se alege pentru PA0 functia GPIO_EXTI0. Tab-ul Pinout va arata acum ca in figura 17.5.
Se intra acum in tab-ul Configuration si se selecteaza I2C. Aici va exista singura
diferenta intre proiectele celor doua placi, si anume adresa proprie. Dupa cum se observa in
explicatia din partea de jos a figurii 17.6, adresa declarata aici va fi deplasata cu un bit spre
stanga in program, deci inmultita cu 2. Se poate alege aici pentru master de exemplu valoarea
1 iar pentru slave se va alege 2. Aceasta inseamna ca placa slave va avea in program adresa 4,
in timp ce placa master va avea adresa 2 (desi aceasta nu va fi necesara decat in cazul in care
intervine alt master pe magistrala).
Pentru validarea si arbitrarea prioritatilor intreruperilor, se intra in NVIC si se bifeaza
EXTI line 0, cu Sub Priority 1.
Se genereaza acum proiectul cu denumirea I2C02ma in IAR.
Se inchide priectul Cube MX si se deschide unul nou repetandu-se pasii anteriori, cu
singura deosebire ca se va alege adresa proprie 2, care se va transforma in program, cum s-a
aratat adresa 4. Se va genera acum in IAR codul proiectului slave cu denumirea I2C04sl.

Figura 17.5. Pinout-ul proiectelor I2C02ma si I2C04sl

Figura 17.6. Configuratia I2C pentru proiectul I2C02ma. Pentru I2C04sl la Primare
slave address se va scrie 2.
In fisierul main.c pentru master, acesta va transmite mai intai un octet catre slave si apoi
va astepta ca raspuns de la acesta un octet pe care il va afisa pe leduri.

17.4. Interfata I2C folosind functii HAL-API

Pentru transmisie se alege din fisierul sursa stm32f4xx_hal_i2c.c functia


HAL_I2C_Master_Transmit. Primul parametru va fi un pointer pentru handlerul I2C1, al doilea
va fi adresa declarata in slave deplasata spre stanga cu un bit, al treilea va fi adresa variabilei
transmise, iar ultimul va fi timpul de aseptare. Aceasta instructiune implementeaza intregul
protocol prezentat in figura 17.3, pe care il repeta de un numar suficient de ori pentru a primi
acknowledge.
La receptie se va folosi instructiunea similara HAL_I2C_Master_Receive folosind
aceeasi adresa slave si adresa variabilei receptionate.
In continua, variabila receptionata se afiseaza pe leduri deplasata spre stanga cu 12 biti,
avand in vedere ca ledurile user se afla pe pozitiile 12-15 ale portului GPIOD. Se poate
introduce apoi si o oarecare intarziere care simuleaza alte eventuale instructiuni din bucla
infinita a programului.
Prin urmare programul main pentru master va fi ca in figura 17.7, declarandu-se
bineinteles in zona USER CODE PV cele doua variabile utilizate.

Figura 17.7. Fisierul main pentru master

Pentru modificarea valorii variabilei transmise se va folosi, asa cum s-a specificat,
intreruperea externa pe intrarea PA0 (butonul user). Pentru aceasta se va intra in fisierul
stm32f4xx_it.c si se va insera, in zona USER CODE EXTI0_IRQn 0, secventa uzuala folosita
in capitolul 4: asteptare pentru stabilizarea contactului, o citire a butonului pentru reconfirmare
si apoi incrementarea variabilei proprii, a. Acest fisier va arata deci ca in figura 17.8, unde se
va declara bineinteles variabila a ca extern uint8_t, pentru a fi vizibila si in fisierul main.c.
Pentru placa slave, acest fisier va fi identic, iar fisierul main va avea forma din figura
17.9, in care ordinea este inversa fata de master. Astfel, mai intai se receptioneaza un octet de
la master folosind instructiunea HAL_I2C_Slave_Receive al carei prototip se copiaza din
fisierul sursa stm32f4xx_hal_i2c.c. Dupa aceea se face transmisia variabilei proprii prin
instructiunea HAL_I2C_Slave_Transmit, se afiseaza variabila receptionata deplasata spre
stanga cu 12 biti si se insereaza o intarziere. Desigur, se vor declara corespunzator si cele doua
variabile implicate.

Figura 17.8. Fisierul de intreruperi pentru master si pentru slave

Figura 17.8. Fisierul main pentru slave


Modul de lucru:
a) Se incarca in master programul I2C02ma si in slave programul I2C04sl.
b) Se face cuplarea pinului PB7 al placii master cu pinul PB7 al placii slave, a pinului PB6
cu pinul PB6 si a GND de pe cele doua placi. Daca nu sunt alimentate ambele placi, se
va face si legatura intre pinii VDD ai acestora si se alimenteaza doar una dintre ele.
c) Prin rularea simultana a programelor se va observa ca prin actionarea butonului user al
unei placi se va numara pe ledurile user al celeilalte si invers.

17.5. Interfata I2C folosind programarea prin registre.

Functiile API, desi comode si destul de sugestive, iau in consideratie foarte multe
posibilitati ale contextului hardware/software, pe care le testeaza in timp real, tinzand sa
micsoreze viteza de executie. In plus, ele sunt susceptibile de modificari, fiind in continua
evolutie, ceea ce micsoreaza portabilitatea si maintenanta programelor. De aceea, ele se
recomanda doar in portiunile initiale ale curbei de invatare, astfel ca in aplicatii operationale
profesionistii prefera programarea buclelor repetitive direct prin registre.
Ca exemplu, vom reproduce programul precedent folosind varianta cu registre in bucla
infinita. Desi aparent vor fi necesare mai multe instructiuni, se va putea observa ca la incarcarea
in flash aceasta bucla va adauga circa 120 de octeti la restul programului in varianta registre, in
timp ce varianta HAL-API va adauga peste 1300 de octeti, ceea ce indica o viteza de cel putin
10 ori mai mica. In plus, scrierea in registre ramane neschimbata si in conditiile unor frecvente
schimbari ale setului de functii API, a caror portabilitate este deci doar aparenta. Chiar si din
punct de vedere al dificultatii programarii varianta cu registre poate fi avantajoasa, tinand cont
ca documentatia pentru functii API are deja peste 2000 de pagini, destul de interdependente.
Din documentatia microcontrolerelor STM32F4xx se poate afla modul de functionare
pentru cele patru situatii care ne intereseaza: transmisie master, receptie master, receptie slave
si transmisie slave. In cele ce urmeaza, vom considera ca generatorul de cod Cube MX a facut
initializarea microcontrolerului Master si a celui Slave cu parametrii specificati in paragraful
17.3, astfel ca vom rescrie doar instructiunile din bucla infinita.
a) Transmisia master se face conform diagramei din figura 17.3.
In mod normal interfata este in starea Slave. Pentru trecerea in starea Master, trebuie
generata o conditie de Start. Aceasta se face prin scrierea in 1 a bitului START din registrul
I2C_CR1 (pag . 838 reference manual), folosind instructiunea:

I2C1->CR1 |= I2C_CR1_START;// Generare Start

Ca urmare interfata va seta prin hardware bitul SB (Start Bit) din registrul de stare
I2C_SR1 (pag. 843 reference manual). Programul va trebui sa astepte acest eveniment prin
testarea in bucla a acestui bit, cu o instructiune de tipul:

while(!(I2C1->SR1&I2C_SR1_SB));// Asteapta pana cand SB e setat

Dupa ce se asigura de aceasta setare, se poate scrie in registrul de date adresa


dispozitivului Slave, deplasata cu 1 bit spre stanga, deoarece bitul 0 va specifica sensul de
comunicatie: daca este 0 se face transmisie iar daca se pune in 1 este receptie. Daca adresa slave
declarata in Cube MX este 0x02, in DR la transmisie se va scrie 0x04:

I2C1->DR=0x04;//Scrie adresa slave in bitii 7-1 cu bitul 0 in 0


In continuare trebuie asteptat pana cand se termina transmisia octetului de adresa,
momnt semnalat de bitul ADDR din I2C_SR1

while(!(I2C1->SR1&I2C_SR1_ADDR));// Asteapta ca flagul ADDR sa fie 1

Conform documentatiei, pentru a se continua trebuie sters acest bit, printr aceasta citire
din I2C_SR1 urmate de o citire a SR2. Vom folosi in acest scop o variabila temporara, t, declara
in prealabil ca uint16_t:

t=I2C1->SR2;//Citire SR2, pentru stergerea bitului ADDR

Deoarece bitul 0 al adresei a fost 0, in continuare se face o transmisie, cea de date, prin
scrierea octetului respectiv in DR:

I2C1->DR = a; /* Transmite datele */

Daca este de transmis un singur octet de date, se poate genera acum o conditie de Stop
prrin scrierea in 1 a bitului STOP din I2C_CR1, astfel incat interfata revine in starea se Slave:

I2C1->CR1 |= I2C_CR1_STOP;//Genereaza Stop

b) Receptia master se face conform diagramei din figura 17.9.

(1)

Figura 17.9. Transmisia de la master, cu adresa de 7 biti

Ca si la transmisie, primul pas este trecerea in starea Master cu generarea unei conditii
de Start, aceasta se face prin aceeasi secventa: scriere bit START in I2C_CR1 si steptarea
confirmarii acestuia in I2C_SR1, prin instructiunile:

I2C1->CR1 |= I2C_CR1_START;/* Generate Start */


while(!(I2C1->SR1&I2C_SR1_SB));// Asteapta pana cand SB e setat

In continuare se poate transmite adresa, dar pentru a se semnala ca urmeaza o receptie,


bitul 0 al octetului de adresa trebuie sa fie 1:

I2C1->DR = 0x04|0x01;// Transmite adresa slave, cu LSB=1

Ca si in cazul transmisiei, se asteapta terminarea transmiterii prin magistrala a octetului


de adresa, moment semnalat de bitul ADDR din I2C_SR1. Se investigheaza deci in bucla acest
bit, si apoi, pentru a se continua se face stergerea sa prin aceasta citire si o citire a I2C_SR2:

while(!(I2C1->SR1&I2C_SR1_ADDR));//Asteapta ca flagul ADDR=1


t=I2C1->SR2; //Citire SR2 pentru stergerea ADDR
Dupa acest moment se considera ca slave a inceput sa raspunda transmitand datele, deci
trebuie asteptata finalizarea acestei transmisii, semnalata de bitul RXNE din I2C_CR1.

while(!(I2C1->SR1&I2C_SR1_RXNE));//Asteapta finalizare receptie


Urmeaza citirea datelor din DR, generarea conditiei STOP si utilizarea (afisarea) datelor
receptionate:

r=I2C1->DR ; // Receptioneaza datele


I2C1->CR1 |= I2C_CR1_STOP;//Genereaza Stop
GPIOD->ODR=(r<<12);// Afisare date receptionate

Bucla infinita a programului pentru placa master va fi deci ca in figura 17.10.

Figura 17.10. Programul pentru placa master, zona buclei infinite, varianta registre

c) Receptia/transmisia la slave, cu adresa de 7 biti se face conform diagramei


din figura 17.11.

S Address A Data1 A Data2 A


EV1 EV2 EV2
......
Figura 17.11. Receptia/transmisia slave, cu adresa de 7 biti

Dupa cum rezulta din documentatie, pentru ca dispozitivul master sa fie anunt ca oricare
slave adresat sa semnalizeze recunoasterea adresei (Acknowledge), la fiecare slave trebuie setat
bitul ACK din CR1. Aceasta nu se realizeaza de catre Cube MX in secventa de initializare si
trebuie scris inainte de intrarea in bucla infinita, printr-o instructiune de tipul:
I2C1->CR1 |= I2C_CR1_ACK;//Valideaza semnalizarea recunoasterii

In continuare se asteapta receptionarea adresei proprii, moment semnalizat de bitul


ADDR din SR1. Se va sta deci intr-o bucla de interogare a acestui bit, pana cand acesta
devine 1:

while (!(I2C1->SR1&I2C_SR1_ADDR));

Ceea ce se intampla in continuare depinde de bitul 0 al adresei receptionate, a carui


valoare este indicata de bitul TRA din registrul de stare SR2 (pag. 847 din reference manual).
Daca acesta este in 1, va avea loc in continuare o transmisie de la slave la master, ceea ce se
poate face prin scrierea octetului de date in DR:

if (I2C1->SR2&I2C_SR2_TRA) I2C1->DR=a;//Transmisie de la slave

In caz contrar, va avea loc o receptie a slave de la master. Se va sta deci intr-o bucla de
interogare a sfarsitului receptiei, semnalizat de bitul RXNE din SR1, dupa care datele se pot
citi din DR si utiliza:

else
{
while(!(I2C1->SR1&I2C_SR1_RXNE));
r=I2C1->DR;//Receptie slave
}
GPIOD->ODR=(r<<12);// Utilizare date

Portiunea buclei while din programul Slave va arata deci ca in figura 17.12.

Figura 17.12. Programul pentru placa slave, zona buclei infinite, varianta registre

Desigur, in ambele proiecte se va folosi acelasi fisier stm32f4xx_it ca in varianta API,


sau se poate rescrie si aceasta folosind registre, iar modul de lucru va fi acelasi.

S-ar putea să vă placă și