Подключение 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
На его основе можно сделать охранную систему с низким энергопотреблением и высокой чувствительность, а также много другое, например, я его применил в ...
Используете 16 битную переменную и показываете числа до 255 в ней.
Хочу текой же собрать, но не хватает навыков. Есть ли возможность взять мастер класс?