terça-feira, 12 de janeiro de 2016

Robótica: Telemetria & Comunicação Wireless

Atuei em um projeto recentemente em que seria utilizada telemetria analógica para realizar medições de veículos de corrida. Para isto, seria necessário a comunicação entre o dispositivo instalado no veículo com uma central de comunicação, ou seja, comunicação a distância sem-fio.

Utilizando o - sempre sutil - Arduino, consegui desempenhar a tarefa com sucesso, por meio de poucos componentes e com baixo custo, como irei explicitar a seguir.

Componentes
Os componentes utilizados para auxiliar no projeto foram:

  • ARDUINO MEGA 2560
    • foi designado para embarcar os sensores, o display e o transceptor;
    • modelo ideal pela quantidade de portas digitais e analógicas, o suficiente para poder embarcar todos os componentes necessários e poder armazenar memória o suficiente;
    • para alimentação, foi utilizada uma bateria comum de 9V;
Arduino Mega 2560

  • ARDUINO UNO
    • se assemelha ao modelo acima, com a diferença de ser menor, contendo menos portas analógicas, digitais e menos memória;
    • atuou como receptor dos dados da telemetria, conectado ao computador receptor dos dados, via cabo USB;
Arduino Uno

  • NRF24L01 WIRELESS TRANSCEIVER
    • o componente mais importante: o módulo de rede sem fio NRF24L01 é um transceptor fabricado pela Nordic e é uma excelente opção de comunicação wireless entre vários dispositivos, como: Arduino, PIC, Raspberry, BeagleBone entre outros;
    • algumas especificações:
      • alcance de 10 metros (ambientes internos) a 50 metros (campo aberto);
      • possui uma antena embutida que opera na frequência de 2,4GHz com velocidade de 2Mbps;
      • modulação GFSK;
      • habilidade de anti-interferência;
      • verificação de erros por CRC;
      • comunicação multi-ponto de 125 canais;
      • controle de fluxo;
      • tensão de alimentação pode variar entre 1,9 e 3,6V;
    • foram utilizados no total dois módulos: 
      • um NRF24L01 comum, acoplado ao Uno, e
      • outro adaptado com PA (amplificador de potência) e LNA (antena externa), acoplado ao Mega, para alcance de longas distâncias: estima-se 1km!
Módulo NRF24L01 e outro adaptado com PA+LNA

  • DISPLAY LCD
    • este componente realizou a apresentação dos dados lidos pelo Arduino;
    • foi utilizado o modelo 16x2 da fabricante Winstar, com backlight ajustável;
    • foi programado para atuar em tempo real, atualizando os dados de acordo com o interpretado pelos sensores e também com a função de alternar as informações mostradas na tela, por meio de um push-button;
Display LCD Winstar

Montagem

ARDUINO MEGA (TRANSMISSOR)
Do projeto e componentes apresentados na imagem acima, há algumas observações pertinentes a se fazer:
  • para o funcionamento correto do módulo wireless NRF24L01, o ideal é que se use um capacitor de 4.7 μF para o 3.3V do Arduino ou um regulador de tensão, se precisar utilizar alimentação de 5V, convertendo a tensão para 3.3V;
  • no caso de problemas com transmissão utilizando wireless com PA+LNA, uma alternativa é baixar a taxa de transmissão (testar a partir de aprox. 2400bps);
  • a montagem neste Arduino não segue o mesmo padrão dos demais, e deve ser realizado um mapeamento para converter a pinagem dos conectores, e ligar corretamente como mostrado no esquemático a seguir e na tabela de pinagens mais à frente;
  • o led ligado ao pino digital 8 é utilizado para informar visualmente que as mensagens estão sendo trnasmitidas: quando enviado um sinal ao outro transceptor, o led irá piscar;
  • o projeto foi desenvolvido utilizando os RFs com funções distintas - apenas como agente transmissor ou receptor, por sua vez; no entanto é possível utilizá-los como transceptores propriamente: realizar envio e recebimento de mensagens simultaneamente, ficando passível apenas de tratamento para o uso correto;
  • este Arduino possui capacidade para embarcamento de mais sensores; no caso do projeto em questão, os pinos analógicos de A0 a A15 ficaram reservados para embarcar o acelerômetro a ser desenvolvido posteriormente;
Montagem no Arduino Mega

ARDUINO UNO (RECEPTOR)
A montagem da ponta receptora da telemetria por sua vez é mais simples, como mostrada na imagem a seguir, contendo apenas a conexão com o transceptor e um led que, assim como no outro Arduino, irá piscar, mas desta vez, quando receber a mensagem.

Uma observação cabível no caso do receptor: neste caso foi utilizado o Arduino pois se desejava realizar as leituras no computador. Mas há alternativas de se utilizar o módulo RF sem Arduino, por meio de adaptardores FTDI USB, tais como o PIC18F2450 e o FT2232.

Montagem no Arduino Uno

TABELA DE PINAGENS
O mapeamento utilizado no projeto foi como o descrito na tabela a seguir:



