[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); }