STM32 DMA ve Timer PWM Kullanımı

Başlatan Mucit23, 30 Ekim 2020, 00:18:54

Mucit23

Selamlar.

Timer ve PWM kullanarak sabit bir ses dosyasını çalmaya çalışıyorum. Küçük bir wav dosyam var bunu Hex koduna çevirip programa gömdüm. Basitçe istediğim herhangi bir zamanda DMA ve Timer kullanarak dataları pwm aracılığıyla dışarı veriyorum. Bu uygulama daha çok sinüs sinyal üretmede vs kullanılıyor. Bende onu denedim. DMA Circular moddayken gayet iyi bir şekilde çalışıyor. Ama DMA normal Modda çalışıyorken sorun var.

Amacım şu. Herhangi bir anda Timer ve DMA'yı çalıştırarak buffer'daki ses verilerin Timerin PWM duty registerine yazılmasını sağlamak. Bütün Transfer tamamlandığında Timer ve DMA kendini kapatsın ve sonraki tetik verişimi beklesin.

DMA mantıken normal modda çalışması gerekiyor. Denedim ama düzgün çalıştıramadım. Init kodlarım aşağıdaki gibi

void TIM_PWM_Sound_Configuration(void)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  TIM_OCInitTypeDef  TIM_OCInitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;
  DMA_InitTypeDef DMA_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
  /* GPIOA Configuration: Channel 1 as alternate function push-pull */
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_0;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
	
  /* DMA1 Channel2 Config */
  DMA_DeInit(DMA1_Channel2);

  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)TIM2_CCR1_Address;
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)sound_data;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
  DMA_InitStructure.DMA_BufferSize = 256;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

  DMA_Init(DMA1_Channel2, &DMA_InitStructure);

  /* DMA1 Channel5 enable */
  //DMA_Cmd(DMA1_Channel2, ENABLE);

  /* TIM1 Peripheral Configuration --------------------------------------------*/
  /* Time Base configuration */
  TIM_TimeBaseStructure.TIM_Prescaler = 35-1;//8Khz
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseStructure.TIM_Period = 256-1;
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
	
  /* Channel 1 Configuration in PWM mode */
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
  TIM_OCInitStructure.TIM_Pulse = 0;
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
  TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;
  TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
  TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
  TIM_OC1Init(TIM2, &TIM_OCInitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 15;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	NVIC_Init(&NVIC_InitStructure);
	
	DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);
  /* TIM2 Update DMA Request enable */
  TIM_DMACmd(TIM2, TIM_DMA_Update, ENABLE);
  /* TIM2 counter enable */
  //TIM_Cmd(TIM2, ENABLE);
	
}

Kesme içine girdiğimde aşağıdaki şekilde DMA'yı ve Timeri Kapatıyorum.
void DMA1_Channel2_IRQHandler(void)
{
	GPIOB->ODR^=GPIO_Pin_2; //Toggle Led
	TIM_Cmd(TIM2,DISABLE);
	TIM2->CCR1=0;
  DMA_Cmd(DMA1_Channel2, DISABLE);	
	  /* DMA1 Channel5 enable */
	DMA_ClearITPendingBit(DMA_IT_TC);
}

Tetik verdiğimde bir kereliğine sistem çalışıyor. Buffer'daki veriyi Sırayla PWM duty Cycle registerine atıyor. Bittiğinde DMA TC kesmesi oluşuyor. KEsme içindeki kodları çalıştırıyor ve PWM'in durduğunu görüyorum. Tekrar transfer olmuyor. Ama sürekli DMA TC kesmesine giriyor sistem. Ayrıca Tekrar tetik verirsem DMA ve Timer çalışmıyor. Transfer yapılmıyor. Resetleyince Aynı durum başa dönüyor.

Çözemedim tam olarak. DMA Normal modda kullanılıyorken DMA TC kesmesinde DMA'nın ve Timerin kapatılması doğru bir kullanılması mı? Bu işin doğrusu nasıl olmalı?

RaMu

#1
Net bilmiyorum biraz DMA PWM çalıştım ama bende circular mod çalıştırdım hep.

DMA start pwm (timer da olabilir) it
dma stop pwm (timer) it
gibi fonksiyonlar var, onlarla yapılması daha doğru gibi.
Şu olabilir:
HAL_DMA_Abort_IT

