Ir ao conteúdo
  • Cadastre-se

Projetos com Avr : Design, Programação em Basic e Assembly


Ir à solução Resolvido por aphawk,

Posts recomendados

@test man*~,

Parabéns, meu amigo, claro que é util, é um sensor bem baratinho e fácil de usar !

Não tem de ficar olhando o que eu ou outros postam, o que vale é postar algo novo, assim pode ajudar a todos !

Programar tem de ser prazeiroso, nunca um sacrifício ! voce vai aprendendo mais, na medida em que encontra novos desafios, não é ?

Dá para fazer 95% de todos os projetos existentes, usando apenas a versão Demo do Bascom, e usando o Basic puro, sem Asm. Mas quem sabe logo voce se sente bem à vontade, e começa a "procurar sarna prá se coçar" , inclusive visando fabricar algum produto e ganhar um bom dinheiro com isso ?

Sobre o link do site sobre ASM, eu também aprendí por esse link ! Claro que eu já programei outras tres famílias de microprocessadores em Asm no passado, e achei os Avrs tão parecidos quanto os 8080, pelo menos nas instruções, então foi muito fåcil para mim.

Vá em frente que logo voce vai se surpreender !

Paulo

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

  • 3 meses depois...
  • 4 semanas depois...

Projeto de um Analizador Lógico Portátil com Atmega1284P - Parte 9 - GERADOR DE FORMAS DE ONDA POR SOFTWARE USANDO DDS

 

No post anterior, mostrei como podemos programar o hardware dos Atmega para podermos gerar uma determinada frequência. Porém, aquela técnica gera apenas ondas quadradas.

Hoje em dia, precisamos gerar vários tipos de formas de onda, como por exemplo ondas senoidais, ondas triangulares, ondas dente de serra, etc. A maneira mais simples e barata de se fazer isto é atarvás da técnica DDS ( Digital Direct Synthesizer ).

Vamos gerar frequências entre 1 e 300 Khz, com uma excelente qualidade ( utilizaremos um conversor tipo R-2R de 8 bits, com um total de 256 níveis diferentes) , e podemos até utilizar este aparelho como um gerador de Sweep, que é muito útil para quem gosta de brincar com áudio.

Tenho vários modelos de formas de ondas programadas, como senóide, quadrada, dente de serra, triangular, retificação em meia onda, retificação em onda completa, pulso periódico, e no momento estou implementando a possibilidade de o próprio usuário definir um sinal que deseja gerar !

Para manter o projeto barato, não implementei outras melhorias como adaptador de baixa impedância de saída, ajuste da tensão de saída, etc.

No final explicarei o DDS, para que todos vejam que é algo bem simples , embora seja apresentado até nos cursos de Engenharia como algo “revolucionário” e de difícil compreensão.

Até mesmo eu levei um susto quando verifiquei na Wikipédia o que era DDS, do jeito teórico que mostram desistí de entender o princípio, e procurei por exemplos práticos na Internet, e assim entendí o princípio e a simples maneira de implementar o funcionamento em pouquíssimas linhas de programa !

Para uma melhor compreensão, vamos mostrar tudo usando um Arduíno Uno, que fica de fácil reprodução prática.

 

Como gerar uma forma de onda ?

Vejam o esquema abaixo, o qual usa um Arduíno Uno e um conversor D/A do tipo R/2R :

 

IMG%5D

 

No programa, criamos uma tabela representando uma senóide, com 256 valores que variam de 0 a 255. Ou seja, criamos uma tabela que representa os valores de uma função senoidal de amplitude 5 volts, cujo período de 360 graus ficou dividido em 256 passos.


Portanto, o primeiro valor da tabela representa 0 graus, o segundo valor representa 360/256 = 1,4 graus, o segundo valor representa 2,8 graus, e assim por diante até o último , que representa os 360/256*255 = 358,6 graus.

 

Por convenção, o valor para 0 graus é exatamente o valor médio da senoide gerada, que é o numero 128, e portanto representa a tensão de 128/256 * 5 = 2,5 volts na saída do conversor R-2R.

 

Assim, a tensão na saída começa em 2,5 volts, sobe até os 5 Volts ( 90 graus ) , desce novamente para 2,5 volts ( 180 graus ) , continua descendo até 0 volts ( 270 graus ) , e finalmente chega aos 2,5 Volts novamente.

 

Vamos fazer um programa que gere essa forma de onda na saída do conversor R-2R. Esse é o programa DDS0.BAS

 

'----------------------------------------------------------------------
' GERADOR DE SENOIDE DE 20 HZ
' Este programa utiliza o Timer1 de 16 bits para a base de tempo
' onde a cada interrupt pegamos o próximo valor de uma tabela e
' colocamos na saída.
' Ilustra a técnica mais simples de gerar frequências.
'----------------------------------------------------------------------




$crystal = 16000000
$regfile = "m328def.dat"
$hwstack = 40
$swstack = 32
$framesize = 48

Dim X As Byte
Dim Y As Byte


Config Portd = Output

Config Timer1 = Timer , Prescale = 1
' valor teórico do Timer1
Timer1 = 62411
' vamos gerar 5120 interrupts por segundo.
'Como precisamos de 256 steps para completar um período de nossa forma de
'onda, teremos uma frequência na saída de 5120 / 256 = 20 Hz

On Timer1 Timer1_sub Nosave
Enable Timer1
Enable Interrupts
Restore Tabela
X = 0
' começaremos do numero 0
Do
' loop infinito, que no Assembly é traduzido por um simples Jump ao mesmo
' local, portanto a chance de ocorrer uma interrupção DENTRO do Jump é bem
' alta, assim consideramos que entre ocorrer uma interrupção e a primeira
' instrução da rotina de interrupçao vao se passar 6 ciclos em vez dos 4
' que normalmente são definidos
Loop



'------------- ROTINA DE INTERRUPÇÃO timer1 --------------------------------
Timer1_sub:
   'Rotina chamada pelo Timer1. Até ser reposto o valor do Timer vao se
   'passar na media 12 ciclos de clock desde o momento da interrupçao, portanto
   'vamos aumentar esses 12 ciclos do valor do contador.
   Timer1 = 62423
   'recarrega o timer novamente
   Read Y
   Portd = Y
  Incr X
  ' truque pois como a variável é tipo Byte, ao passar de 255 ela vira 0 !
  If X = 0 Then
      Restore Tabela
   End If
Return

End


' TABELA DE uma senóide com 256 pontos

Tabela:

 Data &H80 , &H83 , &H86 , &H89 , &H8C , &H8F , &H92 , &H95 , &H98 , &H9C , &H9F , &HA2 , &HA5 , &HA8 , &HAB , &HAE
 Data &HB0 , &HB3 , &HB6 , &HB9 , &HBC , &HBF , &HC1 , &HC4 , &HC7 , &HC9 , &HCC , &HCE , &HD1 , &HD3 , &HD5 , &HD8
 Data &HDA , &HDC , &HDE , &HE0 , &HE2 , &HE4 , &HE6 , &HE8 , &HEA , &HEC , &HED , &HEF , &HF0 , &HF2 , &HF3 , &HF5
 Data &HF6 , &HF7 , &HF8 , &HF9 , &HFA , &HFB , &HFC , &HFC , &HFD , &HFE , &HFE , &HFF , &HFF , &HFF , &HFF , &HFF
 Data &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFE , &HFE , &HFD , &HFC , &HFC , &HFB , &HFA , &HF9 , &HF8 , &HF7
 Data &HF6 , &HF5 , &HF3 , &HF2 , &HF0 , &HEF , &HED , &HEC , &HEA , &HE8 , &HE6 , &HE4 , &HE2 , &HE0 , &HDE , &HDC
 Data &HDA , &HD8 , &HD5 , &HD3 , &HD1 , &HCE , &HCC , &HC9 , &HC7 , &HC4 , &HC1 , &HBF , &HBC , &HB9 , &HB6 , &HB3
 Data &HB0 , &HAE , &HAB , &HA8 , &HA5 , &HA2 , &H9F , &H9C , &H98 , &H95 , &H92 , &H8F , &H8C , &H89 , &H86 , &H83
 Data &H80 , &H7C , &H79 , &H76 , &H73 , &H70 , &H6D , &H6A , &H67 , &H63 , &H60 , &H5D , &H5A , &H57 , &H54 , &H51
 Data &H4F , &H4C , &H49 , &H46 , &H43 , &H40 , &H3E , &H3B , &H38 , &H36 , &H33 , &H31 , &H2E , &H2C , &H2A , &H27
 Data &H25 , &H23 , &H21 , &H1F , &H1D , &H1B , &H19 , &H17 , &H15 , &H13 , &H12 , &H10 , &H0F , &H0D , &H0C , &H0A
 Data &H09 , &H08 , &H07 , &H06 , &H05 , &H04 , &H03 , &H03 , &H02 , &H01 , &H01 , &H00 , &H00 , &H00 , &H00 , &H00
 Data &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H01 , &H01 , &H02 , &H03 , &H03 , &H04 , &H05 , &H06 , &H07 , &H08
 Data &H09 , &H0A , &H0C , &H0D , &H0F , &H10 , &H12 , &H13 , &H15 , &H17 , &H19 , &H1B , &H1D , &H1F , &H21 , &H23
 Data &H25 , &H27 , &H2A , &H2C , &H2E , &H31 , &H33 , &H36 , &H38 , &H3B , &H3E , &H40 , &H43 , &H46 , &H49 , &H4C
 Data &H4F , &H51 , &H54 , &H57 , &H5A , &H5D , &H60 , &H63 , &H67 , &H6A , &H6D , &H70 , &H73 , &H76 , &H79 , &H7C

 

A ideia mais simples seria criar uma interrupção do Timer, que a cada interrupção pegue um novo valor da tabela, e o coloque na saída do conversor, e já deixe que na próxima interrupção seja pego o próximo valor da tabela.

 

Olha que simples : Se quisermos gerar uma senóide de 20 Hz, composta de 256 amostragens, temos de criar uma interrupção a cada 20 X 256 = 5120 Hz.

 

Portanto, vamos programar o Timer1 , que é de 16 Bits, para gerar essa interrupção. Verificando o datasheet e fazendo as contas, vemos que usando o Prescaler = 1  se recarregarmos o TImer1 com o valor 62411 ele irá interromper no tempo exato.

 

Mas, dentro de nossa rotina de interrupção, a primeira coisa que temos de fazer é reiniciar o Timer1 para que haja uma nova interrupção !

 

Aqui é que entra um pouco de “prática” do programador :

 

No programa principal, eu não faço nada, simplesmente criei um loop infinito, ou seja, existe uma intrução Jump que aponta para ela mesma !

 

Assim, o programa principal fica travado, fazendo esse Jump, sendo que só sai desse loop quando ocorre uma interrupção.

 

De acordo com o datasheet, qualquer AVR demora no mínimo 4 ciclos de clock entre o instante da interrupção e a execução da rotina de interrupção.  

E existe um “MAS” : caso quando ocorra a interrupção a instrução sendo executada demore mais de um único ciclo, essa instrução será finalizada, e só então serão contado os 4 ciclos de clock. Oras, sabemos que dependendo do instante em que ocorra a interrupção podemos ter até 3 ciclos de clock a mais para que seja executada a primeira instrução da rotina de interrupção por causa da instrução tipo Jump no programa principal !

 

Assim, para ter um valor médio, eu assumi que na média temos sempre 2 instruções a mais, ou seja, estou considerando que o tempo para atender a minha interrupção é de 6 ciclos de clock !

 

Agora, olhando a rotina de interrupção, vemos a instrução TIMER1=62423.  

Ué, o que aconteceu aqui ????? Não era 62411 ????!!!!

 

Reparem que no Bascom a instrução Timer1=xxxx demora mais 6 ciclos de clock até ser finalizada ! Assim, eu tenho de diminuir o numero de ciclos de clock entre as interrupções para que os tempos estejam novamente corretos !

 

Fazendo as contas : 6 ciclos para a interrupção mais 6 ciclos para a nova contagem do Timer1 totalizam 12 ciclos de clock, portanto tenho de usar o novo valor de 62411 + 12 = 62423 !

 

Essa é a utilidade do Proteus !!!! Eu simplesmente acompanhei quantos ciclos de clock ocorriam até o término da instruçãp Timer1=xxx, e descobri no passo a passo do Proteus que são 6 ciclos de clock !

 

