Ir ao conteúdo
  • Cadastre-se

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


Ir à solução Resolvido por aphawk,

Posts recomendados

  • Membro VIP
@alexandre.mbm Eu creio que sim, neste vídeo o cara faz uma comparação de uma única sketch operando no mega2560 e no DUE:

Edit: É a mesma interface, no fim do vídeo dá para ver...

 

Eu fui ao Youtube e pude encontrar o código. Para mim não está claro, mas, por ser comparação, dar para inferirmos que o mesmo código foi executado em ambos.

Link para o comentário
Compartilhar em outros sites

@alexandre.mbm,

 

Sim, foi o mesmo código fonte na linguagem do Arduíno, mas esse programa é compilado de maneira diferente para os dois microcontroladores, e claro, são códigos-objeto diferentes que estão sendo executados.

 

Imagina que aparece um cara que resolve fazer em Assembly na marra, buscando a melhor performance no programa, será que esse código dele para fazer exatamente a mesma função rodando no Arduíno Uno não poderia chegar à mesma performance do Arduino Due ????

 

Afinal, sempre e sempre , um programa escrito direto em Assembler por uma pessoa que seja um bom programador vai riodar bem mais rápido do que em qualquer outra linguagem.

 

Cansei de ver programas em ASM feitos da velha boa maneira ( sem macros, sem bibliotecas, e escritos visando a maior velocidade )  feitos para rodarem em um Z80 a 1Mhz baterem programas feitos em C por programadores e que rodavam em um Z-80A a 4 Mhz. 

 

O que quero dizer com isso ?

 

Eu sei que nesse teste acima foram usadas versões DIFERENTES de compiladores, basta ver no site do Arduino.cc .

 

Se o pessoal que criou o compilador do DUE fez uma baita pesquisa do hardware e dos tempos de instruções, eles podem ter melhorado bastante a eficiência do novo compilador em termos de velocidade de execução, e com isso podem chegar a resultados até superiores do que apenas o hardware permitiria !

 

Mas será que se um pessoal igualmente talentoso escrevesse um compilador super otimizado para o UNO, não conseguiria uma performance maior, de maneira que ficasse apenas umas 3 vezes mais lento ?

 

É complicado comparar um mesmo programa fonte para rodar em duas famílias diferentes de microcontroladores, pois sempre vai depender da eficiência de cada compilador.

 

Na grande maioria dos casos, um hardware 5 vezes mais rápido consegue resolver códigos ineficientes e ser ainda mais rápido do que o hardware inferior.

 

Paulo

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

  • 2 semanas depois...

Projeto de um Analizador Lógico Portátil com ATMEGA1284P - PARTE 1

 

Algum tempo atrás eu estava fazendo um pequeno analizador lógico baseado em um Atmega328P.

 

Tive a ideia após ver um projeto semelhante, que foi bem popular na época da postagem. Usava um pequeno display Nokia 5110, desses que se encontram fácilmente a um preço bem baixo. O projeto era legal, as fotos e os vídeos ilustravam bem o funcionamento. Aí, começei a "bolar" o meu analizador....

 

Começei a ter uma série de dúvidas. Primeiro, o que que eu gostaria de ter ?

 

1 - Teria de ser portátil, com um display, e permitir a captura de pelo menos 4 canais, e teria de capturar perfeitamente sinais I2C a 400 Khz. Definí 500 Khz para ter alguma margem de segurança..

2 - Boa capacidade de memória de amostragem, no mínimo 1024K amostras.

3 - Possibilidade de mudar a taxa de amostragem, pois existem eventos lentos também .....

4 - Possibilidade de usar algum tipo de Trigger para inicias as amostragens

5 - Ter fácil visualização dos sinais

 

Aí que começei a empacar com o ATMEGA328P. Motivo principal : clock até 20 Mhz e memória Ram de apenas 2K.

 

Pensei em usar interrupção. Claro, seria muito mais fácil fazer as amostragens e guardar com uma base de tempo de um Timer. Mas .....

 

Apenas para entrar e sair de uma interrupção, perdemos no mínimo 10 ciclos de clock, independente de qual seja o compilador !!!! Se quisermos amostrar com segurança um sinal I2C de 500 Khz, temos 40 ciclos de clock no toal, mas como temos de amostrar tanto a Subida como a Desçida do sinal, temos de amostrar DUAS vezes esse sinal. Assim, temos um sampling rate de 1 Megasample/segundo . O que nos dá apenas 20 ciclos de clock.

Se já perdemos 10 apenas para entrar e sair da interrupção, sobra apenas 10 ciclos de clock, que não dá nem para salvar e repor o contexto .....

 

Então, nada de interrupção, pois temos apenas 20 ciclos de clock entre duas amostragens

 

Aí vem outro problema : o Bascom, como qualquer compilador, sempre gera um código que inclui algumas "salvaguardas" para facilitar a compilação. Isso significa instruções adicionais além das estritamente necessárias, o que faz o programa ficar mais lento, exigindo mais ciclos de clock.

 

Então, vou usar o Bascom para fazer todo o programa, EXCETO a parte de captura dos dados, que será feita em Assembly mesmo, "na marra" , e integrado ao Bascom.

 

Aí, outros pensamentos vieram..... :

 

- Já que é um analizador, porque não integrar também algum tipo de análise de dados seriais ???? Não custa nada, já que o hardware é o mesmo !

- E poderíamos implementar um analizador digital de I2C, para achar endereços, medir velocidades, mostrar conversas, etc . Afinal, usamos um display gráfico!

- E já que estou usando um display gráfico, porque não fazer também a captura de um sinal analógico e mostrar na tela , como um simples osciloscópio ? Claro, usando o ADC interno não vamos conseguir uma grande velocidade, mas algo até uns 50 Khz já ajuda bastante para trabalhos com áudio e outras coisas !

- E já que estamos fazendo medidas analógicas, implementar algumas medidas como Valor RMS de um sinal, Valor Máximo e Valor Mínimo também ajudariam !

 

Então, começei a lembrar de vários projetos que já passaram na minha tela. Se juntar todos eles, otimizando o código, ficaria um excelente aparelho de baixo custo e muito versátil !

 

Mas isso tudo ficaria muito apertado em apenas 32K de programa. Preciso usar um microcontrolador com mais capacidade, tanto de memória de programa como de SRAM, para armazenar mais dados. E que seja no formato DIP, para facilitar a montagem, e que não custe muito caro ( aí complicou .... ) .

 

Logo de cara, pensei no Atmega1284P . Ele tem 128K de programa, 16K de RAM, e tem no formato DIP de 40 pinos, e de quebra, possui DOIS TIMERS de 16 bits ! Excelente, mas será que aqui no Brasil custa muito caro ?

 

Achei na Farnell , na faixa de R$ 34,00 . É uma excelente escolha pois cabe muiiito programa nele, e poderemos trabalhar com até umas 15.000 amostras se for necessário, muito mais do que as 1024 que considerei no início !

 

Então, vou basear o projeto no ATMEGA1284P.

 

 

Captura de dados em alta velocidade

 

Agora, existem dois enfoques para se capturar os dados em alta velocidade :

 

1 - Muito usado pelo pessoal do Arduíno : um monte de instruções de leitura já gravando direto em Ram!!!

     Algo do tipo :

 

Buffer(1)=Input A

Buffer(2)=Input Z

.....

Segundo eles, isso permite uma captura a cada 8 ciclos de clock, o que permite capturar sinais a até 1,25 Mhz aproximadamente, o que já é muito bom.

 

 

Em ASM dos AVR's, ficaria assim :

 

ldS   r26, {bCounter}
ldS   r27,{bcounter+1}

 

In    r16,PINA

st    X+,r16

 

In    r16,PINA

st    X+,r16

 

In    r16,PINA

st    X+,r16

 

...

 

O que daria uma captura a cada 3 ciclos de clock !!!! Fantástico, o que permite analizarmos sinais de até 3 Mhz !!!!!

 

O incoveniente disto : esse código consome 4 bytes para cada captura ! Se quiser fazer 8192 capturas, já consumiremos 32K de memória de programa !

 

 

2 - Evitar o máximo possível de instruções, e usar um loop para que nosso programa não se torne muito grande.

Para isso, temos de pedir ajuda ao hardware interno, para que possamos tomar decisões em instruções rápidas, tipo teste de bit, que consomem apenas um ciclo de máquina.

 

Por exemplo, suponha que vamos ler 8192 amostras, a um sample rate de 1 Megasample/segundo. Temos 20 ciclos de máquina entre cada amostragem. Mas apenas para testar se já atingimos o final do buffer de 8192, consumimos 6 ou 7 ciclos de máquina. Temos de bolar algo que seja mais rápido.

 

Foi quando tive uma ideia de usar um Timer de 16 bits no modo CTC para nos sinalizar em um pino de saída o momento em que já podemos ler uma amostra, e ao mesmo tempo esse sinal incrementará um outro Timer de 16 bits no modo COUNTER  que vai nos sinalizar o momento em que fizemos a última amostragem. Basta usarmos as saídas OCR desses dois timers. e ligarmos as mesmas a dois pinos de entrada livres. Assim, podemos apenas usar instruções rápidas de 1 ciclo de clock para testar e já sabemos se podemos fazer uma leitura ou se ainda temos espaço no buffer para fazer outra leitura !

 

