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

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

전광판 프로젝트에서 파일을 변환하는 기능이 필요해서 검토를 하다보니,
파일을 변환하려면, 읽을 파일과 쓸 파일, 이렇게 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 을 했더니 메모리 값이 변하지 않고,
남아 있는 것을 확인 할 수 있었습니다.




제가 골초라서 수제담배를 검색하다가 여기까지 오게 됐네요.


담배는 일반 식물처럼 일조량과 온도를 맞춰주고, 물을 잘 주면 자란다고 합니다.
검색해 보니 재배 일에 맞춰서 씨앗을 심고, 베란다에서 키우거나 옥상에서 키운다고도 하고 어떤 사람들은 텃밭에서도 키운다고 합니다.

저는 IT 업계에 있고 게을러서 일일히 시간내서 잘 관리하고 그럴 사람이 아닙니다.
그래서 생각이 든 것이, 자동차를 이용하면 어떨까 라는 생각이 들었습니다.
요새는 어두워도 LED 조명으로 식물을 키우는 시대인데, 게다가 자동차는 전기만 있으면 온도와 습도를 원하는 대로 조절을 할 수가 있잖아요. 이동식 담배 재배 비닐하우스가 되는 것이죠.

거의 폐차될 정도의 자동차를 사다가, 휘발류를 떼서 작물을 재배하기는 돈이 많이 드니, 태양광 패널과 밧데리를 붙여서, 자동차를 끌고 일사량이 좋은 시골 아는 지인 집 근처로 이동합니다.

작물 재배용이므로, 시트도 다 떼 버립니다. 시트 대신 흙이 담긴 화분으로 바꿉시다.
일조량이 좋을 때는 자연광으로, 나쁠 때는 태양광 패널로 모은 전기로 채광을 합니다.
물도 자동으로 주도록 펌프를 부착하고, wifi 카메라도 하나 달아주는 센스.. ^^ (서울에서 잘 크는지 봐야겠죠)

담배가 다 자라면, 화분을 빼고 잎을 잘라서 자동차 안에서 온습도를 조절해서 말립니다.
아.. 참. 그늘에서 말려야한다니까, 의자를 잠깐 붙여서 그늘로 이동해야겠습니다.
말리는 작업을 끝내면, 담뱃잎이 나올테고 잘라서 종이에 말아서 피면 되겠습니다.

그런데, 담배가 자라면 매우 크다네요. -> 폐차를 버스로 바꿉시다.^^
폐차도 유지하는 돈이 들지 않나 생각이 잠시 드네요.

차를 자율주행 기능을 약간 넣어서 다 자라면 서울로 배달 기능을 넣어도 좋겠네요.
건조할 경우 자동으로 그늘로 이동.
아.. 귀찮아, 자동차 안에 AI 로봇도 하나 넣어서 다 시켜버리고 싶네요.


제가 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 가 토글되는 것을 확인했으니, 동작이 잘 되네요. ^^


STM32F411 에서 타이머2 인터럽트를 사용하다가, 

동작이 생각처럼 안되어서 쌩쑈를 하다가 나중에 동작이 되긴 했지만 아직까지 이해가 안되는 부분이 있었습니다.

