Ir ao conteúdo
  • Comunicados

    • Gabriel Torres

      Seja um moderador do Clube do Hardware!   12-02-2016

      Prezados membros do Clube do Hardware, Está aberto o processo de seleção de novos moderadores para diversos setores ou áreas do Clube do Hardware. Os requisitos são:   Pelo menos 500 posts e um ano de cadastro; Boa frequência de participação; Ser respeitoso, cordial e educado com os demais membros; Ter bom nível de português; Ter razoável conhecimento da área em que pretende atuar; Saber trabalhar em equipe (com os moderadores, coordenadores e administradores).   Os interessados deverão enviar uma mensagem privada para o usuário @Equipe Clube do Hardware com o título "Candidato a moderador". A mensagem deverá conter respostas às perguntas abaixo:   Qual o seu nome completo? Qual sua data de nascimento? Qual sua formação/profissão? Já atuou como moderador em algo outro fórum, se sim, qual? De forma sucinta, explique o porquê de querer ser moderador do fórum e conte-nos um pouco sobre você.   OBS: Não se trata de função remunerada. Todos que fazem parte do staff são voluntários.
    • DiF

      Poste seus códigos corretamente!   21-05-2016

      Prezados membros do Fórum do Clube do Hardware, O Fórum oferece um recurso chamado CODE, onde o ícone no painel do editor é  <>     O uso deste recurso é  imprescindível para uma melhor leitura, manter a organização, diferenciar de texto comum e principalmente evitar que os compiladores e IDEs acusem erro ao colar um código copiado daqui. Portanto convido-lhes para ler as instruções de como usar este recurso CODE neste tópico:  
Thiago Felipe Soares Gonçalves

Temporização de 1us com ATMEGA328P usando AVR.

Recommended Posts

Boa noite, estou tentando fazer uma temporização de 1us usando um atmega328p com cristal oscilador de 16Mhz, só que não esta funcionando direito. Segue o código:

#include <avr/io.h>
#include<avr/interrupt.h>
#include <util/delay.h>

#define F_CPU 16000000UL

// Variaveis do display
volatile int setPoint = 25;
volatile char num[10] = {0x00,0x02,0x08,0x0A,0x04,0x06,0x0C,0x0E,0x01,0x03};
volatile int dez = 0;
volatile int uni = 0;

volatile int cont = 0;

void config_uC(){

    DDRB |= 0xFF;
    PORTB &= ~0xFF;

    DDRC &= ~0x03;
    PORTC |= 0x03;

}

void config_interrupt_PCINT(){

    cli();

    PCICR |= (1<<PCIE1);
    PCMSK1 |= (1<<PCINT8);
    PCMSK1 |= (1<<PCINT9);

    sei();

}

void config_interrupt_timer(){

   /* cli();  // Temporização de 1ms

    TCCR0A = 0;
    TCCR0B |= (1<<CS00) + (1<<CS01);
    TCNT0 = 5;
    TIMSK0 |= 0x01;

    sei(); */

    cli();   // Temporização de 1us

    TCCR0A = 0;
    TCCR0B |= (1<<CS00);
    TCNT0 = 239;
    TIMSK0 &= ~0x01;

    sei();

}

void display(){

    if(setPoint<10){

        setPoint = 10;

    }

    if(setPoint>95){

        setPoint = 95;

    }

    dez = setPoint/10;
    uni = setPoint%10;

    PORTB |= (1<<PB5);
    PORTB &= ~(0x0F);
    PORTB |= num[uni];
    _delay_ms(1);
    PORTB &= ~(1<<PB5);

    PORTB |= (1<<PB4);
    PORTB &= ~(0x0F);
    PORTB |= num[dez];
    _delay_ms(1);
    PORTB &= ~(1<<PB4);

}

ISR(PCINT1_vect){

    if(PINC&(1<<PC0)){


    }

    if(PINC&(1<<PC1)){


    }

}

ISR(TIMER0_OVF_vect){


    cont++;
    TCNT0 = 239;

    if(cont == 10000){

        display();
        cont = 0;

    }

}

int main(void)
{

    config_uC();
    config_interrupt_PCINT();
    config_interrupt_timer();

    while(1){

        TIMSK0 |= 0x01;
        _delay_ms(1000);
        TIMSK0 &= ~0x01;
        _delay_ms(1000);

    }

}

O que deveria fazer o programa é acender um display em 1 em 1 segundo, isso acontece direito quando uso a temporização de 1ms o display fica acesso sem intermitência, o que rola é que quando uso a temporização de 1us é que ele acende mais com intermitência. Acender de 1 em 1 segundo funciona nos dois, o que estou tentando explicar que a aparencia do de 1ms é mais constante do que o de 1us.

 

Alguém poderia me ajudar a configurar essa temporização de 1us direito.

 

Compartilhar este post


Link para o post
Compartilhar em outros sites

@Thiago Felipe Soares Gonçalves ,

 

Posso te dizer qual é o problema.

 

Vamos à teoria primeiro....

 

Clock de 16 Mhz, significa que temos em cada 1 uSeg apenas 16 ciclos de clock.

Quando ocorre a interrupção, até começar a executar a subrotina já passaram 5 ciclos de clock.

 

Agora vou supor daqui em diante que o compilador é perfeito.... 

 

Sua primeira instrução é incrementar a variável, então vou chutar que só para isso vai demorar mais uns 8 ciclos de clock.

A seguir, repoe o valor do contador do timer, que deve demorar mais uns 4 ciclos de clock.

Aí chega a comparação com o valor limite, que é de 1000 ... só isto deve demorar mais uns 8 ciclos de clock.

Mesmo que ainda não seja atingido o valor de 1000, o compilador habilita novamente a interrupção, que demora mais um ciclo de clock, e finalmente sai da interrupção, demorando mais 5 ciclos de clock.

 

Veja bem, isto supondo que o compilador é perfeito, coisa que não existe !

 

Mesmo que a contagem atinja o valor de 1000, nunca se deve chamar rotinas de display dentro de uma interrupção, pois elas demoram muitos milisegundos para serem executadas. voce deve fazer isso de display FORA de sua interrupção, por meio de um flag de sinalização.

 

Fazendo a contagem total dos ciclos de clock, temos 5+8+4+8+1+5 = 31 ciclos de clock !!!!

 