NRF24L01 vs. Outros
Algumas considerações sobre outros módulos pesquisados e o porquê de ter escolhido o NRF24L01 para desempenhar o papel de comunicador wireless:
  • xBee
    • ótima qualidade porém inviável, tanto pelo preço pela quantidade de componentes necessários para o funcionamento completo no projeto em questão;
  • Apc220
    • posui ótimo alcançe e, em teoria, é simples de ser programado, porém o custo também restringe a viabilidade;
  • Módulos bluetooth
    • baratos e de usabilidade simples, porém curto alcance;
  • Módulos WiFi:
    • CC3000
      • dependência de instalação, configuração e componentes de infra-estrutura WiFi, além do preço ser um pouco salgados;
    • ESP8266
      • o mais barato e simplista na configuração de infra;
      • foi utilizado no projeto mas não funcionou 100%;
      • breve detalhamento a seguir;
  • ESP8266
Não cabe aqui uma descrição técnica aprofundada do módulo, mas ele basicamente se utiliza de redes wireless 802.11 b/g/n, enviando e recebendo dados nos modos AP (Access Point) e STA (Station).

Uma grande vantagem deste módulo é que ele possui um processador poderoso em sua placa, sendo capaz de integração com outros sensores e aplicações específicas, e podendo funcionar sozinho, por meio da configuração do firmware e do uso dos pinos GPIO, além de ser possível montar um webserver para monitoramento do módulo e de que não é necessário mais de um módulo para haver comunicação com o PC.

O módulo comprado apresentou muita instabilidade na montagem, pelo fato do módulo funcionar inteiramente em 3.3V (não apenas a alimentação, como também os demais pinos: RX, TX, CH_PD, RST e GPIO), e o único meio de fazê-lo funcionar foi utilizando o buffer não-inversor CD4050. A vantagem de usar este CI se dá por ser barato, fácil de encontrar, e fácil de utilizar: basta alimentar o circuito com 3.3V, e as entradas de 5V que ele receber serão então transformadas em 3.3V nas suas respectivas saídas. Além disso, usando um único buffer é possível trabalhar com 6 entradas passíveis de serem modificadas de 5V para 3,3V.

Dessa forma sua montagem e configuração foram realizadas com sucesso, porém a leitura das mensagens pelo monitor serial do Arduino não puderam ser ajustadas corretamente - foram testadas praticamente todas as combinações de baud possíveis.

  • NRF24L01
Por fim, este módulo representou a melhor alternativa, chegando a uma configuração estável e realizando a comunicação devidamente. Os testes iniciais se deram na forma de transceptor, utilizando o módulo para receber e transmtir sinais e comandos, simultaneamente. 

Foram testadas funções com push-button, servo motores, leds e acionamento de demais componentes, além da questão de distância: em campo aberto, o maior alcance funcional testado foi de 90m. Para funcionamento do módulo como transceptor, foi usado o conceito de mestre-servo. Para configurar um deles como o mestre, na configuração presente, passa-se um resistor entre um pino configurado no programa como input pull-up e o GND: dessa forma o programa procura por essa conexão para saber qual papel o módulo deve assumir. Não há problema em trocar, durante a execução, qual deles será o mestre, mas se ambos estiverem simultaneamente como mestre ou como servo, a comunicação não será bem-sucedida.

O uso do módulo RF na Nordic pode ser variado, por exemplo para acionamento de relés, automação e controle de sistemas via wireless.

Algumas fotos durante os testes iniciais do módulo em casa:

Testes

Algoritmo
Algumas bibliotecas foram utilizadas, como a do Display LCD (LiquidCrystal), a do Serial (SoftwareSerial) para testes do ESP8266, ambas nativas do Arduino, e a do RF, baixada do site do fabricante.

Basicamente, o algoritmo do módulo consiste da inicialização dos componentes e do loop de comunicação entre ambos. Seria necessário complementação de lógica para realizar cálculo de medições caso o microcontrolador tivesse mais sensores embarcados, apenas uma adaptação e uma iteração a mais no vetor de dados a serem transmitidos.

A seguir, o pseudo-código implementado para cada ponta da telemetria:

RF-EMISSOR
    setup
        radio.inicializa()
        radio.abreCanalTransmissao()
        led.inicializa()
        display.inicializa()
    loop
        se (tempoEnvio > x)
            para (i, 1 a 8)
                rpm[i] ← leAnalogico(i)
            /* medicao rpm */
            msg[0] = codigo
            msg[1] = rpm
            radio.envia(msg)
            led.pisca()
        senao 
            tempoEnvio++
        display.atualiza(rpm)
        delay(100)

RF-RECEPTOR
    setup
        radio.inicializa()
        radio.abreCanalRecepcao()
        radio.comecaOuvir()
        led.inicializa()
    loop
        se (radio.disponivel())
            boolean concluido ← falso
            enquanto (!concluido)
                concluido ← radio.leMensagem(msg)
            codigo ← msg[0]
            rpm ← msg[1]
            comuta
                caso (codigo = x)
                    monitor.imprime(‘rpm’, rpm)
                    led.pisca()
                default
                    monitor.imprime(‘codigo desconhecido’)

Obs: caso queira o código-fonte, favor solicite comentando no post ou enviando um e-mail.

quarta-feira, 6 de janeiro de 2016

Programação: Sudoku & I.A.

O famoso jogo Sudoku consiste em preencher uma matriz numérica, geralmente de ordem 9x9, sem que haja repetição de números nas linhas, colunas e quadrantes (subconjuntos de 3x3 da matriz inteira), dados valores iniciais fixados em células aleatórias.

Como objetivo então, o algoritmo implementado deve prover ao usuário um jogo preenchido e válido, sem que haja alteração dos valores fixos iniciais:

Jogo inicial à esq. e resolução à dir.

Meta-Heurística
Para resolução de problemas de grande escala, de otimização e complexos, como este, o uso das meta-heurísticas pode ser ideal, desde que implementada devidamente. Para encontrar a melhor solução do problema, foi utilizada uma combinação de duas meta-heurísticas: Simulated Annealing (SA) e Tabu Search (TS).

O algoritmo SA consiste numa técnica de busca local probabilística, e se fundamenta numa analogia com a termodinâmica. Este algoritmo representa uma evolução do algoritmo Hill Climbing, de mesma natureza e, assim como no HC, podemos trabalhar sua eficiência diretamente com seus coeficientes. Como complemento, o algoritmo TS consiste em guardar as soluções de cada iteração, de acordo com o tamanho do vetor tabu, para evitar redundância nas comparações da otimização, podendo assim aumentar bastante a performance e a velocidade de convergência.

Aspectos da Solução
Sobre a meta-heurística, algumas das definições fundamentais são:
  • Forma da Solução
    • como estrutura de dados foi utilizada uma matriz 9x9 predefinida;
  • Solução Inicial
    • alocar uma matriz “inicial” com uma solução válida qualquer;
    • alocar uma matriz “solução”, que receberá o cruzamento da matriz inicial com a de entrada (denominada “espelho”);
    • assim, teremos dados concretos (quantidade válida dos números para o jogo) para aplicar as trocas necessárias;
  • Função Objetivo
    • como a solução inicial já contempla validade das linhas (sem repetição), o custo por iteração se dará pelas colunas e quadrantes, sendo o custo a quantidade de números repetidos dentro destas estruturas, separadamente;
  • Função Incremento
    • consiste em trocar aleatoriamente valores de colunas diferentes, respeitando a regra da matriz espelho (não pode haver alteração dos índices preenchidos na matriz espelho);
    • à medida que se resolve a questão do custo das colunas, “transforma-se” a matriz para o formato quadrante (quadrantes viram colunas), para se fazer o mesmo: calcular custo dos quadrantes, e o mesmo é feito com a matriz espelho (apenas a fim de verificação), contemplando assim os dois casos da otimização do problema na mesma solução;

Algoritmo
/* ------------------
 * main.cpp 
 * ------------------ */
matrizEspelho <- leMatrizEntrada();
Sudoku sudoku(matrizEspelho, tamVetorTabu, tempoMaxProcess, temperaturaInicial);
sudoku.executa();
sudoku.imprimeResultado();

/* ------------------
 * Sudoku.cpp
 * ------------------ */
Mat atual, vizinho;
atual <- inicializaMatrizSolucao();
enquanto(!fim) {
  custoAtual <- calculaCusto(atual);
  vizinho <- funcaoIncremento(atual);
  se ( insereTabu(vizinho) &&
    funcaoObjetivo(vizinho) > funcaoObjetivo(atual) &&
    random(1) < calculaSimmAnnealing(vizinho, atual)
  )
  atual <- vizinho;

  objetivo <- funcaoObjetivo(atual);
  se (objetivo < melhor)
    melhor <- objetivo;
  se ( objetivo == 0 ||
    tempo < tempoMaxProcess ||
    temperatura < congelamento
  )
  matrizSolucao <- atual;
  fim <- true;
  tempo++;
  temperatura *= CTE_TEMP;
}

Parâmetros
Foram utilizados nas execuções os seguintes parâmetros:
  • Limite Tabu: 200 (entradas no vetor; inserção cíclica)
  • Limite Tempo: 1.000.000 (iterações)
  • Temperatura: 1.000.000.000 (inicial, até chegar abaixo de “1 / temperatura”)

Observações
A otimização do problema pode variar muito dependendo da forma com que são utilizadas as variáveis de controle do SA e do TS, assim de como é implementada as funções da heurística, em especial a função de incremento. Pode-se ajustar a temperatura inicial da solução, por exemplo, ou o tempo máximo de processamento, a fim de se obter uma solução mais precisa ou uma aproximação mais rápida.

A probabilidade de convergência desta solução foi de aprox. 97,8%, pela média das execuções, ou seja: apesar das grandes chances, nem sempre a solução vai convergir a uma solução ótima, no caso, a um jogo válido. No entanto, soluções utilizando lógica convencional, força bruta ou mesmo outras meta-heurísticas podem chegar a levar minutos - ou muito mais que isso - para prover um resultado, enquanto a solução apresentada levou um tempo médio de execução de 0.534 segundos, se saindo muito a frente das demais.

terça-feira, 5 de janeiro de 2016

Jogos: C# & XNA


O XNA é um framework gratuito da Microsoft direcionado ao desenvolvimento de jogos, para Windows PC, Xbox 360 e Windows Phone 7. Permite criar uma infinidade de jogos, com praticidade e usando uma biblioteca bastante completa e robusta. É ideal para estudantes, pequenos desenvolvedores, criadores independentes e também para quem quer apenas brincar e conhecer um pouco da programação para games.

Para me iniciar neste âmbito, criei um jogo bastante simples, mas um tanto desafiador. O nome do jogo é Touchdown, e representa uma partida de futebol americano, simulando a situação em que o jogador com a posse da bola vai fazer o touchdown (isto explica o nome). Assim, o jogo consiste em fazer o personagem principal cruzar a endzone, passando antes por todos os jogadores do time adversário.

