UNIVERSIDADE DO ESTADO DE SANTA CATARINA – UDESC CENTRO DE CIÊNCIAS TECNOLÓGICAS – CCT
DEPARTAMENTO DE ENGENHARIA ELÉTRICA – DEE PROGRAMA DE PÓS-GRADUAÇÃO EM AUTOMAÇÃO INDUSTRIAL - PGAI
Formação: Mestrado em Automação Industrial
DISSERTAÇÃO DE MESTRADO OBTIDA POR Fabrício Noveletto
Desenvolvimento de um Ambiente de Programação Visual Orientado a Objetos para Robôs Móveis
Apresentada em 10 / 10 / 2003 perante a banca examinadora:
Prof. Dr. Antonio Heronaldo de Sousa - Presidente - CCT/UDESC Prof. PhD. Alcy Rodolfo Carrara - PUC/PR Prof. PhD. Marcelo da Silva Hounsell – CCT/UDESC
Prof. Dr. Silas do Amaral - CCT/UDESC
UNIVERSIDADE DO ESTADO DE SANTA CATARINA – UDESC CENTRO DE CIÊNCIAS TECNOLÓGICAS – CCT
DEPARTAMENTO DE ENGENHARIA ELÉTRICA – DEE PROGRAMA DE PÓS-GRADUAÇÃO EM AUTOMAÇÃO INDUSTRIAL - PGAI
DISSERTAÇÃO DE MESTRADO
Mestrando: FABRÍCIO NOVELETTO – Engenheiro Eletricista Orientador: Prof. Dr. ANTONIO HERONALDO DE SOUSA – CCT/UDESC
Desenvolvimento de um Ambiente de Programação Visual Orientado a Objetos para Robôs Móveis
DISSERTAÇÃO APRESENTADA COMO REQUISITO ÀOBTENÇÃO DO TÍTULO DE MESTRE EMAUTOMAÇÃO INDUSTRIAL DA UNIVERSIDADE DOESTADO DE SANTA CATARINA, CENTRO DECIÊNCIAS TECNOLÓGICAS – CCT, ORIENTADA PELOPROFESSOR DR. ANTONIO HERONALDO DE SOUSA.
Joinville, 10 de Outubro de 2003.
AGRADECIMENTOS Ao amigo e orientador, Professor Antonio Heronaldo de Sousa por sua
dedicação e sabedoria no acompanhamento em todos estes anos de estudo.
A professora Regina de Felice Souza pelo seu incentivo, apoio e presteza.
Ao Professor Marcelo da Silva Hounsell por suas significantes contribuições
no decorrer deste trabalho.
A todos os professores do Departamento de Engenharia Elétrica e a
coordenação do Mestrado em Automação Industrial.
A minha esposa Elaine por seu apoio irrestrito durante toda esta jornada.
A família de minha esposa e toda minha família por estar sempre ao meu lado
em todos os momentos de minha vida, em especial meu pai Ademar, minha mãe
Eronildes e minha irmã Alessandra.
Ao amigo Adriano Bresolin pelo companheirismo em todos estes anos de
estudo.
Por fim, agradeço a todos que de uma forma ou outra colaboraram para que
este trabalho terminasse com sucesso.
SUMÁRIO SUMÁRIO................................................................................................................... I RESUMO..................................................................................................................IV ABSTRACT................................................................................................................V INTRODUÇÃO GERAL ............................................................................................1
Motivação...................................................................................................... 1
Objetivo......................................................................................................... 1
Justificativa ................................................................................................... 2
Delimitação ................................................................................................... 3
Panorama geral da dissertação .................................................................... 3
CAPÍTULO 1: ROBÔS MÓVEIS
Introdução ..................................................................................................... 5
1.1. Considerações iniciais........................................................................ 5
1.2. Evolução dos robôs móveis ............................................................... 6
1.3. Robôs móveis e suas aplicações ..................................................... 10
1.3.1. Robôs industriais ................................................................... 10
1.3.2. Robôs de serviço................................................................... 11
1.3.3. Robôs de campo ................................................................... 11
1.4. Característica dos robôs móveis ...................................................... 12
1.5. O Robô Khepera .............................................................................. 13
1.5.1. Motores e controle do motor.................................................. 15
1.5.2. Sensores infravermelhos de proximidade e luz ambiente ..... 18
1.5.3. O protocolo de comunicação serial ....................................... 22
1.6. Linguagens de programação para robôs móveis ............................. 23
1.6.1. Ambientes de programação para o robô Khepera................. 24
I
CAPÍTULO 2: PROGRAMAÇÃO VISUAL ORIENTADA A OBJETOS Introdução ........................................................................................................ 29
2.1. Evolução das linguagens de programação ...................................... 29
2.2. O paradigma da programação orientada a objetos .......................... 32
2.3. Elementos da programação orientada a objetos.............................. 33
2.3.1. Abstração .............................................................................. 34
2.3.2. Encapsulamento.................................................................... 34
2.3.3. Herança................................................................................. 35
2.3.4. Polimorfismo.......................................................................... 36
2.4. O conceito de objeto ........................................................................ 37
2.4.1. Métodos internos e variáveis públicas................................... 40
2.5. O conceito de classes ...................................................................... 41
2.6. Hereditariedade................................................................................ 44
2.7. Considerações finais sobre programação orientada a objetos......... 46
2.8. Linguagem de Programação Visual ................................................. 47
2.9. Alguns aspectos sobre o uso de linguagens visuais ........................ 48
2.10. Linguagens visuais orientadas a objetos.......................................... 51
CAPÍTULO 3: DESENVOLVIMENTO DO AMBIENTE K++
Introdução ................................................................................................... 53
3.1. A linguagem de programação K++................................................... 53
3.1.1. Hierarquia e descrição de classes......................................... 54
3.1.2. Tipologia e representação de dados ..................................... 56
3.1.3. Estruturas de controle ........................................................... 58
3.1.4. Operações dedicadas............................................................ 59
3.2. O ambiente de programação K++ .................................................... 61
3.3. Implementação do ambiente K++..................................................... 63
3.4. As classes Serial e Khepera ............................................................ 64
3.4.1. A classe Serial....................................................................... 64
3.4.2. A classe Khepera .................................................................. 65
3.4.2.1. Descrição dos métodos da classe Khepera ................ 67
II
CAPÍTULO 4: RESULTADOS OBTIDOS Introdução ................................................................................................... 81
4.1. Usando o ambiente de programação K++........................................ 81
4.2. Programação procedimental usando o K++ ..................................... 84
4.2.1. Exemplo 1 – Robô AGV......................................................... 85
4.2.2. Exemplo 2 – Robô AGV......................................................... 87
4.2.3. Exemplo 3 – Robô Explorador............................................... 88
4.2.4. Exemplo 4 – Robô Explorador............................................... 91
4.3. Programação orientada a objetos usando o K++ ............................. 92
4.3.1. Exemplo 1 – Robô Explorador............................................... 92
4.3.1.1. Definindo a classe Explorador .................................... 92
4.3.1.2. Definindo o objeto AGV .............................................. 93
4.3.2. Exemplo 2 – Robô que se move em direção à luz ................ 94
4.3.2.1. Definindo a classe Besouro ........................................ 95
4.3.2.2. Definindo o objeto BEETLE ........................................ 96
4.4. Programação avançada usando o Visual C++ ................................. 99
CONCLUSÃO........................................................................................................103
Melhorias futuras e possíveis desdobramentos ........................................ 104
REFERÊNCIAS BIBLIOGRÁFICAS......................................................................105 ANEXOS................................................................................................................109
III
RESUMO
Este trabalho apresenta um estudo sobre o desenvolvimento de um ambiente
de programação para robô móvel, chamado K++. Foi usado como base para o
desenvolvimento do ambiente o paradigma da programação orientada a objetos,
conjuntamente com a programação visual.
A principal característica deste ambiente é usar, em conjunto, estruturas
gráficas e estruturas textuais para melhor representar dados e algoritmos. O K++
combina características como a reusabilidade da programação orientada a objetos e
a acessibilidade da programação visual. O uso de estruturas visuais orientadas a
objetos, melhoram a qualidade e a acessibilidade das informações trocadas no
desenvolvimento de algoritmos para robôs móveis.
Além disso, através de uma classe desenvolvida para implementar a
comunicação com o robô móvel, o ambiente K++ permite simulações em tempo real.
Neste sentido, os resultados dos testes com algoritmos desenvolvidos com o K++
foram amplamente satisfatórios.
IV
ABSTRACT
This work presents a study on the development of a environment
programming for mobile robots, called K++. It was used as base for the development
of the environment the paradigm of the object-oriented programming, jointly with to
visual programming.
The main characteristic of this environment is to use, together, structures
graphs and textual structures for best to represent data and algorithms. K++
combines characteristics as the reusability of the object-oriented programming and
the accessibility of the visual programming.
The use of visual structures object-oriented, they improve the quality and the
accessibility of the information changed in the development of algorithms for mobile
robots. Besides, through a class developed to implement the communication with the
mobile robot, the K++ environment allows simulations in real time. In this sense, the
results of the tests with algorithms developed with K++, they were thoroughly
satisfactory.
V
INTRODUÇÃO GERAL
• Motivação
Os robôs móveis estão cada vez mais presentes dentro das empresas e em
outras diversas áreas, executando as mais variadas tarefas. Mas apesar do
crescente número de aplicações onde é feito o uso da robótica móvel, a tarefa de
programar um robô ainda oferece bastante dificuldade. Além da tecnologia
sofisticada do próprio robô, os softwares empregados na sua programação são
bastante complexos e exigem um alto grau de conhecimento relacionado à
linguagem de programação. Sendo assim, a dificuldade em programar robôs faz com
que menos pessoas possam realmente explorar as potencialidades dos robôs e de
suas aplicações.
• Objetivo
Este trabalho tem por objetivo desenvolver uma linguagem de programação
para robôs móveis, que minimize as dificuldades encontradas na maioria das
linguagens de programação usadas para este propósito. Com isto objetiva-se
aumentar o universo de pessoas capazes de desenvolver aplicações para robôs
móveis. Para este trabalho utilizou-se como referência o robô móvel Khepera.
Desta forma propôs-se utilizar os conceitos da programação orientada a
objetos em conjunto com os conceitos da programação visual. A utilização destes
conceitos deu origem a uma nova linguagem de programação visual orientada a
objetos chamada de K++ (em alusão ao robô Khepera).
A idéia central da linguagem K++ é combinar as principais características
obtidas com o uso da programação visual e da programação orientada a objetos: a
acessibilidade e a reusabilidade, respectivamente.
Mais especificamente, além do desenvolvimento de um ambiente de
programação para robôs móveis, o presente trabalho objetivou a implementação dos
mecanismos de interação com um robô real, através da utilização das estruturas da
linguagem K++.
1
• Justificativa
O ambiente de programação visual orientado a objetos para robôs móveis
K++ se justifica pela necessidade do aprimoramento e desenvolvimento de novas
ferramentas que possam auxiliar a programação e o uso desses robôs, uma vez que
há um crescente número de estudos envolvendo o uso dos robôs móveis.
(NOVELETTO, 2003a) (NOVELETTO, 2003b) (NOVELETTO, 2003c).
Uma das dificuldades envolvidas na pesquisa de robôs móveis está no custo
do próprio robô. O seu alto custo acaba muitas vezes inviabilizando bons projetos.
Neste sentido, optou-se usar para a validação do ambiente K++ o robô móvel
Khepera, que possui funcionalidades semelhantes às de robôs móveis de maior
porte, sendo amplamente usado na comunidade acadêmica e científica. A grande
vantagem do uso de robôs móveis de pequeno porte para o estudo e o
desenvolvimento de aplicações está justamente em seu tamanho e custo reduzidos,
pois permite a criação de ambientes para simulações com maior facilidade e com
menor custo (MONDANA, 1993).
Em geral a programação desse tipo de robô é realizada de forma textual,
através do compilador GNU C ou com o uso de ambientes de programação visual
como o LabVIEW® e o MatLab®. Um dos problemas no uso da programação textual
está no elevado nível de envolvimento com detalhes intrínsecos à linguagem C. Já
com o uso dos ambientes de programação visuais citados, por serem softwares
fechados, ocorre uma perda na flexibilidade de programação do robô, uma vez que
apenas o uso das bibliotecas desses ambientes limita o desenvolvimento de
aplicações para o robô. Neste sentido, o K++ por ser um software aberto, permite
que futuras modificações possam ser feitas de acordo com as necessidades do
projeto.
O K++ foi desenvolvido com a ferramenta de programação Visual C++, que
usa a linguagem C++, dando suporte a programação orientada a objetos. Além
disso, essa ferramenta visual permite que as alterações no software possam ser
feitas com maior agilidade (KRUGLINSKI, 1996).
No ambiente proposto (K++), o uso do paradigma da programação orientada
a objetos em conjunto com a programação visual possibilita o reuso de funções e
2
minimiza os erros de programação, garantindo desta forma um maior grau de
produtividade no desenvolvimento de aplicações para o robô.
O K++, por ser uma linguagem visual, usa uma simbologia gráfica para
representar dados e classes e a programação é feita de forma estruturada baseada
em arranjos gráficos. Além disso, a linguagem K++ utiliza ícones para representar as
operações dedicadas efetuadas pelo robô.
O desenvolvimento de algoritmos de forma visual possibilita a ocultação de
detalhes de implementação, focando a programação no nível da aplicação. Já o uso
de ícones e de elementos visuais de programação possibilita a criação de algoritmos
de maneira bastante produtiva, facilitando a compreensão do algoritmo.
• Delimitação
A delimitação da pesquisa realizada neste trabalho se restringe ao
desenvolvimento de uma linguagem visual de programação orientada a objetos para
robôs móveis. Especificamente o ambiente permitirá que as aplicações
desenvolvidas com o K++ sejam executadas diretamente em um robô Khepera real.
Além disto, está previsto para um trabalho futuro, a geração do código em linguagem
C relativo ao algoritmo desenvolvido no K++, para que o robô possa operar em
modo autônomo.
• Panorama geral da dissertação No capítulo 1 são feitas algumas considerações que têm por objetivo
proporcionar uma visão mais ampla a cerca dos robôs móveis e suas aplicações.
Com base no conhecimento dos aspectos gerais que englobam a robótica móvel, é
possível compreender melhor a real necessidade de se criar ferramentas de
desenvolvimento de aplicações.
O capítulo 2 faz um breve apanhado sobre os principais tipos de linguagens
de programação, e dá a sustentação teórica necessária para melhor compreender os
objetivos embutidos na escolha da programação visual em conjunto com a
programação orientada a objetos como plataforma para este trabalho.
3
O capítulo 3 aborda os aspectos envolvidos no desenvolvimento do ambiente
de programação visual orientado a objetos para robô móvel, chamado de K++. Estes
aspectos contemplam a idéia de se usar os paradigmas da programação orientada a
objetos em conjunto com a programação visual para melhorar o desenvolvimento de
aplicações para robôs móveis com simulação em tempo real usando o próprio robô.
Além do uso desses paradigmas de programação, o desenvolvimento do ambiente
também leva em consideração os aspectos psicológicos de aprendizagem
envolvidos no processo.
O capítulo 4 mostra a implementação de algoritmos através do ambiente de
programação K++. Neste capítulo também são abordados alguns aspectos sobre a
utilização do ambiente K++, bem como, todo o processo de criação de algoritmos
usando a programação procedimental e a programação orientada a objetos neste
ambiente. Por fim, serão apresentados alguns aspectos sobre o uso de programação
avançada para o desenvolvimento de novos métodos a partir do Visual C++.
Encerrando o trabalho, são relatadas as conclusões do trabalho e sugeridas
as propostas para trabalhos futuros.
4
CAPÍTULO 1 ROBÔS MÓVEIS
Introdução
As considerações feitas neste capítulo têm por objetivo proporcionar uma
visão mais ampla a cerca dos robôs móveis e suas aplicações. Com base no
conhecimento dos aspectos gerais que englobam a robótica móvel, é possível
compreender melhor a real necessidade de se criar ferramentas de desenvolvimento
de aplicações.
1.1 Considerações iniciais
De acordo com a Robotic Industries Association (RIA) um robô pode ser
definido como sendo um manipulador programável multifuncional, capaz de mover
materiais, partes, ferramentas ou dispositivos específicos através de movimentos
variáveis programados para realizar uma variedade de tarefas (RUSSEL, 1995).
O termo robô se origina da palavra tcheca robota, que significa trabalho
escravo. Este termo foi usado pela primeira vez em 1921 na peça teatral Rossum’s
Universal Robots, escrita pelo escritor tcheco Karel Capek (ASIMOV, 1994). Desde
então a palavra robô vem sendo utilizada para se referir às máquinas que executam
trabalhos para ajudar as pessoas.
Ao longo da história o homem evoluiu criando mecanismos para facilitar sua
vida. Neste sentido, os robôs vêm cumprindo um papel importante na vida do
homem moderno estando presentes em um número cada vez maior principalmente
dentro das indústrias.
A competitividade do mercado estabelece a necessidade de se produzir com
maior velocidade, maior qualidade e menores custos, o que justifica a crescente
utilização de robôs dentro dos processos industriais. Outra importante aplicação na
área de robótica é o uso de robôs móveis para a execução de tarefas consideradas
de risco para o homem. Tarefas como a exploração de ambientes insalubres,
5
manuseio de material radioativo, localização de minas terrestres e submarinas, são
alguns exemplos das aplicações para os robôs móveis.
1.2 Evolução dos robôs móveis
No final da década de 40, o professor W. Grey Walter, um renomado
neurofisiologista, construiu duas tartarugas eletromecânicas bastante avançadas
para aquela época (DI PAOLO, 1998). Seu interesse era estudar o comportamento
destes robôs móveis, que por ele foram denominadas de tartarugas. Do ponto de
vista mecânico e eletrônico, estas tartarugas eram bastante simples: os movimentos
eram feitos por três rodas montadas em triciclo, sendo duas de propulsão e uma de
direção, e eram comandadas por motores elétricos independentes para cada uma
delas. Os sentidos eram determinados por um sistema de sensoriamento bastante
simples, formado por um sensor de luz e um sensor de contatos, montados
externamente. A alimentação de energia era fornecida por uma bateria comum,
montada na parte de trás da tartaruga, e uma carapaça de plástico abrigava e
protegia todo o conjunto. A figura 1.1 mostra uma tartaruga sem a carapaça.
Figura 1.1. Tartaruga eletromecânica desenvolvida por Walter
6
O processador a bordo das tartarugas era extremamente simples: um circuito
analógico com apenas duas válvulas eletrônicas, que comandavam os motores das
rodas e de direção a partir da informação dos sensores. As tartarugas podiam fazer
apenas duas coisas: evitar obstáculos grandes, recuando quando batia em algum, e
seguir alguma fonte de luz. Quando a fonte de luz era muito intensa, o robô recuava,
ao invés de avançar.
Apesar dos notáveis estudos desenvolvidos por W. Grey Walter, alguns
autores identificam como o primeiro robô móvel construído, um robô desenvolvido
pelo Stanford Research Institute em 1968, chamado Shakey (GROOVER, 1988).
Este robô tinha uma grande variedade de sensores, que incluíam uma câmara de
vídeo e sensores de toque e navegava entre as salas do laboratório, enviando sinais
de rádio a um computador, que permitia efetuar algumas tarefas como empurrar
caixas e evitar obstáculos (NITZAN, 1985). O robô Shakey, ilustrado na figura 1.2,
tinha uma unidade de processamento embarcada que coletava os sinais sensoriais e
os enviava para um computador remoto que executava o processamento,
transmitindo ao robô o comando que geraria a ação desejada.
Figura 1.2. Robô Shakey
7
Em 1977 foram desenvolvidos alguns trabalhos com o veículo StanfordCart
do Stanford Artificial Intelligence Laboratory (BOTELHO, 1996). O StanfordCart
trabalhava em uma área plana, com obstáculos colocados separadamente e utilizava
um sistema de navegação baseado no "parar e seguir", parando e fazendo leitura de
seus sensores a cada metro percorrido, realizando o planejamento da rota a seguir
(BRUMIT, 1992).
Durante os anos 80 vários trabalhos foram desenvolvidos em todo o mundo,
usando robôs baseados em reconhecimento de imagens através da visão. Outro
importante estudo sobre robôs móveis foi o desenvolvimento do robô Khepera, da
empresa K-Team, no Microcomputing Laboratory do Swiss Federal Institute of
Technology, com o apoio de outras entidades de pesquisa da Europa (MONDADA,
1993). O robô Khepera tem a capacidade de desviar-se de obstáculos e seguir ou
evitar fontes luminosas, além de permitir a utilização de algumas extensões, como
sistemas de visão, rádio controle e um pequeno manipulador. Dado o seu tamanho
extremamente reduzido e seu custo relativamente baixo, comparado a outros robôs
no mercado, o robô Khepera tornou-se um sucesso dentro das universidades em
várias partes do mundo (BOTELHO, 1996).
Atualmente, principalmente nas grandes indústrias, tem-se destacado a
utilização dos AGV’s (Automated Guided Vehicle), podendo executar desde tarefas
simples de transporte, até soluções de transporte mais complexas.
Uma outra aplicação muito importante para os robôs móveis está no campo
da exploração espacial. Um exemplo deste tipo de robô é o robô Sojourner, visto na
figura 1.3, que realizou uma das maiores façanhas da pesquisa do espaço pelo
homem: a exploração à distância do planeta Marte.
8
Figura 1.3. Robô espacial Soujouner
Atualmente o grande destaque entre os robôs móveis é o Honda Humanoid
Robot, mostrado na figura 1.4 (MAIOR, 1998). Este robô se assemelha aos seres
humanos, possuindo cabeça, tronco e membros, e possui um sofisticado sistema de
equilíbrio, fazendo com que o robô possa subir escadas, se manter em pé e até
mesmo chutar uma bola.
Figura 1.4. Robô humanóide Honda
9
1.3 Robôs móveis e suas aplicações
Trabalhar com robôs móveis é uma tarefa bastante complexa, em que é
necessário um alto grau de conhecimento sobre todos os aspectos que envolvem a
aplicação. As tarefas realizadas por um robô móvel podem variar de aplicações
menos complexas, como o transporte de carga dentro de uma planta industrial, a até
sofisticados sistemas de exploração espacial. A interação do robô com o ambiente
(os diversos sensores necessários, grau de segurança, etc) e o tipo do ambiente
(planos, buracos, obstáculos fixos e móveis entre outros) determinam o grau de
complexidade da aplicação.
Atualmente no mercado mundial de robôs móveis, existem basicamente três
tipos de robôs móveis, que podem ser divididos em industriais, de serviço e de
campo.
1.3.1 Robôs industriais
Os robôs industriais são geralmente plataformas móveis que executam
tarefas pesadas, como, por exemplo, carregar grande quantidade de materiais e
pintar grandes aviões, e geralmente, não têm uma autonomia muito grande
(MONDANA, 1996). Um dos principais métodos de navegação utilizado por esse tipo
de robô é através do uso de linhas pintadas no chão, que são usadas para definir o
caminho que o robô deve seguir. Um exemplo deste tipo de robô industrial é o AGV,
ilustrado na figura 1.5, e pode ser guiado de várias formas, sendo que as mais
comuns são as trilhas pintadas no chão da fábrica e as trilhas magnéticas. Além
disso, existem também os AGV’s guiados por laser, que são indicados para
situações onde a estrutura da fábrica inviabiliza o uso de sensores ópticos e
magnéticos e por fim, os AGV’s guiados por GPS, quando a aplicação exige
transporte em ambiente externo.
10
Figura 1.5. Exemplo de uma célula de manufatura usando um AGV
1.3.2 Robôs de serviço
A robótica de serviço é reconhecida como uma importante área de aplicação
para um futuro próximo. Sistemas de robótica móvel autônoma que executam tarefas
de serviço podem ser utilizados por todos os tipos de aplicação como transporte,
manipulação, exploração, vigilância, entre outros, em ambientes estruturados e com
um mínimo de conhecimento prévio destes (FIRBY, 1993). Os robôs de serviço são
utilizados principalmente para carregar materiais leves, para limpeza e para
vigilância, como por exemplo, na limpeza nos metrôs da França.
1.3.3 Robôs de campo
Os robôs de campo são desenvolvidos para realizar tarefas em ambientes não
estruturados e geralmente perigosos. Estes robôs têm evoluído muito nos últimos
anos. Suas aplicações são a exploração espacial, mineração, limpeza de acidentes
nucleares, desativação de minas terrestres (MÄCHLER, 1995) e submarinas,
exploração submarina a grandes profundidades, navegação em auto-estradas,
exploração de vulcões, e muitas outras (MONDANA, 1996). O sistema de navegação
destes robôs, principalmente os usados em exploração espacial, unem a tele-
operação com comportamentos reativos.
11
Além das três categorias principais, pode-se dizer que também existem robôs
usados para pesquisa, e são usados principalmente pela área acadêmica para o
desenvolvimento de novas tecnologias. Muitas empresas que produzem robôs
comerciais vendem versões para pesquisa de seus modelos. Um exemplo deste tipo
de robô é o Khepera da empresa K-Team, que é amplamente utilizado na
comunidade acadêmica.
1.4 Característica dos robôs móveis
Basicamente um robô móvel é composto por um sistema de processamento
de dados, sensores e atuadores, onde os dados coletados pelos sensores são
processados e enviados ao sistema de processamento que analisa os dados e
controla os atuadores (NITZAN, 1985).
A principal característica de um robô móvel, como o próprio nome já diz, é a
sua capacidade de locomoção. É esta capacidade de locomoção que possibilita aos
robôs móveis um universo muito grande de aplicações.
Os robôs móveis possuem três formas básicas de locomoção: rodas, corpos
articulados e pernas, podendo utilizar uma ou a associação dessas configurações,
geralmente através de motores e atuadores pneumáticos (GROOVER, 1988). A
forma de locomoção do robô deve levar em conta vários fatores como a finalidade, o
tipo de terreno em que opera, fonte de alimentação e autonomia de energia.
Uma das principais características dos robôs móveis atuais é o uso de
sensores externos que os tornam autônomos e capazes de operar em ambientes
com obstáculos. Estes sensores permitem ao robô extrair informações do ambiente,
levando-o a reagir às mudanças do mesmo de forma inteligente.
Os sensores podem ser classificados em sensores com contato e sensores
sem contato. Os sensores com contato ou táteis são dispositivos que indicam o
contato entre eles e algum outro objeto, e são normalmente usados em robôs
industriais ou manipuladores. Já os sensores sem contato, como os sensores de
visão, ultra-som e infravermelho são os principais tipos de sensores usados na
robótica móvel. Os sensores de visão são amplamente utilizados na robótica
principalmente para fazer o mapeamento de ambientes. Já os sensores de ultra-som
12
são usados na robótica para localização de superfície de objetos, determinação da
posição do robô em um dado ambiente, esquemas de navegação e para detecção
de obstáculos. Alguns robôs como o robô Khepera, por exemplo, também usam
sensores infravermelhos para detectar objetos e medir a intensidade de luz ambiente
(K-TEAM, 2002). A evolução tecnológica dos sensores e atuadores, em conjunto com o
processamento dos dados, permitiu o desenvolvimento de uma variedade de robôs
apta a lidar com vários tipos de aplicações, melhorando consideravelmente o
desempenho e a confiabilidade destas máquinas. A figura 1.6 mostra um esquema
básico de um robô móvel, composto de sensores, atuadores, CPU e fonte de
energia.
Figura 1.6. Componentes de um robô
1.5 O Robô Khepera
O Khepera é um robô móvel que possui funcionalidades semelhantes às de
robôs móveis de maior porte. Ele pode ser controlado através de um cabo RS-232
conectado a porta serial do PC ou pode rodar em modo autônomo. Geralmente o
Khepera é usado conectado ao PC nas fases de implementação e testes de
programas. Após esta fase, a aplicação pode ser transferida diretamente para a
memória do robô, possibilitando que o robô trabalhe em modo autônomo, ou seja,
sem estar conectado ao PC (K-TEAM, 1999a) (K-TEAM, 1999b).
13
O Khepera é um robô sofisticado que incorpora em sua unidade de
processamento um processador Motorola 68331 de 32 bits funcionando a uma
freqüência de 16 MHz e possui uma memória RAM de 256 Kbytes e uma memória
EPROM que pode variar de 128 a 256 Kbytes de acordo com o modelo do robô.
O movimento do robô é obtido através de dois servo-motores DC acoplados a
caixas de redução 25:1 com um sofisticado sistema de sensoriamento obtido por
sensores magnéticos e sinais de quadratura com uma resolução de 12 pulsos/mm.
Além disso, o Khepera possui um sistema de baterias recarregáveis, que
permitem uma autonomia de 30 minutos em movimento contínuo no modo
autônomo.
O sensoriamento do ambiente é feito por oito sensores infravermelhos (IR)
com emissor e receptor no mesmo componente, que permitem a obtenção de
valores de proximidade do robô em relação a objetos e níveis de luz ambiente.
A comunicação com o robô é feita através de uma interface serial RS-232
com velocidade de comunicação de até 38 Kbps.
Outra característica importante é a dimensão do robô que mede apenas 55
mm de diâmetro e 30 mm de altura. Também podem ser acrescentados ao módulo
básico do robô, módulos para visão, rádio controle e um manipulador. A figura 1.7
mostra um robô Khepera com sistema de visão e garra, operando em modo
autônomo (K-TEAM, 1999b).
Figura 1.7. Robô Khepera
14
Com todas essas funcionalidades o robô Khepera possibilita o teste em
ambientes reais de algoritmos desenvolvidos em simulações para o planejamento de
trajetórias, desvio de obstáculos, processamento de dados sensoriais, inteligência
artificial, entre muitos outros (LUND, 1996) (FLOREANO, 1996).
1.5.1 Motores e controle do motor
Como já mencionado cada roda é movida por um motor DC acoplado a uma
caixa de redução 25:1, onde um encoder incremental é colocado no eixo do motor,
enviando 24 pulsos por rotação da roda. Isto permite uma resolução de 600 pulsos
por rotação da roda, o que corresponde a 12 pulsos/mm da trajetória percorrida pelo
robô (K-TEAM, 1999b).
O processador do Khepera controla diretamente a energia enviada aos
motores, além de ler os pulsos enviados pelos encoders (contadores digitais de
posição). Uma rotina de interrupção detecta cada pulso do encoder incremental e
atualiza um contador de posição da roda.
A energia aplicada ao motor pode ser ajustada pelo processador fazendo o
chaveamento ON/OFF a uma determinada freqüência por um dado tempo. A
freqüência de chaveamento básica é constante e suficientemente alta para não
deixar o motor reagir a um simples chaveamento. Desta forma, o motor reage a um
tempo médio de energia, que pode ser modificado mudando o período em que motor
fica chaveado em ON (K-TEAM, 1999b). Este método em que somente a proporção
entre os períodos ON e OFF é modificada é chamado de PWM (pulse width
modulation), e é ilustrado na figura 1.8.
A figura 1.8, bem como as figuras ilustradas adiante neste capítulo foram
mantidas conforme as referências bibliográficas para que não houvesse possíveis
distorções na tradução dos termos utilizados.
15
Figura 1.8. O controle PWM (K-TEAM, 1999b)
O valor PWM é definido pelo tempo que o motor fica chaveado em ON, sendo
que este valor pode ser ajustado diretamente ou pode ser gerenciado por um
controlador local para o motor. O controlador do motor pode executar o controle da
velocidade ou da posição do motor, ajustando o valor correto do PWM de acordo
com a velocidade real ou posição real dos encoders.
O funcionamento dos motores também pode ser ajustado através de dois
controladores PID executados em uma rotina de interrupção do processador. Cada
termo destes controladores (Proporcional, Integral e Derivativo) é associado a uma
constante Kp, Ki e Kd respectivamente.
Nesse caso, o controle do motor pode ser efetuado de dois modos: o modo de
velocidade e o modo de posição. A figura 1.9 ilustra os controladores do motor e os
níveis de acesso permitido ao usuário.
16
Figura 1.9. Estrutura dos níveis de controles do motor e níveis de configuração do
usuário (K-TEAM, 1999b)
O modo de controle ativo é determinado pelo tipo de comando recebido. Se o
controlador recebe um comando de controle de velocidade, este é chaveado para o
modo de velocidade. Se o controlador recebe um comando de controle de posição, o
modo de controle é a automaticamente chaveado para o modo de posição.
Parâmetros de controle diferentes (Kp, Ki e Kd) podem ser ajustados para cada um
dos dois modos de controle.
Usando o modo de velocidade, o controlador terá como entrada um valor para
a velocidade das rodas, e o controle do motor modificará a velocidade das rodas. A
modificação da velocidade das rodas é feita da maneira mais rápida possível de
forma abrupta, sendo que nenhuma limitação para a aceleração é considerada neste
modo.
Usando o modo de posição, o controlador terá como entrada a posição alvo
da roda, com máxima aceleração e velocidade. Usando estes valores, o controlador
do motor acelera a roda até que a velocidade máxima seja atingida e desacelera até
alcançar a posição alvo. Este movimento segue um perfil trapezoidal que pode ser
visto na figura 1.10.
17
Figura 1.10. Curvas de aceleração e desaceleração para o controle de posição (K-
TEAM, 1999b)
Os valores de entrada e o modo de controle podem ser mudados a qualquer
momento. O controlador atualizará e executará o novo perfil no modo de posição, ou
simplesmente seguirá com uma determinada velocidade até que novos valores
sejam ajustados para o modo de velocidade.
A velocidade dos motores usa como unidade pulso/10 ms, que corresponde a
8mm/s, atingindo uma velocidade máxima é de 127/10ms, que corresponde a 1m/s.
Já, no controle de posicionamento do robô, a unidade retornada é equivalente a um
pulso do encoder, que corresponde a 0,08 mm.
1.5.2 Sensores infravermelhos de proximidade e luz ambiente
Os sensores infravermelhos (IR) estão dispostos em ângulos apropriados
como mostrado na figura 1.11 (vista superior), cobrindo o campo de atuação do
robô.
18
Figura 1.11. Posição dos sensores infravermelhos (K-TEAM, 1999b)
Os sensores são montados de forma que emissor e receptor fiquem no
mesmo componente, e podem efetuar dois tipos de medidas:
• A luminosidade do ambiente. Esta medição é feita usando somente a parte
receptora do dispositivo, sem haver a emissão de luz. Uma nova medição é
feita a cada 20 ms. Durante estes 20 ms, os sensores são lidos
seqüencialmente a cada 2,5 ms, totalizando desta forma a leitura de cada um
dos oito sensores.
• A luz refletida pelos obstáculos. Esta medição é realizada através da emissão
de luz pela parte emissora do dispositivo. O valor retornado é a diferença
entre a medição feita com emissão de luz e a medida feita sem emissão de
luz (luz ambiente). De modo similar à leitura de luz ambiente, uma nova
medida é feita a cada 20 ms e os sensores são lidos seqüencialmente a cada
2,5 ms.
A sensibilidade na resposta dos sensores de proximidade (onde a luz é
refletida pelo obstáculo) está diretamente relacionada ao tipo de material reflexivo. A
escala de sensibilidade de proximidade possui uma resolução de 10 bits com valores
entre 0 e 1023, sendo que quanto maior o valor mais próximo do obstáculo o robô se
19
encontra. Estes valores dependem também da refletividade do obstáculo. Quanto
melhor for a refletividade, maior será a sensibilidade do sensor. O plástico branco,
por exemplo, tem uma ótima refletividade, enquanto uma madeira escura tem uma
baixa refletividade. A figura 1.12 mostra a resposta dos sensores para alguns tipos
de materiais como: plástico preto, isopor verde, esponja rosa, plástico branco,
plástico cinza, madeira e cobre (K-TEAM, 2002a).
Figura 1.12. Valores medidos da luz refletida por vários tipos de objetos versus a
distância do objeto (K-TEAM, 1999b)
Os sensores de luminosidade funcionam de forma similar aos sensores de
proximidade com uma escala de sensibilidade com resolução de 10 bits, sendo 450
o valor padrão aproximado para um ambiente escuro. Deste modo, quanto menor o
valor retornado pelo sensor de luz ambiente, mais próximo da fonte de luz o robô se
encontra. É importante salientar que estas medidas dependem fortemente de fatores
como a distância da fonte de luz, sua cor, intensidade, posição vertical, entre outros.
A figura 1.13 mostra os valores típicos de luz ambiente medidos pelos sensores a
uma determinada distância de uma fonte de luz de 1 Watt (K-TEAM, 1999b) (K-
TEAM, 1999b).
20
.
Figura 1.13. Valores típicos para medida da luz ambiente versus distância de uma
fonte de luz de 1 Watt (K-TEAM, 1999b)
É necessário conhecer todos os aspectos do ambiente em que o robô está
inserido, pois, além das imprecisões do ambiente, pode haver diferenças físicas
entre as características de cada sensor. A figura 1.14 mostra uma medição feita por
seis sensores de um mesmo robô colocado em condições idênticas. Pequenas
diferenças de condições, como orientação vertical do sensor, condições de luz
ambiente e a cor do piso, podem provocar diferenças nas medidas.
Figura 1.14. Valores típicos de luz refletida por um obstáculo versus distância do
obstáculo para sensores do mesmo tipo em condições idênticas (K-TEAM, 1999b)
21
1.5.3 O protocolo de comunicação serial
O controle do robô pode ser feito através de um computador usando uma
porta de comunicação serial RS-232. A configuração dos parâmetros de
comunicação da porta serial (baudrate, start bit, stop bit e bit de paridade) no
computador deve estar de acordo com a configuração dos parâmetros de
comunicação serial existente no hardware do robô (K-TEAM, 1999b). Esta
configuração é feita através de jumpers localizados na parte superior do robô, como
mostra a figura 1.15.
Figura 1.15. Posição e configuração dos jumpers (K-TEAM, 1999b)
Nos modos 01, 02 e 03 a comunicação é feita com velocidade de 9600, 19200
e 38400 bps respectivamente.
A comunicação entre o computador e o robô Khepera é feita enviando e
recebendo mensagens no padrão ASCII. A comunicação se efetua através de
comandos enviados do computador para o Khepera, e quando necessário, uma
resposta é enviada do Khepera para o computador.
A comunicação é sempre iniciada pelo computador, e é baseada em dois
tipos de interações com o robô. O primeiro tipo é a interação com o robô através de
ferramentas de controle, que possibilitam manipular aspectos mais específicos do
robô como por exemplo, iniciar uma aplicação armazenada na memória ROM do
robô ou simplesmente reiniciar o robô.
22
O segundo tipo de interação é chamado de protocolo de controle, e permite
controlar todas as funcionalidades do robô. Cada interação entre o robô e o
computador é composta por:
• Um comando, iniciando com uma ou duas letras maiúsculas no formato
ASCII, seguido, se necessário, por um conjunto de parâmetros separados por
virgula (de acordo com o comando executado) e finalizado por um retorno de
carro ou avanço de linha, enviado do computador para o robô.
• Uma resposta, iniciando com um ou dois caracteres minúsculos, iguais aos do
comando de envio. Dependendo do comando, uma seqüência de números,
separados por virgula, é enviada após o caractere inicial e finalizada por um
retorno de carro ou avanço de linha, enviado do robô para o computador.
Este protocolo de controle pode implementar até 18 diferentes comandos para o
controle do robô (K-TEAM, 1999b).
1.6 Linguagens de programação para robôs móveis
As linguagens de programação para robôs móveis geralmente são linguagens
proprietárias, ou seja, feitas exclusivamente para a programação de um determinado
robô. Um dos problemas desse tipo de linguagem é que a programação fica limitada
a um conjunto predefinido de funções destinadas ao controle do robô. Muitas vezes,
essa limitação imposta pela linguagem de programação não permite que as
potencialidades do robô sejam exploradas na sua totalidade.
Alguns modelos de robôs permitem que, além da sua própria linguagem de
programação, suas funções sejam acessadas por meio de alguma outra linguagem.
O acesso ao robô por meio de uma linguagem de alto nível proporciona um
acréscimo importante no desenvolvimento de aplicações.
Além das linguagens de programação para robôs móveis reais, existem ainda
os ambientes de simulação. Através do modelamento matemático do robô é possível
desenvolver um ambiente de simulação capaz de se aproximar de forma satisfatória
23
dos modelos reais. Pelo fato de não ser necessária a presença do robô real, a
grande vantagem dos ambientes simulados é o baixo custo de desenvolvimento.
1.6.1 Ambientes de programação para o robô Khepera
O Khepera pode ser programado de várias maneiras. A maneira mais comum
é enviar comandos ao robô usando um protocolo de comunicação específico,
através de qualquer software de comunicação serial. Fica claro que estes programas
não podem implementar nenhum tipo de algoritmo, pois eles não podem manipular
os dados enviados e recebidos do robô. Para que se possa passar algum tipo de
tarefa para o robô há a necessidade de programas mais sofisticados. Dentre os
ambientes de programação mais conhecidos para a programação do Khepera existe
o KTProject, que é uma interface gráfica para ambiente Windows desenvolvida pelo
próprio fabricante do robô para que programas desenvolvidos em C sejam
compilados e gravados diretamente na memória do Khepera, permitindo que o
mesmo execute tarefas em modo autônomo. Outra importante ferramenta de
programação é o GNU C Cross Compiler, usada para o desenvolvimento de
aplicações nativas nas plataformas Windows, Linux e Sun (K-TEAM, 1999b) (K-
TEAM, 2002b).
O Khepera também pode ser programado através de ambientes de
simulação. Um exemplo de ambiente de simulação é o Webots (CYBERBOTICS,
2003), um simulador para robôs móveis desenvolvido pela Cyberbotics. Este
software, ilustrado na figura 1.16, tem se mostrado uma importante ferramenta para
pesquisadores e professores que atuam na área de robótica, agentes autônomos,
visão computacional e inteligência artificial. O simulador inclui modelos de dois robôs
reais, o robô Alice e o robô Khepera produzidos pela empresa suíça K-Team, e
permite incluir vários módulos, como módulo de visão e garras. O usuário pode
programar virtualmente o robô usando C ou C++, que é compatível com o robô real.
Um ambiente de edição 3D permite que o usuário crie os seus próprios cenários de
trabalho.
24
Figura 1.16. Webots - Ambiente de simulação para o robô Khepera
Também são utilizados outros ambientes para desenvolvimento de pesquisa
com o robô Khepera, como o LabVIEW e o MatLAB. Esses dois ambientes
possibilitam, através da instalação de plugins, efetuar a comunicação com o robô,
através da porta serial do microcomputador (K-TEAM, 1999b).
O LabVIEW (Laboratory Visual Environment Workbench) é um ambiente de
programação visual orientada a fluxo de dados muito utilizado na área de
engenharia, mais especificamente em aplicações de aquisição de dados, controle,
simulação, processamento de sinais e análise (NATIONAL, 2000). Ele é composto
de uma grande variedade de instrumentos virtuais, mais conhecidos por VI (virtual
instruments) que podem ser interligados para formar alguma aplicação. No caso do
robô Khepera, os instrumentos virtuais básicos, são formados por motores e
sensores e podem ser acessados através de painéis frontais. Com os painéis
disponíveis para o controle dos motores é possível ler a posição e velocidade
instantânea de cada roda, controlar a velocidade e posição de cada roda e
configurar os parâmetros da curva de velocidade, por exemplo.
Os painéis frontais são descritos por diagramas que estabelecem a lógica de
programação desde o processamento das informações de entrada até a geração
25
dos dados de saída. Estes diagramas são compostos por símbolos que representam
os controles e indicadores do painel frontal e por operações e estruturas
predefinidas na linguagem. Além das estruturas predefinidas, também é possível a
criação de sub-rotinas.
O painel de controle para configurar a velocidade de cada motor, mostrado na
figura 1.17, pode alterar a velocidade dos motores através de botões deslizantes, ou
manualmente, digitando o valor da velocidade no campo apropriado. A figura 1.18
mostra o diagrama correspondente ao painel frontal para o controle dos motores.
Figura 1.17. Painel frontal do controle de velocidade dos motores
26
Figura 1.18. Diagrama correspondente ao painel frontal para controlar a velocidade
dos motores
Da mesma forma, os valores retornados pelos sensores infravermelhos do
robô podem ser visualizados através de um painel de controle. O painel de controle
para leitura dos sensores, mostrado na figura 1.19, é composto por 8 medidores que
indicam através de uma barra o valor de cada sensor.
Figura 1.19. Painel de sensores para mostrar a leitura dos sensores infravermelhos
Através destes painéis de controle é possível desenvolver um grande número
de aplicações para o Khepera. Um exemplo de uma aplicação para o robô Khepera
usando o LabVIEW pode ser visto na figura 1.20, que mostra o painel de um veículo
27
de Braitenberg. A estrutura de controle foi baseada no trabalho de V. Braitenberg
(BRAITENBERG, 1984). A figura 1.21 mostra o diagrama VI que corresponde ao
painel do veiculo de Braitenberg.
Figura 1.20. Painel do veículo de Braitenberg
Figura 1.21. Diagrama do veículo de Braitenberg
28
CAPÍTULO 2 PROGRAMAÇÃO VISUAL ORIENTADA A OBJETOS
Introdução
Este capítulo tem por objetivo fazer um breve apanhado sobre os principais
tipos de linguagens de programação, e dar a sustentação teórica necessária para
melhor compreender os objetivos embutidos na escolha da programação visual em
conjunto com a programação orientada a objetos como plataforma para este
trabalho.
2.1 A evolução das linguagens de programação
As linguagens de programação basicamente evoluíram a partir do surgimento
do primeiro computador. Inicialmente, as linguagens eram baseadas em um conjunto
de códigos binários que representavam alguma instrução do processador. Essas
linguagens são conhecidas como linguagens de máquina e, basicamente existe uma
linguagem para cada família de processador. Apesar de simples, comparadas às
linguagens de alto nível disponíveis atualmente, as linguagens de máquina são
bastante eficientes no que diz respeito à velocidade de execução das instruções. O
fato de se programar usando código binário ou hexadecimal, faz com que esse tipo
de linguagem não seja a mais adequada para a descrição de um programa, uma vez
que os programas desenvolvidos podem ser sofisticados e essa linguagem primitiva
não é nem um pouco amigável ao programador. Uma evolução nas linguagens de
baixo nível veio com o uso da linguagem Assembly, que faz uso de códigos
mnemônicos, facilitando assim o desenvolvimento de programas (SETHI, 1996).
O próximo passo no sentido da evolução das linguagens de programação foi
as linguagens de alto nível não-estruturadas, que são mais sofisticadas que as
linguagens de baixo nível e seus comandos não estão diretamente vinculados ao
processador e sistema utilizados, permitindo seu uso em diferentes plataformas e
tornando a linguagem mais flexível. Do mesmo modo, a semântica de seus termos
29
torna-se mais genérica, não estando associada ao conjunto de instruções que irão
efetivamente implementá-la durante a execução do programa, onde operações mais
sofisticadas podem ser emuladas por seqüências de instruções mais simples do que
em linguagem de máquina. As linguagens COBOL (1959) e BASIC (1963) são
exemplos de linguagens não-estruturadas (GUDWIN, 1997). Apesar de melhorar
consideravelmente o desenvolvimento de software, as linguagens de programação
não-estruturadas geralmente não oferecem uma estrutura de controle adequada,
admitindo, por exemplo, o uso de desvios incondicionais (GOTO) que dificultam a
compreensão do código.
Neste sentido, surgiram as linguagens de programação estruturadas, também
conhecidas como procedimentais. Diferentemente das linguagens não-estruturadas,
as linguagens estruturadas possibilitam o uso de estruturas de controle, que tornam
possíveis operações como o teste de condições (IF – THEN – ELSE), o controle de
repetição de blocos de código (FOR – WHILE – DO) e a seleção de alternativas
(SWITCH - CASE). As linguagens procedimentais permitem que o código do
programa seja separado em módulos chamados de funções ou procedimentos. As
linguagens procedimentais são caracterizadas pela existência de algoritmos, que
determinam uma seqüência de chamadas de procedimentos, que constituem o
programa. As linguagens procedimentais mais comuns são o C, o PASCAL e o
FORTRAN (GUDWIN, 1997). Existem também as linguagens de programação funcionais, que evidenciam
um estilo de programação diferente das linguagens procedimentais. Esse tipo de
linguagem tem um conjunto de regras sintáticas e semânticas que podem ser
estendidas de muitas maneiras permitindo a criação de novas formas de expressar
problemas de uma maneira muito inteligível e de fácil manutenção. A programação
funcional enfatiza a avaliação de expressões, ao invés da execução de comandos.
As expressões nessas linguagens são formadas utilizando-se funções para combinar
valores básicos. As linguagens funcionais mais conhecidas são o LISP e o
PROLOG.
Seguindo a constante evolução das linguagens de programação, surgiu
então, o paradigma da programação orientada a objetos. As linguagens orientadas a
objetos foram originadas a partir da necessidade de se organizar o processo de
30
programação em uma linguagem. Na verdade a programação orientada a objetos é
uma técnica de programação. Uma linguagem é dita uma linguagem orientada a
objetos, se ela suporta o estilo de programação orientada a objetos. Um dos
objetivos da engenharia de software é separar o projeto de desenvolvimento de um
software em diversos módulos, de tal forma que o seu desenvolvimento e
manutenção possam ser implementados com baixo custo. No passado, os
paradigmas da engenharia de software derivavam os módulos baseados na
funcionalidade de um sistema que correspondiam basicamente a módulos
procedimentais. O paradigma de orientação a objeto mudou essa concepção,
idealizando a idéia de objetos, como módulos que se comunicam por meio de
mensagens, encapsulando ao mesmo tempo dados e funções. A primeira linguagem
orientada a objetos que se tem notícia é o Simula, desenvolvido em 1967.
Posteriormente veio o Smalltalk em 1970 (GUDWIN, 1997). Atualmente, existe uma
enorme diversidade de linguagens orientadas a objetos, abrangendo desde
linguagens de propósito geral, até linguagens para multimídia e programação em
lógica. Linguagens como o C++ incorporam características das linguagens de
programação orientadas a objetos. Já a linguagem Java é totalmente orientada a
objetos.
A evolução na capacidade de processamento do hardware também permitiu
que surgissem novas linguagens de programação, como as linguagens visuais, que
aproveitam essa capacidade de processamento para o desenvolvimento de
ambientes gráficos (WHITLEY, 1996). Pode-se dizer que uma informação visual é
mais fácil de ser entendida do que uma informação textual, portanto, a especificação
de um programa por meio de diagramas, ícones e outros recursos visuais tende a
tornar a programação mais fácil, permitindo que um grupo maior de usuários tenha
acesso ao desenvolvimento de programas. Uma representação visual de um
programa normalmente limita a flexibilidade dos programas que podem ser
desenvolvidos, mas em contrapartida, as linguagens de programação visual
permitem que programas sejam elaborados com mais facilidade e de forma mais
rápida do que a programação somente textual.
Seguindo as características da programação visual, existem também as
linguagens de programação que não são somente visuais, mas têm parte de sua
31
especificação determinada de forma visual. Normalmente este tipo de linguagem de
programação visual não é simplesmente uma linguagem, mas uma ferramenta de
programação que adota uma linguagem de programação textual, com recursos de
programação visual, a fim de melhorar a produtividade no desenvolvimento de
aplicações. Em outros casos, as ferramentas de programação visual efetivamente
participam da elaboração do programa, como por exemplo na determinação de
hierarquia de classes e métodos, em linguagens orientadas a objeto. As linguagens
incluídas nesta categoria são normalmente desenvolvimentos de linguagens de
programação convencional ou textual, acrescidas das ferramentas visuais. Algumas
linguagens que usam esse tipo de recurso são o Delphi, o Visual Basic e o Visual
C++ (GUDWIN, 1997).
Por fim, é possível citar as linguagens de programação visual puras, onde o
programa é descrito exclusivamente por meio de gráficos e diagramas. Neste tipo de
programação, o programa é elaborado através de diagramas com nós e arcos,
sendo que os nós representam módulos de software, a partir de uma biblioteca
prévia de módulos, e os arcos determinam a conexão entre os diferentes módulos.
Devido à forte dependência dos módulos de software, o uso deste tipo de linguagem
torna-se bastante específico, limitando desta forma o universo de aplicações. Um
exemplo deste tipo de linguagem é o Simulink, que trabalha em conjunto com o
Matlab e é usado para simulação de sistemas dinâmicos (GUDWIN, 1997).
2.2 O paradigma da programação orientada a objetos
Um dos principais objetivos da programação orientada a objetos é reduzir a
complexidade no desenvolvimento de software e aumentar sua produtividade. Neste
sentido, o aumento da complexidade dos ambientes computacionais que se
caracterizam por sistemas heterogêneos, podem se beneficiar com o uso da
programação orientada a objetos (VOSS, 1991).
A programação orientada a objetos não tem a intenção de substituir a
programação estruturada tradicional, podendo então, ser considerada como uma
evolução de práticas que são recomendadas na programação estruturada (LEE,
2002). O modelo de objetos permite a criação de bibliotecas que tornam efetivos o
32
compartilhamento e a reutilização de código, reduzindo o tempo de desenvolvimento
e, principalmente, simplificando o processo de manutenção das aplicações.
A grande dificuldade para compreender a programação orientada a objetos é
a diferença de abordagem do problema, pois, enquanto a programação estruturada
tem como principal foco as ações através de procedimentos e funções, a
programação orientada a objetos se preocupa com os objetos, suas propriedades e
seus relacionamentos.
2.3 Elementos da programação orientada a objetos
A utilização da programação orientada a objetos permite um aumento de
produtividade pelo reuso de elementos de software. Ela também contempla as
características psicológicas do programador, pois nos modelos de aprendizagem, a
organização da informação de entrada conduz à aprendizagem mais rápida e à
melhor memorização (SOUSA, 1999). A recuperação da informação armazenada se
torna muito mais fácil se esta é organizada desde o início. Os itens na memória de
longo prazo são tanto mais prontamente recuperados quanto mais se acham
estruturados ou categorizados. O estratagema dos métodos propostos para o
desenvolvimento de sistemas de memória consiste em aprender a organizar as
informações que devemos aprender, de modo que elas possam ser novamente
encontradas em nossa memória, quando necessário. Este ponto vai ao encontro da
estruturação da programação orientada a objetos. Os objetos (entidades) são
organizados ou categorizados em classes, formando uma estrutura hierarquizada,
possibilitando uma boa organização da informação, que é captada pelas pessoas
envolvidas no desenvolvimento dos programas.
Por fim, pode-se dizer que o desenvolvimento de um programa orientado a
objetos, é baseado em quatro princípios básicos: abstração, encapsulamento,
herança e polimorfismo, que serão discutidos a seguir.
33
2.3.1 Abstração
A abstração é o processo de extrair as características essenciais de um
objeto real e é necessária para se ter um modelo o mais exato possível da realidade
sobre o qual se possa operar. O conjunto de características resultante da abstração
forma um tipo de dados abstrato com informações sobre seu estado e
comportamento. A abstração no entanto pode não produzir os mesmos resultados
para diferentes observadores, pois, dependendo do contexto onde é utilizada, a
abstração de um objeto descrito por uma pessoa pode ser diferente na visão de
outra. No desenvolvimento de software a abstração é o mecanismo básico utilizado
para realização da análise do domínio da aplicação, e cada passo do processo de
engenharia de software é um aprimoramento do nível de abstração da solução
(PRESSMAN, 1995).
2.3.2 Encapsulamento
Através do encapsulamento, os atributos de um objeto só podem ser
alterados por métodos definidos na própria classe, garantindo desta forma a
integridade dos atributos do objeto. A única maneira de um objeto alterar os atributos
de um outro objeto é através da ativação de um de seus métodos por uma
mensagem. Este conceito, onde atributos e métodos são visíveis apenas através de
mensagens, é conhecido como encapsulamento. O encapsulamento funciona como
uma proteção para os atributos e métodos, além de tornar explícito qualquer tipo de
comunicação com o objeto (WINBLAND, 1993).
Um exemplo prático de encapsulamento no mundo real pode ser descrito por
um vídeo cassete. Em um vídeo cassete existe um número determinado de funções
que podem ser executadas, como por exemplo, avançar, voltar, gravar, tocar, parar
e ejetar a fita. Dentro do vídeo cassete, porém, há várias outras funções sendo
realizadas como acionar o motor, desligar o motor, acionar o cabeçote de gravação,
liberar o cabeçote, e outras operações mais complexas. Essas funções são
escondidas dentro do mecanismo do vídeo cassete e não temos acesso a elas
diretamente. Quando a tecla play é pressionada, o motor é ligado e o cabeçote de
34
reprodução é acionado, sem que para isso, seja necessário saber como é feito
internamente.
A principal vantagem do encapsulamento é poder esconder a complexidade
do código e proteger os dados, permitindo o acesso a eles apenas através de
métodos evitando que seus dados sejam corrompidos por aplicações externas.
No exemplo do vídeo cassete, quando o método gravar é acionado, o
aparelho grava as informações presentes na fita de vídeo em um formato padrão,
que poderá ser lido por outros aparelhos similares. Se não existisse o método gravar
e fosse possível o acesso direto à fita, cada pessoa poderia estabelecer uma
maneira diferente de gravar informações, fazendo com que o processo de se gravar
uma fita de vídeo se tornasse uma tarefa complicada, pois o usuário teria que
conhecer toda a complexidade do aparelho. Isso acarretaria numa perda de
compatibilidade pois não haveria uma forma de proteger os dados para que fossem
sempre gravados em um formato padrão. Neste caso, o encapsulamento funciona
tanto para proteger os dados, como para simplificar o uso de um objeto.
Outro exemplo pode ser dado pelos aplicativos desenvolvidos para a
plataforma Windows. Em aplicações deste tipo, quando em alguma parte do
programa onde é necessário o uso de um botão OK e um botão Cancela, por
exemplo, não é preciso entender como ele foi escrito, nem saber quais são seus
dados internos. Basta saber como construí-lo, mudar o texto que contém a
informação do botão e depois incluí-lo no programa, sem correr o risco de corromper
a estrutura interna do botão, definida em uma biblioteca padrão.
2.3.3 Herança
O conceito de herança permite definir uma nova classe, com base em uma já
existente. Uma classe-filha pode ser criada herdando os membros de dados e os
métodos de uma classe-pai. Assim, novas classes podem ser criadas pela
especialização da classe-pai, formando uma hierarquia de classes (WINBLAND,
1993).
De uma maneira bastante simples, pode-se dizer que herança é o
aproveitamento e extensão das características de uma classe existente. Na natureza
35
existem muitos exemplos de herança. No reino animal, os mamíferos herdam a
característica de terem uma espinha dorsal por serem uma subclasse dos
vertebrados, podendo ainda, acrescentar a essa característica, outras, como ter
sangue quente e amamentar. Os roedores herdam todas as características dos
vertebrados e mamíferos e acrescentam outras, e assim por diante. Em
programação, a herança ocorre quando um objeto aproveita a implementação
(estruturas de dados e métodos) de um outro tipo de objeto para desenvolver uma
especialização dele. A herança permite a formação de uma hierarquia de classes,
onde cada nível é uma especialização do nível anterior, que é mais genérico.
Em programação orientada a objetos, é comum desenvolver estruturas
genéricas para depois ir acrescentando detalhes. Isto simplifica o código e permite
uma organização maior de um projeto, fazendo com que seja possível a reutilização
de código. Se for preciso implementar um botão azul e já temos uma classe que
define as características de um botão cinza, podemos fazer com que nossa
implementação seja uma subclasse do botão cinza, e depois estender suas
características. Desta forma, nos concentramos apenas no necessário, que neste
caso é a cor do botão, e evitamos ter que definir novamente todas as características
para um novo botão.
2.3.4 Polimorfismo
O termo polimorfismo significa ter muitas formas. Em programação, esta
definição pode ser traduzida pelo envio de uma mesma mensagem para um conjunto
de objetos, onde cada objeto responde de forma diferente em função da mensagem
recebida (WINBLAND, 1993). Polimorfismo também pode ser definido como sendo a
propriedade de se utilizar um mesmo nome para fazer coisas diferentes. Por
exemplo, mandar alguém correr pode produzir resultados diferentes dependendo da
situação. Se a pessoa estiver parada e em pé, irá obedecer à ordem simplesmente
correndo. Se a pessoa estiver no volante de um carro, o resultado será o aumento
da pressão do pé no acelerador.
Em programação, uma mesma mensagem poderá provocar um resultado
diferente, dependendo dos argumentos que foram passados e do objeto que o
36
receberá. Por exemplo, o envio de uma instrução que desenhe para uma subclasse
de polígono, poderá desenhar um triângulo, um retângulo ou um pentágono,
dependendo da classe que receber a instrução. Isto é muito útil para escrever
programas versáteis, que possam lidar com vários tipos diferentes de objetos.
2.4 O conceito de objeto
Objetos são a base da tecnologia orientada a objetos e consistem de modelos
ou abstrações de objetos reais e preservam as características essenciais de um
objeto real, ou seja, o seu estado e o seu comportamento. Desta forma qualquer
objeto real pode ser abstraído para filtrar seu estado e comportamento. A Tabela 1
mostra alguns exemplos.
Tabela 2.1. Exemplos de objetos reais
OBJETO ESTADO COMPORTAMENTO
Carro velocidade, marcha, cor, modelo, marca
troca marchas, acelera, dá partida, freia
Gato nome, raça, com fome, com preguiça mia, dorme, se estica, brinca, caça
Caneta cor, nível de tinta escreve, coloca mais tinta
Toca-fitas ligado, girando, tocando, gravando liga, grava, toca, avança, volta, para
Em programação orientada a objetos, o estado de um objeto é representado
por membros de dados, e seu comportamento implementado com métodos, sendo
que, a única forma de mudar o estado dos dados é através de métodos. Por
exemplo, para mudar o estado do vídeo cassete para ligado, é necessário utilizar o
método liga. A figura 2.1 representa um objeto de software.
37
Figura 2.1. Representação de um objeto de software
Tudo o que o objeto de software sabe (seu estado) e tudo o que ele pode
fazer (o seu comportamento) é determinado pelos seus membros de dados e pelos
seus métodos.
Para entender melhor o conceito de objeto, no exemplo a seguir, um avião
Cessna 185 será representado através de um objeto de software, neste caso, um
objeto chamado ppHCC, como mostra a figura 2.2. Neste objeto, as variáveis que
indicam o estado atual do avião são dadas pela velocidade, direção e altitude,
podendo ter seus valores alterados através dos métodos públicos partida(),
acelera(), leme(direção), aileron(direção), elevador(posição) e altímetro(). A
mudança no estado das variáveis do avião pode, por exemplo, fazer o avião ganhar
altitude, velocidade ou mudar de direção.
38
Figura 2.2. O objeto ppHCC
Sabe-se que um objeto sozinho não tem muita utilidade, portanto, um avião
sem piloto, não teria muita utilidade. Para que um objeto seja funcional, é necessário
que ele interaja com outros objetos, para que desta maneira se possa construir
modelos de comportamento mais complexo.
Um objeto solicita a outro que execute uma determinada atividade através de
uma mensagem, podendo também transmitir informações de um objeto para outro
(JONES, 2001). No exemplo do avião Cessna, é necessário que haja um outro
objeto, neste caso um objeto piloto, que envie mensagens para o avião para que ele
possa sair do chão. No exemplo ilustrado na figura 2.3, um avião voa a uma
velocidade de 280 Km/h e 2,5 Km de altura, onde um objeto Dumont (um piloto)
invoca o método elevador() sobre o objeto ppHCC com o parâmetro (+2). O método
é então executado, fazendo o elevador se mover duas posições para cima,
aumentando a altitude e reduzindo a velocidade (mudando o estado dos dados do
objeto ppHCC).
39
Figura 2.3. Troca de mensagens entre objetos
2.4.1 Métodos internos e variáveis públicas
A única maneira de se mudar qualquer estado do avião é através dos seus
métodos públicos, já que os campos de dados são privativos e o seu acesso direto
não é permitido ao mundo externo. Se os campos de dados fossem públicos, seria
possível alterá-los diretamente, podendo comprometer o funcionamento normal do
objeto. A figura 2.4 ilustra uma nova versão do avião Cessna ppHCC, onde é
possível alterar diretamente os valores dos membros de dados, sem usar os
métodos. Pela notação utilizada, é possível notar que agora os membros de dados
direção, altitude e velocidade podem ser acessados publicamente.
40
Figura 2.4. Membros de dados acessados publicamente
Isto pode ser comparado a mover o leme, aileron e elevador com a mão, em
vez de usar o manche e o pedal, tornando o avião mais difícil de usar e
comprometendo a sua segurança. A maioria das linguagens de programação
permite o uso de campos de dados públicos. Geralmente, as desvantagens desse
procedimento são maiores que as vantagens; portanto, é aconselhável que se use o
encapsulamento dos dados pelos métodos sempre que possível.
Um objeto complexo pode ter centenas de métodos, mas geralmente, alguns
poucos métodos interessam a quem vai usá-lo. Por exemplo, quem dirige um carro
não precisa saber de coisas como injetar combustível e provocar faísca. E precisa
somente ligar a ignição. Neste caso, ligar a ignição é um método público, que
interessa ao motorista; já injetar combustível e provocar faísca são métodos internos
(ou privados) ao funcionamento do automóvel, que podem ser chamados por outros
métodos sem que o motorista precise se preocupar.
2.5 O conceito de classes
Uma classe é uma descrição de um conjunto de objetos quase idênticos, e
consiste em métodos e dados que resumem as características comuns de um
conjunto de objetos (WINBLAND, 1993). As características de estado e
41
comportamento do objeto ppHCC ilustrado nos exemplos anteriores, são
características de qualquer avião Cessna 185 que sai da fábrica. Estas
características são definidas nas classes que podem ser vistas como a planta usada
para construir o avião.
A figura 2.5 representa uma classe, e pode-se observar a semelhança com o
diagrama utilizado para representar os objetos. O retângulo levemente apagado
sugere que a classe é apenas um molde onde se encaixam os objetos. Apesar desta
semelhança, a classe não é um objeto, pois não possui um estado, nem um
comportamento específico, portanto, uma classe não é algo que existe e que possa
ter estados. De uma maneira geral, uma classe pode ser definida como um gabarito
com o qual objetos podem ser construídos.
Figura 2.5. Representação de uma classe
Por exemplo, com as plantas que contêm o projeto de um avião Cessna,
pode-se construir vários aviões Cessna. O projeto inclui as especificações do motor,
da fuselagem, da asa, desenhos em perspectiva, moldes, entre outros, e descreve
em detalhes tudo o que o Cessna pode fazer, ou seja, como vai funcionar o
elevador, o aileron, o leme, o trem de pouso e outros. Portanto, contêm tudo o que
um Cessna tem, mas não é um Cessna.
42
Para produzir os aviões ppHCC, ppHCD, ppHCE, todos Cessna 185, define-
se uma classe chamada Cessna, como ilustra a figura 2.6. Esta classe possui a s
declarações dos membros de dados que serão utilizadas para armazenar a direção,
velocidade e altitude, além da implementação dos métodos que cada Cessna deve
ter.
Figura 2.6. Representação da classe Cessna
Os objetos são instâncias das classes, pois, quando um objeto é criado, disse
que o mesmo é instanciado a uma classe. Cada instância é uma entidade individual
e pode receber valores para os seus membros de dados e modificá-los através de
métodos. Com uma fábrica pode-se fabricar 100 aviões, que podem então ter
velocidades, altitudes e direções diferentes.
Tendo criado a classe Cessna, pode-se criar vários aviões com as mesmas
características básicas. Por exemplo, se ppHCC, ppHCD e ppHCE são instâncias da
classe Cessna, todos têm uma velocidade, uma altitude e uma direção distintas e
podem dar partida, acelerar, subir e descer. Neste caso ppHCC pode estar a 350
Km/h voando para o Norte, enquanto ppHCD pode estar estacionado no hangar.
A grande vantagem de se usar classes é a reutilização do código. Toda a
estrutura de um objeto é definida na classe, que depois é utilizada quantas vezes for
43
necessário para criar vários objetos. Com orientação a objetos, o programador se
preocupa com o objetivo principal do programa que quer desenvolver, sem precisar
ficar preocupado com detalhes como programação de botões, molduras, interfaces
gráficas, hélices e outros. Simplesmente o programador usa objetos existentes, para
montar a infraestrutura do seu projeto e se concentra na implementação específica
do problema que pretende resolver.
2.6 Hereditariedade
A hereditariedade constitui-se de uma vantagem exclusiva das linguagens
orientadas a objetos, a de estender e refinar tipos de dados e funcionalidade sem
duplicações e sem ter de acessar o código fonte original (WINBLAND, 1993).
As características altitude, velocidade e direção, podem estar definidas em
uma classe de aviões chamada de Aeronave, que declara métodos partida(),
acelera(), leme(direção), aileron(direção), elevador(posição) e altímetro(). Esses
métodos e dados podem ser herdados pelas classes Jato e Hélice que são tipos de
aeronaves. A classe Cessna pode ser uma subclasse de Hélice e a classe Jato pode
ter outras subclasses como F14 e Boing737, por exemplo.
Em cada classe, métodos novos podem ser criados, outros podem ser
redefinidos e novos campos de dados podem ser declarados. Cada subclasse
adiciona alguma nova funcionalidade à classe-pai e continua sendo um objeto do
tipo definido pela raiz das classes. Ou seja, tanto Cessna, Piper, Airbus300, e
Boeing737 são aviões, mas Cessna e Piper são movidos à hélice, e Airbus300 e
Boing737 são movidos a jato. A figura 2.7 ilustra uma hierarquia de classes formada
pelas subclasses da classe Aeronave. É importante observar esta relação de ser no
projeto de sistemas orientados a objetos.
44
Figura 2.7. Hierarquia de classes da classe Aeronave
Todos os métodos e dados públicos de uma classe-pai são herdados pelas
suas classes-filha. Desta forma, se a classe Aeronave já define o método partida(),
não há necessidade da classe Cessna redefini-lo, ou seja, os objetos criados com a
classe Cessna já nascem com um método partida().
Na maioria das vezes, a forma como uma classe genérica define uma ação
não serve para classes mais específicas. Provavelmente a partida na classe
Aeronave seja manual. Neste caso, a subclasse pode sobrepor ou sobrecarregar os
métodos herdados. O Cessna, por exemplo, pode ou não definir um novo método
partida(tipo) que suporte tanto partida manual como elétrica, mantendo o método
partida() para acionamento manual (sobrecarga), ou simplesmente reescrever
partida() para que faça somente partida elétrica (sobreposição). A classe Cessna,
por exemplo, pode acrescentar novos métodos como baixarTremdePouso() e
horizonteArtificial() (para ler as indicações do horizonte artificial). A classe Jato deve
sobrepor os métodos partida(), elevador(), leme() com uma implementação
específica para um jato genérico, e talvez acrescentar flaps(), pilotoAutomatico(). A
classe Jato também pode acrescentar novas variáveis, como capacidadeDeCarga e
númeroDePassageiros, para caracterizar melhor os objetos que serão produzidos.
45
2.7 Considerações finais sobre programação orientada a objetos
A grande vantagem do paradigma da programação orientada a objetos é o
seu caráter unificador: trata todas as etapas do desenvolvimento de sistemas e
ambientes sob uma única abordagem. Nesse sentido, a metodologia orientada a
objetos cria uma representação do domínio de problema do mundo real e a leva a
um domínio de solução que é o software (PRESSMAN, 1995).
A programação orientada a objetos tornou-se atrativa devido a vantagens
como, reuso de código, facilidade para ampliação e manutenção de aplicações,
sendo que, o reuso de código é, sem dúvida, reconhecido como a maior vantagem
da utilização da programação orientada a objetos, pois permite que programas
sejam escritos mais rapidamente. Nos dias atuais, devido à grande competitividade
do mercado, as empresas estão sempre buscando melhorar seus serviços. O grau
de informatização de todo o processo empresarial é fundamental para que as
empresas se tornem competitivas. Portanto, a possibilidade de que seus sistemas
possam ser desenvolvidos e alterados mais rapidamente torna-se uma grande
vantagem competitiva. Neste sentido, a programação orientada a objetos, através do
reuso de código, traz uma grande contribuição nesta área, possibilitando o
desenvolvimento de novos sistemas utilizando código já existente.
Além do aumento de produtividade oferecido pela reuso de código, o uso da
tecnologia orientada a objetos pode resultar em maior eficiência porque reduz o risco
de erro humano. As estruturas dos programas permanecem intactas e a alteração
propaga-se naturalmente pela hierarquia de classes (WINBLAND, 1993).
A possibilidade de expansão da aplicação, também chamada de
extensibilidade, pode ser vista como a capacidade de uma aplicação crescer
facilmente sem aumentar demasiadamente a sua complexidade ou comprometer o
seu desempenho. Pode-se dizer também que, a extensibilidade e a herança
freqüentemente caminham lado a lado (JONES, 2001). A programação orientada a
objetos é adequada ao desenvolvimento de grandes sistemas uma vez que é
possível construir e ampliar um sistema agrupando objetos, fazendo-os trocar
mensagens entre si. O encapsulamento proporciona ocultamento e proteção da
informação, sendo que o acesso a objetos somente pode ser realizado através das
46
mensagens que ele está habilitado a receber. Nenhum objeto pode manipular
diretamente o estado interno de outro objeto. Se houver necessidade de alterar as
propriedades de um objeto ou a implementação de algum método, os outros objetos
não sofrerão nenhum impacto, desde que a interface permaneça idêntica. Isto
diminui em grande parte os esforços despendidos em manutenção. Além disso, para
utilizar um objeto, o programador não necessita conhecer a fundo a sua
implementação.
O polimorfismo torna o programa mais enxuto, claro e fácil de compreender,
pois, sem polimorfismo, o programa teria grandes listas de métodos com nomes
diferentes, mas comportamento similar. Na programação, a escolha de um entre os
vários métodos seria realizada por estruturas de múltipla escolha muito grandes. Em
termos de manutenção, isto significa que o programa será mais facilmente entendido
e alterado.
A herança também torna a manutenção mais fácil, pois, se uma aplicação
precisa de alguma funcionalidade adicional, não é necessário alterar o código atual,
simplesmente cria-se uma nova geração de uma classe, herdando o comportamento
antigo, e adicionando-se um novo comportamento ou redefinindo-se o
comportamento antigo.
2.8 Linguagem de Programação Visual
Embora existam muitas definições para o termo linguagem de programação
visual ou linguagem de programação gráfica, uma notação comum para todos que
usam programação visual é que um programa pode ser chamado de visual se este
usa representações gráficas de forma significativa no processo de programação
(SHU, 1988).
Nos últimos anos, muitos trabalhos sobre programação visual foram
desenvolvidos, resultando em uma grande quantidade de programas visuais e
sistemas de visualização. Algumas destas linguagens visuais são desenvolvidas
especificamente para facilitar o processo de programação em uma determinada
aplicação, deixando o objetivo principal para o domínio da aplicação. A filosofia por
trás das linguagens de programação visual é que programas criados com imagens e
47
gráficos são mais fáceis de entender do que os criados através de linguagens
textuais, mas também existem evidências empíricas contra o uso de linguagens
visuais em certas aplicações (RAJEEV, 1993) (WHITLEY, 1996). Existem também
muitos fatores que devem ser levados em consideração quando se estuda a
viabilidade do uso de uma linguagem de programação visual ou um sistema de
visualização, como por exemplo a natureza do domínio da aplicação, a habilidade
dos programadores e usuários e o ambiente em que a linguagem será
implementada.
As linguagens de programação visuais representam de forma direta a
estrutura dos dados e algoritmos, aumentando a habilidade do programador na
construção e compreensão dos programas. Nas linguagens textuais tradicionais, a
estrutura dos dados e algoritmos é codificada em forma de texto. Já nas linguagens
visuais, o ambiente visual remove a camada de detalhes de implementação presente
na programação textual, como por exemplo detalhes de sintaxe e a necessidade de
decorar identificadores e associá-los aos seus respectivos tipos, e permite que o
programador manipule diretamente a estrutura do programa.
2.9 Alguns aspectos sobre o uso de linguagens visuais
É notório que o ser humano apresenta uma maior habilidade para interpretar
um algoritmo através de uma linguagem gráfica do que em uma linguagem textual. A
representação gráfica de um algoritmo permite observar a aplicação como um todo,
tornando mais fácil a compreensão do mesmo. A figura 2.8 mostra um exemplo onde
é possível observar que a representação gráfica permite visualizar, de forma direta,
os caminhos de ligação (M) entre as instâncias (I), enquanto que na representação
textual esses caminhos são vistos de forma indireta.
48
Figura 2.8. A representação gráfica e a representação textual
Pesquisas realizadas na área da neurocognição mostraram que o hemisfério
esquerdo do cérebro processa as informações de maneira seqüencial, verbal e
lógica, enquanto que no hemisfério direito, as informações são processadas de
maneira simultânea, visual e espacial (SPRINGER, 1985).
É possível, então, afirmar que as técnicas textuais ou verbais de descrição de
algoritmos, como os pseudocódigos e as linguagens de programação textuais
tradicionais, ativam de forma mais intensa os recursos neurocognitivos do hemisfério
esquerdo do cérebro, pois este tipo de construção de algoritmos possui mais
estímulos seqüenciais, verbais e lógicos. Isto ocorre porque nas técnicas de
programação textual, a quantidade de informação espacial não é significativa,
fazendo com que o hemisfério direito do cérebro não contribua significativamente no
processo de compreensão, já que desta forma, somente parte do cérebro está
49
sendo acionada. Seguindo a mesma linha de raciocínio, nas técnicas de
programação visual, os mecanismos gráficos utilizados para a compreensão de
algoritmos, como por exemplo, os fluxogramas estruturados, tendem a estimular os
dois hemisférios cerebrais, pois, apesar de trabalhar com pouca informação verbal, a
programação visual possui informações seqüenciais e lógicas e obviamente, muita
informação visual (SPRINGER, 1985).
A figura 2.9 mostra que para uma informação de entrada na forma textual, a
área ativa (área escura) no cérebro é o hemisfério esquerdo, já para uma informação
de entrada visual, como ilustra o exemplo, o cérebro passa a ativar os dois
hemisférios.
Figura 2.9. O processamento de informações no cérebro
Estudos realizados por Ben A. Calonni e Donald J. Bagert (CALONNI, 1994) a
respeito do uso de linguagens gráficas para desenvolvimento de algoritmos
mostraram que o uso de linguagens visuais torna a tarefa de programar mais
intuitiva, facilitando desta forma a aprendizagem e compreensão de algoritmos.
50
Com base nessas premissas, estes estudos proporcionaram o
desenvolvimento de uma linguagem de programação procedimental baseada em
ícones, denominada de BACCII (Ben A. Calloni Coding Iconic Interface). Foram
então, realizados testes comparativos entre o BACCII e o PASCAL. Nos testes
propostos, alunos das primeiras fases de graduação do curso de Engenharia Elétrica
foram divididos em turmas distintas: um grupo usou o BACCII e o outro grupo usou o
PASCAL, para desenvolvimento de algoritmos. Após uma análise estatística do
desempenho dos alunos, concluiu-se que a linguagem visual foi mais eficaz que a
linguagem textual, melhorando a aprendizagem e compreensão de algoritmos.
Este resultado vai ao encontro ao trabalho realizado por David A. Scanlan na
área de compreensão de algoritmos, que após testes realizados concluiu que os
métodos gráficos possibilitam a abstração necessária dos detalhes de sintaxe,
apresentando-se melhores que os métodos textuais nos processos cognitivos para o
ensino do desenvolvimento de algoritmos (SCALAN, 1989).
2.10 Linguagens visuais orientadas a objetos A programação visual orientada a objetos é uma programação que combina
as técnicas das linguagens de programação orientada a objetos e da programação
visual em uma só linguagem. O termo linguagem visual orientada a objetos se aplica
às linguagens de programação orientada a objetos que possuem uma sintaxe visual,
como também se aplica às linguagens orientadas a objetos que possuem uma
sintaxe textual, mas que tenham um ambiente visual de programação (BURNETT,
1995). É importante salientar que um determinado programa que possua uma saída
visual e que tenha sido desenvolvido usando uma linguagem orientada a objetos,
não necessariamente é um programa visual orientado a objetos. É preciso que seja
significativo o uso de representações gráficas no processo de programação para que
uma linguagem possa receber o termo visual. A figura 2.10 descreve uma
classificação geral para as linguagens orientadas a objetos.
51
Figura 2.10. Classificação das linguagens orientadas a objetos
Outro fator importante no uso da programação orientada a objetos em
conjunto com a programação visual é a possibilidade, por exemplo, de visualizar
graficamente as hierarquias de classes, o que não ocorre com a programação
orientada a objetos apresentada na forma textual.
A programação visual oferece à programação orientada a objetos a
capacidade de descrever visualmente os componentes de uma classe, bem como
representar graficamente a hierarquia das classes e o comportamento dos objetos.
De modo similar, a programação orientada a objetos pode oferecer a programação
visual, um maior alcance no domínio de suas aplicações, já que a programação
visual se consolidou somente no uso de pequenas aplicações. Desta forma, ao fazer
o reuso de objetos existentes, as linguagens visuais podem implementar aplicações
maiores e permitir um melhor reuso de código entre aplicações.
52
CAPÍTULO 3 DESENVOLVIMENTO DO AMBIENTE DE PROGRAMAÇÃO K++ Introdução
Este capítulo abordará os aspectos envolvidos no desenvolvimento do
ambiente de programação visual orientado a objetos para robô móvel, chamado de
K++. O ambiente proposto contempla a idéia de se usar os paradigmas da
programação orientada a objetos em conjunto com a programação visual, para um
melhor desenvolvimento de aplicações para robôs móveis com simulação em tempo
real usando
o próprio robô. Além do uso desses paradigmas de programação, o
desenvolvimento do ambiente também levou em consideração os aspectos
psicológicos de aprendizagem envolvidos no processo. É importante também
salientar que as aplicações criadas no ambiente aqui proposto, foram desenvolvidas
baseadas no uso do robô móvel Khepera.
3.1 A linguagem de programação K++
O ambiente de programação K++ foi baseado em um trabalho que propunha o
desenvolvimento de um ambiente de programação visual orientado a objetos para
microcontroladores (SOUSA, 1996a) (SOUSA, 1998). Este ambiente de
programação, conhecido como O++, tem como característica principal, o
desenvolvimento de aplicações para microcontroladores através de uma interface
visual. A aplicação desenvolvida no ambiente de programação O++ é convertida em
um código C e posteriormente compilada, gerando um programa executável para ser
enviado ao microcontrolador a fim de executar a aplicação propriamente dita.
De maneira semelhante, o ambiente de programação K++ também descreve
os algoritmos para implementação de aplicações de forma visual, porém, além de
poder gerar código para o robô, ele também permite a simulação de algoritmos em
tempo real.
53
A linguagem de programação K++ foi projetada baseada nos conceitos da
programação orientada a objetos, utilizando para isso, um subconjunto de
declarações textuais em C++, que é uma linguagem orientada a objetos. Além disso,
o K++ introduz uma simbologia gráfica para representar classes e tem suporte a
programação estruturada baseada na técnica de arranjos gráficos proposta por
Nassi-Shneiderman, conhecidas como cartas NS (NASSI, 1984).
A linguagem de programação K++ foi desenvolvida com o objetivo de
minimizar as limitações existentes nas linguagens de programação atuais para o
Khepera, permitindo que se possa desenvolver aplicações de forma mais didática e
com maior produtividade. Isso é obtido, pois a linguagem K++ não requer um alto
grau de conhecimento de programação, como acontece com o desenvolvimento
utilizando-se linguagem textual (linguagem C++, por exemplo). Além disso, a
linguagem K++ não limita o desenvolvimento de aplicações, uma vez que esse
desenvolvimento não está centrado apenas no uso de funções pré-definidas em
suas bibliotecas, como ocorre em algumas linguagens visuais, tais como o
LABVIEW® e MATLAB®. A principal característica da linguagem K++ é a
diversidade de funções e aplicações que podem ser desenvolvidas para o robô, a
partir de classes que contemplem um variado número de métodos para o controle do
robô.
3.2 Hierarquia e descrição de classes
A linguagem K++ representa a hierarquia de classes através de um diagrama
baseado em ícones. As classes são descritas com um nome, na forma textual, e
uma figura representada por um ícone. Na programação visual é bastante comum o
uso de ícones para a representação de objetos de software, pois a escolha
apropriada de um ícone para expressar os objetos representados pela classe tornam
a programação mais amigável. Além disso, a hierarquia de classes é visualizada no
ambiente na forma top-down, ou seja, a hierarquia é determinada pelos níveis de
cima para baixo. A figura 3.1 ilustra um exemplo de hierarquia de classes, onde é
possível observar as derivações a partir do nível mais alto da hierarquia. Nesta
figura, pode-se observar que a classe I é derivada da classe H, e seguindo o mesmo
54
raciocínio, a classe H é derivada da classe F, que é derivada da E, que por sua vez
é derivada da classe C e, finalmente, a classe C é derivada da classe A. O uso
dessa estrutura visual torna evidente a relação existente entra as classes, pois como
mostra a figura 3.1, pode-se visualizar de forma direta que a classe I é herdeira da
classe C, não sendo necessário recorrer a relações intermediárias.
Figura 3.1. Exemplo de hierarquia de classes
A descrição de composição das classes na linguagem K++ é feita baseada
em uma notação proposta por Wasserman (WASSERMAN, 1990). Esta notação
determina que toda classe pode ser descrita de forma visual, como sendo um
retângulo com o nome da classe, onde esse retângulo delimita a área usada para as
declarações da classe. Aliada a notação já existente, foi acrescida à informação
textual uma representação icônica para auxiliar a descrição da natureza da classe.
A área delimitada pelo retângulo é usada para introduzir os membros de
dados e os métodos que compõem a classe, podendo os atributos serem públicos
ou protegidos. Os elementos definidos como protegidos podem ser acessados
somente pela classe ou por seus herdeiros, enquanto que os elementos definidos
como públicos, podem ser acessados e manipulados fora da classe.
A diferenciação entre membros de dados públicos e privados é feita de forma
visual, ou seja, o elemento que estiver totalmente dentro da área delimitada pelo
retângulo é definido como protegido, ao passo que os elementos que estiverem
55
sobre a fronteira delimitada pelo retângulo são considerados públicos. A figura 3.2
ilustra a descrição de uma classe chamada jogador, onde é possível observar um
método público chamado Chutar e um método protegido chamado Correr, bem
como, dois membros de dados protegidos chamados força e posição.
Figura 3.2. Exemplo de descrição de classe
3.1.1. Tipologia e representação de dados
A linguagem K++ combina a informação textual para nomear um membro de
dado ou um método com uma simbologia gráfica que representa o tipo do membro
de dado ou do valor de retorno do método. Esta informação textual é inserida na
representação gráfica, facilitando a compreensão dos elementos inseridos no
contexto.
A simbologia usada para representar os tipos de dados foi baseada
parcialmente, em uma notação sugerida na literatura (CALLONI, 1994). Algumas
modificações foram efetuadas no sentido de compor de maneira adequada os tipos
de membros de dados usados na linguagem K++. Conforme pode ser observado na
figura 3.3, um dado do tipo inteiro é representado por um retângulo com os cantos
arredondados, o que sugere o arredondamento do valor, e um dado do tipo real é
representado por um retângulo com os cantos pontiagudos, o que sugere a precisão
56
do valor. É possível observar que um retângulo com cantos arredondados pode ser
inserido em um retângulo com cantos pontiagudos, portanto, pode-se dizer que o
retângulo com cantos arredondados está contido no retângulo com cantos
pontiagudos, da mesma forma que o conjunto dos números inteiros está contido no
conjunto dos números reais.
Os membros de dados do tipo lógico são representados por uma
circunferência parcialmente preenchida, sugerindo os valores de falso e verdadeiro.
Para os membros de dados do tipo caractere, os mesmos são representados por um
triângulo, em alusão à letra grega delta.
Seguindo o mesmo raciocínio, a simbologia que representa os tipos de dados
descritos por estruturas homogêneas ou arrays é feita através de uma representação
gráfica, que usa uma coleção de elementos iguais ao do tipo base do membro de
dado.
Os membros de classe, ou seja, os membros comuns a todos os objetos de
uma classe, são representados por um retângulo de linha dupla, com o objetivo de
diferencia-los dos membros de instância.
A notação utilizada para representação de um objeto é feita usando um ícone
de identificação referente à classe a qual o objeto pertence, em conjunto com uma
descrição textual com o nome do objeto.
Figura 3.3. Representação dos membros de dados
57
Outra característica importante presente na linguagem K++ é a forma como a
linguagem distingue as diferenças entre membro de dados local, global e
parâmetros. Esta distinção é feita utilizando uma relação espacial entre o retângulo
delimitador do membro de dado com o retângulo delimitador do módulo onde o
membro de dado foi definido, ou seja, se o membro de dado estiver totalmente
dentro do módulo, ele é definido como local; por outro lado, se o mesmo estiver
totalmente fora do módulo, ele é definido como global. Seguindo a mesma analogia,
caso o membro de dado esteja na fronteira do módulo, o mesmo é considerado
como um parâmetro de entrada. A figura 3.4 ilustra a definição de membro de dado
local, global e parâmetro.
Figura 3.4. Representação dos tipos de dados
3.1.2 Estruturas de controle
As estruturas de controle usadas para programação na linguagem K++ são
baseadas no uso de arranjos gráficos propostos por Nassi-Shneiderman (NASSI,
1994), também chamadas de cartas NS.
58
A utilização das cartas NS, mostradas na figura 3.5, permite a construção de
programas estruturados, substituindo os desvios incondicionais por arranjos
embutidos. Este tipo de diagrama usa estruturas visuais para representar o fluxo de
controle e informações textuais para descrever os dados manipulados, testes de
condições, indicação de número de repetições, entre outras.
Figura 3.5. Estruturas de controle usadas no K++
3.1.3 Operações dedicadas A linguagem K++ possui um grupo de operações dedicadas, destinadas
especificamente ao domínio da aplicação. As operações dedicadas possibilitam
descrever a aplicação através de recursos que representam os métodos
desenvolvidos especificamente para o robô Khepera. Estes recursos são
representados por ícones que definem a natureza da operação. Neste sentido, cada
ícone foi desenvolvido de forma que a identificação da operação seja a mais intuitiva
possível. É possível observar na figura 3.6, por exemplo, que a orientação das setas
representa o sentido de deslocamento do robô.
59
Figura 3.6. Operações dedicadas no K++
As operações dedicadas indicam basicamente a natureza do processamento
que se deseja realizar, sendo necessário, portanto, que sejam indicados os
parâmetros que serão usados na operação. Esses parâmetros geralmente são
constantes, identificadores de membro de dados, entre outros. A figura 3.7 mostra
um exemplo de operação em um módulo desenvolvido com o K++, onde é possível
observar no módulo de partida ou principal, a definição de um membro de dado do
tipo inteiro (speed) e, em seguida, o escopo do módulo principal, onde é definida
uma determinada aplicação.
60
Figura 3.7. Exemplo de operações em um módulo em K++
3.2 O ambiente de programação K++
A figura 3.8 ilustra o ambiente K++, que foi estruturado para trabalhar com
três vistas conjuntas. A primeira, chamada de vista de catálogo (lado esquerdo do
ambiente), é usada para organizar os elementos do programa (classes, referência ao
código e dados). A segunda, chamada de vista de script (lado direito), é usada para
desenho de código, descrição de classes e descrição de dados. E a última, chamada
vista de saída (lado inferior), é usada para exibir mensagens de erro e ou
advertências ao usuário.
61
Figura 3.8. Vista principal do ambiente de programação K++
Para promover a facilidade de uso e reduzir a taxa de erro do usuário, a
interface homem-máquina, no ambiente K++, utiliza dois mecanismos importantes: a
visualização automatizada dos elementos da vista de catálogo e a troca de
informações entre as vistas do ambiente, por meio do recurso drag-and-drop
(arrastar e soltar).
Além disso, a linguagem K++ possui um conjunto de operações dedicadas,
dispostas em uma barra de ferramentas, que permite desenvolver uma ampla
variedade de programas para o robô Khepera. O uso de um grupo de operações
dedicadas facilita a implementação das aplicações, minimizando erros de
programação e acelerando o desenvolvimento de aplicações. Atualmente as
operações contemplam as principais funções para controlar o robô, como andar,
parar, girar, ler sensores de proximidade e luz ambiente, entre outros. Essas funções
são obtidas através da classe Khepera, desenvolvida em C++ especificamente para
o robô Khepera e incorporada ao ambiente K++ para poder realizar simulação em
tempo real.
62
É importante salientar que, apesar da linguagem K++ permitir o uso de
conceitos orientados a objetos, é possível a implementação de aplicações usando
estruturas puramente procedimentais. A linguagem K++ não exige que as aplicações
sejam implementadas usando a programação orientada a objetos, ou seja, pode-se
implementar aplicações usando a programação procedimental. Isto se deve ao fato
de muitos programadores desse tipo de aplicação não terem conhecimento sobre a
programação orientada a objetos. Assim, o universo de programadores que podem
utilizar a linguagem K++ é ampliado, mesmo que não sejam exploradas todas as
suas potencialidades.
3.3 Implementação do ambiente K++ O ambiente de programação K++, como citado anteriormente, foi
desenvolvido a partir de um ambiente de programação (O++) para
microcontroladores (SOUSA, 1999). Neste sentido, o desenvolvimento do ambiente
K++ foi focado principalmente nas alterações intrínsecas ao domínio da aplicação,
aproveitando então, a estrutura básica já existente. Apesar de manter a estrutura
básica, algumas funcionalidades do ambiente foram modificadas, tendo em vista a
grande diferença existe entre os domínios das aplicações propostas.
O programa escolhido para desenvolver o K++ foi baseado no próprio
desenvolvimento do ambiente original, ou seja, seguindo as mesmas premissas.
Optou-se por continuar o trabalho usando o Visual C++, que é um ambiente de
desenvolvimento que explora as potencialidades da programação visual. O uso da
metodologia orientada a objetos, aliada aos recursos de manipulação de estruturas
gráficas, faz do Visual C++, uma importante ferramenta para o desenvolvimento de
softwares (KRUGLINSKI, 1996) (HOLZNER, 2001).
63
3.4 As classes Serial e Khepera
São as classes Serial e Khepera que possibilitam ao ambiente de programação
K++ interagir com o robô Khepera. Todo processo de comunicação serial com o
robô, bem como todos os métodos que permitem que as operações do ambiente
K++ controlem o Khepera em tempo real são obtidas com o uso destas classes. A
figura 3.9 mostra como o ambiente K++ depende das classes Khepera e Serial para
implementar a comunicação com o robô. No anexo I estão os códigos dessas
classes implementados em C++.
Figura 3.9. Uso das classes Khepera e Serial pelo Ambiente K++
3.4.1 A classe Serial
A classe Serial é responsável pela comunicação entre o robô e o
microcomputador, sendo, portanto, indispensável que seu controle ocorra de forma
confiável. Apesar dos conceitos a respeito da comunicação serial serem bastante
64
conhecidos, desenvolver um programa para trabalhar com comunicação serial não é
sempre uma tarefa fácil (CAMPBELL, 1993). Cada tipo de aplicação impõe seu grau
de complexidade, fazendo com que seja necessário ter um bom conhecimento do
domínio da aplicação em desenvolvimento.
A classe Serial desenvolvida para trabalhar com o robô Khepera usa
características de controle específicas ao robô. Ela incorpora o controle dos dados
enviados e recebidos, baseado no protocolo de comunicação usado pelo robô.
Apesar de internamente bastante complexa, a classe Serial permite, através da
própria definição de classe, ser utilizada de maneira simples dentro do ambiente de
programação. Os métodos descritos pela classe Serial permitem definir qual a porta
de comunicação será utilizada, qual a velocidade de comunicação entre o
microcomputador e o robô, abrir e fechar o canal de comunicação e enviar e receber
pacotes de dados já formatados nos padrões do protocolo de comunicação do robô,
além de controlar os sinais de comunicação como CTS (clear to send), DSR (data
set ready) e timeout (CAMPBELL, 1993).
3.4.2 A classe Khepera
A classe Khepera permite que o ambiente de programação K++, além de
gerar código para o robô Khepera, possa trabalhar em modo autônomo, e possa
simular algoritmos em tempo real. A linguagem K++ incorpora a classe Khepera em
conjunto com a classe Serial para enviar e receber comandos em um formato
baseado em um protocolo de comunicação desenvolvido pela K-Team [K-TEAM]
especificamente para o robô. Esses comandos são encapsulados nos métodos
definidos para a classe Khepera e usados pelo ambiente K++.
Os nomes de alguns métodos como MoveForward, MoveBackward, TurnLeft
e TurnRight foram inspirados na linguagem LOGO (PAPERT, 1985) (HARVEY,
1997) e seguem a característica de uma linguagem de programação voltada para o
ensino, expressando de forma clara e didática, todos os métodos desenvolvidos
para o robô Khepera. A linguagem LOGO é uma linguagem de descrição de
movimentos em um plano, concebida com propósitos educacionais consagrada no
meio acadêmico e totalmente adequada para ser adequada no controle de robôs
65
móveis. Baseado neste raciocínio, o restante dos métodos seguiu as mesmas
características, ou seja, foram definidos com nomes, que, por si só, sugerem a
função do método. A tabela 3.1 mostra os métodos descritos pela classe Khepera.
Tabela 3.1. Métodos utilizados para o controle do robô
Métodos Descrição dos métodos
MoveForward (velocidade) Move robô para frente em linha reta com determinada velocidade (mm/s)
MoveBackward (velocidade) Move robô para traz em linha reta com determinada velocidade (mm/s)
StopMotors( ) Desliga os motores M1 (motor direito) e M2 (motor esquerdo)
ResetCountMotors( ) Zera contadores de posição de ambos os motores
ReadLightSensors (valor [8]) Retorna a leitura dos 8 sensores de luz ambiente
ReadProxSensors (valor [8]) Retorna a leitura dos 8 sensores de proximidade
ReadMotorPosition(valor [2]) Retorna a leitura da posição dos motores M1 e M2
ReadMotorSpeed(valor [2]) Retorna a leitura da velocidade dos motores M1 e M2
TurnLeft(ângulo) Gira o robô à esquerda sobre seu eixo com dado ângulo (graus)
TurnRight(ângulo) Gira o robô à direita sobre seu eixo com dado ângulo (graus)
MoveLine(distância, velocidade) Move robô em linha reta por uma dada distância (mm) e com uma dada velocidade (cm/s)
ObstacleSkipper (ângulo) Faz o robô desviar de um obstáculo girando um determinado ângulo sobre seu eixo
LightFinder(valor) Faz o robô girar no sentido da fonte de luz de maior intensidade, retornando o valor de saturação da luz
SetPWM(motor1,motor2) Seta amplitude do PWM para os motores M1 e M2
SetSpeedProfile(velocidade1, aceleração1, velocidade2, aceleração2)
Seta velocidade e aceleração para o controle de posição dos motores M1 e M2
SetPIDposition(Kp,Ki,Kd) Seta parâmetros do PID para o controle de posição
66
Com estes métodos, é possível implementar uma grande variedade de
movimentos com o robô, como mover para frente e para trás, girar e fazer curvas, e
implementar vários controles sobre o robô, como ler os sensores de proximidade e
luz ambiente, ler a velocidade dos motores, ler a posição dos motores, configurar
parâmetros de controle, entre outros. Esta variedade de métodos permite o
desenvolvimento de um grande número de aplicações, como algoritmos para seguir
trajetórias (BIANCHI, 2001) (LUND, 1996), algoritmos para veículos de Braitenberg
(BRAINTENBERG, 1984) entre outros. É importante salientar que, apesar da classe
Khepera possuir esta variedade de métodos já implementados, nada impede que um
programador acrescente novos métodos a partir dos métodos existentes. Além
disso, a classe Khepera, por ser baseada na orientação a objetos, permite o reuso
de seu código em alguma outra aplicação para o robô Khepera desenvolvida com o
Visual C++.
3.4.2.1 Descrição dos métodos da classe Khepera
Todos os métodos descritos a seguir foram desenvolvidos baseados em um
determinado robô Khepera. Sabendo que os dados de odometria podem mudar de
robô para robô, o ambiente K++ permite configurar alguns parâmetros importantes,
como a resolução do encoder e distância entre os eixos das rodas. Portanto, os
métodos explicados a seguir são baseados nos valores obtidos com um determinado
robô Khepera que foi usado nos experimentos.
• MoveForward (velocidade)
Este método é responsável pelo deslocamento do robô e tem como parâmetro
de entrada um valor de velocidade dado em pulso/10ms. Este valor é atribuído aos
dois motores simultaneamente e pode variar de 1 pulso/10ms, que corresponde a
uma velocidade de 8 mm/s (velocidade mínima), a até 127 pulsos/10ms, que
corresponde a aproximadamente 1 m/s (velocidade máxima). Como o parâmetro de
entrada (velocidade) é dado em pulso/10ms, as velocidades possíveis para o robô
ocorrem numa escala de 8 mm/s em 8 mm/s, ou seja, as velocidades serão
67
escalonadas em saltos de velocidade (8 mm/s, 16 mm/s, 24 mm/s ...1016 mm/s).
Para fins didáticos, optou-se por usar como unidade de velocidade o cm/s, sendo
possível obter valores em uma escala unitária aproximada (1 cm/s, 2 cm/s, 3 cm/s
...100 cm/s), o que não comprometeu a eficiência do robô para as aplicações
estudadas.
• MoveBackward (velocidade)
De modo similar ao método MoveForward, este método faz com que o robô
ande para trás em linha reta com uma determinada velocidade até que algum outro
comando seja enviado ao robô. A inversão no sentido de deslocamento do robô é
obtida simplesmente invertendo-se o sinal do parâmetro de entrada. Os valores
usados para a velocidade são os mesmos usados no método MoveForward.
• StopMotors ( )
O método StopMotors, como o próprio nome sugere, faz os motores pararem
simultaneamente. O método StopMotors funciona de forma similar ao método
MoveForward, ou seja, se for atribuída uma velocidade zero ao parâmetro de
entrada em MoveForward, o efeito sobre o robô será o mesmo obtido pelo uso do
método StopMotors. Apesar de simples, o uso de um método exclusivo para
interromper o deslocamento do robô torna o entendimento do algoritmo mais fácil.
• ResetCountMotors ( )
O método ResetCountMotors(), ao ser acionado, zera o valor dos contadores
dos motores. Apesar de simples, este método é muito utilizado em aplicações em
que se precisa conhecer a posição do robô dentro do ambiente, pois o seu
posicionamento é baseado exclusivamente na contagem dos encoders dos motores.
68
• ReadLightSensors ( int valor [8] )
O método ReadLightSensors é responsável pela leitura dos valores dos 8
sensores de luz ambiente. Ao ser acionado, este método retorna um valor para cada
sensor e os armazena em um vetor de oito posições, sendo que cada sensor varia
de 0 (ambiente com muita luminosidade) a valores próximos a 450 (ambiente
escuro). Este método pode ser usado, por exemplo, em alguma aplicação em que o
robô tenha que seguir uma fonte de luz. A figura 3.10 ilustra um exemplo com uma
vetor de valores para os sensores de luminosidade de acordo com a distância da
fonte de luz.
Figura 3.10. Leitura dos sensores de luz ambiente
• ReadProxSensors ( int valor [8] )
De maneira similar ao método ReadLightSensors, o método
ReadProxSensors é responsável pela leitura dos valores dos sensores de
proximidade e é utilizado para detectar obstáculos na trajetória do robô. Este método
69
retorna o valor de cada sensor e os armazena em uma matriz de 8 posições, sendo
que cada valor varia de 0 (robô longe do obstáculo) a 1023 (robô próximo ao
obstáculo). Vale lembrar que a distância em que o robô se encontra do obstáculo
depende diretamente do tipo de material do objeto. A figura 3.11 mostra um
exemplo com um vetor de valores para os sensores de proximidade de acordo com a
distância do obstáculo.
Figura 3.11. Leitura dos sensores de luz proximidade
• ReadMotorPosition ( int valor [2] )
Este método faz a leitura de cada um dos encoders acoplados às rodas do
robô (um pulso do encoder corresponde a 0,08 mm). Neste caso, por exemplo, para
um deslocamento da roda de 10 cm, o valor lido para o encoder desta roda será
equivalente a 1250 pulsos. O número de pulsos para cada encoder pode variar em
uma escala de 32 bits.
70
• ReadMotorSpeed ( int valor [2] )
Este método faz a leitura da velocidade instantânea para cada um dos
motores do robô (cada pulso/10ms corresponde a 8 mm/s). A velocidade do robô
pode variar de 8 mm/s a aproximadamente 1 m/s.
• TurnLeft (ângulo, velocidade)
O método TurnLeft tem como parâmetros de entrada o valor do ângulo
(graus) e velocidade (mm/s) de giro do robô. Neste método o motor do lado direito
do robô gira no sentido horário e o motor do lado esquerdo no sentido oposto,
fazendo o robô girar sobre seu próprio eixo para o lado esquerdo. Internamente o
método leva em consideração o diâmetro do robô para poder fazer a conversão do
valor do ângulo de giro, dado em graus, para um valor correspondente a leitura do
encoder. A figura 3.12 ilustra o processo de conversão das unidades, que funciona
da seguinte forma: sabendo que a distância entre as rodas do robô é de 53 mm,
pode-se facilmente deduzir que a circunferência completa descrita pelo giro de
somente uma roda é de aproximadamente 166,5 mm. Vale lembrar que os valores
aqui definidos como constantes (distância entre as rodas do robô e resolução do
encoder) foram baseados em um determinado robô Khepera usado no
desenvolvimento deste trabalho. Em uma breve analogia é possível deduzir que, se
166,5 mm equivale a uma volta completa do robô sobre seu eixo (360°), o percurso
percorrido pela roda para avançar 1° é de aproximadamente 0,46 mm. Por fim,
sabendo que um pulso do encoder equivale a 0,08 mm, conclui-se que 1° de giro do
robô equivale a aproximadamente 5,78 pulsos do encoder da roda. Desta forma, o
método executa um loop fazendo a leitura da posição do encoder até que o valor lido
seja igual ao calculado para a distância percorrida no giro.
Para o robô girar um ângulo de 90° (ver figura 3.13), por exemplo, o valor lido
no encoder deverá ser de aproximadamente 520 pulsos, ou seja, o robô irá girar até
que o contador de pulsos do encoder chegue a 520.
71
Figura 3.12. Processo de conversão de unidades para o método TurnLeft
Figura 3.13. Exemplo de giro com o método TurnLeft
72
• TurnRight (ângulo, velocidade)
De maneira similar ao método TurnLeft, o método TurnRight tem como
parâmetro de entrada o ângulo (graus) e a velocidade (mm/s) de giro do robô. Neste
método, o motor do lado direito do robô gira no sentido anti-horário e o motor do lado
esquerdo no sentido horário, fazendo com que o robô gire sobre seu próprio eixo
para o lado direito. Os procedimentos internos do método são os mesmos do método
TurnLeft.
• MoveLine (distância, velocidade)
O método MoveLine facilita a execução de trajetórias em linha reta quando o
objetivo é se deslocar por uma determinada distância. Este método recebe como
parâmetros de entrada os valores da distância (mm) a ser percorrida e a velocidade
(cm/s) de deslocamento do robô. O método MoveLine usa internamente os métodos
MoveForward, ReadMotorPosition e ReadProxSensors, como mostra o código
descrito na figura 3.14.
Apesar de sua implementação não ser muito complexa, este método pode ser
considerado um método avançado e que facilita consideravelmente a
implementação de algoritmos. No próximo capítulo, serão apresentados alguns
exemplos de aplicações, onde é possível observar a importância do método
MoveLine.
Este método é baseado na resolução do encoder que faz a leitura da posição
dos motores, portanto, os cálculos e conversões demonstrados a seguir foram
baseados no robô Khepera usado neste trabalho. Eventuais diferenças na resolução
do encoder devem ser consideradas e podem ser alteradas nos parâmetros de
configuração do Kephera disponível no ambiente de programação K++.
73
Figura 3.14. Código do método MoveLine
Inicialmente o valor da distância é transformado em posições do encoder e
então o robô começa a se deslocar através da chamada do método MoveForward,
executado internamente ao método MoveLine, com uma determinada velocidade.
Durante o percurso o método ReadMotorPosition é executado dentro de um loop, de
forma a ler a posição dos encoders até que o valor lido seja igual ao valor calculado
para a distância. Sabendo que a cada pulso de encoder, o robô se move 0,08 mm, é
fácil concluir que 1 mm de deslocamento do robô equivale a 12,5 pulsos do encoder.
O método MoveLine também implementa a detecção de obstáculos. Para
isso, faz-se uso do método ReadProxSensor dentro do mesmo loop, verificando
instantaneamente a leitura dos sensores de proximidade. É possível observar no
algoritmo que é feita somente a leitura dos quatro sensores frontais, pois, como o
movimento do robô é para frente em linha reta, não há necessidade de leitura dos
sensores laterais e traseiros.
Também pode ser observado, que é determinado um valor constante para o
valor de saturação dos sensores chamado m_prox, que pode chegar até 1023. O
valor padrão de m_prox é 800, podendo ser mudado de acordo com o tipo de
material do obstáculo. Neste caso, quanto maior o valor de m_prox, mais próximo do
74
obstáculo o robô parará. Em algumas aplicações não é interessante que o robô se
aproxime demais do obstáculo, portanto, quanto o valor para m_prox ser diminuído.
O valor de m_prox pode ser ajustado nos parâmetros de configuração do robô
existente no ambiente de programação K++.
Sendo assim, caso exista um obstáculo na trajetória do robô, o robô ficará
parado em frente ao obstáculo até que o obstáculo seja removido, seguindo então
até a posição final dada pelo parâmetro de distância. Um exemplo de uso para esta
importante funcionalidade seria o robô trabalhando como um AGV em um chão de
fábrica. Caso algum objeto ou pessoa, por exemplo, se encontre na trajetória do
robô, o mesmo parará, evitando uma colisão. Assim que o objeto em questão sair do
percurso do robô, o mesmo seguirá em frente até o seu destino. Esta situação é
mostrada na figura 3.15.
Figura 3.15. Tratamento de obstáculos usado no método MoveLine
75
• ObstacleSkipper (ângulo)
O método ObstacleSkkiper tem uma grande importância no desenvolvimento
de aplicações que envolvem principalmente a exploração de ambientes. A sua
finalidade básica é encontrar e desviar de obstáculos. Neste caso, quando o robô
encontra um obstáculo, ele pára e gira em torno de seu eixo por um dado ângulo,
evitando desta forma o obstáculo. Internamente, como pode ser visto na Figura 3.16,
o método ObstacleSkkiper executa continuamente uma rotina de verificação dos
sensores de proximidade, até que um obstáculo seja encontrado. Esta condição
acontece quando o valor de um dos sensores é maior que o valor da constante de
saturação m_prox, já comentada no método MoveLine.
Ao encontrar o obstáculo, o robô primeiramente parará, e em seguida é feito
um cálculo da soma dos sensores de cada lado do robô. É baseado nestes valores
que o algoritmo define para que lado o robô deve girar. Nesse caso, se a soma dos
valores dos sensores do lado esquerdo (sensores 0, 1, 2 e 7) for maior que a soma
dos valores dos sensores do lado direito (sensores 3, 4, 5, 6), o robô girará para a
direita, caso contrário o robô girará para a esquerda. Vale lembrar que quanto maior
o valor retornado pelo sensor de proximidade, mais próximo do obstáculo o robô se
encontra, portanto, se a soma dos sensores do lado esquerdo é maior que a soma
dos sensores do lado direito, fica claro que a maior parte do obstáculo está do lado
esquerdo, portanto o robô deve girar para a direita a fim de se desviar do obstáculo.
76
Figura 3.16. Código interno ao método ObstacleSkipper para desvio de obstáculo
• LightFind (valor)
Este é outro importante método existente na classe Khepera. Sua finalidade
básica é a de encontrar a direção da maior fonte de luz. Apesar de internamente
este método ser bastante complexo, o seu uso no ambiente de programação é
bastante simples, sendo executado através da operação LightFinder. O
funcionamento do método pode ser observado na figura 3.17, onde é ilustrada uma
situação onde o sensor 4 recebe a maior incidência de luz. Basicamente, o método
faz a leitura dos sensores de luminosidade e analisa qual sensor tem o menor valor
(condição de maior intensidade de luz), fazendo o robô girar no sentido do sensor
que retornou o menor valor. O giro do robô é baseado na posição do sensor
localizado no robô, ou seja, cada sensor possui uma localização com referência no
centro do robô. Por exemplo, o sensor 0 localizado na lateral esquerda do robô, está
a 90° do centro do centro do robô. Baseado nestas considerações, a tabela 3.2
77
mostra os valores de giro do robô de acordo com o sensor que recebe a maior
intensidade de luz.
Tabela 3.2. Ângulo de giro do robô de acordo com a luminosidade nos sensores
Sensor Ângulo de giro Sensor 0 90° à esquerda Sensor 1 45° à esquerda Sensor 2 10° à esquerda Sensor 3 10° à direita Sensor 4 45° à direita Sensor 5 90° à direita Sensor 6 170° à direita Sensor 7 170° à esquerda
Observando novamente a figura 3.17 fica claro que, como o sensor 4 está
recebendo a maior quantidade de luz, o sentido do giro será para direita e o valor do
ângulo de giro será de 45°; portanto, o robô girará 45° à direita.
Figura 3.17. Funcionamento do método LightFind
78
• SetPWM(M1,M2)
Este método permite que os valores do PWM dos motores sejam alterados de
acordo com a necessidade da aplicação. A menor razão PWM é igual a zero (0%),
sendo que 255 corresponde à razão de avanço máximo e –255 corresponde a razão
de avanço mínimo.
• SetSpeedProfile(V1,A1,V2,A2)
O método SetSpeedProfile permite que a curva característica para a
velocidade e aceleração do controle de posição seja alterada. O parâmetro
max_speed, conforme ilustrado na figura 3.18, indica a máxima velocidade
alcançada durante o deslocamento. A unidade de velocidade para este caso é o
pulso/10 ms que corresponde a 8 mm/s. A unidade de aceleração é o
[(pulse/256)/10ms)/10 ms], que corresponde a 3,125 mm/s2. O valor padrão para a
velocidade máxima é igual a 20 e a aceleração 64.
Figura 3.18. Curva característica de aceleração e velocidade
79
• SetPIDposition(Kp,Ki,Kd)
Este método configura os parâmetros PID (Kp, Ki, Kd) para o controlador de
posição. Nas aplicações desenvolvidas com o K++ não foram feitas alterações em
nenhum destes parâmetros, o que não impede que algum outro tipo de aplicação
altere estes valores de acordo com sua necessidade. Os valores padrão para os
parâmetros do PID são os seguintes: Kp = 3000, Kd = 4000 e Ki = 20.
80
CAPÍTULO 4 RESULTADOS OBTIDOS Introdução O objetivo deste capítulo é demonstrar a implementação de algoritmos
através do ambiente de programação K++. Serão também abordados alguns
aspectos sobre a utilização do ambiente K++, bem como, todo o processo de criação
de algoritmos usando a programação procedimental e a programação orientada a
objetos neste ambiente. Por fim, serão apresentados alguns aspectos sobre o uso
de programação avançada para o desenvolvimento de novos métodos a partir do
Visual C++.
4.1 Usando o ambiente de programação K++
Como já mencionado, o objetivo do ambiente de programação K++ é facilitar o
desenvolvimento de algoritmos para robôs móveis. Neste sentido, a visualização
automatizada dos elementos da vista de catálogo e a troca de informação entre as
vistas do ambiente, por meio do recurso drag and drop, são elementos fundamentais
na utilização do ambiente K++.
Conforme ilustra a figura 4.1, a vista de catálogo é composta por quatro
árvores, que armazenam informações sobre a hierarquia das classes, funções
definidas pelo usuário, dados relativos ao módulo em foco e as possíveis
mensagens que podem ser ativadas, bem como as funções que podem ser
chamadas.
A árvore que armazena a hierarquia das classes (figura 4.1a) é montada
diretamente pelo usuário, de acordo com a estruturação das classes usadas na
aplicação. As demais árvores são montadas seguindo uma estruturação própria, que
é pertinente ao tipo de informação que ela armazena. Particularmente, as árvores de
dados e mensagens são montadas automaticamente, de acordo com o contexto da
área de desenho (vista de script).
81
Figura 4.1. As árvores de classes, funções, dados e chamada
A árvore que armazena as funções (figura 4.1b) é estruturada em quatro
ramos, sendo o primeiro ramo o módulo de partida (main) do programa. Os demais
ramos correspondem ao tipo de retorno da função (int, real, void).
A árvore que armazena os dados (figura 4.1c) é estruturada de acordo com o
escopo de seus elementos e é dividida em quatro ramos. O primeiro ramo está
relacionado com os dados globais, o segundo corresponde aos dados definidos
localmente, o terceiro corresponde aos dados advindos de parâmetros de entrada e
o último ramo armazena os membros de dados dos objetos acessíveis pelo módulo
em foco.
A árvore de chamada de módulo (figura 4.1d) é organizada em duas seções.
A primeira está relacionada com as mensagens que podem ser ativadas e a
segunda é formada por métodos ou funções que podem ser chamadas. As
mensagens são categorizadas seguindo o escopo dos objetos acessíveis ao módulo
selecionado. Por fim, as funções são organizadas seguindo o valor de retorno.
É importante salientar que a visualização das árvores é ativada
automaticamente, de acordo com o tipo de desenho que o usuário está realizando
na vista de script. Estas árvores também podem ser ativadas pelo usuário, utilizando
diretamente os botões localizados acima da estrutura das árvores.
A montagem das árvores de dados (figura 4.1c) e de módulos (figura 4.1b) é
feita automaticamente, seguindo o contexto da operação selecionada e do módulo
82
ativo. Ao selecionar uma operação, o ambiente K++ busca os elementos de dados
definidos no escopo do módulo ao qual pertence a operação, monta a árvore de
dados e efetua sua visualização. Quando a operação selecionada for de ativação de
mensagens ou de chamada de função, o ambiente K++ monta e visualiza a árvore
de funções e funções membros, visíveis ao módulo ao qual pertence a operação.
Desta forma, o ambiente K++ altera a disposição das informações disponíveis
de acordo com o contexto da operação em uso. A forma automatizada de
disponibilizar ao usuário somente aquilo que está à disposição para o seu uso facilita
e agiliza o desenvolvimento dos algoritmos. Isto também possibilita uma diminuição
nos erros do usuário, pois, a organização das informações segue o escopo do
módulo que está sendo construído.
Outra característica importante do ambiente é que, quando uma alteração é
feita em algum elemento do programa, essa alteração é atualizada automaticamente
em todas as operações que referenciam este elemento dentro do programa,
evitando assim a propagação de erros sintáticos de nomes.
Por fim, o ambiente K++ permite que alterações nos parâmetros de
configuração do robô e nos parâmetros relativos ao ambiente em que o robô se
encontra sejam feitas de maneira simples. No caso dos parâmetros de configuração
do robô, é possível alterar o valor da resolução do encoder e o valor da distância
entre os eixos das rodas do robô. Para os parâmetros de configuração do ambiente,
é possível determinar quais os valores de saturação para os sensores de
proximidade e luz ambiente. Dependendo da aplicação, por exemplo, pode não ser
interessante que o robô se aproxime demasiadamente do obstáculo. Neste caso,
deve-se diminuir o valor de saturação para os sensores de proximidade. Caso a
aplicação, por exemplo, não necessite que o robô se aproxime demais da fonte de
luz, o valor de saturação dos sensores de luz ambiente deve ser aumentado. A
figura 4.2 mostra a caixa de diálogo de configuração de parâmetros disponíveis no
ambiente K++.
83
Figura 4.2. Diálogo de configuração de parâmetros no ambiente K++
4.2 Programação procedimental usando o K++ O uso da programação procedimental no ambiente K++ permite que
aplicações menos complexas sejam desenvolvidas de maneira mais simples, pois é
comum que muitos programadores, que trabalham no desenvolvimento de software
para robôs, não tenham familiaridade com a programação orientada a objetos,
justificando assim, o uso da programação procedimental. A programação orientada a
objetos é indicada para aplicações mais complexas, onde o reuso de código é
fundamental para a implementação de algoritmos. Portanto, é interessante que o
programador se familiarize com o ambiente, primeiramente desenvolvendo
aplicações mais simples através da programação procedimental. A seguir serão
dados alguns exemplos de aplicações desenvolvidas usando apenas a programação
procedimental.
84
4.2.1 Exemplo 1 – Robô AGV
O primeiro exemplo é dado por uma aplicação que tem a finalidade de andar
por um determinado percurso, simulando o deslocamento de um AGV em um chão
de fábrica, conforme ilustra a figura 4.3.
Figura 4.3. Robô Khepera fazendo um percurso definido em um chão de fábrica
Nesta aplicação, o robô deverá partir de um ponto inicial e percorrer um
percurso quadrangular. Para isso será necessário o uso de duas operações para o
desenvolvimento do algoritmo: a operação MoveLine e a operação TurnLeft.
Neste exemplo, as operações são definidas apenas dentro do módulo de
partida (main), e pode ser visualizado na figura 4.4.
85
Figura 4.4. Algoritmo do exemplo 1 desenvolvido no K++
As operações contidas no módulo main são executadas na forma seqüencial
de cima para baixo, a não ser que haja alguma estrutura que permita desvios, como
as estruturas do tipo if, for e while. Neste caso, pode-se observar que o algoritmo é
executado seqüencialmente até a última instrução. Primeiramente é executado o
método MoveLine, com parâmetro de entrada igual a 100 para a distância e 5 para a
velocidade, fazendo o robô andar em linha reta por 100 mm a 5 cm/s, para em
seguida executar o método TurnLeft, com parâmetro de entrada igual a 90, fazendo
o robô girar sobre seu eixo em 90°. Repetindo esta seqüência, como ilustrado na
figura 4.4, e acompanhando o raciocínio inicial, é fácil perceber que o robô
percorrerá uma trajetória quadrada.
Vale lembrar que, como descrito no capítulo 3, a operação MoveLine
implementa internamente a detecção de obstáculos frontais, ou seja, caso algum
objeto ou pessoa se encontre em frente ao robô, o mesmo ficará parado até que o
obstáculo seja removido.
86
4.2.2 Exemplo 2 – Robô AGV O segundo exemplo apresentado na forma de programação procedimental é
semelhante ao primeiro exemplo, mudando somente a forma de escrever o
algoritmo. No exemplo 1, pode-se notar que o algoritmo repete em seu código várias
vezes os mesmos comandos. Baseado nesta observação e fazendo uso de um novo
recurso do ambiente K++, é possível simplificar consideravelmente o algoritmo
escrito no exemplo 1 através do uso de uma estrutura de controle for, como mostra a
figura 4.5.
Figura 4.5. Algoritmo do exemplo 2 usando estrutura de controle
Através do uso desta estrutura de controle do tipo for, ou seja, uma estrutura
de controle que implementa iterações, as operações MoveLine e TurnRight, são
executadas dentro de um loop. Para efetuar a estrutura for é necessária uma
variável de controle, neste caso a variável counter, que é definida no escopo do
módulo main, conforme ilustra a figura 4.6. Portanto, repetindo este loop 4 vezes, é
87
possível observar, seguindo o mesmo raciocínio do exemplo 1, que o robô executará
uma trajetória quadrada.
Figura 4.6. Definição da variável counter no módulo main
4.2.3 Exemplo 3 – Robô Explorador
Este exemplo simula uma aplicação de exploração de ambiente. O objetivo é
fazer com que o robô navegue continuamente dentro de um determinado ambiente,
desviando-se de obstáculos, como ilustra a figura 4.7.
Figura 4.7. Exemplo de exploração de ambiente com obstáculos
88
Diferentemente dos primeiros exemplos, a aplicação de exploração de
ambiente envolve o uso de um número maior de operações para o desenvolvimento
do algoritmo. Neste caso, o algoritmo é formado por estruturas de controle do tipo
seqüência (atribuição), if (decisão) e do/while (repetição condicional) e pelas
operações MoveForward, ReadProxSensors, TurnLeft, TurnRight e StopMotors,
conforme descrição realizada no capítulo 3.
Assim como nos exemplos anteriores, este algoritmo é totalmente
desenvolvido dentro do módulo principal e, além disto, faz uso de algumas variáveis,
também definidas no escopo do módulo main. A figura 4.8 ilustra as variáveis
definidas no módulo main.
Figura 4.8. Definição das variáveis no módulo main
É possível observar, conforme ilustra a figura 4.9, que o algoritmo é
executado continuamente (loop eterno) dentro de uma estrutura de controle do tipo
do/while. A primeira instrução do loop principal é a operação MoveForward, que faz
o robô se movimentar para frente em linha reta com uma velocidade de 5 cm/s. Em
seguida, o algoritmo entra em um loop secundário (estrutura do/while), com o
objetivo de fazer continuamente a leitura dos sensores frontais de proximidade.
Após a leitura dos sensores, é atribuída às variáveis esq e dir, a soma da leitura dos
sensores de proximidade do lado esquerdo e direito, respectivamente. Este loop é
finalizado quando a variável soma, obtida por uma operação que faz a soma das
89
variáveis esq e dir, atinge um valor maior que 1000. Vale lembrar que quanto maior o
valor da variável soma, mais próximo do obstáculo o robô se encontra. Portanto,
quando o robô se aproxima de um obstáculo, a variável soma atinge o valor de
saturação (neste caso, maior que 1000), e o loop secundário é finalizado. Em
seguida, através de uma estrutura de controle do tipo decisão (if), o algoritmo
compara as variáveis esq (soma dos sensores do lado direito do robô) e dir (soma
dos sensores do lado direito do robô) e, caso a soma dos sensores do lado
esquerdo seja maior que a soma dos sensores do lado direito, o robô irá girar 90°
para a direita. Isto significa dizer que os maiores valores de saturação se encontram
no lado esquerdo do robô, ou seja, a maior parte do obstáculo está do lado esquerdo
do robô. Portanto, neste caso, o robô girará 90° para a direita e o algoritmo voltará
para o inicio do loop principal.
Figura 4.9. Algoritmo para exploração de ambiente no K++
90
4.2.4 Exemplo 4 – Robô Explorador
O objetivo desta aplicação, assim como no exemplo 3, é a de explorar um
determinado ambiente, evitando obstáculos. Para este exemplo, foi usada uma
operação, chamada de ObstacleSkipper, desenvolvida especificamente para o
desvio de obstáculos.
Como ilustrado na figura 4.10, é possível observar que, com o uso desta
operação específica, o algoritmo desenvolvido ficou menor do que o algoritmo do
exemplo anterior, ou seja, toda a parte de leitura de sensores, verificação de
saturação dos sensores e comparação dos valores obtidos, foi substituída por uma
única operação. Neste caso, a substituição de um conjunto de operações por uma
única operação equivalente, facilita o desenvolvimento do algoritmo, pois o uso de
um número expressivo de estruturas visuais torna o seu entendimento mais difícil.
Figura 4.10. Algoritmo simplificado para exploração de ambiente no K++
Outra observação importante neste exemplo é que o mesmo não faz uso de
variáveis para o controle do algoritmo, pois a própria operação ObstacleSkipper
implementa internamente as variáveis de controle.
91
4.3 Programação orientada a objetos usando o K++
A principal característica do ambiente de programação K++ é a possibilidade
de descrever visualmente algoritmos usando programação orientada a objetos.
Como se sabe, o uso da programação orientada a objetos é indicada no
desenvolvimento de aplicações que necessitam de reuso de código, ocultação de
informação, dentre outros. Neste sentido, o exemplo dado a seguir, mostra uma
aplicação onde é possível observar a hierarquia de classes e o reuso de código,
além de outros conceitos de programação orientada a objetos.
4.3.1 Exemplo 1 – Robô Explorador
Este exemplo ilustra uma aplicação onde o robô tem como objetivo navegar
por um determinado ambiente, desviando de obstáculos quando necessário. Como a
intenção é desenvolver o algoritmo usando programação orientada a objetos, o
primeiro passo para iniciar o algoritmo é criar uma classe que implemente a
navegação e o desvio de obstáculos.
4.3.1.1 Definindo a classe Explorador
Inicialmente, cria-se uma classe chamada Explorador (em alusão a
exploração de ambientes inóspitos), que como o próprio nome sugere, tem por
objetivo fazer a exploração de um dado ambiente. Para tal, serão implementados
dois métodos nesta classe: o método mover e o método navegar, conforme ilustra a
figura 4.11.
O método mover faz com que o robô se mova no ambiente, verificando a
presença de obstáculos, através da leitura dos sensores de proximidade do robô.
Internamente, o método mover executa a operação MoveForward, que faz o robô se
deslocar para frente em linha reta com uma dada velocidade, neste caso 5 cm/s, e
em seguida executa a operação ObstacleSkipper, que faz o robô parar ao encontrar
um obstáculo, e girar com determinado ângulo, neste caso 45°, fazendo o robô
desviar-se da trajetória de colisão com o obstáculo.
92
O método navegar, implementa a navegação contínua do ambiente através
da chamada do método mover, dentro de um loop eterno.
Figura 4.11. A classe Explorador
4.3.1.2 Definindo o objeto AGV
O próximo passo no desenvolvimento orientado a objeto, agora que já existe
uma classe definida para a exploração de ambientes, é criar um objeto e torna-lo
uma instância desta classe. Como já mencionado no capítulo 2, a classe é somente
um molde para a criação de objetos, ou seja, é preciso criar algum objeto para que
se possa interagir com o mundo real. Neste sentido, é criado então um objeto
chamado AGV (em alusão a capacidade de navegação dos AGV’s) pertencente à
classe Explorador. A figura 4.12a mostra o escopo do módulo main, onde é criado
um objeto chamado AGV, pertencente à classe Explorador. Por fim, a aplicação é
executada a partir de uma operação de chamada de método, onde é enviada uma
mensagem para o objeto AGV começar a navegar (ver figura 4.12b).
93
Figura 4.12. Módulo de partida do exemplo 1
4.3.2 Exemplo 2 – Robô que se move em direção à luz
Este segundo exemplo ilustra uma nova aplicação onde o robô tem como
objetivo, além de navegar por um determinado ambiente, encontrar uma fonte de luz
fixa em algum ponto do ambiente explorado, desviando de obstáculos quando
necessário. Neste caso, será usado o mesmo algoritmo desenvolvido no exemplo
anterior, já que o objetivo aqui é demonstrar o reuso de código.
Como já existe uma classe que implementa a navegação e o desvio de
obstáculos, será necessário somente definir uma nova classe que implemente a
navegação em direção à luz. É importante observar que aqui é utilizado o conceito
de herança, onde uma nova classe é criada a partir de uma classe existente. Vale
lembrar que definindo uma nova classe a partir de uma classe existente, esta nova
classe herdará todos os métodos da classe-pai.
94
4.3.2.1 Definindo a classe Besouro
A classe Besouro (em alusão a uma espécie de besouro que voa na direção
da luz) é criada como uma especialização da classe-pai, Explorador. O objetivo
desta nova classe é explorar um determinado ambiente, navegando até encontrar a
fonte de luz de maior intensidade existente neste ambiente.
A classe Besouro, ilustrada na figura 4.13, define apenas o método
procuraLuz, e herda os métodos da classe-pai Explorador. Este método executa,
primeiramente, através de uma estrutura de controle do tipo do/while, a operação
LightFinder, que verifica a intensidade de luz ambiente em cada sensor, e gira o
robô no sentido da maior intensidade de luz, retornando o menor valor registrado
pelos sensores. Neste caso, quanto menor o valor retornado pelos sensores, maior é
a intensidade da luz. Após a execução da operação LightFinder, o método
procuraLuz executa o método mover da classe-pai Explorador.
Na verdade, este é um ponto chave da orientação a objeto: o reuso de código.
Aqui, é possível observar que não é preciso definir um novo método mover para que
a classe Besouro possa se deslocar no ambiente. Basta usar o método mover já
definido na classe Explorador, pois a classe Besouro é filho da classe-pai
Explorador. Sendo assim, através do conceito de herança, a classe Besouro herda
todos os métodos definidos na classe-pai, Explorador.
Por fim, no método procuraLuz, a condição de saída da estrutura do/while é
dada pela comparação entre o valor retornado pelo sensor (menor valor) e o valor de
saturação da luz, ou seja, caso o valor retornado pelo sensor seja menor que o valor
de saturação da luz (condição de proximidade da luz), a aplicação é finalizada.
95
Figura 4.13. Classe Besouro
4.3.2.2 Definindo o objeto BEETLE
A figura 4.14a mostra o escopo do módulo main da aplicação, onde é criado
um novo objeto chamado BEETLE (besouro em inglês), pertencente à classe
Besouro. Como já mencionado, a classe Explorador tem como objetivo a exploração
de ambientes e a classe Besouro, uma especialização da classe Explorador, tem
como objetivo navegar (explorar) na direção da maior intensidade de luz do
ambiente.
A figura 4.14b mostra o módulo main, onde é possível observar a operação de
envio de mensagem ao objeto BEETLE, executando o método da classe Besouro,
procuraLuz. Vale lembrar que o método procuraLuz, apesar de pertencer a classe
Besouro, ativa o método mover pertencente a classe-pai, Explorador.
96
Sendo assim, é possível perceber neste exemplo o reuso efetivo de código,
onde uma nova classe (classe Besouro) foi criada a partir de uma classe já existente
(classe Explorador), herdando desta forma, todos os métodos definidos na classe-
pai.
Figura 4.14. Descrição do modulo main
A figura 4.15 ilustra a árvore de classes e a árvore de chamada relativa a este
exemplo, onde é possível visualizar a ligação entre as classes Besouro e Explorador.
97
Figura 4.15. Visualização das árvores de classes e chamadas
Como já mencionado, no ambiente de programação K++, quando uma
mensagem é inserida no escopo de um módulo, a árvore de chamadas é mostrada
automaticamente no ambiente, bastando apenas arrastar (recurso drag and drop) o
método a ser executado pela chamada da mensagem. Este recurso é muito
importante, pois permite ao programador visualizar, de forma bastante clara a
ligação existente entre os objetos definidos pelas classes e seus respectivos
métodos que podem ser usados na aplicação em foco, facilitando assim o
desenvolvimento dos algoritmos e evitando erros de programação.
Por fim, este exemplo mostrou de uma maneira didática as vantagens
do uso da programação orientada a objetos. A criação de uma nova classe a
partir de uma classe já existente reforça a idéia do reuso de código através da
herança dos métodos descritos na classe-pai, e a comunicação dos objetos
através do envio de mensagens facilita a compreensão dos algoritmos
desenvolvidos.
98
4.4 Programação avançada usando o Visual C++ Além das funcionalidades do ambiente de programação K++, a classe
Khepera usada no ambiente permite também que outras aplicações possam ser
desenvolvidas usando o Visual C++. A grande vantagem desta abordagem está no
fato de que a biblioteca de operações do K++ pode ser expandida de acordo com a
necessidade das aplicações, bastando para isso, o conhecimento do programador
para desenvolver novas operações. Apesar do K++ ser um software aberto, fazer
alterações em suas bibliotecas requer uma certa habilidade do programador. Mas o
fato do K++ ter sido desenvolvido usando os conceitos da programação orientada a
objetos permite através do reuso de código, o desenvolvimento de novas operações
de maneira bastante produtiva.
Neste sentido, o exemplo a seguir mostra uma aplicação complexa usando a
classe Khepera. O objetivo aqui é mostrar que é possível criar algoritmos para
aplicações complexas a partir das operações já definidas pela classe Khepera. A
figura 4.16 ilustra o exemplo em que o robô irá se deslocar até um dado ponto P.
Figura 4.16. O robô se move até o ponto P
99
Também é possível observar na figura 4.16 que caso haja algum obstáculo o
robô irá desviar da trajetória desse obstáculo e seguir novamente em direção ao
ponto P. O algoritmo dessa aplicação é apresentado em forma de fluxograma na
figura 4.17, o que facilita a compreensão do mesmo.
Figura 4.17. Fluxograma da aplicação que move robô até o ponto P
Inicialmente, o robô é colocado em um determinado ponto do ambiente dado
pela coordenada (0,0). Neste exemplo, o ponto P fica acima e a direita do ponto
inicial, assim o robô girará para o lado direito até apontar sua frente em direção ao
ponto P, e em seguida irá se locomover até atingir o ponto final. Nesse exemplo
nota-se a existência de um obstáculo na trajetória do robô. Portanto, para atingir o
100
ponto P, o robô precisa primeiro se desviar deste obstáculo, para depois seguir até o
ponto final. Neste caso, quanto o robô encontra o obstáculo, pára, gira 90° para
direita e anda a distância equivalente ao seu diâmetro, com o objetivo de tirá-lo da
trajetória do obstáculo.
O próximo passo é calcular a nova posição do robô e determinar qual o
ângulo de giro que o robô deve efetuar para que sua frente aponte novamente para
o ponto P. Feito isso, o robô irá se locomover novamente em direção ao ponto final.
Vale lembrar que, caso haja um novo obstáculo à frente do robô, ele irá repetir todo
o procedimento de desvio e cálculo de posição atual até que o robô atinja seu alvo, o
ponto P.
Nesta aplicação o cálculo da posição atual do robô é baseado somente na
leitura dos encoders das rodas através do uso do método ReadMotorPosition. A
dificuldade de basear-se somente na leitura dos encoders está no fato de que, por
exemplo, quando o robô encontra um obstáculo, ele girará sobre seu eixo, fazendo
com que o valor dos encoders sejam alterados, sendo que o robô não saiu do seu
lugar geométrico. Portanto, foi necessária uma série de cálculos e considerações
para resolução deste problema. De qualquer forma, os resultados obtidos com o uso
deste algoritmo atingiram o objetivo principal, que era o desenvolvimento de
aplicações complexas usando os métodos da classe Khepera. No anexo II, é
possível observar a complexidade do algoritmo que executa esta aplicação.
É importante salientar que, dependendo da habilidade do programador, a
classe Khepera pode ser adaptada para ser utilizada em alguma outra linguagem de
programação como Java, por exemplo, que é uma linguagem extremamente usada
para desenvolver aplicações para Internet. Neste sentido, uma aplicação
desenvolvida em Java e que use as classes Khepera e Serial poderia controlar um
robô Khepera através da Internet. Vale lembrar também que a classe Serial
desenvolvida para fazer a comunicação com o robô Khepera através da porta serial,
pode ser usada para qualquer outro tipo de robô que também use este tipo de
interface.
101
Desta forma, o uso das classes Khepera e Serial permite expandir o universo
de aplicações para robôs móveis, seja através da criação de novas operações no
ambiente K++ ou pelo uso dessas classes em alguma outra linguagem de
programação.
102
CONCLUSÃO
Nos dias atuais, ainda se perde muito tempo no estudo e desenvolvimento de
aplicações para robôs móveis. Assim, o presente trabalho contempla uma real
necessidade no desenvolvimento de softwares específicos para a área de robótica
móvel. Com o uso de um ambiente de programação como o K++, que incorpora a
produtividade da programação orientada a objetos e a versatilidade da programação
visual, a distância existente entre as etapas do projeto podem ser minimizadas.
Além disso, a utilização deste ambiente estende seus benefícios a um grupo de
pessoas que necessariamente não precisam ser especialistas em programação,
ampliando desta forma a utilização da ferramenta. Isto se deve ao fato de que o
ambiente K++ possui uma interface amigável, que facilita a programação e o
desenvolvimento de aplicações para o robô khepera. A escolha da metodologia
orientada a objetos e da linguagem de programação C++, atendeu as necessidades
do projeto, que levou em consideração a capacidade de reusar códigos.
Outro aspecto importante do K++ é a possibilidade de desenvolver novas
ferramentas para trabalhos futuros baseados em robôs programados em C, através
da modelagem de uma nova classe que contemple os aspectos funcionais deste
robô, ampliando o universo de aplicações para diferentes tipos de robôs.
Também é importante salientar que os resultados obtidos nos testes com o
ambiente de programação K++ foram satisfatórios. Através do desenvolvimento das
aplicações para o robô, as vantagens do uso da programação visual e da
programação orientada a objetos tornaram-se evidentes.
103
Melhorias futuras e possíveis desdobramentos A facilidade de criar aplicações é resultado de uma boa interface visual e de
um bom projeto de software. Apesar disto, o ambiente K++ possui ainda algumas
limitações que dificultam o desenvolvimento de aplicações mais complexas.
Atualmente, o ambiente suporta somente dados dos tipos real e inteiro. Apesar de
estar projetado para trabalhar com dados booleanos e vetores, pequenas
modificações devem ser feitas para que o ambiente possa trabalhar com esses
outros tipos de dados. Apesar de não ter sido comentado durante o texto, o
ambiente K++ ainda permite converter parcialmente o programa criado visualmente
em um código C, faltando ainda ajustar o mecanismo de tradução. Como o objetivo
deste trabalho era efetuar a controlar o robô em tempo real, não houve preocupação
em gerar o código C da aplicação. Em um trabalho futuro, a implementação da
geração do código C permitirá que o programa simulado no robô seja enviado a sua
memória para que o mesmo trabalhe em modo autônomo.
Outra importante melhoria pode ser obtida através da inclusão de novos
elementos ao robô como visão, atuadores (garras) e rádio-controle. A inclusão de
novos elementos ao ambiente K++ é uma relativamente complexa e exige um bom
conhecimento do ambiente Visual C++ e da estrutura interna do K++. De qualquer
maneira, a grande vantagem de um ambiente como o K++ é o fato de ser um
software que permite que o programador possa modificá-lo de acordo com as suas
necessidades.
Por fim, um importante desdobramento deste trabalho é a sua aplicação como
ferramenta de apoio ao ensino de programação. O uso de um ambiente de
programação visual para robôs móveis pode facilitar a compreensão dos algoritmos,
melhorando desta forma o aprendizado, pois concilia os conceitos de programação
com uma aplicação do mundo real.
104
REFERÊNCIAS BIBLIOGRÁFICAS
ASIMOV, I., 1994, “Visões de robô”, São Paulo: Círculo do Livro, 424 p.
BIANCHI, R.A.C., Simões, A.S., Costa, A.H.R., 2001, “Comportamentos Reativos
para Seguir Pistas em Robô Móvel Guiado por Visão”, V Simpósio Brasileiro de
Automação Inteligente.
BOTELHO, S. S. C., 1996, “Desenvolvimento de sistemas inteligentes para controle
de robôs móveis”, Dissertação de mestrado em Ciências da Computação –
Instituto de Informática, Universidade Federal do Rio Grande do Sul, Porto
Alegre, 124 p.
BRAITENBERG, V., 1984, “Vehicles: experiments in synthetic psychology”,
Cambridge, MIT Press, 152 p.
BRUMITT, B. L., Coulter, R. C., Stentz, A., 1992, “Dynamic trajectory planning for a
cross-country navigator”, Boston Proceedings of SPIE Symposium on Mobile
Robots.
BURNETT, M. M., et al, 1995. Visual Object-Programming: Concepts and
Environments, Prentice Hall.
CALLONI, B. A., Bargert, D. J., 1994, “Iconic programming in BACCII vs Textual
Programming: which is a better Learning Environment?”, SIGCSE Bulletin vol.
26, n° 1, pp. 188-192.
CAMPBELL, J., 1993, “C Programmer's Guide to Serial Communication”, Sams, pp.
407-598.
CYBERBOTICS, 2003, “Webots Reference Manual”, Cyberbotics, Disponível Em
http://
cyberboticspc1.epfl.ch/cdrom/common/doc/webots/reference/reference.pdf
DI PAOLO E. A.,1998, “An investigation into the evolution of communication”,
Adaptive Behavior journal, vol. 6, n° 2, pp. 285-324
FIRBY, R. James, 1993, “An architecture for a synthetic vacuum cleaner”, AAAI Fall
Symposium, Series Workshop on instantiating realworlds agents. 9 p.
FLOREANO, D., Mondana, F., 1996, “Evolution of homing navigation in a real mobile
robot”, IEEE Transactions on Systems, Man, and Cybernetics – Part B, vol. 26,
pp. 396-407
105
GROOVER, M. P., WEISS, M., NAGEL, R. N., et al, 1988, “Robótica: tecnologia e
programação”, São Paulo: McGraw-Hill, 401 p.
GUDWIN, R. R., 1997, “Linguagens de programação”, Notas de aula, 17 p.
Disponível em http://www.eng.uerj.br/~araujo/disciplinas/Caract/ling_prog.pdf
HARVEY, B., 1997, “Computer Science Logo Style”, MIT Press, vol. 1, Cambridge,
pp. 17-39
HOLZNER, S., 2001, “C++ Black Book”, Makron Books, 646 p.
JONES, M. J., 2001, “Fundamentos do desenho orientado a objeto com UML”,
Makron Books, 462 p.
KRUGLINSKI, D. J., 1996, “Inside Visual C++”, Microsoft Press, 843 p.
K-TEAM, 1999a, “Khepera BIOS Manual”, K-Team, Switzerland, 121 p. Disponível
em http://www.k- team.com/download/khepera.html
K-TEAM, 1999b, “Khepera User Manual”, K-Team, Switzerland, 52 p. Disponível em
http://www.k- team.com/download/khepera.html
K-TEAM, 2002a, “IR Sensors Report”, K-Team, Switzerland, 19 p. Disponível em
http://www.k-team.com/download/khepera.html
K-TEAM, 2002b, “Khepera Programming Manual”, K-Team, Switzerland, 48 p.
Disponível em http://www.k- team.com/download/khepera.html
LEE, R. C., Tepfenhart, W. M., 2002. “UML and C++ - Guia prático de
desenvolvimento orientado a objeto”, Makron Books, pp. 23-39.
LUND, H.H., Cuenta, E.V., Hallan, J., 1996, “A Simple Mobile Real-team Robot
Tracking System”, Technical Paper 41, Department Artificial of Intelligence,
University of Edinburgh.
MÄCHLER P., 1995, "Detection Technologies for Anti-Personnel Mines",
Autonomous Vehicles in Mine Countermeasures Symposium, Monterey,
California, pp. 150-154.
MAIOR, D. S., 1998, “Os robôs estão chegando”, Ciência Hoje - suplemento
tecnologia, vol. 23, n°136.
MONDANA, F., Wrinkled, E., Ienne, P., 1993, “Mobile Robot Miniaturization: A Tool
For Investigation In Control Algorithms”, Experimental Robotics III - Proceedings
of the Third International Symposium on Experimental Robotics, October 28-30,
Kyoto,Japan, pp. 501-513
106
MONDANA, F., Floreano, D., 1996, “Evolution and mobile autonomous robotics”,
Towards Evolvable Hardware; The Evolutionary Engineering Approach, Berlin,
pp. 221-249
NASSI, I., Shnneiderman, B., 1984, “Flowchart Techniques for Structured
Programming”, ACM Sigplan Notices, Vol. 8, n° 8, p. 723-728.
NATIONAL, 2000, “LABVIEW® Manuals”, National Instruments, 272 p
NITZAN, D., 1985, “Development of intelligent robots: achievements and issues”,
IEEE Journal of Robotics and Automation, vol.1, n°1, pp. 3-13.
NOVELETTO F., Sousa, A. H., Hounsell, M. S., 2003, “Desenvolvimento de um
Ambiente de Programação Visual para Robôs Móveis”, III Congresso Brasileiro
de Computação – CBComp, pp. 914-924.
NOVELETTO F., Sousa, A. H., Hounsell, M. S., 2003, “Desenvolvimento de um
Ambiente de Programação Visual para Robôs Móveis”, I2TS'2003 –
International Information and Telecommunication Technologies Symposium –
(IEEE Seção Sul Brasil) – Workshop III CBComp Best Papers.
NOVELETTO F., Sousa, A. H., Hounsell, M. S., 2003, “The development of a visual
programming environment for mobile robots”, COBEM-2003 – 17° Congresso
Brasileiro de Engenharia Mecânica .
PAPERT, S., 1985, “Logo: Computadores e Educação”, Editora Brasiliense, 254 p.
PRESSMAN, R. S., 1995, “Engenharia de Software”, Makron Books, 1056 p
RAJEEV K. P., Burnett, M. M., 1993, “Is It Easier to Write Matrix Manipulation
Programs Visually or Textually? An Empirical Study”, Proceedings of the 1993
IEEE Symposium on Visual Languages, pp. 344-351.
RUSSEL, S. J., Norvig, P.,1995, “Artificial Intelligence: A Modern Approach”, Prentice
Hall, 932 p.
SCALAN, D. A., 1989, “Structured Flowcharts Outperform Pseudocodes: An
Experimental Comparation”, IEEE Software vol. 6, n° 5, pp. 28-36
SETHI, R., 1996, "Programming Languages: Concepts and Constructs", Second
edition, Addison – Wesley, 624 p.
SHU N.C., 1988. Visual Programming, Van Nostrand Reinhold Company Inc.,New
York.
107
SOUSA, A. H., Ferreira, E. C., Silva, M. A. Vieira, 1996a, “ONAGRO – A Graphical
Environment to the Development of Microcontrollers Software”, Proceedings of
WAC-96: Second World Automation Congress, Montpellier-France, p. 301 – 306
SOUSA, A. H., Ferreira, E. C., 1998, “O++: A Visual Object-Oriented Language for
Embedded Systems”, Proceedings of ISSCI-98: International Symposium on
Soft Computing for Industry, Achorage/AK/USA.
SOUSA, A. H., “Uma proposta de linguagem visual orientada a objetos para
programação de microcontroladores”, Campinas, 1999, 116 p. Tese de
Doutorado – Engenharia Elétrica – Universidade Estadual de Campinas.
SPRINGER, S. P., Deutsch, G., 1985, “Left Brain, Right Brain”, W. H. Freeman and
Company, New York
VOSS, G, 1991, “Object-Oriented Programming: An Introduction”, McGraw-Hill, 584 p
WASSERMAN, A. I., et al, 1990, “The Object-Oriented Structured Design Notation for
Software Design Representation”, IEEE Computer, vol. 23, n° 3, p 50 – 63
WHITLEY K. N., 1996, “Visual Programming Languages and the Empirical Evidence
for and Against”, Journal of Visual Languages and Computing, vol. 8, 19 p
WINBLAND, A. L., Edwards, S. D., King, D. R., 1993, “Software Orientado ao
Objeto”, Makron Books, 314 p.
108
ANEXO I Implementação da classe Serial em C++
Arquivo: serial.cpp
109
ARQUIVO SERIAL.CPP
// Serial.cpp // by ANTONIO HERONALDO DE SOUSA E FABRICIO NOVELETTO // FACULDADE DE ENGENHARIA DE JOINVILLE // SETEMBRO 2002 #include "stdafx.h" #include "Serial.h" CSerial::CSerial() { memset( &m_OverlappedRead, 0, sizeof( OVERLAPPED ) ); memset( &m_OverlappedWrite, 0, sizeof( OVERLAPPED ) ); m_hIDComDev = NULL; m_bOpened = FALSE; } CSerial::~CSerial() { Close(); } BOOL CSerial::Open( int nPort, int nBaud ) { if( m_bOpened ) return( TRUE ); char szPort[15]; char szComParams[50]; DCB dcb; wsprintf( szPort, "COM%d", nPort ); m_hIDComDev = CreateFile( szPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL ); if( m_hIDComDev == NULL ) return( FALSE ); memset( &m_OverlappedRead, 0, sizeof( OVERLAPPED ) ); memset( &m_OverlappedWrite, 0, sizeof( OVERLAPPED ) ); COMMTIMEOUTS CommTimeOuts; CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF; CommTimeOuts.ReadTotalTimeoutMultiplier = 0; CommTimeOuts.ReadTotalTimeoutConstant = 0; CommTimeOuts.WriteTotalTimeoutMultiplier = 0; CommTimeOuts.WriteTotalTimeoutConstant = 5000; SetCommTimeouts( m_hIDComDev, &CommTimeOuts ); wsprintf( szComParams, "COM%d:%d,n,8,1", nPort, nBaud ); m_OverlappedRead.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); m_OverlappedWrite.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); dcb.DCBlength = sizeof( DCB ); GetCommState( m_hIDComDev, &dcb ); dcb.BaudRate = nBaud; dcb.ByteSize = 8;
110
unsigned char ucSet; ucSet = (unsigned char) ( ( FC_RTSCTS & FC_DTRDSR ) != 0 ); ucSet = (unsigned char) ( ( FC_RTSCTS & FC_RTSCTS ) != 0 ); ucSet = (unsigned char) ( ( FC_RTSCTS & FC_XONXOFF ) != 0 ); if( !SetCommState( m_hIDComDev, &dcb ) || !SetupComm( m_hIDComDev, 10000, 10000 ) || m_OverlappedRead.hEvent == NULL || m_OverlappedWrite.hEvent == NULL ){ DWORD dwError = GetLastError(); if( m_OverlappedRead.hEvent != NULL ) CloseHandle( m_OverlappedRead.hEvent ); if( m_OverlappedWrite.hEvent != NULL ) CloseHandle( m_OverlappedWrite.hEvent ); CloseHandle( m_hIDComDev ); return( FALSE ); } m_bOpened = TRUE; return( m_bOpened ); } BOOL CSerial::Close( void ) { if( !m_bOpened || m_hIDComDev == NULL ) return( TRUE ); if( m_OverlappedRead.hEvent != NULL ) CloseHandle( m_OverlappedRead.hEvent ); if( m_OverlappedWrite.hEvent != NULL ) CloseHandle( m_OverlappedWrite.hEvent ); CloseHandle( m_hIDComDev ); m_bOpened = FALSE; m_hIDComDev = NULL; return( TRUE ); } BOOL CSerial::WriteCommByte( unsigned char ucByte ) { BOOL bWriteStat; DWORD dwBytesWritten; bWriteStat = WriteFile( m_hIDComDev, (LPSTR) &ucByte, 1, &dwBytesWritten, &m_OverlappedWrite ); if( !bWriteStat && ( GetLastError() == ERROR_IO_PENDING ) ){ if( WaitForSingleObject( m_OverlappedWrite.hEvent, 1000 ) ) dwBytesWritten = 0; else{ GetOverlappedResult( m_hIDComDev, &m_OverlappedWrite, &dwBytesWritten, FALSE ); m_OverlappedWrite.Offset += dwBytesWritten; } } return( TRUE ); } int CSerial::SendData( const char *buffer, int size ) { if( !m_bOpened || m_hIDComDev == NULL ) return( 0 );
111
DWORD dwBytesWritten = 0; int i; for( i=0; i<size; i++ ){ WriteCommByte( buffer[i] ); dwBytesWritten++; } return( (int) dwBytesWritten ); } int CSerial::ReadDataWaiting( void ) { if( !m_bOpened || m_hIDComDev == NULL ) return( 0 ); DWORD dwErrorFlags; COMSTAT ComStat; ClearCommError( m_hIDComDev, &dwErrorFlags, &ComStat ); return( (int) ComStat.cbInQue ); } int CSerial::ReadData( void *buffer, int limit ) { if( !m_bOpened || m_hIDComDev == NULL ) return( 0 ); BOOL bReadStatus; DWORD dwBytesRead, dwErrorFlags; COMSTAT ComStat; ClearCommError( m_hIDComDev, &dwErrorFlags, &ComStat ); if( !ComStat.cbInQue ) return( 0 ); dwBytesRead = (DWORD) ComStat.cbInQue; if( limit < (int) dwBytesRead ) dwBytesRead = (DWORD) limit; bReadStatus = ReadFile( m_hIDComDev, buffer, dwBytesRead, &dwBytesRead, &m_OverlappedRead ); if( !bReadStatus ){ if( GetLastError() == ERROR_IO_PENDING ){ WaitForSingleObject( m_OverlappedRead.hEvent, 2000 ); return( (int) dwBytesRead ); } return( 0 ); } return( (int) dwBytesRead ); } int CSerial::ReadCommand(char *lpBuffer) { time_t t0, t0a, t1, t1a; int szBuffer; int nBytesRead;
112
int ret, flag; char lpBuffer_aux[100]; lpBuffer[0]='\0'; lpBuffer_aux[0]='\0'; t0a = time(NULL); do { t0 = time(NULL); do { szBuffer=ReadDataWaiting(); t1 = time(NULL); } while ((szBuffer==0) && ((t1-t0) < 2)); if (szBuffer!=0) { nBytesRead = ReadData(lpBuffer_aux, szBuffer); lpBuffer_aux[szBuffer]='\0'; strcat(lpBuffer,lpBuffer_aux); } szBuffer = strlen(lpBuffer); if (szBuffer > 0) { if (lpBuffer[szBuffer-1] == 10) { flag = 0; } else { flag = 1; } } else { flag = 1; } t1a = time(NULL); } while (flag && ((t1a-t0a) < 2) ); if (szBuffer > 2) { lpBuffer[szBuffer-2] = '\0'; //lpBuffer[szBuffer-2] = '\0'; ret = szBuffer - 2; } else ret = -1; return (ret); } int CSerial::WriteCommand(char *lpBuffer, char *lpBuffer_ret) { int ret; char lpBuffer_trans[50]; int len = strlen(lpBuffer);
113
strcpy(lpBuffer_trans, lpBuffer); lpBuffer_trans[len] = 13; lpBuffer_trans[len+1] = 10; lpBuffer_trans[len+2] = '\0'; SendData(lpBuffer_trans, strlen(lpBuffer_trans)); ret = ReadCommand(lpBuffer_ret); if (ret != -1) { if (lpBuffer_ret[0] == (lpBuffer_trans[0])) //+32)) ret = 1; else ret = -1; } return(ret); }
114
ANEXO II Implementação da classe Khepera em C++
Arquivo: khepera.cpp
115
ARQUIVO KHEPERA.CPP // khepera.cpp // by ANTONIO HERONALDO DE SOUSA E FABRICIO NOVELETTO // FACULDADE DE ENGENHARIA DE JOINVILLE // SETEMBRO 2002 #include "stdafx.h" #include "khepera.h" #include <math.h> IMPLEMENT_SERIAL(CKhepera, CObject, 0); CKhepera::CKhepera() { m_prox = PROX_SATURATION; m_axis = AXIS_DISTANCE; m_encoder = ENCODER_RESOLUTION; m_light = LIGHT_SATURATION; } CKhepera::~CKhepera() { } BOOL CKhepera::Open( int nPort, int nBaud ) { return( serial.Open(nPort,nBaud)); } BOOL CKhepera::Close( void ) { return(serial.Close()); } // ******************************************* // Move robô para frente // ******************************************* void CKhepera::MoveForward(int Motor1, int Motor2) { char lpBuffer_ret[50]; char buf[50]; char charMotor1[10], charMotor2[10]; _itoa(Motor1,charMotor1,10); _itoa(Motor2,charMotor2,10); strcpy(buf, "D,"); strcat(buf, charMotor1); strcat(buf, ","); strcat(buf, charMotor2); serial.WriteCommand(buf,lpBuffer_ret); }
116
// ******************************************* // Move robô para traz // ******************************************* void CKhepera::MoveBackward(int Motor1, int Motor2) { char lpBuffer_ret[50]; char buf[50]; char charMotor1[10], charMotor2[10]; _itoa(Motor1,charMotor1,10); _itoa(Motor2,charMotor2,10); strcpy(buf, "D,"); strcat(buf, charMotor1); strcat(buf, ","); strcat(buf, charMotor2); serial.WriteCommand(buf,lpBuffer_ret); } // ************************************************* // Para motores do robô // ************************************************* void CKhepera::StopMotors() { char lpBuffer_ret[50]; lpBuffer_ret[0]='\0'; serial.WriteCommand("D,0,0",lpBuffer_ret); } // ************************************************* // Leitura dos contadores dos motores // ************************************************* void CKhepera::ReadMotorPosition(int* count_motors) { char lpBuffer_ret[50]; serial.WriteCommand("H",lpBuffer_ret); CString strCountMotors(lpBuffer_ret); CString Count_Motors_Str[10]; CString s1; CString s2(""); int z = 0; int x = strCountMotors.GetLength(); for( int i = 2; i < x; ++i ) { s1 = strCountMotors.GetAt(i); if (s1 == ",") { z++; Count_Motors_Str[z] = s2; s2 = ""; } else s2 = s2 + s1; }
117
Count_Motors_Str[2] = s2; for( int ii = 1; ii < 3; ++ii ) { count_motors[ii-1] = atoi(Count_Motors_Str[ii]); } } // ************************************************* // Leitura dos sensores de Proximidade // ************************************************* void CKhepera::ReadProxSensors(int* val_sensor) { char lpBuffer_ret[50]; serial.WriteCommand("N",lpBuffer_ret); CString strSensors(lpBuffer_ret); CString Val_Sensor_Str[10]; CString s1; CString s2(""); int z = 0; int x = strSensors.GetLength(); for( int i = 2; i < x; ++i ) { s1 = strSensors.GetAt(i); if (s1 == ",") { z++; Val_Sensor_Str[z] = s2; s2 = ""; } else s2 = s2 + s1; } Val_Sensor_Str[8] = s2; for( int ii = 1; ii < 9; ++ii ) // ???????? { val_sensor[ii-1] = atoi(Val_Sensor_Str[ii]); } }
118
// ************************************************* // Leitura dos sensores de luminosidade // ************************************************* void CKhepera::ReadLightSensors(int* val_sensor) { char lpBuffer_ret[50]; serial.WriteCommand("O",lpBuffer_ret); CString strSensors(lpBuffer_ret); CString Val_Sensor_Str[10]; CString s1; CString s2(""); int z = 0; int x = strSensors.GetLength(); for( int i = 2; i < x; ++i ) { s1 = strSensors.GetAt(i); if (s1 == ",") { z++; Val_Sensor_Str[z] = s2; s2 = ""; } else s2 = s2 + s1; } Val_Sensor_Str[8] = s2; for( int ii = 1; ii < 9; ++ii ) // ???????? { val_sensor[ii-1] = atoi(Val_Sensor_Str[ii]); } } // ************************************************* // Gira robô à esquerda // ************************************************* void CKhepera::TurnLeft(double degrad, int speed) { int count_motors[10]; MoveForward(-speed,speed); ReadMotorPosition(count_motors); int CountStop = (count_motors[1] + (int) (m_axis * degrad)); do { ReadMotorPosition(count_motors); } while(count_motors[1]<CountStop); StopMotors(); }
119
// ************************************************* // Gira robô à direita // ************************************************* void CKhepera::TurnRight(double degrad, int speed) { int count_motors[10]; MoveForward(speed,-speed); ReadMotorPosition(count_motors); int CountStop = (count_motors[0] + (int)(m_axis * degrad)); do { ReadMotorPosition(count_motors); } while(count_motors[0]<CountStop); StopMotors(); } // ************************************************************ // Move robô por dada distância com dada velocidade // ************************************************************ void CKhepera::MoveLine(int distance, int angle, int speed) { int count_motors[10]; int val_sensor[10]; TurnRight(angle); MoveForward(speed,speed); ReadMotorPosition(count_motors); int CountStop = (count_motors[0] + (int)(12.5 * distance)); do { ReadMotorPosition(count_motors); ReadProxSensors(val_sensor); // Se existir obstaculo PARE! // ----------------------------------------------- if (val_sensor[1]>m_prox || val_sensor[2]>m_prox || val_sensor[3]>m_prox || val_sensor[4]>m_prox) { StopMotors(); } else MoveForward(speed,speed); // ----------------------------------------------- } while (count_motors[0]<CountStop); StopMotors(); }
120
// ************************************************* // Move robô até um dado ponto P // ************************************************* void CKhepera::MoveToPoint(int Xfinal, int Yfinal, int* coordXY) { int Contador, CountStop; int x_ref, y_ref; int count_motors[2]; int val_sensor[8]; int flagStop = 0; double eixoX, eixoY, distance, angleDeg, angleRad; double pi = 3.1415926535; int x_atual = 0; int y_atual = 0; do { eixoX = (double) Xfinal - x_atual; eixoY = (double) Yfinal - y_atual; distance = sqrt(eixoX*eixoX + eixoY*eixoY); angleRad = atan(eixoY / eixoX); angleDeg = (angleRad*180.0 / pi); if (eixoX > 0) { TurnRight(90 - angleDeg); } else { TurnLeft(90 - angleDeg); } ResetMotors(); MoveForward(5,5); CountStop = (int)(12.5 * distance); do { ReadMotorPosition(count_motors); ReadProxSensors(val_sensor); Contador = (int)(count_motors[0]*m_encoder); x_ref = (int)(Contador * cos(angleRad)); y_ref = (int)(Contador * sin(angleRad)); } while ( (count_motors[0]<CountStop) && (val_sensor[0]<m_prox) && (val_sensor[1]<m_prox) && (val_sensor[2]<m_prox) && (val_sensor[3]<m_prox) && (val_sensor[4]<m_prox) && (val_sensor[5]<m_prox));
121
if (x_atual > Xfinal) x_atual = x_atual - x_ref; else x_atual = x_atual + x_ref; if (y_atual > Yfinal) y_atual = y_atual - y_ref; else y_atual = y_atual + y_ref; if (count_motors[0]>=CountStop) StopMotors(); else { int sentido_giro = -1; int left_sensors = val_sensor[0]+val_sensor[1]+val_sensor[2]; int right_sensors = val_sensor[3]+val_sensor[4]+val_sensor[5]; if (left_sensors > right_sensors) { sentido_giro = 1; // gira para direita TurnRight(90); } else { sentido_giro = 0; // gira para esquerda TurnLeft(90); } int sair = 0; ResetMotors(); MoveForward(2,2); while (!sair) { ReadProxSensors(val_sensor); if ((sentido_giro == 1) && (val_sensor[0] < 1)) sair = 1; if ((sentido_giro == 0) && (val_sensor[5] < 1)) sair = 1; } MoveLine(60,0,2); // anda o diametro do robo ReadMotorPosition(count_motors); Contador = (int)(count_motors[0]*m_encoder); x_ref = (int)(Contador * sin(angleRad)); y_ref = (int)(Contador * cos(angleRad));
122
if (sentido_giro == 1) { x_atual = x_atual + x_ref; y_atual = y_atual - y_ref; TurnLeft(180 - angleDeg + 3);//ERRO = 3 } else { x_atual = x_atual - x_ref; y_atual = y_atual + y_ref; TurnRight(angleDeg); } } } while ( ((x_atual <= (0.9*Xfinal)) || (x_atual >= (1.1*Xfinal))) || ((y_atual <= (0.9*Yfinal)) || (y_atual >= (1.1*Yfinal))) ); StopMotors(); } // ************************************************* // Zera os contadores dos motores // ************************************************* void CKhepera::ResetMotors() { char lpBuffer_ret[50]; serial.WriteCommand("G,0,0",lpBuffer_ret); } // ************************************************* // Código para Desvio de Obstáculo // ************************************************* void CKhepera::ObstacleSkipper(int val_angle) { int val_sensor[10]; do { ReadProxSensors(val_sensor); } while ( val_sensor[0]<m_prox && val_sensor[1]<m_prox && val_sensor[2]<m_prox
&& val_sensor[3]<m_prox && val_sensor[4]<m_prox && val_sensor[5]<m_prox);
StopMotors(); int leftSensors = val_sensor[0] + val_sensor[1] + val_sensor[2]; int rightSensors = val_sensor[3] + val_sensor[4] + val_sensor[5]; if (leftSensors < rightSensors) { TurnLeft(val_angle); } else { TurnRight(val_angle); } }
123
// ************************************************* // Código para encontrar luz // ************************************************* int CKhepera::LightFinder(int val_sat) //int CKhepera::LightFinder() { int val_light; char lpBuffer_ret[50]; if (val_sat == -1) val_sat = m_light; serial.WriteCommand("O",lpBuffer_ret); CString strSensors(lpBuffer_ret); CString Val_Sensor_Str[10]; CString s1; CString s2(""); int z = 0; int x = strSensors.GetLength(); int val_sensor[8]; for( int i = 2; i < x; ++i ) { s1 = strSensors.GetAt(i); if (s1 == ",") { z++; Val_Sensor_Str[z] = s2; s2 = ""; } else s2 = s2 + s1; } Val_Sensor_Str[8] = s2; for( int ii = 1; ii < 9; ++ii ) // ???????? { val_sensor[ii-1] = atoi(Val_Sensor_Str[ii]); } // Verifica qual sensor tem o menor valor (mais luz) int lightSensor=512; int numSensor=0; for( int iii = 0; iii < 8; ++iii ) { if (val_sensor[iii] < lightSensor) { numSensor = iii; lightSensor = val_sensor[iii]; } }
124
if (lightSensor <= val_sat) val_light=1; else val_light=0; switch(numSensor) { case 0: TurnLeft(90); break; case 1: TurnLeft(45); break; case 2: TurnLeft(10); break; case 3: TurnRight(10); break; case 4: TurnRight(45); break; case 5: TurnRight(90); break; case 6: TurnRight(170); break; case 7: TurnLeft(170); break; }
return (val_light); } void CKhepera::SetValues(float a, float e, int l, int p) { m_axis = a; m_encoder = e; m_light = l; m_prox = p; } void CKhepera::GetValues(float &a, float &e, int &l, int &p) { a = m_axis; e = m_encoder; l = m_light; p = m_prox; }
125
void CKhepera::Serialize(CArchive& ar) { if (ar.IsStoring()) { ar << (WORD) m_light; ar << (WORD) m_prox; ar << m_axis; ar << m_encoder; } else { WORD wTemp; ar >> wTemp; m_light = (int)wTemp; ar >> wTemp; m_prox = (int)wTemp; ar >> m_axis; ar >> m_encoder; } }
126
Top Related