Skip to content. Skip to navigation

Eletronica.org

Sections
Personal tools
You are here: Home Artigos Eletrônica Digital e Sistemas Embarcados Programação Assembly para Microcontroladores

Document Actions

Programação Assembly para Microcontroladores

by Suporte Eletronica .org last modified 05/05/2008 01:00

Parece ser um pouco de contra-senso escrever sobre programação assembly, quando a maioria dos desenvolvedores de sistemas embutidos está interessada em usar linguagem C em seus projetos. Mas na verdade, quem trabalha com micro-controladores deve ter bom domínio de assembly e de como tirar proveito de suas características para aperfeiçoar as regiões críticas de código.

Parece ser um pouco de contra-senso escrever sobre programação assembly, quando a maioria dos desenvolvedores de sistemas embutidos está interessada em usar linguagem C em seus projetos. Mas na verdade, quem trabalha com micro-controladores deve ter bom domínio de assembly e de como tirar proveito de suas características para aperfeiçoar as regiões críticas de código.

Um programa de computador pode ser representado de diversas formas. Linguagens de alto nível, como C, C++ e Java, dão uma maior capacidade de abstração, recursos computacionais e independência do hardware ao programador. Programas escritos em assembly tendem a ser menores e mais rápidos do que os escritos em linguagens de alto nível, entretanto são dependentes de plataforma e difíceis de depurar e dar manutenção.

O processador só entende a linguagem de máquina específica de sua arquitetura, que é representada pelos seus códigos de operação (opcodes) no formato binário. Seria extremamente tedioso, e até mesmo desumano, escrever programas utilizando os opcodes, por isso usamos uma representação simbólica da linguagem de máquina: o assembly. Um programa especial chamado assemblador trata os símbolos e os transformam em código binário, tratando ainda da alocação da memória de programa e de dados.

Neste artigo, iremos estudar a estrutura da linguagem assembly de uma das famílias de micro-controladores mais usadas no mundo a PICmicro MIDRANGE da Microchip, também conhecidos como PIC16CXXX.

Arquitetura PIC16

Como dito antes, o assembly é a representação simbólica da linguagem de máquina. Então, antes de aprendermos a programar em assembly, devemos entender como funciona o processador em questão. Questões como tamanho da memória e sua organização, modos de endereçamento, registradores especiais e o próprio conjunto de instruções precisam ser bem estudadas.

Os “PIC16” são micro-controladores de arquitetura RISC ( Reduced Instruction Set Computer). Esta arquitetura permite que o micro-controlador tenha um conjunto de instruções mais simples e de menor número (apenas 35), dois formatos de instrução apenas (uma para instruções que manipulam bits e outras para bytes), execução de uma instrução por ciclo de máquina, pipeline de execução e busca de instruções, e amplo número de registradores de uso geral.

Essa família possui uma arquitetura baseada em acumulador (o registrador W), todas as instruções lógicas e aritméticas são referenciadas à ele. Os registradores são divididos em duas categorias: especiais (SFR) e de propósitos geral (GPR). A maior parte dos SFRs são usados para configurar e usar os diversos periféricos disponíveis. O registrador de STATUS sinaliza condições especiais ocorridas durante as operações. Ele vai ser muito útil quando iniciarmos a programação!

A memória dos PIC16 está organizada de modo que o barramento de dados é separado do barramento de instruções (arquitetura harward modificada). Dessa forma, podem-se realizar acessos simultâneos as duas regiões de memória. Entretanto, as duas memórias estão organizadas em regiões de endereçamento especial, os conhecidos bancos de memória (paginação). Todo mundo que já trabalhou com essa família tem alguma coisa para falar desse “recurso”. Muitos erros de programação consistem na utilização errada ou de enganos em relação aos bancos de memória.

Os dispositivos da MIDRANGE possuem uma pilha de oito níveis, isolada da região endereçável de memória. A pilha serve para guardar temporariamente o endereço de retorno das rotinas. Quando ocorre um overflow na pilha o primeiro endereço é sobre-escrito, como em um buffer circular.

