[이 프로젝트에 사용된 MCU는 NUCLEO-F446RE(STM32F446RET6) 입니다.]
이번에는 memory to GPIO 를 DMA로 출력하는 테스트를 할 예정입니다.

타이머 1개를 트리거 신호로 사용하여 65535개까지 연속으로 데이터를 출력할 수 있으며,
속도는 최대 22.7MHz 까지 나옵니다.

8 비트 데이터 버스로 사용한다면 대략 180Mbit/s 의 속도가 나오므로 꽤 나오죠?
저는 동시에 여러개의 SPI 버스의 데이터를 송신하는 용도로 사용하려고 테스트를 진행중입니다.
(예전에 테스트를 어느정도 해 놓고, 정리 차원에서 카페에 글을 올리는 중입니다.)

예전에 테스트한 보드는 Nucleo STM32F411 보드에서 했었고,
이번에는 Nucleo STM32F446 보드에서 테스트할 예정입니다.

참고로 한 내용은 st 홈페이지의 AN4666 입니다.

예제 프로그램을 위의 링크된 사이트에서 다운 받을 수 있는데, 회원을 가입해야 했던 것 같습니다.

STM32F476G Discovery 보드의 예제인데, TIM2 의 CH1을 DMA 트리거 소스로 프로그램 되어 있습니다.
하지만 STM32F411,STM32F446 에서는 TIM2/CH1 을 DMA 소스로 했을 때 트리거 신호가 나오지 않는 문제가 발생했습니다.

아무리 해도 안되서 TIM1/CH1 을 트리거 소스로 사용하니 트리거 신호가 동작했습니다.

원인은 모르겠으나, TIM1 과 TIM2 의 큰 차이는 Repetition Count 가 있고 없고의 차이인데 확실히 트리거 신호가 안나오는 이유는 모르겠지만, TIM1/CH1 은 동작합니다. (원인을 알고 계시면 알려 주시면 감사하겠습니다)

다음은, STM32CubeMX 툴로 프로젝트를 만드는 과정을 그림으로 올려 보겠습니다.

먼저 Pinout 탭에서 사용할 기능을 설정합니다.
NuCleo 보드에서 클럭을 8MHz Bypass 클락과 32.768 KHz 크리스탈을 사용하므로 RCC를 그에 맞게 설정하고,
트리거 소스로 사용할 TIM1 의 Clock Source 를 Internal Clock, 출력 채널을 PWM Generation CH1 으로 설정합니다.
이 때, GPIO 출력으로 사용할 포트는 CubeMX 툴에 표시되지 않습니다. (나중에 알았는데, GPIO OUT 으로 설정하면 녹색으로 표시 된답니다. 속성으로 하다 보니, 모르는 것이 많네요.^^)



실제 Nucleo 보드의 출력핀은 다음과 같습니다.



다음은 큐브 툴에서 Clock을 설정합니다. 입력 클럭 8MHz,32.768 KHz 클럭을 소스로 시스템 클럭은 STM32F446 의 최대 클럭인 180MHz 로 설정을 했습니다. 180MHz 를 쓰고 엔터키만 치만 나머지를 자동으로 맞춰주니 골치아파할 필요가 없습니다.



다음은 TIM1 주기와 듀티 및 DMA 설정을 위해서 CubeMX 툴의 configuration 탭을 선택하고 TIM1 을 클릭합니다.



parameter Setting 에서 제 경험상 가장 빠른 출력 주기인 7로 주기를 설정하고, High 로 유지할 펄스는 2로 설정합니다.
주기를 더 빠르게 하면 트리거 출력이 나오다 없어지거나 처음부터 안나옵니다.



다음은 DMA Setting 입니다.
수정할 부분은 Direction 을 Memory to Peripheral 로 고치고, 저는 Data Width를 Byte로 해서 출력 하려고 Byte로 설정했습니다.



