poniedziałek, 26 września 2016

[6] STM32F4 - Discovery - CubeMx - USB

W tym poście chciałbym opisać sposób komunikacji z komputerem za pomocą USB. Opiszę tutaj sposób przygotowania układu do uzyskania komunikacji poprzez Virtual Serial Port.

Konfiguracja programu w środowisku CubeMx


W pierwszej kolejności należy odpowiednio skonfigurować zegary. Należy to zrobić w taki sposób aby USB nie było taktowane wyższym sygnałem niż 48 MHz. Można to ustawić w następujący sposób:

Rys. 1. Ustawienie zegarów


Oczywiście jako źródło sygnału wybrałem HSE.

Po ustawieniu taktowania przyszła kolej na włączenie USB FS. Pozwoli ona na wysyłanie oraz odbieranie danych poprzez port COM.

Następnie włączam przerwania od przycisku, na które dane będą wysyłane:

Ostatnim krokiem jest włączenie przerwań, oraz ustawienie sposobu komunikacji dla USB_DEVICE na Communication Device Class (Virtual Com Port). Obu tych operacji dokonuje się w części Configuration.

Instalacja sterowników


Na stronie ST udostępniony jest wersja sterownika portu virtualnego 1.4.0. U mnie on niechciał działać, port wirtualny nie był odpowiednio wykrywany. U mnie poprawnie zadziałała wersja 1.3.1. Po wykryciu zostanie tam dodane do odpowiedniego portu COM.

Programowanie


Poniżej przedstawiam w jaki sposób należy zmodyfikować plik main w projekcie. 

Na samym początku należy dołączyć bibliotekę usbd_cdc_if.h. Zawiera ona funkcje pozwalające min. na przesłanie danych, ustawienie bufora itp.

  1. /* USER CODE BEGIN Includes */
  2. #include "usbd_cdc_if.h"
  3. /* USER CODE END Includes */

Dalej w main() włączane są wszystkie peryferia:

  1.   /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  2.   HAL_Init(); //Włączenie wszystkich peryferiow
  3.   /* Configure the system clock */
  4.   SystemClock_Config(); //Konfiguracja zegara
  5.   /* Initialize all configured peripherals */
  6.   MX_GPIO_Init();       //Ustawienie portów GPIO
  7.   MX_USB_DEVICE_Init(); //Wlaczenie biblioteki do USB

W następnym kroku zostaje już wprowadzić dane do zdefiniowane wcześniej bufora oraz wysłać je za pomocą odpowiedniej funkcji. Dane do odpowiedniej postaci najłatwiej przetworzyć wykorzystując do tego funkcję sprintf. 

  1. HAL_Delay(1000); //Delay 1ms
  2. //Dane do bufora buffer, dlugosc wiadomosci do lenght_message_to_send
  3. lenght_message_to_send = sprintf(buffer, "Liczba %d, Float %f, Znak %c\n\r", 54, 98.05, 'w');
  4. CDC_Transmit_FS(buffer, lenght_message_to_send);

Ten sposób jest w porządku. Natomiast można nie zdążyć zaobserwować danych na komputerze. Wobec tego lepszym rozwiązaniem jest wykorzystanie przerwania, najwygodniej od przycisku zamontowanego na płytce, który jest podłączony do pinu PA0. Włączenie przerwania w CubeMX opisałem powyżej. Dzięki temu za każdym naciśnięciem przycisku można wyświetlić jakiś znak.

  1.   while (1)
  2.   {
  3.             if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET)
  4.             {
  5.                     HAL_Delay(100);
  6.                     if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET)
  7.                     {
  8.                         HAL_Delay(100);
  9.                         lenght_message_to_send = sprintf(buffer, "Liczba %d, Float %f, Znak %c\n\r", 54, 98.05, 'w');
  10.                         CDC_Transmit_FS(buffer, lenght_message_to_send);
  11.                     }  
  12.             }
  13.   /* USER CODE END WHILE */
  14.   /* USER CODE BEGIN 3 */
  15.   }

Ok to teraz w drugą stronę. Czyli odbieranie danych przez komputer z STM-a.

Należy zdeklarować odpowiedni buffor nadawczy oraz flagę przechowującą informacje o odebraniu danych z układu:

  1. uint8_t Received_Buffer[255];
  2. uint8_t Received_Flag = 0;

Teraz czas na instrukcje jakie należy dodać w pętli while:

  1. while (1)
  2. {
  3.     if(Received_Flag == 1){ Received_Flag = 0;
  4.         lenght_message_to_send = sprintf(buffer, "BUF: %s\n", Received_Buffer);
  5.         CDC_Transmit_FS(buffer, lenght_message_to_send);
  6.     }
  7. }

W pliku usbd_cdc_if.c należy dokonać małej korekty. Na samym początku trzeba zwiększyć wielkość buffora danych jakie może zostać odebrane i wysłane. Ta część programu może być dowolnie modyfikowana i zmieniania.

  1. /* USER CODE BEGIN PRIVATE_DEFINES */
  2. /* Define size for the receive and transmit buffer over CDC */
  3. /* It's up to user to redefine and/or remove those define */
  4. #define APP_RX_DATA_SIZE  255
  5. #define APP_TX_DATA_SIZE  255
  6. /* USER CODE END PRIVATE_DEFINES */

Następnym elementem jest modyfikacja kodu funkcji odbierającej dane:

  1. static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
  2. {
  3.   /* USER CODE BEGIN 6 */
  4.   USBD_CDC_SetRxBuffer(hUsbDevice_0, &Buf[0]);
  5.   USBD_CDC_ReceivePacket(hUsbDevice_0);
  6.    
  7.   uint8_t i;
  8.   //Odnosniki do zmiennych
  9.   extern uint8_t Received_buffer[255];
  10.   extern uint8_t Received_Flag;
  11.   for(iter = 0; iter<APP_RX_DATA_SIZE; ++i)
  12.   {  ReceivedData[i] = 0;  }
  13.   strlcpy(Received_buffer, Buf, (*Len) + 1);
  14.   Received_Flag = 1;
  15.    
  16.   return (USBD_OK);
  17.   /* USER CODE END 6 */
  18. }

Należy pamiętać o kilku elementach, pierwszym z nich jest podłączenie urządzenia do komputera. Nie można odrazu uruchamiać funkcji przesyłającej dane, należy dać układom chwile czasu aż, nastąpi poprawne połączenie. W przypadku za szybkiego przesyłania danych, jeszcze przed poprawnym uruchomieniem, stm prawdopodobnie nie zostanie wykryty. Wystarczy po funkcji MX_USB_DEVICE_Init() wsadzić Delay około 1 sekundy powinno w zupełności wystarczyć.

Drugi problem dotyczy tego, że czasem może nie udać się uruchomić terminala z aktywnym portem COM, mimo tego że urządzenie zostanie wykryte. W tym przypadku pomaga ponowne podłączenie płytki bądź sam jej reset oraz zamknięcie terminala