STM32F4xx 에서 USB MSC 를 테스트하다 보니 가끔 파일이 깨지는 현상이 발생했습니다.


여러가지 개선책을 생각하고 적용을 해 봤습니다.
1. DMA 적용.
: 많이 깨짐 현상이 줄어들었다. 
그래도 가끔 깨지는 파일이 발생했고, DMA를 적용하기 전에는 여러 곳이 깨졌는데, DMA를 적용하니 꼭 파일의 끝 부분이 깨진다.
2. 섹터 크기 일치 시킴.
STM32F4xx 의 f/w 에서 설정된 파일 시스템은 FAT32,Sector 크기는 512, 
이에 연결되어 있는 SD Card 의 format을 보면 파일시스템는 FAT였고 Sector 크기가 8192 였다.
이것을 일치 시켜서 테스트해 봤는데, 깨짐 현상이 아직까지 나타나지 않았다.
SD Card 의 포맷을 다음과 같이 FAT32,Sector Size = 512 로 수정.

[CUBEMX 툴에서 설정된 섹터 크기 정보]






[MSC 에서 설정을 바꿔서 다시 포맷]




SDIO - 4Bit 방식으로 SD-Card 를 STM32F4xx 와 연결했고,

USB MSC(Mass Storage Class) 로 PC 와 연결하면 USB Drive 가 생성되는 프로그램에서 테스트한 결과입니다.

우선, SDIO-4bit , SDIO CLK-42MHz 로 설정했는데 USB MSC로 동작 시키면 생각 보다 너무 느립니다.
몇 MByte/s 쯤 나오지 않을까 생각 했는데 1MByte/s 도 안나옵니다.

기본적인 원인은 USB_FS 최대 속도가 12Mbit/sec (1.5MByte/s) 라는 점입니다.
여러가지 콘트롤을 위한 패킷을 빼면 1Mbyte 정도 나오리라 예상이 되는데, 그래서 몇 MByte/s 는 나올리가 없는 것이죠.




다음은 SDIO CLK 을 여러가지 값으로 변경시의 속도 입니다. 
위의 USB Speed 를 SDIO CLK 테스트를 해 보고 나서 알아서, 왜 이런 쓰잘떼기 없는 테스트를 했나 하는 자괴감이 드네요. ㅜㅜ
[아래 그림의 번호 설명]
1. 약 3MByte 의 파일 5개를 PC에서 STM32F4xx 의 USBMSC 로 Copy.
2. 약 3MByte 의 파일 5개를 STM32F4xx 의 USBMSC에서  PC로 Copy.
3. 약 250MByte 의 파일 1개를 PC에서 STM32F4xx 의 USBMSC 로 Copy.
4. 약 250MByte 의 파일 1개를 STM32F4xx 의 USBMSC에서  PC로 Copy.




혹시 Sector 의 크기와 속도의 관계는 없는 것인가 하는 생각에, 섹터 크기를 CUBEMX 툴 설정에서 512 에서 4096으로 늘려서 테스트 해 봤습니다. 




별 차이 없었습니다.




섹터를 변경할 때에, MSC 에 의해 생성된 USB Drive 포맷도 할당 단위 크기를 변경해서 수행했습니다.
섹터의 크기는 디스크의 반응 속도에 영향을 미쳤습니다.
섹터가 많으면 그 만큼 파일을 검색하는 시간이 많이 걸리는 듯 하여, 더 많은 시간이 지나야 USB Drive 가 PC에서 나타나네요.
즉, USB MSC 의 할당 단위가 크면(512바이트 보다 4096 바이트가) 응답 속도가 빠르다.





참고 : [USB 버전 별 속도 비교]




안녕하세요, 오랜만입니다.

전광판 프로젝트가 얼추 마무리되어 다시 활동을 재개 합니다. ^^

전광판 프로젝트에서 파일을 변환하는 기능이 필요해서 검토를 하다보니,
파일을 변환하려면, 읽을 파일과 쓸 파일, 이렇게 2개의 파일을 열어야 하네요.

그럼 먼저 여러 개의 파일을 열려면 어디를 고쳐야 하는지 찾아 봤는데,
다행히도 CUBEMX 는 기본으로 2개의 파일을 열 수 있도록 셋팅이 되어 있었습니다.
총 255 개의 파일을 열 수 있다고 합니다.

셋팅 방법은 CUBEMX 의 Configuration TAB 에서, Middleware 의 FATFS 를 선택합니다.




