STM32 BluePill çok kanallı ADC (DMA) kullanımı

 1) Tek Kanal ADC

A1 pinimizi gelip sol click tıkladığımız zaman önümüze çıkan seçeneklerden ADC1_IN1 olarak ayarlıyoruz. A1 pinimiz (0 ila 3.3V arası) gelen gerilim seviyesini 12 bit ADC ile yani 212 hassasiyette 0 ila 4095 arası değerde okumaktadır. Potun ucu GND’ye yaklaştıkta 0’a, Vcc’ye yaklaştıkça 4095’ e doğru artacaktır.



ADC’nin aktif olduğu durumda örnekleme sayısı durumuna göre CubeIDE hata verebilir ve  Clock configuration kısmından ADC prescaler değerini değiştirilmesi gerekebilir. Bu Clock issues hatası durumunda Resolve Clock issues der isek hata kendiliğinden çözülecektir


Pin bağlantılarımız aşağıdaki gibidir. 



Kod satırlarımız ise sağda verilmiştir. Potansiyometrenin orta pini her zaman sinyal pinidir.

/* USER CODE BEGIN 0 */
uint16_t = adcValue;
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
  HAL_ADC_Start(&hadc1);
/* USER CODE END 2 */

  /* Infinite loop */

  /* USER CODE BEGIN WHILE */

  while (1)

  {

        HAL_ADC_PollForConversion(&hadc1,1000);

         adcValue = HAL_ADC_GetValue(&hadc1);

    /* USER CODE END WHILE */

 

    /* USER CODE BEGIN 3 */

  }

  /* USER CODE END 3 */


Kod bloklarının ne işe yaradıklarını açıklayalım;

HAL_ADC_Start: Bu fonksiyon, ilgili ADC modülünü (örnekte ADC1) başlatmak için kullanılır. ADC donanımını aktif hâle getirir ve dönüşüm işlemlerine hazır hale gelmesini sağlar. Bu adım, analog veri okumaya başlamadan önce mutlaka yapılmalıdır; aksi takdirde ADC çalışmaz veya geçersiz veri üretir.

HAL_ADC_PollForConversion: Bu fonksiyon, ADC’nin analog veriyi sayısal değere dönüştürme işleminin tamamlanmasını bekler. “Polling” yani sürekli kontrol yöntemiyle çalışır ve belirtilen süre (örnekte 1000 ms) içinde dönüşüm tamamlanmazsa zaman aşımı oluşur. Dönüşüm tamamlandığında kod bir sonraki adıma geçer.

HAL_ADC_GetValue: Bu fonksiyon, ADC tarafından dönüşümü tamamlanan analog verinin sayısal karşılığını döndürür. Elde edilen değer genellikle 12 bitlik (0–4095 arası) bir sayıdır ve genellikle bir değişkende saklanarak işlenir. Bu değer, analog sensör verisini yazılım düzeyinde kullanmak için gereklidir.


2. Çok Kanallı ADC

  • DMA'sız metod (Polling)


STM32’de Çoklu ADC yani multiADC okumak istersek birden fazla yöntemimiz ve kullanım şekillerimiz bulunmaktadır. Şu an daha önce gösterdiğimiz Polling metodu üzerinden çok ADC’nin nasıl okunacağını doğrudan kod bloğu ile gösterelim.

 

/* USER CODE BEGIN 0 */

uint16_t readValue1, readValue2, readValue3;

ADC_ChannelConfTypeDef sConfigPrivate = {0};

/* USER CODE END 0 */

 

  /* Infinite loop */

  /* USER CODE BEGIN WHILE */

  while (1)

  {

         sConfigPrivate.Rank = ADC_REGULAR_RANK_1;

         sConfigPrivate.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;

 

         sConfigPrivate.Channel = ADC_CHANNEL_1;

         HAL_ADC_ConfigChannel(&hadc1, &sConfigPrivate);

         HAL_ADC_Start(&hadc1);

         HAL_ADC_PollForConversion(&hadc1,1000);

         readValue1 = HAL_ADC_GetValue(&hadc1);

         HAL_ADC_Stop(&hadc1);

 

         sConfigPrivate.Channel = ADC_CHANNEL_2;

         HAL_ADC_ConfigChannel(&hadc1, &sConfigPrivate);

         HAL_ADC_Start(&hadc1);

         HAL_ADC_PollForConversion(&hadc1,1000);

         readValue2 = HAL_ADC_GetValue(&hadc1);

         HAL_ADC_Stop(&hadc1);

 

         sConfigPrivate.Channel = ADC_CHANNEL_3;

         HAL_ADC_ConfigChannel(&hadc1, &sConfigPrivate);

         HAL_ADC_Start(&hadc1);

         HAL_ADC_PollForConversion(&hadc1,1000);

         readValue3 = HAL_ADC_GetValue(&hadc1);

         HAL_ADC_Stop(&hadc1);

 

    /* USER CODE END WHILE */

 

    /* USER CODE BEGIN 3 */

  }

 

