[PROJEKT] Rejestrator sygnałów analogowych na STM32 – jednokanałowy oscyloskop USB
Należy zauważyć, że wysyłanych jest 10 paczek po 64-bajty co wynika z rozmiaru bufora próbek, który jest 320-elementową tablicą typu unsignedshort, czyli ma długość 640 bajtów. Natomiast callback od EP3_IN ma podobną strukturę wysyłania kolejnych paczek:
void EP3_IN_Callback(void) { unsigned char i, k; /* Jezeli wszystkie pomiary sa wykonane przygotuj kolejna paczke */ if(ready_to_send == 1){ for(i = 0, k = 0; i < 64; ++k){ USB_Tx_Buffer[i] = ((samples_buffer[(10 - remainPackets)*32 + k] & 0xFF00) >> 8); ++i; USB_Tx_Buffer[i] = ((samples_buffer[(10 - remainPackets)*32 + k] & 0x00FF)); ++i; } } /* W przeciwnym wypadku przygotuj pusta paczke */ else{ for(i = 0; i < 64; ++i){ USB_Tx_Buffer[i] = 0xFF; } } /* Jezeli wszystkie paczki zostalywyslane */ if((--remainPackets) == 0){ ready_to_send = 0; /* Wlaczenietimera 1 */ TIM_Cmd(TIM1, ENABLE); } /* 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); }
Po podłączeniu urządzenia-rejestratora do portu USB komputera należy włączyć aplikację libusb-win32 Inf-Wizard, w której powinna zostać wyświetlona informacja o nowym urządzeniu USB z portem wirtualnym COM. Dalej wystarczy postępować zgodnie z zaleceniami w efekcie czego zostanie utworzony plik .inf urządzenia USB gotowy do zapisu w systemie operacyjnym.
Czasami podczas pracy w środowisku uVision wraz z urządzeniem mogą pojawić się problemy jak niemożliwość dostępu współpracującej aplikacji PC. Należy wtedy wyjść z trybu debuggingu w uVision, oraz wyłączyć i ponownie podłączyć urządzenie do portu USB.
/* ADC1 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); /* DMA1 */ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); /* GPIOs: A, B */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); /* Timer 1 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
Następnie należy poinformować układ NVIC przy jakich zdarzeniach powinny być generowane przerwania:
/* DMA1 kanal 1 */ NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); /* Timer 1 */ NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
Dalej następuje konfiguracja wyprowadzenia PA1 na które będzie podawany rejestrowany sygnał:
/* Konfiguracja wyprowadzenia PA1 jako wejscieanalogowe do ADC1 kanal 1 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);
Kolejno należy skonfigurować układ podstawy czasu timera 1 oraz jego kanał 1 i należy pamiętać o tym, że timer ten jest podłączony do magistrali APB2 która jest taktowana sygnałem 72 MHz. Wstępnie kanał 1 jest skonfigurowany do odliczania 62,5 us, czyli sygnał będzie próbkowany z częstotliwością 16 kHz.
/* Ustawienia ukladu podstawy czasu TIM1 */ TIM_TimeBaseStructure.TIM_Period = 65535; TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); /* Konfiguracjakanalu 1 */ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 4500; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM1, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Disable); /* Wlaczenie przerwania od kanalu 1 */ TIM_ITConfig(TIM1, TIM_IT_CC1, ENABLE);
Następnie jest konfigurowany i włączany kontroler DMA:
DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&samples_buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // Kierunek: zrodlem jest ADC DMA_InitStructure.DMA_BufferSize = SAMPLES_BUFFER_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // Dane bedaprzesylaneciagle DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); // Wlacz przerwanie od zapelnieniacalego bufora DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE); // Wlacz DMA DMA_Cmd(DMA1_Channel1, ENABLE);
Ostatnią czynnością jest skonfigurowanie kanału 1 przetwornika A/D:
// Czestotliwoscsygnalutaktujacegouklad ADC = 12 MHz (przy F_APB2 = 72 MHz) RCC_ADCCLKConfig(RCC_PCLK2_Div6); // Jeden przetwornik, pracujacyniezaleznie ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // Pomiar jednego kanalu, wylacz opcje skanowania ADC_InitStructure.ADC_ScanConvMode = DISABLE; // Wylaczpomiar w trybieciaglym ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // Wyzwalanie z kanalu 1 TIM1 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; // Dane wyrownane do prawej - znaczacychbedzie 12 mlodszychbitow ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // Jedenkanal ADC_InitStructure.ADC_NbrOfChannel = 1; // Inicjujprzetwornik ADC_Init(ADC1, &ADC_InitStructure); // Grupa regularna, czas probkowania 55,5 cykla ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5); // Zdarzenie updatebedziegenerowac zadania DMA ADC_DMACmd(ADC1, ENABLE); // Wlacz ADC1 ADC_Cmd(ADC1, ENABLE); // Resetuj rejestry kalibracyjne ADC_ResetCalibration(ADC1); // Czekaj, azskonczyresetowac while(ADC_GetResetCalibrationStatus(ADC1)); // Start kalibracji ADC1 ADC_StartCalibration(ADC1); // Czekaj na zakonczenie kalibracji ADC1 while(ADC_GetCalibrationStatus(ADC1));
Funkcja main() wygląda następująco:
int main(void){ Set_System(); Set_USBClock(); USB_Interrupts_Config(); USB_Init(); RCC_Config(); // Konfiguracja sygnalow zegarowych NVIC_Config(); // Konfiguracja NVIC GPIO_Config(); // Konfiguracja wyprowadzen TIM_Config(); // Konfiguracja timera TIM1 DMA_Config(); // Konfiguracja kontrolera DMA ADC_Config(); // Konfiguracja przetwornika A/D /* Wlaczenietimera 1 */ TIM_Cmd(TIM1, ENABLE); while(1); }
Organizacja pomiarów jest dość prosta, a najważniejszy kod tej części programu znajduje się w pliku stm32_it.c i wygląda następująco:
void DMA1_Channel1_IRQHandler(void){ /* Przerwanie od pelnegozapelnienia bufora? */ if(DMA_GetITStatus(DMA1_IT_TC1) != RESET){ DMA_ClearITPendingBit(DMA1_IT_TC1); /* Wylaczenietimera */ TIM_Cmd(TIM1, DISABLE); ready_to_send = 1; // Ustaw gotowosc paczek do wyslania } } void TIM1_CC_IRQHandler(void){ /* Przerwanie od kanalu 1? */ if(TIM_GetITStatus(TIM1, TIM_IT_CC1) != RESET){ TIM_ClearITPendingBit(TIM1, TIM_IT_CC1); ADC_ExternalTrigConvCmd(ADC1, ENABLE); // Inicjujkolejnypomiar TIM_SetCompare1(TIM1, TIM_GetCapture1(TIM1) + CC1); } }