Agora que o mecanismo está explicado, reparem ALGO FUNDAMENTAL :

 

Mudando o valor do Timer, posso gerar a frrequência que eu quiser !!!

 

Por exemplo, reparem o programa DDS1.bas, ele tem o valor do Timer1 modificado para obter uma frequência de 30 Hz !

 

'----------------------------------------------------------------------
' GERADOR DE SENOIDE DE 30 HZ
' Este programa utiliza o Timer1 de 16 bits para a base de tempo
' onde a cada interrupt pegamos o próximo valor de uma tabela e
' colocamos na saída.
' Ilustra a técnica mais simples de gerar frequências.
'----------------------------------------------------------------------




$crystal = 16000000
$regfile = "m328def.dat"
$hwstack = 40
$swstack = 32
$framesize = 48

Dim Digitos As Byte

Dim X As Byte
Dim Y As Byte


Config Portd = Output





Config Timer1 = Timer , Prescale = 1
' valor teórico do Timer1
Timer1 = 63453
' vamos gerar 7681,23 interrupts por segundo.
'Como precisamos de 256 steps para completar um período de nossa forma de
'onda, teremos uma frequência na saída de 7681,23 / 256 = 30 Hz

On Timer1 Timer1_sub Nosave
Enable Timer1
Enable Interrupts
Restore Tabela
X = 0
' começaremos do numero 0
Do
' loop infinito, que no Assembly é traduzido por um simples Jump ao mesmo
' local, portanto a chance de ocorrer uma interrupção DENTRO do Jump é bem
' alta, assim consideramos que entre ocorrer uma interrupção e a primeira
' instrução da rotina de interrupçao vao se passar 6 ciclos em vez dos 4
' que normalmente são definidos
Loop



'------------- ROTINA DE INTERRUPÇÃO timer1 --------------------------------
Timer1_sub:
   'Rotina chamada pelo Timer1. Até ser reposto o valor do Timer vao se
   'passar na media 12 ciclos de clock desde o momento da interrupçao, portanto
   'vamos aumentar esses 12 ciclos do valor do contador.
   Timer1 = 63465
   'recarrega o timer novamente
   Read Y
   Portd = Y
  Incr X
  If X = 0 Then
      Restore Tabela
   End If
Return

End


' TABELA DE uma senóide com 256 pontos

Tabela:

 Data &H80 , &H83 , &H86 , &H89 , &H8C , &H8F , &H92 , &H95 , &H98 , &H9C , &H9F , &HA2 , &HA5 , &HA8 , &HAB , &HAE
 Data &HB0 , &HB3 , &HB6 , &HB9 , &HBC , &HBF , &HC1 , &HC4 , &HC7 , &HC9 , &HCC , &HCE , &HD1 , &HD3 , &HD5 , &HD8
 Data &HDA , &HDC , &HDE , &HE0 , &HE2 , &HE4 , &HE6 , &HE8 , &HEA , &HEC , &HED , &HEF , &HF0 , &HF2 , &HF3 , &HF5
 Data &HF6 , &HF7 , &HF8 , &HF9 , &HFA , &HFB , &HFC , &HFC , &HFD , &HFE , &HFE , &HFF , &HFF , &HFF , &HFF , &HFF
 Data &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFE , &HFE , &HFD , &HFC , &HFC , &HFB , &HFA , &HF9 , &HF8 , &HF7
 Data &HF6 , &HF5 , &HF3 , &HF2 , &HF0 , &HEF , &HED , &HEC , &HEA , &HE8 , &HE6 , &HE4 , &HE2 , &HE0 , &HDE , &HDC
 Data &HDA , &HD8 , &HD5 , &HD3 , &HD1 , &HCE , &HCC , &HC9 , &HC7 , &HC4 , &HC1 , &HBF , &HBC , &HB9 , &HB6 , &HB3
 Data &HB0 , &HAE , &HAB , &HA8 , &HA5 , &HA2 , &H9F , &H9C , &H98 , &H95 , &H92 , &H8F , &H8C , &H89 , &H86 , &H83
 Data &H80 , &H7C , &H79 , &H76 , &H73 , &H70 , &H6D , &H6A , &H67 , &H63 , &H60 , &H5D , &H5A , &H57 , &H54 , &H51
 Data &H4F , &H4C , &H49 , &H46 , &H43 , &H40 , &H3E , &H3B , &H38 , &H36 , &H33 , &H31 , &H2E , &H2C , &H2A , &H27
 Data &H25 , &H23 , &H21 , &H1F , &H1D , &H1B , &H19 , &H17 , &H15 , &H13 , &H12 , &H10 , &H0F , &H0D , &H0C , &H0A
 Data &H09 , &H08 , &H07 , &H06 , &H05 , &H04 , &H03 , &H03 , &H02 , &H01 , &H01 , &H00 , &H00 , &H00 , &H00 , &H00
 Data &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H01 , &H01 , &H02 , &H03 , &H03 , &H04 , &H05 , &H06 , &H07 , &H08
 Data &H09 , &H0A , &H0C , &H0D , &H0F , &H10 , &H12 , &H13 , &H15 , &H17 , &H19 , &H1B , &H1D , &H1F , &H21 , &H23
 Data &H25 , &H27 , &H2A , &H2C , &H2E , &H31 , &H33 , &H36 , &H38 , &H3B , &H3E , &H40 , &H43 , &H46 , &H49 , &H4C
 Data &H4F , &H51 , &H54 , &H57 , &H5A , &H5D , &H60 , &H63 , &H67 , &H6A , &H6D , &H70 , &H73 , &H76 , &H79 , &H7C

Se abrir o arquivo de simulação no Proteus 8, vai ver o funcionamento e comprovar a forma de onda na saída, bem como a frequência que está sendo gerada !

 

Isto que estamos fazendo já pode ser chamado de DDS, mas é o DDS “grosseiro” !

 

Infelizmente, tanto o Bascom como qualquer linguagem C tem a mesma limitação : tempos adicionais envolvidos na execução do programa, o que faz com que seja impossível obtermos frequências altas.

 

No início dos anos 2000, um cara chamado Jesper teve uma brilhante ideia : em vez de usar uma interrupção de um Timer, que envolve vários ciclos de clock para ser efetuada, ele fizesse algo semelhante em um loop principal de um programa ????

 

Ele imaginou uma série de somas de 8 bits, criando um número de 24 bits, e a parte alta desses 24 bits aponta exatamente para uma tabela de valores !

 

Se a nossa tabela tem 256 valores, bastam 8 bits para apontar para um valor nessa tabela, que serão exatamente os 8 bits mais altos.

 

E temos os 16 bits mais baixos para podermos controlar com uma precisão enorme o instante em que a soma gera um overflow, incrementando os 8 bits mais altos, e assim apontando para um novo valor na tabela !

 

Aí nasceu o atual DDS !

 

Segue em anexo o arquivo para a simulação no Proteus 8.

 

A seguir, apresentarei o moderno conceito de DDS utilizando menos de 10 bytes em Assembly, e que permite maravilhas na linha Atmega !

 

Paulo

DDS.pdsprj

  • Curtir 2
Link para o comentário
Compartilhar em outros sites

@test man*~ ,

 

Opa meu amigo, legal, o mais importante é entender todas as técnicas que estou apresentando ao longo destes artigos do analizador lógico!  Já tem muita coisa bem avançada apresentada, como a manipulação do Program Pointer mediante uma interrupção do Timer, que é uma sacada que permite velocidades enormes em um pequeno loop de captura de dados em Assembler, permitindo sair de um loop infinito e direcionando o programa pra onde quisermos, usando uma interrupção de um Timer com o tempo cuidadosamente calculado.

 

E agora quando eu postar o DDS utilizando um pequeno loop em Assembler, voce vai ficar impressionado como uma coisinha tão simples pode criar resultados tão maravilhosos !

 

Detalhe : sempre tem o Bascom envolvido para facilitar tudo, e tem um pequeno código em Asm que eu sempre tento explicar para que muitos percam o medo kkkk !

 

Eu estou ficando velho e ranzinza ( até pareço um certo membro antigo kkkkk ), levar esse conhecimento adquirido para a cova sem ao menos tentar passar para outros não é uma atitude digna. Portanto, ficará aqui disponível para quem precisar dele algum dia...

 

Paulo

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

Paulo, primeiramente parabéns e obrigado pela disposição e boa vontade em compartilhar todo esse material.

Acompanho o tópico desde o início, realmente muito bom.

Como colega disse acima, eu também não posso mais acompanhar e "brincar" com essas coisas como eu gostaria. Ainda compro algumas coisas, mas acho que por costume mesmo (rs), vai ficando tudo aqui parado, sem uso.

 

Achei muito legal esse projeto do analisador, gostaria de conseguir tempo pra montar um, só falta isso, os componentes acredito que já tenho.:huh:

 

Valeu!

Link para o comentário
Compartilhar em outros sites

@Fervolt ,

 

Obrigado, meu amigo, eu estou quase no final da descrição de funcionamento de cada um dos programinhas que irão rodar nesse pequeno aparelho.

 

Ainda não terminei o programa que integra todas as funções descritas, como os menus de configurações, parametrização, etc, sempre deixo isso para o final.

 

Se voce gosta disto tudo, não será difícil achar um tempinho livre para brincar com uma ou outra montagem, afinal a diversão é extremamente necessária para atenuar o nosso dia a dia, que está ficando cada vez mais estafante só de ver os noticiários sobre tanta corrupção e ninguém nunca é culpado de nada....

 

Enfim, seu comentário é mais um incentivo para eu continuar aqui postando projetos interessantes !

 

Paulo

 

 

Link para o comentário
Compartilhar em outros sites

Projeto de um Analizador Lógico Portátil com Atmega1284P - Parte 9 - GERADOR DE FORMAS DE ONDA POR SOFTWARE USANDO DDS

 

PRINCIPIO DE FUNCIONAMENTO DE UM DDS MODERNO

 

Agora, vamos ver como funciona o tal de DDS real.

 

Imagine que temos uma tabela com 256 valores . Esses valores podem ir de 0 até 255, e representam a amplitude da forma de onda em cada instante.

 

Se quisermos gerar uma senoide, entre o inicio dela e o final teremos 256 possíveis valores. Dá para perceber que desta maneira poderemos fazer a nossa forma de onda com uma boa precisão, sem aquele "serrilhado", pois a variação será bem contínua.

 

Agora, imagine que temos um pequeno trecho de programa que some dois números. Vamos estipular que o maior valor dessa soma terá 24 bits ( 2^24).

 

A esse resultado de 24 bits daremos o nome de acumulador de fase. Na verdade, como estamos usando um microcontrolador de 8 bits, teremos esse resultado em um conjunto de três registradores de 8 bits cada um.

 

O registrador que contém os 8 bits mais significativos será usado como o indexador da tabela de 256 valores que representam a forma de onda.

 

Assim, dá para perceber que se um dos números a serem somados for bem pequeno, vai demorar muito tempo para que a soma modifique os 8 bits mais significativos. Por outro lado, se um dos números for muito grande, os 8 bits mais significativos vão mudar muito rapidamente.

 

Essa "velocidade" de variação dos 8 bits mais significativos é que determina a frequência que será gerada !

 

A vantagem de trabalhar com um AVR é que ele executa essa soma de uma maneira muito rápida, isto é, em poucos ciclos de clock. Na verdade, o nosso loop de programa que gera a forma de onda é executado em apenas 8 ciclos de clock !

 

Aqui cabe uma observação : o programa original feito pelo Jesper mantinha a tabela com a forma de onda na memória Flash, e portanto o loop de execução demorava 9 ciclos de clock.

Eu tive uma ideia muito boa : manter a tabela na memória SRAM, e com isso ganhei 1 ciclo de clock, portanto meu loop é executado em 8 ciclos de clock, permitindo gerar frequências mais altas !

 

Assim, já podemos calcular a resolução de nosso gerador :

 

Resolução = Fclock / 8 / ( 2^24)   

 

Como nosso cristal neste exemplo é de 16 MHz, temos uma resolução aproximada de 0,11920928955078 Hertz !!!!!

 

Ou seja, podemos gerar qualquer frequência múltipla de 0,1192 Hertz. Claro que não conseguiremos gerar uma frequência exata em números inteiros, mas o erro será sempre muito pequeno, da ordem de décimos de hertz.

 

Agora, vamos ver o trecho de programa em Assembly que faz o loop :

 

'        r28,r29,r30 is the phase accumulator
'        r24,r25,r26 is the adder value determining frequency
'
'        add value to accumulator
'        load byte from current table in Ram
'        output byte to port
'        repeat
'
Loop2:
                add                r28,r24                  ' 1
                adc                r29,r25                  ' 1
                adc                r30,r26                  ' 1
                LD R0,Z                                     ' 2
                Out Portd , R0                              ' 1
                rjmp        LOOP2                           ' 2 => 8 cycles

 

Os registradores R28,R29 e R30 juntos compõem o Acumulador de Fase, e sempre iniciam com valor zero.

O numero que determina a frequência do sinal, chamado de numero de fase, estará nos registradores R24,R25 e R26.

 

O truque está em posicionar a nossa tabela sempre em um endereço múltiplo de 0100h; no meu caso posicionei em 0100h, e então precisamos fazer R31 = 1 . Porquê ?

 

Reparem a instrução LD R0,Z  . Z é o par de registradores R30 / R31 . Assim, sempre vamos carregar em R0 um dos valores de nossa tabela, o qual será determinado pelo índex R30. Uma vez carregado em R0, já colocaremos na porta de saída PORT.D, que é onde está o nosso conversor R/2R.

 

Reparem que fazendo R31=1 sempre carregaremos o valor entre as posições de memória 0100h e 01FFh , e o nosso loop é sempre executado em apenas 8 ciclos de clock.

 

Reparem o funcionamento do Loop : a cada 8 ciclos de clock, a soma é atualizada pelo valor de fase, e vai chegar o momento em que a contagem vai atualizar os 8 bits mais significativos, e nesse instante pegaremos um novo valor da tabela e será colocado na saída. É um exemplo de altíssima eficiência de um pequeno programa, e é justamente aí que reside a superioridade da linha AVR sobre a linha dos PICs.

 

Só resta uma coisa : como fazer o cálculo do numero de fase,  a ser colocado em R24, R25 e R26 ?

 

Basta fazermos uma conta :

 

Divida a frequência a ser gerada pela resolução de nosso gerador !!!

 

Por exemplo, para gerar 20 Khz, chegamos ao número  20000 / 0,11920928955078 = 167772,16 

Usamos o numero inteiro 167772, e vamos obter a frequência de 19.999,98 Hertz. Um erro de apenas 0,000096 % !

 

A seguir , postarei o programa completo, com os arquivos para a simulação no Proteus.

 

Paulo

 

Link para o comentário
Compartilhar em outros sites

Projeto de um Analisador Lógico Portátil com Atmega1284P - Parte 9 - GERADOR DE FORMAS DE ONDA POR SOFTWARE USANDO DDS

 

EXEMPLO DE UM GERADOR DE FORMAS DE ONDA POR DDS

 

Para facilitar o entendimento e até mesmo a montagem de quem quiser brincar com isso, usei um Arduíno Uno em vez do Atmega1284, e poderemos gerar formas de onda com frequência até 300 kHZ. Para uso em áudio, as formas de onda até 25 Khz são extremamente precisas, com baixa distorção.

 

O programa apresentado aqui, para ser realmente útil, ainda precisa ter algumas coisas acrescentadas, como por exemplo tratamento correto do display e um rotary encoder para permitir selecionar várias coisas como o tipo de forma de onda a ser gerada e a frequência dela. As chaves SW1 e SW2 mostradas no esquema não foram utilizadas no programa.

 

O Rotary Encoder será o responsável para interromper esse loop em Assembler, mediante uma interrupção gerada ao pressionar o botão do rotary, que manipularia o Program Counter e desviaria para um tipo de Menu.

 

O projeto final com o Atmega1284 terá além do descrito acima uma poderosa função onde poderemos "desenhar" a forma de onda que queremos criar, por exemplo :  informaremos a quantidade de pontos que precisaremos para desenhar a nossa forma de onda ( sempre um múltiplo de 16, tipo 16, 32, 64, 128 ou 256 pontos ), e através do rotary encoder informaremos o valor de cada um dos pontos; ao final, reescalamos essa forma de onda para gerar os 256 pontos da tabela.

 

Além dito, terá também uma função de varredura de frequência ( Sweep ) , podendo fazer o papel de um Sweep Generator. Esta função é facilmente implementada,mudando-se o valor a ser somado no Acumulador de fase de tempos em tempos mediante uma interrupção de um Timer. Assim poderemos gerar um Sweep, indo desde por exemplo de 20 Hz a 20 Khz em 5 segundos, ou indo de 1 Khz para 1,5 Khz em 50 mseg.

 

Eu deixei algumas tabelas prontas neste programa, para gerar as formas de onda mais comuns : Senoide, triangular, dente de serra, onda quadrada. Basta mudar uma linha no programa e você pode gerar qualquer uma dessas formas de onda.

 

Tem também uma função que gera a forma de onda sobre um capacitor quando ele se carrega ou se descarrega através de um resistor. É bem interessante de se mudar os valores de R e de C e ver o comportamento da forma de onda !

 

Deixei pronta também uma função que cria uma forma de onda tipo um pequeno impulso positivo ou negativo, por exemplo um sinal que fica normalmente em nível alto, cai para nível baixo durante um décimo do tempo, e volta para o nível alto os 90% restantes. O interessante é que podemos definir qual é o valor do nível alto, qual é o valor do nível baixo, e qual o percentual do sinal em cada nível, permitindo assim uma precisa geração de impulsos.

 

Neste projeto eu uso um pequeno display igual ao que uso no projeto do analisador, mas se você quiser pode montar sem o display.

 

Vamos ao programa :

 

$crystal = 16000000
$regfile = "m328def.dat"
$hwstack = 40
$swstack = 32

$norampz
Disable Interrupts

Dim Tabela_user(257) As Byte At &H0100
Dim X As Byte
Dim X1 As Byte
Dim X2 As Byte
Dim X3 As Byte
Dim Y As Word
Dim Tipo_gerador As Byte
Dim Frequencia As Single
Dim Temp1 As Single
Dim Temp2 As Single

Dim Impulso_freq As Single
Dim Impulso_largura As Byte
Dim Impulso_origem As Byte
Dim Impulso_amplitude As Byte
Dim Flag_impulso_updown As Bit


Dim Valor_rc As Single
Dim Periodos_rc As Byte
Dim Flag_cargarc As Bit
Dim Valor_word As Word


Dim Banda_sweep As Single
Dim Tempo_sweep As Word

Const Resolution = 0,11920928955078
Dim Valor As Long
Dim Dvalor As Dword
Dim Byte4 As Byte At Dvalor Overlay
Dim Byte3 As Byte At Dvalor + 1 Overlay
Dim Byte2 As Byte At Dvalor + 2 Overlay
Dim Byte1 As Byte At Dvalor + 3 Overlay

'*******************************************************************************
$lib "glcd-Nokia3310-M.lib"

Config Graphlcd = 128x64sed , A0 = Portc.3 , Si = Portc.4 , Sclk = Portc.5
' Rst & Cs1 is optional

Const Negative_lcd = 0                                      ' Inverting screen
Const Rotate_lcd = 0                                        ' Rotate screen to 180°

Dim I As Byte
Dim Teste As String * 14

'***************************** Program *****************************************

Initlcd

Config Portd = Output

'--------------------------------------------------
' estas definições não sau usadas ainda....
Sw1 Alias Pinb.4
Sw2 Alias Pinb.5
Re1 Alias Pinc.0
Re2 Alias Pinc.1
Resw Alias Pinc.2


Config Sw1 = Input
Config Sw2 = Input
Config Re1 = Input
Config Re2 = Input
Config Resw = Input

Sw1 = 1
Sw2 = 1
Re1 = 1
Re2 = 1
Resw = 1
'--------------------------------------------------
Cls
Setfont Font6x8
Lcdat 1 , 1 , " Gerador DDS "


Frequencia = 20000

'Aqui definimos o tipo de forma de onda que vamos gerar
'1 =  Senoide
'2 = Onda Quadrada
'3 = Onda Triangular
'4 = Onda Dente de Serra ( Rampa )
'5 = Carga ou descarga RC
'6 = Onda tipo Impulso positivo ou negativo

Tipo_gerador = 1

If Tipo_gerador = 1 Then
  Restore Seno
  Gosub Copy_tabela
Elseif Tipo_gerador = 2 Then
  Restore Square
  Gosub Copy_tabela
Elseif Tipo_gerador = 3 Then
  Restore Triangle
  Gosub Copy_tabela
Elseif Tipo_gerador = 4 Then
  Restore Sawtooth
  Gosub Copy_tabela
Elseif Tipo_gerador = 5 Then
  Valor_rc = 0.00001                                        ' R x C 
  Periodos_rc = 15
  Flag_cargarc = 1                                          ' vamos gerar a carga
  Gosub Cria_tabela_rc
Elseif Tipo_gerador = 6 Then
  Impulso_freq = 10000
  Impulso_largura = 5                                       'largura de 5%
  Impulso_origem = 90                                       'pulso no final da forma de onda ( 90% )
  Impulso_amplitude = 50                                    'amplitude do pulsos é de 50%
  Flag_impulso_updown = 0                                   '0 = pulso negativo
  Gosub Cria_impulso
End If


Temp1 = Frequencia / Resolution
Temp1 = Temp1 + 0.500001
Valor = Int(temp1)
Dvalor = Valor
Lcdat 2 , 3 , Dvalor
!call _send_buffer


Code_for_sram:
R28 = 0
R29 = 0
R31 = 1                                                     'determina a tabela
R30 = 0
R24 = Byte4
R25 = Byte3
R26 = Byte2

$asm
' main loop
'        16mhz sram    Resolution = 0,11920928955078
'        r28,r29,r30 is the phase accumulator
'        r24,r25,r26 is the adder value determining frequency
'
'        add value to accumulator
'        load byte from current table in Ram
'        output byte to port
'        repeat
'
Loop2:
                add                r28,r24                  ' 1
                adc                r29,r25                  ' 1
                adc                r30,r26                  ' 1
                LD R0,Z                                     ' 2
                Out Portd , R0                              ' 1
                rjmp        LOOP2                           ' 2 => 8 cycles

$end Asm


Copy_tabela:
For Y = 1 To 256
 Read X
 Tabela_user(y) = X
Next
Return

Cria_tabela_rc:
Temp1 = Valor_rc * Periodos_rc
Frequencia = 1 / Temp1
If Frequencia < 1 Then
  Lcdat 5 , 1 , "              "
  !call _send_buffer
  Lcdat 5 , 1 , "DIMINUA RC"
Elseif Frequencia > 100000 Then
  Lcdat 5 , 1 , "              "
  !call _send_buffer
  Lcdat 5 , 1 , "AUMENTE RC"
Else
  Frequencia = Int(frequencia)
  Valor = Frequencia
  Dvalor = Valor
  Lcdat 5 , 1 , "              "
  Rcall _send_buffer
  Lcdat 5 , 1 , "Freq="
  rcall _send_buffer
  Lcdat 5 , 37 , Valor
End If
Rcall _send_buffer
For Y = 1 To 256
  Temp2 = 1 / Frequencia
  Decr Y
  Temp2 = Temp2 * Y
  Temp2 = Temp2 / 255
  Temp2 = -1 * Temp2
  Temp2 = Temp2 / Valor_rc
  Temp2 = Exp(temp2)
  If Flag_cargarc = 1 Then
    Temp2 = 1 - Temp2
  End If
  Temp2 = 255 * Temp2
  Incr Y
  Valor_word = Temp2
  Tabela_user(y) = Low(valor_word)
Next
Return

Cria_impulso:
Temp1 = Impulso_origem * 255
Temp1 = Temp1 + 0.01
Temp1 = Temp1 / 100
Valor_word = Int(temp1)
' valor_word é a posição na tabela onde começa o impulso
X1 = Low(valor_word)
Temp1 = Impulso_largura * 255
Temp1 = Temp1 + 0.01
Temp1 = Temp1 / 100
Valor_word = Int(temp1)
'valor_word é a largura da tabela com o valor do impulso
X2 = Low(valor_word)
Temp1 = Impulso_amplitude * 255
Temp1 = Temp1 + 0.01
Temp1 = Temp1 / 100
Valor_word = Int(temp1)
' valor_word é a amplitude do impulso
X3 = Low(valor_word)
' agora, vamos preencher a tabela