O jogo conta com animações dos personagens e do cenário, tela inicial (menu), efeitos sonoros e música de fundo (AC/DC, por sinal!), além de outros elementos básicos de jogos em geral e adendos inerentes ao tema/jogabilidade.

Ao decorrer do jogo, o personagem pode sofrer danos, de acordo com a aproximação do(s) adversário(s) ao jogador e tempo de jogo. No entanto, o personagem conta com itens auxiliares no campo que atuam como incremento de vida e diminuição da velocidade dos inimigos. Ao chegar ao fim da "vida" o jogo é finalizado, assim como quando o jogador chega ao seu objetivo, ambos os casos são informados através da mensagem na tela.

Alguns dos desafios no desenvolvimento do jogo foram a manutenção do sincronismo do plano de fundo com o movimento dos jogadores, a sintonia de movimento e velocidades, a detecção de colisão aprimorada, cálculos de interação posicional, a manutenção de estados do jogo/personagens, e especialmente, no meu caso, a criação do design do jogo! (um fator importantíssimo, mas raramente uma das habilidades de um programador: sorte a minha que tive algumas - apenas algumas - ajudinhas de uma noiva designer...).

Algumas das imagens do jogo:


      


O algoritmo é extenso o suficiente para não ser postado aqui, caso tenha curiosidade ou dúvidas quanto a programação nesta "modalidade" solicite o código-fonte através de meu e-mail que poderei fornecê-lo. Como dito anteriormente, irei me aprofundar na questão "jogo x programação" em breve em um post específico para isto.

Jogos: Android, C++ & NDK

Recentemente desenvolvi um jogo para Android, utilizando a linguagem C++, em conjunto com a IDE CodeBlocks e plataforma Windows. Como sabemos, os aplicativos para Android são por natureza baseados em Java, por isso, foi preciso uma "conversão" do produto do desenvolvimento para a sua execução correta.

Ferramentas
Os elementos gerais necessários para a adequação da plataforma e ambientação para o desenvolvimento foram:

  • Android SDK
    • é o core do Android, contendo aplicação, máquinas virtuais para emulação, bibliotecas, drivers, arquivos de sistema e demais ferramentas
    • para uso deste, foi baixado o Android Studio do site: https://developer.android.com/sdk/
  • NDK - Native Development Kit
    • ferramenta utilizada para realizar a compilação dos fontes do jogo
    • a versão baixada foi a r10e do site https://developer.android.com/ndk/
  • SDL - Simple Directmedia Layer
    • possui bibliotecas específicas para desenvolvimento do jogo e tratamento de imagens
    • a versão baixada foi a v2.0 do site https://www.libsdl.org/
    • também foi necessário baixar separadamente a biblioteca “SDL_image”, que possui módulos que não vêm junto com o pacote SDL
  • Apache Ant
    • ferramenta utilizada para realizar o deploy da aplicação, ou seja, gerar o apk executável que será instalado ou emulado em um dispositivo com Android
    • a versão baixada foi a v1.9.6 do site https://ant.apache.org/

Sobre o Jogo
O nome do jogo é Kingdom Blizzard, e é um jogo do tipo estratégico, estilo “capture the flag”,
englobando diversos aspectos de jogos neste formato. O tema é baseado nos reinos da era
medieval, e o ambiente é de nevasca, por isso o nome “Kingdom Blizzard”.

O objetivo é andar com o rei (personagem principal), passando pelos obstáculos e inimigos, até chegar do outro lado e capturar a bandeira. No caso de colisão com inimigos, o personagem volta à posição inicial do jogo. Não é possível se sobrepor (passar por cima) dos obstáculos no campo (icebergs). A moeda posicionada em um espaço do campo, se capturada pelo jogador, dará a ele um tempo de fuga, em que os inimigos ficarão congelados. O movimento do personagem se dá por toques na tela, por isso é necessário que o aparelho possua touchscreen.

Nas imagens do jogo a seguir é possível ter uma noção do esquema gráfico. Por ser estática a imagem não será possível ver a animação do jogo: os objetos e o personagem possuem quadros que mudam constantemente, e o personagem principal se vira ao lado que for direcionado no movimento, além dos inimigos que ficam o tempo todo indo de um lado para o outro horizontalmente, com a intenção de colidir com o personagem. As únicas peças estáticas no jogo são o fundo e os obstáculos (icebergs).

Obs: Não se enganem, este jogo não foi desenvolvido com o intuito de bombar no Google Play, mas apenas de conhecer, uma espécie de "hello world" nestas águas do Android.

Início/Término do jogo


Emulação
Inicialmente, o jogo foi testado através de emuladores do Android SDK. Por questões
de performance e adequação de hardware, este emulador teve de ser substituído. O programa
utilizado então para esta finalidade foi o BlueStacks, um emulador que atende bem pelo propósito. O jogo foi compilado oficialmente para rodar em Android versão (até) 5.0.1, e foi testado em alguns dispositivos (tablets e smartphones, com versões 4.x e 5.x de Android), não apresentando problemas.

Ambiente
A preparação do ambiente para o desenvolvimento foi provavelmente a etapa mais difícil (e bota difícil nisto), visto que a adequação e interação entre as variadas ferramentas para o desenvolvimento nem sempre demandam uma instalação e configuração padrão, apresentando muitas vezes mal comportamento como problemas e instabilidade, além de que possivelmente seja preciso baixar arquivos, bibliotecas e releases específicas de ferramentas, de acordo com o problema. No entanto ressalto aqui que a adequação do ambiente é fundamental para a construção do projeto e funcionamento da aplicação.

