fin de la relecture
authorPaul Kocialkowski <contact@paulk.fr>
Mon, 11 May 2015 14:46:36 +0000 (16:46 +0200)
committerPaul Kocialkowski <contact@paulk.fr>
Mon, 11 May 2015 14:46:36 +0000 (16:46 +0200)
aspects-informatique.tex
bibliographie.bib
memoire.tex

index 04d32e1..e058209 100644 (file)
@@ -40,27 +40,27 @@ De manière générale, il n'est pas garanti que ces libertés sont accordés av
 
 \subsubsection*{Communication entre circuits intégrés}
 
-La communication entre plusieurs circuits intégrés a lieu en reliant des dispositifs d'entrée/sortie entre eux avec des conducteurs (des pistes sur un circuit imprimé ou de simples fils), que l'on pourra appeler lignes. Lorsqu'on peut relier plus que deux appareils sur les mêmes lignes, on parle alors de bus, qui désigne à la fois l'ensemble des lignes reliant les points d'entrée/sortie et de manière plus générale, l'ensemble du système de communication.\\
-Des informations vont alors être échangées au travers de ces conducteurs, le plus généralement\footnote{Certaines normes de transmission utilisent d'autres modes de communication, tels que le LVDS pour Low Voltage Differential Signaling en Anglais ou  transmission différentielle basse-tension en Français, que l'on n'abordera pas en détail.} sous forme numérique : il s'agira de bits, dont les valeurs traduisent des états logiques qui sont physiquement caractérisés par certaines tensions\footnote{Ces concepts sont détaillés dans la partie \ref{systemes-logiques}}. Le potentiel de référence utilisé pour la mesure des tensions devra donc être commun entre les deux circuits : chaque bus de données contiendra alors généralement une ligne dédié à la mise en commun de la masse.\\
+La communication entre plusieurs circuits intégrés a lieu en reliant des dispositifs d'entrée/sortie entre eux avec des conducteurs (des pistes sur un circuit imprimé ou de simples fils), que l'on pourra appeler lignes. Lorsqu'on peut relier plus que deux appareils sur les mêmes lignes, on parle alors de bus qui désigne à la fois l'ensemble des lignes reliant les points d'entrée/sortie et de manière plus générale, l'ensemble du système de communication.\\
+Des informations vont alors être échangées au travers de ces conducteurs, le plus généralement\footnote{Certaines normes de transmission utilisent d'autres modes de communication, tels que le LVDS pour Low Voltage Differential Signaling en Anglais ou  transmission différentielle basse-tension en Français, que l'on n'abordera pas plus en détail.} sous forme numérique : il s'agira de bits, dont les valeurs traduisent des états logiques qui sont physiquement caractérisés par certaines tensions\footnote{Ces concepts sont détaillés dans la partie \ref{systemes-logiques}}. Le potentiel de référence utilisé pour la mesure des tensions devra donc être commun entre les deux circuits : chaque bus de données contiendra alors généralement une ligne dédié à la mise en commun de la masse.\\
 
-L'échange de données proprement dit consistera en une alternance de lectures et d'écritures sur le bus de communication. Plus précisément, un bit sera émis par un des circuits intégrés quand l'état logique est forcé par ce circuit. Il est alors nécessaire que le circuit intégré souhaitant recevoir l'information « lise » l'état logique d'une ligne, c'est à dire qu'il relève cet état logique sans l'influencer. On parlera alors d'un troisième état pour le circuit, dit état déconnecté ou de haute impédance\footnote{On parle d'impédance comme équivalent complexe de la résistance, dans le cas de variations sinusoïdales des grandeurs.} (hi-Z)\footnote{Le symbole Z représente l'impédance et le préfixe hi signifie high en Anglais, soit haut en Français}. Cet état correspondra à une forte résistance entre le point considéré et la masse, qui ne perturbe alors pas la tension appliquée en ce point par un circuit externe. Ainsi, en état hi-Z, il sera possible pour le circuit recevant l'information de relever l'état logique de la ligne sans le changer.\\
+L'échange de données proprement dit consistera en une alternance de lectures et d'écritures sur le bus de communication. Plus précisément, un bit sera émis par un des circuits intégrés quand l'état logique est forcé par ce circuit. Il est alors nécessaire que le circuit intégré souhaitant recevoir l'information « lise » l'état logique d'une ligne, c'est à dire qu'il relève cet état logique sans l'influencer. On parlera alors d'un troisième état pour le circuit, dit état déconnecté hi-Z\footnote{Le symbole Z représente l'impédance et le préfixe hi signifie high en Anglais, soit haut en Français} ou état de haute impédance\footnote{On parle d'impédance comme équivalent complexe de la résistance, dans le cas de variations sinusoïdales des grandeurs.}. Cet état traduira une forte résistance entre le point considéré et la masse dans le circuit en question, qui ne perturbe alors pas la tension appliquée en ce point par un circuit externe. Ainsi, en état hi-Z, il sera possible pour le circuit recevant l'information de relever l'état logique de la ligne sans le changer.\\
 
 Si tous les circuits reliés au bus sont en état hi-Z (c'est à dire qu'aucun circuit n'impose d'état logique), l'état du circuit sera à priori indéterminé. C'est pour cela que l'on connecte généralement aux lignes des résistances dites de rappel, de valeur faible devant la résistance interne du circuit, que l'on porte à un potentiel correspondant au niveau logique souhaité. Si on souhaite qu'une ligne du bus soit dans un état haut quand il n'y a pas d'activité, on reliera alors cette ligne avec une résistance de rappel à la tension positive de référence.
 
 \subsubsection*{Écrasement, arbitrage et rôles}
 
 Dans le cas où tous les circuits intégrés utilisent la même ligne à la fois pour la lecture et l'écriture, on peu se retrouver dans le cas où deux circuits intégrés cherchent à écrire sur la ligne simultanément : on parle alors d'écrasement des données. C'est une des raisons qui conduit à la nécessité d'attribuer des temps réservés à la lecture et à l'écriture pour chaque puce et donc de synchroniser les communications.\\
-Cependant, il est également possible de mettre en place un arbitrage : les deux circuits doivent alors lire l'état de la ligne après avoir écrit un bit. Si l'état ne correspond pas au bit envoyé, le circuit perd la main et doit cesser d'écrire sur le bus : on dit qu'il a perdu l'arbitrage. Cela n'est possible que quand un état logique a priorité sur l'autre (il s'agit d'une caractéristique du bus) : si un circuit inscrit un état haut alors qu'un autre circuit inscrit simultanément un été bas, la ligne prendra systématiquement l'un des deux états. On qualifie alors cet état de dominant et l'autre état de récessif. Par exemple, pour le bus CAN\footnote{Controller Area Network en Anglais, soit contrôleur de zone de réseau en Français}, très utilisé dans les automobiles, l'état logique bas est dominant et l'état haut est récessif.\\
+Cependant, il est également possible de mettre en place un arbitrage : les deux circuits doivent alors lire l'état de la ligne après avoir écrit un bit. Si l'état ne correspond pas au bit envoyé, le circuit perd la main et doit cesser d'écrire sur le bus : on dit qu'il a perdu l'arbitrage. Cela n'est possible que quand un état logique a priorité sur l'autre (il s'agit d'une caractéristique du bus) : si un circuit inscrit un état haut alors qu'un autre circuit inscrit simultanément un été bas, la ligne prendra systématiquement l'un des deux états. On qualifie alors cet état de dominant et l'autre état de récessif. Par exemple, pour le bus CAN\notetrad{Controller Area Network}{contrôleur de zone de réseau}, très utilisé dans les automobiles, l'état logique bas est dominant et l'état haut est récessif.\\
 
-Plutôt que de permettre à tous les circuits intégrés d'écrire en même temps, certains bus attribuent également des rôles, maître ou esclave à chaque circuit. C'est par exemple le case du bus \iic\footnote{\iic/IIC pour Inter Integrated Circuits en Anglais, soit inter circuits intégrés en Français}, très utilisé pour la communication entre le processeur et les puces dans les ordinateurs. Généralement, le circuit dédié à la gestion de l'\iic\ attaché au processeur (on parle de contrôleur \iic) est maître du bus alors que toutes les autres puces reliées au bus sont esclaves. Dans cette configuration, seul le maître peut commencer à écrire sur le bus et les esclaves ne pourront écrire sur le bus qu'en réponse à une demande du maître.\\
+Plutôt que de permettre à tous les circuits intégrés d'écrire en même temps, certains bus attribuent également des rôles, maître ou esclave à chaque circuit. C'est par exemple le case du bus \iic\notetrad{\iic$\mathrm{/IIC\ pour}$ Inter Integrated Circuits}{inter circuits intégrés}, très utilisé pour la communication entre le processeur et les puces dans les ordinateurs. Généralement, le circuit dédié à la gestion de l'\iic\ attaché au processeur (on parle de contrôleur \iic) est maître du bus alors que toutes les autres puces reliées au bus sont esclaves. Dans cette configuration, seul le maître peut commencer à écrire sur le bus et les esclaves ne pourront écrire sur le bus qu'en réponse à une demande du maître.\\
 
-Quand plusieurs esclaves sont connectés au même bus, des adresses pourront leur être attribuées pour que le maître indique à quel circuit la transaction s'adresse. L'esclave pourra alors confirmer sa présence en écrivant un bit de valeur particulière à un moment donné de l'échange : on parle d'acquittement. La valeur de ce bit sera choisie en fonction du rappel de la ligne : s'il existe une résistance de rappel connectée à l'état logique haut, le bit d'acquittement sera d'état logique bas et réciproquement, de sorte que le bit ne soit jamais lu si aucun esclave n'est concerné. L'utilisation d'un bit d'acquittement pourra d'ailleurs également être utilisée pour confirmer le bon déroulement d'une action à la fois par le maître et l'esclave concerné.
+Quand plusieurs esclaves sont connectés au même bus, des adresses pourront leur être attribuées pour que le maître indique à quel circuit la transaction s'adresse. L'esclave pourra alors confirmer sa présence en écrivant un bit de valeur particulière à un moment donné de l'échange : on parle d'acquittement. La valeur de ce bit sera choisie en fonction du rappel de la ligne : s'il existe une résistance de rappel connectée à l'état logique haut, le bit d'acquittement sera d'état logique bas et réciproquement, de sorte que cette valeur du bit ne soit jamais lue si aucun esclave n'est concerné. Un bit d'acquittement pourra d'ailleurs également être utilisée pour confirmer le bon déroulement d'une action à la fois par le maître et l'esclave concerné au cours de la transaction.
 
 \subsubsection*{Protocole de communication}
 
 Il s'agit bien là de règles formelles qui doivent être respectées par chaque puce utilisant le bus : l'ensemble de ces règles constitue le protocole de communication utilisé sur le bus. En particulier, le protocole va permettre de définir quelle puce peut lire ou écrire à quel moment. Cela n'est cependant possible qu'avec un signal de synchronisation permettant de découper le temps en quantités discrètes : on utilise donc une horloge comme tel signal de synchronisation, qui fait partie intégrante du bus et qui doit être prise en compte par tous les circuits connectés au bus. Par exemple, dans le cas du bus \iic, il n'est permis de changer l'état de la ligne de données que sur un temps bas de l'horloge. Le temps haut est alors réservé à la lecture de l'état du bus (sauf exceptions telles que le signalement d'un début ou d'une fin de transaction). Le protocole définit par ailleurs la signification de la succession des bits envoyés sur le bus, à partir du signalement du début de la transaction. On détaillera en partie ce protocole dans la partie \ref{communication-i2c}.\\
 
-Certains bus n'utilisent cependant pas d'horloge de synchronisation mais une configuration explicite de la vitesse de changement d'état du bus, commune à tous les circuits reliés au bus. La communication sera synchronisée par un bit ou un ensemble de bits donnés que le circuit de réception pourra identifier. Des horloges internes aux composants seront de cette manière synchronisées entre-elles pour permettre de donner sens à la communication. Ce principe est exploité dans les composants UART\footnote{Universal Asynchronous Receiver Transmitter en Anglais, soit émetteur-récepteur asynchrone universel en Français.}.\\
+Certains bus n'utilisent cependant pas d'horloge de synchronisation mais une configuration explicite de la vitesse de changement d'état du bus, commune à tous les circuits reliés au bus. La communication sera synchronisée par un bit ou un ensemble de bits donné que le circuit de réception pourra identifier. Des horloges internes aux composants seront de cette manière synchronisées entre elles pour permettre de donner sens à la communication. Ce principe est exploité dans les composants UART\notetrad{Universal Asynchronous Receiver Transmitter}{émetteur-récepteur asynchrone universel}.\\
 
 Il existe donc une grande variété de bus, chacun avec un certain nombre de caractéristiques :
 \begin{itemize}
@@ -71,57 +71,57 @@ Il existe donc une grande variété de bus, chacun avec un certain nombre de car
 \item[--] Avec résistance(s) de rappel à un niveau particulier
 \end{itemize}
 
-\subsection{Communication entre systèmes numériques via le protocole \iic }
+\subsection{Communication entre systèmes numériques via le protocole \iic}
 \label{communication-i2c}
 
-Au sein de la Cubieboard2, la communication avec le circuit intégré responsable de la gestion de l'alimentation électrique, le PMIC\footnote{Power Management Integrated Circuit en Anglais, soit circuit intégré de gestion de l'énergie en Français.} \bsc{AXP209} est effectuée par un bus \iic. Il s'agit d'un bus aux caractéristiques suivantes :
+Au sein de la Cubieboard2, la communication avec le circuit intégré responsable de la gestion de l'alimentation électrique, le PMIC\notetrad{Power Management Integrated Circuit}{circuit intégré de gestion de l'énergie} \emph{AXP209} est effectuée par un bus \iic. Il s'agit d'un bus aux caractéristiques suivantes :
 \begin{itemize}
