[이 프로젝트에 사용된 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


안녕하세요,


요 근래, 파일 시스템 테스트 하다가 안되서 고생고생 했네요.

그 고생한 내용은 다음과 같습니다.

파일 관련 함수는 거의 다 테스트를 했는데, f_read() 함수만 유독 실행이 될 때도 있고, 
어떤 때는 계속 실행이 안되고 Hardfault Error 인터럽트로 점프해서 멈춰 있었습니다.

같은(거의 비슷한?) 입력 파라메터를 갖고 있는 f_write() 함수는 또 잘 된다.
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from a file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to a file */

하~.., 그래서 사용자 함수를 하나 만들어서 파라메터를 f_read() 와 같이 정의를 해서 테스트를 해보니 f_read()함수와 같은 증상이 발생. ㅜㅜ

안되는 원인을 찾아 보려고 f_read()함수의 파라메터의 입력을 각각 1가지씩 사용자 함수에 적용해서 테스트를 해 봤더니,
입력 인자 중 void* buff 가 문제였습니다.

원인은 입력 인자인 배열(void *buff)을 f_write()에서 보다 많이 잡아서 Stack overflow 가 걸리는 것이었습니다.

에러가 나면 스택 오버플로우 인지 확인도 못하고(혹시 아시는 분 있으시면 방법을 알려 주시면 감사하겠습니다),
그냥 프로그램이 멈춰버리니.. 며칠을 밤새고 나서야 찾을 수 있었습니다.

이번 글의 요점은, 파일 시스템 관련 함수를 사용할 경우에는 스텍을 충분히 확보하라 입니다.

그럼, 다음은 스텍을 늘리는 방법입니다. 방법은 2가지가 있네요.
1. startup_stm32f411xe.s 파일을 열어서 다음과 같이 스택 사이즈를 변경합니다.



2. CUBE 툴에서 project > Setting 메뉴에서 수정할 수도 있습니다. (저는 큐브 툴로 KEIL 소스를 만들어서 이런 방법도 있음을 알려 드립니다)



스택이 0x400 일 때 멈춰버린 프로그램이..



스택을 0x800 으로 고치니까 아주 잘 됩니다. 잘 되서 f_lseek()함수도 사용해서 파일의 offset를 막 이동해 가면서 읽어서 UART로 마구마구 뿌려 댔습니다. ^^



그런데, 나중에 알게된 사실인데... 

큐브 툴의 Help 메뉴를 누르면 UM1718.pdf 파일이 열리는데, 이 곳의 7번의 제목의 글에 스택을 충분이 할당하라는 내용이 약간 나옵니다. ㅜㅜ 저와 같은 문제로 고생하는 사람이 많진 않지만 약간 있나 봅니다.
많았으면 반드시 스택을 많이 잡고 시작하라고 했을 텐데.





그리고 이 PDF의 파일 시스템 예제가 인터넷에 돌아다니는 예제 보다, 가장 잘 나온 것 같습니다.
참고하시면 도움이 될 것 같습니다.


STM32F4xx에서 uart2 로 printf()함수를 사용하는 방법을 테스트 해보고 정보를 공유합니다.


다음과 같은 코드를 main.c 파일에 추가하면 됩니다.

/* USER CODE BEGIN 0 */
#ifdef __GNUC__
 #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
 #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
 
PUTCHAR_PROTOTYPE
{
 HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 1);
 return ch;
}
/* USER CODE END 0 */



이 코드를 추가하기 전에 huart2 는 정의를 해야겠죠.
그리고 uart HAL 함수 HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 1); 를 사용해서 폴링으로 1Byte 전송하도록 되어 잇습니다.


이번에는 GPIO에 여러핀을 동시에 출력하는 방법을 알아보겠습니다.


핀 설정도 동시에 할 순 있는데, 좀 귀찮아서 핀설정은 각 핀마다 했습니다.

이제 제가 프로젝트를 시작해야 해서, 프로젝트에서 사용해야 할 핀들을 다 포함시킨 상태에서
GPIO 병렬 포트로 사용할 핀을 지정을 해 봤습니다.

GPIO 설정은 Cube 툴에서 할 수 없으니까, 핀이 표시가 되지 않아서 따로 사용할 핀을 정리해 봤습니다.