Dois são os modos de endereçamentos disponíveis: direto e indireto. O endereçamento indireto é provido pelos registradores INDF e FSR. O segundo serve como um ponteiro para uma posição na RAM, enquanto o primeiro é um registro virtual que acessa o endereço apontado pelo FSR. Pode-se considerar um terceiro modo de endereçamento, quando se faz uso do registrador PCLATH e de algumas “operações especiais” para se ter um endereçamento indexado.

Para mais informações sobre a arquitetura dessa família de micro-controladores dê uma olhada em [3].

Na tabela abaixo, segue o conjunto de instruções e seus respectivos opcodes.

Instrução Descrição
ADDWF f,d Adiciona W com o registrador ‘f’. O resultado é salvo em W caso ‘d’ == 0 e em ‘f’ se ‘d’==1.
ANDWF f, d Operação de E bitwise entre W e f. O resultado é salvo em W caso ‘d’ == 0 e em ‘f’ se ‘d’==1.
CLRF f Faz 'f' = 0;
CLRW Faz W = 0;
COMF f,d Complementa o registrador 'f'. O resultado é salvo em W caso ‘d’ == 0 e em ‘f’ se ‘d’==1.
DECF f,d Decrementa o registrador 'f'. O resultado é salvo em W caso ‘d’ == 0 e em ‘f’ se ‘d’==1.
DECFSZ f,d Decrementa o registrador 'f' e salta a próxima instrução se o resultado for igual a 0. O resultado é salvo em W caso ‘d’ == 0 e em ‘f’ se ‘d’==1.
INCF f,d Incrementa o registrador 'f'. O resultado é salvo em W caso ‘d’ == 0 e em ‘f’ se ‘d’==1.
INCFSZ f,d Incrementa o registrador 'f' e salta a próxima instrução se o resultado for igual a 0. O resultado é salvo em W caso ‘d’ == 0 e em ‘f’ se ‘d’==1.
IORWF f,d Operação OU bitwise entre 'W' e 'f'. O resultado é salvo em W caso ‘d’ == 0 e em ‘f’ se ‘d’==1.
MOVF f,d Move o valor do registrador 'f'. O resultado é salvo em W caso ‘d’ == 0 e em f se ‘d’==1.
MOVWF f Move o conteúdo do registrador W para 'f'.
NOP Sem operação (No Operation)
RLF f,d Rotação à esquerda com carry. O resultado é salvo em W caso ‘d’ == 0 e em ‘f’ se ‘d’==1.
RRF f,d Rotação à direita com carry. O resultado é salvo em W caso ‘d’ == 0 e em ‘f’ se ‘d’==1.
SUBWF f,d Subtrai W com f. O resultado é salvo em W caso ‘d’ == 0 e em ‘f’ se ‘d’==1.
SWAPF f,d Troca os nibbles no registrador f. O resultado é salvo em W caso ‘d’ == 0 e em ‘f’ se ‘d’==1.
XORWF f,d Operação bitwise XOR (OU Exclusivo) entre W e f. O resultado é salvo em W caso ‘d’ == 0 e em ‘f’ se ‘d’==1.
BCF f, b Faz o bit 'b' do registrador f igual a 0 (bit clear).
BSF f, b Faz o bit 'b' do registrador 'f' igual a 1 (bit set).
BTFSC f,b Se o bit 'b' no registrador 'f' for igual à 0 a próxima instrução será saltada.
BTFSS f,b Se o bit 'b' no registrador 'f' for igual à 1 a próxima instrução será saltada.
ADDLW k Soma entre o literal 'k' e W.
ANDLW k E lógico entre o literal 'k' e W.
CALL k Chama uma subrotina.
CLRWDT Limpa o Watchdog Timer.
GOTO k Vai à um endereço.
IORLW k Operação OU entre um literal e W.
MOVLW k Move o literal 'k' para W.
RETFIE Retorna de uma interrupção.
RETLW k Retorna com o literal 'k' em W.
RETURN Retorna de uma subrotina.
SLEEP Vai ao modo de 'standby'.
SUBLW k Subtrai W com um literal 'k'.
XORLW Operação XOR (OU Exclusivo) entre W e um literal 'k'.