인터럽트를 처리하는 콜백 함수와 함수 내에서 사용하는 전역 변수들은 다음과 같고, main.c 파일 안에 있습니다.
이 콜백함수는 1ms 마다 호출되도록 설정했고, 
콜백 함수 내에 f_tim2_cpl 가 SET 되는 부분에 LED 와 연결된 포트를 토글하면 잘 동작 됐습니다.

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
uint8_t f_tim2_cpl=0;
uint16_t cnt_tim2[5= {0,0,0,0,0};
uint16_t ref_tim2[5= {50,2000,40,0,0};
 
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    uint8_t cnt_tmr;
    
    if (htim->Instance == TIM2)
    {
        for(cnt_tmr=0;cnt_tmr<5;cnt_tmr++)
        {
            if (cnt_tim2[cnt_tmr] < ref_tim2[cnt_tmr])
            {
                cnt_tim2[cnt_tmr]++;
            }
            if (cnt_tim2[cnt_tmr] == ref_tim2[cnt_tmr])
            {
                cnt_tim2[cnt_tmr] = 0;
                switch (cnt_tmr)
                {
                    case 0:
                        f_tim2_cpl |= F_TIMCMPL_0;
                        break;
                    case 1:
                        f_tim2_cpl |= F_TIMCMPL_1;
                        break;
                    case 2:
                        f_tim2_cpl |= F_TIMCMPL_2;
                        break;
                    case 3:
                        f_tim2_cpl |= F_TIMCMPL_3;
                        break;
                    case 4:
                        f_tim2_cpl |= F_TIMCMPL_4;
                        break;
                }
            }
        }
    }
    else if (htim->Instance == TIM1)
    {
    }
}
cs

그런데, main 함수 내에서 다음과 같이 f_tim2_cpl 의 상태를 while 문의 조건문 안에서 읽어서 비교하면 동작이 안되고,
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
int main(void)
{
  uint16_t i;
  /* 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_USART1_UART_Init();
  MX_TIM2_Init();
 
  /* USER CODE BEGIN 2 */
  for(i=0;i<5;i++)
    cnt_tim2[i] = 0;
  f_tim2_cpl=0;
  HAL_TIM_Base_Start_IT(&htim2);
 
  printf("uart test\n\r");
  while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
  {
  }
  printf("2 sec delay\n\r");
 
  while(1);
}
cs


while() 문 안에, 여러가지 내용을 넣어 봤는데 동작이 되는 경우도 있고 안되는 경우도 있고,
안에 넣은 코드에 따라서 각각 다릅니다.
1. HAL_delay(1); 을 넣은 경우. (정상 동작)
1
2
3
4
5
6
7
    printf("uart test\n\r");
    while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
    {
        HAL_Delay(1);
    }
    printf("2 sec delay\n\r");
 
cs
2. __wfi(); //wait for interrupt 을 넣은 경우. (정상 동작)
1
2
3
4
5
6
7
    printf("uart test\n\r");
    while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
    {
        __wfi();
    }
    printf("2 sec delay\n\r");
 
cs
3. __nop(); // no opration 을 넣은 경우. (동작 안함)
1
2
3
4
5
6
7
    printf("uart test\n\r");
    while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
    {
        __nop();
    }
    printf("2 sec delay\n\r");
 
cs
4. printf() 문 을 넣은 경우, (정상 동작)
1
2
3
4
5
6
7
    printf("uart test\n\r");
    while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
    {
        printf("timer 2 Whay?\n\r");
    }
    printf("2 sec delay\n\r");
 
cs
5. i++; (변수 1씩 증가). (동작 안함).
1
2
3
4
5
6
7
    printf("uart test\n\r");
    while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
    {
        i++;
    }
    printf("2 sec delay\n\r");
 
cs
6. for() 루프로 딜레이. (동작 안함)
1
2
3
4
5
6
7
8
9
10
    printf("uart test\n\r");
    while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
    {
        for(i=0;i<1000;i++)
        {
            __nop();
        }
    }
    printf("2 sec delay\n\r");
 
cs

지금까지의 테스트로 봤을 때, 
인터럽트에 의한 전역 변수의 상태를 읽으려면
- 단순한 for 루프나 폴링 방식의 딜레이로는 안되고,
- __wfi(), HAL_delay(), printf() 의 함수가 사용되어야 읽을 수 있다고 볼 수 있겠습니다.

흠, 지금까지 이런 것을 염두에 두고 인터럽트를 쓰지 않았는데.. 거의 무리없이 지나왔었습니다.
그런데 이제와서 이런 경우를 겪어서 좀 황당하네요.

ㅜㅜ 뭔가 기초가 부족한 것 같습니다. 혹시 누가 정확한 원인을 아시는 분 설명 좀 부탁 드려요.

최종적으로 다음과 같이 사용하니, 별 문제가 없네요. (__wfi() 사용)
1
2
3
4
5
6
7
8
9
10
while(1)
{
    __wfi();
    if (f_tim2_cpl & F_TIMCMPL_2)         // 400ms T compare
    {
        cnt_tim2[2= 0;                        // 400ms counter
        f_tim2_cpl &= ~F_TIMCMPL_2;  // 400ms Flag clear
        HAL_GPIO_TogglePin(IND_LED_FL_GPIO_Port,IND_LED_FL_Pin);    // LED toggle
    }
}
cs


4.2 로 바뀌면서 안되는 것이 정말 저에게는 치명적이라,


USB MSC 가 안되서 4.2 에서 돌아가게 해 보려는데 도무지 방법이 없네요.

누가 4.19 버전 갖고 계시면 좀 보내 주시면 감사하겠습니다.

cubemx 4.16 버전 받았습니다. 필요하신 분은, 댓글에 링크 걸어 두었습니다.

그래도 혹시 4.19 버전 갖고 있으신분, 링크 부탁드립니다.

하하, CUBEMX 4.19 버전도 구했습니다. ST 대리점에 부탁해서 얻었습니다. 휴~~

다시 링크 해 드립니다. 혹시 저와 같은 경우로 어려움을 겪으시는 분들이 계시면 다운 받으시기 바랍니다.
1. cubemx v4.16 : 링크
2. cubemx v4.19 : 링크


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


+ Recent posts