안녕하세요.

STM32 MCU의 peripheral들에 대한 이해를 바탕으로 STM32CubeMX 툴에서 지원하고 있는 FreeRTOS를 실습해보실 수 있습니다. FreeRTOS의 기초지식을 학습하고 STM32F4 MCU 기반에서 STM32CubeMX를 사용하여 FreeRTOS를 생성하는 실습을 통해 FreeRTOS에 대한 이해를 향상 시킬 수 있는 트레이닝 과정입니다.

자세한 정보

어떤 분들이 참석해야 하나요?

STM32 시리즈의 기본 지식을 바탕으로 테크 트레이닝 초급, 중급을 이수한 분들과 업무 현장에서 STM32 MCU 기반에 FreeRTOS를 사용한 임베디드 어플리케이션 구현이 필요한 분들에게 FreeRTOS에 대한 기초지식 학습 및 실습 교육의 기회를 제공해드립니다.

당신의 배움에 대한 혜택

  • STM32 MCU 및 FreeRTOS의 기초 지식
  • STM32CubeMX를 사용한 FreeRTOS 포팅
  • FreeRTOS 의 Task creation, Semaphore, Mutexs, Queues 등 기본 기능 실습



처음으로 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 의 내용이 보입니다.
납땜이 제대로 잘 됐군요.^^




얼마 전에 CUBEMX 업데이트를 했는데, 못보던 LIB가 1개 보이네요.





아직 쓸 일이 없고, 어떻게 쓰는 줄도 모르겠지만, JPEG encode,decode 함수가 있네요.
jpeg_CreateCompress() , jpeg_CreateDecompress()

나중에 쓸 일 있으면 써봐야겠다. ^^.

아뭏든 나온지 보름 정도 밖에 안된 따끈따끈한 라이브러리 랍니다. (2017,3,2)


STM32 MCU 는 PSOC5과 같이 포트를 맘대로 바꾸기 어려워서,

이런 저런 기능을 사용하고 나면 못쓰는 기능들이 생깁니다.

저의 경우에는, TIMx PWM_CHx 핀을 쓰고 싶은데, 없고 TIMx PWM_CHxN 핀은 있어서,
뭐가 다른가 하고 테스트를 해보게 되었습니다.
물론, 메뉴얼을 좀 읽어 보니까 Complementary outputs 기능을 사용할 경우에는 의미가 있지만, 그 내용은 일단 생략하고,
단순한 PWM 출력만을 놓고 테스트 해 봤을 때는, 이름만 다르지 전혀 차이가 없이 사용 가능했습니다.

이 테스트는 STM32F411 Nucleo 보드에서 진행 하였고, 다음과 같은 포트를 사용했습니다.








TIM2 의 설정은 PWM 출력 1개가 나오는데, 이건 오실로 스코프로 찍기 쉽게 
펄스 하나를 TIM2 Update 인터럽트에 맞춰서 내보내도록 설정 한 것으로 중요한 내용이 아니라서 통과하고,
TIM1 의 PWM_CH1 과 PWM_CH2N 을 완벽하게 같은 조건으로 설정하고 포트 출력을 확인해 봤습니다.

[TIM1 PWN 채널 출력 설정]





[위의 설정에 따른 결과]





[설명]
위의 설정은 TIM2 Update INT 마다 TIM1 의 PWM_CH1 과 PWM_CH2N 을 완전히 똑같은 조건으로,
(One Pulse Mode, 5-repeat , 주기 : 5ms, Duty : 1/10) 출력하도록 프로그램을 했고,
오실로 스코프로 출력을 측정한 사진입니다.

결론은 일반적인 PWM 신호를 포트로 출력할 경우는, PWM_CHx 나 PWM_CHxN 은 동일한 출력을 내므로,
PWM_CHx 가 없고 PWM_CHxN 만 있다면 그것을 써도 무방하다.

다만, 프로그램에서 출력 함수가 다르다.
PWM_CHx 출력 함수는 HAL_TIM_OC_Start(TIM_HandleTypeDef *htim, uint32_t Channel)인데,
PWM_CHxN 출력 함수는 HAL_TIMEx_OCN_Start(TIM_HandleTypeDef *htim, uint32_t Channel) 입니다.

