Comment remplacer le pinb 1. Ports du microcontrôleur AVR. Registre de données d'entrée du port A - PINA

Considérons des exemples de paramètres de port dans CodeVision AVR
Par exemple DDRB = 0x02 ; cette entrée signifie que la deuxième branche du port B est configurée comme une sortie, mais d'où vient ce numéro ????

Tout d'abord, traduisons cette entrée sous une forme plus compréhensible pour nous :
le préfixe 0x signifie que le nombre est écrit en notation hexadécimale, afin de comprendre facilement son essence, vous devez le traduire dans le système binaire. La calculatrice Windows d'un ensemble de programmes standard nous aidera, nous la transférons immédiatement en mode programmeur.

Nous le passons en mode système hexadécimal (cochez HEX), et entrons notre numéro 0x02 tout comme 2.

maintenant, nous n'appuyons sur aucun égal, etc. il suffit de passer au système de nombres binaires (cochez Bin)

Reçu le numéro 10. Qu'est-ce que cela signifie? Notre ATmega8 a 8 pattes sur le port B (entouré sur la photo)

donc si vous représentez le nombre 10 comme 0000010, cela signifiera que seule la deuxième jambe est configurée comme sortie, le reste comme entrée.

PORTB.7 PORTB.6 PORTB.5 PORTB.4 PORTB.3 PORTB.2 PORTB.1 PORTB.0
0 0 0 0 0 0 1 0

Il convient de noter ici que si au lieu de
DDRB = 0x02 ;
écrire
DDRB = 0b0000010;
alors cela fonctionnera aussi, c'est-à-dire ce sont des enregistrements équivalents du même numéro, dans différents systèmes de numérotation.

Avec DDRB trié 1 sortie, 0 entrée, et qu'est-ce que cela signifie
PORTB = 0x01 ;
ici le principe est le même, mais :
si une branche est configurée comme sortie et que la valeur PORTB est égale à une sur la même branche, alors la branche après le firmware sera activée par défaut (c'est-à-dire qu'il y aura une tension dessus), si 0, alors la branche sera éteindre. Dans la première leçon, nous pourrions remplacer le PORTB.0 = 1 ; en écrivant PORTB = 0x01 ; et obtenu le même résultat.

En fait, les constructions suivantes sont la bonne solution pour activer/désactiver la jambe du point de vue du langage C :

PORTD | = (1<<3); //включить ножку 3 порта D PORTD |= (1<<2)|(1<<5)|(1<<6); //включить ножки 2, 5, 6 порта D PORTD &= ~(1<<2); //выключить ножку 2 PORTD &= (~((1<<0)|(1<<2)|(1<<4))); //выключить ножки 0, 2, 4

Si une branche est configurée comme entrée et que la valeur PORTB est égale à une sur la même branche, alors une résistance de rappel sera connectée à la branche pour éliminer le bruit. Si vous n'avez pas besoin d'une résistance interne, définissez simplement 0 sur cette jambe.

J'espère que tout est clair, si vous écrivez quelque chose, posez des questions.

Lors de la programmation de microcontrôleurs, vous devez constamment travailler avec des bits. Réglez-les, réinitialisez, vérifiez leur présence dans un registre particulier. Il existe un certain nombre de commandes dans l'assembleur AVR à cet effet. Premièrement, il s'agit d'un groupe d'instructions pour les opérations avec des bits - elles sont conçues pour définir ou effacer des bits dans divers registres du microcontrôleur, et deuxièmement, un groupe de commandes de transfert de contrôle - elles sont conçues pour organiser des branches de programme. Naturellement, il n'y a pas de telles commandes dans le langage C, donc les programmeurs débutants se demandent souvent comment travailler avec des bits en C. Nous allons analyser ce sujet maintenant.

Il y a 6 opérateurs en C pour manipuler les bits. Ils peuvent être appliqués à tout type de variable entier signé ou non signé.

<< - сдвиг влево
>> - décaler à droite
~ - inversion au niveau du bit
| - OU au niveau du bit
& - ET au niveau du bit
^ - OU exclusif au niveau du bit

_______________ décaler à gauche<< _______________

Déplace un nombre de n places vers la gauche. Dans ce cas, les n bits les plus significatifs disparaissent, et les n bits les moins significatifs sont remplis de zéros.


caractère non signé tmp = 3 ; // 0b00000011
tmp = tmp<< 1;
// maintenant la variable tmp contient le numéro 6 ou 0b00000110

Tmp = tmp<< 3;
// maintenant la variable tmp contient le nombre 48 ou 0b00110000

Les expressions dans lesquelles une opération est effectuée sur une variable, puis le résultat de l'opération est affecté à la même variable, peuvent être écrites plus courtes, à l'aide d'opérateurs composés.

Tmp = 7 ; // 0b00000111
tmp<<= 2; // version abrégée
// maintenant la variable tmp contient le nombre 28 ou 0b00011100

L'opération de décalage vers la gauche de n bits équivaut à multiplier la variable par 2 n.

_______________ décaler à droite >> _______________

Décale un nombre de n chiffres vers la droite. Les n bits les moins significatifs sont perdus dans ce cas. Le remplissage des n bits de poids fort dépend du type de la variable et de sa valeur. Les n bits les plus significatifs sont remplis de zéros dans deux cas - si la variable est de type non signé ou si la variable est signée et sa valeur actuelle est positive. Lorsque la variable est signée et que sa valeur est négative, les bits les plus significatifs sont remplis de uns.

Un exemple pour une variable non signée

caractère non signé tmp = 255 ; // 0b11111111
tmp = tmp >> 1 ;
// maintenant la variable tmp contient le nombre 127 ou 0b01111111

Tmp >> = 3 ; // version abrégée
// maintenant la variable tmp contient le nombre 15 ou 0b00001111

Exemple pour une variable signée

entier tmp = 3400 ; // 0b0000110101001000
tmp >> = 2 ;
// maintenant la variable contient le nombre 850 ou 0b0000001101010010

Tmp = -1200 ; // 0b1111101101010000
tmp >> = 2 ;
// maintenant le numéro tmp est -300 ou 0b1111111011010100
// vous voyez - les deux chiffres les plus significatifs sont remplis de uns

Un décalage vers la droite de n bits équivaut à diviser par 2 n. Cependant, il y a quelques nuances. Si les bits de poids faible perdus en contenaient, le résultat d'une telle « division » s'avère approximatif.

par exemple 9/4 = 2,5 un 9 >> 2 (1001 >> 2) égale 2
11/4 = 2,75 et 11 >> 2 (1011 >> 2) égale 2
28/4 = 7 et 28 >> ​​​​2 (11100 >> 2) égale 7

Dans le second cas, l'erreur est plus importante, car les deux bits de poids faible ne font qu'un. Dans le troisième cas, il n'y a pas d'erreur, car les bits perdus sont nuls.

_______________ inversion au niveau du bit ~ _______________

Inverse le nombre bit par bit. Les chiffres dans lesquels il y avait des zéros sont remplis de uns. Les chiffres dans lesquels il y avait des uns sont remplis de zéros. L'opérateur d'inversion au niveau du bit est un opérateur unaire, c'est-à-dire qu'il est utilisé avec un opérande.

caractère non signé tmp = 94 ; // 0b01011110
tmp = ~ tmp;
// maintenant la variable tmp contient le nombre 161 ou 0b10100001

