LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

[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);
}
}
Autor: Jan Szemiet