В данной статье мы научимся измерять угол и отслеживать ориентацию с помощью платы Arduino и датчика акселерометра ADXL345. Приведены схема, программа и видео, демонстрирующее работу проекта (в конце статьи). Сначала мы рассмотрим как работает датчик и как считывать с него данные, а затем с помощью среды разработки Processing мы сделаем 3D-визуализацию ориентации акселерометра.
Ранее на нашем сайте мы рассматривали подключение акселерометров к плате Arduino в следующих статьях:
- счетчик шагов (шагомер) на Arduino и акселерометре;
- робот на Arduino управляемый с помощью жестов рук и акселерометра;
- подключение гироскопа MPU6050 к Arduino.
Необходимые компоненты
- Плата Arduino Nano (купить на AliExpress).
- Акселерометр (ADXL345) (купить на AliExpress).
- Соединительные провода.
Реклама: ООО "АЛИБАБА.КОМ (РУ)" ИНН: 7703380158
Как работает акселерометр ADXL345
Датчик ADXL345 представляет собой 3-осевой акселерометр, который может измерять как статические, так и динамические силы ускорения. Сила земного притяжения является типичным примером статической силы, тогда как динамические силы могут быть вызваны вибрациями, различными движениями и т. д.
Единицей измерения ускорения является метр на секунду в квадрате (м/с^2). Однако датчики акселерометра обычно выражают измерения в «g» или силе тяжести. Один «g» — это величина силы земного притяжения, равная 9,8 метра в секунду в квадрате.
Итак, если у нас есть акселерометр, расположенный горизонтально, с его осью Z, направленной вверх, в противоположность гравитационной силе, выход датчика по оси Z будет 1g. С другой стороны, выходы X и Y будут равны нулю, потому что гравитационная сила перпендикулярна этим осям и никак на них не влияет.
Если мы перевернем датчик вверх дном, то выход по оси Z будет равен -1g. Это означает, что выходные данные датчика из-за его ориентации на гравитацию могут варьироваться от -1g до +1g.
Таким образом, в соответствии с этими данными и с помощью некоторой математики тригонометрии мы можем рассчитать угол, под которым расположен датчик.
Схема проекта
Схема подключения акселерометра ADXL345 к плате Arduino Nano приведена на следующем рисунке.
Акселерометр ADXL345 работает по интерфейсу I2C, поэтому для его подключения к плате Arduino необходимо всего два провода. Ну и еще два провода необходимо для подачи питания на датчик. Более подробно про использование интерфейса I2C в плате Arduino вы можете прочитать в этой статье.
Исходный код программы (скетча)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
/* Arduino and ADXL345 Accelerometer Tutorial by Dejan, https://howtomechatronics.com */ #include <Wire.h> // Wire library - used for I2C communication int ADXL345 = 0x53; // The ADXL345 sensor I2C address float X_out, Y_out, Z_out; // Outputs void setup() { Serial.begin(9600); // Initiate serial communication for printing the results on the Serial monitor Wire.begin(); // Initiate the Wire library // Set ADXL345 in measuring mode Wire.beginTransmission(ADXL345); // Start communicating with the device Wire.write(0x2D); // Access/ talk to POWER_CTL Register - 0x2D // Enable measurement Wire.write(8); // (8dec -> 0000 1000 binary) Bit D3 High for measuring enable Wire.endTransmission(); delay(10); } void loop() { // === Read acceleromter data === // Wire.beginTransmission(ADXL345); Wire.write(0x32); // Start with register 0x32 (ACCEL_XOUT_H) Wire.endTransmission(false); Wire.requestFrom(ADXL345, 6, true); // Read 6 registers total, each axis value is stored in 2 registers X_out = ( Wire.read()| Wire.read() << 8); // X-axis value X_out = X_out/256; //For a range of +-2g, we need to divide the raw values by 256, according to the datasheet Y_out = ( Wire.read()| Wire.read() << 8); // Y-axis value Y_out = Y_out/256; Z_out = ( Wire.read()| Wire.read() << 8); // Z-axis value Z_out = Z_out/256; Serial.print("Xa= "); Serial.print(X_out); Serial.print(" Ya= "); Serial.print(Y_out); Serial.print(" Za= "); Serial.println(Z_out); } |
Объяснение работы программы
Первым делом в программе подключим библиотеку Wire.h, которая используется для связи по протоколу I2C. Каждое устройство, использующее связь по протоколу I2C, имеет уникальный адрес I2C, и этот адрес можно найти в техническом описании датчика (даташит на ADXL345 ). После того, как мы определили I2C адрес и инициализировали переменные для трех его выходов, в функции setup() нам сначала необходимо инициализировать библиотеку Wire, а затем установить акселерометр в режим измерения. Для этого, если мы снова взглянем на таблицу данных, представленную на следующем рисунке, мы увидим, что нам нужно установить бит D3 регистра POWER_CTL в состояние HIGH.
Таким образом, используя функцию beginTransmission(), мы сначала запускаем связь, затем с помощью функции write() мы сообщаем, к какому регистру мы хотим получить доступ, и потом снова с помощью функции write() мы устанавливаем бит D3 в состояние HIGH, записывая число 8 в десятичные числа, которые соответствуют установке бита D3 в в состояние HIGH.
1 2 3 4 5 6 |
// Set ADXL345 in measuring mode Wire.beginTransmission(ADXL345); // Start communicating with the device Wire.write(0x2D); // Access/ talk to POWER_CTL Register - 0x2D // Enable measurement Wire.write(8); // (8dec -> 0000 1000 binary) Bit D3 High for measuring enable Wire.endTransmission(); |
После этого в функции loop() мы будем считывать данные с датчика. Данные для каждой оси хранятся в двух байтах или регистрах. Адреса этих регистров мы можем узнать из даташита на датчик.
Чтобы прочитать их все, мы начинаем с первого регистра и затем с помощью функции requestionFrom() считываем 6 регистров. Далее, используя функцию read(), мы считываем данные из каждого регистра, и, поскольку выходные данные представляют собой дополнения до двух, мы соответствующим образом комбинируем их, чтобы получить правильные значения.
1 2 3 4 5 6 7 8 9 10 11 |
// === Read acceleromter data === // Wire.beginTransmission(ADXL345); Wire.write(0x32); // Start with register 0x32 (ACCEL_XOUT_H) Wire.endTransmission(false); Wire.requestFrom(ADXL345, 6, true); // Read 6 registers total, each axis value is stored in 2 registers X_out = ( Wire.read()| Wire.read() << 8); // X-axis value X_out = X_out/256; //For a range of +-2g, we need to divide the raw values by 256, according to the datasheet Y_out = ( Wire.read()| Wire.read() << 8); // Y-axis value Y_out = Y_out/256; Z_out = ( Wire.read()| Wire.read() << 8); // Z-axis value Z_out = Z_out/256; |
Выходные значения датчика фактически зависят от выбранной чувствительности, которая может варьироваться от +-2g до +-16g. Чувствительность по умолчанию составляет +-2g, поэтому нам нужно разделить выходное значение на 256, чтобы получить значения от -1 до +1g. 256 LSB/g означает, что у нас есть 256 отсчетов на грамм.
В зависимости от используемого приложения мы можем выбрать соответствующую чувствительность. В нашем случае для отслеживания ориентации подойдет чувствительность +-2g, но для приложений, где нам необходимо определять более высокую силу ускорения, например внезапные движения, удары и т. д., целесообразно выбрать другие диапазоны чувствительности, используя регистр DATA_FORMAT и его биты D1 и D0.
Калибровка акселерометра ADXL345
Первоначальный запуск акселерометра показал что считываемые с него данные имеют определенную погрешность, которая была особенно сильной по его оси Z - в районе 0,1g.
Чтобы решить эту проблему, нам нужно откалибровать акселерометр, используя для этого 3 его регистра калибровки смещения. Для этого нам нужно расположить датчик горизонтально и распечатать значения RAW ("сырые"), не деля их на 256.
Теперь мы можем заметить насколько отклоняются значения с выходов, в нашем случае выход Z был около 283. То есть ошибка составляет +27 единиц. Теперь нам нужно разделить это значение на 4, и это даст нам число, которое нам нужно записать в регистр смещения оси Z. И если после этого мы загрузим код в плату, то выход датчика по оси Z будет ровно 256 или 1 g, как и должно быть.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// This code goes in the SETUP section // Off-set Calibration //X-axis Wire.beginTransmission(ADXL345); Wire.write(0x1E); // X-axis offset register Wire.write(1); Wire.endTransmission(); delay(10); //Y-axis Wire.beginTransmission(ADXL345); Wire.write(0x1F); // Y-axis offset register Wire.write(-2); Wire.endTransmission(); delay(10); //Z-axis Wire.beginTransmission(ADXL345); Wire.write(0x20); // Z-axis offset register Wire.write(-7); Wire.endTransmission(); delay(10); |
При необходимости мы должны откалибровать другую ось, используя тот же метод. И сразу заметим, что эта калибровка не записывается в регистры постоянно. Нам необходимо записывать эти значения в регистры при каждом включении датчика.
Как только мы закончим с калибровкой, теперь мы можем, наконец, рассчитать значения Roll и Pitch, или вращения вокруг оси X и вращение вокруг оси Y в градусах, используя следующие две формулы.
1 2 3 |
// Calculate Roll and Pitch (rotation around X-axis, rotation around Y-axis) roll = atan(Y_out / sqrt(pow(X_out, 2) + pow(Z_out, 2))) * 180 / PI; pitch = atan(-1 * X_out / sqrt(pow(Y_out, 2) + pow(Z_out, 2))) * 180 / PI; |
3D-визуализация работы акселерометра ADXL345
Теперь сделаем пример 3D-визуализации акселерометра.
Для этого мы используем тот же самый код, который отправляет значения Roll и Pitch через последовательный порт. Вот полный код Arduino:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
/* Arduino and ADXL345 Accelerometer - 3D Visualization Example by Dejan, https://howtomechatronics.com */ #include <Wire.h> // Wire library - used for I2C communication int ADXL345 = 0x53; // The ADXL345 sensor I2C address float X_out, Y_out, Z_out; // Outputs float roll,pitch,rollF,pitchF=0; void setup() { Serial.begin(9600); // Initiate serial communication for printing the results on the Serial monitor Wire.begin(); // Initiate the Wire library // Set ADXL345 in measuring mode Wire.beginTransmission(ADXL345); // Start communicating with the device Wire.write(0x2D); // Access/ talk to POWER_CTL Register - 0x2D // Enable measurement Wire.write(8); // Bit D3 High for measuring enable (8dec -> 0000 1000 binary) Wire.endTransmission(); delay(10); //Off-set Calibration //X-axis Wire.beginTransmission(ADXL345); Wire.write(0x1E); Wire.write(1); Wire.endTransmission(); delay(10); //Y-axis Wire.beginTransmission(ADXL345); Wire.write(0x1F); Wire.write(-2); Wire.endTransmission(); delay(10); //Z-axis Wire.beginTransmission(ADXL345); Wire.write(0x20); Wire.write(-9); Wire.endTransmission(); delay(10); } void loop() { // === Read acceleromter data === // Wire.beginTransmission(ADXL345); Wire.write(0x32); // Start with register 0x32 (ACCEL_XOUT_H) Wire.endTransmission(false); Wire.requestFrom(ADXL345, 6, true); // Read 6 registers total, each axis value is stored in 2 registers X_out = ( Wire.read() | Wire.read() << 8); // X-axis value X_out = X_out / 256; //For a range of +-2g, we need to divide the raw values by 256, according to the datasheet Y_out = ( Wire.read() | Wire.read() << 8); // Y-axis value Y_out = Y_out / 256; Z_out = ( Wire.read() | Wire.read() << 8); // Z-axis value Z_out = Z_out / 256; // Calculate Roll and Pitch (rotation around X-axis, rotation around Y-axis) roll = atan(Y_out / sqrt(pow(X_out, 2) + pow(Z_out, 2))) * 180 / PI; pitch = atan(-1 * X_out / sqrt(pow(Y_out, 2) + pow(Z_out, 2))) * 180 / PI; // Low-pass filter rollF = 0.94 * rollF + 0.06 * roll; pitchF = 0.94 * pitchF + 0.06 * pitch; Serial.print(rollF); Serial.print("/"); Serial.println(pitchF); } |
Теперь в среде разработки Processing нам нужно получить эти значения и использовать их для поворота 3D-объекта, который мы будем создавать. Вот полный код processing для этого:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
/* Arduino and ADXL345 Accelerometer - 3D Visualization Example by Dejan, https://howtomechatronics.com */ import processing.serial.*; import java.awt.event.KeyEvent; import java.io.IOException; Serial myPort; String data=""; float roll, pitch; void setup() { size (960, 640, P3D); myPort = new Serial(this, "COM8", 9600); // starts the serial communication myPort.bufferUntil('\n'); } void draw() { translate(width/2, height/2, 0); background(33); textSize(22); text("Roll: " + int(roll) + " Pitch: " + int(pitch), -100, 265); // Rotate the object rotateX(radians(roll)); rotateZ(radians(-pitch)); // 3D 0bject textSize(30); fill(0, 76, 153); box (386, 40, 200); // Draw box textSize(25); fill(255, 255, 255); text("www.HowToMechatronics.com", -183, 10, 101); //delay(10); //println("ypr:\t" + angleX + "\t" + angleY); // Print the values to check whether we are getting proper values } // Read data from the Serial Port void serialEvent (Serial myPort) { // reads the data from the Serial Port up to the character '.' and puts it into the String variable "data". data = myPort.readStringUntil('\n'); // if you got any bytes other than the linefeed: if (data != null) { data = trim(data); // split the string at "/" String items[] = split(data, '/'); if (items.length > 1) { //--- Roll,Pitch in degrees roll = float(items[0]); pitch = float(items[1]); } } } |
Описание работы этого кода: итак, здесь нам нужно включить последовательную библиотеку, задать последовательный порт и скорость передачи данных, которая должна соответствовать скорости передачи скетча, загруженного в плату Arduino. Затем мы считываем поступающие данные и помещаем их в соответствующие переменные крена и тангажа. В основном цикле отрисовки мы используем эти значения для поворота 3D-объекта, и в данном случае это простое поле с определенным цветом и текстом на нем.
Если мы запустим скетч, появится 3D-объект, который будет отслеживать ориентацию датчика акселерометра. Здесь мы можем заметить, что объект на самом деле немного шатается, потому что акселерометр улавливает не только гравитационную силу, но и небольшие колебания, создаваемые движениями нашей руки. Чтобы получить более плавный результат, мы можем использовать простой фильтр нижних частот. Автор проекта реализовал в коде Arduino следующий фильтр, который берет 94% предыдущего состояния и добавляет 6% текущего состояния или угла.
1 2 3 |
// Low-pass filter rollF = 0.94 * rollF + 0.06 * roll; pitchF = 0.94 * pitchF + 0.06 * pitch; |
С помощью этого фильтра мы можем заметить, что объект теперь движется намного более плавно, но есть и побочный эффект — более медленная реакция. Мы также можем заметить, что нам не хватает Yaw или вращения вокруг оси Z. Используя только данные 3-осевого акселерометра, мы не можем рассчитать рысканье, к сожалению.
Чтобы сделать это и улучшить общую производительность нашего датчика отслеживания ориентации, нам нужно подключить в наш проект дополнительный датчик - гироскоп, и объединить его данные с акселерометром.
Таким образом, для решения этой задачи мы можем либо использовать акселерометр ADXL345 в сочетании с гироскопом гироскопа, либо использовать MPU6050, который сочетает функции 3-осевого акселерометра и 3-осевого гироскопа, интегрированные в один чип.