이렇게 하고 나서 Keil compiller 프로젝트 형식으로 소스 코드를 만들기 위해 project/Setting 메뉴에서 Toolchain / IDE 옵션을 MDK-ARM V5 로 선택합니다.



그런 다음, project/Generate Code 를 클릭하면 Keil 프로젝트가 만들어 집니다.



main.c 의 main() 함수에 추가한 내용은 아래 붉은색 네모의 내용이 다 입니다.



그런데 가장 중요한 부분은 stm32f4xx_it.c 에 있는 DMA IRQHandler 함수에 TIM1 을 멈추게 하는 코드를 넣어야 한다는 점입니다.
저는 예제를 받아서 수정을 한 것이 아니고, 참고로 해서 CubeMX 툴에서 다시 만들었기 때문에 main.c 파일만 보고 수정된 부분을 비교해서 수정을 했기 때문에 stm32f4xx_it.c 파일을 수정해야 한다는 생각을 못하고 프로그램을 실행하니 아무리 해도 동작이 안되었습니다. 원인은 이 코드를 수정하지 않아서 트리거 클락이 끝없이 나가는 것이 문제였습니다.

그리고 트리거 클럭은 앞뒤로 더미 클럭이 나가니, GPIO를 사용할 경우 주의하시기 바랍니다. SPI로 동작 시킬 경우에는 데이터에 /CS 데이터를 실어서 출력하면 더미데이터를 신경 안 쓰고 사용할 수 있을 것 같습니다.
더미 데이터는 클럭이 빠를수록 더 많이 나옵니다.



다음은 첨부된 프로젝트 파일의 실행 결과 입니다.



다음의 소스 코드를 첨부합니다.

mem_t_GPIO_v1.zip


휴, 안도의 한숨이 나옵니다.

일주일 가량의 시간이 걸려 SDIO(4Bit Bus Width, DMA) 로 micro-SD Card 읽기를 성공했네요.

속도는 약 8.23 MByte / s  정도 나오네요. 아마도 오실로 스코프 상에서는 좀 더 빨라서 9 MBte /s 에 더 가까울 것 같습니다.
DMA를 사용하지 않았을 때는 1.2 MByte / s 정도 나왔으니 8배 정도 빠를 것 같습니다.



검색해 보니, 현재 SD 카드가 엄청 느린 녀석이었는데 좀 더 좋은 것을 쓰면 속도가 더 나올 수 있는 것 같습니다.
어떤 사람은 19 MByte/s 까지 나온다고 합니다. (링크)



DMA 의 개념은, 보통 메모리에 있는 데이터를 읽어서 SPI/UART/SDIO 통신으로 내보내는 경우에 메모리에서 데이터를 읽는 시간이 상당이 걸리는데 그 시간을 줄이는 목적이 있습니다. 
다른 좋은 장점이 또 있을 것 같은데요, 일단은 제가 실감한 것이 그렇습니다.

예를 들자면 다음과 같은 그림으로 잘 설명이 될 것 같습니다.



실제로 제가 측정한 내용은 위와같이, 데이터를 읽는 시간이 SDIO로 보내는 시간 보다 엄청 걸렸습니다. 
위의 그림과 예전의 테스트 상황이 99.9%(?) 일치 했습니다. ^^

그림으로 보니 엄청 빨라질 것 같죠? (DMA를 쓰면 빨라 집니다. 확실히~)

DMA를 사용하려다가 몇가지 복병을 만났었는데, 소개해 볼 까 합니다.
(참고로 저는 Receive 동작만 테스트 했습니다)
1. DMA 인터럽트 우선순위를 설정하지 않아서 프로그램이 멈추는 문제 발생
   - DMA RX 인터럽트 추가한 후, 인터럽트 번호를 잘 설정해 줄 것.
2. DMA 로 f_read 함수를 실행 시, BLOCK_SIZE(512 Byte) 이상을 읽으면 Error 리턴.
   - Err 리턴을 무시할 것. (현재 프로그램을 정리하다 보니 이 현상이 없어 졌는데, 조금 더 두고 봐야 겠습니다)
