STM32F4 DMA + GPIO ile Senkronize transfer nasıl yapılır?

Başlatan Mucit23, 25 Nisan 2017, 17:00:42

devrecii

Dostum direk fonksiyonu koda eklersen o fonksiyonu çağırmaz ,  fonksiyon adresinin vektör tablosuna eklenmesi gerekiyor
asm de direk ilgili yere yazdığında iş bitiyor ama hal de cubemx de nvic setting butonu var oraya girip dmainteerupt enable dersen sth_it.c dosyanın içinde kendisi o fonksiyonu veriyor.

Dma kannalrının tek interruptu var ama interrut registeri var resigterde inteerupun hangi amaçla çağrıldığı bilgisi bu resiterle ulaştırılıyor eğer registerin o bitini reset yapmazsan bir daha interrrupu çağırmaz

 HAL_DMA_IRQHandler  bu o işi yapıyor  sen registerdeki bilgiyi alıp  dmahalf complete full complete  yada neyse onu öğrenip ayrı bir fonksiyon çağırablirsin.

 Timeri bir registerinde istersen update yada pwm compre ayrı ayrı set ederek dma ya clock ulaştırabilrisin .
 

Mucit23

@iboibo açıklama için teşekkürler. Fonksiyon belirtmeyi anladım gibi ama şimdilik özel fonkisyona gerek yok gibi. Öncelikli Olarak sistemin Genel olarak çalışmasını sağlamalıyım. Fakat bunu başaramadım.

Amacım Timer Uptate kesmesi oluştuğu anda DMA'nın tetiklemesi ve hafızadaki veriyi GPIO'ya taşıması. Bunun için kendimçe bir init kodu hazırladım fakat çalıştıramadım.

void HUB75_Tim_DMA_Init(void)
{
  __HAL_RCC_TIM3_CLK_ENABLE();  //Enable TIM3;
	__HAL_RCC_DMA1_CLK_ENABLE();  //Enable DMA1

  TIM_InitStruct.Instance = TIM3;
  TIM_InitStruct.Init.Prescaler = 72-1;
  TIM_InitStruct.Init.CounterMode = TIM_COUNTERMODE_UP;
  TIM_InitStruct.Init.Period = 1000-1;  
  TIM_InitStruct.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  TIM_InitStruct.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
	TIM_InitStruct.Init.RepetitionCounter = 0;

	
  if (HAL_TIM_Base_Init(&TIM_InitStruct) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
	
  DMA_InitStruct.Instance = DMA1_Channel2;
  DMA_InitStruct.Init.Direction = DMA_MEMORY_TO_PERIPH;
  DMA_InitStruct.Init.PeriphInc = DMA_PINC_DISABLE;
  DMA_InitStruct.Init.MemInc = DMA_MINC_ENABLE;
  DMA_InitStruct.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; // 16 bits
  DMA_InitStruct.Init.MemDataAlignment = DMA_PDATAALIGN_HALFWORD;
  DMA_InitStruct.Init.Mode = DMA_CIRCULAR;
  DMA_InitStruct.Init.Priority = DMA_PRIORITY_LOW;
	if (HAL_DMA_Init(&DMA_InitStruct) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
	
	__HAL_LINKDMA(&TIM_InitStruct,hdma[TIM_DMA_ID_UPDATE],DMA_InitStruct);
	
  HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 0, 0); // enable DMA IRQ
  HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);
	
  __HAL_TIM_ENABLE_DMA(&TIM_InitStruct, TIM_DMA_UPDATE);
	__HAL_TIM_ENABLE(&TIM_InitStruct);
	
	HAL_DMA_Start_IT(TIM_InitStruct.hdma[TIM_DMA_ID_UPDATE],(uint32_t)&my_buf, (uint32_t)&GPIOA->ODR, 100);
	
}

DMA Mode Circular yaptım. Yani Mantıken Timer Update kesmesi oluştuğu sürece Sürekli olarak bu işi tekrarlaması ve DMA'nın kesme üretmesi gerekir. Ama hiç kesmeye gitmiyor. Nerde hata yapıyor olabilirim? Amacım dediğim gibi Timer Update olayı ile dma yı tetiklemek.

devrecii

  Timer3 ch3 den clock alabiliyor ama update den clock alamıyor yani sen ch3 e pwm ver ve ch3 dma request biti set et
 (büyük ihtimalle cubemx yapıyordur )








Mucit23

Ben Orayı atlamışım sanırım. kanal iki Timer3'den request alabiliyor ama bunun için Tim Output Compareyi de ayarlamak lazım sanırım. Ben onu yapmadım. Hatam bu sanırım. Akşam hemen deneyeceğim. DMA kanal 3'den Update olayı ile request alabiliyor. DMA kanal 3'ü ayarlamam lazım.

crazy

Stm32f103c8 için örnek
#include "stm32f10x.h"                  // Device header

const static u16 dizi[]= {
0xff,0x00,0xff,0xff,0xff,0xff
};

const static u16 dizi_1[]= {
0x00,0xff,0x00,0x00,0x00,0x00
};


  u8 transfer_tamamlandi = 0,transfer_hata = 0,transfer_yarisi_tamamlandi = 0;;
void DMA1_Channel2_IRQHandler(void)
{
 if(DMA1->ISR & (1<<5))// kanal2 transfer tamamlandi bayragi
 {
 transfer_tamamlandi ^= 1;
 DMA1->IFCR |= (1<<5); // kanal2 transfer tamamlandi bayragini temizle
  }
 
 if(DMA1->ISR &(1<<6))//kanal2 transfer yarisi tamamlandi
 {
 transfer_yarisi_tamamlandi ^=1;
 
 DMA1->IFCR  |=(1<<6);//kanal2 transfer yarisi tamamlandi bayragi temizle
 }
 
 if(DMA1->ISR & (1<<7))//kanal2 transferde hata var bayragi
 {
 transfer_hata ^=1;
 DMA1->IFCR = (1<<7);//kanal2 transferde hata var bayragi temizle.
 
 }
}

u8 kanal_6_transfer_tamamlandi = 0;
void DMA1_Channel6_IRQHandler(void)
{
 kanal_6_transfer_tamamlandi ^=1;
  DMA1->IFCR |= (1<<21);
 
}
void Init_DMA() 
{ 
 RCC->AHBENR        |= 0x0001;    //DMA1 clock enable 

 DMA1_Channel2->CPAR  =  (uint32_t)&GPIOA->ODR;
 DMA1_Channel6->CPAR  = (uint32_t)&GPIOB->ODR;
 
 DMA1_Channel2->CMAR  = (uint32_t) dizi;
 DMA1_Channel6->CMAR  = (uint32_t)dizi_1;
 DMA1_Channel6->CNDTR = 6;
 DMA1_Channel2->CNDTR = 6;
 DMA1_Channel6->CCR  |=(1<<7)|  // hafiza artirma modu acik
                      (1<<5)|  // circular mod acik
                      (1<<4)|  //okuma hafizadan yapilacak  (diziden)
                      (1<<10)|  //16 bit
                      (1<<8) |  //16 bit
                      (1<<1)|  // transfer tamamlandi kesmesi aktif
                        (1<<0);  //dma1 kanal6 aktif
 
 DMA1_Channel2->CCR |= (1<<7)|  // hafiza artirma modu acik
                      (1<<5)|  // circular mod acik
                      (1<<4)|  //okuma hafizadan yapilacak  (diziden)
                      (1<<10)|  //16 bit
                      (1<<8) |  //16 bit
                      (1<<1) |  //transfer tamamlandi kesmesi aktif
                      (1<<2)|  //tranfer yarisi tamamlandi kesmesi aktif
                      (1<<3)|  //transfer hata kesmesi aktif
                        (1<<0);  //dma1 kanal2 aktif
 
 
 NVIC->ISER[0]=        (1<<12);  //DMA1 kanal2 global interrupt aktif    
 NVIC->ISER[0]=        (1<<16);  //DMA1 kanal6 global interrupt aktif    
 

}

void Init_TIM()
{
 RCC->APB1ENR  |= 0x00000003;                          /* Timer2- 3 clock enable                  */
 TIM3->CCER    |= 0x0101;                              /* Compare 1-3 output enable                */
 TIM3->CCMR1    |= 0x0068;                              /* PWM mode 1 (CH1)compare 1 preload enable */
 TIM3->CCMR2    |= 0x0068;                              /* PWM mode 1 (CH3)compare 3 preload enable */
 TIM3->DIER    |= 0x0A00;                              /* Compare 1-3 DMA request enable          */
 TIM3->PSC      = 0;                                    /* Prescaler                                */
 TIM3->ARR      = 71;                                  /* Period                                  */
 TIM3->CCR1      = TIM3->ARR/2;                          /* CH1 duty                                */
 TIM3->CCR3      = TIM3->ARR/2;                          /* CH3 duty                                */
 TIM3->CR1      |= 0x0081;                              /* counter enable                          */ 
}

void Init_GPIO()
{
 RCC->APB2ENR |= 0x0000000D;                              /* PORTA-B AF clock            */
 GPIOA->CRL    = 0x33333333;                              /* PA0-7 gpio output push pull */
 GPIOB->CRL    = 0x33333333;
 
}

int main()
{
 
 Init_GPIO();
 Init_TIM();
 Init_DMA();
 
 
 
 
 while(1)
 {
 
 
 }
 
}

Mucit23

Teşekkürler. Yakın zamanda HAL library ile bende aynı işi yaptım. Test ediyorum. Toparlayınca paylaşacağım.

Mucit23

Dediğim gibi bu işi yapmıştım ama ozaman ufak bir sorun yaşamıştım. Araya başka işler girince kaldı öylece. Şimdi  @crazy yazınca tekrardan bakayım dedim. Ozaman şöyle bir sorun yaşadım. Umarım anlatabilirim telefondan yazıyorum.

Dma yı bildiğiniz gibi Timerin UPDATE olayı ile tetikliyoruz. Dma her tetiklendiğinde sıradaki veriyi alıp GPIOya yazıyor. Clock sinyalini ise Timerin OC kanalı ile üretiyoruz. OC birimini PWM1 modunda çalıştırıyorum. ARR/2 Değerinde çıkış lojik 0 dan lojik 1 e yükseliyor. Gayet normal fakat sorun şuki DMA nın transfer yapabilmesi için bir kere tim update olayı olması lazım. Bu süre içerisinde Timer ve OC ikilisi bir clock sinyali üretiyor. Fakat henüz tim update olayı gerçekleşmediğinden dma veri aktaramıyor dolayısıyla sadece boş clock üretilmiş olunuyor. 8 bitlik veri için 9 tane saat darbesi üretiliyor. Bu şekilde veri aktarılmış olunuyor

En son DMA Transfer Complete kesmesi oluştuğunda sürekli clock oluşmaya devam etmemesi için timeri durduruyorum. (@crazy senin kodunu inceledim böyle birşey yapmamışsın. Bu şekilde hatalı çalışabilir.)

Bahsettiğim gibi bir sorun var. Aşağıda osiloskop ile yakaladığım görüntü var.

Burada ilk clock darbesinde data aktarılmıyor sonraki clock darbeleriyle 9 bitlik veri geliyor bu 9 bitlik veri ise aktardığım paralel verinin en düşük biti  :)

Sonuç olarak ben 9 bitlik yada 9 byte lık veri aktarabilmem için dma ya veri boyutunu bir fazla girmem gerekiyor. Baştaki boş clock ise sorun yaratacak bana.

Bunu nasıl çözebilirim. Fikri olan varmı

crazy

Hocam sizin kadar anlamıyorum,hobi amaçlı takılıyorum ben farklı zamanlarda 12 adet pinden  pwm üretmek için kullanmıştım.Gayet güzel çalışıyor.Değişken tanımlarken dizinin birini u16 diğerini u32 tanımladığım için bir sorun olmuştu.
Alıntı YapDMA1_Channel6->CNDTR = 6;
 DMA1_Channel2->CNDTR = 6;
Eğer iki kanal kullanıyorsanız,aktarılan veri sayısı farklı olduğu için olabilirmi.Dediginiz kanallarin transfer hizlarini degistirmeyi  istedigimde  kanal 2 _6 yi TIMx CCRx durtuyor, ama  TIMx_CCRx lere farkli deger verdigimde veri  transfer hizinda  bir degisiklik olmuyor.

Mucit23

Çözdüm sayılır. Çözüm basit. Transferi başlamadan önce TIM->CNT değerini ARR-1 olarak giriyorum. Dolayısıyla transferi başlattıktan sonra ilk clock darbesinden sonra tim_update olayı oluşuyor ve dma ilk aktarımı yapıyor. Dolayısıyla boş clock üretilmemiş olunuyor. Düşük hızlarda iyi çalışıyor fakat yüksek hızlarda pek verim alamadım sebebide şudur.

Dma çalışma modunu Circular Yerine Normal yaptığımda Dma transferi tamamladıktan sonra duruyor fakat Timer durmuyor. Timer ve OC ikilisi boş clock üretmeye devam ediyor. Bunu DMA TC kesmesinde timeri durdurarak çözdüm. Fakat bu seferde yüksek hızlarda özellikle 1mhz den sonra DMA TC Kesmesi oluşup timer kapatılana kadar birkaç clock darbesi daha üretiliyor. Anlaşılan o ki donanım desteği olmadan verim almak zor. 1mhz clock hızı bile bana güven vermedi STM32F103 için. Şimdilik osiloskop ile deneme yapıyorum lojik analizatör ile ayrıntılı inceleme yapmam lazım. Fakat 700 KHz ve altı uygulanabilir görünüyor.

Belki F4 ile daha iyi sonuçlar alınabilir. Fakat pek aklıma yatmadı. 700kb/sn ile full color Rgb panel sürülebilirmi onu düşünüyorum. Yapabilirsem mükemmel olur.

Daha iyi optimizasyon önerileri olan varmı?



devrecii

@Mucit23 pwm kanali ile DMA ya ulaşsaydın ya update değil, diğer bir kanalı da çıkışa clock olarak verseydin.

Clockta söyle bişey var DAta gönderilir hazır olur sonra da clock gönderilir. Haberleşme Böyledir genelde.

Saniyede Max kaç hız alabildin. 

Mucit23

@iboibo Dediğin mantıklı olabilir denemem lazım. DMA'yı CC1 den tetik alacak şekilde ayarlasak ve CC2 yi de yine ARR/2 oranında clock üretecek şekilde ayarlasam timer ile uğraşmam herhalde. Ama yinede yüksek hızlara çıkamıyoruz. Çünkü kesme içerisinde tim ox kanalını kapatmam gerekiyor. Buda sorun yaratıyor. Şimdilik 1Mhze kadar denedim. Daha yüksek hızlarda (5-6Mhz) saçma sapan çalışıyor. 1mhz clock hızında bile pek güven vermedi açıkçası test etmem lazım.

Clock üretimi dediğin gibi yapılıyor zaten yukarıda eklediğim osiloskop görüntüsünde görebilirsin

Aslında biraz daha optimizasyon yapılırsa güvenli bir şekilde 1 mbyte/sn aktarım hızına ulaşılabilir

devrecii

DMA dursa bile extra bikaç clock gitmesi ciddi bir problem söyle bişey olabilir ;

Başka timer bloku kullanarak çıkışında pwm alırsın  bu pwm sinyali uzunluğu tam göndermek istediğin clock sayısına göre olur çıkışa normal clock sinyali ile AND kapısı koyarsın.

Start ettiğinde her iki timeri start edersin bu timer 1 durumunda başlar ve bir süre sonra 0 konumuna düşer esas clock
sayıma devam etse bile clock çıkışı durur tekrar ayarlamaları yapacak yeterince zaman verir sana.