For Y = 1 To 256
  If Flag_impulso_updown = 1 Then
    Tabela_user(y) = 0
  Else
    Tabela_user(y) = 255
  End If
Next
'pronto, tabela preenchida com o valor normal sem o impulso
X = X1 + X2
Decr X
For Y = X1 To X
  If Flag_impulso_updown = 1 Then
    Tabela_user(y) = X3
  Else
    Tabela_user(y) = 255 - X3
  End If
Next
Frequencia = Impulso_freq
  Valor = Frequencia
  Dvalor = Valor
  Lcdat 5 , 1 , "              "
  Rcall _send_buffer
  Lcdat 5 , 1 , "Freq="
  rcall _send_buffer
  Lcdat 5 , 37 , Valor
Return

End

'------------ seguem as tabelas pre-definidas ----------------------
Seno:
 Data &H80 , &H83 , &H86 , &H89 , &H8C , &H8F , &H92 , &H95 , &H98 , &H9C , &H9F , &HA2 , &HA5 , &HA8 , &HAB , &HAE
 Data &HB0 , &HB3 , &HB6 , &HB9 , &HBC , &HBF , &HC1 , &HC4 , &HC7 , &HC9 , &HCC , &HCE , &HD1 , &HD3 , &HD5 , &HD8
 Data &HDA , &HDC , &HDE , &HE0 , &HE2 , &HE4 , &HE6 , &HE8 , &HEA , &HEC , &HED , &HEF , &HF0 , &HF2 , &HF3 , &HF5
 Data &HF6 , &HF7 , &HF8 , &HF9 , &HFA , &HFB , &HFC , &HFC , &HFD , &HFE , &HFE , &HFF , &HFF , &HFF , &HFF , &HFF
 Data &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFE , &HFE , &HFD , &HFC , &HFC , &HFB , &HFA , &HF9 , &HF8 , &HF7
 Data &HF6 , &HF5 , &HF3 , &HF2 , &HF0 , &HEF , &HED , &HEC , &HEA , &HE8 , &HE6 , &HE4 , &HE2 , &HE0 , &HDE , &HDC
 Data &HDA , &HD8 , &HD5 , &HD3 , &HD1 , &HCE , &HCC , &HC9 , &HC7 , &HC4 , &HC1 , &HBF , &HBC , &HB9 , &HB6 , &HB3
 Data &HB0 , &HAE , &HAB , &HA8 , &HA5 , &HA2 , &H9F , &H9C , &H98 , &H95 , &H92 , &H8F , &H8C , &H89 , &H86 , &H83
 Data &H80 , &H7C , &H79 , &H76 , &H73 , &H70 , &H6D , &H6A , &H67 , &H63 , &H60 , &H5D , &H5A , &H57 , &H54 , &H51
 Data &H4F , &H4C , &H49 , &H46 , &H43 , &H40 , &H3E , &H3B , &H38 , &H36 , &H33 , &H31 , &H2E , &H2C , &H2A , &H27
 Data &H25 , &H23 , &H21 , &H1F , &H1D , &H1B , &H19 , &H17 , &H15 , &H13 , &H12 , &H10 , &H0F , &H0D , &H0C , &H0A
 Data &H09 , &H08 , &H07 , &H06 , &H05 , &H04 , &H03 , &H03 , &H02 , &H01 , &H01 , &H00 , &H00 , &H00 , &H00 , &H00
 Data &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H01 , &H01 , &H02 , &H03 , &H03 , &H04 , &H05 , &H06 , &H07 , &H08
 Data &H09 , &H0A , &H0C , &H0D , &H0F , &H10 , &H12 , &H13 , &H15 , &H17 , &H19 , &H1B , &H1D , &H1F , &H21 , &H23
 Data &H25 , &H27 , &H2A , &H2C , &H2E , &H31 , &H33 , &H36 , &H38 , &H3B , &H3E , &H40 , &H43 , &H46 , &H49 , &H4C
 Data &H4F , &H51 , &H54 , &H57 , &H5A , &H5D , &H60 , &H63 , &H67 , &H6A , &H6D , &H70 , &H73 , &H76 , &H79 , &H7C


Sawtooth:                                                   ' 256 sawtoothwave table
Data &H00 , &H01 , &H02 , &H03 , &H04 , &H05 , &H06 , &H07 , &H08 , &H09 , &H0A , &H0B , &H0C , &H0D , &H0E , &H0F
Data &H10 , &H11 , &H12 , &H13 , &H14 , &H15 , &H16 , &H17 , &H18 , &H19 , &H1A , &H1B , &H1C , &H1D , &H1E , &H1F
Data &H20 , &H21 , &H22 , &H23 , &H24 , &H25 , &H26 , &H27 , &H28 , &H29 , &H2A , &H2B , &H2C , &H2D , &H2E , &H2F
Data &H30 , &H31 , &H32 , &H33 , &H34 , &H35 , &H36 , &H37 , &H38 , &H39 , &H3A , &H3B , &H3C , &H3D , &H3E , &H3F
Data &H40 , &H41 , &H42 , &H43 , &H44 , &H45 , &H46 , &H47 , &H48 , &H49 , &H4A , &H4B , &H4C , &H4D , &H4E , &H4F
Data &H50 , &H51 , &H52 , &H53 , &H54 , &H55 , &H56 , &H57 , &H58 , &H59 , &H5A , &H5B , &H5C , &H5D , &H5E , &H5F
Data &H60 , &H61 , &H62 , &H63 , &H64 , &H65 , &H66 , &H67 , &H68 , &H69 , &H6A , &H6B , &H6C , &H6D , &H6E , &H6F
Data &H70 , &H71 , &H72 , &H73 , &H74 , &H75 , &H76 , &H77 , &H78 , &H79 , &H7A , &H7B , &H7C , &H7D , &H7E , &H7F
Data &H80 , &H81 , &H82 , &H83 , &H84 , &H85 , &H86 , &H87 , &H88 , &H89 , &H8A , &H8B , &H8C , &H8D , &H8E , &H8F
Data &H90 , &H91 , &H92 , &H93 , &H94 , &H95 , &H96 , &H97 , &H98 , &H99 , &H9A , &H9B , &H9C , &H9D , &H9E , &H9F
Data &HA0 , &HA1 , &HA2 , &HA3 , &HA4 , &HA5 , &HA6 , &HA7 , &HA8 , &HA9 , &HAA , &HAB , &HAC , &HAD , &HAE , &HAF
Data &HB0 , &HB1 , &HB2 , &HB3 , &HB4 , &HB5 , &HB6 , &HB7 , &HB8 , &HB9 , &HBA , &HBB , &HBC , &HBD , &HBE , &HBF
Data &HC0 , &HC1 , &HC2 , &HC3 , &HC4 , &HC5 , &HC6 , &HC7 , &HC8 , &HC9 , &HCA , &HCB , &HCC , &HCD , &HCE , &HCF
Data &HD0 , &HD1 , &HD2 , &HD3 , &HD4 , &HD5 , &HD6 , &HD7 , &HD8 , &HD9 , &HDA , &HDB , &HDC , &HDD , &HDE , &HDF
Data &HE0 , &HE1 , &HE2 , &HE3 , &HE4 , &HE5 , &HE6 , &HE7 , &HE8 , &HE9 , &HEA , &HEB , &HEC , &HED , &HEE , &HEF
Data &HF0 , &HF1 , &HF2 , &HF3 , &HF4 , &HF5 , &HF6 , &HF7 , &HF8 , &HF9 , &HFA , &HFB , &HFC , &HFD , &HFE , &HFF


Triangle:                                                   '256  Trianglewave Table
Data &H00 , &H02 , &H04 , &H06 , &H08 , &H0A , &H0C , &H0E , &H10 , &H12 , &H14 , &H16 , &H18 , &H1A , &H1C , &H1E
Data &H20 , &H22 , &H24 , &H26 , &H28 , &H2A , &H2C , &H2E , &H30 , &H32 , &H34 , &H36 , &H38 , &H3A , &H3C , &H3E
Data &H40 , &H42 , &H44 , &H46 , &H48 , &H4A , &H4C , &H4E , &H50 , &H52 , &H54 , &H56 , &H58 , &H5A , &H5C , &H5E
Data &H60 , &H62 , &H64 , &H66 , &H68 , &H6A , &H6C , &H6E , &H70 , &H72 , &H74 , &H76 , &H78 , &H7A , &H7C , &H7E
Data &H80 , &H82 , &H84 , &H86 , &H88 , &H8A , &H8C , &H8E , &H90 , &H92 , &H94 , &H96 , &H98 , &H9A , &H9C , &H9E
Data &HA0 , &HA2 , &HA4 , &HA6 , &HA8 , &HAA , &HAC , &HAE , &HB0 , &HB2 , &HB4 , &HB6 , &HB8 , &HBA , &HBC , &HBE
Data &HC0 , &HC2 , &HC4 , &HC6 , &HC8 , &HCA , &HCC , &HCE , &HD0 , &HD2 , &HD4 , &HD6 , &HD8 , &HDA , &HDC , &HDE
Data &HE0 , &HE2 , &HE4 , &HE6 , &HE8 , &HEA , &HEC , &HEE , &HF0 , &HF2 , &HF4 , &HF6 , &HF8 , &HFA , &HFC , &HFE
Data &HFF , &HFD , &HFB , &HF9 , &HF7 , &HF5 , &HF3 , &HF1 , &HEF , &HEF , &HEB , &HE9 , &HE7 , &HE5 , &HE3 , &HE1
Data &HDF , &HDD , &HDB , &HD9 , &HD7 , &HD5 , &HD3 , &HD1 , &HCF , &HCF , &HCB , &HC9 , &HC7 , &HC5 , &HC3 , &HC1
Data &HBF , &HBD , &HBB , &HB9 , &HB7 , &HB5 , &HB3 , &HB1 , &HAF , &HAF , &HAB , &HA9 , &HA7 , &HA5 , &HA3 , &HA1
Data &H9F , &H9D , &H9B , &H99 , &H97 , &H95 , &H93 , &H91 , &H8F , &H8F , &H8B , &H89 , &H87 , &H85 , &H83 , &H81
Data &H7F , &H7D , &H7B , &H79 , &H77 , &H75 , &H73 , &H71 , &H6F , &H6F , &H6B , &H69 , &H67 , &H65 , &H63 , &H61
Data &H5F , &H5D , &H5B , &H59 , &H57 , &H55 , &H53 , &H51 , &H4F , &H4F , &H4B , &H49 , &H47 , &H45 , &H43 , &H41
Data &H3F , &H3D , &H3B , &H39 , &H37 , &H35 , &H33 , &H31 , &H2F , &H2F , &H2B , &H29 , &H27 , &H25 , &H23 , &H21
Data &H1F , &H1D , &H1B , &H19 , &H17 , &H15 , &H13 , &H11 , &H0F , &H0F , &H0B , &H09 , &H07 , &H05 , &H03 , &H01

Square:                                                     ' 256 Squarewave Table
Data &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00
Data &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00
Data &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00
Data &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00
Data &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00
Data &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00
Data &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00
Data &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00 , &H00
Data &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF
Data &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF
Data &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF
Data &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF
Data &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF
Data &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF
Data &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF
Data &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF , &HFF

$include "font6x8.font"

 

Reparem a estrutura abaixo:

Dim Dvalor As Dword
Dim Byte4 As Byte At Dvalor Overlay
Dim Byte3 As Byte At Dvalor + 1 Overlay
Dim Byte2 As Byte At Dvalor + 2 Overlay
Dim Byte1 As Byte At Dvalor + 3 Overlay

Criamos uma variável Dvalor que pode receber numero inteiro de 32 bits, para receber o nosso numero de fase que na verdade tem apenas 24 bits.

 

Para facilitar a nosso programa em Assembler ter acesso aos 4 bytes que compõem essa variável, criamos um Overlay, isto é, criamos 4 novas variáveis do tipo Byte para podermos acessar cada um dos 4 bytes individualmente, e é justamente o que nosso programa em Assembly faz , acessando apenas os três bytes que formam os 24 bits e fazendo a soma ao acumulador de fase.

 

Outro truque legal : 

