Sistem de monitorizare și comandă pentru [627134]
FACULTATEA DE AUTOMATICĂ ȘI CALCULATOARE
DEPARTAMENTUL CALCULATOARE
Sistem de monitorizare și comandă pentru
vehicule electrice bazat pe microcontroller
LUCRARE DE LICENȚĂ
Absolvent: [anonimizat]: S.L.Dr.Ing Radu DĂNESCU
2012
VIZAT,
DECAN, DIRECTOR DEPARTAMENT,
Prof. dr. ing. Liviu MICLEA Prof. dr. ing. Rodica
POTOLEA
Absolvent: [anonimizat]
1.Enunțul temei: Problema pe care lucrarea de față o abordează se referă la conducerea
unei mașini în mod eficient, având ca și constrângeri o destinație aflată la un anumit
număr de kilometri sau/și o constrângere legată de timpul maxim până la care se dorește
ajungerea la destinație. Se v a folosi un microcontroller pentru achiziția datelor de la
senzori și trimiterea lor la o tabletă cu sistem de operare Android.
2.Conținutul lucrării: Pagina de prezentare, Declarație, Introducere, Obiectivele
proiectului, Studiu bibliografic, Analiză și fundamentare teoretică, Proiectare de detaliu
și implementare, Testare și validare, Manual de instalare și utilizare, Concluzii,
Bibliografie, Anexe
3.Locul documentării: Universitatea Tehnică din Cluj-Napoca, Departamentul
Calculatoare
4.Consultanți: S.L. Dr. Ing. Radu Dănescu, A.L. Dr. Ștefan Breban
5.Data emiterii temei: 1 noiembrie 2011
6.Data predării: 29 Iunie 2012
Absolvent:_____________________________
Coordonator științific: _____________________________
Declarație
Subsemnatul Lucian CRIȘAN, student: [anonimizat],
Universitatea Tehnică din Cluj-Napoca, declar că ideile, analiza, proiectarea,
implementarea, rezultatele și concluziile cuprinse în această lucrare de licență constituie
efortul meu propriu, mai puțin acele elemente ce nu îmi aparțin, pe care le indic și
recunosc ca atare.
Declar de asemenea că, după știința mea, lucrarea în această formă este originală
și nu a mai fost niciodată prezentată sau depusă în alte locuri sau alte instituții decât cele
indicate în mod expres de mine.
Data: 29 Iunie 2012 Absolvent: [anonimizat]: 210842
Semnătura:______________________
Cuprins
1. Introducere – Contextul proiectului …………………………………………………………………. 1
1.1 Contextul proiectului ……………………………………………………………………………….. 1
1.2 Domeniul temei de licență ………………………………………………………………………… 1
2. Obiectivele proiectului …………………………………………………………………………………… 2
2.1 Măsurarea tensiunii pe șunt și pe divizorul de tensiune ………………………………… 2
2.1.1 Asigurarea bazei de timp pentru achiziți a de date…………………………………. 2
2.1.2 Asigurarea voltajului de referință ……………………………………………………….. 2
2.1.3 Ameliorarea variaților consumului de curent ……………………………………….. 2
2.2 Comunicarea cu tableta ……………………………………………………………………………. 3
2.2.1 Protocolul Android Open Accessory …………………………………………………… 3
2.2.2 Alimentarea tabletei ………………………………………………………………………….. 3
2.2.3 Posibilitatea înlocuiri tabletei …………………………………………………………….. 3
2.3 Asigurarea independenței în comunicare …………………………………………………….. 3
2.3.1 Trimiterea parametrilor de configurație ai microcontroller-ului ……………….4
3. Studiu bibliografic …………………………………………………………………………………………. 5
3.1 Variante alternative de conectare la microcontroller …………………………………….. 5
3.1.1 Hacking…………………………………………………………………………………………… 5
3.1.2 Bridge între USB și serial ………………………………………………………………….. 5
3.1.3 Bridge între BlueTooth și serial ………………………………………………………….. 5
3.2 Android Open Accessory Protocol( ADK) ………………………………………………….. 6
3.2.1 Android@Home ……………………………………………………………………………….. 6
3.3 Sistem audio-video al mașinii conectat la Android ………………………………………. 7
3.4 Conducerea eficientă a vehiculelor electrice ……………………………………………….. 7
3.5 Roboți autonomi ……………………………………………………………………………………… 8
4. Analiză și fundamentare teoretică ……………………………………………………………………. 9
4.1 MPLAB X……………………………………………………………………………………………… 9
4.2 Microchip Application Library ………………………………………………………………….. 9
4.3 Componente hardware ……………………………………………………………………………. 10
4.3.1 Circuitul de achiziție ……………………………………………………………………….. 10
4.3.2 H-Bridge……………………………………………………………………………………….. 10
4.3.3 Motoarele de curent continuu …………………………………………………………… 11
4.4 Perifericele microcontroller-ului ……………………………………………………………… 12
4.4.1 Timer…………………………………………………………………………………………….. 12
4.4.2 Output Compare ……………………………………………………………………………… 13
4.4.3 Input capture ………………………………………………………………………………….. 15
4.4.4 I2C……………………………………………………………………………………………….. 16
4.4.5 ADC……………………………………………………………………………………………… 17
4.4.6 RTCC……………………………………………………………………………………………. 18
4.4.7 Controller-ul de întreruperi ………………………………………………………………. 19
4.5 Protocoale folosite …………………………………………………………………………………. 20
4.5.1 I2C……………………………………………………………………………………………….. 20
4.5.2 USB………………………………………………………………………………………………. 22
4.5.2.1 O scurtă introducere …………………………………………………………………. 22
4.5.2.2 Tipuri de transfer ……………………………………………………………………… 23
4.5.2.3 Modelul de comunicație USB ……………………………………………………. 23
4.5.2.4 Descriptorii de USB …………………………………………………………………. 24
4.5.2.5 Procesul de enumerare ……………………………………………………………… 24
4.5.2.6 USB Embedded Host Stack ………………………………………………………. 25
4.5.2.6.1 Target Peripheral List( TPL) ……………………………………………… 25
4.5.2.6.2 Arhitectura stivei ……………………………………………………………… 26
4.5.2.6.3 Driver-ul client bazat pe evenimente ………………………………….. 27
4.5.3 Android Open Accessory Protocol …………………………………………………….. 27
4.6 Algoritmii propuși …………………………………………………………………………………. 28
4.6.1 Conversia din analogic în digital ………………………………………………………. 30
4.6.2 Măsurarea turației …………………………………………………………………………… 31
4.6.3 Actualizarea datelor ………………………………………………………………………… 32
4.6.4 Calibrare motoare …………………………………………………………………………… 33
4.6.5 Înlănțuirea comenzilor pe USB ……………………………………………………….. 33
5. Proiectare de detaliu și implementare …………………………………………………………….. 35
5.1 Modulul de DAC …………………………………………………………………………………… 36
5.2 Modulul de Input Capture ……………………………………………………………………… 37
5.3 Modulul de PWM ………………………………………………………………………………….. 39
5.4 Modulul de ADC ………………………………………………………………………………….. 39
5.5 Modulul de RTCC …………………………………………………………………………………. 40
5.6 Programul principal ……………………………………………………………………………….. 40
5.6.1 Structura comenziilor ………………………………………………………………………. 41
5.6.2 Trimiterea datelor către tabletă …………………………………………………………. 42
5.6.3 Funcția de trimitere a datelor către Android ……………………………………….. 43
5.6.4 Inițializarea comunicației pe USB și ADK …………………………………………. 43
5.6.5 USBTask()= keep the stack running ………………………………………………….. 44
5.6.7 Inițializarea plăcii de dezvoltare ……………………………………………………….. 44
5.7 Logica generală a aplicației …………………………………………………………………….. 44
6. Testare și validare ………………………………………………………………………………………… 46
7. Manual de instalare și utilizare …………………………………………………………………….. 49
7.1. Instalare………………………………………………………………………………………………. 49
7.1.1 Instalare hardware …………………………………………………………………………… 49
7.1.2 Instalare software ……………………………………………………………………………. 51
7.2 Manualul utilizatorului …………………………………………………………………………… 52
8. Concluzii……………………………………………………………………………………………………. 53
8.1 Rezumat……………………………………………………………………………………………….. 53
8.2 Rezultate obținute ………………………………………………………………………………….. 53
8.3 Posibile dezvoltări și îmbunătățiri ulterioare ……………………………………………… 54
9. Bibliografie………………………………………………………………………………………………… 56
10. Anexe………………………………………………………………………………………………………… 57
1. Introducere – Contextul proiectului
1.1 Contextul proiectului
Lucrarea de față este o componentă a mașinii solare dezoltată în cadrul
Universității Tehnice din Cluj-Napoca și intitulată Solis.EV . Vehiculul se dorește a fi unul
de tip autonom, a cărui propulsie este de tip electric, alimentarea făcându-se folosind
acumulatori încărcați de la panouri fotovoltaice.
Pentru preluarea informațiilor de la senzori se folosește un microcontroller, util și
pentru acționarea motoarelor. Afișarea informațiilor și intepretarea lor se face folosind o
tabletă care are un sistem de operare de tip Android, în locul panoului central de bord.
1.2 Domeniul temei de licență
Problema pe care lucrarea de față o abordează se referă la conducerea unei mașini
în mod eficient, având ca și constrângeri o destinație aflată la un anumit număr de
kilometri sau/și o constrângere legată de timpul maxim până la care se dorește ajungerea
la destinație. Ca și model de mașină se va considera una de tip solar, cu alimentare
electrică de la acumulatori.
Comparativ cu circuitele analogice, un sistem de comanda si monitorizare bazat
pe microcontroller permite o mai mare flexibilitate, și o adaptare mai bună la modificarea
parametrilor. Pentru senzori și elemente de acționare se va folosi o adresare individuală
în locul unei magistrale comune.
În cadrul mașinii solare un aspect important îl reprezintă monitorizarea bateriilor,
deoarece folosirea lor peste limita permisă poate produce distrugerea lor. Astfel se va
monitoriza consumul de curent din circuit precum și gradul lor de descărcare.
Cunoașterea acestor parametri permite o estimare a timpului rămas până la descarcarea
bateriilor, în ritmul de consum actual . Folosind curbele de descărcare se pot indica
diverse moduri de prelungire a duratei de descărcare, în funcție de consumul de curent.
Un alt aspect important îl constituie acționarea motoarelor. În urma testelor s-a
constat că circuitele de acționare răspund în mod diferit la stimuli . Din această cauză este
necesar ca atunci când se conectează un set nou de motoare să se facă o calibrare inițială a
lor. În timpul funcționării se folosește și o buclă de comandă închisă, implementată în
software, pentru a reduce diferențele între turații.
Comunicarea cu senzorii, sau cu tableta se face folosind protocoale cum ar fi I2C
respectiv USB. Datele circul ă în general într-o singură direcție de la senzori spre
microcontroller, sau de la acesta spre elementele de acționare și în ambele sensuri pe
USB.
1
2. Obiectivele proiectului
2.1 Măsurarea tensiunii pe șunt și pe divizorul de tensiune
Principalul obiectiv al acestei lucrări îl constituie monitorizarea descărcării unor
acumulatori când sunt folosiți cu un consum variabil. În acest sens s-a folosit un șunt de
0.8Ω astfel încât măsurând căderea de tensiune să se poată afla consumul momentan de
curent. Pentru măsurare a lui s-a folosit puntea Wheatstone, datorită necesității existenței
unei precizii ridicate pentru măsurarea unei rezistențe de valoare mică .
Principiul prin care se află intensitatea știind căderea de tensiune pe șunt este de a
folosi legea lui Ohm: U= I* R. V oi folosi un scurt exemplu: la o valoare de tensiune
măsurată pe șunt de 0.4V și rezistența de 0.8Ω, folosind formula enunțată anterior se
ajunge la valoarea intensității de 0.5A.
Astfel obiectivul principal presupune măsurarea în fapt a două tensiuni: una pe
acumulatori, folosind un divizor și alta pe șunt. S-a ales un șunt de o valoare mică pentru
a avea un consum minim de curent. Acest fapt face ca tensiunea măsurată să nu
depășească 0.5V . Valoarea măsurată pe divizor este de 3 ori mai mare, maximul fiind de
1.5V .
2.1.1 Asigurarea bazei de timp pentru achiziție date
Un alt aspect important în cadrul proiectului este de integrare în timp a celor două
valori măsurate. Pentru aceasta este necesar ca achiziția datelor să se facă la intervale de
timp foarte bine stabilite. Nu este atât de important să se și trimită datele cu exact aceiași
constanță. Pentru aceasta s-a ales o bază de timp de 1Hz, care este asigurată de circuitul
de RTCC. O variație în generarea acestei baze de timp va duce în timp la rezultate
eronate.
2.1.2 Asigurarea voltajului de referință
O altă parte esențială a măsurării tensiunilor o constituie menținerea referinței
pentru ADC la o valoare constantă. Pentru aceasta se vor folosi regulatorul de tensiune de
pe placă în combinație cu circuitul de DAC. Convertorul din analogic în digital va folosi
o referință de 1.5V în locul celei e 3.25V folosindu-se mai eficient cei 10 biți ai
rezultatului conversiei din analog și tot odată duce la o precizie ridicată la măsurarea mV.
2.1.3 Ameliorarea variaților consumului de curent
Datorită modului de construcție al motoarelor și a faptului că sunt comandate în
PWM apar variații ale consumului de curent, de frecvența comenzii primite. Acest lucru
se datorează răspunsului bobinelor la semnalul dreptunghiular. Pentru atenuarea efectului
s-a folosit un condensator și o mediere a valoriilor măsurate folosind software-ul.
Asigurarea unei referințe stabile pentru ADC nu este suficientă. Baza de timp a
convertorului trebui să fie de asemenea stabilă. În acest sens oscilatorul intern al modului
nu poate fi folosit, fiind nerecomandat la măsurători de precizie. Folosirea semnalului de
ceas al perifericelor(care este divizat din oscilatorul principal) este o alternativă mult mai
potrivită.
2
2.2 Comunicarea cu tableta
Datorită capacității limitate de procesare și de afișare a microcontroller-ului se
impune folosirea unei tablete pentru prelucrea informațiilor, salvarea și afișarea lor în
timp real, utilizatorului uman. Acest lucru ridică cel puțin două probleme: trimitirea
informațiilor la o frecvență ridicată și posibilitatea alimentarii tabletei în același timp.
2.2.1 Protocolul Android Open Accessory
Este un protocol dezvoltat de Google care folosește USB-ul împreună cu un
mecanism de hand-shake permițând comunicarea între un master și un slave. Trebuie avut
în vedere că (momentan) tableta joacă rolul de slave. Astfel codul necesar funcționării
comunicației cade în sarcina microcontroller-ului. Un alt dezavantaj ce trebuie avut în
vedere este că doar ultimele versiuni de Android suportă acest protocol.
2.2.2 Alimentarea tabletei
Obținerea alimentării continue a tabletei este necesară deoarece dacă se folosește
ca și panou de bord, nu se poate îndepărta în timpul funcționării pentru a se reîncărca.
Folosirea unei surse alternative de alimentare nu este o s oluție generală, deoarece de cele
mai multe ori, nu există o mufă separată în acest scop.
Avantajul USB este acela că permite transferul datelor la viteze de ordinul MB/s
permițând și alimentarea slave-ului în același timp. Tensiunea de alimentare este de 5V la
un curent maxim de 250mA, ceea ce în cazul de față este suficient.
2.2.3 Posibilitatea înlocuiri tabletei
Se dorește folosirea unui protocol care să permită o înlocuire cât mai facilă a unei
tablete cu alta de același tip sau alt model. Soluțiile de tip „hacking” pe cât sunt de
interesante și de captivante presupun de cele mai multe ori desfacerea carcasei și
cositorirea unor fire.
Folosirea soluției prezentate în această lucrare, presupune doar îndepărtarea
cablului de USB, schimbarea tabletei, încărcarea pe aceasta a programului Android, apoi
se reconectează cablul și se poate relua activitatea.
2.3 Asigurarea independenței în comunicare
Se dorește ca o schimbare fie a dispozitivului slave, fie a celui master să nu
presupună și modificarea protocolului de comunicare. În acest sens s-a ales un protocol
simplu, bazat pe trimiterea de octeți. Astfel fiecare mesaj este precedat de un cod de
identificare, urmat apoi de un număr fix de octeți care reprezintă argumentele.
Octet 1Octet 2…. Octet n+1
Cod mesaj Argument 1…. Argument n
Tabelul 2.1: Formatul general al unui mesaj
În cazul în care o valoare ocupă mai mult de un octet(ca de exemplu valoarea de
3
la ADC care este pe 10 biți) se va trimite pe numărul necesar de octeți, cu aliniere la
dreapta în cadrul grupui de bytes.
2.3.1 Trimiterea parametrilor de configurație ai microcontroller-ului
Fiecare microcontroller este influențat de hardware în ceea ce privește modul de
reprezentare al rezultatelor și numărul de biți pe câți se reprezintă acestea. Astfel este de
preferat ca înainte de a se începe trimiterea de mesaje să se trimită configurațiile plăcii de
dezvoltare. Trebuie avut în vedere că și frecvența plăcii poate să fie diferită, precum și
frecvența de lucru pentru periferice.
4
3. Studiu bibliografic
Materialele folosite pentru realizarea acestei lucrări cuprind: data sheet, reference
manual, aplication note, cărți, lucrări de laborator, precum și numeroase pagini de pe
internet. Acestea sunt în marea majoritate în limba engleză cu câteva excepții în limba
română. Datorită noutății tehnologiei ADK, care a fost lansată în primăvara anului 2011,
nu se poate vorbi despre o bibliografie de mari dimensiuni.
3.1 Variante alternative de conectare la microcontroller
3.1.1 Hacking
Reprezintă varianta cea mai riscantă și în același timp și cea mai interesantă de
comunicare. Așa cum se arată în articolul [1] sunt două abordări posibile: prima
presupune folosirea unui bridge între USB și portul serial, iar a doua și cea mai dificilă
presupune găsirea și conectarea la portul serial direct de pe magistrala telefonului.
Principalul avantaj al ultimei metode îl constituie costul. Dezavantajul major, îl
constituie faptul că fiecare dispozitiv cu Andoid, are pinii de la portul serial situați în altă
locație. Accesul la schemele hardware este dificil, și de cele mai multe ori acestea nu sunt
făcute publice deoarece clientul nu are nevoi e în mod normal de aceste informații.
3.1.2 Bridge între USB și serial
Folosirea unui bridge între serial și USB este prezentat în resursa [2], în acest
articol fiind prezentat modul concret de construire al acestui bridge. Avantajul abordării
curente și a celei precedente este reprezentat de faptul că pe partea de microcontroller se
folosește comunicarea prin protocolul UART. Acesta este suportat nativ de către toate
dispozitivele și de asemenea este un protocol ușor de folosit.
Un bridge pentru microcontroller este explicat și în resursa [3], care spre
deosebire de variantele anterioare bazate pe serial necesită și folosirea unei librării mai
complexe. De asemenea suportul pentru kit-ul de dezvoltare este oferit doar pentru
Arduino, ceea ce limitează complexitatea programului ce poate fi încărcat pe placă.
3.1.3 Bridge între BlueTooth și serial
Asupra variantelor wireless m-am oprit cu documentarea doar pe varianta de
Bluetooth, datorită prețului mai redus al shield-ului, și a protocolului care este mai
simplu. Pentru aceasta se poate folosi [4], care nu este altceva decât o librărie tot pentru
Arduino. Aceasta acceptă un număr mare de bridge-uri între UART și BlueTooth, atât
timp cât se setează baud rate-ul pentru comunicația serială la o valoare prestabilită.
În tutorialul [5] se explică pe larg modul general de conectare între device-uri
folosind un bridge de la BlueTooth la serial. Se oferă de asemenea atât schemele de
montaj construite în jurul unui Arduino și a unui bridge de la SparkFun, precum și codul
pentru Android și pașii necesari dezvoltării aplicației pe microcontroller.
5
3.2 Android Open Accessory Protocol( ADK)
Fiecare telefon sau tabletă poate folosi USB-ul împreună cu protocolul ADB(care
în mod normal este folosit pentru depanare) pentru trimiterea datelor, așa cum este
prezentat în blog-ul [6]. O privire critică asupra ADK se găsește pe blog-ul lui Romfron
[7], unde se combate vehement ADK-ul, fiind considerat ca lipsit de inovație față de
ADB, deoarece limitează folosirea telefonului/tabletei precum și modele acceptate. ADK
are totuși avantajul că permite folosirea( momentan în dezvoltare) în paralel a funcție de
depanare fie printr-un USB secundar sau prin wireless.
Singurele documentații oficiale referitoare la ADK sunt cele oferite de Google,
unde există o scurtă secțiune dedicată [8], în cadrul ghidului pentru Android. Acestei
documentații i se alătură documentația oferită de către Microchip [9]. Ambele
documentații însă nu sunt destul de detaliate, oferind informații foarte puține.
ADK folosește momentan telefonul pe post de slave și placa de dezvolatare pe
post de master, USB-ul fiind un protocol asimetric. Decizia aceasta a fost luată, așa cum
se arată în blog-ul oficial [10], deoarece momentan majoritatea telefoanelor nu au modul
master și nici OTG, ci numai slave. De asemenea deoarece majoritatea suportă ADB, au
cel puțin modul de slave, pentru a se conecta la PC și a realiza depanarea print-un cablu
USB. Din această cauză, așa cum se remarcă și în blog, este o situație inversată, „cu susul
în jos”: telefonul deși are putere de calcul mai mare acționează ca și slave, având de
asemenea nevoie și de alimentare.
Pe viitor, așa cum se arată în blog-ul amintit mai sus, [10], se va încerca să nu se
mai ofere alimentare telefonului și să se suporte distribuirea de conținut audio peste
USB( acest lucru nu este fezabil în varianta de față, deoarece nu se suportă transferul
izocron). În cadrul Google I/O din 2011 [11] s-a lansat ideea chiar ca telefonul să fie host,
ceea ce ar ușura foarte mult munca dezvoltatorilor de embedded.
3.2.1 Android@Home
Conceptul a fost prezentat în cadrul Google I/O 2011 [12], ca fiind o extindere
pentru Android Open Accessory Protocol pentru automatizarea unei întregi locuințe.
Dispozitivul Android va trebui să folosească Android@Home Framework pentru a putea
comunica cu senzorii folosind Wi-Fi.
Un protocol wireless, care folosește unde radio de 900MHz a fost dezvoltat
pentru conectarea elementelor ce nu pot îngloba capacitățiile de Wi-Fi. În acest sens a
fost prezentat un bec bazat pe tehnologia LED și produs împreună cu Light Science, care
folosește acest protocol proprietar.
Oferta de produse compatibile cu Android@Home este momentan redusă. Totuși
posibilitatea conectării la internet oferă accesul la Google Cloud și toate serviciile
asociate lui. Un alt exemplu prezentat a fost înglobarea serviciului Music Beta, care
permite sincronizarea diverselor dispozitive care au Android, prin folosirea unui cont
Google. Toată muzica se va afla salvată în cloud, astfel încât nu va ocupa spațiu pe disc.
6
3.3 Sistem audio-video al mașinii conectat la Android
În resursa [13] se exemplifică o implementare a protocolului ADK oferită de
Harman, prin sistemul lor de audio-video instalat pe mașină. Telefonul sau tableta pot fi
comandate de la comenziile de pe ecranul tactil, prin comandă vocală, butoanele de pe
volan sau direct din panoul central sistemului audio- video.
Se poate astfel să se trimită un sms sau e-mail folosind comanda vocală, sau să se
redea conținutul unui mesaj primit în boxele mașinii. Posibilitatea de a răspunde la
telefon, sau de a apela un număr se poate face tot vocal, sau din butoanele de pe volan.
Rețele sociale, cum ar fi Facebook sau Twitter au fost și ele tratate de Harman. Conținutul
text al oricărui post primit poate fi redat de către sistemul audio al mașinii.
Aplicațiile de navigație care se găsesc pe telefoane și tablete pot fi folosite pentru
găsirea celei mai apropiate stați i de alimentare cu carburanți, sau pentru a descoperi
restaurantele din zonă. De asemenea pot fi redate și filme, pe panoul sistemului audio-
video sau pe ecrane care pot fi montate în tetiere.
Totuși sitemul audio-video oferit de firma Harman nu interfațează cu partea de
control al parametrilor mașinii(cu excepția volumului în difuzoare, și a conținutului unor
ecrane), fiind mai mult o extindere a ecranului tactil al tabletei. Aici dispozitivul Android
are rolul de gestionar pentru muzică, filme, apleuri și mesaje primite, sau postăriile de pe
rețelele sociale.
3.4 Conducerea eficientă a vehiculelor electrice
În lucrarea [14] se tratează modul în care pot fi satisfăcute cerințele clientului
ținând cont de resursele mașinii. Abordarea adoptată în acest articol este ierarhică, și
cuprinde 4 module.
Fiecare modul constituie o intrare pentru cel de nivel superior, astfel încât se
transmit o colecție de soluții stagiului următor, acesta combinând fiecare soluție primită
cu cea proprie, pentru a obține tot o colecție de soluții. Un optim local unui nivel poate să
nu facă parte și din soluția globală, de aceea se trimit un set de soluții.
Soluția unui singur drum optimal, din punct de vedere al clientului, implică
satisfacerea constrângerilor de timp și de consum energetic. La acestea se pot adăuga
costuri de reîncărcare, timpi morți în care se face alimentarea, numărul de astfel de opriri
și alte criterii.
Primul nivel tratat este cel al componentelor mașinii, cu rol asupra confortului
clientului sau asupra posibilității efective de locomoție. La acest nivel strategiile sunt
foarte simple, și sunt constituite în mare din compararea unor threshold-uri.
La nivelul următor, cel al călătoriei se iau în considerare punctele de alimentare și
destinațiile, și modul în care se poate ajunge cât mai eficient la ele. Aici pentru elaborarea
strategiilor se folosesc grafuri.
Nivelul călătoriei este format dintr-o serie de rute, și puncte de staționare/parcare
reprezentate de destinații intermediare sau finale. Pentru conectarea rutelor se folosește
programarea dinamică, sau găsirea unui optim local.
Ultimul nivel este cel de asigurare a mobilității, în care un client nu este legat în
mod direct de un anumit mijloc de transport; poate folosi și mijloace de transport în
comun, sau pe anumite porțiuni poate să călătorească alături de un cunoscut. În acest scop
7
se folosesc algoritmi din teoria jocului.
Concluzia acestei lucrări este că nu doar costul unei călătorii poate fi îmbunătățit,
dar chiar și timpul total poate fi mult îmbunătățit în paralel. Pentru susținerea acestei
afirmații s-a dezvoltat un cadru matematic prin care se studiază complexitatea
algoritmilor, și spațiu computațional al soluțiilor. Este necesar să se renunțe la anumite
variante în stagii cât mai incipiente, deoarece dacă nu spațiul soluțiilor poate ajunge la
ordinul miilor, iar analiza lor va consuma foarte mult timp. De asemenea alegerea unei
medii, în locul unui optim global poate să satisfacă de cele mai multe ori cerințele
clientului.
3.5 Roboți autonomi
În cartea [15] se prezintă tipurile de roboți autonomi, mobilitatea lor, senzori și
percepția mediului, localizarea și planificarea călătoriei.
Un robot care are 3 roți are avantajul stabilității și de asemenea nu are nevoie de
suspensie, deoarece un plan este determinat de 3 puncte și astfel fiecare roată are contact
permanent cu solul. O altă condiție necesară este ca centrul de greutate să se găsească în
interiorul triunghiului format de cele 3 roți.
Pentru control direcției, puntea din față se acționează independent, iar în spate se
poate folosi o roată sferică sau una pivotantă. Schimbarea direcției se face prin rotirea în
jurul centrului de greutate, blocând o roată și cealaltă fiind acționată în sensul dorit. În
locul blocării se poate folosi acționarea în sens invers, ceea ce duce ca robotul să rotească
la punct fix. Dificultatea controlului constă în faptul că turațiile celor două roți motoare
trebuie să fie identice pentru o deplasare în linie dreaptă. Folosirea unei roți sferice sau a
uneia pivotante în spate nu influențează în mod semnificativ manevrabilitatea robotului.
Pentru atingerea destinației se poate folosi o planificare fie în buclă deschisă, fie
în buclă închisă. Prima abordare presupune divizarea traseului în mai multe segmente de
tipul liniilor sau arcelor de cerc. Această tehnică nu ia în calcul modificăriile neprevăzute
în configurația mediului, și de asemenea viteza de deplasare are fluctuații mari datorită
neregularitățiilor ce apar în compunerea drumului ales.
Folosirea unei bucle închise, prin analiza feed back-ului primit de la diverși
senzori, constituie o abordare mai realistă și ușor adaptabilă la schimbăriile de mediu.
Tehnica de bază constă în alegerea unor puncte intermediare, care urmează să fie vizitate.
8
4. Analiză și fundamentare teoretică
4.1 MPLAB X
Mediul de dezvoltare MPLAB X v.1.10, asigură dezvoltarea facilă a aplicațiilor
pentru microcontroller-ele Microchip. Are funcții de programare a acestora folosind un
cablu USB sau un programator, de citire a conținutului memoriei și de execuție a unui
program pas-cu-pas.
Testarea aplicațiilor, în lipsa unei plăci de dezvoltare, se poate face folosind
simulatorul, care poate emula orice microcontroller suportat de mediul de dezvoltare. Are
dezavantajul de a a fișa registrele în mod listă, ordonate după adresă sau după nume, în
locul unei grupări după perifericul de care aparțin.
Dezvoltarea programelor se poate face, folosind fie limbajul de asamblare fie C.
Prin folosirea directive pentru compilare, se permite introducerea de cod scris în alt
limbaj decât cel ales de utilizator la crearea proiectului. Se oferă suport pentru limbaj ul
de asamblare prin ASM iar prin C18/C30/C32 respectiv HI-TECH PICC/PICC-18 se
oferă suport pentru C. Varietatea mare de compilatoare se explică prin multitudinea de
arhitecturi suportate: de 8, 24 și 32 de biți precum și procesoare digitale de semnal.
Pe lângă compilatoarele necesare, pentru rularea IDE-ului este nevoie și de
instalarea unui JRE. În cazul în care este instalată și versiunea 8 de MPLAB este nevoie
de un driver switch pentru conectarea la placa de dezvoltare. Switch-ul are rolul de a
încărca driver-ul corespunzător folosiri USB ca și programator pentru microcontroller;
atât versiunea 8 cât și versiunea X au driver propriu.
Mediul de programare fiind dezvoltat plecând de la NetBeans oferă un grad ridicat
de modularitate printr-un set de plug-in-uri. Unul foarte util este DMCI care permite
vizualizarea diverselor tipuri de variabile, afișarea unui vector pe grafic sau modificarea
unor valori folosind sliders sau butoane de tip on/off pentru booleans.
4.2 Microchip Application Library
Cuprinde o serie de librării scrise în C pentru folosirea perifericelor de pe placă, a
căror programare este mai dificilă. Se oferă acces la stivele de TCP/IP, USB, Wireless,
ecrane tactile, SD card și sisteme de fișiere. Aceste librări pot fi folosite individual sau
pot fi combinate în cazul în care aplicația este mai complicată.
Librăria Android Accessory Framework, folosește și librăria de USB pentru a
oferi un API facil dezvoltării aplicațiilor de tip ADK. Generarea fișierelor de configurație
pentru driver-ul de USB se poate face folosind programul USB Config, prin configurarea
pas-cu-pas a fiecărei opțiuni asociate comunicației seriale .
Librăriile necesare pot fi incluse în proiectul curent în două moduri: prin
adăugarea locației la include path ca și cale absolută, sau prin copierea lor în proiectul
curent și referirea folosind calea relativă. Metoda a doua asigură nu doar portabilitate, dar
împiedică modificarea voluntară sau nu a fișierelor originale din librărie. Compilatorul
folosește include path pentru a căuta dependențele proiectului, cum ar fi fișierele din
librăriile standard.
9
4.3 Componente hardware
4.3.1 Circuitul de achiziție
Circuitul de achiziție este prezentat în figura 4.1, în continuare fiind prezentate
componentele constructive. Schema a fost realizată folosind: falstad.com/circuit/, un
applet în Java, de tip drag-and-drop.
Acumulatorii sunt de tip reîncărcabil, cu o tensiune la borne de 1.2V și o
capacitate de 2450mAh. În locul acumulatorilor se pot folosi baterii obișnuite de 1.5V.
Cei 4 acumulatori sunt legați în serie, oferind astfel 4.8V , capacitatea rămânând la
valoarea de 2450mAh.
Circuitul de măsură al voltajului la borne este format din 4 rezistențe de 1K Ω în
serie, cu scopul de a forma un divizor de tensiune. Rezistențele pot fi de orice valoare, cât
timp sunt de valori egale. Dacă sunt de valori diferite tensiunea pe o rezistență va trebui
calculată folosind proporțiile aritmetice. În cazul folosiri a 4 rezistențe, căderea de
tensiune pe o rezistență este de 4 ori mai mică decât în tot circuitul serie. În cazul de față
s-a ales valoarea de 1KΩ pentru a se reduce consumul de curent în circuit.
Condensatorul este de tip electrolitic și are o capacitate de 4.7mF, iar cele două
motoare sunt reprezentate pe schemă ca și două rezistențe de 100 Ω, aflate în partea
dreaptă. Asupra rolului condensatorului voi reveni într-un capitol viitor, iar motoarele vor
fi prezentate în continuare.
4.3.2 H-Bridge
Circuitul are rolul de a schimba direcția motorului, fără a fi necesară modificarea
montajului. Pentru aceasta se folosesc patru tranzistori grupați în două perechi: Q1/Q3 și
10
Figura 4.1: Circuitul de achiziție
Q2/Q4, comandați prin semnalele A, respectiv B.
Dacă A are valoarea '1', iar B are valoarea '0', motorul se va roti în sensul acelor
de ceasornic, iar dacă A este '0' și B este '1', se va roti în sensul opus acelor de ceasornic.
Pentru a se opri motorul, ambele semnale de control trebuie să fie la valoarea '0'. Dacă
ambele semnale sunt '1', comportament ul va fi nedefinit.
În figura 4.2 este prezentat un Pmod ce îndeplinește funcția descrisă mai sus, și
schema generală a circuitului de H bridge, folosită pentru exemplificarea funcționării.
Așa cum se poate observa circuitul de alimentare este separat de comandă, astfel încât
motoarele pot fi acționate la tensiuni și curenți mari.
4.3.3 Motoarele de curent continuu
Motoarele sunt de tip Igarashi IG22, cu o cutie de viteze încorporată, ce are un
raport de 1:19, funcționând ca un reductor cu raport fix. Modelul de față se poate
alimenta la tensiuni de maxim 6V , curent continuu, iar consumul de curent are valoarea
maximă de 125mA. În figura 4.3 este prezentată o privire de ansamblu asupra motorului,
o vedere din spate a encoder-ului și ieșirea în defazaj a celor doi senzori Hall.
În partea din spate a fiecărui motor se află un encoder, bazat pe efect Hall, care
măsoară gradul de rotație . Scopul acestui circuit este să determine turația motorului,
folosind doi senzori magnetici dispuși la o anumită distanță unul față de celălalt.
Magnetul folosit este unul obișnuit, de tip Nord-Sud. Cei doi senzori oferă la ieșire
semnalele A și B, defazate cu 900, și cu un factor de umplere de 50%, așa cum se vede în
11
Figura 4.2: PMod HB5(în partea stângă) și schema generală a H
bridge(în dreapta)
partea dreaptă jos.
Trebuie avut în vedere că senzorii măsoară direct turația rotorului, astfel valoarea
obținută este de 19 ori mai mare decât ieșirea din reductor, care acționează roțile.
Senzorul Hall poate fi folosit chiar dacă motorul nu este alimentat. Valoarea gradului de
rotație este una relativă la momentul începerii măsurării. Folosirea unei codificări Gray
asociate acestui encoder ar fi permis cunoașterea în orice moment al gradului de rotație.
Figura 4.3: Motorul de curent continuu și encoder-ul
4.4 Perifericele microcontroller-ului
În sectiuniile următoare sunt prezentate perifericele folosite în cadrul proiectului.
Termenul de PB se referă la semnalul de ceas al perifericelor, iar SOSC la oscilatorul
secundar. Ceasul general al sistemului, SYSCLK , este divizat pentru a se obține PB.
Notația 'x' din denumirea registrelor sau pinilor se referă la registrul/pinul asociat unei
anumite instanțe a perifericului, ca de exemplu dacă x este 1 în TxCK este pinul
semnalului de ceas extern pentru Timer 1.
4.4.1 Timer
Am folosit ca și principală sursă de informare Reference Manual [16]. Acest
model de chip are un numar de 5 timere de 16 biți(T1- T5). Pentru a se obține un timer de
32 de biți se pot asocia T2 cu T3 sau T4 cu T5.
12
Se folosește termenul de Type A pentru cele care pot fi folosite doar ca și 16 biți,
respectiv Type B pentru cele care pot fi folosite și ca 32 biți. În cazul B se folosesc
notațiile de TimerX și TimerY, primul fiind considerat master(ex emplu Timer 2) iar al
doilea fiind considerat slave(exemplu T3). Masterul stabilește perioada și setăriile de
bază, registrele lui Y sunt folosite doar pentru setarea întreruperilor.
Pot fi identificate mai multe moduri de operare:
•Modul sincron care este asemănător pentru tipul A și B presupune folosirea
Peripheral Bus Clock (PB) și eventual a unui prescaler.
•Modul sincron cu ceas extern presupune folosirea unui pin(TxCK) unde este
furnizat un ceas extern. Acesta este sincronizat cu ceasul sistemului.
•Modul gated presupune folosirea unui semnal extern care validează numărarea.
•Modul asincron. Acesta este singurul mod care apare doar la tipul A, spre
deosebire de modurile de mai sus care apar la ambele tipuri. Se poate folosi ca și
sursa T1CK sau oscilatorul secundar (SOSC). Se oferă mecanisme de sincronizare
între PB clock și ceasul extern.
Fiecare timer are propria întrerupere, a cărei prioritate/subprioritate poate fi setată
individual. Poate avea surse multiple cum ar fi atingerea valorii din PR sau, dacă se
folosește în modul gated, de un falling edge pe gate.
4.4.2 Output Compare
Principala sursă de informare privind PWM a constituit-o Reference Manual[17].
Poate fi folosit doar cu Timer 2 sau cu Timer 3. Se pot folosi un număr de maxim
5 canale. În privința registrelor necesare, numărul acestora este mic:
•OCxCON, folosit pentru configurarea modulului
13
Figura 4.4: Timer 1
•OCxR și OCxRS folosite pentru compararea cu valori de 16 biți sau de 32 de biți
dacă se folosește și registrul secundar( OCxRS).
Din punctul de vedere al modurilor de funcționare se pot distinge:
•modul Single compare, a cărui ieșire poate fi folosit ă pentru a avea high, low sau
toggle, la potrivirea valorii din timer cu cea din output compare(OcxR) .
•Modul Dual compare, folosește ambele registre pentru comparare. De exmplu la
atingerea valorii din primul registru va trece în starea low, apoi la atingerea valorii
din registrul al doilea va trece în high.
•Modul PWM. Perioada se stabilește prin setarea timer-ului, iar factorul de umplere
prin setarea OcxR. OcxRS are aici rol de buffer, și se transferă în OcxR la
overflow-ul timer-ului.
PWM poate fi folosit cu scopul de convertor digital-analogic. Modificând factorul
de umplere se modifică și ieșirea. La frecvențe mari, de ordinul kHz, un factor de
umplere de x%, va face ca ieșirea să fie percepută ca fiind x% din valoarea maximă. În
exemplul din figura 4.6 la un factor de umplere de 50%, ieșirea este măsurată ca fiind 5V,
jumătate din cei 10V, cât reprezintă valoarea high a semnalului dreptunghiular.
Fiecare canal are asociat o întrerupere, astfel se pot seta individual prioritățiile și
subprioritățiile.
14
Figura 4.6: PWM ca și regulator de tensiune
Figura 4.5: Funcționarea OC
4.4.3 Input capture
Este folosit pentru a se efectua măsurători ale frecvenței sau ale duratei unui
impuls, așa cum se arată în Reference Manual [18].
Modulul are două registre: unul pentru configurare ICxCON și unul de tip buffer
ICxBuf. Prin intermediul acestui registru buffer poate fi citită stiva(FIFO). Dacă stiva
conține 4 valori atunci sunt necesare 4 citiri din ICxBUF. Stiva are un număr maxim de 4
cuvinte. Se poate folosi ca și bază de timp Timer 2, Timer 3 sau ambele pentru o bază de
timp de 32 de biți.
Se poate folosi în următoarele moduri:
•simple capture, mod în care se alege modul de rising/falling, iar întreruperea va fi
generată după numărul de evenimente specificat în ICxCON.
•prescaled capture, mod în care se va genera un eveniment tot la al 4-lea sau 16-
lea eveniment.
•Modul edge detect va detecta atât tranzițiile high-low cât și low-high . Modul
interrupt-only se folosește pentru modul sleep și idle.
Modulul are două întreruperi: ICxIF, care apare după numărul dorit de
evenimente și ICxIE care apare în urma unei erori cum ar fi faptul ca nu se citește din
FIFO și apare over run.
15
Figura 4.7: Modulul de input capture
Figura 4.8: Modul general de folosire al IC și Timer
În figura 4.8, este prezentat conceptual funcționarea modului. Modul de folosire
este de single capture, și evenimentele de interes sunt cele de tipul rising edge, marcate
cu verde pe figură. Registrul se va incrementa cu frecvența ceasului folosit ca și bază
pentru măsurătoare. Dacă apare un eveniment se generează o întrerupere, se salvează
valoarea, apoi registrul se va reinițializa.
4.4.4 I2C
Cele mai importante caracteristici ale modulul ui sunt următoarele:
•logică separată pentru partea de slave/master
•posibilitatea de a lucra atât cu adrese 7 bit cât și de 10 bit
•posibilitatea arbitrări în sisteme de tip multimaster
•poate lucra la frecvențe de 100KHz, 400KHz sau 1MHz.
Registrele care controlează modulul de I2C sunt prezentate în [19] și sunt
următoarele:
•I2CxCON, este registrul care va controla funcționarea I2C
•I2CxSTAT, conține o serie de flags
•I2CxADD, este registrul ce conține adresa în cazul în care funcționează ca și slave
•I2CxMSK, este un registru de mască pentru adresă, astfel încât să poată să
răspundă la mai multe adrese
•I2CxBRG, folosit pentru baud rate
•I2CxTRN, aici se vor pune datele care se doresc trimise
•I2CxRCV , aici se vor depunde datele primite
Întreruperile asociate cu acest modul sunt diferențiate pentru modul de folosire
master, slave sau sunt comune(bus collision), sursele în fiecare caz fiind multiple. Pentru
recepționarea datelor se folosește principiul de double buffer. Prima dată datele sunt
deplasate folosind I2CxRSR iar apoi odată terminată operația(după primirea a 8 biți) se
mută datele în I2CxRCV. Dacă vechea valoare nu a fost citită din acest registru se va
semnaliza prin flagul I2xCOV, iar valoarea nouă se va pierde.
Funcția de reload automat pentru Baud Rate Generator are sens în contextul
arbitrării, pentru a determina o nouă încercare de obținere a magistralei. BRG se va
decrementa cât timp SCL este low, iar apoi va încerca să aducă SCL în starea high.
Pierderea arbitrării poate să apară la Start, Repeated Start, trimitere de date, adresă, sau la
Stop; și se va semnaliza prin setarea flag-ului de BCL.
In continuare voi prezenta unele particularități pentru modulul de I2C în modul
16
master. La inițializarea unui Start trebuie să se aștepte terminarea lui, altfel se va
semnaliza o coliziune pe bus. Pentru a se verifica dacă bus-ul este idle(SCL este high) se
mai poate verifica flagul de Stop. Pentru o abordare bazată pe întreruperi este necesară
construirea unui state machine deoarece, întreruperea pentru modul master poate fi
activată de mai multe surse.
4.4.5 ADC
Registrele folosite pentru programarea modului sunt urm ătoarele:
•AD1CON1, AD1CON2, AD1CON3, folosite pentru configurare
•AD1CHS, folosit la selectarea pinilor ce vor fi conectați la SHA, prin cele două
multiplexoare
•AD1PCFG, este folosit pentru configurarea pinilor ca și digital sau analogic
•AD1CSSL, este folosit pentru alegerea piniilor ce vor fi folosiți la scanare
•ADC1BUF0- ADC1BUFF, constituie stiva unde vor fi depuse rezultatele
Pentru a se folosi un pin ca și intrare analogică mai întâi se va marca ca și pin de
intrare folosind registrul TRIS, apoi se va seta ca și analogic folosind AD1PCFG.
Rezultatul conversie este de 10 biți, dar poate fi citit ca și un număr pe 16 sau pe 32 de
biți atât în format întreg cât și în format fracțional, cu semn sau fără. Numerele negative
nu se obțin în urma citiri unei valori negative, ci datorită faptului că sunt în prima
jumătate a intervalului, în mijlocul intervalului fiind valoarea 0.
17
Figura 4.9: Modulu ADC
Setarea modului de conversie poate fi realizată în 4 feluri și anume: manual prin
setarea bitului de sample; prin folosirea Timer2 și/sau Timer3; prin tr-o întrerupere
externă; sau în mod auto. Modul auto va seta la finalul conversie bitul de sample, ceea ce
va duce la începerea unei noi eșantionări.
Se poate specifica numărul de conversi i după care se va genera o întrerupere, acest
număr fiind de maxim 16 sau 8 în funcție de Buffer Fill Mode. Rezultatele vor fi salvate
în stivă începând cu BUF0 și continuând până la maxim BUFF. Prin folosirea dual buffer
stiva este împărțită în două zone: primele 8 BUF0- BUF7 și ultimele BUF8- BUFF. Astfel
mai întâi se scrie în prima zona, se generează întrerupere, apoi se trece la a doua zonă, și
se generează întrerupere, apoi rezultatele conversiei vor fi depuse din nou în prima zonă.
Generarea întreruperii se face după scrierea numărului indicat de valori, valoarea maximă
în acest mod fiind de 8. Mai întâi se va scrie în buffer-ul 0, apoi în 1,2 în prima zonă;
respectiv 8, 9, 10 pentru zona a doua. Astfel se permite o operație de citire dintr-o zonă,
în timp ce se scriu rezultatele în altă zonă , fără riscul de suprascriere a valorilor.
Perioada convertor ului se numește TAD și este dată fie de un oscilator intern, f ie de
semnalul de ceas al perifericelor. Convertorul este unul de tipul sample and hold, fiind
prezentat în figura 4.9 ca o zonă punctată cu denumirea S/H. Funcționarea este prezentată
în figura 4.10, valoarea analogică având culoarea gri, punctele de eșantionare fiind
prezentate cu linii punctate, și valoarea digitizată având culoarea roșie pe grafic. Prima
dată se face o încărcare a condenstatorului, urmată de decuplarea întrerupătorului. După
perioada de eșantionare, urmează perioada de hold în care se face conversia valorii din
condensator. Perioada de sample poate fi modificată de la 1 la 127 T AD , dar perioada de
conversie este fixată la 12 T AD.
În sectiunia 17.5 din [20] se prezintă modul de calcul al T AD și pentru a se obține o
frecventa de eșantionare de 1000ksps.
4.4.6 RTCC
RTCC este un circuit ce asigură păstrarea datei în format BCD, facilitând astfel
interpretarea cât mai rapidă a datei fără a mai fi nevoie de prelucrări ulterioare. Se oferă
posibilitatea de întrerupere și toggle de output la intervale de o secundă, un minut, o oră
și alte intervale mai mari. În mod intern perioada de lucru este de o jumătate de secundă,
dar pentru citire rezoluția este la o secundă.
În ceea ce privește alarma ea poate fi setată într-un interval de la o jumătate de
secundă până la un an ș i poate fi repetată de un număr de maxim 255 de ori. Dacă se
folosește opțiunea de Chime se va repeta la nesfârșit, detectând atingerea numărului de 0
18
Figura 4.10: Funcționarea S/H
repetări contorul se va reinițializa.
Accesul la registrele pentru timp și pentru alarmă se face doar dacă bitul
RTCSYNC, respectiv ALRMSYNC este zero. Acest interval de timp este de 32 de
perioade de ceas, o citire sau o scriere în afara acestui interval producând efecte
nedefinite.
În figura 4.11 se prezintă modul în care se incrementează registrul secundelor.
După 32768 de perioade de ceas are loc incrementarea registrului.
În cazul în care se observă că referința nu are exact 32 768Hz se poate ajusta cu
formula: (32 768- Frecvență măsurată )*60= Eroare. Acestă valoare poate avea atât valori
pozitive cât și negative. Modulul va face corecțiile necesare o dată la 1 minut, prin
modificarea secundelor.
Pentru alarmă se va seta data și timpul primei declanșări, apoi se alege când(după
o secundă, o zi, un an) se va declanșa și de câte ori se va repeta alarma la intervalul
specificat. Data primei alarme este comparată cu o mască. De exemplu, dacă se alege un
interval de un minut nu se vor lua în considerare secundele pentru a se stabili perioada de
declanșare. La incrementarea registrului minutelor vom avea o alarmă. În cazul extrem,
de repetare la un interval de o secundă, setarea datei primei alarme este inutilă.
Configurația măști în acest caz face ca prima alarmă să apară după o secundă de la
setarea bitului de enable. Pentru înțelegerea acestui modul recomand urmărirea [21]și
[22].
4.4.7 Controller-ul de î ntreruperi
Acest microcontroller are un număr de 46 vectori de întrerupere și un număr de 58
de IRQ( surse de întrerupere, inclusiv cele 5 externe). Atât în modul single cât și în modul
multi- vector se pot folosi registre de tip shadow(secondary register set).
•Modul single vector : este modul default, în care va intra după reset. Se va folosi
aceiași adresă indiferent de vectorul care a generat-o.
•Modul multi vector : fiecare vector are asociat o adresă unică calculată folosind o
adresă de bază și un deplasament.
Întreruperile pot avea prioritate între 1(minim) și 7(maxim). Prioritatea 0 se
folosește pentru a inhiba întreruperea. Controller-ul de întreruperi va servi o întrerupere
de ordin superior, chiar dacă există una de ordin inferior în execuție . Aceiași ordonare și
regulă de servire apare și în cazul folosirii subprioritățiilor ce au valori între 0(minim) și
3(maxim). Pe lângă aceste reguli pentru ordonare se folosește și ordinea naturală, unde 0
este maxim și vectorul 45 este minim. La intrarea în ISR (Interrupt service routine) este
obligatoriu să se marcheze sursa care a generat întreruperea pentru a evita întreruperi de
tip recursiv.
19
Figura 4.11: Incrementarea secundelor
Registrele shadow(al doilea set de registre GPR) aduc o creștere a
performanței deoarece nu mai este nevoie să se facă salvări de context pe stivă. Acestea
sunt valabile pentru single vector, iar în multi vector sunt valabile doar pentru
întreruperile de nivel mai ridicat.
Tehnica TEMPORAL PROXIMITY INTERRUPT COALESCING, presupune
folosirea unei cozi unde se pun întreruperile, de grad egal sau mai mic cu un prag dat, și
care vor fi apoi servite înlănțuit. Se folosește un timer, care este declanșat de apariția
primei întreruperi ce respectă condiția de grad. Odată ajuns la zero, se vor procesa
înlănțuit toate acele întreruperi care au respectat condiția de a intra în coada de așteptare.
Pentru aceasta este nevoie să se încarce pragul dorit în TPC și valoarea de unde se va
decrementa în IPTMR( valoare pe 32 biți). Acest timer folosește direct SYSCLK, fără
prescaler.
Se pot folosi de asemenea și până la 5 intreruperi externe, care pot fi pe front
crescător sau descrecător, și două întreruperi de tip soft, a căror declanșare poate fi făcută
exclusiv prin cod. Pentru înțelegerea întreruperilor recomand [23].
4.5 Protocoale folosite
4.5.1 I2C
Este un protocol de tipul master slave. Se folosesc două linii pentru comunicare:
una pentru date(SDA), și alta pentru semnalul de ceas(SCL). Ambele linii sunt de tipul
open-drain, pentru a se putea modifica valoarea liniei din orice punct. Pe linia de SDA se
pot încărca datele fie de către master, fie de către slave; dar linia de SCL este permanent
controlată de master, slave-ul putând doar să prelungească timpul în care se găsește în
starea '0'.
Figura 4.12: Magistrala I2C
Liniile au valoarea default '1', datorită rezistențelor de pull-up Rp, care sunt
conectate la Vdd, așa cum se poate vedea în figura 4.1 2. Pentru modificarea valorii la '0'
se va conecta linia dorită la GND, fie din master, fie din slave.
Pentru a se modifica valoarea linie SDA , linia de SCL trebuie să fie în poziția low.
Singurele exceptii de la această regulă sunt condițiile de start și stop, în care datele trec
din high în low și invers, deși linia semnalului de ceas are valoarea '1', așa cum se arată și
în figura 4.13.
20
Datele transmise pe magistrală au 8 biți în dimensiune, urmate apoi de bit de
confirmare. În figura 4. 14 se prezintă schema generală de transfer pe magistrala I2C.
Repetarea startului apare pentru a nu se pierde arbitrarea atunci când se dore ște să se
transfere mai mult de 1 byte spre un dispozitiv .
Magistralele cu mai mult de un master au nevoie de arbitrare. În figura 4.15 s-a
presupus că sunt conectate 2 astfel de dispozitive. Ambele verifică linia de SCL și
încearcă pornirea unui transfer, și implicit obținerea magistralei. Deoarece ambele au
putut da startul, vor continua să pună datele în paralel, până când Data1 este '1', însă SDA
este '0', ceea ce înseamnă că primul a pierdut magistrala, și astfel renunță să mai pună
date pe I2C.
21
Figura 4.14: Formatul general de transfer al datelor
Figura 4.13: Start, schimbare date, stop
Un mecanism similar celui de arbitrare apare și la semnalul de ceas și se numește
'clock streching'. Dacă slave-ul mai are nevoie de timp pentru procesare va menține linia
de clock la valoarea '0', exemplificat în figura 4. 14, pentru a putea face procesăriile
necesare.
Protocolul I2C presupune trimiterea mai întâi a adresei slave-ului de la care se
dorește recepționarea/ trimiterea datelor, însoțit de un bit de read/write, iar la final de
ACK. În octeții următori se găsesc datele de la slave spre master, sau în sens invers. Cel
care este destinatarul datelor trebuie să confirme transferul prin ACK. Asupra tipurilor de
transfer voi reveni în capitolele următoare unde vor fi și exemplificate pentru DAC.
4.5.2 USB
Pentru această prezentare generală a protocolului am folosit [24] și [25], precum
și unele constatări experimentale.
4.5.2.1 O scurtă introducere
Diferențele între funcționalitățiile portului USB de pe placa de dezvoltare și cel de
pe un PC sunt minime. Diferențele majore sunt următoarele: oferă suport doar pentru
anumite clase sau dispozitive, poate suporta doar anumite tipuri de transfer, nu suportă
hub-uri, cantitatea de curent oferită este limitată.
Permite conectarea diverselor periferice la calculator prin folosirea unui cablu
unic. Într-un sistem conținând o magistrală USB, perifericele se pot conecta în serie sau
într-o topologie sub formă de stea pe mai multe nivele. Un avantaj important îl reprezintă
conectarea de tip plug-and-play care nu nece sită desfacerea carcasei sau repornirea
sistemului.
Portul USB detectează adăugarea unui nou periferic și determină în mod automat
resursele necesare acestuia, inclusiv driver-ul software, rata de transfer și consumul de
curent. Perifericele se pot afla la o distanță de până la 5 m unele față de altele sau față de
un distribuitor(hub).
În configurația de față oferită de Microchip nu se poate folosi distribuitor. Din
această cauză topologiile de tip stea nu pot fi implementate. De asemenea deși din punct
22
Figura 4.15: Arbitrarea a două masters
de vedere al USB Host Stack se permite conectarea mai multor dispozitive in serie, placa
de dezvoltare asigură un singur port de USB și astfel se poate conecta practic un singur
periferic la un moment dat.
Fiecare funcție(periferic) conține informații de configurație care descriu
posibilitățile sale și resursele necesare. Aceste informații sunt transmise calculatorului
gazdă ca răspuns la o tranzacție de control. Înaintea utilizării unei funcții, aceasta trebuie
configurată de calculatorul gazdă. Această configurare presupune alocarea unei lățimi de
bandă în cadrul magistralei USB și selectarea opțiunilor specifice de configurație.
4.5.2.2 Tipuri de transfer
Arhitectura USB permite patru tipuri de transferuri de date: de control, de
întrerupere, de date voluminoase și izocrone.
Transferurile de control se utilizează de driver-ele calculatorului gazdă pentru
configurarea dispozitivelor care sunt atașate la sistem.
Transferurile de întrerupere se utilizează pentru date care trebuie transmise cu
o întârziere limitată. Transferul acestor date poate fi solicitat de un dispozitiv în orice
moment, iar rata de transfer pe magistrală nu poate fi mai redusă decât cea specificată de
dispozitiv. Tastatura sau mouse-ul pot folosi acest tip de transfer pentru notificarea
apăsării unei taste, respectiv pentru trimiterea noilor coordonate ale cursorului.
Transferurile de date voluminoase (“bulk”) se utilizează cu periferice cum sunt
memorii de masă, imprimante sau scanere. Aceste date sunt secvențiale, și de dimensiuni
mari. Fiabilitatea transferurilor este asigurată la nivel hardware prin utilizarea unui cod
detector de erori și reluarea unui transfer cu erori de un anumit număr de ori. Rata de
transfer poate varia în funcție de alte activități de pe magistrală.
Transferurile izocrone se utilizează pentru datele care trebuie furnizate cu o
anumită rată de transfer constantă și a căror sincronizare trebuie garantată. Un exemplu ar
fi streaming-ul audio sau video, unde o eroare la transmiterea unui pachet duce la
anularea retransmisiei și continuarea cu următorul din secvența de transfer.
În cazul aplicației dezvoltate se folosesc cu precădere transferuri de control și de
întrerupere, existând suport și pentru bulk ; transferurile izocrone nu sunt suportate.
4.5.2.3 Modelul de comunicație USB
Protocolul USB este de tip asimetric, și folosește adresarea pentru identificarea
dispozitivelor conectate. Spre deosebire de I2C magistrala nu este de tipul multimaster.
Magistrala USB are 4 fire: VBUS și GND sunt folosite pentru alimentarea dispozitivelor
iar D+ și D- se folosesc pentru transferul într-o singură direcție la un moment dat al
datelor.
Comunicarea între host și periferic se face folosind fluxuri de comunicație numite
pipes, pentru realizarea legăturii la nivel de end point-uri. End point-ul este folosit alături
de adresa device-ului pentru identificare. El este necesar deoarece la o adresa(la un
dispozitiv) pot exista mai multe tipuri de transferuri fiecare fiind asociat cu un end point.
Punctele terminale pot fi de intare sau de ieșire, iar cel cu număr 0 este default fiind
folosit pentru configurare.
Există două moduri de comunicație printr-o conductă (pipe): șir sau mesaj. În
modul șir, datele nu au o structură definită de specificațiile USB. Datele sunt transferate
secvențial și au o direcție predefinită, de intrare sau de ieșire. Conductele de tip șir permit
23
transferuri de întrerupere, bulk sau izocrone. În modul mesaj, datele au o anumită
structură definită de specificațiile USB. Conținutul datelor transferate printr-o conductă
de tip mesaj este interpretat de controller-ul USB. Aceste conducte sunt controlate de
calculatorul gazdă și permit doar transferuri de control, în ambele direcții, fiind folosite
pentru configurarea dispozitivelor .
Punctele terminale conțin buffere de intrare sau de ieșire prin intermediul cărora
se realizează comunicația între calculatorul gazdă și dispozitivul USB. De exemplu, dacă
programul rulat pe calculatorul gazdă transmite un pachet adresat unui anumit dispozitiv
USB, acest pachet va fi depus în bufferul unui punct terminal de ieșire al dispozitivului.
Ulterior, controller-ul dispozitivului va citi din buffer pachetul recepționat. Dacă
dispozitivul trebuie să transmită date la calculatorul gazdă, nu poate depune datele direct
pe magistrală, deoarece toate tranzacțiile sunt inițiate de master. Dispozitivul va depune
datele în buffer-ul unui punct terminal de intrare, iar aceste date vor fi transferate la
cererea calculatorului printr-un pachet de intrare. Noțiunile de intrare/ieșire sunt relative
la host. Master-ul a fost denumit alternativ calculator sau host. Pentru slave s-a folosit
denumirea de dispozitiv.
4.5.2.4 Descriptorii de USB
Ierarhia de descriptori are ca rădăcină la nivelul superior descriptorul de
dispozitiv. La nivelul următor se află descriptorii de configurație; există câte un asemenea
descriptor pentru fiecare configurație a dispozitivului. Pentru fiecare configurație pot
exista unul sau mai mulți descriptori de interfață, în funcție de numărul de interfețe ale
configurației respective. În sfârșit, pentru fiecare punct terminal al fiecărei interfețe există
câte un descriptor al punctului terminal.
Descriptorul de dispozitiv este folosit pentru a oferi informații generale despre
dispozitiv: clasa/subclasa producator ID/produs ID, revizia de USB. Descriptorul de
configurație este folosit pentru a primi informații legate de tipul alimentării: proprii sau
prin bus-ul de USB. În cazul în care un dispozitiv are asociate mai multe astfel de
dispozitive, la un moment dat poate fi activat doar unul. Un număr oarecare de puncte
terminale pot fi grupate logic într-un descriptor de interfață ; care este folosit in cadrul
dispozitivelor multifuncționale. La un moment dat pot fi activate mai mulți asemenea
descriptori. Conține informații legate de numărul de puncte terminale și tipul de
clasă/subclasă. Descriptori de end-point sunt folosiți pentru a se stabili direcția, timp ul de
acces, lațimea de bandă, adresa dispozitivului. Un alt descriptor opțional dar care este
implementat in USB Host Stack este cel de șir de caractere( string) folosit pentru a se
obține informații in plain text legate de capabilitățiile dispozitivului.
4.5.2.5 Procesul de enumerare
Atunci când un dispozitiv este conectat/ deconectat la magistrala USB,
calculatorul gazdă execută un proces numit enumerare pentru a determina modificările
apărute în configurația sistemului USB. La conectarea unui dispozitiv USB la magistrală,
se execută următoarele operații:
1. Calculatorul gazdă folosind o rezistență determină conectarea unui nou
dispozitiv.
2. Calculatorul gazdă așteaptă un timp de cel puțin 100ms pentru ca tensiunea de
24
alimentare a dispozitivului să devină stabilă, după care transmite o comandă de validare
și de resetare a portului. Folosind măsurarea tensiunii pe liniile de date, se determină
prezența rezistențelor de pull-up/pull-down, și astfel se determină viteza dispozitivului:
viteză ridicată(480 Mbiți/s) sau viteza normală (12 Mbiți/s).
3. După terminarea procedurii de resetare, portul este validat. Dispozitivul se află
acum în starea implicită și poate absorbi un curent de maxim 100 mA de pe linia VBUS a
magistralei. Dispozitivul va răspunde la tranzacții cu adresa implicită zero.
4. Calculatorul gazdă solicită descriptorul de dispozitiv, iar dispozitivul transmite
acest descriptor prin intermediul conductei implicite.
5. Calculatorul gazdă asignează o adresă unică dispozitivului.
6. Calculatorul gazdă solicită descriptorii de configurație, iar dispozitivul
transmite calculatorului gazdă acești descriptori.
7. Pe baza informațiilor de configurație, calculatorul gazdă asignează o anumită
configurație dispozitivului. Dispozitivul se află acum în starea configurată și toate
punctele terminale ale acestei configurații sunt configurate conform caracteristicilor
specificate în descriptorii acestora.
Dispozitivul este pregătit pentru utilizare și poate absorbi de pe linia VBUS a
magistralei curentul specificat pentru configurația selectată.
Atunci când dispozitivul USB este deconectat de la un port USB, calculatorul
gazdă dezactivează portul respectiv și actualizează informațiile sale despre topologia
magistralei.
4.5.2.6 USB Embedded Host Stack
În Application note [26] se face o scurtă introducere a modului de funcționare a
USB host adaptat pentru embedded. Articolul [27] vine ca și o completare a articolului
precedent, insistându-se mai mult pe partea de programare, cu multe exemple de cod.
4.5.2.6.1 Target Peripheral List( TPL)
Conține o descriere a dispozitivelor ce pot fi suportate de host fie pe baz ă de clasă
și de protocol (ca de exemplu tabelul 4.1 ) sau de VID-PID (ca de exemplu tabelul 4.2).
În cazul microcontroller-ului , spre deosebire de PC, TPL rămâne fix, putându-se suporta
doar dispozitivele declarate inițial.
DescriereNumele claseiCodul claseiCodul subclaseiProtocol
Flash DriveMass Storage0x080x060x50
Tabelul 4.1: Exemplu de TPL pentr u Clasă, Subclasă și Protocol
DescriereProducătorModelVID(vector ID)PID(product ID)
Device cu
suport ADKGoogleTelefon/ tabletă0x18D10x2D00
Tabelul 4.2: Exemplu de TPL pentru VID, PID
25
4.5.2.6.2 Arhitectura stivei
Este formată din două părți: mașina de stări(pentru enumerarea dispozitivelor) și
handler-ul de întreruperi(pentru procese critice).
În figura 4.16 se prezintă schema generală pentru state machine. Inițializarea are
rolul de a pregăti controller-ul USB, și se realizează o singură dată după reset, trecându-
se apoi în starea detached, în care se așteaptă conectarea dispozitivelor. Un dispozitiv
atașat trece din starea atached în starea de primire a unei adrese dacă respectă condițiile
electrice specifice. Starea configured presupune pregătirea end point-urilor pentru a se
realiza transferul bidirecțional, printr-un apel al handler-ului de inițializare specific
dispozitivului, folosind intrările din TPL și Client Driver Table. La deconectare se trece
din starea running în starea detached.
Stiva de firmware e ste o structură de tip ierarhic. Aplicația( codul utilizatorului)
reprezintă ultimul nivel , USB Client Driver este folosit pentru inițializarea dispozitivului
26
Figura 4.16: Mașina de stări
descoperit, iar USB Host Layer este librăria care facilitează accesul la resursele hardware
și oferă funcționalitatea de bază pentru modul: identificare, enumerare, managementul de
drivere. Pot exista mai multe USB Client Driver dacă se dorește oferirea suportului
pentru un număr mai mare de dispozitive.
O organizare pe layers se folosește și pentru handler-ul de întrerupere, având trei
nivele: electric, mesaje USB și mesaje Android(ultimul nivel). Fiecare nivel tratează
evenimentele specifice, iar la întâlnirea unuia necunoscut se face o trimitere spre nivelele
superioare pentru analiză.
4.5.2.6.3 Driver-ul client bazat pe evenimente
Aplicația principală are rolul de a apela
constant layer-ul de la nivelul cel mai scăzut :
USB host(săgeată #2). La terminarea unei
activități pe USB se va apela(#3) în client
driver, iar dacă este suportat, se va apela mai
apoi aplicația(#4). Apelurile, folosind structuri,
trimit și informații legate de sursă și parametrii.
Astfel tranzițiile de la nivelele superioare, apar
ca urmare a evenimentelor de pe USB, fiecare
nivel având un state machine.
Layer-ul Host este influențat și de
evenimetele ce apar pe magistrală cum ar fi
conectarea sau deconectarea unui dispozitiv.
Apelul #2 are rolul de a permite tranzițiile între
sub-stăriile de la adresare sau configurare.
Se poate implementa driver-ul client și
folosind polling. Diferența între cele două
metode apare la modul în care se face tranziția
între stări, marcată prin folosirea de while loops
( polling) sau de întreruperi și call backs( event
based). Varianta cu întreruperi este mai
eficientă. Exemple de call back sunt apelurile
#3 și #4.
4.5.3 Android Open Accessory Protocol
Un USB host(accesoriul) va trebui să implementeze următorii pași:
•așteaptă și detectează conectarea device-ului cu Android
•verifică dacă acest device suportă modul accesoriu
•dacă nu este deja în acest mod va încerca pornirea acestuia în modul amintit
27
Figura 4.17: Driver-ul bazat pe
evenimente
•stabilește comunicarea cu device-ul folosind protocolul amintit în subtitlu
Accesoriul verifică la fiecare conectare a unui slave ca vendor ID device-ului să
fie 0x18D1, și ID de produs să fie 0x2D00(ADK) sau 0x2D01(ADK și ADB). Dacă nu
se primesc aceste răspunsuri, microcontroller-ul va încerca pornirea dispozitivului în mod
accesoriu: trimite o comandă de tip 51, de obținere a protocolului, iar device-ul va
răspunde cu un număr nenul dacă are implementat ADK.
Folosind comanda 52 se vor trimite pe rând descrierile către accesoriu. Apoi se va
trimite device-ului comanda 53 pentru a porni în modul accesoriu. După trimiterea
comenzii 53, USB-ul de pe device se va reporni ceea ce duce la un nou proces de
enumerare. De această dată vendor ID și product ID trebuie să fie cele corecte, în caz
contrar, sau la orice eroare apărută pe parcurs, înseamnă că device-ul nu cunoaște
protocolul cerut. Pentru mai multe detalii legate de structura acestor mesaje standard
poate fi consultată secțiunea 5.9. Comenzi USB din [25].
CommandRequest Type RequestValueIndexLengthData(optio
nal)
Get
protocol
(5110)USB_Dir_out|
USB_type_Vend
or| USB_Device51002Get
protocol
Send
strings
(5210)USB_Dir_out|
USB_type_Vend
or| USB_Device5200… 5String
dimensionSend a
string
Strart
(5310)USB_Dir_out|
USB_type_Vend
or| USB_Device53000Send Null
Tabelul 4.3: Comenzi de inițializare ADK
Dacă ID-ul de producător este 0x2D00, atunci este o singură interfață cu două
endpoint-uri bulk, unul pentru intrare și altul pentru ieșire. Dacă primim ID 0x2D01,
atunci sunt două interfețe(ADK și ADB) fiecare cu câte două bulk endpoints. În tabelul
4.3 sunt prezentate schematic etapele protocolului, numerele fiind în baza zece. Valorile
lui index pentru comanda 52 în ordine crescătoare sunt: producător, model, descriere,
versiune, URI, și număr serial. URI se folosește pentru a se descărca aplicația din Google
Play în cazul în care nu este instalată pe telefon/tabletă. Comenziile 52 și 53 nu se
folosesc dacă device-ul se află deja în modul accesoriu.
4.6 Algoritmii propuși
Întregul sistem poate fi privit ca și un sistem distribuit, deoarece fiecare modul își
execută funcționalitatea independent de restul.
28
Figura 4.18: Structura logică a aplicației
În figura 4.18 este prezentată schema generală a aplicației. Codul de culori oferă o
mai bună grupare vizuală a componentelor. Perifericele producătoare/consumatoare de
date sunt colorate cu albastru, iar zonele partajate sunt de culoarea orange. Memoriile
partajate pot fi privite ca și buffere, cu mai multe periferice care scriu și care citesc din
aceste zone. Circuitele de USB și RTCC sunt colorate cu verde respectiv roșu deoarece
îndeplinesc funcționalități speciale. RTCC se folosește pentru a controla procesul de
actualizare a zonei comune văzută de USB, copierea datelor făcându-se de la dreapta la
stânga(săgeata de pe desen). Modulul USB este cel care comunică mai departe cu tableta.
Direcția săgeților arată modul de deplasare al datelor, fără a se insista pe tipul sau numele
datelor care circulă.
Datele care intră în ADC sunt de tip analogic, iar cele care intră, respectiv ies din
IC și din PWM sunt de tip digital, de tip semnal dreptunghiular. RTCC este alimentat de o
sinusoidă de 32768Hz.
29
4.6.1 Conversia din analogic în digital
În figura 4.19 se prezintă schema generală de conversie din analogic în digital.
Primul pas presupune eșantionarea și conversia efectivă a celor două tensiuni(canal 1 si
canal 2). În funcție de ce zonă folosim pentru salvare se va salva în zona 0 sau în 1.
Valoriile pentru i sunt de la 0 la 7, iar pentru j sunt de la 8 la 15. Atât i, cât și j se vor
incrementa, iar după ce au ajuns la 7 respectiv 15 se generează o întrerupere și se
reinițializează. În întrerupere are loc citirea celor 8 valori din buffere, în funcție de zona
de lucru și medierea lor. Eșantionarea celor două tensiuni se face în mod alternativ.
De exemplu, considerăm că zona de lucru este 0. Se face conversia primei tensiuni
și salvarea în buffer-ul 0, iar a doua tensiune se salvează în bufferul 1. Se va continua cu
salvarea canalului 1 în buffer-ul 2, și a canalului 2 în buffer-ul 3. După ce s-a scris și în
bufferul 7 se generează întreruperea. În întrerupere se ține seama de zona curentă de lucru
și de alternanța cu care se salvează cele două canale.
30
Figura 4.19: Schema generală de conversie
4.6.2 Măsurarea turației
În figura 4.20 se prezintă modul general în care se măsoară frecvența unui semnal
dreptunghiular. Cât timp nu apare un front crescător se incrementează un timer. La
apariția unui eveniment se generează o întrerupere. În întrerupere se verifică dacă a fost
primul astfel de eveniment. În caz afirmativ se reinițializează timer-ul. La al doilea
eveniment mai întâi se va salva valoarea ceasului, apoi acesta se va reinițializa. Al treilea
eveniment este considerat din nou ca fiind primul, acțiunea algoritmului fiind similară.
Figura 4.21: Modul de folosire al două module de IC și un Timer
În figura 4.21 se prezintă cazul în care se măsoară două frecvențe folosind un
singur timer. Culoarea albastră reprezintă primul canal, iar culoarea verde reprezintă al
31
Figura 4.20: Măsurare frecvență puls
doilea canal. În acest caz mai întâi se analizează primul canal în așteptarea a două fronturi
de interes(F1 și F2). În acest timp orice eveniment pe al doilea canal este ignorat. După
măsurarea perioadei primului canal(T canal 1) acesta se ignoră și se urmărește apariția a
două fronturi pe canalul 2. Ciclul apoi se reia cu primul canal. Pentru măsurarea perioadei
pe oricare canal se consideră algoritmul prezentat în prima parte a secțiunii.În mod
similar, cazul de față poate fi extins la orice număr de canale, creșterea numărului ducând
la scăderea ratei cu care se actualizează informațiile legate de frecvența lor.
4.6.3 Actualizarea datelor
Actualizarea datelor apare în figura 4.18 ca și un transfer între două zone partajate.
Actualizarea repezintă o copiere a datelor din zona din partea dreaptă în zona din partea
stângă.
Figura 4.22: Cele două moduri de actualizare a datelor
În figura 4.22 sunt prezentate cele două regimuri de funcționare a mecanismului
de actualizare: normal și calibrarea motoarelor. În modul de funcționare normală se face o
copiere a datelor la o frecvență de 1Hz. Această frecvență poate fi obținută folosind un
circuit extern, un timer intern sau circuitul de RTCC. Circuitul de RTCC are avantajul că
nu este folosit de altă componentă sau de către program.
În cazul în care se dorește o calibrare a motoarelor valoriile măsurate vor fi
transferate imediat ce au fost calculate. Pentru aceasta la fiecare modificare a factorului
de umplere al PWM are loc un răspuns care cuprinde tensiuniile și frecvențele măsurate.
32
4.6.4 Calibrare motoare
Procedura de calibrare presupune
folosirea factorului de umplere al PWM
care comandă viteza motoarelor. Factorul
de umplere va lua valoarea initială 100%,
și va continua să scadă atât timp cât turația
motorului este diferită de zero. În cazul în
care s-a ajuns la 0, motorul se consideră
oprit, și factorul de umplere se setează la
zero. Procedura este evidențiată în figura
4.23 și trebuie repetată pentru fiecare
motor în parte.
Calibrarea are rolul de a stabili
intervalul de valori pentru PWM care
produce rotirea motorului. Ca și rezultat se
elimină zonele cu factor de umplere mic, în
care motorul are turație 0.
În locul decrementării factorului de
umplere se poate porni de la valoarea zero,
și apoi să se incrementeze cât timp turația este zero. Această variantă a algoritmului
produce un prag al factorului de umplere mai mare decât varianta care presupune
decrementarea. Explicația constă în faptul că pentru a se pune inițial în mișcare bobina
rotorului este nevoie de un impuls de tensiune mai mare. Odată obținută mișcarea de
rotație, se poate reduce factorul de umplere cu câteva procente, fără a se constata oprirea
motorului.
4.6.5 Înlănțuirea comenzilor pe USB
În figura 4.24 se prezintă modul în care poate fi crescută performanța
transferurilor pe USB prin înlănțuirea comezilor. Fiecare comandă este identificată
printr-un cod unic urmată de un număr cunoscut de argumente. În exemplul următor s-a
considerat transmisia unui număr de 3 comenzi pe magistrala serială, fiecare comandă
fiind de dimensiune variabilă. Fiecare dreptunghi are dimeunea de 1 octet. Se folosește
un vector unde vor fi copiate comenziile, lungimea fiind egală cu suma dimensiunilor
comenzilor în cazul maxim de înlănțuire.În cazul extrem număr maxim de înlănțuiri este
egal cu numărul de comenzi de ieșire pe magistrala serială. Numărul poate fi redus dacă
existența unor comenzi de ieșire este exclusivă. Pointer-ul se folosește pentru a indica
locația următoare unde se poate copia informația.
La primul pas se copiază prima comandă și se incrementează poziția pointerului cu
3, reprezentând numărul de octeți mutați. Acțiuni similare se execută și pentru
următoarele două comenzi, pointer-ul indicând la final locația de după argumentul
comenzii 3. Operația poate fi extinsă până la copierea unui număr de 1023 de octeți, dacă
se folsește modul bulk. Principalul avantaj al înlănțuirii îl constituie faptul că printr-un
singur transfer de USB se vor transmite mai multe date. Un dezavantaj minor este
reprezentat de logica de decodificare a mesajului, care presupune împărțirea acestuia în
33
Figura 4.23: Calibrare motor
tokens(comenzi) în funcție de codul aflat pe primul octet și manipularea individuală a
fiecărei comenzi.
Pentru a se trimite și date care nu constituie comenzi, sau a căror lungime este
variabilă, trebuie ca informațiile să fie împachetate. În acest sens, se va folosi un cod de
mesaj special, pentru a codifica informațiile care nu constituie comenzi. Pe următorul
octet se va specifica numărul de bytes al mesajului, iar în următorii octeți se vor copia
datele efective. În acest caz pentru a se putea descifra mesajul este nevoie să se citească
primi doi octeți, primul este folosit pentru a marca un mesaj special, iar următorul pentru
a specifica numărul de argumente componente.
O altă abordare pentru mesajele speciale presupune încadrarea datelor între un cod
special și un marcator de final(asemănător \n în C). Pentru a se include marcatorul de
sfârșit în corpul mesajului se va adopta o tehnică similară caracterelor escape din C.
Lipsa marcatorului de final va permite citirea întregului buffer de intrare al USB, ceea ce
poate duce la pierderea comenziilor care nu vor mai fi interpretate.
Pentru introducerea mesajelor de dimensiune variabilă, prima abordare este mai
sigură și mai ușor de implementat.
34
Figura 4.24: Înlănțuirea comenziilor pe USB
5. Proiectare de detali u și implementare
În figura 5.1 se prezintă schema generală a aplicației. Microcontroller-ul(uC) este
prezentat ca o componentă separată, cu rol în controlul traficului informației. Săgețiile
indică direcțiile de circulație ale datelor. Senzorii și elementele de acționare sunt
prezentate ca și componente separate, dar o parte sunt unități din interiorul uC, divizarea
fiind făcută astfel doar pentru a evidenția rolul lor diferit în sistem.
În figura 5.2 se prezintă structura aplicației din punct de vedere al depenențelor de
componte hardware și software. Aplicația de față are ca și bază un exemplu oferit de
Microchip, în care se folosește tableta pentru a controla 4 LED-uri, a citi 2 butoane și un
potențiometru de pe placă. Codul de culori s-a folosit pentru a evidenția zonele care au
presupus dezvoltare, sau care au avut nevoie doar de documentare și testare.
În partea superioară a imaginii sunt prezenate cu orange fișierele create pentru
realizarea funcționalităților propuse. Divizarea pe fișiere are rolul de a permite o grupare
a algoritmilor folosiți pentru îndeplinirea unui task. Fișierul mainDef.h conține o serie de
variabile externe, vizibile din mai multe fișiere, precum și încapsularea accesului pentru
datele folosite de mai multe module prin folosirea de macro-uri. Pentru îndeplinirea unei
funcționalități se foloște perechea de fișiere .h și.c. În fișierul header sunt redefiniți
regiștrii folosiți(pentru o denumire uniformă) și parametrii de configurare. În fișierul
sursă se găsește codul efectiv. Main.c este fișierul principal, și folosește funcționalități
din restul modulelor. Conține inițializăriile modulelor și bucla principală.
Android Accessory Framework și USB Stack sunt cele două librării care
realizează comunicarea pe protocolul Andoid Open Accessory. Aplicația folosește cele
35
Figura 5.1: Schema generală a aplicației
două librării amintite, împreună cu Microchip Peripheral Library(MPL), care este folosit
pentru accesul la modulele de bază ale microcontroller-ului. MPL folosește cod scris în
ASM sau în C. Toate librăriile au fost folosite ca atare, fiind nevoie doar integrarea lor în
proiect și testarea funcționalităților.
Folosirea modulelor a presupus analiza modului de operare și a interdependențelor
existente, pentru a se folosi cât mai eficient un număr minim de astfel de componente.
Partea de hardware a presupus design-ul folosind unelte cum ar fi falstad.com/circuit,
prototipizarea pe breadboard și împrimarea efectivă a montajului pe o placă.
Figura 5.2:Dependențele aplicației
5.1 Modulul de DAC
Acest modul are o adresă fixă 0x60, pinii de A0- A2 find lipiți la GND. Acest
modul este un convertor pe 12 biți asigurând astfel un interval de 4096 de valori. Ieșirea
este proporțională cu intrarea, din această cauză este sensibil la valoarea intrării.
S-au implementat funcțiile de scriere în registru și în EEROM precum și de citire
a registrului și a memoriei. Memoria nevolatilă poate fi folosită pentru a se programa o
singură dată modulul, pentru ca apoi acesta să păstreze valoriile și după o repornire a
sistemului.
Semnăturile celor două funcții de scriere void writeDACRegister( unsigned int
DACValue, unsigned short PowerDownOption) și respectiv a void
36
writeDACRegisterAndEEPROM( unsigned int DACValue, unsigned int
PowerDownOption) sunt identice, prima valoare fiind un număr între 0- 4095, iar cea dea
doua valoare reprezintă opțiunea de power down, pentru a se obține high impedance dacă
se dorește. Implementarea acestor metode urmărește Reference Manual-ul de la
producător.
Funcția de citire din DAC nu este folosită, semnătura ei fiind BOOL
readDACRegisterANDEEPROM ( BYTE i2cbyte[]), și returnează un boolean pentru a
semnaliza dacă operația s-a executat sau nu cu succes, iar rezultatul este depus în
i2cbyte[].
În figura 5.3 se prezintă modul în care se poate scrie în registrele circuitului de
DAC, fără salvarea în EEPROM. În primul octet se precizează adresa dispozitivului,
împreună cu tipul operației, în acest caz este una de scriere. În octetul al doilea și al
treilea se prezintă valoriile care trebuie încărcate în registre, de interes fiind DAC
Register, care va reprezenta valoarea ieșirii; tipul operației se specifică prin Fast Mode
Command. Fiecare byte se va confirma(Ack) de către DAC. Pentru a se salva în
EEPROM, în locul bitului de stop se repetă conținutul octețiilor 2 și 3 de pe figură.
5.2 Modulul de Input Capture
S-a ales folosirea Timer 2 ca și bază de timp pe 16 biți, cu un prescaler de 16, și a
întreruperilor de la canalul 2, respectiv 3 de la Input Capture. Am ales folosirea unei
abordări de tip state machine state pentru a se permite măsurarea cît mai exactă a două
frecvențe folosind un singur timer.
Se definesc macro-urile pentru prima sursă sampleIC2Start și sampleIC2Stop ,
respectiv sampleIC3Start și sampleIC3Stop, pentru a doua sursă de frecvență și se
folosește o variabilă care va trece prin cele 4 stări. Ca și exemplificare se folosește tabelul
5.1, abordarea fiind similară cu cea propusă în secțiunea 4.6.2.
Pentru măsurarea lui IC2, variabila va lua mai întâi valoarea sampleIC2Start,
odată cu primul rising edge; la următorul eveniment pe IC2 va trece în sampleIC2Stop. În
timpul în care variabila a avut una din cele două valori enumerate mai sus, orice
eveniment de pe IC3 va fi pur și simplu ignorat. În sampleIC2Stop, doar un eveniment pe
IC3 va determina tranziția în sampleIC3Start, evoluția fiind similară pentru IC3.
Detecția opririi unuia dintre motoare se face folosind detectarea condiției de
overflow de la baza de timp, caz în care se verifică dacă variabila este sampleIC2Start
sau sampleIC3Start , pentru a se determina sursa. Se va trece apoi la canalul următor în
starea de start, pentru a se evita blocarea algoritmului.
37
Figura 5.3: Scrierea în registre, fără salvare în EEPROM
stare actuală rising edge stare următoare Observații
sampleIC2StartIC2sampleIC2Stop resetează Timerul
IC3păstrază starea
actuală
sampleIC2StopIC3sampleIC3Startpreia valoarea din
Timer și îl resetează
IC2păstrază starea
actuală
sampleIC3StartIC2păstrază starea
actuală
IC3sampleIC3Stop resetează Timerul
sampleIC3StopIC3păstrază starea
actuală
IC2sampleIC2Startpreia valoarea din
Timer și îl resetează
Tabelul 5.1: Evoluția stărilor pentru implementarea input capture
În continuare voi prezenta codul pentru ISR de input capture pentru primul canal:
1 | #if CaptureMotor1INTLevel == IC_INT_PRIOR_4
2 | void __ISR(CaptureMotor1INTVector , ipl4)
InputCapture2Routine (void){
3 | INTClearFlag (INT_IC2);
4 | bufferTemp = CaptureMotor1ReadCapture ()& 0xFFFF;
5 | if(sampleICx== sampleIC2Start ){
6 | TimerValueReg = 0; // reset the timer
7 | sampleICx = sampleIC2Stop; // go to next state
8 | }else
9 | if(sampleICx== sampleIC2Stop){
10| val1IC2 = bufferTemp; TimerValueReg= 0; sampleICx=
11| sampleIC3Start;}
12| if(CaptureMotor1ControlReg .ICOV)
13| val1IC2 = InCapOVFL;
14| }
15| #endif
, și codul pentru ISR a timer-ului:
1 | void __ISR(_TIMER_2_VECTOR , ipl1) Timer2Routine(void){
2 | INTClearFlag (INT_T2);
3 | if((sampleICx== sampleIC2Start ) ||(sampleICx==
sampleIC2Stop)){
4 | sampleICx = sampleIC3Start ; val1IC2= TimerOVFLInCap;
5 | }else
6 | if((sampleICx== sampleIC3Start ) ||(sampleICx==
sampleIC3Stop)){
7 | sampleICx = sampleIC2Start; val1IC3= TimerOVFLInCap;
8 | }
9 | }
38
5.3 Modulul de PWM
În cadrul acestui modul, spre deosebire de celelalte, nu sunt alte funcții decât cea
de set up, nefiind necesare întreruperi, totul fiind controlat de hardware.
Codul pentru funcția de set-up este următorul:
1 |MotorsDirectionConfigPins ();
2 | MotorSpeedPWMConfigPins ();
3 | setMotorsDirection (1);
4 | PWM_PRReg_FREQ= getPR_For_PWM(PWM_desired_freq, 1);
5 | OpenTimerForMotors (MotorConfigs, PWM_PRReg_FREQ);
6 | OpenOCMotor1(Motor1Configs, 0,0);//PWM motor 1
7 | OpenOCMotor2(Motor2Configs, 0,0);//PWM motor 2
Pinii de output ai modului de PWM trebuie marcați ca fiind de ieșire.În funcția de
mai sus se setează doar frecvența bazei de timp, factorul de umplere pentru motoare fiind
fixat la zero.
5.4 Modulul de ADC
Active buffer Index Valoare
00channel4mas1
1 channel8mas1
2channel4mas2
3 channel8mas2
4channel4mas3
5 channel8mas3
6channel4mas4
7 channel8mas4
18channel4mas1
9 channel8mas1
10channel4mas2
11 channel8mas2
12channel4mas3
13 channel8mas3
14channel4mas4
15 channel8mas4
Tabelul 5.2: Folosirea buffer-elor de la ADC
În figura 5.2 se prezintă modul în care sunt folosite cele 15 buffere pentru
eșantionarea alternativă a două canale. Ordinea se salvare a celor două canale eșantionate
39
este alternativă. La un moment dat se poate lucra cu o singură zonă, fiind disponibile fie
registrele 0- 7, fie 8- 15. În întrerupere se citesc cele 4 valori pentru fiecare canal din
zona activă, valoarea canalului fiind media lor. Modulul nu se poate folosi în cazul de
față la valoarea maximă, deoarece produce blocarea restului perifericelor, chiar dacă are o
prioritate mai mică decât restul.
5.5 Modulul de RTCC
În continuare se prezintă o parte a void setUpRTCC_1secINT (), care realizează
configurarea modulului și codul handler-ului de întrerupere void
__ISR(_RTCC_VECTOR, ipl7SRS) RtccIsr( void).
1|RtccInit();
2|while(RtccGetClkStat()!= RTCC_CLK_ON);
3|RtccChimeEnable();
4|RtccSetAlarmRptCount (255);
5|RtccSetAlarmRpt(RTCC_RPT_SEC);
6|RtccAlarmEnable();
În acest cod are loc o inițializare a modului de RTCC, urmată prin while-ul care
testează dacă este conectat oscilatorul extern. Următoarele 4 instrucțiuni sunt folosite
pentru a seta alarm și repetarea ei nedefinită( funcția de chime).
1 | #if RTCCLevel== INT_PRIORITY_LEVEL_7
2 | void __ISR(_RTCC_VECTOR, ipl7SRS) RtccIsr(void)
3 | {
4 | INTClearFlag(INT_RTCC);
5 | SetInputCapture1 (GetFreqMotor1());
6 | SetInputCapture2 (GetFreqMotor2());
7 | SetVoltageBatt(GetChannel4Batt());
8 | SetVoltageSunt(GetChannel8Sunt());
9 | mLedRTCCToggle();
10| rtcc_1secINTR = TRUE;
11| }
12| #endif
Handler-ul de întrerupere al RTCC este cel care controlează schimbul de date de
la și spre USB. Acest lucru se realizează prin cele 4 apeluri de macro și marcarea flag-
ului rtcc_1secINTR cu TRUE. Variabila booleană se folosește în bucla principală din
main pentru încărcarea efectivă a datelor pe magistrală. Pentru a se obține o frecvență
mai mare va trebui modificat repetarea alarmei la RTCC_RPT_HALF_SEC, ceea ce va
dubla frecvența de trimitere a datelor. Toggle-ul LED-ului are scop de verificare vizuală.
5.6 Programul principal
În continuare voi prezenta programul principal prin evidențierea zonelor
importante pentru înțelegerea algoritmului. Bucățiile de cod pot să fie diferite față de cele
din codul aplicației, deoarece unele linii de cod au fost înlăturate pentru a se putea
urmării mai ușor logica aplicației.
40
5.6.1 Structura comenziilor
Comenziile sunt definite folosind o enumerare, în locul unor typedef/macro pentru
o grupare vizuală mai eficientă.
1 |typedef enum _ACCESSORY_DEMO_COMMANDS
2 |{
3 | COMMAND_getConfig_ADC = 0x01,
4 | COMMAND_getConfig_Fuses_PWM = 0x02,
5 | COMMAND_getConfig_InputCapture = 0x03,
6 | COMMAND_getValue_InputCapture = 0x04,
7 | COMMAND_getValue_Voltage = 0x05,
8 | COMMAND_setValue_PWM = 0x06,
9 | COMMAND_shutDown = 0x07,
10| COMMAND_initialPWMConfig = 0x08,
11| COMMAND_finishedPWMConfig = 0x09,
12| COMMAND_getConfig_MotorsID = 0x0A,
13| COMMAND_setDirectionMotor = 0x0B,
14| COMMAND_APP_CONNECT = 0xFE,
15| COMMAND_APP_DISCONNECT = 0xFF
16|} ACCESSORY_DEMO_COMMANDS ;
, iar lungimiile sunt fixe:
1 | #define Command_getValue_Voltage_Size 4
Coman
dăArg 1Arg 2Arg 3Arg 4Arg 5Arg 6Arg 7Arg 8
1ADC Size[Vref]{Vref}
2Freq procFPLLMU
LFPLLDI
VFPLLODI
VFPBDIVOC sizeFreq OCOC presc
3IC prescIC sizeEvent size
4HFreq IC 1LFreq IC 1HFreq IC 2LFreq IC 2
5HU șuntLU șuntHU batteryLU battery
6HDuty
motor 1LDuty
motor 1HDuty
motor 2LDuty motor
2
7
8
9
10ID motor 1ID motor 2
11Dir motors
254
255
Tabelul 5.3: Comenziile trimise între tabletă și uC
În tabelul 5.3 se prezintă structura comenziilor. O parte a acestor comenzi sunt de
tipul flag, și nu au argumente, contând doar ID lor, cum ar fi 7,8,9. Pentru parametrii care
ocupă mai mult de un octet s-a folosit HArg respectiv LArg pentru partea superioară
41
respectiv inferioară a argumentului. Comenziile au structură și lungime fixă.
Pachetele de intrare și de ieșire nu sunt altceva decât un șir de bytes, lucru
evidențiat prin codul care urmează:
1| typedef struct __attribute__((packed))
2| {
3| BYTE command;
4| BYTE data[MAX_COMMAND_DATA_Size ];
5| }ACCESSORY_APP_PACKET outgoing_packet; ;
Structura precedentă poate fi folosită ca atare sau se poate considera că este
formată dintr-un vector de bytes, așa cum se folosește în codul următor:
1 |errorCode = AndroidAppRead (device_handle, (BYTE*)&command_packet,
(DWORD)sizeof(command_packet));
2 |switch(command_packet->command)
3 |{
4 |case COMMAND_setValue_PWM :
5 | commandSize= Command_setValue_PWM_Size + 1;
6 | if(initialPWMconfig == TRUE){
7 | rtcc_1secINTR= TRUE;
8 | SetInputCapture1 (GetFreqMotor1());
9 | SetInputCapture2 (GetFreqMotor2());
10| SetVoltageBatt(GetChannel4Batt());
11| SetVoltageSunt(GetChannel8Sunt());
12| }
13| break;
14|case COMMAND_initialPWMConfig :
15| break;
16|case COMMAND_finishedPWMConfig :
17| break;
18|case COMMAND_setDirectionMotor :
19| break;
20|case COMMAND_shutDown :
21| break;
22|case COMMAND_APP_DISCONNECT :
23| break;
24|default:
//Error, unknown command
25| DEBUG_ERROR("Error: unknown command received" );
26| break;
27|}
În timpul citiri cu AndroidAppRead, pe magistrala USB pot exista mai multe
comenzi înlănțuite. Prin verificarea primului octet, care indică lungimea primei comenzi,
se determină numărul de bytes rămași pentru procesare și adresa din cadrul vectorului de
unde se va continua procesarea.
1| size-= commandSize;
2| relativeAddressInCommandPacket += commandSize;
3| if(size!= 0)
4| memcpy(command_packet,&read_buffer[relativeAddress],size);
5.6.2 Trimiterea datelor către tabletă
În cazul în care se trimit mesaje la viteză maximă, fără a folosi întârzierea de o
secundă se produce încălzirea tabletei. Pentru a se limita numărul de mesaje trimise în
42
bucla principală din main se verifică dacă a trecut o secundă de la ultima transmisie:
1| if(rtcc_1secINTR== FALSE)
2| continue;
Se verifică dacă sunt și alte operații de scriere în așteptare, iar în caz negativ se va
putea trimite mesajul dorit. Pentru a crește eficiența în coada de mesaje pot fi puse mai
multe mesaje, fiecare de dimensiune definită.
1| if(writeInProgress == FALSE){
2|outgoing_packet.command = COMMAND_getValue_InputCapture ;
3|outgoing_packet.data[0]= ….
4|memcpy(&write_buffer[adresaWrite], (BYTE*)&outgoing_packet ,
Command_getValue_InputCapture_Size + 1);
5|adresaWrite+= Command_getValue_InputCapture_Size + 1;
6| }
În final are loc și scrierea propriuzisă, precum și marcarea faptului că a avut loc o
scriere(writeInProgress ):
1| if(writeInProgress== FALSE){
2| rtcc_1secINTR= FALSE;
3|writeInProgress= TRUE;
4|errorCode = AndroidAppWrite (device_handle, write_buffer ,
adresaWrite);
5| }
5.6.3 Funcția de trimitere a datelor către Android
În continuare se va prezenta modul în care se trimit datele către tableta Android,
fiind evidențiate și fișierele în care sunt definite funcțiile respective, pentru a se putea
urmării nivelurile multiple de redirectări care apar ca urmare a existenței layer-urilor.
•usb_host_android.c :: AndroidAppRead(void* handle, BYTE* data, DWORD
size) . Se face o parcurgere a numărului de dispozitive Android și fiecare va fi
notificat. Parametrul handle reprezintă informația primită la conectara
dispozitivului, iar data este un buffer unde va fi depozitată informația. Dacă la
acest nivel se face verificarea de device la nivel de Android, la următorul nivel de
redirectare se va face verificarea de adresa, RX/TX busy, stare:
• usb_host_android_protocol_v1.c ::AndroidAppRead_Pv1( void* handle,
BYTE* data, DWORD size). Se folosește o redirectare la:
• usb_host.c::BYTE USBHostRead( BYTE deviceAddress, BYTE
endpoint, BYTE *pData, DWORD size ), unde se va verifica la
nivel de tip de endpoint și starea ultimului transfer. Se va face o
nouă redirectare la o metodă din același fișier
•void _USB_InitRead( USB_ENDPOINT_INFO
*pEndpoint, BYTE *pData, WORD size ) care va face
încărcarea efectivă a registrelor cu valorile necesare.
5.6.4 Inițializarea comunicației pe USB și ADK
Se folosesc funcțiile: USBHostInit(unsigned long flag) și AndroidAppStart
(&myDeviceInfo), pentru realizarea inițializăriilor necesare.În cadrul primei funcții se
folosește structura usbDeviceInfo pentru a se vedea dacă există sau trebuie alocat
43
Endpoint-ul 0, singurul care se folosește momentan în aplicație. Are loc și o inițializare a
variabilelor folosite pentru menținerea adreselor și starea endpoint-ului curent, precum și
inițializarea stivei de evenimente.
În cadrul celei de-a doua funcții se inițializează structuriile:
ANDROID_ACCESSORY_INFORMATION , care conține informații legate de
producător, model, descriere; ANDROID_DEVICE_DATA care conține informații legate
de driver, protocol, stare, adrese; ANDROID_PROTOCOL_V1_DEVICE_DATA se
inițializează cu informații legate de adresa device-ului, starea acestuia, numărul endpoint-
ului de intrare și de ieșire, precum și starea liniilor RX și TX.
5.6.5 USBTask()= keep the stack running
Cuprinde două apeluri de funcții: USBHostTask() și AndroidTask(). Prima funcție
are rolul de a asigura procesarea ultimului eveniment din stivă, și de a notifica aplicația.
Funcția void AndroidTasks( void) verifică dacă dispozitivul conectat suportă
modul accesoriu. Se folosește AndroidTasks_Pv1() pentru a itera peste numărul de
dispozitive conectate, verificând la fiecare descrierea și dacă poate întra în modul
accesoriu. În caz afirmativ se apelează funcția de call back din fișierul principal
USB_ApplicașionEventHandler , folosit pentru a seta flag-uri care marchează
descoperirea unui nou dispozitiv.
5.6.7 Inițializarea plăcii de dezvoltare
Funcția initPIC() are rolul de a inițializa platforma de dezvoltare să funcționeze la
o frecvență de 60MHz. În fișierul HARDWARE_PROFILE_PIC32MX460F512L.h se
definesc parametrii de funcționare cum ar fi frecvența de bază a sistemului, frecvența
pentru periferice, și o serie de flag-uri pentru a semnala perifericele folosite de aplicație.
5.7 Logica generală a aplicației
Folosind ca și suport explicațiile din capitolele anterioare în continuare se va
prezenta logica de ansamblu a aplicației. Se va folosi pseudocod pentru exemplificare:
1 | inițializare_module ();
2 | activare_intreruperi ();
3 | while(1){
4 | USBTasks();
5 | if(device_attached == FALSE) continue;
6 |if(readyToRead == TRUE)AndroidAppRead();
7 |AndroidAppIsReadComplete ();
8 |while(size > 0)procesează_comenzi ();
9 |readyToRead= True;
10|if(connected_to_app == FALSE)continue;
11|if(configsSendOK== FALSE) send_configs();
12|if(writeInProgress== TRUE )
13|AndroidAppIsWriteComplete () ;
14|if(rtcc_1secINTR== FALSE)continue;
15|if(writeInProgress== FALSE) AndroidAppWrite();
16| }
Aplicația este formată din două părți: inițializarea modulelor folosite și bucla
44
principală care rulează la infinit.
În partea de inițializare se configurează fiecare modul și se activează global
întreruperile.
Fiecare iterație a buclei va apela USBTask pentru a menține funcționarea stivei
USB. În cazul în care nu este atașat niciun dispozitiv pe magistrală se continuă cu iterația
următoare. Spre deosebire de linia 5, în linia 10 se verifică dacă s-a primit mesajul de
conectare de la aplicația Android. Și în acest caz se continuă cu iterația următoare în caz
negativ.
Mecanismul de citire este prezentat în liniile 6 și 7, iar cel de scriere în liniile 13 și
15. Mecanismul de citire verifică la fiecare pas dacă ultima operație de citire s-a executat
cu succes, însă pentru write se foloște un flag, verificarea fiind făcută doar dacă a avut loc
o scriere precedentă. În cazul în care s-a citit cel puțin o comandă în linia 8 se va procesa
șirul de octeți primit, iar în rândul următor se marchează terminarea procesării, ceea ce
permite citirea unei noi valori.
Fiecare dispozitiv conectat pe magistrală va primi informațiile legate de
configurația microcontroller-ului: dimensiunea registrelor, frecvențele folosite, procedeu
evidențiat în linia 11.
Pentru a nu se produce o încărcare a magistralei cu informații despre turații și
tensiuni, se folosește o întârziere de minim o secundă față de ultimul transfer. La
frecvențe mari ale transferului dinspre microcontroller spre Android s-a constat o creștere
a temperaturii tabletei, și performanțe mai scăzute.
Un mecanism care nu a fost prezentat în pseudocod este cel de schimbare a
direcției motoarelor. Se folosește mecanismul din linia 14 pentru a verifica că a trecut cel
puțin o secundă între oprirea motoarelor și schimbarea direcției, cu scopul de a proteja
puntea H de scurt-circuit. Orice instrucțiune aflată după linia 14 va fi executată o dată la
o secundă.
45
6. Testare și validare
Verificarea RTCC s-a făcut folosind DMCI, un plug-in pentru NetBeans, folosind
ca și bază de timp un timer pe 32 de biți, alimentat direct de la ceasul sistemului la
80MHz. În întreruperea de la RTCC o dată la o secundă se citește timerul apoi acesta se
reinițializează. Curbele obțiute sunt relativ line, iar abaterea astfel calculată este de 2.25e-
6, ceea ce reprezintă o perioadă de 1.0000655 secunde pentru RTCC.
În figurile 6.1 și 6.2 se prezintă graficele pentru funcționarea extremă: alături de o
sursă de căldură sau folosind un ventilator îndreptat spre oscilator.
În figura 6.3 s-a analizat oscilatorul circuitului de timp real, model LF
XTAL002995, al firmei IQD, după ce a fost adăugat un condensator de 220pF, in paralel
celui exstent, C15 de pe placă. S-a obținut o frecvență apropiată de 32 768Hz.
46
Figura 6.2: Funcționarea cu o sursă de căldură
Figura 6.1: Functionarea cu o sursă răcire
Alături de analiza circuitului de RTCC s-au studiat și diferențele între valoarea
afișată pe tabletă și măsurată folosind osciloscopul, pentru divizorul de tensiune(figura
6.4) și pentru șunt(figura 6.5), diferențele fiind nesemnificative.
În figura 6.5 cu galben se prezintă tensiunea pe șunt după filtrarea folosind
condensatorul iar cu roșu se prezintă semnalul dacă nu se folosește filtrare. Frecvența
vârfurilor de consum este identică cu cea de la acționarea motoarelor cu semnalul
dreptunghiular al PWM.
47
Figura 6.3: Analiza folosind osciloscopul
Figura 6.4: Valoarea măsurată pe osciloscop(stânga) și 4* tensiunea măsurată de
placă(dreapta); pentru divizorul de tensiune
Ieșirea circuitului DAC a fost analizată folosind osciloscopul, fără a se obține
diferențe notabile între valoarea dorită și cea măsurată. De asemenea forma este una
liniară, fără fluctuații, potrivită astfel pentru a fi folosită ca și referință.
48
Figura 6.5: Valoarea măsurată pe șunt folosind osciloscopul(stânga) și tableta(dreapta)
Figura 6.6: Ieșirea circuitului de DAC
7. Manual de instalare și utilizare
7.1. Instalare
În continuare se vor explica modul în care se realizează montajul cablat asociat
microcontroller-ului și pașii necesari instalării aplicației.
7.1.1 Instalare hardware
Pentru a se putea realiza montajul sunt necesare: o placă de tip Cerebot32MX4, 4
rezistențe, 1 condensator, două motoare, suporturile necesare și cabluri pentru conectică.
Placa de tip Cerebot32MX4, poate fi schimbată ușor cu alt tip de placă, de la
Digilent care are microcontroller de tip PIC 32MX, fiind necesare doar mici modificări,
care pot fi ușor descoperite prin compararea celor două Reference Manual. În continuare
se va prezenta montajul.
Schema circuitului de măsurare a fost prezentată în figura 4.1. Ea cuprinde 4
rezistențe de 1 KΩ, legate în serie, și având rol de divizor de tensiune. Condensatorul are
rol de netezire a fluctuațiilor de tensiune, și are valoarea de 4700 μF, fiind produs de
SAMXON. Șuntul este unul improvizat, cu 4 borne, cîte două pentru plus respectiv minus
și are o rezistență de 0.8Ω măsurată cu puntea Wheatstone.
Deoarece acumulatorii actuali sunt de capacitate mică se recomandă alimentarea
plăcii prin alt circuit. Poate fi folosit în acest sens cablul USB folosit la programare sau o
sursă de la Digilent( care se conectează la 220V) sau 4 baterii de 1.5V . Pentru alimentarea
plăcii se vor folosi tensiuni cuprinse între 3.6V- 5V DC, iar consumul de curent este între
250mA și 300mA.
În figura 7.1 se prezintă schema generală de montaj. Nu se insistă exact pe pinii
unde se conectează perifericele, fiind evidențiat doar portul. Pentru a afla poziția pinilor
se va folosi tabelul 7.2. Firele de alimentare folosesc convenția: roșu la Vcc și negru la
GND. În cazul conectării circuitului de măsură la placă se folosesc 3 fire: negru, alb și
roșu. Pe această schemă se folosesc alte culori pentru a se deosebi de circuitul de
alimentare. Culoarea albastră folosită la conectarea motoarelor și pentru USB are rolul de
a arăta că este vorba despre o grupare de fire, respectiv despre o magistrală.
Tensiunea generată de DAC se conectează la ADC ca și referință externă prin
două fire: roșu și negru. Oscilatorul cu quartz, modelul LF XTAL002995 al firmei IQD,
din poziția X2 este reprezentat în zona centrală folosind o imagine default. Pentru
funcționarea corectă are nevoie de lipirea în paralel cu C15 a unui condensator de 220 pF.
În tabelul 7.1 se prezintă poziția jumper-ilor care influențează în mod direct
aplicația. Restul jumper-ilor pot fi în orice poziție, și nu vor influența funcționarea
normală. Se pot folosi în paralel cablul de programare/depanare conectat la PC și la J11 și
o alimentare de la o sursă externă pentru placă.
49
Figura 7.1: Schema generală de montaj
Jumper Poziție Explicații
J10 Ieșirea de la DAC
J12External powerAlimentare de la o sursă
externă prin J13, J14 sau J18
J16VBUSON Alimentare USB
JP5Conectat Detectare supra-curenți pe
USB
JP6HostSelecție între alimentarea
J17 și J15(micro USB, pe
verso-ul plăcii)
JPA- JPK3.3V Alimentare periferice la
3.25V
RestulPoziție default Nu influențează aplicația
Tabelul 7.1: Poziție jumpers
50
În tabelul 7.2 se prezintă modul de conectare al perifericelor la placă.
Pin PMod(periferic) Pin Cerebot Explicații
DAC GND( Negru) JK 07
Referința pentru ADC
DAC Referință( Roșu) JK 08
GND circuit măsură(Negru) JJ 02
Circuitul extern de măsură V oltaj șunt(Roșu) JJ 09
V oltaj divizor baterii(Alb) JJ 07
Motor 1 HB5 JD 01- JD06 Motoarele au nevoie de
sursă externă de alimentareMotor 2 HB5 JD 07- JD12
Tabletă Android J17 Conectare tabletă folosind
cablu USB
Tabelul 7.2: Legarea pinilor de la Cerebot la pinii perifericelor.
7.1.2 Instalare software
Pentru a se putea rula aplica ția de față este nevoie să fie instalat MPLAB X v1.10
sau o variantă mai nouă; instalarea Microchip Application Library nefiind necesară. Ca și
sitem de operare am folosit Windows 7, pe 64 de biți. Librăria de PIC C folosită are
versiunea 2.02.
Folosind opțiunea de Package care poate fi găsită printr-un click dreapta pe proiect
se va obține o copie portabilă a proiectului. Structura de directoare după dezarhivare
diferă de o structură clasică de proiect, deoarece toate dependențele sunt copiate local.
Pentru a se programa mucrocontroller-ul se vor executa pașii:
1.Se va dezarhiva codul sursă. Fie C:\Folder Test\Android App calea unde s-a
realizat dezarhivarea. În folderul Andoid App se va găsi directorul scr care conține
aplicația și toate dependențele.
2.Se va importa codul sursă aflat în C:\Folder Test\Android App\src\Android
Accessories\Basic Communication Demo [RMK] – v2\Firmware
3.Folosind click dreapta pe proiect și Properties se va verifica că în partea dreaptă
în zona Configuration tipul Device -ului este PIC32MX460F512L(figura 7.2,
marcajul #1). Se va verifica ca la Compiler Toolchains să fie selectat un C32(#2).
În partea stângă la Categories se verifică că PIC32MX460F512L_PIM este
activ(înconjurat cu [ și ])(#3). În caz contrar în partea de jos la Manage
configurations(#4) se va putea activa(#5).
4.Se vor salva modificările prin Ok (#6).
5.Se va programa microcontroller-ul folosind opțiunea Make and program device
main project.
Un prim indiciu asupra succesului programării îl constituie aprinderea lui LED2 la
un interval de 1 secundă.
51
Figura 7.2: Proprietățiile proiectului
În figura 7.3 se prezintă buid path; căile analizate pentru include sunt fie locale,
obținute în urma dezarhivării codului și aflate în C:\Folder Test\Android App ; sau sunt
fișiere aflate în sursele compilatorului C:\Program Files (x86)\Microchip\mplabc32.
7.2 Manualul utilizatorului
Utilizatorul va trebui să acționeze întrerupătorul SW1( pentru a permite alimentarea
microcontroller-ului) și să conecteze tableta folosind USB-ul.
52
Figura 7.3: Structura de directoare analizată pentru include
8. Concluzii
Această rubrică cuprinde 3 categorii, și anume: un rezumat, rezultatele
experimentale obținute și posibile dezvoltări/ îmbunătățiri.
8.1 Rezumat
Sistemul de față are principalele atribuții de a monitoriza consumul de curent din
circuit și de acționa motoarele. Interacțiunea cu utilizatorul se face printr-o tabletă,
folosind USB și protocolul Android Open Accessory.
Se folosesc două canale analogice pentru măsurarea tensiunii de pe șunt și de pe
divizorul de tensiune. Șuntul fiind de valoare cunoscută se va transforma căderea de
tensiune în curent. Ca și voltaj de referință se folosește ieșirea circuitului de DAC, care
este fixată la 1.5V.
Frecvențele minime ale motoarelor ce pot fi măsurate au valoarea de 30Hz. Se
măsoară turațiile efective, înainte de reductorul de 1:19. La turații mai mici decât limita
inferioară se constată oprirea motorului deoarece corespunde unui factor de umplere al
PWM sub pragul minim de funcționare.
Pentru calcularea consumului total în circuit se folosește un circuit de RTCC
pentru a salva o dată la o secundă valoriile măsurate care apoi vor fi trimise pe USB și
însumate pe tabletă. Principalele date care sosesc de la tabletă sunt cele referitoare la
factorul de umplere pentru PWM.
Pentru a se asigura independența și portabilitatea, înaintea conectării unui
dispozitiv pe USB se trimit parametrii microcontroller-ului: dimensiunea registrelor care
conțin rezultatele și frecvețele de lucru pentru ceasul sistem și cel al perifericelor precum
și factorii de divizare pentru semnalele de ceas folosite de periferice.
8.2 Rezultate obținute
În figura 8.1 se prezintă comportarea circuitului de RTCC în condiții normale,
măsurat folosind oscilatorul principal de 80MHz. Tehnica de măsurare folosită a fost
descrisă în capitolul 6, și se bazează pe acuratețea cristalui principal de pe placă. Abaterea
53
Figura 8.1: Valoare RTCC măsurată în condiții normale
frecvenței măsurată în acest nod este de 2.25ppm, valoare care se încadrează în limita
recomandată de producător de ±20ppm. Cu toate acestea valoriile obtinute pentru
intervalul de o secundă sunt în jurul valorii de 1.0000655sec. Frecvența diferită a
oscilatorului secundar este confirmată de figura 6.3 unde valoarea măsurată este de
32.89KHz. Funcționarea în prezența unei surse de căldură(figura 6.1) sau de răcire(6. 2)
produce efecte vizibile în ceea ce privește frecvența de funcționare, așa cum specifică și
producătorul. Aspectul uniform al graficelor, precum și eroarea mai mică de 10e-6,
recomandă folosirea RTCC ca și bază de timp pentru măsură.
Diferențele între valoriile de tensiune măsurate pe microcontroller-ul și osciloscop
sunt mai mici de ordinul 0,001, lucru vizibil în figurile 6.4, și 6.5. Pot să apară unele
diferențe momentane mai mari, în special la valoarea de pe șunt, care așa cum se observă
în imaginea 6.5 prezintă mici fluctuații. Pentru calibrare s-a folosit măsurarea unei
tensiuni stabile(ieșirea de la DAC) folosind ADC de pe placă; în acest caz erorile au fost
sub pragul de 0.001. Nu se recomandă folosirea depanării pentru a citi valoarea măsurată,
deoarece fiecare pas de depanare produce o fluctuație semnificativă în consumul de
curent. Pentru verificare se poate folosi trimierea valorii pe UART.
Funcția de calibrare a motoarelor obține diferențe între cele două turații ale
motoarelor de 6Hz, ceea ce reprezintă o abatere de 3% între motoare. O cauză posibilă o
reprezintă diferențele între cele două punți H, care nu reușesc să producă ieșiri identice
chiar dacă comenziile sunt identice( au fost conectate același PWM). Pentru compensare,
se folosește o buclă închisă de reglaj implementată în software-ul de pe tabletă. Motorul
care are turație mai mare va fi încetinit pentru a ajunge la turația celui lent.
Pentru testare s-a folosit un singur microcontroller și o singură tabletă, precum și
un singur circuit de măsură. Posibilitatea portării codului pentru placa de dezvoltare a fost
testată folosind un alt PC și urmărind pașii din secțiunea 7.1.2.
8.3 Posibile dezvoltări și îmbunătățiri ulterioare
O primă dezvoltare ar putea fi introducerea unui RTOS. Acest lucru aduce cu sine
o serie de probleme cum ar fi faptul că stiva de Android/USB trebuie adaptată pentru
mediul multi-tasking. Această problemă este tratată în Integrating Microchip Libraries
with a Real-Time Operating System , un application note oferit de Microchip. Avantajele
evidente sunt: o mai mare modularitate a codului și evidențierea mult mai clară a
dependențelor între module prin folosirea cozilor, sau a semafoarelor. În acest sens s-a
instalat pe placă FreeRTOS, și s-au rulat unele aplicații de test, fără a instala și librăria de
USB.
O altă dezvoltare legată tot de sistemele de operare o constituie instalarea unei
versiuni de Linux. Acest lucru face posibilă folosirea unui număr mare de programe
scrise în C pentru Linux și destinate inițial pentru PC, să fie folosite și aici. Instalarea
unui kernel clasic de Linux pe configurația actuală este aproape imposibilă, în principal
datorită memoriei limitate, așa cum se arată pe forumul [28], dar o versiune minimală de
Linux, adaptată pentru embedded, ar putea fi instalată.
Folosirea codului scris în asamblare constituie întotdeauna o îmbunătățire, însă
pentru aceasta este necesară o analiză amănunțită a codului, pentru a identifica zonele
care merită îmbunătățite. Se poate folosi în acest sens Legea lui Amdahl.
54
Ca și o altă îmbunătățire, o analiză mai amănunțită a codului ar putea duce la o
reducere a dimensiunii unor variabile, sau la eliminarea altora. De asemenea câmpurile
structurilor pot fi specificate de ce dimensiune să fie, împiedicând astfel să se mai facă
alinierea.
O altă îmbunătățire o constituie folosirea zonelor critice. În aplicație, în
configurația de față, nu se folosesc zone critice pentru a se accesa variabilele importante
pentru aplicație. Singurul mecanism de protecție fiind folosirea nivelelor multiple de
priorități pentru întreruperi, cu nivelul cel mai ridicat pentru RTCC. Cel mai simplu mod
de implementare a zonelor critice îl constituie dezactivarea generală a întreruperilor la
intrarea în ISR și reactivarea lor la ieșirea din handler.
Pentru a se reduce consumul de curent se poate folosi microcontroller-ul în modul
de sleep, atunci cînd nu sunt date de procesat. Pentru aceasta, ar trebui diminuată de
asemena și frecvența cu care se intră în întreruperile fiecărui modul, să fie cît mai
apropiată de 10 Hz, pentru a se realiza 10 măsurători între două transferuri pe magistrala
de USB.
Funcțiile a căror durată de execuție este mare, pot fi împărțite între mai multe stări
și la fiecare apel să se facă doar un pas din algoritm. Astfel se reduce durata apelului
funcției dar crește complexitatea în ceea ce privește înțelegerea codului și mentenanța lui.
Pentru a se îmbunății și mai mult timpul general de execuție și main loop-ul poate fi
divizat în stări și vor fi apelate anumite funcții doar dacă a fost un eveniment.
Ca și o îmbunătățire a codului, se poate schimba vizibilitatea variabilelor și se pot
semnala cele accesibile din mai multe surse cu volatile. De asemnea se pot folosi mult
mai intens pointeri. Respectarea unui standard industrial cum ar fi MISRA sau chiar
ANSI C ar fi binevenită.
Pentru creșterea modularității în anamblu se poate folosi un ecran tactil, care să ne
permită realizarea configurațiilor plăcii. Astfel se poate selecta numărul de motoare din
sistem, dacă se dorește conectarea la același Output Compare, ce resurse hardware se vor
folosi(în cazul în care se poate alege între două module similare), diverse frecvențe de
lucru. Acest lucru ar duce la o adaptare mult mai bună la cerințe, și o configurare rapidă
pentru un număr mare de aplicații.
55
Bibliografie
[1] "Android Serial Port", http://www.progsrp.moonfruit.com/#/android-
serial/4548697508.
[2] "Android G1 Serial to USB Cable", http://www.instructables.com/id/Android-G1-
Serial-Cable/.
[3] "Microbridge", http://code.google.com/p/microbridge/.
[4] "Amarino", 2009, http://www.amarino-toolkit.net/index.php/docs.html.
[5] "Tutorial: Android to Microcontroller via Bluetooth", 2011,
http://www.ryandebenham.com/?cat=14.
[6] "The Android Debug Bridge (ADB)", 2011,
http://www.androidauthority.com/about-android-debug-bridge-adb-21510/.
[7] "A closer look at Google’s open accessory development kit", 2011,
www.romfont.com/2011/05/11/a-closer-look-at-googles-open-accessory-development-
kit/.
[8] "Android Open Accessory Development Kit", 2011,
http://developer.android.com/guide/topics/usb/adk.html.
[9] "Microchip's Accessory Framework for Android(tm)", Microchip, 2011.
[10] "A Bright Idea: Android Open Accessories", 2011, http://android-
developers.blogspot.com/2011/05/bright-idea-android-open-accessories.html.
[11] "Google I/O 2011: Android Open Accessory API and Development Kit (ADK)",
2011, http://www.youtube.com/watch?v=s7szcpXf2rE.
[12] "Google I/O 2011: Keynote Day One ", 2011, http://www.youtube.com/watch?
feature=player_embedded&v=OxzucwjFEEs#!.
[13] "Harman to bring Android integration to cars… finally", 2011,
www.autoblog.com/2011/07/15/harman-to-bring-android-integration-to-cars-finally/.
[14] Nicklas Hochy, Kevin Zemmery, Bernd Wertherand Roland Y . Siegwart ,"Electric
Vehicle Travel Optimization—Customer Satisfaction Despite ResourceConstraints",
IEEE 2012 Intelligent Vehicles Symposium , Alcalá de Henares, 2012.
[15] Roland Siegwart, Illah R. Nourbakhsh ,"Introduction to Autonomous Mobile
Robots", MIT Press, Cambridge, 1st edition, 2004.
[16] "Timers", Microchip, 2011.
[17] "Output Compare", Microchip, 2011.
[18] "Input Capture", Microchip, 2011.
[19] "Inter-Integrated Circuit™ (I2C™)", Microchip, 2011.
[20] "10-bit Analog-to-Digital Converter (ADC)", Microchip, 2011.
[21] "Oscillators Reference Manual", Microchip, 2011.
[22] "Real-Time Clock and Calendar (RTCC)", Microchip, 2011.
[23] "Interrupts Reference Manual", Microchip, 2011.
[24] Zoltan Francisc Baruch ,"Sisteme de I/E", Editura Albastră, Cluj-Napoca, ediția 1,
2000.
[25] Baruch Zoltan Francisc ,"INTERFAȚA USB", UTCN, Cluj-Napoca, Lucrare de
laborator.
[26] "USB OTG and Embedded Host", Microchip, 2008.
[27] "USB Embedded Host Stack Programmer's Guide", Microchip, 2008.
[28] "PIC32 Linux / eCos Challenge", 2011,
http://www.microchip.com/forums/m293237.aspx .
56
Anexa 1.
/*
* File: HardwareProfile – PIC32MX460F512L PIM.h
* Description: Used to specify board settings
*/
#ifndef HARDWARE_PROFILE_PIC32MX460F512L_PIM_H
#define HARDWARE_PROFILE_PIC32MX460F512L_PIM_H
/** Board definition
***********************************************/
// These defintions will tell the main() function which board is
// currently selected. This will allow the application to add
// the correct configuration bits as wells use the correct
// initialization functions for the board. These defitions are
only
// required in the stack provided demos. They are not required in
// final application design.
#define DEMO_BOARD PIC32MX460F512L_PIM
#define EXPLORER_16
//#define RUN_AT_48MHZ
//#define RUN_AT_24MHZ
#define RUN_AT_60MHZ
// Various clock values
#if defined(RUN_AT_48MHZ)
#define GetSystemClock() 48000000UL
#define GetPeripheralClock() 48000000UL
#define GetInstructionClock() (GetSystemClock() / 2) ???
#elif defined(RUN_AT_24MHZ)
#define GetSystemClock() 24000000UL
#define GetPeripheralClock() 24000000UL
#define GetInstructionClock() (GetSystemClock() / 2) ???
#elif defined(RUN_AT_60MHZ)
#define GetSystemClock() 60000000UL
#define GetPeripheralClock() 60000000UL // Will be
divided down
#define GetInstructionClock() (GetSystemClock() / 2) ???
#else
#error Choose a speed
#endif
#define DEMO_BOARD_NAME_STRING "PIC32MX460F512L PIM"
/** Hardware use, the values aren't used in practice
****************/
#define InputCaptureChannel2
#define InputCaptureChannel3 3
#define ADC1 1
#define OutputCompare2 2
#define OutputCompare3 3
#define Timer2ForInputCapture 2
#define Timer3ForOutputCompare 3
#define RTCC1sec 1
#define I2CChannel1ForDAC 1
/** Interrupts level
***********************************************/
57
#define CaptureMotor1INTLevel IC_INT_PRIOR_4
#define CaptureMotor2INTLevel IC_INT_PRIOR_4
#define TimerForICINTLevel T1_INT_PRIOR_1
#define INTLevelADC ADC_INT_PRI_3
#define RTCCLevel INT_PRIORITY_LEVEL_7
/** I/O pin definitions
********************************************/
#define INPUT_PIN 1
#define OUTPUT_PIN 0
#endif //HARDWARE_PROFILE_PIC32MX460F512L_PIM_H
Anexa 2.
/*
* File: InputCap.h
* Description: Define public function; redefine used registers
*
* Created on March 6, 2012, 9:54 PM
*/
#include "HardwareProfile.h"
#ifndef InputCap_H
#define InputCap_H
#define sampleIC2Start 1
#define sampleIC2Stop 2
#define sampleIC3Start 3
#define sampleIC3Stop 4
#define TimerOVFLInCap 1
#define InCapOVFL 2
// – Capture Every rise edge
// – Enable capture interrupts
// – Use Timer 2 source
// – Interrupt on every edge of interest
#define ICsettings IC_EVERY_RISE_EDGE | IC_INT_1CAPTURE |
IC_TIMER2_SRC | IC_FEDGE_RISE | IC_ON
/**
* Will prepare the Input Capture for channel 2 and 3, using as the
time base Timer 2
* We use a prescaler for Timer 2, in order to get small
frequencies, around 30HZ
* We will use interrupts asociated with the input capture and the
timer
*/
void setUpInputCap();
#ifdef InputCaptureChannel2
#define CaptureMotor1PortEnable mPORTDSetPinsDigitalIn(1<<
9); // IC2 for the first motor
#define OpenCaptureMotor1 OpenCapture2
#define SetPriorityICMotor1 SetPriorityIntIC2
#define EnableINTICMotor1 EnableIntIC2
#define CaptureMotor1INTVector _INPUT_CAPTURE_2_VECTOR
#define CaptureMotor1INTBit INT_IC2
#define CaptureMotor1ReadCapture mIC2ReadCapture
#define CaptureMotor1ControlReg IC2CONbits
#endif
#if defined(InputCaptureChannel3)
58
#define CaptureMotor2PortEnable mPORTCSetPinsDigitalIn(1<<
1); // and IC3 for the second motor
#define OpenCaptureMotor2 OpenCapture3
#define SetPriorityICMotor2 SetPriorityIntIC3
#define EnableINTICMotor2 EnableIntIC3
#define CaptureMotor2INTVector _INPUT_CAPTURE_3_VECTOR
#define CaptureMotor2INTBit INT_IC3
#define CaptureMotor2ReadCapture mIC3ReadCapture
#define CaptureMotor2ControlReg IC3CONbits
#endif
#if defined(Timer2ForInputCapture)
#define OpenTimerForIC OpenTimer2
#define TimerForICConfigs T2_ON | T2_SOURCE_INT|
T2_PS_1_32
#define TimerForICSetPriority mT2SetIntPriority
#define TimerForICSetSubPriority mT2SetIntSubPriority
#define TimerForICINTEnable mT2IntEnable
#define TimerValueReg TMR2
#endif
#endif
Anexa 3.
/*
* File: InputCap.c
* Description: Contains the code to sample two channels
*/
#include <plib.h>
#include "InputCap.h"
#include "../HardwareProfile.h"
unsigned int bufferTemp;
/*@see main.c*/
extern unsigned int val1IC2;
extern unsigned int val1IC3;
extern BYTE nrImpulsuriIC2 ;
extern BYTE nrImpulsuriIC3 ;
extern BYTE nrEvents1, nrEvents2;
BYTE sampleICx= sampleIC2Start ;
void setUpInputCap(){
CaptureMotor1PortEnable ;
CaptureMotor2PortEnable ;
// Enable Input Capture Module 2
OpenCaptureMotor1 (ICsettings);
SetPriorityICMotor1 (CaptureMotor1INTLevel | IC_INT_SUB_PRIOR_3 ); //
Priority 4
EnableINTICMotor1 ;
// Enable Input Capture Module 3
OpenCaptureMotor2 (ICsettings);
SetPriorityICMotor2 (CaptureMotor1INTLevel | IC_INT_SUB_PRIOR_2 ); //
Priority 4
EnableINTICMotor2 ;
59
// We use timer 2 for measuring the input capture
OpenTimerForIC (TimerForICConfigs , 0xFFFF); // we use a prescaler of
32 to get freq over 30Hz
TimerForICSetPriority (TimerForICINTLevel ); // will have a freq of
3.75Mhz for a Periph clock of 60Mhz and T= 266.7 ns
TimerForICSetSubPriority (2);
TimerForICINTEnable (TRUE);
}
/**
* We use a state machine to figure out if it is time to read the value
or not
*/
#if CaptureMotor1INTLevel == IC_INT_PRIOR_4
void __ISR(CaptureMotor1INTVector , ipl4) InputCapture2Routine (void){
INTClearFlag (INT_IC2);
bufferTemp = CaptureMotor1ReadCapture ()& 0xFFFF;
nrImpulsuriIC2 ++; // will count no mather in what
state we are
if(sampleICx== sampleIC2Start ){
TimerValueReg = 0; // reset the timer
sampleICx = sampleIC2Stop; // go to next state
}else
if(sampleICx== sampleIC2Stop){
val1IC2 = bufferTemp;
TimerValueReg = 0; // reset the timer
sampleICx = sampleIC3Start ; // go to next state
}
if(CaptureMotor1ControlReg .ICOV)
val1IC2 = InCapOVFL;
}
#endif
/**
* We use a state machine to figure out if it is time to read the value
or not
*/
#if CaptureMotor2INTLevel == IC_INT_PRIOR_4
void __ISR(CaptureMotor2INTVector , ipl4) InputCapture3Routine (void){
INTClearFlag (CaptureMotor2INTBit );
bufferTemp = CaptureMotor2ReadCapture ()& 0xFFFF;
nrImpulsuriIC3 ++; // will count no mather in what
state we are
if(sampleICx== sampleIC3Start ){
TimerValueReg = 0; // reset the timer
sampleICx = sampleIC3Stop; // go to next state
}else
if(sampleICx== sampleIC3Stop){
val1IC3 = bufferTemp;
TimerValueReg = 0; // reset the timer
sampleICx = sampleIC2Start ; // go to next state
}
if(CaptureMotor2ControlReg .ICOV)
val1IC3 = InCapOVFL;
}
#endif
/**
60
* if I have overflow this means we don't have input on IC2 or IC3
*/
#if TimerForICINTLevel == T1_INT_PRIOR_1
void __ISR(_TIMER_2_VECTOR , ipl1) Timer2Routine(void){
INTClearFlag (INT_T2);
if((sampleICx== sampleIC2Start ) ||(sampleICx== sampleIC2Stop)){
sampleICx = sampleIC3Start ;
val1IC2 = TimerOVFLInCap ;
}else
if((sampleICx== sampleIC3Start ) ||(sampleICx==
sampleIC3Stop)){
sampleICx = sampleIC2Start ;
val1IC3 = TimerOVFLInCap ;
}
}
#endif
Anexa 4.
/*
* File: DAC.h
* Author: elcano
* Description: Defines the settings used by the DAC; enumerate the
public functions
* Created on March 6, 2012, 10:03 PM
*/
#ifndef DAC_H
#define DAC_H
// Clock Constants
#define I2C_CLOCK_FREQ 400000 //The I2C on the board
support a medium speed (100K or 400K bps),
// DAC Constants
#define DAC_I2C_BUS I2C1
#define DAC_ADDRESS 0x60 // 0b1100000 MPC4725
DAC
#define dutyCicleMaxDAC 0x0FFF
/**
* Writes the DAC register, but don't write the EEPROM
* It doesn't need the reead command to verify the RDY bit, because
we don't use EEPROM
*
* @param DACValue A 12 bit value [0- 4095] or [0x0000- 0x0FFF]
* @param PowerDownOption In general we use 0x0 for normal use
*/
void writeDACRegister (unsigned int DACValue, unsigned short
PowerDownOption);
/**
* I recomand to use a read command and verify the RDY bit
* See the previous description for the param
* @param DACValue
* @param PowerDownOption
*/
void writeDACRegisterAndEEPROM (unsigned int DACValue, unsigned int
PowerDownOption);
61
/**
* NEED REWORK, IT DOWSN'T WORK PROPERLY
* Read the setting[1 byte], content of the DAC Register[2 byte],
and then the content of the EEPROM [2 byte]
* @param i2cbyte
* @return
*/
BOOL readDACRegisterANDEEPROM (BYTE i2cbyte[5]);
/**
* Will set the DAC to the desired value
* @param valReg should be a value betwen 0 and 4095. At 4095 is
3.25V the output(the value of the board)
*/
void setUpDAC(unsigned short valReg);
#endif
Anexa 5.
/*
* File: DAC.c
* Author: elcano
* Descriptor: The code for the DAC
* Created on March 6, 2012, 10:03 PM
*/
#include <plib.h>
#include "DAC.h"
#include "HardwareProfile.h"
//
***********************************************************************
*****
//
***********************************************************************
*****
// Local Support Routines
//
***********************************************************************
*****
//
***********************************************************************
*****
/
***********************************************************************
********
Function:
BOOL StartTransfer( BOOL restart )
Summary:
Starts (or restarts) a transfer to/from the EEPROM.
Description:
This routine starts (or restarts) a transfer to/from the EEPROM,
waiting (in
a blocking loop) until the start (or re-start) condition has
completed.
62
Precondition:
The I2C module must have been initialized.
Parameters:
restart – If FALSE, send a "Start" condition
– If TRUE, send a "Restart" condition
Returns:
TRUE – If successful
FALSE – If a collision occured during Start signaling
Example:
<code>
StartTransfer(FALSE);
</code>
Remarks:
This is a blocking routine that waits for the bus to be idle and
the Start
(or Restart) signal to complete.
*********************************************************************
********/
BOOL StartTransfer ( BOOL restart )
{
I2C_STATUS status ;
// Send the Start (or Restart) signal
if(restart)
{
I2CRepeatStart (DAC_I2C_BUS);
}
else
{
// Wait for the bus to be idle, then start the transfer
while( !I2CBusIsIdle(DAC_I2C_BUS) );
if(I2CStart(DAC_I2C_BUS) != I2C_SUCCESS)
{
DBPRINTF ("Error: Bus collision during transfer Start\n" );
return FALSE;
}
}
// Wait for the signal to complete
do
{
status = I2CGetStatus(DAC_I2C_BUS);
} while ( !(status & I2C_START) );
return TRUE;
}
/
***********************************************************************
63
********
Function:
BOOL TransmitOneByte( BYTE data )
Summary:
This transmits one byte to the EEPROM.
Description:
This transmits one byte to the EEPROM, and reports errors for any
bus
collisions.
Precondition:
The transfer must have been previously started.
Parameters:
data – Data byte to transmit
Returns:
TRUE – Data was sent successfully
FALSE – A bus collision occured
Example:
<code>
TransmitOneByte(0xAA);
</code>
Remarks:
This is a blocking routine that waits for the transmission to
complete.
*********************************************************************
********/
BOOL TransmitOneByte ( BYTE data )
{
// Wait for the transmitter to be ready
while(!I2CTransmitterIsReady (DAC_I2C_BUS));
// Transmit the byte
if(I2CSendByte(DAC_I2C_BUS, data) == I2C_MASTER_BUS_COLLISION )
{
DBPRINTF ("Error: I2C Master Bus Collision\n" );
return FALSE;
}
// Wait for the transmission to finish
while(!I2CTransmissionHasCompleted (DAC_I2C_BUS));
return TRUE;
}
/
***********************************************************************
********
Function:
void StopTransfer( void )
64
Summary:
Stops a transfer to/from the EEPROM.
Description:
This routine Stops a transfer to/from the EEPROM, waiting (in a
blocking loop) until the Stop condition has completed.
Precondition:
The I2C module must have been initialized & a transfer started.
Parameters:
None.
Returns:
None.
Example:
<code>
StopTransfer();
</code>
Remarks:
This is a blocking routine that waits for the Stop signal to
complete.
*********************************************************************
********/
void StopTransfer( void )
{
I2C_STATUS status ;
// Send the Stop signal
I2CStop(DAC_I2C_BUS);
// Wait for the signal to complete
do
{
status = I2CGetStatus(DAC_I2C_BUS);
} while ( !(status & I2C_STOP) );
}
/**
* Writes the DAC register, but don't write the EEPROM
* It doesn't need the reead command to verify the RDY bit, because we
don't use EEPROM
*
* @param DACValue A 12 bit value [0- 4095] or [0x0000- 0x0FFF]
* @param PowerDownOption In general we use 0x0 for normal use
*/
void writeDACRegister (unsigned int DACValue, unsigned short
PowerDownOption){
BYTE i2cData [10];
I2C_7_BIT_ADDRESS SlaveAddress ;
int Index ;
int DataSz ;
65
BOOL Success = TRUE;
// Initialize the data buffer
I2C_FORMAT_7_BIT_ADDRESS (SlaveAddress, DAC_ADDRESS, I2C_WRITE);
i2cData[0] = SlaveAddress.byte;
i2cData[1] = 0x40| (PowerDownOption& ((1<< 1)| (1<< 2)));
// C2, C1, C0= 010 WriteDAC Register, and PD1, PD0= 00 for normal mode
of power down
i2cData[2] = (DACValue& 0x0FF0)>> 4; // DAC output high byte
i2cData[3] = (DACValue& 0x000F)<< 4; // DAC output low
byte (12 bits in total)
DataSz = 4;
// Start the transfer to write data to the EEPROM
if( !StartTransfer(FALSE) )
{
while(1);
}
// Transmit all data
Index = 0;
while( Success && (Index < DataSz) )
{
// Transmit a byte
if (TransmitOneByte(i2cData[Index]))
{
// Advance to the next byte
Index ++;
// Verify that the byte was acknowledged
if(!I2CByteWasAcknowledged (DAC_I2C_BUS))
{
DBPRINTF ("Error: Sent byte was not acknowledged\n" );
Success = FALSE;
}
}
else
{
Success = FALSE;
}
}
// End the transfer (hang here if an error occured)
StopTransfer ();
if(!Success)
{
while(1);
}
}
/**
* I recomand to use a read command and verify the RDY bit
* See the previous description for the param
* @param DACValue
* @param PowerDownOption
*/
void writeDACRegisterAndEEPROM (unsigned int DACValue, unsigned int
PowerDownOption){
66
BYTE i2cData [10];
I2C_7_BIT_ADDRESS SlaveAddress ;
int Index ;
int DataSz ;
BOOL Success = TRUE;
// Initialize the data buffer
I2C_FORMAT_7_BIT_ADDRESS (SlaveAddress, DAC_ADDRESS, I2C_WRITE);
i2cData[0] = SlaveAddress.byte;
i2cData[1] = 0x60| (PowerDownOption& ((1<< 1)| (1<< 2)));
// C2, C1, C0= 011 WriteDAC Register and EEPROM; and PD1, PD0= 00 for
normal mode of power down
i2cData[2] = (DACValue& 0x0FF0)>> 4; // DAC output high byte
i2cData[3] = (DACValue& 0x000F)<< 4; // DAC output low
byte (12 bits in total)
i2cData[4] = 0x60| (PowerDownOption& ((1<< 1)| (1<< 2)));
// C2, C1, C0= 011 WriteDAC Register and EEPROM; and PD1, PD0= 00 for
normal mode of power down
i2cData[5] = (DACValue& 0x0FF0)>> 4; // DAC output high byte
i2cData[6] = (DACValue& 0x000F)<< 4; // DAC output low
byte (12 bits in total)
DataSz = 7;
// Start the transfer to write data to the EEPROM
if( !StartTransfer(FALSE) )
{
while(1);
}
// Transmit all data
Index = 0;
while( Success && (Index < DataSz) )
{
// Transmit a byte
if (TransmitOneByte(i2cData[Index]))
{
// Advance to the next byte
Index ++;
// Verify that the byte was acknowledged
if(!I2CByteWasAcknowledged (DAC_I2C_BUS))
{
DBPRINTF ("Error: Sent byte was not acknowledged\n" );
Success = FALSE;
}
}
else
{
Success = FALSE;
}
}
// End the transfer (hang here if an error occured)
StopTransfer ();
if(!Success)
{
while(1);
67
}
}
BOOL readDACRegisterANDEEPROM (BYTE i2cbyte[5]){
BYTE i2cData [10];
I2C_7_BIT_ADDRESS SlaveAddress ;
int Index ;
int DataSz ;
BOOL Success = TRUE;
// Initialize the data buffer
I2C_FORMAT_7_BIT_ADDRESS (SlaveAddress, DAC_ADDRESS, I2C_READ);
i2cData[0] = SlaveAddress.byte;
DataSz = 1;
// Start the transfer to read the EEPROM.
if( !StartTransfer(FALSE) )
{
while(1);
}
// Address the EEPROM.
Index = 0;
while( Success & (Index < DataSz) )
{
// Transmit a byte
if (TransmitOneByte(i2cData[Index]))
{
// Advance to the next byte
Index ++;
}
else
{
Success = FALSE;
}
// Verify that the byte was acknowledged
if(!I2CByteWasAcknowledged (DAC_I2C_BUS))
{
DBPRINTF ("Error: Sent byte was not acknowledged\n" );
Success = FALSE;
}
}
// Read the DAC register
Index = 0;
while( Success & (Index < 5) )
{
if(I2CReceiverEnable (DAC_I2C_BUS, TRUE) == I2C_RECEIVE_OVERFLOW )
{
DBPRINTF ("Error: I2C Receive Overflow\n" );
Success = FALSE;
}
else
{
while(!I2CReceivedDataIsAvailable (DAC_I2C_BUS));
68
i2cbyte [Index] = I2CGetByte(DAC_I2C_BUS);
I2CAcknowledgeByte (DAC_I2C_BUS, TRUE);
Index ++;
}
}
// End the transfer (stop here if an error occured)
StopTransfer ();
if(!Success)
{
while(1);
}
if((i2cbyte[0]& (0x80)== 1))
return TRUE;
else
return FALSE;
}
void setUpDAC(unsigned short valReg){
UINT32 actualClock ;
// Initialize debug messages (when supported)
DBINIT();
#if defined(I2CChannel1ForDAC)
// Set the I2C baudrate
actualClock = I2CSetFrequency (DAC_I2C_BUS, GetPeripheralClock (),
I2C_CLOCK_FREQ);
if ( abs(actualClock-I2C_CLOCK_FREQ) > I2C_CLOCK_FREQ/10 )
{
DBPRINTF ("Error: I2C1 clock frequency (%u) error exceeds
10%%.\n", (unsigned)actualClock);
}
// Enable the I2C bus
I2CEnable (DAC_I2C_BUS, TRUE);
///////////////////////////////////////////////////////////////
//////
//////////////// Testarea
functiilor ///////////////////////////////
///////////////////////////////////////////////////////////////
//////
writeDACRegisterAndEEPROM (valReg, 0); //1.5V
I2CEnable (DAC_I2C_BUS, FALSE); // and now we will disable it
#endif
}
Anexa 6.
/*
* File: ADCsetUp.h
* Author: elcano
* Description: Defines the parameters used by the ADC; redefines the
used registers; enumerate the public functions
69
* Created on March 6, 2012, 8:46 PM
*/
#include "HardwareProfile.h"
#ifndef ADCsetUp_H
#define ADCsetUp_H
// define setup parameters for OpenADC10
// Turn module on | in idle mode | ouput in integer
| trigger mode auto | enable autosample
#define PARAM1 ADC_MODULE_ON | ADC_IDLE_CONTINUE| ADC_FORMAT_INTG
| ADC_CLK_AUTO | ADC_AUTO_SAMPLING_ON
// ADC ref external | disable offset test | enable
scan mode | perform 8 samples whith average | use dual buffers | do
not use alternate mode
#define PARAM2 ADC_VREF_EXT_EXT | ADC_OFFSET_CAL_DISABLE |
ADC_SCAN_ON | ADC_SAMPLES_PER_INT_8 | ADC_ALT_BUF_ON |
ADC_ALT_INPUT_OFF
// use ADC PB clock | set sample time | maxim=
127 (ADCS+ 1)*2
#define PARAM3 ADC_CONV_CLK_PB| ADC_SAMPLE_TIME_12|
ADC_CONV_CLK_32Tcy
// Scan #1 | Scan #2
#define PARAM4 ENABLE_AN4_ANA| ENABLE_AN8_ANA
// Skip all but Analog 4| Analog 8
#define PARAM5 SKIP_SCAN_AN0| SKIP_SCAN_AN1| SKIP_SCAN_AN2|
SKIP_SCAN_AN3| \
SKIP_SCAN_AN5| SKIP_SCAN_AN6| SKIP_SCAN_AN7| \
SKIP_SCAN_AN9| SKIP_SCAN_AN10| SKIP_SCAN_AN11|
SKIP_SCAN_AN12| SKIP_SCAN_AN13| SKIP_SCAN_AN14| SKIP_SCAN_AN15
/************************************ Methods signature
******************************************************/
/**
* Will prepare the ADC to work with interrupts
* Will use double buffers and will make 4 samples for each
channel, and will save the average
* Will use the internal counter for sampling
* Auto convert and auto sampling: in this mode will sample and
convert for the desired time and then will restart the process
*/
void setUpADC();
#if defined(ADC1)
#define CloseADC CloseADC10
#define SetChannelADC SetChanADC10
#define OpenConfigADC OpenADC10
#define ConfigureINTADC ConfigIntADC10
#define INTConfigADC ADC_INT_ON| INTLevelADC|
ADC_INT_SUB_PRI_2
#define EnableADC EnableADC10
#define ADCVecor _ADC_VECTOR
#define ADCClearINTFlag mAD1ClearIntFlag
#define ReadADCActiveBuffer ReadActiveBufferADC10
#define ReadADCStack ReadADC10
#endif
#endif
70
Anexa 7.
/*
* File: ADCsetUp.c
* Description: The code for the ADC
*/
#include "ADCsetUp.h"
#include "HardwareProfile – PIC32MX460F512L PIM.h"
#include <plib.h>
/**
* The method used to configure the ADC
*/
/*@see main.c for declaration*/
extern volatile unsigned int channel4, channel8;
unsigned int channel4mas1, channel4mas2, channel4mas3, channel4mas4;
unsigned int channel8mas1, channel8mas2, channel8mas3, channel8mas4;
void setUpADC(){
CloseADC();{ // ensure the ADC is off before setting the
configuration
// we use only MUX A, and AN1 as external ground
SetChannelADC ( ADC_CH0_NEG_SAMPLEA_AN1 );
OpenConfigADC ( PARAM1, PARAM2, PARAM3, PARAM4, PARAM5 ); //
configure ADC using parameter define above
ConfigureINTADC (INTConfigADC);
}EnableADC(); // Enable the ADC
}
/**
* The interrupt handler for ADC, we use a level 3 for interrupt
*/
#if INTLevelADC == ADC_INT_PRI_3
void __ISR(ADCVecor, ipl3) ADCHandler(void){
ADCClearINTFlag ();
unsigned int offset = 8 * ((~ReadADCActiveBuffer () &
0x01)); // determine which buffer is idle and create an offset
// GND Prima sonda Scan #1
Scan #2 Scan #3 Scan #4
//#define PARAM4 ENABLE_AN1_ANA | ENABLE_AN3_ANA|
ENABLE_AN8_ANA| ENABLE_AN9_ANA| ENABLE_AN14_ANA| ENABLE_AN15_ANA
channel4mas1 = ReadADCStack(offset);
channel8mas1 = ReadADCStack(offset+ 1);
channel4mas2 = ReadADCStack(offset+ 2);
channel8mas2 = ReadADCStack(offset+ 3);
channel4mas3 = ReadADCStack(offset+ 4);
channel8mas3 = ReadADCStack(offset+ 5);
channel4mas4 = ReadADCStack(offset+ 6);
channel8mas4 = ReadADCStack(offset+ 7);
channel4 = (channel4mas1+ channel4mas2+ channel4mas3+
channel4mas4)>> 2;
channel8 = (channel8mas1+ channel8mas2+ channel8mas3+
channel8mas4)>> 2;
}
#endif
Anexa 8.
/*
* File: PWMMotors.h
71
* Author: elcano
* Description: Enumerate the public function; redefines the used
registers
* Created on March 6, 2012, 9:13 PM
*/
#include "HardwareProfile.h"
#ifndef PWMMotors_H
#define PWMMotors_H
#define PWM_desired_freq 5000 // 5KHzs
/**
* It uses Timer 3 as time base and Output compare 2& 3 as PWM.
* Here we don't use interrupts, everything is done in hardware
*/
void setUpPWM();
/**
* A small function used to calculate the value witch will be save
in a register, based on the values of desired frequency, the prescaler,
and the peripheral clock
* @param desiredFreq
* @param prescaler
* @return
*/
unsigned int getPR_For_PWM(unsigned int desiredFreq, unsigned int
prescaler);
/**
* Used to set the direction of the motors
* Will set the direction in opossite, because the motors are
monted back to back
* @param direction 0 or 1
* @pre before the enable signal should be stopped(the PWM output)
*/
void setMotorsDirection (BYTE direction);
#define SetDutyMotor1 SetDCOC2PWM
#define SetDutyMotor2 SetDCOC3PWM
#if defined(OutputCompare3)
#if defined(OutputCompare3)
#define Motor1DirectionPin 6
#define Motor2DirectionPin 7
#define Motor1SpeedPWMPin 1
#define Motor2SpeedPWMPin 2
#define MotorsDirectionPort LATD
#define mPortMotorsDirectionSetDigitalOut(x)
mPORTDSetPinsDigitalOut(x) // here is the same port for direction and
for speed(PWM)
#define mPortMotorsSpeedPWMSetDirectionOut(x)
mPORTDSetPinsDigitalOut(x)
#define MotorsDirectionConfigPins()
mPortMotorsDirectionSetDigitalOut((1<< Motor1DirectionPin)|(1<<
Motor2DirectionPin)) // direction for motors
#define MotorSpeedPWMConfigPins()
mPortMotorsSpeedPWMSetDirectionOut((1<< Motor1SpeedPWMPin)| (1<<
Motor2SpeedPWMPin)) // speed of the motors //_RD1| _RD2 (PWM output)
#define OpenOCMotor1 OpenOC2
#define Motor1Configs OC_ON | OC_TIMER_MODE16 |
72
OC_TIMER3_SRC | OC_PWM_FAULT_PIN_DISABLE
#define OpenOCMotor2 OpenOC3
#define Motor2Configs OC_ON | OC_TIMER_MODE16 |
OC_TIMER3_SRC | OC_PWM_FAULT_PIN_DISABLE
#endif
#endif
#if defined(Timer3ForOutputCompare)
#define OpenTimerForMotors OpenTimer3
#define MotorConfigs T3_ON | T3_SOURCE_INT|
T3_PS_1_1
#endif
#endif
Anexa 9.
/*
* File: PWMMotors.c
* Description: The code for the PWM, in this case only the setUp
function
*/
#include <plib.h>
#include "PWMMotors.h"
#include "HardwareProfile.h"
unsigned int PWM_PRReg_FREQ, duty;
/*
* First we will set up the pins then the timer and the output compare
*/
void setUpPWM(){
MotorsDirectionConfigPins (); // direction for motors
MotorSpeedPWMConfigPins (); // speed of the motors //_RD1| _RD2
(PWM output)
setMotorsDirection (1);
PWM_PRReg_FREQ = getPR_For_PWM (PWM_desired_freq , 1);
OpenTimerForMotors (MotorConfigs, PWM_PRReg_FREQ ); // 16bit Sysnc
mode, pentru o frecventa de
OpenOCMotor1 (Motor1Configs, 0,0);//PWM motor 1
OpenOCMotor2 (Motor2Configs, 0,0);//PWM motor 2
}
unsigned int getPR_For_PWM (unsigned int desiredFreq, unsigned int
prescaler){
return GetPeripheralClock ()/(prescaler* desiredFreq);
};
inline __attribute__ ((always_inline)) void setMotorsDirection (BYTE
direction){
MotorsDirectionPort &= 0xFFFF^ ((1<< Motor1DirectionPin )|(1<<
Motor2DirectionPin ));
MotorsDirectionPort |= ((direction& 1)<< Motor1DirectionPin )| ((1<<
Motor2DirectionPin )^((direction& 1)<< Motor2DirectionPin ));
}
Anexa 10.
/*
* File: rtcc_1secINTR.h
* Author: elcano
* Description: Enumerate the public function; because it is only a
73
RTCC, we don't redefine the registers, only some macros.
* Created on March 26, 2012, 7:42 PM
*/
#include "HardwareProfile.h"
#ifndef rtcc_1secINTR_H
#define rtcc_1secINTR_H
/**
* Will set the internal RTCC to a frequency of 1Hz interrupt
generation
* We don't use date and time because here it is useless
* We use internal interrupt
*/
void setUpRTCC_1secINT ();
#ifdef RTCC1sec
#define LEDRTCCTest BIT_11
#define mSetLedRTCCOutDir()
{mPORTBSetPinsDigitalOut(LEDRTCCTest);}
#define mLedRTCCOn() {mPORTBSetBits(LEDRTCCTest);}
#define mLedRTCCOff() {mPORTBClearBits(LEDRTCCTest);}
#define mLedRTCCToggle() {mPORTBToggleBits(LEDRTCCTest);}
#define RTCC_IntrruptDisable() {RtccAlarmDisable();} //
{INTEnable(INT_RTCC, INT_DISABLED);}
#define RTCC_InterruptEnable() {RtccAlarmEnable();} //
{INTEnable(INT_RTCC, INT_ENABLED);} // it is call by default in
SetUpRtcc_1secINTR
#endif
#endif
Anexa 11.
/*
* File: rtcc_1secINTR.h
* Description: The code for the RTCC, for test it is used an onboard
LED
*/
#include <plib.h>
#include "rtcc_1secINTR.h"
#include "GenericTypeDefs.h"
#include "HardwareProfile.h"
#include "mainDef.h"
#include "InputCap.h"
extern BOOL rtcc_1secINTR ;
void setUpRTCC_1secINT (){
mSetLedRTCCOutDir (); // only to see on a PIN
the frequency
mLedRTCCOff ();
//RtccSetCalibration(-12); // if we need
calibration, could be set when the RTCC is turned off
RtccInit (); // init the RTCC
while(RtccGetClkStat()!= RTCC_CLK_ON);// wait for the SOSC to
74
be actually running and RTCC to have its clock source
// could wait here at
most 32ms
// set time and date (not necesary if we use only to generate 1
second interrupt)
// RtccOpen(0x17040000, 0x08100604, 0); // time is MSb: hour,
min, sec, rsvd. date is MSb: year, mon, mday, wday.
// please note that the
rsvd field has to be 0 in the time field!
RtccChimeEnable (); // rollover of the alarm when
it is decremented to 0, it jumps to 0xFF
RtccSetAlarmRptCount (255);// if we use "chime" we could
use here any number and stilwe wil get uninterrupted repeats
RtccSetAlarmRpt (RTCC_RPT_SEC);// one alarm every
second
// set the alarm (not necesary if we use only to generate 1
second interrupt)
// RtccSetAlarmTimeDate(0x17045000, 0x08100604);
RtccAlarmEnable (); // enable the alarm
INTSetVectorPriority (INT_RTCC_VECTOR, RTCCLevel);
// set the RTCC priority in the INT controller
INTSetVectorSubPriority (INT_RTCC_VECTOR,
INT_SUB_PRIORITY_LEVEL_1 );// set the RTCC sub-priority in the
INT controller
INTEnable (INT_RTCC, INT_ENABLED );
// enable the RTCC event interrupts in the INT controller.
mLedRTCCOn ();
}
/**
* Interrupt handler with Second register set
*/
#if RTCCLevel== INT_PRIORITY_LEVEL_7
void __ISR(_RTCC_VECTOR, ipl7SRS) RtccIsr(void)
{
// once we get in the RTCC ISR we have to clear the RTCC
int flag
INTClearFlag (INT_RTCC);
// will copy the values from interrupt(like IC, ADC) to the
variables whitch will be send over USB
// we don't verify if the values changes, we only asure
that we have a 1 second freq
SetInputCapture1 (GetFreqMotor1());
SetInputCapture2 (GetFreqMotor2());
SetVoltageBatt (GetChannel4Batt ());
SetVoltageSunt (GetChannel8Sunt ());
SetNrEventsMotor1 (GetNrImpulsuri1());
SetNrEventsMotor2 (GetNrImpulsuri2());
mLedRTCCToggle ();
rtcc_1secINTR = TRUE;
}
#endif
75
Anexa 12.
/*
* File: mainDef.h
* Author: elcano
* Description: Enumerate the variables used by several modules;
defines some macros used by several source files; defines the command
size
* Created on March 11, 2012, 10:23 PM
*/
#ifndef mainDef_H
#define mainDef_H
#include "ADCsetUp.h"
#include "InputCap.h"
#include "PWMMotors.h"
#include "DAC.h"
#include "rtcc_1secINTR.h"
#include "InputCap.h"
// will be declared in the main file
extern volatile unsigned int val1IC2;
extern volatile unsigned int val1IC3;
extern volatile BYTE nrImpulsuriIC2 ;
extern volatile BYTE nrImpulsuriIC3 ;
extern volatile BYTE nrEvents1 , nrEvents2;
extern volatile unsigned int inputCapture1;
extern volatile unsigned int inputCapture2;
extern volatile unsigned int voltageSunt;
extern volatile unsigned int voltageBatt;
extern volatile unsigned int channel4;
extern volatile unsigned int channel8;
//#define DEBUG_MODE 1
#define Command_getConfig_ADC_Size 3 // whitout the
command ID(name), only the data
#define Command_getConfig_Fuses_PWM_Size 8
#define Command_getConfig_InputCapture_Size 3
#define Command_getValue_Voltage_Size 4
#define Command_getValue_InputCapture_Size 6
#define Command_setValue_PWM_Size 4
#define Command_shutDown_Size 0
#define COMMAND_initialPWMConfig_Size 0
#define COMMAND_finishedPWMConfig_Size 0
#define Command_App_Disconnect_Size 0
#define Command_App_Connect_Size 0
#define COMMAND_getConfig_MotorsID_Size 2
#define COMMAND_setDirectionMotor_Size 1
#define MAX_COMMAND_DATA_Size 8 // whitout the
command ID only the data
#define DAC_1dot5V 1890 // the value
witch should be put in DAC register to have Vout= 1.5V at Vin= 3.25V
typedef enum _MotorChangeDirectionStates {
IDLE= 1, //IDLE= Changed
NeedToChange = 2,
76
ChangeNow = 3,
}MotorChangeDirectionStates ;
// some getters for values calculated in interrupts
#define GetFreqMotor1() val1IC2
#define GetFreqMotor2() val1IC3
#define GetChannel8Sunt() channel8
#define GetChannel4Batt() channel4
#define GetNrImpulsuri1() nrImpulsuriIC2
#define GetNrImpulsuri2() nrImpulsuriIC3
// some setters, for variables whitch will be send over USB
#define SetInputCapture1(value) {inputCapture1= value;}
#define SetInputCapture2(value) {inputCapture2= value;}
#define SetVoltageSunt(value) {voltageSunt= value;}
#define SetVoltageBatt(value) {voltageBatt= value;}
#define SetNrEventsMotor1(value){nrEvents1= value; value= 0;} //
read and after we make a reset
#define SetNrEventsMotor2(value){nrEvents2= value; value= 0;} //
read and after we make a reset
// some getters, used to send values on the USB
#define GetInputCapture1() inputCapture1
#define GetInputCapture2() inputCapture2
#define GetVoltageSunt() voltageSunt
#define GetVoltageBatt() voltageBatt
#define GetNrEventsMotor1() nrEvents1
#define GetNrEventsMotor2() nrEvents2
/**
* Will send the configuration to Android, using the commands of
the type: COMMAND_getConfig_xxxx
* Precondition: The USB must be set up before
* Postcondition: None, it have no side efects
*/
void sendConfigToAndroid ();
#endif
Anexa 13.
/
***********************************************************************
********
USB Android Accessory basic demo with accessory in host mode
The code for the application;
***********************************************************************
********/
// Include files
#include <stdio.h>
#include "USB/usb.h"
#include "USB/usb_host_android.h"
#include "Compiler.h"
77
#include "HardwareProfile.h"
#include "mainDef.h"
// If a maximum current rating hasn't been defined, then define 500mA
by default
#ifndef MAX_ALLOWED_CURRENT
#define MAX_ALLOWED_CURRENT (500) // Maximum
power we can supply in mA
#endif
// Define a debug error printing
#define DEBUG_ERROR(a) Nop(); Nop(); Nop();
// Configuration bits for the device. Please refer to the device
datasheet for each device
// to determine the correct configuration bit settings
#ifdef __C30__
#if defined(__PIC24FJ256GB110__)
_CONFIG2 (FNOSC_PRIPLL & POSCMOD_HS & PLL_96MHZ_ON & PLLDIV_DIV2)
// Primary HS OSC with PLL, USBPLL /2
_CONFIG1 (JTAGEN_OFF & FWDTEN_OFF & ICS_PGx2) // JTAG off,
watchdog timer off
#elif defined(__PIC24FJ64GB004__)
_CONFIG1 (WDTPS_PS1 & FWPSA_PR32 & WINDIS_OFF & FWDTEN_OFF &
ICS_PGx1 & GWRP_OFF & GCP_OFF & JTAGEN_OFF)
_CONFIG2 (POSCMOD_HS & I2C1SEL_PRI & IOL1WAY_OFF & OSCIOFNC_ON &
FCKSM_CSDCMD & FNOSC_PRIPLL & PLL96MHZ_ON & PLLDIV_DIV2 & IESO_ON)
_CONFIG3 (WPFP_WPFP0 & SOSCSEL_SOSC & WUTSEL_LEG & WPDIS_WPDIS &
WPCFG_WPCFGDIS & WPEND_WPENDMEM )
_CONFIG4 (DSWDTPS_DSWDTPS3 & DSWDTOSC_LPRC & RTCOSC_SOSC &
DSBOREN_OFF & DSWDTEN_OFF)
#elif defined(__PIC24FJ256GB106__)
_CONFIG1 ( JTAGEN_OFF & GCP_OFF & GWRP_OFF & COE_OFF &
FWDTEN_OFF & ICS_PGx2)
_CONFIG2 ( 0xF7FF & IESO_OFF & FCKSM_CSDCMD & OSCIOFNC_OFF &
POSCMOD_HS & FNOSC_PRIPLL & PLLDIV_DIV3 & IOL1WAY_ON)
#elif defined(__PIC24FJ256DA210__) || defined(__PIC24FJ256GB210__)
_CONFIG1 (FWDTEN_OFF & ICS_PGx2 & GWRP_OFF & GCP_OFF &
JTAGEN_OFF)
_CONFIG2 (POSCMOD_HS & IOL1WAY_ON & OSCIOFNC_ON & FCKSM_CSDCMD &
FNOSC_PRIPLL & PLL96MHZ_ON & PLLDIV_DIV2 & IESO_OFF)
#elif defined(IOIO)
_CONFIG1 (FWDTEN_OFF & ICS_PGx2 & GWRP_OFF & GCP_OFF &
JTAGEN_OFF)
_CONFIG2 (POSCMOD_NONE & IOL1WAY_ON & OSCIOFNC_ON & FCKSM_CSDCMD
& FNOSC_FRCPLL & PLL96MHZ_ON & PLLDIV_NODIV & IESO_OFF)
#elif defined(__dsPIC33EP512MU810__) || defined(PIC24EP512GU810_PIM)
_FOSCSEL (FNOSC_FRC);
_FOSC(FCKSM_CSECMD & OSCIOFNC_OFF & POSCMD_XT);
_FWDT(FWDTEN_OFF);
#endif
#elif defined( __PIC32MX__ )
#pragma config UPLLEN = ON // USB PLL Enabled
#pragma config FPLLMUL = MUL_15 // PLL Multiplier
#pragma config UPLLIDIV = DIV_2 // USB PLL Input Divider
#pragma config FPLLIDIV = DIV_2 // PLL Input Divider
#pragma config FPLLODIV = DIV_1 // PLL Output Divider
78
#pragma config FPBDIV = DIV_1 // Peripheral Clock divisor
#pragma config FWDTEN = OFF // Watchdog Timer
#pragma config WDTPS = PS1 // Watchdog Timer Postscale
//#pragma config FCKSM = CSDCMD // Clock Switching &
Fail Safe Clock Monitor
#pragma config OSCIOFNC = OFF // CLKO Enable
#pragma config POSCMOD = EC /** ANDROID: WE use
Discera "clock"*/
#pragma config IESO = OFF // Internal/External
Switch-over
#pragma config FSOSCEN = ON /** ANDROID: Secondary
Oscillator Enable for RTCC */
#pragma config FNOSC = PRIPLL // Oscillator Selection
#pragma config CP = OFF // Code Protect
#pragma config BWP = OFF // Boot Flash Write Protect
#pragma config PWP = OFF // Program Flash Write
Protect
#pragma config ICESEL = ICS_PGx2 // ICE/ICD Comm Channel
Select
#pragma config DEBUG = ON // Background Debugger
Enable
#else
#error Cannot define configuration bits.
#endif
// C30 and C32 Exception Handlers
// If your code gets here, you either tried to read or write
// a NULL pointer, or your application overflowed the stack
// by having too many local variables or parameters declared.
#if defined(__C30__)
void _ISR __attribute__ ((__no_auto_psv__)) _AddressError(void)
{
DEBUG_ERROR ("Address error" );
while(1){}
}
void _ISR __attribute__ ((__no_auto_psv__)) _StackError(void)
{
DEBUG_ERROR("Stack error");
while(1){}
}
#elif defined(__C32__)
void _general_exception_handler (unsigned cause, unsigned status)
{
unsigned long address = _CP0_GET_EPC();
DEBUG_ERROR("exception");
while(1){}
}
#endif
//Definitions of the various application commnands that can be sent
typedef enum _ACCESSORY_DEMO_COMMANDS
{
COMMAND_getConfig_ADC = 0x01,
COMMAND_getConfig_Fuses_PWM = 0x02,
79
COMMAND_getConfig_InputCapture = 0x03,
COMMAND_getValue_InputCapture = 0x04,
COMMAND_getValue_Voltage = 0x05,
COMMAND_setValue_PWM = 0x06,
COMMAND_shutDown = 0x07,
COMMAND_initialPWMConfig = 0x08, // used to calculate
the lower limit for PWM at the desired freq
COMMAND_finishedPWMConfig = 0x09,
COMMAND_getConfig_MotorsID = 0x0A,
COMMAND_setDirectionMotor = 0x0B,
COMMAND_APP_CONNECT = 0xFE,
COMMAND_APP_DISCONNECT = 0xFF
} ACCESSORY_DEMO_COMMANDS ;
//Creation of the data packet that is going to be sent. In this example
// there is just a command code and a one byte data.
typedef struct __attribute__((packed))
{
BYTE command ;
BYTE data[MAX_COMMAND_DATA_Size ];
}ACCESSORY_APP_PACKET ;
//Local prototypes
#if defined(__C32__)
static void InitPIC32(void);
#endif
//local variables
static BYTE read_buffer [64];
static BYTE write_buffer [64];
static BYTE adresaWrite = 0; // pointer to the next location where will
be put the command
static ACCESSORY_APP_PACKET outgoing_packet ;
static void* device_handle = NULL;
static BOOL device_attached = FALSE;
static char manufacturer[] = "Microchip Technology Inc." ;
static char model[] = "Basic Accessory Demo" ;
static char description[] = DEMO_BOARD_NAME_STRING ;
static char version[] = "2.0";
static char uri[] = "http://www.microchip.com/android" ;
static char serial[] = "N/A";
volatile unsigned int channel4, channel8;
volatile unsigned int val1IC2, val1IC3;
volatile unsigned int inputCapture1= 0xFFFF, inputCapture2= 0xFFFF;
volatile unsigned int voltageSunt= 0xFF, voltageBatt= 0xFF;
volatile BYTE nrImpulsuriIC2 = 0;
volatile BYTE nrImpulsuriIC3 = 0;
volatile BYTE nrEvents1=0, nrEvents2= 0;
BOOL rtcc_1secINTR = FALSE;
BOOL configsSendOK = FALSE; // if we could send the configs setting,
will be reset when the device is disconnected
BOOL initialPWMconfig = FALSE; // if it is true will ignore the 1 second
rate transmit, and will transmit when it have the data
MotorChangeDirectionStates needToChangeMotorDirection = IDLE;
BYTE desiredDirectionForMotors ;
ANDROID_ACCESSORY_INFORMATION myDeviceInfo =
{
80
manufacturer ,
sizeof(manufacturer),
model,
sizeof(model),
description,
sizeof(description),
version,
sizeof(version),
uri,
sizeof(uri),
serial,
sizeof(serial)
};
/
***********************************************************************
*****
Function:
int main(void)
Summary:
main function
Description:
main function
Precondition:
None
Parameters:
None
Return Values:
int – exit code for main function
Remarks:
None
*********************************************************************
******/
int main(void)
{
DWORD size = 0;
BYTE commandSize = 0x00; // because our commands have different
sizes
BOOL readyToRead = TRUE;
BOOL writeInProgress = FALSE;
BYTE errorCode ;
BYTE relativeAddressInCommandPacket = 0;
ACCESSORY_APP_PACKET * command_packet = NULL;
BOOL connected_to_app = FALSE;
BOOL need_to_disconnect_from_app = FALSE;
#if defined(__PIC32MX__)
InitPIC32 ();
#endif
81
#if defined(__dsPIC33EP512MU810__) || defined (__PIC24EP512GU810__)
// Configure the device PLL to obtain 60 MIPS operation. The crystal
// frequency is 8MHz. Divide 8MHz by 2, multiply by 60 and divide by
// 2. This results in Fosc of 120MHz. The CPU clock frequency is
// Fcy = Fosc/2 = 60MHz. Wait for the Primary PLL to lock and then
// configure the auxilliary PLL to provide 48MHz needed for USB
// Operation.
PLLFBD = 38; /* M = 60*/
CLKDIVbits.PLLPOST = 0;/* N1 = 2*/
CLKDIVbits.PLLPRE = 0;/* N2 = 2*/
OSCTUN = 0;
/*Initiate Clock Switch to Primary
*Oscillator with PLL (NOSC= 0x3)*/
__builtin_write_OSCCONH (0x03);
__builtin_write_OSCCONL (0x01);
while (OSCCONbits.COSC != 0x3);
// Configuring the auxiliary PLL, since the primary
// oscillator provides the source clock to the auxiliary
// PLL, the auxiliary oscillator is disabled. Note that
// the AUX PLL is enabled. The input 8MHz clock is divided
// by 2, multiplied by 24 and then divided by 2. Wait till
// the AUX PLL locks.
ACLKCON3 = 0x24C1;
ACLKDIV3 = 0x7;
ACLKCON3bits .ENAPLL = 1;
while(ACLKCON3bits.APLLCK != 1);
TRISBbits.TRISB5 = 0;
LATBbits.LATB5 = 1;
#endif
// initialization
setUpInputCap ();
//setUpDAC(DAC_1dot5V); // should be call only once because it
writes the value in EEPROM whitch isn't affected by a reset
setUpPWM();
setUpADC();
setUpRTCC_1secINT ();
INTEnableSystemMultiVectoredInt ();
INTEnableInterrupts ();
USBInitialize (0);
AndroidAppStart (&myDeviceInfo);
while(1)
{
//Keep the USB stack running
USBTasks ();
82
//If the device isn't attached yet,
if(device_attached == FALSE)
{
need_to_disconnect_from_app = FALSE;
connected_to_app = FALSE;
size = 0;
//Continue to the top of the while loop to start the check
over again.
continue;
}
//If the accessory is ready, then this is where we run all of
the demo code
if(readyToRead == TRUE)
{
errorCode = AndroidAppRead (device_handle,
(BYTE*)&read_buffer, (DWORD)sizeof(read_buffer));
//If the device is attached, then lets wait for a command
from the application
if( errorCode != USB_SUCCESS)
{
//Error
DEBUG_ERROR ("Error trying to start read" );
}
else
{
readyToRead = FALSE;
}
}
size = 0;
relativeAddressInCommandPacket = 0;
if(AndroidAppIsReadComplete (device_handle, &errorCode, &size) ==
TRUE)
{
//We've received a command over the USB from the Android
device.
if(errorCode == USB_SUCCESS)
{
//Maybe process the data here. Maybe process it
somewhere else.
command_packet = (ACCESSORY_APP_PACKET *)&read_buffer[0];
//readyToRead= TRUE; will be set after the message it
is bean digest, the size is zero
}
else
{
//Error
DEBUG_ERROR ("Error trying to complete read request" );
}
}
while(size > 0)
{
if(connected_to_app == FALSE)
83
{
if(command_packet->command == COMMAND_APP_CONNECT )
{
connected_to_app = TRUE;
need_to_disconnect_from_app = FALSE;
commandSize = Command_App_Connect_Size + 1;
//size-= Command_App_Connect_Size+ 1;// recalculate
the size, using the relative size of the command
//relativeAddressInCommandPacket+=
Command_App_Connect_Size+ 1;
}
}
else
{
if(configsSendOK== TRUE)
switch(command_packet->command)
{
case COMMAND_setValue_PWM :
if(sizeof(command_packet-> data)>=
Command_setValue_PWM_Size ){ // could be more than one
command
SetDutyMotor1 ((command_packet->
data[0]<< 8)| command_packet -> data[1]); // we make a 16 bit from two 8
bit
SetDutyMotor2 ((command_packet->
data[2]<< 8)| command_packet -> data[3]);
commandSize = Command_setValue_PWM_Size +
1;
if(initialPWMconfig == TRUE){
rtcc_1secINTR = TRUE; // will force
to transmit the data faster
SetInputCapture1 (GetFreqMotor1());
SetInputCapture2 (GetFreqMotor2());
SetVoltageBatt (GetChannel4Batt());
SetVoltageSunt (GetChannel8Sunt());
}
//size-= Command_setValue_PWM_Size+ 1;
//relativeAddressInCommandPacket+=
Command_setValue_PWM_Size+ 1;
}
break;
case COMMAND_initialPWMConfig :
if(sizeof(command_packet-> data)>=
COMMAND_initialPWMConfig_Size ){ // could be more than one
command
commandSize =
COMMAND_initialPWMConfig_Size + 1;
initialPWMconfig = TRUE;
RTCC_IntrruptDisable (); // we will
generate manual the sampling rate
setUpDAC (DAC_1dot5V); // here it is a
good place to initialize also the DAC to be sure it is ok the voltage
reference
}
break;
case COMMAND_finishedPWMConfig :
84
if(sizeof(command_packet-> data)>=
COMMAND_finishedPWMConfig_Size ){ // could be more than
one command
commandSize =
COMMAND_finishedPWMConfig_Size + 1;
initialPWMconfig = FALSE; // we are
finished with the "burst" mode
RTCC_InterruptEnable (); // the
sampling rate will be generate in auto mode at 1Hz
}
break;
case COMMAND_setDirectionMotor :
if(sizeof(command_packet-> data)>=
COMMAND_setDirectionMotor_Size ){ // could be more than
one command
commandSize =
COMMAND_setDirectionMotor_Size + 1;
SetDutyMotor1 (0); // before we stop the
PWMs, and then, after a second we change the sign
SetDutyMotor2 (0);
desiredDirectionForMotors =
command_packet->data[0];
needToChangeMotorDirection =
NeedToChange;
}
break;
case COMMAND_shutDown :
if(sizeof(command_packet-> data)>=
Command_shutDown_Size ){
/**
* ANDROID:
* The code to shut down the system
* For now we will shut down both motors
*/
SetDutyMotor1 (0);
SetDutyMotor2 (0);
commandSize = Command_shutDown_Size + 1;
//size-= Command_shutDown_Size+ 1;
//relativeAddressInCommandPacket+=
Command_shutDown_Size+ 1;
}
break;
case COMMAND_APP_DISCONNECT :
if(sizeof(command_packet-> data)>=
Command_App_Disconnect_Size ){
need_to_disconnect_from_app = TRUE;
commandSize =
Command_App_Disconnect_Size + 1;
//size-= Command_App_Disconnect_Size+ 1;
//relativeAddressInCommandPacket+=
Command_App_Disconnect_Size+ 1;
}
break;
default:
//Error, unknown command
85
DEBUG_ERROR ("Error: unknown command
received");
break;
}
}
// And move the pointer to the next packet (this works
because
// all command packets are 2 bytes. If variable packet
size
// then need to handle moving the pointer by the size of
the
// command type that arrived.
size -= commandSize;
relativeAddressInCommandPacket += commandSize;
if(size!= 0)
memcpy (command_packet,
&read_buffer[relativeAddressInCommandPacket ], size);
if(need_to_disconnect_from_app == TRUE)
{
break;
}
}
if(size == 0)
{
readyToRead = TRUE;
}
if(connected_to_app == FALSE)
{
//If the app hasn't told us to start sending data, let's
not do anything else.
continue;
}
// WE TRY TO SEND THE CONFIGS
// At first run we move in the buffer the configs(a local one);
here will be put the local buffer contain in the output buffer
// At second run we will verify if have been send with succes
the infos, and if so we will set a flag
// Because the flag eill be set only at the second pass, the
program counter won't go further, because of the continue statement
adresaWrite = 0;
if(configsSendOK== FALSE){
// If there is a write already in progress, we need to
check its status
if( writeInProgress == TRUE )
{
if(AndroidAppIsWriteComplete (device_handle, &errorCode,
&size) == TRUE)
{
writeInProgress = FALSE;
if(errorCode != USB_SUCCESS)
{
//Error
DEBUG_ERROR ("Error trying to complete write" );
}else
86
configsSendOK = TRUE;
}
}else{
sendConfigToAndroid (); // we will prepare the output
buffer whir all the configs
writeInProgress = TRUE;
errorCode = AndroidAppWrite (device_handle, write_buffer,
adresaWrite);
if( errorCode != USB_SUCCESS )
{
DEBUG_ERROR ("Error trying to send button update" );
}
}
}
if(configsSendOK== FALSE)
continue;
// if we are here after all the continue statemant maybe we
have smth to send, we will use a local buffer
// If there is a write already in progress, we need to check
its status
if( writeInProgress == TRUE )
{
if(AndroidAppIsWriteComplete (device_handle, &errorCode,
&size) == TRUE)
{
writeInProgress = FALSE;
if(need_to_disconnect_from_app == TRUE)
{
connected_to_app = FALSE;
need_to_disconnect_from_app = FALSE;
}
if(errorCode != USB_SUCCESS)
{
//Error
DEBUG_ERROR ("Error trying to complete write" );
}
}
}
if((need_to_disconnect_from_app == TRUE) && (writeInProgress ==
FALSE))
{
outgoing_packet .command = COMMAND_APP_DISCONNECT ;
//outgoing_packet.data = 0;
writeInProgress = TRUE;
errorCode = AndroidAppWrite (device_handle,
(BYTE*)&outgoing_packet, Command_App_Disconnect_Size + 1);
if( errorCode != USB_SUCCESS )
{
DEBUG_ERROR ("Error trying to send button update" );
}
}else
// it isn't the time to send smth, we will wait a little
87
more
if(rtcc_1secINTR== FALSE)
continue;
if(needToChangeMotorDirection == ChangeNow){
setMotorsDirection (desiredDirectionForMotors );
needToChangeMotorDirection = IDLE; // do not change the
order of the two ifs
}
if(needToChangeMotorDirection == NeedToChange){
needToChangeMotorDirection = ChangeNow; // we will wait
minimum 1 sec
}
// If we need up update the inputCapture on the Android device
and we aren't
// already busy in a write, then we can send the new button
data.
// if((inputCapNeedUpdate == TRUE)&& (writeInProgress== FALSE))
if(writeInProgress == FALSE)
{
outgoing_packet .command = COMMAND_getValue_InputCapture ;
outgoing_packet .data[0]= GetInputCapture1 () >> 8;
// we send the first value, followed by the second one
outgoing_packet .data[1]= GetInputCapture1 () & 0x00FF;
outgoing_packet .data[2]= GetInputCapture2 () >> 8;
outgoing_packet .data[3]= GetInputCapture2 () & 0x00FF;
outgoing_packet .data[4]= GetNrEventsMotor1 ()& 0x00FF;
outgoing_packet .data[5]= GetNrEventsMotor2 ()& 0x00FF;
memcpy (&write_buffer[adresaWrite], (BYTE*)&outgoing_packet,
Command_getValue_InputCapture_Size + 1);
adresaWrite += Command_getValue_InputCapture_Size + 1;
// writeInProgress = TRUE;
}
//If we need up update the pot status on the Android device and
we aren't
// already busy in a write, then we can send the new pot data.
if(writeInProgress == FALSE)
{
outgoing_packet .command = COMMAND_getValue_Voltage ;
outgoing_packet .data[0]= GetVoltageSunt() >> 8;
outgoing_packet .data[1]= GetVoltageSunt() & 0x00FF;
outgoing_packet .data[2]= GetVoltageBatt() >> 8;
outgoing_packet .data[3]= GetVoltageBatt() & 0x00FF;
memcpy (&write_buffer[adresaWrite], (BYTE*)&outgoing_packet,
Command_getValue_Voltage_Size + 1);
adresaWrite += Command_getValue_Voltage_Size + 1;
// writeInProgress = TRUE;
}
// at the end we try to write smth, could be locked by the need
of deconect from the app
if(writeInProgress== FALSE){
rtcc_1secINTR = FALSE; // will be set by the RTCC
88
writeInProgress = TRUE;
errorCode = AndroidAppWrite (device_handle, write_buffer,
adresaWrite);
if( errorCode != USB_SUCCESS )
{
DEBUG_ERROR ("Error trying to send pot update" );
}
}
} //while(1) main loop
}
/
***********************************************************************
*****
Function:
BOOL USB_ApplicationDataEventHandler( BYTE address, USB_EVENT
event, void *data, DWORD size )
Summary:
Handles USB data application events
Description:
Handles USB data application events
Precondition:
None
Parameters:
BYTE address – address of the device causing the event
USB_EVENT event – the event that has occurred
void* data – data associated with the event
DWORD size – the size of the data in the data field
Return Values:
BOOL – Return TRUE of the event was processed. Return FALSE if the
event
wasn't handled.
Remarks:
None
*********************************************************************
******/
BOOL USB_ApplicationDataEventHandler ( BYTE address, USB_EVENT event ,
void *data, DWORD size )
{
return FALSE;
}
/
***********************************************************************
*****
Function:
BOOL USB_ApplicationEventHandler( BYTE address, USB_EVENT event,
void *data, DWORD size )
Summary:
89
Handles USB application events
Description:
Handles USB application events
Precondition:
None
Parameters:
BYTE address – address of the device causing the event
USB_EVENT event – the event that has occurred
void* data – data associated with the event
DWORD size – the size of the data in the data field
Return Values:
BOOL – Return TRUE of the event was processed. Return FALSE if the
event
wasn't handled.
Remarks:
None
*********************************************************************
******/
BOOL USB_ApplicationEventHandler ( BYTE address, USB_EVENT event , void
*data, DWORD size )
{
switch( event )
{
case EVENT_VBUS_REQUEST_POWER :
// The data pointer points to a byte that represents the
amount of power
// requested in mA, divided by two. If the device wants
too much power,
// we reject it.
if (((USB_VBUS_POWER_EVENT_DATA *)data)->current <=
(MAX_ALLOWED_CURRENT / 2))
{
return TRUE;
}
else
{
DEBUG_ERROR ( "\r\n***** USB Error – device requires too
much current *****\r\n" );
}
break;
case EVENT_VBUS_RELEASE_POWER :
case EVENT_HUB_ATTACH :
case EVENT_UNSUPPORTED_DEVICE :
case EVENT_CANNOT_ENUMERATE :
case EVENT_CLIENT_INIT_ERROR :
case EVENT_OUT_OF_MEMORY :
case EVENT_UNSPECIFIED_ERROR : // This should never be
generated.
case EVENT_DETACH: // USB cable has been
detached (data: BYTE, address of device)
case EVENT_ANDROID_DETACH :
90
device_attached = FALSE;
configsSendOK = FALSE; // if another
device will be connected
return TRUE;
break;
// Android Specific events
case EVENT_ANDROID_ATTACH :
device_attached = TRUE;
device_handle = data;
return TRUE;
default :
break;
}
return FALSE;
}
/
***********************************************************************
*****
Function:
void InitPIC32(void)
Summary:
Initialize the PIC32 core to the correct modes and clock speeds
Also enables multi vector interrupt
Description:
Initialize the PIC32 core to the correct modes and clock speeds
Precondition:
Only runs on PIC32
Parameters:
None
Return Values:
None
Remarks:
None
*********************************************************************
******/
#if defined(__PIC32MX__)
static void InitPIC32(void)
{
int value;
#if defined(RUN_AT_60MHZ)
// Use OSCCON default
#else
OSCCONCLR = 0x38000000; //PLLODIV
#if defined(RUN_AT_48MHZ)
OSCCONSET = 0x08000000; //PLLODIV /2
#elif defined(RUN_AT_24MHZ)
91
OSCCONSET = 0x10000000; //PLLODIV /4
#else
#error Cannot set OSCCON
#endif
#endif
value = SYSTEMConfigWaitStatesAndPB ( GetSystemClock() );
// Enable the cache for the best performance
CheKseg0CacheOn ();
// INTEnableSystemMultiVectoredInt();
// will be set later
/*ANDROID: We let JTAG enable for debugging
*
*/
//DDPCONbits.JTAGEN = 0;
value = OSCCON;
while (!(value & 0x00000020))
{
value = OSCCON; // Wait for PLL lock to stabilize
}
// INTEnableInterrupts();
// will be set later
}
#endif
/**
* Will send the configuration to Android, using the commands of the
type: COMMAND_getConfig_xxxx
* Precondition: The USB must be set up before
* Postcondition: None, it have no side efects
* Return: Will return true if all the configs were send with succes,
and false if not
*/
void sendConfigToAndroid (){
BYTE sizeWhitoutIDCmd ;
BYTE index= 0; // folosit pentru parcurgerea tabloului de configs
BYTE nrOfTry = 1; // se va incerca de 1 ori sa se trimita
configurarile
BOOL allFlags = FALSE; // will be an AND of the flagsOkTransmit[]
BYTE configs [4][MAX_COMMAND_DATA_Size + 2]={ //Size+ 1 for the ID
and another one for the size
{COMMAND_getConfig_ADC , Command_getConfig_ADC_Size ,
10, (1<< 5)| ((5>> 8)& 0x1F), 5, 0, 0, 0, 0, 0}, // ID, dim
data, ADCsize, Vref intreg & Vref fractional parte dominanta, Vref
fractional
{COMMAND_getConfig_Fuses_PWM , Command_getConfig_Fuses_PWM_Size ,
80, 15, 2, 1, 1, 16, PWM_desired_freq /100, 1}, // ID, dim
data, Freq a cristalului de pe placa* 100KHz, FPLLMUL, FPLLDIV,
FPLLODIV, FPBDIV, OutComp reg size, OutComp freq *100Hz, T3_PS_1_1
{COMMAND_getConfig_InputCapture , Command_getConfig_InputCapture_Size ,
92
32, 16, 8, 0, 0, 0, 0, 0}, // ID, dim data, Prescaler
T2_PS_1_16, dim in biti a rezultatului, dim in bits of the number of
events
{COMMAND_getConfig_MotorsID , COMMAND_getConfig_MotorsID_Size ,
1, 2, 0, 0, 0, 0, 0, 0} // ID, id motor 1(with IC2 and OC2),
id motor 2(with IC3 and OC3)
};
while((nrOfTry)> 0&& (allFlags== FALSE)){
nrOfTry– ;
allFlags = TRUE; // if remain true it means that all configs
have been send with success
// we have to send 3 configs arrays, whitch means 3 commands
with data
// at every step we send the command+ data then we try to see
if there was completed the last write on Android
for(index= 0; index< 4; index++){
sizeWhitoutIDCmd = configs[index][1];
outgoing_packet .command = configs[index][0];
memcpy (outgoing_packet.data, &configs[index][2],
sizeWhitoutIDCmd );
memcpy (&write_buffer[adresaWrite], (BYTE*)&outgoing_packet,
sizeWhitoutIDCmd + 1);
adresaWrite += sizeWhitoutIDCmd + 1;
}
}
}
93
Copyright Notice
© Licențiada.org respectă drepturile de proprietate intelectuală și așteaptă ca toți utilizatorii să facă același lucru. Dacă consideri că un conținut de pe site încalcă drepturile tale de autor, te rugăm să trimiți o notificare DMCA.
Acest articol: Sistem de monitorizare și comandă pentru [627134] (ID: 627134)
Dacă considerați că acest conținut vă încalcă drepturile de autor, vă rugăm să depuneți o cerere pe pagina noastră Copyright Takedown.
