wtorek, 1 maja 2018

[23] STM32F7 - Bluetooth LE RN48

Ten post chciałbym poświęcić na opisanie biblioteki odpowiedzialnej za komunikację z układem BLE RN4870/RN4871.

[Źródło: http://www.st.com/en/evaluation-tools/32f746gdiscovery.html]

Opis układu:


Układ RN4870 oraz RN4871 są to moduły Bluetooth Low Energy w standardzie 4.2. Komunikacja z układami odbywa się za pomocą komend ASCII przez interfejs UART.

Posiadają one wbudowany stos bluetooth wraz z silnikiem skryptowym. Z tego powodu mogą one pracować jako niezależne urządzenia.

W porównaniu z poprzednią generacją posiadają 2,5 raza szybszą przepustowość danych.

Znalezione obrazy dla zapytania bluetooth microchip rn4870 sklep
[Źródło: https://www.allaboutcircuits.com/news/microchip-produces-first-bluetooth-4.2-compliant-devices-rn4870-and-rn4871/]

Dzięki temu, że komunikacja jest przez interfejs UART bez problemów można podłączyć moduł do konwertera, a następnie bezpośrednio do komputera. Pozwala to na prostszy sposób ustawiania modułu.

Dokumentację można pobrać pod tym linkiem.
Wykaz komend wraz z dokładnym objaśnieniem można znaleźć pod tym linkiem.

Podłączenie układu:


Przykładowe podłączenie można znaleźć w dokumentacji układu.

Podłączenie dla RN4870:


Podłączenie RN4871:


Przykładowa aplikacja bez mikrokontrolera:


Jak wspomniałem wcześniej układu można używać bez mikrokontrolera. Jeden z prostych przykładów wygląda następująco:

$$$ - Uruchomienie trybu komend
+ - włączenie opcji ECHO
SS,C0 - Uruchomienie usługi UART Transparent 
R,1 - ponowne uruchomienie układu do załadowania danych

Po wprowadzeniu komend można użyć aplikacji Microchip Bluetooth Data do przesyłania danych z telefonu do modułu bluetooth. Po ich przesłaniu dane odczytane zostaną przesłane przez UART.



Biblioteka:


Poniżej niektóre funkcje odpowiedzialne za obsługę układu:

Definicja pinów i prędkości dla UART:

  1. #define MICROCH_RN48_UART_COM       USART6
  2. #define MICROCH_RN48_UART_BAUD      115200
  3. #define MICROCH_RN48_UART_TX_PIN    USART6_TX1
  4. #define MICROCH_RN48_UART_TX_PORT   USART6_GPIOC
  5. #define MICROCH_RN48_UART_RX_PIN    USART6_RX1
  6. #define MICROCH_RN48_UART_RX_PORT   USART6_GPIOC

Na samym początku funkcja uruchamiająca UART:

  1. static void usart_rn48_Init(void)
  2. {
  3.     GPIO_InitTypeDef gpio_init_structure;
  4.     PORT_CLOCK_ENABLE(MICROCH_RN48_UART_TX_PORT);
  5.     PORT_CLOCK_ENABLE(MICROCH_RN48_UART_RX_PORT);
  6.     usart_Initialize_Clock(MICROCH_RN48_UART_COM);
  7.     gpio_init_structure.Pin = MICROCH_RN48_UART_TX_PIN;
  8.     gpio_init_structure.Mode = GPIO_MODE_AF_PP;
  9.     gpio_init_structure.Speed = GPIO_SPEED_FAST;
  10.     gpio_init_structure.Pull = GPIO_PULLUP;
  11.     gpio_init_structure.Alternate = GPIO_AF8_USART6;
  12.     HAL_GPIO_Init(MICROCH_RN48_UART_TX_PORT, &gpio_init_structure);
  13.     /* GPIO RX */
  14.     gpio_init_structure.Pin = MICROCH_RN48_UART_RX_PIN;
  15.     gpio_init_structure.Mode = GPIO_MODE_AF_PP;
  16.     gpio_init_structure.Alternate = GPIO_AF8_USART6;
  17.     HAL_GPIO_Init(MICROCH_RN48_UART_RX_PORT, &gpio_init_structure);
  18.     UART_Handle6.Instance                       = USART6;
  19.     UART_Handle6.Init.BaudRate                  = MICROCH_RN48_UART_BAUD;
  20.     UART_Handle6.Init.WordLength                = UART_WORDLENGTH_8B;
  21.     UART_Handle6.Init.StopBits                  = UART_STOPBITS_1;
  22.     UART_Handle6.Init.Parity                    = UART_PARITY_NONE;
  23.     UART_Handle6.Init.HwFlowCtl                 = UART_HWCONTROL_NONE;
  24.     UART_Handle6.Init.Mode                      = UART_MODE_TX_RX;
  25.     UART_Handle6.Init.OverSampling              = UART_OVERSAMPLING_8;
  26.     UART_Handle6.Init.OneBitSampling            = UART_ONEBIT_SAMPLING_DISABLED;
  27.     UART_Handle6.AdvancedInit.AdvFeatureInit    = UART_ADVFEATURE_NO_INIT;
  28.     HAL_UART_Init(&UART_Handle6);
  29.     HAL_NVIC_DisableIRQ(USART6_IRQn);
  30.     HAL_NVIC_SetPriority(USART6_IRQn, 0, 1);
  31.     HAL_NVIC_EnableIRQ(USART6_IRQn);
  32.     HAL_NVIC_ClearPendingIRQ(USART6_IRQn);
  33.     __HAL_UART_ENABLE_IT(&UART_Handle6, UART_IT_RXNE);
  34.     USART6->CR1 |= USART_CR1_RXNEIE;
  35. }

Uruchamiany jest UART6 z przerwaniami od nadawania.

Przesyłanie danych wygląda następująco:

  1. static void rn48_SendStringUart(char* sendPointer, MIRCOCH_RN48_END_DATA_TypeDef lastByte)
  2. {
  3.     while (*sendPointer != 0)
  4.     {
  5.         rn48_SendByteUart(*sendPointer);
  6.         sendPointer++;
  7.     }
  8.     if(lastByte==RN48_LFCR) {
  9.         rn48_SendByteUart(0x0A);
  10.         rn48_SendByteUart(0x0D);
  11.     }
  12.     else if(lastByte==RN48_CRLF) {
  13.         rn48_SendByteUart(0x0D);
  14.         rn48_SendByteUart(0x0A);
  15.     }
  16.     else if(lastByte==RN48_LF) {
  17.         rn48_SendByteUart(0x0A);
  18.     }
  19.     else if(lastByte==RN48_CR) {
  20.         rn48_SendByteUart(0x0D);
  21.     }
  22. }
  23. static void rn48_SendByteUart(char valueToSend)
  24. {
  25.      usart_PutChar(MICROCH_RN48_UART_COM, valueToSend);
  26. }

Poniżej przedstawię wykaz kilku komend jakie zostały zawarte w programie. Są one pisane podobnie, więc nie zawieram tutaj wszystkich funkcji. Całość można pobrać z dysku Google. Link na dole strony.

Teraz główna funkcja uruchamiająca układ w trybie komend:

  1. static MICROCH_RN48_ERRORStatus_TypeDef rn48_CmdMode(void)
  2. {
  3.     uint32_t maxTimeToWait = 0;
  4.     rn48_clearBuffer();
  5.     for(maxTimeToWait=0;maxTimeToWait<MICROCHIP_RN48_DELAY_TIME;maxTimeToWait++) {}
  6.     RN48_SendString("$$$", RN48_CRLF);
  7.     maxTimeToWait=0;
  8.     do
  9.     {
  10.         maxTimeToWait++;
  11.         if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer, "CMD>", 4) != MICROCH_OP_ERROR)
  12.         {
  13.             return MICROCH_OP_OK;
  14.         }
  15.     }while((maxTimeToWait<MICROCHIP_RN48_RX_TIMEOUT));
  16.     return MICROCH_OP_ERROR;
  17. }

