Подключение ADXL345 к STM32 I²C (датчик ускорения (акселерометр) от Analog Devices)

Микроэлектромеханическая система ADXL345

Цифровой датчик ускорения (акселеро метр) ADXL345 я купил в довесок к основному заказу, не смотря на то, что широко распространенным и известным были акселерометры серии MPUxxxx.

Когда пришло время, то узнав о его возможностях стало сразу интересно, т.к. этот МЭМС кроме просто измерения значений ускорения умеет обрабатывать полученные данные :

  • Обнаружение одиночного/двойного удара
  • Слежение за активностью/неактивностью
  • Обнаружения свободного падения

Купить на Aliexpress


🏷️ GY-291 ADXL345 3-Axis Sensor Acceleration (1.80💲): https://ali.ski/qtauvp

🏷️ ADXL345 3-axis IIC / SPI (0.89💲): https://ali.ski/qji2qF
🏷️ Digital Sensor ADXL345 (1.12💲): https://ali.ski/OdPBr

🏷️ 5pcs ADXL345BCCZ LGA-14 (8.01💲): https://ali.ski/Ds_9fe
🏷️ 5pcs ADXL345 ADXL345BCCZ LGA-14 (4.50💲): https://ali.ski/0PJRn
🏷️ 5pcs ADXL345 Modules (9.40💲): https://ali.ski/TVSh9

🏷️ STM32F401 Module (3.50💲): https://ali.ski/T9_RBe
🏷️ STM32F401 Black Pill USB C(3.88💲): https://ali.ski/wvygW

🏷️ STM32F401 DevBoard Type-C (3.46💲): https://ali.ski/SvrJ8d


Обвязка для микросхемы

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

  • При черчении схемки не забываем про подтягивающие резисторы к плюсу (4.7 кОм - 10 кОм) для SDA и SCL.
  • Также питание лучше производить от понижающего преобразователя напряжения с несколькими конденсаторами на входе и выходе.
  • И еще нужны развязывающие (decoupling) конденсаторы по питанию, ставьте 0.1 мкФ керамический и 10 мкФ танталовый, также можно добавить ферритовую бусинку с импедансом до 100 Ом последовательно.

Создание проекта в STM32CubeIDE

Создаем новый проект:

Выбираем микроконтроллер:

Любое название:

Включаем отладчик:

Включаем II2, изменяем частоту на 400 кГц ( Fast Mode):

И генерируем код

Чтение номера устройства (DEVICE ID)

Прежде чем общаться с устройством нужно узнать его адрес, есть основной - это 7-ми разрядное число  0x53, а также дополнительный 0x1D, который можно выбрать установив на выводе 12 (SDO/ALT ADDRESS) высокий уровень (по умолчанию на модули он подтянут к минусу).

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

#define ADXL_ADR (0x53 << 1)

Самое простое это пока узнать номер этого устройства , это значение должно быть равно 0xE5:

Создадим структуру и в ней переменную размером один байт для этого:

struct Adxl_Data { uint8_t id; }Adxl345;

Перед бесконечным циклом сначала отправляется адрес устройства и запрос на запись, после подтверждения датчиком отправляется адрес регистра (здесь 0x00). Далее остается отправить запрос на чтения и считать полученный байт.

uint8_t CmdDevid = 0x00; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADRESS, (uint8_t*)&CmdDevid , 1, 1000); HAL_Delay(100); HAL_I2C_Master_Receive(&hi2c1, ADXL_ADRESS, (uint8_t*)&Adxl345.id, 1, 1000);

Теперь можно добавить структуру и посмотреть переменную в окне  Live Expressions (найти окно при помощи поиска Ctrl+3):

Записав и проанализировав логические уровни с помощью  логического анализатора также все хорошо сходиться:

Занесение адресов регистров

Для удобства доступа к регистрам и выставления нужных бит создана структура и перечисление:

struct AdxlCommands { uint8_t DEVID; /* Device ID */ uint8_t THRESH_TAP; /* Tap threshold */ uint8_t OFSX; /* X-axis offset */ uint8_t OFSY; /* Y-axis offset */ uint8_t OFSZ; /* Z-axis offset */ uint8_t DUR; /* Tap duration */ uint8_t Latent; /* Tap latency */ uint8_t Window; /* Tap window */ uint8_t THRESH_ACT; /* Activity threshold */ uint8_t THRESH_INACT; /* Inactivity threshold */ uint8_t TIME_INACT; /* Inactivity time */ uint8_t ACT_INACT_CTL; /* Axis enable control for activity and inactivity detection */ uint8_t THRESH_FF; /* Free-fall threshold */ uint8_t TIME_FF; /* Free-fall time */ uint8_t TAP_AXES; /* Axis control for single tap/double tap */ uint8_t ACT_TAP_STATUS; /* Source of single tap/double tap */ uint8_t BW_RATE; /* Data rate and power mode control */ uint8_t POWER_CTL; /* Power-saving features control */ uint8_t INT_ENABLE; /* Interrupt enable control */ uint8_t INT_MAP; /* Interrupt mapping control */ uint8_t INT_SOURCE; /* Source of interrupts */ uint8_t DATA_FORMAT; /* Data format control */ uint8_t DATAX0; /* X-Axis Data 0 */ uint8_t DATAX1; /* X-Axis Data 1 */ uint8_t DATAY0; /* Y-Axis Data 0 */ uint8_t DATAY1; /* Y-Axis Data 1 */ uint8_t DATAZ0; /* Z-Axis Data 0 */ uint8_t DATAZ1; /* Z-Axis Data 1 */ uint8_t FIFO_CTL; /* FIFO control */ uint8_t FIFO_STATUS; /* FIFO status */ }; struct AdxlCommands AdxlReg = {0x00, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}; enum AdxlBitNum { D0, D1, D2, D3, D4, D5, D6, D7 };

Получение ускорения по трем осям XYZ

Каждой оси принадлежит два восьмиразрядных регистра, то есть всего для XYZ их шесть:

Создадим вспомогательный массив из двух элементов (для передачи адреса и значения):

uint8_t i2c_tx[2];

Нужно выбрать частоту измерений, по умолчанию установлена 100 Гц, пусть так и будет:

/* Rate */ i2c_tx[0] = AdxlReg.BW_RATE; i2c_tx[1] = ((1 << D3) | (1 << D1)); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Теперь обязательно включить измерение (выход из режима ожидания):

/* Measure */ i2c_tx[0] = AdxlReg.POWER_CTL; i2c_tx[1] = (1 << D3); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Добавим переменные в структуру для трех трех осей (знаковые), а также восьмиразрядный массив из шести элементов для сырых данных с регистров ( DATAX0-DATAZ1):

struct Adxl_Data { uint8_t id; int16_t x, y, z; uint8_t raw[6]; }Adxl345;

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

HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)AdxlReg.DATAX0, 1, 100); HAL_I2C_Master_Receive(&hi2c1, ADXL_ADR, (uint8_t*)Adxl345.raw, 6, 100);

Полученные данные с регистров соединяются. Байты идут по очереди, сначала младший, потом старший, поэтому просто необходимо применить операцию ИЛИ Adxl345.raw[0] со сдвинутым на 8 (1 байт = 8 бит) Adxl345.raw[1], т.к. для каждой оси предназначено два 8-бит регистра, ведь используемое разрешение АЦП датчика по умолчанию 10-бит:

Adxl345.x = ((int16_t)(Adxl345.raw[1] << 8) | Adxl345.raw[0]); Adxl345.y = ((int16_t)(Adxl345.raw[3] << 8) | Adxl345.raw[2]); Adxl345.z = ((int16_t)(Adxl345.raw[5] << 8) | Adxl345.raw[4]); HAL_Delay(50);

Теперь в режиме отладки можно покрутить датчик и посмотреть как меняются показания (изменить систему исчисления для отображения можно нажав на три точки, а далее открыть и закрыть ветку структуры нажатием на нее):

Давайте опять посмотрим что происходит с помощью очень нужного прибора —  логического анализатора за 5$. А здесь все хорошо и четко:

Еще интересно смотреть графики (нужен вывод SWO на программаторе-отладчике:

Но веселее это делать через STM32CubeMonitor, т.к. он намного резвее:

Пока что эти циферки абстракты для нас, нужно узнать в каком именно режиме измерения ускорения мы находимся для непосредственно расчета значений в м/с² или просто еденицах g. Добавим переменные в структуру для значений в виде единиц ускорения g:

struct Adxl_Data { uint8_t id; int16_t x, y, z; float xg, yg, zg; uint8_t raw[6]; }Adxl345;

В регистре DATA_FORMAT (0x31) есть биты, которые устанавливают диапазон измерений:

Установим наибольший, записав в 0-й и 1-й биты единицы для диапазона ±16g.

/* Range 16g */ i2c_tx[0] = AdxlReg.DATA_FORMAT; i2c_tx[1] = ((1 << D0) | (1 << D1)); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Теперь можем обратиться к другой таблице, и, так как используется разрядность по умолчанию 10-бит при диапазоне  ±16g, поэтому для расчёета берется множитель 31.2 mg/LSB (31.2 мg/бит).

#define SCALE_FACTOR 31.2 Adxl345.xg = (SCALE_FACTOR*Adxl345.x)/1000; Adxl345.yg = (SCALE_FACTOR*Adxl345.y)/1000; Adxl345.zg = (SCALE_FACTOR*Adxl345.z)/1000;

Ну что ж, очень даже похоже на правду 

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

/* USER CODE BEGIN PV */ struct AdxlCommands { uint8_t DEVID; /* Device ID */ uint8_t THRESH_TAP; /* Tap threshold */ uint8_t OFSX; /* X-axis offset */ uint8_t OFSY; /* Y-axis offset */ uint8_t OFSZ; /* Z-axis offset */ uint8_t DUR; /* Tap duration */ uint8_t Latent; /* Tap latency */ uint8_t Window; /* Tap window */ uint8_t THRESH_ACT; /* Activity threshold */ uint8_t THRESH_INACT; /* Inactivity threshold */ uint8_t TIME_INACT; /* Inactivity time */ uint8_t ACT_INACT_CTL; /* Axis enable control for activity and inactivity detection */ uint8_t THRESH_FF; /* Free-fall threshold */ uint8_t TIME_FF; /* Free-fall time */ uint8_t TAP_AXES; /* Axis control for single tap/double tap */ uint8_t ACT_TAP_STATUS; /* Source of single tap/double tap */ uint8_t BW_RATE; /* Data rate and power mode control */ uint8_t POWER_CTL; /* Power-saving features control */ uint8_t INT_ENABLE; /* Interrupt enable control */ uint8_t INT_MAP; /* Interrupt mapping control */ uint8_t INT_SOURCE; /* Source of interrupts */ uint8_t DATA_FORMAT; /* Data format control */ uint8_t DATAX0; /* X-Axis Data 0 */ uint8_t DATAX1; /* X-Axis Data 1 */ uint8_t DATAY0; /* Y-Axis Data 0 */ uint8_t DATAY1; /* Y-Axis Data 1 */ uint8_t DATAZ0; /* Z-Axis Data 0 */ uint8_t DATAZ1; /* Z-Axis Data 1 */ uint8_t FIFO_CTL; /* FIFO control */ uint8_t FIFO_STATUS; /* FIFO status */ }; struct AdxlCommands AdxlReg = {0x00, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}; enum AdxlBitNum { D0, D1, D2, D3, D4, D5, D6, D7 }; struct AdxlData { uint8_t id; int16_t x,y,z; double xg, yg, zg; uint8_t activity; uint8_t int_src; uint8_t raw[6]; }Adxl345; /* ADXL345 I2C address */ #define ADXL_ADR (0x53 << 1) /* Scale Factor (for 16g) */ #define SCALE_FACTOR 31.2 /* USER CODE END PV */ /* USER CODE BEGIN 2 */ /* XYZ ACCELERATION MEASURMENT*/ uint8_t i2c_tx[2]; /* Rate 100Hz */ i2c_tx[0] = AdxlReg.BW_RATE; i2c_tx[1] = ((1 << D3) | (1 << D1)); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Range 16g */ i2c_tx[0] = AdxlReg.DATA_FORMAT; i2c_tx[1] = ((1 << D0) | (1 << D1)); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Measure */ i2c_tx[0] = AdxlReg.POWER_CTL; i2c_tx[1] = (1 << D3); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)&AdxlReg.DATAX0, 1, 100); HAL_I2C_Master_Receive(&hi2c1, ADXL_ADR, (uint8_t*)&Adxl345.raw, 6, 100); Adxl345.x = ((int16_t)(Adxl345.raw[1] << 8) | Adxl345.raw[0]); Adxl345.y = ((int16_t)(Adxl345.raw[3] << 8) | Adxl345.raw[2]); Adxl345.z = ((int16_t)(Adxl345.raw[5] << 8) | Adxl345.raw[4]); Adxl345.xg = (SCALE_FACTOR*Adxl345.x)/1000; Adxl345.yg = (SCALE_FACTOR*Adxl345.y)/1000; Adxl345.zg = (SCALE_FACTOR*Adxl345.z)/1000; HAL_Delay(50); /* USER CODE END WHILE */

Настройка засекания активности с прерыванием на выводе

Невероятно полезная вещь, на мой взгляд, ведь это дает возможность делегировать слежку за значениями ускорения с нужным порогом самому же измерителю, а потом просто отловить переход на выводе INT1/INT2 с помощью внешнего прерывания на МК, тем самым определив, что устройство взяли/привели в движение/переместили.

Итак, сначала нужно выбрать оси, которые будут задействованы, пусть это будут все три XYZ. Сравнение может происходить с нулем, или относительно текущего значения, советую выбирать второе.

/* Activity Axes */ i2c_tx[0] = AdxlReg.ACT_INACT_CTL; i2c_tx[1] = ((1 << D7) | (1 << D6) | (1 << D5) | (1 << D4)); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Задаем порог, его можно найти опытным путем, поставлю 10, это позволит отловить "взятие" устройства без ложных срабатываний.

/* Threshold Activity */ i2c_tx[0] = AdxlReg.THRESH_ACT; i2c_tx[1] = 10; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Чтобы выбрать вывод прерывания на вывод ( INT1/INT2) нужно записать 0 для INT1 в бит D4 (Activity), а если нужно на вывод INT1, то 1 (не забываем, что если включили прерывания от других источников, то они могут/будут мешать!). Пусть прерывание срабатывает на выводе INT1:

/* Int Map */ i2c_tx[0] = AdxlReg.INT_MAP; i2c_tx[1] = ~(uint8_t)(1 << D4); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Теперь нужно прерывание по активности в регистре INT_ENABLE:

/* Interrupt Enable */ i2c_tx[0] = AdxlReg.INT_ENABLE; i2c_tx[1] = (1 << D4); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Важно! Теперь нужно включить режим измерения (выйти из режима ожидания ( standby)):

/* Measure */ i2c_tx[0] = AdxlReg.POWER_CTL; i2c_tx[1] = (1 << D3); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);<br>