Tmp = ~ tmp;
// maintenant dans tmp à nouveau le numéro 94 ou 0b01011110

_______________ OU au niveau du bit | ______________

Opérateur | effectue une opération OU logique entre les bits correspondants des deux opérandes. Le résultat d'une opération OU logique entre deux bits ne sera 0 que si les deux bits sont égaux à 0. Dans tous les autres cas, le résultat sera 1. Ceci est illustré dans la table de vérité.

Opérateur | généralement utilisé pour définir les bits donnés d'une variable à un.

Tmp = 155
tmp = tmp | 4 ; // met le deuxième bit de la variable tmp à un

155 0b100110 11
4 0b00000 1 00
159 0b100111 11

Il est assez difficile d'utiliser des nombres décimaux pour définir les bits. Il est beaucoup plus pratique de le faire en utilisant l'opération de décalage à gauche.<<.


tmp = tmp | (un<<4); // met le quatrième bit de la variable tmp à un

Nous lisons de droite à gauche - décalez un quatre chiffres vers la gauche, OU entre le nombre reçu et la valeur de la variable tmp, affectez le résultat à la variable tmp.


Vous pouvez définir plusieurs bits sur un comme ceci

Tmp = tmp | (un<<7)|(1<<5)|(1<<0);
// définit les septième, cinquième et zéro bits de la variable tmp à un

L'opérateur d'affectation composé | = peut être utilisé pour rendre la notation plus compacte.

Tmp | = (1<<4);
tmp | = (1<<7)|(1<<5)|(1<<0);

_______________ ET au niveau du bit & _______________

L'opérateur & effectue une opération ET logique entre les bits correspondants des deux opérandes. Le résultat d'une opération ET logique entre deux bits ne sera 1 que si les deux bits sont égaux à 1. Dans tous les autres cas, le résultat sera 0. Ceci est illustré dans la table de vérité.

L'opérateur & est couramment utilisé pour remettre à zéro un ou plusieurs bits.

Tmp = 155 ;
tmp = tmp & 247; // zéro le troisième bit de la variable tmp

155 0b10011 011
&
247 0b1111 0 111
147 0b10010 011

Vous voyez, le troisième bit est devenu 0, et le reste des bits n'a pas changé.

La mise à zéro des bits à l'aide de chiffres décimaux n'est pas pratique. Mais vous pouvez vous faciliter la vie en utilisant des opérateurs<< и ~

Tmp = 155 ;
tmp = tmp & (~ (1<<3)); // zéro le troisième bit

1<<3 0b00001 000
~(1<<3) 0b11110 111
tmp & (~ (1<<3)) 0b10011 011 & 0b11110 111
résultat 0b10010 011

Nous lisons de droite à gauche - décalons un de trois chiffres vers la gauche, inversons le nombre résultant, effectuons l'opération & entre la valeur de la variable tmp et le nombre inversé, affectons le résultat à la variable tmp.


Vous pouvez mettre à zéro quelques bits comme ceci

tmp = tmp & (~ ((1<<3)|(1<<5)|(1<<6))); // mettre à zéro les troisième, cinquième et sixième bits



En utilisant l'opérateur d'affectation composé & =, vous pouvez écrire l'expression de manière plus compacte

Tmp & = (~ ((1<<3)|(1<<5)|(1<<6)));

Comment vérifier si un bit est défini dans une variable ?
Vous devez remettre à zéro tous les bits, à l'exception du contrôle, puis comparer la valeur résultante avec zéro

si((tmp & (1<<2)) != 0){
// le bloc ne sera exécuté que s'il est défini

}

si((tmp & (1<<2)) == 0){
// le bloc ne sera exécuté que s'il n'est pas défini
// deuxième bit de la variable tmp

}

_______________ OU exclusif au niveau du bit ^ _______________


L'opérateur ^ effectue une opération OU exclusif logique entre les bits correspondants des deux opérandes. Le OU exclusif logique donnera 0 si les bits sont égaux. Dans tous les autres cas, le résultat sera 1. Ceci est illustré dans la table de vérité.

L'opérateur ^ n'est pas utilisé aussi souvent que les autres opérateurs au niveau du bit, mais il y a du travail à faire pour cela. Par exemple, il peut être utilisé pour inverser un ou plusieurs bits d'une variable.


tmp = 155 ;
tmp = tmp ^ 8; // inverse le quatrième bit avec tmp

155 0b10011 011
^
8 0b0000 1 000
147 0b10010 011

Le quatrième bit a changé sa signification en l'opposé, et le reste des bits est resté inchangé.

Tmp = tmp ^ 8 ; // inverser à nouveau le quatrième bit avec tmp

147 0b10010 011
^
8 0b000 0 1 000
155 0b10011 011

Vous voyez, le quatrième bit a encore changé de sens pour le contraire.

Il est beaucoup plus pratique d'écrire une expression de cette façon.

Tmp = tmp ^ (1<<3); // inverser le troisième bit avec tmp

Et donc c'est pratique et compact

Tmp ^ = (1<<4); // inverse le quatrième bit

Peut inverser plusieurs bits en même temps

Tmp ^ = ((1<<4)|(1<<2)|(1<<1)); // inverse 4,2 et 1 bits

L'OR exclusif au niveau du bit a une autre propriété intéressante... Il peut être utilisé pour échanger les valeurs de deux variables. Cela nécessite généralement une troisième variable.


tmp = var1;
var1 = var2;
var2 = tmp;

Mais en utilisant l'opérateur ^, vous pouvez réorganiser les valeurs comme ceci :

var1 ^ = var 2;
var 2 ^ = var 1;
var 1 ^ = var 2;

De la magie pure, même si, pour être honnête, je n'ai jamais utilisé une telle technique.

________________ Directive #définir__________________


Maintenant, nous savons comment mettre à zéro et inverser les bits, nous savons comment vérifier si un bit est défini ou non. Les expressions ci-dessus sont assez lourdes, mais avec l'aide de la directive de préprocesseur #define, elles peuvent être rendues plus agréables.

La directive #define est utilisée pour attribuer des noms symboliques aux constantes et aux macros. L'utilisation de noms symboliques rend le programme plus modifiable et portable.

Par exemple, vous utilisez une constante dans le texte du programme et vous devez soudainement modifier sa valeur. S'il ne se produit qu'à trois endroits, vous pouvez le corriger manuellement, mais que se passe-t-il s'il se produit sur cinquante lignes ? Non seulement le correctif prendra beaucoup de temps, mais il est également facile de se tromper dans ce cas. C'est là que la directive #define est utile. Au début du programme, le nom symbolique de la constante est spécifié, qui est utilisé tout au long du programme. Si nous devons modifier cette valeur, cela se fait à un seul endroit. Et avant la compilation, le préprocesseur substituera sa valeur dans toutes les expressions au lieu du nom de la constante.

La programmation du microcontrôleur est inextricablement liée à son matériel et le plus souvent au harnais externe. Prenez, par exemple, les boutons - en les interrogeant dans notre programme, nous nous référons aux sorties réelles du microcontrôleur. Et si nous avions soudain besoin d'utiliser le programme d'interrogation des boutons dans un schéma différent, où les boutons sont connectés à d'autres broches ? Nous devrons corriger le programme. Encore une fois, en spécifiant un nom symbolique pour les broches correspondantes à l'aide de la directive #define, modifier le programme sera aussi simple que de décortiquer des poires


