2017-06-10 4 views
1

J'utilise un Atmega328. J'ai des broches de sortie qui sont distribuées "au hasard" le long du brochage, c'est-à-dire qu'elles n'appartiennent pas aux mêmes ports. Par exemple, mes broches de sortie pourraient être PB0, PB4, PC1, PC3 et PD1. J'ai toujours utilisé Assembly pour programmer des microcontrôleurs, ce serait donc la première fois que j'utilise C. Ce que je veux savoir, c'est s'il existe un moyen d'éviter d'utiliser DDRx et Px pour chaque broche pour les régler ou les effacer.Programmation AVR en C

Par exemple, je voudrais utiliser quelque chose comme ceci pour régler le premier bit de Port B:

#define NAME_1 DDRB,0 

sbi NAME_1; 

Est-ce possible?

EDIT:

Peut-être que je ne me suis pas exprimé clairement. Ce que je veux, c'est pouvoir me référer à certaines broches du port E/S avec des noms significatifs. Par exemple, nom PD3 "blue_LED", de sorte que le code est plus lisible et, si la position de la LED bleue est modifiée plus tard, le code peut être facilement modifié. Autrement dit, je veux pouvoir allumer et éteindre certaines broches avec leurs noms qui ne sont pas codés en dur. Y a-t-il un moyen?

+0

' | = 1;' ne compilerait pas quelque chose de significatif? (après avoir remplacé '' par quelque chose comment le port B peut être défini en C, je ne connais pas votre plate-forme) – Ped7g

+0

Oui vous devriez pouvoir avoir une expérience semblable en C, vous voudriez démonter et inspecter pour vous assurer qu'il utilise les bonnes instructions, mais le compilateur est plutôt bon à ce sujet. –

Répondre

4

L'instruction sbi est particulière en ce qu'elle manipule directement un peu dans un port d'E/S sur la plate-forme AVR. Le cours normal avec les ports d'E/S est que vous devez utiliser d'autres instructions (comme out) pour copier des mots entiers entre le port d'E/S et un registre. Cela dit, il n'y a pas sbi en C. C ne connaît pas ces caractéristiques spéciales d'une plate-forme particulière. Pour l'assemblage que vous donnez à titre d'exemple, vous pouvez écrire dans C:

DDRB |= 1<<0; 

Je pense personnellement que cela semble assez concis, mais bien sûr, vous pouvez définir une macro

#define sbi(x,b) (x) |= 1<<(b) 

sbi(DDRB, 0); 

(où b est le « nombre de bits ») et peut-être l'inverse

#define cbi(x,b) (x) &= ~(1<<(b)) 

cbi(DDRB, 0) 

Cela fonctionnerait, mais je recommande paspour l'utiliser. Alors que la première notation DDRB |= 1<<0; est évidente pour tout programmeur C, l'utilisation de macros comme celle-ci ne l'est probablement pas.

Comme note finale, si vous êtes préoccupé par la performance: Je ne l'ai pas vérifié, mais je suis assez sûr avr-gcc est assez intelligent pour émettre sbi et cbi instructions lors d'une opération de masque de bits sur un port E/S effectivement change juste un seul bit. edit: voir la réponse de JLH pour un résultat expérimental que gcc-avren effet est assez intelligent pour émettre ces instructions sbi/cbi.

+0

Merci pour votre réponse!Peut-être que je ne me suis pas exprimé clairement. Ce que je veux, c'est pouvoir me référer à certaines broches du port E/S avec des noms significatifs. Par exemple, si PD3 est une LED bleue, être capable de régler "LED bleue allumée" au lieu de "4ème bit de Port D", afin que le code soit plus lisible et, si la position de la LED bleue est modifiée ultérieurement, le code peut être facilement modifié. Autrement dit, je veux pouvoir allumer et éteindre certaines broches avec leurs noms qui ne sont pas codés en dur. Y a-t-il un moyen? – Tendero

+1

@Tendero utilise simplement des macros fonctionnelles, par ex. '#define golden_led_on() (PORTA | = 1 << 4)' et appelle avec 'golden_led_on();'. Cela fonctionnerait aussi avec une macro "simple", mais cela 1.) semblerait bizarre dans votre code et 2.) vous empêcherait de le remplacer par une vraie fonction plus tard si nécessaire. –