Infelizmente, no caso do Timer no modo CTC, quando a saída OCR é levada ao nivel alto, para fazermos esse sinal voltar ao nivel baixo temos de reprogramar todo o Timer, o que consome mais de 12 ciclos de clock, o que não dá para ser feito.

 

A solução foi usar a saida OCR no modo Toggle, e mudei a temporização para fazer o Toggle em apenas a metade do tempo. Assim, no tempo desejado, eu terei duas mudanças seguidas, o que vai dar o mesmo resultado !

 

Antes que digam qualquer coisa, eu tentei por dois dias seguidos tudo o que era possível para zerar esse sinal OCR..... e desistí ! Portanto, essa é a maneira que será implementada para o modo de captura mais rápido, que será o suficiente para o nosso intuíto.

 

Para os modos de captura mais lentos, usarei uma outra rotina, que nao será tão crítica.

 

Dito isto, vamos mostrar a ideia básica com o Proteus.

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

Projeto de um Analizador Lógico Portátil com Atmega1284P - PARTE 2

 

Segue o programa utilizado :

'--------------------------------------------------------------------------
'               ANALIZADOR LOGICO COM ATMEGA1284P                                       '
'                                                                          
'
' teste da rotina de captura em alta velocidade
'
'--------------------------------------------------------------------------'
$regfile = "m1284PdefOK.dat"
$crystal = 20000000
$hwstack = 40
$swstack = 40
$framesize = 40
Dim Buffer(8193) As Byte At &H180
Dim Bcounter As Word
Bcounter = &H180
Config Pinb.2 = Input
Config Pind.0 = Input
Config Pinb.1 = Input
Config Pinb.4 = Input
Config Pinb.7 = Output
Config Porta = Input
Config Portd.5 = Output
Config Portb.6 = Output
' TESTE DA ROTINA DE CAPTURA EM ALTA VELOCIDADE
' TIMER 1 CONTROLA A QUANTIDADE DE LEITURAS
Compare1a = 8192
' a saída OCR1A ficará alta quando atingirmos 8192 amostragens
Timer1 = 0
Config Timer1 = Counter , Edge = Rising , Compare A = Set , Compare B = Disconnect
' TIMER3 DEFINE O INTERVALO ENTRE AS LEITURAS
Compare3a = 7
' com esse valor, teremos um toggle na saída OCR3A a cada 8 pulsos de clock
' como a rotina em assembler faz a amostragem quando estiver no nivel alto,
' a saída fica em nivel alto a cada 2x8 = 16 ciclos de clock.
' Assim, estamos amostrando a cada 20 MHz / 16 = 1,25 Mhz.
' Podemos garantir que vamos conseguir pegar um sinal digital do tipo I2C
' com frequência máxima de 612,5 Khz . A frequência alta mais usada é de
' 400 Khz, então estamos tranquilos !
Timer3 = 0
Config Timer3 = Timer , Prescale = 1 , Compare A = Toggle , Compare B = Disconnect , Clear_timer = 1
' Agora, segue a rotina de captura :
$asm
'aqui vamos supor que tudo está inicializado, inclusive os timers.
'salva contexto
push  r16
in    r16, SREG
push  r16
push  r17
push  r26
push  r27
'pega o valor do inicio do buffer:
ldS   r26, {bCounter}
ldS   r27,{bcounter+1}
'vamos zerar o contador de amostragens
LDI   R17,$00
STS   TCNT1H,R17
STS   TCNT1L,R17
NOP
Faz_loop:
SBIS  PINB,1                                          'TESTA SE TÁ NA HORA DE LER
RJMP FAZ_LOOP
Pode_ler:
In    r16,PINA                                        'LE DADOS      
st    X+,r16                                          'ARMAZENA BUFFER E INCREMENTA
SBIs PINB,2                                           'AINDA TEM ESPAÇO ?
RJMP faz_loop
' buffer está cheio
POP R27
POP R26
POP R17
POP R16
!out  sreg,r16
pop   r16
$end Asm
Stop
End

Agora, o esquema utilizado na simulação :

qprl2e.jpg

 

 

O interessante é a maneira como foram interligados os módulos Timer1 e Timer3.

 

Timer3 está no modo CTC, contando até 7 ciclos, zerando a contagem e alterando o sinal de OCR3A. Como o transbordo ocorre no próximo ciclo de clock, esse transbordo ocorre a cada 8 ciclos de clock.

 

Timer1 está no modo Counter, contando até 8192 eventos, sendo que esse evento é a ida para nivel alto de OCR3A, que significa que uma amostragem foi feita e armazenada na memória.

 

O programa em Assembly fica esperando a saída OCR3A ir para nível alto, o que ocorre a cada 16 ciclos de clock ( repare que usei Capture3a=7 ) , o que gera um Toogle a cada 8 ciclos, e assim depois de mais 8 ciclos a saída estará em nivel alto novamente. Quando o programa detecta o nível alto, ele faz a leitura da entrada no Port A , armazena essa leitura no buffer, e testa a saida OCR1A para ver se o buffer está cheio. Se não estiver, volta a esperar uma nova subida de OCR3A para realizar outra leitura.

 

O legal disso é que o hardware interno zera o Timer para começar de novo, e o próprio hardware também faz a inversão de nível de OCR3A, assim não preciso fazer nada no programa !

 

E o outro Timer, que é o Timer1, está rodando no modo Counter, usando essa mesma saída OCR3A como gerador, assim ele faz  por hardware a comparação que irá me avisar quando atingiu a quantidade máxima de leituras, e nessa hora saio da rotina de captura.

 

Em anexo estão os arquivos fontes do programa, bem como a simulação no Proteus 8.2 .

 

Observação importante :

 

Após dois dias de tentativas, descobri que o arquivo de definição do Atmega1284P que o Bascom utiliza tem um dado faltante sobre o Timer3, e isso faz com que o programa funcione errado.... Eu corrigí o arquivo, e incluí ele no arquivo zipado. Por favor, esse arquivo tem de ser colocado no diretório de instalação do Bascom, onde existem dezenas desse tipo .DEF !

 

 

A seguir, vou apresentar a rotina que será utilizada nas capturas de mais baixa velocidade.

analizador.rar

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

Projeto de um Analizador Lógico Portátil com Atmega1284P - PARTE 3

 

Antes de mostrar a rotina utilizada nos modos de baixa velocidade, vou mostrar uma outra ideia que tive, e com um uma rotina bem pequena conseguí chegar a 4 Megasamples/seg !!!!!

 

Isto significa uma captura a cada 5 ciclos de clock, que é superior à ultima mostrada, e usando apenas um único Timer de 16 bits !

 

Segue o novo circuito, repare que não usei nada externo ao microcontrolador, nenhum sinal interligado !

 

2wfvj1c.jpg

 

Agora, segue o novo programa :

'--------------------------------------------------------------------------
'
'               ANALIZADOR LOGICO COM ATMEGA1284P                          
'
'                                                                          
'
' teste da rotina de captura em muito alta velocidade = 4 Megasample/seg !                          
'
'--------------------------------------------------------------------------'
$regfile = "m1284PdefOK.dat"
$crystal = 20000000
$hwstack = 40
$swstack = 40
$framesize = 40
Dim Buffer(8193) As Byte At &H180
Dim Bcounter As WordBcounter = &H180
Dim Temp As Word
Config Pinb.2 = Input
Config Pind.0 = Input
Config Pinb.1 = Input
Config Pinb.4 = Input
Config Pinb.7 = Output
Config Porta = Input
Config Portd.5 = Output
Config Portb.6 = Output
' TESTE DA ROTINA DE CAPTURA EM ALTISSIMA VELOCIDADE
'ESSE VALOR DE TIMER1 É APENAS PARA COMEÇAR ... BASTA SER DIFERENTE DE 0 !
Timer1 = 1
'AGORA, VAMOS FAZER UMAS CONTAS. A ROTINA DE CAPTURA INICIALIZA NOVAMENTE O
'CONTADOR DO TIMER1, E QUANDO O CONTADOR CHEGAR A 65535 E VIRAR A 0, VAI GERAR
'UMA INTERRUPÇÃO. ENTRE ZERARMOS O CONTADOR E INICIALIZAR A LEITuRA TEREMOS DOIS
'CICLOS DE CLOCK. DEPOIS DISSO, PARA CADA LEITURA TEREMOS MAIS 5 CICLOS.
'ASSIM, SE QUISERMOS LER 8192 VEZES, TEMOS DE MULTIPLICA POR 5 E
'SOMAR 2 AO NUMERO OBTIDO, O QUE VAI NOS DAR O NUMERO DE CICLOS QUE
'PRECISAMOS ESPERAR PARA TER A LEITURA. RESTA APENAS FAZER A CONTA DE
'65536 - ESSE NUMERO, E TEREMOS O NUMERO DE CONTAGENS A SER INICIALIZADO.
Temp = 5 * 8192
Temp = Temp + 2
Temp = 65536 - Temp
Config Timer1 = Timer , Prescale = 1 , Compare A = Disconnect , Compare B = Disconnect
On Timer1 Int1_sbr Nosave
Enable Timer1
Start Timer1
Enable Interrupts
' Agora, segue a rotina de captura :
$asm
'aqui vamos supor que tudo está inicializado, inclusive o timer.
'salva contexto
push  r16
in    r16, SREG
push  r16
push  r17
push  r26
push  r27
'pega o valor do inicio do buffer:
ldS   r26, {bCounter}
ldS   r27,{bcounter+1}
'vamos inicializar o timer1
LDS    R17,{TEMP}
LDS    R16,{TEMP+1}
STS   TCNT1H,R16
STS   TCNT1L,R17
'pronto, agora é só fazer as leituras em sequencia no loop infinito
Pode_ler:
In    r16,PINA                                        'LE DADOS
st    X+,r16                                          'ARMAZENA BUFFER E INCREMENTA
RJMP PODE_LER
' buffer está cheio
Acabou:
POP R27
POP R26
POP R17
POP R16
!out  sreg,r16
pop   r16
$end Asm
'pronto , final do programa !
Stop

