Skip to content

Commit

Permalink
activity diagram added to explain ISRs in FreeRTOS
Browse files Browse the repository at this point in the history
  • Loading branch information
mariusmm committed Feb 28, 2019
1 parent 709e8ed commit f9ba1ce
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 3 deletions.
23 changes: 20 additions & 3 deletions capitol_4.tex
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ \chapter{Conceptes bàsics de FreeRTOS}

\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 Comunicació entre tasques: semàfors, cues, etc.
\item Gestió de temps: Timers, {\em timeouts}, {\em delays}, etc.
\end{itemize}

Expand Down Expand Up @@ -137,7 +137,9 @@ \section{Interrupcions a FreeRTOS}

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}).
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]
Expand All @@ -152,8 +154,23 @@ \section{Interrupcions a FreeRTOS}
}
\end{lstlisting}

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}.

\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}
Expand Down
68 changes: 68 additions & 0 deletions capitol_Test.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
% \part{Test i Qualitat}
% \label{part:test}
TBD
\chapter{Conceptes Bàsics}
\label{ch:test_1}

En el desenvolupament normal de software, en general, sempre hi ha una
fase de test per assegurar la correcte implementació de la
lògica. Depenen de quin sector en trobem, aquesta fase té més o menys importància en relació a esforços i dedicació. Mentre en certs sectors (medicina, automoció, espai) el tests és vital perquè hi ha en joc vides o molts diners invertits, en altres se li dedica poca o gairebé nul.la importància.

Sense ànims d'entrar en conceptes molt teòrics, simplement recordar que en el món de l'enginyeria de software hi ha certa categorització en la part de test, des els anomenats test de caixa blanca o unit test: verificar que els mètodes implementats no contenen errors, test de caixa negra, integració i/o verificació: assegurar que el codi fa el que toca.

En el món dels encastats ens trobem molts cops que la fase de test es redueix a simplement 4 proves generals de funcionalitat esperada i poca cosa més, s'assumeix que el codi sol ser simple i les implicacions d'una fallada tampoc seran gaire greus.

No obstant, degut a què els softwares encastats són presents a molts llocs, una bona política i praxis sempre és recomanable.

Suposem que estem treballant amb uns sensors de temperatura, per regla general aquest tipus de sensors proporcionen unes lectures que cal convertir amb alguna funció de transferència si volem saber el seu valor en graus per exemple, aquestes funcions de transferència, generalment funcions polinòmiques, venen detallades en els manuals d'usuari dels sensors. En el cas més simple seria simplement un coeficient, per tan, si tenim un codi que intenta ser genèric, podríem implementar un petit mòdul com el del llistat \ref{TestTempSensor}

\begin{lstlisting}[caption={Conversió sensor de temperatura},style=customc,label=TestTempSensor]

static float temp_coeff = 1;
void temp_set_coef(float c)
{
temp_coeff = c;
}

float temp_convert (int count)
{
return temp_coeff * count;
}
\end{lstlisting}

Com certificaríem que no hem comès cap error, inicialment faríem algun {\em check} manual/visual per veure que les temperatures ens quadren per exemple, però per sistematitzar-ho podríem fer quelcom similar al mostrat al llistat \ref{TestTempSensor2}, on estaríem implementant un Unit Test típic.

\index{main()}
\begin{lstlisting}[caption={Test conversió sensor de temperatura},style=customc,label=TestTempSensor2]

main ()
{
temp_set_coef (0.3);

if (temp_convert (2) == 2*0.3)
{
printf ("Conversio Temperatura OK\n");
}
else
{
printf ("Conversio Temperatura Fail\n");
}
}
\end{lstlisting}

Si implementem el codi (en PC normal) i executem, segurament ens pot sorprendre el resultat, ja que per pantalla es mostrarà \textbf{Conversió Temperatura Fail}, si revisem el codi veurem que la lògica ens indica que el test hauria de ser correcte. En aquest cas el problema es la representació de les dades, els valors decimals es guarden en memòria seguint el format del estàndard \cite{IEEE754} i en el cas de usar 32 bits en aquest format, certs valors no són completament ben representats introduint una desviació mínima, suficient per a que guardant el valor \textbf{0.3} per després multiplicar-lo per \textbf{2} no sigui exactament igual a \textbf{2*0.3}. Això ens porta a dos observacions: hem de pensar que molts cops hem de fer comprovacions amb marges de confiança i, hem d'entendre i avaluar les implicacions a llarg termini, és a dir, en l'exemple la desviació és mínima, però si es va acumulant pot acabar essent no menyspreable, acumular 2 cops per segon temperatures agafades amb l'exemple, suposa un 0.16\% de desviació per dia, 0.6\% a la setmana. Si pensem en dispositius \gls{IoT} pensats en durar mesos o anys enseguida podem veure que coses així de trivials, no codificades oportunament, poden arribar a introduir errors greus en les dades.


