LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

[PROJEKT] Rejestrator sygnałów analogowych na STM32 – jednokanałowy oscyloskop USB

Pierwszym plikiem, który zostanie zmodyfikowany będzie platform_config.h (tutaj i w kolejnych plikach w celach przejrzystości projektu zostaną usunięte fragmenty kodów, które dotyczą innych platform lub nie są wykorzystywane jak np. funkcja USART). Powinien on zawierać następujące elementy:

/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#if defined (USE_STM3210B_EVAL)
  #include "stm3210b_eval.h"
#endif /* USE_STM3210B_EVAL */

/* Exported constants --------------------------------------------------------*/
#if !defined (USE_STM3210B_EVAL)
 #define USE_STM3210B_EVAL
#endif

/* Define the STM32F10x hardware depending on the used evaluation board */
#ifdef USE_STM3210B_EVAL
#define USB_DISCONNECT                      GPIOA
 #define USB_DISCONNECT_PIN                  GPIO_Pin_0
 #define RCC_APB2Periph_GPIO_DISCONNECT      RCC_APB2Periph_GPIOA
#endif /* USE_STM3210B_EVAL */

Ostatnie 3 wiersze z dyrektywą #define opisują wyprowadzenie PA0, które będzie służyło do sterowania włączeniem/wyłączeniem urządzenia z szyny USB. W pliku hw_config.c znajduje się funkcja USB_Interrupts_Config(), w której jest wybierana grupa priorytetów i konfigurowane samo  przerwania od modułu USB – należy to uwzględnić przy konfigurowaniu przerwań aplikacji użytkowej:

void USB_Interrupts_Config(void)
{
NVIC_InitTypeDefNVIC_InitStructure;

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

Dalej ważnym będzie określenie jakie endpointy urządzenia USB są dostępne w projekcie i w razie potrzeby ich zmodyfikowanie i/lub dodanie nowych (można to zrobić czytając sam kod projektu lub wykorzystać program testlibusb-win dostępny w pobranej bibliotece libusb-win32 do obsługi portu USB z poziomu PC. Gotowy projekt wirtualnego portu COM umożliwia komunikację z wykorzystaniem jednego z 4 endpointów:

  • Endpoint 0 IN oraz OUT jest wykorzystywany w procesie enumeracji, gdy urządzenie jest podłączane do komputera i konfigurowane,
  • Endpoint 1 IN, który jest przypisany do interfejsu 1 i który posiada adres 0x81, służy do wysyłania do komputera 64-bajtowych paczek (transmisja typu bulk),
  • Endpoint 2 IN, przypisany do interfejsu 0 pod adresem 0x82 z maksymalny rozmiarem wysyłanych do komputera paczek 8-bajtów (transmisja typu interrupt),
  • Endpoint 3 OUT, podobnie jak EP1 jest przypisany do interfejsu 1, ale posiada adres 0x03 i służy do odbierania z komputera 64-bajtowych paczek (transmisja typu bulk).

Do odbierania komend z komputera zostanie wykorzystany EP3_OUT, a do wysyłania danych do komputera będzie otwarty i skonfigurowany EP3_IN w celu przedstawienia sposobu w jaki można to zrobić. Pozostałe włączone już endpointynie będą wykorzystywane i mogą nie działać poprawnie przy próbie dostępu do nich z aplikacji PC.

W pliku usb_conf.h znajduje się poniższy fragment kodu:

/* EP0  */
/* rx/tx buffer base address */
#define ENDP0_RXADDR        (0x40)
#define ENDP0_TXADDR        (0x80)

/* EP1  */
/* tx buffer base address */
#define ENDP1_TXADDR        (0xC0)
#define ENDP2_TXADDR        (0x100)
#define ENDP3_RXADDR        (0x110)

Dla otwieranego EP3_IN należy jeszcze dodać następujący wiersz:

#define ENDP3_TXADDR        (0x150)

Powyższy adres wynika z tego, że EP3_OUT ma rozmiar 64 bajtów, a więc kończy się na adresie 0x150. Dalej w pliku znajduje się fragment kodu, w którym należy zakomentować te definicje endpointów które będąotwarte, czyli:

/* CTR service routines */
/* associated to defined endpoints */
/*#define  EP1_IN_Callback   NOP_Process*/
#define  EP2_IN_Callback   NOP_Process
/*#define  EP3_IN_Callback   NOP_Process*/
#define  EP4_IN_Callback   NOP_Process
#define  EP5_IN_Callback   NOP_Process
#define  EP6_IN_Callback   NOP_Process
#define  EP7_IN_Callback   NOP_Process

#define  EP1_OUT_Callback   NOP_Process
#define  EP2_OUT_Callback   NOP_Process
/*#define  EP3_OUT_Callback   NOP_Process*/
#define  EP4_OUT_Callback   NOP_Process
#define  EP5_OUT_Callback   NOP_Process
#define  EP6_OUT_Callback   NOP_Process
#define  EP7_OUT_Callback   NOP_Process

Teraz już można wykonać właściwe otwarcie EP3_IN dodając w funkcji Virtual_Com_Port_Reset() w pliku usb_prop.c następujący fragment:

/* Initialize Endpoint 3 */
SetEPType(ENDP3, EP_BULK);        // EP z transmisjatypu Bulk
SetEPTxAddr(ENDP3, ENDP3_TXADDR); // Ustawienieadresu EP3_IN
SetEPTxCount(ENDP3, 64);          // Ustawienierozmiaru EP3_IN
SetEPTxStatus(ENDP3, EP_TX_VALID);// Uaktywnienie EP3_IN

oraz dodać wpis do deskryptora konfiguracji w tablicy Virtual_Com_Port_ConfigDescriptor[] pliku usb_desc.c zaraz za deskryptorem EP3_OUT:

/*Endpoint 3 Descriptor*/
0x07,   /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE,   /* bDescriptorType: Endpoint */
0x83,   /* bEndpointAddress: (IN3) */
0x02,   /* bmAttributes: Bulk */
0x40,   /* wMaxPacketSize: */
0x00,
0x00,   /* bInterval: ignore for Bulk transfer */

Również należy zmienić rozmiar tej tablicy w pliku usb_desc.h dodając 7 bajtów:

#define VIRTUAL_COM_PORT_SIZ_CONFIG_DESC        67+7

Ostatnią czynnością przy konfiguracji komunikacji po USB i zahaczającą o kolejny etap projektu jest dodanie/modyfikacja callback’ów w pliku usb_endp.c. Należy tutaj wiedzieć, że wywołanie tychże callback’ów następuje po transmisji, czyli po zapisaniu/odczytaniu do/z endpointa danych. W obu przypadkach na końcu każdej takiej funkcji są umieszczane wywołania procedur zapisu kolejnych danych do wysłania, ustawieniu ich rozmiaru oraz aktywowanie gotowości endpointa do pracy. Callback od EP3_OUT wygląda następująco:

void EP3_OUT_Callback(void)
{
  unsigned char i, k;
  uint8_t last_cmd = 0;

/* Pobierz odebrane z komputera dane i zapisz do bufora RX */
USB_SIL_Read(EP3_OUT, USB_Rx_Buffer);

/* Pobierz kod komendy */
last_cmd = USB_Rx_Buffer[0];

  /* Komenda testowa do migania dioda LED2 podlaczonej do wyprowadzenia PB2 */
if(last_cmd == 0x01){
GPIO_WriteBit(GPIOB, GPIO_Pin_2, (BitAction)(1 - 
GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_2)));
  }
  /* Komendapobraniapaczek-sampli */
  else if(last_cmd == GET_SAMPLES_CMD){
/* Jezeli wszystkie pomiary sa wykonane przygotuj pierwsza paczke */
if(ready_to_send == 1){
      for(i = 0, k = 0; i < 64; ++k){
USB_Tx_Buffer[i] = ((samples_buffer[k] & 0xFF00) >> 8);
        ++i;
USB_Tx_Buffer[i] = ((samples_buffer[k] & 0x00FF));
++i;
      }
    }
    /* W przeciwnym wypadku przygotuj pusta paczke */
else{
      for(i = 0; i < 64; ++i){
USB_Tx_Buffer[i] = 0x00;
}
    }
    /* Ustaw licznik paczek oczekujacych na wyslanie */
remainPackets = 9;

    /* Zapisz dane do EP3 */
USB_SIL_Write(EP3_IN, USB_Tx_Buffer, 64);

/* Ustaw liczbebajtow do wyslania */
SetEPTxCount(ENDP3, 64);
    /* Aktywuj wyslanie danych do EP3 */
SetEPTxValid(ENDP3);
  }
  /* Komenda zmiany podstawy czasu, czestotliwosciprobkowania oraz dokladnosci przetwornika */
else if(last_cmd == SET_SAMPLE_TIME_CMD){
    unsigned char Tsamp_nr = USB_Rx_Buffer[1];

/* Jezeliwartosc jest z zakresu 0-4 */
if(Tsamp_nr< 5){
      /* Ustaw czas probkowania */
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, timesamp[Tsamp_nr]);
/* Ustaw okres probkowania */
      CC1 = Tsamp[Tsamp_nr];
    }
  }

  /* Aktywuj przyjmowanie danych przez EP3 */
SetEPRxValid(ENDP3);
}
Autor: Jan Szemiet