O DMA czyli Direct Memory Access, każdy z nas pewnie słyszał lub przynajmniej obiło mu się to o uszy. Jakieś tam szybkie operacje na pamięci i na tym nasza przeciętna wiedza się kończy. Ale skąd niby mamy mieć na ten temat wiedzę skoro w AVR-ach (zakładam , że każdy hobbysta staruje z poziomu AVR-a) tej funkcjonalności długo by szukać no dopiero w zapomnianych przez świat AVR XMEGACH (swoją drogą bardzo fajnych mikrokontrolerach).
W mikrokontrolerach PIC24 i PIC32 (do 8bitowców nie zaglądałem) DMA jest na porządku dziennym. W mikrokontrolerze, który obecnie poznaję czyli PIC24HJ128GP502 mamy 8 kanałów DMA potrafiących "gadać" nie tylko z pamięcią ale również z peryferiami takimi jak: UART, SPI, ADC, ECAN, IC, OC, INT0.
Tu trochę bardzo podstawowej teorii o DMA z Wikipedii https://pl.wikipedia.org/wiki/Direct_Memory_Access aby wyrobić sobie ogólny pogląd.
Aby bardziej przybliżyć jak działa DMA rozważmy najpierw jak jest realizowany normalny proces skopiowania jednej komórki pamięci z jednego adresu pod inny adres. W tym przypadku Procesor na magistralę adresową wystawia adres komórki, która ma być odczytana i wysyła żądanie odczytu. Pamięć lub układ peryferyjny udostępnia żądany bajt informacji na magistrali danych.
Procesor kopiuje ten bajt do jednego ze swoich rejestrów roboczych WREG0-WREG15. Następnie procesor na magistralę adresową wypisuje adres komórki, do której mają trafić dane. Na magistralę danych kopiuje wartość rejestru WREG0-WREG15, który przechowuje interesujące nas informacje, po czym wysyła sygnał zapisu. Adresy komórek źródłowych i docelowych również są przechowywane w rejestrach roboczych WREG0-WREG15.
Powyższa operacja, ma dwie wady. Trwa dość długo i całkowicie pochłania rdzeń procesora, przez co nie może on wykonywać żadnych innych operacji. Kopiowanie dużych bloków pamięci może na długi czas zablokować procesor. I tutaj przychodzi nam z pomocą układ DMA.
Zadaniem DMA jest skopiowanie określonej liczby bajtów z jednego miejsca w drugie miejsce bez angażowania procesora. W tym czasie procesor może wykonywać inne operacje całkowicie bez utraty funkcjonalności.
Co się stanie w sytuacji, kiedy procesor będzie chciał uzyskać dostęp do pamięci, w czasie kiedy DMA transferuje dane? Wówczas praca DMA zostanie automatycznie zawieszona, a kiedy procesor zakończy swoje działanie, DMA będzie kontynuować pracę.
Oprócz zastosowań typowych, jak kopiowanie danych z jednej tablicy do drugiej, DMA bardzo dobrze współpracuje z innymi układami peryferyjnymi mikrokontrolera. DMA potrafi przesyłać dane z tablicy do układu interfejsowego UART czy SPI, dzięki czemu całkowicie sprzętowo i bardzo szybko można przesłać duży blok danych pomiędzy pamięcią a urządzeniem peryferyjnym . W podobny sposób istnieje możliwość odbierania danych z układów peryferyjnych i przesyłania ich do pamięci.
DMA równie dobrze współpracuje z przetwornikiem analogowo-cyfrowym i cyfrowo-analogowym. Na przykład, pomiar przetwornikiem może być inicjowany timerem w zadanych odstępach czasowych, a po zakończeniu pomiaru, DMA może kopiować wyniki pomiaru z przetwornika do tablicy w pamięci, którą procesor przetwarza w czasie rzeczywistym.
Poniżej rysunek poglądowy dla obiegu danych DMA w naszym mikrokontrolerze PIC24HJ :
Przyjrzyjmy się rejestrom , które są potrzebne do działania z DMA i poszczególnym bitom, które będziemy wykorzystywać. :
DMAxCON (DMA Control Register) gdzie x oznacza numer kanału od 0 do 7.
W rejestrze tym interesują nas nastepujące bity :
- bit 15 oznaczony jako CHEN (Chanel Enable Bit) uaktywniamy przed transferem lub wyłączamy kanał DMA przy czym po zakończeniu transferu danych wyłączenie (ustawienie 0 następuje automatycznie). Przed wysyłką kolejnego transferu trzeba pamiętać aby bit ustawić na 1.
- bit 14 oznaczony jako SIZE (Data Transfer Size bit) wybieramy rozmiar danych przesyłanych przez DMA, Byte (8 bitów) lub Word (16 bitów). I tu trzeba uważać bo np do przesyłania z/do UARTA musimy ustawić Byte.
- bit 13 DIR (Transfer Direction bit (source/destination bus select) ustawiamy, kierunek przepływu danych np z pamięci do peryferiów lub odwrotnie. Dla przykładu jeśli chcemy nadawać po UART (TX) to ustawiamy tutaj "1".Jeśli odbieramy dane po UART (RX) to ustawiamy bit rejestru na "0".
- bit 5-4 AMODE (Addressing Mode Select bits) adresowanie z inkrementacją lub nie. Standardowe ustawienie to "00" ale wyłączenie inkremetacji też znajduje zastosowanie np jeśli chcemy wynik z ADC tylko w jednej komórce pamięci odświerzać.
- bit 1-0 MODE (Operating Mode Select bits) ważny rejestr w którym ustawiamy tryby pracy DMA, trybów w sumie jest 3 : (OneShot - jeden transfer danych i zamknięcie kanału DMA, Continuous - zapętlony transfer danych, PingPong - sekwencja przesyłu danych z dwóch buforów/tablic) dokłady opis i rysunki działania trybów w materiałach w linku pod artykułem. Na przykład dla transmisji po UART (TX) wybieramy tryb OneShot bez Continuous i bez PinPonga-a , wpisujemy zatem do rejestru "01"
DMAxREQ (IRQ Select Register)
Rejestr ten ma dwa kluczowe zadania : ustawia jak ma być zainicjowany transfer (Manual/Automatic) i drugie zadanie to wskazanie na peryferia (na wektor przerwań od danego peryferia) z jakim będzie DMA wymieniał dane.
W rejestrze tym interesują nas nastepujące bity :
- bit 15 oznaczony jako FORCE, np dla nadawania przez UART (TX), transfer DMA musi być zainicjowany manulanie czyli bit ustawiamy na "1", w przypadku odbioru UART (RX) ustawiamy na automat.
- bit 6-0 IRQSEL tutaj wpisujemy wartość z Table 8-1 str 107 (datasheet link w materiałach pod artykułem) i np dla UART1 (TX) wpisujemy do rejestru wartość 0001100 czyli w hex 0xC.
DMAxSTA (RAM START ADDRESS Register)
tutaj wpisujemy adres początku naszego bufora z danymi do przesłania.
Ważna rzecz, kanał DMA otwieramy (DMAxCONbits.CHEN = 1)po zapisie w tym rejestrze inaczej jak podaje producent mogą być nieprzewidywalne zachowania DMA.
DMAxPAD (PERIPHERAL ADDRESS Register)
tutaj wpisujemy adres rejestru danego peryferia, adresy możemy sobie wyłuskać za pomocą operatora pobrania adresu "&" lub podane mamy je w tabelce Table 8-1 str 107 (datasheet)
Np dla UART1 (TX) rejestrem adekwatnym jest U1TXREG
DMAxSTA i DMAxSTB (RAM START ADDRESS Register)
tutaj wpisujemy adres początku naszego bufora z danymi, mamy dwa rejestry do wykorzystania czyli możemy podać adresy początków dla dwóch oddzielnych buforów.
W trybie PingPong te dwa rejestry możemy pchnąć w jednej sekwencji a w trybie OneShot
możemy alternatywnie albo jeden albo drugi pchnąć w zależności np od jakiegoś warunku.
DMAxCNT (TRANSFER COUNT Register)
tutaj podajemy ile danych chcemy przesłać z bufora, podajemy od 0 do 1023 co odpowiada wartości 1024 bajtów maksymalnie do przesłania. Kontroler DMA wykorzystuje dane z tego rejestru do inkrementowania komórek pamięci podczas transferu danych z bufora.
Przykład zapisu do resjestru może wyglądać tak :
DMA0CNT = strlen(bufor)-1; /*funkcja strlen() oblicza ilość znaków w buforze bez znaków specjalnych, trzeba zainkludować bibliotekę #include <string.h>.*/
I to by było tyle jeśli chodzi o rejestry , które w praktyce będziemy wykorzystywać jeśli chcemy działać na DMA. W sumie jest tego niewiele do ogarnięcia i jak się raz zatrybi o co biega z tym DMA to otwierają nam się drzwi do innego wspaniałego świata pełnego alternatywnych możliwośći :), gdzie mikrokontroler przez większość czasu nam leżakuje i możemy , żeby się za bardzo nie rozleniwił dorzucić mu wiadro innych zadań.
Wspomnę jeszcze dla porządku o rejestrach DMACS0,DMACS1 i DSADR ale to już wyższa półka działania z DMA, w 95 % nie ma potrzeby korzystania z tych rejestrów.
Wszelkie operacje DMA muszą być dokonywane w obszarze wydzielonej pamięci DMA RAM , pamięć ta ma w PIC24HJ128GP502 wymiar 2 kB. W jednej transmisji DMA możemy przesłać maksymalnie 1024 bajtów , przy czym np.w trybie PingPong mamy możliwość wysłania danych z dwóch buforów po 1024 bajty każdy i pchnięcie tego w jednej sekwencji - jeden bufor po drugim. Przykładowe zdefiniowanie bufora z danymi w obszarze pamięci DMA RAM wygląda tak :
char bufor[] __attribute__((space(dma)))="Transfer DMA\r\n";
I teraz jak chcemy np.ten bufor pchnąć po UART-cie to wszystkim zajmuje się kontroler DMA bez angażowania cykli procesora.
W przypadku mikrokontrolerów dsPIC33 w jednym strzale można przez DMA puścić aż 16 kB danych.
Poniżej kod z przykładem transferu za pomocą DMA, danych z bufora do UART1 (TX)
Definiujemy sobie w DMA RAM, bufor znaków i wypychamy w świat po UART, cała operacja zostanie wykonana w tle bez angażowania zasobów (cykli) procesora .
Pozdrawiam
picmajster.blog@gmail.com
Linki :
http://ww1.microchip.com/downloads/en/DeviceDoc/70309B.pdf
http://forbot.pl/blog/artykuly/programowanie/kurs-stm32-8-dma-czyli-bezposredni-dostep-do-pamieci-id8465
http://ww1.microchip.com/downloads/en/DeviceDoc/70293G.pdf
/******************************************************************************
File: ustaw_zegar.h
******************************************************************************/
#ifndef USTAW_ZEGAR_H
#define USTAW_ZEGAR_H
#define FCY 40000000 /* podajemy wartosc ustawionego zegara (40 MHz), wazne
aby przed includowaniem <libpic30.h>, potrzebne to jest do wyliczania delay-i*/
/*deklaracja funkcji*/
void ustaw_zegar(void) ;
#endif /* USTAW_ZEGAR_H */
/*******************************************************************************
File: uart.h
*******************************************************************************/
#ifndef UART_H
#define UART_H
#define BAUDRATE 9600 /*definujemy szybkosc UART-a*/
#define BRGVAL ((FCY/BAUDRATE)/16)-1
/*deklaracja funkcji*/
void Ustaw_UART1(void);
#endif /* UART_H */
/*******************************************************************************
File: ustaw_zegar.c
******************************************************************************/
/*Ustawiamy zegar wewnetrzny na ok 40 MHz (dokladnie na 39.61375 MHz*/
#include "xc.h" /* wykrywa rodzaj procka i includuje odpowiedni plik
naglówkowy "p24HJ128GP502.h"*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*dyrektywy do uint8_t itp*/
#include "ustaw_zegar.h" /*z uwagi na FCY musi byc przed #include <libpic30.h>*/
/*definicja funkcji*/
void ustaw_zegar(void) {
/*
* to co mozemy ustawic za pomoca '#pragma' jest dostepne w pliku
* xc16/docs/config_index.html
*/
#pragma config JTAGEN = OFF
// Watchdog timer enable/disable by user software
#pragma config FWDTEN = OFF
//********************Start Ustawien Zegara************************
/*
* Fcy - zegar instrukcji , Fosc - zegar rdzenia (jest zawsze dwa razy wiekszy
* od zegara instrukcji)) Ustawiamy zegar instrukcji na 40 Mhz z wewnetrznego
* oscylatora Fin=7.37 MHz w/g wzoru Fcy=Fosc/2 gdzie Fosc=Fin x (M/(N1+N2))
* gdzie M=43, N2=2, N1=2 ustawiane w rejestrach PLLFBD/PLLPOST/PLLPRE
*/
//Select Internal FRC (Fast RC Oscillator)
#pragma config FNOSC = FRC // FOSCSEL-->FNOSC=0b000 (Fast RC Oscillator (FRC))
//Enable Clock Switching and Configure
#pragma config FCKSM = CSECMD //FOSC-->FCKSM=0b01 - wlacz zegar
#pragma config OSCIOFNC = OFF //FOSC-->OSCIOFNC=1 - Fcy b?dzie na pinie OSCO
/*Config PLL prescaler, PLL postscaler, PLL divisor*/
PLLFBD = 41 ; //M=43 (0 bit to 2 st?d 41 = 43 patrz w rejestrze), tutaj 3.685 x 43 = 158.455MHz
CLKDIVbits.PLLPRE=0 ; //N1=2, tutaj 7.37 MHz / 2 = 3.685 MHz
CLKDIVbits.PLLPOST=0 ; //N2=2, tutaj 158.455 MHz / 2 = 79.2275 MHz (Fosc)
/*
* UWAGA przerwania musza byc wylaczone podczas wywolywania ponizszych
* dwóch funkcji __builtin_write_...brak definicji w pliku naglówkowym
* to wewnetrzne funkcje kompilatora patrz help M-LAB IDE
* i datasheet str 140(11.6.3.1 Control Register Lock)
*/
/*Initiate Clock Switch to Internal FRC with PLL (OSCCON-->NOSC = 0b001)*/
__builtin_write_OSCCONH(0x01); //tutaj argumentem jest wartosc z NOSC
/*Start clock switching*/
__builtin_write_OSCCONL(0x01);
/*Wait for Clock switch to occur*/
while(OSCCONbits.COSC !=0b001);
/*Wait for PLL to lock*/
while(OSCCONbits.LOCK !=1) {};
}
/*******************************************************************************
File: uart.c
******************************************************************************/
#include "xc.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*dyrektywy uint8_t itp*/
#define FCY 40000000 /* podajemy wartosc ustawionego zegara (40 MHz), wazne
aby przed includowaniem <libpic30.h>, potrzebne to jest do wyliczania delay-i*/
#include <libpic30.h> // biblioteka dajaca dostep do delay-i.
#include "uart.h"
/*definicje funkcji*/
void Ustaw_UART1(void) {
/*remapping pinow pod UART1*/
/*TX ustawiamy na pinie 14 (RP5)--> U1TX wartosc 00011, wpisac na bitach od 8 do 12 do rejestru RPOR2*/
RPOR2bits.RP5R = 3;
/*RX ustawiamy na pinie 11 (RP4)--> U1RX wartosc 00100, wpisac na bitach od 0 do 5 do rejestru RPINR18*/
RPINR18bits.U1RXR = 4 ;
/*konfiguracja UARTA1 do wspolpracy z DMA*/
U1MODEbits.STSEL = 0; /* 1-Stop bit*/
U1MODEbits.PDSEL = 0; /* No Parity, 8-Data bits*/
U1MODEbits.ABAUD = 0; /* Auto-Baud disabled */
U1MODEbits.BRGH = 0; /* Standard-Speed mode */
U1BRG = BRGVAL; /* Baud Rate setting */
U1STAbits.UTXISEL0 = 0; /*Interrupt after one TX character is transmitted*/
U1STAbits.UTXISEL1 = 0;
U1MODEbits.UARTEN = 1; /*Enable UART*/
U1STAbits.UTXEN = 1; /*Enable UART TX*/
/* Wait at least 105 microseconds (1/9600) before sending first char */
__delay_us(105);
/*Uff..koniec konfiguracji UART1*/
}
/*******************************************************************************
File: main.c
******************************************************************************/
#include "xc.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*dyrektywy uint8_t itp*/
#include <string.h>
#include "ustaw_zegar.h" /*z uwagi na FCY musi byc przed #include <libpic30.h>*/
#include <libpic30.h> // biblioteka dajaca dostep do delay-i.
#include "uart.h"
/*deklaracje funkcji*/
void transfer_DMA_UART1(void) ;
/*lokujemy bufor do pamieci DMA RAM, uzywamy wewnetrznej funkcji kompilatora __attribute__((space(dma)))*/
char bufor[] __attribute__((space(dma)))="Transfer DMA\r\n";
/*dma
- PIC24E/H MCUs, dsPIC33E/F DSCs only
Allocate the variable in DMA memory. Variables in DMA memory can be accessed using ordinary C statements and by the DMA peripheral.
__builtin_dmaoffset() and __builtin_dmapage() can be used to find the correct offset for configuring the DMA peripheral. See ?Built-in Functions? for details.
#include <p24Hxxxx.h>
unsigned int BufferA[8]
__attribute__((space(dma)));
unsigned int BufferB[8]
__attribute__((space(dma)));
int main()
{
DMA1STA =
__builtin_dmaoffset(BufferA);
DMA1STB =
__builtin_dmaoffset(BufferB);
}
*/
int main(void) {
/*konfiguracja zegara i inne potrzebne do startu mikrokontrolera*/
ustaw_zegar();
Ustaw_UART1();
__delay_ms(3000) ;
transfer_DMA_UART1(); /*wyslij do UARTA nasz bufor z danymi*/
while(DMA0CONbits.CHEN != 0) {} /*czekaj az kanal po ostatniej transmisji na DMA0 zostanie zamkniety/zwolniony*/
transfer_DMA_UART1(); /*ponownie wyslij do UARTA nasz bufor z danymi*/
while(1)
{
/*petla glowna programu*/
}
return 0;
}
/*definicje funkcji*/
void transfer_DMA_UART1(void){
/*Konfiguracja DMA kanal 0 do transmisji UART w trybie One_Shot (bez powtówrzenia)*/
/* Rejestr DMAxCON
* bit15 -0 chen/chanel --> disable
* bit14 -1 size --> Byte
* bit13 -1 dir --> Read from DMA RAM address write to peripheral address
* bit12 -0 half --> Initiate block transfer complete interrupt when all of the data has been moved
* bit11 -0 nullw --> Normal operation
* bit10-6 Unimplemented raed as 0
* bit5-4 -00 amode --> Register Indirect with Post_Incerement mode
* bit3-2 Unimplemented read as 0
* bit1-0 -01 mode --> OneShot, Ping-Pong modes disabled*/
DMA0CON = 0x6001 ;/*0x6001 -m.in kanal DMA disable, wartosc wynika z ustawienia bitow jak wyzej*/
DMA0CNT = strlen(bufor)-1;/*wyliczamy ile znaków do przeslania mamy z bufora, max 1024 bajty*/
/*IRQ Select Register,wskazujemy UART1 TX*/
DMA0REQ = 0x000C ;/*UART1 TX*/
/*Peripheral Adress Register*/
DMA0PAD = (volatile unsigned int)&U1TXREG ; /*rzutowanie typu i pobranie adresu rejestru U1TXREG*/
/*wewnetrzna konstrukcja/funkcja kompilatora - "__builtin_dmaoffset()"*/
DMA0STA = __builtin_dmaoffset(bufor) ;/*taka jest konstrukcja wskazania na bufor z danymi*/
IFS0bits.DMA0IF = 0 ; /*clear DMA Interrupt Flag */
IEC0bits.DMA0IE = 1 ; /*enable DMA Interrupt */
/*Wazne kanal DMA moze byc otwarty dopiero po wpisaniu danych do rejestru DMASTA i DMAxCNT*/
DMA0CONbits.CHEN = 1; /*Canal DMA0 enable*/
/*Force a single DMA transfer (Manual mode), inicjuj transfer*/
DMA0REQbits.FORCE = 1;
/*po zakonczonym transferze automatycznie kanal DMA zostaje wylaczony, zmienia
*sie wartosc rejestru DMAxCON na 16-tym bicie z "1" na "0". Aby ponownie odpalic transfer
*nalezy wlaczyc ten bit DMAxCON.bits.CHEN=1 i odpalic transfer DMA0REQbits.FORCE = 1
*/
}
/*przerwanie DMA kiedy blok danych zosta? wyslany z DMA RAM do UART-a*/
/*Ale uwaga to nie oznacza , ze transmisja UART zostala zakonczona calkowicie*/
void __attribute__((interrupt, no_auto_psv))_DMA0Interrupt(void)
{
/*kod obslugi przerwania jesli potrzeba*/
IFS0bits.DMA0IF = 0; /*Clear DMA Interrupt flag*/
}
Your style is very unique inn comparison to other people I have read stuff from.
OdpowiedzUsuńThanks for posting when you have the opportunity, Guess I will just bookmark this web site.