그 다음으로, FATFS Configuration 의 Set Define 에서 FS_LOCK 값을 수정하면 동시에 열 수 있는 파일의 갯수를 조정할 수 있는데, 설정값에 따라 총 255개의 파일을 동시에 열 수 있습니다. 저는 동시에 2개의 파일만 열려고 하니, 디폴트로 2로 그냥 놔두겠습니다.
혹시나 해서 이 값을 1로 바꾸니, 나중에 열도록 한 파일이 안 열리더군요. 
따라서 이 값이 동시에 열 수 있는 파일의 갯수를 의미함이 틀림없습니다. ^^




USB MSC(Mass Storage Class) 는 예전에 SD_Card 를 USB Drive 로 동작시켰던 글에서,
귀찮아서 DMA를 동작 시키지 않도록 프로그램했습니다. 
DMA를 사용하지 않는 방법은, 예전 글에서 DMA 를 설정하는 부분과 인터럽트 우선 순위를 설정하는 작업을 생략하고,
usbd_storage_if.c 파일의 STORAGE_Read_FS() 함수와 STORAGE_Write_FS()함수의 내부에 쓰인 함수를 일반 폴링 함수로 고치면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*******************************************************************************
* Function Name  : STORAGE_Read_FS
* Description    : 
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
int8_t STORAGE_Read_FS (uint8_t lun, 
                        uint8_t *buf, 
                        uint32_t blk_addr,                       
                        uint16_t blk_len)
{
  /* USER CODE BEGIN 6 */ 
  BSP_SD_ReadBlocks((uint32_t *)buf, blk_addr * STORAGE_BLK_SIZ, STORAGE_BLK_SIZ, blk_len);
  //BSP_SD_ReadBlocks_DMA((uint32_t *)buf, blk_addr * STORAGE_BLK_SIZ, STORAGE_BLK_SIZ, blk_len);
  return (USBD_OK);
  /* USER CODE END 6 */ 
}
 
/*******************************************************************************
* Function Name  : STORAGE_Write_FS
* Description    :
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
int8_t STORAGE_Write_FS (uint8_t lun, 
                         uint8_t *buf, 
                         uint32_t blk_addr,
                         uint16_t blk_len)
{
  /* USER CODE BEGIN 7 */ 
  BSP_SD_WriteBlocks((uint32_t *)buf, blk_addr * STORAGE_BLK_SIZ, STORAGE_BLK_SIZ, blk_len);
  //BSP_SD_WriteBlocks_DMA((uint32_t *)buf, blk_addr * STORAGE_BLK_SIZ, STORAGE_BLK_SIZ, blk_len);
  return (USBD_OK);
  /* USER CODE END 7 */ 
}
cs

그럼 다음으로, 파일을 2개 열어서 테스트하기 전에 USB MSC 로 읽을 파일을 PC에서 STM32F4 에 연결된 SD-Card 로 Copy 했습니다.
인터넷에서 돌아다니는 해리포터 소설 1장 만 띠어다 herry-1.txt 로 만들어 Sd card 에 복사 했습니다. 
혹시 몰라 2장도 넣는데 .. 파일 3개 여는 테스트는 귀찮네요. ^^. 2개가 되면 3개도 되겠죠 뭐.




다음은, herry-1.txt 파일에서 512 바이트를 읽어서 herry-cnv.txt 파일에 쓰는 소스 코드입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
int main(void)
{
 
  /* USER CODE BEGIN 1 */
 
  /* 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_SDIO_SD_Init();
  MX_USART1_UART_Init();
  MX_FATFS_Init();
  MX_USB_DEVICE_Init();
 
  /* USER CODE BEGIN 2 */
    HAL_SD_ErrorTypedef res_sd;
    FRESULT res;
    FILINFO fno;
    FIL fil_1,fil_2;
    DIR dir;
    FATFS fs32;
    uint32_t byteswritten, bytesread;                     /* File write/read counts */
    uint8_t read_buf[1000];
    
    
    char path[200];
    #if _USE_LFN
    TCHAR lfn[_MAX_LFN + 1];
    fno.lfname = lfn;
    fno.lfsize = sizeof lfn;
    #endif
    
    res = f_mount(&fs32,SD_Path,1);
    sprintf(path,"herry-1.txt");
    res = f_open(&fil_1, (char*)path,FA_READ);
    sprintf(path,"herry-cnv.txt");
    res = f_open(&fil_2, (char*)path, FA_WRITE | FA_CREATE_ALWAYS);
    
    res = f_read(&fil_1, &read_buf[0],512&bytesread);
    res = f_write(&fil_2, &read_buf[0], 512&byteswritten);
    
    res = f_close(&fil_1);
    res = f_close(&fil_2);
    
    res = f_mount(0,SD_Path,0);
 
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
 
  }
  /* USER CODE END 3 */
 
}
cs

위의 코드를 컴파일해서 다운로드 후 실행하면, herry-cnv.txt 파일이 생깁니다.




다음과 같이 각 파일을 열어 보니, 2개의 파일을 열어서(1개는 raed,1개는 Write) 512 바이트를 읽고 쓰는 테스트가 완료됐음을 확인했습니다.




첨부한 파일 이름이 프로젝트 진행 중인 파일 이름으로 되어 있어서 길이가 긴데, 이름은 아무 의미 없습니다. ^^

usb_msc_rf_sdi_fcv_nv102.zip


STM32 MCU는 칩마다 각각 다른 96bit(12-Byte)의 고유번호(Unique ID)를 갖고 있다.


이 고유 번호는 종종 쓸모가 있다. 
- 1회용 싸구려 소모품이 아닌 이상, 제품 마다 이력 관리를 할 수도 있고,
- 가공 해서 암호를 만들 수도 있으며,
- 무선 장비의 경우에는 맥어드레스라고 하여, 서로 간의 통신할 때 충돌을 방지하는 용도로도 사용할 수 있다.


고유 번호가 없을 경우, 칩마다 각각 플래쉬 메모리에 서로 다른 번호를 만들어서 넣어 줘야 하는데,
여간 불편한 게 아니다.

고유 아이디를 만드려면, 코드에 직접 넣어서 컴파일해서 MCU에 프로그램해서 넣는 방법과 
통신을 통해서 프로토콜로 플래쉬의 특정 주소에 써 넣는 방법이 주로 사용될 것이다.

고유 아이디를 만들어서 플래쉬에 쓰는 경우에는, 
플래쉬에 썼기 때문에 잘못해서 지워지는 경우가 있을 수 있고, 
사람이 하다 보니 중복해서 쓰거나 안 쓰고 넘어가는 경우도 종종 있다.

또한 칩 하나하나 마다 컴파일을 하거나 통신 프로토콜로 아이디를 쓰려면 툴을 사용해야 하고 시간도 많이 걸리니 모든것이 낭비여서, 고유 아이디가 제공된다는 점은 어떻게 보면 큰 장점이다.

STM32F4 MCU 에서 제공된 고유 아이디를 읽으려면, 고유 아이디가 위치한 특정 어드레스에서 값을 읽으면 된다.

아주 간단한 예로 끝내겠다. 
다음 예는 유니크 아이디가 위치한 주소(0x1FFF7A10)에서 12바이트를 
8비트/16비트/32비트 단위로 읽어서 각각 UART로 출력하는 예이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    #define ID_UNIQUE_ADDRESS        0x1FFF7A10
 
    uint8_t i;
    uint8_t uu_id_8b[12];
    uint16_t uu_id_16b[6];
    uint32_t uu_id_32b[3];
    
    // 8-bit 12-EA Read
    for (i=0;i<12;i++)
        uu_id_8b[i] = *(uint8_t *)(ID_UNIQUE_ADDRESS+i);
    // 16-bit 6-EA Read
    for (i=0;i<6;i++)
        uu_id_16b[i] = *(uint16_t *)(ID_UNIQUE_ADDRESS+i*2);
    // 32-bit 3-EA Read
    for (i=0;i<3;i++)
        uu_id_32b[i] = *(uint32_t *)(ID_UNIQUE_ADDRESS+i*4);
 
    printf("UUID[8-Bit]  ");
    for(i=0;i<12;i++)
        printf(":%02X",uu_id_8b[i]);
 
    printf("\n\rUUID[16-Bit] ");
    for(i=0;i<6;i++)
        printf(":%04X",uu_id_16b[i]);
 
    printf("\n\rUUID[32-Bit] ");
    for(i=0;i<3;i++)
        printf(":%08X",uu_id_32b[i]);
 
    while(1);
cs





참고로 다음과 같은 정보도 있다.
읽는 방식은 메뉴얼을 참고로 위의 코드 예를 조금 변경하면 되겠고, 
더 자세한 정보는 링크에 가서 코드를 다운 받아서 보면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * @brief  Get STM32F4xx device signature
 * @note   Defined as macro to get maximal response time
 * @param  None
 * @retval Device signature, bits 11:0 are valid, 15:12 are always 0.
 *           - 0x0413: STM32F405xx/07xx and STM32F415xx/17xx)
 *           - 0x0419: STM32F42xxx and STM32F43xxx
 *           - 0x0423: STM32F401xB/C
 *           - 0x0433: STM32F401xD/E
 *           - 0x0431: STM32F411xC/E
 */
/**
 * @brief  Get STM32F4xx device revision
 * @note   Defined as macro to get maximal response time
 * @param  None
 * @retval Device revision value
 *           - 0x1000: Revision A
 *           - 0x1001: Revision Z
 *           - 0x1003: Revision Y
 *           - 0x1007: Revision 1
 *           - 0x2001: Revision 3
 */
#define ID_DBGMCU_IDCODE        0xE0042000
 
 
/**
 * @brief  Get STM32F4xx device's flash size in kilo bytes
 * @note   Defined as macro to get maximal response time
 * @param  None
 * @retval Flash size in kilo bytes
 */
#define ID_FLASH_ADDRESS        0x1FFF7A22
cs




------------------------------------------------------------------------------------------------





------------------------------------------------------------------------------------------------




전원이 꺼지거나 리셋이 되도 지워지지 말아야할 정보를 사용할 경우, 

외부에 추가로 EEPROM 을 붙여서 SPI/I2C 로 읽고 쓸 수도 있지만,
STM32 MCU 에  Flash 메모리도 남고 아주 자주 쓰고 지우는 경우도 아니라서 Flash 메모리를 EEPROM 처럼 사용하는 방법을 알아보고 간단한 테스트를 해 보려고 합니다.

테스트하기 편하게 제가 갖고 있는 STM32F411 Nucleo 보드를 사용해서 STLINK 로 프로그램을 다운로드하고,
UART로 테스트 내용을 확인할 계획입니다. 추가로 외부에 연결할 부품도 필요 없기에 보드 사진은 생략합니다.

Nucleo 보드에서 사용하는 기능은 다음과 같습니다.
1. User SW 입력 (GPIO INPUT)
2. LED 출력 (GPIO OUT)
3. 디버그용 UART




내부 플레쉬 메모리를 읽고 쓰기 위해서 예전에 Custom DFU bootloader 를 만들 때 사용했던 함수들을 재활용해야겠습니다. 

메모리 엑세스 관련, 간단하게 함수 프로토 타입만 적어 놓겠고 자세한 내용은 예전 글을 참고 하시기 바랍니다.
1
2
3
4
5
6
7
8
uint16_t MEM_If_Init_FS(void);
uint16_t MEM_If_Erase_FS (uint32_t Add);
uint16_t MEM_If_Write_FS (uint8_t *src, uint8_t *dest, uint32_t Len);
uint8_t *MEM_If_Read_FS  (uint8_t *src, uint8_t *dest, uint32_t Len);
uint16_t MEM_If_DeInit_FS(void);
 
uint32_t GetSectorSize(uint32_t Sector);
uint32_t GetSector(uint32_t Address);
cs


1. MCU에서 사용할 메모리를 선택하기 전에 예상되는 코드 메모리를 계산.
현재 프로젝트를 진행하고 있는 프로그램에 적용할 생각으로 테스트를 하고 있는데, 프로그램 영역과 겹치지 않도록
남는 영역에 EEPROM Emulation 용 메모리를 선택해야 하므로, 프로그램 코드의 크기를 계산해 봐야 겠습니다.


[프로젝트로 진행중인 프로그램 크기 계산]




Total ROM Size = 37580(Code) + 1948(RO) + 344(RW) = 39872(0x9BC0)
Total RAM Size = 344(RW) + 71448(ZI) = 71792(0x11870)

64KByte = 64*1024 = 65536Byte(0x10000) 이므로, APP 프로그램(0x9BC0)는 0,1,2 Sector 범위에 들어 가고 약간 여분으로 프로그램을 더 추가 하더라도 3번 Sector를 넘어가진 않을 것 같습니다.

2. 위의 프로그램의 코드 계산의 토대로 메모리를 할당해 봤습니다.




APP 프로그램은 어드레스 0x0800 0000 부터 늘어나기 때문에, EEPROM Emulation 용 Flash 메모리는 뒤쪽에 안 쓰는 곳에 지정을 하는 게 맞고, 겨우 몇십 바이트 정도만 사용할 예정이라 가능한 작은 크기의 Sector를 할당하는 것이 좋겠습니다.

하지만, 지금 현재 개발 중인 APP 소스 크기가 39KB 에 달해서 Sector 0,1,2 를 사용하고 있습니다. 따라서 완료 시점까지 좀 여유있게 프로그램 메모리를 더 할당해야 해서 Sector 0~3 까지 64KB 를 할당하고, 아깝지만 EEPROM 영역을 Sector4(64KB) 로 할당을 해야 겠습니다. 뒤로 갈수록 섹터 크기가 커지니, 메모리가 어마어마하게 낭비되고 지우는 시간도 많이 걸릴 테지만, 128KB 를 쓰는 것은 더 아깝고 그다지 더 쓸 일도 없을 것 같으니 그만 안타까운 마음을 접기로 했습니다.

3. 버퍼 메모리(Read/Write 변수) 할당
EEPROM 이 아니라 지정된 메모리에 프로그램을 쓰려면, 전체 Sector를 지우고 변경할 내용을 한번에 써야하기에
메모리에 쓸 영역만큼의 SRAM 크기를 지정해야 합니다.
저는 아무리 사용해 봐야 512 개 정도면 충분할 것 같아서 512 바이트만큼 배열 변수로 할당했습니다.


4. EEPROM Emulator 테스트 프로그램 구상
- 메모리를 초기화 하기 위한 Flash 메모리 영역에 인식 ID 를 지정해서, 처음으로 메모리를 사용할 경우 디폴트 값으로 초기화를 하는 용도로 사용.
- 10-Byte의  Flash Memory 에, Nucleo 보드의 User-SW 를 1번 누를 때 마다 각각의 Byte 의 값이 1씩 증가되면서,
Write 한다. (현재 변화된 10-Byte의 Flash Memory 값을 읽어서 UART에 표시)
- 전원 Reset 시 10-Byte의 Flash Memory 값을 읽어서 UART에 표시하여 Flash 에 값이 남아있는지 확인. 


5. APP 프로그램의 코드영역이 EEPROM FLASH 영역을 침범하지 않도록 ROM Size 변경
ROM 크기를 기본 256KB 에서 64KB 로 변경해서, 만일 코드 사이즈가 커져서
EEPROM 영역에 데이터가 써지는 것을 방지한다.





6-0. eeprom emulation Test 프로그램의 #define .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 512KB
#define ADDR_FLASH_SECTOR_0     ((uint32_t)0x08000000/* Base @ of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1     ((uint32_t)0x08004000/* Base @ of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2     ((uint32_t)0x08008000/* Base @ of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3     ((uint32_t)0x0800C000/* Base @ of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4     ((uint32_t)0x08010000/* Base @ of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5     ((uint32_t)0x08020000/* Base @ of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6     ((uint32_t)0x08040000/* Base @ of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7     ((uint32_t)0x08060000/* Base @ of Sector 7, 128 Kbytes */
 
#define FLASH_USER_START_ADDR   ADDR_FLASH_SECTOR_4   /* Start @ of user Flash area */
#define FLASH_USER_END_ADDR     FLASH_USER_START_ADDR  +  GetSectorSize(FLASH_SECTOR_4) -1 /* End @ of user Flash area : sector start address + sector size -1 */
 
#define USR_MEM_CNT            512
#define USR_ASIGN_ID        0xA5
#define USR_ASIGN_Add        0
#define USR_DATA0_Add        1
 
cs

6-1. eeprom emulation Test 프로그램의 main() 함수 프로그램 소스코드.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
int main(void)
{
 
  /* USER CODE BEGIN 1 */
    uint8_t flash_buffer[512];
    __IO uint32_t data32 = 0;
    
  /* 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_USART2_UART_Init();
 
  /* USER CODE BEGIN 2 */
    printf("UART Test..!!\n\r");
    
    // Flash data memory Test..
    init_FLASH(flash_buffer);
    
    MEM_If_Read_FS ((uint8_t *)FLASH_USER_START_ADDR, flash_buffer, 512);
    dump_data(flash_buffer,0,16);
    
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
    uint8_t sw_stat,old_sw_stat;
    old_sw_stat = sw_stat = HAL_GPIO_ReadPin(USER_SW_GPIO_Port,USER_SW_Pin);
    
  while (1)
  {
    old_sw_stat = sw_stat;
    sw_stat = HAL_GPIO_ReadPin(USER_SW_GPIO_Port,USER_SW_Pin);
        
    if (old_sw_stat != sw_stat)
    {
        if (sw_stat == 0)
        {
            proc_sw_push(flash_buffer);
            MEM_If_Read_FS ((uint8_t *)FLASH_USER_START_ADDR, flash_buffer, 512);
            dump_data(flash_buffer,0,16);
        }
    }
        HAL_Delay(50);
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
 
  }
  /* USER CODE END 3 */
 
}
cs


