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:
- #define LSM303D_TEMP_OUT 0x05
- #define LSM303D_ADDRES 0x3A
- #define LSM303D_STATUS_M 0x07
- #define LSM303D_OUT_X_M 0x08
- #define LSM303D_OUT_Y_M 0x0a
- #define LSM303D_OUT_Z_M 0x0c
- #define LSM303D_WHO_AM_I 0x0f
- #define LSM303D_CTRL0 0x1f
- #define LSM303D_CTRL1 0x20
- #define LSM303D_CTRL2 0x21
- #define LSM303D_CTRL3 0x22
- #define LSM303D_CTRL4 0x23
- #define LSM303D_CTRL5 0x24
- #define LSM303D_CTRL6 0x25
- #define LSM303D_CTRL7 0x26
- #define LSM303D_STATUS 0x27
- #define LSM303D_OUT_X_A 0x28
- #define LSM303D_OUT_Y_A 0x2a
- #define LSM303D_OUT_Z_A 0x2c
Następnie należy włączyć odpowiednie porty GPIO oraz interfejs kounikacyjny I2C:
- void init_I2C2(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- I2C_InitTypeDef I2C_InitStruct;
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
- //SCL: PB8, SDA: PB9;
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
- GPIO_Init(GPIOB, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_I2C2);
- GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_I2C2);
- I2C_InitStruct.I2C_ClockSpeed = 100000;
- I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
- I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
- I2C_InitStruct.I2C_OwnAddress1 = 0x00;
- I2C_InitStruct.I2C_Ack = I2C_Ack_Disable;
- I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
- I2C_Init(I2C2, &I2C_InitStruct);
- I2C_Cmd(I2C2, ENABLE);
- }
Kolejnym krokiem jest funkcja pozwalająca na ustawienie adresu w pamięci:
- void LSM303D_REGISTER_START(uint8_t registe_r)
- {
- //Wyslanie sygnalu start, czekanie na zakonczenie
- I2C_GenerateSTART(I2C2, ENABLE);
- while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
- //Wyslanie adresu ukladu, czekanie na odpowiedz
- I2C_Send7bitAddress(I2C2, DEVICE_LSM303_ADDRES, I2C_Direction_Transmitter);
- while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
- //Przeslanie danych do ukladu
- I2C_SendData(I2C2, 0x80 | registe_r);
- while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);
- }
Następnie należy przygotować funkcję odczytujące wartości z układu:
- Void LSM303D_READ_DATA(uint8_t registe_r, void* data, int size)
- {
- int i;
- uint8_t* buffer = (uint8_t*)data;
- //Wpisanie danych do rejestru
- LSM303D_REGISTER_START(registe_r);
- //Wygenerowanie sygnalu start
- I2C_GenerateSTART(I2C2, ENABLE);
- while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
- //Wlaczenie odsylania potwierdzen
- I2C_AcknowledgeConfig(I2C2, ENABLE);
- I2C_Send7bitAddress(I2C2, DEVICE_LSM303_ADDRES, I2C_Direction_Receiver);
- while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);
- for (i = 0; i < size - 1; i++)
- {
- //Odebranie danych do bufora
- while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);
- buffer[i] = I2C_ReceiveData(I2C2);
- }
- //Wylaczenie wysylania potwierdzen
- I2C_AcknowledgeConfig(I2C2, DISABLE);
- I2C_GenerateSTOP(I2C2, ENABLE);
- while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);
- //Wpisanie odebranych danych do bufora
- buffer[i] = I2C_ReceiveData(I2C2);
- }
Wpisujące dane do układu:
- void LSM303D_WRITE_DATA(uint8_t registe_r, const void* data, int buff_size)
- {
- int i;
- uint8_t* buffer = (uint8_t*)data;
- LSM303D_REGISTER_START(registe_r);
- for (i = 0; i < buff_size; i++)
- {
- I2C_SendData(I2C2, buffer[i]);
- while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);
- }
- I2C_GenerateSTOP(I2C2, ENABLE);
- }
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.
- uint8_t LSM303D_ID_READ(void)
- {
- uint8_t IDRead_Data;
- LSM303D_READ_DATA(0x0F, &IDRead_Data, sizeof(IDRead_Data));
- if(IDRead_Data == 0x49) { return 1; }
- else { return 0; }
- }
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.
- void LSM303D_WRITE_TO_REGISTER(uint8_t registe_r, uint8_t value)
- {
- LSM303D_WRITE_DATA(registe_r, &value, sizeof(value));
- }
- uint8_t LSM303D_READ_REGISTER(uint8_t registe_r)
- {
- uint8_t data = 0;
- LSM303D_READ_DATA(registe_r, &data, sizeof(data));
- return data;
- }
- uint16_t LSM303D_READ_DATA_VALUE(uint8_t registe_r)
- {
- uint16_t data = 0;
- LSM303D_READ_DATA(registe_r, &data, sizeof(data));
- return data;
- }
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.
- void LSM303D_TEMPERATURE_ON()
- {
- LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL5, 0x80);
- }
Dane z czujnika należy odczytać poprzez rejestr Temp Out 0x05.
- uint16_t LSM303D_TEMPERATURE_READ()
- {
- return LSM303D_READ_DATA_VALUE(LSM303D_TEMP_OUT);
- }
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:
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.
- void LSM303D_ACCELEROMETER_ON()
- {
- LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL1, 0x10);
- LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL1, 0x60|0x07);
- }
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:
- void LSM303D_ACCELERATION_SCALE(int scale)
- {
- //Dopuszczalne parametry to 2/4/6/8/16
- switch(scale)
- {
- case 2:
- {
- //00 000 000
- LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL2, 0x00);
- break;
- }
- case 4:
- {
- //00 001 000
- LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL2, 0x08);
- break;
- }
- case 6:
- {
- //00 010 000
- LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL2, 0x10);
- break;
- }
- case 8:
- {
- //00 011 000
- LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL2, 0x18);
- break;
- }
- case 16:
- {
- //00 100 000
- LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL2, 0x20);
- break;
- }
- default:
- {
- LSM303D_WRITE_TO_REGISTER(LSM303D_CTRL2, 0x00);
- break;
- }
- }
- }
Następnie dane należy odebrać:
- void LSM303D_ACCELEROMETER_READ()
- {
- //Odczyt danych
- uint16_t xdat = LSM303D_READ_DATA_VALUE(LSM303D_OUT_X_A);
- uint16_t ydat = LSM303D_READ_DATA_VALUE(LSM303D_OUT_Y_A);
- uint16_t zdat = LSM303D_READ_DATA_VALUE(LSM303D_OUT_Z_A);
- //Przetworzenie danych dla zakresu 2g
- xdat = (xdat * 0x02)/0x7FA6;
- ydat = (ydat * 0x02)/0x7FA6;
- zdat = (zdat * 0x02)/0x7FA6;
- }