'--------------------------------------------------------
Int1_sbr:
'esta rotina vai ser chamada quando termos lotado o buffer de leituras
'e então fazemos uma troca do endereço de retorno, para que na saida desta interrupção
'o program counter aponte para o programa no label ACABOU:  !
'assim, saimos fora do loop de leitura de altíssima velocidade !
$asm
POP R16
POP R17
LDI R26, LOW(ACABOU *1)
LDI R27, HIGH(ACABOU *1)
PUSH R26
PUSH R27
$end Asm
Return
End

Qual o novo truque utilizado ?

 

Lembram do que acontece em uma interrupção : O microcontrolador termina a instrução que está fazendo, e coloca o endereço da próxima instrução no stack pointer, e em seguida pula para o endereço da rotina de interrupção.

 

Quando termina a interrupção, o contador de programa é atualizado novamente a partir do stack, e pula para o endereço que lá foi armazenado, que seria o da próxima instrução que seria executada quando houve a interrupção.

 

Agora, vejam o loop de leitura em ASM : eu leio o Port A, e guardo o valor no endereço do buffer, que é automáticamente incrementado, e pulo para fazer a leitura de novo.

Isto demora apenas 5 ciclos de clock. E do jeito que está, não tem como sair desse loop, ele irá se repetir indefinidamente !

 

Aí é que entra a interrupção !

 

Se eu gerar uma interrupção, eu posso retirar do stack o endereço da próxima instrução que ia ser executada, e colocar o endereço para onde eu quero que continue, que lógico será um endereço já fora do loop !!!!!

 

O truque é usar o Timer1 para fazer a contagem dos ciclos, e só interromper no momento exato que eu terminei de encher o buffer com as capturas !

 

Então, basta fazer a conta : entre carregar o valor inicial do Timer1 e fazer a primeira leitura, ocorrem dois ciclos de clock. E para cada leitura, preciso de 5 ciclos de clock extra. Portanto, preciso de 5 * 8190 +2 = 40.952 ciclos antes de interromper, e para isso tenho de carregar a contagem inicial de 65536 - 40952 = 24584.

 

A rotina em ASM pega o endereço inicial do buffer de leituras, e pega também o numero a ser inicializado no Timer1; inicializa o contador do Timer1, e começa a fazer as leituras e armazenar no buffer.

Quando fizer a ultima amostragem, ocorre o estouro do Timer1, causando uma interrupção, e nessa rotina eu faço a troca do endereço de retorno, fazendo com que a rotina de capture termine !

 

Simples, com o Bascom !!!!! ( e um pouquinho de Assembler .... )  :D

 

Seguem em anexo os arquivos para a simulação.

 

A seguir, a rotina de captura mais lenta , com tempos de captura escalonáveis.

analisador4.rar

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

@test man*~,

Sim, isso mesmo. Qualquer tipo de teste que eu fizer dentro do loop vai fazer cair muito a velocudade.

Foi uma bela maneira de fazer o hardware interno ajudar !

@alexandre.mbm,

Da maneira que eu quero fazer, será muito mais do que um analisador lógico !

Posso acompanhar conversas entre chips com I2C, posso visualizar o comportamento digital de até 8 pontos simultâneos, posso visualizar uma conversa usando protocolo Serial , posso medir tempos entre eventos, medir frequências, medir alguns valores RMS, e podem aparecer outras idéias durante este desenvolvimento !

É um projeto que está começando, e todos podem sugerir o que acharem necessário !

Paulo

Link para o comentário
Compartilhar em outros sites

@test man*~,

Sim, isso mesmo. Qualquer tipo de teste que eu fizer dentro do loop vai fazer cair muito a velocudade.

Foi uma bela maneira de fazer o hardware interno ajudar !

@alexandre.mbm,

Da maneira que eu quero fazer, será muito mais do que um analisador lógico !

Posso acompanhar conversas entre chips com I2C, posso visualizar o comportamento digital de até 8 pontos simultâneos, posso visualizar uma conversa usando protocolo Serial , posso medir tempos entre eventos, medir frequências, medir alguns valores RMS, e podem aparecer outras idéias durante este desenvolvimento !

É um projeto que está começando, e todos podem sugerir o que acharem necessário !

Paulo

 

Estou achando este seu projeto bem instrutivo, você usa uns macetes bem interessantes, mas tem muitas destas instruções que você usa que eu não entendo.

 

Você teria uma boa fonte (literatura e pode ser em inglês mesmo, de preferência com exemplos) onde tivesse uma descrição detalhada do funcionamento das instruções ASM ? Acho que também seria interessante que você colocasse mais comentários nos seus códigos, seria muito mais didático, e ajudaria muito quem quer aprender ASM AVR. Entender o funcionamento das instruções em ASM para quem não domina bastante o inglês é complicado.

 

No meu tempo de escovador de bits no PC eu consegui uma ótima literatura em português que explicava profundamente o ASM 8088, e isto me fez evoluir bastante, mas para AVR a literatura é toda em inglês e é bastante difícil entender o funcionamento de algumas instruções ASM para mim que não sou fluente em inglês.

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

@Intrudera6,

 

Ok, vou comentar melhor o trecho em Assembler. Vai ser um excelente exemplo do que se pode fazer com as duas linguagens ao mesmo tempo.

 

Repare que estou fazendo algo mais didático, em vez de apresentar tudo pronto, estou passando todos os trechos do desenvolvimento, minhas dúvidas, meus erros e minhas soluções !!!!  Afinal, é o que acontece com 99,99% dos projetos !

 

Sobre os macetes .... antigamente quando eu era bem mais novo, fazia essas coisas toda hora no Z-80. Afinal, a verdadeira tarefa de um engenheiro é "engenhar" , que eu traduzo como "bolar soluções para um problema e escolher a melhor solução custo/benefício dentre elas" . Mas são truques que hoje em dia os programadores em C não conheçem, e é justamente o calcanhar de Aquiles dos usuários das linguagens em alto nivel. E idéias como essa que me ocorreu de usar o Timer1 interrompendo o loop infinito não podem ser feitas em C, pelo desconhecimento do programador dos ciclos de máquina e detalhes de funcionamento do hardware, como o Pc e o Stack Pointer e funcionamento de uma interrupção em termos do hardware. Fora o problema de usar corretamente todos os "jeitinhos" do compilador !

 

Faz muita diferença um programador aprender tudo em ASM, praticar durante anos, e depois aprender uma linguagem de alto nivel,  e o programador que já aprende direto uma linguagem de alto nível. Sempre o segundo será muito mais limitado nas soluções de problemas, e sempre terá soluções mais lentas e menos eficientes. 

 

Infelizmente, o ASM está indo seguramente ao ostraçismo, mesmo nos microcontroladores..... afinal, este mesmo Atmega1284P tem 128K de memória de programa.... é irreal falar que dá para usar nem a metade disto em ASM !!!! 

 

O maior programa em ASM que eu fiz foi um sistema operacional orientado a controle de processos, em conjunto com um outro engenheiro muito bom ( que era bem mais experiente do que eu e me ensinou muitos truques do CP/M ), chamamos ele de SOCO ( Sistema Operacional para COntrole de Processos ), com ele realizamos alguns trabalhos de automação que geraram objetos de quase 16K de Assembler puro..... Mas o sistema era muito legal, tinha processos escalonados no tempo, processos que se repetiam de tempos em tempos, tinha um núcleo de comunicaçào serial com computadores externos que já tratava a transferência de dados, resumindo era um Kernel muito eficiente. O truque de manipular endereços de retorno do Stack eu aprendí com esse engenheiro, ele escreveu um módulo de Debuguer em tempo real que auxiliava muito no desenvolvimento e depuração das aplicações. Quando saí da empresa o trabalho ficou com eles e se perdeu ....

 

Mas, voltando ao tópico,  creio que a maneira que o Bascom faz a interfaçe com o Asm é genial, muito simples, e permite a um programador escrever rotinas muito rápidas e poderosas, sem precisar saber como compilar um programa em ASM ..... deixe que o Bascom cuida disto !!!!

 

Sobre as instruções que voce não conhece, acho que são alguns truques do compilador do Bascom, eles estão explicados no Help sobre o uso do ASM dentro do bascom, segue o link :

 

http://avrhelp.mcselec.com

 

Quando a página abrir, clique à esquerda em BASCOM LANGUAGE FUNDAMENTALS, e depois clique mais embaixo em MIXING ASM AND BASIC.

 

Vai ter uma explicação de alguns truques para interfacear com variáveis e endereços do Bascom com o Basic.

 

Depois, clique logo embaixo em ASSEMBLER MENMONICS, e vai ter uma relação das instruções dos AVR, bem como uma pequena descrição e os flags que são alterados por elas.

 

