Dans la plupart des systèmes, des adresses de mémoire spéciales sont utilisées pour les E/S. La lecture et l'écriture sur de telles adresses exécutent certaines fonctions au lieu de simplement déplacer des données. Dans les systèmes x86, il existe également des instructions d'entrée/sortie spéciales IN et OUT pour cela.
Le cas le plus simple est appelé E/S parallèles générales (GPIO), où vous pouvez lire ou écrire des données directement depuis/vers des broches électriques externes sur le périphérique. Il y a plusieurs adresses mémoire, appelées registres, où vous pouvez lire les données du port (tension proche de 0 = 0, près de la tension d'alimentation = 1), où vous pouvez écrire des données sur le port et où vous pouvez définir si une broche particulière est entrée (le bit correspondant est généralement 0) ou sortie (le bit est 1). Chaque microcontrôleur a GPIO. Dans votre exemple, le bouton pourrait être connecté à un jeu de broches que le logiciel pourrait détecter. Il le ferait typiquement toutes les 10ms et ne réagirait que s'il avait une valeur stable pour plusieurs lectures, c'est ce qu'on appelle le debouncing. Alors il écrirait un 1 à une sortie, qui via un transistor pour l'amplification pourrait conduire un moteur. Si vous sentez que vous relâchez l'interrupteur, vous pouvez éteindre le moteur en écrivant un 0. Et ainsi de suite, ce programme fonctionnera jusqu'à ce que vous éteigniez l'appareil.
Il y a beaucoup d'autres périphériques d'E/S à d'autres fins avec typiquement des centaines de registres pour les contrôler. Si vous voulez en voir plus, vous pouvez regarder dans la feuille de données de certains microcontrôleurs. Par exemple, voici the data sheet of ATtiny4/5/9/10, un très petit contrôleur de la famille Atmel AVR.
Aujourd'hui, la plupart du firmware est écrit en C, à l'exception des appareils les plus petits et un peu de code spécial pour la gestion des réinitialisations et les interruptions, ce qui est écrit en langage assembleur.