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

TMS320F28388D ] CPU2에서 SD카드 사용 + 속도 테스트

by eteo 2022. 12. 28.

개인 기록용 정리글

 

CPU1 설정

// 핀먹스 세팅
// GPIO103 -> SDCardCS Pinmux
GPIO_setPinConfig(GPIO_103_GPIO103);
// SPIC -> mySPI0 Pinmux
GPIO_setPinConfig(GPIO_100_SPIC_SIMO);
GPIO_setPinConfig(GPIO_101_SPIC_SOMI);
GPIO_setPinConfig(GPIO_102_SPIC_CLK);

// 칩셀렉트핀 init
//SDCardCS initialization
GPIO_setDirectionMode(SDCardCS, GPIO_DIR_MODE_OUT);
GPIO_setPadConfig(SDCardCS, GPIO_PIN_TYPE_STD);
// CS핀 마스터코어 CPU2로 설정
GPIO_setMasterCore(SDCardCS, GPIO_CORE_CPU2);
GPIO_setQualificationMode(SDCardCS, GPIO_QUAL_SYNC);
GPIO_writePin(SDCardCS, 1);

// SPI init
//mySPI0 initialization
SPI_disableModule(mySPI0_BASE);
SPI_setConfig(mySPI0_BASE, DEVICE_LSPCLK_FREQ, SPI_PROT_POL0PHA1,
			  SPI_MODE_MASTER, 12500000, 	8);
SPI_enableFIFO(mySPI0_BASE);
SPI_disableLoopback(mySPI0_BASE);
SPI_setEmulationMode(mySPI0_BASE, SPI_EMULATION_STOP_AFTER_TRANSMIT);
SPI_enableModule(mySPI0_BASE);

// SPIC 제어권 CPU2에게 넘기기
SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL6_SPI, 3, SYSCTL_CPUSEL_CPU2);

// boot CPU2
Device_bootCPU2(BOOTMODE_BOOT_TO_FLASH_SECTOR0);

 

여기서 CS핀과 SPI init 코드는 sdspi 라이브러리에서 재사용하게 된다. 사실 SPI 모듈의 디테일한 설정은 CPU2에서만 하는게 맞지만 syscfg 툴로 생성한거라 그냥 뒀다.

 

 

 

 

 

CPU2 설정

 

void CS_init()
{
    GPIO_writePin(103, 1);
    GPIO_setDirectionMode(103, GPIO_DIR_MODE_OUT);
    GPIO_setPadConfig(103, GPIO_PIN_TYPE_STD);
    GPIO_setQualificationMode(103, GPIO_QUAL_SYNC);
}

void SPI_init()
{
    SPI_disableModule(SPIC_BASE);
    SPI_setConfig(SPIC_BASE, DEVICE_LSPCLK_FREQ, SPI_PROT_POL0PHA1,
                  SPI_MODE_MASTER, 12500000,    8);
    SPI_enableFIFO(SPIC_BASE);
    SPI_disableLoopback(SPIC_BASE);
    SPI_setEmulationMode(SPIC_BASE, SPI_EMULATION_STOP_AFTER_TRANSMIT);
    SPI_enableModule(SPIC_BASE);
}

먼저 CS핀과 SPI init을 해준다.

 

 

 

SD카드와 fatfs 라이브러리는 아래 경로에 있다.

C:\ti\c2000\C2000Ware_4_02_00_00\libraries\fatfs

그중에 fatfs 와 sdspi 폴더를 가져오면 된다.

 

원본이 수정되지 않게 워크스페이스 안에 가져와서 프로젝트에 포함시킨다. fatfs 폴더 안의 utils와 ffsystem.c 는 빌드에서 제외하던가 삭제한다. fatfs 라이브러리 설정은 ffconf.h 파일을 통해 하면 된다.

두 폴더 안의 헤더파일이 있으니 Properties - Build - Compiler - #include search path 에 등록해 둔다.

 

 

 

 

 

fatfs 라이브러리를 사용하는 구조는 대강 이렇게 생겼다.

User는 f_open(), f_write(), f_read(), f_close() 등의 API 를 사용하면 되고 disk I/O layer는 칩 제조사가 제공해주는 것으로  SDFatFS.c 와 sdspi.c 파일이 있는데, 저 그림에서 SDFatFS.c 가 mmc.c 역할을하고 sdspi.c가 spi.c 역할을 한다고 보면 된다.

 

SDFatFS 파일에서 ff.h, diskio.h, sdspi.h 헤더파일을 include 하고 있고 SDFatFS_init() 후에 SDFatFS_open() 함수를 사용하면 이 안에서 disk_register() 함수를 통해 FatFs 모듈과의 연결고리를 형성하고 하고 f_mount()를 한다.

 

 

 

 

참고로 sdspi.c 파일을 확인하면 오래된 SD카드와의 호환성을 위해 SD init 시에 400KHz로 초기화하고 나중에 2.5MHz로 재설정하는데 해당 부분은 주석처리해도 잘 작동한다. 그리고 실제 spi모듈을 통해 커맨드를 송신하고 데이터를 송수신하는 함수인 SPI_pollingFIFOTransactionWithNullSupport() 함수 내부를 Blocking 함수에서 NonBlocking 함수로 변경만해줘도 속도개선이 된다. 그리고 에러 발생시 ESTOP0; 하는 코드도 처리가 필요하다.

 

SDSPI_Handle SDSPI_open(SDSPI_Handle handle)
{
//...

    /*
     * SPI is initially set to 400 kHz to perform SD initialization.  This is
     * is done to ensure compatibility with older SD cards.  Once the card has
     * been initialized (in SPI mode) the SPI peripheral will be closed &
     * reopened at 2.5 MHz.
     */
    //spiHandle initialization
//    SPI_disableModule(handle->spiHandle);
//    SPI_setConfig(handle->spiHandle, DEVICE_LSPCLK_FREQ, SPI_PROT_POL0PHA1,
//                  SPI_MODE_MASTER, 400000, 8);
//    SPI_enableFIFO(handle->spiHandle);
//    SPI_disableLoopback(handle->spiHandle);
//    SPI_setEmulationMode(handle->spiHandle, SPI_EMULATION_STOP_AFTER_TRANSMIT);
//    SPI_enableModule(handle->spiHandle);

//...
}

int16_t SDSPI_initialize(SDSPI_Handle handle)
{
//...

//    else {
        /* Reconfigure the SPI to operate @ 2.5 MHz */
        // spiHandle initialization
//        SPI_disableModule(handle->spiHandle);
//        SPI_setConfig(handle->spiHandle, DEVICE_LSPCLK_FREQ, SPI_PROT_POL0PHA1,
//                      SPI_MODE_MASTER, 2500000, 8);
//        SPI_enableFIFO(handle->spiHandle);
//        SPI_disableLoopback(handle->spiHandle);
//        SPI_setEmulationMode(handle->spiHandle, SPI_EMULATION_STOP_AFTER_TRANSMIT);
//        SPI_enableModule(handle->spiHandle);
//        SPI_init()
//    }

//...
}

 

 

 

 

SDapp.c 파일을 하나 만들고 sdspi 폴더의 헤더를 추가한 후 필요한 변수들을 추가하고, 함수를 만들어 사용하면된다.

 

#include <sdspi/sdspi.h>
#include <sdspi/SDFatFS.h>

// 사용하는 SD카드 수
uint16_t SDFatFS_config_count = 1;

// SDFatFS_Object, 포인터 통해서만 사용됨
SDFatFS_Object sdfatfsObject;

// SDFatFS_Object의 포인터(=SDFatFS_Handle) 배열. 현재 사용하는 SDFatFS_Object는 1개
SDFatFS_Object* SDFatFS_config [] = {&sdfatfsObject};

// SDFatFS_Object 를 담는 포인터
static SDFatFS_Handle sdFatFs_handle;

// SDSPI_Object, 포인터 통해서만 사용됨. 멤버변수 중 cardType과 isOpen은 0으로 초기화됨
SDSPI_Object sdspiObject = {
        .spiHandle = SPIC_BASE,
        .spiCsGpioIndex = 103
};

