wtorek, 23 sierpnia 2016

[12] STM32F4 - Discovery - Akcelerometr LIS302DL

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:

  • 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:

  1. void GPIO_LIS302DL_INIT()
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStruct;
  4.  
  5.     RCC_AHB1PeriphClockCmd(LIS302DL_MISO_RCC , ENABLE);
  6.     RCC_AHB1PeriphClockCmd(LIS302DL_CS_RCC , ENABLE);
  7.  
  8.     //MISO & MOSI & SCK
  9.     GPIO_InitStruct.GPIO_Pin = LIS302DL_MISO_PIN | LIS302DL_MOSI_PIN  | LIS302DL_SCK_PIN;
  10.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
  11.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  12.     GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
  13.     GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
  14.  
  15.     GPIO_Init(LIS302DL_SPI_PORT, &GPIO_InitStruct);
  16.  
  17.     GPIO_PinAFConfig(LIS302DL_SPI_PORT, LIS302DL_MISO_PINSOURCE, LIS302DL_SPI_PIN_AF);
  18.     GPIO_PinAFConfig(LIS302DL_SPI_PORT, LIS302DL_MOSI_PINSOURCE, LIS302DL_SPI_PIN_AF);
  19.     GPIO_PinAFConfig(LIS302DL_SPI_PORT, LIS302DL_SCK_PINSOURCE, LIS302DL_SPI_PIN_AF);
  20.     //CS
  21.     GPIO_InitStruct.GPIO_Pin = LIS302DL_CS_PIN;
  22.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
  23.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  24.     GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
  25.     GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
  26.     GPIO_Init(LIS302DL_CS_PORT, &GPIO_InitStruct);
  27. }

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:

  1. void LIS302DL_SPI_Init(void)
  2. {
  3.    SPI_InitTypeDef SPI_InitTypeDefStruct;
  4.  
  5.    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
  6.  
  7.    SPI_InitTypeDefStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  8.    SPI_InitTypeDefStruct.SPI_Mode = SPI_Mode_Master;
  9.    SPI_InitTypeDefStruct.SPI_DataSize = SPI_DataSize_8b;
  10.    SPI_InitTypeDefStruct.SPI_CPOL = SPI_CPOL_High;
  11.    SPI_InitTypeDefStruct.SPI_CPHA = SPI_CPHA_2Edge;
  12.    SPI_InitTypeDefStruct.SPI_NSS = SPI_NSS_Soft;
  13.    SPI_InitTypeDefStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
  14.    SPI_InitTypeDefStruct.SPI_FirstBit = SPI_FirstBit_MSB;
  15.    SPI_Init(LIS302DL_SPI_PORT, &SPI_InitTypeDefStruct);
  16.    GPIO_LIS302DL_INIT();
  17.  
  18.    //Ustawienie pinu CS w stan wysoki
  19.    LIS302DL_CS_HIGH;
  20.  
  21.    SPI_Cmd(LIS302DL_SPI_PORT, ENABLE);
  22. }

