Sterownik PWM – #2 – Odczyt ADC

Na rozgrzewkę:

W poprzednim wpisie z tej serii wspomniałem o użyciu timera do wyzwalania pomiaru ADC. Teraz chcę Ci zaprezentować implementację tego rozwiązania oraz jego cel, więc do rzeczy.

Zakres wpisu:

  • Mierzone wartości ADC w projekcie
  • Synchronizacja PWM z pomiarem
  • Moment pomiaru
  • DMA (Direct Memory Access)

Mierzone wartości ADC w projekcie:

Pomiary ADC są wykorzystywane w projekcie do odczytu wartości prądu oraz napięcia na danym wyjściu. Odczyt prądu wykonywany jest metodą low-side, który wybrałem z powodu tańszej implementacji niż high-side. Pomiar low-side nie zabezpiecza przed zewnętrznym zwarciem wyjścia do masy. Dlatego niezbędne jest zastosowanie jakiegoś dodatkowego rozwiązania, aby zwarcie nie spowodowało spalenia urządzenia. Do rozwiązania wspomnianego problemu wybrałem pomiar napięcia każdego wyjścia względem masy. Jeśli wartość napięcia będzie niższa niż ustawiony poziom przy jednocześnie włączonym wyjściu to nastąpi detekcja zwarcia i wyjście zostanie wyłączone. Jak wspomniałem poprzednio w projekcie będzie około 30 wyjść, dlatego niezbędne będą multipleksery. 

Synchronizacja PWM z pomiarem:

PWM generowany w sposób programowy ma wiele ograniczeń, jednym z nich jest metoda synchronizacji pomiaru z przebiegiem PWM. W projekcie wykorzystałem wyzwalanie pomiaru ADC za pomocą timera. Do wyzwalanie ADC za pomocą timera służy funkcja TRGO. Podczas konfiguracji timera należy aktywować następującą funkcję: TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update). Konfiguracja timera:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseInitTypeDef tim2;
tim2.TIM_CounterMode = TIM_CounterMode_Up;
tim2.TIM_Prescaler = 10000;
tim2.TIM_Period = 41;
tim2.TIM_ClockDivision = TIM_CKD_DIV1;
tim2.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &tim2);
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
Poniżej zamieszczam część konfiguracji przetwornika ADC, do użycia wyzwalania pomiaru za pomocą timera:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
ADC_InitTypeDef adc;
ADC_DeInit();
adc.ADC_DataAlign = ADC_DataAlign_Right;
adc.ADC_Resolution = ADC_Resolution_12b;
adc.ADC_ContinuousConvMode = DISABLE;
adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
adc.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising
adc.ADC_NbrOfConversion = 4;
adc.ADC_ScanConvMode = ENABLE;
ADC_Init(ADC1, &adc);
Pomiar ADC można wyzwalać używając także innych timerów, poniżej przedstawiam tabelę z Reference Manual [1], w której zawarte są informację na temat innych zdarzeń mogących wyzwalać pomiar.
Ustawienie dotyczące konwersji ciągłej przetwornika ADC (adc.ADC_ContinuousConvMode = DISABLE) jest wyłączone z racji wyzwalania pomiaru za pomocą timera.

Moment pomiaru:

Jak wspomniałem powyżej, pomiar ADC jest zsynchronizowany z przebiegiem PWM, tak aby był realizowany tylko i wyłącznie podczas stanu wysokiego. W momencie generowania stanu wysokiego wyzwalany jest timer związany z pomiarem ADC, zaś w stanie niskim timer jest dezaktywowany. 

W programie zawarłem zabezpieczenie od chwilowej niesynchronicznej pracy PWM (np. podczas małego wypełnienia), które ma wpływ na pomiar ADC. Zabezpieczenie jest zrealizowane w postaci programowego filtru dolnoprzepustowego, pojedyncze niezsynchronizowane próbki nie będą wpływały na wynik pomiaru.

DMA (Direct Memory Access):

W projekcie do obsługi przetwornika ADC użyłem DMA, aby nie obciążać procesora, a po drugie ze względów edukacyjnych:). 
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
DMA_InitTypeDef DMAInit;
DMA_DeInit(DMA2_Stream0);
DMAInit.DMA_BufferSize = 4;
DMAInit.DMA_Channel = DMA_Channel_0;
DMAInit.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMAInit.DMA_Memory0BaseAddr = (uint32_t)&ADCValue[0];
DMAInit.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMAInit.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMAInit.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMAInit.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMAInit.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMAInit.DMA_Mode = DMA_Mode_Circular;
DMAInit.DMA_Priority = DMA_Priority_High;
DMAInit.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMAInit.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMAInit.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMAInit.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream0, &DMAInit);
DMA_StructInit(&DMAInit);
DMA_Cmd(DMA2_Stream0, ENABLE);

Informację o tym, którego użyć strumienia DMA, oraz kanału także znajdziesz w Reference Manual [1]:

Poniżej kilka słów komentarza do konfiguracji DMA:

  • Pisząc program testy wykonywałem na Discovery używając diod umieszczonych na płytce, dlatego rozmiar bufora wynosi 4.
  • Wartość ADC przechowywana jest w rejestrze DR.
  • Wartość ADC kopiowana jest do zmiennej ADCValue[0]. W konfiguracji wskazujemy tylko pierwszy adres zmiennej.
  • Inkrementacja peryferiów jest wyłączona z racji odczytu z jednego rejestru.
  • Inkrementacja pamięci jest załączona dlatego, że wykonywany jest pomiar w tym momencie dla 4 wartości
  • FIFO mode jest wyłączony, czyli załączony jest tryb bezpośredni. Mogłem sobie pozwolić na taką konfigurację z racji tego samego rozmiaru transferowanych danych w peryferiach jak i w pamięci. Tryb FIFO jest wykorzystywany, gdy rozmiary te różnią się.
  • FIFO threshold, wskazuję wartość po jakiej ma się odbyć transfer z peryferiów do pamięci, lecz jest wykorzystywany tylko w momencie aktywnej funkcji FIFO.
  • Tryb burst jest także tylko aktywny, kiedy jest włączona funkcja FIFO.

[1] Dostęp w Internecie: https://www.st.com/resource/en/reference_manual/dm00096844-stm32f401xb-c-and-stm32f401xd-e-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf

Autor artykułu
Mateusz Pluta

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *