-
Notifications
You must be signed in to change notification settings - Fork 0
/
capitol_5.tex
599 lines (457 loc) · 30.1 KB
/
capitol_5.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
% \part{Temes avançats}
% \label{part:avançats}
% https://books.google.es/books?id=pWlbvW0H3IAC&pg=PA274&lpg=PA274&dq=embedded+programming+models&source=bl&ots=vQxIpc23Pp&sig=c5q9zBbwd7on7V2E_awwZ54rHpg&hl=ca&sa=X&ved=2ahUKEwjl6KCgopbfAhUNXRUIHferAIcQ6AEwCHoECAUQAQ#v=onepage&q=embedded%20programming%20models&f=false
% https://www.nap.edu/read/10193/chapter/7
% https://www.embedded.com/design/debug-and-optimization/4006502/Back-to-the-basics-Picking-the-right-computational-model
% https://courses.cs.washington.edu/courses/cse466/02au/Lectures/State-models.pdf
%% FSM, FSMD, HCFSM, Program-state machine model (PSM), Dataflow, 02auSynchronous dataflow,
Fins ara hem vist com controlar els perifèrics més habituals que poden trobar a un microcontrolador, fent ús de les biblioteques dels fabricants.
També s'ha observat que la programació per sistemes encastats és força diferent a la d'una aplicació d'escriptori o d'una {\em app} per un telèfon mòbil. Per això cal ara dedicar uns capítols a presentar els diferents models de programació més utilitzats en la programació de sistemes encastats. També caldrà introduir conceptes teòrics potser nous i classificar aquestes models segons diferents aspectes per donar un seguit de criteris a l'hora de decidir quin model utilitzar per cada aplicació concreta.
\chapter{Model d'interfície amb perifèrics}
\label{ch:modelinterficie}
Una primera classificació de com es planifica i s'acaba codificant la nostra aplicació és segons com es faci la interfície amb els diversos perifèrics. Aquesta classificació es basa en la ja coneguda dicotomia entre el {\em polling} i el treball amb interrupcions sobre perifèrics. Aquesta classificació és independent de les següents classificacions que es faran, tal com es veurà més endavant i, de fet, es poden combinar amb les classificacions següents segons les necessitats de cada aplicació.
\section{{\em Polling} d'esdeveniments}
\label{sec:polling}
El primer model de programació i potser el més senzill és el d'un bucle infinit que està en una espera activa ({\em polling}) de certs esdeveniments per actuar com calgui segons l'aplicació. En aquest model no es fan servir les interrupcions i en tot moment es prova de fer la lectura dels perifèrics o sensors que han de permetre una lectura nova i potser provocar un canvi en el sistema. Per tant, aquest model consistirà bàsicament en un bucle infinit dins la funció {\em main} de l'aplicació. Aquest bucle anirà repetint indefinidament les lectures necessàries i les actuacions pertinents si s'han pogut fer.
Un exemple d'aquesta mena d'estil de programació s'ha vist a \fullref{ch:aplicaciosenzilla}, on l'aplicació ha de llegir un sensor de distància per mostrar-la en un LEDs controlant la seva potència. En aquest exemple, el codi conté una funció {\bf main} que hi ha un bucle infinit on contínuament es prova de llegir el valor de proximitat del sensor i, si és el cas, variar la lluminositat del LED segons la lectura feta (veure també el codi~\ref{ProximityExample}).
Aquesta mena de programació és suficient per múltiples aplicacions senzilles, on la lògica de l'aplicació depèn de poques variables o condicions i el concepte de {\em timeout} o de temps en general n'és absent. Si, per exemple, volem que transcorri algun temps entre algunes instruccions o esperar-se un determinat temps per realitzar una operació, caldrà introduir el temps al model.
També cal tenir en compte que aquest model fa que tota l'estona el microcontrolador estigui treballant i, per tant, el consum energètic serà el màxim. Això per múltiples aplicacions no serà cap problema, però s'haurà de tenir en compte en aplicacions orientades al baix consum.
\section{Interrupcions}
\label{sec:interrupcions}
L'altre opció és dissenyar l'aplicació de manera que els diversos perifèrics llencin interrupcions quan hagin fet les diverses tasques necessàries i el microcontrolador pugui estar {\em Idle} tot esperant per rebre les interrupcions i seguir amb l'aplicació.
Normalment aquest mode de treballar fa el codi una mica més complex (veure \fullref{gpio_isr} i \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/GPIO_2}{\bf el repositori}). En aquests casos es configuren primer tots els perifèrics necessaris per l'aplicació i a partir d'allà es té o bé un bucle infinit molt senzill processant dades o es fa tot en funcions de {\em callback} i dins les \gls{ISR} i al final del main tant sols hi ha un bucle infinit sense fer res.
En aquest model, el microcontrolador podrà estar en un mode de baix consum fins que no es generi una interrupció d'algun dels perifèrics, baixant considerablement el consum energètic de tot el sistema (vegeu \fullref{sec:lowpowerstrategies}).
% AIxò pot ser un subaparta?
\chapter{Models de computació}
\label{ch:modescomputacio}
Segons com es dissenyi i codifiqui el còmput de les dades d'entrada per obtenir unes sortides determinades, tenim diferents models de computació. Anem a veure els més comuns.
\section{Bucle de control}
\label{sec:buclecontrol}
Una característica força diferent entre un codi de programa per un sistema encastat respecte a un codi per una aplicació habitual és que en el cas dels encastats el programa no pot acabar mai. I això és perquè el codi de programa és únic, i per tant, si acaba el codi el microcontrolador no tindrà cap altre codi a executar i acabarà per reiniciar-se el sistema (que pot ser el comportament desitjat en algun cas).
Dit això, el cas més senzill d'estil de programació per sistemes encastats és un simple bucle infinit on s'executen les operacions a realitzar per l'aplicació desitjada. Abans d'aquest bucle es configuren i preparen els dispositius i variables necessàries, tal com es pot veure a diferents exemples (\href{https://github.com/mariusmm/cursembedded/blob/master/Simplicity/GPIO_1/}{GPIO\_1}, \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/Printf_SWO}{Printf\_SWO}, \href{https://github.com/mariusmm/cursembedded/blob/master/Simplicity/PWM_1/}{PWM\_1}).
L'exemple \fullref{gpio_example} \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/GPIO_1}{\bf al repositori}) és un exemple senzill d'aquest tipus de codi.
Quan l'aplicació es complica i comença a tenir més camins de decisió i condicions, s'acostuma a canviar el model cap a màquines d'estat finits, com es veurà al següent apartat.
Aquest model de programació és el que es fa servir en Arduino, on s'ha separat en dues funcions, una per configurar els perifèrics i demès (funció {\bf setup()} i la funció principal que es va cridant un cop i un altre (funció {\bf loop()}. La funció {\bf main()} del sistema Arduino bàsicament executa el que es veu al llistat~\ref{arduinomain} (es pot veure a \$ARDUINO\$/hardware/arduino/avr/cores/arduino/main.cpp).
\begin{lstlisting}[style=customc, label=arduinomain, caption=funció main() d'Arduino]
int main(void)
{
...
setup();
for (;;) {
loop();
if (serialEventRun) serialEventRun();
}
return 0;
}
\end{lstlisting}
.
\section{Màquines d'estat finits}
\label{sec:FSM}
Una màquina d'estats (\gls{FSM}, de {\em Finite State Machine} en anglès) és un model de màquina que reacciona a certes entrades i calcula el valor per les sortides, segons l'estat en que estigui. També es pot veure com un seguit d'estats possibles (finit!) en el que pot estar la màquina (el nostre programa) i que va canviant segons les entrades del moment. Les sortides depenen de l'estat on s'està i/o de les entrades (segons el tipus de màquina que s'estigui modelant: màquina de Moore o de Mealy) \cite{wiki:FSM}.
\begin{remark}
Formalment un FSM es pot definir com una 6-tupla de $(S, I, O, F, H, s)$ tal que:
\begin{itemize}
\item $S$ és un conjunt finit d'estats ${s_0,s_1, ..., s_n}$
\item $I$ és un conjunt d'entrades ${i_0, i_1, ..., i_m}$
\item $O$ és un conjunt de sortides ${o_0, o_1, ..., o_k}$
\item $F$ és la funció de transició del tipus: $F: S \times I \to S$
\item $H$ és la funció de sortida tal que:
\begin{itemize}
\item Si la màquina és del tipus Moore: $H$ és del tipus: $H: S \to O$
\item Si la màquina és del tipus Mealy, $H$ és del tipus: $H: S \times I \to O$
\end{itemize}
\item $s \in S$ és l'estat inicial
\end{itemize}
\end{remark}
Així, el diagrama d'estats de la FSM que implementaria l'exemple vist a (veure \fullref{gpio_example} i \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/GPIO_1}{\bf el repositori}) seria el que es veu a la Figura~\ref{fig:FSM_GPIO1}.
\begin{figure}[h!]
\centering
\begin{tikzpicture}[>=stealth',shorten >=1pt,auto,node distance=3cm]
\node[initial,state, fill=ocre!30] (S) {$Off$};
\node[state, fill=ocre!30] (q1) [right of=S] {$On$};
\path[->] (S) edge [loop above] node {'0'} (S)
edge node {'1'} (q1)
(q1) edge [bend left] node {'0'} (S)
edge [loop above] node {'1'} (q1);
\end{tikzpicture}
\caption{FSM per l'exemple GPIO\_1}
\label{fig:FSM_GPIO1}
\end{figure}
On l'entrada ('1' o '0') es correspon amb l'entrada del pin corresponent, i $On$ i $Off$ vol dir que en aquell està el LED encès o apagat.
Aquesta FSM es pot codificar de la manera que es veu al Llistat~\ref{gpio_example_FSMMoore}. En aquest codi es pot veure que dins el el bucle infinit de la funció {\em main} hi ha una estructura {\em switch-case} per cobrir tots els estats. Aquests es defineixen amb un {\em enum} amb els noms desitjats i es crea una variable d'aquest tipus, que s'inicialitza amb l'estat inicial (en aquest cas l'estat {\bf On}). Després, per cada estat s'avalua l'entrada i es pren la definició de quin ha de ser el proper estat. En aquest exemple la FSM és de tipus Moore, i per això la sortida del LED està fixada per cada estat. Si es vol implementar amb una màquina de Mealy el codi hauria de ser tal com es veu al Llistat~\ref{gpio_example_FSMMealy}, on la sortida depèn de l'estat i l'entrada actual.
\begin{lstlisting}[style=customc,caption={Codi d'exemple de GPIO},label=gpio_example_FSMMoore]
enum {On, Off} state;
state = On;
int input_value;
...
/* Infinite loop, FSM*/
while (1) {
input_value = GPIO_PinInGet(gpioPortD, 8);
switch (state) {
case On:
GPIO_PinOutSet(gpioPortD, 7);
if (input_value == 0) {
state = Off;
} else {
state = On;
}
break;
case Off:
GPIO_PinOutClear(gpioPortD, 7);
if (input_value == 1) {
state = On;
} else {
state = Off;
}
break;
case default:
/* something wrong has happened */
GPIO_PinOutClear(gpioPortD, 7);
state = On;
break;
}
}
\end{lstlisting}
\begin{lstlisting}[style=customc,caption={Codi d'exemple de GPIO},label=gpio_example_FSMMealy]
...
switch ( state ) {
case On:
if (input_value == 0) {
GPIO_PinOutClear(gpioPortD, 7);
state = Off;
} else {
GPIO_PinOutSet(gpioPortD, 7);
state = On;
}
break;
case Off:
if (input_value == 1) {
GPIO_PinOutSet(gpioPortD, 7);
state = On;
} else {
GPIO_PinOutClear(gpioPortD, 7);
state = Off;
}
break;
case default:
/* something wrong has happened */
GPIO_PinOutClear(gpioPortD, 7);
state = On;
break;
}
}
\end{lstlisting}
Les FSM son una bona manera d'estructura la solució de l'aplicació, ja que cal dissenyar-les i pensar-les abans de començar a escriure codi. L'ús d'aquests mecanismes també ajuda a reduir el nombre de camins d'execució, cosa que simplifica el test del codi.
\subsection{Màquina d'estats finits estesa}
\label{sec:EFSM}
Les màquines d'estats finits esteses (\gls{EFSM}) amplien el concepte de les FSM amb el de {\bf variables} que mantenen valors interns de manera que la funció de transició pot preguntar per valors d'aquestes variables \cite{wiki:EFSM}.
\begin{remark}
Formalment un EFSM es pot definir com una 8-tupla de $(S, I, O, D, E, U, F, s)$ tal que:
\begin{itemize}
\item $S$ és un conjunt finit d'estats ${s_0,s_1, ..., s_n}$
\item $I$ és un conjunt d'entrades ${i_0, i_1, ..., i_m}$
\item $O$ és un conjunt de sortides ${o_0, o_1, ..., o_k}$
\item $D$ és un espai lineal de j dimensions $D_1 \times D_2 \times ... \times D_j$
\item $E$ és un conjunt de funcions d'activació del tipus: $E: D \to {0, 1}$
\item $U$ és un conjunt de funcions d'actualització del tipus: $U: D \to D$
\item $F$ és la funció de transició del tipus: $F: S \times I \times E \to S \times U \times O$
\item $s \in S$ és l'estat inicial
\end{itemize}
\end{remark}
Amb aquesta mena de màquines d'estats, es poden tenir variables que, per exemple, comptin fins a un cert valor i llavors permetre un canvi d'estat, o que una variable controli un interval de temps, etc.
\subsection{Un exemple amb FSM}
\label{sub:fsm_example}
Veiem un exemple dissenyant un termòstat senzill amb la nostra placa d'avaluació. Es simularà la lectura d'un termòmetre amb el potenciòmetre que ja es va fer servir a l'exemple \fullref{sub:ADC} i es farà servir un dels LEDs per simular que s'engega l'escalfador d'aigua.
Així, per implementar un termòstat senzill, cal implementar la màquina d'estats de la Figura~\ref{fig:FSM_THERMO}. En aquest diagrama d'estats no es dibuixen les transicions que mantenen l'estat, que en aquest cas seria si no es compleixen les condicions de temperatura (si la temperatura és superior a \si{21 \degreeCelsius} i està a l'estat $Off$ es manté l'estat, el mateix per l'estat $On$ si la temperatura està per sota del \si{23 \degreeCelsius}).
\begin{remark}
Que hi hagin dos estats i les transicions entre ells sigui amb temperatures diferents (s'engega a \si{21 \degreeCelsius} i s'apaga amb \si{23 \degreeCelsius}) serveix per no tenir un termòstat engegant-se i parant-se contínuament un cop s'arriba a la temperatura indicada.
\end{remark}
El codi es troba al repositori, al \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FSM_1}{\bf projecte FSM\_1}.
\begin{figure}[h!]
\centering
\begin{tikzpicture}[>=stealth',shorten >=1pt,auto,node distance=4cm]
\node[initial,state, fill=ocre!30] (S) {$Off$};
\node[state, fill=ocre!30] (q1) [right of=S] {$On$};
\path[->] (S)
% edge [loop above] node {$\square$} (S)
edge [bend left] node {$Temp<21$} (q1)
(q1) edge [bend left] node {$Temp>23$} (S)
% edge [loop above] node {$\square$} (q1);
;
\end{tikzpicture}
\caption{FSM d'un termòstat senzill}
\label{fig:FSM_THERMO}
\end{figure}
El codi d'aquest projecte comparteix funcions o crides a la biblioteca \gls{ADC} d'EMLIB ja vistes a \fullref{sub:ADC_example}. S'han encapsulat dins la funció \index{ADCGetValue()}{\bf ADCGetValue()}, que fa {\em polling} de l'ADC per obtenir un valor de conversió (veure el Llistat~\ref{ADCGetValue}).
\index{ADCGetValue()}\index{ADC\_Start()}\index{ADC\_DataSingleGet()}
\begin{lstlisting}[style=customc,caption=funció ADCGetValue(),label=ADCGetValue]
static uint32_t ADCGetValue() {
uint32_t ADCvalue = 0;
ADC_Start(ADC0, adcStartSingle);
while (ADC0->STATUS & ADC_STATUS_SINGLEACT);
ADCvalue = ADC_DataSingleGet(ADC0);
return ADCvalue;
}
\end{lstlisting}
\index{getTemperature()}\index{ADCGetValue()}
\begin{lstlisting}[style=customc,caption=funció getTemperature(),label=getTemperature]
static uint32_t getTemperature() {
uint32_t raw_sensor_value;
uint32_t temperature_value;
raw_sensor_value = ADCGetValue();
/* Fake conversion: just for the example we map 0..4095 values of the ADC to 15..35 celsius degree */
temperature_value = ((raw_sensor_value * 20) / 4095) + 15;
return temperature_value;
}
\end{lstlisting}
La funció \index{getTemperature()}{\bf getTemperature()} utilitza la funció anterior per obtenir un valor de l'ADC, convertir-lo a valor de temperatura (simulada en aquest exemple) i retornar el valor calculat (veure el Llistat~\ref{getTemperature}).
També existeixen unes funcions de simulació {\bf SwitchOff()} i {\bf SwitchOn()} que son les que engegarien i pararien l'escalfador d'aigua. Per les proves aquestes funcions encenen o apaguen un dels LEDs de la placa.
La implementació de la FSM és molt senzilla i es veu al codi del Llistat~\ref{codi_FSM_thermosta} i al \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FSM_1}{repositori}. Primerament es llegeix la temperatura (simulada) i llavors, segons quin estat estigui la màquina, es compara amb un valor o un altre per saber si cal canviar d'estat o mantenir-se en l'actual. A cada estat es crida a la funció de sortida {\bf SwitchOff()} o {\bf SwitchOn()} (la màquina és una màquina d'estats de Moore).
Cal fer notar que la FSM està permanentment consultant el valor del termòmetre (simulat), ja que quan acaba una avaluació de l'estat i pren la sortida oportuna, el codi torna a començar el bucle. Això pot ser un problema en segons quins casos, com el que el sensor a llegir tingui un nombre limita de lectures o calgui un consum del dispositiu molt baix.
\begin{remark}
Tot i que els dos exemples que han aparegut sobre FSMs son amb només dos estats, una màquina d'estats en pot tenir un nombre arbitrari segons les necessitats de l'aplicació.
\end{remark}
\begin{lstlisting}[style=customc,caption=Codi de la FSM per un termòstat senzill,label=codi_FSM_thermosta]
while (1) {
temperature = getTemperature();
switch (state) {
case Thermo_OFF:
if (temperature < MIN_TEMPERATURE) {
state = Thermo_ON;
printf("Temp: %ld. Changing state to Thermo_ON\r\n",
temperature);
}
SwitchOff();
break;
case Thermo_ON:
if (temperature > MAX_TEMPERATURE) {
state = Thermo_OFF;
printf("Temp: %ld. Changing state to Thermo_OFF\r\n",
temperature);
}
SwitchOn();
break;
default:
state = Thermo_OFF;
SwitchOff();
}
}
\end{lstlisting}
\section{Codificant FSMs}
Com ja hem vist al Llistats~\ref{gpio_example_FSMMealy} i \ref{codi_FSM_thermosta}, és relativament senzill codificar una FSM en C. Tot i això, es pot trobar un model genèric per simplificar les coses. Anem a presentar-lo.
Com s'ha dit anteriorment, una FSM consta de 4 operacions:
\begin{itemize}
\item Llegir les entrades. Això provocarà o no canvis en l'estat i les sortides.
\item Calcular els nous valors de les variables.
\item Escriure les sortides que calgui segons l'estat de la màquina d'estats.
\item Calcular l'estat següent segons les entrades llegides, les diverses variables i l'estat actual.
\end{itemize}
L'ordre d'aquestes operacions és indiferent mentre s'executin totes 4 a cada iteració. D'aquesta manera, podem disposar la nostra funció de {\em loop()} tal com es veu al Llistat~\ref{FSMsketch}.
\index{main()}\index{setup()}\index{loop()}
\begin{lstlisting}[style=customc,caption={Estructura bàsica d'una FSM},label=FSMsketch]
void loop() {
read_inputs();
calc_values();
write_outputs();
next_estate();
}
int main(void) {
...
setup();
...
while(1) {
loop();
}
}
\end{lstlisting}
Si adaptem l'exemple del termòstat a aquest mètode de codificació, quedaria tal com es veu al Llistat~\ref{FSMsketchTermostat} i al \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FSM_2}{repositori}.
\index{read\_inputs()}\index{calc\_values()}\index{write\_outputs()}\index{next\_estate()}
\begin{lstlisting}[style=customc,caption={Codi de termòstat amb l'estructura bàsica d'una FSM},label=FSMsketchTermostat]
void read_inputs() {
temperature = getTemperature();
}
void calc_values() {
switch(state) {
case Thermo_OFF:
termostat_on = false;
break;
case Thermo_ON:
termostat_on = true;
break;
}
}
void write_outputs() {
if (termostat_on) {
SwitchOn();
} else {
SwitchOff();
}
}
void next_estate() {
switch(state) {
case Thermo_OFF:
if (temperature < MIN_TEMPERATURE) {
state = Thermo_ON;
}
break;
case Thermo_ON:
if (temperature > MAX_TEMPERATURE) {
state = Thermo_OFF;
}
break;
}
}
\end{lstlisting}
Si la nostra aplicació requereix més d'una FSM, es poden intercalar les crides a cada operació de manera que es vagin executant alternant les FSMs tal com es veu a la Figura~\ref{multiple_FSM}.
\index{loop()}
\begin{lstlisting}[style=customc,caption={Funció de loop amb múltiples FSMs},label=multiple_FSM]
void loop() {
read_inputs_FSM1();
read_inputs_FSM2();
...
read_inputs_FSMN();
calc_values_FSM1();
calc_values_FSM2();
...
calc_values_FSMN();
write_outputs_FSM1();
write_outputs_FSM2();
...
write_outputs_FSMN();
next_estate_FSM1();
next_estate_FSM2();
...
next_estate_FSMN();
}
\end{lstlisting}
Si s'utilitza habitualment aquest mode de programació, és possible que surti a compte muntar-se una estructura de control pròpia que manegui la crida ordenada d'aquestes funcions i sigui senzill registrar una nova FSM per ser executada concurrentment amb les demès. Un esbòs d'aquest {\em kernel} podria ser el que es veu al Llistat~\ref{FSM_kernel}.
\begin{lstlisting}[style=customc,caption={Estructura bàsica d'un {\em kernel} per múltiples FSMs},label=FSM_kernel]
#define MAX_FSM 10
typedef struct {
void (*read_input_func)(void);
void (*calc_values_func)(void);
void (*write_outputs_func)(void);
void (*next_estate_func)(void);
} FSM_t;
FSM_t FSM_array[MAX_FSM]; // can manage MAX_FSM FSMs
bool register_FSM(FSM_t &fsm) {
const int index = 0;
FSM_array[index] = fsm;
index++;
}
void loop() {
int i;
for(i = 0; i < MAX_FSM; i++) {
if (FSM_array[i].read_input_func) {
FSM_array[i].read_input_func();
}
}
for(i = 0; i < MAX_FSM; i++) {
if (FSM_array[i].calc_values_func) {
FSM_array[i].calc_values_func();
}
}
...
}
\end{lstlisting}
Aquest {\em kernel} va executant cada una de les operacions de totes les FSM que s'hi hagin registrat prèviament. D'aquesta manera es pot tenir implementades les FSMs que solucionin la nostra aplicació d'una forma ràpida.
\section{Flux de dades}
\label{sec:dataflow}
TBD
\chapter{Tractament del temps}
\label{ch:tractamenttemps}
Tot i que el tractament del temps (deixar passar un cert temps, esperar per un esdeveniment un cert temps, etc.) es pot fer tant amb FSMs com amb un bucle de control senzill, hi ha models pensats que el tracten específicament.
\section{FSMs amb temps}
Una forma molt senzilla d'afegir temps a una FSM és afegir un temps d'espera a la funció {\em loop()}. Un cop decidit el temps de cada iteració, cal afegir aquest retard ({\em delay}) al final de cada iteració. Així, el codi de la funció {\em loop()} quedarà tal com es veu al Llistat~\ref{FSM_temps} i al \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FSM_3}{repositori}.
\begin{lstlisting}[style=customc,caption={FSM amb control del temps},label=FSM_temps]
const int period = 50; // period time in miliseconds
void loop() {
uint32_t start_time, end_time, iteration_time;
start_time = RTC_CounterGet(); // time value in miliseconds
/* FSM */
read_inputs();
calc_values();
write_outputs();
next_estate();
/* FSM */
end_time = RTC_CounterGet();
iteration_time = end_time - start_time;
delay(period - iteration_time);
}
\end{lstlisting}
D'aquesta manera cada iteració s'executarà amb el període triat sempre que el temps d'execució de les seves operacions no l'excedeixi. Com es veu al codi, el temps a esperar-se de la funció {\em delay()} es calcula a cada iteració, això permet que cada iteració es faci en el període fixat independentment del temps que hagi transcorregut l'execució de les operacions anteriors. Com que sovint les operacions no transcorren en un temps determinista, inserir un simple {\em delay(period)} faria que l'execució de cada iteració es fes en un temps diferent.
En aquest model, la funció {\em delay()} pot manegar opcions de baix consum, de manera que pot fer que el microcontrolador entri a un mode de baix consum mentre transcorre el temps seleccionat.
\section{Tasques periòdiques}
\label{sec:tasquesperiodiques}
Un altre model força habitual de tractar el temps d'una forma senzilla és fent ús de tasques programades. Aquestes tasques son funcions que es criden de forma periòdica i realitzen les tasques necessàries per l'aplicació final. Així, podem tenir un codi similar al del Llistat~\ref{tasquesperiodiquesmain}
\begin{lstlisting}[style=customc,caption={Estructura bàsica de les tasques programades},label=tasquesperiodiquesmain]
void tasca1(void) {
// codi tasca 1
}
void tasca2(void) {
// codi tasca 2
}
void main(void) {
// inicialitzacions
...
// es registren les dues tasques, una es crida cada 5 segons, l'altra cada 15 segons
Registra_tasca(tasca1, 5000);
Registra_tasca(tasca2, 15000);
while(1) {
Executa_kernel();
}
}
\end{lstlisting}
La funció \index{Registra\_tasca()}{\bf Registra\_tasca()} registra la funció que se li passa per a que es cridi cada cert temps. El {\em kernel} s'encarrega de programar algun {em timer} per a que notifiqui el microcontrolador el temps corresponent i, per exemple, pugui posar en un mode de baix consum el microcontrolador mentre aquest temps no arribi.
Com que el microcontrolador es pot despertar per diversos esdeveniments, dins el bucle infinit de la funció \index{main()}{\bf main()} es crida a la funció per a que el {\em kernel}, si s'escau cridi al a funció registrada, torni a programar el {\em timer} com calgui i torni a dormir el microcontrolador. El pseudocodi per aquesta funció seria tal com es veu al Llistat~\ref{tasquesperiodiquestimers}.
\begin{lstlisting}[style=customc,caption={Estructura bàsica de la funció Executa\_kernel()},label=tasquesperiodiquestimers]
void Executa_kernel(void) {
Configurar els timers necessaris
Posar processador en mode de baix consum
/* Aqui el processador esta suspes esperant algun esdeveniment o que es dispari un timer */
Esbrinar quina tasca toca executar-se
Executar tasca
(Opcional) Cridar a una funcio generica de l'usuari
}
\end{lstlisting}
\subsection{Implementació}
En el cas de treballar amb la plataforma EFM32 part d'aquestes funcionalitats les tenim implementades a la biblioteca RTCDRV i SLEEP que pertanyen a la biblioteca d'alt nivell EMDRV del fabricant \cite{EMDRV}.
La primera de les biblioteques, RTCDRV, permet gestionar {\em timers} virtuals fent servir només un {\em Timer} real, com el nom indica, es fa servir el {\em timer} RTC del microcontrolador (veure \fullref{sub:RTC}) \cite{RTCDRV}. D'aquesta manera es simplifica tenir múltiples {\em timers} i permet registrar \glspl{callback} per quan un {\em timer} arriba al seu temps programat de manera que quan un {\em timer} arriba al seu temps d'expiració es crida a la funció de {\em callback} registrada.
La segona llibreria, SLEEP, gestionar automàticament els modes de baixa energia del microcontrolador, fent que aquest entri al mode més baix possible segons els perifèrics que es tenen activats. D'aquesta manera, amb una sola crida a una funció de la biblioteca s'aconsegueix posar el microcontrolador en mode de baix consum \cite{SLEEPDRV}.
D'aquesta manera, la funció {\em Registra\_tasca()} passaria a ser una crida a la funció \index{RTCDRV\_StartTimer()}{\bf RTCDRV\\\_StartTimer()} amb la funció periòdica com a {\em callback} i la resta de paràmetres com calgui ({\em rtcdrvTimerTypePeriodic}, el temps en mil·lisegons, etc.). I la funció {\em Executa\_kernel()} no hauria de fer gran cosa.
Aquesta estratègia te un problema, i és que la funció de {\em callback} se la crida dins del context d'interrupció, cosa no sempre desitjable, ja que les ISR haurien de ser sempre funcions molt curtes, sense gaire funcionalitat, tal com es va explicar a \fullref{ch:IRQ}. Per solucionar això es pot canviar una mica l'estratègia i mantenir una estructura que permeti saber quina funció cal cridar i tenir una funció de {\em callback} única que mantingui aquesta informació. Llavors, a la funció {\em Executa\_kernel()} es comprova si s'ha de cridar alguna funció i llavors es crida des d'allà, fora del context d'interrupció. Això es pot veure al Llistat~\ref{kernel_efm32}.
\index{RTCDRV\_AllocateTimer()}\index{RTCDRV\_StartTimer()}
\begin{lstlisting}[style=customc,caption={Estructura bàsica de la funció Executa\_tasca()},label=kernel_efm32]
/* Aquesta funcio es crida des d'una ISR, ha de ser curta */
static void TimerCallback(RTCDRV_TimerID_t id, void* param) {
int timer;
index = *(int*)param;
my_timers[index].semaphores = true;
}
void Registra_tasca(mycallback_t func, uint32_t period) {
static int i = 0;
my_timers[i].callbacks = func;
my_timers[i].value = i;
RTCDRV_AllocateTimer(&my_timers[i].timers_array);
RTCDRV_StartTimer(my_timers[i].timers_array, rtcdrvTimerTypePeriodic, period, TimerCallback, &i);
i++;
}
static void Executa_tasca(int timeout) {
int i;
for (i = 0; i < EMDRV_RTCDRV_NUM_TIMERS; i++) {
if (my_timers[i].semaphores == true) {
my_timers[i].semaphores = false;
if (my_timers[i].callbacks) {
my_timers[i].callbacks();
}
}
}
}
\end{lstlisting}
Aquest model de programació és força senzill i es podria veure com un pas previ a l'ús d'un RTOS on el maneig de les tasques és mes complex i ens ofereixen mecanismes de sincronització i comunicació entre les tasques més enllà de variables compartides.
\section{Multitasca}
\label{sec:multitasca}
Com ja veurem a \fullref{part:freertos}, és possible tenir multitasca en sistemes encastats. Pot ser una bona forma de tenir múltiples tasques funcionant alhora sense haver de gestionar nosaltres mateixos aquesta complexitat.
Donats l'ús de recursos que es fa i la complexitat a l'hora de programar per aquesta mena de sistemes fa que no sigui la solució idònia per tot tipus d'aplicació encastada.
A més, aquesta estratègia pot incloure també \glspl{FSM}, de manera que una o més tasques de l'aplicació estiguin implementades amb una FSM tal com s'ha comentat a les seccions anteriors.
L'ús de RTOS facilita la comunicació entre tasques,