Lição 3: Sentindo a Rotação
O CanSat NeXT possui dois CIs de sensores na placa CanSat NeXT. Um deles é o barômetro que usamos na última lição, e o outro é a unidade de medição inercial LSM6DS3. O LSM6DS3 é uma IMU de 6 eixos, o que significa que é capaz de realizar 6 medições diferentes. Neste caso, é a aceleração linear em três eixos (x, y, z) e a velocidade angular em três eixos.
Nesta lição, vamos examinar o exemplo de IMU na biblioteca e também usar a IMU para fazer um pequeno experimento.
Exemplo de Biblioteca
Vamos começar vendo como o exemplo da biblioteca funciona. Carregue-o a partir de Arquivo -> Exemplos -> CanSat NeXT -> IMU.
A configuração inicial é novamente a mesma - incluir a biblioteca, inicializar a serial e o CanSat. Então, vamos focar no loop. No entanto, o loop também parece muito familiar! Lemos os valores exatamente como na última lição, só que desta vez há muitos mais deles.
float ax = readAccelX();
float ay = readAccelY();
float az = readAccelZ();
float gx = readGyroX();
float gy = readGyroY();
float gz = readGyroZ();
Cada eixo é realmente lido com alguns centenas de microssegundos de diferença. Se você precisar que eles sejam atualizados simultaneamente, confira as funções readAcceleration e readGyro.
Após ler os valores, podemos imprimi-los como de costume. Isso poderia ser feito usando Serial.print e println exatamente como na última lição, mas este exemplo mostra uma maneira alternativa de imprimir os dados, com muito menos escrita manual.
Primeiro, um buffer de 128 caracteres é criado. Em seguida, este é inicializado para que cada valor seja 0, usando memset. Depois disso, os valores são escritos no buffer usando snprintf()
, que é uma função que pode ser usada para escrever strings com um formato especificado. Finalmente, isso é apenas impresso com Serial.println()
.
char report[128];
memset(report, 0, sizeof(report));
snprintf(report, sizeof(report), "A: %4.2f %4.2f %4.2f G: %4.2f %4.2f %4.2f",
ax, ay, az, gx, gy, gz);
Serial.println(report);
Se o acima parecer confuso, você pode simplesmente usar o estilo mais familiar usando print e println. No entanto, isso se torna um pouco irritante quando há muitos valores para imprimir.
Serial.print("Ax:");
Serial.println(ay);
// etc
Finalmente, há novamente um pequeno atraso antes de iniciar o loop novamente. Isso está principalmente lá para garantir que a saída seja legível - sem um atraso, os números mudariam tão rapidamente que seria difícil lê-los.
A aceleração é lida em Gs, ou múltiplos de . A velocidade angular está em unidades de .
Tente identificar o eixo com base nas leituras!
Detecção de Queda Livre
Como exercício, vamos tentar detectar se o dispositivo está em queda livre. A ideia é que jogássemos a placa no ar, o CanSat NeXT detectaria a queda livre e acenderia o LED por alguns segundos após detectar um evento de queda livre, para que possamos saber que nossa verificação foi acionada mesmo após pegá-lo novamente.
Podemos manter a configuração como estava e apenas focar no loop. Vamos limpar a função de loop antiga e começar do zero. Só por diversão, vamos ler os dados usando o método alternativo.
float ax, ay, az;
readAcceleration(ax, ay, az);
Vamos definir queda livre como um evento quando a aceleração total está abaixo de um valor limite. Podemos calcular a aceleração total a partir dos eixos individuais como
O que ficaria no código algo assim.
float totalSquared = ax*ax+ay*ay+az*az;
float acceleration = Math.sqrt(totalSquared);
E enquanto isso funcionaria, devemos notar que calcular a raiz quadrada é realmente lento computacionalmente, então devemos evitar fazê-lo se possível. Afinal, poderíamos simplesmente calcular
e comparar isso a um valor limite predefinido.
float totalSquared = ax*ax+ay*ay+az*az;
Agora que temos um valor, vamos começar a controlar o LED. Poderíamos ter o LED aceso sempre que a aceleração total estiver abaixo de um valor limite, no entanto, seria mais fácil ler se o LED permanecesse aceso por um tempo após a detecção. Uma maneira de fazer isso é criar outra variável, vamos chamá-la de LEDOnTill, onde simplesmente escrevemos o tempo até onde queremos manter o LED aceso.
unsigned long LEDOnTill = 0;
Agora podemos atualizar o temporizador se detectarmos um evento de queda livre. Vamos usar um valor limite de 0.1 por enquanto. O Arduino fornece uma função chamada millis()
, que retorna o tempo desde que o programa começou em milissegundos.
if(totalSquared < 0.1)
{
LEDOnTill = millis() + 2000;
}
Finalmente, podemos apenas verificar se o tempo atual é mais ou menos do que o especificado em LEDOnTill
, e controlar o LED com base nisso. Aqui está como a nova função de loop se parece:
unsigned long LEDOnTill = 0;
void loop() {
// Ler Aceleração
float ax, ay, az;
readAcceleration(ax, ay, az);
// Calcular aceleração total (ao quadrado)
float totalSquared = ax*ax+ay*ay+az*az;
// Atualizar o temporizador se detectarmos uma queda
if(totalSquared < 0.1)
{
LEDOnTill = millis() + 2000;
}
// Controlar o LED com base no temporizador
if(LEDOnTill >= millis())
{
digitalWrite(LED, HIGH);
}else{
digitalWrite(LED, LOW);
}
}
Testando este programa, você pode ver quão rápido ele agora reage, já que não temos um atraso no loop. O LED acende imediatamente após deixar a mão ao ser jogado.
- Tente reintroduzir o atraso na função de loop. O que acontece?
- Atualmente, não temos nenhuma impressão no loop. Se você apenas adicionar uma instrução de impressão ao loop, a saída será realmente difícil de ler e a impressão diminuirá significativamente o tempo do ciclo do loop. Você consegue pensar em uma maneira de imprimir apenas uma vez por segundo, mesmo que o loop esteja rodando continuamente? Dica: veja como o temporizador do LED foi implementado.
- Crie seu próprio experimento, usando a aceleração ou a rotação para detectar algum tipo de evento.
Na próxima lição, deixaremos o domínio digital e tentaremos usar um estilo diferente de sensor - um medidor de luz analógico.