-\item[--] Synchrone, horloge de fréquence quelconque allant jusqu'à $400$ kHz
+\item[--] Synchrone, horloge de fréquence quelconque allant jusqu'à 400 kHz
 \item[--] Communication en série
 \item[--] Circuits intégrés affectés d'une priorité de type maître/esclave (le SoC est maître) et chacun d'une adresse
 \item[--] Avec une ligne commune pour la lecture et l'écriture
 \item[--] Avec résistances de rappel au niveau haut\\
 \end{itemize}
 
-La ligne de données est notée $SDA$ et la ligne d'horloge $SCL$. Le schéma électrique\notecite{CubieboardDiagram} de connexion de l'\bsc{AXP209} au SoC est détaillé avec la figure \ref{axp209} :
+La ligne de données est notée $SDA$ et la ligne d'horloge $SCL$. Le schéma électrique\notecite{CubieboardDiagram} de connexion de l'\emph{AXP209} au SoC est détaillé avec la figure \ref{axp209} :
 
 \begin{figure}[!h]
 \centering
 \includegraphics[width=9cm]{axp209.png}
-\caption{Schéma de connexion de l'\bsc{AXP209} au SoC}
+\caption{Schéma de connexion de l'\emph{AXP209} au SoC}
 \label{axp209}
 \end{figure}
 
-On souhaite alors faire communiquer la Cubieboard2 avec le microcontrôleur Arduino, en utilisant le bus et le protocole \iic. La Cubieboard2 dispose de 5\notecite{A20UserManual} composants dédiés à la gestion du bus \iic\ intégrés au sein de son SoC : il s'agit de contrôleurs \iic\ \bsc{Marvell MV64xxx}\notecite{AWIPCores}. L'Arduino ne dispose par contre pas de contrôleur intégré. Il va s'agir d'implémenter la gestion du protocole \iic\ dans un programme qui sera exécuté sur l'Arduino, en utilisant deux lignes numériques d'entrée/sortie dont dispose le microcontrôleur de l'Arduino : ils'agit de la technique dite de bit banging.\\ Ces lignes d'entrée/sortie se comportent d'après la logique trois états, avec un état haut, un état bas et un état hi-Z qui permettra la lecture. On pourra configurer le mode de chaque ligne avec des instructions. Les entrées/sorties sont plus précisément contrôlées en écrivant à des adresses mémoire précises (on parle de MMIO\footnote{Memory-Mapped Input/Output en Anglais, soit entrée/sortie cartographiée en mémoire en Français}) plutôt qu'avec des instructions directes pour le processeur (on parle de PIO\footnote{Programmed Input/Output en Anglais, soit entrée/sortie programmée en Français}).\\
+On souhaite alors faire communiquer la Cubieboard2 avec le microcontrôleur Arduino, en utilisant le bus et le protocole \iic. La Cubieboard2 dispose de 5 composants\notecite{A20UserManual} dédiés à la gestion du bus \iic\ intégrés au sein de son SoC : il s'agit de contrôleurs \iic\ \bsc{Marvell} \emph{MV64xxx}\notecite{AWIPCores}. L'Arduino ne dispose par contre pas de contrôleur intégré pour l'\iic. Il va s'agir d'implémenter la gestion du protocole \iic\ dans un programme qui sera exécuté sur l'Arduino, en utilisant deux lignes numériques d'entrée/sortie dont dispose le microcontrôleur de l'Arduino : ils'agit de la technique dite de bit banging.\\ Ces lignes d'entrée/sortie se comportent d'après la logique trois états, avec un état haut, un état bas et un état hi-Z qui permettra la lecture. On pourra configurer le mode de chaque ligne avec des instructions. Les entrées/sorties sont plus précisément contrôlées en écrivant à des adresses mémoire précises (on parle de MMIO\notetrad{Memory-Mapped Input/Output}{entrée/sortie cartographiée en mémoire}) plutôt qu'avec des instructions directes pour le processeur (on parle de PIO\notetrad{Programmed Input/Output}{entrée/sortie programmée}).\\
 
-Afin de relier les deux systèmes, on cherchera à connecter deux lignes, entre deux entrées/sorties numériques de l'Arduino et des connecteurs reliés aux points $SDA$ et $SCL$ de la Cubieboard2. Si les entrées/sorties numériques de l'Arduino sont disponibles en grande quantité, seul le contrôleur \iic\ identifié par l'index 1 est accessible par les connecteurs externes de la carte (dans la littérature spécifique au SoC \bsc{A20}, il est appelé $TWI1$\footnote{Two Wire Interface en Anglais, soit interface à deux fils en Français, terme équivalent à \iic.}). On relie alors les connecteurs reliés à $TWI1-SDA$ et $TWI1-SCK$\footnote{Référence : http://linux-sunxi.org/Cubieboard/ExpansionPorts}à l'Arduino. Une photographie des deux cartes connectées est reporté en annexe \ref{ardui2c-photo}.\\
+Afin de relier les deux systèmes, on cherchera à connecter deux lignes, entre deux entrées/sorties numériques de l'Arduino et des connecteurs reliés aux points $SDA$ et $SCL$ de la Cubieboard2. Si les entrées/sorties numériques de l'Arduino sont disponibles en grande quantité, seul le contrôleur \iic\ identifié par l'index 1 est accessible par les connecteurs externes de la carte Cubieboard2 (dans la littérature spécifique au SoC \emph{A20}, il est appelé $TWI1$ : la dénomination TWI\notetrad{Two Wire Interface}{interface à deux fils} est équivalente à \iic). On relie alors les connecteurs reliés à $TWI1-SDA$ et $TWI1-SCK$\notecite{linux-sunxi-cubieboard-expansion-ports} à l'Arduino. Une photographie des deux cartes connectées est reportée en annexe \ref{ardui2c-photo}.\\
 
 \subsubsection*{\bsc{U-Boot} et prise en charge du contrôleur \iic\ $TWI1$}
 
-Du point de vue du logiciel, on utilisera directement la ligne de commande du chargeur de démarrage \bsc{U-Boot}. Il s'agit du premier logiciel exécuté par la carte (avant le chargement du système d'exploitation) et qui offre un interpréteur de commandes proposant de multiples fonctionnalités, y compris la gestion du bus \iic. Cependant, au jour de réalisation de l'expérience, \bsc{U-Boot} ne prenait en charge que le premier contrôleur \iic, noté $TWI0$ (utilisé pour communiquer avec le PMIC \bsc{AXP209}).\\
+Du point de vue du logiciel, on utilisera directement la ligne de commande du chargeur de démarrage \bsc{U-Boot}. Il s'agit du premier logiciel exécuté par la carte (avant le chargement du système d'exploitation) et qui offre un interpréteur de commandes proposant de multiples fonctionnalités, y compris la gestion du bus \iic. Cependant, au jour de réalisation de l'expérience, \bsc{U-Boot} ne prenait en charge que le premier contrôleur \iic, noté $TWI0$ (utilisé pour communiquer avec le PMIC \emph{AXP209}).\\
 
 Afin de réaliser l'expérience, nous avons modifié le code source (écrit en langage C) d'\bsc{U-Boot}, ce qui a été possible comme il s'agit d'un logiciel libre. En particulier, il aura été nécessaire de mettre en place une gestion dynamique de l'index du bus à utiliser, de configurer les points d'entrée/sortie dédiés à l'\iic\ et de définir l'emplacement en mémoire de ces contrôleurs. Le développement d'\bsc{U-Boot} est conduit de manière communautaire (comme c'est souvent le cas pour un logiciel libre) et les contributions externes sont les bienvenues, ce qui nous aura poussé à proposer notre modification aux mainteneurs du logiciel, au travers de la liste de discussion\footnote{Liste de discussion du projet \bsc{U-Boot} : http://lists.denx.de/mailman/listinfo/u-boot} prévue à cet effet. Après 5 différentes itérations de la proposition\footnote{Archives de la liste de discussion du projet \bsc{U-Boot} pour le mois d'Avril 2015 : http://lists.denx.de/pipermail/u-boot/2015-April/}, résultats des commentaires émis par la communauté et les mainteneurs des parties du code concernés par la modification, celle-ci a finalement été approuvée et intégrée au code source officiel du projet\footnote{Modification "i2c: mvtwsi: Support for up to 4 different controllers" : http://git.denx.de/?p=u-boot.git;a=commit;h=dd82242b4dd7d251ef9ba43563cf9a0017d6f98e}\footnote{Modification "sunxi: Complete i2c support for each supported platform
-" : http://git.denx.de/?p=u-boot.git;a=commit;h=6c739c5d8a3466f8ef2f8543636484957bcca6ee}. On aura vérifié le bon fonctionnement du code en utilisant l'analyseur logique et en lançant un sondage du bus avec la commande $i2c\ probe$, qui fait apparaître des signaux sur la lignes de donnée et la ligne d'horloge.
+" : http://git.denx.de/?p=u-boot.git;a=commit;h=6c739c5d8a3466f8ef2f8543636484957bcca6ee}. On aura vérifié le bon fonctionnement du code en utilisant l'analyseur logique et en lançant un sondage du bus avec la commande $i2c\ probe$, qui fait apparaître des signaux sur la ligne de donnée et la ligne d'horloge.
 
 \subsubsection*{Réalisation du programme}
 
-Une fois les aspects matériels et logiciels en place, on peut commencer la réalisation du programme propre à l'Arduino. Celui-ci sera réalisé en langage C natif, c'est à dire sans sur-couche propre à l'Arduino. Il existe en effet un IDE\footnote{Integrated Development Environment en Anglais, soit environnement de développement intégré en Français} propre à l'Arduino, contenant un certain nombre de sur-couches au langage C++ utilisé. Comme notre application est fortement dépendante du temps, on cherchera à écrire du code au plus proche du langage machine afin de ne pas ralentir l'exécution. Le système de compilation sera basé sur de traditionnels $Makefiles$, très couramment utilisés pour le développement au sein de systèmes de type \bsc{Unix}. Le compilateur utilisé est une variante de \bsc{GCC} propre à l'architecture matérielle \bsc{AVR} : \bsc{avr-gcc}. On utilisera une librairie C standard propre à l'\bsc{AVR}, qui fournit également des définitions propres au microcontrôleur : l'\bsc{avr-libc}. Enfin, le logiciel sera envoyé sur la carte en utilisant l'utilitaire \bsc{avrdude}. Enfin, on utilisera l'outil de gestion de code source \bsc{git} pour garder trace des modifications. Tous ces logiciels sont des logiciels libres et on donne d'ailleurs comme licence d'utilisation à notre logiciel, astucieusement nommé $ardui2c$, la Licence Publique Générale GNU en version 3 ou plus récente\footnote{Texte complet de la licence : https://www.gnu.org/licenses/gpl.html}, ce qui en fait également un logiciel libre.\\
+Une fois les aspects matériels et logiciels en place, on peut commencer la réalisation du programme propre à l'Arduino. Celui-ci sera réalisé en langage C natif, c'est à dire sans sur-couche propre à l'Arduino. Il existe en effet un IDE\notetrad{Integrated Development Environment}{environnement de développement intégré} propre à l'Arduino, contenant un certain nombre de sur-couches au langage C++ utilisé. Comme notre application est fortement dépendante du temps, on cherchera à écrire du code au plus proche du langage machine afin de ne pas ralentir l'exécution. Le système de compilation sera basé sur de traditionnels $Makefiles$, très couramment utilisés pour le développement au sein de systèmes de type \bsc{Unix}. Le compilateur utilisé est une variante de \bsc{GCC} propre à l'architecture matérielle \bsc{AVR} : \bsc{avr-gcc}. On utilisera une librairie C standard propre à l'\bsc{AVR}, qui fournit également des définitions propres au microcontrôleur : l'\bsc{avr-libc}. De plus, le logiciel sera envoyé sur la carte en utilisant l'utilitaire \bsc{avrdude}. Enfin, on utilisera l'outil de gestion de code source \bsc{git} pour garder trace des modifications. Tous ces logiciels sont des logiciels libres et on donne d'ailleurs comme licence d'utilisation à notre logiciel, astucieusement nommé $ardui2c$, la Licence Publique Générale GNU en version 3 ou plus récente\footnote{Texte complet de la licence : https://www.gnu.org/licenses/gpl.html}, ce qui en fait également un logiciel libre.\\
 
-Le code source lui-même est rédigé avec un terminologie en Anglais, afin qu'il soit techniquement compréhensible (et modifiable) par le plus grand nombre, mais on attache tout de même des commentaires en Français, afin de faciliter sa compréhension dans le cadre de notre projet. Afin d'analyser l'exécution étape par étape du programme au cours de son écriture, on cherche à utiliser un dispositif d'entrée/sortie simple à utiliser. L'Arduino dispose d'un contrôleur UART permettant d'envoyer au cours de l'exécution des informations de débogage (typiquement, des chaînes de caractères) à un ordinateur relié à la carte par USB (un convertisseur UART/USB est également intégré à la carte Arduino). Cependant, la vitesse de transmission des informations est au maximum de $115200$ bits/s (on parle de bauds), qui n'est pas suffisante pour transmettre suffisamment rapidement ces informations tout en relevant les changements des lignes du bus. En effet, une première expérience montre que le microcontrôleur n'arrive pas à correctement détecter les changements d'états de la ligne d'horloge, qui a une fréquence que l'on a fixée à $50$ kHz, soit $2 * 50 000 = 100 000$ changements d'état logique par seconde. Il est donc évident que cette solution n'est pas utilisable.\\
+Le code source lui-même est rédigé avec une terminologie (noms des fonctions, des variables) en Anglais, afin qu'il soit techniquement compréhensible (et modifiable) par le plus grand nombre, mais on attache tout de même des commentaires en Français, afin de faciliter sa compréhension dans le cadre de notre projet. Afin d'analyser l'exécution étape par étape du programme au cours de son écriture, on cherche à utiliser un dispositif d'entrée/sortie simple à utiliser. L'Arduino dispose d'un contrôleur UART permettant d'envoyer au cours de l'exécution des informations de débogage (typiquement, des chaînes de caractères) à un ordinateur relié à la carte par USB (un convertisseur UART/USB est déjà intégré à la carte Arduino). Cependant, la vitesse de transmission des informations est au maximum de $115200$ bits/s (on parle de bauds), qui n'est pas suffisante pour transmettre suffisamment rapidement ces informations tout en relevant les changements des lignes du bus. En effet, une première expérience montre que le microcontrôleur n'arrive pas à correctement détecter les changements d'états de la ligne d'horloge, qui a une fréquence que l'on a fixée à $50$ kHz, soit $2 * 50 000 = 100 000$ changements d'état logique par seconde. Il est donc évident que cette solution n'est pas utilisable.\\
 
-Pour palier à ce problème, on a utilisé une sortie numérique du microcontrôleur que l'on dédie au débogage ($DEBUG$) et dont on inversera l'état logique à un moment donné de l'exécution du programme (par exemple à la détection d'un événement en particulier). Il s'agit de la fonction $debug$, que l'on associera à un test que l'on souhaite vérifier à l'exécution. Certains événements, tels que les conditions de départ ou de fin ont ainsi pu être détectés, mais on remarque un décalage entre la fin des événements et l'apparition du changement sur la ligne de débogage. L'optimisation du code produit lors de la compilation (argument $-Os$ du compilateur \bsc{avr-gcc}) permet de gagner en temps d'exécution (moins d'instructions à exécuter) et donc de rapprocher le changement d'état de la ligne de débogage de l'événement. On utilise encore une fois l'analyseur logique pour relever l'état des lignes $SDA$, $SCL$ et $DEBUG$.\\
+Pour palier à ce problème, on a utilisé une sortie numérique du microcontrôleur que l'on dédie au débogage ($DEBUG$) et dont on inversera l'état logique à un moment donné de l'exécution du programme (par exemple à la détection d'un événement en particulier). Il s'agit de la fonction $debug$, que l'on appellera avec un test que l'on souhaite vérifier à l'exécution. Certains événements, tels que les conditions de départ ou de fin ont ainsi pu être détectés, mais on remarque un décalage entre la fin des événements et l'apparition du changement sur la ligne de débogage. L'optimisation du code produit lors de la compilation (argument $-Os$ du compilateur \bsc{avr-gcc}) permet de gagner en temps d'exécution (moins d'instructions à exécuter) et donc de rapprocher le changement d'état de la ligne de débogage de l'événement. On utilise encore une fois l'analyseur logique pour relever l'état des lignes $SDA$, $SCL$ et $DEBUG$.\\
 