Programando em Assembly MPASM

Agora que já conhecemos um pouco da arquitetura e do conjunto de instruções dos PIC16, iremos começar a escrever alguns programas simples em MPASM, o assembly da Microchip. Para escrevermos programas em assembly, precisamos de um programa que os traduza para a linguagem de máquina.

A Microchip disponibiliza um programa gratuito que faz o papel de ambiente gráfico de desenvolvimento e vem com ferramentas de assemblador e linker, o MPLAB. Faça download do aplicativo no site da Microchip (www.microchip.com), e instale-o em seu computador.

Iremos analisar três trechos de código que nos darão uma visão geral da programação assembly para micro-controladores.

O primeiro trecho de código realiza um loop de oito iterações onde duas variáveis são somadas continuamente e o resultado é salvo no registrador W, fazemos uso de uma terceira variável como contador.

1 - clrf  counter             ; zera o valor do contador
2 -
3 - loop:   
4 - movf  somador_1, 0       ;move o valor de somador_1 para W
5 - addwf somador_2, 0       ;soma W com somador_2 e guarda o valor em W
6 - incf     counter, 1     ;incrementa o registrador counter
7 - btfss    counter,#3     ;testa se o 3 bit de counter está setado
8 - goto    loop
9 - fim_loop:               ;fim do loop
A linha 1 prepara a variável “counter” para servir como contador, zerando o seu valor. O loop inicia na linha 3, o valor de “somador_1” é atribuído ao registrador W. Em seguida, o valor de W é somado ao valor de “somador_2” e o resultado é armazenado no próprio W. Estas duas linhas são o corpo do loop, agora vamos ao controle do número de iterações.

As linhas restantes controlam quantas vezes o corpo do loop será executado. A linha 6 incrementa o valor do contador, indicando quantas iterações houve. Utilizamos a instrução “btfss counter, #3” que simplesmente testa se o bit 3 da variável “counter” está setado e caso esteja pula a próxima instrução; para verificar o fim do loop.

Vocês podem estar se perguntando por que fizemos uso dessa instrução, o objetivo deste trecho de código é repetir oito vezes o somatório e então sair. Sabemos que oito em binário é representado por “00001000”, ou seja, o bit 3 setado.

É claro que se o número de iterações não fosse uma potência de dois o trecho de código acima estaria inutilizado. Muitas vezes, precisamos fazer reduções de código para adequarmos o tamanho do código ao espaço de memória de programa disponível. E nos vemos obrigados a fazer uso deste tipo de “aperfeiçoamento” de código, dificilmente um compilador C faria algo deste gênero. “É uma questão de ver a simetria do problema!”, como diz meu antigo professor de Geometria Euclidiana.

No próximo trecho de código, iremos realizar uma comparação entre duas variáveis com o objetivo de saber qual é a maior.

1 – movwf var_1         ; move o valor de var_1 para W
2 –     subwf var_2, 0          ;subtrai o valor de var_2 de W
3 –     btfss STATUS, C     ;verifica o estado do bit CARRY
4 –     goto  v1_maior      ;vai para valor menor
5 – v2_maior:           ; var_2 > var_1  
6 –     return          ;sai da rotina
7 – v1_maior:           ;trata o caso de var_2 < var_1

Esse trecho é bem simples antes de uma operação de “subwf” o bit C do registrador STATUS é setado, após a execução da instrução, caso o registrado seja menor do que W o bit C é limpo. O registrador STATUS contém diversos flags que nos auxiliam a tomar decisões após instruções aritméticas.

O último trecho analisado irá copiar dez bytes de uma posição de memória RAM para outra região.

