Lolin S2 Mini & Sparkfun BNO080

   iconI2C.png    volts33.png

Photo

Lolin S2 Mini & Sparkfun BNO080
Lolin S2 Mini & Sparkfun BNO080
Lolin S2 Mini & Sparkfun BNO080
Lolin S2 Mini & Sparkfun BNO080
Lolin S2 Mini & Sparkfun BNO080
Lolin S2 Mini & Sparkfun BNO080

Sketch

// LiLon ESP32-S2 with BNO080 IMU - Quaternion & Euler Angles
// Copyright (C) 2023 https://www.roboticboat.uk
// 44bfb3ca-2c8f-4e36-b123-6f2655c631e4
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// These Terms shall be governed and construed in accordance with the laws of
// England and Wales, without regard to its conflict of law provisions.
// IDE Arduino 1.8.19
// Register information (for example which register does what) was obtained from BNO080 datasheet v1.3
// Copyright © 2017, Hillcrest Laboratories, Inc. All rights reserved
#include <Wire.h>
#define I2C_SDA 3
#define I2C_SCL 5
#define COMMUNICATION_TIMEOUT 1000
#define I2C_BUFFER_SIZE 16
#define PACKET_BUFFER_SIZE 1000
#define COMMAND_PACKET_SIZE 21
struct Quaternion {
float real, i, j, k;
};
struct Euler {
float roll, pitch, yaw;
float roll2, pitch2, yaw2;
};
uint8_t nZeroReadings = 0;
uint8_t nReceived = 0;
uint8_t _i2cAddress = 0x4B;
uint8_t receiveBuffer[14];
uint8_t packetBuffer[PACKET_BUFFER_SIZE];
uint8_t commandPacket[COMMAND_PACKET_SIZE];
uint8_t packetSequencialNumber = 0;
uint8_t resetSequencialNumber = 0;
uint16_t packetBufferPtr;
float accelX, accelY, accelZ;
float gyroX, gyroY, gyroZ;
float magnetX, magnetY, magnetZ;
float linearAccelX, linearAccelY, linearAccelZ;
byte accel_status;
byte gyro_status;
byte magnet_status;
byte linearAccel_status;
byte quantRotationVec_status;
byte quantGameRotationVec_status;
int16_t accelXraw, accelYraw, accelZraw;
int16_t gyroXraw, gyroYraw, gyroZraw;
int16_t magnetXraw, magnetYraw, magnetZraw;
int16_t quantRotationVec_accuracy;
Quaternion quantRotationVec;
Quaternion quantGameRotationVec;
Euler euler;
void setup()
{
// Update the User
Serial.begin(115200);
// Start the i2c network
Wire.begin(I2C_SDA, I2C_SCL);
// Set the clock frequency for I2C communication to 400 Khz
Wire.setClock(400000);
// Initialise the BMO080 IMU module
SetupBMO080();
}
void loop()
{
receivePacket();
// Check quality of data
if (accelX == 0 && accelY == 0){nZeroReadings++;}
// If we have too many zeros, then restart the BNO080
if (nZeroReadings > 20){
// We are not synconised correctly
Serial.println("Out of synchronous with data received. Restart BNO080");
SetupBMO080();
nZeroReadings = 0;
}
// print the data
Serial.print("$ACC,");
Serial.print(accelX);
Serial.print(",");
Serial.print(accelY);
Serial.print(",");
Serial.print(accelZ);
Serial.print(",");
printStatus(accel_status);
Serial.print(" m/s^2\t");
// Serial.print("$lnrACC,");
// Serial.print(linearAccelX);
// Serial.print(",");
// Serial.print(linearAccelY);
// Serial.print(",");
// Serial.print(linearAccelZ);
// Serial.print(" m/s^2\t");
//
// Serial.print("$GYR,");
// Serial.print(gyroX);
// Serial.print(",");
// Serial.print(gyroY);
// Serial.print(",");
// Serial.print(gyroZ);
// Serial.print(" rad/s\t");
Serial.print("$MAG,");
Serial.print(magnetX);
Serial.print(",");
Serial.print(magnetY);
Serial.print(",");
Serial.print(magnetZ);
Serial.print(",");
printStatus(magnet_status);
Serial.print(" uTesla \t");
Serial.print("$QUA,");
Serial.print(quantRotationVec.real, 4);
Serial.print(",");
Serial.print(quantRotationVec.i, 4);
Serial.print(",");
Serial.print(quantRotationVec.j, 4);
Serial.print(",");
Serial.print(quantRotationVec.k, 4);
Serial.print(",");
printStatus(quantRotationVec_status);
Serial.print(",");
Serial.print(quantRotationVec_accuracy);
Serial.print(" rijk\t\t");
Quanternion2Euler(quantRotationVec);
Serial.print("$EUL,");
Serial.print(euler.yaw, 1);
Serial.print(",");
Serial.print(euler.roll, 1);
Serial.print(",");
Serial.print(euler.pitch, 1);
Serial.println(" degrees yaw,roll,pitch");
delay(10);
}
void SetupBMO080()
{
// Set the commandPacket to zero
memset(commandPacket, 0, sizeof(commandPacket));
delay(1000);
// Restart the BMO080 module
Restart();
// Set Accelerator, reportID 0x01 at 50ms intervals
SetPacketInterval(0x01, 50);
// Set Gyroscope, reportID 0x02 at 50ms intervals
SetPacketInterval(0x02, 50);
// Set Magnet, reportID 0x03 at 50ms intervals
SetPacketInterval(0x03, 50);
// Set Linear Accelerator, reportID 0x04 at 50ms intervals
SetPacketInterval(0x04, 50);
// Set Raw Accelerator, reportID 0x14 at 50ms intervals
SetPacketInterval(0x14, 50);
// Set Raw Gyroscope, reportID 0x15 at 50ms intervals
SetPacketInterval(0x15, 50);
// Set Raw Gyroscope, reportID 0x16 at 50ms intervals
SetPacketInterval(0x16, 50);
// Set Rotation Vector, reportID 0x08 at 50ms intervals
SetPacketInterval(0x05, 50);
// Set Game Rotation Vector, reportID 0x08 at 50ms intervals
SetPacketInterval(0x08, 50);
}
void Restart() {
// Allocate memory
uint8_t resetPacket[5];
// Size of data transmission
resetPacket[0] = 0x05;
resetPacket[1] = 0x00;
// Channel 1
resetPacket[2] = 0x01;
// Sequence number
resetPacket[3] = resetSequencialNumber++;
// Data command
resetPacket[4] = 0x01;
// Send the packet
uint8_t status = writeBytes(5, resetPacket);
if (status != 0){
Serial.println("Restart status error");
}
// Wait for the BNO080 to restart. This delay is essential
delay(2000);
}
void SetPacketInterval(uint8_t reportID, uint16_t milliSeconds)
{
// Create header
// Least significant byte (LSB) size of data
commandPacket[0] = (COMMAND_PACKET_SIZE) & 0xFF;
// Most significant byte (MSB) size of data
commandPacket[1] = ((COMMAND_PACKET_SIZE) >> 8) & 0xFF;
// Channel number hard coded at 2
commandPacket[2] = 2;
// Sequencial 8-bit number to indicate following packet
commandPacket[3] = packetSequencialNumber++;
// Set options Command reference
commandPacket[4] = 0xFD;
// ReportID reference
commandPacket[5] = reportID;
// Time interval in microseconds
uint32_t microSeconds = (uint32_t)milliSeconds * 1000;
// Set the microsecond interval
commandPacket[9] = microSeconds & 0xFF;
commandPacket[10] = (microSeconds >> 8) & 0xFF;
commandPacket[11] = (microSeconds >> 16) & 0xFF;
commandPacket[12] = (microSeconds >> 24) & 0xFF;
// Send the packet
uint8_t status = writeBytes(COMMAND_PACKET_SIZE, commandPacket);
if (status != 0){
Serial.print("SetPacketInterval status error");
}
}
float Convert2float(int16_t intvalue, float divisor) {
float fvalue = intvalue;
return fvalue / divisor;
}
uint8_t receivePacket()
{
// Header bytes to receive
uint8_t numHeaderBytes = 4;
uint8_t headerBuffer[numHeaderBytes];
uint8_t dataBuffer[I2C_BUFFER_SIZE];
//char showHex[3];
// Reset packet buffer pointer
packetBufferPtr = 0;
// Number of bytes to receive
nReceived = Wire.requestFrom(_i2cAddress, numHeaderBytes);
if (nReceived != numHeaderBytes) {
Serial.println("failed 3");
//Wire.flush();
return 0;
}
// Setup timeout parameter
int timeout = COMMUNICATION_TIMEOUT;
// Wait for the bytes to arrive.
// Don't wait forever as this will hang the whole program
while ((Wire.available() < numHeaderBytes) && (timeout-- > 0))
delay(1);
// Waited too long
if (timeout <= 0) return (uint8_t)5;
// Read the data into the headerBuffer
for (uint8_t i = 0; i < numHeaderBytes; i++)
{
headerBuffer[i] = Wire.read();
//sprintf (showHex, "%02x", headerBuffer[i]);
//Serial.print(showHex);
}
// Data package length
uint16_t numDataBytes = (uint16_t)(headerBuffer[1] << 8 | headerBuffer[0]);
if (numDataBytes == 0) return (uint8_t)8;
numDataBytes = numDataBytes - numHeaderBytes;
// Need to break up into 32 byte reads
uint16_t bytesRemaining = numDataBytes;
while (bytesRemaining > 0)
{
uint16_t dataBytes = bytesRemaining;
if (dataBytes > I2C_BUFFER_SIZE - 4) dataBytes = I2C_BUFFER_SIZE - 4;
// Number of bytes to receive
nReceived = Wire.requestFrom(_i2cAddress, (uint8_t)(dataBytes + 4));
if (nReceived != dataBytes + 4) {
Serial.println("failed 4");
return (uint8_t)55;
}
// Skip over the SHTP header bytes 0,1,2 & 3
for (uint8_t i = 0; i < 4; i++) Wire.read();
// Read the data into the headerBuffer
for (uint8_t i = 0; i < dataBytes; i++)
{
dataBuffer[i] = Wire.read();
packetBuffer[packetBufferPtr] = dataBuffer[i];
packetBufferPtr++;
if (packetBufferPtr == PACKET_BUFFER_SIZE) packetBufferPtr = PACKET_BUFFER_SIZE - 1;
}
bytesRemaining = bytesRemaining - dataBytes;
}
// Lets try and process this packetBuffer.
uint16_t tmp = 5;
uint8_t inc = 0;
while (tmp < packetBufferPtr) {
//Serial.print("[");
//sprintf (showHex, "%02x", packetBuffer[tmp]);
//Serial.print(showHex);
//Serial.print(": size ");
inc = processReport(packetBuffer[tmp], tmp);
//Serial.print(inc);
//Serial.print("] ");
if (inc == 0) break;
tmp = tmp + inc;
}
//Serial.println("");
return (uint8_t)0;
}
uint8_t processReport(uint8_t reportID, uint16_t tmp)
{
// Want to know the number of bytes in the report
switch (reportID) {
case 0x01:
// Accelerometer (0x01)
// The Q point is 8. 2^8=256
accel_status = packetBuffer[tmp + 2] & 0x03;
accelX = Convert2float((int16_t) packetBuffer[tmp + 5] << 8 | packetBuffer[tmp + 4], 256.0f);
accelY = Convert2float((int16_t) packetBuffer[tmp + 7] << 8 | packetBuffer[tmp + 6], 256.0f);
accelZ = Convert2float((int16_t) packetBuffer[tmp + 9] << 8 | packetBuffer[tmp + 8], 256.0f);
return (uint8_t)10;
case 0x02:
// Gyroscope Calibrated (0x02)
// The Q point is 9. 2^9=512
gyro_status = packetBuffer[tmp + 2] & 0x03;
gyroX = Convert2float((int16_t) packetBuffer[tmp + 5] << 8 | packetBuffer[tmp + 4], 512.0f);
gyroY = Convert2float((int16_t) packetBuffer[tmp + 7] << 8 | packetBuffer[tmp + 6], 512.0f);
gyroZ = Convert2float((int16_t) packetBuffer[tmp + 9] << 8 | packetBuffer[tmp + 8], 512.0f);
return (uint8_t)10;
case 0x03:
// Magnetic Field Calibrated (0x03)
// The Q point is 4. 2^4 = 16
magnet_status = packetBuffer[tmp + 2] & 0x03;
magnetX = Convert2float((int16_t) packetBuffer[tmp + 5] << 8 | packetBuffer[tmp + 4], 16.0f);
magnetY = Convert2float((int16_t) packetBuffer[tmp + 7] << 8 | packetBuffer[tmp + 6], 16.0f);
magnetZ = Convert2float((int16_t) packetBuffer[tmp + 9] << 8 | packetBuffer[tmp + 8], 16.0f);
return (uint8_t)10;
case 0x04:
// Linear Acceleration (0x04)
// The Q point is 8. 2^8=256
linearAccel_status = packetBuffer[tmp + 2] & 0x03;
linearAccelX = Convert2float((int16_t) packetBuffer[tmp + 5] << 8 | packetBuffer[tmp + 4], 256.0f);
linearAccelY = Convert2float((int16_t) packetBuffer[tmp + 7] << 8 | packetBuffer[tmp + 6], 256.0f);
linearAccelZ = Convert2float((int16_t) packetBuffer[tmp + 9] << 8 | packetBuffer[tmp + 8], 256.0f);
return (uint8_t)10;
case 0x05:
// Rotation Vector (0x05)
quantRotationVec_status = packetBuffer[tmp + 2] & 0x03;
quantRotationVec.i = Convert2float((int16_t) packetBuffer[tmp + 5] << 8 | packetBuffer[tmp + 4], 16384.0f);
quantRotationVec.j = Convert2float((int16_t) packetBuffer[tmp + 7] << 8 | packetBuffer[tmp + 6], 16384.0f);
quantRotationVec.k = Convert2float((int16_t) packetBuffer[tmp + 9] << 8 | packetBuffer[tmp + 8], 16384.0f);
quantRotationVec.real = Convert2float((int16_t) packetBuffer[tmp + 11] << 8 | packetBuffer[tmp + 10], 16384.0f);
quantRotationVec_accuracy = (int16_t) packetBuffer[tmp + 13] << 8 | packetBuffer[tmp + 12];
return (uint8_t)14;
case 0x06:
// Gravity (0x06)
return (uint8_t)10;
case 0x07:
// Gravity Uncalibrated (0x07)
return (uint8_t)16;
case 0x08:
// Game Rotation Vector (0x08)
// The Q point is 14. 2^14 = 16384
quantGameRotationVec_status = packetBuffer[tmp + 2] & 0x03;
quantGameRotationVec.i = Convert2float((int16_t) packetBuffer[tmp + 5] << 8 | packetBuffer[tmp + 4], 16384.0f);
quantGameRotationVec.j = Convert2float((int16_t) packetBuffer[tmp + 7] << 8 | packetBuffer[tmp + 6], 16384.0f);
quantGameRotationVec.k = Convert2float((int16_t) packetBuffer[tmp + 9] << 8 | packetBuffer[tmp + 8], 16384.0f);
quantGameRotationVec.real = Convert2float((int16_t) packetBuffer[tmp + 11] << 8 | packetBuffer[tmp + 10], 16384.0f);
return (uint8_t)12;
case 0x09:
// Geomagnetic Rotation Vec (0x09)
return (uint8_t)14;
case 0x0F:
// Magnetic Field Uncalibrat (0x0F)
return (uint8_t)16;
case 0x10:
// Tap Detector (0x10)
return (uint8_t)5;
case 0x11:
// Step Counter (0x11)
return (uint8_t)12;
case 0x12:
// Significant Motion (0x12)
return (uint8_t)6;
case 0x13:
// Stability Classifier (0x13)
return (uint8_t)6;
case 0x14:
// Raw Accelerometer (0x14)
accelXraw = (int16_t) packetBuffer[tmp + 5] << 8 | packetBuffer[tmp + 4];
accelYraw = (int16_t) packetBuffer[tmp + 7] << 8 | packetBuffer[tmp + 6];
accelZraw = (int16_t) packetBuffer[tmp + 9] << 8 | packetBuffer[tmp + 8];
return (uint8_t)16;
case 0x15:
// Raw Gyroscope (0x15)
gyroXraw = (int16_t) packetBuffer[tmp + 5] << 8 | packetBuffer[tmp + 4];
gyroYraw = (int16_t) packetBuffer[tmp + 7] << 8 | packetBuffer[tmp + 6];
gyroZraw = (int16_t) packetBuffer[tmp + 9] << 8 | packetBuffer[tmp + 8];
return (uint8_t)16;
case 0x16:
// Raw Magnetometer (0x16)
magnetXraw = (int16_t) packetBuffer[tmp + 5] << 8 | packetBuffer[tmp + 4];
magnetYraw = (int16_t) packetBuffer[tmp + 7] << 8 | packetBuffer[tmp + 6];
magnetZraw = (int16_t) packetBuffer[tmp + 9] << 8 | packetBuffer[tmp + 8];
return (uint8_t)16;
case 0x18:
// Step Detector (0x18)
return (uint8_t)8;
case 0x19:
// Shake Detector (0x19)
return (uint8_t)6;
case 0x1A:
// Flip Detector (0x1A)
return (uint8_t)6;
case 0x1B:
// Pickup Detector (0x1B)
return (uint8_t)6;
case 0x1C:
// Stability Detector (0x1C)
return (uint8_t)6;
case 0x1F:
// Sleep Detector (0x1F)
return (uint8_t)6;
case 0x20:
// Tilt Detector (0x20)
return (uint8_t)6;
case 0x28:
// ARVR-Stabilized Rotation Vector (0x28)
return (uint8_t)14;
case 0x29:
// ARVR-Stabilized Game Rotation Vector (0x29)
return (uint8_t)12;
case 0x2A:
// Gyro-Integrated Rotation Vector (0x2A)
return (uint8_t)14;
case 0xFA:
// Timestamp Rebase (0xFA)
return (uint8_t)5;
default:
return 0;
}
}
//========== i2c commands ===============
//
// NACK errors are
// 0: OK - successful send
// 1: Send receiveBuffer too large for the twi receiveBuffer.
// 2: Register address was sent and a NACK received. Master should send a STOP condition.
// 3: Data was sent and a NACK received. Slave has sent all data. Master can send a STOP condition.
// 4: A different twi error took place
// 5: Time out on waiting for data to be received.
// 6: Checking data was correctly written failed
uint8_t readRegister(uint8_t registerAddress, uint8_t* dest)
{
// Initialise the transmit message buffer
Wire.beginTransmission(_i2cAddress);
// Add the i2c module Register address to transmit message buffer
Wire.write(registerAddress);
// Transmit message and accept NACK response
// The Wire.endTransmission() is used when writing data only
// Send a restart to keep connection alive
uint8_t nackCatcher = Wire.endTransmission(false);
// Return if we have a connection problem
if (nackCatcher != 0) return nackCatcher;
// Number of bytes to receive
nReceived = Wire.requestFrom(_i2cAddress, (uint8_t)1);
if (nReceived == 0) {
Serial.println("failed 1");
return (uint8_t)5;
}
// Read the data
*dest = Wire.read();
// All ok
return (uint8_t)0;
}
uint8_t readRegisters(uint8_t registerAddress, uint8_t numBytes, uint8_t* dest)
{
// Initialise the transmit message buffer
Wire.beginTransmission(_i2cAddress);
// Add the i2c module Register address to transmit message buffer
Wire.write(registerAddress);
// Transmit message and accept NACK response
// The Wire.endTransmission() is used when writing data only
// Send a restart to keep connection alive
uint8_t nackCatcher = Wire.endTransmission(false);
// Return if we have a connection problem
if (nackCatcher != 0) return nackCatcher;
// Number of bytes to receive
nReceived = Wire.requestFrom(_i2cAddress, numBytes);
if (nReceived != numBytes) {
Serial.println("failed 2");
return (uint8_t)5;
}
uint8_t i = 0;
// read the data into the receiveBuffer
while ( Wire.available() )
{
dest[i++] = Wire.read();
}
// All ok
return (uint8_t)0;
}
uint8_t writeRegister(uint8_t registerAddress, uint8_t dataByte) {
// Initialise the transmit message buffer
Wire.beginTransmission(_i2cAddress);
// Add the i2c module Register address to transmit message buffer
Wire.write(registerAddress);
// Add the command data to the transmit message buffer
Wire.write(dataByte);
// Transmit message and accept NACK response
// The Wire.endTransmission() is used when writing data only
uint8_t nackCatcher = Wire.endTransmission();
// Return if we have a connection problem
if (nackCatcher != 0) return nackCatcher;
// Wait for 10ms
delay(10);
// Allocate memory.
uint8_t checkByte;
// We are now going to check the operation was successful
// This is not really required, but most of the time we are debugging
// Read the Register
readRegister(registerAddress, &checkByte);
// Check the data read back is the same
if (checkByte == dataByte) return (uint8_t)0;
// Update the User
Serial.println("ERROR: i2c module register not accepting writes");
// An error happened
return (uint8_t)6;
}
uint8_t writeBytes(uint8_t numBytes, uint8_t* dataBytes) {
// Initialise the transmit message buffer
Wire.beginTransmission(_i2cAddress);
// Loop over the bytes to send
for (uint8_t i = 0; i < numBytes; i++)
{
// Add the command data to the transmit message buffer
Wire.write(dataBytes[i]);
}
// Transmit message and accept NACK response
// The Wire.endTransmission() is used when writing data only
uint8_t nackCatcher = Wire.endTransmission();
// Return nack
return nackCatcher;
}
void Quanternion2Euler(Quaternion q)
{
// Calculates the Euler ZYX angles
// Derived by comparing the Quaternion and Euler rotation matrices
// Return angles in degrees (not radians)
float norm = sqrt(q.real * q.real + q.i * q.i + q.j * q.j + q.k * q.k);
float dqw = q.real / norm;
float dqx = q.i / norm;
float dqy = q.j / norm;
float dqz = q.k / norm;
float ysqr = dqy * dqy;
// roll (x-axis rotation)
float t0 = +2.0 * (dqw * dqx + dqy * dqz);
float t1 = +1.0 - 2.0 * (dqx * dqx + ysqr);
euler.roll = atan2(t0, t1) * 180.0f / PI;
// pitch (y-axis rotation)
float t2 = +2.0 * (dqw * dqy - dqz * dqx);
t2 = t2 > 1.0 ? 1.0 : t2;
t2 = t2 < -1.0 ? -1.0 : t2;
euler.pitch = asin(t2) * 180.0f / PI;
// yaw (z-axis rotation)
float t3 = +2.0 * (dqw * dqz + dqx * dqy);
float t4 = +1.0 - 2.0 * (ysqr + dqz * dqz);
euler.yaw = atan2(t3, t4) * 180.0f / PI;
euler.yaw = -euler.yaw;
if (euler.yaw > 360) euler.yaw -= 360;
if (euler.yaw < 0) euler.yaw += 360;
}
void printStatus(byte level) {
// Want to know the number of bytes in the report
switch (level) {
case 0x00:
Serial.print("D");
return;
case 0x01:
Serial.print("C");
return;
case 0x02:
Serial.print("B");
return;
case 0x03:
Serial.print("A");
return;
}
}
RoboticBoat.uk