-Une fois tous les moyens de déterminer l'état du système en place, il a été possible de commencer l'implémentation du protocole \iic\footnote{La compréhension du protocole est basée sur les explications et les exemples de la référence \cite{WikipediaI2C} et des connaissances antérieures.}. On choisit de réaliser le programme sous la forme d'une machine à états\notecite{WikipediaAutomateFini}, avec un variable $state$ décrivant l'état du système. Les différents états possibles sont définis au début du programme et chaque état est décrit dans un commentaire associé. Dans la fonction principale ($main$) du programme, une boucle infinie ($while (1)$) assure l'interprétation du protocole en continu et contient un disjonction des cas sur l'état du système ($switch (state)$) attribue à chaque état les actions à réaliser, après avoir lu l'état des lignes du bus et gardé référence de l'état précédent.\\
+Une fois tous les moyens de déterminer l'état du système en place, il a été possible de commencer l'implémentation du protocole \iic\footnote{La compréhension du protocole est basée sur les explications et les exemples de la référence \cite{WikipediaI2C} et des connaissances antérieures.}. On choisit de réaliser le programme sous la forme d'une machine à états\notecite{WikipediaAutomateFini}, avec une variable $state$ décrivant l'état du système. Les différents états possibles sont définis au début du programme et chaque état est décrit dans un commentaire associé. Dans la fonction principale ($main$) du programme, une boucle infinie ($while (1)$) assure l'interprétation du protocole en continu et contient une disjonction des cas sur l'état du système ($switch (state)$) qui attribue à chaque état les actions à réaliser, après avoir lu l'état des lignes du bus et gardé référence de l'état précédent.\\
 