그리고 현재 사용한 Nucleo 보드에서 핀헤더의 위치도 표시해 봤습니다.



자.. 그럼 핀 설정은 정리해 보자면,

DATA[7:2] : PA15,14,13,12,11,10

DATA[1:0] : PC7,6

SYNC CLK : PB5


핀 설정 코드는 1핀마다 설정을 했더니 너무 길어서 소스코드 첨부해 놓을 테니 참고하시기 바람니다.

main.c 파일 안에 static void MX_GPIO_Init(void) 함수 보시면 되겠습니다.


이렇게 설정을 했습니다. 간단히 설명을 해 보자면, SYNC CLK 의 Rising Edge 마다 8비트 데이터를 출력 하는 테스트 입니다.

8비트 병렬 통신을 구현할 생각이고, STM 칩으로 8비트 데이터를 보내고 SYNC 출력을 1번 내보내는 겁니다.


8번 출력(0x01->0x02->0x04->0x08->0x10->0x20->0x40->0x80) 을 반복해서 내보내도록 했습니다.


다음은 main.c 의 main()함수의 주요 동작 코드입니다.


  for (i=0;i<8;i++)

  {

par_data[i] = (1<< i);

      // (0x01->0x02->0x04->0x08->0x10->0x20->0x40->0x80) 데이터 생성

  }


  while (1)

  {

par_ReAssemble(par_data,8);

       // (0x01->0x02->0x04->0x08->0x10->0x20->0x40->0x80) 데이터 출력 + SYNC_CLK 출력

HAL_Delay(1);

  }




현재 핀을 여러용도록 사용했더니, 출력할 포트가 2개로 나뉘어 버려서 8비트 데이터를 재정렬하도록 프로그램을 만들었습니다.

재정렬 함수의 코드는 다음과 같습니다.

void par_ReAssemble(uint8_t *par_data,uint16_t cnt_data)

{

uint16_t i;

for (i=0;i<8;i++)

{

// DATA[7:2] : PA15,14,13,12,11,10

GPIOA->ODR = (((uint32_t)par_data[i]) << 8)&0xFC00;

// DATA[1:0] : PC7,6

GPIOC->ODR = (((uint32_t)par_data[i]) << 6)&0x00C0;

//HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5,GPIO_PIN_SET);

//HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5,GPIO_PIN_RESET);

GPIOB->BSRR = GPIO_PIN_5;

GPIOB->BSRR = (GPIO_PIN_5<<16);

}

}


위의 함수에서 GPIOA->ODR 이 보이시죠?

이 레지스터가 각각 16비트폭으로서, 병렬 포트를 출력하도록 하는 레지스터입니다.

쉬프트 연산과 마스크 연산을 해서 병렬포트 출력 레지스터에 값을 쓰면 해단 핀들로 0또는 1이 출력 됩니다.


1비트 출력은 2가지 방법이 있습니다.

1. HAL 드라이버에서 제공하는 함수를 사용.

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5,GPIO_PIN_SET);

2. 직접 레지스터에 쓰기.

GPIOB->BSRR = GPIO_PIN_5;

GPIOB->BSRR = (GPIO_PIN_5<<16);


HAL_GPIO_WritePin 함수는 누구나 인식하기 쉬워서 보면 아시겠고,

BSRR 레지스터는 32비트 구조인데, 1을 해당 비트에 써주면 하위 16비트는 SET, 상위 16비트는 RESET 기능을 합니다.




위에서 2가지 방법이 있다고 했는데, 사실은 HAL.. 함수에서 BSRR 레지스터를 사용하고 있어서 본래는 1가지 방법입니다.

그런데 제가 왜 레지스터에 집접 썼는가 하면 HAL.. 함수가 너무 느려서 입니다. 

테스트를 해 본 결과, 레지스터를 사용한 방법보다 2배나 느리더군요.


다음은 같은 동작을 수행했을 때, 왼쪽이 HAL 함수, 오른쪽이 레지스터에 직접 쓴 방법을 사용한 결과 입니다.



다음은 8비트로 1바이트를 보냈을 때, 직접 속도를 계산해 봤습니다. 



나중에 설명을 하려고 했는데, 먼저 해 버렸네요.

이게 제가 알려드리고자 한 핵심이고 앞으로 설명할 나머지는 크게 중요하진 않습니다.