Do komunikacji z układem służą następujące rejestry zdefiniowane razem z prototypami w pliku .h:

  1. #include "stm32f4xx.h"
  2. #include "stm32f4xx_gpio.h"
  3. #include "stm32f4xx_spi.h"
  4.  
  5. //SPI1    MOSI - PA7    MISO - PA6     SCK - PA5
  6.  
  7. #ifndef STM32_LID302DL_H_
  8. #define STM32_LID302DL_H_
  9.  
  10. #define LIS302DL_SPI_PORT               SPI1
  11.  
  12. #define LIS302DL_SPI_PIN_AF             GPIO_AF_SPI1
  13.  
  14. #ifndef LIS302DL_MISO_PIN
  15. #define LIS302DL_MISO_RCC               RCC_AHB1Periph_GPIOA
  16. #define LIS302DL_MISO_PINSOURCE         GPIO_PinSource6
  17. #define LIS302DL_MISO_PORT              GPIOA
  18. #define LIS302DL_MISO_PIN               GPIO_Pin_6
  19. #endif
  20.  
  21. #ifndef LIS302DL_MOSI_PIN
  22. #define LIS302DL_MOSI_RCC               RCC_AHB1Periph_GPIOA
  23. #define LIS302DL_MOSI_PINSOURCE         GPIO_PinSource7
  24. #define LIS302DL_MOSI_PORT              GPIOA
  25. #define LIS302DL_MOSI_PIN               GPIO_Pin_7
  26. #endif
  27.  
  28. #ifndef LIS302DL_SCK_PIN
  29. #define LIS302DL_SCK_RCC                RCC_AHB1Periph_GPIOA
  30. #define LIS302DL_SCK_PINSOURCE          GPIO_PinSource5
  31. #define LIS302DL_SCK_PORT               GPIOA
  32. #define LIS302DL_SCK_PIN                GPIO_Pin_5
  33. #endif
  34.  
  35. #ifndef LIS302DL_CS_PIN
  36. #define LIS302DL_CS_RCC             RCC_AHB1Periph_GPIOE
  37. #define LIS302DL_CS_PORT            GPIOE
  38. #define LIS302DL_CS_PIN             GPIO_Pin_3
  39. #endif
  40.  
  41. /* CS pin settings */
  42. #define LIS302DL_CS_LOW             LIS302DL_CS_PORT->BSRRH = LIS302DL_CS_PIN
  43. #define LIS302DL_CS_HIGH            LIS302DL_CS_PORT->BSRRL = LIS302DL_CS_PIN
  44.  
  45. /* Who I am values */
  46. #define LIS302DL_ID                                     0x3B
  47.  
  48. #define LIS302DL_REG_WHO_I_AM                           0x0F
  49.  
  50. //Rejestry kontrolne
  51. #define LIS302DL_CTRL_REGISTER1_ADDR                    0x20
  52. #define LIS302DL_CTRL_REGISTER2_ADDR                    0x21
  53. #define LIS302DL_CTRL_REGISTER3_ADDR                    0x22
  54.  
  55. //Rejestr danych wyjściowcyh
  56. #define LIS302DL_OUT_X_ADDR                             0x29
  57. #define LIS302DL_OUT_Y_ADDR                             0x2B
  58. #define LIS302DL_OUT_Z_ADDR                             0x2D
  59.  
  60. //Dodatkowe ustawienia
  61. #define LIS302DL_LOW_POWER_MODE_ON                      ((uint8_t)0x40)
  62. #define LIS302DL_FULLSCALE_2_3                          ((uint8_t)0x00)
  63. #define LIS302DL_FULLSCALE_9_2                          ((uint8_t)0x20)
  64. #define LIS302DL_SELFTEST_NORMAL                        ((uint8_t)0x00)
  65. #define LIS302DL_XYZ_ENABLE                             ((uint8_t)0x07)
  66. #define LIS302DL_SERIALINTERFACE_4WIRE                  ((uint8_t)0x00)
  67. #define LIS302DL_BOOT_NORMALMODE                        ((uint8_t)0x00)
  68. #define LIS302DL_BOOT_REBOOTMEMORY                      ((uint8_t)0x40)
  69. #define LIS302DL_FILTEREDDATASELECTION_OUTPUTREGISTER   ((uint8_t)0x20)
  70. #define LIS302DL_REG_ID                                 ((uint8_t)0x0F)
  71.  
  72. //Wlaczenie przerwan od filtrow
  73. #define LIS302DL_HIGHPASSFILTERINTERRUPT_OFF            ((uint8_t)0x00)
  74. #define LIS302DL_HIGHPASSFILTERINTERRUPT_1              ((uint8_t)0x04)
  75. #define LIS302DL_HIGHPASSFILTERINTERRUPT_2              ((uint8_t)0x08)
  76. #define LIS302DL_HIGHPASSFILTERINTERRUPT_1_2            ((uint8_t)0x0C)
  77.  
  78. //Wybranie poziomu filtra
  79. #define LIS302DL_HIGHPASSFILTER_LEVEL_0                 ((uint8_t)0x00)
  80. #define LIS302DL_HIGHPASSFILTER_LEVEL_1                 ((uint8_t)0x01)
  81. #define LIS302DL_HIGHPASSFILTER_LEVEL_2                 ((uint8_t)0x02)
  82. #define LIS302DL_HIGHPASSFILTER_LEVEL_3                 ((uint8_t)0x03)
  83.  
  84. //Dobor czulosci
  85. #define LIS302DL_SENSITIVITY_2_3G                       18
  86. #define LIS302DL_SENSITIVITY_9_2G                       72
  87.  
  88. #define LIS302DL_LOWPOWERMODE_POWER_DOWN                   ((uint8_t)0x00)
  89. #define LIS302DL_LOWPOWERMODE_ACT                           ((uint8_t)0x40)
  90.  
  91. //Wielkosc strumienia danych
  92. #define LIS302DL_DATARATE_100                           ((uint8_t)0x00)
  93. #define LIS302DL_DATARATE_400                           ((uint8_t)0x80)
  94.  
  95. typedef struct {
  96.     int16_t X;
  97.     int16_t Y;
  98.     int16_t Z;
  99. } LIS302DL_t;
  100.  
  101. void GPIO_LIS302DL_INIT();
  102.  
  103. void LIS302DL_SPI_Init(void);
  104.  
  105. uint8_t SPI_SendFunction(SPI_TypeDef* SPIx, uint8_t data);
  106.  
  107. void SPI_WRITE_MORE_DATA(SPI_TypeDef* SPIx, uint16_t* dataOut, uint16_t licznik);
  108.  
  109. void LIS302DL_WRITE_TO_SPI(uint8_t* data, uint8_t address, uint16_t licznik);
  110.  
  111. uint8_t LIS302DL_READ_ID();
  112.  
  113. void LIS302DL_INIT_DATA();
  114.  
  115. void LIS302DL_READ_XYZ(LIS302DL_t* LIS302DL_Axes);
  116.  
  117. void LIS302DL_READ_DATA_FROM_SPI(uint8_t* data, uint8_t address, uint8_t licznik);
  118.  
  119. void SPI_READ_MODE_DATA(SPI_TypeDef* SPIx, uint8_t* dataInput, uint8_t trash, uint32_t licznik);
  120.  
  121.  
  122. #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.

  1. uint8_t SPI_SendFunction(SPI_TypeDef* SPIx, uint8_t data)
  2. {
  3.     //Wprowadzanie danych do bufora
  4.     SPIx->DR = data;
  5.     //Odczekanie na zakonczenie transmisji
  6.     while((((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY)));
  7.     //Zwrocenie danych z bufora
  8.     return SPIx->DR;
  9. }
  10.  
  11. void SPI_WRITE_MORE_DATA(SPI_TypeDef* SPIx, uint16_t* dataOut, uint16_t licznik)
  12. {
  13.     uint16_t i;
  14.  
  15.     for (= 0; i < licznik; i++)
  16.     {
  17.         //Dane do bufora
  18.         SPIx->DR = dataOut[i];
  19.         //Czekanie na koniec transmisji
  20.         while((((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY)));
  21.         //Odczytanie rejestru z danymi
  22.         SPIx->DR;
  23.     }
  24. }

  1. void LIS302DL_WRITE_TO_SPI(uint8_t* data, uint8_t address, uint16_t licznik)
  2. {
  3.     //Rozpoczęcie transmisji od ustawienia pinu CS na LOW
  4.     LIS302DL_CS_LOW;
  5.  
  6.     if (licznik > 1)
  7.     {
  8.         address |= 0x40;
  9.     }
  10.     //Wyslanie adresu do ukladu
  11.     SPI_SendFunction(LIS302DL_SPI_PORT, address);
  12.  
  13.     //Wyslanie danych przez SPI do ukladu
  14.     SPI_SendFunction(LIS302DL_SPI_PORT, data);
  15.  
  16.     //Zakonczenie przesyłania danych, pin CS na HIGH
  17.     LIS302DL_CS_HIGH;
  18. }


Jeśli wykorzystuje się biblioteki HALA to wysyłanie paczki danych wygląda trochę inaczej:

  1. uint8_t SPISendFunction_FOR_HAL(SPI_TypeDef* SPIx, uint8_t data)
  2. {
  3.     //Sprawdzenie czy SPI jest wlaczone
  4.      if (!((SPIx)->CR1 & SPI_CR1_SPE)) {return;}
  5.    
  6.     //Wprowadzenie danych do bufora
  7.     SPIx->DR = data;
  8.    
  9.     //Odczekanie na zakonczenie transmisji
  10.     while((((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY)));
  11.    
  12.     //Zwrocenie danych do bufora
  13.     return SPIx->DR;
  14. }

Poniżej znajduje się funkcja odbierająca dane od bufora:

  1. void LIS302DL_READ_DATA_FROM_SPI(uint8_t* data, uint8_t address, uint8_t licznik)
  2. {
  3.     //CS na LOW
  4.     LIS302DL_CS_LOW;
  5.  
  6.     address |= 0x80;
  7.  
  8.     if (licznik > 1) { address |= 0x40; }
  9.  
  10.     //Wyslanie adresu do ukladu
  11.     SPI_SendFunction(LIS302DL_SPI_PORT, address);
  12.     //Odbior przeslanych danych
  13.     SPI_READ_MODE_DATA(LIS302DL_SPI_PORT, data, 0x00, licznik);
  14.  
  15.     //CS na HIGH
  16.     LIS302DL_CS_HIGH;
  17. }
  18.  
  19. void SPI_READ_MODE_DATA(SPI_TypeDef* SPIx, uint8_t* dataInput, uint8_t trash, uint32_t licznik)
  20. {
  21.     uint32_t i;
  22.  
  23.     //Sprawdzenie czy SPI jest wlaczone
  24.     if (!((SPIx)->CR1 & SPI_CR1_SPE)) {return;}
  25.  
  26.     while((((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY)));
  27.  
  28.     for (= 0; i < licznik; i++)
  29.     {
  30.         //Dane do bufora
  31.         SPIx->DR = trash;
  32.  
  33.         //Czekanie na wyslanie danych
  34.         while((((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY)));
  35.  
  36.         //Zapis danych do bufora
  37.         dataInput[i] = SPIx->DR;
  38.     }
  39. }

Odczyt identyfikatora układu. Dla czujnika LIS302DL powinno się uzyskać wartość 0x3B. Jeśli zostanie zwrócona wartość

  1. uint8_t LIS302DL_READ_ID()
  2. {
  3.     uint8_t ident;
  4.     //Odczytanie ID
  5.     LIS302DL_READ_DATA_FROM_SPI(&ident, LIS302DL_REG_ID, 1);
  6.     return ident;
  7. }

Kolejna funkcja pozwoli na ustawienie poszczególnych parametrów układu.

  1. void LIS302DL_INIT_DATA()
  2. {
  3.     uint16_t ctrl;
  4.  
  5.     //Restart czujnika
  6.     //Odczytanie danych 0x21h - CRTL_REG2
  7.     LIS302DL_READ_DATA_FROM_SPI((uint8_t *)&ctrl, LIS302DL_CTRL_REGISTER2_ADDR, 1);
  8.     //Wprowadzenie do rejestru 2 danych ustawiających wartość REEBOT z 0 na 1
  9.     ctrl |= LIS302DL_BOOT_REBOOTMEMORY;
  10.     LIS302DL_WRITE_TO_SPI((uint8_t *)&ctrl, LIS302DL_CTRL_REGISTER2_ADDR, 1);
  11.  
  12.     //Wlaczenie ustawien
  13.     ctrl = (uint16_t) (LIS302DL_DATARATE_100 | LIS302DL_LOWPOWERMODE_ACT | LIS302DL_SELFTEST_NORMAL | LIS302DL_XYZ_ENABLE);
  14.     ctrl |= (uint16_t) LIS302DL_FULLSCALE_9_2;
  15.     //Wprowadzenie ustawien do rejestru REG1 0x20
  16.     LIS302DL_WRITE_TO_SPI((uint8_t *)&ctrl, LIS302DL_CTRL_REGISTER1_ADDR, 1);
  17.  
  18.     //Odczytanie danych dotyczących filtra REG2 0x21
  19.     LIS302DL_WRITE_TO_SPI((uint8_t *)&ctrl, LIS302DL_CTRL_REGISTER2_ADDR, 1);
  20.  
  21.     //Zanegowanie aktualnych ustawień
  22.     ctrl &= (uint8_t) ~(LIS302DL_FILTEREDDATASELECTION_OUTPUTREGISTER | LIS302DL_HIGHPASSFILTER_LEVEL_3 |LIS302DL_HIGHPASSFILTERINTERRUPT_1_2);
  23.  
  24.     ctrl |= (uint8_t) (LIS302DL_HIGHPASSFILTERINTERRUPT_1_2 | LIS302DL_FILTEREDDATASELECTION_OUTPUTREGISTER);
  25.     ctrl |= (uint8_t) LIS302DL_HIGHPASSFILTER_LEVEL_3;
  26.     //Wprowadzenie ustawien
  27.     LIS302DL_WRITE_TO_SPI((uint8_t *)&ctrl, LIS302DL_CTRL_REGISTER2_ADDR, 1);
  28. }

Ostatnia funkcja służy do odczytania danych z poszczególnych osi.

  1. void LIS302DL_READ_XYZ(LIS302DL_t* LIS302DL_Axes)
  2. {
  3.     //Bufor przechowujący dane z osi
  4.     int8_t buffer[3];
  5.     int16_t SwitchXY;
  6.    
  7.     //Odczyt danych z rejestrów przechowujących dane
  8.     LIS302DL_READ_DATA_FROM_SPI((uint8_t*)&buffer[0], LIS302DL_OUT_X_ADDR, 1);
  9.     LIS302DL_READ_DATA_FROM_SPI((uint8_t*)&buffer[1], LIS302DL_OUT_Y_ADDR, 1);
  10.     LIS302DL_READ_DATA_FROM_SPI((uint8_t*)&buffer[2], LIS302DL_OUT_Z_ADDR, 1);
  11.  
  12.     //Przetworzenie wartości odpowiednio x, y, z
  13.     LIS302DL_Axes->= (int16_t) (buffer[0]) * LIS302DL_SENSITIVITY_9_2G;
  14.     LIS302DL_Axes->= (int16_t) (buffer[1]) * LIS302DL_SENSITIVITY_9_2G;
  15.     LIS302DL_Axes->= (int16_t) (buffer[2]) * LIS302DL_SENSITIVITY_9_2G;
  16.    
  17.     SwitchXY  = LIS302DL_Axes->X;
  18.     LIS302DL_Axes->= LIS302DL_Axes->Y;
  19.     LIS302DL_Axes->= -SwitchXY;
  20. }

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.