Örnek: STM32F4 Discovery Çok kanal ADC

Başlatan Klein, 02 Ağustos 2012, 18:35:23

Klein

#include "STM32F4xx.h" 
#include "stdio.h" 
 
unsigned char WAdr,RAdr;
char RxBuf[128];
char TxBuf[128];
unsigned char timer_update=0; 
uint16_t adc_val[4];
uint8_t ch_index =0;
/*********************************************************************************
 CPU frekansi 168Mhz
 AHB frekansi 84 Mhz
 APB frekansi 42 Mhz
*********************************************************************************/
 
void SystemInit()
{
unsigned int i;
      for (i=0;i<0x00100000;i++);     // OSC oturtma ve kurtarma rutini
      RCC->CFGR |= 0x00009400;        // AHB ve APB hizlarini max degerlere set edelim
      RCC->CR |= 0x00010000;          // HSE Xtal osc calismaya baslasin        
      while (!(RCC->CR & 0x00020000));// Xtal osc stabil hale gelsin
      RCC->PLLCFGR = 0x07402A04;      // PLL katsayilarini M=4, N=168, P=2 ve Q=7 yapalim   168 Mhz 
      RCC->CR |= 0x01000000;          // PLL calismaya baslasin  (Rehber Sayfa 95)
      while(!(RCC->CR & 0x02000000)); // Pll hazir oluncaya kadar bekle
      FLASH->ACR = 0x00000605;        // Flash ROM icin 5 Wait state secelim ve ART yi aktif edelim (Rehber Sayfa 55)
      RCC->CFGR |= 0x00000002;        // Sistem Clk u PLL uzerinden besleyelim
      while ((RCC->CFGR & 0x0000000F) != 0x0000000A); // Besleninceye kadar bekle
      RCC->AHB1ENR |= 0x0000001F;     // GPIO A,B,C,D,E clock'u aktif edelim 
      GPIOD->MODER  = 0x55550000;     // GPIOD nin 15, 14, 13, 12, 11, 10, 9, 8 pinleri cikis tanimlandi (LEDler icin)
      GPIOD->OSPEEDR= 0xFFFFFFFF;     // GPIOD nin tum cikislari en yuksek hizda kullanacagiz 
     RCC->APB1ENR|=0x00000020;       // Timer7 CLK'u aktif edelim (84 Mhz)

     TIM7->CR1=0x0080;               // Otomatik Reload
     TIM7->PSC =839;                 // Prescaler degerimiz 839, Count frekansimiz = fCK_PSC / (Yuklenen Deger + 1) 84E6 / (840) = 100 KHz
     TIM7->ARR =5;                   // Counter, Decimal 1 olunca basa donsun. Her 20 mikrosaniye de bir timer int olusacak.
     TIM7->DIER=0x0001;              // Update Int enable
     NVIC->ISER[1] = 0X00800000;     // NVIC de Timer 7 interrupta izin verelim
     TIM7->CR1|=0x0001;              // Counter Enable

     GPIOB->MODER |= 0x0000000F; //  B0 ve B1 analog
     GPIOC->MODER |= 0x00000F00; //  C4 ve C5 analog
	  
     RCC->APB2ENR|=0x00000100;  // ADC saat kaynağını aktif ettik. 
     ADC1->CR1 |=0x00000120; //  ADC scan modunda çalışacak. Interrupt aktif.
	 NVIC_EnableIRQ(ADC_IRQn); // NVIC ADC kesmesini aktif ettik. 

     ADC1->SQR1 |= (3 << 20); // Kaç kanalı tarayacağımızı bildiriyoruz. 20..23 bitler; 4 kanal
     ADC1->SQR3 |= 8; // Çevrime ilk girecek kanal 8. (PB0)
     ADC1->SQR3 |= (9 << 5); // Çevrime 2. sırada girecek kanal 9. (PB1)
     ADC1->SQR3 |= (14 << 10); // Çevrime 3. sırada girecek kanal 14. (PC4)
     ADC1->SQR3 |= (15 << 15); // Çevrime 4. sırada girecek kanal 15. (PC5)
	 ADC1->SMPR1 |=0xFFFFFFFF; // En düşük çevirim hızı.  
	 ADC1->SMPR2 |=0xFFFFFFFF; // En düşük çevirim hızı.

     ADC1->CR2 |=0x00000403;   // AD converter'i açtık. Sürekli çevirim yapacağız. 
	 						   // Her kanal çevrimi bittiğinde EOC set olacak. 
} 
 
