niedziela, 9 października 2022

[5] STM32F4 - Biblioteka LL - I2C BMI160

W tym poście chciałbym opisać interfejs I2C z bibliotekami LL.

[Źródłohttp://www.st.com/en/evaluation-tools/stm32f4discovery.html]

Komunikacja po I2C zostanie nawiązana z czujnikiem BMI160.

Uruchomienie interfejsu I2C w programie CubeMx jest dosyć standardowe. Jedyną znaczącą różnicą jest ustawienie bibliotek z domyślnych HAL na LL. 

Na samym początku należy przygotować dwie podstawowe funkcje, odpowiedzialne za komunikację po I2C czyli zapis i odczyt danych.

W celu odczytu danych musimy przejść przez następujące procedury:

1 - Konfiguracja bitu NACK, ACK. Zależna od ilości danych jakie będą odczytywane z urządzenia.
2 - Wygenerowanie warunku startu. 
3 - Sprawdzenie czy flaga startu transmisji została ustawiona. 
4 - Wpisanie adresu urządzenia.
5 - Odczekanie na przesłanie adresu.
6 - Wyczyszczenie flagi addresu.
7 - Sprawdzenie flagi RXNE oraz odczytanie danych w pętli. Bajt po bajcie. 
8 - Jeśli odczytany ostatni bajt to zmiana flagi ACK na NACK
9 - Warunek zakończenia transmisji. 

Funkcja wypełniająca zadania opisane powyżej wygląda następująco:

  1. int8_t I2C_BMI160_ReceiveData(uint8_t devAddr, uint8_t* buffer, uint8_t len, uint16_t maxDelay)
  2. {
  3.   uint16_t countTimeout = 0;
  4.  
  5.   if(len == 1) { LL_I2C_AcknowledgeNextData(I2C1, LL_I2C_NACK); }
  6.   else { LL_I2C_AcknowledgeNextData(I2C1, LL_I2C_ACK); }
  7.  
  8.   LL_I2C_GenerateStartCondition(I2C1);
  9.  
  10.   while(!LL_I2C_IsActiveFlag_SB(I2C1)) {
  11.     LL_mDelay(1);
  12.     countTimeout++;
  13.     if(countTimeout > maxDelay) {
  14.         return -1;
  15.     }
  16.   }
  17.  
  18.   LL_I2C_TransmitData8(I2C1, (devAddr) | 0x01);
  19.  
  20.   while(!LL_I2C_IsActiveFlag_ADDR(I2C1)) {
  21.     LL_mDelay(1);
  22.     countTimeout++;
  23.     if(countTimeout > maxDelay) {
  24.         return -2;
  25.     }
  26.   }
  27.  
  28.   LL_I2C_ClearFlag_ADDR(I2C1);
  29.  
  30.   for(int i = 0; i < len; i++) {
  31.     if(i == (len - 1)) {
  32.       LL_I2C_AcknowledgeNextData(I2C1, LL_I2C_NACK);
  33.     }
  34.  
  35.     while(!LL_I2C_IsActiveFlag_RXNE(I2C1)) {
  36.       LL_mDelay(1);
  37.       countTimeout++;
  38.       if(countTimeout > maxDelay) {
  39.           return -3;
  40.       }
  41.     }
  42.  
  43.     buffer[i] = LL_I2C_ReceiveData8(I2C1);
  44.   }
  45.  
  46.   LL_I2C_GenerateStopCondition(I2C1);
  47.  
  48.   return 0;
  49. }

Następną funkcja jest przesyłanie danych:

  1. int8_t I2C_BMI160_SendData(uint8_t devAddr, uint8_t* buffer, uint16_t len, uint16_t maxDelay)
  2. {
  3.   uint16_t countTimeout = 0;
  4.  
  5.   LL_I2C_GenerateStartCondition(I2C1);
  6.  
  7.   while(!LL_I2C_IsActiveFlag_SB(I2C1)) {
  8.     LL_mDelay(1);
  9.     countTimeout++;
  10.     if(countTimeout > maxDelay) {
  11.         return -1;
  12.     }
  13.   }
  14.  
  15.   LL_I2C_TransmitData8(I2C1, (devAddr) | 0x00);
  16.  
  17.   while(!LL_I2C_IsActiveFlag_ADDR(I2C1))
  18.   {
  19.     LL_mDelay(1);
  20.     countTimeout++;
  21.     if(countTimeout > maxDelay) {
  22.         return -2;
  23.     }
  24.   }
  25.  
  26.   LL_I2C_ClearFlag_ADDR(I2C1);
  27.  
  28.   for(int i=0; i < len; i++)
  29.   {
  30.     while(!LL_I2C_IsActiveFlag_TXE(I2C1)) {
  31.       LL_mDelay(1);
  32.       countTimeout++;
  33.       if(countTimeout > maxDelay) {
  34.           return -3;
  35.       }
  36.     }
  37.  
  38.     LL_I2C_TransmitData8(I2C1, buffer[i]);
  39.   }
  40.  
  41.   while(!LL_I2C_IsActiveFlag_BTF(I2C1)) {
  42.     LL_mDelay(1);
  43.     countTimeout++;
  44.     if(countTimeout > maxDelay) {
  45.         return -4;
  46.     }
  47.   }
  48.  
  49.   LL_I2C_GenerateStopCondition(I2C1);
  50.  
  51.   return 0;
  52. }

Tutaj procedura jest następująca:

1 - Wygenerowanie warunku startu.
2 - Sprawdzenie ustawienia flagi Start Bit. Ustawiona po wygenerowaniu sekwencji startu.
3 - Wpisanie adresu układu
4 - Odczekanie na wysłanie adresu
5- Czekanie na zakończenie wysyłania adresu.
6 - Przesłanie danych, sprawdzenie flagi TXE z informacją czy zakończono wysyłanie bajtu danych.
7 - Po zakończenie przesyłania odczekanie na ustawienie flagi BTF (Byte transfer finish).
8 - Wygenerowanie warunku stopu. 

Po przygotowaniu podstawowych funkcji, trzeba przygotować funckje odpowiedzialne za komunikację z układem BMI160:

  1. static int8_t BMI160_WriteRegister(uint8_t address, uint8_t cmd);
  2. static int8_t BMI160_ReadRegister(uint8_t address, uint8_t dataSize, uint8_t *rec);
  3. static uint8_t BMI160_ReadRegBits(uint8_t reg, unsigned pos, unsigned len);
  4. static int8_t BMI160_WriteRegisterBits(uint8_t reg, uint8_t data, unsigned pos, unsigned len);

Poniżej rozwinięcie funkcji:

  1. static int8_t BMI160_ReadRegister(uint8_t address, uint8_t dataSize, uint8_t *rec)
  2. {
  3.     int8_t opResoult = 0;
  4.     uint8_t data[1] = {0x00};
  5.     data[0] = address;
  6.  
  7.     opResoult = I2C_BMI160_SendData(BMI160_I2C_ADDR, data, 1, 50);
  8.     if(opResoult != 0) { return (opResoult); }
  9.     opResoult = I2C_BMI160_ReceiveData(BMI160_I2C_ADDR + 1, &rec[0], dataSize, 50);
  10.  
  11.     return opResoult;
  12. }

  1. static int8_t BMI160_WriteRegister(uint8_t address, uint8_t cmd)
  2. {
  3.     uint8_t data[2] = {0x00};
  4.     int8_t opResoult = 0;
  5.     data[0] = address;
  6.     data[1] = cmd;
  7.  
  8.     opResoult = I2C_BMI160_SendData(BMI160_I2C_ADDR, data, 2, 50);
  9.  
  10.     return opResoult;
  11. }

  1. static uint8_t BMI160_ReadRegBits(uint8_t reg, unsigned pos, unsigned len)
  2. {
  3.     uint8_t readData = 0;
  4.     int8_t opStatus = BMI160_ReadRegister(reg, 1, &readData);
  5.  
  6.     if(opStatus != 0) {
  7.         return 0x00;
  8.     }
  9.  
  10.     uint8_t mask = (1 << len) - 1;
  11.     readData >>= pos;
  12.     readData &= mask;
  13.  
  14.     return readData;
  15. }

  1. static int8_t BMI160_WriteRegisterBits(uint8_t reg, uint8_t data, unsigned pos, unsigned len)
  2. {
  3.     uint8_t readData = 0;
  4.     int8_t opStatus = BMI160_ReadRegister(reg, 1, &readData);
  5.  
  6.     if(opStatus != 0) {
  7.         return opStatus;
  8.     }
  9.  
  10.     uint8_t mask = ((1 << len) - 1) << pos;
  11.  
  12.     data <<= pos;
  13.     data &= mask;
  14.     readData &= ~(mask);
  15.     readData |= data;
  16.  
  17.     return BMI160_WriteRegister(reg, readData);
  18. }

Pozostałe funkcje są prawie identyczne jak te opisywane w tym poście. Jedyna różnica polega na usunięciu funkcji oraz zmiennych bezpośrednio odwołujących się do zmiennych/funkcji z biblioteki HAL.

Lista zaimplementowanych funkcji jest następująca:

  1. #ifndef __BMI160_H
  2. #define __BMI160_H
  3.  
  4. #include "main.h"
  5. #include "bmi160_defines.h"
  6.  
  7. int8_t BMI160_Initialize(void);
  8. int8_t BMI160_ReadChipID(uint8_t *chipId);
  9. uint8_t BMI160_CheckSensorID(void);
  10. int8_t BMI160_PerformSoftReset(void);
  11. int8_t BMI160_PowerUpAccelerometer(void);
  12. int8_t BMI160_PowerUpGyroscope(void);
  13. int8_t BMI160_ReadGyro(int16_t* x, int16_t* y, int16_t* z);
  14. float BMI160_ScaledData(const int16_t gRaw, const BMI160GyroRange gyroRange);
  15. int8_t BMI160_GetTemperature(int16_t *temp);
  16. float BMI160_ConvertRawTemp(int16_t tempRaw);
  17. int8_t BMI160_GetAccelerationX(int16_t *accel);
  18. int8_t BMI160_GetAccelerationY(int16_t *accel);
  19. int8_t BMI160_GetAccelerationZ(int16_t *accel);
  20. int8_t BMI160_GetAcceleration(int16_t* x, int16_t* y, int16_t* z);
  21. int8_t BMI160_GetMotion6(int16_t* ax, int16_t* ay, int16_t* az, int16_t* gx, int16_t* gy, int16_t* gz);
  22. BMI160GyroRate BMI160_GetGyroRate(void);
  23. int8_t BMI160_SetGyroRate(const BMI160GyroRate rate);
  24. BMI160AccelRate BMI160_GetAccelRate(void);
  25. int8_t BMI160_SetAccelRate(const BMI160AccelRate rate);
  26. BMI160DLPFMode BMI160_GetGyroDLPFMode(void);
  27. BMI160DLPFMode BMI160_GetAccelDLPFMode(void);
  28. int8_t BMI160_SetAccelDLPFMode(BMI160DLPFMode mode);
  29. BMI160GyroRange BMI160_GetFullScaleGyroRange(void);
  30. int8_t BMI160_SetFullScaleGyroRange(const BMI160GyroRange range);
  31. BMI160AccelRange BMI160_GetFullScaleAccelRange(void);
  32. int8_t BMI160_SetFullScaleAccelRange(const BMI160AccelRange range);
  33. uint8_t BMI160_GetAccelOffsetEnabled(void);
  34. int8_t BMI160_SetAccelOffsetEnabled(const uint8_t enabled);
  35. int8_t BMI160_AutoCalibrateXAccelOffset(int target);
  36. int8_t BMI160_AutoCalibrateYAccelOffset(int target);
  37. int8_t BMI160_AutoCalibrateZAccelOffset(int target);
  38. int8_t BMI160_GetXAccelOffset(uint8_t *accelOffset);
  39. int8_t BMI160_SetXAccelOffset(int8_t offset);
  40. int8_t BMI160_GetYAccelOffset(uint8_t *accelOffset);
  41. int8_t BMI160_SetYAccelOffset(int8_t offset);
  42. int8_t BMI160_GetZAccelOffset(uint8_t *accelOffset);
  43. int8_t BMI160_SetZAccelOffset(int8_t offset);
  44. uint8_t BMI160_GetGyroOffsetEnabled(void);
  45. int8_t BMI160_SetGyroOffsetEnabled(uint8_t enabled);
  46. int8_t BMI160_AutoCalibrateGyroOffset(void);
  47. int8_t BMI160_GetXGyroOffset(int16_t *gyrOffset);
  48. int8_t BMI160_SetXGyroOffset(int16_t offset);
  49. int8_t BMI160_GetYGyroOffset(int16_t *gyrOffset);
  50. int8_t BMI160_SetYGyroOffset(int16_t offset);
  51. int8_t BMI160_GetZGyroOffset(int16_t *gyrOffset);
  52. int8_t BMI160_SetZGyroOffset(int16_t offset);
  53. uint8_t BMI160_GetFreefallDetectionThreshold(uint8_t *freeFallDetectPtr);
  54. int8_t BMI160_SetFreefallDetectionThreshold(uint8_t threshold);
  55. int8_t BMI160_GetFreefallDetectionDuration(uint8_t *freeFallDetectPtr);
  56. int8_t BMI160_SetFreefallDetectionDuration(uint8_t duration);
  57. int8_t BMI160_GetShockDetectionThreshold(uint8_t *shockDetectTreshPtr);
  58. uint8_t BMI160_GetShockDetectionDuration(uint8_t *shockDetectDuratPtr);
  59. int8_t BMI160_SetShockDetectionDuration(uint8_t duration);
  60. int8_t BMI160_GetStepDetectionMode(uint8_t *stepModePtr);
  61. int8_t BMI160_SetStepDetectionMode(const BMI160StepMode mode);
  62. uint8_t BMI160_GetStepCountEnabled(void);
  63. int8_t BMI160_SetStepCountEnabled(const uint8_t enabled);
  64. uint16_t BMI160_GetStepCount(uint16_t *stepCountPtr);
  65. int8_t BMI160_GetMotionDetectionThreshold(uint8_t * motionDetectTreshPtr);
  66. int8_t BMI160_SetMotionDetectionThreshold(const uint8_t threshold);
  67. uint8_t BMI160_GetMotionDetectionDuration(void);
  68. int8_t BMI160_SetMotionDetectionDuration(const uint8_t samples);
  69. int8_t BMI160_GetZeroMotionDetectionThreshold(uint8_t * zeroMotionDetectTreshPtr);
  70. int8_t BMI160_SetZeroMotionDetectionThreshold(const uint8_t threshold);
  71. uint8_t BMI160_GetZeroMotionDetectionDuration(void);
  72. int8_t BMI160_SetZeroMotionDetectionDuration(const uint8_t duration);
  73. uint8_t BMI160_GetTapDetectionThreshold(void);
  74. int8_t BMI160_SetTapDetectionThreshold(const uint8_t threshold);
  75. uint8_t BMI160_GetTapShockDuration(void);
  76.  
  77. #endif

Przykładowy test układu:

  1.   /* USER CODE BEGIN 2 */
  2.   int16_t tempVal = 0;
  3.   int8_t readGyroStat = 0;
  4.   int8_t readTempValStat = 0;
  5.   int8_t getAccelerationStat = 0;
  6.   int8_t getMotion6Stat = 0;
  7.   float convertTemp = 0.0;
  8.   int16_t x = 0;
  9.   int16_t y = 0;
  10.   int16_t z = 0;
  11.   int16_t x1 = 0;
  12.   int16_t y1 = 0;
  13.   int16_t z1 = 0;
  14.   int16_t ax1 = 0;
  15.   int16_t ay1 = 0;
  16.   int16_t az1 = 0;
  17.   int16_t gx1 = 0;
  18.   int16_t gy1 = 0;
  19.   int16_t gz1 = 0;
  20.   int8_t initOpStat = BMI160_Initialize();
  21.   //BMI160_CheckSensorID();
  22.   /* USER CODE END 2 */
  23.  
  24.   /* Infinite loop */
  25.   /* USER CODE BEGIN WHILE */
  26.   while (1)
  27.   {
  28.     /* USER CODE END WHILE */
  29.       readGyroStat = BMI160_ReadGyro(&x, &y, &z);
  30.       LL_mDelay(100);
  31.       readTempValStat = BMI160_GetTemperature(&tempVal);
  32.       convertTemp = BMI160_ConvertRawTemp(tempVal);
  33.       LL_mDelay(100);
  34.       getAccelerationStat = BMI160_GetAcceleration(&x1, &y1, &z1);
  35.       LL_mDelay(100);
  36.       getMotion6Stat = BMI160_GetMotion6(&ax1, &ay1, &az1, &gx1, &gy1, &gz1);
  37.       LL_mDelay(300);
  38.     /* USER CODE BEGIN 3 */
  39.   }
  40.   /* USER CODE END 3 */

Pliki do projektu można pobrać z dysku Google pod tym linkiem.