LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

Mikrokontroler STM32 jako konwerter USB na 2xRS232

Główny element konwertera to plik converter.c. W jego nagłówku znajdują się wszystkie potrzebne ustawienia – mapowanie elementów vcom na poszczególne porty USART, numery portów i pinów interfejsów USART itp.
Zainicjowanie biblioteki USB jest wykonywane przy starcie systemu. Nie zagłębiając się w nią zbyt mocno powiem, że używamy tu gotowego automatu do tworzenia konfiguracji dzięki czemu stworzenie deskryptora konfiguracji i potrzebnych struktur używanych przez bibliotekę USB jest bardzo proste. Zainicjowanie USB odbywa się za pomocą funkcji pokazanej na list. 2.

List. 2. Funkcja inicjująca interfejs USB w mikrokontrolerach STM32

void usbd_init(void) {
  USBD_device_descriptor *usbd_dev_desc;
  CCM_session_params ccm_session;

  // -----------------------------
  // Inicjalizacja konfiguracji
  // -----------------------------
  // otwarcie konfiguracji do automatycznego tworzenia
  // za pomocą biblioteki CCM
  
  CCM_open_config(USBD_STM32, &usbdc, &ccm_session);

  // dodanie wirtualnego portu szeregowego (vCOM)
  // (lub 2 vCOM-ów) do tworzonej przez nas konfiguracji
#if(USE_VCOM1)
  CDC_VCOM_add_item(&(vcom1_params.vcom), &ccm_session);
#endif
#if(USE_VCOM2)
  CDC_VCOM_add_item(&(vcom2_params.vcom), &ccm_session);
#endif

  // zamkniecie sesji CCM i zainstalowanie konfiguracji
  // - po wyjsciu z tej funkcji mamy gotowy deskryptor konfiguracji
  // w tablicy usbd_conv_descriptor oraz wypelniona i zainstalowana
  // strukture usbdc.
  CCM_close_and_install_config(usbd_conv_descriptor, &ccm_session);
 
  //----------------------------------------------------
  //Zmiana pol deskryptora urzadzenia (Devce Descriptor)
  //----------------------------------------------------
  usbd_dev_desc = USBD_get_dev_descriptor_pointer( 
                  & usbdc);
  // tu wypelniamy sobie parametry vendorID i productID
  // w tym projekcie ustawione przykladowe wartości tak jak w demo ST
  usbd_dev_desc->idVendor           = 0x0483;
  usbd_dev_desc->idProduct          = 0x5740;

  // jakiej wersji USB uzywamy
  usbd_dev_desc->bcdUSB            = 0x0110;

  // klasa urzadzenia od razu informuje,
  // ze jest to urzadzenie komunikacyjne
  usbd_dev_desc->bDeviceClass       = 0x02;

  // ------------------------------
  // Elementy opcjonalne - stringi
  // ------------------------------
  // zainstalowanie stringow
  USBDS_install(( uint8_t*) Manufacturer_String_Descriptor, 
                &( usbd_dev_desc-> iManufacturer), 
                & usbdc);
  USBDS_install(( uint8_t*) Product_String_Descriptor, 
                &( usbd_dev_desc-> iProduct), 
                &usbdc);
  
  // przygotowujemy SerialNumber_String_Descriptor
  SerialNumber_fill();
  USBDS_install(( uint8_t*) SerialNumber_String_Descriptor, 
                &( usbd_dev_desc-> iSerialNumber), 
                &usbdc);

  // na koniec – wlaczenie peryferium USB
  USBD_activate(&usbdc);
}

Następuje w niej utworzenie i zainstalowanie konfiguracji (wywołanie pierwszych 4 funkcji) a następnie dodatkowo zmiana niektórych pól deskryptora urządzenia i zainstalowanie stringów – ciągów znakowych unicode opisujących urządzenie.
Tak stworzona konfiguracja dla dwóch obiektów vcom ma 4 interfejsy: 2 interfejsy CCI (Communication Class Interface) pełniące funkcję notyfikacyjną i 2 interfejsy CDI (Communication Data Interface) służące do transmisji danych. Każdy interfejs typu CCI posiada jeden punkt końcowy (endpoint) typu interrupt (przerwaniowy) natomiast interfejs CDI posiada 2 punkty końcowe typu bulk (masowe) – jeden typu IN i jeden OUT służące do przesyłu danych Tx i Rx danego obiektu vcom. Z tego wynika, że na jeden wirtualny port szeregowy przypadają 3 punkty końcowe. Mikrokontrolery STM32 z wbudowanymi interfejsami USB posiadają ich 8, więc łatwo obliczyć, że można na tym procesorze zaimplementować max. 2 wirtualne COM-y. Zajmą one 7 punktów końcowych – 3 na 2 porty + zerowy punkt zawsze używany do transmisji kontrolnej.
Inicjalizacja portu USART następuje zaś – zgodnie z ideą klasy CDC – dopiero w czasie działania konwertera, gdy nadejdzie od hosta żądanie otwarcia portu. W tym momencie wywoływana jest przez bibliotekę CDC odpowiednia funkcja podpięta do obiektu vcom. Następuje w niej sparsowanie przesłanych przez hosta parametrów pracy portu USART. W tej aplikacji zaimplementowałem ustawianie takich parametrów jak: prędkość transmisji, liczba bitów stopu i typ parzystości. Następnie wywoływana jest napisana do tego celu prosta funkcja inicjująca port USART, przedstawiona na list. 3.

List. 3. Funkcja inicjująca port USART