/********************************************************************************************************
								ADC KESME RUTİNİ
**********************************************************************************************************/
void ADC_IRQHandler()
{
      	if(ADC1->SR & 0x0002){
           	ADC1->SR &= ~0x0002;
			adc_val[ch_index] = ADC1->DR;
			if(++ch_index > 3)ch_index=0;
        }

}



/*********************************************************************************
      USART3 modulunu kullanarak asenkron haberlesme (Hata kontrolu yapilmiyor)
*********************************************************************************/
void USART3_IRQHandler()
{
volatile int Sts;
     Sts=USART3->SR;
     RxBuf[WAdr]=USART3->DR;
     WAdr=(WAdr+1)&0x7F;
}
 

void UsartInit()
{
     WAdr=0;RAdr=0;
 
// USART3 MODULUNU AKTIF HALE GETIRELIM
 
      RCC->APB1ENR|=0x00040000;  // USART3 Clk Enable (Rehber Sayfa 113)
      RCC->APB1RSTR|=0x00040000;  // USART3 Resetlendi
      GPIOB->AFR[1]=0x07777700;  // PB10..PB14 pinleri USART3 ile alakalandirildi (Hard Sayfa 49)
      GPIOB->MODER|=0x2AA00000;  // GPIOB 10..14 icin alternatif fonksiyon tanimi (Rehber Sayfa 148)
 
// USART3 MODULUNU AYARLAYALIM  // 1 Start, 8 Data, 1 Stop, No parity (Default degerler)
 
      RCC->APB1RSTR&=~0x00040000;  // USART3 Reseti kaldiralim
//      USART3->SR&=~0X03FF;   // Status registeri silelim
      USART3->BRR=0X1112;    // 9600 Baud
 
      USART3->CR1|=0x0000202C;  // USART3 enable
      NVIC->ISER[1]|=0x80;         // NVIC da USART3 interrupta izin verelim
}
 
void SendChar(char Tx) 
{
      while(!(USART3->SR&0x80));  // TX Buffer dolu ise bekle (Rehber Sayfa 646)
      USART3->DR=Tx;
}
 
void SendTxt(char *Adr)
{
      while(*Adr) 
        {
          SendChar(*Adr);
          Adr++;
        }  
}
 
char DataReady()
{
       return(WAdr-RAdr);
} 
 
char ReadChar()
{
char Dat;
    
      Dat=RxBuf[RAdr];
      RAdr=(RAdr+1)&0x7F;
      return(Dat);
}


void TIM7_IRQHandler()
{
TIM7->SR=0;    
timer_update=1;
} 
 
// Rx ve TX pinlerini (GPIOB10 ve GPIOB11) birbirine baglarsaniz gonderdiginiz datalar geri gelecektir
 
int main()
{
unsigned int ledtime;
      UsartInit(); 
      SendTxt("PicProje"); 
      ADC1->CR2 |=0x40000000; // ADC çevrimini başlattık. 
      while(1){

      	if(timer_update){
            timer_update=0;
            if(++ledtime > 5000){
      			ledtime =0;
                GPIOD->ODR ^= 0x0000008000;
                sprintf(TxBuf,"ch0:%d ch1:%d ch2:%d ch3:%d\r",adc_val[0],adc_val[1],adc_val[2],adc_val[3]);
                SendTxt(TxBuf);
      		}
        }
      }; 
}


Register açıklamaları:

RCC->APB2ENR|=0x00000100;  // ADC saat kaynağını aktif ettik.
Artk her donanım için clk  kaynağını aktif etmemiz gerektiğini , aktif olmadıkları zaman güç tüketiminin düştüğünü biliyoruz. Bu yüzden bunun detayına girmiyorum.


ADC1->CR1 |=0x00000120; 
ADC scan modunda çalışacak.CR1 registerinin 8. biti scan modu açma kapama biti.
Eğer scan modu açıksa , kaç tane kanalın taranmasını istediysek  o kadar kanalın çevrimi sırayla yapılır. Tüm kanallar bitince çevirim durur.
CR1 registeri 5. biti  kesme izni.  ADC'ye kesme izni verdik.