Basicamente, os passos seguidos foram:
  1. Descompactação e instalação das ferramentas listadas
  2. Extração da pasta principal “android-project”
  3. Criação da pasta assets na pasta principal para armazenar texturas
  4. Alteração do arquivo “Android.mk” da pasta android-project/jni
    • Alterar nome do programa principal e adicionar demais criados
  5. Criação de link simbólico da pasta SDL dentro da android-project/jni
  6. Copiar a pasta do SDL_image para dentro da pasta SDL/include
  7. Copiar as libs jpeg-9, libpng-1.6.2, libwebp-0.3.0, tiff-4.0.3 e zlib-1.2.8 para a android-project/jni e mapeá-las no arquivo SDL2_image/Android.mk
  8. Configurar variáveis de ambiente no Windows para contemplar os diretórios:
    • Variável Path
      • {dir}\android-ndk-r10e;
      • {dir}\apache-ant-1.9.6\bin;
      • C:\Users\{user}\AppData\Local\Android\sdk;
      • C:\Users\{user}\AppData\Local\Android\sdk\tools;
      • C:\Users\{user}\AppData\Local\Android\sdk\build-tools;
      • C:\Users\{user}\AppData\Local\Android\sdk\platform-tools
    • Criar variável ANDROID_SDK_HOME:
      • C:\Users\{user}\AppData\Local\Android\sdk

Depois do ambiente configurado, criar programa principal main.cpp na pasta android-project/jni/src, com declaração das bibliotecas a serem utilizadas (inclusive a SDL_image.h). Algumas configurações corretivas, avançadas e adicionais podem ser feitas, por exemplo, nos arquivos da pasta jni:
  • Application.mk: contém informações do emulador e versão do Android
  • src/Android.mk: contém declarações das pastas da infra (SDL, programas e bibliotecas)
Feito isto, para compilação utilizamos o comando ndk-build no cmd. Após a compilação, o deploy é feito através do comando ant debug também no cmd. Com isto, o executável será gerado na pasta android-project/bin. No BlueStacks, a forma de execução foi apenas clicar no arquivo gerado, e este então é instalado no App e pode ser executado pela interface.

Mídia e Texturas
Dentre os elementos que compõem o jogo, temos as texturas, que compõem a estética do jogo. Demais elementos como audio e efeitos visuais (como o flip dos sprites, por exemplo) não foram aprofundados por questões de suporte à plataforma, que são dificilmente encontrados. No mais, os sprites, textos e imagens que constituem a estética do jogo foram em sua maioria encontrados na internet ou produzidos por programas de manipulação de imagem (PhotoFiltre, Adobe Photoshop, etc). Dentre as texturas encontradas no jogo, temos:
  • Sprite do personagem
  • Sprites dos inimigos
  • Sprite de objetos do jogo (no caso, a moeda)
  • Objeto iceberg (obstáculo)
  • Objetos bandeira e portal (posições inicial e final, respectivamente)
  • Textura do plano de fundo

Algoritmo
O programa do jogo desenvolvido envolve aspectos fundamentais recorrentes de jogos, tais como efeitos de animação (texturas e sprites), sensação de movimento, detector de colisão, jogabilidade (herói, inimigo, obstáculos), níveis de dificuldade distintos durante o jogo (no caso, conforme aproximação do objetivo) e etapas principais: início, reinício e fim de jogo (no caso de vitória).

O algoritmo em si não será postado aqui pois é um pouco extenso. Pretendo em breve explicitar o funcionamento de um jogo básico (lógica/algoritmo) de forma genérica, em um post específico.

Obs: este jogo não foi desenvolvido com pretensões comerciais, assim não será disponibilizado no Google Play

terça-feira, 14 de julho de 2015

Robótica: PID Controller

O controle PID - ou controle proporcional integral derivativo - é uma técnica proveniente da engenharia e pode assumir um papel muito sutil e relevante na robótica. Em suma, seu objetivo é prover uma forma de manutenção de estados com retroalimentação, onde a partir de uma comparação entre o estado esperado na saída com o seu estado real (entrada), o controlador recebe os dados e atua de forma a fazer com que a saída seja alterada para o seu valor esperado, no melhor caso, ou o mais próximo possível, ou seja: realizar ciclos de manutenção de valor aproximado baseado em erro.

É composto por 3 coeficientes: ganho proporcional, ganho integral e ganho derivativo, coeficientes que serão empregados para a obtenção de valores mais satisfatórios. Cada coeficiente atuará em uma forma específica de correção:

  • P :: correção proporcional ao erro – a correção a ser aplicada ao processo deve crescer na proporção que cresce o erro entre o valor real e o desejado.
  • I  :: correção proporcional ao produto erro x tempo – erros pequenos mas que existem há muito tempo requerem correção mais intensa.
  • D :: correção proporcional à taxa de variação do erro – se o erro está variando muito rápido, esta taxa de variação deve ser reduzida para evitar oscilações.

A equação mais usual do PID é a apresentada no esquema a seguir:

Equação PID convencional

Onde KpKi Kd são os ganhos das parcelas P, I e D, e definem a intensidade de cada ação. Vale lembrar que esta equação pode variar dependendo do fabricante ou do caso a ser aplicado.