Mas já lhe aviso que alguns Atmegas mais novos possuem umas instruções a mais...... só mesmo consultando o datasheet !

 

Sobre um guia mais geral do Assembler dos AVR's, com alguns exemplos e algumas rotínas úteis, eu uso este link aqui :

 

http://www.avr-asm-tutorial.net/avr_en/index.html

 

Sugiro que voce baixe o primeiro arquivo para ler em seu computador :

 

http://vg09.met.vgwort.de/na/2837cf90914640d8ab431ac278cbbe1c?l=http://www.avr-asm-download.de/beginner_en.pdf

 

Vou comentar os trechos em Assembler para ajudar o entendimento e posto hoje à noite. Mas já deixo claro que não sou muito experiente em ASM desses AVR's, mas pelo que vejo ainda dá pro gasto kkkkk !

 

Paulo

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

@Intrudera6,

 

Vou explicar aqui um pouco sobre o código utilizado no programa em Bascom, que faz a captura dos dados a cada 5 ciclos de clock.

Em primeiro lugar, preciso explicar um pouco sobre a maneira que o Bascom funciona.

Mas antes temos de ver a estrutura do AVR.


Essa família de microcontroladores possuem 32 registradores internos, numerados de R0 até R31.

Do R24 até o R31 eles podem ser usados em pares, como se fossem registradores de 16 bits.

E do R26 até o R31 eles são agrupados em pares que podem servir de índice de 16 bits para transferência de informações, e nesse caso eles são tratados por um nome diferente :

R26 / R27 – X
R28 / R29 – Y
R30 / R31 – Z


Esses registradores de 16 bits X , Y e Z permitem instruções muito poderosas quando usados como indexadores, pois algumas instruções de transferência de dados já incrementam / decrementam esses registradores de forma automática ! Assim , eles são a escolha natural quando precisamos fazer uma série de transferências entre a memória e o microcontrolador de forma seqüencial.

Além desses registradores, ainda temos o SREG ( Status Register ) , que armazena todos os flags que podem ser alterados pelo resultado de uma instrução, e temos ainda o Stack Pointer e o Program Counter.

 

Devido a essa fartura de registradores, sempre tentamos trabalhar mantendo os resultados neles, pois as operações são sempre mais rápidas.

Cada comando na linguagem Basic é transformado pelo compilador em uma série de instruções em Assembly.

E entre duas instruções em Basic, podemos colocar nosso código em Assembler , observando alguns cuidados sobre os conteúdos de registradores que devemos manter sempre inalterados ou o programa pode ficar perdido ou fazer coisas inesperadas.

Portanto, buscando essa informação na Internet, sabemos quais registradores não deveremos mexer sob hipótese nenhuma. Por outro lado, podemos usar muitos deles livremente, sem nenhum problema de alterar o conteúdo.

Por outro lado, um uso muito comum de programas em ASM é justamente para fazer uma rotina de interrupção, e nesse caso temos de tomar muito cuidado para não alterar o conteúdo do SREG.

Para isso é que existem instruções do tipo PUSH .

Essa instrução pega o conteúdo de um registrador e o coloca no Stack Pointer, para que posteriormente retiremos esse conteúdo com uma instrução POP que vai devolver o conteúdo ao registrador.

Um esqueleto de rotina muito usada em interrupção é esse abaixo :

$Asm

    PUSH R0
    IN R0,SREG
    PUSH R0
    PUSH R26
    PUSH R27
    .......
    .......
    .......
    POP R27
    POP R26   
    POP R0
    OUT SREG,R0
    POP R0
$End Asm

Reparem que logo que acontece a interrupção, salvamos no Stack o conteúdo de R0, para que possamos copiar o conteúdo de SREG nele, e salvamos novamente no Stack.
Assim, podemos usar livremente o registrador R0 e podemos alterar o SREG sem nenhum problema.
Para podermos ter alguns registradores extras para o nosso programa, também salvo no Stack os registradores R26 e R27 ( o famoso par X ), assim posso usar o X como um índice para as transferências de dados entre o microcontrolador e a memória.
Depois disto, faço todo o código de meu programa, e ao final eu desfaço exatamente na mesma sequência todos os dados que foram colocados no Stack, devolvendo os mesmos aos seus registradores.
Assim, ao final tudo está exatamente da mesma maneira que estava e não vai afetar o funcionamento do resto do programa.

Cabem aqui mais algumas observações sobre o uso dos registradores do AVR.

Os registradores que estão alocados logo no início do endereçamento, isto é, entre a posição 00 e a $3F podem ser acessados por várias instruções em Assembly, e a maneira mais rápida de transferir informações é com o uso dos comandos IN e OUT.
Como o SREG é um deles, é natural usarmos isso para transferir dados entre o SREG e os outros registradores.

