O mecanismo de leitura/escrita de 16 bits nos PIC18

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;

Leave a Reply