Dim Tabela_user(257) As Byte At &H0100

Aloquei a tabela que vai ser utilizada pela rotina em Assembly na memória Ram, iniciando exatamente no endereço 0100h. Tem de ser sempre em um endereço múltiplo de 0100H para podermos pegar os 256 valores da tabela.

 

Esse recurso de facilitar o intercambio de valores de variáveis entre o Bascom e um programa em Assembly é um dos mais poderosos recursos do Bascom, que o diferencia de todos os outros compiladores Basic.

 

Se repararem bem, do jeito que está , o programa gera uma onda senoidal, de frequência 20 Khz, com amplitude variando entre 0 e 5 Volts.

 

Para gerar outras formas de onda, basta mudar o valor da variável Tipo_gerador .

 

No caso das formas de onda RC, temos de informar o valor da constante RC, definir quantos períodos de tempo RC iremos mostrar, e também definir se queremos a carga ou descarga. Experimentem variar para ver como muda a forma de onda gerada !

 

A seguir, segue o esquema e algumas telas da simulação :

 

2v3ftb8.jpg

 

2ptzbyc.jpg

 

k2ya15.jpg

 

1qpf9z.jpg

 

Seguem em anexo os arquivos para a simulação do Proteus, bem como o fonte do programa , e vários objetos já compilados para poder ver a simulação de várias formas de onda.

 

Paulo

 

 

 

DDS3.rar

  • Curtir 2
Link para o comentário
Compartilhar em outros sites

  • 2 semanas depois...
  • Membro VIP

Vi que o chicão comentou e resolvi passar por aqui kk.

 

bacana Paulão! Me fale se a essência está aqui...

Loop2:
                add                r28,r24                  ' 1
                adc                r29,r25                  ' 1
                adc                r30,r26                  ' 1
                LD R0,Z                                     ' 2
                Out Portd , R0                              ' 1
                rjmp        LOOP2                           ' 2 => 8 cycles

 

e como é a conexão com a tabela, algum destes regs é o index dela? e .. como sai do loop? Interrupt?

Fale + muito + sobre! Caso entenda, penso em fazer um alternativo em c... quando me aposentar kk

 

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

  • Membro VIP

@aphawk

Grato pelo "mestre", mas neste caso, o aluno ultrapassou o mestre nos AVRs.

Não falei antes, mas estou aprendendo "a velejar em aguas" dos 32bits, tipo raspberry e teensy

Quando tiver algo pronto postarei.

O código que a

@Isadora Ferraz  postou acima não está formatado como está no bascom. Fica mais fácil de entender um print screen desta parte em ASM.

2016-03-09_192527.jpg.ef2d56e57c47d96360

 

Aliás, faz sentido a questão.

Não encontrei nem a chamada e nem uma instrução condicional de saída deste loop. Não teria que ter um contador até 8 pra sair do loop?

Link para o comentário
Compartilhar em outros sites

@Isadora Ferraz  e @_xyko_ ,

 

No projeto completo, quando eu aperto o push-button do rotary switch, gero uma interrupção, e nela manipulo o stack pointer, e jogo de volta para qualquer trecho de programa que eu queira, basta consultar uma tabelinha de flags para eu saber qual tipo de instrumento estava rodando, e saber para qual novo Menu transferir. É o mesmo princípio que usei na parte do analisador que faz a captura de dados na velocidade mais alta possível, só que naquele caso quem interrompe é uma interrupção gerada pelo Timer, o qual eu programei para interromper no instante exato logo após a captura do último dado no buffer de captura.

 

Se eu usar algum condicional, vou perder tempo nos testes dentro do loop, e assim a performance vai cair bastante, tornando o instrumento pouco versátil.

 

No programa que postei, primeiro preparo a tabela que desejo gerar, calculo os valores a serem somados, e já posso rodar o loop direto. Não implementei nesse programa a interrupção que faz sair do loop.

 

Sobre o índex da tabela, se repararem logo no início do programa, eu criei uma tabela de 257 elementos ( embora só use 256... sabe como é, não custa nada garantir que alguma conta minha possa estar errada de 1 kkkk ) e fixei o endereço do primeiro elemento dela em 0100h. E reparem que eu carreguei o registrador R31 com o valor 01h, e não mudo nunca esse valor.

 

Resumindo a resposta para a Isadora, R30 é o index da tabela. E a saida do loop tem de ser feita por uma interrupt, a qual não implementei nesse programa, mas estará presente ao final do projeto do analisador.

 

Nos AVRs, o par de registradores R30/R31 podem ser usados como um registrador de 16bits, ( o mesmo ocorre com os registradores R26/27 e R28/R29, chamados respectivamente de X e Y ) muito útil para servir de índex, e nesse caso é chamado de registrador Z. Só falta explicar de onde vem o valor de R30 .... que é  justamente o byte mais alto da soma que é obtida a cada execução do loop , no caso ele tem os 8 bits mais altos da soma que tem no total 24 bits. Toda vez que ocorrer um overflow na soma dos primeiros 16 bits, o registrador R30 é incrementado, idealmente de uma unidade, mas quanto mais alta a frequência desejada, pode acontecer dele ser incrementado até em várias dezenas a cada loop de soma. Assim, basta carregar no registrador R0 o valor no endereço apontado por R30/R31, que sempre será um endereço entre 0100h e 01ffh ( pois fixei R31=1 ), que abrange os 256 elementos da tabela !

 

O loop total consome 8 ciclos de clock apenas.

 

Reparem que toda a estrutura do programa em Basic serve apenas para carregar ou criar as tabelas que desejamos, e claro, definir o valor a ser somado a cada loop nos registradores R24/R26/R26 ( podendo ter até 24 bits ) em função da frequência que desejamos obter no DDS.

 

O que me impressionou é a elegância do loop, ou melhor, a simplicidade da ideia do autor, a minha contribuição foi apenas a de basear a matriz da tabela na memória SRAM, pois o autor e todos os outros que seguiram usavam a tabela armazenada na memória Flash de programa, e a carga desse valor consome 3 ciclos, enquanto a da Ram consome apenas 2. É um ganho de 9/8 = mais de 10% de velocidade maior.

 

Não sei se consegui me explicar kkkk podem perguntar mais ok ?

 

Paulo

 

 

 

 

 

 

Link para o comentário
Compartilhar em outros sites

  • Membro VIP

hã? kk

 

se não sai do loop, como mudou as formas de onda?

e isso...

definir o valor a ser somado a cada loop nos registradores R24/R26/R26 ( podendo ter até 24 bits ) em função da frequência que desejamos obter no DDS.

controla a frequência ou resolução de 24bits? ou os 2?

Ah .. agora que vi isso...

' main loop
'        16mhz sram    Resolution = 0,11920928955078
'        r28,r29,r30 is the phase accumulator
'        r24,r25,r26 is the adder value determining frequency
'
'        add value to accumulator
'        load byte from current table in Ram
'        output byte to port
'        repeat

o que é um acumulador de fase? (Já sei o que está pensando... pra eu ler os conceitos do dds)

 

eu tenho + perguntas... + tarde eu faço ou não.

Link para o comentário
Compartilhar em outros sites

5 horas atrás, Isadora Ferraz disse:

hã? kk

 

 

kkk o loop vai sempre pegando os valores que estão na tabela. Se eu mudar os valores da tabela, mudo completamente a forma de onda, certo ?  Só não muda a frequência... que para alterar, tem de mudar o tal de valor de fase, que está nos registros R24/R26/R26.

 

No programa que postei, isso tudo é feito ANTES de chegar no loop... 

 

A resolução é definida pela velocidade do loop e pelo tamanho do acumulador de fase. Se você olhar a teoria que eu postei pouco antes do artigo do programa, vai entender a conta que foi feita para o cálculo da resolução.

 

O tal do acumulador de fase são os 3 registradores que vão sempre sendo incrementados ( R28/R29/R30 ) , e o indexador da tabela são os dois registradores R30/R31.

 

Pode perguntar que eu não mordo ! ( ainda... )

 

Paulo

 

 

Link para o comentário
Compartilhar em outros sites

  • mês depois...