1 -      movlw 0x45        ;
2 -      movwf  pointer_0     ;inicializa pointer_0 com o endereço do buffer destino
3 -      movlw 0x55
4 -      movwf pointer_1      ;inicializa pointer_1 com o endereço do buffer fonte
5 -      movwf FSR, 1         ;aponta o FSR para o endereço fonte
6 – copy_ram:
7 -      movf   INDF, 0       ;põe em W o valor da posição do buffer fonte
8 -      movwf  aux           ;guarda o valor na variável auxiliar
9 -      movf   pointer_0     ;
10-      movwf FSR        ;aponta para o buffer destino 
11-      movf    aux,0        ;
12-      movwf INDF           ; põe o valor de aux no buffer destino
13-  incf  pointer_0, 1   ;incrementa os ponteiros
14-      incf  pointer_1, 1
15-      incf  counter        ;incrementa o contador    
16-      movlw d’10’
17-  subwf counter, 0     ;verifica se passaram-se todas a iterações    
18-      btfss   STATUS, C
19-  goto  copy_ram_aux   ;counter < 10
20- fim_copy_ram:         ;todos os bytes copiados!!!   
21-     return
22-
23- copy_ram_aux:             ; aponta FSR para a próxima posição
24- movf pointer_1, 0     ; do buffer fonte
24- movwf FSR
25- goto copy_ram         ;volta para realizar outra cópia

Este trecho é bem mais complexo do que os primeiros, fazemos uso do modo de endereçamento indireto para copiar um buffer de dez bytes entre duas regiões de memória RAM.

A primeira parte atribui a dois ponteiros os valores iniciais dos buffers de destino e fonte. Então o registrador FSR é iniciado com a posição inicial do buffer fonte. O valor da posição apontado por FSR é copiado para a variável auxiliar “aux” via registrador INDF.

FSR é apontado para o buffer destino nas linhas 9 e 10. E o valor de “aux” é copiado para INDF que agora aponta para o buffer destino. As linhas seguintes incrementam o valor dos ponteiros dos buffers.

A partir da linha 15 segue o código de controle que verifica quantos bytes foram copiados e caso necessário configura o FSR para apontar novamente para o buffer fonte. Este tipo de rotina é interessante quando se trabalha com recepção de caracteres por uma interface serial e precisa-se copiar o buffer de recepção para ser tratado por outra rotina.

Dicas Úteis

O conjunto de instruções PIC16 é bem reduzido, o que pode representar mais um desafio para o desenvolvedor de sistemas embutidos. Conforme o programa vai crescendo em tamanho, cresce também a dificuldade em mantê-lo e entendê-lo. Por isso, é importante aplicar uma metodologia bem estruturada na análise e codificação do sistema. Diagramas de fluxo e de estado dão maior entendimento e são mais fáceis de trabalhar do que especificações escritas.

Comentários bem elaborados também ajudam na programação e deixam importantes informações para os próximos programadores que venham a trabalhar com o sistema.

Caso o seu problema seja espaço de programa, vale a pena queimar neurônios pensando na melhor forma de escrever dada rotina. Muitas vezes, o próprio problema dá pistas de soluções que consomem menos instruções. Um bom conhecimento e prática no assembly também ajuda muito!!!

Referências

1 - ABD-EL-BARR, Mostafa; EL-REWINI, Hesham. Fundamentals of Computer Organization and Architecture. WILEY, 1 Ed. 2005.
2 - SHIVA, Sajjan. Computer Design and Architecture. DEKKER, 3 Ed. 2000.
3 - MICROCHIP. PICmicro MIDRANGE MCU Family Reference Manual. Microchip Technology, 1997.


Otávio Alcântara


Otávio Alcântara é Tecnólogo em Telemática pelo CEFET-CE e especializado em desenvolvimento de software em tempo real para sistemas embutidos .





Acesso rápido: Lista Completa de Projetos | Diretório de Artigos | Apostilas & E-Books | Política de Privacidade

Publicidade

 
Assine o informativo Eletronica.org Grupos de Usuários O'Reilly

Log in


Forgot your password?
New user?
Recent Changes
All recent changes…
Conheça Também
Sites em Português
  - Neoradix
  - DQSoft
  - Gabiarra
  - SafiraTec
  - Feira de Ciências
  - RoboFEI
  - Transistor548
  - Rogercom.com
  - Pise na Grama
  - Electronics.com.br
  - Tecnocientista.info

Listas em Português
  - PicListBrasil
  - SisEembarcados

Sites em Inglês
  - Embedded.com
  - Hack a Day
  - Electr. Design
  - Arch Embedded!



[O que é isso?]
 

Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: