Ir ao conteúdo
  • Cadastre-se
aphawk

RESOLVIDO AUXÍLIO PARA FAZER UM PEQUENO CÓDIGO EM ASSEMBLY PARA AVR

Recommended Posts

Pessoal, estou terminando um prejeto muito legal para publiicar aqui no fórum.

 

É um analisador lógico portatil, muito barato de se fazer, com um ATMEGA328P a 20 Mhz e um display Nokia5510. 

 

Meu objetivo é que esse aparelho possa mostrar perfeitamente sinais na faixa de 500 Khz, mas no momento estou conseguindo apenas 150 khz, pois trato a interrupção em Basic, e consome muito mais ciclos de máquina do que uma rotina específica para isso, em Assembly.

 

Preciso que alguém me ajude com uma pequena rotina em Assembler , pois eu posso colocar ela diretamente dentro do meu programa em Bascom ( direto dentro da rotina de interrupção ! O mais importante é que ela seja muito rápida na execução, pois estou demorando cerca de 220 ciclos de máquina para fazer isto em Basic... quanto menos ciclos, melhor !

 

O que eu preciso fazer é o seguinte : 

 

Tenho um buffer em RAM, de 1800 bytes. Vamos supor que está iniciando no endereço XXXX.

 

Toda vez que houver uma interrupção, minha rotina faz o seguinte :

-Pàra o TIMER1

-Pega o valor na entrada PORTC e guarda em um registro

-Pega o valor do TIMER1 ( word de 16 bytes ) e armazena o lsb em xxxx e o msb em xxxx+1 .

-Pega o valor do TIMER0 ( Byte ) e armazena em xxxx+2

-Continua o TIMER1

-Pega o registro onde guardei o PORTC e armazena em xxxx+3 .

- Incrementa o contador para xxxx+4

- Verifica se ele ultrapassou o tamanho máximo do buffer (1800 bytes)

- Se ultrapassou, coloca 0XFF em uma determinada posição de memória ( chamarei de FLAG_XXXX) e desabilita o TIMER1. Após isto, termina a rotina.

- Se não ultrapassou, termina a rotina.

 

Ou seja, a cada interrupção, repetirei esse processo, até o Buffer ser totalmente preenchido.

É muito importante que essa rotina use o menor número de registradores do AVR.

 

Não me parece difícil de ser feita, usando os pares de registradores próprios para isso, mas não gostaria de ter de ficar aprendendo mais este Assembler ..... 

 

Quam se habilita a me ajudar ? Vai ser um projeto muito legal, com 6 entradas lógicas, e de custo muito baixo !

 

Paulo

Compartilhar este post


Link para o post
Compartilhar em outros sites

@,

 

 

Oba, legal, vai ajudar muito nesse projetinho do analizador.

 

Eu tinha certeza absoluta que voce iria me ajudar hehehe valeu ! 

 

Antes que voce me pergunte, eu criei um timer de 24 bits, ligando o Timer0 na saída do Timer1 !

Asim, mudando o prescaler, eu consigo analisar com uma boa precisão durante o perído mínimo 8 segundos ( prescaler=8) até por exemplo mais de um minuto ( prescaler=64), com um erro muito, muito pequeno, pois o meu step mínimo é de 8 ciclos de clock ( a 20 Mhz ) ! 

 

 

Paulo 

Compartilhar este post


Link para o post
Compartilhar em outros sites

Estou percebendo que este fórum esta evoluindo muito e ficando ousado!!

MatheusLPS,desmembrando o CCS inclusive olhando a listagem ASM.

@aphawk utilizando rotinas ASM com AVR para otimização de rotinas.

MOR,começando a programação em AVR.

...

Compartilhar este post


Link para o post
Compartilhar em outros sites

@vtrx,

 

Percebí uma pontinha de tiração de sarro ..... kkkkk

 

Pois é ... nem sempre conseguimos fazer tudo o que queremos com as linguagens que dominamos !

 

Eu tentei até onde pude, e consegui até algo que foi legal : o meu programa atual, em basic, tem a mesma capacidade de captura ( rodando a 20 Mhz ) de um programa que foi feito totalmente em C com um AVR rodando a 16 Mhz . O que estou fazendo vai ficar bem superior ao original, pois a captura do original é feita baseada em um timer, e esse timer é determinado atarvés do tempo decorrente entre as duas primeiras mudanças no nível das entradas.

 

O meu simplesmente faz a captura a todo instante que houver uma variação nas entradas, e guardo junto com o sinal o valor de um contador de 24 bits criado pelos dois timers juntos.....

 

Olha o projeto original, que é bem legal, mas usando um timer de 16 bits :

 

http://www.serasidis.gr/circuits/mini_logic_analyzer/miniLogicAnalyzer.htm

 

O Autor fez uma atualização, onde ele diminuiu o numero máximo de amostras para 256, assim ele usa tudo Byte em vez de Words, e assim conseguiu chegar em captura de 400 Khz .

 

Eu pensei em usar apenas 16 bits também, assim caberiam mais amostras, mas teria de mudar o funcionamento da rotina de interrupção, e capturar no mesmo princípio que o do autor usa. Mas acho que o que eu idealizei é mais preciso.

 

Paulo

Compartilhar este post


Link para o post
Compartilhar em outros sites

 

Estou percebendo que este fórum esta evoluindo muito e ficando ousado!!

MatheusLPS,desmembrando o CCS inclusive olhando a listagem ASM.

@aphawk utilizando rotinas ASM com AVR para otimização de rotinas.

MOR,começando a programação em AVR.

 

É o caminho natural das coisas, apesar de ser chato ficar repetindo, antes de conhecer IDE, linguagem de programação, assembly que o compilador gera, programar em assembly, mememememememem, é preciso conhecer a máquina que você tem na mesa, microcontrolador nada mais é que um tópico avançado de circuitos digitais, ficar com medo de uma listagem em assembly é coisa de pessoa que nunca leu um datasheet ou mesmo um manual de referência do processor, e fica frustrado quando o led nao pisca usando um framework pronto (Arduinos e afins). Se é leigo, comece do começo, não pense em tentar conhecer seu micro se nao sabe o que é um registrador. 

Ta mas vai ter gente que vai me dizer que o cara quer ver funcionando pra se motivar, e eu concordo, ver a coisa acontecendo ajuda e muito, o grande problema é quando o cara avança desse ponto antes de ir pra trás buscar aos poucos o que motivou a coisa acontecer... e ai acontece o que vemos muito no forum, estudantes desesperados em fazer seu TCC funcionar querendo sempre tudo pronto, é mandar o cara ler um datasheet pra ele abandonar o tópico.

Desabafos a parte, @aphawk, acabei de fazer uma listagem no papel de pao e vou simular quando sair do trabalho e posto o resultado aqui.

(o Basic tem inline assembly?)

Abs.

Felipe

Compartilhar este post


Link para o post
Compartilhar em outros sites

@

 

kkkk pode desabafar , meu amigo, eu vivo fazendo isso também ....

 

O pior é perceber, pelo nível dos pedidos de ajuda, que quase todos que pedem ajuda em TCC ou semelhante NEM SABEM o que precisa ser feito, e nem tem interesse em aprender.... eu me lembro de um cara , fazem uns dois anos, que simplesmente postou o que ele precisaria fazer no seu TCC, e pediu para encaminhar o projeto terminado no email dele !!!!!!!!!!!!!!!!!!!!!!!!  

 

Olha o meu código na rotina de interrupçào, nele eu já troquei uma instrução do Basic que incrementa uma variável por Assembler ( INCR index ) onde ganhei 16 ciclos de máquina, e determinei na configuração da interrupção para não salvar nenhum registro, senão o código gerado faria PUSH de R0 a R31 e ao final os POPS todos.... e eu fico responsável pelos salvamentos do contexto.

 

Eu olhei o código .ASM gerado, e salvei só os registros que foram usados pelas instruções em Basic.

 

Repare que o compilador se vira muito bem misturando o Basic com o ASM :

Sample_isr: PUSH R24 IN R24,SREG PUSH R24 PUSH R25 PUSH R26 PUSH R27 PUSH R28 PUSH R29 PUSH R30 PUSH R31 PUSH R10 PUSH R11 PUSH R16 PUSH R17 push R23 Stop Timer1 SAMPLE = Pinc Disable Pcint1 Time1(index) = Timer1 Time0(index) = Timer0 Sample_value(index) = Sample 'Incr Index LDS R24,{index} subi r24,$ff sts {index},r24 lds r24,{index+1} sbci r24,$ff sts {index+1},r24 If Index = 400 Then  Flag_endspace = 1 End If Enable Pcint1 Start Timer1 pop R23 pop R17 pop R16 pop R11 POP R10 POP R31 POP R30 POP R29 POP R28 POP R27 POP R26 POP R25 POP R24 !OUT SREG,R24 POP R24 Return 

No meu código , a matriz Time1 é de word, e a Time0 e sample_value são de byte, pois ficou bem mais rápido usar tres matrizes e um só indexador... ah, um ultimo detalhe, quando tiver um "'" no início da linha é um REMARK apenas ok ?

 

Paulo

Compartilhar este post


Link para o post
Compartilhar em outros sites

Galera, acredito que o que acontece com a maioria dos iniciantes e/ou hobbystas, é que normalmente nao constroem projetos mais ousados ou que demandem uma processamento mais crítico.

 

Isso ocorreu comigo desde sempre. Só agora com o desenvolvimento do analisador por FHT que vi o limiar do desempenho dos PICs 8 bits. Aí realmente nao tem como fugir. Ou você aplica um clock altíssimo e espera que ele de conta, ou coloca trechos de assembler no meio.

 

E a vida. O negócio é nao desanimar e nao assustar com o código desconhecido. E uma questao de hábito. Pode ser quie no início, nao entendemos nada do código visto, mas é só dedicar um pouquinho mais.

 

Sobre os TCCs da vida, vejo isso o tempo todo. O povo pega algum projeto de eletronica para fazer, mas nao entende porque ele é feito daquela forma.

 

Certa vez um amigo meu foi fazer um projeto de acionamento de motor de passo (olha que coisa besta). No projeto que ele achou (nem desenvolveu, só copiou) tinha que usar uma fonte com 4 saída de 9V. Imaginei que seria 9V para cada bobina do motor de passo. Falei com ele que ele nao precisava de uma fonte dessa pois com uma fonte normal de 12V você consegue drivear o motor de forma muito simples. Aí ele disse que nao ia mudar pois o projeto que ele achou exigia uma fonte dessa. 

 

Sei que ele pagou em torno de 70 reais na fonte, só porque possui 4 saídas de 9V. Nao entendi até hoje. Eu nem quis saber também pois tentei ajudar e ele recusou... deixei quieto....

 

Falou

Compartilhar este post


Link para o post
Compartilhar em outros sites

Mestre @aphawk, veja se isso lhe atende:
 

;Listagem para tratamento de interrupcao do analisador logico.DSEG	ORG 0x40       ;coloca no começo da area de ram por acesso diretobCounter .BYTE 2    ; aloca 2 bytes para o contador de memoriabFlag	.BYTE 1.DSEG abBuffer 	.BYTE 1800 ; aloca 1800 bytes para o bufferISR:;Salva somente os  ultimos argumentos:; 4 registros sao mais que suficientes:	in   r0, SREG;optei pelos high registers pois aceitam qualquer instrucao	push r0	push r16 	push r17	push r18	push r19	push r20	push r21;preciso do registo X para enderecamento indireto:	push r26	push r27	;para contagem do timer 1 colocando 0 no prescaler:;infelizmente ele nao possui acesso imediato entao ;terei que mapea - lo em memoria    ld  r16,TCCR1B    andi r16,$F8	st  TCCR1B, r16		;leitura do portc:	in  r19, PINC	;leitura do timer 1:	ld  r16, TCNTL1    ld  r17, TCNTH1	;calcula o offset de memoria desejado;para acesso:;pega o valor do contador atual:	ld   r20, bCounter	addi r20, (low)abBuffer;coloca lsB em XLOw:    mov  r26,r20;agora define a parte alta:    ld   r0,bCounter + 1	ldi   r20,(high)abBuffer 	addc r20, r0;coloca MSB em XHIGH:	mov  r27,r0;salva TIMER1 na RAM		st   X+,r16	st   X+,r17;acessa valor do timer0:    in   r16, TCNT0    st   X+, r16	;acessa timer 1 e recoloca o prescaler 1:8;agora voltou a funcionar    ld   r16, TCCR1B	ori  r16, $02    st   TCCR1B, r16		;guarda portc na RAM:     st  X+, r19	 ;checa se o buffer ja foi preenchido:	 mov  r20, r26;carrega tamanho maximo do buffer low:		 ldi  r21, ((low) abBuffer +  (low)1800) ;ccheca se atingiu o valor:     cp    r20, r21	 brne  FIM_ISR:	 ;se deu zero vejamos se a posicao final do buffer tbm foi atingida:	mov  r20, r27     	 ;carrrega parte alta do buffer:	ldi  r21,((high) abBuffer +  (high)1800) ;checa por um zero aqui:    cp   r20, r21    brne FIM_ISR;se atingiu o buffer entao limpamos o contador:	clr  r0	st   r0, bCounter	st   r0, bCounter + 1	;marcamos buffer cheio no flag:	ldi   r21, $0FF	st    bFlag, r21	;derruba timer 1 escrevendo 0 no prescaler    ld  r16,TCCR1B    andi r16,$F8	st  TCCR1B, r16		jmp FIM_PROCESSO	FIM_ISR:;incrementa posicao na RAM		ld r20,bCounter; a RAM avanca 4 posicoes a cada iteração		addi r20, $04 	st  bCounter, r20;calcula deslocamento na parte alta		clr r0	ld  r20, bCounter + 1	addc r0, r20	st  bCounter + 1, r20	FIM_PROCESSO:	;retoma contexto:	pop	 r27	pop	 r26	pop  r21	pop  r20	pop	 r19	pop  r18	pop  r17	pop  r16	pop  r0	out  SREG,r0;encerra ISR		reti				
Ah! Desempenho medido no debbuger (STK500):

Melhor caso: (buffer ainda nao encheU) 54 ciclos de maquina
Pior caso (buffer cheio): 59 ciclos.

Todo o desempenho medi incluindo o reti e todos os branches usados nesse codigo.

Se tiver duvidas por favor me avise.

PS: tive que fazer algumas brincadeiras na memoria RAM pra aumentar o desempenho, 

 

E antes que esqueça, que arquitetura do c*r*l*o!

Abs.

Felipe 

Compartilhar este post


Link para o post
Compartilhar em outros sites

@,

 

Opa, valeu mesmo !!!!

 

Dei uma lida e acho que está certinho, e o desempenho está fantástico ! vai ser 4 vezes mais rápido que o meu código original e vai permitir em torno de 600 Khz para capturas, o que atende perfeitamente até I2c de 400 Khz !

 

Vou colocar no Bascom e posto logo o resultado !

 

Quanto à arquitetura, realmente é bem poderosa e de boa performance, mas isso de 32 registradores, sendo que alguns fazem coisas que os outros não fazem, e alguns fazem pares para endereçamento, é muito complicado para mim entender de cara.... vou estudar direitinho o seu código e executar no simulador para acompanhar e aprender !

 

 

Obrigado, Felipe !

 

Paulo

 

 

update : postado às 01:25 da matina... e após levar um baile acertando a sintaxe correta das instruções em ASM que não estavam todas corretas ( grrrrrrr ) , e uns errinhos tipo registros destinos trocados com origem numa instrução, finalmente funcionou direitinho !

 

Segue o código atualizado, e ao final algumas observações minhas sobre o Bascom com Assembly :

Sample_isr:'Salva somente os  ultimos argumentos:' 4 registros sao mais que suficientes:        in   r0, SREG'optei pelos high registers pois aceitam qualquer instrucao        push r0        push r16        push r17        push r18        push r19        push r20        push r21'preciso do registro X para enderecamento indireto:        push r26        push r27'para contagem do timer 1 colocando 0 no prescaler:'infelizmente ele nao possui acesso imediato então'terei que mapea - lo em memoria        ldS  r16,TCCR1B        andi r16,$F8        stS  TCCR1B, r16'leitura do portc:        in  r19, PINC'leitura do timer 1:        ldS  r16, TCNT1L        ldS  r17, TCNT1H'Calcula O Offset De Memoria Desejado para acesso:'pega o valor do contador atual:        ldS   r26, {bCounter}        ldi r27,$6b        add r26,r27'agora define a parte alta:        ldS   r0,{bCounter + 1}        ldi   r27, $01        adc   r27, r0'coloca MSB em XHIGH:'        mov  r27,r20'salva TIMER1 na RAM         st   X+,r16         st   X+,r17'acessa valor do timer0:         lds  r16, TCNT0         st   X+, r16'acessa timer 1 e recoloca o prescaler 1:8'agora voltou a funcionar         ldS  r16, TCCR1B         ori  r16, $02         stS   TCCR1B, r16'guarda portc na RAM:         st  X, r19'checa se o buffer ja foi preenchido:         mov  r20, r26'carrega tamanho maximo do buffer low:         ldi  r21, $73'checa se atingiu o valor:         cp    r20, r21         brne  FIM_ISR:'se deu zero vejamos se a posicao final do buffer também foi atingida:        mov  r20, r27'carrrega parte alta do buffer:        ldi  r21,$08'checa por um zero aqui:        cp   r20, r21        brne FIM_ISR'se atingiu o buffer então limpamos o contador:        clr  r0        stS  {bCounter},r0        stS  {bCounter + 1},r0'marcamos buffer cheio no flag:        ldi   r21, $0FF        stS    {bFlag}, r21'derruba timer 1 escrevendo 0 no prescaler        ldS  r16,TCCR1B        andi r16,$F8        stS  TCCR1B, r16        jmp FIM_PROCESSOFIM_ISR:'incrementa posicao na RAM        ldS r26,{bCounter}' a RAM avanca 4 posições a cada iteração        ldi r27,$04        add r26, r27        stS  {bCounter}, r26'calcula deslocamento na parte alta        clr r0        ldS  r20, {bCounter + 1}        adc r20, r0        stS  {bCounter + 1}, r20FIM_PROCESSO:'retoma contexto:        pop  r27        pop  r26        pop  r21        pop  r20        pop  r19        pop  r18        pop  r17        pop  r16        pop  r0        !out  SREG,r0'encerra ISR Return

Curiosidades :

 

- Definí o endereço na SRAM para o Buffer . E fiz o calculo da posição finalk na mão mesmo, e informei direto no programa.

- a instrução OUT SREG,R0 dá erro, porque existe OUT também no Basocm, então ví no Help que nesse caso tem de começar com um ! para o compilador não se perder.

- o ASM consegue acessar as variáveis do Basic fácil, bastando escrever a variável entre colchetes , exemplo {Bcounter} .

 

Resumo : acho que consigo usar algumas coisas em Assembler nesse AVR !  Se perder mais umas 6 horas por dia, durante uma semana, já saio programando de novo em ASM.... kkkkkk

 

Valeu, Felipe !

 

Tive uma ideia para ganhar mais alguns ciclos de clock ... acho que em vez de manter um índice para calcular qual a próxima posição para guardar os dados, que tal guardar direto o ENDEREÇO aonde temos de armazenar ???? Assim, é só ficar armazenando e incrementando o endereço... acho que ganhamos mais de 10 ciclos de clock !

 

Vou tentar amanhã !

 

Paulo

Compartilhar este post


Link para o post
Compartilhar em outros sites

 

Curiosidades :

 

- Definí o endereço na SRAM para o Buffer . E fiz o calculo da posição finalk na mão mesmo, e informei direto no programa.

- a instrução OUT SREG,R0 dá erro, porque existe OUT também no Basocm, então ví no Help que nesse caso tem de começar com um ! para o compilador não se perder.

- o ASM consegue acessar as variáveis do Basic fácil, bastando escrever a variável entre colchetes , exemplo {Bcounter} .

 

Resumo : acho que consigo usar algumas coisas em Assembler nesse AVR !  Se perder mais umas 6 horas por dia, durante uma semana, já saio programando de novo em ASM.... kkkkkk

 

Normal, deixe - me apenas refrescar as mentes a respeito do processo de compilação (independente da linguagem), primeiramente o compilador gera a partir da linguagem de programação usada o arquivo correspondente em linguagem assembly, esse(s) arquivo(s) é enviado ao assembler (montador) que converte tudo em linguagem de máquina e em seguida envia  ao linker, responsável por alocar os módulos de código na posição desejada de memoria do processador, seja ela de programa ou dados.

Paulo, o problema da sintaxe em geral eu já esperava, mesmo que o Assembly(linguagem) seja igual por ser AVR, o assembler(montador) varia de fabricante a fabricante de ferramentas, ou seja o do Bascom funciona de um jeito, o do GCC funciona de outro, e o AVRASM de outro, cada um possui sua propria diretivas e formas de escrita das instruções...por isso é bom nunca confundir conceitos, programa - se em assembly, e a ferramenta que coverte é o assembler.

Fico feliz que tenha ajudado, eu imagino que seja possivel otimizar ainda mais, só que faz muuuuuito tempo que nao coloco a mão no assembly do AVR, apesar de gostar muito do funcionamento dele, no XMEGA de 16bits esse set de instruções é melhor ainda, visto que nao preciso ficar fazendo de forma constante malabarismos com bytes (pra acesso ate 64KB de memoria de programa, usa - se endereçamento linear).

 

 

 

Tive uma ideia para ganhar mais alguns ciclos de clock ... acho que em vez de manter um índice para calcular qual a próxima posição para guardar os dados, que tal guardar direto o ENDEREÇO aonde temos de armazenar ???? Assim, é só ficar armazenando e incrementando o endereço... acho que ganhamos mais de 10 ciclos de clock !

Paulo, não acho que isso seja uma boa ideia, pois de qualquer forma voce precisaria configurar uma condição inicial e buscar o endereco base, além disso pode ser necessario usar mais registros o que poderia causar um fenomeno que pouca gente conhece mas muita gente despreza que é o register pressure,  quando usamos mais registros que o necessario e a CPu quando retorna da ISR precisa usar muito mais pilha para preservar o contexto, além disso perderiamos mais ciclos pois seriam necessarios mais push e mais pops além  disso vamos ganhar de um lado e perder do outro pois vamos ter que guardar o valor do offset na RAM o que implicaria em mais um acesso.Acha mesmo necessário isso?

Qualquer coisa grita.

Abs.

Felipe

Compartilhar este post


Link para o post
Compartilhar em outros sites

@,

 

Opa muito obrigado mesmo, na verdade o teu código serviu para tirar o ferrugem aqui das minhas engrenagens do Assembler .... resolvi entender essa "festa" de registros dos Avrs; as instruções que tenho de utilizar seguem exatamente a sintaxe do datasheet da Atmel. Tem uma que você passou para fazer a soma com carry, que não existe no datasheet, e dava pau direto; depois quando acertei a sintaxe, ví que ela não funcionava a não ser com os tais pares de registros, aí mudei por mim mesmo o jeito de calcular.

 

Só estou tendo dificuldade de passar para o compilador coisas como o cálculo do endereço final do buffer, que não consegui de jeito nenhum fazer ele aceitar aquela sintaxe do low(......... + .........) , aí eu resolví definindo os endereços que me interessaram na SRAM, e fiz o cálculo na mão mesmo.

 

Percebí agora que estamos salvando um registro que nem mexemos nele......

 

Mas acho que fazer a conta sem um índice vai ficar mais rápido o programa, não pretendo guardar o offset, e sim o endereço mesmo para guardar o byte !  te falo amanhã se deu certo ....

 

Paulo

Compartilhar este post


Link para o post
Compartilhar em outros sites

@aphawk,estou curioso se do jeito que está,a rotina ficou mais rápida :confused:

 

PS:Não deixe o MOR perceber que você está programando em ASM para AVR...

Compartilhar este post


Link para o post
Compartilhar em outros sites

 

 

@aphawk,estou curioso se do jeito que está,a rotina ficou mais rápida  :confused:

 

é só ler os dados de desempenho iniciais que o próprio @aphwak disse:

 

 

 

Preciso que alguém me ajude com uma pequena rotina em Assembler , pois eu posso colocar ela diretamente dentro do meu programa em Bascom ( direto dentro da rotina de interrupção ! O mais importante é que ela seja muito rápida na execução, pois estou demorando cerca de 220 ciclos de máquina para fazer isto em Basic... quanto menos ciclos, melhor !

 

De resto só pegar a rotina, o manual do instruction set do AVR e contar os ciclos, resultados práticos, só no debbuger ou simulador do AVR studio.

 

 

 

 

Mas acho que fazer a conta sem um índice vai ficar mais rápido o programa, não pretendo guardar o offset, e sim o endereço mesmo para guardar o byte !  te falo amanhã se deu certo ....

Não disse que não dá, mas sou contra a filosofia de: "tudo por velocidade de execução", como ja disse fazer o que voce está propondo pode exigir mais registros do scratchpad do AVR, e causar register pressure, pelo menos nos high registers (r16 em diante), e fazer com que sobre somente os low registers que nao suportam todas as operações do set de instruções do AVR, eu iria por outro caminho para otimização, minimzar por exemplo os acessos a memoria RAM e abusar do endereçamento memory - to - memory, além disso me permita uma sugestã  mestre, utilize potencias de 2 para determinar os tamanhos de seus buffers, alem de melhorar o alinhamento a memória, ajuda bastante para operações de watermark(checar se preenchemos um buffer por exemplo) e ainda permite o uso de um buffer circular, que acho mais adequado para sua aplicação.

Enfim, boa sorte, e qualquer coisa grita!

Abs.

Felipe

Compartilhar este post


Link para o post
Compartilhar em outros sites

 

é só ler os dados de desempenho iniciais que o próprio @aphwak disse:

Quero saber na pratica pois na teoria tudo é bonito :D

Compartilhar este post


Link para o post
Compartilhar em outros sites

@vtrx,

Dei uma pequena mexida, e implementei um sinal no portd que vai pra 1 no início da rotina e volta pra 0 no final ( quer dizer, inicio após os pushs e final antes dos pops.... esse intervalo está um pouco maior do que 2,5 microsegundos, ou seja, ainda não permite a captura de 400 Khz com a precisão de tempo de 24 bits....

Vou tentar modificando como eu falei acima, preciso chegar em torno de 45 ciclos no máximo para fazer o que eu planejei.

Senão, terei de abrir mão e baixar para 16 bits de precisão, e mudar o princípio da captura.

O projeto original que eu postei logo no início faz uma suposição que ao meu ver invalida o uso mais sério : ele mede o tempo entre as duas primeiras alterações de níveis, e programa o timer para gerar uma interrupção exatamente nesse tempo, e captura as entradas e preenche o buffer todo dessa maneira, independente se houve ou não alguma alteração.

Acho esse procedimento muito ineficiente e falho, pois não há nenhuma garantia de que as duas primeiras alteraçoes de níveis são as mais rápidas que poderiam existir !

O meu projeto só armazena no buffer SE houver alguma alteração, e mantinha a contagem dos timers desde 0 até o limite dos 24 bits.

Acho que vou ter de mudar o princípio, usar apenas 16 bits, e zerar o timer a toda interrupção, assim terei entre dois eventos consecutivos um tempo máximo de 65536 x 8 = 26 milisegundos no máximo, usando apenas o Timer1 . O truque que tem de ser feito é mudar o prescaler, um valor de 32 seria perfeito para poder medir pulsos de 400 Khz, e aumentaria o meu tempo máximo entre dois eventos para 96 milisegundos, o que já permite a captura completa de um sinal de IR padrão Nec.

Vou ver se invento algo tipo usar um dos timers de 8 bits como um prescaler do de 16 bits.

Mas aceito sugestões viu !!!!!!

UPDATE - consigo utilizar o Timer2 como um prescaler configurável contínuamente entre 1 e 255, e uso a saída dele para alimentar Timer1 !!! É quase a inversão do que eu tinha feito até agora, eu usava o Timer1 como contador e a saída dele alimentava a entrada do Timer0 que funcionava como contador, para ter os 24 bits de clock.

Agora, dá para fazer o que eu falei acima : configuro o timer0 como um prescaler de valor 40 , e alimento a saída dele no Timer1. Assim, posso trabalhar guardando apenas 16 bits como base de tempo, o que vai diminuir bastante o numero de ciclos na rotina de interrupção, e também vai permitir um aumento no numero de amostras, que agora será de 600.

O valor escolhido de 40 tem um motivo : permite a captura de um evento de 0,5 microsegundos, que permite capturar um evento I2c a 400 Khz com folga !

E me obriga a escrever uma rotina com menos de 40 ciclos também !

Vou reescrever a rotina !

Paulo

Compartilhar este post


Link para o post
Compartilhar em outros sites

@aphawk,minha curiosidade é saber se a rotina toda em ASM esta muito mais rápida que a rotina em BASIC para saber se o compilador Basic conseguiu gerar um código satisfatório.

Compartilhar este post


Link para o post
Compartilhar em outros sites

@vtrx,

Sim, está, a que eu escreví, embora diferente mas com o mesmo objetivo, demorava mais de 220 ciclos de clock !!!!!

Mas em termos de ciclos de máquina, o compilador BASCOM tem uma falha grave , na minha opinião, que é de fazer algumas operações de maneira não otimizada, por exemplo, incrementar uma variável de 16 bits demora 9 ciclos de máquina a mais do que fazer em Assembly... E também existe uma falta de padronização nos registros utilizados, pois existe um uso de muitos registros diferentes. Veja que logo no primeiro código que eu postei, eu tive de salvar 12 registros, e repor eles depois, isso ainda reescrevendo o INCR em Assembly.

Po, são 48 ciclos perdidos só nisto !

De uma maneira geral, posso dizer que o código em Assembly executou a interrupção 2,5 vezes mais rápido ! Considero que no quesito velocidade, é satisfatório o código do Bascom puro. Na verdade eu esperava ser bem mais lento !

E vê se não espalha isto aqui pro MOR kkkkkk !

UPDATE - Me sinto no Paraíso agora !!!!!! Acho que com essa possibilidade de integrar de forma fácil o Assembly ao BASCOM permite fazer qualquer coisa que antes eu pensei que só seriam possíveis em Assembly puro !

É uma maravilha de produtividade poder fazer a parte geral de um programa em Basic, e a parte onde precisa de muita performance, como em rotinas de interrupção como esta aqui, em Assembly e integrar tudo junto....

Além de coisas que podemos fazer usando o hardware nativo dos Avrs, como estes truques de encadear timers uns nos outros, que permitem economizar CI's adicionais !

 

 

UPDATE 07/05 4:01 da matina :

 

Fiz as medidas com o ISIS, levando em conta os tempos envolvidos para entrar na interrupção, executar, e finalmente voltar à instrução que estava sendo executada, e percebí que os tempos que o Felipe passou não conferiam :

 

1 - Código Bascom puro - 248 ciclos

2 - Código proposto pelo Felipe - 93 ciclos ( dá 84 em vez de 50 e poucos..... )

 3 - Novo código otimizado ao extremo depois de 6 horas de pêlo no ovo - 65 ciclos

 

Segue o novo código :

Sample_isr:'Salva somente os  ultimos argumentos:'4 registros sao mais que suficientes:        in    r0, SREG'optei pelos high registers pois aceitam qualquer instrucao        push  r0        push  r16'preciso do registro X para enderecamento indireto:        push  r26        push  r27'pára contagem do timer 2 colocando 0 no prescaler:        clr   r16        sts   TCCR2B, r16'leitura do timer 1:        lds   r16, TCNT1L'pega o valor do contador atual:        lds   r26, {bCounter}        lds   r27,{bcounter+1}'salva TIMER1 na RAM        st    X+,r16        lds   r16, TCNT1H        st    X+,r16'acessa timer2 e recoloca o prescaler 1:1'agora volta a funcionar o Timer2        ldi   r16,$01        sts   TCCR2B, r16'leitura do portC        in    r16, PINC'guarda portc na RAM:        st    X+, r16'Vamos zerar a contagem do Timer1        clr   r16        sts   tcnt1h, r16        sts   tcnt1l, r16'checa se o buffer ja foi preenchido:'carrega tamanho maximo do buffer low:        ldi   r16, $73'checa se atingiu o valor:        cp    r26, r16        brne  FIM_ISR:'se deu zero vejamos se a posicao final do buffer também foi atingida:'carrrega parte alta do buffer:        ldi  r16,$08'checa por um zero aqui:        cp    r27, r16        brne  FIM_ISR'se atingiu o buffer então limpamos o contador:'marcamos buffer cheio no flag:        ldi   r16, $0FF        sts   {bFlag}, r16        jmp   FIM_PROCESSOFim_isr:        sts   {bCounter}, r26        sts   {bCounter + 1}, r27Fim_processo:'retoma contexto:        pop   r27        pop   r26        pop   r16        pop   r0        !out  SREG,r0'encerra ISR

Como podem perceber, não dá para fazer as capturas de 400 Khz, pois perco alguns ciclos parando o timer0, e depois zerando o Timer1 para iniciar novamente....

A unica solução é usar um clock externo de 32 Mhz, aí sim, chegamos em quase 450 Khz.

 

Começo a duvidar que o projeto original a atualização que o autor postou consiga mesmo fazer a captura de 400 Khz, ainda mais com clock de 16 Mhz, Verifiquei um sinal de I2c a 400 Khz, no osciloscópio e percebí uma coisa, tem de capturar 8 vezes dentro de 10 microsegundos, portanto 0,8 capturas por microsegundo e isto é totalmente impossivel mesmo a 32 Mhz pois seriam apenas 40 ciclos de máquina disponíveis para isto !  .... amanhã vou simular o projeto dele prá conferir.

 

estou pensando em usar uma SRAM externa e uma lógica, à moda antiga......

Paulo

  • Curtir 1

Compartilhar este post


Link para o post
Compartilhar em outros sites

 

1 - Código Bascom puro - 248 ciclos

2 - Código proposto pelo Felipe - 93 ciclos ( dá 84 em vez de 50 e poucos..... )

 3 - Novo código otimizado ao extremo depois de 6 horas de pêlo no ovo - 65 ciclos

É... vamos as possíveis causas. Existe algum qualificador tipo volatile como C para que o Bascom ignore qualquer tentativa de otimização?

Falo isso pois desconfio de uma hipotese: Você está usando um tipo de assembly chamado inline assembly no qual integra - se diretamente linhas em asm no código Basic, o que me faz muitas vezes mixar codigo C e assembly em arquivos separados é porque esse tipo de asm o compilador durante a montagem da imagem binária pode "não gostar " do que você fez e mudar totalmente as linhas de código.

Em C quando usamos inline e queremos que mesmo assim o compilador nao mexa usamos volatile asm () e ainda assim dependendo da otimização usada ele vai lá e mexe. Isso pode ser um dos problemas.

Pensei essa noite em formas alternativas e mais rápidas de fazer o que voce precisa, @aphawk, ficaria muito difícil pra você trabalhar com buffers cujo o tamanho em bytes sao potencias de 2? por exemplo 2048 bytes em vez de 1800? e outra, porque parar a contagem dos timers se os mesmos estão com prescalers de 1:8 o que em tese me da 8 ciclos de máquina disponível para capturar o valor do registro e salvar na RAM.

 

 

 

Fiz as medidas com o ISIS

Medo! Ainda confio mais nas medidas reais que fiz com o STK500 no AVR Studio hahahaahahha!

 

Enfim, por enquanto é isso. Abs!

 

Compartilhar este post


Link para o post
Compartilhar em outros sites

@,

 

Eu conferí os tempos com o Datasheet tambem, e bateram...... 

 

Mas não ví mudança no código do Bascom quando usei o Assembler junto. Eu abri com um disassembler, e o código gerado pelo basic não mudou. O ASM também ficou exatamente igual como listado acima.

 

Isso não tem problema nenhum, afinal o que quero é usar o Basic nas estruturas onde a velocidade não importa.

 

Quanmto ao que voce falou, eu posso usar 1024 bytes sem nenhum problema, só que caberiam poucas amostras. Já 2048 não dá, pois não sobraria 1 byte para eu usar no Basic.... nem para os stacks !!!!!

 

Quanto aos timers, repare que eu fiz um truque melhor. Temos 40 ciclos de máquina entre duas capturas, o que em tese permite capturas a cada 0,5 microsegundos. Então, usando o Timer0 como um prescaler configurável, eu divido por 20 o sinal de clock, e faço o Toggle na saída dele, o que gera uma subida na saída a cada 40 ciclos, para alimentar o Timer1. O Timer1 tem prescaler = 1 . Assim, posso dizer que a resolução das capturas atende aos requisitos para capturar perfeitamente I2C a 400 khz, e consigo usar apenas um contador de 16 bits para a base de tempo. O que me complicou foi justamente zerar o Timer1 a cada captura, para poder obter um tempo bem preciso entre dois eventos do analizador.

 

Pensei agora em não usar a interrupção !!!!  Afinal, se essa captura ficar dentro de uma rotina específica, não precisaremos fazer PUSHS e POPS , e nem o overhead de sair do programa principal e depois voltar para ele. Que tal essa saída ?

 

Só precisamos checar os estados da entrada a todo instante, caso mude, fazemos o procedimento, e voltamos a esperar uma mudança. Implementamos um flag para sinalizar que o timer1 virou, para poder sair da rotina. Pode ser que isso consiga fazer em 40 ciclos.....

 

Paulo

Compartilhar este post


Link para o post
Compartilhar em outros sites

mestre @aphwak, gostei da ideia de mexer com assembly de 8bits novamente, fiquei encucado, então me veio a ideia de usar X e Y como dois datapointers e com o auxilio do memory map deu pra dar uma enxugada boa no codigo, so vou te pedir para observar algumas coisas:

- Fiz essa estrategia baseada no memory map do ATMEGA328, se o chip for diferente pode ser necessario recalcular alguns offsets;

 

- Coloquei uma rotina extra para colocar o buffer de captura e o endereço em condições iniciais conhecidas, basta chamar uma vez no começo

 do programa que a propria ISR se encarrega de manter os valores dentro do range;

 

- Infelizmente nao medi os tempos, mas funciona corrretamente, vale lembrar que usei o AVRASM para escrever e pode ser necessario alterar sintaxe e operandos no BASCOM dada as razoes que ja expliquei no post anterior.

 

- Nao parei a contagem do timer0 e timer1 para economia de ciclos. 

 

A estratégia é bem mais simples no inicio da subrotina uso dois pares de datapointers, X e Y (AVR adoro você!), em um retiro o endereco de buffer corrente para qual devo apontar, no outro carrego o registrador que tenha o endereço mais proximo de 0 (PINC!)... ai fica fácil, carregao Y em r18, sem fazer postinc, e armazeno r18 em X com postinc (esse contem o endereço do buffer), após isso somo em r28:r29 um offset para o proximo registro no meu caso TCNT0 (! por isso nao uso postinc em Y pois calculo o offset na mao !), e repito o processo, de Y para r18, de r18 pra X, e e de novo ate salvar o timer 1, no fim da ISR uma simples comparação  para checar se o buffer encheu, se nao apenas guardo o endereço corrente de volta na variavel se sim, recarrego a variavel com o endereço base do buffer, coloco 0xFF no flag e derrubo as interrupções (veja se acha uma solução melhor).

Abaixo o código:

;Listagem para tratamento de interrupcao do analisador logico;agora o buffer tem 1024 posições:			.DSEG	abBuffer  	.BYTE  1024	bFlag		.BYTE  1	wBaseEnd        .BYTE  2						.CSEG	;esta funcao inicializa o sistema de captura:IniciaCaptura:		;salva argumentos:	push r18	push r19		;incializa buffer com endereco base:	ldi  r18, (low)abBuffer	ldi  r19, (high)abBuffer		;enderecamento direto:	sts  wBaseEnd, r18	sts  wBaseEnd+1, r19		;limpa flag de buffer cheio:	clr  r18	sts  bFlag,r18		;retoma	contexto:	pop r19	pop r18		;encerra funcao	ret					ISR:	in   r0, SREG	;optei pelos high registers pois aceitam qualquer instrucao	push r0	push r18	push r19	;preciso do registo X para enderecamento indireto:	push r26	push r27	;preciso do Y tambem		push r28	push r29;Inicia processo da ISR:		;carrega endereco mais baixo do register MAP, no caso PINC:	ldi	r28,(low)PINC	ldi	r29,(high)PINC		;carrega endereço do buffer:		lds  r26, wBaseEnd	lds  r27, wBaseEnd + 1		;recupera PINC	lds r18, Y		;salva no buffer:	sts X+, r18		;aponta a posicao de TCNT0 na RAM:	adiw r28:r29, $20		;pega timer 1:	lds  r18, Y		;salva no buffer:	sts  X+, r18		;aponta a posição do timer 1:	adiw r28:r29, $40		;acessa	lds r18, Y+		;buffer:	sts  X+, r18	;acessa parte alta:	lds  r18, Y		;buffer:	sts  X+		;checa se encheu o buffer;	ldi r18, (low)(abBuffer + 1024)	ldi r19, (high)(abBuffer + 1024)		;checa se zerou:	cmp  r18, r26	brne NAO_ENCHEU		;checamos a parte alta:	cmp  r19, r27	brne NAO_ENCHEU		;se encheu seta o flag de buffer cheio:	ldi  r19, $0FF	sts  bFlag, r19		;recarrega endereço com valor inicial:	ldi  r18, (low)abBuffer	ldi  r19, (high)abBuffer		;enderecamento direto:	sts  wBaseEnd, r18	sts  wBaseEnd+1, r19		;PAULO POR FAVOR CHEQUE SE PRECISA:	;Derruba interrupcoes:	cli	jmp  FIM_PROCESSO	;se nao encheu apenas salvamos o endereco de volta na RAM:	NAO_ENCHEU:		;apenas guarda endereco na variavel destinada:	sts wBaseEnd, r26	sts wBaseEnd+1, r27	FIM_PROCESSO:			;retoma contexto:	pop  r29	pop  r28	pop	 r27	pop	 r26	pop	 r19	pop  r18	pop  r0	out  SREG,r0		;encerra ISR		reti	 

Veja se ajuda, e vamos nos falando :)

Abs.

 

Compartilhar este post


Link para o post
Compartilhar em outros sites

@,

 

Opa, valeu !!!!!

 

Ontem não tive tempo de testar pois voltei só de madrugada. Vou testar hoje a noite e te respondo !

 

Realmente, é interessante esse sistema do AVR, depois que entendí melhor as diferenças dos registradores, gostei de brincar com isso também kkkkk !

 

Obrigado pela ajuda , Mestre Felipe !

 

Paulo

Compartilhar este post


Link para o post
Compartilhar em outros sites

Fuçando o set de instruções do AVR, deu pra dar mais uma otimizadinha, olha só que legal mestre @aphawk:


 

;Listagem para tratamento de interrupcao do analisador logico;agora o buffer tem 1024 posições:				.DSEG	abBuffer  	.BYTE  1024	bFlag		.BYTE  1	wBaseEnd    .BYTE  2							.CSEG	;esta funcao inicializa o sistema de captura:IniciaCaptura:		;salva argumentos:	push r18	push r19		;incializa buffer com endereco base:	ldi  r18, (low)abBuffer	ldi  r19, (high)abBuffer		;enderecamento direto:	sts  wBaseEnd, r18	sts  wBaseEnd+1, r19		;limpa flag de buffer cheio:	clr  r18	sts  bFlag,r18		;retoma	contexto:	pop r19	pop r18		;encerra funcao	ret					ISR:	in   r0, SREG	;optei pelos high registers pois aceitam qualquer instrucao	push r0	push r18	push r19	;preciso do registo X para enderecamento indireto:	push r26	push r27	;preciso do Y tambem		push r28	push r29;Inicia processo da ISR:		;carrega endereco mais baixo do register MAP, no caso PINC:	ldi	r28,(low)PINC	ldi	r29,(high)PINC		;carrega endereço do buffer:		lds  r26, wBaseEnd	lds  r27, wBaseEnd + 1		;recupera PINC	lds r18, Y		;salva no buffer:	sts X+, r18		;aponta a posicao de TCNT0 na RAM:	adiw r28:r29, $20		;pega timer 1:	lds  r18, Y		;salva no buffer:	sts  X+, r18		;aponta a posição do timer 1:	adiw r28:r29, $40		;acessa	lds r18, Y+		;buffer:	sts  X+, r18	;acessa parte alta:	lds  r18, Y		;buffer:	sts  X+	;Aqui checamos se o buffer encheu:			;checa se zerou:	cpi  r26,(low)(abBuffer + 1024)	brne NAO_ENCHEU		;checamos a parte alta:	cpi   r27,(high)(abBuffer + 1024)	brne NAO_ENCHEU		;se encheu seta o flag de buffer cheio:	ldi  r19, $0FF	sts  bFlag, r19		;recarrega endereço com valor inicial:	ldi  r18, (low)abBuffer	ldi  r19, (high)abBuffer		;enderecamento direto:	sts  wBaseEnd, r18	sts  wBaseEnd+1, r19		;PAULO POR FAVOR CHEQUE SE PRECISA:	;Derruba interrupcoes:	cli	jmp  FIM_PROCESSO	;se nao encheu apenas salvamos o endereco de volta na RAM:	NAO_ENCHEU:		;apenas guarda endereco na variavel destinada:	sts wBaseEnd, r26	sts wBaseEnd+1, r27	FIM_PROCESSO:			;retoma contexto:	pop  r29	pop  r28	pop	 r27	pop	 r26	pop	 r19	pop  r18	pop  r0	out  SREG,r0		;encerra ISR		reti				

Deu pra ganhar mais 4 ciclos :)

Abs.

Compartilhar este post


Link para o post
Compartilhar em outros sites

@,

 

Ainda não deu ... 62 ciclos, sem parar o timer ...

 

Bom, já desisti de fazer a captura na interrupção, nunca vamos conseguir 40 ciclos. Vai ser mesmo na mão....

 

estou pensando aqui nisso agora.

 

 

UPDATE 08:20 ..

 

CONSEGUIIIII !!! Faz tudo certinho, inclusive parar o timer antes da medição, e depois de armazenado zera o timer e reinicia. Melhor caso 32 cilcos, e pior caso 37 ciclos !!!!!

 

Agora apenas umas poucas mudanças para detectar o inicio do sistema de capturas,e a parte compl;icada resolveu !

'aqui vamos supor que tudo está inicializado, inclusive os timers.'salva contexto      push r0      in   r0, SREG      push r0      push r16      push r17      push r18      push r26      push r27'pega o valor do inicio do buffer:      ldS  r26, {bCounter}      ldS  r27,{bcounter+1}'faz uma primeira leitura para comparar as outras      in   r18,pinc'guardamos em r16, que sempre será o estado anterior      mov  r16,r18Loop_teste:' lemos a entrada      In r16 , Pinc' conparamos com a anterior      cp    r16,r18' se for diferente, teve mudança      brne  mudou_entrada' não houve mudança, entao'testa se estourou timer      lds   r17,{flag_24b_over}      andi  r17, $ff      breq  LOOP_TESTE' timer1 estourou, fim do processo      clr  r0      cp   r0, r0      breq FIM_PROCESSOMudou_entrada:'pàra timer1      clr   r17      !out  TCCR1B, r17'      in r17, tccr1b'guarda leitura do timer 1:      in    r17, TCNT1L      st    X+,r17      in    r17, TCNT1H      st    X+,r17'guarda o valor da entrada      st    x+,r16'guarda para comparar      mov   r18,r16'tudo guardado'testar agora o buffer se encheu'carrega tamanho maximo do buffer low:      ldi   r16, $73'checa se atingiu o valor:      cp    r26, r16      brne  buffer_ok:'se deu zero vejamos se a posicao final do buffer também foi atingida:'carrrega parte alta do buffer:      ldi   r16,$08'compara se atingiu valor e se atingiu, fim do buffer !      cp    r27, r16      breq  fim_processo'se não atingiu o bufferBuffer_ok:'zera timer      clr   r17      !out  tcnt1h, r17      !out  tcnt1l, r17'inicia nova contagem contagem      ldi   r17,$06      !out  TCCR1B, r17'tudo pronto , vamos recomeçar !      breq loop_teste'acabou o espaço no buffer !!!Fim_processo:      pop r27      pop r26      pop r18      pop r17      pop r16      pop r0      !out sreg,r0      pop r0' final do processo de amostragem, com pior caso de 37 ciclos !!! normal de 32

Paulo

  • Curtir 1

Compartilhar este post


Link para o post
Compartilhar em outros sites

@aphawk vai ter que atualizar a sua apostila do  BASCOM com uma sessão 'advance' de como otimizar rotinas no BASCOM com Assembly. :D

Compartilhar este post


Link para o post
Compartilhar em outros sites
Visitante
Este tópico está impedido de receber novos posts.





Sobre o Clube do Hardware

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

Direitos autorais

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

×