사용자의 편의성이냐? 속도냐? 둘 중 하나를 선택하시면 됩니다.


그런데.. 저 파형이 링잉이 발생하는게 거슬리네요. 파형이 원래 그런것인지 오실로 스코프가 문제인지.. 궁금하네요. 내일 오실로 스코프 바꿔서 테스트 해 봐야겠습니다.



다음은 HAL 함수를 사용해서 프로그램을 돌려 본 결과 입니다.

검증을 위해 4채널 오실로 스코프를 이용해서 1로 예상되는 포인트만 찍었습니다. 9개의 신호를 한번에 찍을 수가 없어서 3개의 프로브를 나눠서 측정을 했습니다.


[ SYNC_CLK 와 DATA 0,1,2 ]



[ SYNC_CLK 와 DATA 2,3,4 ]



[ SYNC_CLK 와 DATA 5,6,7 ]



다행이 예상대로 잘 나왔네요.


그럼 이만 마치겠습니다.


소스 코드 첨부 합니다.

perallel_GPIO.zip



구글 크롬을 실행하고, 다음의 사이트로 갑니다.


여기서 platform -> Development Boards 로 가서, Boards 를 클릭해 주면 이 사이트에서 컴파일할 수 있는 보드들의 목록이 나옵니다.





저는 STM32F411 MCU로 된 NUCLEO-F411 을 갖고 있는데, 미리 지원하는 보드를 알아보고 국내 판매 사이트에서 1개 사셔도 괜찮겠습니다. 어떤 것은 1만원 정도밖에 안하네요.
게중에 NECLEO-F103RB는 아두이노를 이식할 수도 있으니 재밌는 작업을 많이 할 수 있겠네요.
제가 사용하는 보드가 여기 있네요. NECLEO-F411RE



$12.74 밖에 안한답니다.



다시 home 으로 이동해서 developer Resource 를 클릭한 다음 Compiler를 클릭합니다.



그러면, 회원 가입하는 화면이 나옵니다. 여기 가입해야만 컴파일러를 쓰실 수 있습니다. ^^



log in 하면, 크롬 웹에서 바로 컴파일러가 실행 되는데, 저는 이전에 Platform을 선택했나 봅니다. 이전에 테스트 한 프로젝트가 서버에 기록되어 있군요. 혹시 Platform 을 선택하지 않았거나 추가할 경우는 오른쪽 위의 그림을 클릭 해 주시면 됩니다. Platform을 선택하지 않았으면 자동으로 선택 창이 뜰 것 같은데.. 이전의 상황이 기억이 안나는군요.



다음은 위에서 클릭하면 뜬다고 설명했던 platform 선택 및 추가 하는 창입니다.



플랫폼 선택하고 나서, 새로운 프로그램을 하나 만들어 봅시다. 왼쪽 위에 new를 누르면 창이 하나 뜸니다.



해당하는 보드의 플랫폼을 선택하고 탬플릿을 눌러 보면 다양한 예제들이 나옵니다. 제가 Cube 툴과 Keil 툴로 개고생해서 만들었던 예제들이 다 들어 있네요. ㅠㅠ SDIO 로 구현한 u-SD-Card 예제는 없네요.



Blinky LED test for the ST Nucleo boards 플랫폼을 선택해서 프로그램을 하나 만들어 봅니다.



OK 를 누르면 다음과 같은 창이 뜨고, 컴파일 버튼을 눌러 봅니다.



컴파일 버튼을 누르면 프로그램이 컴파일되고 난 결과물인 Nucleo_blink_led_NUCLEO_F411RE.bin 파일이 자동으로 다운로드 폴더에 저장 됩니다.



이 파일을 폴더 열기로 열어서, bin 파일을 ST-LINK 디버거에 의해 자동으로 생성된 USB DISK인 NODE_F411RE(J:) 에 드래그-앤-드롭 해 주면 자동으로 프로그램이 STM32F411 MCU에 다운로드된 후, 프로그램이 실행됩니다.


Nucleo 보드의 녹색 LED가 깜박깜박 하고 있군요. ^^


이번에는 SPI 통신을 테스트 해 봤습니다.


원래는 WIFI 모듈을 연결하여 테스트를 진행하려고 했는데, 
회사에서 WIFI 모듈은 잠깐 미뤄두고 급한 일을 먼저 처리하라고 해서..
IC를 연결하지 않고 SPI Write만 동작시키고, 파형만 오실로 스코프로 찍어 봤습니다.


Nucleo 보드의 SPI 용 핀 할당은 다음과 같습니다.



STM32F411 의 SPI용  핀할당 내용입니다.
SPI_CS 는 일반 GPIO로 제어했습니다.



아무 설정도 안하고 기본 SPI 스피드는 42MHz 입니다. Cube 툴에 표시 되네요.



다음은 SPI_CS 를 일반 GPIO 출력으로 설정하는 코드 예제입니다.
PA8 핀을 사용했습니다. 설정은 내부 풀업,출력,Fast Speed,초기값 HIGH  입니다.



기본 SPI_CLK 스피드인 42MHz 로 출력을 테스트 해 보니, 오실로 스코프가 파형을 못 쫒아가서 파형이 엉망으로 나오네요.



다음은 42MHz SPI CLK 확대한 그림입니다. 스코프가 못 쫒아갑니다. 오실로 스코프 회사에 연락해 보니, 측정할 클럭의 10배는 되야 보인다 하더군요. 42MHz 이면 500MHz 오실로 스코프가 필요하단 말이네요. 제가 갖고 있는 건, 200MHz 입니다.



다음은 SPI_CLK를 21MHz로 설정한 소스 코드입니다. 



이 코드로 동작시킨 21MHz SPI_CLK 과 파형입니다.



그리고 다음은 21MHz SPI_CLK 확대 파형입니다. 클럭이 잘 보이네요.



그런데 이상한 점은 42MHz 나 21MHz 나 통신 속도가 똑같다는 점입니다.
42MHz가 전송 속도는 21MHz 보다 2배는 빨라야 하는데, 10 Byte 전송 테스트시 같은 시간이 걸린다는 게 좀 의아하네요.

좀 전에, 스코프 회사에서 점검하고 갔는데.. 아무리 해도 개선이 안되네요.
금요일에 300MHz 오실로 스코프를 가지고 온다니까, 스코프가 문제인지 파형이 문제인지 확인할 수 있겠습니다.


DMA 나 인터럽트로 동작시켜야 SPI_CLK 사이의 구간이 짧아 지려나? 
현재는 회사 다음 일정이 바빠서 인터럽트나 DMA 테스트는 나중에 진행하겠고, 
테스트 후에 자료를 다시 올리겠습니다.





테스트 한 소스코드 첨부합니다.

SPI_test.zip


일단 동작이 되서 아무생각 없이 넘어 갔는데,

데이터 전송률을 계산하려다보니 SDIO 클럭을 계산하는 방법을 찾아 봤습니다.

설명이 명확하게 안나온 것인지, 제가 이해를 못 한 것인지 ... 해서 또 노가다를 뛰었습니다.

레퍼런스 메뉴얼에는 다음과 같은 블럭도가 있었습니다.



내용은 SDIO 는 PCLK2 와 SDIOCLK 2개를 사용한다는 것이고,
실제 외부에 나와있는 SDIO_CK 는 SDIOCLK 와 관계가 있고,
SDIOCLK 는 48MHz 이다.

그런데, "SDIOCLK 는 PCLK 와 관계가 없는것인가? " 라는 내용을 잘 찾을 수가 없었습니다.
관계가 있는지 없는지 확인을 해 봤습니다.



APB2CLK 을 2분주하고 SDIO_CK 를 오실로 스코프로 찍어 보니, 아무런 변화가 없었습니다.
즉 PCLK2 하고는 관계가 없다고 볼 수 있겠죠. 그렇다면 SDIOCLK 는 48MHz로 내부에서 PCLK와 상관없이 고정된 틀럭이라고 보면 될 것 같습니다.


그러면 SDIO_CK 는 어떻게 설정을 하는가를 찾아 보니, CUBE 툴에 다음과 같은 내용이 있었습니다.



SDIO_CK = SDIOCLK / [CLKDIV + 2] 라고 되어 있습니다.
현재 설정은 void MX_SDIO_SD_Init() 함수에서 CLKDIV 가 0으로 되어 있으니 SDIO_CK = SDIOCLK(48MHz) / [0 + 2] = 24MHz 입니다.
실제로 오실로 스코프로 봤습니다.



계산상으로 21MHz 이니까, 거의 24MHz 가 나오네요. 대략 맞네요.

그럼 CLKDIV를  2로 해서 4분주를 하면 12MHz 정도 나올 것으로 예상하고 오실로 스코프로 측정을 해 봤습니다.





SDIO_CK 가 계산상으로는 10MHz 로 대략 12MHz 정도로 일치한다고 봅니다. 눈으로 보고 측정을 하니 오차가 좀 있네요.


따라서 결론은,
1. SDIO_CK 는 SDIOCLK 하고만 관계가 있고, 
2. SDIOCLK는 48MHz로 고정된 독립된 클락이다.
3. SDIO_CK 의 속도를 조정하는 공식은 SDIO_CK = SDIOCLK / [CLKDIV + 2] 이다.


STM32F4xx 의 SDIO 통신방식으로 u-SD Card 를 다뤄 보겠습니다.


여러가지 예제를 보고 그대로 했는데도 자꾸 실수를 하는 바람에,
동작 시키기까지 하루 종일 걸렸네요. ^^

SD 카드 모듈은 Waveshare 에서 만든 그냥 u-SD Card 모듈인데, 엘레파츠에서 샀습니다.



Nucleo 보드와 SD Card 모듈의 H/W 연결은 다음과 같습니다.



실제 연결한 그림입니다. 사진을 잘 못 찍어서 이렇네요. ^^



SDIO의 큐브에서 설정은 다음과 같습니다. SDIO 를 넣었더니 clock 설정에서 APB1 peripheral clocks(MHz) 가 42MHz 이하로 줄여야 한다고 에러가 나서 42MHz로 맞추다 보니 시스템 클럭은 84MHz로 줄어 버렸습니다.



SDIO 4Bit 핀아웃은 다음과 같습니다. 데이터버스 4개,CLK,CMD 로 총 6개의 I/O 가 사용되고 경우에 따라서 CD(Card Detect) 입력을 1개 쓸 수도 있습니다. 일단은 테스트니까, CD는 사용 안했습니다.



프로그램은 그냥 Polling으로 처리했습니다. 나중에 DMA를 사용하면 아주 빠르다고 하네요.
프로그램은 
1. SD card 초기화하고, 
2. 텍스트 파일을 1개 만들어서 , 
3. 파일에 스트링을 몇자 쓰고 ,
4. 파일을 닫아서,
5. PC에서 SD Card를 읽어서 확인해 봤습니다.

다른 것은 별 어려움이 없었는데, 4Bit Wire로 바꾸는 것이 좀 어려웠습니다.
어딘가에서 바뀔 수도 있는지 모르겠는데, BSP_SD_Init(); 함수를 실행해야 4비트 방식으로 바뀌는 것인데..
프로그램 어디에도 BSP_SD_Init();함수를 실행하지를 않아서 SD Card 초기화 하고 나서 main()함수안에 BSP_SD_Init(); 함수를 실행하도록 수정했습니다.

int main(void)
{

  /* USER CODE BEGIN 1 */
    uint8_t i;
    char tx_buf[100];
    uint8_t file_path[13] = "testfile.txt";
    file_path[12] = '\0';
    uint8_t testBuffer[16] = "SD write success";
    UINT testBytes;
  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_TIM3_Init();
  MX_USART2_UART_Init();
  MX_I2C1_Init();
  MX_SDIO_SD_Init();
  MX_FATFS_Init();

  /* Initialize interrupts */
  MX_NVIC_Init();

  /* USER CODE BEGIN 2 */
for (i=0;i<10;i++)
{
sprintf(tx_buf,"UART Test ... [%3d] \n\r",i);
HAL_UART_Transmit(&huart2,(uint8_t *)tx_buf,cus_Size_of(tx_buf),3);
}
HAL_TIM_Base_Start_IT(&htim3);

BSP_SD_Init();

   :
   :
   :
}




다음은 이 프로그램에 의해 UART로 response 를 출력해 본 내용입니다. (제가 디버그용으로 UART를 주로 쓰는 편이라..)
response 가 0 이면 정상입니다.



다음은 SD card 내용과 그안에 만들어진 텍스트 파일의 내용입니다.





소스파일 첨부 합니다.


+ Recent posts