NVIC_EnableIRQ(ADC_IRQn);
NVIC ADC kesmesini aktif ettik.


   
     ADC1->SQR1 |= (3 << 20); // Kaç kanalı tarayacağımızı bildiriyoruz. 20..23 bitler; 4 kanal
     ADC1->SQR3 |= 8; // Çevrime ilk girecek kanal 8. (PB0)
     ADC1->SQR3 |= (9 << 5); // Çevrime 2. sırada girecek kanal 9. (PB1)
     ADC1->SQR3 |= (14 << 10); // Çevrime 3. sırada girecek kanal 14. (PC4)
     ADC1->SQR3 |= (15 << 15); // Çevrime 4. sırada girecek kanal 15. (PC5)

SQR registerleri  hangi kanalın hangi sırayla çevirime gireceğini seçmemizi sağlar. Register 5'er bitlik alanlara ayrılmıştır. Her bir SQR registeri 6 tane kanalın sıralamasını seçmemize izin verir.   SQR3 registeri ilk 6 sıra, SQR2  7- 11 arası SQR1 de 12-15 arası olmak üzere toplam 16 kanalın sırasını seçmemize olanak verir. 
SQR1 reg isterini diğer SQR registerlerinden ayıran  özellik ise, Bu registerin 20-21-221 ve 23. bitlerinden scan modda  kaç kanalın taranacağını seçebiliyor olmamızdır.

     ADC1->SMPR1 |=0xFFFFFFFF; // En düşük çevirim hızı. 
     ADC1->SMPR2 |=0xFFFFFFFF; // En düşük çevirim hızı.


ADC'yi en düşük  çevirim hızında çalıştırıyoruz. SMPR registerleri her kanal için ayrı ayrı çevirim hızı belirleme imkanı veriyor. Her kanal için 3 bit ayrılmış.
Eğer yüksek hız kullanılırsa interrupt, hatta DMA kullanmak gerek. Aksi durumda siz daha DR registerini okuyamadan yeni veri geliyor.



ADC1->CR2 |=0x00000403;   // AD converter'i açtık. Sürekli çevirim yapacağız.
                          // Her kanal çevrimi bittiğinde EOC set olacak.

CR2 registerinin ilk biti ADON bitidir. ADC'yi açma ve kapatmaya yarar. Burada dikkat edilmesi gereken şey ADC ON yapıldıktan sonra stabil olması için bir süre geçmesi gerekliliğidir. Stabil olduktan sonra çevirimi başlatmak gerek. Rehberde bununla ilgili açıklama var.
CR2 registerinin 1. biti ise continious mode seçim bitidir. Eğer 0 ise single 1 ise Continious modda çalışır.
CR2 registerinin 10. biti(EOCS)  EOC (End of conversion) modunu seçer. Eğer bu  bit 0 ise, tüm kanallar tarandıktan sonra EOC bitiset edilir. Bu bit 1 olursa her kanalın örneklemesi bittiğinde EOC biti set edilir. 

ADC1->CR2 |=0x40000000; // ADC çevrimini başlattık.
CR2 registerinin 3. biti çevirimi başlatır.  Continious mode seçili olduğu için  bir kez başlatmak yeterlidir. Eğer single modda olsaydık. çevirim bittikten sonra başlatmak için yeniden set etmemiz gerekecekti.


LukeSkywalker


Klein

ADC ayarları için  alternatif

#include "STM32F4xx.h" 
#include "stdio.h" 
#include "STM32F4xx_adc.h"
#include "STM32F4xx_GPIO.h"
 
unsigned char WAdr,RAdr;
char RxBuf[128];
char TxBuf[128];
unsigned char timer_update=0; 
uint16_t adc_val[4];
uint8_t ch_index =0;
/*********************************************************************************
 CPU frekansi 168Mhz
 AHB frekansi 84 Mhz
 APB frekansi 42 Mhz
*********************************************************************************/
 
void SystemInit()
{

                ...  // diğer başlangıç kodları

	 PB->MODER.p0 = PORT_ANALOG;
	 PB->MODER.p1 = PORT_ANALOG;
	 PC->MODER.p4 = PORT_ANALOG;
	 PC->MODER.p5 = PORT_ANALOG;

                RCC->APB2ENR|=0x00000100;  // ADC saat kaynağını aktif ettik. 
	 AD1->CR1.SCAN = ADC_SCAN_ENABLE;  // scan modunu açtık
	 AD1->CR1.EOCIE = ADC_INTERRUPT_ENABLE; // interrupt izni verdik.

	 NVIC_EnableIRQ(ADC_IRQn); // NVIC ADC kesmesini aktif ettik. 

	 AD1->SQR1.L = 3;  //4 kanal tarayacağız
	 AD1->SQR3.SQ1 = 8;  //Çevrime ilk girecek kanal 8. (PB0)
	 AD1->SQR3.SQ2 = 9;  //Çevrime 2. sırada girecek kanal 9. (PB1)
	 AD1->SQR3.SQ3 = 14;  //Çevrime 3. sırada girecek kanal 14. (PC4)
	 AD1->SQR3.SQ4 = 15;  //Çevrime 4. sırada girecek kanal 15. (PC5)
	  
	 AD1->SMPR2.SMP8 = ADC_SMPTIME_480_CYCLES; // 8. kanal en düşük hız 
	 AD1->SMPR2.SMP9 = ADC_SMPTIME_480_CYCLES; // 9. kanal en düşük hız 
	 AD1->SMPR1.SMP14 = ADC_SMPTIME_480_CYCLES; // 14. kanal en düşük hız 
	 AD1->SMPR1.SMP15 = ADC_SMPTIME_480_CYCLES; // 15. kanal en düşük hız 
	
	 AD1->CR2.ADON = ADC_ON; // ADC'yi açtık
	 AD1->CR2.CONT = ADC_CONTINIOUS_CONVERSION; // sürekli çevirim yapacağız. 
	 AD1->CR2.EOCS =  ADC_EOC_MODE_CHANNEL;  // EOC biti her kanalın çevrimi bittiğinde set edilecek.
} 
 
/********************************************************************************************************
								ADC KESME RUTİNİ
**********************************************************************************************************/
void ADC_IRQHandler()
{
      	if(AD1->SR.EOC){
           	AD1->SR.EOC=0;
			adc_val[ch_index] = AD1->DR;
			if(++ch_index > 3)ch_index=0;
        }

}

muhittin_kaplan

Hocam Burayı Biraz Açarmısınız
ADC ayar derken, sadece ADC okuma amaçlımı burası. (USART tan gönderme felan yapmadığınızdan mı böyle yazdınız ? )

alisoy

DMA ile cok kanallı okuma ornegi de yapabilir miyiz? Ben degerleri aldıgım halde surekli olarak alamadım .Diger normal okuma yoluyla surekli okudugum halde   DMA ile sadece Bir kez deger okumaktayım.  Yardımlarınızı bekliyorum .

GreeN

Alıntı yapılan: alisoy - 08 Ekim 2012, 20:50:54
Ben degerleri aldıgım halde surekli olarak alamadım .Diger normal okuma yoluyla surekli okudugum halde   DMA ile sadece Bir kez deger okumaktayım.  Yardımlarınızı bekliyorum .

Sürekli okuma için ADC Continuous modda ve DMA Circular modda olmalı .
ADC->CR2 DDS de set edilmeli .
ilk aklıma bunlar geldi.
Terörü Lanetliyoruz.

Klein

#6
Alıntı yapılan: muhittin_kaplan - 08 Ekim 2012, 19:59:53
Hocam Burayı Biraz Açarmısınız
ADC ayar derken, sadece ADC okuma amaçlımı burası. (USART tan gönderme felan yapmadığınızdan mı böyle yazdınız ? )
ADC register ayarları için yazdığım headerin kullanımını gösteriyor.

Kendi headeri ile
ADC1->SQR1 |= (3 << 20); // Kaç kanalı tarayacağımızı bildiriyoruz. 20..23 bitler; 4 kanal


Alternatif header ile
AD1->SQR1.L = 3;  //4 kanal tarayacağız


Alıntı YapDMA ile cok kanallı okuma ornegi de yapabilir miyiz? Ben degerleri aldıgım halde surekli olarak alamadım .Diger normal okuma yoluyla surekli okudugum halde   DMA ile sadece Bir kez deger okumaktayım.  Yardımlarınızı bekliyorum .

@GreeN  bir yorum getirmiş.  DMA ile ilgili bir çalışma yapmadım şu ana kadar. Bir ara fırsat bulduğumda ilgileneceğim.

alisoy

yaptıgım kod su sekilde . Circuler modu aktif ediyorum .

void ADC_DMA_ayarla()
{
 int i;
 DMA2_Stream4->CR&=0xFFFFFFFE;
 RCC->APB2ENR|=0x00000100;  	    // ADC1 saat kaynagini aktif ettik. 
 RCC->AHB1ENR |= 0x00400000;        // DMA2 clock'u aktif edelim
 RCC->AHB1RSTR|= 0x00400000;       // DMA2 resetleyelim
 for(i=0;i<0x1FF;i++);			   // Azcik bekleyelim
 RCC->AHB1RSTR&=~0x00400000;     // DMA2 reseti kaldiralim
// GPIOB->MODER |= 0x0000000C;	    //ADC1 kanal 9
 GPIOC->MODER |= 0x00000003;	    //ADC1 kanal 10
 DMA2_Stream4->CR &= ~0x00000001;
 //DMA2_Stream4->CR |= 0x01800000;
 DMA2_Stream4->CR |= 0x00002900;
 DMA2_Stream4->PAR = (int)&ADC1->DR;  // peripheral Base adress secilir.
 DMA2_Stream4->M0AR = (uint32_t) & ADCConvertedValue;
 DMA2_Stream4->FCR&=~0xFFFFFF40;
 DMA2_Stream4->NDTR = 2; 					// 2 pin okuayacag1z.
 DMA2_Stream4->CR =0x01E05501;
 //DMA2_Stream4->CR |= 0x00030000;				//DMA oncelik yuksek

 DMA2_Stream4->CR |= 0x00000001;				//DMA aciyoruz.	
 NVIC_EnableIRQ(DMA2_Stream4_IRQn);                       // DMA kesme aktif

			
			
			
ADC1->CR1 |=0x00000100; 		       //  ADC scan modunda çal1sacak.
ADC1->CR2 |= 0x00000002;			//continuous modda
//  ADC1->CR2 |= 0x200000000; 		// external trigeer ac1ldi;
   ADC1->SQR1 |= 0x00500000; 		// 2 tane okuyacag1z
 ADC1->SQR3 |= 0x000009A; 		        // hangi sirada olacagi
//  ADC1->SQR3 |= 0x0000000A; 		 // Çevrime ilk girecek kanal 10. (PC0)
ADC1->SMPR1 =1;
ADC1->CR1 |= 0x00000020;			 // end of convertion da kesme uretmesi için 
ADC1->CR2 |= 0x00000100; 			// DMA  istegi act1k;
ADC1->CR2 |= (1 << 30);                // donusumu baslat
ADC1->CR2 |= 0x00000001; 		 // ADC act1k;
// 	NVIC->ISER[0] |= 0x00040000;       // ADC için kesmeyi actik 
 NVIC_EnableIRQ (ADC_IRQn); 
}

void ADC_IRQHandler()
{
	GPIOD->ODR ^= 0x00008000; 
	if (ADC1->SR & (1<<1)) 
 	{  
 	             
  deger = (ADC1->DR) & 0xFFF;   // Buradaki deger surekli guncellenmesine ragmen DMA daki      ADCConvertedValue degeri sadece 1 kez guncelleniyor.
   ADC1->SR &= ~(1<<1); 
ADC1->SR=0;
	}
}

pisayisi

Multi adc modunda son transferden sonra dma nın enable edilmesi lazım,

/
* Enable DMA request after last transfer (multi-ADC mode)
 ADC->CCR |= ((uint32_t)0x00002000)
Murat

alisoy


Deli_Nedym

Jak çıkışından ses alabilen var mı bu stm32f4'te ??
Amplifikatör bağlayıp yapıyoruz ama sonuçta bi ses çıkışı için jak koymuşlar neden kullanamıyoruz bunu veya da nasıl kullanıyoruz bilen var mı ?

Klein

O çıkış CS43L22 Audio dac / D class amplifier  çipine bağlı.  Eğer bu çipin nasıl çalıştığını biliyorsanız, kulaklık çıkışını kullanabilirsiniz. 

mesaj birleştirme:: 24 Kasım 2012, 14:37:12

STM32F4Discovery için ST'nin yayınladığı örneklerde ses çıkışı ile ilgili örnek var.
Evet gayet temiz bir ses çıkışı var.

Deli_Nedym

O örnekleri nereden bulabiliriz hocam acaba ?


Deli_Nedym

Teşekkürler sağolasın kolay gelsin  :)