SPI DMA 사용 - NOR Flash

STM32 내장 FLASH가 아닌 외부 FLASH에 데이터를 읽고 쓰는 것이 가능하지만, DMA를 사용하지 않는다면 상당히 느린 속도로 데이터를 읽고 쓰게 될 것이다.

 

FLASH는 같은 영역에 데이터를 overwrite 할 수 없기 때문에, 같은 영역에 데이터를 쓰려면 블럭 또는 섹터 단위로 기록할 영역을 지우고 써야하기 한다. 따라서, 쓰기 속도는 DMA를 사용하여도 괄목할만한 속도 개선이 되지 않지만 읽기 속도는 엄청난 향상을 볼 수 있다.

위 이미지는 1MB 데이터를 읽고 쓰는데 걸린 시간을 ms 단위로 출력한 것이며, DMA 사용 유무에 따라 읽기 속도가 확연히 차이가 나는 것을 확인할 수 있다.

 

STM32F429에서 180MHz 시스템 클럭을 사용하고 있으며, SPI는 아래와 같이 설정하였다.

외부 FLASH W25qxx 드라어버 소스는 아래 GitHub에서 다운로드 받을 수 있으며, 아래 코드는 일반 방식과 DMA 방식을 모두 지원하고 있다.

 

GitHub - grrrr/w25qxx: w25qxx SPI FLASH driver for stm32 HAL

w25qxx SPI FLASH driver for stm32 HAL. Contribute to grrrr/w25qxx development by creating an account on GitHub.

github.com

위 드라이버 코드를 적용하여 SPI 인터럽트 콜백을 활성화 시키기 위해 stm32fxx_hal_conf.h 파일을 아래와 같이 수정하도록 한다.

diff --git a/Core/Inc/stm32f4xx_hal_conf.h b/Core/Inc/stm32f4xx_hal_conf.h
index 6825bfd..9279f27 100644
--- a/Core/Inc/stm32f4xx_hal_conf.h
+++ b/Core/Inc/stm32f4xx_hal_conf.h
@@ -187,7 +187,7 @@
 #define  USE_HAL_SRAM_REGISTER_CALLBACKS        0U /* SRAM register callback disabled      */
 #define  USE_HAL_SPDIFRX_REGISTER_CALLBACKS     0U /* SPDIFRX register callback disabled   */
 #define  USE_HAL_SMBUS_REGISTER_CALLBACKS       0U /* SMBUS register callback disabled     */
-#define  USE_HAL_SPI_REGISTER_CALLBACKS         0U /* SPI register callback disabled       */
+#define  USE_HAL_SPI_REGISTER_CALLBACKS         1U /* SPI register callback disabled       */
 #define  USE_HAL_TIM_REGISTER_CALLBACKS         0U /* TIM register callback disabled       */
 #define  USE_HAL_UART_REGISTER_CALLBACKS        0U /* UART register callback disabled      */
 #define  USE_HAL_USART_REGISTER_CALLBACKS       0U /* USART register callback disabled     */

그리고 FLASH 읽고 쓰기 속도를 확인하기 위해 블럭 단위의 rw 함수를 아래와 같이 만들어 테스트하였다.

int spinor_read_block(uint32_t address, uint8_t *buffer, uint32_t length)
{
    uint8_t  block_num;
    uint32_t block_addr = 0;
    uint32_t block_size = W25qxx_GetBlockSize();

    if((address % block_size) != 0)
    {
        printf("ERROR :: %s() : address does  not align by %ldKb\r\n", __func__, block_size / 1024);
        return -1;
    }

    if((length % block_size) == 0)
        block_num = (length / block_size);
    else
        block_num  = (length / block_size) + 1;
    block_addr = address / block_size;

    for(int i = 0 ; i &lgt; block_num ; i++)
    {
        if(i == (block_num - 1))
            W25qxx_ReadBlock(&buffer[i*block_size], i+block_addr, 0, length);
        else
        {
            W25qxx_ReadBlock(&buffer[i*block_size], i+block_addr, 0, block_size);
            length -= block_size;
        }
    }

    return 0;
}

int spinor_write_block(uint32_t address, uint8_t *buffer, uint32_t length)
{
    uint8_t  block_num;
    uint32_t block_addr = 0;
    uint32_t block_size = W25qxx_GetBlockSize();

    if((address % block_size) != 0)
    {
        printf("ERROR :: %s() : address does  not align by %ldKb\r\n", __func__, block_size / 1024);
        return -1;
    }

    if((length % block_size) == 0)
        block_num = (length / block_size);
    else
        block_num  = (length / block_size) + 1;
    block_addr = address / block_size;

    for(int i = 0 ; i < block_num ; i++)
    {
        W25qxx_EraseBlock(i+block_addr);

        if(i == (block_num - 1))
            W25qxx_WriteBlock(&buffer[i*block_size], i+block_addr, 0, length);
        else
        {
            W25qxx_WriteBlock(&buffer[i*block_size], i+block_addr, 0, block_size);
            length -= block_size;
        }
    }

    return 0;
}

spinor_write_block() 함수에서 FLASH 특성에 따라 W25qx_WriteBlock() 함수를 호출하기 전에 W25qxx_EraseBlock() 함수를 호출해야 하는 것을 명심하도록 한다.

 

마지막으로 uart command 함수를 아래와 같이 만들어 SPI 속도를 측정하도록 한다.

void check_nor_command(char *command)
{
    if(strncmp(command, "info", 4) == 0)
    {
        printf("NOR info ======================\r\n");
        printf("FLASH size   = 0x%lx\r\n", spinor_get_size());
        printf("Block size   = 0x%lx\r\n", spinor_get_block_size());
        printf("Sector size  = 0x%lx\r\n", spinor_get_sector_size());
    }
    else if(strncmp(command, "bread", 5) == 0)
    {
        uint32_t check_time;
        uint32_t length = spinor_get_block_size()*16;

        memset(block1, 0x00, length);
        check_time  = HAL_GetTick();
        spinor_read_block(0x10000, block1, length);
        printf("1MB(16 Block) Read  :: proc_time = %ld\r\n", (HAL_GetTick() - check_time));
        if(memcmp(block0, block1, length) != 0) printf("block does not same !!!\r\n");
    }
    else if(strncmp(command, "bwrite", 6) == 0)
    {
        uint32_t check_time;
        uint32_t length = spinor_get_block_size() * 16;

        memset(block0, 0xA0, length);
        check_time  = HAL_GetTick();
        spinor_write_block(0x10000, block0, length);
        printf("1MB(16 Block) Write :: proc_time = %ld\r\n", (HAL_GetTick() - check_time));
    }
}

STM32 프로젝트를 진행하면서 spinor rw 속도가 나오지 않아,  구글링하여 SPI DMA를 정리하였다. 프로젝트를 진행하면서 부족한 부분은 그때 그때 추가 정리해야 할 듯 싶다.