DMA KONUSU VE NUVOTON M480 ÖRNEĞİ

Enes ALTUN
6 min readMay 9, 2021

DMA(Direct Memory Acces), yani türkçe ismiyle Doğrudan Bellek Erişimi hakkında bilgi sahibi olmak istiyorsanız ve bu yazıya denk gelmişseniz,bingo! Çünkü DMA kavramını öğrendiğiniz zaman bu muhteşem özelliği kullanmadan proje yapmak istemeyeceksiniz. Hatta mikrodenetleyici seçimi yaparken bu özelliği arayacaksınız. Ne yazık ki her mikrodenetleyici içerisinde bu özellik yok. Peki nedir bu DMA kavramı, nerelerde işime yarar, bana katkısı nedir gibi sorularınızı bu yazımda cevaplayıp sevdiğim bir işlemci markası olan Nuvoton ailesine sahip M487JIDAE üzerinde örneklerini yapacağım.

Şimdi durumu daha iyi kavramanız açısından durumu benzetme kullanarak yapacağım. Mikrodenetleyicilerde bildiğiniz üzere CPU yani Türkçe adıyla Merkezi İşlem Birimi bulunur. Bu CPU’yu bir marketçiye benzetelim. Mikrodenetleyicinin kendiside market olsun. Datalarımız ise market içerisindeki ürünler olsun. Bu durumda raflardaki ürünler aslında hafızadaki datalar olacak. Bu marketçi sürekli müşterileri karşılıyor ve istedikleri ürünleri belli raflardan çekip çekip müşterilere veriyor. 10 dakikada bir müşteri geldiğine göre ve her bir müşteri yaklaşık 5 dakika marketçiyi meşgul ettiğine göre buraya kadar sıkıntı yok. Çünkü marketçi sıkışmadan işini rahatça bitiriyor. Bazen de üst kattaki komşu sepet salıyor bu sepete siparişleri dolduruyor. (Sepet örneğini UART’ta veri alışverişi gibi düşünelim). Şimdi öyle bir an düşünelim ki markete aynı anda 2 müşteri geliyor. Marketçi bu 2 müşterinin de isteklerini yapabilecek güçte. Tek çekirdek işlemciyle çalıştığımızı varsayarak marketçi bir müşteriyle ilgilendikten sonra diğerinin isteğini yerine getirmeye çalışacaktır. Sonra aniden markete 3. müşteri gelir. Marketçi bu müşteriyi de bekletmek zorundadır. Çünkü artık işlem süresi uzamıştır. Tüm bunlar yaşanırken birde üst komşu sepeti salıverir. Bu artık marketçi yani CPU için büyük bir işlem yükü haline gelmiştir. Peki simdi marketçi ne yapacaktır? Gerçek hayatta da olduğu gibi yanına bir çırak alacaktır.

Bu çırağın görevleri bellidir:

  1. Rafları düzenlemek (memory to memory) ,
  2. müşteriye raflardan ürünün teslimini sağlamak(memory to periphal),
  3. toptancıdan alınan malları rafa dizmek (periphal to memory)
  4. hatta bazen toptancıdan aldığı malı rafa dizmeden direkt müşteriye satmak (periphal to periphal).

Fakat bu çırak asla marketçiden emir almadan bu işleri tek başına yapamıyor. Yani marketçinin bir kez çırağa bu işi yap demesi lazım.

Şimdi örneğimize geri dönersek marketçinin sadece çırağa sepete bak demesi yeterli olacaktır. Daha sonra kendisi mevcut işlerine devam edecek ve o işlerini yaparken aynı anda sepet işlemi de çırak tarafından yapılacaktır. İşlem bittiğinde ise çırak gelip “bitti usta!” diyecektir. İşte çırağın söylediği bu cümle CPU’da bir kesmeyi ifade eder. Data transfer işlemi bittiğinde bir kesme oluşur. Bizde kesme fonksiyonu içerisinde belirlediğimiz bir değişken yardımıyla işlemin bitip bilmediğini kontrol edebiliriz.

Tahmin ettiğiniz üzere DMA bu örnekte çırak olmakta. Çırağın işlemleri kendi kafasına göre yapmamasının ve marketçiden komut beklemesinin sebebi ise DMA’nın başlayabilmesi için CPU’dan bir komut almak zorunda olmasıdır. Ardından CPU işine devam edecek ve istenilen işlemi DMA, CPU yardımı olmadan yapacaktır.

Transfer Modları

DMA, temel mod(Basic Mode) ve dağıtıcı-toplayıcı mod(Scatter-Gather Mode) olmak üzere iki moda ayrılmaktadır.

Temel Mod (Basic Mode)

Temel mod DMA sistemlerinde kod blokları belirli bir fiziksel adresten diğer belirli bir fiziksel adrese sıralı bir şekilde aktarılmaktadır. Her bir kod bloğunun transfer işlemi bittiğinde kesme(interrupt) oluşmakta ve diğer kod bloğu işlemi için izin beklenmektedir.

Temel Mod data transfer örneği

Dağıtıcı-Toplayıcı Mod (Scatter-Gather Mode)

Dağıtıcı-toplayıcı modu temel mod DMA’lara göre daha karmaşık bir yapıya sahiptir. Bu modda dataların fiziksel adresleri sıralı gelmemektedir. Datalar DMA tarafından uygun alanlara dağıtılarak ardından bu alanlardan toplanmaktadırlar. Böylece büyük boyutlu datalar hafızada kargaşaya neden olmadan otomatik bir şekilde hafızada yer bulacaktır. Büyük dataların bloklara bölünme ihtiyacı olmadığı için sürekli kesme oluşturmayacaktır. Bu özellikte büyük dataların transfer işleminde dağıtıcı-toplayıcı modu üstün kılmaktadır.

Dağıtıcı-toplayıcı mod data transfer örneği

Transfer Tipleri

M487JIDAE içerisinde bulunan PDMA(peripheral direct memory access), tekli transfer(single transfer) tipi ve sürekli transfer (burst transfer) tipi olmak üzere iki farklı transfer tipini desteklemektedir.

Tekli Transfer Tipi(Single Transfer)

Tekli transfer tipi seçildiğine her bir data transfer işlemi için CPU’dan komut beklemektedir. Transfer işlemi bittiğinde ise kesme gerçekleşmektedir ve diğer işlemler için CPU’dan komut bekleyecektir.

Sürekli Transfer Tipi(Burst Transfer)

Sürekli transfer tipinde DMA, CPU’dan bir komut aldığında burst transfer genişliği kadar datayı sürekli şekilde transfer edecektir. Transfer işlemi bittiğinde ise kesme gerçekleşmektedir ve diğer işlemler için CPU’dan komut bekleyecektir.

Şimdi iki transfer tipini daha yakından anlamak amacıyla bir örnekle anlatayım.

Kanal1 tekli transfer kullanıyor olsun ve transfer sayısı 127 olarak ayarlayalım. Kanal0 ise sürekli transfer tipine, 128 burst transfer genişliğine ve 256 transfer sayısına sahip olsun. Böylece işlemler aşağıdaki şekilde olacaktır.

· Kanal1 ve kanal0 için aynı anda başlat komutu verilmiştir.

· Default ayarda kanal1 kanal0’dan daha öncelikli olduğu için transfer işlemine ilk kanal1 başlayacaktır. Kanal1 tekli transfer seçildiği için 1 transfer datası kadar işlem yapacaktır.

· Ardından PDMA kontrolcüsü kanal0 ‘a geçiş yapacaktır. Kanal0 sürekli transfer işleminde olduğu için 128 adet datayı tek bir adımda transfer edecektir.

· Kanal0 128 adet datayı transfer ettiğinde PDMA kontrolcüsü kanal0’a geçiş yapacaktır ve transfer işlemi için CPU’dan başlat komutu bekleyecektir.

· Kanal1 1 baytlık datayı 127 kez transfer ettiğinde ve kanal0 128 baytlık datayı 256 kere transfer ettiğinde bütün transfer işlemi tamamlanmış olacaktır.

Örnek Kod

NuMaker-PFM-M487 deneme kiti üzerinde hazırladığım bu yazılımda 0’dan 250’ye kadar bir sayaç saydırıp bu sayacın değerini DMA yardımıyla UART1 üzerinden okuyacağım.

UART0 ise printf işlemleri için kullanılmıştır. Ayrıca deneme kiti üzerinde bulunan D6 ve D7 numaralı LED’ler transfer işlemleri sırasında yan-sön yaptırılmıştır.