3. 4gb 이상의 u-SD 카드 동작시 SD_read() 함수 입력 변수 계산 오류
   - SD_read() 함수 내의 BSP_SD_ReadBlocks() 함수의 입력 변수 계산 코드 수정.
     (아직 테스트는 못 해봄. 4gb 이상 sd-card 없음)

위 내용의 해결 과정에 앞서서, 일반 polling 방식을 DMA 방식으로 바꾸는 방법을 알아보겠습니다.
저는 거의 모든 STM32 시리즈의 SDIO,u-SD Card 문서 및 웹사이트를 다 돌아 다니면서 코드를 분석도 해 봤는데 별로 건지지 못했고, 노가다 테스트로 해결했습니다.

결론은.. 진짜, 쉽습니다. 

Cube 툴로 생성된 Keil 소스코드에서, Middlewares/FatFs 폴더의 sd_diskio.c 를 열어서 SD_read()함수의 내부 코드를 바꾸면 됩니다.
SD_read() 함수 내의 폴링 방식의 Read 함수인 BSP_SD_ReadBlocks 를 BSP_SD_ReadBlocks_DMA 로,
   단지 이름만 바꿔 주면 됩니다.





/**
  * @brief  Reads Sector(s)
  * @param  lun : not used
  * @param  *buff: Data buffer to store read data
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to read (1..128)
  * @retval DRESULT: Operation result
  */
DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
{
  DRESULT res = RES_OK;
  /*
  if(BSP_SD_ReadBlocks((uint32_t*)buff, 
                       (uint64_t) (sector * BLOCK_SIZE), 
                       BLOCK_SIZE, 
                       count) != MSD_OK)*/
/*
  if(BSP_SD_ReadBlocks_DMA((uint32_t*)buff, 
                       (uint64_t) (sector * BLOCK_SIZE), 
                       BLOCK_SIZE, 
                       count) != MSD_OK)*/
  if(BSP_SD_ReadBlocks_DMA((uint32_t*)buff, 
                       ((uint64_t)sector * BLOCK_SIZE)
                       BLOCK_SIZE, 
                       count) != MSD_OK)
  {
    res = RES_ERROR;
  }
  
  return res;
}

그럼 다음으로 제가 만난 복병과 그 해결 방법을 설명 드리겟습니다.
먼저 위에 그림에서 추가로 4gb이상의 sd Card를 읽을 때, 수정해야 하는 내용이 먼저 나와서,
4g sd-card가 없어서 아직 테스트는 못해 봤지만 코드의 수정 내용을 알려 드립니다.

[4gb 이상의 SD Card Access 시 수정할 사항]
현재 sd_diskio.c 파일 내에 있는 SD_read 함수 내부의 코드에서 
BSD_SD_ReadBlocks/BSD_SD_ReadBlocks_DMA 함수의 2번째 파라 메터의 계산 식이 틀려서 4gb 이상의 Sd card가 Access 되지 않는다고 합니다.
 (uint64_t) (sector * BLOCK_SIZE) 을 ((uint64_t) sector * BLOCK_SIZE)로 수정하면 된다고 하네요.

다음은 4gb sd card 이상의 동작에서 오류 수정을 당부하는 내용의 원문입니다. [링크]



[DMA로 수정한 상태에서 f_read 동작이 안되는 경우]
제가 처음에 SD_read 함수에서 BSP_SD_ReadBlocks() 함수를 BSP_SD_ReadBlocks_DMA()함수로 바꿨는데 프로그램이 멈춰버리더군요. 원인은 우연히 찾았는데, DMA 인터럽트 우선순위를 CUBE 툴에서 설정하지 않아서 우선순위가 0으로 되어 있었던 것을 Rx는 1, Tx는 2 로 수정을 했더니 프로그램이 정상 동작을 했습니다.
CUBE 툴에서 인터럽트 우선 순위는 따로 사용자가 결정을 해 줘야 하는 것 같습니다.