A instrução IN R0,SREG transfere o conteúdo de SREG p[ara o registrador R0.
A instrução OUT SREG,R0 transfere o conteúdo de R0 para o registrador SREG.

Já para registradores alocados entre o endereço $60 até o endereço $FF não podemos usar o IN e nem o OUT, e temos de usar as instruções LD/LDS/LDD  e ST/STS/STD.

Agora, uma particularidade do Bascom : existem algumas instruções em ASM que tem exatamente o mesmo nome no Bascom, mas claro que fazem coisas totalmente diferentes.

Para evitar problemas, o Bascom pede que quando escrevermos uma dessas instruções em ASM coloquemos na frente dela um ponto de exclamação ( ! ) .
Esse é o motivo de vocês verem na listagem do Bascom algo tipo !OUT  .....

Agora, outras facilidade que o Bascom nos oferece :

Quando declaramos no Bascom uma variável do tipo Word ( 16 bits ) , o conteúdo dessa variável pode ser transferido para o ASM, simplesmente usando o seguinte comando :

LDS R26,{nome da variável)
LDS R27,{nome da variável + 1)

Apenas para ilustrar, a instrução LDS significa Load Data from Sram, isto é, carrega um registrador com o conteúdo do endereço dado.

Se colocarmos um Label em nosso programa, o endereço real de memória da posição do Label também é passada exatamente da mesma maneira !

Os registradores de I/O internos ao AVR, que são relativos ao hardware, podem ser acessados pelo mesmo nome que eles possuem no Datasheet, e nesse caso o Bascom nos mostra sempre esses nomes na cor vermelha.

Um exemplo em meu programa é o registrador de I/O chamado de TCNT1L e TCNT1H, que são os registradores da contagem do Timer1. Como é um registrador de 16 bits, existe o primeiro byte que é o menos significativo (porisso chamado de TCNT1L) , e o segundo byte é o mais significativo ( TCNT1H ).

Claro que ainda tem muita coisa a ser entendida sobre os AVR’s, como o uso das instruções rápidas para os primeiros registradores do endereço $00 até o $1F, mas como não usei isto aqui, deixo para o leitor pesquisar....

Vamos ao trecho em ASM propriamente dito :






$asm

‘Primeiro, vamos salvar o conteúdo dos registradores que iremos usar

      PUSH  R16
      IN    R16, SREG
      PUSH  R16
      PUSH  R17
      PUSH  R26
      PUSH  R27

'Agora, a variável Bcounter possui o endereço inicial da matriz criada para armazenar
‘ os valores das leituras.
‘Se prestarem mais atenção, vão reparar que eu FIXEI o endereço inicial da matriz
‘ usando no final do DIM a expressão “AT &H180” , isto é, o primeiro elemento dessa
‘ matriz está na posição $0180, o segundo está em &0181, e assim por diante.
‘ Vamos carregar o registrador X com esse endereço.

      LDS   R26, {bCounter}
      LDS   R27,{bcounter+1}

'Agora, vamos carregar outros registradores com o conteúdo da variável TEMP, que
‘ tem armazenado o numero de ciclos de clock que precisamos aguardar até que todos
‘ os 8190 elementos da matriz estejam preenchidos com as leituras.

      LDS    R17,{TEMP}
      LDS    R16,{TEMP+1}

‘A seguir, temos de colocar esses valores dentro do registrador da contagem do
‘ Timer1. Como esse é um registrador de I/O acima da faixa permitida para uma
‘ instrução OUT, temos de usar a STS, que significa Storage Sram from Register.
‘Aqui existe uma sequência obrigatória, de acordo com o Datasheet sempre temos
‘ de começar pela parte mais significativa e a seguir a menos sognificativa.

      STS   TCNT1H,R16
      STS   TCNT1L,R17

'Agora, vamos começar a fazer as leituras da maneira mais rápida possível,
‘ que é lendo a entrada do PORT A com o comando IN, e a seguir armazenamos
‘ o valor lido no endereço apontado pelos registrador X, e já incrementamos esse
‘ endereço para que aponte para a próxima posição a ser armazenada a leitura !
‘ Depois disto, pulamos para o início desse loop, usando uma instrução de Jump
‘ relativo, que é a RJMP , e que consome apenas dois ciclos de clock.
‘Assim, no total, perdemos apenas 5 ciclos de clock nesse loop !

Pode_ler:
      IN    R16,PINA                                      
      ST    X+,r16                                         
      RJMP PODE_LER

‘Agora, quando terminarmos de fazer todas as leituras, este trecho a seguir retira
‘ os valores que foram armazenados no Stack, para que o programa prossiga
‘ normalmente.
‘Claro que isto nunca aconteceria se eu não conseguisse colocar o endereço  do label
‘ “Acabou” no Program Counter, o que será feito manipulando o Stack na rotina
‘ de interrupção do Timer1 mostrada adiante.

Acabou:
      POP R27
      POP R26
      POP R17
      POP R16
      !OUT  SREG,R16
      POP   R16
$end Asm

Stop


‘ A seguir, o Label Int1_sbr  é para onde vai ser jogado o Program Counter quando
‘ ocorrer a interrupção do Timer1.
‘ Reparem que ao final do comando em Basic ON INTERRUPT ...... eu usei a
‘ cláusula NOSAVE, isto é, o Bascom não salva o conteúdo de nenhum registrador
‘ e assim eu tenho certeza que logo aonde o Stack Pointer está apontando é o endereço
‘ para onde o programa vai voltar a executar ao fim da interrupção.
‘Normalmente voltaria para dentro daquele loop infinito, pois só poderia estar sendo
‘ executada alguma daquelas 3 instruções !
‘ E é aqui que eu faço o truque :
‘ Descarto o endereço que estava no Stack usando dois POPS, a seguir carrego o
‘ endereço do label “Acabou” , e coloco ESSE ENDEREÇO no Stack !
‘Assim, quando sair da interrupção, O Program Counter vai ser carregado com o
‘endereço que eu desejo !

Int1_sbr:

$asm

 

'Vamos descartar os bytes alto e baixo do endereço armazenado no Stack

POP R16
POP R17

 

'A seguir, a maneira de se carregar um endereço a partir de um label, e está documentada no Help

' do Bascom, no link que indiquei acima.

 

LDI R26, LOW(ACABOU *1)
LDI R27, HIGH(ACABOU *1)

 

'Agora, vamos RECOLOCAR o novo endereço no Stack

 

PUSH R26
PUSH R27

$End Asm

‘O comando abaixo RETURN é do Bascom, e vai finalizar a interrupção para nós !

Return
 

 

 

Espero que tenham conseguido entender melhor.

Paulo


 

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

Projeto de um Analizador Lógico Portátil com Atmega1284P - PARTE 4  -  FREQUENCÍMETRO

 

Já vimos a parte mais complicada, que é a captura em altíssima velocidade para podermos capturar os sinais, muito necessária para um bom analisador.

 

Vamos para um assunto um pouco mais fácil, que é a implementação de um frequêncímetro, o qual pode medir sinais até 1 Mhz com um erro menor do que 5 hz, podendo chegar até 10 Mhz com um erro máximo de medida em torno de 300 Hz. Esse erro, que é crescente a partir de 1 Mhz, é causado pelo fato de estarmos usando Timers internos de 16bits. Explicarei com mais detalhes mais abaixo.

 

Para mantermos o hardware o mais simples possível, não vamos acrescentar nenhum CI extra, que foi o que eu fiz no projeto que apresentei no meu Tutorial. Aqui, como vamos usar um cristal comum de 20 Mhz, ele possui uma relativa variação térmica ( 100 partes por milhão por grau Celsius... ), e o seu valor pode variar significativamente com a temperatura. Além disso, a tolerância de fabricação também não é das melhores, e nesse caso eu acredito que os erros causados pelos algoritmos com timers de 16 bits será menor do que os causados pelo baixo custo do cristal !

 

Dito isto, vamos ao circuito utilizado :

 

 

t892ki.jpg

 

 

Mais uma vez, muito simples, e agora usaremos a entrada T1 do Timer1 para receber a frequência a ser medida.

 

Usei um programa disponível na Internet, o qual foi escrito para usar um Attiny , mas do jeito que ele foi escrito apresenta um erro crescente mesmo quando a frequência passa dos 50 Khz. Assim, usei o ASM junto com o Basic, e assim os erros só aparecem a partir de 1 Mhz.

 

Segue o programa  :

$regfile = "m1284PdefOK.dat"
$crystal = 20000000
$hwstack = 52
$swstack = 52
$framesize = 32
' vamos usar uma lib especializada para calculos usando
' numeros do tipo DOUBLE para termos a maior precisaão.
$lib "double.lbx"
Disable Jtag
' Precisamos configurar o pino que vai receber a frequência
' a ser medida como ENTRADA.
Config Pinb.1 = Input
Config Timer1 = Counter , Edge = Rising
'
Ddrb.1 = 0
'
Portb.1 = 1
'-------'
Timer3 = 52084
Config Timer0 = Timer , Prescale = 1024
'
Tccr3a = &B01000000
'
Tccr3b = &B00000101
'
Tccr3c = &B10000000
'
Ocr3ah = &B10000000
'
Ocr3al = &B00000000
'---------------
Enable Ovf1
Enable Ovf0
Enable Interrupts
' Agora, para que percamos o minimo possível de tempo, vamos
' habilitar as interrupções e dizer ao Bascom que não precisa
' salvar nenhum registrador, pois faremos isso dentro de nossas
' próprias rotinas de interrupção.
'---------------------
On Ovf1 Pulse_counter Nosave
On Ovf0 Controle Nosave
'-------------------
Dim A As Double
Dim B As Byte
Dim B1 As Byte
Dim A_temp As Single
Dim A_display As Long
Dim Flag_mostrar As Bit
B = 0
B1 = 0
'-----------------------
Start Timer0
'------------
Do
' Vamos esperar o total de 76 contagens do timer0
' que será sinalizada pelo flag Flag_mostrar = 1
'
If Flag_mostrar = 1 Then
' Vamos parar imediatamente a contagem dos Timers para ter o menor
' erro possível com um hardware tão simples.
  Stop Timer1
  Stop Timer0
  Disable Interrupts
' Vamos zerar o flag para a próxima vez, e chamaremos a rotina de
' calculo do valor para mostrar.
  Reset Flag_mostrar
  Gosub Displays
' Agora, já mostramos a frequência e podemos voltar a fazer tudo
' de novo ....  
  Enable Interrupts
End If
Loop                                                        
'
end program
'--------------Displays:
' Agora temos de calcular quantas vezes fomos interrompidos.
' Como o contador 1 é de 16 bits, ele gerou uma interrupt
' a cada 65536 ciclos na sua entrada de frequência.
' Assim, como em cada interrupt do Timer1 a variável B era incrementada,
' temos de multiplicar B por 65536, e ainda temos de somar a contagem
' atual do Timer1.
A_temp = B * 65536
A_temp = A_temp + Counter1
' Agora, para fazermos uma conta bem certinha, vamos converter o nosso
' numero obtido para o tipo DOUBLE .
A = A_temp
' E aqui está o truque .....
' O Timer0 está configurado com PRESCALER = 1024, assim temos uma
' contagem a cada 20000000/1024 = 19531,25 ciclos de clock.
' O timer0 conta até 256, e gera uma interrupção, assim temos uma
' interrupção a cada 76,2939453125 ciclos de clock.
' Este tempo é muito preciso pois é gerado no hardware do Timer.
' Também significa que em essa contagem é atingida exatamente em 1 segundo !
' Se fosse possível contar as interrupções até exatamente esse numero,
' o valor de A_temp seria a nossa frequência, sem precisar de nenhuma
' conversão ! Mas temos o problema de trabalharmos apenas com numeros
' inteiros de 8 bits para ganharmos a máxima velocidade possível.
' Então, se fizermos uma comparação da contagem do Timer0 com o numero
' inteiro 76, a contagem de ciclos que obtivemos em A_TEMP é um pouco
' menor, e então para compensar vamos fazer uma multiplicação do
' numero em A_TEMP :  A_TEMP * 76,2939453125 / 76 e assim corrigimos
' a nossa amostragem para o que seria obtido no tempo exato de 1 segundo !
' Reparem que 76,2939453125 / 76 resulta em 1,00386770148  !
A = A * 1.00386770148
A_display = A
'
'--- Pronto, o valor está em A_DISPLAY ---
'
' Basta mostrarmos de alguma maneira e voltar a medir.
' Vamos zerar as contagens, e ligar de novo os contadores
B = 0
B1 = 0
Counter1 = 0
Start Timer0
Start Timer1
Return

'-------------------------------------------------------------
' Rotina para contar os pulsos na entrada do Timer1
Pulse_counter:
$asm
PUSH  R16
IN    R16,SREG
PUSH  R16
push  R26
PUSH  R27
PUSH  R24
'Agora vamos incrementar a variável B
Loadadr B , X
LD    R24,X
SUBI  R24,$FF
ST    X,R24
POP   R24
POP   R27
POP   R26
POP   R16
!OUT  SREG,R16
POP   R16
$end Asm
Return

'-----------------------------------------------------
' Rotina de controle do tempo para calculo e exibição
Controle:
$asm
PUSH  R16
IN    R16,SREG
PUSH  R16
push  R26
PUSH  R27
PUSH  R24
' Vamos incrementar a variável B1
Loadadr B1 , X
LD    R24,X
SUBI  R24,$FF
ST   X,R24
'E vamos ver se chegou a 76 interrupções
cpi   r24,76
brne  controle_saida
$end Asm
' Se chegamos aqui, já tivemos 76 interrupções
' e temos de ligar o flag para sinalizar ao programa principal
Set Flag_mostrar
$asm
' E agora podemos sair da interrupção.
Controle_saida:
POP   R24
POP   R27
POP   R26
POP   R16
!OUT  SREG,R16
POP   R16
$end Asm
Return
End

O truque é usar o Timer1 como Counter, e ir totalizando as contagens que ocorrem durante o tempo exato de 1 segundo. Assim, essa contagem pode ser apresentada diretamente como a frequência do sinal !

 

Mas, como fazer isso se o Timer1 é de 16 bits apenas, podendo armazenar no máximo 65535 ????  Se tivermos de medir uma frequência tipo 100 Khz, o timer irá estourar !

Então, vamos gerar uma interrupção, e iremos armazenando a quantidade de vezes que o Timer1 estourou , assim quando tivermos o tempo exato de 1 segundo basta pegarmos a quantidade de vezes que estourou a contagem, multiplicar por 65536, e somar a contagem atual existente no Timer1, e teremos a nossa frequência !

 

O problema nosso é justamente esse : sabermos o tempo exato de 1 segundo !!!!  Como só temos timers de 16 bits, teremos de usar interrupção de qualquer maneira. ( seria muito bom pelo menos um Timer de 32 bits !!! )

 

Então, temos livre o Timer0. Se fizermos um prescaler de 1024, teremos uma contagem a cada 20000000 / 1024 = 19531,25 ciclos de clock . Mas infelizmente esse número não é inteiro ...... bom, de qualquer maneira, se programamos uma interrupção no estouro deste Timer0, teremos um estouro a cada 256 contagens, o que nos gera uma interrupção a cada  19531,25 / 256 = 76,2939453125 ciclos de clock. Embora esse numero não é inteiro, significa que se fizermos uma contagem das interrupções geradas nesse Timer0, quando atingimos esse numero exato de 76,2939453125 , terão se passados EXATAMENTE 1 SEGUNDO !!!!

 

Como não conseguimos uma contagem fracionada no hardware, vamos usar um contador simples tipo byte, e quando esse numero atingir 76, terão se passados quase 1 segundo. Isto significa que a contagem de pulsos será um pouco menor do que a real que ocorreria durante 1 segundo exato, então temos de multiplicar a contagem obtida por um fator de correção, e esse fator é exatamente dado por  76,2939453125 / 76 =  1.00386770148 !

 

Assim, resolvemos o problema, e temos o numero bem preciso .

 

Dentro do programa estão os comentários mais completos sobre o funcionamento.

 

O truque de usar alguns trechos em ASM faz com que os tempos perdidos nas interrupções sejam cerca de 20% do tempo que seria perdido usando apenas o Bascom, e isso é o que fez o programa continuar com leituras bem precisas, mesmo com altas frequências. De fato, em meus testes, mesmo quando leio uma frequência de 10 Mhz, o erro é menor do que 300 Hz, inferior à variação do cristal para 15 graus de temperatura, fora a imprecisão do mesmo !

 

Segue em anexo os fontes e os arquivos para simulação no Proteus.

 

Paulo

frequencímetro.rar

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

Mexer com essas paradas de tempo é muito bom (quando se sabe assembly HEHE)... Muito bacana!

Eu acho que consegui entender, um timer conta os pulsos enquanto outro timer conta o tempo, não estava entendendo a parte:

 

'Agora vamos incrementar a variável BLoadadr B , XLD R24,XSUBI R24,$FFST X,R24

 

mas agora entendi:

 

Mas, como fazer isso se o Timer1 é de 16 bits apenas, podendo armazenar no máximo 65535 ????  Se tivermos de medir uma frequência tipo 100 Khz, o timer irá estourar! Então, vamos gerar uma interrupção, e iremos armazenando a quantidade de vezes que o Timer1 estourou [...]

 

O frequencímetro e o analisador estarão em um único ATmega? Só poderá ser usado um de cada vez né?

 

seria muito bom pelo menos um Timer de 32 bits !!!

 

Recebi um PIC32MX com ele da pra combinar 2 timers de 16 e criar o de 32...  Recebi mas não posso usá-lo até comprar o PICKIT 3, pelo menos a microchip dá um desconto bacana e não cobra frete HEHE! Vou aproveitar e arrumar um PIC32MZ... São overkill para hobby mas fazer o que né HEHE!

 

Show de bola!

Link para o comentário
Compartilhar em outros sites

Só para constar.

Fiz um frequencímetro com PIC16F628A.

Usei o TIMER0. Ele possui apenas um registro com 8 bits, o TMR0. Possui um prescaler com 8 bits. Até aí só contaria até 65k.

Incluí mais dois registros de propósito geral.  Com isso o número de contagens pode chegar a 4,3 bilhões. Notar que com apenas três registros (prescaler, TMR0 e mais um de propósito geral) a contagem poderia alcançar 16,8 milhões. Como o TIMER0 responde até 40 ou 50 MHz, então tem que possuir os quatro registros mesmo.

O pulo do gato é que o período de contagem é de 1,000.000 s (com seis casas decimais mesmo). Criei um loop com exatos 5.000.000 de ciclos em assembler. No início do programa eu conto o período com o clock do cristal e comprovam 5.000.000 contagens. Com o cristal de 20MHz, cada clock fica em 200ns, que multiplicados por 5.000.000 fornecem 1,000.000s.

Durante este loop, monitoro o transbordo do timer0 com uma frequência maior que o TIMER0 leva para transbordar. Assim, não ocorrem dois transbordos sem que eu identifique o primeiro.

Se houve transbordo, incremento o primeiro registro, caso ele transbordou, incremento o segundo registro.

Qualquer que seja o percurso do programa durante este período, o número de clocks são exatos 5.000.000.

Incluí um ajuste da frequência do cristal, caso haja a possibilidade de se calibrar com um frequencímetro com maior precisão.

Coloquei um amplificador para sinais de menor tensão e para entradas TTL. O único problema foi que o amplificador e o chip 74HC32 não responderam além de uns 15MHz.

NUNCA MAIS FAREI UM TRABALHO ASSIM. DEU MUITO TRABALHO...No máximo poderei usar este frequencímetro em outro projeto.

 

MOR_AL

Link para o comentário
Compartilhar em outros sites

@MOR,

 

Eu ví algo desse tipo para Pic, realmente ele chega até uns 40 Mhz , é incrível que o Timer0 consiga fazer essa contagem no modo assíncrono !

Mas dá mesmo um trabalho enorme ficar acertando tudo em ASM para que dê de qualquer maneira o tempo exato de 1 segundo..... nem tentei fazer isso aqui pois me cansei só de pensar kkkkk !

 

@test man*~,

 

Tudo vai rodar no mesmo Atmega1284P, minha ideia é um Menu onde voce possa escolher qual o tipo de instrumento que vai rodar , um de cada vez !

 

Estou implementando o analizador de I2C com deteção automática de frequência.... logo postarei aqui ele também, e depois um pequeno osciloscópio digital, pena que a telinha não tenha muita resolução, mas vai dar para o gasto !

 

Paulo

Link para o comentário
Compartilhar em outros sites

Após minha resposta acima, hoje fiquei pensando em uma coisa : nos Avrs, os Timers são síncronos, isto é, dependem do clock interno da CPU para serem amostrados. Portanto, logo de cara, temos o problema do Teorema de  Nyquist, que diz que só poderemos medir sinais cuja frequência máxima seja a metade da frequência de amostragem. No nosso caso, seria de 10 Mhz.

 

Porém, existe um documento da ATMEL, dizendo que isto seria válido para sinais perfeitamente simétricos, tipo 50% em nivel alto e 50% em nivel baixo. Como nem sempre é o caso, a ATMEL recomenda medir no máximo frequencias 2,5 vezes menores do que a do clock da CPU.

 

Assim, em nosso caso, com clock de 20 Mhz, poderemos medir frequências até 8 Mhz.

 

Antes que me falem isto, existe uma maneira de o Timer2 trabalhar de modo assíncrono em alguns AVR's, mas exclusivamente para trabalhar como clock externo tipo 32768 Khz,  que não é o nosso caso.

 

Então , fiquei pensando em uma maneira de fazer um programa em que o erro fosse o menor possível, e resolví inverter o uso dos Timer0 e Timer1 do programa acima, e cheguei a um erro máximo de 256 ciclos de clock em um total de 20 Milhoes, o que dá um erro menor do que 13 partes por milhão, menor do que o erro causada pela variação de temperatura em um ÚNICO GRAU CELSIUS !!! Digo isto porque os cristais comuns apresentam até 50 partes por milhão por grau Celsius .

 

Neste programa, levei em conta até os ciclos de clock envolvidos nos comandos de ligar os Timers, e na entrada da rotina de interrupção, e esses erros foram descontados nos cálculos. Mas, mesmo assim, porque existe esse erro máximo de 12,8 partes por milhão ?

 

Existe porque não da para dividir 20 Mhz por 1024 e obter um número inteiro ! Temos sempre de usar um multiplicador para corrigir para o tempo exato de 1 segundo. Um erro mínimo de tempo que causa um erro de uma contagem para mais ou para menos na contagem total do Timer0, quando é multiplicada pelo fator de correção para exatos 1 segundo, pode resultar em um erro de 256 contagens supondo uma frequência alta de 20 Mhz !

 

Ou seja, por causa desse ajuste, introduzimos um erro máximo de 256 contagens para cima ou para baixo da real. Daqui resulta erro de 12,8 partes por milhão.

 

Na verdade, se fosse possível usar o Timer0 no modo Assíncrono, poderiamos medir até 20 Mhz com erro máximo de 1 Hertz !

 

Ou se usarmos um cristal múltiplo exato de 1024, esse erro some, e teremos um erro máximo de 1 Hz nas leituras.

 

Vamos ao novo programa :

 

Lembrando que agora, o sinal a ser medido entra na entrada T0 , isto é, no pino Portb.0 do Atmega1284P.

$regfile = "m1284PdefOK.dat"
$crystal = 20000000
$hwstack = 52
$swstack = 52
$framesize = 32
' vamos usar uma lib especializada para calculos usando
' numeros do tipo DOUBLE para termos a maior precisão.
$lib "double.lbx"
Disable Jtag
' Precisamos configurar o pino que vai receber a frequência
' a ser medida como ENTRADA.
Config Pinb.0 = Input
Config Timer0 = Counter , Edge = RisingConfig Timer1 = Timer , Prescale = 1024
'---------------
Enable Ovf1
Enable Ovf0
Stop Timer0
Stop Timer1
Timer1 = 46005
Counter0 = 0
Enable Interrupts
' Agora, para que percamos o minimo possível de tempo, vamos
' habilitar as interrupções e dizer ao Bascom que não precisa
' salvar nenhum registrador, pois faremos isso dentro de nossas
' próprias rotinas de interrupção.
'---------------------
On Ovf0 Pulse_counter Nosave
On Ovf1 Controle Nosave
'-------------------
Dim A As Double
Dim B As Word
Dim B1 As Word
Dim A_temp As Single
Dim A_display As Long
Dim Flag_mostrar As Bit
B = 0
B1 = 0
'-----------------------
' tudo pronto, vamos habilitar o timer de 1 segundo e o contador :
Start Timer1
Start Timer0
'------------
Do
If Flag_mostrar = 1 Then
  Stop Timer1
  Stop Timer0
  Disable Interrupts
' Vamos zerar o flag para a próxima vez, e chamaremos a rotina de
' calculo do valor para mostrar.
  Reset Flag_mostrar
  Gosub Displays
' Agora, já mostramos a frequência e podemos voltar a fazer tudo
' de novo ....
  Enable Interrupts
End If
Loop                                                        
'
end program
'--------------
Displays:
' Agora temos de calcular quantas vezes fomos interrompidos.
' Como o contador 0 é de 8 bits, ele gerou uma interrupt
' a cada 256 ciclos na sua entrada de frequência.
' Assim, como em cada interrupt do Timer0 a variável B era incrementada,
' temos de multiplicar B por 256, e ainda temos de somar a contagem
' atual do Timer0. Esses valores foram capturados no momento da
' Interrupção de 1 segundo, e a contagem em B foi passada para a
' variável A_TEMP .
A_temp = A_temp * 256
A_temp = A_temp + B1
' Agora, para fazermos uma conta bem certinha, vamos converter o nosso
' numero obtido para o tipo DOUBLE .
A = A_temp
' Agora ... tivemos 5 ciclos de clock extras para a interrupçao, e
' mais 3 ciclos até travar o numero do timer0, total de 8.
' Porém, entre ligarmos o Timer1 e depois o Timer0 perdemos 4 ciclos de clock,
' então é justo descontarmos apenas 4 ciclos !
' Portanto, temos de compensar a contagem, diminuindo o numero pelo fator
' 20000004/20000000 = 1,0000002
A = A / 1.0000002
' E aqui está o truque .....'
O Timer0 está configurado com PRESCALER = 1024, assim temos uma
' contagem a cada 20000000/1024 = 19531,25 ciclos de clock. Temos um erro
' equivalente a 0,25 * 1024 = 256 ciclos de clock que faltariam para
' termos a contagem exata de 20000000 . Assim temos de multiplicar
' por 20000000 /( 20000000 - 256 ) = 1,00001280016
'
'
' Este tempo é muito preciso pois é gerado no hardware do Timer.
' Também significa que essa contagem é atingida exatamente em 1 segundo !
' Se fosse possível contar as interrupções até exatamente esse numero,
' o valor de A_temp seria a nossa frequência, sem precisar de nenhuma
' conversão ! Mas temos o problema de trabalharmos apenas com numeros
' inteiros de 8 bits para ganharmos a máxima velocidade possível.
' Então, se fizermos uma comparação da contagem do Timer0,
' a contagem de ciclos que obtivemos em A_TEMP é um pouco
' menor, e então para compensar vamos fazer uma multiplicação do
' numero em A_TEMP : A_TEMP * 1,00001280016 e assim corrigimos
' a nossa amostragem para o que seria obtido no tempo exato de 1 segundo 
!' Podemos dizer que nosso frequencímetro é preciso em até 12,8 partes por
' milhão ( 256 / 20 = 12,8 ) , que é muito inferior à variação térmica de um
' cristal oscilador !
A = A * 1.00001280016
A_display = A
'
'--- Pronto, o valor está em A_DISPLAY ---
'
' Basta mostrarmos de alguma maneira e voltar a medir.
' Vamos zerar as contagens, e ligar de novo os contadores
B = 0
B1 = 0
Timer1 = 46005
Counter0 = 0
Start Timer1
Start Timer0
Return
'-------------------------------------------------------------
' Rotina para contar os pulsos na entrada do Timer1
Pulse_counter:
$asm
PUSH  R24
IN    R24,SREG
PUSH  R24
push  R26
PUSH  R27
'Agora vamos incrementar a variável B
Loadadr B , X
LD    R24,X
SUBI  R24,$FF
ST    X+,R24
ld    R24, X
SBCI  R24,$FF
ST   X,R24
' Pronto, agora é só sair .
POP   R27
POP   R26
POP   R24
!OUT  SREG,R24
POP   R24
$end Asm
Return
'-----------------------------------------------------
' Rotina de controle do tempo para calculo e exibição
Controle:
$asm
' Agora, o mais importante é salvar a contagem do Counter0 da maneira
' mais rápida possível.
PUSH  R24
IN R24,counter0
' Pronto, contagem atual já lida, vamos armazenas em B1 .
PUSH  R26
PUSH  R27
Loadadr B1 , X
ST    X,R24
'Contagem atual foi guardada em B1.
POP   R27
POP   R26
POP   R24
$end Asm
Pushall
' Aqui podemos perder o tempo que quisermos salvando os registradores ....
' E vamos aproveitar e setar o flag para avisar o programa principal
' e já copiaremos o valor de B para a variável A_TEMP
Set Flag_mostrar
A_temp = B
Popall
Return
End

Paulo

Link para o comentário
Compartilhar em outros sites

Projeto de um Analizador Lógico Portátil com Atmega1284P - PARTE 5  -  Display NOKIA 3310

 

Pretendo usar este display, por ser um dos mais baratos disponíveis no mercado. Possui resolução de 84 x 48, e dá para passar uma ideia geral de uma forma de onda capturada no modo Osciloscópio, o qual vou descrever ao longo deste projeto.

.

Porém, temos um probleminha.... a Library disponível no Bascom não permite o modo gráfico, isto é, permite apenas o uso no modo texto, pois este display não prevê a leitura de sua memória RAM interna, e assim não é possível setar um único ponto no display.

 

Resumindo : uma operação de Escrita nesse display envolve sempre um Byte , e afeta oito pontos do display ao mesmo tempo !

 

Assim, a única coisa que restou fazer é justamente a criação de um Buffer de vídeo na Ram do Atmega1284p, afinal ele é uma festa em termos de Ram : 16K !

 

Para manter todos os pontos, preciso de um Buffer de 84 x 48 / 8 = 504 bytes de Ram. O complicado é ler o Datasheet do danado e entender como é feito  o posicionamento dos bytes no display.

 

Este é o display que usarei, reparem o pequeno tamanho  :

 

tiny_copter.jpg

 

Ele permite uma boa visualização :

 

nokia3310_pic_programming.jpg

 

O datasheet é este aqui :

 

http://eia.udg.edu/~forest/PCD8544_1.pdf

 

O que nos interessa no momento são so desenhos abaixo :

 

1z4bodh.jpg

 

Eles mostram a alocação dos bytes conforme a posição no display.

 

Eles estão alinhados assim : cada coluna possui 6 bytes, num total de 48 pontos. Esses bytes são alinhados de cima para baixo, sendo que o primeiro ponto corresponde ao LSB do byte, e o ultimo ponto corresponde ao MSB.

 

Existem 84 colunas no total, e o CI permite que indiquemos qual a coluna que iremos escrever, qual a posição do byte que vamos escrever na coluna, e podemos enviar esses bytes sequencialmente num máximo de 6 bytes para cada coluna.

 

Se pensarmos em termos de gráfico cartesiano, o fabricante do CI faz o ponto mais à esquerda, no topo, ser a posição (0,0), e o ultimo ponto mais à direita, na parte inferior, é a coordenada ( 83,47).

 

Estamos muito mais acostumados a seguir o eixo Cartesiano, que indica que o ponto (0,0) fica abaixo à esquerda, e o ponto (83,47) fica à diretia, em cima. Esta será a maneira que trabalharemos quando quisermos posicionar algum ponto.

 

Então, nosso programa vai ter de fazer esse trabalho de conversão dos pontos no eixo vertical, pois quando quisermos acender o ponto (0,3) temos de enviar ao CI o ponto (0,44) !

 

Agora, os problemas de software :

 

- A escrita do buffer tem de ser a mais rápida possível, pois se quisermos atualizar a tela muitas vezes por segundo, esse tempo tem de ser bem pequeno. Por exemplo, se quisermos atualizar o display 30 vezes por segundo, temos de demorar no máximo cerca de 33 milisegundos em cada atualização !

 

- O posicionamento no buffer e a correspondente setagem do bit desejado tem de ser feito muito rápidamente também, para não prejudicar a visualização.

 

- Da mesma maneira, se vamos ter uma função que "acende" um ponto no display, temos de ter uma função que apaga esse ponto também !

 

Inicialmente, escreví a rotina que mostra o buffer no display em Basic mesmo, e consegui o tempo de 22,4 milisegundos, o que já está bom para o que pretendemos fazer; mas, como estou gostando de misturar o Asm com o Basic, resolví reescrever a rotina em Assembler.

 

Aqui é que acontece o que eu falo toda hora prá todo mundo : é muito fácil escrever no Basic do Bascom ! Demorei cerca de meia hora para escrever, testar, corrigir e estar pronta ! Mas quando fui fazer A MESMA COISA em ASM ........ demorei 2,5 horas para deixar tudo redondinho !!!!

 

Mas valeu a pena : agora consigo passar o buffer ao display  em cerca de 18 milisegundos ! Só não diminui muito mais por causa dos tempos envolvidos na comunicação SPI entre o display e o Atmega1284P.

 

As rotinas que acendem um ponto e apagam um ponto fiz em Basic mesmo, mas escolhendo comandos que são muito rápidos, e cheguei a 25 microsegundos para marcar ou desmarcar um ponto no buffer.

 

Nem vou reescrever em ASM pois já ficou bem rápida e atende perfeitamente ao que eu preciso.

 

Para quem quiser baixar a library para o Nokia3310, que é necessária ao uso do display, baixe deste link :

 

http://www.mcselec.com/index2.php?option=com_forum&Itemid=59&page=viewtopic&t=12163

 

Agora, segue o meu pequeno trecho de programa a ser adicionado a qualquer programa que use essa library, basta apenas uma declaração DIM criando um buffer de 504 bytes, e a declaração das 3 subrotinas, bem como as próprias sub-rotinas.

 

Em anexo, segue um pequeno programinha de teste, que fiz usando um Atmega328P ( Arduíno ... ).

Dim Vidram(504) As Byte
Declare Sub Pixel_set(byval X_lin As Byte , Byval Y_col As Byte)
Declare Sub Pixel_reset(byval X_lin As Byte , Byval Y_col As Byte)
Declare Sub Print_screen()

'------------------------------------------
Sub Print_screen()
'Send all the buffer to display
'------------------------------------------
$asm
push R0
push R1
push R20 
push R21 
push R22 
push R23
PUSH R24
PUSH R25
push R26
push R27
PUSH R30
PUSH R31
ldi  R20,0
L1:
$end Asm
Glcdcmd 34
$asm
mov  R22,R20
ldi  R23,128
add  R22,R23
push R23
$end Asm
Glcdcmd R22
Glcdcmd 64
$asm
POP  R23
ldi  R21,0
L2:
mov  R23,R20
ldi  R22,0
ldi  R24,6
mul  R23,R24
mov  R25,R0
mov  R26,R1
mul  R22,R24
add  R26,R0
LDI  R22,0
ADD  R25,R21
ADC  R26,R22
mov  R27,R26
mov  R26,R25
Loadadr Vidram , Z
ADD  R26,R30
ADC  R27,R31
ld   r24,x
$end Asm
Glcddata R24
$asm
INC  R21
cpi  R21,6
brNe L2
inc  R20
cpi  R20,84
Brne L1
POP  R31 
POP  R30 
POP  R27 
POP  R26 
POP  R25
POP  R24 
POP  R23
POP  R22
POP  R21 
POP  R20
POP  R1
POP  R0
$end Asm
End Sub
'-----------------------------------------------------------------------
Sub Pixel_set(x_lin As Byte , Y_col As Byte)
'
'Set pixel in buffer. Works as cartesian coordinates
' from  (0,0) until (83,47).
'-----------------------------------------------------------------------
Local Pi_index As Word
Local Pi_temp As Byte
Local Pi_temp1 As Byte
Pi_index = X_lin * 6
Pi_temp = 47 - Y_col
Pi_temp1 = Pi_temp
Shift Pi_temp1 , Right , 3
Pi_index = Pi_index + Pi_temp1
Incr Pi_index
Shift Pi_temp1 , Left , 3
Pi_temp1 = Pi_temp - Pi_temp1
Pi_temp = 1
Shift Pi_temp , Left , Pi_temp1
Pi_temp1 = Vidram(pi_index)
Pi_temp = Pi_temp Or Pi_temp1
Vidram(pi_index) = Pi_temp
End Sub
'-----------------------------------------------------------------------
Sub Pixel_reset(x_lin As Byte , Y_col As Byte)
'
' Clear pixel in buffer. Works as cartesian coordinates.
'-----------------------------------------------------------------------
Local Pi_index As Word
Local Pi_temp As Byte
Local Pi_temp1 As Byte
Pi_index = X_lin * 6
Pi_temp = 47 - Y_col
Pi_temp1 = Pi_temp
Shift Pi_temp1 , Right , 3
Pi_index = Pi_index + Pi_temp1
Incr Pi_index
Shift Pi_temp1 , Left , 3
Pi_temp1 = Pi_temp - Pi_temp1
Pi_temp = &B11111110
Rotate Pi_temp , Left , Pi_temp1
Pi_temp1 = Vidram(pi_index)
Pi_temp = Pi_temp And Pi_temp1
Vidram(pi_index) = Pi_temp
End Sub

Para acender um ponto no display, uso CALL PIXEL_SET ; para apagar um ponto, uso CALL PIXEL_RESET ; e para mostrar todo o buffer na tela, uso CALL  PRINT_SCREEN .

 

O código em ASM envia em sequência todos os 6 bytes para cada coluna, e envolve uma multiplicaçao de 8 bits por 16 bits. Assim, todos os 504 bytes são enviados na sequência correta para o display.

 

Já as duas rotinas que fazem um ponto acender ou apagar são mais "chatinhas", pois elas tem de achar o byte correto que contém o ponto desejado, e ainda determinar qual o Bit exato que representa o ponto, e alterar apenas esse único bit !

 

Isso envolve algumas multiplicações, adições, e isolamento de bit. Mas para ganhar velocidade, eu usei instruções Shift e Rotate para realizar os cálculos, ganhando velocidade !

 

O que importa é que funcionam, e muito bem !

 

Paulo

NOKIA-3310 LCD example_graphics with points.rar

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

Estamos muito mais acostumados a seguir o eixo Cartesiano, que indica que o ponto (0,0) fica abaixo à esquerda, e o ponto (83,47) fica à diretia, em cima. Esta será a maneira que trabalharemos quando quisermos posicionar algum ponto.

 

Então, nosso programa vai ter de fazer esse trabalho de conversão dos pontos no eixo vertical, pois quando quisermos acender o ponto (0,3) temos de enviar ao CI o ponto (0,44) !

 

Quando fui usar o Processing me perguntei o porquê do plano cartesiano dele ser assim... Dai fui usar aqueles displays 128x64 e o plano também é assim HEHE... Será que eles fazem assim para facilitar na hora da construção?

 

Paulo, existe um programmer & debguger oficial low cost ($40) para os AVRs? Sem um debugger fica complicado dependendo da coisa e o Proteus não possui boa parte dos componentes... 

Link para o comentário
Compartilhar em outros sites

@test man*~,

Olha, eu gostaria de entender isso do fabricante usar sempre de cima prá baixo e da direita prá esquerda.... Pelo menos prá mim é algo que não consigo mentalizar, ainda mais depois de 5 anos de escola de Engenharia, almoçando e jantando eixo cartesiano kkkkkk !

Existem alguns programmers baratos e que funcionam como Debugger também, mas vou te falar uma coisa : usar as versões atuais do Avr Studio é algo quase impossível, a menos que você vire mesmo profissa em Asm.... fora a "enormidade" do programa....

Eu gosto de usar a versão antiga, a 4 , ela é pequena, rápida, e sem muitas complicações, mas desde 2014 o Bascom usa como saída o novo formato, que só pode ser aberto nessas ultimas versões....

Eu uso o Proteus para isso, se você pegar listagem do programa em Bascom e clicar na janela do programa com o botão direito do mouse ele entra no modo Disassembly, onde mostra além da instrução em Basic, todo o código Asm equivalente dela, e eu posso colocar o breakpoint onde eu quiser, além claro de poder executar passo a passo.

Sinceramente, não preciso de nada a mais que o Proteus !

O programador mais em conta e que trabalha com o Avr Studio é o AVRISP MKII , mas eu compraria este aqui por causa da compatibilidade com o windows 8 e de suportar dois protocolos diferentes :

http://www.ebay.com/itm/AVRISP-STK500-MKII-DUAL-USB-programmer-ATMEL-AVR-ISP-PDI-TPI-Xmega-WIN8-DUALPROG-/131088356200?hash=item1e85799368

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...

Ebook grátis: Aprenda a ler resistores e capacitores!

EBOOK GRÁTIS!

CLIQUE AQUI E BAIXE AGORA MESMO!