O gráfico a seguir representa um exemplo canônico da aplicação do controle PID:

Resultado de uma aplicação do CPID

Vale o projeto citado anteriormente como exemplo de aplicação: movimentação de um robô envolvendo sensores e motor. Esta técnica foi essencial na tarefa de fazer o robô andar seguindo linha, aprimorando o controle, suavidade e estabilidade no percurso, especialmente na realização das curvas.

Robótica: Line Follower Robot

Este foi um projeto acadêmico muito interessante em que participei, contemplando a robótica como ponto central, onde tínhamos que desempenhar no robô as tarefas de andar seguindo linha e desviando de objetos. Além destes problemas, havia a questão de sua parte elétrica e mecânica, envolvendo escolha de peças, bateria, fabricação do chassi, entre outros. Além disto, a necessidade de adaptar o robô para, por exemplo, subir rampas e andar por superfícies com anormalidades e interrupções na linha representavam outro nível de problema. Por fim, a forma de locomoção foi o ponto de partida para as decisões por vir, e foi dada preferência para um robô movido por esteiras.

Para desempenhar as tarefas do robô e sua interação com as outras partes, utilizamos os seguintes componentes:


  • Arduino Mega
Placa projetada com microcontrolador e suporte a entrada e saída, digital e analógica, para leitura e manipulação de dados e componentes. Utilizada para gerir e executar a lógica do robô em tempo real. É programada através da interface instalada no computador e trabalha com a linguagem Arduino, baseada em C/C++. Possui suporte a alimentação externa e interface USB, via que fornece comunicação com computador. Este modelo foi escolhido em alternativa ao Arduino Uno por ser maior, contendo suficientemente mais portas (digitais e analógicas) e mais memória.
Arduino Mega

  • Motor Shield
Utilizado juntamente com o Arduino para controlar a velocidade dos motores de corrente contínua. Possui canais de controle e um chip baseado em Ponte H, permitindo uma tensão ser aplicada em qualquer direção para ambos os motores. Suporta uma corrente de saída de 600mA e tensão de 4,5 a 36V.

MotorShield L298N


  • Sensores de Refletância
Sensores analógicos utilizados para detectar linha no percurso. Trabalham com led infravermelho e fototransistor, emitindo luz e detectando seu reflexo no solo, sendo possível assim medir intensidade do sinal captado e interpretá-lo como sendo ou não uma linha.
No projeto foram utilizados 5 sensores, sendo destes 3 sensores posicionados no centro, para ajuste de precisão do percurso reto, e 2 sensores subjacentes (esquerda e direita), para detecção de curva através das bordas na linha.
Modelo TCRT-5000

  • Sensores de Ultrassom
Sensores digitais utilizados para detectar objeto no percurso. São os "olhos" do robô (e parecem mesmo). Trabalham com onda sonora, que será emitida e rebatida pelo objeto mais próximo, retornando ao receptor. O cálculo de distância então irá derivar do tempo de emissão e recebimento do sinal.
No projeto foram utilizados 3 sensores, posicionados nos pontos central e laterais, de forma a trabalhar como uma visão periférica, primeiramente detectando um objeto a frente e posteriormente utilizando os laterais para contorná-lo, a uma distância predefinida. Uma alternativa seria utilizar apenas um deste sensor e direcioná-lo para as laterias utilizando um servo-motor convencional.
Modelo HC-SR04


Como utilizamos componentes que realizam algumas funções complexas, utilizamos algumas bibliotecas próprias, como a QTRSensors, do sensor de linha, e a NewPing, do sensor ultrassônico.

O robô se movimenta pelo acionamento de seus dois motores, comandados e controlados pelo Arduino e pela sua interação com o componente MotorShield, ao qual os motores são conectados. A direção é definida através da diferença da velocidade entre os dois motores. Para a realização de uma curva para a direita, por exemplo, o robô deve aumentar a velocidade do motor da esquerda e diminuir do motor da direita, simultaneamente, a fim de iniciar um movimento de rotação. Também, para girar em torno do próprio eixo, pode-se aplicar uma mesma velocidade aos dois motores, contanto que girem em sentidos opostos. A correção de velocidade dos motores, estratégia para realização de curvas e seguir linha, entre outros, são tarefas realizadas pelo controle PID, que será explicado mais detalhadamente em outro post.

O algoritmo no Arduino é constituído basicamente de 2 etapas: Setup e Loop. O Setup é a etapa de inicialização dos programas, setando as variáveis iniciais e realizando as ações primárias do robô, como por exemplo a calibragem dos sensores, que consiste numa varredura em 180º graus do robô, girando em torno do seu próprio eixo, para realizar a primeira detecção de linha e evoluir sua precisão, de acordo com fatores de ambiente. O Loop é onde definimos toda a ação em tempo real do robô, ou seja: validações, tratamentos e demais ações cíclicas que são realizadas no decorrer do percurso.

Para o desvio de objetos, a cada ciclo emitimos o sinal ultrassônico e lemos a distância até o objeto mais próximos, considerando um limite de distância. Nesta situação de proximidade, a decisão a ser tomada pelo robô primeiramente é parar e olhar os lados (através dos sensores ultrassônicos laterais), a fim de buscar o lado mais livre para percorrer, e tomar esta direção. Para realizar este desvio, o robô realiza um percurso predefinido em torno do objeto, andando primeiramente para o lado mais livre até se distanciar o suficiente do objeto, e assim, dobrar para o lado do objeto até cruzar sua posição e poder contorná-lo com mais uma curva, até que o robô ache a linha de volta.

No caso de haver interrupção na linha, o robô ativa um estado de sonda, anda uma certa distância para frente, procurando pela linha, e, caso não encontre, ele desfaz este caminho e o refaz, de forma sinuosa, até encontrar a linha nas proximidades do caminho.

Obs: fluxogramas, algoritmos, fotos do bicho e demais materiais não serão fornecidos por questões autorais.

quinta-feira, 9 de julho de 2015

Visão Computacional: Color Tracking, Line Detection & Shapes

Os programas que resultam nas imagens apresentadas abaixo foram desenvolvidos na linguagem C++, utilizando a IDE Eclipse Luna e o framework OpenCV, já citado anteriormente. Houveram várias tentativas de desenvolvimento em outras IDEs, tais como Visual Studio, NetBeans, Eclipse (Java) e DevC++ (que por fim é o menos recomendado), e também utilizando a linguagem Python. Porém, a que se sobressaiu e apresentou melhores resultados foi o C++ com Eclipse Luna, tanto pela estabilidade quanto pelo material de apoio que pode ser encontrado em fóruns e demais sites.

As bibliotecas mais usadas nos programas são a highgui e a imgproc. A highGUI é responsável por ações como a manipulação de janelas e seus componentes, sendo que estes podem interagir diretamente com o tratamento da imagem, pela leitura e gravação de arquivo em disco e em memória e pela leitura de frames da câmera. a imgProc é responsável pelo processamento de imagens, possuindo definições e funções que auxiliam o desenho em imagens e algumas formas de detecção por padrões.

Sobre os algoritmos desenvolvidos, existem trechos comuns à maioria dos programas de tratamento de imagens. Um deles é a conversão da imagem capturada de RGB para HSV (hue, saturation, value - ou - matriz, saturação e valor), pois este controle é mais eficiente e direto para se manipular o filtro (com iluminação e contraste, por exemplo) do que alterar valores RGB na matriz da imagem. Para se fazer a diferenciação das cores, por exemplo, o valor a ser alterado é relacionado à matriz, quando os demais servem para dar mais precisão na filtragem.

Esquema de sistema de cores HSV

Os algoritmos foram aplicados sobre a exibição da câmera do meu computador, em tempo real. Em todos os programas, o processamento foi mostrado em três janelas: uma com a imagem original da webcam, outra com o filtro aplicado (apenas tratamento, preto e branco, exibindo área ignorada e detectada respectivamente) e outra somando a imagem original com o desenho programado a partir da detecção (na detecção de cores, por exemplo, é apontado localização e nome da cor e, na detecção de formas, é desenhado a forma em torno do objeto).

Veja algumas imagens do resultado dos algoritmos:

Rastreamento de Cores

Detecção de Formas

Detecção de Linha

Neste projeto, alguns algoritmos foram utilizados no intuito de auxiliar atividades envolvendo robótica, através de monitoramento em tempo real, interpretação de informações e as possíveis interações com o robô. Uma adaptação interessante seria embarcar esta aplicação em um celular, por exemplo, possibilitando seu funcionamento portátil e uma transmissão online.

Nota: caso tenha interesse em estudar/utilizar os códigos-fonte, entre em contato por e-mail.

Programação: [C++] Templates, Friend Class & Operator Overloading

A linguagem C/C++ é indiscutivelmente a mais versátil de todas, dotada de recursos de baixo e alto nível e dinâmica para quaisquer tipos de programação. Além de ser a linguagem mãe de S.O.s como o Linux e o Windows, é possível trabalhar com orientação a objetos, listas, funções inline, assembly e mesmo reprogramar a própria linguagem (um dos tópicos que veremos a seguir...).

Trabalhando com classes em C++, podemos utilizar alguns recursos dinâmicos muito úteis como templates e operator overloading (sobrecarga de operadores).

Templates permitem-nos criar funções e classes genéricas. Pode ser usado, por exemplo, para definir containers que armazenam coleções de diferentes tipos de objetos. Veja o exemplo a seguir:
int max(int a, int b) { return a > b ? a : b; }
float max(float a, float b) { return a > b ? a : b; }
string max(string a, string b) { return a > b ? a : b; }
template <class X>X max (X a, X b) { return a > b ? a : b; }

main: 
    max(3, 7); 
    max("a", "b");
Este código permite utilizar uma única função para comparar diferentes tipos de variáveis ou objetos. Mas também podemos utilizar uma outra técnica, até mais sutil, para este tipo de problema...