tim2 kanal  PWM çıkışı,
tim3 ilede DMA tetiklenip PWM duty değerini tutan 512 byte buffer CCR4 e gönderiliyor,
main de sonsuz döngüden önce şöyle açmışım ama
sonra kapatmamışım, geçen sene uğraşmıştım kodun son halini bulamadım.

kodları ekleyince 403 hatası veriyor forum?
Sorularınıza hızlı cevap alın: http://www.picproje.org/index.php/topic,57135.0.html

OG

üstte code tag denemesi yaptım, sorun çıkarmadı.
FORUMU İLGİLENDİREN KONULARA ÖM İLE CEVAP VERİLMEZ.

Mucit23

Alıntı yapılan: RaMu - 30 Ekim 2020, 01:37:24Net bilmiyorum biraz DMA PWM çalıştım ama bende circular mod çalıştırdım hep.

DMA start pwm (timer da olabilir) it
dma stop pwm (timer) it
gibi fonksiyonlar var, onlarla yapılması daha doğru gibi.
Şu olabilir:
HAL_DMA_Abort_IT

tim2 kanal  PWM çıkışı,
tim3 ilede DMA tetiklenip PWM duty değerini tutan 512 byte buffer CCR4 e gönderiliyor,
main de sonsuz döngüden önce şöyle açmışım ama
sonra kapatmamışım, geçen sene uğraşmıştım kodun son halini bulamadım.

kodları ekleyince 403 hatası veriyor forum?

Benim anlamadığım DMA kesme kaynağı ne oluyor? Neden sürekli kesme içerisine giriyor? DMA'yı tetikleyen timer değil mi? Zaten Timeri Durduruyorum. Mantıken sadece Timerin durması ve sonrasında tekrar Timeri çalıştırmam yeterli olması lazım. Nerde hata yapıyorum?

RaMu

@OG @gevv ilgileniyor, benimle ilgili herhalde sorun.
Kişisel mesajlarada kod ekleyemedim.
Ekleyemediğim kodlar şunlar:
https://controlc.com/aa26330e

Kod tag kullansamda kullanmasamda 403 hatası veriyor.
Sorularınıza hızlı cevap alın: http://www.picproje.org/index.php/topic,57135.0.html

JKramer

HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_4);

 hdma_tim3_ch4_up.XferHalfCpltCallback = my_dmat3c4_halfcc;
 hdma_tim3_ch4_up.XferCpltCallback    = my_dmat3c4_fullcc;

 status = HAL_DMA_Start_IT(&hdma_tim3_ch4_up, (uint32_t)dma_wav_buf, (uint32_t)&htim2.Instance->CCR4,512);
 iff(status != HAL_OK)  while(1);

  __HAL_TIM_ENABLE_DMA(&htim3, TIM_DMA_UPDATE);  // Enable the TIM Update DMA request
  __HAL_TIM_ENABLE(&htim3);  // Enable the Peripheral
if clause olursa 403 hatası veriyor, iff yapınca sorun yok :)

Mucit23

@JKramer Bu yapıda sorun varmı? Bende benzer bir işlem yaptım aslında. DMA TC kesmesi olduğu anda sistemi durdurmam lazım. Durdurma işlemini nasıl yapıyorsun?

RaMu

@JKramer in eklediği kodlar benim eklemeye çalışıp hata aldığım kodlar.
Sorun yok wav player için kullandığım kodlar.
Kapatma kısmını yapmamışım.
Sorularınıza hızlı cevap alın: http://www.picproje.org/index.php/topic,57135.0.html

Mucit23

#8
Bu iş HAL kütüphane ile çok güzel oluyor. Ama HAL'in yapısını anlayamıyorum. Muhtemelen benim DMA ve Timer ilişkilendirmemde sorun var. @RaMu verdiğin kodu denedim. STD library'deki hatam DMA kesme bayrağını resetlerken yanlış bayrağı resetlememdi.

DMA_IT_TC yerine DMA1_IT_TC2 olması gerekiyormuş. Yani Sürekli DMA kesmesine girme sorunu çözüldü. Olması gerektiği gibi Transfer tamamlandığında kesme içine giriyor. Flağı resetleyip çıkıyorum.

Şimdi DMA Circular Modda Güzel çalışıyor Fakat DMA Normal Moddayken Sadece init'ten sonra bir kere transfer yapıp duruyor. Tekrar başlatamadım. Bunu çözmem gerekiyor.






