Wednesday, July 11, 2012

ADXL335 - аналоговый акселерометр

Достаточно давно я ничего не писал в свой блог. Вместо бессмысленных оправданий - лучше сразу перейти к делу.

В этом посте я постараюсь рассказать, как можно используя arduino и датчик ADXL335 получить угол относительного отклонения. Начиная писать этот пост, я думал, что он будет самым простым - здесь не понадобится никакой обвязки в схеме. Но, так как пришлось вспомнить немного физики, немного геометрии и вообще пошевелить мозгами - пост набрал вес и теперь по сложности сопоставим со всеми остальными, разве что здесь будет побольше теории.


ADXL335


Итак, давайте для начала немного разберемся в том, что же такое ADXL335. ADXL335 - это аналоговый трехосный акселерометр.
Акселерометр - приспособление для измерения ускорения. Один важный момент, до которого я сам не сразу дошел, важно понимать, что ускорение может быть двух видов: динамическое и статическое.
С динамическим ускорением все должно быть более-менее понятно. Лично я, пока не начал разбираться с акселерометром, только про о нем и знал. Толкнули датчик, предали ему ускорение, он должен это зафиксировать.
Статическое ускорение - это воздействие силы тяжести на наш датчик. Дело в том, что к датчику даже в состоянии полного покоя приложено статическое ускорение равное g.
UPD: Совершенно правильно было замечено в комментариях. Термины динамическое и статическое ускорение перекачевали из англоязычной системы образования и спецификиций на приборы (static & dynamic acceleration). На самом деле, более правильно говорить о силах приложеных к датчику. Я не стал переписывать поста, так как возможно на данном этапе будет проще проводить аналогию со спецификацией, однако следует учитывать, что это лишь подущения и "вольности" перевода.
Напомню, из школьного курса физики: g - это ускорение свободного падения. И это именно то самое g которое не раз упоминается в спецификации нашего устройства.
Так вот, датчик ADXL335 умеет улавливать оба этих ускорения. Самое интересно для меня, как оказалось, именно возможность измерения статического ускорения, так как зная проекции его вектора можно спокойно вычислить угол на который отклонился наш датчик относительно некого нулевого значения.
Лучше всего воздействие статического ускорения на показания датчика показаны в спецификации самого датчика.
Фактически в этой картинке кроется основная тайна этого статического ускорения. Я, однако, игнорировал её очень долго. На ней показано, какими показаниями буду, если датчик ворочать разными способами. Главное - не обращайте особенного внимания на надпись TOP, она несколько вводит в заблуждение. Относительное положение датчика необходимо отслеживать по маленькому беленькому кружечку-метке.
Итак, для начала разберемся с правой частью картинки, на которой меняет свое значение Zout. Согласно этой картинке, если мы положим наш датчик контактами вниз, то значение по оси Z будет равно единице (точнее одному g). Как я уже говорил - это значение ни что иное как проекция вектора статического ускорения на ось нашего датчика. Так как в данном случае вектор совпадает с осью Z, а ускорения свободного падения равняется g, мы и имеет значение Zout = 1g.
Если же мы перевернем датчик контактами вверх, то значение Zout изменится на противоположное.
Стоит отметить, что все остальные ускорения равны нулю, связано это с уже упомянутым совпадением вектора статического ускорения с осью Z, а так же состоянием покоя всего датчика.
Аналогично можно разобрать все остальные пары. Единственное отличие - то, что датчик будет находится на ребре или боку.
Важно так же понимать, что длинна этого вектора в состоянии покоя датчика всегда будет равняться единице. Вектор далеко не всегда будет совпадать с какой-либо из осей - скорее такой вариант, это исключение из правил. Чаще всего этот вектор будет размазан по всем трем осям одновременно. Все же в трехмерном пространстве живем.
ВАЖНО! В отличии от всех рассмотренных ранее, наш сегодняшний испытуемый питается от 3-х вольт. Максимально допустимым значение напряжения для ADXL335 является 3,6 вольта, так что для опыта я буду использовать выход +3,3В на самой плате Arduino, и т.к. это несколько выше рекомендуемого напряжения, нам придется немного подкорректировать некоторые параметры в расчетах.
ADXL335 - трехосный датчик. Фактически, это три разных акселерометра в одном корпусе, каждый из которых отвечает за свою собственную ось X, Y либо же Z.