0

Avec C++ j'ai écrit quelque chose comme ceci:

//IO = I/O Subsystem 
inline void Io_LEDred_Configure() {DDRB |= 1<<0; } //Port B0 
inline void Io_LEDred_On()  {PORTB |= 1<<0; } 
inline void Io_LEDred_Off()  {PORTB &= ~(1<<0);} 

inline void Io_LEDgreen_Configure() {DDRD |= 1<<3; } //Port D3 
inline void Io_LEDgreen_On()  {PORTD |= 1<<3; } 
inline void Io_LEDgreen_Off()  {PORTD &= ~(1<<3);} 

Si vous devez passer un IO à un autre port que vous devez changer que ces trois lignes et vous avez terminé.

En C++, le compilateur émet exactement ce code que vous écririez en tant que codeur assembleur. En C, vous devez utiliser une fonction régulière. Mais les appels viennent avec quelques frais généraux. Si vous voulez éviter cela, vous devez utiliser des macros:

#define IO_LEDRED_CONFIGURE DDRB |= 1<<0 
#define IO_LEDRED_ON  PORTB |= 1<<0 
... 
+2

Vous pouvez utiliser les fonctions 'static inline' en C (et en C++) et la plupart des compilateurs optimisateurs les alignent et évitent la surcharge de l'appel. –

4

J'utilise avg-gcc on pour programmer les AVR Atmel et les paquets de support sont très certainement au courant de cbi, sbi et de toutes les instructions à ce sujet. Il n'est donc pas nécessaire de recourir à l'assemblage, sauf si vous le souhaitez. Voici quelques C démontés pour le prouver.

 PORTD |= (1 << PD0); 
d8: 58 9a   sbi 0x0b, 0 ; 11 

Maintenant, je vais vous montrer comment. Vous pouvez définir chaque bit de chaque port pour faire l'entrée ou la sortie très facilement.

DDRA |= (1<<PA0); 

a pour effet de rendre la broche 0 sur le port A une sortie comme indiqué:

bit
    00000001 // or-ing the 1 adds 1 leaving the other bits alone. 

Pour également épingler A3 une sortie, procédez comme suit:

DDRA |= (1<<PA3); 
bit
    00001001 // now the DDRA register looks like this, pins 0 and 3 set as outputs 

Ennuyeux, droit ? Eh bien, ces expressions côté droit évaluent à des constantes de sorte que vous pouvez combiner en une seule instruction:

DDRA |= (1<<PA0) | (1<<PA3); 

et le compilateur plier le côté droit dans une constante et |= dans le registre. Donc, vous n'avez vraiment besoin que d'une seule instruction comme celle-ci par port, et le compilateur la rend très efficace.

Cela prend en compte la direction - entrée ou sortie. La prochaine consiste à définir et effacer les sorties.

Pour définir une sortie:

PORTD |= (1<<PD); // turns pin 0 on port D on (high) as an output. 
PORTD &= ~(1<<PD); // turns pin 0 on port D off (low) as an output. 
PORTD ^= (1<<PD); // toggles it (high-> low) or (low->high as an output. 

EDIT:

Pour les besoins supplémentaires que vous avez cités sur l'utilisation des noms significatifs, vous pouvez bien sûr faire. Je fais cela régulièrement pour éviter de devoir me souvenir de ce qui est connecté à quoi. Par exemple, à partir de ce même projet:

#define LED_INDICATOR_PIN PA0 
#define LED_INDICATOR_PORT PORTA 
#define LED_INDICATOR_DDR DDRA 

Je me réfère aux noms 'amis' dans mon code:

void initialize(void) 
{ 
    // Set up an output pin to blink an LED to show sign of life. 
    LED_INDICATOR_DDR |= (1 << LED_INDICATOR_PIN); 

    // Set up TTY port. 
    init_uart(); 
} 

void toggleLED(void) 
{ 
    LED_INDICATOR_PORT ^= (1 << LED_INDICATOR_PIN); 
} 

beaucoup plus facile à lire. Est-ce ce que vous cherchez? Je sais que c'est ce que je fais à chaque fois.

+0

Merci pour votre temps. Je crois que je ne me suis pas exprimé clairement. Je vais éditer ma question pour clarifier. – Tendero