6-2. eeprom emulation Test 프로그램의 메모리 초기화 함수 프로그램 소스코드.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void init_FLASH(uint8_t *mem_buf)
{
    memset(mem_buf,0,USR_MEM_CNT);
    MEM_If_Read_FS ((uint8_t *)FLASH_USER_START_ADDR, mem_buf, USR_MEM_CNT);
    
    if (mem_buf[USR_ASIGN_Add] != USR_ASIGN_ID)
    {
        memset(mem_buf,0,USR_MEM_CNT);
        mem_buf[USR_ASIGN_Add] = USR_ASIGN_ID;
        mem_buf[USR_DATA0_Add+0= 0;
        mem_buf[USR_DATA0_Add+1= 1;
        mem_buf[USR_DATA0_Add+2= 2;
        mem_buf[USR_DATA0_Add+3= 3;
        mem_buf[USR_DATA0_Add+4= 4;
        mem_buf[USR_DATA0_Add+5= 5;
        mem_buf[USR_DATA0_Add+6= 6;
        mem_buf[USR_DATA0_Add+7= 7;
        mem_buf[USR_DATA0_Add+8= 8;
        mem_buf[USR_DATA0_Add+9= 9;
        
        MEM_If_Erase_FS(FLASH_USER_START_ADDR);
        MEM_If_Write_FS(mem_buf, (uint8_t *)FLASH_USER_START_ADDR,USR_MEM_CNT);
    }
}
cs

6-3. eeprom emulation Test 프로그램의 dump 함수 프로그램 소스코드.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
uint8_t dump_data(uint8_t *mem_buf,uint32_t start_mem,uint32_t cnt_mem)
{
    uint32_t i,cnt_i;
    
    if (start_mem < USR_MEM_CNT)
    {
        if ((start_mem+cnt_mem) > USR_MEM_CNT)
        {
            return 1;
        }
        else
        {
            for (cnt_i=0;cnt_i < cnt_mem;)
            {
                printf("Addr [%05X ] ",start_mem+cnt_i);
                if ((cnt_mem - cnt_i) < 16)
                {
                    uint16_t t_i;
                    t_i = (cnt_mem - cnt_i);
                    
                    for (i=0;i<t_i;i++,cnt_i++)
                    {
                        printf("%02X ",mem_buf[start_mem+cnt_i]);
                    }
                    printf("\n\r");
                }
                else
                {
                    for (i=0;i<16;i++,cnt_i++)
                    {
                        printf("%02X ",mem_buf[start_mem+cnt_i]);
                    }
                    printf("\n\r");
                }
            }
            return 0;
        }
    }
    else
    {
        return 2;
    }
}
cs

6-4. eeprom emulation Test 프로그램의 스위치처리 함수 프로그램 소스코드.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void proc_sw_push(uint8_t *mem_buf)
{
    static uint8_t cnt_push=0;
    
    cnt_push++;
    printf("cnt Push switch = [%3d]\n\r",cnt_push);
    mem_buf[USR_DATA0_Add+0]++;
    mem_buf[USR_DATA0_Add+1]++;
    mem_buf[USR_DATA0_Add+2]++;
    mem_buf[USR_DATA0_Add+3]++;
    mem_buf[USR_DATA0_Add+4]++;
    mem_buf[USR_DATA0_Add+5]++;
    mem_buf[USR_DATA0_Add+6]++;
    mem_buf[USR_DATA0_Add+7]++;
    mem_buf[USR_DATA0_Add+8]++;
    mem_buf[USR_DATA0_Add+9]++;
    
    MEM_If_Erase_FS(FLASH_USER_START_ADDR);
    MEM_If_Write_FS(mem_buf, (uint8_t *)FLASH_USER_START_ADDR,USR_MEM_CNT);
}
cs

7. 최종 UART 터미날로 본 결과 테스트




버튼을 누를 때 마다, 10 바이트의 값이 1씩 증가하고.. Power On-Reset 을 했더니 메모리 값이 변하지 않고,
남아 있는 것을 확인 할 수 있었습니다.


제가 stm32f411로 테트트하다 보니, 가끔 인터럽트를 사용하면 이상동작을 하는 경우가 있어서,

이전에 만든 DWT Delay 함수를 사용하지 못해서 Polling 으로 micro second Delay 함수를 또 찾아봤습니다.

정말 간단하게 만들 수 있었습니다. 아주 정확하지는 않겠지만, 
system 클럭의 정보를 바탕으로 1clk 당 실행시간을 계산해서 us Delay를 만들어 내는 것으로 보입니다.
따라서 system clk 이 바뀌더라도 대충 맞는 그런그런 정도(아주 정밀하지는 않음)의 딜레이 함수입니다.

제가 구글링에서 참고한 사이트는 다음과 같습니다.

그리고 제가 사용자 함수를 모아 놓는 파일인 user_def.c 와 user_def.h 가 있는데,
user_def.h 파일에만 다음과 같은 함수를 정의해 놓습니다.
1
2
3
4
5
6
7
8
__STATIC_INLINE void Pol_Delay_us(volatile uint32_t microseconds)
{
  /* Go to number of cycles for system */
  microseconds *= (SystemCoreClock / 1000000);
 
  /* Delay till end */
  while (microseconds--);
}
cs


그리고 이 user_def.h 를 인클루드한 소스파일에서 다음과 같이 사용하면 됩니다.
1
2
3
    Pol_Delay_us(50);        // Polling 50us
    //DWT_Delay_us(100);    // DWT 100us
    //HAL_Delay(1);            // HAL Library 1ms
cs


라이브러리로 제공되는 함수가 milisec 함수(HAL_Delay()) 는 있는데,

가끔 가다가 필요한 us 함수를 구글링해서 찾았고, 잘 동작합니다.

링크는 다음과 같습니다. 
https://community.st.com/thread/13838 에서 Knut Knusper 가 쓴 글을 참조하시면 됩니다.

cubemx 로 여러가지 테스트해보다 보면 실수로 User code 부분이 싹~ 날아가는 경우가 많아서,
이곳에 코드를 다시 정리해 봅니다.

저는 user_def.c 라는 파일을 하나 만들어서 cubemx 에서 갑자기 지워지는 것을 막기 위해 필요한 함수를 정의해 놓아서,
새로 추가된 DWT_Delay_Init() 함수는 이곳에 정의해 놓았습니다.

1. user_def.c 에 다음의 코드를 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
uint32_t DWT_Delay_Init(void) {
  /* Disable TRC */
  CoreDebug->DEMCR &= ~CoreDebug_DEMCR_TRCENA_Msk; // ~0x01000000;
  /* Enable TRC */
  CoreDebug->DEMCR |=  CoreDebug_DEMCR_TRCENA_Msk; // 0x01000000;
     
  /* Disable clock cycle counter */
  DWT->CTRL &= ~DWT_CTRL_CYCCNTENA_Msk; //~0x00000001;
  /* Enable  clock cycle counter */
  DWT->CTRL |=  DWT_CTRL_CYCCNTENA_Msk; //0x00000001;
     
  /* Reset the clock cycle counter value */
  DWT->CYCCNT = 0;
     
     /* 3 NO OPERATION instructions */
     __ASM volatile ("NOP");
     __ASM volatile ("NOP");
  __ASM volatile ("NOP");
 
  /* Check if clock cycle counter has started */
     if(DWT->CYCCNT)
     {
       return 0/*clock cycle counter started*/
     }
     else
  {
    return 1/*clock cycle counter not started*/
  }
}
cs

2. user_def.h 에 다음의 코드 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
uint32_t DWT_Delay_Init(void);
 
 
 
/**
 * @brief  This function provides a delay (in microseconds)
 * @param  microseconds: delay in microseconds
 */
__STATIC_INLINE void DWT_Delay_us(volatile uint32_t microseconds)
{
  uint32_t clk_cycle_start = DWT->CYCCNT;
 
  /* Go to number of cycles for system */
  microseconds *= (HAL_RCC_GetHCLKFreq() / 1000000);
 
  /* Delay till end */
  while ((DWT->CYCCNT - clk_cycle_start) < microseconds);
}
cs


3. main.c 의 main() 함수에서 us Delay 함수(DWT_Delay_us()) 사용.
1
2
3
4
5
6
    while(1)
    {
        HAL_GPIO_TogglePin(IND_LED_FL_GPIO_Port,IND_LED_FL_Pin);
        DWT_Delay_us(1000000);    // 1sec
    }
 
cs


1000,000 us (1 sec) 마다 GPIO 가 토글되는 것을 확인했으니, 동작이 잘 되네요. ^^


USB MSC 도 안되고, 


HAL_SD_CardInfoTypedef 도 바뀌었습니다.

BSP_SD_ReadBlocks_DMA(),BSP_SD_WriteBlocks_DMA() 함수도 바뀌었고,

MX_SDMMC1_SD_Init() 함수도 이상하다고 하네요.

얼마 전에, 클럭 조차도 동작 안하던 버그가 있었고요.

아무래도, CUBEMX 4.2 는 쓰지 말고, 이전 버전으로 돌아가야할 듯 합니다.


2018 년 3월 18일에 일어난 업데이트 버그입니다. 예전 글을 1개씩 옮기는 중이라, 현재 일어난 일 인줄 착각 할까봐 이 글을 추가합니다.


CUBEMX 4.2 업데이트를 하고 나서,

KEIL 컴파일러 코드 생성 시, 심각한 버그가 발견 되었습니다.

HCLK 의 초기화가 잘못 되어서 프로그램이 동작되지 않습니다.

SystemClock_Config() 함수 내용 중, 1줄이 잘못 되었는데, 예전 코드로 수정해야 동작이 되네요.

SystemClock_Config() 예전 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void SystemClock_Config(void)
{
 
  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
 
    /**Configure the main internal regulator output voltage 
    */
  __HAL_RCC_PWR_CLK_ENABLE();
 
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
 
    /**Initializes the CPU, AHB and APB busses clocks 
    */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
cs

SystemClock_Config() CUBEMX 4.20 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void SystemClock_Config(void)
{
 
  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
 
    /**Configure the main internal regulator output voltage 
    */
  __HAL_RCC_PWR_CLK_ENABLE();
 
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
 
    /**Initializes the CPU, AHB and APB busses clocks 
    */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSE;
cs


SystemClock_Config() CUBEMX 4.20 코드(수정)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void SystemClock_Config(void)
{
 
  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
 
    /**Configure the main internal regulator output voltage 
    */
  __HAL_RCC_PWR_CLK_ENABLE();
 
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
 
    /**Initializes the CPU, AHB and APB busses clocks 
    */
  //RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
cs


처음으로 SD-Card 를 납땜을 해서, 동작이 잘 되는지 확인을 해 봤습니다.


테스트 방법은, uSD-Card 를 마운트 하고 디렉토리 스캔을 해서 USB_CDC 로 터미널에 뿌려서
안에 넣어둔 파일이 제대로 보이는지 확인하면 되겠습니다.

다음은 보드에 uSD-Card 를 납땜한 모습입니다.




uSD-Card 회로도는 다음과 같습니다. STM32F4xx 에 SDIO-4bit 방식으로 연결했습니다.




다음은 ST 사의 CUBEMX 툴로 SDIO-4BIT 를 추가한 핀맵입니다.




다음은 디렉토리 스캔 함수 인데, 출처는 기억이 안납니다. main.c 파일에 붙여서 사용하면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void f_opendir_scan(void)
{
    #if _USE_LFN
    TCHAR lfn[_MAX_LFN + 1];
    fno.lfname = lfn;
    fno.lfsize = sizeof lfn;
    #endif
    TCHAR path[200= "";
    
    res = f_mount(&fs32,SD_Path,1);
    printf("SD Mount : res f_mount : %02X\n\r",res);
    
    if (res == FR_OK)
    {
    res = f_opendir(&dir,path);
        printf("res f_open : %02X\n\r",res);
        
        if (res == FR_OK)
        {
        while(1)
        {
            char *fn;
             
            res = f_readdir(&dir, &fno);
             
            if (res != FR_OK)
                printf("res = %d f_readdir\n\r", res);
 
            if ((res != FR_OK) || (fno.fname[0== 0))
                break;
 
#if _USE_LFN
      fn = *fno.lfname ? fno.lfname : fno.fname;
#else
      fn = fno.fname;
#endif
            printf("%c%c%c%c ",
                ((fno.fattrib & AM_DIR) ? 'D' : '-'),
                ((fno.fattrib & AM_RDO) ? 'R' : '-'),
                ((fno.fattrib & AM_SYS) ? 'S' : '-'),
                ((fno.fattrib & AM_HID) ? 'H' : '-') );
 
            printf("%10d ", fno.fsize);
             
            printf("%s/%s\n\r", path, fn);
        }        
        }
 
        res = f_mount(0,SD_Path,0);
        printf("SD Unmount : res f_mount : %02X\n\r",res);
    }
}
 
cs

다음은 main() 함수에서, 파일 변수들 정의해 봏고, 디렉토리 스캔함수를 호출해 주면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* Private variables ---------------------------------------------------------*/
HAL_SD_ErrorTypedef res_sd;
 
FRESULT res;
FILINFO fno;
FIL fil;
DIR dir;
FATFS fs32;
 
uint32_t byteswritten, bytesread; /* File write/read counts */
 
 
int main(void)
{
  /* 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_TIM2_Init();
  MX_USB_DEVICE_Init();
  MX_SDIO_SD_Init();
  MX_FATFS_Init();
 
  /* USER CODE BEGIN 2 */
  // HAL_GPIO_WritePin(SDPWR_GPIO_Port, SDPWR_Pin, GPIO_PIN_RESET);
  HAL_Delay(500);
  HAL_TIM_Base_Start_IT(&htim2);
    
  f_opendir_scan();
 
  while (1)
  {
 
  }
}
cs

그리고 나서 프로그램을 다운로드 하고 실행을 하니, uSD-Card 의 내용이 보입니다.
납땜이 제대로 잘 됐군요.^^




+ Recent posts