static void configure_usart( uint8_t usart_num, 
uint32_t baudrate, uint16_t stop_bits, 
uint16_t patity, uint8_t enable_irq) {
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    USART_TypeDef* USARTx;
    GPIO_TypeDef* GPIO_Rx_Port;
    GPIO_TypeDef* GPIO_Tx_Port;
    uint16_t GPIO_Rx_Pin;
    uint16_t GPIO_Tx_Pin;
    uint8_t NVIC_IRQChannel;


    // identify USART number, reset the peripheral and enable its clock
    {
        if(usart_num == 1) {
          USARTx = USART1;
          GPIO_Rx_Port = USART1_RX_PORT;
          GPIO_Tx_Port = USART1_TX_PORT;
          GPIO_Rx_Pin = USART1_RX_PIN;
          GPIO_Tx_Pin = USART1_TX_PIN;
          NVIC_IRQChannel = USART1_IRQn;

          // reset peripheral and enable its clock
          RCC_APB2PeriphResetCmd( RCC_APB2Periph_USART1, ENABLE);
          RCC_APB2PeriphResetCmd( RCC_APB2Periph_USART1, DISABLE);
          RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1, ENABLE);
        }
        else if(usart_num == 2) {
            USARTx = USART2;
          GPIO_Rx_Port = USART2_RX_PORT;
          GPIO_Tx_Port = USART2_TX_PORT;
          GPIO_Rx_Pin = USART2_RX_PIN;
          GPIO_Tx_Pin = USART2_TX_PIN;
          NVIC_IRQChannel = USART2_IRQn;

          // reset peripheral and enable its clock
          RCC_APB1PeriphResetCmd( RCC_APB1Periph_USART2, ENABLE);
          RCC_APB1PeriphResetCmd( RCC_APB1Periph_USART2, DISABLE);
          RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE);
        }
        else if(usart_num == 3) {
          USARTx = USART3;
          GPIO_Rx_Port = USART3_RX_PORT;
          GPIO_Tx_Port = USART3_TX_PORT;
          GPIO_Rx_Pin = USART3_RX_PIN;
          GPIO_Tx_Pin = USART3_TX_PIN;
          NVIC_IRQChannel = USART3_IRQn;

          // reset peripheral and enable its clock
          RCC_APB1PeriphResetCmd( RCC_APB1Periph_USART3, ENABLE);
          RCC_APB1PeriphResetCmd( RCC_APB1Periph_USART3, DISABLE);
          RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART3, ENABLE);
        }
        else {
            return;
        }
    }


    // configure GPIO
    {
     GPIO_InitStructure.GPIO_Pin = GPIO_Tx_Pin;
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
     GPIO_Init( GPIO_Tx_Port, &GPIO_InitStructure);

     GPIO_InitStructure.GPIO_Pin = GPIO_Rx_Pin;
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
     GPIO_Init( GPIO_Rx_Port, &GPIO_InitStructure);
    }


    // configure USART peripheral
    // and enable it
    {
     USART_InitStructure.USART_BaudRate = baudrate;
     USART_InitStructure.USART_WordLength = USART_WordLength_8b;
     USART_InitStructure.USART_StopBits = stop_bits;
     USART_InitStructure.USART_Parity = patity ;
     USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
     USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

     // Initialize registers
     USART_Init(USARTx, &USART_InitStructure);
    
           // Enable the USART
       USART_Cmd(USARTx, ENABLE);
    }


// Enable the USART Interrupt
if(enable_irq)
 {
 NVIC_InitStructure.NVIC_IRQChannel = NVIC_IRQChannel;
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 NVIC_Init(&NVIC_InitStructure);

 // Configure the transmit and receive ISR
 USART_ITConfig(USARTx, USART_IT_TXE, ENABLE);
 USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE);
 }
}

Funkcję tę podzielono na sekcje ilustrujące co należy zrobić, aby uruchomić port USART w procesorze STM32.
Została ona stworzona, gdyż mimo że standardowe biblioteki producenta napisane są w sposób bardzo przemyślany, to skonfigurowanie USART-a zajmuje kilka linii i wymaga dokonania ustawień w kontrolerach różnych peryferiów.
W pierwszej sekcji następuje zidentyfikowanie numeru portu, który inicjujemy – 1, 2 lub 3. W tym samym miejscu uzupełniamy zmienne funkcji oraz – co ważniejsze – następuje tu zerowanie USART-a i zainicjowanie taktującego go zegara w kontrolerze RCC. Jest to bardzo ważna czynność, gdyż w procesorze STM32 każde peryferiom ma wyłączany zegar i po włączeniu procesora wszystkie interfejsy są wyłączone.
W drugiej sekcji następuje skonfigurowanie linii GPIO. Aby USART mógł komunikować się ze światem zewnętrznym należy skonfigurować odpowiednio linie GPIO, gdyż domyślnie po zerowaniu procesora wszystkie są skonfiurowane jako uniwersalne linie wejściowe. Należy je ustawić w tryb: GPIO_Mode_AF_PP dla linii Tx i GPIO_Mode_IN_FLOATING dla Rx.
W trzeciej sekcji następuje skonfigurowanie portu USART i włączenie go w jego własnym kontrolerze. Dokonuje się tego przy pomocy funkcji bibliotecznych. Wypełniamy odpowiednią strukturę z parametrami pracy interfejsu i wywołujemy funkcję inicjującą. Następnie wydajemy polecenie włączenia portu.
W ostatniej sekcji – jeśli nam jest to potrzebne (a w tej aplikacji jest potrzebne) – następuje skonfigurowanie i włączenie przerwania portu USART w kontrolerze NVIC a następnie włączenie przerwania w jego własnym kontrolerze. Konfigurujemy priorytety danego kanału po czym włączamy generowanie danego przerwania przy nadawaniu i odbieraniu.

Tagi: GPIO, Linux, STM32, USB