Связь с магнитным датчиком - TLV493D-A1B6 по I2C

Я использую датчик TLV493D-A1B6, подключенный к Arduino Uno: всю необходимую документацию я прикрепил внизу.

Это 3D-датчик положения магнитного поля, который определяет напряженность магнитного поля близлежащего магнитного поля в направлениях X, Y и Z. Я НЕ использую оценочную плату 2GO. Датчик использует протокол I2C для связи.

У меня этот датчик полностью подключен, правильно припаян к печатной плате со штыревыми выводами и готов к установке на макетную плату. Кроме того, я использую двунаправленный преобразователь логических уровней, который рекомендуется для этого датчика. Я могу это подтвердить, поскольку скетч сканера I2C обнаруживает датчик и выводит его ожидаемый адрес по умолчанию.

В частности, у меня возникли проблемы с пониманием спецификаций и того, как настроить Arduino с использованием библиотеки «Wire.h» для связи с датчиком по I2C для непрерывного получения необработанных/декодированных показаний X, Y, Z.

Я попробовал код из этой ветки: https://forum.arduino.cc/index.php?topic=419380.0 Я попробовал код из этого специального руководства для этого датчика: https://www.allaboutcircuits.com/technical-articles/tutorial-and-overview-of-infineons-3d-magnetic-2go-kit/

Я даже попробовал код с Github для этого датчика: https://github.com/IRNAS/TLV493D-3D-Magnetic-Sensor-Arduino-Library/blob/master/TLV493D/TLV493D.h

Используя любой из вышеперечисленных вариантов, я получу показания «X=-1, Y=-1, Z=-1» или датчик будет выдавать случайные показания «255» и «0» при воздействии любого магнита.

Я написал свой собственный код и потерпел полную неудачу, пытаясь получить что-либо, кроме случайных, явно неправильных показаний, это была моя последняя попытка, я урезал этот код для простоты и ясности. Я также пробовал вариации с использованием основной библиотеки I2C.h, но безуспешно:

#include "Wire.h"       

const int tlv_addr = 0x1F; // получено сканером i2c; все адреса по умолчанию датчика tlv - 0x1F, 0x5E или 0x3E
const int config_reg = 0x00; // У меня есть эта сцена в других примерах кода для этого датчика, я не могу найти адрес «configure» или «command register» ни в одном из его описаний
const int lp_mode = 0x05; //Нашел это в техническом описании как команду режима низкого энергопотребления
// Примечание: Я перешел с const byte на const int для назначений HEX, потому что получаю предупреждение об этом при компиляции скетча, и я нашел в Интернете, что это решение?

void setup() {
  Wire.begin();   //присоединяемся к шине I2C
  Serial.begin(9600); //запустить последовательный порт
  while(!Serial); //ждем, пока Serial станет доступен

  Wire.beginTransmission(tlv_addr); //инициируем связь с датчиком
  Wire.write(config_reg); //установить указатель/доступ к регистру конфигурации
  Wire.write(lp_mode); //из того, что я прочитал, датчик по умолчанию переходит в режим пониженного энергопотребления при запуске, поэтому я отправляю команду режима пониженного энергопотребления в регистр конфигурации, чтобы активировать датчик в режиме пониженного энергопотребления, что должно заставить его начать «ощущать»
  Wire.endTransmission(); //конец конфигурации
  delay(100); //задержка, чтобы дать время датчику обновиться
  Serial.print("Setup Complete\n\n");
}

void loop(){
  Wire.requestFrom(tlv_addr, 3); //запросить 3 необработанных, недекодированных байта из показаний датчика осей X, Y, Z

  //---- Считывание каждого байта данных датчика по одному для каждой оси ----//
  byte Bx = Wire.read(); //Во многих местах кода в начало переменной добавлен символ "*". Не знаете, зачем это нужно?
  byte By = Wire.read();
  byte Bz = Wire.read();

  //---- Распечатка показаний оси датчика ----//
  Serial.println("X= ");
  Serial.print(Bx);       //Также добавлена сцена "&" в начало переменной во многих фрагментах кода, например Serial.print(&Bx);?
  Serial.print("\tY= ");
  Serial.print(By);
  Serial.print("\tZ= ");
  Serial.print(Bz);

  //Ожидается что-то вроде этого: 01011011
  //Получаете -1, 255 или 0 случайным образом?

  delay(1000); //произвольная задержка
}

Более того, я понимаю, что эти значения необходимо декодировать, чтобы понять фактические данные. Я понятия не имею, что происходит на листе карты регистров и почему они сдвигают разные биты сегментов каждого байта, по-видимому, как будто полностью разбирают байты, затем переупорядочивают их, а затем преобразуют их в целые числа, чтобы получить реальные данные?

Я совсем новичок в Arduino и особенно в I2C. Если бы вы могли мне помочь понять, что я упускаю из виду в техническом описании или что я делаю неправильно, говоря простыми словами, я хочу интуитивно понимать, что происходит на каждом этапе, чтобы я мог сделать это снова с другим датчиком I2C самостоятельно. Если кто-то может, пожалуйста, помогите мне с этим.


датчик: https://www.infineon.com/cms/en/product/sensor/magnetic-position-sensor/3d-magnetic-sensor/tlv493d-a1b6/

Техническое описание: https://www.infineon.com/dgdl/Infineon-TLV493D-A1B6-DS-v01_00-EN.pdf?fileId=5546d462525dbac40152a6b85c760e80