Bu kod bloğumuzda ADC’nin config ayarlamaları yapılır. Burada adım adım taramalı bir şekilde adc değerleri okunur.

·     sConfigPrivate.Channel = ADC_CHANNEL_1;  Burada ADC modülümüz kapalıdır yani inaktiftir. ADC kanalını başlatmadan önce kanalı 1 olarak ayarlıyoruz ki bu PA1 pini oluyor.

         HAL_ADC_ConfigChannel(&hadc1, &sConfigPrivate); Bu kanal ayarı ADC1 modülüne uygulanır.

       HAL_ADC_Start(&hadc1); ADC modülü çalıştırılarak dönüşüm işlemi başlatılır.

       HAL_ADC_PollForConversion(&hadc1,1000); Dönüşümün tamamlanması 1000 ms timeout süresiyle beklenir.

        readValue1 = HAL_ADC_GetValue(&hadc1); ADC’nin dijitale çevirdiği değer okunur ve değişkene atanır.

        HAL_ADC_Stop(&hadc1); ADC işlemi sonlandırılarak kaynaklar serbest bırakılır. Ve bir sonraki mod için ayarlamalar daha altta kalan satırlar için gerçekleştirilir. 

  • DMA (Normal Mode)

ADC config tarafında ise Scan conversion mode ‘Enabled’ ve Continous Conversion mode ‘Disabled’ durumuna getirilip number of conversion değeri ise kaç adet adc kanalımız var ise o değerde sayısal değer girilecektir. ilk başta Number of conversion sayısını 3’e çıkaralım. Bunu yaptığımız takdirde Scan conversion mode’u enabled yapmamıza izin verecektir. Onu da daha sonra aktif hale getiriyoruz



Artık ayarlarımız bu şekilde gözükecektir. Bu aşamadan sonra kod kısmına geçebiliriz. Konfigürasyonumuzda Scan mode’i aktif ettik. Burada hangi adc kanalından veri geldiğini if şart koşulu ile belirledikten sonra sırasıyla okunan her değeri ADC_VAL dizisine kaydetmekteyiz. Her HAL_ADC_GetValue fonksiyonu çağrılınca fonksiyon kendiliğinden bir sonraki adc kanalı değerine döner.

DMA’yı normal mode’da kullanacaksak Continous Conversion mode  ‘Disabled’ olarak kullanılacaktır.

Fakat DMA’yı circular mode’da kullanacaksak Continous Conversion mode  ‘Enabled’ olmalıdır’

HAL_ADC_Start_DMA(&hadc1,ADC_VALUE[3], 3); komutuyla ADC DMA’yı başlattık. ADC scan modunda olduğundan ötürü her kanalı okuyup belleğe kaydedecektir. Tüm okuma dönüşümü tamamlandıktan sonra da HAL_ADC_ConvCpltCallback fonksiyonunu çağrıyoruz. Bu sayede işlemciyi meşgul etmeden DMA yolu ile çok kanallı ADC okuyabiliyoruz.

/* USER CODE BEGIN 0 */

uint16_t ADC_VAL[3];

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)

{     

// okuma bittikten sonra gerçekleştirilecek kod buraya yazilabilir.

       HAL_ADC_Start_DMA(&hadc1, ADC_VAL, 3);

}

/* USER CODE END 0 */

  /* USER CODE BEGIN 2 */

  HAL_ADC_Start_DMA(&hadc1, ADC_VAL, 3);

  /* USER CODE END 2 */

  /* Infinite loop */

  /* USER CODE BEGIN WHILE */

  while (1)

  {

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

  }

  /* USER CODE END 3 */


  • DMA(Circular Mode)