Öncelikle PDMA için ayarlamalar aşağıdaki şekilde olacaktır. Ben bu örnekte temel mod ve tekli transfer tipi tercih ettim. Bunun nedeni sürekli transfer tipi sadece memory to memory data transfer işleminde kullanılabilmektedir. Ben bu örnekte memory to peripheral olarak işlem yapacağım mecbur olarak tekli transfer tipi kullanıyorum.

Şimdi gelelim örneğimize, öncelikle PDMA ayarlamalarını aşağıdaki şekilde giriyoruz.

PDMA_Open(PDMA,1 << 0); // DMA Kanal 0 için UART1 TX ayarla

PDMA_SetTransferMode(PDMA,0, PDMA_UART1_TX, 0, 0); //DMA Kanal 0 için “Temel Mod” ayarla

PDMA_SetTransferCnt(PDMA,0, PDMA_WIDTH_8, strlen(str)); //DMA Kanal 0 için str uzunlugu kadar 8bit(PDMA_WIDTH_8) data gönder

PDMA_SetTransferAddr(PDMA,0, ((uint32_t) (&str[0])), PDMA_SAR_INC, UART1_BASE, PDMA_DAR_FIX);//DMA Kanal 0 için kaynak-hedef adreslerini ve niteliklerini belirle

PDMA_SetBurstType(PDMA,0, PDMA_REQ_SINGLE, 0);//DMA Kanal 0 için transfer tipini single olarak belirle.

PDMA_EnableInt(PDMA,0, 0);//DMA Kanal 0 için kesme aktiflestir.

NVIC_EnableIRQ(PDMA_IRQn);//NVIC kesmesini DMA için aktiflestir.

FLAG_ISLEM_BITTI = 0;//Islem bayragini sifirla.PDMA kesmesi geldiğinde bu bayrak 1 oluyor.

daha sonra UART ve GPIO ayarlamalarını yapıyoruz.

void UART0_Init(){

UART_Open(UART0, 115200); //UART0 115200 baudrate olacak sekilde ac.

}

void UART1_Init(){

UART_Open(UART1, 115200); //UART1 115200 baudrate olacak sekilde ac.

}

GPIO_SetMode(PH, BIT0, GPIO_MODE_OUTPUT);

GPIO_SetMode(PH, BIT1, GPIO_MODE_OUTPUT);

Sonsuz döngü içerisine aşağıdaki kodu yazıyoruz.

while(1){

UART_reloadPDMATest();

PH0 = 0; PH1 = 1;

CLK_SysTickDelay(1000000); CLK_SysTickDelay(1000000);CLK_SysTickDelay(1000000);

PH0 = 1;PH1 = 0;

CLK_SysTickDelay(1000000); CLK_SysTickDelay(1000000);CLK_SysTickDelay(1000000);

}

Sonsuz döndü içerisinde LED’ler yan-sön işlemi yaparken aynı zamanda UART_reloadPDMATest() isimli fonksiyon içerisinde sayaç değeri arttılacak ve bu değer PDMA yardımıyla UART1 üzerinden okunacaktır. Ayrıca FLAG_ISLEM_BITTI isimli değişken ile transfer işleminin bitip bitmediği kontrol edilecek, eğer bittiyse transfer işlemi tekrar başlatılacaktır. UART_reloadPDMATest() fonsiyonu şu şekildedir:

if(FLAG_DMA_BASLAT==1){

deneme_counter++;

if(deneme_counter>250) { deneme_counter=0; }

sprintf(str, “Deger:%u\n”, deneme_counter);

printf(“uzunluk : %d \n”,strlen(str));

ReloadPDMA(); // PDMA tekrar başlat

NVIC_EnableIRQ(UART1_IRQn);

UART1->INTEN |=UART_INTEN_TXPDMAEN_Msk;

FLAG_DMA_BASLAT=0;}

if(FLAG_ISLEM_BITTI == 1){

UART1->INTEN &= ~UART_INTEN_TXPDMAEN_Msk;

FLAG_DMA_BASLAT=1; }

Proje çıktısı ise aşağı şekilde olacaktır.

UART1 :

UART0 :

Projeye ait kodu GitHub hesabımda bulabilirsiniz. Umarım bu yazım size yardımcı olabilmiştir. Size bol DMA’lı projeler diliyorum :)

--

--

Enes ALTUN

“Learn from yesterday, live for today, hope for tomorrow.”