FRESULT 에러 리스트 (ff.h)
typedef enum {
FR_OK = 0, /* (0) Succeeded */
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
FR_INT_ERR, /* (2) Assertion failed */
FR_NOT_READY, /* (3) The physical drive cannot work */
FR_NO_FILE, /* (4) Could not find the file */
FR_NO_PATH, /* (5) Could not find the path */
FR_INVALID_NAME, /* (6) The path name format is invalid */
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
FR_EXIST, /* (8) Access denied due to prohibited access */
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
FR_NOT_ENABLED, /* (12) The volume has no work area */
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any parameter error */
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > _FS_SHARE */
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
} FRESULT;



다시 2달이 지난 현재(2016.11.07) STM32F446 에서 테스트를 진행 하고 있는데, 
u-SD CARD 를 mount 할 경우에 FR_NO_FILESYSTEM 에러(13)가 나더니, 예전에 사용한 방식(DMA 인터럽트 우선순위를 바꾸는) 을 사용해도 에러가 났고, UART2 인터럽트 priority 를 6으로 했더니 에러가 없어졌습니다.
(그냥 복불복 입니다. 됭 때도 있고 안될 때도 있는....)

하아~ 그런데 에러가 없어 진 다음에 다시 DMA interrupt priority 를 rx,tx 각각 0으로 돌리고, UART interrupt priority도 0으로
복구했는데도 정상 동작을 합니다. 그리고 나서 전원을 껐다 키니까 처음에 FR_NO_FILESYSTEM 에러가 나네요.
ㅎ~~ 원인은 접촉 불량 내지 선이 너무 길어서 노이즈가 탄 것 같습니다. 현재 u-SD Card 와 Nucleo 보드간 케이블 길이가
약 10cm 정도 되서 노이즈가 좀 심하리라 생각이 드네요. 
따라서 인터럽트를 바꿔서 정상동작을 하고 안하고의 문제나 512 Byte 읽을 때 Error 가 발생하는 원인도 접촉 불량이나 노이즈에 의한 문제로 보입니다. 

아.. 정말정말 정확한 이유는 모르겠지만, 인터럽트 우선순위를 변경하는 것과 u-SD-Card 인식은 관계가 없는 것은 확실합니다.
됐다 안됐다 하는 문제는 접촉 불량과 노이즈인 것 같습니다. 

선을 반으로 줄여 봤어도, 에러가 자꾸 났는데 납땜 문제로 노이즈가 더 심해 졌을 지도 모르겠고..
다시 u-SD-Card 소켙을 바꾸고 긴 케이블을 연결하니 잘 되는 것을 보니 u-SD-Card 소켙의 접촉 불량이나
선 불량 으로 보이고,  통신 속도를 늦춰도 똑같은 현상이 나타나는 것을 보아 고속 통신으로 인한 데이터의 지연 문제는 아닌 것 같습니다.

그냥 아트웍할 때, 신경써서 하면 잘 될 것 같네요.

되긴 하나, 왠지 찝찝 하여.. 다시 껐다 켜고를 반복 하니 또 에러가 났습니다.
u-SD Card 쪽 핀헤더에 연결한 점퍼 와이어를 꾹 눌러주면 되고 띠면 안되는 것으로 보아,
콘넥터 접촉불량으로 결론을 냈습니다. 제가 혹시 혼란을 가중 시켰나 모르겠습니다.

[결론] 

u-SD Card 인터페이스 시, 접촉 불량 안나도록 배선을 잘하라. ^^





[DMA로 수정한 상태에서 BLOCK_SIZE(512 Byte) 이상을 읽으면, f_read() 함수의 return 값이 Error을 리턴한다]
위의 소제목과 같이 Error를 리턴 합니다만, 그냥 무시하고 쓰면 됩니다. 
원인을 찾아서 고치면 좋겠는데, 시간과 능력이 부족하여 저는 그냥 쓰기로 했습니다.
이상하게, 에러는 발생하지만 동작에는 문제가 없었습니다.
(현재 프로그램을 정리하다 보니 이 현상이 없어 졌는데, 조금 더 두고 봐야 겠습니다)

참고로, 인터넷에서 알랑방구 좀 뀌는 외국인의 글을 소개하자면..
u-SD Card 속도를 올리기 위해서는 다음같은 사항을 점검하라고 하네요.
1. 데이터 버스폭은 4Bit 로 했는지 검토.
2. 코드를 DMA로 바꿨는지 검토.
3. 클럭 스피드 검토(Cube 툴에서 기본 설정: 24MHz, 최대 48MHz)

^^   저는 이 분이, sd 카드 DMA에 대해서 알려 주는 것도 같지만.. 지가 한 내용을 가지고 속도 빠르다고 자랑하는 것 같아 보였어요. 소스 코드도 옛날 것이고 받은 코드도 파일이 좀 빠져 있고, 결국엔 아무 도움도 안됐네요.

다만, DMA를 사용하면 빨라진다는 정도만 소득으로 얻었습니다.(링크 주소)



마지막으로 제가 첨부하는 예제의 설명 입니다.
여러가지 테스트 함수들이 들어 있는데, 소스 코드 보시고 참고하시고.. 실제로 실행되는 함수는 read_BMP_file_DMA() 함수입니다.
이 함수 내부의 GPIO ON/OFF 코드는 오실로 스코프로 DMA 시간 테스트를 위해서 만들어 놓은 것이니 괜히 고민하지 마시길 바랍니다. 또한 __nop() 함수는 MCU가 100MHz 동작하니까, 오실로 스코프로 봐도 GPIO 변환 지점이 잘 안보여서 딜레이 대용으로 넣은 것이니 신경 쓰지 않으셔도 됩니다.

예제 코드에서는 64x64 BMP 파일을 DMA 읽어서 UART2 포트로 전부 뿌리도록 되어 있습니다.
UART 출력 결과는 다음과 같습니다.



아가~~ ㄱ ^^ 
정말로 중요한 내용이 빠졌네요.
f_read 에서 한 번에 많은 데이터를 읽을 때, 데이터 버퍼의 크기에 맞게 스택 사이즈 늘려주셔야 합니다.
이전에 다룬 내용을 참고 하시기 바랍니다 




제가 테스트한 보드에서 사용된 MCU의 핀맵입니다.



소스코드 첨부합니다.

sd_LEDSigne_v100_2.zip


다음은 USE_LFN(Use Long Filename) 설정하는 방법에 대해서 알아 보겠습니다.


아주 오래 전에 윈도우에서 dir 하고 command line 명령을 치면,
파일명이 xxxxx~1 라고 나온 경험이 좀 있으실 겁니다.

파일 이름이 긴 파일을 표시하지 못하는 경우입니다. 
이것을 해결하는 방법은 간단합니다.

큐브 툴에서 Configuration 을 클릭해서 창이 하나 뜨고 거기에서 FATFS 를 클릭합니다.
그러면 다시 FATFS Configuration 창이 하나 뜨는데, 
여기서 Locale and Namespace Parameters 의 USE_LFN 항목을 클릭하여 Disable 되어 있던 것을 
Enable with static working buffer on the BSS(1) 로 바꿔 주면 됩니다.



그런 다음 project > Generate Code 하시고 Keil 컴파일러에서 컴파일해서 STM32f4xx 보드에 다운로드하고 실행하면
다음과 같이 긴 파일 이름을 쓰실 수 있습니다.


아래 13번 글과 함께 사용했던 소스 파일 첨부합니다.

sd_LEDSigne_v100.zip


+ Recent posts