Важно! После каждого срабатывания (перехода) нужно считывать значение с регистра INT_SOURCE, чтобы прерывания опять срабатывали!

Добавим переменную для записи данных с регистра статуса прерываний:

struct AdxlData { uint8_t id; int16_t x,y,z; double xg, yg, zg; uint8_t activity; uint8_t int_src; uint8_t raw[6]; }Adxl345;

Определять активность (как и все остальное) можно и без прерывания, просто считывая значение с INT_SOURCE (0x30) и проверяя бит D4 (Activity).

HAL_I2C_Mem_Read(&hi2c1, ADXL_ADR, AdxlReg.INT_SOURCE, 1, &Adxl345.int_src, 1, 100); if(Adxl345.int_src & (1 << D4)) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); Adxl345.int_src = 0; }

Чтобы понять что прерывания происходят запишем логограмму:

Также можно присобачить внешнее прерывание на выводы МК:

Подтяжка к минусу, режим по возрастающему фронту (по умолчанию активный высокий уровень, но это можно изменить битом D5 (INT_INVERT) в регистре DATA_FORMAT (0x31):

Не забываем включить эти самые прерывания (должны появится обработчики (void EXTI15_10_IRQHa..) в файле stm32f4xx_it.c):

Понадобится переменная для каждого вывода:

/* USER CODE BEGIN PV */ /* Flags */ uint8_t AdxlFlgInt1 = 0; uint8_t AdxlFlgInt2 = 0; /* USER CODE END PV */

В функции обратного вызова проверяется от какого вывода прерывание и выставляется соответствующий флаг:

/* USER CODE BEGIN 4 */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == INT1_Pin) { /* Set Flag */ AdxlFlgInt1 = 255; } if(GPIO_Pin == INT2_Pin) { /* Set Flag */ AdxlFlgInt2 = 255; } } /* USER CODE END 4 */

В главном цикле проверяется флаг, который по умолчанию ноль, а выставится при обнаружении перехода от лог.0 к лог. 1 (передний фронт). Орабатывать "активность", например, можно переключением состояния светодиода (для наглядности), а далее должно быть чтение регистра INT_SOURCE (0x30) (обязательно!) и обнуление флага соответственно:

/* USER CODE BEGIN WHILE */ while (1) { if(AdxlFlgInt1) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); HAL_I2C_Mem_Read(&hi2c1, ADXL_ADR, AdxlReg.INT_SOURCE, 1, &Adxl345.int_src, 1, 100); AdxlFlgInt1 = 0; } /* USER CODE END WHILE */

Теперь возможно засечь даже небольшие постукивания или перемещения с ускорением (зависит от порогового значения):

/* USER CODE BEGIN PV */ struct AdxlCommands { uint8_t DEVID; /* Device ID */ uint8_t THRESH_TAP; /* Tap threshold */ uint8_t OFSX; /* X-axis offset */ uint8_t OFSY; /* Y-axis offset */ uint8_t OFSZ; /* Z-axis offset */ uint8_t DUR; /* Tap duration */ uint8_t Latent; /* Tap latency */ uint8_t Window; /* Tap window */ uint8_t THRESH_ACT; /* Activity threshold */ uint8_t THRESH_INACT; /* Inactivity threshold */ uint8_t TIME_INACT; /* Inactivity time */ uint8_t ACT_INACT_CTL; /* Axis enable control for activity and inactivity detection */ uint8_t THRESH_FF; /* Free-fall threshold */ uint8_t TIME_FF; /* Free-fall time */ uint8_t TAP_AXES; /* Axis control for single tap/double tap */ uint8_t ACT_TAP_STATUS; /* Source of single tap/double tap */ uint8_t BW_RATE; /* Data rate and power mode control */ uint8_t POWER_CTL; /* Power-saving features control */ uint8_t INT_ENABLE; /* Interrupt enable control */ uint8_t INT_MAP; /* Interrupt mapping control */ uint8_t INT_SOURCE; /* Source of interrupts */ uint8_t DATA_FORMAT; /* Data format control */ uint8_t DATAX0; /* X-Axis Data 0 */ uint8_t DATAX1; /* X-Axis Data 1 */ uint8_t DATAY0; /* Y-Axis Data 0 */ uint8_t DATAY1; /* Y-Axis Data 1 */ uint8_t DATAZ0; /* Z-Axis Data 0 */ uint8_t DATAZ1; /* Z-Axis Data 1 */ uint8_t FIFO_CTL; /* FIFO control */ uint8_t FIFO_STATUS; /* FIFO status */ }; struct AdxlCommands AdxlReg = {0x00, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}; enum AdxlBitNum { D0, D1, D2, D3, D4, D5, D6, D7 }; struct AdxlData { uint8_t id; int16_t x,y,z; double xg, yg, zg; uint8_t activity; uint8_t int_src; uint8_t raw[6]; }Adxl345; /* ADXL345 I2C address */ #define ADXL_ADR (0x53 << 1) /* USER CODE END PV */ /* USER CODE BEGIN 2 */ /* ACTIVITY */ uint8_t i2c_tx[2]; /* Activity Axes */ i2c_tx[0] = AdxlReg.ACT_INACT_CTL; i2c_tx[1] = ((1 << D7) | (1 << D6) | (1 << D5) | (1 << D4)); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Threshold Activity */ i2c_tx[0] = AdxlReg.THRESH_ACT; i2c_tx[1] = 10; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Int Map */ i2c_tx[0] = AdxlReg.INT_MAP; i2c_tx[1] = ~(1 << D4); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Interrupt Enable */ i2c_tx[0] = AdxlReg.INT_ENABLE; i2c_tx[1] = (1 << D4); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Measure */ i2c_tx[0] = AdxlReg.POWER_CTL; i2c_tx[1] = (1 << D3); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { HAL_I2C_Mem_Read(&hi2c1, ADXL_ADR, AdxlReg.INT_SOURCE, 1, &Adxl345.int_src, 1, 100); if(Adxl345.int_src & (1 << D4)) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); Adxl345.int_src = 0; } HAL_Delay(50); /* USER CODE END WHILE */

Настройка засекания двойного удара

Пользователи первой версии известного умного браслета знают, что в нем не было экрана и кнопок, а выключение будильника, например, происходило путем двойного удара по устройству. Теперь, с использованием ADXL345 и мы с лёгкостью можем сделать то же самое. Картинка ниже показывает какие параметры за что отвечают:

Настроим порог засекания удара, каждый бит (LSB) имеет вес 62.5 mg, опытным путем выяснил, что сила обычного несильного удара пальцем около 50-60 единиц и больше:

/* Threshold Tap */ i2c_tx[0] = AdxlReg.THRESH_TAP; i2c_tx[1] = 60; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

В регистре наибольшей продолжительности удара ставим DUR: 40*625 = 25000 мкс:

/* Tap Duration */ i2c_tx[0] = AdxlReg.DUR; i2c_tx[1] = 40; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Также задаем наибольшее допустимое время (окно), в течении которого может быть зафиксирован второй удар, 

/* Tap Window */ i2c_tx[0] = AdxlReg.Window; i2c_tx[1] = 200; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Время задержки (Latency) устанавливает время после удара, в течении которого НЕ МОЖЕТ быть зарегистрирован второй удар (предохранение от дребезга):

/* Tap Latency */ i2c_tx[0] = AdxlReg.Latent; i2c_tx[1] = 80; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Выбираем нужные оси в TAP_AXES (0x2A), пусть будут задействованы все (но лучше выбирать только одну):

/* Tap Axes */ i2c_tx[0] = AdxlReg.TAP_AXES; i2c_tx[1] = ((1 << D2) | (1 << D1) | (1 << D0)); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Обязательно включить прерывания нужные (здесь это двойной удар – double tap ):

/* Interrupt Enable */ i2c_tx[0] = AdxlReg.INT_ENABLE; i2c_tx[1] = (1 << D5); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Назначаем выход прерывания двойного удара на вывод INT1 путем записи в бит D5 DOUBLE TAP нуля, а во все остальные единицы (чтобы не мешали):

/* Interrupt Map */ i2c_tx[0] = AdxlReg.INT_MAP; i2c_tx[1] = ~(1 << D5); // 11011111 HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Остается начать измерения ( обязательно!):

/* Measure */ i2c_tx[0] = AdxlReg.POWER_CTL; i2c_tx[1] = (1 << D3); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Т.к. прерывание настроено на вывод INT1, то его можно ловить там (как раньше). Но также возможно просто считывание значение с регистра (переменная int_src добавлена в структуру ранее):

HAL_I2C_Mem_Read(&hi2c1, ADXL_ADR, AdxlReg.INT_SOURCE, 1, &Adxl345.int_src, 1, 100); if(Adxl345.int_src & (1 << D5)) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); Adxl345.int_src = 0; }

Всё готового! Отрабатывает отлично (смотреть на область платы сверху красного т.к. синий СИД под платой):