Регистрация карты: https://www.infineon.com/dgdl/Infineon-TLV493D-A1B6_3DMagnetic-UM-v01_03-EN.pdf?fileId=5546d46261d5e6820161e75721903ddd

, 👍2


1 ответ


Лучший ответ:

0

В вашем коде вы считываете только первые 3 регистра и записываете их в переменные Bx - Bz. Эти регистры соответствуют правильным магнитным значениям, но только самым высоким их частям. Датчик измеряет 12-битные значения - из-за более высокой точности -, но один регистр может содержать только 8 бит. Поэтому большие 12-битные значения разделяются на две части . Вам придется считывать все части и правильно их объединять, чтобы получить осмысленные результаты. Кроме того, формат значений - это знаковое 12-битное значение в дополнительном коде до двух, что означает, что десятичное значение вычисляется путем умножения значения 12-го бита на -1. Вам придется преобразовать его в 16-битное дополнение до двух для использования типа int.

В следующей части я буду использовать побитовое И ( & ), побитовое ИЛИ ( | ) и операторы сдвига битов ( << и >>). Я только вкратце объясню, как их использовать. Если вы с ними не знакомы, обратитесь к соответствующему учебнику по C/C++, который может объяснить это более подробно, чем этот ответ.

  1. Побитовое И будет сравнивать каждый бит левого значения с соответствующим битом правого значения. Только если оба бита равны 1, соответствующий бит в выводе будет равен 1, 0 в любом другом случае. Я использую его, чтобы замаскировать некоторые биты значения, фактически удаляя эти биты. Также обратите внимание на различную запись литеральных значений: в шестнадцатеричном 0x0F или двоичном 0b00001111.
  2. Побитовое ИЛИ будет работать как побитовое И, но поместит 1 в бит, когда хотя бы один из соответствующих входных битов равен 1, и выведет 0 в этот бит, если оба входных бита равны 0. Я использую это, чтобы поместить все байты одного значения вместе в одну переменную.
  3. Операторы сдвига << и >> сдвигают значения битов в одну сторону. Биты, выходящие за пределы диапазона значений, теряются. С другой стороны нули сдвигаются в значение. Я использую это для правильного выравнивания в байтах большего значения.

Это важная цифра из руководства пользователя. Она говорит вам, что вы найдете биты с 11 по 4 значения Bx в первом регистре (11 — самый значимый бит). Остальное значение Bx находится в 5-м регистре в 4 самых значимых битах (биты с 3 по 0 значения Bx). Поэтому, чтобы получить правильные значения, нам сначала нужно прочитать все данные (я включу сюда температуру):

// Запросить 7 байт от датчика и вернуть, если он не отправил достаточно (не читать, если нечего читать)
if(Wire.requestFrom(tlv_addr, 7) < 7) return;
// Прочитать все регистры в переменные
byte bx_high = Wire.read();
byte by_high = Wire.read();
byte bz_high = Wire.read();
byte temp_high = Wire.read();
byte bxy_low = Wire.read(); // Это должно быть прочитано вместе. Мы разделим значения позже
byte bz_low = Wire.read();
byte temp_low = Wire.read();

Теперь мы собираем разделенные значения в одну переменную каждое. Чтобы избежать сложностей с типами, я использую здесь unsigned int, который похож на byte, но содержит 16 бит. Для Bx мы должны сдвинуть старшую часть на 4 бита влево, так как для младшей части нужно 4 бита места. Также мы должны разделить младшие части Bx и By. Так как младшая часть Bx находится в старших 4 битах считанного значения, нам нужно сдвинуть их вправо для выравнивания с младшим битом. Остальное работает аналогично, но немного проще. Значение температуры использует другое выравнивание, но принцип тот же.

unsigned int bx_value = (bx_high << 4) | ((bxy_low & 0xF0)>>4);
unsigned int by_value = (by_high << 4) | (bxy_low & 0x0F);
unsigned int bz_value = (bz_high << 4) | (bz_low & 0x0F);
unsigned int temp_value = (temp_high >> 4) | temp_low;

Теперь мы упаковали 12-битные значения в 16-битные беззнаковые целые числа. Теперь нам нужно преобразовать их в 16-битные знаковые целые числа. Но если мы просто приведем их, это приведет к ложным значениям, поскольку дополненный бит здесь 16-й, а не 12-й, как в датчике. Для решения этой проблемы мы используем ответ из этого вопроса:

int16_t Bx = (int16_t)(bx_value << 4) / 16 ;

Есть одно отличие от ответа: у нас 12-битное дополнение до двух, а не 14-битное. Поэтому нам нужно сдвинуть 4 бита, а не 2. Это соответствует делению на 16 (множитель 2 для каждого сдвинутого бита).

Теперь у вас должны быть правильные измеренные значения от датчика.


У вас может быть вторая проблема с вашим кодом. Он считывает регистры каждую секунду, независимо от состояния датчика. В техническом описании указано:

Этот датчик не включает в себя теневые буферы для считывания. Таким образом, он обязательно считывать данные датчика не во время выполнения преобразования чтобы избежать поврежденных чтений.

Так что вы также можете получить искаженные значения, потому что иногда читаете регистры во время записи в них датчиком. Это может быть не большой проблемой здесь (возможно, это случается только время от времени). Но вы можете обойти эту проблему, либо используя вывод INT датчика для запуска операции чтения I2C (в лучшем случае через прерывание), либо настроив датчик для работы в «режиме управляемого ведущим», где он выполняет только новое измерение, когда старые значения были считаны из регистров.

,