Normal moddan farklı olarak DMA Request settings kısmından Circular mode’u seçiyoruz. Dairesel mod, adından da anlaşılacağı üzere verileri dairesel olarak okur. Normal modun aksine bir kere başlatıldıktan tekrar tetiklenmeye ihtiyaç duymaz.


DMA kodu while döngüsünün içine girmeden önce başlatılır. Ve dairesel mod artık başlatılmış olur. Her okuma döngüsü tamamlandığında HAL_ADC_ConvCpltCallback fonksiyonu çağrılır. Fakat burada tekrar bir başlatmaya ihtiyaç duyulmaz. Çünkü dairesel mod bir kere başlatıldıktan sonra tekrar tekrar veri okumaya devam eder. Her iki yöntemin avantaj ve dezavantajlarından kısaca bahsedersek:

/* USER CODE BEGIN 0 */

uint16_t ADC_VAL[3];

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)

{     

// okuma bittikten sonra gerçekleştirilecek kod buraya yazilabilir. Circular mode olduğundan ötürü tekrar HAL_ADC_Start_DMA dememize gerek kalmaz.

}

/* USER CODE END 0 */

  /* USER CODE BEGIN 2 */

  HAL_ADC_Start_DMA(&hadc1, ADC_VAL, 3);

  /* USER CODE END 2 */

  /* Infinite loop */

  /* USER CODE BEGIN WHILE */

  while (1)

  {

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

  }

  /* USER CODE END 3 */


Hangi Mod Ne Zaman Kullanılmalı?

  • Normal Mode: Tek seferlik veya belirli bir süre için veri toplama gerektiğinde (örneğin, bir sıcaklık sensöründen 100 örnek almak).
  • Circular Mode: Sürekli veri akışı gerektiğinde (örneğin, ses sinyali, hareket sensörü verisi veya gerçek zamanlı veri işleme).

Örnek Senaryolar

  • Normal Mode: Bir batarya voltajını belirli aralıklarla ölçmek için ADC'yi kullanıyorsanız, normal mod yeterlidir.
  • Circular Mode: Bir mikrofondan gelen ses sinyalini sürekli olarak işlemek için circular mod tercih edilir.

Ek İpuçları

  • Normal Mod: Daha basit uygulamalarda ve düşük frekanslı veri toplama için uygundur. DMA yapılandırmasını optimize ederek CPU yükünü azaltabilirsiniz.
  • Circular Mod: Buffer boyutunu uygulama gereksinimlerine göre dikkatli seçin. Çift buffer veya kesme tabanlı veri işleme ile veri kaybını önleyebilirsiniz.

ADC Dönüşüm süresinin hesaplanması:


Ek olarak hassas işlemler için ADC örnekleme dönüşümünün ne kadar süre ile sürdüğünü hesaplayalım: Tconv dönüşüm süresi, Sampling Cycles örnekleme süresi, conversion Cycles dönüşüm süresi, ADC Clock ADC frekansı demektir. Conversion Cycles değeri sabit bir dönüşüm süresidir. Bu STM32F103C8T6 için 12.5 çevrim değerine denk gelmektedir. Sampling cycles ve ADC Clock konfigüre edilebilir değerlerdir.



Burada görüldüğü üzere ADC frekansımız 12 Mhz değerindedir. ADC CLOCK = 12000000 buradaki ADC frekansı konfigüre edilebilir.

Sampling Cycles ise her adc çevrimi için ayrı konfigüre edilebilir. Bunlar sırasıyla aşağıda verilen grafikte gösterilmektedir.

Şimdi ise az önceki yaptığımız örneği hesaplayalım. 3 adet ADC’miz var. Örnekleme çevrimini 1.5 Cycles aldık. Bu durumda

Yani neredeyse 1 mikro saniyede bir adc çevrimi okunmuş olmaktadır. Fakat biz burada 3 kanaldan taramalı olarak adc okuduk. Bu da demektir ki çıkan değeri 3 ile çarparsak işlemcinin dönüşüm esnasında harcadığı süre 3.24 mikrosaniye gibi bir süreye tekabül etmektedir. Sampling cycles değerinin düşük veya yüksek olmasının kendisine göre bazı avantaj ve dezavantajları da bulunmaktadır. Daha büyük dönüşüm süresi daha doğru sonuçlar verir fakat daha uzun zaman harcanmasına sebep olur. Daha düşük dönüşüm süresi daha hızlı sonuçlar verir.


Yorumlar