:) muito bom... Até me atiçou a fazer novos projetinhos mas o tempo não ajuda :( HAHA... Então vou apenas compartilhar o último que fiz antes do tempo acabar... É uma comunicação I2C entre um PIC (mestre) e um AVR (escravo). Só antes, quero te agradecer de novo @aphawk  :thumbsup: por ter me introduzido aos AVRs.


Código para o PIC foi feito com o CCS C e para o AVR com o BASCOM AVR.

 

O AVR lê a temperatura de um LM35 e possui dois LEDs conectados a ele, o PIC lê a temperatura do AVR e controla o estado dos leds via comandos serial (digitar l11 liga o led 1 e l10 desligá-lo; l21 para ligar o led 2 e l20 para desligá-lo...)

 

A biblioteca para TWI Slave do BASCOM AVR era um produto a parte então fiz o código escrevendo nos registradores mesmo, o TWI do AVR é bem bacana, seria tranquilo fazer ele funcionar como um multimestre ou hora como escravo e hora como mestre.

 

Usando o "clock stretching" deu para fazer a comunicação I2C a 1MHz.

 

A comunicação fica assim:
srKjTPx.png

 

Circuito:

WdlnIkF.jpg

 

 

Código mestre:

Spoiler

#include <16F883.h>
#device adc=8

#FUSES NOWDT                    //No Watch Dog Timer
#FUSES INTRC_IO                 //Internal RC Osc, no CLKOUT
#FUSES PUT                      //Power Up Timer
#FUSES MCLR                     //Master Clear enabled
#FUSES NOPROTECT                //Code not protected from reading
#FUSES NOCPD                    //No EE protection
#FUSES BROWNOUT                 //Reset when brownout detected
#FUSES NOIESO                   //Internal External Switch Over mode disabled
#FUSES NOFCMEN                  //Fail-safe clock monitor disabled
#FUSES NOLVP                    //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NODEBUG                  //No Debug mode for ICD
#FUSES NOWRT                    //Program memory not write protected
#FUSES BORV21                   //Brownout reset at 2.1V

#use delay(clock=8000000)
#use rs232(baud=115200,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)//Baud rate de 115200 funcionou perfeitamente
#use i2c(Master, Fast = 1000000 ,sda=PIN_C4, scl=PIN_C3, FORCE_HW)
//Fast = 1000000 (1MHz)

#define LED PIN_A0

char string[3];
short dados_recebidos_serial=;

#define avr_slave_write_address 0x12
#define avr_slave_read_address  0x13
#include <AVR_Slave_I2C.c>

#int_RDA
void  RDA_isr(void) {
   gets(string);
   dados_recebidos_serial=1;
}

void main() {
   short LED1=, LED2=;
   int temp_int=, temp_frac=, error=, status=;
     
   setup_adc_ports(NO_ANALOGS|VSS_VDD);
   setup_adc(ADC_OFF);
   
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
   setup_timer_1(T1_DISABLED);
   setup_timer_2(T2_DISABLED,,1);
 
   setup_comparator(NC_NC_NC_NC);
   
   setup_oscillator(OSC_8MHZ);
   
   clear_interrupt(INT_RDA);
   enable_interrupts(INT_RDA);
   enable_interrupts(GLOBAL);

   output_high(LED);
   delay_ms(500);                                       //Espera o slave se estabilizar
   
   while(true) { 
      //Lendo o buffer do AVR slave
      ler_buffer_slave(temp_int, temp_frac, LED1, LED2, error);
      
      //Pisca LED
      output_toggle(LED);
      
      //Se a comunicação ocorreu Ok mostra os valores lidos do slave
      if(error==)
        printf("\fLed 1: %u\n\rLed 2: %u\n\rTemperatura: %02u,%01u", LED1, LED2, temp_int, temp_frac);
        
        else
          printf("\fFalha na comunicacao, slave nao respondeu ao mestre.");
      //Se algum comando serial foi recebido
      if(dados_recebidos_serial==1) {
         dados_recebidos_serial=;
         string[]=toupper(string[]);
         
         if((string[]=='L')&&(string[1]=='1')) {         //Comando L10 = Desligar LED1                                                     
            switch(string[2]) {                           //Comando L11 = Ligar LED1
               case '1':
                  printf("\fComando L11: Ligar LED 1.");
                  status=escrever_buffer_slave(1, LED2);
                  if(status==) {
                    LED1=1;
                    printf("\n\rLED1 atualizado.");
                  }
                    else
                      printf("\n\rAVR slave nao respondeu ao mestre.\nLED1 nao atualizado.");
               break;
               
               case '0':
                  printf("\fComando L10: Desligar LED 1.");
                  status=escrever_buffer_slave(, LED2); 
                  if(status==) {
                    LED1=;
                    printf("\n\rLED1 atualizado.");
                  }  
                    else
                      printf("\n\rAVR slave nao respondeu ao mestre.\nLED1 nao atualizado.");  
               break;
               
               default:
                  printf("\fComando nao reconhecido.");
               break;   
            }
         }
         
         else if((string[]=='L')&&(string[1]=='2')) {    //Comando L20 = Desligar LED2                                                      
            switch(string[2]) {                           //Comando L21 = ligar LED2
               case '1':
                  printf("\fComando L21: Ligar LED 2.");
                  status=escrever_buffer_slave(LED1, 1);
                  if(status==) {
                    LED2=1;
                    printf("\n\rLED2 atualizado.");
                  }
                    else
                      printf("\n\rAVR slave nao respondeu ao mestre.\nLED2 nao atualizado."); 
               break;
               
               case '0':
                  printf("\fComando L20: Desligar LED 2.");
                  status=escrever_buffer_slave(LED1, ); 
                  if(status==) {
                    LED2=;
                    printf("\n\rLED2 atualizado.");
                  }
                    else
                      printf("\n\rAVR slave nao respondeu ao mestre.\nLED2 nao atualizado.");
               break;
               
               default:
                  printf("\fComando nao reconhecido.");
               break;   
            }         
         }
         delay_ms(1500);                                //tempo para o usuário ver a mensagem do comando
      }    
      delay_ms(500);                                    //pede os dados de 0,5 em 0,5 segundos
   }
}

 

 

Código escravo:

Spoiler

'------------------------ Modelo do AVR e velocidade de clock ------------------------'
$regfile = "m2560def.dat"                                   ' ATmega2560
$crystal = 16000000                                         ' Crystal Oscilador externo 16MHz (configurado nos Fuses)

'--------------------------- Configuração dos periféricos ----------------------------'
Config Adc = Single , Prescaler = Auto , Reference = Internal_1.1
' Single = Uma medição quando solicitado
' Referencia interna de 1,1V

Start Adc
' Now give power to the chip
' With STOP ADC, you can remove the power from the chip
' Stop Adc

'------------------------- Configuração da direção dos pinos -------------------------'
Config Pinb.7 = Output                                      ' PB5 saída
Config Pinc.0 = Output                                      ' PB4 saída
Config Pinc.2 = Output                                      ' PB3 saída

Config Pinf.0 = Input
'Didr1.0 = 1                                                 ' Desabilitando entrada digital no pino AN0

'----------------------------------- Nomeando pinos ----------------------------------'
Led1 Alias Portc.0                                          ' Le10 conectado ao PC0
Led2 Alias Portc.2                                          ' Led2 conectado ao PC2
Led_onboard Alias Pinb.7                                    ' Led na placa do arduino

'------------------------ Inicialização dos estados dos pinos ------------------------'
Led1 = 0                                                    ' Inicializa o Led1 desligado
Led2 = 0                                                    ' Inicializa o Led2 desligado

'------------------------------ Declaração de variáveis ------------------------------'
Dim Buffer(4) As Byte
' Este é o buffer que o PIC mestre irá ler e escrever
' Buffer(1) -> Temperatura parte inteira   | R (A escrita é realizada mas o AVR escreve por cima)
' Buffer(2) -> Temperatura parte fracional | R (A escrita é realizada mas o AVR escreve por cima)
' Buffer(3) -> Estado do Led1              | R/W
' Buffer(4) -> Estado do Led2              | R/W

Dim Leitura As Word , Temperatura As Single
' Variáveis usadas no programa principal

Dim Index As Byte , Buffer_address As Byte
' Variáveis usadas na innterrupção TWI

'---------------------------- Inicialização de variáveis -----------------------------'

'----------------------------- Declaração de constantes ------------------------------'
Const Avr_slave_address = &H12                              ' 0x12 (0x12 = escrever | 0x013 = Ler)

'--------------------------------- Código principal ----------------------------------'
Gosub Twi_slave_initialize                                  ' A freq. de operação deste slave deverá ser 16 vezes maior
                                                            ' que a do clock SCL (se SCL = 400KHz então Freq > 16 * 400K)
Enable Interrupts

Do
  Leitura = Getadc(0)                                       ' Lê o canal zero
  ' 0°C = 0V = 0 | 40°C = 1,1V = 1023 | Ganho amplificador operacional (não inversor) 2,75.

  ' 1023    = 40          ___\ Temperatura * 1023 = Leitura * 40 -> Temperatura = Leitura * 40 /1023
  ' Leitura = Temperatura    /

  ' Temperatura = Leitura * 0,039

  Temperatura = Leitura * 0.039                             ' Temperatura float
  Buffer(1) = Temperatura                                   ' Buffer(1) = parte inteira da temperatura
  Temperatura = Temperatura - Buffer(1)                     ' Temperatura = parte fracionária apenas
  Temperatura = Temperatura * 10                            ' Temperatura = UM ÚNICO DÍGITO da parte fracionária
  Buffer(2) = Temperatura                                   ' Buffer(2) = um dígitop da parte fracionaria da temperatura

  If Buffer(3) = 1 Then                                     ' atualiza o LED1 de acordo com o valor no buffer
    Led1 = 1

    Else
      Led1 = 0
  End If

  If Buffer(4) = 1 Then                                     ' atualiza o LED2 de acordo com o valor no buffer
    Led2 = 1

    Else
      Led2 = 0
  End If

  Led_onboard = 1                                           ' Pisca o LED (que está na placa do arduino)
                                                            ' usando o registrador PINx
  Waitms 200

Loop

'-------------------------------------------------------------------------------------'
End

' --------------------------- Inicialização TWI SLAVE --------------------------------'
Twi_slave_initialize:
  Twar = Avr_slave_address                                  ' Endereço deste slave é 0001 001X (0x12)
                                                            ' 0x12 -> Escrever
                                                            ' 0x13 -> Ler

  Twcr.twie = 1
  Twcr.twea = 1                                             ' quando for endereçado este slave gerará o ack
  Twcr.twsta = 0
  Twcr.twsto = 0
  Twcr.twen = 1                                             ' habilita o módulo TWI

  On Twi Twi_activity Save                                  ' Quando a interrupção TWI ocorrer o programa vai
                                                            ' para "Twi_activity"
Return

' ---------------------------------- TWI ISR -----------------------------------------'
Twi_activity:
  push R23

  Select Case Twsr                                          ' TWSR possui a informação do que ocorreu
    ' ----------------------------------  Slave Transmitter Mode ---------------------------------------------'
    ' Own SLA+R has been received; ACK has been returned.
    Case &HA8:
      'Twcr.twint = 1
      Twdr = Buffer(buffer_address)                         ' carrega o Reg. com o valor requerido pelo mestre
      Buffer_address = Buffer_address + 1                   ' Adiaciona 1 ao endereço anterior

    ' Data byte in TWDR has been transmitted; ACK has been received
    Case &HB8:
      'Twcr.twint = 1
      Twdr = Buffer(buffer_address)                         ' Carrega o próximo valor no Reg.
      Buffer_address = Buffer_address + 1                   ' adiciona 1 ao endereço anterior

    ' Data byte in TWDR has been transmitted; NOT ACK has been received
    'Case &HC0:


    ' -------------------------------------  Slave Receiver Mode ---------------------------------------------'
    ' Own slave+w has been received ack has been returned
    Case &H60:
      Index = 0

    ' Previously addressed with own SLA+W; data has been received; ACK has been returned
    Case &H80:
      If Index = 0 Then                                     ' Primeiro byte é o endereço no buffer - buffer(x)
        Buffer_address = Twdr                               ' Pega o endereço onde a operação ocorrerá
        Index = 1

        Else                                                ' A partir do segundo serão recebidos dados
          Buffer(buffer_address) = Twdr                     ' Lê o dado recebido
          Buffer_address = Buffer_address + 1
      End If

    'A STOP condition or repeated START condition has been received while still addressed as Slave
    Case &HA0:
      Index = 0
      ' Switched to the not addressed Slave mode; own SLA will be recognized;
      Twcr.twsta = 0
      Twcr.twsto = 0
      Twcr.twea = 1

  End Select

  ' Para slave transmitter mode o ecravo deve liberar o SCL (clock strech) quando o dado já tiver
  ' sido colocado no TWDR, caso contrário (SCL liberado antes de carregar o TWDR) o TWI enviará
  ' o último byte recebido que provaqvelmente foi o endereço do escravo.
  '
  ' Enquando TWINT for 1 o escravo segurará o clock SCL em zero assim o mestre saberá que o escravo
  ' ainda está preparando o dado a ser enviado.
  Twcr.twint = 1                                            ' limpa o flag de interrupção TWI
                                                            ' Para limpar deve escrever 1
  pop R23
Return

 

 

Arduivos para download (código e simulação):

Clique aqui.

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

TUDO SOBRE COMO USAR LEDS - BARRAS, DISPLAYS, MATRIZES  E MULTIPLEXAÇÃO

 

Pessoal,

 

Este artigo foi publicado fazem alguns anos na parte comum do Fórum, mas como ele fornece respostas prontas para uma grande variedade de possíveis perguntas, resolvi colocar ele aqui também para facilitar o uso de quem programa com Avrs.

 

Todos os conceitos aqui utilizados são universais, e valem para todas as famílias de microcontroladores, como os Pics.

 

O artigo é extenso, são 71 páginas, que pode ser visualizado diretamente, ou pode ser feito o Download para uma leitura off-line.

 

Sei que existem muitos erros de gramática, pois não é o meu forte, e pretendo atualizar o artigo conforme se constate alguma necessidade.

 

Este artigo trata sobre Leds, Displays de Leds, e Matrizes de Leds, e ensina como fazer os cálculos de resistores limitadores para uso direto com os microcontroladores, e como fazer o Multiplex, com várias alternativas de drivers de corrente para aumentar o brilho.

 

A teoria da Multiplexação está apresentada, bem como os conceitos de se utilizar um Timer para esse procedimento.

 

Explico também o uso de CIs populares para esses usos, como o 74HCT595 e o MAX7219, este último muito útil tanto para vários displays de 7 segmentos como para várias matrizes 8x8.

 

Apresento vários programas - exemplos, escritos em Basic, que podem ser portados facilmente para outras linguagens. Todos os programas rodam com a versão FREE do Bascom.

 

E tem um uso interessante de como obter valores fracionários nos resultados, utilizando apenas números inteiros ! Este procedimento sempre nos dá mais velocidade nos cálculos, bem como programas menores.

 

Boas leituras a todos !

 

Paulo

 

SEGUE O LINK PARA O DOWNLOAD NO 4SHARED, COM OS ARQUIVOS PARA SIMULAR NO PROTEUS :

 

http://www.4shared.com/rar/h5W61eggba/como_funciona.html

 

COMO USAR DISPLAYS DE LEDS MULTIPLEXADOS.pdf

  • Curtir 4
Link para o comentário
Compartilhar em outros sites

  • 5 semanas depois...

PROJETO DE UM VU METER ESTÉREO COM INDICADOR DE PICO - Parte 1

 

Fazem algumas semanas um usuário perguntou sobre um VU meter diferente, que utilizava um PIC, e além de mostrar o volume em um par de barras de Leds, tinha uma coisa bem legal : um LED sempre ficava aceso um pouco mais de tempo, indicando que houve um pico de volume, e após algum tempo, esse LED se apaga e acende o imediatamente abaixo; passa mais um tempo, esse LED apaga e acende um abaixo dele, e por aí vai.

 

O efeito fica bem legal, e pode ser visto neste link, na parte final do artigo tem vários vídeos :

 

http://s-o.webnode.cz/vu-metr/

 

O usuário queria que indicasse uma maneira de fazer esse mesmo efeito, porém utilizando 20 Leds para cada canal.

 

Então, resolvi fazer um projeto semelhante, porém baseado em microcontroladores AVR.

 

Para facilitar, usei um Atmega328P, com uma relação custo/benefício muito boa, e para facilitar a minha montagem, resolvi basear em um Arduíno Uno.

 

Comecei a pensar no hardware :

 

- 20 Leds em cada canal totalizam 40 Leds.... não temos tantos pinos de I/O para acessar individualmente, então teremos de fazer por Multiplexação.

 

Resolvi ver as barras de Leds baratas que existem no mercado, e logo vi uma interessante : uma barra com 8 Leds, que podem ser colocadas uma encaixada na outra, podendo obter múltiplos de 8 Leds, como 8,16,24, etc.

Assim, usaria 3 dessas barras em cada canal, totalizando 6 barras.

 

Portanto, se eu usar uma porta de 8 Bits como saída, e mais 6 pinos individuais de I/O, posso fazer o projeto desejado, podendo ter até mesmo 24 Leds em cada canal ! Com isso temos pinos suficientes num simples Arduíno Uno !

 

- Para ser Estéreo, tenho de usar duas entradas separadas do conversor A/D, para fazer a leitura do sinal de entrada.

 

- Seria interessante ter algum tipo de ajuste de sensibilidade, isto é, uma maneira onde possa compensar uma certa variação de intensidade do sinal de entrada. Então, me lembrei que posso mudar o valor da tensão de referência do conversor A/D, usando o pino chamado AREF no Arduíno, onde posso colocar uma tensão variável entre 1.1 Volts até 5 Volts, mediante o uso de um simples trimpot de 10K.

Isto acaba fazendo como se mudasse o ganho do circuito de conversão, e já é o suficiente para o que eu pretendo usar.

 

- E por ultimo, temos de ter um circuito de entrada, que recebe o sinal de áudio e o converte para uma tensão proporcional à intensidade do sinal, e com um tipo de integração do sinal que evitaria que os Leds ficassem com excesso de tremulação : algo perfeito para um simples integrador com RC !

 

Primeiramente tentei usar o mesmo esquema na entrada do conversor , conforme apresentado no esquema existente no link acima, e logo percebi que não existe uma boa linearidade em função da intensidade do volume ( devido ao diodo utilizado para isolar o integrador RC ), aliás acabei achando que poderia fazer algo bem mais linear, de maneira que possa obter algo mais de acordo com o mundo real em termos de variação de intensidade de sinal e mudança nos Leds.

 

Em uma aplicação no datasheet do LM3915 achei este circuito chamado de Detector de Pico :

 

28meglg.jpg

 

Conforme a explicação , esse circuito é perfeito para o que quero pois ele acaba compensando a tensão mínima de condução do diodo 1N914 , que é utilizado para carregar o capacitor que está em paralelo com o resistor, ou seja, é um tipo de circuito integrador.  Logo de cara, já tenho uma enorme vantagem ao simples circuito que utiliza  um diodo Schottky, pois a tensão de condução do 1N914 permanece bem constante, ao passo que a do diodo Schottky varia bastante com a intensidade da corrente, podendo variar desde 0,2 até 0,8 Volts....

 

Na verdade, quando montei esse circuito, a coisa não ocorreu exatamente como foi descrito no datasheet....., pois a tensão VCE do transistor acabou ficando acima de 0,7 Volts, e isso fazia aparecer um nível DC sobre o integrador, e o conversor A/D acabava sempre tendo um determinado valor mínimo acima de zero. Isso tinha de ser resolvido...

 

O pior foi perceber que conforme o transistor utilizado essa tensão VCE variava, sendo que com 5 transistores diferentes obtive tensões entre 0,5 e 1,3 Volts ! O velho problema da variação de ganho dos transistores de um mesmo lote estava me criando problemas mais uma vez.....

 

Portanto, tive de modificar um pouco esse circuito, e no lugar do resistor R3 de 100k usei um trimpot de 100K, e assim posso ajustar com precisão até obter a tensão VCE de 0,7 Volts.

 

A próxima surpresa : Esse circuito utilizado tem ganho unitário .... ou seja, não amplifica em nada o sinal na sua entrada !

 

Quando eu liguei um Iphone como fonte de sinal, diretamente nesse circuito, tive de colocar o volume no máximo, e mesmo usando a tensão de referência mínima possível de cerca de 1.1 Volts no conversor A/D, não conseguia fazer acender mais de 12 Leds ........ Então tive de acrescentar um simples circuito de amplificador de emissor comum, naquela famosa configuração, e calculei os valores para obter o ganho de 10 vezes, e agora consigo o acionamento de todos os 20 Leds facilmente.

 

A seguir, apresentarei o esquema completo utilizado, bem como o programa que foi desenvolvido.

 

Paulo

  • Curtir 2
Link para o comentário
Compartilhar em outros sites

PROJETO DE UM VU METER ESTÉREO COM INDICADOR DE PICO - Parte 2

 

Segue o esquema do circuito adaptador de entrada, que nada mais é do que um simples amplificador de emissor comum com ganho aproximado de 10 vezes, junto com o circuito modificado do detector de pico :

 

faudqd.jpg

 

Dessa maneira, podemos usar tranquilamente uma fonte de baixo nível de sinal, como por exemplo a saída de um celular ou um MP3 player.

Caso queira utilizar uma saída de alto nível, pode-se eliminar o amplificador, ligando-se diretamente ao capacitor C2.

 

Uma coisa que pode ser modificada é o valor do resistor R4. Se aumentar o valor, vai tornar as mudanças dos Leds mais "lentas" , pois estamos aumentando a constante de tempo do integrador RC. E se diminuir, vai tornar as mudanças mais rápidas, aumentando as cintilações dos Leds. É uma questão de gosto de cada um....

 

Uma vez montado o circuito, aplique os 5 Volts, e sem nenhum sinal de entrada, coloque o multímetro para medir a tensão na junção de D1 e R4, e ajuste o trimpot RV1 até obter uma tensão máxima de 0,05 Volts. Um valor entre 0,01 e 0,05 Volts está ótimo.

 

O esquema completo da parte digital segue abaixo :

 

 

207pcvs.jpg

 

Basta ligar a saída dos circuitos adaptadores ( são dois, um para cada canal... ) às entradas LEFT e RIGHT .

 

Em cada barra de Leds, os pinos que são ligados juntos são os CATODOS dos Leds. Como o Proteus não tem as barras com 8 Leds, no esquema estão as barras com 10 Leds. Na verdade, se você quiser pode também usar barras de qualquer número de Leds, desde que sejam agrupados 8 a 8 Leds juntos. Existem algumas barras de 20 Leds que inclusive possuem Leds de cores diferentes, perfeitas para uso neste VU, mas no Ebay só são vendidas nos USA, e o custo de frete torna muiiito caro o seu uso.

 

Embora no esquema tenha utilizado um ATMEGA328 para uma maior clareza, na montagem usei um Arduíno Uno , o qual já me fornece uma saída de tensão de 5Volts para alimentar os circuitos adaptadores;  basta apenas ligar os pinos equivalentes conforme o desenho abaixo :

 

ATmega328P_vs_Arduino_pin_mapping.png

 

Caso alguém queira montar o circuito usando o microcontrolador apenas, tem de incluir o oscilador, que é um cristal de 16 Mhz e dois capacitores de 18 pF ligados aos pinos 9 e 10, além de um capacitor cerâmico de 100 nF e um eletrolítico de 47 uF , ambos para o desacoplamento da tensão de alimentação do microcontrolador. E , claro, adicionar uma boa fonte de alimentação de 5 Volts....

 

Repare que temos outro trimpot aqui, de 10K, que permite um ajuste de sensibilidade. Ele permite ajustar a tensão AREF do conversor digital para um valor entre 1.1 e 5 Volts. Com 1.1, teremos a maior sensibilidade, e vai diminuindo conforme aumentamos a tensão.

 

Caso ajuste para um valor menor do que 1.1 Volts, o conversor vai ficar doido, e todos os Leds vão acender !!!!  Se isso acontecer, basta um toque no trimpot para aumentar a tensão.

 

No meu caso, usando o circuito adaptador acima, pude deixar o trimpot na posição central, e pude controlar facilmente apenas ajustando o volume de saída do meu celular.

 

A seguir, descreverei o programa utilizado.

 

Paulo

Link para o comentário
Compartilhar em outros sites

PROJETO DE UM VU METER ESTÉREO COM INDICADOR DE PICO - Parte 3

 

Segue o arquivo fonte no Bascom :

 

$regfile = "m328pdef.dat"
$crystal = 16000000
$hwstack = 40
$swstack = 40
$framesize = 40

Dim Canal_e As Word
Dim Canal_d As Word
Dim Vmax_e As Byte
Dim Vmax_d As Byte
Dim Old_vmax_e As Byte
Dim Old_vmax_d As Byte
Dim Saida1 As Byte
Dim Saida2 As Byte
Dim Saida3 As Byte
Dim Saida4 As Byte
Dim Saida5 As Byte
Dim Saida6 As Byte
Dim Mask1 As Byte
Dim Mask2 As Byte
Dim Mask3 As Byte
Dim Mask4 As Byte
Dim Mask5 As Byte
Dim Mask6 As Byte
Dim New_mask1 As Byte
Dim New_mask2 As Byte
Dim New_mask3 As Byte
Dim New_mask4 As Byte
Dim New_mask5 As Byte
Dim New_mask6 As Byte
Dim Contador_d As Word
Dim Contador_e As Word
Dim Contador1_e As Byte
Dim Contador1_d As Byte
Dim Disp_index As Byte
Dim Temp1 As Byte


Config Portd = Output
Config Portb.0 = Output
Config Portb.1 = Output
Config Portb.2 = Output
Config Portb.3 = Output
Config Portb.4 = Output
Config Portb.5 = Output

Config Timer0 = Timer , Prescale = 1024
Timer0 = 201
' vamos gerar uma interrupção a cada 3,3 milissegundo
' que corresponde a uma frequencia de Multiplex de 300 Hertz
' ou seja, cada display vai acender 50 vezes por segundo !
On Timer0 Timer0_sub
Enable Timer0
' aqui definimos a subrotina que irá ser chamada a cada
' interrupção, e já deixamos o timer0 correr
Config Adc = Single , Prescaler = 64 , Reference = Internal
Start Adc
' aqui configuramos o conversor a/d para fazer uma leitura
' apenas quando pedirmos, vai usar como referencia 5V
Enable Interrupts

Sempre:
nop
Goto Sempre

 '------------- ROTINAS DE INTERRUPÇÃO ------------------------------------
Timer0_sub:
' Rotina chamada pelo Timer1 a cada 1,2 milisegundos
Timer0 = 201
' recarrega o timer novamente
If Disp_index > 5 Then
  Disp_index = 0
End If
' vamos ver se já fizemos o ultimo digito, pois então teremos de
' começar pelo primeiro novamente

'agora, vamos medir o sinal na entrada apenas a cada 6 interrupções
'ou seja, estamos atualizando o display cerca de  137 vezes por segundo
If Disp_index = 0 Then

  Canal_e = Getadc(0)
  Mask1 = 0
  Mask2 = 0
  Mask3 = 0

  If Canal_e = 1023 Then
    Vmax_e = 20
    Mask3 = 8
    Saida1 = &B11111111
    Saida2 = &B11111111
    Saida3 = &B00001111
   Elseif Canal_e > 971 Then
    Vmax_e = 19
    Mask3 = 4
    Saida1 = &B11111111
    Saida2 = &B11111111
    Saida3 = &B00000111
  Elseif Canal_e > 920 Then
    Vmax_e = 18
    Mask3 = 2
    Saida1 = &B11111111
    Saida2 = &B11111111
    Saida3 = &B00000011
  Elseif Canal_e > 870 Then
    Vmax_e = 17
    Mask3 = 1
    Saida1 = &B11111111
    Saida2 = &B11111111
    Saida3 = &B00000001
  Elseif Canal_e > 817 Then
    Vmax_e = 16
    Mask2 = 128
    Saida1 = &B11111111
    Saida2 = &B11111111
    Saida3 = &B00000000
  Elseif Canal_e > 766 Then
    Vmax_e = 15
    Mask2 = 64
    Saida1 = &B11111111
    Saida2 = &B01111111
    Saida3 = &B00000000
  Elseif Canal_e > 715 Then
    Vmax_e = 14
    Mask2 = 32
    Saida1 = &B11111111
    Saida2 = &B00111111
    Saida3 = &B00000000
  Elseif Canal_e > 664 Then
    Vmax_e = 13
    Mask2 = 16
    Saida1 = &B11111111
    Saida2 = &B00011111
    Saida3 = &B00000000
  Elseif Canal_e > 613 Then
    Vmax_e = 12
    Mask2 = 8
    Saida1 = &B11111111
    Saida2 = &B00001111
    Saida3 = &B00000000
  Elseif Canal_e > 562 Then
    Vmax_e = 11
    Mask2 = 4
    Saida1 = &B11111111
    Saida2 = &B00000111
    Saida3 = &B00000000
  Elseif Canal_e > 511 Then
    Vmax_e = 10
    Mask2 = 2
    Saida1 = &B11111111
    Saida2 = &B00000011
    Saida3 = &B00000000
  Elseif Canal_e > 459 Then
    Vmax_e = 9
    Mask2 = 1
    Saida1 = &B11111111
    Saida2 = &B00000001
    Saida3 = &B00000000
  Elseif Canal_e > 408 Then
    Vmax_e = 8
    Mask1 = 128
    Saida1 = &B11111111
    Saida2 = &B00000000
    Saida3 = &B00000000
  Elseif Canal_e > 357 Then
    Vmax_e = 7
    Mask1 = 64
    Saida1 = &B01111111
    Saida2 = &B00000000
    Saida3 = &B00000000
  Elseif Canal_e > 306 Then
    Vmax_e = 6
    Mask1 = 32
    Saida1 = &B00111111
    Saida2 = &B00000000
    Saida3 = &B00000000
  Elseif Canal_e > 255 Then
    Vmax_e = 5
    Mask1 = 16
    Saida1 = &B00011111
    Saida2 = &B00000000
    Saida3 = &B00000000
  Elseif Canal_e > 204 Then
    Vmax_e = 4
    Mask1 = 8
    Saida1 = &B00001111
    Saida2 = &B00000000
    Saida3 = &B00000000
  Elseif Canal_e > 152 Then
    Vmax_e = 3
    Mask1 = 4
    Saida1 = &B00000111
    Saida2 = &B00000000
    Saida3 = &B00000000
  Elseif Canal_e > 101 Then
    Vmax_e = 2
    Mask1 = 2
    Saida1 = &B00000011
    Saida2 = &B00000000
    Saida3 = &B00000000
  Else
    Vmax_e = 1
    Mask1 = 1
    Saida1 = &B00000001
    Saida2 = &B00000000
    Saida3 = &B00000000
  End If
  Canal_d = Getadc(1)
End If


If Disp_index = 3 Then

  Canal_d = Getadc(1)
  Mask4 = 0
  Mask5 = 0
  Mask6 = 0

  If Canal_d = 1023 Then
    Vmax_d = 20
    Mask6 = 8
    Saida4 = &B11111111
    Saida5 = &B11111111
    Saida6 = &B00001111
  Elseif Canal_d > 971 Then
    Vmax_d = 19
    Mask6 = 4
    Saida4 = &B11111111
    Saida5 = &B11111111
    Saida6 = &B00000111
  Elseif Canal_d > 920 Then
    Vmax_d = 18
    Mask6 = 2
    Saida4 = &B11111111
    Saida5 = &B11111111
    Saida6 = &B00000011
  Elseif Canal_d > 869 Then
    Vmax_d = 17
    Mask6 = 1
    Saida4 = &B11111111
    Saida5 = &B11111111
    Saida6 = &B00000001
  Elseif Canal_d > 817 Then
    Vmax_d = 16
    Mask5 = 128
    Saida4 = &B11111111
    Saida5 = &B11111111
    Saida6 = &B00000000
  Elseif Canal_d > 766 Then
    Vmax_d = 15
    Mask5 = 64
    Saida4 = &B11111111
    Saida5 = &B01111111
    Saida6 = &B00000000
  Elseif Canal_d > 715 Then
    Vmax_d = 14
    Mask5 = 32
    Saida4 = &B11111111
    Saida5 = &B00111111
    Saida6 = &B00000000
  Elseif Canal_d > 664 Then
    Vmax_d = 13
    Mask5 = 16
    Saida4 = &B11111111
    Saida5 = &B00011111
    Saida6 = &B00000000
  Elseif Canal_d > 613 Then
    Vmax_d = 12
    Mask5 = 8
    Saida4 = &B11111111
    Saida5 = &B00001111
    Saida6 = &B00000000
  Elseif Canal_d > 562 Then
    Vmax_d = 11
    Mask5 = 4
    Saida4 = &B11111111
    Saida5 = &B00000111
    Saida6 = &B00000000
  Elseif Canal_d > 511 Then
    Vmax_d = 10
    Mask5 = 2
    Saida4 = &B11111111
    Saida5 = &B00000011
    Saida6 = &B00000000
  Elseif Canal_d > 459 Then
    Vmax_d = 9
    Mask5 = 1
    Saida4 = &B11111111
    Saida5 = &B00000001
    Saida6 = &B00000000
  Elseif Canal_d > 408 Then
    Vmax_d = 8
    Mask4 = 128
    Saida4 = &B11111111
    Saida5 = &B00000000
    Saida6 = &B00000000
  Elseif Canal_d > 357 Then
    Vmax_d = 7
    Mask4 = 64
    Saida4 = &B01111111
    Saida5 = &B00000000
    Saida6 = &B00000000
  Elseif Canal_d > 255 Then
    Vmax_d = 6
    Mask4 = 32
    Saida4 = &B00111111
    Saida5 = &B00000000
    Saida6 = &B00000000
  Elseif Canal_d > 204 Then
    Vmax_d = 5
    Mask4 = 16
    Saida4 = &B00011111
    Saida5 = &B00000000
    Saida6 = &B00000000
  Elseif Canal_d > 152 Then
    Vmax_d = 4
    Mask4 = 8
    Saida4 = &B00001111
    Saida5 = &B00000000
    Saida6 = &B00000000
  Elseif Canal_d > 101 Then
    Vmax_d = 3
    Mask4 = 4
    Saida4 = &B00000111
    Saida5 = &B00000000
    Saida6 = &B00000000
  Elseif Canal_d > 51 Then
    Vmax_d = 2
    Mask4 = 2
    Saida4 = &B00000011
    Saida5 = &B00000000
    Saida6 = &B00000000
  Else
    Vmax_d = 1
    Mask4 = 1
    Saida4 = &B00000001
    Saida5 = &B00000000
    Saida6 = &B00000000
  End If
  Canal_e = Getadc(0)
End If


'agora, vamos comparar se teve novo máximo
  If Vmax_d >= Old_vmax_d Then
    Old_vmax_d = Vmax_d
    Contador_d = 0
    Contador1_d = 0
    New_mask4 = Mask4
    New_mask5 = Mask5
    New_mask6 = Mask6
  Else
    If Contador_d > 96 Then
  ' precisamos fazer o decaimento
      Incr Contador1_d
      If Contador1_d = 12 Then

  'agora, mudar para o led logo abaixo
        If New_mask6 = 8 Then
          New_mask6 = 4
        Elseif New_mask6 = 4 Then
          New_mask6 = 2
        Elseif New_mask6 = 2 Then
          New_mask6 = 1
        Elseif New_mask6 = 1 Then
          New_mask6 = 0
          New_mask5 = 128
        Elseif New_mask5 > 1 Then
          Shift New_mask5 , Right , 1
        Elseif New_mask5 = 1 Then
          New_mask5 = 0
          New_mask4 = 128
        Elseif New_mask4 > 1 Then
          Shift New_mask4 , Right , 1
        Else
          nop
        End If
        Contador1_d = 0
        If Old_vmax_d > 0 Then
          Decr Old_vmax_d
        End If
      Else
        nop
      End If
    End If
  End If

'agora, vamos comparar se teve novo máximo
  If Vmax_e >= Old_vmax_e Then
    Contador_e = 0
    Old_vmax_e = Vmax_e
    Contador1_e = 0
    New_mask1 = Mask1
    New_mask2 = Mask2
    New_mask3 = Mask3

  Else
    If Contador_e > 96 Then
  ' precisamos fazer o decaimento
      Incr Contador1_e
      If Contador1_e = 12 Then
  'agora, mudar para o led logo abaixo
        If New_mask3 = 8 Then
          New_mask3 = 4
        Elseif New_mask3 = 4 Then
          New_mask3 = 2
        Elseif New_mask3 = 2 Then
          New_mask3 = 1
        Elseif New_mask3 = 1 Then
          New_mask3 = 0
          New_mask2 = 128
        Elseif New_mask2 > 1 Then
          Shift New_mask2 , Right , 1
        Elseif New_mask2 = 1 Then
          New_mask2 = 0
          New_mask1 = 128
        Elseif New_mask1 > 1 Then
          Shift New_mask1 , Right , 1
        Else
          nop
        End If
        Contador1_e = 0
        If Old_vmax_e > 0 Then
          Decr Old_vmax_e
        End If
      Else
        nop
      End If
    End If
  End If

Portb = 0
'Apaga todos os leds
Temp1 = Lookup(disp_index , Tabela_pinos)

If Disp_index = 0 Then
  Portd = Saida1 Or New_mask1
Elseif Disp_index = 1 Then
  Portd = Saida2 Or New_mask2
Elseif Disp_index = 2 Then
  Portd = Saida3 Or New_mask3
Elseif Disp_index = 3 Then
  Portd = Saida4 Or New_mask4
Elseif Disp_index = 4 Then
  Portd = Saida5 Or New_mask5
Elseif Disp_index = 5 Then
  Portd = Saida6 Or New_mask6
Else
  nop
End If

Incr Disp_index
Incr Contador_e
Incr Contador_d

If Contador_d = 1000 Then
  Decr Contador_d
End If

If Contador_e = 1000 Then
  Decr Contador_e
End If

Portb = Temp1

Return

End

Tabela_pinos:
Data 1 , 2 , 4 , 8 , 16 , 32

 

Como podem perceber, existe 6 fases distintas, numeradas de 0 até 5, onde além de tratarmos o multiplex das 6 barras, aproveitamos também a fase 0 e a fase 3 para fazer as leituras respectivas dos canais esquerdo e direito.

 

Quando fazemos o processamento dos valores dos canais, também aproveitamos para preparar a leitura do valor do ADC do outro canal.

 

Parece besteira, mas quando mudamos o canal do conversor A/D, existe um certo delay para que o capacitor interno se carregue com o valor do canal desejado. Tento evitar ao máximo que esse delay prejudique a leitura do valor DC do canal desejado, devido ao efeito de crosstalk interno existente no conversor A/D.

 

E existe um processamento a mais, que é justamente o tratamento de manter aceso o Led correspondente ao pico detectado, e fazer o decaimento ao longo do tempo.

 

Caso alguém precise de mais informações, posso detalhar o processo.

 

Segue em anexo os arquivos fonte e objeto para serem gravados.

 

Paulo

 

VU METER OPTIMIZED.bas

VU METER OPTIMIZED.hex

VU METER OPTIMIZED.obj

Link para o comentário
Compartilhar em outros sites

  • mês depois...

@test man*~ ,

 

Poxa, você chegou um mês atrasado.... eu até havia anunciado ele aqui no CDH, mas acabei vendendo no Mercado Livre....

 

Agora só tenho o de bancada RIGOL DS1054Z, com todos os opcionais implementados ( é uma maravilha viu, e não vendo por valor nenhum ! ) e um Nano Quad, portátil....

 

Quanto à foto.... vai entender..... pior que eu entro no TINYPIC, e a foto do esquema está lá, aí editei o post, apaguei a foto errada, colei o novo link, salvei o post , e ...... continuou apareçendo a foto da tilápia !!!!!!!!!

 

Tive de apagar a imagem DENTRO do Tinypic, e mandar de novo do meu computador para lá... e agora deu certo !

 

Já pensou se der alguma coisa errada em TODOS os links que eu uso lá ???? Nem quero pensar !

 

Paulo

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

Visitante
Este tópico está impedido de receber novas respostas.

Sobre o Clube do Hardware

No ar desde 1996, o Clube do Hardware é uma das maiores, mais antigas e mais respeitadas comunidades 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

×
×
  • Criar novo...

 

GRÁTIS: ebook Redes Wi-Fi – 2ª Edição

EBOOK GRÁTIS!

CLIQUE AQUI E BAIXE AGORA MESMO!