czwartek, 15 września 2016

[17] STM32F4 - 3 osiowy akcelerometr LSM303D

Tym razem chciałbym omówić obsługę akcelerometru, magnetometru oraz termometru LSM303D. Komunikacja z tym układem odbywa się za pomocą interfejsu I2C bądź SPI. W tym poście opiszę komunikację poprzez I2C.

Opis układu


Na rysunku 1 znajduje się wygląd zewnętrzny modułu:

Rys. 1. Moduł z układem LSM303D [botland]



Parametry czujnika:

  • Napięcie zasilania od 2.5V do 5.5V;
  • Pobór prądu wynosi 5mA;
  • Komunikacja za pomocą interfejsu I2C bądź SPI;
  • Dane z akcelerometru oraz magnetometru są przechowywane w 16 bitowym rejestrze;
  • Konfigurowalny zakres czułości dla akcelerometru wynosi +/-2; 4; 6; 8; 16g;
  • Konfigurowalny zakres czułości dla magnetometr wynosi +/-2; 4; 8; 12 gauss


Poniżej na rysunku 2 znajduje się schemat wykorzystywanego przeze mnie modułu.

Rys. 2. Schemat modułu

Na samej górze znajduje się stabilizator na napięcie 3.3V. Wyprowadzenia sygnałów przechodzą bezpośrednio na wyprowadzenia. Dodatkowo na płytce montowane są rezystory podciągające oraz kondensatory filtrujące. Na wejściach SDA oraz SCL podłączono do tranzystorów, umożliwiających konwersję napięć. Dzięki czemu układ przy komunikacji z I2C może pracować z napięciami od 2.5V do 5V.

Podłączenie


W celu podłączenia układu potrzebne są piny GND i VCC, które wiadomo gdzie są połączone, oraz piny SDA oraz SCL. One natomiast mogą zostać podłączone do następujących wyprowadzeń I2C:

  • I2C1 - SCL: PB6, SDA: PB7;
  • I2C1 - SCL: PB10, SDA: PB11;
  • I2C1 - SCL: PA8, SDA: PC9;
  • I2C2 - SCL: PB8, SDA: PB9;
  • I2C2 - SCL: PF1, SDA: PF0;
  • I2C2 - SCL: PH7, SDA: PH8;
  • I2C3 - SCL: PB6, SDA: PB9;
  • I2C3 - SCL: PH4, SDA: PH5;

Wszystkie linie taktowane są zegara APB1.

Obsługa I2C

Domyślnie pin CS jest podciągnięty do zasilania. Wobec tego domyślnie układ jest przystosowany do komunikacji poprzez magistralę I2C.

Układ posiada 7 bitowy adres. Najmłodszy z tych bitów może być konfigurowany za pomocą linii SA0. Domyślnie pin ten jest podłączony do zasilania.

Programowanie


W pierwszej kolejności należy zdefiniować potrzebne zestawy rejestrów:

  1. #define LSM303D_TEMP_OUT            0x05
  2. #define LSM303D_ADDRES              0x3A
  3. #define LSM303D_STATUS_M            0x07
  4. #define LSM303D_OUT_X_M             0x08
  5. #define LSM303D_OUT_Y_M             0x0a
  6. #define LSM303D_OUT_Z_M             0x0c
  7. #define LSM303D_WHO_AM_I            0x0f
  8. #define LSM303D_CTRL0               0x1f
  9. #define LSM303D_CTRL1               0x20
  10. #define LSM303D_CTRL2               0x21
  11. #define LSM303D_CTRL3               0x22
  12. #define LSM303D_CTRL4               0x23
  13. #define LSM303D_CTRL5               0x24
  14. #define LSM303D_CTRL6               0x25
  15. #define LSM303D_CTRL7               0x26
  16. #define LSM303D_STATUS              0x27
  17. #define LSM303D_OUT_X_A             0x28
  18. #define LSM303D_OUT_Y_A             0x2a
  19. #define LSM303D_OUT_Z_A             0x2c

Następnie należy włączyć odpowiednie porty GPIO oraz interfejs kounikacyjny I2C:

  1. void init_I2C2(void)
  2. {  
  3.         GPIO_InitTypeDef GPIO_InitStruct;
  4.         I2C_InitTypeDef I2C_InitStruct;
  5.    
  6.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
  7.         RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
  8.    
  9.         //SCL: PB8, SDA: PB9;
  10.    
  11.         GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
  12.         GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;      
  13.         GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;     
  14.         GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;        
  15.         GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;          
  16.         GPIO_Init(GPIOB, &GPIO_InitStruct);            
  17.    
  18.         GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_I2C2);
  19.         GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_I2C2);
  20.    
  21.         I2C_InitStruct.I2C_ClockSpeed = 100000;
  22.         I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
  23.         I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
  24.         I2C_InitStruct.I2C_OwnAddress1 = 0x00; 
  25.         I2C_InitStruct.I2C_Ack = I2C_Ack_Disable;      
  26.         I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
  27.         I2C_Init(I2C2, &I2C_InitStruct);
  28.  
  29.         I2C_Cmd(I2C2, ENABLE);
  30. }

Kolejnym krokiem jest funkcja pozwalająca na ustawienie adresu w pamięci:

  1. void LSM303D_REGISTER_START(uint8_t registe_r)
  2. {
  3.     //Wyslanie sygnalu start, czekanie na zakonczenie
  4.     I2C_GenerateSTART(I2C2, ENABLE);
  5.     while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
  6.    
  7.     //Wyslanie adresu ukladu, czekanie na odpowiedz
  8.     I2C_Send7bitAddress(I2C2, DEVICE_LSM303_ADDRES, I2C_Direction_Transmitter);
  9.     while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
  10.    
  11.     //Przeslanie danych do ukladu
  12.     I2C_SendData(I2C2, 0x80 | registe_r);
  13.     while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);
  14. }

Następnie należy przygotować funkcję odczytujące wartości z układu:

  1. Void LSM303D_READ_DATA(uint8_t registe_r, void* data, int size)
  2. {
  3.      int i;
  4.      uint8_t* buffer = (uint8_t*)data;
  5.      
  6.      //Wpisanie danych do rejestru
  7.      LSM303D_REGISTER_START(registe_r);
  8.      
  9.      //Wygenerowanie sygnalu start
  10.      I2C_GenerateSTART(I2C2, ENABLE);
  11.      while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
  12.      
  13.      //Wlaczenie odsylania potwierdzen
  14.      I2C_AcknowledgeConfig(I2C2, ENABLE);
  15.      I2C_Send7bitAddress(I2C2, DEVICE_LSM303_ADDRES, I2C_Direction_Receiver);
  16.      while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);
  17.      
  18.      for (= 0; i < size - 1; i++)
  19.      {
  20.           //Odebranie danych do bufora
  21.             while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);
  22.             buffer[i] = I2C_ReceiveData(I2C2);
  23.      }
  24.      //Wylaczenie wysylania potwierdzen
  25.      I2C_AcknowledgeConfig(I2C2, DISABLE);
  26.      
  27.      I2C_GenerateSTOP(I2C2, ENABLE);
  28.      while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);
  29.      
  30.      //Wpisanie odebranych danych do bufora
  31.      buffer[i] = I2C_ReceiveData(I2C2);
  32. }

Wpisujące dane do układu:

  1. void LSM303D_WRITE_DATA(uint8_t registe_r, const void* data, int buff_size)
  2. {
  3.      int i;
  4.      uint8_t* buffer = (uint8_t*)data;
  5.      
  6.      LSM303D_REGISTER_START(registe_r);
  7.    
  8.      for (= 0; i < buff_size; i++)
  9.      {
  10.          I2C_SendData(I2C2, buffer[i]);
  11.          while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);
  12.      }
  13.      
  14.      I2C_GenerateSTOP(I2C2, ENABLE);
  15. }

Kolejna funkcja ma za zadanie sprawdzenie identyfikatora układu. W tym celu należy odczytać wartość zwracaną z rejestru 0x49. W przypadku odczytania nie poprawnej wartości zostanie zwrócone zero, dla poprawnej zwróci 1.

  1. uint8_t LSM303D_ID_READ(void)
  2. {
  3.     uint8_t IDRead_Data;
  4.    
  5.     LSM303D_READ_DATA(0x0F, &IDRead_Data, sizeof(IDRead_Data));
  6.    
  7.     if(IDRead_Data == 0x49) { return 1; }
  8.     else { return 0; }
  9. }

