Arduino UNO - UNO master - slave

Caros,

Estou numa experiência de conectar dois Arduinos Uno em que o MASTER faz um request ao SLAVE e, este, lê o valor de um COMPASS e retorna o valor lido ao MASTER.

Facilmente consigo pôr os dois a “falar” desta forma através de um programa exemplo que vi no Youtube, no entanto, o retorno do SLAVE é um conjunto de caracteres que o MASTER não consegue entender.

No meio disto tudo, a variável de retorno deverá ser um inteiro para que eu a possa utilizar numa formula matemática.

Se alguem me poder ajudar, desde já agradeço

Segue em anexo o codigo dos dois Master e Slave.

Obrigado desde já

Master_Slave.txt (10.3 KB)

O seu problema está muito mal explicado. Está a misturar float, com inteiro e com char. Mesmo a implementação da comunicação I2C, não sei se estará correcta. Repare que está a enviar um float:

float RUMO_ACTUAL = 0.0;

convertido para int:

        //Wire.write(int(RUMO_ACTUAL));  // tem de se passar o valor inteiro porque o float não passa no Wire.write.

(ou pelo menos penso que era essa a ideia, e do outro lado está a ler um char:

    char c = Wire.read();

Já experimentou enviar um char, em vez de um int? Sabe qual é a diferença entre um char e um int? E a diferença entre um int e um float? E se começar por tentar perceber bem isso?

Sendo assim, tente trocar:

        //Wire.write(int(RUMO_ACTUAL));  // tem de se passar o valor inteiro porque o float não passa no Wire.write.

por:

        Wire.write((unsigned char) RUMO_ACTUAL);  // ...

e:

    char c = Wire.read();
    Serial.print(c);
        
    RUMO_ACTUAL = atoi(c);        //<---------------------- DÁ ERRO conversion from 'char' to 'const char*' [-fpermissive]
    
    //Serial.println("estou em loop");

por:

    unsigned char c = Wire.read();
    Serial.print(c);
        
    //RUMO_ACTUAL = atoi(c);        //<---------------------- DÁ ERRO conversion from 'char' to 'const char*' [-fpermissive]
    
    //Serial.println("estou em loop");

Se isto funcionar depois fica mais fácil dar o próximo passo. (esse passo não é usar a função atoi).

Obrigado Luis, vou experimentar.

A minha maior dificuldade até agora é o facto do C não tratar as variáveis de uma forma mais "normal" para mim.

Para mim, tenho como definição:

Inteiro X=10 Float =10.9 String = "abc"

mas quando começamos a falar de strings com '' e strings com "", ainda estou verde. Quando falamos de char*, aí nem verde ainda estou.

Sei o que é uma matriz e um vector, mas de resto ainda estou a aprender.

Com a vossa ajuda, daqui a umas semanas estou um sabichão.

Luis, conversa à parte, obrigado e amanhã vou experimentar o que me ensinou hoje e depois dou feedback

Obrigado

Luis,

Fazer um cast para unsigned char nao vai miraculosamente decompor os 4 bytes do float para enviar pelo protocolo I2C…

Boas,

Tenho estado aqui a tentar fazer alguma coisa disto mas a coisa não está a resultar.

Entretanto assolou-me outra dúvida que pode ter a ver com isto (ou não)

O Master liga ao Slave e o Slave com o Compass assim:

Master Slave Compass
A4 <–> A4
A5 <–> A5
SLC <–> SLC
SDA <–> SDA

Não haverá aqui qq coisa mal? Afinal o A4 e A5 não são o mesmo que o SLC e SDA?

Se for assim tenho o master ligado aos mesmos pinos que o Compass no Slave. Verdade?

Ou então já tenho as pestanas a colarem…

Isto:

Serial.begin(9600);

Tem de estar no setup.

Porque nao experimentas:

Para enviar:

float val = 0.0;
Wire.write((unsigned char*)&val,4);

Para receber:

unsigned char rec[4]; 
float heading = 0.0;

for (unsigned char i = 0; i<4; i++) { 
   rec[i] = Wire.read();
}

memcpy(&heading, rec,4);//copias os bytes para a variavel em formato float.

O truque do memcpy pode ser usado para enviar tambem.

Boa tarde Bubulindo

Só agora vi esta tua msg.
Entretanto com a compatibilização das variáveis já consigo ver o valor da variável que quero passar, ou seja no lado do slave:

// ARDUINO CHINÊS SLAVE
// Reference the I2C Library
#include <Wire.h> //ver se este include já está no main program
// Reference the HMC5883L Compass Library
#include <HMC5883L.h>

HMC5883L compass;
int error = 0;

int RUMO_ACTUAL;
float xv, yv, zv;
float calibrated_values[3]; 
void setup()
{
    compass = HMC5883L(); // Construct a new HMC5883 compass.
    setupHMC5883L();
    Wire.begin(5); // Start the I2C interface.   PORTA 5     
    
 
Serial.begin(9600);
}
// FIM VOID SETUP


void loop()
{
    
        RUMO_ACTUAL = 0;
        obter_rumo_actual();
 Wire.onRequest(ordem_do_master);
        
        //Wire.write(int(RUMO_ACTUAL));  // tem de se passar o valor inteiro porque o float não passa no Wire.write.
 //Serial.println(RUMO_ACTUAL);
    Serial.print("Rumo = ");
    Serial.println(RUMO_ACTUAL);
 delay(100);

}
// FIM VOID LOOP

void ordem_do_master()
{
  //Wire.write("1234567890");
  Wire.write((RUMO_ACTUAL));
  
}

// ------------------------------------FUNÇÕES---------------------------------

//--------------------------------------------------------------------------------

void obter_rumo_actual()
{

  //MagnetometerRaw raw = compass.ReadRawAxis();
  //MagnetometerScaled scaled = compass.ReadScaledAxis();
  float values_from_magnetometer[3];
  getHeading();
  values_from_magnetometer[0] = xv;
  values_from_magnetometer[1] = yv;
  values_from_magnetometer[2] = zv;
  
  transformation(values_from_magnetometer);

  //int MilliGauss_OnThe_XAxis = scaled.XAxis;// (or YAxis, or ZAxis)

  //float heading = atan2(scaled.YAxis, scaled.XAxis);
  float heading = atan2(calibrated_values[1], calibrated_values[0]);
  // Once you have your heading, you must then add your 'Declination Angle', which is the 'Error' of the magnetic field in your location.
  // Find yours here: http://www.magnetic-declination.com/
  // Mine is: 2� 37' W, which is 2.617 Degrees, or (which we need) 0.0456752665 radians, I will use 0.0457
  // No meu caso é 2º 06'W  = 2,10 graus ou seja 0.0366 radianos
  // If you cannot find your Declination, comment out these two lines, your compass will be slightly off.

  float declinationAngle = 0.366;
  heading += declinationAngle;
  
  // Correct for when signs are reversed.
  if(heading < 0)
    heading += 2*PI;
    
  // Check for wrap due to addition of declination.
  if(heading > 2*PI)
    heading -= 2*PI;
   
  // Convert radians to degrees for readability.
  RUMO_ACTUAL = heading * 180/PI;  
  
  //Wire.write(int(RUMO_ACTUAL));  // tem de se passar o valor inteiro porque o float não passa no Wire.write.
}
// FIM DE OBTER_RUMO_ACTUAL

//------------------------------------------------------------
void transformation(float uncalibrated_values[3])    
{
  //calibration_matrix[3][3] is the transformation matrix
  //replace M11, M12,..,M33 with your transformation matrix data
  double calibration_matrix[3][3] = 
  {
    {1.25, 0.027, 0.048},
    {0.011, 1.148, -0.17},
    {0.016, 0.026, 1.327}  
  };
  //bias[3] is the bias
  //replace Bx, By, Bz with your bias data
  double bias[3] = 
  {
    1.771,
    -111.863,
    23.651
  };  
  //calculation
  for (int i=0; i<3; ++i) uncalibrated_values[i] = uncalibrated_values[i] - bias[i];
  float result[3] = {0, 0, 0};
  for (int i=0; i<3; ++i)
    for (int j=0; j<3; ++j)
      result[i] += calibration_matrix[i][j] * uncalibrated_values[j];
  for (int i=0; i<3; ++i) calibrated_values[i] = result[i];
}

//------------------------------------
void setupHMC5883L()
{  
  compass.SetScale(0.88);
  compass.SetMeasurementMode(Measurement_Continuous);
}
//------------------------------------------- 
void getHeading()
{ 
  MagnetometerRaw raw = compass.ReadRawAxis();
  xv = (float)raw.XAxis;
  yv = (float)raw.YAxis;
  zv = (float)raw.ZAxis;
}

Nos Serial.print mesmo antes do fim do loop() consigo ver no SLAVE os valores correctos no formato 999.

Mas, e como disse no meu post anterior, estes valores aparecem correctos no SLAVE se os pinos de connecção entre MASTER e SLAVE (A4 e A5) estiverem desligados.

Estive a investigar sobre este extraordinário protocolo e “acho” que vou ter de dar endereços diferentes ás duas comunicações:
um endereço entre MASTER-SLAVE e outro endereço entre SLAVE-COMPASS.

Que achas?

(Obrigado: vou depois experimentar o que dizes no post#5.)

Obviamente que vais ter de dar dois enderecos diferentes...

Normalmente o endereco do compasso nao e possivel de mudar.

Mas porque e que estas a ligar dois arduinos ao mesmo compasso? A partir do momento que estao os tres dispositivos ligados nas mesmas linhas, podes aceder ao compasso com apenas um Arduino.

Certo.

O que se passa é o seguinte:

no Master eu tenho (pretendo ter) toda a inteligencia para processar leituras de GPS e controlo de uma placa de relays. O Slave só serve para ler o compasso. (O valor lido no SLAVE é fundamental para actuações de relays no Master)

Cheguei a esta conclusão (se calhar mal) porque, inicialmente tinha um UNO ligado a um GPS + uma placa de relays e verifiquei que o GPS não aponta a direcção em que vamos de forma imediada.

Então resolvi juntar-lhe um Compass (este sim dá logo notícia para onde o nariz está virado). Resolvi pôr tudo no mesmo UNO e a coisa pára ou não funciona de forma contínua. Ou seja: UNO com GPS e placa de relays tudo ok. Por outro lado UNO com Compas - calibraddo, tudo ok, quando juntei tudo, a coisa não funciona escorreita.

Daí resolvi acrescentar mais um UNO só para o compass.

Enveredei por esta hipótese também para aprender mais sobre Master/Slave.

bubulindo: Luis,

Fazer um cast para unsigned char nao vai miraculosamente decompor os 4 bytes do float para enviar pelo protocolo I2C...

Não era essa a minha ideia, até porque o OP não estava a enviar os 4 bytes.

luisilva: Não era essa a minha ideia, até porque o OP não estava a enviar os 4 bytes.

Luís,

Já consigo ler o valor em int (vê post#6). O problema agora é de hardware/portas do I2c

samueljanes: Certo.

O que se passa é o seguinte:

no Master eu tenho (pretendo ter) toda a inteligencia para processar leituras de GPS e controlo de uma placa de relays. O Slave só serve para ler o compasso. (O valor lido no SLAVE é fundamental para actuações de relays no Master)

Cheguei a esta conclusão (se calhar mal) porque, inicialmente tinha um UNO ligado a um GPS + uma placa de relays e verifiquei que o GPS não aponta a direcção em que vamos de forma imediada.

Então resolvi juntar-lhe um Compass (este sim dá logo notícia para onde o nariz está virado). Resolvi pôr tudo no mesmo UNO e a coisa pára ou não funciona de forma contínua. Ou seja: UNO com GPS e placa de relays tudo ok. Por outro lado UNO com Compas - calibraddo, tudo ok, quando juntei tudo, a coisa não funciona escorreita.

Daí resolvi acrescentar mais um UNO só para o compass.

Enveredei por esta hipótese também para aprender mais sobre Master/Slave.

Isso é matar uma mosca com um canhão, só porque não se sabe o que é um mata moscas. Se a actuação dos relés não interfere com a leitura da bússola, penso que o melhor é usar apenas 1 Aduino para fazer isso.

luisilva: Isso é matar uma mosca com um canhão, só porque não se sabe o que é um mata moscas. Se a actuação dos relés não interfere com a leitura da bússola, penso que o melhor é usar apenas 1 Aduino para fazer isso.

Luís,

Pois, se calhar tens toda a razão do mundo, o problema é que ainda sou muito maçarico nestas coisas, leio muito e depois o canhão só custou 3.5€(Uno+Compass) e vá de ir para a frente pensando em resolver o problema o mais rápido possível.

O certo é que quando junto as 3 coisas(GPS+Relays+Compass+LCD, tinha-me esquecido deste) + LIBs fico com Variáveis globais usam 1.173 bytes (57%) de memória dinâmica, restando 875 bytes para variáveis locais. O maximo é 2.048 bytes. e não sei se não será memória (a sua falta) que me pára o programa (outras vezes nem funciona).

Certo é que: UNO-1 com GPS+Relays+LCD funciona a 100% UNO-2 com Compass calibrado funciona a 100%

Quando junto os sketchs, não funciona ou funciona por pouco tempo. Ou seja o programa tem um comportamento imprevisível.

E que tal postar esses códigos? (os originais e a união) Talvez se consiga perceber porque é que isso não está a funcionar.

Luís,

/Obrigado pela disponibilidade.

Tentei postar os códigos mas, ambos ultrapassam o 9000 carateres, por isso vai em anexo.

Obrigado pela disponibilidade./

versão OK.txt (29.9 KB)

Versão junta.txt (69.2 KB)

Parece-me interessante a ideia e à primeira vista não encontro nenhum erro que implique o não funcionamento. A única coisa que me parece é que isto não pode trabalhar apenas com a bússola, uma vez que que dá o rumo pretendido é o GPS.
Uma coisa que vejo que não me parece muito boa ideia, e que pode estar a provocar lentidão é a utilização do LCD série. Se percebi qual é o LCD há versões desse LCD mas tipo paralelo. Uma coisa que também posso não estar a perceber é o conceito de “lentidão”.
A ideia é usar a bússola para visualização apenas (no LCD) ou para o cálculo como está?

Luís,

Obrigado antes de mais.

O valor do compass é para fazer cáculos: calcular a diferença entre rumo correcto (GPS) e o rumo actual (Compass) e daí calcular o angulo de viragem (para a direita ou para a esquerda) e afectar os relays que vão virar o motor.

O GPS está sempre a mandar mensagens (NMEA Sentences) e sei sempre em tempo útil qual é o rumo correcto. O problema é que o GPS não me dá o rumo_actual com a mesma velocidade daí a necessidade do compass. Ás vezes viro o GPS mais rapido e o GPS continua a apontar o rumo antigo.

Relativamente ao LCD, este, poderá ser um dos problemas com o SoftwareSerial.h e o I2C.? Haverá alguma incompatibilidade?

O LCD só requer 4 fios ligados, daí a minha escolha (http://www.ptrobotics.com/lcd-grafico/1251-serial-graphic-lcd-128x64.html?search_query=PTR001251&results=1).

Ou então poderá ser problema de consumo de memória?

No sketch OK: Variáveis globais usam 929 bytes (45%) de memória dinâmica, restando 1.119 bytes para variáveis locais. O maximo é 2.048 bytes.

No sketch da união: Variáveis globais usam 1.173 bytes (57%) de memória dinâmica, restando 875 bytes para variáveis locais. O maximo é 2.048 bytes.

O problema não é a memória. O Arduino não é um PC, não tem Sistema Operativo e para além da memória que indica que está a utilizar, não deve utilizar muito mais (apenas um par de variáveis definidas dentro das funções).

Luís,

Voltei à versão antes do Master/Slave.

Entretanto li isto: https://raspberryralph.wordpress.com/2014/07/16/i2c-and-software-serial-and-hardware-serial-working-fine/.

Dei-me ao trabalho de substituir todos os delay() por uma função baseada em millis().

O comportamento continua a ser errático. Consegui que trabalhasse cerca de 25 minutos e depois pára.

Fiz-lhe um debug caso não detecte nmeaSourceA.available(): } else { lcd.gotoLine(7); lcd.eraseBlock(0, 47, 130, 90); tempo = x; xwait(); lcd.gotoLine(8); Serial.print(nmeaDecoder.term(0)); nmeaSourceA.begin(4800);

} //endif do if (nmeaSourceA.available())

} // FIM DE VOID LOOP

Vai funcionando até que crasha. Já experimentei sem e com a reinicialização do nmeaSourceA.begin(4800); e invariavelmente crasha: ou não arranca ou, ao fim de algum tempo cracha sendo que na linha 8 do ecran começam a aparecer caracteres atrás de caracteres (lixo) sem sequer ser respeitado o delay imposto pela função xwait em que tempo= x = 500.

A função é esta e funciona bem.

// FUNÇÃO DE SUBSTITUIÇÃO DA UTILIZAÇÃO DO XDELAY() void xwait() // pressupõe que a variável tempo esteja afectada com os milisegundos de espera { unsigned long agora = millis(); while ((agora + tempo) >= millis()) { // não faz nada

}

return; }

Já experimentei a fazer o upload várias vezes seguidas para garantir que o sketch passa efetivamente e o comportamento continua a ser sempre inesperado. Funciona 10 minutos ou funciona 20 minutos ou não arranca ou faz o primeiro refresh de LCD e pára.

Já tentei mudar a LIB SoftwareSerial pela NewSoftSerial mas não me deixa.

Vou agoar investigar mais sobre a SoftwareSerial. Pode que!....

Depois vou-me à Lib do LCD...isto alguma coisa há-de dar.

A função xwait faz exactamente o mesmo que a função delay... Sem tirar nem pôr.

Viste o exemplo blink without delay?

Podes meter mais do que um post com o código... Fazer download com um iPad não é possível. :(