-Les entrées/sorties sont effectuées par des lectures ou des écritures sur des ports données, formalisés par des adresses mémoire. On utilise les opérations booléennes du langage C pour effectuer les actions de lecture et d'écriture de bits aux adresses et aux bits nécessaires, tel qu'indiqué par la documentation du microcontrôleur\notecite{AVRDatasheet} et les déclarations de l'\bsc{avr-libc}. Ainsi, pour forcer l'état logique de la ligne de données, on écrira un bit dont le poids est défini par la définition $PORTB4$ à l'adresse donnée par la définition $PORTB$, soit en C : $PORTB |= (1 << PORTB4)$. Lors de l'implémentation, on fera attention à ne changer l'état du bus de données que sur un temps bas d'horloge et de ne lire l'état logique du bus imposé par le maître que sur un temps haut d'horloge, comme indiqué dans les spécifications du protocole I2C\notecite{WikipediaI2C}.\\
+Les entrées/sorties sont effectuées par des lectures ou des écritures sur des ports donnés, accessibles par des adresses mémoire. On utilise les opérations booléennes du langage C pour effectuer les actions de lecture et d'écriture de bits aux adresses et aux bits nécessaires, tel qu'indiqué par la documentation du microcontrôleur\notecite{AVRDatasheet} et les déclarations de l'\bsc{avr-libc}. Ainsi, pour forcer l'état logique de la ligne de données, on écrira un bit dont le poids est défini par la définition $PORTB4$ à l'adresse donnée par la définition $PORTB$, soit en C : $PORTB |= (1 << PORTB4)$. Lors de l'implémentation, on fera attention à ne changer l'état du bus de données que sur un temps bas d'horloge et de ne lire l'état logique du bus imposé par le maître que sur un temps haut d'horloge, comme indiqué dans les spécifications du protocole I2C\notecite{WikipediaI2C}.\\
 
-Avec les protocole \iic, les données de l'esclave sont référencées par des adresses : on parle alors de registres, dont la taille vaut un octet. Le maître pourra lire ou écrire les données d'un registre en spécifiant son adresse. Notre implémentation dispose de $5$ registres, représentant $CPBx$ au format ASCII\notetrad{American Standard Code for Information Interchange}{Code américain normalisé pour l'échange d'information} avec un octet final nul pour terminer la chaîne de caractères.\\
+Avec le protocole \iic, les données de l'esclave sont référencées par des adresses : on parle alors de registres, dont la taille vaut un octet. Le maître pourra lire ou écrire les données d'un registre en spécifiant son adresse. Notre implémentation dispose de $5$ registres, contenant la chaîne de caractères $CPBx$ au format ASCII\notetrad{American Standard Code for Information Interchange}{code américain normalisé pour l'échange d'information} avec un octet final nul pour marquer la fin de la chaîne.\\
 
 L'ensemble du code source ainsi réalisé est reporté en annexe \ref{ardui2c}. Il est également disponible sur Internet\footnote{Le code source d'Ardui2c est disponible à l'adresse : http://git.paulk.fr/gitweb/?p=ardui2c.git;a=summary} au travers du système de gestion de version $git$, qui garde trace des modifications et permet de dupliquer aisément le code source. C'est un outil de gestion de code source aux visées communautaires, initialement développé par \bsc{Linux Torvalds} pour la gestion du code du noyau \bsc{Linux}, dont il est également l'auteur initial.
 
 \subsubsection*{Utilisation du programme}
 
-On montre l'utilisation du programme à partir de la Cubieboard2. Dans l'interpréteur de commandes \bsc{U-Boot}, il faudra tout d'abord sélectionner l'index du bus que l'on souhaite utiliser et la vitesse de l'horloge (on choisit $50$ kHz) :
+On montre l'utilisation du programme à partir de la Cubieboard2. Dans l'interpréteur de commandes d'\bsc{U-Boot}, il faudra tout d'abord sélectionner l'index du bus que l'on souhaite utiliser et la vitesse de l'horloge (on choisit $50$ kHz) :
 \begin{verbatim}
 sunxi# i2c dev 1
 Setting bus to 1
@@ -134,7 +134,7 @@ On pourra alors détecter la présence de l'Arduino sur le bus avec la commande
 sunxi# i2c probe
 Valid chip addresses: 42
 \end{verbatim}
-L'adresse 0x42\footnote{Le préfixe $0x$ indique l'utilisation de la base 16, c'est donc $66$ en décimal.} est bien l'adresse attribuée à l'Arduino.\\
+L'adresse 0x42\footnote{Le préfixe 0x indique l'utilisation de la base 16 ou représentation hexadécimale. 0x42 en hexadécimal vaut donc 66 en décimal.} est bien l'adresse attribuée à l'Arduino.\\
 
 On pourra alors lire les 5 premiers registres :
 \begin{verbatim}
@@ -142,7 +142,7 @@ sunxi# i2c md 0x42 0x00 0x5
 0000: 43 50 42 78 00    CPBx
 \end{verbatim}
 
-De même, il est possible de changer la valeur d'un registre et d'observer ce changement :
+De même, il est possible de changer la valeur d'un registre et d'observer ce changement (on inscrit ici la valeur 0x50 dans le registre 0, qui correspond à un $P$ en ASCII) :
 \begin{verbatim}
 sunxi# i2c mw 0x42 0x00 0x50
 sunxi# i2c md 0x42 0x00 0x5
@@ -162,67 +162,65 @@ On relève également l'activité sur le bus avec l'analyseur logique et le logi
 
 \subsection{Efficacité du traitement algorithmique et complexité}
 
-La vitesse d’exécution d'un algorithme dépend du \og coût\fg{} de ses opérations, chaque opération de base nécessitant un certain temps de calcul par le processeur. Il existe une science informatique théorique et mathématique, qui étudie le coût en temps et ressources d'un algorithme pour résoudre un problème donné : la \emph{théorie de la complexité}. La \emph{complexité} d'un algorithme est une grandeur abstraite, c'est une fonction qui mesure le nombre d'opérations de base effectuées en fonction d'un paramètre $n$ (ce paramètre peut par exemple être la taille d'une liste), ou plus généralement qui caractérise la \og taille\fg{} d'un problème. La complexité mesure l'ordre de grandeur du coût algorithmique pour des grandes valeurs de $n$, généralement on la compare à une autre fonction mathématique dont le comportement en l'infini est similaire. On utilise pour cela les notations de \bsc{landau} : O, $\Omega$ ou $\theta$. \\
+La vitesse d'exécution d'un programme par un ordinateur dépend directement de sa capacité à traiter les opérations dans le temps, chaque opération de base nécessitant un certain temps de calcul par le processeur. La \emph{théorie de la complexité} formalise l'étude du coût en temps et en ressources des algorithmes. La \emph{complexité} d'un algorithme est une grandeur abstraite, c'est une fonction qui mesure le nombre d'opérations de base effectuées en fonction d'un paramètre $n$ (ce paramètre peut par exemple être la taille d'une liste), ou plus généralement qui caractérise la « taille » d'un problème. La complexité mesure l'ordre de grandeur du coût algorithmique pour des grandes valeurs de $n$, généralement on la compare à une autre fonction mathématique dont le comportement en l'infini est similaire. On utilise pour cela les \emph{notations de Landau} : O, $\Omega$ ou $\theta$. \\
 
-On rappelle les définitions mathématiques de ces différentes notations. Considérons deux fonctions $f:\R\mapsto\R$ et $g:\R\mapsto\R$ ainsi que deux constantes $(C_1,C_2)\in\R^2$. On a :
+On rappelle les définitions mathématiques de ces différentes notations. Considérons deux fonctions $f:\R\mapsto\R$ et $g:\R\mapsto\R$ ainsi que deux constantes $(C_1,C_2)\in\R^2$. Alors :
 \begin{itemize}
-       \item[--] on dit que $f$ est \emph{dominée} par $g$ quand $x$ tend vers l'infini si : \[\exists A\in\R,\ \forall x\in\R,\ x\geq A\ \Rightarrow\ |f(x)|\leq C_1|g(x)|\]
-       On note alors : $f=O(g)$. Visuellement cela signifie que les variations en l'infini de $f$ sont conditionnées par celles de $g$.
-       \item[--] on note $f=\Omega(g)$ si : \[\exists A\in\R,\ \forall x\in\R,\ x\geq A\ \Rightarrow\ C_1|g(x)|\leq|f(x)|\]
-       \item[--] on note $f=\theta$ si $f=O(g)$ et $f=\Omega(g)$, i.e si : \[\exists A\in\R,\ \forall x\in\R,\ x\geq A\ \Rightarrow\ C_1|g(x)|\leq|f(x)|\leq C_2|g(x)|\]
+\item[--] On note $f=O(g)$ si : $$\exists A\in\R,\ \forall x\in\R,\ x\geq A\ \Rightarrow\ |f(x)|\leq C_1|g(x)|$$ et on dit que $f$ est \emph{dominée} par $g$ quand $x$ tend vers l'infini. Visuellement cela signifie que les variations en l'infini de $f$ sont conditionnées par celles de $g$.
+\item[--] On note $f=\Omega(g)$ si : $$[\exists A\in\R,\ \forall x\in\R,\ x\geq A\ \Rightarrow\ C_1|g(x)|\leq|f(x)|$$
+\item[--] On note $f=\theta(g)$ si $f=O(g)$ et $f=\Omega(g)$, soit si : $$\exists A\in\R,\ \forall x\in\R,\ x\geq A\ \Rightarrow\ C_1|g(x)|\leq|f(x)|\leq C_2|g(x)|$$
 \end{itemize}
 
-C'est une notion très formelle et qui se base sur une unité arbitraire, on ne mesure pas réellement un temps, mais plutôt un nombre d'opérations élémentaires effectuées qui peuvent être par exemple : une comparaison de deux variables, un test binaire, une opération arithmétique, la création ou la modification d'une variable. Chaque opération coûte une unité, on peut ainsi en étudiant l'algorithme déduire une loi formelle qui définit sont comportement pour une taille initiale $n$, souvent notée $\mathcal{C}(n)$. L'étude de la complexité d'un algorithme permet d'évaluer son efficacité, plus la vitesse de convergence de $\mathcal{C}$ est lente et plus l'algorithme est efficace : un algorithme de complexité linéaire ($\mathcal{C}(n)=\theta(n)$) sera préféré à un algorithme de de complexité polynomiale (par exemple $\mathcal{C}(n)=\theta(n^2)$), tandis qu'un algorithme de complexité logarithmique ($\mathcal{C}(n)=\theta(\log(n))$) sera préféré à un autre de complexité linéaire.\\
+C'est une notion très formelle et qui se base sur une unité arbitraire : on ne mesure pas réellement un temps, mais plutôt un nombre d'opérations élémentaires effectuées qui peuvent être par exemple une comparaison de deux variables, un test binaire, une opération arithmétique, la création ou la modification d'une variable. Chaque opération coûte une unité et on peut en étudiant l'algorithme déduire une loi formelle qui définit sa complexité pour une taille de données $n$, souvent notée $\mathcal{C}(n)$. L'étude de la complexité d'un algorithme permet d'évaluer son efficacité : plus l'évolution de $\mathcal{C}$ avec $n$ est lente et plus l'algorithme est efficace : un algorithme de complexité linéaire ($\mathcal{C}(n)=\theta(n)$) sera préféré à un algorithme de de complexité quadratique (par exemple $\mathcal{C}(n)=\theta(n^2)$), tandis qu'un algorithme de complexité logarithmique ($\mathcal{C}(n)=\theta(\log(n))$) sera préféré à un autre de complexité linéaire.\\
 
-Cette grandeur est donc une grandeur à considérer pour l'optimisation du temps d'exécution d'un programme. En effet, plusieurs solutions algorithmiques de complexités différentes peuvent exister pour un problème quelconque et il ne tiendra bien souvent qu'à l'inventivité du programmeur pour trouver une solution encore meilleure.\\
+Cette grandeur est donc une grandeur à considérer pour l'optimisation du temps d'exécution d'un programme. En effet, plusieurs solutions algorithmiques de complexités différentes peuvent exister pour un problème quelconque et il ne tiendra bien souvent qu'à l'inventivité du programmeur pour trouver une solution encore meilleure (bien qu'il existe toujours une limite basse à la complexité pour la résolution d'un problème donné).\\
 
-Cependant, bien qu'un algorithme puisse être perfectible, sa vitesse d’exécution sera toujours limitée par les capacités matérielles de la machine sur laquelle on le lance. En effet, le processeur d'un ordinateur traite un certain nombre d'opérations (ou instructions) les unes à la suite des autres, cadencé par une horloge. Certains processeurs disposent d'instructions particulières, qui combinent parfois plusieurs opérations élémentaires en une(c'est par exemple le cas des extensions \bsc{NEON} dans les processeurs \bsc{ARM} tels que celui de la Cubieboard2.\\
+Cependant, bien qu'un algorithme puisse être perfectible, sa vitesse d’exécution sera toujours limitée par les capacités matérielles de la machine sur laquelle on l'exécute. En effet, le processeur d'un ordinateur traite un certain nombre d'opérations (ou instructions) les unes à la suite des autres, cadencé par une horloge. Certains processeurs disposent d'instructions particulières, qui combinent parfois plusieurs opérations élémentaires en une (c'est par exemple le cas des extensions \bsc{NEON} dans les processeurs \bsc{ARM} tels que celui de la Cubieboard2).\\
 
  Le temps d’exécution par le processeur d'un programme peut donc dépendre des paramètres suivants :
 \begin{itemize}
-       \item[--] les extensions utilisées pour les instructions
-       \item[--] les optimisations effectuées par le compilateur (par exemple, certaines opérations sont redondantes et peuvent-être éliminées durant la phase d'optimisation), afin de réduire le nombre d'instructions
-       \item[--] le nombre de $n$ d'instructions qui doivent être exécutées 
-       \item[--] le nombre $c$ moyen de cycles d'horloges (aussi abrégé $CPI$ : de l'anglais \emph{Cycle Per Instruction}) que nécessite une instruction : ce nombre dépend donc de la complexité de l'instruction, i.e comme vu précédemment, du nombre d'accès mémoire, des opérandes utilisées, ...
-       \item[--] la période $\tau$ d'un cycle d'horloge du processeur
-\end{itemize}\\
+\item[--] les extensions utilisées pour les instructions
+\item[--] les optimisations effectuées par le compilateur (par exemple, certaines opérations sont redondantes et peuvent-être éliminées durant la phase d'optimisation), afin de réduire le nombre d'instructions
+\item[--] le nombre de $n$ d'instructions qui doivent être exécutées 
+\item[--] le nombre $c$ moyen de cycles d'horloges (aussi abrégé CPI : de l'Anglais \emph{Cycle Per Instruction}) que nécessite une instruction pour être traitée
+\item[--] la période $\tau$ du cycle d'horloge du processeur\\
+\end{itemize}
 
 Le temps d'exécution $T_e$ d'un processus peut ainsi être déterminé grâce à l'équation :
 \begin{equation}
 T_e=nc\tau
 \label{eq:tempsexec}
 \end{equation}
-% Evolution architecture ordinateur Google Livres
 
-L'amélioration des performances passe donc nécessairement par un travail sur ces facteurs. Autant les valeurs $n$ et $c$ dépendent de l'algorithme, autant la données $\tau$ intrinsèque au matériel utilisé. Comme on a $\tau=\frac{1}{f}$ (où $f$ est la fréquence d'horloge du processeur), l'enjeu est d'augmenter la valeur de $f$ afin de diminuer le temps d'exécution des programmes. La fréquences est dépendant de la taille et du nombre de composants qui composent une unité de calcul, plus précisément les transistors qui constituent la \og brique\fg{} essentielle d'un processeur. La miniaturisation des transistors permet d'en augmenter leur densité surfacique sur un circuit intégré, et donc d'améliorer la fréquence d'horloge.\\ L'ingénieur américain Robert \bsc{Dennard} apporta une énorme contribution en mettant au point une méthode permettant de diminuer la taille des transistors et ainsi d'en d'augmenter la rapidité. L'évolution du nombre de transistors par unité de surface dans un processeur suit la loi de \bsc{Moore} : c'est une loi exponentielle qui prévoit le doublement des performances tous les 18 mois.
+L'amélioration des performances passe donc nécessairement par un travail sur ces facteurs. Autant les valeurs $n$ et $c$ dépendent de l'algorithme, autant la donnée $\tau$ est intrinsèque au matériel utilisé. Comme on a $\tau=\frac{1}{f}$ (où $f$ est la fréquence d'horloge du processeur), l'enjeu est d'augmenter $f$ afin de diminuer le temps d'exécution des programmes. La fréquence est limitée par la taille et le nombre de composants utilisés dans un processeur, plus précisément les transistors qui constituent la base essentielle d'un processeur et qui présentent une fréquence d'utilisation limitée par leur taille. La miniaturisation des transistors permet donc d'améliorer la fréquence d'horloge utilisable.\\ L'ingénieur américain Robert Dennard apporta une énorme contribution en mettant au point une méthode permettant de diminuer la taille des transistors et ainsi d'en d'augmenter la rapidité. Par ailleurs, l'augmentation du nombre de transistors permet un traitement plus efficace des instructions et donc de réduire la variable $c$ associée au CPI. L'évolution du nombre de transistors par unité de surface dans un processeur correspond à la \emph{loi de Moore} : c'est une loi exponentielle qui prévoit le doublement des performances tous les 18 mois.
 
 \subsection{Limites à la montée en fréquence et parallélisme}
 
-Si l'augmentation des fréquence en vue d'augmenter les performances d'exécution des programmes a pu porter ses fruits pendant plusieurs années, il devient de plus en plus difficile d'augmenter ces fréquences. Ainsi, à partir des années 2000, il a été nécessaire de s'orienter vers d'autres moyens pour rendre les processeurs plus performants. Les causes de ces limitations à la montée en fréquence sont multiples, et la majorité sont liés à des phénomènes physiques, qui posent un certain nombre de problèmes :\\
+Si l'augmentation des fréquences en vue d'augmenter les performances d'exécution des programmes a pu porter ses fruits pendant plusieurs années, il devient de plus en plus difficile d'augmenter ces fréquences. Ainsi, à partir des années 2000, il a été nécessaire de s'orienter vers d'autres moyens pour rendre les processeurs plus performants. Les causes de ces limitations à la montée en fréquence sont multiples et la majorité sont liées à des phénomènes physiques, qui posent un certain nombre de problèmes :\\
 
-Premièrement, la miniaturisation des transistors amène à la diminution des dimensions des fils d'interconnexion. Or le \emph{temps d'interconnexion}, c'est-à-dire le temps nécessaire pour que l'information transite d'un transistor à un autre, reste globalement constant. Malgré la finesse atteinte dans la gravure des processeurs, il n'est plus possible que l'information se propage dans les circuits en un temps imposées par de très hautes fréquences, puisque ce temps est directement fonction de la vitesse physique de propagation du signal électrique et la distance à parcourir ($v = \frac{d}{t}$), l'une étant constante et l'autre ayant atteint ses limites.\\
+Premièrement, la miniaturisation des transistors amène à la diminution des dimensions des fils d'interconnexion. Or le \emph{temps d'interconnexion}, c'est-à-dire le temps nécessaire pour que l'information transite d'un transistor à un autre, reste globalement constant. Malgré la finesse atteinte dans la gravure des processeurs, il n'est plus possible que l'information se propage dans les circuits en un temps imposé par de très hautes fréquences. En effet, ce temps est directement fonction de la vitesse physique de propagation du signal électrique et de la distance à parcourir ($v = \frac{d}{t}$), l'une étant constante et l'autre ayant atteint ses limites.\\
 
-Le second problème est d'ordre énergétique, en effet un transistor consomme de l'énergie (et en dissipe) à chaque commutation, ainsi on peut facilement voir que la consommation électrique et la dissipation d'énergie sont proportionnelles aux produit du nombre de transistor dans le circuit par la fréquence de l'horloge. Avec de très hautes fréquences, les processeurs sont gourmands en énergie et en dissipent beaucoup sous forme de chaleur : ils s'échauffent et sont de plus en plus compliqué à refroidir (nécessaire pour éviter la fusion des matériaux). Les systèmes de refroidissement à air utilisés dans les ordinateurs personnels ne suffisent plus.\\
+Le second problème est d'ordre énergétique, en effet un transistor consomme de l'énergie (et en dissipe) à chaque commutation : ainsi on peut facilement voir que la consommation électrique et la dissipation d'énergie sont proportionnelles aux produit du nombre de transistor dans le circuit par la fréquence de l'horloge. Avec de très hautes fréquences, les processeurs sont gourmands en énergie et en dissipent beaucoup sous forme de chaleur : ils s'échauffent et il est de plus en plus complexe de les refroidir (nécessaire pour éviter la fusion des matériaux). Les systèmes de refroidissement à air utilisés dans les ordinateurs personnels ne suffisent plus, même si l'efficacité énergétique des processeurs n'a cessé d'augmenter : c'est la \emph{loi de Kommey}.\\
 
 Il n'est donc plus possible d'augmenter les fréquences et donc de diminuer le facteur $\tau$ dans l'équation \eqref{eq:tempsexec}. On tentera alors de réduire le nombre $c$ de cycles par instruction. Pour cela, une solution consistera à augmenter le nombre d'instructions qui s'exécutent simultanément : on parle alors de parallélisme. Cette solution n'est effectivement applicable qu'à condition que le processeur soit capable de gérer plusieurs instructions simultanément. Cela implique alors une certaine conception du processeur, avec une duplication des circuits logiques effectuant le traitement de ces instructions : on parle de cœur du processeur. Ainsi, un processeur capable de parallélisme est qualifié de multi-cœur.\\
 
-Le système d'exploitation s'exécutant sur le processeur sera en charge de distribuer les processus en cours d'exécution sur les différents cœurs du processeur, permettant ainsi un gain en efficacité globale du système. Au sein d'un programme, il est également possible d'exécuter plusieurs fonctions en parallèle au sein de fils d'exécution séparés (ou \textit{threads} en Anglais), qui pourront être exécutés sur des cœurs séparés. Cette technique de programmation fonctionne d'ailleurs avec un seul cœur, de la même manière que différents processus sont exécutés avec une impression de simultanéité avec un seul cœur (un seul processus n'est véritablement exécuté à la fois, mais le système d'exploitation alterne rapidement les processus).\\
+Le système d'exploitation s'exécutant sur le processeur sera en charge de distribuer les processus en cours d'exécution sur les différents cœurs du processeur, permettant ainsi un gain en efficacité globale du système. Au sein d'un programme, il est également possible d'exécuter plusieurs fonctions en parallèle au sein de fils d'exécution séparés (ou \textit{threads} en Anglais), qui pourront être exécutés sur des cœurs séparés. Cette technique de programmation fonctionne d'ailleurs avec un seul cœur, de la même manière que différents processus sont exécutés avec une impression de simultanéité avec un seul cœur (un seul processus n'est véritablement exécuté à la fois, mais le système d'exploitation alterne rapidement les processus qui s'exécutent).\\
 
-Les ordinateurs utilisant des processeurs multi-cœurs sont désormais très courants sur le marché et de nos jours, il s'agit même de la norme. Ce n'est pas seulement le cas sur les ordinateurs traditionnels, puisque les appareils mobiles tels que les smartphones ou les tablettes sont également dotés de processeurs multi-cœurs. C'est de même le cas pour différents autres formats d'ordinateurs, y compris les ordinateurs à carte unique tels que la Cubieboard2. La Cubieboard2 est d'ailleurs la seconde itération de la série Cubieboard, utilisant un SoC \bsc{A20} à deux cœurs alors que la première itération utilise un SoC \bsc{A10} monocœur. La Cubieboard2 est donc également intéressante du point de vue du parallélisme.\\
+Les ordinateurs utilisant des processeurs multi-cœurs sont désormais très courants sur le marché et de nos jours, il s'agit même de la norme. Ce n'est pas seulement le cas sur les ordinateurs traditionnels, puisque les appareils mobiles tels que les smartphones ou les tablettes sont également dotés de processeurs multi-cœurs. C'est de même le cas pour différents autres formats d'ordinateurs, y compris les ordinateurs à carte unique tels que la Cubieboard2. La Cubieboard2 est d'ailleurs la seconde itération de la série Cubieboard, utilisant un SoC \emph{A20} à deux cœurs alors que la première itération utilise un SoC \emph{A10} monocœur. La Cubieboard2 est donc également intéressante du point de vue du parallélisme.\\
 
