Esta é uma pré-visualização de arquivo. Entre para ver o arquivo original
1 Linguagem para Programar Microcontroladores: Assembly, C ou Basic? Introdução: Num processador (microprocessador ou microcontrolador), o significado de cada instrução do conjunto de instruções está embutido ao nível do hardware, nos circuitos, mais precisamente no bloco do processador denominado decodificador de instrução. Tais instruções são denominadas “opcodes” (códigos operacionais). Apesar das instruções poderem ser escritas em código binário, em função da arquitetura dos processadores disponíveis, o tamanho mínimo das instruções e dos dados utilizado normalmente é de oito bits, o que caracteriza um Byte. A este nível, para facilitar o trabalho dos programadores, o código pode ser escrito na notação de dígitos hexadecimal (dois dígitos em hexadecimal para cada Byte). Dizemos que códigos escritos desta forma são escritos em linguagem de máquina. Porém, este método de escrever o código é complexo e exige o conhecimento de um grande número de códigos operacionais e a criação de programas para microcontroladores e microprocessadores pode se tornar uma tarefa desgastante á medida que aumenta a complexidade da aplicação desenvolvida. Os primeiros dispositivos programáveis tinham seus programas escritos em linguagem de código de máquina, os quais eram geralmente inseridos através de um teclado de uma unidade de programação, em formato de dígitos hexadecimais. O trabalho de programação era assim bastante complexo tomando um longo tempo dos programadores, o que o tornava também muito oneroso. Aliado a isso, a sempre crescente demanda pela programação de sistemas, para facilitar o trabalho de escrever código de baixo nível, foi desenvolvido um sistema onde grupos de códigos operacionais semelhantes receberam nomes que lembram suas funções e que os tornaram muito mais práticos de serem usados. Estes nomes são denominados mnemônicos. Isto provocou o surgimento de uma nova forma de programação, dando origem à linguagem Assembly, que consiste numa forma de representação dos códigos de máquina usando mnemônicos, ou seja, abreviações de termos usuais que descrevem a operação efetuada pelo comando do código de máquina. Um mnemônico é um nome reservado de uma família de códigos operacionais que realizam tarefas semelhantes no processador. Os códigos operacionais atuais diferem quanto ao tamanho e tipo de operandos que sejam utilizados. Exemplo: Opcode Operando Instrução (mnemônico) (hexadecimal) DEC A 14H --- SUBB A, R4 9CH --- SUBB A, 0XXH 94H XXH 2 Os mnemônicos possibilitam escrever código de um modo muito mais intuitivo e sem perda de precisão. Existe uma correlação exata entre o que você escreve com mnemônicos e o que você obtém como códigos operacionais acabados. A linguagem de mnemônicos se chama: ASSEMBLY (repare no Y no final). O programa montador, ou seja, o programa que transforma o código escrito na linguagem Assembly em linguagem de máquina, substituindo as instruções, variáveis pelos códigos binários e endereços de memória correspondente, é que se chama ASSEMBLER. Voltando ao exemplo anterior, escrevemos o mnemônico SUBB A, R4 e o assembler transforma SUBB A, R4 na sequência de bits correspondente ao hexadecimal 9C, ou seja, em 1001 1100. Esta sequência de bits corresponde à instrução embutida nos circuitos do microcontrolador Atmel TSC80251 (código operacional) que faz com que o dado contido no registrador acumulador seja subtraido do valor contido no registrador R4 e do valor do flag de carry, deixando o resultado no acumulador. Assim, para trabalhar com Assembly é necessário o uso de um programa montador (Assembler) que faz a conversão dos mnemônicos em código de máquina. Sem dúvida nenhuma, a representação das instruções em Assembly tornou interpretação da leitura do programa pelos seres humanos muito mais simples do que se empregarmos o código de máquina original, pois os mnemônicos são contrações de palavras básicas da língua inglesa. No entanto, ainda ficam pendentes muitos problemas. Já os compiladores das várias outras linguagens de nível mais alto, como os compiladores de linguagem C, por exemplo, fazem a compilação dos programas em duas etapas: na primeira etapa, o código fonte escrito em C é transformando para código em Assembly e na segunda etapa é feita a montagem, ou seja, é gerado o código binário com a ajuda de um Assembler. A linguagem C foi originalmente desenvolvida para programação de baixo nível para sistemas com poucos recursos, “runtime” problemático e que processam operações básicas e de baixo nível. Até o desenvolvimento da linguagem C, não existiam linguagens de programação de alto nível adequadas à tarefa de desenvolver os sistemas operacionais, ou seja, os programas especiais utilizados para o controle genérico de um computador, e também outros softwares de baixo nível. Aos desenvolvedores restava somente conformar-se em utilizar o Assembly para a execução destas tarefas. Foi principalmente a partir das necessidades de reescrita do sistema operacional UNIX, que até então era escrito em Assembly, que surgiu a linguagem C. De fato, a implementação da linguagem é tão poderosa que a linguagem C foi escolhida para o desenvolvimento de outros sistemas operacionais como o Windows e o Linux. A linguagem C foi criada em 1972 por Dennis Ritchie, da Bell Laboratories. Esta linguagem consiste na realidade em uma linguagem de nível intermediário entre o Assembly e as demais linguagens de alto nível, como Pascal, Java, etc. Mas afinal de contas, qual é a linguagem mais adequada para desenvolvimento de projetos de eletrônica envolvendo microcontroladores. Apesar de a resposta parecer 3 bastante óbvia, várias pessoas se perguntam sobre qual linguagem é melhor: C ou Assembly. Essa resposta é dependente da situação: para programas muito pequenos, que utilizam apenas funções básicas do microcontrolador, talvez o assembler seja a solução mais adequada. Já para programas maiores, que incluem um grande número de projetos comerciais, a linguagem C é sem dúvidas a mais adequada. O primeiro fato a se considerar é que tanto a linguagem de máquina quanto a linguagem Assembly, bem como o programa montador, são específicos de cada processador. Isso significa que um programa desenvolvido em Assembly para um microcontrolador 8051 não tem portabilidade para outro microcontrolador, por exemplo, o PIC. Para utilizar-se um programa desenvolvido em linguagem Assembly de um processador, em outro diferente, o programador deve executar a tradução cabal do programa para a linguagem Assembly do processador de destino. Isso equivale a dizer que Assembly não é propriamente dita uma linguagem, mas sim um conjunto de muitos dialetos. Quem aprende uma linguagem Assembly, aprende a linguagem Assembly de um determinado produto (processador). Outro fato é que linguagem Assembly é de baixo nível, ou seja, ela tem ainda as seguintes desvantagens: • Ela não utiliza uma filosofia de programação estruturada a qual propicia uma construção mais simples e clara das aplicações pela organização dos programas em módulos ou estruturas independentes entre si (que em linguagem C são chamadas de funções), as quais tem o objetivo de realizar uma tarefa específica. • Ela não possui nenhum comando, instrução ou função que vá além daqueles definidos no conjunto de instruções do processador utilizado. Isso implica em um trabalho extra para os programadores ao desenvolver rotinas ou operações que não fazem parte do conjunto de instruções do processador, produzindo, por conseguinte, programas muito extensos e complexos, com um fluxo muitas vezes difícil de ser seguido, pois qualquer operação mais complexa que se enseje programar na aplicação, esta deve ser traduzida em um conjunto de instruções Assembly. A linguagem de programação C é uma linguagem de uso geral, que permite obter um código eficiente, implementar uma programação estruturada e ainda disponibiliza um conjunto bastante sofisticado de operadores. A linguagem C, não foi desenvolvida para nenhuma área em particular. A sua generalidade, combinada com ausência de restrições, torna a linguagem C numa solução de programação eficiente e conveniente para uma grande variedade de aplicações de software. Muitas aplicações podem ser resolvidas de uma forma mais fácil e eficiente em C, do que nas suas linguagens nativas. C é uma linguagem de programação genérica desenvolvida para ser tão eficiente e rápida quanto a linguagem Assembly e tão calcada em filosofia de programação estruturada quanto as demais linguagens de alto nível. De fato, devido a sua proximidade com o hardware e com o Assembly, a linguagem C é considerada como a linguagem mais eficiente atualmente disponível, entendendo-se por eficiência como a velocidade com que o compilador traduz um programa em C para o 4 código de máquina bem como o tamanho do pacote de código de máquina gerado na compilação, o que é muito relevante devido aos limitados recursos de memória normalmente encontrado nos microcontroladores. A Maioria dos compiladores C respeita o padrão definido para a linguagem C pelo ANSI, (American National Standards Institute). Como a linguagem C por si só não é capaz de efetuar operações de leitura ou escrita em periféricos, o que requer normalmente intervenção de um sistema operativo, estas facilidades são disponibilizadas como parte da biblioteca standard, pelo que algumas funções da biblioteca e da própria linguagem estão adaptadas para endereçar as particularidades do microcontrolador, e que é dedicado a gerar um código para o microcontrolador específico de uma forma extremamente rápida e compacta. Algumas comparações entre as duas linguagens: • Ao programar em assembly o código gerado não irá depender do compilador utilizado, pois o mesmo precisará apenas traduzir as instruções digitadas pelo programador para o código de máquina a ser gravado no microcontrolador. • Quando um programa é feito em C o compilador tem um papel fundamental e decisivo na eficiência do mesmo. Bons compiladores C para 8051, por exemplo, em geral são caros. • Codificar em assembly é muito mais susceptível a erros que em C. • Modificar um programa grande em assembly é extremamente mais complicado que modificar um programa em C. • Testar um programa grande em um simulador assembly é em geral mais difícil e às vezes quase impossível dependendo da complexidade do hardware externo envolvido. • A maioria dos fabricantes disponibiliza ferramentas e ambientes de desenvolvimento considerando que os programas serão feitos em C. • A maior parte das bibliotecas, funções e material de apoio encontrados na internet estão em C. • Apesar dos compiladores de linguagem C se tornarem cada vez melhores, muitas vezes é possível otimizar trechos de códigos escrevendo-os em Assembly. A linguagem assembly é uma linguagem de baixo nível que exige um bom conhecimento do hardware no qual pretendemos implementá-la, no caso um microcontrolador específico, o que dificulta o reaproveitamento de código, pois geralmente um programa feito para um hardware não pode ser utilizado na implementação para outro hardware por causa das diferenças de suas arquiteturas. No entanto, em aplicações críticas que exigem muito em termos de memória e velocidade de execução, nenhuma outra linguagem é tão eficiente quanto um código bem feito em Assembly, contudo a complexidade de implementação nessa linguagem de baixo nível é elevada se comparada à feita em uma linguagem de nível mais alto, como o C, e 5 dependendo da complexidade do sistema, a utilização do assembly pode se tornar até mesmo inviável em termos de tempo de projeto e desenvolvimento e organização do código. Como se pode observar, a linguagem assembler pode ser extremamente improdutiva na maioria das situações e geralmente é encarada, fortemente, como um método didático e aproveitada neste caso. Ter um razoável conhecimento do que está por trás das instruções C durante a elaboração de um programa é sem dúvida um fator diferencial para um programador de microcontroladores. A linguagem C, porém, encapsula a complexidade de implementação de algumas funcionalidades em simples funções e diretivas, tais como a conversão A/D e a comunicação serial, usadas no presente estudo de caso. A desvantagem desta linguagem de alto nível está no consumo de memória RAN e ROM, devido ao encapsulamento do código. De forma semelhante a um montador, um compilador é um programa capaz de gerar código de máquina para ser executado por um microcontrolador (ou algum outro processador qualquer) a partir do código-fonte escrito por um programador. Há, no entanto, claras diferenças entre um compilador e um montador, como mostra a tabela a seguir: Característica Montador Compilador Tipo da linguagem do código-fonte Linguagem de montagem (“assembly”) Linguagem de alto nível (C, Pascal, etc). Dependência entre o código-fonte e o processador sim não Dependência entre o código de máquina gerado e o processador sim sim Possibilidade de inclusão de arquivos no código-fonte sim sim Número de instruções de código de máquina (opcodes) geradas para cada instrução do código-fonte Uma ou mais (duas no máximo) uma ou mais (pode passar de 100) Existência de “diretivas” (não geram código de máquina, mas fornecem informações para essa geração) sim sim Custo baixo; geralmente, gratuito alto; pode chegar a centenas de dólares Possibilidade de otimização do código de máquina gerado Não (deve ser gerado já otimizado) sim Tamanho do código-fonte maior menor Tamanho do código de máquina gerado menor maior Tempo de execução do código de máquina gerado menor maior Tempo para o desenvolvimento do código-fonte maior menor Facilidade de alteração do código- fonte menor maior Principais características de um compilador, comparadas com as de um montador. 6 As quatro últimas linhas desta tabela praticamente definem a escolha da linguagem para o desenvolvimento de um projeto. Quando há restrições para o tamanho do código de máquina gerado e/ou para o tempo de execução (o que acontece, muitas vezes, em aplicações “embarcadas”, onde os microcontroladores são mais comuns), em geral utiliza-se programação em linguagem de montagem. Já quando a preocupação é com a redução do tempo de desenvolvimento do código-fonte e com a facilidade de sua manutenção, em geral opta-se pela programação em linguagem de alto nível. Não são raros os casos em que se utilizam os dois tipos de linguagem. Linguagens Compiladas X Linguagens Interpretadas: Durante a fase de codificação do programa da aplicação os programadores normalmente utilizam ferramentas, as quais são programas de computador especiais, denominados geradores de programa, que realizam automaticamente a conversão do programa da aplicação (algoritmo) em códigos ou mesmo comandos de determinada linguagem e em alguns casos, os mesmos fazem também a tradução do código. Esses programas especiais são classificados em duas categorias distintas: • Compiladores e • Interpretadores. As linguagens interpretadas, como o BASIC e BASIC STAMP, são aquelas em que a tradução da linguagem é feita em tempo real, durante a execução do programa. Isso significa que as linguagens interpretadas são mais lentas, pois o processo de tradução tem de ser feita para cada instrução do programa. Por sua vez as linguagens compiladas são aquelas em que o processo de tradução (compilação) é feito previamente e o código gerado pelo compilador pode ser carregado na memória e a execução pode ser feita diretamente por esse código. Note que no caso das linguagens interpretadas, o trabalho de tradução do código- fonte, além de ter de ser feito em tempo de execução, o que torna a execução lenta, tal trabalho deve ser feito pelo próprio processador que irá executar o código gerado, o que significa dizer que o programa interpretador BASIC fica carregado consumindo recursos de memória e devido a limitação de tais recursos nos microcontroladores isso resulta uma enorme diferença, se comparado com as linguagens compiladas. Existe ainda a questão da otimização do código: nas linguagens interpretadas cada comando de alto nível é traduzido em uma respectiva seqüência de comandos de baixo nível pelo programa interpretador. Se um mesmo comando for utilizado diversas vezes no programa, serão geradas tantas seqüências de comandos de baixo nível quantas forem as aparições do comando de alto nível. Já por sua vez os compiladores, por serem programas altamente complexos são também programas volumosos e de modo algum poderiam ser carregados na parca memória dos microcontroladores. Os compiladores utilizam-se de uma variedade de artifícios para a otimização do código gerado, tanto tendo objetivando a minimização do espaço ocupado pelo código, quanto na velocidade de execução do mesmo. 7 Outra questão interessante é que em aplicações críticas, nas quais cada microssegundo é muito relevante, como rotinas de tratamento de interrupção com elevado número de interrupções num curto espaço de tempo, rotinas de manipulação de dados como conversões entre bases numéricas, etc, podemos utilizar código Assembly dentro do programa em linguagem C, de forma a acelerar ou otimizar ainda mais a sua execução. A linguagem Assembly pode ser inserida no programa C em qualquer ponto, bastando para isso utilizar as diretivas #asm e #endasm. As variáveis C conservam seus nomes na utilização da linguagem Assembly. Que outra linguagem de alto nível permite isso? Além daquilo que já foi exposto, por ser calcado em filosofia de programação estruturada, a linguagem C permite construções mais simples e claras dos programas de aplicação, o que facilita a criação de programas de maior complexidade, quando comparado com outras linguagens as quais não são estruturadas como Assembly ou BASIC. Utilizando a Linguagem C para Programar Microcontroladores: Os compiladores são ferramentas poderosas para a produção de software, por traduzirem programas escritos em linguagem de alto nível para programas escritos em linguagem de máquina. Entretanto, a interação da linguagem de alto nível com as operações sobre o hardware (operações de entrada e saída), em alguns casos, não são previstas na linguagem. Nos programas executados em computadores, as operações de entrada e saída são executadas pelo sistema operacional, através de "systems calls". Nos equipamentos desprovidos de sistema operacional é necessário que o programador crie os programas básicos de entrada e saída, escritos usualmente em linguagem assembly para que apresentem um bom grau de otimização, minimizando o tempo de sua execução. Eles serão utilizados por outros programas, escritos em linguagem de alto nível ou mesmo em linguagem assembly, e são comumente agrupados em bibliotecas. Esses programas são escritos na forma de sub-rotinas, e convém altamente que sejam respeitados os padrões de passagem de parâmetros e de devolução de resultados definidos pelo compilador que se pretende utilizar, visando as suas chamadas dentro de programas escritos em linguagem de alto nível. Esse conjunto de sub-rotinas básicas deve ser anexado ao programa que as utilize na fase de ligação/alocação. Cada sub-rotina deve, portanto, ser declarada como pública para que sejam satisfeitas as chamadas geradas pelo programa em linguagem de alto nível, que a declarará como externa. De forma análoga, pode-se desenvolver rotinas em C e chamá-las em programas elaborados em linguagem assembly. Em C é de extrema importância os tipos de dados. Identifique os diferentes tipos de dados utilizados e a correspondência ao número de bits e intervalo de valores que podem tomar. 8 No processo de compilação também é possível definir o modelo de memória a utilizar. Os tipos de modelos de memória são: SMALL, COMPACT e LARGE. A utilização do compilador de C, permite a declaração de rotinas de serviço à interrupção, o que faz que o programa salte para essa parte do código quando uma interrupção ocorre. Características Inerentes dos Compiladores “C”: O uso de uma linguagem de alto nível no desenvolvimento de programas para microprocessadores e microcontroladores requer alguns cuidados adicionais. Mesmo se utilizando linguagens padronizadas, como a Linguagem C, não existe um padrão de como o código deve ser gerado para cada processador. Compiladores de fabricantes diferentes que geram código para um mesmo processador não necessariamente adotam as mesmas convenções. Assim, cada compilador apresenta características próprias para a geração das instruções em linguagem de máquina, tais como passagem de parâmetros para subrotinas, devolução de resultados de funções, tratamento de interrupções, etc. Assim, compiladores de fabricantes diferentes, que geram código para um mesmo processador, não necessariamente adotam as mesmas convenções. Além disso, muitos recursos existentes em uma implementação convencional de C, utilizada em computadores, nem sempre são encontrados em implementações especificas para a geração de código para determinados microprocessadores e microcontroladores (por exemplo, 8051, 8080/8085, Z80, 8086/8088, 68000, etc). Com o advento do microcomputador pessoal (por exemplo, o PC, da IBM e sucessores), surgiram compiladores C específicos para essas máquinas, com bibliotecas de rotinas de manipulação dos seus periféricos típicos (teclado, vídeo, disco, etc), e gerando o programa executável compatível com elas, e que nem sempre permitem que o mesmo possa ser utilizado para a geração de programas para sistemas diferentes, baseados no mesmo processador (por exemplo, baseados no 8086/8088). Tais sistemas costumam apresentar áreas de memória especificas, diferente dos computadores pessoais, exigindo compiladores e outras ferramentas de geração de programa, com capacidade de alocação de programas para qualquer região de memória escolhida pelo programador. Os manuais específicos de cada compilador devem ser sempre consultados, observando-se as restrições existentes, os recursos de biblioteca disponíveis, os mecanismos de geração de código e demais convenções adotadas. Compiladores “C” e os Microcontroladores: Atualmente, a grande maioria dos compiladores “C” para microcontroladores não é gratuita. Em muitos casos os custos envolvidos cabem no orçamento das empresas que desejam desenvolver seus produtos utilizando um compilador comercial. Porém para muitos leitores, este “custo” é às vezes inaceitável, pois não cabe em seus orçamentos. Apesar da maioria dos fabricantes oferecer seus compiladores em versões para testes, eles sempre o fazem com algumas restrições tais como: 9 • Limite no tamanho máximo do código gerado; • Impossibilidade de gerar código para alguns tipos de microcontroladores; • Tempo de uso limitado há alguns meses, entre outras. Talvez devido a isto, possa-se explicar o uso por muitos da linguagem “Assembly”. Esta, geralmente é fornecida de maneira gratuita pelo próprio fabricante do microcontrolador. Assim, muitos optam por esta linguagem não por escolha, mas por pura falta de alternativa. Não se deve descarta o aprendizado da linguagem “assembly” do microcontrolador que se deseja utilizar. Ela ainda é, e continuará sendo, a “base” de toda programação para microcontroladores. Muitas vezes, em uma determinada parte do programa, pode-se necessitar de uma “gerência” maior das ações sobre o microcontrolador, por exemplo. E isso só poderá ser obtido se o leitor construir esta parte de código em “Assembly” (a maioria dos compiladores “C” aceita a inserção de mnemônicos Assembly diretamente no código “C” a ser copilado). Mas, felizmente, nem tudo está perdido para aquele que não pode adquirir um compilador do tipo comercial. Existem versões de compiladores, desenvolvidas por programadores “autônomos”, que são distribuídas gratuitamente na Internet. Claro que o leitor deve ter em mente que se trata, na sua maioria, de compiladores em “desenvolvimento”. Muitos ainda estão disponibilizados em versão betha, novos implementos são inseridos e antigos “bugs” corrigidos. Sendo assim, estes compiladores não são indicados para empresas que desejam desenvolver seus produtos, até porque estes compiladores não possuem qualquer garantia e / ou suporte. Eles são indicados apenas para aficionados, estudantes e amantes do “mundo microcontrolado” que desejam desenvolver suas próprias soluções sem arcar com os custos de um compilador durante a fase de aprendizado. Um compilador gratuito: Na Internet é possível encontrar bons compiladores “C” para os mais variados tipos de microcontroladores. No caso eu sugiro que o leitor comece seu aprendizado com o compilador SDCC para o 8051. O SDCC (Small Device C Compiler) é um compilador distribuído gratuitamente na Web sob a licença GNU. Para obter este compilador o leitor deverá acessar o link http://souceforge.net/project/showfiles.php?group_id=599 e fazer o “download” do arquivo “sdcc-2.4.0-setup.exe” presente na pagina indicada. André Luis Lenz Técnico de Ensino do NAI – Núcleo de Automação Industrial Escola SENAI “Mariano Ferraz” – Vila Leopoldina – São Paulo – SP andrellenz@hotmail.com