środa, 11 listopada 2015

[3] STM32 M3 - Nucleo - F103RB - Przetwornik ADC

W tym poście chciałbym opisać sposób obsługi przetwornika analogowo cyfrowego.

Wstęp


W opisywanym mikrokontrolerze zastosowano dwa 12 bitowe przetwoniki ADC. Każdy z tych przetworników wyposażono na wejściu w multiplekser. Pozwala on na odczytywanie danych z 16 linii wejściowych

Przetworniki są 12 bitowe oznacza to, że otrzymana wartość napięcia będzie przetwarzana na jej reprezentację cyfrową z zakresu od 0 do 4096. Obsługiwane są piny od 0 do 15. Do kanału 16 podłączony został wbudowany czujnik temperatury układu. Natomiast do kanału 17 podłączone zostało napięcie referencyjne.

Maksymalny czas przetwarzania wartości przez kanał ADC obliczany jest z nastepującego wzoru:

T = czas próbkowania + 12,5 taktu

Czas próbkowania jest wybierany programowo spośród następujących wartości 1,5, 7,5, 13,5, 28,5, 41,5, 55,5, 71,5, 239,5.

Jeśli częstotliwość wynosi 10MHz i czas próbkowania 252. Wtedy otrzymuje się następujący czas przetwarzania.

T = 239,5 + 12,5 = 252 takty
T = 1/10MHz * 252 = 25,2 us

Należy pamiętać, że ADC toleruje napięcia do 3.3V. Z tego powodu nie należy podłączać napięcia wyższego np. 5V ponieważ można bardzo łatwo uszkodzić mikrokontroler.

Inicjalizacja ADC


ADC taktowane jest poprzez APB2. Częstotliwość tej linii przy korzystaniu z PLL wynosi 64MHz. Taką częstotliwością nie można taktować tego przetwornika, ponieważ jego maksymalna częstotliwość pracy wynosi 14MHz, natomiast minimalna 0,6MHz. Informacje te można znaleźć w nocie katalogowej mikrokontrolera na stronie 75.

Częstotliwość taktowana z szyny APB2 może zostać podzielona przez następujące wartości 1, 2, 4, 6, 8. Dzięki temu dostaniemy odpowiednio dobraną wartość częstotliwości, która będzie się mieścić w wymaganym przedziale. Aby częstotliwość była niższa od 14 należy zastosować dzielnik 6 lub 8.

Program - Potencjometr


Ten program przedstawia sposób jednokrotnego pomiaru napięcia sygnału analogowego z potencjometru. Oznacza to, że pomiar następuje tylko jeden raz. Wartość zmierzona będzie przechowywana w zmiennej i nie ulegnie zmianie. Nawet jeśli to napięcie będzie zmieniane.
Aby w takim trybie pracy można było dokonać kolejnego pomiaru należy wprowadzić linię ADC_SoftwareStartConvCmd(ADC1, ENABLE) która będzie wykonywana bezpośrednio przed kolejnym pomiarem.

Poniżej przedstawiam pełny program. Wynik jego można oglądać w trybie debugowania.
#include <stdio.h>
#include <stdint.h>
#include "stm32f10x.h"
 
void Delay(int);
void ADCInit();
void GPIOInit();
 
int main(void)
{
 uint16_t adconv = 0;
 float wartosc = 0;
 
 GPIOInit();
 ADCInit();
 
 while (1) {
 
  adconv = ADC_GetConversionValue(ADC1);
  wartosc = (float)adconv * 3.3 / 4096.0;
 }
}
void Delay(int time)
{
    int i;
    for (i = 0; i < time * 5000; i++) {
    }
}
 
void ADCInit(void)
{
 ADC_InitTypeDef ADCInit;
 
 //Włączenie sygnału taktującego
 RCC_ADCCLKConfig(RCC_PCLK2_Div6);
 //Zmniejszenie częstotliwości z 64MHz na 64MHz/6 = 10,66MHz
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
 
 ADC_StructInit(&ADCInit);
 
 //Niezależna praca przetwornika
 ADCInit.ADC_Mode = ADC_Mode_Independent;
 //Wylaczone skanowanie kanałów, pomiar tylko na jednym kanale
 ADCInit.ADC_ScanConvMode = DISABLE;
 //Dokonywanie pomiarów w trybie jednokrotnym
 ADCInit.ADC_ContinuousConvMode = DISABLE;
 //Liczba wykorzystywanych kanałów
 ADCInit.ADC_NbrOfChannel = 1;
 //Wyzwalanie zewnętrzne wyłączone
 ADCInit.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
 
 //Inicjalizacja przetwornika
 ADC_Init(ADC1, &ADCInit);
 
 //Ustawienie parametrów
 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_71Cycles5);
 //Włączenie ADC1
 ADC_Cmd(ADC1, ENABLE);
 
 //Reset rejestrów kalibracyjnych
 ADC_ResetCalibration(ADC1);
 //Czekanie na reset
 while (ADC_GetResetCalibrationStatus(ADC1));
 
 //Kalibracja
 ADC_StartCalibration(ADC1);
 //Odczekanie na wykonanie kalibracji
 while (ADC_GetCalibrationStatus(ADC1));
        //Wyzwolenie pojedynczego pomiaru
 ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
 
void GPIOInit(void)
{
 GPIO_InitTypeDef GPIOInit;
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
 
 GPIOInit.GPIO_Pin = GPIO_Pin_0;
 GPIOInit.GPIO_Mode = GPIO_Mode_AIN;
 GPIO_Init(GPIOA, &GPIOInit);
}

W celu dokonywania ciągłych konwersji należy zmienić wartość parametru ADC_ContinuousConvMode z Disable na Enable.
ADCInit.ADC_ContinuousConvMode = ENABLE;

Po jego zmianie konwersja jest ciągłą, parametr zmienia się dynamicznie.

ADC_RegularChannelConfig określa parametry przetwarzania. W tym przypadku kanał 0 będzie przetwarzany przez przetwornik ADC1. Ma on zostać przypisany z numerem 1 do grupy podstawowej. Czas próbkowania został określony na 71,5 taktu.

 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_71Cycles5);

Program - Wewnętrzny Termometr


W tym programie zaprezentuje sposób uzyskania dostępu do wewnętrznego termometru podłączonego na stałe do kanału 16 ADC. Dokładność skalibrowanego czujnika wynosi +/- 1,5 st C. Jest on najczęściej wykorzystywany do pomiarów temperatury mikrokontrolera. W celu jego ochrony przed przegrzaniem.

Przetwarzana jest wartość z zakresu napięciowego od 2 do 3.6V.

Poniżej przedstawiam całość programu do obserwacji temperatury.
#include <stdio.h>
#include "stm32f10x.h"
 
void Delay(int);
void ADCInit(void);
 
int main(void)
{
 uint16_t wartosc = 0;
 float temperatura = 0;
 float napiecie = 0;
 
 ADCInit();
 while (1) {
  //Odczytanie wartosci z przetwornika
  uint16_t wartosc = ADC_GetConversionValue(ADC1);
  //Przetworznie odczytanej wartosci na napiecie
  napiecie = wartosc * 3.3f / 4096.0f;
  //Obliczenie temperatury
  temperatura = (((1430 - (wartosc*0.8067))/4.3) + 25.0);
  Delay(2000);
 }
}
 
void ADCInit(void)
{
 ADC_InitTypeDef ADCInit;
 
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
 RCC_ADCCLKConfig(RCC_PCLK2_Div6);
 
  ADC_StructInit(&ADCInit);
 
  ADCInit.ADC_Mode = ADC_Mode_Independent;
  ADCInit.ADC_ScanConvMode = DISABLE;
  ADCInit.ADC_ContinuousConvMode = ENABLE;
  ADCInit.ADC_NbrOfChannel = 1;
  ADCInit.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
 
  ADC_Init(ADC1, &ADCInit);
 
  ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_239Cycles5);
  ADC_Cmd(ADC1, ENABLE);
 
  ADC_ResetCalibration(ADC1);
  while (ADC_GetResetCalibrationStatus(ADC1));
 
  ADC_StartCalibration(ADC1);
  while (ADC_GetCalibrationStatus(ADC1));
 
  //Włączenie czujnika temperatury
  ADC_TempSensorVrefintCmd(ENABLE);
 
  ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
void Delay(int time)
{
    int i;
    for (i = 0; i < time * 5000; i++) {
    }
}

Odczytana wartość z przetwornika waha się w granicach od 1693 do 1695. Po skorzystaniu z wzoru otrzymujemy następujące wartości temperatury:


V25 - jest to napięcie w temperaturze 25 st. C. Przyjmuje się ją jako 1,43 V. Oscyluje ona w zakresie od 1,34 do 1,52 V.  
Vsense - jest to zmierzone napięcie czujnika
AVG_SLOPE - czułość czujnika. Przyjmuje 4,3mV/st. C

Odczytana temperatura wynosi 39,7 st.C. Należy pamiętać aby czas próbkowania wynosił przynajmniej 17us.