-Il existe cependant d'autres limites à l'augmentation des performances des ordinateurs,\\indépendamment des considérations relatives aux processeurs : c'est particulièrement le cas des accès en mémoire, qui peuvent prendre un temps très grand devant le traitement des opérations. C'est une des raisons qui conduit à introduire plusieurs types de mémoires au sein d'un ordinateur. On trouve alors une mémoire de stockage des données, dite mémoire morte, lente d'accès (en particulier pour l'écriture) mais disponible en grandes quantités et une mémoire dédiée à l'exécution des programmes, dite mémoire vive, considérablement plus réactive mais coûteuse à produire et donc disponible en faibles quantités. Un grand nombre d'accès à la mémoire morte pourra donc avoir l'effet de « goulot d'étranglement » et considérablement réduire les performances d'exécution du programme.
+Il existe cependant d'autres limites à l'augmentation des performances des ordinateurs,\\indépendamment des considérations relatives aux processeurs : c'est particulièrement le cas des accès en mémoire, qui peuvent prendre un temps très grand devant le traitement des opérations purement arithmétiques. C'est une des raisons qui conduit à introduire plusieurs types de mémoires au sein d'un ordinateur. On trouve alors une mémoire de stockage des données, dite mémoire morte, lente d'accès (en particulier pour l'écriture) mais disponible en grandes quantités et une mémoire dédiée à l'exécution des programmes, dite mémoire vive, considérablement plus réactive mais coûteuse à produire et donc disponible en faibles quantités. Un grand nombre d'accès à la mémoire morte pourra donc avoir l'effet de « goulot d'étranglement » et considérablement réduire les performances d'exécution du programme.
 
 \subsection{Explicitation du gain en efficacité et contraintes}
 
-Si la multiplicité des cœurs permet d'exécuter plusieurs programmes réellement en simultané, il est courant qu'un seul programme requiert toutes les capacités de la machine tant la tâche qu'il accomplie complexe. Si ce programme n'est pas prévu pour fonctionner sur plusieurs fils distincts, le calcul ne sera effectué que sur un seul cœur et l'avantage du multi-cœur sera en grande partie perdu, si ce n'est que le autres cœurs permettront de garantir une utilisation fluide du système pendant ce calcul lourd. Il est cependant bien souvent souhaitable de réduire la durée du calcul au détriment de l'utilisabilité immédiate de l'ordinateur.\\
+Si la multiplicité des cœurs permet d'exécuter plusieurs programmes réellement en simultané, il est courant qu'un seul programme requiert toutes les capacités de la machine tant la tâche qu'il accomplie est complexe. Si ce programme n'est pas prévu pour fonctionner sur plusieurs fils distincts, le calcul ne sera effectué que sur un seul cœur et l'avantage du multi-cœur sera en grande partie perdu, si ce n'est que le autres cœurs permettront de garantir une utilisation fluide du système pendant ce calcul lourd. Il est cependant bien souvent souhaitable de réduire la durée du calcul au détriment de l'utilisabilité immédiate de l'ordinateur.\\
 
-On va donc chercher à écrire et utiliser des programmes utilisant le nombre maximal de cœurs disponibles, ou pour le moins qui offrent à l'utilisateur cette possibilité, bien souvent en lui permettant de choisir le nombre de fils d'exécution (qui seront répartis sur les différents cœurs) à utiliser. Néanmoins, tous les algorithmes de ne prêtent pas forcément bien à un découpage en plusieurs fils s'exécutant sur plusieurs cœurs simultanément. En effet, exécuter plusieurs calculs en parallèle suppose que ces calculs sont indépendants entre eux. Hors, le cas général est plutôt le traitement d'un problème avec des étapes logiques qui dépendent respectivement des étapes précédentes. Il n'est donc pas toujours possible de paralléliser les calculs. Certaines grandes familles d'algorithmes pourront au contraire bien s'y prêter, comme c'est le cas des algorithmes de type « diviser pour régner » qui découpent les problèmes en sous-problèmes indépendants plus petits. On pourra alors traiter chaque sous problème sur un cœur séparé. Il est cependant inévitable de rassembler les calculs effectués en parallèle afin d'obtenir un résultat cohérent. Selon les problèmes, cette étape peut être triviale ou au contraire prendre un temps signification, qui ne pourra être effectué que sur un cœur.\\
+On va donc chercher à écrire et utiliser des programmes utilisant le nombre maximal de cœurs disponibles, ou pour le moins qui offrent à l'utilisateur cette possibilité, bien souvent en lui permettant de choisir le nombre de fils d'exécution (qui seront répartis sur les différents cœurs) à utiliser. Néanmoins, tous les algorithmes ne se prêtent pas forcément bien à un découpage en plusieurs fils s'exécutant en parallèle. En effet, exécuter plusieurs calculs en parallèle suppose que ces calculs sont indépendants entre eux. Hors, le cas général est plutôt le traitement d'un problème avec des étapes logiques qui dépendent respectivement des étapes précédentes. Il n'est donc pas toujours possible de paralléliser les calculs. Certaines grandes familles d'algorithmes pourront au contraire bien s'y prêter, comme c'est le cas des algorithmes de type « diviser pour régner » qui découpent les problèmes en sous-problèmes indépendants plus petits. On pourra alors traiter chaque sous problème sur un cœur séparé. Il est cependant inévitable de rassembler les calculs effectués en parallèle afin d'obtenir un résultat cohérent. Selon les problèmes, cette étape peut être triviale ou au contraire prendre un temps significant, qui ne pourra être effectué que sur un cœur.\\
 
-Expérimentalement, on va relever le gain en temps lié à l'utilisation de plusieurs fils d'exécution (répartis sur les différents cœurs) en encodant\footnote{Il s'agit de changement de format numérique.}, opération bien connue pour être demandant en calculs, qui sont liés aux opérations de décompression et de compression des images (et de manière moindre, du son). On effectue tout d'abord l'opération sur la Cubieboard2 en précisant au programme d'encodage \bsc{avconv} de n'utiliser qu'un seul fil d'exécution. Le temps d'exécution du programme est alors de \textbf{4 minutes et 41,509 secondes}. En précisant au programme d'utiliser deux fils d'exécutions (un pour chaque cœur du processeur), on obtient alors un temps d'exécution de \textbf{2 minutes et 53,528 secondes}, soit une réduction du temps de presque moitié : l'utilisation de plusieurs fils prouve donc son efficacité.\\
+Expérimentalement, on va relever le gain en temps lié à l'utilisation de plusieurs fils d'exécution (répartis sur les différents cœurs) en encodant\footnote{Il s'agit de changement de format numérique.} une courte vidéo, opération bien connue pour être demandante en calculs, qui sont liés aux opérations de décompression et de compression des images (et de manière moindre, du son). On effectue tout d'abord l'opération sur la Cubieboard2 en précisant au programme d'encodage \bsc{avconv} de n'utiliser qu'un seul fil d'exécution. Le temps d'exécution du programme est alors de \textbf{4 minutes et 41,509 secondes}. En précisant au programme d'utiliser deux fils d'exécutions (un pour chaque cœur du processeur), on obtient alors un temps d'exécution de \textbf{2 minutes et 53,528 secondes}, soit une réduction du temps de presque moitié : l'utilisation de plusieurs fils prouve donc son efficacité.\\
 
-On effectue alors la même expérience avec ordinateur portable, équipé d'un processeur \emph{Intel i7-3630QM}. Pour la même vidéo, le temps d'encodage est de \textbf{25,536 secondes} avec un seul fil, \textbf{15,883 secondes} avec 2 fils et \textbf{15,797 secondes} avec l'ensemble des 8 fils (un pour chaque cœur disponible). On constate bien un gain en temps lors de l'utilisation de deux fils plutôt qu'un seul, mais il n'y a pas de différence avec l'utilisation de 8 fils plutôt que de deux. On s'intéresse alors aux relevés d'utilisation des cœurs durant l'opération, reportés en annexe \ref{encodage}. Avec un fil, un cœur est sollicité en permanence (100\%) alors que dans les deux autres cas, l'utilisation des cœurs n'est jamais maximum. On suppose donc qu'il s'agit d'un algorithme qui présente des limites à la parallélisation, si on exclut tout autre facteur limitant l'efficacité (comme le temps de lecture et d'écriture des données sur le disque, par exemple). Cette caractéristique peut être spécifique à l'un des formats vidéo mis en jeu dans l'encodage. Les formats vidéo récents sont conçus pour être fortement parallélisables pour que les performances d'encodage (et de décodage) puissent augmenter avec le nombre de cœurs disponibles.\\
+On effectue alors la même expérience avec un ordinateur portable équipé d'un processeur octo-cœur \emph{Intel i7-3630QM}. Pour la même vidéo, le temps d'encodage est de \textbf{25,536 secondes} avec un seul fil, \textbf{15,883 secondes} avec 2 fils et \textbf{15,797 secondes} avec l'ensemble des 8 fils (un pour chaque cœur disponible). On constate bien un gain en temps lors de l'utilisation de deux fils plutôt qu'un seul, mais il n'y a pas de différence avec l'utilisation de 8 fils plutôt que de deux. On s'intéresse alors aux relevés d'utilisation des cœurs durant l'opération, reportés en annexe \ref{encodage}. Avec un fil, un cœur est sollicité en permanence (100\%) alors que dans les deux autres cas, l'utilisation des cœurs n'est jamais maximum. On suppose donc qu'il s'agit d'un algorithme qui présente des limites à la parallélisation, si on exclut tout autre facteur limitant l'efficacité (comme le temps de lecture et d'écriture des données sur le disque, par exemple). Cette caractéristique peut être spécifique à l'un des formats vidéo mis en jeu dans l'encodage. Les formats vidéo récents sont conçus pour être fortement parallélisables pour que les performances d'encodage (et de décodage) puissent augmenter avec le nombre de cœurs disponibles.\\
 
-Si l'utilisation de plusieurs fils d'exécutions (qui seront répartis sur plusieurs cœurs avec un processeur qui le permet) peur présenter des avantages d'efficacité, cela impose l'utilisation de certaines techniques de synchronisation des données au sein du programme réalisé. C'est ce type de contrainte que l'on souhaite mettre en évidence avec la réalisation d'un court programme, écrit en langage C qui utilise deux fonctions, exécutées dans deux fils d'exécutions distincts (gérés avec la librairie standard \bsc{pthread}) :
+Si l'utilisation de plusieurs fils d'exécution (qui seront répartis sur plusieurs cœurs avec un processeur qui le permet) peur présenter des avantages d'efficacité, cela impose l'utilisation de certaines techniques de synchronisation des données au sein du programme réalisé. C'est ce type de contrainte que l'on souhaite mettre en évidence avec la réalisation d'un court programme, écrit en langage C qui utilise deux fonctions, exécutées dans deux fils d'exécution distincts (gérés avec la librairie standard \bsc{pthread}) :
 \begin{itemize}
 \item[--] La fonction principale $main$, qui initialise les données utilisées par le programme, démarre le second fil d'exécution et calcule une moyenne de $n$ valeurs stockées dans un tableau.
 \item[--] Une fonction $thread\_random$ exécutée dans un second fil, qui tire des nombres aléatoires et les stocke dans un tableau.\\
@@ -230,7 +228,7 @@ Si l'utilisation de plusieurs fils d'exécutions (qui seront répartis sur plusi
 
 Le programme est tout d'abord écrit sans se soucier de synchronisation entre les fils d'exécution. Le code source du programme est reporté en annexe \ref{thread} et certains aspects techniques non-directement liés au propos sont explicités sous la forme de commentaires dans le code.\\
 
-Afin de stocker les données, on définit une structure $struct data$ contenant le nombre $n$ de valeurs tirées et un tableau de 10 entiers. La valeur maximum de $n$ sera donc 10 et sera réinitialisée à 0 tous les 10 tirages. Le fil d'exécution principal (fonction $main$) va donc vérifier que la valeur $n$ n'est pas nulle avant de calculer la moyenne, qui implique une division par $n$. En effet, une division par zéro causerait une erreur d'exécution du programme (dite erreur de segmentation) qui cause l'arrêt complet et irrécupérable de son exécution.\\
+Afin de stocker les données, on définit une structure $struct\ data$ contenant le nombre $n$ de valeurs tirées et un tableau de 10 entiers. La valeur maximum de $n$ sera donc 10 et $n$ sera réinitialisée à 0 tous les 10 tirages. Le fil d'exécution principal (fonction $main$) va donc vérifier que la valeur $n$ n'est pas nulle avant de calculer la moyenne, qui implique une division par $n$. En effet, une division par zéro causerait une erreur d'exécution du programme (dite erreur de segmentation) qui cause l'arrêt complet et irrécupérable de son exécution.\\
 
 On compile alors le programme (avec le compilateur \bsc{gcc}) et on constate qu'après avoir affiché un nombre aléatoire (mais généralement assez faible) de moyennes calculées, l'exécution est interrompue par une erreur d'exécution. La sortie du programme affiche :
 \begin{verbatim}
@@ -246,7 +244,7 @@ Moyenne : -2025419578
 Exception en point flottant
 \end{verbatim}
 
-En effet, cet erreur traduit une division par zéro, effectuée à la ligne 87, même si la valeur $n$ a été vérifiée comme différente de zéro avant cette opération. En effet, comme les deux fils d'exécution sont exécutés en même temps (ou dans le cas d'un processeur à un seul cœur, alternativement, mais le découpage du temps d'exécutions attribué à chaque fil n'est pas prédictible pour le programmeur), il est possible que le second fil d'exécution change la valeur de $n$ entre le test avec zéro et l'opération effectivement effectuée. Par exemple, si on a $n=9$ au moment du test, puis le second fil s'exécute et tire un dixième nombre, réinitialisant $n$ à 0, la division sur le premier fil sera effectuée avec $n = 0$.\\
+En effet, cet erreur traduit une division par zéro, effectuée à la ligne 87, même si la valeur $n$ a été vérifiée comme différente de zéro avant cette opération. En effet, comme les deux fils d'exécution sont exécutés en même temps (ou dans le cas d'un processeur à un seul cœur, alternativement, mais le découpage du temps d'exécutions attribué à chaque fil n'est pas prédictible pour le programmeur), il est possible que le second fil d'exécution change la valeur de $n$ entre le test avec zéro et l'exécution effective de l'opération de division. Par exemple, si on a $n=9$ au moment du test, puis que le second fil s'exécute et tire un dixième nombre, réinitialisant $n$ à 0, la division sur le premier fil sera effectuée avec $n = 0$.\\
 
 Afin de palier à ce problème, on introduit la notion de blocage des fils d'exécution. Il s'agit de fonctions de blocage et de déblocage liés à une variable de type mutex\notetrad{Mutual exclusion}{exclusion mutuelle}, définies dans la librairie \bsc{pthread} et qui fonctionnent comme suit\notecite{pthread} :
 \begin{itemize}
@@ -255,9 +253,9 @@ Afin de palier à ce problème, on introduit la notion de blocage des fils d'ex
 \item[--] Une appel à la fonction de déblocage $pthread\_mutex\_unlock$ passe la variable mutex à un état débloqué et débloque l'exécution des fils bloqués par ce mutex.\\
 \end{itemize}
 
-Ce mécanisme va donc permettre de bloque l'un des fils d'exécution quand des données communes aux deux fils sont utilisés, ce qui n'empêchera pas les fils d'effectuer certains actions (comme l'affichage de la moyenne calculée) indépendamment du blocage. Ainsi, on modifie le code source du programme en y insérant des appels à $pthread\_mutex\_lock$ avant les blocs de code utilisant les données communes et des appels à $pthread\_mutex\_unlock$ après. Les modifications apportées au code sont reportées en annexe \ref{thread-diff}.\\
+Ce mécanisme va donc permettre de bloquer l'un des fils d'exécution quand des données communes aux deux fils sont utilisées, ce qui n'empêchera pas les fils d'effectuer certains actions (comme l'affichage de la moyenne calculée) indépendamment du blocage. Ainsi, on modifie le code source du programme en y insérant des appels à $pthread\_mutex\_lock$ avant les blocs de code utilisant les données communes et des appels à $pthread\_mutex\_unlock$ après. Les modifications apportées au code sont reportées en annexe \ref{thread-diff}, au format de différences généré par l'outil \bsc{diff}.\\
 
-On constate dès lors que l'exécution du programme se déroule sans encombres et des valeurs sont affichées continuellement, sans erreur :
+On constate dès lors que l'exécution du programme se déroule sans encombre et des valeurs sont affichées continuellement, sans erreur :
 \begin{verbatim}
 Moyenne : -248157731
 Moyenne : -47172384
@@ -268,4 +266,4 @@ Moyenne : 488697234
 Moyenne : 338524137
 \end{verbatim}
 
-L'utilisation des mutex reste néanmoins un sujet sensible dans l'écriture de code source, en particulier à cause de problèmes d'auto-blocage ou d'inter-dépendances. Dans le premier cas, il s'agit de plusieurs appels successifs à $pthread\_mutex\_lock$ dans le même fil d'exécution, ce qui a pour effet de bloquer ce fil sans qu'il ne soit garanti qu'un autre fil puisse le débloquer. En particulier, si les parties du code à protéger des autres fils sont entourées d'appels à $pthread\_mutex\_lock$ et $pthread\_mutex\_unlock$, les fils seront tous bloqués avant de pour débloquer ce premier fil, ce qui aboutit à un programme irrémédiablement bloqué dans sa globalité. Dans le second cas, il s'agira de l'utilisation de plusieurs mutex distincts avec au moins deux fils d'exécution, chacun bloquant un mutex et attendant le déblocage de l'autre mutex (les appels à $pthread\_mutex\_lock$ seront donc dans l'ordre inversé sur les deux fils d'exécution), ce qui aboutit au blocage des deux fils, irrémédiable s'il n'existe pas d'autre fil capable d'appeler $pthread\_mutex\_unlock$ pour débloquer l'ensemble.
+L'utilisation des mutex reste néanmoins un sujet sensible dans l'écriture de code source, en particulier à cause de problèmes d'auto-blocage ou d'inter-dépendances. Dans le premier cas, il s'agit de plusieurs appels successifs à $pthread\_mutex\_lock$ dans le même fil d'exécution, ce qui a pour effet de bloquer ce fil sans qu'il ne soit garanti qu'un autre fil puisse le débloquer. En particulier, si les parties du code à protéger des autres fils sont entourées d'appels à $pthread\_mutex\_lock$ et $pthread\_mutex\_unlock$, les fils seront tous bloqués avant de pouvoir débloquer ce premier fil, ce qui aboutit à un programme irrémédiablement bloqué dans sa globalité. Dans le second cas, il s'agira de l'utilisation de plusieurs mutex distincts avec au moins deux fils d'exécution, chacun bloquant un mutex et attendant le déblocage de l'autre mutex (les appels à $pthread\_mutex\_lock$ seront donc dans l'ordre inversé sur les deux fils d'exécution), ce qui aboutit au blocage des deux fils, irrémédiable s'il n'existe pas d'autre fil capable d'appeler $pthread\_mutex\_unlock$ pour débloquer l'ensemble.
index 7e980db..bc9cc5b 100644 (file)
@@ -19,7 +19,7 @@ note={https://fr.wikipedia.org/wiki/Transistor\_bipolaire}
 @misc{wikipedia-oscillateur-electronique,
 author={Wikipédia},
 title={Oscillateur électronique},
-note={http://fr.wikipedia.org/wiki/Oscillateur_électronique}
+note={http://fr.wikipedia.org/wiki/Oscillateur\_électronique}
 }
 
 @misc{wikipedia-ne555,
@@ -90,6 +90,12 @@ title={Used {IP} {C}ores},
 note={http://linux-sunxi.org/Used\_IP\_cores}
 }
 
+@misc{linux-sunxi-cubieboard-expansion-ports,
+author={Linux-Sunxi},
+title={Cubieboard/{E}xpansionports},
+note={http://linux-sunxi.org/Cubieboard/ExpansionPorts}
+}
+
 @misc{WikipediaAutomateFini,
 author={Wikipédia},
 title={Automate fini},
index 53b4881..af708db 100644 (file)
@@ -20,7 +20,7 @@
 \usepackage{pdfpages}
 
 \newcommand{\derivd}[3][\null]{\frac{{\rm d}^{#1}#2}{{\rm d}#3^{#1}}}
-\newcommand{\R}{\mathrm{R}}
+\newcommand{\R}{\mathbb{R}}
 \newcommand{\e}[1]{\text{e}^{#1}}
 \newcommand{\ohm}{$\mathrm{\Omega}$}
 \newcommand{\microf}{$\mathrm{\mu}$F}