본문 바로가기
DSP, MCU/TMS320F2838x (C28x)

TMS320F28388D ] DMA 예제

by eteo 2022. 12. 11.

 

gsram에서 DMA를 사용해 데이터를 이동시키는 예제

 

 

 

 

//
// Included Files
//
#include "driverlib.h"
#include "device.h"
#include "board.h"

//
// DMA data sections
// 이전글에 썼지만 DMA에 접근 가능한 메모리 영역이 정해져 있고 그 중 하나가 RAMGS이다.
// 따라서 source 와 destination으로 사용할 버퍼를 RAMGS 영역에 배치하는 전처리 지시 코드가 필요하다.
#pragma DATA_SECTION(sData, "ramgs0");  // map the TX data to memory
#pragma DATA_SECTION(rData, "ramgs1");  // map the RX data to memory

//
// Globals
// sData에 있는 내용을 rData로 옮길 것이다.
uint16_t sData[128];   // Send data buffer
uint16_t rData[128];   // Receive data buffer

// 전송완료를 나타내기 위한 플래그
volatile uint16_t done;

// const void* 형으로 캐스팅해 변수에 저장
const void *destAddr = (const void *)rData;
const void *srcAddr = (const void *)sData;

//
// Function Prototypes
//
void error();
// transfer 종료시 호출될 콜백함수(ISR)
__interrupt void INT_myDMA0_ISR(void);

 

 

 

 

.cmd 파일에 정의된 내용

   ramgs0 : > RAMGS0, type=NOINIT
   ramgs1 : > RAMGS1, type=NOINIT

 

 

 

 

 

void main(void)
{
    uint16_t i;

    //
    // Initialize device clock and peripherals
    //
    Device_init();
    
    // 예제라고 실행해보니 interrupt와 PIE vector를 initialize 하는 코드가 없어 의도대로 실행되지 않았고 직접 추가했다.
    Interrupt_initModule();
    Interrupt_initVectorTable();

	// 실제 DMA 설정 코드들은 syscfg 툴을 생성되어 이 안에 담긴다.
    Board_init();


    //
    // User specific code, enable interrupts:
    // Initialize the data buffers
    // 시작지와 목적지 버퍼 초기화
    for(i = 0; i < 128; i++)
    {
        sData[i] = i;
        rData[i] = 0;
    }

    //
    // Enable interrupts required for this example
    // INT_DMA_CH6 인터럽트를 enable 한다.
    Interrupt_enable(INT_myDMA0);
    EINT;                                // Enable Global Interrupts

    // Start DMA channel
    // DMA_CH6_BASE 스타트
    DMA_startChannel(myDMA0_BASE);

    done = 0;           // Test is not done yet

	// DMA Transfer가 종료시 호출되는 ISR에서 done을 1로 만들때까지 반복
    while(!done)        // wait until the DMA transfer is complete
    {
    	// 소프트웨어 트리거를 쏘는 부분이다.
       DMA_forceTrigger(myDMA0_BASE);

		// NOP는 아무것도 하지 않는 어셈블리 명령어이다 그 앞의 RPT #N 은 N+1 instruction cycle 동안 아무것도 하지않는단 뜻이다.
        // 컴파일러가 코드 경로를 축소하지 않도록 해서 중단점을 찍어볼 때 쓰일 수 있다.
       asm(" RPT #255 || NOP");
    }

    //
    // When the DMA transfer is complete the program will stop here
    //
    ESTOP0;
}

//
// error - Error Function which will halt the debugger
//
void error(void)
{
    ESTOP0;  //Test failed!! Stop!
    for (;;);
}

//
// local_D_INTCH6_ISR - DMA Channel6 ISR
// DMA 채널 6 ISR
__interrupt void INT_myDMA0_ISR(void)
{
    uint16_t i;

	// DMA_CH6_BASE 스탑
    DMA_stopChannel(myDMA0_BASE);
    
    // ACK to receive more interrupts from this PIE group
    EALLOW;
    // INT_DMA_CH6 이 속한 그룹에 다시 인터럽트가 걸릴 수 있게 clearACK 여기선 의미가 없다.
    Interrupt_clearACKGroup(INTERRUPT_ACK_GROUP7);
    EDIS;

	// 잘 이동됐는지 체크
    for( i = 0; i < 128; i++ )
    {
        //
        // check for data integrity
        //
        if (rData[i] != i)
        {
            error();
        }
    }

	// 테스트 완료
    done = 1; // Test done.
    return;
}

 

 

 

 

 

 

asm(" RPT #255 || NOP"); 과 ISR 내부에 중단점을 찍고 버퍼를 확인하면 데이터가 이동하는 것을 볼 수 있다.

 

 

 

 

 

 

 

syscfg 툴 상 DMA 설정

 

 

 

 

 

 

 

board.c 의 내용

 

EALLOW;

DMA_initController();

    DMA_setEmulationMode(DMA_EMULATION_STOP);
    
    DMA_configAddresses(myDMA0_BASE, destAddr, srcAddr);
    DMA_configBurst(myDMA0_BASE, 8U, 1, 1);
    DMA_configTransfer(myDMA0_BASE, 16U, 1, 1);
    DMA_configWrap(myDMA0_BASE, 65535U, 0, 65535U, 0);
    DMA_configMode(myDMA0_BASE, DMA_TRIGGER_SOFTWARE, DMA_CFG_ONESHOT_DISABLE | DMA_CFG_CONTINUOUS_DISABLE | DMA_CFG_SIZE_16BIT);
    DMA_setInterruptMode(myDMA0_BASE, DMA_INT_AT_END);
    DMA_enableInterrupt(myDMA0_BASE);
    DMA_disableOverrunInterrupt(myDMA0_BASE);
    DMA_enableTrigger(myDMA0_BASE);
    DMA_stopChannel(myDMA0_BASE);

	Interrupt_register(INT_myDMA0, &INT_myDMA0_ISR);
	Interrupt_disable(INT_myDMA0);
    
    
