introducere in programarea microcontrollerelor
TRANSCRIPT
INTRODUCERE IN PROGRAMAREA MICROCONTROLLERELOR DIN SERIA ATMEL AVR
1. Resurse necesare Resursele necesare pentru studiul microcontrollerelor din seria Atmel AVR sunt prezentate in fig. 1.
Fig. 1 Resurse pentru studiul microcontrollerelor Atmel AVR Acest document prezinta detaliat fiecare din resursele de mai sus. 2. Modulul de dezvoltare cu microcontroller Schema bloc a modulelor cu care vom lucra este prezentata in fig. 2
MCUATMega16
Interfataseriala Rs232
Interfataseriala Rs485
Interfata deprogramare
Circuite declock si RESET
Sursa dealimentare
DRIVERE PELINIILE DEIESIRE
CIRCUITEPENTRUIZOLAREINTRARI
Fig. 2 Schema bloc a modulelor de dezvoltare
COMPUTERECHIPAT CU UNCOMPILATORPENTRULIMBAJUL C
PROGRAMATOR
MODUL DEDEZVOLTARE CUMICROCONTROLLER
2.1. Schema de principiu a modulului cu 2 relee
ATMega16
Y1
16MHZ SS
O0
O1
O2
O3
PB0/T01
PB1/T12
PB2/INT23
PB3/OC04
PB4/SS5
PB5/MOSI6
PB6/MISO7
PB7/SCL8
RESET9
VCC10
GND11
XT AL212
XT AL113
PD0/RXD14
PD1/TXD15
PD2/INT016
PD3/INT117
PD418
PD519
PD620
PA040
PA139
PA238
PA337
PA436
PA535
PA634
PA733
AREF32
GND31
AVCC30
PC729
PC628
PC527
PC426
PC325
PC224
PC123
PC022
PD721
U1
AN0
AN1
I7
I6
I5
VREF
VREF
XT AL2
XT AL1
RXD
MOSI
MISO
SCL
RESET
VCCC2
22p
C1
22p
VCC
R30
10K
TXD
O4
O5
PWM1
O6
PWM2
I4
I3
I2
I1
I0
O7
JP1
JUMPER
RESET
C23
2.2uF
Fig. 3 Schema de principiu a modulului de dezvoltare (MCU+clock+RESET)
1
3
5
7
9
2
4
6
8
10
J3
CON10A
BA
RX232
DTR2321
3
5
7
9
2
4
6
8
10
J1
CON10A
DCD232
TX232
Conector RS232
Conector RS485
RX
D2
RX
D
12
3 J2 CO
N3
B
RX
D1
VCC
R5
10K
D4
DE3
R1
RE2
A6
B7
U3
SN75176
TXD
PA7
RXD2
Interfata RS485
A
B
R6
10K
C1+1
V+2
C1-3
C2+4
C2-5
V-6
T2OUT7
R2IN8
R2OUT9
T2IN10
T1IN11
R1OUT12
R1IN13
T1OUT14
U2
MAX232
Interfata RS232
C3
22uF
C4
22uF
RX232
DCD232
TXD
RXD1
I0
TX232
DTR232
O7
C5
22uF
C6
22uF
VCC
JUMPER PENTRUSELECTIA RS232-RS485
Fig. 4 Interfetele seriale RS232/RS485
Fig. 5. Driverele pentru iesirile digitale si releele
VCC
VCC
12
13
A11
K12
K23
A24
A35
K36
K47
A48
E116
C115
C214
E213
E312
C311
C410
E49
U6
CNY74-4
I0
I1
XI0
XI1
R16
4K7
R17
4K7
R18
4K7
XI2
XI3
R19
4K7
I2
I3
VCC
VCC
14
15
XI0
XI1
XI2
XI3
1
2
3
4
J13
XI4
XI5
XI6
XI7
1
2
3
4
J14
VCC
VCC
16
17
A11
K12
K23
A24
A35
K36
K47
A48
E116
C115
C214
E213
E312
C311
C410
E49
U7
CNY74-4
I4
I5
XI4
R20
4K7
R21
4K7
XI5
XI6
XI7
R22
4K7
R23
4K7
I6
I7
VCC
VCC
18
R12G
19
R12H
XA0
XA1
AO0
AO1
1
2
3
4
J15
Fig. 6 Circuitele pentru izolarea intrarilor digitale
C0
NO0
NC0
NO1
1
2
3
4
5
6
J11NC0
K1
C0
REL0
REL1
REL2
REL3
REL4
REL5
I11
I22
I33
I44
I55
I66
I77
I88
GND9
KID10
O811
O712
O613
O514
O415
O316
O217
O118
U5
ULN2803S
O0
O1
O2
O3
O4
O5
O6
O7
Iesiri digitale
Vpp
REL6
REL7
VPP
REL0
NO0C1
NC1
REL21
2
3
4
5
6
J12
REL3
REL4
REL5
REL6
REL7
NC1
NO1
K2
REL1
C1
VPP
VCC
VCC
AO0
10
9
8
4
11
U9C
TL084
V-
R9
1K C17
2.2uF
AN0 PH0
3
2
1
4
11
U9A
TL084
V-
D3
DZ5V1
XA0
D4
DZ5V1
5
6
7
4
11
U9B
TL084
VCC
VCC
AO1
12
13
14
4
11
U9D
TL084
V-
R10
1K C18
2.2uF
AN1 PH1
V-
XA1
Intrari analogice Iesiri analogice (PWM)
Fig. 7 Circuitele pentru adaptarea intrarilor analogice si iesirile PWM Schema modulului cu 8 relee este prezentata integral in fig. 8 Diferente intre cele doua module: a. Modulul cu 8 relee nu mai foloseste intrari/iesiri analogice b. In schimb, sunt disponibile mai multe intrari digitale I8-I15, pe conectorul J21 c. Cablajul imprimat permite conectarea a pana la 8 relee direct pe placa. Nota Consultati foile de catalog ale circuitelor de interfata pentru a intelege bine functionarea intregului montaj
VP
PX
1 2 3 4J1
3
1 2 3 4J1
4
1 2 3 4J1
6
C0
NO
0
C1
NO
1
C2
NO
2
C3
NO
3
K1
RE
L2
C0
NO
0
R1
6
4K
7R
17
4K
7
R1
8
4K
7
RE
L0
RE
L1
RE
L2
D1
6
D1
7
D1
8
Vp
p
Vp
p
Vp
p
VC
C
VC
C
A1
1
K1
2
K2
3
A2
4
A3
5
K3
6
K4
7
A4
8
E1
16
C1
15
C2
14
E2
13
E3
12
C3
11
C4
10
E4
9
U11
CN
Y7
4-4
I0 I1
12
R3
5A
13
R3
5B
VR
EF
VR
EF
I5I6I7
XI0
R3
1
4K
7
R3
2
4K
7
I8 I9 I10
I11
I12
I13
I14
I15
XTA
L2
XTA
L1
RX
D
SS
MO
SI
MIS
O
RE
SE
T
O0
O1
O2
O3
VC
C
PB
0/T
01
PB
1/T
12
PB
2/I
NT
23
PB
3/O
C0
4
PB
4/S
S5
PB
5/M
OS
I6
PB
6/M
ISO
7
PB
7/S
CL
8
RE
SE
T9
VC
C1
0
GN
D11
XTA
L2
12
XTA
L1
13
PD
0/R
XD
14
PD
1/T
XD
15
PD
2/I
NT
01
6
PD
3/I
NT
11
7
PD
41
8
PD
51
9
PD
62
0
PA
04
0
PA
13
9
PA
23
8
PA
33
7
PA
43
6
PA
53
5
PA
63
4
PA
73
3
AR
EF
32
GN
D3
1
AV
CC
30
PC
72
9
PC
62
8
PC
52
7
PC
42
6
PC
32
5
PC
22
4
PC
12
3
PC
02
2
PD
72
1
U1
AT
90
S8
53
5
SC
K
Y1
8M
HZ
C1
22
p
C2
22
p
VC
C
JP
1
JU
MP
ER
R3
0
10
K
C2
3
2.2
uFRE
SE
T
TX
D
O4
O5
PW
M1
O6
PW
M2
I3 I2 I1 I0 O7
I4
XI1
XI2
XI3
R3
3
4K
7
R3
4
4K
7
I2 I3
14
R3
5C
15
R3
5D
D1
9
D2
0
D2
1
D2
2
Vp
p
Vp
p
Vp
p
Vp
p
VC
C
VC
C
R1
9
4K
7
R2
0
4K
7R
21
4K
7
R2
2
4K
7
RE
L3
RE
L4
RE
L5
RE
L6
K2
RE
L2
VP
P
VP
P
RE
L0
RE
L1
C1
NO
1
1 2 3 4J1
9
CO
N4
1 2 3 4J2
0
CO
N4
C4
NO
4
C5
NO
5
C6
NO
6
C7
NO
7
1 2 3 4J2
2
CO
N4
XI0
XI1
XI2
XI3
K3
RE
L2
VP
P
RE
L2
C2
NO
2
R2
3
4K
7
RE
L7
D2
3
Vp
p
RX
23
2
VC
C
I9 I11
I13
I15
RX
23
2
1 3 5 7 9
2 4 6 8
10
J1
CO
N1
0A
DC
D2
32
RX
D1
1 3 5 7 9
2 4 6 8
10
J2
1
CO
N1
0A
I8 I10
I12
I14
C1
+1
V+
2
C1
-3
C2
+4
C2
-5
V-
6
T2
OU
T7
R2
IN8
R2
OU
T9
T2
IN1
0
T1
IN11
R1
OU
T1
2R
1IN
13
T1
OU
T1
4
U2
MA
X2
32
C3
22
uF
JP
2
JU
MP
ER
DC
DI0
VC
C
C4
22
uF
C5
22
uF
C6
22
uF
DC
D2
32
TX
D
TX
23
2
DT
R2
32
TX
23
2
TX
D
RX
D2
RX
D
O7
O7
DC
DD
TR
23
2
D4
DE
3
R1
RE
2
A6
B7
U3
SN
75
17
6
A B
VC
C
R5
10
K
R6
10
K
1 3 5 7 9
2 4 6 8
10
J1
0
CO
N1
0A
RE
SE
T
PD
2-M
ISO
PD
3-M
OS
I
PD
4-S
CK
PD
5-S
S
SC
K
MIS
O
MO
SI
SS
0V
CC
SS
1
SS
2
SS
3
K4
RE
L2
K5
RE
L2
VP
P
RE
L3
C3
C4
NO
3
K6
RE
L2
VP
P
RE
L4
C5
NO
4
NO
5
SS
0
SS
1
SS
2
SS
3
1 3 5 7 9
2 4 6 8
10
J1
7
CO
N1
0A
SS
12
3 J2
CO
N3
B
RX
D2
1 3 5 7 9
2 4 6 8
10
J3
CO
N1
0A
AB
VP
P
RX
D1
VC
C
VC
CV
CC
C9
22
uF
C1
0
10
0n
C11
10
0n
C1
4
10
0u
F
12
D2
1N
40
01
VP
PX
VP
P
C2
1
C1
VC
C
C2
2
C1
Vp
p
VC
C
Vcc
R2
7
47
0
D7
I11
I22
I33
I44
I55
I66
I77
I88
GN
D9
KID
10
O8
11
O7
12
O6
13
O5
14
O4
15
O3
16
O2
17
O1
18
U5
UL
N2
80
3S
O0
O1
O2
O3
O4
O5
O6
O7
RE
L0
RE
L1
RE
L2
RE
L3
RE
L4
RE
L5
RE
L6
RE
L7
Vp
p
K7
RE
L2
VP
P
VP
P
RE
L5
RE
L6
C6
NO
6
K8
RE
L2
VP
P
RE
L7
C7
NO
7
VR
EF
VC
C
R3
10
0
C1
9
2.2
uF
IN1
G N D
2
OU
T3
U1
0
UA
78
05
VP
P
Fig. 8 Schema completa a modulului cu 8 relee
3. Programatorul ISP Memoria de program a microcontrollerului ATMega16 este de tip flash si se poate programa “in system” (ISP) cu ajutorul unor interfete simple. Vom folosi cea mai simpla interfata de programare, denumita in literatura “Kanda systems”, sau STK200. Schema de principiu a interfetei este prezentata in fig. 9.
Fig. 9 Scheme interfetei de programare Kanda Systems Denumirea Kanda Systems vine de la firma care a comercializat-o initial. Se conecteaza prin J1 la portul paralel al computerului, iar la J2 cu modulul de dezvoltare echipat cu microcontroller. Comunicatia cu microcontrollerul este de fapt seriala. Alimentarea circuitului 74HC244 se face din sursa modulului de dezvoltare prin conectorul J2. Evident ca pe computerul la care se conecteaza interfata trebuie sa existe un soft special care sa controleze dialogul cu microcontrollerul pentru programare. In cazul de fata, compilatorul HP Infotech cu care lucram include un modul de programare.
4. Compilatorul CodeVisionAVR produs de HP INFOTECH Este unul din cele mai bune produse de acest gen disponibil pe piata. Principalul sau avantaj consta in existenta unui asa-numit “CodeWizzard”, care automatizeaza procedura de creare a unui nou proiect si genereaza automat codul pentru initializarea diverselor subsisteme ale microcontrollerului. Utilizatorului ii ramane doar sarcina de a scrie programul principal. In acest paragraf, este prezentata pas cu pas secventa de creare a unui proiect, prin care se comanda iesirea digitala PB0 alternativ ON/OFF cu durata de 0.5 secunde. Pasul 1. Se lanseaza CVAVR Pasul 2. Din menu-ul principal se selecteaza optiunea File, New Programul prezinta urmatoarea fereastra de dialog:
Fig. 10 Secventa de dialog CVAVR Se selecteaza Project si se apasa butonul OK. Pasul 3. CVAVR prezinta fereastra de dialog din fig. 11
Fig. 11 Dialog CVAVR Se raspunde cu Yes pentru lansarea utilitarului CodeWizard Pasul 4. Se selecteaza tipul de microcontroller cu care se lucreaza – in cazul de fata ATMega16 si frecventa oscilatorului (data de cristalul de quartz care echipeaza modulul – in cazul de fata 16MHz). (vezi fig. 12)
Fig. 12 Dialog CodeWizardAVR Pasul 5. Configurarea resurselor folosite in proiect. Singura resursa folosita in exemplul considerat este Port B bit 0. Se selecteaza Tab-ul Ports. Se obtine fereastra de dialog din figura 13.
Fig. 13 Dialog CodeWizardAVR
In aplicatia noastra, din toate resursele microcontrollerului folosim doar o linie din portul B, pe care dorim s-o configuram ca linie de iesire. In consecinta, vom selecta Tab-ul marcat Port B, apoi vom face click pe Bit 0. Se obtine fereasta din figura 14.
Fig. 14. Configurarea Port B bit 0 ca linie de iesire cu CodeWizardAVR Pasul 6. Generarea efectiva a proiectului Se alege din menu-ul CodeWizardAVR optiunea File, si apoi Generate Save and Exit. In continuare programul prezinta o fereastra de dialog in care se solicita numele dorit pentru fisierele sursa (.C) project (.prj) si CodeWizardProject (.cwp) (vezi fig. 15). Pentru usurinta gestionarii proiectelor, este recomandabil sa salvati fiecare proiect nou intr-un folder distinct, de exemplu C:\CVAVR\WORK\LED1 Puteti selecta acelasi nume (led1) pentru fisierele sursa (.c) si project (.prj, .cwp). Dupa salvarea proiectului nou generat, se paraseste utilitarul CodeWizardAVR si se intra in editorul CodeVisionAVR, afisandu-se in partea dreapta programul sursa generat de Wizard. Acesta contine chiar o serie de comentarii si indicatii de genul “Place you global variables here”, “Place your code here”, etc., care sunt utile la primul contact cu mediul CodeVisionAVR.
Fig. 15 Dialogul pentru salvarea proiectului Pasul 7. Editarea fisierelor generate automat de CodeWizardAVR Iata listing-ul complet al programului generat automat: /************************************************** *** This program was produced by the CodeWizardAVR V1.25.5 Standard Automatic Program Generator © Copyright 1998-2007 Pavel Haiduc, HP InfoTech s.r .l. http://www.hpinfotech.com Project : Version : Date : 3/14/2008 Author : Y406 Company : Ugal. Comments: Chip type : ATmega16 Program type : Application Clock frequency : 16.000000 MHz Memory model : Small External SRAM size : 0 Data Stack size : 256 *************************************************** **/ #include <mega16.h> // Declare your global variables here void main(void) { // Declare your local variables here // Input/Output Ports initialization // Port A initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Fun c2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T Sta te2=T State1=T State0=T PORTA=0x00; DDRA=0x00; // Port B initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Fun c2=In Func1=In Func0=Out
// State7=T State6=T State5=T State4=T State3=T Sta te2=T State1=T State0=0 PORTB=0x00; DDRB=0x01; // Port C initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Fun c2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T Sta te2=T State1=T State0=T PORTC=0x00; DDRC=0x00; // Port D initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Fun c2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T Sta te2=T State1=T State0=T PORTD=0x00; DDRD=0x00; // Timer/Counter 0 initialization // Clock source: System Clock // Clock value: Timer 0 Stopped // Mode: Normal top=FFh // OC0 output: Disconnected TCCR0=0x00; TCNT0=0x00; OCR0=0x00; // Timer/Counter 1 initialization // Clock source: System Clock // Clock value: Timer 1 Stopped // Mode: Normal top=FFFFh // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off // Input Capture on Falling Edge // Timer 1 Overflow Interrupt: Off // Input Capture Interrupt: Off // Compare A Match Interrupt: Off // Compare B Match Interrupt: Off TCCR1A=0x00; TCCR1B=0x00; TCNT1H=0x00; TCNT1L=0x00; ICR1H=0x00; ICR1L=0x00; OCR1AH=0x00; OCR1AL=0x00; OCR1BH=0x00; OCR1BL=0x00; // Timer/Counter 2 initialization // Clock source: System Clock // Clock value: Timer 2 Stopped // Mode: Normal top=FFh // OC2 output: Disconnected ASSR=0x00; TCCR2=0x00; TCNT2=0x00; OCR2=0x00; // External Interrupt(s) initialization // INT0: Off // INT1: Off // INT2: Off MCUCR=0x00; MCUCSR=0x00; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x00; // Analog Comparator initialization // Analog Comparator: Off // Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80; SFIOR=0x00; while (1) { // Place your code here }; }
Observati urmatoarele elemente: a. #include <mega16.h> - este header-ul care contine definitiile resurselor
corespunzator microcontrollerului selectat. b. In functia main() se incepe cu o serie de initializari din care remarcam
initializarea registrului de directie asociat cu Port B, DDRB cu valoarea 0x01, ceea ce este echivalent cu configurarea Port B bit 0 ca linie de iesire.
c. Dupa initializari, programul continua cu un while (1) – o bucla infinita unde trebuie plasat codul aplicatiei propriu-zise.
Editarea sursei generate automat are in vedere urmatoarele aspecte: a. O modificare de ordin “cosmetic” – destinata sa faca programul mai usor de
citit, consta in eliminarea comentariilor inutile si plasareaq tuturor initializarilor intr-o functie init(), care va fi apelata la inceputul main().
b. Nu uitati sa includeti propriile comentarii care sa va ajute sa rememorati scopul proiectului si eventualele amanunte demne de retinut in legatura cu el.
c. Alegerea si includerea fisierelor header necesare aplicatiei (daca este cazul) d. Scrierea functiei main corespunzator cerintelor aplicatiei Iata cum arata programul dupa editare: /* Un prim test cu microcontrollerul ATMega16 Se comanda comutarea alternativa ON/OFF la interval e de 0.5 sec a unui LED conectat la Portb bit 0. Constantele de timp folosesc functiile predefinite in delay.h */ #include <mega16.h> #include <delay.h> // Declare your global variables here void init(void); void main(void) { // Declare your local variables here init();
while (1) { // Place your code here PORTB.0=1; delay_ms(500); PORTB.0=0; delay_ms(500); }; } void init(void) { // Input/Output Ports initialization // Port A initialization PORTA=0x00; DDRA=0x00; // Port B initialization PORTB=0x00; DDRB=0x01; // Port C initialization PORTC=0x00; DDRC=0x00; // Port D initialization PORTD=0x00; DDRD=0x00; // Timer/Counter 0 initialization // Clock source: System Clock // Clock value: Timer 0 Stopped // Mode: Normal top=FFh // OC0 output: Disconnected TCCR0=0x00; TCNT0=0x00; OCR0=0x00; // Timer/Counter 1 initialization // Clock source: System Clock // Clock value: Timer 1 Stopped // Mode: Normal top=FFFFh // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off // Input Capture on Falling Edge // Timer 1 Overflow Interrupt: Off // Input Capture Interrupt: Off // Compare A Match Interrupt: Off // Compare B Match Interrupt: Off TCCR1A=0x00; TCCR1B=0x00; TCNT1H=0x00; TCNT1L=0x00; ICR1H=0x00; ICR1L=0x00; OCR1AH=0x00; OCR1AL=0x00; OCR1BH=0x00; OCR1BL=0x00; // Timer/Counter 2 initialization // Clock source: System Clock
// Clock value: Timer 2 Stopped // Mode: Normal top=FFh // OC2 output: Disconnected ASSR=0x00; TCCR2=0x00; TCNT2=0x00; OCR2=0x00; // External Interrupt(s) initialization // INT0: Off // INT1: Off // INT2: Off MCUCR=0x00; MCUCSR=0x00; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x00; // Analog Comparator initialization // Analog Comparator: Off // Analog Comparator Input Capture by Timer/Counter 1: Off ACSR=0x80; SFIOR=0x00; }
Pasul 8 – Compilarea programului si generarea codului executabil. Din menu-ul principal se selecteaza Project apoi Make. Daca la compilare se detecteaza erori, acestea sunt semnalate. Pasul 9. Transferul programului executabil in memoria microcontrollerului In urma compilarii, CVAVR genereaza un fisier cu extensia .rom care contine codul executabil al aplicatiei. Inainte de a lansa utilitarul de programare, asigurati-va ca a fost selectat programatorul corect (Kanda systems). In acest scop, selectati din menu-ul principal optiunea Settings si apoi, Programmer si specificati tipul de programator si portul paralel la care acesta este conectat. (LPT1:)
Fig. 16 Selectia tipului de programator
Se conecteaza programatorul la conectorul J10 si se alimenteaza modulul de dezvoltare ca in figura 17. Asigurati-va ca programatorul este conectat si la portul paralel al computerului.
Conector programator
V+ GND Fig. 17 Conectarea programatorului si a tensiunii de alimentare Nota: Alimentarea modulului se face de la un alimentator stabilizat de 12V dc, cu polaritatea indicata in figura 17. Curentul necesar este de circa 100mA. Programatorul se alimenteaza pe cablul de conectare la modulul cu microcontroller din sursa stabilizata de 5V a acestuia, deci nu mai e necesara o sursa suplimentara pentru programator. Lansarea modulului soft de programare se face alegand din menu-ul principal optiunea Tools si apoi Chip Programmer, sau direct actionand butonul marcat cu un chip ca in figura 18.
LANSARE PROGRAMATOR
Fig. 18 Lansarea modulului soft de programare
Fereastra de dialog a programatorului este prezentata in figura 19.
Fig. 19 Fereastra de dialog a programatorului Programarea efectiva a chipului consta in urmatoarele etape: a. Stergerea memoriei flash. Se face selectand din menu optiunea Program - >
Erase chip. b. Incarcarea fisierului .rom in bufferul programatorului. Se face alegand din
menu optiunea File ->Load Flash c. Transferul continutului bufferului in memoria flash a microcontrollerului. Se
face alegand din menu optiunea Program - > FLASH. Transferul dureaza cateva secunde in functie de lungimea programului, dupa care programul este automat lansat in executie. Daca toate etapele de mai sus au fost parcurse corect, LED-ul conectat la PortB bit 0 incepe sa clipeasca cu frecventa de 1Hz. Precautii importante 1. Inainte de a face orice operatie cu programatorul, asigurati-va ca check-box-
ul “program fuse bits este sters (nemarcat). Programarea la intamplare a fuse-bits conduce aproape intotdeauna la “defectarea” microcontrollerului – in sensul ca puteti dezactiva viitoarele programari, sau puteti configura clock-ul altfel decat cu oscilatorul cu cristal de quartz existent pe placa. In orice situatie, aceste configurari la intamplare fac inutilizabil circuitul.
2. Nu folositi NICIODATA butonul Program All!
Exercitiu: Modificati exemplul de mai sus in asa fel incat LED-ul de pe PORTB bit 0 sa comute la intervale de o secunda, iar LED-ul de pe PORTB bit 1 sa comute la intervale de 500ms. Indicatie: - se modifica functia init, in asa fel incat sa se configureze bit 0 si bit 1 ai PortB
ca linii de iesire. - Se modifica main() in asa fel incat sa se comande si PortB bit 1 Introducere in programarea microcontrollerelor din seria Atmel AVR Partea a II-a II.1 Exemplu de folosire a liniilor de intrare iesire digitale. Comanda unui motor pas cu pas. Tema: Sa se scrie un program care sa comande un motor pas cu pas in sens de rotatie direct daca intrarea portului C bitul 0 este in 1 si in sens invers daca intrarea portului C bitul zero este 0. Motorul se conecteaza prin driverul ULN2803 la liniile PORTD bitii 2,3 5, si 7. Solutie: Iata listing-ul programului care corespunde cerintelor de mai sus: #include <mega16.h> #include <delay.h> // Declare your global variables here unsigned char STEPM[4]={0x04,0x08,0x20,0x80}; int current_position; void step(int i); void init(void); void main(void) { init(); while (1) { step(current_position); delay_ms(3); }; } /* Rutina step executa un pas inainte sau inapoi in fu nctie de starea liniei de intrare 0 a portului C
*/ void step(int offset) { if(PINC.1==1) { PORTB.0=1; //semnalizare pe LED PORTD=STEPM[offset]; offset++; if(offset==4) offset=0; current_position=offset; } if(PINC.1==0) { PORTB.0=0; //semnalizare pe LED PORTD=STEPM[offset]; offset--; if(offset<0) offset=3; current_position=offset; } }
Observatii: 1. Listingul de mai sus nu contine si initializarile. Asigurati-va ca in rutina de
initializari, ati configurat PORTD in mod output (DDRD=0xFF) si PORTB.1 de asemenea in mod output.
2. Important! Cand se citeste un port de intrare folositi PINx in loc de PORTx . PORTx se foloseste NUMAI in operatiile de scriere intr-un port de iesire. Daca in program faceti citirea PORTx in loc de PINx, obtineti ultima valoarea scrisa in port NU STAREA LINIEI DE INTRARE.
II.2 Un exemplu de folosire a timerelor In exemplele precedente, pentru controlul unor intarzieri am folosit functiile predefinite delay_ms(unsigned int) - intarziere cu un numar de milisecunde definit de parametrul functiei si delay_us(unsigned int) – intarziere cu un numar de microsecunde definit de parametrul functiei. Dezavantajul acestei solutii este acela ca functiile delay realizeaza intarzierea printr-o bucla de asteptare (decrementarea unui contor). Pe durata asteptarii, microcontrollerul nu mai poate executa alte functii si efectul este ca 99% din timpul total se pierde in asteptare. O solutie mult mai buna este sa folosim timerele interne pentru generarea unor intervale de timp precise. In esenta, subistemul timer al unui microcontroller contine un numarator realizat hardware, care poate numara pe un ceas cu frecventa selectabila prin program (cu ajutorul unui asa-numit prescaller). Susbsitemul timer poate genera cereri de intrerupere in urmatoarele situatii: a. La overflow – cand se atinge valoarea maxima, inainte de revenirea la starea
0 a numaratorului (numararea se face de regula in sens direct)
b. La “output compare” – cand valoarea din numarator egaleaza valoarea dintr-un registru special (OCR – output compare register) care este accesibil pentru scriere/citire prin program
c. La o conditie de “input capture” – cand se detecteaza o tranzitie (front crescator sau cazator al unei intrari asociate cu timerul respectiv).
Generarea unor intervale precise cu ajutorul intreruperii de overflow a unui timer. La scrierea unei valori CNTVAL in numaratorul timerului, (TCNT) se stie ca acesta va ajunge la overflow peste un numar de (0xFFFF-CNTVAL+1) perioade ale ceasului de numarare. De exemplu, daca dorim ca timerul sa genereze intreruperi la intervale de 2ms, iar frecventa selectata pentru ceasul de numarare a timerului este 2MHz, atunci trebuie sa scriem in TCNT la fiecare intrerupere valoarea CNTVAL=(65535-4000+1). Generarea unor intervale precise de timp cu ajutorul functiei output compare In cazul output compare se genereaza intrerupere in momentul cand numaratorul ajunge sa egaleze valoarea scrisa prin program in registrul OCR asociat. Ca sa generam interruperi la intervale de 2ms cu un ceas de 2MHz, trebuie sa adunam valoarea curenta a OCR cu 4000 si sa scriem rezultatul inapoi in OCR. In acest fel, urmatoarea intrerupere va surveni peste 4000 de perioade ale ceasului de numarare, adica – la 2MHz – peste 2ms. Exercitiu Folosind timerul 1 in mod output compare, sa se scrie un program care sa comande alternativ LED-urile conectate pe PORTb.0 si PORTB.1 la intervale de timp de 200ms respectiv 330ms. Solutie: 1. Se parcurge procedura de creare a unui nou proiect descrisa anterior 2. Se configureaza PORTB in asa fel incat bitul 0 si 1 sa fie iesiri. 3. Se configureaza Timer1 ca in figura 1: 4. Se salveaza proiectul 5. Se editeaza proiectul generat automat de CodeWizard, conform cu listing-ul
urmator: #include <mega16.h> unsigned char TTAB[8]; unsigned int OCR1; bit qtoc=0;
// Timer 1 output compare A interrupt service routi ne // se seteaza flagul qtoc // se scrie OCR1 cu valoarea curenta + 20000 - in a sa fel incat urmatoarea // intrerupere sa vina peste 10ms interrupt [TIM1_COMPA] void timer1_compa_isr(void) { qtoc=1; OCR1=(OCR1AH*256+OCR1AL)+20000; OCR1AH=(OCR1&0xFF00)>>8; OCR1AL=OCR1&0xFF; } void init(void); void soft_timers(void); void dectmr10ms(void); void main(void) { init(); TTAB[0]=20; // 20*10=200ms TTAB[1]=33; // 33*10=330ms // Global enable interrupts #asm("sei") while (1) { soft_timers(); if(TTAB[0]==0) { TTAB[0]=20; PORTB.0=!(PORTB.0); } if(TTAB[1]==0) { TTAB[1]=33; PORTB.1=!(PORTB.1); } }; } // Verifica daca a fost intrerupere (qtoc=1) // Daca a fost, sterge flagul qtoc, apoi decremente aza TTAB[i] daca // valoarea gasita este mai mare decat zero void soft_timers(void) { if(qtoc==0) return; qtoc=0; dectmr10ms();
} void dectmr10ms(void) { unsigned char i; for(i=0;i<8;i++) { if(TTAB[i]!=0) TTAB[i]--; } }
Fig. 1 Configurarea timer1 pentru intreruperi la output compare, cu ceas de 2MHz Observatii - In rutina de tratare a intreruperii, care se executa la intervale de 10ms, se
seteaza flagul qtoc (care va fi testat apoi de functia soft_timers() ) si se aduna 20.000 la valoarea curenta din OCR1A, in asa fel incat urmatoarea intrerupere sa vina peste 20000*0.5us=10000us=10ms
- Rutina soft_timers() verifica valorile din matricea TTAB[8] – daca sunt diferite de zero le decrementeaza. In acest fel, daca scriem prin program o valoare in TTAB, aceasta va fi decrementata la intervale de 10ms pana la zero.
- Pentru a realiza prin program diverse intervale de timp, tot ce avem de facut este sa initializam TTAB cu valoarea dorita si sa testam momentul cand timerul soft TTAB ajunge la zero.
II.3. Organizarea modulara a proiectelor Pe masura ce complexitatea programelor creste, constatam ca este incomod sa scriem intregul program sursa intr-un singur fisier. CVAVR permite organizarea proiectelor in mai multe fisiere sursa, care sunt compilate simultan. Pe langa avantajul comoditatii, acest mod de organizare are inca un avataj major – modulele scrise si testate pot fi refolosite, practic fara modificari in alte proiecte. Exercitiu Sa se reorganizeze proiectul din exercitiul precedent, in asa fel incat toate rutinele refoeritoare la timer sa fie plasate intr-un fisier distinct de modulul main. Solutie 1. Din menu-ul principal, se selecteaza New 2. Apare fereastra de dialog din figura 2
Fig. 2 Fereatra de dialog pentru crearea unui nou fisier sursa Se creaza automat fisierul gol untitled.c. Se salveaza (Save as) cu numele dorit – timer1.c.
Fig. 3 Fereastra navigatorului dupa crearea unui nou fisier 3. Cu Cut si Paste se muta din fisierul principal toate variabilele si functiile care
se refera la timer in noul fisier creat. Se adauga #include <mega16.h> si in fisierul nou creat!!
4. Se definesc in modulul main ca externe functiile si variabilele mutate in fisierul nou creat.
5. Se alege din menu-ul principal optiunea Project -> Configure si se adauga fisierul nou creat la proiect. (vezi fig. 4)
Fig. 4 Adaugarea fisierului nou creat la proiect 6. Se apasa butonul Add si din fereastra de dialog care urmeaza se selecteaza
fisierul nou creat. Fereastra navigatorului devine:
Fig. 5 Fereastra navigatorului dupa adaugarea fisierului timer1.c la proiect 7. Se compileaza proiectul (din menu-ul principal se alege optiunea Project ->
Make) Iata listingul celor doua module ale proiectului in forma finala: Modulul principal – tmr1.c #include <mega16.h> extern void init_timer1(void); extern void soft_timers(void); extern unsigned char TTAB[8]; void init(void); void main(void) { init(); init_timer1(); TTAB[0]=20; // 20*10=200ms TTAB[1]=33; // 33*10=330ms // Global enable interrupts #asm("sei") while (1) { soft_timers(); if(TTAB[0]==0) { TTAB[0]=20; PORTB.0=!(PORTB.0); } if(TTAB[1]==0) {
TTAB[1]=33; PORTB.1=!(PORTB.1); } }; }
Modulul timer1.c #include <mega16.h> unsigned char TTAB[8]; unsigned int OCR1; bit qtoc=0; void dectmr10ms(void); // Timer 1 output compare A interrupt service routi ne // se seteaza flagul qtoc // se scrie OCR1 cu valoarea curenta + 20000 - in a sa fel incat // urmatoarea intrerupere sa vina peste 10ms interrupt [TIM1_COMPA] void timer1_compa_isr(void) { qtoc=1; OCR1=(OCR1AH*256+OCR1AL)+20000; OCR1AH=(OCR1&0xFF00)>>8; OCR1AL=OCR1&0xFF; } void soft_timers(void) { if(qtoc==0) return; qtoc=0; dectmr10ms(); } void dectmr10ms(void) { unsigned char i; for(i=0;i<8;i++) { if(TTAB[i]!=0) TTAB[i]--; } } void init_timer1(void) { // Timer/Counter 1 initialization // Clock source: System Clock // Clock value: 2000.000 kHz // Mode: Normal top=FFFFh // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off
// Input Capture on Falling Edge // Timer 1 Overflow Interrupt: Off // Input Capture Interrupt: Off // Compare A Match Interrupt: On // Compare B Match Interrupt: Off TCCR1A=0x00; TCCR1B=0x02; TCNT1H=0x00; TCNT1L=0x00; ICR1H=0x00; ICR1L=0x00; OCR1AH=0x00; OCR1AL=0x00; OCR1BH=0x00; OCR1BL=0x00; } II.4. Refolosirea modulelor soft in alte proiecte Exercitiu Sa se implementeze soft un monostabil care sa activeze LED-ul conectat la PORTB.0 pentru o durata de 2 secunde, de fiecare data cand se activeaza intrarea PORTC.0 (nota: intrarea este actiuva LOW din cauza optocuplorului). 1. Se deschide proiectul tmr1 2. In fereastra navigatorului, se selecteaza fisierul tmr1.c 3. Din menu-ul principal se selecteaza optiunea Save as si se salveaza fisierul
intr-un folder nou creat – sa zicem C:\cvavr\work\tmr2 4. Se repeta operatia cu toate fisierele din proiectul model pe care dorim sa le
folosim in noul proiect. 5. Se inchide proiectul model 6. Se foloseste CodeWizard pentru a genera un nou proiect. Se salveaza noul
proiect cu denumirea tmr2 7. Se inlocuieste continutul fisierului tmr2.c cu continutul fisierului tmr1.c folosind
Copy/Paste. 8. Se configureaza noul proiect incat sa includa fisierul timer1.c 9. Se recompileaza pentru a verifica corectitudinea operatiilor. daca nu sunt
erori, procesul de export a proiectului este incheiat si acum putem trece la editarea noului proiect.
Modificarile pentru implementarea functiei de monostabil se executa doar in modulul principal, conform listing-ului urmator: #include <mega16.h> extern void init_timer1(void); extern void soft_timers(void); extern unsigned char TTAB[8]; void init(void);
void main(void) { init(); init_timer1(); TTAB[0]=0; // Global enable interrupts #asm("sei") while (1) { soft_timers(); if(PINC.0==0) TTAB[0]=200; if(TTAB[0]!=0) PORTB.0=1; if(TTAB[0]==0) PORTB.0=0; } } Introducere in programarea microcontrollerelor din seria Atmel AVR Partea a III-a III. Exemple de utilizare a interfetei seriale asincrone III.1. Generalitati despre comunicatia seriala asincrona Majoritatea microcontrollerelor sunt echipate cu o interfata seriala asincrona denumita SCI (Serial Communication Interface) sau UART/USART (Universal Synchronous/Asynchronous Receiver Transmitter). In principiu, o transmisie seriala asincrona poate fi realizata cu ajutorul unor registre de deplasare, ca in figura 1.
Tx Shift register Rx Shift register
TxCLK RxCLK
Data Data
Serial
line
Transmiter Receiver Fig. 1 Schema bloc a unei comunicatii asincrone Se observa ca pe linia de comunicatie nu se transmite si un clock de sincronizare si, din acest motiv, sunt necesare urmatoarele masuri pentru sincronizarea emitatorului cu receptorului: a. Emitatorul si receptorul trebuie sa convina asupra unei “viteze de transmisie”
– cu alte cuvinte frecventa ceasurilor TXCLK si RXCLK trebuie sa fie aceeasi.
b. Fiecare octet de date trebuie precedat de un “bit de start”, cau polaritate inversa fata de starea de repaus a liniei de comunicatie (de obicei starea de repaus a liniei este 1 logic, astfel incat bitul de start are valoarea logica zero)
c. Dupa fiecare octet e transmite cel putin un “bit de stop” pe durata caruia linia are polaritatea starii de repaus (1 logic).
Cu aceste conventii, octetul 0x31 (codul ASCII al cifre 1) se transmite astfel:
Start Stop
Tb/2 Tb Tb Tb Tb Tb Tb Tb Tb Tb
1 0 0 0 1 1 0 0
Fig. 2 Forma de unda a semnalului pentru transmisia asincrona a octetului 0x31 Observati ca octetii se transmit serial incapand cu bitul cel mai putin semnificativ. Schema bloc a USART este prezentata in figura 3
BAUD rate generator
Tx shift register
Control logic
Internal bus
Rx shift register
Control Tx Data Rx Data Status
CLK
TxD RxD
Fig. 3 Schema bloc generala a unui USART Microcontrollerele din seria AVR sunt echipate cu unul sau (uneori) doua USART-uri. ATmega16 are un usart. Registrele de date ale transmitatorului si receptorului sunt “vizibile” pentru program la aceeasi adresa, denumite simbolic UDR (Usart Data Register), cu precizarea ca la scriere in UDR se scrie in registrul de date al transmitatorului iar la citire se citeste din registrul de date al receptorului. Registrul de control si cel de stare sunt comasate in registrele de control si stare UCSRA, UCSRB, si UCSRC. UCSRA are structura:
Fig. 4 Registrul de control si stare UCSRA Semnificatia bitilor: RXC- RX Complete – receptie completa TXC Tx Complete – transmisie completa UDRE – Usart Data Register Empty – setat pe 1 cand registrul de date al transmitatorului este gol (se pot accepta scrieri) FE, DOR, PE sunt biti de eroare (Framing error, Overrun error, si parity error) U2X – bit de control Cand e setat dubleaza viteza de comunicatie asincrona. MPCM – Multi Processor Communication Mode. Comanda intrarea intr-un regim de functionare multiprocesor, care nu ne intereseaza acum. UCSRB contine biti de control pentru activarea/dezactivarea separata a transmitatorului si receptorului si pentru validarea generarii de intreruperi, iar UCSRC contine biti de control pentru selectia modului de functionare (sincron/asincron) si pentru activarea/dezactivarea controlului de paritate, pentru selectia numarului de biti per caracter etc. III.2. Programarea interfetei seriale asincrone la AVR Programarea interfetei seriale consta in: a. Initializarea interfetei, prin activarea emitatorului si receptorului, selectia
vitezei de comunicatie, a numarului de biti per caracter, si activarea (daca e cazul) intreruperilor asociate cu interfata.
b. Programarea emisiei si receptiei la nivel de caracter. O metoda buna de a programa emisia si receptia caracterelor este de a activa intreruperile de receptie si de a testa bitul de stare UDRE (Data register empty) la emisia unui caracter, Iata un exemplu de functie care emite un caracter pe linia seriala: #define UDRE 5 void send_char(char ch) { UDR = ch; while ( !( UCSRA & (1<<UDRE)) ); }
Se observa ca emisia efectiva a caracterului incepe odata cu scrierea in registrul UDR. Apoi se asteapta momentul cand bitul de stare UDRE (bitul 5) din registrul UCSRA devine 1, moment in care transmisia este incheiata. Se mai poate scrie si asa:
#define TXC 6 void send_char(unsigned char ch) { UDR = ch; while ( ( UCSRA & (1<<TXC))==0 ); UCSRA=(1<<TXC); } De aceasta data se testeaza bitul de stare TXC (Tx Complete - bitul 6 din UCSRA). Diferenta este ca bitul TXC nu se sterge automat si trebuie sters prin program scriind 1 in pozitia 6 din UCSRA!! Un exemplu de rutina de intrerupere la receptia unui caracter de la USART este urmatorul: // USART Receiver interrupt service routine interrupt [USART_RXC] void usart_rx_isr(void) { char status, data; status=UCSRA; data= UDR; rxdata=data; rflg=1; }
Se observa ca rutina de intrerupere face urmatoarele operatii: a. Seteaza flagul rflg=1 pentru a indica programului principal ca s-a primit un
caracter pe linia seriala b. Salveaza caracterul primit in variabila rxdata c. Salveaza registrul de stare UCSRA (inclusiv bitii de eroare) in variabila status III.3 Exemple de aplicatii simple de folosire a intefetei seriale III.3.1. Exemplul nr. 1 Sa se scrie un program care face primeste pe linia seriala caracterele emise de un terminal ASCII. Face ecou la toate caracterele, dar schimba case de la literele mici la literele mari. Se parcurg urmatorii pasi: a. Se creaza un nou proiect folosind CodeWizardAVR. Sa zicem ca il denumim
serial1. b. Se selecteaza tab-ul “USART”. se obtine urmatoarea fereastra de dialog:
Fig. 5 Dialogul in cazul initializarii USART-ului la ATMEGA16 Se bifeaza check-box-urile Receiver si Transmitter pentru validarea lor. Se obtine urmatoarea fereastra:
Fig. 6 Dialogul pentru initializarea USART-ului Restul perifericelor se lasa la initializarile default.
c. Se editeaza codul generat pentru a obtine urmatoarele fisiere-sursa: - Un fisier distinct USART.C cu urmatorul continut: #include <mega16.h> #define RXB8 1 #define TXB8 0 #define UPE 2 #define OVR 3 #define FE 4 #define UDRE 5 #define RXC 7 #define FRAMING_ERROR (1<<FE) #define PARITY_ERROR (1<<UPE) #define DATA_OVERRUN (1<<OVR) #define DATA_REGISTER_EMPTY (1<<UDRE) #define RX_COMPLETE (1<<RXC) bit rflg; unsigned char rxdata; void send_char(char ch); void init_usart(void); // USART Receiver interrupt service routine interrupt [USART_RXC] void usart_rx_isr(void) { char status,data; status=UCSRA; data=UDR; rflg=1; rxdata=data; } void send_char(char ch) { UDR = ch; while ( !( UCSRA & (1<<UDRE)) ); } void init_usart(void) { // USART initialization // Communication Parameters: 8 Data, 1 Stop, No Par ity // USART Receiver: On // USART Transmitter: On // USART Mode: Asynchronous // USART Baud Rate: 9600 UCSRA=0x00; UCSRB=0x98;
UCSRC=0x86; UBRRH=0x00; UBRRL=0x67; }
Si programul principal serial1.c cu urmatorul continut: #include <mega16.h> extern unsigned char rxdata; extern bit rflg; extern void send_char(char ch); extern void init_usart(void); void init(void); unsigned char change_case(unsigned char ch); void main(void) { init(); init_usart(); // Global enable interrupts #asm("sei") while (1) { if(rflg==1) { rflg=0; rxdata=change_case(rxdata); send_char(rxdata); } }; } /* Caracterele ASCII pentru litere mici sunt in plaja 0x61 ("a") pana la 0x7A ("z") Caracterele coresunzatoare literelor mari sunt in p laja 0x41 ("A") pana la 0x5A ("Z"). Conversia consta in stergerea bitului 5 (0x20) */ unsigned char change_case(unsigned char ch) { // verificam daca parametrul corespunde unei li tere mici if((ch<0x61)||(ch>0x7A)) return(ch); else { ch=ch&0xDF; return(ch);
} }
Nota: Tabelul urmator contine toate codurile ASCII:
Tabelul 1. Codurile ASCII
0 1 2 3 4 5 6 7 8 9 A B C D E F 0 NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI 1 DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US 2 SP ! " # $ % & ' ( ) * + , - . / 3 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 4 @ A B C D E F G H I J K L M N O 5 P Q R S T U V W X Y Z [ \ ] ^ _ 6 ` a b c d e f g h i j k l m n o 7 p q r s t u v w x y z { | } ~ DEL
III.3.2 Exemplul nr. 2 Sa se scrie un program care activeaza releul conectat pe PORTB.0 cand pe linia seriala se primeste ASCII ‘1’ si il dezactiveaza cand se primeste ASCII ‘0’. Se face ecou la toate caracterele primite, pentru control. Se cfolosesc procedurile de copiere a unui proiect cu alt nume descrise in cap[itolul II. Se modifica modulul principal in urmatoarele aspecte: a. Se schimba initializarea portului B in asa fel incat PORTB.0 sa fie configurat
ca linie de iesire b. Se modifica bucla infinita din functia main() in felul urmator: while (1) { if(rflg==1) { rflg=0; send_char(rxdata); if(rxdata==0x31) PORTB.0=1; if(rxdata==0x30) PORTB.0=0; } };
Introducere in programarea microcontrollerelor din seria Atmel AVR Partea a IV-a IV. Exemple de utilizare a convertorului A/D intern IV.1. Generalitati despre convertorul A/D In esenta, orice microcontroller este o masina digitala, capabila sa prelucreze marimi reprezentate in sistemul binar. Pentru a opera cu semnale analogice, este necesara o interfata speciala, denumita convertor analog-digital, care esantioneaza semnalul analogic la momente discrete de timp, si evalueaza amplitudinea acestuia (vezi figura 1).
f(t)
S1
S2
S3
S4
T1 T2 T3 T4
t
Fig. 1 Esantionarea unui semnal analogic Convertorul A/D masoara si reprezinta numeric valorile S1, S2, S3 ... ale esantioanelor semnalului analogic, la momente discrete de timp T1, T2, T3... Schema bloc a unui convertor A/D este prezentata in figura 2.
Vref
DAC SAR & control
logic
S/HMUX
ADC CONTROL ADC STATUS ADC DATA
Internal bus
Analog
input
ADCLK
_
+
Fig. 2 Schema bloc a unui convertor A/D tipic Principalele caracteristici ale unui convertor A/D sunt descrise de “rezolutia convertorului” – definita ca numarul de biti ai rezultatului conversiei si “timpul de
conversie” – definit ca intervalul intre momentul cand se genereaza o comanda de conversie si momentul in care rezultatul este disponibil. Din punct de vedere al programarii, convertorul A/D este conectat la unitatea centrala a micorcontrollerului prin intermediul unui registru de control si stari, care contine biti de comanda pentru activarea blocului de conversie, selectia intrarii analogice multiplexate, selectia ceasului de lucru, comanda de start conversie si bitul de stare care indica sfarsit de conversie si a unui registru de date care va contine rezultatul conversiei. IV.2 Exercitii de programare a convertorului A/D 1. Sa se scrie un program care citeste periodic, la intervale de o secunda, linia
analogica 0 si transmite rezultatul pe interfata seriala, sub forma a doua caractere ASCII.
Solutie: a. Folosind CodeWizard se genereaza un nou proiect si se salveaza intr-un
folder distinct sub numele analog1 b. Se copiaza fisierele usart.c si timer1.c din exemplele descrise in capitolele
anterioare. c. Se foloseste optiunea Tools -> CodeWizard -> Program preview pentru a
initializa convertorul A/D . Se copiaza si se salveaza sectiunea de initializare a convertorului A/D intr-un fisier distinct ADC.C
Acesta va avea urmatorul continut: #include <mega16.h> #include <delay.h> #define ADC_VREF_TYPE 0x60 void init_adc(void) { // ADC initialization // ADC Clock frequency: 1000.000 kHz // ADC Voltage Reference: AVCC pin // Only the 8 most significant bits of // the AD conversion result are used ADMUX=ADC_VREF_TYPE & 0xff; ADCSRA=0x84; } // Read the 8 most significant bits // of the AD conversion result unsigned char read_adc(unsigned char adc_input) { ADMUX=adc_input | (ADC_VREF_TYPE & 0xff); // Delay needed for the stabilization of the ADC in put voltage delay_us(10); // Start the AD conversion ADCSRA|=0x40; // Wait for the AD conversion to complete
while ((ADCSRA & 0x10)==0); ADCSRA|=0x10; return ADCH; }
d. Se editeaza modulul principal (analog1.c) ca sa aiba urmatorul continut: /************************************************** *** Un exemplu de folosire a convertorului A/D Programul citeste linia analogica 0 si transmite rezultatul pe linia seriala sub forma a doua caract ere ASCII *************************************************** **/ #include <mega16.h> extern void init_usart(void); extern void init_timer1(void); extern void init_adc(void); extern void soft_timers(void); extern void send_char(char ch); extern unsigned char read_adc(unsigned char adc_inp ut); extern unsigned char TTAB[8]; unsigned char hinib, lonib; void init(void); void casc(unsigned char ch); void main(void) { unsigned char adc_value; init(); init_usart(); init_timer1(); init_adc(); TTAB[1]=100; // Global enable interrupts #asm("sei") while (1) { soft_timers(); if(TTAB[0]==0) { TTAB[0]=100; adc_value=read_adc(0); casc(adc_value); send_char(hinib); send_char(lonib); send_char(0x20); // emite un spatiu
} }; } // casc - converteste un octet binar la doua carac tere ascii hiniob, lonib void casc(unsigned char ch) { hinib=(ch&0xf0)>>4; lonib=ch&0x0f; if(hinib>9) hinib=hinib+'7'; else hinib=hinib+'0'; if(lonib>9) lonib=lonib+'7'; else lonib=lonib+'0'; } void init(void) { // Input/Output Ports initialization // Port A initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Fun c2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T Sta te2=T State1=T State0=T PORTA=0x00; DDRA=0x00; // Port B initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Fun c2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T Sta te2=T State1=T State0=T PORTB=0x00; DDRB=0x0F; // Port C initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Fun c2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T Sta te2=T State1=T State0=T PORTC=0x00; DDRC=0x00; // Port D initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Fun c2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T Sta te2=T State1=T State0=T PORTD=0x00; DDRD=0x00; // Timer/Counter 0 initialization // Clock source: System Clock // Clock value: Timer 0 Stopped // Mode: Normal top=FFh
// OC0 output: Disconnected TCCR0=0x00; TCNT0=0x00; OCR0=0x00; // Timer/Counter 2 initialization // Clock source: System Clock // Clock value: Timer 2 Stopped // Mode: Normal top=FFh // OC2 output: Disconnected ASSR=0x00; TCCR2=0x00; TCNT2=0x00; OCR2=0x00; // External Interrupt(s) initialization // INT0: Off // INT1: Off // INT2: Off MCUCR=0x00; MCUCSR=0x00; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x10; // Analog Comparator initialization // Analog Comparator: Off // Analog Comparator Input Capture by Timer/Counter 1: Off ACSR=0x80; SFIOR=0x00; }
Observatii: Se observa ca rezultatul binar al conversiei obtinut in variabila adc_value, actualizata prin apelarea functiei read_adc(numar_canal) este un octet binar. Daca emitem pe linia seriala acest octet, terminalul il va afisa intr-un mod neinteligibil. Din acest motiv este necesar ca inainte de a emite octetul citit de la ADC, sa-l convertim la doua caractere ASCII, corespunzator reprezentarii hexazecimale a numarului. Exemplu Sa presupunem ca octetul citit de la ADC are valoarea binara 0011 1010 (0x3A). Daca emitem direct pe linia seriala acest octet, terminalul va interpreta valoarea primita ca un digit ASCII si va afisa ‘:’. Rutina de conversie casc(unsigned char ch) foloseste doua variabile globale hinib si lonib pentru a stoca rezultatul conversiei. void casc(unsigned char ch) { hinib=(ch&0xf0)>>4; lonib=ch&0x0f; if(hinib>9) hinib=hinib+'7';
else hinib=hinib+'0'; if(lonib>9) lonib=lonib+'7'; else lonib=lonib+'0'; }
Observam ca, pentru inceput, casc izoleaza cei doi semiocteti ai parametrului in variabilele hinib si lonib: hinib=(ch&0xf0)>>4; lonib=ch&0x0f;
In acest moment, hinib=0x03 si lonib=0x0A. In continuare, observam ca reprezentarea ASCII a numerelor de la 0 la 9 este 0x30-0x39, ceea ce inseamna ca pentru a converti un numar situat in plaja [0-9] la ASCII este suficient sa adunam constanta 0x30 – care este codul SCII pentru ‘0’. In mod similar pentru a obtine codurile ASCII corespunzatoare numerelor hexazecimale [A-F] este suficient sa adunam constanta 0x37 (echivalent cu ASCII ‘7’). if(hinib>9) hinib=hinib+'7'; else hinib=hinib+'0'; if(lonib>9) lonib=lonib+'7'; else lonib=lonib+'0';
In final, hinib=0x33 si lonib=0x41 (0x0A+0x37=0x41). 2. Adaugati la exercititiul precedent urmatoarea functie: - Sa se activeze releul conectat la PORTB.0 cand se depaseste un prag
PRAG_SUS si releul conectat la PORTB.1 cand valoarea semnalului scade sub PRAG_JOS.
Solutie: Se modifica doar modulul main in felul urmator: while (1) { soft_timers(); adc_value=read_adc(0); if(TTAB[0]==0) { TTAB[0]=100; casc(adc_value); send_char(hinib); send_char(lonib); send_char(0x20); // emite un spatiu } if(adc_value>=PRAG_SUS) PORTB.0=1; else PORTB.0=0; if(adc_value<=PRAG_JOS) PORTB.1=1; else PORTB.1=0; }; }
3. Sa se scrie un program care activeaza si dezactiveaza alternativ releul
conectat la PORTB.0 pentru o durata controlata printr-un potentiometru conectat la intrarea analogica 0.
Solutie: Se modifica functia main() dupa cum urmeaza: while (1) { soft_timers(); if(TTAB[0]==0) { adc_value=read_adc(0); TTAB[0]=~adc_value; PORTB.0=!PORTB.0; casc(adc_value); send_char(hinib); send_char(lonib); send_char(0x20); // emite un spatiu } }; }
Introducere in programarea microcontrollerelor din seria Atmel AVR Partea a V-a V. Notiuni de baza despre PWM V.1 Definitie. Principiul de functionare al generatoarelor PWM PWM este un acronim de la Pulse Width Modulator – Modulator de impulsuri in durata, mai precis e vorba de un generator de semnale dreptunghiulare cu frecventa fixa, dar cu factor de umplere modificat dinamic.
T
PWMOUT
Fig. 1 Forma de unda a unui semnal PWM Un astfel de semnal poate fi generat cu o schema ca in figura 2.
PrescallerClock
Counter
Carry
Comparator
Compareregister
Flipflop
S
R QPWM
Fig. 2 Schema logica a unui circuit generator PWM Circuitul consta intr-un numarator, care numara permanent pe un ceas obtinut prin divizarea programabila cu ajutorul unui prescaller a ceasului principal. Continutul numaratorului este comparat de un comparator digital cu valoarea continuta de un registru programabil (compare register) si la coincidenta se seteaza un bistabil R-S. Stergerea bistabilului se face in momentul cand numaratorul ajunge la overflow. Functionarea schemei din figura 2 este ilustrata in figura 3.
PWMOUT
T ime
T ime
TOP
COMPARE
Fig. 3 Principiul de functionare a unui generator PWM Se observa ca frecventa semnalului PWM este constanta, data de frecventa ceasului principal si de constantele de divizare ale prescallerului si ale numaratorului PWM. Factorul de umplere al semnalului este insa dependent de valoarea regsitrului de comparatie (compare register). Aceasta particularitate face ca sistemul de timere al microcontrollerelor sa poata fi usor fi folosit si ca generator PWM.
In cazul microcontrollerelor din seria AVR, toate timerele principale pot fi folosite si ca generatoare PWM. ATMega16, are 3 timere , iar timer1 are asociate doua registre output compare, deci in total se pot genera 4 semnale PWM simultan. Principala utilitate a semnalelor PWM deriva din faptul ca prin filtrarea trece-jos a unui semnal PWM se obtine un semnal proportional cu factorul de umplere al semnalului PWM, ceea ce este echivalent cu o demodulare. In acest mod, subsistemul PWM al unui microcontroller este echivalent cu un convertor Digital-Analog. V.2 Exemple de programare a timerelor in mod PWM la AVR 1. Folosind timer1 al ATMega16 sa se genereze la iesirile OC1A si OC1B (pinii
18 si 19 ai microcontrollerului) doua ceasuri, unul cu factor de umplere ½ iar celalat cu factor de umplere ¼.
Solutie Folosind CodeWizardAVR, se selecteaza initializarile pentru timer1 ca in figura 4. Apoi se editeaza programul generat pentru a configura iesirile PORTD corespunzatoare pinilor OC1A si OC1B ca iesiri si se aleg valorile de initializare pentru OCR1AL si OCR1BL la jumatate, respectiv un sfert din 255. (0x80 si 0x40). Programul principal se reduce la o bucla infinita de asteptare – totul se reduce la aceste initializari. Odata initializat subsistemul timer PWM functioneaza independent de programul principal.
Fig. 4 Initializarile cerute de exercitiul 1
2. Folosind iesirea PWM OC1B asociata cu timer1 sa se genereze un semnal
care, dupa filtrarea trece-jos, sa aproximeze un semnal liniar variabil. Solutie: Se initializeaza timer1 in mod similar cu initializarea din exercitiul porecedent. In bucla infinita din functia main se scrie urmatorul cod: while (1) { for (i=0;i<10;i++) { OCR1BL=PWMTAB[i]; delay_ms(1); } };
Unde variabila PWMTAB a fost definita ca: unsigned char PWMTAB[10]={10,30,50,70,90,110,130,15 0,170,190};
3. Sa se scrie un program care modifica factorul de umplere al unui ceas
generat la iesirea OC1B in functie de valoarea masurata pe intrarea analogica 0.
Introducere in programarea microcontrollerelor din seria Atmel AVR Partea a VI-a VI. Programarea interfetei SPI VI.1 Definitie. Principiul de functionare SPI este un acronim pentru Synchronous Peripheral Interface – interfata de comunicatie sincrona cu perifericele. Principiul de functionare al SPI se bazeaza pe modul de functionare al registrelor de deplasare. In figura 1, sunt prezentate doua registre de deplasare concatenate si controlate de acelasi ceas de sincronizare. In aceasta structura, continutul registrului A este transferat bit cu bit (serial) in registrul B, sincron cu ceasul exterior, aplicat ambelor registre.
SERIN SERINSEROUT SEROUT
CLK CLK
Data In
Clock
A B
Fig. 1 Un exemplu elementar de comunicatie sincrona cu registre de deplasare Intr-o astfel de structura, dispozitivul care genereaza clock-ul de sincronizare, controleaza integral momentul cand are loc transmisia, precum si viteza de comunicatie si, din acest motiv este denumit MASTER, iar dispozitivul pasiv este numit SLAVE. In practica, pentru a asigura comunicatia bidirectionala intre MASTER si SLAVE, se folosesc circuite care contin patru registre de deplasare, conectate ca in figura 2.
Rx Data Register Tx Data Register
SCK
Tx Data Register Rx Data Register
MISO
SCK
MOSI
SSMaster Slave Fig. 2 Schema bloc a unei conexiuni SPI bidirectionale Se observa ca toate cele patru registre sunt controlate de acelasi clock SCK, generat de dispozitivul MASTER. Liniile de date se numesc MOSI (Master Out Slave In – iesirea de date a dispozitivului MASTER) si MISO (Master In Slave Out – intrarea de date pentru MASTER) Suplimentar fata de aceste semnale, dispozitivul MASTER genereaza inca un semnal de control, denumit SS (SLAVE SELECT), activ de obicei pe zero. In acest mod se pot realiza retele cu un MASTER si mai multe dispozitive SLAVE., fiecare SLAVE avand un semnal distinct de selectie. La un moment dat, numai unitatea SLAVE care primeste semnal de selectie participa la transferul de date. O consecinta evidenta si importanta a faptului ca toate registrele implicate intr-o conexiune SPI sunt controlate de acelasi ceas, este faptul ca transmisia si receptia datelor au loc simultan, cu alte cuvinte, comunicatia SPI este in mod inerent full-duplex.
VI.2 Structura interfetei SPI care echipeaza microcontrollerele Majoritatea microcontrollerelor moderne sunt echipate cu un subsistem SPI, cu structura descrisa in figura 3.
SCK
MOSI
CLK
MISOTx shift register Rx shift register
Control logic
Control
register
Status
registerData
register
Interrupt
request
Internal bus Fig. 3 Schema bloc a interfetei SPI a unui microcontroller Microcontrollerele din seria Atmel AVR, au urmatoarele registre in structura subsistemului SPI: a. Registrul de date al interfetei, denumit SPDR (SPI Data Register) La nivelul
MASTER, o scriere in acest registru initiaza imediat procesul de transfer serial al datelor, prin generarea a 8 impulsuri de ceas. La sfarsitul transferului, SPDR al MASTER contine datele receptionate de la SLAVE.
b. Registrul de stare SPSR (SPI Status Register), contine doi biti de stare, si anume: SPIF (SPI transfer complete Flag) care indica incheierea transferului si WCOL – Write Collision, un bit de eroare, setat atunci cand se incearca o scriere soft in registrul SPDR pe durata cat exista un transfer in curs.
c. Registrul de control SPCR (SPI Control Register) – contine biti de control cu urmatoarele functii:
- selectia vitezei de transfer (frecventa ceasului) - polaritatea clock-ului generat - ordinea de transfer a bitilor (incepand cu MSB sau cu LSB) - selectia regimului de functionare (ca MASTER sau SLAVE) - activarea generala a interfetei - activarea optionala a emisiei unei cereri de intrerupere la sfarsitul unui
transfer SPI. VI.3 Aplicatii ale interfetei SPI a. Conectarea a doua sau mai multe microcontrollere in sisteme multiprocesor.
In acest mod se pot realiza viteze de transfer relativ mari (4Mb/s). b. Conectarea unor memorii sau dispozitive periferice (display-uri, tastaturi etc.)
la un microcontroller. Acest sistem are avantajul ca implica putini pini ai microcontrollerului.
VI.4 Programarea interfetei SPI Programarea interfetei SPI vizeaza urmatoarele aspecte: a. Initializarea interfetei – proces care are ca obiectiv selectia regimului de
functionare (MASTER sau SLAVE), activarea generala a interfetei, selectia frecventei si a polaritatii ceasului, si – eventual – activarea intreruperii de sfarsit de transfer. Este de asemenea necesara configurarea liniilor I/O asignate pentru MOSI si SCK, ca linii de iesire iar a liniei MISO ca linie de intrare.
b. Scrierea functiei de emisie/receptie (reamintim ca procesul de emisie si cel de receptie au loc simultan)
O rutina tipica de emisie/receptie SPI este urmatoarea (ATMega16, MASTER): unsigned char spi_send(unsigned char ch) { PORTB.4=0; // genereaza semnalul SS=0 delay_us(10); // intarziere necesara pen tru periferice lente SPDR=ch; // inceputul transmisiei while(!(SPSR&0x80)); // asteptarea incheierii t ransmisiei PORTB.4=1; // Deselecteaza SLAVE return(SPDR); // intoarce caracterul rec eptionat de la SLAVE }
Exemplu Sa se scrie un program care sa configureze microcontrollerul ATMega16 ca SPI MASTER si sa comande aprinderea unor LED-uri conectate la un circuit MC14489. Solutie Circuitul MC14489 poate comanda direct 24 de LED-uri, sau 5 digiti de afisare cu 7 segmente. Datele care se afiseaza se primesc pe o interfata SPI. Evident, circuitul se comporta ca un SPI SLAVE. Formatul datelor transmise catre MC14489 este urmatorul: a. Dispozitivul MASTER trimite 4 octeti, dintre care primul octet este un cuvand
de configurare. Pentru a accesa individual cel 24 de LED-uri acest cuvant are valoarea 0x3F.
b. In continuare, se trannsmit 3 octeti de date, corespunzator celor 24 de LED-uri pe care le poate controla circuitul.
Toti cei patru octeti se transmit cu bitul cel mai semnificativ primul. Programul de initializare a SPI pentru comanda MC14489 se obtine cu CodeWizardAvr, selectand optiunile din figura 4.
Fig. 4 Dialogul CodeWizardAVR pentru initializarea SPI Se vor configura de asemenea liniile PORTB.4, PORTB.5 si PORTB.7 ca linii de iesire (pentru SS, MOSi si SCK respectiv). Se scrie apoi programul principal ca mai jos: void main(void) { init(); PORTB.4=1; while (1) { send_spi(0x3F); send_spi3(0x81,0x0F,0x55); delay_ms(1000); }; } Iar functiile send_spi() si send_spi3() au urmatorul continut: unsigned char send_spi(unsigned char ch) { PORTB.4=0; delay_us(100); SPDR=ch; while(!(SPSR&0x80)); PORTB.4=1; return(SPDR); } void send_spi3(unsigned char ch1, unsigned char ch2 , unsigned char ch3) { PORTB.4=0; delay_us(10);
SPDR=ch1; while(!(SPSR&0x80)); SPDR=ch2; while(!(SPSR&0x80)); SPDR=ch3; while(!(SPSR&0x80)); PORTB.4=1; }
Nota: Functia send_spi3() a fost necesara pentru ca MC14489 cere ca toate cele 3 caractere care urmeaza dupa caracterul de configurare trebuie transmise fara ca linia SS sa revina in 1. Exercitii 1. Sa se scrie un program care sa afiseze pe LED-urile conectate la un circuit
MC14489 caracterele ASCII primite pe linia seriala asincrona.