다음은 CUBEMX 툴로 설정된 내용을 KEIL 코드로 변환한 소스 중에서,
제가 임의로 추가한 부분입니다.
다음은 먼저 main.c 파일의 main() 함수에서 TIM2 Update Interrupt 시작하는 코드와 TIM2 PWM 포트 출력 코드 이고,
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
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_TIM1_Init();
  MX_TIM2_Init();
 
  /* USER CODE BEGIN 2 */
  HAL_Delay(500);
  HAL_TIM_Base_Start_IT(&htim2);
    HAL_TIM_OC_Start(&htim2,TIM_CHANNEL_1);
 
  /* 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

이 TIM2 인터럽트에 의해 TIM1_PWM_CH1 과 TIM2_PWM_CH2N 을 출력하는 코드입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* Prevent unused argument(s) compilation warning */
    if (htim->Instance == TIM2)
    {
        HAL_TIMEx_OCN_Start(&htim1,TIM_CHANNEL_1);
        HAL_TIM_OC_Start(&htim1,TIM_CHANNEL_2);
    }
    else if (htim->Instance == TIM1)
    {
    }
  /* NOTE : This function Should not be modified, when the callback is needed,
            the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file
   */
}
cs


전체 프로젝트를 첨부해 둡니다.

pwm_chN_out.zip



이전에 올린 글 "[STM32F4xx] uSD-Card USB MSC(Mass Storage Class) CUBEMX 설정 (1/2)" 에 이어서,

프로그램 수정 사항을 올립니다.

MSC 의 수정할 부분은 의외로 별로 없습니다.
CUBEMX 에서 설정만 잘 했으면, usbd_storage_if.c 파일만 손보면 됩니다.

1. #include 추가.
1
2
3
4
5
6
/* Includes ------------------------------------------------------------------*/
#include "usbd_storage_if.h"
/* USER CODE BEGIN INCLUDE */
#include "bsp_driver_sd.h"
/* USER CODE END INCLUDE */
 
cs


2. STORAGE_Init_FS() 함수 수정.
1
2
3
4
5
6
7
int8_t STORAGE_Init_FS (uint8_t lun)
{
  /* USER CODE BEGIN 2 */ 
    BSP_SD_Init();
    return (USBD_OK);
  /* USER CODE END 2 */ 
}
cs


3. STORAGE_GetCapacity_FS() 함수 수정.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int8_t STORAGE_GetCapacity_FS (uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
  /* USER CODE BEGIN 3 */   
  HAL_SD_CardInfoTypedef info;
 
    BSP_SD_GetCardInfo(&info);
 
    *block_num = (info.CardCapacity)/STORAGE_BLK_SIZ  - 1;
    *block_size = STORAGE_BLK_SIZ;
//  *block_num  = STORAGE_BLK_NBR;
//  *block_size = STORAGE_BLK_SIZ;
  return (USBD_OK);
  /* USER CODE END 3 */ 
}
cs

4. STORAGE_Read_FS() 함수 수정.
1
2
3
4
5
6
7
8
9
10
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_DMA((uint32_t *)buf, blk_addr * STORAGE_BLK_SIZ,STORAGE_BLK_SIZ, blk_len);
  return (USBD_OK);
  /* USER CODE END 6 */ 
}
cs

5. STORAGE_Write_FS() 함수 수정.
1
2
3
4
5
6
7
8
9
10
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_DMA((uint32_t *)buf, blk_addr * STORAGE_BLK_SIZ,STORAGE_BLK_SIZ, blk_len);
  return (USBD_OK);
  /* USER CODE END 7 */ 
}
cs

이상이고, SDIO_CD (Card Detection) 신호를 사용하려면 위 함수들에 약간의 추가적인 코드가 필요합니다.
이전 글의 마지막에 링크된 웹사이트를 참고하셔서 수정하시면 됩니다.

다음은, 이 USB MSC 로 생성된 USB 드라이브 내용입니다.