Exemple:

#include "iom8535.h"

// port auquel les boutons sont connectés
#define PORT_BUTTON PORTA
#define PIN_BUTTON PINA
#define DDRX_BUTTON DDRA

// broches auxquelles les boutons sont connectés
#define BAS 3
#define ANNULER 4
#définir jusqu'à 5
#définir ENTRER 6

entier principale ()
{
// configurer le port d'entrée,
// et activer les résistances de rappel

DDRX_BUTTON = 0 ;
PORT_BUTTON = 0xff ;

Lorsque vous spécifiez un nom symbolique, vous pouvez également utiliser les expressions

#define MASK_BUTTONS ((1<

exemple d'utilisation :
tmp = PORTB & MASK_BUTTONS;

Lorsque vous utilisez #define, n'épargnez aucune parenthèse pour définir clairement la séquence dans laquelle les expressions sont évaluées !

Certaines expressions peuvent être déguisées en "fonctions".

#définir ADC_OFF () ADCSRA = 0

exemple d'utilisation :
ADC_OFF ();

Vous pouvez utiliser des définitions multilignes en utilisant le caractère \ à la fin de chaque ligne.

#define INIT_Timer () TIMSK = (1<TCCR0 = (1<TCNT0 = 0; \
OCR0 = 0x7d

exemple d'utilisation :
INIT_Timer ();

Eh bien, l'utilisation la plus puissante de la directive #define consiste à définir des macros (ou simplement des macros). Voici comment vous pouvez utiliser #define pour définir des macros pour les opérations sur les bits décrites précédemment.

#define SetBit (reg, bit) reg | = (1<#define ClearBit (reg, bit) reg & = (~ (1<#define InvBit (reg, bit) reg ^ = (1<#define BitIsSet (reg, bit) ((reg & (1<#define BitIsClear (reg, bit) ((reg & (1<

exemple d'utilisation :

SetBit (PORTB, 0); // définit le bit zéro du port B
InvBit (tmp, 6); // inverse le sixième bit de la variable tmp


si(BitIsClear (PIND, 0)) ( // si le bit zéro dans le registre PIND est effacé
… .. // exécute le bloc
}

Avant la compilation, le préprocesseur remplacera ces lignes par les expressions précédemment déclarées, en y substituant les arguments appropriés.

Les macros sont très puissantes, mais elles doivent être utilisées avec prudence. Voici les râteaux les plus courants qui sont décrits dans tous les manuels de programmation.

Définissons une macro qui calcule le carré d'un nombre :

#définir le CARRÉ (x) x * x

expression
tmp = CARRÉ (mon_var);
donnera le bon résultat.

Et que se passe-t-il si l'expression my_var + 1 est utilisée comme argument de la définition de la macro

tmp = CARRÉ (mon_var +1);

Le préprocesseur remplacera cette ligne par

tmp = ma_var + 1 * ma_var +1 ;

et ce n'est pas du tout le résultat que l'on attend.

Pour éviter de telles erreurs, ne lésinez pas sur les parenthèses lors de la déclaration des macros !

Si vous déclarez une macro comme celle-ci

#define CARRÉ (x) ((x) * (x))

expression
tmp = CARRÉ (mon_var +1);
donnera le résultat correct, car le préprocesseur remplacera cette ligne par
tmp = ((my_var + 1) * (my_var +1));

nous les écrivons dans le dossier du projet, et au début du fichier main.c nous écrivons #include "bits_macros.h"

Bit

Lire écrire

Valeur initiale

· Bit 7 - Activer toutes les interruptions. Pour activer les interruptions, ce bit doit être mis à 1. L'activation d'une interruption spécifique est contrôlée par les registres de masque d'interruption EIMSK et TIMSK. Si ce bit est effacé (= 0), aucune des interruptions n'est traitée. Le bit est effacé par le matériel après qu'une interruption se produit et est défini pour activer l'interruption plus tard avec l'instruction RETI.
· Bit 6 - Enregistrer le bit de copie. Les instructions de copie de bits BLD et BST utilisent ce bit comme source et destination pour les opérations sur les bits. L'instruction BST copie le bit du registre général dans le bit T et l'instruction BLD copie le bit T dans le bit du registre général.
· Bit 5 - Drapeau de demi-portée. Il indique le transfert entre tétrades lors de l'exécution d'un certain nombre d'opérations arithmétiques.
· Bit 4 - Bit de signe. Le bit S a la valeur du résultat de l'opération OU exclusif (N (+) V) sur les drapeaux de dépassement négatif (N) et complément à deux (V).

· Bit 3 - Complément à deux de l'indicateur de débordement. Il prend en charge l'arithmétique du complément à deux.
· Bit 2 - Indicateur de valeur négative. Ce drapeau indique un résultat négatif d'un certain nombre d'opérations arithmétiques et logiques.
· Bit 1 - Drapeau de valeur zéro. Cet indicateur indique un résultat nul pour un certain nombre d'opérations arithmétiques et logiques.
· Bit 0 - Porter le drapeau. Ce fanion indique le report pour les opérations arithmétiques et logiques.

Le microcontrôleur AT90S8535 possède 4 ports d'E/S parallèles A, B, C et D.
Le port A est un port bidirectionnel 8 bits. L'interaction avec le port A s'effectue à travers trois registres dans l'espace d'entrée/sortie de la mémoire de données : registre de données - PORTA, $ 1B ($ 3B), registre de direction de données - DDRA, $ 1A ($ 3A), registre de données d'entrée - PINA, 19$ (39$). Le registre PINA est en lecture seule et les registres PORTA et DDRA sont en lecture-écriture. Le registre PINA n'est pas un registre au sens plein du terme. Son accès fournit une lecture de l'état physique de chaque broche de port. Le port A accepte également les signaux analogiques A/D.

Registre de données du port A -PORTA

Bit

Lire écrire

Valeur initiale

Registre de direction des données du port A -DDRA

Bit

Lire écrire

Valeur initiale

Registre de données d'entrée du port A -PINA

Bit

Lire écrire

Valeur initiale

Le port B est un port d'E/S bidirectionnel 8 bits. En plus du port A, l'interaction avec le port B s'effectue à travers trois registres dans l'espace d'entrée/sortie de la mémoire de données : registre de données - PORTB, 18 $ (38 $), registre de direction de données - DDRB, 17 $ (37 $) et registre de données d'entrée - PINB, 16 $ (36 $). Le registre PINB est en lecture seule. Le registre PINB n'est pas un registre au sens plein du terme. Son accès fournit une lecture de l'état physique de chaque broche de port. Les brochages du port B peuvent exécuter des fonctions alternatives, comme indiqué dans le tableau. 2.1.

Tableau 2.1. Alternatives de brochage du port B

Broche de port

Fonction alternative

T0 - entrée d'horloge pour temporisateur / compteur 0

T1 - Timer / Compteur 1 entrée horloge

AIN0 - borne positive du comparateur

AIN1 - broche négative du comparateur

- entrée de sélection SPI esclave

MOSI - régler la sortie maître / l'entrée SPI esclave

MISO - définir l'entrée maître / la sortie SPI esclave

SCK - Signal d'horloge SPI

Lors de l'utilisation de broches pour des fonctions alternatives, les registres PORTB, DDRB doivent être définis de manière appropriée.

Registre des données portuairesBPORTB

Bit

Lire écrire

Valeur initiale

Registre de direction des données du port B -DDRB

Bit

Lire écrire

Valeur initiale

Registre des données d'entrée du port B -PINB

Bit

Lire écrire

Valeur initiale

Le port C est un port d'E/S bidirectionnel 8 bits. Comme pour les ports A et B, l'interaction avec le port C s'effectue à travers trois registres dans l'espace d'entrée/sortie de la mémoire de données : registre de données - PORTC, 15 $ (35 $), registre de direction de données - DDRC, 14 $ (34 $) et registre de données d'entrée - PINC, 13 $ (33 $). Le registre PINC est en lecture seule et les registres PORTC et DDRC sont en lecture-écriture. Le registre PINC n'est pas un registre au sens plein du terme. Son accès fournit une lecture de l'état physique de chaque broche de port.
Le port C n'a que deux broches qui peuvent remplir des fonctions alternatives : les broches PC6 et PC7 remplissent les fonctions TOSC1 et TOSC2 de Timer / Counter 2.

Registre des données portuairesCPORTC

Bit

Lire écrire

Valeur initiale

Registre de direction des données du port C -RDRC

Bit

Lire écrire

Valeur initiale

Registre des données d'entrée du port C -PINC

Bit

Lire écrire

Valeur initiale

Le port D est un port d'E/S bidirectionnel 8 bits. Outre les ports A, B et C, l'interaction avec le port D s'effectue au travers de trois registres dans l'espace d'entrée/sortie de la mémoire de données : registre de données - PORTD, 12 $ (32 $), registre de direction de données - DDRD, 11 $ ( 31 $) et registre de données d'entrée - PIND, 10 $ (30 $). Le registre PIND est lisible, et les registres PORTD et DDRD sont en lecture/écriture. Le registre PIND n'est pas un registre au sens plein du terme. Son accès fournit une lecture de l'état physique de chaque broche de port.
Les broches du port D peuvent exécuter des fonctions alternatives, comme indiqué dans le tableau. 2.2.

Tableau 2.2. Fonctions alternatives de la broche du port D

Broche de port

Fonction alternative

RxD - entrée récepteur UART

TxD - Sortie émetteur UART

INT0 - entrée d'interruption externe 0

INT1 - entrée d'interruption externe 1

OC1B - broche de comparaison de la sortie B du temporisateur/compteur 1

OC1A - comparaison de sortie de la sortie A du temporisateur/compteur 1

ICP - Entrée 1 du déclencheur de capture de minuterie/compteur

OC2 - comparaison de sortie de la sortie timer / compteur 2

Lors de l'utilisation de broches pour des fonctions alternatives, les registres PORTD, DDRD doivent être définis en conséquence.

Registre des données portuairesPORTD

Bit

Lire écrire

Valeur initiale

Registre de direction des données de portDDRD

Bit

Lire écrire

Valeur initiale

Registre de données d'entrée de portPIN

Bit

Lire écrire

Valeur initiale

Le travail considéré étant le premier, afin d'acquérir les compétences nécessaires au travail avec le complexe du laboratoire, tous les étudiants effectuent d'abord le même travail. Depuis leurs postes de travail, ils introduisent dans le PC le même problème de soustraction du nombre 3 au nombre 5, donné au paragraphe 1.5.3.1. Après avoir compilé le programme, il est écrit dans le microcontrôleur du lieu de travail et son travail est démontré à l'enseignant.
Après une telle connaissance du complexe, l'étudiant procède à la mise en œuvre d'une tâche individuelle. S'il a le temps, l'enseignant peut compliquer la tâche individuelle.

Donc, vous lisez ceci maintenant et vous pensez que la mémoire, les registres, la pile, etc. sont bons. Mais vous ne pouvez pas le sentir, vous ne pouvez pas le voir. Sauf dans le simulateur, mais je peux le faire sur dolphi avec la même condition. Où est la viande !!!

Dans d'autres cours là-bas, presque dès les premières lignes, ils font quelque chose d'important - ils clignotent une diode et disent que c'est notre Hello World. Et ici? Où ???

Oui, oui, oui, je te comprends. De plus, vous avez probablement déjà couru vers vos concurrents et leur avez fait clignoter une diode ;)))) Rien de pardonnable.

