STM32 내장 FLASH가 아닌 외부 FLASH에 데이터를 읽고 쓰는 것이 가능하지만, DMA를 사용하지 않는다면 상당히 느린 속도로 데이터를 읽고 쓰게 될 것이다.
FLASH는 같은 영역에 데이터를 overwrite 할 수 없기 때문에, 같은 영역에 데이터를 쓰려면 블럭 또는 섹터 단위로 기록할 영역을 지우고 써야하기 한다. 따라서, 쓰기 속도는 DMA를 사용하여도 괄목할만한 속도 개선이 되지 않지만 읽기 속도는 엄청난 향상을 볼 수 있다.
위 이미지는 1MB 데이터를 읽고 쓰는데 걸린 시간을 ms 단위로 출력한 것이며, DMA 사용 유무에 따라 읽기 속도가 확연히 차이가 나는 것을 확인할 수 있다.
STM32F429에서 180MHz 시스템 클럭을 사용하고 있으며, SPI는 아래와 같이 설정하였다.
외부 FLASH W25qxx 드라어버 소스는 아래 GitHub에서 다운로드 받을 수 있으며, 아래 코드는 일반 방식과 DMA 방식을 모두 지원하고 있다.
위 드라이버 코드를 적용하여 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를 정리하였다. 프로젝트를 진행하면서 부족한 부분은 그때 그때 추가 정리해야 할 듯 싶다.