EDIS;

 

 

 

DMA_initController();

DMA CONTROL 레지스터를 조작해 hard reset 한다.

 

 

 

DMA_configAddresses(myDMA0_BASE, destAddr, srcAddr);
// 함수원형
void DMA_configAddresses(uint32_t base, const void *destAddr,
                         const void *srcAddr)

DEBUG 모드일때(JTAG 에뮬레이터 연결시) 동작모드를 설정하는 것이다.

 

 

 

DMA_configBurst(myDMA0_BASE, 8U, 1, 1);

BURST_SIZE를 8, SRC_BURST_STEP 과 DST_BURST_STEP 을 1로 설정한다. (첫번째 Burst가 끝난뒤 Active 주소는 Src_Addr+7일 것이다.)

 

함수 안에서 두번째 매개변수 - 1U하여 BURST_SIZE 레지스터에 대입하고 있다.

HWREGH(base + DMA_O_BURST_SIZE)     = size - 1U;

 

 

 

DMA_configTransfer(myDMA0_BASE, 16U, 1, 1);

TRANSFER_SIZE를 16, SRC_TRANSFER_STEP 과 DST_TRANSFER_STEP 을 1로 설정한다. (직전 Burst가 끝나고 다음 Burst가 시작되기 전에 Src_Addr과 Dest_Addr을 1씩 증가한다.)

 

위와 같이 두번째 매개변수에서 - 1U 하여 TRANSFER_SIZE 레지스터에 대입하고 있다.

HWREGH(base + DMA_O_TRANSFER_SIZE)     = (uint16_t)(transferSize - 1U);

 

Transfer는 16번의 Burst로 이루어져 있고 1번의 Burst에 옮겨지는 Word는 8Word이니 최종적으로는 8 * 16 = 128 Word를 이동시키는 것이다.

 

BURST_SIZE+1 와 TRANSFER_SIZE+1  의 곱이 128이 되도록 설정만 하면 과정은 다를 수 있지만 결과는 똑같이 이동될 것이다.

 

 

 

DMA_configWrap(myDMA0_BASE, 65535U, 0, 65535U, 0);

WRAP 설정을한다. SRC/DST_WRAP_STEP은 0이고, 중요한건 SRC/DST_WRAP_SIZE 가 TRANSFER_SIZE보다 크니 WRAP을 사용하지 않겠다는 뜻이다. (만약 128 Word를 계속적으로 복사한다면 Transfer Size랑 같은 값으로 설정하면 될 것이다.)

 

 

 

DMA_configMode(myDMA0_BASE, DMA_TRIGGER_SOFTWARE, DMA_CFG_ONESHOT_DISABLE | DMA_CFG_CONTINUOUS_DISABLE | DMA_CFG_SIZE_16BIT);

 

트리거 소스는 SOFTWARE 트리거로 한다. 만약에 페리페럴과 메모리간 이동이었다면 그래도 소프트웨어 트리거를 쓸 수 있지만 페리페럴 트리거 소스를 사용할 수 도있다.

예를 들어보면 SCIRXFIFO 인터럽트 레벨을 1/16 으로 설정하고 이 인터럽트를 DMA 트리거 소스로 사용하여 FIFO에 1Word가 들어왔을 때마다 해당 DMA 채널 ISR에서 1Word를 버퍼로 이동시키고 끝내는 식이다.

 

OneShot Mode는 disable한다. 만약 OneShot Mode가 enable 되어있다면 한번의 DMA_forceTrigger()로 127 Word가 다 옮겨질 것이다.

 

Continuous Mode는 disable한다. Transfer 완료 후에는 트리거가 들어와도 무시된다.

Transfer의 최소단위인 Word(Data)의 size는 16bits로 한다.

 

 

 

DMA_setInterruptMode(myDMA0_BASE, DMA_INT_AT_END);

Transfer 완료시에 인터럽트가 걸리는 모드로 설정한다.

 

 

 

DMA_enableInterrupt(myDMA0_BASE);

DMA_CH6_BASE 의 인터럽트를 enable 한다.

 

 

 

DMA_disableOverrunInterrupt(myDMA0_BASE);

Overrun 인터럽트는 사용하지 않는다.

 

 

 

DMA_enableTrigger(myDMA0_BASE);

DMA Trigger를 enable한다. MODE.CHx.PERINTE 이 비트가 enable 되어 있지 않으면 아무리 트리거를 쏴도 DMA가 작동하지 않는다.

 

 

 

DMA_stopChannel(myDMA0_BASE);

예제에선 초기설정 후 DMA 채널을 stop해두고 main 함수 안에서 Start하고 있다.

 

 

 

Interrupt_register(INT_myDMA0, &INT_myDMA0_ISR);
// 함수원형
static inline void
Interrupt_register(uint32_t interruptNumber, void (*handler)(void))

INT_DMA_CH6 에 콜백함수(ISR)를 등록한다. 이러면 PIE Vector Table에 두번째 매개변수인 함수 포인터가 카피된다.

 

 

 

Interrupt_disable(INT_myDMA0);

마찬가지로 초기설정 후 DMA CH 인터럽트를 disable 해두고 main 함수에서 enable 하고 있다.