Uruchomienie trybu danych, czyli normalnej pracy:

  1. static MICROCH_RN48_ERRORStatus_TypeDef rn48_DataMode(void)
  2. {
  3.     uint32_t maxTimeToWait = 0;
  4.     rn48_clearBuffer();
  5.     for(maxTimeToWait=0;maxTimeToWait<MICROCHIP_RN48_DELAY_TIME;maxTimeToWait++) {}
  6.     RN48_SendString("---", RN48_CRLF);
  7.     maxTimeToWait=0;
  8.     do
  9.     {
  10.         maxTimeToWait++;
  11.         if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer, "END", 3) != MICROCH_OP_ERROR)
  12.         {
  13.             return MICROCH_OP_OK;
  14.         }
  15.     }while((maxTimeToWait<MICROCHIP_RN48_RX_TIMEOUT));
  16.     return MICROCH_OP_ERROR;
  17. }

Reset do ustawień fabrycznych:

  1. static MICROCH_RN48_ERRORStatus_TypeDef rn48_FactoryReset(void)
  2. {
  3.     uint32_t maxTimeToWait = 0;
  4.  rn48_clearBuffer();
  5.     for(maxTimeToWait=0;maxTimeToWait<MICROCHIP_RN48_DELAY_TIME;maxTimeToWait++) {}
  6.    
  7.     RN48_SendString("SF,1",RN48_CRLF);
  8.     maxTimeToWait=0;
  9.     do
  10.     {
  11.         maxTimeToWait++;
  12.         if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer, "ERR", 3) != MICROCH_OP_ERROR)
  13.         {
  14.             return MICROCH_OP_ERROR;
  15.         }
  16.         else if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer,"%REBOOT%",8)==0)
  17.         {
  18.             return MICROCH_OP_OK;
  19.         }
  20.     }while((maxTimeToWait<MICROCHIP_RN48_RX_TIMEOUT));
  21.     return MICROCH_OP_ERROR;
  22. }