Po tych funkcjach należy przygotować dwie krótkie funkcje za których pomocą będzie można odczytać dane z rejestrów oraz wprowadzić do nich odpowiednie wartości.

  1. void LSM303D_WRITE_TO_REGISTER(uint8_t registe_r, uint8_t value)
  2. {
  3.     LSM303D_WRITE_DATA(registe_r, &value, sizeof(value));
  4. }

  1. uint8_t LSM303D_READ_REGISTER(uint8_t registe_r)
  2. {
  3.     uint8_t data = 0;
  4.     LSM303D_READ_DATA(registe_r, &data, sizeof(data));
  5.     return data;
  6. }

  1. uint16_t LSM303D_READ_DATA_VALUE(uint8_t registe_r)
  2. {
  3.     uint16_t data = 0;
  4.     LSM303D_READ_DATA(registe_r, &data, sizeof(data));
  5.     return data;
  6. }

Kolejna część programu będzie pozwalała na odczytanie zmierzonej wartości temperatury. Aby ją włączyć należy posłużyć się rejestrem kontrolnym CTRL5. Tam należy ustawić ósmy bit na wartość 1 (TEMP_EN). Włączy to możliwość obsługi czujnika temperatury.

  1. void LSM303D_TEMPERATURE_ON()
  2. {
  3.     LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL5, 0x80);
  4. }

Dane z czujnika należy odczytać poprzez rejestr Temp Out 0x05.

  1. uint16_t LSM303D_TEMPERATURE_READ()
  2. {
  3.     return LSM303D_READ_DATA_VALUE(LSM303D_TEMP_OUT);
  4. }

Następnie przedstawię funkcje włączającą akcelerometr. Ustawienia dokonuje się poprzez rejestr CTRL1. Możliwe do ustawienia są różne prędkości odczytu danych oraz ilość osi jakie mają być odczytywane.

Rejestr CRTL1 wygląda następująco:


AODR Pozwalają na ustawienie odpowiedniej wartości szybkości przesyłu danych.


Czyli jeśli do rejestru wprowadzę wartość 0x40 (01000000) to zostanie wybrane 25Hz. Dla wartości 0x60 (01100000) zostanie ustawione 100Hz. Druga część tzn. 0x07 ustawia bity AZEN, AYEN, AXEN. Przez co zostają włączone opcje odczytu z wszystkich osi.

  1. void LSM303D_ACCELEROMETER_ON()
  2. {
  3.     LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL1, 0x10);
  4.     LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL1, 0x60|0x07);
  5. }

Zmiana czułości układu można dokonać poprzez wprowadzenie danych do rejestru CTRL2:


ABW1 oraz ABW2 ustawiają parametry przepustowości filtru antyaliasingowego. AFS2, AFS1 oraz AFS0 pozwalająna ustawienie wartości czułości układu. Do wyboru są następujące ustawienia:


Domyślnie wartości ustawione są na zero. czyli +/- 2g. Poniżej funkcja ustawiająca wprowadzony parametr:

  1. void LSM303D_ACCELERATION_SCALE(int scale)
  2. {
  3.     //Dopuszczalne parametry to 2/4/6/8/16
  4.     switch(scale)
  5.     {
  6.         case 2:
  7.         {
  8.             //00 000 000
  9.             LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL2, 0x00);
  10.             break;
  11.         }
  12.         case 4:
  13.         {
  14.             //00 001 000
  15.             LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL2, 0x08);
  16.             break;
  17.         }
  18.         case 6:
  19.         {
  20.             //00 010 000
  21.             LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL2, 0x10);
  22.             break;
  23.         }
  24.         case 8:
  25.         {
  26.             //00 011 000
  27.             LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL2, 0x18);
  28.             break;
  29.         }
  30.         case 16:
  31.         {
  32.             //00 100 000
  33.             LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL2, 0x20);
  34.             break;
  35.         }
  36.         default:
  37.         {
  38.             LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL2, 0x00);
  39.             break;
  40.         }
  41.     }
  42. }

Następnie dane należy odebrać:

  1. void LSM303D_ACCELEROMETER_READ()
  2. {
  3.     //Odczyt danych
  4.     uint16_t xdat = LSM303D_READ_DATA_VALUE(LSM303D_OUT_X_A);
  5.     uint16_t ydat = LSM303D_READ_DATA_VALUE(LSM303D_OUT_Y_A);
  6.     uint16_t zdat = LSM303D_READ_DATA_VALUE(LSM303D_OUT_Z_A);
  7.    
  8.     //Przetworzenie danych dla zakresu 2g
  9.     xdat = (xdat * 0x02)/0x7FA6;
  10.     ydat = (ydat * 0x02)/0x7FA6;
  11.     zdat = (zdat * 0x02)/0x7FA6;
  12. }

Literatura:

[1] Opis modułu - Pololu