엊그제 uSDcard 내용을 읽어서 USB CDC 로 터미널에 뿌렸던 내용과는 너무나 깔끔하고 좋네요.
파일을 PC에서 수정 가능하고, 쉽게 읽고 쓸 수 있습니다. ^^

[엊그제 uSDcard 내용을 읽어서 USB CDC 로 터미널에 뿌렸던 내용




요새 uSD-Card를 보드에 직접 납땜을 해서,

파일을 수정할 방법을 강구하다가 USB MSC(Mass Storage Device Class)를 공부하게 됐습니다.
2일 동안 회사에서 의자에 붙어서 열심히 팠습니다. ㅠㅠ

된다고 하는 여러 예제들이 있었는데, 왜 나만 안되는 것인지.. 참 답답했는데,
원인은 여러가지겠지만, 
가장 큰 원인은 인터럽트 우선순위를 설정하지 않고 모두 0으로 놓으니 동작이 안됐던 것 같았습니다.

그럼, 차근차근 설명을 해 보겠습니다.
제가 테스트하는 H/W 는 
1. SD-Card SDIO-4Bit DMA , 
2. USB Device Only , 
3. Embedded DFU 방식으로 F/W Upload. (BOOT0 핀과 GPIO INPUT 핀에 공통으로 스위치 사용)
4. BOOT1 을 GPIO OUT(LED OUT) 과 공통으로 사용.
5. RCC(HSE)로 24MHz Crystal 사용.
6. Middle Wares -> FATFS -> SD Card 설정.
7. SDPWR 은 필요 없는 기능이니 신경쓰지 마시고요.
으로 어제와 동일합니다.




어제 쓴 글에서는 디렉토리만 읽어서 USB CDC로 표시만 할 수 있었는데, 이번에는 MSC로 읽기 쓰기를 구현해 보고자,
CUBEMX 툴에서 USB_DEVICE 타입을 Mass Storage Class 로만 변경했습니다.






클락 탭의 설정은 다음과 같습니다.






다음은 CUBEMX 의 Configuration TAB의 상세 설정입니다. 
바꾼 것들만 설명을 할 것인데, 그 외의 설정은 디폴드 설정으로 건드리지 않았습니다.
NVIC 는 각각의 상세 설정에도 있고, 따로 NVIC 설정이 분리되어 있는데, 맨 마지막에 NVIC를 수정할 예정입니다.

















다음은 NIVC(인터럽트 우선순위) 설정으로 어떻게 정하는지 저도 잘 모르겠습니다만, MSC 안 되었던 이유 중 가장 큰 원인이, 이 우선순위를 수정하지 않고 모두 우선순위가 0(Default)으로 내버려 뒀기 때문이었습니다.
나중에 같은 실수를 방지하기 위해, 어떤 기준으로 정하는 것인지 확실히 정리해야 할 것 같습니다.






다음으로 HEAP 과 STACK 을 충분히 잡아 두라해서 일단은 최적화는 못했고, 무식하게 크게 잡아두었습니다.





글이 생각보다 너무 길어지고 시간이 너무 늦어서, S/W 쪽은 눈을 좀 붙이고 시간이 나면 작성하도록 하겠습니다.
제가 여러가지를 참고해서 S/W를 만들었는데, 일단 참고한 윕사이트를 링크해 드립니다.



리셋 시, BOOT0 핀과 BOOT1(PB2) 핀은 조합되어서, Booting mode 를 결정하는데 사용한다.


그런데, 리셋 이후의 BOOT0/BOOT1(PB2) 의 기능은?

테스트를 해보겠습니다.

공간의 절약을 위해 스위치를 BOOT0 과 GPIO Input 핀을 같이 연결해서,
스위치를 누르고 리셋을 하면 BOOT0 이 1이 되어 DFU 모드로 동작하고,
리셋 이후에 스위치를 누르면 일반 GPIO Input 기능으로 동작을 하도록 H/W를 구성해서 테스트를 해 봤습니다.

다음이 H/W 구성도 입니다.




바로 전에 쓴 글에서 LED OUT 을 사용해서 리셋 이후, BOOT1(PB2) 을 GPIO OUT으로 사용할 수 있음은 증명이 됐고,

다음은 리셋 이후, BOOT0 과 연결된 GPIO Input 핀에 연결된 스위치를 누르면서 상태를 읽어서,
USB CDC 로 뿌려 본 동영상입니다.



동영상을 보시면 리셋 이후, GPIO Input 핀과 연결된 BOOT0 핀이,
스위치에 의해서 아무 기능을 하지 않는다는 것을 알 수 있습니다.

결론은, BOO0,BOOT1(PB2) 은 리셋 시에만 영향을 미치고, 리셋 이후에는 아무런 기능도 하지 않는다.


아하 이런, 제목이 좀 잘못된 것 같네요. 하지만 Nucleo 보드에서도 적용 가능하니 놔둡니다.
(회사에서 만든 보드니 커스텀 보드 테스트라고 해야 했나? ^^)

[DFU f/w 업로드 과정]

- 간단한 기능의 S/W 를 컴파일해서, 

- ST 에서 다운 받은 응용프로그램(DfuSe 툴: 링크)으로,
- Embedded DFU 모드용 파일로 변환하고, 
- USB 로 MCU에 업로드하는 방법을 알아보겠습니다.

테스트하고 있는 MCU는 STM32F411CCYx(FLASH 256KB / SRAM 128KB / WLCSP) 입니다.
프로그램은 TIM2 Interrupt 에 의해 500ms 마다 LED 와 연결되어 있는 GPIO 를 토글해서 눈으로 동작을 확인할 생각입니다.

TIM2 프로그램은 예전에 올렸던 글을 참조하셔서 작성하면 됩니다.
STM32F446 으로 작성된 내용인데, 클럭만 틀리고 다 같습니다.

혹시 링크타고 가서 보는 것이 번거로울 것 같아서, 다시 CUBEMX 에서 바꿀 부분만 설명 드리자면,
TIM2 의 클럭 소스를 확인해서, (이전글 참고 : [STM32F4xx] Timer Clock source )





CUBEMX 툴의 TIM2 설정에서 0.5ms 에 해당하는 주기를 넣어 준다.




다음으로 CUBEMX에 의해 소스코드를 만들어 주고(저는 KEIL 컴파일러를 사용하므로 KEIL v5로 선택을 했습니다.)
TIM2 인터럽트에 의해 500ms 마다 GPIO를 토글하는 코드를 추가해 줍니다.
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
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* Prevent unused argument(s) compilation warning */
    if (htim->Instance == TIM2)
    {
        HAL_GPIO_TogglePin(LED_OUT_GPIO_Port,LED_OUT_Pin);
    }
    else if (htim->Instance == TIM1)
    {
    }
  /* NOTE : This function Should not be modified, when the callback is needed,
            the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file
   */
}
/* USER CODE END 0 */
 
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_TIM2_Init();
  MX_USB_DEVICE_Init();
 
  /* USER CODE BEGIN 2 */
 
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  HAL_Delay(500);
  HAL_TIM_Base_Start_IT(&htim2);
    
  while (1)
  {
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
 
  }
  /* USER CODE END 3 */
 
}
cs