// SDSPI_Object의 포인터. SDSPI_Handle 은 SDFatFS_Object의 4번째 멤버로 SDFatFS_open() 호출시 첫번째 매개변수로 넘겨짐
SDSPI_Handle sdspiHandle = &sdspiObject;

// Drive number used for FatFs, 드라이브 0 사용
#define DRIVE_NUM           0
// String conversion macro, define 된 드라이브 넘버 문자열 확장
#define STR_(n)             #n
#define STR(n)              STR_(n)

const char inputfile[] = STR(DRIVE_NUM)":input.txt";
const char outputfile[] = STR(DRIVE_NUM)":output.txt";

const char textarray[] = "Hello World!\n";
uint8_t buff[2048] = {0,};
unsigned int bw = 0;
unsigned int br = 0;

uint16_t writeFlag = 0;

FIL src;
FIL dst;

unsigned int filesize;
FRESULT fresult;


// 현재 SD 카드가 mount 되었는지 확인하는 함수
uint16_t isSdMoundted(void)
{
    uint16_t ret = false;

    if(sdFatFs_handle == NULL || SDFatFS_config[0]->diskState != 0) ret = false;
    else ret = true;

    return ret;
}

// SD카드 init 과 SDFatFS_open
void InitSD(void)
{
    SDFatFS_init();

    sdFatFs_handle = SDFatFS_open(sdspiHandle, DRIVE_NUM);
    if (sdFatFs_handle != NULL)
    {
        fresult = f_open(&src, inputfile, FA_OPEN_EXISTING|FA_READ);
        fresult = f_read(&src, buff, 2048, &br);
        fresult = f_open(&dst, outputfile, FA_CREATE_ALWAYS|FA_WRITE);
    }

}

// 테스트용
int16_t fwriteTest(int16_t size)
{
    f_write(&dst, buff, size, &bw);
    f_sync(&dst);

    return bw;
}

 

 

 

테스트

데이터 유실 방지를 위해서는 쓸때마다 f_open(), f_close()를 해주던가 아니면 open한 상태로 f_sync()를 해주어야 한다.

테스트 해보니 처음 write를 할 때는 평균보다 더 많은 시간이 걸리고 무작위로 10ms가 넘는 시간이 걸릴 때가 있었다.

 

 

 

if(!isSdMoundted())
{
    print("SD card not ready to write!\n");
}
else if(stricmp(argv[1], "write") == 0 && argc >= 4)
{
    if(ReadDec(argv[2]) < 1 || ReadDec(argv[2]) > 2048 || ReadDec(argv[3]) < 1 || ReadDec(argv[3]) > 100)
    {
        print("Input data exceeded valid range\n");
        print("Usage : sd write [1-2048]byte [1-100]count\n");
    }
    else
    {
        int16 writeSize = ReadDec(argv[2]);
        int16 writeCount = ReadDec(argv[3]);

        print("SD Card Writing Test START\r\n");
        print("=======================================\r\n");

        int i;
        uint16 timeBefore;
        uint16 timeTaken;
        uint16 sum = 0;

        uint16 bytesWritten;

        for(i =0; i < writeCount; i++)
        {
            timeBefore = timeTick;
            bytesWritten = fwriteTest(writeSize);
            timeTaken = timeTick-timeBefore;
            print("Written Byte : %d, Time taken : %dms\r\n", bytesWritten, timeTaken);
            sum += timeTaken;
        }
        print("=======================================\r\n");
        print("SD Card Writing Test END\r\n");
        float avgTime = (float)sum/writeCount;
        print("Write Size  : %d\r\n", writeSize);
        print("Write Count : %d\r\n", writeCount);
        print("Average time : %d.%dms\r\n", (int)avgTime, (int)(avgTime * 100) % 100);
        float kbs = (float)writeSize / 1024 * 1000 / avgTime;
        print("Test Result : %d.%d KB/s\r\n", (int)kbs, (int)(kbs * 100) % 100);
    }

}
else{
    print("Usage : sd write [1-2048]byte [1-100]count\n");
}