wtorek, 11 października 2016

[20] STM32F4 - Akcelerometr LIS3DSH

Ten post chciałbym poświęcić na opis programowania akcelerometru LIS3DSH, który został zamontowany na nowszych płytkach Discovery.

Komunikacja, tak samo jak dla LIS302DL, odbywa się za pomocą I2C bądź SPI. Ja wykorzystam ten drugi sposób.

Programowanie:


Poniżej przedstawiam zestaw wykorzystywanych rejestrów:

  1. #define LIS3DSH_ID                          0x3F
  2. #define LIS3DSH_WHO_I_AM                    0x0F
  3. #define LIS3DSH_CTRL_REG4                   0x20
  4. #define LIS3DSH_CTRL_REG1                   0x21
  5. #define LIS3DSH_CTRL_REG2                   0x22
  6. #define LIS3DSH_CTRL_REG3                   0x23
  7. #define LIS3DSH_CTRL_REG5                   0x24
  8. #define LIS3DSH_CTRL_REG6                   0x25
  9. #define LIS3DSH_OUT_X_L                     0x28
  10. #define LIS3DSH_OUT_X_H                     0x29
  11. #define LIS3DSH_OUT_Y_L                     0x2A
  12. #define LIS3DSH_OUT_Y_H                     0x2B
  13. #define LIS3DSH_OUT_Z_L                     0x2C
  14. #define LIS3DSH_OUT_Z_H                     0x2D
  15. #define LIS3DSH_SENSI_0_06G                 0.06
  16. #define LIS3DSH_SENSI_0_12G                 0.12
  17. #define LIS3DSH_SENSI_0_18G                 0.18
  18. #define LIS3DSH_SENSI_0_24G                 0.24
  19. #define LIS3DSH_SENSIY_0_73G                0.73
  20. #define LIS3DSH_DATARATE_100                ((uint8_t)0x60)
  21. #define LIS3DSH_FULLSCALE_2                 ((uint8_t)0x00)
  22. #define LIS3DSH_FULLSCALE_4                 ((uint8_t)0x08)
  23. #define LIS3DSH_FULLSCALE_6                 ((uint8_t)0x10)
  24. #define LIS3DSH_FULLSCALE_8                 ((uint8_t)0x18)
  25. #define LIS3DSH_FULLSCALE_16                ((uint8_t)0x20)
  26. #define LIS3DSH_FULLSCALE_SELECTION         ((uint8_t)0x38)
  27. #define LIS3DSH_FILTER_BW_800               ((uint8_t)0x00)
  28. #define LIS3DSH_FILTER_BW_400               ((uint8_t)0x40)
  29. #define LIS3DSH_FILTER_BW_200               ((uint8_t)0x80)
  30. #define LIS3DSH_FILTER_BW_50                ((uint8_t)(0x80 | 0x40))
  31. #define LIS3DSH_SELFTEST_NORMAL             ((uint8_t)0x00)
  32. #define LIS3DSH_XYZ_ENABLE                  ((uint8_t)0x07)
  33. #define LIS3DSH_SERINTER_4WIRE              ((uint8_t)0x00)
  34. #define LIS3DSH_SM_ENABLE                   ((uint8_t)0x01)
  35. #define LIS3DSH_SM_DISABLE                  ((uint8_t)0x00)


Poniżej część odpowiadająca za ustawienie sposobu transmisji oraz włączenia portów. Akcelerometr jest podłączony do SPI1, PA7 - MOSI, PA6 - MISO, PA5 SCK.

  1. void LIS3DSH_INT_SPI(void)
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStruct;
  4.     SPI_InitTypeDef SPI_InitStruct;
  5.    
  6.     RCC_AHB1PeriphClockCmd(LIS3DSH_CS_RCC, ENABLE); //Zegar dla CS
  7.     RCC_AHB1PeriphClockCmd(LIS3DSH_SPI_PIN_CLOCK, ENABLE); 
  8.     RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;             //Zegar dla SPI1
  9.     _DSB();
  10.     GPIO_InitStruct.GPIO_Pin = LIS3DSH_MISO | LIS3DSH_MOSI | LIS3DSH_SCK;
  11.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
  12.     GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
  13.     GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
  14.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_25MHz;
  15.     GPIO_Init(LIS3DSH_SPI_PORT, &GPIO_InitStruct);
  16.     SPI_StructInit(&SPI_InitStruct);
  17.     SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32;
  18.     SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  19.     SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
  20.     SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
  21.     SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
  22.    
  23.     SPIx->CR1 &= ~SPI_CR1_SPE;          //Wylaczenie SPI
  24.     SPI_Init(SPIx, &SPI_InitStruct);    //Inicjalizacja SPI
  25.     SPIx->CR1 |= SPI_CR1_SPE;           //Wlaczenie SPI
  26.    
  27.     GPIO_InitStruct.GPIO_Pin = LIS3DSH_CS_PIN;
  28.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
  29.     GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
  30.     GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
  31.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_25MHz;
  32.     GPIO_Init(LIS3DSH_CS_PORT, &GPIO_InitStruct);
  33.     LIS3DSH_CS_HIGH;
  34. }

Teraz czas na funkcje pozwalające na transmisję danych po interfejsie I2C:

  1. void LIS3DSH_WRITE_SPI(uint8_t* data, uint8_t addr, uint8_t count) {
  2.     LIS3DSH_CS_LOW;
  3.    
  4.     SPI_SEND(LIS3DSH_SPI, addr);    //Przeslanie adresu
  5.     /* Send data */
  6.     SPI_WRIIE_MULTI(LIS302DL_LIS3DSH_SPI, data, count); //Przeslanie danych
  7.    
  8.     LIS3DSH_CS_HIGH;
  9. }
  10. void LIS3DSH_READ_SPI(uint8_t* data, uint8_t addr, uint8_t count) {
  11.     LIS3DSH_CS_LOW;
  12.    
  13.     addr |= 0x80;
  14.    
  15.     SPI_SEND(LIS3DSH_SPI, addr);
  16.     SPI_READ_MULTI(LIS3DSH_SPI, data, 0x00, count);
  17.    
  18.     LIS3DSH_CS_HIGH;
  19. }
  20. uint8_t SPI_SEND(SPI_TypeDef* SPIx, uint8_t data) {
  21.     SPIx->DR = data;
  22.     while (((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY))
  23.     return SPIx->DR;
  24. }
  25. void SPI_SEND_MULTI(SPI_TypeDef* SPIx, uint8_t* dataOut, uint8_t* dataIn, uint16_t count) {
  26.     uint16_t count;
  27.     for (count = 0; count < count; count++) {
  28.         SPIx->DR = dataOut[i];
  29.         while (((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY))
  30.         dataIn[i] = SPIx->DR;
  31.     }
  32. }
  33. void SPI_READ_MULTI(SPI_TypeDef* SPIx, uint8_t* dataIn, uint8_t dummy, uint16_t count) {
  34.     uint8_t i;
  35.     for (= 0; i < count; i++) {
  36.         SPIx->DR = dummy;
  37.         while (((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY))
  38.         dataIn[i] = SPIx->DR;
  39.     }
  40. }

Funkcja ustawiająca i sprawdzająca czy parametry zostały odpowiednio ustawione:

  1. uint8_t LIS3DSH_CHECK_ON(void)
  2. {
  3.     uint8_t id_lis;
  4.     Delayms(10);
  5.     LIS3DSH_INIT_SPI();
  6.     LIS3DSH_INT_ReadSPI(&id_lis, LIS3DSH_WHO_I_AM, 1);
  7.     if (id_lis == LIS3DSH_ID) {
  8.         return id_lis;
  9.     }
  10.    
  11.     return 0;
  12. }

Poniższa funkcja pozwala na ustawienie parametrów takich jak czułość oraz wybranie filtru:

  1. uint8_t LIS3DSH_SET_PARAMETERS(uint8_t sensitivity, uint8_t filter) {
  2.     uint8_t tmpreg;
  3.     uint16_t temp;
  4.     float sensitivity_set;
  5.     LIS3DSH_INIT_SPI();
  6.     Delayms(10);
  7.    
  8.     temp = (uint16_t) (LIS3DSH_DATARATE_100 | LIS3DSH_XYZ_ENABLE);
  9.     temp |= (uint16_t) (LIS3DSH_SERIALINTERFACE_4WIRE | LIS3DSH_SELFTEST_NORMAL);  
  10.     if (sensitivity == LIS3DSH_Sensitivity_2G) {
  11.         temp |= (uint16_t) (LIS3DSH_FULLSCALE_2);
  12.         sensitivity_set = LIS3DSH_SENSITIVITY_0_06G;
  13.     }
  14.     else if (Sensitivity == TM_LIS3DSH_Sensitivity_4G) {
  15.         temp |= (uint16_t) (LIS3DSH_FULLSCALE_4);
  16.         sensitivity_set = LIS3DSH_SENSITIVITY_0_12G;
  17.     }
  18.     else if (Sensitivity == TM_LIS3DSH_Sensitivity_6G) {
  19.         temp |= (uint16_t) (LIS3DSH_FULLSCALE_6);
  20.         sensitivity_set = LIS3DSH_SENSITIVITY_0_18G;
  21.     }
  22.     else if (Sensitivity == TM_LIS3DSH_Sensitivity_8G) {
  23.         temp |= (uint16_t) (LIS3DSH_FULLSCALE_8);
  24.         sensitivity_set = LIS3DSH_SENSITIVITY_0_24G;
  25.     }
  26.     else if (Sensitivity == TM_LIS3DSH_Sensitivity_16G) {
  27.         temp |= (uint16_t) (LIS3DSH_FULLSCALE_16);
  28.         sensitivity_set = LIS3DSH_SENSITIVITY_0_73G;
  29.     }
  30.     else { return 1; }
  31.    
  32.     if (Filter == LIS3DSH_Filter_800Hz) { temp |= (uint16_t)(LIS3DSH_FILTER_BW_800 << 8); }
  33.     else if (Filter == LIS3DSH_Filter_400Hz) { temp |= (uint16_t)(LIS3DSH_FILTER_BW_400 << 8); }
  34.     else if (Filter == LIS3DSH_Filter_200Hz) { temp |= (uint16_t)(LIS3DSH_FILTER_BW_200 << 8); }
  35.     else if (Filter == LIS3DSH_Filter_50Hz) { temp |= (uint16_t)(LIS3DSH_FILTER_BW_50 << 8); }
  36.     else { return 2; }
  37.    
  38.     tmpreg = (uint8_t)(temp);
  39.     LIS3DSH_WRITE_SPI(&tmpreg, LIS3DSH_CTRL_REG4, 1);
  40.     tmpreg = (uint8_t)(temp>>8);
  41.     LIS3DSH_WRITE_SPI(&tmpreg, LIS3DSH_CTRL_REG5, 1);
  42.     return 0;
  43. }

W tej funkcji sprawdzane są parametry podane z tymi zdeklarowanymi w define. Gdy odpowiedni zostanie znaleziony to zostaje on wprowadzony do rejestru,

Ostatnia z funkcji pozwoli na odczytanie wartości z poszczególnych osi:

  1. typedef struct {
  2.     int16_t X;
  3.     int16_t Y;
  4.     int16_t Z;
  5. } DATA_FROM_AXES_LIS3DSH_t;
  6. void LIS3DSH_READ_DATA(TM_LIS302DL_LIS3DSH_t *DAT_FROM_AXCES_STRUCT) {
  7.     int8_t buffer[6];
  8.     LIS3DSH_READ_SPI((uint8_t*)&buffer[0], LIS3DSH_OUT_X_L, 1);
  9.     LIS3DSH_READ_SPI((uint8_t*)&buffer[1], LIS3DSH_OUT_X_H, 1);
  10.     LIS3DSH_READ_SPI((uint8_t*)&buffer[2], LIS3DSH_OUT_Y_L, 1);
  11.     LIS3DSH_READ_PI((uint8_t*)&buffer[3], LIS3DSH_OUT_Y_H, 1);
  12.     LIS3DSH_READ_SPI((uint8_t*)&buffer[4], LIS3DSH_OUT_Z_L, 1);
  13.     LIS3DSH_READ_SPI((uint8_t*)&buffer[5], LIS3DSH_OUT_Z_H, 1);
  14.    
  15.     DAT_FROM_AXCES_STRUCT->= (int16_t)((buffer[1] << 8) + buffer[0]) * set_sensitivity;
  16.     DAT_FROM_AXCES_STRUCT->= (int16_t)((buffer[3] << 8) + buffer[2]) * set_sensitivity;
  17.     DAT_FROM_AXCES_STRUCT->= (int16_t)((buffer[5] << 8) + buffer[4]) * set_sensitivity;
  18. }

Dane są wprowadzane do struktury i odczytywane z odpowiednich rejestrów, jeden dla bitów niższych oraz jeden dla bitów wyższych. Są one następnie wprowadzane do bufora i odpowiednio przekształcane do danych w strukturze.

Funkcje są wywoływane w następującej kolejności, na samym początku inicjalizacja wszystkich potrzebnych pinów oraz SPI potem wprowadzane są dane dotyczące filtru oraz czułości. Później zostaje już tylko odczytywanie danych z układu i przetworzenie ich zgodnie ze swoimi wymaganiami. Dane są zapisane bezpośrednio w strukturze.