Funkcja odczytu danych:

  1. static MICROCH_RN48_ERRORStatus_TypeDef rn48_DumpInformation(void)
  2. {
  3.     uint32_t maxTimeToWait = 0;
  4.  rn48_clearBuffer();
  5.     for(maxTimeToWait=0;maxTimeToWait<MICROCHIP_RN48_DELAY_TIME;maxTimeToWait++) {}
  6.     RN48_SendString("D",RN48_CRLF);
  7.     maxTimeToWait=0;
  8.     do
  9.     {
  10.         maxTimeToWait++;
  11.         if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer, "SERVICES=", 9) != MICROCH_OP_ERROR)
  12.         {
  13.             return MICROCH_OP_OK;
  14.         }
  15.     }while((maxTimeToWait<MICROCHIP_RN48_RX_TIMEOUT));
  16.     return MICROCH_OP_ERROR;
  17. }

Zwraca następujące informacje:


Reset układu. Ta komenda wykonywana jest w celu uruchomienia skonfigurowanych usług:

  1. static MICROCH_RN48_ERRORStatus_TypeDef rn48_Reboot(void)
  2. {
  3.     uint32_t maxTimeToWait = 0;
  4.     rn48_clearBuffer();
  5.    
  6.     for(maxTimeToWait=0;maxTimeToWait<MICROCHIP_RN48_DELAY_TIME;maxTimeToWait++) {}
  7.     RN48_SendString("R,1", RN48_CRLF);
  8.    
  9.     maxTimeToWait=0;
  10.     do
  11.     {
  12.         maxTimeToWait++;
  13.         if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer, "%REBOOT%",8) != MICROCH_OP_ERROR)
  14.         {
  15.             return MICROCH_OP_OK;
  16.         }
  17.     }while((maxTimeToWait<MICROCHIP_RN48_RX_TIMEOUT));
  18.     return MICROCH_OP_ERROR;
  19. }

Ustawienie nazwy układu:

  1. static MICROCH_RN48_ERRORStatus_TypeDef rn48_SetDeviceName(char *name, uint8_t nameSize)
  2. {
  3.     uint32_t maxTimeToWait = 0;
  4.     if(nameSize > 20)
  5.     {
  6.         return MICROCH_OP_ERROR;
  7.     }
  8.     rn48_clearBuffer();
  9.     for(maxTimeToWait=0;maxTimeToWait<MICROCHIP_RN48_DELAY_TIME;maxTimeToWait++) {}
  10.     memset(MICROCH_RN48_t.sendDataBuffer, 0x00, sizeof(MICROCH_RN48_t.sendDataBuffer));
  11.     strcpy(MICROCH_RN48_t.sendDataBuffer,"SN,");
  12.     strcat(MICROCH_RN48_t.sendDataBuffer, name);
  13.     RN48_SendString(MICROCH_RN48_t.sendDataBuffer, RN48_CRLF);
  14.     do
  15.     {
  16.         maxTimeToWait++;
  17.         if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer, "AOK",3) != MICROCH_OP_ERROR)
  18.         {
  19.             return MICROCH_OP_OK;
  20.         }
  21.     }while((maxTimeToWait<MICROCHIP_RN48_RX_TIMEOUT));
  22.     return MICROCH_OP_ERROR;
  23. }

Jako argumenty podawana jest nazwa oraz jej długość. Nazwa nie może przekraczać 20 znaków.

Ustawienie klucza publicznego:

  1. static MICROCH_RN48_ERRORStatus_TypeDef rn48_SetUUID(char *uuidKey, uint8_t uuidKeySize)
  2. {
  3.     uint32_t maxTimeToWait = 0;
  4.     if(uuidKeySize != 4 && uuidKeySize != 32)
  5.     {
  6.         return MICROCH_OP_ERROR;
  7.     }
  8.     rn48_clearBuffer();
  9.     for(maxTimeToWait=0;maxTimeToWait<MICROCHIP_RN48_DELAY_TIME;maxTimeToWait++) {}
  10.     memset(MICROCH_RN48_t.sendDataBuffer, 0x00, sizeof(MICROCH_RN48_t.sendDataBuffer));
  11.     strcpy(MICROCH_RN48_t.sendDataBuffer,"PS,");
  12.     strcat(MICROCH_RN48_t.sendDataBuffer, uuidKey);
  13.     rn48_SendStringUart(MICROCH_RN48_t.sendDataBuffer, RN48_CRLF);
  14.     maxTimeToWait=0;
  15.     do
  16.     {
  17.         maxTimeToWait++;
  18.         if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer, "AOK",3) != MICROCH_OP_ERROR)
  19.         {
  20.             return MICROCH_OP_OK;
  21.         }
  22.         else if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer, "ERR",3) != MICROCH_OP_ERROR)
  23.         {
  24.             return MICROCH_OP_ERROR;
  25.         }
  26.     }while((maxTimeToWait<MICROCHIP_RN48_RX_TIMEOUT));
  27.     return MICROCH_OP_ERROR;
  28. }

Wyczyszczenie wszystkich usług. Po jej wysłaniu wymagane jest podanie sygnału resetu.

  1. static MICROCH_RN48_ERRORStatus_TypeDef rn48_ClearsSettings(void)
  2. {
  3.     uint32_t maxTimeToWait = 0;
  4.     rn48_clearBuffer();
  5.    
  6.     for(maxTimeToWait=0;maxTimeToWait<MICROCHIP_RN48_DELAY_TIME;maxTimeToWait++) {}
  7.     RN48_SendString("PZ",RN48_CRLF);
  8.    
  9.     maxTimeToWait=0;
  10.     do
  11.     {
  12.         maxTimeToWait++;
  13.         if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer, "AOK",3) != MICROCH_OP_ERROR)
  14.         {
  15.             return MICROCH_OP_OK;
  16.         }
  17.         else if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer, "ERR",3) != MICROCH_OP_ERROR)
  18.         {
  19.             return MICROCH_OP_ERROR;
  20.         }
  21.     }while((maxTimeToWait<MICROCHIP_RN48_RX_TIMEOUT));
  22.     return MICROCH_OP_ERROR;
  23. }

Ustaw klucz prywatny:

  1. static MICROCH_RN48_ERRORStatus_TypeDef rn48_SetPrivateCharacteristic(char *uuidKey, char uuidKeySize,
  2.                                                         uint8_t propBitm, uint8_t maxDataSize)
  3. {
  4.     uint32_t maxTimeToWait = 0;
  5.     if(uuidKeySize != 4 && uuidKeySize != 32)
  6.     {
  7.         return MICROCH_OP_ERROR;
  8.     }
  9.     rn48_clearBuffer();
  10.     for(maxTimeToWait=0;maxTimeToWait<MICROCHIP_RN48_DELAY_TIME;maxTimeToWait++) {}
  11.     memset(MICROCH_RN48_t.sendDataBuffer, 0x00, sizeof(MICROCH_RN48_t.sendDataBuffer));
  12.     strcpy(MICROCH_RN48_t.sendDataBuffer,"PC,");
  13.     strcat(MICROCH_RN48_t.sendDataBuffer, uuidKey);
  14.     strcat(MICROCH_RN48_t.sendDataBuffer, ",");
  15.     strcat(MICROCH_RN48_t.sendDataBuffer, propBitm);
  16.     strcat(MICROCH_RN48_t.sendDataBuffer, ",");
  17.     strcat(MICROCH_RN48_t.sendDataBuffer, maxDataSize);
  18.     RN48_SendString(MICROCH_RN48_t.sendDataBuffer, RN48_CRLF);
  19.    
  20.     maxTimeToWait=0;
  21.     do
  22.     {
  23.         maxTimeToWait++;
  24.         if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer, "AOK",3) != MICROCH_OP_ERROR)
  25.         {
  26.             return MICROCH_OP_OK;
  27.         }
  28.         else if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer, "ERR",3) != MICROCH_OP_ERROR)
  29.         {
  30.             return MICROCH_OP_ERROR;
  31.         }
  32.     }while((maxTimeToWait<MICROCHIP_RN48_RX_TIMEOUT));
  33.     return MICROCH_OP_ERROR;
  34. }

Ustaw adres układu bluetooth:

  1. static MICROCH_RN48_ERRORStatus_TypeDef rn48_SetBltAdress(char *dataPointer, uint8_t dataSize)
  2. {
  3.     uint32_t maxTimeToWait = 0;
  4.     if(dataSize != 12)
  5.     {
  6.         return MICROCH_OP_ERROR;
  7.     }
  8.     rn48_clearBuffer();
  9.     for(maxTimeToWait=0;maxTimeToWait<MICROCHIP_RN48_DELAY_TIME;maxTimeToWait++) {}
  10.     memset(MICROCH_RN48_t.sendDataBuffer, 0x00, sizeof(MICROCH_RN48_t.sendDataBuffer));
  11.     strcpy(MICROCH_RN48_t.sendDataBuffer,"SR,");
  12.     strcat(MICROCH_RN48_t.sendDataBuffer, dataPointer);
  13.     RN48_SendString(MICROCH_RN48_t.sendDataBuffer, RN48_CRLF);
  14.     do
  15.     {
  16.         maxTimeToWait++;
  17.         if(rn48_searchForFrame(MICROCH_RN48_RX_t.rx_sendDataBuffer, "AOK",3) != MICROCH_OP_ERROR)
  18.         {
  19.             return MICROCH_OP_OK;
  20.         }
  21.     }while((maxTimeToWait<MICROCHIP_RN48_RX_TIMEOUT));
  22.     return MICROCH_OP_ERROR;
  23. }

W przedstawionych powyżej funkcjach czekam na odpowiedź w pętli od komendy z określonym timeoutem. Funkcja wyszukująca ramkę prezentuje się następująco:

  1. static MICROCH_RN48_ERRORStatus_TypeDef rn48_searchForFrame(char* buffer, char* frame, uint16_t lengthFrame)
  2. {
  3.     char* p = buffer;
  4.     for (int i=0; i<(MICROCHIP_RN48_RX_BUFFER_SIZE - lengthFrame); i++)
  5.     {
  6.         if(memcmp(p, frame, lengthFrame * sizeof(uint8_t)) == 0 )
  7.         {
  8.             return i;
  9.         }
  10.         p++;
  11.     }
  12.     return MICROCH_OP_ERROR;
  13. }

Ramkę można dekodować po odczytaniu znaku końca czyli 0x0D.

Pliki do projektu można pobrać z dysku Google pod tym linkiem.