%% https://www.linkedin.com/groups/71510/71510-6414628032614531072?midToken=AQGd6dzk9cCBeg&trk=eml-b2_anet_digest_weekly-group_discussions-13-grouppost~disc~0&trkEmail=eml-b2_anet_digest_weekly-group_discussions-13-grouppost~disc~0-null-c2f7k~jiycm722~n-null-communities~group~discussion&lipi=urn%3Ali%3Apage%3Aemail_b2_anet_digest_weekly%3BMmKcvYQVRnKLZUe5NsCdVw%3D%3D

\chapter{Test}
\label{ch:test_2}
TBD
\section{\em Test Driven Development}
TBD
\section{Plataformes}
TBD
\section{\em Mocks}
TBD
\section{Estandards}
TBD
Binary file added imatges/FreeRTOSISR.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added imatges/FreeRTOSISRYield.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions plantuml/FreeRTOSISR.plantuml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
' FreeRTOS activity diagram with 2 tasks when an IRQ triggers an ISR.
' The ISR should awake Task1 that it is waiting for a data in a Queue.
' The ISR is a wrong code for FreeRTOS, because it lacks the
' taskYIELD_FROM_ISR() call

@startuml

skinparam sequence {
ArrowColor #009944
ActorBorderColor #009944
LifeLineBorderColor #009944
LifeLineBackgroundColor #00AA55
ParticipantBorderColor #009944
ParticipantBackgroundColor #00AA55
}

participant Tasca1 order 1
participant Tasca2 order 2
participant Kernel order 3
participant ISR order 4
activate Tasca1
|||
Tasca1 -> Kernel: xQueueReceive()
deactivate Tasca1
activate Kernel

Kernel -->> Tasca1: blocked
Kernel -->> Tasca2: running
deactivate Kernel
activate Tasca2
|||
ISR o<--]: IRQ
deactivate Tasca2
activate ISR

ISR-> Kernel: xQueueSendFromISR()
deactivate ISR
activate Kernel
Kernel-->ISR: pdTRUE
deactivate Kernel
activate ISR
|||
ISR --> Tasca2: Return from ISR
destroy ISR
activate Tasca2
|||


@enduml
52 changes: 52 additions & 0 deletions plantuml/FreeRTOSISRYield.plantuml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
' FreeRTOS activity diagram with 2 tasks when an IRQ triggers an ISR.
' The ISR should awake Task1 that it is waiting for a data in a Queue.
`The ISR calls taskYIELD_FROM_ISR() macro to do so.

@startuml

skinparam sequence {
ArrowColor #009944
ActorBorderColor #009944
LifeLineBorderColor #009944
LifeLineBackgroundColor #00AA55
ParticipantBorderColor #009944
ParticipantBackgroundColor #00AA55
}

participant Tasca1 order 1
participant Tasca2 order 2
participant Kernel order 3
participant ISR order 4
activate Tasca1
|||
Tasca1 -> Kernel: xQueueReceive()
deactivate Tasca1
activate Kernel

Kernel -->> Tasca1: blocked
Kernel -->> Tasca2: running
deactivate Kernel
activate Tasca2
|||
ISR o<--]: IRQ
deactivate Tasca2
activate ISR

ISR-> Kernel: xQueueSendFromISR()
deactivate ISR
activate Kernel
Kernel-->ISR: pdTRUE
deactivate Kernel
activate ISR
|||
ISR --> Kernel: taskYIELD_FROM_ISR()
destroy ISR
activate Kernel
|||
Kernel -->> Tasca2: blocked
Kernel -->> Tasca1: running
Kernel --> Tasca1: pdTRUE
deactivate Kernel
activate Tasca1
|||
@enduml

0 comments on commit f9ba1ce

Please sign in to comment.