Então, não då para fazer essa sua interrupção a cada 1 useg, pois ela demora quase 2 useg só para ser executada !

 

Experimente o seguinte : em vez de fazer a cada 1 useg, faça a cada 5 useg, que vai funcionar, ok ?

 

Existem alguns truques, como por exemplo fazer o próprio hardware gerar essa interrupção, assim voce não teria o trabalho de repor o contador do timer e ganharia alguns ciclos de clock, e teria de fazer em Assembly puro esse trecho da interrupção, mas não daria para fazer com 16 Mhz, mas creio que daria com cristal de 20 Mhz.

 

O motivo é que perdemos 11 ciclos de clock entre a entrada e a saída da interrupção, e mesmo a 20 Mhz só temos mais 9 ciclos de clock para fazer algo útil.....

 

Paulo

Editado por aphawk
  • Curtir 2

Compartilhar este post


Link para o post
Compartilhar em outros sites
12 horas atrás, aphawk disse:

supondo que o compilador é perfeito

Certa feita (há long time ago...) fiz um programa relativamente complexo em assembly. Na sequencia, tempos depois, fi-lo em c. você pode acreditar que em c  ficou menor, mais otimizado, ocupou menos espaço? Pois pode.

 

Voltando... portanto a dica é fazer o menor numero possível de coisas numa interrupt. O próprio nome sugere isso. 'Pow tava indo tão bem e você tinha que me interromper kará io'

  • Curtir 1

Compartilhar este post


Link para o post
Compartilhar em outros sites
6 horas atrás, Isadora Ferraz disse:

Certa feita (há long time ago...) fiz um programa relativamente complexo em assembly. Na sequencia, tempos depois, fi-lo em c. você pode acreditar que em c  ficou menor, mais otimizado, ocupou menos espaço? Pois pode.

 

Voltando... portanto a dica é fazer o menor numero possível de coisas numa interrupt. O próprio nome sugere isso. 'Pow tava indo tão bem e você tinha que me interromper kará io'

 

Isso aconteceu comigo algumas vezes, quando programava para MS-DOS.

Algumas funções de alto nível, como gravação em bloco em periférico, ou leitura em bloco, costumam ficar menores do que a gente tentar fazer na mão em Assembly.

 

Quando o problema é o tempo de execução, sempre o Assembly ganha de lavada ( claro, depende do conhecimento do programador sobre o hardware ) , por exemplo neste instante estou tentando otimizar em Assembly uma interrupção do hardware para tratar a leitura de um desses Rotary encoder chineses que podem ser acionados a qualquer instante ... o problema é que uso no mesmo projeto 2 interfaces seriais, sendo que uma é por software, e justamente essa tem de se comunicar a 57600 Bps..., e esses malditos encoders geram ruído na comutação, então tem de filtrar ruído dentro da interrupt dele..... fora a conversa via I2C com um RTC que teve de ter a velocidade baixada para funcionar direito....

 

Estou usando uma biblioteca pronta para a serial simulada por software, mas ela não funciona direito se houver interrupção que consuma mais do que cerca de 60 ciclos de clock.......  na verdade estou tirando leite de pedra para baratear todo o projeto, então estou re-escrevendo a biblioteca em Assembly aproveitando o máximo características dos AVR's, tentando deixar as partes críticas imunes a essa interrupt do hardware .... já consegui fazer funcionar bem com clock de 16 Mhz, mas o cliente quer usar 8 Mhz para poder utilizar uma família obsoleta de microcontrolador ( e por isso mesmo 50% mais barato do que os mais atuais... ), e já perdi mais de 6 horas só hoje tentando fazer a bendita funcionar direito com o clock de 8 Mhz ....

 

Paulo

Compartilhar este post


Link para o post
Compartilhar em outros sites

Bacana. Vou registrar mas obviamente você ja pensou nisso e não o fez por motivos de economia e afins.

Sobre mais de uma interface serial: um simples multiplex digital (ou analógico) pode chavear entre as 'portas'. Com um ou 2 pinos do mc (que você não tem disponível) e uma lógica e algoritimo pode-se fazer este controle de direcionamento de portas (inventei isto agora). O exemplo é a maneira como sãomultiplexadas as entradas analógicas de um mc.

2 horas atrás, aphawk disse:

Estou usando uma biblioteca pronta para a serial simulada por software

Mas serial é relativamente simples. Faça a sua lib

abç

  • Curtir 1

Compartilhar este post


Link para o post
Compartilhar em outros sites
  • Autor do tópico
  • Só mais uma pergunta, eu consigo deixar a frequência de clock do meu microcontrolador num período de 1us, pois dai posso usar o time1 do atmega328p para gerar interrupções num período variante de 0 a 8333 us. A ideia é chavear um triac no momento certo para controlar a potência do meu sistema, mas utilizando o delay na interrupção externa interfere demais no meu display. Por isso estava tentando fazer a interrupção pelo timer.

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    você pode tentar usar uma linguagem ainda mais de baixo nível e também  otimizar teu código, reduzir as informações das no interrupt. E se ainda assim não conseguir você pode fazer overclock no atmega aumentar os ciclos por segundo e atingir sua meta.

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites
  • Autor do tópico
  • O que eu realmente quero fazer é um controle de potência pelo angulo de potência de um triac, eu consegui fazer isso para um PIC16F688 com o clock interno de 4MHz utilizando uma interrupção pelo timer que contava com períodos de 1us. Quero fazer a mesma coisa agora controlar a potência de uma carga, porém quero utilizar um display que projetei com dois botões que controla quanto de potência esta sendo injetada. Segue o código:

    #include <avr/io.h>
    #include<avr/interrupt.h>
    #include <util/delay.h>
    
    #define F_CPU 8000000UL		//Baixei o clock da CPU para 8MHz, mas não mudei o cristal oscilador que continua com 16MHz
    							//Quero conseguir um periodo de 1us
    float PWR = 0;
    float T1 = 0;
    volatile int Tempo = 0;		//Tempo que espero para disparar o TRIAC
    
    char msbyte = 0;			//Bytes mais significatos do Tempo
    char lsbyte = 0;			//Bytes menos significatos do Tempo
    
    volatile int setPoint = 25;
    volatile char num[10] = {0x00,0x02,0x08,0x0A,0x04,0x06,0x0C,0x0E,0x01,0x03};
    volatile int dez = 0;
    volatile int uni = 0;
    
    volatile int flag_display = 0;
    
    void config_uC(){
    
        DDRB |= 0xFF;
        PORTB &= ~0xFF;
    
        DDRC &= ~0x03;
        PORTC |= 0x03;
    
        DDRD |= (1<<PD2);
        PORTD &= ~(1<<PD2);
    
    }
    
    void config_interrupt(){		//Configuração da Interrupção externa que sincronisa com a rede elétrica (Funcionando direito)
    
        cli();
    
        EICRA |= (1<<ISC10);
        EICRA |= (1<<ISC11);
        EIMSK |= (1<<INT1);
    
        sei();
    
    }
    
    void config_interrupt_PCINT(){		//Configuração de interrupção PCINT dos botões que incrementão e decrementão o display (Funciona 									 //Direito)
    
        cli();
    
        PCICR |= (1<<PCIE1);
        PCMSK1 |= (1<<PCINT8);
        PCMSK1 |= (1<<PCINT9);
    
        sei();
    
    }
    
    void config_interrupt_timer(){		//Configuração da Interrupção pelo Timer1 deve contar de 1us em 1us até chegar ao tempo 											//especificado nos registradores OCR1AH e OCR1AL quando estrourar a contagem deve jogar um pulso 									 //em PD2. (Esta parte não esta funcionando.)
    
        cli();
    
        TCCR1A = 0x00;
        TCCR1B |= (1<<WGM12) + (1<<CS11); 	//Configuro para o mode CTC e divisor de clock por 8.
        OCR1AH = 0x00;
        OCR1AL = 0x00;
        TIMSK1 &= ~0x02;					//Inicia desabilitado
    
        sei();
    
    }
    
    void display(int flag){		//Função do display. (Funciona direito)
    
        if(setPoint<10){
    
            setPoint = 10;
    
        }
    
        if(setPoint>95){
    
            setPoint = 95;
    
        }
    
        dez = setPoint/10;
        uni = setPoint%10;
    
        if(flag_display){
    
            PORTB |= (1<<PB5);
            PORTB &= ~(0x0F);
            PORTB |= num[uni];
            _delay_ms(1);
            PORTB &= ~(1<<PB5);
    
        }else{
    
            PORTB |= (1<<PB4);
            PORTB &= ~(0x0F);
            PORTB |= num[dez];
            _delay_ms(1);
            PORTB &= ~(1<<PB4);
    
        }
    
    }
    
    void pulso(){		//Função do Pulso. (Funciona Direito)
    
        PORTD |= (1<<PD2);
        _delay_us(10);
        PORTD &= ~(1<<PD2);
    
    }
    
    ISR(INT1_vect){			//Aparentemente executa correntamente a interrupção externa pois o display funcina perfeitamente.
      						//porém não tenho certeza se esta habilitando minha interrupção pelo timer1.
    
        lsbyte = Tempo;		//Pago o bytes menos significativos do tempo.
        msbyte = (Tempo & 0xFF00) >> 8;		//Pago o bytes mais significativos do tempo.
    
        OCR1AH = msbyte;		//Passo o bytes mais significativos do tempo.
        OCR1AL = lsbyte;		//Passo o bytes menos significativos do tempo.
    
        TIMSK1 |= 0x02;			//Habilito a interrupção do timer1
    
        flag_display = ~flag_display;		//Flag que controla qual segmento do display estou mostrando
        display(flag_display);				//Chamo a função do display
    
    }
    
    ISR(PCINT1_vect){		//Interrupção PCINT, funciona direito pois muda o valor do meu display.
    
        if(PINC&(1<<PC0)){
    
            setPoint++;
    
        }
    
        if(PINC&(1<<PC1)){
    
            setPoint--;
    
        }
    
    }
    
    ISR(TIMER1_COMPA_vect){			//Interrupção do Timer um por comparação OCR1A, não esta funcionando direito pois não estou 										//conseguindo controlar o ponto de disparo do TRIAC.
    
        TIMSK1 ^= 0x02;				//Essa função eu tentei dessa maneira também TIMSK1 &= ~0x02 e também TIMSK1 = 0;
        pulso();
    
    }
    
    int main(void)
    {
    
        config_uC();
        config_interrupt();
        config_interrupt_PCINT();
        config_interrupt_timer();
    
        while(1){
    
            PWR = setPoint*255.00/100.00;
            T1 = 32.60*(255.00 - PWR);
    
            Tempo = (int)T1;		//Converto o tempo de float para uma variavél inteira.
    
        }
    
    }

    Inseri os comentários de algumas coisas que estou fazendo e o que acho que esta dando problema, sei que a interrupção externa funciona direito com o display, mas a interrupção pelo timer não funciona pois a tensão que meso com o multímetro não muda quando estou setando com o display. Espero que assim tenha explicado melhor o problema. Valeu o interesse de todos no meu probleminha.

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    Penso que 8333 unidades de potência é muita flatulência pra pouco resultado final da digestão. Penso que nenhum sentido humano (normal) vá detectar uma variação de quase 1 décimo de milésimo. Considere uma variação de p.ex. 0 a 99 (ou 1 a 100%). Isto já te ajudaria na solucionática da sua problemática. (cara... e num é que "solucionática" o corretor nem xiou?!)

    Some-se isso ao fato que você deve bolar um plano pra sincronizar o display com a rede. P.ex. usar a passagem por zero que deve dar os 120Hz pra multiplexar/sincronizar o sistema. Bom, foi chute... Publique esquema e mais detalhes.

    E nada de usar float. Além de não precisar de precisão, a matemática dele come recursos valiosos do sistema

     

    Ah sim, não analisei o fonte com a profundidade necessária, sorry.

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    Pois é, eu também não ví nada sobre o sincronismo com a rede....

     

    Bom, fazer o Timer1 contar a cada 1 useg usando um clock de  8 Mhz é fácil, use prescaler de 1 com preload de 65528 e ele vai contar a exatos 1 Mhz.

     

    Só não entendo de que maneira que você vai fazer ele trabalhar a 8 Mhz usando um cristal de 16 Mhz !!!!!!

    O fato de você colocar essa frequência no seu programa não vai adiantar de nada.... O que existe no AVR é que voce pode programar um Fuse para que ele divida o clock por 8, mas isso não adianta nada no seu caso.

     

    Melhor usar no seu programa a frequência de 16 Mhz mesmo, e assim use preload de 65520, e vai ter a contagem a 1 Mhz.

     

    Claro que não vai adiantar nada você mandar interromper em contagens mínimas, por exemplo abaixo de 20 useg, pois não vai conseguir fazer o seu pulso, por menor que seja ele....

     

    Outra coisa, esse pulso, para acionar o Triac, tem de ter um tempo mínimo, mas o que o pessoal faz é simplesmente usar um dos modos do Timer/Counter que após o tempo programado envia nível alto para a saída, pois a partir desse momento o Triac vai continuar acionado até a tensão da rede cruzar novamente por zero, independente de manter ou não o seu gate acionado.

     

    Toda a lógica depende do seu sincronismo com a passagem por zero.

     

    Aqui no Fórum existem vários exemplos sobre esse assunto.

     

    Paulo

     

     

    Editado por aphawk

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites
  • Autor do tópico
  • Vamos lá depois de ler o que me escreveram eu modifiquei o código.

     @Isadora Ferraz o tempo de 8333 us é o meio período de uma senoide ou seja 1/120, preciso desse controle preciso pois estarei trabalhando futuramente com controle PID e para manter meu sistema estável preciso controlar a potência da maneira sensível possível.

    Assim vou poder chavear o TRIAC sempre na mesma posição calculando um valor entre 500us - 7800us, esse recuo se faz necessário para não interferir no meu zero crossing, assim o controle não se perde.

    @aphawk o que você escreveu esta extremamente certo, eu errei ao tentar mudar frequência de clock pelo código, eu fazia isso no MSP430 setando o clock interno dele e pensei que dava pra fazer isso no atemega328p, então mudei a ideia usando o clock de 16MHz com o preescaler de 8, configurei o Timer1 para fazer interrupção por comparação, sendo assim eu calculo meu tempo no main(), separo os bits mais e menos significavos e transfiro ela para os registradores OCR1AH e OCR1AL e depois a interrupção externa habilita a interrupção pelo Timer1, esse deveria começa a contar e quando desse o estouro a interrupção do Timer1 devia se desabilitar e dar um pulso no meu pino PD2.

     

    O que tenho hj um display que funciona bem e uma interrupção de timer1 que não funciona.

     

    #include <avr/io.h>
    #include<avr/interrupt.h>
    #include <util/delay.h>
    
    #define F_CPU 16000000UL
    
    float PWR = 1000.00;
    volatile int Tempo = 1000;
    
    char msbyte = 0;
    char lsbyte = 0;
    
    volatile int setPoint = 25;
    volatile char num[10] = {0x00,0x02,0x08,0x0A,0x04,0x06,0x0C,0x0E,0x01,0x03};
    volatile int dez = 0;
    volatile int uni = 0;
    
    volatile int flag_display = 0;
    
    
    void config_uC(){                               //Configuração das portas do uC
    
        DDRB |= 0xFF;
        PORTB &= ~0xFF;
    
        DDRC &= ~0x03;
        PORTC |= 0x03;
    
        DDRD |= (1<<PD2);
        PORTD &= ~(1<<PD2);
    
    }
    
    void config_interrupt(){                        //Configuração da interrupção externa PD3/INT0
    
        cli();
    
        EICRA |= (1<<ISC10);
        EICRA |= (1<<ISC11);
        EIMSK |= (1<<INT1);
    
        sei();
    
    }
    
    void config_interrupt_PCINT(){                  //Configuração do PCINT
    
        cli();
    
        PCICR |= (1<<PCIE1);
        PCMSK1 |= (1<<PCINT8);
        PCMSK1 |= (1<<PCINT9);
    
        sei();
    
    }
    
    void config_interrupt_timer(){                  //Configuração da interrupção pelo Timer1
    
        cli();
    
        TCCR1A = 0x00;
        TCCR1B = (1<<WGM12) + (1<<CS11);            //Comparação com OCR1A e Preescaler de 8
        OCR1AH = 0x03;                              //Bits mais significativo
        OCR1AL = 0xE8;                              //Bits menos significativo
        TIMSK1 = 0x00;                            //Desabilitada a interrupção pelo Timer1
    
        sei();
    
    }
    
    void display(int flag){
    
        if(setPoint<10){
    
            setPoint = 10;
    
        }
    
        if(setPoint>90){
    
            setPoint = 90;
    
        }
    
        dez = setPoint/10;
        uni = setPoint%10;
    
        if(flag_display){
    
            PORTB &= ~(1<<PB4);
            PORTB &= ~(0x0F);
            PORTB |= num[uni];
            PORTB |= (1<<PB5);
    
        }else{
    
            PORTB &= ~(1<<PB5);
            PORTB &= ~(0x0F);
            PORTB |= num[dez];
            PORTB |= (1<<PB4);
    
        }
    
    }
    
    void pulso(){
    
        PORTD |= (1<<PD2);
        _delay_us(10);
        PORTD &= ~(1<<PD2);
    
    }
    
    ISR(INT1_vect){
    
        TIMSK1 = 0x02;                             //Habilita interrupção pelo Timer1
    
        OCR1AH = msbyte;                            //Transfere os bits mais significativos do tempo calculado
        OCR1AL = lsbyte;                            //Transfere os bits menos significativos do tempo calculado
    
        flag_display = ~flag_display;
        display(flag_display);
    
    }
    
    ISR(PCINT1_vect){
    
        if(PINC&(1<<PC0)){
    
            setPoint++;
    
        }
    
        if(PINC&(1<<PC1)){
    
            setPoint--;
    
        }
    
    }
    
    ISR(TIMER1_COMPA_vect){
    
        TIMSK1 = 0x00;                            //Desabilita a ineterrupção pelo Timer1
        pulso();
    
    }
    
    int main(void)
    {
    
        config_uC();
        config_interrupt();
        config_interrupt_PCINT();
        config_interrupt_timer();
    
        while(1){
    
            PWR = setPoint*16665/100.00;            //Calcula o tempo a partir do setPoint inc/decrementado da interrupção PCINT
            Tempo = (int)(16665 - PWR);             //Calcula o tempo e transforma para inteiro.
    
            msbyte = (Tempo & 0xFF00) >> 8;         //Separa dos bits mais significativos do tempo
            lsbyte = Tempo;                         //Separa dos bits menos significativos do tempo
    
        }
    
    }

    As linhas a seguir:

     

            PWR = setPoint*16665/100.00;            
            Tempo = (int)(16665 - PWR);

     

    O numero 16665 é equivalente em tempo a 8333us, que é basicamente uma regra de 3. O (int) serve pra converter o float em um int. O PDF a seguir contem meu esquemático do circuito, aonde tenho meu zero crossing e o circuito de chaveamento do TRIAC.

     

    Só falta isso funcionar para eu poder seguir em frente com esse projeto.

     

         

    12.pdf

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    Contra a minha vontade baixei o pdf. (publique em formato de desenho .jpg,.png assim aparece direto na página do forum) Seu esquema ta confuso. Não vi o display e nem sei se é multiplexado. (também nao analisei seu código, lembra?).

     

    Pelo que entendi você quer um controle 'suave' de altíssima resolução quase linear. Permita-me opinar que isso é meio desnecessário mesmo pra pid. Também repito que contas com float comem tempo e recurso do teu mc o que pode estar atrapalhando nas temporizações. E mais... você não precisa converter pra unidades humanamente conhecidas pra trabalhar no 'interior' do mc pois ele não é humano (dããnnn). Ele gosta mais de binários e hexas.

     

    Ok isso não resolve seu problema mas é uma tentativa de fazer você acompanhar o raciocínio do mc

     

    Mesmo sem entender direito seu esquema e fonte, a dica que lhe pode ser útil (ou não totalmente inútil) é: sincronize todo o seu fluxo (o loop principal) com a passagem por zero

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites
  • Autor do tópico
  • @Isadora Ferraz O display já esta funcionando da maneira que deveria, ele deixou de ser problema, no diagrama que mandei ele não esta presente, pois são placas separadas, mas tem o conjunto de pinos ao qual ligo o display, agora o problema é o Timer que não esta disparando e eu não sei o porque, revi varias vezes os registradores e não sei o que estou errando na configuração deles, a conversão que faço é necessária pois o timer1  do atema328p tem dois registradores que armazenam o valor do contador de 0-65535, os dois registradores OCR1AH e OCR1AL armazenam respectivamente os 8 bits mais significativos e o outro os 8 bits menos significativos para ter um total de 16 bits do OCR1A correspondente ao valor de 0-65535. Por isso o valor do tempo que eu calculo naquela regrinha de 3 tem que ser convertido, vou descrever abaixo:

     

    Se eu tenho um clock de 16Mhz seu período é o inverso da frequência que é 62,5ns, o máximo de tempo que posso ter no timer1 sem preescaler é então (62,5ns*(2^16)) que é equivalente a 4,096ms. Lembrando que estou chaveando um TRIAC entre o intervalo de 1/120Hz que é equivalente a um período de 8,333ms que é metade de uma senoide, sendo assim o timer1 sem preescaler não atende minhas necessidades.

     

    Agora se eu usar o clock de 16Mhz com um preescaler de 8, vou ter um período de  500ns (1/2Mhz), o que me possibilita o tempo máximo de 32,768ms (500ns*(2^16)) no timer1, sendo mais do que necessário para minha aplicação.

     

    Agora a regra de três:

     

    O timer1 é um timer de 16 bits o que significa que posso ter um contador de 0-65535. Sendo assim:

     

    32,786 ms--------------------65535 ciclos

      8,333 ms--------------------~16656 ciclos

     

    Como calculo a potência injetada na minha carga:

     

    0 %------------------------100 %                  >>> setPoint

    0 ms-----------------------8,333 ms

    0 ciclos -------------------~16656 ciclos    >>> Tempo

     

    PWR = setPoint*16656/100;

     

    Esse valor esta em float preciso dele em int.

     

    Tempo = (int)(16656 - PWR);

     

    Agora separo os bits mais e menos significativos nas variáveis que passarei para os registradores.

     

    msbyte = (Tempo & 0xFF00) >> 8; >>>> mais significativo;

    lsbyte = Tempo; >>>> menos significativo;

     

    Por fim, passo esses valores para os registradores correspondentes.

     

    OCR1AH = msbyte;

    OCR1AL = lsbyte;

     

    Espero ter deixado mais claro o que estou fazendo.

     

     

     

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    Sobre o float o que quis dizer é que você (o mc) não precisa trabalhar com tempo e sim com ciclos. Algo como ao invés de fazer delayUs(8333/2) você pode fazer um delay especial com unidades do seu timer. Claro você conhecendo o tempo de cada unidade. Mas esquece... Isso penso que pra quando você amadurecer 1 pouco...

     

    Pelo que entendi da sua problemática é que você consegue acionar o display (que não sei ainda se é multiplexado) mas não tem sucesso no controle de carga simultaneamente. Neste caso, sempre caímos no lance do sincronismo com a rede. Ela é que tem que "dar as cartas" entende? Pouco posso acrescentar a não ser te sugerir que isole as coisas: esqueça o display e faça um controle da potência p.ex. Em algum momento você deve perceber quem está zoando quem.

     

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    Olá de novo. Agradeço a gentil mp. Realmente tenho dificuldade em me comunicar no seu idioma.

    Continuemos por aqui mesmo ok? Quanto mais cabeças a pensar, melhor. Exponha o que você quer exatamente que ocorra, o que está ocorrendo e melhore o esquema. Por enquanto o resumo que só sei é que você tem um sistema com um tal display misterioso e que fazer um dimmer.

    Tem um dimmer que fiz com um pic10f e 2 botões sobe e desce em c. Trisimples. Funcionou no proteus: não montei. Se quiser (e eu achar) posso compartilhar.

    • Curtir 1

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites
  • Autor do tópico
  • Vou tentar ser o mais objetivo possível, vamos esquecer o display a partir de agora já que este esta dando muita dor de cabeça e não é o objetivo principal do projeto e vamos nos concentrar a partir de agora no controle de potência. Para controlar a potência eu utilizo o circuito da figura abaixo:

     

    596f4aae2726c_ControledePotncia.thumb.jpg.8fe2759945bace39c710b4277102d32a.jpg

     

    Neste esquemático temos os módulos necessários para fazer esse controle potência a partir do angulo de potencia do TRIAC, mas o que seria esse angulo potência, basicamente é o ponto na senoide onde o TRIAC é disparado e entra em condução, podemos observar isso na figura a baixo:

     

    power28.gif.01f21afaaa0967ce7f4622ae82b8bc4a.gif

     

    O principio é simples, temos um semiciclo de senoite que compreendi o angulo de 0°~180°, no nosso caso a frequência da onda senoidal é 60Hz e o seu semiciclo possui a frequência de 120Hz que equivale em tempo a 8,333ms, chaveando um TRIAC sempre na mesma ponto deste semiciclo obtemos o efeito de controlar a tensão RMS aplicada a uma carga e por consequência a potência já que P=V²/R, desta maneira conseguimos a potência desejada apenas mudando a posição aonde é disparado o TRIAC.

     

    A teoria é simples, mas a pratica é um pouco mais complicado, já que o uC precisa saber aonde disparar o TRIAC para obter a potência desejada e para isso ele precisa saber em que ponto do semiciclo da senoide ele esta e em que ponto ele deve disparar o TRIAC para obter a potência desejada.

     

    Assim sendo eu utilizamos um circuito Zero Crossing para sincronizar o uC com a rede elétrica, este circuito enviar um sinal (pulso) para o uC toda vez que identifica uma passagem por zero na onda senoidal, conforme a figura abaixo:

     

    596f5112a61b6_ZeroCrossing.png.eea61c14b8d5d34d24891868f6bda0fe.png

     

    Os ponto em vermelho na figura são representam as passagens por zero na onda senoidal, o circuito de Zero Crossing retifica essa onda senoidal deixando ela puramente positiva e nas proximidades do zero ele envia um pulso para uC, neste momento o uC sabe que esta no inicio de um semiciclo de onda, a partir deste ponto ele conta o tempo necessário para disparar o TRIAC e obter a potência desejada.

     

    No firmware desse uC precisamos ter então uma interrupção externa para captar esse sinal e a partir dele habilitar uma interrupção por timer que no seu estouro dispara o TRIAC no angulo certo para a potência desejada.

     

    Por esta razão é necessário configurar o Timer para disparar no intervalo entre 0°~180°, equivalente em tempo 0ms~8,333ms.

     

    O programa que já enviei aqui funciona a parte de sincronização, o uC sabe onde esta no semiciclo, mas esta se perdendo onde dispara o TRIAC, porque o Timer1 não esta funcinando direito o que deixa ele bem louquinho chaveando em qualquer posição. 

     

    Não consegui ser muito objetivo.

    :tw_grimace:

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    Legal que você entendeu e compartilhou o funcionamento de um dimmer com mc. Aí sim hein!

    você já meio caminho andado.

    Veja uma opção que talvez eu (eu) tentaria no seu lugar pro caso de do meu chefe chato falar que demite se eu não usar interrupt e timer. O que você vai ler a seguir é só conceito pois não usei interrupt pra isso.

    Na interrupção, realimente o timer com a variável de controle e espere o timer passar por ela - ou melhor, zerar, antes de dar um pulsinho no gate. Obviamente o timer terá que estar rodando e numa frequência definida-conhecida

    vejamos... algo como
     

    #define pulso PORTB.ODR0;//veja a sintaxe do seu compilador
    unsigned char potencia
    interrupt_da_passagem_por_zero()
    {
    TMRL=0;//veja a definição do seu mc
    TMRH=potencia;
    while(TMRH); //aqui está o atraso
    pulso=1;
    asm("nop";asm("nop";asm("nop";//alguns nops pra alarguear o pulso
    pulso=0;
    }

    De fato nem precisa usar o timer pra dar o atraso. Pode usar um delay qualquer

    a variável potencia você controla no loop principal com botões p.ex.

    Perceba zero de matemática e apenas usando byte (que o mc a-d-o-r-a)

    Isso deve funcionar mas você não vai querer fazer isso pois o mc vai ficar parado muito tempo. (a não ser que ele faça só isso e mais naaaddaaa). É só pra você testar. Em outro momento você pode bolar um jeito de gerar tal pulso pelo hw do mc p.ex. usado o pwm.

    Não vi o ds deste mc mas se ele liberar um pulso cada vez que o timer passa por zero (não confunda com zero crossing), seus problemas acabaram. O mc não fica "perdendo tempo"

     

    A propósito, achei ...

     

     


     

    
    //programa dimmer com pic10f200
    //16/04/08
    //V0.0 - simulação no MPLAB OK
    //V0.1 - simulação no Proteus
    
    #include <pic.h>
    
    #define sobe GP0
    #define desce GP1
    #define triac GP2
    #define zeroc GP3
    
    #define ligado 1
    #define desligado 0
    
    __CONFIG(PROTECT & WDTDIS & MCLRDIS);
    
    void delay(unsigned char dl)
    {
    while(dl--); //alguns uSegs
    }
    
    //******************************************************
    
    void main (void)
    {
    unsigned char a=120; //valor de teste ~ metade da potência
    OPTION=0b00000100; ///pullup, prescaler timer0=1:32
    TRIS=0b1011; //só GP2=saída=dispara triac
    GPIO=0xff;
    
    for (;;)
    {
    while(!zeroc); //aguarda passagem por zero
    
    TMR0=a-20; //de neg. p pos. alguns triacs disparam antes (ou depois!)
    while(TMR0);//aguarda por alguns mSeg
    
    triac=ligado; //pulso...
    delay(30); 
    triac=desligado; //...no gate
    
    while(zeroc); //aguarda passagem por zero
    	
    TMR0=a;
    while(TMR0); //aguarda por alguns mSeg
    
    triac=ligado;
    delay(30);
    triac=desligado;
    
    if (!sobe) a++; //ângulo de disparo
    if (!desce) a--;
    
    if (a<25) a=25; //limite mínimo
    if (a>240) a=240; //máximo
    
    	
    }
    
    }
    

     

     

    Pode não lhe ser totalmente inútil
    Essencialmente é o que você descreveu com a tradicional ausência de matemática e minimalismo. E mesmo se algum dia eu (eu) for fazer algo com pid, tentarei permanecer firme nos meus princípios minimalísticos kk. Aliás, desculpe mas não resisti em usar só byte. Minimalismo, lembra? Se você conseguiu fazer o seu timer contar a 1microsegundo por pulso e insistir em, você pode fazer a espera deste tempo. Algo como

    unsigned int microsegundo;

    while(microsegundo) {microsegundo=TMRL+TMRH*256);}//atraso em us

    Sei lá.. tá mieo etsrahno isso nmu etsá?..kK

    abç

     

    Editado por Isadora Ferraz
    code e complementos

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    @Thiago Felipe Soares Gonçalves ,

     

    Já ouviu aquela famosa frase : "A teoria na prática é outra " ?

     

    Use os dois canais do osciloscópio e confira se o pulso de passagem por zero está mesmo coincidindo com a passagem da senóide reais..... vai perceber que tem mais de 10 graus de erro....

     

    Você não precisa gerar nenhum pulso, pois o Triac vai ficar acionado de qqr maneira até que a tensão sobre ele chegue a zero. Basta levar a saída para nível alto e pronto.

     

    Existe um dos modos do Timer do Atmega que faz exatamente isso que você precisa, você apenas define a contagem, e ao final a saída OC do Timer vai para nível alto, ao mesmo tempo que gera uma interrupção e assim você pode atender a interrupção e zerar essa saída. O Triac vai ficar acionado até o final do semiciclo.

     

    Mas você não vai conseguir controlar de 0 a 180 graus, a menos que modifique o seu circuito sensor só vai conseguir de uns 10 graus até 180, o que vai permitir entregar de zero até uns 97% da potência.

     

    Já teve uma discussão sobre isto no Fórum....

     

    Paulo

     

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    Bacana paulão. Quando você diz

    31 minutos atrás, aphawk disse:

    saída OC do Timer vai para nível alto

    é um pino físico externo do mc? Ou só pra "uso interno"? Se externo realmente os problemas do garoto acabaram e .. porquê você não disse isso antes? !

    O algoritimo teórico penso ser algo como:

    -interrupt cruza zero,-zera pino gate e/ou algum flag,-programa timer com a potência invertida e sai fora pra fazer coisas + importantes.

    Daqui pra frente é o hw que vai acionar o gate no fim da contagem (penso que não precisa de interrupt  do timer). Só isso. Sem delay e etc. Quase o momento de pensar o display multiplexado...

    Bati na trave? kk

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    @Isadora Ferraz ,

     

    Sim, é um pino físico, no Atmega328P o Timer1 tem duas saídas disponíveis, a OC1A e a OC1B, e elas podem ser programadas independentemente para mudar o nível quando atingir a contagem desejada E gerar uma interrupção ao mesmo tempo.

     

    Você acertou, é só colocar a contagem inicial e deixar que o hardware faça o acionamento, só que temos de reprogramar a saída para o nível 0 antes de fazer o Timer contar novamente ( e claro, ANTES de ocorrer uma nova transição por zero do detector de passagem por zero... ) . Eu aproveitaria a interrupção para zerar a saída, mas com um pequeno delay para garantir que o Opto acionou o Triac.

     

    Ou pode ser tudo feito sem interrupção  também, apenas baseado numa rotina de delay. Ou pode fazer o tratamento do display multiplexado por uma outra interrupção de outro Timer,  Ou...... 

     

    Tem muitas maneiras para se fazer esse projeto !

     

    E também não acho que tenha alguma utilidade tanta precisão de 16 bits para esse controle..... pra mim 1% de step já é muito alto para isso, pois teria de calcular qual a potência entregue para a carga, que não é de jeito nenhum linear com os graus de acionamento.

     

    Eu estou mais assistindo porquê não entendo nada dessa linguagem .....

     

    Paulo

     

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    Bastante informação nesse tópico, eu realmente estranhei tudo isso ainda mais que quando vejo esses circuitos dimmer não parece tão difícil assim (usam circuitos bem simples). Até pensei "não recordo de ver nenhum dimmer com um micro controlador"... Com a explicação do circuito ficou bem mais fácil entender X)

     

    Enfim... acompanhando aqui ^^

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    @Bommu Perneta ,

     

    Os dimmers normais funcionam baseado num princípio bem simples.... um potenciômetro faz um divisor de tensão, e quando a tensão chega ao nível suficiente, o Triac dispara e pronto. Esses são os chineses baratinhos que tem no mercado aos milhares.

     

    Para um controle microprocessado, em 99% dos casos se usa o princípio de temporização com referência, isto é, o micro fica contando o tempo a partir da passagem por zero, assim se consegue fazer um controle razoável ( mas não linear ) da potência aplicada à carga.

     

    Existem alguns CIs dedicados para isso, que são pequenos microprocessadores com programa próprio e que inclusive usam sensor de toque para o aumento e diminuição do nível de luz, mas só funcionam para lâmpada incandescentes.

     

    Agora que as luzes são de Leds ou fluorescentes, tem de se fazer de outras maneiras bem mais complexas ....

     

    Paulo

    • Curtir 1

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites
  • Autor do tópico
  • Então amigos  voltei, desculpe a demora mais estava testando varias coisas aqui, e acho que encontrei o problema, só não sei a causa e como resolve-ló.

     

    Então primeiro o código e depois explicações do ocorrido.

     

    #include <avr/io.h>
    #include <avr/interrupt.h>
    
    #define F_CPU 16000000UL
    
    void config_uC(){
    
        DDRC |= (1<<PC0)|(1<<PC1);
        PORTC = 0x00;
    
    }
    
    void config_interrupt(){
    
        cli();
    
        EICRA |= (1<<ISC10);
        EICRA |= (1<<ISC11);
        EIMSK |= (1<<INT1);
    
        sei();
    
    }
    
    void config_interrupt_timer1(){
    
        cli();
    
        TCCR1A = 0x00;
        TCCR1B |= (1<<WGM12)|(1<<CS11);             //habilita o modo CTC, configura o preescaler 8
        OCR1A = 8000;                               //temporização de 4ms
        TIMSK1 &= ~(1<<OCIE1A);                     //desabilitada a interrupção por timer
    
        sei();
    
    }
    
    ISR(INT1_vect){
    
        PORTC ^= (1<<PC0);                          //troca o estado do pino PC0 a cada interrupção externa
        PORTC &= ~(1<<PC1);                         //reseta o pino PC1 do timer
        OCR1A = 8000;                               //temporização de 4ms
        TIMSK1 |= (1<<OCIE1A);                      //habilitada a interrupção por timer
    
    }
    
    ISR(TIMER1_COMPA_vect){
    
        PORTC |= (1<<PC1);                          //seta o pino PC1 do timer / indica a entrada na rotina de interrupção do timer
        TIMSK1 &= ~(1<<OCIE1A);                     //desabilita a interrupção por timer
    
    }
    
    int main(void)
    {
    
        config_uC();
        config_interrupt();
        config_interrupt_timer1();
    
        while(1);
    
    }

    Aqui esta o código bem simples e limpo, no meu main() só tem as rotinas de configuração das portas do uC, da interrupção externa e do timer e o loop infinito.

     

    Na configuração da interrupção externa eu configurei ela para interromper sempre que aparecer uma borna de subida.

     

    Na configuração da interrupção do timer eu configurei o modo CTC, que interrompe sempre que meu contador chegar no OCR1A ocorrendo assim o estouro do timer, a temporização configurada é de 4ms, eu testei essa configuração utilizando somente o timer e nada mais, trocando o estado de um pino a cada 4ms e no osciloscópio apareceu bonitinho a forma de onda que eu queria.

     

    O que esse código deveria fazer é simples, sempre que ocorrer uma interrupção externa que nesse caso ocorre num período de exatos 8,3ms (verificado no osciloscópio) ele troca o estado do pino PC0, abaixa o pino PC1, seta o registrador OCR1A e por habilita a interrupção do timer.

     

    Por sua vez, o timer habilitado temporiza 4ms, até ocorrer seu estouro e assim entrar na rotina de interrupção ativando o pino PC1, em seguida desabilita a interrupção do timer.

     

    A seguir uma figura dos estados dos pinos como deveria funcionar:

     

    59767efe14ca8_WhatsAppImage2017-07-24at20_11_16.thumb.jpeg.b180ecd61503811b5110f44ea73cf5b5.jpeg

     

    O pino PC0 é referente a interrupção externa, e o pino PC1 e referente a interrupção do timer. A forma de onda que deveria ser visualizada no osciloscópio deveria ser esta acima.

     

    O que realmente acontece segue nas imagens abaixo tiradas do osciloscópio.

     

    Nesta imagem, podemos ver em azul a forma de onda da interrupção externa que troca o estado do pino PC0 a cada 8,3ms (isso esta funcionando direito). Em amarelo podemos ver a forma de onda da interrupção pelo timer. Aqui já notamos um problema ele não esta temporizando os 4ms que especifiquei.

     

    59767fb070bdf_WhatsAppImage2017-07-24at20_11.16(1).thumb.jpeg.b8dad4924eba88f605de02e516b43acc.jpeg

     

    Aqui temos um zoom nas formas de onda focalizando bem o inicio da interrupção externa, podemos verificar na curva em amarelo que a interrupção do timer esta sendo habilitada a cada cilclo e que conseguimos entrar dentro da rotina da interrupção do timer a prova disso é que o pino PC1 é elevado ao nível alto, porém o que não ocorre é a temporização dos 4ms.

     

    59767fb257227_WhatsAppImage2017-07-24at20_11.16(2).thumb.jpeg.06ecdbe1c74a8428290fe4a3ec20f2c1.jpeg

     

    E depois de toda essa explicação, testes, figuras e tudo mais, chego a conclusão que a unica coisa que não esta funcionando é essa temporização eu realmente estou quebrando a cabeça pra saber o porque, suspeito que seja algum registrador não configurado, mas mesmo assim esta muito estranho pois configurando só o timer eu consigo os exatos 4ms. 

     

    Vou continuar a trabalhar nisso espero que com isso possamos resolver esse problema para que ninguém mais caia nessa mesma armadilha.

    Editado por Thiago Felipe Soares Gonçalves

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    Algo parecido na gringa  http://forum.arduino.cc/index.php?topic=192440.0

     

    Ainda estou estudando os dois link ainda

    http://www.gammon.com.au/forum/?id=11504

    http://www.gammon.com.au/interrupts

    Longos de se estudar T_T parece que o dono do tópico conseguiu reunir um interrupt externo e de tempo o que ele tinha para fazer um único programa.

     

    Mas vou estudar isso devagar, para meu tico e teco não queimar X)

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    @Thiago Felipe Soares Gonçalves ,

     

    Não entendo nada dessa linguagem, mas fiz naquela que eu entendo, Basic :

     

    $regfile = "m328pdef.dat"
    $crystal = 16000000
    $hwstack = 32
    $swstack = 10
    $framesize = 40
    
    Config Portb.1 = Output
    Config Pind.3 = Input
    Set Pind.3
    
    Config Int1 = Rising
    Config Timer1 = Timer , Prescale = 256 , Compare A = Disconnect , Compare B = Disconnect
    Ocr1a = 250
    Timer1 = 0
    On Int1 Start_pulse
    On Compare1a Stop_pulse
    
    Portb.1 = 1
    Enable Compare1a
    Enable Interrupts
    Enable Int1
    
    Do
    Loop
    
    Start_pulse:
     Portb.1 = 0
     Ocr1a = 250
     Timer1 = 0
     Enable Timer1
     Start Timer1
     Return
    
    Stop_pulse:
      Portb.1 = 1
      Disable Timer1
      Return
    

    Fiz da maneira mais "burra" possível, pois tem como usar um dos modos do CTC que muda a saída do Timer pra mim, sem eu precisar mandar comando na interrupção.... mas funciona.

    O tempo de 4 mseg não é super-preciso, mas dá para o gasto.

     

    Segue uma imagem dos sinais :

     

     

     

    Capturar.PNG

     

    Em amarelo a entrada a 120 Hz, e em azul a saída dos 4 mseg .

     

    Espero que a lógica te ajude a consertar o seu programa.

     

    Paulo

    Editado por aphawk

    Compartilhar este post


    Link para o post
    Compartilhar em outros sites

    Crie uma conta ou entre para comentar

    Você precisar ser um membro para fazer um comentário






    Sobre o Clube do Hardware

    No ar desde 1996, o Clube do Hardware é uma das maiores, mais antigas e mais respeitadas publicações sobre tecnologia do Brasil. Leia mais

    Direitos autorais

    Não permitimos a cópia ou reprodução do conteúdo do nosso site, fórum, newsletters e redes sociais, mesmo citando-se a fonte. Leia mais

    ×