Je ne voulais tout simplement pas m'arrêter au même clignotement de didodiks, mais pour progresser, vous avez besoin d'une compréhension claire des fondements et des principes - une base théorique puissante. Mais maintenant, c'était au tour de la pratique.

Nous avons déjà couvert les ports, vous avez déjà un modèle de programme, alors commençons tout de suite.

Outils
Travailler avec des ports signifie généralement travailler avec des bits. Il s'agit de régler un peu, d'effacer un peu, d'inverser un peu. Oui, bien sûr, il y a des commandes pratiques en assembleur

cbi / sbi, mais ils fonctionnent exclusivement dans une petite plage d'adresses (de 0 à 1F, écrivons donc d'abord des macros universelles afin de pouvoir les utiliser à l'avenir et ne pas nous soucier de l'espace d'adressage.

Les macros seront appelées :

  • octet SETB, bit, temp
  • octet CLRB, bit, temp
  • Octet INVB, bit, temp, temp2

De plus, lorsque vous travaillez avec les bits de l'adresse PBB inférieure (0-1F), la valeur du paramètre TEMP peut être omise - elle ne sera de toute façon pas remplacée. À l'exception des commandes d'inversion, n'importe qui aura besoin de registres intermédiaires.

Il est également utile d'avoir un groupe de macros sans casse. Plus précisément, ils utiliseront des registres, mais les enregistreront d'abord sur la pile. Ils peuvent être poussés sans réfléchir comme des commandes ordinaires. Mais ils prendront plus de temps à s'exécuter et nécessiteront de la RAM.

  • SETBM octet, bit
  • CLRBM octet, bit
  • octet INVBM, bit

Voici leur code source. Comme vous pouvez le voir, les conditions du langage macro sont activement utilisées, ce qui permet de créer des macros universelles. Le compilateur déterminera avec quelle version le coller :)

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 < 0x20 SBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ORI R17,1<<@1 OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ORI R17,1<<@1 STS @0,R17 POP R17 .endif .endif .ENDM ;SET BIT with REG .MACRO SETB .if @0 < 0x20 ; Low IO SBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ORI @2,1<<@1 OUT @0,@2 .else ; Memory LDS @2,@0 ORI @2,1<<@1 STS @0,@2 .endif .endif .ENDM ;............................................................. ;Clear BIT with REG .MACRO CLRB .if @0 < 0x20 ; Low IO CBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ANDI @2,~(1<<@1) OUT @0,@2 .else ; Memory LDS @2,@0 ANDI @2,~(1<<@1) STS @0,@2 .endif .endif .ENDM ;Clear BIT with STACK .MACRO CLRBM .if @0 < 0x20 CBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ANDI R17,~(1<<@1) OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ANDI R17,~(1<<@1) STS @0,R17 POP R17 .endif .endif .ENDM ;............................................................. .MACRO INVB .if @0 < 0x40 IN @2,@0 LDI @3,1<<@1 EOR @3,@2 OUT @0,@3 .else LDS @2,@0 LDI @3,1<<@1 EOR @2,@3 STS @0,@2 .endif .ENDM .MACRO INVBM .if @0 < 0x40 PUSH R16 PUSH R17 IN R16,@0 LDI R17,1<<@1 EOR R17,R16 OUT @0,R17 POP R17 POP R16 .else PUSH R16 PUSH R17 LDS R16,@0 LDI R17,1<<@1 EOR R17,R16 STS @0,R17 POP R17 POP R16 .endif .ENDM ;= End macro.inc ========================================

