O Mars Rover Tupiniquim aterrisa em Londres!

A edição de maio da revista britânica MagPi publicou uma matéria muito legal sobre meu Projeto “Mars Rover Tupiniquin”. Para quem não conhece, a MagPi é a revista oficial do RapsberryPi. Você pode baixar grátis as edições da revista em PDF.

A edição de maio (45) pode ser baixada no link: https://raspberrypi.org/magpi-issues/MagPi45.pdf

Espero que gostem!

(A repórter trocou o meu nome por “Marcio”, mas está valendo! 😉

IMG_0471

IMG_0472

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebook

Saludos desde el sur del mundo! 😉

Um abraço e obrigado

Marcelo

Robô explorador de labirintos, utilizando Inteligência Artificial com Arduino

Esta é a segunda e última parte de um projeto mais complexo, que explora a potencialidade de um robô seguidor de linha. Nesta etapa, aplicaremos conceitos de inteligência artificial na exploração de labirintos, implementando algoritmos que nos ajudarão a encontrar o caminho da saída mais curto e rápido.

Este projeto foi desenvolvido a partir de meu último tutorial: “Rex”, um robô que nunca perde a linha! Depois de se conseguir desenvolver um robô com capacidade para seguir linhas, o próximo passo natural é sem dúvida, dar-lhe algum grau de inteligência. Assim, nessa etapa, o nosso querido “Rex, o Robô” tentará encontrar uma forma de escapar de um “labirinto” tomando o caminho mais curto e o mais rápido possível.
A propósito, Rex odeia o Minotauro….. 😉

A maioria dos labirintos, por mais complexa sua concepção possa parecer, foram essencialmente formados a partir de uma parede contínua com muitos cruzamentos e bifurcações. Se a parede circundante do objectivo final de um labirinto está ligado ao perímetro do labirinto na entrada, o labirinto sempre poderá ser resolvido mantendo-se uma mão em contacto com a parede, não importando os muitos desvios que possam existir. Estes labirintos “simples” são conhecido como “Simply-connected” ou “perfeitos”, ou em outras palavras, que não contêm loops.

Voltando ao nosso projeto, ele será dividido em duas partes:

1. (Primeira passada): O robô encontrará o seu caminho para sair de um labirinto perfeito desconhecido. Não importa onde você o colocar dentro do labirinto, ele sempre encontrará uma “solução de saída”.

2. (Segunda passada): Uma vez que o robô encontrou uma possível solução para sair do labirinto, ele deve otimizar-la, encontrando o caminho mais curto para ir do início ao fim.

O vídeo abaixo, mostra um exemplo do robô encontrando seu caminho para sair do labirinto (chegar “ao final”). Na primeira vez que o robô explora o labirinto, é claro que vai perder muito tempo “pensando” sobre o que fazer em todo e qualquer cruzamento. Para testar as possibilidades, ele tomará vários caminhos errados e becos sem saída, o que faz com que ele escolha caminhos mais longos e execute várias  “marcha-rés” desnecessárias.

Durante esta”1ª passada”, o robô irá acumulando experiências, “tomando notas” sobre os diferentes cruzamentos e eliminando os atalhos ruins. Em sua “segunda passada”, o robô irá direta e rapidamente ao final do labirinto sem qualquer erro ou dúvida. Ao longo deste tutorial,  exploraremos em detalhes como fazê-lo:

LISTA DE MATERIAIS:

A lista de materiais é basicamente a mesmo que usamos com robô seguidor de linha, exceto que foram incluídos 2 Sensores adicionais para uma melhor precisão na detecção da cruzamentos a esquerda e a direita. O preço final do robô, continua barato (cerca de US$85,00):

  • Corpo (pode ser adaptado para as suas necessidades):
    • 2 quadrados de madeira (80x80mm)
    • 3 Grampos de papel
    • 2 rodas de madeira (diâmetro: 50 mm)
    • 1 roda “solta” (Caster)
    • 9 Elásticos
    • Fita 3M “Command”
    • Articulações plásticas para fixação do sensor
  • Protoboard e fiação
  • 2 conjuntos de baterias (4XNi-metal hidreto) –  5V cada conjunto
  • 2 Servos de Rotação Contínua (SM-S4303R)
  • Arduino Nano
  • Módulo Bluetooth  HC-06
  • 5 sensores x Linha (TCRT5000 4CH Infrared Linha Pista Seguidor Módulo Sensor + 1 sensor de Pista independente)
  • 2 sensores ZX03 (baseado no TCRT5000) Reflective Infrared Sensors (saída analógica)
  • 1 LED
  • 1 Botão

ALTERAÇÕES AO CORPO DO ROBÔ

Retirar o conjunto original dos 5 Sensores e fixar os novos sensores reflectivos aos extremos esquerdo e direito da barra de suporte plástica.

É aconselhável manter os 7 sensores o mais alinhado possível.

Instalação e testes dos novos sensores

A nova matriz de 7 sensores, é montado de forma a que os 5 originais sejam utilizados exclusivamente para o controlo PID e detecção da “linha completa” (explicado mais adiante) e que os novos 2 sensores somente para a detecção dos cruzamentos a esquerda e a direita.

Como uma rápida revisão, vamos lembrar como os 5 sensores originais “digitais” trabalham:

Se um sensor está centrado em relação à linha preta, este irá produzir um sinal HIGH. Por outro lado, o espaço entre os sensores devem ser calculados de tal modo a permitir que dois sensores possam cobrir a largura total da linha preta produzindo assim um sinal HIGH simultaneamente em ambos os sensores.

Como os 2 novos sensores “analógicos” trabalham:

Se um dos sensores está centrado em relação à linha preta, o resultado observado na saída do ADC interno do Arduino será geralmente menor que “100” (lembre-se que o ADC produz uma saída que vai de 0 a 1023). Sobre superfícies mais claras, o valor de saída deverá ser maior (500 a 600 sobre papel branco, por exemplo). Estes valores devem ser testado em diferentes situações de luz de superfície e de materiais para se definir a constante LIMIAR (“THRESHOLD”) correta a ser usada (ver o quadro aqui).

Abaixo, o diagrama mostrando os componentes conectados ao Arduino:

Maze Solver Circuit

Olhando o código do Arduino, cada um dos sensores será definido com um nome específico:

// LFSensor more to the Left is "0"
const int lineFollowSensor0 = 12; //Using Digital input
const int lineFollowSensor1 = 18; //Using Analog Pin A4 as Digital input
const int lineFollowSensor2 = 17; //Using Analog Pin A3 as Digital input
const int lineFollowSensor3 = 16; //Using Analog Pin A2 as Digital input
const int lineFollowSensor4 = 19; //Using Analog Pin A5 as Digital input

const int farRightSensorPin = 0; //Analog Pin A0
const int farLeftSensorPin = 1; //Analog Pin A1

 

Recordando, as possíveis combinações de saída para a matriz de 5 sensores original do seguidor de linha são:

  • 0 0 0 0 1
  • 0 0 0 1 1
  • 0 0 0 1 0
  • 0 0 1 1 0
  • 0 0 1 0 0
  • 0 1 1 0 0
  • 0 1 0 0 0
  • 1 1 0 0 0
  • 1 0 0 0 0

Com a adição dos 2 novos sensores, as suas saídas possíveis são:

  • Sensor Esquerdo: Saída Analógica maior ou menor do que o valor definido de THRESHOLD
  • Sensor Direito: Saída Analógica maior ou menor do queo valor definido de THRESHOLD

A fim de armazenar os valores de cada um dos sensores uma variável tipo matriz  (Array) é criada para os sensores digitais originais 5:

int LFSensor[5]={0, 0, 0, 0, 0};

E duas variáveis do tipo inteiras para os 2 novos sensores analógicos:

int farRightSensor = 0;
int farLeftSensor = 0;

As variáveis serão constantemente actualizadas dependendo do estado de cada um dos sensores:

LFSensor[0] = digitalRead(lineFollowSensor0);
 LFSensor[1] = digitalRead(lineFollowSensor1);
 LFSensor[2] = digitalRead(lineFollowSensor2);
 LFSensor[3] = digitalRead(lineFollowSensor3);
 LFSensor[4] = digitalRead(lineFollowSensor4);
 
 farRightSensor = analogRead(farRightSensorPin);
 farLeftSensor = analogRead(farLeftSensorPin);

Possuindo 5 sensores, como se viu no projeto do Robô seguidor de linha, se permite a geração de uma “variável de erro” que ajudará a controlar a posição do robô sobre a linha. Essa variável de erro será mantida e uma nova denominada “mode” será incluída para saber se o robô está seguindo uma linha, sobre uma linha contínua, uma intersecção ou fora da linha.

Esta variável “mode” será usada também com os novos sensores de esquerda e direita. Consideremos que os novos sensores da esquerda e da direita geraram 3 estados possíveis: H (maior do que THRESHOLD), L (menor do que oTHRESHOLD) e X (irrelevante). Para as saídas digitais, manteremos “0”, “1” e também introduziremos o “X”:

Intersection

X 1  1  1  1  1 X ==> mode = CONT_LINE; error = 0;
H 0 X X X X L ==> mode = RIGHT_TURN; error = 0;  (Veja o exemplo na imagem acima)
L X X X X 0 H ==> mode = LEFT_TURN; error = 0;
X 0 0 0 0 0 X ==> mode = NO_LINE; error = 0;
H 0 0 0 0 1 H ==> mode = FOLLOWING_LINE; error = 4;
H 0 0 0 1 1 H ==> mode = FOLLOWING_LINE; error = 3;
H 0 0 0 1 0 H ==> mode = FOLLOWING_LINE; error = 2;
H 0 0 1 1 0 H ==> mode = FOLLOWING_LINE; error = 1;
H 0 0 1 0 0 H ==> mode = FOLLOWING_LINE; error = 0;
H 0 1 1 0 0 H ==> mode = FOLLOWING_LINE; error = -1;
H 0 1 0 0 0 H ==> mode = FOLLOWING_LINE; error = -2
H 1 1 0 0 0 H ==> mode = FOLLOWING_LINE; error = -3;
H 1 0 0 0 0 H ==> mode = FOLLOWING_LINE; error = -4;

Assim, a implementação da lógica acima na função:

void readLFSsensors()

irá retornar as variáveis “mode” e “error” que serão utilizados na lógica do programa.

É importante testar a lógica dos sensores antes de seguir com o projeto. A função de teste abaixo está incluída no código e poderá ser usado para fins de ensaio:

void testSensorLogic(void) 
{
    Serial.print (farLeftSensor);
    Serial.print (" <== LEFT RIGH==> ");
    Serial.print (farRightSensor);
    Serial.print (" mode: ");
    Serial.print (mode);
    Serial.print (" error:");
    Serial.println (error);
}

Resolvendo o labirinto – a regra da mão esquerda

Como discutido na introdução deste tutorial, a maioria dos labirintos são essencialmente formados a partir de uma parede contínua com muitos cruzamentos e desvios.

Pesquisando na Wikipedia, aprendemos que “o seguidor da parede” é o algorítmo mais conhecido para percorrer labirintos. É também conhecido como “regra da mão esquerda” ou a “regra da mão direita”. Se o labirinto é simplesmente conectado, isto é, todos as suas paredes são ligadas entre si, mantendo-se uma mão em contato com uma das paredes do labirinto é garantido que chegará a uma saída. Usaremos aqui a “Regra da mão esquerda”.

Em resumo, a regra da mão esquerda pode ser descrito como:

  • Coloque a mão esquerda na parede.
  • Comece a andar para a frente
  • Em cada cruzamento, e ao longo do labirinto, manter a sua mão esquerda tocando na parede à sua esquerda.
  • Eventualmente, você vai chegar ao final do labirinto. Você provavelmente não vai seguir o caminho mais curto e mais direto, mas você chegará lá.

Portanto, a chave aqui é identificar as intersecções, definindo que medidas tomar com base nas regras acima. Especificamente no nosso tipo de labirinto em 2D , podemos encontrar 8 tipos diferentes de intersecções :

Intersections types

Olhando a imagem acima, podemos perceber que as possíveis ações nos cruzamentos são:

  1. Em um “cruzamento tipo cruz”
    • Vá para a esquerda ou
    • Vá para a direita ou
    • Siga em frente ou
  2. Em um “T”:
    • Vá para a esquerda ou
    • Vá para a direita
  3. Em um “virar somente a direita”:
    • Vá para a direita
  4. Em um “virar somente a esquerda”:
    • Vá para a esquerda
  5. Em um a frente ou esquerda:
    • Vá para a esquerda ou
    • Siga em frente
  6. Em um a frente ou ou a direita:
    • Vá para a direita ou
    • Siga em frente
  7. Em um beco sem saída:
    • Volte
  8. No final do labirinto:
    • Pare

Aplicando-se  a “regra da mão esquerda” a lista acima, será reduzida a apenas uma opção para uma cada uma das possibilidades:

  1. Em um “cruzamento tipo cruz”
    • Vá para a esquerda
  2. Em um “T” (Transversal):
    • Vá para a esquerda
  3. Em um “virar somente a direita”:
    • Vá para a direita
  4. Em um “virar somente a esquerda”:
    • Vá para a esquerda
  5. Em um a frente ou esquerda:
    • Vá para a esquerda
  6. Em um a frente ou ou a direita:
    • Siga em frente
  7. Em um beco sem saída:
    • Volte
  8. No final do labirinto:
    • Pare

Estamos quase lá. Quando o robô atinge um beco sem saída é fácil identificá-lo, porque não existem situações ambíguas (já implementamos essa ação com o Robô seguidor de linha). O problema está quando o robô encontra uma “linha” por exemplo, pois a linha pode ser parte de um cruzamento tipo “cruz” (1) ou de um “T” (2) ou mesmo um “Final” (8). Além disso, quando o robô chega a um “virar à esquerda ou à direita”, esses cruzamentos podem ser os do tipo simples (opções 3 ou 4) ou opções que podem ir para a frente (5 ou 6). Para se descobrir exatamente em que tipo de intersecção está o robô, é necessário incorporar um passo adicional: o robô deve “dar um passinho a frente” ou seja rodar o que chamamos de “extra inch” e assim “ver” o que vem por adiante:

Maze4
Em termos de fluxo, todas as acções possíveis podem ser descritas como:

  • Em um beco sem saída:
    • Volte
  • Em uma linha:
    • Executar uma polegada extra
    • Se há uma linha:
      • É uma “cruz” ==> Ir para ESQUERDA
    • Se não houver nenhuma linha:
      • é um “T” ==> Ir para ESQUERDA
    • Se houver outra linha:
      • É o fim de Maze ==> PARAR
  • Em uma curva à direita:
    • Executar uma polegada extra
    • se há uma linha:
      • É ir a frente ou virar a direita ==> ir direto
    • Se não houver nenhuma linha:
      • é um virar obrigatoriamente a direita==> Ir para DIREITA
  • Em uma curva à esquerda:
    • Executar uma polegada extra
    • se há uma linha:
      • É ir a frente ou virar a esquerda ==> virar a esquerda
    • Se não houver nenhuma linha:
      • é um virar obrigatoriamente a esquerda==> Ir para ESQUERDA

Note-se que de facto, no caso de um “virar à esquerda”, você poderá pular o teste porque o robô tomará à esquerda de qualquer maneira. Deixei a explicação mais genérica somente para claridade. No código real ignorarei este teste.

IMG_3474

 

A foto ao lado , é de um labirinto bem simples que desenhei no chão do meu laboratório, usando fita isolante de 18mm (3/4) que uso para testes (ainda bem que minha mãe não viu!!!! ;-0:

Aplicando o algorítmo “Left Hand on the Wall” ao código do Arduino

Uma vez que já temos a função  readLFSsensors () modificada, para incluir os 2 sensores adicionais, podemos também re-escrever a função “Loop” introduzindo o algoritmo como descrito anteriormente:

void loop() 
{
     readLFSsensors(); 
     switch (mode)
     { 
         case NO_LINE: 
            motorStop();
            goAndTurn (LEFT, 180);
            break;
         case CONT_LINE: 
            runExtraInch();
            readLFSsensors();
            if (mode == CONT_LINE) mazeEnd();
            else goAndTurn (LEFT, 90); 
            break;
         case RIGHT_TURN: 
            runExtraInch();
            readLFSsensors();
            if (mode == NO_LINE) goAndTurn (RIGHT, 90); 
            break; 
         case LEFT_TURN: 
            goAndTurn (LEFT, 90); 
            break; 
         case FOLLOWING_LINE: 
            followingLine();
            break; 
 
     }
}

Algumas funções importantes aparecem aqui.

followingLine() é a mesma utilizada com o robô seguidor de linha, que quando se está apenas seguindo uma linha, deve-se: calcular o PID e controlar os motores, dependendo dos valores dos ganhos da malha de controle usando a função: motorPIDcontrol ();

runExtraInch (): vai empurrar o robô para a frente um pouquinho. Quanto o robô se moverá, dependerá do tempo que você usa na função delay(), antes que mande parar os motores.

void runExtraInch(void)
{
    motorPIDcontrol();
    delay(extraInch);
    motorStop();
}

goAndTurn (direction, angle): Esta função é importante, porque você na verdade não pode virar o robô, tão logo perceba o tipo de intersecção em que está. Lembre-se que projectamos um robô do tipo diferencial que, quando faz curvas, “gira em torno do seu eixo”. Assim, para sair de um cruzamento, girar 90 graus e continuar movendo-se sobre a linha, o centro das rodas deve obrigatoriamente estar alinhado com o centro da intersecção. Uma vez que a linha dos sensores está à frente do eixo das rodas, o robô deve mover-se para a frente para alinhá-los. Os moteres devem funcionar por um tempo “t” dependendo da distância entre a linha dos sensores e o eixo dos motores ( “d”), velocidade e tamanho das rodas. Esta constante de tempo “t” é  no código: adjGoAndTurn, que deve ser ajustada dependendo de seu projeto:

Turn_Explanation

void goAndTurn(int direction, int degrees)
{
    motorPIDcontrol();
    delay(adjGoAndTurn);
    motorTurn(direction, degrees);
}

Neste ponto, o robô já está “resolvendo um labirinto”!  Você acabou de terminar o “Primeiro Passo”. Não importa onde você começar dentro de um labirinto, você chegará ao final.

Abaixo, um vídeo mostrando um teste para esta fase do projeto:

Tomando nota do Caminho

Consideremos o exemplo abaixo:

Maze1

Partindo do ponto escolhido, o robô encontrará 15 Interseções antes de chegar ao final do labirinto:

  1. Esquerda (L)
  2. Back (B)
  3. Esquerda (L)
  4. Esquerda (L)
  5. Esquerda (L)
  6. Back (B)
  7. Reto (S)
  8. Back (B)
  9. Esquerda (L)
  10. Esquerda (L)
  11. Back (B)
  12. Reto (S)
  13. Esquerda (L)
  14. Esquerda (L)
  15. Fim

O que deve ser feito em qualquer um desses cruzamentos é “salvar a decisão tomada” na mesma sequência em que aconteça. Para isso, vamos criar uma nova variável (matriz) que irá armazenar o caminho que o robô tenha tomado:

A variável path[] e 2 índices variáveis, serão utilizados em conjunto para se gravar os passos:

char path[100] = "";
unsigned char pathLength = 0; // the length of the path
int pathIndex = 0; // used to reach an specific array element.

Voltando ao exemplo, uma vez percorrido todo o circuito, as variáveis ficariam:

path = [LBLLLBSBLLBSLL] e  pathLengh = 14

Simplificando (otimizando) o Caminho

Voltemos ao nosso exemplo. Olhando para o primeiro grupo de cruzamentos, percebemos que o primeiro ramo esquerdo é na verdade um “Dead End”, e assim, se o robô em vez de um “lateral-esquerdo-esquerdo” apenas tivesse seguido reto nesse primeiro cruzamento, uma grande quantidade de energia e tempo seriam salvas! Em outras palavras, uma sequência do tipo “LBL”, de facto, seria o mesmo que “S”.

Maze2

Isso é exatamente como o caminho completo pode ser otimizado. Se você analisar todas as possibilidades onde um “U turn” (back) é utilizado, o conjunto de 3 cruzamentos onde o”U-Turn” ( “B”) aparece  (“xBx”) poderá ser reduzido para apenas um.

A descrição acima é apenas um exemplo, abaixo podemos encontrar a lista completa de possibilidades:

LBR = B
LBS = R
RBL = B
SBL R =
SBS = B
LBL = S

Aplicando-se as substituições acima para o caminho completo de nosso exemplo, podemos reduzi-lo a:

path = [LBLLLBSBLLBSLL] ==> LBL = S

path = [SLLBSBLLBSLL] ==> LBS = R

path = [SLRBLLBSLL] ==> RBL = B

path = [SLBLBSLL] ==> LBL = S

path = [SSBSLL] ==> SBS = B

path = [SBLL] ==> SBL = R

path = [RL]

Olhando para o exemplo, é muito claro que se o robô gira para a DIREITA logo no primeiro cruzamento e depois disso, à esquerda, ele chegará ao final do labirinto pelo caminho mais curto!

Maze3

A primeira passada será consolidada na função mazeSolve (). Esta função é, de facto, a função loop () utilizada anteriormente, onde se incorporaram as etapas de armazenamento e otimização do caminho. Quando a primeira passada termina, a variavel path[] conterá o caminho já optimizado.

Uma nova variável é introduzida para sinalizar o final da “passada”:

unsigned int status = 0; // solving = 0; reach end = 1

Abaixo a função completa para a primeira etapa do programa:

void mazeSolve(void)
{
   while (!status)
   {
      readLFSsensors(); 
      switch (mode)
      { 
         case NO_LINE: 
            motorStop();
            goAndTurn (LEFT, 180);
            recIntersection('B');
            break;
         case CONT_LINE: 
            runExtraInch();
            readLFSsensors();
            if (mode != CONT_LINE) {goAndTurn (LEFT, 90); recIntersection('L');} // or it is a "T" or "Cross"). In both cases, goes to LEFT
            else mazeEnd(); 
            break;
         case RIGHT_TURN: 
            runExtraInch();
            readLFSsensors();
            if (mode == NO_LINE) {goAndTurn (RIGHT, 90); recIntersection('R');}
            else recIntersection('S');
            break; 
         case LEFT_TURN: 
            goAndTurn (LEFT, 90); 
            recIntersection('L');
            break; 
         case FOLLOWING_LINE: 
            followingLine();
            break; 
 
       }
   }
}

Aqui uma nova função foi introduzida: recIntersection (direction)

Esta função será a usada para armazenar as decisões tomadas nos cruzamentos e também para chamar outra importante função: simplifyPath(), que irá otimizando “em tempo real” o grupo de 3 cruzamentos envolvendo “U-Turn”, como vimos anteriormente.

void recIntersection(char direction)
{
    path[pathLength] = direction; // Store the intersection in the path variable.
    pathLength ++;
    simplifyPath(); // Simplify the learned path.
}

O crédito para a criação da função simplifyPath () é de Patrick McCabe. Eu apenas a incluí ao meu código (para mais detalhes, visite patrickmccabemakes.com):

void simplifyPath()
{
   if(pathLength < 3 || path[pathLength-2] != 'B') // only simplify the path if the second-to-last turn was a 'B'
   return;
   int totalAngle = 0;
   int i;
   for(i=1;i<=3;i++)
   {
     switch(path[pathLength-i])
     {
        case 'R':
           totalAngle += 90;
           break;
        case 'L':
           totalAngle += 270;
           break;
        case 'B':
           totalAngle += 180;
           break;
      }
    }
    totalAngle = totalAngle % 360;  // Get the angle as a number between 0 and 360 degrees.
    switch(totalAngle) // Replace all of those turns with a single one.
    {
       case 0:
          path[pathLength - 3] = 'S';
          break;
       case 90:
          path[pathLength - 3] = 'R';
          break;
       case 180:
          path[pathLength - 3] = 'B';
          break;
       case 270:
          path[pathLength - 3] = 'L';
          break;
     }
     pathLength -= 2; // The path is now two steps shorter.
}

A segunda passada: resolvendo o labirinto o mais rápido possível!

O programa principal: loop () é bem simples:

void loop() 
{
    ledBlink(1);
    readLFSsensors(); 
    mazeSolve(); // First pass to solve the maze
    ledBlink(2); // end 1st pass
    
    while (digitalRead(buttonPin) && !mode) { }
    pathIndex = 0;
    status = 0;
    mazeOptimization(); //run the maze as fast as possible
    ledBlink(3); // end of 2nd pass. Maze solved!
}

Assim, quando a primeira passada termina, o que devemos fazer é apenas “alimentar” o robô com o caminho otimizado. Ele vai começar a percorrer o labirinto novamente e quando uma intersecção for encontrado, ele não mais tomará decisões, mas simplesmente seguirá o que está armazenado na variável path [].

Para a segunda passada usamos a função mazeOptimization(), que por sua vez, chama a função mazeTurn(path[]) que comandará os movimentos do robô nessa segunda passada:

void mazeOptimization (void)
{
   while (!status)
   {
      readLFSsensors(); 
      switch (mode)
      {
         case FOLLOWING_LINE:
            followingLine();
            break; 
         case CONT_LINE:
            if (pathIndex >= pathLength) mazeEnd (); 
            else {mazeTurn (path[pathIndex]); pathIndex++;}
            break; 
         case LEFT_TURN:
            if (pathIndex >= pathLength) mazeEnd (); 
            else {mazeTurn (path[pathIndex]); pathIndex++;}
            break; 
         case RIGHT_TURN:
            if (pathIndex >= pathLength) mazeEnd (); 
            else {mazeTurn (path[pathIndex]); pathIndex++;}
            break; 
      } 
   } 
}
void mazeTurn (char dir) 
{
   switch(dir)
   {
      case 'L': // Turn Left
         goAndTurn (LEFT, 90); 
         break; 
      case 'R': // Turn Right
         goAndTurn (RIGHT, 90); 
         break; 
      case 'B': // Turn Back
         goAndTurn (RIGHT, 180); 
         break; 
      case 'S': // Go Straight
         runExtraInch(); 
         break;
    }
}

A segunda passada está feita!

O vídeo abaixo mostra o exemplo trabalhado aqui completo, onde Rex encontra seu caminho para livrar-se do Minotauro! 😉

O código Arduino completo para este projeto, poderá ser encontrado no link abaixo:

Codigo para o Arduino

Usando o aplicativo Android para o ajuste

A App Android desenvolvido para o projeto do Robô seguidor de Linha também pode ser usado aqui.  O código Arduino apresentado na última etapa já inclui comunicação com o dispositivo Android, mas se não quiser usar-lo não há problema, porque o código é “transparente”.

Eu usei bastante o dispositivo Android durante o projeto para enviar dados de teste do robô para o dispositivo, utilizando-se o campo de “Mensagem recebida”.

Diversas variáveis devem ser bem definidas, a fim de garantir que o robô gire corretamente. Os mais importantes estão abaixo (os marcados em negrito tive mudar-los várias vezes):

const int power = 250;
const int iniMotorPower = 250;
const int adj = 0;
float adjTurn = 8;
int extraInch = 200;
int adjGoAndTurn = 800;
const int THRESHOLD = 150;
float Kp=50;
float Ki=0;
float Kd=0;

CONCLUSÃO

Esta é a segunda e última parte de um projeto complexo, explorando a potencialidade de um robô seguidor de linha, onde aplicando-se conceitos de inteligência artificial se conseguiu explorar labirintos,  encontrando o caminho da saída mais curto e rápido.

Espero que esse trabalho possa contribuir para que outras pessoas possam aprender mais sobre eletrônica, robôs, Arduino, etc.

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebook

Saludos desde el sur del mundo! 😉

Um abraço e até o próximo post!

Obrigado

Marcelo

Conectando “coisas” através do Bluetooth

 

bluetooth-logo

Uma rede sem fio local (e pessoal) porreta é sem dúvida a Bluetooth (BT). Hoje em nosso dia-a-dia é comum encontrar-nos com celulares, aparelhos de som, cameras, etc., entrelaçados com a ajuda da famosa “luzinha azul”

No mundo do IoT e da automação em geral, é muito comum deparar-nos com controles remotos via celulares utilizando tecnologia BT. Isso é devido a 2 componentes básicos mas muito importantes:

  1. Plataforma de desenvolvimento para OS ANDROID
  2. Módulos BT baratos e acessíveis (Como por exemplo o HC-06)

Neste tutorial, vou desenvolver algumas idéias de como controlar as saídas de um Arduíno através de um celular de maneira a mover um Robot, acionar lâmpadas em uma casa, etc.

No mercado é comum encontrar módulos de BT 3.0  “Master-Slave” como o HC-05  e “Slaves” como o HC-06. Já mais recentemente, apareceram os HC-08 e HC-10 que trabalham com tecnologia BT 4.0 ou BLE (“Bluetooth Low Energy”). Os módulos BLE são os únicos que podem ser conectados a um Iphone, pois infelizmente a Apple não fornece suporte a ao BT 3.0.

Para os projetos discutidos aqui, usarei um HC-06 que é bem popular e barato (Bye, bye, Iphone, vamos de Android!). O Módulo é alimentado com 5V o que faz com que ele seja facilmente conectado a um Arduino

HC06 PinsUNO por exemplo, para receber e transmitir informações a outros dispositivos como um PC ou um telefone celular. Seus pinos de transmissão e recepção podem ser conectados diretamente ao UNO, não havendo a necessidade de se utilizar divisores de tensão como vimos no caso do ESP8266.

Na prática, o HC-06 deve ser ligado diretamente aos pinos 0 e 1 do Arduino (Serial):

  • HC06-Tx ao Arduino pin 0 (Rx)
  • HC06-Rx ao Arduino pin 1 (Tx)

Ao se usar a entrada serial por HW do UNO é muito importante lembrar-se que o HC-06 não pode estar fisicamente conectado aos pinos 0 e 1 durante a carga do programa, isso porque o USB também usa essa mesma serial.  Uma maneira simples de se contornar esse probleminha (se seu projeto não utiliza muitos GPIOs do UNO) é usar uma porta serial por SW através da library SoftwareSerial (a mesma que usamos no caso do ESP8266). Em nosso caso aqui, usaremos os pinos 10 e 11 do UNO (Tx, Rx respectivamente).

UNO-HC06

O passo seguinte será escrever um codigo bem simples para se poder testar, programar e inicializar o HC-o6:

Para iniciar, incluir a Library Software Serial, definindo a variável “BT” como a nova porta serial.

#include <SoftwareSerial.h>
SoftwareSerial BT(10, 11); // RX, TX
String command = ""; // Stores response of bluetooth device
 
void setup()
{
 Serial.begin(9600);
 Serial.println("Type AT commands!");
 BT.begin(9600); // HC-06 usually default baud-rate
}

Em seguida vem o corpo principal do código que simplesmente espera por dados vindos do BT e uma vez que eles chegem, os mesmos são escritos no Serial Monitor. Da mesma maneira, se podem enviar comandos AT desde o monitor serial até o módulo HC-06.

void loop()
{
 if (BT.available()) // receive data if available.
 {
  while(BT.available()) // "keep receiving".
  {
   delay(10); //Delay added to make thing stable 
   char c = BT.read(); //Conduct a serial read
   command += c; //build the string.
  } 
  Serial.println(command);
  command = ""; // No repeats
 } 
 if (Serial.available())
 {
  delay(10); 
  BT.write(Serial.read());
 }
}

 

Uma vez carregado o programa, faça alguns testes básicos. Por exemplo, envie “AT“, o módulo deverá responder “OK“. Pergunte a versão do Firmware: “AT+VERSION”, o módulo deverá responser, por exemplo: “linvorV1.8“. Com o HC-06 é possível definir um nome para o módulo por exemplo: “AT+NAMEMJRoBot_BT_HC06“.  Mas diferente de outros módulos, voce não conseguirá saber qual é o nome que está definido para o módulo. Ao se enviar o comando anterior, o HC-06 responderá simplesmente: “OKsetname”

Em geral, o HC-o6 vem de fábrica com o password (ou PIN): 1234. Voce poderá definir um novo com o comando AT: AT+PINxxxx onde ‘xxxx‘ serão 4 números.

OK! Módulo conectado ao UNO e funcionando. Hora de lançar mão do velho e bom celular Android!!!

Existem muitas apps na loja da Google que podem ser utilizadas. Vou sugerir duas delas, por serem as que usarei nos tutoriais deste blog. Essas apps foram desenvolvidas por mim utilizando o MIT Application2 tool (veja projeto aqui) e estão disponíveis sem custo na loja da Google:

  1. MJRoBot II BT Control
  2. MJRoBot BT Digital Analog Voice Control

MJRoBot BT IconO App MJR0Bot II foi desenvolvido para comandar Robots. Ele basicamente envia um caracter para cada comando de direção, modos AUTO/MANUAL, velocidade + e velocidade-. Alem de permitir o envio de mensagens em modo texto.

2015-12-11 20.26.44 copy

 

MJRoBot BT Ctrl app Logo

O App. MJRoBot Digital Analog Voice Control, envia comandos para acionamento digitais (ligar/desligar) tanto por botões como por voz e comandos numéricos para controle de PWMs (0-255).

FullSizeRender 10

 

Faça o Download de qualquer uma das duas Apps, vá ao set-up do celular e procure o modulo BT fazendo a conexão (entre com o PIN 1234 ou algum outro definido por você).  Isso deverá ser feito uma única vez, pois o celular guardará os dados de conexão. Uma vez que o celular e o HC-06 estão conversando, é só usar as APPs.

A partir da segunda vez, ao lançar-se a app, o modulo BT estará desconectado.

FullSizeRender 14

Acione o botão de BT, para que o app informe dos modulos disponíveis:

FullSizeRender 12

Selecione o nome do módulo (No caso é esse com o HC-06 ao final).

O App então mostrará “CONNECTED”, informando que está “pared” com o HC-06

FullSizeRender 13

A partir daí, é só ir acionando os botões do APP e observar no Monitor Serial, o que é que o APP está enviando.

Por exemplo, acionando “ON” e “OFF” sequencialmente para os 4 devices, no caso da APP MJR0Bot BT Digi/Ana/Voice Ctrl, o resultado seria:

dev1on
dev1off
dev2on
dev2off
dev3on
dev3off
dev4on
dev4off 

Agora que já temos um App para o Android e sabemos como funciona um modulo BT, vamos colocar a mão na massa e criar algo de util!

Controlando as saídas do Arduino.

Vamos construir o circuito abaixo:

Picture1

A idéia será usar o App MJRoBot Digi/Ana/Voice Ctrl para acender e apagar os LEDS e também controlar a intensidade dos mesmos.

Estaremos relacionando:

  • Device 1: "dev1on/dev1off" ==> LED Red ==> Pin 3 do UNO
  • Device 2: "dev2on/dev2off" ==> LED Yellow ==> Pin 5 do UNO
  • Device 3: "dev3on/dev3off" ==> LED Green==> Pin 6 do UNO
  • Device 4: "dev4on/dev4off" ==> LED Blue==> Pin 9 do UNO

Ou seja, ao acionar o botão “ON” relacionado ao “Device 1”, a mensagem de texto “dev1on”será enviada ao Arduino. Ao receber essa mensagem, o LED vermelho deverá acender e assim por diante.

Observe que os 4 pinos são pinos com capacidade de gerar PWM. Isso é importante para a utilização dos “sliders” da App, que enviarão valores para controlar a intensidade dos LEDs via PWM

  • Dev A0: "r/ 0-255" ==> LED Red ==> Pin 3 do UNO
  • Dev A1: "y/ 0-255" ==> LED Yellow ==> Pin 5 do UNO
  • Dev A2: "g/ 0-255" ==> LED Green==> Pin 6 do UNO
  • Dev A3: "b/ 0-255" ==> LED Blue==> Pin 9do UNO

No caso dos controles deslizantes (“sliders”), antes dos valores para controle do PWM (0 a 255), um caracter será enviado para que o Arduino saiba de que “slider” estará chegando o comando.

O código:

Setup inicial:

#include <SoftwareSerial.h>
SoftwareSerial BT(10, 11); //TX, RX respectively
String device;
const int dev1 = 3; //PWM
const int dev2 = 5; //PWM
const int dev3 = 6; //PWM
const int dev4 = 9; //PWM
void setup() 
{
 BT.begin(9600);
 Serial.begin(9600);
 
 pinMode(dev1, OUTPUT);
 pinMode(dev2, OUTPUT);
 pinMode(dev3, OUTPUT);
 pinMode(dev4, OUTPUT);
}

Podemos dividir a parte principal do programa em 4 blocos:

  1. Espera dos comandos BT e construção da variável “device”
  2. Acionamento dos LEDS a partir dos botões
  3. Acionamento dos LEDS a partir de comandos de voz
  4. Controle do intensidade dos LEDs via Sliders
void loop() 
{
 while (BT.available()) 
 {
   delay(10); //Delay added to make thing stable 
   char c = BT.read(); //Conduct a serial read
   device += c; //build the string.
 } 
   if (device.length() > 0) 
 {
   Serial.println(device); 
   // Button control:
   if (device == "dev1on") {digitalWrite(dev1, HIGH);}
   else if (device == "dev1off") {digitalWrite(dev1, LOW);}
   else if (device == "dev2on") {digitalWrite(dev2, HIGH);}
   else if (device == "dev2off") {digitalWrite(dev2, LOW);}
   else if (device == "dev3on") {digitalWrite(dev3, HIGH);}
   else if (device == "dev3off") {digitalWrite(dev3, LOW);}
   else if (device == "dev4on") {digitalWrite(dev4, HIGH);}
   else if (device == "dev4off") {digitalWrite(dev4, LOW);}
 
   // Voice control:
   else if (device == "ligar um" || device == "Ligar 1") {digitalWrite(dev1, HIGH);}
   else if (device == "desligar um" || device == "desligar 1") {digitalWrite(dev1, LOW);}
   else if (device == "ligar som" || device == "Ligar som") {digitalWrite(dev2, HIGH);}
   else if (device == "desligar som" || device == "Desligar som") {digitalWrite(dev2, LOW);}
   else if (device == "ligar TV" || device == "Ligar TV") {digitalWrite(dev3, HIGH);}
   else if (device == "desligar TV" || device == "Desligar TV") {digitalWrite(dev3, LOW);}
   else if (device == "ligar quarto" || device == "Ligar quarto") {digitalWrite(dev4, HIGH);}
   else if (device == "desligar quarto" || device == "Desligar quarto") {digitalWrite(dev4, LOW);}
   // Slider control:
   char colour = device[0]; 
   int value = device[2]; 
   Serial.print(" "); 
   Serial.println(value); 
   if ( colour == 'r') 
   { 
     analogWrite(dev1, value); // use value to set PWM for LED brightness 
   } 
     if ( colour == 'y') 
   { 
     analogWrite(dev2, value); // use value to set PWM for LED brightness 
   } 
     if ( colour == 'g') 
  { 
    analogWrite(dev3, value); // use value to set PWM for LED brightness 
  } 
    if ( colour == 'b') 
  { 
    analogWrite(dev4, value); 
  }
  device=""; //Reset the variable
 } 
}

 

Link para o código Arduino

No vídeo abaixo, uma demonstração da porção programa acima (botões & Slider):

O controle de dispositivos IoT por voz é uma tendência nos dias de hoje. Conseguir este tipo de controle com o conjunto Arduino/HC-06/Android é extremamente simples. Os dispositivos Android já possuem essa característica “de fábrica”. No App que desenvolví, apenas acrescentei um botão que faz com que o Android “escute” um comando e o envie em formato texto para o Arduino. O codigo se encarrega de “ler” a string que chega.

Em vermelho, ressalto no código alguns possíveis comandos de voz. Ao receber por  “Ligar TV” por exemplo, o LED verde (correspondente ao device 3) acenderá (em vez do LED podíamos ter uma relé que ligaria a TV).

No vídeo abaixo, uma pequena demonstração do controle por voz:

 

Bom, acredito que já é possível ver o enorme potencial do controle de “coisas” usando BT/Android/Arduino. Em meus próximos posts estarei desenvolvendo Robots que poderão ser controlados remotamente como vimos aqui.

Um abraço e até mais!

 

Basic stuff – “O ovo”…. ou será “a galinha”….?

Para começar (e se desenvolver) no mundo da eletrônica, o melhor caminho sem dúvida é a internet! Vai aqui uma super dica de como se tornar um grande programador através da técnica do “Psychding”:

Psychding

Heheheheheh! Seria bom se só isso resolvesse! Mas ajuda, claro! Mas para aprender mesmo, vale a pena uns cursos, muita leitura e claro, “mãos na massa!”. Para o pessoal que está no Brasil um site muito legal é o:

Laboratório de Garagem

Como o pessoal do Garagem mesmo explica, “O Laboratório de Garagem é uma iniciativa voltada para a integração, colaboração e apoio aos desenvolvedores independentes de ciência e tecnologia, ou como gostamos de ser chamados: garagistas.

Somos uma rede social, uma incubadora, uma loja virtual e um grupo de pessoas que acreditam que a próxima revolução pode (e vai) sair de uma garagem, ainda mais se ela estiver conectada a muitas outras garagens.

Eu também sou um dos garagistas e tenho procurado dar meus pitecos por lá. Vale a pena se associar, participar, mandar sugestões, etc. O pessoal de lá é muito legal, estão sempre prontos a ajudar, não importando se as dúvidas são básicas ou complicadas!

O Lab de Garagem por exemplo, tem um curso on-line básico muito legal (em português) para quem quer se iniciar no mundo dos Arduinos:

Curso Arduino – Loboratorio de Garagem

É isso aí! Inté!

Marcelo Rovai no Garagem

Cursos de eletrônica online (foco: IoT)

No meu último post, comentei que o curso que fiz através da plataforma EDX me ajudou a re-descobrir o mundo da eletrônica. De lá para cá, tenho nas horas vagas procurado fazer alguns cursos tanto do EDX quanto do COURSERA. Essas plataformas são a ponta de lança para que grandes universidades ou empresas possam propiciar cursos on-line. Em geral a qualidade é muito boa e a profundidade depende da instituição ou do tema. Como regra geral, eu diria que os cursos do EDX são mais complexos, profundos e necessitam bastante dedicação e tempo. Já os do COURSERA, são mais superficiais e rápidos. Esses cursos são conhecidos como MOOC (Massive Open Online Course) e seguramente vão transformar a educação como a conhecemos hoje.

Existem tanto cursos “isolados” sobre um determinado tema  como outros que são “Especializações”, ou seja um conjunto de cursos (em geral 5 + um projeto final) que também podem ser feitos individualmente. Todos os cursos podem ser feitos sem certificado final (gratuitos) ou com direito a certificado (em média, uns USD50/curso).

Aqui vão algumas sugestões de cursos que estão abertos no momento, outros que já fecharam, mas ainda disponibilizam conteúdos e alguns que ainda estão por vir:

O legal é explorar as plataformas (no primeiro parágrafo estão os links) e encontrar o curso que te apetece. Tem para todos os gostos!

Boa sorte, arregasse as mangas e mande bala!

Saludos!

File 1-12-16, 19 31 04

File 1-12-16, 19 27 12

File 1-12-16, 19 26 44

 

 

MJRoBot I – O Robot de Berkeley

Passos para a construção de um robot autônomo.

Fazem alguns meses, terminei meu primeiro curso on-line. Foi uma experiência nova mas que despertou em mim uma paixão antiga a muito adormecida, a eletrônica! De lá para cá, fiz vários outros cursos, mas como o primeiro a gente nunca esquece……..

O curso foi: EE40LX: Electronic Interfaces, através da plataforma EDX e ministrado pela Berkeley University, da California. A idéia foi uma revisão geral de eletrônica básica através de exercícios e experimentos, culminando em um Robot que obedecesse alguns requisitos básicos:

  • O robot deveria no mínimo:
    • Responder a luz
    • Produzir som
    • Responder ao som
    • Mover motores
  • Adicionalmente, incorporei ao projeto:
    • Produzir luz
    • Responder remotamente a comandos IR

O monstrengo final…..

MJRoBot I

Para ser aprovado, entreguei o vídeo abaixo aonde demonstro que o robot cumpria as especificações do curso:

Para a construção do robot, utilizei a lista de componentes abaixo:

Photo 1-11-16, 18 47 23

Para o coração (ou melhor, o cerebro….) do robot, utilizei o Arduino UNO. abaixo o diagrama de blocos com as respectivas pinagens. Para um projeto mais elaborado é fundamental desenvolver este tipo de diagrama. Ele vai sempre ser a guia a que se tem que recorrer durante a codificação do SW.

Photo 1-11-16, 18 46 08

O Hardware:

“Resposta a luz”

A base da “reação a luz”, foram as fotocélulas. As fotocélulas (que são resistores que variam com a luz), foram conectados na configuração “ponte de Whitestone”. Isso fazia que uma variação na intensidade de luz provocasse um desequilíbrio da ponte resistiva, o que levava a saturação de um amplificador operacional. Meio enrolado, mas muito inteligente (não eu, mas os caras de Berkley que sugeriram a solução 😉

Photo 1-11-16, 18 48 11

Outra sacada muito legal, foi o uso de um “Power Block”, ou seja, ao invés de deixar a ponte constantemente alimentada (importante lembrar que em um robot autônomo, bateria é vida!), se alimenta a mesma via um pino de saída do Arduino. Assim, somente quando se necessita verificar se o sensor tem luz ou não, 5V é produzido pelo Arduino, alimentando momentaneamente a ponte durante a leitura (no caso, o pino 8).

Foram utilizadas duas fotocelulas, uma frontal e uma traseira. A ação é simples, quando o robot de desloca para a frente e a luz é interrompida, os motores dão “marcha-ré”. E obviamente, vice-e-versa.

 

“Geração e resposta a som”

Para que o robot pudesse “ouvir”, um velho e bom microfone de PC foi o escolhido. Como o sinal do microfone é muito baixo, um Amplificador Operacional inversor foi utilizado para amplificação do sinal. Também antes de ser amplificado, o sinal de som passou por um filtro passa altas, eliminando-se assim a componente DC.

Para “falar”, foi utilizado um simples “buzzer”.

Photo 1-11-16, 18 48 00

“Mover motores”

Para o movimento dos motores uma Ponte H, a velha e boa L293D foi a escolhida. Importante não esquecer de adicionar uns capacitores cerâmicos nos terminais do motor. Isto ajudará a diminuir eventuais ruídos elétricos produzidos pelas “escovas” dos motores DC (10pF está bem). Ah! falando em ruído, nunca é demais lembrar que sempre é bom alimentar os motores com uma batería separada da usada pelo Arduíno (não se esquecendo de conectar os “terras”).Photo 1-11-16, 18 48 21

Para sinalização, utilizei um LED RGB catodo comum. Com isso, podia acompanhar algumas ações do robot através dos LEDs.

Para receber comandos via um controle remoto IR (Infra vermelho), utilizei um  VS1838b. É um sensor con tres terminais, VCC, GND e Signal. Um sensor simples e bem confiável.

Juntando tudo fica uma massaroca mais ou menos assim:

Photo 9-13-15, 21 05 28

O Software:

A idéia do SW foi bem simples. Ao ser ligado,  o robot gerava 4 bips, ficando a espera do sinal “ON” proveniente do controle remoto. Ao receber-lo, soavam 2 bips, o LED ficava azul e o robot estava pronto para receber um comando de voz. No caso um simples ruído mais pronunciado como um assovio ou uma palma (o ganho do Amp Op, deve ser testado para gerar um sinal coerente). Também é importante que durante a faze de “set-up”, se “escute” o nível de ruído do ambiente, assim consegue-se uma melhor precisão e o robot não parte com qualquer barulhinho.

Com o comando de som, o robot começava a mover-se para a frente (LED ficava verde) até que o sensor de luz frontal fosse bloqueado. Nesse momento, o LED ficava vermelho e o robot passava a mover-se de “marcha-ré”, até que o sensor de luz traseiro fosse bloqueado, invertendo-se assim o movimento.  O robot ficava nesse zigue-zague até receber um comando para parar (controle remoto), ou até que o “piloto” dormisse de tédio fazendo o monstrengo se arrebentar no chão (é, o teste foi em cima de uma mesa…..).

Abaixo, o fluxo do programa (dá para entender melhor que o meu Bla-bla-bla anterior 😉

Photo 1-11-16, 18 46 31

O codigo final do robot pode ser baixado de meu Dropbox público:

https://www.dropbox.com/sh/246l6yksl6fj0uw/AACsVcKSg5yIOohXSj6yuL63a?dl=0

That’s all Folks! Apenas um minuto de silencio para o velho e bom MJRoBot I, que hoje descansa em paz em minha gaveta de componentes e serve de partes para seu irmão mais novo (ou filho?), o MJR0Bot II!!!!!!! que aparecerá em uma nova entrada deste Blog.

Para matar a saudade fica umas fotinhos para lembrar a construção do rapaz!

Photo 1-11-16, 18 49 06

Ciao, bello! Hora de tomar um vinhozinho!

CONCLUSÃO

Como sempre, espero que este projeto ajude outras pessoas a encontrar seu caminho no apaixonante mundo da eletrônica, robótica e do IoT!

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebook

“Saludos desde el sur del mundo!” 😉

Um abraço e até o próximo post!

Obrigado

Marcelo

 

 

MJRoBot Blog/Website

Santiago, 11 de janeiro de 2016

Um começo…..

Hoje começo a escrever meu Blog/Website “MJRoBot”. A idéia é poder compartir idéias e experiencias no mundo da eletrônica. A ênfase será no uso de plataformas de desenvolvimento baseadas em microcontroladores, como Arduino e computadores completos como o Raspberry-Pi.

2015-12-11 20.26.44 copyCom o rápido desenvolvimento do que chamamos hoje de “Internet of Things” ou simplesmente “IoT”, Arduinos, sensores, etc., estão cada vez mais ao alcance das mãos de curiosos, “hobbystas”, estudantes, profissionais, etc.

Espero que meus projetos, posts e idéias possam ajudar outras pessoas a encontrar seu caminho no apaixonante mundo da eletrônica, robótica e do IoT!

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebook

“Saludos desde el sur del mundo!” 😉

Um abraço e até o próximo post!

Obrigado

Marcelo