Diversos modelos de PICs (como o PIC18F4520) incluem mecanismos de escrita/leitura de 16 bits para alguns dos seus registradores (tais como os contadores de 16 bits dos timers).
A razão para a existência de tais mecanismos é simples: a CPU destes microcontroladores possui um barramentos de dados de 8 bits e por isso, para operar com um registrador de 16 bits são necessárias duas operações.
Até aí não há grandes problemas, pois este é o cotidiano de muitas máquinas. O problema acontece quando o registrador de 16 bits pode ser alterado tanto pela CPU quanto por um periférico. Neste caso, no mesmo instante em que a CPU está realizando uma operação num dos bytes do registrador, o periférico pode estar alterando o outro, e o resultado disso é uma corrupção do valor do registrador.
Os fabricantes de chips de 8 bits têm resolvido esta questão utilizando mecanismos de travamento (buffers ou latches temporários) que funcionam como rascunho ou armazenamento temporário.
No caso do mecanismo dos PIC18 e falando especificamente dos timers, este mecanismo consiste num buffer intermediário que se comunica com os 8 bits superiores do contador de 16 bits do timer.
O mecanismo funciona assim: nas operações de leitura, ao se fazer a leitura dos 8 bits menos significativos do contador (a parte baixa da contagem), os 8 bits mais significativos são armazenados num buffer temporário. Este buffer irá manter a informação indefinidamente (até que ocorra uma nova leitura da parte baixa do contador).
Vamos utilizar o timer 1 como exemplo: a leitura do registrador TMR1L retorna a parte baixa da contagem do timer 1 e ao mesmo tempo armazena a parte alta da contagem no registrador TMR1H, assim, o programa pode posteriormente ler o registrador TMR1H sem a preocupação de que o hardware tenha alterado algum dos registradores: a contagem de 16 bits é lida sem qualquer problema de corrupção dos dados.
No caso de uma operação de escrita, o mecanismo entra em ação da seguinte forma: ao escrever na parte baixa do timer, a parte alta é automaticamente atualizada com o buffer temporário. Novamente no caso do timer 1, ao escrever no registrador TMR1L, a contagem de 16 bits do timer 1 é atualizada com a parte alta vinda do TMR1H e a parte baixa vinda do TMR1L.
Podemos sintetizar o que foi dito acima da seguinte forma:
– leitura do contador do timer: lê-se primeiro o TMRxL e em seguida o TMRxH;
– escrita no contador do timer: escreve-se primeiro no TMRxH e em seguida no TMRxL.
Em linguagem assembly estas operações puras de escrita e leitura são bastante simples de se entender e realizar:
MOVFF TMR1L,CONTAGEML ; leitura da parte baixa do timer (armazena a parte alta em TMR1H) MOVFF TMR1H,CONTAGEMH ; leitura da parte alta do timer MOVFF CONTAGEMH,TMR1H ; escreve no buffer temporário (o timer não é alterado) MOVFF CONTAGEML,TMR1L ; escreve na parte baixa do timer (a parte alta é carregada com o valor de TMR1H)
O problema ocorre quando utilizamos instruções do tipo R-M-W (read-modify-write) para alterar o valor do contador de um timer. Suponha que seja necessário somar o valor 0x1234 ao valor atual da contagem do timer 1. A solução óbvia seria:
MOVLW 0x12 ADDWF TMR1H,F MOVLW 0x34 ADDWF TMR1L,F
Esta solução, apesar de atender a regra de que devemos escrever primeiro na parte alta e depois na parte baixa do timer, não irá funcionar corretamente! A razão disso é que a primeira instrução ADDWF tenta fazer a soma do valor em W (0x12) com o conteúdo de TMR1H, numa operação típica R-M-W: primeiro o conteúdo do TMR1H é lido, em seguida alterado e após isso o resultado é escrito de volta no TMR1H.
O problema é que este registrador ainda não contem a parte alta da contagem atual! O TMR1H somente será carregado com a parte alta da contagem do timer na leitura do TMR1L (graças ao mecanismo de leitura do timer).
A solução para isso é muito simples: basta realizar a leitura do TMR1L antes de se tentar modificar a contagem do timer:
MOVFF TMR1L,TEMP ; lê TMR1L e salva em TEMP (agora o TMR1H contém a parte alta da contagem do timer 1) MOVLW 0x12 ADDWF TMR1H,F ; soma 0x12 ao TMR1H (agora funciona!) MOVLW 0x34 ADDWF TEMP,W ; soma 0x34 ao valor temporário do TMR1L salva o resultado em W MOVWF TMR1L ; atualiza TMR1L (toda a contagem de 16 bits do timer é atualizada)
Em C o procedimento é praticamente o mesmo, mas utilizamos uma variável local temporária para armazenar o resultado da leitura do TMR1L:
unsigned char temp; temp = TMR1L TMR1H += 0x12; TMR1L = temp + 0x34;
Note que os procedimentos acima não consideram a possibilidade de transporte do byte menos significativo para o byte mais significativo. Caso isso possa ocorrer na aplicação (no caso do timer esta possibilidade não é tão comum), será necessário realizar a soma utilizando uma variável temporária e considerando o eventual carry:
MOVFF TMR1L,TEMPL ; lê TMR1L e salva em TEMPL (TMR1H é atualizado com a parte alta da contagem do timer 1) MOVLW 0x34 ADDWF TEMPL,F ; soma 0x34 a TEMPL MOVLW 0x12 ADDWFC TMR1H,F ; soma 0x12 mais o carry da soma anterior ao TMR1H MOVFF TEMPL,TMR1L ; copia TEMPL para TMR1L (altera toda a contagem de 16 bits do timer 1)
Em C podemos fazer uma adição de 16 bits (do TMR1, por exemplo) utilizando o seguinte trecho de código:
union { unsigned int word; struct { unsigned char byte_low; unsigned char byte_high; }; } my_word; ... my_word.byte_low = TMR1L; my_word.byte_high = TMR1H; my_word.word += 0x1234; TMR1H = my_word.byte_high; TMR1L = my_word.byte_low;