; = Démarrer macro.inc ========================================== SET BIT avec pile .MACRO SETBM .if @ 0< 0x20 SBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ORI R17,1<<@1 OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ORI R17,1<<@1 STS @0,R17 POP R17 .endif .endif .ENDM ;SET BIT with REG .MACRO SETB .if @0 < 0x20 ; Low IO SBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ORI @2,1<<@1 OUT @0,@2 .else ; Memory LDS @2,@0 ORI @2,1<<@1 STS @0,@2 .endif .endif .ENDM ;............................................................. ;Clear BIT with REG .MACRO CLRB .if @0 < 0x20 ; Low IO CBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ANDI @2,~(1<<@1) OUT @0,@2 .else ; Memory LDS @2,@0 ANDI @2,~(1<<@1) STS @0,@2 .endif .endif .ENDM ;Clear BIT with STACK .MACRO CLRBM .if @0 < 0x20 CBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ANDI R17,~(1<<@1) OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ANDI R17,~(1<<@1) STS @0,R17 POP R17 .endif .endif .ENDM ;............................................................. .MACRO INVB .if @0 < 0x40 IN @2,@0 LDI @3,1<<@1 EOR @3,@2 OUT @0,@3 .else LDS @2,@0 LDI @3,1<<@1 EOR @2,@3 STS @0,@2 .endif .ENDM .MACRO INVBM .if @0 < 0x40 PUSH R16 PUSH R17 IN R16,@0 LDI R17,1<<@1 EOR R17,R16 OUT @0,R17 POP R17 POP R16 .else PUSH R16 PUSH R17 LDS R16,@0 LDI R17,1<<@1 EOR R17,R16 STS @0,R17 POP R17 POP R16 .endif .ENDM ;= End macro.inc ========================================

Au fil du temps, lorsque vous écrivez en langage assembleur, il y a beaucoup de telles macros. Ils sont placés dans un fichier séparé et simplement connectés à n'importe lequel de vos projets, et l'écriture de code devient facile et agréable.

Mais revenons au code,
Faisons clignoter la lumière LED, alors enfin?

Bien sûr, pas de problème. Les LED sont déjà montées sur la carte de démonstration, pourquoi ne pas les utiliser ? Ils s'accrochent aux broches du port PD4, PD5, PD7. Il suffit de mettre des pulls.

; Initialisation du matériel interne ====================================== SETB DDRD, 4, R16; DDRD.4 = 1 SETB DDRD, 5, R16 ; DDRD.5 = 1 SETB DDRD, 7, R16 ; DDRD.7 = 1 ; Fin de l'initialisation du matériel interne ====================================

Il reste à allumer nos diodes. Ils sont allumés en écrivant des bits dans le registre PORT. Nous le faisons déjà dans la section principale du programme.

; Principal ================================================== ======= Principal : SETB PORTD, 4, R16 ; Allumé LED1 SETB PORTD, 7, R16; Allumez LED3 JMP Principal; Fin principal =============================================== = ====

Nous compilons, vous pouvez l'exécuter dans le traceur, vous verrez immédiatement comment les bits changent. Nous flashons ... et après avoir cliqué sur RESET et déchargé le bootloader (si bien sûr vous l'avez fait), vous verrez l'image suivante :


Et pour la version II


Dans! Le courant est ennuyeux. Faisons-les cligner des yeux.

Remplaçons simplement nos macros.

; Principal ================================================== ======= Principal : SETB PORTD, 4, R16 ; Allumé LED1 INVB PORTD, 7, R16, R17 ; LED3 JMP inversé principal ; Fin principal =============================================== = ====

Ils l'ont allumé, flashé...

Mais les figues - les deux sont allumées, mais l'une est un peu plus faible. Il clignote en fait, mais très très rapidement. Si vous insérez un oscilloscope dans la broche PD7, vous verrez que le niveau y change avec une fréquence vertigineuse :


Que faire? Ralentissez évidemment. Comment? Le moyen le plus simple, qui est pratiqué dans la grande majorité des tutoriels et des démarrages rapides, est un délai terne. Celles. obtenir un code comme :

; Principal ================================================== ======= Principal : SETB PORTD, 4, R16 ; Allumé LED1 INVB PORTD, 7, R16, R17 ; Retard LED3 RCALL inversé JMP Principal ; Fin principal =============================================== = ====; Procédure ================================================= == .equ LowByte = 255 .equ MedByte = 255 .equ HighByte = 255 Délai : LDI R16, LowByte ; Nous chargeons trois octets LDI R17, MedByte ; Notre extrait LDI R18, boucle HighByte : SUBI R16,1 ; Soustraire 1 SBCI R17.0 ; Soustraire uniquement C SBCI R18.0 ; Soustraire de la boucle BRCC uniquement ; S'il n'y a pas de report - branche RET ; Fin de la procédure ===============================================

Nous l'avons demandé, lancé ... Oh oui, maintenant le clignotement sera perceptible.

Avec les paramètres 255.255.255, le temps d'exposition à 8MHz sera d'environ 2,1 secondes. Vous pouvez augmenter le délai de bits de quelques octets supplémentaires. Ensuite, vous pouvez charger au moins une heure.

Mais cette méthode est imparfaite, maintenant je vais vous montrer pourquoi.

Ajoutons un bouton. Laissez LED3 clignoter en sens inverse. Et nous ferons en sorte que lorsque le bouton est enfoncé, la LED1 soit allumée pour nous, et lorsqu'elle est relâchée, la LED2 soit allumée.

Prenez l'horloge A comme bouton et connectez-la au port PD6.


Il en est de même pour la version II. Prenez simplement le bouton du groupe de boutons et connectez la broche PD6 du contrôleur à la broche COL1 sur le panneau de boutons.

Faites uniquement attention aux cavaliers qui se trouvent sur les cavaliers du champ de bouton. Les bleus sont comme ça. Et aussi un cavalier noir discret, qui est indiqué par la flèche droite. Il relie la colonne de boutons la plus à gauche à la terre. Bondit sur les broches GND et ROW1. Tout est signé au tableau.

La vérification du bouton est effectuée par la commande SBIC, mais il doit d'abord être initialisé. Faites DDR = 0, PORT = 1 - entrée pullup.

Ajoutez ces lignes à la section Internal Hardware Init :

; Principal ================================================== ======= Principal : SBIS PIND, 6 ; Si le bouton est enfoncé - transition RJMP BT_Push SETB PORTD, 5 ; Allumez LED2 CLRB PORTD, 4 ; Eteindre la LED1 Ensuite : INVB PORTD, 7, R16, R17 ; Retard LED3 RCALL inversé JMP Main BT_Push : SETB PORTD, 4 ; Allumez LED1 CLRB PORTD, 5 ; Eteindre LED2 RJMP Suivant ; Fin principal =============================================== = ====

Eh bien cho, ça marche. Le bouton est enfoncé - les diodes changent. Le troisième fait un clin d'œil gai. Mais il y a un zapadlo :

Le programme ralentit ! J'ai appuyé sur le bouton, mais l'image n'a pas changé, je dois attendre, maintenez-le... Pourquoi ? Et c'est à cause de notre redlocking.

Vous souvenez-vous que je vous ai dit que les retards stupides dans lesquels MK ne fait rien sont un mal infernal? Ici! Vous en êtes maintenant convaincu vous-même. Eh bien, le mal doit être combattu. Comment? Eh bien, je l'ai aussi dit - faire une boucle continue avec des drapeaux. Ajoutez au moins un travail utile à notre retard.

Vielle à roue
Maintenant, je vais vous montrer comment vous pouvez faire un orgue numérique. Vous vous souvenez comment ça marche ?

Il y a un tambour avec des clous saillants et des ressorts dans des tons différents. Les clous tournent, les ressorts tirent - ils tintent. Il s'avère raskolbasny Mouzon. Et si nous dépliions notre vielle à roue en une bande. Les ongles ne ressemblent-ils pas à des 1 ? ;))))

La bande sera un compteur qui comptera de zéro, disons, à FF.FF.FF.FF, puis de nouveau à zéro ou à n'importe quelle valeur, autant que nécessaire et nous le ferons. Et nos sous-programmes joueront le rôle de ressorts, s'accrochant aux bons endroits - comparant leur constante à la valeur actuelle du compteur.

Cela correspondait-il ? Nous faisons "DRYN!"

Il ne reste plus qu'à s'inscrire sur le cycle temporel de notre orgue où et quoi doit être lancé. Et ici, il y a une fonctionnalité très pratique - pour construire des séquences cycliques, nous avons juste besoin d'attraper un bit.

Disons que l'organe compte de 0 à 1000, et nous devons faire clignoter la diode 10 fois. Il n'est pas nécessaire de coller 10 gestionnaires avec des valeurs différentes. Un suffit, mais pour qu'il attrape la valeur ** 10. Tout le reste n'est pas important pour nous. Et cela fonctionnera sur 0010, 0110, 0210, 0310, 0410, 0510, 0610, 0710, 0810, 0910. Des intervalles plus fréquents sont également divisés selon nos besoins, il suffit d'entrer dans une autre catégorie. Ici, vous devez juste vous rappeler de couper les bits de poids fort afin qu'ils ne gênent pas.

Commençons. Tout d'abord, créons notre compteur dans le segment de données :

LDS R16, CCNT LDS R17, CCNT + 1 LDS R18, CCNT + 2 LDS R19, CCNT + 3

Ça y est, maintenant dans R16 l'octet le moins significatif de notre compteur, et dans R19 le plus ancien.

Les registres peuvent être poussés sur la pile à l'avance, mais je préfère vous donner un autre conseil - lorsque vous écrivez un programme, réfléchissez à un algorithme afin d'utiliser les registres comme des données TEMP solides dont la pertinence n'est pertinente qu'ici et maintenant. Et ce qui leur arrivera lors de la prochaine procédure n'est plus important - tout ce qui doit être enregistré dans la RAM.

Vous pouvez le faire comme ceci :

LDI R20.1 ; Nous avons besoin d'un CLR R15 ; Et aussi zéro. AJOUTER R16, R20 ; Ajoutez 1 si le registre est 255, alors ce sera C ADC R17, R15 ; Ajouter 0 + C ADC R18, R15 ; Ajouter 0 + C ADC R19, R15 ; Ajouter 0 + C

J'ai dû passer deux autres registres pour stocker les constantes de notre addition. Tout cela du fait que l'AVR n'est pas en mesure d'ajouter des registres avec un numéro immédiat. Mais il sait soustraire.

J'ai déjà montré que R - (- 1) = R + 1, mais personne ne nous interdit d'arranger ici la même technique - faire des additions par soustractions.

1 2 3 4 SUBI R16, (- 1) SBCI R17, (- 1) SBCI R18, (- 1) SBCI R19, (- 1)

SUBI R16, (- 1) SBCI R17, (- 1) SBCI R18, (- 1) SBCI R19, (- 1)

Nous donnera l'incrément du nombre à quatre octets R19 : R18 : R17 : R16

Maintenant, je vais vous montrer un peu de magie entière.
Pourquoi cela fonctionnera-t-il ? Regardez vous-même :

SUBI R16, (- 1) est, en fait, R16 - 255 et dans presque tous les cas, il nous accordera un prêt de la catégorie suivante - C. Et dans le registre, il y aura le nombre le plus grand.

Celles. voyez comment ces calculs fonctionnent, souvenez-vous du nombre dans les codes supplémentaires. Permettez-moi de vous montrer avec un exemple décimal à quatre rangées. Nous avons SEULEMENT QUATRE DÉCHARGES, ni plus, ni moins. Un nombre négatif est 0-1, n'est-ce pas ? D'ACCORD.

1 C 0000-1 = 9999 + C

Celles. nous l'avons en quelque sorte pris comme si nous en retirions 1 à cinq chiffres 1 0000, mais nous n'avons que quatre chiffres ! Code supplémentaire 9999 reçu et indicateur de prêt C (signalant qu'il y avait un prêt)

Celles. dans nos calculs entiers 9999 = -1 :) C'est facile à vérifier -1 + 1 = 0, n'est-ce pas ?

9999 + 1 = 1 C 0000 Correct ! :))) Et 1 des bits les plus significatifs ne rentrait pas dans la capacité de bits et est entré dans le drapeau de report C, qui signale également un débordement.

Bon, maintenant prenons et faisons R - (- 1). Soit R = 4

1 C 0004-9999 = 0005 + C

Alors ils l'ont pris et l'ont ajouté par soustraction. Juste magique, non ? ;)

Le problème de l'assembleur est que ce ne sont que des commandes, pas une doctrine ou une règle. Et les commandes qui impliquent des calculs symboliques peuvent être utilisées n'importe où, du moment qu'elles donnent le résultat dont nous avons besoin !

Ici et ici - notre compteur n'est pas signé, mais nous utilisons les fonctionnalités du calcul signé car c'est plus pratique pour nous.

Le drapeau C n'apparaîtra pas uniquement lorsque nous atteignons 255 (9999 en décimal), alors 255-255 = 0 et seul Z sautera, mais nous n'en avons pas besoin.

STS CCNT, R16 STS CCNT + 1, R17 STS CCNT + 2, R18 STS CCNT + 3, R19

Le code d'incrémentation d'une constante de quatre octets en mémoire peut être replié dans une macro pour ne pas encombrer le code

; Principal ================================================== ======= Principal : SETB PORTD, 4 ; Allumé LED1 INVB PORTD, 7, R16, R17 ; LED3 inversée Suivant : INCM CCNT JMP Principal

Démarrez le mode de débogage et placez un point d'arrêt (F9) sur l'étiquette Main et déplacez le curseur sur le premier point d'arrêt.

0xB6 (CCNT) 0x9F (CCNT + 1) 0x04 (CCNT + 2) 0x00 (CCNT + 3)

Il ne reste plus qu'à comparer le nombre avec cette distribution.

Comment comparer ? Assez facile. Tout dépend de ce que nous voulons obtenir. S'il y a UN événement pour TOUTE la période du compteur global de notre organe, alors c'est stupide, octet octet. Dans ce cas, votre diode clignotera une seconde après le démarrage, puis vous patienterez une demi-heure jusqu'à ce que tout le compteur de quatre octets déborde.