Mucit23

Sorumu şöyle güncelleyeyim. DMA Normal Modda çalışıyorken Transfer Tamamlandıktan sonra Suspend konumuna geçiyor. Aynı işlemi tekrar baştan yapması yani dma'yı Restart yapmak için ne yapmak gerekiyor?

magnetron

ADC_SoftwareStartConvCmd(ADCX, ENABLE);

bu komut işe yarar mı

RaMu

Alıntı yapılan: Mucit23 - 31 Ekim 2020, 12:51:19Bu iş HAL kütüphane ile çok güzel oluyor. Ama HAL'in yapısını anlayamıyorum.
...
Benimde başlangıçta çok anladığım söylenemez.
Onlarca pwm ve veya dma ile alakalı bulduğum youtube videolarını izleyip,
birçok benzer işi yapan kodları inceleyip ufak değişikliklerle deneyip,
referance manual vb. dökümanları tekrar tekrar irdeledikten sonra
kafamda ufacık bir şeyler canlanmaya başlamıştı.
Özellikle referance manual de nispeten güzel bahsediyor ama
st dökümanları sanki eksik ve bölük pörçük,
onu bir yerden buluyorsun başka kısım bambaşka dökümanda biraz bahsedilmiş,
en basit ve can alıcı noktadan hiç haber yok...

Kısaca ısrarcı olunca çözülüyor.
Bunları sende biliyorsunda yinede üstüne gidip çözmen için bir de ben söyleyeyim istedim.
Donanım çok kapsamlı yapacak bir şey yok, kurcalayacağız.

Birkaç tane audio player wav player tarzı örneği incelesen bir yerinde vardır istediğini nasıl yaptıkları.
Benim denediğim bir örnek varda bulamadım hangisiydi,
bulursam ekleyeceğim.
Oynat, durdur, aç, kapat, sıradaki gibi kısımları vardı,
bu kısımlarda tamda istediğini yapıyordu.
Sorularınıza hızlı cevap alın: http://www.picproje.org/index.php/topic,57135.0.html

Mucit23

Sorunu çözdüm gibi. Elimde Çalışan Hal örneği olunca aynısını STD Library'de yapmak zor olmadı. Sorunun sebebi şuymuş.

DMA Normal modda çalışıyorken Transfer sonrası Suspend durumunun geçmesi için Buffer boyutunun yeniden girilmesi gerekiyor. Aynı zamanda Global interrupt dahil bütün flagların temizlenmiş olması lazım.

Sebebi şu. DMA_CCRx üzerinden DMA aktif edildikten sonra DMA ilk request ile birlikte çalışmaya başlar. Buradaki uygulama için DMA'ya istekte bulunan TIM_Update olayı. DMA her TIM Update Olayında bir adet Transfer yapar ve DMA_CNDTRx  registerini yani buffer boyutunu -1 Yapar. Yani Geriye doğru sayıyor.
CNDTR Registeri 0 olduğunda bütün veri aktarılmış olur ve DMA Transfer Complete Kesmesi oluşur. Bir Sonraki Request'de eğer CNDTR  registeri sıfır ise aktarım yapılmaz. Tekrar aktarım yapılması için CNDTR  registerinin tekrar init değerine yüklenmesi lazım. İşte DMA_Circular Modda bu işlem otomatik olarak yapılır. Tek fark bu anladığım kadarıyla. Yani Eğer DMA Circular moda çalışıyorsa CNDTR Registeri 0 olunca DMA TC kesmesi oluşur ve CNDTR registeri otomatik olarak reload yapılır.

Sonuç olarak DMA eğer normal modda çalışıyorken Aktarım sonrası tekrar aktarım yapılması için

İlk Önce DMA_CCRx üzerinden DMA kapatılmalı.
Daha sonra DMA_CNDTRx Registeri ile Aktarım boyutu tekrar girilmeli
Son olarak yine DMA_CCRx üzerinden DMA Aktif edilmeli.

DMA aktif edildikten sonra gelen ilk request ile birlikte aktarıma başlayacaktır.

STD Library için ben aşağıdaki şekilde yaptım.

DMA_Cmd(DMA1_Channel2, DISABLE);
DMA1_Channel2->CNDTR=256;
DMA_Cmd(DMA1_Channel2, ENABLE);

Kenarda kalsın böyle lazım olur belki birine