Помимо обозначенного статического ускорения наш датчик запросто будет справляться и с обычными ускорениями, до 3g. Это можно использовать например, чтобы определить находится ли вся конструкция в движении, и даже в каком направлении она двигается. Например, ускорение в -3g по оси Z скорее всего скажет нам, что мы только что неплохо шандарахнулись об пол, упав откуда-то. Можно измерять ускорение при начале движения и тем самым делать его более плавным, без резких рывков.
Достаточно часто в интернете в ходе поисков нарывался на обсуждения акселерометра в качестве датчика для системы автопилота. Это не лучшая идея, скажу вам сразу. Во всяком случае, могу совершенно точно сказать, используя только акселерометр в качестве датчика сделать это не получится. Дело в том, что угол наклона в пространстве мы можем нормально вычислить только в состоянии покоя всего устройства. А какой может быть покой у авиамодели? Однако, акселерометр будет отличным дополнением к электрокомпасу в купе с гироскопом.

Подключаемся

Описание теоретических основ - это безусловно здорово, но пора бы и к практике перейти. Я использовал датчик ADXL335 уже купленный с необходимой обвязкой, так как запаять его в ручную слишком сложно, да и нет такой необходимости, если честно. Сегодняшняя схема, до неприличия простая.
Единственное, на что стоит обратить внимание еще раз - мы используем для питания выход 3v3 Arduino, а не 5v, как это было во всех остальных случаях. Выходы X, Y, Z нашего датчика соединены с входами ANALOG IN 0, 1, 2 нашей Arduino.
В этом проекте я использовал Arduino UNO, так как все остальные были заняты.
Вот так все выглядит у меня.

Пишем код
Перейдем собственно к самой интересной части, в которой мы объединим всю теорию и железо описанное выше в одно целое. Помимо этого, это будет первый пост, где мы напишем целых две прошивки. Первая из них призвана помочь нам в калибровке сенсора. Как показала практика - это будет не лишним. Итак, приступим:
#define ADC_ref 5.0
#define analog_resolution 1024.0

unsigned int value_x;
unsigned int value_y;
unsigned int value_z;

void setup()   {
  Serial.begin(9600);
}
 
void loop() {
  value_x = analogRead(0);
  value_y = analogRead(1);
  value_z = analogRead(2);
  
  Serial.print(value_x/analog_resolution*ADC_ref, 5);
  Serial.print(" ");
  Serial.print(value_y/analog_resolution*ADC_ref, 5);
  Serial.print(" ");
  Serial.println(value_z/analog_resolution*ADC_ref, 5);
  
  delay(500);
}

Итак, давайте разберемся, что тут происходит.

#define ADC_ref 5.0
#define analog_resolution 1024.0

Обозначаем пару констант. ADC_ref - это максимальное значение в вольтах которое может снять аналоговый вход. analog_resolution - это разрешающая способность нашего аналогового входа. На arduino она равна 210 или 1024.
После объявления констант идет пара переменных в которых мы будем хранить показания снятые с нашего датчика и инициализация серийного порта, чтобы можно было получить какие-то данные на компьютере.
В функции loop мы в начале получаем данные с трех наших аналоговых пинов, к которым и подключен наш датчик, а после этого пересчитываем полученное число в вольты и выводим их на серийный порт.
Зальем эту прошивку в нашу Arduino UNO, откроем серийный монитор (ctrl+shift+m) и соберем кое какие данные. Нам понадобятся данные шести положений, как на уже упомянутом рисунке.
В фотографиях это выглядит как-то так.

На самом деле я делал замеры на более ровной поверхности, но мне не хватило рук заснять это. Так что не думайте, что я сделал все так криво. Просто мне хотелось показать вам идею как это делать. На включенном серийном мониторе можно видеть что-то вроде.
Где первый столбец - показания по оси X, второй - Y, третий - Z. Полученные в результате замеров данные я свел в одну таблицу:

zero
g
-g
avr. deviation
x
1.60156
1.92871
1.26465
0.33203
y
1.60156
1.92871
1.26465
0.33203
z
1.64551
1.94336
1.31836
0.3125

В столбец zero занесены значения out от нуля, во-втором столбце с заголовком g занесены значения, когда ускорение свободного падения (оно же статическое) - совпадает с вектором, так же как и в столбце -g.
Напишем вторую прошивку, которая будет собираться показания с нашего датчика и преобразовывать их в проекции ускорений по трем осям, а потом еще и высчитаем относительный угол отклонения нашего датчика в пространстве, используя данные о статическом ускорении, о котором я долго распинался в начале поста. Большое спасибо за основу для кода посту на http://www.electronicsblog.net.
#define ADC_ref 5.0
#define analog_resolution 1024.0

#define zero_x 1.60156
#define zero_y 1.60156
#define zero_z 1.64551

#define sensitivity_x 0.33
#define sensitivity_y 0.33
#define sensitivity_z 0.31
 
unsigned int value_x;
unsigned int value_y;
unsigned int value_z;
 
float xv;
float yv;
float zv;
 
float angle_x;
float angle_y;
float angle_z;
 
void setup()   {
  Serial.begin(9600);
}
 
void loop() {
  value_x = analogRead(0);
  value_y = analogRead(1);
  value_z = analogRead(2);
  
  xv = (value_x/analog_resolution*ADC_ref-zero_x)/sensitivity_x;
  yv = (value_y/analog_resolution*ADC_ref-zero_y)/sensitivity_y;
  zv = (value_z/analog_resolution*ADC_ref-zero_z)/sensitivity_z;
  
  Serial.print("x = ");
  Serial.print(xv);
  Serial.print("g   y = ");
  Serial.print(yv);
  Serial.print("g   z = ");
  Serial.print(zv);
  Serial.println("g");

  angle_x =atan2(-yv,-zv)*RAD_TO_DEG;
  angle_y =atan2(-xv,-zv)*RAD_TO_DEG;
  angle_z =atan2(-yv,-xv)*RAD_TO_DEG;
  
  Serial.print(angle_x);
  Serial.print(" deg ");
  Serial.print(angle_y);
  Serial.print(" deg ");
  Serial.print(angle_z);
  Serial.println(" deg");

  delay(1500);
}
Рассмотрим основные части записанного кода. Части которые отвечают за вывод на серийный порт я буду буду пропускать, так как это мы делали уже множество раз.

#define zero_x 1.60156
#define zero_y 1.60156
#define zero_z 1.64551

Значение нуля из нашей таблицы для каждой оси. Получены нами в результате проделанной ранее калибровки. Вообще, в идеале, при питании микросхемы ADXL335 ровными 3в, это значение должно быть равно 1.5, но из-за увеличившегося напряжения питания значение несколько увеличилось. Если не проводить калибровку, то это значение можно принять как напряжение питания деленное на 2. Интересным фактом является то, что значение сильнее увеличилось именно по оси Z. Согласно спецификации - это нормально.

#define sensitivity_x 0.33
#define sensitivity_y 0.33
#define sensitivity_z 0.31
Чувствительность нашего сенсора. Если пренебречь калибровкой, то можно принимать это значение как 1/10 напряжения питания микросхемы ADXL335. А вот теперь перейдем к магии.

  xv = (value_x/analog_resolution*ADC_ref-zero_x)/sensitivity_x;
  yv = (value_y/analog_resolution*ADC_ref-zero_y)/sensitivity_y;
  zv = (value_z/analog_resolution*ADC_ref-zero_z)/sensitivity_z;

Такой не слишком хитрой формулой мы вычисляем проекцию вектора ускорения на оси координат, причем единицей деления на осях будет значение ускорения свободного падения g (благодаря делению на значение sensitivity_*). Теперь зная эти проекции мы можем вычислить угол отклонения датчика в каждой полскости. Для этого возьмем арктангенс от проекции вектора на определенную плоскость.

  angle_x =atan2(-yv,-zv)*RAD_TO_DEG;
  angle_y =atan2(-xv,-zv)*RAD_TO_DEG;
  angle_z =atan2(-yv,-xv)*RAD_TO_DEG;

Значение RAD_TO_DEG является константой при умножении на которую значение угла в радианах преобразуется в более привычные нам углы в градусах. Вот кажется и все, остается только глянуть видео демонстрацию того, что у нас получилось.
Небольшой шум в конце видео - это кот прыгнул на стол прямо рядом с камерой :)
Напоследок хотелось бы пояснить - формулы используемые в примерах далеки от совершенства в плане производительности. Их все можно очень неплохо оптимизировать, но тогда они потеряли свою наглядность. Основной целью поста было показать возможности и немного разобраться в новом датчике.