LusoRobótica - Robótica em Português
Sistemas específicos => Arduino / AVR => Tópico iniciado por: c3dr1c em 19 de Janeiro de 2013, 21:17
-
Boas.
Criei este topico para expor as minhas duvidas de principiante.
Ando a estudar alguns tutoriais que encontro por ai e ja entendo algumas coisas, mas poucas :P
O que ando a fazer encontrei-o aqui: http://netexplorers.org/AVR/AVR_C.pdf (http://netexplorers.org/AVR/AVR_C.pdf)
Da autoria do Senso, do Cynary e do Njay
Agora experimentei este programa mas nao entendo certas coisas...
#include <avr/io.h> // Iniciar o programa
#include <avr/interrupt.h>
char output = 0; // Estado do led.
int main(void) {
DDRD |= (1<<PD4); // Inicializar o pino digital 4 como output.
PORTD &= ~(1<<PD4); // Inicializar o pino digital 4 como desligado.
EICRA |= ((1<<ISC00) | (1<<ISC01)); // Configurar interrupção no pino INT0 para quando este transita para HIGH
EIMSK |= (1<<INT0); // Ligar interrupções particulares
sei();
for(;;);
}
ISR(INT0_vect) { // Definir o que fazer quando acontece esta interrupção
output = ~output; // Alterar o estado
PORTD &= ~(1<<PD4); // Desligar o pino, isto é necessário para quando o output é 0 se poder desligar.
PORTD |= ((output&1)<<PD4); // output&1 pois só nos interessa o primeiro bit, assim evitamos mexer nos outros pinos.
}
Segundo o que percebi o programa dentro do main corre infinitamente ate ocorrer uma interrupção. Quando é interrompido corre uma vez o ISR e depois volta a correr o main. É isto?
Agora nao entendo isto:
PORTD &= ~(1<<PD4); // Inicializar o pino digital 4 como desligado.
Assim sempre que fizesse um ciclo nao deveria meter a saida digital a 0? E sendo assim quando houvesse a interrupção e mudasse o estado para 1, ele voltaria de novo a 0?
PORTD &= ~(1<<PD4); // Desligar o pino, isto é necessário para quando o output é 0 se poder desligar.
Desligar o que? Se é 0 porque se tem de desligar?
Cumps
-
Nope cedric. Não entendeste bem o que é uma interrupção. Quando o chip arranca, a execução chega à main() que é executada até chegar ao fim; no fim, temos
for (;; );
Esta instrução acima é um ciclo infinito. Portanto o CPU fica ali "às voltas" sem sair do mesmo sítio. A função main() na prática nunca termina, a execução não sai de lá (do ciclo infinito). Se formos ver o código assembly deste for(;; );, é algo do género disto:
ciclo:
jmp ciclo
O CPU executa uma instrução de salto (jmp = jump) para o endereço da própria instrução, daí que a execução nunca mais sai dali. É dificil tentar explicar isto sem saber exactamente qual é o teu nível de conhecimentos em programação.
Agora as interrupções. Quando ocorre uma interrupção, o CPU pára o que está a fazer e faz uma chamada (subrotina) à rotina de interrupção. O que é que quer dizer "pára o que está a fazer?" Quer dizer que, quando o CPU detecta que há uma interrupção "a pedir para correr", ele não executa a próxima instrução que ia executar e em vez disso salta para rotina de interrupção, executa-a, e no final volta ao "código anterior" e continua a execução no ponto onde tinha ficado, como se nada tivesse acontecido. O CPU verifica se há interrupções para correr após cada instrução que executa (cada instrução máquina, e não cada instrução C).
Imagina que o CPU está a executar a seguinte função
void f ()
{
a = 0;
b = 1;
}
e que durante a execução da instrução a = 0; fica pronta para correr a interrupção
ISR(...)
{
c = 2;
}
O que acontece é que a execução do CPU vai ser
a = 0;
c = 2; (dentro da interrupção)
b = 1;
Ou seja, quando há uma interrupção o CPU não pára de executar uma função, invoca a interrupção e volta ao inicio da função que estava a executar antes da interrupção; o que acontece é que o CPU pára a instrução que estava a executar, invoca a interrupção, e depois continua a executar a instrução que iria executar se a interrupção nunca tivesse ocorrido. O código que é interrompido nunca dá pela interrupção, é como se nada se tivesse passado.
Repara que a interrupção poderia pedir para correr em qualquer ponto da execução da função. Outros cenários possíveis seriam:
c = 2; (dentro da interrupção)
a = 0;
b = 1;
ou
a = 0;
b = 1;
c = 2; (dentro da interrupção)
Em geral não sabemos em que ponto do código é que uma interrupção vai ocorrer, pode ser em qualquer altura (desde que as interrupções estejam habilitadas).
Deu para entender melhor assim?
-
Ah ja entendi, acho eu ;D
Ele corre o que está dentro do main e depois ao chegar ao for(;; ) fica la dentro. Como nao existe nenhuma condição de saida o programa fica sempre ali. La dentro quando ocorre a interrupção, ele faz toggle à saida 4 e volta ao for(;; ), certo?
Sendo assim ja percebo o uso disto para inicializar o pino 4 desligado :D
PORTD &= ~(1<<PD4); // Inicializar o pino digital 4 como desligado.
Agora dentro da interrupção nao percebo isto na mesma:
PORTD &= ~(1<<PD4); // Desligar o pino, isto é necessário para quando o output é 0 se poder desligar.
Então ele primeiro inverte o "output", depois desliga o pino e depois atribui o "output", ao bit de ligar/desligar o pino, do register PORTD.
Porque tem de ser assim? Porque tem de se desligar sempre o pino? Se o output = 0 depois a ultima instrução nao atribui esse zero ao bit PD4 e desliga o pino?
Ou é devido à manipulação de bit's com |=, &=??
O meu nivel de programação é baixo... Sei labview, simatic step7,... que é tudo diferente disto :P
Quando era mais novo aprendi alguma coisa de pascal e fortran. Nem arduino sei.
Mas agora tou decidido aprender programar micros avr e há-de ser como deve de ser, em avr-gcc :D
Muito obrigado
Cumps
-
Essa forma de actuar n ovalor do pino foi apenas a técnica usada. Na verdade realmente estão a meter o pino a zero antes e depois metem a 1 se for para ficar a 1. É que a operação & só mete bits a zero e a operação | só mete bits a 1.
Há outras formas de fazer o mesmo sem fazer 2 escritas para o pino, sem causar "glitches", como por exemplo
PORTD = (PORTD & ~_BV(PD4)) | (output & _BV(PD4));
Se apenas queremos inverter o estado do pino, podemos ainda fazer uma destas 2
PORTD ^= _BV(PD4);
// isto funciona nos AVR de 3ª geração como o ATmega168/328 e
// é a forma mais rápida de inverter o estado de um pino num AVR:
PIND = _BV(PD4);
É uma questão de ganhar familiaridade com as operações "bitwise" e conhecer o sistema para o qual se está a programar.
-
Eu por acaso tinha experimentado algo parecido, mas o led ficou a piscar mt rapido
#include <avr/io.h> // Iniciar o programa
#define F_CPU 16000000UL
#include <avr/interrupt.h>
int main(void) {
DDRD |= (1<<PD4); // Inicializar o pino digital 4 como output.
PORTD &= ~(1<<PD4); // Inicializar o pino digital 4 como desligado.
EICRA |= ((1<<ISC00) | (1<<ISC01)); // Configurar interrupção no pino INT0 para quando este transita para HIGH
EIMSK |= (1<<INT0); // Ligar interrupções particulares
sei();
for(;;);
}
ISR(INT0_vect) { // Definir o que fazer quando acontece esta interrupção
PORTD ^= (1<<PD4); // Fazer toggle do pino digital 4
}
Qual a diferença do PORTD ^=_BV(PD4) - que configura o pino como saida - para o PORTD ^=(1<<PD4) - que faz o mesmo, penso eu.
Eu conheço os operadores logicos e codigo binario. Eu tive electronica digital no curso. Se calhar tenho de começar a olhar para isto como binário, não?
-
DDRD |= _BV(PD4) é que configura o pino (PD4) como saída.
PORTD ^= _BV(PD4) inverte o estado do bit 4 (o pino PD4), desde que ele esteja configurado como saída, claro.
Eu prefiro usar _BV(x) do que (1<<x).
Em C lidamos mais com bytes e respectivos múltiplos, mas estamos nestas instruções a manipular bits individuais.
-
Enganei-me, queria dizer que inverte a saida :P
Thanks