DFU 로 프로그램을 STM32F4xx 에 업로드 하기 위해서는 HEX 파일이 필요하므로, 
KEIL 컴파일러의 설정에서 다음과 같이 Create HEX File 옵션을 선택해 준다.





다음 과정은 예전에 Custom DFU 에서 설명했던 내용과 똑같은데, 
ST 홈페이지에서 DFU File Manager 를 다운 받아서 실행해서 다음과 같이 입력하고,
S19 or HEX 버튼을 눌러서 생성된 HEX 파일을 선택해 주고 DFU 파일을 원하는 위치에다 만들어 줍니다.




다음으로 DfuSe Demo 프로그램을 ST 사에서 다운 받아서 실행시키고,
BOOT0 을 VCC 로 연결하고 리셋을 합니다(제 경우에는 스위치를 BOOT0 와 VCC 사이에 달아 주고, 스위치를 누른 상태에서 전원을 넣어 줍니다), 
STM Device in DFU Mode 드라이브가 생기면서 DfuSe Demo 프로그램에 해당 칩 정보가 표시됩니다.
DfuSe Demo 프로그램에서 다음과 같이 순서대로 실행하시면 프로그램이 Upgrade 된 후, 실행이 됩니다.
1. Choose 버튼을 눌러 DFU File Manager 에서 만들어 놓은 DFU 파일을 선택한다.
2. Upgrade 버튼을 눌러서 프로그램을 MCU 에 Upload 한다.
3. Leave DFU mode 버튼을 누르거나, BOOT0 핀을 VCC와 연결을 끊고 리셋을 하면 프로그램이 실행됩니다.  