A Sobrecarga de Operadores é uma técnica de programação utilizada para redefinir o significado dos operadores naturais da linguagem. Vamos supor que temos o objeto Item, e, declarados dois objetos, queremos comparar um com o outro em relação ao preço. Neste caso temos basicamente dois problemas: como saber que atributo comparar? Como fazer a comparação deste atributo naturalmente? Na sobrecarga de operadores, todos os operadores podem ser sobrescritos. Para este problema, faremos a sobrecarga do operador "<". Veja o código a seguir:
class Item {
public:
    Item(int i, float p) : id(i), preco(p) {};
    bool operator< (Item item) const{ return preco < item.preco; }
    int id;
    string nome;
    float preco;
};
int main() {
    Item i1(10,1);
    Item i2(20,2);
    (i1 < i2) ? cout << i1.id : cout << i2.id;
}
O programa então irá comparar o preço dos itens pelo preço e informará o respectivo id. Vamos apresentar agora algo ainda mais elaborado...Neste caso, se eu quisesse passar diretamente apenas o objeto Item para o operador cout, ele não saberia qual informação exibir. Para isto, podemos utilizar outra técnica de sobrescrita, que se trata na verdade da reescrita do operador "<<" interno da biblioteca ostream. Normalmente, a restrição de acesso a membros privados e protegidos da linguagem não nos permite fazer esta alteração, a menos que declaremos a classe ou membro como Friend. Veja o exemplo a seguir:
class Item {
public:
    Item(int i, string n, float p) : id(i), nome(n) , preco(p) {};
    bool operator< (Item item) const{ return preco < item.preco; }
    friend ostream& operator<<(ostream& out, Item& item)
        { out << item.nome; return out; }
    int id;
    string nome;
    float preco;
};
int main() {
    Item i1(10,"A",1);
    Item i2(20,"B",2);
    (i1 < i2) ? cout << i1 : cout << i2;
}
Com isto, ao invés de retornarmos o id  (definido manualmente), o programa automaticamente retornará o nome do item para sua saída padrão.

quarta-feira, 8 de julho de 2015

Visão Computacional: Concepts & OpenCV

Podemos descrever a visão computacional como "a ciência e tecnologia das máquinas que enxergam". A partir da captura de imagens - ou vídeo - é possível extrair infinitas possibilidades de informações. Esta técnica é largamente utilizada no meio industrial, em sistemas artificiais, biotecnologia, modelagem de ambientes, organização de informação, interação (atrelado a interação humano-computador) dentre outras. Ela atua especialmente em tarefas como reconhecimento, detecção, identificação, tratamento de dados e de imagem em si.

Seu fluxo funcional se baseia na seguinte sequência de tarefas: aquisição de imagem, pré-processamento, extração de características, detecção e segmentação, e por fim, processamento de alto nível. A partir destes - ou parte destes - temos uma gama de aplicações, tais como computação gráfica, processamento de imagens, aprendizagem de máquina, reconhecimento de padrões, sistemas de visão entre outros, além de ser uma forte aliada à robótica.

O OpenCV é um framework open-source multiplataforma para desenvolvimento de aplicativos na área de visão computacional. Foi originalmente desenvolvido pela Intel em 2000 e é bastante utilizado no meio acadêmico e também comercial. Sua biblioteca possui módulos de processamento de imagens e vídeo I/O, estrutura de dados, álgebra linear, interface gráfica (GUI), sistema de janelas independentes, controle de mouse e teclado, além de mais de 350 algoritmos de visão computacional como: filtros de imagem, calibração de câmera, reconhecimento de objetos, análise estrutural entre outros, podendo inclusive trabalhar com estas funções paralelamente e com processamento em tempo real.

O framework foi originalmente desenvolvido em C/C++, mas atualmente é suportado nas linguagens Java, Python e Visual Basic, sendo possível desenvolver para diversas plataformas, inclusive Android e iOS. Algumas de suas aplicações englobam interface humano-computador, identificação de objetos, sistema de reconhecimento facial e de movimentos, robôs móveis, reconstrução 3D entre outros.

Alguns exemplos de utilização da visão computacional atualmente:

Reconhecimento facial

 
 
Jogos e simulação virtual

Monitoramento urbano

Tratamento de imagens

A visão computacional contribui com a tecnologia e a sociedade à medida que evolui e se especializa. Hoje ela tanto auxilia e diverte seus usuários quanto pode salvar vidas. Por exemplo, em um evento esportivo, com milhares de espectadores, a visão computacional pode ajudar a monitorar a multidão, de forma a evitar que um tumulto qualquer faça as pessoas ficarem bloqueadas, com ferimentos ou até mesmo vítimas fatais. Não há limites para a aplicação da visão computacional.

Novas idéias surgem a cada dia e assim novos desafios dão abertura a inovações e descobertas, estimulando a evolução da visão computacional e difundindo seu uso em todas as áreas possíveis.

Música: Blues Rock

Se você começou a ler por aqui, eis uma trilha sonora para acompanhar o restante das leituras.

Há aproximadamente 2 anos fui chamado a contribuir em uma rede de sites de rádio. Desta rede, 3 rádios possuem um canal de Blues Rock que dirijo: RadioTunes, JazzRadio e RockRadio, tendo a JazzRadio ganhado prêmio como melhor rádio da internet em 2014, pela JazzWeek.

Este canal contempla o blues das vertentes mais voltadas ao rock e à melodia, com muitos solos, algumas baladas, honky tonks, muitos clássicos, algumas novidades e - muito recorrentemente no mundo da música - várias versões de clássicos, como Bo Diddley, Freddie King, Howlin' Wolf entre outros (incluindo eles mesmo). Alguns representantes dos variados gêneros: ZZ Top, Johnny Winter, Rory Gallagher, Jimmy Dawkins, Tommy Castro, Gary Moore, Rick Derringer, Stevie Ray Vaughan, Eric Clapton, Alvin Lee, Joe Bonamassa, etc.

O site possui também a versão mobile e assinatura Premium, para reprodução de qualidade excelente das músicas e sem comerciais.

Deleite-se ao som do Blues!

Nota: As músicas são negocialmente autorizadas pelas gravadoras, pagando-se royalties pelo stream.