/* USER CODE BEGIN PV */ struct AdxlCommands { uint8_t DEVID; /* Device ID */ uint8_t THRESH_TAP; /* Tap threshold */ uint8_t OFSX; /* X-axis offset */ uint8_t OFSY; /* Y-axis offset */ uint8_t OFSZ; /* Z-axis offset */ uint8_t DUR; /* Tap duration */ uint8_t Latent; /* Tap latency */ uint8_t Window; /* Tap window */ uint8_t THRESH_ACT; /* Activity threshold */ uint8_t THRESH_INACT; /* Inactivity threshold */ uint8_t TIME_INACT; /* Inactivity time */ uint8_t ACT_INACT_CTL; /* Axis enable control for activity and inactivity detection */ uint8_t THRESH_FF; /* Free-fall threshold */ uint8_t TIME_FF; /* Free-fall time */ uint8_t TAP_AXES; /* Axis control for single tap/double tap */ uint8_t ACT_TAP_STATUS; /* Source of single tap/double tap */ uint8_t BW_RATE; /* Data rate and power mode control */ uint8_t POWER_CTL; /* Power-saving features control */ uint8_t INT_ENABLE; /* Interrupt enable control */ uint8_t INT_MAP; /* Interrupt mapping control */ uint8_t INT_SOURCE; /* Source of interrupts */ uint8_t DATA_FORMAT; /* Data format control */ uint8_t DATAX0; /* X-Axis Data 0 */ uint8_t DATAX1; /* X-Axis Data 1 */ uint8_t DATAY0; /* Y-Axis Data 0 */ uint8_t DATAY1; /* Y-Axis Data 1 */ uint8_t DATAZ0; /* Z-Axis Data 0 */ uint8_t DATAZ1; /* Z-Axis Data 1 */ uint8_t FIFO_CTL; /* FIFO control */ uint8_t FIFO_STATUS; /* FIFO status */ }; struct AdxlCommands AdxlReg = {0x00, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}; enum AdxlBitNum { D0, D1, D2, D3, D4, D5, D6, D7 }; struct AdxlData { uint8_t id; int16_t x,y,z; double xg, yg, zg; uint8_t activity; uint8_t int_src; uint8_t raw[6]; }Adxl345; /* ADXL345 I2C address */ #define ADXL_ADR (0x53 << 1) /* USER CODE END PV */ /* USER CODE BEGIN 2 */ /* DOUBLE TAP */ uint8_t i2c_tx[2]; /* Tap Axes */ i2c_tx[0] = AdxlReg.TAP_AXES; i2c_tx[1] = ((1 << D2) | (1 << D1) | (1 << D0)); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Threshold Tap */ i2c_tx[0] = AdxlReg.THRESH_TAP; i2c_tx[1] = 60; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Tap Duration */ i2c_tx[0] = AdxlReg.DUR; i2c_tx[1] = 40; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Tap Latency */ i2c_tx[0] = AdxlReg.Latent; i2c_tx[1] = 80; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Tap Window */ i2c_tx[0] = AdxlReg.Window; i2c_tx[1] = 200; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Interrupt Enable */ i2c_tx[0] = AdxlReg.INT_ENABLE; i2c_tx[1] = (1 << D5); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Interrupt Map */ i2c_tx[0] = AdxlReg.INT_MAP; i2c_tx[1] = ~(1 << D5); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Measure */ i2c_tx[0] = AdxlReg.POWER_CTL; i2c_tx[1] = (1 << D3); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { HAL_I2C_Mem_Read(&hi2c1, ADXL_ADR, AdxlReg.INT_SOURCE, 1, &Adxl345.int_src, 1, 100); if(Adxl345.int_src & (1 << D5)) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); Adxl345.int_src = 0; } HAL_Delay(50); /* USER CODE END WHILE */

Настройка засекания одиночного удара

В общем то то же самое, только включить прерывание нужно от одиночного толчка ( SINGLE TAP).

Свободное падение

Во время свободного падения ускорение по всем осям XYZ должно быть нулевым (измеренное будет около нуля). Здесь очень простая настройка, которая заключается в установлении порога для трех осей (вес 62.5 mg/LSB, советуют ставить от 5*62.5 = 312.5 mg):

/* Free-fall threshold */ i2c_tx[0] = AdxlReg.THRESH_FF; i2c_tx[1] = 3; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

А также время, которое должно пройти для засекания свободного падения (вес 5 мс/LSB, советуют 100-350 мс):

/* Free-fall time */ i2c_tx[0] = AdxlReg.TIME_FF; i2c_tx[1] = 2; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Теперь только выбрать вывод, на который будет выводиться прерывание (если нужно), установкой единицы для бита D2 (FREE_FALL) назначаем ему вывод прерывания на INT2:

/* Int Map */ i2c_tx[0] = AdxlReg.INT_MAP; i2c_tx[1] = ~(1 << D2); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Разрешаем прерывание:

/* Interrupt Enable */ i2c_tx[0] = AdxlReg.INT_ENABLE; i2c_tx[1] = (1 << D2); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

И конечно же нужно начать измерение, если этого не было сделано ранее:

/* Measure */ i2c_tx[0] = AdxlReg.POWER_CTL; i2c_tx[1] = (1 << D3); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000);

Теперь в главном цикле делаем то же самое, что и ранее, только бит здесь будет проверятся D2 FREE_FALL (конечно здесь лучше ловить это через внешнее прерывание на выводе):

Работает отлично (смотреть на синий светодиод, во время падения он погаснет) :

/* USER CODE BEGIN PV */ struct AdxlCommands { uint8_t DEVID; /* Device ID */ uint8_t THRESH_TAP; /* Tap threshold */ uint8_t OFSX; /* X-axis offset */ uint8_t OFSY; /* Y-axis offset */ uint8_t OFSZ; /* Z-axis offset */ uint8_t DUR; /* Tap duration */ uint8_t Latent; /* Tap latency */ uint8_t Window; /* Tap window */ uint8_t THRESH_ACT; /* Activity threshold */ uint8_t THRESH_INACT; /* Inactivity threshold */ uint8_t TIME_INACT; /* Inactivity time */ uint8_t ACT_INACT_CTL; /* Axis enable control for activity and inactivity detection */ uint8_t THRESH_FF; /* Free-fall threshold */ uint8_t TIME_FF; /* Free-fall time */ uint8_t TAP_AXES; /* Axis control for single tap/double tap */ uint8_t ACT_TAP_STATUS; /* Source of single tap/double tap */ uint8_t BW_RATE; /* Data rate and power mode control */ uint8_t POWER_CTL; /* Power-saving features control */ uint8_t INT_ENABLE; /* Interrupt enable control */ uint8_t INT_MAP; /* Interrupt mapping control */ uint8_t INT_SOURCE; /* Source of interrupts */ uint8_t DATA_FORMAT; /* Data format control */ uint8_t DATAX0; /* X-Axis Data 0 */ uint8_t DATAX1; /* X-Axis Data 1 */ uint8_t DATAY0; /* Y-Axis Data 0 */ uint8_t DATAY1; /* Y-Axis Data 1 */ uint8_t DATAZ0; /* Z-Axis Data 0 */ uint8_t DATAZ1; /* Z-Axis Data 1 */ uint8_t FIFO_CTL; /* FIFO control */ uint8_t FIFO_STATUS; /* FIFO status */ }; struct AdxlCommands AdxlReg = {0x00, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}; enum AdxlBitNum { D0, D1, D2, D3, D4, D5, D6, D7 }; struct AdxlData { uint8_t id; int16_t x,y,z; double xg, yg, zg; uint8_t activity; uint8_t int_src; uint8_t raw[6]; }Adxl345; /* ADXL345 I2C address */ #define ADXL_ADR (0x53 << 1) /* USER CODE END PV */ /* USER CODE BEGIN 2 */ /* FREE-FALL */ uint8_t i2c_tx[2]; /* FREE-FALL */ /* Free-fall threshold */ i2c_tx[0] = AdxlReg.THRESH_FF; i2c_tx[1] = 3; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Free-fall time */ i2c_tx[0] = AdxlReg.TIME_FF; i2c_tx[1] = 2; HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Int Map */ i2c_tx[0] = AdxlReg.INT_MAP; i2c_tx[1] = ~(1 << D2); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Interrupt Enable */ i2c_tx[0] = AdxlReg.INT_ENABLE; i2c_tx[1] = (1 << D2); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* Measure */ i2c_tx[0] = AdxlReg.POWER_CTL; i2c_tx[1] = (1 << D3); HAL_I2C_Master_Transmit(&hi2c1, ADXL_ADR, (uint8_t*)i2c_tx, 2, 1000); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { HAL_I2C_Mem_Read(&hi2c1, ADXL_ADR, AdxlReg.INT_SOURCE, 1, &Adxl345.int_src, 1, 100); if(Adxl345.int_src & (1 << D2)) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); Adxl345.int_src = 0; } HAL_Delay(10); /* USER CODE END WHILE */

Видос

Купить на Aliexpress


🏷️ GY-291 ADXL345 3-Axis Sensor Acceleration (1.80💲): https://ali.ski/qtauvp

🏷️ ADXL345 3-axis IIC / SPI (0.89💲): https://ali.ski/qji2qF
🏷️ Digital Sensor ADXL345 (1.12💲): https://ali.ski/OdPBr

🏷️ 5pcs ADXL345BCCZ LGA-14 (8.01💲): https://ali.ski/Ds_9fe
🏷️ 5pcs ADXL345 ADXL345BCCZ LGA-14 (4.50💲): https://ali.ski/0PJRn
🏷️ 5pcs ADXL345 Modules (9.40💲): https://ali.ski/TVSh9

🏷️ STM32F401 Module (3.50💲): https://ali.ski/T9_RBe
🏷️ STM32F401 Black Pill USB C(3.88💲): https://ali.ski/wvygW
🏷️ STM32F401 DevBoard Type-C (3.46💲): https://ali.ski/SvrJ8d


Итого

Датчик классный, буду его использовать и вам советую.

↪️ GitHub:  https://github.com/Egoruch/ADXL345-STM32-HAL

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

796
RSS
Нет комментариев. Ваш будет первым!
Загрузка...