Pour qu'il clignote toutes les secondes, vous devez masquer les bits de poids fort du compteur global lors de la comparaison, comme s'il avait une profondeur de bits inférieure à 32 bits (et qu'il déborde plus souvent).

Les octets inférieurs sont comparés tels quels, et les octets les plus anciens uniquement jusqu'à son bit maximum, le reste doit être coupé.

Celles. le bit le plus significatif pour ce cas est CCNT + 2 = 0x04 si en représentation binaire alors 0x04 = 00000 100 et donc, nous avons un compteur à quatre chiffres, ce qui signifie un événement avec un masque

00 04 9F B6 ( 00000000 00000 100 10011111 10110110)

avant débordement, il y aura un certain nombre de fois. Vous voyez, j'ai surligné les zéros en gras. Nous ne comparerons pas du tout l'ancien, mais avant l'ancien, nous devons passer par le AND en utilisant le masque 00000111 afin de couper les bits les plus significatifs.

Ils doivent encore être remplis avant le débordement et la remise à zéro du compteur. Mais si nous les déguisons, leur sort ultérieur ne nous dérange pas. Même si ça marche jusqu'à la seconde venue, on s'en fiche.

LDS R16, CCNT ; Nous chargeons des nombres dans les registres LDS R17, CCNT + 1 LDS R18, CCNT + 2 ANDI R18,0x07; Appliquer un masque CPI R16.0xB6 ; Comparons octet par octet BRNE NoMatch CPI R17,0x9F BRNE NoMatch CPI R18,0x04 BRNE NoMatch; S'il correspond, alors nous effectuons l'action Match : INVB PORTD, 7, R16, R17 ; LED3 inversée ; Ne correspond pas - ne le faites pas :) NoMatch : Suivant : INCM CCNT ; Tourner l'orgue de Barbarie JMP Main

Dans, chargé clignote maintenant. Il n'y a pas de blunts, rien n'accroche nulle part, et le cycle principal vole avec un sifflet, juste le temps de tourner le tambour d'orgue de Barbarie :)

Il clignote juste sensiblement plus lentement que nous le voulions. Pas 1 seconde, mais 8. Eh bien, que vouliez-vous - en ajoutant une procédure de comparaison, nous avons allongé le cycle de quelques commandes supplémentaires. Et maintenant, il n'est pas exécuté à 25 barres, mais à 36. Recalculez à nouveau tous les nombres :)))))

Mais ce n'est pas encore le plus souvent ! L'astuce est qu'une partie de votre code est en cours d'exécution, et une partie ne l'est pas - les commandes de comparaison et de transition. Par conséquent, il est plus facile de calculer avec précision le retard dans les cycles d'horloge - il est plus facile de s'étrangler tout de suite - vous devez calculer pour chaque itération quand et combien de transitions vous aurez, combien de cycles d'horloge elles prendront ...

Et si le code est encore plus gros, alors finalement le tuyau et l'erreur s'accumulent à chaque itération !

Mais si vous ajoutez le code pour traiter les boutons :

; Principal ================================================== ======= Principal : SBIS PIND, 6 ; Si le bouton est enfoncé - transition RJMP BT_Push SETB PORTD, 5 ; Allumez LED2 CLRB PORTD, 4 ; Éteignez la LED1 Suivant : LDS R16, CCNT ; Nous chargeons des nombres dans les registres LDS R17, CCNT + 1 LDS R18, CCNT + 2 ANDI R18,0x07 CPI R16,0xB6; Comparons octet par octet BRNE NoMatch CPI R17,0x9F BRNE NoMatch CPI R18,0x04 BRNE NoMatch; S'il correspond, alors nous effectuons l'action Match : INVB PORTD, 7, R16, R17 ; LED3 inversée ; Ne correspond pas - ne le faites pas :) NoMatch : NOP INCM CCNT JMP Main BT_Push : SETB PORTD, 4 ; Allumez LED1 CLRB PORTD, 5 ; Eteindre LED2 RJMP Suivant ; Fin principal =============================================== = ====

Ensuite, nous verrons qu'il ne reste aucune trace des freins. Les boutons répondent instantanément à la pression et la diode clignote toute seule. Multitâche ! :)

En général, la vielle à roue n'est pas adaptée là où des calculs précis sont nécessaires. Mais si la tâche est de la série "vous devez vous branler périodiquement et pas fondamentalement comment exactement", alors c'est tout. Parce que ne prend pas de ressources matérielles comme une minuterie.

Par exemple, scannez le clavier périodiquement, disons tous les 2048 tours de boucle principale. Calculez vous-même quel nombre vous devez charger pour comparaison et quel masque imposer :)

Vous pouvez télécharger et jeter un œil, le tracer pour voir comment tout se passe là-bas.

Et pour des calculs de temps précis, il existe des minuteries. Mais ils sont un sujet distinct.

Contrôle des ports dans AVR GCC. Registres DDRx et PORTx.
Représentation des nombres. Opérations au niveau du bit.
Fonction de retard. Saut inconditionnel dans le programme.

Les ports du microcontrôleur sont des périphériques d'entrée/sortie qui permettent au microcontrôleur d'envoyer ou de recevoir des données. Le port standard du microcontrôleur AVR possède huit bits de données qui peuvent être transmis ou reçus en parallèle. Chaque bit (ou bit) correspond à la sortie (leg) du microcontrôleur. Les pieds du microcontrôleur sont également appelés broches. Pour désigner les ports, les lettres latines A, B, C, etc. sont utilisées. Le nombre de ports d'E/S varie selon le modèle de microcontrôleur.

Tout port du microcontrôleur peut être configuré en entrée ou en sortie. Pour ce faire, vous devez écrire le registre correspondant au port DDRx valeur requise. De plus, toute sortie (broche) du port peut être configurée séparément comme entrée ou sortie. Dans tous les cas, que vous souhaitiez configurer un port entier ou une seule broche, vous devrez travailler avec des plates-formes DDRx.

DDRx - registre de direction de transfert de données. Ce registre détermine si une broche de port particulière est une entrée ou une sortie. Si un bit du registre DDRx contient une unité logique, la broche de port correspondante est configurée en tant que sortie, sinon - en tant qu'entrée. La lettre x dans ce cas doit représenter le nom du port avec lequel vous travaillez. Ainsi, pour le port A ce sera le registre DDRA, pour le port B ce sera le registre DDRB, et ainsi de suite.

Utilisant AVR GCC, vous pouvez écrire l'une ou l'autre valeur dans le registre requis de l'une des manières suivantes.

Pour tout le port à la fois.

DDRD = 0xff ;

Toutes les broches du port D seront configurées comme sorties.

0xff- représentation hexadécimale du nombre ff, où 0x est le préfixe utilisé pour écrire les nombres hexadécimaux. En représentation décimale, ce sera le nombre 255 et en binaire, il ressemblera à 11111111. C'est-à-dire que les unités logiques seront écrites dans tous les bits du registre DDRD.

V AVR GCC le préfixe 0b est utilisé pour représenter des nombres binaires. Ainsi, le nombre 11111111 doit être représenté dans le programme par 0b11111111. Nous pouvons écrire la commande précédente de manière plus lisible.

DDRD = 0b11111111;

Bien que cette notation semble plus descriptive, il est courant d'utiliser la notation hexadécimale lors de la configuration des ports.

