W tym poście chciałbym opisać sposób działanie układu BMI160.
Opis układu:
Układ MEMS Bosch BMI160 (datasheet) jest układem składającym się z 16 bitowego 3 osiowego akcelerometru i 3 osiowego żyroskopu. Można mierzyć przyśpieszenie liniowe w zakresach +/- 2g, 4g, 8, 16g oraz prędkość kątową w zakresie od -/+ 125 dps.
Arduino ESP32:
Na samym początku przetestuje podstawowe działanie czujnika z układem ESP32 komunikacja po interfejsie I2C. Skorzystam z gotowych bibliotek i przykładów dostępnych w serwisie GitHub.
Zacznę od sprawdzenie połączenia z czujnikiem przez zastosowanie scanera I2C w celu ustalenia adresu. Poniższy przykład można znaleźć w Internecie:
- #include <Wire.h>
- void setup()
- {
- Serial.begin (115200);
- Wire.begin (21, 22); // sda= GPIO_21 /scl= GPIO_22
- }
- void Scanner ()
- {
- Serial.println ();
- Serial.println ("I2C scanner. Scanning ...");
- byte count = 0;
- Wire.begin();
- for (byte i = 8; i < 120; i++)
- {
- Wire.beginTransmission (i); // Begin I2C transmission Address (i)
- if (Wire.endTransmission () == 0) // Receive 0 = success (ACK response)
- {
- Serial.print ("Found address: ");
- Serial.print (i, DEC);
- Serial.print (" (0x");
- Serial.print (i, HEX); // PCF8574 7 bit address
- Serial.println (")");
- count++;
- }
- }
- Serial.print ("Found ");
- Serial.print (count, DEC); // numbers of devices
- Serial.println (" device(s).");
- }
- void loop()
- {
- Scanner ();
- delay (100);
- }
Czujnik podłączony do urządzenia w następujący sposób:
- 3V3 - 3V3
- GND - GND
- SCL - D22
- SDA - D21
- SAO - GND
W odpowiedzi w terminalu otrzymamy adres 0x68. Gdy pin SAO zostanie odłączony od linii GND adres urządzenia wynosi 0x69.
Program odczytujący dane z układu (biblioteka do pobrania z serwisu GitHub):
- #include <BMI160Gen.h>
- const int i2c_addr = 0x68;
- void setup() {
- Serial.begin(115200);
- BMI160.begin(BMI160GenClass::I2C_MODE, i2c_addr);
- }
- void loop() {
- int gx, gy, gz;
- BMI160.readGyro(gx, gy, gz);
- Serial.print("g:\t");
- Serial.print(gx);
- Serial.print("\t");
- Serial.print(gy);
- Serial.print("\t");
- Serial.print(gz);
- Serial.println();
- delay(500);
- }
Otrzymane odpowiedzi z odczytem danych wyglądają następująco:
- 01:33:38.004 -> g: 15069 189 4871
- 01:33:38.802 -> g: 2800 -2667 -1027
- 01:33:39.032 -> g: -2903 -2179 8126
- 01:33:39.505 -> g: -18955 -3761 -12845
- 01:33:40.003 -> g: -2875 -2031 -6746
- 01:33:40.516 -> g: 14811 5472 8239
- 01:33:41.009 -> g: 8308 -7069 8401
- 01:33:41.493 -> g: 11115 -1527 3109
- 01:33:42.008 -> g: -32768 -4731 -25564
- 01:33:42.494 -> g: -3444 -2422 -13484
- 01:33:43.028 -> g: 1199 -3269 -6408
Poniżej przykład wykonujący to samo zadanie z wykorzystaniem komunikacji po SPI. Tutaj biblioteka jest dość dobrze przygotowana i wystarczy zmienić deklarację z podaniem modu pracy jako SPI i zdefiniować pin dla CS:
- #include <BMI160Gen.h>
- const int select_pin = 5;
- const int i2c_addr = 0x68;
- void setup() {
- Serial.begin(115200);
- BMI160.begin(BMI160GenClass::SPI_MODE, select_pin);
- }
- void loop() {
- int gx, gy, gz;
- BMI160.readGyro(gx, gy, gz);
- Serial.print("g:\t");
- Serial.print(gx);
- Serial.print("\t");
- Serial.print(gy);
- Serial.print("\t");
- Serial.print(gz);
- Serial.println();
- delay(500);
- }
Podłączenie:
- 3V3 - 3V3
- GND - GND
- CS (SS) - D5
- MISO (SAO) - D19
- MOSI (SDA) - D23
- SCK (SCL) - D18
W odpowiedzi otrzymane zostaną następujące dane:
- 01:10:36.214 -> g: 2865 -2576 2111
- 01:10:36.724 -> g: -92 -22 -56
- 01:10:37.204 -> g: -1988 -10649 12775
- 01:10:37.723 -> g: 2604 -19088 4062
- 01:10:38.205 -> g: 3755 -16539 -6777
- 01:10:38.724 -> g: -5743 20632 9237
- 01:10:39.204 -> g: -10513 23714 2920
- 01:10:39.728 -> g: -2843 -7475 -6811
- 01:10:40.194 -> g: 1084 -17247 -2291
- 01:10:40.721 -> g: 13147 -8942 -6255
- 01:10:41.208 -> g: 990 15187 4411
- 01:10:41.704 -> g: -5058 5372 12371
- 01:10:42.219 -> g: -330 2668 1128
- 01:10:42.704 -> g: -247 -41 193
STM32:
W tej części chciałbym opisać bibliotekę czujnika przygotowaną i przetestowaną na układzie STM32F4. Biblioteka jest przystosowana do obsługi w interfejsie I2C.
Podłączenie jest następujące:
- 3V3 - 3V3
- GND - GND
- SCL - PB6
- SDA - PB7
Do stworzenia biblioteki dla STM32 posłużyłem się wcześniej opisaną biblioteką da Arduino.
Czujnik podłączony jest do interfejsu I2C1. Wygenerowana inicjalizacja wygląda następująco:
- static void MX_I2C1_Init(void)
- {
- /* USER CODE BEGIN I2C1_Init 0 */
- /* USER CODE END I2C1_Init 0 */
- /* USER CODE BEGIN I2C1_Init 1 */
- /* USER CODE END I2C1_Init 1 */
- hi2c1.Instance = I2C1;
- hi2c1.Init.ClockSpeed = 100000;
- hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
- hi2c1.Init.OwnAddress1 = 0;
- hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
- hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
- hi2c1.Init.OwnAddress2 = 0;
- hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
- hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
- if (HAL_I2C_Init(&hi2c1) != HAL_OK)
- {
- Error_Handler();
- }
- /* USER CODE BEGIN I2C1_Init 2 */
- /* USER CODE END I2C1_Init 2 */
- }
Poniżej przedstawię kilka funkcji stosowanych do uruchomienia układu i obsługi. Na samym początku inicjalizacja:
- HAL_StatusTypeDef BMI160_Initialize(void)
- {
- HAL_StatusTypeDef opStatus = HAL_ERROR;
- opStatus = BMI160_PerformSoftReset();
- if(opStatus != HAL_OK) { return opStatus; }
- HAL_Delay(2);
- if(BMI160_CheckSensorID() == 0) { return HAL_ERROR; }
- opStatus = BMI160_PowerUpAccelerometer();
- if(opStatus != HAL_OK) { return opStatus; }
- opStatus = BMI160_PowerUpGyroscope();
- if(opStatus != HAL_OK) { return opStatus; }
- opStatus = BMI160_SetFullScaleGyroRange(BMI160_GYRO_RANGE_250);
- if(opStatus != HAL_OK) { return opStatus; }
- opStatus = BMI160_SetFullScaleAccelRange(BMI160_ACCEL_RANGE_2G);
- if(opStatus != HAL_OK) { return opStatus; }
- opStatus = BMI160_WriteRegister(BMI160_RA_INT_MAP_0, 0xFF);
- if(opStatus != HAL_OK) { return opStatus; }
- opStatus = BMI160_WriteRegister(BMI160_RA_INT_MAP_1, 0xF0);
- if(opStatus != HAL_OK) { return opStatus; }
- opStatus = BMI160_WriteRegister(BMI160_RA_INT_MAP_2, 0x00);
- if(opStatus != HAL_OK) { return opStatus; }
- return HAL_OK;
- }
Komunikacja z czytnikiem odbywa się za pomocą kilku różnych funkcji:
- static HAL_StatusTypeDef BMI160_WriteRegisterBits(uint8_t reg, uint8_t data, unsigned pos, unsigned len)
- {
- uint8_t readData = 0;
- HAL_StatusTypeDef opStatus = BMI160_ReadRegister(reg, 1, &readData);
- if(opStatus != HAL_OK)
- {
- return opStatus;
- }
- uint8_t mask = ((1 << len) - 1) << pos;
- data <<= pos;
- data &= mask;
- readData &= ~(mask);
- readData |= data;
- return BMI160_WriteRegister(reg, readData);
- }
- static uint8_t BMI160_ReadRegBits(uint8_t reg, unsigned pos, unsigned len)
- {
- uint8_t readData = 0;
- HAL_StatusTypeDef opStatus = BMI160_ReadRegister(reg, 1, &readData);
- if(opStatus != HAL_OK)
- {
- return 0x00;
- }
- uint8_t mask = (1 << len) - 1;
- readData >>= pos;
- readData &= mask;
- return readData;
- }
- static HAL_StatusTypeDef BMI160_WriteRegister(uint8_t address, uint8_t cmd)
- {
- uint8_t data[2] = {0x00};
- HAL_StatusTypeDef opResoult = HAL_ERROR;
- data[0] = address;
- data[1] = cmd;
- opResoult = HAL_I2C_Master_Transmit(&hi2c1, BMI160_I2C_ADDR, data, 2, 10);
- return opResoult;
- }
- static HAL_StatusTypeDef BMI160_ReadRegister(uint8_t address, uint8_t dataSize, uint8_t *rec)
- {
- HAL_StatusTypeDef opResoult = HAL_ERROR;
- uint8_t data[1] = {0x00};
- data[0] = address;
- opResoult = HAL_I2C_Master_Transmit(&hi2c1, BMI160_I2C_ADDR, data, 1, 10);
- opResoult = HAL_ERROR;
- opResoult = HAL_I2C_Master_Receive(&hi2c1, BMI160_I2C_ADDR + 1, &rec[0], dataSize, 10);
- return opResoult;
- }
W pętli pobieram dane z czujnika za pomocą funkcji ReadGyro oraz GetAcceleration. Pobierają one te same dane co GetMotion6.
- HAL_StatusTypeDef opstat3 = BMI160_ReadGyro(&x, &y, &z);
- HAL_Delay(100);
- int16_t tempVal = BMI160_GetTemperature();
- convertTemp = BMI160_ConvertRawTemp(tempVal);
- HAL_Delay(100);
- HAL_StatusTypeDef opstat4 = BMI160_GetAcceleration(&x1, &y1, &z1);
- HAL_Delay(100);
- HAL_StatusTypeDef opstat5= BMI160_GetMotion6(&ax1, &ay1, &az1, &gx1, &gy1, &gz1);
- HAL_Delay(300);
Dodatkowo pobieram jeszcze dane z czujnika temperatury.
Pobieranie danych z czujnika następuje przez odczyt odpowiednich rejestrów;
- int16_t BMI160_GetTemperature(void) {
- uint8_t buffer[2] = {0x00};
- HAL_StatusTypeDef opStat = BMI160_ReadRegister(BMI160_RA_TEMP_L, 2, &buffer[0]);
- if(opStat != HAL_OK) { return HAL_ERROR; }
- return (((int16_t)buffer[1]) << 8) | buffer[0];
- }
- float BMI160_ConvertRawTemp(int16_t tempRaw)
- {
- float convertTemp = 0;
- if(tempRaw & 0x8000) { convertTemp = (23.0F - ((0x10000 - tempRaw)/512.0F)); }
- else { convertTemp = ((tempRaw/512.0F) + 23.0F); }
- return convertTemp;
- }
- HAL_StatusTypeDef BMI160Class_GetAcceleration(int16_t* x, int16_t* y, int16_t* z) {
- uint8_t buffer[6] = {0x00};
- HAL_StatusTypeDef opStat = BMI160_ReadRegister(BMI160_RA_ACCEL_X_L, 6, &buffer[0]);
- *x = (((int16_t)buffer[1]) << 8) | buffer[0];
- *y = (((int16_t)buffer[3]) << 8) | buffer[2];
- *z = (((int16_t)buffer[5]) << 8) | buffer[4];
- return opStat;
- }
- HAL_StatusTypeDef BMI160Class_GetMotion6(int16_t* ax, int16_t* ay, int16_t* az, int16_t* gx, int16_t* gy, int16_t* gz) {
- uint8_t buffer[12] = {0x00};
- HAL_StatusTypeDef opStat = BMI160_ReadRegister(BMI160_RA_GYRO_X_L, 12, &buffer[0]);
- *gx = (((int16_t)buffer[1]) << 8) | buffer[0];
- *gy = (((int16_t)buffer[3]) << 8) | buffer[2];
- *gz = (((int16_t)buffer[5]) << 8) | buffer[4];
- *ax = (((int16_t)buffer[7]) << 8) | buffer[6];
- *ay = (((int16_t)buffer[9]) << 8) | buffer[8];
- *az = (((int16_t)buffer[11]) << 8) | buffer[10];
- return opStat;
- }
Konwersja danych z żyroskopu jest wykonywana przez następującą funkcję:
- float BMI160_ScaledData(int16_t gRaw, BMI160GyroRange gyroRange) {
- float g = 0.0F;
- if(gyroRange == BMI160_GYRO_RANGE_2000) {
- g = (gRaw / SENS_2000_DPS_LSB_PER_DPS);
- }
- else if(gyroRange == BMI160_GYRO_RANGE_1000) {
- g = (gRaw / SENS_1000_DPS_LSB_PER_DPS);
- }
- else if(gyroRange == BMI160_GYRO_RANGE_500) {
- g = (gRaw / SENS_500_DPS_LSB_PER_DPS);
- }
- else if(gyroRange == BMI160_GYRO_RANGE_250) {
- g = (gRaw / SENS_250_DPS_LSB_PER_DPS);
- }
- else if(gyroRange == BMI160_GYRO_RANGE_125) {
- g = (gRaw / SENS_125_DPS_LSB_PER_DPS);
- }
- return g;
- }
W kolejny poście postaram się umieścić wykorzystywanie tego układu poprzez komunikację przez SPI.
Bibliotekę wraz z funkcjami nie wypisanymi w tym poście można pobrać z dysku Google pod tym linkiem.