[프로그램 실행 동영상]


이번에는 아주 간단한 DFU(device firmware upgrade) 모드를 테스트 해 보겠습니다.


예전에 올렸던 내용은 System memory에 들어 있는 boot loader를 사용하지 않고,
boot loader 를 직접 만들어서 동작하는 Custom mode DFU를 소개했었습니다.

이번에 설명 드리는 DFU 는 이미 칩생산 시에 들어가 있는 부트로더를 사용하는 것으로 embedded mode DFU 라고도 부릅니다.

여러가지 통신 I/F 를 선택해서 부트로더를 사용할 수 있는데, 
저는 USB 포트를 사용하면 가장 간편하고 빨라서 DFU를 선택했습니다.

[embedded mode bootloader 종류]





부트로드 모드로 들어가기 위해서는 System memory 에 있는 부트로더 프로그램이 실행되도록,
RESET 시 , BOOT0/BOOT1 핀을 다음과 같이 유지시켜 줘야 한다. (리셋 이후에는 필요없음)
1. BOOT0 핀 : 1 (HIGH)
2. BOOT1 핀 : 0 (LOW)





Nucleo 보드로 테스트를 해 봤는데, 
BOOT1 핀은 기본으로 리셋 시 0 으로 인식을 하므로 아무것도 연결 안했고,
BOOT0 만 VDD 와 점퍼로 Short 시켰다.
그리고 이미 전원이 연결되어 있으므로 USB 케이블은 USB DP,USB DM, GND 3선만 연결했다.





[Nucleao 보드 사진]




[USB 케이블 연결 사진]





참고 : PC 쪽 USB 포트 선 결선 정보.





이렇게 하드웨어를 구성했으면, 리셋 버튼을 눌러 주자.
제대로 동작을 한다면, 다음과 같이 PC 장치 드라이브에 드라이버가 설치될 것이다.




다음 번에 hex 파일을 올리는 것을 간단하게 설명 드리겠습니다.

DFU가 SLINK 로 프로그램하는 것보다는 번거롭지만, 
어디를 가든 PC 1대만 있으면 USB 포트로 업데이트가 가능하니 편리합니다.


[2017.04.19] - 내용 추가 
최근에 겪었던 내용을 추가합니다.
얼마전에 여러 기능을 갖고 있는 APP에 UART Rx Interrupt 코드를 추가했는데, 천천이 데이터를 받으면 괜찮은데, 빨리 데이터를 받으면 UART RX Interrupt 기능이 동작하지 않았습니다. 
고전적인 방법을 쓰지 않고 HAL 라이브러리에 의해 프로그램을 만들어서 그런지느리고 무거운 느낌이 들었습니다.
HAL 드라이버가 다양한 기능을 한 번에 처리하고 , 사용하기 쉽다고 하지만 인터럽트 처리에 있어서는 효율이 떨어지는 것 같습니다.

위와 같이 죽었던 문제점은 HAL 드라이버를 사용하지 않았더니 없어졌습니다.
다음의 글을 참고로 코드를 작성했습니다. 설명이 아주 작 되어 있으니 참고하시기 바랍니다.


어쩌면 USB-to-UART 프로그램으로 ESP8266 에 다운로드가 안되는 문제도 위와 같이 수정하면, 잡힐 것도 같습니다.
----------------------------------------------------------------------------------------------

안녕하세요,

글을 쓸 소재들이 요즘 많아 졌는데, 올릴 시간이 없군요.

얼마 전에 PSOC5로, 
1. 일반 TR(MMBT3904)의 내부 다이오드에 정전류를 흘려서 전압을 재서 온도를 측정한 것도 있고,
2. 열전대로 온도 측정 및 열전대 측정값 디지탈 필터로 노이즈 제거,
3. PSOC5 의 새로운 콤포넌트인 LED Driver 로 7-SEGMENT 7개와 8개의 LED 다이나믹 스캔 제어(DMA로 구성되어 CPU에 부하를 주지 않음)
4. 다이오드 상온 측정값과 열전대의 목표지점의 온도를 측정하면서, PID로 전열기를 제어,
5. STM32F4xx 로 USB VCP 를 통해서 UART로 ESP-8266의 프로그램을 Upload (테스트 완료 예정)
6. ESP-8266으로 FTP 서버 만들기(SPIFF:완료,SD-CARD:테스트 중,SOFT AP FTP Server:테스트 중)의 글을 올릴 생각입니다.

이번에는 5. STM32F4xx 로 USB CDC 를 통해서 UART로 ESP-8266의 프로그램을 Upload (테스트 완료 예정) 을 
테스트하기위해 우선, STM32F4xx로 USB-to-UART 통신 테스트를 했습니다.


예전에 USB CDC 로 Tx 테스트를 해서 글을 올려 놓았는데 (http://cafe.naver.com/cortexsh/997),
USB CDC Rx(데이터를 USB Serial Port로 받는) 테스트는 정말 구현하기 힘들었습니다.

여기저기 인터넷을 뒤지고 뒤져서 겨우 단서를 하나 찾았습니다. (http://www.openstm32.org/forumthread2250)




다음의 사이트에서 STM32Cube_FW_F4_Vxx 라는 펌웨어 예제(링크)들이 있는데, 
다운 받은 zip 파일을 풀어, 
그 중에서 STM324xG EVAL/Application/USB_Device/CDC_Standalone 의 프로젝트를 참고하라고 되어 있었습니다.
이 예제를 많이 참고해서 STM32F4xx 보드로 USB-to-UART 를 구현할 수 있었습니다.

STM32F4xx 에서 사용한 내부 기능은 다음과 같습니다.
1. UART2 : UART TX (DMA 전송) , UART RX (RX Interrupt)
2. TIM2 : 5ms 마다 USB Rx로 수신된 데이터를 UART2 로 DMA 전송
3. USB Device Only : USB CDC(Communication Device Class) 초기화 함수를 실행하면,
 데이터가 자동으로(인터럽트에 의해) 수신되면서 CDC_Receive_FS() 함수가 실행됨.




[사용하지 않는 핀 설명]
위의 그림에서 
- ESP_RST(GPIO_OUT) , ESP_FLASH(GPIO_OUT) 은 나중에 ESP-8266 에 프로그램을 업로드하기 위해,
모드를 RUN-MODE,FLASH-MODE 로 변경하기 위해 설정해 놓았고 현재는 사용하지 않는다.
- 또, UART1 Rx/Tx 핀은 ESP-8266 에 프로그램을 Upload 할 때 사용하는 UART 핀으로 설정한 것으로,
현재는 사용하지 않는다.


개략적으로 프로그램 동작을 설명하자면, 
1. STM32F4xx Nucleo 보드에 UART2 와 USB CDC, TIM2 Update Interrupt 기능을 
   CUBEMX 툴로 사용하도록 설정하고 KEIL MDK 5.0 소스코드를 생성.



2. UART2 , USB CDC , TIM2 초기화.
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
69
70
71
72
73
74
75
76
77
78
79
80
81
// usb_device.c
/* init function */                        
void MX_USB_DEVICE_Init(void)
{
  /* Init Device Library,Add Supported Class and Start the library*/
  USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
 
  USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC);
 
  USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);
 
  USBD_Start(&hUsbDeviceFS);
 
}
 
------------------------------------------------------------------------------
 
// main.c 
/* USART2 init function */
static void MX_USART2_UART_Init(void)
{
 
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
 
}
 
static void MX_DMA_Init(void
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();
 
  /* DMA interrupt init */
  /* DMA1_Stream6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 00);
  HAL_NVIC_EnableIRQ(DMA1_Stream6_IRQn);
 
}
 
/* TIM2 init function */
static void MX_TIM2_Init(void)
{
 
  TIM_ClockConfigTypeDef sClockSourceConfig;
  TIM_MasterConfigTypeDef sMasterConfig;
 
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 0;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 960000/2;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
 
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
 
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
 
}
 
cs

3. UART2 RX인터럽트에 의해 데이터 수신시 USB CDC TX 큐버퍼에 데이터 저장. (in main.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  /* Increment Index for buffer writing */
  UserTxBufPtrIn++;
  
  /* To avoid buffer overflow */
  if(UserTxBufPtrIn == APP_RX_DATA_SIZE)
  {
    UserTxBufPtrIn = 0;
  }
  
  /* Start another reception: provide the buffer pointer with offset and the buffer size */
  HAL_UART_Receive_IT(huart, (uint8_t *)(UserTxBufferFS + UserTxBufPtrIn), 1);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
}
cs

4. 5ms 마다 TIM2 Update 인터럽트가 걸리면 UART2 RX로 받은 데이터가 있는지 체크해서  
USB CDC TX 큐버퍼에 있는 데이터를 USB CDC Tx 핀으로 모두 송신. (in 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
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    uint32_t buffptr;
    uint32_t buffsize;
 /* Prevent unused argument(s) compilation warning */
    if (htim->Instance == TIM2)
    {
  
  if(UserTxBufPtrOut != UserTxBufPtrIn)
  {
    if(UserTxBufPtrOut > UserTxBufPtrIn) /* Rollback */
    {
      buffsize = APP_TX_DATA_SIZE - UserTxBufPtrOut;
    }
    else 
    {
      buffsize = UserTxBufPtrIn - UserTxBufPtrOut;
    }
    
    buffptr = UserTxBufPtrOut;
    
    USBD_CDC_SetTxBuffer(&hUsbDeviceFS, (uint8_t*)&UserTxBufferFS[buffptr], buffsize);
    
    if(USBD_CDC_TransmitPacket(&hUsbDeviceFS) == USBD_OK)
    {
      UserTxBufPtrOut += buffsize;
      if (UserTxBufPtrOut == APP_RX_DATA_SIZE)
      {
        UserTxBufPtrOut = 0;
      }
    }
  }
    }
    else if (htim->Instance == TIM1)
    {
    }
  /* NOTE : This function Should not be modified, when the callback is needed,
            the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file
   */
}
cs

5. USB CDC Rx로 인터럽트에 의해 Rx 큐버퍼에 데이터가 수신되면,
 바로 UART2 로 데이터를 DMA 방식으로 전송. (in usbd_cdc_if.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
/**
  * @brief  CDC_Receive_FS
  *         Data received over USB OUT endpoint are sent over CDC interface 
  *         through this function.
  *           
  *         @note
  *         This function will block any OUT packet reception on USB endpoint 
  *         untill exiting this function. If you exit this function before transfer
  *         is complete on CDC interface (ie. using DMA controller) it will result 
  *         in receiving more data while previous ones are still not sent.
  *                 
  * @param  Buf: Buffer of data to be received
  * @param  Len: Number of data received (in bytes)
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */
 
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
    HAL_UART_Transmit_DMA(&huart2, Buf, *Len);
  return (USBD_OK);
  /* USER CODE END 6 */ 
}
 
cs

6. 1~5번 항목을 무한 반복.


다음은 실제 Nucleo 보드와 USB 케이블 연결된 사진 입니다.





다음 그림은 프로그램을 실행함에 따라서, 장치관리자로 COM 포트가 생성된 모습입니다.




다음은 테라텀 프로그램으로 각각의 COM 포트에 연결해서 송수신 테스트를 해 본 사진입니다.




다음은 실시간 테스트 영상입니다.


마지막으로 이 프로젝트의 소스 파일을 첨부합니다.


+ Recent posts