W przykładzie opiszę sposób obsługi trzy osiowego akcelerometru firmy ST LIS302DL, który był umieszczany na starszych wersjach płytek Discovery z układem STM32F407VG.
Układ do komunikacji wykorzystuje magistralę SPI.
Opis układu
Opisywany czujnik pozwala detekcję przyśpieszeń od +/- 2g do +/- 8g. Komunikacja może się odbywać poprzez dwa interfejsy komunikacyjne SPI bądź I2C.
Rys. 1. LIS302DL
Układ na płytce discovery zasilany jest napięcie 3,3V. Akceptowana wartość napięcia mieści się w przedziale od 2,16V do 3,6V. Maksymalna wartość tego napięcia wynosi 6V.
Podłączenie
Do obsługi należy wykorzystać jeden z dostępnych interfejsów SPI wraz z dedykowanymi do niego pinami MISO, MOSI oraz SCK. Dodatkowo należy oddzielnie obsłużyć pin CS. Służący do wybrania układu.
Jeśli wykorzystuje się płytkę z zamontowanym na niej układem, wtedy wybór SPI sprowadza się do SPI1 i następującego zestawu pinów:
Dodatkowo podłączone są jeszcze dwa piny INT1 do PE0 oraz INT2 do PE1.
Podłączenie na płytce STM32F4 Discovery wygląda następujo:
Jeśli wykorzystuje się płytkę z zamontowanym na niej układem, wtedy wybór SPI sprowadza się do SPI1 i następującego zestawu pinów:
- MOSI do PA7
- MISO do PA6
- SCK do PA5
- CS do PE3
Dodatkowo podłączone są jeszcze dwa piny INT1 do PE0 oraz INT2 do PE1.
Podłączenie na płytce STM32F4 Discovery wygląda następujo:
Rys. 2. Podłączenie MEMS do STM32F4 - Discovery
W przypadku oddzielnego wykorzystywania układu nie należy zapominać o kondensatorze 100nF pomiędzy pinami zasilającymi a masą. Dodatkowo można dołożyć kondensator 10uF, podłączony równolegle z elementem 100nF. Oba kondensatory powinny znajdować się możliwie najbliżej pinu 6.
Programowanie
Poniżej przedstawię listę funkcji jakie są wymagane aby poprawnie uruchomić akcelerometr. W pierwszej kolejności należy uruchomić wybrany zestaw pinów z uruchomionymi funkcjami alternatywnymi wraz z ustawieniem pinu CS:
Po tej operacji przychodzi kolej na uruchomianie interfejsu SPI. Do ustawienia są następujące opcje:
Procedura włączenia SPI wygląda następująco:
Do komunikacji z układem służą następujące rejestry zdefiniowane razem z prototypami w pliku .h:
Kolejne funkcje pozwalają na podstawowe przesyłanie danych przez SPI, oraz przesłanie podwójnej paczki danych. Ostatnia funkcja pozwala na wysłanie danych do akcelerometru.
Jeśli wykorzystuje się biblioteki HALA to wysyłanie paczki danych wygląda trochę inaczej:
Poniżej znajduje się funkcja odbierająca dane od bufora:
Odczyt identyfikatora układu. Dla czujnika LIS302DL powinno się uzyskać wartość 0x3B. Jeśli zostanie zwrócona wartość
Kolejna funkcja pozwoli na ustawienie poszczególnych parametrów układu.
Ostatnia funkcja służy do odczytania danych z poszczególnych osi.
W funkcji głównej całość rozpoczyna się od odebrania danych odnośnie ID urządzenia i ustawienia wybranych parametrów. Później wystarczy już tylko odczytywać dane dla poszczególnych osi.
- void GPIO_LIS302DL_INIT()
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- RCC_AHB1PeriphClockCmd(LIS302DL_MISO_RCC , ENABLE);
- RCC_AHB1PeriphClockCmd(LIS302DL_CS_RCC , ENABLE);
- //MISO & MOSI & SCK
- GPIO_InitStruct.GPIO_Pin = LIS302DL_MISO_PIN | LIS302DL_MOSI_PIN | LIS302DL_SCK_PIN;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(LIS302DL_SPI_PORT, &GPIO_InitStruct);
- GPIO_PinAFConfig(LIS302DL_SPI_PORT, LIS302DL_MISO_PINSOURCE, LIS302DL_SPI_PIN_AF);
- GPIO_PinAFConfig(LIS302DL_SPI_PORT, LIS302DL_MOSI_PINSOURCE, LIS302DL_SPI_PIN_AF);
- GPIO_PinAFConfig(LIS302DL_SPI_PORT, LIS302DL_SCK_PINSOURCE, LIS302DL_SPI_PIN_AF);
- //CS
- GPIO_InitStruct.GPIO_Pin = LIS302DL_CS_PIN;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
- GPIO_Init(LIS302DL_CS_PORT, &GPIO_InitStruct);
- }
Po tej operacji przychodzi kolej na uruchomianie interfejsu SPI. Do ustawienia są następujące opcje:
- SPI_Direction - czyli wybór kierunku transmisji danych;
- SPI_Mode - wybór trybu pracy dla STM-a, master lub slave;
- SPI_DataSize - wielkość paczki danych;
- SPI_CPOL - Clock polarity - ustawienie polaryzacji zegara czy dodatnia czy ujemna;
- SPI_CPHA - na którym zboczu sygnału zegarowego będzie przeprowadzana transmisja;
- SPI_NSS - wybór CS czy programowy czy sprzętowy;
- SPI_BaudRatePrescaler - pozwala na ustawienie szybkości zegara dla SPI, minimalny dzielnik wynosi 2. Dalej można ustawić 4, 8, 16, 32, 64, 128, 256;
- SPI_FirstBit - który bit będzie transmitowany jako pierwszy
- SPI_CRCPolynomial - obliczanie kodu CRC sprawdzającego poprawność transmisji.
Procedura włączenia SPI wygląda następująco:
- void LIS302DL_SPI_Init(void)
- {
- SPI_InitTypeDef SPI_InitTypeDefStruct;
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
- SPI_InitTypeDefStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
- SPI_InitTypeDefStruct.SPI_Mode = SPI_Mode_Master;
- SPI_InitTypeDefStruct.SPI_DataSize = SPI_DataSize_8b;
- SPI_InitTypeDefStruct.SPI_CPOL = SPI_CPOL_High;
- SPI_InitTypeDefStruct.SPI_CPHA = SPI_CPHA_2Edge;
- SPI_InitTypeDefStruct.SPI_NSS = SPI_NSS_Soft;
- SPI_InitTypeDefStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
- SPI_InitTypeDefStruct.SPI_FirstBit = SPI_FirstBit_MSB;
- SPI_Init(LIS302DL_SPI_PORT, &SPI_InitTypeDefStruct);
- GPIO_LIS302DL_INIT();
- //Ustawienie pinu CS w stan wysoki
- LIS302DL_CS_HIGH;
- SPI_Cmd(LIS302DL_SPI_PORT, ENABLE);
- }
Do komunikacji z układem służą następujące rejestry zdefiniowane razem z prototypami w pliku .h:
- #include "stm32f4xx.h"
- #include "stm32f4xx_gpio.h"
- #include "stm32f4xx_spi.h"
- //SPI1 MOSI - PA7 MISO - PA6 SCK - PA5
- #ifndef STM32_LID302DL_H_
- #define STM32_LID302DL_H_
- #define LIS302DL_SPI_PORT SPI1
- #define LIS302DL_SPI_PIN_AF GPIO_AF_SPI1
- #ifndef LIS302DL_MISO_PIN
- #define LIS302DL_MISO_RCC RCC_AHB1Periph_GPIOA
- #define LIS302DL_MISO_PINSOURCE GPIO_PinSource6
- #define LIS302DL_MISO_PORT GPIOA
- #define LIS302DL_MISO_PIN GPIO_Pin_6
- #endif
- #ifndef LIS302DL_MOSI_PIN
- #define LIS302DL_MOSI_RCC RCC_AHB1Periph_GPIOA
- #define LIS302DL_MOSI_PINSOURCE GPIO_PinSource7
- #define LIS302DL_MOSI_PORT GPIOA
- #define LIS302DL_MOSI_PIN GPIO_Pin_7
- #endif
- #ifndef LIS302DL_SCK_PIN
- #define LIS302DL_SCK_RCC RCC_AHB1Periph_GPIOA
- #define LIS302DL_SCK_PINSOURCE GPIO_PinSource5
- #define LIS302DL_SCK_PORT GPIOA
- #define LIS302DL_SCK_PIN GPIO_Pin_5
- #endif
- #ifndef LIS302DL_CS_PIN
- #define LIS302DL_CS_RCC RCC_AHB1Periph_GPIOE
- #define LIS302DL_CS_PORT GPIOE
- #define LIS302DL_CS_PIN GPIO_Pin_3
- #endif
- /* CS pin settings */
- #define LIS302DL_CS_LOW LIS302DL_CS_PORT->BSRRH = LIS302DL_CS_PIN
- #define LIS302DL_CS_HIGH LIS302DL_CS_PORT->BSRRL = LIS302DL_CS_PIN
- /* Who I am values */
- #define LIS302DL_ID 0x3B
- #define LIS302DL_REG_WHO_I_AM 0x0F
- //Rejestry kontrolne
- #define LIS302DL_CTRL_REGISTER1_ADDR 0x20
- #define LIS302DL_CTRL_REGISTER2_ADDR 0x21
- #define LIS302DL_CTRL_REGISTER3_ADDR 0x22
- //Rejestr danych wyjściowcyh
- #define LIS302DL_OUT_X_ADDR 0x29
- #define LIS302DL_OUT_Y_ADDR 0x2B
- #define LIS302DL_OUT_Z_ADDR 0x2D
- //Dodatkowe ustawienia
- #define LIS302DL_LOW_POWER_MODE_ON ((uint8_t)0x40)
- #define LIS302DL_FULLSCALE_2_3 ((uint8_t)0x00)
- #define LIS302DL_FULLSCALE_9_2 ((uint8_t)0x20)
- #define LIS302DL_SELFTEST_NORMAL ((uint8_t)0x00)
- #define LIS302DL_XYZ_ENABLE ((uint8_t)0x07)
- #define LIS302DL_SERIALINTERFACE_4WIRE ((uint8_t)0x00)
- #define LIS302DL_BOOT_NORMALMODE ((uint8_t)0x00)
- #define LIS302DL_BOOT_REBOOTMEMORY ((uint8_t)0x40)
- #define LIS302DL_FILTEREDDATASELECTION_OUTPUTREGISTER ((uint8_t)0x20)
- #define LIS302DL_REG_ID ((uint8_t)0x0F)
- //Wlaczenie przerwan od filtrow
- #define LIS302DL_HIGHPASSFILTERINTERRUPT_OFF ((uint8_t)0x00)
- #define LIS302DL_HIGHPASSFILTERINTERRUPT_1 ((uint8_t)0x04)
- #define LIS302DL_HIGHPASSFILTERINTERRUPT_2 ((uint8_t)0x08)
- #define LIS302DL_HIGHPASSFILTERINTERRUPT_1_2 ((uint8_t)0x0C)
- //Wybranie poziomu filtra
- #define LIS302DL_HIGHPASSFILTER_LEVEL_0 ((uint8_t)0x00)
- #define LIS302DL_HIGHPASSFILTER_LEVEL_1 ((uint8_t)0x01)
- #define LIS302DL_HIGHPASSFILTER_LEVEL_2 ((uint8_t)0x02)
- #define LIS302DL_HIGHPASSFILTER_LEVEL_3 ((uint8_t)0x03)
- //Dobor czulosci
- #define LIS302DL_SENSITIVITY_2_3G 18
- #define LIS302DL_SENSITIVITY_9_2G 72
- #define LIS302DL_LOWPOWERMODE_POWER_DOWN ((uint8_t)0x00)
- #define LIS302DL_LOWPOWERMODE_ACT ((uint8_t)0x40)
- //Wielkosc strumienia danych
- #define LIS302DL_DATARATE_100 ((uint8_t)0x00)
- #define LIS302DL_DATARATE_400 ((uint8_t)0x80)
- typedef struct {
- int16_t X;
- int16_t Y;
- int16_t Z;
- } LIS302DL_t;
- void GPIO_LIS302DL_INIT();
- void LIS302DL_SPI_Init(void);
- uint8_t SPI_SendFunction(SPI_TypeDef* SPIx, uint8_t data);
- void SPI_WRITE_MORE_DATA(SPI_TypeDef* SPIx, uint16_t* dataOut, uint16_t licznik);
- void LIS302DL_WRITE_TO_SPI(uint8_t* data, uint8_t address, uint16_t licznik);
- uint8_t LIS302DL_READ_ID();
- void LIS302DL_INIT_DATA();
- void LIS302DL_READ_XYZ(LIS302DL_t* LIS302DL_Axes);
- void LIS302DL_READ_DATA_FROM_SPI(uint8_t* data, uint8_t address, uint8_t licznik);
- void SPI_READ_MODE_DATA(SPI_TypeDef* SPIx, uint8_t* dataInput, uint8_t trash, uint32_t licznik);
- #endif /* STM32_LID302DL_H_ */
Kolejne funkcje pozwalają na podstawowe przesyłanie danych przez SPI, oraz przesłanie podwójnej paczki danych. Ostatnia funkcja pozwala na wysłanie danych do akcelerometru.
- uint8_t SPI_SendFunction(SPI_TypeDef* SPIx, uint8_t data)
- {
- //Wprowadzanie danych do bufora
- SPIx->DR = data;
- //Odczekanie na zakonczenie transmisji
- while((((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY)));
- //Zwrocenie danych z bufora
- return SPIx->DR;
- }
- void SPI_WRITE_MORE_DATA(SPI_TypeDef* SPIx, uint16_t* dataOut, uint16_t licznik)
- {
- uint16_t i;
- for (i = 0; i < licznik; i++)
- {
- //Dane do bufora
- SPIx->DR = dataOut[i];
- //Czekanie na koniec transmisji
- while((((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY)));
- //Odczytanie rejestru z danymi
- SPIx->DR;
- }
- }
- void LIS302DL_WRITE_TO_SPI(uint8_t* data, uint8_t address, uint16_t licznik)
- {
- //Rozpoczęcie transmisji od ustawienia pinu CS na LOW
- LIS302DL_CS_LOW;
- if (licznik > 1)
- {
- address |= 0x40;
- }
- //Wyslanie adresu do ukladu
- SPI_SendFunction(LIS302DL_SPI_PORT, address);
- //Wyslanie danych przez SPI do ukladu
- SPI_SendFunction(LIS302DL_SPI_PORT, data);
- //Zakonczenie przesyłania danych, pin CS na HIGH
- LIS302DL_CS_HIGH;
- }
Jeśli wykorzystuje się biblioteki HALA to wysyłanie paczki danych wygląda trochę inaczej:
- uint8_t SPISendFunction_FOR_HAL(SPI_TypeDef* SPIx, uint8_t data)
- {
- //Sprawdzenie czy SPI jest wlaczone
- if (!((SPIx)->CR1 & SPI_CR1_SPE)) {return;}
- //Wprowadzenie danych do bufora
- SPIx->DR = data;
- //Odczekanie na zakonczenie transmisji
- while((((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY)));
- //Zwrocenie danych do bufora
- return SPIx->DR;
- }
Poniżej znajduje się funkcja odbierająca dane od bufora:
- void LIS302DL_READ_DATA_FROM_SPI(uint8_t* data, uint8_t address, uint8_t licznik)
- {
- //CS na LOW
- LIS302DL_CS_LOW;
- address |= 0x80;
- if (licznik > 1) { address |= 0x40; }
- //Wyslanie adresu do ukladu
- SPI_SendFunction(LIS302DL_SPI_PORT, address);
- //Odbior przeslanych danych
- SPI_READ_MODE_DATA(LIS302DL_SPI_PORT, data, 0x00, licznik);
- //CS na HIGH
- LIS302DL_CS_HIGH;
- }
- void SPI_READ_MODE_DATA(SPI_TypeDef* SPIx, uint8_t* dataInput, uint8_t trash, uint32_t licznik)
- {
- uint32_t i;
- //Sprawdzenie czy SPI jest wlaczone
- if (!((SPIx)->CR1 & SPI_CR1_SPE)) {return;}
- while((((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY)));
- for (i = 0; i < licznik; i++)
- {
- //Dane do bufora
- SPIx->DR = trash;
- //Czekanie na wyslanie danych
- while((((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY)));
- //Zapis danych do bufora
- dataInput[i] = SPIx->DR;
- }
- }
Odczyt identyfikatora układu. Dla czujnika LIS302DL powinno się uzyskać wartość 0x3B. Jeśli zostanie zwrócona wartość
- uint8_t LIS302DL_READ_ID()
- {
- uint8_t ident;
- //Odczytanie ID
- LIS302DL_READ_DATA_FROM_SPI(&ident, LIS302DL_REG_ID, 1);
- return ident;
- }
Kolejna funkcja pozwoli na ustawienie poszczególnych parametrów układu.
- void LIS302DL_INIT_DATA()
- {
- uint16_t ctrl;
- //Restart czujnika
- //Odczytanie danych 0x21h - CRTL_REG2
- LIS302DL_READ_DATA_FROM_SPI((uint8_t *)&ctrl, LIS302DL_CTRL_REGISTER2_ADDR, 1);
- //Wprowadzenie do rejestru 2 danych ustawiających wartość REEBOT z 0 na 1
- ctrl |= LIS302DL_BOOT_REBOOTMEMORY;
- LIS302DL_WRITE_TO_SPI((uint8_t *)&ctrl, LIS302DL_CTRL_REGISTER2_ADDR, 1);
- //Wlaczenie ustawien
- ctrl = (uint16_t) (LIS302DL_DATARATE_100 | LIS302DL_LOWPOWERMODE_ACT | LIS302DL_SELFTEST_NORMAL | LIS302DL_XYZ_ENABLE);
- ctrl |= (uint16_t) LIS302DL_FULLSCALE_9_2;
- //Wprowadzenie ustawien do rejestru REG1 0x20
- LIS302DL_WRITE_TO_SPI((uint8_t *)&ctrl, LIS302DL_CTRL_REGISTER1_ADDR, 1);
- //Odczytanie danych dotyczących filtra REG2 0x21
- LIS302DL_WRITE_TO_SPI((uint8_t *)&ctrl, LIS302DL_CTRL_REGISTER2_ADDR, 1);
- //Zanegowanie aktualnych ustawień
- ctrl &= (uint8_t) ~(LIS302DL_FILTEREDDATASELECTION_OUTPUTREGISTER | LIS302DL_HIGHPASSFILTER_LEVEL_3 |LIS302DL_HIGHPASSFILTERINTERRUPT_1_2);
- ctrl |= (uint8_t) (LIS302DL_HIGHPASSFILTERINTERRUPT_1_2 | LIS302DL_FILTEREDDATASELECTION_OUTPUTREGISTER);
- ctrl |= (uint8_t) LIS302DL_HIGHPASSFILTER_LEVEL_3;
- //Wprowadzenie ustawien
- LIS302DL_WRITE_TO_SPI((uint8_t *)&ctrl, LIS302DL_CTRL_REGISTER2_ADDR, 1);
- }
Ostatnia funkcja służy do odczytania danych z poszczególnych osi.
- void LIS302DL_READ_XYZ(LIS302DL_t* LIS302DL_Axes)
- {
- //Bufor przechowujący dane z osi
- int8_t buffer[3];
- int16_t SwitchXY;
- //Odczyt danych z rejestrów przechowujących dane
- LIS302DL_READ_DATA_FROM_SPI((uint8_t*)&buffer[0], LIS302DL_OUT_X_ADDR, 1);
- LIS302DL_READ_DATA_FROM_SPI((uint8_t*)&buffer[1], LIS302DL_OUT_Y_ADDR, 1);
- LIS302DL_READ_DATA_FROM_SPI((uint8_t*)&buffer[2], LIS302DL_OUT_Z_ADDR, 1);
- //Przetworzenie wartości odpowiednio x, y, z
- LIS302DL_Axes->X = (int16_t) (buffer[0]) * LIS302DL_SENSITIVITY_9_2G;
- LIS302DL_Axes->Y = (int16_t) (buffer[1]) * LIS302DL_SENSITIVITY_9_2G;
- LIS302DL_Axes->Z = (int16_t) (buffer[2]) * LIS302DL_SENSITIVITY_9_2G;
- SwitchXY = LIS302DL_Axes->X;
- LIS302DL_Axes->X = LIS302DL_Axes->Y;
- LIS302DL_Axes->X = -SwitchXY;
- }
W funkcji głównej całość rozpoczyna się od odebrania danych odnośnie ID urządzenia i ustawienia wybranych parametrów. Później wystarczy już tylko odczytywać dane dla poszczególnych osi.