W tym poście chciałbym opisać interfejs I2C z bibliotekami LL.
[Źródło: http://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:
- int8_t I2C_BMI160_ReceiveData(uint8_t devAddr, uint8_t* buffer, uint8_t len, uint16_t maxDelay)
- {
- uint16_t countTimeout = 0;
- if(len == 1) { LL_I2C_AcknowledgeNextData(I2C1, LL_I2C_NACK); }
- else { LL_I2C_AcknowledgeNextData(I2C1, LL_I2C_ACK); }
- LL_I2C_GenerateStartCondition(I2C1);
- while(!LL_I2C_IsActiveFlag_SB(I2C1)) {
- LL_mDelay(1);
- countTimeout++;
- if(countTimeout > maxDelay) {
- return -1;
- }
- }
- LL_I2C_TransmitData8(I2C1, (devAddr) | 0x01);
- while(!LL_I2C_IsActiveFlag_ADDR(I2C1)) {
- LL_mDelay(1);
- countTimeout++;
- if(countTimeout > maxDelay) {
- return -2;
- }
- }
- LL_I2C_ClearFlag_ADDR(I2C1);
- for(int i = 0; i < len; i++) {
- if(i == (len - 1)) {
- LL_I2C_AcknowledgeNextData(I2C1, LL_I2C_NACK);
- }
- while(!LL_I2C_IsActiveFlag_RXNE(I2C1)) {
- LL_mDelay(1);
- countTimeout++;
- if(countTimeout > maxDelay) {
- return -3;
- }
- }
- buffer[i] = LL_I2C_ReceiveData8(I2C1);
- }
- LL_I2C_GenerateStopCondition(I2C1);
- return 0;
- }
Następną funkcja jest przesyłanie danych:
- int8_t I2C_BMI160_SendData(uint8_t devAddr, uint8_t* buffer, uint16_t len, uint16_t maxDelay)
- {
- uint16_t countTimeout = 0;
- LL_I2C_GenerateStartCondition(I2C1);
- while(!LL_I2C_IsActiveFlag_SB(I2C1)) {
- LL_mDelay(1);
- countTimeout++;
- if(countTimeout > maxDelay) {
- return -1;
- }
- }
- LL_I2C_TransmitData8(I2C1, (devAddr) | 0x00);
- while(!LL_I2C_IsActiveFlag_ADDR(I2C1))
- {
- LL_mDelay(1);
- countTimeout++;
- if(countTimeout > maxDelay) {
- return -2;
- }
- }
- LL_I2C_ClearFlag_ADDR(I2C1);
- for(int i=0; i < len; i++)
- {
- while(!LL_I2C_IsActiveFlag_TXE(I2C1)) {
- LL_mDelay(1);
- countTimeout++;
- if(countTimeout > maxDelay) {
- return -3;
- }
- }
- LL_I2C_TransmitData8(I2C1, buffer[i]);
- }
- while(!LL_I2C_IsActiveFlag_BTF(I2C1)) {
- LL_mDelay(1);
- countTimeout++;
- if(countTimeout > maxDelay) {
- return -4;
- }
- }
- LL_I2C_GenerateStopCondition(I2C1);
- return 0;
- }
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:
- static int8_t BMI160_WriteRegister(uint8_t address, uint8_t cmd);
- static int8_t BMI160_ReadRegister(uint8_t address, uint8_t dataSize, uint8_t *rec);
- static uint8_t BMI160_ReadRegBits(uint8_t reg, unsigned pos, unsigned len);
- static int8_t BMI160_WriteRegisterBits(uint8_t reg, uint8_t data, unsigned pos, unsigned len);
Poniżej rozwinięcie funkcji:
- static int8_t BMI160_ReadRegister(uint8_t address, uint8_t dataSize, uint8_t *rec)
- {
- int8_t opResoult = 0;
- uint8_t data[1] = {0x00};
- data[0] = address;
- opResoult = I2C_BMI160_SendData(BMI160_I2C_ADDR, data, 1, 50);
- if(opResoult != 0) { return (opResoult); }
- opResoult = I2C_BMI160_ReceiveData(BMI160_I2C_ADDR + 1, &rec[0], dataSize, 50);
- return opResoult;
- }
- static int8_t BMI160_WriteRegister(uint8_t address, uint8_t cmd)
- {
- uint8_t data[2] = {0x00};
- int8_t opResoult = 0;
- data[0] = address;
- data[1] = cmd;
- opResoult = I2C_BMI160_SendData(BMI160_I2C_ADDR, data, 2, 50);
- return opResoult;
- }
- static uint8_t BMI160_ReadRegBits(uint8_t reg, unsigned pos, unsigned len)
- {
- uint8_t readData = 0;
- int8_t opStatus = BMI160_ReadRegister(reg, 1, &readData);
- if(opStatus != 0) {
- return 0x00;
- }
- uint8_t mask = (1 << len) - 1;
- readData >>= pos;
- readData &= mask;
- return readData;
- }
- static int8_t BMI160_WriteRegisterBits(uint8_t reg, uint8_t data, unsigned pos, unsigned len)
- {
- uint8_t readData = 0;
- int8_t opStatus = BMI160_ReadRegister(reg, 1, &readData);
- if(opStatus != 0) {
- return opStatus;
- }
- uint8_t mask = ((1 << len) - 1) << pos;
- data <<= pos;
- data &= mask;
- readData &= ~(mask);
- readData |= data;
- return BMI160_WriteRegister(reg, readData);
- }
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:
- #ifndef __BMI160_H
- #define __BMI160_H
- #include "main.h"
- #include "bmi160_defines.h"
- int8_t BMI160_Initialize(void);
- int8_t BMI160_ReadChipID(uint8_t *chipId);
- uint8_t BMI160_CheckSensorID(void);
- int8_t BMI160_PerformSoftReset(void);
- int8_t BMI160_PowerUpAccelerometer(void);
- int8_t BMI160_PowerUpGyroscope(void);
- int8_t BMI160_ReadGyro(int16_t* x, int16_t* y, int16_t* z);
- float BMI160_ScaledData(const int16_t gRaw, const BMI160GyroRange gyroRange);
- int8_t BMI160_GetTemperature(int16_t *temp);
- float BMI160_ConvertRawTemp(int16_t tempRaw);
- int8_t BMI160_GetAccelerationX(int16_t *accel);
- int8_t BMI160_GetAccelerationY(int16_t *accel);
- int8_t BMI160_GetAccelerationZ(int16_t *accel);
- int8_t BMI160_GetAcceleration(int16_t* x, int16_t* y, int16_t* z);
- int8_t BMI160_GetMotion6(int16_t* ax, int16_t* ay, int16_t* az, int16_t* gx, int16_t* gy, int16_t* gz);
- BMI160GyroRate BMI160_GetGyroRate(void);
- int8_t BMI160_SetGyroRate(const BMI160GyroRate rate);
- BMI160AccelRate BMI160_GetAccelRate(void);
- int8_t BMI160_SetAccelRate(const BMI160AccelRate rate);
- BMI160DLPFMode BMI160_GetGyroDLPFMode(void);
- BMI160DLPFMode BMI160_GetAccelDLPFMode(void);
- int8_t BMI160_SetAccelDLPFMode(BMI160DLPFMode mode);
- BMI160GyroRange BMI160_GetFullScaleGyroRange(void);
- int8_t BMI160_SetFullScaleGyroRange(const BMI160GyroRange range);
- BMI160AccelRange BMI160_GetFullScaleAccelRange(void);
- int8_t BMI160_SetFullScaleAccelRange(const BMI160AccelRange range);
- uint8_t BMI160_GetAccelOffsetEnabled(void);
- int8_t BMI160_SetAccelOffsetEnabled(const uint8_t enabled);
- int8_t BMI160_AutoCalibrateXAccelOffset(int target);
- int8_t BMI160_AutoCalibrateYAccelOffset(int target);
- int8_t BMI160_AutoCalibrateZAccelOffset(int target);
- int8_t BMI160_GetXAccelOffset(uint8_t *accelOffset);
- int8_t BMI160_SetXAccelOffset(int8_t offset);
- int8_t BMI160_GetYAccelOffset(uint8_t *accelOffset);
- int8_t BMI160_SetYAccelOffset(int8_t offset);
- int8_t BMI160_GetZAccelOffset(uint8_t *accelOffset);
- int8_t BMI160_SetZAccelOffset(int8_t offset);
- uint8_t BMI160_GetGyroOffsetEnabled(void);
- int8_t BMI160_SetGyroOffsetEnabled(uint8_t enabled);
- int8_t BMI160_AutoCalibrateGyroOffset(void);
- int8_t BMI160_GetXGyroOffset(int16_t *gyrOffset);
- int8_t BMI160_SetXGyroOffset(int16_t offset);
- int8_t BMI160_GetYGyroOffset(int16_t *gyrOffset);
- int8_t BMI160_SetYGyroOffset(int16_t offset);
- int8_t BMI160_GetZGyroOffset(int16_t *gyrOffset);
- int8_t BMI160_SetZGyroOffset(int16_t offset);
- uint8_t BMI160_GetFreefallDetectionThreshold(uint8_t *freeFallDetectPtr);
- int8_t BMI160_SetFreefallDetectionThreshold(uint8_t threshold);
- int8_t BMI160_GetFreefallDetectionDuration(uint8_t *freeFallDetectPtr);
- int8_t BMI160_SetFreefallDetectionDuration(uint8_t duration);
- int8_t BMI160_GetShockDetectionThreshold(uint8_t *shockDetectTreshPtr);
- uint8_t BMI160_GetShockDetectionDuration(uint8_t *shockDetectDuratPtr);
- int8_t BMI160_SetShockDetectionDuration(uint8_t duration);
- int8_t BMI160_GetStepDetectionMode(uint8_t *stepModePtr);
- int8_t BMI160_SetStepDetectionMode(const BMI160StepMode mode);
- uint8_t BMI160_GetStepCountEnabled(void);
- int8_t BMI160_SetStepCountEnabled(const uint8_t enabled);
- uint16_t BMI160_GetStepCount(uint16_t *stepCountPtr);
- int8_t BMI160_GetMotionDetectionThreshold(uint8_t * motionDetectTreshPtr);
- int8_t BMI160_SetMotionDetectionThreshold(const uint8_t threshold);
- uint8_t BMI160_GetMotionDetectionDuration(void);
- int8_t BMI160_SetMotionDetectionDuration(const uint8_t samples);
- int8_t BMI160_GetZeroMotionDetectionThreshold(uint8_t * zeroMotionDetectTreshPtr);
- int8_t BMI160_SetZeroMotionDetectionThreshold(const uint8_t threshold);
- uint8_t BMI160_GetZeroMotionDetectionDuration(void);
- int8_t BMI160_SetZeroMotionDetectionDuration(const uint8_t duration);
- uint8_t BMI160_GetTapDetectionThreshold(void);
- int8_t BMI160_SetTapDetectionThreshold(const uint8_t threshold);
- uint8_t BMI160_GetTapShockDuration(void);
- #endif
Przykładowy test układu:
- /* USER CODE BEGIN 2 */
- int16_t tempVal = 0;
- int8_t readGyroStat = 0;
- int8_t readTempValStat = 0;
- int8_t getAccelerationStat = 0;
- int8_t getMotion6Stat = 0;
- float convertTemp = 0.0;
- int16_t x = 0;
- int16_t y = 0;
- int16_t z = 0;
- int16_t x1 = 0;
- int16_t y1 = 0;
- int16_t z1 = 0;
- int16_t ax1 = 0;
- int16_t ay1 = 0;
- int16_t az1 = 0;
- int16_t gx1 = 0;
- int16_t gy1 = 0;
- int16_t gz1 = 0;
- int8_t initOpStat = BMI160_Initialize();
- //BMI160_CheckSensorID();
- /* USER CODE END 2 */
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- /* USER CODE END WHILE */
- readGyroStat = BMI160_ReadGyro(&x, &y, &z);
- LL_mDelay(100);
- readTempValStat = BMI160_GetTemperature(&tempVal);
- convertTemp = BMI160_ConvertRawTemp(tempVal);
- LL_mDelay(100);
- getAccelerationStat = BMI160_GetAcceleration(&x1, &y1, &z1);
- LL_mDelay(100);
- getMotion6Stat = BMI160_GetMotion6(&ax1, &ay1, &az1, &gx1, &gy1, &gz1);
- LL_mDelay(300);
- /* USER CODE BEGIN 3 */
- }
- /* USER CODE END 3 */
[1] BMI160 Datasheet