Afin de configurer toutes les broches du port D comme entrées, vous devez écrire des zéros logiques sur tous les bits du registre DDRD.

DDRD = 0x00 ;

D'autres nombres peuvent être écrits dans le registre DDRD. Par exemple:

DDRD = 0xb3 ;

0xb3- représentation hexadécimale du nombre 179. En binaire, il ressemblera à 10110011. C'est-à-dire que certaines des broches du port D seront configurées comme sorties et d'autres comme entrées.

PD0 - 1 (sortie)
PD1 - 1 (sortie)
PD2 - 0 (entrée)
PD3 - 0 (entrée)
PD4 - 1 (sortie)
PD5 - 1 (sortie)
PD6 - 0 (entrée)
PD7 - 1 (sortie)

Pour configurer séparément la broche PD2 en entrée, nous devons écrire 0 dans le bit correspondant du registre DDRD.Pour cela, la construction suivante est utilisée.

DDRD & = ~ (1
Dans ce cas, le résultat du décalage de deux positions vers la gauche est inversé par une opération d'inversion au niveau du bit, notée " ~ ".

Avec l'inversion, nous obtenons des uns au lieu de zéros, et des zéros au lieu de uns. Cette opération logique est autrement appelée opération NE PAS(Nom anglais PAS).

Ainsi, avec une inversion au niveau du bit de 00000100, nous obtenons 11111011. (Pour plus d'informations sur l'utilisation des nombres dans un microcontrôleur, consultez l'encadré ci-dessous.)

Le nombre résultant est multiplié par le nombre stocké dans le registre DDRD en utilisant l'opération de multiplication logique au niveau du bit &, et le résultat est écrit dans le registre DDRD.

Avec multiplication logique 0*0=0, 0*1=0, 1*1=1 ... L'opération de multiplication logique est autrement appelée l'opération ET(nom anglais AND).

C'est-à-dire que celui que nous avons décalé de deux positions vers la gauche devient zéro lorsqu'il est inversé et multiplié par le bit correspondant stocké dans le registre DDRD. Multiplié par zéro, nous obtenons zéro. Ainsi, le bit PD2 devient nul.

Une fois la direction du transfert de données pour le port configurée, vous pouvez attribuer une valeur au port, qui sera stockée dans le registre correspondant. PORTx.
PORTx est un registre de port, où x est le nom du port.

Si la broche est configurée en tant que sortie, un un dans le bit correspondant du registre PORTx génère un signal de haut niveau sur la broche et un zéro - un signal de bas niveau.

Si la broche est configurée en entrée, un un dans le bit correspondant du registre PORTx connecte une résistance de rappel interne à la broche, qui fournit un niveau élevé à l'entrée en l'absence de signal externe.

Vous pouvez définir "1" sur toutes les broches du port D comme suit.

PORTD = 0xff ;

Et vous pouvez définir "0" sur toutes les broches du port D comme ceci.

PORTD = 0x00 ;

Chaque bit des registres PORTx est accessible séparément, tout comme dans le cas des registres DDRx.

Par exemple, la commande

PORTD | = 1
définira "1" (signal haut) sur la broche PD3.

PORTD & = ~ (1
définira « 0 » (signal faible) sur la broche PD4.

V AVR GCC le décalage peut être effectué à l'aide de la fonction _BV () qui effectue un décalage de bit et insère le résultat dans le code compilé.

Dans le cas de l'utilisation de la fonction _BV(), les deux commandes précédentes ressembleront à ceci.

PORTD | = _BV (PD3) ; // mettre "1" sur la ligne 3 du port D

PORTD & = ~ _BV (PD4) ; // mettre "0" sur la ligne 4 du port D

Selon la méthode de connexion, la LED s'allumera soit à partir d'un signal de niveau élevé appliqué à la broche PD1 du microcontrôleur, comme dans le premier cas, soit à partir d'un signal de niveau faible dans le cas de la connexion illustrée dans la deuxième figure.

/ ************************************************* ** ********************** EXEMPLE DE LED ALLUMEE AVEC SIGNAL DE HAUT NIVEAU Exemple de raccordement sur la Figure 1 ************** * ******************************************************* ********* /#inclure $ WinAVR = ($ _GET ["avr"]); si ($ WinAVR) inclure ($ WinAVR) ; ?> entier principale ( annuler) { // démarrage du programme principal DDRD = 0xff ; PORTD | = _BV (PD1) ; // définir "1" (élevé) sur la broche PD1 }

Essayons maintenant de faire clignoter la LED connectée comme indiqué sur la figure de gauche. Pour cela, nous utilisons la fonction de délai _delay_ms().

La fonction _delay_ms() forme le délai en fonction de l'argument qui lui est passé, exprimé en millisecondes (en une seconde 1000 millisecondes). La latence maximale peut aller jusqu'à 262,14 millisecondes. Si l'utilisateur passe une valeur supérieure à 262,14 à la fonction, la résolution sera automatiquement réduite à 1/10 de milliseconde, ce qui permet des délais allant jusqu'à 6,5535 secondes. (Vous pouvez lire sur la formation de retards plus longs dans l'article.)

La fonction _delay_ms() est contenue dans le fichier delay.h, nous devrons donc connecter ce fichier au programme. De plus, pour le fonctionnement normal de cette fonction, vous devez spécifier la fréquence à laquelle fonctionne le microcontrôleur, en hertz.

/ ************************************ EXEMPLE DE CLIGNOTEMENT DE LA LED Exemple de connexion sur la Figure 1 *** * ************************************* /#define F_CPU 1000000UL #include #inclure entier principale ( annuler) { // démarrage du programme principal DDRD = 0xff ; // configure toutes les broches du port D comme sorties PORTD | = _BV (PD1) ; _delay_ms (500); // attend 0,5 s. PORTD | = _BV (PD1) ; // mettre "1" (niveau haut) sur la broche PD1, // allumer la LED _delay_ms (500); // attend 0,5 s. PORTD & = ~ _BV (PD1) ; // mettre "0" (niveau bas) sur la broche PD1, // éteindre la LED } // parenthèse fermante du programme principal

La séquence de clignotement des LED sera très courte. Afin de rendre le clignotement continu, vous pouvez organiser une boucle sans fin à l'aide de l'opérateur "goto". L'instruction goto se déplace à l'endroit du programme indiqué par l'étiquette. Le nom de l'étiquette ne doit pas contenir d'espaces. Le nom de l'étiquette est suivi de deux points. Il ne doit pas y avoir d'espace entre le nom de l'étiquette et les deux points.
/ ************************************************* ** **** EXEMPLE DE CLIGNOTEMENT SANS FIN Exemple de connexion dans la Figure 1 ******************************** *** ****************** /#define F_CPU 1000000UL // spécifie la fréquence en hertz#inclure #inclure entier principale ( annuler) { // démarrage du programme principal DDRD = 0xff ; // configure toutes les broches du port D comme sorties début: // étiquette pour la commande goto start PORTD | = _BV (PD1) ; // mettre "1" (niveau haut) sur la broche PD1, // allumer la LED _delay_ms (250); // attend 0,25 s. PORTD & = ~ _BV (PD1) ; // mettre "0" (niveau bas) sur la broche PD1, // éteindre la LED _delay_ms (250); // attend 0,25 s. aller à début; // aller au début de l'étiquette } // parenthèse fermante du programme principal

Vous avez aimé l'article ? Partager avec des amis: