-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcapitol_4.tex
1261 lines (946 loc) · 76.1 KB
/
capitol_4.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
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
%% Capitol 4
% \part{FreeRTOS}
% \label{part:freertos}
\chapter{Conceptes bàsics de FreeRTOS}
\label{ch:FreeRTOS}
% \chapter{Sistemes Operatius de Temps Real}
En el Firmware per sistemes encastats que hem vist fins ara es basen en un bucle infinit on es van executant les tasques a fer. Això acostuma a ser prou bo per sistemes senzills, com ara llegir d'un \gls{ADC} i decidir alguna cosa, o actuar sobre una sortida segons el valor d'un sensor, etc.
Per sistemes més complexos o amb requeriments crítics, s'acostuma a fer servir un Sistema Operatiu per gestionar diferents tasques.
\begin{remark}
Un sistema és de Temps Real quan el temps de resposta del sistema a un event extern està fitada. És a dir, es pot saber i està garantit el temps total entre que
succeeix un esdeveniment i el sistema genera una sortida.
Un exemple típic és el d'un airbag. El temps màxim, sigui el que sigui que el sistema estigui fent entre que es detecta l'accident i es dispara l'airbag està garantit i limitat.
Un Sistema Operatiu de Temps Real (RTOS en anglès) és un Sistema Operatiu que està dissenyat per garantir aquesta fita de temps.
\end{remark}
\glsreset{RTOS}
En aquest llibre farem servir \gls{FreeRTOS} \cite{freertos}, un \gls{RTOS} de codi obert àmpliament utilitzat. En el cas de EFM32 i STM32 els fabricants ens proporcionen el {\em porting} de FreeRTOS a les seves plataformes.
\begin{remark}
Se'n diu {\em porting} al fet d'adaptar un codi a una plataforma específica. Per exemple, en el cas del FreeRTOS, cal adaptar una sèrie de funcions per a que tot el sistema funcioni, com ara la gestió dels rellotges, la gestió de tasques, etc.
\end{remark}
En tot \gls{OS} (i \gls{RTOS}) les feines a fer per part del sistema es reparteixen en diferents tasques (\glspl{task} en anglès). Aquestes tasques s'executen “simultàniament” i, per tant, cal repensar bé tot el sistema abans de començar a escriure el codi.
En sistemes encastats, un SO (o RTOS) no ofereix totes les funcionalitats a les que estem acostumats quan sentim parlar d'un SO. Així, normalment el que ens ofereix un RTOS és:
\begin{itemize}
\item Gestió de tasques: Creació, execució, estat de les tasques, prioritats de tasques, etc.
\item Comunicació entre tasques: semàfors, cues, etc.
\item Gestió de temps: Timers, {\em timeouts}, {\em delays}, etc.
\end{itemize}
Les tasques són les unitats bàsiques de funcionament i és on s'implementen les funcionalitats del sistema.
\section{Temps Real}
Com ja s'ha dit, FreeRTOS és un Sistema Operatiu de Temps Real, que vol dir que totes les seves operacions tenen un temps d'execució fitat de manera que permet construir aplicacions de Temps Real.Aquest temps fita ve donat perquè tot les funcions del Sistema Operatiu son deterministes, això és, es pot saber {\em a priori} quin temps tardaran a executar-se i, en principi, no han de dependre de factors externs. Així, per exemple, desbloquejar una tasca que està depenent d'un semàfor sempre tardarà el mateix temps per una plataforma donada, fer un {\em context switch} entre tasques el mateix, encara que hi hagi dues o cinquanta tasques preparades, etc.
Això és especialment important quant la nostra aplicació ha de reaccionar ràpidament a algun esdeveniment extern, ja que podrem mesurar i/o calcular la fita màxima de la operació crítica i podrem confiar en que el sistema sempre estarà fitat per aquest valor siguin les condicions que siguin.
Quan es parla de temps real, usualment se'n fa una classificació en dos tipus: {\em soft real-time} i {\em hard real-time}. S'entén per {\em soft real-time} aquella aplicació en que existeix alguna restricció de temps en algun moment, però aquesta restricció es pot incomplir sense que l'aplicació falli. Un exemple podria ser una aplicació on cal mostrar vídeo per una pantalla; aquesta aplicació tindrà una fita tal que permeti mostra rel vídeo de forma prou suau, però si mai es per un {\em frame} l'aplicació no falla, si no que l'usuari veurà un artefacte estany a la pantalla. En canvi, un sistema que es defineixi com {\em hard real-time} no pot incomplir en cap moment aquesta restricció temporal. Un exemple típic és l'{\em airbag} d'un cotxe, que cal que es dispari en el moment adequat i que, en canvi, pot provocar danys si es dispara més tard.
Cal fer notar que precisament per la condició de cricitat i de determinisme, hem d'escriure les funcions d'\gls{ISR} el més curt possibles ja que, a la majoria de les plataformes, quan s'està executant una ISR estan desactivades les \glspl{IRQ} i el RTOS està ``venut'', en el sentit que no pot interrompre ni controlar el temps que es gasta en la ISR.
\section{Tasques}
Una tasca és el que es coneix com a procés en els Sistemes Operatius de propòsit general. Per tant, una tasca serà l'estructura mínima de codi en que es divideix una aplicació. Aquestes tasques seran les que el planificador o {\em scheduler} del Sistema Operatiu anirà executant i retirant d'execució segons certes condicions que veurem a continuació.
Bàsicament una tasca pot estar en l'estat {\em Running} (executant-se), {\em Ready} (disponible per ser executada) o {\em Blocked} (no preparada perquè està esperant a algun esdeveniment)\footnote{També hi ha l'estat {\em Suspended} on la pròpia tasca demana sortir de la llista de {\em Ready}}. A la Figura~\ref{fig:taskstate} es pot veure quins estats pot tenir una tasca en FreeRTOS \cite[92]{FreeRTOSBook}.
\begin{figure}
\centering
\fbox{\color{ocre}\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/tskstate.png}}
\caption{Estats possibles d'una tasca}{Estats possibles d'una tasca. \copyright\ FreeRTOS}
\label{fig:taskstate}
\end{figure}
Les tasques són funcions amb un bucle infinit i una inicialització prèvia i en cap cas la funció pot retornar. Si una tasca ja no és necessària, es pot eliminar per part el RTOS, però no pot acabar retornant la funció per si mateixa.
Pel cas de \gls{FreeRTOS}, tenen l'aspecte que es mostra al Llistat~\ref{task}.
\index{OneTask()}
\begin{lstlisting}[caption={Esquelet d'una tasca},style=customc,label=task]
static void OneTask(void *pParameter) {
(void) pParameter;
/* Init */
while(1) {
/* bucle infinit */
}
}
\end{lstlisting}
Una aplicació basada en un RTOS és un conjunt de tasques funcionant concurrentment i comunicades entre elles. El \gls{RTOS} ens dona diferents funcions que ens permeten: comunicar tasques entre elles o bloquejar-se per un cert temps.
\subsection{Prioritats}
Quan es creen les tasques, a cada una se li assigna una certa prioritat. Aquesta prioritat marcarà quina de les tasques de l'estat {\em Ready} passarà a executar-se {\em Running} a cada moment. A \gls{FreeRTOS} les tasques amb una prioritat d'un valor més baix tenen menor prioritat (0 és el valor més baix i menys prioritari). Diferents tasques poden tenir la mateixa prioritat.
Existeix una tasca {\em Idle} que s'executa cada cop que cap altra tasca està a l'estat {\em Ready}. Aquesta tasca té la prioritat més baixa, de valor 0 i per tant només s'executa quan cap tasca està llesta. La tasca {\em Idle} sempre està disponible per executar-se a qualsevol moment.
Per saber més sobre prioritats i com assignar-les a les tasques, veieu \fullref{sec:priorities_RMA}.
\subsection{L'ús de l'{\em stack} en un S.O.}
\label{sub:StackOS}
Per crear una tasca, un dels paràmetres que es passa a la funció {\bf xTaskCreate}\index{xTaskCreate()} és la mida de l'\gls{stack} per la tasca que s'està creant.
Se'n diu {\em stack} a la regió de memòria assignada a una tasca que s'utilitza bàsicament per dues coses:
\begin{itemize}
\item guardar els valors dels paràmetres de les diferents crides a funcions que faci la tasca
\item emmagatzemar el seu context quan el SO retira la tasca d'execució.
\end{itemize}
Aquest \gls{stack} normalment es gestiona com una pila (amb els mètodes {\em push} i {\em pop}) i acostuma a situar-se al final de la memòria i “créixer cap avall”.
A priori, és molt difícil saber quant {\em stack} farà servir una tasca, així que el més habitual és donar un valor per defecte prou gran i després, durant el funcionament normal, observar quanta se'n fa servir realment.
Per saber el nivell màxim a on s'ha arribat a l'{\em stack} de cada tasca, FreeRTOS fa servir un mètode una pel peculiar: a l'inicialitzar la tasca (i el seu {\em stack}) omple l'{\em stack} amb un valor predeterminat i conegut. Després, quan ja està funcionant el nostre sistema, només cal comptar quantes posicions de l'{\em stack} mantenen el valor conegut. Si s'acosta a 0 voldrà dir que la tasca està fent servir la majoria de l'espai de l'{\em stack}.
Per activar aquesta funcionalitat cal editar el fitxer {\bf FreeRTOSConfig.h} i definir la macro {\bf configCHECK\_FOR\_STACK\_OVERFLOW} amb el valor 2 i la macro {\bf uxTaskGetStackHighWaterMark} a 1.
Activant la primera de les macros, el sistema operatiu també controla que no s'intenti accedir fora de l'{\em stack}. D'aquesta manera podem saber si cal més {\em stack}, però no si ens en sobra ni quanta. En activar aquesta opció cal definir una funció de hook que s'anomeni {\bf vApplicationStackOverflowHook()}\index{vApplicationStackOverflowHook()} i que es cridarà cada cop que es detecti un error en accedir l'{\em stack}.
%\section{Scheduler}
% {\bf taskYIELD()}\index{taskYIELD()}
\section{El temps en un RTOS}
\label{sec:RTOS_time}
Habitualment un RTOS ha de portar un control del temps per decidir quina tasca s'ha d'executar, saber quan desbloquejar una tasca, etc.
Normalment es basen en el concepte de \gls{tick}. Un {\em tick} es genera de forma periòdica (normalment fent servir algun Timer) i la freqüència d'aquest {\em tick} ens marca el temps mínim que pot manegar el RTOS.
En el cas de FreeRTOS, a cada {\em tick} es deixa d'executar la tasca que està {\em running} i s'executa el planificador del RTOS. Si aquest planificador detecta que una tasca més prioritària està a punt per ser executada (estat {\em ready}), la posarà a executar ({\em running}) enlloc de la que hi havia fins feia un moment.
La freqüència d'aquest {\em tick} pot variar molt segons el sistema que tinguem, però acostuma a anar entre els 1000 Hz i els 50Hz. Segons la freqüència del {\em tick} tindrem un sistema més ràpid de resposta en segons quins casos a canvi d'alentir lleugerament l'execució general del sistema i de tenir menys resolució en les funcions de control de temps.
\subsection{Funcions per controlar el temps}
Tot OS proporciona una sèrie de funcions per gestionar el temps, això és:
\begin{itemize}
\item bloquejar una tasca durant un cert temps determinat.
\item controlar el {\em timetout} a certes crides del mateix.
\end{itemize}
\subsubsection{\em Delays}
Les funcions {\bf vTaskDelay()}\index{vTaskDelay()} i {\bf vTaskDelayUntil()}\index{vTaskDelayUntil()} són les dues funcions que ens permeten bloquejar la tasca que la crida durant el temps que s'especifica com a paràmetre.
Aquestes dues funcions faran que la tasca entri immediatament a l'estat {\em blocked} i restarà en aquest estat durant tota l'estona que s'especifiqui. Un cop hagi passat aquest temps la tasca passarà a l'estat {\em Ready}.
El paràmetre que rep la funció {\bf vTaskDelay()}\index{vTaskDelay()} és el nombre de {\em ticks} que es vol que la tasca estigui {\em blocked}. Com més precís sigui el {\em tick} del sistema (més freqüència) més acurat podrà ser aquest {\em delay}.
Cal veure que segons la prioritat de la tasca en qüestió i de la prioritat de la tasca que s'estigui executant, aquesta tasca pot romandre en l'estat {\em Ready} un temps indefinit.
\subsubsection{\em Timeout}
A les funcions d'accedir a un recurs compartit, com un semàfor o una cua, apareix un paràmetre que és el temps (en {\em Ticks}) que la tasca que accedix esperà a poder realitzar l'operació. Així, si s'especifica un temps de 100 mil·lisegons per llegir d'una cua, la funció retornarà passat aquest temps si la cua està buida i ningú hi ha escrit res o retornarà tant bon punt algú hi escrigui alguna dada.
\section{Interrupcions a FreeRTOS}
\label{ch:FreeRTOSIRQ}
FreeRTOS deixa el maneig de les interrupcions a mans del desenvolupador, demanant unes certes condicions.
Cal tenir en compte que les interrupcions són esdeveniments totalment asíncrons i imprevisibles i que prenen el control de forma automàtica. Això fa que mentre està funcionant una \gls{ISR} el {\em kernel} del Sistema Operatiu no es pot executar i que, quan acabi d'executar la ISR, si no fem res, tornarà el control cap a la tasca que s'estava executant. Això pot provocar que una ISR alliberi un recurs o posi disponible una dada i que una tasca d'alta prioritat passi a l'estat {\em Ready} però el {\em kernel}, com que no s'executa, no pugui passar-li l'execució i se segueixi amb la taca menys prioritària que s'estava executant.
Per això les funcions per accedir a recursos com semàfors o cues des d'una \gls{ISR} tenen un paràmetre extra, que informa si s'ha despertat una tasca més prioritària. Si és el cas, cal que el codi de la ISR faci un {\bf portYIELD\_FROM\_ISR()}\index{portYIELD\_FROM\_ISR()} per cridar al {\em kernel} del Sistema Operatiu (veure Llistat~\ref{ISRYield}.
A la Figura~\ref{fig:FreeRTOSISR} hi ha un diagrama de seqüència d'un exemple amb dues tasques: la Tasca 1 és la més prioritària i es bloqueja esperant rebre una dada per una cua. Quan es bloqueja s'executa la Tasca 2. Mentre s'està executant arriba una IRQ que posa una dada a la cua de la Tasca1 i retorna. Com que el {\em kernel} no pot obté el control, es segueix executant la Tasca 2, menys prioritària. Al diagrama de la Figura~\ref{fig:FreeRTOSISRYield} succeeix el mateix que abans, però ara la \gls{ISR} crida a {\bf portYIELD\_FROM\_ISR()} en acabar i llavor es passa a executar el {\em kernel} i aquest dona l'execució a la Tasca 1. Aquest és el funcionament correcte que s'espera del sistema.
\index{any\_IRQHandler()}\index{xSemaphoreGiveFromISR()}\index{portYIELD\_FROM\_ISR()}
\begin{lstlisting}[style=customc,caption=Codi ISR d'exemple,label=ISRYield]
void any_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
...
/* Toggle semaphore */
xSemaphoreGiveFromISR(semaphore_button_0, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
\end{lstlisting}
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/FreeRTOSISR.png}
\caption{Diagrama de seqüència de dues tasques}
\label{fig:FreeRTOSISR}
\end{figure}
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/FreeRTOSISRYield.png}
\caption{Diagrama de seqüència de dues tasques correcte}
\label{fig:FreeRTOSISRYield}
\end{figure}
Aquesta funció de {\em yield} retorna de la ISR i executa el {\em kernel} si la variable passada té un valor diferent a {\bf pdFALSE}.
Sempre es diu que les \glspl{ISR} han de ser el més curtes possibles, això és pels següents motius:
\begin{itemize}
\item Mentre s'està executant una \gls{ISR} no es pot executar cap tasca, per molt prioritària que sigui.
\item Depenent de l'arquitectura, mentre s'està executant una ISR la resta d'\glspl{IRQ} estan desactivades.
\item Algunes arquitectures poden permetre anidar interrupcions, cosa que augmenta la complexitat i la incertesa de tot el sistema. Quan més curta sigui la ISR més improbable que això passi.
\end{itemize}
És per això que les bones pràctiques diuen que el codi dins una \gls{ISR} hi hagi poc codi i es facin servir semàfors o cues per notificar tasques on s'executin les operacions pertinents amb les prioritats adequades.
\chapter{Primer exemple amb FreeRTOS}
\label{sec:FreeRTOS_exemple_1}
El \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FreeRTOS_Blink}{primer exemple} és el nostre vell conegut ‘Hello World' per embedded, és a dir, blinkar un LED.
L'exemple consisteix en una sola tasca que s'encarrega de blinkar el LED. Com totes les tasques, consisteix en un bucle infinit on s'inclou tota la funcionalitat de la tasca (Llistat \ref{HelloWorldFreeRTOSTask}).
\index{TaskLedToggle()}\index{LedToggle()}\index{vTaskDelay()}
\begin{lstlisting}[caption={Tasca TaskLedToggle per FreeRTOS},style=customc,label=HelloWorldFreeRTOSTask]
static void TaskLedToggle(void *pParameter) {
(void) pParameter;
for (;;) {
LedToggle();
vTaskDelay(pdMS_TO_TICKS(500));
}
}
\end{lstlisting}
Aquesta tasca fa servir una funció del RTOS ({\bf vTaskDelay()}\index{vTaskDelay()}) que bloqueja la tasca per un determinat temps. Així doncs, aquesta tasca tant sols farà Toggle al LED cada mig segon. Cal fixar-se que aquesta funció rep com a paràmetre el nombre de {\em ticks} a esperar-se. Aquest número es calcula amb la macro {\bf pdMS\_TO\_TICKS()}\index{pdMS\_TO\_TICKS()}, que passa de mil·lisegons a {\em ticks}.
Al {\bf main()}\index{main()} el que veiem és que, després de la inicialització habitual hem de crear les tasques que tingui el nostre sistema i tot seguit engegar el FreeRTOS (Llistat \ref{HelloWorldFreeRTOSMain}).
\index{main()}\index{xTaskCreate()}\index{vTaskStartScheduler()}
\begin{lstlisting}[caption={Main HelloWorld per FreeRTOS},style=customc,label=HelloWorldFreeRTOSMain]
main() {
...
/* Create our first task */
xTaskCreate(TaskLedToggle, (const char *) "LedToggle",
configMINIMAL_STACK_SIZE, NULL, TOGGLE_TASK_PRIORITY, NULL);
/* Start FreeRTOS Scheduler */
vTaskStartScheduler();
...
}
\end{lstlisting}
La funció {\bf xTaskCreate()\index{xTaskCreate()}} rep diferents paràmetres:
\begin{enumerate}
\item Punter a la funció que implementa la tasca
\item Nom que li posem a la tasca (per debug)
\item \gls{stack} reservat per la tasca (veure~\fullref{sub:StackOS})
\item Punter a paràmetres per la tasca
\item Prioritat de la tasca
\item {\em Handle} a la tasca creada
\end{enumerate}
Aquesta funció retorna {\bf pdPASS} si s'ha creat la tasca o un error en cas contrari.
La funció {\bf vTaskStartScheduler()\index{vTaskStartScheduler()}}, que en condicions normals no retorna mai, comença l'execució del FreeRTOS i aquest, al seu torn, executarà la nostra tasca.
% A continuació veurem diversos exemples de com comunicar tasques fent servir semàfors, mutex i cues.
% \chapter{Tasques a S.O.}
\chapter{Controlant el temps a les tasques}
Habitualment les tasques dins d'un sistema amb un Sistema Operatiu s'executen periòdicament, de manera que la tasca fa la seva feina i després demana suspendre's per un temps determinat. Les tasques, doncs poden cridar a la funció {\bf vTaskDelay()} per demanar al {\em kernel} que la suspengui un determinat temps passat com a paràmetre. Per exemple, al codi \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FreeRTOS_Blink}{FreeRTOS\_Blink} la tasca principal te l'estructura del Llistat~\ref{FreeRTOS_BlinkTask}. En aquest cas senzill, la tasca anirà canviant el LED d'encès a apagat cada 500 mil·lisegons
\index{TaskLedToggle()}\index{LedToggle()}\index{vTaskDelay()}\index{pdMS\_TO\_TICKS()}
\begin{lstlisting}[style=customc, label=FreeRTOS_BlinkTask, caption=Tasca de l'exemple FreeRTOS\_BlinkTask]
static void TaskLedToggle(void *pParameter) {
for (;;) {
LedToggle();
vTaskDelay(pdMS_TO_TICKS(500));
}
}
\end{lstlisting}
Però què passaria si el temps d'execució de la tasca fos variable? Si tenim un codi més complicat que segons les condicions tardi més o menys temps, es tindrà que la tasca tarda un cert temps variable a executar-se i després es suspèn durant 500 mil·lisegons. Això farà que la seva periodicitat variï a cada execució de la tasca, cosa que pot ser inacceptable segons els casos.
Per això FreeRTOS proporciona la funció \index{vTaskDelayUntil()}{\bf vTaskDelayUntil()} que te en compte el tems utilitzat per la tasca abans de suspendre-la, de manera que es torni a cridar exactament en el període de temps desitjat.
\section{Un exemple amb vTaskDelayUntil()}
A l'exemple \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FreeRTOS_Delay}{FreeRTOS\_Delay} es fa servir la funció \index{vTaskDelayUntil()}{\bf vTaskDelayUntil()} dins una tasca que cada cop que s'executa un cicle te un temps diferent d'execució. Això és fa amb una crida a \index{vTaskDelay()}{\bf vTaskDelay()} amb un retard aleatori amb valors entre 0 i 300 mil·lisegons.
Després d'aquesta execució es crida la funció {\bf vTaskDelayUntil()} per suspendre de manera que la seva periodicitat sigui sempre de 500 mil·lisegons (veure Llistat~\ref{FreeRTOS_Delay}).
\begin{lstlisting}[style=customc, label=FreeRTOS_Delay, caption=Tasca de l'exemple FreeRTOS\_Delay]
static void TaskLedToggle(void *pParameter) {
...
for (;;) {
LedToggle();
printf("%lu", xTaskGetTickCount());
/* Random delay from o to 300 ms. */
random_time = rand() % 300;
printf("\t %lu\r\n", random_time);
delay_time = pdMS_TO_TICKS(random_time);
vTaskDelay(delay_time);
/* We want the task exactly every 500 milliseconds independently
* from the (different) execution time */
vTaskDelayUntil(&previous_tick_time, pdMS_TO_TICKS(500));
}
}
\end{lstlisting}
Per comprova el correcte funcionament es treu per la consola de {\em debug} el {\em tick} en que s'està executant i el temps aleatori que es gasta per la tasca (veure Figura~\ref{fig:TaskDelayConsole}). Es pot veure com, a la primera columna, el tick on s'executa la tasca és sempre múltiple de 64 i que el temps que està executant-se la tasca va variant (segona columna).
\begin{figure}
\centering
\includegraphics[width=0.65\textwidth, keepaspectratio]{imatges/TaskDelayUntilConsole.png}
\caption{Consola amb la sortida de l'exemple FreeRTOS\_Delay}
\label{fig:TaskDelayConsole}
\end{figure}
\subsection{Comprovació amb l'oscil·loscopi}
Es pot aprofitar que l'exemple fa {\em toggle} del LED el GPIO també està connectat a un pin del connector d'expansió de la placa de desenvolupament (al pin 15 del {\em Expansion header}).
A més, s'ha reduït el temps del període a 100 mil·lisegons i un {\em delay} aleatori d'entre 0 i 75 mil·lisegons. Així es pot comprovar amb l'oscil·loscopi que el senyal generat és prou bo i estable (veure Figura~\ref{fig:DelayUntil}).
\begin{figure}
\centering
\includegraphics[width=0.65\textwidth, keepaspectratio]{imatges/DelayUntil.png}
\caption{Captura de l'oscil·loscopi de l'exemple vTaskDelayUntil()}
\label{fig:DelayUntil}
\end{figure}
%%%%%%%% AMPLIAR
\chapter{Comunicació entre tasques}
En aquest capítol es detallen diferents mecanismes de sincronització i comunicació entre tasques en un Sistema Operatiu encastat.
Qualsevol RTOS que porti aquest nom ens oferirà una sèrie de mecanismes per a comunicar tasques entre elles. Els mecanismes més habituals són:
\begin{itemize}
\item Semàfors: Una tasca no pot agafar un semàfor fins que una altre no l'allibera.
\item Cues: Permeten enviar informació d'una tasca a una altra.
\item {\em Mutex}: Permet protegir un recurs compartit de manera que només una tasca el faci servir en un moment donat.
\end{itemize}
N'hi ha d'altres, com esperar i enviar esdeveniments o grups d'esdeveniments, {\em mailboxes}, senyals, etc. que acostumen a ser pròpies de cada \gls{OS} concret.
\section{Semàfors}
Un semàfor és un dels mecanismes de comunicació entre tasques mes habituals dels que ofereix un OS. En essència el funcionament d'un semàfor és tal que una tasca prova d'agafar el semàfor i es quedarà esperant que una altra tasca doni el semàfor o ho tornarà a provar més endavant \cite[244]{EmbeddedDictionary} \cite[187]{programmingembedded}.
Habitualment es fan servir per sincronitzar almenys dues tasques que comparteixen el semàfor o per protegir una secció crítica.
\subsection{Semàfors a FreeRTOS}
\label{sub:semaphores}
A FreeRTOS tenim diferents tipus de semàfors:
\begin{itemize}
\item {\em Binary}: pot tenir només l'estat ‘agafat' o ‘donat'. Es fan servir per sincronitzar tasques.
\item {\em Counting}: s'emmagatzema un número, que s'incrementa en ‘donar' el semàfor i es redueix en ‘agafar' el semàfor. Sempre que tingui un valor positiu es podrà ‘agafar' el semàfor. Serveix per portar un compte del nombre de recursos disponibles.
\item {\em Mutex}: són una variant dels semàfors binaris que inclouen mecanismes d'herència de prioritats. Es fan servir per implementar l'exclusió mútua. En parlarem més endavant, a \fullref{sec:Mutex}.
\end{itemize}
\subsection{Exemple amb semàfors}
\label{sub:semafors_exemple}
A \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FreeRTOS_Semaphore}{l'exemple} hi ha una tasca que es queda esperant a agafar un semàfor i quan l'aconsegueix fa un {\em toggle} del LED (veure Llistat~\ref{semaphore_task_example} i el diagrama de seqüència~\ref{fig:SeqDiagramSemaphore}).
\index{TaskLedToggle()}\index{xSemaphoreTake()}\index{LedToggle()}
\begin{lstlisting}[style=customc,caption=Tasca amb semàfor d'exemple,label=semaphore_task_example]
static void TaskLedToggle(void *pParameter) {
(void) pParameter;
for (;;) {
xSemaphoreTake(semaphore_button_0, portMAX_DELAY);
LedToggle();
}
}
\end{lstlisting}
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/SemaphoreFreeRTOS.png}
\caption{Diagrama de seqüència de l'exemple amb semàfors}.
\label{fig:SeqDiagramSemaphore}
\end{figure}
La funció {\bf main()}\index{main()} crida la funció {\bf BSP\_Init()}\index{BSP\_Init()} que configura els GPIOs corresponents als botons i es registra una \gls{ISR} ({\bf GPIO\_EVEN\_IRQHandler()}\index{GPIO\_EVEN\_IRQHandler()}) pel botó 0, que ‘dóna' el semàfor en quan es prem el corresponent botó a la PCB (Llistat~\ref{ISR_semaphore}).
Després es crea el semàfor que compartiran la tasca i la ISR i tot seguit es crea la tasca com ja hem vist a l'exemple anterior..
Per últim s'engega el {\em kernel} del RTOS.
Cal notar que les funcions per agafar o donar un semàfor són diferents segons estiguem a una tasca o a una \gls{ISR}. En el cas de la ISR, la funció de donar al semàfor ens indica si hi ha alguna tasca que cal desbloquejar perquè l'està esperant. En cas que sigui cert, cal fer un {\em yield} des de la \gls{ISR} per a que el planificador ({\em scheduler}) del RTOS pugui actuar immediatament.
\index{xSemaphoreGiveFromISR()}\index{portYIELD\_FROM\_ISR()}
\begin{lstlisting}[caption=ISR del botó 0,style=customc,label=ISR_semaphore]
ISR() {
...
/* Toggle semaphore */
xSemaphoreGiveFromISR(semaphore_button_1, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
\end{lstlisting}
Tant en aquest exemple com en els següents el {\em timeout} de les crides és {\bf portMAX\_DELAY}. Aquesta macro serveix per indicar a la funció que es vol esperar un temps infinit a que l'operació es pugui realitzar. En aquest cas, la funció cridada no retornarà fins que pugui executar l'acció i bloquejant la tasca el temps necessari. D'aquesta mena de crides a funcions se'n diu crides bloquejants.
\begin{exercise}
Es pot provar d'implementar un codi que blinki el LED tantes vegades com vegades s'ha premut el botó 0. És pot fer amb un semàfor tipus {\em counting semaphore}.
\end{exercise}
\section{Cues}
\label{sec:queue}
Hem vist que els semàfors són útils per sincronitzar tasques i per protegir zones d'exclusió mútua, però no ens donen cap solució senzilla per enviar informació o dades d'una tasca a una altra. Aquesta comunicació és per les cues \cite[102]{FreeRTOSBook}.
Ens podem imaginar una cua com un recurs compartit entre dues o més tasques, on unes poden escriure-hi i d'altres hi poden llegir dades. Habitualment (en FreeRTOS és així), les cues s'implementen amb una estructura tipus FIFO (First-In First-Out) protegida de tal manera que no hi hagi cap \gls{race condition} durant el seu funcionament\footnote{S'anomena {\em race condition} al malfuncionament d'un codi donat que està executant-se en un entorn multitasca. Aquesta mena d'errors poden ser molt difícils de detectar i arreglar}.
A més, per tal de poder implementar sistemes d'una forma senzilla, els accessos a les cues poden ser bloquejants: la tasca que fa l'accés es quedarà bloquejada fins que pugui fer l'accés (esperar a poder escriure una dada, donat que no podia perquè la cua era plena) o esperar fins que hi hagi una dada (perquè s'ha intentat llegir de la cua quan aquesta era buida).
Les operacions habituals a una cua son:
\begin{itemize}
\item Crear una cua ({\em create}): normalment cal especificar quin tipus de dades ha d'emmagatzemar la cua i quants espais o llocs cal preparar.
\item Inserir una dada ({\em send}): afegir (si hi ha lloc) una dada nova a la cua.
\item Llegir dada ({\em receive}): treure una dada (si n'hi ha) de la cua.
\end{itemize}
Altres operacions poden ser:
\begin{itemize}
\item Mirar si hi ha una dada disponible (sense llegir-la) ({\em peek}).
\item Esborrar tot el contingut de la cua ({\em reset}).
\item Inserir una dada al principi de la cua (sense complir que la cua és una FIFO).
\end{itemize}
Les cues són recursos que poden ser compartits per vàries tasques, podent-hi escriure diferents tasques o \glspl{ISR} i poder llegir-la també diferents tasques, tot i que això de tenir múltiples tasques llegint d'una cua no és gaire habitual.
Per saber quina mida han de tenir les cues, veieu \fullref{sec:mida_cues}.
\subsection{Exemple amb cues}
\label{sub:cues_exemple}
A \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FreeRTOS_Queue}{l'exemple de cues} hi ha una sola tasca que fa {\em blinkar} el \gls{LED} segons una variable. Aquesta variable s'obté de llegir (o intentar-ho) una cua. Aquesta cua (anomenada {\em queue\_buttons}) l'escriuen les dues \gls{ISR} associades als dos botons. Una envia el valor corresponent a 250 ms i l'altre ISR envia el valor que correspon a 1000 ms (Llistat~\ref{Queue_example_ISR} i diagrama de seqüència~\ref{fig:SeqDiagramQueue}).
\index{xQueueSendFromISR()}\index{portYIELD\_FROM\_ISR()}
\begin{lstlisting}[style=customc, label=Queue_example_ISR, caption=Part del codi d'una de les ISRs]
...
/* Send the data to the Queue */
xQueueSendFromISR(queue_buttons, (void* ) &new_delay,
&xHigherPriorityTaskWoken);
/* Awake a task ? */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
...
\end{lstlisting}
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/QueueFreeRTOS.png}
\caption{Diagrama de seqüència de l'exemple de cues}
\label{fig:SeqDiagramQueue}
\end{figure}
En aquest cas, la tasca fa servir la funció {\bf xQueueReceive()}\index{xQueueReceive()} amb un valor de 0 a l'últim paràmetre, que és el temps en {\em ticks} que ha d'esperar-se a rebre un valor en el cas que la cua estigui buida (veure Llistat~\ref{queue_example_main}).
\index{xQueueReceive()}\index{vTaskDelay()}\index{LedToggle()}\index{TaskLedToggle()}
\begin{lstlisting}[style=customc,label=queue_example_main, caption=Part principal de la tasca TaskLedToggle]
for (;;) {
/* try to get new delay time from queue */
if (xQueueReceive(queue_buttons, &recv_delay, (TickType_t ) 0)) {
my_delay = recv_delay;
}
/* wait for m_delay & toggle the LED */
vTaskDelay(my_delay);
LedToggle();
}
\end{lstlisting}
En aquest exemple volem que encara que no hi hagi dada a la cua, la tasca segueixi executant-se. Per saber si la funció de rebre dades ha obtingut una dada nova, cal comprovar si ha retornat \gls{pdTRUE}. Si retorna \gls{pdFALSE} és que ha acabat el temps d'espera (en el nostre cas 0 {\em ticks}) i no ha pogut extreure un nou valor de la cua. Aquest és un exemple d'accés no bloquejant a una cua.
Les cues cal crear-les abans d'utilitzar-les cridant a la funció {\bf xQueueCreate()}\index{xQueueCreate()}. Aquesta funció crea la cua amb la longitud desitjada i amb la capacitat indicada (Llistat~\ref{queue_example_create}).
Usualment això es crea a la funció {\bf main()}\index{main()} o, en tot cas, abans d'engegar el Sistema Operatiu.
\index{xQueueCreate()}
\begin{lstlisting}[style=customc, label=queue_example_create, caption=Creació d'una cua]
/* Create Queue */
queue_buttons = xQueueCreate(QUEUE_LENGTH, sizeof(uint32_t));
\end{lstlisting}
% A les cues de FreeRTOS hi ha una particularitat que cal tenir en compte, i és que les funcions que accedeixen a la cua treballen amb punters a les dades, no amb les dades mateixes. Això es fa per evitar còpies de les dades innecessàries a l'stack (ho veurem més endavant).
%
% Per això a l'exemple tenim:
% \begin{lstlisting}[style=customc,label=ISR_semaphore]
% xQueueSendFromISR(queue_buttons, (void* ) &new_delay,
% &xHigherPriorityTaskWoken);
% \end{lstlisting}
%
% on new_delay està definit de tipus uint32_t.
%
% Això vol dir que la variable on es guarden les dades que s'envien per la cua cal que segueixi viva fins que la tasca que rep la dada pugui llegir-la. Així, cal vigilar amb variables automàtiques:
\subsection{Enviant múltiples dades per una cua}
\label{sub:dades_cua}
Quan ens cal enviar diverses dades d'una tasca a una altra (o d'una ISR a una tasca, o d'una tasca a una ISR, etc.) la forma més senzilla de fer-ho és fent servir una estructura per fer un paquet.
Suposem que tenim una tasca que llegeix dades d'un acceleròmetre, que treu dades de 16 bits (uint16\_t) per cada un dels eixos (X, Y, Z). Aquestes dades les volem enviar a una segona tasca que fa els càlculs pertinents per l'aplicació i, per tant, posem una cua entre les dues tasques.
Per enviar el triplet de dades d'una tasca a l'altra, podríem muntar una cua on enviéssim les dades per ordre (primer X, després Y, després Z, altre cop X, després Y, etc.). Això ens podria portar problemes si la tasca que rep les dades perd una dada, ja que llavors estaríem confonent una dada per una altra.
L'altra opció podria ser posar tres cues, una per cada coordenada i la tasca consumidora anar llegint de cada una. Això però, sembla que és una mica massa complexitat per un problema senzill.
La millor solució consisteix a preparar una estructura de dades i que sigui aquesta estructura la que s'envia per la cua. Així, les tres dades viatgen juntes i cada una associada al seu camp corresponent. La definició de l'estructura haurà de ser comú a totes les tasques i cues que la facin servir; en un projecte gran caldrà definir-la en un {\em header} comú per la resta de mòduls del projecte.
Així, podríem definir una estructura tal com es veu al llistat~\ref{queue_struct_example}.
\begin{lstlisting}[style=customc, label=queue_struct_example, caption=Paquet dins d'estructura,floatplacement=h! ]
struct queue_pkt {
uint16_t eixX;
uint16_t eixY;
uint16_t eixZ;
};
\end{lstlisting}
I es crea la cua tal com es veu al Llistat~\ref{queue_struct_create_example}.
\index{xQueueCreate()}
\begin{lstlisting}[style=customc, label=queue_struct_create_example, caption=Creació de la cua amb un paquet de dades,floatplacement=h!]
queue_handle = xQueueCreate(QUEUE_LENGTH, sizeof(struct queue_pkt));
\end{lstlisting}
Així, la informació que es mourà per la cua serà l'estructura i caldrà agafar cada dada de la mateixa de la forma habitual, tal com es veu al Llistat~\ref{queue_struct_read_example}.
\index{xQueueReceive()}
\begin{lstlisting}[style=customc, label=queue_struct_read_example, caption=Rebre un paquet de dades de la cua]
...
if (xQueueReceive(queue_buttons, &pkt, (TickType_t ) 0)) {
eixX = pkt.eixX;
}
...
\end{lstlisting}
Al \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FreeRTOS_Queue_2}{repositori} l'exemple FreeRTOS\_Queue\_2 es fa servir una estructura per passar dades mitjançant una cua.
\section{Mutex}
\label{sec:Mutex}
Quan tenim un recurs, {\em driver}, memòria compartida, secció crítica o qualsevol altre recurs que només es pot fer servir per una sola tasca a cada moment, cal muntar un mecanisme d'exclusió mútua que ens asseguri que no tindrem cap problema \cite[244]{FreeRTOSBook}.
Aquest mecanisme és molt similar a un semàfor binari però cal incloure algun mecanisme per prevenir la inversió de prioritats. Aquest mecanisme és el Mutex (d'aquí els ve el nom: {\em Mutual Exclusion}). Els Mutex implementen un mecanisme d'herència de prioritats de tal manera que si una tasca d'alta prioritat està esperant un Mutex que té una tasca de baixa prioritat, aquesta última veu augmentada la seva prioritat a la mateixa prioritat que la tasca d'alta prioritat mentre té el Mutex per tal que tingui més oportunitat d'alliberar-lo ja que altres tasques amb prioritats entre mig poden bloquejar la tasca de baixa prioritat \footnote{Aquest problema és el conegut {\em inversió de prioritats} que pot donar molts mal de caps si no es detecta a temps} (veure \fullref{sec:priorityinv} més endavant).
Per l'ús correcte d'un Mutex, el que es fa és provar d'agafar el Mutex abans d'entrar a la secció crítica, si es té èxit s'executa el que calgui dins la secció crítica i a continuació s'allibera el Mutex. Com ens podem imaginar, cal que el temps que estem dins una secció crítica sigui el més curt possible. Com la resta de crides d'aquesta mena, el paràmetre {\em timeout} ens permet seleccionar el temps que la tasca ha d'estar esperant auqe el Mutex estigui disponible (des de 0 fins a temps infinit).
En el cas de FreeRTOS cal primer crear el Mutex i a partir de llavors ja es pot fer servir per part de les tasques. Les tasques poden agafar o donar un Mutex amb les mateixes funcions de manegar semàfors que ja coneixem.
\subsubsection{Exemple amb Mútex}
\label{sub:mutex_exemple}
Al \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FreeRTOS_Mutex}{repositori del curs} tenim un exemple on dues tasques fan ús d'un recurs compartit com pot ser la consola de debug (amb el printf) i es comparteix amb un Mutex.
A l'exemple tal com està ara, no està definit la macro USE\_MUTEX i el codi no en fa ús. Si executem el codi tal qual està, veurem que la sortida de les dues tasques es barreja ja que no hi ha cap control de qui escriu i quan (Llistat \ref{outputconsole_1}).
\index{xSemaphoreTake()}
\begin{lstlisting}[style=customc, numbers=left, caption=Exemple d'ús de macros en C, label=macro_example]
#ifdef USE_MUTEX
if (xSemaphoreTake(example_mutex, portMAX_DELAY)) {
#else
{
#endif
\end{lstlisting}
\begin{remark}
Un exemple d'ús de macros en C es veu al llistat~\ref{macro_example}. En aquest codi, si la macro {\bf USE\_MUTEX} no està definida no s'executa la línia 2 i no es té en compte el Mutex.
\end{remark}
\begin{lstlisting}[style=customc, label=outputconsole_1, caption=Sortida de la consola sense Mutex]
from Task1
Other text Some text from Task 2
from Task1
Other text Some text from Task 2
from Task1
Other text Some text from Task 2
from Task1
\end{lstlisting}
Si traiem el comentari i activem la macro USE\_MUTEX llavors el codi de manegar el Mutex s'activa i llavors veurem que la sortida per la consola ja és la correcta (Llistat~\ref{outputconsole_2}).
\begin{lstlisting}[style=customc, label=outputconsole_2, caption=Sortida de la consola amb Mutex]
Some text from Task1
Other text from Task 2
Some text from Task1
Other text from Task 2
Some text from Task1
Other text from Task 2
\end{lstlisting}
Què passa en aquest cas? Doncs que abans de treure el text per la consola, es demana el Mutex i queda protegida la secció crítica i tot funciona com ha de ser.
A l'exemple es fa servir la comanda {\bf taskYIELD()}\index{taskYIELD()} entre mig dels dos printf per simular que la tasca en aquell punt perd l'execució. Com segur que saps, les condicions de carrera (\glspl{race condition}) són molt complicades de trobar i provocar perquè són infreqüents i només passen de tant en tant; i és per això que provoquem el canvi de tasca amb la comanda {\bf taskYIELD()}.
En aquest exemple i per fer-ho senzill, la crida per demanar el Mutex porta com a segon paràmetre {\bf portMAX\_DELAY}, que fa que la tasca quedi bloquejada fins que s'alliberi el Mutex. També es pot afegir un temps d'espera ({\em timeout}) i llavors la funció retorna quan s'ha agafat el Mutex (i retorna \gls{pdTRUE}) o quan ha passat el temps d'espera (i retorna \gls{pdFALSE}).
L'ús de Mútex és necessari per controlar l'accés a qualsevol secció crítica que tinguem al nostre projecte. Habitualment en tindrem per cada ús o crida a un {\em driver} que pugui portar-nos problemes d'aquesta mena. Per exemple, si dues tasques han d'accedir al bus \gls{I2C} per accedir a diferents sensors caldrà protegir amb un Mutex les crides a les llibreries del sistema.
\begin{exercise}
La prioritat de les dues tasques a l'exemple és la mateixa. Com exercici es pot provar de canviar les prioritats i treure els Mutex, a veure què passa i intentar entendre el perquè.
\end{exercise}
\section{\em Event Groups}
A més dels mecanismes ja explicats, i que son els més típics en la comunicació i sincronització entre tasques, FreeRTOS ens proporciona un mecanisme que permet bloquejar o desbloquejar una o vàries tasques segons succeeixi un o varis esdeveniments \cite[266]{FreeRTOSBook}. Aquest mecanisme es diu {\em Event Groups} i permet que una o vàries tasques defineixen a quins esdeveniments son sensibles i el {\em kernel} gestiona tot el necessari. Aquest mecanisme permet:
\begin{itemize}
\item Que una tasca o més tasques estiguin bloquejades esperant diversos esdeveniments
\item Que un esdeveniment desbloquegi vàries tasques de forma simultània.
\end{itemize}
% Aquest mecanisme es podria simular amb diversos semàfors binaris (veure \fullref{sub:semaphores}) però augmentaria la complexitat i la possibilitat d'introduir errors.
Aquest mecanisme es basa en declarar {\em flags} per cada esdeveniment per tot seguit definir un grup de {\em flags} anomenat {\em Event Groups}. Aquests {\em flags} individuals s'emmagatzemen com un sol bit, de manera que només poden tenir el valor 0 o 1, on el 0 vol dir que l'esdeveniment no ha succeït i l'1 que l'esdeveniment si que ha succeït. Tots aquests bits es guardaran en una variable de 8 o 24 bits depenent com estigui definida la constant {\bf configUSE\_16\_BIT\_TICKS} al fitxer {\em FreeRTOSConfig.h} (si la constant val 1 el grup conté només 8 bits, si val 0 (valor per defecte) el grup conté 24 bits).
Les tasques que han d'estar pendents d'aquest grup notifiquen al {\em kernel} de quins esdeveniments particulars volen dependre (bits individuals de la paraula), si s'ha de complir tots o només un (operació ``AND'' o ``OR''), si s'han de netejar els {\em flags} que han desbloquejat la tasca i el temps que es pot esperar a que això succeeixi ({\em timeout}).
Així, i de forma general, tindrem que qui manegui els esdeveniments (típicament ISRs, però poden ser altres tasques) posaran els {\em flags} individuals a '1' quan aquests succeeixin mentre que una o més tasques estaran bloquejades esperant que una sèrie de bits del grup s'activin (només un o tots depenent de la configuració particular).
\subsection{Exemple de {\em event groups}}
El codi d'aquest exemple es troba \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FreeRTOS_EventGroups}{al repositori} i conté una sola tasca esperant per que es compleixin dos esdeveniments, que serà la pulsació dels dos botons de la placa de prototipat. Per tant, l'exemple farà {\em toggle} del LED quan s'hagin pres els dos botons (no cal que sigui simultàniament).
\index{xEventGroupWaitBits()}
\begin{lstlisting}[style=customc,caption=Tasca esperant per un grup d'esdeveniments,label=EventGroupsTask]
#define FIRST_BUTTON_BIT (1 << 0)
#define SECOND_BUTTON_BIT (1 << 1)
static void TaskLedToggle(void *pParameter) {
(void) pParameter;
while (true) {
xEventGroupWaitBits(event_group, (FIRST_BUTTON_BIT | SECOND_BUTTON_BIT), pdTRUE, pdTRUE, portMAX_DELAY);
LedToggle();
}
}
\end{lstlisting}
Al Llistat~\ref{EventGroupsTask} es presenta la tasca, que simplement es queda bloquejada esperant pels dos bits del grup prèviament definits. Els dos paràmetres següents (tots dos {\bf pdTRUE}) indiquen que cal netejar els esdeveniments i cal esperar a tots els esdeveniments s'hagin activat. Per últim, també es configura un {\em timeout} infinit perquè la tasca es quedi bloquejada per sempre esperant als dos esdeveniments.
El codi de la ISR (veure Llistat~\ref{EventGroupsISR}) conté el codi ja conegut per netejar els flags del mòdul GPIO i tot seguit es notifica l'esdeveniment corresponent al grup amb la funció {\em xEventGroupSetBitsFromISR}. En aquest cas els paràmetres son: el grup a notificar, el bit del grup a notificar i l'últim paràmetre serveix per rebre la informació de si cal notificar al {\em kernel} que una tasca s'ha desbloquejat.
\index{xEventGroupSetBitsFromISR()}\index{portYIELD\_FROM\_ISR()}
\begin{lstlisting}[style=customc,caption=ISR notificant un esdeveniment a un grup,label=EventGroupsISR]
void GPIO_ODD_IRQHandler(void) {
uint32_t aux;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* clear flags */
aux = GPIO_IntGet();
GPIO_IntClear(aux);
xEventGroupSetBitsFromISR(event_group, SECOND_BUTTON_BIT, &xHigherPriorityTaskWoken);
/* Awake a task ? */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
\end{lstlisting}
Si a la tasca es canviés la crida per la del Llistat~\ref{EventGroupsTaskB} llavors la tasca es desbloquejarà qua passi qualsevol dels dos esdeveniments (es farà una ``OR'' entre els tots els bits del grup enlloc d'una ``AND'').
\begin{lstlisting}[style=customc,caption=Tasca esperant un grup d'esdeveniments (OR),label=EventGroupsTaskB]
static void TaskLedToggle(void *pParameter) {
(void) pParameter;
while (true) {
xEventGroupWaitBits(event_group, (FIRST_BUTTON_BIT | SECOND_BUTTON_BIT), pdTRUE, pdFALSE, portMAX_DELAY);
LedToggle();
}
}
\end{lstlisting}
\subsection{Sobre el determinisme dels grups d'esdeveniments}
La implementació d'aquests {\em Event Groups} a FreeRTOS es fan mitjançant una tasca auxiliar del mòdul de Timers software ja que la resolució d'un grup d'esdeveniments no és determinista (ja que no es pot saber per avançat quantes tasques o quants esdeveniments estan involucrats a cada moment). La tasca auxiliar es crea automàticament el primer cop que es crea un grup d'esdeveniments amb la prioritat per defecte d'aquest tasca.
A més, la comunicació entre les funcions de l'API ({\em xEventGroupSetBitsFromISR()}, etc.) i la tasca auxiliar es fa mitjançant una cua, de manera que no és possible el determinisme de tot el mecanisme pel cas general.
Per tot això, cal ser curós en quins casos fer servir aquest mecanisme, per senzill que pugui semblar i depenent de la criticitat de l'aplicació i del cas particular.
\section{Conjunt de cues {\em Queue Sets}}
Hi ha casos on una tasca pot voler rebre dades de cues diferents on, potser, per cada cua es reben dades de tipus diferents. FreeRTOS ens proporciona un mecanisme per poder rebre de vàries cues d'una forma senzilla.
El mecanisme és el {\em Queue set} (conjunt de cues) que permet agrupar tot de cues i semàfors i després consultar per part d'una tasca si hi ha alguna dada disponible a alguna de les cues o semàfor.
El que cal fer és crear un conjunt, afegir-hi els mecanismes de sincronització que es vulguin incloure i ja només cal consultar la disponibilitat a través del conjunt enlloc de cada mecanisme per separat.
Anem a veure-ho en un exemple, que com sempre \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FreeRTOS_QueueSets}{està al repositori}. A l'exemple es modifica l'\href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FreeRTOS_Queue}{exemple de cues anterior} perquè cada ISR enviï la seva dada per una cua diferent ({\em queue\_buttons\_1}, {\em queue\_buttons\_2}).
El primer que cal fer és crear el conjunt de cues ({\em Queue Sets}) tal com es veu al Llistat~\ref{QueueSetMain}. Cal veure que quan és crea el conjunt cal especificar la longitud de totes les cues i semàfors que s'agrupen. Per les cues cal sumar el nombre d'elements, pels semàfors binaris la longitud és 1 i pels semàfors comptadors la longitud és el valor màxim que poden tenir.
\index{xQueueCreateSet()}\index{xQueueCreate()}
\begin{lstlisting}[style=customc,caption={Creació del conjunt de cues}, label=QueueSetMain]
...
#define QUEUE_LENGTH (10)
QueueSetHandle_t queue_set;
QueueHandle_t queue_buttons_1;
QueueHandle_t queue_buttons_2;
void main() {
...
queue_set = xQueueCreateSet( QUEUE_LENGTH + QUEUE_LENGTH );
queue_buttons_1 = xQueueCreate(QUEUE_LENGTH, sizeof(uint32_t));
queue_buttons_2 = xQueueCreate(QUEUE_LENGTH, sizeof(uint32_t));
...
}
\end{lstlisting}
A la tasca s'ha canviat com es rep les dades de cada ISR i ara es consulta el conjunt de cues (Llistat~\ref{QueueSetTask}). Aquesta funció retorna el {\em handler} al mecanisme que té una dada disponible, de manera que a continuació es consulta al mecanisme i s'adquireix la dada rebuda. La resta del mecanisme és força similar a l'exemple anterior.
\index{xQueueSelectFromSet()}\index{xQueueReceive()s}
\begin{lstlisting}[style=customc,caption={Tasca que fa servir el conjunt de cues}, label=QueueSetTask]
static void TaskLedToggle(void *pParameter) {
...
for (;;) {
selected_queue = xQueueSelectFromSet(queue_set, 0);
if ( selected_queue == queue_buttons_1) {
xQueueReceive(queue_buttons_1, &my_delay, (TickType_t ) 0);
} else if (selected_queue == queue_buttons_2) {
xQueueReceive(queue_buttons_2, &my_delay, (TickType_t ) 0);
}
vTaskDelay(my_delay);
LedToggle();
}
}
\end{lstlisting}
Cal fer notar que l'exemple es fa amb només dues cues, però els {\em Queue Sets}, malgrat el nom, també poden incloure semàfors de la mateixa manera i consultar-los de la mateixa forma.
\section{Notificacions a tasques}
Les notificacions a tasques (en anglès {\em Direct to Task Notifications}) son un mecanisme propi de FreeRTOS similar a les cues, semàfors i mútex però més simple i, en alguns casos, més eficient (aquest mecanisme pot ser fins a un 45\% més ràpid que un mecanisme basat en un semàfor binari).
Si bé els mecanismes introduïts fins ara eren objectes que existien entre les tasques que comunicaven, les notificacions es fan de forma directe entre \gls{ISR} i tasques o entre dues tasques sense cap objecte addicional. Això te l'avantatge que s'estalvia memòria, ja que no cal mantenir tanta informació i que el mecanisme és més ràpid, però comporta certes limitacions:
\begin{itemize}
\item No es pot enviar una notificació cap a una \gls{ISR}, tot i que si que es pot a l'inversa.
\item Només es pot notificar a una sola tasca, ja que es notifica directament la tasca, no cap mecanisme intermig.
\item No es pot emmagatzemar dades, ja que el mecanisme de notificació pot manegar una i només una dada.
\end{itemize}
Les funcions de notificació a tasques necessiten conèixer el {\em handler} de la tasca a enviar, cosa que s'acostuma a fer mitjançant variables globals.
A la Taula~\ref{tb:task_notifications} es resumeixen les crides a les notificacions i a quins mecanismes poden substituir \footnote{També hi ha les funcions per ISRs vTaskNotifyGiveFromISR() i xTaskNotifyFromISR()}.
\index{xTaskNotifyGive()}\index{ulTaskNotifyTake()()}\index{xTaskNotify()}\index{xTaskNotifyWait()}
\index{vTaskNotifyGiveFromISR()}\index{xTaskNotifyFromISR()}
\begin{table}
\caption{Crides de notificacions de tasques i els mecanismes equivalents }
\centering
\begin{tabular}{|c|c|}
\hline
Semàfor binari & xTaskNotifyGive() / ulTaskNotifyTake() \\
\hline
Semàfor comptador & xTaskNotifyGive() / ulTaskNotifyTake() \\
\hline
Grup d'esdeveniments & xTaskNotify() / xTaskNotifyWait()\\
\hline
Cua (d'un sol element) & xTaskNotify() / xTaskNotifyWait()\\
\hline
\end{tabular}
\label{tb:task_notifications}
\end{table}
\subsection{Exemple de notificació directa a tasques}
\href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FreeRTOS_TaskNotify}{Al repositori} hi ha l'exemple més senzill on es notifica una tasca que es suspèn esperant un esdeveniment. Aquest esdeveniment ve donat per la pulsació d'un dels botons i la notificació per part de la \gls{ISR}.
\index{ulTaskNotifyTake()}
\begin{lstlisting}[style=customc,caption={Tasca que espera la notificació}, label=DirectNotificationTask]
static void TaskLedToggle(void *pParameter) {
(void) pParameter;
while (true) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
TriggerToggle();
LedToggle();
}
}
\end{lstlisting}
El Llistat~\ref{DirectNotificationTask} mostra la tasca, que simplement espera agafar la notificació amb la funció {\bf ulTaskNotifyTake()} per tot seguit fer {\em toggle} del LED. Els paràmetres de la crida fan que es netegi el flag un cop s'ha rebut i s'esperi indefinidament.
Cada una de les ISR tant sols notifica la tasca amb la crida corresponent, tal com es veu al Llistat~\ref{DirectNotificationISR}. En aquest cas s'ha de fer servir la variable {\em taskhandle} que emmagatzema el {\em handle} a la tasca que està esperant la notificació. Aquesta variable és global a tot el codi i està definida al principi del fitxer main.c.
\index{vTaskNotifyGiveFromISR()}
\begin{lstlisting}[style=customc,caption={Tasca que espera la notificació}, label=DirectNotificationISR]
void GPIO_ODD_IRQHandler(void) {
uint32_t aux;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* clear flags */
aux = GPIO_IntGet();
GPIO_IntClear(aux);
vTaskNotifyGiveFromISR(taskhandle, &xHigherPriorityTaskWoken);
/* Awake a task ? */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
\end{lstlisting}
\section{Comparant temps de resposta}
En aquesta secció valorarem els temps de resposta mesurats en els tres mecanismes de comunicació entre esdeveniments i tasques, que son els semàfors, els grups d'esdeveniments i les notificacions directes.
A la Taula~\ref{tb:IRQtoTaskTime} es resumeixen aquestes mesures i a les Figures~\ref{fig:IRQtoTaskTime_Semaphhore},\ref{fig:IRQtoTaskTime_Direct} i \ref{fig:IRQtoTaskTime_Group} es veuen les mesures fetes amb l'oscil·loscopi. Com es pot comprovar, el mecanisme més senzill i ràpid és el de la notificació directa, el semàfor afegeix una mica més de complexitat i per tant de temps de resposta i, per últim, el mecanisme més complex del grup d'esdeveniments és el mecanisme més lent amb diferència.
Cal fer notar també que la resposta és de l'ordre de microsegons, que està per sota del temps de {\em tick}, cosa que significa que la tasca que s'estigui executant en el moment d'ocorre l'esdeveniment s'interromp i es passa a executar la tasca que està esperant el mecanisme associat a l'esdeveniment.
\begin{table}
\caption{Crides de notificacions de tasques i els mecanismes equivalents }
\centering
\begin{tabular}{|c|c|}
\hline
Mecanisme & Temps (microsegons) \\
\hline
Semàfor binary & 93,6 \\
\hline
Notificació directa & 79,2 \\
\hline
Grup d'esdeveniments & 306 \\
\hline
\end{tabular}
\label{tb:IRQtoTaskTime}
\end{table}
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/IRQtoSemaphore.png}
\caption{Temps de resposta usant semàfors}
\label{fig:IRQtoTaskTime_Semaphhore}
\end{figure}
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/IRQtoDirectTask.png}
\caption{Temps de resposta usant notificació a tasca}
\label{fig:IRQtoTaskTime_Direct}
\end{figure}
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/IRQtoEventGroup.png}
\caption{Temps de resposta usant groups de notificació}
\label{fig:IRQtoTaskTime_Group}
\end{figure}
\chapter{Exemple amb la UART i interrupcions}
A l’\href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/FreeRTOS_UART}{exemple Freertos\_UART} hi ha el mateix exemple vist a \fullref{sec:UART_example_2} però en aquest cas usant FreeRTOS. Per això hi ha uns pocs canvis:
\begin{itemize}
\item A les interrupcions de la USART ({\bf USART1\_TX\_IRQHandler()}\index{USART1\_TX\_IRQHandler()} i {\bf USART1\_RX\_IRQHandler()}\index{USART1\_RX\_IRQHandler()}) se les canvia la prioritat, ja que a FreeRTOS la prioritat de les interrupcions han de tenir un valor diferent a 0. Veure aquest enllaç de la documentació de FreeRTOS \cite{FreeRTOSIRQ}.
\item Enlloc de fer servir el \gls{buffer circular} es fa servir una cua de FreeRTOS. Així la \gls{ISR} de recepció guarda el valor rebut a una cua i la \gls{ISR} de transmissió va buidant la mateixa cua.
\item La funció {\bf USART\_Send()}\index{USART\_Send()} també fa servir la cua de transmissió per extreure els caràcters a enviar per la UART
\item Enlloc d’un {\em while(1)} al {\bf main()}\index{main()}, s'ha creat una tasca que prova de llegir un caràcter de la cua de recepció per fer la seva feina.
\end{itemize}
\index{USART1\_RX\_IRQHandler()}\index{USART\_Rx()}\index{xQueueSendFromISR()}\index{USART\_IntClear()}\index{portYIELD\_FROM\_ISR()}
\begin{lstlisting}[style=customc,caption={ISR de RX de la UART amb FreeRTOS}]
void USART1_RX_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
char data;
if (USART1->IF & LEUART_IF_RXDATAV) {
data = USART_Rx(USART1);
xQueueSendFromISR(USART_RX_queue, &data, &xHigherPriorityTaskWoken);
USART_IntClear( USART1, USART_IEN_RXDATAV);
}
/* Awake a task ? */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
\end{lstlisting}
\index{USART1\_TX\_IRQHandler()}\index{USART\_IntClear()}\index{xQueueReceiveFromISR()}\index{USART\_Tx()}
\begin{lstlisting}[style=customc,caption={ISR de TX de la UART amb FreeRTOS}]
void USART1_TX_IRQHandler(void) {
char data;
USART_IntClear( USART1, USART_IEN_TXC);
if (xQueueReceiveFromISR(USART_TX_queue, &data, 0) == pdTRUE) {
USART_Tx(USART1, data);
}
}
\end{lstlisting}
\index{USART\_Send()}\index{xQueueReceive()}\index{USART\_Tx()}
\begin{lstlisting}[style=customc,caption={funció UART\_Send() per FreeRTOS}]
void USART_Send(USART_TypeDef *usart) {
int data;
if (xQueueReceive(USART_TX_queue, &data, 0) == pdTRUE) {
USART_Tx(USART1, data);
}
}
\end{lstlisting}
\index{UARTTask()}\index{xQueueReceive()}\index{xQueueSend()}\index{USART\_Send()}
\begin{lstlisting}[style=customc,caption=Tasca principal de l'exemple]
static void UARTTask(void *pParameter) {
(void) pParameter;
char recv_char;
char tx_char;
for (;;) {
if (xQueueReceive(USART_RX_queue, &recv_char, portMAX_DELAY)) {
tx_char = recv_char;
xQueueSend(USART_TX_queue, &tx_char, 0);
tx_char++;
xQueueSend(USART_TX_queue, &tx_char, 0);
tx_char++;
xQueueSend(USART_TX_queue, &tx_char, 0);
USART_Send(USART1);
}
}
}
\end{lstlisting}
Per la resta, l'aplicació té el mateix funcionament que l'exemple sense RTOS. Es pot consultar l'exemple original per una millor explicació del funcionament (veure~\fullref{sec:UART_example_2}).
\chapter{Una aplicació completa amb FreeRTOS}
Per resumir i posar un exemple de tot el vist sobre \gls{FreeRTOS}, agafarem l'aplicació d'exemple feta a \fullref{ch:aplicaciosenzilla} i es transformarà en una aplicació amb FreeRTOS. Per començar es treballarà amb la versió amb {\em polling} de l'aplicació original.
\section{Tasques}
Una de les característiques d'un \gls{RTOS} és que les diferents tasques a fer per l'aplicació es divideixen en tasques. En aquest cas, sembla senzill pensar que es pot dividir la feina a fer en dos parts: (i) llegir la dada del sensor i (ii) canviar el \gls{duty cycle} del \gls{PWM} segons el valor llegit. En resum, les tasques seran:
\begin{itemize}
\item {\bf ReadSensor\_task}: aquesta tasca llegeix periòdicament el valor de proximitat del sensor i envia aquesta dada cap a l'altra tasca. Això ho fa cada mig segon i fa {\em polling} del registre d'{\em estatus} del sensor (veure llistat~\ref{readsensor_task}).
\item {\bf MngData\_task}: aquesta tasca rep la dada de proximitat i fa dues coses: la treu per la consola de {\em debug} amb un {\bf printf()}\index{printf()} i canvia el ritme de pampallugueig del LED segons aquest valor. Aquesta tasca es bloqueja esperant obtenir una dada, i en quan en rep una canvia el paràmetre del \gls{PWM} i imprimeix el valor per la consola (veure llista~\ref{mgndata_task}).
\end{itemize}
Les dues tasques s'han de poder comunicar, doncs la tasca que llegeix el valor de proximitat del sensor l'ha de poder enviar a la tasca que gestiona el \gls{PWM}. Així doncs, tenim dues tasques comunicades amb una cua (anomenada {\bf sensor\_data\_queue}). Com que el tipus de dades que hem d'enviar d'una tasca a l'altra és tant sols el valor de proximitat proporcionat pel sensor, la cua pot emmagatzemar directament aquest valor. És per això que la cua es crea amb 4 posicions de 8 bits cada una (veure Llistat~\ref{createtasks}).
\index{ReadSensor\_task()}\index{APDS\_9960\_InitProximity()}\index{APDS\_9960\_ReadProximity()}
\index{xQueueSend()}\index{vTaskDelay()}
\begin{lstlisting}[style=customc,caption={Tasca ReadSensor },label=readsensor_task]
static void ReadSensor_task(void *pParameter) {
uint8_t p_data;
bool ret;
(void) pParameter;
APDS_9960_InitProximity();
while (pdTRUE) {
ret = APDS_9960_ReadProximity(&p_data);
if (ret == true) {
xQueueSend(sensor_data_queue, &p_data, portMAX_DELAY);
}
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
\end{lstlisting}
\index{MngData\_task()}\index{xQueueReceive()}\index{printf()}
\index{PWM\_Set()}
\begin{lstlisting}[style=customc,caption={Tasca MngData},label=mgndata_task]
static void MngData_task(void *pParameter) {
uint8_t p_data;
(void) pParameter;
while (pdTRUE) {
xQueueReceive(sensor_data_queue, &p_data, portMAX_DELAY);
printf("Proximity: %d\r\n", p_data);
/* Convert from range 0 - 256 to 0 - 100 */
PWM_Set((p_data * 100) / 256 );
}
}
\end{lstlisting}
\index{main()}\index{xQueueReceive()}\index{xTaskCreate()}
\begin{lstlisting}[style=customc,caption={Creació de tasques},label=createtasks]
main() {
...
/* Create queue to send data between two tasks */
sensor_data_queue = xQueueCreate(4, sizeof(uint8_t));
/* Create read sensor task */
xTaskCreate(ReadSensor_task, (const char *) "ReadSensor",
configMINIMAL_STACK_SIZE-65, NULL, READ_TASK_PRIORITY, NULL);
/* Create print & LED ctrl task */
xTaskCreate(MngData_task, (const char *) "MngData",
100-5, NULL, MNG_TASK_PRIORITY, NULL);
...
\end{lstlisting}
Com ja hem vist anteriorment, es creen les dues tasques dins de la funció {\bf main()}\index{main()} (Llistat~\ref{createtasks}).
\section{Modificant el {\em wrapper} d'I2C}
\label{sec:wrapperI2C}
Com que FreeRTOS és un sistema operatiu preemptiu, cal que les funcions de les biblioteques es puguin fer servir per diverses tasques alhora (siguin re-entrants) \cite[236]{FreeRTOSBook}. Habitualment això es fa amb un {\em mutex} que protegeixi la o les seccions crítiques de cada biblioteca. En el cas de la biblioteca {\em I2C\_Wrapper} es fa amb un sol {\em mutex} que protegeix la crida a la transferència \gls{I2C} pròpiament dita (veure Llistat~\ref{I2CWriteRegisterFreeRTOS}).
\index{I2C\_WriteRegister()}\index{xSemaphoreTake()}
\index{I2C\_TransferInit()}\index{I2C\_Transfer()}\index{xSemaphoreGive()}
\begin{lstlisting}[style=customc,caption={Part de la funció I2C\_WriteRegister() adaptada a FreeRTOS}, label=I2CWriteRegisterFreeRTOS]
bool I2C_WriteRegister() {
...
xSemaphoreTake(I2C_mutex, portMAX_DELAY);
I2C_Status = I2C_TransferInit(I2C0, &seq);
while (I2C_Status == i2cTransferInProgress) {
I2C_Status = I2C_Transfer(I2C0);
}
xSemaphoreGive(I2C_mutex);
...
}
\end{lstlisting}